Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
557 views
in Technique[技术] by (71.8m points)

javascript - Force directed graphs nodes stick to the center

After following up from this question Insert text inside Circle in D3 chart

My nodes are sticking to the center. I am not sure which property is directing my nodes and their x and y coordinates. I recently chnaged my code to add a g layer to the circles so that i can append text along with shape.

DATA

https://api.myjson.com/bins/hwtj0

UPDATED CODE

    async function d3function() {
        d3.selectAll("svg > *").remove();

        const svg = d3.select("svg");
        file = document.getElementById("selectFile").value;
        console.log("File: " + file)
        var width = 900
        var height = 900 
        svg.style("width", width + 'px').style("height", height + 'px');

        data = (await fetch(file)).json()
        d3.json(file).then(function(data) {

            const links = data.links.map(d => Object.create(d));
            const nodes = data.nodes.map(d => Object.create(d));
            console.log(links.length);
            console.log(nodes.length);
            const simulation = forceSimulation(nodes, links).on("tick", ticked);

            var categorical = [
              { "name" : "schemeAccent", "n": 8},
              { "name" : "schemeDark2", "n": 8},
            ]
            // var colorScale = d3.scaleOrdinal(d3[categorical[6].name])

            var color = d3.scaleOrdinal(d3[categorical[1].name]);


            var drag = simulation => {

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

                  function dragged(d) {
                    d.fx = d3.event.x;
                    d.fy = d3.event.y;
                  }

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

                  return d3.drag()
                      .on("start", dragstarted)
                      .on("drag", dragged)
                      .on("end", dragended);
            }

            const link = svg.append("g")
                  .attr("stroke", "#999")
                  .attr("stroke-opacity", 0.6)
                .selectAll("line")
                .data(links)
                .enter().append("line")
                  .attr("stroke-width", d => Math.sqrt(d.value));

            // link.append("title").text(d => d.value);

            // var circles = svg.append("g")
            //       .attr("stroke", "#fff")
            //       .attr("stroke-width", 1.5)
            //     .selectAll(".circle")
            //     .data(nodes)

            // const node = circles.enter().append("circle")
            //       .attr("r", 5)
            //       .attr("fill", d => color(d.group))
            //       .call(drag(simulation));

            const node = svg.append("g")
                  .attr("stroke", "#fff")
                  .attr("stroke-width", 1.5)
                  .selectAll("circles")
                  .data(nodes)
                  .enter()
                .append("g")
                .classed('circles', true)
                .attr('transform', d => 'translate(' + d.x + ',' + d.y + ')');

                node.append("circle")
                .classed('circle', true)
                .attr("r", 5)
                .attr("fill", d => color(d.group))
                .call(drag(simulation));

                node
                  .append("text")
                  .classed('circleText', true)
                  .attr('dy', '0.35em')
                  .attr('dx', 5)
                  .text(d => "Node: " + d.id);

            node.append("title").text(d => "Node: " + d.id);

            function ticked() {
                link
                    .attr("x1", d => d.source.x)
                    .attr("y1", d => d.source.y)
                    .attr("x2", d => d.target.x)
                    .attr("y2", d => d.target.y);

                node
                    .attr("cx", d => d.x)
                    .attr("cy", d => d.y);
            }

        });

    }



    function forceSimulation(nodes, links) {
      return d3.forceSimulation(nodes)
          .force("link", d3.forceLink(links).id(d => d.id))
          .force("charge", d3.forceManyBody())
          .force("center", d3.forceCenter());
    }

UPDATED OUTPUT enter image description here

EXPECTED OUTPUT along with node names

UPDATED HTML

<g stroke="#fff" stroke-width="1.5">
   <g class="circle" cx="-35.89111508769784" cy="131.13965804447696">
      <circle class="circle" r="5" fill="#1b9e77"></circle>
      <text class="circleText" dy="0.35em" dx="5">Node: 0</text>
      <title>Node: 0</title>
   </g>
   <g class="circle" cx="70.97799024729613" cy="-195.71408429254427">
      <circle class="circle" r="5" fill="#d95f02"></circle>
      <text class="circleText" dy="0.35em" dx="5">Node: 3</text>
      <title>Node: 3</title>
   </g>
   [....]
  </g>
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You have to adapt your code slightly as it currently assumes that you're working with circle elements, where you specify the centres using cx and cy, but you are now using g elements, which use standard x and y coordinates.

