如何正确缩放和平移 D3 v5 y 轴中的 y 轴

How to proper zoom and pan only the y-axis in D3 v5 y-axis

我正在使用 d3 v6 开发可视化,它基本上使用一组条目来显示水平层。 可视化效果如下所示:

需要注意的是,随着层数的增加,由于比例减小,一些层变得无法看到。所以我想做一个缩放(和平移)功能,但它只能在 Y 轴上工作。我进行了很多搜索并找到了本教程 (https://observablehq.com/@d3/x-y-zoom),我从中进行了一些更改以适应我的可视化效果:

      const svgHeight: any = d3.select(svgContainer.current).attr('height');
      const yScaleGlobal = d3
        .scaleLinear()
        .domain([0, maxYValues])
        .range([0, svgHeight - MARGINS.TOP - MARGINS.BOTTOM]);

      const yAxis = d3.axisLeft(yScaleGlobal).tickFormat((d: any) => `${d} m`);

      const gY = layerGroup.append('g').attr('class', 'y-axis');

      // z holds a copy of the previous transform, so we can track its changes
      let z = d3.zoomIdentity;

      // set up the ancillary zooms and an accessor for their transforms
      const zoomY = d3.zoom().scaleExtent([0.2, 5]);
      const ty = () => d3.zoomTransform(gY.node());

      gY.call(zoomY).attr('pointer-events', 'none');

      drawViz();

      const zoom = d3.zoom().on('zoom', function (e) {
        const t = e.transform;
        const k = t.k / z.k;
        const point = center(e, this);

        if (k === 1) {
          // pure translation?
          gY.call(zoomY.translateBy, 0, (t.y - z.y) / ty().k);
        } else {
          // if not, we're zooming on a fixed point
          gY.call(zoomY.scaleBy, k, point);
        }

        z = t;

        drawViz();
      });

      svg.call(zoom).call(zoom.transform, d3.zoomIdentity.scale(0.8)).node();

      function drawViz() {
        const yr = ty().rescaleY(yScaleGlobal);
        yAxis.scale(yr);
        gY.call(yAxis, yr);
        if (data) updateLayers(data, yr);
      }

      function center(event, target) {
        if (event.sourceEvent) {
          const p = d3.pointers(event, target);
          return [d3.mean(p, (d) => d[0]), d3.mean(p, (d) => d[1])];
        }
        return [svgWidth / 2, svgHeight / 2];
      }

updateLayers函数是这样的:

     const updateLayers = (data: LAYER_COMPONENT_TYPE[], yScale) => {
        const layerFill = getLayerFill(data);

        const rects = layerGroup.selectAll('rect').data(data);

        rects.exit().remove();

        const newLayers = rects
          .enter()
          .append('rect')
          .attr('x', 10)
          .style('opacity', 0.5)
          .attr('width', 300)
          .style('stroke', '#101010')
          .style('stroke-width', '1px');

        newLayers
          .merge(rects)
          .style('fill', (d: LAYER_COMPONENT_TYPE) => {
            if (!layerFill[`${d.fgdc_texture}.${d.from}`].url) {
              return layerFill[`${d.fgdc_texture}.${d.from}`];
            }
            svg.call(layerFill[`${d.fgdc_texture}.${d.from}`]);
            return layerFill[`${d.fgdc_texture}.${d.from}`].url();
          })
          .attr('y', (d: LAYER_COMPONENT_TYPE, i) => {
            if (i === 0) return yScale(d.from);
            return yScale(data[i - 1].to);
          })
          .attr('height', (d: LAYER_COMPONENT_TYPE, i) => {
            return yScale(d.to - d.from);
          });
      };

缩放和平移以及整个可视化效果都很好,但是 yScale 有一个奇怪的行为。矩形的高度在缩放或平移时超出比例(我已经意识到高度的异常等于 yScale 的“0 m”点的距离) here you can view the problem

无论如何,我可以弄清楚代码有什么问题或导致异常的原因。所以我需要一些帮助来解决这个问题

编辑 1: 我正在使用反应 btw

我已经想出了一个解决方案(采用更好更简洁的方法)。此解决方案基于 TBD 的 d3 timeline implementation。使用他的解决方案,我刚刚适应了 y 轴和我的代码

    const maxYValues = d3.max(maxValues) || 0;

    const yScaleGlobal = d3
      .scaleLinear()
      .domain([0, maxYValues])
      .range([0, svgHeight - MARGINS.TOP - MARGINS.BOTTOM]);

    const yAxis = d3.axisLeft(yScaleGlobal).tickFormat((d: any) => `${d} m`);

    const gY = litoligicalGroup.select(`.${styles.yAxis}`).call(yAxis);

    const spanY = (d) => {
      if (d.thickness) return yScaleGlobal(0) - 10;
      return yScaleGlobal(d.from);
    };

    const spanH = (d) => {
      if (d.thickness) return 10;
      return yScaleGlobal(d.to - d.from);
    };

    const zoom = d3
      .zoom()
      .scaleExtent([0.2, 5])
      .on('zoom', (e) => {
        const transform = e.transform;

        gY.call(yAxis.scale(transform.rescaleY(yScaleGlobal)));
        // the viz group is where all the layers are attached
        vizGroup
          .selectAll('rect')
          .attr('y', (d) => {
            if (!d) return null;
            return transform.applyY(spanY(d));
          })
          .attr('height', (d) => {
            if (!d) return null;
            return transform.k * spanH(d);
          });
      });

    drawProfile();

    svg.call(zoom);

    function drawProfile() {
      if (geologicData) updateGeology(geologicData, yScaleGlobal);
      if (constructionData) updatePoco(constructionData, yScaleGlobal);
    }