javascript - 放大和缩小功能在 D3 图表中未按预期工作并使用react

标签 javascript reactjs d3.js

在我的 D3 图表中,放大和缩小功能未按 D3 图表中的预期工作。

它正在缩放整个图形,而不仅仅是绘制的线条和区域。预期的行为应该只是放大和缩小图表,而不是 x 轴和 y 轴。

当我使用以下代码使用 d3.zoom() 时 -

.call(d3.zoom().on("zoom", function () {
   svg.attr("transform", d3.event.transform)
 }));

但是上面的代码也会更新轴。所以我尝试添加剪辑路径并尝试使用它,但它不起作用。

   // A function that updates the chart when the user zoom and thus new boundaries are available

    const updateChart = () => {
      // recover the new scale
      var newX = d3.event.transform.rescaleX(xScale);
      var newY = d3.event.transform.rescaleY(yScale);

      // update axes with these new boundaries
      xAxis.call(d3.axisBottom(newX));
      yAxis.call(d3.axisLeft(newY));

      // update circle position
      scatter
        .selectAll("circle")
        .attr("cx", function(d) {
          return newX(d.Sepal_Length);
        })
        .attr("cy", function(d) {
          return newY(d.Petal_Length);
        });
    };

    var zoom = d3
      .zoom()
      .scaleExtent([0.5, 20]) // This control how much you can unzoom (x0.5) and zoom (x20)
      .extent([[0, 0], [containerWidth, containerHeight]])
      .on("zoom", updateChart);

    var svg = d3
      .select(this.refs.chart)
      .append("svg")
      .attr("width", containerWidth)
      .attr("height", containerHeight)
      .call(zoom);

    // Create the scatter variable: where both the circles and the brush take place
    var scatter = svg.append("g").attr("clip-path", "url(#clip)");

这种散布应该移动圆圈以进行放大和缩小。我的工作代码沙箱here -

谢谢。

最佳答案

我尝试在react之外重新制作这个,你必须遵循d3 Zoom hierarki,

  1. 绘制定义,

  2. 绘制剪辑,

  3. 在剪辑中绘制要移动的项目,

  4. 更新元素

缩放后请记住比例正在更改,请使用新比例来更新所有元素,如果不这样做,它将跟随缩放而下降。