First, remove the transform from the g element (that's a leftover from my demo code):

const node = svg.append("g")
  .attr("stroke", "#fff")
  .attr("stroke-width", 1.5)
.selectAll(".circles")  // note - should be .circles!
  .data(nodes)
  .enter()
  .append("g")
  .classed('circles', true)

and in the ticked() function, change the node updating code into a transform that works on g elements (which don't have cx or cy):

node.attr('transform', d => 'translate(' + d.x + ',' + d.y + ')' )

Demo:

var json = {"nodes":[{"id":"0","group":0},{"id":"1","group":1},{"id":"2","group":2},{"id":"3","group":3},{"id":"4","group":4},{"id":"5","group":5},{"id":"6","group":6},{"id":"7","group":7},{"id":"8","group":8},{"id":"9","group":9},{"id":"10","group":10},{"id":"11","group":11},{"id":"12","group":12},{"id":"13","group":13},{"id":"14","group":14},{"id":"15","group":15},{"id":"16","group":16},{"id":"17","group":17},{"id":"18","group":18},{"id":"19","group":19}],"links":[{"source":"0","target":"1","value":1},{"source":"0","target":"18","value":1},{"source":"0","target":"10","value":1},{"source":"0","target":"12","value":1},{"source":"0","target":"5","value":1},{"source":"0","target":"8","value":1},{"source":"1","target":"0","value":1},{"source":"1","target":"9","value":1},{"source":"1","target":"4","value":1},{"source":"2","target":"4","value":1},{"source":"2","target":"17","value":1},{"source":"2","target":"13","value":1},{"source":"2","target":"15","value":1},{"source":"3","target":"6","value":1},{"source":"4","target":"14","value":1},{"source":"4","target":"2","value":1},{"source":"4","target":"5","value":1},{"source":"4","target":"19","value":1},{"source":"4","target":"1","value":1},{"source":"5","target":"4","value":1},{"source":"5","target":"0","value":1},{"source":"6","target":"3","value":1},{"source":"7","target":"18","value":1},{"source":"7","target":"16","value":1},{"source":"8","target":"0","value":1},{"source":"9","target":"1","value":1},{"source":"10","target":"0","value":1},{"source":"10","target":"15","value":1},{"source":"12","target":"0","value":1},{"source":"13","target":"15","value":1},{"source":"13","target":"2","value":1},{"source":"14","target":"4","value":1},{"source":"15","target":"13","value":1},{"source":"15","target":"10","value":1},{"source":"15","target":"2","value":1},{"source":"16","target":"7","value":1},{"source":"17","target":"2","value":1},{"source":"18","target":"0","value":1},{"source":"18","target":"7","value":1},{"source":"19","target":"4","value":1},{"source":"19","target":"4","value":1}]};


d3.selectAll("svg > *").remove();

const svg = d3.select("svg");
var width = 900
var height = 900
svg.style("width", width + 'px').style("height", height + 'px');

const links = json.links.map(d => Object.create(d));
const nodes = json.nodes.map(d => Object.create(d));
const simulation = forceSimulation(nodes, links).on("tick", ticked);

var categorical = [
{
  "name": "schemeAccent",
  "n": 8
},
{
  "name": "schemeDark2",
  "n": 8
}, ]

var color = d3.scaleOrdinal(d3[categorical[1].name]);


var drag = simulation => {

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

  function dragged(d) {
    d.fx = d3.event.x;
    d.fy = d3.event.y;
  }

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

  return d3.drag()
    .on("start", dragstarted)
    .on("drag", dragged)
    .on("end", dragended);
}

const link = svg.append("g")
  .attr("stroke", "#999")
  .attr("stroke-opacity", 0.6)
  .selectAll("line")
  .data(links)
  .enter().append("line")
  .attr("stroke-width", d => Math.sqrt(d.value));

const node = svg.append("g")
  .attr("stroke", "#fff")
  .attr("stroke-width", 1.5)
  .selectAll(".circles")
  .data(nodes)
  .enter()
  .append("g")
  .classed('circles', true)
  .call(drag(simulation))
//    .attr('transform', d => 'translate(' + d.x + ',' + d.y + ')');

const circle = node.append("circle")
  .classed('circle', true)
  .attr("r", 5)
  .attr("fill", d => color(d.group))

node
  .append("text")
  .classed('circleText', true)
  .attr('dy', '0.35em')
  .attr('dx', 5)
  .text(d => "Node: " + d.id);

node.append("title").text(d => "Node: " + d.id);

function ticked() {
  link
    .attr("x1", d => d.source.x)
    .attr("y1", d => d.source.y)
    .attr("x2", d => d.target.x)
    .attr("y2", d => d.target.y);

  node.attr('transform', d => 'translate(' + d.x + ',' + d.y + ')')
}

function forceSimulation(nodes, links) {
  return d3.forceSimulation(nodes)
    .force("link", d3.forceLink(links).id(d => d.id))
    .force("charge", d3.forceManyBody())
    .force("center", d3.forceCenter());
}
.circleText { fill: black; stroke: none }
<script src="//d3js.org/d3.v5.js"></script>
<svg></svg>

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...