0

I need to create a line chart using React and D3 as shown below. The line chart i need to create

I already have this chart with the code i have written Line chart till now

The code till now is -

import React, { Component } from "react";
import * as d3 from "d3";

const width = 400;
const height = 350;
const margin = { top: 20, right: 8, bottom: 20, left: 35 };

class LineChart extends Component {
  state = {
    line: []
  };

  xAxis = d3.axisBottom();
  yAxis = d3.axisLeft();

  static getDerivedStateFromProps(nextProps, prevState) {
    const { data } = nextProps;
    console.log("test", data);
    if (!data) return {};

    const yScale = d3.scaleLinear().range([height - margin.bottom, margin.top]);

    const xScale = d3
      .scaleBand()
      .domain(data.map(d => d.price))
      .range([margin.left, width - margin.right])
      .padding(1);

    // set domains on the scales
    const demandMax = d3.extent(data, d => d.demand);
    const priceMax = d3.extent(data, d => d.price);
    yScale.domain(demandMax);

    const lineGenerator = d3
      .line()
      .x(d => xScale(d.price))
      .y(d => yScale(d.demand));

    const line = [
      {
        fill: "none",
        path: lineGenerator(data),
        stroke: "steelblue"
      }
    ];

    return { line, xScale, yScale, data };
  }

  componentDidUpdate() {
    this.xAxis.scale(this.state.xScale);
    d3.select(this.refs.xAxis).call(this.xAxis);
    this.yAxis.scale(this.state.yScale);
    d3.select(this.refs.yAxis).call(this.yAxis);
  }

  render() {
    console.log(this.state.line);
    console.log(this.state.data);
    return (
      <svg width={width} height={height}>
        {this.state.line.map((d, i) => (
          <path key={i} d={d.path} stroke={d.stroke} fill={d.fill} />
        ))}
        <g ref="xAxis" transform={`translate(0, ${height - margin.bottom})`} />
        <g ref="yAxis" transform={`translate(${margin.left}, 0)`} />
      </svg>
    );
  }
}

export default LineChart;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.1/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

Now i need to add functionality that circles come around all the points and intitially there is area with dotted lines for one of the points with values displayed inside the rectangle. then when user hovers over other point the dotted lines corresponds to that points. How can i achieve this functionality using d3 in react. I am unable to come up with a solution?

1 Answers1

1

I don't have a functioning version of your code, so I can't test whether this works exactly as you want. But it should be close enough so that you can merge it into your code.

Regarding the circles, you can do something along the lines of:

<g transform={/* Apply appropriate translate here */}>
  {data.map((d, i) => {
    return <circle
      key={i}
      cx={this.state.xScale(d.price)}
      cy={this.state.yScale(d.demand)}
      r={5}
      style={{
        fill: 'white',
        stroke: 'black'
      }}
    />
  })}
</g>

And put it after your other g elements in your svg element.

Regarding the dotted lines, you can listen to the mousemove event, update your state, and draw the lines accordingly. First add the event listener to get the mouse coordinates and store them in your state:

<svg 
  [your existing attributes]
  ...
  ref="svg" onMouseMove={(
    const xy = d3.mouse(this.refs.svg)
    // Update your state with the x and y coordinates in the xy array.
  )})>
  ...
  [your existing jsx]
</svg>

Remember to add a mouseout or mouseleave event listener, that unsets the xy value in your state.

Then you can simply add the paths in another g element, whenever there is an xy value in your state:

{if (this.state.xy) {
  return <g>
      <path
        d={`M{margin.left},${this.state.xy[1]}L${this.state.xy[0]},${this.state.xy[1]}`}
        style={ fill: 'none', strokeDasharray: '5,5', strokeWidth: 2 }
      />
      <path
        d={`M{this.state.xy[0]},${height - margin.bottom}L${this.state.xy[0]},${this.state.xy[1]}`}
        style={ fill: 'none', strokeDasharray: '5,5', strokeWidth: 2 }
      />
    </g>
} else {
  return null
}}

Again, as I don't have a working example to work with, this is not copy/paste-able, so you'll need to merge it into your project yourself.

Also, some of your positioning is a bit unconventional. I think I have taken it into account, but I cant be certain.

Anyway, I hope this helps!

matthias.rocks
  • 625
  • 3
  • 7