CngxChart
projects/common/chart/chart/chart.component.ts
Import#
import { CngxChart } from '@cngx/common/chart'
Description#
Top-level chart container. Hosts an <svg> viewBox, applies
CngxResizeObserver via hostDirectives to track its rendered
size, and provides CNGX_CHART_CONTEXT so child atoms
([cngxAxis], layer atoms) read the live scales without injecting
the concrete CngxChart class.
Layer atoms and [cngxAxis] are attribute directives: consumers
mount them on <svg:g> hosts inside the chart so the SVG namespace
boundary stays clean. An element selector (<cngx-axis> etc.)
inside <svg> would create an XHTML-namespaced custom element
whose SVG-namespaced children would not lay out, leaving the chart
blank in real browsers (jsdom is permissive and would mask this).
Scales are derived from content-child [cngxAxis] directives:
the X axis (top/bottom position) drives xScale, the Y axis
(left/right) drives yScale. With no axis present, the
corresponding scale falls back to a no-op () => 0 and content
children may render off-canvas - consumers must mount at least
one axis per direction they actually use.
The [width] / [height] inputs override the resize observer for
fixed-dimension presets (inline sparkline at 80×24, etc.).
The plot area
Marks are mapped onto the plot area, not onto the whole viewBox:
the viewBox minus the room the projected axes need for their tick
labels and titles. The chart publishes the resulting rectangle on
CngxChartContext.plot, and both its own scale ranges and
every [cngxAxis] line resolve against that one derivation, so a
tick can never drift away from the mark it labels.
The reserved room is derived, not configured. Which sides reserve comes from the projected axis set; how much each side reserves comes from the axis itself, which sizes the gutter from the tick labels it already formats. There is no input, no CSS custom property and no DI token to tune it - a chart without axes reserves nothing and fills its box exactly as it did before the plot area existed, as does one whose axes are CngxAxisDomain, which draws nothing.
Every axis reserves on its own side and a little on the two perpendicular ones, because a tick label is centred on its tick and the end ticks sit on the plot corners.
Nothing here reads the DOM. The gutter is arithmetic over label
strings, so it is at final width in the first painted frame and
renders identically under SSR. The cost is that one character's
width is an estimate rather than the font's real advance width; a
consumer restyling --cngx-axis-font-size gets labels at their own
size inside a gutter sized for the default.
Metadata#
Content Slots#
Host#
Providers#
CNGX_CHART_CONTEXT- useExisting
CngxChart
Dependencies#
CNGX_CHART_I18Ninject()i18nRelationships
Index#
Properties
Methods
Derived State
Inputs#
"auto" | "off"Controls when the SR-only data-table view is exposed to assistive
technology. 'auto' (default) shows the table whenever the data
has more than one point - a single value is announced via the
aria-label, no table needed. 'off' keeps the table hidden
regardless. The table element is always present in the DOM -
visibility flips through aria-hidden, the linked id on
aria-describedby never disappears.
'auto'string | nullOptional explicit aria-label override. When set, supersedes the
auto-Summary derived from data + threshold layers. Use when the
default summary phrasing does not fit the chart's domain.
null, { alias: 'aria-label' }CngxAsyncState | undefined Optional connection-lifecycle envelope, additive and independent of
[state]. Bind a websocket/SSE adapter's CngxAsyncState<unknown> so
connection blips surface via the *cngxChartConnectionError /
*cngxChartReconnecting overlays without overriding the data view.
Consumers care only about the status surface, hence unknown as T.
number | undefinedOptional async-state envelope. When bound, the chart routes through
the same skeleton / empty / error / content state machine that the
preset molecules use. Without this input, the chart always renders
its content (Pillar-2 always-in-DOM data-table id stays stable, but
the SR data-table only flips out of aria-hidden when the active
view is 'content').
Accepts the standard CngxAsyncState<T> shape (any producer:
createManualState, createAsyncState, injectAsyncState,
fromHttpResource, tap* pipeline output). The chart exposes its
state via this explicit input, not a CNGX_STATEFUL provision - bind
bridges directly, e.g. <cngx-toast-on [state]="chart.state()">. The
separate [connectionState] input tracks connection lifecycle so the
two channels never contend for a single precedence seat.
(d: T, i: number) => numberNumeric accessor used to project generic data to the values fed
into the auto-Summary. Default Number(d) works for readonly number[] data; structured data must override.
DEFAULT_SUMMARY_ACCESSORnumber | undefinedInstance Properties#
unknownThe most recent significant transition (trend-flip or threshold-cross)
derived from summary, or null. The chart stays
pure-derivation - a companion <cngx-chart-announcer> subscribes to
this surface and voices it to assistive technology.
createSignificantChangeTracker(this.summary)unknowncomputed<ScaleFn<XScaleInput>>(
() => {
const axes = this.axes();
const plot = this.plot();
// Guard the plot extent, not the box: a chart narrower than the
// room its axes need would otherwise get a backwards range and
// paint every mark mirrored.
if (plot.width <= 0) {
return NOOP_SCALE;
}
const xAxis = axes.find((a) => isHorizontalPosition(a.position()));
if (!xAxis) {
return NOOP_SCALE;
}
return this.xScaleCache.get(xAxis.type(), xAxis.domain() ?? [], [plot.x0, plot.x1]);
},
{ equal: (a, b) => a === b },
)unknowncomputed<ScaleFn<number>>(
() => {
const axes = this.axes();
const plot = this.plot();
if (plot.height <= 0) {
return NOOP_Y_SCALE;
}
const yAxis = axes.find((a) => isVerticalPosition(a.position()));
if (!yAxis) {
return NOOP_Y_SCALE;
}
// SVG Y-axis is flipped - domain[max] maps to the plot area's top edge, domain[min] to its bottom edge.
return this.yScaleCache.get(yAxis.type(), yAxis.domain() ?? [], [
plot.y1,
plot.y0,
]) as ScaleFn<number>;
},
{ equal: (a, b) => a === b },
)Methods#
U[]Generic-aware data accessor satisfying CngxChartContext.data.
The single boundary cast lives here; layer atoms call
this.ctx.data<T>() and receive readonly T[] directly with no
per-site as cast.
HostBindings#
| Binding | Expression |
|---|---|
[attr.aria-label] | ariaLabelText() |
[attr.aria-describedby] | dataTableId |
[attr.aria-busy] | busy() ? "true" : null |
[class.cngx-chart--responsive] | isResponsive() |
[class.cngx-chart--content-hidden] | contentHidden() |
[style.width.px] | width() ?? null |
[style.aspect-ratio] | explicitAspectRatio() |
[style.--cngx-chart-plot-block-start] | plotVars().blockStart |
[style.--cngx-chart-plot-block-end] | plotVars().blockEnd |
[style.--cngx-chart-plot-inline-start] | plotVars().inlineStart |
[style.--cngx-chart-plot-inline-end] | plotVars().inlineEnd |
Chart-level CSS custom properties consumed by every chart atom in
@cngx/common/chart. This file is a pure token surface - no
selectors apart from :root, no @scope. A two-level cascade keeps
multi-layer recoloring side-effect-free: atom-local var (e.g.
--cngx-line-color) -> chart-level var (--cngx-chart-primary)
-> consumer accent (--accent, --danger) -> foundation token.
Atom-local pass-through
Each atom owns its own --cngx-<atom>-color token that always
falls back to the matching chart-level token, never to a literal.
Recoloring one line on a multi-layer chart stays scoped to that
line; overriding --cngx-chart-primary would otherwise shift
every layer (Pillar 3: no configuration-by-side-effect).
--cngx-line-color--cngx-area-fill--cngx-bar-color--cngx-scatter-color--cngx-threshold-color--cngx-band-color
Inheritance
Each chart-level token chains through a consumer-facing accent
tier and then a foundation --cngx-color-* token, so brand
overrides at either level cascade automatically. Because
@property initial-value cannot reference var(), the literal
defaults are mirrored in :root assignments:
--cngx-chart-primary->--accent->--cngx-color-primary--cngx-chart-secondary->--accent-secondary->--cngx-color-text-muted--cngx-chart-danger->--danger->--cngx-color-danger--cngx-chart-success->--success->--cngx-color-success--cngx-chart-axis-color->--text-secondary->--cngx-color-text-muted--cngx-chart-text-color->--text-primary->--cngx-color-text--cngx-chart-grid-color-color-mix(currentColor 14%)overlay, paints at consistent contrast on any body surface--cngx-skeleton-bg- samecurrentColoroverlay shared with the chart skeleton placeholder
Index#
Variant / Danger
Variant / Success
Typography
Surface
<color>oklch(0.66 0.19 50)Primary line / fill color shared by chart atoms. Falls back
through the consumer-facing --accent and then to the foundation
--cngx-color-primary (Ember).
See: [[--cngx-color-primary]]
<color>oklch(0.65 0.02 250)Secondary fill color for multi-series layers.
Variant / Danger
<color>oklch(0.55 0.18 25)Danger-coded series color.
See: [[--cngx-color-danger]]
Variant / Success
<color>oklch(0.55 0.15 145)Success-coded series color.
See: [[--cngx-color-success]]
Typography
<color>oklch(0.25 0.015 250)Axis / legend / annotation text color.
Layout
*5 / 2Default aspect ratio of the chart frame. Registered with
inherits: true deliberately: with inherits: false a value set
on :root or any container resets to the initial value at every
element boundary and never reaches the chart host, silently
no-opping the documented consumer override.