1
0
Fork 0

Initial commit

This commit is contained in:
Oystein Kristoffer Tveit 2023-06-24 18:55:41 +02:00
commit 02151694a8
Signed by: oysteikt
GPG Key ID: 9F2F7D8250F35146
4 changed files with 236 additions and 0 deletions

BIN
.github/images/screenshot.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

5
README.md Normal file
View File

@ -0,0 +1,5 @@
# Connect Arrows D3.js Example
This is an example of how you can create a drag and drop system for arrows in D3.js
![Screenshot](./.github/images/screenshot.png)

19
index.html Normal file
View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Connect Arrows</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js"
integrity="sha512-M7nHCiNUOwFt6Us3r8alutZLm9qMt4s9951uo8jqO4UwJ1hziseL6O3ndFyigx6+LREfZqnhHxYjKRJ8ZQ69DQ=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script defer src="./script.js"></script>
</head>
<body>
<h1>Minimal example of arrow connection in d3</h1>
<svg id="graph" height="900", width="900"></svg>
</body>
</html>

212
script.js Normal file
View File

@ -0,0 +1,212 @@
const data = {
nodes: [
{ id: 'a', name: 'A' },
{ id: 'b', name: 'B' },
{ id: 'c', name: 'C' },
{ id: 'd', name: 'D' },
{ id: 'e', name: 'E' },
{ id: 'f', name: 'F' },
{ id: 'g', name: 'G' },
],
// Format: { source: nodeId, target: nodeId, type: String }
links: [],
}
const LINK_TYPES = {
'Useful': '#b03d17',
'Important': '#b8f27a',
'Necessary': '#32a852',
'Logically Connected': '#110dde',
'Generalization': '#00f4fc',
'Synonym': '#bd00fc',
}
const NODE_HEIGHT = 100;
const NODE_WIDTH = 100;
const LINK_WIDTH = 8;
const COLOR_CIRCLE_RADIUS = 20;
const svg = d3.select('#graph');
let selectedLinkType = 'Useful';
svg.append('g').attr('id', 'links');
svg.append('g').attr('id', 'startNodes');
svg.append('g').attr('id', 'endNodes');
svg.append('g').attr('id', 'linkTypes');
const eventPositionToNode = (event) => {
const { x, y } = event;
const nodes = svg.selectAll('.node').nodes();
const node = nodes.find((node) => {
let { x: nodeX, y: nodeY } = node.getBoundingClientRect();
nodeX -= svg.node().getBoundingClientRect().x;
nodeY -= svg.node().getBoundingClientRect().y;
return x >= nodeX && x <= nodeX + NODE_WIDTH && y >= nodeY && y <= nodeY + NODE_HEIGHT;
});
return node;
};
const updateLinks = () => {
svg
.select('#links')
.selectAll('line')
.data(data.links)
.join('line')
.attr('x1', (d) => {
const node = d3.select(`#startNode-${d.source}`);
let { x, y } = node.node().getBoundingClientRect();
x -= svg.node().getBoundingClientRect().x;
return x + NODE_WIDTH / 2;
}
)
.attr('y1', (d) => {
const node = d3.select(`#startNode-${d.source}`);
let { x, y } = node.node().getBoundingClientRect();
y -= svg.node().getBoundingClientRect().y;
return y + NODE_HEIGHT / 2;
}
)
.attr('x2', (d) => {
const node = d3.select(`#endNode-${d.target}`);
let { x, y } = node.node().getBoundingClientRect();
x -= svg.node().getBoundingClientRect().x;
return x + NODE_WIDTH / 2;
}
)
.attr('y2', (d) => {
const node = d3.select(`#endNode-${d.target}`);
let { x, y } = node.node().getBoundingClientRect();
y -= svg.node().getBoundingClientRect().y;
return y + NODE_HEIGHT / 2;
}
)
.attr("stroke-width", LINK_WIDTH)
.attr('stroke', (d) => LINK_TYPES[d.type])
.on('click', (_, d) => {
// Remove the link
const index = data.links.findIndex((link) => link.source === d.source && link.target === d.target);
data.links.splice(index, 1);
updateLinks();
});
};
const drag_handler = d3.drag()
.on("start", (event) => {
// Add a temporary arrow and let it follow the mouse
svg.append("line")
.attr("x1", event.x)
.attr("y1", event.y)
.attr("x2", event.x)
.attr("y2", event.y)
.attr("stroke-width", LINK_WIDTH)
.attr('stroke', LINK_TYPES[selectedLinkType])
.attr("marker-end", "url(#arrowhead)")
.attr("id", "temp-arrow");
})
.on("drag", (event) => {
svg.select("#temp-arrow")
.attr("x2", event.x)
.attr("y2", event.y);
})
.on("end", (event) => {
// 1. Track the node that the arrow is pointing to, if any, and add it to the links
// 2. Remove the temporary arrow
// 3. Redraw the links
const target = eventPositionToNode(event);
if (target) {
const d3Target = d3.select(target);
// Remove existing link between the two nodes if any.
const existingLink = data.links.findIndex((link) => {
link.source === event.subject.id && link.target === d3Target.datum().id;
})
if (existingLink !== -1) {
data.links.splice(existingLink, 1);
}
data.links.push({
source: event.subject.id,
target: d3Target.datum().id,
type: selectedLinkType,
});
}
svg.select("#temp-arrow").remove();
updateLinks();
});
const startNodes = svg
.select('#startNodes')
.selectAll('g')
.data(data.nodes, d => d.id)
.join("g")
.attr('id', d => `startNode-${d.id}`)
.attr('class', 'node')
.attr('transform', (_d, i) => `translate(100, ${i * (NODE_HEIGHT + 10)})`)
.attr('style', 'cursor: pointer;')
.call((node) => node
.append('rect')
.attr('width', NODE_WIDTH)
.attr('height', NODE_HEIGHT)
.attr('fill', 'red')
)
.call((node) => node
.append('text')
.attr('style', 'fill: white; font-size: 30px;')
.attr('dy', 60)
.attr('dx', 40)
.text(d => d.name)
)
.call(drag_handler);
const endNodes = svg
.select('#endNodes')
.selectAll('g')
.data(data.nodes, d => d.id)
.join("g")
.attr('id', d => `endNode-${d.id}`)
.attr('class', 'node')
.attr('transform', (_d, i) => `translate(500, ${i * (NODE_HEIGHT + 10)})`)
.call((node) => node
.append('rect')
.attr('width', NODE_WIDTH)
.attr('height', NODE_HEIGHT)
.attr('fill', 'blue')
)
.call((node) => node
.append('text')
.attr('style', 'fill: white; font-size: 30px;')
.attr('dy', 60)
.attr('dx', 40)
.text(d => d.name)
);
const linkTypes = svg
.select('#linkTypes')
.selectAll('g')
.data(Object.keys(LINK_TYPES))
.join("g")
.attr('id', d => d)
.call((node) => node
.append('circle')
.attr('cx', (_d, i) => 250 + (i * (COLOR_CIRCLE_RADIUS * 2 + 10)))
.attr('cy', 800)
.attr('r', COLOR_CIRCLE_RADIUS)
.attr('fill', (d) => LINK_TYPES[d])
.attr('stroke-width', 5)
)
.on('click', (event, d) => {
console.log('clicked', d);
d3.select('#linkTypes').selectAll('circle').attr('stroke', 'none');
selectedLinkType = d;
d3.select(event.target).attr('stroke', 'red');
});
d3.select('#' + selectedLinkType).attr('stroke', 'red');