I haven't seen an example of an H Bar chart for Dashboard 2 so, as I wanted one, I put this together. The flow contains (real) example data and hopefully will work for others. Be aware that the CSS for each widget pollutes the whole page.
I am sure there are improvement opportunities so let me know if you find some
The H Bar chart can also be set up as a stacked H Bar chart
[{"id":"84c4a8c53f4017b3","type":"ui-template","z":"fe7296cf2a5f7fba","group":"2a9d414817265ee2","page":"","ui":"","name":"HBar Chart Example","order":3,"width":"2","height":"1","head":"","format":"<template>\n <div >\n <div class = \"chart-title\">{{chartTitle}}</div>\n <div class = \"chart-container\"><canvas ref=\"chart\"/></div>\n </div> \n\n</template>\n\n<script src=\"https://cdn.jsdelivr.net/npm/chart.js\"></script>\n\n<script>\n export default {\n data() {\n // define variables available component-wide\n // (in <template> and component functions)\n return {\n isChartLoaded: false,\n seriesCount: 0,\n\n chartType: 'HBar', // This is what the widget was designed for, so untested as a vertical bar chart\n isStacked: false, // false for a non-stacked H Bar chart\n \n /******** These options can be dynamically edited ********\n * Use msg.ui_update.<option name>\n */\n chartTitle: 'Hourly Energy Useage',\n\n // Note that these are the reverse of a normal Bar chart\n yAxisKey: 'period', // String value - label, 'y' is the default\n xAxisKey: 'value', // Number value - data, 'x' is the default\n seriesKey: '', // '' is the default (none). Can be 'topic' or a\n // key in the incoming data\n\n xAxisMin: 0,\n xAxisMax: 0.5,\n\n yAxisTitle: '',\n xAxisTitle: '',\n\n /**********************************************************/\n\n isLegendDisplay: false,\n autoSkipOn: false,\n\n dataSets: [],\n \n }\n },\n\n watch: {\n msg: function () {\n if ( Object.hasOwn(this.msg, 'ui_update') ) {\n if (this.msg.ui_update?.chartTitle) this.setChartTitle(this.msg.ui_update.chartTitle)\n\n if (this.isChartLoaded) {\n this.setDynamicOptions(this.msg.ui_update)\n }\n\n } \n\n if (this.isChartLoaded) {\n if (this.msg.payload !== undefined) this.onInput(this.msg)\n\n }\n \n }\n },\n \n computed: {\n // Colours for the first 7 entries in both stacked and non-stacked charts\n backgroundColours() {\n let backgroundColour = [\n 'rgba(255, 99, 132, 1.0)',\n 'rgba(171, 199, 232, 1.0)',\n 'rgba(255, 127, 14, 1.0)',\n 'rgba(44, 160, 44, 1.0)',\n 'rgba(152, 223, 158, 1.0)',\n 'rgba(214, 39, 40, 1.0)',\n 'rgba(255, 155, 150, 1.0)',\n ]\n\n return backgroundColour\n \n },\n \n borderColours() {\n let borderColour = [\n 'rgb(255, 99, 132)',\n 'rgb(171, 199, 232)',\n 'rgb(255, 127, 14)',\n 'rgb(44, 160, 44)',\n 'rgb(152, 223, 158)',\n 'rgb(214, 39, 40)',\n 'rgb(255, 155, 150)',\n\n ]\n\n return borderColour\n \n },\n\n },\n\n methods: {\n // Expose a method to our <template> and Vue Application\n setDynamicOptions(uiOptions) {\n const chartOptions = this.chart.options\n for (const [option, value] of Object.entries(uiOptions)) {\n let optionKey = ''\n let update = {}\n if (option === 'xAxisTitle') {\n optionKey = chartOptions.scales.x.title\n update = {display: true, text: value}\n\n } \n\n if (option === 'yAxisTitle') {\n optionKey = chartOptions.scales.y.title\n update = {display: true, text: value}\n\n }\n\n if (option === 'xAxisMin') {\n chartOptions.scales.x.min = value\n\n }\n\n if (option === 'xAxisMax') {\n chartOptions.scales.x.max = value\n\n }\n\n if (option === 'yAxisKey') {\n chartOptions.parsing.yAxisKey = value\n\n }\n\n if (option === 'xAxisKey') {\n chartOptions.parsing.xAxisKey = value\n\n }\n\n updateOption(update, optionKey)\n\n }\n\n if (this.chart) this.chart.update()\n\n // Local function for updates requiring more than one entry in chart options to be updated\n function updateOption( update, optionKey) {\n for (const [key, value] of Object.entries(update)) {\n optionKey[key] = value\n\n }\n\n }\n\n },\n\n setChartTitle(title) {\n this.chartTitle = title\n\n },\n \n draw() {\n const ctx = this.$refs.chart\n \n // Render the chart\n const chart = new Chart(ctx, {\n type: 'bar',\n data: {\n labels: [],\n datasets: [{\n label: '',\n axis: 'y',\n data: [],\n backgroundColor: this.backgroundColours,\n\n borderColor: this.borderColors,\n\n borderWidth: 1,\n\n }]\n },\n\n options: {\n indexAxis: 'y',\n plugins: {\n legend: {\n display: this.isLegendDisplay,\n\n },\n \n }, \n \n elements: {\n bar: {\n borderWidth: 1,\n }\n },\n\n parsing: {\n xAxisKey: this.yAxisKey,\n yAxisKey: this.xAxisKey\n\n },\n\n maintainAspectRatio: false, // Let the chart take the shape of the container\n animation: false, // Do not animate\n responsive: true, // - and please be responisve\n \n scales: {\n y: {\n title: {display: (this.yAxisTitle === '') ? false : true,\n text: this.yAxisTitle,\n align: 'center',\n },\n\n ticks: {\n autoSkip: this.autoSkipOn, // Turn on/off automatic tick count calculations\n },\n stacked: this.isStacked, // Is this a 'stacked' bar chart\n },\n\n x: {\n title: {display: (this.xAxisTitle ==='') ? false : true,\n text: this.xAxisTitle,\n align: 'center',\n },\n beginAtZero: true,\n min: this.xAxisMin,\n max: this.xAxisMax,\n stacked: this.isStacked,\n }\n }\n }\n })\n\n // Make this available to all elements of the component\n this.chart = chart\n },\n\n onInput(inputData) {\n const chartData = inputData.payload\n const chartTopic = (inputData?.topic) ?? ''\n\n if (Array.isArray(chartData)) {\n if (chartData.length === 0) {\n this.clearChart()\n\n return\n \n } else {\n\n // Configure the dataSets property as the chart datasets and clear any data values\n this.dataSets = this.chart.data.datasets\n this.dataSets.forEach(element => element.data = [])\n\n // For each item in array add the data to proper dataset\n // Series key defaults to ''\n // the data has format [{seriesKey: <string>, xAxisKey: <string>, yAxisKey: <number>}, and many more of such ...]\n chartData.forEach(point => {\n \n let dataSetName = (this.seriesKey === 'topic') ? chartTopic : ''\n let x = ''\n let y = 0\n \n if (this.seriesKey === '' || this.seriesKey === 'topic') {\n ({ [this.xAxisKey]: x, [this.yAxisKey]: y } = point)\n\n } else {\n ({ [this.seriesKey]: dataSetName, [this.xAxisKey]: x, [this.yAxisKey]: y } = point)\n\n }\n\n // If an HBar chart the incoming x & y axis data is swapped on the chart\n if (this.chartType === 'HBar') {\n this.addDataPoint(dataSetName, {[this.xAxisKey]: y, [this.yAxisKey]: x})\n\n } else {\n this.addDataPoint(dataSetName, {[this.xAxisKey]: x, [this.yAxisKey]: y})\n\n }\n\n })\n\n this.updateChart()\n \n }\n \n } else {\n this.send({error: `H Bar Chart requires an Array of Objects`})\n }\n\n },\n\n addDataPoint(dataSetName, data) {\n let dataSet = this.dataSets.find(chartSet => chartSet.label === dataSetName)\n if (!dataSet) {\n if (this.seriesCount === 0) {\n this.dataSets[0].label = dataSetName\n dataSet = this.dataSets[0]\n\n } else {\n dataSet = this.addDataset(dataSetName)\n \n this.dataSets.push(dataSet)\n\n }\n\n }\n\n this.seriesCount++\n\n dataSet.data.push(data)\n\n },\n\n updateChart() {\n if (this.chart) {\n this.chart.data.datasets = this.dataSets\n\n this.chart.update()\n }\n\n },\n\n clearChart() {\n if (this.dataSets.length > 0) {\n this.dataSets.forEach(element => element.data = [])\n\n this.updateChart()\n\n }\n\n },\n\n // Add a copy of the first dataset\n addDataset(dataSetName) {\n let dataSet = {...this.chart.data.datasets[0]}\n\n dataSet.label = dataSetName\n\n if (this.isStacked) {\n dataSet.backgroundColor = this.backgroundColours[this.dataSets.length % 6]\n dataSet.borderColor = this.borderColours[this.dataSets.length % 6]\n\n }\n\n return dataSet\n \n },\n\n },\n \n mounted() {\n // Code here when the component is first loaded\n let interval = setInterval(() => {\n if (window.Chart) {\n // The chart.js is loaded, so we can now use it\n clearInterval(interval)\n \n this.draw()\n\n this.isChartLoaded = true\n \n }\n\n }, 100)\n\n },\n\n unmounted() {\n // Code here when the component is removed from the Dashboard\n // i.e. when the user navigates away from the page\n }\n \n }\n</script>\n\n<style scoped>\n .chart-container {\n height: 425px;\n position: relative;\n font-size: 1.4rem\n\n }\n\n .chart-title{\n text-align:center;\n\n }\n\n .centre-text {\n text-align: center;\n font-size: 0.8rem; \n }\n\n</style>","storeOutMessages":true,"passthru":false,"resendOnRefresh":true,"templateScope":"local","className":"","x":1400,"y":1280,"wires":[["8350eb4dabc47a74"]],"info":".base-container{\r\n width:100%;\r\n height: 100%;\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n\r\n}\r\n.chart-container{\r\n position: relative;\r\n margin: auto;\r\n height: 100cqb; /*use container query units to give full height of the container (100% of container height) */\r\n width: 100cqi; /*use container query units to give full width of the container (100% of container height)*/\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n}"},{"id":"71bfd7bb53c60e4a","type":"inject","z":"fe7296cf2a5f7fba","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[{\"period\":\"Wed\",\"value\":0.5753218637843528},{\"period\":\"Thu\",\"value\":0.4568307622663269},{\"period\":\"Fri\",\"value\":0.48251723337081315},{\"period\":\"Sat\",\"value\":0.6762580781813892},{\"period\":\"Sun\",\"value\":0.5352040556527177},{\"period\":\"Mon\",\"value\":0.21310310680635436},{\"period\":\"Tue\",\"value\":0.49344858364974303}]","payloadType":"jsonata","x":1110,"y":1280,"wires":[["84c4a8c53f4017b3"]]},{"id":"5701a3fd6d727d3f","type":"inject","z":"fe7296cf2a5f7fba","name":"","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":1110,"y":1360,"wires":[["6ed4ba4423b95f71"]]},{"id":"6ed4ba4423b95f71","type":"change","z":"fe7296cf2a5f7fba","name":"Set Chart \\n Properties","rules":[{"t":"set","p":"ui_update.chartTitle","pt":"msg","to":" Daily Energy Useage","tot":"str"},{"t":"set","p":"ui_update.xAxisMax","pt":"msg","to":"5","tot":"num"}],"action":"","property":"","from":"","to":"","reg":false,"x":1310,"y":1360,"wires":[["84c4a8c53f4017b3"]]},{"id":"2a9d414817265ee2","type":"ui-group","name":"HBar Chart","page":"f14e4c53d983b36e","width":"4","height":"1","order":2,"showTitle":true,"className":"","visible":"true","disabled":"false","groupType":"default"},{"id":"f14e4c53d983b36e","type":"ui-page","name":"Scope Example","ui":"80f2e5f9dbf80780","path":"/page10","icon":"home","layout":"grid","theme":"a5bbf4397c8aa75a","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":"80f2e5f9dbf80780","type":"ui-base","name":"Environment","path":"/dashboard","appIcon":"","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false,"showPageTitle":true,"navigationStyle":"default","titleBarStyle":"default"},{"id":"a5bbf4397c8aa75a","type":"ui-theme","name":"Default Theme","colors":{"surface":"#ffffff","primary":"#0094ce","bgPage":"#ffffff","groupBg":"#eeeeee","groupOutline":"#cccccc"},"sizes":{"pagePadding":"12px","groupGap":"6px","groupBorderRadius":"5px","widgetGap":"12px","density":"default"}}]