Skip to content

Editable dashboard graphics

A drawing is an equipment diagram inside a dashboard: a pumping station, a single-line diagram, a room layout. It is made of shapes, text, images, lines, and equipment symbols. Their properties, such as a tank’s level, a pump’s running state, or a line’s color, can follow live parameter values, and objects can open details or send commands when an operator selects them.

A drawing is stored as a versioned JSON document in the dashboard’s configuration. The browser editor, the assistant, workspace source pushed with hmi push, and the graphics API all edit that same document, so a generated layout can be corrected in the browser without editing dashboard source. The Canvas dashboard template starts with an empty drawing on a 10-unit grid. For the steps to draw a diagram, see Draw an equipment diagram.

DashboardGraphics from @overvis/hmi-workspace-ui displays one drawing. It takes workspaceId, dashboardId, and a UUID drawingId. The optional defaultDocument supplies a drawing before its first save; a saved drawing takes precedence. Several components can share a dashboard when each has its own drawing ID. Changing the workspace, dashboard, or drawing ID opens a new session; a draft or pending action is never carried over to another drawing.

The document is stored at dashboard.configuration.graphics[drawingId]. Each element, connector, and slot has a UUID.

The drawing displays like an interactive image. viewBox: { x, y, width, height } on the document sets its fixed frame and aspect ratio; without it, the frame is derived from the saved geometry. Live readings, including bound size and rotation changes, do not change the frame.

  • On desktops the viewer scales the frame to the available width, without a grid or pan and zoom controls. Optional maxWidth and maxHeight props limit the displayed drawing in pixels while preserving its aspect ratio, for example maxWidth={1000} maxHeight={640} for a compact schematic on a large monitor. The limits apply only in view mode.
  • On phones, tablets, and windows narrower than 1024 px, a drawing that would become too small to read opens at a legible minimum scale instead, starting from its top-left corner. Dragging pans, pinching or the zoom buttons zoom, and the fit button shows the whole drawing.
  • Full screen above the drawing shows it over the whole content area, with the same gestures, until Exit full screen or Escape.

Drawings are light-only. In the dark theme the page around the drawing turns dark, while the drawing, its in-drawing controls, and the dialogs it opens keep the light theme.

Version 2 supports rectangles, rounded rectangles, ellipses, text, polygons, junctions, images, groups, symbols, and lines. A circle is an ellipse with equal dimensions. Equipment and rooms can be composed from these elements without special prefabs. Only this document version is accepted.

  • Element x and y are absolute drawing coordinates, also for grouped elements. width and height are unrotated dimensions, and rotation is in degrees about the element center.
  • Symbol placements may set mirrorX and mirrorY to true; mirroring applies in the symbol’s own frame before rotation.
  • Shape points use normalized coordinates between 0 and 1 inside the element’s rectangle.
  • Groups identify their children through parentId; moving a group moves its descendants and the bends of lines inside it.
  • locked prevents moving and resizing an element. Its ports stay available for connections.
  • A shape’s, image’s, or line’s text is drawn in its stroke color; only a text object uses its fill color for text.

Shapes and lines share one back-to-front order. Their optional integer layer places lower values behind higher values. Objects without a layer follow those with one, in element order, then line order. Copies and inserted symbols appear above existing objects.

Lines are stored in connectors. Each from and to end is either a free { x, y } point or an attachment { elementId, port }: a shape’s left, right, top, or bottom port, a junction’s center, or a symbol’s named port. route is orthogonal (the default), straight, or curve. bends are absolute coordinates, and arrow adds an arrowhead. Optional parentId puts a line in a group, and locked prevents moving it. A line that ends on another line joins it through a junction; lines that merely cross are not connected. An electrical busbar or distribution trunk is drawn with ordinary lines and junctions.

graphicsDocumentType from @overvis/hmi-shared/graphics is the TypeBox shape of a document, and graphicsDocumentSchema validates the shape and the graph rules: unique IDs, existing line ends and slots, valid groups, one binding per property, and valid input ranges. It also bounds geometry and array sizes. graphicsConfigurationIssues(configuration) checks the whole graphics map of a dashboard configuration, including drawing UUIDs, without changing it.

Text that viewers read can be a plain string or one text per locale. This applies to the drawing title; the text of shapes, text objects, images, and lines; the prefix and suffix of text format bindings; command label and toggle onLabel and offLabel; and symbol properties declared as display text, such as the built-in symbols’ label. A locale map has two-letter lowercase keys and non-empty values:

{ "en": "01 / SOURCE", "pl": "01 / ŹRÓDŁO", "de": "01 / QUELLE" }

A plain string reads the same in every language. Command values, slot names, and element names are always plain strings.

Inside a dashboard, a drawing shows the dashboard’s language: the UI language when the dashboard’s supportedLocales lists it, otherwise English. A map without that language falls back to English, then to the first locale in alphabetical order that has text. Spaces and line breaks are kept, so a suffix such as " kW" keeps its leading space.

Localized text is written in code: in the drawing JSON, through hmi push, or by the assistant. The editor shows each localized field in the drawing’s language, and editing it stores the typed plain text in place of all its translations.

Element names label objects for authors in the editor. Viewers’ tooltips and screen-reader labels use the object’s text or the symbol’s label instead.

This document contains one tank rectangle whose fill level follows a named slot. The slot has no parameter yet; with a workspace parameter UUID in its parameterId, the tank shows the live level.

{
"version": 2,
"title": "Tank",
"background": "#ffffff",
"grid": 10,
"viewBox": { "x": 80, "y": 80, "width": 160, "height": 220 },
"elements": [
{
"id": "4c71fa0d-9fc7-4bb3-8103-4adebd6be852",
"kind": "rectangle",
"name": "Tank",
"x": 100,
"y": 100,
"width": 120,
"height": 180,
"rotation": 0,
"parentId": null,
"locked": false,
"appearance": {
"fill": "#38bdf8",
"stroke": "#334155",
"strokeWidth": 2,
"opacity": 1,
"text": "",
"fontSize": 16,
"fillLevel": 1,
"visible": true,
"animated": false
},
"bindings": [
{
"property": "fillLevel",
"kind": "scale",
"source": { "kind": "slot", "slotId": "a732f598-b685-45b5-b788-df3421f4807f" },
"inputMin": 0,
"inputMax": 100,
"outputMin": 0,
"outputMax": 1
}
],
"action": null
}
],
"connectors": [],
"slots": [
{
"id": "a732f598-b685-45b5-b788-df3421f4807f",
"name": "Level",
"parameterId": null
}
]
}

Each visual property has at most one binding, and each binding reads exactly one parameter. A source is either { "kind": "parameter", "parameterId": "<uuid>" } or a named slot. A slot is a named parameter assignment that several bindings share: assigning a parameter to the slot rebinds all of them at once. Logic that combines several readings needs a calculated parameter.

Bindings can format text with precision, prefix, and suffix; scale a numeric input range to width, height, rotation, fill level, or opacity; map thresholds to fill or stroke colors; and apply conditions to visibility or animation. Different properties of one object can use different parameters. The bindable properties follow the object’s artwork:

ObjectBindable properties
Shapes and groupsText, fill and stroke colors, width, height, rotation, fill level, opacity, visibility, animation
TextText, fill color, width, height, rotation, opacity, visibility
ImageLabel text and its stroke color, width, height, rotation, opacity, visibility
LineText, stroke color, opacity, visibility, animation
SymbolWidth, height, rotation, opacity, visibility; the symbol’s own properties use their property bindings

Unavailable data is not zero. A drawing distinguishes unknown, stale, and failed readings:

  • An object keeps its last appearance with a quality indication, and animations pause while readings are not current. Selecting a quality badge shows the parameter diagnostics.
  • An object whose visibility is bound to an unavailable reading is hidden, so missing data never shows a status label or an alarm. A stale reading keeps the visibility it last produced.
  • A symbol resolves quality per property. An unavailable reading shows ”—”, and an unavailable operating state shows Unknown and stops the animation it drives; the outline, label, and other readings stay visible. A stale value keeps its last value in a stale style.
  • A binding whose slot has no parameter counts as unavailable. A symbol property with an explicit value, or left at its default, counts as current. When equipment has no running, fault, or flow signal, bind that property through a slot without a parameter instead of setting false or 0, so the drawing shows it as unknown until a signal is assigned.
  • An equipment alarm is a parameter value, separate from communication quality.

