react

D3 and react

I know, there are various posts about how to integrate D3 into react components like this or this. If you try it naively you'll face the problem of having a new diagram for each state change and that's a bit odd. Otherwise the showed solutions in above mentioned posts will require much more time to add a short small little D3 diagram. Here's my solution which will get you a diagram controlled by D3 and updated on whatever your condition is. The really important part is to remove the whole svg item inside the given react item and re-add it. Not the most elegant way but hey: it works!

import React from "react";
import { Link } from 'react-router-dom';
import {connect} from 'react-redux';
import $ from "jquery";
import '../../ext/js/bootstrap';
import * as d3 from "d3";

class PriceGraphComponent extends React.Component {

    constructor(props) {
        super(props);
        this.svg = null;
    }

    renderD3(item) {
        var data = item.prices;
        var mindate = data[0].seen,
            maxdate = data[data.length-1].seen;

        console.log(data);

        var margin = {top: 40, right: 40, bottom: 40, left: 40},
            width = 530 - margin.left - margin.right,
            height = 500 - margin.top - margin.bottom;

        var x = d3.scaleTime()
            .domain(d3.extent(data, function(d) {return new Date(d.seen)}))
            .range([0, width]);

        var y = d3.scaleLinear()
            .domain(d3.extent(data, function(d) {return d.price_per_unit}))
            .range([height, 0]);


        var line = d3.line()
            .defined(function(d) { return d; })
            .x(function(d) { return x(new Date(d.seen)); })
            .y(function(d) { return y(d.price_per_unit); });

        this.svg = d3.select("#chart").append("svg")
            .datum(data)
            .attr("width", width + margin.left + margin.right)
            .attr("height", height + margin.top + margin.bottom)
            .append("g")
            .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

        this.svg.append("g")
            .attr("class", "axis axis--x")
            .attr("transform", "translate(0," + height + ")")
            .call(d3.axisBottom(x).tickSize(0).tickFormat(() => {}));

        this.svg.append("g")
            .attr("class", "axis axis--y")
            .call(d3.axisLeft(y));

        this.svg.append("path")
            .attr("class", "line")
            .attr("d", line);

        this.svg.selectAll(".dot")
            .data(data.filter(function(d) { return d; }))
            .enter().append("circle")
            .attr("class", "dot")
            .attr("cx", line.x())
            .attr("cy", line.y())
            .attr("r", 3.5);
    }

    componentDidMount() {
        this.renderD3(this.props.item);
    }


    shouldComponentUpdate(nextProps, nextState, nextContext) {
        if (this.props.item.loading == true && nextProps.item.loading == false && nextProps.item.error != true){
            d3.select("#chart > svg").remove();
            this.renderD3(nextProps.item);
        }
        return false;
    }

    render() {
        return (<div className="col-md-5 mb-4">
                    <div className="card shadow-nohover">
                        <div className="card-header">Horizontal Bar Chart</div>
                        <div className="card-body" id="chart"> </div>
                    </div>
                </div>);
    }
}

function mapStateToProps(state){
    return {...state};
};

function matchDispatchToProps(dispatch){
    return {dispatch};
};

export default connect(mapStateToProps, matchDispatchToProps)(PriceGraphComponent);