Unlisted
Edited
Nov 26
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
c.flatMap(d => d.coordinates).flat(2)
Insert cell
c = contours().size([model.width, model.height]).smooth(smooth).thresholds(thresholds)(model.values)
Insert cell
models = new Map(
[
[
"grid0",
{
values: d3
.cross(d3.range(10), d3.range(10))
.map(([i, j]) => (i + 1) * (j + 1)),
width: 10,
height: 10
}
],
[
"grid1",
{
values: d3.cross(d3.range(10), d3.range(10)).map(([i, j]) => i * j),
width: 10,
height: 10
}
],
[
"grid2",
{
values: [
[
3.931884, 3.949764, 3.967644, 3.985524, 4.003405, 4.021285,
4.039165, 4.057045, 4.074925, 4.092805, 4.110685, 4.128565,
4.146446, 4.164326, 4.182206, 4.200086
],
[
3.890766, 3.910306, 3.929845, 3.949385, 3.968925, 3.988464,
4.008004, 4.027544, 4.047084, 4.066623, 4.086163, 4.105703,
4.125243, 4.144782, 4.164322, 4.183862
],
[
3.849648, 3.870847, 3.892046, 3.913245, 3.934445, 3.955644,
3.976844, 3.998043, 4.019242, 4.040442, 4.061641, 4.08284, 4.10404,
4.125239, 4.146438, 4.167637
],
[
3.808529, 3.831388, 3.854247, 3.877106, 3.899965, 3.922824,
3.945683, 3.968542, 3.991401, 4.014259, 4.037118, 4.059978,
4.082836, 4.105695, 4.128554, 4.151413
],
[
3.767411, 3.791929, 3.816448, 3.840966, 3.865485, 3.890004,
3.914522, 3.939041, 3.963559, 3.988078, 4.012596, 4.037115,
4.061633, 4.086152, 4.11067, 4.135189
],
[
3.726293, 3.752471, 3.778649, 3.804827, 3.831005, 3.857183,
3.883361, 3.909539, 3.935718, 3.961896, 3.988074, 4.014252, 4.04043,
4.066608, 4.092786, 4.118965
],
[
3.685174, 3.713012, 3.74085, 3.768687, 3.796525, 3.824363, 3.852201,
3.880038, 3.907876, 3.935714, 3.963552, 3.991389, 4.019227,
4.047065, 4.074903, 4.10274
],
[
3.644056, 3.673553, 3.703051, 3.732548, 3.762045, 3.791543, 3.82104,
3.850537, 3.880035, 3.909532, 3.939029, 3.968527, 3.998024,
4.027521, 4.057019, 4.086516
],
[
3.602938, 3.634095, 3.665252, 3.696409, 3.727566, 3.758723,
3.789879, 3.821036, 3.852193, 3.88335, 3.914507, 3.945664, 3.976821,
4.007978, 4.039135, 4.070292
],
[
3.56182, 3.594636, 3.627453, 3.660269, 3.693086, 3.725902, 3.758719,
3.791535, 3.824352, 3.857168, 3.889985, 3.922801, 3.955618,
3.988434, 4.021251, 4.054067
],
[
3.520701, 3.555177, 3.589653, 3.62413, 3.658606, 3.693082, 3.727558,
3.762034, 3.79651, 3.830986, 3.865462, 3.899939, 3.934415, 3.968891,
4.003367, 4.037843
],
[
3.479583, 3.515719, 3.551854, 3.58799, 3.624126, 3.660262, 3.696397,
3.732533, 3.768669, 3.804804, 3.84094, 3.877076, 3.913212, 3.949347,
3.985483, 4.021619
],
[
3.438465, 3.47626, 3.514055, 3.551851, 3.589646, 3.627441, 3.665237,
3.703032, 3.740827, 3.778623, 3.816418, 3.854213, 3.892009,
3.929804, 3.967599, 4.005394
],
[
3.397346, 3.436801, 3.476256, 3.515711, 3.555166, 3.594621,
3.634076, 3.673531, 3.712986, 3.752441, 3.791896, 3.83135, 3.870805,
3.91026, 3.949715, 3.98917
],
[
3.356228, 3.397343, 3.438457, 3.479572, 3.520686, 3.561801,
3.602915, 3.64403, 3.685144, 3.726259, 3.767373, 3.808488, 3.849602,
3.890717, 3.931831, 3.972946
],
[
3.31511, 3.357884, 3.400658, 3.443432, 3.486206, 3.52898, 3.571754,
3.614529, 3.657303, 3.700077, 3.742851, 3.785625, 3.828399,
3.871173, 3.913947, 3.956722
]
]
.flat()
.map((d) => 5 * d),
width: 16,
height: 16
}
],
["volcano", volcano]
],
{ label: "model", value: this?.value }
)
Insert cell
import {data as volcano} from "@d3/volcano-contours"
Insert cell
cases = [
[],
[[[1.0, 1.5], [0.5, 1.0]]],
[[[1.5, 1.0], [1.0, 1.5]]],
[[[1.5, 1.0], [0.5, 1.0]]],
[[[1.0, 0.5], [1.5, 1.0]]],
[[[1.0, 1.5], [0.5, 1.0]], [[1.0, 0.5], [1.5, 1.0]]],
[[[1.0, 0.5], [1.0, 1.5]]],
[[[1.0, 0.5], [0.5, 1.0]]],
[[[0.5, 1.0], [1.0, 0.5]]],
[[[1.0, 1.5], [1.0, 0.5]]],
[[[0.5, 1.0], [1.0, 0.5]], [[1.5, 1.0], [1.0, 1.5]]],
[[[1.5, 1.0], [1.0, 0.5]]],
[[[0.5, 1.0], [1.5, 1.0]]],
[[[1.0, 1.5], [1.5, 1.0]]],
[[[0.5, 1.0], [1.0, 1.5]]],
[]
];
Insert cell
mutable inserts = null
Insert cell
mutable debug = null
Insert cell
contours = {
const { ascending, thresholdSturges, extent, ticks, nice } = d3;
const slice = Array.prototype.slice;
const noop = () => {};

return function () {
var dx = 1,
dy = 1,
threshold = thresholdSturges,
smooth = smoothLinear;

function contours(values) {
mutable debug = [];
mutable inserts = [];
var tz = threshold(values);

// Convert number of thresholds into uniform thresholds.
if (!Array.isArray(tz)) {
const e = extent(values, finite);
tz = ticks(...nice(e[0], e[1], tz), tz);
while (tz[tz.length - 1] >= e[1]) tz.pop();
while (tz[1] < e[0]) tz.shift();
} else {
tz = tz.slice().sort(ascending);
}

return tz.map((value) => contour(values, value));
}

// fix corners and edges
//function edges(ring) {
// let [xa, ya] = ring[ring.length - 1];
// for (let i = 0; i < ring.length; ++i) {
// const [xb, yb] = ring[i];
// let insert;
// if (xb === 0 && xa !== xb) insert = [0, ya];
// if (xa === 0 && xa !== xb) insert = [0, yb];
// if (xb === dx && xa !== xb) insert = [dx, ya];
// if (xa === dx && xa !== xb) insert = [dx, yb];
// if (yb === 0 && ya !== yb) insert = [xa, 0];
// if (ya === 0 && ya !== yb) insert = [xb, 0];
// if (yb === dy && ya !== yb) insert = [xa, dy];
// if (ya === dy && ya !== yb) insert = [xb, dy];
// // if (xa===0 && yb === dy) {insert=null}
// (xa = xb), (ya = yb);
// if (insert) {
// mutable inserts.push(insert);
// ring.splice(i++, 0, insert);
// }
// }
//}

// fix corners only
function corners(ring) {
let [xa, ya] = ring[ring.length - 1];
for (let i = 0; i < ring.length; ++i) {
const [xb, yb] = ring[i];
let insert;
if (xa < 0.5 && yb > dy - 0.5) insert = [xa, yb];
if (xb > dx - 0.5 && ya > dy - 0.5) insert = [xb, ya];
if (xa > dx - 0.5 && yb < 0.5) insert = [xa, yb];
if (xb < 0.5 && ya < 0.5) insert = [xb, ya];
(xa = xb), (ya = yb);
if (insert) {
mutable inserts.push(insert);
ring.splice(i++, 0, insert);
}
}
}

// Accumulate, smooth contour rings, assign holes to exterior rings.
// Based on https://github.com/mbostock/shapefile/blob/v0.6.2/shp/polygon.js
function contour(values, value) {
const v = value == null ? NaN : +value;
if (isNaN(v)) throw new Error(`invalid value: ${value}`);

var polygons = [],
holes = [];

isorings(values, v, function (ring) {
if (fixEdges) corners(ring);
mutable debug.push(ring);
if (area(ring) > 0) polygons.push([ring]);
else holes.push(ring);
smooth(ring, values, v);
});

holes.forEach(function (hole) {
for (var i = 0, n = polygons.length, polygon; i < n; ++i) {
if (contains((polygon = polygons[i])[0], hole) !== -1) {
polygon.push(hole);
return;
}
}
});

return {
type: "MultiPolygon",
value: value,
coordinates: polygons
};
}

// Marching squares with isolines stitched into rings.
// Based on https://github.com/topojson/topojson-client/blob/v3.0.0/src/stitch.js
function isorings(values, value, callback) {
var fragmentByStart = new Array(),
fragmentByEnd = new Array(),
x,
y,
t0,
t1,
t2,
t3;

// Special case for the first row (y = -1, t2 = t3 = 0).
x = y = -1;
t1 = above(values[0], value);
mutable debug.push(t1 << 1);
cases[t1 << 1].forEach(stitch);
while (++x < dx - 1) {
(t0 = t1), (t1 = above(values[x + 1], value));
cases[t0 | (t1 << 1)].forEach(stitch);
}
cases[t1 << 0].forEach(stitch);

// General case for the intermediate rows.
while (++y < dy - 1) {
x = -1;
t1 = above(values[y * dx + dx], value);
t2 = above(values[y * dx], value);
cases[(t1 << 1) | (t2 << 2)].forEach(stitch);
while (++x < dx - 1) {
(t0 = t1), (t1 = above(values[y * dx + dx + x + 1], value));
(t3 = t2), (t2 = above(values[y * dx + x + 1], value));
cases[t0 | (t1 << 1) | (t2 << 2) | (t3 << 3)].forEach(stitch);
}
cases[t1 | (t2 << 3)].forEach(stitch);
}

// Special case for the last row (y = dy - 1, t0 = t1 = 0).
x = -1;
t2 = above(values[y * dx], value);
const eq = t2 && above(value, values[y * dx]);
cases[t2 << 2].forEach(stitch);
while (++x < dx - 1) {
(t3 = t2), (t2 = above(values[y * dx + x + 1], value));
cases[(t2 << 2) | (t3 << 3)].forEach(stitch);
}
cases[t2 << 3].forEach(stitch);

function stitch(line) {
var start = [line[0][0] + x, line[0][1] + y],
end = [line[1][0] + x, line[1][1] + y],
startIndex = index(start),
endIndex = index(end),
f,
g;
if ((f = fragmentByEnd[startIndex])) {
if ((g = fragmentByStart[endIndex])) {
delete fragmentByEnd[f.end];
delete fragmentByStart[g.start];
if (f === g) {
f.ring.push(end);
callback(f.ring);
} else {
fragmentByStart[f.start] = fragmentByEnd[g.end] = {
start: f.start,
end: g.end,
ring: f.ring.concat(g.ring)
};
}
} else {
delete fragmentByEnd[f.end];
f.ring.push(end);
fragmentByEnd[(f.end = endIndex)] = f;
}
} else if ((f = fragmentByStart[endIndex])) {
if ((g = fragmentByEnd[startIndex])) {
delete fragmentByStart[f.start];
delete fragmentByEnd[g.end];
if (f === g) {
f.ring.push(end);
callback(f.ring);
} else {
fragmentByStart[g.start] = fragmentByEnd[f.end] = {
start: g.start,
end: f.end,
ring: g.ring.concat(f.ring)
};
}
} else {
delete fragmentByStart[f.start];
f.ring.unshift(start);
fragmentByStart[(f.start = startIndex)] = f;
}
} else {
fragmentByStart[startIndex] = fragmentByEnd[endIndex] = {
start: startIndex,
end: endIndex,
ring: [start, end]
};
}
}
}

function index(point) {
return point[0] * 2 + point[1] * (dx + 1) * 4;
}

function smoothLinear(ring, values, value) {
ring.forEach(function (point, i) {
var x = point[0],
y = point[1],
xt = x | 0,
yt = y | 0,
k = yt * dx + xt,
v0 = valid(values[k]);


// Special case for the first column: move horizontally
// TODO: do it for the last column, first row, last row…
if (x === 0) {
// estimate the value at the point, by linear interpolation (todo: bilinear model? in particular in the corners?)
const estimate = y === dy ? 2 * values[k - dx] - values[k - dx + 1] : 2 * v0 - values[k + 1];
point[0] += Math.max(0, smooth1(valid(estimate), y === dy ? valid(values[k - dx]) : v0, value));
}

if (x > 0 && x < dx && xt === x) {
if (yt === dy) {
point[0] += smooth1(
valid(values[k - dx - 1]),
valid(values[k - dx]),
value
);
} else {
point[0] += smooth1(valid(values[k - 1]), v0, value);
}
}
if (y > 0 && y < dy && yt === y) {
if (xt === dx) {
point[1] += smooth1(
valid(values[k - dx - 1]),
valid(values[k - 1]),
value
);
} else {
point[1] += smooth1(valid(values[(yt - 1) * dx + xt]), v0, value);
}
}
});
}

function clamp(x, min, max) {
return x < min ? min : x > max ? max : x;
}

contours.contour = contour;

contours.size = function (_) {
if (!arguments.length) return [dx, dy];
var _0 = Math.floor(_[0]),
_1 = Math.floor(_[1]);
if (!(_0 >= 0 && _1 >= 0)) throw new Error("invalid size");
return (dx = _0), (dy = _1), contours;
};

contours.thresholds = function (_) {
return arguments.length
? ((threshold =
typeof _ === "function"
? _
: Array.isArray(_)
? constant(slice.call(_))
: constant(_)),
contours)
: threshold;
};

contours.smooth = function (_) {
return arguments.length
? ((smooth = _ ? smoothLinear : noop), contours)
: smooth === smoothLinear;
};

return contours;
};

// When computing the extent, ignore infinite values (as well as invalid ones).
function finite(x) {
return isFinite(x) ? x : NaN;
}

// Is the (possibly invalid) x greater than or equal to the (known valid) value?
// Treat any invalid value as below negative infinity.
function above(x, value) {
return x == null ? false : +x >= value;
}

// During smoothing, treat any invalid value as negative infinity.
function valid(v) {
return v == null || isNaN((v = +v)) ? -Infinity : v;
}

function smooth1(v0, v1, value) {
const a = value - v0;
const b = v1 - v0;
const d = isFinite(a) || isFinite(b) ? a / b : Math.sign(a) / Math.sign(b);
return isFinite(d) ? d - 0.5 : 0;
}
}
Insert cell
contains = {
return function(ring, hole) {
var i = -1, n = hole.length, c;
while (++i < n) if (c = ringContains(ring, hole[i])) return c;
return 0;
}

function ringContains(ring, point) {
var x = point[0], y = point[1], contains = -1;
for (var i = 0, n = ring.length, j = n - 1; i < n; j = i++) {
var pi = ring[i], xi = pi[0], yi = pi[1], pj = ring[j], xj = pj[0], yj = pj[1];
if (segmentContains(pi, pj, point)) return 0;
if (((yi > y) !== (yj > y)) && ((x < (xj - xi) * (y - yi) / (yj - yi) + xi))) contains = -contains;
}
return contains;
}

function segmentContains(a, b, c) {
var i; return collinear(a, b, c) && within(a[i = +(a[0] === b[0])], c[i], b[i]);
}

function collinear(a, b, c) {
return (b[0] - a[0]) * (c[1] - a[1]) === (c[0] - a[0]) * (b[1] - a[1]);
}

function within(p, q, r) {
return p <= q && q <= r || r <= q && q <= p;
}
}
Insert cell
constant = x => () => x;
Insert cell
area = function(ring) {
var i = 0, n = ring.length, area = ring[n - 1][1] * ring[0][0] - ring[n - 1][0] * ring[0][1];
while (++i < n) area += ring[i - 1][1] * ring[i][0] - ring[i - 1][0] * ring[i][1];
return area;
}