Decorative objects do not respond to clicks. An object’s action can open parameter details, history, a value-edit dialog, or another dashboard. In view mode, selecting a symbol without an action or named controls opens its details, so every piece of equipment answers alike; one without live data says it is not connected to live data.

The details dialog is titled with the symbol’s label or the object’s name. Each bound property is a row under the symbol’s own name for it: states by their names (“Open”, “Running”), conditions as Yes or No, and readings with the binding’s precision and the parameter’s units. Readings show when they were read, and a stale one how long ago. Open history opens a temporary report of the object’s parameters. The value-edit dialog shows the current value with units and the parameter’s allowed range, such as “Allowed: 5 bar to 8 bar”, and refuses a value outside it.

Viewers can inspect; operators can perform permitted parameter writes; editors, admins, and owners can edit drawings. Parameter-write permissions apply when an action is invoked from a drawing, and editing a drawing disables equipment writes.

A dashboard action opens another dashboard, or a page of one. Its optional path, search, and hash address a page, query, or panel within the target dashboard, as in { kind: "dashboard", dashboardId, path: "/stations/<stationId>", hash: "pump-<pumpId>" }. Omitting all three opens the dashboard root. An action within the drawing’s own dashboard that sets no search keeps the current query, so a page opened from an overview returns to the same filters. Paths start with / and stay beneath the dashboard; see dashboard subpages.

Editing targets desktops. Edit drawing in the bar above the drawing opens the editor. The toolbar holds undo and redo, arrange (group, ungroup, bring to front, send to back), rotate and flip, duplicate and delete, and the insert tools: Symbols, the Line tool with its route, shapes, junction, and image. Each tooltip names its shortcut, and Keyboard shortcuts (or ?) lists every shortcut and pointer gesture. Scrolling or dragging the background pans; Ctrl+scroll or a pinch zooms. Panning, zooming, and fitting in the editor choose the frame saved for viewers.

While editing, live binding effects pause, so an object cannot hide or resize itself while it is selected; a bound reading shows ”—” with its units instead of a value. Save returns to the live drawing. Cancel with unsaved changes, or leaving the page, asks first: Discard changes drops the draft and Keep editing returns to it. When Save finds an invalid value, a message names the object and field, and Properties selects the field.

Properties shows the settings of the selected object. For a symbol, they come in the order of configuration: name, symbol type, label, Variant, Key properties, and Readings and states, then the collapsed Placement (position, size, orientation, mirroring, and lock), Appearance, and Advanced bindings, then the click action. Shapes, text, images, junctions, and groups show their name, Appearance, Placement, and click action, with only the settings their artwork uses. With several objects selected, Properties shows the settings they share; a setting whose values differ shows as mixed, and a change applies to every selected object.

A property that can follow a parameter has a link button beside its label. Linking replaces the value with a binding card that shows the parameter with its type, last reading, and unit, or No parameter assigned. Choosing a parameter assigns it to the binding’s slot, so every binding using that slot follows; Shared slot names the slot and offers No shared slot and New slot for this binding. Remove binding restores the value. Unbound key properties are marked Not bound. Parameter assignments lists every slot with its parameter and how many bindings use it. Use default returns a changed symbol property to the symbol’s default.

With the Line tool (L), drag from a port or anywhere on the drawing to draw a line; the tool stays on until Escape. The route buttons beside it choose Right angle, Straight, or Curved. A new line takes the color and width of the line it branches from, or of the most recent similar line. An orthogonal line without bends is routed automatically: it leaves and enters each port in the direction the port faces and goes around equipment when it must. Dragging a segment or adding a bend saves the route as bends. Double-clicking a line adds a bend, and double-clicking a bend removes it. Dropping a line end on another line branches it with a junction.

  • Shift, Control, or Command adds objects to the selection; Shift+drag on the background selects everything inside the box.
  • Duplicate (Ctrl+D, or Command+D on macOS) copies the selection, with the lines between copied objects, one grid step clear of the originals.
  • Objects snap to the drawing grid while they are moved and resized; hold Alt (Option on macOS) to place them freely. Values typed in Properties apply exactly.
  • Rotate counter-clockwise and Rotate clockwise (Shift+R and R), Flip horizontally and Flip vertically (Shift+H and Shift+V) act on each selected object in place. Symbols turn in 90° steps.
  • Delete (Delete or Backspace) removes the selection, with the lines attached to deleted equipment. Deleting a junction keeps its lines.

