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

Семантическое масштабирование диаграммы направленности силы в d3

Было показано много случаев для геометрического масштабирования, ориентированного по силе, с помощью SVG Geometric Zooming.

В геометрическом масштабировании мне нужно добавить атрибут преобразования в функцию масштабирования. Однако при семантическом масштабировании, если я добавлю только атрибут преобразования в node, ссылки не будут подключаться к node. Итак, мне интересно, существует ли решение для геометрического масштабирования для диаграммы направленности силы в d3.

Вот мой пример с геометрическим масштабированием после предыдущего случая. У меня две проблемы:

  • Когда я уменьшаю масштаб, а затем перетаскиваю весь граф, график будет странно исчезать.
  • Используя ту же функцию перерисовки
function zoom() {
  vis.attr("transform", transform);
}
function transform(d){
  return "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")";
}

Это обновляет только один атрибут svg-элемента "transform". Но как заставить функцию изменить позицию node?

Но я хочу сделать семантическое масштабирование. Я попытался изменить функцию масштабирования и преобразования, но не уверен, как правильно это сделать. Здесь я пытаюсь. Функции, которые я изменил:

function zoom() {
  node.call(transform);
  // update link position
  update();
}
function transform(d){
  // change node x, y position, not sure what function to put here.
}

4b9b3361

Ответ 1

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

Во-первых, вам нужно четко понять, чего вы пытаетесь достичь. Это отличается для двух типов масштабирования. Мне не очень нравится терминология, введенная Майком Бостоком (она не полностью согласуется с не-d3 использованием терминов), но мы можем также придерживаться ее, чтобы она соответствовала другим примерам d3.

В "геометрическом масштабировании" вы масштабируете все изображение. Круги и линии становятся больше и дальше друг от друга. SVG имеет простой способ выполнить это через атрибут "transform". Когда вы устанавливаете transform="scale(2)" на элементе SVG, он рисуется так, как будто все в два раза больше. Для круга его радиус получает два раза большие, а позиции cx и cy получают в два раза больше расстояния от точки (0,0). Вся система координат изменяется, поэтому один блок теперь равен двум пикселам на экране, а не одному.

Аналогично, transform="translate(-50,100)" изменяет всю систему координат, так что (0,0) точка системы координат перемещается на 50 единиц влево и на 100 единиц вниз от верхнего левого угла (который является значением по умолчанию точка).

Если вы оба переводите и масштабируете элемент SVG, порядок важен. Если перевод выполняется до масштаба, то перевод выполняется в исходных единицах. Если перевод выполняется после масштабирования, чем перевод в масштабируемых единицах.

Метод d3.zoom.behavior() создает функцию, которая прослушивает колеса мыши и события перетаскивания, а также события сенсорного экрана, связанные с масштабированием, Он преобразует эти пользовательские события в пользовательское событие "масштабирования".

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

Для геометрического масштабирования то, что вы обычно делаете, это установить масштаб и преобразовать атрибут преобразования в элемент <g>, который содержит контент, который вы хотите увеличить. В этом примере реализуется метод геометрического масштабирования на простом SVG, состоящем из равномерно размещенных линий сетки:
http://jsfiddle.net/LYuta/2/

Код масштабирования просто:

function zoom() {
    console.log("zoom", d3.event.translate, d3.event.scale);
    vis.attr("transform", 
             "translate(" + d3.event.translate + ")" 
                + " scale(" + d3.event.scale + ")"
             );
}

Масштабирование выполняется путем установки атрибута transform на "vis", который представляет собой выбор d3, содержащий элемент <g>, который сам содержит весь контент, который мы хотим увеличить. Факторы перевода и масштабирования поступают непосредственно из события масштабирования, созданного поведением d3.

В результате все становится больше или меньше - ширина линий сетки, а также расстояние между ними. Строки все еще имеют stroke-width:1.5;, но определение того, что 1,5 равно на экране, изменилось для них и всего остального в преобразованном элементе <g>.

Для каждого события масштабирования коэффициенты трансляции и масштаба также регистрируются на консоли. Глядя на это, вы заметите, что если вы уменьшите масштаб, масштаб будет находиться между 0 и 1; если вы увеличены, оно будет больше 1. Если вы нажмете (перетащите, чтобы переместить) график, масштаб не изменится вообще. Однако числа переводов меняются как на панорамирование, так и на масштабирование. Это потому, что перевод представляет положение (0,0) точки на графике относительно положения верхнего левого угла SVG. При масштабировании изменяется расстояние между (0,0) и любой другой точкой графика. Поэтому, чтобы содержимое под мышью или пальцем касалось в том же положении на экране, положение точки (0,0) должно перемещаться.

В этом примере вы должны обратить внимание на ряд других вещей:

  • Я изменил объект поведения масштабирования с помощью метода .scaleExtent([min,max]). Это устанавливает ограничение на значения шкалы, которые поведение будет использовать в событии масштабирования, независимо от того, сколько пользователь вращает колесо.

  • Преобразование находится в элементе <g>, а не в <svg>. Это потому, что элемент SVG в целом рассматривается как элемент HTML и имеет другой синтаксис и свойства преобразования.

  • Поведение масштабирования привязано к другому элементу <g>, содержащему основной <g> и фоновый прямоугольник. Прямоугольник фона есть, чтобы мыши и события касания могли наблюдаться, даже если мышь или касание не правы на линии. Сам элемент <g> не имеет высоты или ширины и поэтому не может напрямую отвечать на пользовательские события, он получает только события от своих детей. Я оставил прямоугольник черного цвета, чтобы вы могли определить, где он находится, но вы можете установить его стиль fill:none;, пока вы также установите его на pointer-events:all;. Прямоугольник не может находиться внутри <g>, который преобразуется, потому что тогда область, которая реагирует на события масштабирования, также уменьшится, когда вы уменьшите масштаб, и, возможно, исчезнет из поля зрения с края SVG.

    /li >
  • Вы можете пропустить прямоугольник и второй элемент <g>, привязав поведение масштабирования непосредственно к объекту SVG, как в этой версии скрипку. Тем не менее, вы часто не хотите, чтобы события во всей области SVG вызывали масштабирование, поэтому хорошо знать, как и почему использовать параметр фона.

Здесь тот же метод геометрического масштабирования, применяемый к упрощенной версии вашего силового макета:
http://jsfiddle.net/cSn6w/5/

Я сократил количество узлов и ссылок и убрал поведение node -drag и поведение node -expand/collapse, поэтому вы можете сфокусироваться на масштабировании. Я также изменил параметр "трения", так что график перестает двигаться; увеличьте его, пока он все еще движется, и вы увидите, что все будет двигаться по-прежнему.

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

"Семантическое масштабирование" графика, в том смысле, что Майк Босток использует термин, - это масштабирование макета графика без масштабирования отдельных элементов. (Обратите внимание, что существуют другие интерпретации "семантического масштабирования" для других контекстов.)

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

Вы можете сами выполнить эти вычисления, используя значения перевода и масштабирования для размещения объектов на основе этих формул:

zoomedPositionX = d3.event.translate[0] + d3.event.scale * dataPositionX 

zoomedPositionY = d3.event.translate[1] + d3.event.scale * dataPositionY

Я использовал этот подход для реализации семантического масштабирования в этой версии примера gridlines:
http://jsfiddle.net/LYuta/4/

Для вертикальных линий они были первоначально расположены так:

vLines.attr("x1", function(d){return d;})
    .attr("y1", 0)
    .attr("x2", function(d){return d;})
    .attr("y2", h);

В функции масштабирования, которая изменяется на

vLines.attr("x1", function(d){
        return d3.event.translate[0] + d*d3.event.scale;
    })
    .attr("y1", d3.event.translate[1])
    .attr("x2", function(d){
        return d3.event.translate[0] + d*d3.event.scale;
    })
    .attr("y2", d3.event.translate[1] + h*d3.event.scale);

Аналогичным образом изменяются горизонтальные линии. Результат? Положение и длина линий изменяются при масштабировании, без того, чтобы линии становились толще или тоньше.

Это немного усложняется, когда мы пытаемся сделать то же самое для макета силы. Это потому, что объекты в графе компоновки сил также переустанавливаются после каждого события "тика". Чтобы держать их в правильных местах для увеличения, методу позиционирования меток придется использовать формулы с увеличенной позицией. Это означает, что:

  • Шкала и перевод должны сохраняться в переменной, к которой можно получить доступ с помощью функции tick; и,
  • Для использования функции галочки необходимо использовать значения по умолчанию и значения трансляции, если пользователь еще ничего не увеличил.

Масштаб по умолчанию будет равным 1, а перевод по умолчанию будет [0,0], представляющий нормальный масштаб и отсутствие перевода.

Вот как это выглядит с помощью семантического масштабирования на упрощенном макете силы:
http://jsfiddle.net/cSn6w/6/

Теперь функция масштабирования

function zoom() {
    console.log("zoom", d3.event.translate, d3.event.scale);
    scaleFactor = d3.event.scale;
    translation = d3.event.translate;
    tick(); //update positions
}

Он устанавливает переменные scaleFactor и translation, а затем вызывает функцию tick. Функция галочки выполняет все позиционирование: при инициализации, после событий силового макета, а также после событий масштабирования. Он выглядит как

function tick() {
    linkLines.attr("x1", function (d) {
            return translation[0] + scaleFactor*d.source.x;
        })
        .attr("y1", function (d) {
            return translation[1] + scaleFactor*d.source.y;
        })
        .attr("x2", function (d) {
            return translation[0] + scaleFactor*d.target.x;
        })
        .attr("y2", function (d) {
            return translation[1] + scaleFactor*d.target.y;
        });

    nodeCircles.attr("cx", function (d) {
            return translation[0] + scaleFactor*d.x;
        })
        .attr("cy", function (d) {
            return translation[1] + scaleFactor*d.y;
        });
}

Каждое значение позиции для кругов и ссылок корректируется с помощью перевода и масштабного коэффициента. Если это имеет смысл для вас, этого должно быть достаточно для вашего проекта, и вам не нужно использовать шкалы. Просто убедитесь, что вы всегда используете эту формулу для преобразования между координатами данных (d.x и d.y) и отображаемыми координатами (cx, cy, x1, x2 и т.д.), Используемыми для размещения объектов.

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

Для геометрического масштабирования преобразование между положением экрана и положением данных может быть опущено с помощью d3.mouse(). Использование d3.mouse(SVGElement) вычисляет положение мыши в системе координат, используемой этим SVGElement. Поэтому, если мы передаем элемент, представляющий преобразованную визуализацию, он возвращает координаты, которые могут использоваться непосредственно для установки положения объектов.

Макрос перетаскиваемого геометрического масштабирования выглядит следующим образом:
http://jsfiddle.net/cSn6w/7/

Функция перетаскивания:

function dragged(d){
    if (d.fixed) return; //root is fixed

    //get mouse coordinates relative to the visualization
    //coordinate system:    
    var mouse = d3.mouse(vis.node());
    d.x = mouse[0]; 
    d.y = mouse[1];
    tick();//re-position this node and any links
}

Однако для семантического масштабирования SVG-координаты, возвращаемые d3.mouse(), больше не соответствуют координатам данных. Вы должны учитывать масштаб и перевод. Вы делаете это путем переустановки приведенных выше формул:

zoomedPositionX = d3.event.translate[0] + d3.event.scale * dataPositionX 

zoomedPositionY = d3.event.translate[1] + d3.event.scale * dataPositionY

становится

dataPositionX = (zoomedPositionX - d3.event.translate[0]) / d3.event.scale

dataPositionY = (zoomedPositionY - d3.event.translate[1]) / d3.event.scale

Следовательно, функция перетаскивания для примера семантического масштабирования

function dragged(d){
    if (d.fixed) return; //root is fixed

    //get mouse coordinates relative to the visualization
    //coordinate system:
    var mouse = d3.mouse(vis.node());
    d.x = (mouse[0] - translation[0])/scaleFactor; 
    d.y = (mouse[1] - translation[1])/scaleFactor; 
    tick();//re-position this node and any links
}

Этот перетаскиваемый синтаксический макет синтаксического масштабирования реализован здесь:
http://jsfiddle.net/cSn6w/8/

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

... и я вернулся:

Глядя на все вышеперечисленные функции преобразования данных в экран, разве вы не думаете, что "не будет ли проще иметь функцию, чтобы делать это каждый раз?" То, что d3 scales предназначено для: для преобразования значений данных для отображения значений.

Вы не часто видите масштабы в примерах силового макета, потому что объект компоновки сил позволяет напрямую устанавливать ширину и высоту, а затем создает значения данных d.x и d.y в этом диапазоне. Установите ширину и высоту макета для ширины и высоты визуализации, и вы можете использовать значения данных непосредственно для позиционирования объектов на дисплее.

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

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

При масштабировании отношения между доменом и диапазоном изменяются, поэтому одно из этих значений должно измениться на шкале. К счастью, нам не нужно самостоятельно определять формулы, потому что поведение масштабирования D3 вычисляет это для нас - если мы присоединим объекты шкалы к объекту поведения масштабирования, используя его методы .x() и .y().

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

Здесь семантический масштаб сетки, реализованный с использованием шкал:
http://jsfiddle.net/LYuta/5/

Key code:

/*** Configure zoom behaviour ***/
var zoomer = d3.behavior.zoom()
                .scaleExtent([0.1,10])
        //allow 10 times zoom in or out
                .on("zoom", zoom)
        //define the event handler function
                .x(xScale)
                .y(yScale);
        //attach the scales so their domains
        //will be updated automatically

function zoom() {
    console.log("zoom", d3.event.translate, d3.event.scale);

    //the zoom behaviour has already changed
    //the domain of the x and y scales
    //so we just have to redraw using them
    drawLines();
}
function drawLines() {
    //put positioning in a separate function
    //that can be called at initialization as well
    vLines.attr("x1", function(d){
            return xScale(d);
        })
        .attr("y1", yScale(0) )
        .attr("x2", function(d){
            return xScale(d);
        })
        /* etc. */

Объект изменения размера d3 изменяет масштаб, изменяя свой домен. Вы можете получить аналогичный эффект, изменив диапазон шкалы, поскольку важная часть изменяет отношения между доменом и диапазоном. Однако диапазон имеет еще одно важное значение: представление максимального и минимального значений, используемых на дисплее. Только изменяя сторону домена шкалы с поведением масштабирования, диапазон по-прежнему представляет собой действительные отображаемые значения. Это позволяет нам реализовать другой тип масштабирования, когда пользователь изменяет размеры дисплея. Путем того, чтобы SVG изменил размер в соответствии с размером окна, а затем установив диапазон шкалы на основе размера SVG, график может реагировать на разные размеры окна/устройства.

Здесь пример сильной семантической сетки масштабирования, выполненный с учетом масштабов:
http://jsfiddle.net/LYuta/9/

Я дал данные о высотах и ​​ширинах SVG, основанные на процентах, в CSS, которые будут превышать значения высоты и ширины атрибута. В script я переместил все строки, которые относятся к высоте и ширине отображения, в функцию, которая проверяет фактический элемент svg на его текущую высоту и ширину. Наконец, я добавил прослушиватель изменения размера окна, чтобы вызвать этот метод (который также запускает повторную ничью).

Key code:

/* Set the display size based on the SVG size and re-draw */
function setSize() {
    var svgStyles = window.getComputedStyle(svg.node());
    var svgW = parseInt(svgStyles["width"]);
    var svgH = parseInt(svgStyles["height"]);

    //Set the output range of the scales
    xScale.range([0, svgW]);
    yScale.range([0, svgH]);

    //re-attach the scales to the zoom behaviour
    zoomer.x(xScale)
          .y(yScale);

    //resize the background
    rect.attr("width", svgW)
            .attr("height", svgH);

    //console.log(xScale.range(), yScale.range());
    drawLines();
}

//adapt size to window changes:
window.addEventListener("resize", setSize, false)

setSize(); //initialize width and height

Те же идеи - использование масштабов для компоновки графика, с изменением домена с увеличением и изменяющимся диапазоном от событий изменения размера окна, конечно, могут быть применены к силовому макету. Тем не менее, нам все же приходится иметь дело с описанным выше усложнением: как отменить преобразование из значений данных для отображения значений при работе с событиями node -drag. Для этого также подходит линейный масштаб d3: scale.invert(). Если w = scale(x), то x = scale.invert(w).

В событии node -drag код с использованием шкал поэтому:

function dragged(d){
    if (d.fixed) return; //root is fixed

    //get mouse coordinates relative to the visualization
    //coordinate system:
    var mouse = d3.mouse(vis.node());
    d.x = xScale.invert(mouse[0]); 
    d.y = yScale.invert(mouse[1]); 
    tick();//re-position this node and any links
}

Остальная часть примера синтаксического масштабирования семантического масштабирования, выполненного с учетом масштабов, находится здесь:
http://jsfiddle.net/cSn6w/10/


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

Ответ 2

Вам нужно как преобразовать node, так и перерисовать пути.

Идея "семантического масштабирования" заключается в том, что вы меняете масштаб своего макета, но не размер отдельных элементов.

Если вы настроили поведение масштабирования, как в связанном примере, оно автоматически обновляет масштаб x и y для вас. Затем вы повторно устанавливаете положение узлов на основе этих масштабов, и вы также можете повторно установить положение и форму ссылок.

Если ваши ссылки являются прямыми линиями, переустановите позиции x1, y1, x2 и y2, используя обновленные шкалы x и y. Если ваши ссылки - это пути, созданные с помощью d3.svg.diagonal и x и y, переустановите атрибут "d" с той же функцией.

Если вам нужны более конкретные инструкции, вам нужно будет опубликовать свой код.