JavaScript Polar Map Example

Demonstrates how to create a Polar Map Example using our PolarTriangleRenderableSeries class, along with a triangulation alorithm.

Fullscreen

Edit

 Edit

Docs

src/drawExample.ts
import {
    NumberRange,
    XyDataSeries,
    ETriangleSeriesDrawMode,
    PolarPanModifier,
    PolarZoomExtentsModifier,
    PolarMouseWheelZoomModifier,
    PolarTriangleRenderableSeries,
    PolarNumericAxis,
    EPolarAxisMode,
    EAxisAlignment,
    SciChartPolarSurface,
} from "scichart";

import constrainedDelaunayTriangulation from "./constrainedDelaunayTriangulation";
import { appTheme } from "./appTheme";

export const drawExample = async (rootElement: string | HTMLDivElement) => {
    // Create a SciChartSurface
    const { wasmContext, sciChartSurface } = await SciChartPolarSurface.create(rootElement);

    sciChartSurface.debugRendering = false;

    let showFromSouthPole = false;

    const setView = (positionFromSPole: boolean) => {
        showFromSouthPole = positionFromSPole;

        sciChartSurface.xAxes.clear();
        sciChartSurface.yAxes.clear();
        sciChartSurface.renderableSeries.clear(true);

        const xAxis = new PolarNumericAxis(wasmContext, {
            polarAxisMode: EPolarAxisMode.Angular,
            visibleRange: new NumberRange(-180, 180), // longitude
            flippedCoordinates: showFromSouthPole ? true : false,
            axisAlignment: EAxisAlignment.Bottom,
            useNativeText: true,
            drawMajorBands: false,
            drawMajorGridLines: false,
            drawMajorTickLines: false,
            drawMinorGridLines: false,
            drawMinorTickLines: false,
            labelPrecision: 0,
        });
        sciChartSurface.xAxes.add(xAxis);

        const yAxis = new PolarNumericAxis(wasmContext, {
            polarAxisMode: EPolarAxisMode.Radial,
            visibleRange: new NumberRange(-90, 90), // latitude
            axisAlignment: EAxisAlignment.Left,
            flippedCoordinates: showFromSouthPole ? false : true,
            useNativeText: true,
            drawMajorBands: false,
            drawMajorGridLines: false,
            drawMajorTickLines: false,
            drawMinorGridLines: false,
            drawMinorTickLines: false,
            labelPrecision: 0,
        });
        sciChartSurface.yAxes.add(yAxis);
    };

    setView(true);

    const [min, max] = [140, 1379302771]; // population min max

    function interpolateColor(min: number, max: number, value: number) {
        // Clamp value between min and max
        value = Math.max(min, Math.min(max, value));
        // Normalize to [0,1]
        let t = (value - min) / (max - min);

        // Parse hex colors to RGB
        function hexToRgb(hex: string) {
            hex = hex.replace(/^#/, "");
            if (hex.length === 3)
                hex = hex
                    .split("")
                    .map((x) => x + x)
                    .join("");
            const num = parseInt(hex, 16);
            return [num >> 16, (num >> 8) & 255, num & 255];
        }

        // Interpolate between two RGB colors
        function lerp(a: number, b: number, t: number) {
            return Math.round(a + (b - a) * t);
        }

        // The low end of the scale has to contrast with the chart background. White reads well on the
        // dark themes but vanishes into the light one, so start from a pale blue there instead.
        const colorA = hexToRgb(appTheme.isDark ? "#ffffff" : "#c3cfe8");
        const colorB = hexToRgb("#1e3489");
        const r = lerp(colorA[0], colorB[0], t);
        const g = lerp(colorA[1], colorB[1], t);
        const b = lerp(colorA[2], colorB[2], t);

        // Convert back to hex
        return "#" + [r, g, b].map((x) => x.toString(16).padStart(2, "0")).join("");
    }

    let dataArray: { name: string; gdp: number; population: number; areaData: number[][] }[] = [];

    const setMapJson = (mapData: { features: any[] }) => {
        dataArray = [];

        mapData?.features.forEach((state, i) => {
            if (state.geometry.type === "Polygon") {
                let area = state.geometry.coordinates[0];
                area.pop();
                let areaData = [].concat(...constrainedDelaunayTriangulation(area));

                dataArray.push({
                    name: state.properties.NAME,
                    gdp: +state.properties.GDP_MD_EST,
                    population: +state.properties.POP_EST,
                    areaData,
                });
            } else {
                let polyArea = state.geometry.coordinates;

                if (state.properties.SOVEREIGNT === "Antarctica" && showFromSouthPole === false) {
                    return;
                }

                polyArea.forEach((a: any[]) => {
                    let area = a[0];

                    area.pop();
                    let areaData = [].concat(...constrainedDelaunayTriangulation(area));

                    dataArray.push({
                        name: state.properties.NAME,
                        gdp: +state.properties.GDP_MD_EST,
                        population: +state.properties.POP_EST,
                        areaData,
                    });
                });
            }
        });
    };

    const setMap = () => {
        sciChartSurface.renderableSeries.clear(true);

        const series = dataArray.map((d, i) => {
            const dataSeries = new XyDataSeries(wasmContext, {
                xValues: d.areaData.map((p) => p[0]),
                yValues: d.areaData.map((p) => p[1]),
            });

            const triangleSeries = new PolarTriangleRenderableSeries(wasmContext, {
                dataSeries: dataSeries,
                drawMode: ETriangleSeriesDrawMode.List,
                fill: interpolateColor(min, max, d.population),
                opacity: 0.9,
            });

            return triangleSeries;
        });

        sciChartSurface.renderableSeries.add(...series);
    };

    sciChartSurface.chartModifiers.add(new PolarPanModifier());
    sciChartSurface.chartModifiers.add(new PolarZoomExtentsModifier());
    sciChartSurface.chartModifiers.add(new PolarMouseWheelZoomModifier());
    return { sciChartSurface, wasmContext, setMapJson, setMap, setView };
};

Polar Map Example - JavaScript

Overview

This example demonstrates how to create a polar map visualization using SciChart.js in JavaScript. The implementation renders geographic data as a series of colored triangles on a polar coordinate system, with options to view from either the North or South pole.

Technical Implementation

The chart uses a PolarNumericAxis for angular (longitude) and radial (latitude) coordinates. Geographic polygons are converted to triangles using a constrained Delaunay triangulation algorithm. Each triangle's color represents population density, calculated via an interpolation function between white and blue.

Features and Capabilities

The visualization includes interactive modifiers like PolarPanModifier and PolarZoomExtentsModifier. The flippedCoordinates property enables switching between North and South pole views dynamically.

Integration and Best Practices

The example follows best practices for WebAssembly initialization and surface disposal. Geographic data is loaded asynchronously from a JSON file, with proper error handling.