Symbols opens the library: equipment categories on the left, with Favorites and Recent above them, and search, an industry filter, and the All, Built-in, or Workspace source above the thumbnails. Search matches names, IDs, descriptions, variants, and keywords, including translated synonyms, abbreviations such as VFD, and instrument tags such as PT, LT, or FT. Pipes, tees, and junctions are not symbols: draw them with the Line and Junction tools.

Selecting a tile shows its details: a preview, its purpose, variants, live properties with the essential ones marked, connection points, and state examples. Insert takes the chosen variant; a click on the drawing places it, and R, Shift+R, Shift+H, and Shift+V turn and mirror it first. With Place multiple on, each click adds another copy until Done or Escape. Placing needs no bindings; they are set afterward in Properties. The star on a tile marks a favorite. Favorites and recent symbols are kept in this browser for the signed-in user.

A symbol is one piece of equipment in the drawing. Selecting, moving, resizing, and rotating act on the whole symbol; its artwork has no separately selectable parts. A symbol declares its own properties, such as a pump’s running and fault or a reservoir’s level, and decides how those values change its artwork. It also declares named ports for lines and, optionally, named controls.

A placement uses kind: "symbol", symbolId, properties for explicit values, and propertyBindings for properties that follow parameters. It keeps the common element geometry, appearance, bindings, and action fields; actions holds its named controls. Property names must be unique within each list. Omitted properties use the symbol’s current defaults, and explicit values survive symbol updates. A missing symbol or one that fails to render shows a placeholder.

  • Bind each reading to the property for its quantity, even when another property sits where the number should appear. A flowmeter’s flow is a rate that also points its arrow, so a volume counter goes to total. When a built-in layout cannot place a reading, use a text object bound to the parameter or your own symbol.
  • Symbols show the states and readings they are given; they never calculate them. A hopper does not derive its inventory from its level, and a weighbridge does not derive net weight from gross and tare.
  • Bind each symbol’s essential properties, listed under Key properties, so it shows its operating state. An optional reading that is neither bound nor set is left out rather than shown as a default.
  • Alarms and faults come from the flags bound to them; a symbol does not raise an alarm because a reading crosses a limit.
  • State choices, such as a valve’s open, closed, moving, and unknown, take a states binding that maps the controller’s codes to them.
  • Equipment that runs animates only on fresh feedback, stops on stale or unavailable data, and respects the browser’s reduced-motion preference.
  • Ports have stable names, roles (process, air, electrical, signal, or material), and directions. Mirroring keeps port names, so an inlet stays the inlet. Equipment of one family puts its main ports at a common height, so a row placed on one line connects with straight lines.
  • Every symbol takes an optional label for its equipment tag, such as P-01 or QF1.

Built-in symbol IDs start with hmi.. The library’s details, or graphics.symbols.get, list each symbol’s properties, units, ports, and examples.

