d3rrc
Version:
Reusable D3 Charts for React.js
246 lines (228 loc) • 6.77 kB
JSX
var React = require('react');
var Support = require('../lib/support');
var d3 = require('d3');
var ScatterChart = React.createClass({
chart: function(selection){
var {
margin,
width,
height,
duration,
xAxis: showXAxis,
yAxis: showYAxis,
identity,
idx,
style,
color: getColor,
x: getX,
y: getY,
r: getR,
scaleX: getScaleX,
scaleY: getScaleY,
text: getText,
getIdentity,
enterNode,
updateNode,
exitNode,
} = Support.getProps(this, 'chart', {
margin: Support.types.Object({
top: Support.types.Number(30),
right: Support.types.Number(10),
bottom: Support.types.Number(50),
left: Support.types.Number(50)
}),
width: Support.types.Number(-1),
height: Support.types.Number(420),
duration: Support.types.Number(500),
xAxis: Support.types.Boolean(true),
yAxis: Support.types.Boolean(true),
identity: Support.types.String('_id'),
idx: Support.types.Number(1),
style: Support.types.Object(false),
x: Support.types.Function(function x(d){
return d.x;
}),
y: Support.types.Function(function y(d){
return d.y;
}),
r: Support.types.Function(function(d){
return 8;
}),
scaleX: Support.types.Function(function(data, w){
var min = d3.min(data, getX), max = d3.max(data, getX);
var r = ((max - min) * 0.1) || 1;
min -= r;
max += r;
return d3.scale.linear()
.domain([min, max])
.range([0, w])
;
}),
scaleY: Support.types.Function(function(data, h){
var min = d3.min(data, getY), max = d3.max(data, getY);
var r = ((max - min) * 0.1) || 1;
min -= r;
max += r;
return d3.scale.linear()
.domain([max, min])
.range([0,h])
;
}),
color: Support.types.Function(function(d){
return 'black';
}),
text: Support.types.Function(function(d){ return d.text||''; }),
getIdentity: Support.types.Function(function(d){
return d[identity] || (d[identity] = ++idx);
}),
enterNode: Support.types.Function(function(node){
var circle = node.append('svg:circle')
.attr('r', 1e-6);
if(getColor){
circle.style('fill', getColor);
}
node.append('svg:text')
.attr('text-anchor', 'middle')
.attr('dy', function(d){return -getR(d)-3})
.text(getText)
.style('fill-opacity', 1);
}),
updateNode: Support.types.Function(function(node){
node.select('text')
.text(getText);
node.select('circle')
.attr('r', getR);
}),
exitNode: Support.types.Function(function(node){
node.select('circle')
.attr('r', 1e-6);
node.select('text')
.style('fill-opacity', 1e-6);
})
});
selection.each(function(data){
var wid = width===-1?this.offsetWidth:width;
var w = wid - margin.left - margin.right;
var h = height - margin.top - margin.bottom;
var svg = d3.select(this).select('svg');
var x = getScaleX(data, w);
var y = getScaleY(data, h);
if(showXAxis){
var xAxis = d3.svg.axis()
.scale(x)
.orient('bottom')
;
}
if(showYAxis){
var yAxis = d3.svg.axis()
.scale(y)
.orient('left')
;
}
if(!svg[0][0]){
svg = d3.select(this).append('svg');
}
svg
.attr('width', wid)
.attr('height', height)
;
var node = svg.selectAll('g.node')
.data(data, getIdentity)
;
var nodeEnter = node.enter().append('svg:g')
.attr('class', 'node')
.attr('transform', function(d, i) {
return 'translate(' + (x(getX(d, i)) + margin.left) + ',' + (margin.top+h) + ')';
})
;
enterNode(nodeEnter);
var nodeUpdate = node.transition()
.duration(duration)
.attr('transform', function(d, i) { return 'translate(' + (x(getX(d, i)) + margin.left) + ', ' + (y(getY(d, i)) + margin.top) + ')'; })
;
updateNode(nodeUpdate);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr('transform', function(d, i) { return 'translate(' + (x(getX(d, i)) + margin.left) + ', ' + (margin.top+h) + ')'; })
.remove()
;
exitNode(nodeExit);
if(showXAxis){
var xA = svg.select('g.xAxis');
if(!xA[0][0]){
xA = svg.append('g')
.attr('class', 'axis xAxis')
.attr('transform', 'translate(' + margin.left + ',' + (h+margin.top) + ')')
.call(xAxis)
;
}else{
xA.transition()
.duration(duration)
.call(xAxis)
;
}
xA.selectAll('line')
.attr('style', 'shape-rendering: crispEdges; stroke: black; fill: none;')
;
xA.selectAll('path')
.attr('style', 'shape-rendering: crispEdges; stroke: black; fill: none;')
;
}
if(showYAxis){
var yA = svg.select('g.yAxis');
if(!yA[0][0]){
yA = svg.append('g')
.attr('class', 'axis yAxis')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
.call(yAxis)
;
}else{
yA.transition()
.duration(duration)
.call(yAxis)
;
}
yA.selectAll('line')
.attr('style', 'shape-rendering: crispEdges; stroke: black; fill: none;')
;
yA.selectAll('path')
.attr('style', 'shape-rendering: crispEdges; stroke: black; fill: none;')
;
}
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, {
'bar-chart': true
});
return (
<div className={classNames}>
</div>
);
}
});
module.exports = {
ScatterChart: ScatterChart
};