[data-reveal]{opacity:1!important;transform:none!important}
Charts and Widgets
Interfaces

Charts and Widgets

The chart module and the three card modules cover dashboard surfaces: a chart of a series, a single figure with a delta, a ranked list, and the period selector the others read their date range from.

Overview

The chart module and the three card modules cover dashboard surfaces: a chart of a series, a single figure with a delta, a ranked list, and the period selector the others read their date range from. Each one is a builder that produces a component description; each one either carries its data inline (staticDataset, staticItems, staticValue) or names a fetchUrl the frontend calls.

All of them are exported from the package root; the charts also have their own subpath.

import {
  ChartArea,
  ChartCard,
  KpiCard,
  PeriodSelector,
  TopListCard,
} from "@antelopejs/interface-dms/base";
// charts only
import { ChartLine, ChartType } from "@antelopejs/interface-dms/base/chart";

A Dashboard in One Page

The widgets compose through the layout builders from Layout and Containers. One PeriodSelector declares a scope id; every widget that names the same periodScope follows its range.

import {
  PageController,
  pagesCategory,
  RegisterPage,
} from "@antelopejs/interface-dms/page";
import {
  ChartArea,
  ChartCard,
  KpiCard,
  PeriodSelector,
  TopListCard,
} from "@antelopejs/interface-dms/base";
import { Grid, GridRow } from "@antelopejs/interface-dms/base/grid";
import { DefaultLayout } from "@antelopejs/interface-dms/base/layouts";

const SCOPE_ID = "ai-overview";

@RegisterPage()
export class OverviewPage extends PageController(
  "overview",
  { displayName: "Overview", icon: "i-ph-chart-line", category: pagesCategory },
  DefaultLayout({ fullWidth: true }),
) {
  static periodSelector = PeriodSelector({
    id: SCOPE_ID,
    align: "right",
    defaultPreset: "last-7-days",
    defaultComparison: "previous-period",
    presets: ["today", "last-7-days", "last-30-days"],
  });

  static kpis = Grid({ gap: "1rem" }).child(
    "kpiRow",
    GridRow().child(
      "actions",
      KpiCard({
        title: "Actions",
        icon: "i-ph-lightning",
        fetchUrl: "/ai/metrics/kpi/actions",
        periodScope: SCOPE_ID,
        valueFormat: "compact",
        compareLabel: "vs previous period",
      }),
    ),
  );

  static activityRow = Grid({ gap: "1rem" }).child(
    "row",
    GridRow()
      .child(
        "activityChart",
        ChartCard({
          title: "AI activity",
          description: "Actions over the selected period",
          icon: "i-ph-chart-line",
          fetchUrl: "/ai/metrics/series",
          periodScope: SCOPE_ID,
          valueFormat: "compact",
          chart: ChartArea({ xaxisType: "datetime" }),
        }),
        { colSpan: 2 },
      )
      .child(
        "topSkills",
        TopListCard({
          title: "Top skills",
          fetchUrl: "/ai/metrics/top-skills?limit=5",
          periodScope: SCOPE_ID,
        }),
      ),
  );
}

Chart Builders

There is one builder per chart type. Each takes its own props with the type discriminant optional — the builder fills it in — and returns a ComponentBuilder for that props type.

BuilderChartTypeProps interface
ChartLineLINELineChartProps
ChartAreaAREAAreaChartProps
ChartRangeAreaRANGE_AREARangeAreaChartProps
ChartBarBARBarChartProps
ChartColumnCOLUMNColumnChartProps
ChartScatterSCATTERScatterChartProps
ChartMixedMIXEDMixedChartProps
ChartRadarRADARRadarChartProps
ChartCandlestickCANDLESTICKCandlestickChartProps
ChartDonutDONUTDonutChartProps
ChartPiePIEPieChartProps
ChartRadialBarRADIAL_BARRadialBarChartProps
ChartHeatmapHEATMAPHeatmapChartProps

ChartType is the enum behind the discriminant: LINE, AREA, RANGE_AREA, BAR, COLUMN, SCATTER, DONUT, PIE, MIXED, RADAR, RADIAL_BAR, HEATMAP, CANDLESTICK. AnyChartProps is the union of all thirteen props interfaces.

