d3js多线散点图缩放

d3js multi-line scatterplot zoom

我正在使用 d3 v6 绘制带缩放的多线散点图。我是 d3 的新手,根据不同的示例,我可以使 images/points 的缩放功能正常工作。问题是线条没有缩放。我查看了许多类似的问题,但是 none 这些解决方案对我有用。

我使用的代码:

    var margin = {
      top: 50,
      right: 30,
      bottom: 30,
      left: 210,
    };
    var svg = d3.select("svg"),
      width = 1410 - margin.left - margin.right,
      height = 620 - margin.top - margin.bottom;

    svg
      .append("defs")
      .append("clipPath")
      .attr("id", "clip")
      .append("rect")
      .attr("width", width)
      .attr("height", height);

    d3.csv("CSV_files/NSW_pathway.csv").then(function (data1) {
      var groupData = d3.group(data1, (d) => d.pathway_name);

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

      var xAxis = d3.axisBottom(xScale).ticks(0).tickSize(-height);
      var yAxis = d3.axisLeft(yScale).ticks(0).tickSize(-width);

      var gX = svg
        .append("g")
        .attr(
          "transform",
          "translate(" + margin.left + "," + (margin.top + height) + ")"
        )
        .call(xAxis);

      var gY = svg
        .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
        .call(yAxis);

      var focus = svg
        .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
        .attr("class", "line")
        .attr("clip-path", "url(#clip)");

      const color = d3
        .scaleOrdinal()
        .range(["#e41a1c", "#377eb8", "#4daf4a", "#984ea3"]);

      var points_g = svg
        .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
        .attr("clip-path", "url(#clip)")
        .classed("points_g", true);

      var label = svg
        .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
        .attr("class", "label")
        .attr("clip-path", "url(#clip)");

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

      const mouseover = function (event, d) {
        div.style("opacity", 1);
      };

      const mousemove = function (event, d) {
        div
          .html(function (d1) {
            if (d.type != "learner")
              return `The resource name is ${d.resource_name}`;
            else return `This is ${d.name}`;
          })
          .style("position", "absolute")
          .style("left", event.pageX + 15 + "px")
          .style("top", event.pageY + 15 + "px");
      };

      const mouseleave = function (event, d) {
        div.transition().duration(200).style("opacity", 0);
      };

      var points = points_g.selectAll("point").data(data1);

      points = points
        .enter()
        .append("image")
        .attr("xlink:href", function (d) {
          if (d.type == "video") return "Images/3.jpg";
          else if (d.type == "pdf") return "Images/4.png";
          else if (d.type == "none") return "Images/5.png";
        })
        .attr("x", function (d) {
          return xScale(+d.x) - 10;
        })
        .attr("y", function (d) {
          return yScale(+d.y) - 10;
        })
        .attr("width", 20)
        .attr("height", 20)
        .on("mouseover", mouseover)
        .on("mousemove", mousemove)
        .on("mouseleave", mouseleave);

      label
        .selectAll(".text")
        .data(data1)
        .enter()
        .append("text")
        .text(function (d) {
          return d.topic;
        })
        .attr("x", function (d) {
          return xScale(+d.x) + 10;
        })
        .attr("y", function (d) {
          return yScale(+d.y) + 10;
        });

      focus
        .selectAll("line")
        .data(groupData)
        .enter()
        .append("path")
        .attr("fill", "none")
        .attr("stroke", function (d) {
          return color(d[0]);
        })
        .attr("stroke-width", 1)
        .attr("d", function (d) {
          return d3
            .line()
            .curve(d3.curveMonotoneX)
            .x(function (d) {
              return xScale(+d.x);
            })
            .y(function (d) {
              return yScale(+d.y);
            })(d[1]);
        });

      var zoom = d3
        .zoom()
        .scaleExtent([0.5, 20])
        .extent([
          [0, 0],
          [width, height],
        ])
        .on("zoom", zoomed);

      svg
        .append("rect")
        .attr("width", width)
        .attr("height", height)
        .style("fill", "none")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
        .lower();

      svg.call(zoom).call(zoom.transform, d3.zoomIdentity);

      function zoomed({ transform }) {
        var new_xScale = transform.rescaleX(xScale);
        var new_yScale = transform.rescaleY(yScale);

        gX.call(xAxis.scale(new_xScale));
        gY.call(yAxis.scale(new_yScale));

        points
          .data(data1)
          .attr("x", function (d) {
            return new_xScale(d.x) - 10;
          })
          .attr("y", function (d) {
            return new_yScale(d.y) - 10;
          });

        label
          .selectAll("text")
          .data(data1)
          .attr("x", function (d) {
            return new_xScale(d.x) + 15;
          })
          .attr("y", function (d) {
            return new_yScale(d.y) + 15;
          });

        focus.selectAll("line").attr("d", function (d) {
          return d3
            .line()
            .curve(d3.curveMonotoneX)
            .x(function (d) {
              return xScale(+d.x);
            })
            .y(function (d) {
              return yScale(+d.y);
            })(d[1]);
        });
      }
    });

