Reference
API
Every export of @industrieh/starmap-render, with its signature. Everything comes from the package root.
Rendering
renderSystem
functionDraw one system as a standalone SVG document, at a given moment.
function renderSystem(
system: StarSystem,
options?: RenderSystemOptions,
): stringParameters
- systemStarSystem
- One system —
galaxy.systems[0], not the galaxy. - optionsRenderSystemOptions
- See the table below.
Returns
A complete
<svg>…</svg> string — write it to a file, inline it, or put it in a data: URI.const svg = renderSystem(galaxy.systems[0], { t: 12.5, width: 700, height: 700 })
await Bun.write('sol.svg', svg)renderSystemContent
functionThe same drawing, without the
<svg> around it. This is what an interactive application wants: it owns the root element, so it owns the viewBox — and pan and zoom are then nothing more than changing it.function renderSystemContent(
system: StarSystem,
options?: RenderSystemOptions,
): stringconst body = renderSystemContent(system, { t })
const box = systemViewBox(system)
container.innerHTML = `<svg viewBox="${viewBoxAttr(box)}">${body}</svg>`renderGalaxy
functionDraw the whole map: systems as points, hyperlanes as the network between them. Takes no
t — at galaxy scale a planet is smaller than a rounding error.function renderGalaxy(
galaxy: Galaxy,
options?: RenderGalaxyOptions,
): stringconst route = findRoute(galaxy, 'sol', 'vega')
const map = renderGalaxy(galaxy, { highlight: route?.systemIds })renderGalaxyContent
functionThe galaxy's layers alone. Pair it with
galaxyViewBox() when your application owns the root element.function renderGalaxyContent(
galaxy: Galaxy,
options?: RenderGalaxyOptions,
): stringOptions
Both option types extend DocumentOptions, so width, height and title are available on all four functions — the two …Content ones ignore them, since they emit no root element.
| Field | Type | Description |
|---|---|---|
t | numberdefault 0 | Simulation time, in seconds. System only— compared against each object's period, the same way systemPositions() does it. |
theme | ThemeInputdefault DEFAULT_THEME | Colours. Anything left out is merged from the default — Theming. |
margin | numberdefault 0.45 / 0.12 | Empty space around the drawing, as a fraction. 0.45 of outerLimit for a system; 0.12 of the largest dimension for a galaxy. |
labels | booleandefault true | Names of planets and gates, or of systems on the galaxy map. |
backdrop | numberdefault 260 / 400 | How many decorative background stars. 0 draws none — worth doing for a thumbnail or an animation. |
idPrefix | stringdefault 'starmap' | Prefix of the gradient ids this drawing declares. Two SVGs inlined on one page share an id namespace, so give each its own — why it matters. |
systems | SystemsById | System only. From indexSystems(galaxy). When given, a gate label names where it leads. |
highlight | readonly string[] | Galaxy only. An ordered path — exactly what findRoute() returns in systemIds. Consecutive entries light the lane between them. |
width, height | number | On the root element, in pixels. Left out by default, so the drawing fills its container. |
title | string | Adds a <title> child and role="img", so a screen reader announces the drawing instead of skipping it. |
Framing
systemViewBox
functionThe framing
renderSystem()would use. Square and centred on the origin, because a system's star is always at (0, 0).function systemViewBox(system: StarSystem, margin?: number): ViewBox
const SYSTEM_MARGIN = 0.45Returns
{ x, y, w, h } in world units. A system whose outerLimit is 0 or nonsense falls back to 100.systemViewBox(system) // { x: -435, y: -435, w: 870, h: 870 } — outerLimit 300 + 45%
systemViewBox(system, 0) // { x: -300, y: -300, w: 600, h: 600 } — cropped to the edgegalaxyViewBox
functionThe bounding box of the systems, plus a margin. Not centred — a galaxy keeps the coordinates you gave it.
function galaxyViewBox(galaxy: Galaxy, margin?: number): ViewBox
const GALAXY_MARGIN = 0.12Returns
An empty galaxy returns
-100 -100 200 200. A single system, or several in a straight line, would give a box with zero width or height — both are clamped to at least 1.galaxyViewBox(galaxy) // { x: -50.4, y: -50.4, w: 520.8, h: 260.8 }viewBoxAttr
functionFormat a ViewBox as the attribute value.
function viewBoxAttr(view: ViewBox): stringviewBoxAttr(systemViewBox(system)) // '-435 -435 870 870'svgDocument
functionWrap finished layers in a standalone document. Writes
xmlns, which is what makes the string usable outside an HTML parser — a .svg file, a data: URI, an <img src>.function svgDocument(
view: ViewBox,
body: string,
options?: DocumentOptions,
): stringsvgDocument({ x: 0, y: 0, w: 10, h: 10 }, '<circle r="1"/>', { title: 'T' })
// <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10"
// preserveAspectRatio="xMidYMid meet" role="img">
// <title>T</title>
// <circle r="1"/>
// </svg>Shapes
arcPath
functionThe
d of a circular arc centred on the origin. Used for asteroid belts, which are arcs rather than full rings.function arcPath(r: number, startDeg: number, sweepDeg: number): stringPast 359.5° it becomes two arcs
A single SVG arc cannot describe a complete circle — its end point would be its start point, and the arc would collapse to nothing.
arcPath(220, 0, 120)
// 'M 220 0 A 220 220 0 0 1 -110 190.53'
arcPath(220, 0, 360)
// 'M 220 0 A 220 220 0 1 1 -220 0 A 220 220 0 1 1 220 0'backdropStars
functionA field of decorative stars, sown in a square centred on the origin. Deterministic: the same seed always gives the same sky.
function backdropStars(
count: number,
radius: number,
seed: string | number,
scale?: number,
): BackdropStar[]Parameters
- countnumber
- How many.
- radiusnumber
- Half-side of the square they are sown in.
- seedstring | number
- Anything stable. A string is hashed, a number is used as is.
- scalenumber = 1
- Multiplies each dot radius. Dots are sized in world units, so a galaxy-wide view needs bigger ones than a single system to look the same.
Returns
{ x, y, r, o }[] — position, radius, opacity.It runs on its own seed, not the galaxy's
A generator sharing one sequence with its backdrop would shift its whole universe by changing the number of background stars.
Theme
resolveTheme
functionFill a
ThemeInput in from the default. Tables are merged key by key, so { planets: { terran: '#7ee787' } } keeps every other planet colour.function resolveTheme(input?: ThemeInput): Theme
const DEFAULT_THEME: ThemeReturns
A complete Theme — every field filled in, every table carrying its default entry.
Nothing is merged deeper than a table
A
RouteStyle is replaced whole, because a dash pattern without its width is not a style. And a field explicitly set to undefined falls back to the default rather than leaving a hole in the drawing.const theme = resolveTheme({ background: '#000', planets: { terran: '#7ee787' } })
theme.background // '#000' yours
theme.planets.terran // '#7ee787' yours
theme.planets.gas // '#d8a06a' still the defaultpickColor, pickRouteStyle
functionLook a key up, falling back to the table's
default. The core's vocabularies are open unions, so a table can never be exhaustive — which is why default is required by the type.function pickColor(table: ColorMap, key: string | undefined): string
function pickRouteStyle(table: RouteStyleMap, key: string | undefined): RouteStylepickColor(theme.planets, 'terran') // '#7ee787'
pickColor(theme.planets, 'shattered') // '#8b8377' — the default
pickColor(theme.planets, undefined) // '#8b8377' — the default tooString primitives
The four functions the whole package writes its output through, exported so your own overlay gets the same escaping and number formatting. See Draw your own overlay.
| Field | Type | Description |
|---|---|---|
el(name, attrs, children?) | => string | One element, self-closing when it has no children. An attribute that is undefined, null or false is dropped.el('circle', { r: 4.005 }) → <circle r="4"/> |
group(attrs, parts) | => string | A <g> around several parts. Empty parts are dropped, and a group left with nothing returns '' — which is why an empty layer is never emitted. |
text(content, attrs) | => string | A <text> element. The content is escaped; pass raw names. |
num(value) | => string | Two decimals, no exponent. A non-finite value becomes '0' rather than 'NaN', which no SVG reader accepts. |
dash(...lengths) | => string | A stroke-dasharray value. Dash lengths are written into a string rather than an attribute, so they would otherwise escape num(). |
escapeXml(value) | => string | Escapes &, <, >, " and '. Every name in a galaxy is user data. |
background(view, fill) | => string | The oversized rectangle behind everything — why three times. |
Types
| Field | Type | Description |
|---|---|---|
RenderSystemOptions, RenderGalaxyOptions | options | Both extend DocumentOptions. The table above. |
DocumentOptions | options | { width?, height?, title? } — the root element. |
ViewBox | geometry | { x, y, w, h }, in SVG viewBox order. |
BackdropStar | geometry | { x, y, r, o }. |
Theme, ThemeInput | theme | What the drawing reads, and what you write. The same two-layer split as the core. |
ColorMap | theme | A colour per key, plus a required default. |
RouteStyle, RouteStyleMap | theme | { stroke, dash, width }, and a table of them. |
Attrs, AttrValue | primitives | What el() accepts. AttrValue includes undefined, null and false, all of which drop the attribute. |