@heojeongbo/fluxion-render 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shared/lib/math.ts","../src/entities/axis-grid-layer/model/axis-grid-layer.ts","../src/entities/layer-stack/model/layer-stack.ts","../src/shared/lib/color.ts","../src/entities/lidar-scatter-layer/model/lidar-scatter-layer.ts","../src/shared/model/ring-buffer.ts","../src/entities/line-chart-layer/model/line-chart-layer.ts","../src/entities/line-chart-static-layer/model/line-chart-static-layer.ts","../src/shared/model/scheduler.ts","../src/shared/model/viewport.ts","../src/features/engine/model/engine.ts","../src/app/worker/fluxion-worker.ts"],"sourcesContent":["/**\n * Pick an aesthetically pleasing step for an axis covering [min, max] with\n * ~`targetTicks` divisions. Based on the standard nice-number heuristic.\n */\nexport function niceStep(range: number, targetTicks: number): number {\n const rough = range / Math.max(1, targetTicks);\n const pow10 = Math.pow(10, Math.floor(Math.log10(rough)));\n const norm = rough / pow10;\n let nice: number;\n if (norm < 1.5) nice = 1;\n else if (norm < 3) nice = 2;\n else if (norm < 7) nice = 5;\n else nice = 10;\n return nice * pow10;\n}\n\nexport function niceTicks(min: number, max: number, targetTicks = 6): number[] {\n if (!isFinite(min) || !isFinite(max) || max <= min) return [];\n const step = niceStep(max - min, targetTicks);\n const start = Math.ceil(min / step) * step;\n const out: number[] = [];\n for (let v = start; v <= max + step * 1e-6; v += step) {\n out.push(Number(v.toFixed(12)));\n }\n return out;\n}\n","import { niceTicks } from \"../../../shared/lib/math\";\nimport { formatClock } from \"../../../shared/lib/time-format\";\nimport type { Layer } from \"../../../shared/model/layer\";\nimport type { Bounds, Viewport } from \"../../../shared/model/viewport\";\n\nexport interface AxisGridConfig {\n /** Fixed x-range. Used when `xMode` is \"fixed\" (default). */\n xRange?: [number, number];\n yRange?: [number, number];\n gridColor?: string;\n axisColor?: string;\n labelColor?: string;\n font?: string;\n targetTicks?: number;\n /** If true (default), writes this layer's bounds into `viewport` so data layers share them. */\n applyToViewport?: boolean;\n /**\n * \"fixed\": xRange is literal world units (default).\n * \"time\": bounds follow the streaming `viewport.latestT` as a trailing\n * sliding window `[latestT - timeWindowMs, latestT]`. yRange is still fixed.\n */\n xMode?: \"fixed\" | \"time\";\n /** Width of the sliding window in ms when xMode=\"time\". Default 5000. */\n timeWindowMs?: number;\n /**\n * Absolute wall-clock epoch (ms) corresponding to data timestamp `0`. When\n * set together with `xMode: \"time\"`, tick labels render as wall clock\n * instead of elapsed seconds. Typically set once at host creation:\n * `timeOrigin: Date.now()` on the main thread.\n */\n timeOrigin?: number;\n /**\n * Clock-pattern string used to render x tick labels when `xMode: \"time\"`\n * AND `timeOrigin` is set. Default `\"HH:mm:ss\"`. Supported tokens:\n * `HH / H / mm / m / ss / s / SSS / S`. Anything else is a literal.\n *\n * Ignored when `timeOrigin` is not provided — elapsed-seconds fallback\n * (`\"X.Xs\"`) is used instead.\n */\n xTickFormat?: string;\n\n // ─── y scaling ────────────────────────────────────────────\n /**\n * \"fixed\" (default): use configured `yRange`.\n * \"auto\": data-driven. Reads `viewport.observedYMin/Max` during draw,\n * applies padding and clamps, updates `bounds.yMin/yMax`. Requires at\n * least one data layer (e.g. `LineChartLayer`) in the stack to publish\n * observations via its `scan()` pass.\n */\n yMode?: \"fixed\" | \"auto\";\n /** Padding ratio applied above/below the observed range. Default 0.1 (10%). */\n yAutoPadding?: number;\n /** Absolute lower clamp after padding. */\n yAutoMin?: number;\n /** Absolute upper clamp after padding. */\n yAutoMax?: number;\n\n // ─── Visual toggles (all default true) ────────────────────\n /** Show vertical grid lines at x ticks. */\n showXGrid?: boolean;\n /** Show horizontal grid lines at y ticks. */\n showYGrid?: boolean;\n /** Show the x=0 / y=0 axis lines when 0 is inside the range. */\n showAxes?: boolean;\n /** Show tick labels along the x axis. */\n showXLabels?: boolean;\n /** Show tick labels along the y axis. */\n showYLabels?: boolean;\n}\n\n/**\n * Owns the viewport bounds orchestration for a chart: x window (fixed or\n * time-sliding), y range (fixed or data-driven auto), and renders the\n * visible grid/axes/labels on top.\n *\n * Orchestration (scan + bounds computation) runs independently of the\n * visual toggles — you can turn every `show*` off and still use this layer\n * purely as a controller. LayerStack insertion order matters: add this\n * before any data layer so the bounds are written before they're read.\n *\n * v0.3 limitation: single-axis only. `observedYMin/Max` live on `Viewport`,\n * so a second `AxisGridLayer` in the same stack would bleed observations.\n */\nexport class AxisGridLayer implements Layer {\n readonly id: string;\n private gridColor = \"rgba(255,255,255,0.08)\";\n private axisColor = \"rgba(255,255,255,0.4)\";\n private labelColor = \"rgba(255,255,255,0.7)\";\n private font = \"10px sans-serif\";\n private targetTicks = 6;\n private bounds: Bounds = { xMin: -1, xMax: 1, yMin: -1, yMax: 1 };\n private applyToViewport = true;\n private xMode: \"fixed\" | \"time\" = \"fixed\";\n private timeWindowMs = 5000;\n private timeOrigin: number | null = null;\n private xTickFormat = \"HH:mm:ss\";\n private yMode: \"fixed\" | \"auto\" = \"fixed\";\n private yAutoPadding = 0.1;\n private yAutoMin: number | undefined;\n private yAutoMax: number | undefined;\n private showXGrid = true;\n private showYGrid = true;\n private showAxes = true;\n private showXLabels = true;\n private showYLabels = true;\n\n constructor(id: string) {\n this.id = id;\n }\n\n setConfig(config: unknown): void {\n const c = config as AxisGridConfig;\n if (c.xRange) {\n this.bounds.xMin = c.xRange[0];\n this.bounds.xMax = c.xRange[1];\n }\n if (c.yRange) {\n this.bounds.yMin = c.yRange[0];\n this.bounds.yMax = c.yRange[1];\n }\n if (c.gridColor) this.gridColor = c.gridColor;\n if (c.axisColor) this.axisColor = c.axisColor;\n if (c.labelColor) this.labelColor = c.labelColor;\n if (c.font) this.font = c.font;\n if (c.targetTicks) this.targetTicks = c.targetTicks;\n if (c.applyToViewport !== undefined) this.applyToViewport = c.applyToViewport;\n if (c.xMode !== undefined) this.xMode = c.xMode;\n if (c.timeWindowMs !== undefined) this.timeWindowMs = c.timeWindowMs;\n if (c.timeOrigin !== undefined) this.timeOrigin = c.timeOrigin;\n if (c.xTickFormat !== undefined) this.xTickFormat = c.xTickFormat;\n if (c.yMode !== undefined) this.yMode = c.yMode;\n if (c.yAutoPadding !== undefined) this.yAutoPadding = c.yAutoPadding;\n if (c.yAutoMin !== undefined) this.yAutoMin = c.yAutoMin;\n if (c.yAutoMax !== undefined) this.yAutoMax = c.yAutoMax;\n if (c.showXGrid !== undefined) this.showXGrid = c.showXGrid;\n if (c.showYGrid !== undefined) this.showYGrid = c.showYGrid;\n if (c.showAxes !== undefined) this.showAxes = c.showAxes;\n if (c.showXLabels !== undefined) this.showXLabels = c.showXLabels;\n if (c.showYLabels !== undefined) this.showYLabels = c.showYLabels;\n }\n\n setData(_buffer: ArrayBuffer, _length: number, _viewport: Viewport): void {}\n\n resize(_viewport: Viewport): void {}\n\n /**\n * Orchestration pass: establish x bounds so data layers' `scan` can filter\n * visible samples correctly. yMode:\"auto\" is finalized in `draw` after all\n * line layers have published their observations.\n */\n scan(viewport: Viewport): void {\n if (this.xMode === \"time\") {\n const latestT = viewport.latestT;\n this.bounds.xMin = latestT - this.timeWindowMs;\n this.bounds.xMax = latestT;\n }\n if (this.applyToViewport) {\n viewport.setBounds(this.bounds);\n }\n }\n\n draw(ctx: OffscreenCanvasRenderingContext2D, viewport: Viewport): void {\n // Finalize y-auto bounds. Runs after all line-layer scans have\n // published their observed extents into the viewport.\n if (this.yMode === \"auto\") {\n let yMin = viewport.observedYMin;\n let yMax = viewport.observedYMax;\n if (!Number.isFinite(yMin) || !Number.isFinite(yMax)) {\n // No data yet — fall back to configured yRange. If that is also\n // degenerate (defaults [-1, 1] from construction), use [-1, 1].\n yMin = this.bounds.yMin;\n yMax = this.bounds.yMax;\n if (yMin === yMax) {\n yMin = -1;\n yMax = 1;\n }\n } else if (yMin === yMax) {\n // Flat line — expand so stroke has vertical room.\n yMin -= 0.5;\n yMax += 0.5;\n } else {\n const pad = (yMax - yMin) * this.yAutoPadding;\n yMin -= pad;\n yMax += pad;\n }\n if (this.yAutoMin !== undefined && yMin < this.yAutoMin) yMin = this.yAutoMin;\n if (this.yAutoMax !== undefined && yMax > this.yAutoMax) yMax = this.yAutoMax;\n this.bounds.yMin = yMin;\n this.bounds.yMax = yMax;\n if (this.applyToViewport) viewport.setBounds(this.bounds);\n }\n\n const { widthPx: w, heightPx: h } = viewport;\n const xTicks = niceTicks(this.bounds.xMin, this.bounds.xMax, this.targetTicks);\n const yTicks = niceTicks(this.bounds.yMin, this.bounds.yMax, this.targetTicks);\n\n // ── Grid lines ──\n if (this.showXGrid || this.showYGrid) {\n ctx.strokeStyle = this.gridColor;\n ctx.lineWidth = 1;\n ctx.beginPath();\n if (this.showXGrid) {\n for (let i = 0; i < xTicks.length; i++) {\n const x = Math.round(viewport.xToPx(xTicks[i])) + 0.5;\n ctx.moveTo(x, 0);\n ctx.lineTo(x, h);\n }\n }\n if (this.showYGrid) {\n for (let i = 0; i < yTicks.length; i++) {\n const y = Math.round(viewport.yToPx(yTicks[i])) + 0.5;\n ctx.moveTo(0, y);\n ctx.lineTo(w, y);\n }\n }\n ctx.stroke();\n }\n\n // ── Zero axes ──\n if (this.showAxes) {\n ctx.strokeStyle = this.axisColor;\n ctx.beginPath();\n if (this.bounds.xMin < 0 && this.bounds.xMax > 0) {\n const x0 = Math.round(viewport.xToPx(0)) + 0.5;\n ctx.moveTo(x0, 0);\n ctx.lineTo(x0, h);\n }\n if (this.bounds.yMin < 0 && this.bounds.yMax > 0) {\n const y0 = Math.round(viewport.yToPx(0)) + 0.5;\n ctx.moveTo(0, y0);\n ctx.lineTo(w, y0);\n }\n ctx.stroke();\n }\n\n // ── Labels ──\n if (this.showXLabels || this.showYLabels) {\n ctx.fillStyle = this.labelColor;\n ctx.font = this.font;\n if (this.showXLabels) {\n ctx.textBaseline = \"top\";\n for (let i = 0; i < xTicks.length; i++) {\n const x = viewport.xToPx(xTicks[i]);\n ctx.fillText(\n formatTick(xTicks[i], this.xMode, this.timeOrigin, this.xTickFormat),\n x + 2,\n h - 12,\n );\n }\n }\n if (this.showYLabels) {\n ctx.textBaseline = \"middle\";\n for (let i = 0; i < yTicks.length; i++) {\n const y = viewport.yToPx(yTicks[i]);\n ctx.fillText(String(yTicks[i]), 2, y - 6);\n }\n }\n }\n }\n\n dispose(): void {}\n}\n\nfunction formatTick(\n value: number,\n mode: \"fixed\" | \"time\",\n timeOrigin: number | null,\n pattern: string,\n): string {\n if (mode === \"time\") {\n if (timeOrigin != null) {\n return formatClock(timeOrigin + value, pattern);\n }\n // Elapsed-only fallback (timeOrigin missing).\n const s = value / 1000;\n return `${s.toFixed(1)}s`;\n }\n return String(value);\n}\n","import type { Layer } from \"../../../shared/model/layer\";\nimport type { Viewport } from \"../../../shared/model/viewport\";\n\nexport class LayerStack {\n private layers: Layer[] = [];\n private byId = new Map<string, Layer>();\n\n add(layer: Layer): void {\n this.layers.push(layer);\n this.byId.set(layer.id, layer);\n }\n\n remove(id: string): void {\n const layer = this.byId.get(id);\n if (!layer) return;\n this.byId.delete(id);\n const i = this.layers.indexOf(layer);\n if (i >= 0) this.layers.splice(i, 1);\n layer.dispose();\n }\n\n get(id: string): Layer | undefined {\n return this.byId.get(id);\n }\n\n resizeAll(viewport: Viewport): void {\n for (let i = 0; i < this.layers.length; i++) {\n this.layers[i].resize(viewport);\n }\n }\n\n /**\n * Pre-draw pass. Layers that implement `scan` update shared viewport state\n * (bounds, observed extents) here so downstream layers' `draw` sees the\n * correct values. Iterates in insertion order so axis-grid (added first)\n * writes bounds before data layers read them.\n */\n scanAll(viewport: Viewport): void {\n for (let i = 0; i < this.layers.length; i++) {\n this.layers[i].scan?.(viewport);\n }\n }\n\n drawAll(ctx: OffscreenCanvasRenderingContext2D, viewport: Viewport): void {\n for (let i = 0; i < this.layers.length; i++) {\n this.layers[i].draw(ctx, viewport);\n }\n }\n\n disposeAll(): void {\n for (let i = 0; i < this.layers.length; i++) {\n this.layers[i].dispose();\n }\n this.layers.length = 0;\n this.byId.clear();\n }\n}\n","/**\n * Intensity (0..1) → viridis-ish RGB LUT.\n * Precomputed 256-entry lookup to avoid per-point math during rendering.\n */\nconst LUT_SIZE = 256;\nconst lutR = new Uint8ClampedArray(LUT_SIZE);\nconst lutG = new Uint8ClampedArray(LUT_SIZE);\nconst lutB = new Uint8ClampedArray(LUT_SIZE);\n\nfunction ramp(t: number): [number, number, number] {\n const stops: [number, [number, number, number]][] = [\n [0.0, [13, 8, 135]],\n [0.25, [84, 2, 163]],\n [0.5, [156, 23, 158]],\n [0.75, [225, 100, 98]],\n [1.0, [240, 249, 33]],\n ];\n for (let i = 0; i < stops.length - 1; i++) {\n const [t0, c0] = stops[i];\n const [t1, c1] = stops[i + 1];\n if (t <= t1) {\n const k = (t - t0) / (t1 - t0);\n return [\n c0[0] + (c1[0] - c0[0]) * k,\n c0[1] + (c1[1] - c0[1]) * k,\n c0[2] + (c1[2] - c0[2]) * k,\n ];\n }\n }\n return stops[stops.length - 1][1];\n}\n\nfor (let i = 0; i < LUT_SIZE; i++) {\n const [r, g, b] = ramp(i / (LUT_SIZE - 1));\n lutR[i] = r;\n lutG[i] = g;\n lutB[i] = b;\n}\n\nexport interface IntensityLUT {\n readonly r: Uint8ClampedArray;\n readonly g: Uint8ClampedArray;\n readonly b: Uint8ClampedArray;\n readonly size: number;\n}\n\nconst SINGLETON: IntensityLUT = Object.freeze({\n r: lutR,\n g: lutG,\n b: lutB,\n size: LUT_SIZE,\n});\n\nexport function intensityLUT(): IntensityLUT {\n return SINGLETON;\n}\n","import { intensityLUT } from \"../../../shared/lib/color\";\nimport type { Layer } from \"../../../shared/model/layer\";\nimport type { Viewport } from \"../../../shared/model/viewport\";\n\nexport interface LidarScatterConfig {\n /** Floats per point. Default 4 (x,y,z,intensity). Minimum 2 (x,y). */\n stride?: number;\n /** Point size in pixels. */\n pointSize?: number;\n /** Max intensity value for normalization (0..1). Default 1.0. */\n intensityMax?: number;\n /** Solid color override. When set, intensity LUT is ignored. */\n color?: string;\n}\n\nconst LUT_BUCKETS = 256;\n\n/**\n * Renders a point cloud as a 2D top-down scatter.\n * Expects Float32Array with layout [x, y, z, intensity, ...] (stride configurable).\n *\n * Fast path for 10k+ points: counting-sort by intensity-LUT bucket so each\n * color bucket issues one `beginPath / rect×N / fill` cycle. This reduces\n * fillStyle state changes from O(N) to at most 256 per frame, and collapses\n * all per-point fillRect calls into a single path fill per color.\n *\n * Scratch buffers (`sortedX`, `sortedY`, `bucketCount`) live as layer fields\n * and grow by 25% when the point budget expands, so steady-state pushes\n * allocate zero memory.\n */\nexport class LidarScatterLayer implements Layer {\n readonly id: string;\n private stride = 4;\n private pointSize = 2;\n private intensityMax = 1;\n private solidColor: [number, number, number] | null = null;\n private data: Float32Array | null = null;\n private length = 0;\n\n // Scratch buffers for counting-sort batching.\n private sortedX: Float32Array = new Float32Array(0);\n private sortedY: Float32Array = new Float32Array(0);\n private bucketCount: Uint32Array = new Uint32Array(LUT_BUCKETS);\n private bucketOffset: Uint32Array = new Uint32Array(LUT_BUCKETS);\n private scratchCapacity = 0;\n\n constructor(id: string) {\n this.id = id;\n }\n\n setConfig(config: unknown): void {\n const c = config as LidarScatterConfig;\n if (c.stride !== undefined) this.stride = Math.max(2, c.stride | 0);\n if (c.pointSize !== undefined) this.pointSize = Math.max(1, c.pointSize);\n if (c.intensityMax !== undefined) this.intensityMax = c.intensityMax;\n if (c.color !== undefined) this.solidColor = parseColor(c.color);\n }\n\n setData(buffer: ArrayBuffer, length: number, _viewport: Viewport): void {\n this.data = new Float32Array(buffer, 0, length);\n this.length = length;\n }\n\n resize(_viewport: Viewport): void {}\n\n draw(ctx: OffscreenCanvasRenderingContext2D, viewport: Viewport): void {\n const data = this.data;\n if (!data || this.length < this.stride) return;\n\n const stride = this.stride;\n const size = this.pointSize;\n const half = size / 2;\n const count = (this.length / stride) | 0;\n\n if (this.solidColor) {\n this.drawSolid(ctx, viewport, data, stride, count, size, half);\n return;\n }\n\n this.ensureScratch(count);\n this.drawBucketed(ctx, viewport, data, stride, count, size, half);\n }\n\n private drawSolid(\n ctx: OffscreenCanvasRenderingContext2D,\n viewport: Viewport,\n data: Float32Array,\n stride: number,\n count: number,\n size: number,\n half: number,\n ): void {\n const [r, g, b] = this.solidColor as [number, number, number];\n ctx.fillStyle = `rgb(${r},${g},${b})`;\n ctx.beginPath();\n for (let i = 0; i < count; i++) {\n const o = i * stride;\n const px = viewport.xToPx(data[o]);\n const py = viewport.yToPx(data[o + 1]);\n ctx.rect(px - half, py - half, size, size);\n }\n ctx.fill();\n }\n\n private drawBucketed(\n ctx: OffscreenCanvasRenderingContext2D,\n viewport: Viewport,\n data: Float32Array,\n stride: number,\n count: number,\n size: number,\n half: number,\n ): void {\n const lut = intensityLUT();\n const invMax = 1 / (this.intensityMax || 1);\n const sortedX = this.sortedX;\n const sortedY = this.sortedY;\n const bucketCount = this.bucketCount;\n const bucketOffset = this.bucketOffset;\n\n // Reset bucket histograms.\n bucketCount.fill(0);\n\n // Pass 1: compute pixel coords + bucket index, fill histogram.\n // Stash bucket index in a parallel-ish way: we'll recompute in pass 2\n // to avoid a third scratch buffer. The cost is one extra intensity read.\n for (let i = 0; i < count; i++) {\n const o = i * stride;\n const intensity = stride >= 4 ? data[o + 3] : 1;\n let idx = (intensity * invMax * (LUT_BUCKETS - 1)) | 0;\n if (idx < 0) idx = 0;\n else if (idx >= LUT_BUCKETS) idx = LUT_BUCKETS - 1;\n bucketCount[idx]++;\n }\n\n // Prefix-sum: bucketOffset[b] = sum of bucketCount[0..b-1]\n let acc = 0;\n for (let b = 0; b < LUT_BUCKETS; b++) {\n bucketOffset[b] = acc;\n acc += bucketCount[b];\n }\n\n // Pass 2: scatter points into bucket-ordered scratch. We reuse\n // `bucketCount` as a write cursor by decrementing it back to 0.\n // Snapshot offsets first, then advance cursors from each offset.\n // Simpler: use bucketCount as an independent write cursor initialised to 0.\n const writeCursor = bucketCount; // reused, currently holds histogram\n // Restore to 0 now that bucketOffset has captured the prefix sum.\n for (let b = 0; b < LUT_BUCKETS; b++) writeCursor[b] = 0;\n\n for (let i = 0; i < count; i++) {\n const o = i * stride;\n const intensity = stride >= 4 ? data[o + 3] : 1;\n let idx = (intensity * invMax * (LUT_BUCKETS - 1)) | 0;\n if (idx < 0) idx = 0;\n else if (idx >= LUT_BUCKETS) idx = LUT_BUCKETS - 1;\n const pos = bucketOffset[idx] + writeCursor[idx];\n writeCursor[idx]++;\n sortedX[pos] = viewport.xToPx(data[o]);\n sortedY[pos] = viewport.yToPx(data[o + 1]);\n }\n\n // Pass 3: emit one path per non-empty bucket.\n for (let b = 0; b < LUT_BUCKETS; b++) {\n const n = writeCursor[b];\n if (n === 0) continue;\n ctx.fillStyle = `rgb(${lut.r[b]},${lut.g[b]},${lut.b[b]})`;\n ctx.beginPath();\n const start = bucketOffset[b];\n const end = start + n;\n for (let i = start; i < end; i++) {\n ctx.rect(sortedX[i] - half, sortedY[i] - half, size, size);\n }\n ctx.fill();\n }\n }\n\n private ensureScratch(count: number): void {\n if (count <= this.scratchCapacity) return;\n const next = Math.max(count, Math.ceil(this.scratchCapacity * 1.25), 1024);\n this.sortedX = new Float32Array(next);\n this.sortedY = new Float32Array(next);\n this.scratchCapacity = next;\n }\n\n dispose(): void {\n this.data = null;\n this.sortedX = new Float32Array(0);\n this.sortedY = new Float32Array(0);\n this.scratchCapacity = 0;\n }\n}\n\nfunction parseColor(css: string): [number, number, number] {\n if (css.startsWith(\"#\")) {\n const hex = css.slice(1);\n if (hex.length === 3) {\n return [\n parseInt(hex[0] + hex[0], 16),\n parseInt(hex[1] + hex[1], 16),\n parseInt(hex[2] + hex[2], 16),\n ];\n }\n if (hex.length === 6) {\n return [\n parseInt(hex.slice(0, 2), 16),\n parseInt(hex.slice(2, 4), 16),\n parseInt(hex.slice(4, 6), 16),\n ];\n }\n }\n return [255, 255, 255];\n}\n","/**\n * Fixed-capacity ring buffer over a Float32Array.\n * Stores interleaved records of `stride` floats each (e.g. [x,y] stride=2).\n * Zero-allocation push; draw iterates from tail to head in chronological order.\n */\nexport class RingBuffer {\n readonly stride: number;\n readonly capacity: number;\n readonly data: Float32Array;\n private head = 0;\n private count = 0;\n\n constructor(capacity: number, stride: number) {\n this.capacity = capacity;\n this.stride = stride;\n this.data = new Float32Array(capacity * stride);\n }\n\n get length(): number {\n return this.count;\n }\n\n push(record: ArrayLike<number>): void {\n const base = this.head * this.stride;\n for (let i = 0; i < this.stride; i++) {\n this.data[base + i] = record[i];\n }\n this.head = (this.head + 1) % this.capacity;\n if (this.count < this.capacity) this.count++;\n }\n\n pushMany(records: Float32Array): void {\n const recCount = records.length / this.stride;\n for (let r = 0; r < recCount; r++) {\n const base = this.head * this.stride;\n const src = r * this.stride;\n for (let i = 0; i < this.stride; i++) {\n this.data[base + i] = records[src + i];\n }\n this.head = (this.head + 1) % this.capacity;\n if (this.count < this.capacity) this.count++;\n }\n }\n\n forEach(fn: (data: Float32Array, offset: number, index: number) => void): void {\n const start = this.count < this.capacity ? 0 : this.head;\n for (let i = 0; i < this.count; i++) {\n const slot = (start + i) % this.capacity;\n fn(this.data, slot * this.stride, i);\n }\n }\n\n clear(): void {\n this.head = 0;\n this.count = 0;\n }\n}\n","import type { Layer } from \"../../../shared/model/layer\";\nimport { RingBuffer } from \"../../../shared/model/ring-buffer\";\nimport type { Viewport } from \"../../../shared/model/viewport\";\n\nexport interface LineChartConfig {\n color?: string;\n lineWidth?: number;\n /** Ring buffer capacity (number of [t,y] samples retained). Default 2048. */\n capacity?: number;\n}\n\n/**\n * Streaming time-series line chart. Expects `Float32Array [t, y, t, y, ...]`\n * where `t` is host-relative milliseconds (monotonic). Each `setData` call\n * appends to an internal ring buffer, so draw cost is O(capacity), not O(total\n * samples pushed).\n *\n * On every append the layer advances `viewport.latestT` so the axis-grid in\n * `xMode: \"time\"` can compute a trailing sliding window.\n */\nexport class LineChartLayer implements Layer {\n readonly id: string;\n private color = \"#4fc3f7\";\n private lineWidth = 1;\n private ring: RingBuffer;\n\n constructor(id: string) {\n this.id = id;\n this.ring = new RingBuffer(2048, 2);\n }\n\n setConfig(config: unknown): void {\n const c = config as LineChartConfig;\n if (c.color !== undefined) this.color = c.color;\n if (c.lineWidth !== undefined) this.lineWidth = c.lineWidth;\n if (c.capacity !== undefined && c.capacity !== this.ring.capacity) {\n this.ring = new RingBuffer(c.capacity, 2);\n }\n }\n\n setData(buffer: ArrayBuffer, length: number, viewport: Viewport): void {\n if (length < 2) return;\n const arr = new Float32Array(buffer, 0, length);\n this.ring.pushMany(arr);\n // The newest timestamp in this batch sits at index (length - 2).\n // Advance the shared latestT so axis-grid time mode can follow.\n const t = arr[length - 2];\n if (t > viewport.latestT) viewport.latestT = t;\n }\n\n resize(_viewport: Viewport): void {}\n\n /**\n * Pre-draw pass: compute the visible-window min/max of y values in this\n * layer's ring buffer and merge them into `viewport.observedYMin/Max`.\n * AxisGridLayer with `yMode: \"auto\"` reads the aggregate in draw.\n *\n * `viewport.bounds.xMin` was already written by AxisGridLayer.scan (which\n * runs earlier in insertion order), so we can filter stale samples here.\n */\n scan(viewport: Viewport): void {\n if (this.ring.length === 0) return;\n const xMin = viewport.bounds.xMin;\n let localMin = viewport.observedYMin;\n let localMax = viewport.observedYMax;\n this.ring.forEach((data, off) => {\n const t = data[off];\n if (t < xMin) return;\n const y = data[off + 1];\n if (y < localMin) localMin = y;\n if (y > localMax) localMax = y;\n });\n viewport.observedYMin = localMin;\n viewport.observedYMax = localMax;\n }\n\n draw(ctx: OffscreenCanvasRenderingContext2D, viewport: Viewport): void {\n if (this.ring.length < 2) return;\n\n ctx.strokeStyle = this.color;\n ctx.lineWidth = this.lineWidth;\n ctx.beginPath();\n\n // Sample filter: skip records older than the current x-window. Combined\n // with axis-grid time mode, this lets consumers \"select a window\" by\n // changing `timeWindowMs` and have the chart both retarget AND drop\n // old samples from the drawn path in one go.\n const xMin = viewport.bounds.xMin;\n\n let first = true;\n this.ring.forEach((data, off) => {\n const t = data[off];\n if (t < xMin) return;\n const px = viewport.xToPx(t);\n const py = viewport.yToPx(data[off + 1]);\n if (first) {\n ctx.moveTo(px, py);\n first = false;\n } else {\n ctx.lineTo(px, py);\n }\n });\n ctx.stroke();\n }\n\n dispose(): void {\n this.ring.clear();\n }\n}\n","import type { Layer } from \"../../../shared/model/layer\";\nimport type { Viewport } from \"../../../shared/model/viewport\";\n\nexport interface LineChartStaticConfig {\n color?: string;\n lineWidth?: number;\n /**\n * Data layout:\n * - \"xy\" (default): interleaved [x,y,x,y,...] stride=2\n * - \"y\": [y0,y1,...] with implicit x = linear sweep across `viewport.bounds.x`\n */\n layout?: \"xy\" | \"y\";\n}\n\n/**\n * One-shot xy line chart. Replaces the entire series on every `setData`.\n * Use this for pre-computed plots, snapshot visualizations, or any dataset\n * whose x-axis is not a time stream. For streaming time-series data, use\n * `LineChartLayer` (kind \"line\") instead.\n */\nexport class LineChartStaticLayer implements Layer {\n readonly id: string;\n private color = \"#4fc3f7\";\n private lineWidth = 1;\n private layout: \"xy\" | \"y\" = \"xy\";\n private data: Float32Array | null = null;\n private length = 0;\n\n constructor(id: string) {\n this.id = id;\n }\n\n setConfig(config: unknown): void {\n const c = config as LineChartStaticConfig;\n if (c.color !== undefined) this.color = c.color;\n if (c.lineWidth !== undefined) this.lineWidth = c.lineWidth;\n if (c.layout !== undefined) this.layout = c.layout;\n }\n\n setData(buffer: ArrayBuffer, length: number, _viewport: Viewport): void {\n this.data = new Float32Array(buffer, 0, length);\n this.length = length;\n }\n\n resize(_viewport: Viewport): void {}\n\n draw(ctx: OffscreenCanvasRenderingContext2D, viewport: Viewport): void {\n const data = this.data;\n if (!data || this.length < 2) return;\n\n ctx.strokeStyle = this.color;\n ctx.lineWidth = this.lineWidth;\n ctx.beginPath();\n\n if (this.layout === \"xy\") {\n const n = this.length >> 1;\n if (n < 2) return;\n ctx.moveTo(viewport.xToPx(data[0]), viewport.yToPx(data[1]));\n for (let i = 1; i < n; i++) {\n const j = i * 2;\n ctx.lineTo(viewport.xToPx(data[j]), viewport.yToPx(data[j + 1]));\n }\n } else {\n const n = this.length;\n const xMin = viewport.bounds.xMin;\n const xMax = viewport.bounds.xMax;\n const step = (xMax - xMin) / Math.max(1, n - 1);\n ctx.moveTo(viewport.xToPx(xMin), viewport.yToPx(data[0]));\n for (let i = 1; i < n; i++) {\n ctx.lineTo(viewport.xToPx(xMin + i * step), viewport.yToPx(data[i]));\n }\n }\n ctx.stroke();\n }\n\n dispose(): void {\n this.data = null;\n }\n}\n","/**\n * rAF-based render scheduler. Only calls `tick` on frames where dirty is set.\n * Uses the worker-global requestAnimationFrame when available; otherwise falls\n * back to setTimeout(16ms).\n */\nexport class Scheduler {\n private dirty = false;\n private running = false;\n private raf: number | null = null;\n private readonly tick: () => void;\n\n constructor(tick: () => void) {\n this.tick = tick;\n }\n\n start() {\n if (this.running) return;\n this.running = true;\n this.loop();\n }\n\n stop() {\n this.running = false;\n if (this.raf != null) {\n if (typeof cancelAnimationFrame !== \"undefined\") {\n cancelAnimationFrame(this.raf);\n } else {\n clearTimeout(this.raf);\n }\n this.raf = null;\n }\n }\n\n markDirty() {\n this.dirty = true;\n }\n\n private loop = () => {\n if (!this.running) return;\n if (this.dirty) {\n this.dirty = false;\n this.tick();\n }\n if (typeof requestAnimationFrame !== \"undefined\") {\n this.raf = requestAnimationFrame(this.loop);\n } else {\n this.raf = setTimeout(this.loop, 16) as unknown as number;\n }\n };\n}\n","export interface Bounds {\n xMin: number;\n xMax: number;\n yMin: number;\n yMax: number;\n}\n\nexport class Viewport {\n widthPx = 0;\n heightPx = 0;\n dpr = 1;\n\n bounds: Bounds = { xMin: -1, xMax: 1, yMin: -1, yMax: 1 };\n\n /**\n * Most recent data timestamp (ms, host-relative) seen across all streaming\n * layers. Streaming `LineChartLayer` updates this on setData; `AxisGridLayer`\n * in time mode uses it to compute a sliding window.\n */\n latestT = 0;\n\n /**\n * Per-frame aggregate of observed y values across all data layers that\n * currently overlap the visible time window. `AxisGridLayer` in\n * `yMode: \"auto\"` reads these in draw to compute bounds.yMin/yMax.\n *\n * Initialised to +/-Infinity by `beginScan()` at the top of every frame,\n * then layers merge their visible-window min/max in via `scan()`.\n */\n observedYMin = Number.POSITIVE_INFINITY;\n observedYMax = Number.NEGATIVE_INFINITY;\n\n setSize(width: number, height: number, dpr: number) {\n this.widthPx = width;\n this.heightPx = height;\n this.dpr = dpr;\n }\n\n setBounds(b: Bounds) {\n this.bounds = b;\n }\n\n /** Called by Engine at the start of each render frame before scan pass. */\n beginScan(): void {\n this.observedYMin = Number.POSITIVE_INFINITY;\n this.observedYMax = Number.NEGATIVE_INFINITY;\n }\n\n xToPx(x: number): number {\n const { xMin, xMax } = this.bounds;\n return ((x - xMin) / (xMax - xMin)) * this.widthPx;\n }\n\n yToPx(y: number): number {\n const { yMin, yMax } = this.bounds;\n return this.heightPx - ((y - yMin) / (yMax - yMin)) * this.heightPx;\n }\n}\n","import { AxisGridLayer } from \"../../../entities/axis-grid-layer\";\nimport { LayerStack } from \"../../../entities/layer-stack\";\nimport { LidarScatterLayer } from \"../../../entities/lidar-scatter-layer\";\nimport { LineChartLayer } from \"../../../entities/line-chart-layer\";\nimport { LineChartStaticLayer } from \"../../../entities/line-chart-static-layer\";\nimport type { Layer } from \"../../../shared/model/layer\";\nimport { Scheduler } from \"../../../shared/model/scheduler\";\nimport { Viewport } from \"../../../shared/model/viewport\";\nimport type { HostMsg, LayerKind } from \"../../../shared/protocol\";\nimport { Op } from \"../../../shared/protocol\";\n\nfunction createLayer(id: string, kind: LayerKind): Layer {\n switch (kind) {\n case \"line\":\n return new LineChartLayer(id);\n case \"line-static\":\n return new LineChartStaticLayer(id);\n case \"lidar\":\n return new LidarScatterLayer(id);\n case \"axis-grid\":\n return new AxisGridLayer(id);\n }\n}\n\n/**\n * Worker-side engine. Owns the OffscreenCanvas, layer stack, viewport,\n * and render scheduler. All state lives here; main thread just pushes messages.\n */\nexport class Engine {\n private canvas: OffscreenCanvas | null = null;\n private ctx: OffscreenCanvasRenderingContext2D | null = null;\n private readonly viewport = new Viewport();\n private readonly stack = new LayerStack();\n private readonly scheduler: Scheduler;\n private bgColor = \"#0b0d12\";\n\n constructor() {\n this.scheduler = new Scheduler(() => this.render());\n }\n\n dispatch(msg: HostMsg): void {\n switch (msg.op) {\n case Op.INIT:\n this.init(msg.canvas, msg.width, msg.height, msg.dpr);\n break;\n case Op.RESIZE:\n this.resize(msg.width, msg.height, msg.dpr);\n break;\n case Op.ADD_LAYER: {\n const layer = createLayer(msg.id, msg.kind);\n if (msg.config !== undefined) layer.setConfig(msg.config);\n layer.resize(this.viewport);\n this.stack.add(layer);\n this.scheduler.markDirty();\n break;\n }\n case Op.REMOVE_LAYER:\n this.stack.remove(msg.id);\n this.scheduler.markDirty();\n break;\n case Op.CONFIG: {\n const layer = this.stack.get(msg.id);\n if (layer) {\n layer.setConfig(msg.config);\n this.scheduler.markDirty();\n }\n break;\n }\n case Op.DATA: {\n const layer = this.stack.get(msg.id);\n if (layer) {\n layer.setData(msg.buffer, msg.length, this.viewport);\n this.scheduler.markDirty();\n }\n break;\n }\n case Op.DISPOSE:\n this.dispose();\n break;\n }\n }\n\n private init(canvas: OffscreenCanvas, width: number, height: number, dpr: number) {\n this.canvas = canvas;\n this.ctx = canvas.getContext(\"2d\");\n this.resize(width, height, dpr);\n this.scheduler.start();\n this.scheduler.markDirty();\n }\n\n private resize(width: number, height: number, dpr: number) {\n if (!this.canvas) return;\n this.canvas.width = Math.max(1, Math.round(width * dpr));\n this.canvas.height = Math.max(1, Math.round(height * dpr));\n this.viewport.setSize(width, height, dpr);\n this.stack.resizeAll(this.viewport);\n this.scheduler.markDirty();\n }\n\n private render() {\n const ctx = this.ctx;\n if (!ctx || !this.canvas) return;\n // 2-pass: scan (orchestration: time window, observed y, bounds) then\n // draw. AxisGridLayer.scan writes bounds; LineChartLayer.scan reads\n // bounds and publishes observed y extents; AxisGridLayer.draw finishes\n // the y-auto computation using those extents.\n this.viewport.beginScan();\n this.stack.scanAll(this.viewport);\n const { dpr } = this.viewport;\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n ctx.fillStyle = this.bgColor;\n ctx.fillRect(0, 0, this.viewport.widthPx, this.viewport.heightPx);\n this.stack.drawAll(ctx, this.viewport);\n }\n\n private dispose() {\n this.scheduler.stop();\n this.stack.disposeAll();\n this.canvas = null;\n this.ctx = null;\n }\n}\n","import { Engine } from \"../../features/engine\";\nimport type { HostMsg } from \"../../shared/protocol\";\n\nconst engine = new Engine();\n\nself.onmessage = (e: MessageEvent<HostMsg>) => {\n try {\n engine.dispatch(e.data);\n } catch (err) {\n console.error(\"[fluxion-worker] dispatch error:\", err);\n }\n};\n\nself.addEventListener(\"error\", (e) => {\n console.error(\"[fluxion-worker] uncaught error:\", e.message ?? e);\n});\n\nself.addEventListener(\"messageerror\", (e) => {\n console.error(\"[fluxion-worker] message deserialization failed:\", e);\n});\n"],"mappings":"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","names":[]}
@@ -0,0 +1,8 @@
1
+ export { A as AxisGridConfig, D as DType, F as FluxionDataSink, a as FluxionHost, b as FluxionHostOptions, c as FluxionTypedArray, H as HostMsg, L as LayerKind, d as LidarLayerHandle, e as LidarPoint, f as LidarScatterConfig, g as LidarStride, h as LineChartConfig, i as LineChartStaticConfig, j as LineLayerHandle, k as LineSample, l as LineStaticLayerHandle, X as XyPoint } from './fluxion-host-C_n0cGQO.js';
2
+
3
+ type TickFormatter = (epochMs: number) => string;
4
+ /** Build a memoized formatter for a pattern. */
5
+ declare function makeClockFormatter(pattern: string): TickFormatter;
6
+ declare function formatClock(epochMs: number, pattern: string): string;
7
+
8
+ export { type TickFormatter, formatClock, makeClockFormatter };
package/dist/index.js ADDED
@@ -0,0 +1,20 @@
1
+ import {
2
+ FluxionHost,
3
+ LidarLayerHandle,
4
+ LineLayerHandle,
5
+ LineStaticLayerHandle
6
+ } from "./chunk-56NZ4OEO.js";
7
+ import {
8
+ formatClock,
9
+ makeClockFormatter
10
+ } from "./chunk-3PK3KDO5.js";
11
+ import "./chunk-R7FLS7BG.js";
12
+ export {
13
+ FluxionHost,
14
+ LidarLayerHandle,
15
+ LineLayerHandle,
16
+ LineStaticLayerHandle,
17
+ formatClock,
18
+ makeClockFormatter
19
+ };
20
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,180 @@
1
+ import { h as LineChartConfig, i as LineChartStaticConfig, f as LidarScatterConfig, A as AxisGridConfig, b as FluxionHostOptions, a as FluxionHost } from './fluxion-host-C_n0cGQO.js';
2
+ export { L as LayerKind } from './fluxion-host-C_n0cGQO.js';
3
+ import * as react from 'react';
4
+ import { RefObject, CSSProperties } from 'react';
5
+
6
+ /**
7
+ * Declarative layer spec used by `useFluxionCanvas` and `<FluxionCanvas/>`.
8
+ *
9
+ * Discriminated union: `kind` narrows `config` to the matching layer-specific
10
+ * type, so wrong fields are caught at compile time. Prefer the layer factory
11
+ * helpers (`lineLayer`, `axisGridLayer`, etc.) for ergonomic construction —
12
+ * they encode the kind so callers don't repeat themselves.
13
+ */
14
+ type FluxionLayerSpec = {
15
+ id: string;
16
+ kind: "line";
17
+ config?: LineChartConfig;
18
+ } | {
19
+ id: string;
20
+ kind: "line-static";
21
+ config?: LineChartStaticConfig;
22
+ } | {
23
+ id: string;
24
+ kind: "lidar";
25
+ config?: LidarScatterConfig;
26
+ } | {
27
+ id: string;
28
+ kind: "axis-grid";
29
+ config?: AxisGridConfig;
30
+ };
31
+ interface UseFluxionCanvasOptions {
32
+ layers: FluxionLayerSpec[];
33
+ hostOptions?: FluxionHostOptions;
34
+ onReady?: (host: FluxionHost) => void;
35
+ }
36
+ interface UseFluxionCanvasResult {
37
+ /**
38
+ * Attach to a `<div>`. The hook appends a `<canvas>` child imperatively —
39
+ * do NOT render a canvas yourself inside this container.
40
+ */
41
+ containerRef: RefObject<HTMLDivElement>;
42
+ /**
43
+ * Live host handle. `null` until the effect completes on the first mount.
44
+ * Updates to a new instance if the component remounts.
45
+ */
46
+ host: FluxionHost | null;
47
+ }
48
+ /**
49
+ * Low-level React hook that owns a FluxionRender worker + OffscreenCanvas.
50
+ *
51
+ * Consumers attach `containerRef` to any `<div>` and the hook:
52
+ * 1. Creates a fresh `<canvas>` inside the div on mount (one-shot
53
+ * `transferControlToOffscreen` is safe because each effect invocation
54
+ * allocates its own element — StrictMode double-invoke compatible)
55
+ * 2. Spins up a `FluxionHost` + worker and registers the supplied layers
56
+ * 3. Observes container size / DPR and forwards resize messages
57
+ * 4. Terminates the worker + removes the canvas on unmount
58
+ *
59
+ * `layers`, `hostOptions`, and `onReady` are captured on mount only — future
60
+ * prop changes are intentionally ignored (this matches the v0.1 widget and
61
+ * keeps the hook predictable). Swap the `key` on the host element if you
62
+ * need a full re-initialization.
63
+ */
64
+ declare function useFluxionCanvas(options: UseFluxionCanvasOptions): UseFluxionCanvasResult;
65
+
66
+ /**
67
+ * Layer factory helpers. Each one binds a `kind` to the matching config
68
+ * type so callers never write the kind string themselves and the config
69
+ * fields are checked at the call site.
70
+ *
71
+ * ```ts
72
+ * useFluxionCanvas({
73
+ * layers: [
74
+ * axisGridLayer("axis", { xMode: "time", timeWindowMs: 5000 }),
75
+ * lineLayer("chart", { color: "#4fc3f7" }),
76
+ * ],
77
+ * });
78
+ * ```
79
+ */
80
+ declare function lineLayer(id: string, config?: LineChartConfig): FluxionLayerSpec;
81
+ declare function lineStaticLayer(id: string, config?: LineChartStaticConfig): FluxionLayerSpec;
82
+ declare function lidarLayer(id: string, config?: LidarScatterConfig): FluxionLayerSpec;
83
+ declare function axisGridLayer(id: string, config?: AxisGridConfig): FluxionLayerSpec;
84
+
85
+ interface ResizeInfo {
86
+ width: number;
87
+ height: number;
88
+ dpr: number;
89
+ }
90
+ /**
91
+ * Observes both element size (ResizeObserver) and devicePixelRatio changes
92
+ * (matchMedia on the current DPR). Fires `onResize` whenever either changes.
93
+ */
94
+ declare function useResizeObserver(ref: RefObject<HTMLElement>, onResize: (info: ResizeInfo) => void): void;
95
+
96
+ interface UseFluxionStreamOptions<T> {
97
+ /** Live host from `useFluxionCanvas` (or `null` during mount). */
98
+ host: FluxionHost | null;
99
+ /** Interval between ticks in ms. Typically `1000 / targetHz`. */
100
+ intervalMs: number;
101
+ /**
102
+ * One-shot initializer. Runs exactly once when `host` transitions from
103
+ * null to non-null. Use it to resolve typed handles (`host.line("id")`)
104
+ * or cache per-stream state. The returned value is passed as the second
105
+ * argument to `tick` on every interval fire.
106
+ */
107
+ setup: (host: FluxionHost) => T;
108
+ /**
109
+ * Called on every interval tick. `tMs` is host-relative (starts at 0
110
+ * when the first tick fires). Return the number of samples you pushed
111
+ * this tick for rate tracking; return 0 if you don't care.
112
+ */
113
+ tick: (tMs: number, state: T) => number;
114
+ }
115
+ interface UseFluxionStreamResult {
116
+ /** Samples pushed per second, refreshed every 500ms. 0 until first batch. */
117
+ rate: number;
118
+ }
119
+ /**
120
+ * Runs a `setInterval`-driven data pump against a FluxionRender host with
121
+ * built-in rate tracking. Handles the boilerplate that every streaming demo
122
+ * repeats:
123
+ *
124
+ * - Wait for `host` to become non-null
125
+ * - Establish a host-relative time origin on first tick
126
+ * - Resolve typed handles once (via `setup`)
127
+ * - Fire `tick(t, state)` on an interval
128
+ * - Accumulate sample counts into a 500ms-window rate estimate
129
+ * - Clean up the interval on unmount / host change
130
+ *
131
+ * `setup` and `tick` are captured by ref so unstable references (e.g. inline
132
+ * arrow functions) don't tear down the interval on every render. Only `host`
133
+ * and `intervalMs` drive the effect.
134
+ *
135
+ * Errors thrown inside `tick` are caught and logged — the interval keeps
136
+ * running. This matches the worker's own error-handling behavior.
137
+ */
138
+ declare function useFluxionStream<T>(opts: UseFluxionStreamOptions<T>): UseFluxionStreamResult;
139
+
140
+ /**
141
+ * Declaratively sync a layer's config to the host whenever the config
142
+ * actually changes. Compares via `JSON.stringify` so unstable object
143
+ * references with identical content don't trigger duplicate messages
144
+ * to the worker.
145
+ *
146
+ * Takes a full `FluxionLayerSpec` (kind + id + config) so the config type
147
+ * is checked against the layer kind at the call site. Pair with the layer
148
+ * factory helpers for ergonomic usage:
149
+ *
150
+ * ```ts
151
+ * useLayerConfig(host, axisGridLayer("axis", { timeWindowMs: windowMs }));
152
+ * ```
153
+ *
154
+ * Only the `config` payload is forwarded to the worker; `kind` is purely
155
+ * a compile-time discriminator. The layer must already exist in the worker
156
+ * (added at mount via `useFluxionCanvas`) — this hook does not create it.
157
+ *
158
+ * Keep configs small (shallow object of primitives). For large configs
159
+ * call `host.configLayer` directly in a memoized effect.
160
+ */
161
+ declare function useLayerConfig(host: FluxionHost | null, spec: FluxionLayerSpec): void;
162
+
163
+ interface FluxionCanvasProps {
164
+ layers: FluxionLayerSpec[];
165
+ style?: CSSProperties;
166
+ className?: string;
167
+ hostOptions?: FluxionHostOptions;
168
+ onReady?: (host: FluxionHost) => void;
169
+ }
170
+ interface FluxionCanvasHandle {
171
+ getHost(): FluxionHost | null;
172
+ }
173
+ /**
174
+ * Thin wrapper around {@link useFluxionCanvas}. Use this when you just want
175
+ * a filled-container canvas; reach for the hook directly when you need to
176
+ * control the wrapping DOM yourself.
177
+ */
178
+ declare const FluxionCanvas: react.ForwardRefExoticComponent<FluxionCanvasProps & react.RefAttributes<FluxionCanvasHandle>>;
179
+
180
+ export { FluxionCanvas, type FluxionCanvasHandle, type FluxionCanvasProps, FluxionHost, type FluxionLayerSpec, type ResizeInfo, type UseFluxionCanvasOptions, type UseFluxionCanvasResult, type UseFluxionStreamOptions, type UseFluxionStreamResult, axisGridLayer, lidarLayer, lineLayer, lineStaticLayer, useFluxionCanvas, useFluxionStream, useLayerConfig, useResizeObserver };
package/dist/react.js ADDED
@@ -0,0 +1,195 @@
1
+ import {
2
+ FluxionHost
3
+ } from "./chunk-56NZ4OEO.js";
4
+ import "./chunk-R7FLS7BG.js";
5
+
6
+ // src/widgets/fluxion-canvas/lib/layer-specs.ts
7
+ function lineLayer(id, config) {
8
+ return { id, kind: "line", config };
9
+ }
10
+ function lineStaticLayer(id, config) {
11
+ return { id, kind: "line-static", config };
12
+ }
13
+ function lidarLayer(id, config) {
14
+ return { id, kind: "lidar", config };
15
+ }
16
+ function axisGridLayer(id, config) {
17
+ return { id, kind: "axis-grid", config };
18
+ }
19
+
20
+ // src/widgets/fluxion-canvas/lib/use-resize-observer.ts
21
+ import { useEffect } from "react";
22
+ function useResizeObserver(ref, onResize) {
23
+ useEffect(() => {
24
+ const el = ref.current;
25
+ if (!el) return;
26
+ let mql = null;
27
+ let cancelled = false;
28
+ const fire = () => {
29
+ if (cancelled) return;
30
+ const rect = el.getBoundingClientRect();
31
+ onResize({
32
+ width: rect.width,
33
+ height: rect.height,
34
+ dpr: window.devicePixelRatio || 1
35
+ });
36
+ };
37
+ const subscribeDpr = () => {
38
+ if (mql) mql.removeEventListener("change", handleDpr);
39
+ mql = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);
40
+ mql.addEventListener("change", handleDpr);
41
+ };
42
+ const handleDpr = () => {
43
+ fire();
44
+ subscribeDpr();
45
+ };
46
+ const ro = new ResizeObserver(fire);
47
+ ro.observe(el);
48
+ subscribeDpr();
49
+ fire();
50
+ return () => {
51
+ cancelled = true;
52
+ ro.disconnect();
53
+ if (mql) mql.removeEventListener("change", handleDpr);
54
+ };
55
+ }, [ref, onResize]);
56
+ }
57
+
58
+ // src/widgets/fluxion-canvas/lib/use-fluxion-canvas.ts
59
+ import { useCallback, useEffect as useEffect2, useRef, useState } from "react";
60
+ function useFluxionCanvas(options) {
61
+ const containerRef = useRef(null);
62
+ const hostRef = useRef(null);
63
+ const [host, setHost] = useState(null);
64
+ const optionsRef = useRef(options);
65
+ optionsRef.current = options;
66
+ useEffect2(() => {
67
+ const container = containerRef.current;
68
+ if (!container) return;
69
+ const canvas = document.createElement("canvas");
70
+ canvas.style.display = "block";
71
+ canvas.style.width = "100%";
72
+ canvas.style.height = "100%";
73
+ container.appendChild(canvas);
74
+ const current = optionsRef.current;
75
+ const instance = new FluxionHost(canvas, current.hostOptions);
76
+ hostRef.current = instance;
77
+ for (const l of current.layers) instance.addLayer(l.id, l.kind, l.config);
78
+ setHost(instance);
79
+ current.onReady?.(instance);
80
+ return () => {
81
+ instance.dispose();
82
+ hostRef.current = null;
83
+ setHost(null);
84
+ if (canvas.parentNode === container) container.removeChild(canvas);
85
+ };
86
+ }, []);
87
+ const handleResize = useCallback((info) => {
88
+ const instance = hostRef.current;
89
+ if (!instance) return;
90
+ if (info.width === 0 || info.height === 0) return;
91
+ instance.resize(info.width, info.height, info.dpr);
92
+ }, []);
93
+ useResizeObserver(containerRef, handleResize);
94
+ return { containerRef, host };
95
+ }
96
+
97
+ // src/widgets/fluxion-canvas/lib/use-fluxion-stream.ts
98
+ import { useEffect as useEffect3, useRef as useRef2, useState as useState2 } from "react";
99
+ function useFluxionStream(opts) {
100
+ const { host, intervalMs } = opts;
101
+ const setupRef = useRef2(opts.setup);
102
+ const tickRef = useRef2(opts.tick);
103
+ setupRef.current = opts.setup;
104
+ tickRef.current = opts.tick;
105
+ const [rate, setRate] = useState2(0);
106
+ useEffect3(() => {
107
+ if (!host) {
108
+ setRate(0);
109
+ return;
110
+ }
111
+ const state = setupRef.current(host);
112
+ const t0 = Date.now();
113
+ let pushes = 0;
114
+ let lastReport = t0;
115
+ const interval = setInterval(() => {
116
+ const now = Date.now();
117
+ const t = now - t0;
118
+ let n = 0;
119
+ try {
120
+ n = tickRef.current(t, state) || 0;
121
+ } catch (err) {
122
+ console.error("[useFluxionStream] tick error:", err);
123
+ }
124
+ pushes += n;
125
+ if (now - lastReport >= 500) {
126
+ setRate(Math.round(pushes * 1e3 / (now - lastReport)));
127
+ pushes = 0;
128
+ lastReport = now;
129
+ }
130
+ }, intervalMs);
131
+ return () => {
132
+ clearInterval(interval);
133
+ setRate(0);
134
+ };
135
+ }, [host, intervalMs]);
136
+ return { rate };
137
+ }
138
+
139
+ // src/widgets/fluxion-canvas/lib/use-layer-config.ts
140
+ import { useEffect as useEffect4, useRef as useRef3 } from "react";
141
+ function useLayerConfig(host, spec) {
142
+ const lastSentRef = useRef3(null);
143
+ const serialized = JSON.stringify(spec.config);
144
+ useEffect4(() => {
145
+ if (!host) {
146
+ lastSentRef.current = null;
147
+ return;
148
+ }
149
+ if (lastSentRef.current === serialized) return;
150
+ lastSentRef.current = serialized;
151
+ if (spec.config !== void 0) {
152
+ host.configLayer(spec.id, spec.config);
153
+ }
154
+ }, [host, spec.id, serialized]);
155
+ }
156
+
157
+ // src/widgets/fluxion-canvas/ui/fluxion-canvas.tsx
158
+ import { forwardRef, useImperativeHandle } from "react";
159
+ import { jsx } from "react/jsx-runtime";
160
+ var FluxionCanvas = forwardRef(
161
+ function FluxionCanvas2({ layers, style, className, hostOptions, onReady }, ref) {
162
+ const { containerRef, host } = useFluxionCanvas({
163
+ layers,
164
+ hostOptions,
165
+ onReady
166
+ });
167
+ useImperativeHandle(ref, () => ({ getHost: () => host }), [host]);
168
+ return /* @__PURE__ */ jsx(
169
+ "div",
170
+ {
171
+ ref: containerRef,
172
+ className,
173
+ style: {
174
+ position: "relative",
175
+ width: "100%",
176
+ height: "100%",
177
+ ...style
178
+ }
179
+ }
180
+ );
181
+ }
182
+ );
183
+ export {
184
+ FluxionCanvas,
185
+ FluxionHost,
186
+ axisGridLayer,
187
+ lidarLayer,
188
+ lineLayer,
189
+ lineStaticLayer,
190
+ useFluxionCanvas,
191
+ useFluxionStream,
192
+ useLayerConfig,
193
+ useResizeObserver
194
+ };
195
+ //# sourceMappingURL=react.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/widgets/fluxion-canvas/lib/layer-specs.ts","../src/widgets/fluxion-canvas/lib/use-resize-observer.ts","../src/widgets/fluxion-canvas/lib/use-fluxion-canvas.ts","../src/widgets/fluxion-canvas/lib/use-fluxion-stream.ts","../src/widgets/fluxion-canvas/lib/use-layer-config.ts","../src/widgets/fluxion-canvas/ui/fluxion-canvas.tsx"],"sourcesContent":["import type { AxisGridConfig } from \"../../../entities/axis-grid-layer\";\nimport type { LidarScatterConfig } from \"../../../entities/lidar-scatter-layer\";\nimport type { LineChartConfig } from \"../../../entities/line-chart-layer\";\nimport type { LineChartStaticConfig } from \"../../../entities/line-chart-static-layer\";\nimport type { FluxionLayerSpec } from \"./use-fluxion-canvas\";\n\n/**\n * Layer factory helpers. Each one binds a `kind` to the matching config\n * type so callers never write the kind string themselves and the config\n * fields are checked at the call site.\n *\n * ```ts\n * useFluxionCanvas({\n * layers: [\n * axisGridLayer(\"axis\", { xMode: \"time\", timeWindowMs: 5000 }),\n * lineLayer(\"chart\", { color: \"#4fc3f7\" }),\n * ],\n * });\n * ```\n */\n\nexport function lineLayer(id: string, config?: LineChartConfig): FluxionLayerSpec {\n return { id, kind: \"line\", config };\n}\n\nexport function lineStaticLayer(\n id: string,\n config?: LineChartStaticConfig,\n): FluxionLayerSpec {\n return { id, kind: \"line-static\", config };\n}\n\nexport function lidarLayer(id: string, config?: LidarScatterConfig): FluxionLayerSpec {\n return { id, kind: \"lidar\", config };\n}\n\nexport function axisGridLayer(id: string, config?: AxisGridConfig): FluxionLayerSpec {\n return { id, kind: \"axis-grid\", config };\n}\n","import { useEffect, type RefObject } from \"react\";\n\nexport interface ResizeInfo {\n width: number;\n height: number;\n dpr: number;\n}\n\n/**\n * Observes both element size (ResizeObserver) and devicePixelRatio changes\n * (matchMedia on the current DPR). Fires `onResize` whenever either changes.\n */\nexport function useResizeObserver(\n ref: RefObject<HTMLElement>,\n onResize: (info: ResizeInfo) => void,\n): void {\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n\n let mql: MediaQueryList | null = null;\n let cancelled = false;\n\n const fire = () => {\n if (cancelled) return;\n const rect = el.getBoundingClientRect();\n onResize({\n width: rect.width,\n height: rect.height,\n dpr: window.devicePixelRatio || 1,\n });\n };\n\n const subscribeDpr = () => {\n if (mql) mql.removeEventListener(\"change\", handleDpr);\n mql = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);\n mql.addEventListener(\"change\", handleDpr);\n };\n const handleDpr = () => {\n fire();\n subscribeDpr();\n };\n\n const ro = new ResizeObserver(fire);\n ro.observe(el);\n subscribeDpr();\n fire();\n\n return () => {\n cancelled = true;\n ro.disconnect();\n if (mql) mql.removeEventListener(\"change\", handleDpr);\n };\n }, [ref, onResize]);\n}\n","import { type RefObject, useCallback, useEffect, useRef, useState } from \"react\";\nimport type { AxisGridConfig } from \"../../../entities/axis-grid-layer\";\nimport type { LidarScatterConfig } from \"../../../entities/lidar-scatter-layer\";\nimport type { LineChartConfig } from \"../../../entities/line-chart-layer\";\nimport type { LineChartStaticConfig } from \"../../../entities/line-chart-static-layer\";\nimport { FluxionHost, type FluxionHostOptions } from \"../../../features/host\";\nimport { type ResizeInfo, useResizeObserver } from \"./use-resize-observer\";\n\n/**\n * Declarative layer spec used by `useFluxionCanvas` and `<FluxionCanvas/>`.\n *\n * Discriminated union: `kind` narrows `config` to the matching layer-specific\n * type, so wrong fields are caught at compile time. Prefer the layer factory\n * helpers (`lineLayer`, `axisGridLayer`, etc.) for ergonomic construction —\n * they encode the kind so callers don't repeat themselves.\n */\nexport type FluxionLayerSpec =\n | { id: string; kind: \"line\"; config?: LineChartConfig }\n | { id: string; kind: \"line-static\"; config?: LineChartStaticConfig }\n | { id: string; kind: \"lidar\"; config?: LidarScatterConfig }\n | { id: string; kind: \"axis-grid\"; config?: AxisGridConfig };\n\nexport interface UseFluxionCanvasOptions {\n layers: FluxionLayerSpec[];\n hostOptions?: FluxionHostOptions;\n onReady?: (host: FluxionHost) => void;\n}\n\nexport interface UseFluxionCanvasResult {\n /**\n * Attach to a `<div>`. The hook appends a `<canvas>` child imperatively —\n * do NOT render a canvas yourself inside this container.\n */\n containerRef: RefObject<HTMLDivElement>;\n /**\n * Live host handle. `null` until the effect completes on the first mount.\n * Updates to a new instance if the component remounts.\n */\n host: FluxionHost | null;\n}\n\n/**\n * Low-level React hook that owns a FluxionRender worker + OffscreenCanvas.\n *\n * Consumers attach `containerRef` to any `<div>` and the hook:\n * 1. Creates a fresh `<canvas>` inside the div on mount (one-shot\n * `transferControlToOffscreen` is safe because each effect invocation\n * allocates its own element — StrictMode double-invoke compatible)\n * 2. Spins up a `FluxionHost` + worker and registers the supplied layers\n * 3. Observes container size / DPR and forwards resize messages\n * 4. Terminates the worker + removes the canvas on unmount\n *\n * `layers`, `hostOptions`, and `onReady` are captured on mount only — future\n * prop changes are intentionally ignored (this matches the v0.1 widget and\n * keeps the hook predictable). Swap the `key` on the host element if you\n * need a full re-initialization.\n */\nexport function useFluxionCanvas(\n options: UseFluxionCanvasOptions,\n): UseFluxionCanvasResult {\n const containerRef = useRef<HTMLDivElement>(null);\n const hostRef = useRef<FluxionHost | null>(null);\n const [host, setHost] = useState<FluxionHost | null>(null);\n\n // Stash the latest options in a ref so the mount effect can stay with\n // an empty dep array without going stale between StrictMode invocations.\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const canvas = document.createElement(\"canvas\");\n canvas.style.display = \"block\";\n canvas.style.width = \"100%\";\n canvas.style.height = \"100%\";\n container.appendChild(canvas);\n\n const current = optionsRef.current;\n const instance = new FluxionHost(canvas, current.hostOptions);\n hostRef.current = instance;\n for (const l of current.layers) instance.addLayer(l.id, l.kind, l.config);\n setHost(instance);\n current.onReady?.(instance);\n\n return () => {\n instance.dispose();\n hostRef.current = null;\n setHost(null);\n if (canvas.parentNode === container) container.removeChild(canvas);\n };\n }, []);\n\n const handleResize = useCallback((info: ResizeInfo) => {\n const instance = hostRef.current;\n if (!instance) return;\n if (info.width === 0 || info.height === 0) return;\n instance.resize(info.width, info.height, info.dpr);\n }, []);\n\n useResizeObserver(containerRef, handleResize);\n\n return { containerRef, host };\n}\n","import { useEffect, useRef, useState } from \"react\";\nimport type { FluxionHost } from \"../../../features/host\";\n\nexport interface UseFluxionStreamOptions<T> {\n /** Live host from `useFluxionCanvas` (or `null` during mount). */\n host: FluxionHost | null;\n /** Interval between ticks in ms. Typically `1000 / targetHz`. */\n intervalMs: number;\n /**\n * One-shot initializer. Runs exactly once when `host` transitions from\n * null to non-null. Use it to resolve typed handles (`host.line(\"id\")`)\n * or cache per-stream state. The returned value is passed as the second\n * argument to `tick` on every interval fire.\n */\n setup: (host: FluxionHost) => T;\n /**\n * Called on every interval tick. `tMs` is host-relative (starts at 0\n * when the first tick fires). Return the number of samples you pushed\n * this tick for rate tracking; return 0 if you don't care.\n */\n tick: (tMs: number, state: T) => number;\n}\n\nexport interface UseFluxionStreamResult {\n /** Samples pushed per second, refreshed every 500ms. 0 until first batch. */\n rate: number;\n}\n\n/**\n * Runs a `setInterval`-driven data pump against a FluxionRender host with\n * built-in rate tracking. Handles the boilerplate that every streaming demo\n * repeats:\n *\n * - Wait for `host` to become non-null\n * - Establish a host-relative time origin on first tick\n * - Resolve typed handles once (via `setup`)\n * - Fire `tick(t, state)` on an interval\n * - Accumulate sample counts into a 500ms-window rate estimate\n * - Clean up the interval on unmount / host change\n *\n * `setup` and `tick` are captured by ref so unstable references (e.g. inline\n * arrow functions) don't tear down the interval on every render. Only `host`\n * and `intervalMs` drive the effect.\n *\n * Errors thrown inside `tick` are caught and logged — the interval keeps\n * running. This matches the worker's own error-handling behavior.\n */\nexport function useFluxionStream<T>(\n opts: UseFluxionStreamOptions<T>,\n): UseFluxionStreamResult {\n const { host, intervalMs } = opts;\n const setupRef = useRef(opts.setup);\n const tickRef = useRef(opts.tick);\n setupRef.current = opts.setup;\n tickRef.current = opts.tick;\n\n const [rate, setRate] = useState(0);\n\n useEffect(() => {\n if (!host) {\n setRate(0);\n return;\n }\n\n const state = setupRef.current(host);\n const t0 = Date.now();\n let pushes = 0;\n let lastReport = t0;\n\n const interval = setInterval(() => {\n const now = Date.now();\n const t = now - t0;\n let n = 0;\n try {\n n = tickRef.current(t, state) || 0;\n } catch (err) {\n console.error(\"[useFluxionStream] tick error:\", err);\n }\n pushes += n;\n if (now - lastReport >= 500) {\n setRate(Math.round((pushes * 1000) / (now - lastReport)));\n pushes = 0;\n lastReport = now;\n }\n }, intervalMs);\n\n return () => {\n clearInterval(interval);\n setRate(0);\n };\n }, [host, intervalMs]);\n\n return { rate };\n}\n","import { useEffect, useRef } from \"react\";\nimport type { FluxionHost } from \"../../../features/host\";\nimport type { FluxionLayerSpec } from \"./use-fluxion-canvas\";\n\n/**\n * Declaratively sync a layer's config to the host whenever the config\n * actually changes. Compares via `JSON.stringify` so unstable object\n * references with identical content don't trigger duplicate messages\n * to the worker.\n *\n * Takes a full `FluxionLayerSpec` (kind + id + config) so the config type\n * is checked against the layer kind at the call site. Pair with the layer\n * factory helpers for ergonomic usage:\n *\n * ```ts\n * useLayerConfig(host, axisGridLayer(\"axis\", { timeWindowMs: windowMs }));\n * ```\n *\n * Only the `config` payload is forwarded to the worker; `kind` is purely\n * a compile-time discriminator. The layer must already exist in the worker\n * (added at mount via `useFluxionCanvas`) — this hook does not create it.\n *\n * Keep configs small (shallow object of primitives). For large configs\n * call `host.configLayer` directly in a memoized effect.\n */\nexport function useLayerConfig(host: FluxionHost | null, spec: FluxionLayerSpec): void {\n const lastSentRef = useRef<string | null>(null);\n const serialized = JSON.stringify(spec.config);\n\n useEffect(() => {\n if (!host) {\n lastSentRef.current = null;\n return;\n }\n if (lastSentRef.current === serialized) return;\n lastSentRef.current = serialized;\n if (spec.config !== undefined) {\n host.configLayer(spec.id, spec.config);\n }\n // `spec` is referenced through `serialized` + `spec.id`; the raw object\n // would defeat the diff (new identity every render).\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [host, spec.id, serialized]);\n}\n","import { type CSSProperties, forwardRef, useImperativeHandle } from \"react\";\nimport type { FluxionHost, FluxionHostOptions } from \"../../../features/host\";\nimport { type FluxionLayerSpec, useFluxionCanvas } from \"../lib/use-fluxion-canvas\";\n\nexport interface FluxionCanvasProps {\n layers: FluxionLayerSpec[];\n style?: CSSProperties;\n className?: string;\n hostOptions?: FluxionHostOptions;\n onReady?: (host: FluxionHost) => void;\n}\n\nexport interface FluxionCanvasHandle {\n getHost(): FluxionHost | null;\n}\n\n/**\n * Thin wrapper around {@link useFluxionCanvas}. Use this when you just want\n * a filled-container canvas; reach for the hook directly when you need to\n * control the wrapping DOM yourself.\n */\nexport const FluxionCanvas = forwardRef<FluxionCanvasHandle, FluxionCanvasProps>(\n function FluxionCanvas({ layers, style, className, hostOptions, onReady }, ref) {\n const { containerRef, host } = useFluxionCanvas({\n layers,\n hostOptions,\n onReady,\n });\n\n useImperativeHandle(ref, () => ({ getHost: () => host }), [host]);\n\n return (\n <div\n ref={containerRef}\n className={className}\n style={{\n position: \"relative\",\n width: \"100%\",\n height: \"100%\",\n ...style,\n }}\n />\n );\n },\n);\n"],"mappings":"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","names":["useEffect","useEffect","useEffect","useRef","useState","useEffect","useRef","FluxionCanvas"]}