Public
Edited
Mar 12, 2023
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
height = Math.floor(width * 0.72)
Insert cell
Insert cell
Insert cell
Insert cell
viewof nodes = Inputs.table(cityData.nodes)
Insert cell
function coordinates(node) {
return [node.x, node.y];
}
Insert cell
projection = d3.geoMercator().fitSize([width, height], {
type: "MultiPoint",
coordinates: nodes.map(coordinates)
})
Insert cell
{
const svg = d3.create("svg").attr("viewBox", [0, 0, width, height]);
const path = d3.geoPath(projection).pointRadius(1.5);

svg
.append("path")
.attr(
"d",
path({ type: "MultiPoint", coordinates: nodes.map(coordinates) })
)
.attr("fill", "#888");

return svg.node();
}
Insert cell
Insert cell
viewof edges = Inputs.table(cityData.edges)
Insert cell
nodesIndex = d3.index(nodes, (d) => d.osmid)
Insert cell
{
const svg = d3.create("svg").attr("viewBox", [0, 0, width, height]);
const path = d3.geoPath(projection);

const length2color = d3
.scaleSequential()
.interpolator(d3.interpolateBlues)
.domain([
d3.quantile(
edges.map((d) => d.length),
0.05
),
d3.quantile(
edges.map((d) => d.length),
0.95
)
]);

svg
.append("g")
.selectAll("path")
.data(edges)
.join("path")
.attr("d", (e) =>
path({
type: "LineString",
coordinates: [
coordinates(nodesIndex.get(e.u)),
coordinates(nodesIndex.get(e.v))
]
})
)
.attr("stroke", (e) => length2color(e.length));

return svg.node();
}
Insert cell
Insert cell
import { shortest_tree } from "@fil/dijkstra"
Insert cell
graph = {
const graph = {
sources: [],
targets: [],
costs: []
};
const nodesIndex = new Map(nodes.map(({ osmid }, i) => [osmid, i]));
for (const { u, v, length } of edges) {
graph.sources.push(nodesIndex.get(u));
graph.targets.push(nodesIndex.get(v));
graph.costs.push(length); // Equal cost, regardless of street type
}
return graph;
}
Insert cell
origins = (replay,
Array.from({ length: nOrigins }, () => (Math.random() * nodes.length) | 0))
Insert cell
tree = shortest_tree({ graph, origins })
Insert cell
map = () => {
const delay0 = 300;

const svg = d3
.create("svg")
.attr("viewBox", [0, 0, width, height])
.style("background", "#333");

const path = d3.geoPath(projection);
const color = d3.scaleOrdinal(d3.shuffle(d3.schemeSet1));

// all streets have same line style, regardless of their highway type
const gGraph = svg
.append("g")
.style("stroke", "white")
.style("stroke-width", 0.125);

// streets, base color
gGraph
.append("path")
.datum({
type: "MultiLineString",
coordinates: edges.map(({ u, v }) => [
coordinates(nodesIndex.get(u)),
coordinates(nodesIndex.get(v))
])
})
.attr("d", path);

// streets, colored by the origins
// timing the animation with the cost values
svg
.append("g")
.attr("opacity", 0.7)
.selectAll("path")
.data(d3.range(nodes.length))
.join("path")
.attr("d", (i) =>
tree.predecessor[i] === -1
? null
: path({
type: "LineString",
coordinates: [
coordinates(nodes[tree.predecessor[i]]),
coordinates(nodes[i])
]
})
)
.attr("stroke", (i) => color(tree.origin[i]))
// Animation starts here
.attr("opacity", 0)
.attr("stroke-dasharray", [0, 1000])
.transition()
.delay((i) => delay0 + tree.cost[i])
.attr("opacity", 1)
.attr("stroke-dasharray", (i) =>
tree.predecessor[i] === -1
? []
: [(tree.cost[i] - tree.cost[tree.predecessor[i]]) / 10, 1000]
);

const gNodes = svg.append("g");
const sorted = d3.sort(d3.range(nodes.length), (i) => tree.cost[i]);

path.pointRadius(2);
gNodes
.selectAll("path")
.data(d3.range(nodes.length))
.join("path")
.attr("d", (i) =>
path({
type: "Point",
coordinates: coordinates(nodes[i])
})
)
.attr("fill", (i) => color(tree.origin[i]))
// Animation of nodes
.attr("opacity", 0)
.transition()
.delay((i) => delay0 + tree.cost[i])
.duration(1000)
.attr("opacity", 1);

path.pointRadius(5);
svg
.append("g")
.selectAll("path")
.data(origins)
.join("path")
.attr("d", (i) =>
path({
type: "Point",
coordinates: coordinates(nodes[i])
})
)
.attr("stroke", "#ddd")
.attr("fill", (i) => color(tree.origin[i]));

svg
.append("g")
.attr("transform", "translate(20, 40)")
.append("text")
.text(`${city}, Hokkaido`)
.style("fill", "#ddd")
.style("font-family", "sans-serif")
.style("font-size", "22px");
svg
.append("g")
.attr("transform", "translate(20, 65)")
.append("text")
.text(
`${nodes.length.toLocaleString()} nodes / ${edges.length.toLocaleString()} edges`
)
.style("fill", "#ccc")
.style("font-family", "sans-serif")
.style("font-size", "15px");

return svg.node();
}
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