简体   繁体   中英

D3 zoomable sunburst not zooming (with data from R)

I'm trying to adapt some code so I can plot data in a sunburst (using D3) chart with data coming from R as a dataframe.

I've got most of it working ok except the zooming part, when i click (almost) nothing happens.

On mouseOver I've got a portion of circle with a greater radius than the one for the sunburst to help emphasizing the data I'm hovering. This explains the arcHighlight variable. Also one can notice it is not constructed using my scales a and b . I modifed the original code to use the scales, and I left the arcHighlight as is for now.

After a bit of digging around, this question seems to indicate that I need to delete the partition.size since it will be taken care of by my scales a and b . I did try that but it does not plot anything if I comment the size part like this :

  var partition = d3.layout.partition()
      //.size([2 * Math.PI, (radius - outerRadius) * (radius - outerRadius)])
      .value(function(d) { return d[x.options.valueField || "size"]; });

At the end of the click function, I'm making my highlight circle invisible. That works correctly, but do not do the zooming. As soon as I move my mouse elsewhere, my highlight circle reappear (normal, color coded in the mouseover function). So it all works correctly without crashing or freezing, but just the zooming part. I must be missing something easy but can't figure out what.

Thanks for all of you guys who might be able to help out. Please also note that I'm pretty new to JS so it might just be a very simple mistake, my apologies if so.

My code (what I think is relevant... ) :

  // Dimensions of sunburst
  var width = el.getBoundingClientRect().width - (x.options.legend.w ? x.options.legend.w : 75);
  var height = el.getBoundingClientRect().height - 70;
  var radius = Math.min(width, height) / 2;
  var outerRadius = radius/3.5; // reserved pixels all around the vis

  // Create scales
  var a = d3.scale.linear()
        .range([0, 2 * Math.PI]);

  var b = d3.scale.linear()
        .range([0, (radius - outerRadius)]);

  var vis = d3.select(el).select(".sunburst-chart").select("svg")
      .append("g")
      .attr("id", el.id + "-container")
      .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");

  var partition = d3.layout.partition()
      .size([2 * Math.PI, (radius - outerRadius) * (radius - outerRadius)])
      .value(function(d) { return d[x.options.valueField || "size"]; });

  var arc = d3.svg.arc()
      .startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, a(d.x/(2 * Math.PI)))); })
      .endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, a((d.x+d.dx)/(2 * Math.PI)))); })
      .innerRadius(function(d) { return (d.dy == d.y) ? Math.max(0, b((Math.sqrt(d.y))/(radius - outerRadius))/3) : Math.max(0, b((Math.sqrt(d.y))/(radius - outerRadius))); })
      .outerRadius(function(d) { return Math.max(0, b((Math.sqrt(d.y + d.dy))/(radius - outerRadius))); });

  var arcHighlight = d3.svg.arc()
      .startAngle(function(d) { return d.x; })
      .endAngle(function(d) { return d.x + d.dx; })
      .innerRadius(function(d) { return 0; })
      .outerRadius(function(d) { return radius; });

  var highlight = vis.append("path")
        .attr("d", null)
        //.style("opacity", 0);
        .style("fill", "#eee"); 

  var path = vis.data([json]).selectAll("dataArc")
        .data(nodes)
        .enter().append("path")
        .attr("display", function(d) { return d.depth ? null : "none"; })
        .attr("d", arc)
        .attr("fill-rule", "evenodd")
        .style("fill", function(d) { return colors.call(this, d.name); })
        .style("opacity", 1)
        .on("mouseover", mouseover)
        //.on("mouseleave", mouseleave) // do not work
        .on("click", click);

  // Add the mouseleave handler to the bounding circle.
  d3.select(el).select("#"+ el.id + "-container").on("mouseleave", mouseleave);

The click function :

  function click(d) {
    vis.selectAll("path")
        .transition('arc_tween') 
        .duration(1750)
        .attrTween("d", arcTween(d));

    highlight
        .transition()
        .duration(250)
        .attr("d", null);

  }

and the arcTween function :

