2016-01-16 5 views
4

Я пытаюсь найти пути для отображения рядом друг с другом, так что они будут толкать друг друга (факторинг по ширине и соседним точкам), а не перекрываться.Бок о бок пути в d3

Это моя скрипка, в основном собрана из примеров https://jsfiddle.net/crimsonbinome22/k2xqn24x/

var LineGroup = svg.append("g") 
.attr("class","line"); 

var line = d3.svg.line() 
.interpolate("linear") 
.x(function(d) { return (d.x); }) 
.y(function(d) { return (d.y); }) 
; 

LineGroup.selectAll(".line") 
.data(series) 
.enter().append("path") 
.attr("class", "line") 
.attr("d", function(d){ return line(d.p); }) 
.attr("stroke", function(d){ return d.c; }) 
.attr("stroke-width", function(d){ return d.w; }) 
.attr("fill", "none"); 

И это то, что я надеюсь достичь в this image here, в основном:

  • Для всех линий посадки на то же самое, надавите на них влево или вправо от этой точки, так что вместе они вокруг него.
  • Фактор в ширине линии, чтобы они не перекрывались, или оставляли пробелы между ними.
  • Уметь обрабатывать пути с различным количеством точек (максимум в примере 3, но я хочу иметь дело с до 10)
    • Примечания хотя точки, которые перекрываются всегда будут иметь один и тот же показатель (они не будут петля вокруг, но просто выходите наружу, как дерево)
  • Уметь обрабатывать различные количества линий, приземляющихся на одну и ту же точку.

Некоторые вопросы, которые я имею:

  • Я новичок в d3, и я нахожу функции немного озадачивает. Не уверен, как даже начать применять логику, которая будет перемещать линии вокруг.
  • В моей структуре данных есть некоторая избыточная информация, например r для ранга (для принятия решения о том, нажимать влево или вправо) и w для ширины, оба из которых всегда будут одинаковыми для определенной строки.
  • У меня много данных, поэтому используемая здесь структура данных не будет работать с данными csv, которые у меня есть. Возможно, сейчас пропустите этот вариант, и я открою для него новый вопрос позже.

У меня был поиск, но я не могу найти примеров того, как это сделать. В некотором смысле это почти как диаграмма аккордов, но немного отличается, и я не могу найти много подходящего кода для повторного использования. Любая помощь в том, как достичь этого (либо с подходом, который я начал, либо чем-то совершенно другим, если я его пропустил), будет оценена.

+0

Это очень сложный, нетривиальный вопрос. В худшем случае вам понадобится использовать [line-line intersection] (https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection) уравнения, чтобы определить, где они пересекаются, а затем сместить их до тех пор, т. Однако ваша ситуация может быть проще, поскольку линии лежат друг над другом. Интересно, можете ли вы определить наклон и перехват каждой линии, и если они совпадут с вами, сдвиньте влево или вправо. – Mark

+0

Я согласен с сложностью, но думал, что это будет проще, чем вы описали. Например, я не думал, что мне нужно будет вычислить пересечения, так как я хочу только сдвинуть строки, где встречаются вершины. Например: возьмите первую точку на каждом пути и сгруппируйте те, у которых одинаковые (x, y), затем выполняйте перемещение внутри каждой группы. Сделайте то же самое для второй точки в каждом пути, третий по каждому пути и т. Д. Существует потенциал для сложности (например: разработка угла, чтобы нажимать на них, а не только налево/вправо), но в качестве начала просто зная, что они нужно двигаться, должно быть (надеюсь) просто? – crimsonbinome22

ответ

2

Я хотел бы пойти со следующими шагами:

  • вычислить массив узла объектов, т.е.один объект для каждой точки посещается линией
  • вычислить дерева на этом узле (то есть, для каждого узла, добавить ссылки на его родитель и ребенок)
  • убедитесь, что дети любого узла являются упорядочены в соответствии на угол они делают с этим узлом
  • в этой точке, каждая строка теперь только зависит от конечного узла
  • для каждого узла вычисления упорядоченного списка линий, проходящих через
    • посетите все узлы снизу вверх (т. начиная с листьев)
    • список «идти через» является объединение списков детей + все линии, которые заканчиваются на текущем узле
  • для каждого узла, вычислить массив смещений (путем суммирования последовательных ширины линий, проходящих через)
  • наконец, для каждой строки и каждого узла в строке, проверить массив смещений, чтобы знать, сколько линия должна быть смещена

