animation - 如何为元素设置动画以跟随 d3 中的圆弧质心?

标签 animation d3.js transition pie-chart

我有一个简单的饼图,其标签如下:

var data = [{
  label: 'Star Wars',
  instances: 20
}, {
  label: 'Lost In Space',
  instances: 32
}, {
  label: 'the Boston Pops',
  instances: 80
}, {
  label: 'Indiana Jones',
  instances: 74
}, {
  label: 'Harry Potter',
  instances: 23
}, {
  label: 'Jaws',
  instances: 10
}, {
  label: 'Lincoln',
  instances: 15
}];

svg = d3.select("svg");
canvas = d3.select("#canvas");
art = d3.select("#art");
labels = d3.select("#labels");

var pie = d3.layout.pie().value(function(d, i) {
    return d.instances;
  })
  .sort(null);

var height = 500,
  width = 500,
  labelRadius = 175;

svg.attr({
  height: height,
  width: width
});

canvas.attr("transform", "translate(" + (width / 2) + "," + (height / 2) + ")");

arc = d3.svg.arc()
  .innerRadius(50)
  .outerRadius(150);

colors = d3.scale.category10();

var path = art.selectAll(".wedge").data(pie(data)).enter().append("path")
  .attr("class", "wedge")
  .attr("d", arc)
  .style("fill", function(d, i) {
    return colors(i);
  })
  .each(function(d) {
    this._current = d;
  });

enteringLabels = labels.selectAll(".label").data(pie(data)).enter();
labelGroups = enteringLabels.append("g").attr("class", "label");
labelGroups.append("circle").attr({
  x: 0,
  y: 0,
  r: 2,
  fill: "#000",
  transform: function(d, i) {
    centroid = arc.centroid(d);
    return "translate(" + arc.centroid(d) + ")";
  },
  'class': "label-circle"
});

textLines = labelGroups.append("line").attr({
  x1: function(d, i) {
    return arc.centroid(d)[0];
  },
  y1: function(d, i) {
    return arc.centroid(d)[1];
  },
  x2: function(d, i) {
    centroid = arc.centroid(d);
    midAngle = Math.atan2(centroid[1], centroid[0]);
    x = Math.cos(midAngle) * labelRadius;
    return x;
  },
  y2: function(d, i) {
    centroid = arc.centroid(d);
    midAngle = Math.atan2(centroid[1], centroid[0]);
    y = Math.sin(midAngle) * labelRadius;
    return y;
  },
  'class': "label-line"
});

textLabels = labelGroups.append("text").attr({
  x: function(d, i) {
    centroid = arc.centroid(d);
    midAngle = Math.atan2(centroid[1], centroid[0]);
    x = Math.cos(midAngle) * labelRadius;
    sign = (x > 0) ? 1 : -1
    labelX = x + (5 * sign)
    return labelX;
  },
  y: function(d, i) {
    centroid = arc.centroid(d);
    midAngle = Math.atan2(centroid[1], centroid[0]);
    y = Math.sin(midAngle) * labelRadius;
    return y;
  },
  'text-anchor': function(d, i) {
    centroid = arc.centroid(d);
    midAngle = Math.atan2(centroid[1], centroid[0]);
    x = Math.cos(midAngle) * labelRadius;
    return (x > 0) ? "start" : "end";
  },
  'class': 'label-text'
}).text(function(d) {
  return d.data.label
});

alpha = 0.5;
spacing = 12;

function relax() {
  again = false;
  textLabels.each(function(d, i) {
    a = this;
    da = d3.select(a);
    y1 = da.attr("y");
    textLabels.each(function(d, j) {
      b = this;
      // a & b are the same element and don't collide.
      if (a == b) return;
      db = d3.select(b);
      // a & b are on opposite sides of the chart and
      // don't collide
      if (da.attr("text-anchor") != db.attr("text-anchor")) return;
      // Now let's calculate the distance between
      // these elements. 
      y2 = db.attr("y");
      deltaY = y1 - y2;

      // Our spacing is greater than our specified spacing,
      // so they don't collide.
      if (Math.abs(deltaY) > spacing) return;

      // If the labels collide, we'll push each 
      // of the two labels up and down a little bit.
      again = true;
      sign = deltaY > 0 ? 1 : -1;
      adjust = sign * alpha;
      da.attr("y", +y1 + adjust);
      db.attr("y", +y2 - adjust);
    });
  });
  // Adjust our line leaders here
  // so that they follow the labels. 
  if (again) {
    labelElements = textLabels[0];
    textLines.attr("y2", function(d, i) {
      labelForLine = d3.select(labelElements[i]);
      return labelForLine.attr("y");
    });
    setTimeout(relax, 20)
  }
}

