Skip to content

Ui5Chart

Introduction

The Ui5Chart is a UI5 artifact that renders a single data visualization — a sparkline, trend line, bar comparison, donut, etc. — inside a dashboard group. A chart is computed server-side from bound slot values and the request context, and emits a sap.f.Card envelope wrapping a com.laravelui5.core.Chart payload.

Like Ui5Tile, a chart is not routable: it has no URL, no manifest, and no standalone endpoint. It exists only as part of a Ui5Dashboard, and its whole envelope (header + canvas + layout) is computed by its provider at the moment the dashboard is emitted. The rendering engine itself (ECharts) is host-loaded, not bundled by Core — see ECharts loading.

Conceptual Overview

Two seats, two jobs

  • The chart class (Ui5ChartInterface) is pure metadata: identity, the slots it depends on, and a handle to its provider. It is deliberately not a template factory — a chart's option tree is data-driven, so only the executor, which has the data, can build it.

  • The chart provider (ChartProviderInterface) is the executor. It computes the full chart envelope from the bound slot values and the request context:

    php
    public function getChart(array $boundParams, Ui5ContextInterface $context): Chart;

Two-DTO composition: Chart wraps ChartCanvas

The wire output is a two-layer envelope — an outer sap.f.Card (with header, optional toolbar, optional layoutData) carrying an inner com.laravelui5.core.Chart (with engine + option). At authoring time that maps to two DTOs:

  • Chart — the outer envelope. Extends AbstractCard. Takes a required canvas: ChartCanvas slot plus optional Header / layoutData / sizing.
  • ChartCanvas — the inner engine-rendered DTO. Carries engine + option. It is never used on its own: a Chart is the only thing that carries one (see There is no second path).

The provider composes both layers explicitly: it builds the Chart and hands it a ChartCanvas. The two-layer shape is visible at every authoring site, mirroring how a Tile provider composes GenericTileTileContent[] → payload. On the wire, Chart places the canvas inside a sap.m.VBox as the card's content.

Header auto-population

If the provider returns a Chart with header: null, the ChartEmitter auto-builds a Header from the artifact's getTitle() (→ title) and getDescription() (→ subtitle). Providers that want a custom header (status text, icon, timestamp, toolbar) pass an explicit Header through the Chart constructor.

Lifecycle

  1. The chart is declared in a module and referenced from a dashboard group.
  2. When the dashboard is emitted, the framework resolves the chart's required slots and calls getChartProvider()->getChart($boundParams, $context).
  3. The provider returns a Chart (wrapping a ChartCanvas); the framework auto-populates the header from artifact metadata if absent, then places the envelope verbatim into the dashboard tree.

The provider reads the actor from $context, never from a slot — slots carry wire-shaped values, identity comes from the context.

How to Generate

bash
php artisan ui5:chart Finance/RevenueTrend \
  --title="Revenue Trend" \
  --description="Quarterly revenue over the trailing 12 months"

Options

OptionDefaultDescription
name (argument)(required)Format: {AppName}/{ChartName}
--titleThe chart nameChart title
--descriptionChart generated via ui5:chartChart description
--seed[=<name>](bare)Scaffold the provider from a named seed (see Seeding)

The PHP and JS namespaces are derived from the target app's module — there are no namespace-prefix options.

Output

Given php artisan ui5:chart Finance/RevenueTrend:

ui5/
└── Finance/
    └── src/
        └── Charts/
            ├── RevenueTrendChart.php
            └── Provider/
                └── RevenueTrendChartProvider.php

On success the command prints the exact registration directives — the module method (getCharts()) and the owning Dashboard Group's getChildNamespaces(), each with the literal line to add.

Seeding

By default the ChartProvider is a bare shell — it returns a chart with an empty ECharts option. To scaffold a populated starting point:

bash
php artisan ui5:chart Finance/RevenueTrend --seed=revenue-trend
php artisan ui5:chart Finance/RevenueTrend --seed   # pick from the catalog

--seed swaps exactly one file — the ChartProvider. An unknown seed name fails with the list of available seeds. The chart seed catalog ships revenue-trend, calls-per-week, pipeline-funnel, quota-attainment and revenue-mix; it is open content — a new seed is a stub plus a seeds.json row, no command change.

Artifact Roles

RevenueTrendChart.php

Implements Ui5ChartInterface. Identity is declared as class constants (the getters are inherited from AbstractUi5Chart); the only behaviour is the handle to its provider:

plannedThe generators still slug the URL segment with `Str::snake` for tiles, charts, actions, reports and resources — so a freshly scaffolded one reads `project_kpi`. Moving them all to `kebab` is queued; it changes new scaffolds only, never an existing artifact.
php
class RevenueTrendChart extends AbstractUi5Chart
{
    public const string NAMESPACE   = 'com.acme.finance.charts.revenue-trend';
    public const string VERSION     = '1.0.0';
    public const string TITLE       = 'Revenue Trend';
    public const string DESCRIPTION = 'Quarterly revenue over the trailing 12 months';

