Created
February 22, 2018 00:14
-
-
Save yan130/2e1b8bd9e4f2028f1982dd20358614fe to your computer and use it in GitHub Desktop.
react line chart with deviation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* @flow | |
*/ | |
import React, { Component } from 'react'; | |
import d3 from 'd3'; | |
import { Xaxis, Yaxis, Chart, Svg } from 'react-d3-core'; | |
class LineChartWithDeviations extends Component { | |
constructor(props: Object) { | |
super(props); | |
this.state={ | |
xline: d3.scale.linear(), | |
yline: d3.scale.linear() | |
} | |
} | |
componentWillReceiveProps(nextProps) { | |
const { | |
width, | |
height, | |
margins, | |
chartData | |
} = nextProps; | |
let xRange = [0, width - margins.left - margins.right]; | |
let yRange = [height - margins.top - margins.bottom, 0]; | |
let xDomain = d3.extent(chartData, function(d){ return d.x ; }); | |
let yMax = d3.extent(chartData, function(d){ return d.y + d.d; })[1]; | |
let yMin = d3.extent(chartData, function(d){ return d.y - d.d; })[0]; | |
let yDomain = [yMin, yMax]; | |
this.setState({xRange, xDomain, yRange, yDomain}); | |
this.setState({xline : d3.scale.linear().domain(xDomain).range(xRange)}); | |
this.setState({yline : d3.scale.linear().domain(yDomain).range(yRange)}); | |
this.setState({barLength: 0.3*(xRange[1]/chartData.length)}); | |
let that = this; | |
d3.select(this.refs.svgContainer).on('mousemove', function mouseMoveHandler() { | |
// console.log(d3.mouse(this)); | |
let bisectDate = d3.bisector(function(d) { return d.x; }).left; | |
let x0 = that.state.xline.invert(d3.mouse(this)[0]- margins.left), | |
i = bisectDate(chartData, x0, 1) - 1, | |
d0 = chartData[i - 1], | |
d1 = chartData[i]; | |
// for debug | |
// console.log(i) | |
let d = (d1 !== undefined || x0 - d0.x > d1.x - x0) ? d1 : d0; | |
if(d) { | |
that.setState({dFocus: d}); | |
that.setState({xFocus: that.state.xline(d.x)}); | |
that.setState({yFocus: that.state.yline(d.y)}); | |
let text = d3.select("text"); | |
let bbox = text.node().getBBox(); | |
let padding = 2; | |
let rect = d3.select("rect") | |
.attr("width", bbox.width + (padding * 2)) | |
.attr("height", bbox.height + (padding * 2)); | |
} | |
}) | |
} | |
_mkLine() { | |
let that = this; | |
return ( | |
<path | |
stroke="black" | |
strokeWidth="2" | |
fill="none" | |
d={that._setAxes(this.props.chartData)} | |
strokeDasharray="5,5" | |
/> | |
) | |
} | |
_setAxes (data) { | |
let that = this; | |
let line = d3.svg.line() | |
.x(function(d) { return that.state.xline(d.x); }) | |
.y(function(d) { return that.state.yline(d.y); }) | |
return line(data); | |
} | |
_setDeviations (x, center, dev) { | |
let devData= [{"x": x, "y": center + dev}, {"x": x, "y": center - dev}] | |
return this._setAxes(devData) | |
} | |
_mkMarker() { | |
let that = this; | |
let {barLength} = this.state; | |
return ( | |
<g> | |
{ | |
this.props.chartData.map((line, i) => { | |
return ( | |
<g key={i}> | |
<path stroke="black" | |
strokeWidth={1} | |
fill="none" | |
d={that._setDeviations(line.x, line.y, line.d)}/> | |
<line stroke="black" | |
strokeWidth={1} | |
fill="none" | |
x1={that.state.xline(line.x) - barLength} y1={this.state.yline(line.y + line.d)} | |
x2={that.state.xline(line.x) + barLength} y2={this.state.yline(line.y + line.d)} | |
/> | |
<line stroke="black" | |
strokeWidth={1} | |
fill="none" | |
x1={that.state.xline(line.x) - barLength} y1={this.state.yline(line.y - line.d)} | |
x2={that.state.xline(line.x) + barLength} y2={this.state.yline(line.y - line.d)} | |
/> | |
</g> | |
) | |
}) | |
} | |
</g> | |
) | |
} | |
_mkFocus() { | |
const {xFocus, yFocus, dFocus, barLength} = this.state; | |
return( | |
dFocus ? | |
<g> | |
<circle | |
stroke="black" | |
strokeWidth="2" | |
fill="none" | |
cx={xFocus} | |
cy={yFocus} | |
r={4} | |
display={focus}/> | |
<rect x={xFocus} y={yFocus} rx={10} ry={10} width={150} height={50} | |
fill="rgb(31, 119, 180)" | |
stroke="rgb(31, 119, 180)" | |
opacity={0.8} | |
/> | |
<text x={xFocus } y={yFocus + barLength} fill="black"> | |
<tspan x={xFocus } dy=".6em">{this.props.xLabel + ": " + dFocus.x}</tspan> | |
<tspan x={xFocus } dy="1.2em">{this.props.yLabel + ": " + dFocus.y.toFixed(2)}</tspan> | |
<tspan x={xFocus } dy="1.2em">{ "Deviation: " + dFocus.d.toFixed(2)}</tspan> | |
</text> | |
</g> : null | |
) | |
} | |
render() { | |
const {chartData, width, margins, height, id} = this.props; | |
let x = function(d) { | |
return d.x; | |
}; | |
let y = function(d) { | |
return d.y; | |
}; | |
let that = this; | |
let divStyle = { | |
width: width | |
}; | |
let t = `translate(${margins.left}, ${margins.top})`; | |
let tX = `translate(0, ${height - margins.top})`; | |
return( | |
<div style={divStyle}> | |
<svg | |
height = {height} | |
width = {width} | |
id = {id} | |
ref = "svgContainer" | |
> | |
<g | |
transform = {t} | |
> | |
<g> | |
{this._mkFocus()} | |
</g> | |
<g> | |
{this._mkLine()} | |
</g> | |
<g> | |
{this._mkMarker()} | |
</g> | |
<g transform={`translate(0, ${height - margins.top - margins.bottom})`}> | |
<line x1={0} y1={0} | |
x2={width - margins.left - margins.right} y2={0} | |
stroke="black" | |
strokeWidth={1} | |
fill="none"/> | |
</g> | |
<g> | |
<line x1={0} y1={0} | |
x2={0} y2={height - margins.top - margins.bottom} | |
stroke="black" | |
strokeWidth={1} | |
fill="none"/> | |
</g> | |
<Xaxis | |
{...this.props} | |
xDomain= {this.state.xDomain} | |
xRange = {this.state.xRange} | |
x= {x} | |
/> | |
<Yaxis | |
{...this.props} | |
y= {y} | |
yDomain= {this.state.yDomain} | |
yRange = {this.state.yRange} | |
yAxisClassName = 'y-axis' | |
/> | |
</g> | |
</svg> | |
</div> | |
) | |
} | |
} | |
export default LineChartWithDeviations; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment