755/5000 您好,我在使用d3.js库的图形时遇到问题
节点和链接(边)的位置来自后端。 我捕获了鼠标单击的位置并使其保持反应状态,然后在画布上单击时,我执行了一个名为handleClick的函数,该函数查找节点数组并比较节点的位置是否与鼠标匹配点击位置 如果万一,那么我返回该节点并将其设置为react
在画布上放大/缩小时会出现问题。并且在拖动整个图时,只要d3.zoomIdentity的变换为x:0,y:0 k:1
我分享了一小段代码
非常感谢您
let transform = d3.zoomIdentity;
const crossesGraph = useSelector(state => getCrossesSelector(state));
useEffect(() => {
if (!loading && crossesGraph) {
if (crossesGraph.error_message) return alert(crossesGraph.error_message);
window.addEventListener('resize', updateWindowSize);
// Get properties from canvas object
const d3Canvas = d3.select('canvas');
const canvas = d3Canvas._groups[0][0];
// get height from the Canvas' CSS
const styleHeight = +getComputedStyle(canvas)
.getPropertyValue('height')
.slice(0, -2);
// get width from the Canvas' CSS
const styleWidth = +getComputedStyle(canvas)
.getPropertyValue('width')
.slice(0, -2);
// Fix DPI (Dots Per Pixel)
d3Canvas
.attr('width', styleWidth * dpi)
.attr('height', styleHeight * dpi)
.on('click', () => {
// Ignore the click event if it was suppressed
if (d3.event.defaultPrevented) {
return;
}
// Extract the click location\
const coordinates = d3.mouse(canvasRef.current);
const x = coordinates[0];
const y = coordinates[1];
const pos = { x: x * dpi, y: y * dpi };
setMousePos(pos);
});
const context = canvas.getContext('2d');
const width = canvas.width;
const height = canvas.height;
const r = 1.5;
//Creates a new simulation with the specified array of nodes and no forces.
//https://github.com/d3/d3-force#forceSimulation
//If force() is specified, assigns the force for the specified name and returns this simulation.
//https://github.com/d3/d3-force#simulation_force
const simulation = d3
.forceSimulation()
.force(
'link',
d3.forceLink().id(node => {
return node.id;
}),
)
.force('charge', d3.forceManyBody())
.force('center', d3.forceCenter(width / 2, height / 2));
const draw = () => {
context.save();
context.clearRect(0, 0, width, height);
context.translate(transform.x, transform.y);
context.scale(transform.k, transform.k);
let nodeRadio = NODE_RADIO_ROOT;
let isFirstNode = true;
// Draw edges
crossesGraph.links.forEach(link => {
context.beginPath();
context.moveTo(link.source.x, link.source.y);
context.lineTo(link.target.x, link.target.y);
context.lineWidth = Math.sqrt(link.value);
context.strokeStyle = '#000';
context.stroke();
});
// Draw nodes
crossesGraph.nodes.forEach((node, i) => {
context.beginPath();
// Node fill
context.moveTo(node.x + r, node.y);
if (isFirstNode) {
isFirstNode = false;
// Draw circle with angles from 0 to 2 * Math.PI
context.drawImage(
document.getElementById(node.label),
0,
0,
DEFAULT_NODE_SIZE,
DEFAULT_NODE_SIZE,
node.x - (nodeRadio * 2) / 2,
node.y - (nodeRadio * 2) / 2,
nodeRadio * 2,
nodeRadio * 2,
);
context.strokeStyle = '#000';
context.stroke();
} else {
nodeRadio =
selectedNode !== null ? (selectedNode.id === node.id ? NODE_RADIO_SELECTED : NODE_RADIO) : NODE_RADIO;
// Draw circle with angles from 0 to 2 * Math.PI
// context.arc(node.x, node.y, nodeRadio, 0, 2 * Math.PI);
context.drawImage(
document.getElementById(node.label),
0,
0,
DEFAULT_NODE_SIZE,
DEFAULT_NODE_SIZE,
node.x - (nodeRadio * 2) / 2,
node.y - (nodeRadio * 2) / 2,
nodeRadio * 2,
nodeRadio * 2,
);
}
});
context.restore();
}; //end draw
simulation.nodes(crossesGraph.nodes).on('tick', () => {
draw();
});
simulation.force('link').links(crossesGraph.links);
const dragsubject = () => {
let i;
let x = transform.invertX(d3.event.x);
let y = transform.invertY(d3.event.y);
let dx;
let dy;
const nodeRadius = 3;
for (i = crossesGraph.nodes.length - 1; i >= 0; --i) {
let node = crossesGraph.nodes[i];
dx = x - node.x;
dy = y - node.y;
if (dx * dx + dy * dy < nodeRadius * nodeRadius) {
node.x = transform.applyX(node.x);
node.y = transform.applyY(node.y);
return node;
}
}
};
d3.select(canvas)
.call(
d3
.drag()
.container(canvas)
.subject(dragsubject)
.on('start', () => {
if (!d3.event.active) {
simulation.alphaTarget(0.3).restart();
}
d3.event.subject.fx = transform.invertX(d3.event.x);
d3.event.subject.fy = transform.invertY(d3.event.y);
})
.on('drag', () => {
d3.event.subject.fx = transform.invertX(d3.event.x);
d3.event.subject.fy = transform.invertY(d3.event.y);
})
.on('end', () => {
if (!d3.event.active) {
simulation.alphaTarget(0);
}
d3.event.subject.fx = null;
d3.event.subject.fy = null;
}),
)
.call(
d3
.zoom()
.scaleExtent([1, 1000])
.on('zoom', () => {
console.log('se ejecuta el zoomed');
zoomed();
}),
);
const zoomed = () => {
transform = d3.event.transform;
console.log('transform', transform)
draw();
};
}
}, [loading, crossesGraph, dpi, windowSize, selectedNode]);
const handleClick = () => {
const nodeSelected = crossesGraph.nodes.find(node => {
console.log('transform', transform);
//console.log('mouse x', mousePos.x);
console.log('node x', node.id, node.x, mousePos.x);
return distance(mousePos.x, mousePos.y, node.x, node.y) < 6;
});
if (!nodeSelected) {
return;
}
setSelectedNode(nodeSelected);
};
const updateWindowSize = () => {
setDpi(window.devicePixelRatio);
setwindowSize(window.innerWidth);
};
// more code
<canvas className={classes.canvas} onClick={handleClick} ref={canvasRef}></canvas>