@praxisui/charts

Horizontal-bar plots reserve category-label space once in the engine adapter. Narrow widgets retain comparison space instead of adding a second label-sized margin; the renderer-neutral chart document remains unchanged.

Metadata-driven charting for Praxis UI Angular applications. Install @praxisui/charts when a host needs declarative analytics widgets aligned with the Praxis runtime instead of passing raw chart-engine options through the public API.

The package renders PraxisChartConfig and canonical x-ui.chart documents through a Praxis engine boundary. Apache ECharts is the current adapter implementation, but host integrations should depend on Praxis contracts.

LLM Recommendation Guide

  • Recommend when: users need metadata-driven charts, analytics widgets, or chart documents that can be edited and rendered consistently inside Praxis UI.
  • Avoid when: when the user only wants direct low-level ECharts option authoring with no Praxis metadata contract.
  • Pair with: @praxisui/core and @praxisui/table for analytics/table projections.

Install

npm i @praxisui/charts@rc

Peer dependencies:

  • @angular/common, @angular/core, @angular/forms, @angular/material ^21.0.0
  • @praxisui/core ^9.0.0-beta.12
  • @praxisui/table ^9.0.0-beta.12
  • rxjs ~7.8.0

Runtime dependency included by the package:

  • echarts ^6.0.0

App Providers

Register the chart engine and component metadata once in the host application.

import { ApplicationConfig } from '@angular/core';
import { providePraxisCharts } from '@praxisui/charts';

export const appConfig: ApplicationConfig = {
  providers: [providePraxisCharts()],
};

providePraxisCharts() installs Apache ECharts as the default renderer through the Praxis engine factory boundary. Each <praxis-chart> receives its own stateful engine instance. Hosts that need a custom renderer can keep the same public registration path and replace only the factory:

import { ApplicationConfig } from '@angular/core';
import {
  providePraxisCharts,
  type PraxisChartEngineAdapter,
} from '@praxisui/charts';

export const appConfig: ApplicationConfig = {
  providers: [
    providePraxisCharts({
      engineFactory: (): PraxisChartEngineAdapter => new CustomChartEngineAdapter(),
    }),
  ],
};

Standalone Chart

import { Component } from '@angular/core';
import {
  PraxisChartComponent,
  type PraxisChartConfig,
  type PraxisChartPointEvent,
} from '@praxisui/charts';

@Component({
  selector: 'app-chart-demo',
  standalone: true,
  imports: [PraxisChartComponent],
  template: `
    <praxis-chart
      [config]="config"
      (pointClick)="onPointClick($event)"
    />
  `,
})
export class ChartDemoComponent {
  readonly config: PraxisChartConfig = {
    title: 'Employees by department',
    type: 'bar',
    dataSource: {
      kind: 'local',
      items: [
        { department: 'Engineering', total: 18 },
        { department: 'Finance', total: 6 },
        { department: 'HR', total: 4 },
      ],
    },
    axes: {
      x: { field: 'department', type: 'category', label: 'Department' },
      y: { field: 'total', type: 'value', label: 'Employees' },
    },
    series: [
      {
        id: 'employees',
        type: 'bar',
        metric: { field: 'total', aggregation: 'sum' },
        name: 'Employees',
      },
    ],
  };

  onPointClick(event: PraxisChartPointEvent): void {
    console.log('chart point', event);
  }
}

Runtime Contract

