Skip to content

Reports - Extending

WideQuick comes with a set of default report templates, but it is also possible to create fully custom templates tailored to specific needs. This section covers the complete process of creating a custom report template and its corresponding ReportController view, from designing the Excel layout to wiring it up with live data from the database.

This section assumes familiarity with the basics of the report system. If needed, see Get started and Configuring.

Creating templates

Creating a custom report template takes three steps:

  1. Design the template in Excel
  2. Configure the data sheet, macros and loggers
  3. Wiring it up

Designing the template

The first step is to design the report template in Excel. This includes deciding the layout, what graphs to include, and how data should be presented. WideQuick places no restrictions on the design, so the template can be tailored freely.

A report template typically consists of three sheets:

  • Presentation sheet: the visible part of the report. Contains graphs, tables, and any other visual elements the report should display.
  • Data sheet: a hidden sheet containing the raw data pulled from the database. This sheet feeds the presentation sheet with data. The data sheet is easier to configure in WideQuick® Designer, where hidden sheets are visible, rather than directly in Excel.
  • Meta sheet: a hidden sheet containing general information about the report, such as the report title, current date and time, selected logger, and selected signals. This sheet is useful for displaying report metadata on the presentation sheet.

Getting started

A good starting point is to sketch out what the final report should look like before opening Excel. Knowing what data is needed and how it should be presented makes configuring the data sheet and macros much easier.

To add a new sheet to the template, open the template in WideQuick® Designer and navigate to the bottom of the sheet tabs. Click Create New Sheet, see image below. To hide the sheet, right-click the sheet tab and select Hide Sheet.

newSheet
Adding a new sheet to the template in WideQuick Designer.

Note

Hidden sheets are only visible in WideQuick® Designer and will not be visible in the final generated report or when opening the template directly in Excel. Try generating a report with the sheet visible once the macro command is set, to get a feel for how the data is oriented.

Configuring the data sheet, macros and loggers

The data sheet is a hidden sheet within the report template that acts as a container for the raw data pulled from the database. When a report is generated, WideQuick uses macro commands placed in the data sheet to query the database and populate it with data.

To add a macro command, right-click a cell in the data sheet and select Edit Expression. This will open the following popup:

MacroConfigure
The Edit Expression popup for inserting a macro command.

