Skip to content

Instantly share code, notes, and snippets.

@pierreferry
Last active April 6, 2018 08:17
Show Gist options
  • Save pierreferry/7b18537900f4a4325a8ab64ea8f22744 to your computer and use it in GitHub Desktop.
Save pierreferry/7b18537900f4a4325a8ab64ea8f22744 to your computer and use it in GitHub Desktop.
MultipleLinksTest
license: mit

TweenLink bug

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

<!DOCTYPE html>
<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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment