Home > Blockchain >  d3js multi-line scatterplot zoom
d3js multi-line scatterplot zoom

Time:11-29

I am working on a multi-line scatterplot with zoom using d3 v6. I am new to d3 and based on different examples, I could get the zoom function working for the images/points. The problem is that the lines aren't zooming. I looked at many similar questions, but none of those solutions are working for me.

The code I am using:

    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]);
        });
      }
    });

A sample of the csv file:

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

Thank you for your help.

CodePudding user response:

It's not zooming the whole page, it's zooming the whole svg, your large margins extend beyond the charting area. One solution is to add the g element not on your svg but only on your chart area.

But using your code, there are 2 things preventing your lines from zooming.

1: your selection is empty - line is a d3 abstraction that returns a path

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

2: Simple mistake - you're using the old scale not the new one

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))
    })
}

CodePudding user response:

I don't have a sample of your csv file so this isn't tested, but if you want to zoom the whole chart just add a parent g after your svg and transform that..

...
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)
}

Note that this also adds pan, but if you only want zoom you can use:

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