var data = [
      {
        startTime: "1567765320049",
        magnitude: 0,
        startupMagnitude: 0,
        startupRunningStatus: "IN_SYNC"
      },
      {
        startTime: "1567851720049",
        magnitude: 0,
        startupMagnitude: 0,
        startupRunningStatus: "IN_SYNC"
      },
      {
        startTime: "1568024520049",
        magnitude: 10,
        startupMagnitude: 10,
        startupRunningStatus: "IN_SYNC"
      },
      {
        startTime: "1568283720049",
        magnitude: 10,
        startupMagnitude: 0,
        startupRunningStatus: "OUT_OF_SYNC"
      },
      {
        startTime: "1568629320049",
        magnitude: 0,
        startupMagnitude: 10,
        startupRunningStatus: "OUT_OF_SYNC"
      },
      {
        startTime: "1569061320049",
        magnitude: 0,
        startupMagnitude: 0,
        startupRunningStatus: "IN_SYNC"
      },
      {
        startTime: "1569579720049",
        magnitude: -20,
        startupMagnitude: 0,
        startupRunningStatus: "OUT_OF_SYNC"
      },
      {
        startTime: "1570184520049",
        magnitude: -20,
        startupMagnitude: -10,
        startupRunningStatus: "OUT_OF_SYNC"
      },
      {
        startTime: "1570875720049",
        magnitude: 0,
        startupMagnitude: 0,
        startupRunningStatus: "IN_SYNC"
      },
      {
        startTime: "1571653320049",
        magnitude: 10,
        startupMagnitude: -0,
        startupRunningStatus: "OUT_OF_SYNC"
      },
      {
        startTime: "1572517320049",
        magnitude: 0,
        startupMagnitude: -10,
        startupRunningStatus: "OUT_OF_SYNC"
      },
      {
        startTime: "1573467720049",
        magnitude: 0,
        startupMagnitude: -10,
        startupRunningStatus: "OUT_OF_SYNC"
      },
      {
        startTime: "1574504520049",
        magnitude: 10,
        startupMagnitude: -10,
        startupRunningStatus: "OUT_OF_SYNC"
      },
      {
        startTime: "1575627720049",
        magnitude: 10,
        startupMagnitude: -10,
        startupRunningStatus: "OUT_OF_SYNC"
      }
    ];
    
      
  var drawLineGraph = function(containerHeight, containerWidth, data, yLabel, warnLine) {
    // A function that updates the chart when the user zoom and thus new boundaries are available
    var newX = '' 
    const updateChart = () => {
      d3.select("#focusCircle").style('display', 'none')
      // recover the new scale
      newX = d3.event.transform.rescaleX(xScale);
      var newY = yScale;


      // update axes with these new boundaries
      d3.select("#axisX").call(d3.axisBottom(newX));
      //d3.select('#axisY').call(d3.axisLeft(newY));

      // update circle position
      scatter
        .selectAll("circle")
        .attr("cx", function(d) {
          if (d) {
            return newX(d.startTime);
          }
        })
        .attr("cy", function(d) {
          if (d) {
            return newY(d.magnitude);
          }
        });

      var line2 = d3
        .line()
        .x(function(d) {
          return newX(d.startTime);
        })
        .y(function(d) {
          return newY(d.startupMagnitude);
        });

      var area = d3
        .area()
        .x(function(d) {
          return newX(d.startTime);
        })
        .y0(function(d) {
          return yScale(d.startupMagnitude);
        })
        .y1(height);

      var line = d3
        .line()
        .x(function(d) {
          return newX(d.startTime);
        })
        .y(function(d) {
          return newY(d.magnitude);
        });

        svg.on("mousemove", function() {
        d3.select("#focusCircle").style('display', 'block')
          var mouse = d3.mouse(this);
          var mouseDate = newX.invert(mouse[0]);
          var i = bisectDate(data, mouseDate); // returns the index to the current data item
  
          var d0 = data[i - 1];
          var d1 = data[i];
          let d;
          // work out which date value is closest to the mouse
          if (typeof d1 !== "undefined") {
            d = mouseDate - d0.startTime > d1.startTime - mouseDate ? d1 : d0;
          } else {
            d = d0;
          }
  
          div
            .html(
              `<span>${parseDate(d.startTime)}</span>
          <span>Magnitude: ${d.magnitude} </span>`
            )
            .style("left", d3.event.pageX + "px")
            .style("top", d3.event.pageY - 28 + "px");
          var x = newX(d.startTime);
          var y = yScale(d.magnitude);
  
          focus
            .select("#focusCircle")
            .attr("cx", x)
            .attr("cy", y);
          focus
            .select("#focusLineX")
            .attr("x1", x)
            .attr("y1", yScale(yDomain[0]))
            .attr("x2", x)
            .attr("y2", yScale(yDomain[1]));
          focus
            .select("#focusLineY")
            .attr("x1", xScale(xDomain[0]))
            .attr("y1", y)
            .attr("x2", xScale(xDomain[1]))
            .attr("y2", y);
        });

      scatter.select("#line2").attr("d", line2);
      scatter.select(".line").attr("d", line);
      scatter.select("#area").attr("d", area);
    };

    var zoom = d3
      .zoom()
      .scaleExtent([0.5, 20]) // This control how much you can unzoom (x0.5) and zoom (x20)
      .extent([[0, 0], [containerWidth, containerHeight]])
      .on("zoom", updateChart);

    var svg = d3
      .select('#chart')
      .append("svg")
      .attr("width", containerWidth)
      .attr("height", containerHeight);

    var clip = svg
      .append("defs")
      .append("SVG:clipPath")
      .attr("id", "clip")
      .append("SVG:rect")
      .attr("width", containerWidth)
      .attr("height", containerHeight)
      .attr("x", 50)
      .attr("y", 0);

    // Create the scatter variable: where both the circles and the brush take place
    var scatter = svg.append("g").attr("clip-path", "url(#clip)");

    var margin = { top: 50, left: 50, right: 50, bottom: 80 };

    var height = containerHeight - margin.top - margin.bottom;
    var width = containerWidth - margin.left - margin.right;

    var xDomain = d3.extent(data, function(d) {
      return d.startTime;
    });
    var yDomain = d3.extent(data, function(d) {
      return d.magnitude;
    });

    var xScale = d3
      .scaleTime()
      .range([0, width])
      .domain(xDomain);
    var yScale = d3
      .scaleLinear()
      .range([height, 0])
      .domain(yDomain);

    var xAxis = d3.axisBottom(xScale);
    var yAxis = d3.axisLeft(yScale);

    var line = d3
      .line()
      .x(function(d) {
        return xScale(d.startTime);
      })
      .y(function(d) {
        return yScale(d.magnitude);
      });

    var line2 = d3
      .line()
      .x(function(d) {
        return xScale(d.startTime);
      })
      .y(function(d) {
        return yScale(d.startupMagnitude);
      });

    var area = d3
      .area()
      .x(function(d) {
        return xScale(d.startTime);
      })
      // .x0(function(d) {
      //   return xScale(d.startTime);
      // })
      // .x1(function(d) {
      //   return xScale(d.magnitude);
      // })
      .y0(function(d) {
        return yScale(d.startupMagnitude);
      })
      .y1(height);
    // .y0(height)
    // .y1(function(d) { return yScale(d.magnitude); });

    // var area = d3
    //   .area()
    //   .x0(function(d) {
    //     return xScale(d.startTime);
    //   })
    //   .x1(function(d) {
    //     return xScale(d.startTime);
    //   })
    //   .y0(function(d) {
    //     return yScale(d.magnitude);
    //   })
    //   .y1(function(d) {
    //     return yScale(0);
    //   });

    // var area = d3
    //   .area()
    //   .x(function(d) {
    //     return xScale(d.startTime);
    //   })
    //   .y0(function(d) {
    //     return yScale(d.magnitude);
    //   })
    //   .y1(yScale(0));

    // Define the div for the tooltip
    var div = d3
      .select("body")
      .append("div")
      .attr("class", "tooltip")
      .style("opacity", 0);

    var g = scatter
      .append("g")
      .attr("transform", "translate(" + margin.left + ", " + margin.top + ")");
      
    var g2 = svg
      .append("g")
      .attr("transform", "translate(" + margin.left + ", " + margin.top + ")");

    g.append("path")
      .datum(data)
      .attr("class", "area")
      .attr("id", "area")
      .attr("d", area);

    g.append("g")
      .attr("class", "x axis")
      .attr("id", "axisX")
      .attr("transform", "translate(0, " + height + ")")
      .call(xAxis);

    g2.append("g")
      .attr("class", "y axis")
      .attr("id", "axisY")
      .call(yAxis)
      .append("text")
      .attr("transform", "rotate(-90)")
      .attr("y", 6)
      .attr("dy", ".71em")
      .attr("text-anchor", "end")
      .text(yLabel);

    g.append("path")
      .datum(data)
      .attr("class", "line")

      .attr("d", line);

    g.append("path")
      .datum(data)
      .attr("class", "line2")
      .attr("id", "line2")
      .attr("d", line2);

    g.selectAll("circle")
      .data(data)
      .enter()
      .append("circle")
      .attr("cx", function(d) {
        return xScale(d.startTime);
      })
      .attr("cy", function(d) {
        return yScale(d.magnitude);
      })
      .attr("r", function(d) {
        if (d.startupRunningStatus === "OUT_OF_SYNC") {
          return 5;
        }
      })
      .attr("class", "circle");

    //legend code
    svg
      .append("circle")
      .attr("cx", 40)
      .attr("cy", 380)
      .attr("r", 6)
      .style("fill", "#1391d8");
    svg
      .append("circle")
      .attr("cx", 40)
      .attr("cy", 400)
      .attr("r", 6)
      .style("fill", "red");
    svg
      .append("text")
      .attr("x", 60)
      .attr("y", 380)
      .text("Startup Config")
      .style("font-size", "15px")
      .attr("alignment-baseline", "middle");
    svg
      .append("text")
      .attr("x", 60)
      .attr("y", 400)
      .text("Running Config")
      .style("font-size", "15px")
      .attr("alignment-baseline", "middle");

    // focus tracking
    var focus = g.append("g").style("display", "none");

    focus
      .append("line")
      .attr("id", "focusLineX")
      .attr("class", "focusLine");
    focus
      .append("line")
      .attr("id", "focusLineY")
      .attr("class", "focusLine");
    focus
      .append("circle")
      .attr("id", "focusCircle")
      .attr("r", 5)
      .attr("class", "circle focusCircle");

    //grid line
    const make_x_axis = () => {
      return d3.axisBottom(xScale);
    };

    scatter
      .append("g")
      .attr("class", "grid")
      .attr("transform", "translate(50," + (height + 50) + ")")
      .call(
        make_x_axis()
          .tickSize(-height, 0, 0)
          .tickFormat("")
      );

    var bisectDate = d3.bisector(function(d) {
      return d.startTime;
    }).left;
    var parseDate = d3.timeFormat("%Y-%m-%d %H:%M:%S");
    g.selectAll("dot")
      .data(data)
      .enter()
      .append("rect")
      .attr("class", "overlay")
      .attr("width", width)
      .attr("height", height)

      
      svg.on("mouseover", function(d) {
        console.log('ok')
        focus.style("display", null);
        div
          .transition()
          .duration(200)
          .style("opacity", 0.9);
      })
      .on("mouseout", function() {
        focus.style("display", "none");
        div
          .transition()
          .duration(300)
          .style("opacity", 0);
      })

      .on("mousemove", function() {
        var mouse = d3.mouse(this);
        var mouseDate = xScale.invert(mouse[0]);
        var i = bisectDate(data, mouseDate); // returns the index to the current data item

        var d0 = data[i - 1];
        var d1 = data[i];
        let d;
        // work out which date value is closest to the mouse
        if (typeof d1 !== "undefined") {
          d = mouseDate - d0.startTime > d1.startTime - mouseDate ? d1 : d0;
        } else {
          d = d0;
        }

        div
          .html(
            `<span>${parseDate(d.startTime)}</span>
        <span>Magnitude: ${d.magnitude} </span>`
          )
          .style("left", d3.event.pageX + "px")
          .style("top", d3.event.pageY - 28 + "px");
        var x = xScale(d.startTime);
        var y = yScale(d.magnitude);

        focus
          .select("#focusCircle")
          .attr("cx", x)
          .attr("cy", y);
        focus
          .select("#focusLineX")
          .attr("x1", x)
          .attr("y1", yScale(yDomain[0]))
          .attr("x2", x)
          .attr("y2", yScale(yDomain[1]));
        focus
          .select("#focusLineY")
          .attr("x1", xScale(xDomain[0]))
          .attr("y1", y)
          .attr("x2", xScale(xDomain[1]))
          .attr("y2", y);
      });

    svg
      .append("rect")
      .attr("width", containerWidth)
      .attr("height", containerHeight)
      .style("fill", "none")

      .style("pointer-events", "all")

      .call(zoom);

    // warn line

    // if (
    //   warnLine &&
    //   yDomain[0] < warnLine.lineValue &&
    //   yDomain[1] > warnLine.lineValue
    // ) {
    //   g.append("line")
    //     .attr("x1", xScale(xDomain[0]))
    //     .attr("y1", yScale(warnLine.lineValue))
    //     .attr("x2", xScale(xDomain[1]))
    //     .attr("y2", yScale(warnLine.lineValue))
    //     .attr("class", "zeroline");
    //   g.append("text")
    //     .attr("x", xScale(xDomain[1]))
    //     .attr("y", yScale(warnLine.lineValue))
    //     .attr("dy", "1em")
    //     .attr("text-anchor", "end")
    //     .text(warnLine.label)
    //     .attr("class", "zerolinetext");
    // }
  }
  
    drawLineGraph(410, 700, data, "Magnitude", {
      lineValue: 0,
      label: "Startup Config!"
    });