ApplicationSymbols and variants
Controlshmi.button
Pumpinghmi.pump (centrifugal), hmi.submersible-pump, hmi.inline-pump
Water storagehmi.tank with open, closed, horizontal, and conical bodies; hmi.well, hmi.reservoir
Valveshmi.shutoff-valve with ball, gate, and butterfly bodies; hmi.solenoid-valve, hmi.modulating-valve, hmi.three-way-valve, hmi.check-valve, hmi.pressure-reducing-valve
Water instrumentshmi.water-filter, hmi.flowmeter, hmi.pressure-instrument, hmi.level-instrument
Electricalhmi.ac-supply, hmi.lamp, hmi.motor, hmi.refrigeration-load, hmi.circuit-breaker
Power and meteringhmi.electricity-meter, hmi.electricity-meter-compact, hmi.three-phase-panel, hmi.disconnector, hmi.contactor, hmi.earthing-switch, hmi.fuse, hmi.transformer, hmi.generator, hmi.automatic-transfer-switch, hmi.variable-frequency-drive, hmi.solar-array, hmi.solar-inverter, hmi.ups, hmi.battery-storage with rack and bank variants, hmi.capacitor-bank
Hydronic heatinghmi.boiler, hmi.burner, hmi.heat-exchanger, hmi.heat-pump, hmi.hot-water-tank, hmi.radiator, hmi.expansion-vessel
Air handlinghmi.fan, hmi.damper, hmi.air-filter, hmi.air-coil, hmi.heat-recovery, hmi.humidity-unit, hmi.chiller, hmi.cooling-tower, hmi.air-terminal, hmi.fan-coil
Thermal instruments and controlshmi.heat-meter, hmi.environment-sensor, hmi.temperature-probe, hmi.temperature-controller, hmi.thermal-zone
Warehouses and cold storagehmi.rack, hmi.pallet with tote and container, hmi.door with sliding, roller shutter, and sectional, hmi.loading-dock, hmi.stacker-crane with vertical lift, hmi.agv with forklift, hmi.storage-zone, hmi.cold-room with freezer, hmi.cooling-unit
Manufacturinghmi.conveyor with belt and roller bodies; hmi.machine, hmi.robot-cell, hmi.inspection-station, hmi.cylinder (pneumatic, hydraulic, electric), hmi.agitator (top-entry, side-entry, submersible), hmi.presence-sensor (photoelectric, proximity), hmi.stack-light
Compressed airhmi.air-compressor with screw and piston bodies; hmi.air-receiver, vertical or horizontal; hmi.air-dryer, refrigerated or desiccant; hmi.line-filter with particulate, coalescing, and activated carbon elements; hmi.pressure-reducing-valve as the regulator
Thermal processinghmi.heater with element and bank (1–6 stages) variants; hmi.furnace with oven and furnace variants; hmi.kiln with rotary and tunnel variants
Fuel and gashmi.gas-detector; hmi.tank with its leak alarm, hmi.flowmeter with its gas-meter body, hmi.pressure-reducing-valve as a gas regulator, and hmi.burner
Food and beveragehmi.process-vessel with jacketed vessel and fermenter variants; hmi.filling-station with bottle and can variants
Quarrieshmi.hopper (hopper, silo); hmi.stockpile (conical, elongated); hmi.feeder (vibrating, apron); hmi.crusher (jaw, cone, impact); hmi.screen (single, double, triple deck); hmi.trommel; hmi.belt-scale; hmi.chute; hmi.diverter-gate; hmi.dust-collector (baghouse, cartridge); hmi.spray-suppression (spray bar, fog cannon); hmi.conveyor and hmi.weighbridge
Waste and recyclinghmi.waste-container (wheelie bin, skip, underground); hmi.compactor (stationary, self-contained); hmi.baler (horizontal, vertical); hmi.shredder (single-shaft, twin-shaft); hmi.separator (magnetic, optical); hmi.collection-vehicle (rear loader, side loader); hmi.weighbridge
Water treatmenthmi.dosing-pump with diaphragm and peristaltic heads; hmi.dosing-unit as a chemical skid or gas chlorinator; hmi.uv-disinfection; hmi.aerator with surface and brush rotors; hmi.diffuser; hmi.clarifier, circular or rectangular; the submersible hmi.agitator for flocculation and hmi.fan as an aeration blower
Irrigation and agriculturehmi.irrigation-zone; hmi.sprinkler with rotor, spray head, and drip emitter variants; hmi.fertigation-unit; hmi.soil-sensor; the well, pumps, water filter, flowmeter, pressure instrument, valves, dosing pump, and tank are also listed under Agriculture

For a placed symbol, Variant offers the symbol’s own variants, such as the ball, gate, or butterfly body of a shutoff valve, and other symbols of the same family with the same connection points and properties, such as a solenoid valve. Switching keeps the object’s position, orientation, bindings, and connections. A change that needs other connection points or behavior, such as a two-way valve becoming a three-way valve, needs a new symbol in its place.

Built-in equipment keeps every port on the drawing grid through rotation, flipping, and resizing when the grid spacing is 1, 2, 5, or 10. Pumps, valves, motors, and meters keep their proportions; tanks, wells, reservoirs, doors, zones, and similar equipment stretch in width and height, and conveyors in width. Placement offers Orientation in 90° steps, Mirrored horizontally, and Mirrored vertically. Labels and readings stay upright when a symbol is turned.

Opening a drawing or editing a property or binding never moves equipment. When a symbol’s position, size, or angle is off the grid, Properties lists each value with the one Align to grid would apply, such as “X 233 → 230”, and Align to grid applies them.

graphics.get returns { document, revision }; revision is null when no drawing is saved. graphics.save takes that value as expectedRevision and changes one drawing, keeping the rest of the dashboard configuration:

const scope = { workspaceId, dashboardId, drawingId };
const current = await client.graphics.get.query(scope);
const result = graphicsDocumentSchema["~standard"].validate(candidate);
if (result.issues) throw new Error("Invalid drawing");
await client.graphics.save.mutate({
...scope,
document: result.value,
expectedRevision: current.revision,
});

A save whose expectedRevision is not the current revision fails with graphics_conflict; the steps to resolve it are in Draw an equipment diagram. Live readings do not change revisions. A graphics save also changes the dashboard’s revision, so a later hmi push of that dashboard from a checkout that did not pull it fails with push_conflict.

A dashboard.update that replaces the configuration must include expectedConfiguration from the read it was based on, and fails with dashboard_configuration_conflict when the configuration has changed since.

graphics.save checks every placement of a built-in symbol against the catalog: the symbol ID, property names and values, the ports lines attach to, rotation in 90° steps, and, on drawings whose grid is 1, 2, 5, or 10, a size and corner that keep the ports on the grid. Placements of workspace symbols are not checked. A property value may be a locale map only when the property is localized.

Objects added or changed since the saved drawing produce errors, and unchanged objects produce warnings, so an existing drawing never blocks a save. Off-grid placement and rotation are errors only in changes the assistant makes; in a person’s edits they are warnings. Errors reject the save with graphics_invalid_document and details.issues; a successful save returns its warnings in issues. graphics.validate takes the same input without expectedRevision and returns { issues } without saving. hmi push runs the same checks and fails with push_validation_failed, listing the issues with their drawingId in details.graphicsIssues.

Each issue has severity (error or warning), code, elementId, and symbolId, plus the fields that locate and repair it:

codeMeaningRepair fields
unknown_symbolNo built-in symbol has this hmi. ID—
unknown_propertyAn override or property binding names no propertyproperty, allowed (property names)
invalid_property_valueA property override breaks the property schemaproperty, schema, allowed (choices)
unknown_portA line attaches to a port the symbol lacksconnectorId, port, allowed (ports)
off_grid_portA port is not on the drawing gridport, expected (aligned port point)
off_grid_positionThe placement’s corner is not on the gridexpected (aligned corner)
invalid_sizeThe size puts ports off the gridexpected (width, height)
invalid_rotationRotation is not a multiple of 90 degreesexpected (nearest rotation)

graphics.symbols.search (optional text, category, industry, and locale) returns a compact list of built-in symbols, and graphics.symbols.get returns full details for chosen IDs: variants, each property’s stored-value schema with its display label and choice labels, units, essential properties, localized: true on display-text properties, ports with their role and direction, dimensions, grid metadata, named controls, and examples. Text is in the requested locale. Workspace symbols are not listed; their definitions are in the workspace source.

A workspace symbol is a .tsx file under src/symbols/, including subdirectories, whose default export is a defineGraphicsSymbol definition. The workspace dashboard build finds these files and includes them in every dashboard, without per-dashboard imports, and they are published with the rest of the workspace source. A component change updates every placement on dashboard reload without rewriting drawings. IDs must be unique, and IDs starting with hmi. are reserved for built-in symbols; a dashboard that exports one fails to load. The steps to write one are in Author a workspace symbol.

import Type, { type Static } from "typebox";
import { defineGraphicsSymbol } from "@overvis/hmi-workspace-ui";
const properties = Type.Object(
{
running: Type.Boolean({ title: "Running", default: false }),
},
{ additionalProperties: false },
);
function Motor({ running }: Static<typeof properties>) {
return (
<svg className="size-full" viewBox="0 0 120 80">
<rect x="10" y="10" width="100" height="60" rx="8" fill="#cbd5e1" />
<circle cx="90" cy="30" r="8" fill={running ? "#22c55e" : "#64748b"} />
</svg>
);
}
export default defineGraphicsSymbol({
id: "my-workspace.motor",
name: () => "Motor",
width: 120,
height: 80,
stretch: false,
ports: [{ id: "shaft", x: 1, y: 0.5 }],
properties: () => properties,
Component: Motor,
});

