1996 lines
54 KiB
Vue
1996 lines
54 KiB
Vue
<template>
|
||
<custom-modal
|
||
v-model="visible"
|
||
:width="1280"
|
||
class="interactive-analysis-tools-dialog"
|
||
:enableFullScreen="true"
|
||
title="Interactive Analyse Tools"
|
||
:footer="null"
|
||
destroy-on-close
|
||
@fullscreen="handleFullScreenChange"
|
||
>
|
||
<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"
|
||
/>
|
||
<rect-list
|
||
ref="rectListRef"
|
||
:chartRef="$refs.chartRef"
|
||
:baseControls="baseCtrls_Copy"
|
||
:draggable="isModifying"
|
||
@move="handleMove"
|
||
@moveEnd="handleMoveEnd"
|
||
/>
|
||
</div>
|
||
<!-- 缩略图 -->
|
||
<div class="thumbnail">
|
||
<CustomChart ref="thumbnailRef" :option="thumbnailOption" />
|
||
</div>
|
||
<!-- 缩略图结束 -->
|
||
|
||
<!-- 表格 -->
|
||
<div class="table">
|
||
<p class="title">
|
||
<span @click="handleChangeMarkLine('prev')">< </span>
|
||
6 Peaks with Anthro.Nuclides
|
||
<span @click="handleChangeMarkLine('next')">></span>
|
||
</p>
|
||
<custom-table
|
||
ref="tableRef"
|
||
size="small"
|
||
: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>
|
||
<!-- 表格结束 -->
|
||
|
||
<resize-observer @notify="handleResize" />
|
||
</div>
|
||
|
||
<!-- 右侧 -->
|
||
<div class="interactive-analysis-tools-right">
|
||
<title-over-border class="peak-box-container" :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" :class="{ 'is-fitting': isFitting }" @click="handleFit">Fit</a-button>
|
||
</div>
|
||
|
||
<div class="peak-box-item symbol" :key="4">
|
||
<a-button type="primary" @click="handleChangeMarkLine('prev')"><</a-button>
|
||
<a-button type="primary" @click="handleChangeMarkLine('next')">></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" key="modify-btn" :class="{ 'is-modify': isModifying }" @click="handleModifyCP"
|
||
>(M)odify CP</a-button
|
||
>
|
||
</div>
|
||
<div class="peak-box-item">
|
||
<a-button type="primary" @click="handleEditSlope">Edit (S)lope</a-button>
|
||
</div>
|
||
<div class="peak-box-item">
|
||
<a-button type="primary" :disabled="isOperationStackEmpty" @click="handleUndo">Undo</a-button>
|
||
</div>
|
||
<div class="peak-box-item">
|
||
<a-button type="primary" :loading="isReploting" @click="handleReplot">Replot</a-button>
|
||
</div>
|
||
<div class="peak-box-item">
|
||
<a-button type="primary" :loading="isAccepting" @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"
|
||
:step="0.1"
|
||
:min="0"
|
||
@change="handleToleranceChange"
|
||
></a-input-number>
|
||
</a-form-model-item>
|
||
</a-form-model>
|
||
<div class="identify-item">
|
||
<div class="title">Possible Nuclide</div>
|
||
<a-spin :spinning="!!(selectedTableItem && selectedTableItem._loading)">
|
||
<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>
|
||
</a-spin>
|
||
</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>
|
||
<!-- 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"
|
||
:channel_1="channel_1"
|
||
:channel_2="channel_2"
|
||
:isInsertPeak="isInsertPeak"
|
||
@result="handleInsertSuccess"
|
||
@cancel="handleCancelSuccess"
|
||
/>
|
||
<!-- Fit Peaks and Baseline弹窗 结束 -->
|
||
<!-- Nuclide Review 弹窗开始 -->
|
||
<nuclide-review-modal v-model="nuclideReviewModalVisible" :sampleId="sampleId" :channel="currChannel" />
|
||
<!-- Nuclide Review 弹窗结束 -->
|
||
|
||
<!-- Edit Slope 弹窗 -->
|
||
<edit-slope-modal ref="editSlopeModal" @change="handleSlopeChange" />
|
||
<!-- Edit Slope 结束 -->
|
||
</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, postAction } from '@/api/manage'
|
||
import { cloneDeep } from 'lodash'
|
||
import { buildLineSeries, findSeriesByName, getXAxisAndYAxisByPosition, rangeNumber } from '@/utils/chartHelper'
|
||
import SampleDataMixin from '@/views/spectrumAnalysis/SampleDataMixin'
|
||
import GeneralCommentModal from './components/GeneralCommentModal.vue'
|
||
import EditSlopeModal from './components/EditSlopeModal.vue'
|
||
// import Response from './Response.json'
|
||
import { updateBaseLine } from '@/utils/WasmHelper'
|
||
import RectList from './components/RectList.vue'
|
||
import { isNullOrUndefined } from '@/utils/util'
|
||
import { findNearPeak, getLineData, transformPointListData } from '@/utils/sampleHelper'
|
||
import { getSampleData } from '@/utils/SampleStore'
|
||
|
||
// 初始配置
|
||
const initialOption = {
|
||
grid: {
|
||
top: 40,
|
||
left: 80,
|
||
right: 30,
|
||
bottom: 30,
|
||
},
|
||
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: {},
|
||
}
|
||
|
||
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 == 'Infinity' ? 'inf' : text.toFixed(3)
|
||
},
|
||
},
|
||
{
|
||
title: '#Cmnt',
|
||
dataIndex: 'comments',
|
||
width: 120,
|
||
},
|
||
{
|
||
title: 'Nuclides',
|
||
dataIndex: 'nuclides',
|
||
width: 120,
|
||
ellipsis: true,
|
||
customRender: (text) => {
|
||
return text && text.join(';')
|
||
},
|
||
},
|
||
]
|
||
|
||
// 缩略图配置
|
||
const thumbnailOption = {
|
||
grid: {
|
||
top: 0,
|
||
left: 0,
|
||
right: 0,
|
||
bottom: 0,
|
||
},
|
||
xAxis: {
|
||
type: 'category',
|
||
axisLine: {
|
||
lineStyle: {
|
||
color: '#fff',
|
||
},
|
||
},
|
||
splitLine: {
|
||
show: false,
|
||
},
|
||
axisLabel: {
|
||
show: false,
|
||
},
|
||
axisTick: {
|
||
show: false,
|
||
},
|
||
min: 1,
|
||
max: 'dataMax',
|
||
},
|
||
yAxis: {
|
||
type: 'value',
|
||
axisLine: {
|
||
show: false,
|
||
},
|
||
splitLine: {
|
||
show: false,
|
||
},
|
||
axisLabel: {
|
||
show: false,
|
||
},
|
||
max: 0,
|
||
min: 0,
|
||
},
|
||
series: null,
|
||
}
|
||
|
||
const nuclideIdentifyModal = {
|
||
possibleNuclide: '',
|
||
tolerance: 0.5,
|
||
identifiedNuclide: '',
|
||
}
|
||
|
||
// 操作类型
|
||
const Operators = {
|
||
ADD: 1, // 新增
|
||
REMOVE: 2, // 移除
|
||
MODIFY: 3, // 改变
|
||
SLOPE_CHANGE: 4, // 改变slope
|
||
}
|
||
|
||
export default {
|
||
mixins: [ModalMixin, SampleDataMixin],
|
||
components: {
|
||
CustomChart,
|
||
TitleOverBorder,
|
||
PeakCommentModal,
|
||
FitPeaksAndBaseLineModal,
|
||
NuclideReviewModal,
|
||
GeneralCommentModal,
|
||
EditSlopeModal,
|
||
RectList,
|
||
},
|
||
props: {
|
||
colorConfig: {
|
||
type: Object,
|
||
default: () => ({}),
|
||
},
|
||
},
|
||
data() {
|
||
return {
|
||
columns,
|
||
searchParam: {
|
||
energy: '',
|
||
tolerance: '',
|
||
},
|
||
option: cloneDeep(initialOption),
|
||
opts: { notMerge: false },
|
||
thumbnailOption: cloneDeep(thumbnailOption),
|
||
channelBaseCPChart: [],
|
||
channelBaseLineChart: [],
|
||
channelCountChart: [],
|
||
channelPeakChart: [],
|
||
barChart: [],
|
||
energy: [],
|
||
list: [],
|
||
BaseCtrls: {},
|
||
baseCtrls_Copy: {},
|
||
sampleId: -1,
|
||
|
||
peakCommentModalVisible: false, // Comment 弹窗是否显示
|
||
generalCommentModalVisible: false, // Comment 弹窗是否显示
|
||
|
||
btnGroupType: 1, // 右侧 Peak 中的按钮组切换
|
||
|
||
selectedKeys: [], // 选中的列表
|
||
|
||
fitPeaksAndBaselineModalVisible: false, // Fit Peaks And Base Line 弹窗
|
||
nuclideReviewModalVisible: false, // Nuclide Review 弹窗
|
||
|
||
model: cloneDeep(nuclideIdentifyModal),
|
||
|
||
currChannel: undefined, // 当currChannel前选中的channel
|
||
|
||
channel_1: undefined, // 用于Fit Peaks And Baseline Modal的道值
|
||
channel_2: undefined,
|
||
isInsertPeak: false, // 是否是插入Peak
|
||
|
||
selectedTableItem: undefined, // 当前选中的表格项
|
||
|
||
isModifying: false, // 正在修改控制点
|
||
isFitting: false, // 正在进行Fit操作
|
||
firstFittingChannel: null, // Fit操作时点击的第一个channel
|
||
isAccepting: false,
|
||
isReploting: false,
|
||
|
||
operationStack: [], // 操作记录
|
||
replotNeeded: false,
|
||
}
|
||
},
|
||
created() {
|
||
this.option.tooltip.formatter = (params) => {
|
||
const channel = parseInt(params[0].value[0])
|
||
const energy = this.energy.pointlist ? this.energy.pointlist[channel - 1].x : 0
|
||
return `<div class="channel">Channel: ${channel}</div>
|
||
<div class="energy">${isNullOrUndefined(energy) ? '' : `Energy: ${energy.toFixed(2)}`}</div>`
|
||
}
|
||
},
|
||
methods: {
|
||
getInfo() {
|
||
this.option.series = []
|
||
this.thumbnailOption.series = []
|
||
this.list = []
|
||
this.model = cloneDeep(nuclideIdentifyModal)
|
||
|
||
const { inputFileName } = this.sampleData
|
||
|
||
const currSampleDetailInfo = getSampleData(inputFileName)
|
||
const {
|
||
data: { allData, shadowChannelChart, shapeChannelData, peak, BaseCtrls, barChart },
|
||
} = currSampleDetailInfo
|
||
|
||
const channelBaseLine = getLineData(allData, 'BaseLine', 'channel')
|
||
const channelPeakGroup = getLineData(allData, 'Peak', 'channel', true)
|
||
|
||
const allEnergy = getLineData(allData, 'Energy', 'energy')
|
||
|
||
this.channelBaseCPChart = shapeChannelData
|
||
this.channelBaseLineChart = channelBaseLine
|
||
this.channelCountChart = shadowChannelChart
|
||
this.channelPeakChart = channelPeakGroup
|
||
this.energy = allEnergy
|
||
this.BaseCtrls = BaseCtrls
|
||
this.barChart = barChart
|
||
|
||
this.setChartOption(channelBaseLine, shadowChannelChart, channelPeakGroup, shapeChannelData, barChart)
|
||
this.list = peak
|
||
},
|
||
|
||
setChartOption(baseLine, count, peaks, baseCP, bar) {
|
||
const series = []
|
||
|
||
// 推入BaseLine
|
||
series.push(this.buildBaseLine(baseLine))
|
||
|
||
// 推入Count
|
||
series.push(this.buildCountLine(count))
|
||
|
||
// 推入Peak
|
||
series.push(...this.buildPeaks(peaks))
|
||
|
||
// 推入基线控制点
|
||
series.push(this.buildCtrlPoint(baseCP))
|
||
this.option.series = series
|
||
|
||
this.thumbnailOption.series = this.buildBarChart(bar)
|
||
this.setThumbnailRange(1, bar.length)
|
||
},
|
||
|
||
reset() {
|
||
this.currChannel = undefined
|
||
this.btnGroupType = 1
|
||
this.opts.notMerge = false
|
||
this.isFitting = false
|
||
this.replotNeeded = false
|
||
this.selectedTableItem = null
|
||
this.$nextTick(() => {
|
||
this.option.brush = { toolbox: [] }
|
||
const firstLine = this.list[0]
|
||
if (firstLine) {
|
||
this.handleTableRowClick(firstLine)
|
||
this.selectedKeys = [firstLine.index]
|
||
}
|
||
})
|
||
this.clearRect()
|
||
this.handleResetChart()
|
||
},
|
||
|
||
beforeModalOpen() {
|
||
this.getInfo()
|
||
this.reset()
|
||
},
|
||
|
||
// 点击图表,设置红线,改变表格的选中项
|
||
handleChartClick(param) {
|
||
const { offsetX, offsetY } = param
|
||
const point = getXAxisAndYAxisByPosition(this.$refs.chartRef.getChartInstance(), offsetX, offsetY)
|
||
if (point) {
|
||
const xAxis = Math.round(point[0])
|
||
this.setMarkLineXAxis(xAxis)
|
||
|
||
this.currChannel = xAxis
|
||
const { index } = findNearPeak(xAxis, this.list)
|
||
|
||
if (this.list.length) {
|
||
const selectedRow = this.list[index]
|
||
this.selectTableRow(selectedRow.index, index)
|
||
this.getSelPosNuclide(selectedRow)
|
||
this.selectedTableItem = selectedRow
|
||
}
|
||
|
||
// 如果点击了Fit按钮
|
||
if (this.isFitting) {
|
||
this.handleFittingChannel(xAxis)
|
||
}
|
||
}
|
||
},
|
||
|
||
// 处理Fit中的两个channel
|
||
handleFittingChannel(channel) {
|
||
// 第一个channel存在,又点了一次,则是第二次点击
|
||
if (this.firstFittingChannel !== null) {
|
||
// 查找两个channel之间的Peak峰
|
||
|
||
let left = this.firstFittingChannel
|
||
let right = channel
|
||
if (left > right) {
|
||
right = left
|
||
left = channel
|
||
}
|
||
|
||
const peaksBetweenChannel = this.list.filter((peak) => {
|
||
const centroidId = peak.peakCentroid
|
||
return centroidId >= left && centroidId <= right
|
||
})
|
||
|
||
if (!peaksBetweenChannel.length) {
|
||
this.$message.warn(`There are 0 peak between channel ${left} and ${right}`)
|
||
this.isFitting = false
|
||
return
|
||
}
|
||
|
||
this.channel_1 = left
|
||
this.channel_2 = right
|
||
this.isInsertPeak = false
|
||
this.fitPeaksAndBaselineModalVisible = true
|
||
|
||
this.isFitting = false
|
||
this.firstFittingChannel = null
|
||
} else {
|
||
this.firstFittingChannel = channel
|
||
}
|
||
},
|
||
|
||
// 切换图表上的红色竖线及表格选中
|
||
handleChangeMarkLine(direction) {
|
||
const prevAxis = this.getMarkLineXAxis()
|
||
let i,
|
||
size = this.list.length
|
||
if (direction == 'next') {
|
||
for (i = 0; i < size; i++) {
|
||
const centroid = Math.round(this.list[i].peakCentroid)
|
||
if (centroid > prevAxis) {
|
||
this.setMarkLineXAxis(centroid)
|
||
const selectedRow = this.list[i]
|
||
this.selectedTableItem = selectedRow
|
||
this.selectTableRow(selectedRow.index, i)
|
||
this.getSelPosNuclide(selectedRow)
|
||
return
|
||
}
|
||
}
|
||
} else if (direction == 'prev') {
|
||
for (i = size - 1; i >= 0; i--) {
|
||
const centroid = Math.round(this.list[i].peakCentroid)
|
||
if (centroid < prevAxis) {
|
||
this.setMarkLineXAxis(centroid)
|
||
const selectedRow = this.list[i]
|
||
this.selectedTableItem = selectedRow
|
||
this.selectTableRow(selectedRow.index, i)
|
||
this.getSelPosNuclide(selectedRow)
|
||
return
|
||
}
|
||
}
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 根据当前Peak调整缩放范围
|
||
*/
|
||
adjustArea() {
|
||
const {
|
||
xAxis: { max, min },
|
||
} = this.option
|
||
|
||
// 找到最高点在这个范围内的峰
|
||
const peaks = []
|
||
for (let i = 0; i < this.channelPeakChart.length; i++) {
|
||
const peak = this.channelPeakChart[i]
|
||
const pointlist = peak.pointlist
|
||
|
||
// 如果 第一个/最后一个 都 大于/小于 范围,跳过
|
||
if (pointlist[0].x > max || pointlist[pointlist.length - 1].x < min) {
|
||
continue
|
||
}
|
||
|
||
// 找到峰最大值
|
||
const peakMaxY = Math.max(...pointlist.map(({ y }) => y))
|
||
const find = pointlist.find(({ y }) => y == peakMaxY)
|
||
|
||
// 如果最大值在范围内
|
||
if (find.x >= min && find.x <= max) {
|
||
peaks.push({
|
||
max: peakMaxY,
|
||
min: Math.min(pointlist[0].y, pointlist[pointlist.length - 1].y),
|
||
})
|
||
}
|
||
}
|
||
|
||
const peaksMax = Math.max(...peaks.map(({ max }) => max))
|
||
const peaksMin = Math.min(...peaks.map(({ min }) => min))
|
||
|
||
const {
|
||
yAxis: { max: yAxisMax, min: yAxisMin },
|
||
} = this.option
|
||
|
||
if (peaksMax > yAxisMax) {
|
||
this.option.yAxis.max = Math.ceil(peaksMax)
|
||
}
|
||
|
||
if (peaksMin < yAxisMin) {
|
||
this.option.yAxis.min = Math.floor(peaksMin)
|
||
}
|
||
},
|
||
|
||
selectTableRow(key, index) {
|
||
this.selectedKeys = [key]
|
||
this.$refs.tableRef.scrollIntoView(index)
|
||
},
|
||
|
||
// 设置红色标记线的位置
|
||
setMarkLineXAxis(xAxis) {
|
||
const markLineOption = this.option.series[0].markLine.data[0]
|
||
markLineOption.xAxis = xAxis
|
||
|
||
const { xAxis: chartXAxisOption } = this.option
|
||
const { max, min } = chartXAxisOption
|
||
|
||
// 如果不在范围内
|
||
if (xAxis >= max || xAxis <= min) {
|
||
const halfDiff = (max - min) / 2
|
||
const lastChannel = this.channelCountChart.pointlist[this.channelCountChart.pointlist.length - 1].x
|
||
let nextMax = xAxis + halfDiff
|
||
let nextMin = xAxis - halfDiff
|
||
chartXAxisOption.max = nextMax > lastChannel ? lastChannel : nextMax
|
||
chartXAxisOption.min = nextMin < 1 ? 1 : nextMin
|
||
|
||
this.setThumbnailRange(chartXAxisOption.min, chartXAxisOption.max)
|
||
this.adjustArea()
|
||
}
|
||
},
|
||
|
||
getMarkLineXAxis() {
|
||
const markLineOption = this.option.series[0].markLine.data[0]
|
||
return markLineOption.xAxis
|
||
},
|
||
|
||
// 获取右下角possible nuclide 和 identified nuclide
|
||
async getSelPosNuclide(row) {
|
||
this.model.possibleNuclide = ''
|
||
this.model.identifiedNuclide = ''
|
||
this.model.tolerance = 0.5
|
||
|
||
if (!row._possible) {
|
||
this.$set(row, '_loading', true)
|
||
try {
|
||
const { sampleId, inputFileName: fileName } = this.sampleData
|
||
const { success, result, message } = await getAction('/gamma/getSelPosNuclide', {
|
||
sampleId,
|
||
channel: Math.round(row.peakCentroid),
|
||
fileName,
|
||
})
|
||
if (success) {
|
||
const { possible } = result
|
||
this.$set(row, '_possible', possible)
|
||
} else {
|
||
this.$message.error(message)
|
||
}
|
||
} catch (error) {
|
||
console.error(error)
|
||
} finally {
|
||
row._loading = false
|
||
}
|
||
}
|
||
},
|
||
async handleToleranceChange(val) {
|
||
// this.selectedTableItem._loading = true
|
||
this.searchParam.energy = this.selectedTableItem.energy
|
||
this.searchParam.tolerance = val
|
||
try {
|
||
const { sampleId, inputFileName: fileName } = this.sampleData
|
||
const { success, result, message } = await getAction('/gamma/searchNuclide', {
|
||
sampleId,
|
||
fileName,
|
||
...this.searchParam,
|
||
})
|
||
if (success) {
|
||
const { list } = result
|
||
this.selectedTableItem._possible = list
|
||
} else {
|
||
this.$message.error(message)
|
||
}
|
||
} catch (error) {
|
||
console.error(error)
|
||
} finally {
|
||
// this.selectedTableItem._loading = false
|
||
}
|
||
},
|
||
|
||
// 显示peak comment弹窗
|
||
handleAddPeakComment() {
|
||
if (!this.selectedKeys.length) {
|
||
this.$message.warn('Please Select a Peak that You Want to Add Comment!')
|
||
return
|
||
}
|
||
this.peakCommentModalVisible = true
|
||
},
|
||
|
||
// 显示general comment弹窗
|
||
handleAddGeneralComment() {
|
||
this.generalCommentModalVisible = true
|
||
},
|
||
|
||
// Insert按钮
|
||
handleInsert() {
|
||
this.isFitting = false
|
||
const { rg_high, rg_low } = this.BaseCtrls
|
||
if (!this.currChannel || this.currChannel <= rg_low + 1 || this.currChannel >= rg_high - 1) {
|
||
this.$message.warn("Couldn't insert peak, maybe out of range")
|
||
return
|
||
}
|
||
|
||
this.channel_1 = this.currChannel
|
||
|
||
this.fitPeaksAndBaselineModalVisible = true
|
||
this.isInsertPeak = true
|
||
},
|
||
|
||
// 点击 Fit Peak XXX 弹窗中的 Peaks 按钮
|
||
handleInsertSuccess(result) {
|
||
const {
|
||
allData,
|
||
barChart,
|
||
channelBaseLineChart,
|
||
channelPeakChart,
|
||
shadowChannelChart,
|
||
shadowEnergyChart,
|
||
shapeChannelData,
|
||
shapeEnergyData,
|
||
table,
|
||
} = result
|
||
|
||
this.$bus.$emit('gammaRefresh', {
|
||
allData,
|
||
channelPeakChart,
|
||
shadowChannelChart,
|
||
shadowEnergyChart,
|
||
shapeChannelData,
|
||
shapeEnergyData,
|
||
peak: table,
|
||
barChart: this.barChart,
|
||
})
|
||
|
||
this.channelPeakChart = channelPeakChart
|
||
this.channelBaseLineChart = channelBaseLineChart
|
||
this.barChart = barChart
|
||
|
||
this.setChartOption(
|
||
channelBaseLineChart,
|
||
this.channelCountChart,
|
||
channelPeakChart,
|
||
this.channelBaseCPChart,
|
||
barChart
|
||
)
|
||
this.list = table
|
||
this.handleTableRowClick(this.list[this.curRow])
|
||
},
|
||
|
||
// 点击 Fit Peak XXX 弹窗中的 Cancel 按钮
|
||
handleCancelSuccess(result) {
|
||
const { channelPeakChart, table } = result
|
||
this.channelPeakChart = channelPeakChart
|
||
|
||
this.setChartOption(
|
||
this.channelBaseLineChart,
|
||
this.channelCountChart,
|
||
channelPeakChart,
|
||
this.channelBaseCPChart,
|
||
this.barChart
|
||
)
|
||
|
||
this.list = table
|
||
},
|
||
|
||
// 删除
|
||
handleDel() {
|
||
this.isFitting = false
|
||
|
||
if (!this.selectedKeys.length) {
|
||
this.$message.warn('No peak to delete.')
|
||
return
|
||
}
|
||
|
||
this.$confirm({
|
||
title: 'Warning',
|
||
content: 'Are you sure to delete this peak?',
|
||
cancelButtonProps: {
|
||
props: {
|
||
type: 'warn',
|
||
},
|
||
},
|
||
onOk: async () => {
|
||
// 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: this.curRow,
|
||
})
|
||
if (success) {
|
||
const {
|
||
allData,
|
||
channelPeakChart,
|
||
shadowChannelChart,
|
||
shadowEnergyChart,
|
||
shapeChannelData,
|
||
shapeEnergyData,
|
||
table,
|
||
} = result
|
||
|
||
this.$bus.$emit('gammaRefresh', {
|
||
allData,
|
||
channelPeakChart,
|
||
shadowChannelChart,
|
||
shadowEnergyChart,
|
||
shapeChannelData,
|
||
shapeEnergyData,
|
||
peak: table,
|
||
barChart: this.barChart,
|
||
})
|
||
|
||
this.opts.notMerge = true
|
||
this.channelPeakChart = channelPeakChart
|
||
const series = []
|
||
// 推入旧的BaseLine
|
||
series.push(this.buildBaseLine(this.channelBaseLineChart))
|
||
|
||
// 推入旧的Count
|
||
series.push(this.buildCountLine(this.channelCountChart))
|
||
|
||
// 推入Peak
|
||
series.push(...this.buildPeaks(channelPeakChart))
|
||
|
||
// 推入旧的基线控制点
|
||
series.push(this.buildCtrlPoint(this.channelBaseCPChart))
|
||
|
||
this.list = table
|
||
|
||
this.option.series = series
|
||
this.$nextTick(() => {
|
||
this.resetChartOpts()
|
||
})
|
||
|
||
this.selectedKeys = []
|
||
|
||
this.selectedTableItem = null
|
||
} else {
|
||
this.$message.error(message)
|
||
}
|
||
} catch (error) {
|
||
console.error(error)
|
||
}
|
||
},
|
||
})
|
||
},
|
||
|
||
// 重置图表配置
|
||
resetChartOpts() {
|
||
this.opts.notMerge = false
|
||
this.option.brush = { toolbox: [] }
|
||
},
|
||
|
||
// 匹配
|
||
handleFit() {
|
||
if (!this.channelPeakChart || !this.channelPeakChart.length) {
|
||
this.$message.warn('No peak to fit.')
|
||
return
|
||
}
|
||
|
||
this.isFitting = true
|
||
this.firstFittingChannel = null
|
||
},
|
||
|
||
// 表格的行点击
|
||
handleTableRowClick(row, index) {
|
||
if (this.selectedTableItem == row) {
|
||
return
|
||
}
|
||
|
||
const channel = Math.round(row.peakCentroid)
|
||
this.currChannel = channel
|
||
|
||
this.option.series[0].markLine.data[0].xAxis = channel
|
||
|
||
const { xAxis: chartXAxisOption } = this.option
|
||
const { max, min } = chartXAxisOption
|
||
|
||
// 如果不在范围内
|
||
if (channel >= max || channel <= min) {
|
||
const halfDiff = (max - min) / 2
|
||
const lastChannel = this.channelCountChart.pointlist[this.channelCountChart.pointlist.length - 1].x
|
||
let nextMax = channel + halfDiff
|
||
let nextMin = channel - halfDiff
|
||
chartXAxisOption.max = nextMax > lastChannel ? lastChannel : nextMax
|
||
chartXAxisOption.min = nextMin < 1 ? 1 : nextMin
|
||
|
||
this.setThumbnailRange(chartXAxisOption.min, chartXAxisOption.max)
|
||
}
|
||
|
||
this.getSelPosNuclide(row)
|
||
|
||
this.selectedTableItem = row
|
||
|
||
this.adjustArea()
|
||
},
|
||
|
||
// 鼠标按下时开启可刷选状态
|
||
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.adjustArea()
|
||
|
||
this.setThumbnailRange(x1, x2)
|
||
if (this.btnGroupType == 2) {
|
||
this.buildRect()
|
||
}
|
||
}
|
||
this.clearBrush(chart)
|
||
},
|
||
|
||
// 查找barChart范围内的最大值
|
||
setThumbnailRange(x1, x2) {
|
||
const slicedArr = this.barChart.slice(x1 - 1, x2)
|
||
const yData = slicedArr.map(({ y }) => y)
|
||
const max = Math.max(...yData)
|
||
const min = Math.min(...yData)
|
||
const thumbnailYMax = Math.max(Math.abs(max), Math.abs(min))
|
||
|
||
this.thumbnailOption.xAxis.min = x1
|
||
this.thumbnailOption.xAxis.max = x2
|
||
this.thumbnailOption.yAxis.max = thumbnailYMax
|
||
this.thumbnailOption.yAxis.min = -thumbnailYMax
|
||
},
|
||
|
||
handleResetChart() {
|
||
this.option.xAxis.min = 1
|
||
this.option.xAxis.max = 'dataMax'
|
||
this.option.yAxis.min = 0.1
|
||
this.option.yAxis.max = 'dataMax'
|
||
|
||
this.setThumbnailRange(1, this.barChart.length)
|
||
|
||
if (this.btnGroupType == 2) {
|
||
this.buildRect()
|
||
}
|
||
},
|
||
|
||
// 切换操作
|
||
handleSwitchOperation() {
|
||
// 切换到Base Line 和 Control Point 操作
|
||
if (this.btnGroupType == 1) {
|
||
this.btnGroupType = 2
|
||
this.baseCtrls_Copy = cloneDeep(this.BaseCtrls)
|
||
this.replotNeeded = false
|
||
|
||
// 供编辑的白色基线
|
||
const baseLineEditSeries = buildLineSeries(
|
||
'BaseLine_Edit',
|
||
this.baseCtrls_Copy.baseline.map((val, index) => [index + 1, val]),
|
||
this.colorConfig.Color_Fitbase || '#fff',
|
||
{
|
||
zlevel: 21,
|
||
}
|
||
)
|
||
this.option.series.push(baseLineEditSeries)
|
||
|
||
this.$nextTick(() => {
|
||
this.buildRect()
|
||
})
|
||
}
|
||
// 切换回 Peak 操作
|
||
else {
|
||
this.btnGroupType = 1
|
||
this.opts.notMerge = true
|
||
const baseLineEditSeries = findSeriesByName(this.option.series, 'BaseLine_Edit')
|
||
const index = this.option.series.findIndex((item) => item == baseLineEditSeries)
|
||
this.option.series.splice(index, 1)
|
||
|
||
this.clearRect()
|
||
|
||
const baseLineSeries = findSeriesByName(this.option.series, 'BaseLine')
|
||
baseLineSeries.data = transformPointListData(this.channelBaseLineChart.pointlist) // 恢复基线
|
||
|
||
const baseLineCP = findSeriesByName(this.option.series, 'BaseLine_Ctrl_Point')
|
||
baseLineCP.data = this.buildCPPointData(this.channelBaseCPChart)
|
||
|
||
this.redrawPeaks(this.channelPeakChart)
|
||
|
||
this.$nextTick(() => {
|
||
this.resetChartOpts()
|
||
})
|
||
}
|
||
|
||
this.isModifying = false
|
||
this.isFitting = false
|
||
this.clearOperationStack()
|
||
},
|
||
|
||
// 根据数据绘制小方块
|
||
buildRect() {
|
||
this.$refs.rectListRef.init()
|
||
},
|
||
|
||
clearRect() {
|
||
const rectListRef = this.$refs.rectListRef
|
||
if (rectListRef) {
|
||
rectListRef.clear()
|
||
}
|
||
},
|
||
|
||
// 小方块移动
|
||
handleMove(yctrl) {
|
||
this.baseCtrls_Copy.yctrl = yctrl
|
||
this.redrawBaseLine()
|
||
},
|
||
|
||
// 小方块移动完毕
|
||
handleMoveEnd(prevYAxis, index) {
|
||
this.isModifying = false
|
||
this.pushOperationStack(Operators.MODIFY, {
|
||
index,
|
||
prevYAxis,
|
||
})
|
||
},
|
||
|
||
// 重新生成基线
|
||
redrawBaseLine() {
|
||
this.replotNeeded = true
|
||
try {
|
||
console.time('updateBaseLine')
|
||
const res = updateBaseLine(JSON.stringify(this.baseCtrls_Copy))
|
||
console.timeEnd('updateBaseLine')
|
||
const parsed = JSON.parse(res)
|
||
const { baseline } = parsed
|
||
const baseLineEditSeries = findSeriesByName(this.option.series, 'BaseLine_Edit')
|
||
baseLineEditSeries.data = baseline.map((val, index) => [index + 1, val])
|
||
|
||
this.baseCtrls_Copy.baseline = baseline
|
||
} catch (error) {
|
||
console.error(error)
|
||
}
|
||
},
|
||
|
||
// 重绘Peaks
|
||
redrawPeaks(peakList) {
|
||
this.option.series = this.option.series.filter((item) => {
|
||
return !item.name.includes('Peak_')
|
||
})
|
||
this.option.series.push(...this.buildPeaks(peakList))
|
||
},
|
||
|
||
/**
|
||
* 设置小方块可拖拽
|
||
*/
|
||
setGraphicDraggable(draggable) {
|
||
this.isModifying = draggable
|
||
},
|
||
|
||
// 在当前选中的红线位置新增控制点
|
||
handleAddCP() {
|
||
this.setGraphicDraggable(false)
|
||
const { rg_high, rg_low } = this.BaseCtrls
|
||
if (!this.currChannel || this.currChannel < rg_low || this.currChannel > rg_high) {
|
||
this.$message.warn("Can't insert Control Point out of range")
|
||
return
|
||
}
|
||
|
||
const { xctrl, yctrl, baseline, yslope } = this.baseCtrls_Copy
|
||
|
||
let i = 0 // 记录新控制点在列表中的位置
|
||
for (; i < xctrl.length; ++i) {
|
||
const currCP = xctrl[i]
|
||
if (currCP >= this.currChannel) {
|
||
if (currCP == this.currChannel) {
|
||
this.$message.warn(`The new control point in channel ${this.currChannel} exists, can't introduce twice`)
|
||
return
|
||
}
|
||
break
|
||
}
|
||
}
|
||
|
||
// 新增的控制点跟基线的值平齐
|
||
const yAxis = baseline[this.currChannel - 1]
|
||
xctrl.splice(i, 0, this.currChannel)
|
||
yctrl.splice(i, 0, yAxis)
|
||
yslope.splice(i, 0, 0)
|
||
this.buildRect()
|
||
this.pushOperationStack(Operators.ADD, { index: i })
|
||
},
|
||
|
||
// 移除控制点
|
||
handleRemoveCP() {
|
||
this.setGraphicDraggable(false)
|
||
|
||
const { xctrl, yctrl, yslope } = this.baseCtrls_Copy
|
||
|
||
// find nearest control-point
|
||
let i = 1
|
||
for (; i < xctrl.length; ++i) {
|
||
const currXAxis = xctrl[i]
|
||
if (currXAxis >= this.currChannel) {
|
||
const prevX = xctrl[i - 1]
|
||
if (currXAxis - this.currChannel > this.currChannel - prevX) --i
|
||
break
|
||
}
|
||
}
|
||
|
||
if (i == 0 || i >= xctrl.length - 1) {
|
||
this.$message.warn("Can't remove first/last control point")
|
||
return
|
||
}
|
||
|
||
const [removeXAxis] = xctrl.splice(i, 1)
|
||
const [removeYAxis] = yctrl.splice(i, 1)
|
||
const [removeYSlope] = yslope.splice(i, 1)
|
||
this.buildRect()
|
||
this.redrawBaseLine()
|
||
this.pushOperationStack(Operators.REMOVE, {
|
||
index: i,
|
||
removeXAxis,
|
||
removeYAxis,
|
||
removeYSlope,
|
||
})
|
||
},
|
||
|
||
// 修改控制点
|
||
handleModifyCP() {
|
||
this.setGraphicDraggable(!this.isModifying)
|
||
},
|
||
|
||
// 编辑斜率
|
||
handleEditSlope() {
|
||
this.setGraphicDraggable(false)
|
||
|
||
const { xctrl, yslope } = this.baseCtrls_Copy
|
||
if (!xctrl.length) {
|
||
this.$message.warn('No control points to be edited')
|
||
return
|
||
}
|
||
// find nearest control-point
|
||
let i = 1,
|
||
n = xctrl.length
|
||
for (; i < n; ++i) {
|
||
const currXCtrl = xctrl[i]
|
||
const prevXCtrl = xctrl[i - 1]
|
||
if (currXCtrl >= this.currChannel) {
|
||
if (currXCtrl - this.currChannel > this.currChannel - prevXCtrl) {
|
||
--i
|
||
}
|
||
break
|
||
}
|
||
}
|
||
if (i == n) i = n - 1
|
||
|
||
this.$refs.editSlopeModal.open({
|
||
index: i,
|
||
value: yslope[i],
|
||
allowNaN: !(i == 0 || i == n - 1),
|
||
})
|
||
},
|
||
|
||
// 确认编辑斜率
|
||
handleSlopeChange(slope, index, prevSlope) {
|
||
if (slope === prevSlope) {
|
||
return
|
||
}
|
||
|
||
const { yslope } = this.baseCtrls_Copy
|
||
yslope[index] = slope
|
||
this.pushOperationStack(Operators.SLOPE_CHANGE, {
|
||
index,
|
||
slope: prevSlope,
|
||
})
|
||
this.redrawBaseLine()
|
||
this.buildRect()
|
||
},
|
||
|
||
// 撤销
|
||
handleUndo() {
|
||
this.setGraphicDraggable(false)
|
||
this.popOperationStack()
|
||
},
|
||
|
||
// 将原先的基线和控制点移动到新位置
|
||
async handleReplot() {
|
||
if (!this.replotNeeded) {
|
||
return
|
||
}
|
||
try {
|
||
const { inputFileName: fileName } = this.sampleData
|
||
this.isReploting = true
|
||
const { success, result, message } = await postAction('/gamma/replotBaseLine', {
|
||
...this.baseCtrls_Copy,
|
||
fileName,
|
||
replotNeeded: this.replotNeeded,
|
||
})
|
||
if (success) {
|
||
const { chartData, peakSet, shapeData } = result
|
||
|
||
const { xctrl, yctrl, yslope, baseline } = this.baseCtrls_Copy
|
||
const baseLineSeries = findSeriesByName(this.option.series, 'BaseLine')
|
||
baseLineSeries.data = baseline.map((val, index) => [index + 1, val])
|
||
|
||
const baseLineCP = findSeriesByName(this.option.series, 'BaseLine_Ctrl_Point')
|
||
// 第一个控制点(因为第一个和最后一个不会被删除)
|
||
const firstCP = this.channelBaseCPChart[0]
|
||
const { color, size } = firstCP
|
||
const baseCPPoints = xctrl.map((xAxis, index) => {
|
||
return {
|
||
size,
|
||
color,
|
||
point: {
|
||
x: xAxis,
|
||
y: yctrl[index],
|
||
},
|
||
}
|
||
})
|
||
baseLineCP.data = this.buildCPPointData(baseCPPoints)
|
||
|
||
this.opts.notMerge = true
|
||
this.redrawPeaks(peakSet)
|
||
this.$nextTick(() => {
|
||
this.resetChartOpts()
|
||
})
|
||
|
||
this.replotNeeded = false
|
||
} else {
|
||
this.$message.error(message)
|
||
}
|
||
} catch (error) {
|
||
console.error(error)
|
||
} finally {
|
||
this.isReploting = false
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 构建基线控制点数据
|
||
* @param {Array} controlPointList
|
||
**/
|
||
buildCPPointData(controlPointList) {
|
||
return controlPointList.map(({ size, color, point: { x, y } }) => {
|
||
return {
|
||
value: [x, y],
|
||
itemStyle: {
|
||
color: 'transparent',
|
||
borderColor: color,
|
||
borderWidth: size / 2,
|
||
},
|
||
}
|
||
})
|
||
},
|
||
|
||
// 确定对Baseline Control Points 的操作
|
||
async handleAccept() {
|
||
// this.BaseCtrls = cloneDeep(this.baseCtrls_Copy)
|
||
// const { baseline, xctrl, yctrl } = this.BaseCtrls
|
||
// this.channelBaseLineChart.pointlist = baseline.map((val, index) => {
|
||
// return {
|
||
// x: index + 1,
|
||
// y: val
|
||
// }
|
||
// })
|
||
|
||
// this.channelBaseCPChart = xctrl.map((val, index) => {
|
||
// return {
|
||
// color: this.channelBaseCPChart[0].color,
|
||
// name: index.toString(),
|
||
// point: {
|
||
// x: val,
|
||
// y: yctrl[index]
|
||
// },
|
||
// size: 4
|
||
// }
|
||
// })
|
||
|
||
const { inputFileName: fileName } = this.sampleData
|
||
|
||
try {
|
||
this.isAccepting = true
|
||
const { success, result, message } = await postAction('/gamma/acceptBaseLine', {
|
||
...this.baseCtrls_Copy,
|
||
fileName,
|
||
})
|
||
if (success) {
|
||
this.BaseCtrls = cloneDeep(this.baseCtrls_Copy)
|
||
|
||
const {
|
||
allData,
|
||
barChart,
|
||
channelBaseLineChart,
|
||
peakSet,
|
||
shadowChannelChart,
|
||
shadowEnergyChart,
|
||
shapeChannelData,
|
||
shapeData,
|
||
shapeEnergyData,
|
||
} = result
|
||
|
||
this.channelBaseLineChart = channelBaseLineChart
|
||
this.channelPeakChart = peakSet
|
||
this.shadowChannelChart = shadowChannelChart
|
||
this.channelBaseCPChart = shapeChannelData
|
||
this.barChart = barChart
|
||
|
||
this.btnGroupType = 1
|
||
this.opts.notMerge = true
|
||
this.clearRect()
|
||
|
||
this.setChartOption(channelBaseLineChart, this.channelCountChart, peakSet, this.channelBaseCPChart, barChart)
|
||
this.$nextTick(() => {
|
||
this.resetChartOpts()
|
||
})
|
||
|
||
this.$bus.$emit('accept', { ...result, BaseCtrls: cloneDeep(this.baseCtrls_Copy) })
|
||
} else {
|
||
this.$message.error(message)
|
||
}
|
||
} catch (error) {
|
||
console.error(error)
|
||
} finally {
|
||
this.isAccepting = false
|
||
}
|
||
},
|
||
|
||
// 右下角添加当前选中的nuclide
|
||
async handleAddNuclide() {
|
||
const nuclides = this.selectedTableItem.nuclides
|
||
const possibleNuclide = this.model.possibleNuclide
|
||
if (!nuclides.includes(possibleNuclide)) {
|
||
if (this.selectedTableItem._adding) {
|
||
return
|
||
}
|
||
try {
|
||
this.$set(this.selectedTableItem, '_adding', true)
|
||
const { inputFileName: fileName } = this.sampleData
|
||
const { success, message } = await postAction('/gamma/addNuclide', {
|
||
curRow: this.curRow,
|
||
nuclideName: possibleNuclide,
|
||
fileName,
|
||
list_identify: nuclides,
|
||
energyTolerance: this.model.tolerance
|
||
})
|
||
if (success) {
|
||
nuclides.push(possibleNuclide)
|
||
} else {
|
||
this.$message.error(message)
|
||
}
|
||
} catch (error) {
|
||
console.error(error)
|
||
} finally {
|
||
this.selectedTableItem._adding = false
|
||
}
|
||
}
|
||
},
|
||
// 右下角删除当前选中的nuclide
|
||
async handleDelNuclide() {
|
||
if (!this.model.identifiedNuclide) {
|
||
return
|
||
}
|
||
const nuclides = this.selectedTableItem.nuclides
|
||
if (this.selectedTableItem._deleting) {
|
||
return
|
||
}
|
||
const findIndex = nuclides.findIndex((nuclide) => nuclide == this.model.identifiedNuclide)
|
||
if (-1 !== findIndex) {
|
||
try {
|
||
this.$set(this.selectedTableItem, '_deleting', true)
|
||
const { inputFileName: fileName } = this.sampleData
|
||
const { success, result, message } = await postAction('/gamma/deleteNuclide', {
|
||
curRow: this.curRow,
|
||
nuclideName: this.model.identifiedNuclide,
|
||
fileName,
|
||
list_identify: nuclides,
|
||
})
|
||
if (success) {
|
||
const { identify, table } = result
|
||
this.selectedTableItem.nuclides = identify
|
||
this.list = table
|
||
} else {
|
||
this.$message.error(message)
|
||
}
|
||
} catch (error) {
|
||
console.error(error)
|
||
} finally {
|
||
this.selectedTableItem._deleting = false
|
||
}
|
||
}
|
||
},
|
||
|
||
// 构建baseline
|
||
buildBaseLine(channelBaseLineChart) {
|
||
return buildLineSeries(
|
||
'BaseLine',
|
||
transformPointListData(channelBaseLineChart.pointlist),
|
||
channelBaseLineChart.color,
|
||
{
|
||
markLine: {
|
||
silent: true,
|
||
symbol: 'none',
|
||
label: {
|
||
show: false,
|
||
},
|
||
lineStyle: {
|
||
color: 'red',
|
||
width: 1,
|
||
},
|
||
data: [{ xAxis: -1 }],
|
||
},
|
||
zlevel: 10,
|
||
}
|
||
)
|
||
},
|
||
|
||
// 构建count
|
||
buildCountLine(channelCountChart) {
|
||
return buildLineSeries('CountChart', transformPointListData(channelCountChart.pointlist), channelCountChart.color)
|
||
},
|
||
|
||
// 构建Peaks
|
||
buildPeaks(channelPeakChart) {
|
||
return channelPeakChart.map((item, index) => {
|
||
return buildLineSeries('Peak_' + (index + 1), transformPointListData(item.pointlist), item.color)
|
||
})
|
||
},
|
||
|
||
// 构建基线控制点
|
||
buildCtrlPoint(channelBaseCPChart) {
|
||
return {
|
||
name: 'BaseLine_Ctrl_Point',
|
||
type: 'scatter',
|
||
data: this.buildCPPointData(channelBaseCPChart),
|
||
silent: true,
|
||
animation: false,
|
||
zlevel: 20,
|
||
}
|
||
},
|
||
|
||
// 构建缩略图
|
||
buildBarChart(barChart) {
|
||
return {
|
||
name: 'BarChart',
|
||
type: 'bar',
|
||
data: barChart.map(({ x, y }) => [x, y]),
|
||
itemStyle: {
|
||
color: '#fff',
|
||
},
|
||
lineStyle: {
|
||
width: 1,
|
||
},
|
||
symbol: 'none',
|
||
symbolSize: 1,
|
||
emphasis: {
|
||
disabled: true,
|
||
},
|
||
animation: false,
|
||
silent: true,
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 推入操作
|
||
* @param {*} operator 操作符
|
||
* @param {*} operand 操作数
|
||
*/
|
||
pushOperationStack(operator, operand) {
|
||
this.operationStack.push({
|
||
operator,
|
||
operand,
|
||
})
|
||
},
|
||
|
||
/**
|
||
* 弹出操作
|
||
*/
|
||
popOperationStack() {
|
||
const { operator, operand } = this.operationStack.pop()
|
||
const { index } = operand
|
||
|
||
const { xctrl, yctrl, yslope } = this.baseCtrls_Copy
|
||
switch (operator) {
|
||
case Operators.ADD:
|
||
xctrl.splice(index, 1)
|
||
yctrl.splice(index, 1)
|
||
yslope.splice(index, 1)
|
||
break
|
||
case Operators.MODIFY:
|
||
const { prevYAxis } = operand
|
||
|
||
// 恢复点的y轴位置
|
||
yctrl[index] = prevYAxis
|
||
break
|
||
case Operators.REMOVE:
|
||
const { removeXAxis, removeYAxis, removeYSlope } = operand
|
||
xctrl.splice(index, 0, removeXAxis)
|
||
yctrl.splice(index, 0, removeYAxis)
|
||
yslope.splice(index, 0, removeYSlope)
|
||
break
|
||
case Operators.SLOPE_CHANGE:
|
||
const { slope } = operand
|
||
yslope[index] = slope
|
||
break
|
||
}
|
||
this.buildRect()
|
||
// 恢复基线位置
|
||
this.redrawBaseLine()
|
||
},
|
||
|
||
/**
|
||
* 清理操作栈
|
||
*/
|
||
clearOperationStack() {
|
||
this.operationStack = []
|
||
},
|
||
|
||
handleResize() {
|
||
this.$refs.chartRef.resize()
|
||
this.$refs.thumbnailRef.resize()
|
||
},
|
||
|
||
handleFullScreenChange(isFullScreen) {
|
||
this.columns[7].width = isFullScreen ? 180 : 120
|
||
},
|
||
},
|
||
computed: {
|
||
curRow() {
|
||
const [selectedKey] = this.selectedKeys
|
||
const findIndex = this.list.findIndex((item) => item.index == selectedKey)
|
||
return findIndex
|
||
},
|
||
|
||
isOperationStackEmpty() {
|
||
return this.operationStack.length == 0
|
||
},
|
||
},
|
||
}
|
||
</script>
|
||
|
||
<style lang="less" scoped>
|
||
.interactive-analysis-tools {
|
||
display: flex;
|
||
padding-top: 5px;
|
||
gap: 20px;
|
||
|
||
&-left {
|
||
flex: 1;
|
||
overflow: hidden;
|
||
|
||
.chart {
|
||
height: 331px;
|
||
position: relative;
|
||
}
|
||
|
||
.thumbnail {
|
||
height: 50px;
|
||
margin: 10px 30px 35px 80px;
|
||
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 {
|
||
width: 290px;
|
||
flex-shrink: 0;
|
||
|
||
.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 {
|
||
margin-bottom: 10px;
|
||
|
||
.title {
|
||
background-color: #497e9d;
|
||
height: 30px;
|
||
line-height: 30px;
|
||
text-align: center;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.content {
|
||
height: 80px;
|
||
background-color: #275466;
|
||
margin-top: 10px;
|
||
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%;
|
||
}
|
||
}
|
||
::v-deep {
|
||
.ant-modal {
|
||
top: 5px;
|
||
padding-bottom: 7px;
|
||
}
|
||
}
|
||
.is-modify,
|
||
.is-fitting {
|
||
color: #f00;
|
||
}
|
||
</style>
|
||
<style lang="less">
|
||
.interactive-analysis-tools-dialog.fullscreen {
|
||
.interactive-analysis-tools {
|
||
height: calc(100vh - 90px);
|
||
|
||
&-left {
|
||
display: flex;
|
||
flex-direction: column;
|
||
|
||
.chart {
|
||
flex: 1;
|
||
}
|
||
|
||
.thumbnail {
|
||
height: 60px;
|
||
}
|
||
|
||
.table {
|
||
height: 397px;
|
||
|
||
.custom-table {
|
||
height: calc(100% - 82px);
|
||
|
||
.ant-spin-nested-loading,
|
||
.ant-spin-container,
|
||
.ant-table,
|
||
.ant-table-content,
|
||
.ant-table-scroll {
|
||
height: 100%;
|
||
}
|
||
|
||
.ant-table-body {
|
||
height: calc(100% - 25px) !important;
|
||
max-height: calc(100% - 25px) !important;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
&-right {
|
||
display: flex;
|
||
flex-direction: column;
|
||
|
||
.identify-box {
|
||
flex: 1;
|
||
padding-top: 5px;
|
||
overflow: hidden;
|
||
|
||
.title-over-border {
|
||
height: 100%;
|
||
|
||
&-content {
|
||
display: flex;
|
||
flex-direction: column;
|
||
|
||
.identify-item {
|
||
flex: 1;
|
||
overflow: auto;
|
||
|
||
.ant-spin-nested-loading,
|
||
.content {
|
||
height: calc(100% - 40px);
|
||
.ant-spin-container,
|
||
.content {
|
||
height: 100%;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</style>
|