首页 > 解决方案 > 我如何创建一个

使用 d3.js

问题描述

我想dl使用 d3.js 从一些数据的列表中创建一系列标签。

我想出的代码是这样的:

var x=d3.select("body")
    .append('ol')
    .selectAll('li')
    .data(data)
    .enter()
    .append('li')
    .append('dl')
    .selectAll()
    .data(d=>Object.entries(d.volumeInfo)).enter();

x.append('dt')
    .text(d=>d[0]);

x.append('dd')
    .text(d=>d[1]);

wheredata是一个对象数组。一切正常,除了元素的顺序不正确。

这是我设法得到的订单:

<dl>
    <dt>key1</dt>
    <dt>key2</dt>
    <dd>value1</dd>
    <dd>value2</dd>
</dl>

但它应该是这样的:

<dl>
    <dt>key1</dt>
    <dd>value1</dd>
    <dt>key2</dt>
    <dd>value2</dd>
</dl>

我已经进行了相当多的谷歌搜索,但没有任何答案可以回答这个问题,至少不是以在 v5 中有效的方式,或者不是超过一个 dt/dd pair

这似乎是 d3.js 应该能够做的基本事情。

标签: javascriptd3.js

解决方案


在您的解决方案中:

x.append('dt')
    .text(d=>d[0]);

x.append('dd')
    .text(d=>d[1]);

所有附加有enter().append()循环的元素都按照它们附加的顺序附加到父级,对你来说运行如下:首先是所有的dts,然后是所有的dds,正如你所看到的。由 enter 语句创建的占位符节点(这些不是附加元素)不会以您可能期望它们的方式嵌套子节点。

尽管 d3 不包括通过简单方法一样简单的方法来实现您正在寻找的selection.append()方法,但使用标准 d3 方法和一两个额外的步骤可以相当容易地实现所需的行为。或者,我们可以自己将该功能构建到 d3.selection 中。

对于我的回答,我将以一个使用您的数据结构并输入模式的示例结束,但首先我将在这里稍微简化嵌套 - 而不是嵌套追加,我只是展示了几种可能的追加有序兄弟的方法。首先,我还简化了数据结构,但原理保持不变。


第一种方法可能是最直接的:使用selection.each()函数。使用 enter 选择(使用父项或输入的占位符),使用 each 方法附加两个单独的元素:

var data = [
{name:"a",description:"The first letter"},
{name:"b",description:"The second letter"}
];


d3.select("body")
  .selectAll(null)
  .data(data)
  .enter()
  .each(function(d) {
	var selection = d3.select(this);
	
	// append siblings:
	selection.append("dt")
	  .html(function(d) { return d.name; });
	selection.append("dd")
	  .html(function(d) { return d.description; })
    
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>


但是,也许更优雅的选择是深入研究 d3.selection() 并使用它来给我们一些新的行为。下面我添加了一个selection.appendSibling()方法,该方法允许您在选择中的每个项目的正下方附加一个配对的兄弟元素:

d3.selection.prototype.appendSibling = function(type) {
    var siblings =  this.nodes().map(function(n) {
        return n.parentNode.insertBefore(document.createElement(type), n.nextSibling);
    })
    return d3.selectAll(siblings).data(this.data());
}

它获取选择中的每个节点,创建一个指定类型的新配对兄弟节点(每个都紧跟在 DOM 中的原始节点之后),然后将新节点放入 d3 选择中并绑定数据。这允许您将方法链接到其上以设置元素的样式等,并允许您访问绑定的数据。请参阅下面的实际操作:

// modify d3.selection so that we can append a sibling
d3.selection.prototype.appendSibling = function(type) {
  var siblings =  this.nodes().map(function(n) {
    return n.parentNode.insertBefore(document.createElement(type), n.nextSibling);
  })
  return d3.selectAll(siblings).data(this.data());
}

var data = [
    {name:"a",description:"The first letter"},
    {name:"b",description:"The second letter"}
    ];

d3.select("body")
  .selectAll(null)
  .data(data)
  .enter()
  .append("dt")
  .html(function(d) { return d.name; })
  .appendSibling("dd")  // append siblings
  .html(function(d) { return d.description; })  // modify the siblings
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>

当然,将兄弟姐妹放在单独的选择中可能是明智的,这样您就可以管理每个兄弟姐妹的更新/进入/退出等。

此方法很容易应用于您的示例,这是一个嵌套解决方案,它使用您期望的结构数据和 appendSibling 方法:

// modify d3.selection so that we can append a sibling
d3.selection.prototype.appendSibling = function(type) {
  var siblings =  this.nodes().map(function(n) {
    return n.parentNode.insertBefore(document.createElement(type), n.nextSibling);
  })
  return d3.selectAll(siblings).data(this.data());
}

var data = [
 {volumeInfo: {"a":1,"b":2,"c":3}},
 {volumeInfo: {"α":1,"β":2}}
]

var items = d3.select("body")
    .append('ol')
    .selectAll('li')
    .data(data)
    .enter()
    .append('li')
    .append('dl')
    .selectAll()
    .data(d=>Object.entries(d.volumeInfo)).enter();
  
var dt = items.append("dt")
  .text(function(d) { return d[0]; })
  
var dd = dt.appendSibling("dd")
  .text(function(d) { return d[1]; })
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>


推荐阅读