[Announce] @flowfuse/node-red-dashboard release version 1.30.0

The flowfuse dashboard has been released at 1.30.0. A summary of the numerous fixes and one or two enhancements can be found here.

The chart widget in particular has a number of fixes and also now you can access the underlying eCharts object via msg.ui_update. This adds great flexibility to the widget as virtually any of the vast number of eCharts options can be adjusted. For example, to move the yAxis scale to the right hand side, change the title font size and drop the top of the grid area down, msg.ui_update would be

{
    "chartOptions": {
        "yAxis": {
            "position": "right"
        },
        "grid": {
            "top": 90
        },
        "title": {
            "textStyle": {
                "fontSize": 20
            }
        }
    }
}

The changes are additive, so if it was also desired to move the bottom of the grid, after the above message was sent, then another msg.ui_update could be sent containing

{
    "chartOptions": {
        "grid": {
            "bottom": 200
        }
    }
}

Options available can be found in the eCharts documentation

There have also been some bug fixes, one to watch out for is the fact that there was a bug that caused the X axis time range limit not to be applied under some circumstances, particularly when adding historical data. The time limit is now correctly applied, but you may have been accidentally relying on the bug to show your data. If you find that data is no longer appearing then check the setting. It defaults to 1 hour. To completely disable the time limit it can be set to 0.

11 Likes

@Colin Many thanks for your good work on this.

1 Like

