Published
Edited
Dec 16, 2021
3 stars
Insert cell
# Germany Christmas Tree
Insert cell
network()
Insert cell
delaunay = d3.Delaunay.from(nodes)
Insert cell
opacityRand = () => Math.random() * 0.7 + 0.3
Insert cell
network = () => {
const svg = d3.select(DOM.svg(width, height)).style("background", "#ececec");
const g = svg.append("g").attr("transform", "translate(100,50)");
// const colorInter = d3.interpolateCubehelix("#000", "#EDF5EE");

const diagram = delaunay.voronoi([0, 0, width, width / 1.6]);

const links = [];
const { halfedges, triangles, hull } = delaunay;

// internal edges
for (let i = 0, n = halfedges.length; i < n; ++i) {
const j = halfedges[i];
if (j < i) continue;
const ti = triangles[i] * 2;
const tj = triangles[j] * 2;
links.push({ source: nodes[ti / 2], target: nodes[tj / 2] });
}

let hullIndex = -1;
links.push({
source: nodes[hull[++hullIndex]],
target: nodes[hull[++hullIndex]]
});

while (hullIndex + 2 <= hull.length)
links.push({
source: nodes[hull[++hullIndex]],
target: nodes[hull[++hullIndex]]
});

links.forEach((link, index) => {
link.defaultColor = "#333";
if (link == null || link.source == null || link.target == null)
link.target = link.source;

const dx = link.source[0] - link.target[0];
const dy = link.source[1] - link.target[1];
link.distance = Math.sqrt(dx * dx + dy * dy);
});

nodes.forEach((pt) => {
pt.edges = links.filter((link) => pt === link.source || pt === link.target);
});

g.attr("stroke-width", 1);

const edges = g
.selectAll("path.link")
.data(links)
.enter()
.append("path")
// .attr("stroke-width", 0.6)
// .style("shape-rendering", "crisp-edges")
// .attr("stroke-opacity", (d) => opacityRand())
.attr("stroke-opacity", (d) => {
return (
1 -
Math.sqrt(
Math.pow(d.source[0] - d.target[0], 2) +
Math.pow(d.source[1] - d.target[1], 2)
) /
70
);
})
.attr("stroke", (d) => {
if (isNaN(d.source[0])) {
d.source[0] = d.target[0];
d.source[1] = d.target[1];
}

if (isNaN(d.target[0])) {
d.target[0] = d.source[0];
d.target[1] = d.source[1];
}
var mid = (d.source[1] + d.target[1]) / 2;
// console.log(d);
return color(heightScale(mid));
})
.attr("d", (d) => `M${d.source} L${d.target}`);

const circles = g
.selectAll("circle")
.data(nodes)
.enter()
.append("circle")
.attr("r", (d) => 0.5 + Math.random() * 1.5)
.attr("opacity", (d) => opacityRand())
.attr("fill", (d, i) => {
return color(d[1] ? heightScale(d[1]) : 0.5);
})
.attr("cx", (d) => (d[0] ? d[0] : 0))
.attr("cy", (d) => (d[1] ? d[1] : -1000));

g.selectAll("circle")
.transition()
.delay((d) => Math.random() * 1000)
.duration(2500)
.on("start", function repeat() {
d3.active(this)
.attr("r", (d) => Math.random() * 7)
.transition()
.on("start", repeat);
});

return svg.node();
}
Insert cell
color = d3.piecewise(d3.interpolateRgb.gamma(0.8), ["black", "red", "#FFCC00"])
Insert cell
heightScale = d3
.scaleLinear()
.domain([d3.min(nodes, (d) => d[1]), d3.max(nodes, (d) => d[1])])
.range([0, 1])
Insert cell
widthScale = d3
.scaleLinear()
.domain([d3.min(nodes, (d) => d[0]), d3.max(nodes, (d) => d[0])])
.range([0, 1])
Insert cell
nodes.filter((d) => d[1] < 50 && d[0] < 30)
Insert cell
nodes = austriaGeo.features.map((d) => path.centroid(d))
Insert cell
path = d3.geoPath(projection)
Insert cell
height = 800
Insert cell
projection = d3
.geoAlbers()
.rotate([-15, 0])
.fitExtent(
[
[10, 20],
[width * 0.85, height * 0.85]
],
austriaGeo
)
Insert cell
austriaGeo = topojson.feature(topo, topo.objects["gadm36_DEU_2"])
Insert cell
topojson = require("topojson-client@3")
Insert cell
topo = FileAttachment("germany.topo.json").json()
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more