Charts
Data visualization on a dependency-free SVG kernel — no D3, no Chart.js, no canvas library. Eleven cartesian series types (line/spline/area/spline-area/stacked-area/bar/stacked/full-stacked/ scatter/range-area/candlestick) plus pie and doughnut share pure engines for 1-2-5 nice-tick scales, calendar-true time axes, log axes, stacking with separate negative branches and single-path rendering that stays fluid at 10k+ points. Wheel and drag-select zoom (cursor-centered, Escape resets), crosshair, shared tooltips, an interactive legend and point/series selection are built in. No WAI-ARIA APG chart pattern exists, so the widget composes: role="img" with a generated label, a screen-reader-only data table, real legend buttons, and keyboard point inspection — arrows walk arguments and series with polite live-region announcements.
@oge-ui/charts is a commercial package — free for evaluation and development, with no watermark and no runtime license checks. See licensing for the terms.
Getting started
One element, a working chart: the category axis auto-detects from the string arguments, the value axis picks nice ticks, the legend toggles series, and hovering shows the crosshair and tooltip.
| Argument | Product | Services |
|---|---|---|
| Q1 | 120 | 60 |
| Q2 | 150 | 74 |
| Q3 | 138 | 90 |
| Q4 | 190 | 105 |
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { OgeChart } from '@oge-ui/charts';
import type { OgeChartSeriesInput } from '@oge-ui/charts';
@Component({
selector: 'demo-root',
imports: [OgeChart],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- One element, a working chart: category axis auto-detected
from the string arguments, nice-tick value axis, interactive legend
(click hides a series), hover tooltip and crosshair. Everything is
drawn as dependency-free SVG — no D3, no canvas library. -->
<oge-chart
[dataSource]="data"
[series]="series"
title="Quarterly revenue"
style="height: 380px"
/>
`,
})
export class Demo {
protected readonly data = [
{ quarter: 'Q1', product: 120, services: 60 },
{ quarter: 'Q2', product: 150, services: 74 },
{ quarter: 'Q3', product: 138, services: 90 },
{ quarter: 'Q4', product: 190, services: 105 },
];
protected readonly series: OgeChartSeriesInput[] = [
{ type: 'bar', argumentField: 'quarter', valueField: 'product', name: 'Product' },
{ type: 'line', argumentField: 'quarter', valueField: 'services', name: 'Services' },
];
}Series types
Sixteen series types share one kernel — line/spline/step lines, five area flavors, four bar flavors (incl. rangeBar spanning value1..value2), scatter, bubble (sizeField drives each bubble's area), rangeArea and candlestick. showLabels prints values next to small series, null values become gaps, and hovering a legend item spotlights its series.
| Argument | Band | Steps | Bubbles |
|---|---|---|---|
| 1 | 22 – 42 | 60 | 80 |
| 2 | 27.753 – 47.753 | 74.383 | 74.647 |
| 3 | 32.098 – 52.098 | 85.244 | 60.881 |
| 4 | 33.97 – 53.97 | 89.925 | 44.596 |
| 5 | 32.912 – 52.912 | 87.279 | 32.767 |
| 6 | 29.182 – 49.182 | 77.954 | 30.458 |
| 7 | 23.693 – 43.693 | 64.234 | 38.659 |
| 8 | 17.791 – 37.791 | 49.477 | 53.857 |
| 9 | 12.918 – 32.918 | 37.296 | 69.545 |
| 10 | 10.27 – 30.27 | 30.674 | 79.004 |
| 11 | 10.493 – 30.493 | 31.232 | 78.184 |
| 12 | 13.534 – 33.534 | 38.834 | 67.436 |
| 13 | 18.647 – 38.647 | 51.618 | 51.362 |
| 14 | 24.581 – 44.581 | 66.454 | 36.847 |
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { OgeChart } from '@oge-ui/charts';
import type { OgeChartSeriesInput } from '@oge-ui/charts';
@Component({
selector: 'demo-root',
imports: [OgeChart],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- Sixteen series types share one kernel: line/spline/step
lines, five area flavors, four bar flavors (incl. rangeBar spanning
value1..value2), scatter, bubble (sizeField drives the AREA of each
bubble), rangeArea and candlestick. showLabels prints SI-formatted
values next to small series; null values become gaps, never fake
zeros. Hovering a legend item spotlights its series. -->
<oge-chart
[dataSource]="data"
[series]="series"
[commonSeries]="{ argumentField: 'day' }"
style="height: 380px"
/>
`,
})
export class Demo {
protected readonly data = Array.from({ length: 14 }, (_, i) => ({
day: i + 1,
smooth: Math.sin(i / 2) * 30 + 60,
lo: Math.sin(i / 2) * 12 + 22,
hi: Math.sin(i / 2) * 12 + 42,
dots: Math.cos(i / 1.5) * 25 + 55,
weight: (i % 5) + 1,
}));
protected readonly series: OgeChartSeriesInput[] = [
{ type: 'rangeBar', value1Field: 'lo', value2Field: 'hi', name: 'Band' },
{ type: 'stepLine', valueField: 'smooth', name: 'Steps', width: 2.5 },
{
type: 'bubble',
valueField: 'dots',
sizeField: 'weight',
name: 'Bubbles',
opacity: 0.75,
},
];
}Time axis & strip lines
Date arguments auto-detect the time axis: ticks are calendar-true (real month boundaries, DST-safe) and labels format through Intl in your locale. stripLines mark a deadline (line) or a window (band).
| Argument | Visitors |
|---|---|
| Jan 1, 2026 | 400 |
| Jan 2, 2026 | 424.632 |
| Jan 3, 2026 | 449.06 |
| Jan 4, 2026 | 473.079 |
| Jan 5, 2026 | 496.493 |
| Jan 6, 2026 | 519.112 |
| Jan 7, 2026 | 540.755 |
| Jan 8, 2026 | 561.255 |
| Jan 9, 2026 | 580.456 |
| Jan 10, 2026 | 598.221 |
| Jan 11, 2026 | 614.429 |
| Jan 12, 2026 | 628.979 |
| Jan 13, 2026 | 641.791 |
| Jan 14, 2026 | 652.804 |
| Jan 15, 2026 | 661.983 |
| Jan 16, 2026 | 669.311 |
| Jan 17, 2026 | 674.798 |
| Jan 18, 2026 | 542.475 |
| Jan 19, 2026 | 544.395 |
| Jan 20, 2026 | 544.632 |
| Jan 21, 2026 | 543.283 |
| Jan 22, 2026 | 540.463 |
| Jan 23, 2026 | 536.305 |
| Jan 24, 2026 | 530.96 |
| Jan 25, 2026 | 524.591 |
| Jan 26, 2026 | 517.376 |
| Jan 27, 2026 | 509.503 |
| Jan 28, 2026 | 501.168 |
| Jan 29, 2026 | 492.572 |
| Jan 30, 2026 | 483.919 |
| Jan 31, 2026 | 475.415 |
| Feb 1, 2026 | 467.263 |
| Feb 2, 2026 | 459.664 |
| Feb 3, 2026 | 452.808 |
| Feb 4, 2026 | 310.88 |
| Feb 5, 2026 | 306.051 |
| Feb 6, 2026 | 302.48 |
| Feb 7, 2026 | 300.308 |
| Feb 8, 2026 | 299.662 |
| Feb 9, 2026 | 300.648 |
| Feb 10, 2026 | 303.352 |
| Feb 11, 2026 | 307.841 |
| Feb 12, 2026 | 314.157 |
| Feb 13, 2026 | 322.321 |
| Feb 14, 2026 | 332.33 |
| Feb 15, 2026 | 344.161 |
| Feb 16, 2026 | 357.766 |
| Feb 17, 2026 | 373.076 |
| Feb 18, 2026 | 390.001 |
| Feb 19, 2026 | 408.43 |
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { OgeChart } from '@oge-ui/charts';
import type { OgeChartSeriesInput, OgeChartStripLine } from '@oge-ui/charts';
@Component({
selector: 'demo-root',
imports: [OgeChart],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- Date arguments auto-detect the time axis: ticks are
calendar-true (real month boundaries, DST-safe) and labels format
through Intl in your locale. stripLines mark a deadline (line) or a
window (band) on the argument axis. -->
<oge-chart
[dataSource]="data"
[series]="series"
[stripLines]="stripLines"
[argumentAxis]="{ grid: true }"
style="height: 380px"
/>
`,
})
export class Demo {
protected readonly data = Array.from({ length: 120 }, (_, i) => ({
date: new Date(2026, 0, 1 + i),
visitors: 400 + Math.sin(i / 9) * 150 + (i % 17) * 8,
}));
protected readonly series: OgeChartSeriesInput[] = [
{ type: 'area', argumentField: 'date', valueField: 'visitors', name: 'Visitors' },
];
protected readonly stripLines: OgeChartStripLine[] = [
{ start: new Date(2026, 2, 1), end: new Date(2026, 2, 15), label: 'Campaign' },
{ start: new Date(2026, 3, 10), label: 'Release', color: '#dc2626' },
];
}Stacked series
stackedBar accumulates per argument with negatives stacking downward separately; fullStackedBar normalizes each argument to 100%; the stack option splits independent groups.
| Argument | Online | Retail | Refunds |
|---|---|---|---|
| Jan | 40 | 24 | -6 |
| Feb | 52 | 28 | -4 |
| Mar | 47 | 35 | -9 |
| Apr | 61 | 31 | -5 |
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { OgeChart } from '@oge-ui/charts';
import type { OgeChartSeriesInput } from '@oge-ui/charts';
@Component({
selector: 'demo-root',
imports: [OgeChart],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- stackedBar accumulates per argument (negatives stack
downward separately); fullStackedBar normalizes each argument to
100%. The stack option splits series into independent stack groups. -->
<oge-chart
[dataSource]="data"
[series]="series"
[commonSeries]="{ argumentField: 'month' }"
[valueAxis]="{ abbreviate: false }"
style="height: 380px"
/>
`,
})
export class Demo {
protected readonly data = [
{ month: 'Jan', on: 40, off: 24, refunds: -6 },
{ month: 'Feb', on: 52, off: 28, refunds: -4 },
{ month: 'Mar', on: 47, off: 35, refunds: -9 },
{ month: 'Apr', on: 61, off: 31, refunds: -5 },
];
protected readonly series: OgeChartSeriesInput[] = [
{ type: 'stackedBar', valueField: 'on', name: 'Online' },
{ type: 'stackedBar', valueField: 'off', name: 'Retail' },
{ type: 'stackedBar', valueField: 'refunds', name: 'Refunds' },
];
}Zoom, pan & tooltips
50,000 points per series stay fluid: paths auto-downsample with LTTB (Largest-Triangle-Three-Buckets — peaks survive) to roughly one point per pixel, while hit-testing stays a binary search over the full data. Wheel zooms around the cursor, dragging selects a range, Shift+drag pans, Escape resets — [(visualRange)] is two-way and shared tooltips list every series.
| Argument | CPU | Memory |
|---|---|---|
| Jan 1, 2026 | 40 | 73 |
| Jan 1, 2026 | 41.417 | 73.999 |
| Jan 1, 2026 | 42.833 | 74.996 |
| Jan 1, 2026 | 44.249 | 75.99 |
| Jan 1, 2026 | 45.665 | 76.982 |
| Jan 1, 2026 | 47.081 | 77.972 |
| Jan 1, 2026 | 48.496 | 78.96 |
| Jan 1, 2026 | 49.91 | 72.946 |
| Jan 1, 2026 | 51.323 | 73.929 |
| Jan 1, 2026 | 52.736 | 74.91 |
| Jan 1, 2026 | 54.147 | 75.889 |
| Jan 1, 2026 | 55.558 | 76.866 |
| Jan 1, 2026 | 56.967 | 77.84 |
| Jan 1, 2026 | 45.374 | 78.813 |
| Jan 1, 2026 | 46.781 | 72.783 |
| Jan 1, 2026 | 48.185 | 73.751 |
| Jan 1, 2026 | 49.588 | 74.716 |
| Jan 1, 2026 | 50.989 | 75.68 |
| Jan 1, 2026 | 52.388 | 76.641 |
| Jan 1, 2026 | 53.785 | 77.6 |
| Jan 1, 2026 | 55.18 | 78.557 |
| Jan 1, 2026 | 56.572 | 72.512 |
| Jan 1, 2026 | 57.963 | 73.465 |
| Jan 1, 2026 | 59.35 | 74.415 |
| Jan 1, 2026 | 60.735 | 75.364 |
| Jan 1, 2026 | 62.118 | 76.31 |
| Jan 1, 2026 | 50.497 | 77.254 |
| Jan 1, 2026 | 51.874 | 78.196 |
| Jan 1, 2026 | 53.248 | 72.136 |
| Jan 1, 2026 | 54.618 | 73.074 |
| Jan 1, 2026 | 55.986 | 74.009 |
| Jan 1, 2026 | 57.35 | 74.943 |
| Jan 1, 2026 | 58.71 | 75.874 |
| Jan 1, 2026 | 60.067 | 76.803 |
| Jan 1, 2026 | 61.421 | 77.731 |
| Jan 1, 2026 | 62.77 | 71.656 |
| Jan 1, 2026 | 64.116 | 72.579 |
| Jan 1, 2026 | 65.458 | 73.5 |
| Jan 1, 2026 | 66.796 | 74.419 |
| Jan 1, 2026 | 55.13 | 75.336 |
| Jan 1, 2026 | 56.459 | 76.251 |
| Jan 1, 2026 | 57.785 | 77.164 |
| Jan 1, 2026 | 59.105 | 71.075 |
| Jan 1, 2026 | 60.422 | 71.984 |
| Jan 1, 2026 | 61.734 | 72.891 |
| Jan 1, 2026 | 63.041 | 73.796 |
| Jan 1, 2026 | 64.343 | 74.7 |
| Jan 1, 2026 | 65.641 | 75.601 |
| Jan 1, 2026 | 66.934 | 76.5 |
| Jan 1, 2026 | 68.222 | 70.397 |
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { OgeChart } from '@oge-ui/charts';
import type { OgeChartRange, OgeChartSeriesInput } from '@oge-ui/charts';
@Component({
selector: 'demo-root',
imports: [OgeChart],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- 50,000 points per series stay fluid: paths auto-downsample with
LTTB (Largest-Triangle-Three-Buckets — peaks survive) to roughly
one point per pixel, hit-testing is a binary search over the FULL
data and pointer work is rAF-coalesced. Wheel zooms around the
cursor, dragging selects a range, Shift+drag pans, Escape resets;
visualRange is two-way; shared tooltips list every series. -->
<oge-chart
[dataSource]="data"
[series]="series"
[(visualRange)]="range"
zoomEnabled="both"
[panEnabled]="true"
[tooltip]="{ shared: true }"
[crosshair]="{ horizontal: true }"
style="height: 380px"
/>
`,
})
export class Demo {
protected readonly range = signal<OgeChartRange | null>(null);
protected readonly data = Array.from({ length: 50_000 }, (_, i) => ({
t: new Date(2026, 0, 1, 0, i * 15),
cpu: 40 + Math.sin(i / 60) * 25 + (i % 13),
memory: 55 + Math.cos(i / 90) * 18 + (i % 7),
}));
protected readonly series: OgeChartSeriesInput[] = [
{ type: 'line', argumentField: 't', valueField: 'cpu', name: 'CPU' },
{ type: 'line', argumentField: 't', valueField: 'memory', name: 'Memory' },
];
}Candlestick & multi-axis
Candlesticks read OHLC fields; a second value axis (position: 'end') carries the volume bars so the two scales stay independent. Rising/falling bodies color via the theme tokens.
| Argument | OGE | Volume |
|---|---|---|
| Jul 1, 2026 | O 100 H 104 L 94 C 98 | 800 |
| Jul 2, 2026 | O 103.969 H 108.845 L 99.969 C 104.845 | 920 |
| Jul 3, 2026 | O 107.753 H 114.802 L 103.753 C 110.802 | 1,040 |
| Jul 4, 2026 | O 111.18 H 119.165 L 107.18 C 115.165 | 1,160 |
| Jul 5, 2026 | O 114.098 H 121.553 L 110.098 C 117.553 | 1,280 |
| Jul 6, 2026 | O 111.388 H 116.979 L 107.388 C 112.979 | 1,400 |
| Jul 7, 2026 | O 112.97 H 116.97 L 107.817 C 111.817 | 1,520 |
| Jul 8, 2026 | O 113.808 H 117.808 L 105.703 C 109.703 | 1,640 |
| Jul 9, 2026 | O 113.912 H 117.912 L 103.371 C 107.371 | 1,760 |
| Jul 10, 2026 | O 113.337 H 117.337 L 101.472 C 105.472 | 800 |
| Jul 11, 2026 | O 107.182 H 111.182 L 95.428 C 99.428 | 920 |
| Jul 12, 2026 | O 105.58 H 109.58 L 95.347 C 99.347 | 1,040 |
| Jul 13, 2026 | O 103.693 H 107.693 L 96.017 C 100.017 | 1,160 |
| Jul 14, 2026 | O 101.702 H 105.702 L 96.992 C 100.992 | 1,280 |
| Jul 15, 2026 | O 99.791 H 105.733 L 95.791 C 101.733 | 1,400 |
| Jul 16, 2026 | O 93.141 H 100.769 L 89.141 C 96.769 | 1,520 |
| Jul 17, 2026 | O 91.918 H 99.855 L 87.918 C 95.855 | 1,640 |
| Jul 18, 2026 | O 91.26 H 98.051 L 87.26 C 94.051 | 1,760 |
| Jul 19, 2026 | O 91.27 H 95.742 L 87.27 C 91.742 | 800 |
| Jul 20, 2026 | O 92.008 H 96.008 L 85.558 C 89.558 | 920 |
| Jul 21, 2026 | O 88.493 H 92.493 L 79.229 C 83.229 | 1,040 |
| Jul 22, 2026 | O 90.693 H 94.693 L 79.415 C 83.415 | 1,160 |
| Jul 23, 2026 | O 93.534 H 97.534 L 81.534 C 85.534 | 1,280 |
| Jul 24, 2026 | O 96.901 H 100.901 L 85.648 C 89.648 | 1,400 |
| Jul 25, 2026 | O 100.647 H 104.647 L 91.428 C 95.428 | 1,520 |
| Jul 26, 2026 | O 99.602 H 103.602 L 93.204 C 97.204 | 1,640 |
| Jul 27, 2026 | O 103.581 H 108.102 L 99.581 C 104.102 | 1,760 |
| Jul 28, 2026 | O 107.401 H 114.223 L 103.401 C 110.223 | 800 |
| Jul 29, 2026 | O 110.884 H 118.827 L 106.884 C 114.827 | 920 |
| Jul 30, 2026 | O 113.877 H 121.486 L 109.877 C 117.486 | 1,040 |
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { OgeChart } from '@oge-ui/charts';
import type { OgeChartSeriesInput } from '@oge-ui/charts';
@Component({
selector: 'demo-root',
imports: [OgeChart],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- Candlesticks read OHLC fields; a second value axis
(position: 'end') carries the volume bars so the two scales stay
independent. Rising/falling bodies color via the theme tokens. -->
<oge-chart
[dataSource]="data"
[series]="series"
[valueAxis]="[
{ title: 'Price' },
{ position: 'end', title: 'Volume' },
]"
style="height: 380px"
/>
`,
})
export class Demo {
protected readonly data = Array.from({ length: 30 }, (_, i) => {
const open = 100 + Math.sin(i / 4) * 12 + (i % 5);
const close = open + Math.sin(i / 2) * 6 - 2;
return {
day: new Date(2026, 6, 1 + i),
o: open,
h: Math.max(open, close) + 4,
l: Math.min(open, close) - 4,
c: close,
vol: 800 + (i % 9) * 120,
};
});
protected readonly series: OgeChartSeriesInput[] = [
{
type: 'candlestick',
argumentField: 'day',
openField: 'o',
highField: 'h',
lowField: 'l',
closeField: 'c',
name: 'OGE',
},
{
type: 'bar',
argumentField: 'day',
valueField: 'vol',
name: 'Volume',
axis: 1,
opacity: 0.4,
},
];
}Pie & doughnut
Pie and doughnut share the kernel: outside labels with connector lines, small-value grouping folds the tail into an "Others" slice, and clicking a slice (or its legend button) selects and explodes it.
| Chrome | 62 (62%) |
|---|---|
| Safari | 20 (20%) |
| Edge | 6 (6%) |
| Firefox | 5 (5%) |
| Others | 7 (7%) |
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { OgePieChart } from '@oge-ui/charts';
@Component({
selector: 'demo-root',
imports: [OgePieChart],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- Pie and doughnut share the kernel: outside labels with
connector lines, small-value grouping folds the tail into an
"Others" slice, clicking (or the legend) selects and explodes. -->
<oge-pie-chart
[dataSource]="data"
argumentField="browser"
valueField="share"
type="doughnut"
[innerRadius]="0.55"
[smallValuesGrouping]="{ mode: 'topN', topCount: 4 }"
title="Browser share"
style="height: 360px"
/>
`,
})
export class Demo {
protected readonly data = [
{ browser: 'Chrome', share: 62 },
{ browser: 'Safari', share: 20 },
{ browser: 'Edge', share: 6 },
{ browser: 'Firefox', share: 5 },
{ browser: 'Samsung', share: 3 },
{ browser: 'Opera', share: 2 },
{ browser: 'Other', share: 2 },
];
}Polar & radar
Radar/polar on the same kernel: categories slot around the circle, values map radially with nice-tick rings. line/area draw closed radar loops (a null value breaks the loop into a gap), scatter renders markers, bar renders sectors — and spider swaps circular rings for polygons.
| Argument | Ada | Grace |
|---|---|---|
| TypeScript | 9 | 7 |
| CSS | 6 | 8 |
| SQL | 7 | 5 |
| Rust | 4 | 6 |
| Go | 5 | 9 |
| Testing | 8 | 7 |
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { OgePolarChart } from '@oge-ui/charts';
import type { OgeChartSeriesInput } from '@oge-ui/charts';
@Component({
selector: 'demo-root',
imports: [OgePolarChart],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- Radar/polar on the same kernel: categories slot around the
circle, values map radially with nice ticks. line/area draw closed
radar loops (a null value breaks the loop into a gap), scatter renders
markers, bar renders sectors. spider swaps circular rings for
polygons. -->
<oge-polar-chart
[dataSource]="data"
[series]="series"
[commonSeries]="{ argumentField: 'skill' }"
[spider]="true"
title="Team skills"
style="height: 400px"
/>
`,
})
export class Demo {
protected readonly data = [
{ skill: 'TypeScript', ada: 9, grace: 7 },
{ skill: 'CSS', ada: 6, grace: 8 },
{ skill: 'SQL', ada: 7, grace: 5 },
{ skill: 'Rust', ada: 4, grace: 6 },
{ skill: 'Go', ada: 5, grace: 9 },
{ skill: 'Testing', ada: 8, grace: 7 },
];
protected readonly series: OgeChartSeriesInput[] = [
{ type: 'area', valueField: 'ada', name: 'Ada' },
{ type: 'line', valueField: 'grace', name: 'Grace', width: 2.5 },
];
}Annotations
Annotations anchor on the plot: point draws a marker dot with a connector into a label box at (argument, value); text places the label alone. *ogeChartAnnotationTemplate swaps in arbitrary HTML.
| Argument | Price |
|---|---|
| 1 | 80 |
| 2 | 84.473 |
| 3 | 88.788 |
| 4 | 92.793 |
| 5 | 96.347 |
| 6 | 99.329 |
| 7 | 101.641 |
| 8 | 103.209 |
| 9 | 103.991 |
| 10 | 103.977 |
| 11 | 103.186 |
| 12 | 101.67 |
| 13 | 99.509 |
| 14 | 96.81 |
| 15 | 93.7 |
| 16 | 90.322 |
| 17 | 86.833 |
| 18 | 83.389 |
| 19 | 80.15 |
| 20 | 77.263 |
| 21 | 74.864 |
| 22 | 73.068 |
| 23 | 71.968 |
| 24 | 71.626 |
| 25 | 72.077 |
| 26 | 73.322 |
| 27 | 75.331 |
| 28 | 78.045 |
| 29 | 81.375 |
| 30 | 85.208 |
| 31 | 89.412 |
| 32 | 93.838 |
| 33 | 98.331 |
| 34 | 102.731 |
| 35 | 106.882 |
| 36 | 110.64 |
| 37 | 113.873 |
| 38 | 116.474 |
| 39 | 118.358 |
| 40 | 119.471 |
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { OgeChart } from '@oge-ui/charts';
import type { OgeChartAnnotation, OgeChartSeriesInput } from '@oge-ui/charts';
@Component({
selector: 'demo-root',
imports: [OgeChart],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- Annotations anchor on the plot: 'point' draws a marker dot
with a connector into a label box at (argument, value); 'text' places
the label alone (top of the plot without a value).
*ogeChartAnnotationTemplate swaps in arbitrary HTML. -->
<oge-chart
[dataSource]="data"
[series]="series"
[annotations]="annotations"
style="height: 380px"
/>
`,
})
export class Demo {
protected readonly data = Array.from({ length: 40 }, (_, i) => ({
day: i + 1,
price: 80 + Math.sin(i / 5) * 20 + i / 2,
}));
protected readonly series: OgeChartSeriesInput[] = [
{ type: 'spline', argumentField: 'day', valueField: 'price', name: 'Price' },
];
protected readonly annotations: OgeChartAnnotation[] = [
{ type: 'point', text: 'All-time high', argument: 34, value: 116.9 },
{ type: 'point', text: 'Correction', argument: 22, value: 76.1, offsetY: 24 },
{ type: 'text', text: 'Q1 guidance', argument: 8 },
];
}Range selector
The overview strip: a mini background chart with a draggable window and two WAI-ARIA slider handles (arrows adjust, Home/End jump, Escape mid-drag restores). Bound to the chart's [(visualRange)], the two stay in lockstep — drag the window and the chart zooms.
| Argument | Sales |
|---|---|
| Jan 1, 2026 | 200 |
| Jan 2, 2026 | 209.998 |
| Jan 3, 2026 | 219.987 |
| Jan 4, 2026 | 229.955 |
| Jan 5, 2026 | 239.894 |
| Jan 6, 2026 | 249.792 |
| Jan 7, 2026 | 259.642 |
| Jan 8, 2026 | 269.432 |
| Jan 9, 2026 | 279.153 |
| Jan 10, 2026 | 288.797 |
| Jan 11, 2026 | 298.354 |
| Jan 12, 2026 | 241.815 |
| Jan 13, 2026 | 251.171 |
| Jan 14, 2026 | 260.415 |
| Jan 15, 2026 | 269.537 |
| Jan 16, 2026 | 278.531 |
| Jan 17, 2026 | 287.388 |
| Jan 18, 2026 | 296.102 |
| Jan 19, 2026 | 304.666 |
| Jan 20, 2026 | 313.073 |
| Jan 21, 2026 | 321.318 |
| Jan 22, 2026 | 329.394 |
| Jan 23, 2026 | 271.297 |
| Jan 24, 2026 | 279.021 |
| Jan 25, 2026 | 286.563 |
| Jan 26, 2026 | 293.919 |
| Jan 27, 2026 | 301.085 |
| Jan 28, 2026 | 308.058 |
| Jan 29, 2026 | 314.836 |
| Jan 30, 2026 | 321.417 |
| Jan 31, 2026 | 327.8 |
| Feb 1, 2026 | 333.983 |
| Feb 2, 2026 | 339.966 |
| Feb 3, 2026 | 279.749 |
| Feb 4, 2026 | 285.333 |
| Feb 5, 2026 | 290.719 |
| Feb 6, 2026 | 295.908 |
| Feb 7, 2026 | 300.902 |
| Feb 8, 2026 | 305.704 |
| Feb 9, 2026 | 310.317 |
| Feb 10, 2026 | 314.744 |
| Feb 11, 2026 | 318.989 |
| Feb 12, 2026 | 323.057 |
| Feb 13, 2026 | 326.952 |
| Feb 14, 2026 | 264.68 |
| Feb 15, 2026 | 268.246 |
| Feb 16, 2026 | 271.656 |
| Feb 17, 2026 | 274.918 |
| Feb 18, 2026 | 278.037 |
| Feb 19, 2026 | 281.021 |
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { OgeChart, OgeRangeSelector } from '@oge-ui/charts';
import type { OgeChartRange, OgeChartSeriesInput } from '@oge-ui/charts';
@Component({
selector: 'demo-root',
imports: [OgeChart, OgeRangeSelector],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- The overview strip: a mini background chart with a draggable
window and two WAI-ARIA slider handles (arrows adjust, Home/End jump,
Escape mid-drag restores). Bind the same range to a chart's
[(visualRange)] and the two stay in lockstep. -->
<oge-chart
[dataSource]="data"
[series]="series"
[(visualRange)]="range"
zoomEnabled="both"
style="height: 300px"
/>
<oge-range-selector
[dataSource]="data"
[series]="miniSeries"
[(value)]="range"
style="display: block; margin-top: 8px"
/>
`,
})
export class Demo {
protected readonly range = signal<OgeChartRange | null>(null);
protected readonly data = Array.from({ length: 365 }, (_, i) => ({
date: new Date(2026, 0, 1 + i),
sales: 200 + Math.sin(i / 20) * 80 + (i % 11) * 6,
}));
protected readonly series: OgeChartSeriesInput[] = [
{ type: 'line', argumentField: 'date', valueField: 'sales', name: 'Sales' },
];
protected readonly miniSeries: OgeChartSeriesInput[] = [
{ type: 'area', argumentField: 'date', valueField: 'sales', name: 'Sales' },
];
}Selection, i18n & export
selectionMode="point" rings clicked points (Ctrl adds to the set). Every user-facing string, aria labels included, lives in OgeChartsMessages (provideOgeChartsConfig(), locale). The dependency-free @oge-ui/charts/export-image entry serializes the live SVG with inlined styles — PNG via canvas rasterization, or the standalone .svg itself.
| Argument | Value |
|---|---|
| Jan | 12 |
| Feb | 31 |
| Mar | 24 |
| Apr | 42 |
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { OgeChart, provideOgeChartsConfig } from '@oge-ui/charts';
import type { OgeChartPointEvent, OgeChartPointRef, OgeChartSeriesInput } from '@oge-ui/charts';
@Component({
selector: 'demo-root',
imports: [OgeChart],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- selectionMode="point" rings clicked points (Ctrl adds);
legendClick is cancelable. Every user-facing string, aria included,
lives in OgeChartsMessages (provideOgeChartsConfig, locale). The
dependency-free export entry serializes the SVG with inlined styles
— PNG via canvas rasterization, or the standalone .svg itself. -->
<div class="mb-2 flex gap-2">
<button type="button" (click)="exportPng(chart)">Export PNG</button>
<button type="button" (click)="exportSvg(chart)">Export SVG</button>
</div>
<oge-chart
#chart
[dataSource]="data"
[series]="series"
selectionMode="point"
[(selectedPoints)]="selected"
locale="de"
(pointClick)="lastPoint = $event"
style="height: 340px"
/>
`,
})
export class Demo {
// App-wide (main.ts / route providers):
// provideOgeChartsConfig({ locale: 'de', a11yTableLimit: 100 })
protected readonly selected = signal<readonly OgeChartPointRef[]>([]);
protected lastPoint: OgeChartPointEvent<Record<string, unknown>> | null =
null;
protected readonly data = [
{ month: 'Jan', value: 12 },
{ month: 'Feb', value: 31 },
{ month: 'Mar', value: 24 },
{ month: 'Apr', value: 42 },
];
protected readonly series: OgeChartSeriesInput[] = [
{ type: 'bar', argumentField: 'month', valueField: 'value', name: 'Value' },
];
/** The exporter loads lazily and needs no third-party library at all. */
protected async exportPng<T extends object>(
chart: OgeChart<T>,
): Promise<void> {
const { exportChartToPng } = await import('@oge-ui/charts/export-image');
await exportChartToPng(chart, { filename: 'chart.png' });
}
protected async exportSvg<T extends object>(
chart: OgeChart<T>,
): Promise<void> {
const { exportChartToSvg } = await import('@oge-ui/charts/export-image');
exportChartToSvg(chart, { filename: 'chart.svg' });
}
}