Подтвердить что ты не робот

Изменение и переходный набор данных в диаграмме аккордов с D3

Я работаю над диаграммой аккордов, используя D3.

Я пытаюсь сделать так, чтобы, когда пользователь нажимает на ссылку, набор данных изменится на другой предопределенный набор данных. Я посмотрел на http://exposedata.com/tutorial/chord/latest.html и http://fleetinbeing.net/d3e/chord.html, и попытался использовать некоторые элементы в там, чтобы заставить его работать.

Вот JavaScript, чтобы создать диаграмму "по умолчанию":

var dataset = "data/all_trips.json";

var width = 650,
    height = 600,
    outerRadius = Math.min(width, height) / 2 - 25,
    innerRadius = outerRadius - 18;

var formatPercent = d3.format("%");

var arc = d3.svg.arc()
    .innerRadius(innerRadius)
    .outerRadius(outerRadius);

var layout = d3.layout.chord()
    .padding(.03)
    .sortSubgroups(d3.descending)
    .sortChords(d3.ascending);

var path = d3.svg.chord()
    .radius(innerRadius);

var svg = d3.select("#chart_placeholder").append("svg")
    .attr("width", width)
    .attr("height", height)
  .append("g")
    .attr("id", "circle")
    .attr("transform", "translate(" + width / 1.5 + "," + height / 1.75 + ")");

svg.append("circle")
    .attr("r", outerRadius);

d3.csv("data/neighborhoods.csv", function(neighborhoods) {
  d3.json(dataset, function(matrix) {

    // Compute chord layout.
    layout.matrix(matrix);

    // Add a group per neighborhood.
    var group = svg.selectAll(".group")
        .data(layout.groups)
      .enter().append("g")
        .attr("class", "group")
        .on("mouseover", mouseover);

    // Add a mouseover title.
    group.append("title").text(function(d, i) {
      return numberWithCommas(d.value) + " trips started in " + neighborhoods[i].name;
    });

    // Add the group arc.
    var groupPath = group.append("path")
        .attr("id", function(d, i) { return "group" + i; })
        .attr("d", arc)
        .style("fill", function(d, i) { return neighborhoods[i].color; });

    var rootGroup = d3.layout.chord().groups()[0];

    // Text label radiating outward from the group.
    var groupText = group.append("text");

   group.append("svg:text")
        .each(function(d) { d.angle = (d.startAngle + d.endAngle) / 2; })
        .attr("xlink:href", function(d, i) { return "#group" + i; })
        .attr("dy", ".35em")
        .attr("color", "#fff")
        .attr("text-anchor", function(d) { return d.angle > Math.PI ? "end" : null; })
        .attr("transform", function(d) {
          return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")" +
            " translate(" + (innerRadius + 26) + ")" +
            (d.angle > Math.PI ? "rotate(180)" : "");
        })
        .text(function(d, i) { return neighborhoods[i].name; });

    // Add the chords.
    var chord = svg.selectAll(".chord")
        .data(layout.chords)
      .enter().append("path")
        .attr("class", "chord")
        .style("fill", function(d) { return neighborhoods[d.source.index].color; })
        .attr("d", path);

    // Add mouseover for each chord.
    chord.append("title").text(function(d) {
      if (!(neighborhoods[d.target.index].name === neighborhoods[d.source.index].name)) {
      return numberWithCommas(d.source.value) + " trips from " + neighborhoods[d.source.index].name + " to " + neighborhoods[d.target.index].name + "\n" +
        numberWithCommas(d.target.value) + " trips from " + neighborhoods[d.target.index].name + " to " + neighborhoods[d.source.index].name;
      } else {
        return numberWithCommas(d.source.value) + " trips started and ended in " + neighborhoods[d.source.index].name;
      }
    });

    function mouseover(d, i) {
      chord.classed("fade", function(p) {
        return p.source.index != i
            && p.target.index != i;
      });
      var selectedOrigin = d.value;
      var selectedOriginName = neighborhoods[i].name;
    }
  });
});

