D3 v4, перемещая и круг, и текст одновременно

1

В качестве отправной точки я использую этот код для создания ориентированного графа с D3 v4.

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

Однако круг и текст вообще не отображаются, даже если я изменяю функцию tick для обновления своих позиций, например:

circle
    .attr("cx", function(d) { return d.x; })
    .attr("cy", function(d) { return d.y; });

Моя реализация может быть найдена в этом jsfiddle и ниже:

// Create somewhere to put the force directed graph
var svg = d3.select("svg"),
    width = +svg.attr("width"),
    height = +svg.attr("height");

var radius = 20;

// DATA
var nodes_data =  [
    {"id": "Lillian", "sex": "F"},
    {"id": "Gordon", "sex": "M"},
    {"id": "Sylvester", "sex": "M"},
    {"id": "Mary", "sex": "F"},
    {"id": "Helen", "sex": "F"},
    {"id": "Jamie", "sex": "M"},
    {"id": "Jessie", "sex": "F"},
    {"id": "Ashton", "sex": "M"},
    {"id": "Duncan", "sex": "M"},
    {"id": "Evette", "sex": "F"},
    {"id": "Mauer", "sex": "M"},
    {"id": "Fray", "sex": "F"},
    {"id": "Duke", "sex": "M"},
    {"id": "Baron", "sex": "M"},
    {"id": "Infante", "sex": "M"},
    {"id": "Percy", "sex": "M"},
    {"id": "Cynthia", "sex": "F"},
    {"id": "Feyton", "sex": "M"},
    {"id": "Lesley", "sex": "F"},
    {"id": "Yvette", "sex": "F"},
    {"id": "Maria", "sex": "F"},
    {"id": "Lexy", "sex": "F"},
    {"id": "Peter", "sex": "M"},
    {"id": "Ashley", "sex": "F"},
    {"id": "Finkler", "sex": "M"},
    {"id": "Damo", "sex": "M"},
    {"id": "Imogen", "sex": "F"}];
var links_data = [
    {"source": "Sylvester", "target": "Gordon", "type":"A" },
    {"source": "Sylvester", "target": "Lillian", "type":"A" },
    {"source": "Sylvester", "target": "Mary", "type":"A"},
    {"source": "Sylvester", "target": "Jamie", "type":"A"},
    {"source": "Sylvester", "target": "Jessie", "type":"A"},
    {"source": "Sylvester", "target": "Helen", "type":"A"},
    {"source": "Helen", "target": "Gordon", "type":"A"},
    {"source": "Mary", "target": "Lillian", "type":"A"},
    {"source": "Ashton", "target": "Mary", "type":"A"},
    {"source": "Duncan", "target": "Jamie", "type":"A"},
    {"source": "Gordon", "target": "Jessie", "type":"A"},
    {"source": "Sylvester", "target": "Fray", "type":"E"},
    {"source": "Fray", "target": "Mauer", "type":"A"},
    {"source": "Fray", "target": "Cynthia", "type":"A"},
    {"source": "Fray", "target": "Percy", "type":"A"},
    {"source": "Percy", "target": "Cynthia", "type":"A"},
    {"source": "Infante", "target": "Duke", "type":"A"},
    {"source": "Duke", "target": "Gordon", "type":"A"},
    {"source": "Duke", "target": "Sylvester", "type":"A"},
    {"source": "Baron", "target": "Duke", "type":"A"},
    {"source": "Baron", "target": "Sylvester", "type":"E"},
    {"source": "Evette", "target": "Sylvester", "type":"E"},
    {"source": "Cynthia", "target": "Sylvester", "type":"E"},
    {"source": "Cynthia", "target": "Jamie", "type":"E"},
    {"source": "Mauer", "target": "Jessie", "type":"E"},
    {"source": "Duke", "target": "Lexy", "type":"A"},
    {"source": "Feyton", "target": "Lexy", "type":"A"},
    {"source": "Maria", "target": "Feyton", "type":"A"},
    {"source": "Baron", "target": "Yvette", "type":"E"},
    {"source": "Evette", "target": "Maria", "type":"E"},
    {"source": "Cynthia", "target": "Yvette", "type":"E"},
    {"source": "Maria", "target": "Jamie", "type":"E"},
    {"source": "Maria", "target": "Lesley", "type":"E"},
    {"source": "Ashley", "target": "Damo", "type":"A"},
    {"source": "Damo", "target": "Lexy", "type":"A"},
    {"source": "Maria", "target": "Feyton", "type":"A"},
    {"source": "Finkler", "target": "Ashley", "type":"E"},
    {"source": "Sylvester", "target": "Maria", "type":"E"},
    {"source": "Peter", "target": "Finkler", "type":"E"},
    {"source": "Ashley", "target": "Gordon", "type":"E"},
    {"source": "Maria", "target": "Imogen", "type":"E"}];

