import { useMemo } from "react";
import {
Box,
ChartSurface,
Text,
useNativeRenderer,
useUiCapabilities,
useUiHost,
} from "../../ui";
import { useThemeColors } from "../../theme/theme-context";
import type { PricePoint } from "../../types/financials";
import { resolveNativeBitmapSize, shouldRenderNativeBitmap } from "../chart/native/bitmap-support";
import { renderSparklineBitmap } from "./bitmap";
import {
buildSamples,
colorWithAlpha,
resolveSparklineHistory,
sparklineColor,
sparklineValues,
svgAreaPath,
svgPath,
type PriceSparklinePeriod,
type PriceSparklineTrend,
} from "./model";
import { renderPriceSparkline } from "./index";
export const PRICE_SPARKLINE_COLUMN_ID = "sparkline";
export const PRICE_SPARKLINE_PERIOD_LABEL = "1M";
export { resolvePriceSparklineRange } from "./model";
export type { PriceSparklinePeriod, PriceSparklineTrend } from "./model";
const SPARKLINE_HEIGHT = 1;
function DesktopPriceSparkline({
values,
width,
height,
color,
emptyColor,
}: {
values: number[];
width: number;
height: number;
color: string;
emptyColor: string;
}) {
const svgWidth = Math.max(24, width * 8);
const svgHeight = Math.max(18, height * 18);
const path = svgPath(buildSamples(values, svgWidth, svgHeight, 2));
if (!path) return {" "};
return (
);
}
function TerminalPriceSparkline({
priceHistory,
values,
width,
height,
color,
area = false,
}: {
priceHistory: PricePoint[];
values: number[];
width: number;
height: number;
color: string;
area?: boolean;
}) {
const { nativeCharts, cellWidthPx = 8, cellHeightPx = 18, pixelRatio = 1 } = useUiCapabilities();
const nativeRenderer = useNativeRenderer();
const rendererResolution = nativeRenderer.resolution;
const rendererCapabilities = nativeRenderer.capabilities;
const rendererTerminalWidth = nativeRenderer.terminalWidth;
const rendererTerminalHeight = nativeRenderer.terminalHeight;
const bitmap = useMemo(() => {
if (values.length < 2 || !shouldRenderNativeBitmap(nativeRenderer, nativeCharts === true)) return null;
const bitmapSize = resolveNativeBitmapSize({
width,
height,
resolution: rendererResolution,
terminalWidth: rendererTerminalWidth,
terminalHeight: rendererTerminalHeight,
cellWidthPx,
cellHeightPx,
pixelRatio,
});
return renderSparklineBitmap(values, bitmapSize.pixelWidth, bitmapSize.pixelHeight, color, { area, compact: height <= 1 });
}, [
area,
cellHeightPx,
cellWidthPx,
color,
height,
nativeCharts,
nativeRenderer,
pixelRatio,
rendererCapabilities,
rendererResolution,
rendererTerminalHeight,
rendererTerminalWidth,
values,
width,
]);
const fallback = useMemo(
() => renderPriceSparkline(priceHistory, { width, height, maxPoints: priceHistory.length }),
[height, priceHistory, width],
);
return (
{fallback ? {fallback} : null}
);
}
export function PriceSparkline({
priceHistory,
width,
trend,
period = "1M",
height = SPARKLINE_HEIGHT,
area = false,
}: {
priceHistory: PricePoint[] | undefined;
width: number;
trend?: PriceSparklineTrend;
period?: PriceSparklinePeriod;
height?: number;
area?: boolean;
}) {
const colors = useThemeColors();
const uiHost = useUiHost();
const sparklineHistory = useMemo(() => resolveSparklineHistory(priceHistory ?? [], period), [period, priceHistory]);
const values = useMemo(() => sparklineValues(sparklineHistory), [sparklineHistory]);
if (values.length < 2) {
return {" "};
}
const color = sparklineColor(values, trend, colors);
return uiHost.kind === "desktop-web"
?
: ;
}
export function PriceAreaSparklineBackground({
priceHistory,
trend,
period = "1M",
insetTop = 0,
}: {
priceHistory: PricePoint[] | undefined;
trend?: PriceSparklineTrend;
period?: PriceSparklinePeriod;
/** Pixels kept clear above the chart, so the line never crosses header text. */
insetTop?: number;
}) {
const colors = useThemeColors();
const uiHost = useUiHost();
const sparklineHistory = useMemo(() => resolveSparklineHistory(priceHistory ?? [], period), [period, priceHistory]);
const values = useMemo(() => sparklineValues(sparklineHistory), [sparklineHistory]);
if (uiHost.kind !== "desktop-web" || values.length < 2) return null;
const color = sparklineColor(values, trend, colors);
const baseline = 100;
const samples = buildSamples(values, 100, baseline, 0);
const linePath = svgPath(samples);
const areaPath = svgAreaPath(samples, baseline);
if (!linePath || !areaPath) return null;
return (
);
}