如何使用d3.force对散点图进行动态,非重叠标记

时间:2018-06-14 05:44:56

标签: javascript d3.js force-layout

编辑:是否可以将d3.force导入我的反应组件?如果没有,那么我可能会无缘无故地挣扎。

从谷歌上搜索问题(如何在没有重叠的情况下将标签添加到散点图中),答案似乎是d3.force(至少这一个答案),这是我想尝试的答案包裹我的头。

我创建了一个非常小的简单示例,其中包含~15个圆圈及其相应的文本标签,这些标签当前位于标记的右侧6个像素和3个像素的位置。结果是重叠标签名称的杂乱图表。

const myData = [
  {x:25, y:30, name:"tommy"},
  {x:8, y:12, name:"joey"},
  {x:92, y:107, name:"nicky"},
  {x:85, y:50, name:"peter"},
  {x:65, y:80, name:"mickie"},
  {x:65, y:80, name:"gregie"},
  {x:54, y:6, name:"tammie"},
  {x:102, y:42, name:"benny"},
  {x:66, y:45, name:"dennie"},
  {x:81, y:44, name:"jerryi"},
  {x:127, y:36, name:"garrie"},
  {x:62, y:30, name:"frankie"},
  {x:157, y:55, name:"joeiyei"},
  {x:157, y:62, name:"nickaie"},
  {x:58, y:105, name:"tommie"}
]

d3.select('#mySvg')
  .selectAll('circle')
  .data(myData)
  .enter()
  .append('circle')
  .attr('cx', d => d.x)
  .attr('cy', d => d.y)
  .attr('r', 6)
  .attr('fill', 'blue')
  
d3.select('#mySvg')
  .selectAll('text')
  .data(myData)
  .enter()
  .append('text')
  .attr('x', d => d.x + 6)
  .attr('y', d => d.y + 3)
  .attr('font-size', '0.8em')
  .text(d => d.name)
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg id="mySvg">

我很难知道从哪里开始使用d3.force文档。我应该将整个图表转换为力图吗?我不介意某些点重叠,并且在我的数据中(在此示例中),某些点具有完全相同的x,y坐标。

此外,是否可以在标签和点之间连接一条浅灰色线条,这样就不会混淆哪个标签对应哪个点?

如果之前有人问过,请道歉。在我努力寻找这个问题的答案时,我发现了一些完全是力图的图(我认为我不想这样)。任何帮助都在这里赞赏!!!

1 个答案:

答案 0 :(得分:2)

首先,不要将整个图表变成强制导向图表。鉴于我在您的代码中看到的内容,点的精确垂直和水平位置编码信息,必须准确地传输给用户。根据其定义,力导向图表将无法保证这种精确度。

您可以使用力模拟来避免标签的碰撞,即仅对文本应用模拟,而不对点应用模拟。这并不难,但即便如此,我也建议不要这样做。这里的原因很简单:你有文字标签,而不是像圆圈这样的图形元素。那些文本不能自由旋转,不应该旋转(这使得阅读更难)。

因此,鉴于文本应横向呈现,在这种情况下我最喜欢的方法是使用text-anchor。为了充分利用可用区域(这里我使用简单的线性标尺),您可以最大限度地扩大散布,这种方法可以非常有效。它非常简单:

首先,我们创建一个名称数组,显示在点的左侧:

const namesToLeft = ["dennie", "gregie"];

然后,我们更改了text-anchor

.attr("text-anchor", function(d) {
    return namesToLeft.indexOf(d.name) > -1 ? "end" : "start"
})

以下是演示:

&#13;
&#13;
const myData = [{
    x: 25,
    y: 30,
    name: "tommy"
  },
  {
    x: 8,
    y: 12,
    name: "joey"
  },
  {
    x: 92,
    y: 107,
    name: "nicky"
  },
  {
    x: 85,
    y: 50,
    name: "peter"
  },
  {
    x: 65,
    y: 80,
    name: "mickie"
  },
  {
    x: 65,
    y: 80,
    name: "gregie"
  },
  {
    x: 54,
    y: 6,
    name: "tammie"
  },
  {
    x: 102,
    y: 42,
    name: "benny"
  },
  {
    x: 66,
    y: 45,
    name: "dennie"
  },
  {
    x: 81,
    y: 44,
    name: "jerryi"
  },
  {
    x: 127,
    y: 36,
    name: "garrie"
  },
  {
    x: 62,
    y: 30,
    name: "frankie"
  },
  {
    x: 157,
    y: 55,
    name: "joeiyei"
  },
  {
    x: 157,
    y: 62,
    name: "nickaie"
  },
  {
    x: 58,
    y: 105,
    name: "tommie"
  }
];

var scale = d3.scaleLinear()
  .domain([0, 160])
  .range([0, 300]);

const namesToLeft = ["dennie", "gregie"];

d3.select('#mySvg')
  .selectAll('circle')
  .data(myData)
  .enter()
  .append('circle')
  .attr('cx', d => scale(d.x))
  .attr('cy', d => scale(d.y))
  .attr('r', 6)
  .attr('fill', 'blue')

d3.select('#mySvg')
  .selectAll('text')
  .data(myData)
  .enter()
  .append('text')
  .attr("text-anchor", function(d) {
    return namesToLeft.indexOf(d.name) > -1 ? "end" : "start"
  })
  .attr('x', d => scale(d.x) + (namesToLeft.indexOf(d.name) > -1 ? -6 : 6))
  .attr('y', d => scale(d.y) + 3)
  .attr('font-size', '0.8em')
  .text(d => d.name)
&#13;
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg id="mySvg" width="400" height="300">
&#13;
&#13;
&#13;