Data Widgets

Data widgets are a set of React components for building data dashboards inside custom views. They integrate with data queries to fetch, display, and interact with query results.

The same widgets draw in an AI chat answer: the show_data tool takes a query and one widget with its props as JSON, and the chat renders it as a card inside the answer. See Data widgets in answers.

Quick Start

A data dashboard is a DataDashboard holding DataDashlet panels. Each dashlet runs one query and hands its result to render as a single dataset, data. Every widget takes that dataset as data={data} and binds fields by name:

<custom name="my-dashboard" title="My Dashboard">
  <template><![CDATA[<>
    <DataDashboard>

      <DataDashlet query="my.query" colSpan={6} height={400}
        render={(data) => (
          <DataWidget data={data} title="My Chart">
            <DataChart data={data} type="bar" x="name" series={[{ field: "value" }]} />
          </DataWidget>
        )}
      />

      <DataDashlet query="my.other-query" colSpan={6}
        render={(data) => (
          <DataWidget data={data} title="My Table" padding={0}>
            <DataTable data={data} sortable />
          </DataWidget>
        )}
      />

    </DataDashboard>
  </>]]></template>
</custom>

Keep the render callback to widgets and field names. It is still plain JSX, so a shape no widget prop can express can be built there and passed to a widget as records (with columns), but values, colours and orderings belong in widget props and in the query.

A field a widget binds that the query does not return shows an error naming the columns the query does return, rather than an empty widget.

The <dataset> element is optional when using DataDashlet — data fetching is handled via the query REST API.

The Dataset

render receives the query result as it came from the server, plus what the dashlet controls:

Property Type Description

records

object[]

Result rows, with a selection column’s values replaced by their titles

rawRecords

object[]

The same rows with stored values

columns

DataColumn[]

Column metadata, including declared format and aggregate

completeness

string

complete, page, truncated or unknown (see data queries)

total

number | null

Counted rows, or null when no count ran

offset, limit

number

The window the server applied

summary

object[]

Query-scope totals the query declares under <summary>

parameters

object[]

The parameter values the query ran with

loading

boolean

true during the initial fetch

refresh

function

Re-fetch the data

setOffset

function

Change the pagination offset

onFilter

function

Opens the filter dialog for dashlet-specific params (only when the query has filterable params not covered by shared dashboard params)

A widget given records instead of data treats them as of unknown completeness: nothing counted them.

Values are formatted by the format, currency (or currencyField) and scale their query column declares, unless a widget prop overrides it. A missing value shows as a dash, never as zero.

Layout Components

DataDashboard

A CSS grid container (12 columns) that arranges dashlets. Supports shared dashboard-level filters that propagate to all child dashlets.

Prop Type Description

params

object

Shared filter config: { query: "queryName", names: ["param1", "param2"] }

gap

number | string

Gap between dashlets (default: "1rem")

children

ReactNode

DataDashlet components

When params is set, a floating filter button appears in the top-right corner. Clicking it opens a filter dialog with the specified parameters (metadata and defaults are fetched from the query’s describe endpoint). Applying the filter updates all dashlets that share those parameter names.

<DataDashboard params={{ query: "sale.kpi", names: ["fromDate", "toDate"] }}>
  <DataDashlet query="sale.kpi" ... />
  <DataDashlet query="sale.daily-trends" ... />
</DataDashboard>

DataDashlet

Fetches data from a named query and passes the dataset to a render function.

Prop Type Description

query

string

Name of the data query to execute

params

object

Query parameters to pass

paramMap

object

Maps query param names to shared param names (e.g., { startDate: "fromDate" })

limit

number

Page size for server-side pagination

colSpan

number

Grid column span (1-12, default: full width)

height

number | string

Fixed height for the dashlet

render

function

(data) ⇒ ReactNode, receiving the dataset

When the server cut the result down to its row cap, the dashlet notes it under its content, with the counted total.

Use paramMap when a query uses different parameter names than the shared dashboard params:

<!-- This query uses startDate/endDate instead of fromDate/toDate -->
<DataDashlet query="other.query"
  paramMap={{ startDate: "fromDate", endDate: "toDate" }}
  render={...} />

DataWidget

A card container with optional header (title, icon, pagination, refresh).

Prop Type Description

data

Dataset

Supplies the loading state, the window for paging and the filter button

title

string

Header title

subtitle

string

Header subtitle

icon

string

Material icon name

padding

number | string

Body padding (default: "0.5rem", use 0 for tables)

