【发布时间】:2021-03-11 11:16:28
【问题描述】:
我从 bl.ocks.org 找到了这个 http://bl.ocks.org/rkirsling/5001347 图表,并希望迁移到 D3 v6。
我设法在没有任何语法错误的情况下运行,但如果您单击一个节点,结果似乎是错误的。
我猜的功能:
function mousedown(event) {
// because :active only works in WebKit?
svg.classed('active', true);
if (event.ctrlKey || mousedownNode || mousedownLink) return;
// insert new node at point
const point = d3.pointer(this);
const node = { id: ++lastNodeId, reflexive: false, x: point[0], y: point[1] };
nodes.push(node);
restart();
}
触发不需要的行为。只要我将d3.mouse(this) 更改为d3.pointer(this),源节点就会开始不规律地跳舞。
有什么提示可以在下面修复我当前的代码吗?
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>D3v6 dyn link</title>
<!-- call external d3.js framework -->
<script src="https://d3js.org/d3.v6.js"></script>
</head>
<style>
svg {
background-color: #FFF;
cursor: default;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
-o-user-select: none;
user-select: none;
}
svg:not(.active):not(.ctrl) {
cursor: crosshair;
}
path.link {
fill: none;
stroke: #000;
stroke-width: 4px;
cursor: default;
}
svg:not(.active):not(.ctrl) path.link {
cursor: pointer;
}
path.link.selected {
stroke-dasharray: 10, 2;
}
path.link.dragline {
pointer-events: none;
}
path.link.hidden {
stroke-width: 0;
}
circle.node {
stroke-width: 1.5px;
cursor: pointer;
}
circle.node.reflexive {
stroke: #000 !important;
stroke-width: 2.5px;
}
text {
font: 12px sans-serif;
pointer-events: none;
}
text.id {
text-anchor: middle;
font-weight: bold;
}
</style>
<body>
<svg id="svg"> </svg>
<script>
// set up SVG for D3
const width = window.innerWidth
const height = window.innerHeight
const colors = d3.scaleOrdinal(d3.schemeCategory10);
const svg = d3.select('body')
.append('svg')
.on('contextmenu', (event) => { event.preventDefault(); })
.attr('width', width)
.attr('height', height);
// set up initial nodes and links
// - nodes are known by 'id', not by index in array.
// - reflexive edges are indicated on the node (as a bold black circle).
// - links are always source < target; edge directions are set by 'left' and 'right'.
const nodes = [
{ id: 0, reflexive: false },
{ id: 1, reflexive: true },
{ id: 2, reflexive: false }
];
let lastNodeId = 2;
const links = [
{ source: nodes[0], target: nodes[1], left: false, right: true },
{ source: nodes[1], target: nodes[2], left: false, right: true }
];
// init D3 force layout
const force = d3.forceSimulation()
.force('link', d3.forceLink().id((d) => d.id).distance(150))
.force('charge', d3.forceManyBody().strength(-500))
.force('x', d3.forceX(width / 2))
.force('y', d3.forceY(height / 2))
.on('tick', tick);
// init D3 drag support
const drag = d3.drag()
// Mac Firefox doesn't distinguish between left/right click when Ctrl is held...
.filter((event) => event.button === 0 || event.button === 2)
.on('start', (event, d) => {
if (!event.active) force.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
})
.on('drag', (event, d) => {
d.fx = event.x;
d.fy = event.y;
})
.on('end', (event, d) => {
if (!event.active) force.alphaTarget(0);
d.fx = null;
d.fy = null;
});
// 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:path')
.attr('d', 'M0,-5L10,0L0,5')
.attr('fill', '#000');
svg.append('svg:defs').append('svg:marker')
.attr('id', 'start-arrow')
.attr('viewBox', '0 -5 10 10')
.attr('refX', 4)
.attr('markerWidth', 3)
.attr('markerHeight', 3)
.attr('orient', 'auto')
.append('svg:path')
.attr('d', 'M10,-5L0,0L10,5')
.attr('fill', '#000');
// line displayed when dragging new nodes
const dragLine = svg.append('svg:path')
.attr('class', 'link dragline hidden')
.attr('d', 'M0,0L0,0');
// handles to link and node element groups
let path = svg.append('svg:g').selectAll('path');
let circle = svg.append('svg:g').selectAll('g');
// mouse event vars
let selectedNode = null;
let selectedLink = null;
let mousedownLink = null;
let mousedownNode = null;
let mouseupNode = null;
function resetMouseVars() {
mousedownNode = null;
mouseupNode = null;
mousedownLink = null;
}
// update force layout (called automatically each iteration)
function tick() {
// draw directed edges with proper padding from node centers
path.attr('d', (d) => {
const deltaX = d.target.x - d.source.x;
const deltaY = d.target.y - d.source.y;
const dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
const normX = deltaX / dist;
const normY = deltaY / dist;
const sourcePadding = d.left ? 17 : 12;
const targetPadding = d.right ? 17 : 12;
const sourceX = d.source.x + (sourcePadding * normX);
const sourceY = d.source.y + (sourcePadding * normY);
const targetX = d.target.x - (targetPadding * normX);
const targetY = d.target.y - (targetPadding * normY);
return `M${sourceX},${sourceY}L${targetX},${targetY}`;
});
circle.attr('transform', (d) => `translate(${d.x},${d.y})`);
}
// update graph (called when needed)
function restart() {
// path (link) group
path = path.data(links);
// update existing links
path.classed('selected', (d) => d === selectedLink)
.style('marker-start', (d) => d.left ? 'url(#start-arrow)' : '')
.style('marker-end', (d) => d.right ? 'url(#end-arrow)' : '');
// remove old links
path.exit().remove();
// add new links
path = path.enter().append('svg:path')
.attr('class', 'link')
.classed('selected', (d) => d === selectedLink)
.style('marker-start', (d) => d.left ? 'url(#start-arrow)' : '')
.style('marker-end', (d) => d.right ? 'url(#end-arrow)' : '')
.on('mousedown', (d) => {
if (d3.event.ctrlKey) return;
// select link
mousedownLink = d;
selectedLink = (mousedownLink === selectedLink) ? null : mousedownLink;
selectedNode = null;
restart();
})
.merge(path);
// circle (node) group
// NB: the function arg is crucial here! nodes are known by id, not by index!
circle = circle.data(nodes, (d) => d.id);
// update existing nodes (reflexive & selected visual states)
circle.selectAll('circle')
.style('fill', (d) => (d === selectedNode) ? d3.rgb(colors(d.id)).brighter().toString() : colors(d.id))
.classed('reflexive', (d) => d.reflexive);
// remove old nodes
circle.exit().remove();
// add new nodes
const g = circle.enter().append('svg:g');
g.append('svg:circle')
.attr('class', 'node')
.attr('r', 12)
.style('fill', (d) => (d === selectedNode) ? d3.rgb(colors(d.id)).brighter().toString() : colors(d.id))
.style('stroke', (d) => d3.rgb(colors(d.id)).darker().toString())
.classed('reflexive', (d) => d.reflexive)
.on('mouseover', function (d) {
if (!mousedownNode || d === mousedownNode) return;
// enlarge target node
d3.select(this).attr('transform', 'scale(1.1)');
})
.on('mouseout', function (d) {
if (!mousedownNode || d === mousedownNode) return;
// unenlarge target node
d3.select(this).attr('transform', '');
})
.on('mousedown', (event, d) => {
if (event.ctrlKey) return;
// select node
mousedownNode = d;
selectedNode = (mousedownNode === selectedNode) ? null : mousedownNode;
selectedLink = null;
// reposition drag line
dragLine
.style('marker-end', 'url(#end-arrow)')
.classed('hidden', false)
.attr('d', `M${mousedownNode.x},${mousedownNode.y}L${mousedownNode.x},${mousedownNode.y}`);
restart();
})
.on('mouseup', function (d) {
if (!mousedownNode) return;
// needed by FF
dragLine
.classed('hidden', true)
.style('marker-end', '');
// check for drag-to-self
mouseupNode = d;
if (mouseupNode === mousedownNode) {
resetMouseVars();
return;
}
// unenlarge target node
d3.select(this).attr('transform', '');
// add link to graph (update if exists)
// NB: links are strictly source < target; arrows separately specified by booleans
const isRight = mousedownNode.id < mouseupNode.id;
const source = isRight ? mousedownNode : mouseupNode;
const target = isRight ? mouseupNode : mousedownNode;
const link = links.filter((l) => l.source === source && l.target === target)[0];
if (link) {
link[isRight ? 'right' : 'left'] = true;
} else {
links.push({ source, target, left: !isRight, right: isRight });
}
// select new link
selectedLink = link;
selectedNode = null;
restart();
});
// show node IDs
g.append('svg:text')
.attr('x', 0)
.attr('y', 4)
.attr('class', 'id')
.text((d) => d.id);
circle = g.merge(circle);
// set the graph in motion
force
.nodes(nodes)
.force('link').links(links);
force.alphaTarget(0.3).restart();
}
function mousedown(event) {
// because :active only works in WebKit?
svg.classed('active', true);
if (event.ctrlKey || mousedownNode || mousedownLink) return;
// insert new node at point
const point = d3.pointer(this);
const node = { id: ++lastNodeId, reflexive: false, x: point[0], y: point[1] };
nodes.push(node);
restart();
}
function mousemove() {
if (!mousedownNode) return;
// update drag line
dragLine.attr('d', `M${mousedownNode.x},${mousedownNode.y}L${d3.pointer(this)[0]},${d3.pointer(this)[1]}`);
}
function mouseup() {
if (mousedownNode) {
// hide drag line
dragLine
.classed('hidden', true)
.style('marker-end', '');
}
// because :active only works in WebKit?
svg.classed('active', false);
// clear mouse event vars
resetMouseVars();
}
// app starts here
svg.on('mousedown', mousedown)
.on('mousemove', mousemove)
.on('mouseup', mouseup);
restart();
</script>
</body>
</html>
【问题讨论】:
标签: javascript d3.js