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

Seus dados

Todo pacote recebe a mesma coisa: pontos { t, v, w }, em ordem de tempo. Uma leitura de sensor, uma negociação e uma amostra de CPU entram assim, e o Nonius nunca sabe qual é qual. Este capítulo mostra como suas leituras viram pontos, e como pontos viram um arquivo de série.

Um ponto

CampoO que éTipo
to tempo: nanossegundos desde 1970-01-01 00:00 UTCum inteiro de 64 bits com sinal
vo valor: um inteiro com um número fixo de casas decimaisum inteiro de 64 bits com sinal
wo peso: 1 para uma leitura simplesum inteiro de 64 bits sem sinal

Os três são inteiros, e esse é o sentido do formato: nada passa por um número de ponto flutuante a caminho da tela, então nada é arredondado.

Tempo

Um tempo é uma contagem de nanossegundos desde a época Unix, em UTC — nunca um horário local. Uma contagem de 64 bits alcança uns 292 anos para cada lado de 1970 e guarda cada nanossegundo: uma vista de poucos nanossegundos em 2026 ainda desenha cada ponto onde ele está. Esse é o “exato em qualquer zoom” do Nonius.

Converter é uma multiplicação:

DePara nanossegundos
Segundos Unix× 1.000.000.000
Date.parse do JavaScript (milissegundos)× 1.000.000, como BigInt
DateTime do .NET (UTC)(time - DateTime.UnixEpoch).Ticks * 100
Um texto ISO 8601, num CSVo nonius-server csv lê, com Z ou um deslocamento como -03:00, e até nove casas decimais de segundo

Os pontos vêm em ordem de tempo. Dois pontos podem ter o mesmo tempo; um ponto anterior ao que veio antes é recusado, com a linha onde aconteceu.

Valores

Um valor é um inteiro, e a série diz quantas casas decimais ele tem: a sua escala (também chamada de expoente). Com duas casas, 21,37 °C é 2137; com três, uma tensão de 4,217 é 4217. A escala é um número só para a série inteira, escolhido uma vez: o máximo de casas decimais que seus valores têm.

Por que não um número de ponto flutuante? Porque nele 0.1 + 0.2 não é 0.3, e um gráfico que resume milhões de pontos levaria esses erros até os rótulos. Um inteiro é exato, e a escala só põe a vírgula de volta quando um rótulo é desenhado.

Um inteiro de 64 bits guarda até uns 9,2 × 10¹⁸: com duas casas decimais, valores até uns 92 quatrilhões. Escolher mais casas do que seus dados têm só custa alcance.

O peso

w é quanto um ponto conta. Para uma leitura, é 1. Para uma negociação, pode ser o volume. Os resumos do gráfico somam os pesos, então o resumo de um minuto sabe quanto aconteceu nele, não só a máxima e a mínima. Quando fica de fora (no encodeNsb do JavaScript, num CSV sem coluna de peso), todo peso é 1.

Lacunas

A linha liga cada ponto ao seguinte, por mais distantes que estejam. Um sensor que manda dados em rajadas, ou que ficou desligado por um dia, é desenhado como uma linha contínua, a menos que você diga onde ela quebra: chart.setSeriesGap(0, 60_000_000_000n) no navegador (o id da série, 0 para a primeira, e depois nanossegundos como BigInt), gap= na imagem de um servidor, Gap no .NET. A linha quebra sempre que um ponto vem mais que esses nanossegundos depois do anterior.

O gráfico não adivinha a lacuna: você conhece o ritmo dos seus dados, e um palpite quebraria a linha em lugares diferentes em zooms diferentes. Algumas vezes o espaçamento normal é um bom começo: para uma leitura a cada 10 segundos, um minuto mantém ligadas uma ou duas leituras perdidas e ainda quebra numa interrupção.

Fusos horários

Os dados estão sempre em UTC. Um fuso muda só os rótulos: diga qual (timeZone: "Europe/Lisbon" no navegador, tz= na imagem de um servidor, um TimeZoneInfo no .NET) e o eixo, a mira e o tooltip leem o relógio local, dos dois lados de cada entrada e saída do horário de verão.

Escrever um arquivo de série

