Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
aa7f400
docs: clarify agent workflow
Federicocervelli May 25, 2026
ec09318
feat: add histogram series support
Federicocervelli May 25, 2026
2b1a961
Improve axis and crosshair overlays
Federicocervelli May 28, 2026
9e64496
Keep interval highlights above crosshair lines
Federicocervelli May 28, 2026
581ecd0
Update generated docs
Federicocervelli May 28, 2026
18db445
Merge pull request #49 from Federicocervelli/fix/axis-crosshair-overlays
Federicocervelli May 28, 2026
274c992
Merge development into histogram support
Federicocervelli May 28, 2026
358241f
Merge pull request #48 from Federicocervelli/feature/histogram-support
Federicocervelli May 28, 2026
b1132a9
Render crosshair lines under chart content
Federicocervelli May 28, 2026
b880cd4
Deduplicate crosshair layer setup
Federicocervelli May 28, 2026
c2767e9
Use 1M-bin histogram preview
Federicocervelli May 28, 2026
5d85ecc
Merge pull request #50 from Federicocervelli/fix/crosshair-under-series
Federicocervelli May 28, 2026
28c0735
Prepare v0.3.13 release
Federicocervelli May 28, 2026
17d7834
Align generated docs with CI build
Federicocervelli May 28, 2026
674fd68
Stabilize generated bundle docs
Federicocervelli May 28, 2026
e446191
Stabilize README performance size
Federicocervelli May 28, 2026
7d822b1
Include interval ranges in series bounds
Federicocervelli May 28, 2026
480c771
Round generated bundle sizes
Federicocervelli May 28, 2026
cfdf9a4
Deduplicate overlay and benchmark helpers
Federicocervelli May 28, 2026
68abb24
chore: website fixups
Federicocervelli May 28, 2026
31b3bd6
Coalesce hover tooltip updates
Federicocervelli May 28, 2026
72bb47d
Merge pull request #53 from Federicocervelli/pr/cleanup-deduplicate-o…
Federicocervelli May 28, 2026
a61d7e5
refactor overlay helpers and clarify API docs
Federicocervelli May 28, 2026
7c934d1
Merge pull request #54 from Federicocervelli/cleanup/code-health-inef…
Federicocervelli May 28, 2026
3df134d
chore: prepare v0.3.14 release
Federicocervelli May 28, 2026
5744a97
Merge main into development for release
Federicocervelli May 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<img src="assets/blazeplot.png" alt="BlazePlot" width="720" />
</p>