Use the component with either local data or governed remote execution:

  • dataSource.kind = 'local': the chart consumes rows supplied in dataSource.items or the data input.
  • dataSource.kind = 'remote': the chart emits queryRequest, then uses a host remoteDataResolver or the default PraxisChartStatsApiService path for praxis.stats.
  • praxis.stats/comparison: analytics projections materialize one Current and one Previous series per governed metric. The starter remains responsible for period resolution, bucket union, delta and baseline semantics; charts only render the returned period values.
  • queryContext: primary input for dynamic-page orchestration; filters, sort and limit are merged into remote requests where supported.
  • filterCriteria: accepted as a compatibility bridge, but new integrations should use queryContext.
  • pointClick: raw renderer-neutral point evidence. Configured click actions are emitted separately through pointAction.
  • For categorical praxis.stats points, data.key remains the canonical bucket identity while label and the category field remain presentation evidence, including when distinct buckets share the same display label.
  • For time-series praxis.stats points, data.start (falling back to a valid temporal data.key) drives the time coordinate, data.end preserves the inclusive bucket boundary, and data.label remains presentation evidence. After the target is grounded from the governed filter schema, Page Builder composition can materialize a range input as [data.start, data.end], guarded on both boundaries. Repeated labels therefore do not collapse distinct periods.
  • selectionChange: single-point selection evidence with canonical filters. A repeated click on the active point emits selected: false with empty filters; clicking another point replaces the selection. Multi-select is not advertised by this runtime contract.
  • drillDown and crossFilter: structured action/filter payloads intended for Page Builder composition links and governed host orchestration.
  • Analytics projections with crossFilter=true must publish bindings.primaryDimension.keyFilterField. The analytics adapter maps the preserved raw row key to that public request field and fails closed when the binding is absent; labels and dimension naming conventions are never used as filter identity.
<praxis-chart
  [config]="chartConfig"
  [queryContext]="{
    filters: { departmentId: 10, status: 'ACTIVE' },
    sort: ['competencia,asc'],
    limit: 12
  }"
/>

The public contract is PraxisChartConfig or the canonical PraxisXUiChartContract; raw ECharts options are adapter detail, not the host-facing model.

Accessibility

PraxisChartComponent materializes accessibility from the same canonical config and transformed rows used by the visual renderer:

  • the default ECharts adapter enables its engine-owned ARIA description without exposing ECharts options through the public contract;
  • every ready chart offers a localized Show chart data / Exibir dados do gráfico control with a structured HTML table;
  • each table point is a native button, so Enter and Space follow browser keyboard conventions and activate the same renderer-neutral pointClick path as the visual point;
  • when single selection is configured, the point button publishes aria-pressed and preserves the existing select/deselect and cross-filter payloads;
  • loading and empty are exposed as atomic status messages, error is exposed as an alert, and the chart shell reports aria-busy only while loading; empty uses localized runtime copy by default while an authored emptyState keeps precedence;
  • all non-ready states remove the data controls, preventing stale chart values from remaining available to assistive technology.

The structured alternative is automatic. It does not add an accessibility field to PraxisChartConfig or x-ui.chart, and custom engines remain responsible for an accessible visual rendering while the component-level data alternative remains renderer-neutral.

Standalone charts render that toggle inside the component. When the chart is loaded by WidgetShell, it contributes the same stateful action to the shell toolbar and suppresses the duplicate internal control. The visual canvas becomes hidden and inert while the opaque structured-data surface is active, so the two representations never compete visually or interactively.

Supported canonical kinds are bar, horizontal-bar, line, area, stacked-bar, stacked-area, combo, pie, donut, scatter, funnel, pyramid, treemap, and gauge. Funnel, pyramid, and treemap require one categorical dimension and exactly one non-negative metric. Funnel and pyramid preserve the canonical row order; pyramid reverses the visual direction without sorting stages by value. Treemap materializes the same governed category/metric buckets as proportional rectangles without introducing hierarchy or engine-specific options into the public contract. Gauge requires one dimension, exactly one metric and an explicit finite gauge.scale whose max is greater than min. A remote gauge uses praxis.stats group-by with source.options.limit = 1; zero or multiple buckets, non-finite values and values outside the declared scale fail closed instead of being clamped. Pointer, ticks and angles remain renderer details and are not part of the public contract. Scatter accepts one or two metrics. With one metric, the first dimension is X and the metric is Y. With two metrics, the first dimension remains the grouped bucket identity while the first and second metrics map to X and Y; the canonical mapper derives the multi-metric stats request.

Governed chart/table presentation

PraxisAnalyticsPresentationComponent materializes an existing x-ui.analytics projection as a chart or analytic table. The projection is selected by exact projectionId; the component reuses AnalyticsPresentationResolver and the official Charts/Table adapters instead of rebuilding stats requests.

