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

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more