Skip to content

Instantly share code, notes, and snippets.

@m99coder
Last active August 29, 2015 14:25
Show Gist options
  • Save m99coder/54d6e0130064c699e6e4 to your computer and use it in GitHub Desktop.
Save m99coder/54d6e0130064c699e6e4 to your computer and use it in GitHub Desktop.
D3 Multiline Chart with Data Generator and Path Translation
/**
* Data Generator
*/
var DataGenerator = (function() {
// number of series
var _numberOfSeries = 1;
// number of data points
var _numberOfDataPoints = 40;
// start at timestamp
var _startTimestampAt = new Date().getTime();
// start at value
var _startValueAt = 50;
// incrementTo (i days by default)
var _incrementTo = function(c, i) {
var date = new Date(c);
date.setDate(date.getDate() + i);
return date.getTime();
};
// data array
var _data = [];
// create normal distribution of num numbers with mean and deviation
var _distribute = function(num, mean, deviation) {
return d3.range(num).map(d3.random.normal(mean, deviation));
};
// shift directions
var _SHIFT_TO_LEFT = -1;
var _SHIFT_TO_RIGHT = 1;
// shift data by num positions to direction (-1 for left, 1 for right)
var _shift = function(direction, num) {
for (var i=0, n=_data.length; i<n; i++) {
if (direction === _SHIFT_TO_LEFT) {
for (j=0; j<num; j++) {
var newValue = _data[i].shift();
newValue.timestamp = _incrementTo(_data[i][_data[i].length - 1].timestamp, 1);
_data[i].push(newValue);
}
}
if (direction === _SHIFT_TO_RIGHT) {
for (j=0; j<num; j++) {
var newValue = _data[i].pop();
newValue.timestamp = _incrementTo(_data[i][0].timestamp, -1);
_data[i].unshift(newValue);
}
}
}
};
return {
/**
* getter/setter for number of series
* @param {number} num number of series to generate
* @returns {*}
*/
numberOfSeries: function(num) {
if (typeof num === 'undefined') {
return _numberOfSeries;
}
_numberOfSeries = num;
return this;
},
/**
* getter/setter for number of data points
* @param {number} num number of data points to generate
* @returns {*}
*/
numberOfDataPoints: function(num) {
if (typeof num === 'undefined') {
return _numberOfDataPoints;
}
_numberOfDataPoints = num;
return this;
},
/**
* getter/setter for start timestamp
* @param {number} start start timestamp
* @returns {*}
*/
startTimestampAt: function(start) {
if (typeof start === 'undefined') {
return _startTimestampAt;
}
_startTimestampAt = start;
return this;
},
/**
* getter/setter for start value
* @param {number} start start value
* @returns {*}
*/
startValueAt: function(start) {
if (typeof start === 'undefined') {
return _startValueAt;
}
_startValueAt = start;
return this;
},
/**
* getter/setter for increment to callback
* @param {function} inc increment callback
* @returns {*}
*/
incrementTo: function(inc) {
if (typeof inc === 'undefined') {
return _incrementTo;
}
_incrementTo = inc;
return this;
},
/**
* generate data
* @param {number} mean mean for normal distribution
* @param {number} deviation deviation for normal distribution
* @returns {DataGenerator}
*/
generate: function(mean, deviation) {
var m = mean || 0;
var d = deviation || 0.2;
for (var i=0; i<_numberOfSeries; i++) {
_data[i] = [];
var distribution = d3.range(_numberOfDataPoints).map(d3.random.normal(m, d));
for (var j=0; j<_numberOfDataPoints; j++) {
_data[i][j] = {
timestamp: _incrementTo(_startTimestampAt, j),
value: _startValueAt + distribution[j]
};
}
}
return this;
},
/**
* shift data points to the left by num positions
* @param {number} num number of positions to shift
*/
shiftToLeft: function(num) {
_shift(_SHIFT_TO_LEFT, num);
return this;
},
/**
* shift data points to the right by num positions
* @param {number} num number of positions to shift
*/
shiftToRight: function(num) {
_shift(_SHIFT_TO_RIGHT, num);
return this;
},
get: function() {
return _data;
}
};
})();
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Path Translations</title>
<link rel="stylesheet" type="text/css" href="http://fonts.googleapis.com/css?family=Roboto+Slab:400,100,300,700">
<link rel="stylesheet" type="text/css" href="http://fonts.googleapis.com/css?family=Roboto:400,100,100italic,300,300italic,400italic,500,500italic,700,700italic,900,900italic">
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<div id="line-chart"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js"></script>
<script src="data-generator.js"></script>
<script src="line-chart.js"></script>
<script>
// get data generator for 2 series
var dataGenerator =
DataGenerator
.numberOfSeries(2)
.startTimestampAt(new Date(2015, 0, 1).getTime())
.generate(0, 10);
// get data
var data = dataGenerator.get();
// get line chart
var lineChart =
LineChart
.config({
id: '#line-chart'
});
var updateChart = function() {
lineChart
.data(data)
.draw(function() {
// shift to left by one position
data = dataGenerator.shiftToLeft(1).get();
updateChart();
});
};
updateChart();
</script>
</body>
</html>
/**
* Line Chart
*/
var LineChart = (function() {
// default configuration
var _DEFAULT_CONFIG = {
id: 'chart',
marginTop: 10,
marginRight: 10,
marginBottom: 10,
marginLeft: 10,
width: 600,
height: 140
};
// configuration
var _config = {};
// scales
var _scales = {};
// line handle
var _line =
d3.svg.line()
.interpolate('basis')
.x(function(d) {
return _scales.x(d.timestamp);
})
.y(function(d) {
return _scales.y(d.value);
});
// svg handle
var _svg = null;
// data array
var _data = [];
// get series range
// first and last element are cutted out to paint a more precise line
var _seriesRange = function(accessor) {
var seriesRanges = [];
_data.map(function(series) {
d3.extent(series.filter(function(d, i) {
return i > 0 && i < series.length - 1;
}), accessor).map(function(d) {
seriesRanges.push(d);
});
});
return d3.extent(seriesRanges);
};
return {
/**
* getter/setter for configuration
* @param {object} config configuration
* @returns {*}
*/
config: function(config) {
if (typeof config === 'undefined') {
return _config;
}
_config = config;
var id = _config.id || _DEFAULT_CONFIG.id;
var width = _config.width || _DEFAULT_CONFIG.width;
var height = _config.height || _DEFAULT_CONFIG.height;
var marginTop = _config.marginTop || _DEFAULT_CONFIG.marginTop;
var marginRight = _config.marginRight || _DEFAULT_CONFIG.marginRight;
var marginBottom = _config.marginBottom || _DEFAULT_CONFIG.marginBottom;
var marginLeft = _config.marginLeft || _DEFAULT_CONFIG.marginLeft;
// update scales
_scales.x = d3.time.scale().range([0, width]);
_scales.y = d3.scale.linear().range([height, 0]);
_scales.color = d3.scale.category10();
// init svg
_svg = d3.select(id)
.append('svg')
.attr('width', width + marginLeft + marginRight)
.attr('height', height + marginTop + marginBottom)
.append('g')
.attr('transform', 'translate(' + marginLeft + ',' + marginRight + ')');
// clipping path
_svg.append('defs').append('clipPath')
.attr('id', 'clip')
.append('rect')
.attr('width', width - marginLeft - marginRight)
.attr('height', height - marginTop - marginBottom)
.attr('x', marginLeft)
.attr('y', marginTop);
return this;
},
/**
* getter/setter for data
* @param {array} data data
* @returns {*}
*/
data: function(data) {
if (typeof data === 'undefined') {
return _data;
}
_data = data;
return this;
},
/**
* draw chart
* @param {function} ready ready callback
* @returns {LineChart}
*/
draw: function(ready) {
// update scale domains
_scales.x.domain(_seriesRange(function(d) {
return d.timestamp;
}));
/*
_scales.y.domain(_seriesRange(function(d) {
return d.value;
}));
*/
_scales.y.domain([80, 20]);
_svg.selectAll('.series')
.data(data)
.enter()
.append('g')
.attr('clip-path', 'url(#clip)')
.attr('class', 'series')
.append('path')
.attr('class', 'line')
.attr('stroke', function(d, i) {
return _scales.color(i);
});
_svg.selectAll('.line')
.attr('d', function(d) {
return _line(d);
})
.attr('transform', null)
.transition()
.duration(950)
.ease('linear')
.attr('transform', function(d) {
return 'translate(' + _scales.x(d[0].timestamp) + ')';
})
.each('end', function(d, i) {
if (i === _data.length - 1) {
ready();
}
});
return this;
},
/**
* get scales
* @returns {}
*/
scales: function() {
return _scales;
}
};
})();
* {
font-family: 'Roboto', sans-serif;
color: #666;
}
body {
margin: 20px 20px 20px 100px;
}
h1, h2, h3 {
font-family: 'Roboto Slab', serif;
color: #000;
}
.line {
fill: none;
stroke-width: 1.5px;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment