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 не применяется, поскольку переключение происходит слишком быстро. Может быть, мой подход совершенно неверен в этом случае.
У вас есть идея, что я делаю неправильно, или совет, как мне лучше решить эту проблему?
Любая помощь приветствуется! Заранее большое спасибо!