D3js将圆圈附加到强制导向布局

时间:2017-07-21 10:28:43

标签: javascript d3.js

我显然是javascript的新手,当然也是D3js ......

我无法弄清楚如何将“Point”变成附加到节点/路径的“圆圈”?

我猜它与tick()函数有关,但我找不到启发文档:(

var node = svg.selectAll("path.node")
.data(dataset.nodes)
.enter().append("path").attr("class", "node")
.style("cursor","pointer")
.style("fill", function(d) {
        if (d.color) {return d.color;}
        else  { return "#fff" }
    ;})
.call(force.drag);

// add the nodes
node.append("circle")
    .attr("r", 35);

function tick() {
  node.attr("d", function(d) { var p = path({"type":"Feature","geometry":{"type":"Point","coordinates":[d.x, d.y]}}); return p ? p : 'M 0 0' });
  link.attr("d", function(d) { var p = path({"type":"Feature","geometry":{"type":"LineString","coordinates":[[d.source.x, d.source.y],[d.target.x, d.target.y]]}}); return p ? p : 'M 0 0' });
}

我在这里制作了一个代码集https://codepen.io/unit60/pen/KqLEPe

非常感谢正确方向的转向......帮助:)

1 个答案:

答案 0 :(得分:1)

我想我明白你在问什么。在当前的代码段中,您将circletext嵌套在path元素下。这不适用于path的无效子项。而是将它们放在g(组):

var node = svg.selectAll(".node")
    .data(dataset.nodes)
    .enter()
    .append("g")
    .attr("class", "node")
    .style("cursor","pointer")
    .call(force.drag);

// add the nodes
node.append("circle")
    .attr("r", 35)
    .style("fill", function(d) {
      if (d.color) {return d.color;}
      else  { return "#fff"; }
   });


// add the text 
node.append("text")
    .attr("x", 12)
    .attr("dy", ".35em")
    .text(function(d) { return d.name; });

然后我们需要更新tick函数以正确放置组(而不是绘制像它一样的路径):

function tick() {
  node.attr("transform", function(d) {   
    // change this to centroid 
    var p = path.centroid({"type":"Feature","geometry":{"type":"Point","coordinates":[d.x, d.y]}});    
    return "translate(" + p + ")";
  });  

  link.attr("d", function(d) { 
    var p = path({"type":"Feature","geometry":{"type":"LineString","coordinates":[[d.source.x, d.source.y],[d.target.x, d.target.y]]}}); return p ? p : 'M 0 0' });
  }
}

更新codepen;运行示例:

var dataset = {
    nodes: [{
        name: "Location 01",
        class: "overlay01-link",
        color: "red"
    }, {
        name: "Location 02",
        class: "overlay02-link",
        color: "orange"
    }, {
        name: "Location 03"
    }, {
        name: "Location 04"
    }, {
        name: "Location 05"
    }, {
        name: "Location 06"
    }, {
        name: "Location 07"
    }, {
        name: "Location 08"
    }, {
        name: "Location 09"
    }, {
        name: "Location 10"
    }],
    edges: [{
        source: 0,
        target: 1
    }, {
        source: 0,
        target: 2
    }, {
        source: 0,
        target: 3
    }, {
        source: 0,
        target: 4
    }, {
        source: 1,
        target: 5
    }, {
        source: 2,
        target: 5
    }, {
        source: 2,
        target: 5
    }, {
        source: 3,
        target: 4
    }, {
        source: 5,
        target: 8
    }, {
        source: 5,
        target: 9
    }, {
        source: 6,
        target: 7
    }, {
        source: 7,
        target: 8
    }, {
        source: 8,
        target: 9
    }]
};

var projections = {
  "Albers": d3.geo.albers(),
  "Azimuthal Equal Area": d3.geo.azimuthalEqualArea(),
  "Azimuthal Eqidistant": d3.geo.azimuthalEquidistant(),
  "Conic Conformal": d3.geo.conicConformal(),
  "Conic Equal Area": d3.geo.conicEqualArea(),
  "Conic Equidistant": d3.geo.conicEquidistant(),
  "Eqirectangular": d3.geo.equirectangular(),
  "Gnomonic": d3.geo.gnomonic(),
  "Mercator": d3.geo.mercator(),
  "Orthographic": d3.geo.orthographic(),
  "Stereographic": d3.geo.stereographic(),
  "Transverse Mercator": d3.geo.transverseMercator(),
};
var config = { "projection": "Orthographic", "clip": false, "friction": .45, "linkStrength": 1, "linkDistance": 320, "charge": 3, "gravity": .1, "theta": .1 };
var gui = new dat.GUI();
//var projectionChanger = gui.add(config, "projection", ['equalarea', 'equidistant', 'gnomonic', 'orthographic', 'stereographic', 'rectangular']);
var projectionChanger = gui.add(config, "projection", Object.keys(projections));
//http://stackoverflow.com/a/3417242
function wrapIndex(i, i_max) {
     return ((i % i_max) + i_max) % i_max;
}
projectionChanger.onChange(function(value) {
  projection = projections[value]
  .scale(height/2)
  .translate([(width/2)-125, height/2])
  .clipAngle(config["clip"] ? 90 : null)

  path.projection(projections[value])
  return
  if(value == 'rectangular') {
    path = d3.geo.path().projection(function(coordinates){
     console.log(coordinates[0], coordinates[1])
     return [
           wrapIndex(coordinates[0], width),
           wrapIndex(coordinates[1], height),
           ];
    });
    config['clip'] = false
  } else {
    projection.mode(value)
    path = d3.geo.path().projection(projection)
  }

  force.start()
});