csv 文件样本:

x,y,name,type,topic,resource_name,pathway_name
0,0,start,none,Sponsored Search Markets,Networks Crowd and Markets_NCMch15.pdf,pathwayOne
0,0,start,none,Sponsored Search Markets,Networks Crowd and Markets_NCMch15.pdf,pathwayTwo
0.086511627906977,0.16,horse,pdf,Graphs,Networks Crowd and Markets_NCMch2.pdf,pathwayOne
0.12,0.283768436578171,choice,pdf,Network Centrality,Notes_CGT BASED network CENTRALITY - L2.pdf,pathwayTwo
0.32,0.27217943628424,plex,video,Network Models,Network Analysis_LNch13.pdf,pathwayOne
0.775398773006135,0.33,social,pdf,Clustering,Network Analysis_LNch8.pdf,pathwayTwo
1,1,end,none,Allocation in Networks,Notes_Allocation in networks with DON-L3.pdf,pathwayOne
1,1,end,none,Allocation in Networks,Notes_Allocation in networks with DON-L3.pdf,pathwayTwo

感谢您的帮助。

我没有你的 csv 文件的样本,所以这没有经过测试,但如果你想缩放整个图表,只需在你的 svg 和 transform 之后添加父级 g那..

...
svg
  .append("defs")
  .append("clipPath")
  .attr("id", "clip")
  .append("rect")
  .attr("width", width)
  .attr("height", height);
  
   // NEW - add g 
   .append('g')

// NEW - adjust scaleExtent to your needs
const zoom = d3.zoom()
      .scaleExtent([1, 8])
      .on('zoom', updateChart)
svg.call(zoom)

function updateChart(event) {
    svg.attr('transform', event.transform)
}

请注意,这也会添加平移,但如果您只想缩放,则可以使用:

let scale = 1 
...
function updateChart(event) {
    if(event.transform.k === scale) { return }
    svg.attr('transform', event.transform)
    scale = event.transform.k
}

它不是在缩放整个页面,而是在缩放整个 svg,您的大边距超出了制图区域。一种解决方案是不在您的 svg 上添加 g 元素,而只在您的图表区域添加。

但是使用您的代码,有两件事阻止了您的线条缩放。

1:您的选择是空的 - 行是一个 d3 抽象,returns 一个路径

function zoomed() {
   ...
   // empty selection
   console.log(focus.selectAll('line')) 
   // try instead 
   console.log(focus.selectAll('path'))
}

2:简单的错误 - 您使用的是旧秤而不是新秤

function zoomed() {
    ... 
    focus.selectAll('path').attr('d', d => {
        return d3.line()
            // using old scale
            .x(di => xScale(+di.x))
            // change to 
            .x(di => new_xScale(+di.x))
    })
}