ketan
ketan

Reputation: 2904

how to render svg element of d3.js using react.js?

I'm trying to render graph using react.js. In which I'm trying to use simple ReactDOM.render() method on html page. when I'm appending this graph content to the body it will work for me well but I want to render this using react.js.

program-code:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<!--react js url here -->
</head>
<body>
<script>
var csv_data = "date,bucket,count\n19:37:1 164,30000,12\n19:37:1 283,30000,8\n19:37:1 349,80000,2\n19:37:1 421,30000,16\n19:37:1 599,30000,2\n19:37:1 608,40000,4\n19:37:1 755,30000,4\n19:37:1 857,30000,2\n19:37:1 915,40000,6";
var margin = {
        top : 20,
        right : 90,
        bottom : 30,
        left : 50
    }, width = 960 - margin.left - margin.right, height = 500 - margin.top
            - margin.bottom;

var svg = d3.select("#svg").attr("width",
            width + margin.left + margin.right).attr("height",
            height + margin.top + margin.bottom).append("g").attr(
            "transform",
            "translate(" + margin.left + "," + margin.top + ")");
var buckets = d3.csv.parse(csv_data);

    buckets.forEach(function(d) {
        d.date = new Date(timeFormat.parse(d.date));
        d.bucket = +d.bucket;
        d.count = +d.count;
    });

    x.domain(d3.extent(buckets, function(d) {
        return d.date;
    }));

    y.domain(d3.extent(buckets, function(d) {
        return d.bucket;
    }));

    z.domain([ 0, d3.max(buckets, function(d) {
        return d.count;
    }) ]);
x.domain([ x.domain()[0], +x.domain()[1] + xStep ]);
    y.domain([ y.domain()[0], y.domain()[1] + yStep ]);

    // Display the tiles for each non-zero bucket.
    svg.selectAll(".tile").data(buckets).enter().append("rect").attr(
            "class", "tile").attr("x", function(d) {
        return x(d.date);
    }).attr("y", function(d) {
        return y(d.bucket + yStep);
    }).attr("width", x(xStep) - x(0)).attr("height", y(0) - y(yStep))
            .style("fill", function(d) {
                return z(d.count);
            });

    // Add a legend for the color values.
    var legend = svg.selectAll(".legend").data(
            z.ticks(6).slice(1).reverse()).enter().append("g").attr(
            "class", "legend").attr("transform", function(d, i) {
        return "translate(" + (width + 20) + "," + (20 + i * 20) + ")";
    });

    legend.append("rect").attr("width", 20).attr("height", 20).style(
            "fill", z);

    legend.append("text").attr("x", 26).attr("y", 10).attr("dy", ".35em")
            .text(String);

    svg.append("text").attr("class", "label").attr("x", width + 20).attr(
            "y", 10).attr("dy", ".35em").text("Count");

    // Add an x-axis with label.
    svg.append("g").attr("class", "x axis").attr("transform",
            "translate(0," + height + ")").call(xAxis).append("text").attr(
            "class", "label").attr("x", width).attr("y", -6).attr(
            "text-anchor", "end").text("Time");

    // Add a y-axis with label.
    svg.append("g").attr("class", "y axis").call(yAxis).append("text")
            .attr("class", "label").attr("y", 6).attr("dy", ".71em").attr(
                    "text-anchor", "end").attr("transform", "rotate(-90)")
            .text("Latency");
</script>
<div id="container"></div>
<script type="text/jsx">
    ReactDOM.render(<svg id="svg"></svg>,document.getElementById('container'));     
</script>
</body>
</html>

But graph is not visible in the browser when I'm requesting to this page. what is that I'm missing here?

Upvotes: 3

Views: 6509

Answers (3)

Lukas Liesis
Lukas Liesis

Reputation: 26403

I published a tiny npm package to resolve this. npm i hook-use-d3 and then use like so:

In sample below I added some d3 code to show you animated green dot, to make sure you will have visual to confirm it works. As an extra, it's also animated :)

import * as d3 from "d3";
import React from "react";
import useD3 from "hook-use-d3";

export default function SomeComponent() {
  const d3ref = useD3((svg) => {
    const circle = svg
      .append("circle")
      .attr("cx", 50)
      .attr("cy", 50)
      .attr("r", 1)
      .attr("fill", "#4CAF50");

    function animate() {
      let radius = 5;
      let distance = 35;
      circle
        .transition()
        .duration(2000)
        .attrTween("r", () => d3.interpolate(radius, radius + distance))
        .on("end", () => {
          radius = circle.attr("r");
          distance = 35;

          circle
            .transition()
            .duration(2000)
            .attrTween("r", () => d3.interpolate(radius, radius - distance))
            .on("end", animate);
        });
    }
    animate();
  }, []);

  return <svg ref={d3ref} style={{ height: 100, width: 100 }}></svg>;
}

If you want to see the code of the hook, posted it on github: https://github.com/liesislukas/hook-use-d3/tree/main

enter image description here

Upvotes: 2

ViggoV
ViggoV

Reputation: 2173

Since the d3 code is executed before the React code, the svg element doesn't exist yet. You can create a react class which renders the svg element, and then place your d3 code componentDidMount and/or componentDidUpdate method. React renders everything in a shadow DOM before adding it to the actual DOM so you need to make sure the element is available before you can select it with d3 (or jQuery, etc.)

class MySVG extends React.Component {

  componentDidMount() {
    var svg = d3.select('#my-svg');
    //Do svg stuff
  }

  render() {
    return (
      <svg id="my-svg"></svg>
    );
  }

}

export default MySVG;

and then render mySVG with ReactDOM.render()

Upvotes: 4

Gerardo Furtado
Gerardo Furtado

Reputation: 102194

Unless "svg" is the ID of an existing svg in the page, it seems to me that you forgot to append the SVG in your variable svg:

var svg = d3.select("#svg")
    .append("svg")//this appends the SVG
    .attr("width", width + margin.left + margin.right)
    .attr("height",  height + margin.top + margin.bottom)
    .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

Upvotes: 0

Related Questions