2013-03-08 1 views
6

Я использую d3 нарисовать поток графики очень похожи на официальный пример http://bl.ocks.org/mbostock/4060954:перехода при добавлении новых данных в d3 streamgraph

enter image description here

Единственное отличие заключается в том, как я обновил его с новыми данными. Я не только хочу вертикальный переход (y-value), но и хочу добавить новые точки данных справа. Весь график должен стать сжатым в горизонтальном направлении.

Это не проблема для достижения желаемого результата, единственная проблема заключается в том, что переход между двумя состояниями выглядит не так, как ожидалось.

Вы можете найти аа минимальный пример странного эффекта перехода на 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);}); 
} 
+0

jsfiddle не работает ... –

+0

извините, теперь он – dedan

ответ

9

Эта проблема вращается вокруг того факта, что для 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.

В любом случае это решение работает для примера, который у вас был. Надеюсь, это поможет.

+0

благодарит много, это действительно решило мою проблему. В начале я не понял, что вы подразумеваете под самым большим путем. Но теперь я понял, что вы имеете в виду тот, который является самым высоким в уложенном графике. Поскольку этот порядок не изменился, вы полностью решили мою проблему. Также спасибо за хорошее объяснение – dedan