d3js强制有向图箭头未显示

时间:2020-05-12 16:35:41

标签: d3.js

我有一个使用d3.js版本5的力导向图,并且想为每个链接添加箭头。我已在下面添加了代码,并发布了a jsfiddle。我正在寻求有关为何箭头(id = {end-arrow)未显示在图中的指导。我在end-arrowlink的声明中引用.attr("marker-end","url(#end-arrow)")作为属性,我不知道如何解决此问题。

HTML:

<svg id="viz"></svg>

带有d3.js版本5的Javascript:

// based on  https://bl.ocks.org/mapio/53fed7d84cd1812d6a6639ed7aa83868

var width = 600;
var height = 400;
var border = 1;
var bordercolor="black";
var color = d3.scaleOrdinal(d3.schemeCategory10); // coloring of nodes

var graph = {
  "nodes": [
    {"id": "4718871", "group": 2, "img": "https://derivationmap.net/static/multiplybothsidesby.png", "width": 298, "height": 30, "linear index": 2},
    {"id": "2131616531", "group": 0, "img": "https://derivationmap.net/static/2131616531.png", "width": 103, "height": 30, "linear index": 0},
    {"id": "9565166889", "group": 0, "img": "https://derivationmap.net/static/9565166889.png", "width": 24, "height": 23, "linear index": 0},
    {"id": "9040079362", "group": 0, "img": "https://derivationmap.net/static/9040079362.png", "width": 18, "height": 30, "linear index": 0},
    {"id": "9278347", "group": 1, "img": "https://derivationmap.net/static/declareinitialexpr.png", "width": 270, "height": 30, "linear index": 1},
    {"id": "6286448", "group": 4, "img": "https://derivationmap.net/static/declarefinalexpr.png", "width": 255, "height": 30, "linear index": 4},
    {"id": "2113211456", "group": 0, "img": "https://derivationmap.net/static/2113211456.png", "width": 121, "height": 34, "linear index": 0},
    {"id": "2169431", "group": 3, "img": "https://derivationmap.net/static/dividebothsidesby.png", "width": 260, "height": 30, "linear index": 3},
    {"id": "3131111133", "group": 0, "img": "https://derivationmap.net/static/3131111133.png", "width": 121, "height": 34, "linear index": 0}
  ],
  "links": [
    {"source": "2169431", "target": "2113211456", "value": 1},
    {"source": "2113211456", "target": "6286448", "value": 1},
    {"source": "9278347", "target": "3131111133", "value": 1},
    {"source": "4718871", "target": "2131616531", "value": 1},
    {"source": "9040079362", "target": "4718871", "value": 1},
    {"source": "2131616531", "target": "2169431", "value": 1},
    {"source": "3131111133", "target": "4718871", "value": 1},
    {"source": "9565166889", "target": "2169431", "value": 1}
  ]
};

var label = {
    "nodes": [],
    "links": []
};

graph.nodes.forEach(function(d, i) {
    label.nodes.push({node: d});
    label.nodes.push({node: d});
    label.links.push({
        source: i * 2,
        target: i * 2 + 1
    });
});

var labelLayout = d3.forceSimulation(label.nodes)
    .force("charge", d3.forceManyBody().strength(-50))
    .force("link", d3.forceLink(label.links).distance(0).strength(2));

var graphLayout = d3.forceSimulation(graph.nodes)
    .force("charge", d3.forceManyBody().strength(-3000))
    .force("center", d3.forceCenter(width / 2, height / 2))
    .force("x", d3.forceX(width / 2).strength(1))
    .force("y", d3.forceY(height / 2).strength(1))
    .force("link", d3.forceLink(graph.links).id(function(d) {return d.id; }).distance(50).strength(1))
    .on("tick", ticked);

var adjlist = [];

graph.links.forEach(function(d) {
    adjlist[d.source.index + "-" + d.target.index] = true;
    adjlist[d.target.index + "-" + d.source.index] = true;
});

function neigh(a, b) {
    return a == b || adjlist[a + "-" + b];
}


var svg = d3.select("#viz").attr("width", width).attr("height", height);

// define arrow markers for graph links
svg.append("svg:defs").append("svg:marker")
    .attr("id", "end-arrow")
    .attr("viewBox", "0 -5 10 10")
    .attr("refX", 6)
    .attr("markerWidth", 3)
    .attr("markerHeight", 3)
    .attr("orient", "auto")
  .append("svg:line")
    .attr("d", "M0,-5L10,0L0,5")
    .attr("fill", "black");

// http://bl.ocks.org/AndrewStaroscik/5222370
            var borderPath = svg.append("rect")
                .attr("x", 0)
                .attr("y", 0)
                .attr("height", height)
                .attr("width", width)
                .style("stroke", bordercolor)
                .style("fill", "none")
                .style("stroke-width", border);

var container = svg.append("g");

svg.call(
    d3.zoom()
        .scaleExtent([.1, 4])
        .on("zoom", function() { container.attr("transform", d3.event.transform); })
);

var link = container.append("g").attr("class", "links")
    .selectAll("line")
    .data(graph.links)
    .enter()
    .append("line")
    .attr("stroke", "#aaa")
    .attr("stroke-width", "1px")
    .attr("marker-end","url(#end-arrow)");

var node = container.append("g").attr("class", "nodes")
    .selectAll("g")
    .data(graph.nodes)
    .enter()
    .append("circle")
    .attr("r", 5)
    .attr("fill", function(d) { return color(d.group); })

node.on("mouseover", focus).on("mouseout", unfocus);

node.call(
    d3.drag()
        .on("start", dragstarted)
        .on("drag", dragged)
        .on("end", dragended)
);

var labelNode = container.append("g").attr("class", "labelNodes")
    .selectAll("text")
    .data(label.nodes)
    .enter()
    .append("image")
    // alternative option, unverified: https://stackoverflow.com/questions/39908583/d3-js-labeling-nodes-with-image-in-force-layout
    // I have no idea why the i%2 is needed; without it I get two images per node
    // switching between i%2==1 and i%2==0 produces different image locations (?)
    .attr("xlink:href", function(d, i) { return i % 2 == 1 ? "" : d.node.img; } )
    .attr("x", 0)
    .attr("y", 0)
    // the following alter the image size
    .attr("width", function(d, i) { return d.node.width/2; })
    .attr("height", function(d, i) { return d.node.height/2; })
//    .append("text")
//    .text(function(d, i) { return i % 2 == 0 ? "" : d.node.id; })
//    .style("fill", "#555")
//    .style("font-family", "Arial")
//    .style("font-size", 12)
    .style("pointer-events", "none"); // to prevent mouseover/drag capture

node.on("mouseover", focus).on("mouseout", unfocus);

function ticked() {

    node.call(updateNode);
    link.call(updateLink);

    labelLayout.alphaTarget(0.3).restart();
    labelNode.each(function(d, i) {
        if(i % 2 == 0) {
            d.x = d.node.x;
            d.y = d.node.y;
        } else {
            var b = this.getBBox();

            var diffX = d.x - d.node.x;
            var diffY = d.y - d.node.y;

            var dist = Math.sqrt(diffX * diffX + diffY * diffY);

            var shiftX = b.width * (diffX - dist) / (dist * 2);
            shiftX = Math.max(-b.width, Math.min(0, shiftX));
            var shiftY = 16;
            this.setAttribute("transform", "translate(" + shiftX + "," + shiftY + ")");
        }
    });
    labelNode.call(updateNode);

}

function fixna(x) {
    if (isFinite(x)) return x;
    return 0;
}

function focus(d) {
    var index = d3.select(d3.event.target).datum().index;
    node.style("opacity", function(o) {
        return neigh(index, o.index) ? 1 : 0.1;
    });
    labelNode.attr("display", function(o) {
      return neigh(index, o.node.index) ? "block": "none";
    });
    link.style("opacity", function(o) {
        return o.source.index == index || o.target.index == index ? 1 : 0.1;
    });
}

function unfocus() {
   labelNode.attr("display", "block");
   node.style("opacity", 1);
   link.style("opacity", 1);
}

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

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

function dragstarted(d) {
    d3.event.sourceEvent.stopPropagation();
    if (!d3.event.active) graphLayout.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) graphLayout.alphaTarget(0);
    d.fx = null;
    d.fy = null;
}

