maplang
v0.1.0
Published
A diagramming language for maps — turn simple text into clean SVG maps.
Maintainers
Readme
MapL
A diagramming language for maps — turn simple text into clean SVG maps.
⚠️ Work in progress. MapL is in early design. The language spec and engine are still taking shape — APIs and syntax will change.
What is MapL?
MapL (Map Language) lets you describe a map in plain text and renders it to SVG — no GeoJSON wrangling, no GIS software, no coordinate math.
map
fill France color=#2c7fb8
fill Germany
fill Italy
point Paris
point Berlin
point [12.4964, 41.9028]The goal: be to maps what Mermaid is to diagrams — simple to write, easy to embed in docs, and stable to render.
Data-driven thematic maps
Feed in values and let choropleth color regions automatically — a full thematic map with a title and legend, no per-region fill needed:
map
title "Western Europe — population (millions)"
data {
France: 67.4
Germany: 83.2
Italy: 59.0
Spain: 47.4
Portugal: 10.3
}
choropleth scheme=YlGnBu steps=5 method=quantile
legend position=bottom-right title="Population (M)"Data can also come from a file: load gdp.csv key=country value=gdp (CSV/JSON).
Add theme dark for a dark background. An explicit fill X color=… always
overrides the choropleth color for that region.
Proportional symbols and lines (SPEC v1.1)
Give a point a value= to size it by area (radius ∝ √value), tune the range
with bubble, and connect places with line:
map
point Paris value=11 label="Paris"
point London value=9 label="London"
bubble max=38 min=5
line London -> Paris curve=arc dashed color=#e11d48
legend position=bottom-left title="Metro pop (M)"line endpoints use the same addressing as point (Name, "quoted", or
[lng, lat]); curve=arc draws a purely visual upward curve (not a geodesic).
A legend becomes a size legend when valued points are present.
One line can draw several segments (SPEC v2): a chain connects
consecutive stops, a fan pairs one hub with a comma list on one side of
the arrow — lists on both sides are an error (no cartesian products):
line London -> Paris -> Rome -> Athens curve=arc # 3 segments
line Shanghai -> Tokyo, Seoul, Singapore # hub fan-out
line Paris, Berlin -> London # fan-inGive a fill a label= to name the region — the text lands at the shape's
visual center (robust for concave shapes and archipelagos, where an area
centroid would fall outside) with a halo for legibility:
map
fill France label="France"
fill Norway label="Norway"Framing
map takes no region — the view is framed automatically around whatever you
draw. Tune the breathing room with margins on the map line (CSS order:
top right bottom left):
map margin=40 # 40px on every side
map margin=20,60 # 20 top/bottom, 60 left/right
map marginBottom=80 # override a single sideVariables (SPEC v2)
Use a vars block to keep repeated colors, numbers, names, and quoted strings
in one place. Variables are expanded before parsing, so they work anywhere a
normal value works:
vars {
base: #c6dbef
accent: #e6550d
focus: France
city: "New York"
}
map
fill $focus color=$accent
fill Germany color=$base
point $city label=$cityEach variable must be declared as name: value; an undefined $name reports
the line where it is used. Values containing spaces must be quoted.
Style classes (SPEC v2)
Classes apply the same named style to several fills, points, or lines. Multiple classes are applied left-to-right, and explicit properties on the statement always win:
class land {
color: #c6dbef
stroke: #ffffff
strokeWidth: 1.2
}
class highlight { color: #e6550d }
map
fill France class=land
fill Italy class="land highlight"
fill Spain class=land color=#74c476 # inline color winsLine-only class properties are width, curve: straight|arc, and
dashed: true|false. An unknown class reports the line where it is used.
Selectors: * and comma lists (SPEC v2)
One fill can target several regions — or every region of the basemap:
class muted { color: #e2e8f0 }
map
fill * class=muted # whole basemap, a base coat
fill Spain, Portugal color=#74c476
fill Belgium, Netherlands, Luxembourg color=#6baed6A comma list is exactly equivalent to writing one fill per name (unknown
names report the same error). When statements touch the same region, the later
one replaces the earlier one — so fill * works as a base coat that named
fills and choropleth colors paint over. There is no prefix/fuzzy matching:
list regions explicitly, or use * for the deterministic "everything".
point accepts comma lists too, freely mixing the three locator kinds; named
places keep their own default labels:
point Paris, [118.8, 32.06], "南京南站" color=#e6550d size=5point * is deliberately not supported ("every city" is not a meaningful
map), and * never applies to line — but line has chain and fan forms,
see the lines section above.
Design principles
- Deterministic core — same DSL in, same SVG out. No I/O inside the engine.
- Offline-first — built-in low-resolution data works without any network.
- Pluggable everything — data sources, geocoding, and AI all sit behind interfaces, so the same engine can power both an offline build and optional online services.
- Embeddable — a plain TypeScript/JS library that drops into the browser, Obsidian, VS Code, and Node without a separate binary.
Try it
npm install
npm run demo # renders examples/europe.mapl -> examples/europe.svgOr use the API / CLI:
import { renderMapL } from "maplang/node";
const svg = renderMapL("map\nfill France\npoint Paris");For browser, editor, and custom-host integrations, import the I/O-free Core
entry point and supply a DataProvider:
import { renderMapLToSvg, type DataProvider } from "maplang";
declare const dataProvider: DataProvider;
const svg = renderMapLToSvg("map\nfill France", dataProvider);Advanced render backends can use the experimental scene IR instead of SVG:
import { compileMapL, renderSvg } from "maplang";
const scene = compileMapL("map\nfill France", dataProvider);
const svg = renderSvg(scene);mapl examples/europe.mapl -o out.svgPlayground
A local browser playground with live preview and preset examples:
npm run playground # serves http://localhost:8000 (live rebuild)Or build a static bundle and open the page directly:
npm run playground:build # writes playground/app.js
# then open playground/index.html in a browserProject layout
src/
core/ # Parser, AST, SVG renderer, colors; no host I/O
node/ # Node data adapter and convenience API
index.ts # Public I/O-free Core entry (npm `maplang`)
cli.ts # Node CLI entry
playground/ # Browser editor; imports only the public Core API
data/ # Bundled TopoJSON and gazetteer
scripts/ # Data build pipelineThe Playground build aliases mapl to src/index.ts, so it bundles the same
public Core API that consumers import after publication. Its development command
watches this source entry; it does not reach into Core implementation modules
directly.
Status
Pre-alpha. A minimal engine works end-to-end: a parser for map / fill /
point plus data-driven data / load / choropleth / legend / title /
theme (SPEC v1) and proportional symbols / line / bubble (SPEC v1.1),
built-in geography (Natural Earth 1:50m map units, so
France is the metropole and French Guiana is a separate region; rebuilt via
npm run data:build), and an SVG renderer built on d3-geo + d3-scale.
Syntax and APIs will still change.
License
Inspired by Mermaid and D2. MapL is an independent project and is not affiliated with either.
