d3rrc
Version:
Reusable D3 Charts for React.js
264 lines (241 loc) • 8.09 kB
JSX
var React = require('react');
var Support = require('../lib/support');
var d3 = require('d3');
var LineChart = React.createClass({
chart: function(selection){
var {
margin,
width,
height,
yAxisTitle,
lineInterpolation,
duration,
color,
style,
seriesNames,
seriesValues,
pointNames,
pointIndexes,
pointValues,
pointText,
enterSeries,
updateSeries,
exitSeries,
enterPoints,
updatePoints,
exitPoints,
enterSeriesTitle,
updateSeriesTitle,
exitSeriesTitle
} = Support.getProps(this, 'chart', {
margin: Support.types.Object({
top: Support.types.Number(20),
right: Support.types.Number(80),
bottom: Support.types.Number(30),
left: Support.types.Number(50)
}),
width: Support.types.Number(-1),
height: Support.types.Number(420),
yAxisTitle: Support.types.String(false),
lineInterpolation: Support.types.String('linear'),
duration: Support.types.Number(500),
color: Support.types.Function(d3.scale.category10()),
style: Support.types.Object(false),
seriesNames: Support.types.Function(function(d) { return d[0]; }),
seriesValues: Support.types.Function(function(d) { return d[1]; }),
pointNames: Support.types.Function(function(d) { return d[0]; }),
pointIndexes: Support.types.Function(function(d, i) { return i; }),
pointValues: Support.types.Function(function(d) { return d[1]; }),
pointText: Support.types.Function(function(d){return '';}),
enterSeries: Support.types.Function(function(series){
series.append('path')
.attr('class', 'line')
;
}),
updateSeries: Support.types.Function(function(series){
series.select('path.line')
.attr('d', function(d) {
return line(seriesValues(d));
})
.attr('stroke', function(d) { return color(seriesNames(d)); });
}),
exitSeries: Support.types.Function(function(series){
series.remove();
}),
enterPoints: Support.types.Function(function(points){
points
.append("circle")
.attr("stroke", function(d){
return color(seriesNames(this.parentNode.parentNode.__data__));
})
.attr("r", 5)
.attr("fill", "white").attr("fill-opacity", .5)
.append('title')
.text(pointText);
}),
updatePoints:Support.types.Function(function(points){
points.select('circle title')
.text(pointText);
}),
exitPoints:Support.types.Function(function(points){
points.select('circle').remove();
}),
enterSeriesTitle: Support.types.Function(function(seriesTitle){
seriesTitle
.append('text');
}),
updateSeriesTitle: Support.types.Function(function(seriesTitle){
seriesTitle
.attr('transform', function(d) {
var idx = d.idx;
return 'translate(' + x(pointIndexes(d.value, idx)) + ',' + y(pointValues(d.value, idx)) + ')';
})
.attr('x', 3)
.attr('dy', '.35em')
.select('text')
.text(function(d) { return d.name; });
}),
exitSeriesTitle: Support.types.Function(function(series){
series.remove();
})
});
var w = width - margin.left - margin.right;
var h = height - margin.top - margin.bottom;
var x = d3.time.scale()
.range([0, w]);
var y = d3.scale.linear()
.range([h, 0]);
var xAxis = d3.svg.axis()
.orient('bottom');
var yAxis = d3.svg.axis()
.scale(y)
.orient('left');
var line = d3.svg.line()
.interpolate(lineInterpolation) // a B-spline, with control point duplication on the ends
.x(function(d, i) { return x(pointIndexes(d, i)); })
.y(function(d, i) { return y(pointValues(d, i)); });
selection.each(function(data) {
var svgWidth = width===-1?this.offsetWidth:width;
var maxChartHeight = height - margin.top - margin.bottom;
w = svgWidth - margin.left - margin.right;
x.range([0, w]);
xAxis.scale(x);
var svg = d3.select(this).selectAll('svg').data([data]);
svg.enter().append('svg')
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
svg
.attr('width', svgWidth)
.attr('height', height);
svg = svg.select('g');
if(color&&color.domain){
var names = data.map(seriesNames);
color.domain(names);
}
var indexes = data.map(seriesValues).map(function(list){
return list.map(pointIndexes);
}).reduce(function(curr, res){
return res.concat(curr);
}, []);
x.domain(d3.extent(indexes));
y.domain([
d3.min(data, function(c) { return d3.min(seriesValues(c), pointValues); }),
d3.max(data, function(c) { return d3.max(seriesValues(c), pointValues); })
]);
var xAxisGroup = svg.selectAll('g.x.axis');
if(!xAxisGroup[0][0]){
xAxisGroup = svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + h + ')');
}
xAxisGroup.call(xAxis);
var yAxisGroup = svg.selectAll('g.y.axis');
if(!yAxisGroup[0][0]){
yAxisGroup = svg.append('g')
.attr('class', 'y axis');
if(yAxisTitle){
yAxisGroup
.append('text')
.attr('transform', 'rotate(-90)')
.attr('y', 6)
.attr('dy', '.71em')
.style('text-anchor', 'end')
.text(yAxisTitle);
}
}
yAxisGroup.call(yAxis);
var series = svg.selectAll('.series').data(data, function(d){
return seriesNames(d);
});
var seriesEnter = series.enter()
.append('g')
.attr('class', 'series')
;
enterSeries(seriesEnter);
updateSeries(series);
var seriesExit = series.exit();
exitSeries(seriesExit);
var points = series.selectAll('.points').data(function(d){
return seriesValues(d);
});
var pointsEnter = points.enter()
.append('g')
.attr('class', 'points')
;
points
.attr('transform', function(d, i) {
return 'translate(' + x(pointIndexes(d, i)) + ',' + y(pointValues(d, i)) + ')';
})
;
enterPoints(pointsEnter);
updatePoints(points);
var pointsExit = points.exit();
exitPoints(pointsExit);
var seriesTitle = svg.selectAll('g.series.title').data(data);
var seriesTitleEnter = seriesTitle.enter()
.append('g')
.attr('class', 'series title');
var seriesTitleExit = seriesTitle.exit();
seriesTitle.datum(function(d){
var values = seriesValues(d);
return {name: seriesNames(d), value: values[values.length - 1], idx: values.length - 1};
});
enterSeriesTitle(seriesTitleEnter);
updateSeriesTitle(seriesTitle);
exitSeriesTitle(seriesTitleExit);
if(style){
Object.keys(style).forEach(function(key){
svg.selectAll(key).attr('style', style[key]);
});
}
});
},
renderChart: function(data){
if(!data){
return;
}
var container = this.getDOMNode();
d3.select(container)
.datum(data)
.call(this.chart)
;
},
componentDidMount: function(){
return this.renderChart(this.props.data);
},
componentDidUpdate: function(){
return this.renderChart(this.props.data);
},
render: function(){
var classNames = Support.classList(this, {
'line-chart': true
});
return (
<div className={classNames}>
</div>
);
}
});
module.exports = {
LineChart: LineChart
};