Click on a node to activate transition ( random transition + zoom of the circles and the link between them should follow ).
This Block has been made to demonstrate the issue when combining markers with zoom on Microsoft Edge. The page is crashing or reloading if the computer has less than 4GB of RAM.
On GoogleChrome and FireFox it works just fine.
Built with blockbuilder.org
forked from pierreferry's block: TweenLinkTest
xxxxxxxxxx
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }
.node { fill: orange }
g { fill: none }
circle { fill: red; stroke: none }
path { stroke: blue }
path.arrow { fill: blue }
defs { fill: blue }
text {
stroke: black;
fill: black;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 12px;
text-anchor: middle;
}
</style>
</head>
<body>
<script>
const NODE_RADIUS = 20;
const ARROW_LENGTH = 10;
const nodes = [
{ x: Math.random() * 800 + 50, y: Math.random() * 300 + 50 },
{ x: Math.random() * 800 + 50, y: Math.random() * 300 + 50 },
{ x: Math.random() * 800 + 50, y: Math.random() * 300 + 50 }
];
// const nodes = [
// { x: 300, y: 100 },
// { x: 300, y: 350 },
// { x: 700, y: 100 }
// ];
const links = [
{ source: 0, target: 1, text: '0-1' },
{ source: 0, target: 1, text: '0-1' },
{ source: 1, target: 0, text: '1-0' },
{ source: 0, target: 1, text: '0-1' },
{ source: 0, target: 2, text: '0-2' },
{ source: 2, target: 1, text: '2-1' },
{ source: 2, target: 1, text: '2-1' },
{ source: 1, target: 0, text: '1-0' },
{ source: 1, target: 0, text: '1-0' },
];
const linkGroups = [];
links.forEach(link => {
const existingGroup = linkGroups.find(linkGroup => linkGroup[0].source === link.source && linkGroup[0].target === link.target
|| linkGroup[0].source === link.target && linkGroup[0].target === link.source);
if (existingGroup) {
existingGroup.push(link);
}
else {
linkGroups.push([link]);
}
})
const getLinksGroup = (source, target) => {
return links.filter(link =>(link.source === source && link.target === target
|| link.target === source && link.source === target));td
}
const getTranslateCoords = (animVal) => {
for (let i = 0; i < animVal.numberOfItems; ++i) {
if (animVal.getItem(i).type === animVal.getItem(i).SVG_TRANSFORM_TRANSLATE) {
return { x: animVal.getItem(i).matrix.e, y: animVal.getItem(i).matrix.f };
}
}
return { x: 0, y: 0 };
};
const getScaleFactors = (animVal) => {
for (let i = 0; i < animVal.numberOfItems; ++i) {
if (animVal.getItem(i).type === animVal.getItem(i).SVG_TRANSFORM_SCALE) {
return { x: animVal.getItem(i).matrix.a, y: animVal.getItem(i).matrix.d };
}
}
return { x: 1, y: 1 };
};
const getLinkCoords = (sourcePosition, targetPosition) => {
const x1 = sourcePosition.x;
const y1 = sourcePosition.y;
const z1 = sourcePosition.z;
const x2 = targetPosition.x;
const y2 = targetPosition.y;
const z2 = targetPosition.z;
const r1 = NODE_RADIUS * z1;
const r2 = NODE_RADIUS * z2;
const hypotenuse = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
const angle = Math.atan2(x1 - x2, y2 - y1);
const sourceX = x1 - Math.sin(angle) * r1;
const sourceY = y1 + Math.cos(angle) * r1;
return { x: sourceX, y: sourceY, length: hypotenuse, angle: angle * 180 / Math.PI };
};
const $svg = d3.select("body")
.append("svg")
.attr("width", 960)
.attr("height", 500);
const $zoom = $svg.append('g').attr('class', 'graph-area-g-container');
const $zoomedGroup = $zoom.append('g').attr('class', 'zoomed-group');
const zoom = d3.zoom()
.scaleExtent([ 0.1, 4 ])
.on('zoom.nodes', () => $zoomedGroup.attr('transform', d3.event.transform));
$zoom.call(zoom);
$zoomedGroup.selectAll('.linkGroup')
.data(linkGroups)
.enter()
.append('g')
.attr('class', 'linkGroup')
.each(function(dLinkGroup, iLinkGroup) {
const $linkGroup = d3.select(this);
const linkGroupCoords = getLinkCoords({ x: nodes[dLinkGroup[0].source].x, y: nodes[dLinkGroup[0].source].y, z: 1}, { x: nodes[dLinkGroup[0].target].x, y: nodes[dLinkGroup[0].target].y, z: 1});
$linkGroup.attr('transform', `translate(${ nodes[dLinkGroup[0].source].x },${ nodes[dLinkGroup[0].source].y }) scale(1) rotate(${ linkGroupCoords.angle })`);
$linkGroup.selectAll('.link')
.data(dLinkGroup)
.enter()
.append('g')
.attr('class', 'link')
.each(function(d, i) {
const $g = d3.select(this);
const linkCoords = getLinkCoords({ x: nodes[d.source].x, y: nodes[d.source].y, z: 1}, { x: nodes[d.target].x, y: nodes[d.target].y, z: 1});
const middle = {
x: 0,
y: linkCoords.length / 2
};
const lineData = [
{ x: 0, y: d.source === dLinkGroup[0].source ? 0 : linkCoords.length },
{ x: middle.x + (NODE_RADIUS * i - NODE_RADIUS * ( dLinkGroup.length - 1) / 2) * 4 , y: middle.y },
{ x: 0, y: d.source === dLinkGroup[0].source ? linkCoords.length : 0}
];
const bezierPath = d3.path()
bezierPath.moveTo(lineData[0].x, lineData[0].y)
bezierPath.quadraticCurveTo(lineData[1].x, lineData[1].y, lineData[2].x, lineData[2].y);
const path = $g.append("path")
.attr("class", "line")
.attr("id", `${d.source}-${d.target}-${i}`)
.attr("d", bezierPath);
const pathLength = path.node().getTotalLength();
const arrowStartCoords = path.node().getPointAtLength(pathLength - NODE_RADIUS - ARROW_LENGTH);
const arrowEndCoords = path.node().getPointAtLength(pathLength - NODE_RADIUS);
const angleDeg = Math.atan2(arrowEndCoords.y - arrowStartCoords.y, arrowEndCoords.x - arrowStartCoords.x) * 180 / Math.PI;
$g.append('path')
.attr('d', 'M0,5 L10,0 L0,-5 Z')
.attr('class', 'arrow')
.attr('transform', `translate(${arrowStartCoords.x}, ${arrowStartCoords.y}) rotate(${angleDeg})`)
$g.append('text')
.attr('dy', '-4px')
.attr('transform', `translate(${lineData[1].x / 2}, ${lineData[1].y}), rotate(${linkGroupCoords.angle >= 0 ? -90 : 90})`)
.text(d.text)
});
});
$zoomedGroup.selectAll('.node')
.data(nodes)
.enter()
.append('g')
.attr('class', 'node')
.attr('transform', (d) => `translate(${d.x}, ${d.y})`)
.call($g => {
$g.append('circle')
.attr('r', NODE_RADIUS);
});
</script>
</body>
https://d3js.org/d3.v4.min.js