The Chart Output node is the terminal node of a dashboard widget workflow. It takes the rows produced by the upstream nodes and turns them into a standardized chart payload that the dashboard renders.
Overview
| Property | Value |
|---|---|
| Inputs | 1 |
| Outputs | 0 (terminal) |
| Output | Standardized chart payload |
Place this node last in a dashboard-widget workflow. For entity relationships, the node before it produces a graph object (see Entity relationships). Other data-driven charts use an array of row objects, for example:
[
{ "month": "Jan", "revenue": 120 },
{ "month": "Feb", "revenue": 150 }
]When you only have example/sample data (no real source), produce these rows with a Set node using $array(dict(...), dict(...)) — for example $array(dict(month="Jan", revenue=120), dict(month="Feb", revenue=150)). Do not use ${...} or bare {...} object literals; Heym builds objects only with dict(key=value).
Parameters
| Parameter | Type | Description |
|---|---|---|
label | string | Node identifier (camelCase) |
chartType | string | pie, bar, line, area, table, numeric, gauge, scatter, proportion, barGauge, gantt, erDiagram, text, or hitl |
orientation | string | vertical or horizontal (bar charts only) |
dataPath | string | Dot path to the rows array (or graph object for erDiagram) inside the upstream output (e.g. data or result.items). Leave empty to auto-detect. |
labelField | string | Row key used as the category label (pie/bar/line) |
valueField | string | Row key used as the numeric value (pie/bar/line/numeric/gauge) |
series | array | Optional multi-series definition [{ "name": "Sent", "field": "sent" }] for bar/line (overrides valueField) |
columns | array | Optional column list for table (defaults to the keys of the first row) |
statusColumn | string | Optional table column rendered as colored status chips. It must be one of the shown columns. |
statusTones | object | Optional map from a status value to a tone (success, attention, failure, waiting, neutral) for values the defaults do not cover |
linkColumn | string | Optional table column whose cells hold a URL, shown as a link button that opens it in a new tab. It must be one of the shown columns. |
linkLabel | string | Optional text of the link button. Left empty, the button shows only a link icon. |
xField / yField | string | Row keys for the X and Y numeric axes (scatter) |
min / max | number | Numeric range for gauge (default 0 / 100) |
unit | string | Optional unit shown next to a numeric or gauge value |
text | string | Markdown message for the text chart type. Leave empty and set valueField to render a dynamic message pulled from upstream data. |
title | string | Optional chart title |
hitlWorkflowId | string | hitl only. Show only the reviews of this workflow. Left empty, the widget shows every review. |
hitlNodeId | string | hitl only. Narrow hitlWorkflowId to one step: an Agent with human review, or a Slack approval, buttons or form step. |
Chart types
- bar / line / area / pie — categorical charts driven by
labelField+valueField(orseriesfor multi-series bar/line/area).areais a filled trend chart. - table — raw rows rendered as a scrollable table using
columns. One column can render as status chips, and rows can open a detail page. - numeric — a single KPI value from the first row, with an optional
unit. - gauge — a single value shown against a
min–maxrange (e.g. a percentage). UsesvalueField. - scatter — X/Y points from
xFieldandyFieldfor correlation plots. - proportion — a single horizontal bar split into shares with a percentage legend (e.g. a language breakdown). Uses
labelField+valueField. - barGauge — one horizontal gauge per row with a red→green gradient and a value (e.g. free disk space). Uses
labelField+valueField, optionalunitandmax(defaults to the largest row value). - gantt — task schedules with dates, progress and dependencies; see Gantt schedules.
- erDiagram — database entities, fields, keys and relationships; see Entity relationships.
- text — a markdown message (e.g. a status note like "Last execution at 19:47"). Put the markdown in
textfor a static message, or leavetextempty and setvalueFieldto render a string produced upstream. Supports headings, bold/italic, lists, links, and inline code. - hitl — a carousel of your pending human reviews. The header shows
1/n pending; the left arrow is disabled on the first card and the right arrow on the last. The workflow name opens that run on the canvas. Approve, Request changes, and Reject resolve it, and the history icon beside the widget title opens that run in the history dialog on the same page. This chart type ignores upstream rows, so the widget workflow is only the Chart Output node. Each signed-in person sees the reviews on workflows they own. Pick a Workflow (and Select agent when it has several review steps) in the node's properties or the widget's Settings to show that workflow's reviews instead, to everyone who can open it; see HITL widgets.
Gantt schedules
Choose Gantt (gantt) for project schedules and task timelines. Set dataPath to the task array, such as tasks. Rows use fixed field names:
{
"tasks": [
{ "id": "design", "label": "Design", "start": "2026-10-01", "end": "2026-10-05", "progress": 100 },
{ "id": "build", "label": "Build", "start": "2026-10-05", "end": "2026-10-14", "progress": 40, "dependencies": ["design"] }
]
}start and end accept ISO dates or datetimes. Values without a timezone use UTC; timezone offsets are converted to UTC. end must be on or after start. progress is optional (0–100, default 0); dependencies contains existing task ids. Ids must be unique. Hover a task to read its full dates, progress and dependencies. Large charts scroll inside the widget. At most 500 tasks are accepted.
For sample data, a Set mapping named tasks can use $array(dict(id="design", label="Design", start="2026-10-01", end="2026-10-05", progress=60)). Connect it to Chart Output with chartType: "gantt" and dataPath: "tasks". Map source columns into these fixed names with a Set node; labelField and valueField are not used.
Entity relationships (DB)
Choose Entity relationships (DB) (erDiagram) for database schemas and entity connections. Set dataPath to a graph object, such as graph, containing both arrays:
{
"graph": {
"entities": [
{ "id": "customers", "label": "Customers", "fields": [{ "name": "id", "type": "uuid", "key": "PK" }] },
{ "id": "orders", "label": "Orders", "fields": [{ "name": "customer_id", "type": "uuid", "key": "FK" }] }
],
"relations": [
{ "source": "customers", "target": "orders", "sourceField": "id", "targetField": "customer_id", "cardinality": "one-to-many", "label": "places" }
]
}
}Fields have a name, optional type, and optional key (PK, FK, PK/FK, or empty). Relations reference existing entity ids; optional sourceField and targetField anchor the connection to named fields. cardinality accepts one-to-one, one-to-many (default), many-to-one, or many-to-many. Entity ids and field names within each entity must be unique. At most 100 entities, 100 fields per entity and 500 relations are accepted.
With a Set node, construct graph with $dict(entities=array(dict(...)), relations=array(dict(...))); use chartType: "erDiagram" and dataPath: "graph". This is a visualization of the schema you supply; it does not inspect a live database automatically.
Both types appear in Add widget with a preview and in the canvas node's chart type selector. Heym Work can propose these types when you describe a schedule or database schema, drawing the proposed tasks or entities before you build the page.
Interactive task lists
Use GitHub-flavored markdown task list syntax in the static text field:
- [x] Option 1
- [ ] Option 2
- [ ] Option 3On the Dashboard, checkboxes render as clickable controls (dark and light mode friendly). Toggling a checkbox updates the widget workflow's text field and persists across refreshes.
Inline editing: double-click a task item's label to edit it in place. Press Enter or click away to save; press Escape to cancel. Clear the label completely to remove that item from the list.
Links in task items: when a label contains a markdown link ([title](url)), clicking the link opens the URL without toggling the checkbox. Click elsewhere on the row to toggle.
Static text only: interactivity requires GFM task list syntax (- [ ] / - [x]). Plain dynamic status messages without task items stay read-only. When markdown comes from valueField, the first toggle copies it into the static text field so changes persist.
Numbered lists
Text widgets render markdown with explicit line numbers preserved. Prefix each line with the number you want shown (e.g. 9., 8., 7.). Heym does not renumber lines for you.
Why explicit numbers? Standard markdown ordered lists (1., 2., 3.) become HTML <ol> lists, which only count up from the first number. A list written as 9. / 8. / 7. would display as 9, 10, 11 in a normal markdown renderer. Prefixing each line with the intended number avoids that.
Descending lists (e.g. newest first): build the markdown in a workflow loop and compute the display number per item:
${loop.total - loop.index}. [${item.title}](https://example.com/watch?v=${item.id})Typical pattern:
- Fetch or build an array of items (e.g. HTTP →
body.videos). - Loop over the array; inside the body, use a Variable with
.add()to append one numbered line per iteration. - After the loop's done branch, Set
join("\n")on the accumulated array (or equivalent) into a single markdown string. - Point Chart Output
valueFieldat that string (or set statictext).
Example line expression inside the loop (label processVideos, items with title and id):
$processVideos.total - $processVideos.index + ". [" + $processVideos.item.title + "](https://www.youtube.com/watch?v=" + $processVideos.item.id + ")"Resulting markdown:
9. [Latest video](https://www.youtube.com/watch?v=abc)
8. [Previous video](https://www.youtube.com/watch?v=def)
1. [Oldest video](https://www.youtube.com/watch?v=xyz)For a simple ascending list starting at 1, either prefix each line (1., 2., 3.) or use a loop with $loop.index + 1. Use bullet lists (- item) when you do not need numbers.
Status chips
A table can show one column as colored chips. Set Status column in the node's properties (or statusColumn in the DSL) to a column name. Each value gets one of five tones, and common words get one without configuration:
| Tone | Default words |
|---|---|
success | success, successful, succeeded, done, complete, completed, approved, active, ok, paid, passed, healthy, resolved, won, delivered, published |
attention | warning, attention, review, needs review, at risk, partial, degraded, overdue, unpaid, expiring |
failure | failed, failure, error, errored, rejected, declined, blocked, lost, expired |
waiting | pending, waiting, queued, running, in progress, processing, scheduled, draft, new, open |
neutral | anything else |
Matching ignores case, _ and -, so IN_PROGRESS and in-progress are both waiting. Add Status tones for other values; an entry overrides the default for that value:
{
"type": "chartOutput",
"data": {
"label": "invoices",
"chartType": "table",
"dataPath": "rows",
"columns": ["invoice", "customer", "state"],
"statusColumn": "state",
"statusTones": { "Disputed": "failure" }
}
}The server resolves each value to its tone, so Heym and Heym Work show the same colors.
Link buttons
A table can turn one column into buttons that open a link. Set Link column in the node's properties (or linkColumn in the DSL) to a column whose cells hold a URL, and optionally Link button text (linkLabel). Each cell becomes a button that opens its URL in a new tab. Without button text, the button shows only a link icon:
| Cell value | Opens |
|---|---|
https://jira.example.com/browse/T-1 | Another site, in a new tab |
/dashboards | A page inside Heym, in a new tab |
Only http:// and https:// addresses and paths that start with a single / become buttons. Any other value, such as javascript: or //host, stays plain text, and an empty cell shows nothing.
{
"type": "chartOutput",
"data": {
"label": "tickets",
"chartType": "table",
"dataPath": "rows",
"columns": ["ticket", "owner", "url"],
"linkColumn": "url",
"linkLabel": "Open ticket"
}
}The button works next to a row link: clicking the button opens its link in a new tab, and clicking anywhere else on the row still opens the detail page.
Detail pages: $page.record
A dashboard opened with ?record=<value> runs every widget workflow with that value as $page.record. A table row link opens such a page; see Detail pages. On the dashboard opened without a record, and in the expression dialog, $page.record is null. A node labelled page takes precedence over it.
Use it to fetch the one record the page is about, for example in an HTTP node:
https://crm.example.com/api/customers?id=$page.record$page.record is the clicked row's value itself, such as V-1002, not an object with fields. To find that record in a DataTable, filter its record column by the value: operation find with the filter {"vendor_id": "$page.record"}. A path such as $page.record.id resolves to nothing, so a widget the AI builds or fine-tunes has those fields dropped and reads $page.record instead.
The value comes from a URL that anyone can edit. The dashboard only accepts values in its record format (letters, digits, -, _ and . by default), but treat it as untrusted input all the same: pass it where the node escapes values, such as an HTTP query parameter or a DataTable or ClickHouse filter value, and never build query text by joining strings with it. Widgets still run as the dashboard owner, whoever opens the page.
How data is resolved
- If
dataPathis set, the node follows that dot path into the upstream output. - Otherwise it looks for an array: a top-level list, a
dataarray, or the first list-valued field. - Rows straight from a Data Table node (
{id, data, created_at}) are read with their columns at the top level next toid, solabelField,valueFieldandcolumnsname the table's own columns. - For
table, each row becomes a table row usingcolumns(or the first row's keys). WithstatusColumn, the payload also carries the tone of each value in that column, and withlinkColumnit carries the link column and button text. - For
numericandgauge, the value comes fromvalueFieldon the first row (or the first numeric field). - For
scatter, each row becomes an[xField, yField]point. - For
text, the message is taken fromvalueFieldon the first row when set, otherwise from the statictextconfig (otherwise the first string field of the first row).
Example
{
"type": "chartOutput",
"data": {
"label": "revenueChart",
"chartType": "bar",
"orientation": "vertical",
"dataPath": "data",
"labelField": "month",
"valueField": "revenue",
"title": "Monthly revenue"
}
}Gauge (single value vs. a range):
{
"type": "chartOutput",
"data": {
"label": "cpuGauge",
"chartType": "gauge",
"dataPath": "rows",
"valueField": "value",
"min": 0,
"max": 100,
"unit": "%"
}
}Scatter (X/Y points):
{
"type": "chartOutput",
"data": {
"label": "correlation",
"chartType": "scatter",
"dataPath": "rows",
"xField": "x",
"yField": "y"
}
}Proportion (one bar split by share + percentage legend):
{
"type": "chartOutput",
"data": {
"label": "languages",
"chartType": "proportion",
"dataPath": "rows",
"labelField": "name",
"valueField": "value",
"title": "Most Used Languages"
}
}Area (filled multi-series trend):
{
"type": "chartOutput",
"data": {
"label": "memCpu",
"chartType": "area",
"dataPath": "rows",
"labelField": "time",
"series": [
{ "name": "Memory", "field": "memory" },
{ "name": "CPU", "field": "cpu" }
],
"title": "Memory / CPU"
}
}Bar gauge (per-row gradient gauge with values):
{
"type": "chartOutput",
"data": {
"label": "freeDisk",
"chartType": "barGauge",
"dataPath": "rows",
"labelField": "name",
"valueField": "value",
"unit": "GB",
"title": "Free disk space"
}
}Text (a static markdown message — no upstream rows required):
{
"type": "chartOutput",
"data": {
"label": "statusNote",
"chartType": "text",
"text": "**Last execution** at `19:47` — all checks passed ✅",
"title": "Status"
}
}Text (dynamic — the message string is produced upstream and pulled via valueField):
{
"type": "chartOutput",
"data": {
"label": "lastRun",
"chartType": "text",
"dataPath": "rows",
"valueField": "message"
}
}Example AI prompts
When generating a widget with AI, prompts like these map cleanly to each type:
- Bar — "Show monthly revenue for the last 6 months as a vertical bar chart with example data."
- Line — "Plot daily active users over the last 14 days as a line chart."
- Area — "Show memory and CPU usage over the last 24 hours as an area chart with two series. Use example data."
- Pie — "Break down execution status (success, error, cancelled) as a pie chart."
- Table — "Show the top 10 workflows by execution count as a scrollable table with name and runs columns. Use example data."
- Numeric — "Show today's total workflow executions as a single KPI with unit 'runs'. Use example data."
- Gauge — "Show current CPU usage as a gauge from 0 to 100 percent."
- Scatter — "Plot request latency versus payload size as a scatter chart with example data."
- Proportion — "Show my most used languages as a proportion bar: Kotlin 49.64%, JavaScript 23.73%, TypeScript 11.64%, Java 8.92%, Python 6.06%. Use example data."
- Bar gauge — "Show free disk space per volume (sda1 to sda7) as a bar gauge in GB. Use example data."
- Text — "Add a text widget with a markdown checklist (one checked item, two unchecked), or list the 5 newest tutorial videos in descending order (5. down to 1.) with YouTube links — prefix each line with an explicit number (
5. Title,4. Title, …)."
Related
- Dashboard tab — build and arrange widgets
- Output node — terminal node for regular workflows