Um arquivo de série — formato NSB1 — guarda as três colunas, a escala e uma soma de verificação. Todo pacote escreve os mesmos bytes para os mesmos pontos, pelo mesmo código Rust, então um arquivo escrito no .NET é lido pelo navegador e servido pelo nonius-server sem nenhuma etapa no meio.

De um CSV, sem código

O nonius-server transforma um CSV num arquivo de série. Cada linha é um tempo e um valor, com ou sem cabeçalho:

time,price
1788220800000000000,101.25
1788220800000001500,99.5
nonius-server csv prices.csv prices.nsb
nonius-server csv sheet.csv day.nsb --delimiter ';' --time time --value price --weight volume

A primeira linha toma a primeira coluna como tempo e a segunda como valor. A segunda dá nome às colunas da exportação de uma planilha, com ; entre os campos e vírgula decimal. Nada é arredondado: o valor é guardado com o máximo de casas decimais que algum valor do arquivo tem. As opções (--time-unit, --decimals e as outras) estão na página do nonius-server.

Do Rust

#![allow(unused)]
fn main() {
// A reading of 21.37 °C at two decimal places is 2137; w is 1 for a
// plain reading.
let points = [Point { t: 1_735_689_600_000_000_000, v: 2137, w: 1 }];
let file = File::options().read(true).write(true).create(true).truncate(true).open("day.nsb")?;
write_series(file, &points, Scale::new(2)?)?;
}

O write_series está no nonius-core, com a feature std. O arquivo é aberto para leitura e escrita: o escritor relê o que escreveu para calcular o hash.

Do .NET

(DateTime Time, decimal Celsius)[] readings =
[
    (new DateTime(2026, 3, 1, 0, 0, 0, DateTimeKind.Utc), 21.37m),
    (new DateTime(2026, 3, 1, 0, 0, 1, 500, DateTimeKind.Utc), 21.4m),
    (new DateTime(2026, 3, 1, 0, 0, 3, DateTimeKind.Utc), -0.05m),
];
// A time is nanoseconds since 1970 (UTC), and a tick is 100 of
// them. A value is an integer of the decimal places kept, here 2:
// 21.4 is 2140. w is 1 for a plain reading.
var points = readings
    .Select(r => new Point((r.Time - DateTime.UnixEpoch).Ticks * 100, (long)decimal.Round(r.Celsius * 100m), 1))
    .ToArray();
using var file = File.Create(path);
Nsb.Write(file, points, 2);

Nsb.Encode(points, 2) dá os mesmos bytes como byte[]. Os dois estão no pacote NuGet Nonius.

Do JavaScript

No Node (20.16 ou mais novo) ou no navegador, o encodeNsb do pacote npm nonius:

const readings = [
  { time: "2026-03-01T00:00:00Z", celsius: "21.37" },
  { time: "2026-03-01T00:00:01.5Z", celsius: "21.4" },
  { time: "2026-03-01T00:00:03Z", celsius: "-0.05" },
];

// A time is nanoseconds since 1970 (UTC), a BigInt. `Date` counts
// milliseconds, so a finer time needs its own parsing.
const nanoseconds = (iso) => BigInt(Date.parse(iso)) * 1_000_000n;

// A value is an integer of the decimal places you keep, here 2, read
// from its text and never through a float: "21.4" is 2140.
function hundredths(text) {
  const [whole, fraction = ""] = text.split(".");
  if (fraction.length > 2) throw new Error(`${text} has more than 2 decimal places`);
  const sign = whole.startsWith("-") ? -1n : 1n;
  return sign * (BigInt(whole.replace("-", "")) * 100n + BigInt(fraction.padEnd(2, "0")));
}

const t = BigInt64Array.from(readings, (r) => nanoseconds(r.time));
const v = BigInt64Array.from(readings, (r) => hundredths(r.celsius));
writeFileSync("readings.nsb", await encodeNsb({ t, v }, 2));

O valor é lido do texto de propósito: Math.round(21.4 * 100) acerta aqui, mas um float não guarda todo decimal, e o texto sempre guarda.

Próximo

Com um arquivo de série, escolha o caminho até o gráfico. Se ele for servido em tiles, a pirâmide é como o servidor o resume.