Echarts Flexdash Gauges

Just sharing an Echart gauge that I've created in Flexdash, using the flexdash custom node.

gauge

[{"id":"27e8564637493f56","type":"flexdash custom","z":"1543d308b342690a","fd_container":"28d1859702cdf729","fd_cols":"3","fd_rows":"4","fd_array":false,"fd_array_max":10,"fd_output_topic":"","fd_loopback":false,"name":"Echarts","title":"","sfc_source":"<template>\n  <v-chart class=\"chart\" :option=\"option\" autoresize />\n</template>\n\n<style scoped>\n.chart {\n  height: 100%;\n}\n</style>\n\n<script>\nimport { use } from 'echarts/core'\nimport { CanvasRenderer } from 'echarts/renderers';\nimport { GaugeChart } from 'echarts/charts'\nimport {\n  TitleComponent,\n  TooltipComponent,\n  LegendComponent,\n} from 'echarts/components';\n\nimport VChart, { THEME_KEY } from 'vue-echarts'\nuse([\n  CanvasRenderer,\n  GaugeChart,\n  TitleComponent,\n  TooltipComponent,\n  LegendComponent,\n]);\n\nexport default {\n  // Props are the inputs to the widget.\n  // They can be set dynamically using Node-RED messages using `msg.<prop>`.\n  // In a \"custom widget\" like this one they cannot be set via the Node-RED flow editor:\n  // use the default values in the lines below instead.\n  props: {\n    option: { default: {} },\n  },\n  components: { VChart },\n}\n</script>\n","import_map":{"echarts/core":"https://cdn.jsdelivr.net/npm/echarts/dist/echarts.esm.js","echarts/renderers":"https://esm.sh/echarts/renderers","echarts/charts":"https://esm.sh/echarts/charts","echarts/components":"https://esm.sh/echarts/components","vue-echarts":"https://cdn.jsdelivr.net/npm/vue-echarts@6.3.3/dist/index.esm.min.js","vue-demi":"https://cdn.jsdelivr.net/npm/vue-demi@0.13.11/lib/v3/index.mjs","resize-detector":"https://cdn.jsdelivr.net/npm/resize-detector@0.3.0/esm/index.js"},"x":550,"y":1960,"wires":[[]]},{"id":"69c3d54ac37e10a9","type":"function","z":"1543d308b342690a","name":"sample echarts","func":"/*\nUse msg.title, msg.min, msg.max & msg.units to\nchange default values, and msg.payload for gauge value.\n*/\nlet gaugeTitle, gaugeUnits, gaugeMin, gaugeMax\nconst dataValue = msg.payload\nif (msg.title)  { gaugeTitle = msg.title     } else gaugeTitle = \"\"\nif (msg.units)  { gaugeUnits = msg.units     } else gaugeUnits =\"Ā°C\"\nif (msg.min)    { gaugeMin = Number(msg.min) } else gaugeMin = 0\nif (msg.max)    { gaugeMax = Number(msg.max) } else gaugeMax = 100\n\nconst option = {\n    title: {\n        text: gaugeTitle\n        },\n    series: [\n        {\n            type: 'gauge',\n            axisLine: {\n                lineStyle: {\n                    width: 30,\n                    color: [\n                        [0.3, '#67e0e3'],\n                        [0.7, '#37a2da'],\n                        [1, '#fd666d']\n                    ]\n                }\n            },\n            pointer: {\n                itemStyle: {\n                    color: 'auto'\n                }\n            },\n            axisTick: {\n                distance: -30,\n                length: 8,\n                lineStyle: {\n                    color: '#fff',\n                    width: 2\n                }\n            },\n            splitLine: {\n                distance: -30,\n                length: 30,\n                lineStyle: {\n                    color: '#fff',\n                    width: 4\n                }\n            },\n            axisLabel: {\n                color: 'auto',\n                distance: 40,\n                fontSize: 12\n            },\n            detail: {\n                valueAnimation: true,\n              //  formatter: '{value} Ā°C',\n                formatter: '{value}' + gaugeUnits,\n                color: 'auto',\n                fontSize: 14\n            },\n            data: [\n                {\n                    value: dataValue\n                }\n            ],\n            max: gaugeMax,\n            min: gaugeMin\n        }\n    ]\n};\n\nreturn { option }","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":370,"y":1960,"wires":[["27e8564637493f56"]]},{"id":"ca67244a0349768d","type":"inject","z":"1543d308b342690a","name":"Random payload","props":[{"p":"payload"},{"p":"title","v":"Temperature","vt":"str"},{"p":"max","v":"30","vt":"num"}],"repeat":"1","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"$floor($random()*30)","payloadType":"jsonata","x":160,"y":1960,"wires":[["69c3d54ac37e10a9"]]},{"id":"28d1859702cdf729","type":"flexdash container","name":"charts","kind":"StdGrid","fd_children":",d2dbe97c44f6fb5a,d2b3883a74adc563,ab904c40d22f83e1,527401ee602f4386,4f31b9c704e0b947,a8dfa00ae270ac88,b27685878ffe3711,8713c375979525c8,5021eeaf697e64c2,02a8193ebdb37b8f,4ba2dad94e5ffd6f,5aa2f5882d6d108c,99d0a46353e4f3f8,8db95e700c175b9c,27e8564637493f56,2582a4c3e5ae0e0e","title":"","tab":"9f23b0158f8280e3","min_cols":"1","max_cols":"20","unicast":"","parent":"","solid":false,"cols":"1","rows":"1"},{"id":"9f23b0158f8280e3","type":"flexdash tab","name":"charts","icon":"mdi-chart-line","title":"charts","fd_children":",28d1859702cdf729","fd":"e8f5aea52ab49500"}]

[EDIT 27/12/22] - Flow updated to allow properties to be set via incoming msg (similar to default FD widgets)

8 Likes

Nicely done!! I really want to get into playing with Flexdash as soon as I get all the other things I have to do (per the other half) get done :frowning_face:

3 Likes

Only problem so far is the size of the Echarts libraries, it takes a long time on first load...

@tve The titles of the widgets that you have created, all respect the light/dark color scheme, for example black text for light scheme, whilst changing to white text for dark scheme.
For consistency, can that also be achieved whilst using the FD custom node?
(The background changes OK, but the title does not - as per the screenshot above in first post)

I can easily add colour to the title using this -

    title: {
        text: gaugeTitle,
        textStyle: {
            color: '#0000FF'
            },
        },

If it's not easy to achieve, I could always use a mid-grey, so it could be seen in both schemes.

1 Like

Excellent gauge.

Did you try putting the Echarts library into local directory? This way, the library loads pretty fast, and no internet access is required.

Can you provide a repro for me? Here's a very simple custom widget:

[{"id":"b27685878ffe3711","type":"flexdash custom","z":"451d9acba8e52c2f","fd_container":"28d1859702cdf729","fd_cols":1,"fd_rows":1,"fd_array":false,"fd_array_max":10,"fd_output_topic":"","fd_loopback":false,"name":"Widget with Imports","title":"with imports","sfc_source":"<template>\n  <v-btn variant=\"elevated\" class=\"ma-auto\" @click=\"clicked()\">\n    <span class=\"label\">{{ label }}</span>\n  </v-btn>\n</template>\n\n<style scoped>\n  .label { color: red; }\n</style>\n\n<script scoped>\n\nimport uPlot from 'uplot'\nimport { version } from 'vue';\nimport { useDisplay } from \"vuetify\"\n\nexport default {\n  // name: 'SimpleButton', // the name of the widget (ignored in this context)\n\n  // Props are the inputs to the widget.\n  // They can be set dynamically using Node-RED messages using `msg.<prop>`.\n  // In a \"custom widget\" like this one they cannot be set via the Node-RED flow editor:\n  // use the default values in the lines below instead.\n  props: {\n    label: { default: \"clickme\" }, // text to show inside button\n    output: { default: \"I was clicked\" }, // value to output when clicked\n  },\n\n  mounted() {\n    console.log(\"Widget with Imports:\", uPlot, version, useDisplay)\n  },\n\n  // simple methods within the component\n  methods: {\n    clicked() { // handle the clicking of the button, i.e., the handler for the '@click'\n      this.$emit('send', this.output) // emit an event (Vue concept), a 'send' event goes to NR\n    },\n  },\n}\n</script>\n","import_map":{},"x":150,"y":500,"wires":[[]]},{"id":"28d1859702cdf729","type":"flexdash container","name":"charts","kind":"StdGrid","fd_children":",d2dbe97c44f6fb5a,d2b3883a74adc563,ab904c40d22f83e1,527401ee602f4386,4f31b9c704e0b947,a8dfa00ae270ac88,b27685878ffe3711,8713c375979525c8,5021eeaf697e64c2,02a8193ebdb37b8f,4ba2dad94e5ffd6f,5aa2f5882d6d108c,99d0a46353e4f3f8,8db95e700c175b9c,e63901c8e782e435","title":"","tab":"9f23b0158f8280e3","min_cols":"1","max_cols":"20","unicast":"","parent":"","solid":false,"cols":"1","rows":"1"},{"id":"9f23b0158f8280e3","type":"flexdash tab","name":"charts","icon":"mdi-chart-line","title":"charts","fd_children":",28d1859702cdf729","fd":"e8f5aea52ab49500"}]

This is how it looks for me:

image

image

So something must be going on in your case...

I have, it's attached to the first post above.

I'm trying to pass the title in via msg, and allowing the Echarts opts to print the title.

    title: {
        text: gaugeTitle,
        },

Initially, I did try allowing msg.title to pass from the inject node, through the function node, and use the custom node's 'title' contained in config, which would probably be the better option (as that does change according to the scheme)

widget

...but that didn't seem to work out when using msg.title - repro below;

[{"id":"9f44bd0b2968a316","type":"flexdash custom","z":"1543d308b342690a","fd_container":"28d1859702cdf729","fd_cols":1,"fd_rows":1,"fd_array":false,"fd_array_max":10,"fd_output_topic":"","fd_loopback":false,"name":"Widget with Imports","title":"","sfc_source":"<template>\n  <v-btn variant=\"elevated\" class=\"ma-auto\" @click=\"clicked()\">\n    <span class=\"label\">{{ label }}</span>\n  </v-btn>\n</template>\n\n<style scoped>\n  .label { color: red; }\n</style>\n\n<script scoped>\n\nimport uPlot from 'uplot'\nimport { version } from 'vue';\nimport { useDisplay } from \"vuetify\"\n\nexport default {\n  // name: 'SimpleButton', // the name of the widget (ignored in this context)\n\n  // Props are the inputs to the widget.\n  // They can be set dynamically using Node-RED messages using `msg.<prop>`.\n  // In a \"custom widget\" like this one they cannot be set via the Node-RED flow editor:\n  // use the default values in the lines below instead.\n  props: {\n    label: { default: \"clickme\" }, // text to show inside button\n    output: { default: \"I was clicked\" }, // value to output when clicked\n  },\n\n  mounted() {\n    console.log(\"Widget with Imports:\", uPlot, version, useDisplay)\n  },\n\n  // simple methods within the component\n  methods: {\n    clicked() { // handle the clicking of the button, i.e., the handler for the '@click'\n      this.$emit('send', this.output) // emit an event (Vue concept), a 'send' event goes to NR\n    },\n  },\n}\n</script>\n","import_map":{},"x":530,"y":2205,"wires":[["1a059719aaeea8db"]]},{"id":"0a668f4c1b559954","type":"inject","z":"1543d308b342690a","name":"","props":[{"p":"title","v":"my name","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":365,"y":2205,"wires":[["9f44bd0b2968a316"]]},{"id":"1a059719aaeea8db","type":"debug","z":"1543d308b342690a","name":"debug 26","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":710,"y":2205,"wires":[]},{"id":"28d1859702cdf729","type":"flexdash container","name":"charts","kind":"StdGrid","fd_children":",d2dbe97c44f6fb5a,d2b3883a74adc563,ab904c40d22f83e1,527401ee602f4386,4f31b9c704e0b947,a8dfa00ae270ac88,b27685878ffe3711,8713c375979525c8,5021eeaf697e64c2,02a8193ebdb37b8f,4ba2dad94e5ffd6f,5aa2f5882d6d108c,99d0a46353e4f3f8,8db95e700c175b9c,27e8564637493f56,2582a4c3e5ae0e0e,9f44bd0b2968a316","title":"","tab":"9f23b0158f8280e3","min_cols":"1","max_cols":"20","unicast":"","parent":"","solid":false,"cols":"1","rows":"1"},{"id":"9f23b0158f8280e3","type":"flexdash tab","name":"charts","icon":"mdi-chart-line","title":"charts","fd_children":",28d1859702cdf729","fd":"e8f5aea52ab49500"}]
1 Like

I can't load the echarts widget anymore, it fails with

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://esm.sh/v102/node_buffer.js. (Reason: CORS header ā€˜Access-Control-Allow-Originā€™ missing). Status code: 200.

Not sure, looks like a propblem at esm.sh, dunno.

There is a bug which prevents msg.title from updating the title if the widget does not have a title prop (but see below), I need to fix that.

The animated GIF in your first post shows a title that doesn't look like what FD would render. That is rendered by Echarts, hence no light/dark adjustment.

const option = {
    title: {
        text: gaugeTitle
    },

In general in FD:

  • if the widget has a title prop then it needs to render the title itself
  • if the widget does not have a title prop, the wrapper around the widget renders it
  • the default text color changes with the theme
1 Like

Yes, that's correct.

In that case, I'll revert to Plan A and allow msg.title to pass through the function node, to the FD node. (No rush to fix).

That's clear. Thanks.

Wow! The weather is bad near you!!! :rofl:

1 Like

This a useful gauge, again by eCharts. It's like a thermostat that displays 2 sets of data, which could be the 'set' temperature, and the 'actual' temperature.

temp

[{"id":"2582a4c3e5ae0e0e","type":"flexdash custom","z":"1543d308b342690a","fd_container":"28d1859702cdf729","fd_cols":"3","fd_rows":"4","fd_array":false,"fd_array_max":10,"fd_output_topic":"","fd_loopback":false,"name":"Temp Echarts","title":"Temperature","sfc_source":"<template>\n  <v-chart class=\"chart\" :option=\"option\" autoresize />\n</template>\n\n<style scoped>\n.chart {\n  height: 100%;\n}\n</style>\n\n<script>\nimport { use } from 'echarts/core'\nimport { CanvasRenderer } from 'echarts/renderers';\nimport { GaugeChart } from 'echarts/charts'\nimport {\n  TitleComponent,\n  TooltipComponent,\n  LegendComponent,\n} from 'echarts/components';\n\nimport VChart, { THEME_KEY } from 'vue-echarts'\nuse([\n  CanvasRenderer,\n  GaugeChart,\n  TitleComponent,\n  TooltipComponent,\n  LegendComponent,\n]);\n\nexport default {\n  // Props are the inputs to the widget.\n  // They can be set dynamically using Node-RED messages using `msg.<prop>`.\n  // In a \"custom widget\" like this one they cannot be set via the Node-RED flow editor:\n  // use the default values in the lines below instead.\n  props: {\n    option: { default: {} },\n  },\n  components: { VChart },\n}\n</script>\n","import_map":{"echarts/core":"https://cdn.jsdelivr.net/npm/echarts/dist/echarts.esm.js","echarts/renderers":"https://esm.sh/echarts/renderers","echarts/charts":"https://esm.sh/echarts/charts","echarts/components":"https://esm.sh/echarts/components","vue-echarts":"https://cdn.jsdelivr.net/npm/vue-echarts@6.3.3/dist/index.esm.min.js","vue-demi":"https://cdn.jsdelivr.net/npm/vue-demi@0.13.11/lib/v3/index.mjs","resize-detector":"https://cdn.jsdelivr.net/npm/resize-detector@0.3.0/esm/index.js"},"x":600,"y":2095,"wires":[[]]},{"id":"e58479387d184238","type":"function","z":"1543d308b342690a","name":"Temp Echarts Options","func":"const dataValue = msg.payload\nconst option = {\n    series: [\n        {\n            type: 'gauge',\n            center: ['50%', '60%'],\n            startAngle: 200,\n            endAngle: -20,\n            min: 0,\n            max: 30,\n            splitNumber: 6,\n            itemStyle: {\n                color: '#FFAB91'\n            },\n            progress: {\n                show: true,\n                width: 30\n            },\n            pointer: {\n                show: false\n            },\n            axisLine: {\n                lineStyle: {\n                    width: 30\n                }\n            },\n            axisTick: {\n                distance: -45,\n                splitNumber: 5,\n                lineStyle: {\n                    width: 2,\n                    color: '#999'\n                }\n            },\n            splitLine: {\n                distance: -52,\n                length: 14,\n                lineStyle: {\n                    width: 3,\n                    color: '#999'\n                }\n            },\n            axisLabel: {\n                distance: 0,\n                color: '#999',\n                fontSize: 14\n            },\n            anchor: {\n                show: false\n            },\n            title: {\n                show: false\n            },\n            detail: {\n                valueAnimation: true,\n                width: '60%',\n                lineHeight: 40,\n                borderRadius: 8,\n                offsetCenter: [0, '-15%'],\n                fontSize: 40,\n                fontWeight: 'bolder',\n                formatter: '{value}Ā°C',\n                color: 'auto'\n            },\n            data: [\n                {\n                    value: dataValue[0]\n                }\n            ]\n        },\n        {\n            type: 'gauge',\n            center: ['50%', '60%'],\n            startAngle: 200,\n            endAngle: -20,\n            min: 0,\n            max: 30,\n            itemStyle: {\n                color: '#FD7347'\n            },\n            progress: {\n                show: true,\n                width: 8\n            },\n            pointer: {\n                show: false\n            },\n            axisLine: {\n                show: false\n            },\n            axisTick: {\n                show: false\n            },\n            splitLine: {\n                show: false\n            },\n            axisLabel: {\n                show: false\n            },\n            detail: {\n                show: false\n            },\n            data: [\n                {\n                    value: dataValue[1]\n                }\n            ]\n        }\n    ]\n};\n\nreturn { option }","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":395,"y":2095,"wires":[["2582a4c3e5ae0e0e"]]},{"id":"b5f4e4a81a8f611d","type":"function","z":"1543d308b342690a","name":"dummy data","func":"const arr = [[10,15],[11,15],[12,15],[13,15],[14,15],[15,15],[15,19],[16,19],[17,19],[18,19],[19,19],[19,13],[17,13],[15,13],[13,13],[12,13],[11,13],[10,14],[10,15]];\n\narr.map((element) => {\n    node.send({payload: element});\n});","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":145,"y":2110,"wires":[["bb59284a87ad2e26"]]},{"id":"777a123d12f6cff9","type":"inject","z":"1543d308b342690a","name":"","props":[{"p":"payload"}],"repeat":"19","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"","payloadType":"str","x":130,"y":2065,"wires":[["b5f4e4a81a8f611d"]]},{"id":"bb59284a87ad2e26","type":"delay","z":"1543d308b342690a","name":"","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":140,"y":2155,"wires":[["e58479387d184238"]]},{"id":"28d1859702cdf729","type":"flexdash container","name":"charts","kind":"StdGrid","fd_children":",d2dbe97c44f6fb5a,d2b3883a74adc563,ab904c40d22f83e1,527401ee602f4386,4f31b9c704e0b947,a8dfa00ae270ac88,b27685878ffe3711,8713c375979525c8,5021eeaf697e64c2,02a8193ebdb37b8f,4ba2dad94e5ffd6f,5aa2f5882d6d108c,99d0a46353e4f3f8,8db95e700c175b9c,27e8564637493f56,2582a4c3e5ae0e0e,9f44bd0b2968a316","title":"","tab":"9f23b0158f8280e3","min_cols":"1","max_cols":"20","unicast":"","parent":"","solid":false,"cols":"1","rows":"1"},{"id":"9f23b0158f8280e3","type":"flexdash tab","name":"charts","icon":"mdi-chart-line","title":"charts","fd_children":",28d1859702cdf729","fd":"e8f5aea52ab49500"}]

Thanks David, no, not yet, I want to explore eCharts & Flexdash a little more before doing so. Possibly looking at other chart libraries as well.

2 Likes

Well if you look at my last gauge... that's me turning the heating down, and my wife turning it back up again :laughing: :laughing: :laughing:

5 Likes

Wonderful, Just what I needed for my new Flexdash dashboard.

Edit:

when i send the title with msg.title, it is not centered, but if i hard code into the gauge prop, it is centered. how to send custom title but with centered ?

1 Like

can you assist how do i do that? i have an offline application running, and this may seems to be a problem if I have to be connected to web all the time.

ITMT i will try to open the source and save the page in my local directory and see what it does

You need to create an NPM package. There is the node-red-fd-testnodes package as sample and the docs also contain info. This part of the docs is not complete, though.

If you use dashboard, then you can download "echarts.min.js" (about 1MB) and put into your Node-RED static directory.
Then use template node to include "echarts" like the following.
Include Echarts

By doing this, Echarts loads very fast, and you do not need to connect to internet to use it.

With Flexdash, probably you can do this similarly? Sorry I have not had time diving into Flexdash.

this is way beyond my comprehension right now, so i am waiting to improve my skill set...

thanks for the tip

The custom widget and FD in general use ES modules, so it's more natural to get the ".esm.js" package and import it. But that's not the issue, the issue is that the echarts package (whether umd or esm) is now just the core of echarts and you need additional stuff for renderers, nice charts, and more. I don't know much about echarts, you can plot some things just with the core packages, but many of the examples I've seen need more.

But isn't that because we have been using the Echarts' minimum bundle via CDN, instead of the 'full package' and therefore have to add in more modules to add features that aren't in the minimum bundle?

1 Like

There is no full package. If someone can point me to a "fuller" package I can easily make it work.
Or put differently, if someone can show the gauges that Paul demoed here in ui_template node I can make it work in FD as well...