Published unlisted
Edited
May 15, 2019
Insert cell
Insert cell
chart = md`<div id="container" style="width:${width}px;height:${height}px">
<div>${staticcanvas}</div>
<div>${mobilecanvas}</div>
</div>
<style>
#container div { height: 0 }
</style>`
Insert cell
staticcanvas = {
const context = DOM.context2d(width, height),
path = d3.geoPath(projection).context(context);

context.lineWidth = 1;
lignes.features.forEach(f => {
context.beginPath();
path(f);
if (f.properties.metro) {
context.strokeStyle = "#666";
context.lineWidth = 6;
context.stroke();
}
context.strokeStyle = "rgb(" + f.properties.couleur + ")";
context.lineWidth = 1;
context.stroke();
});

return context.canvas;
}
Insert cell
mobilecanvas = {
// const width = 500;
projection.fitExtent([[10, 10], [width - 10, height - 10]], bus);
const context = DOM.context2d(width, height),
path = d3.geoPath(projection).context(context);

do {
fade(context, 0.05);

sims(Date.now()).forEach(a => {
context.beginPath();
path.pointRadius(a.metro ? 5 : 2.5);
path({
type: "Point",
coordinates: a.coordinates
});
context.fillStyle = a.color;
context.fill();
});
yield context.canvas;
} while (true);

return context.canvas;
}
Insert cell
function fade(context, alpha) {
const { globalAlpha, globalCompositeOperation } = context;
context.globalCompositeOperation = "destination-out";
context.fillStyle = "white";
context.globalAlpha = alpha || 1;
context.fillRect(0, 0, context.canvas.width, context.canvas.height);
context.globalAlpha = globalAlpha;
context.globalCompositeOperation = globalCompositeOperation;
}
Insert cell
sims = {
const agents = lignes.features
//.filter((_, i) => i === 0)
.map(d => {
const line = d.geometry.coordinates;
const domain = [];
var c0 = line[0],
l = 0;
line.forEach(c => {
domain.push((l += d3.geoDistance(c, c0)));
c0 = c;
});
const lon = d3
.scaleLinear()
.domain(domain)
.range(line.map(d => d[0])),
lat = d3
.scaleLinear()
.domain(domain)
.range(line.map(d => d[1]));

return {
line,
domain,
max: l,
pos0: l * Math.random(),
speed: l / 10,
dir: 1,
lon,
lat,
color: `rgb(${d.properties.couleur})`,
metro: d.properties.metro
};
});

return function(t) {
return agents.map(a => {
a.pos = ((t / 1000) * a.speed + a.pos0) % (2 * a.max);
if (a.pos > a.max) a.pos = a.pos - a.max; // 2 * a.max - a.pos;
return {
coordinates: [a.lon(a.pos), a.lat(a.pos)],
color: a.color,
metro: a.metro
};
});
};
}
Insert cell
projection = d3
.geoMercator()
.fitExtent([[10, 10], [width - 10, height - 10]], bus)
Insert cell
height = 800
Insert cell
Insert cell
api_METRO =
"https://download.data.grandlyon.com/wfs/rdata?SERVICE=WFS&VERSION=2.0.0&request=GetFeature&typename=tcl_sytral.tcllignemf&outputFormat=application/json; subtype=geojson&SRSNAME=EPSG:4326&count=10000&startIndex=1"
Insert cell
bus = d3.json(api_BUS)
Insert cell
[...d3.group(bus.features, d => d.properties.ligne)].filter(
d => d[1].length !== 2
)
Insert cell
metro = d3.json(api_METRO).then(
d => (
d.features.forEach(
f => (f.properties.metro = f.properties.ligne[0] !== "F") // Métro ou *F*uniculaire ?
),
d
)
)
Insert cell
lignes = ({
type: "FeatureCollection",
features: [metro.features, bus.features].flat()
})
Insert cell
d3 = require("d3@5", "d3-array@2")
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