Create your own custom symbol in PI Vision for heat-maps or profile analysis

Discussion created by mbaillargeon on Jul 28, 2020

Use Case: Many companies in pulp and paper, metals, facilities, and pharmaceutical industries analyze high-low data in a 2-D view or profile view to quickly spot anomalies so that they can reduce waste and improve quality and yield.

This type of analysis is possible within PI Vision using the extensibility functionality and 3rd party symbols available online. This example provides a high-level overview of the below topics:

  • The use of AF and EF to organize and add context to the quality data coming from a paper machine scanner with up to 1000 head-boxes.
    • For those of you not in the paper industry, a head-box takes a line of measurements that is represented as a vertical line in the above image. Stacking these linear measurements together creates a 2-D view.
  • The use of PI Vision to build a custom symbol to display 1 or more properties, such as thickness, temperature, moisture, etc. in a heat-map that shows low and high values of data.

NOTE: The intention of this post is to showcase an “art-of-the-possible” scenario and encourage further innovation within the PI Square community. This post and included video are not a detailed how-to guide. The specific heat-map symbol code created for the recording is not available to the public. As is the case with any custom code, whatever you end up creating for your unique situation will be outside the scope for receiving support from OSIsoft TechSupport. Instead, we welcome any questions or comments to be added at the end of this post to build community-driven support.

We broke down the process we went through to build the symbol into six steps:


Step 1: Find an existing JavaScript heatmap symbol

The first step is to either build your own JavaScript heatmap library (not covered here) or get/purchase one available on the internet. For this example, we used this one:

However, there are many others such as:


Please make sure to confirm the license associated with the symbol to ensure that your particular usage is in compliance with the symbol’s terms of use. Once you have selected your preferred JavaScript symbol, place it in the following directory on the PI Vision server so that your code is able to reference it: \PIVision\Scripts\app\editor\symbols\ext\libraries


Step 2: Pick you dataset and create the AF structure

The second step is to pick your dataset and create the AF structure if you do not have one already. The ideal way that we found to work with AF was to group all the individual values that we wanted to plot as attributes of a single element:



The advantage of this structure is that you can quickly select all the attributes in PI Vision. If you already have an AF structure that looks like the one below, where each profile represented as an element it will still work, but you will need to either add each element (profile) one by one or use the PI Vision search to drag and drop into the plot:


Step 3: Create a class for your heatmap in the template.html file

Any custom PI Vision symbol will usually have 3 files:

  • Implementation: This is where the logic of the code will be written. It will be covered on step 5.
  • Presentation: This file holds the configuration of the appearance of the symbol and will be covered below.
  • Configuration: This is for the configuration of the symbol when you right click on it. It defines the different buttons, sliders and fields that will be made available for the user.



Very few lines of code are necessary for the presentation layer. Its filename will end with template.html. In this example, the filename is heatmap-demo-template.html but you can choose any name you’d like.  The key point is that all three layers filenames need the same prefix (heatmap-demo- in this case). In the code below, the heatmap object is configured to occupy 100% of the symbol space. It could have just as easily been configured it to occupy 90% of the height and use the remaining space to add some text.



Step 4: Instantiate the heatmap in the heatmap.js file

Steps 4 and 5 are where you really start coding the symbol. The best resource to begin building custom symbols is the PI Vision extensibility guide. You can download the extensibility guide from Github here. There is also a new guide "Getting Started with PI Vision Extensibility" that I recommended viewing on the same Github page. There might be slight differences between PI Vision versions so please make sure you download the guide with the same version as your PI Vision server. If this is the first time that you’re deploying custom symbols, you should first upgrade your PI Vision server to the latest version to get the latest features and added stability and security. If this is not your first symbol and you are considering upgrading, please note that upgrading a PI Vision server might break some of your current symbols. If this happens, the extensibility guide might provide some guidance on what has changed and what needs to be repaired in the code.


Once the template.html is created, it is time to start coding the implementation layer. If you have never created a custom symbol before, start by trying to instantiate the heatmap without any values first. Refer to the PI Vision extensibility guide for the different sections of the code that are required in the implementation layer. You will also want to look at the specific documentation of the heatmap symbol you choose for instructions on instantiation. For the particular symbol used in this example, there is a function to create the heatmap instance:



The example above shows the heatmap section of the template file and specifies some of the parameters listed in the heatmap documentation (radius, maxOpacity, minOpacity and blur). If your code does not have any errors, you should be able to test the symbol in PI Vision at this point. You will see a blank heatmap as we are not pulling any values yet. Once you have confirmed the ability to visualize a blank heatmap, the next step is to retrieve the values within the PI Vision display’s time range and feed them to the heatmap.


