如何让SVG组遵循圆形渐变

时间:2017-05-17 14:26:30

标签: javascript d3.js svg

我试图让一个正在排水的圆圈动画,并且效果很好。但是我想要一个"标记"跟随排水水平,我很难理解如何做到这一点。

我已经在这篇文章中嵌入了一个示例,其中圆圈填充动画,数字动画。

最终状态可以在这里看到:

Final state of the animation

问题是;我想放置" XXX使用"基于排水百分比的标记。但我很难找到如何实现这一点。

所以它必须上下移动,但也要根据百分比左右移动。

我的代码如下:



function foo({x = 10}) {
  return x;
}

foo(); // TypeError: can't convert undefined to object
foo({}); // 10

const usedAmount = 200;
const totalAmount = 400;

const radius = 120;

const valuePercent = (usedAmount / totalAmount) * 100;

var svg = d3.select("svg"),
    width = +svg.attr("width"),
    height = +svg.attr("height"),
    g = svg.append("g").attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");

 let grad = svg
   .append("defs")
     .append("linearGradient")
       .attr("id", "grad")
       .attr("x1", "0%")
       .attr("x2", "0%")
       .attr("y1", "0%")
       .attr("y2", "0%");

grad.append("stop").attr("offset", "1%").style("stop-color", '#000');
grad.append("stop").attr("offset", "1%").style("stop-color", '#ccc');

let arc = d3.arc()
  .innerRadius( radius - 40 )
  .outerRadius( radius )
  .endAngle(2 * Math.PI)
  .startAngle(0 * Math.PI);

let cutout = svg.select('defs')
  .append('clipPath')
  .attr('clip-rule', 'evenodd')
  .attr('id', 'cutout')
  .append("path")
  .attr('transform', 'translate(' + radius + ',' + radius + ')')
  .attr('d', arc)
  .attr('clip-rule', 'evenodd')
  .attr('fill', '#ccc');

svg.append("circle")
  .attr("cx", radius)
  .attr("cy", radius)
  .attr("r", radius)
  .attr("clip-path", "url(#cutout)") // Apply the mask
  .attr("fill", "url(#grad)");

grad
  .transition()
  .duration(3000)
  .ease(d3.easeQuad)
  .delay(300)
  .attr("y1", valuePercent + 1 + '%');

var marker = svg.append('g')
  .attr('class', 'gauge__fillup__follow')
  .attr("transform", "translate(" + 200 + "," + 50 + ")");

marker.append("rect")
  .attr("x", 10)
  .attr("y", 6)
  .attr("fill", "#000")
  .attr("width", 35)
  .attr("height", 3);

marker.append('svg:text')
  .attr('class', 'label')
  .attr('z-index', '4')
  .attr('x', 50)
  .attr('y', 0)
  .attr('dy', 12)
  .attr('text-anchor', 'left')
  .datum({textContent: ''})
  .text(200)
  .transition()
  .duration(3000)
  .delay(300)
  .ease(d3.easeQuad)
  .tween("text", function(d) {
  const i = d3.interpolate(0, this.textContent, d);
  return (t) => {
    d3.select(this).text(Math.round(i(t)));
  };
});

marker.append('svg:text')
  .attr('class', 'label')
  .attr('color', 'white')
  .attr('z-index', '4')
  .attr('x', 50)
  .attr('y', 16)
  .attr('dy', 12)
  .attr('text-anchor', 'left')
  .text('used');




结束此结果:https://codepen.io/Saturate/pen/BROzBe

2 个答案:

答案 0 :(得分:4)

您只需要将组翻译相同的数量:

marker.transition()
    .duration(3000)
    .ease(d3.easeQuad)
    .delay(300)
    .attr("transform", "translate(" + 220 + "," 
        + ((usedAmount / totalAmount) * (radius * 2)) + ")");

以下是演示:

const usedAmount = 200;
const totalAmount = 400;

const radius = 120;

const valuePercent = (usedAmount / totalAmount) * 100;

var svg = d3.select("svg"),
    width = +svg.attr("width"),
    height = +svg.attr("height"),
    g = svg.append("g").attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");

let grad = svg
    .append("defs")
    .append("linearGradient")
    .attr("id", "grad")
    .attr("x1", "0%")
    .attr("x2", "0%")
    .attr("y1", "0%")
    .attr("y2", "0%");

grad.append("stop").attr("offset", "1%").style("stop-color", '#000');
grad.append("stop").attr("offset", "1%").style("stop-color", '#ccc');

let arc = d3.arc()
    .innerRadius(radius - 40)
    .outerRadius(radius)
    .endAngle(2 * Math.PI)
    .startAngle(0 * Math.PI);

let cutout = svg.select('defs')
    .append('clipPath')
    .attr('clip-rule', 'evenodd')
    .attr('id', 'cutout')
    .append("path")
    .attr('transform', 'translate(' + radius + ',' + radius + ')')
    .attr('d', arc)
    .attr('clip-rule', 'evenodd')
    .attr('fill', '#ccc');

svg.append("circle")
    .attr("cx", radius)
    .attr("cy", radius)
    .attr("r", radius)
    .attr("clip-path", "url(#cutout)") // Apply the mask
    .attr("fill", "url(#grad)");

grad
    .transition()
    .duration(3000)
    .ease(d3.easeQuad)
    .delay(300)
    .attr("y1", valuePercent + 1 + '%');

var marker = svg.append('g')
    .attr('class', 'gauge__fillup__follow')
    .attr("transform", "translate(" + 220 + "," + 0 + ")");

