Переход при добавлении новых данных в d3 streamgraph
Я использую d3 для рисования потокового графа, очень похожего на официальный пример http://bl.ocks.org/mbostock/4060954:
Разница лишь в том, как я обновил его новыми данными. Я не только хочу вертикальный (у-значение) переход, но также хочу добавить новые точки данных справа. Весь график должен быть сжат в горизонтальном направлении.
Достигнуть желаемого результата не составило труда, единственная проблема заключается в том, что переход между двумя состояниями выглядит не так, как ожидалось.
Вы можете найти минимальный пример странного эффекта перехода на JSfiddle: http://jsfiddle.net/jaYJ9/4/
Нажмите кнопку обновления, чтобы увидеть эффект
test_data0 = [{"0": 0.0, "1": 0.0, "-1": 0.0}, {"0": 0.0, "1": 0.6, "-1": 0.0}, {"0": 0.0, "1": 0.3, "-1": 0.0}, {"0": 0.0, "1": 0.0, "-1": 0.6}, {"0": 0.3, "1": 0.0, "-1": 0.0}, {"0": 0.0, "1": 0.3, "-1": 0.3}, {"0": 0.3, "1": 0.0, "-1": 0.0}, {"0": 0.3, "1": 0.0, "-1": 0.0}, {"0": 0.0, "1": 0.0, "-1": 0.0}]
test_data1 = [{"0": 0.0, "1": 0.0, "-1": 0.0}, {"0": 0.0, "1": 0.6, "-1": 0.0}, {"0": 0.0, "1": 0.3, "-1": 0.0}, {"0": 0.0, "1": 0.0, "-1": 0.6}, {"0": 0.3, "1": 0.0, "-1": 0.0}, {"0": 0.0, "1": 0.3, "-1": 0.3}, {"0": 0.3, "1": 0.0, "-1": 0.0}, {"0": 0.3, "1": 0.0, "-1": 0.0}, {"0": 0.0, "1": 0.0, "-1": 0.0}, {"0": 0.0, "1": 0.0, "-1": 0.0}]
$('#update').click(function(){
streamed_history(test_data1)
});
var width = 300,
height = 200,
colors = {'0': '#6ff500', '1': '#ffad0a', '-1': '#f90035'},
feedbacks = [-1, 0, 1],
stack = d3.layout.stack();
var svg = d3.select("#timeline").append("svg")
.attr("width", width)
.attr("height", height);
var y = d3.scale.linear()
.domain([0, 1])
.range([height, 0]);
streamed_history(test_data0)
function streamed_history(data) {
data_array = feedbacks.map(function (f) {
return data.map(function(element, i) { return {x: i, y: element[f]}; })
}),
layers = stack(data_array)
layers = feedbacks.map(function (f, i) {
return {layer: layers[i], feedback: f, color: colors[f]}
})
var x = d3.scale.linear()
.domain([0, data.length - 1])
.range([0, width]);
var area = d3.svg.area().interpolate("basis")
.x(function(d) { return x(d.x); })
.y0(function(d) { return y(d.y0); })
.y1(function(d) { return y(d.y0 + d.y); });
//enter
svg.selectAll("path")
.data(layers)
.enter().append("path")
.attr("d", function (d) {return area(d.layer);})
.style("fill", function(d) { return d.color; });
//update
d3.selectAll("path")
.data(layers)
.transition()
.duration(2000)
.attr("d", function (d) {return area(d.layer);});
}
1 ответ
Эта проблема связана с тем фактом, что для SVG-анимации вы можете перемещать точки только в конец пути.
Во-первых, это исправление (это будет работать только в том случае, если графика всегда имеет вертикальную плотность и имеет последовательный порядок, для которого график является самым высоким):
...
var area = d3.svg.area().interpolate("basis")
...
.y0(function(d) { return y(null); }) // The null here is super important!
...
...
// Add this function
function fixPath (path) {
var Lidx = path.indexOf('L');
var Cidx = path.slice(Lidx).indexOf('C');
var PCidx = path.slice(0,Lidx).lastIndexOf('C');
var lp = path.substr(PCidx, Lidx-PCidx);
var ss = path.substr(Lidx, Cidx);
return (path.slice(0,Lidx) + lp + ss + path.slice(Lidx));
}
...
svg.selectAll("path")
.data(layers.reverse()) // Gotta reverse the order!
.attr("d", function (d) { return fixPath(area(d.layer)); }) // Have to double up the bottom right corner to avoid artifacts
...
...
d3.selectAll("path")
.data(layers)
.attr("d", function (d) { return fixPath(area(d.layer)); }) // When updating too!
...
Рабочий пример здесь: http://jsfiddle.net/f5JSR/2/
Теперь объяснение...
Каждая из цветовых полос на вашем графике является замкнутым путем, и d3.js создает их так, что ни одна из этих цветовых полос не перекрывается друг с другом. Проблема в том, что это означает, что каждый из этих путей начинается в нижнем левом углу и проходит вокруг себя. Когда вы добавляете новую точку на эти пути, вы добавляете ее в конце, и она проталкивает остальную часть пути против часовой стрелки (создавая этот странный эффект анимации).
Я изначально пытался решить эту проблему с помощью SVG отсечения и fill-rule: evenodd
свойство, но, похоже, что для использования отсечения необходимо создать составные пути, а добавление новых точек толкает их в конец этого составного пути (например, вы не можете поместить точку на первый путь составного пути) Таким образом, проблема сохраняется.
Вместо этого это решение избавляет от хитрости d3.js и вместо этого заставляет все ваши цветные полосы расширяться до нижней части графика (это то, что y(null);
линия делает). Затем он упорядочивает пути так, чтобы самые высокие были нарисованы первыми. Этот подход нарушается, если высота одного графика падает ниже высоты другого графика.
Наконец, когда точки перемещаются вокруг правого нижнего угла, могут быть некоторые странные артефакты. Чтобы это исправить, я удваиваю количество точек в правом нижнем углу, используя fixPath
функция.
В любом случае, это решение работает для вашего примера. Надеюсь, это поможет.