// Set up the simulation and add forces
var simulation = d3.forceSimulation()
                    .nodes(nodes_data);

var link_force =  d3.forceLink(links_data)
                        .id(function(d) { return d.id; });

var charge_force = d3.forceManyBody()
    .strength(-200);

var center_force = d3.forceCenter(width / 2, height / 2);

simulation
    .force("charge_force", charge_force)
    .force("center_force", center_force)
    .force("links",link_force);


// Add tick instructions:
simulation.on("tick", tickActions );

// Add encompassing group for the zoom
var g = svg.append("g")
    .attr("class", "everything");

// Draw lines for the links
var link = g.append("g")
        .attr("class", "links")
            .selectAll("line")
            .data(links_data)
            .enter()
                .append("line")
                    .attr("stroke-width", 2)
                    .style("stroke", linkColour);

// Draw circles and texts for the nodes
var nodes = g.append("g")
        .attr("class", "nodes");

var node = nodes.selectAll("node")
        .data(nodes_data)
        .enter()
            .append("node");

var circle = node.append("circle")
        .attr("r", radius)
        .attr("fill", circleColour)
        .attr("cx", function(d){return d.x})
        .attr("cy", function(d){return d.y});

var text = node.append("text")
        .text("asdasdasdas")
        .attr("cx", function(d){return d.x})
        .attr("cy", function(d){return d.y});

// Add drag capabilities
var drag_handler = d3.drag()
    .on("start", drag_start)
    .on("drag", drag_drag)
    .on("end", drag_end);

drag_handler(node);

// Add zoom capabilities
var zoom_handler = d3.zoom()
    .on("zoom", zoom_actions);

zoom_handler(svg);

/** Functions **/

function circleColour(d){
    if(d.sex == "M"){
        return "blue";
    } else {
        return "pink";
    }
}

// Function to choose the line colour and thickness
function linkColour(d){
    return "black";
}

// Drag functions
function drag_start(d) {
 if (!d3.event.active) simulation.alphaTarget(0.3).restart();
    d.fx = d.x;
    d.fy = d.y;
}

// Make sure you can't drag the circle outside the box
function drag_drag(d) {
  d.fx = d3.event.x;
  d.fy = d3.event.y;
}

function drag_end(d) {
  if (!d3.event.active) simulation.alphaTarget(0);
  d.fx = null;
  d.fy = null;
}

// Zoom functions
function zoom_actions(){
    g.attr("transform", d3.event.transform)
}

function tickActions() {
    // update node positions each tick of the simulation
    node
        .attr("cx", function(d) { return d.x; })
        .attr("cy", function(d) { return d.y; });

    // update link positions
    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; });
  }
Теги:
d3.js

2 ответа

3
Лучший ответ

В вашем коде есть три проблемы:

  1. Нет элемента SVG, называемого <node>. Таким образом, вместо...

    .append("node");
    

    ... так должно быть:

    .append("g");
    
  2. Текстовые элементы не имеют атрибутов cx или cy.

  3. Вместо изменения cx или cy node, у которого нет (поскольку это элемент <g>), вы должны его перевести:

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

Все вместе, это ваш обновленный код:

.links line {
  stroke: #999;
  stroke-opacity: 0.6;
}

