AnalysisSystemForRadionucli.../src/views/spectrumAnalysis/gamma-analysis.vue

939 lines
26 KiB
Vue

<template>
<div class="gamma-analysis">
<a-spin :spinning="isLoading">
<!-- 二级交互栏 -->
<div class="spectrum-analysis-sub-operators">
<pop-over-with-icon placement="bottomLeft">
Detailed-Information
<detailed-infomation slot="content" :data="detailedInfomation" />
</pop-over-with-icon>
<pop-over-with-icon placement="bottomLeft">
QC Flags
<qc-flags slot="content" :data="qcFlags" />
</pop-over-with-icon>
<pop-over-with-icon>
Graph Assistance
<graph-assistance
v-if="!isLoading"
slot="content"
@change="handleGraphAssistanceChange"
@reset="handleReset"
/>
</pop-over-with-icon>
<pop-over-with-icon>
Nuclide Library
<nuclear-library slot="content" />
</pop-over-with-icon>
<div class="peak-info">
<button-with-switch-icon @change="handlePeakInfoChange">
Peak Information
</button-with-switch-icon>
</div>
</div>
<!-- 二级交互栏结束 -->
<!-- 主体部分 -->
<div class="gamma-analysis-main">
<div class="gamma-analysis-chart">
<custom-3d-chart
ref="chartRef"
:option="option"
@zr:click="handleChartClick"
@zr:mousedown="handleMouseDown"
@zr:mouseup="handleMouseUp"
@brushEnd="handleBrushEnd"
style="height: 100%"
/>
<div class="gamma-analysis-thumbnail">
<custom-3d-chart
ref="thumbnailChartRef"
:option="thumbnailOption"
@zr:click="handleThumbnailChartClick"
style="height: 100%"
/>
</div>
</div>
</div>
<!-- 主体部分结束 -->
</a-spin>
</div>
</template>
<script>
import Custom3dChart from '@/components/Custom3DChart/index.vue'
import PopOverWithIcon from './components/SubOperators/PopOverWithIcon.vue'
import DetailedInfomation from './components/SubOperators/DetailedInfomation.vue'
import QcFlags from './components/SubOperators/QcFlags.vue'
import GraphAssistance from './components/SubOperators/GraphAssistance.vue'
import NuclearLibrary from './components/SubOperators/NuclearLibrary.vue'
import ButtonWithSwitchIcon from './components/SubOperators/ButtonWithSwitchIcon.vue'
import { getAction } from '@/api/manage'
import Response from './response.json'
import { getXAxisAndYAxisByPosition } from '@/utils/chartHelper'
// 初始配置
const initialOption = {
grid: {
top: 40,
left: 60,
right: 50,
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: '#099D24',
width: 2
}
},
formatter: undefined,
className: 'figure-chart-option-tooltip'
},
xAxis: {
name: 'Channel',
nameTextStyle: {
color: '#8FD4F8',
fontSize: 16,
align: 'right',
verticalAlign: 'top',
padding: [30, 0, 0, 0]
},
axisLine: {
lineStyle: {
color: '#ade6ee'
}
},
splitLine: {
show: false
},
axisLabel: {
textStyle: {
color: '#ade6ee'
}
},
min: 0,
max: 'dataMax',
animation: false
},
yAxis: {
name: 'Counts',
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,
max: 'dataMax',
animation: false
},
series: [],
brush: {}
}
// 缩略图配置
const thumbnailOption = {
grid: {
top: 0,
left: 5,
right: 5,
bottom: 0
},
xAxis: {
type: 'category',
axisLine: {
show: false
},
splitLine: {
show: false
},
axisLabel: {
show: false
},
min: 0,
max: 'dataMax'
},
yAxis: {
axisLine: {
show: false
},
splitLine: {
show: false
},
axisLabel: {
show: false
}
},
series: []
}
export default {
props: {
sample: {
type: Object
}
},
components: {
Custom3dChart,
PopOverWithIcon,
DetailedInfomation,
QcFlags,
GraphAssistance,
NuclearLibrary,
ButtonWithSwitchIcon
},
data() {
return {
isLoading: false,
option: initialOption,
thumbnailOption,
detailedInfomation: [],
qcFlags: [],
graphAssistance: {},
channelPeakGroup: [],
energyPeakGroup: []
}
},
created() {
this.option.title.text = '{a|Channel:0} {a|Energy:0} {a|Counts:0} {a|Detectability:0}'
},
mounted() {
this.option.brush = { toolbox: [] }
},
methods: {
async getSampleDetail() {
const { dbName, sampleId } = this.sample
try {
this.isLoading = true
this.option.series = []
this.thumbnailOption.series = []
const { success, result, message } = await getAction('/gamma/gammaByDB', {
dbName,
sampleId
})
// const { success, result, message } = Response
console.log('%c [ result ]-243', 'font-size:13px; background:pink; color:#bf2c9f;', result)
if (success) {
this.isLoading = false
const {
dead_time,
live_time,
real_time,
start_time,
DetailedInformation,
QCFlag,
allData,
shadowChannelChart,
shadowEnergyChart,
shapeChannelData,
shapeEnergyData
} = result
this.detailedInfomation = DetailedInformation
this.qcFlags = QCFlag
const channelPeakGroup = allData.filter(item => item.name == 'Peak' && item.group == 'channel')
const energyPeakGroup = allData.filter(item => item.name == 'Peak' && item.group == 'energy')
const channelBaseLine = allData.find(item => item.name == 'BaseLine' && item.group == 'channel')
const energyBaseLine = allData.find(item => item.name == 'BaseLine' && item.group == 'energy')
const channelLcLine = allData.find(item => item.name == 'Lc' && item.group == 'channel')
const energyLcLine = allData.find(item => item.name == 'Lc' && item.group == 'energy')
const channelScacLine = allData.find(item => item.name == 'Scac' && item.group == 'channel')
const energyScacLine = allData.find(item => item.name == 'Scac' && item.group == 'energy')
this.allEnergy = allData.find(item => item.name == 'Energy' && item.group == 'energy')
this.allChannel = allData.find(item => item.name == 'Count' && item.group == 'channel')
// 保存Peak Line
this.channelPeakGroup = channelPeakGroup
this.energyPeakGroup = energyPeakGroup
// 保存 Spectrum Line
this.shadowChannelChart = shadowChannelChart
this.shadowEnergyChart = shadowEnergyChart
// 保存基线
this.channelBaseLine = channelBaseLine
this.energyBaseLine = energyBaseLine
// 保存Lc
this.channelLcLine = channelLcLine
this.energyLcLine = energyLcLine
// 保存Scac
this.channelScacLine = channelScacLine
this.energyScacLine = energyScacLine
// 保存 基线控制点
this.shapeChannelData = shapeChannelData
this.shapeEnergyData = shapeEnergyData
this.option.yAxis.max = Math.ceil(Math.max(...shadowChannelChart.pointlist.map(item => item.y)) * 1.1)
// 推入Spectrum Line
this.option.series.push({
name: 'Spectrum',
type: 'line',
data: shadowChannelChart.pointlist.map(({ x, y }) => [x, y]),
itemStyle: {
color: `rgb(${shadowChannelChart.color})`
},
lineStyle: {
width: 1
},
symbol: 'none',
symbolSize: 1,
emphasis: {
disabled: true
},
markLine: {
silent: true,
symbol: 'none',
label: {
show: false
},
lineStyle: {
color: 'red',
width: 2
},
data: [{ xAxis: -1 }]
},
animation: false
})
// 右上角缩略图推入Spectrum Line
this.thumbnailOption.series.push({
name: 'Spectrum',
type: 'line',
data: shadowChannelChart.pointlist.map(({ x, y }) => [x, y]),
itemStyle: {
color: `rgb(${shadowChannelChart.color})`
},
lineStyle: {
width: 1
},
symbol: 'none',
symbolSize: 1,
emphasis: {
disabled: true
},
silent: true,
markLine: {
silent: true,
symbol: 'none',
label: {
show: false
},
lineStyle: {
type: 'solid',
color: '#1397a3',
width: 2
},
data: []
}
})
// 推入BaseLine
this.option.series.push({
name: 'BaseLine',
type: 'line',
data: channelBaseLine.pointlist.map(({ x, y }) => [x, y]),
itemStyle: {
color: `rgb(${channelBaseLine.color})`
},
lineStyle: {
width: 1
},
symbol: 'none',
emphasis: {
disabled: true
},
animation: false
})
// 推入LcLine线
this.option.series.push({
name: 'LcLine',
type: 'line',
data: channelLcLine.pointlist.map(({ x, y }) => [x, y]),
itemStyle: {
color: `rgb(${channelLcLine.color})`
},
lineStyle: {
width: 1
},
symbol: 'none',
emphasis: {
disabled: true
},
animation: false
})
// 推入Scac线
this.option.series.push({
name: 'ScacLine',
type: 'line',
data: channelScacLine.pointlist.map(({ x, y }) => [x, y]),
itemStyle: {
color: `rgb(${channelScacLine.color})`
},
lineStyle: {
width: 1
},
symbol: 'none',
emphasis: {
disabled: true
},
animation: false
})
// 推入基线控制点
this.option.series.push({
name: 'BaseLine_Ctrl_Point',
type: 'scatterGL',
data: shapeChannelData.map(({ size, color, point: { x, y } }) => {
return {
value: [x, y],
itemStyle: {
color: 'transparent',
borderColor: color,
borderWidth: size / 2
}
}
}),
emphasis: {
disabled: true
},
animation: false
})
// 推入Peak Line
const peakLines = []
channelPeakGroup.forEach((item, index) => {
peakLines.push({
name: `Peak_${index}`,
type: 'line',
data: item.pointlist.map(({ x, y }) => [x, y]),
itemStyle: {
color: `rgb(${item.color})`
},
lineStyle: {
width: 1
},
symbol: 'none',
animation: false
})
})
this.option.series.push(...peakLines)
this.option.tooltip.formatter = this.tooltipFormatter
} else {
this.$message.error(message)
}
} catch (error) {
console.error(error)
}
},
tooltipFormatter(params) {
if (this.graphAssistance.axisType == 'Energy') {
const energy = params[0].value[0]
const channel = this.getChannelByEnergy(energy)
return `<div class="channel">Channel: ${channel}</div>
<div class="energy">Energy: ${energy.toFixed(2)}</div>`
} else {
const channel = parseInt(params[0].value[0])
const energy = this.allEnergy.pointlist[channel]
return energy
? `<div class="channel">Channel: ${channel}</div>
<div class="energy">Energy: ${energy.x.toFixed(2)}</div>`
: undefined
}
},
// Graph Assistance 操作
handleGraphAssistanceChange({ type, label, value }) {
// 类型变化
if (type == 'labelChange') {
switch (label) {
case 'Linear':
break
case 'Log10':
break
case 'Channel':
case 'Energy':
this.graphAssistance.axisType = label
this.handleReset()
this.redrawLineBySeriesName(
'BaseLine',
this.energyBaseLine,
this.channelBaseLine,
this.graphAssistance.Baseline
)
this.redrawLineBySeriesName('LcLine', this.energyLcLine, this.channelLcLine, this.graphAssistance.Lc)
this.redrawLineBySeriesName(
'ScacLine',
this.energyScacLine,
this.channelScacLine,
this.graphAssistance.SCAC
)
this.redrawLineBySeriesName('Spectrum', this.shadowEnergyChart, this.shadowChannelChart)
this.redrawCtrlPointBySeriesName()
this.redrawPeakLine()
this.redrawThumbnailChart()
break
case 'Lines':
this.option.series[0].type = 'line'
this.option.series[0].symbol = 'none'
this.thumbnailOption.series[0].type = 'line'
this.thumbnailOption.series[0].symbol = 'none'
break
case 'Scatter':
this.option.series[0].type = 'scatterGL'
this.option.series[0].symbol = 'circle'
this.thumbnailOption.series[0].type = 'scatterGL'
this.thumbnailOption.series[0].symbol = 'circle'
break
}
}
// 值变化
else if (type == 'valueChange') {
this.graphAssistance[label] = value
switch (label) {
case 'Cursor':
// 显示红色竖线
if (value) {
this.option.series[0].markLine.lineStyle.width = 2
} else {
this.option.series[0].markLine.lineStyle.width = 0
}
break
case 'Baseline':
this.redrawLineBySeriesName('BaseLine', this.energyBaseLine, this.channelBaseLine, value)
break
case 'Lc':
this.redrawLineBySeriesName('LcLine', this.energyLcLine, this.channelLcLine, value)
break
case 'SCAC':
this.redrawLineBySeriesName('ScacLine', this.energyScacLine, this.channelScacLine, value)
break
}
}
},
// 根据seriesName重绘线
redrawLineBySeriesName(seriesName, energyData, channelData, isShow = true) {
const series = this.findSeriesByName(seriesName)
if (isShow) {
const data = this.graphAssistance.axisType == 'Energy' ? energyData : channelData
series.data = data.pointlist.map(({ x, y }) => [x, y])
} else {
series.data = []
}
},
// 重绘控制点
redrawCtrlPointBySeriesName() {
const series = this.findSeriesByName('BaseLine_Ctrl_Point')
const data = this.graphAssistance.axisType == 'Energy' ? this.shapeEnergyData : this.shapeChannelData
series.data = data.map(({ size, color, point: { x, y } }) => {
return {
value: [x, y],
itemStyle: {
color: 'transparent',
borderColor: color,
borderWidth: size / 2
}
}
})
},
// 重绘Peak Line
redrawPeakLine() {
this.option.series = this.option.series.filter(item => {
return -1 == item.name.indexOf('Peak_')
})
const data = this.graphAssistance.axisType == 'Energy' ? this.energyPeakGroup : this.channelPeakGroup
const peakLines = []
data.forEach((item, index) => {
peakLines.push({
name: `Peak_${index}`,
type: 'line',
data: item.pointlist.map(({ x, y }) => [x, y]),
itemStyle: {
color: `rgb(${item.color})`
},
lineStyle: {
width: 1
},
symbol: 'none',
animation: false
})
})
this.option.series.push(...peakLines)
},
// 重绘右上角的缩略图
redrawThumbnailChart() {
const series = this.thumbnailOption.series[0]
const data = this.graphAssistance.axisType == 'Energy' ? this.shadowEnergyChart : this.shadowChannelChart
series.data = data.pointlist.map(({ x, y }) => [x, y])
},
// 点击图表,设置红线
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
}
},
resize() {
this.$refs.chartRef.resize()
this.$refs.thumbnailChartRef.resize()
},
// peak info 点击左右方向
handlePeakInfoChange(direction) {
this.moveMarkLine(direction)
},
/**
* 向某一个方向移动标记线
* @param { 'left'| 'right' } direction
*/
moveMarkLine(direction) {
const prevAxis = this.option.series[0].markLine.data[0].xAxis
// 获取每一段 Channel 中的最大值
const maxXAxises = this.channelPeakGroup.map(item => {
const allY = item.pointlist.map(item => item.y)
const max = item.pointlist.find(point => point.y == Math.max(...allY))
return max.x
})
if (direction == 'right') {
// 找到第一个比prevAxis大的xAxis
const find = maxXAxises.find(xAxis => xAxis > prevAxis)
if (find) {
this.option.series[0].markLine.data[0].xAxis = find
}
} else if (direction == 'left') {
// 找到第一个比prevAxis小的xAxis
const find = maxXAxises.reverse().find(xAxis => xAxis < prevAxis)
if (find) {
this.option.series[0].markLine.data[0].xAxis = find
}
}
},
// 鼠标按下时开启可刷选状态
handleMouseDown() {
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 = chart.getModel().getComponent('yAxis').axis.scale._extent[1]
let [x1, y2, x2, y1] = [...point1, ...point2] // 根据解析出的数据确定真实的范围
const xAxisLimit = this.rangeNumber(0, xAxisMax)
const yAxisLimit = this.rangeNumber(0, 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
if (this.graphAssistance.axisType == 'Energy') {
const channel1 = this.getChannelByEnergy(x1)
const channel2 = this.getChannelByEnergy(x2)
this.setThumbnailChartRect(channel1, y2, channel2, y1)
} else {
this.setThumbnailChartRect(x1, y2, x2, y1)
}
}
this.clearBrush(chart)
},
// 在右上角缩略图中设置范围
setThumbnailChartRect(x1, y2, x2, y1) {
this.thumbnailChartRect = [x1, y2, x2, y1]
const { markLine } = this.thumbnailOption.series[0]
const pointList = [
[
[x1, y1],
[x2, y1]
],
[
[x2, y1],
[x2, y2]
],
[
[x2, y2],
[x1, y2]
],
[
[x1, y2],
[x1, y1]
]
]
const lines = pointList.map(point => {
return this.generateLineDataByTwoPoints(point[0], point[1])
})
markLine.data = lines
},
// 缩略图点击
handleThumbnailChartClick(param) {
const { offsetX, offsetY } = param
const thumbnailChart = this.$refs.thumbnailChartRef.getChartInstance()
const point = getXAxisAndYAxisByPosition(thumbnailChart, offsetX, offsetY)
if (point && this.thumbnailChartRect && this.thumbnailChartRect.length) {
const [x1, y2, x2, y1] = this.thumbnailChartRect
const halfWidth = Math.ceil((x2 - x1) / 2)
const halfHeight = Math.ceil((y2 - y1) / 2)
let [xAxis, yAxis] = point
xAxis = parseInt(xAxis)
yAxis = parseInt(yAxis)
const xAxisMax = thumbnailChart.getModel().getComponent('xAxis').axis.scale._extent[1]
const yAxisMax = thumbnailChart.getModel().getComponent('yAxis').axis.scale._extent[1]
const xAxisLimit = this.rangeNumber(halfWidth, xAxisMax - halfWidth)
const yAxisLimit = this.rangeNumber(halfHeight, yAxisMax - halfHeight)
xAxis = xAxisLimit(xAxis)
yAxis = yAxisLimit(yAxis)
this.setThumbnailChartRect(xAxis - halfWidth, yAxis + halfHeight, xAxis + halfWidth, yAxis - halfHeight)
if (this.graphAssistance.axisType == 'Energy') {
const x1 = parseInt(this.shadowEnergyChart.pointlist[xAxis - halfWidth].x)
const x2 = parseInt(this.shadowEnergyChart.pointlist[xAxis + halfWidth].x)
this.option.xAxis.min = x1
this.option.xAxis.max = x2
} else {
this.option.xAxis.min = xAxis - halfWidth
this.option.xAxis.max = xAxis + halfWidth
}
this.option.yAxis.min = yAxis - halfHeight
this.option.yAxis.max = yAxis + halfHeight
}
},
// 重置
handleReset() {
this.option.xAxis.min = 0
this.option.xAxis.max = 'dataMax'
this.option.yAxis.min = 0
this.option.yAxis.max = Math.ceil(Math.max(...this.shadowChannelChart.pointlist.map(item => item.y)) * 1.1)
this.thumbnailOption.series[0].markLine.data = []
this.thumbnailChartRect = []
},
// 根据name查找series
findSeriesByName(seriesName) {
return this.option.series.find(item => item.name == seriesName)
},
/**
* 根据两个点生成一个markLine直线
*/
generateLineDataByTwoPoints(point1, point2) {
return [
{
xAxis: point1[0],
yAxis: point1[1]
},
{
xAxis: point2[0],
yAxis: point2[1]
}
]
},
/**
* 限定数字在一定范围
* @param {Number} min
* @param {Number} max
*/
rangeNumber(min, max) {
return num => {
return num > max ? max : num < min ? min : num
}
},
getChannelByEnergy(energy) {
let channel = 0
for (let index = 1; index < this.allEnergy.pointlist.length; index++) {
const currEnergy = this.allEnergy.pointlist[index].x
if (currEnergy >= energy) {
const prevEnergy = this.allEnergy.pointlist[index - 1].x
if (currEnergy - energy > energy - prevEnergy.x) {
channel = index
} else {
channel = index + 1
}
break
}
}
return channel
}
},
watch: {
sample: {
handler() {
this.getSampleDetail()
},
immediate: true
}
}
}
</script>
<style lang="less" scoped>
.gamma-analysis {
height: 100%;
.ant-spin-nested-loading {
height: 100%;
::v-deep {
.ant-spin-container {
height: 100%;
}
}
}
&-main {
height: calc(100% - 51px);
display: flex;
overflow: auto hidden;
position: relative;
}
&-chart {
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
}
&-thumbnail {
position: absolute;
top: 50px;
right: 10px;
width: 500px;
height: 20%;
background-color: #153e44;
}
}
</style>