function animated_map(options) {
const svg = d3.select(
DOM.svg(
options.width_padding ? width + options.width_padding : width,
height
)
);
let background_colour,
shape_behind_colour,
shape_behind_opacity,
text_colour,
sheetsActive_colour;
if (options.colour_mode == 'dark') {
background_colour = 'black';
shape_behind_colour = 'white';
shape_behind_opacity = 0.4;
text_colour = 'white';
sheetsActive_colour = options.sheetsActive_colour || 'cyan';
} else {
background_colour = 'white';
shape_behind_colour = 'black';
shape_behind_opacity = 0.05;
text_colour = 'black';
sheetsActive_colour = options.sheetsActive_colour || 'pink';
}
let translate_x = options.translate_x || 0,
translate_y = options.translate_y || 0;
// Add background coloured rectangle
let background = svg
.append('rect')
.attr('x', '0')
.attr('y', '0')
.attr(
'width',
options.width_padding ? width + options.width_padding : width
)
.attr('height', height)
.attr('fill', background_colour);
// define SVG defs
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"/>'
);
let map_wrapper = svg
.append('g')
.attr('id', 'map_wrapper')
.style(
'transform',
options.width_padding
? `translate(${options.width_padding / 2 +
translate_x}px,${translate_y}px)`
: ""
);
// Draw pencil outline of shape data (here Great Britain) beneath revealing map sheets.
map_wrapper
.append('path')
.attr('id', 'land_background')
.attr(
'd',
path(options.shape_behind || { type: 'FeatureCollection', features: [] })
)
.attr('fill', options.colour_mode == 'light' ? shape_behind_colour : 'none')
.attr(
'stroke',
options.colour_mode == 'light' ? 'none' : shape_behind_colour
)
.attr('stroke-width', '2px')
.attr('opacity', shape_behind_opacity)
.attr('transform', 'scale(0.994)') // shrink the shape a very small amount because the noise blur expands beyond its outline and can be visible outside of the map polygon area
.attr('filter', 'url(#noise1)');
// Draw historical map imagery from map tiles
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', options.maps_baseColor || 'none')
.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);
////////////////
/// 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);
// Add text for the year
let text = svg
.append('text')
.attr('x', width - 110)
.style(
'transform',
options.width_padding ? `translate(${options.width_padding}px,0px)` : ''
)
.attr('y', 50)
.attr('fill', text_colour)
.attr('font-size', '50px')
.text(year);
// Add the maps
map_wrapper
.append('path')
.attr('id', 'land_active1')
.attr('fill', sheetsActive_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;
// Create 3 paths drawing a fade-out colour over the new sheets that year (opacity 1 → 0.5), year - 1 (fainter: opacity 0.5 → 0.2 ), and year - 2 (fades to disappear).
map_wrapper
.append('path')
.attr('id', 'land_active2')
.attr('fill', sheetsActive_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', sheetsActive_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', sheetsActive_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);
// Draw sheet boundary outline
let sheets_outline = options.draw_sheetOutlines
? map_wrapper
.append('path')
.attr('id', 'sheetOutlines_cumulative')
.attr('stroke', sheetsActive_colour)
.attr('opacity', 0.5)
.attr('fill', 'none')
.attr('d', path(map_data_yearCumulative[year]))
: null;
// 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 svg.node();
}