|
class Sparkline extends React.Component { |
|
constructor(props) { |
|
super(props) |
|
this.xScale = d3.scaleLinear() |
|
this.yScale = d3.scaleLinear() |
|
this.line = d3.line() |
|
this._updateDataTransforms(props) |
|
} |
|
|
|
componentDidMount() { |
|
const self = this |
|
d3.select(ReactDOM.findDOMNode(this.refs.svg)) |
|
.on('mousemove', function() { |
|
self._onMouseMove(d3.mouse(this)[0]) |
|
}) |
|
.on('mouseleave', function() { |
|
self._onMouseMove(null) |
|
}) |
|
} |
|
|
|
componentWillReceiveNewProps(newProps) { |
|
this._updateDataTransforms(newProps) |
|
} |
|
|
|
_updateDataTransforms(props) { |
|
const { xAccessor, yAccessor, width, height, data } = props |
|
|
|
this.xScale.domain([0, data.length]).range([0, width]) |
|
|
|
this.yScale.domain([0, 10]).range([height, 0]) |
|
|
|
this.line |
|
.x((d, i) => this.xScale(xAccessor(d, i))) |
|
.y((d, i) => this.yScale(yAccessor(d, i))) |
|
|
|
this.bisectByX = d3.bisector(xAccessor).left |
|
} |
|
|
|
_onMouseMove(xPixelPos) { |
|
const { data, onHover } = this.props |
|
if (xPixelPos === null) { |
|
onHover(null, null) |
|
} else { |
|
const xValue = this.xScale.invert(xPixelPos) |
|
const i = this.bisectByX(data, xValue, 1) |
|
onHover(data[i], i) |
|
} |
|
} |
|
|
|
render() { |
|
const { data, width, height, xAccessor, hovered } = this.props |
|
const hoveredRender = hovered ? ( |
|
<line |
|
x1={this.xScale(xAccessor(hovered))} |
|
x2={this.xScale(xAccessor(hovered))} |
|
y0={0} |
|
y1={height} |
|
style={{ strokeWidth: '0.5px', stroke: 'orange' }} |
|
/> |
|
) : null |
|
return ( |
|
<svg width={width} height={height} ref="svg"> |
|
<path |
|
style={{ fill: 'none', strokeWidth: '0.5px', stroke: 'steelblue' }} |
|
d={this.line(data)} |
|
/> |
|
{hoveredRender} |
|
</svg> |
|
) |
|
} |
|
} |
|
Sparkline.defaultProps = { |
|
xAccessor: ({ x }) => x, |
|
yAccessor: ({ y }) => y |
|
} |
|
|
|
class Example extends React.Component { |
|
constructor(props) { |
|
super(props) |
|
this.state = { |
|
hovered: null |
|
} |
|
} |
|
|
|
render() { |
|
const { data } = this.props |
|
const { hovered } = this.state |
|
const value = hovered |
|
? `${hovered.y} is the current value under cursor` |
|
: `${data.reduce((s, { y }) => s + y, 0)} is the sum of all the values` // total |
|
const divStyle = { |
|
color: 'orange', |
|
position: 'fixed', |
|
top: '50px' |
|
} |
|
const valueStyle = { |
|
position: 'fixed', |
|
top: '50px', |
|
left: '110px' |
|
} |
|
|
|
return ( |
|
<div style={divStyle}> |
|
<Sparkline |
|
data={data} |
|
width={100} |
|
height={20} |
|
hovered={hovered} |
|
onHover={(hovered, index) => this.setState({ hovered })} |
|
/> |
|
<div style={valueStyle}>{value}</div> |
|
</div> |
|
) |
|
} |
|
} |
|
|
|
let data = [ |
|
3, |
|
6, |
|
2, |
|
7, |
|
5, |
|
2, |
|
1, |
|
3, |
|
8, |
|
9, |
|
2, |
|
5, |
|
9, |
|
3, |
|
6, |
|
3, |
|
6, |
|
2, |
|
7, |
|
5, |
|
2, |
|
1, |
|
3, |
|
8, |
|
9, |
|
2, |
|
5, |
|
9, |
|
2, |
|
7, |
|
5, |
|
2, |
|
1, |
|
3, |
|
8, |
|
9, |
|
2, |
|
5, |
|
9, |
|
3, |
|
6, |
|
2, |
|
7, |
|
5, |
|
2, |
|
1, |
|
3, |
|
8, |
|
9, |
|
2, |
|
5, |
|
9 |
|
] |
|
// Interesting fact: d3.bisect accessors assume your not bisecting by the index. |
|
// Duh... |
|
data = data.map((y, x) => { |
|
return { x, y } |
|
}) |
|
|
|
ReactDOM.render(<Example data={data} />, document.getElementById('example')) |