Skip to content

Instantly share code, notes, and snippets.

@iosonosempreio
Last active July 9, 2021 16:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save iosonosempreio/361588b354e16af9a5950d6bf6c8800c to your computer and use it in GitHub Desktop.
Save iosonosempreio/361588b354e16af9a5950d6bf6c8800c to your computer and use it in GitHub Desktop.
d3-zoom on HTML
.DS_Store
data.csv
data.tsv
<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>
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 };
}
#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