2016-02-11 19:52:12 -06:00
|
|
|
import LineChart from './line';
|
|
|
|
|
|
|
|
|
|
const INTERPOLATION = 'cardinal';
|
|
|
|
|
|
|
|
|
|
// inspired by https://bl.ocks.org/mbostock/3885211
|
|
|
|
|
class SplineStackChart extends LineChart {
|
|
|
|
|
|
2016-02-15 16:09:40 -06:00
|
|
|
get chart_options(){
|
2016-02-22 13:45:43 -06:00
|
|
|
return Object.assign(Object.assign({}, LineChart.DEFAULTS), {
|
2016-02-15 16:09:40 -06:00
|
|
|
interpolation: INTERPOLATION
|
|
|
|
|
});
|
|
|
|
|
}
|
2016-02-11 19:52:12 -06:00
|
|
|
|
2016-02-15 16:09:40 -06:00
|
|
|
afterAxes(){
|
|
|
|
|
var spline_stack = this;
|
|
|
|
|
spline_stack.fnArea = d3.svg.area()
|
2016-02-11 19:52:12 -06:00
|
|
|
.x(function(d, i) { return spline_stack.x_scale(d.x); })
|
|
|
|
|
.y0(function(d) { return spline_stack.y_scale(d.y0); })
|
2016-02-15 16:09:40 -06:00
|
|
|
.y1(function(d) { return spline_stack.y_scale(d.y0 + d.y); })
|
|
|
|
|
.interpolate(spline_stack.interpolation);
|
2016-02-11 19:52:12 -06:00
|
|
|
|
2016-02-15 16:09:40 -06:00
|
|
|
spline_stack.fnStack = d3.layout.stack()
|
2016-02-11 19:52:12 -06:00
|
|
|
.values(function(d) { return d.values; });
|
|
|
|
|
|
2016-02-15 16:09:40 -06:00
|
|
|
// function that returns unique color based on series_title.
|
|
|
|
|
spline_stack.fnColor = d3.scale.category20();
|
2016-02-13 16:49:32 -06:00
|
|
|
}
|
|
|
|
|
|
2016-02-11 19:52:12 -06:00
|
|
|
serializeData(data){
|
|
|
|
|
var spline_stack = this,
|
|
|
|
|
serialized_data = {
|
2016-02-15 16:09:40 -06:00
|
|
|
series: [] };
|
2016-02-11 19:52:12 -06:00
|
|
|
data.series.forEach(function(series, i){
|
2016-02-21 18:40:55 -06:00
|
|
|
series.css_class = series.css_class || spline_stack.toClass ? spline_stack.toClass(series) : "series-" + i;
|
2016-02-15 16:09:40 -06:00
|
|
|
if (spline_stack.domain_attr !== 'x' && spline_stack.range_attr !== 'y'){
|
|
|
|
|
series.values = series.values.map((value)=>{
|
|
|
|
|
return {x: value[spline_stack.domain_attr], y: value[spline_stack.range_attr], series: series};
|
|
|
|
|
});
|
|
|
|
|
}
|
2016-02-11 19:52:12 -06:00
|
|
|
serialized_data.series.push(series);
|
|
|
|
|
});
|
2016-02-21 18:40:55 -06:00
|
|
|
|
2016-02-15 16:09:40 -06:00
|
|
|
serialized_data.series = spline_stack.fnStack(serialized_data.series);
|
|
|
|
|
// assume all series have same domain, use first series to establish extent.
|
|
|
|
|
serialized_data.domain_extent = d3.extent(serialized_data.series[0].values.map((value)=>{ return value.x; }));
|
|
|
|
|
// final series will have the highest y values.
|
|
|
|
|
serialized_data.range_max = d3.max(serialized_data.series[serialized_data.series.length - 1].values.map((value)=>{ return value.y0 + value.y; }))
|
|
|
|
|
|
2016-02-11 19:52:12 -06:00
|
|
|
return serialized_data;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
drawData(data){
|
|
|
|
|
var spline_stack = this;
|
2016-02-15 16:09:40 -06:00
|
|
|
data = spline_stack.serializeData(data);
|
2016-02-11 19:52:12 -06:00
|
|
|
|
|
|
|
|
// calibrate axes.
|
2016-02-15 16:09:40 -06:00
|
|
|
spline_stack.y_scale.domain([0, data.range_max]);
|
|
|
|
|
spline_stack.svg.select(".d3-chart-range.d3-chart-axis")
|
|
|
|
|
.call(spline_stack.y_axis);
|
2016-02-11 19:52:12 -06:00
|
|
|
|
2016-02-15 16:09:40 -06:00
|
|
|
spline_stack.x_scale.domain(data.domain_extent);
|
|
|
|
|
spline_stack.svg.select(".d3-chart-domain.d3-chart-axis").call(spline_stack.x_axis);
|
2016-02-11 19:52:12 -06:00
|
|
|
|
2016-02-15 16:09:40 -06:00
|
|
|
var stack = spline_stack.svg.selectAll(".d3-chart-spline-stack")
|
|
|
|
|
.data(data.series);
|
2016-02-22 13:45:43 -06:00
|
|
|
[stack.enter().append("path"), stack].forEach((paths)=>{
|
2016-02-15 16:09:40 -06:00
|
|
|
spline_stack.applyData(paths);
|
2016-02-11 19:52:12 -06:00
|
|
|
});
|
|
|
|
|
stack.exit().remove();
|
2016-02-21 18:40:55 -06:00
|
|
|
|
|
|
|
|
if (spline_stack.include_dots){
|
|
|
|
|
data.series.forEach((series)=>{
|
|
|
|
|
var dots = spline_stack.svg.selectAll(".d3-chart-spline-dot." + series.css_class)
|
|
|
|
|
.data(series.values);
|
2016-02-22 13:45:43 -06:00
|
|
|
[dots.enter().append("circle"), dots].forEach((circles)=>{
|
2016-02-21 18:40:55 -06:00
|
|
|
spline_stack.applyDots(series, circles);
|
|
|
|
|
});
|
|
|
|
|
dots.exit().remove();
|
|
|
|
|
});
|
|
|
|
|
}
|
2016-02-11 19:52:12 -06:00
|
|
|
}
|
|
|
|
|
|
2016-02-15 16:09:40 -06:00
|
|
|
applyData(paths){
|
2016-02-11 19:52:12 -06:00
|
|
|
var spline_stack = this;
|
2016-02-15 16:09:40 -06:00
|
|
|
paths
|
2016-02-22 13:45:43 -06:00
|
|
|
.attr("class", function(series){ return "d3-chart-spline-stack " + series.css_class;})
|
2016-02-15 16:09:40 -06:00
|
|
|
.attr("d", function(series){ return spline_stack.fnArea(series.values); })
|
2016-02-21 18:40:55 -06:00
|
|
|
.style("fill", function(series){ return spline_stack.fnColor(series.title); })
|
|
|
|
|
.attr('opacity', 1);
|
2016-02-11 19:52:12 -06:00
|
|
|
}
|
2016-02-21 18:40:55 -06:00
|
|
|
|
|
|
|
|
applyDots(series, circles){
|
|
|
|
|
var spline_stack = this;
|
|
|
|
|
circles
|
2016-02-22 13:45:43 -06:00
|
|
|
.attr('class', 'd3-chart-spline-dot ' + series.css_class)
|
2016-02-21 18:40:55 -06:00
|
|
|
.attr("r", 2)
|
|
|
|
|
.attr("cx", function(d, i){ return spline_stack.x_scale(d.x); })
|
|
|
|
|
.attr("cy", function(d, i){ return spline_stack.y_scale(d.y + d.y0); })
|
|
|
|
|
.attr("title", function(d, i){ return spline_stack.titleizeDatum(series, d); })
|
|
|
|
|
.style("fill", spline_stack.fnColor(series.title))
|
|
|
|
|
.attr('opacity', 1)
|
|
|
|
|
.attr('stroke-width', 0)
|
|
|
|
|
.attr('stroke', '#fff');
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-11 19:52:12 -06:00
|
|
|
}
|
2016-02-15 16:09:40 -06:00
|
|
|
|
|
|
|
|
export default SplineStackChart;
|