marker.append("rect")
    .attr("x", 10)
    .attr("y", 1)
    .attr("fill", "#000")
    .attr("width", 35)
    .attr("height", 3);

marker.append('svg:text')
    .attr('class', 'label')
    .attr('z-index', '4')
    .attr('x', 50)
    .attr('y', -6)
    .attr('dy', 12)
    .attr('text-anchor', 'left')
    .datum({
        textContent: ''
    })
    .text(200)
    .transition()
    .duration(3000)
    .delay(300)
    .ease(d3.easeQuad)
    .tween("text", function(d) {
        const i = d3.interpolate(0, this.textContent, d);
        return (t) => {
            d3.select(this).text(Math.round(i(t)));
        };
    });

marker.append('svg:text')
    .attr('class', 'label')
    .attr('color', 'white')
    .attr('z-index', '4')
    .attr('x', 50)
    .attr('y', 10)
    .attr('dy', 12)
    .attr('text-anchor', 'left')
    .text('used');

marker.transition()
    .duration(3000)
    .ease(d3.easeQuad)
    .delay(300)
    .attr("transform", "translate(" + 220 + "," + ((usedAmount / totalAmount )*(radius*2)) + ")");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.min.js"></script>
<svg height="500" width="500"></svg>

编辑:以下是attrTween的翻译,从上到下,从左到右:

marker.transition()
    .duration(3000)
    .ease(d3.easeQuad)
    .delay(300)
    .attrTween("transform", function() {
        return function(t) {
            return "translate(" + (radius * (1 + (Math.sin(Math.PI / 2 * t)))) + "," 
            + (((usedAmount / totalAmount) * (radius * 2)) * (1 - (Math.cos(Math.PI / 2 * t)))) + ")"
        }
    });

以下是演示:

const usedAmount = 200;
const totalAmount = 400;

const radius = 120;

const valuePercent = (usedAmount / totalAmount) * 100;

var svg = d3.select("svg"),
  width = +svg.attr("width"),
  height = +svg.attr("height"),
  g = svg.append("g").attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");

let grad = svg
  .append("defs")
  .append("linearGradient")
  .attr("id", "grad")
  .attr("x1", "0%")
  .attr("x2", "0%")
  .attr("y1", "0%")
  .attr("y2", "0%");

grad.append("stop").attr("offset", "1%").style("stop-color", '#000');
grad.append("stop").attr("offset", "1%").style("stop-color", '#ccc');

let arc = d3.arc()
  .innerRadius(radius - 40)
  .outerRadius(radius)
  .endAngle(2 * Math.PI)
  .startAngle(0 * Math.PI);

let cutout = svg.select('defs')
  .append('clipPath')
  .attr('clip-rule', 'evenodd')
  .attr('id', 'cutout')
  .append("path")
  .attr('transform', 'translate(' + radius + ',' + radius + ')')
  .attr('d', arc)
  .attr('clip-rule', 'evenodd')
  .attr('fill', '#ccc');

svg.append("circle")
  .attr("cx", radius)
  .attr("cy", radius)
  .attr("r", radius)
  .attr("clip-path", "url(#cutout)") // Apply the mask
  .attr("fill", "url(#grad)");

grad
  .transition()
  .duration(3000)
  .ease(d3.easeQuad)
  .delay(300)
  .attr("y1", valuePercent + 1 + '%');

var marker = svg.append('g')
  .attr('class', 'gauge__fillup__follow')
  .attr("transform", "translate(" + (radius) + "," + 0 + ")");

marker.append("rect")
  .attr("x", 10)
  .attr("y", 1)
  .attr("fill", "#000")
  .attr("width", 35)
  .attr("height", 3);

marker.append('svg:text')
  .attr('class', 'label')
  .attr('z-index', '4')
  .attr('x', 50)
  .attr('y', -6)
  .attr('dy', 12)
  .attr('text-anchor', 'left')
  .datum({
    textContent: ''
  })
  .text(200)
  .transition()
  .duration(3000)
  .delay(300)
  .ease(d3.easeQuad)
  .tween("text", function(d) {
    const i = d3.interpolate(0, this.textContent, d);
    return (t) => {
      d3.select(this).text(Math.round(i(t)));
    };
  });

marker.append('svg:text')
  .attr('class', 'label')
  .attr('color', 'white')
  .attr('z-index', '4')
  .attr('x', 50)
  .attr('y', 10)
  .attr('dy', 12)
  .attr('text-anchor', 'left')
  .text('used');

marker.transition()
  .duration(3000)
  .ease(d3.easeQuad)
  .delay(300)
  .attrTween("transform", function() {
    return function(t) {
      return "translate(" + (radius * (1 + (Math.sin(Math.PI / 2 * t)))) + "," + (((usedAmount / totalAmount) * (radius * 2)) * (1 - (Math.cos(Math.PI / 2 * t)))) + ")"
    }
  });
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.min.js"></script>
<svg height="500" width="500"></svg>

答案 1 :(得分:1)

你的问题让我意识到我并不像我想的那样理解三角学,所以我对它进行了尝试。一旦我学会了pi和sine的基本原理,答案就变得非常明确了。

我阅读的精彩文章引导我回答:https://betterexplained.com/articles/intuitive-understanding-of-sine-waves/

https://jsfiddle.net/zk0wsq5a/2/

marker.transition()
  .duration(3000)
  .ease(d3.easeQuad)
  .delay(300)
  .attrTween("transform", function() {
    return function(t) {
        let distance = Math.PI * t * (usedAmount / totalAmount)
        let x = radius + (radius * Math.sin(distance))
        let y = radius * (1-Math.cos(distance))
      return `translate(${x} ,${y})`
    }
  });