Drawing
One Rust core turns a view into what a frame draws, everywhere Nonius runs. In a browser it is compiled to WebAssembly and draws with WebGPU, with WebGL2 where WebGPU is missing, and on the CPU into a 2D canvas when the browser gives neither. In .NET and C it is a native library. As an image, it draws on the CPU into a PNG. Each draws the same frame: the same axes, labels, crosshair and legend, drawn by the chart itself and never laid over it as HTML.
So the choice below is only where your program lives.
In a browser
The npm package nonius draws into a <canvas>. Two rules hold in
every page: the box sizes the canvas, never the canvas’s own pixels;
and the page is served over HTTP, since a module and its WebAssembly
do not load from file://.
<!-- Names the package for the browser, as a bundler would. -->
<script type="importmap">{ "imports": { "nonius": "./node_modules/nonius/index.js" } }</script>
<style>
/* The box gives the canvas its size; the chart draws at that size in
device pixels and follows it. */
.plot { position: relative; height: 400px; }
.plot canvas { position: absolute; inset: 0; width: 100%; height: 100%; touch-action: none; }
</style>
<div class="plot"><canvas></canvas></div>
<script type="module">
import { fromPoints } from "./points.js";
await fromPoints(document.querySelector("canvas"));
</script>
That is a whole page. The import map tells the browser where "nonius"
is; with a bundler (Vite, webpack), the bundler does that instead. The
module it loads makes the chart, gives it points by any
path — the tiles’ module there is a whole one — and
lets a person explore it with interact: drag to pan, the wheel or two
fingers to zoom, hover for the crosshair. A chart fed by tiles hands
interact its tiles, interact(chart, canvas, { tiles }), so that
moving fetches what the new view needs.
The chart’s options go to createChart, and each has a method to
change it later:
createChart(canvas, { theme: "light", timeZone: "America/Sao_Paulo", title: "Estufa 2", unit: "°C" }),
then setTheme, setTimeZone(chart, name), setTitle and setUnit.
Every option, typed and documented, is in the package’s index.d.ts.
React
A chart is made asynchronously (it waits for its WebAssembly) and owns memory outside JavaScript’s, so a component makes it in an effect and frees it in the effect’s clean-up — also when the clean-up comes first, as React’s development mode does on purpose:
createChart(element).then((made) => {
if (disposed) {
made.free();
return;
}
chart = made;
chart.setScale(decimals);
chart.push(points.t, points.v, points.w);
return () => {
disposed = true;
interaction?.stop();
chart?.free();
};
Vue
The same, in onMounted and onBeforeUnmount, with one Vue-specific
rule: keep the chart in a plain variable, not a ref.
// Plain variables, not refs: Vue would wrap the chart in a proxy, and the
// chart's methods must be called on the chart itself.
let chart: Chart | undefined;
let interaction: ReturnType<typeof interact> | undefined;
Both components are whole in the package’s React and Vue examples, run with Vite and its framework’s own plug-in alone: the WebAssembly needs no plug-in of its own.
In .NET
The NuGet package Nonius holds the native core for Windows x64 and
Linux x64. On it, two controls.
Avalonia, for a desktop window (Nonius.Avalonia):
var series = Series.FromNsb(File.ReadAllBytes(nsb));
var chart = new NoniusChart { Series = series };
chart.PointPicked += (_, point) => Console.WriteLine(point);
window.Content = chart;
Blazor WebAssembly, for a page (Nonius.Blazor), which runs the
browser package underneath:
<NoniusChart @ref="chart" Nsb="day-4096.nsb" Start="First" End="Last" Theme="theme" Gap="gap"
TimeZone="zone" Title="@title" Unit="@unit"
ViewChanged="Changed" style="display: block; width: 800px; height: 400px" />
Times and values cross between .NET and the page as decimal strings, never as JavaScript numbers, which would round a nanosecond time of today to 256 ns.
From C
nonius-capi is the core behind a C interface: a header, nonius.h,
and a library to link. The .NET packages are built on it, and any
other language that can call C — Python’s ctypes, Go’s cgo, Java’s
FFM — can reach Nonius the same way. A series file drawn as a PNG:
NoniusSeries *series = NULL;
/* Summaries from one second up, nine levels. */
int32_t status = nonius_series_from_nsb(bytes, len, 1000000000, 9, &series);
if (status != NONIUS_OK) {
fprintf(stderr, "%s\n", nonius_status_message(status));
return 1;
}
Every call returns a status; nonius_status_message says it in words.
The whole program, and how to compile it with MSVC or gcc, are on
nonius-capi’s page.
As an image
A still is one frame drawn on the CPU, with no window and no GPU, encoded as a PNG: for a report, an e-mail, a server’s answer. Four ways to the same image:
| From | How |
|---|---|
| A server | GET /v1/series/{id}/png?… (Serving) |
| .NET | series.StillPng(new Still(…)) (Choose a path) |
| C | nonius_still_png(series, &still, &png) |
| Rust | nonius_render::still_png |
In Rust, a still says everything a frame needs:
#![allow(unused)]
fn main() {
let png = still_png(&series, &Still {
start: 0,
end: 1_000_000_000,
width: 640,
height: 320,
theme: Theme::dark(),
ratio: 1.0,
scale: Scale::new(2)?,
gap: None,
zone: Zone::UTC,
title: None,
unit: None,
})?;
}
width and height are the image’s own pixels; ratio is the pixel
ratio of the screen it is meant for, which scales the labels and lines,
not the image.
The same chart everywhere
Each option has the same meaning in every package, and the same default: dark, UTC, every point joined, no header.
| Browser | .NET | Server image | |
|---|---|---|---|
| Theme | theme: "light" | Theme.Light | theme=light |
| Time zone | timeZone: "Europe/Lisbon" | a TimeZoneInfo | tz=Europe/Lisbon |
| Gap | setSeriesGap(0, ns), ns a BigInt | Gap | gap= |
| Title, unit | createChart’s title, unit | Title, Unit | title=, unit= |