import { useMemo } from "react";
import { CompositeChart, EmptyState, StaticChartSurface, type StaticChartOverlay } from "../../../components";
import { formatCompositeSeriesValue } from "../../../components/chart/composite/format";
import { resolveChartPalette } from "../../../components/chart/core/palette";
import { useThemeColors } from "../../../theme/theme-context";
import {
volatilityCurveChartModel, volatilityHistorySeries, volatilityIndexHistoryPoints,
} from "./chart-model";
import type { FredVolatilityHistory, VolatilityBoardRow, VolatilityCurve } from "./model";
interface ChartSize { width: number; height: number }
export function VolatilityCurveChart({ curve, width, height, focused = false }: ChartSize & { curve: VolatilityCurve; focused?: boolean }) {
const colors = useThemeColors();
const model = useMemo(() => volatilityCurveChartModel(curve.points), [curve.points]);
const palette = resolveChartPalette(colors);
const lineColor = curve.termState === "inverted" ? colors.warning : palette.lineColor;
const observations: StaticChartOverlay[] = [{ id: "Observed close", color: lineColor, style: "points",
points: model.points.flatMap((point, index) => Number.isFinite(point.close) ? [{ index, value: point.close }] : []),
}];
if (!observations[0]!.points.length) return ;
return value.toFixed(1)}
xAxisTicks={model.ticks} formatXAxisCursorValue={model.formatCursor} focused={focused} />;
}
const HISTORY_PANELS = [{ id: "vol", height: 2 }, { id: "ratio", height: 1 }];
/** VIX 30D and 3M over the 3M/30D ratio on one date axis, with the flat line at 1.00. */
export function VolatilityHistoryChart({ fred, width, height, focused = false }: ChartSize & { fred: FredVolatilityHistory; focused?: boolean }) {
const colors = useThemeColors();
const palette = resolveChartPalette(colors);
const ratioColor = fred.termState === "inverted" ? colors.warning : palette.lineColor;
const series = useMemo(() => volatilityHistorySeries(fred,
{ spot: colors.warning, threeMonth: colors.borderFocused, ratio: ratioColor, flat: colors.textDim }),
[fred, colors.warning, colors.borderFocused, colors.textDim, ratioColor]);
// The flat reference is drawn, not named: its level is the whole point of it.
const legendSeries = useMemo(() => series.filter((entry) => entry.id !== "flat"), [series]);
if (!series.some((entry) => entry.id !== "flat" && entry.points.some((point) => point.value != null))) {
return ;
}
return entry.unitGroup === "ratio" ? value.toFixed(2) : formatCompositeSeriesValue(value, entry)}
formatAxisValue={(value, domain) => domain.unitGroup === "ratio" ? value.toFixed(2) : `${value.toFixed(0)}%`}
colors={{ background: palette.bgColor, grid: palette.gridColor, crosshair: palette.crosshairColor, text: colors.text,
textDim: palette.axisColor, negative: colors.negative }} />;
}
export function VolatilityIndexHistoryChart({ row, width, height, focused = false }: ChartSize & { row: VolatilityBoardRow; focused?: boolean }) {
const colors = useThemeColors();
const points = useMemo(() => volatilityIndexHistoryPoints(row.history, row.missingDates), [row.history, row.missingDates]);
if (!points.some((point) => Number.isFinite(point.close))) return ;
return value.toFixed(1)} focused={focused} />;
}