Edit: бег пример https://jsfiddle.net/toh7d9tq/1/

Я использовал несколько иной подход в течение последних двух шагов (вычисления смещения): Я на самом деле создать новый p массив для каждой серии со списком пара {node, offset}. Таким образом, гораздо легче получить доступ ко всем соответствующим данным в функции рисования.

Мне нужно было добавить искусственный корень, чтобы иметь хорошую стартовую линию (и сделать ее проще для рекурсии и углов и всего), вы можете пропустить ее на этапе рисования, если хотите.

function key(p) { 
    return p.time+"_"+p.value 
    } 

    // a node has fields: 
    // - time/value (coordinates) 
    // - series (set of series going through) 
    // - parent/children (tree structure) 
    // - direction: angle of the arc coming from the parent 

    //artificial root 
    var root={time:200, value:height, series:[], direction:-Math.PI/2}; 

    //set of nodes 
    var nodes = d3.map([root], key); 
    //create nodes, link each series to the corresponding leaf 
    series.forEach(function(s){ 
    s.pWithOffset=[]; //this will be filled later on 
    var parent=root; 
    s.p.forEach(function(d) { 
    var n=nodes.get(key(d)); 
    if (!n) { 
     //create node at given coordinates if does not exist 
     n={time:d.time, 
      value:d.value, 
      parent:parent, 
      series:[], 
      direction:Math.atan2(d.value-parent.value, d.time-parent.time)}; 
     nodes.set(key(n),n); 
     //add node to the parent's children 
     if (!parent.children) parent.children=[]; 
     parent.children.push(n); 
    }  
    //this node is the parent of the next one 
    parent=n; 
    }) 
    //last node is the leaf of this series 
    s.leafNode=parent; 
    parent.series.push(s); 
    }) 

    //sort children by direction 
    nodes.values().forEach(function(n){ 
     if (n.children) 
     n.children.sort(function (a,b){ 
     if (a.direction>n.direction) 
     return a.direction-b.direction; 
     }); 
     }); 

    //recursively list all series through each node (bottom-up) 
    function listSeries(n) { 
    if (!n.children) return; 
    n.children.forEach(listSeries); 
    n.series=d3.merge(n.children.map(function(c){return c.series})); 
    } 
    listSeries(root); 
    //compute offsets for each series in each node, and add them as a list to the corresponding series 
    //in a first time, this is not centered 
    function listOffsets(n) { 
    var offset=0; 
    n.series.forEach(function(s){ 
     s.pWithOffset.push({node:n, offset:offset+s.w/2}) 
     offset+=s.w;  
    }) 
    n.totalOffset=offset; 
    if (n.children) 
     n.children.forEach(listOffsets); 
    } 
    listOffsets(root); 

А потом в секции рисования:

var line = d3.svg.line() 
    .interpolate("linear") 
    .x(function(d) { return (d.node.time-Math.sin(d.node.direction)*(d.offset-d.node.totalOffset/2)); }) 
    .y(function(d) { return (d.node.value+Math.cos(d.node.direction)*(d.offset-d.node.totalOffset/2)); }) 
    ; 

LineGroup.selectAll(".line") 
    .data(series) 
    .enter().append("path") 
    .attr("class", "line") 
    .attr("d", function(d){ return line(d.pWithOffset); }) 
    .attr("stroke", function(d){ return d.c; }) 
    .attr("stroke-width", function(d){ return d.w; }) 
    .attr("fill", "none"); 
+0

Это звучит как отличное решение, и было бы приятно видеть его в действии. Я начал что-то похожее на это, но только дошел до того, чтобы рассчитать, как далеко продвигать линии влево/вправо, чтобы они не перекрывались. Не дошел до углов, но, учитывая мой код, я не думаю, что скоро буду! – crimsonbinome22

+1

@ crimsonbinome22 Вот, я завершил ответ с кодом – tarulen

+0

Это очень впечатляет и очень элегантно сделано, спасибо за вашу помощь! В ближайшее время я попытаюсь это сделать. – crimsonbinome22