D3 v4 Sunburst масштабировать и обновлять основные данные по клику

Это мой первый вопрос в stackru, и я надеюсь, что вы можете помочь мне с этой проблемой, для которой я еще не нашел решения. Я хочу использовать D3 v4 и визуализацию солнечных лучей в веб-приложении для отображения и навигации по некоторым данным, предоставленным файлом json. Мне удалось отобразить данные с помощью солнечных лучей (со всплывающими подсказками), которые также позволяют приближать детей, используя переход, когда пользователь нажимает на соответствующую дугу.

Для моего приложения я хочу использовать исходный небольшой файл json, который показывает только часть полных (гораздо больших) данных. Эти небольшие данные должны быть расширены по требованию и динамически визуализироваться на солнце, когда пользователь нажимает на дугу (для которой можно получить дополнительную информацию). Кроме того, когда пользователь "возвращается" к родительской дуге, недавно добавленные данные должны быть удалены, чтобы снова отображались только исходные данные.

Итак, рабочий процесс: на старте появляются солнечные лучи (data1); пользователь нажимает на дочернюю дугу; эта дуга должна быть новым фокусом / центром солнечных лучей, в то время как исходные данные расширяются до (data2) обновление визуализации солнечных лучей; пользователь видит новый фокус и вновь загруженные дочерние элементы выбранной дуги; когда пользователь перемещается назад, (data1) должен отображаться снова. Последняя часть все еще отсутствует, и я позабочусь об этом позже, но перед этим я столкнулся с проблемой. Мне удалось реализовать переключение между двумя наборами данных, но результат нового фокуса и поведение солнечных лучей не соответствуют ожиданиям. (data1) а также (data2) являются частичными примерами данных из набора данных flare.json и при нажатии на дугу "аналитика", для которой новые данные должны быть загружены и визуализированы, после перехода только два ("кластер" и "граф") из трех его дочерних элементов (отсутствует "оптимизация"). Но дополнительный щелчок по дуге "аналитики" выявляет все больше и больше пропавших третьих детей. Чем больше я нажимаю, тем больше "правильный" результат как-то аппроксимируется.

Я создал рабочий пример в JSFiddle, который демонстрирует мою озабоченность и возникающую проблему. Кроме того, вот код:

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

И наборы данных data1 а также 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}
   ]
  }]
  };

Причина, почему я использую setTimeout вызов метода click заключается в том, что когда я немедленно применяю переключатель к новым данным, первое событие перехода / масштабирования в начале метода click не применяется, поскольку переключение происходит слишком быстро. Может быть, мой подход совершенно неверен в этом случае.

У вас есть идея, что я делаю неправильно, или совет, как мне лучше решить эту проблему?

Любая помощь приветствуется! Заранее большое спасибо!

0 ответов

Другие вопросы по тегам