Skip to main content

Build a Dashboard

Reference Materials: All About Dashboards.

In 2020 Inductive Automation released the perspective dashboard component. With this addition to the component palette, users now have the ability to create their own analysis and operational screens from the browser without having to touch the designer.

KanoaMES extends this component to provide a self-serve user dashboard and marquee implementation.

dashboards

The Home page now points to the Dashboard Viewer. Users that have GatewayAdmin rights can setup the default dashboard that will be displayed.

dashboards

Users can can select a different dashboard and set that to be their default dashboard.

Dashboards can be grouped together in Marquees that can rotate through each dashboard on Andon boards and Big TV's around your plant.


The Dashboard Editor​

Dashboards can be created by selecting 'Dashboard Editor' in the navigation menu. A dashboard consists of a number of widgets that are positioned within a dashboard.

dashboards

To create a new dashboard, click the + button. To edit an existing dashboard, select the dashboard.

Dashboard Info Panel​

  • Name - Give a name to your dashboard. The dashboardId will be shown on the right.
  • Description - A description of what this dashboard does.
  • Folder - Dashboards can be grouped by folder. To edit folders, select the dashboards
  • Private - A dashboard can be made private to the user who created it by enabling the 'private' checkbox. Public dashboards can be viewed by everyone.
  • Enabled - A dashboard must be enabled to show up in the dashboard selector.
  • Debug - Show Header - When enabled, each widget will display the widgetId, widgetName and dashboardWidgetId in the header
  • Debug - Show Dimensions - When enabled and in view mode dashboards, an icon displaying the identified breakpoint with a tooltip will be displayed next to the icon detailing which breakpoint is being shown.

Breakpoints​

Breakpoints allow you to change up what is displayed based on screen size and orientation. We have created three breakpoints, 'Desktop', 'Tablet' and 'Mobile'. If only one breakpoint is configured for a dashboard, that is what will be shown regardless of screen size and aspect ratio.

dashboards

Creating Widgets​

From within the Ignition Designer, any view can effectively be turned into a widget. How well it responds to a mobile responsive environment is entirely in your hands and will be dependent upon how you have constructed your view. We recommend using flex containers almost exclusively as that is the standard container for web design.

We'll use the assetDowntimeBarChart widget as an example. Widgets views can be created under any folder. We are creating ours in the kanoa\core\dbp\widgets folder in the kanoaAPP project. Yours will be somewhere in your project.

dashboards

The only requirements for a widget is that the view parameters start off with a dictionary called 'value'. This is a common practice of ours here at Kanoa. For one it enables a view to be used as a sub-view in a table, it also makes it simple to copy and paste view parameters between views in the designer when testing and debugging.

To make a widget respond to the asset selector and date selector widgets, take a look at how we implemented our view custom property filters binding.

dashboards

The function kanoa.dbp.widget.getWidgetFilters() checks the view parameters and the custom session properties to build up a set of filters that can then be applied to the widget.

To turn a view into a widget or edit an existing widget, click the dashboards button.

dashboards

Widget Info Panel​

  • Name - Give your widget a name. The widgetId will be shown on the right.
  • Description - A description of what this widget does.
  • Category - Select or create a new category for this widget. When adding a widget to a dashboard, this widget will be displayed within this category.
  • View Path - Copy the view path of your widget from within the Ignition designer.
  • View Params - Copy the view params from this view. As mentioned above, the only requirements for a widget is that the view parameters start off with a dictionary called 'value'.

View parameters may be edited directly by editing the object string in the text area, or they may be edited by the table interface below.

Parameter variable types may be specified in the Type dropdown. A default value may also be configured by editing the Default column cell.

Parameters may be configured to have preset options, by editing the Options column cell. The Options cell value should be a comma separated list of string options.

Adding Widgets to a Dashboard​

In the right hand pane, select and drag an area to add a widget. We provide some widgets out of the box for you. Custom widgets can also be added.

