Skip to content
Monomer

Write a dashboard chart source

A CHART widget stores TypeScript or JavaScript module code in module.source. The code turns query results into an ECharts option. A single big-number display uses the same CHART widget; it does not need a separate widget type.

With MCP, describe the advertised Dashboard create or draft edit operation using monomer_describe, then read this guide with monomer_docs at /en/dashboards/chart-source/index.md. You can also find it through /en/dashboards/llms.txt. Follow Create a dashboard or Review and apply changes for the complete definition and draft workflow.

Set type to "CHART", declare one or more top-level query names in queryNames, and supply module.inputs with both parameters and variables arrays. Use empty arrays when the source needs neither. Each input name must exist in the dashboard’s top-level declaration. Put the complete source below in module.source, encoded as one JSON string, including export default.

queryNames selects the results available to the widget. The chart does not execute queries or fetch results itself. The host reads every referenced query’s successful result before replacing the widget’s ready bundle. During replacement, it keeps the previous results and their variable values together. Query, variable, locale, or viewport changes can trigger another render; resizing does not run a warehouse query.

Export exactly one defineChart call as the default export. Its object accepts only these two methods:

MethodContract
render(frame)Required and synchronous. Read the current frame and return a complete, non-null, plain ECharts option object. An empty object is valid. Do not return a Promise, array, or primitive.
setup({ chart })Optional and synchronous. Runs once per chart instance. Register event handlers here rather than during render. The chart handle provides limited ECharts capabilities, not a DOM element or the raw ECharts instance.

The examples use only render. defineChart is provided by the runtime: do not import it or ECharts. The host creates, resizes, and disposes the chart and validates the returned option.

FieldWhat the source receives
queriesAn immutable map keyed by the widget’s queryNames. Each result has immutable columns and rows.
parametersRead-only values for names in module.inputs.parameters, fixed when the session opens.
variablesRead-only values for names in module.inputs.variables, from the displayed ready bundle. Read variables.name directly; there is no .value or .set on a value.
localeThe host’s BCP-47 locale, used for display formatting.
viewportThe chart content box’s width and height in CSS pixels.

A row is a flat object keyed by result column name, not a positional array. Semantic result keys include "metric.revenue" and "metric_time.week"; SQL results use the exact declared column names. Check the query’s actual columns before writing the source. Display labels do not change those keys.

Each cell is string | boolean | null. Numeric values, including INTEGER, DECIMAL, and FLOAT, arrive as strings. Convert explicitly with Number only when you need chart coordinates, accepting its precision limit. Keep the original decimal string for displayed amounts. Empty results have an empty rows array while retaining their column descriptors.

Each column descriptor contains name, logicalType, nullable, and numberFormat. The host translates a Semantic metric’s stored display settings into numberFormat options for Intl.NumberFormat; SQL columns and columns without a format have null. Applying these options is the source’s choice.

Time values arrive as strings, already in the Workspace’s Reporting time zone:

logicalTypeWhere it comes fromExample
DATESemantic time columns at day, week, month, quarter, or year grain, and SQL dates"2026-08-03"; a week is its Monday
DATETIMESemantic time columns at hour or finer grain: the Reporting time zone’s wall-clock time, without an offset. Also SQL timestamps without a time zone."2026-08-03T14:00:00.000000"
TIMESTAMPSQL timestamps with a time zone, as UTC instants"2026-08-03T05:00:00.000000Z"

Never parse DATE or DATETIME values with new Date(). It reads a date-only string as midnight UTC and a date and time without an offset in the viewer’s own time zone, so the same row can land on different days or hours for different viewers. Use the strings directly as category labels; they sort chronologically as text. To format one for display, read its parts, build the moment with Date.UTC, and format it with Intl.DateTimeFormat and timeZone: "UTC", which shows exactly the date and time in the result:

const [year, month, day] = value.slice(0, 10).split("-").map(Number);
const label = new Intl.DateTimeFormat(locale, { timeZone: "UTC", dateStyle: "medium" })
.format(Date.UTC(year, month - 1, day));