Every builder gives the component a display name from title, falling back to the chart's own name ("Line Chart", "Donut Chart", …), and a matching icon.

Common Options

Every chart props interface extends BaseComponentProps and adds:

OptionTypeDescription
titlestringChart title, also the component display name.
descriptionstringChart description.
colorChartColorOne colour or a colour list.
heightstringCSS height.
widthnumberWidth.
showTooltipbooleanShow the value tooltip.
showLegendbooleanShow the series legend.
legendPositionChartLegendPosition"top" or "bottom"; defaults to top for the axis charts, bottom for the circular ones.
fetchUrlstringURL the chart loads its data from.
fetchUrlMethodHttpMethodMethod for fetchUrl.
periodScopestringId of the PeriodSelector whose range this chart follows.
realtimeTopicstring | string[]Realtime topics to subscribe the page to.
rawOptionsKeyValuePair[]Escape hatch: extra key/value options passed through to the chart renderer. A plain key replaces that whole section; a dotted key (markers.size) sets one option inside it.
rawCssKeyValuePair[]Escape hatch: extra key/value CSS declarations.

KeyValuePair is { key: string; value: string }.

When realtimeTopic is set, the builder registers each topic against the page it is declared on, using RegisterPageTopic from @antelopejs/interface-dms/realtime.

Cartesian Charts

LineChartProps, AreaChartProps, RangeAreaChartProps, BarChartProps, ColumnChartProps, ScatterChartProps, MixedChartProps, RadarChartProps and CandlestickChartProps share a second layer of options on top of the common ones:

OptionTypeDescription
staticDatasetChartSeries[]Inline data, as an alternative to fetchUrl.
showGridbooleanDraw the background grid.
yRange{ min: number; max: number }Fixed Y bounds.
autoYRangeChartAutoYRangeOpt-in annotation-aware native Y bounds, padded and rounded to a round step. Explicit yRange wins.
comparisonStyleComparisonStyleHow the comparison series is drawn: "dashed", "solid" or "dimmed".
smoothbooleanSmooth the line between points.
curveChartCurveLine interpolation, honoured by the line, area, rangeArea and mixed types. Takes precedence over smooth when both are set.
annotationsChartAnnotation[]Horizontal reference lines on the Y axis.
syncGroupstringCharts sharing a group name share their crosshair and tooltip position.
xaxisType"category" | "datetime" | "numeric"Interpretation of the X values.

ChartAutoYRange exposes padding?: number, defaulting to 0.08. The renderer extends native bounds to include finite annotation y and y2 coordinates, then subtracts abs(min) * padding from the lower bound and adds abs(max) * padding to the upper bound. At zero, the magnitude is 1 instead. Zero padding is valid; negative or nonfinite padding uses the default. Native series handling remains with the chart engine, including range and candle points and stacked series. This option does not add data to empty charts or enforce a zero baseline. The existing raw yaxis escape hatch remains the final override.

And each adds its own:

PropsExtra options
LineChartPropsstrokeWidth?: number
AreaChartPropsfillOpacity?: number, stacked?: boolean
RangeAreaChartPropsfillOpacity?: number
BarChartPropsstacked?: boolean, barWidth?: number, roundedCorners?: boolean, orientation?: AxeOrientation
ColumnChartPropsstacked?: boolean, columnWidth?: number, roundedCorners?: boolean
ScatterChartPropspointSize?: number, showLabels?: boolean
MixedChartPropsseriesDefs?: MixedChartSeriesDef[]
RadarChartPropsfillOpacity?: number
CandlestickChartProps—

MixedChartSeriesDef declares how one series of a mixed chart is drawn: { name: string; type: ChartType.LINE | ChartType.AREA | ChartType.COLUMN | ChartType.RANGE_AREA; color?: ChartColorValue }.

ChartCurve is "smooth" | "straight" | "stepline". Prefer it to smooth, which only chooses between the first two.

ChartAnnotation draws a horizontal reference on the Y axis — a spend cap, a max bound, a volume capacity:

FieldTypeDescription
ynumberValue the line sits at. Required.
y2numberSet it to shade the band between y and y2 instead of drawing a line.
labelstringText on the annotation.
colorChartColorValueAnnotation colour.
dashedbooleanDraw the line dashed.

