...
...
| Canvas | Width × Height | When |
|---|---|---|
| Standard HD | 1366 × 728 | Default, works everywhere |
| Wide | 1600 × 900 | Modern control-room monitors |
| Full HD | 1920 × 1080 | Dedicated operator displays |
| 4K scaled | 3840 × 2160 | Rare — prefer 1920×1080 with StretchFill |
text margin = 20 gap = 15 titleBar = 60 // top strip for display title + status bottomPanel =
Canvas inherits from ISA-101 process graphics, not from SaaS design. Three rules do most of the work:
Use theme brushes, not hex. Call list_elements('ThemeColors') for the brushes available in the active theme. Background is always theme:PageBackground; zone fills are theme:PanelBackground; text uses the theme's foreground brushes.
| Canvas | Width × Height | When |
|---|---|---|
| Standard HD | 1366 × 728 | Default, works everywhere |
| Wide | 1600 × 900 | Modern control-room monitors |
| Full HD | 1920 × 1080 | Dedicated operator displays |
| 4K scaled | 3840 × 2160 | Rare — prefer 1920×1080 with StretchFill |
text margin = 20 gap = 15 titleBar = 60 // top strip for display title + status bottomPanel = 140 // for trend/alarm/summary zoneHeight = Height - titleBar - bottomPanel - margin zoneWidth = (Width - 2*margin - (N-1)*gap) / N zone[i].Left = margin + i * (zoneWidth + gap) zone[i].Top = titleBar zone[i].Width = zoneWidth zone[i].Height = zoneHeight
...
json { "Type": "ShapeGroup", "Left": 300, "Top": 200, "Width": 180, "Height": 220, "Stretch": "None", "Children": [ { "Type": "Ellipse", "Left": 40, "Top": 0, "Width": 100, "Height": 20 }, { "Type": "Rectangle", "Left": 40, "Top": 10, "Width": 100, "Height": 180 }, { "Type": "Ellipse", "Left": 40, "Top": 180, "Width": 100, "Height": 20 } ], "FillTheme": "ElementBlue", "Dynamics": [ { "Type": "FillColorDynamic", "LinkedValue": "@Tag.Reactor/Alarm", "ChangeColorItems": [ { "Type": "ChangeColorItem", "ChangeLimit": 0, "LimitColor": "#FF1E3A8A" }, { "Type": "ChangeColorItem", "ChangeLimit": 01, "LimitColor": "#FF1E3A8A#FFEF4444" } ] } , { "Type": "ChangeColorItem", "ChangeLimit": 1, "LimitColor": "#FFEF4444" } ] } ] }
Any inline SVG string becomes a ShapeGroup with native WPF shapes:
json { "Type": "SvgGroup", "Left": 224, "Top": 240, "Width": 180, "Height": 220, "SvgContent": "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 180 220'><ellipse cx='90' cy='14' rx='60' ry='10' fill='#95a5a6'/><rect x='30' y='14' width='120' height='180' fill='#3498db'/><ellipse cx='90' cy='194' rx='60' ry='10' fill='#95a5a6'/></svg>", "Name": "ReactorVessel" }
Supported SVG elements: rect, circle, ellipse, line, path, polyline, polygon, g.
Trade-off: SvgContent hex colors are opaque to the theme system. Fine for process-meaning colors (heat red, water blue), wrong for UI chrome. If you need theme-reactive composed equipment, use ShapeGroup directly.
Use Group when children need INDEPENDENT dynamics:
json { "Type": "Group", "Left": 100, "Top": 100, "Width": 400, "Height": 280, "Children": [ { "Type": "Rectangle", "Left": 0, "Top": 0, "Width": 400, "Height": 280, "FillTheme": "PanelBackground" }, { "Type": "TextBlock", "Left": 16, "Top": 16, "Width": 360, "Height": 24, "LinkedValue": "Reactor Control" }, { "Type": "Button", "Left": 16, "Top": 220, "Width": 120, "Height": 40, "LabelLink": "Start", "Dynamics": [{ "Type": "ActionDynamic", "MouseLeftButtonDown": { "Type": "DynamicActionInfo", "ActionType": "SetValue", "ObjectLink": "@Tag.Reactor/Running", "ObjectValueLink": 1 }}] } ] }
The archetype: a cylindrical vessel with a heating jacket that changes color when the heater is running.
] }
Any inline SVG string becomes a ShapeGroup with native WPF shapes. Supported SVG elements: rect, circle, ellipse, line, path, polyline, polygon, g. Trade-off: SvgContent hex colors are opaque to the theme system. Fine for process-meaning colors, wrong for UI chrome. If you need theme-reactive composed equipment, use ShapeGroup directly.
Use Group when children need INDEPENDENT dynamics (a panel with its own chart, buttons, and labels that moves as a unit but where each child has its own behavior).
Before composing equipment from primitives, look for an existing symbol. The search order matters — using the wrong source creates inconsistency across a solution's displays.
list_elements('Solution') shows what the plant has already standardized on. If a Solution/Tanks/Tank_Vertical exists, use it — other displays in this solution already do. Mixing Solution symbols with Library symbols or Wizards for the same equipment type produces a display that visually disagrees with the rest of the plant's screens.list_elements('Library/HPG') for High Performance Graphics — flat, theme-aware symbols that respond to the standard state convention (0=Off/Stopped/Closed, 1=On/Running/Open, 2=Disabled/Out-of-Service) via the HPOffFill/HPOnFill/HPDisableFill theme brushes. This is the right default for operator control screens.list_elements('Library/HMI') — traditional detailed symbols with shading and depth. Right for training material, technical diagrams, and mechanical documentation. Wrong for live operator screens (too much visual noise competes with state communication).Wizard/BLOWER, Wizard/MOTOR, Wizard/PUMP, Wizard/TANK, Wizard/VALVE. See Recipe 2.Rule: when writing to an existing solution, run list_elements('Solution') before reaching for a Library symbol or a Wizard. Match what's already there.
The archetype: a cylindrical vessel with a heating jacket that changes color when the heater is running.
json { "Type": "Ellipse", "Left": 300, "Top": 180, "Width": 96, "Height": 24, "FillTheme": "ElementGray" }, { "Type": "Rectangle", "Left": 328, "Top": 150, "Width": 40, "Height": 36, "FillTheme": "ElementGray" }, { "Type": "Cylinder", "Left": 276, "Top": 200, "Width": 144, "Height": 300, "FillTheme": "ElementBluejson { "Type": "Ellipse", "Left": 300, "Top": 180, "Width": 96, "Height": 24, "FillTheme": "ElementGray" }, { "Type": "Rectangle", "Left": 328256, "Top": 150280, "Width": 4022, "Height": 36180, "FillTheme": "OffFill", "Dynamics": [ { "Type": "ElementGray" }, "FillColorDynamic", "LinkedValue": "@Tag.Reactor/Heater/Running", "ChangeColorItems": [ { "Type": "CylinderChangeColorItem", "LeftChangeLimit": 2760, "TopLimitColor": 200"#FF7F1D1D" }, { "WidthType": 144"ChangeColorItem", "HeightChangeLimit": 3001, "FillThemeLimitColor": "ElementBlue#FFEF4444" } ] } ] }, { "Type": "Rectangle", "Left": 256418, "Top": 280, "Width": 22, "Height": 180, "FillTheme": "OffFill", "Dynamics": [ { "Type": "FillColorDynamic", "LinkedValue": "@Tag.Reactor/Heater/Running", "ChangeColorItems": [ /* same FillColorDynamic */ ] }
Elements top to bottom: drive cap (Ellipse), motor housing (Rectangle), vessel body (Cylinder with ElementBlue), left jacket rail (Rectangle with heater-gated FillColorDynamic), right jacket rail (same).
For an impeller shaft: add a thin vertical Rectangle, apply RotateDynamic gated by Running:
json { "Type": "ChangeColorItemRectangle", "ChangeLimitLeft": 0344, "LimitColorTop": "#FF7F1D1D" }230, { "TypeWidth": "ChangeColorItem"8, "ChangeLimitHeight": 1240, "LimitColorFillTheme": "#FFEF4444DefaultStroke" } ] } ] }, { "TypeDynamics": "Rectangle", "Left[ { "Type": 418, "TopRotateDynamic": 280, "WidthLinkedValue": 22, "Height30": 180, "FillThemeIsRpm": "OffFill"true, "DynamicsOnOffLink": [ /* same FillColorDynamic */ ] }
Elements top to bottom: drive cap (Ellipse), motor housing (Rectangle), vessel body (Cylinder with ElementBlue), left jacket rail (Rectangle with heater-gated FillColorDynamic), right jacket rail (same).
For an impeller shaft: add a thin vertical Rectangle, apply RotateDynamic gated by Running:
json { "Type": "Rectangle", "Left": 344, "Top": 230, "Width": 8, "Height": 240, "FillTheme": "DefaultStroke", "Dynamics": [ { "Type": "RotateDynamic", "LinkedValue": "30", "IsRpm": true, "OnOffLink": "@Tag.Reactor/Agitator/Running" } ] }
LinkedValue: "30" and IsRpm: true means "rotate at 30 rpm." OnOffLink gates the rotation — the shaft only spins when Running=1.
"@Tag.Reactor/Agitator/Running" } ] }
LinkedValue: "30" and IsRpm: true means "rotate at 30 rpm." OnOffLink gates the rotation — the shaft only spins when Running=1.
Five Wizard symbols — Wizard/BLOWER, Wizard/MOTOR, Wizard/PUMP, Wizard/TANK, Wizard/VALVE — are pre-wired with common state dynamics. When one of these five fits the equipment, it's almost always the fastest path.
The AI's job when dropping a Wizard:
Left, Top, Width, Height)SymbolLabels (State, RPM, whatever the symbol exposes) to tagsVisual customization — orientation, style variant, pipe-connection direction, foot details — is handled by the user in Designer via the Wizard configuration button (double-click the symbol in the editor). Do not attempt to reproduce these variants via raw geometry or extra elements; the Wizard holds them.
<ac:structured-macro ac:name="code"> <ac:parameter ac:name="language">json</ac:parameter> ac:plain-text-bodyjson { "Type": "Symbol", "SymbolName": "Wizard/PUMP", "Left": 500, "Top": 300, "Width": 80, "Height": 80, "SymbolLabels": [ { "Type": "SymbolLabel", "Key": "State", "LabelName": "State", "LabelValue": "@Tag.Pump1/Running", "FieldType": "Expression" }, { "Type": "SymbolLabel", "Key": "RPM", "LabelName": "RPM", "LabelValue": "@Tag.Pump1/Speed", "FieldType": ": "Expression" } ] }Expression" } ] }</ac:plain-text-body> </ac:structured-macro>
The symbol itself knows how to change its fill based on State — no extra dynamics FillColorDynamic needed. To add a click-to-open-detail action, add put the dynamic ActionDynamic directly on the Symbol:
json "Dynamics": [ { "Type": "ActionDynamic", "MouseLeftButtonDown": { "Type": "DynamicActionInfo", "ActionType": "OpenDisplay", "ObjectLink": "PumpDetail" } } ]
For equipment outside the five Wizard types, follow the symbol source order in §4.5 — Solution first, then HPG Library, then HMI Library, then primitives.
...
json { /* Recipe 1 elements for vessel body with jacket */ }, <p>{ "Type": "Path", "Left": 305, "Top": 220, "Width": 130, "Height": 240, "Data": "M0,0 Q65,20 130,0 M0,40 Q65,60 130,40 M0,80 Q65,100 130,80 M0,120 Q65,140 130,120 M0,160 Q65,180 130,160 M0,200 Q65,220 130,200", "StrokeTheme": "StateRed", "StrokeThickness": 2, "Fill": "#00000000", "FillTheme": "", "Dynamics": [ { "Type": "VisibilityDynamic", "LinkedValue": "@Tag.Reactor/Heater/Running" } ] }]]></ac:plain-text-body> </ac:structured-macro></p> <p>The coils are only visible when the heater is active — a subtle but unmistakable visual cue for operators.</p> <h3>Recipe 5 — Reference displays to study</h3> <p>For a worked canvas at scale, read displays from the <code>Industrial_Ontology_Enhanced_Demo</code> solution (solution_id <code>J8P7LY</code>). The display <code>ProcessAreaOverview</code> is a good starting anchor — it demonstrates zone decomposition, inline readouts attached to equipment, pipe/gridline composition, and the use of Library symbols alongside primitives. Read with <code>get_objects('DisplaysList', names=['ProcessAreaOverview'], detail='full')</code> to see how the pieces compose at scale.</p> <h2>Section 6 — Dynamics reference</h2> <p>Call <code>list_dynamics()</code> for the full list. Here are the The 14 types grouped by what they do:</p> <h3>Action category</h3> <table> <tbody> <tr><th>Dynamic</th><th>What it does</th><th>Use for</th></tr> <tr><td><code>ActionDynamic</code></td><td>Run an action on mouse event</td><td>Navigation, tag writes, scripts, toggles</td></tr> <tr><td><code>CodeBehindDynamic</code></td><td>Run code on display lifecycle events</td><td>Init, cleanup, periodic updates</td></tr> <tr><td><code>HyperlinkDynamic</code></td><td>Open external URL on click</td><td>Documentation links, external dashboards</td></tr> </tbody> </table> <h3>Color category</h3> <table> <tbody> <tr><th>Dynamic</th><th>What it does</th><th>Use for</th></tr> <tr><td><code>FillColorDynamic</code></td><td>Change element Fill based on thresholds</td><td>Status indicators, process meaning</td></tr> <tr><td><code>LineColorDynamic</code></td><td>Change element Stroke based on thresholds</td><td>Pipe color-by-flow, boundary-alarm borders</td></tr> <tr><td><code>TextColorDynamic</code></td><td>Change text Foreground based on thresholds</td><td>Alert text, highlighted values</td></tr> </tbody> </table> <h3>Animation category (Canvas-only)</h3> <table> <tbody> <tr><th>Dynamic</th><th>What it does</th><th>Use for</th></tr> <tr><td><code>RotateDynamic</code></td><td>Rotate element by angle or rpm</td><td>Pointers, impeller shafts, motors, compass needles</td></tr> <tr><td><code>ScaleDynamic</code></td><td>Scale element by tag value</td><td>Growth/shrink animations, level indicators</td></tr> <tr><td><code>MoveDragDynamic</code></td><td>Move element by tag value OR let user drag</td><td>Sliding valves, operator drag-to-set</td></tr> <tr><td><code>SkewDynamic</code></td><td>Skew element</td><td>Perspective effects, rare</td></tr> </tbody> </table> <h3>Data category (Canvas-only)</h3> <table> <tbody> <tr><th>Dynamic</th><th>What it does</th><th>Use for</th></tr> <tr><td><code>BargraphDynamic</code></td><td>Fill a Rectangle proportionally based on value</td><td>Tank level fill, progress fills, power meter strips</td></tr> </tbody> </table> <h3>Visibility, Security, Feedback</h3> <table> <tbody> <tr><th>Dynamic</th><th>What it does</th><th>Use for</th></tr> <tr><td><code>VisibilityDynamic</code></td><td>Show/hide based on expression</td><td>Conditional panels, state-dependent overlays, coils-only-when-heating</td></tr> <tr><td><code>SecurityDynamic</code></td><td>Hide/disable based on user permission</td><td>Admin-only controls, role-based HMI</td></tr> <tr><td><code>ShineDynamic</code></td><td>Mouse-over highlight / glow effect</td><td>Hover feedback on clickable shapes</td></tr> </tbody> </table> <h3>Copy-paste patterns</h3> <p><strong>status_indicator</strong> (shape that changes color on running/stopped):</p> <ac:structured-macro ac:name="code"> <ac:parameter ac:name="language">json</ac:parameter> <ac:plain-text-body><![CDATA[{ "Type": "Ellipse", "Left": 100, "Top": 100, "Width": 24, "Height": 24, "Dynamics": [ { "Type": "FillColorDynamic", "LinkedValue": "@Tag.Equipment/Running", "ChangeColorItems": [ { "Type": "ChangeColorItem", "ChangeLimit": 0, "LimitColor": "#FF808080" }, { "Type": "ChangeColorItem", "ChangeLimit": 1, "LimitColor": "#FF34D399" } ] } ] }
...
Background set explicitly to theme:PageBackground (not the #FFFAFAFA default)FillTheme: "PanelBackground" — FIRST in ElementsCenterValueDynamics array (never as direct properties)ChangeColorItems is a flat array (no ColorChangeList wrapper)list_elements() before useget_state after write shows errorList empty...
text Rectangle // backgrounds, bars, pipes (when rectangular) Ellipse // caps, LEDs, pump bodies Polygon // arrows, funnels, custom closed shapes Gridline // pipes (always prefer over Polyline) Path // curves, coils, complex shapes Cylinder // vessels, tanks (first-class shortcut) TextBlock // all text (labels, values, composite) Symbol // WizardSolution / Library / Solution symbols Wizard ShapeGroup // composed equipment with unified dynamics RadialGauge // temperature, pressure, any circular gauge
...