Insert cell
Plot.geo({"type":"MultiPolygon","value":0,"coordinates":[[[[10,10],[10,9.5],[10,8.5],[10,7.5],[10,6.5],[10,5.5],[10,4.5],[10,3.5],[10,2.5],[10,1.5],[10,0.5],[10,0],[9.5,0],[8.5,0],[7.5,0],[6.5,0],[5.5,0],[4.5,0],[3.5,0],[2.5,0],[1.5,0],[0.5,0],[0,0],[0,0.5],[0,1.5],[0,2.5],[0,3.5],[0,4.5],[0,5.5],[0,6.5],[0,7.5],[0,8.5],[0,9.5],[0,10],[0.5,10],[1.5,10],[2.5,10],[3.5,10],[4.5,10],[5.5,10],[6.5,10],[7.5,10],[8.5,10],[9.5,10],[10,10]],[[1.5,2.5],[0.5,1.5],[1.5,0.5],[2.5,1.5],[1.5,2.5]],[[3.5,5.5],[2.5,4.5],[3.5,3.5],[4.5,4.5],[3.5,5.5]],[[2.5,8.5],[1.5,7.5],[2.5,6.5],[3.5,7.5],[2.5,8.5]],[[7.5,8.5],[6.5,7.5],[7.5,6.5],[8.5,7.5],[7.5,8.5]]]]}, {fill: "#eee", stroke: "black"}).plot()
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