|
<!DOCTYPE html> |
|
<head> |
|
<meta charset="utf-8"> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script> |
|
<script src="https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-xpa1/t39.3284-6/12512178_218562685145124_130271029_n.js"></script> |
|
<script src="https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-xfa1/t39.3284-6/12512184_1664789273772979_614489084_n.js"></script> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.24/browser.js"></script> |
|
</head> |
|
<body> |
|
<div id='example'></div> |
|
<script type="text/babel"> |
|
class Sparkline extends React.Component { |
|
constructor(props) { |
|
super(props); |
|
this.xScale = d3.scale.linear(); |
|
this.yScale = d3.scale.linear(); |
|
this.line = d3.svg.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') |
|
); |
|
</script> |
|
</body> |