What this dashboard is for, how to record an apparent-wind log it can read — including straight from a Signal K server — and the short version of the privacy story: your data never leaves your browser.
It turns a log of apparent wind and boat speed into something you can actually read. Load a file and you get:
It is aimed at sailors who want to answer concrete questions after a sail: was that boat speed a gust or good trim, how does apparent angle move against speed on each point of sail, where did the wind field change. It is free, needs no account, and runs entirely in the browser.
Open the dashboard → — the bundled demo log loads first, so you can see every panel before recording anything of your own.
A JSON object with a data array of records. A plain array of records works too, and
so does an object whose first array holds them:
{
"data": [
{ "datetime": "2026-01-01T10:00:00Z",
"lat": 12.4115, "lon": 100.0177,
"sog": 3.2, "heading": 180,
"windSpeed": 8.1, "windAngle": 45,
"windGust": 10.4 }
]
}
| Field | Meaning |
|---|---|
datetime | ISO date-time — chart labels and popups |
lat, lon | degrees — map, heatmap, wind arrows |
sog | boat speed over ground, knots |
heading | vessel heading, degrees, 0–360 |
windSpeed | apparent wind speed, knots |
windAngle | apparent wind angle in degrees relative to the bow — negative to port, positive to starboard |
windGust | gust, knots — optional, null is fine |
Speeds are interpreted as knots; the unit selectors convert them for display. If your file uses different key names, a one-time field-mapping window opens when you load it. The limit is 25 MB. The i button in the dashboard's data bar opens the same format reference with a Load Sample Data download.
Signal K is the open marine data standard: a gateway or server reads your instruments (NMEA 0183, NMEA 2000, AIS, a connected GPS) and turns them into JSON that any web app can subscribe to over a WebSocket. If your boat already runs a Signal K server, you already have every number this dashboard needs — you just need to record it.
The entry point is /signalk on the server's host and port (the Node server defaults
to port 3000, others use 80/443 or 3443). It answers with the endpoints it supports:
curl http://localhost:3000/signalk
The streaming endpoint sends delta messages — small JSON updates carrying a
path and a value for each reading:
ws://localhost:3000/signalk/v1/stream?subscribe=self
Save as record-signalk.mjs and run it with Node.js 22 or newer — it uses the built-in
WebSocket, so there is nothing to install:
// record-signalk.mjs — sample a Signal K server into dashboard JSON
import { writeFileSync } from 'node:fs';
const SERVER = 'ws://localhost:3000/signalk/v1/stream?subscribe=self';
const INTERVAL_MS = 60_000; // one reading per minute, like the demo log
const OUT = 'wind-log.json';
const MS_TO_KN = 1.943844; // Signal K speed unit is m/s
const TO_DEG = 180 / Math.PI; // Signal K angle unit is radians
const state = {};
const data = [];
const ws = new WebSocket(SERVER);
ws.onmessage = (ev) => {
const msg = JSON.parse(ev.data);
if (!msg.updates) return; // "hello" and heartbeats carry no values
for (const update of msg.updates) {
for (const { path, value } of update.values || []) state[path] = value;
}
};
ws.onopen = () => console.log('recording from ' + SERVER + ' — Ctrl+C stops');
setInterval(() => {
const pos = state['navigation.position'];
const sog = state['navigation.speedOverGround'];
const aws = state['environment.wind.speedApparent'];
const awa = state['environment.wind.angleApparent'];
const hdg = state['navigation.headingTrue']
?? state['navigation.headingMagnetic']
?? state['navigation.courseOverGround'];
if (!pos || typeof sog !== 'number' || typeof aws !== 'number' ||
typeof awa !== 'number' || typeof hdg !== 'number') return;
const rec = {
datetime: new Date().toISOString(),
lat: pos.latitude,
lon: pos.longitude,
sog: +(sog * MS_TO_KN).toFixed(1),
heading: Math.round(((hdg * TO_DEG) % 360 + 360) % 360),
windSpeed: +(aws * MS_TO_KN).toFixed(1),
windAngle: Math.round(awa * TO_DEG),
windGust: null // no standard Signal K gust key
};
data.push(rec);
writeFileSync(OUT, JSON.stringify({ data })); // safe to stop with Ctrl+C
console.log(rec.datetime, rec.windSpeed + ' kn at ' + rec.windAngle + '°');
}, INTERVAL_MS);
When you have enough readings, stop it with Ctrl+C and press
Upload statistic in the dashboard's data bar — wind-log.json loads
like any other file, mapping window included if your server names things differently.
Units. Signal K publishes speeds in m/s and angles in radians, so the script converts both: m/s → knots, radians → degrees. Apparent wind angle is negative to port in both conventions, so the sign is kept as is. Heading is normalised to 0–360°.
History. A server that keeps a log can replay it through the same protocol —
/signalk/v1/playback?startTime=<ISO time>&playbackRate=1 — so this script can
record a past sail without the boat leaving the dock. The REST API
(/signalk/v1/api/vessels/self) returns the current full model if you prefer to poll.
windSpeed, sog and so on, once per file.?data=<url>; the Share dialog can
reference it instead of copying the readings into the link.This dashboard does not collect, store or share your data.
#share=, which browsers do not send to servers — only people you send the link to
can open it. You can also share the view with no data at all.localStorage, under the key wind-dashboard-state.In short: the app visualises your log; it has no mechanism to take it anywhere. Close the tab and nothing of yours remains — except the file you created, which was yours from the start.