Blazor-ApexMaps
0.4.0
dotnet add package Blazor-ApexMaps --version 0.4.0
NuGet\Install-Package Blazor-ApexMaps -Version 0.4.0
<PackageReference Include="Blazor-ApexMaps" Version="0.4.0" />
<PackageVersion Include="Blazor-ApexMaps" Version="0.4.0" />
<PackageReference Include="Blazor-ApexMaps" />
paket add Blazor-ApexMaps --version 0.4.0
#r "nuget: Blazor-ApexMaps, 0.4.0"
#:package Blazor-ApexMaps@0.4.0
#addin nuget:?package=Blazor-ApexMaps&version=0.4.0
#tool nuget:?package=Blazor-ApexMaps&version=0.4.0
Blazor-ApexMaps
A Blazor wrapper for ApexMaps: interactive geographic data visualization, with the whole option surface typed in C#.
<ApexMaps Options="_options" Height="440" />
@code {
private readonly MapsOptions _options = new()
{
Geo = new GeoOptions { Map = GeoMaps.WorldCountries110m },
Series =
[
new ChoroplethSeries
{
Name = "Unemployment rate",
JoinBy = JoinSpec.Between("iso_a3", "code"),
Data = [new { code = "FRA", value = 7.3 }, new { code = "DEU", value = 5.7 }],
},
],
};
}
That is the whole example. No geometry to find, host or parse, no script tag, no CDN reference, no stylesheet link, and no projection, palette, classification, legend, label or tooltip configuration: the defaults are meant to be publishable.
Live demos - one page per feature.
Versioning.
0.4.0bundlesapexmaps@1.3.0. Up to0.3.0this package matched the core's version; the core is now 1.x with a semver promise, while this wrapper keeps its own 0.x line until its option surface settles. The engine, the six series, the registry and everything in the table below are working and tested. Each release names the apexmaps it bundles.
What you get
| Area | Detail |
|---|---|
| Series | ChoroplethSeries, BubbleSeries (proportional symbols), MarkerSeries (seven shapes, categorical colour, clustering †), ArcSeries (great circles with travelling flow beads) †, LineSeries (routes through given vertices) †, HexbinSeries (points binned into a hexagonal lattice) †, plus an automatic basemap when no feature series is present |
| Geometry | 26 built-in packs named through GeoMaps: world countries and coastline, US states and all 3,231 counties, EU NUTS 0-3, and admin-1 for 15 more countries. Lazy, one request per pack, provenance and attribution attached |
| Projections | 13 projections with aliases through Projections, spec objects with rotation, centre, standard parallels and clip angle, and a globe that spins under the drag |
| Data | Your own models, records, anonymous types or dictionaries. Join-key auto-detection, mismatch diagnostics with suggestions through DiagnoseJoinAsync, opt-in fuzzy matching |
| Scales | Quantile, equal interval, Jenks, threshold, linear, log, sqrt, ordinal; 17 palettes sampled in OkLab; square-root size scales with nested-circle legends |
| Fills | Flat colour, eight pattern tiles with automatic ink contrast †, or an image per region clipped to its own outline † |
| Interaction | Anchored wheel zoom, inertial pan, pinch, on-screen controls, globe dragging, hover states, click and box selection, cross-map linked selection †, legend class muting, drilldown with a breadcrumb † |
| Camera | FlyToAsync, EaseToAsync, JumpToAsync, FrameFeatureAsync, ResetViewAsync, RotateToAsync, all interruptible; on a globe a move to a place turns the sphere |
| Components | Classed, gradient and nested-circle legends with a hover marker, HTML tooltips, collision-avoiding labels, editorial annotations † |
| Events | All 17 core events as EventCallback parameters, with typed payloads |
| Export | ExportPngAsync, ExportSvgAsync, GetDataUriAsync, GetSvgStringAsync, and GetSpecAsync for the resolved options tree |
| Accessibility | ARIA roles, an auto-generated description, roving-tabindex keyboard navigation, an optional data table, prefers-reduced-motion. On by default and free in every tier |
| Platform | .NET 9, nullable reference types, XML docs on every option, Blazor WebAssembly and Blazor Server |
† Licensed feature. It works without a key so you can evaluate it, with a watermark on the map. See Licensing.
Installation
dotnet add package Blazor-ApexMaps
// Program.cs
using Blazor_ApexMaps.Extensions;
builder.Services.AddApexMaps();
@* _Imports.razor *@
@using Blazor_ApexMaps.Components
@using Blazor_ApexMaps.Models
That is all the setup there is. The package carries the apexmaps core as an ES module and imports it
itself, and the core injects its own stylesheet, so there is nothing to add to index.html or
App.razor.
Geometry
Finding, converting and hosting boundaries is the tax on every map project, and it is paid before any chart is drawn, so the geometry is part of the product:
Geo = new GeoOptions { Map = GeoMaps.WorldCountries110m } // canonical id
Geo = new GeoOptions { Map = GeoMaps.WorldCountries } // detail-free: the lightest one
Geo = new GeoOptions { Map = GeoMaps.Us } // states, with albersUsa
Geo = new GeoOptions { Map = GeoMaps.JapanPrefectures } // the country's own word for its tier
Geo = new GeoOptions { Map = GeoMaps.EuNuts2 } // Eurostat regions
Geo = new GeoOptions { Map = "https://example.com/districts.json" } // your own URL
Geo = new GeoOptions { Map = MapSource.FromGeometry(myGeoJsonObject) } // geometry in hand
Nothing is fetched until a pack is named, and one pack is one request however many maps on the page ask for it. Each pack carries a recommended join key, a recommended projection where the generic default would be wrong, and its provenance.
Packs come from the apexmaps-geo package on jsDelivr by default, so nothing needs installing. For
an offline or air-gapped app, publish that package's files under wwwroot and point the library at
them:
builder.Services.AddApexMaps(options =>
{
options.GeoSource = "/apexmaps-geo/";
});
ApexMapsRuntimeService reaches the rest of the registry at runtime: RegisterMapAsync (any
geometry under an id of your own, including a floor plan drawn against the identity projection),
RegisterPaletteAsync, ListMapsAsync, ListProjectionsAsync, ListPalettesAsync and
MapMetaAsync for a pack's source, licence, attribution and vintage.
The component
<ApexMaps @ref="_map"
Options="_options"
Series="_series"
Map="@GeoMaps.UsStates"
Height="480"
Width="@("100%")"
Class="my-map"
AutoUpdate="true"
OnFeatureClick="OnFeatureClick"
OnSelectionChange="OnSelectionChange" />
Options is the whole tree; Series, Map, Width and Height are shorthands for the parts of
it that change most often. With AutoUpdate on (the default), a parameter change is compared
against what was last sent to the browser and applied the cheap way where it can be: a change
confined to the series tweens fills and radii rather than rebuilding the DOM, and a changed map or
projection reprojects. A parent that rebuilds its options object on every render is not a redraw.
Methods
Take a @ref and drive the map directly:
await _map.UpdateSeriesAsync(nextSeries); // tweens
await _map.UpdateOptionsAsync(new MapsOptions { Geo = new GeoOptions { Projection = Projections.Mercator } });
await _map.FlyToAsync(new CameraTarget { Center = new LonLat(2.35, 48.85), Zoom = 8 });
await _map.FrameFeatureAsync("FRA", new FrameOptions { Padding = 40 });
await _map.ResetViewAsync();
await _map.ZoomInAsync(); await _map.ZoomOutAsync();
await _map.RotateToAsync(-25, -18); // turns a globe
await _map.DrillToAsync("CA"); await _map.DrillUpAsync(int.MaxValue);
await _map.SetSelectionAsync("FRA", "DEU"); await _map.ClearSelectionAsync();
var diagnostics = await _map.DiagnoseJoinAsync();
var spec = await _map.GetSpecAsync(); // resolved options, as JSON
await _map.ExportPngAsync(new MapExportOptions { Scale = 2 });
var uri = await _map.GetDataUriAsync();
Events
All 17, as EventCallback parameters: OnRendered, OnUpdated, OnResized, OnFeatureClick,
OnFeatureHover, OnFeatureFocus, OnMarkClick, OnMarkHover, OnClusterClick, OnDrilldown,
OnDrillup, OnSelectionChange, OnLegendToggle, OnZoom, OnPanEnd, OnRotate, OnRotateEnd.
private void OnFeatureClick(FeatureEventArgs e)
{
_selected = e.Key; // join key
_label = e.Name; // display name
_value = e.Value; // null where the feature had no data
var row = e.Read<CountryRow>(); // the data row, as your own model
var iso = e.Property("iso_a3"); // a geometry property
}
Formatters and accessors
The options that take a function in the core take one here too, as JavaScript source through
JsFunction. They run per mark, per frame, or during projection, so a round trip to .NET for each
of a few thousand features would be a stutter rather than a chart: the source travels as a string
and is compiled once, in the browser.
Tooltip = new TooltipOptions
{
Formatter = JsFunction.From("ctx => `<b>${ctx.name}</b>: ${ctx.value ?? 'no data'}`"),
},
Fill = new SeriesFillOptions
{
Pattern = JsFunction.From("ctx => ctx.value > 55 ? ({ type: 'crosshatch' }) : null"),
},
A JsFunction is compiled with new Function, so it runs in the global scope: it can see window
and anything your app put there, and it cannot see your C# variables. It is evaluated as code, so
never build one from untrusted input.
Anything this wrapper does not model yet goes through AdditionalOptions, on MapsOptions and on
every series, which is merged in with its keys written exactly as given.
Ignite UI theming
From Blazor-ApexMaps 0.4.0, which bundles apexmaps 1.3.0, maps can follow the Ignite UI theme on the page: the series colours, text, borders, focus ring, tooltip surface, font, the basemap and no-data greys, outlines, and light or dark. It is an add-on, it is free, and it needs no licence key. Turn it on where you register the services:
builder.Services.AddApexMaps(options =>
{
options.IgniteUI = true;
});
The component then loads _content/Blazor-ApexMaps/js/apexmaps.ignite-ui.esm.js before the first
map renders. To load it yourself instead, add the script-tag build to index.html (or App.razor):
<script src="_content/Blazor-ApexMaps/js/apexmaps.ignite-ui.min.js"></script>
The theme ranks below everything you set:
| Rank | Source |
|---|---|
| 1 | An option you pass in MapsOptions |
| 2 | An --apexmaps-* CSS variable |
| 3 | An --apx-* family token |
| 4 | Theme.Palette |
| 5 | The Ignite UI theme (--ig-*), with the add-on loaded |
| 6 | The built-in defaults |
With Theme.Mode unset, a dark Ignite UI theme turns the map dark; a mode you pass wins.
Theme = new ThemeOptions { DesignSystem = false } turns the layer off for one map, and
Tokens = false turns it off together with the --apx-* tokens. Without the add-on the --ig-*
tokens are ignored, with no error.
The map reads the theme when it renders. After a stylesheet swap (an Ignite UI light/dark switch,
say), call await _map.UpdateOptionsAsync(new MapsOptions()) to apply it at once.
The font comes from --ig-font-family, which is usually a web font, and the Ignite UI theme sheets
do not load it. Load the font on the page; if it finishes loading after the first render, the same
UpdateOptionsAsync call measures the labels with it.
Licensing
Dual licensed on the same terms as the rest of the family: a free Community License for individuals, non-profits, educators and organizations under $2M USD in annual revenue, budget or funding, and a paid Commercial or OEM license at or above that. One key works across every Apex product, so an ApexCharts or ApexGrid customer does not buy a second one for maps. See LICENSE.
The line is that a map that answers a question is free, and a map that becomes an application is licensed.
| Free, always | Licensed |
|---|---|
| Choropleth, bubble and marker series, and the automatic basemap | Point clustering (Cluster) |
| Every one of the 13 built-in projections, with spec objects | Projections you register yourself |
| The geometry registry, all 26 packs, provenance and attribution | Drilldown and the breadcrumb (Drilldown) |
| Tooltips, legends, labels, data labels, states and themes | Editorial annotations (Annotations) |
| Zoom, pan, pinch, hover, click and box selection, the camera API | Arc and line route series |
| Joins, fuzzy matching, and the join diagnostics | Linked selection across maps (Link) |
| Scales, palettes, size legends, responsive rules | Story mode (ChartContext.Story) |
| Flat fills, in every scale and palette | Pattern fills and image fills |
| PNG and SVG export | Hexbin series (HexbinSeries) |
| The accessibility layer | Hex tile maps (GeoOptions.Layout) |
Without a valid key the licensed features still work, in full, with a watermark on the map, so you can evaluate them in your own app with your own data. A valid key removes the watermark, without a reload. A map that uses none of them renders clean, with or without a key.
builder.Services.AddApexMaps(options =>
{
options.LicenseKey = "APEX-xxxxxxxx";
});
Keys are per product, not per page: AddApexMaps and AddApexCharts set different copies of the
licence manager, because each library bundles its own. An app with a chart and a map configures
both, with the same key. Get a key at apexcharts.com/pricing.
The software licence does not cover the geographic data, which is published by third parties
under its own terms: Natural Earth and US Census TIGER/Line are public domain, Eurostat GISCO NUTS
is CC BY 4.0 and is credited on screen automatically. MapMetaAsync(id) says which is which.
Requirements
- .NET 9.0 or later
- Blazor WebAssembly or Blazor Server
- A browser with ES module support (every current one)
The demo app
git clone https://github.com/apexcharts/Blazor-ApexMaps.git
cd Blazor-ApexMaps
dotnet run --project src/Blazor-ApexMaps.Sample
One page per feature, so each loads on its own and cannot be broken by an unrelated one: choropleth, scales and palettes, projections and the globe, bubbles and markers, arcs and routes, drilldown, selection and linking, camera and export, theming and annotations, join diagnostics, and every event as it happens.
Development
dotnet build Blazor-ApexMaps.sln
dotnet pack src/Blazor-ApexMaps/Blazor-ApexMaps.csproj -c Release
# End-to-end: drive the sample in a real browser and fail on a page that draws nothing
dotnet run --project src/Blazor-ApexMaps.Sample --urls http://localhost:5186 &
cd tests/e2e && npm install && npx playwright install --with-deps chromium && npm test
The e2e smoke test is the one that matters for a wrapper: the whole contract lives in the interop layer and the core, so a mis-serialized option, a broken import path, an uncompilable formatter or a moved pack id would only surface at runtime. It loads every demo page in headless Chromium and fails on a console error, a visible Blazor error UI, a map that drew zero marks, an event that never reached .NET, or an export that produced nothing.
Project structure
src/Blazor-ApexMaps/
Components/ApexMaps.razor the component: parameters, methods, event bridge
Models/ the option tree, series, data rows, events, enums
Interop/ApexMapsInterop.cs the .NET side of the bridge
Serialization/ camelCase JSON, and JsFunction
Services/ApexMapsRuntimeService licence, geometry source, the registry
wwwroot/js/blazor-apexmaps.js the JS side of the bridge
wwwroot/js/apexmaps.esm.js the vendored apexmaps core
src/Blazor-ApexMaps.Sample/ the demo app, one page per feature
tests/e2e/ headless browser smoke and interaction test
Related
- ApexMaps - the core library, with
react-apexmaps,vue-apexmapsandngx-apexmapsalongside it - Blazor-ApexTree, Blazor-ApexSankey, Blazor-ApexGantt - the rest of the family
- ApexCharts
Support
- Issues: github.com/apexcharts/Blazor-ApexMaps/issues
- Licensing: apexcharts.com/pricing
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net9.0
- Microsoft.AspNetCore.Components.Web (>= 9.0.10)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
0.4.0 Bundles apexmaps 1.3.0. Adds the hexbin series (HexbinSeries, HexbinDatum), which bins
points into a hexagonal lattice sized in screen pixels and colours each cell by count, sum,
mean, min or max. Adds GeoOptions.Layout, which draws a region set as a hex tile map instead
of its real boundaries and morphs between the two when it is toggled. Both are licensed
features: they work without a key for evaluation, with a watermark. Adds opt-in Ignite UI
theming (ApexMapsConfiguration.IgniteUI, ThemeOptions.DesignSystem and Tokens); it is off
unless you turn it on. Behaviour changes from the core: --apx-series-N and --apx-font-family
tokens on the page now apply to maps, and colours in any CSS syntax (names, hsl(), oklch(),
var()) now paint in scales and fills instead of every feature taking the first stop.
0.3.0 Initial release. Wraps apexmaps 0.3.0: choropleth, bubble, marker, arc and line series;
13 projections with spec objects; the built-in geometry registry (26 packs, fetched on demand);
scales, palettes, pattern and image fills; legend, tooltip, data labels and editorial annotations;
zoom, pan, globe rotation, box selection, linked selection and drilldown; the camera API
(flyTo/easeTo/jumpTo/fitBounds/frameFeature/resetView); PNG and SVG export; all 17 core events.