Last active
July 9, 2021 16:44
-
-
Save iosonosempreio/361588b354e16af9a5950d6bf6c8800c to your computer and use it in GitHub Desktop.
d3-zoom on HTML
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.DS_Store | |
data.csv | |
data.tsv |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<link rel="stylesheet" href="./style.css"> | |
<svg id="main-svg"> | |
<g></g> | |
</svg> | |
<div id="main"> | |
<div id="g1"></div> | |
</div> | |
<script src="https://d3js.org/d3.v7.min.js"></script> | |
<script src="./script.js"></script> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let data = []; | |
let zoomLevel = 0; | |
const xy = d3.scaleLinear().domain([0, 1]).range([0, 100]); | |
const simulation = d3 | |
.forceSimulation() | |
.force( | |
"x", | |
d3 | |
.forceX((d) => xy(+d._x)) | |
.strength((d) => (d.category === "tactic" ? 0 : 0.1)) | |
) | |
.force( | |
"y", | |
d3 | |
.forceY((d) => xy(+d._y)) | |
.strength((d) => (d.category === "tactic" ? 0 : 0.1)) | |
) | |
.force( | |
"link", | |
d3.forceLink().id((d) => d.id) | |
) | |
.force("collide", d3.forceCollide().radius(75)) | |
.on("tick", ticked) | |
.velocityDecay(0.6) | |
.alphaDecay(0.01) | |
.stop(); | |
const zoom = d3.zoom().on("zoom", zoomed); | |
const main = d3.select("#main").call(zoom); | |
const mainSvg = d3.select("#main-svg").style("background-color", "#F9F9F9"); | |
const bbox = main.node().getBoundingClientRect(); | |
const width = bbox.width; | |
const height = bbox.height; | |
const g1 = d3.select("#g1"); | |
const g1Svg = mainSvg.select("g"); | |
let item = g1.selectAll(".item"); | |
let link = g1Svg.selectAll(".link"); | |
d3.tsv("./data.tsv").then((_data) => { | |
data = _data; | |
update(makeClusters(data), []); | |
}); | |
main | |
.call( | |
zoom.transform, | |
d3.zoomIdentity.translate(width / 2, height / 2).scale(0.01) | |
) | |
.transition() | |
.duration(1000) | |
.call( | |
zoom.transform, | |
d3.zoomIdentity.translate(width / 2, height / 2).scale(0.15) | |
); | |
function zoomed(e) { | |
const { x, y, k } = e.transform; | |
const previousZoom = zoomLevel; | |
if (k < 0.2) { | |
zoomLevel = 0; | |
} else if (k < 0.5) { | |
zoomLevel = 1; | |
} else { | |
zoomLevel = 2; | |
} | |
g1.style("transform", `translate(${x}px,${y}px) scale(${k})`); | |
g1Svg.style("transform", `translate(${x}px,${y}px) scale(${k})`); | |
if (previousZoom != zoomLevel) { | |
switch (zoomLevel) { | |
case 0: | |
update(makeClusters(data), []); | |
mainSvg.style("background-color", "#F9F9F9"); | |
break; | |
case 1: | |
const projects = makeItems(data, previousZoom !== 2); | |
update(projects, []); | |
mainSvg.style("background-color", "#F5F5F5"); | |
break; | |
case 2: | |
const net = makeNetworks(data); | |
update(net.nodes, net.links); | |
mainSvg.style("background-color", "#EBEBEB"); | |
break; | |
} | |
} | |
} | |
function ticked() { | |
item.style("top", (d) => d.y).style("left", (d) => d.x); | |
link | |
.attr("x1", (d) => d.source.x) | |
.attr("y1", (d) => d.source.y) | |
.attr("x2", (d) => d.target.x) | |
.attr("y2", (d) => d.target.y); | |
} | |
function update(nodes, links) { | |
item = item.data(nodes, (d) => d.id); | |
item | |
.exit() | |
.transition() | |
.duration(750) | |
.style("opacity", "-0.5") | |
.style("top", (d) => d.fading_y + "px") | |
.style("left", (d) => d.fading_x + "px") | |
.remove(); | |
item = item | |
.enter() | |
.append("svg") | |
.classed("item", true) | |
.style("opacity", "0") | |
.style("position", "absolute") | |
.style("transform", "translate(-50%,-50%)") | |
.attr("width", 100) | |
.attr("height", 100) | |
.style("background-color", (d) => | |
d.category === "cluster" | |
? "#7765E3" | |
: d.category === "tactic" | |
? "#FFFFFF" | |
: "#E4FF1A" | |
) | |
.merge(item); | |
item.transition().duration(500).style("opacity", "1"); | |
item | |
.selectAll("text") | |
.data( | |
(d) => [d], | |
(d) => d.id | |
) | |
.join("text") | |
.attr("fill", "black") | |
.attr("x", 50) | |
.attr("y", 60) | |
.attr("font-size", 50) | |
.attr("text-anchor", "middle") | |
.text((d) => d.id); | |
link = link.data(links, (d) => d.id); | |
link | |
.exit() | |
.transition() | |
.duration(250) | |
.style("opacity", "-0.5") | |
.remove(); | |
link = link | |
.enter() | |
.append("line") | |
.classed("line", true) | |
.attr("stroke", "black") | |
.style("opacity", "0") | |
.merge(link); | |
link.transition().delay(500).duration(500).style("opacity", "1"); | |
simulation.nodes(nodes); | |
simulation.force("link").links(links); | |
simulation.alpha(1).restart(); | |
} | |
function makeClusters(data) { | |
const clusters = d3 | |
.flatRollup( | |
data, | |
(v) => [d3.mean(v, (d) => d._x), d3.mean(v, (d) => d._y)], | |
(d) => d.cluster | |
) | |
.map((d) => ({ | |
id: d[0], | |
_x: d[1][0], | |
_y: d[1][1], | |
x: xy(d[1][0]), | |
y: xy(d[1][1]), | |
fading_x: xy(d[1][0]), | |
fading_y: xy(d[1][1]), | |
category: "cluster", | |
})); | |
return clusters; | |
} | |
function makeItems(data, setCoordinates) { | |
const clustersPositions = d3.flatRollup( | |
data, | |
(v) => [d3.mean(v, (d) => d._x), d3.mean(v, (d) => d._y)], | |
(d) => d.cluster | |
); | |
data.forEach((d) => { | |
const _clusterPosition = clustersPositions.find((c) => c[0] === d.cluster); | |
if (setCoordinates) { | |
d.x = xy(_clusterPosition[1][0]); | |
d.y = xy(_clusterPosition[1][1]); | |
} | |
d.fading_x = xy(_clusterPosition[1][0]); | |
d.fading_y = xy(_clusterPosition[1][1]); | |
}); | |
return data; | |
} | |
function makeNetworks(data) { | |
const clustersPositions = d3.flatRollup( | |
data, | |
(v) => [d3.mean(v, (d) => d._x), d3.mean(v, (d) => d._y)], | |
(d) => d.cluster | |
); | |
const tactics = d3.flatRollup( | |
data, | |
(v) => { | |
const _arr = v.map((vv) => vv.alltactics.split(";")).flat(); | |
const _cluster = clustersPositions.find((c) => c[0] === v[0].cluster); | |
return d3 | |
.flatGroup(_arr, (d) => d) | |
.map((d) => ({ | |
id: _cluster[0] + "-" + d[0], | |
label: d[0], | |
_x: _cluster[1][0], | |
_y: _cluster[1][1], | |
x: xy(_cluster[1][0]), | |
y: xy(_cluster[1][1]), | |
fading_x: xy(_cluster[1][0]) + 0, | |
fading_y: xy(_cluster[1][1]) + 0, | |
category: "tactic", | |
})); | |
}, | |
(d) => d.cluster | |
); | |
const flatTactics = tactics.map((d) => d[1]).flat(); | |
const links = d3.flatRollup( | |
data, | |
(v) => { | |
return v.map((d) => { | |
const temp = d.cluster + "-" + d.id + "-"; | |
return d.alltactics.split(";").map((t) => ({ | |
id: temp + t, | |
source: d, | |
target: flatTactics.find((ft) => ft.id === d.cluster + "-" + t), | |
})); | |
}); | |
}, | |
(d) => d.cluster | |
); | |
const flatLinks = links.map((d) => d[1].flat()).flat(); | |
return { nodes: data.concat(flatTactics), links: flatLinks }; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#main-svg { | |
display: block; | |
width: calc(100vw - 16px); | |
height: calc(100vh - 16px); | |
position: absolute; | |
} | |
#main { | |
display: block; | |
width: calc(100vw - 16px); | |
height: calc(100vh - 16px); | |
overflow: hidden; | |
position: absolute; | |
} | |
#map { | |
background-color: #f2f9ff; | |
border: 1px solid #3479FF; | |
position: absolute; | |
bottom: 16px; | |
right: 16px; | |
} | |
#viewport { | |
background-color: #f2f9ff; | |
border: 1px solid #3479FF; | |
} | |
#g0 { | |
position: absolute; | |
} | |
#g1 { | |
background-color: #fafafa; | |
display: block; | |
position: absolute; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment