Skip to content

Instantly share code, notes, and snippets.

@micahstubbs
Last active April 9, 2019 17:44
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save micahstubbs/75aede6164e7fdc3527e3ffae7921460 to your computer and use it in GitHub Desktop.
Save micahstubbs/75aede6164e7fdc3527e3ffae7921460 to your computer and use it in GitHub Desktop.
React + D3 Interactive Sparkline
license: CC0-1.0
height: 100
border: no

a fork of @milr0c's quick React + D3 sparkline, where I change some colors and add some descriptive text. an effort to show a simple, easily digestible example of React and D3 together.


Original README.md

Just a quick example of how I use React + d3 now a days.

<!DOCTYPE html>
<head>
<meta charset="utf-8" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.9.2/d3.min.js"></script>
<script
src="https://unpkg.com/react@16/umd/react.development.js"
crossorigin
></script>
<script
src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"
crossorigin
></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.24/browser.js"></script>
</head>
<body>
<div id="example"></div>
<script lang="babel" type="text/babel" src="./index.js"></script>
</body>
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'))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment