Переход градиентной заливки одного узла среди многих - PullRequest
2 голосов
/ 23 марта 2019

Допустим, у меня есть SVG со структурой, подобной этой:

<svg>
    <defs>
        <linearGradient id="gradient-red">...</linearGradient>
        <linearGradient id="gradient-blue">...</linearGradient>
    </defs>
    <g class="node">
        <circle r="50" style="fill: url('#gradient-red');"></circle>
    </g>
    <g class="node">
        <circle r="100" style="fill: url('#gradient-red');"></circle>
    </g>
    <g class="node">
        <circle r="150" style="fill: url('#gradient-red');"></circle>
    </g>
    <g class="node">
        <circle r="200" style="fill: url('#gradient-red');"></circle>
    </g>
    <g class="node">
        <circle r="250" style="fill: url('#gradient-red');"></circle>
    </g>
</svg>

Теперь у меня есть пять кругов с красноватыми градиентами.Я понимаю, как изменить цвет выбранного узла - я просто нацеливаю его (через d3.select) и меняю его стиль на 'fill', 'url("#gradient-blue").Но как бы я пошел по поводу перехода градиентной заливки от красного к синему для этого одного узла?

Что-то вроде этого не приводит к анимации / переходу и вместо этого вызываетмгновенный обмен цвета:

d3.transition().duration(1000)
    .tween('start', () => {
        let test = d3.select(currentTarget);
        test.transition().duration(1000).style('fill', 'url("#gradient-blue")');

И если бы я должен был перевести stop-color самих градиентов, он изменит всех узлов / окружностей (потому чтовы меняете <defs>).

Что я делаю не так?

1 Ответ

3 голосов
/ 23 марта 2019

Интерполяция перехода

В D3 переход в основном интерполирует начальное значение к конечному значению.Это может быть легко продемонстрировать, если мы интерполируем числа.Например, давайте перейдем от 50 к 2000:

const interpolator = d3.interpolate(50, 2000);
d3.range(0, 1.05, 0.05).forEach(function(d) {
  console.log(interpolator(d))
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

Мы также можем интерполировать строки:

const interpolator = d3.interpolate("March, 2000", "March, 2020");
d3.range(0, 1.05, 0.05).forEach(function(d) {
  console.log(interpolator(d))
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

Проблема

Теперь давайте посмотрим на ваш случай: вы хотите интерполировать из этого:

  • url("#gradient-red")

На это:

  • url("#gradient-blue")

Каковы здесь возможные промежуточные звенья?Вы видите, что это невозможно?Вот доказательство:

const interpolator = d3.interpolate("url(#gradient-red)", "url(#gradient-blue)");
d3.range(0, 1.1, 0.1).forEach(function(d) {
  console.log(interpolator(d))
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

Как видите, самая первая интерполяция мгновенно приведет к конечному значению.

Возможные решения

Наиболее очевидным решением является интерполяция стоп-цвета.Однако, как вы только что обнаружили, это изменит градиент всех кругов.

Итак, наивное исправление создает несколько градиентов, по одному для каждого круга, с уникальными идентификаторами.Хотя это может быть подходящим решением для 3 или 4 кругов, это явно не умное решение, если у вас есть десятки или сотни элементов.

Это, как говорится, мое предложение:

  1. Создайте временный градиент, давайте присвоим ему идентификатор #gradient-temporary, как красный.
  2. Затем, когда вы выбираете (или фильтруете его каким-либо образом) круг, изменяете его заливку с "url(#gradient-red)" на "url(#gradient-temporary)".Это изменение является немедленным, на экране не видно никакого эффекта.
  3. Выполните переход на стоп-цвете этого временного градиента.
  4. Когда переход завершится, измените заливку круга с "url(#gradient-temporary)"до "url(#gradient-blue)".Опять же, это немедленно.Кроме того, измените цвет остановки временного градиента обратно на красный.

Таким образом, вы можете иметь сотни кругов, но вам просто нужно 3 градиента для их перехода.

Здесьдемонстрация с этим подходом, нажмите на каждый кружок, чтобы перевести его:

const circles = d3.selectAll("circle");
circles.on("click", function() {
  const element = this;
  d3.select(element).style("fill", "url(#gradient-temporary)");
  d3.select("#gradient-temporary").select("stop:nth-child(2)")
    .transition()
    .duration(1000)
    .style("stop-color", "rgb(0,0,255)")
    .on("end", function() {
      d3.select(element).style("fill", "url(#gradient-blue)");
      d3.select("#gradient-temporary").select("stop:nth-child(2)")
        .style("stop-color", "rgb(255,0,0)")
    })
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg>
    <defs>
        <linearGradient id="gradient-red" x1="0%" y1="0%" x2="100%" y2="0%">
            <stop offset="0%" style="stop-color:rgb(211,211,211);stop-opacity:1" />
            <stop offset="100%" style="stop-color:rgb(255,0,0);stop-opacity:1" />
        </linearGradient>
        <linearGradient id="gradient-temporary" x1="0%" y1="0%" x2="100%" y2="0%">
            <stop offset="0%" style="stop-color:rgb(211,211,211);stop-opacity:1" />
            <stop offset="100%" style="stop-color:rgb(255,0,0);stop-opacity:1" />
        </linearGradient>
        <linearGradient id="gradient-blue" x1="0%" y1="0%" x2="100%" y2="0%">
            <stop offset="0%" style="stop-color:rgb(211,211,211);stop-opacity:1" />
            <stop offset="100%" style="stop-color:rgb(0,0,255);stop-opacity:1" />
        </linearGradient>
    </defs>
    <g class="node">
        <circle r="20" cx="20" cy="70" style="fill: url('#gradient-red');"></circle>
    </g>
    <g class="node">
        <circle r="20" cx="80" cy="70" style="fill: url('#gradient-red');"></circle>
    </g>
    <g class="node">
        <circle r="20" cx="140" cy="70" style="fill: url('#gradient-red');"></circle>
    </g>
    <g class="node">
        <circle r="20" cx="200" cy="70" style="fill: url('#gradient-red');"></circle>
    </g>
    <g class="node">
        <circle r="20" cx="260" cy="70" style="fill: url('#gradient-red');"></circle>
    </g>
</svg>
...