Scatter & lines on maps
Overview
A scattergeo trace places points on a map by longitude and latitude, or by the name of a place (a country code, a country name, a US state, a feature of your own GeoJSON). It is the map twin of scatter: markers, lines, text and filled areas, with the same mode, marker, line and text attributes. Use it for point data with a location (cities, stations, events), for bubble maps, and for routes: lines between two points follow the great circle, the shortest path on the globe.
Traces are drawn on a geo subplot, layout.geo (and geo2, geo3, … for more), which holds the projection, the base map and the view. Maps describes it.
Maps are not part of the full bundle. Add one import next to it, or register tracesGeo in a partial bundle (see adding the package):
import '@mk7s/holochart';
import '@mk7s/holochart/geo';Pick a different chart when:
- the value belongs to a region, not to a point: use a choropleth, which fills the region;
- the positions are not geographic, or the area is small enough that the curvature of the earth does not matter and you have your own background: a scatter on cartesian axes with
scaleanchoris simpler; - you need streets, terrain or satellite imagery under the points: that takes a tile map, which Holochart does not have yet (it is planned).
Minimal example
import { createChart } from '@mk7s/holochart';
import '@mk7s/holochart/geo';
createChart(document.getElementById('chart')!, {
data: [{ type: 'scattergeo', lon: [-0.13, 2.35, 13.4], lat: [51.51, 48.86, 52.52] }],
});lon is degrees east and lat degrees north. The trace goes on layout.geo, which is created for it: a world map in the equirectangular projection with coastlines. By default the view is fitted to the points (geo.fitbounds: 'locations'), so this figure shows western Europe; set fitbounds: false to see the whole world, as the live example does. The default mode is 'markers' whatever the number of points.
scattergeo/basicData format
lonandlat. Arrays (plain or typed) of the same length; with different lengths the shorter one wins. A point whose longitude or latitude is not a number is a gap: it draws nothing and breaks the line there, unlessconnectgaps: true.locations. Instead of coordinates, a place per point.locationmodesays what the entries name, and each point is drawn at the label point of its feature. When a trace has bothlocationsandlon/lat, the locations win.locationmodeA location is Matched against 'ISO-3'(default)an ISO 3166-1 alpha-3 code ( 'FRA'), in any casethe countries of the base map 'USA-states'a postal code ( 'CA') or a state's namethe states of the base map 'country names'a country's name, or an ISO code the countries of the base map 'geojson-id'a string or a number (the default with a geojson)the features of the trace's geojsonThe countries are those of the subplot's
scopeat itsresolution: the 110m data leaves out the smallest countries (Malta, Singapore, …), whichresolution: 50has, and a scope other thanworldhas the countries of its continent only. The table of country names is a chunk of its own, loaded the first time a figure uses'country names'.geojsonandfeatureidkey. AFeatureCollectionor aFeaturewithPolygonorMultiPolygongeometries, as an object or as a URL.featureidkeyis where each feature has the id thatlocationsname ('id'by default, or a property such as'properties.name'). A point is drawn at the mean of the vertices of its feature's largest polygon.Unmatched locations. A location that names no feature is skipped, and one console warning per trace lists the unmatched ones.
Points the projection hides. A point on the far side of an orthographic globe, or outside the three parts of Albers USA, is not drawn and cannot be hovered or selected. Lines are cut at the edge of the projection.
import { createChart } from '@mk7s/holochart';
import '@mk7s/holochart/geo';
// Markers at US states, named by postal code, on the map of the United States.
createChart(document.getElementById('chart')!, {
data: [
{
type: 'scattergeo',
locationmode: 'USA-states',
locations: ['CA', 'TX', 'NY'],
text: ['California', 'Texas', 'New York'],
marker: { size: [20, 16, 12] },
},
],
layout: { geo: { scope: 'usa' } },
});Variations
Great-circle routes on a globe
With mode: 'lines', each segment follows the great circle between its two points. On an orthographic projection that is the straightest line on the globe; segments that pass behind it are cut at the edge. A NaN after each destination breaks one trace into separate routes. Drag the globe to turn it:
scattergeo/great-circlesRoutes on a flat map
On a flat projection great circles are curves. mode: 'lines+markers' marks the stops, and one trace per route gives each a color and a legend entry. A line that leaves the map at one side is cut there and goes on at the other, as the polar route does here; projection.rotation.lon chooses the meridian in the middle, and so where the cut is:
scattergeo/routesBubble map
marker.size takes a number per point, and sizemode: 'area' with sizeref makes the area of a bubble follow its value. Numbers in marker.color go through a colorscale; marker.showscale adds the colorbar:
scattergeo/bubblesText labels on a scoped map
mode: 'markers+text' writes text beside each marker, placed by textposition (one value, or one per point to keep neighbours apart). scope: 'europe' draws one continent in the scope's own projection, with country borders:
scattergeo/europeThe United States
scope: 'usa' is the Albers USA projection: the lower 48 states with Alaska and Hawaii as insets, and state borders as subunits:
scattergeo/usaMarkers at named places
With locations, a point needs no coordinates: here ISO-3 codes, each drawn at the label point of its country. %{location} is available in hovertemplate and texttemplate:
scattergeo/locationsFilled areas
fill: 'toself' closes each run of the line between gaps into a shape on the sphere and fills it. The edges are great circles, and of the two regions a closed path bounds, the smaller one is filled:
scattergeo/fillFrom a table, with Express
hx.scatterGeo and hx.lineGeo build these traces from rows: one trace per value of color, marker areas from size.
Styling
- Markers, lines and text. The same attributes as scatter:
marker(symbol,size,sizemode,sizeref,sizemin,colorwith colorscales,coloraxisand a colorbar,line,opacity),line(color,width,dash),text,texttemplate,textpositionandtextfont. See the scatter page. Lines have noshape: a segment is always a great circle. - Fills.
fillis'none'or'toself'.fillcolordefaults to the line color, half transparent. The line of a filled trace is closed. - Selection styles.
selectedandunselected(marker.color,marker.size,marker.opacity,textfont.color), as on scatter. - The map. The base layers (
showland,showocean,showcountries,showlakes, …, each with its color), the graticule (lonaxis.showgrid,lataxis.showgrid), the background (bgcolor) and the projection belong to the subplot: see Maps. - Draw order. Scatter traces are drawn above every base layer and above choropleth regions on the same subplot, in trace order.
- Default look. The default
holocharttemplate styles geo subplots for the dark page: dark land, lighter borders and coastlines.layout.template: 'plotly-classic'gives Plotly's look. See themes and templates.
import { createChart } from '@mk7s/holochart';
import '@mk7s/holochart/geo';
createChart(document.getElementById('chart')!, {
data: [
{
type: 'scattergeo',
lon: [-74.01, -0.13, 139.69],
lat: [40.71, 51.51, 35.69],
text: ['New York', 'London', 'Tokyo'],
mode: 'lines+markers+text',
textposition: 'top center',
line: { width: 1.5, dash: 'dot' },
marker: { symbol: 'diamond', size: 9 },
},
],
layout: {
geo: {
fitbounds: false,
projection: { type: 'natural earth' },
showocean: true,
showcountries: true,
},
},
});Interactivity
Hover. The closest point in screen distance shows
(lat°, lon°), thentextorhovertext. A trace given bylocationsshows the location instead of the coordinates.hoverinfotakes the flagslon,lat,location,textandname.hovertemplatetakes%{lon},%{lat},%{text},%{location},%{customdata}and, for a feature of the trace'sgeojson,%{properties.<key>}.tsimport { createChart } from '@mk7s/holochart'; import '@mk7s/holochart/geo'; createChart(document.getElementById('chart')!, { data: [ { type: 'scattergeo', lon: [2.35, 13.4], lat: [48.86, 52.52], text: ['Paris', 'Berlin'], hovertemplate: '%{text}: %{lat:.2f}° N, %{lon:.2f}° E<extra></extra>', }, ], });Moving the map. A drag pans a scoped map and turns a world map; the wheel and a pinch zoom; a double-click goes back to the first view. The keys each gesture writes are in Maps. The traces follow a pan or a zoom without being projected again.
Events.
hover,clickand selection points carrylon,latandlocation(nullfor a trace given by coordinates) next topointNumberandcurveNumber. For a trace given bylocations,lonandlatare where the point is drawn, and a feature of the trace'sgeojsonadds itsproperties.tschart.on('click', (e) => console.log(e.points[0]?.lon, e.points[0]?.lat));Selection. With
dragmode: 'select'or'lasso', a drag selects the points inside the box or the lasso, in projected space; the map does not move while one of these modes is on. Only traces with markers or text select, as in Plotly, and points the projection hides are not selectable.selectedpointsholds the selection.
Performance notes
- Markers and text are scatter's own GPU-instanced markers and text. Positions are projected on the CPU through d3-geo, once, and a pan or a zoom reuses them: it only changes a transform. A rotation projects every point again on each frame.
- Measured on an M1 Max: 100,000 markers are projected again in 6 to 13 ms per frame of a rotation, which holds 60 fps there with little to spare. Lines cost more, because each segment is resampled along its great circle: 10,000 long lines take 49 to 75 ms per frame, and lines are not simplified while the map turns. For many routes on a map that users will turn, prefer a scoped map or a flat projection that pans.
- A trace given by
locationswaits for the base map (or thegeojson, or the table of country names) and draws when it has arrived.chart.readywaits with it; a load that fails is warned about once and the trace draws nothing. - See Maps for what the base map costs.
Accessibility notes
- Screen readers: the hidden description (see the accessibility guide) reads each trace by its kind (map scatter, map line or map area) with its point count and its extent in longitude and latitude. Its data table lists longitude and latitude per point, the location first for a trace given by
locations, andtextwhen given per point. - Keyboard: Tab moves into the plot area; the arrow keys then step through the points that are on the map and in view, in data order, each showing its hover label. A point the projection hides, or one a pan has taken out of the subplot, is not a stop; the view keys (Shift + arrows,
+/-,0) move the map and bring it in. - Reading the map: every projection distorts areas, distances or both. For bubble maps, where sizes are compared across the map, use an equal-area projection (
'equal earth','mollweide','albers') andsizemode: 'area'. - Color: the default template's land and borders are low in contrast on purpose, so that markers stand out. Tell traces apart by marker symbol or line dash as well as by color.
Attribute reference
See the scattergeo attribute reference for every trace attribute, its type and its default. The subplot is under geo in the layout reference: projection, scope, fitbounds, resolution and the base layers.
Related charts
- Choropleth: values of regions as fill colors, on the same subplots; a
scattergeotrace over a choropleth labels or marks its regions - Scatter, line and bubble: the cartesian twins of this trace
- Maps: the geo subplot, projections, base layers and interactions
- Express maps:
hx.scatterGeoandhx.lineGeofrom tabular data
Plotly migration notes
- Attribute names and defaults match Plotly's
scattergeo:lon,lat,locations,locationmode,geojson,featureidkey,geo, the scatter modes, markers, lines, text and thetoselffill. A Plotly figure renders unchanged once the page imports@mk7s/holochart/geo; without it the trace is hidden and the warning names the import. - The base map is built from Natural Earth and bundled, where Plotly fetches its own files from a CDN, so coastlines and borders differ slightly from Plotly's and no request is made. See Maps.
'ISO-3'codes match in any case and with spaces around them; Plotly compares them as given.- With
'geojson-id', every location that names a feature is drawn. Plotly keeps one point per distinct location (the last), so a location given twice is drawn once. - Locations that match nothing are named in one console warning per trace. Plotly logs each one at its verbose level only.
fill: 'toself'fills the smaller of the two regions a closed path bounds, whichever way round the points are given; in Plotly a path drawn the other way round fills the whole globe except the shape. An open run is closed back to its first point; Plotly drops the last point of an open run when it fills it.geo.fitboundsdefaults to'locations', as in plotly.js 4. A figure written for an older Plotly, where the default wasfalse, shows the area of its data instead of the whole world: setfitbounds: false.- Not supported yet:
marker.angleref,marker.standoff,marker.gradient,marker.line.dash,textfont.shadow,texttemplatefallbackandhovertemplatefallback.textfont.styleandtextfont.weighttake one value, not an array.