CHART · CHARTGROUP
Composable time-series analysis
Build independent line and bar charts over one synchronized time domain. Forecasts are ordinary series, derived panels resolve their inputs by stable ID, and every panel keeps its own Y-axis and controls.
Preview
Hover any panel for synchronized tooltips. Zoom the monthly production view into May 2025 to reveal the daily pressure and hourly vibration detail. Open a panel's settings to change its controls or apply presentation mode everywhere.
- Oil actual
- Oil forecast
- Gas actual
- Gas forecast
- Water actual
- Oil vs plan
- Tubing pressure
- Pump vibration
Composition model
1 · Registry
Give each source a stable ID such as oil.actual or pressure.actual.
2 · Panels
Each chart selects source IDs or declares a derived series and its dependencies.
3 · Group
ChartGroup synchronizes time, cursor, and annotations—not unrelated Y scales.
Installation
pnpm add @aai-agency/og-componentsUsage
import {
Chart,
ChartGroup,
type ChartConfig,
} from "@aai-agency/og-components/chart";
// One independently configured panel
<Chart
kind="line"
id="production"
label="Production"
series={series}
/>;
// The same series schema can render as bars
<Chart kind="bar" series={series} />;
const charts: ChartConfig[] = [
{
id: "production",
label: "Production — actuals & forecasts",
kind: "line",
series: ["oil.actual", "oil.forecast", "gas.actual"],
inheritAnnotations: true,
},
{
id: "variance",
label: "Plan variance",
kind: "bar",
symmetricY: true,
series: [{
id: "oil.variance",
label: "Oil vs plan",
sourceSeriesIds: ["oil.actual", "oil.plan"],
derive: ({ time, getSeries }) => {
const actual = getSeries("oil.actual");
const plan = getSeries("oil.plan");
return time.map((_, index) => {
const observed = actual?.values[index];
const expected = plan?.values[index];
return observed == null || expected == null
? null
: observed - expected;
});
},
unit: "BBL/d",
}],
},
];
export function WellWorkbench({ series, annotations }) {
return (
<ChartGroup
series={series}
charts={charts}
annotations={annotations}
timeZone="America/Chicago"
/>
);
}Formatting and typography
Formatters receive semantic context, so one function can distinguish axes from tooltips and resolve a panel, series, unit, or left/right axis. Typography accepts both font sizes and font weights.
<ChartGroup
series={series}
charts={charts}
formatXValue={(timestamp) =>
new Intl.DateTimeFormat("en-US", {
month: "short",
year: "numeric",
}).format(new Date(timestamp * 1000))
}
formatYValue={(value, context) => {
const formatted = Intl.NumberFormat("en-US", {
notation: Math.abs(value) >= 10_000 ? "compact" : "standard",
maximumFractionDigits: context.unit === "mm/s" ? 2 : 0,
}).format(value);
return context.location === "tooltip" && context.unit
? `${formatted} ${context.unit}`
: formatted;
}}
typography={{
fontFamily: "Inter, sans-serif",
axisTickFontSize: 11,
axisTickFontWeight: 500,
axisLabelFontSize: 12,
axisLabelFontWeight: 600,
tooltipFontSize: 10,
tooltipFontWeight: 400,
tooltipHeaderFontWeight: 600,
legendFontSize: 10,
legendFontWeight: 500,
titleFontSize: 11,
titleFontWeight: 600,
}}
/>Chart and ChartGroup props
| Prop | Type | Default | Description |
|---|---|---|---|
kind | "line" | "bar" | - | Required on Chart. Selects the visual renderer independently from seriesType. |
series | TimeSeries[] | - | Canonical series registry. Use stable IDs; forecasts use seriesType="forecast". |
charts | ChartConfig[] | - | Ordered line or bar panels. Each panel selects source IDs or registers a derived series. |
annotations | Annotation[] | [] | Operational ranges shared with panels that set inheritAnnotations. |
timeZone | string | UTC | IANA time zone used for calendar buckets, axes, and tooltip dates. |
formatXValue | (timestamp: number) => string | - | Formats X-axis ticks and the shared tooltip header. |
formatYValue | ChartYValueFormatter | - | Formats axes and tooltips with chart, series, axis, unit, and location context. |
typography | ChartTypography | - | Controls font family, size, and weight for axes, labels, tooltips, legends, and titles. |
