5e761d68b4
- Clear input value after confirming in the SaveConfigModal component - Add a check for undefined grid source before removing series in chart actions Signed-off-by: peterlu14 <peterlu810516@gmail.com>
1158 lines
42 KiB
JavaScript
1158 lines
42 KiB
JavaScript
import types from '@/store/modules/task/content/types'
|
|
import newDataZoom from '@/factories/chart/dataZoomFactory'
|
|
import newChart from '@/factories/chart/chartFactory'
|
|
import newTooltip from '@/factories/chart/tooltipFactory'
|
|
import newSeries from '@/factories/chart/seriesFactory'
|
|
import { dataStreamBuffer } from '@/data/task/DataStreamBuffer'
|
|
import { taskInfo } from '@/data/task/TaskInfo'
|
|
import global_ from '@/data/global/global'
|
|
|
|
import { IIRFilter } from '@/data/task/filter/IIRFilter.js'
|
|
|
|
import config from '@/data/config-table/index'
|
|
|
|
const typePath = types.chart
|
|
|
|
const chartActs = {
|
|
/**
|
|
* init chartData from cache (cache will only record non-default chart)
|
|
*
|
|
**/
|
|
[typePath.initCustomizedFromCache]: async ({ state, getters, commit, dispatch }, payload) => {
|
|
// const { controllerID } = payload
|
|
// const cacheData = await localforage.getItem('chart_data_' + controllerID)
|
|
// if (cacheData !== null) {
|
|
// // use ID to create new chart by cache
|
|
// for (const _chart of Object.values(cacheData)) {
|
|
// dispatch(typePath.add, {
|
|
// chartID: _chart.chartID,
|
|
// maxDots: _chart.maxDots,
|
|
// gridLength: _chart.grid.length,
|
|
// })
|
|
// for (let gridIndex = 0; gridIndex < _chart.grid.length; gridIndex++) {
|
|
// if (gridIndex in _chart.gridSource) {
|
|
// dispatch(typePath.registerFromCache, {
|
|
// chartID: _chart.chartID,
|
|
// gridIndex: gridIndex,
|
|
// gridSource: _chart.gridSource[gridIndex],
|
|
// })
|
|
// }
|
|
// }
|
|
// state.chartData[_chart.chartID].mappingID = _chart.mappingID
|
|
// state.chartData[_chart.chartID].createdBy = 'manual'
|
|
// }
|
|
// dispatch(typePath.refreshPanel)
|
|
// }
|
|
},
|
|
/**
|
|
* set chartData to cache (cache will only record non-default chart)
|
|
*
|
|
*/
|
|
[typePath.saveToCache]: async ({ state, getters, commit, dispatch }, payload) => {
|
|
// const { controllerID } = payload
|
|
// const _chartData = {}
|
|
// for (const [_id, _chart] of Object.entries(state.chartData)) {
|
|
// // manual chart clear series data
|
|
// if (_chart.createdBy === 'manual') {
|
|
// _chartData[_id] = JSON.parse(JSON.stringify(_chart))
|
|
// _chartData[_id].series.map((ele) => { ele.data.length = 0 })
|
|
// }
|
|
// }
|
|
// await localforage.setItem('chart_data_' + controllerID, _chartData)
|
|
},
|
|
/*
|
|
* add chartID to chartData
|
|
* @param {String} chartID
|
|
* @param {Number} maxDots
|
|
* @param {Number} gridLength
|
|
* @return {object} : chart data
|
|
*/
|
|
[typePath.add]: ({ state, getters, commit, dispatch }, payload) => {
|
|
const { chartID, maxDots, gridLength } = payload
|
|
if (state.chartData[chartID] == null) {
|
|
state.chartData[chartID] = newChart(chartID, Object.keys(state.chartData).length)
|
|
state.chartData[chartID].maxDots = maxDots
|
|
state.chartData[chartID].tooltip = newTooltip()
|
|
dispatch(typePath.updateGridList, { chartID: chartID, length: gridLength })
|
|
return state.chartData[chartID]
|
|
} else {
|
|
return state.chartData[chartID]
|
|
}
|
|
},
|
|
|
|
/**
|
|
* register source channel setting from cache
|
|
*
|
|
* @param {String} chartID
|
|
* @param {Number} gridIndex
|
|
* @param {Object} gridSource
|
|
*/
|
|
[typePath.registerFromCache]: ({ state, getters, commit, dispatch }, payload) => {
|
|
const { chartID, gridIndex, gridSource } = payload
|
|
// one gridSource may have multiple axis (i.e. 疊圖)
|
|
gridSource.forEach(grid => {
|
|
const xAxisInfo = parseGridSourceAxis(grid.xSource)
|
|
const yAxisInfo = parseGridSourceAxis(grid.ySource)
|
|
dispatch(typePath.register, {
|
|
chartID: chartID,
|
|
gridIndex: gridIndex,
|
|
deviceNameX: xAxisInfo.deviceName,
|
|
deviceIDX: xAxisInfo.deviceID,
|
|
deviceX: xAxisInfo,
|
|
channelX: xAxisInfo.channel,
|
|
deviceNameY: yAxisInfo.deviceName,
|
|
deviceIDY: yAxisInfo.deviceID,
|
|
deviceY: yAxisInfo,
|
|
channelY: yAxisInfo.channel,
|
|
formula: null,
|
|
})
|
|
})
|
|
},
|
|
/**
|
|
* register device-channel to chartData
|
|
*
|
|
* @param {String} chartID
|
|
* @param {Number} gridIndex
|
|
* @param {String} deviceNameX
|
|
* @param {String} deviceIDX
|
|
* @param {Number} channelX
|
|
* @param {String} deviceNameY
|
|
* @param {String} deviceIDY
|
|
* @param {Number} channelY
|
|
* @param {String} formula
|
|
* @return {object} : new registered chart
|
|
*/
|
|
[typePath.register]: ({ state, getters, commit, dispatch }, payload) => {
|
|
const { chartID, gridIndex, deviceNameX, deviceIDX, deviceX, channelX, deviceNameY, deviceIDY, deviceY, channelY, formula } = payload
|
|
console.log('register', chartID, gridIndex, deviceNameX, deviceIDX, deviceX, channelX, deviceNameY, deviceIDY, deviceY, channelY, formula)
|
|
const sourceList = [
|
|
{
|
|
name: deviceNameX,
|
|
mac_address: (deviceX) ? deviceX.mac_address : deviceX,
|
|
channel: channelX,
|
|
},
|
|
{
|
|
name: deviceNameY,
|
|
mac_address: (deviceY) ? deviceY.mac_address : deviceY,
|
|
channel: channelY,
|
|
},
|
|
]
|
|
if (state.chartData[chartID] == null) {
|
|
return false
|
|
}
|
|
|
|
let xAxisType
|
|
let yAxisType
|
|
let xAxisSource
|
|
let yAxisSource
|
|
// formulaSource
|
|
const formulaSource = []
|
|
let legendName
|
|
|
|
if (formula == null) {
|
|
legendName = getLegendName(sourceList, formula, state.deviceListNew)
|
|
} else {
|
|
legendName = getLegendName(sourceList, formula.id, state.deviceListNew)
|
|
}
|
|
|
|
if (formula == null) {
|
|
switch (sourceList[0].name) {
|
|
case 'Time':
|
|
xAxisType = 'time'
|
|
xAxisSource = ['Time', 'Time']
|
|
break
|
|
default:
|
|
xAxisType = 'value'
|
|
xAxisSource = [sourceList[0].mac_address, sourceList[0].channel]
|
|
break
|
|
}
|
|
switch (sourceList[1].name) {
|
|
case 'Time':
|
|
yAxisType = 'time'
|
|
yAxisSource = [-1, -1]
|
|
break
|
|
default:
|
|
yAxisType = 'value'
|
|
yAxisSource = [sourceList[1].mac_address, sourceList[1].channel]
|
|
break
|
|
}
|
|
} else {
|
|
sourceList.forEach(source => {
|
|
formulaSource.push([source.id, source.channel])
|
|
})
|
|
xAxisType = 'time'
|
|
yAxisType = 'value'
|
|
}
|
|
|
|
// register legendName
|
|
if (state.chartData[chartID].legend.data.indexOf(legendName) >= 0) {
|
|
// have been registered
|
|
return false
|
|
}
|
|
state.chartData[chartID].legend.data.push(legendName)
|
|
|
|
// if AxisIndex != -1 then add new axis into list
|
|
let _xAxisIndex = -1
|
|
let _yAxisIndex = -1
|
|
// the grid have exist, so just use the axis in grid
|
|
for (let i = 0; i < state.chartData[chartID].xAxis.length; i++) {
|
|
if (state.chartData[chartID].xAxis[i].gridIndex === gridIndex) {
|
|
_xAxisIndex = i
|
|
}
|
|
}
|
|
for (let i = 0; i < state.chartData[chartID].yAxis.length; i++) {
|
|
if (state.chartData[chartID].yAxis[i].gridIndex === gridIndex) {
|
|
_yAxisIndex = i
|
|
}
|
|
}
|
|
// create new axis
|
|
if (_xAxisIndex === -1) {
|
|
state.chartData[chartID].xAxis.push(makeXAxis(gridIndex, xAxisType, channelX))
|
|
_xAxisIndex = state.chartData[chartID].xAxis.length - 1
|
|
}
|
|
if (_yAxisIndex === -1) {
|
|
state.chartData[chartID].yAxis.push(makeYAxis(gridIndex, yAxisType, channelY))
|
|
_yAxisIndex = state.chartData[chartID].yAxis.length - 1
|
|
}
|
|
|
|
state.chartData[chartID].chartXMode = state.chartData[chartID].xAxis[_xAxisIndex].type
|
|
state.chartData[chartID].chartYMode = state.chartData[chartID].yAxis[_yAxisIndex].type
|
|
|
|
// update dataZoom AxisIndex list
|
|
const dataZoomXAxisIndex = Array.apply(null, { length: state.chartData[chartID].xAxis.length }).map(Number.call, Number)
|
|
const dataZoomYAxisIndex = Array.apply(null, { length: state.chartData[chartID].yAxis.length }).map(Number.call, Number)
|
|
if (state.chartData[chartID].dataZoom == null) {
|
|
state.chartData[chartID].dataZoom = newDataZoom()
|
|
}
|
|
state.chartData[chartID].dataZoom[0].xAxisIndex = dataZoomXAxisIndex
|
|
state.chartData[chartID].dataZoom[1].xAxisIndex = dataZoomXAxisIndex
|
|
state.chartData[chartID].dataZoom[2].yAxisIndex = dataZoomYAxisIndex
|
|
|
|
// register series
|
|
const _series = newSeries('line', legendName, _xAxisIndex, _yAxisIndex, xAxisSource, yAxisSource, formula, formulaSource)
|
|
// const _seriesIndex = state.chartData[chartID].series.length
|
|
state.chartData[chartID].series.push(_series)
|
|
|
|
// state.chartData[chartID].xAxis[_xAxisIndex].seriesIndex.push(_seriesIndex)
|
|
// state.chartData[chartID].yAxis[_yAxisIndex].seriesIndex.push(_seriesIndex)
|
|
|
|
// state.chartData[chartID].xAxis[_xAxisIndex].boundaryGap = [0, '5%']
|
|
// state.chartData[chartID].yAxis[_yAxisIndex].boundaryGap = [0, '10%']
|
|
|
|
// console.log(state.chartData[chartID])
|
|
// set xAxis format
|
|
// TODO: need to get the time unit for the CanvasChart
|
|
|
|
// register series into grid
|
|
const _axisList = []
|
|
const sourceArray = []
|
|
|
|
if (formula == null) {
|
|
sourceList.forEach((source, index) => {
|
|
let _axis = 'X'
|
|
if (index !== 0) {
|
|
_axis = 'Y'
|
|
}
|
|
sourceArray.push(source)
|
|
switch (source.name) {
|
|
case 'Time':
|
|
_axisList.push(_axis + ' AXIS = TIME')
|
|
break
|
|
case 'Neulive':
|
|
dataStreamBuffer.register(source.id, source.channel)
|
|
_axisList.push(_axis + ' AXIS = NEULIVE ' + source.id + ' CHANNEL ' + source.channel)
|
|
break
|
|
case 'Elite':
|
|
dataStreamBuffer.register(chartID, source.mac_address, source.channel)
|
|
_axisList.push(_axis + ' AXIS = ELITE ' + source.id + ' CHANNEL ' + source.channel)
|
|
break
|
|
case 'EliteEIS':
|
|
// eslint-disable-next-line no-case-declarations
|
|
// const device = state.deviceListNew.find((ele) => String(ele.memory_board) === String(source.id))
|
|
// if (device === undefined) break
|
|
// if (Number(device.configuration.MODE) === 0 || Number(device.configuration.MODE) === 5) {
|
|
// state.chartData[chartID].series[state.chartData[chartID].series.length - 1].showSymbol = true
|
|
// state.chartData[chartID].series[state.chartData[chartID].series.length - 1].showAllSymbol = true
|
|
// }
|
|
dataStreamBuffer.register(chartID, source.mac_address, source.channel)
|
|
switch (source.channel) {
|
|
case 1:
|
|
_axisList.push(_axis + ' AXIS = ELITEEIS ' + source.id + ' CHANNEL ' + source.channel)
|
|
break
|
|
default:
|
|
_axisList.push(_axis + ' AXIS = ELITEEIS ' + source.id + ' CHANNEL ' + source.channel)
|
|
break
|
|
}
|
|
break
|
|
default:
|
|
dataStreamBuffer.register(chartID, source.mac_address, source.channel)
|
|
_axisList.push(_axis + ' AXIS = DEVICE ' + source.id + ' CHANNEL ' + source.channel)
|
|
break
|
|
}
|
|
})
|
|
} else {
|
|
_axisList.push('X AXIS = TIME')
|
|
_axisList.push('Y AXIS = FORMULA' + formula.id)
|
|
sourceList.forEach(source => {
|
|
dataStreamBuffer.register(chartID, source.id, source.channel)
|
|
})
|
|
}
|
|
|
|
const _gridSeries = {
|
|
name: legendName,
|
|
// xAxis: _axisList[0],
|
|
// yAxis: _axisList[1],
|
|
xSource: sourceArray[0],
|
|
ySource: sourceArray[1],
|
|
}
|
|
if (state.chartData[chartID].gridSource[gridIndex] == null) {
|
|
state.chartData[chartID].gridSource[gridIndex] = []
|
|
}
|
|
if (!state.chartData[chartID].gridSource[gridIndex].some(el => el.name === _gridSeries.name)) {
|
|
state.chartData[chartID].gridSource[gridIndex].push(_gridSeries)
|
|
}
|
|
|
|
// assign mappingID with device ID
|
|
if (deviceX) {
|
|
if (state.chartData[chartID].mappingID.indexOf(deviceX.mac_address) === -1 && deviceX.mac_address) {
|
|
state.chartData[chartID].mappingID.push(deviceX.mac_address)
|
|
}
|
|
}
|
|
if (deviceY) {
|
|
if (state.chartData[chartID].mappingID.indexOf(deviceY.mac_address) === -1 && deviceY.mac_address) {
|
|
state.chartData[chartID].mappingID.push(deviceY.mac_address)
|
|
}
|
|
}
|
|
return state.chartData[chartID]
|
|
},
|
|
/**
|
|
* update specific chart grid length
|
|
*
|
|
* @param {String} chartID
|
|
* @param {Number} length
|
|
*/
|
|
[typePath.updateGridList]: ({ state, getters, commit, dispatch }, payload) => {
|
|
const { chartID, length } = payload
|
|
state.chartData[chartID].grid.length = 0
|
|
for (let i = 0; i < length; i++) {
|
|
state.chartData[chartID].grid.push(makeGrid(i, length))
|
|
}
|
|
},
|
|
/**
|
|
* refresh chart data from data stream buffer
|
|
*
|
|
* @param {String} chartID
|
|
*/
|
|
[typePath.refreshData]: ({ state, getters, commit, dispatch }, payload) => {
|
|
const { chartID } = payload
|
|
if (state.chartData[chartID] == null) {
|
|
return false
|
|
}
|
|
|
|
// for each series registered
|
|
state.chartData[chartID].series.forEach(series => {
|
|
if (series.formula != null) {
|
|
let zeroBatch = false
|
|
series.formulaSource.forEach(source => {
|
|
if (dataStreamBuffer.getByChartID(chartID)[source[0]][source[1]].length === 0) {
|
|
zeroBatch = true
|
|
}
|
|
})
|
|
if (zeroBatch) {
|
|
series.formulaSource.forEach(source => {
|
|
if (dataStreamBuffer.getByChartID(chartID)[source[0]][source[1]].length > 0) {
|
|
dataStreamBuffer.merge(chartID, source[0], source[1])
|
|
}
|
|
})
|
|
return false
|
|
} else {
|
|
const popData = []
|
|
const indexNow = []
|
|
const indexArray = []
|
|
series.formulaSource.forEach((source, index) => {
|
|
const _popData = dataStreamBuffer.pop(chartID, source[0], source[1])
|
|
popData.push({
|
|
data: _popData,
|
|
indexRaw: index,
|
|
})
|
|
indexNow.push(0)
|
|
})
|
|
// form min to max
|
|
popData.sort((a, b) => {
|
|
if (Object.keys(a.data).length > Object.keys(b.data).length) {
|
|
return 1
|
|
} else {
|
|
return -1
|
|
}
|
|
})
|
|
popData.forEach((_popData, index) => {
|
|
if (popData[index + 1] != null) {
|
|
indexArray.push(getIndexArrayForBalenceSampleRate([], Object.keys(popData[index + 1].data).length - Object.keys(popData[index].data).length, Object.keys(popData[index].data).length))
|
|
}
|
|
})
|
|
let value
|
|
switch (series.formula.type) {
|
|
case 'Sheet Resistance':
|
|
for (let i = 0; i < popData[0].data.length; i++) {
|
|
let dataI
|
|
let dataV
|
|
dataV = popData[0].indexRaw === 0 ? popData[0].data[i].data : popData[1].data[indexNow[1]].data
|
|
dataI = popData[0].indexRaw === 1 ? popData[0].data[i].data : popData[1].data[indexNow[1]].data
|
|
// console.log(dataV, dataI, popData[0], popData[1])
|
|
// 4.5324 * 1000
|
|
value = parseFloat(dataV / dataI) * 4532.4
|
|
series.data.push([popData[1].data[indexNow[1]].time, value])
|
|
indexNow[1]++
|
|
|
|
if (series.dataYMax < value || series.dataYMax == null) {
|
|
series.dataYMax = value
|
|
}
|
|
if (series.dataYMin > value || series.dataYMin == null) {
|
|
series.dataYMin = value
|
|
}
|
|
|
|
indexArray.forEach(pattern => {
|
|
if ((i + 1) % pattern[0] === 0) {
|
|
for (let j = 0; j < pattern[1]; j++) {
|
|
if (popData[1].data[Object.keys(popData[1].data)[indexNow[1]]] != null) {
|
|
dataV = popData[0].indexRaw === 0 ? popData[0].data[i].data : popData[1].data[indexNow[1]].data
|
|
dataI = popData[0].indexRaw === 1 ? popData[0].data[i].data : popData[1].data[indexNow[1]].data
|
|
// console.log(dataV, dataI, popData[0], popData[1])
|
|
value = parseFloat(dataV / dataI) * 4532.4
|
|
series.data.push([popData[1].data[indexNow[1]].time, value])
|
|
indexNow[1]++
|
|
|
|
if (series.dataYMax < value || series.dataYMax == null) {
|
|
series.dataYMax = value
|
|
}
|
|
if (series.dataYMin > value || series.dataYMin == null) {
|
|
series.dataYMin = value
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
series.formula.dataNow = value.toFixed(3)
|
|
break
|
|
}
|
|
|
|
// series data list shift
|
|
if (series.data.length > state.chartData[chartID].maxDots) {
|
|
series.data.splice(0, series.data.length - state.chartData[chartID].maxDots)
|
|
}
|
|
|
|
// axis shift
|
|
if (series.data.length > 0) {
|
|
state.chartData[chartID].xAxis[series.xAxisIndex].max = series.data[series.data.length - 1][0]
|
|
state.chartData[chartID].xAxis[series.xAxisIndex].min = series.data[0][0]
|
|
const center = (series.dataYMax + series.dataYMin) / 2
|
|
const range = (series.dataYMax - series.dataYMin)
|
|
if (state.chartData[chartID].yAxis[series.yAxisIndex].max < center + (range * 2) || state.chartData[chartID].yAxis[series.yAxisIndex].max == null) {
|
|
state.chartData[chartID].yAxis[series.yAxisIndex].max = center + (range * 2)
|
|
}
|
|
if (state.chartData[chartID].yAxis[series.yAxisIndex].min > center - (range * 2) || state.chartData[chartID].yAxis[series.yAxisIndex].min == null) {
|
|
state.chartData[chartID].yAxis[series.yAxisIndex].min = center - (range * 2)
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if (series.xAxisSource[0] === 'Time' && series.yAxisSource[0] === 'Time') {
|
|
return false
|
|
}
|
|
if (series.xAxisSource[0] === 'Time' || series.yAxisSource[0] === 'Time') {
|
|
// x axis is time, y axis is value, so only need a device-channel data stream
|
|
let popData = []
|
|
|
|
// get raw deviceID
|
|
const rawDeviceIDX = series.xAxisSource[0]
|
|
const rawDeviceIDY = series.yAxisSource[0]
|
|
|
|
// pop data
|
|
if (series.xAxisSource[0] === 'Time') {
|
|
popData = dataStreamBuffer.pop(chartID, rawDeviceIDY, series.yAxisSource[1])
|
|
} else {
|
|
popData = dataStreamBuffer.pop(chartID, rawDeviceIDX, series.xAxisSource[1])
|
|
}
|
|
|
|
popData.forEach(data => {
|
|
// const _popData = data.data
|
|
let _popData = Number(data.data)
|
|
series.filter.forEach(filter => {
|
|
if (filter.filter != null) {
|
|
_popData = IIRFilter.filter(filter.filter, _popData)
|
|
}
|
|
})
|
|
const _popTime = data.time
|
|
// series data is empty or popdata time > last series data time
|
|
if (series.xAxisSource[0] === 'Time') {
|
|
if (_popTime < 0) {
|
|
series.data.push([])
|
|
} else {
|
|
series.data.push([_popTime, _popData])
|
|
// get max/min value
|
|
if (series.dataYMax < _popData || series.dataYMax == null) {
|
|
series.dataYMax = _popData
|
|
}
|
|
if (series.dataYMin > _popData || series.dataYMin == null) {
|
|
series.dataYMin = _popData
|
|
}
|
|
}
|
|
} else {
|
|
if (_popTime < 0) {
|
|
series.data.push([])
|
|
} else {
|
|
series.data.push([_popData, _popTime])
|
|
// get max/min value
|
|
if (series.dataXMax < _popData || series.dataXMax == null) {
|
|
series.dataXMax = _popData
|
|
}
|
|
if (series.dataXMin > _popData || series.dataXMin == null) {
|
|
series.dataXMin = _popData
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
// series data list shift by time
|
|
if (series.data.length > 0) {
|
|
if (series.xAxisSource[0] === 'Time') {
|
|
if (series.data.length > state.chartData[chartID].maxDots) {
|
|
series.data.splice(0, series.data.length - state.chartData[chartID].maxDots)
|
|
}
|
|
} else if (series.yAxisSource[0] === 'Time') {
|
|
if (series.data.length > state.chartData[chartID].maxDots) {
|
|
series.data.splice(0, series.data.length - state.chartData[chartID].maxDots)
|
|
}
|
|
}
|
|
}
|
|
|
|
// axis shift
|
|
if (series.data.length > 0) {
|
|
if (series.xAxisSource[0] === 'Time') {
|
|
state.chartData[chartID].xAxis[series.xAxisIndex].max = series.data[series.data.length - 1][0]
|
|
state.chartData[chartID].xAxis[series.xAxisIndex].min = series.data[0][0]
|
|
} else {
|
|
state.chartData[chartID].yAxis[series.yAxisIndex].max = series.data[series.data.length - 1][1]
|
|
state.chartData[chartID].yAxis[series.yAxisIndex].min = series.data[0][1]
|
|
}
|
|
}
|
|
} else {
|
|
// x axis and y axis is value, so need two device-channel data stream and align
|
|
// get real deviceID from controller
|
|
const rawDeviceIDX = series.xAxisSource[0]
|
|
const rawDeviceIDY = series.yAxisSource[0]
|
|
|
|
// get real deviceID fault
|
|
if (rawDeviceIDX == null || rawDeviceIDY == null) {
|
|
return false
|
|
}
|
|
if (dataStreamBuffer.getByChartID(chartID)[rawDeviceIDX][series.xAxisSource[1]].length === 0 &&
|
|
dataStreamBuffer.getByChartID(chartID)[rawDeviceIDY][series.yAxisSource[1]].length === 0) {
|
|
// all of buffer is empty
|
|
return false
|
|
} else if (dataStreamBuffer.getByChartID(chartID)[rawDeviceIDX][series.xAxisSource[1]].length === 0 ||
|
|
dataStreamBuffer.getByChartID(chartID)[rawDeviceIDY][series.yAxisSource[1]].length === 0) {
|
|
// merge all batch from the buffer not empty
|
|
// const xLarge = dataStreamBuffer.getByChartID(chartID)[rawDeviceIDX][series.xAxisSource[1]].length > dataStreamBuffer.getByChartID(chartID)[rawDeviceIDY][series.yAxisSource[1]].length
|
|
// if (xLarge) {
|
|
// dataStreamBuffer.merge(chartID, rawDeviceIDX, series.xAxisSource[1])
|
|
// } else {
|
|
// dataStreamBuffer.merge(chartID, rawDeviceIDY, series.yAxisSource[1])
|
|
// }
|
|
return false
|
|
}
|
|
// get data from buffer
|
|
const popDataX = dataStreamBuffer.pop(chartID, rawDeviceIDX, series.xAxisSource[1])
|
|
const popDataY = dataStreamBuffer.pop(chartID, rawDeviceIDY, series.yAxisSource[1])
|
|
|
|
// data align
|
|
let indexArray = []
|
|
if (Math.abs(popDataX.length - popDataY.length) < 10) {
|
|
const lenBatch = popDataX.length > popDataY.length ? popDataY.length : popDataX.length
|
|
for (let i = 0; i < lenBatch; i++) {
|
|
const _popDataX = (popDataX[i].data)
|
|
const _popDataY = (popDataY[i].data)
|
|
if (_popDataY != null) {
|
|
// [data1, data2, time]
|
|
series.data.push([_popDataX, _popDataY, parseInt(popDataX[i].time)])
|
|
if (series.dataXMax < _popDataX || series.dataXMax == null) {
|
|
series.dataXMax = _popDataX
|
|
}
|
|
if (series.dataXMin > _popDataX || series.dataXMin == null) {
|
|
series.dataXMin = _popDataX
|
|
}
|
|
if (series.dataYMax < _popDataY || series.dataYMax == null) {
|
|
series.dataYMax = _popDataY
|
|
}
|
|
if (series.dataYMin > _popDataY || series.dataYMin == null) {
|
|
series.dataYMin = _popDataY
|
|
}
|
|
} else {
|
|
// the batch Y is empty
|
|
break
|
|
}
|
|
}
|
|
} else {
|
|
const xLarge = popDataX.length > popDataY.length
|
|
const popDataLarge = popDataX.length > popDataY.length ? popDataX : popDataY
|
|
const popDataSmall = popDataX.length > popDataY.length ? popDataY : popDataX
|
|
// get index array to know what time we need to copy a data to align its
|
|
indexArray = getIndexArrayForBalenceSampleRate([], popDataLarge.length - popDataSmall.length, popDataSmall.length)
|
|
let _index = 0
|
|
for (let i = 0; i < popDataSmall.length; i++) {
|
|
let _popDataX = xLarge ? (popDataLarge[_index].data) : (popDataSmall[i].data)
|
|
let _popDataY = xLarge ? (popDataSmall[i].data) : (popDataLarge[_index].data)
|
|
// [data1, data2, time]
|
|
series.data.push([_popDataX, _popDataY, parseInt(popDataLarge[_index].time)])
|
|
_index++
|
|
|
|
if (series.dataXMax < _popDataX || series.dataXMax == null) {
|
|
series.dataXMax = _popDataX
|
|
}
|
|
if (series.dataXMin > _popDataX || series.dataXMin == null) {
|
|
series.dataXMin = _popDataX
|
|
}
|
|
if (series.dataYMax < _popDataY || series.dataYMax == null) {
|
|
series.dataYMax = _popDataY
|
|
}
|
|
if (series.dataYMin > _popDataY || series.dataYMin == null) {
|
|
series.dataYMin = _popDataY
|
|
}
|
|
|
|
indexArray.forEach(pattern => {
|
|
if ((i + 1) % pattern[0] === 0) {
|
|
for (let j = 0; j < pattern[1]; j++) {
|
|
_popDataX = xLarge ? (popDataLarge[_index].data) : (popDataSmall[i].data)
|
|
_popDataY = xLarge ? (popDataSmall[i].data) : (popDataLarge[_index].data)
|
|
// [data1, data2, time]
|
|
if (_popDataX != null) {
|
|
series.data.push([_popDataX, _popDataY, parseInt(popDataLarge[_index].data)])
|
|
_index++
|
|
if (series.dataXMax < _popDataX || series.dataXMax == null) {
|
|
series.dataXMax = _popDataX
|
|
}
|
|
if (series.dataXMin > _popDataX || series.dataXMin == null) {
|
|
series.dataXMin = _popDataX
|
|
}
|
|
if (series.dataYMax < _popDataY || series.dataYMax == null) {
|
|
series.dataYMax = _popDataY
|
|
}
|
|
if (series.dataYMin > _popDataY || series.dataYMin == null) {
|
|
series.dataYMin = _popDataY
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// series data list shift
|
|
if (series.data.length > state.chartData[chartID].maxDots) {
|
|
series.data.splice(0, series.data.length - state.chartData[chartID].maxDots)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
const popCycle = dataStreamBuffer.pop(chartID, state.chartData[chartID].mappingID, 4)
|
|
if (popCycle.length) {
|
|
state.chartData[chartID].cycle = popCycle.at(-1).data
|
|
}
|
|
},
|
|
/**
|
|
* generate default chart
|
|
*
|
|
* @param {Array} _deviceList
|
|
* @param {String} _itemID
|
|
* @param {String} _chartID
|
|
* @param {String} _type
|
|
* @return {bool}
|
|
*/
|
|
[typePath.defaultChartGenerator]: async ({ state, getters, commit, dispatch }, payload) => {
|
|
const { _deviceList, _itemID, _chartID, _type, _reset } = payload
|
|
let item
|
|
_deviceList.forEach(_device => {
|
|
if (_itemID === _device.id) {
|
|
item = _device
|
|
}
|
|
})
|
|
taskInfo.getFormulaList().forEach(_formula => {
|
|
if (_itemID === _formula.id) {
|
|
item = _formula
|
|
}
|
|
})
|
|
if (item == null) {
|
|
return false
|
|
}
|
|
|
|
const _chartRegisterDevice = (chartID, device) => {
|
|
if (device.library_name.includes('Elite')) {
|
|
const setting = global_.getDefaultChartTypeByMode(device.configuration.MODE, device.library_name)
|
|
const xType = setting[0]
|
|
let xValue = setting[1]
|
|
const yType = setting[2]
|
|
let yValue = setting[3]
|
|
const xDevice = xType === 'Time' ? undefined : device
|
|
const yDevice = yType === 'Time' ? undefined : device
|
|
if (xType === 'Time') xValue = 'Time'
|
|
if (yType === 'Time') yValue = 'Time'
|
|
dispatch(typePath.register, {
|
|
chartID: chartID,
|
|
gridIndex: 0,
|
|
deviceNameX: xType,
|
|
deviceIDX: device.id,
|
|
deviceX: xDevice,
|
|
channelX: xValue,
|
|
deviceNameY: yType,
|
|
deviceIDY: device.id,
|
|
deviceY: yDevice,
|
|
channelY: yValue,
|
|
formula: null,
|
|
})
|
|
state.chartData[chartID].chartDevice = 'elite'
|
|
state.chartData[chartID].chartMode = device.configuration.MODE
|
|
}
|
|
}
|
|
|
|
const _chartRegisterFormula = (chartID, formula) => {
|
|
switch (formula.type) {
|
|
case 'Sheet Resistance':
|
|
dispatch(typePath.register, {
|
|
chartID: chartID,
|
|
gridIndex: 0,
|
|
deviceNameX: 'Elite',
|
|
deviceIDX: formula.I_device.id,
|
|
channelX: 1,
|
|
deviceNameY: 'Elite',
|
|
deviceIDY: formula.V_device.id,
|
|
channelY: 2,
|
|
formula: formula,
|
|
})
|
|
break
|
|
}
|
|
}
|
|
|
|
// TODO remove old buffer register
|
|
if (_chartID != null) {
|
|
// update only one
|
|
|
|
dispatch(typePath.reset, {
|
|
chartID: _chartID,
|
|
mappingID: state.chartData[_chartID].mappingID,
|
|
cardName: state.chartData[_chartID].cardName,
|
|
})
|
|
state.chartData[_chartID].mappingID = item.id
|
|
switch (_type) {
|
|
case 'device':
|
|
_chartRegisterDevice(_chartID, item)
|
|
break
|
|
case 'formula':
|
|
_chartRegisterFormula(_chartID, item)
|
|
break
|
|
}
|
|
} else {
|
|
// update all
|
|
let isSetting = false
|
|
|
|
for (const chartID in state.chartData) {
|
|
if (state.chartData[chartID].mappingID.includes(item.mac_address)) {
|
|
if (state.chartData[chartID].createdBy === 'default') {
|
|
isSetting = true
|
|
dispatch(typePath.reset, {
|
|
chartID: chartID,
|
|
mappingID: state.chartData[chartID].mappingID,
|
|
cardName: state.chartData[chartID].cardName,
|
|
reset: _reset,
|
|
})
|
|
switch (_type) {
|
|
case 'device':
|
|
_chartRegisterDevice(chartID, item)
|
|
break
|
|
case 'formula':
|
|
_chartRegisterFormula(chartID, item)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (isSetting === false) {
|
|
return false
|
|
}
|
|
}
|
|
dispatch(typePath.refreshPanel)
|
|
return true
|
|
},
|
|
/**
|
|
* reset chart option
|
|
*
|
|
* @param {String} chartID
|
|
* @param {String} mappingID
|
|
* @param {String} cardName
|
|
*/
|
|
[typePath.reset]: ({ state, getters, commit, dispatch }, payload) => {
|
|
const { chartID, mappingID, cardName, reset } = payload
|
|
state.chartData[chartID].legend.data.length = 0
|
|
state.chartData[chartID].xAxis.length = 0
|
|
state.chartData[chartID].yAxis.length = 0
|
|
state.chartData[chartID].gridSource.length = 0
|
|
if (reset === undefined) {
|
|
if (state.chartData[chartID].series.data != null) {
|
|
state.chartData[chartID].series.data.length = 0
|
|
}
|
|
state.chartData[chartID].series.length = 0
|
|
}
|
|
if (mappingID == null) {
|
|
state.chartData[chartID].mappingID = []
|
|
}
|
|
if (cardName == null) {
|
|
state.chartData[chartID].cardName = 'Chart' + String(chartID)
|
|
}
|
|
state.chartData[chartID].dataZoom = null
|
|
dispatch(typePath.updateGridList, { chartID: chartID, length: 1 })
|
|
},
|
|
|
|
/**
|
|
* reset chart option
|
|
*
|
|
* @param {String} chartID
|
|
* @param {String} mappingID
|
|
* @param {String} cardName
|
|
*/
|
|
[typePath.resetSeriesData]: ({ state, getters, commit, dispatch }, payload) => {
|
|
const { chartID, mappingID } = payload
|
|
|
|
// check chart need to reset or not
|
|
if (String(mappingID) === String(state.chartData[chartID].mappingID)) {
|
|
// reset series data
|
|
for (const i in state.chartData[chartID].series) {
|
|
state.chartData[chartID].series[i].data.length = 0
|
|
}
|
|
// reset databuffer
|
|
const databuffer = dataStreamBuffer.getByChartID(chartID)
|
|
if (databuffer) {
|
|
for (const device in databuffer) {
|
|
// console.log('device', device)
|
|
for (const channel in databuffer[device]) {
|
|
// console.log('channel', channel, databuffer[device][channel])
|
|
databuffer[device][channel].length = 0
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
/**
|
|
* refresh (key++) EChart
|
|
*
|
|
*/
|
|
[typePath.refreshChart]: async ({ state, getters, commit, dispatch }, payload) => {
|
|
// to avoid overflow
|
|
state.taskContentChartKey < 1000 ? state.taskContentChartKey++ : state.taskContentChartKey = 0
|
|
},
|
|
/**
|
|
* refresh (key++) chart panel (i.e. EChart list)
|
|
*
|
|
*/
|
|
[typePath.refreshPanel]: async ({ state, getters, commit, dispatch }, payload) => {
|
|
// to avoid overflow
|
|
state.taskContentChartPanelKey < 1000 ? state.taskContentChartPanelKey++ : state.taskContentChartPanelKey = 0
|
|
},
|
|
[typePath.removeGridSource]: async ({ state, getters, commit, dispatch }, payload) => {
|
|
const { chartID, gridIndex, gridSourceIndex } = payload
|
|
|
|
const gridSource = state.chartData[chartID].gridSource[gridIndex][gridSourceIndex]
|
|
if (gridSource === undefined) return
|
|
// remove series
|
|
const seriesIndex = state.chartData[chartID].series.findIndex((val) => {
|
|
return val.name === gridSource.name
|
|
})
|
|
state.chartData[chartID].series.splice(seriesIndex, 1)
|
|
// remove legend
|
|
state.chartData[chartID].legend.data = state.chartData[chartID].legend.data.filter((val) => {
|
|
return val !== gridSource.name
|
|
})
|
|
// remove xAxis, yAxis seriesIndex
|
|
// state.chartData[chartID].xAxis[gridIndex].seriesIndex = state.chartData[chartID].xAxis[gridIndex].seriesIndex.filter((val) => {
|
|
// console.log(val, seriesIndex, val !== seriesIndex)
|
|
// return val !== seriesIndex
|
|
// })
|
|
// state.chartData[chartID].yAxis[gridIndex].seriesIndex = state.chartData[chartID].yAxis[gridIndex].seriesIndex.filter(ele => ele !== seriesIndex)
|
|
// remove gridsource
|
|
state.chartData[chartID].gridSource[gridIndex].splice(gridSourceIndex, 1)
|
|
state.chartData[chartID].gridSource = JSON.parse(JSON.stringify(state.chartData[chartID].gridSource))
|
|
checkMappingID(state.chartData[chartID])
|
|
},
|
|
[typePath.removeGrid]: async ({ state, getters, commit, dispatch }, payload) => {
|
|
const { chartID, gridIndex } = payload
|
|
// remove all grid source & series
|
|
if (state.chartData[chartID].gridSource[gridIndex]) {
|
|
for (const gridSource in state.chartData[chartID].gridSource[gridIndex]) {
|
|
await dispatch(typePath.removeGridSource, {
|
|
chartID: chartID,
|
|
gridIndex: gridIndex,
|
|
gridSourceIndex: gridSource,
|
|
})
|
|
}
|
|
}
|
|
|
|
state.chartData[chartID].xAxis.splice(gridIndex, 1)
|
|
state.chartData[chartID].yAxis.splice(gridIndex, 1)
|
|
state.chartData[chartID].grid.splice(gridIndex, 1)
|
|
console.log('removeGrid!!!!', state.chartData[chartID])
|
|
},
|
|
[typePath.reLegendName]: async ({ state, getters, commit, dispatch }, payload) => {
|
|
const { chartID } = payload
|
|
// remove all grid source & series
|
|
getLegendName(state.chartData[chartID].sourceList, null, state.deviceListNew)
|
|
},
|
|
}
|
|
|
|
export default chartActs
|
|
|
|
function checkMappingID (chart) {
|
|
// check id is need to remove or not
|
|
const idList = [...chart.mappingID]
|
|
for (const id of idList) {
|
|
const result = checkGridSource(chart, id)
|
|
if (result === false) {
|
|
chart.mappingID = chart.mappingID.filter(ele => ele !== id)
|
|
}
|
|
}
|
|
}
|
|
|
|
function checkGridSource (chart, deviceID) {
|
|
for (const grid of chart.grid) {
|
|
for (const gridSource of chart.gridSource[grid.index]) {
|
|
if (gridSource.xSource.mac_address === deviceID) {
|
|
return true
|
|
}
|
|
if (gridSource.ySource.mac_address === deviceID) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
function parseGridSourceAxis (gridSourceAxis) {
|
|
// gridsource axis example:
|
|
// 'X AXIS = TIME'
|
|
// 'Y AXIS = NEULIVE 0 CHANNEL 2'
|
|
const deviceInfo = {
|
|
deviceName: '',
|
|
deviceID: '',
|
|
channel: '',
|
|
}
|
|
if (gridSourceAxis.name === 'Time') {
|
|
deviceInfo.deviceName = 'Time'
|
|
deviceInfo.deviceID = 'Time'
|
|
deviceInfo.channel = 'Time'
|
|
} else {
|
|
deviceInfo.deviceName = gridSourceAxis.name
|
|
deviceInfo.deviceID = gridSourceAxis.id
|
|
deviceInfo.channel = Number(gridSourceAxis.channel)
|
|
deviceInfo.mac_address = gridSourceAxis.mac_address
|
|
}
|
|
|
|
// const eliteChannel = ['CURRENT', 'VOLTAGE', 'RESISTANCE']
|
|
|
|
// const parseResult = gridSourceAxis.split(' = ')[1].split(' ')
|
|
// const _deviceName = parseResult[0]
|
|
// if (_deviceName === 'TIME') {
|
|
// deviceInfo.deviceName = 'Time'
|
|
// deviceInfo.deviceID = 'Time'
|
|
// deviceInfo.channel = 'Time'
|
|
// } else {
|
|
// switch (_deviceName) {
|
|
// case 'NEULIVE':
|
|
// deviceInfo.deviceName = 'Neulive'
|
|
// deviceInfo.deviceID = parseResult[1]
|
|
// deviceInfo.channel = parseInt(parseResult[3])
|
|
// break
|
|
// case 'ELITE':
|
|
// deviceInfo.deviceName = 'Elite'
|
|
// deviceInfo.deviceID = parseResult[1]
|
|
// deviceInfo.channel = eliteChannel.indexOf(parseResult[2]) + 1
|
|
// break
|
|
// case 'ELITEEIS':
|
|
// deviceInfo.deviceName = 'EliteEIS'
|
|
// deviceInfo.deviceID = parseResult[1]
|
|
// deviceInfo.channel = Number(parseResult[3])
|
|
// break
|
|
// default:
|
|
// break
|
|
// }
|
|
// }
|
|
return deviceInfo
|
|
}
|
|
|
|
function getIndexArrayForBalenceSampleRate (indexArray, dis, l) {
|
|
if (indexArray.length >= 3 || dis <= 0) {
|
|
return indexArray
|
|
}
|
|
const per = dis / l
|
|
if (per >= 1) {
|
|
indexArray.push([1, Math.floor(per)])
|
|
dis = dis - (l * Math.floor(per))
|
|
} else {
|
|
indexArray.push([Math.ceil(1 / per), 1])
|
|
dis = dis - Math.floor(l / Math.ceil(1 / per))
|
|
}
|
|
|
|
return getIndexArrayForBalenceSampleRate(indexArray, dis, l)
|
|
}
|
|
|
|
// generate x axis format
|
|
function makeXAxis (gridIndex, type, channel) {
|
|
return {
|
|
type: type,
|
|
name: '',
|
|
gridIndex: gridIndex,
|
|
axisLine: { onZero: false, lineStyle: { color: '#aaa' } },
|
|
axisTick: { show: false },
|
|
axisLabel: {
|
|
show: true,
|
|
showMinLabel: false,
|
|
showMaxLabel: false,
|
|
color: '#34495e',
|
|
// rotate: 45,
|
|
// margin: 12,
|
|
},
|
|
splitLine: { show: true, lineStyle: { color: '#aaa' } },
|
|
// minorSplitLine: {
|
|
// show: true,
|
|
// lineStyle: { color: '#eee' },
|
|
// },
|
|
// splitNumber: 20,
|
|
interval: null,
|
|
scale: true,
|
|
min: 'dataMin',
|
|
max: 'dataMax',
|
|
channel: channel,
|
|
seriesIndex: [],
|
|
}
|
|
}
|
|
|
|
// generate y axis format
|
|
function makeYAxis (gridIndex, type, channel) {
|
|
return {
|
|
type: type,
|
|
// // name: 'none',
|
|
// // nameLocation: 'center',
|
|
// // nameTextStyle: {
|
|
// // // align: 'left',
|
|
// // // verticalAlign: 'bottom',
|
|
// // },
|
|
gridIndex: gridIndex,
|
|
axisTick: { show: false },
|
|
axisLine: { show: false }, // color: '#ccc' } },
|
|
axisLabel: {
|
|
show: true,
|
|
color: '#34495e',
|
|
showMinLabel: false,
|
|
showMaxLabel: false,
|
|
},
|
|
splitLine: { show: true, lineStyle: { color: '#aaa' } },
|
|
// // minorSplitLine: {
|
|
// // show: true,
|
|
// // lineStyle: { color: '#eee' },
|
|
// // },
|
|
animation: false,
|
|
scale: true,
|
|
min: 'dataMin',
|
|
max: 'dataMax',
|
|
splitNumber: 3,
|
|
// minInterval: 1,
|
|
interval: null,
|
|
channel: channel,
|
|
seriesIndex: [],
|
|
}
|
|
}
|
|
|
|
// generate grid
|
|
function makeGrid (index, length) {
|
|
const _height = (80 / length) + '%'
|
|
const _top = index * (80 / length) + 5 + '%'
|
|
return {
|
|
index: index,
|
|
top: _top,
|
|
height: _height,
|
|
left: '10%',
|
|
right: '4%',
|
|
series: [],
|
|
}
|
|
}
|
|
|
|
// get legend name
|
|
function getLegendName (sourceList, formulaID, deviceList) {
|
|
let legendName = ''
|
|
if (formulaID == null) {
|
|
// source only 2 device
|
|
sourceList.forEach(source => {
|
|
let device = ''
|
|
let channelName = ''
|
|
switch (source.name) {
|
|
case 'Time':
|
|
legendName += 'T'
|
|
break
|
|
case 'Elite':
|
|
// eslint-disable-next-line no-case-declarations
|
|
device = deviceList.find((ele) => String(ele.mac_address) === String(source.mac_address))
|
|
if (device === undefined) break
|
|
// eslint-disable-next-line no-case-declarations
|
|
channelName = getConfigChannel(device.library_name, device.configuration.MODE, source.channel - 1)
|
|
if (!channelName) break
|
|
legendName += source.mac_address.slice(12, 18) + '-' + channelName.name.replace('_', '')
|
|
break
|
|
case 'EliteEIS':
|
|
// eslint-disable-next-line no-case-declarations
|
|
device = deviceList.find((ele) => String(ele.mac_address) === String(source.mac_address))
|
|
if (device === undefined) break
|
|
// eslint-disable-next-line no-case-declarations
|
|
channelName = getConfigChannel(device.library_name, device.configuration.MODE, source.channel - 1)
|
|
if (!channelName) break
|
|
if ([0, 5].indexOf(Number(device.configuration.MODE)) !== -1) {
|
|
legendName += source.mac_address.slice(12, 18) + '-' + channelName.name.replace('_', '')
|
|
break
|
|
}
|
|
legendName += source.mac_address.slice(12, 18) + '-' + channelName.name.replace('_', '')
|
|
break
|
|
default:
|
|
legendName += 'D' + source.id + 'CH' + source.channel
|
|
break
|
|
}
|
|
legendName += '_'
|
|
})
|
|
legendName = legendName.substr(0, legendName.length - 1)
|
|
} else {
|
|
legendName = 'T_F' + formulaID
|
|
}
|
|
return legendName
|
|
}
|
|
|
|
function getConfigChannel (library, mode, channel) {
|
|
return config.getChannelConfig(library, mode, channel)
|
|
}
|
|
|
|
export function getTimeFormatter (val) {
|
|
const hour = parseInt(val / 3.6e9)
|
|
const minute = parseInt((val % 3.6e9) / 1000000 / 60)
|
|
const second = parseInt((val % 6e7) / 1000000)
|
|
const millisecond = parseInt((val % 1e6) / 1000)
|
|
let label = ''
|
|
if (hour > 0) {
|
|
label += hour + ' h '
|
|
}
|
|
if (minute > 0) {
|
|
label += minute + ' m '
|
|
}
|
|
if (second > 0) {
|
|
label += second + ' s '
|
|
}
|
|
if (millisecond > 0) {
|
|
label += millisecond + ' ms '
|
|
}
|
|
return label
|
|
}
|