1 个答案:

答案 0 :(得分:0)

基于d3js Slack渠道的反馈,存在两个问题:

  1. 在箭头的定义中,需要.append("svg:path")
  2. 修复该问题后,箭头太小了,隐藏在节点圆的后面。通过增大箭头,可以看到它们。

我已经更新了http://bl.ocks.org/bhpayne/0a8ef2ae6d79aa185dcf2c3a385daf25,修改后的代码如下:

HTML

<svg id="viz"></svg>

Javascript + d3js

// based on  https://bl.ocks.org/mapio/53fed7d84cd1812d6a6639ed7aa83868

var width = 600;
var height = 400;
var border = 3;
var bordercolor = "black";
var color = d3.scaleOrdinal(d3.schemeCategory10); // coloring of nodes

var graph = {
  "nodes": [{
      "id": "4718871",
      "group": 2,
      "img": "https://derivationmap.net/static/multiplybothsidesby.png",
      "width": 298,
      "height": 30,
      "linear index": 2
    },
    {
      "id": "2131616531",
      "group": 0,
      "img": "https://derivationmap.net/static/2131616531.png",
      "width": 103,
      "height": 30,
      "linear index": 0
    },
    {
      "id": "9565166889",
      "group": 0,
      "img": "https://derivationmap.net/static/9565166889.png",
      "width": 24,
      "height": 23,
      "linear index": 0
    },
    {
      "id": "9040079362",
      "group": 0,
      "img": "https://derivationmap.net/static/9040079362.png",
      "width": 18,
      "height": 30,
      "linear index": 0
    },
    {
      "id": "9278347",
      "group": 1,
      "img": "https://derivationmap.net/static/declareinitialexpr.png",
      "width": 270,
      "height": 30,
      "linear index": 1
    },
    {
      "id": "6286448",
      "group": 4,
      "img": "https://derivationmap.net/static/declarefinalexpr.png",
      "width": 255,
      "height": 30,
      "linear index": 4
    },
    {
      "id": "2113211456",
      "group": 0,
      "img": "https://derivationmap.net/static/2113211456.png",
      "width": 121,
      "height": 34,
      "linear index": 0
    },
    {
      "id": "2169431",
      "group": 3,
      "img": "https://derivationmap.net/static/dividebothsidesby.png",
      "width": 260,
      "height": 30,
      "linear index": 3
    },
    {
      "id": "3131111133",
      "group": 0,
      "img": "https://derivationmap.net/static/3131111133.png",
      "width": 121,
      "height": 34,
      "linear index": 0
    }
  ],
  "links": [{
      "source": "2169431",
      "target": "2113211456",
      "value": 1
    },
    {
      "source": "2113211456",
      "target": "6286448",
      "value": 1
    },
    {
      "source": "9278347",
      "target": "3131111133",
      "value": 1
    },
    {
      "source": "4718871",
      "target": "2131616531",
      "value": 1
    },
    {
      "source": "9040079362",
      "target": "4718871",
      "value": 1
    },
    {
      "source": "2131616531",
      "target": "2169431",
      "value": 1
    },
    {
      "source": "3131111133",
      "target": "4718871",
      "value": 1
    },
    {
      "source": "9565166889",
      "target": "2169431",
      "value": 1
    }
  ]
};