И вот что я пытаюсь сделать, чтобы перерисовать диаграмму с новыми данными (есть элемент изображения с id "самкой".

d3.select("#female").on("click", function () {
  var new_data = "data/women_trips.json";
  reRender(new_data);
});

function reRender(data) {
  var layout = d3.layout.chord()
  .padding(.03)
  .sortSubgroups(d3.descending)
  .matrix(data);

  // Update arcs

  svg.selectAll(".group")
  .data(layout.groups)
  .transition()
  .duration(1500)
  .attrTween("d", arcTween(last_chord));

  // Update chords

  svg.select(".chord")
     .selectAll("path")
     .data(layout.chords)
     .transition()
     .duration(1500)
     .attrTween("d", chordTween(last_chord))

};

var arc =  d3.svg.arc()
      .startAngle(function(d) { return d.startAngle })
      .endAngle(function(d) { return d.endAngle })
      .innerRadius(r0)
      .outerRadius(r1);

var chordl = d3.svg.chord().radius(r0);

function arcTween(layout) {
  return function(d,i) {
    var i = d3.interpolate(layout.groups()[i], d);

    return function(t) {
      return arc(i(t));
    }
  }
}

function chordTween(layout) {
  return function(d,i) {
    var i = d3.interpolate(layout.chords()[i], d);

    return function(t) {
      return chordl(i(t));
    }
  }
}
4b9b3361

Ответ 1

Создание диаграммы аккордов

Существует несколько уровней для создания диаграммы аккордов с d3, что соответствует d3, тщательному разделению манипуляций данными с визуализацией данных. Если вы собираетесь не только создать диаграмму аккордов, но и обновить ее плавно, вам нужно четко понять, что делает каждая часть программы и как они взаимодействуют.

Sample Chord Diagram, from the example linked above

Во-первых, аспект обработки данных. Инструмент d3 Chord Layout берет ваши данные о взаимодействиях между различными групп и создает набор объектов данных, которые содержат исходные данные, но также назначаются измерения угла. Таким образом, он похож на инструмент компоновки пива, но есть некоторые важные различия, связанные с повышенной сложностью компоновки аккордов.

Как и другие инструменты компоновки d3, вы создаете объект макета аккорда, вызывая функцию (d3.layout.chord()), а затем вы вызываете дополнительные методы в объекте макета для изменения настроек по умолчанию. Однако, в отличие от инструмента компоновки пива и большинства других макетов, объект макета аккорда не является функцией, которая берет ваши данные в качестве входных данных и выводит вычисленный массив объектов данных с установленными атрибутами макета (углы).

Вместо этого ваши данные являются еще одним параметром для макета, который вы определяете с помощью метода .matrix() и который сохраняется в макете объект. Данные должны храниться в объекте, потому что есть два разных массива объектов данных с атрибутами макета, один для хордов (соединения между разными группами) и один для самих групп. Тот факт, что объект макета хранит данные, имеет важное значение при работе с обновлениями, так как вы должны быть осторожны, чтобы не переписать старые данные с новыми, если вам все еще нужны старые данные для переходов.

var chordLayout = d3.layout.chord() //create layout object
                  .sortChords( d3.ascending ) //set a property
                  .padding( 0.01 ); //property-setting methods can be chained

chordLayout.matrix( data );  //set the data matrix

Доступ к объектам групповых данных осуществляется путем вызова .groups() на макете аккорда после того, как была установлена ​​матрица данных. Каждая группа эквивалентна строке в вашей матрице данных (т.е. Каждый подмассив в массиве массивов). Объектам групповых данных присваиваются значения начального угла и конечного угла, представляющие участок круга. Это очень похоже на круговую диаграмму, причем разница в том, что значения для каждой группы (и для круга в целом) вычисляются путем суммирования значений для всей строки (подмассива). Объекты групповых данных также имеют свойства, представляющие их индекс в исходной матрице (важно, потому что они могут быть отсортированы в другом порядке) и их общее значение.

Объекты данных аккорда получают доступ, вызывая .chords() на макете аккорда после того, как была установлена ​​матрица данных. Каждый аккорд представляет два значения в матрице данных, что эквивалентно двум возможным отношениям между двумя группами. Например, в примере @latortue09 отношения - это велосипедные поездки между окрестностями, поэтому аккорд, который представляет поездки между соседним кварталом A и соседством B, представляет собой количество поездок от A до B, а также число от B до A. Если соседство A находится в строке a вашей матрицы данных, а окрестности B находится в строке b, тогда эти значения должны быть в data[a][b] и data[b][a] соответственно. (Конечно, иногда отношения, которые вы рисуете, не будут иметь такого типа направления к ним, и в этом случае ваша матрица данных должна быть симметричной, что означает, что эти два значения должны быть равными.)

Каждый объект данных аккорда имеет два свойства: source и target, каждый из которых является его собственным объектом данных. И исходный, и целевой объект данных имеют одинаковую структуру с информацией об односторонней связи от одной группы к другой, включая исходные индексы группы и значение этого отношения, а начальные и конечные углы представляют сечение одного группового сегмента круга.

Именование источника/цели несколько путано, так как, как я упоминал выше, объект хорды представляет оба направления связи между двумя группами. Направление, которое имеет большее значение, определяет, какая группа называется source и которая называется target. Так что, если есть 200 поездок из соседства A в соседство B, но 500 поездок от B до A, то source для этого объекта аккорда будет представлять собой участок сегмента окружности B круга, а target будет представлять собой часть Окрестности Отрезка круга. Для отношений между группой и самим собой (в этом примере поездки, которые начинаются и заканчиваются в том же районе), исходный и целевой объекты одинаковы.

Одним из важных важных аспектов массива объектов данных аккордов является то, что он содержит только объекты, в которых существуют отношения между двумя группами. Если нет никаких поездок между окрестностями A и соседством B в любом направлении, то для этих групп не будет объекта данных аккорда. Это становится важным при обновлении с одного набора данных на другой.

Во-вторых, аспект визуализации данных. Средство компоновки Chord создает массивы объектов данных, преобразуя информацию из матрицы данных в углы круга. Но он ничего не рисует. Чтобы создать стандартное представление SVG диаграммы аккордов, вы используете d3 selection для создания элементов, соединенных с массивом объектов данных макета. Поскольку на диаграмме аккордов есть два разных массива объектов данных макета, один для хорд и один для групп, существуют два разных выбора d3.

В простейшем случае оба выбора будут содержать <path> элементы (и два типа путей будут различаться классом). <path>, которые соединены с массивом данных для групп аккордовых диаграмм, становятся дугами вокруг внешней окружности, а <path>, которые соединены с данными для самих аккордов, становятся полосами по кругу.

Форма a <path> определяется его атрибутом "d" (путь данных или направлений). D3 имеет множество генераторов данных пути, которые являются функциями, которые берут объект данных и создают строку, которая может использоваться для пути "d" атрибут. Каждый генератор путей создается путем вызова метода d3, и каждый может быть изменен путем вызова его собственных методов.

Группы в стандартной диаграмме хорды рисуются с помощью d3.svg.arc() генератора данных пути. Этот генератор дуги является тем же самым, что используется графами пирога и пончика. В конце концов, если вы удалите аккорды с диаграммы аккордов, у вас есть просто диаграмма пончика, состоящая из дуг группы. Генератор дуги по умолчанию ожидает передачи данных с объектами startAngle и endAngle; объекты групповой информации, созданные компоновкой аккордов, работают с этим значением по умолчанию. Генератор дуги также должен знать внутренний и внешний радиус дуги. Они могут быть указаны как функции данных или как константы; для хордовой диаграммы они будут постоянными, одинаковыми для каждой дуги.

var arcFunction = d3.svg.arc() //create the arc path generator
                               //with default angle accessors
                  .innerRadius( radius )
                  .outerRadius( radius + bandWidth); 
                               //set constant radius values

var groupPaths = d3.selectAll("path.group")
                 .data( chordLayout.groups() ); 
    //join the selection to the appropriate data object array 
    //from the chord layout 

groupPaths.enter().append("path") //create paths if this isn't an update
          .attr("class", "group"); //set the class
          /* also set any other attributes that are independent of the data */

groupPaths.attr("fill", groupColourFunction )
          //set attributes that are functions of the data
          .attr("d", arcFunction ); //create the shape
   //d3 will pass the data object for each path to the arcFunction
   //which will create the string for the path "d" attribute

Аккорды на диаграмме аккордов имеют форму, уникальную для этого типа диаграммы. Их формы определяются с помощью d3.svg.chord() генератора данных пути. Генератор аккордов по умолчанию ожидает данные формы, созданной объектом макета аккорда, единственное, что нужно указать, это радиус круга (который обычно будет таким же, как внутренний радиус групп дуги).

var chordFunction = d3.svg.chord() //create the chord path generator
                                   //with default accessors
                    .radius( radius );  //set constant radius

var chordPaths = d3.selectAll("path.chord")
                 .data( chordLayout.chords() ); 
    //join the selection to the appropriate data object array 
    //from the chord layout 

chordPaths.enter().append("path") //create paths if this isn't an update
          .attr("class", "chord"); //set the class
          /* also set any other attributes that are independent of the data */

chordPaths.attr("fill", chordColourFunction )
          //set attributes that are functions of the data
          .attr("d", chordFunction ); //create the shape
   //d3 will pass the data object for each path to the chordFunction
   //which will create the string for the path "d" attribute

Это простой случай, только с элементами <path>. Если вы хотите также иметь текстовые метки, связанные с вашими группами или аккордами, ваши данные объединяются в элементы <g> и элементы <path> и элементы <text> для ярлыков (и любые другие элементы, такие как галочка метки в примере цвета волос) являются дочерними элементами того, кто наследует объект данных. Когда вы обновляете график, вам необходимо обновить все подкомпоненты, на которые влияют данные.

Обновление диаграммы аккордов

С учетом всей этой информации, как вы должны подходить к созданию диаграммы аккордов, которая может быть обновлена ​​новыми данными?

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

В этом примере шаги инициализации будут включать создание элемента <svg> и центрированного <g>, а также чтение в массиве информации о разных окрестностях. Затем метод инициализации вызовет метод обновления с матрицей данных по умолчанию. Кнопки, которые переключаются на другую матрицу данных, вызовут тот же метод.

/*** Initialize the visualization ***/
var g = d3.select("#chart_placeholder").append("svg")
        .attr("width", width)
        .attr("height", height)
    .append("g")
        .attr("id", "circle")
        .attr("transform", 
              "translate(" + width / 2 + "," + height / 2 + ")");
//the entire graphic will be drawn within this <g> element,
//so all coordinates will be relative to the center of the circle

g.append("circle")
    .attr("r", outerRadius);

d3.csv("data/neighborhoods.csv", function(error, neighborhoodData) {

    if (error) {alert("Error reading file: ", error.statusText); return; }

    neighborhoods = neighborhoodData; 
        //store in variable accessible by other functions
    updateChords(dataset); 
    //call the update method with the default dataset url

} ); //end of d3.csv function

/* example of an update trigger */
d3.select("#MenOnlyButton").on("click", function() {
    updateChords( "/data/men_trips.json" );
    disableButton(this);
});

Я просто передаю URL-адрес данных для функции обновления, что означает, что первой строкой этой функции будет вызов функции анализа данных. Полученная матрица данных используется как матрица для нового объекта макета данных. Нам нужен новый объект макета, чтобы сохранить копию старого макета для функций перехода. (Если вы не собираетесь переходить к изменениям, вы можете просто вызвать метод matrix на том же макете, чтобы создать новый.) Чтобы свести к минимуму дублирование кода, я использую функцию для создания нового объекта макета и для установки все его варианты:

/* Create OR update a chord layout from a data matrix */
function updateChords( datasetURL ) {

  d3.json(datasetURL, function(error, matrix) {

    if (error) {alert("Error reading file: ", error.statusText); return; }

    /* Compute chord layout. */
    layout = getDefaultLayout(); //create a new layout object
    layout.matrix(matrix);

    /* main part of update method goes here */

  }); //end of d3.json
}

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

Во-первых, цепочка объединения данных. Один для групп и один для аккордов.
Чтобы поддерживать постоянство объекта через переходы - и чтобы уменьшить количество графических свойств, которые вы должны установить при обновлении, вы должны установить ключевая функция в вашем соединении данных. По умолчанию d3 сопоставляет данные с элементами в пределах выбора, основанными только на их порядке в странице/массиве. Поскольку в нашем массиве макета аккорда .chords() не указаны аккорды, в этом наборе данных есть нулевое отношение, порядок хорд может быть несовместим между раундами обновления. Массив .groups() также может быть повторно отсортирован по заказам, которые не соответствуют исходной матрице данных, поэтому мы также добавляем ключевую функцию для безопасности. В обоих случаях ключевые функции основаны на свойствах .index, которые макет аккорда хранится в объектах данных.

/* Create/update "group" elements */
var groupG = g.selectAll("g.group")
    .data(layout.groups(), function (d) {
        return d.index; 
        //use a key function in case the 
        //groups are sorted differently between updates
    });

/* Create/update the chord paths */
var chordPaths = g.selectAll("path.chord")
    .data(layout.chords(), chordKey );
        //specify a key function to match chords
        //between updates

/* Elsewhere, chordKey is defined as: */

function chordKey(data) {
    return (data.source.index < data.target.index) ?
        data.source.index  + "-" + data.target.index:
        data.target.index  + "-" + data.source.index;

    //create a key that will represent the relationship
    //between these two groups *regardless*
    //of which group is called 'source' and which 'target'
}

Обратите внимание, что аккорды являются элементами <path>, но группы представляют собой элементы <g>, которые будут содержать как <path>, так и <text>.

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

Во-вторых, цепочка ввода.. Для всех объектов данных, которые не соответствуют элементу (это все они, если это первый раз, когда визуализация рисуется), нам нужно создать элемент и его дочерние компоненты. В это время вы также хотите установить любые атрибуты, которые являются постоянными для всех элементов (независимо от данных) или которые основаны на значениях данных, которые вы используете в ключевой функции, и, следовательно, не будут меняться при обновлении.

var newGroups = groupG.enter().append("g")
    .attr("class", "group");
//the enter selection is stored in a variable so we can
//enter the <path>, <text>, and <title> elements as well

//Create the title tooltip for the new groups
newGroups.append("title");

//create the arc paths and set the constant attributes
//(those based on the group index, not on the value)
newGroups.append("path")
    .attr("id", function (d) {
        return "group" + d.index;
        //using d.index and not i to maintain consistency
        //even if groups are sorted
    })
    .style("fill", function (d) {
        return neighborhoods[d.index].color;
    });

//create the group labels
newGroups.append("svg:text")
    .attr("dy", ".35em")
    .attr("color", "#fff")
    .text(function (d) {
        return neighborhoods[d.index].name;
    });


//create the new chord paths
var newChords = chordPaths.enter()
    .append("path")
    .attr("class", "chord");

// Add title tooltip for each new chord.
newChords.append("title");

Обратите внимание, что цвета заливки для групповых дуг установлены для ввода, но не для цветов заливки для аккордов. Это потому, что цвет аккорда будет меняться в зависимости от того, какая группа (из двух соединяемых аккордов) называется "источником" и которая является "мишенью", т.е. В зависимости от того, какое направление отношения сильнее (имеет больше поездок).

В-третьих, цепочка обновлений.. Когда вы добавляете элемент в выбор .enter(), этот новый элемент заменяет владельца нулевого места в исходном выборе соединения данных. После этого, если вы будете манипулировать исходным выбором, настройки будут применяться как к новым, так и к элементам обновления. Таким образом, вы устанавливаете любые свойства, которые зависят от данных.

//Update the (tooltip) title text based on the data
groupG.select("title")
    .text(function(d, i) {
        return numberWithCommas(d.value) 
            + " trips started in " 
            + neighborhoods[i].name;
    });

//update the paths to match the layout
groupG.select("path") 
    .transition()
        .duration(1500)
        .attr("opacity", 0.5) //optional, just to observe the transition
    .attrTween("d", arcTween( last_layout ) )
        .transition().duration(10).attr("opacity", 1) //reset opacity
    ;

//position group labels to match layout
groupG.select("text")
    .transition()
        .duration(1500)
        .attr("transform", function(d) {
            d.angle = (d.startAngle + d.endAngle) / 2;
            //store the midpoint angle in the data object

            return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")" +
                " translate(" + (innerRadius + 26) + ")" + 
                (d.angle > Math.PI ? " rotate(180)" : " rotate(0)"); 
            //include the rotate zero so that transforms can be interpolated
        })
        .attr("text-anchor", function (d) {
            return d.angle > Math.PI ? "end" : "begin";
        });

// Update all chord title texts
chordPaths.select("title")
    .text(function(d) {
        if (neighborhoods[d.target.index].name !== 
                neighborhoods[d.source.index].name) {

            return [numberWithCommas(d.source.value),
                    " trips from ",
                    neighborhoods[d.source.index].name,
                    " to ",
                    neighborhoods[d.target.index].name,
                    "\n",
                    numberWithCommas(d.target.value),
                    " trips from ",
                    neighborhoods[d.target.index].name,
                    " to ",
                    neighborhoods[d.source.index].name
                    ].join(""); 
                //joining an array of many strings is faster than
                //repeated calls to the '+' operator, 
                //and makes for neater code!
        } 
        else { //source and target are the same
            return numberWithCommas(d.source.value) 
                + " trips started and ended in " 
                + neighborhoods[d.source.index].name;
        }
    });

//update the path shape
chordPaths.transition()
    .duration(1500)
    .attr("opacity", 0.5) //optional, just to observe the transition
    .style("fill", function (d) {
        return neighborhoods[d.source.index].color;
    })
    .attrTween("d", chordTween(last_layout))
    .transition().duration(10).attr("opacity", 1) //reset opacity
;

//add the mouseover/fade out behaviour to the groups
//this is reset on every update, so it will use the latest
//chordPaths selection
groupG.on("mouseover", function(d) {
    chordPaths.classed("fade", function (p) {
        //returns true if *neither* the source or target of the chord
        //matches the group that has been moused-over
        return ((p.source.index != d.index) && (p.target.index != d.index));
    });
});
//the "unfade" is handled with CSS :hover class on g#circle
//you could also do it using a mouseout event on the g#circle

Изменения выполняются с помощью d3 переходовдля создания плавного перехода от одной диаграммы к другой. Для изменений в формах пути пользовательские функции используются для перехода, сохраняя общую форму. Подробнее о ниже.

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

Вы можете создать настраиваемый переход, чтобы преобразовать фигуры в ничто, но я просто использую затухание до нулевой прозрачности:

//handle exiting groups, if any, and all their sub-components:
groupG.exit()
    .transition()
        .duration(1500)
        .attr("opacity", 0)
        .remove(); //remove after transitions are complete


//handle exiting paths:
chordPaths.exit().transition()
    .duration(1500)
    .attr("opacity", 0)
    .remove();

О пользовательских функциях твинов:

Если вы только использовали анимацию по умолчанию для переключения с одной формы пути на другую, результаты могут выглядеть странно. Попробуйте переключиться с "Только мужчины" на "Только женщины" , и вы увидите, что аккорды отсоединяются от края круга. Если бы позиции дуги изменились более значительно, вы увидите, что они пересекают круг, чтобы достичь своего нового положения, вместо того, чтобы скользить по кольцу.

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

Пользовательская функция твинов позволяет определить, как должен быть сформирован путь на каждом шаге перехода. Я написал здесь комментарии о твинных функциях здесь и здесь, поэтому я не собираюсь его перефразировать, Но краткое описание состоит в том, что функция твинов, которую вы передаете в .attrTween(attribute, tween), должна быть функцией, которая вызывается один раз для элемента и сама должна возвращать функцию, которая будет вызываться при каждом "тике" перехода, чтобы вернуть значение атрибута в этот момент перехода.

Чтобы получить плавные переходы форм пути, мы используем две функции генератора данных пути - генератор дуги и генератор аккордов - для создания данных пути на каждом этапе перехода. Таким образом, дуги всегда будут выглядеть как дуги, и аккорды всегда будут выглядеть как аккорды. Переходящая часть - это значения начального и конечного угла. Учитывая два разных объекта данных, которые описывают один и тот же тип формы, но с разными значениями угла, вы можете использовать d3.interpolateObject(a,b) для создания функции, которая будет дают вам объект на каждом этапе перехода, который имеет соответствующие свойства угла перехода. Поэтому, если у вас есть объект данных из старого макета и соответствующий объект данных из нового макета, вы можете плавно перемещать дуги или аккорды из одной позиции в другую.

Однако, что вы должны делать, если у вас нет старого объекта данных? Либо потому, что этот аккорд не имел соответствия в старой компоновке, либо потому, что это первый раз, когда визуализация рисуется, и нет старого макета. Если вы передадите пустой объект в качестве первого параметра в d3.interpolateObject, перешедший объект всегда будет точно конечным значением. В сочетании с другими переходами, такими как непрозрачность, это может быть приемлемым. Однако я решил сделать такой переход, чтобы он начинался с формы нулевой ширины, то есть формы, где углы начала совпадают с торцевыми углами, а затем расширяется до конечной формы:

function chordTween(oldLayout) {
    //this function will be called once per update cycle

    //Create a key:value version of the old layout chords array
    //so we can easily find the matching chord 
    //(which may not have a matching index)

    var oldChords = {};

    if (oldLayout) {
        oldLayout.chords().forEach( function(chordData) {
            oldChords[ chordKey(chordData) ] = chordData;
        });
    }

    return function (d, i) {
        //this function will be called for each active chord

        var tween;
        var old = oldChords[ chordKey(d) ];
        if (old) {
            //old is not undefined, i.e.
            //there is a matching old chord value

            //check whether source and target have been switched:
            if (d.source.index != old.source.index ){
                //swap source and target to match the new data
                old = {
                    source: old.target,
                    target: old.source
                };
            }

            tween = d3.interpolate(old, d);
        }
        else {
            //create a zero-width chord object
            var emptyChord = {
                source: { startAngle: d.source.startAngle,
                         endAngle: d.source.startAngle},
                target: { startAngle: d.target.startAngle,
                         endAngle: d.target.startAngle}
            };
            tween = d3.interpolate( emptyChord, d );
        }

        return function (t) {
            //this function calculates the intermediary shapes
            return path(tween(t));
        };
    };
}

(Проверьте скрипт для кода анимации дуги, который немного проще)

Текущая версия: http://jsfiddle.net/KjrGF/12/