onRefresh

function

Refresh button callback, typically data.refresh

onPageChange

function

Pagination callback, typically data.setOffset

footnote

ReactNode

A muted line under the content, such as the rule the figures are read against

loading, total, offset, limit, onFilter

Override what data supplies

Refresh and paging are opt-in: a query’s own limit, such as a top 10, is a window worth showing but not a page to move through.

DataLayout

A simple CSS grid layout container for arranging items in columns.

Prop Type Description

cols

number

Number of columns

gap

number | string

Gap between items

Chart

DataChart

Renders charts using ECharts. Supports multiple chart types, dual value axes, mixed series types, and grouped/stacked layouts.

Prop Type Description

data

Dataset

The dashlet’s dataset

type

string

Chart type (see below)

x

string

Category field (default: the first column)

series

object[]

{ field, type?, axis?, stack?, title? } per series (default: the second column)

axes

object[]

Value axis options by index: { format?, currency?, scale?, min?, max? }

group

string

One series per value of this field, with a single series

stacked

boolean

Stack every series (default: false)

missing

string

"gap" (default) draws a missing value as a gap, "zero" as 0

smooth

boolean

Smooth line curves (default: false)

legend

boolean

Show legend (default: true)

colors

string[]

Colour values

xRotate

number

Category label rotation angle (default: 45)

Supported chart types: bar, line, pie, donut, area, hbar, scatter, radar, funnel. Axis labels and tooltips format values by the axis format, or by the series column’s declared format.

A pie, donut or funnel refuses a negative value, and a grouped chart refuses two rows for one category in one group: the query should aggregate them.

Mixed Chart Example

Give a series its own type and axis:

<DataChart data={data} type="bar" x="date"
  series={[
    { field: "revenue" },
    { field: "orderCount", type: "line", axis: 1 },
  ]} />

This renders revenue as bars on the first value axis and orderCount as a line on the second.

Grouped Chart Example

Use group with a single series to split it by group value:

<DataChart data={data} type="bar" x="month"
  series={[{ field: "revenue" }]} group="region" stacked />

This creates one bar series per distinct region value, stacked by month.

Tables

DataTable

A sortable, exportable data table with optional grouping and totals.

Prop Type Description

data

Dataset

The dashlet’s dataset

fields

FieldSpec[]

Columns to show, in order, and how each is drawn (shows all if omitted; see Fields)

sortable

boolean

Enable column sorting (default: true)

exportable

boolean

Enable CSV export (default: true)

compact

boolean

Compact row height

aggregates

boolean

Show the totals row (default: true)

groupBy

string[]

Fields to group rows by, outermost first

The totals row covers the columns the query aggregates. Sums and counts add up, minima and maxima combine, and an average has no total, since an average of averages ignores group sizes. Rows are totalled only when they are the whole answer; for a page or a truncated result the row shows the query’s <summary> totals, captioned as query totals, or nothing.

Amounts whose column names a currencyField are shown in each row’s currency and are totalled only across rows in one currency: a total, a group subtotal or a pivot cell over several currencies is left empty rather than adding them up. Such a column’s query total is not shown, since it would add every currency.

For server-side pagination, give the dashlet a limit and the widget an onPageChange:

<DataDashlet query="my.query" limit={10}
  render={(data) => (
    <DataWidget data={data} title="Paginated Table" padding={0}
      onPageChange={data.setOffset} onRefresh={data.refresh}>
      <DataTable data={data} sortable />
    </DataWidget>
  )}
/>

DataPivot

A pivot table with hierarchical row grouping, sortable columns, totals, and optional inline bars or heatmap shading.

Prop Type Description

data

Dataset

The dashlet’s dataset

rows

string[]

Row dimensions, outermost first (hierarchical)

cols

string[]

Column dimensions for dynamic column pivoting

measures

object[]

{ field, fn?, format?, title? } per value; fn is sum (default), avg, min, max or count

showTotals

boolean

Show row and grand totals (default: true)

showBars

boolean

Show inline bar indicators (default: false)

heatmap

boolean

Shade each cell by its size against the measure’s largest cell at the same row level, negative values in red; a group is compared with its peer groups, never with the rows it adds up, and the total column and grand total stay unshaded (default: false)

Every cell, row total and grand total is computed from the underlying rows, per measure, so a grand average is the average of the rows rather than of the group averages. When the query already aggregated, a measure whose column is an average cannot be summed or averaged again; take its min or max, or select its sum and count in the query.