var label = {
  "nodes": [],
  "links": []
};

graph.nodes.forEach(function(d, i) {
  label.nodes.push({
    node: d
  });
  label.nodes.push({
    node: d
  });
  label.links.push({
    source: i * 2,
    target: i * 2 + 1
  });
});

var labelLayout = d3.forceSimulation(label.nodes)
  .force("charge", d3.forceManyBody().strength(-50))
  .force("link", d3.forceLink(label.links).distance(0).strength(2));

var graphLayout = d3.forceSimulation(graph.nodes)
  .force("charge", d3.forceManyBody().strength(-3000))
  .force("center", d3.forceCenter(width / 2, height / 2))
  .force("x", d3.forceX(width / 2).strength(1))
  .force("y", d3.forceY(height / 2).strength(1))
  .force("link", d3.forceLink(graph.links).id(function(d) {
    return d.id;
  }).distance(50).strength(1))
  .on("tick", ticked);

var adjlist = [];

graph.links.forEach(function(d) {
  adjlist[d.source.index + "-" + d.target.index] = true;
  adjlist[d.target.index + "-" + d.source.index] = true;
});

function neigh(a, b) {
  return a == b || adjlist[a + "-" + b];
}


var svg = d3.select("#viz").attr("width", width).attr("height", height);

// define arrow markers for graph links
svg.append("svg:defs").append("svg:marker")
  .attr("id", "end-arrow")
  .attr("viewBox", "0 -5 10 10")
  .attr("refX", 10)
  .attr("markerWidth", 20)
  .attr("markerHeight", 20)
  .attr("orient", "auto")
  .append("svg:path")
  .attr("d", "M0,-5L20,0L0,5")
  .attr("fill", "#000");

