Флеш d3 v4 переход

Кто-нибудь знает способ "очистить" переход. У меня есть переход, определенный следующим образом:

this.paths.attr('transform', null)
  .transition()
  .duration(this.duration)
  .ease(d3.easeLinear)
  .attr('transform', 'translate(' + this.xScale(translationX) + ', 0)')

Я знаю, что могу сделать

this.paths.interrupt();

чтобы остановить переход, но это не заканчивает мою анимацию. Я хотел бы иметь возможность "очистить" переход, который немедленно завершит анимацию.

2 ответа

Решение

Если я правильно понимаю (и мог бы этого не делать), для этого не существует готового решения, если не пойти немного под капот. Тем не менее, я считаю, что вы могли бы создать функциональность относительно простым способом, если selection.interrupt() имеет форму, которую вы ищете.

Для этого вам нужно будет создать новый метод для выбора d3, который обращается к данным перехода (расположен по адресу: selection.node().__transition). Данные перехода включают в себя данные о подростках, таймере и других деталях перехода, но наиболее простым решением было бы установить длительность на ноль, что заставит переход завершиться и перевести его в конечное состояние:

Переменная данных __transition может иметь пустые слоты (с переменным числом), что может вызвать горе в Firefox (насколько я знаю, при использовании циклов forEach), поэтому я использовал подход с ключами, чтобы получить непустые значения. слот, который содержит переход.

d3.selection.prototype.finish = function() {
    var slots = this.node().__transition;
    var keys = Object.keys(slots);
    keys.forEach(function(d,i) {
        if(slots[d]) slots[d].duration = 0;
    })  
}

Если вы работаете с задержками, вы также можете вызвать обратный вызов таймера с помощью чего-то вроде: if(slots[d]) slots[d].timer._call();, так как установка задержки на ноль не влияет на переход.

Используя этот блок кода вы звоните selection.finish() который заставит переход в его конечное состояние, щелкните по кругу, чтобы вызвать метод:

d3.selection.prototype.finish = function() {
  var slots = this.node().__transition;
  var keys = Object.keys(slots);
  keys.forEach(function(d,i) {
    if(slots[d]) slots[d].timer._call(); 
  }) 
}
 
var svg = d3.select("body")
   .append("svg")
   .attr("width", 500)
   .attr("height", 500);
 
var circle = svg.selectAll("circle")
   .data([1,2,3,4,5,6,7,8])
   .enter()
   .append("circle")
   .attr("cx",50)
   .attr("cy",function(d) { return d * 50 })
   .attr("r",20)
   .on("click", function() {  d3.select(this).finish() })
 
circle
   .transition()
   .delay(function(d) { return d * 500; })
   .duration(function(d) { return d* 5000; })
   .attr("cx", 460)
   .on("end", function() {
      d3.select(this).attr("fill","steelblue"); // to visualize end event
   })
 
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.12.0/d3.min.js"></script>

Конечно, если вы хотите сохранить метод d3-ish, верните выделение, чтобы после него можно было включить дополнительные методы. А для полноты вы захотите убедиться, что есть переход к финишу. С этими дополнениями новый метод может выглядеть примерно так:

d3.selection.prototype.finish = function() {
  // check if there is a transition to finish:
  if (this.node().__transition) {
      // if there is transition data in any slot in the transition array, call the timer callback:
      var slots = this.node().__transition;
      var keys = Object.keys(slots);
      keys.forEach(function(d,i) {
        if(slots[d]) slots[d].timer._call(); 
   })   
  }
  // return the selection:
  return this;
}

Вот пример этой более полной реализации.


Выше для версий 4 и 5 D3. Воспроизвести это в версии 3 немного сложнее, так как таймеры и переходы были немного переработаны для версии 4. В третьей версии они немного менее дружественны, но поведение может быть достигнуто с небольшими изменениями. Для полноты, вот блок примера d3v3.

Эндрю ответ великий. Однако ради любопытства я считаю, что это можно сделать без расширения прототипов, используя .on("interrupt" как слушатель.

Здесь я беззастенчиво копирую код Эндрю для переходов и этот ответ для получения целевого атрибута.

selection.on("click", function() {
    d3.select(this).interrupt()
})

transition.on("interrupt", function() {
    var elem = this;
    var targetValue = d3.active(this)
        .attrTween("cx")
        .call(this)(1);
    d3.select(this).attr("cx", targetValue)
})

Вот демо:

var svg = d3.select("svg")

var circle = svg.selectAll("circle")
  .data([1, 2, 3, 4, 5, 6, 7, 8])
  .enter()
  .append("circle")
  .attr("cx", 50)
  .attr("cy", function(d) {
    return d * 50
  })
  .attr("r", 20)
  .on("click", function() {
    d3.select(this).interrupt()
  })

circle
  .transition()
  .delay(function(d) {
    return d * 500;
  })
  .duration(function(d) {
    return d * 5000;
  })
  .attr("cx", 460)
  .on("interrupt", function() {
    var elem = this;
    var targetValue = d3.active(this)
      .attrTween("cx")
      .call(this)(1);
    d3.select(this).attr("cx", targetValue)
  })
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="500" height="500"></svg>

PS: в отличие от ответа Эндрю, так как я использую d3.active(node) здесь щелчок работает, только если переход уже начался.

Другие вопросы по тегам