    public function getChartProvider(): string
    {
        return RevenueTrendChartProvider::class;
    }
}

getChartProvider() declares the provider by class-string; the framework resolves it through the container, so the provider's constructor dependencies are autowired. Never write return app(...) or return new ... here — a declaration method declares, it does not build.

If the chart depends on slot values, declare them with getRequiredSlots(); the resolved values arrive in $boundParams. (See Slots.)

RevenueTrendChartProvider.php

Composes the full envelope. Follow the injection recipe: services go in the constructor (autowired, since the framework resolves the declared class-string through the container); the per-request inputs — $boundParams and $context — arrive as the typed getChart() arguments.

php
class RevenueTrendChartProvider implements ChartProviderInterface
{
    public function __construct(private readonly RevenueRepository $revenue) {}

    public function getChart(array $boundParams, Ui5ContextInterface $context): Chart
    {
        $series = $this->revenue->quarterly();

        return new Chart(
            canvas: new ChartCanvas(
                engine: 'echarts',
                option: [
                    'xAxis'  => ['type' => 'category', 'data' => $series->labels()],
                    'yAxis'  => ['type' => 'value'],
                    'series' => [['type' => 'line', 'data' => $series->values()]],
                ],
            ),
            layoutData: new GridContainerItemLayoutData(columns: 6, rows: 4),
            height:     '100%',
        );
    }
}

The option is engine-native JSON; Core does not translate, normalise, or schema-validate it. See the ECharts option reference.

Module Integration

Register charts in their module's getCharts(), passing the module instance:

php
public function getCharts(): array
{
    return [
        new Charts\RevenueTrendChart($this),
    ];
}

A chart is rendered by referencing its ::NAMESPACE from a dashboard group's getChildNamespaces() — see Ui5Dashboard. There is no route to configure: charts are composed, not addressed.

There is no second path

A ChartCanvas is only ever carried by a Chart. There is no other container that takes one, and the dashboard's wire vocabulary is closed: the walker in @laravelui5/core accepts sixteen class names, and anything else from the server fails envelope validation with UnknownControlError before it renders.

Two consequences worth knowing before you try:

Chart already is the card. It extends AbstractCard, and the emitter turns it into the two-level wire shape sap.f.Cardcom.laravelui5.core.Chart. Wrapping it in another card is not a smaller path, it is a second card.

sap.ui.integration.widgets.Card takes no content at all. Its constructor takes a manifest — an integration card's body comes from its card manifest, rendered on the server, not from a DTO tree assembled in PHP. The two classes share a short name and nothing else.

If a chart needs surrounding chrome — a caption, a toolbar, a second figure — that is what the Chart's own header and the header's toolbar are for, and both are part of the vocabulary.

The working reference

php artisan ui5:assemble Showcase scaffolds five chart artifacts across three dashboard groups — a sparkline, a trend, a funnel, a gauge and a mix. Each one is a provider returning a Chart that wraps a ChartCanvas, exactly as above:

php
public function getChart(array $boundParams, Ui5ContextInterface $context): Chart
{
    return new Chart(
        canvas: new ChartCanvas(
            engine: 'echarts',
            option: [
                'xAxis'  => ['type' => 'category', 'data' => ['Q1', 'Q2', 'Q3', 'Q4']],
                'yAxis'  => ['type' => 'value'],
                'series' => [['type' => 'bar', 'data' => [120, 200, 150, 80]]],
            ],
        ),
        height:     '100%',
        layoutData: new GridContainerItemLayoutData(columns: 2, rows: 2),
    );
}

That is the generated revenue-trend seed, unedited. See Self-contained apps for the whole assembly, and --seed on ui5:chart for the other four.

ECharts loading

Core ships zero third-party JS. ECharts is loaded by the host via the bootstrap-extension hook (@includeIf('ui5.head')) — typically pinned to a CDN URL or a host-served asset:

blade
{{-- resources/views/ui5/head.blade.php --}}
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/echarts.min.js"></script>

The control reads window.echarts when it renders (onAfterRendering). If it isn't on the page, the chart stays empty and an error is logged; the rest of the dashboard keeps working. See the bootstrap-mechanism spec for the broader extension model.

Best Practices

  • Keep the chart class metadata-only; build the visual in the provider.
  • Inject services via the provider's constructor; take per-request values from $boundParams / $context.
  • Let ChartEmitter populate the header from artifact metadata by default; override with an explicit Header only when you need status text, an icon, or a header toolbar.
  • The option is engine-native — author it directly against the ECharts reference. Don't wrap it in helpers prematurely.
  • For press-intent on the whole card, set parameters on the Chart; for per-data-point intent, stuff intent onto the ECharts data points and read it in the consumer's press handler — see Ui5Dashboard for the action-event shape.