How Cards Read and Show
Change a card's column headers, number and date formats, hidden columns, widths, total row, data labels, legend, axes, chart variant and highlights, and a dashboard's title, through Semaphor MCP.
An agent can finish a card the way a person does in the editor: name its columns, format its numbers and dates, hide a column, add a total row, put the values on the bars, move the legend, title an axis, stack the bars, or highlight the rows that need attention. It reads each setting in the outline and changes it with the same words, through semaphor_propose_change and semaphor_apply_change like every other change (see Making Changes).
{
"dashboardId": "d_8f2k",
"changes": [
{
"op": "card.update",
"cardId": "c_suppliers",
"columns": {
"Net Purchase Value": { "label": "Purchase value", "format": { "type": "currency", "currency": "USD", "maximumFractionDigits": 0 } },
"Net Tons": { "hidden": true }
}
}
]
}The proposal reads it back in plain words, with a value in the new format:
Renames the column "Net Purchase Value" to "Purchase value" in "Purchases by supplier", formats Purchase value like $12,480, and hides Net Tons for everyone (it still sorts and filters).
Read the Settings
semaphor_get_dashboard_details with fields: ["outline"] gives each editable card its settings, in the shape a change takes:
{
"cardId": "c_suppliers",
"title": "Purchases by supplier",
"type": "table",
"editable": true,
"columns": {
"Supplier": { "field": "supplier_id", "role": "dimension", "label": "Supplier", "inherits": ["label", "format"] },
"Net Purchase Value": { "field": "net_purchase_value", "role": "measure", "label": "Net Purchase Value", "width": 180,
"format": { "type": "currency", "currency": "USD" }, "inherits": ["label"] }
},
"totals": { "row": true, "measures": { "Net Purchase Value": "overall" } },
"highlights": [
{ "ruleId": "h_1", "column": "Net Purchase Value", "apply": "row", "enabled": true,
"when": { "operator": "<", "value": 1000 }, "color": "red" }
],
"editorOnly": ["Column display (Supplier)"]
}| Field | What it holds |
|---|---|
columns | Each column by the name its header shows: label, format (the card's own), hidden, width, and inherits, what it takes from the model. A column that inherits its format shows the model's format, which comes with the query's results. field and role say which column it is. |
orientation, stack, donut | A chart's variant (see Chart Variants) |
columnOrder, totals, dataLabels, legend, axes | The card's own settings, when it has them. columnOrder shows only when the author has set an order. |
highlights | Each highlight rule, with the ruleId that removes it |
editorOnly | Settings the card has that a change doesn't state, in the editor's words. A change keeps them exactly as they are. |
Send a card's settings back as you read them and nothing changes, so an agent can copy them, change only what the user asked, and send them back.
Columns
columns on card.add or card.update names each column by its name in the outline. Send only the settings that change.
| Setting | Values |
|---|---|
label | The header. null returns it to the model's label. |
format | A number, currency, percent, duration or date format, in the shape the outline shows. Send a whole format with its type, or only the fields to change, which are merged onto the format the column shows. null returns the column to the model's format. |
hidden | true hides the column for everyone who views the card; it still sorts and filters. false shows it again. Tables and detail tables. |
width | Pixels, as the table draws it, from 50 to 500. null returns it to automatic. Tables and detail tables. |
{ "Revenue": { "format": { "type": "number", "compact": true } } }
{ "Margin": { "format": { "type": "percent", "percentValueMode": "fraction", "maximumFractionDigits": 1 } } }
{ "Order month": { "format": { "type": "temporal_bucket", "presentation": { "mode": "preset", "preset": "long" } } } }
{ "Ordered at": { "format": { "type": "date", "preset": "short" } } }
{ "Revenue": { "format": { "maximumFractionDigits": 0 } } }- A date grouped by month, quarter or year takes a
temporal_bucketformat: a preset (short,long,numeric,iso, and the week, quarter and fiscal presets at their grains) or, for a month or year, apatternno finer than the grain. A date grouped by hour, minute or second takes apatternsuch asHH:mm, orh:mm afor a 12-hour time, on every database; a pattern the grain doesn't take is refused with the patterns it does. - A date shown as it is, in a detail table, a SQL table or a Matrix level, takes a
dateformat with apresetor apattern. A detail table shows only the time with a pattern such asHH:mm, or a 12-hour time with{ "pattern": "h:mm a", "dialect": "date_fns" }on PostgreSQL, Snowflake and ClickHouse (see Dates and times in a table). The same field may appear twice in a detail table; the outline names the copies apart. - Each format is checked against the card that draws it, so the sentence's sample is what the viewer sees. A column whose card shows its values as they come (a chart's categories, text on a table) takes no format, and the refusal says where one applies.
- A SQL table's columns are named by the query's result columns. The proposal runs the card once to read them.
Card Settings
| Setting | Values | Cards |
|---|---|---|
columnOrder | Column names, as the outline shows them, in the order wanted; the columns not named keep their places (see Column order) | Tables and detail tables, SQL ones included |
totals | { "row": true } adds a total row; false removes it. measures sets each measure's total (see Totals) | Tables built from a dataset |
dataLabels | { "show": true } shows the values on the chart; add format for a number format of their own, or null to follow the measure | Bar, line, area and pie charts |
legend | { "show": true, "position": "bottom" }; position is top, bottom, left or right | Bar, line, area and pie charts |
axes | { "x": {...}, "y": {...} }, each { "title", "min", "max" }; null removes a title or returns a bound to automatic | Bar, line and area charts |
columnFilters | false stops viewers filtering the table's columns from their headers; true lets them again. On unless turned off (see Table column filters) | Tables, detail tables and Matrix cards built from a dataset |
tableSearch | false removes the table's search box; true brings it back. On unless turned off (see Table column filters) | Tables and detail tables built from a dataset |
trend | true draws a line of the KPI's value over the period it counts, on the date it counts on (no Trend By needed); false leaves the space under the value to the summary's drivers when the summary shows on the card, else a target's progress | KPIs built from a dataset |
trendGrain | The trend's grain: "automatic" (fits the period the KPI counts: Last 7 days by day, Last 90 days by week, Year to date by month), or "hour", "day", "week", "month", "quarter", "year". A KPI with a Trend By draws by the Trend By's grain | KPIs built from a dataset, without a Trend By |
{
"op": "card.update",
"cardId": "c_rev_region",
"dataLabels": { "show": true },
"legend": { "show": true, "position": "bottom" },
"axes": { "y": { "title": "Revenue", "min": 0 } }
}x and y are the axes as they appear on screen: on a horizontal bar chart, y lists the categories and x shows the values. A range goes on the axis that shows values.
Column order
columnOrder sets the order every viewer starts with, as an author's drag of a column header in the editor does, and saves the same thing. Any column can go anywhere, a measure between two group columns included. Name only the columns to move: the rest keep their places after the column they follow.
{
"op": "card.update",
"cardId": "c_sales",
"columnOrder": ["Yard", "Material", "Net", "Notes"]
}- The readback names the new order: "Puts the columns of "New sales" in order: Yard, Material, Net, Notes." Hidden columns are left out of it.
- The query doesn't change: only the columns' order on screen and in downloads.
- A table pivoted by columns keeps its row columns before the pivoted ones: order the row columns among themselves and the measures among themselves. A measure before a row column is refused with
value_invalidand the rule in words. - An unknown name is refused with
column_not_foundand the card's columns inallowed; a name given twice is refused withvalue_invalid. - SQL tables name their result columns; the proposal runs the card once to read them.
- Viewers who arranged the table themselves keep their own order; Reset columns returns them to this one.
Totals
While a table's total row shows, the outline lists what each measure totals to, by the column's name: "overall" (the measure worked out once over the whole table, the default), "sumOfRows" (the rows added together), or "none" (a table calculation with no single total, such as a moving average). A change sets a measure to its default, as the outline shows it, or to "sumOfRows"; row can be left out to change only the measures.
{
"op": "card.update",
"cardId": "c_purchases",
"totals": { "row": true, "measures": { "Net Tons": "sumOfRows" } }
}- The readback says what each total is: "Adds a total row to "Purchases by supplier" and totals Net Tons as the sum of all rows." Sum of rows on a distinct count or an average adds "(adds each row's value; a customer in two rows counts twice)".
- A total the measure can't have is refused with
value_invalidand what it takes:"overall"on a moving average reads "can't have an overall total: A moving average has no single total.", withallowed["none", "sumOfRows"]. - A column that isn't a measure is refused; a total is set for each measure. A measure can also be named by its result alias.
- A total row change needs the user's yes, since the total runs its own query.
- SQL tables take no
totals; their total row adds up the rows as before.
A KPI's trend runs on the date the KPI already counts on: its Trend By, else the dataset's primary date, else the field of the dashboard's date filter. "trend": true needs no Trend By. A trendGrain on a KPI with a Trend By is refused with value_invalid and the fix: change the Trend By's timeGrain, or remove the Trend By to set a grain here.
Chart Variants
A card's type is one of the eight card types, and its variant is a property of it:
| Variant | type and properties |
|---|---|
| Horizontal bar | bar with "orientation": "horizontal" |
| Stacked bar | bar with "stack": "stacked" (horizontal too, with both) |
| Stacked line, stacked area | line or area with "stack": "stacked" |
| Doughnut | pie with "donut": true |
{
"op": "card.update",
"cardId": "c_rev_region",
"stack": "stacked",
"query": { "dimensions": ["region", "category"] }
}A variant change keeps the card's legend, data labels, axes and colors. A bar chart turned horizontal keeps each axis's title and range with its role, so the value axis keeps "Revenue" and its range. Changing to another chart family, such as a bar chart to a table, works like the editor's type picker.
Highlights
highlight.add colors the cells or rows of a table, detail table or KPI where a condition holds, shades a column on a color scale, or adds data bars:
{ "op": "highlight.add", "cardId": "c_orders", "column": "Margin", "when": { "operator": "<", "value": 0.1 }, "color": "red", "apply": "row" }{ "op": "highlight.add", "cardId": "c_orders", "column": "Margin", "scale": { "low": "red", "high": "green" } }
{ "op": "highlight.add", "cardId": "c_orders", "column": "Revenue", "bars": { "color": "blue" } }| Field | Values |
|---|---|
column | A column's name in the outline |
when and color | { "operator": "=", "!=", ">", ">=", "<" or "<=", "value" }, { "operator": "between", "values": [low, high] }, { "operator": "contains", "value" }, { "operator": "is_null" }, { "top": n } or { "bottom": n }, with the color for the cell or row where it holds |
scale | { "low", "high", "mid" }: colors from the column's lowest value to its highest |
bars | { "color" }: a bar in each cell, in proportion to its value |
apply | cell (the default) or row, for a condition |
Colors are red, amber, green, blue and gray, the editor's own, or a hex color such as #1d4ed8. A KPI's highlight colors its value when a condition holds.
The proposal's change entry gives the new rule's ruleId, and so does the outline afterwards. highlight.remove takes it and keeps the card's other rules:
{ "op": "highlight.remove", "cardId": "c_orders", "ruleId": "h_1" }The Dashboard's Title and Description
{ "op": "dashboard.update", "title": "Weekly sales review" }description sets the dashboard's description; null removes it. The outline shows both.
Confirm
A proposal's confirm says whether the user needs to see it before it's applied:
not_needed: the proposal only changes how cards read and show (labels, formats, hidden columns, widths, data labels, legend, axes, variants and highlights) or the dashboard's title and description. An agent can apply it as soon as it's proposed.needed: anything else, such as a query, a type, a card added or removed, a filter, a total row or a KPI's trend (each runs its own query). It's alsoneededwhen a change would remove something viewers saved, such as a column their bookmarks use; theviewer_settings_affectedwarning says what. Show the summary to the user first.
The apply checks again, and refuses with confirm_changed if the proposal now needs the user's confirmation.
When a Setting Is Refused
A refused setting changes nothing in the proposal and says what to do. Each refusal names the card and column as the user sees them, lists what the card or setting takes in allowed, and often carries a suggestedChange you can send as written.
| Code | When |
|---|---|
setting_not_for_card | The card doesn't take that setting, such as axes on a pie chart |
column_not_found, column_ambiguous | No column, or two, have that name; allowed lists the card's column names |
columns_unavailable | A SQL table's query couldn't be run to read its columns |
format_invalid | A format that isn't valid, or that the column's card doesn't draw |
date_finer_than_grain | A date format finer than the column's grouping; allowed lists that grain's presets |
color_unknown | A color that isn't one of the five names or a hex color |
highlight_not_found | No highlight on the card has that ruleId |
value_invalid | A width outside the column's range, an axis that ends before it starts, an empty title, or two labels for one column |
Making Changes
Preview and apply changes to dashboards and semantic models through Semaphor MCP.
What a Number Means
Compare a measure with an earlier period or a target, say which way is good, choose how the change shows, and run a table calculation such as a running total or a share of the total, through Semaphor MCP.