Reactjs d3雷达图在React中呈现不一致

Reactjs d3雷达图在React中呈现不一致,reactjs,d3.js,Reactjs,D3.js,我的图表是昨晚绘制的。现在,它有时会完全渲染,有时会部分渲染,或者通常根本不渲染。我在开发工具中没有发现错误。我将我的d3代码复制到一个简单的javascript文件中,只使用了最新的d3脚本,它可以正常工作。如有任何见解,将不胜感激。非常感谢 回购协议(如果有帮助): 只有在正确安装的情况下,才能在RadarChart组件内运行D3代码: 添加useRefhook并将ref绑定到SVG: 把你所有的D3代码放在绘图图中: 查看它在a中的工作。您可以提供一个工作片段或小提琴吗?(第一次使用cod

我的图表是昨晚绘制的。现在,它有时会完全渲染,有时会部分渲染,或者通常根本不渲染。我在开发工具中没有发现错误。我将我的d3代码复制到一个简单的javascript文件中,只使用了最新的d3脚本,它可以正常工作。如有任何见解,将不胜感激。非常感谢

回购协议(如果有帮助):


只有在正确安装的情况下,才能在
RadarChart
组件内运行D3代码:

  • 添加
    useRef
    hook并将ref绑定到SVG:
  • 把你所有的D3代码放在绘图图中:

  • 查看它在a

    中的工作。您可以提供一个工作片段或小提琴吗?(第一次使用codesandbox——这是一个来自repo的git克隆)我想知道useEffect的实现是否会有所帮助。可能存在DOM操作冲突。但我似乎不能正确地执行钩子。你就是迈克尔。非常感谢你。
    import React from "react";
    import * as d3 from "d3";
    
    function RadarChart() {
    
      
      /////////////////////////////////////////////////////////
      /////////////// The Radar Chart Function ////////////////
      /////////////// Written by Nadieh Bremer ////////////////
      ////////////////// VisualCinnamon.com ///////////////////
      /////////// Inspired by the code of alangrafu ///////////
      /////////////////////////////////////////////////////////
    
      /* Radar chart design created by Nadieh Bremer - VisualCinnamon.com */
    
      //////////////////////// Set-Up //////////////////////////////
    
      var margin = { top: 100, right: 100, bottom: 100, left: 100 },
        width = Math.min(700, window.innerWidth - 10) - margin.left - margin.right,
        height = Math.min(
          width,
          window.innerHeight - margin.top - margin.bottom - 20
        );
    
      ////////////////////////// Data //////////////////////////////
    
      var data = [
        [
          //Tsircon
          { axis: "BALLISTIC", value: 65 },
          { axis: "Glide", value: 39 },
          { axis: "20G TURN", value: 43 },
          { axis: "FISH HOOK DIVE", value: 50 },
          { axis: "PHUGOID MOTION", value: 60 },
          { axis: "ENERGY SCRUB", value: 52 },
        ],
        //   [
        //     //Kinzhal
        //     { axis: "BALLISTIC", value: 0.27 },
        //     { axis: "Glide", value: 0.16 },
        //     { axis: "20G TURN", value: 0.35 },
        //     { axis: "FISH HOOK DIVE", value: 0.13 },
        //     { axis: "PHUGOID MOTION", value: 0.2 },
        //     { axis: "ENERGY SCRUB", value: 0.13 },
        //   ],
        //   [
        //     //Example Missile Overlay
        //     { axis: "BALLISTIC", value: 0.26 },
        //     { axis: "Glide", value: 0.1 },
        //     { axis: "20G TURN", value: 0.3 },
        //     { axis: "FISH HOOK DIVE", value: 0.14 },
        //     { axis: "PHUGOID MOTION", value: 0.22 },
        //     { axis: "ENERGY SCRUB", value: 0.04 },
        //   ],
      ];
      //////////////////// Draw the Chart //////////////////////////
    
      var color = d3.scaleBand().range(["#739CC4", "#CC333F", "#00A0B0"]);
    
      var radarChartOptions = {
        w: width,
        h: height,
        margin: margin,
        maxValue: 0.5,
        levels: 5,
        roundStrokes: false,
        color: color,
      };
      //Call function to draw the Radar chart
      RadarChart(".radarChart", data, radarChartOptions);
    
      function RadarChart(id, data, options) {
        var cfg = {
          w: 600, //Width of the circle
          h: 600, //Height of the circle
          margin: { top: 20, right: 20, bottom: 20, left: 20 }, //The margins of the SVG
          levels: 3, //How many levels or inner circles should there be drawn
          maxValue: 0, //What is the value that the biggest circle will represent
          labelFactor: 1.25, //How much farther than the radius of the outer circle should the labels be placed
          wrapWidth: 60, //The number of pixels after which a label needs to be given a new line
          opacityArea: 0.35, //The opacity of the area of the blob
          dotRadius: 4, //The size of the colored circles of each blog
          opacityCircles: 0.1, //The opacity of the circles of each blob
          strokeWidth: 2, //The width of the stroke around each blob
          roundStrokes: false, //If true the area and stroke will follow a round path (cardinal-closed)
          color: d3.scaleBand(d3.schemeCategory10), //Color function
        };
    
        //Put all of the options into a variable called cfg
        if ("undefined" !== typeof options) {
          for (var i in options) {
            if ("undefined" !== typeof options[i]) {
              cfg[i] = options[i];
            }
          } //for i
        } //if
    
        //If the supplied maxValue is smaller than the actual one, replace by the max in the data
        var maxValue = Math.max(
          cfg.maxValue,
          d3.max(data, function (i) {
            return d3.max(
              i.map(function (o) {
                return o.value;
              })
            );
          })
        );
    
        var allAxis = data[0].map(function (i, j) {
            return i.axis;
          }), //Names of each axis
          total = allAxis.length, //The number of different axes
          radius = Math.min(cfg.w / 2, cfg.h / 2), //Radius of the outermost circle
          Format = d3.format("%"), //Percentage formatting
          angleSlice = (Math.PI * 2) / total; //The width in radians of each "slice"
    
        //Scale for the radius
        var rScale = d3.scaleLinear().range([0, radius]).domain([0, maxValue]);
    
        //////////// Create the container SVG and g /////////////
    
        //Remove whatever chart with the same id/class was present before
        d3.select(id).select("svg").remove();
    
        //Initiate the radar chart SVG
        var svg = d3
          .select(id)
          .append("svg")
          .attr("width", cfg.w + cfg.margin.left + cfg.margin.right)
          .attr("height", cfg.h + cfg.margin.top + cfg.margin.bottom)
          .attr("class", "radar" + id);
        //Append a g element
        var g = svg
          .append("g")
          .attr(
            "transform",
            "translate(" +
              (cfg.w / 2 + cfg.margin.left) +
              "," +
              (cfg.h / 2 + cfg.margin.top) +
              ")"
          );
    
        ////////// Glow filter ///////////
    
        //Filter for the outside glow
        var filter = g.append("defs").append("filter").attr("id", "glow"),
          feGaussianBlur = filter
            .append("feGaussianBlur")
            .attr("stdDeviation", "2.5")
            .attr("result", "coloredBlur"),
          feMerge = filter.append("feMerge"),
          feMergeNode_1 = feMerge.append("feMergeNode").attr("in", "coloredBlur"),
          feMergeNode_2 = feMerge.append("feMergeNode").attr("in", "SourceGraphic");
    
        /////////////// Draw the Circular grid //////////////////
    
        //Wrapper for the grid & axes
        var axisGrid = g.append("g").attr("class", "axisWrapper");
    
        //Draw the background circles
        axisGrid
          .selectAll(".levels")
          .data(d3.range(1, cfg.levels + 1).reverse())
          .enter()
          .append("circle")
          .attr("class", "gridCircle")
          .attr("r", function (d, i) {
            return (radius / cfg.levels) * d;
          })
          .style("fill", "#CDCDCD")
          .style("stroke", "#CDCDCD")
          .style("fill-opacity", cfg.opacityCircles)
          .style("filter", "url(#glow)");
    
        //Text indicating at what % each level is
        axisGrid
          .selectAll(".axisLabel")
          .data(d3.range(1, cfg.levels + 1).reverse())
          .enter()
          .append("text")
          .attr("class", "axisLabel")
          .attr("x", 4)
          .attr("y", function (d) {
            return (-d * radius) / cfg.levels;
          })
          .attr("dy", "0.4em")
          .style("font-size", "10px")
          .attr("fill", "#737373")
          .text(function (d, i) {
            return Format((maxValue * d) / cfg.levels);
          });
    
        //////////////////// Draw the axes //////////////////////
    
        //Create the straight lines radiating outward from the center
        var axis = axisGrid
          .selectAll(".axis")
          .data(allAxis)
          .enter()
          .append("g")
          .attr("class", "axis");
        //Append the lines
        axis
          .append("line")
          .attr("x1", 0)
          .attr("y1", 0)
          .attr("x2", function (d, i) {
            return rScale(maxValue * 1.1) * Math.cos(angleSlice * i - Math.PI / 2);
          })
          .attr("y2", function (d, i) {
            return rScale(maxValue * 1.1) * Math.sin(angleSlice * i - Math.PI / 2);
          })
          .attr("class", "line")
          .style("stroke", "white")
          .style("stroke-width", "2px");
    
        //Append the labels at each axis
        axis
          .append("text")
          .attr("class", "legend")
          .style("font-size", "11px")
          .attr("text-anchor", "middle")
          .attr("dy", "0.35em")
          .attr("x", function (d, i) {
            return (
              rScale(maxValue * cfg.labelFactor) *
              Math.cos(angleSlice * i - Math.PI / 2)
            );
          })
          .attr("y", function (d, i) {
            return (
              rScale(maxValue * cfg.labelFactor) *
              Math.sin(angleSlice * i - Math.PI / 2)
            );
          })
          .text(function (d) {
            return d;
          })
          .call(wrap, cfg.wrapWidth);
    
        svg
          .append("text")
          .attr("x", width / 4.8)
          .attr("y", 75 - margin.top / 2)
          .attr("text-anchor", "middle")
          .style("font-size", "24px")
          .style("font-weight", "bold")
          .style("fill", "white")
          .text("MANEUVERS");
    
        svg
          .append("text")
          .attr("x", width / 4.2)
          .attr("y", 95 - margin.top / 2)
          .attr("text-anchor", "middle")
          .style("font-size", "16px")
          .style("fill", "white")
          .text("initiate: % ground track");
    
        svg
          .append("text")
          .attr("x", width / 3.6)
          .attr("y", 115 - margin.top / 2)
          .attr("text-anchor", "middle")
          .style("font-size", "16px")
          .style("fill", "#8CD9FF")
          .text("% variability");
    
        svg
          .append("text")
          .attr("x", width / 8)
          .attr("y", 135 - margin.top / 2)
          .attr("text-anchor", "middle")
          .style("font-size", "16px")
          .style("fill", "white")
          .text("sample:");
    
        svg
          .append("text")
          .attr("x", width / 3.4)
          .attr("y", 135 - margin.top / 2)
          .attr("text-anchor", "middle")
          .style("font-size", "16px")
          .style("fill", "#76ADDB")
          .text("% trajectories");
    
        ///////////// Draw the radar chart blobs ////////////////
    
        //The radial line function
        var radarLine = d3
          .lineRadial()
          .curve(d3.curveLinearClosed)
          .radius(function (d) {
            return rScale(d.value);
          })
          .angle(function (d, i) {
            return i * angleSlice;
          });
        if (cfg.roundStrokes) {
          radarLine.curve(d3.curveCardinalClosed);
        }
    
        //Create a wrapper for the blobs
        var blobWrapper = g
          .selectAll(".radarWrapper")
          .data(data)
          .enter()
          .append("g")
          .attr("class", "radarWrapper");
    
        //Append the backgrounds
        blobWrapper
          .append("path")
          .attr("class", "radarArea")
          .attr("d", function (d, i) {
            return radarLine(d);
          })
          .style("fill", function (d, i) {
            return cfg.color(i);
          })
          .style("fill-opacity", cfg.opacityArea)
          .on("mouseover", function (d, i) {
            //Dim all blobs
            d3.selectAll(".radarArea")
              .transition()
              .duration(200)
              .style("fill-opacity", 0.1);
            //Bring back the hovered over blob
            d3.select(this).transition().duration(200).style("fill-opacity", 0.7);
          })
          .on("mouseout", function () {
            //Bring back all blobs
            d3.selectAll(".radarArea")
              .transition()
              .duration(200)
              .style("fill-opacity", cfg.opacityArea);
          });
    
        //Create the outlines
        blobWrapper
          .append("path")
          .attr("class", "radarStroke")
          .attr("d", function (d, i) {
            return radarLine(d);
          })
          .style("stroke-width", cfg.strokeWidth + "px")
          .style("stroke", function (d, i) {
            return cfg.color(i);
          })
          .style("fill", "none")
          .style("filter", "url(#glow)");
    
        //Append the circles
        blobWrapper
          .selectAll(".radarCircle")
          .data(function (d, i) {
            return d;
          })
          .enter()
          .append("circle")
          .attr("class", "radarCircle")
          .attr("r", cfg.dotRadius)
          .attr("cx", function (d, i) {
            return rScale(d.value) * Math.cos(angleSlice * i - Math.PI / 2);
          })
          .attr("cy", function (d, i) {
            return rScale(d.value) * Math.sin(angleSlice * i - Math.PI / 2);
          })
          .style("fill", function (d, i, j) {
            return cfg.color(j);
          })
          .style("fill-opacity", 0.8);
    
        //////// Append invisible circles for tooltip ///////////
    
        //Wrapper for the invisible circles on top
        var blobCircleWrapper = g
          .selectAll(".radarCircleWrapper")
          .data(data)
          .enter()
          .append("g")
          .attr("class", "radarCircleWrapper");
    
        //Append a set of invisible circles on top for the mouseover pop-up
        blobCircleWrapper
          .selectAll(".radarInvisibleCircle")
          .data(function (d, i) {
            return d;
          })
          .enter()
          .append("circle")
          .attr("class", "radarInvisibleCircle")
          .attr("r", cfg.dotRadius * 1.5)
          .attr("cx", function (d, i) {
            return rScale(d.value) * Math.cos(angleSlice * i - Math.PI / 2);
          })
          .attr("cy", function (d, i) {
            return rScale(d.value) * Math.sin(angleSlice * i - Math.PI / 2);
          })
          .style("fill", "none")
          .style("pointer-events", "all")
          .on("mouseover", function (d, i) {
            let newX = parseFloat(d3.select(this).attr("cx")) - 10;
            let newY = parseFloat(d3.select(this).attr("cy")) - 10;
    
            tooltip
              .attr("x", newX)
              .attr("y", newY)
              .text(Format(d.value))
              .transition()
              .duration(200)
              .style("opacity", 1);
          })
          .on("mouseout", function () {
            tooltip.transition().duration(200).style("opacity", 0);
          });
    
        //Set up the small tooltip for when you hover over a circle
        var tooltip = g.append("text").attr("class", "tooltip").style("opacity", 0);
    
        /////////////////// Helper Function /////////////////////
    
        //Taken from http://bl.ocks.org/mbostock/7555321
        //Wraps SVG text
        function wrap(text, width) {
          text.each(function () {
            var text = d3.select(this),
              words = text.text().split(/\s+/).reverse(),
              word,
              line = [],
              lineNumber = 0,
              lineHeight = 1.4, // ems
              y = text.attr("y"),
              x = text.attr("x"),
              dy = parseFloat(text.attr("dy")),
              tspan = text
                .text(null)
                .append("tspan")
                .attr("x", x)
                .attr("y", y)
                .attr("dy", dy + "em");
    
            while ((word = words.pop())) {
              line.push(word);
              tspan.text(line.join(" "));
              if (tspan.node().getComputedTextLength() > width) {
                line.pop();
                tspan.text(line.join(" "));
                line = [word];
                tspan = text
                  .append("tspan")
                  .attr("x", x)
                  .attr("y", y)
                  .attr("dy", ++lineNumber * lineHeight + dy + "em")
                  .text(word);
              }
            }
          });
        } //wrap
      } //RadarChart
      return (
        <div>
          <h1>Hi, I'm the radar chart</h1>
          <svg className="radarChart container"></svg>
        </div>
      );
    }
    export default RadarChart;
    
        "d3": "^6.7.0",
        "d3-scale": "^3.3.0",
        "gh-pages": "^3.1.0",
        "react": "^17.0.2",
        "react-dom": "^17.0.2",
    
    const svgRef = useRef(); 
    ...
    return (
      <div>
        <h1>Hi, I'm the radar chart</h1>
        <svg ref={svgRef} className="radarChart container" ></svg>
      </div>
    );
    
    useEffect(() => {
      if (svgRef.current) {
        const svg = d3.select(svgRef.current);
        drawChart(svg);
      }
    }, [svgRef]);
    
    const drawChart = svg => {
      ...
      svg.selectAll("*").remove();
    
      //Initiate the radar chart SVG
      svg
        .attr("width", cfg.w + cfg.margin.left + cfg.margin.right)
        .attr("height", cfg.h + cfg.margin.top + cfg.margin.bottom)
      ...
    }