AnalysisSystemForRadionucli.../src/views/spectrumAnalysis/components/Modals/AnalyzeInteractiveToolModal/index.vue

1528 lines
41 KiB
Vue
Raw Normal View History

<template>
<custom-modal v-model="visible" :width="1280" title="Interactive Analyse Tools" :footer="null" destroy-on-close>
<a-spin :spinning="isLoading">
<div class="interactive-analysis-tools">
<div class="interactive-analysis-tools-left">
<div class="chart">
<CustomChart
ref="chartRef"
:option="option"
:opts="opts"
@zr:mousedown="handleMouseDown"
@zr:mouseup="handleMouseUp"
@brushEnd="handleBrushEnd"
@zr:click="handleChartClick"
/>
</div>
<!-- 缩略图 -->
<div class="thumbnail">
<CustomChart :option="thumbnailOption" />
</div>
<!-- 缩略图结束 -->
<!-- 表格 -->
<div class="table">
<p class="title">
<span @click="handleChangeMarkLine('prev')">&lt; </span>
6 Peaks with Anthro.Nuclides
<span @click="handleChangeMarkLine('next')">&gt;</span>
</p>
<custom-table
:class="list.length ? 'has-data' : ''"
:list="list"
:columns="columns"
:scroll="{ y: 288 }"
:selectedRowKeys.sync="selectedKeys"
rowKey="index"
:canDeselect="false"
@rowClick="handleTableRowClick"
>
</custom-table>
<div class="operators">
<a-button type="primary" @click="nuclideReviewModalVisible = true">Nuclide Review Window</a-button>
<a-button type="primary" @click="handleAddPeakComment()">Add Peak Comment</a-button>
<a-button type="primary" @click="handleAddGeneralComment()">Add General Comment</a-button>
</div>
</div>
<!-- 表格结束 -->
</div>
<!-- 右侧 -->
<div class="interactive-analysis-tools-right">
<title-over-border :title="btnGroupType == 1 ? 'Peak' : 'Baseline Control Points'">
<div class="peak-box">
<!-- 按钮组1 -->
<template v-if="btnGroupType == 1">
<div class="peak-box-item">
<a-button type="primary" @click="handleInsert">Insert</a-button>
</div>
<div class="peak-box-item">
<a-button type="primary" @click="handleDel">Delete</a-button>
</div>
<div class="peak-box-item">
<a-button type="primary" @click="handleFit">Fit</a-button>
</div>
<div class="peak-box-item symbol" :key="4">
<a-button type="primary" @click="handleChangeMarkLine('prev')">&lt;</a-button>
<a-button type="primary" @click="handleChangeMarkLine('next')">&gt;</a-button>
</div>
<div class="peak-box-item base-line">
<a-button type="primary" @click="handleSwitchOperation">BaseLine</a-button>
</div>
</template>
<!-- 按钮组2 -->
<template v-if="btnGroupType == 2">
<div class="peak-box-item">
<a-button type="primary" @click="handleAddCP">(A)dd CP</a-button>
</div>
<div class="peak-box-item">
<a-button type="primary" @click="handleRemoveCP">(R)emove CP</a-button>
</div>
<div class="peak-box-item">
<a-button type="primary" @click="handleModifyCP">(M)odify CP</a-button>
</div>
<div class="peak-box-item">
<a-button type="primary">Edit (S)lope</a-button>
</div>
<div class="peak-box-item">
<a-button type="primary">Undo</a-button>
</div>
<div class="peak-box-item">
<a-button type="primary">Replot</a-button>
</div>
<div class="peak-box-item">
<a-button type="primary" @click="handleAccept">Accept</a-button>
</div>
<div class="peak-box-item">
<a-button type="primary" @click="handleSwitchOperation">Cancel</a-button>
</div>
</template>
</div>
</title-over-border>
<div class="reset-btn-box">
<a-button type="primary" @click="handleResetChart">Reset Chart</a-button>
</div>
<div class="identify-box">
<title-over-border title="Nuclide Identify">
<a-form-model class="tolerance">
<a-form-model-item label="Tolerance">
<a-input-number v-model="model.tolerance"></a-input-number>
</a-form-model-item>
</a-form-model>
<div class="identify-item">
<div class="title">
Possible Nuclide
</div>
<div class="content">
<template v-if="selectedTableItem && selectedTableItem._possible">
<div
class="item"
:class="{ active: possible == model.possibleNuclide }"
v-for="(possible, index) in selectedTableItem._possible"
:key="index"
@click="model.possibleNuclide = possible"
>
{{ possible }}
</div>
</template>
</div>
</div>
<div class="identify-item">
<div class="title">
Nuclide Identified
</div>
<div class="content">
<template v-if="selectedTableItem">
<div
class="item"
:class="{ active: identified == model.identifiedNuclide }"
v-for="(identified, index) in selectedTableItem.nuclides"
:key="index"
@click="model.identifiedNuclide = identified"
>
{{ identified }}
</div>
</template>
</div>
</div>
<div class="identify-operators">
<span class="text">{{ model.possibleNuclide }}</span>
<a-button type="primary" :disabled="!model.possibleNuclide" @click="handleAddNuclide">Add</a-button>
<a-button type="primary" @click="handleDelNuclide">Del</a-button>
</div>
</title-over-border>
</div>
</div>
<!-- 右侧结束 -->
</div>
</a-spin>
<!-- Peak Comment弹窗 开始 -->
<peak-comment-modal v-model="peakCommentModalVisible" :curRow="curRow" />
<!-- Peak Comment弹窗 结束 -->
<!-- General Comment弹窗 开始 -->
<general-comment-modal v-model="generalCommentModalVisible" />
<!-- General Comment弹窗 结束 -->
<!-- Fit Peaks and Baseline弹窗 开始 -->
<fit-peaks-and-base-line-modal
v-model="fitPeaksAndBaselineModalVisible"
:curChan="currChannel"
@result="handleInsertSuccess"
@cancel="handleCancelSuccess"
/>
<!-- Fit Peaks and Baseline弹窗 结束 -->
<!-- Nuclide Review 弹窗开始 -->
<nuclide-review-modal v-model="nuclideReviewModalVisible" :sampleId="sampleId" :channel="currChannel" />
<!-- Nuclide Review 弹窗结束 -->
</custom-modal>
</template>
<script>
import CustomChart from '@/components/CustomChart/index.vue'
import TitleOverBorder from '../../TitleOverBorder.vue'
import PeakCommentModal from './components/PeakCommentModal.vue'
import FitPeaksAndBaseLineModal from './components/FitPeaksAndBaselineModal.vue'
import NuclideReviewModal from './components/NuclideReviewModal.vue'
import ModalMixin from '@/mixins/ModalMixin'
import { getAction } from '@/api/manage'
import { cloneDeep } from 'lodash'
import Response from './Response.json'
import { findSeriesByName, getXAxisAndYAxisByPosition, rangeNumber } from '@/utils/chartHelper'
import SampleDataMixin from '@/views/spectrumAnalysis/SampleDataMixin'
import GeneralCommentModal from './components/GeneralCommentModal.vue'
// 初始配置
const initialOption = {
grid: {
top: 40,
left: 40,
right: 30,
bottom: 30,
containLabel: true
},
title: {
text: '',
left: 'center',
bottom: 10,
textStyle: {
color: '#8FD4F8',
rich: {
a: {
padding: [0, 20, 0, 0],
fontSize: 16
}
}
}
},
tooltip: {
trigger: 'axis',
axisPointer: {
lineStyle: {
color: '#3CAEBB',
width: 1
}
},
formatter: undefined,
className: 'figure-chart-option-tooltip'
},
xAxis: {
axisLine: {
lineStyle: {
color: '#ade6ee'
}
},
splitLine: {
show: false
},
axisLabel: {
textStyle: {
color: '#ade6ee'
}
},
min: 1,
max: 'dataMax',
animation: false
},
yAxis: {
type: 'log',
name: 'Counts',
nameLocation: 'center',
nameGap: 40,
nameTextStyle: {
color: '#8FD4F8',
fontSize: 16
},
axisLine: {
show: true,
lineStyle: {
color: '#ade6ee'
}
},
splitLine: {
show: true,
lineStyle: {
color: 'rgba(173, 230, 238, .2)'
}
},
axisLabel: {
textStyle: {
color: '#ade6ee'
}
},
min: 0.1,
max: 'dataMax',
animation: false
},
series: [],
brush: {},
graphic: []
}
const columns = [
{
title: 'ID',
customRender: (_, __, index) => {
return index + 1
},
width: 60
},
{
title: 'Energy (keV)',
dataIndex: 'energy',
width: 120,
customRender: text => {
return text.toFixed(3)
}
},
{
title: 'Centroid (C)',
dataIndex: 'peakCentroid',
width: 120,
customRender: text => {
return text.toFixed(3)
}
},
{
title: 'FWHM (keV)',
dataIndex: 'fwhm',
width: 120,
customRender: text => {
return text.toFixed(3)
}
},
{
title: 'Area',
dataIndex: 'area',
width: 120,
customRender: text => {
return text.toFixed(3)
}
},
{
title: 'Detectability',
dataIndex: 'significance',
width: 120,
customRender: text => {
return text.toFixed(3)
}
},
{
title: '#Cmnt',
dataIndex: 'comments',
width: 120
},
{
title: 'Nuclides',
dataIndex: 'nuclides',
width: 120,
customRender: text => {
return text && text.join(';')
}
}
]
// 缩略图配置
const thumbnailOption = {
grid: {
top: 0,
left: 5,
right: 5,
bottom: 0
},
xAxis: {
type: 'category',
axisLine: {
show: false
},
splitLine: {
show: false
},
axisLabel: {
show: false
},
min: 1,
max: 'dataMax'
},
yAxis: {
type: 'value',
axisLine: {
show: false
},
splitLine: {
show: false
},
axisLabel: {
show: false
},
min: 0.1,
max: 'dataMax'
},
series: null
}
export default {
mixins: [ModalMixin, SampleDataMixin],
components: {
CustomChart,
TitleOverBorder,
PeakCommentModal,
FitPeaksAndBaseLineModal,
NuclideReviewModal,
GeneralCommentModal
},
data() {
this.columns = columns
return {
option: cloneDeep(initialOption),
opts: { notMerge: false },
thumbnailOption: cloneDeep(thumbnailOption),
isLoading: false,
channelBaseCPChart: [],
channelBaseLineChart: [],
channelCountChart: [],
channelPeakChart: [],
energy: [],
list: [],
sampleId: -1,
peakCommentModalVisible: false, // Comment 弹窗是否显示
curRow: -1,
generalCommentModalVisible: false, // Comment 弹窗是否显示
btnGroupType: 1, // 右侧 Peak 中的按钮组切换
selectedKeys: [], // 选中的列表
fitPeaksAndBaselineModalVisible: false, // Fit Peaks And Base Line 弹窗
nuclideReviewModalVisible: false, // Nuclide Review 弹窗
model: {
possibleNuclide: '',
tolerance: 0.5,
identifiedNuclide: ''
},
currChannel: undefined, // 当currChannel前选中的channel
selectedTableItem: undefined, // 当前选中的表格项
isModifying: false // 正在修改控制点
}
},
created() {
this.option.tooltip.formatter = params => {
const channel = parseInt(params[0].value[0])
const energy = this.energy[channel - 1]
return `<div class="channel">Channel: ${channel}</div>
<div class="energy">Energy: ${energy.toFixed(2)}</div>`
}
},
methods: {
async getInfo() {
try {
this.isLoading = true
this.option.series = []
// const { success, result, message } = Response
const { success, result, message } = await getAction('/gamma/InteractiveTool', {
sampleId: this.sampleId,
fileName: this.fileName
})
if (success) {
this.isLoading = false
const {
barChart,
channelBaseCPChart,
channelBaseLineChart,
channelCountChart,
channelPeakChart,
energy,
table
} = result
console.log('%c [ ]-374', 'font-size:13px; background:pink; color:#bf2c9f;', result)
this.channelBaseCPChart = channelBaseCPChart
this.channelBaseLineChart = channelBaseLineChart
this.channelCountChart = channelCountChart
this.channelPeakChart = channelPeakChart
this.energy = energy
const series = []
// 推入BaseLine
series.push({
...this.buildSeriesOption(
'BaseLine',
channelBaseLineChart.pointlist.map(({ x, y }) => [x, y]),
`rgb(${channelBaseLineChart.color})`
),
markLine: {
silent: true,
symbol: 'none',
label: {
show: false
},
lineStyle: {
color: 'red',
width: 1
},
data: [{ xAxis: -1 }]
},
zlevel: 10
})
// 推入Count
series.push(
this.buildSeriesOption(
'CountChart',
channelCountChart.pointlist.map(({ x, y }) => [x, y]),
`rgb(${channelCountChart.color})`
)
)
// 推入Peak
const peakSeries = []
channelPeakChart.forEach((item, index) => {
peakSeries.push(
this.buildSeriesOption(
'Peak_' + (index + 1),
item.pointlist.map(({ x, y }) => [x, y]),
`rgb(${item.color})`
)
)
})
series.push(...peakSeries)
// 推入基线控制点
series.push({
name: 'BaseLine_Ctrl_Point',
type: 'scatter',
data: channelBaseCPChart.map(({ size, color, point: { x, y } }) => {
return {
value: [x, y],
itemStyle: {
color: 'transparent',
borderColor: color,
borderWidth: size / 2
}
}
}),
silent: true,
animation: false,
zlevel: 20
})
this.thumbnailOption.series = this.buildSeriesOption(
'BarChart',
barChart.map(({ x, y }) => [x, y]),
'#fff',
{
silent: true
}
)
this.list = table
this.option.series = series
} else {
this.$message.error(message)
}
} catch (error) {
console.error(error)
}
},
// 构建series
buildSeriesOption(name, data, color, extra = {}) {
return {
name,
type: 'line',
data,
itemStyle: {
color
},
lineStyle: {
width: 1
},
symbol: 'none',
symbolSize: 1,
emphasis: {
disabled: true
},
silent: true,
animation: false,
...extra
}
},
beforeModalOpen() {
const { sampleId, inputFileName } = this.sampleData
this.sampleId = sampleId
this.fileName = inputFileName
this.currChannel = undefined
this.btnGroupType = 1
this.getInfo()
this.opts.notMerge = false
this.option.graphic = []
this.$nextTick(() => {
this.option.brush = { toolbox: [] }
this.selectedKeys = []
})
},
// 点击图表,设置红线,改变表格的选中项
handleChartClick(param) {
const { offsetX, offsetY } = param
const point = getXAxisAndYAxisByPosition(this.$refs.chartRef.getChartInstance(), offsetX, offsetY)
if (point) {
const xAxis = parseInt(point[0].toFixed())
this.option.series[0].markLine.data[0].xAxis = xAxis
this.currChannel = xAxis
// 获取每一段 Channel 中的最大值
const maxXAxises = this.getPeakMaxValues()
let index = 0
// 计算当前选中的xAxis跟哪条 peak的最大值 最近
if (xAxis >= maxXAxises[maxXAxises.length - 1]) {
index = maxXAxises.length - 1
} else if (xAxis <= maxXAxises[0]) {
index = 0
} else {
for (let i = 1; i < maxXAxises.length; i++) {
const prev = maxXAxises[i - 1]
const curr = maxXAxises[i]
if (xAxis >= prev && xAxis <= curr) {
index = xAxis - prev < curr - xAxis ? i - 1 : i
break
}
continue
}
}
const selectedRow = this.list[index]
this.selectedKeys = [selectedRow.index]
this.getSelPosNuclide(selectedRow)
}
},
// 切换图表上的红色竖线及表格选中
handleChangeMarkLine(direction) {
const markLineOption = this.option.series[0].markLine.data[0]
const prevAxis = markLineOption.xAxis
// 获取每一段 Channel 中的最大值
const maxXAxises = this.getPeakMaxValues()
if (direction == 'next') {
// 找到第一个比prevAxis大的xAxis
const find = maxXAxises.find(xAxis => xAxis > prevAxis)
if (find) {
markLineOption.xAxis = find
}
} else if (direction == 'prev') {
// 找到第一个比prevAxis小的xAxis
const find = cloneDeep(maxXAxises)
.reverse()
.find(xAxis => xAxis < prevAxis)
if (find) {
markLineOption.xAxis = find
}
}
const xAxis = markLineOption.xAxis
if (xAxis >= 0) {
const index = maxXAxises.findIndex(item => item == xAxis)
if (index !== -1) {
this.selectedKeys = [this.list[index].index]
}
}
},
// 获取右下角possible nuclide 和 identified nuclide
async getSelPosNuclide(row) {
this.model.possibleNuclide = ''
this.model.identifiedNuclide = ''
if (!row._possible) {
try {
const { success, result, message } = await getAction('/gamma/getSelPosNuclide', {
sampleId: this.sampleId,
channel: parseInt(row.peakCentroid),
nuclides: row.nuclides.join(','),
fileName: this.fileName
})
if (success) {
const { possible } = result
this.$set(row, '_possible', possible)
} else {
this.$message.error(message)
}
} catch (error) {
console.error(error)
}
}
},
// 获取每一段 Channel 中的最大值
getPeakMaxValues() {
const maxXAxises = this.channelPeakChart.map(item => {
const allY = item.pointlist.map(point => point.y)
const max = item.pointlist.find(point => point.y == Math.max(...allY))
return max.x
})
return maxXAxises
},
// 显示peak comment弹窗
handleAddPeakComment () {
if (!this.selectedKeys.length) {
this.$message.warn('Please Select a Peak that You Want to Add Comment!')
return
}
const [willDelKey] = this.selectedKeys
const findIndex = this.list.findIndex(item => item.index == willDelKey)
this.curRow = findIndex
this.peakCommentModalVisible = true
},
// 显示general comment弹窗
handleAddGeneralComment() {
this.generalCommentModalVisible = true
},
// Insert按钮
handleInsert() {
const xAxises = this.channelBaseCPChart.map(({ point: { x } }) => x)
const min = xAxises[0]
const max = xAxises[xAxises.length - 1]
if (!this.currChannel || this.currChannel < min || this.currChannel > max) {
this.$message.warn("Couldn't insert peak, maybe out of range")
return
}
this.fitPeaksAndBaselineModalVisible = true
},
// 点击 Fit Peak XXX 弹窗中的 Peaks 按钮
handleInsertSuccess(result) {
const {
allData,
barChart,
channelBaseLineChart,
channelPeakChart,
shadowChannelChart,
shadowEnergyChart,
shapeChannelData,
shapeEnergyData,
table
} = result
this.$emit('refresh', {
allData,
channelPeakChart,
shadowChannelChart,
shadowEnergyChart,
shapeChannelData,
shapeEnergyData
})
this.channelPeakChart = channelPeakChart
this.channelBaseLineChart = channelBaseLineChart
const series = []
// 推入BaseLine
series.push({
...this.buildSeriesOption(
'BaseLine',
channelBaseLineChart.pointlist.map(({ x, y }) => [x, y]),
`rgb(${channelBaseLineChart.color})`
),
markLine: {
silent: true,
symbol: 'none',
label: {
show: false
},
lineStyle: {
color: 'red',
width: 1
},
data: [{ xAxis: -1 }]
},
zlevel: 10
})
// 推入旧的Count
series.push(
this.buildSeriesOption(
'CountChart',
this.channelCountChart.pointlist.map(({ x, y }) => [x, y]),
`rgb(${this.channelCountChart.color})`
)
)
// 推入Peak
const peakSeries = []
channelPeakChart.forEach((item, index) => {
peakSeries.push(
this.buildSeriesOption(
'Peak_' + (index + 1),
item.pointlist.map(({ x, y }) => [x, y]),
`rgb(${item.color})`
)
)
})
series.push(...peakSeries)
// 推入旧的基线控制点
series.push({
name: 'BaseLine_Ctrl_Point',
type: 'scatter',
data: this.channelBaseCPChart.map(({ size, color, point: { x, y } }) => {
return {
value: [x, y],
itemStyle: {
color: 'transparent',
borderColor: color,
borderWidth: size / 2
}
}
}),
silent: true,
animation: false,
zlevel: 20
})
this.thumbnailOption.series = this.buildSeriesOption(
'BarChart',
barChart.map(({ x, y }) => [x, y]),
'#fff',
{
silent: true
}
)
this.list = table
this.option.series = series
},
// 点击 Fit Peak XXX 弹窗中的 Cancel 按钮
handleCancelSuccess(result) {
const { channelPeakChart, table } = result
this.channelPeakChart = channelPeakChart
const series = []
// 推入旧的BaseLine
series.push({
...this.buildSeriesOption(
'BaseLine',
this.channelBaseLineChart.pointlist.map(({ x, y }) => [x, y]),
`rgb(${this.channelBaseLineChart.color})`
),
markLine: {
silent: true,
symbol: 'none',
label: {
show: false
},
lineStyle: {
color: 'red',
width: 1
},
data: [{ xAxis: -1 }]
},
zlevel: 10
})
// 推入旧的Count
series.push(
this.buildSeriesOption(
'CountChart',
this.channelCountChart.pointlist.map(({ x, y }) => [x, y]),
`rgb(${this.channelCountChart.color})`
)
)
// 推入Peak
const peakSeries = []
channelPeakChart.forEach((item, index) => {
peakSeries.push(
this.buildSeriesOption(
'Peak_' + (index + 1),
item.pointlist.map(({ x, y }) => [x, y]),
`rgb(${item.color})`
)
)
})
series.push(...peakSeries)
// 推入旧的基线控制点
series.push({
name: 'BaseLine_Ctrl_Point',
type: 'scatter',
data: this.channelBaseCPChart.map(({ size, color, point: { x, y } }) => {
return {
value: [x, y],
itemStyle: {
color: 'transparent',
borderColor: color,
borderWidth: size / 2
}
}
}),
silent: true,
animation: false,
zlevel: 20
})
this.list = table
this.option.series = series
},
// 删除
handleDel() {
if (!this.selectedKeys.length) {
this.$message.warn('No peak to delete.')
return
}
this.$warning({
title: 'Warning',
content: 'Are you sure to delete this peak?',
onOk: async () => {
const [willDelKey] = this.selectedKeys
const findIndex = this.list.findIndex(item => item.index == willDelKey)
// this.list.splice(findIndex, 1)
// this.selectedKeys = []
// const seriesIndex = this.option.series.findIndex(item => {
// return item.name == 'Peak_' + willDelKey
// })
// this.opts.notMerge = true
// this.option.series.splice(seriesIndex, 1)
// this.channelPeakChart.splice(findIndex, 1)
// this.$nextTick(() => {
// this.resetChartOpts()
// })
try {
const { inputFileName: fileName } = this.sampleData
const { success, result, message } = await getAction('/gamma/deletePeak', {
fileName,
curRow: findIndex
})
if (success) {
console.log('%c [ ]-935', 'font-size:13px; background:pink; color:#bf2c9f;', result)
const {
allData,
channelPeakChart,
shadowChannelChart,
shadowEnergyChart,
shapeChannelData,
shapeEnergyData,
table
} = result
this.$emit('refresh', {
allData,
channelPeakChart,
shadowChannelChart,
shadowEnergyChart,
shapeChannelData,
shapeEnergyData
})
this.channelPeakChart = channelPeakChart
const series = []
// 推入旧的BaseLine
series.push({
...this.buildSeriesOption(
'BaseLine',
this.channelBaseLineChart.pointlist.map(({ x, y }) => [x, y]),
`rgb(${this.channelBaseLineChart.color})`
),
markLine: {
silent: true,
symbol: 'none',
label: {
show: false
},
lineStyle: {
color: 'red',
width: 1
},
data: [{ xAxis: -1 }]
},
zlevel: 10
})
// 推入旧的Count
series.push(
this.buildSeriesOption(
'CountChart',
this.channelCountChart.pointlist.map(({ x, y }) => [x, y]),
`rgb(${this.channelCountChart.color})`
)
)
// 推入Peak
const peakSeries = []
channelPeakChart.forEach((item, index) => {
peakSeries.push(
this.buildSeriesOption(
'Peak_' + (index + 1),
item.pointlist.map(({ x, y }) => [x, y]),
`rgb(${item.color})`
)
)
})
series.push(...peakSeries)
// 推入旧的基线控制点
series.push({
name: 'BaseLine_Ctrl_Point',
type: 'scatter',
data: this.channelBaseCPChart.map(({ size, color, point: { x, y } }) => {
return {
value: [x, y],
itemStyle: {
color: 'transparent',
borderColor: color,
borderWidth: size / 2
}
}
}),
silent: true,
animation: false,
zlevel: 20
})
this.list = table
this.option.series = series
} else {
this.$message.error(message)
}
} catch (error) {
console.error(error)
}
}
})
},
// 重置图表配置
resetChartOpts() {
this.opts.notMerge = false
this.option.brush = { toolbox: [] }
},
// 匹配
handleFit() {
if (!this.list.length) {
this.$message.warn('No peak to fit.')
return
}
},
// 表格的行点击
handleTableRowClick(row) {
if (this.selectedTableItem == row) {
return
}
const channel = row.peakCentroid
this.currChannel = channel
this.option.series[0].markLine.data[0].xAxis = channel
this.getSelPosNuclide(row)
this.selectedTableItem = row
},
// 鼠标按下时开启可刷选状态
handleMouseDown() {
if (this.isModifying) {
return
}
const chart = this.$refs.chartRef.getChartInstance()
chart.dispatchAction({
type: 'takeGlobalCursor',
// 如果想变为“可刷选状态”,必须设置。不设置则会关闭“可刷选状态”。
key: 'brush',
brushOption: {
// 参见 brush 组件的 brushType。如果设置为 false 则关闭“可刷选状态”。
brushType: 'rect'
}
})
},
handleMouseUp() {
setTimeout(() => {
const chart = this.$refs.chartRef.getChartInstance()
this.clearBrush(chart)
}, 0)
},
clearBrush(chart) {
// 清理刷选的范围
chart.dispatchAction({
type: 'brush',
areas: []
})
// 改为不可刷选状态
chart.dispatchAction({
type: 'takeGlobalCursor'
})
},
// 刷选完毕时
handleBrushEnd(param) {
const chart = this.$refs.chartRef.getChartInstance()
const areas = param.areas[0]
if (areas) {
const range = areas.range
const [[minX, maxX], [minY, maxY]] = range
const point1 = chart.convertFromPixel({ seriesIndex: 0 }, [minX, minY]).map(num => parseInt(num.toFixed()))
const point2 = chart.convertFromPixel({ seriesIndex: 0 }, [maxX, maxY]).map(num => parseInt(num.toFixed()))
const xAxisMax = chart.getModel().getComponent('xAxis').axis.scale._extent[1]
const yAxisMax = this.option.yAxis.max
let [x1, y2, x2, y1] = [...point1, ...point2] // 根据解析出的数据确定真实的范围
const xAxisLimit = rangeNumber(1, xAxisMax)
const yAxisLimit = rangeNumber(0.1, yAxisMax)
x1 = xAxisLimit(x1)
x2 = xAxisLimit(x2)
y1 = yAxisLimit(y1)
y2 = yAxisLimit(y2)
this.option.xAxis.min = x1
this.option.xAxis.max = x2
this.option.yAxis.min = y1
this.option.yAxis.max = y2
this.thumbnailOption.xAxis.min = x1
this.thumbnailOption.xAxis.max = x2
this.thumbnailOption.yAxis.min = y1
this.thumbnailOption.yAxis.max = y2
if (this.btnGroupType == 2) {
this.$nextTick(() => {
this.option.graphic = this._channelBaseCPChart.map(({ point: { x, y } }, dataIndex) => {
return this.buildGraphicPoint(chart, x, y, dataIndex)
})
})
}
}
this.clearBrush(chart)
},
handleResetChart() {
this.option.xAxis.min = 1
this.option.xAxis.max = 'dataMax'
this.option.yAxis.min = 0.1
this.option.yAxis.max = 'dataMax'
this.thumbnailOption.xAxis.min = 1
this.thumbnailOption.xAxis.max = 'dataMax'
this.thumbnailOption.yAxis.min = 0.1
this.thumbnailOption.yAxis.max = 'dataMax'
if (this.btnGroupType == 2) {
const chart = this.$refs.chartRef.getChartInstance()
this.$nextTick(() => {
this.option.graphic = this._channelBaseCPChart.map(({ point: { x, y } }, dataIndex) => {
return this.buildGraphicPoint(chart, x, y, dataIndex)
})
})
}
},
// 切换操作
handleSwitchOperation() {
// 切换到Base Line 和 Control Point 操作
if (this.btnGroupType == 1) {
this.btnGroupType = 2
const originalCPSeries = findSeriesByName(this.option.series, 'BaseLine')
const baseLineEditSeries = this.buildSeriesOption('BaseLine_Edit', cloneDeep(originalCPSeries.data), '#fff', {
zlevel: 21
})
this.option.series.push(baseLineEditSeries)
const chart = this.$refs.chartRef.getChartInstance()
this.option.graphic = this.channelBaseCPChart.map(({ point: { x, y } }, dataIndex) => {
return this.buildGraphicPoint(chart, x, y, dataIndex)
})
this._channelBaseCPChart = cloneDeep(this.channelBaseCPChart)
}
// 切换回 Peak 操作
else {
this.btnGroupType = 1
this.opts.notMerge = true
this.option.series.splice(this.option.series.length - 1, 1)
this.option.graphic = []
this.$nextTick(() => {
this.resetChartOpts()
})
}
},
buildGraphicPoint(chart, x, y, dataIndex) {
const [xPix, yPix] = chart.convertToPixel('grid', [x, y])
return {
type: 'rect',
position: [xPix, yPix],
shape: {
x: -4,
y: -4,
width: 8,
height: 8
},
style: {
stroke: 'red',
fill: 'transparent',
lineWidth: 2
},
draggable: false,
ondrag: function() {
const [xPixel] = chart.convertToPixel('grid', [x, y])
this.position[0] = xPixel
},
ondragend: ({ offsetY }) => {
this.option.graphic[dataIndex].position = [xPix, offsetY]
this.setGraphicDraggable(false)
const [xAxis, yAxis] = getXAxisAndYAxisByPosition(chart, xPix, offsetY)
const baseLineEditSeries = findSeriesByName(this.option.series, 'BaseLine_Edit')
baseLineEditSeries.data[parseInt(xAxis) - 1] = [x, yAxis]
this._channelBaseCPChart[dataIndex].point.y = yAxis
},
zlevel: 100
}
},
/**
* 设置小方块可拖拽
*/
setGraphicDraggable(draggable) {
this.option.graphic.forEach(item => {
item.draggable = draggable
})
this.isModifying = draggable
},
// 在当前选中的红线位置新增控制点
handleAddCP() {
const xAxises = this.channelBaseCPChart.map(({ point: { x } }) => x)
const min = xAxises[0]
const max = xAxises[xAxises.length - 1]
if (!this.currChannel || this.currChannel < min || this.currChannel > max) {
this.$message.warn("Can't insert Control Point out of range")
return
}
const chart = this.$refs.chartRef.getChartInstance()
const controlPointList = this.option.graphic
const find = controlPointList.find(item => {
return item.position[0] == xPix
})
if (find) {
return
}
let i = 0 // 记录新控制点在列表中的位置
const [xPix] = chart.convertToPixel('grid', [this.currChannel, 0])
for (; i < controlPointList.length; ++i) {
const currCP = controlPointList[i].position[0]
if (currCP >= xPix) {
if (currCP == xPix) {
this.$message.warn(`The new control point in channel ${this.currChannel} exists, can't introduce twice`)
return
}
break
}
}
const baseLineEditSeries = findSeriesByName(this.option.series, 'BaseLine_Edit')
const yAxis = baseLineEditSeries.data[this.currChannel - 1][1]
this.option.graphic.splice(
i,
0,
this.buildGraphicPoint(chart, this.currChannel, yAxis, this.option.graphic.length)
)
this._channelBaseCPChart.splice(i, 0, { point: { x: this.currChannel, y: yAxis } })
},
// 移除控制点
handleRemoveCP() {
// find nearest control-point
const chart = this.$refs.chartRef.getChartInstance()
const controlPointList = this.option.graphic
let i = 1
for (; i < controlPointList.length; ++i) {
const [currX, currY] = controlPointList[i].position
const [currXAxis] = getXAxisAndYAxisByPosition(chart, currX, currY)
if (currXAxis >= this.currChannel) {
const [prevX, prevY] = controlPointList[i - 1].position
const [prevXAxis] = getXAxisAndYAxisByPosition(chart, prevX, prevY)
if (currXAxis - this.currChannel > this.currChannel - prevXAxis) --i
break
}
}
if (i == 0 || i >= controlPointList.length - 1) {
this.$message.warn("Can't remove first/last control point")
return
}
controlPointList.splice(i, 1)
},
// 修改控制点
handleModifyCP() {
this.setGraphicDraggable(true)
},
// 确定对Control Point 的操作
handleAccept() {},
// 右下角添加当前选中的nuclide
handleAddNuclide() {
const nuclides = this.selectedTableItem.nuclides
const possibleNuclide = this.model.possibleNuclide
if (this.selectedTableItem && !nuclides.includes(possibleNuclide)) {
nuclides.push(possibleNuclide)
}
},
// 右下角删除当前选中的nuclide
handleDelNuclide() {
const [willDelKey] = this.selectedKeys
const find = this.list.find(item => item.index == willDelKey)
console.log('%c [ find ]-762', 'font-size:13px; background:pink; color:#bf2c9f;', find)
if (find) {
const nuclides = find.nuclides
const findIndex = nuclides.findIndex(nuclide => nuclide == this.model.identifiedNuclide)
if (-1 !== findIndex) {
nuclides.splice(findIndex, 1)
}
}
}
}
}
</script>
<style lang="less" scoped>
.interactive-analysis-tools {
display: flex;
&-left {
width: 75%;
margin-right: 20px;
.chart {
height: 331px;
}
.thumbnail {
height: 50px;
margin: 10px 10px 35px 40px;
background-color: #255369;
}
.table {
.title {
color: #0cebc9;
font-size: 20px;
text-align: center;
margin-bottom: 10px;
user-select: none;
span {
cursor: pointer;
margin: 0 5px;
}
}
.custom-table {
&.has-data {
::v-deep {
.ant-table-body {
height: 288px;
background-color: #06282a;
}
}
}
::v-deep {
.ant-table-placeholder {
height: 289px;
display: flex;
justify-content: center;
align-items: center;
}
}
}
.operators {
display: flex;
margin-top: 10px;
gap: 10px;
.ant-btn {
flex: 1;
}
}
}
}
&-right {
.peak-box {
height: 326px;
&-item:not(:last-child) {
margin-bottom: 10px;
}
.symbol {
display: flex;
.ant-btn {
flex: 1;
&:first-child {
margin-right: 10px;
}
}
}
}
.base-line {
margin-top: 136px;
}
.reset-btn-box {
margin-top: 20px;
margin-bottom: 30px;
}
.identify-box {
.tolerance {
::v-deep {
.ant-form-item {
margin-bottom: 10px;
&-control-wrapper {
flex: 1;
}
&-control {
width: 100%;
}
}
}
.ant-input-number {
width: 100%;
}
}
.identify-item {
.title {
background-color: #497e9d;
height: 30px;
line-height: 30px;
text-align: center;
font-size: 16px;
}
.content {
height: 80px;
background-color: #275466;
margin: 10px 0;
padding: 5px;
overflow: auto;
.item {
cursor: pointer;
line-height: 26px;
padding: 0 5px;
&.active {
background: #296d81;
}
}
}
}
.identify-operators {
display: flex;
gap: 10px;
.text {
flex: 1;
line-height: 32px;
background-color: #285366;
padding: 0 10px;
}
.ant-btn {
width: 50px;
padding-left: 5px;
padding-right: 5px;
}
}
}
}
.ant-btn {
width: 100%;
}
}
</style>