ChartJS 用真实的数据替换图表虚拟数据

kq0g1dla  于 11个月前  发布在  Chart.js
关注(0)|答案(1)|浏览(146)

工作中的一台机器发出状态数据,我想在水平条形图中显示这些数据。
状态可以是ACTIVESTOPPEDINTERRUPTED
真实的数据来自:current_array01.MTConnectStreams.Streams.DeviceStream.ComponentStream[8].Events.Execution,对象包含以下示例数据:

{
  "_dataItemId": "exec",
  "_timestamp": "2023-10-23T15:16:08.4531+02:00",
  "_name": "execution",
  "_sequence": "135616878",
  "__text": "STOPPED"
}

字符串
其中__text可以是上面解释的任何一种状态。真实的数据变量将以5秒的间隔被读取。正如@kikon所提到的,对象只有1个时间戳,而图表有2个。第二个时间戳将是原始时间戳+ 5秒,因此图表条目将相互跟进。下面是它应该如何看的例子。
x1c 0d1x的数据
我的虚拟数据图表看起来像这样:

// setup 
const data = {
  labels: ['Status'],
  datasets: [{
    label: 'ACTIVE',
    data: [
      {x: ['2023-01-01 00:00:00', '2023-01-01 01:00:00'], y: 'Status'},
      {x: ['2023-01-01 03:00:00', '2023-01-01 06:00:00'], y: 'Status'}
    ],
    backgroundColor: 'rgba(88,188,116,1)'
  },{
    label: 'STOPPED',
    data: [
      {x: ['2023-01-01 02:00:00', '2023-01-01 03:00:00'], y: 'Status'}
    ],
    backgroundColor: 'rgba(255,172,100,1)'
  },{
    label: 'INTERRUPTED',
    data: [
      {x: ['2023-01-01 01:00:00', '2023-01-01 02:00:00'], y: 'Status'},
      {x: ['2023-01-01 06:00:00', '2023-01-01 07:00:00'], y: 'Status'}
    ],
    backgroundColor: 'rgba(195,40,96,1)'
  }]
};

// config 
const config = {
  type: 'bar',
  data,
  options: {
    responsive: true,
    maintainAspectRatio: false,
    indexAxis: 'y',
    scales: {
      x: {
        type: 'time',
        time: {
          unit: 'hour'
        },
        min: '2023-01-01'
      },
      y: {
        beginAtZero: true,
        stacked: true,
        ticks: {
          display: false,
        }
      }
    },
    plugins: {
      tooltip: {
        enabled: false,
      }
    },
    layout: {
      padding: {
        top: 10,
        right: 20,
        bottom: 10,
        left: 20
      }
    },
  }
};

// render init block
const statusChart = new Chart(
  document.getElementById('statusChart01'),
  config
);


如何编辑代码,使其每秒获取真实的数据,并将其添加到图表中,最多添加100个条目,以便最旧的条目从图表中消失?

xghobddn

xghobddn1#

更新图表的一种简单而标准的方法是,只需更改配置对象中data.datasets对象的内容,然后调用chart.update()方法,其中chart是图表对象,是调用Chart构造函数的结果。
例如,在更新函数中,x轴可以作为chart.options.scales.x访问;它的属性也可以直接通过config.options.scales.x更改,其中config是配置对象,在唯一调用Chart构造函数(const chart = new Chart(config))时使用的参数。
从您的设置描述中得到的数据结构是queue,JavaScript Array通过其方法.push()(enqueue)和.shift()(dequeue)完成此操作,没有问题。然而,实际上有三个不同的数据集,每个颜色(状态)一个,并且知道在出队时从哪一个删除最左边的数据点需要一些编码,这些编码可能无法从文本描述中受益,因此最好将其留在下面的清单中。
模拟你描述的流设置很有趣,这是一个变量访问器(current_array01.MTConnectStreams.Streams.DeviceStream.ComponentStream[8].Events.Execution),每当评估时可能会返回一个不同的值(我设置为每5秒后随机更改(或不更改))。它在没有明显调用函数的情况下更改其值的事实是通过get语法完成的。
下面是代码的一个版本,在代码片段中:

const DATA_REFRESH_INTERVAL = 5000; // create new data point after DATA_REFRESH_INTERVAL ms
const POLL_INTERVAL = 1000; // poll the stream every POLL_INTERVAL ms
const MAX_DATA_POINTS = 30; // remove old data points if there are more than MAX_DATA_POINTS

// empty initial chart
const datasets = [{
    label: 'ACTIVE',
    data: [],
    backgroundColor: 'rgba(88,188,116,1)'
}, {
    label: 'STOPPED',
    data: [],
    backgroundColor: 'rgba(255,172,100,1)'
}, {
    label: 'INTERRUPTED',
    data: [],
    backgroundColor: 'rgba(195,40,96,1)'
}];