function arcTween(d) {

    var xd = d3.interpolate(a.domain(), [ (d.x/(2 * Math.PI)) , ((d.x+d.dx)/(2 * Math.PI)) ]);
    var yd = d3.interpolate(b.domain(), [ ((Math.sqrt(d.y))/(radius - outerRadius)) , (radius - outerRadius) ]);
    var yr = d3.interpolate(b.range(), [0, (radius - outerRadius)] );

    // For each node, return an interpolator function that D3 can use to transition.
    // The scales only need to be modified once per transition step, so only do this
    // when i = 0. In all cases the interpolator just re-applies the arc function,
    // which uses our newly updated scales to produce new curves.

    return function(d, i) {
        (i == 0) ?
            function(t) {
                a.domain(xd(t));
                b.domain(yd(t));
                return arc(d);
            } 
        : function(t) {
                return arc(d);
            };
    }
  }

The original arcTween function I'm trying to adapt looks like this :

function arcTween(root) {

    var xd = d3.interpolate(x.domain(), [root.x, root.x + root.dx]);
    var yd = d3.interpolate(y.domain(), [root.y, 1]);
    var yr = d3.interpolate(y.range(), [root.y ? 20 : 0, r - outerRadius]);

    // For each node, return an interpolator function that D3 can use to transition.
    // The scales only need to be modified once per transition step, so only do this
    // when i = 0. In all cases the interpolator just re-applies the arc function,
    // which uses our newly updated scales to produce new curves.
    return function(d, i) {
        return i
            ? function(t) { return arc(d); }
        : function(t) { x.domain(xd(t)); y.domain(yd(t)).range(yr(t)); return arc(d); };
    };
}

This function is in a .js file that does not work with R. For some reason the return i instruction is freezing my chart while not zooming either, hence my go with (i==0)?... .

Edit 1 : how R is feeding this sunburst chart The dataframe I use for this has only two columns, the first one containing only the hierarchy as a string (hyphen seperated) and the second column the number of occurences of such a hierarchy. It could look like this :

 search-search-search-product-product-product, 7311
 search-search-search-product-product-search, 2807
 search-search-search-product-search-account, 145
 search-search-search-product-search-end, 501
 search-search-search-product-search-home, 57
 search-search-search-product-search-other, 16
 search-search-search-product-search-product, 4559
 search-search-search-product-search-search, 2030
 search-search-search-search-account-account, 300
 search-search-search-search-account-end, 49

In the .JS file, there is a buildhierarchy function that takes a 2 column df and transform it into a hierarchical structure suitable for partitioning. Here is the code :

function buildHierarchy(csv) {
  var root = {"name": "root", "children": []};
  for (var i = 0; i < csv.length; i++) {
    var sequence = csv[i][0];
    var size = +csv[i][1];
    if (isNaN(size)) { // e.g. if this is a header row
      continue;
    }
    var parts = sequence.split("-");
    var currentNode = root;
    for (var j = 0; j < parts.length; j++) {
      var children = currentNode["children"];
      var nodeName = parts[j];
      var childNode;
      if (j + 1 < parts.length) {
   // Not yet at the end of the sequence; move down the tree.
    var foundChild = false;
    for (var k = 0; k < children.length; k++) {
      if (children[k]["name"] == nodeName) {
        childNode = children[k];
        foundChild = true;
        break;
      }
    }
  // If we don't already have a child node for this branch, create it.
    if (!foundChild) {
      childNode = {"name": nodeName, "children": []};
      children.push(childNode);
    }
    currentNode = childNode;
      } else {
    // Reached the end of the sequence; create a leaf node.
    childNode = {"name": nodeName, "size": size};
    children.push(childNode);
      }
    }
  }
  return root;

};

It looks like you are not returning the function that takes t as an argument. You can see the original tween has one more return than your changed version has.

return function(d, i) {
        return (i == 0) ?
            function(t) {
                a.domain(xd(t));
                b.domain(yd(t));
                return arc(d);
            } 
        : function(t) {
                return arc(d);
            };
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM