компактный способ кодирования цепных переходов в D3 - PullRequest
0 голосов
/ 22 сентября 2018

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

orange -> purple -> blue -> yellow;

и следующие цвета ко второму:

blue -> yellow -> orange -> purple.

Я пробовал с приведенным ниже кодом (fiddle здесь ), но он не работает.Какой самый компактный способ добиться этого?

    var svg = d3.select('svg');

    var dataSet = [20, 20];

    var group=svg.append("g");
    var circles = group.selectAll('circle')
    .data(dataSet)
    .enter()
    .append('circle')
    .attr("r",function(d){ return d })
    .attr("cx",function(d, i){ return i * 100 + 50 })
    .attr("cy",50)
    .attr("fill",'black');

    var t1 = d3
    .transition()
    .duration(1000)
    .attr("fill","orange")
    .transition()
    .duration(1000)
    .attr("fill","purple");

    var t2 = d3
    .transition()
    .duration(1000)
    .attr("fill","blue")
    .transition()
    .duration(1000)
    .attr("fill","yellow");

    group.select(":nth-child(1)")
    .transition(t1).transition(t2); 
    group.select(":nth-child(2)")
    .transition(t2).transition(t1); 

Ответы [ 2 ]

0 голосов
/ 22 сентября 2018

Есть множество способов добиться этого.Как отмечено в другом ответе, использование функции для этого сделает ваш код компактным.Лично я склонен использовать конечное событие перехода для запуска следующего перехода из функции перехода.

Общая форма для такого рода функции следующая:

function transition() {
   // ....  optional logic here.
   d3.select(this) // do transition:
     .transition()
     .attr("fill", ... )
     .on("end", transition); // and repeat.
}

Это может бытьвызывается с selection.each(transition)

Один из подходов к управлению текущим цветом / переходом в цикле заключается в использовании пользовательского атрибута.Ниже я использую .attr("i") для отслеживания:

var data = [
  ["orange","purple","blue","yellow"],
  ["blue","yellow","orange","purple"]
];

var svg = d3.select("svg");
var circles = svg.selectAll()
  .data(data)
  .enter()
  .append("circle")
  .attr("r", 20)
  .attr("cx", function(d,i) { return i * 50 + 50; })
  .attr("cy", 50)
  .attr("fill", function(d) { return d[0]; })
  .attr("i",0)
  .each(transition);


