Изменения данных в диаграмме аккорда с Д3

Я работаю над диаграммой аккордов, используя 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));
    }
  }
}

1 ответов


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

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

Sample Chord Diagram, from the example linked above

во-первых, аспект обработки данных. Д3 хорда элемент берет данные о взаимодействиях между различными группами и создает набор объектов данных, которые содержат исходные данные, а также измерения угла . Таким образом, это похоже на пирог layout инструмент, но есть некоторые важные различия, связанные с усложнением аккорд макет.

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

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

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 поездок из района а в район Б, но 500 поездок из В В а, то source для этого объекта аккорда будет представлять собой отрезок сегмента окрестности B круга, а target будет представлять часть сегмента окрестности A круга. Для отношений между группой и самой собой (в этом примере поездки, которые начинаются и заканчиваются в одной и той же окрестности), исходные и целевые объекты одинаковы.

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

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

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

форму <path> определяется "d" (данные пути или направления). D3 имеет разнообразие генераторы данных пути, которые являются функциями, которые принимают объект данных и создают строку, которая может использоваться для пути . Каждый генератор путей создается путем вызова метода 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> элемент, а также чтение в массиве информации о различных районах. Затем метод инициализации вызовет метод update с матрицей данных по умолчанию. Кнопки, которые переключаются на другую матрицу данных, будут вызывать то же самое метод.

/*** 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-адрес данных функции update, что означает, что первая строка этой функции будет вызовом функции анализа данных. Результирующая матрица данных используется в качестве матрицы для новая объект компоновки данных. Нам нужен новый объект макета, чтобы сохранить копию старого макета для функций перехода. (Если вы не собираетесь переходить изменения, вы можете просто вызвать 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
}

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

во-первых, цепочка соединения данных. один для групп и один для аккордов.
Сохранить объект постоянства через переходы -- и для уменьшения количества графических свойств, которые вы должны установить при обновлении -- вы захотите установить ключевую функцию в своем соединении данных. По умолчанию d3 сопоставляет данные элементам в выборке, основываясь только на их порядок в странице / массиве. Потому что наш макет аккорда .chords() массив не включает аккорды если в этом наборе данных есть нулевая связь, порядок аккордов может быть несовместимым между раундами обновления. The .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() методы доступа к этим цепям.

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

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, но не цвета заливки для аккордов. Это связано с тем, что цвет аккорда будет меняться в зависимости от того, какая группа (из двух, которые соединяет аккорд) называется "источником", а какая - "целью", т. е. в зависимости от того, какое направление отношений сильнее (имеет больше поездок).

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

//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 для создания плавного перехода от одной диаграммы к другой. Для изменений фигур пути пользовательскими функциями являются используется для выполнения перехода при сохранении общей формы. Подробнее о тех, кто ниже.

в-четвертых, выход (цепи). если какие-либо элементы из предыдущей диаграммы больше не имеют соответствия в новых данных-например, если Аккорд не существует, потому что нет отношений между этими двумя группами (например, нет поездок между этими двумя окрестностями) в этом наборе данных-тогда вы должны удалить этот элемент из визуализации. Вы можете либо удалить их немедленно, чтобы они исчезли, чтобы освободить место для перехода данных, или вы можете использовать переход их, а затем удалить. (Зовет .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();

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

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

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

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

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

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

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

    //Create a key:value version of the old layout's 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));
        };
    };
}

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

живая версия в целом: http://jsfiddle.net/KjrGF/12/