When rows has several fields (e.g. ["customer", "product"]), the pivot renders collapsible row groups. Use the + / - buttons to expand or collapse all groups.

Indicators

DataKPI

A single metric display with icon, label, formatted value and an optional trend line.

Prop Type Description

data

Dataset

The dashlet’s dataset

field

string

The value’s field

recordIndex

number

The row to read (default: 0)

value

number | string

A literal value, instead of field

label

string

Display label

icon

string

Material icon name

format

string

number, currency, compact or percent (default: the column’s declared format)

prefix, suffix

string

Text around the value

comparison

object

Change against another value (see below)

sparkline

string

A field whose values across all rows, in their order, draw a trend line; row recordIndex is marked on it

color

string

Accent color: primary, success, warning, danger, info, or any CSS color

variant

string

Visual style: filled, outlined, or flat (default)

comparison is { field | value, label?, display?, better? }: the value to compare against, from another field of the same row or a literal; display is percent (default) or absolute; better is higher, lower or neutral (default). An unchanged value is neutral, a percent change against zero is not shown, and nothing is coloured good or bad until better says which way is good.

<DataKPI data={data} field="revenue" label="Revenue" icon="payments"
  comparison={{ field: "previousRevenue", label: "vs last month", better: "higher" }} />

DataProgress

A progress indicator in bar, ring, or gauge style.

Prop Type Description

data

Dataset

The dashlet’s dataset

field

string

The value’s field, read from row recordIndex

targetField

string

The target’s field, in the same row

value, target

number

Literal value and target (target default: 100)

label

string

Display label

format

string

Display format: percent (default), number, or currency

variant

string

bar, ring, or gauge (default: bar)

display

string

What the text states: percent, value, fraction (3/5) or fraction-percent (3/5 | 60%); default: value under a number or currency format, else percent

precision

number

Decimal places of the percentage (default: 1)

layout

string

stacked (default) or inline: a bar’s label, bar and value on one line, the value in the bar’s colour

color

string

A fixed colour; without one, thresholds decide

thresholds

object[]

{ min, color } bands in percent of the target

size

string

Size for ring/gauge: sm, md (default), or lg

showValue

boolean

Show the formatted value (default: true)

The default thresholds are danger from 0, warning from 50 and success from 80 percent; a given array replaces them entirely, and the band with the highest minimum the value reaches wins. The fill clamps to 0–100 while the displayed value does not. A missing value, or a target of zero or less, shows as unavailable.

DataCard

The fields of one row, stacked or on a grid, for a detail summary.

Prop Type Description

data

Dataset

The dashlet’s dataset

fields

FieldSpec[]

The fields to show (see Fields)

recordIndex

number

The row to show (default: 0)

gridColumns

string

A CSS grid track list; without one, fields stack

gap

string

Space between fields

children

ReactNode

Additional content below the fields

Fields

DataList, DataCard and DataTable draw values through the same field descriptors:

Property Type Description

field

string

The record key

type

string

text (default), badge, dot (a coloured dot beside the text) or progress

label

string

A label above the value, or a table column’s title

icon

string

A Material icon before the value, in the value’s colour or the accent colour

to

string

A second field shown after this one as a range, start → end

format, currency, scale

Override the column’s declared presentation

colors

object

Value → colour token or CSS colour

labels

object

Value → display text

bold, muted, wrap

boolean

Text appearance

align

string

start, center or end

col, colSpan, row

number

1-based placement on the container’s gridColumns

progress

object

{ target?, targetField?, thresholds?, color?, format?, display?, precision?, layout? } for type: "progress"

colors and labels match the stored value first, then the shown title. Fields render in the order they are listed; grid placement only moves them visually, so keyboard and screen-reader order follow the list.

Lists and Flows

DataList

A list of rows: ranked bars, or rows drawn from fields.

Prop Type Description

data

Dataset

The dashlet’s dataset

label, value

string

Ranked bars: the label and value fields (default: the first two columns)

fields

FieldSpec[]

Rows drawn from fields instead (see Fields)

itemKey

string

The field that identifies a row; required with fields

gridColumns

string

CSS grid tracks for each row’s fields

gap

string

Space between a row’s fields

sort

object | false

{ field, direction: "asc" | "desc" }; rows keep the query’s order without it

limit

number | "all"

Maximum number of rows (default: 10)

showValue

boolean

Show the formatted value next to the bar

showRank

boolean

Show rank numbers

color

string

Bar color (semantic name or CSS color)

