Painting Maps
These functions give you the color of every part of a map: land, water, roads, parks, buildings, and labels, correct for the current device, mode, and theme. Adapters turn each answer into a MapLibre GL JS paint property, and TRMNLMaps assembles whole map styles out of them.
Map slots
Every resolver takes an optional { el }: the id
or element whose nearest .screen ancestor
supplies the paint. Each returns a canonical
Fill; see
Paint API
Paint API
TRMNLPaint: read the exact colors and patterns CSS paints right now, from JavaScript
TRMNLPaint is the framework's JavaScript API for its paint: colors, border lines, and text styles. Ask it for any framework color and it returns what CSS would actually paint right now, with the device and the active theme already applied. Use it wherever JavaScript draws: charts, maps, canvases, or your own rendering.
for the shape.
-
slot(name, { el, kind }): the Fill of one component slot.kindis'bg'(the default) for an area,'text'for a label, or'border'for a line, which returns a BorderFill whoserender.strokeis the one color a line can take. -
series(i, count, { el }): the fill for route i of count, from the same chart-series ramp charts use. See Painting Charts Painting Charts Chart series colors for the current device, mode, and theme, with Highcharts adapters These functions pick the colors for a chart. Ask for series 2 of 5 and you get its paint, correct for the current device, mode, and theme. Adapters then turn each answer into the exact option Highcharts expects. .
The area slots are map-land (the canvas),
map-water,
map-green (parks, forest, grass, wetland),
map-farmland,
map-sand (beach, sand, bare rock),
map-area (residential, commercial, industrial; the
canvas itself on 1-bit),
map-site (schools, hospitals, parking),
map-building (also piers, dams, bridges) and
map-transit (station dots).
The line slots are map-road (motorway to
tertiary), map-road-minor (residential,
service, pedestrian, runways), map-path
(footway, cycleway, track, steps), map-rail
(rail, tram, subway, aerialways), map-boundary
and map-water-line (rivers, canals, ferries).
The text role map-label inks every place and
water label; tiers differ by size, not tone, because a small dithered label would not survive a busy map.
Every map slot is a bg slot, lines included, so each resolves through the device mode: a dither tile on 1- and 2-bit screens, a solid on 4-bit and up, a hue on the color panels. A line painted from a tile is drawn as the polygon of its stroke filled with that tile, so a 1-bit road is the same screen-aligned dither its area would be, never a gray the panel cannot print; every line defaults to a gray short of the ink, so a plotted route in the ink reads on top of the roads.
Land follows the canvas and the labels follow the text channel, so a theme restates the token-bound
areas and lines with bg-slot, the mixin it
uses for every other component: see
Theme Slots
Theme Slots
Every part of a screen a theme can recolor, from whole-screen colors down to single components, utilities, borders, and chart series
This page lists every slot: each part of a screen a theme can recolor, and the mixin that sets it. A slot takes a framework token, not a raw color, so whatever you map still renders correctly on every device.
.
// An area: a tile on 1-bit, a solid on 4-bit, blue on a color panel.
var water = TRMNLPaint.slot("map-water", { el: "my-map" });
// A line is a bg slot too: a tile on 1-bit (drawn as a line pattern), a gray on 4-bit.
var road = TRMNLPaint.slot("map-road", { el: "my-map" });
// A label: the ink of the map-label text role.
var label = TRMNLPaint.slot("map-label", { el: "my-map", kind: "text" });
MapLibre adapters
Adapters shape a resolved Fill or
BorderFill for one specific renderer. They copy
resolved values into the renderer's native form; no adapter adds contrast heuristics, thresholds or
substitute design rules.
-
toMapLibre(fill): returns{ color, ink, pattern }. A solid Fill gives its color as bothcolorandink; a tile Fill gives its painted ink and a registered pattern image forfill-pattern, with the field composited in and a pixel ratio that lands one tile pixel on one device pixel; a line Fill gives its stroke and never a pattern.
TRMNLMaps (see the next section) is the
MapLibre composition layer over TRMNLPaint, the way
TRMNLCharts is for Highcharts:
Painting Charts
Painting Charts
Chart series colors for the current device, mode, and theme, with Highcharts adapters
These functions pick the colors for a chart. Ask for series 2 of 5 and you get its paint, correct for the current device, mode, and theme. Adapters then turn each answer into the exact option Highcharts expects.
. Pattern images are added to the map as the style asks
for them, so a plugin never handles the image itself.
// MapLibre: a flat color on solid panels, a registered pattern on 1- and 2-bit screens.
var water = TRMNLPaint.toMapLibre(TRMNLPaint.slot("map-water", { el: "my-map" }));
var paint = water.pattern
? { "fill-pattern": water.pattern.id } // dither tile
: { "fill-color": water.color }; // solid
// A line layer: TRMNLMaps.style() widens a tile line into a fill with the tile after the
// tiles load; by hand, a solid rail gives you the color and a dashed line takes the ink.
var road = TRMNLPaint.toMapLibre(TRMNLPaint.slot("map-road", { el: "my-map" }));
var linePaint = { "line-color": road.color || road.ink, "line-width": TRMNLPaint.px(2, { el: "my-map" }) };
The TRMNLMaps API
TRMNLMaps ships in the same
plugins.js runtime as
TRMNLPaint and composes MapLibre GL JS styles
and options out of it. It resolves no paint of its own: every value it returns comes from a TRMNLPaint
resolver and adapter.
Map
Map
Plot locations and routes on a vector map that adapts to the device and theme
Maps render OpenStreetMap vector tiles through MapLibre GL JS, with every layer painted by the framework. The TRMNLMaps helper composes the map style from the live screen, so a map adapts to the device and themes like the rest of the screen. Maps are plotted, never satellite, and never interactive.
puts it to work in street, route and Strava
examples.
Every method takes the same optional { el } as
the resolvers: the map container id or element whose nearest
.screen supplies the paint. Omit it on a
single-screen plugin.
Resolving map paint
-
paint(token, { el }): one palette token as MapLibre paint,{ color, ink, pattern }. -
series(i, count, { el }): the MapLibre paint for route i of count from the screen's chart-series ramp. -
route(map, coords, { el, i, n, width, id, casing }): plots a route as the polygon of its stroke (default width 3, throughTRMNLPaint.px()) filled with series i of n over a contrast casing, crisp and re-widened for every camera. Call it once the map has loaded. -
dot(map, lngLat, { el, i, n, radius, hollow, id }): plots a disc (default radius 4) in that series paint with a contrast ring;hollow: truemakes it a ring around a contrast core, so a start dot and an end ring read apart on one ink. -
applySwatches({ el }): paints every element under the screen carryingdata-map-slot="map-water"(anddata-map-slot-kind="text"or"border") from that slot, for a legend.
Building the style
-
tiles(preset): a tile source, the vector tile URL template, zoom range and attribution. Resolved in order: the argument ('osm','trmnl', or{ url, key, preset }merging over a preset), then the host'swindow.__TRMNL_MAPS__.tiles, then'osm', the public endpoint. A url may carry{key}. -
style(preset, { el, tiles, labels, buildings }): a complete MapLibre style forstreets,minimal,outlineorblank, every layer painted from the map slots.labels: falsedrops the labels,labels: 'major'keeps the big place names,buildings: falsedrops the footprints. -
options({ el, preset, center, zoom }): Map options for a still map: the container, every handler and animation off, no controls, the screen's pixel ratio, and the style for the preset. Pass the result tonew maplibregl.Map(). -
merge(base, overrides): a deep merge of two plain objects, where arrays and scalars replace. Layer your own ids, sources and options over the defaults with it.
Fitting and decoding
-
fit(map, coords, { padding, maxZoom }): frames the coordinates without animation, on an integer zoom with the center snapped to the pixel grid so dither patterns stay crisp. Returns the{ center, zoom }it jumped to. -
decodePolyline(str, precision): a Google encoded polyline (Strava'smap.summary_polyline) as[lng, lat]pairs, ready for a GeoJSON LineString.
Keeping the map current
-
watch(el, buildFn): builds the map now and again whenever the device, scale, mode, dark-mode or theme classes change.buildFncreates and returns the map; the previous one is removed first and the new one attached. Returns a stop function. -
attach(map, { el }): registers a map you built yourself: pattern images, the pixel-grid snap, the labels, the attribution and readiness.watch()calls it for you. -
ready(map): a promise that resolves once the map has drawn everything it knows about. -
settle({ maxWaitMs }): the bounded wait the runtime runs at the end of a pass, sowindow.TRMNL_PLUGINS_READYflips only once every attached map is idle. Default 6000 ms, orwindow.__TRMNL_MAPS_SETTLE_MS__. -
refresh({ maxWaitMs }): rebuilds every watched map from the live cascade and settles them, without re-running the pass. For a host that rescales the screen after the pass, the way a screenshot service sets its capture pixel ratio last. -
supported(): whether this browser can draw a MapLibre map. Without WebGL,watch()flags the containerdata-map-unsupportedand shows a.map__fallbackchild if you placed one.
MapLibre numbers do not read CSS, so resolve widths, radii and padding with
TRMNLPaint.px() inside the build function. See
Paint API
Paint API
TRMNLPaint: read the exact colors and patterns CSS paints right now, from JavaScript
TRMNLPaint is the framework's JavaScript API for its paint: colors, border lines, and text styles. Ask it for any framework color and it returns what CSS would actually paint right now, with the device and the active theme already applied. Use it wherever JavaScript draws: charts, maps, canvases, or your own rendering.
.
Every mark is a fill
Nothing TRMNLMaps draws is a MapLibre line or circle, which would be anti-aliased and, for a tile, filtered along the line. Every road, dash, stop, route and dot is the polygon of its stroke, filled with the slot's tile or solid without anti-aliasing, so it lands on the pixel grid like every other tile on the screen. The runtime widens them after the tiles load and again for every camera, in device pixels: a 1px line is one row of the panel's pixels on a 1.8x device as on a 1x one.
Labels
Place and water labels are framework elements, not MapLibre text. After every idle the runtime reads
the label features out of the loaded tiles, writes each one into the container as a
label (big places) or
label label--small (towns, suburbs, water)
with text-stroke text-stroke--large, snaps it
to whole pixels, and keeps the biggest that fit without overlap. Every tier takes the map-label ink.
The biggest names win the space. Small kinds wait for closer zooms (towns from zoom 9, villages from 11, suburbs from 12, neighbourhoods from 13), water earns a name once it covers about a label's worth of screen, and a small map holds a few names instead of a crowd. No label lands on the credit.
So labels take the screen's own typography (TRMNL pixel fonts on 1-bit and 2-bit low-density panels,
Inter on 4-bit and high density), the text-stroke halo, and the map-label slot for ink, with no glyph
endpoint involved. style() decides which
tiers a preset shows.
var el = "my-map";
// watch() rebuilds on every device, mode, dark-mode and theme change.
TRMNLMaps.watch(el, function () {
var map = new maplibregl.Map(TRMNLMaps.options({ el: el, preset: "minimal" }));
map.on("load", function () {
TRMNLMaps.route(map, coords, { el: el, width: 3 });
TRMNLMaps.dot(map, coords[0], { el: el, id: "start" });
});
TRMNLMaps.fit(map, coords, { padding: TRMNLPaint.px(20, { el: el }), maxZoom: 15 });
return map;
});
Live example
The swatches below are painted with
TRMNLMaps.applySwatches() inside
TRMNLPaint.watch(), with no map library
involved. Change the device mode or Style in the screen picker and they repaint from the live cascade:
tiles on 1- and 2-bit screens, solids on 4-bit, hues on a color panel.
<div class="w--14 h--4 mb--2 rounded--small" data-map-slot="map-water"></div>
<div class="w--14 h--1.5 mb--2" data-map-slot="map-road"></div>
<script type="text/javascript">
var el = "map-slot-swatches";
TRMNLPaint.watch(el, function () {
TRMNLMaps.applySwatches({ el: el });
});
</script>