具有多个子项的基准继承

时间:2019-05-10 10:16:46

标签: javascript d3.js

如果从父选择中使用select从其数据绑定中选择了

d3.js,则该子元素将继承子元素。如何对多个子对象执行相同的方法-即在父对象上使用selectAll,并将数据传播到该子对象选择中?我尝试在子项选择上使用datum(function(d) {return d;}),但是似乎只能使用一次,并且在尝试更新时由于某种原因会返回旧数据。

我可以通过为每个孩子循环使用select来解决此问题,但是我很乐意找到一种更优雅的方法。

在这个小提琴中,左边的不是有效的,而是所需的解决方案,右边是有效的但较难看的解决方案。按下按钮以查看不同的行为

var oldData = [{
  x: 0,
  y: 0
}, {
  x: 10,
  y: 0
}];
var newData = [{
  x: 0,
  y: 0
}, {
  x: 10,
  y: -5
}];

var lineGen = d3.line()
  .x(function(d) {
    return d.x;
  })
  .y(function(d) {
    return d.y;
  });

function case1(data) {
  var sel = d3.select('g.c1')
    .selectAll('g.connection')
    .data(data);
  sel.exit().remove();

  var selEnter = sel.enter()
    .append('g').classed('connection', true);
  selEnter.append('path').classed('real', true);
  selEnter.append('path').classed('mouse-capture', true);
  selEnter.merge(sel)
    .selectAll('path')
    .datum(function(d) {
      return d;
    })
    .attr('d', function(d) {
      return lineGen(d);
    });
}

function case2(data) {
  var sel = d3.select('g.c2')
    .selectAll('g.connection')
    .data(data);
  sel.exit().remove();

  var selEnter = sel.enter()
    .append('g').classed('connection', true);
  selEnter.append('path').classed('real', true);
  selEnter.append('path').classed('mouse-capture', true);
  sel = selEnter.merge(sel);
  sel.select('path.real')
    .attr('d', function(d) {
      return lineGen(d);
    });
  sel.select('path.mouse-capture')
    .attr('d', function(d) {
      return lineGen(d);
    });
}

function setNewData() {
  case1([newData]);
  case2([newData]);
}

var svg = d3.select("body").append("svg")
  .attr("width", 400)
  .attr("height", 100)
  .attr('viewBox', '-5 -10 40 20');

svg.append('g')
  .classed('c1', true);

svg.append('g')
  .classed('c2', true)
  .attr('transform', 'translate(20,0)');

case1([oldData]);
case2([oldData]);
body {
  margin: 0;
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
}

path.real {
  stroke-width: 1px;
  stroke: black;
}

path.mouse-capture {
  stroke-width: 5px;
  stroke: black;
  opacity: 0;
}

g:hover path.real {
  stroke: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<!DOCTYPE html>

<head>
  <meta charset="utf-8">
</head>

<body>
  <button onclick="setNewData()">new data</button>
</body>

1 个答案:

答案 0 :(得分:2)

很遗憾,除非您更改了d3-selection源代码,否则您将无法实现所需的功能。

如您所知,selectAllselect不同,它不会传播数据。为了让其他读者也理解这个问题,以下是我制作的表格,该表格总结了selectselectAll之间的区别:

+------------------+----------------------------------+----------------------------+
| Method           |              select()            |         selectAll()        |
+------------------+----------------------------------+----------------------------+
| Selection        | selects the first element        | selects all elements that  |
|                  | that matches the selector string | match the selector string  |
+------------------+----------------------------------+----------------------------+
| Grouping         | Does not affect grouping         | Affects grouping           |
+------------------+----------------------------------+----------------------------+
| Data propagation | Propagates data                  | Doesn't propagate data     |
+------------------+----------------------------------+----------------------------+

因此,您的function case1方法是在艰辛的环境中,因为:

  • 如果单独使用selectAll,它将不会传播数据;
  • 如果将selectAlldatum一起使用,它将只访问所选元素的旧基准(如您所发现的那样);
  • 如果您将selectAlldata一起使用,则返回原点 包裹在一个数组中,它将仅返回一次数据 (在您的情况下,仅针对第一个路径),这是预期的,因为它是组的父数据。如果将console.log(i)放在datum(d,i)内,则会看到i始终是0

但是,出于好奇, ,它可以在function case1中完成您想要的操作,但是比function case2丑陋(顺便说一下,这是惯用的D3):直接从父级获取数据。

赞:

.datum(function(d) {
    return d3.select(this.parentNode).datum();
})

这是演示:

var oldData = [{
  x: 0,
  y: 0
}, {
  x: 10,
  y: 0
}];
var newData = [{
  x: 0,
  y: 0
}, {
  x: 10,
  y: -5
}];

var lineGen = d3.line()
  .x(function(d) {
    return d.x;
  })
  .y(function(d) {
    return d.y;
  });

function case1(data) {
  var sel = d3.select('g.c1')
    .selectAll('g.connection')
    .data(data);
  sel.exit().remove();

  var selEnter = sel.enter()
    .append('g').classed('connection', true);
  selEnter.append('path').classed('real', true);
  selEnter.append('path').classed('mouse-capture', true);
  selEnter.merge(sel)
    .selectAll('path')
    .datum(function(d) {
      return d3.select(this.parentNode).datum();
    })
    .attr('d', function(d) {
      return lineGen(d);
    });
}

function case2(data) {
  var sel = d3.select('g.c2')
    .selectAll('g.connection')
    .data(data);
  sel.exit().remove();

  var selEnter = sel.enter()
    .append('g').classed('connection', true);
  selEnter.append('path').classed('real', true);
  selEnter.append('path').classed('mouse-capture', true);
  sel = selEnter.merge(sel);
  sel.select('path.real')
    .attr('d', function(d) {
      return lineGen(d);
    });
  sel.select('path.mouse-capture')
    .attr('d', function(d) {
      return lineGen(d);
    });
}

function setNewData() {
  case1([newData]);
  case2([newData]);
}

var svg = d3.select("body").append("svg")
  .attr("width", 400)
  .attr("height", 100)
  .attr('viewBox', '-5 -10 40 20');

svg.append('g')
  .classed('c1', true);

svg.append('g')
  .classed('c2', true)
  .attr('transform', 'translate(20,0)');

case1([oldData]);
case2([oldData]);
body {
  margin: 0;
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
}

path.real {
  stroke-width: 1px;
  stroke: black;
}

path.mouse-capture {
  stroke-width: 5px;
  stroke: black;
  opacity: 0;
}

g:hover path.real {
  stroke: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<!DOCTYPE html>

<head>
  <meta charset="utf-8">
</head>

<body>
  <button onclick="setNewData()">new data</button>
</body>