# Different types in one chart

**URL:** <https://discourse.nodered.org/t/different-types-in-one-chart/100746>\
**Category:** Dashboard\
**Tags:** dashboard-2\
**Created:** [11 April 2026 06:58 UTC](https://discourse.nodered.org/t/different-types-in-one-chart/100746 "2026-04-11T06:58:32Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![nygma2004](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/nygma2004/32/1308_2.png) [@nygma2004](https://discourse.nodered.org/u/nygma2004)\
**Post date:** [11 April 2026 06:58 UTC](https://discourse.nodered.org/t/different-types-in-one-chart/100746/1 "2026-04-11T06:58:32Z")

</div>

I wanted to create a chart which has both a bar and a line series. I got it working, with Gemini's help:

 ![image](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/8/4/84562da5b5410ab32024cdf447774c6b019b5094.png)

But since I am a boomer I don't immediately trust AI and wanted to ask the experts here.

So there is a very good video from Yaser in YT [https://www.youtube.com/watch?v=KmVCIY4cpgg](https://www.youtube.com/watch?v=KmVCIY4cpgg) and he had an example how to create a chart in `ui-tempate`. When I tried it did not work, although it is clearly working in the video, therefore something must have changed in NR or in DB2.

Gemini was able to fix this saying " In Dashboard 2.0, the `Chart` object isn't attached to the global `window` object by default to avoid library version conflicts. To use Chart.js inside a `ui-template`, you need to **explicitly load the script** within your template."

Above all, it generated this function in the `mounted` section:

```auto
mounted() {
    // 1. Manually load Chart.js
    if (!window.Chart) {
      const script = document.createElement('script');
      script.src = "https://cdn.jsdelivr.net/npm/chart.js";
      script.onload = () => {
        this.scriptLoaded = true;
        // If data arrived before the script loaded, render it now
        if (this.msg?.payload) {
          this.renderChart(this.msg.payload);
        }
      };
      document.head.appendChild(script);
    } else {
      this.scriptLoaded = true;
    }
  },

```

Here is a small flow sample for completeness:

```auto
[{"id":"e0dc352d2b367475","type":"inject","z":"9d6045f175be2d26","name":"Sample","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"auto_calibration_status\":\"standby\",\"detection_range\":0,\"energy_streaming\":true,\"illuminance\":294,\"led_indicator\":true,\"linkquality\":153,\"occupancy\":false,\"presence_clear_cooldown\":15,\"presence_state\":\"absence\",\"sensitivity_preset\":\"medium\",\"zone_10_active\":true,\"zone_10_motion_energy\":3,\"zone_10_motion_threshold\":5,\"zone_10_presence_energy\":3,\"zone_10_presence_threshold\":4,\"zone_1_active\":true,\"zone_1_motion_energy\":6,\"zone_1_motion_threshold\":14,\"zone_1_presence_energy\":7,\"zone_1_presence_threshold\":15,\"zone_2_active\":true,\"zone_2_motion_energy\":4,\"zone_2_motion_threshold\":11,\"zone_2_presence_energy\":4,\"zone_2_presence_threshold\":12,\"zone_3_active\":true,\"zone_3_motion_energy\":2,\"zone_3_motion_threshold\":9,\"zone_3_presence_energy\":1,\"zone_3_presence_threshold\":10,\"zone_4_active\":true,\"zone_4_motion_energy\":1,\"zone_4_motion_threshold\":6,\"zone_4_presence_energy\":1,\"zone_4_presence_threshold\":6,\"zone_5_active\":true,\"zone_5_motion_energy\":2,\"zone_5_motion_threshold\":6,\"zone_5_presence_energy\":2,\"zone_5_presence_threshold\":6,\"zone_6_active\":true,\"zone_6_motion_energy\":2,\"zone_6_motion_threshold\":5,\"zone_6_presence_energy\":2,\"zone_6_presence_threshold\":4,\"zone_7_active\":true,\"zone_7_motion_energy\":2,\"zone_7_motion_threshold\":5,\"zone_7_presence_energy\":3,\"zone_7_presence_threshold\":4,\"zone_8_active\":true,\"zone_8_motion_energy\":2,\"zone_8_motion_threshold\":5,\"zone_8_presence_energy\":3,\"zone_8_presence_threshold\":4,\"zone_9_active\":true,\"zone_9_motion_energy\":2,\"zone_9_motion_threshold\":5,\"zone_9_presence_energy\":3,\"zone_9_presence_threshold\":4}","payloadType":"json","x":270,"y":2380,"wires":[["a0f53de29453daea"]]},{"id":"5e938ec24a8430c7","type":"function","z":"9d6045f175be2d26","name":"Convert Data for Chart","func":"const motion = [];\nconst presence = [];\n\nfor (let i = 1; i <= 10; i++) {\n\n motion.push({ \n \"range\": (i - 1) * 0.5 + \"m\",\n \"energy\": msg.payload[\"zone_\" + i + \"_motion_energy\"],\n \"threshold\": msg.payload[\"zone_\" + i +\"_motion_threshold\"]\n });\n\n presence.push({\n \"range\": (i - 1) * 0.5 + \"m\",\n \"energy\": msg.payload[\"zone_\" + i + \"_presence_energy\"],\n \"threshold\": msg.payload[\"zone_\" + i + \"_presence_threshold\"]\n });\n\n}\n\nmsg.payload = {\"motion\": motion, \"presence\": presence};\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":560,"y":2480,"wires":[["0e7766d2ac4e7f2a","3172769fa990f755"]]},{"id":"0e7766d2ac4e7f2a","type":"change","z":"9d6045f175be2d26","name":"Get motion","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.motion","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":830,"y":2460,"wires":[["f9f30ca98bce76c3"]]},{"id":"3172769fa990f755","type":"change","z":"9d6045f175be2d26","name":"Get presence","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.presence","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":840,"y":2520,"wires":[["956f4cc885a664d6"]]},{"id":"f9f30ca98bce76c3","type":"ui-template","z":"9d6045f175be2d26","group":"a87dc2d2e84fee78","page":"","ui":"","name":"Motion","order":1,"width":0,"height":0,"head":"","format":"<template>\n <div class=\"chart-holder\">\n <canvas id=\"myMotionChart\"></canvas>\n </div>\n</template>\n\n<script>\nexport default {\n data() {\n return {\n chart: null,\n scriptLoaded: false\n }\n },\n mounted() {\n // 1. Manually load Chart.js\n if (!window.Chart) {\n const script = document.createElement('script');\n script.src = \"https://cdn.jsdelivr.net/npm/chart.js\";\n script.onload = () => {\n this.scriptLoaded = true;\n // If data arrived before the script loaded, render it now\n if (this.msg?.payload) {\n this.renderChart(this.msg.payload);\n }\n };\n document.head.appendChild(script);\n } else {\n this.scriptLoaded = true;\n }\n },\n watch: {\n msg: {\n handler(val) {\n if (val?.payload && this.scriptLoaded) {\n this.renderChart(val.payload);\n }\n },\n immediate: true\n }\n },\n methods: {\n renderChart(data) {\n if (!Array.isArray(data)) return;\n\n const labels = data.map(d => d.range);\n const energies = data.map(d => d.energy);\n const thresholds = data.map(d => d.threshold);\n\n // Grab the canvas via ID for reliability in this scope\n const canvas = document.getElementById('myMotionChart');\n if (!canvas) return;\n \n const ctx = canvas.getContext('2d');\n\n if (this.chart) {\n this.chart.destroy();\n }\n\n this.chart = new window.Chart(ctx, {\n type: 'bar',\n data: {\n labels: labels,\n datasets: [\n {\n label: 'Energy',\n data: energies,\n backgroundColor: 'rgba(54, 162, 235, 0.6)',\n borderColor: 'rgba(54, 162, 235, 1)',\n borderWidth: 1,\n order: 2 // Bars in background\n },\n {\n label: 'Threshold',\n type: 'line',\n data: thresholds,\n borderColor: 'rgba(255, 99, 132, 1)',\n showLine: false, // <--- This removes the connecting line\n pointStyle: 'line', // <--- This changes the dot to a horizontal dash\n pointRadius: 10, // <--- Makes the dash wider\n pointBorderWidth: 3, // <--- Makes the dash thicker\n order: 1 // Line in foreground\n }\n]\n },\n options: {\n responsive: true,\n maintainAspectRatio: false,\n scales: {\n y: { beginAtZero: true }\n }\n }\n });\n }\n },\n unmounted() {\n if (this.chart) {\n this.chart.destroy();\n }\n }\n};\n</script>\n\n<style>\n.chart-container {\n position: relative;\n height: 400px;\n}\n</style>\n","storeOutMessages":true,"passthru":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":1030,"y":2460,"wires":[[]]},{"id":"956f4cc885a664d6","type":"ui-template","z":"9d6045f175be2d26","group":"9421109df037ba34","page":"","ui":"","name":"Presence","order":1,"width":0,"height":0,"head":"","format":"<template>\n <div class=\"chart-holder\">\n <canvas id=\"myPresenceChart\"></canvas>\n </div>\n</template>\n\n<script>\nexport default {\n data() {\n return {\n chart: null,\n scriptLoaded: false\n }\n },\n mounted() {\n // 1. Manually load Chart.js\n if (!window.Chart) {\n const script = document.createElement('script');\n script.src = \"https://cdn.jsdelivr.net/npm/chart.js\";\n script.onload = () => {\n this.scriptLoaded = true;\n // If data arrived before the script loaded, render it now\n if (this.msg?.payload) {\n this.renderChart(this.msg.payload);\n }\n };\n document.head.appendChild(script);\n } else {\n this.scriptLoaded = true;\n }\n },\n watch: {\n msg: {\n handler(val) {\n if (val?.payload && this.scriptLoaded) {\n this.renderChart(val.payload);\n }\n },\n immediate: true\n }\n },\n methods: {\n renderChart(data) {\n if (!Array.isArray(data)) return;\n\n const labels = data.map(d => d.range);\n const energies = data.map(d => d.energy);\n const thresholds = data.map(d => d.threshold);\n\n // Grab the canvas via ID for reliability in this scope\n const canvas = document.getElementById('myPresenceChart');\n if (!canvas) return;\n \n const ctx = canvas.getContext('2d');\n\n if (this.chart) {\n this.chart.destroy();\n }\n\n this.chart = new window.Chart(ctx, {\n type: 'bar',\n data: {\n labels: labels,\n datasets: [\n {\n label: 'Energy',\n data: energies,\n backgroundColor: 'rgba(54, 162, 235, 0.6)',\n borderColor: 'rgba(54, 162, 235, 1)',\n borderWidth: 1,\n order: 2 // Bars in background\n },\n {\n label: 'Threshold',\n type: 'line',\n data: thresholds,\n borderColor: 'rgba(255, 99, 132, 1)',\n showLine: false, // <--- This removes the connecting line\n pointStyle: 'line', // <--- This changes the dot to a horizontal dash\n pointRadius: 10, // <--- Makes the dash wider\n pointBorderWidth: 3, // <--- Makes the dash thicker\n order: 1 // Line in foreground\n }\n]\n },\n options: {\n responsive: true,\n maintainAspectRatio: false,\n scales: {\n y: { beginAtZero: true }\n }\n }\n });\n }\n },\n unmounted() {\n if (this.chart) {\n this.chart.destroy();\n }\n }\n};\n</script>\n\n<style>\n.chart-container {\n position: relative;\n height: 400px;\n}\n</style>\n","storeOutMessages":true,"passthru":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":1040,"y":2520,"wires":[[]]},{"id":"a87dc2d2e84fee78","type":"ui-group","name":"Motion Energies","page":"865c8ac82222ced6","width":6,"height":1,"order":-1,"showTitle":true,"className":"","visible":"true","disabled":"false","groupType":"default"},{"id":"9421109df037ba34","type":"ui-group","name":"Presence Energies","page":"865c8ac82222ced6","width":6,"height":1,"order":-1,"showTitle":true,"className":"","visible":"true","disabled":"false","groupType":"default"},{"id":"865c8ac82222ced6","type":"ui-page","name":"Zemismart ZPS-Z1","ui":"cb79bc4520925e32","path":"/zps-z1","icon":"home","layout":"grid","theme":"0d92c765bfad87e6","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":"cb79bc4520925e32","type":"ui-base","name":"My UI","path":"/dashboard","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control","ui-text"],"showPathInSidebar":false},{"id":"0d92c765bfad87e6","type":"ui-theme","name":"Basic Blue Theme","colors":{"surface":"#4d58ff","primary":"#0094ce","bgPage":"#eeeeee","groupBg":"#ffffff","groupOutline":"#cccccc"},"sizes":{"pagePadding":"12px","groupGap":"12px","groupBorderRadius":"4px","widgetGap":"12px"}}]

```

This flow processes and visualizes the data from a mmWave radar presence sensor and shows the energy reflections in distance ranges in the bar graph and the lines are the threshold values for reporting occupancy.

Would you say that this approach is safe/good to be used?

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/totallyinformation/32/31_2.png) [@TotallyInformation](https://discourse.nodered.org/u/TotallyInformation)\
**Post date:** [11 April 2026 12:31 UTC](https://discourse.nodered.org/t/different-types-in-one-chart/100746/2 "2026-04-11T12:31:10Z")

</div>

This will auto-grab the chart library from the Internet if it can't find it. I don't think that should be needed and might result in a different version being loaded. I wouldn't use that personally.

---

<div class="post-metadata">

**Author:** ![rakgupta](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/rakgupta/32/42535_2.png) [@rakgupta](https://discourse.nodered.org/u/rakgupta)\
**Post date:** [11 April 2026 13:22 UTC](https://discourse.nodered.org/t/different-types-in-one-chart/100746/3 "2026-04-11T13:22:54Z")

</div>

> [@TotallyInformation](#):
>
> I don't think that should be needed and might result in a different version being loaded.

I got around this potential issue by creating a ui-template (widget,global scope) and loading the library there.

> [@DB2: ui-template eCharts fails to load](https://discourse.nodered.org/t/db2-ui-template-echarts-fails-to-load/100578/5):
>
> Thanks - I changed the CDN and that seemed to fix it. I also realized that I was loading eCharts in every ui-template. I removed those and used the new CDN in a ui-template (Type = widget, Scope = "All Pages"). Hopefully, if there are any changes in the future, I only have to address them in one place crossed_fingers:t3 Would the correct way be to put it in a sub-directory in the node-red directory on the RPi that is running Node-RED? I think I also have to make some changes then to the set…

---

<div class="post-metadata">

**Author:** ![nygma2004](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/nygma2004/32/1308_2.png) [@nygma2004](https://discourse.nodered.org/u/nygma2004)\
**Post date:** [11 April 2026 13:44 UTC](https://discourse.nodered.org/t/different-types-in-one-chart/100746/4 "2026-04-11T13:44:13Z")

</div>

> [@rakgupta](#):
>
> I got around this potential issue by creating a ui-template (widget,global scope) and loading the library there.

Thanks, I implemented the same global ui-template way and it seems to be working just fine.

---

<div class="post-metadata">

**Author:** ![system](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/1X/d073cd938eafa2e558d7c2cd59003b3ef4963033.png) [@system](https://discourse.nodered.org/u/system)\
**Post date:** [11 May 2026 13:44 UTC](https://discourse.nodered.org/t/different-types-in-one-chart/100746/5 "2026-05-11T13:44:36Z")

</div>

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.