.axis path,
.axis line {
  fill: none;
  stroke: #e0e0e0;
  shape-rendering: crispEdges;
}

.x.axis path {
  display: none;
}

.line {
  fill: none;
  stroke: red;
  stroke-width: 2px;
}

.line2 {
  fill: none;
  stroke: #2e59cf;
  stroke-width: 0.3px;
}

.circle {
  /* fill: white;
  stroke: steelblue;
  stroke-width: 2px; */
  fill: steelblue;
  stroke: steelblue;
  /* stroke-width: 2px; */
}

.area {
  fill: #2e59cf;
  stroke: none;
  opacity: 0.1;
}

.zeroline {
  fill: none;
  stroke: #1391d8;
  stroke-width: 1px;
  stroke-dasharray: 8 8;
}

.zerolinetext {
  fill: #1391d8;
}

.overlay {
  fill: none;
  stroke: none;
  pointer-events: all;
}

.focusLine {
  fill: none;
  stroke: steelblue;
  stroke-width: 0.5px;
}

.focusCircle {
  fill: red;
}

div.tooltip {
  position: absolute;
  text-align: center;
  width: 150px;
  height: 38px;
  padding: 2px;
  font: 12px sans-serif;
  background: lightsteelblue;
  border: 0px;
  border-radius: 8px;
  pointer-events: none;
}

.grid .tick {
  stroke: lightgrey;
  stroke-width: 0.7px;
  stroke-dasharray: 8 8;
  opacity: 0.3;
}
.grid path {
  stroke-width: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div className="App">
  <h1>Chart</h1>
  </div>
 <div id="chart"></div>

关于javascript - 放大和缩小功能在 D3 图表中未按预期工作并使用react,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57849225/

相关文章:

javascript - 可缩放圆形包装 - 父级未定义

javascript - Canvas getImageData().data 返回半张图像

javascript - Bootbox HTML 渲染

javascript - 用 jQuery/JS 重置计数器?

javascript - 类型错误 : Cannot read property 'actualPersonAlbum' of undefined

arrays - 在 API 调用后更改元素的状态以显示组件数组

javascript - 在特定条件下手动调用事件处理程序时 IE 出错

reactjs - 如何禁用来自 React Native API 的某些特定项目的可触摸不透明度

d3.js - D3 拖动错误 'Cannot read property x of undefined'

javascript - d3js 文本未显示在节点图上