Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

English

Desenhar

Um núcleo Rust transforma uma vista no que um quadro desenha, em todo lugar onde o Nonius roda. No navegador, ele é compilado para WebAssembly e desenha com WebGPU, com WebGL2 onde falta WebGPU, e na CPU num canvas 2D quando o navegador não dá nenhum dos dois. No .NET e no C, é uma biblioteca nativa. Como imagem, desenha na CPU num PNG. Todos desenham o mesmo quadro: os mesmos eixos, rótulos, mira e legenda, desenhados pelo próprio gráfico e nunca sobrepostos a ele como HTML.

Então a escolha abaixo é só onde o seu programa vive.

No navegador

O pacote npm nonius desenha num <canvas>. Duas regras valem em toda página: a caixa dá o tamanho ao canvas, nunca os pixels do próprio canvas; e a página é servida por HTTP, já que um módulo e o seu WebAssembly não carregam de 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>

Essa é uma página inteira. O import map diz ao navegador onde está o "nonius"; com um bundler (Vite, webpack), o bundler faz isso. O módulo que ela carrega cria o gráfico, entrega os pontos por qualquer caminho — o módulo dos tiles ali está inteiro — e deixa a pessoa explorar com o interact: arrastar para mover, roda do mouse ou dois dedos para o zoom, passar o cursor para a mira. Um gráfico alimentado por tiles entrega os tiles ao interact, interact(chart, canvas, { tiles }), para que mover busque o que a nova vista precisa.

As opções do gráfico vão para o createChart, e cada uma tem um método para mudá-la depois: createChart(canvas, { theme: "light", timeZone: "America/Sao_Paulo", title: "Estufa 2", unit: "°C" }), e depois setTheme, setTimeZone(chart, name), setTitle e setUnit. Toda opção, tipada e documentada, está no index.d.ts do pacote.

React

O gráfico é criado de forma assíncrona (ele espera o seu WebAssembly) e tem memória fora da do JavaScript, então um componente o cria num efeito e o libera na limpeza do efeito — também quando a limpeza vem antes, como o modo de desenvolvimento do React faz de propósito:

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

O mesmo, em onMounted e onBeforeUnmount, com uma regra própria do Vue: guarde o gráfico numa variável simples, não num 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;

Os dois componentes estão inteiros nos exemplos de React e Vue do pacote, rodando com o Vite e só o plug-in do próprio framework: o WebAssembly não precisa de plug-in nenhum.

No .NET

O pacote NuGet Nonius traz o núcleo nativo para Windows x64 e Linux x64. Sobre ele, dois controles.

Avalonia, para uma janela de desktop (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, para uma página (Nonius.Blazor), que usa o pacote do navegador por baixo:

<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" />

Tempos e valores passam entre o .NET e a página como strings decimais, nunca como números JavaScript, que arredondariam um tempo em nanossegundos de hoje para 256 ns.

A partir do C

O nonius-capi é o núcleo atrás de uma interface C: um cabeçalho, o nonius.h, e uma biblioteca para ligar. Os pacotes .NET são feitos sobre ele, e qualquer outra linguagem que chama C — o ctypes do Python, o cgo do Go, o FFM do Java — chega ao Nonius do mesmo jeito. Um arquivo de série desenhado como 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;
}

Toda chamada devolve um status; nonius_status_message o diz em palavras. O programa inteiro, e como compilá-lo com MSVC ou gcc, estão na página do nonius-capi.

Como imagem

Uma still é um quadro desenhado na CPU, sem janela e sem GPU, codificado como PNG: para um relatório, um e-mail, a resposta de um servidor. Quatro caminhos para a mesma imagem:

DeComo
Um servidorGET /v1/series/{id}/png?… (Servir)
.NETseries.StillPng(new Still(…)) (Escolha o caminho)
Cnonius_still_png(series, &still, &png)
Rustnonius_render::still_png

No Rust, uma still diz tudo o que um quadro precisa:

#![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 e height são os pixels da própria imagem; ratio é a razão de pixels da tela a que ela se destina, que escala os rótulos e as linhas, não a imagem.

O mesmo gráfico em todo lugar

Cada opção tem o mesmo sentido em todo pacote, e o mesmo padrão: escuro, UTC, todo ponto ligado, sem cabeçalho.

Navegador.NETImagem do servidor
Tematheme: "light"Theme.Lighttheme=light
FusotimeZone: "Europe/Lisbon"um TimeZoneInfotz=Europe/Lisbon
LacunasetSeriesGap(0, ns), ns um BigIntGapgap=
Título, unidadetitle, unit do createChartTitle, Unittitle=, unit=