diff --git a/README.md b/README.md index 99537a9..cab3f6d 100644 --- a/README.md +++ b/README.md @@ -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. | @@ -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. | diff --git a/docs/api-reference.md b/docs/api-reference.md index ada5fea..5cce639 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -85,7 +85,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. | @@ -174,7 +174,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. | diff --git a/src/core/SeriesStore.ts b/src/core/SeriesStore.ts index a666409..a58d7f4 100644 --- a/src/core/SeriesStore.ts +++ b/src/core/SeriesStore.ts @@ -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, y: ArrayLike): void; /** Append data in the supported object, row, or legacy array forms. */ @@ -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): void { this.appendYArray(y); @@ -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, @@ -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); diff --git a/src/core/types.ts b/src/core/types.ts index f9384b3..b25f8c2 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -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, y: ArrayLike): void; diff --git a/src/data.ts b/src/data.ts index fdbae57..9a7fb8b 100644 --- a/src/data.ts +++ b/src/data.ts @@ -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); } diff --git a/src/react.ts b/src/react.ts index 73a220b..8e1dedd 100644 --- a/src/react.ts +++ b/src/react.ts @@ -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; diff --git a/src/render/WebGL2Backend.ts b/src/render/WebGL2Backend.ts index e2a1d27..000bfd3 100644 --- a/src/render/WebGL2Backend.ts +++ b/src/render/WebGL2Backend.ts @@ -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; diff --git a/src/ui/Annotations.ts b/src/ui/Annotations.ts index 956d5dc..9070767 100644 --- a/src/ui/Annotations.ts +++ b/src/ui/Annotations.ts @@ -156,7 +156,7 @@ export interface AnnotationsPlugin extends ChartPlugin { const SVG_NS = "http://www.w3.org/2000/svg"; -function svg(tag: K): SVGElementTagNameMap[K] { +function createSvgElement(tag: K): SVGElementTagNameMap[K] { return document.createElementNS(SVG_NS, tag); } @@ -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"; @@ -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"); @@ -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)); @@ -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)); @@ -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); @@ -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)); @@ -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)); @@ -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))); @@ -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)); diff --git a/src/ui/Crosshair.ts b/src/ui/Crosshair.ts index ec322ee..269f8df 100644 --- a/src/ui/Crosshair.ts +++ b/src/ui/Crosshair.ts @@ -14,7 +14,7 @@ export type CrosshairLabelPlacement = "bottom-right" | "top-right" | "bottom-lef /** Custom renderer for crosshair pick highlights. */ export type CrosshairHighlightRenderer = (position: CrosshairPosition, container: HTMLElement, chart: Chart) => void; -/** Crosshair position in client and data coordinates. */ +/** Crosshair position in data coordinates and plot-relative CSS pixels. */ export interface CrosshairPosition { readonly dataX: number; readonly dataY: number; @@ -88,6 +88,12 @@ export interface CrosshairPlugin extends ChartPlugin { subscribe(event: "measurechange" | "measureend", callback: (measurement: RulerMeasurement) => void): () => void; } +const SVG_NS = "http://www.w3.org/2000/svg"; + +function createSvgElement(tag: K): SVGElementTagNameMap[K] { + return document.createElementNS(SVG_NS, tag); +} + function countSamplesInRange(chart: ChartPluginContext, xMin: number, xMax: number): number { const viewport = { xMin, xMax, yMin: -Infinity, yMax: Infinity }; let total = 0; @@ -394,7 +400,7 @@ export function crosshairPlugin(options: CrosshairPluginOptions = {}): Crosshair label.style.font = options.labelFont ?? chart.theme.tooltipFont; label.style.whiteSpace = "nowrap"; - rulerSvg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + rulerSvg = createSvgElement("svg"); rulerSvg.style.position = "absolute"; rulerSvg.style.inset = "0"; rulerSvg.style.width = "100%"; @@ -402,7 +408,7 @@ export function crosshairPlugin(options: CrosshairPluginOptions = {}): Crosshair rulerSvg.style.display = "none"; rulerSvg.style.overflow = "hidden"; rulerSvg.style.zIndex = "1"; - rulerLine = document.createElementNS("http://www.w3.org/2000/svg", "line"); + rulerLine = createSvgElement("line"); rulerLine.setAttribute("stroke", color); rulerLine.setAttribute("stroke-width", String(options.width ?? 1)); if (dash) rulerLine.setAttribute("stroke-dasharray", dash); diff --git a/src/ui/Navigator.ts b/src/ui/Navigator.ts index ab9fae7..d76b8fc 100644 --- a/src/ui/Navigator.ts +++ b/src/ui/Navigator.ts @@ -48,7 +48,7 @@ interface DragState { const SVG_NS = "http://www.w3.org/2000/svg"; -function svg(tag: K): SVGElementTagNameMap[K] { +function createSvgElement(tag: K): SVGElementTagNameMap[K] { return document.createElementNS(SVG_NS, tag); } @@ -169,7 +169,7 @@ export function navigatorPlugin(options: NavigatorPluginOptions = {}): Navigator overlay.setAttribute("preserveAspectRatio", "none"); while (paths.length < selectedSeries.length) { - const path = svg("path"); + const path = createSvgElement("path"); path.setAttribute("fill", "none"); path.setAttribute("vector-effect", "non-scaling-stroke"); overlay.insertBefore(path, windowRect); @@ -265,16 +265,16 @@ export function navigatorPlugin(options: NavigatorPluginOptions = {}): Navigator chart.setLayoutReservation(reservationId, placement === "top" ? { top: height + margin * 2 } : { bottom: height + margin * 2 }); } - overlay = svg("svg"); + overlay = createSvgElement("svg"); overlay.style.width = "100%"; overlay.style.height = "100%"; overlay.setAttribute("aria-hidden", "true"); overlay.style.display = "block"; - windowRect = svg("rect"); - leftHandle = svg("rect"); - rightHandle = svg("rect"); - leftHandleHit = svg("rect"); - rightHandleHit = svg("rect"); + windowRect = createSvgElement("rect"); + leftHandle = createSvgElement("rect"); + rightHandle = createSvgElement("rect"); + leftHandleHit = createSvgElement("rect"); + rightHandleHit = createSvgElement("rect"); for (const handle of [leftHandle, rightHandle]) { handle.style.cursor = "ew-resize"; handle.style.pointerEvents = "none"; diff --git a/src/ui/OverlayUtils.ts b/src/ui/OverlayUtils.ts index af9d1e9..407a276 100644 --- a/src/ui/OverlayUtils.ts +++ b/src/ui/OverlayUtils.ts @@ -49,9 +49,9 @@ export function placeFixedWithinViewport( element: HTMLElement, clientX: number, clientY: number, - options: { readonly offsetX: number; readonly offsetY: number; readonly margin?: number }, + options: { readonly offsetX: number; readonly offsetY: number; readonly margin?: number; readonly size?: { readonly width: number; readonly height: number } }, ): void { - const rect = element.getBoundingClientRect(); + const rect = options.size ?? element.getBoundingClientRect(); const margin = options.margin ?? 4; const doc = element.ownerDocument; const viewportWidth = Math.max(1, globalThis.innerWidth || doc.documentElement.clientWidth); diff --git a/src/ui/Tooltip.ts b/src/ui/Tooltip.ts index c1f20cb..35e7503 100644 --- a/src/ui/Tooltip.ts +++ b/src/ui/Tooltip.ts @@ -1,5 +1,5 @@ import type { Chart, ChartHoverState, ChartPickGroup, ChartPickItem, ChartPickMode, ChartPlugin, ChartPluginContext } from "./Chart.js"; -import { clamp, createLongPressTouchTracker, createOverlayLayer, createPickMarker, formatCompactNumber, pickAtDataX, rgba, renderPickItems } from "./OverlayUtils.js"; +import { createLongPressTouchTracker, createOverlayLayer, createPickMarker, formatCompactNumber, pickAtDataX, placeFixedWithinViewport, renderPickItems, rgba } from "./OverlayUtils.js"; /** Options for the built-in hover tooltip plugin. */ export interface TooltipPluginOptions { @@ -50,14 +50,14 @@ interface TooltipPeer { const tooltipGroups = new Map>(); function placeTooltip(container: HTMLElement, state: ChartHoverState, options: TooltipPluginOptions, size: { readonly width: number; readonly height: number }): void { - const margin = 4; - const width = Math.max(1, size.width || 240); - const height = Math.max(1, size.height || 80); - const viewportWidth = Math.max(1, globalThis.innerWidth || container.ownerDocument.documentElement.clientWidth); - const viewportHeight = Math.max(1, globalThis.innerHeight || container.ownerDocument.documentElement.clientHeight); - const x = clamp(state.clientX + (options.offsetX ?? 12), margin, Math.max(margin, viewportWidth - width - margin)); - const y = clamp(state.clientY + (options.offsetY ?? 12), margin, Math.max(margin, viewportHeight - height - margin)); - container.style.transform = `translate(${x}px, ${y}px)`; + placeFixedWithinViewport(container, state.clientX, state.clientY, { + offsetX: options.offsetX ?? 12, + offsetY: options.offsetY ?? 12, + size: { + width: Math.max(1, size.width || 240), + height: Math.max(1, size.height || 80), + }, + }); } /** Create a plugin that displays picked data values in a tooltip. */