.nodes circle {
  stroke: black;
  stroke-width: 0px;
}
<svg width="800" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
  // Create somewhere to put the force directed graph
  var svg = d3.select("svg"),
    width = +svg.attr("width"),
    height = +svg.attr("height");

  var radius = 20;

  // DATA
  var nodes_data = [{
    "id": "Lillian",
    "sex": "F"
  }, {
    "id": "Gordon",
    "sex": "M"
  }, {
    "id": "Sylvester",
    "sex": "M"
  }, {
    "id": "Mary",
    "sex": "F"
  }, {
    "id": "Helen",
    "sex": "F"
  }, {
    "id": "Jamie",
    "sex": "M"
  }, {
    "id": "Jessie",
    "sex": "F"
  }, {
    "id": "Ashton",
    "sex": "M"
  }, {
    "id": "Duncan",
    "sex": "M"
  }, {
    "id": "Evette",
    "sex": "F"
  }, {
    "id": "Mauer",
    "sex": "M"
  }, {
    "id": "Fray",
    "sex": "F"
  }, {
    "id": "Duke",
    "sex": "M"
  }, {
    "id": "Baron",
    "sex": "M"
  }, {
    "id": "Infante",
    "sex": "M"
  }, {
    "id": "Percy",
    "sex": "M"
  }, {
    "id": "Cynthia",
    "sex": "F"
  }, {
    "id": "Feyton",
    "sex": "M"
  }, {
    "id": "Lesley",
    "sex": "F"
  }, {
    "id": "Yvette",
    "sex": "F"
  }, {
    "id": "Maria",
    "sex": "F"
  }, {
    "id": "Lexy",
    "sex": "F"
  }, {
    "id": "Peter",
    "sex": "M"
  }, {
    "id": "Ashley",
    "sex": "F"
  }, {
    "id": "Finkler",
    "sex": "M"
  }, {
    "id": "Damo",
    "sex": "M"
  }, {
    "id": "Imogen",
    "sex": "F"
  }];
  var links_data = [{
    "source": "Sylvester",
    "target": "Gordon",
    "type": "A"
  }, {
    "source": "Sylvester",
    "target": "Lillian",
    "type": "A"
  }, {
    "source": "Sylvester",
    "target": "Mary",
    "type": "A"
  }, {
    "source": "Sylvester",
    "target": "Jamie",
    "type": "A"
  }, {
    "source": "Sylvester",
    "target": "Jessie",
    "type": "A"
  }, {
    "source": "Sylvester",
    "target": "Helen",
    "type": "A"
  }, {
    "source": "Helen",
    "target": "Gordon",
    "type": "A"
  }, {
    "source": "Mary",
    "target": "Lillian",
    "type": "A"
  }, {
    "source": "Ashton",
    "target": "Mary",
    "type": "A"
  }, {
    "source": "Duncan",
    "target": "Jamie",
    "type": "A"
  }, {
    "source": "Gordon",
    "target": "Jessie",
    "type": "A"
  }, {
    "source": "Sylvester",
    "target": "Fray",
    "type": "E"
  }, {
    "source": "Fray",
    "target": "Mauer",
    "type": "A"
  }, {
    "source": "Fray",
    "target": "Cynthia",
    "type": "A"
  }, {
    "source": "Fray",
    "target": "Percy",
    "type": "A"
  }, {
    "source": "Percy",
    "target": "Cynthia",
    "type": "A"
  }, {
    "source": "Infante",
    "target": "Duke",
    "type": "A"
  }, {
    "source": "Duke",
    "target": "Gordon",
    "type": "A"
  }, {
    "source": "Duke",
    "target": "Sylvester",
    "type": "A"
  }, {
    "source": "Baron",
    "target": "Duke",
    "type": "A"
  }, {
    "source": "Baron",
    "target": "Sylvester",
    "type": "E"
  }, {
    "source": "Evette",
    "target": "Sylvester",
    "type": "E"
  }, {
    "source": "Cynthia",
    "target": "Sylvester",
    "type": "E"
  }, {
    "source": "Cynthia",
    "target": "Jamie",
    "type": "E"
  }, {
    "source": "Mauer",
    "target": "Jessie",
    "type": "E"
  }, {
    "source": "Duke",
    "target": "Lexy",
    "type": "A"
  }, {
    "source": "Feyton",
    "target": "Lexy",
    "type": "A"
  }, {
    "source": "Maria",
    "target": "Feyton",
    "type": "A"
  }, {
    "source": "Baron",
    "target": "Yvette",
    "type": "E"
  }, {
    "source": "Evette",
    "target": "Maria",
    "type": "E"
  }, {
    "source": "Cynthia",
    "target": "Yvette",
    "type": "E"
  }, {
    "source": "Maria",
    "target": "Jamie",
    "type": "E"
  }, {
    "source": "Maria",
    "target": "Lesley",
    "type": "E"
  }, {
    "source": "Ashley",
    "target": "Damo",
    "type": "A"
  }, {
    "source": "Damo",
    "target": "Lexy",
    "type": "A"
  }, {
    "source": "Maria",
    "target": "Feyton",
    "type": "A"
  }, {
    "source": "Finkler",
    "target": "Ashley",
    "type": "E"
  }, {
    "source": "Sylvester",
    "target": "Maria",
    "type": "E"
  }, {
    "source": "Peter",
    "target": "Finkler",
    "type": "E"
  }, {
    "source": "Ashley",
    "target": "Gordon",
    "type": "E"
  }, {
    "source": "Maria",
    "target": "Imogen",
    "type": "E"
  }];

  // Set up the simulation and add forces
  var simulation = d3.forceSimulation()
    .nodes(nodes_data);

  var link_force = d3.forceLink(links_data)
    .id(function(d) {
      return d.id;
    });

  var charge_force = d3.forceManyBody()
    .strength(-200);

  var center_force = d3.forceCenter(width / 2, height / 2);

  simulation
    .force("charge_force", charge_force)
    .force("center_force", center_force)
    .force("links", link_force);


  // Add tick instructions:
  simulation.on("tick", tickActions);

  // Add encompassing group for the zoom
  var g = svg.append("g")
    .attr("class", "everything");

  // Draw lines for the links
  var link = g.append("g")
    .attr("class", "links")
    .selectAll("line")
    .data(links_data)
    .enter()
    .append("line")
    .attr("stroke-width", 2)
    .style("stroke", linkColour);

  // Draw circles and texts for the nodes
  var nodes = g.append("g")
    .attr("class", "nodes");

  var node = nodes.selectAll("node")
    .data(nodes_data)
    .enter()
    .append("g");

  var circle = node.append("circle")
    .attr("r", radius)
    .attr("fill", circleColour);

  var text = node.append("text")
    .text("asdasdasdas")
    .attr("y", -22)
    .attr("text-anchor", "middle");

  // Add drag capabilities
  var drag_handler = d3.drag()
    .on("start", drag_start)
    .on("drag", drag_drag)
    .on("end", drag_end);

  drag_handler(node);

  // Add zoom capabilities
  var zoom_handler = d3.zoom()
    .on("zoom", zoom_actions);

  zoom_handler(svg);

  /** Functions **/

  function circleColour(d) {
    if (d.sex == "M") {
      return "blue";
    } else {
      return "pink";
    }
  }

  // Function to choose the line colour and thickness
  function linkColour(d) {
    return "black";
  }

  // Drag functions
  function drag_start(d) {
    if (!d3.event.active) simulation.alphaTarget(0.3).restart();
    d.fx = d.x;
    d.fy = d.y;
  }

  // Make sure you can't drag the circle outside the box
  function drag_drag(d) {
    d.fx = d3.event.x;
    d.fy = d3.event.y;
  }

  function drag_end(d) {
    if (!d3.event.active) simulation.alphaTarget(0);
    d.fx = null;
    d.fy = null;
  }

  // Zoom functions
  function zoom_actions() {
    g.attr("transform", d3.event.transform)
  }

  function tickActions() {
    // update node positions each tick of the simulation
    node.attr("transform", function(d) {
      return "translate(" + d.x + "," + d.y + ")"
    });

    // update link positions
    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;
      });
  }