When a click handler passes a clicked day, week, or month to a dashboard Variable, declare that Variable as DATE and filter the query with DATE. A TIMESTAMP Variable rejects a "YYYY-MM-DD" value. An hour-level DATETIME value has no offset, while a TIMESTAMP Variable needs one; adding Z is correct only when the Reporting time zone is UTC.

This source assumes a verified query named weeklyRevenue, ordered by week ascending, with non-null DATE column "metric_time.week" and nullable DECIMAL column "metric.revenue". Select that query with queryNames: ["weeklyRevenue"] and use module.inputs: { parameters: [], variables: [] }. Replace the names with your actual query and result keys.

export default defineChart({
render({ queries }) {
const rows = queries.weeklyRevenue.rows;
return {
tooltip: { trigger: "axis" },
xAxis: {
type: "category",
data: rows.map(row => row["metric_time.week"])
},
yAxis: { type: "value" },
series: [{
type: "line",
name: "Revenue",
data: rows.map(row => row["metric.revenue"] === null
? null : Number(row["metric.revenue"]))
}]
};
}
});

The ordered week values, DATE strings such as "2026-08-03", label the horizontal axis without being parsed. A null revenue creates a gap, and an empty result produces an empty series. The plotted coordinates and default tooltip use JavaScript numbers; use the original decimal strings in a custom formatter if the displayed amount must retain full precision.

This source assumes a verified aggregate query named revenueTotal that returns at most one row with nullable DECIMAL column "metric.revenue". Select it with queryNames: ["revenueTotal"] and use module.inputs: { parameters: [], variables: [] }. It displays the first row’s total using ECharts graphic text, with no axes or series.

export default defineChart({
render({ queries, locale }) {
const result = queries.revenueTotal;
const value = result.rows[0]?.["metric.revenue"];
const column = result.columns.find(column => column.name === "metric.revenue");
const format = new Intl.NumberFormat(locale,
column?.numberFormat ?? { maximumFractionDigits: 2 });
return {
graphic: [{
type: "text",
left: "center",
top: "middle",
silent: true,
style: {
text: value == null ? "No data" : format.format(value),
fontSize: 48,
align: "center",
verticalAlign: "middle"
}
}]
};
}
});

Intl.NumberFormat accepts the decimal string directly, preserving its precision before display rounding. A zero is formatted as zero; an empty result or null total displays No data. This source displays the query’s aggregate; it does not calculate a new metric.

  • Source is limited to 256 KiB in UTF-8 bytes. Imports, require, dynamic imports, and named exports are unavailable.
  • Execution is isolated in a Worker with immutable inputs. There is no window, document, DOM access, network access, storage, timer, ambient current time, or random source. Use explicit dates and Intl with an explicit locale; Intl.DateTimeFormat also requires an explicit timeZone and date value. Numeric toLocaleString does not honor formatting options in this runtime; use Intl.NumberFormat.
  • Setup, render, event handlers, and option callbacks must be synchronous. The host enforces execution deadlines and validates option values, depth, size, callbacks, and effects. Return plain objects, arrays, strings, finite numbers, booleans, null, and supported synchronous source callbacks. Do not return native instances such as Date, Map, or BigInt values in the option.
  • Tooltip supports canvas auto or richText mode. HTML tooltips, DOM append/CSS options, navigation links, and toolbox dataView or saveAsImage are rejected. Images must be inline PNG, JPEG, or WebP resources; remote images are unavailable.
  • The complete frame has a 16 MiB / 250,000-node budget; the complete option has an 8 MiB / 250,000-node budget. Each callback’s combined inputs and its output have separate 512 KiB / 20,000-node budgets. Nodes include objects, arrays, keys, and values, not just rows. Multiple queries share the frame budget; the runtime does not truncate or downsample results to fit it.

Place CHART widgets in a section with height.mode: "SCALED", and declare any required COLUMN child weights as described in Create a dashboard. Saving or applying a definition does not prove the queries or source rendered successfully. Refresh the affected draft queries and inspect their results and chart preview before applying.