starmap

Reference

API

Every export of @industrieh/starmap-render, with its signature. Everything comes from the package root.

Looking for explanations rather than signatures?

Drawing covers the layers, Theming the colours, Framing the viewBox and embedding.

Rendering

renderSystem

function
Draw one system as a standalone SVG document, at a given moment.
function renderSystem(
  system: StarSystem,
  options?: RenderSystemOptions,
): string

Parameters

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

function
The 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,
): string
const body = renderSystemContent(system, { t })
const box = systemViewBox(system)
container.innerHTML = `<svg viewBox="${viewBoxAttr(box)}">${body}</svg>`

renderGalaxy

function
Draw 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,
): string
const route = findRoute(galaxy, 'sol', 'vega')
const map = renderGalaxy(galaxy, { highlight: route?.systemIds })

renderGalaxyContent

function
The galaxy's layers alone. Pair it with galaxyViewBox() when your application owns the root element.
function renderGalaxyContent(
  galaxy: Galaxy,
  options?: RenderGalaxyOptions,
): string

Options

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.

FieldTypeDescription
tnumberdefault 0Simulation time, in seconds. System only— compared against each object's period, the same way systemPositions() does it.
themeThemeInputdefault DEFAULT_THEMEColours. Anything left out is merged from the default — Theming.
marginnumberdefault 0.45 / 0.12Empty space around the drawing, as a fraction. 0.45 of outerLimit for a system; 0.12 of the largest dimension for a galaxy.
labelsbooleandefault trueNames of planets and gates, or of systems on the galaxy map.
backdropnumberdefault 260 / 400How many decorative background stars. 0 draws none — worth doing for a thumbnail or an animation.
idPrefixstringdefault '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.
systemsSystemsByIdSystem only. From indexSystems(galaxy). When given, a gate label names where it leads.
highlightreadonly string[]Galaxy only. An ordered path — exactly what findRoute() returns in systemIds. Consecutive entries light the lane between them.
width, heightnumberOn the root element, in pixels. Left out by default, so the drawing fills its container.
titlestringAdds a <title> child and role="img", so a screen reader announces the drawing instead of skipping it.

Framing

systemViewBox

function
The 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.45

Returns

{ 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 edge

galaxyViewBox

function
The 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.12

Returns

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

function
Format a ViewBox as the attribute value.
function viewBoxAttr(view: ViewBox): string
viewBoxAttr(systemViewBox(system))   // '-435 -435 870 870'

svgDocument

function
Wrap 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,
): string
svgDocument({ 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

function
The 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): string

Past 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

function
A 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

function
Fill 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: Theme

Returns

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 default

pickColor, pickRouteStyle

function
Look 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): RouteStyle
pickColor(theme.planets, 'terran')      // '#7ee787'
pickColor(theme.planets, 'shattered')   // '#8b8377' — the default
pickColor(theme.planets, undefined)     // '#8b8377' — the default too

String 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.

FieldTypeDescription
el(name, attrs, children?)=> stringOne 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)=> stringA <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)=> stringA <text> element. The content is escaped; pass raw names.
num(value)=> stringTwo decimals, no exponent. A non-finite value becomes '0' rather than 'NaN', which no SVG reader accepts.
dash(...lengths)=> stringA stroke-dasharray value. Dash lengths are written into a string rather than an attribute, so they would otherwise escape num().
escapeXml(value)=> stringEscapes &, <, >, " and '. Every name in a galaxy is user data.
background(view, fill)=> stringThe oversized rectangle behind everything — why three times.

Types

FieldTypeDescription
RenderSystemOptions, RenderGalaxyOptionsoptionsBoth extend DocumentOptions. The table above.
DocumentOptionsoptions{ width?, height?, title? } — the root element.
ViewBoxgeometry{ x, y, w, h }, in SVG viewBox order.
BackdropStargeometry{ x, y, r, o }.
Theme, ThemeInputthemeWhat the drawing reads, and what you write. The same two-layer split as the core.
ColorMapthemeA colour per key, plus a required default.
RouteStyle, RouteStyleMaptheme{ stroke, dash, width }, and a table of them.
Attrs, AttrValueprimitivesWhat el() accepts. AttrValue includes undefined, null and false, all of which drop the attribute.