Получите стрелки, чтобы указать на внешний край node в D3

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

Вот как я создаю маркеры и ссылки:

svg.append("svg:defs").selectAll("marker")
    .data(["prereq", "coreq"])
    .enter().append("svg:marker")
    .attr("id", String)
    .attr("viewBox", "0 -5 10 10")
    .attr("refX", 15)
    .attr("markerWidth", 6)
    .attr("markerHeight", 6)
    .attr("orient", "auto")
    .append("svg:path")
    .attr("d", "M0,-5L10,0L0,5");

var link = svg.selectAll(".link")
    .data(force.links())
    .enter().append("line")
    .attr("class", "link")
    .attr("marker-end", function(d) { return "url(#" + d.type + ")"; });

Я заметил, что атрибут refX указывает, как далеко от конца строки появляется стрелка. Как я могу сделать это зависимым от радиуса node, на который он указывает? Если я не могу этого сделать, могу ли я вместо этого изменить конечные точки самих линий? Я предполагаю, что я сделаю это в этой функции, которая сбрасывает конечные точки линий, когда все перемещается:

function tick() {
        link
            .attr("x1", function(d) { return d.source.x; })
            .attr("y1", function(d) { return d.source.y; })
            .attr("x2", function(d) { return d.target.x; })
            .attr("y2", function(d) { return d.target.y; });

        circle.attr("transform", function(d) {
            return "translate(" + d.x + "," + d.y + ")";
        });

        text.attr("transform", function(d) {
            return "translate(" + d.x + "," + d.y + ")";
        });
    }

Какой подход имеет больше смысла и как я его реализую?

Ответы

Ответ 1

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

Затем я добавил к моим узлам поле "радиус". Я просто сделал это, когда я установил атрибут радиуса, добавив его как фактическое поле, а не сразу возвращая значение:

var circle = svg.append("svg:g").selectAll("circle")
                    .data(force.nodes())
                    .enter().append("svg:circle")
                    .attr("r", function(d) {
                        if (d.logic != null) {
                            d.radius = 5;
                        } else {
                            d.radius = node_scale(d.classSize);
                        }
                        return d.radius;

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

function tick(e) {

        path.attr("d", function(d) {
            // Total difference in x and y from source to target
            diffX = d.target.x - d.source.x;
            diffY = d.target.y - d.source.y;

            // Length of path from center of source node to center of target node
            pathLength = Math.sqrt((diffX * diffX) + (diffY * diffY));

            // x and y distances from center to outside edge of target node
            offsetX = (diffX * d.target.radius) / pathLength;
            offsetY = (diffY * d.target.radius) / pathLength;

            return "M" + d.source.x + "," + d.source.y + "L" + (d.target.x - offsetX) + "," + (d.target.y - offsetY);
        });

В принципе, треугольник, образованный контуром, это общее изменение x (diffX), и полное изменение y (diffY) является аналогичным треугольником к тому, что сформирован сегментом пути внутри цели node (т.е. node), изменение x внутри цели node (offsetX), а y изменяется внутри цели node (offsetY). Это означает, что отношение целевого радиуса node к общей длине пути равно отношению offsetX к diffX и отношению offsetY к diffY.

Я также изменил значение refX на 10 для стрелок. Я не уверен, почему это было необходимо, но теперь это работает!

Ответ 2

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