Jebzaki
Jebzaki

Reputation: 163

dX and dY values undefined when upgrading from d3.js v3 to v4

I've been trying to move a zoomable treemap from v3 to v4 but the last piece I need I'm unable to get the dx and dy values to draw my rectangles correctly. In my layout function the child dx and dy are undefined which just leads to another undefined. So when it finally reaches rect it's no use to me there.

Where have I gone wrong?

With a blockbuilder link as suggested.

http://blockbuilder.org/jebzaki/b2252c00d51ffaca0c7408b394b168a5

    var margin = {top: 25, right: 0, bottom: 0, left: 0},
    width = 960,
    height = 640 - margin.top - margin.bottom,
    formatNumber = d3.format(",d"),
    transitioning;

    var x = d3.scaleLinear()
        .domain([0, width])
        .range([0, width]);

    var y = d3.scaleLinear()
        .domain([0, height])
        .range([0, height]);

    var color = d3.scaleThreshold()
    .domain([-5,-0,0,5])
    .range(["#BB0000","#600A0A","#404040","#064D15","#1CA41C"]);

    var svg = d3.select("#heatmap").append("svg")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.bottom + margin.top)
        .style("margin-left", -margin.left + "px")
        .style("margin.right", -margin.right + "px")
      .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
        .style("shape-rendering", "crispEdges");

    var treemap = d3.treemap()
        .tile(d3.treemapSquarify.ratio(height / width * 0.5 * (1 + Math.sqrt(5))))
    .size([width, height])
    .round(false)
    .paddingInner(1);

    var grandparent = svg.append("g")
        .attr("class", "grandparent");

    grandparent.append("rect")
        .attr("y", -margin.top)
        .attr("width", width)
        .attr("height", margin.top);

    grandparent.append("text")
        .attr("x", 6)
        .attr("y", 6 - margin.top)
        .attr("dy", ".75em");

