简体   繁体   English

d3中的并排路径

[英]Side-by-side paths in d3

I'm trying out a way to get paths to display next to each other, such that they'll push each other around (factoring in widths and neighbouring points) and not overlap. 我正在尝试一种方法来获得彼此相邻的路径,这样它们就会相互推动(分解宽度和相邻点)并且不会重叠。

This is my fiddle, mostly pieced together from examples https://jsfiddle.net/crimsonbinome22/k2xqn24x/ 这是我的小提琴,大多是从例子中拼凑而成的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");

And this is what I'm hoping to achieve in this image here , basically: 这就是我希望在这张图片中实现的目的,基本上:

  • For all lines landing on the same point, push them left or right of that point so together they center around it. 对于落在同一点上的所有线路,将它们向左或向右推动,使它们一起围绕它。
  • Factor in line width so they don't overlap, or leave whitespace between. 线宽因子,因此它们不重叠,或在它们之间留下空白。
  • Be able to handle paths with different numbers of points (max in example is 3 but I want to deal with up to 10) 能够处理具有不同点数的路径(例如,最大值为3但我想处理多达10个)
    • Note though points that overlap will always have the same index (they won't loop around, but just go outwards like a tree) 请注意,虽然重叠的点总是具有相同的索引(它们不会循环,但只是像树那样向外)
  • Be able to handle different numbers of lines landing on the same point. 能够处理落在同一点上的不同数量的线。

Some issues I'm having: 我遇到的一些问题:

  • I'm new to d3 and I find functions a bit baffling. 我是d3的新手,我觉得功能有点莫名其妙。 Not sure how to even start to apply logic that will move the lines around. 不知道如何开始应用将移动线条的逻辑。
  • My data structure has some redundant info in it, such as r for the rank (to decide whether to push left or right) and w for the width both of which will always be the same for a particular line. 我的数据结构中有一些冗余信息,例如r表示排名(决定是向左还是向右),w表示宽度,对于特定行,宽度都是相同的。
  • I have a lot of data so the data structure used here won't work with the csv data I have. 我有很多数据,所以这里使用的数据结构不适用于我拥有的csv数据。 Can maybe skip this one for now and I'll open up a new question for that one later. 也许现在可以跳过这个,我稍后会为那个问题打开一个新问题。

I've had a search around but can't find any examples of how to do this. 我有一个搜索,但找不到任何如何做到这一点的例子。 In a way it's almost like a chord diagram but a little different, and I can't find much relevant code to reuse. 在某种程度上,它几乎像一个和弦图,但有点不同,我找不到很多相关的代码可以重用。 Any help on how to achieve this (either with the approach I've started, or something totally different if I've missed it) would be appreciated. 如何实现这一点的任何帮助(无论是我已经开始的方法,还是我错过了它的完全不同的东西)将不胜感激。

I would go with the following steps: 我会采取以下步骤:

  • compute an array of node objects, ie one object for each point visited by a line 计算节点对象的数组,即一条线访问的每个点的一个对象
  • compute the tree on this node (that is, for every node, add links to its parent and children) 计算此节点上的 (即,为每个节点添加指向其父节点和子节点的链接)
  • make sure that children of any node are ordered according to the angle they make with this node 确保任何节点的子节点根据它们与此节点的角度进行排序
  • at this point, each line now only depends on its final node 此时,每条线现在仅取决于其最终节点
  • for each node compute an ordered list of lines going through 为每个节点计算经过的有序列表
    • visit all nodes bottom-up (ie starting from the leaves) 自下而上访问所有节点(即从叶子开始)
    • the "go-through" list is the concatenation of the lists of the children + all lines that end at the current node “浏览”列表是子列表+在当前节点结束的所有行的串联
  • for each node, compute an array of offsets (by summing the successive width of the lines going through) 对于每个节点,计算一组偏移量 (通过对经过的线的连续宽度求和)
  • finally, for every line and every node in the line, check the array of offsets to know how much the line must be shifted 最后,对于行中的每一行和每个节点, 检查偏移数组以了解必须移动的行数

Edit: running example https://jsfiddle.net/toh7d9tq/1/ 编辑:运行示例 https://jsfiddle.net/toh7d9tq/1/

I have used a slightly different approach for the last two steps (computing the offset): I actually create a new p array for each series with a list of pairs {node, offset} . 我在最后两个步骤中使用了稍微不同的方法(计算偏移量):我实际上为每个系列创建一个新的p数组,其中包含一对{node, offset}的列表。 This way it is much easier to access all relevant data in the drawing function. 这样,在绘图功能中访问所有相关数据就容易得多。

I needed to add an artificial root to have a nice starting line (and to make it easier for recursion and angles and everything), you can skip it in the drawing phase if you want. 我需要添加一个人工根来获得一个很好的起始线(并使其更容易递归和角度以及所有内容),如果需要,可以在绘图阶段跳过它。

  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);

And then in the drawing section: 然后在绘图部分:

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");

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM