Динамически обновлять D3 Sunburst, если исходный json обновлен (элемент добавлен или удален)
Я новичок в D3 и пытаюсь динамически обновлять диаграмму, если исходный json изменен. Но я не могу этого достичь.
Пожалуйста, проверьте этот plunkr
Js:
var width = 500,
height = 500,
radius = Math.min(width, height) / 2;
var x = d3.scale.linear()
.range([0, 2 * Math.PI]);
var y = d3.scale.sqrt()
.range([0, radius]);
var color = d3.scale.category10();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + (height / 2 + 10) + ") rotate(-90 0 0)");
var partition = d3.layout.partition()
.value(function(d) {
return d.size;
});
var arc = d3.svg.arc()
.startAngle(function(d) {
return Math.max(0, Math.min(2 * Math.PI, x(d.x)));
})
.endAngle(function(d) {
return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx)));
})
.innerRadius(function(d) {
return Math.max(0, y(d.y));
})
.outerRadius(function(d) {
return Math.max(0, y(d.y + d.dy));
});
//d3.json("/d/4063550/flare.json", function(error, root) {
var root = initItems;
var g = svg.selectAll("g")
.data(partition.nodes(root))
.enter().append("g");
var path = g.append("path")
.attr("d", arc)
.style("fill", function(d) {
return color((d.children ? d : d.parent).name);
})
.on("click", click)
.each(function(d) {
this.x0 = d.x;
this.dx0 = d.dx;
});
//.append("text")
var text = g.append("text")
.attr("x", function(d) {
return y(d.y);
})
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
.attr("transform", function(d) {
return "rotate(" + computeTextRotation(d) + ")";
})
.text(function(d) {
return d.name;
})
.style("fill", "white");
function computeTextRotation(d) {
var angle = x(d.x + d.dx / 2) - Math.PI / 2;
return angle / Math.PI * 180;
}
function click(d) {
console.log(d)
// fade out all text elements
if (d.size !== undefined) {
d.size += 100;
};
text.transition().attr("opacity", 0);
path.transition()
.duration(750)
.attrTween("d", arcTween(d))
.each("end", function(e, i) {
// check if the animated element data e lies within the visible angle span given in d
if (e.x >= d.x && e.x < (d.x + d.dx)) {
// get a selection of the associated text element
var arcText = d3.select(this.parentNode).select("text");
// fade in the text element and recalculate positions
arcText.transition().duration(750)
.attr("opacity", 1)
.attr("transform", function() {
return "rotate(" + computeTextRotation(e) + ")"
})
.attr("x", function(d) {
return y(d.y);
});
}
});
} //});
// Word wrap!
var insertLinebreaks = function(t, d, width) {
alert(0)
var el = d3.select(t);
var p = d3.select(t.parentNode);
p.append("g")
.attr("x", function(d) {
return y(d.y);
})
// .attr("dx", "6") // margin
//.attr("dy", ".35em") // vertical-align
.attr("transform", function(d) {
return "rotate(" + computeTextRotation(d) + ")";
})
//p
.append("foreignObject")
.attr('x', -width / 2)
.attr("width", width)
.attr("height", 200)
.append("xhtml:p")
.attr('style', 'word-wrap: break-word; text-align:center;')
.html(d.name);
alert(1)
el.remove();
alert(2)
};
//g.selectAll("text")
// .each(function(d,i){ insertLinebreaks(this, d, 50 ); });
d3.select(self.frameElement).style("height", height + "px");
// Interpolate the scales!
function arcTween(d) {
var xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
yd = d3.interpolate(y.domain(), [d.y, 1]),
yr = d3.interpolate(y.range(), [d.y ? 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);
};
};
}
function arcTweenUpdate(a) {
console.log(path);
var _self = this;
var i = d3.interpolate({ x: this.x0, dx: this.dx0 }, a);
return function(t) {
var b = i(t);
console.log(window);
_self.x0 = b.x;
_self.dx0 = b.dx;
return arc(b);
};
}
setTimeout(function() {
path.data(partition.nodes(newItems))
.transition()
.duration(750)
.attrTween("d", arcTweenUpdate)
}, 2000);
Ответы
Ответ 1
В дополнение к тому, что @Cyril предложил удалить следующую строку:
d3.select(self.frameElement).style("height", height + "px");
Я сделал дополнительные изменения в вашей скрипке: рабочая скрипка
Идея, используемая здесь, заключается в добавлении функции updateChart
, которая принимает items
, а затем генерирует диаграмму:
var updateChart = function (items) {
// code to update the chart with new items
}
updateChart(initItems);
setTimeout(function () { updateChart(newItems); }, 2000);
Это не использует созданную arcTweenUpdate
функцию, но я попытаюсь объяснить базовую концепцию:
Сначала вам нужно JOIN
новые данные с вашими существующими данными:
// DATA JOIN - Join new data with old elements, if any.
var gs = svg.selectAll("g").data(partition.nodes(root));
затем ENTER
для создания новых элементов, если требуется:
// ENTER
var g = gs.enter().append("g").on("click", click);
Но нам также нужно UPDATE
существующие/новые узлы path
и text
с новыми данными:
// UPDATE
var path = g.append("path");
gs.select('path')
.style("fill", function(d) {
return color((d.children ? d : d.parent).name);
})
//.on("click", click)
.each(function(d) {
this.x0 = d.x;
this.dx0 = d.dx;
})
.transition().duration(500)
.attr("d", arc);
var text = g.append("text");
gs.select('text')
.attr("x", function(d) {
return y(d.y);
})
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
.attr("transform", function(d) {
return "rotate(" + computeTextRotation(d) + ")";
})
.text(function(d) {
return d.name;
})
.style("fill", "white");
и после того, как все будет создано/обновлено, удалите узлы g
, которые не используются, т.е. EXIT
:
// EXIT - Remove old elements as needed.
gs.exit().transition().duration(500).style("fill-opacity", 1e-6).remove();
Вся эта картина JOIN + ENTER + UPDATE + EXIT
показана в следующих статьях Майка Бостока:
Ответ 2
В стороне скрипта setTimeout
не работает, потому что:
d3.select(self.frameElement).style("height", height + "px");
Вы получите Uncaught SecurityError: не удалось прочитать свойство "frame" из "окна": заблокировано кадр с источником " https://fiddle.jshell.net" от доступа к кадру с источником и setTimeout никогда не вызывается.
Итак, вы можете удалить эту строку d3.select(self.frameElement).style("height", height + "px");
только для скрипта.
Кроме того:
Функция таймаута должна выглядеть так:
setTimeout(function() {
//remove the old graph
svg.selectAll("*").remove();
root = newItems;
g = svg.selectAll("g")
.data(partition.nodes(newItems))
.enter().append("g");
/make path
path = g.append("path")
.attr("d", arc)
.style("fill", function(d) {
return color((d.children ? d : d.parent).name);
})
.on("click", click)
.each(function(d) {
this.x0 = d.x;
this.dx0 = d.dx;
});
//make text
text = g.append("text")
.attr("x", function(d) {
return y(d.y);
})
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
.attr("transform", function(d) {
return "rotate(" + computeTextRotation(d) + ")";
})
.text(function(d) {
return d.name;
})
.style("fill", "white");
}
рабочий скрипт здесь
Ответ 3
для ввода() и переходов на работу вам нужно дать d3 способ идентифицировать каждый элемент в ваших данных. функция .data() имеет второй параметр, который позволяет вам возвращать что-то для использования в качестве идентификатора. enter() будет использовать идентификатор, чтобы определить, является ли объект новым.
попробуйте изменить
path.data(partition.nodes(newItems))
.data(partition.nodes(root));
к
path.data(partition.nodes(newItems), function(d){return d.name});
.data(partition.nodes(root), function(d){return d.name});