Published
Edited
Feb 17, 2021
6 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
hebrides_animated = {
// Create the animation by cycling through the images frames
const context = DOM.context2d(hebrides_size, hebrides_size, 1);
let frame = 0;
let frame_total = Animation_frames.length;
let ticker = d3.interval(elapsed => {
context.clearRect(0, 0, hebrides_size, hebrides_size); // clear canvas
// since we generated the animation frames as ImageData, we draw them using .putImageData() https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/putImageData
context.putImageData(Animation_frames[frame], 0, 0);
if (direction == "Outwards") {
if (frame == frame_total - 1) {
frame = 0;
} else {
frame++;
}
} else {
if (frame == 0) {
frame = frame_total - 1;
} else {
frame--;
}
}
}, tickDuration);
return context.canvas;
}
Insert cell
Animation_frames = create_animationFrames({
geojson: hebrides,
size: hebrides_size,
waterlines_total: 14,
colour: '#34b2d9', // blue #247BA0 // orange #FF9505
waterlines_spacingExponent: 1 / 2,
radial_distance: hebrides_waterlines_radialDistance,
frames_total: 18
})
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
scilly_animated = {
// Create the animation by cycling through the images frames
const context = DOM.context2d(scilly_size, scilly_size, 1);
let frame = 0;
let frame_total = Animation_frames_scilly.length;
let ticker = d3.interval(elapsed => {
context.clearRect(0, 0, scilly_size, scilly_size); // clear canvas
// since we generated the animation frames as ImageData, we draw them using .putImageData() https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/putImageData
context.putImageData(Animation_frames_scilly[frame], 0, 0);
if (direction == "Outwards") {
if (frame == frame_total - 1) {
frame = 0;
} else {
frame++;
}
} else {
if (frame == 0) {
frame = frame_total - 1;
} else {
frame--;
}
}
}, tickDuration);
return context.canvas;
}
Insert cell
Animation_frames_scilly = create_animationFrames({
geojson: scilly,
size: scilly_size,
waterlines_total: 22,
colour: '#5c6cbd',
waterlines_spacingExponent: 1 / 2,
radial_distance: scilly_waterlines_radialDistance,
frames_total: 18
})
Insert cell
Insert cell
Insert cell
Insert cell
function create_animationFrames(data) {
let geojson = data.geojson,
size = data.size,
waterlines_total = data.waterlines_total,
colour = data.colour,
waterlines_spacingExponent = data.waterlines_spacingExponent,
radial_distance = data.radial_distance,
frames_total = data.frames_total;

// Use turf.rewind to make sure that the geojson winding direction is correct for d3 (or else polygons can be inverted into holes) - more on this https://bl.ocks.org/mbostock/a7bdfeb041e850799a8d3dce4d8c50c8
geojson = turf.rewind(geojson, { reverse: true });

// Create and translate the projection
let projection_canvas = d3
.geoMercator()
.fitSize([size - radial_distance, size - radial_distance], geojson);
let projection_translate = projection_canvas.translate();
projection_canvas = projection_canvas.translate([
projection_translate[0] + radial_distance / 2,
projection_translate[1] + radial_distance / 2
]);

// smoothing/curve functions for path
function curveContext(curve) {
return {
moveTo(x, y) {
curve.lineStart();
curve.point(x, y);
},
lineTo(x, y) {
curve.point(x, y);
},
closePath() {
curve.lineEnd();
}
};
}

function geoCurvePath(curve, projection, context) {
return object => {
const pathContext = context === undefined ? d3.path() : context;
d3.geoPath(projection_canvas, curveContext(curve(pathContext)))(object);
return context === undefined ? pathContext + "" : undefined;
};
}

let curve_this = d3.curveCatmullRomClosed,
path_svg = geoCurvePath(curve_this)(geojson),
path = new Path2D(path_svg);

// power scale for spacing the waterlines
let waterlines_spacing = d3
.scalePow()
.exponent(waterlines_spacingExponent)
.domain([0, waterlines_total - 1])
.range([radial_distance, 2]);

// Array of the original waterline positions
let waterline_index_array = Array.from(Array(waterlines_total).keys()),
waterline_spacing_array = waterline_index_array.map(d =>
waterlines_spacing(d)
);

// generate the paths for each frame
let frame_paths = [];
for (let j = 0; j < frames_total; j++) {
let new_waterline_spacing_array = waterline_spacing_array
.slice(0, waterline_spacing_array.length - 1)
.map((d, i) =>
d3.interpolateNumber(waterline_spacing_array[i + 1], d)(
j / frames_total
)
);
// create new first waterline
new_waterline_spacing_array.push(
d3.interpolateNumber(0, 2)(j / frames_total)
);
frame_paths.push(new_waterline_spacing_array);
}

// Create the animation frame images
let animation_frames = [];

for (let j = 0; j < frames_total; j++) {
const canvas = DOM.canvas(size, size);
const context = canvas.getContext('2d');
const path_orig = d3.geoPath(projection_canvas, context);

for (let i = 0; i < waterlines_total; i++) {
// draw colour
context.beginPath();
context.strokeStyle = colour;
if (i == 0) {
// outermost waterline fading out
context.globalAlpha = 1 - j / frames_total;
} else if (i == waterlines_total - 1) {
// new innermost waterline fading in
context.globalAlpha = j / frames_total;
} else {
context.globalAlpha = 1;
}
context.lineWidth = frame_paths[j][i] + 2;
context.stroke(path);
// clip out most of the stroke, so it becomes a line
context.beginPath();
context.globalAlpha = 1;
context.lineWidth = frame_paths[j][i];
context.globalCompositeOperation = 'destination-out'; // clipping
context.stroke(path);
context.globalCompositeOperation = 'source-over';
}
// draw the original (unsmoothed) geojson over the top
context.beginPath();
path_orig(geojson);
context.lineWidth = 2;
context.fillStyle = colour;
context.fill();

animation_frames.push(context.getImageData(0, 0, size, size));
}
return animation_frames;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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