d3 принудительно направленные узлы в виде текста - вычислить относительное положение стрелки

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

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

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

var force = d3.layout.force().nodes(dataset.nodes).links(dataset.edges)...
var edges = svg.selectAll("line").data(dataset.edges).enter().append("line")...
var nodes = svg.selectAll("text").data(dataset.nodes).enter().append("text")...

force.on("tick", function(){
    // Why does the following function not work? How to implement this correctly?
    edges.attr(function(d){ 
        var x1 = d.source.x
        var y1 = d.source.y
        var x2 = d.target.x
        var y2 = d.target.y
        // ... calulate new source and targetcoordinates ... I can do this myself 
        return {
            "x1": newSourceX,
            "y1": newSourceY,
            "x2": newTargetX,
            "y2": newTargetY
        }
    });
});

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

Как вы думаете, это лучшее решение?


person fabi    schedule 17.10.2013    source источник
comment
Не уверен, что вы имеете в виду. Вам не нужно вычислять, куда указывает стрелка, см., например. в этом примере.   -  person Lars Kotthoff    schedule 17.10.2013
comment
Я хотел бы использовать текст в качестве узлов, как показано в в этом примере. Стрелки должны указывать за пределы текстового элемента, но каждый текстовый элемент имеет свою собственную ширину. Точная проблема заключается в том, чтобы получить исходную и целевую координаты от каждого ребра внутри функции tick().   -  person fabi    schedule 18.10.2013
comment
Вы видели этот вопрос?   -  person Lars Kotthoff    schedule 18.10.2013


Ответы (2)


Попробуйте использовать следующее в качестве отправной точки. Вызов updateLines(".links") внутри tick() устанавливает, что линии достигают только радиусов заданных узлов, а не края прямоугольника.

var updateLines = function (selection) {
    selection.each( function(d) {
        var x1 = d.source.x,
            y1 = d.source.y,
            x2 = d.target.x,
            y2 = d.target.y,
            rad = Math.atan((y2-y1)/(x2-x1)),
            offsetX1 = d.source.r * -Math.cos(rad),
            offsetY1 = d.source.r * Math.sin(rad),
            offsetX2 = d.target.r * Math.cos(rad),
            offsetY2 = d.target.r * -Math.sin(rad);
        d3.select(this)
        .attr("x1", function() {
            if (x2 - x1 >= 0) {
                return x1 - offsetX1;
            } else {
                return x1 + offsetX1;
            }                
        })
        .attr("y1", function() {
            if (x2 - x1 >= 0) {
                return y1 + offsetY1;
            } else {
                return y1 -  offsetY1;
            }
        })
        .attr("x2", function() {
            if (x2 - x1 >= 0) {
                return x2 - offsetX2;
            } else {
                return x2 + offsetX2;
            }
        })
        .attr("y2", function() {
            if (x2 - x1 >= 0) {
                return y2 + offsetY2;
            } else {
                return y2 -  offsetY2;
            }
        })
    })
}

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

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

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

var yourBool = Math.atan(offsetX/offsetY) > Math.atan( textbox.width / 2 / textbox.height / 2 )
// or Math.atan(y/x) and so on...

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

person Walter Roman    schedule 18.10.2013
comment
Спасибо. Эта функция не совсем то, что мне нужно, но она поможет мне вычислить позиции ребер по тексту узла. Позже отпишусь о результате :) - person fabi; 18.10.2013

Ответ Уолтерса требует некоторых изменений для правильной обработки корректировки конца строки, поскольку координаты x и y как источника, так и цели должны быть включены в определение добавления или вычитания вычисляемой корректировки. Например, чтобы рассчитать корректировку X2:

  .attr("x2", function() {
    if ((y2 >= y1) && (x2 >= x1)) return  x2 - offsetX2;
    if ((y2 >= y1) && (x2 < x1)) return  x2 + offsetX2;
    if ((y2 < y1) && (x2 >= x1)) return  x2 - offsetX2;
    if ((y2 < y1) && (x2 < x1)) return  x2 + offsetX2;
  }
person SteveC    schedule 10.10.2017