eurostat-map

Multi-Layer Stack Guide & Migration

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.


1. Core Architecture: Map vs. Layer

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:


2. Multi-Layer Declaration API

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();

Chaining with addLayer

Alternatively, 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();

3. Layer Roles: Base vs. Overlay

To prevent visual conflicts, layers have distinct roles:

  1. Base Layers (base): Color region backgrounds. A map can have at most one base layer (e.g. choropleth, categorical, etc.).
  2. Overlay Layers (overlay): Render shapes or vector symbols (e.g., proportionalSymbol, pieChart) at centroids.

Correctness and DOM Isolation


4. Categorical exceptions within a single base layer

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).

5. Backwards Compatibility & Legacy Sugar

All legacy single-type constructors and setters remain fully supported: