Unlisted
Edited
Jun 28, 2019
Importers
1 star
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
raw = d3.csv(
"https://gist.githubusercontent.com/Fil/2a8cbb481023bd8224d48b10c55c5215/raw/28c8c8276f8b10eecce84f038c7fd24e7153acf9/ice-sheets.csv",
d3.autoType
)
// Data source: https://nsidc.org/data/seaice_index/archives
Insert cell
poles = d3.group(
raw
.filter(d => d.extent > 0)
.sort((a, b) => d3.ascending(a.year, b.year) || d3.ascending(a.mo, b.mo)),
d => d.region
)
Insert cell
region = "N"
Insert cell
scaleRadius = d3
.scaleLinear()
.domain([0, d3.max(poles.get(region), d => d.extent)])
.range([0, 200])
Insert cell
line = d3
.lineRadial()
.angle(d => (d.mo / 12) * Math.PI * 2)
.radius(d => scaleRadius(d.extent))
Insert cell
/*
* A fork of d3.curveNatural that solves both issues (more or less).
*/
curveRadialNatural = {
return function(complete) {
function Natural(context) {
this._context = context;
}

Natural.prototype = {
areaStart: function() {
this._line = 0;
},
areaEnd: function() {
this._line = NaN;
},
lineStart: function() {
this._x = [];
this._y = [];
},
lineEnd: function() {
var x = this._x,
y = this._y,
n = x.length;

if (n) {
this._line
? this._context.lineTo(x[0], y[0])
: this._context.moveTo(x[0], y[0]);
if (n === 2) {
this._context.lineTo(x[1], y[1]);
} else {
var px = controlPoints(x),
py = controlPoints(y);
for (
var i0 = 0, i1 = 1;
i1 < (this.complete ? n : n - 1); // TBD: apply only on the last element of data!
++i0, ++i1
) {
this._context.bezierCurveTo(
px[0][i0],
py[0][i0],
px[1][i0],
py[1][i0],
x[i1],
y[i1]
);
}
}
}

if (this._line || (this._line !== 0 && n === 1))
this._context.closePath();
this._line = 1 - this._line;
this._x = this._y = null;
},
point: function(x, y) {
// if the new point has a a wide angle with the previous one, insert intermediates
x = +x;
y = +y;
const l = this._x.length;
if (l > 0) {
const px = this._x[l - 1],
py = this._y[l - 1],
norm = Math.sqrt(x ** 2 + y ** 2),
pnorm = Math.sqrt(px ** 2 + py ** 2),
N = norm * pnorm,
angle = N ? Math.acos((x * px + y * py) / N) : 0;
if (Math.abs(angle) > 1) {
var a = Math.atan2(py, px),
b = Math.atan2(y, x);
if (a < b - Math.PI) a += 2 * Math.PI;
if (b < a - Math.PI) b += 2 * Math.PI;
for (var t = 0.34; t < 1; t += 0.333) {
const n = pnorm * Math.pow(norm / pnorm, t),
alpha = a * (1 - t) + b * t;
this._x.push(n * Math.cos(alpha));
this._y.push(n * Math.sin(alpha));
}
}
}
this._x.push(x);
this._y.push(y);
}
};

// See https://www.particleincell.com/2012/bezier-splines/ for derivation.
function controlPoints(x) {
var i,
n = x.length - 1,
m,
a = new Array(n),
b = new Array(n),
r = new Array(n);
(a[0] = 0), (b[0] = 2), (r[0] = x[0] + 2 * x[1]);
for (i = 1; i < n - 1; ++i)
(a[i] = 1), (b[i] = 4), (r[i] = 4 * x[i] + 2 * x[i + 1]);
(a[n - 1] = 2), (b[n - 1] = 7), (r[n - 1] = 8 * x[n - 1] + x[n]);
for (i = 1; i < n; ++i)
(m = a[i] / b[i - 1]), (b[i] -= m), (r[i] -= m * r[i - 1]);
a[n - 1] = r[n - 1] / b[n - 1];
for (i = n - 2; i >= 0; --i) a[i] = (r[i] - a[i + 1]) / b[i];
b[n - 1] = (x[n] + a[n - 1]) / 2;
for (i = 0; i < n - 1; ++i) b[i] = 2 * x[i + 1] - a[i + 1];
return [a, b];
}

return function(context) {
return new Natural(context);
};
};
}
Insert cell
/*
* BAD!!
* this code would (arguably) be simpler, as it overloads the existing d3.curveNatural
* it works well for artifact 1, but not for artifact 2, as I can't figure how to access this._x
*/
curve2 = {
return function(complete) {
return function(context) {
const curve = d3.curveNatural(context);

curve.point = function(x, y) {
// if the new point has a a wide angle with the previous one, insert intermediates
x = +x;
y = +y;

const l = curve._x.length;
if (l > 0) {
const px = curve._x[l - 1],
py = curve._y[l - 1],
norm = Math.sqrt(x ** 2 + y ** 2),
pnorm = Math.sqrt(px ** 2 + py ** 2),
N = norm * pnorm,
angle = N ? Math.acos((x * px + y * py) / N) : 0;
if (Math.abs(angle) > 1) {
var a = Math.atan2(py, px),
b = Math.atan2(y, x);
if (a < b - Math.PI) a += 2 * Math.PI;
if (b < a - Math.PI) b += 2 * Math.PI;
for (var t = 0.34; t < 1; t += 0.333) {
const n = pnorm * Math.pow(norm / pnorm, t),
alpha = a * (1 - t) + b * t;
curve._x.push(n * Math.cos(alpha));
curve._y.push(n * Math.sin(alpha));
}
}
}
curve._x.push(x);
curve._y.push(y);
};

// DOESNT WORK
curve.lineEnd = function() {
var x = curve._x,
y = curve._y,
n = x.length;

if (n) {
curve._line
? curve._context.lineTo(x[0], y[0])
: curve._context.moveTo(x[0], y[0]);
if (n === 2) {
curve._context.lineTo(x[1], y[1]);
} else {
var px = curve.controlPoints(x),
py = curve.controlPoints(y);
for (var i0 = 0, i1 = 1; i1 < (complete ? n : n - 1); ++i0, ++i1) {
curve._context.bezierCurveTo(
px[0][i0],
py[0][i0],
px[1][i0],
py[1][i0],
x[i1],
y[i1]
);
}
}
}
};

return curve;
};
};
}
Insert cell
Insert cell
d3 = require("d3@5", "d3-array@2")
Insert cell
Insert cell
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