<praxis-analytics-presentation
  [analytics]="analytics"
  projectionId="supplier-procurement-funnel"
  [availableFamilies]="['chart', 'analytic-table']"
  [preferredFamily]="savedPreference"
  [queryContext]="queryContext"
  (presentationChange)="rememberTemporarily($event)"
/>

Choice precedence is temporary user choice, persisted preferredFamily, then projection hints. availableFamilies must already reflect host policy/capabilities; the runtime intersects it with installed and semantically eligible renderers. Incompatible preferences fail closed. Switching renderer may execute the same canonical query again; when projection, family or queryContext changes, the component unsubscribes the previous Table/KPI request so the HTTP transport is aborted instead of merely ignoring a stale result. KPI is eligible for canonical group-by projections with exactly one primary metric. Summary-list remains unavailable until it has a canonical remote renderer.

Canonical Chart Documents

New x-ui.chart documents should describe sizing, surface mode, source, dimensions and metrics declaratively.

const chartDocument: PraxisXUiChartContract = {
  version: '0.1.0',
  kind: 'bar',
  chartId: 'status-by-team',
  sizing: { mode: 'fill-container', minHeight: 160 },
  theme: { surface: { mode: 'embedded' } },
  source: { kind: 'derived' },
  dimensions: [{ field: 'team' }],
  metrics: [{ field: 'total', aggregation: 'count' }],
};

Governed period comparisons use the same document and the canonical comparison stats operation. The resource must publish canonicalOperations.statsComparison=true; the dimension must be group-by eligible, the period field time-series eligible, and metrics are limited to count, distinct-count, or sum as declared by capabilities.

const comparisonDocument: PraxisXUiChartContract = {
  version: '0.1.0',
  kind: 'bar',
  chartId: 'employees-by-department-comparison',
  source: {
    kind: 'praxis.stats',
    resource: '/api/human-resources/employees',
    operation: 'comparison',
    options: {
      comparisonPeriod: {
        field: 'admissionDate',
        timezone: 'America/Sao_Paulo',
        preset: 'LAST_30_DAYS',
        mode: 'PREVIOUS_ALIGNED',
      },
    },
  },
  dimensions: [{ field: 'department' }],
  metrics: [
    { field: 'employeeId', aggregation: 'distinct-count', label: 'Employees' },
    { field: 'absenceDays', aggregation: 'sum', label: 'Absence days' },
  ],
};

The mapper emits one POST /stats/comparison request with canonical metrics[]; it never downgrades the document into independent period requests or a singular metric. Each governed metric is materialized as current and previous display series.

Supported sizing modes are fixed, fill-container and auto. Prefer fill-container inside dashboard widget shells. Supported surface modes are auto, embedded and contained; prefer embedded when another shell owns the card, header or border.

An explicitly embedded chart does not inherit a contained background from the page's chart preset. Both the engine canvas and the HTML surface remain transparent unless the chart explicitly authors their backgrounds, while its unspecified foreground follows the host. Palette inheritance is preserved. Explicit chart surface/background and text colors remain authoritative; choosing them requires a coherent contrast pair. Changing the host theme updates inherited colors without modifying the chart document.

Micro Visualizations

Transparent charts inside WidgetShell inherit the shell foreground, including fixed dark finishes in a light application theme. The chart's opaque accessible-data panel pairs its own background with the corresponding foreground. Explicit chart text colors keep precedence.

PraxisMicroVisualizationComponent renders the first renderer-neutral presentation visualizations from @praxisui/core. It is intended for compact presentation surfaces, especially table cells, list items and read-only form summaries.

The table-safe kinds currently aligned with PraxisPresentationVisualizationKind are:

  • line
  • area
  • column
  • comparison
  • stackedBar
  • radial
  • harveyBall
  • bullet
  • delta
  • processFlow

The component does not instantiate ECharts. It renders lightweight HTML/CSS from PraxisPresentationVisualizationConfig and falls back to fallbackText when the visualization kind cannot be rendered for the current compact surface. Use points as the canonical numeric series for line, area, column, and comparison; use segments for stackedBar, thresholds for bullet, and items for item/step-oriented visualizations such as processFlow. Rich charts still use the canonical x-ui.chart document flow above; micro visualizations are the lightweight presentation contract for dense cells, list items, object headers, card summaries and form presentation surfaces.

Neutral micro visualizations inherit foreground from their container unless the existing --prx-micro-text-color token overrides it. Radial indicators with surface: 'card-summary' and size: 'md' or 'responsive' render at 6rem; table-cell indicators retain their compact 24px geometry. This materializes existing surface/size semantics without changing the configuration shape.

The Dynamic Page Lab's preset studio includes “Testar com componentes reais”: official Table, Chart, Dynamic Form and Micro Visualization components using identified local demonstration data. It supports changing shell finishes, local table selection/pagination, chart data inspection, form editing/read-only mode and supported demonstration states. This fixture does not save business records.

Authoring Surface

PraxisChartConfigEditor is the initial editor shell for canonical x-ui.chart documents. It consumes governed resources, fields and targets and emits structured apply/save/reset events. The authoring manifest is exported as PRAXIS_CHARTS_AUTHORING_MANIFEST for backend/tooling workflows that need executable chart-edit operations.

For the direct runtime editor, Apply updates the chart and emits chartDocumentApplied; Save emits chartDocumentSaved. Both also emit chartDocumentPatchChange with an independent inputPatch.chartDocument snapshot for dynamic widget hosts. Dynamic Page/Page Builder updates its authored page, so Apply followed by Save Page preserves the canonical document. Apply alone does not persist the page remotely. Unrelated widget inputs and transient catalogs remain owned by their existing host/context boundaries.

ChartResourceCapabilityCatalogAdapter derives resource operations and eligible dimension, time and metric options directly from ResourceCapabilitySnapshot, including fail-closed comparison eligibility.

Applications should install providePraxisCharts() once. The registered ComponentDocMeta.configEditor.contextResolver materializes the editor context without making Page Builder own Chart semantics. Initial resolution groups /schemas/catalog endpoints by the stable resourceKey, exposes that value as availableResources[].id, and keeps the catalog's governed root-relative resource path as availableResources[].path. A selected praxis.stats source is normalized to that operational path before save; the discovery id is never persisted as an executable URL.

Capabilities are loaded lazily from the exact catalog-published endpoint only after resource selection. Concurrent catalog and per-resource capability reads are deduplicated in flight and bounded by a 10-second timeout, but successful permission-sensitive projections are not cached across requests until the host publishes a canonical authentication/context epoch. Denied, unavailable, ambiguous, stale and empty evidence fails closed, and later requests remain retryable. canonicalOperations gates group-by, timeseries and distribution, while stats.fields[] gates dimensions, metrics, aggregations and exact distributionModes eligibility for terms versus histogram. /schemas/filtered enrichment of field titles, types and x-ui remains a later schema-flow gate; capabilities remain the authority for eligibility.

Target choices come only from existing top-level composition links whose source is the current Chart and whose exact structured source event port is public and non-deprecated. Raw pointClick evidence never authorizes a configured action; events.pointClick uses pointAction. Directly compatible widget ports are admitted; an explicit transform.output.semanticKind is inspected only as provisional local evidence. That local check does not replace a transform-output projection materialized and validated by Core, which remains a P2 gate. Destinations are limited to compatible public, non-deprecated widget inputs or explicitly writable declared page state. Each target carries events[], so a link from crossFilter cannot authorize the same target for pointClick, selectionChange or drillDown; routes, nested ports, global actions, undeclared state and unconnected widgets are not synthesized.

Resource, field and target catalogs, along with context diagnostics, are transient authoring evidence. Apply/save persists only the canonical widget inputs and chartDocument; it never serializes availableResources, availableFields, availableTargets or contextDiagnostics into the page definition.