Step 5: Link the values to the heatmap instance

PI Vision offers multiple ways of retrieving the data. We used the TimeSeries data shape with the ModePlotValues option as it made the most sense for a heatmap:

 In our symbol, each column represents a different attribute, and the y axis is the time. Therefore, we needed a data shape that would pull in multiple interpolated values for many attributes. PI Vision will take care of fetching the values from the attribute(s) that the user will drop on the heatmap symbol, but we still need to code what it needs to do with the data. The ability to add more attributes to a symbol is set in the datasourceBehavior parameter.


The algorithm above takes the incoming data (data.Data) and creates a newDataObject table. As per the heatmap documentation, our heatmap is expecting the incoming data to be in the following format:

At this point, PI Vision is providing an array of data with attribute, value, and timestamp but not the x and y positions necessary for a 2D representation. Therefore, the next step is to write a calculation that will provide the x and y positions and store the results in another table. The x and y positions are relative to the height and width of the heatmap object which were stored as variables (see code above: Height and Width variables):


Since this heatmap symbol is being used only as a tool for demonstrations, we are going to make some assumptions in the programming logic which might not be ok if it was being used in a production environment where more precision would be necessary. First, for the X axis, we are going to space out evenly the profiles over the heatmap symbol width. In some situations, the profiles might not be evenly be spaced. Second, we are calculating the relative position of each y position. If the heatmap symbol height is too small or if there are too many values being received, we will obtain something similar to the Profile 3 in the example above which might not offer sufficient precision.

These 2 heatmaps are actually the same values (Sinusoid, Sinusoidu and CDT158) over the same period of time (8 hours). If the values you are looking to plot have a high density of data like the CDT158 tag, you will need to make sure that the symbol occupies a lot of space in the PI Vision canvas or that you reduce it’s density by requesting longer intervalled data.


The logic described below can serve as a starting point for your own heatmap symbol but as discussed, you might need to add additional code to handle formatting and data density.


We decided that the x position would be calculated as follow:

attribute.X[i] = (Width / (number of attributes + 1)) * (i + 1)


If there are 3 attributes, and the heatmap symbol is 1000 pixels (px) by 1000 px, the x position for each will be:

attribute.X[0] = (1000px / (3+1)) * (0+1) = 250 px
attribute.X[1] = (1000px / (3+1)) * (1+1) = 500 px
attribute.X[2] = (1000px / (3+1)) * (2+1) = 750 px


For the y position the formula we used is:


attribute.Y[j] = (attribute.Timestamp – min.Timestamp) * Height / timerange


To find the y position we calculate the ratio of the difference between the timestamp of the value and the minimal time of the PI Vision page and the timerange of the PI Vision page. We then multiply this ratio by the height of the symbol.


For example, if we are plotting an hourly value over 24 hours the 3 first values would be:


attribute.Y[0] = (hour 0 – hour 0)*1000 px / 24 hours = 0 px
attribute.Y[1] = (hour 1 – hour 0)*1000 px / 24 hours = 41.7 px
attribute.Y[2] = (hour 2 – hour 0)*1000 px / 24 hours = 83.3 px

attribute.Y[24] = Math.round((hour 24 – hour 0)*1000 px / 24 hours = 1000 px


Once you have populated the table with x, y, and value you must send it over to the heatmap. I used the dataupdate function since it is called anytime new values are updated, therefore the heatmap will be refreshed.


The min and max values above are being used as references for the symbol to properly display the colors and are extra features that we implemented later:


On the left, the max variable is set to 100 and all the values inside the heatmap vary from 0-100. The lower values are in blue and the highest ones are in red. If the Max Value field is changed to 200, no value will be in red as they are now too low compared to the max. Additional features like this will be covered in the next section.


Step 6: Work on other features and fix issues

User based settings can be done in the configuration layer. In our heatmap, we created some sliders and binded them to the heatmap configuration properties. For example, the user can define the radius of each circle and they can define the number of intervals for the data retrieval:


Once you have a basic functioning heatmap symbol and have implemented the configuration layer you can begin working on other features. For example, we added a tooltip on ours. Whenever you hover the mouse over a value it will display the attribute name, the timestamp, and the value:

To implement the tooltip we created a new function updateTooltip() and called it when the mouse is over the symbol:

As you can see, the customization possibilities are endless! We hope that this example gets the wheels turning on the art of the possible and we’re certain that this community will continue to develop great extensions. Feel free to ask any questions, share ideas or your own examples of the heatmap symbol.


Happy coding!