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

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