The VControlLegend component provides an interactive legend for map layers. It supports three legend types:
Features:
<script setup lang="ts">
import { VMap, VControlLegend, VLayerMaplibreGeojson } from '@geoql/v-maplibre';
const mapOptions = {
style: 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json',
center: [-74.5, 40],
zoom: 9,
};
const legendItems = [
{ value: 'residential', label: 'Residential', color: '#4CAF50' },
{ value: 'commercial', label: 'Commercial', color: '#2196F3' },
{ value: 'industrial', label: 'Industrial', color: '#FF9800' },
];
</script>
<template>
<VMap :options="mapOptions" style="height: 500px">
<VLayerMaplibreGeojson
source-id="buildings"
layer-id="buildings-layer"
:source="buildingsSource"
:layer="buildingsLayer"
/>
<VControlLegend
:layer-ids="['buildings-layer']"
type="category"
:items="legendItems"
title="Land Use"
position="top-right"
:interactive="true"
/>
</VMap>
</template>
<script setup lang="ts">
import { VMap, VControlLegend } from '@geoql/v-maplibre';
const gradientItems = [
{
min: 0,
max: 100,
colors: ['#ffffcc', '#a1dab4', '#41b6c4', '#225ea8'],
minLabel: 'Low',
maxLabel: 'High',
},
];
</script>
<template>
<VMap :options="mapOptions" style="height: 500px">
<VControlLegend
:layer-ids="['choropleth-layer']"
type="gradient"
:items="gradientItems"
title="Population Density"
/>
</VMap>
</template>
<script setup lang="ts">
import { VMap, VControlLegend } from '@geoql/v-maplibre';
const sizeItems = [
{ value: 10, label: 'Small (< 1000)', size: 8 },
{ value: 50, label: 'Medium (1000-5000)', size: 16 },
{ value: 100, label: 'Large (> 5000)', size: 24 },
];
</script>
<template>
<VMap :options="mapOptions" style="height: 500px">
<VControlLegend
:layer-ids="['points-layer']"
type="size"
:items="sizeItems"
title="City Population"
/>
</VMap>
</template>
string[]trueArray of layer IDs to filter when legend items are toggled.
'category' | 'gradient' | 'size'false'category'The legend type to render.
LegendItem[]falseExplicit legend items. If not provided and autoGenerate is true, items are generated from paint properties.
'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'false'top-right'Position of the legend control on the map.
stringfalseMapLibre paint property to read for auto-generation and filtering (e.g., 'fill-color').
booleanfalsefalseAuto-generate legend items from MapLibre paint expressions. Supports match, step, and interpolate expressions.
stringfalse'Legend'Title displayed in the legend header.
booleanfalsefalseStart with the legend collapsed.
booleanfalsetrueEnable click-to-filter functionality for category legends.
Emitted when a legend item is clicked.
interface ItemClickEvent {
item: LegendItem;
index: number;
visible: boolean;
}
Emitted when the filter state changes.
interface FilterChangeEvent {
filter: FilterState;
layerIds: string[];
}
Emitted for v-model:filter binding.
<script setup lang="ts">
import { ref } from 'vue';
import type { FilterState } from '@geoql/v-maplibre';
const filterState = ref<FilterState>({
visibleValues: ['residential', 'commercial', 'industrial'],
});
</script>
<template>
<VControlLegend
:layer-ids="['buildings-layer']"
type="category"
:items="legendItems"
v-model:filter="filterState"
/>
<p>Visible categories: {{ filterState.visibleValues.join(', ') }}</p>
</template>
The legend can auto-generate items from MapLibre paint expressions:
<template>
<!-- Layer with fill-color: ['match', ['get', 'type'], 'A', '#ff0000', 'B', '#00ff00', '#cccccc'] -->
<VControlLegend
:layer-ids="['my-layer']"
type="category"
:auto-generate="true"
property="fill-color"
/>
</template>
<template>
<!-- Layer with fill-color: ['interpolate', ['linear'], ['get', 'value'], 0, '#0000ff', 100, '#ff0000'] -->
<VControlLegend
:layer-ids="['choropleth']"
type="gradient"
:auto-generate="true"
property="fill-color"
/>
</template>
When a category is toggled off, the control applies a filter to hide matching features:
map.setFilter(layerId, ['in', ['get', 'property'], ['literal', visibleValues]]);
deck.gl layer filtering requires DataFilterExtension to be pre-configured:
<script setup lang="ts">
import { DataFilterExtension } from '@deck.gl/extensions';
const layer = new ScatterplotLayer({
id: 'scatter',
data: points,
extensions: [new DataFilterExtension()],
getFilterValue: (d) => categoryToIndex(d.category),
filterRange: [0, Infinity],
});
</script>
If a deck.gl layer doesn't have DataFilterExtension, a warning is logged.
import type {
LegendType,
LegendItem,
CategoryLegendItem,
GradientLegendItem,
SizeLegendItem,
FilterState,
LegendControlOptions,
} from '@geoql/v-maplibre';
// Category item
const categoryItem: CategoryLegendItem = {
value: 'residential',
label: 'Residential',
color: '#4CAF50',
visible: true,
count: 150, // Optional feature count
};
// Gradient item
const gradientItem: GradientLegendItem = {
min: 0,
max: 100,
colors: ['#0000ff', '#ff0000'],
minLabel: 'Low',
maxLabel: 'High',
stops: [0, 25, 50, 75, 100], // Optional color stops
};
// Size item
const sizeItem: SizeLegendItem = {
value: 50,
label: 'Medium',
size: 16,
};
// Filter state
const filterState: FilterState = {
visibleValues: ['residential', 'commercial'],
minRange: 0, // For gradient filtering
maxRange: 100,
};