ReferenceError: Chart is not defined (ui-template Chart.js)

I need to create two Y-axes: one for temperature and one for humidity. Each with its own scale. But I couldn't do this in ui-chart. Then I tried to use ui-template. But there I see an empty block, and the web browser gives an error "errorCaptured ReferenceError: Chart is not defined"

Google suggested pasting the <script src="https://cdn.jsdelivr.net"></script>
code in different places, but it didn't help.
How can I fix this?

Yes you can, using msg.ui_update.chartOptions. That gives you access to the underlying eCharts object. Have a look at the help text for the node for the principles of how to do it. I thought I had an example but apparently not. If you can't make it work then show us what you have tried.

I have done this using Chart.js and also eCharts. However, this was before DB2 adopted eCharts as the charting engine and as @Colin suggested, you can now use msg.ui_update.chartOptions. I have not updated my charts back to using the native chart widget, but here is an example of how eCharts using chartOptions

this.myChart.setOption({
        textStyle: { fontFamily: 'Tahoma,sans-serif' },
        title: { text: title,
              left:'auto',
              textStyle:{
                    fontWeight:'normal',
                    fontSize: 22,
                    },
              },
        tooltip: { trigger: "item", axisPointer: {type: "shadow"} },
        legend: {
          show: true,
          data: [
            { name: 'Battery %', icon: 'rect',itemStyle:{color:colors.battery }},
            { name: 'Temperature °F', icon: 'rect',itemStyle:{color:colors.temperature}}
          ],
          textStyle: {
            //color: 'white', // Color of the legend text
            //fontFamily: 'Arial', // Font family
            fontSize: 16, // Font size
            fontWeight: 'bold' // Font weight (e.g., 'normal', 'bold', 'bolder', 'lighter')
          }
        },
        //toolbox: { show: true, feature: { saveAsImage: {} } },
        grid: { left: "3%", right: "3%", containLabel: true },
        xAxis: {
          type: "category",
          name:'Time',
          nameGap: 30,
          nameLocation:'middle',
          nameTextStyle: {
            fontWeight: 'bold', // or 'normal', 'bolder', 'lighter', or a number like '700'
            fontSize: 16 // Font size in pixels
          },
          boundaryGap: false,
          data: labels,
          axisTick: { show: true },
          axisLabel: { formatter: "{value}", color: 'white' },
          axisLine: {
            lineStyle: {
            color: 'white',
            width: 2
            }
          }
        },
        yAxis: [
          {
            type: "value",
            name: 'Battery %',
            nameLocation: 'middle',
            position: 'left',
            min: minBattery,
            max: maxBattery,
            nameGap: 50,
            axisLine: { show: true, lineStyle: { color: 'white', width: 2 } },
            axisTick: { show: true },
            axisLabel: { formatter: "{value} %", color: 'white' },
            splitLine: { show: true, lineStyle: { color: '#333' } },
            nameTextStyle: { fontSize: 16, fontWeight: 'bold' },
            scale: true,
            axisPointer: { snap: true },
            yAxisIndex: 0
          },
          {
            type: "value",
            name: 'Temperature °F',
            nameLocation: 'middle',
            position: 'right',
            min: minTemp,
            max: maxTemp,
            nameGap: 50,
            axisLine: { show: true, lineStyle: { color: 'white', width: 2 } },
            axisTick: { show: true },
            axisLabel: { formatter: "{value} °F", color: 'white' },
            splitLine: { show: false },
            nameTextStyle: { fontSize: 16, fontWeight: 'bold' },
            scale: true,
            axisPointer: { snap: true },
            yAxisIndex: 1
          }
        ],
        series: [
          {
            name: 'Battery %',
            type: 'bar',
            yAxisIndex: 0,
            connectNulls: true,
            data: battery,
            barMaxWidth: 40,
            itemStyle: { color: colors.battery, borderColor: colors.battery, borderWidth: 1 },
            showSymbol: false,
            tooltip: {
              valueFormatter: function (value) {
              return value + ' %';
              }
            },
            stack: 'tesla'
          },
          {
            name: 'Temperature °F',
            type: 'line',
            smooth: true,
            connectNulls: true,
            yAxisIndex: 1,
            data: temp,
            lineStyle: { color: colors.temperature, width: 5, type: 'solid' },
            showSymbol: false,
            tooltip: {
              valueFormatter: function (value) {
              return value + ' •F';
              }
            },
          }
        ]
      }

The Y-axis block contains the 2 axis and the position: defines if it is the left or right axis. Similarly the series block defines which data set is mapped (though I don't think you need to do that with the new Chart widget but I could be wrong).

I may be stating the obvious, but when instantiating an external package in a ui-template, it is always a good practice to validate that it has fully loaded else you may get this exact reference error.
I always validate (in mounted() ) that the package finished loaded. See the following example (on the Tabulator package):

async function waitForTabulator() {
  const maxRetries = 20;
  const delay = 100; // ms

  for (let i = 0; i < maxRetries; i++) {
    if (typeof Tabulator === "function") {
      return true; // equivalent to 'return Promise.resolve(true)'
    }
    console.log(`Tabulator package not ready (${i})`);
    await sleep(delay);
  }
  throw new Error("Tabulator package load failure");
}
//------------------------
function sleep(ms)
{
    return new Promise((resolve) => {
        setTimeout(resolve, ms);
    });
}

Note that the function is async, meaning that if you need to wait for it to finish before you continue, put it in a wrapper async function and await it there.

I was misled by this comment: 2 Y-axis for a line chart - #2 by hotNipi
I didn't see anything like this in the node's help. There's a lot missing there. But your code example really helped me. I got two axes, each axis with its own dimension. But both graphs are tied to the same axis, the left one.


The temperature graph (blue) is linked to the humidity axis values, rather than to its own axis on the right.

Flow:

[{"id":"77ac0d428d4f7fc3","type":"inject","z":"chart_dual_axis_flow","name":"","props":[{"p":"payload"}],"repeat":"3","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"true","payloadType":"bool","x":730,"y":520,"wires":[["158e13789d14967c"]]},{"id":"158e13789d14967c","type":"function","z":"chart_dual_axis_flow","name":"RND Humidity","func":"// Humidity\nfunction getRandomInt(min, max) {\n  min = Math.ceil(min);\n  max = Math.floor(max);\n  msg.payload = Math.floor(Math.random() * (max - min)) + min;\n}\ngetRandomInt(15, 90);\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":900,"y":520,"wires":[["11076a4cf987c7cf"]]},{"id":"32a7ba35c0e492b6","type":"function","z":"chart_dual_axis_flow","name":"RND Temperature","func":"// Temperature\nfunction getRandomInt(min, max) {\n  min = Math.ceil(min);\n  max = Math.floor(max);\n  msg.payload = Math.floor(Math.random() * (max - min)) + min;\n}\ngetRandomInt(-22, 0);\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":890,"y":480,"wires":[["b3b5272d8e5ef29e"]]},{"id":"647b3cae3cab0cd7","type":"inject","z":"chart_dual_axis_flow","name":"","props":[{"p":"payload"}],"repeat":"3","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"true","payloadType":"bool","x":730,"y":480,"wires":[["32a7ba35c0e492b6"]]},{"id":"5b399b19bfae6249","type":"ui-chart","z":"chart_dual_axis_flow","group":"1a6de553fcc28deb","name":"Сlimate","label":"chart","order":9007199254740991,"chartType":"line","category":"topic","categoryType":"msg","xAxisLabel":"","xAxisProperty":"","xAxisPropertyType":"timestamp","xAxisType":"time","xAxisFormat":"","xAxisFormatType":"auto","xmin":"","xmax":"","yAxisLabel":"","yAxisProperty":"payload","yAxisPropertyType":"msg","ymin":"","ymax":"","bins":10,"action":"append","stackSeries":false,"pointShape":"circle","pointRadius":4,"showLegend":true,"removeOlder":"5","removeOlderUnit":"60","removeOlderPoints":"","colors":["#00ff1e","#ff0000","#ff7f0e","#2ca02c","#a347e1","#d62728","#ff9896","#9467bd","#c5b0d5"],"textColor":["#666666"],"textColorDefault":true,"gridColor":["#e5e5e5"],"gridColorDefault":true,"width":"8","height":"8","className":"","interpolation":"linear","x":1280,"y":500,"wires":[[]]},{"id":"b3b5272d8e5ef29e","type":"function","z":"chart_dual_axis_flow","name":"Temperature","func":"  msg.payload;\n  msg.topic = \"Temperature\";\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1090,"y":480,"wires":[["5b399b19bfae6249"]]},{"id":"11076a4cf987c7cf","type":"function","z":"chart_dual_axis_flow","name":"Humidity","func":"msg.payload;\nmsg.topic = \"Humidity\";\n\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1100,"y":520,"wires":[["5b399b19bfae6249"]]},{"id":"9697b39ea6d8c307","type":"function","z":"chart_dual_axis_flow","name":"series","func":"msg.payload = {\n\n  \"series\": [\n    {\n      \"name\": \"Temperature\",\n      \"itemStyle\": {\n        \"color\": \"blue\"\n      }\n    },\n\n    {\n      \"name\": \"Humidity\",\n      \"itemStyle\": {\n        \"color\": \"red\"\n      }\n    }\n  ],\n\n\n        xAxis: {\n          type: \"time\",\n          name:'Time',\n          nameGap: 30,\n          nameLocation:'middle',\n          nameTextStyle: {\n            fontWeight: 'bold',\n            fontSize: 16,\n            color: 'red'\n          },\n          \n          boundaryGap: false,\n          //data: labels,\n          axisTick: { show: true },\n          //axisLabel: { formatter: \"{value}\", color: 'green' },\n          axisLine: {\n            lineStyle: {\n            color: 'yellow',\n            width: 2\n            }\n          }\n          \n        },    \n        yAxis: [\n          {\n            type: \"value\",\n            name: 'Humidity',\n            nameLocation: 'middle',\n            position: 'left',\n            min: 10,\n            max: 100,\n            nameGap: 10,\n            axisLine: { show: true, lineStyle: { color: 'red', width: 1 } },\n            axisTick: { show: true },\n            axisLabel: { formatter: \"{value} %\", color: 'red' },\n            splitLine: { show: true, lineStyle: { color: '#333' } },\n            nameTextStyle: { fontSize: 16, fontWeight: 'bold' },\n            scale: true,\n            axisPointer: { snap: true },\n            yAxisIndex: 0\n          },\n          \n          {\n            type: \"value\",\n            name: 'Temperature',\n            nameLocation: 'middle',\n            position: 'right',\n            min: -25,\n            max: 0,\n            nameGap: 10,\n            axisLine: { show: true, lineStyle: { color: 'blue', width: 2 } },\n            axisTick: { show: true },\n            axisLabel: { formatter: \"{value} °С\", color: 'aqua' },\n            splitLine: { show: true }, \n            nameTextStyle: { fontSize: 16, fontWeight: 'bold' },\n            scale: true,\n            axisPointer: { snap: true },\n            yAxisIndex: 1\n          }\n\n\n\n\n        ]\n\n\n\n};\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":870,"y":600,"wires":[["5eef0cf35c6f04a5"]]},{"id":"80b74a6f2870417d","type":"inject","z":"chart_dual_axis_flow","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"true","payloadType":"bool","x":750,"y":600,"wires":[["9697b39ea6d8c307"]]},{"id":"5eef0cf35c6f04a5","type":"change","z":"chart_dual_axis_flow","name":"msg.ui_update.chartOptions","rules":[{"t":"set","p":"ui_update.chartOptions","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1060,"y":600,"wires":[["5b399b19bfae6249"]]},{"id":"1a6de553fcc28deb","type":"ui-group","name":"climate test","page":"ddcd6724d9bcbf2e","width":"11","height":1,"order":1,"showTitle":true,"className":"","visible":"true","disabled":"false","groupType":"default"},{"id":"ddcd6724d9bcbf2e","type":"ui-page","name":"climate test","ui":"27d73580a0f044ca","path":"/climate","icon":"home","layout":"grid","theme":"a70602e73bbb2a95","breakpoints":[{"name":"Default","px":"0","cols":"3"},{"name":"Tablet","px":"576","cols":"6"},{"name":"Small Desktop","px":"768","cols":"9"},{"name":"Desktop","px":"1024","cols":"12"}],"order":1,"className":"","visible":"true","disabled":"false"},{"id":"27d73580a0f044ca","type":"ui-base","name":"My Dashboard","path":"/dashboard","appIcon":"","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false,"headerContent":"page","navigationStyle":"default","titleBarStyle":"default","showReconnectNotification":true,"notificationDisplayTime":1,"showDisconnectNotification":true,"allowInstall":false},{"id":"a70602e73bbb2a95","type":"ui-theme","name":"Admin Theme","colors":{"surface":"#454545","primary":"#d47e1c","bgPage":"#eeeeee","groupBg":"#ffffff","groupOutline":"#cccccc"},"sizes":{"pagePadding":"12px","groupGap":"12px","groupBorderRadius":"4px","widgetGap":"12px","density":"default"}},{"id":"769462ef5dbbfbfb","type":"global-config","env":[],"modules":{"@flowfuse/node-red-dashboard":"1.30.2"}}]

How to attach a graph line to its axis?

There are thousands of options in the eCharts docs, it is not possible to document all those in the dashboard docs.

Add a yAxisIndex setting to each series, matching the setting in the relevant axis. So the series config become

  "series": [
    {
      "name": "Temperature",
      "itemStyle": {
        "color": "blue"
      },
      yAxisIndex: 1
    },

    {
      "name": "Humidity",
      "itemStyle": {
        "color": "red"
      },
      yAxisIndex: 0
    }
  ],

Is it ok if I use this as an example of how to do multi axis charts in the future when others ask about it?

Yes, it works as expected now!
Thank you!


We need to work with the design and color to make it look beautiful. But the main thing is that now it works correctly.

[{"id":"77ac0d428d4f7fc3","type":"inject","z":"chart_dual_axis_flow","name":"","props":[{"p":"payload"}],"repeat":"3","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"true","payloadType":"bool","x":730,"y":520,"wires":[["158e13789d14967c"]]},{"id":"158e13789d14967c","type":"function","z":"chart_dual_axis_flow","name":"RND Humidity","func":"// Humidity\nfunction getRandomInt(min, max) {\n  min = Math.ceil(min);\n  max = Math.floor(max);\n  msg.payload = Math.floor(Math.random() * (max - min)) + min;\n}\ngetRandomInt(15, 90);\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":900,"y":520,"wires":[["11076a4cf987c7cf"]]},{"id":"32a7ba35c0e492b6","type":"function","z":"chart_dual_axis_flow","name":"RND Temperature","func":"// Temperature\nfunction getRandomInt(min, max) {\n  min = Math.ceil(min);\n  max = Math.floor(max);\n  msg.payload = Math.floor(Math.random() * (max - min)) + min;\n}\ngetRandomInt(-22, 0);\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":890,"y":480,"wires":[["b3b5272d8e5ef29e"]]},{"id":"647b3cae3cab0cd7","type":"inject","z":"chart_dual_axis_flow","name":"","props":[{"p":"payload"}],"repeat":"3","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"true","payloadType":"bool","x":730,"y":480,"wires":[["32a7ba35c0e492b6"]]},{"id":"5b399b19bfae6249","type":"ui-chart","z":"chart_dual_axis_flow","group":"1a6de553fcc28deb","name":"Сlimate","label":"chart","order":9007199254740991,"chartType":"line","category":"topic","categoryType":"msg","xAxisLabel":"","xAxisProperty":"","xAxisPropertyType":"timestamp","xAxisType":"time","xAxisFormat":"","xAxisFormatType":"auto","xmin":"","xmax":"","yAxisLabel":"","yAxisProperty":"payload","yAxisPropertyType":"msg","ymin":"","ymax":"","bins":10,"action":"append","stackSeries":false,"pointShape":"circle","pointRadius":4,"showLegend":true,"removeOlder":"5","removeOlderUnit":"60","removeOlderPoints":"","colors":["#00ff1e","#ff0000","#ff7f0e","#2ca02c","#a347e1","#d62728","#ff9896","#9467bd","#c5b0d5"],"textColor":["#666666"],"textColorDefault":true,"gridColor":["#e5e5e5"],"gridColorDefault":true,"width":"8","height":"8","className":"","interpolation":"linear","x":1280,"y":500,"wires":[[]]},{"id":"b3b5272d8e5ef29e","type":"function","z":"chart_dual_axis_flow","name":"Temperature","func":"  msg.payload;\n  msg.topic = \"Temperature\";\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1090,"y":480,"wires":[["5b399b19bfae6249"]]},{"id":"11076a4cf987c7cf","type":"function","z":"chart_dual_axis_flow","name":"Humidity","func":"msg.payload;\nmsg.topic = \"Humidity\";\n\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1100,"y":520,"wires":[["5b399b19bfae6249"]]},{"id":"9697b39ea6d8c307","type":"function","z":"chart_dual_axis_flow","name":"series","func":"msg.payload = {\n\n  \"series\": [\n    {\n      \"name\": \"Temperature\",\n      \"itemStyle\": {\n        \"color\": \"blue\"\n      },\n      yAxisIndex: 1\n    },\n\n    {\n      \"name\": \"Humidity\",\n      \"itemStyle\": {\n        \"color\": \"red\"\n      },\n      yAxisIndex: 0\n    }\n  ],\n\n\n        xAxis: {\n          type: \"time\",\n          name:'Time',\n          nameGap: 30,\n          nameLocation:'middle',\n          nameTextStyle: {\n            fontWeight: 'bold',\n            fontSize: 16,\n            color: 'red'\n          },\n          \n          boundaryGap: false,\n          //data: labels,\n          axisTick: { show: true },\n          //axisLabel: { formatter: \"{value}\", color: 'green' },\n          axisLine: {\n            lineStyle: {\n            color: 'yellow',\n            width: 2\n            }\n          }\n          \n        },    \n        yAxis: [\n          {\n            type: \"value\",\n            name: 'Humidity',\n            nameLocation: 'middle',\n            position: 'left',\n            min: 10,\n            max: 100,\n            nameGap: 10,\n            axisLine: { show: true, lineStyle: { color: 'red', width: 1 } },\n            axisTick: { show: true },\n            axisLabel: { formatter: \"{value} %\", color: 'red' },\n            splitLine: { show: true, lineStyle: { color: '#333' } },\n            nameTextStyle: { fontSize: 16, fontWeight: 'bold' },\n            scale: true,\n            axisPointer: { snap: true },\n            yAxisIndex: 0\n          },\n          \n          {\n            type: \"value\",\n            name: 'Temperature',\n            nameLocation: 'middle',\n            position: 'right',\n            min: -25,\n            max: 0,\n            nameGap: 10,\n            axisLine: { show: true, lineStyle: { color: 'blue', width: 2 } },\n            axisTick: { show: true },\n            axisLabel: { formatter: \"{value} °С\", color: 'aqua' },\n            splitLine: { show: true }, \n            nameTextStyle: { fontSize: 16, fontWeight: 'bold' },\n            scale: true,\n            axisPointer: { snap: true },\n            yAxisIndex: 1\n          }\n\n\n\n\n        ]\n\n\n\n};\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":870,"y":600,"wires":[["5eef0cf35c6f04a5"]]},{"id":"80b74a6f2870417d","type":"inject","z":"chart_dual_axis_flow","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"true","payloadType":"bool","x":750,"y":600,"wires":[["9697b39ea6d8c307"]]},{"id":"5eef0cf35c6f04a5","type":"change","z":"chart_dual_axis_flow","name":"msg.ui_update.chartOptions","rules":[{"t":"set","p":"ui_update.chartOptions","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1060,"y":600,"wires":[["5b399b19bfae6249"]]},{"id":"1a6de553fcc28deb","type":"ui-group","name":"climate test","page":"ddcd6724d9bcbf2e","width":"11","height":1,"order":1,"showTitle":true,"className":"","visible":"true","disabled":"false","groupType":"default"},{"id":"ddcd6724d9bcbf2e","type":"ui-page","name":"climate test","ui":"27d73580a0f044ca","path":"/climate","icon":"home","layout":"grid","theme":"a70602e73bbb2a95","breakpoints":[{"name":"Default","px":"0","cols":"3"},{"name":"Tablet","px":"576","cols":"6"},{"name":"Small Desktop","px":"768","cols":"9"},{"name":"Desktop","px":"1024","cols":"12"}],"order":1,"className":"","visible":"true","disabled":"false"},{"id":"27d73580a0f044ca","type":"ui-base","name":"My Dashboard","path":"/dashboard","appIcon":"","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false,"headerContent":"page","navigationStyle":"default","titleBarStyle":"default","showReconnectNotification":true,"notificationDisplayTime":1,"showDisconnectNotification":true,"allowInstall":false},{"id":"a70602e73bbb2a95","type":"ui-theme","name":"Admin Theme","colors":{"surface":"#454545","primary":"#d47e1c","bgPage":"#eeeeee","groupBg":"#ffffff","groupOutline":"#cccccc"},"sizes":{"pagePadding":"12px","groupGap":"12px","groupBorderRadius":"4px","widgetGap":"12px","density":"default"}},{"id":"4770c3c549051e01","type":"global-config","env":[],"modules":{"@flowfuse/node-red-dashboard":"1.30.2"}}]
1 Like