Custom ImageList string
A few weeks ago I was asked if it’s possible to combine text, type and state and generate an image visualizing those data sources.
Looking at the options for the TableItemImageList you can either use default image assignments [‘Media/SVG/neg.svg’, ‘Media/SVG/pos.svg’] or bind a variable holding the data in a GraphicCollection type. Since the source data leads to over 1000 images (just using a single letter, number and 4 states), creating and managing them that wasn’t a good option.
Luckily, I found out is that mappView can also render raw SVG data supplied through that binding and that’s how I was able to generate SVG images at runtime based on information of a text and state array. The data string is written into a session variable which is bound to the TableItemImageList.
Here is a working sample!
Node Names are assigned in the INIT of the PLC program. States are 0 by default.
- Changing the State changes the color of the image.
- Changing the Node Name changes the text inside the image,
but - also changes the shape of the image depending on the letter.
For this example the letter V is associated with a round shape and the letter M with a triangle shape.

Just a small snippet from the mappView diagnostic page how wild the string for the ImageList looks.
I don’t think I forgot anything to share if I did please let me know.
Example EventScript
/**
* @eventScriptSetId HD_LS1_contentService1
* @contentId HD_LS1_contentService1
*/
// -----------------------------------------------------------------------
// Per-row image builder
//
// imageList[i] = SVG for Nodes[i] + State[i]
// TABLE_ITEM_IMAGE_ARRAY_NODE[i] = i (written element by element so selectedIndex[i] = i)
//
// stateIndex: 0 = Green 1 = Blue 2 = Yellow 3 = Red 4+ = White
// Shape per node: add entries to SHAPES; anything not listed → square.
//
// PLC program: (add to your PLC program; adjust node names and types as needed)
// Nodes : ARRAY[0..19] OF STRING[3];
// ImageList : ARRAY[0..19] OF INT;
// State : ARRAY[0..19] OF INT;
//
// -----------------------------------------------------------------------
//
// Content widget: (place in your content file, e.g. HD_LS1_contentService1.content; adjust widget IDs and layout as needed)
//
// <Widget xsi:type="widgets.brease.Table" id="Table1" top="20" left="390" width="490" height="630" zIndex="2" useTableStyling="false">
// <Widgets>
// <Widget xsi:type="widgets.brease.TableItem" id="NodeNames" zIndex="0" columnWidth="130" input="true" text="Node Names" />
// <Widget xsi:type="widgets.brease.TableItem" id="NodeState" zIndex="1" columnWidth="190" format="{'metric':{'decimalPlaces':0,'minimumIntegerDigits':1},'imperial':{'decimalPlaces':0,'minimumIntegerDigits':1},'imperial-us':{'decimalPlaces':0,'minimumIntegerDigits':1}}" input="true" text="State" style="CustomTableItem" />
// <Widget xsi:type="widgets.brease.TableItemImageList" id="ImageNodeStateList" zIndex="2" columnWidth="170" imageList="['Media/SVG/neg.svg', 'Media/SVG/pos.svg']" text="Node State" />
// </Widgets>
// </Widget>
//
// -----------------------------------------------------------------------
//
// Session variables:
//
// <Variable name="NodeStateImageList" xsi:type="ANY_STRING" value="" />
// <Variable name="NodeList" xsi:type="ANY_STRING" value="" />
// <Variable name="StateArray" xsi:type="ANY_STRING" value="" />
//
// -----------------------------------------------------------------------
//
// Bindings: (update as needed to match the actual content and session variable names)
//
// <Binding mode="oneWay">
// <Source xsi:type="session" refId="NodeStateImageList" attribute="value" />
// <Target xsi:type="brease" contentRefId="HD_LS1_contentService1" widgetRefId="ImageNodeStateList" attribute="imageList" />
// </Binding>
// <Binding mode="oneWay">
// <Source xsi:type="opcUa" refId="::Program:Nodes" attribute="value" />
// <Target xsi:type="session" refId="NodeList" attribute="value" />
// </Binding>
// <Binding mode="oneWay">
// <Source xsi:type="opcUa" refId="::Program:State" attribute="value" />
// <Target xsi:type="session" refId="StateArray" attribute="value" />
// </Binding>
// <Binding mode="twoWay">
// <Source xsi:type="opcUa" refId="::Program:Nodes" attribute="value" />
// <Target xsi:type="brease" contentRefId="HD_LS1_contentService1" widgetRefId="NodeNames" attribute="stringValue" />
// </Binding>
// <Binding mode="twoWay">
// <Source xsi:type="opcUa" refId="::Program:State" attribute="value" />
// <Target xsi:type="brease" contentRefId="HD_LS1_contentService1" widgetRefId="NodeState" attribute="value" />
// </Binding>
// <Binding mode="twoWay">
// <Source xsi:type="opcUa" refId="::Program:ImageList" attribute="value" />
// <Target xsi:type="brease" contentRefId="HD_LS1_contentService1" widgetRefId="ImageNodeStateList" attribute="selectedIndex" />
// </Binding>
//
// -----------------------------------------------------------------------
var FILLS = ['%234caf50', '%232196f3', '%23ffeb3b', '%23f44336', '%23ffffff']; // colors for each state index; higher values will be clamped to white, %23ffffff; use hex format with URL encoding for # (%23) and alpha if needed, e.g. '%234caf5080' for 50% transparent green
var CONTRAST = ['%23ffffff', '%23ffffff', '%23333333', '%23ffffff', '%23333333']; // text colors for each state index; should contrast with the fill color; higher values will be clamped to the last defined color
var SHAPES = { 'V': 'circle', 'M': 'triangle' }; //special shapes for certain labels; add more as needed, e.g. 'C1': 'triangle'
const MAX_INDEX = 4; // number of defined states - 1; higher values will be clamped to this
const TABLE_ITEM_IMAGE_ARRAY_NODE = '::Program:ImageList'; // OPC UA node where the image array indices are written; must match the binding for selectedIndex in the binding file
// -----------------------------------------------------------------------
function makeSvg(label, stateIndex) {
var fill = FILLS[stateIndex];
var text = CONTRAST[stateIndex];
var shape = SHAPES[label] || 'square';
var raw;
if (shape === 'circle') {
raw = '<svg width="80" height="80" viewBox="0 0 80 80" xmlns="http://www.w3.org/2000/svg">' +
'<circle cx="40" cy="40" r="34" fill="' + fill + '"/>' +
'<text x="40" y="40" text-anchor="middle" dominant-baseline="central" ' +
'font-family="Arial" font-size="28" font-weight="bold" fill="' + text + '">' +
label + '</text></svg>';
} else if (shape === 'triangle') {
raw = '<svg width="80" height="80" viewBox="0 0 80 80" xmlns="http://www.w3.org/2000/svg">' +
'<polygon points="40,0 80,80 0,80" fill="' + fill + '"/>' +
'<text x="40" y="40" text-anchor="middle" dominant-baseline="central" ' +
'font-family="Arial" font-size="28" font-weight="bold" fill="' + text + '">' +
label + '</text></svg>';
} else {
raw = '<svg width="80" height="80" viewBox="0 0 80 80" xmlns="http://www.w3.org/2000/svg">' +
'<rect width="80" height="80" rx="14" ry="14" fill="' + fill + '"/>' +
'<text x="40" y="40" text-anchor="middle" dominant-baseline="central" ' +
'font-family="Arial" font-size="28" font-weight="bold" fill="' + text + '">' +
label + '</text></svg>';
}
return 'data:image/svg+xml,' + encodeSvg(raw);
}
function encodeSvg(svg) {
return svg.replace(/</g, '%3C').replace(/>/g, '%3E').replace(/"/g, '%22');
}
var currentNodes = [];
var currentStates = [];
function buildRowImages() {
if (currentNodes.length === 0) { return; }
var entries = [];
for (var i = 0; i < currentNodes.length; i++) {
var s = (i < currentStates.length) ? parseInt(currentStates[i], 10) : 0;
if (isNaN(s) || s < 0) { s = 0; }
if (s > MAX_INDEX) { s = MAX_INDEX; }
entries.push("'" + makeSvg(currentNodes[i], s) + "'");
opcua(TABLE_ITEM_IMAGE_ARRAY_NODE + '[' + i + ']').setValueNumber(i);
}
variables.NodeStateImageList.setValueString('[' + entries.join(',') + ']');
console.log('buildRowImages:', currentNodes.length, 'rows, states:', currentStates);
}
function parseNodeList(raw) {
if (Array.isArray(raw)) {
return raw.filter(function(s) { return s && s.length > 0; });
}
if (typeof raw === 'string') {
try {
var parsed = JSON.parse(raw);
if (Array.isArray(parsed)) {
return parsed.filter(function(s) { return s && s.length > 0; });
}
} catch (e) { /* fall through */ }
var labels = [];
var parts = raw.split(',');
for (var i = 0; i < parts.length; i++) {
var label = parts[i].replace(/^\s+|\s+$/g, '');
if (label.length > 0) { labels.push(label); }
}
return labels;
}
return [];
}
variables.NodeList.valueChanged(function(e) {
currentNodes = parseNodeList(e.detail.newValue);
buildRowImages();
});
variables.StateArray.valueChanged(function(e) {
var val = e.detail.newValue;
currentStates = Array.isArray(val) ? val.map(Number) : [];
buildRowImages();
});
// Initialise on load
currentNodes = parseNodeList(variables.NodeList.getValue());
var initState = variables.StateArray.getValue();
currentStates = Array.isArray(initState) ? initState.map(Number) : [];
buildRowImages();
HD_LS1_contentService1.zip (2.6 KB)
