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
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; }
g { fill: orange }
circle { fill: red; stroke: red }
line { stroke: blue }
text { text-anchor: middle }
</style>
</head>
<body>
<script>
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 - (r1 + r2), angle: angle * 180 / Math.PI };
};
const NODE_RADIUS = 20;
const nodes = [
{ x: Math.random() * 800 + 50, y: Math.random() * 300 + 50 },
{ x: Math.random() * 800 + 50, y: Math.random() * 300 + 50 }
];
const links = [
{ source: 0, target: 1 }
];
const handleNodeClick = (d, i) => {
const newZ = 1 + Math.random();
d3.selectAll('.node')
.each(function(d, i) {
const newX = Math.random() * 800 + 50;
const newY = Math.random() * 300 + 50;
nodes[i].x = newX;
nodes[i].y = newY;
d3.select(this)
.transition()
.duration(1000)
.attr('transform', () => `translate(${newX},${newY}) scale(${newZ})`);
});
d3.selectAll('.link')
.each(function(d, i) {
const $g = d3.select(this);
$g.transition()
.duration(1000)
.tween('move.link', (d, i, g) => {
return () => {
const source = d3.selectAll('.node').filter((_, index) => index === d.source).node();
const target = d3.selectAll('.node').filter((_, index) => index === d.target).node();
const { x: x1, y: y1 } = getTranslateCoords(source.transform.animVal);
const { x: z1 } = getScaleFactors(source.transform.animVal);
const { x: x2, y: y2 } = getTranslateCoords(target.transform.animVal);
const { x: z2 } = getScaleFactors(target.transform.animVal);
const linkCoords = getLinkCoords({ x: x1, y: y1, z: z1 }, { x: x2, y: y2, z: z2 });
const z = Math.min(z1, z2);
$g.attr('transform', `translate(${ linkCoords.x },${ linkCoords.y }) scale(${z}) rotate(${ linkCoords.angle })`);
$g.select('line').attr('y2', linkCoords.length / z);
}
});
});
};
const $svg = d3.select("body")
.append("svg")
.attr("width", 960)
.attr("height", 500);
const $defs = $svg.append('defs');
$defs.append('marker')
.attr('id', 'arrow')
.attr('refX', 10)
.attr('refY', 5)
.attr('markerWidth', 20)
.attr('markerHeight', 20)
.attr('orient', 'auto')
.append('path')
.attr('d', 'M0,0 L10,5 L0,10 Z')
.attr('class', 'arrow');
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('.link')
.data(links)
.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});
$g.attr('transform', `translate(${ linkCoords.x },${ linkCoords.y }) scale(1) rotate(${ linkCoords.angle })`);
$g.append('line')
.attr('y2', linkCoords.length)
.style('marker-end', 'url(#arrow)')
});
$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)
.on('click', handleNodeClick);
});
</script>
</body>
https://d3js.org/d3.v4.min.js