relax();

d3.selectAll("#randomize")
  .on("click", function() {
    data = _(data).map(function(v) {
      v.instances = Math.floor((Math.random() * 100) + 1);
      return v;
    });

    pie.value(function(d) {
      return d.instances;
    });

    path = path
      .data(pie(data));

    path.transition().duration(750)
      .attrTween("d", function(d) {
        var interpolate = d3.interpolate(this._current, d);
        this._current = interpolate(0);
        return function(t) {
          return arc(interpolate(t));
        };
      });
  });
.label-text {
  alignment-baseline: middle;
  font-size: 12px;
  font-family: arial, helvetica, "sans-serif";
  fill: #393939;
}
.label-line {
  stroke-width: 1;
  stroke: #393939;
}
.label-circle {
  fill: #393939;
}
#randomize {
  position: absolute;
  padding: 10px;
  z-index: 100;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore.js"></script>
<button id="randomize">Randomize</button>
<svg>
  <g id="canvas">
    <g id="art" />
    <g id="labels" /></g>
</svg>

我可以更新/随机化数据以使弧长动画化,但我不知道如何使标签“跟随”弧的质心。我知道我可能需要沿着弧的质心/路径插入标签,但我似乎不知道如何开始。

最佳答案

click 函数中,您需要重置绑定(bind)到各个 DOM 的数据:

//update the groups with the new data
enteringLabels = labels.selectAll(".label").data(pie(data));
//update the circle with the new data
labelGroups = d3.selectAll(".label-circle").data(pie(data));
//update the lines with the new data
textLines = d3.selectAll(".label-line").data(pie(data));
//update the text with the data
textLabels = d3.selectAll(".label-text").data(pie(data));
//for transition   
labelGroups.transition().duration(1000).attr({
  transform: function(d, i) {
    centroid = arc.centroid(d);
    return "translate(" + arc.centroid(d) + ")";
  },
  'class': "label-circle"
});   
//for transition   
textLines.transition().duration(1000).attr({
  x1: function(d, i) {
    return arc.centroid(d)[0];
  },
  y1: function(d, i) {
    return arc.centroid(d)[1];
  },
  x2: function(d, i) {
    centroid = arc.centroid(d);
    midAngle = Math.atan2(centroid[1], centroid[0]);
    x = Math.cos(midAngle) * labelRadius;
    return x;
  },
  y2: function(d, i) {
    centroid = arc.centroid(d);
    midAngle = Math.atan2(centroid[1], centroid[0]);
    y = Math.sin(midAngle) * labelRadius;
    return y;
  },
  'class': "label-line"
});

textLabels.transition().duration(1000).attr({
  x: function(d, i) {
    centroid = arc.centroid(d);
    midAngle = Math.atan2(centroid[1], centroid[0]);
    x = Math.cos(midAngle) * labelRadius;
    sign = (x > 0) ? 1 : -1
    labelX = x + (5 * sign)
    return labelX;
  },
  y: function(d, i) {
    centroid = arc.centroid(d);
    midAngle = Math.atan2(centroid[1], centroid[0]);
    y = Math.sin(midAngle) * labelRadius;
    return y;
  },
  'text-anchor': function(d, i) {
    centroid = arc.centroid(d);
    midAngle = Math.atan2(centroid[1], centroid[0]);
    x = Math.cos(midAngle) * labelRadius;
    return (x > 0) ? "start" : "end";
  },
  'class': 'label-text'
}).text(function(d) {
  return d.data.label
});

工作代码here

关于animation - 如何为元素设置动画以跟随 d3 中的圆弧质心?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38841523/

相关文章:

python - 如何使用 matplotlib 为一组点设置动画?

d3.js - D3 - 如何在手动缩放和平移到国家/地区路径后获得正确的比例和平移原点

javascript - 使用 styleTween 进行带百分比的 d3 过渡

javascript - 如何部分或仅按百分比沿路径过渡

android - 如何更改警报对话框的位置

c# - UWP 动画汉堡图标

html - 移动浏览器上奇怪的 CSS '' 叠加效果''

javascript - 使用 d3.js 堆栈布局的堆积条形图;条形部分并不总是在正确的位置

jQuery 'if' 语句未被识别

html - 当图像穿过一条线时反转图像的颜色?