D3 v4 sunburst scales and refreshes baseline data on click

This is my first question on stackoverflow and I hope you can help me with this problem for which I have not yet found a solution. I want to use D3 v4 and sunburst rendering in a web app to display and navigate some data provided by a json file. I was able to display the data using sunburst (with tooltips) which also allows the children to scale using a transition when the user clicks on the corresponding arc.

For my application, I want to use an initial small json file that only displays a portion of the complete (much larger) data. This small data needs to be expanded on demand and rendered dynamically in sunlight when the user clicks on the arc (for which more information can be obtained). In addition, when the user navigates back to the parent arc, the recently added data must be removed so that only the original data is displayed again.

So, the workflow: at startup, a solar charge appears ( data1

); the user clicks on the child arc; this arc should be the new focal point / center of the solar wave while the original data expands to ( data2

), updating the sunburst rendering; the user sees the new focus and newly loaded children of the selected arc; when the user navigates back, ( data1

) should show up again. The last part is still missing and I'll take care of that later, but before that I ran into a problem. I managed to implement a switch between the two datasets, but the result of the new focus and sunburst behavior is not as expected. ( data1

) and (data2

) are partial examples of data from the flare.json dataset and when clicking on the "analytics" of an arc for which new data should be loaded and visualized, after the transition only two ("cluster" and "graph") of its three children (missing "optimization "). But an extra click on the "analytics" arc reveals more and more missing third children. The more I press, the more "correct" the result somehow gets closer.

I created a working example in the JSFiddle that demonstrates my concern and the issue I am experiencing. Also, here's the code:

var width = 500;
var height = 500;
var radius = Math.min(width, height) / 2 - 10;
var color = d3.scaleOrdinal(d3.schemeCategory20);

var x = d3.scaleLinear()
.range([0, 2 * Math.PI]);

var y = d3.scaleSqrt()
.range([0, radius]);

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height)
  .append("g")
    .attr("transform", "translate(" + width / 2 + "," + (height / 2 + 10) + ")");

var arc = d3.arc()
.padAngle(.01)
.padRadius(radius/3)
.startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x0))); })
.endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x1))); })
.innerRadius(function(d) { return Math.max(0, y(d.y0)); })
.outerRadius(function(d) { return Math.max(0, y(d.y1)); });

var partition = d3.partition();
var root = d3.hierarchy( data1 ).count();

var g = svg.selectAll("g")
.data( partition(root).descendants(), function(d){return d.data.name;})
.enter().append("g");

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

var path = g.append("path")
.attr("d", arc)
.style("stroke", "white")
.style('stroke-width', 0.5)
.attr( "id", function(d){ return 'path' + d.data.name; })
.style("fill", function(d) { return color((d.children ? d : d.parent).data.name); })
.style("opacity", 0)
.on("click", click)
.on("mouseover", function(d) {
    tt.transition()
    .duration(200)
    .style("opacity", 1.0);
    tt.html( showTooltipInfo(d) )
    .style("left", (d3.event.pageX) + "px")
    .style("top", (d3.event.pageY - 28) + "px");
})                  
.on("mouseout", function(d) {       
    tt.transition()
    .duration(500)
    .style("opacity", 0);
})
.each(function(d,i){
    this.xx0 = d.x0;
    this.xx1 = d.x1;
    this.yy0 = d.y0;
    this.yy1 = d.y1;
})
.transition()
.duration(1000)
.style("opacity", 1);

function showTooltipInfo(d){
    return d.data.name;
}

function click(d) {
    svg.selectAll("g").select("path")
    .transition()
    .duration(750)
    .attrTween("d", arcTweenZoom(d))
    .each(function(d,i){
        this.xx0 = d.x0;
        this.xx1 = d.x1;
        this.yy0 = d.y0;
        this.yy1 = d.y1;
    });
    var selectedD = d;

    setTimeout(function () {
        var newRoot;
        if (selectedD.data.name === "analytics")
            newRoot = d3.hierarchy( data2 ).count();
        else
            return;

        var groups = svg.selectAll("g")
        .data( partition(newRoot).descendants(), function(d){return d.data.name;} );

        groups.exit()
        .transition()
        .duration(10)
        .style("opacity", 0)
        .remove();

        groups.select("path")
        .transition()
        .duration(750)
        .attrTween("d", arcTweenData)
        .each(function(d,i){
            this.xx0 = d.x0;
            this.xx1 = d.x1;
            this.yy0 = d.y0;
            this.yy1 = d.y1;
        });

        groups.enter().append("g").append("path")
        .attr("d", arc)
        .style("stroke", "white")
        .style('stroke-width', 0.5)
        .attr( "id", function(d){ return 'path' + d.data.name; })
        .style("fill", function(d) { return color((d.children ? d : d.parent).data.name); })
        .style("opacity", 0)
        .on("click", click)
        .on("mouseover", function(d) {
            tt.transition()
            .duration(200)
            .style("opacity", 1.0);
            tt.html( showTooltipInfo(d) )
            .style("left", (d3.event.pageX) + "px")
            .style("top", (d3.event.pageY - 28) + "px");
        })                  
        .on("mouseout", function(d) {       
            tt.transition()
            .duration(500)
            .style("opacity", 0);
        })
        .each(function(d,i){
            this.xx0 = d.x0;
            this.xx1 = d.x1;
            this.yy0 = d.y0;
            this.yy1 = d.y1;
        })
        .transition()
        .delay(250)
        .duration(750)
        .style("opacity", 1);
    }, 500);
}

