Skip to content
design
Components

Nautical chart

A nautical chart with the server's geometry underneath and a React layer drawn over it.

On this page
14 14 18 18 26 26 31 31 24 24 42 42 68 68 64 64 61 61 89 89 46 46 125 125 142 142 73 73 22 22 274 274 248 248 31 31 336 336 Pier W Pier E No 2 No 1 No 4 No 3 Fairway Bank S Head Head Gull Rock Wreck Marina Anchorage 0.25 nm N
A harbor approach from the chart renderer, with the vessel and an AIS target drawn over it.
Code
import { NauticalChart } from "@bosunkit/components/nautical-chart";
import type { ReactElement } from "react";
import harbor from "./harbor.svg?raw";

export const description: string =
  "A harbor approach from the chart renderer, with the vessel, her track and an AIS target drawn over it.";

export default function WithGeometry(): ReactElement {
  return (
    <div style={{ width: "100%", maxWidth: 720 }}>
      <NauticalChart staticSvg={harbor} width={800} height={600}>
        {/* The track the vessel has made good, oldest first. */}
        <polyline
          points="474,600 470,560 464,520 458,486 455,452"
          fill="none"
          stroke="var(--accent)"
          strokeWidth={2}
          strokeDasharray="4 4"
          opacity={0.7}
        />
        {/* An AIS target leaving harbor, with its course vector. */}
        <g transform="translate(438 196) rotate(170)">
          <line x1={0} x2={0} y1={-8} y2={-48} stroke="var(--amber)" strokeWidth={1.5} />
          <path d="M 0 -8 L 5 7 L 0 4 L -5 7 Z" fill="var(--amber)" stroke="var(--bg)" />
        </g>
        {/* Own vessel inbound on the channel: hull on heading, six-minute course vector. */}
        <g transform="translate(455 452) rotate(352)">
          <line x1={0} x2={0} y1={-12} y2={-84} stroke="var(--accent)" strokeWidth={2} />
          <path d="M 0 -12 L 7 10 L 0 6 L -7 10 Z" fill="var(--accent)" stroke="var(--bg)" />
        </g>
      </NauticalChart>
    </div>
  );
}

A chart is an SVG in two layers. The static layer holds the geometry the server renders and the chart inserts as raw markup. The dynamic layer holds the React children, such as the vessel, AIS targets and the track, which change as the data does. Both share one viewBox, so the chart scales to the width of its container and keeps its proportions. On a screen whose height is known, a phone on its side among them, the chart is never taller than the screen. The chart is announced to assistive technology as an image with a translated label.

The hero's geometry is the chart renderer's own output for a fixed harbor scene, written by a test in packages/bosun/ui/chartsvg that fails when the renderer and the file part. The vessel, her track and the AIS target are the React layer.

Examples

Without geometry

When the server has no geometry for the area, staticSvg is null and the overlay says so.

No chart data for this area
A chart with no geometry for the area shows a message drawn by the overlay.
Code
import { NauticalChart } from "@bosunkit/components/nautical-chart";
import type { ReactElement } from "react";

export const description: string = "A chart with no geometry for the area shows a message drawn by the overlay.";

export default function WithoutGeometry(): ReactElement {
  return (
    <div style={{ width: "100%", maxWidth: 720 }}>
      <NauticalChart staticSvg={null} width={800} height={600}>
        <text
          x={400}
          y={300}
          textAnchor="middle"
          fill="var(--text-dim)"
          fontFamily="var(--font-mono)"
          fontSize="14"
          opacity="0.5"
        >
          No chart data for this area
        </text>
      </NauticalChart>
    </div>
  );
}

Props

PropTypeDefaultDescription
staticSvgrequiredstring | null–Server-rendered SVG geometry, inserted as raw HTML (safe — server-generated).
widthnumber | undefined800SVG viewBox width (default 800).
heightnumber | undefined600SVG viewBox height (default 600).
childrenReactNode–Dynamic overlay components rendered as SVG children (vessel, AIS, track).
classNamestring | undefined–Optional additional CSS class.