</script>
  • 0
    Ах, глупый я, с добавлением ... Дополнительный вопрос: теперь тексты прикрепляются к концам строки, а не к кругу, также круги несколько «случайно смещены». Требуется ли какой-то регулярный HTML-код, чтобы круги были прикреплены на концах линий, а текст должен быть над / поверх кругов?
  • 0
    Измените положения y и x в выделении текста. Посмотрите на ответ еще раз, я только что отредактировал фрагмент.
1

Вот что, я полагаю, вы хотите: https://jsfiddle.net/n504g8vw/1/

Как я это сделал? Прежде всего, я удалил элемент узла и изменил его для элемента ag с узлом класса.

var node = nodes.selectAll("g")
    .data(nodes_data)
    .enter()
    .append("g")
        .attr('class', 'node');

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

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

Узлы теперь перемещаются с графиком, но центр круга не совпадает с центром узлов. Вы должны установить центр каждого круга равным 0,0.

var circle = node.append("circle")
    .attr("r", radius)
    .attr("fill", circleColour)
    .attr("cx",0)
    .attr("cy", 0);

Наконец, вы хотите, чтобы текст был сосредоточен на узле, поэтому вы помещаете в текст текст css, вы можете сделать это с помощью D3, как это (или непосредственно на вашем листе css)

var text = node.append("text")
    .text("asdasdasdas")
    .style("text-anchor", "middle");

Ещё вопросы

Сообщество Overcoder
Наверх
Меню