A negative value draws as a red bar rather than an empty one.

<DataList data={data} itemKey="requestId"
  gridColumns="auto minmax(0, 1fr) auto"
  fields={[
    { field: "status", type: "dot", colors: { ABSENT: "danger", PRESENT: "success" } },
    { field: "name", bold: true },
    { field: "period", align: "end" },
    { field: "absenceType", muted: true, col: 2 },
    { field: "approval", type: "badge", col: 3 },
  ]} />

DataFlow

A horizontal step/pipeline visualization with optional tags.

Prop Type Description

data

Dataset

The dashlet’s dataset: one step per row

label, value

string

The step label and value fields

status

string

The field whose value picks a step’s colour (default: label)

colors

object

Value → colour, e.g. { CLOSED: "success", OPEN: "primary" }

format

string

Value format (default: the column’s declared format)

steps

object[]

Explicit steps instead of rows

tags

object[]

Optional tag badges displayed below the pipeline

An explicit step is { label, value, format?, prefix?, suffix?, color?, icon?, action? }. The action field navigates to a view when the step icon is clicked (e.g. "sale.orders"). Each tag object is { label, color? }. Labels show as written.

Utility Components

DataEmpty

Placeholder component for loading, error, and empty states. Used internally by DataDashlet but can also be used directly.

Prop Type Description

state

string

"loading", "error", or "empty"

message

string

Error message to display (for "error" state)

DataParams

A form component for rendering query parameter inputs. Used internally by the filter dialog but can also be used standalone for inline parameter forms.

Prop Type Description

params

DataParam[]

Parameter metadata array

values

object

Current parameter values

onChange

function

Called with updated values (values) ⇒ void

layout

string

"inline" (default) or "stacked"

submitLabel

string

Label for submit button

onSubmit

function

Submit button callback

Supports scalar types (text, number, date, boolean), selections, and relational types (many-to-one, many-to-many) with auto-complete.

Complete Example

<custom name="sale-analytics" title="Sales Analytics">
  <template><![CDATA[<>
    <DataDashboard params={{ query: "sale.kpi", names: ["fromDate", "toDate"] }}>

      <DataDashlet query="sale.kpi" colSpan={12}
        render={(data) => (
          <DataLayout cols={4} gap="1rem">
            <DataWidget>
              <DataKPI data={data} field="totalOrders" label="Total Orders"
                icon="shopping_cart" color="primary" />
            </DataWidget>
            <DataWidget>
              <DataKPI data={data} field="totalRevenue" label="Total Revenue"
                icon="payments" format="compact" color="success" />
            </DataWidget>
          </DataLayout>
        )}
      />

      <DataDashlet query="sale.order-status" colSpan={12}
        render={(data) => (
          <DataWidget data={data} title="Sales Pipeline">
            <DataFlow data={data} label="status" value="revenue"
              colors={{ OPEN: "primary", CLOSED: "success", CANCELED: "danger" }} />
          </DataWidget>
        )}
      />

      <DataDashlet query="sale.daily-trends" colSpan={6} height={400}
        render={(data) => (
          <DataWidget data={data} title="Revenue vs Orders">
            <DataChart data={data} type="bar" x="date"
              series={[
                { field: "revenue" },
                { field: "orderCount", type: "line", axis: 1 },
              ]} />
          </DataWidget>
        )}
      />

      <DataDashlet query="sale.top-customers" colSpan={6}
        render={(data) => (
          <DataWidget data={data} title="Top Customers" icon="group">
            <DataList data={data} label="customer" value="revenue"
              showRank color="primary" />
          </DataWidget>
        )}
      />

      {/* Dashlet with its own filter (customerId): the filter button comes from data */}
      <DataDashlet query="sale.customer-orders" colSpan={12}
        render={(data) => (
          <DataWidget data={data} title="Customer Orders" padding={0}
            onRefresh={data.refresh}>
            <DataTable data={data} compact sortable />
          </DataWidget>
        )}
      />

      <DataDashlet query="sale.customer-product-pivot" colSpan={12} height={500}
        render={(data) => (
          <DataWidget data={data} title="Customer x Product" padding={0}>
            <DataPivot data={data} rows={["customer", "product"]}
              measures={[
                { field: "draftQty" },
                { field: "openQty" },
                { field: "closedQty" },
                { field: "totalQty" },
              ]}
              showTotals showBars />
          </DataWidget>
        )}
      />

    </DataDashboard>
  </>]]></template>
</custom>