Components return ordinary React content, including their own <svg> root and viewBox for SVG artwork. Clip, gradient, and mask IDs are shared by every placement on the page unless they are unique per component, such as from useId. Components keep local state through property updates, resizing, and switching between viewing and editing. While editing, the host makes their contents inert and the whole symbol stays selectable. Workspace source is executable dashboard code with the same permissions and trust model as other dashboard components.

Renaming or removing a property or port is a breaking change: existing placements keep their data, and the incompatibilities stay visible for repair. Existing placements of a symbol ID render whatever definition currently has that ID.

The property schema is a closed TypeBox object with a valid default for every property. Supported fields are numbers, integers, booleans, strings, and unions of string literals. title labels the field or a literal choice without changing its stored value. Numeric bounds constrain inputs and clamp bound values. graphicsColor: true on a hex-color string shows a color picker. graphicsText: true on a string marks text that viewers read: a placement may then store one text per locale, and the component receives the text in the drawing’s language. A state union that includes an unknown literal receives it when the state input is unavailable.

Property bindings read one parameter each. Besides the shape binding forms (scale, condition, formatted text, color thresholds), symbols accept:

  • { kind: "value", digits? } passes a numeric reading through unchanged; digits sets the readout precision.
  • { kind: "states", states: [{ value, state }] } maps parameter values (numbers or booleans) to the state IDs of a string-literal union. Unmapped values show as unknown.

A reading can name the string property that holds its unit, as in flow: Type.Number({ default: 0, unitProperty: "unit" }). While flow is bound, unit shows the parameter’s units unless the placement sets unit. Values are never converted.

Port coordinates are normalized between 0 and 1. A line stores the port ID, and the renderer places it according to the instance’s size, mirroring, and rotation. Symbols keep their authored aspect ratio unless stretch is true. On a stretchable symbol, a port can set anchorX or anchorY to start, end, or center to keep its authored distance from that edge or the middle; the default, scale, keeps its fraction. With grid metadata, each anchored distance must be a multiple of 10.

Inside a symbol component, useGraphicsSymbolViewport() returns the instance’s width, height, rotation, mirrorX, mirrorY, the sides that have ports (portSides), the editing state, scale (how much a proportional symbol is enlarged; 1 for a stretchable one), and bindings, the live state of each bound property. Dimensions are in drawing units.

useGraphicsSymbolProperty(name) returns one property’s quality (fresh, stale, or unknown), the binding’s digits, the parameter’s units for a value binding, and assigned: whether the placement binds or sets the property. Unbound properties are fresh; a stale property keeps its value; an unknown property keeps its static or default value, and the component decides how to show it. While editing, a bound numeric property is unknown with its units. formatGraphicsSymbolReading(value, state) formats a reading with that precision, and ”—” when it is unknown. The host pauses CSS animations while editing; motion that the component manages itself must check editing.

function Motor({ running }: Static<typeof properties>) {
const { editing } = useGraphicsSymbolViewport();
const { quality } = useGraphicsSymbolProperty("running");
const color = quality === "unknown" ? "#cbd5e1" : running ? "#22c55e" : "#64748b";
const spinning = running && quality === "fresh" && !editing;
// ...
}

The host rotates and mirrors the whole component. In SVG artwork, GraphicsSymbolText replaces <text> so readings stay upright. GraphicsSymbolCaption, given the artwork’s SVG width and height, draws a label along the bottom edge that moves beside the symbol when it is turned. GraphicsSymbolCaptionBlock draws the tag and status lines below the artwork, unchanged by mirroring. GraphicsSymbolUpright keeps a display face or a stack of readings upright around a point.

Definition names, action labels, and property schemas are factories, such as name: () => t("key"), so their text follows the active language.

