Perspective Trapezoid: Bind SVG Path d to Set End Heights

James Nishida9 min read
HMI / SCADAOther ManufacturerTechnical Reference
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

A Perspective component only exposes width and height in its position properties. Scaling those stretches a trapezoid as one block and gives no control over the height of each end. To get that control, draw the trapezoid as an embedded SVG drawing (ia.shapes.svg) and bind the path's d attribute to an expression that rebuilds the vertex string whenever an input changes. Work through the checks below in order. Each check names what to read, what the result means, and where to go next.

Check 1: Drawing Component Type

Before anything else, confirm the shape is an embedded drawing and not a linked image. Select the shape in the Designer and read its component type in the Property Editor.

  • Type is ia.shapes.svg with a props.elements array: the vertices are live properties. Go to Check 2.
  • Type is an image component pointing at a file path: the SVG was imported with Save and link. The browser renders the file as a flat picture, and none of its geometry is exposed to bindings. Delete the component and import the file again (see below).

When you drag and drop an .svg file onto a view in the Designer, a dialog offers Save and link or Embed image. Choose Embed image. The SVG markup is parsed into a drawing object, and every element (path, rect, group) becomes an entry in props.elements with its attributes as editable properties.

If you have no source drawing, you can start from a blank SVG. Save this text as a .svg file and drag it onto the view with Embed image selected:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
     width="50mm" height="50mm" viewBox="0 0 50 50" version="1.1">
</svg>

This produces an empty ia.shapes.svg. You then add the path element in the Property Editor and set the viewBox to suit the geometry. Do not move on until the component shows a props.elements array.

Check 2: Path Element Location in props.elements

Expand props.elements and find the element whose type is path and which holds a d string. Its index sets the binding target.

  • Path sits at the top level, for example elements[0]: the binding target is props.elements[0].d. Go to Check 3.
  • Path sits inside a group (type: group) with its own elements array: this is typical of Inkscape files. Inkscape wraps drawing content in layer groups and often puts a transform="translate(...)" on the group. Either bind the nested path, for example props.elements[0].elements[0].d, or flatten the drawing before import. If you keep the group, its transform offsets every coordinate you write into d. Account for that offset, or delete the transform and re-anchor the path.
  • Element type is rect or polygon: a rect cannot become a trapezoid. A polygon uses points instead of d. Replace the element with a path, or bind points using the same technique.

For a simple trapezoid, hand-building the path in the blank drawing is usually faster than cleaning up an Inkscape export. The working reference element is a stroked outline with a transparent fill:

"props": {
  "elements": [
    {
      "type": "path",
      "fill": { "paint": "#00000000" },
      "stroke": { "paint": "#000000" }
    }
  ],
  "preserveAspectRatio": "xMinYMin",
  "viewBox": "0 0 200 50"
}

Check 3: viewBox Coordinates and Vertex Map

Read props.viewBox. Every number in d is expressed in viewBox units, not in pixels. The component's position width/height only scales the whole viewBox onto the screen. In the reference build the component is 600 x 150 px and the viewBox is 0 0 200 50, so the drawing space is 200 units wide and 50 units tall.

SVG y grows downward. A larger y value moves a vertex toward the bottom. This is the most common reason a bound shape moves the opposite way from what you expect.

The reference path string is M10 40 L190 40 L180 {value} L20 {value} Z. Its vertices map as follows:

Command Vertex Role
M10 40 (10, 40) Bottom-left corner, start point
L190 40 (190, 40) Bottom-right corner; baseline fixed at y = 40
L180 {value} (180, value) Top-right corner, inset 10 units
L20 {value} (20, value) Top-left corner, inset 10 units
Z back to (10, 40) Closes the outline
  • All computed coordinates stay inside 0–200 horizontally and 0–50 vertically: go to Check 4.
  • Any vertex can leave that range: the shape clips at the viewBox edge. Either rescale the expression or enlarge the viewBox.

preserveAspectRatio: xMinYMin keeps the drawing proportional and anchored to the top-left of the component. Resizing the component therefore scales the trapezoid without distorting it. With none, the drawing stretches to fill the box and the slopes change with the component size.

Check 4: Binding Chain on props.elements[n].d

Open the binding on the d property. The reference uses an expression binding followed by an expression transform, which keeps the numeric scaling separate from the string assembly.

Expression binding (numeric result):

max({../Slider.props.value},5)/100 * 40

Expression transform (builds the path string from {value}):

concat("M10 40 L190 40 L180 ", {value}, " L20 ", {value}, "Z")

Here ../Slider is a sibling ia.input.slider component. A label bound to ../Slider.props.value displays the raw input for commissioning. The max(...,5) clamp sets a floor on the input. The multiplier maps a 0–100 slider onto the 0–40 band above the baseline. Confirm the slider's min and max properties are 0 and 100 before reusing this scaling.

Slider value Computed y (top edge) Trapezoid height (40 − y)
0 2 (clamped from 5) 38
50 20 20
100 40 0 (top edge lies on the baseline)

