animated_map = {
const wrapper_div = DOM.element('div');
const svg_el = DOM.svg(width, height);
const annotation_p = DOM.element('p');
wrapper_div.appendChild(annotation_p);
wrapper_div.appendChild(svg_el);
const svg = d3.select(svg_el);
let countdown_rect_fullWidth = 360;
let background = svg
.append('rect')
.attr('x', '0')
.attr('y', '0')
.attr('width', width)
.attr('height', height)
.attr('fill', background_colour);
let defs = svg.append('defs');
defs
.append('clipPath')
.attr('id', 'clip')
.append('use')
.attr('xlink:href', location + '#land_cumulative');
defs
.append('filter')
.attr('id', 'noise1')
.html(
'<feTurbulence baseFrequency="0.3"/><feDisplacementMap in="SourceGraphic" scale="10"/>'
);
// Create interpolate functions for background pencil outline transform + opacity to handle transitioning over 2 ticks
let scaling_interpolate = d3.interpolate(
'translate(80,-30)scale(1)',
'translate(-70,-100)scale(1.7)'
);
let opacity_interpolate = d3.interpolate(
pencil_outline_opacity1,
pencil_outline_opacity2
);
let map_wrapper = svg
.append('g')
.attr('id', 'map_wrapper')
.attr(
'transform',
year == years_tick_array[0]
? scaling_interpolate(0)
: year == years_tick_array[1]
? scaling_interpolate(0.5)
: scaling_interpolate(1)
);
// background pencil outline of Britain
let background_shape = map_wrapper
.append('g')
.attr('id', 'land_background_wrapper')
.append('path')
.attr('id', 'land_background')
.attr('d', path(land))
.attr('fill', pencil_outline_colour)
.attr('stroke', pencil_outline_colour)
.attr('stroke-width', '2px')
.attr(
'opacity',
year <= years_tick_array[1]
? opacity_interpolate(0)
: year == years_tick_array[2]
? opacity_interpolate(0.5)
: opacity_interpolate(1)
)
.attr('filter', 'url(#noise1)');
// opacity transition at the beginning
if (year == years_tick_array[1]) {
background_shape
.attr('opacity', opacity_interpolate(0))
.transition()
.duration(tickDuration)
.ease(d3.easeLinear)
.attr('opacity', opacity_interpolate(0.5));
} else if (year == years_tick_array[2]) {
background_shape
.attr('opacity', opacity_interpolate(0.5))
.transition()
.duration(tickDuration)
.ease(d3.easeLinear)
.attr('opacity', opacity_interpolate(1));
}
let tiles_wrapper = map_wrapper
.append('g')
.attr('id', 'tile_wrapper')
.attr('clip-path', 'url(' + location + '#clip)');
tiles_wrapper
.append('path')
.attr('id', 'offWhite_background')
.attr('fill', offWhite_colour)
.attr(
'd',
year == years_tick_array[0]
? path(map_data_yearCumulative[year])
: path(map_data_yearCumulative[year - 1])
);
let tiles_wrapper_images = tiles_wrapper
.append('g')
.attr('id', 'tile_wrapper_images');
// To avoid aliasing artifacts (thin white lines) between tiles, two layers of tiles are drawn, with the lower layer’s tiles enlarged by one pixel — From Mike Bostock's https://observablehq.com/@d3/clipped-map-tiles
tiles_wrapper_images
.selectAll('.tiles2')
.data(tiles)
.join('image')
.attr('xlink:href', d => url(d[0], d[1], d[2]))
.attr('onerror', "this.style.display='none'")
.attr('x', d => Math.round((d[0] + tiles.translate[0]) * tiles.scale - 0.5))
.attr('y', d => Math.round((d[1] + tiles.translate[1]) * tiles.scale - 0.5))
.attr('width', tiles.scale + 1)
.attr('height', tiles.scale + 1)
.attr('class', 'tiles2');
tiles_wrapper_images
.selectAll('.tiles1')
.data(tiles)
.join('image')
.attr('xlink:href', d => url(d[0], d[1], d[2]))
.attr('onerror', "this.style.display='none'")
.attr('x', d => Math.round((d[0] + tiles.translate[0]) * tiles.scale))
.attr('y', d => Math.round((d[1] + tiles.translate[1]) * tiles.scale))
.attr('width', tiles.scale)
.attr('height', tiles.scale)
.attr('class', 'tiles1');
map_wrapper
.append('use')
.attr('xlink:href', location + '#land_cumulative')
.attr('fill', 'none')
.attr('stroke', 'lightgray')
.attr('stroke-width', 0.1);
// Text annotation styling
const text_font = 'Neuton';
annotation_p.style['font-size'] = "26px";
annotation_p.style.width = "360px";
annotation_p.style.position = "fixed";
annotation_p.style.transform = 'translate(18px, 0px)';
annotation_p.style['font-family'] = text_font;
annotation_p.style['line-height'] = 'normal';
annotation_p.style.color = text_colour;
let countdown_rect = svg
.append('rect')
.attr('x', 10)
.attr('y', 10)
.attr('width', countdown_rect_fullWidth)
.attr('height', 5)
.attr('fill', 'white')
.style('transform', 'translate(8px,9px)');
////////////////
/// ANIMATED ///
////////////////
defs
.append('path')
.attr('id', 'land_cumulative')
.attr(
'd',
year == years_tick_array[0]
? path(map_data_yearCumulative[year])
: path(map_data_yearCumulative[year - 1])
);
// update map clip - put a delay on this so it doesn't look like a flash with the land_active colour fading in
setTimeout(function() {
defs
.select('#land_cumulative')
.attr('d', path(map_data_yearCumulative[year]));
d3.select('#offWhite_background').attr(
'd',
path(map_data_yearCumulative[year])
);
}, tickDuration / 1.5);
if (viewof year[0].textContent != "Play" && year == years_tick_array[1]) {
map_wrapper
.attr('transform', scaling_interpolate(0))
.transition()
.ease(d3.easeQuadIn)
.duration(tickDuration)
.attr('transform', scaling_interpolate(0.5));
} else if (
viewof year[0].textContent != "Play" &&
year == years_tick_array[2]
) {
map_wrapper
.attr('transform', scaling_interpolate(0.5))
.transition()
.ease(d3.easeQuadOut)
.duration(tickDuration)
.attr('transform', scaling_interpolate(1));
}
// Add text for the year
let text = svg
.append('text')
.attr('x', width - 117)
.attr('y', 52)
.attr('fill', text_colour)
.attr('font-size', '55px')
.attr('font-family', text_font)
.text(year);
// Add text annotation
let current_annotation = annotations_processed
.filter(d => d.year <= year)
.reverse()[0];
annotation_p.textContent = current_annotation.text;
// Transition countdown bar
if (year == year_max) {
countdown_rect.attr('width', countdown_rect_fullWidth);
} else {
countdown_rect.attr(
'width',
countdown_rect_fullWidth *
(1 -
(year - current_annotation.year) / current_annotation.years_to_next)
);
}
// If the scrubber is not paused - transition bar
if (viewof year[0].textContent != "Play" && year != year_max) {
countdown_rect
.transition()
.ease(d3.easeLinear)
.duration(tickDuration)
.attr(
'width',
countdown_rect_fullWidth *
(1 -
(year - current_annotation.year + 1) /
current_annotation.years_to_next)
);
}
// fade in the text annotation if it is a new one
if (year == current_annotation.year) {
d3.select(annotation_p)
.style('opacity', 0)
.transition()
.style('opacity', 1);
}
// Add the maps
map_wrapper
.append('path')
.attr('id', 'land_active1')
.attr('fill', active_tiles_colour)
.attr('opacity', 0)
.attr('d', path(map_data_yearSlice[year]))
.transition()
.duration(tickDuration / 3)
.ease(d3.easeLinear)
.attr('opacity', 1);
let fading_colour_duration = tickDuration * 0.8;
map_wrapper
.append('path')
.attr('id', 'land_active2')
.attr('fill', active_tiles_colour)
.attr('opacity', 1)
.attr(
'd',
year == years_tick_array[0] ? null : path(map_data_yearSlice[year - 1])
)
.transition()
.duration(fading_colour_duration)
.ease(d3.easeLinear)
.attr('opacity', 0.5);
map_wrapper
.append('path')
.attr('id', 'land_active3')
.attr('fill', active_tiles_colour)
.attr('opacity', 0.5)
.attr(
'd',
year <= years_tick_array[1] ? null : path(map_data_yearSlice[year - 2])
)
.transition()
.duration(fading_colour_duration)
.ease(d3.easeLinear)
.attr('opacity', 0.2);
map_wrapper
.append('path')
.attr('id', 'land_active4')
.attr('fill', active_tiles_colour)
.attr('opacity', 0.2)
.attr(
'd',
year <= years_tick_array[2] ? null : path(map_data_yearSlice[year - 3])
)
.transition()
.duration(fading_colour_duration)
.ease(d3.easeLinear)
.attr('opacity', 0);
// sheets outline
let sheets_outline = map_wrapper
.append('path')
.attr('id', 'sheetOutlines_cumulative')
.attr('stroke', sheets_outline_colour)
.attr('fill', 'none')
.attr('d', path(map_data_yearCumulative[year]));
// Draw outline from current annotation
if ('outline' in current_annotation) {
let annotation_outline = map_wrapper
.append('path')
.attr('id', 'annotation_outline')
.attr('fill', 'none')
.attr('stroke', chroma(active_tiles_colour).saturate(1))
.attr('stroke-width', '5px')
.attr('d', path(current_annotation.outline));
if (year == current_annotation.year) {
annotation_outline
.attr('opacity', 0)
.transition()
.duration(tickDuration / 3)
.ease(d3.easeLinear)
.attr('opacity', 1);
}
}
// If the scrubber is paused
if (viewof year[0].textContent == "Play" || year == year_max) {
setTimeout(function() {
d3.selectAll('#land_active1, #land_active2, #land_active3, #land_active4')
.transition()
.duration(tickDuration)
.ease(d3.easeLinear)
.attr('opacity', 0)
.remove();
}, tickDuration);
}
return wrapper_div;
}