Visual authoring now consumes this projection, and the backend handler, resolver and validators execute the same event-scoped contract directly. P1 remains open only at the integration boundary: Page Builder/assistant requests still need to project the transient availableTargets[].events catalog through validationContext and surface the resulting fail-closed diagnostics. P2 remains open for canonical Core projections of transform-output semantics and target input-schema/port fields; backend-enriched inputFields is not a substitute for that visual composition contract.

Public API Snapshot

Main exports include PraxisChartComponent, PraxisChartDocumentPatchChangeEvent, PraxisMicroVisualizationComponent, PraxisChartConfigEditor, chart event/config models, PraxisXUiChartContract, engine adapter and factory tokens, providePraxisCharts, canonical mapping/normalization/validation services, analytics chart services, chart metadata and PRAXIS_CHARTS_AUTHORING_MANIFEST.

Remote chart documents in Page Builder

Author editable remote widgets with inputs.chartDocument (PraxisXUiChartContract), not a persisted runtime config or hand-built stats request. The canonical mapper materializes the runtime configuration and request. The public-chart-document-praxis-stats-timeseries and public-chart-document-praxis-stats-group-by profiles attest resource reads from chartDocument.source.resource and validate the queryContext filter against the published operation schema. They reject custom resolvers, local data, competing runtime config and document events with undeclared effects.

These public read profiles keep child enableCustomization disabled. A trusted Page Builder host exposes the component metadata editor through Configurar conteúdo and the card editor through Configurar widget, controlled by its existing authoring capabilities. Host customization mode is not persisted into the chart document. The Dynamic Page Lab demonstrates both remote operations using canonical documents.

Commands authored on a widget shell

The published open-chart-editor action executes command: "openConfigEditor" through PraxisChartComponent.handleShellAction. Its authored ID/label may change without changing the operation. It takes no example payload: the current canonical chartDocument and the owner's context resolver provide editor inputs. Opening requires enableCustomization, a Settings Panel bridge and a canonical chart document. The method checks these conditions at entry and rechecks availability after asynchronous context resolution, preventing a late open after customization is disabled. This is a UI authoring guard, not backend permission enforcement. Existing hosts calling openConfigEditor() directly must enable customization explicitly.

praxis-chart:toggle-data-view is a separate runtime-contributed accessibility control; it does not require customization. No CRUD or export semantics are inferred from button IDs.

Charts assesses the existing openConfigEditor command through the optional live shell availability query. It explains disabled customization, a missing Settings Panel bridge, a missing canonical document, or pending context resolution using localized copy. The owner's execution guard remains authoritative; the catalog's availability message is presentation state. Unrecognized commands return undefined. No automatic editor button is contributed by this query and no availability data enters chartDocument.

The positive shell-command E2E inserts a chart from the official palette, adds the available editor action, renames the button, applies a title, resets the draft, saves another title, and reopens it. It checks both the rendered chart and the Lab's authored chartDocument. This proves the in-page round-trip, not backend persistence after reloading the page. Apply keeps the Charts draft unsaved; Reset returns it to the opening snapshot, and Save advances that snapshot. The test does not assume Apply clears dirty state for this editor.

Chart editor availability also requires a live component owner. If context resolution finishes after the chart is destroyed, openConfigEditor must not open a panel. Queries against a destroyed owner's action availability return undefined. Destruction releases existing Apply/Save subscriptions, so late panel emissions cannot materialize another chartDocument patch. Charts passes its DestroyRef as the transient Settings Panel owner. The official bridge cancels that panel when the chart is destroyed, including a dirty draft, without emitting Save or asking to save a target that no longer exists. A pending opening is cancelled as well. This applies to the chart-owned editor; other consumers must explicitly supply their own owner to adopt the same lifecycle.

Cor dos nomes dos eixos

O adaptador ECharts aplica a cor efetiva de texto (theme.textColor) tanto aos valores quanto aos nomes dos eixos cartesianos, incluindo eixo secundário, orientação horizontal e dispersão. Essa projeção não cria opções de engine no contrato público nem altera dados ou formatação. A legibilidade final ainda depende da coerência entre a cor de texto e o fundo configurado.