Skip to content

Instantly share code, notes, and snippets.

@eesur
Last active October 23, 2019 21:52
Show Gist options
  • Save eesur/6992045bafc8da95b98b to your computer and use it in GitHub Desktop.
Save eesur/6992045bafc8da95b98b to your computer and use it in GitHub Desktop.
D3 Line chart with toggle legend

D3 Multiple Line chart with toggle legend

Line chart updates the yAxis depending on selection from the legend.

The legend uses purecss for the menu, which can be made into a responsive menu.

The legend has been added to the re-useable components from ScottLogic's awesome d3-financial-components

(function(d3, fc) {
'use strict';
//************************************************************
// Set up variables
//************************************************************
// config references
var chartConfig = {
title : 'Line chart with toggle legend',
target : 'chart-id',
dataUrl : 'pseudo_data.json',
updateVars : {}
};
// access the keys from data
var dataKeys = [];
// varibles used for updates
var updateVars = fc.utilities.defaultConfig().updateVars;
// create container var for different line series
var lineSeriesArray = updateVars.lineSeriesArray;
// create an alt array of dataKeys for updating yScale via legend
var dataKeysUser = updateVars.dataKeysUser; // undefined
// helper function to splice array
function arraySplice(_array, _string) {
var i = _array.indexOf(_string);
if (i != -1) {
_array.splice(i, 1);
}
}
// -----------------------------
function updateUrl() {
return chartConfig.dataUrl;
}
// -----------------------------
// instantiate data config
var data = fc.utilities.dataConfig(),
parseDate = d3.time.format('%Y-%m-%d %H:%M:%S').parse,
formatDate = d3.time.format("%H:%M:%S");
// -----------------------------
// select chart container(s)
var chart = d3.select('#' + chartConfig.target);
// -----------------------------
// set chart title
function chartTitle() {
var title = d3.select('#chart-title');
title.text(chartConfig.title);
}
// -----------------------------
// id for spin loader
var target = document.getElementById(chartConfig.target);
//************************************************************
// JSON callback
//************************************************************
function init(dataUrl, variables) {
// trigger loader unless already active
if (d3.select('.spinner').empty()) {
var spinner = new Spinner(opts).spin(target);
}
// load json data and trigger callback
data.loadJson(dataUrl, function(data) {
// access keys (except date) for legend and yScale extent
dataKeys = d3.keys(data.data[0]).filter(function(d) { return d !== 'date'; }).sort(d3.ascending);
// instantiate functions within callback
dataFormat(data.data);
chartTitle();
lineChart(data.data);
renderLegend(data.data);
// stop the loader
if (!d3.select('.spinner').empty()) {
spinner.stop();
}
});
}
init(updateUrl(), updateVars);
//************************************************************
// Clean/format data
//************************************************************
function dataFormat(data) {
data.forEach(function(d) {
d.date = parseDate(d.date);
for (var i = 0; i < dataKeys.length; i++) {
// ensure numeric value
d[dataKeys[i]] = +d[dataKeys[i]];
// set zero for missing data
if (isNaN(d[dataKeys[i]])) {
d[dataKeys[i]] = 0;
}
}
});
}
//************************************************************
// Line chart
//************************************************************
function lineChart(data) {
// check if dataKeysUser is empty or undefined
if (!_.isArray(dataKeysUser) || _.isEmpty(dataKeysUser) ) {
dataKeysUser = [];
dataKeysUser = dataKeysUser.concat(dataKeys);
}
var chartLayout = fc.utilities.chartLayout();
chartLayout
.marginLeft(60)
.marginRight(20)
.marginBottom(180);
var chartBuilder = fc.utilities.chartBuilder(chartLayout);
chart
.call(chartBuilder);
// -----------------------------
// Create scale for x axis
var xScale = d3.time.scale()
.domain(d3.extent(data, function(d) { return d.date; }))
.range([0, chartLayout.getPlotAreaWidth()])
.nice();
// create scale for left y axis
var yScale = d3.scale.linear()
.domain(fc.utilities.extent(data, dataKeysUser))
.range([chartLayout.getPlotAreaHeight(), 0])
.nice();
// -----------------------------
// ensure locale time for x axis using moment.js
var timeFormatter = function(date) {
return moment(date).format("LT");
};
// create the axes
var xAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom')
.tickPadding(8)
.ticks(d3.time.minutes, 10)
.tickFormat(timeFormatter);
var yAxis = d3.svg.axis()
.scale(yScale)
.orient('left')
.tickPadding(8)
.ticks(9);
function minorTicks() {
d3.selectAll('.minor').remove();
this.selectAll('svg .bottom line').data(xScale.ticks(d3.time.minutes), function(d) { return d; })
.enter()
.append('line')
.attr('class', 'minor')
.attr('y1', 0)
.attr('y2', 5)
.attr('x1', xScale)
.attr('x2', xScale);
}
// add minor ticks to chart
chartLayout.getAxisContainer('bottom')
.call(minorTicks);
// -----------------------------
// create line series
// create 'empty' array
var lineSeriesArray = d3.range(dataKeys.length);
// loop through to store the variable containers to create each line
for (var i = 0; i <= dataKeys.length; i++) {
lineSeriesArray[i] = newLine(i+1);
}
// function to call required component code
function newLine(i) {
return fc.series.lineMultiple()
.xValue(function(d) { return d.date; })
.yValue(function(d) { return d[dataKeys[i-1]]; })
.lineColour(i)
.xScale(xScale)
.yScale(yScale);
}
// -----------------------------
// add the components to the chart
chartBuilder.setAxis('bottom', xAxis);
chartBuilder.setAxis('left', yAxis);
chartBuilder.addToPlotArea(lineSeriesArray);
// -----------------------------
// associate the data
chartBuilder.setData(data);
// draw
chartBuilder.render();
// -----------------------------
}
//************************************************************
// Legend
//************************************************************
function renderLegend(data) {
// create legend
var legend = fc.tools.legend()
.dataKeys(dataKeys)
.dataKeysUser(dataKeysUser)
.lineReferences(lineSeriesArray);
// event to update yScale when toggling line series
legend.on('toggleLegend', function(d, i) {
// console.log('toggleLegend: ' + d, i);
var line = d3.select('.line-' + (i+1)); // just adding one to the zero index of the array
var lineId = d.replace(/\s+/g, '-').toLowerCase();
// toggle item in array
if (!_.contains(dataKeysUser, d)) {
dataKeysUser.push(d);
line
.style('opacity', 1)
.classed('legend-active', false);
d3.select('#' + lineId).style('opacity', 1);
} else if (_.contains(dataKeysUser, d) && dataKeysUser.length > 1) {
arraySplice(dataKeysUser, d);
line
.style('opacity', 0.2)
.classed('legend-active', true);
d3.select('#' + lineId).style('opacity', 0.2);
}
// render updated chart view
updateChart();
});
// Add the legend
d3.select('#legend-list')
.call(legend);
}
// -----------------------------
function updateChart() {
init(updateUrl(), updateVars);
}
// listener for window resize
window.addEventListener('resize', updateChart);
})(d3, fc);
/*svg styles*/
path.line {
fill: none;
/*stroke: #06c;*/
stroke: #d62728; /*red*/
}
path.area {
fill: #9cf;
fill-opacity: 0.5;
}
/*multiple Line Series Styles */
[class$="-series"] * {
vector-effect: non-scaling-stroke;
}
/* For multiple line series*/
path.line-1 , path.line-blue {
fill: none;
stroke: #d62728; /*red*/
}
/* Line Series Styles 2 */
path.line-2, path.line-red {
fill: none;
stroke: #1f77b4; /*blue*/
}
/* Line Series Styles 3 */
path.line-3, path.line-green {
fill: none;
stroke: #2ca02c; /*green*/
}
/* Line Series Styles 4 */
path.line-4, path.line-orange {
fill: none;
stroke: #ff7f0e; /*orange*/
}
/* Line Series Styles 5 */
path.line-5, path.line-purple {
fill: none;
stroke: #9467bd; /*purple*/
}
/* Line Series Styles 6 */
path.line-6, path.line-brown {
fill: none;
stroke: #8c564b; /*brown*/
}
/* Line Series Styles 7 */
path.line-7, path.line-grey {
fill: none;
stroke: #7f7f7f; /*grey*/
}
/* Line Series Styles 8 */
path.line-8, path.line-lime {
fill: none;
stroke: #bcbd22; /*lime*/
}
/* Line Series Styles 9 */
path.line-9, path.line-java {
fill: none;
stroke: #17becf; /*java*/
}
/* Line Series Styles 10 */
path.line-10, path.line-java {
fill: none;
stroke: #e377c2; /*java*/
}
/* Transparent overlay for interactive tools */
.overlay {
stroke-width: 0px;
fill-opacity: 0;
}
/* Default Axis Styles */
.axis .domain,
.axis .tick line {
fill: none;
stroke-width: 1.0;
stroke: #bbb;
}
.gridlines {
stroke: #ccc;
stroke-width: 1.0;
}
.chartArea rect.background {
stroke: #eee;
fill: #fefefe;
}
.axis .minor {
stroke: #ccc;
stroke-width: 1px;
}
g.axis.bottom {
margin-bottom: 20px;
}
.axis text {
font: 11px "Source Code Pro", Consolas, monaco, monospace;
}
/*html styles*/
html,
body,
.fullscreen,
.chart-fullscreen {
max-width: 100%;
width: 100% !important;
height: 100% !important;
}
body {
font-family: "Source Code Pro", Consolas, monaco, monospace;
line-height: 1.5;
}
.chart-fullscreen {
min-height: 100%;
position: relative;
}
.fullscreen-padding {
padding: 5px;
padding-bottom: 100px;
}
#chart-wrap {
position: relative;
}
header {
position: relative;
}
#chart-title-wrap {
position: relative;
}
#chart-title-wrap h1 {
position: relative;
margin: 0px;
text-align: center;
padding: 13px 0 0 10px;
font-size: 21px;
color: #454545;
line-height: 110%;
font-weight: 400;
}
#error {
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
background: #ff851b;
font-size: 16px;
display: inline-block;
padding: 8px 16px;
color: #ffffff;
}
.legend-group {
display: inline-block;
width: 100px;
}
#legend-html {
padding-top: 5px;
padding-left: 40px;
font-size: 13px;
position: absolute;
bottom: 100px;
}
#legend-html ul {
max-width: auto;
overflow: hidden;
}
#legend-html li {
cursor: pointer;
font-size: 13px;
margin-bottom: 3px;
margin-left: 15px;
}
#legend-html li:hover {
opacity: 0.6;
}
#legend-html li span {
padding: 2px 4px;
pointer-events: none;
}
.legend-line {
font-size: 14px;
line-height: 13px;
}
(function(d3, fc) {
'use strict';
fc.utilities.dataConfig = function() {
var config = {},
dispatch = d3.dispatch('dataReady', 'dataLoading'),
data;
var chartId = fc.utilities.defaultConfig().chartId;
// create a method to load json file, and a callback on response
config.loadJson = function(_file, _callback) {
// load json file using d3.json.
d3.json(_file, function (_err, _data) {
//Execute the callback, assign the data to the context.
// produce error if JSON load fails
if (_data) {
console.log('data loaded');
} else if (_err) {
var errorMsg = d3.select('#error');
if (errorMsg.empty()) {
var errorMsg = d3.select('.chart')
.append('h3')
.attr('id', 'error')
.style("opacity", 0)
.html('! Error loading chart data ');
errorMsg.transition()
.style("opacity", 1);
}
return console.warn(_err);
}
_callback(_data);
});
};
d3.rebind(config, dispatch, 'on');
config.chartId = function(value) {
if (!arguments.length) return chartId;
chartId = value;
return this;
};
return config;
};
}(d3, fc));
(function(d3, fc) {
'use strict';
fc.utilities.defaultConfig = function() {
var config = {};
var chartId = 'chart-id';
config.colourArray = ['#d62728' , '#1f77b4', '#2ca02c', '#ff7f0e', '#9467bd', ' #8c564b', '#7f7f7f', '#bcbd22', '#17becf', '#e377c2'];
// add colour range based on d3.scale.category10()
config.colours = d3.scale.ordinal()
.domain(['red', 'blue', 'green', 'orange', 'purple', 'brown', 'grey', 'lime', 'java', 'orchid'])
.range(config.colourArray);
// set locale time using [moment.js](http://momentjs.com/docs/#/customization/long-date-formats/)
moment.locale('en', {
longDateFormat : {
LT: "h:mm",
LTS: "h:mm:ss A",
L: "MM/DD/YYYY",
LL: "MMMM Do YYYY",
LLL: "MMMM Do YYYY LT",
LLLL: "dddd, MMMM Do YYYY LT"
}
});
// update object
config.updateVars = {};
config.chartId = function(value) {
if (!arguments.length) return chartId;
chartId = value;
return this;
};
return config;
};
}(d3, fc));
/* globals window */
/**
* A collection of components that make it easy to build interactive financial charts with D3
*
* @namespace fc
*/
window.fc = {
version: '0.0.0',
/**
* Studies, trend-lines and other financial indicators that can be added to a chart
*
* @namespace fc.indicators
*/
indicators: {},
math: {},
/**
* Useful complex scales which add to the D3 scales in terms of render quality.
* Also, complex financial scales that can be added to a chart
*
* @namespace fc.scale
*/
scale: {
discontinuity: {}
},
series: {},
/**
* Interaction components include events and user options to change views
* Features such as Zoom, Panning
* @namespace fc.interaction
*/
interaction: {},
tools: {},
/**
* Utility components to shorted long winded implementations of common operations.
* Also includes components for mock data generation and layout.
*
* @namespace fc.utilities
*/
utilities: {}
};
(function(d3, fc) {
'use strict';
/**
* The chart builder makes it easier to constructs charts from a number of 'fc' or D3 components. It
* adapts a chartLayout (which is responsible for creating a suitable SVG structure for a chart), and allows
* you to associate components (axes, series, etc ...) with the chart. The chart builder
* is responsible for associating data with the components, setting the ranges of the scales and updating
* the components when the chart needs to be re-drawn.
*
* @type {object}
* @memberof fc.utilities
* @class fc.utilities.chartBuilder
*/
fc.utilities.chartBuilder = function(chartLayout) {
// the components that have been added to the chart.
var plotAreaComponents = [];
var axes = {};
// the selection that this chart is associated with
var callingSelection;
var chartBuilder = function(selection) {
callingSelection = selection;
selection.call(chartLayout);
};
/**
* Adds a number of components to the chart plot area. The chart layout is responsible for
* rendering these components via the render function.
*
* @memberof fc.utilities.chartBuilder#
* @method addToPlotArea
* @param {array} components an array of components to add to the plot area
*/
chartBuilder.addToPlotArea = function(components) {
plotAreaComponents = plotAreaComponents.concat(components);
};
/**
* Provides the data that will be joined with the plot area selection, and as a result
* is the data used by components that are associated with the plot area.
*
* @memberof fc.utilities.chartBuilder#
* @method setData
* @param {array} data the data to associate with the plot area
*/
chartBuilder.setData = function(data) {
chartLayout.getPlotArea().datum(data);
};
/**
* Sets the chart axis with the given orientation. The chart builder is responsible for setting
* the range of this axis and rendering it via the render function.
*
* @memberof fc.utilities.chartBuilder#
* @method setAxis
* @param {string} orientation The orientation of the axis container
* @param {object} axis a D3 or 'fc' axis component
*/
chartBuilder.setAxis = function(orientation, axis) {
axes[orientation] = axis;
};
/**
* Renders all of the components associated with this chart. During the render process
* the axes have their scales set to an appropriate value.
*
* @memberof fc.utilities.chartBuilder#
* @method render
*/
chartBuilder.render = function() {
callingSelection.call(chartLayout);
// call each of the axis components with the axis selection
for (var axisOrientation in axes) {
if (axes.hasOwnProperty(axisOrientation)) {
var axisContainer = chartLayout.getAxisContainer(axisOrientation);
var axis = axes[axisOrientation];
if (axisOrientation === 'top' || axisOrientation === 'bottom') {
axis.scale().range([0, chartLayout.getPlotAreaWidth()]);
} else {
axis.scale().range([chartLayout.getPlotAreaHeight(), 0]);
}
axisContainer.call(axis);
}
}
// call each of the plot area components
plotAreaComponents.forEach(function(component) {
chartLayout.getPlotArea().call(component);
});
};
return chartBuilder;
};
}(d3, fc));
(function(d3, fc) {
'use strict';
/**
* Based on the [Margin Convention]{@link http://bl.ocks.org/mbostock/3019563},
* the Chart Layout component is responsible for defining the chart area.
*
* It attempts to simplify the repetitive process of constructing the chart's layout and its associated elements:
* <ul>
* <li>Define the margins, height and width</li>
* <li>Calculate the inner height and inner width</li>
* <li>Create an SVG</li>
* <li>Create a group for all chart elements; translate it based on the margins</li>
* <li>Create a clipping path for the plot area; add it to the group</li>
* <li>Create groups for the axes</li>
* </ul>
*
* If the width or height of the component have not been explicitly set using chartLayout.height()
* or chartLayout.width(), then the width and height of the chartLayout will try to expand to the
* dimensions of the selected element. If this results in an invalid value, i.e. less than 1,
* a default value will be used.
*
* <hr>
*
* Given a div:
* <pre>
* &lt;div id=&quot;myChart&quot; style=&quot;width:650px; height:300px;&quot;&gt;&lt;/div&gt;
* </pre>
*
* Chart Layout will tranform the selection to create the following elements:
* <pre>
* &lt;div id=&quot;myChart&quot; style=&quot;width:650px; height:300px;&quot;&gt;
* &lt;svg width=&quot;650&quot; height=&quot;300&quot;&gt;
* &lt;g class=&quot;chartArea&quot; transform=&quot;translate(40,20)&quot;&gt;
* &lt;defs&gt;
* &lt;clipPath id=&quot;fcPlotAreaClip_myChart&quot;&gt;
* &lt;rect width=&quot;570&quot; height=&quot;260&quot;&gt;&lt;/rect&gt;
* &lt;/clipPath&gt;
* &lt;/defs&gt;
* &lt;rect class=&quot;background&quot; width=&quot;570&quot; height=&quot;260&quot;&gt;&lt;/rect&gt;
* &lt;g clip-path=&quot;url(#fcPlotAreaClip_myChart)&quot; class=&quot;plotArea&quot;&gt;&lt;/g&gt;
* &lt;g class=&quot;axis bottom&quot; transform=&quot;translate(0,260)&quot;&gt;&lt;/g&gt;
* &lt;g class=&quot;axis top&quot; transform=&quot;translate(0, 0)&quot;&gt;&lt;/g&gt;
* &lt;g class=&quot;axis right&quot; transform=&quot;translate(570, 0)&quot;&gt;&lt;/g&gt;
* &lt;/g&gt;
* &lt;/svg&gt;
* &lt;/div&gt;
* </pre>
*
* @type {object}
* @memberof fc.utilities
* @class fc.utilities.chartLayout
*/
fc.utilities.chartLayout = function() {
// Default values
var margin = {top: 20, right: 40, bottom: 20, left: 40},
width = 0,
height = 0;
var defaultWidth = true,
defaultHeight = true;
// The elements created for the chart
var chartElements = {};
var plotAreaClipId;
/**
* Constructs a new instance of the chartLayout component.
*
* Applies the chartLayout to a [D3 selection]{@link https://github.com/mbostock/d3/wiki/Selections}
* (commonly a <code>div</code>).
* The chartLayout component can only be applied to the first element in a selection,
* all other elements will be ignored.
*
* @example
* // Setup the chart layout
* var layout = fc.utilities.chartLayout();
*
* // Setup the chart
* var setupArea = d3.select('#chart')
* .call(layout);
*
* @memberof fc.utilities.chartLayout#
* @method chartLayout
* @param {selection} selection a D3 selection
*/
var chartLayout = function(selection) {
// Select the first element in the selection
// If the selection contains more than 1 element,
// only the first will be used, the others will be ignored
var element = selection.node(),
style = getComputedStyle(element);
// Attempt to automatically size the chart to the selected element
if (defaultWidth === true) {
// Set the width of the chart to the width of the selected element,
// excluding any margins, padding or borders
var paddingWidth = parseInt(style.paddingLeft, 10) + parseInt(style.paddingRight, 10);
width = element.clientWidth - paddingWidth;
// If the new width is too small, use a default width
if (chartLayout.getPlotAreaWidth() < 1) {
width = 600 + margin.left + margin.right;
}
}
if (defaultHeight === true) {
// Set the height of the chart to the height of the selected element,
// excluding any margins, padding or borders
var paddingHeight = parseInt(style.paddingTop, 10) + parseInt(style.paddingBottom, 10);
height = element.clientHeight - paddingHeight;
// If the new height is too small, use a default height
if (chartLayout.getPlotAreaHeight() < 1) {
height = 400 + margin.top + margin.bottom;
}
}
// Setup the elements - following the general update pattern (http://bl.ocks.org/mbostock/3808218)
//
// When creating the elements for the chart, only one of each element is required. To achieve this we bind
// a single datum to each selection - this is represented in the dummyData variable. This data-join is only
// used for creating and updating the elements - through data(), enter() and exit(); the value of the data
// is irrelevant (but there must only be one value). This approach is similar to that used in D3's axis
// and brush components.
//
// For each element, we:
// 1. Select the element(s) and bind a single datum to that selection
// 2. If no element currently exists, append it (this is in the enter() subselection)
// 3. Update the element as required
// 4. If there are too many of the selected element(>1), then remove it (this is in the exit() subselection)
var container = d3.select(element),
dummyData = [0];
// Create svg
var svg = container.selectAll('svg').data(dummyData);
svg.enter().append('svg');
svg.attr('width', width)
.attr('height', height)
.style('display', 'block');
svg.exit().remove();
// Create group for the chart
function roundToNearestHalfInteger(n) {
var m = Math.round(n);
return n > m ? m + 0.5 : m - 0.5;
}
var chart = svg.selectAll('g.chartArea').data(dummyData);
chart.enter().append('g')
.classed('chartArea', true);
chart.attr('transform', 'translate(' +
roundToNearestHalfInteger(margin.left) + ',' +
roundToNearestHalfInteger(margin.top) + ')');
chart.exit().remove();
// Create defs - for clipping path
var defs = chart.selectAll('defs').data(dummyData);
defs.enter().append('defs');
defs.exit().remove();
// Get an ID for the clipping path
// If the element already has an ID, use that; otherwise, generate one (to avoid duplicate IDs)
plotAreaClipId = plotAreaClipId || 'fcPlotAreaClip_' + (element.id || nextId());
// Clipping path
var clippingPath = defs.selectAll('#' + plotAreaClipId).data(dummyData);
clippingPath.enter().append('clipPath')
.attr('id', plotAreaClipId);
clippingPath.exit().remove();
// Clipping path rect
var clippingPathRect = clippingPath.selectAll('rect').data(dummyData);
clippingPathRect.enter().append('rect');
clippingPathRect
.attr('width', chartLayout.getPlotAreaWidth())
.attr('height', chartLayout.getPlotAreaHeight());
clippingPathRect.exit().remove();
// Create a background element
var plotAreaBackground = chart.selectAll('rect.background').data(dummyData);
plotAreaBackground.enter().append('rect')
.classed('background', true);
plotAreaBackground
.attr('width', chartLayout.getPlotAreaWidth())
.attr('height', chartLayout.getPlotAreaHeight());
plotAreaBackground.exit().remove();
// Create plot area, using the clipping path
// NOTE: We do not use a data-join to 'dummy data' here, because it is expected that the
// user (or chartBuilder) will want to data-join the plotArea with their own data in order
// that it is inherited by the series within the chart
var plotArea = chart.selectAll('g.plotArea');
if (plotArea.empty()) {
plotArea = chart.append('g')
.attr('clip-path', 'url(#' + plotAreaClipId + ')')
.attr('class', 'plotArea');
}
// Add selections to the chart elements object for the getters
chartElements = {
svg: svg,
chartArea: chart,
defs: defs,
plotAreaBackground: plotAreaBackground,
plotArea: plotArea
};
// Create containers for the axes
if (!chartElements.axisContainer) {
chartElements.axisContainer = {};
}
function createAxis(orientation, translation) {
var selection = chart.selectAll('g.axis.' + orientation).data(dummyData);
selection.enter().append('g')
.attr('class', 'axis ' + orientation);
selection.attr('transform', translation);
selection.exit().remove();
if (!chartElements.axisContainer[orientation]) {
chartElements.axisContainer[orientation] = {};
}
chartElements.axisContainer[orientation].selection = selection;
}
createAxis('bottom', 'translate(0, ' + chartLayout.getPlotAreaHeight() + ')');
createAxis('top', 'translate(0, 0)');
createAxis('left', 'translate(0, 0)');
createAxis('right', 'translate(' + chartLayout.getPlotAreaWidth() + ', 0)');
};
/**
* Get/set the size of the top margin between the chart area
* and the edge of its parent SVG.
*
* Increasing the size of a margin affords more space for an axis in the corresponding position.
*
* @memberof fc.utilities.chartLayout#
* @method marginTop
* @param {number} [value] The size of the top margin
* @returns {number|chartLayout} If value is specified, sets the top margin and returns the chartLayout;
* if value is not specified, returns the top margin.
*/
chartLayout.marginTop = function(value) {
if (!arguments.length) {
return margin.top;
}
margin.top = value;
return chartLayout;
};
/**
* Get/set the size of the right margin between the chart area
* and the edge of its parent SVG.
*
* Increasing the size of a margin affords more space for an axis in the corresponding position.
*
* @memberof fc.utilities.chartLayout#
* @method marginRight
* @param {number} [value] The size of the right margin
* @returns {number|chartLayout} If value is specified, sets the right margin and returns the chartLayout;
* if value is not specified, returns the right margin.
*/
chartLayout.marginRight = function(value) {
if (!arguments.length) {
return margin.right;
}
margin.right = value;
return chartLayout;
};
/**
* Get/set the size of the bottom margin between the chart area
* and the edge of its parent SVG.
*
* Increasing the size of a margin affords more space for an axis in the corresponding position.
*
* @memberof fc.utilities.chartLayout#
* @method marginBottom
* @param {number} [value] The size of the bottom margin
* @returns {number|chartLayout} If value is specified, sets the bottom margin and returns the chartLayout;
* if value is not specified, returns the bottom margin.
*/
chartLayout.marginBottom = function(value) {
if (!arguments.length) {
return margin.bottom;
}
margin.bottom = value;
return chartLayout;
};
/**
* Get/set the size of the left margin between the chart area
* and the edge of its parent SVG.
*
* Increasing the size of a margin affords more space for an axis in the corresponding position.
*
* @memberof fc.utilities.chartLayout#
* @method marginLeft
* @param {number} [value] The size of the left margin
* @returns {number|chartLayout} If value is specified, sets the left margin and returns the chartLayout;
* if value is not specified, returns the left margin.
*/
chartLayout.marginLeft = function(value) {
if (!arguments.length) {
return margin.left;
}
margin.left = value;
return chartLayout;
};
/**
* Get/set the width of the chart.
*
* If the width of the chart is not explicitly set before calling chartLayout on a selection,
* the component will attempt to size the chart to the dimensions of the selection's first element.
*
* @memberof fc.utilities.chartLayout#
* @method width
* @param {number} [value] The width of the chart
* @returns {number|chartLayout} If value is specified, sets the width and returns the chartLayout;
* if value is not specified, returns the width.
*/
chartLayout.width = function(value) {
if (!arguments.length) {
return width;
}
width = value;
defaultWidth = false;
return chartLayout;
};
/**
* Get/set the height of the chart.
*
* If the height of the chart is not explicitly set before calling chartLayout on a selection,
* the component will attempt to size the chart to the dimensions of the selection's first element.
*
* @memberof fc.utilities.chartLayout#
* @method height
* @param {number} [value] The height of the chart
* @returns {number|chartLayout} If value is specified, sets the height and returns the chartLayout;
* if value is not specified, returns the height.
*/
chartLayout.height = function(value) {
if (!arguments.length) {
return height;
}
height = value;
defaultHeight = false;
return chartLayout;
};
/**
* Get the width of the plot area. This is the total width of the chart minus the horizontal margins.
*
* @memberof fc.utilities.chartLayout#
* @method getPlotAreaWidth
* @returns {number} The width of the plot area.
*/
chartLayout.getPlotAreaWidth = function() {
return width - margin.left - margin.right;
};
/**
* Get the height of the plot area. This is the total height of the chart minus the vertical margins.
*
* @memberof fc.utilities.chartLayout#
* @method getPlotAreaHeight
* @returns {number} The height of the plot area.
*/
chartLayout.getPlotAreaHeight = function() {
return height - margin.top - margin.bottom;
};
/**
* Get the SVG for the chart.
*
* @memberof fc.utilities.chartLayout#
* @method getSVG
* @returns {selection} The SVG for the chart.
*/
chartLayout.getSVG = function() {
return chartElements.svg;
};
/**
* Get the defs element for the chart.
* The defs element can contain elements to be reused in the SVG, after they're defined;
* for example - a clipping path.
*
* @memberof fc.utilities.chartLayout#
* @method getDefs
* @returns {selection} The defs element for the chart.
*/
chartLayout.getDefs = function() {
return chartElements.defs;
};
/**
* Get the chart area group for the chart.
* Typically axes will be added to the chart area.
*
* @memberof fc.utilities.chartLayout#
* @method getChartArea
* @returns {selection} The chart's plot area.
*/
chartLayout.getChartArea = function() {
return chartElements.chartArea;
};
/**
* Get the plot area's background element.
*
* @memberof fc.utilities.chartLayout#
* @method getPlotAreaBackground
* @returns {selection} The background rect of the plot area.
*/
chartLayout.getPlotAreaBackground = function() {
return chartElements.plotAreaBackground;
};
/**
* Get the plot area group for the chart.
* The plot area has a clipping path, so this is typically where series and indicators will be added.
*
* @memberof fc.utilities.chartLayout#
* @method getPlotArea
* @returns {selection} The chart's plot area.
*/
chartLayout.getPlotArea = function() {
return chartElements.plotArea;
};
/**
* Get the group container for an axis.
*
* @memberof fc.utilities.chartLayout#
* @method getAxisContainer
* @param {string} orientation The orientation of the axis container;
* valid values are 'top', 'bottom', 'left' or 'right'
* @returns {selection} The group for the specified axis orientation.
*/
chartLayout.getAxisContainer = function(orientation) {
return chartElements.axisContainer[orientation].selection;
};
return chartLayout;
};
// Generates an integer ID
var nextId = (function() {
var id = 0;
return function() {
return ++id;
};
})();
}(d3, fc));
(function(d3, fc) {
'use strict';
/**
* The extent function enhances the functionality of the equivalent D3 extent function, allowing
* you to pass an array of fields which will be used to derive the extent of the supplied array. For
* example, if you have an array of items with properties of 'high' and 'low', you
* can use <code>fc.utilities.extent(data, ['high', 'low'])</code> to compute the extent of your data.
*
* @memberof fc.utilities
* @param {array} data an array of data points, or an array of arrays of data points
* @param {array} fields the names of object properties that represent field values
*/
fc.utilities.extent = function(data, fields) {
if (fields === null) {
return d3.extent(data);
}
// the function only operates on arrays of arrays, but we can pass non-array types in
if (!Array.isArray(data)) {
data = [data];
}
// we need an array of arrays if we don't have one already
if (!Array.isArray(data[0])) {
data = [data];
}
// the fields parameter must be an array of field names, but we can pass non-array types in
if (!Array.isArray(fields)) {
fields = [fields];
}
// Return the smallest and largest
return [
d3.min(data, function(d0) {
return d3.min(d0, function(d1) {
return d3.min(fields.map(function(f) {
return d1[f];
}));
});
}),
d3.max(data, function(d0) {
return d3.max(d0, function(d1) {
return d3.max(fields.map(function(f) {
return d1[f];
}));
});
})
];
};
}(d3, fc));
(function(d3, fc) {
'use strict';
fc.utilities.fn = {
identity: function(d) { return d; },
noop: function(d) { }
};
}(d3, fc));
(function(d3, fc) {
'use strict';
// a property that follows the D3 component convention for accessors
// see: http://bost.ocks.org/mike/chart/
fc.utilities.property = function(initialValue) {
var accessor = function(newValue) {
if (!arguments.length) {
return accessor.value;
}
accessor.value = newValue;
return this;
};
accessor.value = initialValue;
return accessor;
};
// a property that follows the D3 component convention for accessors
// see: http://bost.ocks.org/mike/chart/
fc.utilities.functorProperty = function(initialValue) {
var accessor = function(newValue) {
if (!arguments.length) {
return accessor.value;
}
accessor.value = d3.functor(newValue);
return this;
};
accessor.value = d3.functor(initialValue);
return accessor;
};
}(d3, fc));
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Multiple line chart with legend</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href='http://fonts.googleapis.com/css?family=Source+Code+Pro' rel='stylesheet' type='text/css'>
<link rel="stylesheet" href="http://yui.yahooapis.com/pure/0.6.0/pure-min.css">
<!--[if lte IE 8]>
<link rel="stylesheet" href="http://yui.yahooapis.com/pure/0.5.0/grids-responsive-old-ie-min.css">
<![endif]-->
<!--[if gt IE 8]><!-->
<link rel="stylesheet" href="http://yui.yahooapis.com/pure/0.5.0/grids-responsive-min.css">
<!--<![endif]-->
<link href='custom.css' rel='stylesheet' type='text/css'>
<!-- // js dependencies for charts -->
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<!-- http://momentjs.com/ to parse dates-->
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.2/moment.min.js"></script>
<!-- http://fgnass.github.io/spin.js/ -->
<script src="//cdnjs.cloudflare.com/ajax/libs/spin.js/2.0.1/spin.min.js"></script>
<!-- custom settings for spin loader -->
<script>
var opts = {
lines: 13, // The number of lines to draw
length: 6, // The length of each line
width: 4, // The line thickness
radius: 13, // The radius of the inner circle
corners: 1, // Corner roundness (0..1)
color: '#000', // #rgb or #rrggbb or array of colors
speed: 1.5, // Rounds per second
trail: 75, // Afterglow percentage
shadow: false, // Whether to render a shadow
hwaccel: false, // Whether to use hardware acceleration
className: 'spinner', // The CSS class to assign to the spinner
zIndex: 2e9, // The z-index (defaults to 2000000000)
top: '50%', // Top position relative to parent
left: '50%' // Left position relative to parent
};
</script>
<!-- d3-financial-components -->
<script src="fc.js"></script>
<!-- d3-files added to financial-components -->
<script src="lineMultiple.js"></script>
<script src="defaultConfig.js"></script>
<script src="dataConfig.js"></script>
<script src="legend.js"></script>
<!-- render the chart -->
<script src="chart.js"></script>
</head>
<body>
<div class="l-content main-wrapper fullscreen">
<div id="chart-wrap" class="fullscreen fullscreen-padding">
<header>
<div id="chart-title-wrap">
<h1>
<span id="chart-title">Loading chart &hellip;</span>
</h1>
</div>
</header>
<section class="chart-fullscreen" id="chart-id">
<aside id="legend-html"></aside>
</section>
</div>
<script src="chart.js"></script>
</div>
</body>
</html>
(function(d3, fc) {
'use strict';
fc.tools.legend = function() {
var container = 'legend-html', // id
list = 'legend-list', // id references
dataKeys = [], // array of key values from json object data
lineReferences = [], // array for line series variables
//
dataKeysUser = []; // updated keys for yScale extent
var dispatch = d3.dispatch('toggleLegend');
var legend = function() {
// ensure container is visible
d3.select('#' + container).transition().duration(1000).style('opacity', 1);
var legend = d3.select('#' + container + ' ul');
// create legend container if doesn't exist
if (legend.empty()) {
legend = d3.select('#' + container)
.attr('class', 'pure-menu pure-menu-horizontal');
// legend
// .append('span')
// .attr('class', 'pure-menu-heading')
// .text('LEGEND:');
legend
.append('ul')
.attr('class', 'pure-menu-list')
.attr('id', list);
// add the legend items
var legendList = d3.select('#' + list)
.selectAll('li')
.data(dataKeys);
legendList
.enter()
.append('li')
.attr('class', 'pure-menu-item legend-item')
.attr('id', function(d) {
// any spaces replaced with dashes and all letters converted to lowercase
return d.replace(/\s+/g, '-').toLowerCase();
})
.on('click', function(d,i) {
return dispatch.toggleLegend(d, i);
});
legendList.append('span')
.attr('class', 'legend-line')
.style('color', function(d,i) { return fc.utilities.defaultConfig().colourArray[i]; })
.html(' &mdash;');
legendList.append('span')
.attr('class', 'legend-title')
.style('color', '#454545')
.html(function(d) { return d; });
}
};
legend.container = function(value) {
if (!arguments.length) return container;
container = value;
return this;
};
legend.list = function(value) {
if (!arguments.length) return list;
list = value;
return this;
};
legend.dataKeys = function(value) {
if (!arguments.length) return dataKeys;
dataKeys = value;
return this;
};
legend.dataKeysUser = function(value) {
if (!arguments.length) return dataKeysUser;
dataKeysUser = value;
return this;
};
legend.lineReferences = function(value) {
if (!arguments.length) return lineReferences;
lineReferences = value;
return this;
};
d3.rebind(legend, dispatch, 'on');
return legend;
};
}(d3, fc));
(function(d3, fc) {
'use strict';
fc.series.lineMultiple = function() {
// convenience functions that return the x & y screen coords for a given point
var x = function(d) { return line.xScale.value(line.xValue.value(d)); };
var y = function(d) { return line.yScale.value(line.yValue.value(d)); };
var lineData = d3.svg.line()
.defined(function(d) {
return !isNaN(y(d));
})
.x(x)
.y(y);
var line = function(selection) {
selection.each(function(data) {
var path = d3.select(this)
.selectAll('path.line' + '-' + line.lineColour.value)
.data([data]);
path.enter()
.append('path')
.attr('class', 'line ' + 'line' + '-' + line.lineColour.value) // class='line line-red'
path.attr('d', lineData);
line.decorate.value(path);
});
};
line.decorate = fc.utilities.property(fc.utilities.fn.noop);
line.xScale = fc.utilities.property(d3.time.scale());
line.yScale = fc.utilities.property(d3.scale.linear());
line.yValue = fc.utilities.property(function(d) { return d.close; });
line.xValue = fc.utilities.property(function(d) { return d.date; });
line.lineColour = fc.utilities.property('2');
return line;
};
}(d3, fc));
{
"data":[
{
"Mercury":"0.20050597286579",
"Mars":"0.1978449388459",
"date":"2015-04-27 08:09:00",
"Sun":"0.173800006508827",
"Earth":"0.218519883668032",
"Venus":"0.112813827066484",
"Saturn":"0.119654298899409"
},
{
"Sun":"0.09375",
"date":"2015-04-27 08:10:00",
"Mars":"0.191400824323912",
"Mercury":"0.207015591527661",
"Saturn":"0.135435291079929",
"Venus":"0.105463407238876",
"Earth":"0.230123426982547"
},
{
"Saturn":"0.127321646406424",
"Venus":"0.112477195689915",
"Earth":"0.228399800804974",
"date":"2015-04-27 08:11:00",
"Mars":"0.201398565576947",
"Mercury":"0.208264553020062"
},
{
"date":"2015-04-27 08:12:00",
"Mars":"0.231096350308281",
"Mercury":"0.216186696834808",
"Saturn":"0.128305930640955",
"Venus":"0.116347419162254",
"Earth":"0.231396999570417"
},
{
"Mercury":"0.209383046911485",
"date":"2015-04-27 08:13:00",
"Sun":"1.48871426656842",
"Mars":"0.194588513250472",
"Venus":"0.136748741832913",
"Earth":"0.22886760673745",
"Saturn":"0.120201777873589"
},
{
"Venus":"0.116658231690853",
"Earth":"0.226333441094447",
"Saturn":"0.136649005060551",
"Mercury":"0.205886631097724",
"date":"2015-04-27 08:14:00",
"Sun":"3.72000002996488",
"Mars":"0.196511287359232"
},
{
"Mercury":"0.203812197793099",
"date":"2015-04-27 08:15:00",
"Sun":"0.687333345413208",
"Mars":"0.186568742803718",
"Venus":"0.112911502036147",
"Earth":"0.2234158169719",
"Saturn":"0.13230916941577"
},
{
"Saturn":"0.119566128760751",
"Venus":"0.103600362257724",
"Earth":"0.223129887182728",
"date":"2015-04-27 08:16:00",
"Mars":"0.177482977819429",
"Mercury":"0.202131017862737"
},
{
"Mercury":"0.218868570959908",
"Mars":"0.193394808329875",
"date":"2015-04-27 08:17:00",
"Earth":"0.244942637192265",
"Venus":"0.104023262527581",
"Saturn":"0.131941930783657"
},
{
"Mercury":"0.212261050958628",
"date":"2015-04-27 08:18:00",
"Mars":"0.182138845780422",
"Venus":"0.13706502652379",
"Earth":"0.231025125500055",
"Saturn":"0.13059970894955"
},
{
"date":"2015-04-27 08:19:00",
"Mars":"0.204075264047839",
"Mercury":"0.198434335750756",
"Saturn":"0.120438194318624",
"Venus":"0.118839240183529",
"Earth":"0.215281780211218"
},
{
"Mars":"0.18639611388209",
"date":"2015-04-27 08:20:00",
"Mercury":"0.201935963509445",
"Saturn":"0.124101272711984",
"Earth":"0.223222322531776",
"Venus":"0.110041541763561"
},
{
"Mercury":"0.202149996110647",
"date":"2015-04-27 08:21:00",
"Mars":"0.198181031049534",
"Venus":"0.110689741383134",
"Earth":"0.221477720453077",
"Saturn":"0.12621750949828"
},
{
"Venus":"0.104988277827275",
"Earth":"0.220759041705567",
"Saturn":"0.123273632559475",
"Mercury":"0.199568268938784",
"date":"2015-04-27 08:22:00",
"Sun":"1.40190910954367",
"Mars":"0.187811044952161"
},
{
"Earth":"0.218721004771388",
"Venus":"0.114151060588652",
"Saturn":"0.128091711971546",
"Mercury":"0.200637153889676",
"Mars":"0.195274190735779",
"Sun":"0.45076924103957",
"date":"2015-04-27 08:23:00"
},
{
"Saturn":"0.127283804318136",
"Earth":"0.219495466690823",
"Venus":"0.111576912021949",
"Mars":"0.192386009922005",
"Sun":"0.461636348204179",
"date":"2015-04-27 08:24:00",
"Mercury":"0.200431893873901"
},
{
"Saturn":"0.132855257700384",
"Earth":"0.223727631425131",
"Venus":"0.107350587888539",
"Mars":"0.194410273073219",
"Sun":"0.16024999320507",
"date":"2015-04-27 08:25:00",
"Mercury":"0.204216002601068"
},
{
"Earth":"0.217718666272549",
"Venus":"0.11032448281103",
"Saturn":"0.123170959682502",
"Mercury":"0.199556311803277",
"Mars":"0.202751640634417",
"date":"2015-04-27 08:26:00"
},
{
"Mars":"0.192934380612292",
"date":"2015-04-27 08:27:00",
"Mercury":"0.198719367956763",
"Saturn":"0.125014831913961",
"Earth":"0.217621156775913",
"Venus":"0.129028112925984"
},
{
"Mercury":"0.197301503638881",
"Mars":"0.194142367348254",
"date":"2015-04-27 08:28:00",
"Earth":"0.216046349341944",
"Venus":"0.116787696323182",
"Saturn":"0.126600091171198"
},
{
"Earth":"0.222804797563847",
"Venus":"0.113055628539158",
"Saturn":"0.124041848881295",
"Mercury":"0.201402958647237",
"Mars":"0.187358561881329",
"date":"2015-04-27 08:29:00"
},
{
"Mercury":"0.210695981442108",
"date":"2015-04-27 08:30:00",
"Mars":"0.204213627431242",
"Venus":"0.115886770905268",
"Earth":"0.233667507686926",
"Saturn":"0.133538520941903"
},
{
"Venus":"0.114679207090972",
"Earth":"0.228314325031552",
"Saturn":"0.129977627055092",
"Mercury":"0.205787421990354",
"date":"2015-04-27 08:31:00",
"Mars":"0.207430140967927"
},
{
"Saturn":"0.130599907057359",
"Venus":"0.110469028720936",
"Earth":"0.223096390691902",
"date":"2015-04-27 08:32:00",
"Sun":"0.0885833315551281",
"Mars":"0.176225309952443",
"Mercury":"0.198303037807821"
},
{
"Earth":"0.222801023279425",
"Venus":"0.1052971507039",
"Saturn":"0.118250930243101",
"Mercury":"0.200469946863456",
"Mars":"0.183798386809266",
"Sun":"0.101666662221154",
"date":"2015-04-27 08:33:00"
},
{
"Venus":"0.102144692149404",
"Earth":"0.216270134381141",
"Saturn":"0.124578748970095",
"Mercury":"0.194234308355761",
"date":"2015-04-27 08:34:00",
"Mars":"0.189636140786548"
},
{
"Mercury":"0.189492803150465",
"date":"2015-04-27 08:35:00",
"Sun":"0.14360000193119",
"Mars":"0.165333888342095",
"Venus":"0.100671634575136",
"Earth":"0.216931950306482",
"Saturn":"0.121534121261814"
},
{
"Saturn":"0.125743757633299",
"Earth":"0.226643392553528",
"Venus":"0.102427838985286",
"Mars":"0.192284825256308",
"date":"2015-04-27 08:36:00",
"Sun":"0.140874996781349",
"Mercury":"0.202159779928042"
},
{
"Mercury":"0.194243976526567",
"date":"2015-04-27 08:37:00",
"Mars":"0.168322509219562",
"Venus":"0.130890287900187",
"Earth":"0.220953921552013",
"Saturn":"0.118863833141834"
},
{
"Earth":"0.226327254591303",
"Venus":"0.109508790459199",
"Saturn":"0.119290959697188",
"Mercury":"0.199784025965618",
"Mars":"0.172831943315355",
"date":"2015-04-27 08:38:00"
},
{
"Earth":"0.232562743039301",
"Venus":"0.101480276220413",
"Saturn":"0.119188167903122",
"Mercury":"0.199127225063419",
"Mars":"0.166046798678613",
"date":"2015-04-27 08:39:00"
},
{
"Saturn":"0.125738671864829",
"Earth":"0.219378764251977",
"Venus":"0.103287757187452",
"Mars":"0.219380385734284",
"date":"2015-04-27 08:40:00",
"Mercury":"0.201663095910928"
},
{
"Venus":"0.107035830618468",
"Earth":"0.217283522449784",
"Saturn":"0.116888367120816",
"Mercury":"0.195727326105302",
"date":"2015-04-27 08:41:00",
"Mars":"0.176281497190095"
},
{
"date":"2015-04-27 08:42:00",
"Mars":"0.15623627448635",
"Mercury":"0.19042749497268",
"Saturn":"0.121502514949233",
"Venus":"0.106709726590507",
"Earth":"0.218185179452771"
},
{
"Saturn":"0.126546406026762",
"Earth":"0.222774086434988",
"Venus":"0.0997827840796888",
"Mars":"0.341210789434953",
"date":"2015-04-27 08:43:00",
"Mercury":"0.241869640680405"
},
{
"date":"2015-04-27 08:44:00",
"Mars":"0.185065341144085",
"Mercury":"0.192931213161462",
"Saturn":"0.126289791183017",
"Venus":"0.101213007327709",
"Earth":"0.22079581527579"
},
{
"Saturn":"0.118284297089119",
"Venus":"0.107714500940506",
"Earth":"0.247530117942814",
"Sun":"0.127250000834465",
"date":"2015-04-27 08:45:00",
"Mars":"0.204549470335772",
"Mercury":"0.216204473478767"
},
{
"Mars":"0.17575029508443",
"Sun":"0.160125002264977",
"date":"2015-04-27 08:46:00",
"Mercury":"0.195066259330743",
"Saturn":"0.128192889966619",
"Earth":"0.220331865124891",
"Venus":"0.104207141360113"
},
{
"Saturn":"0.120369815795776",
"Venus":"1.104723532647661",
"Earth":"0.214520260460375",
"date":"2015-04-27 08:47:00",
"Mars":"0.172160646802587",
"Mercury":"0.189440087800018"
},
{
"Saturn":"0.119270505539903",
"Earth":"0.213558692597529",
"Venus":"0.816978158598207",
"Mars":"0.179870505953419",
"date":"2015-04-27 08:48:00",
"Mercury":"0.19228048859596"
},
{
"date":"2015-04-27 08:49:00",
"Mars":"0.179002563450976",
"Mercury":"0.192239146909956",
"Saturn":"0.12172568538166",
"Venus":"0.116313239712068",
"Earth":"0.214158102809663"
},
{
"Mercury":"0.199774913337559",
"date":"2015-04-27 08:50:00",
"Mars":"0.208191100166416",
"Venus":"2.112265985251551",
"Earth":"0.223539110035843",
"Saturn":"0.120357795990884"
},
{
"date":"2015-04-27 08:51:00",
"Mars":"0.193101587194638",
"Mercury":"0.19527120852138",
"Saturn":"0.122562365130265",
"Venus":"0.113246769721242",
"Earth":"0.215095372142936"
},
{
"Mars":"0.19718559858853",
"date":"2015-04-27 08:52:00",
"Mercury":"0.198422520355698",
"Saturn":"0.123674777379844",
"Earth":"0.218279064911295",
"Venus":"0.109951321709549"
},
{
"Mercury":"0.19997115011235",
"Mars":"0.217379745736942",
"date":"2015-04-27 08:53:00",
"Earth":"0.214184000876051",
"Venus":"0.108216924802317",
"Saturn":"0.121982507310329"
},
{
"Earth":"0.218541328740697",
"Venus":"0.105563670176837",
"Saturn":"1.1181519801567",
"Mercury":"0.195910731017297",
"Mars":"0.186250652124528",
"date":"2015-04-27 08:54:00"
},
{
"Mercury":"0.20574480694863",
"Mars":"0.901722945545739",
"date":"2015-04-27 08:55:00",
"Earth":"0.227421745617749",
"Venus":"0.107435350137854",
"Saturn":"0.121073018970106"
},
{
"Saturn":"0.120229054112105",
"Earth":"0.215454160841548",
"Venus":"0.1001906547781",
"Mars":"0.191416002636627",
"date":"2015-04-27 08:56:00",
"Mercury":"0.192805219994493"
},
{
"Earth":"0.224853794057543",
"Venus":"0.112450725560633",
"Saturn":"0.124263206083717",
"Mercury":"0.197131452354692",
"Mars":"0.174543794062952",
"date":"2015-04-27 08:57:00"
},
{
"Mars":"0.168700958135131",
"date":"2015-04-27 08:58:00",
"Mercury":"0.19667903225864",
"Saturn":"0.122766333384291",
"Earth":"0.227418491862342",
"Venus":"0.10957194330632"
},
{
"Venus":"0.100547474790642",
"Earth":"0.220718578669601",
"Saturn":"0.120216136251322",
"Mercury":"0.192748008173673",
"Sun":"0.106500000692904",
"date":"2015-04-27 08:59:00",
"Mars":"0.177828138871444"
},
{
"Saturn":"0.134246037333236",
"Venus":"0.105312475953515",
"Earth":"0.23448889259062",
"date":"2015-04-27 09:00:00",
"Sun":"0.115157890868814",
"Mars":"0.181355711054137",
"Mercury":"0.199217274777305"
},
{
"Mercury":"0.188918405911711",
"Mars":"0.17346917709398",
"date":"2015-04-27 09:01:00",
"Sun":"0.156000003218651",
"Earth":"0.225580061767689",
"Venus":"0.101299598290123",
"Saturn":"0.123868741515358"
},
{
"Earth":"0.225449100423744",
"Venus":"0.10117472882159",
"Saturn":"0.123478057663455",
"Mercury":"0.186328796430918",
"Mars":"0.171572867711999",
"Sun":"0.140636369585991",
"date":"2015-04-27 09:02:00"
},
{
"Saturn":"0.12959524444449",
"Venus":"0.0995254586551001",
"Earth":"0.225046837743909",
"date":"2015-04-27 09:03:00",
"Mars":"0.199984114271521",
"Mercury":"0.200182370575012"
},
{
"Saturn":"0.132170822851381",
"Venus":"0.114517269564454",
"Earth":"0.228122851364777",
"date":"2015-04-27 09:04:00",
"Mars":"0.173526723460162",
"Mercury":"0.191513854236738"
},
{
"Saturn":"0.128857762046521",
"Venus":"0.113474603090271",
"Earth":"0.226498684127449",
"date":"2015-04-27 09:05:00",
"Mars":"0.165894722314001",
"Mercury":"0.18500676644511"
},
{
"Saturn":"0.122175096010548",
"Venus":"0.114894147082559",
"Earth":"0.228471722028148",
"date":"2015-04-27 09:06:00",
"Mars":"0.181531899700452",
"Mercury":"0.192781901216797"
},
{
"Mercury":"0.185469158360254",
"Mars":"0.153547175783315",
"Sun":"0.163000002503395",
"date":"2015-04-27 09:07:00",
"Earth":"0.231887561399023",
"Venus":"0.108781468165573",
"Saturn":"0.12691092997881"
},
{
"Saturn":"0.124338791599667",
"Venus":"0.117228857511934",
"Earth":"0.222367345607169",
"Sun":"0.21875",
"date":"2015-04-27 09:08:00",
"Mars":"0.158592411169302",
"Mercury":"0.180894077332331"
},
{
"Mercury":"0.180560073223433",
"Mars":"0.151575082072196",
"date":"2015-04-27 09:09:00",
"Sun":"0.164124995470047",
"Earth":"0.224947693237079",
"Venus":"0.103202896068159",
"Saturn":"0.127900601805952"
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment