Skip to main content
cngx-src documentation

CngxChart

ComponentPrimaryOnPushNo encapsulationv0.1.0WCAG AA

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.

https://cngxjs.github.io/cngx/examples/#/common/chart/primitives/async-state-machine-on-the-primitive https://cngxjs.github.io/cngx/examples/#/common/chart/primitives/combo-bars-moving-average-line https://cngxjs.github.io/cngx/examples/#/common/chart/primitives/line-area-threshold-band https://cngxjs.github.io/cngx/examples/#/common/chart/primitives/multi-series-line-axis-labels-legend https://cngxjs.github.io/cngx/examples/#/common/chart/primitives/overlay-aligned-to-the-plot https://cngxjs.github.io/cngx/examples/#/common/chart/primitives/responsive-fills-parent-width https://cngxjs.github.io/cngx/examples/#/common/chart/primitives/scatter-with-performance-zones https://cngxjs.github.io/cngx/examples/#/common/chart/primitives/time-series-with-threshold-zones

Metadata#

Content Slots#

Host#

Providers#

CNGX_CHART_CONTEXT
useExisting CngxChart

Dependencies#

CNGX_CHART_I18Ninject()i18n

Relationships

Index#

Inputs#

accessibleTable#"auto" | "off"
input()

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.

default 'auto'
ariaLabel#string | null
input()

Optional 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.

default null, { alias: 'aria-label' }

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.

height#number | undefined
input()
preserveAspectRatio#string
input()
default 'xMidYMid meet'

Optional 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.

summaryAccessor#(d: T, i: number) => number

Numeric 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 DEFAULT_SUMMARY_ACCESSOR
width#number | undefined
input()

Instance Properties#

dataTableId#unknown
ProtectedReadonly
nextUid('cngx-chart-data-table')
significantChange#unknown
Readonly

The 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)
xScale#unknown
Readonly
computed<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 },
)
yScale#unknown
Readonly
computed<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#

data#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#

BindingExpression
[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 - same currentColor overlay shared with the chart skeleton placeholder

Index#

Surface

--cngx-chart-primary#<color>
Default value 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]]

--cngx-chart-secondary#<color>
Default value oklch(0.65 0.02 250)

Secondary fill color for multi-series layers.

--cngx-chart-grid-color#<color>
Default value oklch(0.92 0.005 250)

Gridline color.

--cngx-chart-axis-color#<color>
Default value oklch(0.5 0.015 250)

Axis stroke color.

Variant / Danger

--cngx-chart-danger#<color>
Default value oklch(0.55 0.18 25)

Danger-coded series color.

See: [[--cngx-color-danger]]

Variant / Success

--cngx-chart-success#<color>
Default value oklch(0.55 0.15 145)

Success-coded series color.

See: [[--cngx-color-success]]

Typography

--cngx-chart-text-color#<color>
Default value oklch(0.25 0.015 250)

Axis / legend / annotation text color.

Layout

--cngx-chart-aspect-ratio#*
Default value 5 / 2

Default 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.