Below is a description of each field:

  • Variable — Defines which signal to query. Two options are available:

    • variable_ref[#] — Queries a specific signal, where # is the index of the signal. A separate macro call is required for each signal.
    • alarms — A wildcard that pulls all alarms in the selected logger.
  • Values — Defines which data columns to include. Depends on the type of data being queried:

    • Signal data: name, datetime, value is typically sufficient.
    • Alarm data: name, state, severity, timestamp, text, group. Include only the fields needed for the report.
  • Logger — The logger the report queries data from. This must be set statically and cannot be changed dynamically.

  • Fixed/Range timespan — The reporter uses Range timespan by default, with Report_Reference.from_time and Report_Reference.to_time as the start and end times. These are set by the time objects in the ReportController.
  • Limit — The maximum number of rows to populate. This is set by the user in the ReportController when generating the report.
  • Datetime format — The format used for datetime values. Defaults to yyyy-MM-dd hh:mm:ss but can be adjusted to fit the needs of the report.

The placement of the macro command is important. Data is populated starting from the cell the macro is placed in and expands downward. The columns are determined by the Values field. For example, a signal macro with VALUES(name, datetime, value) will populate three columns starting from the macro cell:

Signal macro
Signal macro placed on the data sheet.
Signal macro result
Resulting columns populated by the macro.

And for alarm data with VALUES(name, state, severity, timestamp, text, group):

Alarm macro
Alarm macro placed on the data sheet.
Alarm macro result
Resulting columns populated for alarm data.

The number of rows populated depends on the Limit and timespan, which are set by the user in the ReportController when generating the report.

Meta sheet

The Meta sheet is a useful place to store general report information that is not pulled from the logger. It uses $() expressions to print internal or system variables directly into cells. For example:

  • $(Report_Reference.title) — The report title set by the user.
  • $(Report_Reference.logger) — The logger used for the report.
  • $(Report_Reference.from_time) — The start time of the report.
  • $(Report_Reference.to_time) — The end time of the report.
  • $(Report_Reference.limit) — The maximum number of events.
  • $(Report_Reference.variable_ref[#]) — The signal at index #.
  • $(Report_Reference.unit) — The unit for the report.
  • $(Report_Reference.factorArray[#]) — The scaling factor for each signal at index #.
  • $(System.dateTime) — The current date and time.
  • $(System.user) — The current user.

The Meta sheet is the only way to pass the report title set by the user into the presentation sheet. The integrator is free to include whichever fields are relevant to their report.

Wiring it up

With the presentation sheet designed and the data sheet populated with macro commands, the final step is to connect the two by linking the raw data to the graphs, tables, and other visual elements in the presentation sheet.

A few tips for wiring up the template effectively:

  • Use the data sheet for calculations: since the data sheet is hidden in the final report, it is a good place to perform intermediate calculations before passing the results to the presentation sheet. To edit it in Excel, temporarily unhide it.
  • Keep calculations away from the macro data: since macro data expands downward row by row, place any additional calculations in columns further to the right to avoid conflicts.
  • Reference the data sheet from the presentation sheet: graphs, bars, and other visual elements in the presentation sheet should reference cells in the data sheet rather than containing logic themselves. This keeps the presentation sheet clean and easier to maintain.
  • Use fixed ranges that cover the maximum possible rows: since the amount of data varies depending on the Limit and timespan, define ranges that cover the maximum number of rows the report could produce. Use IFERROR to handle empty cells gracefully.

Note

This guide does not cover Excel formula design in depth, since the possibilities are largely up to the integrator. The tips above are specific to working with WideQuick macro data.

Creating the ReportController view

With the template created, the next step is to build the ReportController view. This view determines which signals and data are sent to the report when it is generated. The system links a ReportController to a report template by matching their names, so it is important that the view is named exactly the same as the report template.

To get started, navigate to Templates in the project tree and duplicate the Template_ReportController view. Move the duplicate to the ReportController folder and rename it to match the report template name.

When a ReportController view is loaded, its template script initializes a view.data object with default values such as report type, time span, logger, and signal references. Each object placed in the view, such as to_time, Report_CustomTitle and LoggerList, reads from and writes to this shared view.data object as the user interacts with them.

When Button_CreateReport object is clicked, it collects the complete view.data object and adds it to a report queue via scReports.addReportToQueue() function. Reports in the queue are processed one at a time. The scReports script populates a global Report_Reference object with the queued data, which the macro commands in the data sheet read from when querying the database.

The correct template is identified and triggered using the report_type field in view.data, which is derived directly from the ReportController view name. This is why it is critical that the ReportController view name exactly matches the report template name. If they differ, the scReports script cannot locate and trigger the correct template.

Naming convention

The ReportController view name must exactly match the report template name. If they differ, scReports script will not be able to locate and trigger the correct template.

To build the view, a set of premade objects is available in the object library under Report. Below is a description of each object and when to use it:

Object Use when
LoggerList The user selects signals from a logger
LarmLogger_List The user selects alarms from an alarm logger
Unit The user selects a unit and prefix for the report
Report_CustomTitle The user sets a custom report title
Button_CreateReport Always. It is required to generate the report
Report_output_type The user chooses between PDF and Excel output
to_time The user sets an end time
from_time The user sets a start time
to_time_Singel The report uses a fixed time span (e.g. last 3 years)
Numbe_of_event The number of collected events should be limited

LoggerList

The LoggerList object is a TreeView configured to display the signals logged by a specific logger. When a signal is selected, a green checkmark appears next to it in the TreeView. The object also includes two text boxes showing the currently selected logger and the number of signals selected out of the maximum allowed.

The logger to display and the maximum number of selectable signals are both configured as properties on the object. Assign the desired values when placing the object in a ReportController.

LarmLogger_List

The LarmLogger_List object is a specialized version of LoggerList, purpose-built for displaying alarms logged by a logger. Unlike LoggerList, it automatically builds its tree based on alarm groups and omits redundant information. For example, if all alarms in the logger follow the pattern AS01.AS01_XXX_XX, the TreeView will display them simply as XXX_XX.

This object has two properties that must be configured:

  • Logger — Determines which logger the TreeView is built from. This should always match the logger that the report queries.
  • Signals — Controls how many signals can be selected for the report. The default maximum is 15, and it can be changed. See Changing the maximum of signals to be selected. This property should always be set to a numerical value, unless the TreeView is intentionally locked.

Unit

The Unit object holds the two pickers that decide the report's display unit: a Unit combo box for the base unit and a Prefix combo box for the SI prefix. The two are combined when the report is created, so k and Wh give kWh.

The Unit list is not a fixed set. It is built at runtime from the units of the signals on the logger the signal list is bound to, reduced to their SI base, so only units the project actually logs are offered. A signal whose unit is not recognised is left out of the list. See Adding a base unit for the recognised set and how to extend it.

Placing the object also turns on unit filtering for the signal list:

  • The signal TreeView stays disabled until a unit is chosen.
  • Choosing a unit filters the TreeView to signals of that dimension.
  • Changing the unit clears the current selection, since the signals selected under the previous unit are no longer in the list.

Filtering is therefore opt-in per ReportController. A controller built without the Unit object leaves the TreeView unfiltered, allows signals of different dimensions in the same report, and sends every value in its own native unit with no scaling applied. The alarm report controllers are built this way.

The object takes no properties. It locates the signal list on its own, so the same object works whether the view holds one logger list or shows a second one in its place.

Report_CustomTitle

The Report_CustomTitle object consists of a label and a text box. The text box sets the title of the report and is configured to communicate with the active data on the view.

Button_CreateReport

The Button_CreateReport object is a button that triggers report generation. When clicked, it gathers data from the view and sends it to the scReports script to create the report.

Report_output_type

The Report_output_type object consists of a label and a ComboBox. The selected ComboBox value determines whether the report is generated as a PDF only, or as both a PDF and an Excel file.

to_time

The to_time object consists of a label and a DateTime editor. The DateTime editor sets the end time of the report and is configured to report its current value to the view.

from_time

The from_time object works the same way as the to_time object, but sets the start time of the report instead.

to_time_Singel

The to_time_Singel object combines the functionality of to_time and from_time into a single object. It is used when the report template requires a fixed time span. The following properties can be configured:

  • Years prior — Sets the time span in years.
  • Weeks prior — Sets the time span in weeks.

These two properties are mutually exclusive: weeks and years cannot be combined.

Numbe_of_event

The Numbe_of_event object consists of a label and a SpinBox. The SpinBox value determines how many events are collected per macro call. This distinction is important:

  • If the alarms wildcard is used, the value represents the total number of events.
  • If variable_ref[#] is used, the value represents the number of events per signal.

Changing the maximum of signals to be selected

In the default reporter settings 15 signals are allowed to be selected for report generations. This can easily be changed if more signals are required. First, in WideQuick® Designer navigate to Data Types → Arrays → variable_ref and select Properties.... This will bring up the following popup:

Variable Array
The variable_ref array properties popup.

Change the Max. size to the desired value. Click Apply then Ok. The Report_Reference array has now been updated.

Next, update the scReports script to handle the new size. Locate the createReport() function and change the value 15 to the new Max. size value:

scReports — createReport() — before
for (var i = 0; i < 15; i++) Report_Reference.variable_ref[i] = "";
var signals = reportObj.variable_ref.split(',');
for (var i = 0; i < signals.length && i < 15; i++) Report_Reference.variable_ref[i] = signals[i];
scReports — createReport() — after
for (var i = 0; i < YOUR_MAX_VALUE; i++) Report_Reference.variable_ref[i] = "";
var signals = reportObj.variable_ref.split(',');
for (var i = 0; i < signals.length && i < YOUR_MAX_VALUE; i++) Report_Reference.variable_ref[i] = signals[i];

Adding a base unit

The report system recognises six physical quantities, each in any SI prefix:

Base unit Dimension power
W 1
Wh 1
m³ 3
m³/h 1
L 1
L/s 1

The prefixes offered are T, G, M, k, h, da, none, d, c, m, µ, n and p, so the six bases cover 78 unit strings between them.

The dimension power is what keeps the scaling correct. A prefix on a linear quantity scales by the prefix itself, so dW is a tenth of W. A prefix on m³ applies to the metre before it is cubed, so dm³ is a thousandth of m³ rather than a tenth. Any base that is a length cubed takes power 3, everything else takes 1.

Adding a base unit is a change in two places in the scReports script, and the two must be kept in step.

First, add the unit to the baseUnitInfo table together with its dimension power:

scReports — baseUnitInfo — before
this.baseUnitInfo = {
    "W": { power: 1 }, "Wh": { power: 1 },
    "m³": { power: 3 }, "m³/h": { power: 1 },
    "L": { power: 1 }, "L/s": { power: 1 }
};
scReports — baseUnitInfo — after
this.baseUnitInfo = {
    "W": { power: 1 }, "Wh": { power: 1 },
    "m³": { power: 3 }, "m³/h": { power: 1 }, "m³/s": { power: 1 },
    "L": { power: 1 }, "L/s": { power: 1 }
};

Then add the same string to baseUnitOrder. That list is ordered longest first so a compound unit is matched whole instead of being read as a prefix followed by a shorter base. m³/h has to be tested before m³, or it would be read as prefix m plus base ³/h. Place a new unit ahead of any shorter unit it begins with:

scReports — baseUnitOrder — before
this.baseUnitOrder = ["m³/h", "L/s", "m³", "Wh", "W", "L"];
scReports — baseUnitOrder — after
this.baseUnitOrder = ["m³/h", "m³/s", "L/s", "m³", "Wh", "W", "L"];

Why the order is a separate list

baseUnitOrder could be derived from the keys of baseUnitInfo at runtime, but this engine throws "Function call requires Object type" when Array.sort() is given a comparator function. The order is kept as a static list instead.

Units where a prefix has no meaning

The prefix pickers apply to every base in the table. A unit that cannot sensibly take a prefix, such as °C, would still be offered with the full prefix list, and k°C is not a meaningful reading. Only add units where SI prefixes carry a meaning.

A signal whose unit is not in the table is skipped without a log entry when the Unit list is built. A logger holding only unrecognised units therefore produces an empty Unit list, and because the signal TreeView is gated on a unit being chosen, the controller appears to offer no selectable signals at all. Adding the unit to the table resolves it.