[![Sponsor](https://img.shields.io/badge/sponsor-GitHub%20Sponsors-EA4AAA?logo=githubsponsors)](https://github.com/sponsors/Federicocervelli)
[![Sponsor](https://img.shields.io/github/sponsors/Federicocervelli?logo=githubsponsors&label=sponsor&color=EA4AAA)](https://github.com/sponsors/Federicocervelli)
[![npm version](https://img.shields.io/npm/v/blazeplot.svg)](https://www.npmjs.com/package/blazeplot)
[![npm downloads](https://img.shields.io/npm/dt/blazeplot.svg)](https://www.npmjs.com/package/blazeplot)
[![license](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
Expand Down Expand Up @@ -217,7 +217,7 @@ Generated from `dist/` after the package build.
| shared WebGL2Backend chunk | `dist/WebGL2Backend-*.js` | 22 KiB |
| shared LinkedChartsCore chunk | `dist/LinkedChartsCore-*.js` | 2 KiB |
| lazy screenshot chunk | `dist/screenshot-*.js` | 4 KiB |
| shared OverlayUtils chunk | `dist/OverlayUtils-*.js` | 3 KiB |
| shared OverlayUtils chunk | `dist/OverlayUtils-*.js` | 4 KiB |
| shared Tooltip chunk | `dist/Tooltip-*.js` | 6 KiB |
| shared Crosshair chunk | `dist/Crosshair-*.js` | 10 KiB |

Expand All @@ -228,7 +228,7 @@ Generated from `dist/index.d.ts` after the package build.
| Export | Kind | Source | JSDoc summary |
|---|---|---|---|
| `AcceleratedDataset` | interface | `./core/types` | Convenience contract for maximum-performance custom datasets. Implement this when a dataset can provide fast exact sample copies, stable viewport sampling, range min/max queries, and renderer-ready min/max buckets. |
| `AppendableDataset` | interface | `./core/types` | Dataset that accepts appended explicit X/Y samples. |
| `AppendableDataset` | interface | `./core/types` | Dataset that accepts appended X/Y samples; implementations may store X values explicitly or use them to seed implicit X spacing. |
| `AttributeSpec` | interface | `./render/types` | Vertex attribute binding for a draw call. |
| `AxisConfig` | interface | `./ui/Chart` | Axis visibility, placement, scale, tick formatting, and title options. |
| `AxisController` | class | `./interaction/AxisController` | Computes axis tick values and labels for a camera. |
Expand Down Expand Up @@ -317,7 +317,7 @@ Generated from `dist/index.d.ts` after the package build.
| `PrecomputedHistogramSeriesConfig` | interface | `./ui/Chart` | Series configuration for `Chart.addHistogram(...)` from precomputed bins. |
| `RangeMinMaxDataset` | interface | `./core/types` | Dataset that can answer min/max Y queries for index ranges. |
| `RangeSampleCopyDataset` | interface | `./core/types` | Optional high-performance extraction capability for datasets that can copy raw samples without going through repeated getX/getY calls. Implement this for very large datasets, implicit-X datasets, or remote/memory-mapped sources. |
| `ReglBackend` | const | `./render/WebGL2Backend` | Deprecated alias for WebGL2Backend. This preserves the pre-native-backend public API. Deprecated: Effective next patch release. Use WebGL2Backend. |
| `ReglBackend` | const | `./render/WebGL2Backend` | Deprecated alias for WebGL2Backend. This preserves the pre-native-backend public API. Deprecated: Use WebGL2Backend; removal target: 0.4.0. |
| `ResolvedChartTheme` | interface | `./ui/theme` | Fully resolved chart theme with concrete RGBA values. |
| `RgbaColor` | type | `./ui/theme` | RGBA color tuple with 0-1 channel values. |
| `RingBuffer` | class | `./core/RingBuffer` | Fixed-capacity sorted XY buffer for explicit X values. |
Expand Down
31 changes: 31 additions & 0 deletions changelogs/v0.3.14.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# BlazePlot v0.3.14

## Changes

- Refined overlay helper implementations while preserving optional plugin bundle boundaries.
- Clarified public API documentation for implicit-X appendable datasets, data exports, React option identity, and deprecated compatibility aliases.
- Regenerated README/API documentation and bundle-size tables for the patch release.

## Benchmarks

## 2026-05-28T22:55:27.769Z

Command: `bun run bench:report --scenario ci-smoke --measure-ms 750 --warmup-ms 100 --width 800 --height 450 --top 12 --setup-timeout-ms 60000 --out-md changelogs/v0.3.14.md`

| Scenario | Browser | Canvas | Renderer | RAF FPS | RAF p95 ms | Chart p50 ms | Chart p95 ms | Points | Draws | Batched | Upload KB |
|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
| ci-smoke | brave | 748x341 | mixed | 60.0 | 16.80 | 2.30 | 4.20 | 5,265 | 4 | 0 | 41.4 |

### CPU hot spots: ci-smoke

| Function | Self ms | Total ms | Location |
|---|---:|---:|---|
| (idle) | 515.6 | 515.6 | runtime |
| updateAxis | 83.2 | 86.6 | AxisOverlay.ts:86 |
| (program) | 75.7 | 75.7 | runtime |
| measure | 24.7 | 53.8 | main.ts:287 |
| appendRange | 12.2 | 24.3 | main.ts:360 |
| (garbage collector) | 5.8 | 5.8 | runtime |
| requestAnimationFrame | 5.1 | 5.1 | runtime |
| queryPhysicalMinMax | 5.0 | 5.0 | RingBuffer.ts:205 |

6 changes: 3 additions & 3 deletions docs/api-reference.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "blazeplot",
"version": "0.3.13",
"version": "0.3.14",
"packageManager": "bun@1.3.14",
"description": "Real-time LOD time series rendering engine for the browser.",
"keywords": [
Expand Down
9 changes: 4 additions & 5 deletions scripts/benchmark-compare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { existsSync } from "node:fs";
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { arch, cpus, platform, release, tmpdir, totalmem } from "node:os";
import { basename, join, resolve } from "node:path";
import officialConfig from "./benchmark-config.json";

interface Options {
scenarios: string[];
Expand Down Expand Up @@ -143,11 +144,9 @@ interface LibraryInfo {
version: string;
}

const OFFICIAL_SCENARIOS = ["line-100k-static", "line-1m-static", "line-1m-pan", "line-1m-stream", "line-10m-accelerated-pan"] as const;
const OFFICIAL_LIBRARIES = ["blazeplot", "uplot", "chartjs"] as const;
const RUNTIME_COMPARISONS = [
{ primaryLibrary: "blazeplot", referenceLibrary: "uplot" },
] as const;
const OFFICIAL_SCENARIOS = officialConfig.scenarios;
const OFFICIAL_LIBRARIES = officialConfig.libraries;
const RUNTIME_COMPARISONS = officialConfig.runtimeComparisons;

interface CompareReport {
schemaVersion: 1;
Expand Down
7 changes: 7 additions & 0 deletions scripts/benchmark-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"libraries": ["blazeplot", "uplot", "chartjs"],
"scenarios": ["line-100k-static", "line-1m-static", "line-1m-pan", "line-1m-stream", "line-10m-accelerated-pan"],
"runtimeComparisons": [
{ "primaryLibrary": "blazeplot", "referenceLibrary": "uplot" }
]
}
2 changes: 1 addition & 1 deletion scripts/bundle-size-check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ function printHelpAndExit(): never {
}

function displayPath(path: string): string {
return path.replace(/-[A-Za-z0-9_]+\.js$/u, "-*.js");
return path.replace(/-[A-Za-z0-9_-]+\.js$/u, "-*.js");
}

function formatBytes(bytes: number): string {
Expand Down
9 changes: 4 additions & 5 deletions scripts/generate-readme-docs.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,10 @@ const docsStartMarker = "<!-- README_DOCS_START -->";
const docsEndMarker = "<!-- README_DOCS_END -->";
const performanceStartMarker = "<!-- README_PERFORMANCE_START -->";
const performanceEndMarker = "<!-- README_PERFORMANCE_END -->";
const officialComparisonLibraries = ["blazeplot", "uplot", "chartjs"];
const officialComparisonScenarios = ["line-100k-static", "line-1m-static", "line-1m-pan", "line-1m-stream", "line-10m-accelerated-pan"];
const runtimeComparisonPairs = [
{ primaryLibrary: "blazeplot", referenceLibrary: "uplot" },
];
const officialComparisonConfig = JSON.parse(readFileSync(resolve(root, "scripts/benchmark-config.json"), "utf-8"));
const officialComparisonLibraries = officialComparisonConfig.libraries;
const officialComparisonScenarios = officialComparisonConfig.scenarios;
const runtimeComparisonPairs = officialComparisonConfig.runtimeComparisons;

const args = new Set(process.argv.slice(2));
const check = args.has("--check");
Expand Down
14 changes: 2 additions & 12 deletions src/core/Histogram.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { upperBoundArray } from "./search.js";
import { StaticDataset } from "./StaticDataset.js";
import type { XRange, XRangeDataset } from "./types.js";

Expand Down Expand Up @@ -130,7 +131,7 @@ export function histogram(values: ArrayLike<number>, options: HistogramOptions =
if (binIndex < 0) binIndex = 0;
if (binIndex >= counted.length) binIndex = counted.length - 1;
} else {
binIndex = upperBound(edges.edges, value) - 1;
binIndex = upperBoundArray(edges.edges, value) - 1;
}

counted[binIndex]!.count++;
Expand Down Expand Up @@ -362,17 +363,6 @@ function inferUniformWidth(edges: readonly number[]): number | null {
return firstWidth;
}

function upperBound(values: readonly number[], needle: number): number {
let low = 0;
let high = values.length;
while (low < high) {
const mid = low + ((high - low) >> 1);
if (values[mid]! <= needle) low = mid + 1;
else high = mid;
}
return low;
}

function hasExplicitEdgeThresholds(options: HistogramOptions): boolean {
return Array.isArray(options.thresholds) || isReadonlyNumberArray(options.thresholds);
}
Expand Down
8 changes: 4 additions & 4 deletions src/core/SeriesStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ export class SeriesStore {
/** Append XY, implicit-X, OHLC, or row-array data and request an on-demand render. */
append(data: SeriesAppendData): void;
/**
* @deprecated Effective next patch release. Use `append({ x, y })` instead.
* @deprecated Use `append({ x, y })` instead; removal target: 0.4.0.
*/
append(x: ArrayLike<number>, y: ArrayLike<number>): void;
/** Append data in the supported object, row, or legacy array forms. */
Expand Down Expand Up @@ -326,7 +326,7 @@ export class SeriesStore {
}

/**
* @deprecated Effective next patch release. Use `append({ y })` instead.
* @deprecated Use `append({ y })` instead; removal target: 0.4.0.
*/
appendY(y: ArrayLike<number>): void {
this.appendYArray(y);
Expand All @@ -342,7 +342,7 @@ export class SeriesStore {
}

/**
* @deprecated Effective next patch release. Use `append({ x, open, high, low, close })` instead.
* @deprecated Use `append({ x, open, high, low, close })` instead; removal target: 0.4.0.
*/
appendOhlc(
x: ArrayLike<number>,
Expand Down Expand Up @@ -421,7 +421,7 @@ export class SeriesStore {
}

/**
* @deprecated Effective next patch release. Use `updateLast({ open, high, low, close })` instead.
* @deprecated Use `updateLast({ open, high, low, close })` instead; removal target: 0.4.0.
*/
updateLastOhlc(open: number, high: number, low: number, close: number): boolean {
return this.updateOhlcAt(this.dataset.length - 1, open, high, low, close);
Expand Down
5 changes: 5 additions & 0 deletions src/core/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,8 @@ export function upperBound(length: number, valueAt: (index: number) => number, v
}
return lo;
}

/** Return the first array index whose value is greater than `value`. */
export function upperBoundArray(values: readonly number[], value: number): number {
return upperBound(values.length, (index) => values[index]!, value);
}
2 changes: 1 addition & 1 deletion src/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ export interface OhlcDataset extends Dataset {
getClose(index: number): number;
}

/** Dataset that accepts appended explicit X/Y samples. */
/** Dataset that accepts appended X/Y samples; implementations may store X values explicitly or use them to seed implicit X spacing. */
export interface AppendableDataset extends Dataset {
push(x: number, y: number): void;
append(x: ArrayLike<number>, y: ArrayLike<number>): void;
Expand Down
2 changes: 1 addition & 1 deletion src/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ export function exportSelectedChartData(
}, options);
}

/** Collect all raw samples from chart series, optionally constrained by options.series and visibleOnly. */
/** Collect raw samples from visible chart series by default; pass visibleOnly: false to include hidden series. */
export function exportAllChartData(chart: Chart, options: ChartDataExportOptions = {}): ChartDataExport {
return collectChartData(chart, { source: "all", bounds: null }, options);
}
Expand Down
1 change: 1 addition & 0 deletions src/react.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { ChartOptions } from "./ui/Chart.js";

/** Props for the React chart host component. */
export interface BlazeChartProps {
/** Chart options used at construction time. Changing object identity disposes and recreates the chart; keep stable with useMemo. */
readonly options?: ChartOptions;
readonly className?: string;
readonly style?: React.CSSProperties;
Expand Down
2 changes: 1 addition & 1 deletion src/render/WebGL2Backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,6 @@ export class WebGL2Backend implements GpuBackend {

/**
* Deprecated alias for WebGL2Backend. This preserves the pre-native-backend public API.
* @deprecated Effective next patch release. Use WebGL2Backend.
* @deprecated Use WebGL2Backend; removal target: 0.4.0.
*/
export const ReglBackend: typeof WebGL2Backend = WebGL2Backend;
22 changes: 11 additions & 11 deletions src/ui/Annotations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ export interface AnnotationsPlugin extends ChartPlugin {

const SVG_NS = "http://www.w3.org/2000/svg";

function svg<K extends keyof SVGElementTagNameMap>(tag: K): SVGElementTagNameMap[K] {
function createSvgElement<K extends keyof SVGElementTagNameMap>(tag: K): SVGElementTagNameMap[K] {
return document.createElementNS(SVG_NS, tag);
}

Expand Down Expand Up @@ -316,7 +316,7 @@ export function annotationsPlugin(options: AnnotationsPluginOptions = {}): Annot
return {
install(chart: ChartPluginContext) {
chartRef = chart;
overlay = svg("svg");
overlay = createSvgElement("svg");
overlay.classList.add(options.className ?? "blazeplot-annotations");
overlay.style.position = "absolute";
overlay.style.inset = "0";
Expand Down Expand Up @@ -420,14 +420,14 @@ function drawAnnotation(
const viewport = chart.getViewport(annotation.yAxis ?? "left");
const xToPx = (x: number): number => ((x - viewport.xMin) / (viewport.xMax - viewport.xMin)) * width;
const yToPx = (y: number): number => ((viewport.yMax - y) / (viewport.yMax - viewport.yMin)) * height;
const group = svg("g");
const group = createSvgElement("g");
if (annotation.className) group.classList.add(annotation.className);

switch (annotation.type) {
case "x-line": {
const x = xToPx(annotation.x);
if (x < 0 || x > width) return;
const line = svg("line");
const line = createSvgElement("line");
line.setAttribute("x1", String(x));
line.setAttribute("x2", String(x));
line.setAttribute("y1", "0");
Expand All @@ -440,7 +440,7 @@ function drawAnnotation(
case "y-line": {
const y = yToPx(annotation.y);
if (y < 0 || y > height) return;
const line = svg("line");
const line = createSvgElement("line");
line.setAttribute("x1", "0");
line.setAttribute("x2", String(width));
line.setAttribute("y1", String(y));
Expand Down Expand Up @@ -500,7 +500,7 @@ function styleStroke(el: SVGElement, color: string, width: number = 1, dash?: st
}

function appendRect(group: SVGGElement, rect: { x: number; y: number; w: number; h: number }, fill: string, stroke?: string, strokeWidth: number = 0): void {
const el = svg("rect");
const el = createSvgElement("rect");
el.setAttribute("x", String(rect.x));
el.setAttribute("y", String(rect.y));
el.setAttribute("width", String(rect.w));
Expand All @@ -518,7 +518,7 @@ function appendMarker(group: SVGGElement, x: number, y: number, radius: number,
const stroke = annotation.strokeColor ?? "rgba(0,0,0,0.35)";
const strokeWidth = annotation.strokeWidth ?? 1;
if (annotation.shape === "diamond") {
const polygon = svg("polygon");
const polygon = createSvgElement("polygon");
polygon.setAttribute("points", `${x},${y - radius} ${x + radius},${y} ${x},${y + radius} ${x - radius},${y}`);
polygon.setAttribute("fill", fill);
polygon.setAttribute("stroke", stroke);
Expand All @@ -529,7 +529,7 @@ function appendMarker(group: SVGGElement, x: number, y: number, radius: number,

if (annotation.shape === "cross") {
for (const [x1, y1, x2, y2] of [[x - radius, y, x + radius, y], [x, y - radius, x, y + radius]] as const) {
const line = svg("line");
const line = createSvgElement("line");
line.setAttribute("x1", String(x1));
line.setAttribute("y1", String(y1));
line.setAttribute("x2", String(x2));
Expand All @@ -540,7 +540,7 @@ function appendMarker(group: SVGGElement, x: number, y: number, radius: number,
return;
}

const circle = svg("circle");
const circle = createSvgElement("circle");
circle.setAttribute("cx", String(x));
circle.setAttribute("cy", String(y));
circle.setAttribute("r", String(radius));
Expand All @@ -553,7 +553,7 @@ function appendMarker(group: SVGGElement, x: number, y: number, radius: number,
function appendStandaloneLabel(group: SVGGElement, annotation: LabelAnnotation, x: number, y: number, defaultColor: string, defaultFont: string): void {
const text = appendText(group, annotation.text, x, y, "start", annotation.color ?? defaultColor, annotation.font ?? defaultFont);
if (annotation.backgroundColor) {
const rect = svg("rect");
const rect = createSvgElement("rect");
rect.setAttribute("x", String(x - 4));
rect.setAttribute("y", String(y - 14));
rect.setAttribute("width", String(Math.max(16, annotation.text.length * 7 + 8)));
Expand All @@ -572,7 +572,7 @@ function appendLabel(group: SVGGElement, label: string | AnnotationLabelOptions
}

function appendText(group: SVGGElement, textValue: string, x: number, y: number, anchor: "start" | "middle" | "end", color: string, font: string): SVGTextElement {
const text = svg("text");
const text = createSvgElement("text");
text.textContent = textValue;
text.setAttribute("x", String(x));
text.setAttribute("y", String(y));
Expand Down
Loading