The input drives the top-edge y coordinate, not the height. Raising the slider shrinks the shape, and at 100 it collapses to a line. If the input should mean height, invert it inside the binding, for example 40 - max({../Slider.props.value},5)/100 * 40.

Watch the spacing inside the concat literals. Each number must be separated from the next token by a space or command letter. "L180 " followed by the value is valid. A missing space, as in L1802, produces the wrong vertex without raising an error.

Symptoms Versus Causes

Symptom Cause Correction
No elements property after import File imported with Save and link Re-import with Embed image
Binding runs but shape does not change Binding sits on the wrong element index or on a group, not the path Re-read the index in props.elements; target the nested path's d
Shape shrinks as the input rises Input mapped to the y coordinate; SVG y increases downward Use baseline - scaled_input
Shape flattens to a line at full scale Scaled y reaches the baseline (40) Cap the scale below the baseline, or invert the mapping
Part of the shape cut off Computed coordinates exceed the viewBox Rescale the multiplier or widen the viewBox
Slopes change when the component is resized preserveAspectRatio set to none Use xMinYMin
Whole shape offset from its expected position Inkscape group transform applied on top of the bound coordinates Remove the transform or compensate in the expression
Vertices land in the wrong place Missing spaces in concat literals merge numbers Check the rendered d string in the Property Editor

Independent End Heights: Build Procedure

The reference drives both top corners with one value, which gives a symmetric trapezoid. To set the height of each end separately, give each end its own input and its own y term. The procedure below builds a shape with vertical ends on a fixed baseline. Its left and right heights are set independently, which suits hoppers, tapered conveyors, and wedge indicators.

  1. Create the drawing from the blank SVG with Embed image. Set props.viewBox to 0 0 200 50 and props.preserveAspectRatio to xMinYMin. Confirm: the component shows an empty props.elements array.
  2. Add one element to props.elements with type = path, fill.paint = #00000000, and stroke.paint = #000000. Confirm: the element appears as elements[0].
  3. Add two ia.input.slider components and name them SliderLeft and SliderRight. Leave the range at 0–100. Confirm: a label bound to each slider's props.value tracks the handle.
  4. Add an expression binding on props.elements[0].d. Each end height maps 0–100 onto 0–38 units, so the top vertex never goes above y = 2:
    concat(
      "M10 40 L190 40 L190 ",
      40 - min(max({../SliderRight.props.value},0),100) * 0.38,
      " L10 ",
      40 - min(max({../SliderLeft.props.value},0),100) * 0.38,
      " Z"
    )
    Confirm: the binding preview shows a string that starts with M10 40 L190 40 L190 and has no error overlay.
  5. For sloped sides instead of vertical ends, change the top x values to insets, for example L180 and L20, as in the reference. The heights stay independent.
  6. For a live process value, replace each slider reference with a tag or view property reference. Keep the min/max clamp so an out-of-range value cannot push a vertex outside the viewBox.
  7. Optionally wrap each numeric term in numberFormat(..., "0.0"). This keeps the d string short and readable when you inspect it during commissioning.

Verification

  1. Put the view in preview mode. Move SliderLeft alone. Confirm: only the left end rises and falls, while the right end and the baseline stay fixed.
  2. Move SliderRight alone. Confirm: only the right end moves.
  3. Drive both sliders to 0. Confirm: the shape collapses to a line on y = 40. This is the zero-height state; if it collapses at 100 instead, the mapping is inverted.
  4. Drive both sliders to 100. Confirm: both ends reach y = 2 with no clipping at the top of the drawing.
  5. Resize the ia.shapes.svg component in the Designer. Confirm: the trapezoid scales proportionally and stays anchored top-left, with slopes unchanged.
  6. Select the component and read the evaluated props.elements[0].d string at an intermediate setting. Confirm: it contains exactly four coordinate pairs separated by spaces and ends with Z.

FAQ

Can I change the trapezoid shape by binding the component width and height?

No. Position width/height only scales the whole viewBox, so the slopes and end heights keep their ratios. Bind props.elements[n].d on an embedded ia.shapes.svg to move individual vertices.

Does an Inkscape SVG work as a bindable Perspective drawing?

Yes, if you drop it on the view and choose Embed image rather than Save and link. Inkscape usually nests the path inside a layer group with a translate transform, so bind the nested element's d and account for the offset, or hand-build the path in a blank embedded SVG.

Can I drive each end of the trapezoid from a different tag?

Yes. Build the d string with concat() and use a separate y term for each end, such as 40 - clampedValue * 0.38, each referencing its own tag or property. Clamp each input with min/max so every vertex stays inside the viewBox.

Why does my SVG trapezoid get shorter when the value goes up?

SVG y coordinates increase downward. An expression like value/100 * 40 moves the top edge down toward the y = 40 baseline. Invert it to 40 - value/100 * 40 so a higher value produces a taller shape.

Back to blog