d3.json("new-data.json", function(error, data) {
  if (error) throw error;

  var root = d3.hierarchy(data)
      .eachBefore(function(d) { d.id = (d.parent ? d.parent.id + "." : "") + d.data.name; })
      .sum((d) => {d.value})
      .sort(function(a, b) { return b.height - a.height || b.value - a.value; });

      initialize(root);
      accumulate(root);
      layout(root);
      treemap(root);
      display(root);

  function initialize(root) {
    root.x = root.y = 0;
    root.dx = width;
    root.dy = height;
    root.depth = 0;
  }

  function accumulate(d) {
    return (d._children = d.children)
        ? d.value = d.children.reduce(function(p, v) { return p + accumulate(v); }, 0)
        : d.value;
  }

    function layout(d) {
      if (d._children) {
        d._children.forEach(function(c) {
          c.x = d.x + c.x * d.dx;
          c.y = d.y + c.y * d.dy;
          c.dx *= d.dx;
          c.dy *= d.dy;
          c.parent = d;
          layout(c);
        });
      }
    }

  function display(d) {
    grandparent
      .datum(d.parent)
        .on("click", transition)
      .select("text")
        .text(name(d));

    grandparent
      .datum(d.parent)
      .select("rect")
      .attr("fill", function(){ return color(d.data.change)})

    var g1 = svg.insert("g", ".grandparent")
      .datum(d)
      .attr("class", "depth");

    var g = g1.selectAll("g")
      .data(d._children)
      .enter().append("g");

    g.filter(function(d) { return d._children; })
      .classed("children", true)
      .on("click", transition);

    g.selectAll(".child")
      .data(function(d) { return d._children || [d]; })
      .enter().append("rect")
      .attr("class", "child")
      .call(rect);

    d3.select("#heatmap").select("#tooltip").remove();
    var div = d3.select("#heatmap").append("div")
      .attr("id", "tooltip")
      .style("opacity", 0);


    g.append("svg:a")
      .attr("xlink:href", function(d) {
          if(!d._children){
              var url = "#";
              return url; 
          }
      })
      .append("rect")
        .attr("class", "parent")
        .call(rect)
        .on("mouseover", function(d) {
               if (d.data.name !== "Results") {
                   d3.select("#tooltip").transition()
                      .duration(200)
                      .style("opacity", 1);
                   d3.select("#tooltip").html("<h3>"+d.data.name+"</h3><table>"+
                            "<tr><td>"+d.data.value+"</td><td> ("+d.data.change+"%)</td></tr>"+
                            "</table>")
                      .style("left", (d3.event.pageX-document.getElementById("heatmap").offsetLeft + 20) + "px")
                      .style("top", (d3.event.pageY-document.getElementById("heatmap").offsetTop - 60) + "px");
               }
        })
        .on("mouseout", function(d) {
          d3.select("#tooltip").transition()
            .duration(500)
            .style("opacity", 0);
        })

    g.append("text")
      .attr("dy", ".75em")
      .text(function(d) { return d.data.name; })
      .call(text);

    function transition(d) {
      if (transitioning || !d) return;
      transitioning = true;

      var g2 = display(d),
        t1 = g1.transition().duration(750),
        t2 = g2.transition().duration(750);

      x.domain([d.x0, d.x0 + d.x1]);
      y.domain([d.y0, d.y0 + d.y1]);

      svg.style("shape-rendering", null);

      svg.selectAll(".depth").sort(function(a, b) { return a.depth - b.depth; });

      g2.selectAll("text").style("fill-opacity", 0);

      t1.selectAll("text").call(text).style("fill-opacity", 0);
      t2.selectAll("text").call(text).style("fill-opacity", 1);
      t1.selectAll("rect").call(rect);
      t2.selectAll("rect").call(rect);

      t1.remove().each("end", function() {
        svg.style("shape-rendering", "crispEdges");
        transitioning = false;
      });
    }

    return g;
  }

  function text(text) {
    text.attr("x", function(d) { return x(d.x0) + (x(d.x0 + d.dx) - x(d.x0))/2; })
      .attr("y", function(d) { return y(d.y0) + (y(d.y0 + d.dy) - y(d.y0))/2; })
      .attr("dy", 0)
      .attr("font-size", function(d) { var w=x(d.x0 + d.dx) - x(d.x0),
                                           h=y(d.y0 + d.dy) - y(d.y0),
                                           t=(d.data.name).length/1.3;
                                       var tf=Math.min(Math.floor(w/t),h/3);
                                       return (tf>=5)?Math.min(tf, 30):0; })
      .attr("fill", "white")
      .attr("text-anchor", "middle");
  }

  function rect(rect) {
    rect.attr("x", function(d) { return x(d.x0); })
      .attr("y", function(d) { return y(d.y0); })
      .attr("width", function(d) { return x(d.x0 + d.dx) - x(d.x0); })
      .attr("height", function(d) { return y(d.y0 + d.dy) - y(d.y0); })
      .attr("fill", function(d){return color(parseFloat(d.data.change));});
  }

  function name(d) {
    return d.parent
      ? "Sector : "+d.data.name+" (Back to Overall Market)"
      : "Overall "+d.data.name;
  }
});

Upvotes: 2

Views: 962

Answers (1)

REEE
REEE

Reputation: 517

I would have posted this as a comment as I'm unsure of whether this is an answer to what you're actually asking (the 'move a zoomable treemap' confused me, not sure if this is a typo though?), but I lack the rep to do so.

Here's a fiddle: http://jsfiddle.net/b238u0hy/5/

I noticed that you must have been following this bl.ock: https://bl.ocks.org/me1er/c64479f1ac8a5f993027f40a36c35dd9.

I then compared your code to the original and the reason why you weren't able to create the rectangles was because you were setting 'dx', 'dy' instead of using the 'x1' and 'y1' values generated by d3.hierarchy.

Example dy/dx problem here:

c.x = d.x + c.x * d.dx;
c.y = d.y + c.y * d.dy;
c.dx *= d.dx;
c.dy *= d.dy;
c.parent = d;
layout(c);

I found the original code slightly more readable so I changed the original code to fit yours (although, I was lazy with a few things since I'm at work, sorry):

  • I commented out your color func as it wasn't working.
  • I loaded the data as a variable and commented out d3.json().
  • I didn't bother removing the shortName, text2 stuff from the original code I just changed the value names so it would run.

Edit: I forgot to add the tooltip back, here's a fiddle with the tooltip: http://jsfiddle.net/74t8b9sp/ (some minor changes, I changed it to on 'mousemove' so the tooltip follows your mouse, rounded the value for display purposes, and removed the internal rect and <a> you added originally).

Upvotes: 1

Related Questions