Compare commits

...

3 Commits

Author SHA1 Message Date
Jordan Hsu e8d75b9877 [init] spike ui 2021-08-26 02:03:30 +08:00
Jordan Hsu 8b78cdcdbd Merge remote-tracking branch 'origin/release/0812_test' into feature/spike_UI 2021-08-20 16:08:37 +08:00
Jordan Hsu f9bf41980b [modify] neulive replay function name 2021-08-18 14:55:45 +08:00
35 changed files with 2444 additions and 80 deletions
+3
View File
@@ -18,6 +18,9 @@ import 'echarts/lib/chart/line'
import 'echarts/lib/component/dataZoom'
import 'echarts/lib/component/tooltip'
import 'echarts/lib/component/legend'
import 'echarts/lib/component/legend/ScrollableLegendModel.js'
import 'echarts/lib/component/legend/ScrollableLegendView.js'
import 'echarts/lib/component/legend/scrollableLegendAction.js'
import 'echarts/lib/component/axisPointer'
import 'echarts/lib/component/grid'
import 'echarts/lib/component/markArea'
@@ -256,7 +256,7 @@ export default {
this.chartOverviewData.series[0].markArea.data[0][1].xAxis = this.startTime + ((this.endTime - this.startTime) * (this.dataZoomEnd / 100))
this.zoomTime = 0
},
drawOverviewData: async function () {
drawInitData: async function () {
this.showChartLoading('overview')
for (const meta in this.dataInfo) {
let metaInfo
@@ -467,7 +467,7 @@ export default {
console.log(this.metaList)
console.log(this.dataInfo)
await this.register()
await this.drawOverviewData()
await this.drawInitData()
this.dataZoomTimer = setInterval(this.checkDataZoom, 1000)
this.$refs.top_control_panel.init()
this.$refs.set_min_max.init()
@@ -0,0 +1,96 @@
<template>
<div>
<va-card>
<p style="font-size: 125%; text-decoration: underline;"> Channel </p>
<channels/>
<p style="font-size: 125%; text-decoration: underline;"> Detect </p>
<p :style="{color: this.$themes.primary}" class="display-6"> High pass filter </p>
<cutoff-freq/>
<filter-order/>
<p :style="{color: this.$themes.danger}" class="display-6"> Threshold </p>
<threshold/>
<br/>
<p class="display-6"> Waveform </p>
<wave-length/>
<prethreshold/>
<refractory-period/>
<detect-button @loadChartData="loadChartData"/>
</va-card>
</div>
</template>
<script>
import Channels from '@/components/analysis/spike/item/Channels.vue'
import CutoffFreq from '@/components/analysis/spike/item/CutoffFreq.vue'
import FilterOrder from '@/components/analysis/spike/item/FilterOrder.vue'
import Prethreshold from '@/components/analysis/spike/item/Prethreshold.vue'
import RefractoryPeriod from '@/components/analysis/spike/item/RefractoryPeriod.vue'
import Threshold from '@/components/analysis/spike/item/Threshold.vue'
import WaveLength from '@/components/analysis/spike/item/WaveLength.vue'
import DetectButton from '@/components/analysis/spike/item/DetectButton.vue'
export default {
name: 'control-panel',
components: {
Channels,
CutoffFreq,
FilterOrder,
Prethreshold,
RefractoryPeriod,
Threshold,
WaveLength,
DetectButton,
},
props: {
},
async created () {
},
data () {
return {
}
},
mqtt: {
},
methods: {
init () {
},
refreshChart (options) {
this.$emit('refreshChart', options)
},
loadChartData () {
this.$emit('loadChartData')
},
},
// use watch because va-select with @change will have bug
watch: {
},
mounted () {
},
}
</script>
<style lang="scss">
.flex-container {
display: flex;
}
.flex-child {
flex: 2;
}
.flex-child1 {
flex: 1;
}
.flex-child2 {
flex: 0;
}
.flex-child:first-child {
margin-right: 1px;
}
.right {
float: right;
}
</style>
+354
View File
@@ -0,0 +1,354 @@
<template>
<div>
<div class="row row-equal">
<div class="flex md12 xl3 xs12 sm12">
<div class="row">
<div class="flex sm12 xl12 md12 xs12">
<va-accordion>
<va-collapse class="mb-2" :isOpenDefault="true">
<span slot="header">Control Panel</span>
<div slot="body">
<div>
<control-panel :ref="'control_panel'" @loadChartData="drawSpikeData"/>
</div>
</div>
</va-collapse>
</va-accordion>
</div>
</div>
</div>
<div :class="chartAreaClass">
<div class="row" >
<div class="flex sm12 xl12 md12 xs12">
<spike-overview-chart :ref="'chart_overview'" :cardheight="'30vh'" :data="chartOverviewData"/>
<spike-chart :ref="'chart_detail'" :cardheight="'60vh'" :data="chartDetailData"/>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapActions, mapGetters, mapMutations } from 'vuex'
import SpikeOverviewChart from './SpikeOverviewChart.vue'
import SpikeChart from './SpikeChart.vue'
import ControlPanel from './ControlPanel.vue'
import api from '@/data/api/index'
import types from '@/store/modules/analysis/spike/types'
import { mapFields } from 'vuex-map-fields'
import newSeries from '@/factories/chart/seriesFactory'
export default {
name: 'spike-data-replay',
components: {
SpikeOverviewChart,
SpikeChart,
ControlPanel,
},
computed: {
...mapFields('spike', [
'chartData',
'chartOverviewData',
'chartDetailData',
'metaList',
'selectedChannel',
'cutoffFreq',
'selectedThreshold',
'upperThreshold',
'lowerThreshold',
'waveLength',
'preThreshold',
'refractoryPeriod',
]),
},
props: {
},
async created () {
this.chartOverviewData = await this.initChart({
chartAllData: this.chartData,
chartID: 0,
gridLength: 1,
type: 'overview',
device: 'neulive',
mode: 0,
})
this.chartDetailData = await this.initChart({
chartAllData: this.chartData,
chartID: 1,
gridLength: 1,
type: 'detail',
device: 'neulive',
mode: 0,
})
},
data () {
return {
dataInfo: {},
maxTime: 0,
chartAreaClass: ['flex', 'xs12', 'sm12', 'md12', 'xl9', 'pa-0'],
}
},
mqtt: {
},
methods: {
showChartLoading: function (chart) {
const loadStyle = {
text: '',
color: '#cccccc',
maskColor: 'rgba(255, 255, 255, 0.8)',
zlevel: 0,
}
switch (chart) {
case 'overview':
this.$refs.chart_overview.showLoading(loadStyle)
break
case 'detail':
this.$refs.chart_detail.showLoading(loadStyle)
break
default:
this.$refs.chart_overview.showLoading(loadStyle)
this.$refs.chart_detail.showLoading(loadStyle)
break
}
},
hideChartLoading: function (chart) {
switch (chart) {
case 'overview':
this.$refs.chart_overview.hideLoading()
break
case 'detail':
this.$refs.chart_detail.hideLoading()
break
default:
this.$refs.chart_overview.hideLoading()
this.$refs.chart_detail.hideLoading()
break
}
},
register: async function () {
const gridLength = 1
this.updateGridListChart({
chartAllData: this.chartData,
chartID: this.chartDetailData.chartID,
gridLength: gridLength,
})
const register = this.metaList.map(meta => {
this.registerChart({
chartAllData: this.chartData,
chartID: this.chartOverviewData.chartID,
gridIndex: 0,
deviceNameX: 'Time',
deviceNameY: 'Neulive',
deviceIDY: meta.name,
channelY: this.selectedChannel,
})
this.registerChart({
chartAllData: this.chartData,
chartID: this.chartDetailData.chartID,
gridIndex: 0,
deviceNameX: 'Time',
deviceNameY: 'Neulive',
deviceIDY: meta.name,
channelY: this.selectedChannel,
})
if (this.maxTime < parseInt(meta.time_duration)) {
this.maxTime = parseInt(meta.time_duration)
}
})
await Promise.all(register)
for (let i = 0; i < 2; i++) {
// ID 0 = overview, ID 1 = detail
this.updateAxisLabelChart({
chartID: i,
axis: 'x',
axisIndex: 0,
channel: this.selectedChannel,
})
this.updateAxisLabelChart({
chartID: i,
axis: 'y',
axisIndex: 0,
channel: this.selectedChannel,
})
this.updateDataZoomChart({
chartAllData: this.chartData,
chartID: i,
})
}
this.updateChartSamplingChart({ chartID: this.chartOverviewData.chartID, seriesIndex: 0, method: function (frame) { return frame[0] } })
this.chartOverviewData.xAxis[0].min = 0
this.chartOverviewData.xAxis[0].max = this.maxTime
},
drawSpikeData: async function () {
this.showChartLoading('overview')
this.showChartLoading('detail')
for (const meta in this.dataInfo) {
let metaInfo
this.metaList.forEach(_meta => {
if (parseInt(_meta.id) === parseInt(meta)) {
metaInfo = _meta
}
})
const sampleRate = (800000 / metaInfo.parameter_set.ADC_CLOCK) / JSON.parse(metaInfo.channels).length // Hz
const period = 1e6 / sampleRate
let thresholdValue
if (this.selectedThreshold === 'over') {
thresholdValue = this.upperThreshold
} else if (this.selectedThreshold === 'bellow') {
thresholdValue = this.lowerThreshold
}
const randomColorArray = []
// [Region] whole processed signal
const allSpikeData = await api.analysis.spikeDetect({
id: meta,
channel: this.selectedChannel,
cut_off_freq: this.cutoffFreq,
threshold: [
`'${this.selectedThreshold}'`,
thresholdValue,
],
waveForm: [this.waveLength, this.preThreshold, this.refractoryPeriod],
ret_data_type: 'all',
})
this.chartOverviewData.series[0].data.length = 0
this.chartOverviewData.color = ['#000000'] // black
const splitData = allSpikeData.data.split('\n')
const allTimeArray = splitData[0].split(' ')
const allTimeArrayLength = allTimeArray.length
const allSignalArray = splitData[1].split(' ')
const allTimeMarkIndexArray = splitData[2].split(' ')
for (let i = 0; i < allTimeArrayLength; i++) {
this.chartOverviewData.series[0].data.push([parseInt(allTimeArray[i]), allSignalArray[i]])
}
this.chartOverviewData.xAxis[0].min = parseInt(allTimeArray[0])
this.chartOverviewData.xAxis[0].max = parseInt(allTimeArray[allTimeArrayLength - 1])
this.chartOverviewData.xAxis[0].axisLabel.show = true
this.chartOverviewData.series[0].markArea.data.length = 0
for (let i = 0; i < allTimeMarkIndexArray.length; i++) {
// generate color array for overview markArea and detail chart series
const randomColor = '#' + Math.floor(Math.random() * 16777215).toString(16) // ref: https://css-tricks.com/snippets/javascript/random-hex-color/
randomColorArray.push(randomColor)
if (Number.isInteger(parseInt(allTimeMarkIndexArray[i]))) { // cuz maybe is '' if no any match after filtering
const timeMarkIndex = parseInt(allTimeMarkIndexArray[i])
const start = parseInt(this.chartOverviewData.series[0].data[timeMarkIndex - parseInt(this.preThreshold / period)][0])
const end = parseInt(this.chartOverviewData.series[0].data[timeMarkIndex + parseInt(this.refractoryPeriod / period)][0])
this.chartOverviewData.series[0].markArea.data.push([
{
itemStyle: {
color: randomColor,
},
name: String(i + 1),
xAxis: start,
},
{
xAxis: end,
},
])
}
}
// [EndRegion]
// [Region] each detailed signal piece
const spikeData = await api.analysis.spikeDetect({
id: meta,
channel: this.selectedChannel,
cut_off_freq: this.cutoffFreq,
threshold: [
`'${this.selectedThreshold}'`,
thresholdValue,
],
waveForm: [this.waveLength, this.preThreshold, this.refractoryPeriod],
ret_data_type: 'partial',
})
for (let i = 0; i < this.chartDetailData.series.length; i++) {
this.chartDetailData.series[i].name = ''
this.chartDetailData.series[i].data.length = 0
}
const detailSignalArray = Object.values(spikeData.data)
if (detailSignalArray.length <= 8) {
this.chartDetailData.tooltip.show = true
} else {
this.chartDetailData.tooltip.show = false
}
this.chartDetailData.legend.data.length = 0
this.chartDetailData.legend.type = 'scroll'
this.chartDetailData.legend.top = '0.25%'
// this.chartDetailData.legendpageButtonPosition = 'start'
this.chartDetailData.animation = false
this.chartDetailData.color = []
for (let i = 0; i < detailSignalArray.length; i++) {
const detailSignal = detailSignalArray[i].split(' ')
const arrayLength = detailSignal.length
const step = parseInt(this.waveLength) / (arrayLength - 1)
const xAxisIndex = 0
const yAxisIndex = 0
const legendName = 'T_' + String(i + 1)
if (this.chartDetailData.series.length <= i) {
const _series = newSeries('line', legendName, xAxisIndex, yAxisIndex, [-1, -1], [metaInfo.name, this.selectedChannel], null, null)
this.chartDetailData.series.push(_series)
}
for (let j = 0; j < arrayLength; j++) {
this.chartDetailData.series[i].data.push([j * step, detailSignal[j]])
}
// push legendName
this.chartDetailData.legend.data.push(legendName)
this.chartDetailData.series[i].name = legendName
// set legend and series color
this.chartDetailData.color.push(randomColorArray[i])
}
this.chartDetailData.xAxis[0].min = 0
this.chartDetailData.xAxis[0].max = parseInt(this.waveLength)
this.chartDetailData.xAxis[0].axisLabel.show = true
// [EndRegion]
}
this.hideChartLoading()
console.log(this.chartOverviewData)
console.log(this.chartDetailData)
},
...mapActions('spike', [types.chart.init, types.chart.initSelectedChannel, types.chart.register, types.chart.clear, types.chart.updateGridList, types.chart.updateDataZoom]),
...mapActions('spike', [types.meta.init]),
...mapActions('spike', [types.rec.init, types.rec.intoData, types.rec.initDisplaySetting, types.rec.updateDisplaySetting]),
...mapMutations('spike', [types.chart.updateAxisLabel, types.chart.updateChartSampling]),
...mapMutations('spike', [types.meta.updateByIndex]),
...mapGetters('spike', [types.meta.getAll]),
...mapGetters('spike', [types.rec.getAll]),
},
// use watch because va-select with @change will have bug
watch: {
},
async mounted () {
const metaIDList = this.$route.params.metaList.split('-')
await this.initMeta({ metaIDList: metaIDList, api: api })
this.metaList = this.getAllMeta()
this.initSelectedChannelChart({ metaIndex: 0 })
await this.initRec({ api: api })
this.dataInfo = this.getAllRec()
console.log(this.metaList)
console.log(this.dataInfo)
await this.register()
this.$refs.control_panel.init()
},
beforeDestroy () {
this.$refs.chart_detail.clear()
for (const id in this.chartData) {
for (const _series of this.chartData[id].series) {
_series.length = 0
}
delete this.chartData[id]
}
this.clearChart({ chartAllData: this.chartData })
},
}
</script>
@@ -0,0 +1,110 @@
<template>
<div class="flex" :class="cardclass">
<va-card :style="'height: ' + cardheight + ';'">
<chart :ref="'chart_ref'" :style="'width: '+ cardwidth + '; height: ' + cardheight + ';'" :options="data" @dataZoom="updateZoom" :auto-resize="true"></chart>
</va-card>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
import types from '@/store/modules/analysis/spike/types'
// import { chartData } from '../../../../data/task/ChartsManagement'
export default {
name: 'spike-replay-chart',
components: {
},
props: {
data: {
type: Object,
default: function () {
return {}
},
},
cardclass: {
type: Array,
default: function () {
return ['xs12', 'md12', 'xl12', 'sm12']
},
},
cardheight: {
type: String,
default: '82vh',
},
cardwidth: {
type: String,
default: 'auto',
},
},
created () {
},
data () {
return {
}
},
computed: {
},
watch: {
},
methods: {
resize: function () {
if (this.$refs.chart_ref != null) {
this.$refs.chart_ref.resize()
}
},
updateZoom: function (e) {
this.$emit('dataZoomEvent', e)
},
mergeOptions (options) {
this.$refs.chart_ref.mergeOptions(options)
},
turnOnAxisEventHandler (eventName, query, handler, attr) {
const chartID = 1
this.updateAxisEventHandlerChart({
chartID: chartID,
axis: attr.axis,
status: 'on',
})
// doc: https://echarts.apache.org/zh/api.html#echartsInstance.on
this.$refs.chart_ref.chart._zr.on(eventName, query, handler)
},
turnOffAxisEventHandler (eventName, handler, attr) {
const chartID = 1
this.updateAxisEventHandlerChart({
chartID: chartID,
axis: attr.axis,
status: 'off',
})
// doc: https://echarts.apache.org/zh/api.html#echartsInstance.off
this.$refs.chart_ref.chart._zr.off(eventName, handler)
},
showLoading (options) {
this.$refs.chart_ref.showLoading(options)
},
hideLoading () {
this.$refs.chart_ref.hideLoading()
},
clear () {
this.$refs.chart_ref.clear()
},
...mapMutations('spike', [types.chart.updateAxisEventHandler]),
},
async mounted () {
this.resizeTimer = setInterval(this.resize, 1000)
},
destroyed () {
clearInterval(this.resizeTimer)
},
}
</script>
<style lang='scss'>
.va-card {
&__body {
padding-top: 0.25rem;
}
}
</style>
@@ -0,0 +1,86 @@
<template>
<div class="flex" :class="cardclass">
<va-card :style="'height: ' + cardheight + ';'">
<chart :ref="'chart_ref'" :style="'width: '+ cardwidth + '; height: ' + cardheight + ';'" :options="data" @dataZoom="updateZoom" :auto-resize="true"></chart>
</va-card>
</div>
</template>
<script>
// import { chartData } from '../../../../data/task/ChartsManagement'
export default {
name: 'spike-overview-chart',
components: {
},
props: {
data: {
type: Object,
default: function () {
return {}
},
},
cardclass: {
type: Array,
default: function () {
return ['xs12', 'md12', 'xl12', 'sm12']
},
},
cardheight: {
type: String,
default: '82vh',
},
cardwidth: {
type: String,
default: 'auto',
},
},
created () {
},
data () {
return {
}
},
computed: {
},
watch: {
},
methods: {
resize: function () {
if (this.$refs.chart_ref != null) {
this.$refs.chart_ref.resize()
}
},
mergeOptions (options) {
this.$refs.chart_ref.mergeOptions(options)
},
updateZoom: function (e) {
this.$emit('dataZoomEvent', e)
},
showLoading (options) {
this.$refs.chart_ref.showLoading(options)
},
hideLoading () {
this.$refs.chart_ref.hideLoading()
},
clear () {
this.$refs.chart_ref.clear()
},
},
async mounted () {
this.resizeTimer = setInterval(this.resize, 1000)
},
destroyed () {
clearInterval(this.resizeTimer)
},
}
</script>
<style lang='scss'>
.va-card {
&__body {
padding-top: 0.25rem;
}
}
</style>
@@ -0,0 +1,43 @@
<template>
<div>
<va-button-toggle small flat v-model="selectedChannel" :options="selectedChannelOptions" @click.native="reloadData(selectedChannel)"/>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
export default {
name: 'Channels',
components: {
},
computed: {
...mapFields('spike', [
'selectedChannel',
'selectedChannelOptions',
]),
},
props: {
},
created () {
},
data () {
return {
}
},
mqtt: {
},
methods: {
reloadData (val) {
this.selectedChannel = val
},
},
async mounted () {
},
destroyed () {
},
watch: {
},
}
</script>
@@ -0,0 +1,57 @@
<template>
<div class="row flex sm12 xl12 md12 xs12 align--center my-0 py-0">
<div class="flex sm6 xl6 md6 xs6 px-0 mx-0">
<p>Cut-off Freq<p/>
</div>
<div class="flex sm6 xl6 md6 xs6 px-0 mx-0">
<va-input
@mouseleave.native="itemChange(cutoffFreq)"
@keyup.enter="itemChange(cutoffFreq)"
@blur="itemChange(cutoffFreq)"
v-model="cutoffFreq"
class="mb-0"
>
<p slot="append" style="margin-right: 0;" small>
Hz
</p>
</va-input>
</div>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
export default {
name: 'CutoffFreq',
components: {
},
computed: {
...mapFields('spike', [
'cutoffFreq',
]),
},
props: {
},
created () {
},
data () {
return {
}
},
mqtt: {
},
methods: {
itemChange (val) {
this.cutoffFreq = val
},
},
async mounted () {
},
destroyed () {
},
watch: {
},
}
</script>
@@ -0,0 +1,49 @@
<template>
<div>
<va-button class="right" small @click="startDetect">
Start Detect
</va-button>
</div>
</template>
<script>
import types from '@/store/modules/analysis/spike/types'
import { mapFields } from 'vuex-map-fields'
import { mapMutations } from 'vuex'
export default {
name: 'Channels',
components: {
},
computed: {
...mapFields('spike', [
'chartDetailData',
'selectedChannel',
'waveLength',
]),
},
props: {
},
created () {
},
data () {
return {
}
},
mqtt: {
},
methods: {
startDetect () {
this.$emit('loadChartData')
},
...mapMutations('spike', [types.chart.updateAxisLabel]),
},
async mounted () {
},
destroyed () {
},
watch: {
},
}
</script>
@@ -0,0 +1,54 @@
<template>
<div class="row flex sm12 xl12 md12 xs12 align--center my-0 py-0">
<div class="flex sm6 xl6 md6 xs6 px-0 mx-0">
<p>Filter order<p/>
</div>
<div class="flex sm6 xl6 md6 xs6 px-0 mx-0">
<va-input
@mouseleave.native="itemChange(filterOrder)"
@keyup.enter="itemChange(filterOrder)"
@blur="itemChange(filterOrder)"
v-model="filterOrder"
class="mb-0"
>
</va-input>
</div>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
export default {
name: 'FilterOrder',
components: {
},
computed: {
...mapFields('spike', [
'filterOrder',
]),
},
props: {
},
created () {
},
data () {
return {
}
},
mqtt: {
},
methods: {
itemChange (val) {
this.filterOrder = val
},
},
async mounted () {
},
destroyed () {
},
watch: {
},
}
</script>
@@ -0,0 +1,57 @@
<template>
<div class="row flex sm12 xl12 md12 xs12 align--center my-0 py-0">
<div class="flex sm6 xl6 md6 xs6 px-0 mx-0">
<p>Prethreshold<p/>
</div>
<div class="flex sm6 xl6 md6 xs6 px-0 mx-0">
<va-input
@mouseleave.native="itemChange(preThreshold)"
@keyup.enter="itemChange(preThreshold)"
@blur="itemChange(preThreshold)"
v-model="preThreshold"
class="mb-0"
>
<p slot="append" style="margin-right: 0;" small>
µs
</p>
</va-input>
</div>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
export default {
name: 'Prethreshold',
components: {
},
computed: {
...mapFields('spike', [
'preThreshold',
]),
},
props: {
},
created () {
},
data () {
return {
}
},
mqtt: {
},
methods: {
itemChange (val) {
this.preThreshold = val
},
},
async mounted () {
},
destroyed () {
},
watch: {
},
}
</script>
@@ -0,0 +1,57 @@
<template>
<div class="row flex sm12 xl12 md12 xs12 align--center my-0 py-0">
<div class="flex sm7 xl7 md7 xs7 px-0 mx-0">
<p>Refractory period<p/>
</div>
<div class="flex sm5 xl5 md5 xs5 px-0 mx-0">
<va-input
@mouseleave.native="itemChange(refractoryPeriod)"
@keyup.enter="itemChange(refractoryPeriod)"
@blur="itemChange(refractoryPeriod)"
v-model="refractoryPeriod"
class="mb-0"
>
<p slot="append" style="margin-right: 0;" small>
µs
</p>
</va-input>
</div>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
export default {
name: 'RefractoryPeriod',
components: {
},
computed: {
...mapFields('spike', [
'refractoryPeriod',
]),
},
props: {
},
created () {
},
data () {
return {
}
},
mqtt: {
},
methods: {
itemChange (val) {
this.refractoryPeriod = val
},
},
async mounted () {
},
destroyed () {
},
watch: {
},
}
</script>
@@ -0,0 +1,83 @@
<template>
<div class="row flex sm12 xl12 md12 xs12 align--center my-0 py-0">
<va-radio-button
v-for="(option, index) in options"
:key="index"
v-model="selectedThreshold"
:option="option"
:label="`${option}`"
/>
<va-input
v-if="selectedThreshold === 'over'"
@mouseleave.native="itemChange(upperThreshold, 'upper')"
@keyup.enter="itemChange(upperThreshold, 'upper')"
@blur="itemChange(upperThreshold, 'upper')"
v-model="upperThreshold"
class="mb-0"
>
<p slot="append" style="margin-right: 0;" small>
µV
</p>
</va-input>
<va-input
v-if="selectedThreshold === 'bellow'"
@mouseleave.native="itemChange(lowerThreshold, 'lower')"
@keyup.enter="itemChange(lowerThreshold, 'lower')"
@blur="itemChange(lowerThreshold, 'lower')"
v-model="lowerThreshold"
class="mb-0"
>
<p slot="append" style="margin-right: 0;" small>
µV
</p>
</va-input>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
export default {
name: 'Threshold',
components: {
},
computed: {
...mapFields('spike', [
'selectedThreshold',
'upperThreshold',
'lowerThreshold',
]),
},
props: {
},
created () {
},
data () {
return {
options: [
// 'Auto',
'over',
'bellow',
],
}
},
mqtt: {
},
methods: {
itemChange (val, type) {
if (type === 'upper') {
this.upperThreshold = val
} else if (type === 'lower') {
this.lowerThreshold = val
}
},
},
async mounted () {
},
destroyed () {
},
watch: {
},
}
</script>
@@ -0,0 +1,57 @@
<template>
<div class="row flex sm12 xl12 md12 xs12 align--center my-0 py-0">
<div class="flex sm7 xl7 md7 xs7 px-0 mx-0">
<p>Waveform length<p/>
</div>
<div class="flex sm5 xl5 md5 xs5 px-0 mx-0">
<va-input
@mouseleave.native="itemChange(waveLength)"
@keyup.enter="itemChange(waveLength)"
@blur="itemChange(waveLength)"
v-model="waveLength"
class="mb-0"
>
<p slot="append" style="margin-right: 0;" small>
µs
</p>
</va-input>
</div>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
export default {
name: 'WaveLength',
components: {
},
computed: {
...mapFields('spike', [
'waveLength',
]),
},
props: {
},
created () {
},
data () {
return {
}
},
mqtt: {
},
methods: {
itemChange (val) {
this.waveLength = val
},
},
async mounted () {
},
destroyed () {
},
watch: {
},
}
</script>
@@ -0,0 +1,37 @@
<template>
<div>
<span> Resample: {{ currentScale }} to 1 </span>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
export default {
name: 'show-resample-rate',
components: {
},
props: {
},
created () {
},
data () {
return {
}
},
computed: {
...mapFields('spike', [
'currentScale',
]),
},
watch: {
},
methods: {
},
mounted () {
},
destroyed () {
},
}
</script>
@@ -182,6 +182,9 @@
<va-popover :message="`replay ${props.rowData.name}`" placement="top">
<va-button flat small color="gray" icon="fa fa-eye" v-if="props.rowData.type !== 'folder'" @click.stop="replay(props.rowData)"/>
</va-popover>
<va-popover :message="`spike ${props.rowData.name}`" placement="top">
<va-button flat small color="gray" icon="fa fa-filter" v-if="props.rowData.type !== 'folder'" @click.stop="spike(props.rowData)"/>
</va-popover>
<va-popover :message="`edit ${props.rowData.name}`" placement="top">
<va-button flat small color="gray" icon="fa fa-pencil" @click.stop="editFileConfirm(props.rowData)"/>
</va-popover>
@@ -668,6 +671,11 @@ export default {
})
}
},
spike: function (meta) {
this.$router.push({
path: '/admin/data-spike/' + meta.id,
})
},
},
mounted () {
},
+8 -5
View File
@@ -1,9 +1,10 @@
function getTime (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 = ''
const hour = Math.floor(val / 3.6e9)
const minute = Math.floor((val % 3.6e9) / 1000000 / 60)
const second = Math.floor((val % 6e7) / 1000000)
const millisecond = Math.floor((val % 1e6) / 1000)
const microsecond = Math.floor(val % 1e6) - 1000 * millisecond
if (hour > 0) {
label += hour + ' h '
}
@@ -16,7 +17,9 @@ function getTime (val) {
if (millisecond > 0) {
label += millisecond + ' ms '
}
// const label = parseInt(parseInt(val / 1000000) / 60) + ' m ' + parseInt((val % 60000000) / 1000000) + ' s'
if (microsecond > 0 && hour === 0 && minute === 0 && second === 0) {
label += microsecond + ' µs '
}
return label
}
+5
View File
@@ -107,6 +107,11 @@ export default new Router({
path: 'data-replay/elite/:metaList?',
component: () => import('../components/analysis/replay/elite/EliteDataReplay.vue'),
},
{
name: 'data-spike',
path: 'data-spike/:metaList?',
component: () => import('../components/analysis/spike/Spike.vue'),
},
{
name: 'task-content',
path: 'task-content/:taskID?',
+2
View File
@@ -9,6 +9,7 @@ import state from './state'
import mutations from './mutations'
import replay from './modules/analysis/replay'
import spike from './modules/analysis/spike'
import file from './modules/file'
import download from './modules/download'
@@ -22,6 +23,7 @@ const store = new Vuex.Store({
modules: {
taskContent,
replay,
spike,
setting,
download,
file,
@@ -261,62 +261,6 @@ const actions = {
}
},
/**
* set time format to xAxis by chart ID
*
* @param {object} chartAllData
* @param {int} chartID
* @param {int} xAxisIndex
* @param {object} series : chart series (echarts series)
*/
[typePath.setTimeXAxis]: (state, payload) => {
payload.chartAllData[payload.chartID].xAxis[payload.xAxisIndex].axisLabel.formatter = function (val) {
const label = parseInt(parseInt(val / 1000000) / 60) + ' m ' + parseInt((val % 60000000) / 1000000) + ' s'
return label
}
},
/**
* set value format by chart ID
*
* @param {object} chartAllData
* @param {int} chartID
* @param {string} axis ('x' or 'y')
* @param {int} axisIndex
* @param {optional, object} format
*/
[typePath.setAxisValueFormat]: (state, payload) => {
let name = '' // string
let unit = '' // string
let unitScale = 1 // int
if (payload.format !== undefined) {
name = payload.format.name // string
unit = payload.format.unit // string
unitScale = payload.format.unitScale // int
}
if (payload.axis === 'x') {
payload.chartAllData[payload.chartID].xAxis[payload.axisIndex].axisLabel.formatter = function (val) {
if (name !== 'time') {
const label = (val / unitScale).toFixed(2) + unit
return label
} else {
var hr = Math.floor(val / 3600000000)
var min = Math.floor((val - (hr * 3600000000)) / 60000000)
var sec = parseInt((val - (hr * 3600000000) - (min * 60000000)) / 1000000)
while (min.length < 2) { min = '0' + min }
while (sec.length < 2) { sec = '0' + min }
if (hr) hr += 'h'
return hr + min + 'm' + sec + 's'
}
}
} else if (payload.axis === 'y') {
payload.chartAllData[payload.chartID].yAxis[payload.axisIndex].axisLabel.formatter = function (val) {
const label = (val / unitScale).toFixed(2) + unit
return label
}
}
},
/**
* register device-channel to chartData
*
@@ -472,19 +416,6 @@ const actions = {
return chartData
},
/**
* reset chart axis label
*
* @param {object} chartData
*/
[typePath.resetChartDataAxisLabel]: async (state, payload) => {
const axisArray = payload.chartData.yAxis
for (let index = 0; index < axisArray.length; ++index) {
payload.chartData.yAxis[index].max = null
payload.chartData.yAxis[index].min = null
}
},
// /**
// * remove device-channel from chart TODO
// *
@@ -20,8 +20,6 @@ const types = {
pushSeries: 'pushSeriesChart',
pushAxis: 'pushAxisChart',
pushLegendName: 'pushLegendNameChart',
setTimeXAxis: 'setTimeXAxisChart',
setAxisValueFormat: 'setAxisValueFormatChart',
pushGridSeries: 'pushGridSeriesChart',
deleteByID: 'deleteByIDChart',
update: 'updateChart',
@@ -34,8 +32,6 @@ const types = {
updateRegistered: 'updateRegisteredChart',
updateAxisSplitNumber: 'updateAxisSplitNumberChart',
updateChartSampling: 'updateChartSamplingChart',
resetChartDataAxisLabel: 'resetChartDataAxisLabelChart',
},
meta: {
init: 'initMeta',
@@ -0,0 +1,506 @@
import newSeries from '@/factories/chart/seriesFactory'
import newChart from '@/factories/chart/chartFactory'
import newTooltip from '@/factories/chart/tooltipFactory'
import newDataZoom from '@/factories/chart/dataZoomFactory'
import types from '@/store/modules/analysis/spike/types'
const typePath = types.chart
// generate x axis format
function makeXAxis (gridIndex, type) {
const xAxis = {
type: type,
gridIndex: gridIndex,
axisLine: { show: false, onZero: false, lineStyle: { color: '#aaa' } },
axisTick: { show: false },
axisLabel: {
show: false,
showMinLabel: true,
showMaxLabel: true,
color: '#aaa',
},
splitLine: { show: true, lineStyle: { color: '#eee' } },
splitNumber: 5,
scale: true,
min: null,
max: null,
}
return xAxis
}
// generate y axis format
function makeYAxis (gridIndex, type) {
return {
type: type,
gridIndex: gridIndex,
axisTick: { show: false },
axisLine: { show: false }, // color: '#ccc' } },
axisLabel: { show: true, color: '#aaa', showMinLabel: true, showMaxLabel: true },
splitLine: { show: true, lineStyle: { color: '#eee' } },
splitNumber: 1,
animation: false,
scale: true,
min: 'dataMin',
max: 'dataMax',
}
}
// generate x axis format
function makeXAxisOverview (gridIndex, type) {
return {
type: type,
gridIndex: gridIndex,
axisLine: { show: false, onZero: false, lineStyle: { color: '#aaa' } },
axisTick: { show: false },
axisLabel: { show: true, color: '#aaa', showMinLabel: true, showMaxLabel: true },
splitLine: { show: false, lineStyle: { color: '#eee' } },
scale: true,
min: null,
max: null,
}
}
// generate y axis format
function makeYAxisOverview (gridIndex, type) {
return {
type: type,
gridIndex: gridIndex,
axisTick: { show: false },
axisLine: { show: false }, // color: '#ccc' } },
axisLabel: { show: true, color: '#aaa', showMinLabel: true, showMaxLabel: true },
splitLine: { show: true, lineStyle: { color: '#eee' } },
splitNumber: 1,
animation: false,
scale: true,
min: 'dataMin',
max: 'dataMax',
}
}
// get legend name
function getLegendName (sourceList) {
let legendName = ''
// source only 2 device
sourceList.forEach(source => {
switch (source.name) {
case 'Time':
legendName += 'T'
break
case 'Neulive':
switch (source.channel) {
case 256:
legendName += '' + source.id + '-X'
break
case 257:
legendName += '' + source.id + '-Y'
break
case 258:
legendName += '' + source.id + '-Z'
break
case 259:
legendName += '' + source.id + '-Mag'
break
default:
legendName += '' + source.id + 'CH' + String(parseInt(source.channel) + 1)
break
}
break
case 'Elite':
switch (source.channel) {
case 1:
legendName += '' + source.id + '-I'
break
case 2:
legendName += '' + source.id + '-V'
break
case 3:
legendName += '' + source.id + '-R'
break
default:
legendName += '' + source.id + 'CH' + source.channel
break
}
break
default:
legendName += '' + source.id + 'CH' + source.channel
break
}
legendName += '_'
})
legendName = legendName.substr(0, legendName.length - 1)
return legendName
}
// generate grid
function makeGrid (index, length, chartID) {
const _height = (82 / length) - 2 + '%'
let _top
if (chartID === 0) {
_top = index * (82 / length) + 8 + '%'
} else {
_top = index * (82 / length) + 6 + '%'
}
const _left = '8%'
const _right = '4%'
return {
index: index,
top: _top,
height: _height,
left: _left,
right: _right,
series: [],
}
}
const actions = {
/**
* init data to chartData via chartID
*
* @param {object} chartAllData
* @param {int} chartID : chart id
* @param {int} gridLength : length of chart grid
* @param {string} type
* @param {string} device
* @param {string} mode
* @return {object} : chart data
*/
[typePath.init]: ({ state, getters, commit, dispatch }, payload) => {
if (payload.chartAllData[payload.chartID] == null) {
payload.chartAllData[payload.chartID] = newChart(payload.chartID)
payload.chartAllData[payload.chartID].tooltip = newTooltip()
payload.chartAllData[payload.chartID].chartType = payload.type
payload.chartAllData[payload.chartID].chartDevice = payload.device
payload.chartAllData[payload.chartID].chartMode = payload.mode
dispatch(typePath.updateGridList, { chartAllData: payload.chartAllData, chartID: payload.chartID, gridLength: payload.gridLength })
return payload.chartAllData[payload.chartID]
} else {
return payload.chartAllData[payload.chartID]
}
},
/**
* init selectedChannel by metaIndex
*
* @param {int} metaIndex : chart id
*/
[typePath.initSelectedChannel]: ({ state, getters, commit, dispatch }, payload) => {
const channelList = JSON.parse(state.metaList[payload.metaIndex].channels)
state.selectedChannel = String(channelList[0])
state.selectedChannelOptions = []
channelList.forEach(function (item) {
if (item < 256) {
state.selectedChannelOptions.push({ label: String(item + 1), value: String(item) })
} else if (item === 256) {
state.selectedChannelOptions.push({ label: 'x', value: String(item) })
} else if (item === 257) {
state.selectedChannelOptions.push({ label: 'y', value: String(item) })
} else if (item === 258) {
state.selectedChannelOptions.push({ label: 'z', value: String(item) })
} else if (item === 259) {
state.selectedChannelOptions.push({ label: 'mag', value: String(item) })
}
})
},
/**
* update grid length by chart ID
*
* @param {object} chartAllData
* @param {int} chartID
* @param {int} gridLength : chart data (echarts options)
*/
[typePath.updateGridList]: async ({ state, getters, commit }, payload) => {
payload.chartAllData[payload.chartID].grid = []
for (let i = 0; i < payload.gridLength; i++) {
payload.chartAllData[payload.chartID].grid.push(makeGrid(i, payload.gridLength, payload.chartID))
}
},
/**
* reset data from chartData via chartID
*
* @param {object} chartAllData
* @param {int} chartID : chart id
* @param {int} gridLength : length of chart grid
* @return {object} : chart data
*/
[typePath.reset]: ({ state, getters, commit, dispatch }, payload) => {
// delete
for (const _series of payload.chartAllData[payload.chartID].series) {
_series.data.length = 0
_series.length = 0
}
delete payload.chartAllData[payload.chartID]
dispatch(typePath.init, { chartAllData: payload.chartAllData, chartID: payload.chartID, gridLength: payload.gridLength })
},
/**
* clear all chart data
*
* @param {int} chartAllData
*/
[typePath.clear]: ({ state, getters, commit }, payload) => {
for (const id in payload.chartAllData) {
for (const _series of payload.chartAllData[id].series) {
_series.data.length = 0
_series.length = 0
}
delete payload.chartAllData[id]
}
},
/**
* update datazoom by chart ID
*
* @param {object} chartAllData
* @param {int} chartID
*/
[typePath.updateDataZoom]: (state, payload) => {
// update dataZoom AxisIndex list
const dataZoomXAxisIndex = Array.apply(null, { length: payload.chartAllData[payload.chartID].xAxis.length }).map(Number.call, Number)
const dataZoomYAxisIndex = Array.apply(null, { length: payload.chartAllData[payload.chartID].yAxis.length }).map(Number.call, Number)
if (payload.chartAllData[payload.chartID].dataZoom == null) {
payload.chartAllData[payload.chartID].dataZoom = newDataZoom()
}
if (payload.chartAllData[payload.chartID].dataZoom != null) {
payload.chartAllData[payload.chartID].dataZoom[0].xAxisIndex = dataZoomXAxisIndex
payload.chartAllData[payload.chartID].dataZoom[1].xAxisIndex = dataZoomXAxisIndex
payload.chartAllData[payload.chartID].dataZoom[2].yAxisIndex = dataZoomYAxisIndex
}
},
/**
* register device-channel to chartData
*
* @param {object} chartAllData
* @param {int} chartID : chart id
* @param {int} gridIndex : index of grid
* @param {int} deviceNameX
* @param {int} deviceIDX
* @param {int} channelX
* @param {int} deviceNameY
* @param {int} deviceIDY
* @param {int} channelY
* @return {object} : chart data
*/
[typePath.register]: ({ state, getters, commit, dispatch }, payload) => {
const chartData = payload.chartAllData[payload.chartID]
const sourceList = [
{
name: payload.deviceNameX,
id: payload.deviceIDX,
channel: payload.channelX,
},
{
name: payload.deviceNameY,
id: payload.deviceIDY,
channel: payload.channelY,
},
]
if (chartData == null) {
return false
}
let xAxisType
let yAxisType
let xAxisSource
let yAxisSource
const legendName = getLegendName(sourceList)
switch (sourceList[0].name) {
case 'Time':
xAxisType = 'time'
xAxisSource = [-1, -1]
break
default:
xAxisType = 'value'
xAxisSource = [sourceList[0].id, sourceList[0].channel]
break
}
switch (sourceList[1].name) {
case 'Time':
yAxisType = 'time'
yAxisSource = [-1, -1]
break
default:
yAxisType = 'value'
yAxisSource = [sourceList[1].id, sourceList[1].channel]
break
}
// 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 < chartData.xAxis.length; i++) {
if (chartData.xAxis[i].gridIndex === payload.gridIndex) {
_xAxisIndex = i
}
}
for (let i = 0; i < chartData.yAxis.length; i++) {
if (chartData.yAxis[i].gridIndex === payload.gridIndex) {
_yAxisIndex = i
}
}
// create new axis
if (_xAxisIndex === -1) {
if (payload.chartID === 0) {
chartData.xAxis.push(makeXAxisOverview(payload.gridIndex, xAxisType))
} else {
chartData.xAxis.push(makeXAxis(payload.gridIndex, xAxisType))
}
_xAxisIndex = chartData.xAxis.length - 1
}
if (_yAxisIndex === -1) {
if (payload.chartID === 0) {
chartData.yAxis.push(makeYAxisOverview(payload.gridIndex, yAxisType))
} else {
chartData.yAxis.push(makeYAxis(payload.gridIndex, yAxisType))
}
_yAxisIndex = chartData.yAxis.length - 1
}
// dispatch(typePath.updateDataZoom, { chartAllData: payload.chartAllData, chartID: payload.chartID })
// register series
const _series = newSeries('line', legendName, _xAxisIndex, _yAxisIndex, xAxisSource, yAxisSource, null, null)
chartData.series.push(_series)
// register series into grid
const _axisList = []
sourceList.forEach((source, index) => {
let _axis = 'X'
if (index !== 0) {
_axis = 'Y'
}
switch (source.name) {
case 'Time':
_axisList.push(_axis + ' AXIS = TIME')
break
case 'Neulive':
_axisList.push(_axis + ' AXIS = NEULIVE ' + source.id + ' CHANNEL ' + source.channel)
break
case 'Elite':
switch (source.channel) {
case 1:
_axisList.push(_axis + ' AXIS = ELITE ' + source.id + ' CURRENT')
break
case 2:
_axisList.push(_axis + ' AXIS = ELITE ' + source.id + ' VOLTAGE')
break
case 3:
_axisList.push(_axis + ' AXIS = ELITE ' + source.id + ' RESISTANCE')
break
default:
_axisList.push(_axis + ' AXIS = ELITE ' + source.id + ' CHANNEL ' + source.channel)
break
}
break
default:
_axisList.push(_axis + ' AXIS = DEVICE ' + source.id + ' CHANNEL ' + source.channel)
break
}
})
const _gridSeries = {
name: legendName,
xAxis: _axisList[0],
yAxis: _axisList[1],
}
chartData.grid[payload.gridIndex].series.push(_gridSeries)
return chartData
},
// /**
// * remove device-channel from chart TODO
// *
// * @param {object} chartAllData
// * @param {int} chartID
// * @param {int} legendName
// * @return {object} : chart data
// */
// [typePath.remove]: ({ state, getters, commit }, payload) => {
// const chartData = getters.getDataByIDChart(payload.chartID)
// if (chartData == null || chartData.legend.data.length === 0) {
// return false
// }
// let _xAxisIndex = -1
// let _yAxisIndex = -1
// // remove legend
// const _legendIndex = chartData.legend.data.indexOf(payload.legendName)
// console.log(_legendIndex)
// if (_legendIndex >= 0) {
// chartData.legend.data.splice(chartData.legend.data.indexOf(payload.legendName), 1)
// } else {
// return false
// }
// // remove series
// for (let i = 0; i < chartData.series.length; i++) {
// if (chartData.series[i].name === payload.legendName) {
// _xAxisIndex = chartData.series[i].xAxisIndex
// _yAxisIndex = chartData.series[i].yAxisIndex
// chartData.series.splice(i, 1)
// break
// }
// }
// // remove grid series name
// const _gridIndex = chartData.xAxis[_xAxisIndex].gridIndex
// for (let i = 0; i < chartData.grid[_gridIndex].series.length; i++) {
// if (chartData.grid[_gridIndex].series[i] === payload.legendName) {
// chartData.grid[_gridIndex].series.splice(i, 1)
// break
// }
// }
// for (let i = 0; i < chartData.grid.length; i++) {
// if (chartData.grid[i].name === payload.legendName) {
// _xAxisIndex = chartData.series[i].xAxisIndex
// _yAxisIndex = chartData.series[i].yAxisIndex
// chartData.series.splice(i, 1)
// break
// }
// }
// chartData.series.forEach(series => {
// if (series.xAxisIndex === _xAxisIndex) {
// _xAxisIndex = -1
// }
// if (series.yAxisIndex === _yAxisIndex) {
// _yAxisIndex = -1
// }
// })
// // remove xAxis
// if (_xAxisIndex !== -1) {
// chartData.xAxis.splice(_xAxisIndex, 1)
// chartData.series.forEach(series => {
// if (series.xAxisIndex > _xAxisIndex) {
// series.xAxisIndex--
// }
// })
// chartData.dataZoom[0].xAxisIndex.pop()
// }
// // remove yAxis
// if (_yAxisIndex !== -1) {
// chartData.yAxis.splice(_yAxisIndex, 1)
// chartData.series.forEach(series => {
// if (series.yAxisIndex > _yAxisIndex) {
// series.yAxisIndex--
// }
// })
// chartData.dataZoom[2].yAxisIndex.pop()
// }
// console.log(chartData)
// return true
// },
}
export default actions
@@ -0,0 +1,9 @@
import chartAct from './chartAct'
import metaAct from './metaAct'
import recAct from './recAct'
export default {
...chartAct,
...metaAct,
...recAct,
}
@@ -0,0 +1,23 @@
import types from '@/store/modules/analysis/spike/types'
const typePath = types.meta
const metaActs = {
/**
* init meta
*
* @param {Array} metaIDList
* @param {objuct} api
* @return {object} : meta list info
*/
[typePath.init]: async ({ state, getters, commit }, payload) => {
const promises = payload.metaIDList.map(async id => {
const metaRes = await payload.api.meta.getByID(id)
return metaRes.data[0]
})
const list = await Promise.all(promises)
commit(typePath.set, { list: list })
},
}
export default metaActs
@@ -0,0 +1,131 @@
import types from '@/store/modules/analysis/spike/types'
const typePath = types.rec
const recActs = {
/**
* get rec by list index
*
* @param {objuct} api
* @return {object} : rec info
*/
[typePath.init]: async ({ state, getters, commit }, payload) => {
const metaList = getters.getAllMeta
const _rec = {}
metaList.forEach(meta => {
_rec[meta.id] = {}
JSON.parse(meta.channels).forEach(ch => {
_rec[meta.id][ch] = {
1: [],
10: [],
100: [],
1000: [],
}
})
})
const getRawInfo = metaList.map(async meta => {
const getData = JSON.parse(meta.channels).map(async ch => {
const res = await payload.api.raw.getAttrByParent(ch, meta.id, 'id-start_time-end_time')
res.data.forEach(_data => {
_data.load = false
_data.data = []
if (parseInt(_data.end_time) <= parseInt(meta.time_duration)) {
if (parseInt(_data.end_time) < 0) {
_data.end_time = parseInt(meta.time_duration)
}
_rec[meta.id][ch][1].push(_data)
}
})
return res.data
})
return await Promise.all(getData)
})
await Promise.all(getRawInfo)
const getMiniInfo = metaList.map(async meta => {
const getData = JSON.parse(meta.channels).map(async ch => {
const miniIDs = Object.values(meta.mini_data[ch])
const combineMiniIDs = [].concat.apply([], miniIDs)
for (const id of combineMiniIDs) {
const res = await payload.api.mini.getAttrByID(ch, id, 'id-start_time-end_time-scale')
res.data.forEach(_data => {
_data.load = false
_data.data = []
if (parseInt(_data.end_time) <= parseInt(meta.time_duration)) {
if (parseInt(_data.end_time) < 0) {
_data.end_time = parseInt(meta.time_duration)
}
_rec[meta.id][ch][parseInt(_data.scale)].push(_data)
}
})
}
// const res = await payload.api.mini.getAttrByParent(ch, meta.id, 'id-start_time-end_time-scale')
// res.data.forEach(_data => {
// _data.load = false
// _data.data = []
// if (_data.end_time < 0) {
// _data.end_time = meta.time_duration
// }
// _rec[meta.id][ch][parseInt(_data.scale)].push(_data)
// })
// return res.data
})
return await Promise.all(getData)
})
await Promise.all(getMiniInfo)
commit(typePath.set, { data: _rec })
},
/**
* into data to rec
*
* @param {int} index
* @param {int} metaID
* @param {int} channel
* @param {int} scale
* @param {object} data
*/
[typePath.intoData]: async ({ state, getters, commit }, payload) => {
commit(typePath.updateDataByIndex, {
index: payload.index,
metaID: payload.metaID,
channel: payload.channel,
scale: payload.scale,
data: payload.data,
})
},
/**
* into data to rec
*
* @param {int} meta
* @param {str} device
*/
[typePath.initDisplaySetting]: async ({ state, getters, commit }, payload) => {
switch (payload.device) {
case 'Neulive':
commit(typePath.initDisplaySettingNeulive, {
meta: payload.meta,
})
break
case 'Elite':
commit(typePath.initDisplaySettingElite, {
meta: payload.meta,
})
break
default:
break
}
},
[typePath.updateDisplaySetting]: async ({ state, getters, commit }, payload) => {
commit(typePath.updateDisplaySetting, {
updateObject: payload.updateObject,
})
},
}
export default recActs
@@ -0,0 +1,9 @@
import metaGet from './metaGet'
import recGet from './recGet'
import { getField } from 'vuex-map-fields'
export default {
...metaGet,
...recGet,
getField,
}
@@ -0,0 +1,44 @@
import types from '@/store/modules/analysis/spike/types'
const typePath = types.meta
const metaGets = {
/**
* get meta by list index
*
* @param {int} index
* @return {object} : meta info
*/
[typePath.getByIndex]: (state) => (index) => {
if (index == null || state.chartData[index] == null) {
return null
}
return state.metaList[index]
},
/**
* get meta by id
*
* @param {int} id
* @return {object} : meta info
*/
[typePath.getByID]: (state) => (id) => {
state.metaList.forEach(meta => {
if (meta.id === id) {
return meta
}
})
return null
},
/**
* get all meta info
*
* @return {Array} : meta list
*/
[typePath.getAll]: (state) => {
return state.metaList
},
}
export default metaGets
@@ -0,0 +1,61 @@
import types from '@/store/modules/analysis/spike/types'
const typePath = types.rec
const recGets = {
/**
* get rec by meta
*
* @return {object} : rec data
*/
[typePath.getByMeta]: (state) => (meta) => {
return state.recInfo[meta]
},
/**
* get all rec data
* @return {Object} : rec data
*/
[typePath.getAll]: (state) => {
return state.recInfo
},
/**
* get cycle data mapping
* @param {string} : meta ID
* @param {int} : cycle channel index
* @param {object} : cycleDataList, list value: object
* @return {object} : cycle mapping
*/
[typePath.getCycleDict]: (state) => (cycleDataList) => {
// get cycle data
// const cycleDataList = state.recInfo[metaID][cycleDataID][state.currentScale]
const cycleDataListNum = cycleDataList.length
var cycleDict = {}
var cycleStartTime = 0
var cycleEndTime = 0
var currentCycleIndex = 0
for (let i = 0; i < cycleDataListNum; i++) {
const cycleDataArray = cycleDataList[i].data
for (let index = 0; index < cycleDataArray.length; index++) {
const value = cycleDataArray[index]
const time = parseInt(value[0])
const cycleIndex = parseInt(value[1])
if (cycleIndex === currentCycleIndex) {
if (currentCycleIndex in cycleDict) {
cycleEndTime = time
cycleDict[cycleIndex][1] = cycleEndTime
} else {
cycleDict[cycleIndex] = [cycleStartTime, cycleEndTime]
}
} else {
cycleStartTime = time
currentCycleIndex++
}
}
}
return cycleDict
},
}
export default recGets
+12
View File
@@ -0,0 +1,12 @@
import actions from './actions'
import getters from './getters'
import mutations from './mutations'
import state from './state'
export default {
namespaced: true,
state: state,
getters: getters,
actions: actions,
mutations: mutations,
}
@@ -0,0 +1,310 @@
import types from '@/store/modules/analysis/spike/types'
const typePath = types.chart
// get legend name
function getLegendName (sourceList) {
let legendName = ''
// source only 2 device
sourceList.forEach(source => {
switch (source.name) {
case 'Time':
legendName += 'T'
break
case 'Neulive':
switch (source.channel) {
case 256:
legendName += '' + source.id + '-X'
break
case 257:
legendName += '' + source.id + '-Y'
break
case 258:
legendName += '' + source.id + '-Z'
break
case 259:
legendName += '' + source.id + '-Mag'
break
default:
legendName += '' + source.id + 'CH' + String(parseInt(source.channel) + 1)
break
}
break
case 'Elite':
switch (source.channel) {
case 1:
legendName += '' + source.id + '-I'
break
case 2:
legendName += '' + source.id + '-V'
break
case 3:
legendName += '' + source.id + '-R'
break
default:
legendName += '' + source.id + 'CH' + source.channel
break
}
break
default:
legendName += '' + source.id + 'CH' + source.channel
break
}
legendName += '_'
})
legendName = legendName.substr(0, legendName.length - 1)
return legendName
}
const chartMutations = {
/**
* update label formatter
*
* @param {int} chartID
* @param {string} axis ('x' or 'y')
* @param {int} axisIndex
* @param {int} channel // use for checking whether it is 3-axis
*/
[typePath.updateAxisLabel]: (state, payload) => {
if (payload.axis === 'x') {
const axisLabel = state.chartData[payload.chartID].xAxis[payload.axisIndex].axisLabel
axisLabel.formatter = function (val) {
let label = ''
const hour = Math.floor(val / 3.6e9)
const minute = Math.floor((val % 3.6e9) / 1000000 / 60)
const second = Math.floor((val % 6e7) / 1000000)
const millisecond = Math.floor((val % 1e6) / 1000)
const microsecond = Math.floor(val % 1e6) - 1000 * millisecond
if (hour > 0) {
label += hour + ' h '
}
if (minute > 0) {
label += minute + ' m '
}
if (second > 0) {
label += second + ' s '
}
if (millisecond > 0) {
label += millisecond + ' ms '
}
if (microsecond > 0 && hour === 0 && minute === 0 && second === 0) {
label += microsecond + ' µs '
}
return label
}
} else if (payload.axis === 'y') {
const axisLabel = state.chartData[payload.chartID].yAxis[payload.axisIndex].axisLabel
axisLabel.formatter = function (val) {
let label
if (payload.channel < 256) {
const num = String(val)
label = num + 'µV'
} else {
label = String(val.toFixed(2))
}
return label
}
}
},
/**
* turn on/off axis event handler
*
* @param {int} chartID
* @param {string} axis ('x' or 'y')
* @param {bool} status (on/off)
*/
[typePath.updateAxisEventHandler]: (state, payload) => {
let _axis
if (payload.axis === 'x') {
_axis = state.chartData[payload.chartID].xAxis
} else if (payload.axis === 'y') {
_axis = state.chartData[payload.chartID].yAxis
}
const len = _axis.length
if (payload.status === 'on') {
for (let i = 0; i < len; i++) {
_axis[i].triggerEvent = true
}
} else if (payload.status === 'off') {
for (let i = 0; i < len; i++) {
_axis[i].triggerEvent = false
}
}
},
/**
* update axis value min & max
*
* @param {int} chartID
* @param {string} axis ('x' or 'y')
* @param {int} axisIndex
* @param {float} minVal
* @param {float} maxVal
*/
[typePath.updateAxisValMinMax]: (state, payload) => {
let grid
if (payload.axis === 'x') {
grid = state.chartData[payload.chartID].xAxis[payload.axisIndex]
} else if (payload.axis === 'y') {
grid = state.chartData[payload.chartID].yAxis[payload.axisIndex]
}
if (payload.minVal !== '') {
grid.min = payload.minVal
} else {
grid.min = 'dataMin'
}
if (payload.maxVal !== '') {
grid.max = payload.maxVal
} else {
grid.max = 'dataMax'
}
},
/**
* update series by cycle
*
* @param {int} chartID
* @param {int} seriesIndex
* @param {array} data
*/
[typePath.updateSeries]: (state, payload) => {
state.chartData[payload.chartID].series[payload.seriesIndex].data = payload.data
},
/**
* update registered chartData
*
* @param {object} chartAllData
* @param {int} chartID : chart id
* @param {int} gridIndex : index of grid
* @param {int} deviceNameX
* @param {int} deviceIDX
* @param {int} channelX
* @param {int} deviceNameY
* @param {int} deviceIDY
* @param {int} channelY
* @return {object} : chart data
*/
[typePath.updateRegistered]: ({ state, getters, commit, dispatch }, payload) => {
const chartData = payload.chartAllData[payload.chartID]
const sourceList = [
{
name: payload.deviceNameX,
id: payload.deviceIDX,
channel: payload.channelX,
},
{
name: payload.deviceNameY,
id: payload.deviceIDY,
channel: payload.channelY,
},
]
if (chartData == null) {
return false
}
const legendName = getLegendName(sourceList)
// register legendName
if (chartData.legend.data.indexOf(legendName) >= 0) {
// have been registered
return false
}
// push legendName
chartData.legend.data[payload.gridIndex] = legendName
// register series into grid
const _axisList = []
sourceList.forEach((source, index) => {
let _axis = 'X'
if (index !== 0) {
_axis = 'Y'
}
switch (source.name) {
case 'Time':
_axisList.push(_axis + ' AXIS = TIME')
break
case 'Neulive':
_axisList.push(_axis + ' AXIS = NEULIVE ' + source.id + ' CHANNEL ' + source.channel)
break
case 'Elite':
switch (source.channel) {
case 1:
_axisList.push(_axis + ' AXIS = ELITE ' + source.id + ' CURRENT')
break
case 2:
_axisList.push(_axis + ' AXIS = ELITE ' + source.id + ' VOLTAGE')
break
case 3:
_axisList.push(_axis + ' AXIS = ELITE ' + source.id + ' RESISTANCE')
break
default:
_axisList.push(_axis + ' AXIS = ELITE ' + source.id + ' CHANNEL ' + source.channel)
break
}
break
default:
_axisList.push(_axis + ' AXIS = DEVICE ' + source.id + ' CHANNEL ' + source.channel)
break
}
})
const _gridSeries = {
name: legendName,
xAxis: _axisList[0],
yAxis: _axisList[1],
}
chartData.grid[payload.gridIndex].series[payload.gridIndex] = _gridSeries
chartData.series[payload.gridIndex].name = legendName
return chartData
},
/**
* update axis 間距刻度數量
*
* @param {int} chartID
* @param {string} axis ('x' or 'y')
*/
[typePath.updateAxisSplitNumber]: (state, payload) => {
if (payload.axis === 'x') {
const axis = state.chartData[payload.chartID].xAxis
const len = axis.length
for (let i = 0; i < len; i++) {
axis[i].splitNumber = 5
}
} else if (payload.axis === 'y') {
const axis = state.chartData[payload.chartID].yAxis
const len = axis.length
let splitNum
if (len === 1) {
splitNum = 5
} else if (len > 1 && len <= 4) {
splitNum = 3
} else if (len > 4) {
splitNum = 1
}
for (let i = 0; i < len; i++) {
axis[i].splitNumber = splitNum
}
}
},
/**
* update chart sampling method
*
* @param {int} chartID
* @param {int} seriesIndex
* @param {object} method
*/
[typePath.updateChartSampling]: (state, payload) => {
state.chartData[payload.chartID].series[payload.seriesIndex].sampling = payload.method
},
}
export default chartMutations
@@ -0,0 +1,11 @@
import chartMut from './chartMut'
import metaMut from './metaMut'
import recMut from './recMut'
import { updateField } from 'vuex-map-fields'
export default {
...chartMut,
...metaMut,
...recMut,
updateField,
}
@@ -0,0 +1,31 @@
import types from '@/store/modules/analysis/spike/types'
const typePath = types.meta
const metaMutations = {
/**
* set meta list
*
*/
[typePath.set]: async (state, payload) => {
state.metaList.length = 0
state.metaList = payload.list
},
/**
* Updates the meta info by index
*
* @param {number} index
* @param {any} item
*/
[typePath.updateByIndex]: function (state, payload) {
for (const item in payload) {
if (item !== 'index') {
state.metaList[payload.index][item] = payload[item]
}
}
},
}
export default metaMutations
@@ -0,0 +1,41 @@
import types from '@/store/modules/analysis/spike/types'
const typePath = types.rec
const recMutations = {
/**
* set rec list
*
* @param {object} data
*/
[typePath.set]: async (state, payload) => {
state.recInfo = payload.data
},
/**
* Updates the rec info by index
*
* @param {any} item
*/
[typePath.update]: function (state, payload) {
for (const item in payload) {
state.recInfo[item] = payload[item]
}
},
/**
* update data rec
*
* @param {int} index
* @param {int} metaID
* @param {int} channel
* @param {int} scale
* @param {object} data
*/
[typePath.updateDataByIndex]: function (state, payload) {
state.recInfo[payload.metaID][payload.channel][payload.scale][payload.index].data = payload.data
state.recInfo[payload.metaID][payload.channel][payload.scale][payload.index].load = true
},
}
export default recMutations
+22
View File
@@ -0,0 +1,22 @@
function newState () {
return {
recInfo: {},
metaList: [],
chartData: {},
chartOverviewData: {},
chartDetailData: {},
selectedChannel: '',
selectedChannelOptions: [],
cutoffFreq: 300,
filterOrder: 4,
selectedThreshold: 'over',
upperThreshold: 68,
lowerThreshold: 0,
waveLength: 1600,
preThreshold: 800,
refractoryPeriod: 800,
}
}
export default newState
+66
View File
@@ -0,0 +1,66 @@
const types = {
chart: {
init: 'initChart',
initSelectedChannel: 'initSelectedChannelChart',
reset: 'resetChart',
register: 'registerChart',
remove: 'removeChart',
clear: 'clearChart',
getDataByID: 'getDataByIDChart',
getAllData: 'getAllDataChart',
setChartData: 'setChartDataChart',
setTooltip: 'setTooltipChart',
setMarkArea: 'setMarkAreaChart',
setMarkAreaData: 'setMarkAreaDataChart',
setDataZoom: 'setDataZoomChart',
updateDataZoom: 'updateDataZoomChart',
updateGridList: 'updateGridListChart',
pushSeries: 'pushSeriesChart',
pushAxis: 'pushAxisChart',
pushLegendName: 'pushLegendNameChart',
pushGridSeries: 'pushGridSeriesChart',
deleteByID: 'deleteByIDChart',
update: 'updateChart',
updateSeries: 'updateSeriesChart',
updateAxis: 'updateAxisChart',
updateSeriesData: 'updateSeriesDataChart',
updateAxisLabel: 'updateAxisLabelChart',
updateAxisEventHandler: 'updateAxisEventHandlerChart',
updateAxisValMinMax: 'updateAxisValMinMaxChart',
updateRegistered: 'updateRegisteredChart',
updateAxisSplitNumber: 'updateAxisSplitNumberChart',
updateChartSampling: 'updateChartSamplingChart',
},
meta: {
init: 'initMeta',
set: 'setMeta',
setByApi: 'setByApiMeta',
updateByIndex: 'updateByIndexMeta',
getByIndex: 'getByIndexMeta',
getByID: 'getByIDMeta',
getAll: 'getAllMeta',
},
rec: {
init: 'initRec',
intoData: 'intoDataRec',
initDisplaySetting: 'initDisplaySettingRec',
initDisplaySettingNeulive: 'initDisplaySettingNeuliveRec',
initDisplaySettingElite: 'initDisplaySettingEliteRec',
set: 'setRec',
update: 'updateRec',
updateDataByIndex: 'updateDataByIndexRec',
updateDisplaySetting: 'updateDisplaySettingRec',
getAll: 'getAllRec',
getCycleDict: 'getCycleDictRec',
},
}
export default types