// http://bl.ocks.org/AndrewStaroscik/5222370
var borderPath = svg.append("rect")
  .attr("x", 0)
  .attr("y", 0)
  .attr("height", height)
  .attr("width", width)
  .style("stroke", bordercolor)
  .style("fill", "none")
  .style("stroke-width", border);

var container = svg.append("g");

svg.call(
  d3.zoom()
  .scaleExtent([.1, 4])
  .on("zoom", function() {
    container.attr("transform", d3.event.transform);
  })
);

var link = container.append("g").attr("class", "links")
  .selectAll("line")
  .data(graph.links)
  .enter()
  .append("line")
  .attr("stroke", "#aaa")
  .attr("marker-end", "url(#end-arrow)")
  .attr("stroke-width", "1px");

var node = container.append("g").attr("class", "nodes")
  .selectAll("g")
  .data(graph.nodes)
  .enter()
  .append("circle")
  .attr("r", 10)
  .attr("fill", function(d) {
    return color(d.group);
  })

node.on("mouseover", focus).on("mouseout", unfocus);

node.call(
  d3.drag()
  .on("start", dragstarted)
  .on("drag", dragged)
  .on("end", dragended)
);

var labelNode = container.append("g").attr("class", "labelNodes")
  .selectAll("text")
  .data(label.nodes)
  .enter()
  .append("image")
  // alternative option, unverified: https://stackoverflow.com/questions/39908583/d3-js-labeling-nodes-with-image-in-force-layout
  // I have no idea why the i%2 is needed; without it I get two images per node
  // switching between i%2==1 and i%2==0 produces different image locations (?)
  .attr("xlink:href", function(d, i) {
    return i % 2 == 1 ? "" : d.node.img;
  })
  .attr("x", 0)
  .attr("y", 0)
  // the following alter the image size
  .attr("width", function(d, i) {
    return d.node.width / 2;
  })
  .attr("height", function(d, i) {
    return d.node.height / 2;
  })
  //    .append("text")
  //    .text(function(d, i) { return i % 2 == 0 ? "" : d.node.id; })
  //    .style("fill", "#555")
  //    .style("font-family", "Arial")
  //    .style("font-size", 12)
  .style("pointer-events", "none"); // to prevent mouseover/drag capture

node.on("mouseover", focus).on("mouseout", unfocus);

function ticked() {

  node.call(updateNode);
  link.call(updateLink);

  labelLayout.alphaTarget(0.3).restart();
  labelNode.each(function(d, i) {
    if (i % 2 == 0) {
      d.x = d.node.x;
      d.y = d.node.y;
    } else {
      var b = this.getBBox();

      var diffX = d.x - d.node.x;
      var diffY = d.y - d.node.y;

      var dist = Math.sqrt(diffX * diffX + diffY * diffY);

      var shiftX = b.width * (diffX - dist) / (dist * 2);
      shiftX = Math.max(-b.width, Math.min(0, shiftX));
      var shiftY = 16;
      this.setAttribute("transform", "translate(" + shiftX + "," + shiftY + ")");
    }
  });
  labelNode.call(updateNode);

}

function fixna(x) {
  if (isFinite(x)) return x;
  return 0;
}

function focus(d) {
  var index = d3.select(d3.event.target).datum().index;
  node.style("opacity", function(o) {
    return neigh(index, o.index) ? 1 : 0.1;
  });
  labelNode.attr("display", function(o) {
    return neigh(index, o.node.index) ? "block" : "none";
  });
  link.style("opacity", function(o) {
    return o.source.index == index || o.target.index == index ? 1 : 0.1;
  });
}

function unfocus() {
  labelNode.attr("display", "block");
  node.style("opacity", 1);
  link.style("opacity", 1);
}

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

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

function dragstarted(d) {
  d3.event.sourceEvent.stopPropagation();
  if (!d3.event.active) graphLayout.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) graphLayout.alphaTarget(0);
  d.fx = null;
  d.fy = null;
}