We have provided a couple of selector widgets, Asset Selector and Date Range Selector. These are special widgets in that there are no user settable parameters and are only used to effect the behavior of other widgets. With these widgets, a user can select a specific asset and date range to be applied to all other widgets on that dashboard, achieved through the use of custom session properties.


Editing Dashboard Widgets​

Once a widget is added to a dashboard, its specific view parameters may be configured. Parameter values may be typed out in the text-field, or selected from a dropdown if options for that parameter were configured.

dashboards

In the previous example, options were configured for 'downtimeBy', so now it has a dropdown selector to pick options from.

dashboards

From Dashboards →

Create a Dashboard
  1. Navigate to Dashboards → Dashboard Editor and create a new dashboard.

  2. Configure it:

    FieldValue
    NameOEE and Top Downtimes
    DescriptionUnderstand what's driving your OEE
    FolderDowntime Dashboards (create the folder with the folder icon if it doesn't exist yet)
    IconOptional
    ColorOptional
    EnabledEnabled
    PrivateOptional — use private if only your user should see it; leave public/shared if the dashboard should be available to others
  3. Save the dashboard. The dashboard is now registered in Kanoa, and the design canvas becomes available for adding widgets.

Add Widgets

Selector widgets define the context the analytics widgets use. This dashboard needs an asset selector and a date range selector.

  1. Select the full top row of the dashboard canvas. In the add widget popup, go to the Selectors section, choose Asset Selector, and add it to the top row. This lets the user choose which asset the dashboard analyzes.
  2. Select the row underneath the asset selector. In the Selectors section, choose Date Range Selector and add it. This date range drives the analytics widgets below it.
Add Analytics Widgets
  1. Select a section of the canvas for the first chart. In the add widget popup, find the Asset OEE Bar Chart and add it. It shows OEE performance for the selected asset and time range.
  2. Select another section of the canvas. Find the Top Downtimes widget and add it. It shows the top downtime reasons for the selected asset and date range.
Adjust widget parameters

Each widget can receive view parameters that adjust how it behaves, which lets you reuse the same widget in different contexts. You type parameter values in, or pick them from a dropdown when the widget defines options for that parameter.

  1. Click the OEE widget and look at its View Params.
  2. Change the OEE metric parameter to focus the widget on a specific metric: enter availability for the oeeMetric parameter.

The widget then responds according to how it was designed.

For this dashboard, the analytics widgets should use the asset chosen in the Asset Selector, so leave the asset path as null. Conceptually:

{ "assetPath": null }

This means "use the current dashboard-selected asset." Alternatively, you can hardcode an asset path so a widget always shows one specific asset:

{ "assetPath": "CoreForge\\Philadelphia\\Machining\\Machine 1" }

Keep the asset path dynamic so the selector drives the dashboard.

Test in View Mode
  1. Switch from edit mode to view mode and confirm that:
    • The dashboard layout looks correct
    • The asset selector and date range selector work
    • The OEE widget and the Top Downtimes widget respond to the selected filters

This validates that the dashboard works as an analytics tool, not just a static layout.

The dashboard editor supports different layouts for different screen sizes. The top of the editor shows the current layout mode, such as Desktop, Tablet, or Mobile, and each breakpoint can have its own widget arrangement. If only one breakpoint is configured, that layout is shown regardless of screen size.

On desktop, place the two analytics widgets side by side. On mobile, side by side is usually too cramped, so stack them vertically: switch to the mobile layout and place the widgets again in the mobile arrangement. This takes some rework, but it gives you much better control over the user experience.

Check your work

Copy this script into the Ignition Designer Script Console (Tools → Script Console) and run it. It checks the Downtime Dashboards folder and the OEE and Top Downtimes dashboard (enabled, with a description), that the Desktop layout has the two selectors and the two analytics widgets side by side, that the OEE widget's oeeMetric is availability and both analytics widgets leave assetPath as null, and that the Mobile layout stacks the two analytics widgets. Each check prints PASS or FAIL with what to fix. It doesn't change anything.

Show the check script
checkLesson-dashboardBuild.py
# Kanoa Ops Training - check "Build a Dashboard" (Analytics, lesson 1)
#
# Run in the Ignition Designer Script Console (Tools > Script Console), in the
# project that inherits KANOA_SCRIPTS. It only reads; it changes nothing.
# Every check prints PASS or FAIL with what to fix, then a summary.

import re

DASHBOARD = 'OEE and Top Downtimes'
FOLDER = 'Downtime Dashboards'
SELECTORS = ['Asset Selector', 'Date Range Selector']
OEE = 'Asset OEE Bar Chart'
DOWNTIMES = 'Top Downtimes'

results = []

def check(ok, what, fix=None):
results.append(bool(ok))
if ok:
print 'PASS %s' % what
else:
print 'FAIL %s' % what
if fix:
print ' -> %s' % fix

def note(what):
print 'NOTE %s' % what

def field(row, *names):
for n in names:
if n in row and row[n] is not None:
return row[n]
return None

def text(v):
return '' if v is None else unicode(v).strip()

def key(v):
return ''.join(ch for ch in text(v).lower() if ch.isalnum())

def truthy(v):
return str(v).lower() in ('true', '1', 'yes')

def rows(data):
# Kanoa returns a dataset, a list of dictionaries, or None
if data is None:
return []
if hasattr(data, 'getRowCount'):
cols = list(data.getColumnNames())
return [dict((c, data.getValueAt(i, c)) for c in cols) for i in range(data.getRowCount())]
return [dict(r) for r in data]

def params(widget):
# viewParams is JSON, but can hold Python-style None/True from older widgets
raw = text(field(widget, 'viewParams'))
if not raw:
return {}
for candidate in (raw, raw.replace('None', 'null').replace('True', 'true').replace('False', 'false')):
try:
return system.util.jsonDecode(candidate) or {}
except:
pass
return {}

def span(widget, start, end):
a, b = field(widget, start), field(widget, end)
return (int(a), int(b)) if a is not None and b is not None else None

def overlaps(s1, s2):
# grid ranges are start-inclusive, end-exclusive (CSS grid lines)
return s1 is not None and s2 is not None and s1[0] < s2[1] and s2[0] < s1[1]

print '=' * 70
print 'Kanoa Ops Training - Build a Dashboard check'
print '=' * 70

# 1. Folder and dashboard
print '\n-- Dashboard'
folders = rows(system.kanoa.dashboard.getFolders({}))
folder = [f for f in folders if text(field(f, 'folderName')) == FOLDER]
near = [f for f in folders if key(field(f, 'folderName')) == key(FOLDER) and f not in folder]
if folder:
check(True, 'Folder %s exists' % FOLDER)
elif near:
check(False, 'Folder %s exists' % FOLDER, "Found '%s'. Rename it to %s." % (text(field(near[0], 'folderName')), FOLDER))
folder = near
else:
check(False, 'Folder %s exists' % FOLDER,
'In Dashboards > Dashboard Editor, click the folder icon and add a folder named %s.' % FOLDER)
folderIds = set(field(f, 'folderId') for f in folder)

dashboards = rows(system.kanoa.dashboard.getDashboards({}))
board = [d for d in dashboards if text(field(d, 'dashboardName')) == DASHBOARD]
if not board:
near = [d for d in dashboards if key(field(d, 'dashboardName')) == key(DASHBOARD)] or \
[d for d in dashboards if 'oee' in key(field(d, 'dashboardName')) and 'downtime' in key(field(d, 'dashboardName'))]
if near:
check(False, 'Dashboard %s exists' % DASHBOARD,
"Found '%s'. Rename it to %s." % (text(field(near[0], 'dashboardName')), DASHBOARD))
board = near
else:
check(False, 'Dashboard %s exists' % DASHBOARD,
'In Dashboards > Dashboard Editor, create a dashboard named %s and save it.' % DASHBOARD)
else:
check(True, 'Dashboard %s exists' % DASHBOARD)

if board:
d = board[0]
dashboardId = field(d, 'dashboardId')
if folderIds:
check(field(d, 'folderId') in folderIds, '%s is in the %s folder' % (DASHBOARD, FOLDER),
'Edit the dashboard and set its Folder to %s.' % FOLDER)
enabled = field(d, 'enabled')
if enabled is not None:
check(truthy(enabled), '%s is enabled' % DASHBOARD, 'Edit the dashboard and tick Enabled.')
check(text(field(d, 'dashboardDescription', 'description')),
'%s has a description' % DASHBOARD, "Edit the dashboard and set its Description, e.g. Understand what's driving your OEE.")

# 2. Widgets on each layout
widgets = rows(system.kanoa.dashboard.getDashboardWidgets({'dashboardId': dashboardId}))
widgets = [w for w in widgets if field(w, 'enabled') is None or truthy(field(w, 'enabled'))]
layouts = {}
for w in widgets:
layouts.setdefault(text(field(w, 'breakpointName')) or 'Desktop', []).append(w)

def find(layout, name):
return [w for w in layouts.get(layout, []) if key(name) in key(field(w, 'widgetName'))]

print '\n-- Desktop layout'
if not layouts:
check(False, '%s has widgets' % DASHBOARD, 'Open it in the Dashboard Editor and add the widgets from the lesson.')
desktop = 'Desktop' if 'Desktop' in layouts else (sorted(layouts)[0] if layouts else 'Desktop')
for name in SELECTORS + [OEE, DOWNTIMES]:
check(find(desktop, name), '%s layout has the %s widget' % (desktop, name),
'In the Dashboard Editor (%s layout), select an empty area of the canvas and add %s.' % (desktop, name))
oee, top = find(desktop, OEE), find(desktop, DOWNTIMES)

if oee:
p = params(oee[0])
metric = text(p.get('oeeMetric'))
check(metric.lower() == 'availability', 'OEE widget shows availability (oeeMetric)',
"oeeMetric is '%s'. Click the OEE widget and set oeeMetric to availability in its View Params." % (metric or 'not set'))
for w, name in [(oee, OEE), (top, DOWNTIMES)]:
if w:
p = params(w[0])
if 'assetPath' in p:
check(p.get('assetPath') in (None, ''), '%s follows the Asset Selector (assetPath is null)' % name,
"assetPath is '%s'. Set it to null so the Asset Selector drives this widget." % text(p.get('assetPath')))
if oee and top:
a, b = oee[0], top[0]
sideBySide = overlaps(span(a, 'rowStart', 'rowEnd'), span(b, 'rowStart', 'rowEnd')) and \
not overlaps(span(a, 'columnStart', 'columnEnd'), span(b, 'columnStart', 'columnEnd'))
check(sideBySide, 'On %s, the OEE and Top Downtimes widgets are side by side' % desktop,
'Place them next to each other on the same rows.')

# 3. Mobile layout: the two analytics widgets stacked
print '\n-- Mobile layout'
moOee, moTop = find('Mobile', OEE), find('Mobile', DOWNTIMES)
check(moOee and moTop, 'Mobile layout has the OEE and Top Downtimes widgets',
'At the top of the Dashboard Editor, switch to Mobile and place the widgets again for that layout.')
if moOee and moTop:
a, b = moOee[0], moTop[0]
stacked = not overlaps(span(a, 'rowStart', 'rowEnd'), span(b, 'rowStart', 'rowEnd'))
check(stacked, 'On Mobile, the OEE and Top Downtimes widgets are stacked',
'Place one above the other, each across the width of the Mobile layout.')
others = sorted(l for l in layouts if l not in (desktop, 'Mobile'))
if others:
note('Also laid out for: %s.' % ', '.join(others))

passed = results.count(True)
print '\n' + '=' * 70
print 'Result: %d of %d checks passed' % (passed, len(results))
if passed == len(results):
print 'All good. Your dashboard is built.'
else:
print 'Fix the FAIL items above, then run this again.'
print '=' * 70