Published unlisted
Edited
May 15, 2019
Insert cell
Insert cell
chart = {
aframe; // wait for the module to load before building (???)
//if (this) return this; // avoids a crash when going fullscreen (??)

await Promises.delay(500);

const height = window.outerHeight - 50;

const chart = html`<div style="min-width:100%;height:${height}px; max-height:100vw;">${scene}</div>`;

var t;
const model = d3.select(chart).select("#model"),
bbox_mixin = d3.select(chart).selectAll("#bboxtube a-tube");

bbox_mixin.attr(
"material",
"color: white" // + color((1 + Math.sin(+Date.now() / 100)) / 2)
);

yield chart;

// <!-- space-time color tubes -->

const routes = d3
.select(chart)
.select("#model")
.append("a-entity")
.attr("id", "routes")
.selectAll("a-entity")
.data(route.features)
.join("a-tube")
.attr("radius", 0.023)
.attr("material", l => `color: ${l.properties.color};`);

var alpha = 0;
do {
alpha = 1 - alpha;
routes
.transition()
.duration(1000)
.attr("path", l =>
l.geometry.coordinates
.map(d => `${xy(projection(d).join(","))} ${z(d, alpha)}`)
.join(", ")
);

d3.select(chart)
.select("a-sky")
.transition()
.duration(1000)
.attr("color", alpha ? "#abbbea" : "#446");
await Promises.delay(3500);
} while (true);

return chart;
}
Insert cell
data = d3.csv(
"https://projet.liris.cnrs.fr/mi2/oddata/samsunghealth/samsunghealth.csv"
)
Insert cell
/* a LineString with z */
route = {
const z = d3
.scaleLinear()
.domain(d3.extent(data, d => +d.alt))
.range([0, 8]);
const color = d3.scaleOrdinal(d3.schemeCategory10);

var route = {
type: "FeatureCollection",
features: [...d3.group(data, d => d.id)].map(l => {
const t0 = d3.min(l[1], d => +d.time),
t = d3
.scaleLinear()
.domain([t0, t0 + 1000000])
.range([0, 2]);
return {
type: "Feature",
properties: { color: color(l[0]) },
geometry: {
type: "LineString",
coordinates: l[1].map(d => [+d.lon, +d.lat, z(d.alt), t(d.time)])
}
};
})
};

return route;
}
Insert cell
projection = d3
.geoMercator()
.fitExtent([[-2, -2], [2, 2]], route)
.precision(0.0001)
Insert cell
xy = d => {
d = d.split(/,/).map(d => +d);
d[1] = -d[1];
return d.join(" ");
}
Insert cell
z = {
const scale = d3
.scaleLinear()
.domain([0, 1])
.range([0, 0.15]);
return (d, alpha) => scale(d[3] * alpha + (1 - alpha) * d[2]);
}
Insert cell
Insert cell
function bboxtube(low, hi, opts) {
let a = "",
i = 0;

if (opts.color) opts.material = `color: ${opts.color};`;

const corners = [
[low[0], low[1]],
[low[0], hi[1]],
[hi[0], hi[1]],
[hi[0], low[1]]
];
for (const c of corners)
a += `<a-tube path="${c[0]} ${c[1]} ${low[2]}, ${c[0]} ${c[1]} ${
hi[2]
}" radius="${opts.radius}" mixin="bbox"></a-tube>`;

corners.push(corners[0]);
d3.pairs(corners).forEach(p => {
a += `<a-tube path="${p[0][0]} ${p[0][1]} ${low[2]}, ${p[1][0]} ${
p[1][1]
} ${low[2]}" radius="${opts.radius}" mixin="bbox"></a-tube>`;
a += `<a-tube path="${p[0][0]} ${p[0][1]} ${hi[2]}, ${p[1][0]} ${p[1][1]} ${
hi[2]
}" radius="${opts.radius}" mixin="bbox"></a-tube>`;
});

return `<a-entity id="bboxtube">${a}
<a-mixin id="bbox" material="${opts.material}"></a-mixin>
</a-entity>`;
}
Insert cell
scene = `<a-scene embedded>

<a-entity id="model" rotation="-90 0 0">


<!-- axes (disabled) -->
<!--
<a-entity id="axes"
line__x="start: 0 0 0; end: 1 0 0; color: yellow"
line__y="start: 0 0 0; end: 0 1 0; color: green"
line__z="start: 0 0 0; end: 0 0 1; color: blue"
></a-entity>
-->

<!-- bounding-box -->
${bboxtube([-2, -2, 0], [2, 2, 8 * 0.15], {
material: "color: lime; metalness: 0.3; roughness:0.8",
radius: 0.005
})}


<!-- planar map -->
<a-plane position="0 0 ${0}" width="4" height="4" opacity=0.99 src="#map" metalness="0.5" side="double">
</a-plane>

</a-entity>


<!-- sky & camera controls -->
<a-sky color="#abbbea"></a-sky>

<a-entity id=camera camera look-controls position="0 1 2.5" orbit-controls="target: 0 0 0; minDistance: 0.5; maxDistance: 10; initialPosition: 0 1 2.5"></a-entity>

</a-scene>`
Insert cell
Insert cell
d3 = require("d3@5", "d3-array@2", "d3-scale-chromatic@1")
Insert cell
map = {
const width = 1500,
height = 1200;
const scale0 = projection.scale(),
translate0 = projection.translate(),
context = DOM.context2d(width, width, 2), // must be square to be mapped to the square <a-plane>
path = d3.geoPath(projection).context(context);

projection.fitExtent([[0, 0], [width, width]], route);
context.beginPath();
//path(world);
context.fillStyle = "rgba(255, 255, 255, 0.5)";
context.fill();
context.strokeStyle = "#222";
context.stroke();
route.features.forEach(r => {
context.beginPath();
path(r);
context.lineWidth = 2;
context.strokeStyle = r.properties.color;
context.stroke();
});
context.canvas.setAttribute("id", "map");

projection.scale(scale0).translate(translate0);

context.canvas.style = "width:100%; height:auto";

return context.canvas;
}
Insert cell
aframe = {
await require("aframe");

// await import("aframe-curve-component/dist/aframe-curve-component.js?module");
// await import("aframe-extras/dist/aframe-extras.js?module");
await require("aframe-extras/dist/aframe-extras.js").catch(() => {}); // works on Firefox

await require("aframe-orbit-controls@1.2.0/dist/aframe-orbit-controls.min.js").catch(
() => {}
);

return window.AFRAME;
}
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