builtinGraphicsSymbols from @overvis/hmi-workspace-ui holds the built-in definitions of the installed package version. defineGraphicsSymbol accepts an optional catalog: () => metadata factory with category, industries, description, keywords, and examples, and optionally family, variants, essential, units, and grid:

  • industries uses fixed IDs: water, wastewater, pumping, energy, power, heating, hvac, temperature, manufacturing, warehouses, waste-recycling, quarries, compressed-air, agriculture, food-beverage, fuel-gas, and controls.
  • family groups related symbols, such as valve.
  • variants are named physical variants, each with a stable id, a translated name, and the property values that select it. graphicsSymbolVariants(properties, "body") derives them from a choice property.
  • essential names the properties to bind for the symbol to show its operating state.
  • units gives fixed display units of numeric properties, such as { opening: "%" }.
  • keywords are extra search terms, such as synonyms and abbreviations.
  • examples have a stable id, a translated name, and property values, validated against the property schema when the definition loads.
  • grid: { base: 10 } declares artwork designed for the 10-unit grid. The editor then computes valid sizes and keeps ports on the grid. Optional minScale and maxScale, or minWidth, minHeight, maxWidth, and maxHeight, narrow the sizes.

Each port may declare a role (process, air, electrical, signal, or material) and a direction (in, out, or both). serializeGraphicsSymbolCatalog(definitions, locale) returns the same details as graphics.symbols.get for any definitions, including workspace symbols. @overvis/hmi-shared/graphics exposes the grid rules: graphicsSymbolSizes, snapGraphicsSymbolSize, graphicsSymbolPortPoints, graphicsSymbolPortLayout, and graphicsSymbolGeometryIssues, whose findings carry the expected value that aligning applies.

A definition declares its control names, for example actions: () => ({ activate: "Pump command" }). Inside its component, useGraphicsSymbolAction("activate") returns label, disabled, pending, and activate for an ordinary UI button. A symbol can have several named controls, and clicking one does not also run the whole symbol’s action. A definition that declares a body, the equipment’s own area as fractions of its box, takes whole-symbol clicks only on that body, so neighbors placed against it keep their own.

const action = useGraphicsSymbolAction("activate");
return (
<Button
type="button"
disabled={action.disabled}
aria-busy={action.pending}
onClick={action.activate}
>
{action.label ?? "Pump command"}
</Button>
);

An instance’s actions.activate stores one action. A toggle looks like this:

{
"kind": "toggle",
"source": { "kind": "slot", "slotId": "9edf66da-084f-4c85-81ba-260da6b418b2" },
"status": { "kind": "slot", "slotId": "62927ac1-a1a6-412a-9e35-c77aa3a1bb78" },
"onValue": "1",
"offValue": "0",
"onLabel": "Turn pump on",
"offLabel": "Turn pump off",
"confirm": true
}

Both slots must exist in the drawing. source is the writable command parameter; status is the feedback parameter and may be a different one. A true or nonzero status offers the off action; false or zero offers the on action. The control never takes a write acknowledgement as equipment feedback. A fixed command instead uses kind: "command", source, value, label, and confirm. Parameter details, history, and write actions and dashboard navigation can also be assigned to a named control; write opens an input dialog for a value.

Confirmation is on by default: it shows the intended action with Confirm and Cancel. Without it, the command is sent on activation. A pending command cannot be sent again, failures stay visible, and missing or stale command or status values disable the control. If the configuration or the observed state changes while a confirmation is open, it asks for a new review. Read-only access and editing disable command controls, and the server still enforces write permissions.

The built-in hmi.button declares activate. With a toggle, it shows its label as a fixed name, such as “Pump”, and a switch for its active property; the next action’s label is its tooltip and the confirmation title. With other actions, it shows the action label, and a bound active fills the button.

PNG and JPEG images live in the workspace at graphics/images/<uuid>.png or graphics/images/<uuid>.jpg; each UUID identifies one file. Image elements refer to the image UUID, with ordinary position and size. Images stay single placements; they are not converted to shapes. graphics.listImages returns the available images. Open dashboards reload an image when it is replaced. Dashboard viewers can see the images. Removing a placement leaves the image for other drawings, and a push that deletes an image still used by a dashboard is rejected. Camera orientation is applied to JPEG images, and their metadata is removed.

POST /api/workspaces/<workspaceId>/graphics/images uploads one image as a multipart file, and GET /api/workspaces/<workspaceId>/graphics/images/<assetId> reads one. Both accept the browser sign-in or an API key in x-api-key; uploading needs editor access. Invalid input returns 400, an upload over the limits 413, missing authentication 401, missing permission 403, and an unknown image 404.