const config = {
    type: 'bar',
    data: {
        labels: ['Status'],
        datasets
    },
    options: {
        responsive: true,
        maintainAspectRatio: false,
        indexAxis: 'y',
        scales: {
            x: {
                type: 'time',
                time: {
                    unit: MAX_DATA_POINTS * DATA_REFRESH_INTERVAL > 120000 ? 'minute' : 'second',
                },
                afterBuildTicks(axis){ // filter out ticks that are not multiple of 10 seconds
                    axis.ticks = axis.ticks.filter(({value}) => value % 10000 < 1)
                },
                min: Date.now() - DATA_REFRESH_INTERVAL * 0.1, // -margin
                max: Date.now() + MAX_DATA_POINTS * DATA_REFRESH_INTERVAL +
                    DATA_REFRESH_INTERVAL * 0.1 // +margin
            },
            y: {
                beginAtZero: true,
                stacked: true,
                ticks: {
                    display: false,
                }
            }
        },
        plugins: {
            tooltip: {
                enabled: false,
            }
        },
        layout: {
            padding: {
                top: 10,
                right: 20,
                bottom: 10,
                left: 20
            }
        },
    }
};

// render init block
const statusChart = new Chart(
    document.getElementById('statusChart01'),
    config
);

////////////////////////////////////////////////////////////////////////////////////////////////////
// random stream simulator
// current_array01.MTConnectStreams.Streams.DeviceStream.ComponentStream[8].Events.Execution will evaluate
// to an object of structure specified in SO question, that changes (or not) randomly every 5 seconds
const current_array01 = {
    MTConnectStreams: {Streams: {DeviceStream: {ComponentStream: Array(9).fill(null)}}}
};
current_array01.MTConnectStreams.Streams.DeviceStream.ComponentStream[8] = {
    Events: Object.create(Object.prototype, {
        _currentText: {value: null, writable: true},
        _currentTimestamp: {value: null, writable: true},
        Execution: {
            get(){
                const now = Date.now();
                if(this._currentTimestamp === null || now - this._currentTimestamp > DATA_REFRESH_INTERVAL){
                    this._currentTimestamp = this._currentTimestamp ? this._currentTimestamp + DATA_REFRESH_INTERVAL : now;
                    const r = Math.random();
                    this._currentText = r < 1 / 3 ? "STOPPED" : r < 2 / 3 ? "ACTIVE" : "INTERRUPTED";
                }
                return {
                    "_dataItemId": "exec",
                    "_timestamp": new Date(this._currentTimestamp).toISOString(),
                    "_name": "execution",
                    "_sequence": "135616878",
                    "__text": this._currentText
                };
            }
        }
    })
};

const allDataPointsOrdered = [];
// keep a queue of all data points in time order, so we know which to remove at dequeue

const addItemToDatasets = ({_timestamp, __text}) => {
    const t0 = Date.parse(_timestamp);
    const dataPoint = {x: [t0, t0 + DATA_REFRESH_INTERVAL * 1.02], y: 'Status'};
    const datasetIndexToAddTo = datasets.findIndex(({label}) => label === __text);
    datasets[datasetIndexToAddTo].data.push(dataPoint)
    allDataPointsOrdered.push({__text, _timestamp});
    if(allDataPointsOrdered.length > MAX_DATA_POINTS){
        const {__text: typeToRemove, _timestamp: dateMin} = allDataPointsOrdered.shift();
        const datasetIndexToRemoveFrom = datasets.findIndex(({label}) => label === typeToRemove);
        datasets[datasetIndexToRemoveFrom].data.shift();
        const shiftXAxis = Date.parse(dateMin) + DATA_REFRESH_INTERVAL - config.options.scales.x.min;
        config.options.scales.x.min += shiftXAxis;
        config.options.scales.x.max += shiftXAxis;
    }
    infoUpdateNumberOfPoints(datasets.reduce((sum, dataset) => sum + dataset.data.length, 0));
    statusChart.clear();
    statusChart.update('none');
}

let current_timestamp = null;
const readStreamUpdateChart = () => {
    const obj = current_array01.MTConnectStreams.Streams.DeviceStream.ComponentStream[8].Events.Execution;
    if(obj._timestamp !== current_timestamp){ // new object was produced
        current_timestamp = obj._timestamp;
        addItemToDatasets(obj);
    }
};
let stopped = false;
const interval = setInterval(
    function(){
        if(!stopped){
            readStreamUpdateChart()
        }
    }, POLL_INTERVAL
);

function stopAll(){
    document.querySelector('#button_stop').setAttribute('disabled', 'd')
    stopped = true;
    clearInterval(interval);
}

function infoUpdateNumberOfPoints(txt){
    document.querySelector('#info').innerText = txt;
}

个字符

相关问题