Skip to main content

Choropleth Maps

Choropleth Maps visualize geographic regions colored by a data metric — population, density, area, or any continuous value — making spatial patterns immediately visible. SciChart.js has no dedicated map series; instead, choropleth maps are composed from FastTriangleRenderableSeries📘 for filled regions, FastLineRenderableSeries📘 for boundary outlines, and FastBubbleRenderableSeries📘 for point-of-interest markers.

tip

The JavaScript Choropleth Map Example can be found in the SciChart.JS Examples Suite on GitHub, or in the live demo at scichart.com/demo.

Above: The JavaScript Choropleth Map example from the SciChart.js Demo

Core Building Blocks

A choropleth map in SciChart.js is assembled from standard chart primitives:

From GeoJSON to Triangle Vertices

FastTriangleRenderableSeries📘 renders triangles, not arbitrary polygons. Geographic region boundaries (typically sourced from GeoJSON) must be converted to a flat list of triangles before they can be passed to XyDataSeries📘.

The standard approach is constrained Delaunay triangulation, which fills the interior of any polygon with non-overlapping triangles that respect the boundary edges:

  1. Load boundary outlines from GeoJSON (array of [longitude, latitude] coordinate pairs per region)
  2. Triangulate each polygon outline using a library such as poly2tri or a custom Bowyer-Watson / sweep-line implementation
  3. Flatten the resulting triangle list: each triangle produces three [x, y] pairs appended in sequence
  4. Store the flat xValues / yValues arrays in XyDataSeries📘 and pass to FastTriangleRenderableSeries📘 with drawMode: ETriangleSeriesDrawMode.List

Pre-computing triangulation offline and bundling the result as JSON (e.g. australiaConverted.json) is strongly recommended — triangulation can be expensive at runtime for regions with many vertices. Each region object in the bundle typically has the shape:

type RegionData = {
name: string; // region identifier
outline: number[][]; // boundary coordinate pairs for the outline series
areaData: number[][] // flat triangle vertices for the fill series
};

Color Mapping

Each region gets its own FastTriangleRenderableSeries📘 instance, and the fill property drives the choropleth coloring. A helper function linearly interpolates between two colors based on where a region's value falls in the dataset range:

function interpolateColor(
value: number,
min: number,
max: number,
colorLow: string,
colorHigh: string
): string {
const t = (value - min) / (max - min);
// parse RGB components of colorLow and colorHigh, blend by t
// return a CSS color string
}

// Per-region usage:
triangleSeries.fill = interpolateColor(region.population, minPop, maxPop, "#1a237e", "#e53935");
triangleSeries.opacity = 0.9;

To switch between metrics (e.g. population vs. area vs. density), recalculate the min/max for the new metric, iterate over all series, and update fill on each.

Boundary Outlines

A separate FastLineRenderableSeries📘 per region renders the boundary outline on top of the filled triangles. The outline xValues/yValues come directly from the region's outline coordinate array — no triangulation needed:

import { FastLineRenderableSeries, XyDataSeries } from "scichart";

const outlineSeries = new FastLineRenderableSeries(wasmContext, {
dataSeries: new XyDataSeries(wasmContext, {
xValues: region.outline.map(p => p[0]),
yValues: region.outline.map(p => p[1]),
}),
stroke: "white",
strokeThickness: 1,
});
sciChartSurface.renderableSeries.add(outlineSeries);

City Markers

FastBubbleRenderableSeries📘 with an EllipsePointMarker📘 places labelled city dots over the map. City names are rendered via the dataLabels API using DataLabelProvider📘:

import {
FastBubbleRenderableSeries,
XyzDataSeries,
EllipsePointMarker,
EHorizontalAnchorPoint,
EVerticalAnchorPoint,
} from "scichart";

const citySeries = new FastBubbleRenderableSeries(wasmContext, {
dataSeries: new XyzDataSeries(wasmContext, {
xValues: cities.map(c => c.lon),
yValues: cities.map(c => c.lat),
zValues: cities.map(() => 1), // uniform size
metadata: cities.map(c => ({ isSelected: false, label: c.name })),
}),
pointMarker: new EllipsePointMarker(wasmContext, {
width: 12,
height: 12,
fill: "white",
stroke: "white",
}),
dataLabels: {
style: { fontFamily: "Arial", fontSize: 12, color: "white" },
horizontalAnchorPoint: EHorizontalAnchorPoint.Left,
verticalAnchorPoint: EVerticalAnchorPoint.Top,
},
});
sciChartSurface.renderableSeries.add(citySeries);

Chart Setup

The axes use isVisible: false since geographic coordinates need no tick marks, and growBy adds margin so regions are not clipped at the chart edge:

import {
SciChartSurface,
NumericAxis,
NumberRange,
ZoomPanModifier,
ZoomExtentsModifier,
MouseWheelZoomModifier,
} from "scichart";

const { wasmContext, sciChartSurface } = await SciChartSurface.create(divElementId);

const growBy = new NumberRange(0.05, 0.05);
sciChartSurface.xAxes.add(new NumericAxis(wasmContext, { isVisible: false, growBy }));
sciChartSurface.yAxes.add(new NumericAxis(wasmContext, { isVisible: false, growBy }));

sciChartSurface.chartModifiers.add(
new ZoomPanModifier(),
new ZoomExtentsModifier(),
new MouseWheelZoomModifier()
);

Preserving Aspect Ratio

Geographic data uses longitude/latitude, which only looks correct at a 1:1 scale ratio. To keep the map undistorted, adjust the visible range of one axis whenever the chart container resizes:

sciChartSurface.addDirtied(() => {
const { width, height } = sciChartSurface.domCanvas2D;
const xRange = sciChartSurface.xAxes.get(0).visibleRange;
const xSpan = xRange.max - xRange.min;
const ySpan = xSpan * (height / width);
const yCentre = (sciChartSurface.yAxes.get(0).visibleRange.min +
sciChartSurface.yAxes.get(0).visibleRange.max) / 2;
sciChartSurface.yAxes.get(0).visibleRange = new NumberRange(
yCentre - ySpan / 2,
yCentre + ySpan / 2
);
});

Series Layering

Render order determines which series appear on top. Add series to sciChartSurface.renderableSeries in this order so fills, outlines, and markers stack correctly:

LayerSeries typePurpose
1 (bottom)FastTriangleRenderableSeries × NFilled region polygons
2FastLineRenderableSeries × NRegion boundary outlines
3 (top)FastBubbleRenderableSeriesCity / POI markers

See Also