|
@@ -8,7 +8,7 @@ import { fixedAxisRange } from '../utils/axis'
|
|
|
const props = defineProps<{
|
|
const props = defineProps<{
|
|
|
data: WaveWindowResponse | null
|
|
data: WaveWindowResponse | null
|
|
|
points: TimePoint[]
|
|
points: TimePoint[]
|
|
|
- mode: 'split' | 'merge'
|
|
|
|
|
|
|
+ mode: 'split' | 'normalized'
|
|
|
loading?: boolean
|
|
loading?: boolean
|
|
|
annotations?: Annotation[]
|
|
annotations?: Annotation[]
|
|
|
siteDescriptions?: Record<string, string>
|
|
siteDescriptions?: Record<string, string>
|
|
@@ -78,7 +78,6 @@ const SPLIT_ROW_HEIGHT = 120
|
|
|
const SPLIT_ROW_GAP = 14
|
|
const SPLIT_ROW_GAP = 14
|
|
|
const CHART_TOP_INSET = 32
|
|
const CHART_TOP_INSET = 32
|
|
|
const CHART_BOTTOM_INSET = 68
|
|
const CHART_BOTTOM_INSET = 68
|
|
|
-const MERGE_CHART_HEIGHT = 560
|
|
|
|
|
const EMPTY_CHART_HEIGHT = 480
|
|
const EMPTY_CHART_HEIGHT = 480
|
|
|
|
|
|
|
|
function buildRowMetas(data: WaveWindowResponse): RowMeta[] {
|
|
function buildRowMetas(data: WaveWindowResponse): RowMeta[] {
|
|
@@ -122,7 +121,7 @@ function rowColor(data: WaveWindowResponse, name: string): string {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function fixedYAxis(devicePoint: string) {
|
|
function fixedYAxis(devicePoint: string) {
|
|
|
- if (props.mode === 'merge') return null
|
|
|
|
|
|
|
+ if (props.mode === 'normalized') return null
|
|
|
const series = props.data?.series.find((item) => item.devicePoint === devicePoint)
|
|
const series = props.data?.series.find((item) => item.devicePoint === devicePoint)
|
|
|
if (!series) return null
|
|
if (!series) return null
|
|
|
return fixedAxisRange(series.measurementType, series.min, series.max)
|
|
return fixedAxisRange(series.measurementType, series.min, series.max)
|
|
@@ -133,7 +132,6 @@ function splitExtraCount(data: WaveWindowResponse): number {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const layoutHeightPx = computed(() => {
|
|
const layoutHeightPx = computed(() => {
|
|
|
- if (props.mode === 'merge') return props.data ? MERGE_CHART_HEIGHT : EMPTY_CHART_HEIGHT
|
|
|
|
|
if (!props.data) return EMPTY_CHART_HEIGHT
|
|
if (!props.data) return EMPTY_CHART_HEIGHT
|
|
|
return splitTotalHeight(buildRowMetas(props.data).length)
|
|
return splitTotalHeight(buildRowMetas(props.data).length)
|
|
|
})
|
|
})
|
|
@@ -239,7 +237,7 @@ function annotationAreas(annotations: Annotation[]): any[] {
|
|
|
function buildAnnotationOverlaySeries(annotations: Annotation[]): echarts.SeriesOption[] {
|
|
function buildAnnotationOverlaySeries(annotations: Annotation[]): echarts.SeriesOption[] {
|
|
|
const data = props.data
|
|
const data = props.data
|
|
|
if (!data) return []
|
|
if (!data) return []
|
|
|
- const gridCount = props.mode === 'merge' ? 1 : buildRowMetas(data).length
|
|
|
|
|
|
|
+ const gridCount = buildRowMetas(data).length
|
|
|
const areas = annotationAreas(annotations)
|
|
const areas = annotationAreas(annotations)
|
|
|
return Array.from({ length: gridCount }, (_, index) => ({
|
|
return Array.from({ length: gridCount }, (_, index) => ({
|
|
|
id: `annotation-overlay-${index}`,
|
|
id: `annotation-overlay-${index}`,
|
|
@@ -322,19 +320,14 @@ function buildOption(zoomMode: 'initial' | 'keep' = 'initial') {
|
|
|
? data.cycles.filter((cycle) => cycle.background)
|
|
? data.cycles.filter((cycle) => cycle.background)
|
|
|
: primaryCycles
|
|
: primaryCycles
|
|
|
const periodBackground = periodAreas(backgroundCycles)
|
|
const periodBackground = periodAreas(backgroundCycles)
|
|
|
- const showSecond = data.secondSeries.data.length > 0
|
|
|
|
|
- const showVolume = data.volumeSeries.data.length > 0 || data.volumeSeries.info != null
|
|
|
|
|
const siteSeries = data.siteSeries?.series.filter((series) => series.data.length) ?? []
|
|
const siteSeries = data.siteSeries?.series.filter((series) => series.data.length) ?? []
|
|
|
- const showSite = siteSeries.length > 0
|
|
|
|
|
- const metas = props.mode === 'merge' ? [] : buildRowMetas(data)
|
|
|
|
|
- const rows = props.mode === 'merge' ? ['合并信号'] : metas.map((meta) => meta.name)
|
|
|
|
|
|
|
+ const metas = buildRowMetas(data)
|
|
|
|
|
+ const rows = metas.map((meta) => meta.name)
|
|
|
const gridCount = rows.length
|
|
const gridCount = rows.length
|
|
|
const topInset = CHART_TOP_INSET
|
|
const topInset = CHART_TOP_INSET
|
|
|
const bottomInset = CHART_BOTTOM_INSET
|
|
const bottomInset = CHART_BOTTOM_INSET
|
|
|
- const rowGap = props.mode === 'merge' ? 0 : SPLIT_ROW_GAP
|
|
|
|
|
- const rowHeight = props.mode === 'merge'
|
|
|
|
|
- ? Math.max(260, MERGE_CHART_HEIGHT - topInset - bottomInset)
|
|
|
|
|
- : SPLIT_ROW_HEIGHT
|
|
|
|
|
|
|
+ const rowGap = SPLIT_ROW_GAP
|
|
|
|
|
+ const rowHeight = SPLIT_ROW_HEIGHT
|
|
|
const grid = Array.from({ length: gridCount }, (_, index) => ({
|
|
const grid = Array.from({ length: gridCount }, (_, index) => ({
|
|
|
left: 70,
|
|
left: 70,
|
|
|
right: 22,
|
|
right: 22,
|
|
@@ -342,9 +335,7 @@ function buildOption(zoomMode: 'initial' | 'keep' = 'initial') {
|
|
|
height: rowHeight,
|
|
height: rowHeight,
|
|
|
containLabel: false,
|
|
containLabel: false,
|
|
|
}))
|
|
}))
|
|
|
- if (props.mode === 'merge') {
|
|
|
|
|
- grid[0] = { left: 70, right: 22, top: topInset, height: rowHeight, containLabel: false }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ const normalized = props.mode === 'normalized'
|
|
|
const xAxes = grid.map((_, index) => ({
|
|
const xAxes = grid.map((_, index) => ({
|
|
|
type: 'value' as const,
|
|
type: 'value' as const,
|
|
|
min: data.xMin,
|
|
min: data.xMin,
|
|
@@ -373,7 +364,7 @@ function buildOption(zoomMode: 'initial' | 'keep' = 'initial') {
|
|
|
let axisName = rowName
|
|
let axisName = rowName
|
|
|
if (meta?.kind === 'site') {
|
|
if (meta?.kind === 'site') {
|
|
|
const rule = siteScaleRuleFor(props.siteDescriptions?.[rowName])
|
|
const rule = siteScaleRuleFor(props.siteDescriptions?.[rowName])
|
|
|
- if (rule) axisName = `${rowName} ${rule.unit}`
|
|
|
|
|
|
|
+ if (rule && !normalized) axisName = `${rowName} ${rule.unit}`
|
|
|
} else if (meta?.kind === 'siteAgg') {
|
|
} else if (meta?.kind === 'siteAgg') {
|
|
|
siteRange = { min: 0, max: 1, interval: 0.2 }
|
|
siteRange = { min: 0, max: 1, interval: 0.2 }
|
|
|
}
|
|
}
|
|
@@ -391,201 +382,141 @@ function buildOption(zoomMode: 'initial' | 'keep' = 'initial') {
|
|
|
formatter: meta?.kind === 'siteAgg' ? (value: number) => Number(value).toFixed(1) : undefined,
|
|
formatter: meta?.kind === 'siteAgg' ? (value: number) => Number(value).toFixed(1) : undefined,
|
|
|
},
|
|
},
|
|
|
splitLine: { show: true, lineStyle: { color: '#e7edf0', width: 1 } },
|
|
splitLine: { show: true, lineStyle: { color: '#e7edf0', width: 1 } },
|
|
|
- scale: meta?.kind === 'site' ? true : isDisplacement,
|
|
|
|
|
- min: siteRange?.min ?? fixed?.min,
|
|
|
|
|
- max: siteRange?.max ?? fixed?.max,
|
|
|
|
|
- interval: siteRange?.interval ?? fixed?.interval,
|
|
|
|
|
|
|
+ scale: normalized ? false : (meta?.kind === 'site' ? true : isDisplacement),
|
|
|
|
|
+ min: normalized ? 0 : (siteRange?.min ?? fixed?.min),
|
|
|
|
|
+ max: normalized ? 1 : (siteRange?.max ?? fixed?.max),
|
|
|
|
|
+ interval: normalized ? 0.2 : (siteRange?.interval ?? fixed?.interval),
|
|
|
}
|
|
}
|
|
|
})
|
|
})
|
|
|
const series: echarts.SeriesOption[] = []
|
|
const series: echarts.SeriesOption[] = []
|
|
|
const deviceColor = (devicePoint: string) => data.series.find((series) => series.devicePoint === devicePoint)?.color ?? colors[data.series.find((series) => series.devicePoint === devicePoint)?.measurementType ?? '加速度']
|
|
const deviceColor = (devicePoint: string) => data.series.find((series) => series.devicePoint === devicePoint)?.color ?? colors[data.series.find((series) => series.devicePoint === devicePoint)?.measurementType ?? '加速度']
|
|
|
- if (props.mode === 'merge') {
|
|
|
|
|
- devicePoints.forEach((devicePoint) => {
|
|
|
|
|
- const source = data.series.find((series) => series.devicePoint === devicePoint)
|
|
|
|
|
- const values = source?.data ?? []
|
|
|
|
|
- const finiteValues = values.map((item) => item.rawValue).filter(Number.isFinite)
|
|
|
|
|
- const [safeMin, safeMax] = finiteValues.length ? minMaxOf(finiteValues) : [0, 1]
|
|
|
|
|
- const span = safeMax - safeMin || 1
|
|
|
|
|
|
|
+ const normValue = (value: number, lo: number, hi: number) => (hi > lo ? (value - lo) / (hi - lo) : 0.5)
|
|
|
|
|
+ metas.forEach((meta, index) => {
|
|
|
|
|
+ if (meta.kind === 'device') {
|
|
|
|
|
+ const source = data.series.find((series) => series.devicePoint === meta.name)
|
|
|
const color = source?.color ?? colors[source?.measurementType ?? '加速度']
|
|
const color = source?.color ?? colors[source?.measurementType ?? '加速度']
|
|
|
|
|
+ const [lo, hi] = minMaxOf((source?.data ?? []).map((item) => item.rawValue).filter(Number.isFinite))
|
|
|
series.push({
|
|
series.push({
|
|
|
- name: devicePoint,
|
|
|
|
|
- type: 'line' as const,
|
|
|
|
|
|
|
+ name: meta.name,
|
|
|
|
|
+ type: 'line',
|
|
|
z: 10,
|
|
z: 10,
|
|
|
- xAxisIndex: 0,
|
|
|
|
|
- yAxisIndex: 0,
|
|
|
|
|
|
|
+ xAxisIndex: index,
|
|
|
|
|
+ yAxisIndex: index,
|
|
|
showSymbol: false,
|
|
showSymbol: false,
|
|
|
connectNulls: false,
|
|
connectNulls: false,
|
|
|
sampling: 'lttb' as const,
|
|
sampling: 'lttb' as const,
|
|
|
- lineStyle: { width: 2.5, color, cap: 'round' as const, join: 'round' as const },
|
|
|
|
|
|
|
+ lineStyle: { width: 2.5, color, cap: 'round', join: 'round' },
|
|
|
itemStyle: { color },
|
|
itemStyle: { color },
|
|
|
- data: values
|
|
|
|
|
|
|
+ data: (source?.data ?? [])
|
|
|
.filter((item) => Number.isFinite(item.x) && Number.isFinite(item.rawValue))
|
|
.filter((item) => Number.isFinite(item.x) && Number.isFinite(item.rawValue))
|
|
|
- .map((item) => [item.x, (item.rawValue - safeMin) / span] as [number, number]),
|
|
|
|
|
|
|
+ .map((item): number[] => (normalized
|
|
|
|
|
+ ? [item.x, normValue(item.rawValue, lo, hi), item.rawValue]
|
|
|
|
|
+ : [item.x, item.rawValue])),
|
|
|
markArea: { silent: true, data: periodBackground },
|
|
markArea: { silent: true, data: periodBackground },
|
|
|
|
|
+ markLine: index === 0 ? { silent: true, symbol: 'none', data: triggerLines(data.triggerXs) } : undefined,
|
|
|
})
|
|
})
|
|
|
- })
|
|
|
|
|
- if (showSecond) {
|
|
|
|
|
- const secondValues = data.secondSeries.data
|
|
|
|
|
- const secondFiniteValues = secondValues.map((item) => item.rawValue).filter(Number.isFinite)
|
|
|
|
|
- const [secondMin, secondMax] = secondFiniteValues.length ? minMaxOf(secondFiniteValues) : [0, 1]
|
|
|
|
|
- const secondSpan = secondMax - secondMin || 1
|
|
|
|
|
|
|
+ } else if (meta.kind === 'site') {
|
|
|
|
|
+ const siteItem = siteSeries.find((series) => series.itemName === meta.name)
|
|
|
|
|
+ if (!siteItem) return
|
|
|
|
|
+ const color = SITE_POINT_COLORS[meta.siteSeq % SITE_POINT_COLORS.length]
|
|
|
|
|
+ const [lo, hi] = minMaxOf(
|
|
|
|
|
+ siteItem.data.map((item) => item.value[1]).filter((value) => Number.isFinite(value) && value !== 0),
|
|
|
|
|
+ )
|
|
|
series.push({
|
|
series.push({
|
|
|
- name: '周期数据',
|
|
|
|
|
|
|
+ name: meta.name,
|
|
|
type: 'line',
|
|
type: 'line',
|
|
|
- xAxisIndex: 0,
|
|
|
|
|
- yAxisIndex: 0,
|
|
|
|
|
|
|
+ z: 10,
|
|
|
|
|
+ xAxisIndex: index,
|
|
|
|
|
+ yAxisIndex: index,
|
|
|
showSymbol: false,
|
|
showSymbol: false,
|
|
|
connectNulls: false,
|
|
connectNulls: false,
|
|
|
- lineStyle: { width: 3, color: colors['周期数据'], cap: 'round' as const, join: 'round' as const },
|
|
|
|
|
- areaStyle: { color: 'rgba(245, 108, 108, 0.16)' },
|
|
|
|
|
- data: secondValues
|
|
|
|
|
- .filter((item) => Number.isFinite(item.x) && Number.isFinite(item.rawValue))
|
|
|
|
|
- .map((item) => [item.x, (item.rawValue - secondMin) / secondSpan] as [number, number]),
|
|
|
|
|
- })
|
|
|
|
|
- }
|
|
|
|
|
- if (showVolume) {
|
|
|
|
|
- const volumeValues = data.volumeSeries.data
|
|
|
|
|
- const volumeFinite = volumeValues.map((item) => item.volume).filter(Number.isFinite)
|
|
|
|
|
- const [volumeMin, volumeMax] = volumeFinite.length ? minMaxOf(volumeFinite) : [0, 1]
|
|
|
|
|
- const volumeSpan = volumeMax - volumeMin || 1
|
|
|
|
|
- series.push({
|
|
|
|
|
- name: '体积',
|
|
|
|
|
- type: 'line',
|
|
|
|
|
- xAxisIndex: 0,
|
|
|
|
|
- yAxisIndex: 0,
|
|
|
|
|
- showSymbol: false,
|
|
|
|
|
- lineStyle: { width: 1.5, color: colors['体积'], opacity: 0.9 },
|
|
|
|
|
- data: volumeValues
|
|
|
|
|
- .filter((item) => Number.isFinite(item.x) && Number.isFinite(item.volume))
|
|
|
|
|
- .map((item) => [item.x, (item.volume - volumeMin) / volumeSpan] as [number, number]),
|
|
|
|
|
- })
|
|
|
|
|
- }
|
|
|
|
|
- if (showSite) {
|
|
|
|
|
- const siteFinite = siteSeries.flatMap((series) => series.data.map((item) => item.value[1])).filter(Number.isFinite)
|
|
|
|
|
- const [siteMin, siteMax] = siteFinite.length ? minMaxOf(siteFinite) : [0, 1]
|
|
|
|
|
- const siteSpan = siteMax - siteMin || 1
|
|
|
|
|
- siteSeries.forEach((siteItem, index) => {
|
|
|
|
|
- const color = SITE_POINT_COLORS[index % SITE_POINT_COLORS.length]
|
|
|
|
|
- series.push({
|
|
|
|
|
- name: siteItem.itemName,
|
|
|
|
|
- type: 'line',
|
|
|
|
|
- xAxisIndex: 0,
|
|
|
|
|
- yAxisIndex: 0,
|
|
|
|
|
- showSymbol: false,
|
|
|
|
|
- connectNulls: false,
|
|
|
|
|
- sampling: 'lttb' as const,
|
|
|
|
|
- lineStyle: { width: 1.4, color, opacity: 0.8 },
|
|
|
|
|
- itemStyle: { color },
|
|
|
|
|
- data: siteItem.data
|
|
|
|
|
- .filter((item) => Number.isFinite(item.x) && Number.isFinite(item.value[1]))
|
|
|
|
|
- .map((item) => [item.x, (item.value[1] - siteMin) / siteSpan] as [number, number]),
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ sampling: 'lttb' as const,
|
|
|
|
|
+ lineStyle: { width: 1.6, color, opacity: 0.9 },
|
|
|
|
|
+ itemStyle: { color },
|
|
|
|
|
+ data: siteItem.data
|
|
|
|
|
+ .filter((item) => Number.isFinite(item.x) && Number.isFinite(item.value[1]))
|
|
|
|
|
+ .map((item): (number | null)[] => {
|
|
|
|
|
+ if (item.value[1] === 0) return [item.x, null]
|
|
|
|
|
+ return normalized
|
|
|
|
|
+ ? [item.x, normValue(item.value[1], lo, hi), item.value[1]]
|
|
|
|
|
+ : [item.x, item.value[1]]
|
|
|
|
|
+ }),
|
|
|
|
|
+ markArea: { silent: true, data: periodBackground },
|
|
|
})
|
|
})
|
|
|
- }
|
|
|
|
|
- } else {
|
|
|
|
|
- metas.forEach((meta, index) => {
|
|
|
|
|
- if (meta.kind === 'device') {
|
|
|
|
|
- const source = data.series.find((series) => series.devicePoint === meta.name)
|
|
|
|
|
- const color = source?.color ?? colors[source?.measurementType ?? '加速度']
|
|
|
|
|
- series.push({
|
|
|
|
|
- name: meta.name,
|
|
|
|
|
- type: 'line',
|
|
|
|
|
- z: 10,
|
|
|
|
|
- xAxisIndex: index,
|
|
|
|
|
- yAxisIndex: index,
|
|
|
|
|
- showSymbol: false,
|
|
|
|
|
- connectNulls: false,
|
|
|
|
|
- sampling: 'lttb' as const,
|
|
|
|
|
- lineStyle: { width: 2.5, color, cap: 'round', join: 'round' },
|
|
|
|
|
- itemStyle: { color },
|
|
|
|
|
- data: source?.data
|
|
|
|
|
- .filter((item) => Number.isFinite(item.x) && Number.isFinite(item.rawValue))
|
|
|
|
|
- .map((item) => [item.x, item.rawValue] as [number, number]) ?? [],
|
|
|
|
|
- markArea: { silent: true, data: periodBackground },
|
|
|
|
|
- markLine: index === 0 ? { silent: true, symbol: 'none', data: triggerLines(data.triggerXs) } : undefined,
|
|
|
|
|
- })
|
|
|
|
|
- } else if (meta.kind === 'site') {
|
|
|
|
|
- const siteItem = siteSeries.find((series) => series.itemName === meta.name)
|
|
|
|
|
- if (!siteItem) return
|
|
|
|
|
- const color = SITE_POINT_COLORS[meta.siteSeq % SITE_POINT_COLORS.length]
|
|
|
|
|
|
|
+ } else if (meta.kind === 'siteAgg') {
|
|
|
|
|
+ siteSeries.forEach((siteItem) => {
|
|
|
|
|
+ const siteValues = siteItem.data
|
|
|
|
|
+ .filter((item) => Number.isFinite(item.value[1]) && item.value[1] !== 0)
|
|
|
|
|
+ .map((item) => item.value[1])
|
|
|
|
|
+ if (!siteValues.length) return
|
|
|
|
|
+ const siteMin = Math.min(...siteValues)
|
|
|
|
|
+ const siteMax = Math.max(...siteValues)
|
|
|
|
|
+ const siteSpan = siteMax - siteMin
|
|
|
|
|
+ const siteIndex = siteSeries.findIndex((series) => series.itemName === siteItem.itemName)
|
|
|
|
|
+ const color = SITE_POINT_COLORS[siteIndex % SITE_POINT_COLORS.length]
|
|
|
series.push({
|
|
series.push({
|
|
|
- name: meta.name,
|
|
|
|
|
|
|
+ name: `${SITE_AGG_PREFIX}${siteItem.itemName}`,
|
|
|
type: 'line',
|
|
type: 'line',
|
|
|
- z: 10,
|
|
|
|
|
|
|
+ z: 9,
|
|
|
xAxisIndex: index,
|
|
xAxisIndex: index,
|
|
|
yAxisIndex: index,
|
|
yAxisIndex: index,
|
|
|
showSymbol: false,
|
|
showSymbol: false,
|
|
|
connectNulls: false,
|
|
connectNulls: false,
|
|
|
sampling: 'lttb' as const,
|
|
sampling: 'lttb' as const,
|
|
|
- lineStyle: { width: 1.6, color, opacity: 0.9 },
|
|
|
|
|
|
|
+ lineStyle: { width: 1.2, color, opacity: 0.85 },
|
|
|
itemStyle: { color },
|
|
itemStyle: { color },
|
|
|
data: siteItem.data
|
|
data: siteItem.data
|
|
|
.filter((item) => Number.isFinite(item.x) && Number.isFinite(item.value[1]))
|
|
.filter((item) => Number.isFinite(item.x) && Number.isFinite(item.value[1]))
|
|
|
- .map((item) => [item.x, item.value[1]] as [number, number]),
|
|
|
|
|
- markArea: { silent: true, data: periodBackground },
|
|
|
|
|
- })
|
|
|
|
|
- } else if (meta.kind === 'siteAgg') {
|
|
|
|
|
- siteSeries.forEach((siteItem) => {
|
|
|
|
|
- const siteValues = siteItem.data
|
|
|
|
|
- .filter((item) => Number.isFinite(item.value[1]))
|
|
|
|
|
- .map((item) => item.value[1])
|
|
|
|
|
- if (!siteValues.length) return
|
|
|
|
|
- const siteMin = Math.min(...siteValues)
|
|
|
|
|
- const siteMax = Math.max(...siteValues)
|
|
|
|
|
- const siteSpan = siteMax - siteMin
|
|
|
|
|
- const siteIndex = siteSeries.findIndex((series) => series.itemName === siteItem.itemName)
|
|
|
|
|
- const color = SITE_POINT_COLORS[siteIndex % SITE_POINT_COLORS.length]
|
|
|
|
|
- series.push({
|
|
|
|
|
- name: `${SITE_AGG_PREFIX}${siteItem.itemName}`,
|
|
|
|
|
- type: 'line',
|
|
|
|
|
- z: 9,
|
|
|
|
|
- xAxisIndex: index,
|
|
|
|
|
- yAxisIndex: index,
|
|
|
|
|
- showSymbol: false,
|
|
|
|
|
- connectNulls: false,
|
|
|
|
|
- sampling: 'lttb' as const,
|
|
|
|
|
- lineStyle: { width: 1.2, color, opacity: 0.85 },
|
|
|
|
|
- itemStyle: { color },
|
|
|
|
|
- data: siteItem.data
|
|
|
|
|
- .filter((item) => Number.isFinite(item.x) && Number.isFinite(item.value[1]))
|
|
|
|
|
- .map((item) => [item.x, siteSpan > 0 ? (item.value[1] - siteMin) / siteSpan : 0.5] as [number, number]),
|
|
|
|
|
- markArea: { silent: true, data: periodBackground },
|
|
|
|
|
- })
|
|
|
|
|
- })
|
|
|
|
|
- } else if (meta.kind === 'second') {
|
|
|
|
|
- series.push({
|
|
|
|
|
- name: '周期数据',
|
|
|
|
|
- type: 'line',
|
|
|
|
|
- z: 10,
|
|
|
|
|
- xAxisIndex: index,
|
|
|
|
|
- yAxisIndex: index,
|
|
|
|
|
- showSymbol: false,
|
|
|
|
|
- connectNulls: false,
|
|
|
|
|
- lineStyle: { width: 3, color: colors['周期数据'], cap: 'round', join: 'round' },
|
|
|
|
|
- areaStyle: { color: 'rgba(245, 108, 108, 0.16)' },
|
|
|
|
|
- itemStyle: { color: colors['周期数据'] },
|
|
|
|
|
- data: data.secondSeries.data
|
|
|
|
|
- .filter((item) => Number.isFinite(item.x) && Number.isFinite(item.rawValue))
|
|
|
|
|
- .map((item) => [item.x, item.rawValue] as [number, number]),
|
|
|
|
|
|
|
+ .map((item): (number | null)[] => {
|
|
|
|
|
+ if (item.value[1] === 0) return [item.x, null]
|
|
|
|
|
+ return [
|
|
|
|
|
+ item.x,
|
|
|
|
|
+ siteSpan > 0 ? (item.value[1] - siteMin) / siteSpan : 0.5,
|
|
|
|
|
+ item.value[1],
|
|
|
|
|
+ ]
|
|
|
|
|
+ }),
|
|
|
markArea: { silent: true, data: periodBackground },
|
|
markArea: { silent: true, data: periodBackground },
|
|
|
})
|
|
})
|
|
|
- } else if (meta.kind === 'volume') {
|
|
|
|
|
- series.push({
|
|
|
|
|
- name: '体积',
|
|
|
|
|
- type: 'line',
|
|
|
|
|
- z: 10,
|
|
|
|
|
- xAxisIndex: index,
|
|
|
|
|
- yAxisIndex: index,
|
|
|
|
|
- showSymbol: false,
|
|
|
|
|
- lineStyle: { width: 2, color: colors['体积'] },
|
|
|
|
|
- itemStyle: { color: colors['体积'] },
|
|
|
|
|
- data: data.volumeSeries.data
|
|
|
|
|
- .filter((item) => Number.isFinite(item.x) && Number.isFinite(item.volume))
|
|
|
|
|
- .map((item) => [item.x, item.volume] as [number, number]),
|
|
|
|
|
- })
|
|
|
|
|
- }
|
|
|
|
|
- })
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ })
|
|
|
|
|
+ } else if (meta.kind === 'second') {
|
|
|
|
|
+ const [lo, hi] = minMaxOf(data.secondSeries.data.map((item) => item.rawValue).filter(Number.isFinite))
|
|
|
|
|
+ series.push({
|
|
|
|
|
+ name: '周期数据',
|
|
|
|
|
+ type: 'line',
|
|
|
|
|
+ z: 10,
|
|
|
|
|
+ xAxisIndex: index,
|
|
|
|
|
+ yAxisIndex: index,
|
|
|
|
|
+ showSymbol: false,
|
|
|
|
|
+ connectNulls: false,
|
|
|
|
|
+ lineStyle: { width: 3, color: colors['周期数据'], cap: 'round', join: 'round' },
|
|
|
|
|
+ areaStyle: { color: 'rgba(245, 108, 108, 0.16)' },
|
|
|
|
|
+ itemStyle: { color: colors['周期数据'] },
|
|
|
|
|
+ data: data.secondSeries.data
|
|
|
|
|
+ .filter((item) => Number.isFinite(item.x) && Number.isFinite(item.rawValue))
|
|
|
|
|
+ .map((item): number[] => (normalized
|
|
|
|
|
+ ? [item.x, normValue(item.rawValue, lo, hi), item.rawValue]
|
|
|
|
|
+ : [item.x, item.rawValue])),
|
|
|
|
|
+ markArea: { silent: true, data: periodBackground },
|
|
|
|
|
+ })
|
|
|
|
|
+ } else if (meta.kind === 'volume') {
|
|
|
|
|
+ const [lo, hi] = minMaxOf(data.volumeSeries.data.map((item) => item.volume).filter(Number.isFinite))
|
|
|
|
|
+ series.push({
|
|
|
|
|
+ name: '体积',
|
|
|
|
|
+ type: 'line',
|
|
|
|
|
+ z: 10,
|
|
|
|
|
+ xAxisIndex: index,
|
|
|
|
|
+ yAxisIndex: index,
|
|
|
|
|
+ showSymbol: false,
|
|
|
|
|
+ lineStyle: { width: 2, color: colors['体积'] },
|
|
|
|
|
+ itemStyle: { color: colors['体积'] },
|
|
|
|
|
+ data: data.volumeSeries.data
|
|
|
|
|
+ .filter((item) => Number.isFinite(item.x) && Number.isFinite(item.volume))
|
|
|
|
|
+ .map((item): number[] => (normalized
|
|
|
|
|
+ ? [item.x, normValue(item.volume, lo, hi), item.volume]
|
|
|
|
|
+ : [item.x, item.volume])),
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
|
|
|
const zoom = zoomMode === 'keep' ? readCurrentZoom(data) : initialZoom(data)
|
|
const zoom = zoomMode === 'keep' ? readCurrentZoom(data) : initialZoom(data)
|
|
|
series.push(...buildAnnotationOverlaySeries(props.annotations ?? []))
|
|
series.push(...buildAnnotationOverlaySeries(props.annotations ?? []))
|
|
@@ -609,24 +540,26 @@ function buildOption(zoomMode: 'initial' | 'keep' = 'initial') {
|
|
|
const lines = [`<strong>窗口位置:${Number(first.value?.[0] ?? 0).toFixed(4)}</strong>`]
|
|
const lines = [`<strong>窗口位置:${Number(first.value?.[0] ?? 0).toFixed(4)}</strong>`]
|
|
|
params.forEach((item) => {
|
|
params.forEach((item) => {
|
|
|
const value = item.value?.[1]
|
|
const value = item.value?.[1]
|
|
|
- if (value !== undefined && value !== null) {
|
|
|
|
|
- const fullName = String(item.seriesName ?? '')
|
|
|
|
|
- const isAgg = fullName.startsWith(SITE_AGG_PREFIX)
|
|
|
|
|
- const base = isAgg ? fullName.slice(SITE_AGG_PREFIX.length) : fullName
|
|
|
|
|
- const seriesName = props.siteDescriptions?.[base] || base
|
|
|
|
|
|
|
+ if (value === undefined || value === null) return
|
|
|
|
|
+ const raw = item.value?.[2]
|
|
|
|
|
+ const fullName = String(item.seriesName ?? '')
|
|
|
|
|
+ const isAgg = fullName.startsWith(SITE_AGG_PREFIX)
|
|
|
|
|
+ const base = isAgg ? fullName.slice(SITE_AGG_PREFIX.length) : fullName
|
|
|
|
|
+ const seriesName = props.siteDescriptions?.[base] || base
|
|
|
|
|
+ const marker = `<span style="color:${item.color}">●</span> ${seriesName}`
|
|
|
|
|
+ if (raw !== undefined && raw !== null) {
|
|
|
lines.push(
|
|
lines.push(
|
|
|
- `<span style="color:${item.color}">●</span> ${seriesName}${isAgg ? ' (归一化)' : ''}: `
|
|
|
|
|
- + `${Number(value).toPrecision(isAgg ? 3 : 7)}`,
|
|
|
|
|
|
|
+ `${marker}: 归一化 ${Number(value).toFixed(3)} / 原值 ${Number(raw).toPrecision(7)}`,
|
|
|
)
|
|
)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ lines.push(`${marker}${isAgg ? ' (归一化)' : ''}: ${Number(value).toPrecision(isAgg ? 3 : 7)}`)
|
|
|
}
|
|
}
|
|
|
})
|
|
})
|
|
|
return lines.join('<br/>')
|
|
return lines.join('<br/>')
|
|
|
},
|
|
},
|
|
|
},
|
|
},
|
|
|
legend: {
|
|
legend: {
|
|
|
- data: props.mode === 'merge'
|
|
|
|
|
- ? [...devicePoints, ...siteSeries.map((series) => series.itemName), ...(showSecond ? ['周期数据'] : []), ...(showVolume ? ['体积'] : [])]
|
|
|
|
|
- : metas.filter((meta) => meta.kind !== 'siteAgg').map((meta) => meta.name),
|
|
|
|
|
|
|
+ data: metas.filter((meta) => meta.kind !== 'siteAgg').map((meta) => meta.name),
|
|
|
formatter: (name: string) => props.siteDescriptions?.[name] || name,
|
|
formatter: (name: string) => props.siteDescriptions?.[name] || name,
|
|
|
top: 0,
|
|
top: 0,
|
|
|
left: 70,
|
|
left: 70,
|
|
@@ -650,14 +583,6 @@ function buildOption(zoomMode: 'initial' | 'keep' = 'initial') {
|
|
|
end: zoom.end,
|
|
end: zoom.end,
|
|
|
},
|
|
},
|
|
|
],
|
|
],
|
|
|
- graphic: props.mode === 'merge' ? [
|
|
|
|
|
- {
|
|
|
|
|
- type: 'text',
|
|
|
|
|
- left: 70,
|
|
|
|
|
- top: 31,
|
|
|
|
|
- style: { text: '归一化值', fill: '#8b9aa2', fontSize: 11 },
|
|
|
|
|
- },
|
|
|
|
|
- ] : [],
|
|
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -804,7 +729,7 @@ function onBlankDoubleClick(event: any) {
|
|
|
const px = event.offsetX
|
|
const px = event.offsetX
|
|
|
const py = event.offsetY
|
|
const py = event.offsetY
|
|
|
if (typeof px !== 'number' || typeof py !== 'number') return
|
|
if (typeof px !== 'number' || typeof py !== 'number') return
|
|
|
- const gridCount = props.mode === 'merge' ? 1 : props.data.devicePoints.length + splitExtraCount(props.data) + 1
|
|
|
|
|
|
|
+ const gridCount = props.data.devicePoints.length + splitExtraCount(props.data) + 1
|
|
|
let inGrid = false
|
|
let inGrid = false
|
|
|
for (let index = 0; index < gridCount; index += 1) {
|
|
for (let index = 0; index < gridCount; index += 1) {
|
|
|
if (chart.containPixel({ gridIndex: index }, [px, py])) {
|
|
if (chart.containPixel({ gridIndex: index }, [px, py])) {
|
|
@@ -896,7 +821,7 @@ onBeforeUnmount(() => {
|
|
|
<canvas :ref="(el) => setPvCanvas(el, rowIndex)" class="pv-preview-canvas" :aria-label="row.title"></canvas>
|
|
<canvas :ref="(el) => setPvCanvas(el, rowIndex)" class="pv-preview-canvas" :aria-label="row.title"></canvas>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
- <div ref="chartElement" class="wave-chart" :class="{ 'is-merge': mode === 'merge' }" :style="{ height: `${layoutHeightPx}px` }"></div>
|
|
|
|
|
|
|
+ <div ref="chartElement" class="wave-chart" :style="{ height: `${layoutHeightPx}px` }"></div>
|
|
|
<div v-if="annotations?.length" class="annotation-nav">
|
|
<div v-if="annotations?.length" class="annotation-nav">
|
|
|
<div class="annotation-nav-head">
|
|
<div class="annotation-nav-head">
|
|
|
<span class="annotation-nav-title">标注导航</span>
|
|
<span class="annotation-nav-title">标注导航</span>
|