Prior to eCharts being adopted as the charting tool for Dashboard 2, I had created all my charts using the ui-template node in DB2. I'm trying to figure out a couple of things:

  1. Do I still need to include the source CDN (e.g. <script type="text/javascript" src="https://fastly.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>) in my ui-template or is there an easier/more efficient way to do it?

  2. One of the reasons for using eCharts (or Chart.js) was that the built in charting options had limited configuration/customization capability. With the ability to now set these via msg.ui_update would it make sense to use the built in charts vs ui-template? Below is an example of ui-template that I am currently using:

<template>
    <div ref="charttpw15" style="width: 100%; height: 100%"></div>
    <style>
        /* This rule targets the ECharts tooltip container and forces its styles */
        .echarts-tooltip-dark {
            background-color: #1C1C1C !important;
            color: #fff !important;
        }
    
        .echarts-tooltip-light {
            background-color: #1C1C1C !important;
            color: #fff !important;
        }
    </style>
</template>

<script type="text/javascript" src="https://fastly.jsdelivr.net/npm/echarts/dist/echarts.min.js"></script>

<script>
    export default {
    data() {
        return {
            myChart: null,
            resizeHandler: null,
        };
    },
    watch: {
        msg: function () {
            if (this.msg && this.msg.topic === "data" && this.myChart && this.msg.payload) {
                const p = this.msg.payload;
                this.update(p.labels, p, this.msg.title, this.msg.colors, this.msg.legend);
            }
        }
    },
    mounted() {
        this.$nextTick(() => {
            const ready = setInterval(() => {
                const container = this.$refs.charttpw15;
                if (window.echarts && container && container.offsetWidth > 0 && container.offsetHeight > 0) {
                    clearInterval(ready);
                    this.init();
                    // If data already exists at mount, draw immediately
                    if (this.msg && this.msg.topic === "data" && this.msg.payload) {
                        const p = this.msg.payload;
                        this.update(p.labels, p, this.msg.title, this.msg.colors, this.msg.legend);
                    }
                    // Setup resize listener
                    this.resizeHandler = () => { if (this.myChart) this.myChart.resize(); };
                    window.addEventListener('resize', this.resizeHandler);
                    // Optional: force resize after layout settles
                    setTimeout(() => { if (this.myChart) this.myChart.resize(); }, 300);
                }
            }, 100);
        });
    },
    unmounted() {  // <-- correct for Dashboard 2
        if (this.myChart) {
            this.myChart.dispose();
        }
        if (this.resizeHandler) {
            window.removeEventListener('resize', this.resizeHandler);
        }
    },
    methods: {
        init() {
            this.myChart = echarts.init(this.$refs.charttpw15, "dark");
            this.myChart.setOption({
                title: { text: "Waiting for data..." },
                textStyle: { fontFamily: 'Tahoma,sans-serif' },
                series: []
            });
        },
        update(xAxisData, datasets, title, colors, legendData) {
            this.myChart.setOption({
                textStyle: { fontFamily: 'Tahoma,sans-serif' },
                title: { text: title, 
                    left:'auto',
                    textStyle:{
                        fontWeight:'normal',
                        fontSize: 22,
                        },
                     },
                tooltip: {
                    trigger: 'item',
                    /*---Disabled options for tooltip---
                        axisPointer: {
                            type: 'cross',
                            label: {
                                backgroundColor: '#6a7985' // Optional: style the axis pointer label itself
                            }
                        },
                        backgroundColor: 'rgba(0,0,0,0.8)', // Semi-transparent black background
                        textStyle: {
                            color: '#fff' // White text
                        },
                        formatter: function (params) {
                            console.log('Tooltip params:', params); 
                            let tooltipContent = '';
                            params.forEach(function (item) {
                                if (item.seriesName) {
                                    tooltipContent += `${item.seriesName}: ${item.data ?? 'n/a'}<br>`;
                                }
                            });
                            return tooltipContent;
                        }
                    */    
                },
                legend: {
                    show: true,
                    data: legendData || [
                        { name: 'Home', icon: 'rect' },
                        { name: 'Solar', icon: 'rect' },
                        { name: 'Grid', icon: 'rect' },
                        { name: 'Battery', icon: 'rect' }
                    ],
                    textStyle: { fontSize: 16, fontWeight: 'bold' }
                },
                toolbox: { show: false },
                grid: { left: "3%", right: "4%", containLabel: true },
                xAxis: {
                    type: "category",
                    boundaryGap: false,
                    data: xAxisData,
                    name: 'Time',
                    nameGap: 30,
                    nameLocation: 'middle',
                    nameTextStyle: { fontWeight: 'bold', fontSize: 16 },
                    axisTick: { show: true },
                    axisLabel: { formatter: "{value}", color: 'white' },
                    axisLine: { lineStyle: { color: 'white', width: 2 } },
                    onZero: false
                },
                yAxis: {
                    type: "value",
                    name: 'kWh',
                    nameGap: 50,
                    nameLocation: 'middle',
                    nameTextStyle: { fontWeight: 'bold', fontSize: 16 },
                    axisLine: { show: true, lineStyle: { color: 'white', width: 2 } },
                    axisTick: { show: true },
                    axisLabel: { formatter: "{value} kWh", color: 'white' },
                    splitLine: { show: true, lineStyle: { color: '#333' } },
                    nameTextStyle: { fontSize: 16, fontWeight: 'bold' },
                    axisPointer: { snap: true },
                    scale: true,
                },
                series: [
                    {
                        name: 'Home',
                        type: 'line',
                        smooth: true,
                        connectNulls: true,
                        data: datasets.home || [],
                        lineStyle: { color: colors.home, width: 3, type: 'solid' },
                        showSymbol: false,
                    },
                    {
                        name: 'Solar',
                        type: 'bar',
                        connectNulls: true,
                        data: datasets.solar || [],
                        barMaxWidth: 40,
                        itemStyle: { color: colors.solar, borderColor: colors.solar, borderWidth: 1 },
                        stack: 'day'
                    },
                    {
                        name: 'Grid',
                        type: 'bar',
                        connectNulls: true,
                        data: datasets.grid || [],
                        barMaxWidth: 40,
                        itemStyle: { color: colors.grid, borderColor: colors.grid, borderWidth: 1 },
                        stack: 'day'
                    },
                    {
                        name: 'Battery',
                        type: 'bar',
                        connectNulls: true,
                        data: datasets.battery || [],
                        barMaxWidth: 40,
                        itemStyle: { color: colors.battery, borderColor: colors.battery, borderWidth: 1 },
                        stack: 'day'
                    }
                ]
            });
        }
    }
}
</script>

Thanks for your help.

I don't know, though this suggests that you do, I think. FR: Expose echarts (and perhaps other libs) to users for ui-template · Issue #1967 · FlowFuse/node-red-dashboard · GitHub

Give it a go and see.

1 Like

Thanks - I've set up notifications for that issue. Hopefully, it will get addressed in one of the updates :crossed_fingers:t3:

This is great.

Is it possible to create Echarts subplots in DB2 now? I use Echarts sublots to display multiple curves at the same page. This is useful when the inputs have different setups: some times two plots need to be shown, and sometime 4 plots need to be shown depending on the setup.

Everything (I believe) as shown on the Echarts example page works.
https://echarts.apache.org/examples/en/index.html#chart-type-line

I'm experimenting a lot these days. It's fantastic. Tons of options.