function arcTweenData(a){
    if ( this.xx0 !== undefined ){
        var oi = d3.interpolate({x0: this.xx0, x1: this.xx1, y0: this.yy0, y1: this.yy1}, a);
        var that = this;
        return function(t) {
            var b = oi(t);
            that.xx0 = b.x0;
            that.xx1 = b.x1;
            that.yy0 = b.y0;
            that.yy1 = b.y1;
            return arc(b);
        };
    }
}

function arcTweenZoom(d){
    var xd = d3.interpolate(x.domain(), [d.x0, d.x1]),
    yd = d3.interpolate(y.domain(), [d.y0, 1]),
    yr = d3.interpolate(y.range(), [d.y0 ? 20 : 0, radius]);
    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);}
    }
}

      

Both datasets data1

and data2

:

var data1 = {
 "name": "flare",
 "children": [
  {
   "name": "analytics",
   "children": []
  },
  {
   "name": "animate",
   "children": [
    {"name": "Easing", "size": 17010},
    {"name": "FunctionSequence", "size": 5842},
    {
     "name": "interpolate",
     "children": [
      {"name": "ArrayInterpolator", "size": 1983},
      {"name": "ColorInterpolator", "size": 2047},
      {"name": "DateInterpolator", "size": 1375},
      {"name": "Interpolator", "size": 8746},
      {"name": "MatrixInterpolator", "size": 2202},
      {"name": "NumberInterpolator", "size": 1382},
      {"name": "ObjectInterpolator", "size": 1629},
      {"name": "PointInterpolator", "size": 1675},
      {"name": "RectangleInterpolator", "size": 2042}
     ]
    },
    {"name": "ISchedulable", "size": 1041},
    {"name": "Parallel", "size": 5176},
    {"name": "Pause", "size": 449},
    {"name": "Scheduler", "size": 5593},
    {"name": "Sequence", "size": 5534},
    {"name": "Transition", "size": 9201},
    {"name": "Transitioner", "size": 19975},
    {"name": "TransitionEvent", "size": 1116},
    {"name": "Tween", "size": 6006}
   ]
  }]
  };

var data2 = {
 "name": "flare",
 "children": [
  {
   "name": "analytics",
   "children": [
    {
     "name": "cluster",
     "children": [
      {"name": "AgglomerativeCluster", "size": 3938},
      {"name": "CommunityStructure", "size": 3812},
      {"name": "HierarchicalCluster", "size": 6714},
      {"name": "MergeEdge", "size": 743}
     ]
    },
    {
     "name": "graph",
     "children": [
      {"name": "BetweennessCentrality", "size": 3534},
      {"name": "LinkDistance", "size": 5731},
      {"name": "MaxFlowMinCut", "size": 7840},
      {"name": "ShortestPaths", "size": 5914},
      {"name": "SpanningTree", "size": 3416}
     ]
    },
    {
     "name": "optimization",
     "children": [
      {"name": "AspectRatioBanker", "size": 7074}
     ]
    }
   ]
  },
  {
   "name": "animate",
   "children": [
    {"name": "Easing", "size": 17010},
    {"name": "FunctionSequence", "size": 5842},
    {
     "name": "interpolate",
     "children": [
      {"name": "ArrayInterpolator", "size": 1983},
      {"name": "ColorInterpolator", "size": 2047},
      {"name": "DateInterpolator", "size": 1375},
      {"name": "Interpolator", "size": 8746},
      {"name": "MatrixInterpolator", "size": 2202},
      {"name": "NumberInterpolator", "size": 1382},
      {"name": "ObjectInterpolator", "size": 1629},
      {"name": "PointInterpolator", "size": 1675},
      {"name": "RectangleInterpolator", "size": 2042}
     ]
    },
    {"name": "ISchedulable", "size": 1041},
    {"name": "Parallel", "size": 5176},
    {"name": "Pause", "size": 449},
    {"name": "Scheduler", "size": 5593},
    {"name": "Sequence", "size": 5534},
    {"name": "Transition", "size": 9201},
    {"name": "Transitioner", "size": 19975},
    {"name": "TransitionEvent", "size": 1116},
    {"name": "Tween", "size": 6006}
   ]
  }]
  };

      

The reason I use the call setTimeout

in the click method is that when I immediately apply the switch to new data, the first transition / zoom event at the beginning of the click method is not applied because the transition is too fast. Maybe my approach in this case is completely wrong.

Do you have an idea what I am doing wrong or advice on how I can better solve this problem?

Any help is appreciated! Thank you so much in advance!

+3


source to share





All Articles