syncGroup is what turns a grid of separate metrics into one readable timeline: give every chart on the page the same group name and hovering one moves the crosshair on all of them.

Circular Charts

CircularChartProps extends the common options with staticDataset?: DonutRecord[] — a flat list of { label: string; value: number } rather than series.

PropsExtra options
DonutChartPropscentralLabel?: string, centralSubLabel?: string, arcWidth?: number
PieChartProps—
RadialBarChartPropshollowSize?: string, showTotal?: boolean

Heatmap

HeatmapChartProps extends the common options directly — not the cartesian layer — with staticDataset?: ChartSeries[], shadeIntensity?: number and distributed?: boolean.

Series and Points

import type {
  ChartCandlePoint,
  ChartSeries,
  ChartSeriesPoint,
} from "@antelopejs/interface-dms/base/chart";

const series: ChartSeries[] = [
  {
    name: "Actions",
    data: [
      { x: "2026-07-01", y: 12 },
      { x: "2026-07-02", y: 18 },
      { x: "2026-07-03", y: null },
    ],
  },
];
TypeShape
ChartSeriesname: string, data: Array<ChartSeriesPoint | ChartCandlePoint | ChartRangePoint | number>, type?: ChartType, color?: ChartColorValue, strokeWidth?: number, strokeDashArray?: number
ChartSeriesPointx: string | number | Date, y: number | null, label?: string
ChartCandlePointx: string | number | Date, y: [number, number, number, number]
ChartRangePointx: string | number | Date, y: [number, number] — the low and high bound a range area fills between
DonutRecordlabel: string, value: number

A null y marks a gap. ChartSeries.type overrides the chart's own type for that series, which is what a mixed chart uses.

Set strokeWidth and strokeDashArray on each series, including comparison series, to keep stroke styling attached to the data when series are reordered, added or removed. Both values are non-negative pixel lengths: strokeWidth: 0 hides the stroke and strokeDashArray: 0 draws a solid line. A positive dash length draws repeating dashes; it is not a custom SVG dash pattern.

Each property falls back independently to the existing chart configuration when omitted. Series values override the chart-level strokeWidth and comparison defaults. Without series styles, line widths remain 3 pixels (1.5 for comparison series), and comparison dashes remain 4 pixels for comparisonStyle: "dashed". rawOptions still wins last: a stroke entry replaces the entire generated stroke section, without deep merging.

These properties also survive mixed-series definition projection. Set them on the data series, not on the positional seriesDefs. Rendering follows the chart type's stroke support; circular records do not expose series strokes. This API does not change fills, interpolation, markers, axes or legends.

Colours and Formats

TypeValues
ColorName"primary", "secondary", "success", "info", "warning", "error", "neutral", "accent"
ChartColorValueColorName or a #…, rgb… or hsl… string
ChartColorChartColorValue | ChartColorValue[]
ComparisonStyle"dashed", "solid", "dimmed"
ValueFormat"number", "currency", "percent", "compact"

ColorName is the chart palette and is distinct from the Color enum in Shared Types: it adds accent and it is a string union rather than an enum. ValueFormat is used by the three card components rather than by the charts themselves.

Chart Events

Namespace memberValue
ChartEvents.SEGMENT_CLICK"DmsComponent.Chart.SegmentClick"
ChartEvents.POINT_CLICK"DmsComponent.Chart.PointClick"

ChartCard

ChartCard wraps a chart in a card that owns the title, the fetch URL and the delta display, and holds the chart as a child under the id "chart".

import { ChartArea, ChartCard } from "@antelopejs/interface-dms/base";

const card = ChartCard({
  title: "Revenue",
  description: "Billed amount over the selected period",
  icon: "i-ph-currency-eur",
  fetchUrl: "/metrics/revenue",
  periodScope: "finance",
  valueFormat: "currency",
  currencyCode: "EUR",
  showDelta: true,
  chart: ChartArea({ xaxisType: "datetime" }),
});

ChartCardBuilderOptions is ChartCardProps plus one required field, chart: Component. The chart is taken out of the options and attached as the card's child, so the serialized card options are exactly ChartCardProps.

ChartCardProps extends BaseComponentProps:

OptionTypeDescription
titlestringCard title. Required; also the component display name.
descriptionstringCard description.
iconstringCard icon. Defaults to "i-ph-chart-line" for the component metadata.
fetchUrlstringURL the card loads its data from.
fetchUrlMethodHttpMethodMethod for fetchUrl.
periodScopestringId of the PeriodSelector to follow.
valueFormatValueFormatHow the headline value is formatted.
currencyCodestringCurrency for valueFormat: "currency".
valuePrecisionValuePrecisionFixed fraction digits or "native" Intl defaults; omitted keeps legacy formatting. Also applies to the previous value and nested chart's value axis and tooltip.
showDeltabooleanShow the change against the comparison period.
showLegendbooleanShow the primary / comparison legend. Defaults to true.
showSeriesLegendbooleanShow the nested chart's legend, naming each series. Defaults to false.
primaryLabelstringLabel of the primary series.
comparisonLabelstringLabel of the comparison series.

KpiCard

KpiCard shows a single figure with an optional delta and sparkline.

import { KpiCard } from "@antelopejs/interface-dms/base";

const denied = KpiCard({
  title: "Denied",
  icon: "i-ph-prohibit",
  fetchUrl: "/ai/metrics/kpi/denied",
  valueFormat: "compact",
  invert: true,
  compareLabel: "vs previous period",
});

KpiCardProps extends BaseComponentProps:

OptionTypeDescription
titlestringCard title. Required; also the component display name.
variant"default" | "stat""stat" is the compact look: mono uppercase label, bare icon.
descriptionstringCard description.
iconstringCard icon. Defaults to "i-ph-trend-up" for the component metadata.
fetchUrlstringURL the card loads its value from.
fetchUrlMethodHttpMethodMethod for fetchUrl.
periodScopestringId of the PeriodSelector to follow.
valueFormatValueFormatHow the value is formatted.
currencyCodestringCurrency for valueFormat: "currency".
valuePrecisionValuePrecisionFixed fraction digits or "native" Intl defaults; omitted keeps legacy formatting.
showDeltabooleanShow the change against the comparison period.
showSparklinebooleanShow the sparkline.
sparklineAccentChartColorValue | "auto"Sparkline colour.
invertbooleanTreat an increase as bad — for metrics where lower is better.
compareLabelstringLabel next to the delta.
staticValuenumberInline value, as an alternative to fetchUrl.
staticDeltanumberInline delta, in percent (12.5 reads +12.5 %).
staticSparklinenumber[]Inline sparkline points.

TopListCard

TopListCard shows a ranked list of items, each with a value and optionally a delta, a sparkline and an avatar.

import { TopListCard } from "@antelopejs/interface-dms/base";

const topSkills = TopListCard({
  title: "Top skills",
  description: "Most-used tools over the selected period",
  fetchUrl: "/ai/metrics/top-skills?limit=5",
  periodScope: "ai-overview",
  showRank: true,
  highlightTopN: 3,
});

TopListCardProps extends BaseComponentProps:

OptionTypeDescription
titlestringCard title. Required; also the component display name.
descriptionstringCard description.
fetchUrlstringURL the card loads its items from.
fetchUrlMethodHttpMethodMethod for fetchUrl.
periodScopestringId of the PeriodSelector to follow.
valueFormatValueFormatHow each item's value is formatted.
currencyCodestringCurrency for valueFormat: "currency".
valuePrecisionValuePrecisionFixed fraction digits or "native" Intl defaults; omitted keeps legacy formatting.
valueSuffixstringUnit written right after each formatted value, space included (" ms"); a $-prefixed key is translated. Not added to a valueLabel.
showRankbooleanShow the rank number.
highlightTopNnumberHow many leading items are highlighted.
rankColorChartColorValueColour of the rank marker.
showDeltabooleanShow each item's delta.
showSparklinebooleanShow each item's sparkline.
sparklineAccentChartColorValue | "auto"Sparkline colour.
invertbooleanTreat an increase as bad.
badgeColorChartColorValueColour of the value badge.
maxHeightstringCSS max-height before the list scrolls.
staticItemsTopListItem[]Inline items, as an alternative to fetchUrl.
emptyLabelstringText shown when there are no items.
skeletonCountnumberPlaceholder rows while fetchUrl loads: the "top N" it answers. Optional. Defaults to 10.
actionsBlockLinkAction[]Link buttons in the card head, after the period badge; quiet ghost buttons unless one sets its own variant or color.

TopListItem is the item shape, the element type of staticItems:

FieldTypeDescription
idstring | numberItem identifier. Required.
titleBlockTextItem label: a string or a ComposedText. Required.
valuenumberItem value. Required.
valueLabelBlockTextThe value as written, in place of the formatted value (which still sizes the bar).
descriptionBlockTextSecondary line.
deltanumber | nullChange against the comparison period, in percent (12.5 reads +12.5 %).
sparklinenumber[]Sparkline points.
iconstringLeading icon.
avatarTopListItemAvatarLeading avatar, { src: string; alt?: string }.
tostringLink target for the item. {{params.X}} / {{query.X}} name the page URL; a query pair whose token has no value is left out.

PeriodSelector

PeriodSelector owns a date range and a comparison mode, and publishes them under its id. Every widget carrying the same value in periodScope uses that range.

import { PeriodSelector } from "@antelopejs/interface-dms/base";

const selector = PeriodSelector({
  id: "finance",
  align: "right",
  defaultPreset: "this-month",
  defaultComparison: "previous-year",
  presets: ["this-month", "last-month", "ytd", "custom"],
  presetLabels: { ytd: "Year to date" },
  showRangeLabel: true,
});

PeriodSelectorProps extends BaseComponentProps:

OptionTypeDescription
idstringScope identifier other widgets name in periodScope, and the prefix of the selector's URL keys. Defaults to "page" (DEFAULT_PERIOD_SCOPE).
defaultPresetPeriodPresetPreset selected when neither the URL nor the user's last choice names one.
defaultComparisonPeriodComparisonComparison selected when neither the URL nor the user's last choice names one.
presetsPeriodPreset[]Presets offered.
comparisonsPeriodComparison[]Comparisons offered.
presetLabelsPartial<Record<PeriodPreset, string>>Overrides for preset labels.
comparisonLabelsPartial<Record<PeriodComparison, string>>Overrides for comparison labels.
alignPeriodAlign"left", "center" or "right".
size"xs" | "sm" | "md" | "lg" | "xl"Control size.
showRangeLabelbooleanShow the resolved date range as text.
rangeLabelPeriodRangeLabel"absolute" (default): both bounds. "relative": the start of a window reaching now, "since yesterday 11:52".
comparisonControlPeriodComparisonControl"menu", "toggle" (one inline button switching a comparison on and off) or "hidden" (nothing drawn, the comparison kept on). Defaults to "toggle" when comparisons offers a single comparison besides "none", "menu" otherwise.
rememberKeystringSelectors carrying the same key share the remembered last choice, on any page.
variantPeriodSelectorVariant"default" or "segmented".
maxDaysnumberThe longest custom range, in days (both ends included), the user can pick in the calendar or the date fields. Presets and a range given by the URL are not limited.

PeriodPreset is "last-hour", "last-24h", "today", "yesterday", "last-7-days", "last-30-days", "last-90-days", "this-month", "last-month", "this-quarter", "last-quarter", "ytd", "last-year", "all" or "custom". "all" is every period: it has no bounds, so the bound widgets are sent no from / to. PeriodComparison is "none", "previous-period", "previous-year" or "custom". PeriodAlign is "left" | "center" | "right".

The selector keeps its period in the page URL, under keys prefixed with its id: ?<id>.period=<preset>&<id>.compare=<comparison> for a preset, ?<id>.from=<date>&<id>.to=<date> for a custom range (an ISO date, or an ISO date-time), plus <id>.compareFrom / <id>.compareTo for a custom comparison; ?<id>.period=all for every period. A URL naming from / to opens on that range even when "custom" is not offered, with no preset selected, and is not remembered. It starts from the URL, then from the user's last choice on that selector of that page (or on any selector sharing its rememberKey), then from defaultPreset / defaultComparison; the range and the comparison are read apart, and an unknown value, one the selector does not offer, or an invalid range falls back silently to the next source. A change replaces the history entry instead of adding one. There is no option to turn this off.

PeriodSelectorVariant is "default" | "segmented". segmented renders the presets as inline pills for one-click range switching; the custom range stays in its popover calendar either way.

Next Steps