运行 为每个过渡帧自定义计算

Run custom calculation for each frame of a transition

作为 d3.treemap() 的一部分:

在一个 svg 元素中嵌套了 groups。每组有一个 recttext 以及一个 tspan 作为 child。 像这样:

<g><rect></rect><text><tspan>Text here</tspan></text></g>

treemap 是动画的,随着值的变化,矩形的大小也会发生变化。要求 tspan 中的文本始终居中。文本元素的中心计算如下:

const calculateCenterOfTextInRectangle = (tspanNode) => {
const rectangleNode = tspanNode.parentElement.previousSibling.previousSibling; // tspan is in text element and 2 nodes above is the rectangle
const centerX = (rectangleNode.getAttribute('width') / 2) - (tspanNode.getComputedTextLength() / 2);
const centerY = (rectangleNode.getAttribute('height') / 2) + (19 / 2); // 19 is the font-size in pixel
return {centerX: centerX,
    centerY: centerY}; };

如果矩形的 heightwidth 不变,则此方法工作正常。但是用户可以选择不同的选择来改变矩形的大小并触发这个 d3.transition():

treemap(root.sum(sum)); // new values
cell.transition()
    .duration(750)
    .attr('transform', (d) => 'translate(' + d.x0 + ',' + d.y0 + ')')
    .selectAll('rect')
    .attr('width', (d) => d.x1 - d.x0)
    .attr('height', (d) => d.y1 - d.y0);

这不会改变需要改变的文本的位置 - 所以作为一个临时解决方案,我想出了这个:

setInterval(
        () => {
            cell.selectAll('tspan')
                .attr('x', function() { return calculateCenterOfTextInRectangle(this).centerX; }) // center x and y. Not using Es6 function because of this context which is the tspan element.
                .attr('y', function() { return calculateCenterOfTextInRectangle(this).centerY; });
        },
        0.1
    );

问题是使用 setInterval 方法时 Web 应用程序的响应速度较慢。我可以不使用setIntervaltransition函数中实现文本居中吗?

在过渡中使用 calculateCenterOfTextInRectangle 的问题在于,即使您在为矩形设置新值后调用它,您也会得到 initial值,这是他预期的行为。

让我们在这个演示中看到它,检查控制台:

var svg = d3.select("svg");
var g = svg.append("g");
var rect = g.append("rect")
  .attr("stroke", "black")
  .attr("fill", "white")
  .attr("x", 0)
  .attr("y", 0)
  .attr("width", 50)
  .attr("height", 150);

var text = g.append("text")
  .text("foo")
  .attr("x", function() {
    return calculateCenterOfTextInRectangle(this).centerX
  })
  .attr("y", function() {
    return calculateCenterOfTextInRectangle(this).centerY
  });

var gTransition = g.transition()
  .duration(5000);

gTransition.select("rect")
  .attr("width", 150)
  .attr("height", 40);

gTransition.select("text")
  .attr("x", function() {
    console.log(calculateCenterOfTextInRectangle(this).centerX)
    return calculateCenterOfTextInRectangle(this).centerX
  })
  .attr("y", function() {
    console.log(calculateCenterOfTextInRectangle(this).centerY)
    return calculateCenterOfTextInRectangle(this).centerY
  })

function calculateCenterOfTextInRectangle(tspanNode) {
  const rectangleNode = tspanNode.previousSibling;
  const centerX = (rectangleNode.getAttribute('width') / 2) - (tspanNode.getComputedTextLength() / 2);
  const centerY = (rectangleNode.getAttribute('height') / 2) + (19 / 2); // 19 is the font-size in pixel
  return {
    centerX: centerX,
    centerY: centerY
  };
};
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg></svg>

这里有不同的解决方案。计算机密集度较低的可能只是将新的 xy 值传递给文本,您可以轻松计算这些值(因为您有矩形的新值)。

但是,如果您想使用过渡本身,您可以使用 tween(这更需要计算机),如下所示:

transition.select("text")
    .tween("positioning", function() {
        var self = this;
        var rectangleNode = this.previousSibling;
        return function() {
            d3.select(self)
                .attr("x", (rectangleNode.getAttribute('width') / 2) - (self.getComputedTextLength() / 2))
                .attr("y", (rectangleNode.getAttribute('height') / 2) + (19 / 2))
        }
    })

这是一个演示:

var svg = d3.select("svg");
var g = svg.append("g");
var rect = g.append("rect")
  .attr("stroke", "black")
  .attr("fill", "white")
  .attr("x", 0)
  .attr("y", 0)
  .attr("width", 50)
  .attr("height", 150);
var text = g.append("text")
  .attr("x", function() {
    return calculateCenterOfTextInRectangle(this).centerX
  })
  .attr("y", function() {
    return calculateCenterOfTextInRectangle(this).centerY
  })
  .text("foo");

var gTransition = g.transition()
  .duration(5000);

gTransition.select("rect")
  .attr("width", 150)
  .attr("height", 40);

gTransition.select("text")
  .tween("positioning", function() {
    var self = this;
    var rectangleNode = this.previousSibling;
    return function() {
      d3.select(self)
        .attr("x", (rectangleNode.getAttribute('width') / 2) - (self.getComputedTextLength() / 2))
        .attr("y", (rectangleNode.getAttribute('height') / 2) + (19 / 2))
    }
  })

function calculateCenterOfTextInRectangle(tspanNode) {
  const rectangleNode = tspanNode.previousSibling;
  const centerX = (rectangleNode.getAttribute('width') / 2) - (tspanNode.getComputedTextLength() / 2);
  const centerY = (rectangleNode.getAttribute('height') / 2) + (19 / 2); // 19 is the font-size in pixel
  return {
    centerX: centerX,
    centerY: centerY
  };
};
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg></svg>