Home > Mobile >  How to do Updating in Barchart d3.js and React?
How to do Updating in Barchart d3.js and React?

Time:09-26

I am trying to update a barchart using React and D3. The functionality I am trying to implement is basically the one shown in this d3 example: https://d3-graph-gallery.com/graph/barplot_button_data_hard.html. Thus I was trying to adapt the code to my react app.

Thus, I want to update the data when my state changes with data from the state. The problem I am facing is that the y-axis does not show any ticks at all, and the x-axis does not update the ticks correctly but rather adds the new tick values to the axis.

My code is as follows:

const BarChart = () => {
  let statePlots = useSelector((state) => state.plots);
  const data = useSelector((state) => state.plots.data);
  const ref = useRef();

  useEffect(() => {
    const svg = d3.select(ref.current);
    // set the dimensions and margins of the graph
    const margin = { top: 30, right: 30, bottom: 70, left: 60 },
      width = 460 - margin.left - margin.right,
      height = 400 - margin.top - margin.bottom;

    // add to the svg object of the page
    svg
      .attr("width", width   margin.left   margin.right)
      .attr("height", height   margin.top   margin.bottom)
      .append("g")
      .attr("transform", `translate(${margin.left},${margin.top})`);

    // Initialize the X axis
    const x = d3.scaleBand().range([0, width]).padding(0.2);
    const xAxis = svg.append("g").attr("transform", `translate(0,${height})`);

    // Initialize the Y axis
    const y = d3.scaleLinear().range([height, 0]);
    const yAxis = svg.append("g").attr("class", "myYaxis");

    // Update the X axis
    x.domain(data.map((d) => d.group));
    xAxis.call(d3.axisBottom(x));

    // Update the Y axis
    y.domain([0, d3.max(data, (d) => d.value)]);
    yAxis.transition().duration(1000).call(d3.axisLeft(y));

    // Create the u variable
    var u = svg.selectAll("rect").data(data);

    u.join("rect") // Add a new rect for each new elements
      .transition()
      .duration(1000)
      .attr("x", (d) => x(d.group))
      .attr("y", (d) => y(d.value))
      .attr("width", x.bandwidth())
      .attr("height", (d) => height - y(d.value))
      .attr("fill", "#69b3a2");
  }, [statePlots]);

  return (
    <div>
      <div>D3 Plot</div>
      <svg ref={ref}></svg>
    </div>
  );
};

The code above does switch the data, but as I mentioned, it doesn't correctly update the ticks. I am very new to d3.js and also not an expert on React. Any help is appreciated. Thanks in advance

CodePudding user response:

The first issue I identified with your adaptation is that you're not separating initialization from the update. Notice there are two different blocks of code in the d3 example you linked, one for initializing which is in the main body of the script, and the other for the update, which is inside the update function. You can separate them using two useEffect hooks and an additional state to check the graph has been initialized.

const [loading, setLoading] = useState(true);
const ref = useRef();
const chartConfig = useRef();
useEffect(() => {
  // set up d3 objects (chartConfig)
  setLoading(false);
}, []);

useEffect(() => {
  if (loading || !chartConfig.current) return;
  const { svg, x, xAxis, y, yAxis } = chartConfig.current;

  // update chart UI
}, [data, loading]);

Additionally, you mistakenly assigned the SVG container by not chaining the "g" element selection, where you should draw all other elements since it has the margins. This was causing the ticks to be too far to the left.

Instead of this

    const svg = d3.select(ref.current);

    // add to the svg object of the page
    svg
      .attr("width", width   margin.left   margin.right)
      .attr("height", height   margin.top   margin.bottom)
      .append("g")
      .attr("transform", `translate(${margin.left},${margin.top})`);

You need to leave as in the example

    const svg = d3
      .select(ref.current)
      .attr("width", width   margin.left   margin.right)
      .attr("height", height   margin.top   margin.bottom)
      .append("g")
      .attr("transform", `translate(${margin.left},${margin.top})`)

And lastly, you also need to remove the old elements when updating the chart

    u.exit().remove();

You can check my adaptation of the example here and change it to your needs https://codesandbox.io/s/d3-charts-initializing-and-updating-leke3m?file=/src/App.js

CodePudding user response:

I think you are not calling enter method and instead of join you should call append

So can you try this and check please

       u
        .enter()
        .append("rect")
        .transition()
        .duration(1000)
        .attr("x", d => x(d.group))
        .attr("y", d => y(d.value))
        .attr("width", x.bandwidth())
        .attr("height", d => height - y(d.value))
        .attr("fill", "#69b3a2")
  • Related