From b1132a98519764b6e3a1d24a0e04e770f58610bd Mon Sep 17 00:00:00 2001 From: Federico Cervelli Date: Thu, 28 May 2026 20:36:57 +0200 Subject: [PATCH 1/3] Render crosshair lines under chart content --- src/ui/Chart.ts | 3 +-- src/ui/ChartLayout.ts | 1 + src/ui/Crosshair.ts | 41 ++++++++++++++++++++++++++++++----------- 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/src/ui/Chart.ts b/src/ui/Chart.ts index f55168f..5cf2b9a 100644 --- a/src/ui/Chart.ts +++ b/src/ui/Chart.ts @@ -1383,9 +1383,8 @@ export class Chart implements ChartPluginContext { this.applyAutoFitYPolicy(); try { - const [r, g, b, a] = this.resolvedTheme.backgroundColor; this.renderer.viewport(0, 0, this.canvas.width, this.canvas.height); - this.renderer.clear(r, g, b, a); + this.renderer.clear(0, 0, 0, 0); const viewport = this.camera.viewport; this.currentXOrigin = viewport.xMin; diff --git a/src/ui/ChartLayout.ts b/src/ui/ChartLayout.ts index 46c4586..e64ee13 100644 --- a/src/ui/ChartLayout.ts +++ b/src/ui/ChartLayout.ts @@ -173,6 +173,7 @@ export class ChartLayout implements ChartLayoutElements { this.canvas.style.position = "absolute"; this.canvas.style.inset = "0"; + this.canvas.style.zIndex = "1"; this.canvas.style.display = "block"; this.canvas.style.width = "100%"; this.canvas.style.height = "100%"; diff --git a/src/ui/Crosshair.ts b/src/ui/Crosshair.ts index 4b261fe..29790b2 100644 --- a/src/ui/Crosshair.ts +++ b/src/ui/Crosshair.ts @@ -189,6 +189,8 @@ export function crosshairPlugin(options: CrosshairPluginOptions = {}): Crosshair const rulerModifier = options.rulerModifier ?? "none"; let chartRef: ChartPluginContext | null = null; let root: HTMLDivElement | null = null; + let lineLayer: HTMLDivElement | null = null; + let overlayLayer: HTMLDivElement | null = null; let vertical: HTMLDivElement | null = null; let horizontal: HTMLDivElement | null = null; let markerLayer: HTMLDivElement | null = null; @@ -208,7 +210,8 @@ export function crosshairPlugin(options: CrosshairPluginOptions = {}): Crosshair const measureEndSubscribers = new Set<(measurement: RulerMeasurement) => void>(); const setVisible = (visible: boolean): void => { - if (root) root.style.display = visible ? "block" : "none"; + if (lineLayer) lineLayer.style.display = visible ? "block" : "none"; + if (overlayLayer) overlayLayer.style.display = visible ? "block" : "none"; }; const emitMove = (position: CrosshairPosition | null): void => { @@ -271,7 +274,7 @@ export function crosshairPlugin(options: CrosshairPluginOptions = {}): Crosshair const renderPosition = (position: CrosshairPosition | null): void => { renderMarkers(position); - if (!position || !root || !vertical || !horizontal || !label) { + if (!position || !lineLayer || !overlayLayer || !vertical || !horizontal || !label) { setVisible(false); return; } @@ -354,11 +357,24 @@ export function crosshairPlugin(options: CrosshairPluginOptions = {}): Crosshair root = document.createElement("div"); root.className = "blazeplot-crosshair"; - root.style.position = "absolute"; - root.style.inset = "0"; - root.style.display = "none"; + root.style.display = "contents"; root.style.pointerEvents = "none"; - root.style.zIndex = String(options.zIndex ?? 22); + + lineLayer = document.createElement("div"); + lineLayer.className = "blazeplot-crosshair-lines"; + lineLayer.style.position = "absolute"; + lineLayer.style.inset = "0"; + lineLayer.style.display = "none"; + lineLayer.style.pointerEvents = "none"; + lineLayer.style.zIndex = String(options.zIndex ?? 0); + + overlayLayer = document.createElement("div"); + overlayLayer.className = "blazeplot-crosshair-overlay"; + overlayLayer.style.position = "absolute"; + overlayLayer.style.inset = "0"; + overlayLayer.style.display = "none"; + overlayLayer.style.pointerEvents = "none"; + overlayLayer.style.zIndex = String(options.zIndex ?? 22); vertical = document.createElement("div"); vertical.style.position = "absolute"; @@ -407,11 +423,12 @@ export function crosshairPlugin(options: CrosshairPluginOptions = {}): Crosshair if (dash) rulerLine.setAttribute("stroke-dasharray", dash); rulerSvg.appendChild(rulerLine); - root.appendChild(vertical); - root.appendChild(horizontal); - root.appendChild(rulerSvg); - root.appendChild(markerLayer); - root.appendChild(label); + lineLayer.appendChild(vertical); + lineLayer.appendChild(horizontal); + lineLayer.appendChild(rulerSvg); + overlayLayer.appendChild(markerLayer); + overlayLayer.appendChild(label); + root.append(lineLayer, overlayLayer); chart.plotElement.appendChild(root); if (options.group) { @@ -507,6 +524,8 @@ export function crosshairPlugin(options: CrosshairPluginOptions = {}): Crosshair } root?.remove(); root = null; + lineLayer = null; + overlayLayer = null; vertical = null; horizontal = null; markerLayer = null; From b880cd4e2b0c02f13d5a4bdd2765797f0f1bd398 Mon Sep 17 00:00:00 2001 From: Federico Cervelli Date: Thu, 28 May 2026 20:38:31 +0200 Subject: [PATCH 2/3] Deduplicate crosshair layer setup --- README.md | 6 +++--- docs/api-reference.md | 6 +++--- src/ui/Crosshair.ts | 28 +++++++++++++--------------- 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 1c061a5..4c7776d 100644 --- a/README.md +++ b/README.md @@ -210,16 +210,16 @@ Generated from `dist/` after the package build. | tooltip plugin entry | `dist/plugins/tooltip.js` | 0.1 KiB | | crosshair plugin entry | `dist/plugins/crosshair.js` | 0.1 KiB | | flamegraph plugin | `dist/plugins/flamegraph.js` | 20.7 KiB | -| shared Chart chunk | `dist/Chart-DstwTTQs.js` | 57.2 KiB | +| shared Chart chunk | `dist/Chart-DniudEGP.js` | 57.2 KiB | | shared streaming data chunk | `dist/UniformRingBuffer-DVJiaja6.js` | 44.0 KiB | | shared OhlcDataset chunk | `dist/OhlcDataset-BzaK030U.js` | 8.6 KiB | | shared AxisController chunk | `dist/AxisController-B5zX7JBz.js` | 13.8 KiB | | shared WebGL2Backend chunk | `dist/WebGL2Backend-DDGRc0UJ.js` | 22.0 KiB | -| shared LinkedChartsCore chunk | `dist/LinkedChartsCore-zxImWpgT.js` | 2.1 KiB | +| shared LinkedChartsCore chunk | `dist/LinkedChartsCore-BLcR3ldB.js` | 2.1 KiB | | lazy screenshot chunk | `dist/screenshot-CljRIqNW.js` | 3.5 KiB | | shared OverlayUtils chunk | `dist/OverlayUtils-YP9KSKL4.js` | 3.1 KiB | | shared Tooltip chunk | `dist/Tooltip-a9NkZoPg.js` | 5.8 KiB | -| shared Crosshair chunk | `dist/Crosshair-C426MhNr.js` | 9.9 KiB | +| shared Crosshair chunk | `dist/Crosshair-Dk4xAusn.js` | 10.3 KiB | ### All public exports diff --git a/docs/api-reference.md b/docs/api-reference.md index bc74657..32b2210 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -67,16 +67,16 @@ Generated from `dist/` after the package build. | tooltip plugin entry | `dist/plugins/tooltip.js` | 0.1 KiB | | crosshair plugin entry | `dist/plugins/crosshair.js` | 0.1 KiB | | flamegraph plugin | `dist/plugins/flamegraph.js` | 20.7 KiB | -| shared Chart chunk | `dist/Chart-DstwTTQs.js` | 57.2 KiB | +| shared Chart chunk | `dist/Chart-DniudEGP.js` | 57.2 KiB | | shared streaming data chunk | `dist/UniformRingBuffer-DVJiaja6.js` | 44.0 KiB | | shared OhlcDataset chunk | `dist/OhlcDataset-BzaK030U.js` | 8.6 KiB | | shared AxisController chunk | `dist/AxisController-B5zX7JBz.js` | 13.8 KiB | | shared WebGL2Backend chunk | `dist/WebGL2Backend-DDGRc0UJ.js` | 22.0 KiB | -| shared LinkedChartsCore chunk | `dist/LinkedChartsCore-zxImWpgT.js` | 2.1 KiB | +| shared LinkedChartsCore chunk | `dist/LinkedChartsCore-BLcR3ldB.js` | 2.1 KiB | | lazy screenshot chunk | `dist/screenshot-CljRIqNW.js` | 3.5 KiB | | shared OverlayUtils chunk | `dist/OverlayUtils-YP9KSKL4.js` | 3.1 KiB | | shared Tooltip chunk | `dist/Tooltip-a9NkZoPg.js` | 5.8 KiB | -| shared Crosshair chunk | `dist/Crosshair-C426MhNr.js` | 9.9 KiB | +| shared Crosshair chunk | `dist/Crosshair-Dk4xAusn.js` | 10.3 KiB | ### All public exports diff --git a/src/ui/Crosshair.ts b/src/ui/Crosshair.ts index 29790b2..08789d7 100644 --- a/src/ui/Crosshair.ts +++ b/src/ui/Crosshair.ts @@ -159,6 +159,17 @@ function createXRangeHighlight(item: ChartPickItem, chart: Chart, strokeColor: s return marker; } +function createCrosshairLayer(className: string, zIndex: number): HTMLDivElement { + const layer = document.createElement("div"); + layer.className = className; + layer.style.position = "absolute"; + layer.style.inset = "0"; + layer.style.display = "none"; + layer.style.pointerEvents = "none"; + layer.style.zIndex = String(zIndex); + return layer; +} + function renderDefaultLabel( position: CrosshairPosition, container: HTMLElement, @@ -360,21 +371,8 @@ export function crosshairPlugin(options: CrosshairPluginOptions = {}): Crosshair root.style.display = "contents"; root.style.pointerEvents = "none"; - lineLayer = document.createElement("div"); - lineLayer.className = "blazeplot-crosshair-lines"; - lineLayer.style.position = "absolute"; - lineLayer.style.inset = "0"; - lineLayer.style.display = "none"; - lineLayer.style.pointerEvents = "none"; - lineLayer.style.zIndex = String(options.zIndex ?? 0); - - overlayLayer = document.createElement("div"); - overlayLayer.className = "blazeplot-crosshair-overlay"; - overlayLayer.style.position = "absolute"; - overlayLayer.style.inset = "0"; - overlayLayer.style.display = "none"; - overlayLayer.style.pointerEvents = "none"; - overlayLayer.style.zIndex = String(options.zIndex ?? 22); + lineLayer = createCrosshairLayer("blazeplot-crosshair-lines", options.zIndex ?? 0); + overlayLayer = createCrosshairLayer("blazeplot-crosshair-overlay", options.zIndex ?? 22); vertical = document.createElement("div"); vertical.style.position = "absolute"; From c2767e921122503e524eccc953ee9e52dfc5d669 Mon Sep 17 00:00:00 2001 From: Federico Cervelli Date: Thu, 28 May 2026 20:40:07 +0200 Subject: [PATCH 3/3] Use 1M-bin histogram preview --- website/src/site/previews-controller.ts | 50 ++++++++++++++++++------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/website/src/site/previews-controller.ts b/website/src/site/previews-controller.ts index 75693b7..57d8b4b 100644 --- a/website/src/site/previews-controller.ts +++ b/website/src/site/previews-controller.ts @@ -1,5 +1,5 @@ import { type ReactiveController, type ReactiveControllerHost } from "lit"; -import { Chart, OhlcRingBuffer, ServerSampledDataset, StaticDataset, UniformRingBuffer, type ChartFrameStats, type ChartPickGroup, type ChartPickMode, type ChartTheme, type SeriesStore, type ViewportPolicy } from "../../../src/index.ts"; +import { Chart, OhlcRingBuffer, ServerSampledDataset, StaticDataset, UniformRingBuffer, type ChartFrameStats, type ChartPickGroup, type ChartPickMode, type ChartTheme, type HistogramBin, type HistogramResult, type SeriesStore, type ViewportPolicy } from "../../../src/index.ts"; import { createLinkedCharts } from "../../../src/linked.ts"; import { annotationsPlugin } from "../../../src/plugins/annotations.ts"; import { crosshairPlugin } from "../../../src/plugins/crosshair.ts"; @@ -589,35 +589,57 @@ export class PreviewChartsController implements ReactiveController { } private mountHistogramPreview(target: HTMLElement): void { - const values = new Float64Array(18_000); - for (let index = 0; index < values.length; index++) { - const phase = index / values.length; - const cluster = index % 9; - const baseline = cluster < 5 ? 70 : cluster < 8 ? 115 : 165; - const seasonal = Math.sin(phase * Math.PI * 10) * 8 + Math.sin(index * 0.017) * 5; - const jitter = (Math.sin(index * 12.9898) * 43758.5453 % 1) * 10; - values[index] = baseline + seasonal + jitter + (index % 997 === 0 ? 85 : 0); + const binCount = 1_000_000; + const initialBins = 1_000; + const x = new Float64Array(binCount); + const y = new Float32Array(binCount); + const bins = new Array(binCount); + let total = 0; + let initialYMax = 0; + for (let index = 0; index < binCount; index++) { + const wave = 80 + + Math.sin(index * 0.00031) * 34 + + Math.sin(index * 0.017) * 18 + + Math.sin(index * 0.071) * 9; + const burst = index % 12_001 < 120 ? 70 * (1 - (index % 12_001) / 120) : 0; + const count = Math.max(1, Math.round(wave + burst + (index % 997 === 0 ? 90 : 0))); + x[index] = index + 0.5; + y[index] = count; + bins[index] = { x: index + 0.5, y: count, xStart: index, xEnd: index + 1, count, index }; + total += count; + if (index < initialBins && count > initialYMax) initialYMax = count; } + const histogram: HistogramResult = { + bins, + x, + y, + binWidth: 1, + total, + underflow: 0, + overflow: 0, + invalid: 0, + min: 0, + max: binCount, + }; const chart = new Chart(target, { - axes: { x: { position: "outside", title: "latency (ms)" }, y: { position: "outside", title: "density" } }, + axes: { x: { position: "outside", title: "bin" }, y: { position: "outside", title: "count" } }, grid: true, hover: { mode: "nearest-x", group: "none" }, plugins: [ interactionsPlugin({ wheelZoom: true, shiftDragPan: true, boxZoom: true, doubleClickReset: true }), - crosshairPlugin({ snap: "nearest-x", label: true, labelPlacement: "top-right", formatX: (value) => `${value.toFixed(0)} ms`, formatY: (value) => value.toFixed(4) }), + crosshairPlugin({ snap: "nearest-x", label: true, labelPlacement: "top-right", formatX: (value) => `bin ${Math.floor(value).toLocaleString()}`, formatY: (value) => value.toFixed(0) }), legendPlugin({ position: "top-left" }), ], accessibility: { label: "Histogram preview" }, }); this.previewCharts.push(chart); - chart.addHistogram({ values, binSize: 5, min: 40, max: 260, normalize: "density", name: "Latency density", downsample: "none" }, { + chart.addHistogram({ histogram, name: "1M bins" }, { color: [0.988, 0.29, 0.02, 0.95], baseline: 0, }); - chart.fitToData({ includeZero: true, padding: { y: 0.12 } }); - chart.setViewport({ ...chart.getViewport(), xMin: 40, xMax: 260, yMin: 0 }); + chart.setViewport({ xMin: 0, xMax: initialBins, yMin: 0, yMax: initialYMax * 1.12 }); chart.start(); }