// cycle endlessly:
function transition() {
  var selection = d3.select(this);
  // keep track of current value:
  var i = selection.attr("i")
  selection
    .attr("i",i = ++i%4)
    .transition()  
    .duration(1000)
    .attr("fill", function(d) { return d[i] })
    .on("end", transition);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
<svg width="500" height="300"></svg>

Если у вас есть переходы, которые отличаются только по порядку (начальная точка), вы можете немного изменить вышеуказанный подход:

var data = [50,75,100,125,150,175,200,225];
var colors = ["orange","purple","blue","yellow"];

var svg = d3.select("svg");
var circles = svg.selectAll()
  .data(data)
  .enter()
  .append("circle")
  .attr("r", 20)
  .attr("cx", function(d) { return d; })
  .attr("cy", 50)
  .attr("fill", function(d,i) { return colors[i%4]; }) // set start fill
  .attr("i", function(d,i) { return i%4;  })   // record start position.
  .each(transition);

function transition() {
  var selection = d3.select(this);
  var i = selection.attr("i");
  i = ++i%colors.length;
  selection.transition()
      .duration(1000)
      .attr("i",i)
      .attr("fill", colors[i])
      .on("end", transition);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
<svg width="500" height="300"></svg>

Первый - я использую наборы цветов в качестве базовых данных, второй - просто положение, получающее доступ к цвету с атрибутом i


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

Например, у нас может быть свойство с именем color и increment, которое при настройке заливки для каждого перехода (опять же, при условии, что у нас есть массив цветов, и каждый круг начинаетсяв другом месте):

function transition() {
  d3.select(this).transition()
      .duration(1000)
      .attr("fill", function(d) { return colors[++d.color%colors.length]; })
      .on("end", transition);
}

var data = d3.range(8).map(function(d) { return {x: d*25+50}; })

var colors = ["orange","purple","blue","yellow"];

var svg = d3.select("svg");
var circles = svg.selectAll()
  .data(data)
  .enter()
  .append("circle")
  .attr("r", 20)
  .attr("cx", function(d) { return d.x; })
  .attr("cy", 50)
  .attr("fill", function(d,i) { return colors[i%4]; }) // set start fill
  .each(function(d,i) { d.color = i%4; })              // record start position.
  .each(transition);

function transition() {
  d3.select(this).transition()
      .duration(1000)
      .attr("fill", function(d) { return colors[++d.color%colors.length]; })
      .on("end", transition);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
<svg width="500" height="300"></svg>

Если вы хотите, чтобы переходы повторялись x раз или даже заканчивались на одном и том же цвете, мы можем сделать это довольно легко, добавив новое свойствок исходным данным для отслеживания завершенных циклов:

var data = d3.range(8).map(function(d) { return {x: d*25+50}; })

var colors = ["orange","purple","blue","yellow"];

var svg = d3.select("svg");
var circles = svg.selectAll()
  .data(data)
  .enter()
  .append("circle")
  .attr("r", 20)
  .attr("cx", function(d) { return d.x; })
  .attr("cy", 50)
  .attr("fill", function(d,i) { return colors[i%4]; }) // set start fill
  .each(function(d,i) { d.color = d.start = i%4; })              // record start position.
  .each(transition);
  
var n = 8; // n cycles

function transition() {
  d3.select(this).transition()
    .duration(1000)
    .attr("fill", function(d) { return colors[++d.color%colors.length]; })
    .on("end", function(d) { if(d.color - d.start < n) transition.apply(this); else return null; });
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
<svg width="500" height="300"></svg>

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

var data = [
  ["orange","purple","blue","yellow"],
  ["blue","yellow","orange","purple"]
];

var svg = d3.select("svg");
var circles = svg.selectAll()
  .data(data)
  .enter()
  .append("circle")
  .attr("r", 20)
  .attr("cx", function(d,i) { return i * 50 + 50; })
  .attr("cy", 50)
  .attr("fill", function(d) { return d.shift(); })
  .each(transition);

function transition() {
  var selection = d3.select(this);
  if(selection.datum().length) {
    selection.transition()
      .duration(1000)
      .attr("fill", function(d) { return d.shift() })
      .on("end", transition);
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
<svg width="500" height="300"></svg>

Вы можете заметить, что ни один из моих фрагментов не использует дочерние селекторы: этого можно избежать с помощью пользовательских атрибутов, свойств данных или, проще, используя .attr("fill",function(d,i){ всам переход для дифференциации нечетных и четных элементов.Я ничего не имею против этих селекторов, просто требуется дополнительный выбор.

0 голосов
/ 22 сентября 2018

Если вы создаете функцию для применения перехода, вы можете сжать код до минимума:

/**
 * Apply a transition with the appropriate delay to a selection
 *
 * @param sel: a d3 selection
 * @param fill: the fill colour
 * @param position: the position of the colour in the set of transitions
 */

function tr(sel, fill, position) {
    sel.transition()
    .duration(1000)
    .delay(1000 * position)
    .ease(d3.easeLinear)
    .attr("fill", fill);
}

// example of use:
tr(group.select(":nth-child(1)"), 'blue', 0)
tr(group.select(":nth-child(2)"), 'red', 2)

В действии:

function tr(sel, fill, pos) {
	sel.transition()
	.duration(1000)
    .delay(1000 * pos)
	.ease(d3.easeLinear)
	.attr("fill", fill);
}

var svg = d3.select('svg');

var dataSet = [20, 20];

var group = svg.append("g");
var circles = group.selectAll('circle')
    .data(dataSet)
    .enter()
    .append('circle')
    .attr("r",function(d){ return d })
    .attr("cx",function(d, i){ return i * 100 + 50 })
    .attr("cy",50)
    .attr("fill",'black');

var colors = {
  1: [ 'orange', 'purple', 'blue', 'yellow' ],
  2: [ 'deepskyblue', 'deeppink', 'goldenrod', 'magenta']
};

Object.keys(colors).forEach(function(ix){
   var el = group.select(":nth-child(" + ix + ")");
   colors[ix].forEach( function(c,i) { tr(el, c, i); });
})
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
...