Направленный график силы D3, различная форма в зависимости от данных и заданного значения?

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

Вы можете увидеть мой рабочий пример только с круглыми узлами здесь: http://jsfiddle.net/dzorz/uWtSk/

Я попытался добавить прямоугольники с оператором if else в часть кода, где я добавляю форму к узлу следующим образом:

function(d)
    {
        if (d.entity == "company")
        {
            node.append("rect")
                .attr("class", function(d){ return "node type"+d.type})
                .attr("width", 100)
                .attr("height", 50)
                .call(force.drag);
        }
        else
        {
        node.append("circle")
            .attr("class", function(d){ return "node type"+d.type})
            .attr("r", function(d) { return radius(d.value) || 10 })
            //.style("fill", function(d) { return fill(d.type); })
            .call(force.drag);
        }
    }

Но тогда я вообще не получил никакой формы ни на одном узле.

Каков правильный способ настроить это?

Весь код выглядит так: script:

var data = {"nodes":[
                        {"name":"Action 4", "type":5, "slug": "", "value":265000},
                        {"name":"Action 5", "type":6, "slug": "", "value":23000},
                        {"name":"Action 3", "type":4, "slug": "", "value":115000},
                        {"name":"Yahoo", "type":1, "slug": "www.yahoo.com", "entity":"company"},
                        {"name":"Google", "type":1, "slug": "www.google.com", "entity":"company"},
                        {"name":"Action 1", "type":2, "slug": "",},
                        {"name":"Action 2", "type":3, "slug": "",},
                        {"name":"Bing", "type":1, "slug": "www.bing.com", "entity":"company"},
                        {"name":"Yandex", "type":1, "slug": "www.yandex.com)", "entity":"company"}
                    ], 
            "links":[
                        {"source":0,"target":3,"value":10},
                        {"source":4,"target":3,"value":1},
                        {"source":1,"target":7,"value":10},
                        {"source":2,"target":4,"value":10},
                        {"source":4,"target":7,"value":1},
                        {"source":4,"target":5,"value":10},
                        {"source":4,"target":6,"value":10},
                        {"source":8,"target":4,"value":1}
                        ]
               }    



    var w = 560,
        h = 500,
        radius = d3.scale.log().domain([0, 312000]).range(["10", "50"]);

    var vis = d3.select("body").append("svg:svg")
        .attr("width", w)
        .attr("height", h);

        vis.append("defs").append("marker")
        .attr("id", "arrowhead")
        .attr("refX", 17 + 3) /*must be smarter way to calculate shift*/
        .attr("refY", 2)
        .attr("markerWidth", 6)
        .attr("markerHeight", 4)
        .attr("orient", "auto")
        .append("path")
            .attr("d", "M 0,0 V 4 L6,2 Z"); //this is actual shape for arrowhead

    //d3.json(data, function(json) {
        var force = self.force = d3.layout.force()
            .nodes(data.nodes)
            .links(data.links)
            .distance(100)
            .charge(-1000)
            .size([w, h])
            .start();



        var link = vis.selectAll("line.link")
            .data(data.links)
            .enter().append("svg:line")
            .attr("class", function (d) { return "link" + d.value +""; })
            .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; })
            .attr("marker-end", function(d) {
                                                if (d.value == 1) {return "url(#arrowhead)"}
                                                else    { return " " }
                                            ;});


        function openLink() {
        return function(d) {
            var url = "";
            if(d.slug != "") {
                url = d.slug
            } //else if(d.type == 2) {
                //url = "clients/" + d.slug
            //} else if(d.type == 3) {
                //url = "agencies/" + d.slug
            //}
            window.open("//"+url)
        }
    }




        var node = vis.selectAll("g.node")
            .data(data.nodes)
          .enter().append("svg:g")
            .attr("class", "node")
            .call(force.drag);

        node.append("circle")
          .attr("class", function(d){ return "node type"+d.type})
            .attr("r", function(d) { return radius(d.value) || 10 })
          //.style("fill", function(d) { return fill(d.type); })
          .call(force.drag);

        node.append("svg:image")
            .attr("class", "circle")
            .attr("xlink:href", function(d){ return d.img_href})
            .attr("x", "-16px")
            .attr("y", "-16px")
            .attr("width", "32px")
            .attr("height", "32px")
            .on("click", openLink());

        node.append("svg:text")
            .attr("class", "nodetext")
            .attr("dx", 0)
            .attr("dy", ".35em")
            .attr("text-anchor", "middle")
            .text(function(d) { return d.name });

        force.on("tick", function() {
          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; });

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

CSS:

.link10 { stroke: #ccc; stroke-width: 3px; stroke-dasharray: 3, 3; }
.link1 { stroke: #000; stroke-width: 3px;}
.nodetext { pointer-events: none; font: 10px sans-serif; }

.node.type1 {
  fill:brown;
}
.node.type2 {
  fill:#337147;
}
.node.type3 {
  fill:blue;
}
.node.type4 {
  fill:red;
}

.node.type5 {
    fill:#1BC9E0;
}

.node.type6 {
    fill:#E01B98;
}

image.circle {
    cursor:pointer;
}

Вы можете редактировать мой jsfiddle, связанный в начале сообщения...


person dzordz    schedule 29.08.2013    source источник
comment
Похоже, вы не используете код для изменения фигур в jsfiddle. Не могли бы вы опубликовать тот, который демонстрирует проблему, пожалуйста?   -  person Lars Kotthoff    schedule 29.08.2013
comment
Я разместил проблемный код с оператором if else выше (см. Первый блок кода)... В jsfiddle я удалил этот оператор, поэтому в нем вы можете видеть только круглые формы.   -  person dzordz    schedule 29.08.2013
comment
Действительно важно, где вы поместите этот код и как вы его назовете.   -  person Lars Kotthoff    schedule 29.08.2013
comment
Итак, чтобы устранить недоразумение, я заменил это: node.append("circle") .attr("class", function(d){ return "node type"+d.type}) .attr("r", function(d) { return radius(d.value) || 10 }) //.style("fill", function(d) { return fill(d.type); }) .call(force.drag); в коде тем первым блоком, который вы можете видеть в начале сообщения.   -  person dzordz    schedule 29.08.2013
comment
Код, который вы разместили выше, определяет функцию. Как вы вызываете эту функцию?   -  person Lars Kotthoff    schedule 29.08.2013
comment
извините, но я нуби... Я все равно не вызывал функцию. Проблема в том, что я не знаю, как правильно разобраться   -  person dzordz    schedule 29.08.2013


Ответы (4)


Решение здесь: http://jsfiddle.net/Bull/4btFx/1/

Я заставил это работать, добавив класс к каждому узлу, а затем используя «selectAll» для каждого класса, чтобы добавить фигуры. В приведенном ниже коде я добавляю класс «узел» и класс, возвращаемый моим JSON (d.type), который является либо «прямоугольным», либо «эллипсным».

  var node = container.append("g")
  .attr("class", "nodes")
  .selectAll(".node")
  .data(graph.nodes)
  .enter().append("g")
  .attr("class", function(d) {
     return d.type + " node";
  })
  .call(drag);

Затем вы можете добавить форму для всех элементов каждого класса:

  d3.selectAll(".rect").append("rect")
  .attr("width", window.nodeWidth)
  .attr("height", window.nodeHeight)
  .attr("class", function(d) { 
     return "color_" + d.class 
  });

  d3.selectAll(".ellipse").append("rect")
  .attr("rx", window.nodeWidth*0.5)
  .attr("ry", window.nodeHeight*0.5)
  .attr("width", window.nodeWidth)
  .attr("height", window.nodeHeight)
  .attr("class", function(d) { 
     return "color_" + d.class 
  });

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

person bu11d0zer    schedule 16.07.2014


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

Подход состоял в том, чтобы создать элементы, а затем расположить их отдельно при вызове моделирования силы (поскольку круг принимает атрибуты cx, cy и r, а прямоугольник принимает x, y, ширину и высоту). Большая часть этого кода следует примеру из этого сообщения блога на носителе: https://medium.com/ninjaconcept/interactive-dynamic-force-directed-graphs-with-d3-da720c6d7811

К вашему сведению, ранее я объявлял «svg» как d3.select(какой-то div с идентификатором или классом) вместе с несколькими не показанными вспомогательными функциями, которые считывают данные (setNodeSize, setNodeColor). Я использовал метод D3.filter для проверки логического поля в данных - является ли узел начальным или нет?

Экземпляр силовой симуляции:

const simulation = d3.forceSimulation()
//the higher the strength (if negative), greater distance between nodes.
.force('charge', d3.forceManyBody().strength(-120)) 
//places the chart in the middle of the content area...if not it's top-left
.force('center', d3.forceCenter(width / 2, height / 2))

Создайте узлы круга:

const nodeCircles = svg.append('g')
.selectAll('circle')
.data(nodes)
.enter()
.filter(d => d.initial)
.append('circle')
.attr('r', setNodeSize)
.attr('class', 'node')
.attr('fill', setNodeColor)
.attr('stroke', '#252525')
.attr('stroke-width', 2)

Затем создайте узлы прямоугольника:

const nodeRectangles = svg.append('g')
.selectAll('rect')
.data(nodes)
.enter()
.filter(d => !d.initial)
.append('rect')
.attr('width', setNodeSize)
.attr('height', setNodeSize)
.attr('class', 'node')
.attr('fill', setNodeColor)
.attr('stroke', '#252525')
.attr('stroke-width', 2)

И затем при вызове моделирования:

simulation.nodes(nodes).on("tick", () => {
nodeCircles
    .attr("cx", node => node.x)
    .attr("cy", node => node.y)
nodeRectangles
    .attr('x', node => node.x)
    .attr('y', node => node.y)
    .attr('transform', 'translate(-10, -7)')

Конечно, это еще не все, чтобы добавить строки/ссылки, текстовые метки и т. д. Не стесняйтесь пинговать меня для получения дополнительного кода. Средний пост, указанный выше, очень полезен!

person Union In Design    schedule 14.07.2020

Я на шаг впереди вас :)

Я решил вашу проблему с использованием «пути» вместо «круг» или «прямоугольник», вы можете посмотреть мое решение и, возможно, помочь мне решить проблему, которая у меня есть...

График, ориентированный на силу D3: обновить позицию узла

person ikamatovic    schedule 29.08.2013