Bubble Chart component created using React and D3
A Pen by Jackfiallos on CodePen.
| <div id="charts"></div> |
Bubble Chart component created using React and D3
A Pen by Jackfiallos on CodePen.
| class BubbleChart extends React.Component { | |
| static propTypes = { | |
| data: React.PropTypes.array, | |
| width: React.PropTypes.number, | |
| height: React.PropTypes.number, | |
| useLabels: React.PropTypes.bool | |
| }; | |
| static defaultProps = { | |
| data: [], | |
| useLabels: false, | |
| width: 600, | |
| height: 400 | |
| }; | |
| constructor(props) { | |
| super(props); | |
| this.minValue = 1; | |
| this.maxValue = 100; | |
| this.mounted = false; | |
| this.state = { | |
| data: [] | |
| }; | |
| this.radiusScale = this.radiusScale.bind(this); | |
| this.simulatePositions = this.simulatePositions.bind(this); | |
| this.renderBubbles = this.renderBubbles.bind(this); | |
| } | |
| componentWillMount() { | |
| this.mounted = true; | |
| } | |
| componentDidMount() { | |
| if (this.props.data.length > 0) { | |
| this.minValue = | |
| 0.95 * | |
| d3.min(this.props.data, item => { | |
| return item.v; | |
| }); | |
| this.maxValue = | |
| 1.05 * | |
| d3.max(this.props.data, item => { | |
| return item.v; | |
| }); | |
| this.simulatePositions(this.props.data); | |
| } | |
| } | |
| componentWillUnmount() { | |
| this.mounted = false; | |
| } | |
| radiusScale = value => { | |
| const fx = d3 | |
| .scaleSqrt() | |
| .range([1, 50]) | |
| .domain([this.minValue, this.maxValue]); | |
| return fx(value); | |
| }; | |
| simulatePositions = data => { | |
| this.simulation = d3 | |
| .forceSimulation() | |
| .nodes(data) | |
| .velocityDecay(0.5) | |
| .force("x", d3.forceX().strength(0.05)) | |
| .force("y", d3.forceY().strength(0.05)) | |
| .force( | |
| "collide", | |
| d3.forceCollide(d => { | |
| return this.radiusScale(d.v) + 2; | |
| }) | |
| ) | |
| .on("tick", () => { | |
| if (this.mounted) { | |
| this.setState({ data }); | |
| } | |
| }); | |
| }; | |
| renderBubbles = data => { | |
| const minValue = | |
| 0.95 * | |
| d3.min(data, item => { | |
| return item.v; | |
| }); | |
| const maxValue = | |
| 1.05 * | |
| d3.max(data, item => { | |
| return item.v; | |
| }); | |
| const color = d3 | |
| .scaleLinear() | |
| .domain([minValue, maxValue]) | |
| .interpolate(d3.interpolateHcl) | |
| .range(["#eb001b", "#f79e1b"]); | |
| // render simple circle element | |
| if (!this.props.useLabels) { | |
| const circles = _.map(data, (item, index) => { | |
| return ( | |
| <circle | |
| key={index} | |
| r={this.radiusScale(item.v)} | |
| cx={item.x} | |
| cy={item.y} | |
| fill={color(item.v)} | |
| stroke={d3.rgb(color(item.v)).brighter(2)} | |
| strokeWidth="2" | |
| /> | |
| ); | |
| }); | |
| return ( | |
| <g | |
| transform={`translate(${this.props.width / 2}, ${this.props | |
| .height / 2})`} | |
| > | |
| {circles} | |
| </g> | |
| ); | |
| } | |
| // render circle and text elements inside a group | |
| const texts = _.map(data, (item, index) => { | |
| const props = this.props; | |
| const fontSize = this.radiusScale(item.v) / 2; | |
| return ( | |
| <g | |
| key={index} | |
| transform={`translate(${props.width / 2 + | |
| item.x}, ${props.height / 2 + item.y})`} | |
| > | |
| <circle | |
| r={this.radiusScale(item.v)} | |
| fill={color(item.v)} | |
| stroke={d3.rgb(color(item.v)).brighter(2)} | |
| strokeWidth="2" | |
| /> | |
| <text | |
| dy="6" | |
| fill="#fff" | |
| textAnchor="middle" | |
| fontSize={`${fontSize}px`} | |
| fontWeight="bold" | |
| > | |
| {item.v} | |
| </text> | |
| </g> | |
| ); | |
| }); | |
| return texts; | |
| }; | |
| render() { | |
| if (this.state.data.length) { | |
| return ( | |
| <svg width={this.props.width} height={this.props.height}> | |
| {this.renderBubbles(this.state.data)} | |
| </svg> | |
| ); | |
| } | |
| return <div>Loading</div>; | |
| } | |
| } | |
| const rawdata = _.map(_.range(24), () => { | |
| return { | |
| v: _.random(10, 100) | |
| }; | |
| }); | |
| ReactDOM.render( | |
| <BubbleChart useLabels data={rawdata} />, | |
| document.getElementById("charts") | |
| ); |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.9.1/d3.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react-dom.min.js"></script> |