/** @jsxImportSource react */ import { forwardRef, memo, useEffect, useRef, type CSSProperties, type ReactNode, type Ref, } from "react"; import type { BitmapSurface, BoxRenderable, ChartCrosshairOverlay, ChartVectorShape, } from "../../../../ui/host"; import { WebBox } from "./box"; const CanvasBitmap = memo(function CanvasBitmap({ bitmap }: { bitmap: BitmapSurface }) { const canvasRef = useRef(null); useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; const context = canvas.getContext("2d"); if (!context) return; const pixels = bitmap.pixels.buffer instanceof ArrayBuffer ? new Uint8ClampedArray(bitmap.pixels.buffer, bitmap.pixels.byteOffset, bitmap.pixels.byteLength) : new Uint8ClampedArray(bitmap.pixels); context.putImageData(new ImageData(pixels, bitmap.width, bitmap.height), 0, 0); }, [bitmap]); return ( ); }); const BoxLayer = memo(function BoxLayer({ bitmap, index }: { bitmap: BitmapSurface; index: number }) { return (
); }); const ChartVectors = memo(function ChartVectors({ vectors }: { vectors: readonly ChartVectorShape[] }) { if (vectors.length === 0) return null; return ( <> {vectors.map((shape) => { const strokeWidth = shape.strokeWidth ?? 1.4; const [first, second] = shape.points; if (shape.box && first && second) { return ( ); } return ( `${point.x},${point.y}`).join(" ")} fill="none" stroke={shape.color} strokeWidth={strokeWidth} strokeLinecap="round" strokeLinejoin="round" vectorEffect="non-scaling-stroke" /> ); })} {/* Ratio space is anisotropic, so handles are placed instead of scaled. */} {vectors.flatMap((shape) => shape.handles ? shape.points.map((point, index) => (
)) : [])} ); }); function ChartCrosshair({ bitmap, crosshair, }: { bitmap: BitmapSurface | null; crosshair: ChartCrosshairOverlay | null; }) { if (!bitmap || !crosshair) return null; const x = bitmap.width <= 1 ? 0 : (crosshair.pixelX / (bitmap.width - 1)) * 100; const y = crosshair.pixelY === null ? null : bitmap.height <= 1 ? 0 : (crosshair.pixelY / (bitmap.height - 1)) * 100; const clampedX = Math.max(0, Math.min(100, x)); const clampedY = y === null ? null : Math.max(0, Math.min(100, y)); return ( <>
{clampedY === null ? null : ( <>
)} ); } export const WebChartSurface = forwardRef & { children?: ReactNode }>( function WebChartSurface({ children, ...props }, ref) { const bitmap = (props.bitmap ?? null) as BitmapSurface | null; const bitmaps = (props.bitmaps ?? null) as readonly BitmapSurface[] | null; const layers = bitmaps ?? (bitmap ? [bitmap] : []); const crosshair = (props.crosshair ?? null) as ChartCrosshairOverlay | null; const vectors = (props.vectors ?? null) as readonly ChartVectorShape[] | null; const baseLayer = layers[0] ?? null; return ( } data-gloom-role={(props["data-gloom-role"] as string | undefined) ?? "chart-surface"} style={{ position: "relative", overflow: "hidden", touchAction: "none", overscrollBehavior: "none", ...(props.style as CSSProperties | undefined), }} > {layers.length > 0 ? layers.map((layer, index) => ( )) : children as ReactNode} ); }, );