var clipChanger = gui.add(config, "clip").listen();
clipChanger.onChange(function(value) {
  projection.clipAngle(value ? 90 : null)
  force.start()
});

var fl = gui.addFolder('Force Layout');
fl.open()

var frictionChanger = fl.add(config, "friction", 0, 1);
frictionChanger.onChange(function(value) {
  force.friction(value)
  force.start()
});

var linkDistanceChanger = fl.add(config, "linkDistance", 0, 400);
linkDistanceChanger.onChange(function(value) {
  force.linkDistance(value)
  force.start()
});

var linkStrengthChanger = fl.add(config, "linkStrength", 0, 1);
linkStrengthChanger.onChange(function(value) {
  force.linkStrength(value)
  force.start()
});

var chargeChanger = fl.add(config,"charge", 0, 500);
chargeChanger.onChange(function(value) {
  force.charge(-value)
  force.start()
});

var gravityChanger = fl.add(config,"gravity", 0, 1);
gravityChanger.onChange(function(value) {
  force.gravity(value)
  force.start()
});

var thetaChanger = fl.add(config,"theta", 0, 1);
thetaChanger.onChange(function(value) {
  force.theta(value)
  force.start()
});

var width = window.innerWidth,
    height = window.innerHeight - 5,
    fill = d3.scale.category20(),
    nodes = [{x: width/2, y: height/2}],
    links = [];

var projection = projections[config["projection"]]
    .scale(height/2)
    .translate([(width/2)-125, height/2])
    .clipAngle(config["clip"] ? 90 : null)

var path = d3.geo.path()
    .projection(projection)

var force = d3.layout.force()
    .linkDistance(config["linkDistance"])
    .linkStrength(config["linkStrength"])
    .gravity(config["gravity"])
    .size([width, height])
    .charge(-config["charge"]);

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height)
    .call(d3.behavior.drag()
      .origin(function() { var r = projection.rotate(); return {x: 2 * r[0], y: -2 * r[1]}; })
      .on("drag", function() { force.start(); var r = [d3.event.x / 2, -d3.event.y / 2, projection.rotate()[2]]; t0 = Date.now(); origin = r; projection.rotate(r); }))

for(x=0;x<100;x++){
  source = nodes[~~(Math.random() * nodes.length)]
  target = {x: source.x + Math.random(), y: source.y + Math.random(), group: Math.random()}
  links.push({source: source, target: target})
  nodes.push(target)
}

var link = svg.selectAll(".link")
    .data(dataset.edges)
    .enter().append("path").attr("class", "link")

var node = svg.selectAll(".node")
    .data(dataset.nodes)
    .enter()
    .append("g")
    .attr("class", "node")
    .style("cursor","pointer")
    .call(force.drag);

// add the nodes
node.append("circle")
    .attr("r", 35)
    .style("fill", function(d) {
      if (d.color) {return d.color;}
      else  { return "#fff"; }
   });


// add the text 
node.append("text")
    .attr("x", 12)
    .attr("dy", ".35em")
    .text(function(d) { return d.name; });
force
    .nodes(dataset.nodes)
    .links(dataset.edges)
    .on("tick", tick)
    .start();

function tick() {
  node.attr("transform", function(d) { 
    
    var p = path.centroid({"type":"Feature","geometry":{"type":"Point","coordinates":[d.x, d.y]}});
    
    return "translate(" + p + ")";

  });  

  link.attr("d", function(d) { var p = path({"type":"Feature","geometry":{"type":"LineString","coordinates":[[d.source.x, d.source.y],[d.target.x, d.target.y]]}}); return p ? p : 'M 0 0' });
}

// action to take on mouse click
function click() {
    d3.select(this).select("text").transition()
        .duration(750)
        .attr("x", 22)
        .style("fill", "steelblue")
        .style("stroke", "lightsteelblue")
        .style("stroke-width", ".5px")
        .style("font", "20px sans-serif");
    d3.select(this).select("circle").transition()
        .duration(750)
        .attr("r", 16)
        .style("fill", "lightsteelblue");
}

// action to take on mouse double click
function dblclick() {
    d3.select(this).select("circle").transition()
        .duration(750)
        .attr("r", 6)
        .style("fill", "#ccc");
    d3.select(this).select("text").transition()
        .duration(750)
        .attr("x", 12)
        .style("stroke", "none")
        .style("fill", "black")
        .style("stroke", "none")
        .style("font", "10px sans-serif");
}
body {
  padding: 0;
  margin: 0;
  background:#222;
}
.node {
  stroke-width: 2.5px;
  width:100px;
  fill:#fff;
}
text {
  fill: #fff;
  font: 10px sans-serif;
  pointer-events: none;
}
circle {
  fill: #ccc;
  stroke: #fff;
  stroke-width: 1.5px;
}

path.link {
  stroke: #fff;
  fill-opacity: 0
}
svg {
  height:100%;
  width:100%;
  display:block;
}
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/dat-gui/0.5/dat.gui.min.js"></script>