Tutorial: Build a Dashboard
Build a line-overview screen: live values, state-coloured equipment, a trend, an alarm banner and drill-down to detail.
You will need: tags with live data (Connect a device) and history enabled on at least one analogue tag. Time: ~30 minutes.
Step 1 — Create the view
In the Designer's Project Browser, click Create View (the + on Views), or right-click the Views folder and choose New View.
| Field | Value |
|---|---|
| Title | Line 1 Overview |
| Slug | line1-overview |
| Type | Flex Layout (Responsive) |
Flex because a dashboard should survive being opened on a display size you did not anticipate. Use coordinate for a mimic where position carries meaning — that is a different screen.
Step 2 — Add the alarm banner
Drag an AlarmBell into the top-right corner. It needs no binding — it reads the alarm system directly.
| Property | Value |
|---|---|
countMode | unacked |
colorByPriority | on |
pulseOnNew | on |
Counting unacknowledged rather than all active means the badge reflects work outstanding, not just conditions present.
Step 3 — Add a KPI row
Drop a FlexContainer across the top and put three Text widgets inside it.
Bind each to a tag. On the speed display, add a Number Format transform (#,##0) — or better,
set a format string on the tag so every screen inherits it.
Step 4 — Add a gauge with state colour
Drag a Gauge and bind:
| Property | Binding |
|---|---|
value | tag Site1/Line1/Filler/Motor1/Speed |
min / max | 0 / 3000 |
gaugeColor | tag …/Speed → Map transform |
In the Map transform, set Match input by to Numeric range and Produce output as to Color:
[,2400) → #16a34a normal
[2400,2800] → #f59e0b high
(2800,] → #dc2626 too high
fallback → #6b7280
(For fixed bands drawn on the dial itself, the Gauge's colorRanges property does the same job
without a binding.)
The colour is now doing the reading for the operator. That is the point of a dashboard.
Step 5 — Add a running indicator
Drag an Icon and bind color to …/Running with a Map transform (Exact value →
Color):
| Input | Output type | Output |
|---|---|---|
true | color | #16a34a |
false | color | #6b7280 |
| fallback | color | #6b7280 |
A grey icon meaning "stopped" and a grey icon meaning "we have no idea" is the failure operators remember. Add a distinct bad-quality appearance, or a small quality indicator beside it.
Step 6 — Add a trend
Drag a Historian chart below the KPI row.
| Property | Value |
|---|---|
series | Two entries, tagPath …/Speed and …/Current |
timeWindowSec | 3600 (last hour) |
aggregate | avg |
The chart reads the historian directly. If it is empty, history is not enabled on those tags. → Historian configuration
Use minmax instead of avg when spikes are the reason you are looking — averaging a 200 ms current
spike into a one-minute bucket erases it. For a single tag in a compact tile, the HMI Trend
widget (HmiTrendChart) draws one tag's trailing window with its alarm limits.
Step 7 — Add drill-down
Drop a Button labelled "Motor detail". Right-click it, choose Edit Events, select Click and add a Navigate action:
Navigate → Switch to view: "motor-detail"
params: { tagPath: "Site1/Line1/Filler/Motor1" }
Then build motor-detail once, parameterised by tagPath. Every motor on the site uses that
one view.
→ Event actions · Embedded views
Step 8 — Add an operator control
Drag a Slider and bind value to …/SpeedSetpoint. Bound to a writable tag, an input widget
reads and writes it.
Two things to get right:
- Set a write level on the tag. A setpoint that changes production should require at least
Operate, and oftenSupervise. - Confirm consequential writes. Tick Require confirmation before writing on the Set tag action and give it a message, rather than writing on every drag. Confirmation is an option on the action, not something a script asks for.
Slider.change → Set tag Site1/Line1/Filler/Motor1/SpeedSetpoint = {{value}}
with confirmation: "Set the filler speed?"
If you need to do more than write — validate a range, then write, then say so — use a Run script action and raise the toast yourself:
const rpm = Number(context.self.props.value);
if (!Number.isFinite(rpm) || rpm > 3000) {
context.ui.notify({ message: "Speed must be at most 3000 RPM", severity: "error" });
return;
}
await system.tag.write("Site1/Line1/Filler/Motor1/SpeedSetpoint", rpm);
context.ui.notify({ message: "Setpoint applied", severity: "success" });
Step 9 — Publish and check as an operator
Save, then open http://localhost:8182/{project}.
You are seeing what an operator sees. Check it on the actual panel hardware — a screen that is comfortable on a laptop can be unreadable on a 10" panel in a bright room.
Fields, buttons and menus draw at the project's Control size (Project settings → Theme), SM by default — sized for a desktop. For a touch panel set it to MD or larger. → Control size
Step 10 — Review
- Abnormal state is obvious without reading numbers
- Bad quality looks different from a normal value
- Colour is not the only signal for anything safety-relevant
- Text is legible at the real viewing distance
- Touch targets work with gloves (Control size MD or larger on a touch panel)
- Detail views are parameterised, not duplicated
- Writes are confirmed and write-level gated
- The screen opens quickly on the target hardware
Next
| Next | Why |
|---|---|
| Alarm on a tag | Make the banner mean something |
| LiveView authentication | Require an operator login |
| Components | The full widget reference |