Skip to content

Instantly share code, notes, and snippets.

@wwymak
Last active February 5, 2016 16:57
Show Gist options
  • Save wwymak/af92efe1462666f5d461 to your computer and use it in GitHub Desktop.
Save wwymak/af92efe1462666f5d461 to your computer and use it in GitHub Desktop.
Reusable linechart module

Reusuable linechart component in ES6 based on http://bost.ocks.org/mike/chart/time-series-chart.js, but with check for whether it is a single series or a multiseries

Use like :

let lineplot = linechart.lineplot()
      .width(chartWidth).height(chartHeight)
      .datapointRadius(5)
      .x(d => +d.key)
      .y(d => +d.value / 1000000) 

d3.select(chartContainerSelector).datum(data).call(lineplot)
/**
* module for reusable linechart
* good ref at http://bost.ocks.org/mike/chart/time-series-chart.js
* (blog post is at http://bost.ocks.org/mike/chart
*/
//todo check if need selection.data([values[, key]])
function lineplot(){
var width = 500,
height = 500,
margins = {top: 40, bottom: 40, left:40, right:40},
line = d3.svg.line().x(X).y(Y),
xScale = d3.scale.ordinal(),
yScale = d3.scale.linear(),
xAxis = d3.svg.axis().scale(xScale).orient("bottom"),
yAxis = d3.svg.axis().scale(yScale).orient("left"),
xValue = function(d) { return d[0]; },
yValue = function(d) { return d[1]; },
datapointRadius = 0;
// The x-accessor for the path generator; xScale ∘ xValue.
function X(d) {
return xScale(d[0]) + xScale.rangeBand()/2
}
function Y(d){
return yScale(d[1])
}
function chart(selection){
var svg, lineSVG, xAxisSVG, yAxisSVG;
selection.each((data) => {
let dataType = "single"
if(Array.isArray(data[0])){
data = data.map(d => {
let outArr = dataMapping(d);
// if the array has a property 'name' then add that to the mapped array to label the lines by
if(d.name) outArr.name = d.name;
//if array has a property 'color' add that to mapped array for linecolor
if(d.color) outArr.color = d.color;
return outArr
});
dataType = "multi"
}else{
data = dataMapping(data);
}
// Convert data to standard [[x1,y1], [x2, y2], [x3, y3]...] per line
// this is needed for nondeterministic accessors.
function dataMapping(data){
return data.map((d, i) => {
return [xValue.call(data, d, i), yValue.call(data, d, i)];
});
}
//for an array of arrays, get all the possible unique x values (data parsed as per dataMapping func)
function getOrdinalRange(data){
var outArr = [];
data.forEach(d => {
d.forEach(i => {
if(outArr.indexOf(i[0]) < 0){
outArr.push(i[0])
}
})
});
outArr.sort((a,b) => (a-b));
return outArr
}
function drawLineGroup(data, svg){
let plottingData;
if(dataType == "single"){
plottingData = [data];
}else {
plottingData = data;
}
let lineFunc = d3.svg.line().x(X).y(Y);
let lineG = svg.selectAll('g.data-lineG').data(plottingData);
let newG = lineG.enter().append('g').attr('class', 'data-lineG');
let line = lineG.selectAll('path.data-line').data(d => [d]);
line.enter().append('path').attr('id', (d,i) => d.name || "linechart-line")
.attr('class', 'data-line');
line.attr('d', lineFunc).attr("fill", "none")
.attr("stroke", (d,i) => d.color || "black");
line.exit().remove();
let datapoints = lineG.selectAll('.data-point')
.data(d => {d.forEach(item => {item.color = d.color}); return d});
datapoints.enter().append("circle")
.attr("r", datapointRadius).attr("class", "data-point")
.attr('fill', (d) => {
return d.color || "black"
});
datapoints.attr("cx", (d, i) => {return xScale(d[0]) + xScale.rangeBand()/2})
.attr("cy", d=> yScale(d[1]))
.attr("r", datapointRadius)
.attr('fill', (d, i) => {
return d.color || "black"
});
datapoints.exit().remove();
lineG.exit().remove();
}
/**
* for an array of arrays, get the max value of Y
* @param data parsed as [er dataMapping func)
*/
function getMaxLinear(data){
d3.max(data, d => { d3.max(i => i[1]) })
}
let xDomain, yDomain;
//have
if(dataType === "multi"){
xDomain = getOrdinalRange(data);
yDomain = [0, d3.max(data.map(d => 1.2 * d3.max(d, i => i[1])))];
}else {
xDomain = data.map( d=> d[0] ).sort((a,b) => (a-b));
yDomain = [0, 1.2 * d3.max(data, d => d[1])]; //add in a bit of padding to the yMax so the lines don't bang against the top of the graph
}
xScale
.domain(xDomain)
.rangeBands([0, (width - margins.left - margins.right)]);
// Update the y-scale.
yScale
.domain(yDomain)
.range([height - margins.top - margins.bottom, 0]);
//select the svg if it exists
//svg = d3.select(this).selectAll("svg.linechart").data([data]);
svg = this.selectAll("svg.linechart").data([data]);
//or add a new one if not
var newSVG = svg.enter().append('svg').attr('class', 'linechart');
lineSVG = newSVG.append("g").attr('class', 'lines')
.attr('transform', `translate(${margins.left} ,${margins.top})`);
xAxisSVG = newSVG.append('g').attr('class', 'x axis')
.attr('transform', `translate( ${margins.left} , ${height - margins.bottom})`);
yAxisSVG = newSVG.append('g').attr('class', 'y axis')
.attr('transform', `translate( ${margins.left} , ${margins.top})`);
svg.attr("width" , width)
.attr("height", height);
let plottingData = data;
svg.select('.x.axis').call(xAxis);
svg.select('.y.axis').call(yAxis);
if(!Array.isArray(data[0])){
plottingData = [data]
}
drawLineGroup(plottingData, svg.select('g.lines'));
//drawLines(plottingData, svg.select('g.lines'));
//drawDataPoints(plottingData, svg.select('g.lines'));
})
}
//getters and setters
chart.width = function(val) {
if (!arguments.length) {
return width;
}
width = val;
return chart;
};
chart.height = function(val) {
if (!arguments.length) {
return height;
}
height = val;
return chart;
};
chart.margins = function(val) {
if (!arguments.length) {
return margins;
}
margins = val;
return chart;
};
chart.xAxis = function(axis){
if (!arguments.length) {
return xAxis
}
xAxis = axis;
return chart;
};
chart.yAxis = function(axis){
if (!arguments.length) {
return yAxis
}
yAxis = axis;
return chart;
};
chart.x = function(xAccessor) {
if (!arguments.length) return xValue;
xValue = xAccessor;
return chart;
};
chart.y = function(yAccessor) {
if (!arguments.length) return yValue;
yValue = yAccessor;
return chart;
};
chart.datapointRadius = function(radius) {
if (!arguments.length) return datapointRadius;
datapointRadius = radius;
return chart;
};
return chart
}
export {lineplot}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment