This guide introduces the Multi-Layer Stack API in eurostat-map. This feature allows stacking multiple thematic layers (such as a choropleth base layer and a proportional symbol overlay layer) on a single map, sharing geometry, projection, and datasets while maintaining isolated rendering groups, tooltips, and legends.
In the legacy API, a map was created with a single type:
eurostatmap.map('choropleth')...
This fused the map frame (dimensions, zoom, projections, dataset registry) with the thematic visualization (class breaks, coloring, styling, legends).
The new architecture splits these concerns:
statData_), build lifecycle, and frame decorations.type (e.g., choropleth, proportionalSymbol), local visual encoding configurations, classification, and styling state. Renders to its own isolated DOM group.To create a stacked map, call eurostatmap.map() with no type argument, then define shared datasets and declare the stack of layers using .layers([...]) or .addLayer(...):
const map = eurostatmap
.map() // returns a map frame with an empty layers stack
.width(800)
.nutsYear(2021)
.nutsLevel(0)
.title('Population and density')
// Define shared datasets (fetched/cached once at the map level)
.stat('density', { eurostatDatasetCode: 'demo_r_d3dens', unitText: 'people/km²' })
.stat('population', { eurostatDatasetCode: 'demo_r_pop', unitText: 'people' })
// Declare the layers stack (rendered from bottom to top)
.layers([
{
type: 'choropleth',
encoding: { fill: { stat: 'density' } },
classificationMethod: 'quantile',
numberOfClasses: 7
},
{
type: 'proportionalSymbol',
encoding: { size: { stat: 'population' } },
psSettings: {
maxSize: 30,
minSize: 3,
fill: '#ff7800',
fillOpacity: 0.6
}
}
])
.build();
addLayerAlternatively, you can add layers imperatively. addLayer returns the layer instance, which lets you configure it via chaining:
const map = eurostatmap.map().width(800);
// Add choropleth base
const base = map.addLayer('choropleth')
.encoding('fill', { stat: 'density' })
.classificationMethod('quantile')
.numberOfClasses(5);
// Add proportional symbols overlay
const overlay = map.addLayer('proportionalSymbol')
.encoding('size', { stat: 'population' })
.legend({
sizeLegend: { title: 'Population' }
});
map.build();
To prevent visual conflicts, layers have distinct roles:
base): Color region backgrounds. A map can have at most one base layer (e.g. choropleth, categorical, etc.).overlay): Render shapes or vector symbols (e.g., proportionalSymbol, pieChart) at centroids.<g> groups above the base layer, ensuring symbols stay visible and clickable.Because a map can have at most one base layer, you cannot stack a categorical layer on top of a choropleth layer to give a handful of regions their own named category (e.g. “no railway lines” on a map of electrification rate) — attempting to addLayer('categorical') when a choropleth base already exists is rejected with A base layer already exists; ignoring extra base layer.
For that specific need — a small, closed set of regions whose raw value isn’t a number and deserves its own legend/tooltip entry, mixed into an otherwise numeric classification — use categoryFillStyle/categoryText on the choropleth layer itself instead of a second base layer:
eurostatmap
.map('ch')
.categoryFillStyle({ '-': '#cccccc' })
.categoryText({ '-': 'No railway lines' })
.build()
Regions whose raw stat value matches a configured key get their own fill colour, legend swatch, and tooltip label instead of numeric classification or generic “no data” treatment. See the choropleth map reference for details. If you need genuinely independent classifications for two variables rendered on top of each other (not just a few named exceptions), reach for the overlay-layer stack in this guide instead (e.g. a categorical base plus a proportionalSymbol overlay).
All legacy single-type constructors and setters remain fully supported:
eurostatmap.map('choropleth') still instantiates a single-type choropleth map.map.colors(), map.classes(), map.legend()) are transparently forwarded to Layer 0 (the active base layer).