Flexdash Bar Chart

I tried to create a column chart/bar chart but unable to get around how to , i am able to generate a line chart with example given. would you please help in a demo flow for the same.
also i need some help in getting the dynamic data from mysql query to be able to be read by flexdash.

currently i am getting the mysql query output in the below format.


but the flexdash example wants it in this way

1 Like

Me neither!
uPlot appears to require a plug-in to display bar charts. Not sure how to implement this yet, but I've tried to interpret Uplot Barchart Plugin - JSFiddle - Code Playground :roll_eyes:

I'm sure in time we'll get some flexdash examples for this and other chart styles.

It would be better if you could store the time in your database as a epoch timestamp, instead of just a time. How do you intend to differentiate between 14:53hrs on one day, and 14:53hrs on the following day?
It would then be fairly easy to reconstruct your data into a format required by flexdash, which I can help with.

What is the type of the time column in mysql? Is there no date portion? Right now the TimePlot really likes to have date & time...

To make things easier I would map the times into today, something like (untested):

const today = (new Date()).toISOString().substring(0,11) // 2022-12-24T
const data = msg.payload.map(d => {
  const datetime = today + d.Time + 'Z' // 2022-12-24T14:53:00Z
  const ts = (new Date(datetime)).getTime() / 1000 // Unix timestamp 1671893580
  return [ ts, d.Reading ]

Yup. I'd like some bar charts too :sleepy:. I wish uPlot was better documented. I just fixed-up TimelinePlot to omit the values in the bars when it gets too dense, that was something...
What did you try for the bar charts? You should be able to do that in CustomWidget, you just include the plugin right there.

Edit: I see, he pulls in quadtree and distr, that starts to get unwieldy in a custom widget... Those two are also used in the timeline plot, I'll make them separate modules in FD and export them. But yeah, in general I guess I need to beef up the editing of custom widgets so one can add library/util files :laughing:

Edit #2: A bit more work thank I would have hoped for, but here is something:

I'll have to push a release so you guys can play with it.

@davidz suggested Echarts, which I rejected 'cause it's almost 2x the size of all of FlexDash but as an extra node-red-fd-echarts, i.e. not built in, it's probably a good idea. Have to look whether "it just drops in" or is a major amt of work...

Edit #3: Oh, import hell, no immediate echarts :poop:, but someday FlexDash Custom Widgets will be awesome :joy:


I can't keep up Thorsten !!
You are developing quicker that I can reply :laughing: :laughing: :laughing:


Yeah, that's because you guys keep asking for features faster than I can hack! :laughing:

1 Like

I feel the momentum for flexdash gathering....


Well, interesting Xmas goose chase... Echarts... Yeah...

I had some nice hack to rewrite "import" statements for the Custom Widgets. Well, that works, but if I import vue-echarts it in turn wants to import 'vue' and 'echarts/core' and stuff like that. Without bundling step that doesn't work.

Fortunately, as I was tracking that down I bumped into the solution, which is to use import maps, shimmed so browsers that don't support them yet don't get left out. OK, so I rewrote (for the umpteenth time) the dynamic import code. Now one can just import "as usual" in a custom widget, e.g.:

And then one just has to tell the browser (eh, tell FlexDash to tell the browser) where those 'echarts/core', 'echarts/renderer', etc modules are supposed to come from. For that there's a new Import Map prop on the custom widget node:

which expands to

And this all works, except that... the distributed 1MB "full echarts" package actually only includes the core! :thinking: To run the simple pie chart example I copied from the vue-echarts site a bunch of additional stuff is needed:


Good grief... Aaaaaaand... these are not available from a CDN! The recommended path is to npm install and let a bundler put the monster package together. The other option is an online build tool where one can select the components, charts, renderers and it builds the package.

So I tried that aaaaaand... it only builds a CJS package, not ESM. Loosers!

So, I wondered, how does it work with uibuilder without build step? Ah, the examples use an older version of Echarts where the all-included package actually included everything. But again, only CJS...

End of story? Well sort-a yes, sort-a no... Yes in that the most reasonable way to plug Echarts into FlexDash... Wait, Echarts is a multiple of the size of FlexDash... So the most reasonable way to plug FlexDash into Echarts is to build a node-red-fd-echarts node (or should that be node-red-echarts-fd ?) :nerd_face:

But where there's a will there's a way? Enter esm.sh! It builds the ESM module on-the-fly! So here we go, import the renderers, charts, and components from esm.sh:

Aaaaand... don't hold your breath: you may run out before this pig loads!
YES! It all loads!


Merry Christmas! :rofl:

NB: I hope it's obvious that this is not the way to use Echarts with FlexDash, it really needs a node-red-fd-echarts node. That will provide a clean way to bundle everything together the way Echarts intends it. If someone is interested in working this I can put an initial "hello echarts" package together.


I know that you are always up for a challenge :wink:
...but did you consider Plotly?
It has a CDN - plotly.js - Libraries - cdnjs - The #1 free and open source CDN built to make life easier for developers which serves plotly 'basic' that I believe includes line, bar, scatter, pie & bubble charts, plus others, weighing it at 748k.
Just tried the 'basic' CDN using version 2.16 on codepen, which worked fine.
Pie chart - https://codepen.io/rossoreed/pen/bGjVWZy
Bar chart - https://codepen.io/rossoreed/pen/GRBpEKw
Bubble chart - https://codepen.io/rossoreed/pen/JjBYJoO
Scatter plot - https://codepen.io/rossoreed/pen/NWBGgGK

1 Like

Plotly is better than Echarts in terms of history data graphic display. Plotly and Echarts are two of my favorite charts. We use Plotly for history data review.

One issue with Plotly is the real-time speed. Echarts runs faster than Plotly when there are many data points that need to be updated in real time.
If Plotly can reach the speed of Echarts, then we will be all over it.

When there are multiple curves and the data do not arrive at the same time, then Echarts works the best per our testing. Echarts uses less CPU and system resources, and runs faster than most other charts.

The testing environment included RPI2 B, RPI3 B, RPI CM4, and different PCs with Chrome, Firefox and Edge browsers.

1 Like

Did you try "import" all of Echarts?

import * as echarts from 'echarts';

// Create the echarts instance
var myChart = echarts.init(document.getElementById('main'));

// Draw the chart
  title: {
    text: 'ECharts Getting Started Example'
  tooltip: {},
  xAxis: {
    data: ['shirt', 'cardigan', 'chiffon', 'pants', 'heels', 'socks']
  yAxis: {},
  series: [
      name: 'sales',
      type: 'bar',
      data: [5, 20, 36, 10, 10, 20]

Never used Echarts, but Plotly has proved a popular choice here in the node-RED community, probably because of it being lightweight, flexible, and it's easy to use documentation.

I only mentioned Plotly because of the difficulties experienced by Thorsten implementing Echarts' numerous packages, and the cumulative file size.

Yes understood. We like and use both charts.

Actually Echarts has larger user base than Plotly, very flexible with detailed documentation.

As to the size, ploty.min.js is 3.5MB, while echarts.min.js is about 1MB, about 1/3 of Plotly.

As to importing, including "echarts.min.js" should be enough? (no need for other files)

Yup. Didn't work. Maybe I'm mis-undestanding something. I went to Vue-ECharts | vue-echarts and clicked on the first vue3 example link: Vue-ECharts + Vue 3 - StackBlitz and the code has:


And those things are not there if you import * as echarts from 'echarts'...

I should have used your example, that works real easy: :boom:


Alright, time to put all this into a release so someone else can tinker with charts!

Re: plotly... I'm really happy with uPlot for FlexDash and believe it's the right choice for a built-in charting package. Primarily it's very small (~40KB!), is fast, and does a lot. This way FlexDash has easy to use charts built-in without anyone feeling like they're pulling in some big thing they don't want. (The easy-to-use referring to the TimePlot and WindPlot nodes.)

Then, when it comes to Echart, Apex charts, Plotly and the other dozen big fully featured charting packages, everyone has their own preference. And that's fine. Someone here needs to figure out how to integrate them into Node-RED flows, e.g., what should messages contain and how do they drive the charting package. Then it's really easy to put together a custom node-red-fd- node. Again, I can get someone started, but I can't figure out how to use these big charting packages...

For completeness, this is the FD CustomWidget node for David's simple example:

[{"id":"e63901c8e782e435","type":"flexdash custom","z":"451d9acba8e52c2f","fd_container":"28d1859702cdf729","fd_cols":"4","fd_rows":"3","fd_array":false,"fd_array_max":10,"fd_output_topic":"","fd_loopback":false,"name":"Simple Echart","title":"","sfc_source":"<template>\n  <div ref=\"echart\" style=\"height:100%; width:100%\"></div>\n</template>\n\n<script>\nimport * as echarts from 'echarts';\n\nexport default {\n  // Create the echarts instance\n  mounted() {\n    //setTimeout(() => {\n      console.log(this.$refs.echart.clientWidth)\n      this.myChart = echarts.init(this.$refs.echart);\n      this.draw()\n    //}, 200)\n  },\n\n  // simple methods within the component\n  methods: {\n    draw() {\n      this.myChart.setOption({\n        title: {\n          text: 'ECharts Simple'\n        },\n        tooltip: {},\n        xAxis: {\n          data: ['shirt', 'cardigan', 'chiffon', 'pants', 'heels', 'socks']\n        },\n        yAxis: {},\n        series: [\n          {\n            name: 'sales',\n            type: 'bar',\n            data: [5, 20, 36, 10, 10, 20]\n          }\n        ]\n      });\n    },\n  },\n}\n\n</script>\n","import_map":{"echarts":"https://cdn.jsdelivr.net/npm/echarts/dist/echarts.esm.js"},"x":480,"y":980,"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"}]

And this is the flow with the complex Pie Chart example:

[{"id":"99d0a46353e4f3f8","type":"flexdash custom","z":"451d9acba8e52c2f","fd_container":"28d1859702cdf729","fd_cols":"3","fd_rows":3,"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 { PieChart } 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  PieChart,\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":460,"y":940,"wires":[[]]},{"id":"cc83c987de3d9288","type":"inject","z":"451d9acba8e52c2f","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":110,"y":940,"wires":[["e72ace42a696fa66"]]},{"id":"e72ace42a696fa66","type":"function","z":"451d9acba8e52c2f","name":"sample echarts","func":"const option = {\n    title: {\n        text: 'Traffic Sources',\n        left: 'center',\n    },\n    tooltip: {\n        trigger: 'item',\n        formatter: '{a} <br/>{b} : {c} ({d}%)',\n    },\n    legend: {\n        orient: 'vertical',\n        left: 'left',\n        data: ['Direct', 'Email', 'Ad Networks', 'Video Ads', 'Search Engines'],\n    },\n    series: [\n        {\n            name: 'Traffic Sources',\n            type: 'pie',\n            radius: '55%',\n            center: ['50%', '60%'],\n            data: [\n                { value: 335, name: 'Direct' },\n                { value: 310, name: 'Email' },\n                { value: 234, name: 'Ad Networks' },\n                { value: 135, name: 'Video Ads' },\n                { value: 1548, name: 'Search Engines' },\n            ],\n            emphasis: {\n                itemStyle: {\n                    shadowBlur: 10,\n                    shadowOffsetX: 0,\n                    shadowColor: 'rgba(0, 0, 0, 0.5)',\n                },\n            },\n        },\n    ],\n}\n\nreturn { option }","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":280,"y":940,"wires":[["99d0a46353e4f3f8"]]},{"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"}]

These examples will only work with the next release...


I also like uPlot, but the only that lets it down is it's documentation... users need a degree in clairvoyance to understand it :grimacing:

1 Like

I didn't paste the flow with the uPlot bars chart custom widget, here it is:

[{"id":"5aa2f5882d6d108c","type":"flexdash custom","z":"451d9acba8e52c2f","fd_container":"28d1859702cdf729","fd_cols":"4","fd_rows":"3","fd_array":false,"fd_array_max":10,"fd_output_topic":"","fd_loopback":false,"name":"BarsPlot","title":"","sfc_source":"<template>\n  <time-plot-raw :data=\"payload\" :options=\"options\"></time-plot-raw>\n</template>\n\n<style>\n  .reverse-legend .u-legend {\n    display: flex !important;\n    align-items: center;\n    justify-content: center;\n    flex-direction: row-reverse;\n  }\n</style>\n\n<script scoped>\n\nconst { colors, color_by_name } = window.Utils['plot-colors']\nconst { toISO } = window.Utils['formatter']\n\nfunction deepEqual(obj1, obj2) {\n  return JSON.stringify(obj1) === JSON.stringify(obj2) // yeah...\n}\n\nexport default {\n  name: \"TimeBarsPlot\",\n  help: `Time-series bar chart with simple options.\n`,\n\n  props: {\n    payload: { // data in row-wise format\n      type: Array,\n      default() { return null },\n      validator(v) { return v === null || Array.isArray(v) },\n      tip: \"array of row-wise data or a single row\",\n    },\n\n    labels: { type: Array, default: ()=>[], tip: \"array of labels for series\" },\n    colors: { type: Array, default: ()=>[], tip: \"array of colors for series, names or #rrggbb\" },\n    // Note: the following props are all individual props instead of having a left_axis:{} and\n    // right_axis:{} prop because it allows individual props to be changed while that's not readily\n    // possible with an object prop. Also, the individual props are a bit easier to discover.\n    left_unit: { type: String, default: \"\", tip: \"unit to label left axis\" },\n    left_min: { type: Number, default: null, tip: \"minimum for left axis\" },\n    left_max: { type: Number, default: null, tip: \"maximum for left axis\" },\n    left_decimals: { type: Number, default: 1, tip: \"decimals on left axis\" },\n    left_isoprefix: { type: Boolean, default: false, tip: \"use SI prefix on left axis\" },\n    left_log: { type: Boolean, default: false, tip: \"use log scale on left axis\" },\n  },\n\n  full_page: true, // can expand to full-page\n\n  data() { return { options: null }},\n  watch: {\n    _options: {\n      immediate: true,\n      handler() {\n        if (!deepEqual(this.options, this._options)) {\n          this.options = this._options\n          console.log(`Options for time-plot-raw: ${(this.labels||[]).join('|')}`, this._options)\n        }\n      }\n    }\n  },\n\n  computed: {\n\n    // generate options for uPlot based on the props\n    // this also emits an event as a side-effect (not supposed to do that, oh well...)\n    _options() {\n      const arrays = [ \"labels\", \"colors\" ]\n      // make sure these props are arrays so we don't have to guard umpteen times below\n      // e.g. instead of this.labels use pp.labels henceforth\n      const pp = Object.fromEntries(\n        arrays.map(p => [p, Array.isArray(this[p]) ? this[p] : []])\n      )\n\n      // declare the series\n      const series = [ { label: \"time\" } ]\n      const ns = Math.max(1, ...arrays.map(a => pp[a].length))\n      for (let s=0; s<ns; s++) {\n        const d = this.left_decimals\n        const u = this.left_unit\n        const iso = this.left_isoprefix\n        const serie = {\n          label: pp.labels[s] || `series ${s+1}`, // see special case below\n          fill: pp.colors[s] ? color_by_name(pp.colors[s]) : colors[s%colors.length],\n          stroke: '#cccccc',\n          width: 0,\n          value: iso ? ((_,v) => this.iso_fmt(v, d, u)) : `v && (v.toFixed(${d}) + \"${u}\")`\n        }\n        series.push(serie)\n      }\n\n      // axes\n      const axes = [ {}, {\n        values: this.left_isoprefix\n                ? (_, spl) => this.iso_axis(spl, this.left_decimals, this.left_unit)\n                : `vv.map(v => v && (v + \"${this.left_unit}\"))`,\n      }]\n\n      // generate the scale definition given the props\n      // for log scale, we have to use hard min/max else uPlot barfs\n      // for linear scale, we use 0.1 pad where no limit is specified, and a soft limit\n      // otherwise, which means that [...]\n      function setScale(log, min, max) {\n        if (log) {\n          const scale = {\n            distr: 3, // 1=linear,2=ordinal,3=log,4=arcsinh\n            range: [null, null], // can't use pad for log scale\n          }\n          if (Number.isFinite(min)) scale.range[0] = min\n          if (Number.isFinite(max)) scale.range[1] = max\n          return scale\n        } else {\n          const scale = { range: { min: {pad:0.1}, max: {pad:0.1} } }\n          if (Number.isFinite(min)) scale.range.min = { soft: min, mode: 1 }\n          if (Number.isFinite(max)) scale.range.max = { soft: max, mode: 1 }\n          return scale\n        }\n      }\n\n      // scales, see also https://github.com/leeoniya/uPlot/issues/526\n      const scales = {}\n      scales.y = setScale(this.left_log, this.left_min, this.left_max)\n      if (this.left_log) {\n        axes[1].grid = { width: 1 }\n        axes[1].ticks = { width: 1 }\n      }\n\n      // series-bars plugin\n      const plugins = [ window.Utils['uplot-series-bars'].default({\n        ori: 0,\n        dir: 1,\n      }) ]\n\n      // put uplot options together\n      const opts = { series, axes, scales, plugins }\n      if (ns == 1 && !pp.labels[0]) {\n        opts.legend = { show: false }\n        opts.series[1].label = \" \"\n      }\n      return opts\n    },\n\n  },\n\n  methods: {\n    iso_fmt(value, decimals, unit) {\n      if (!value) return \"\"\n      const [v, pref] = toISO(value)\n      return v.toFixed(decimals) + pref + unit\n    },\n    iso_axis(splits, decimals, units) {\n      return splits.map(s => s && this.iso_fmt(s, decimals, units))\n    },\n  },\n}\n</script>\n","import_map":"null","x":460,"y":820,"wires":[[]]},{"id":"115b12db403f5d15","type":"inject","z":"451d9acba8e52c2f","name":"ping","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":90,"y":820,"wires":[["fa7b05bffde8aaee"]]},{"id":"fa7b05bffde8aaee","type":"function","z":"451d9acba8e52c2f","name":"set msg","func":"const labels = [ 'a', 'b', 'c']\nconst payload = []\nfor (let t=0; t<10; t++) {\n    const ts = Math.trunc(Date.now()/1000) - t*3600\n    payload.push([\n        (new Date(ts)).toISOString().substring(14,19),\n        t,\n        2*t,\n        10-t,\n    ])\n}\nreturn { labels, payload }","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":260,"y":820,"wires":[["5aa2f5882d6d108c","efe478758dbef860"]]},{"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"}]

(So much discussion has happened in this thread since I posted the query, i am not sure replying here would be wise, any how, )

I do have the time in mysql database as a 'datetime' field, but i need the data in bar chart as only minutes since i usually take only few hours of data for display, not across the dates. but i could get the date in 'datetime' format.

I will go through the entire thread, try to digest and will post back.

1 Like

Well, there was a slight hiccup along the way... You may notice that my custom widget is called BarPlot and not TimeBarPlot... As-is, the uPlot bar chart plugin uses a non-time X axis, so getting datetime, although the right thing to do in general with TimePlot stuff, isn't gonna help with what I pasted. It wasn't clear to me that you needed a TimeBarPlot...

i imported the bar chart widget and able to reproduce.
i see that the individual series cannot be switched on/off like earlier in time plot.
some problem in my setting or is it changed ?



Not happening in timeplot graph also.

i have updated the latest FD


1 Like

Yes, I'm seeing the same now, something's changed.
No errors reported in browser console or log.

Also the datapoint circles are now offset on both time-plot and time-plot-raw


1 Like

A post was split to a new topic: Transform data from array