Guides
Drawing
What the renderer actually puts on screen: the layers, the order they are stacked in, and how each object is constructed out of circles and paths.
Order is the composition
SVG has no z-index. What is written later is drawn on top — so the order of the layers is the composition, and changing it is a visual change.
Each layer is a<g>carrying adata-layer. That attribute is not decoration: it is how an application dims, hides or restyles a whole class of thing without the package knowing anything about it.
{ backdrop: 0, labels: false } — the same drawing with two layers removed, which is the easiest way to see what the others are doing.The system layers
Bottom to top, exactly as they are emitted:
| Field | Type | Description |
|---|---|---|
defs | not a layer | Three radial gradients — the star's halo, its core, and the shading on a planet. Their ids are prefixed, see idPrefix. |
background | a bare <rect> | Fills the view. Three times the viewBox and centred on it, on purpose — the letterboxing story. |
backdrop | data-layer | The decorative star field. Seeded, so it never flickers between frames. |
orbits | data-layer | The circles planets and gates travel on. |
fields | data-layer | Asteroid belts and clusters, with their rocks. |
routes | data-layer | Local routes, each stroked with its own dash pattern. |
star | data-kind | Not a layer of its own — one group, sitting between the fields and the planets. |
planets | data-layer | Planets, their stations, their labels. |
gates | data-layer | Last, so a gate is never hidden behind a planet that drifts over it. |
The galaxy layers
Shorter, because at galaxy scale there is less to say:
| Field | Type | Description |
|---|---|---|
background | a bare <rect> | Same as above. |
backdrop | data-layer | The star field, sown wider. |
lanes | data-layer | The hyperlane network. Highlighted lanes are drawn in the same layer. |
systems | data-layer | A dot per system, its faction ring, its halo, its label. |
factions.default.Nothing moves on the galaxy map
renderGalaxy() takes no t. At galaxy scale a planet is smaller than a rounding error, so there is nothing to animate — the systems are where createGalaxy() put them.Two kinds of size
A system can be 300 world units across, or 30 000. That single fact shapes every number in the renderer, and it splits them into two families:
- Real sizes stay real.A star's radius, a planet's radius, an orbit — those come from the model and are drawn as they are. A planet twice as big looks twice as big.
- Everything else is a fraction of the view. Gate glyphs, stroke widths, label sizes, the gaps between things. Sizing those in absolute units would make them fill the screen in a small system and vanish in a large one.
Inside the source that second family goes through a small Scale object — u for spacing (a 200th of the view), k for strokes, font for labels. You never see it from outside, but it is why a drawing holds up at any size.
This is what makes a thumbnail work
How each object is built
The star
Three circles stacked: a wide halo on the glow gradient, the disc itself, and a bright core. The halo is several times the disc — that is what reads as brightness rather than as a bigger star.
Its colour comes from theme.stars, keyed by star.class. A class the table does not list falls back to the table's default.
Planets and stations
A planet is a disc filled from theme.planets by its type, with a shading gradient over it so it does not read as a flat dot. Two optional additions:
- a ring in
theme.habitablewhenplanet.habitableis true; - a label with its name, when
labelsis on.
Stations are drawn inside the planet's group, because they travel with it. Each is a small mark coloured from theme.stations by its type.
<g data-layer="planets">
<g data-kind="planet" data-id="sol-p1">
<circle .../> the disc
<circle fill="url(#starmap-shade)" .../> the shading
<circle stroke="#4fd8a5" .../> habitable ring
<g data-kind="station" data-id="sol-p1-st0">…</g>
<text>Vesper</text>
</g>
</g>Gates
A glyph on its orbit circle, which is dashed to tell it apart from a planet's. A gate whose status is unstable gets a second ring in theme.gateUnstable.
A gate label can name where it leads
systems: indexSystems(galaxy)and the label becomes the destination's name rather than the gate's. The renderer cannot know it otherwise — a system does not carry its neighbours.import { indexSystems } from '@industrieh/starmap'
renderSystem(system, { systems: indexSystems(galaxy) })Asteroid fields
Two shapes, chosen by the field's discriminant:
- A belt is an arc, stroked at the width of the ring. It is a path rather than a circle because a belt is usually a partial arc.
- A cluster is a circle at its own position.
Either way, the rocks you provided are drawn inside it. The renderer never invents any — an empty rocks array draws the outline and nothing else.
A full ring is a special case worth knowing about: a single SVG arc cannot describe a complete circle, because its end point would be its start point and the arc would collapse to nothing. Past 359.5° the path becomes two half-circles instead.
import { arcPath } from '@industrieh/starmap-render'
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'
// ↑ two arcs, because one cannot close a circleSystems and lanes
On the galaxy map, a system is up to four things stacked:
- a halo, so a dense cluster still reads as points of light;
- a ring in
theme.factions, keyed byfaction.id— only when the system has a faction; - the dot itself;
- its name, when labels are on.
A lane is a line between two systems, in theme.lane — or in theme.highlight when it is part of the highlighted path.
The faction ring is dashed until it is highlighted
theme.highlight; every other one is dashed, in its faction's colour. Two channels rather than one, so the path is still obvious in greyscale — the same reasoning as the route dash patterns.The empty cases
The core makes most fields optional, so the renderer meets them constantly. All of these produce a valid document:
| Field | Type | Description |
|---|---|---|
No planets | a system | No planets layer at all — an empty group is never emitted. |
No type on a planet | undefined | Falls back to theme.planets.default. Not a bug — the core never invents a value, so every table has a fallback. |
No systems | an empty galaxy | A fallback viewBox of -100 -100 200 200, so the document is still displayable. |
One system | a galaxy | Its bounding box would have zero width and height, which renders as nothing. Both are clamped to at least 1. |
outerLimit of 0 | a system | Falls back to 100. A drawing that cannot be framed is worse than one framed arbitrarily. |
A name with an ampersand does not break the document
Ohm & Sons comes out as Ohm & Sons — which is also what stops a name carrying a tag from injecting markup into the page that embeds the drawing.