diff --git a/nezha-fronted/src/assets/css/common.scss b/nezha-fronted/src/assets/css/common.scss index 7856ea690..8e60399da 100644 --- a/nezha-fronted/src/assets/css/common.scss +++ b/nezha-fronted/src/assets/css/common.scss @@ -440,7 +440,7 @@ td .nz-icon-gear:before { .chart-bar, .chart-gauge, .chart-sankey, -.chart-time-series, +.chart-time-series-tooltip, .chart-treemap, .chart-pie, .chart-bubble, @@ -456,7 +456,7 @@ td .nz-icon-gear:before { color: $--color-text-regular !important; box-shadow: none !important; } -.chart-time-series, +.chart-time-series-tooltip, .chart-pie, .chart-bar, .chart-treemap, @@ -464,7 +464,26 @@ td .nz-icon-gear:before { visibility: hidden; position: absolute; } - +.chart-time-series-tooltip { + position: absolute; + display: block; + border-style: solid; + white-space: nowrap; + box-shadow: rgba(0, 0, 0, 0.2) 1px 2px 10px; + transition: opacity 0.2s cubic-bezier(0.23, 1, 0.32, 1) 0s, visibility 0.2s cubic-bezier(0.23, 1, 0.32, 1) 0s, transform 0.4s cubic-bezier(0.23, 1, 0.32, 1) 0s; + background-color: rgb(255, 255, 255); + border-width: 1px; + border-radius: 4px; + color: rgb(102, 102, 102); + font: 14px / 21px "Microsoft YaHei"; + padding: 10px; + top: 0px; + left: 0px; + border-color: rgb(255, 255, 255); + z-index: 99999999; + visibility: visible; + pointer-events: none; +} .yAxis-icon{ margin-right: 4px; background: transparent !important; @@ -543,7 +562,7 @@ i.nz-icon-override{ outline: none; } } -.chart-time-series.hide{ +.chart-time-series-tooltip.hide{ display: none !important; } .alert-rule-info-two{ diff --git a/nezha-fronted/src/components/chart/chart/legend.vue b/nezha-fronted/src/components/chart/chart/legend.vue index 719ab46d7..2540f1176 100644 --- a/nezha-fronted/src/components/chart/chart/legend.vue +++ b/nezha-fronted/src/components/chart/chart/legend.vue @@ -247,26 +247,15 @@ export default { if (this.isGrey[index]) { return false } - return if (this.chartInfo.type === 'pie' || this.chartInfo.type === 'doughnut' || this.chartInfo.type === 'rose') { this.$emit('hoverLegendD3', legendName, index, type) } else if (this.isTimeSeries) { - if (type == 'highlight' && getChart(this.chartId)) { - const option = getChart(this.chartId).getOption() - const series = this.$lodash.cloneDeep(option.series) - series[index].emphasis.focus = 'series' - getChart(this.chartId).setOption({ series }) - } else if (getChart(this.chartId)) { - const option = getChart(this.chartId).getOption() - const series = this.$lodash.cloneDeep(option.series) - series[index].emphasis.focus = 'none' - getChart(this.chartId).setOption({ series }) + const echarts = getChart(this.chartId) + if (type == 'highlight') { + echarts.setSeries(index + 1, {focus:true}) + } else { + echarts.setSeries(null, {focus: true}) } - getChart(this.chartId) && getChart(this.chartId).dispatchAction({ - type: type, - seriesIndex: index, - name: legendName - }) } else { getChart(this.chartId) && getChart(this.chartId).dispatchAction({ type: type, diff --git a/nezha-fronted/src/components/chart/chart/uplot/chartTimeSeries.vue b/nezha-fronted/src/components/chart/chart/uplot/chartTimeSeries.vue index 744993a55..940ad3763 100644 --- a/nezha-fronted/src/components/chart/chart/uplot/chartTimeSeries.vue +++ b/nezha-fronted/src/components/chart/chart/uplot/chartTimeSeries.vue @@ -200,7 +200,7 @@ export default { } }) ], - padding: [15, 15, 15, 15], + padding: [15, this.autoPadRight, 15, 15], legend: { show: false }, @@ -214,11 +214,11 @@ export default { { scale: 'left', values: (u, vals, space) => vals.map(v => leftUnitCompute.compute(v, null, -1, decimals)), - formatValue: (v, d) => { - console.log(v, d, 'vd') - return v - }, incrs: incrs, + // space: (self) => { + // console.log(self) + // return 50 + // }, size (self, values, axisIdx, cycleNum) { const axis = self.axes[axisIdx] @@ -243,34 +243,14 @@ export default { { show: true, side: 1, - grid: { show: false, width: 0 }, + grid: { show: false }, scale: 'right', + // space: (self) => { + // console.log(self) + // return 50 + // }, values: (u, vals, space) => vals.map(v => rightUnitCompute.compute(v, null, -1, decimals)), - formatValue: (v, d) => { - console.log(v, d, 'vd') - return v - }, incrs: rightIncrs - // size (self, values, axisIdx, cycleNum) { - // const axis = self.axes[axisIdx] - // - // // bail out, force convergence - // if (cycleNum > 1) { return axis._size } - // - // let axisSize = axis.ticks.size + axis.gap - // - // // find longest value - // const longestVal = (values ?? []).reduce((acc, val) => ( - // val.length > acc.length ? val : acc - // ), '') - // - // if (longestVal != '') { - // self.ctx.font = axis.font[0] - // axisSize += self.ctx.measureText(longestVal).width / devicePixelRatio - // } - // - // return Math.ceil(axisSize) - // } } ] } diff --git a/nezha-fronted/src/components/chart/chart/uplot/chartTimeSeriesMixin.js b/nezha-fronted/src/components/chart/chart/uplot/chartTimeSeriesMixin.js index bb58b0658..86a460957 100644 --- a/nezha-fronted/src/components/chart/chart/uplot/chartTimeSeriesMixin.js +++ b/nezha-fronted/src/components/chart/chart/uplot/chartTimeSeriesMixin.js @@ -2,8 +2,9 @@ import { initColor, Incrs } from '@/components/chart/chart/tools' import { randomcolor } from '@/components/common/js/radomcolor/randomcolor' import chartDataFormat from '@/components/chart/chartDataFormat' import lodash from 'lodash' -import { getMetricTypeValue } from '@/components/common/js/tools' +import { formatScientificNotation, getMetricTypeValue } from '@/components/common/js/tools' import uPlot from 'uplot' +import bus from '@/libs/bus' export default { data () { return { @@ -75,9 +76,12 @@ export default { const elementNames = this.$lodash.get(this.chartInfo, 'param.rightYAxis.elementNames', []) isRight = elementNames.indexOf(series.elements.name) !== -1 } + const name = legend.name + const alias = legend.alias + const statistics = series.statistics const obj = { - name: series.elements.name + JSON.stringify(series.metric), - label: series.elements.name + JSON.stringify(series.metric), + name: name, + label: alias, class: series.elements.name + JSON.stringify(series.metric), scale: isRight ? 'right' : 'left', // right yAxisIndex: isRight ? 1 : 0, // right @@ -85,9 +89,6 @@ export default { stroke: this.seriesColor[chartIndex], width: 1 / devicePixelRatio } - const name = legend.name - const alias = legend.alias - const statistics = series.statistics this.legends.push({ name, alias, statistics, color: this.seriesColor[chartIndex] }) return obj }, @@ -96,17 +97,11 @@ export default { let tooltipTopOffset = 0 const self = this const tooltip = document.createElement('div') - tooltip.className = 'u-tooltip' - let seriesIdx = null let dataIdx = null - - const fmtDate = uPlot.fmtDate('{M}/{D}/{YY} {h}:{mm}:{ss} {AA}') - let over - let tooltipVisible = false - + let isRender = false function showTooltip () { if (!tooltipVisible) { tooltip.style.display = 'block' @@ -123,23 +118,240 @@ export default { } } - function setTooltip (u) { + function setTooltip (u) { // 生成tooltip内容 showTooltip() - - const top = u.valToPos(u.data[seriesIdx][dataIdx], 'y') + if (!self.$lodash.get(self.chartInfo, 'param.enable.tooltip', false)) { + return + } + isRender = false + console.log(self.chartInfo, seriesIdx, dataIdx, u) + const { left, top } = u.cursor const lft = u.valToPos(u.data[0][dataIdx], 'x') - tooltip.style.top = (tooltipTopOffset + top + shiftX) + 'px' tooltip.style.left = (tooltipLeftOffset + lft + shiftY) + 'px' + let params = [] + const tooltipModel = self.$lodash.get(self.chartInfo, 'param.tooltip.mode', 'single') + console.log(tooltipModel) + if (self.chartInfo.param && !(tooltipModel == 'single')) { + params = self.series.map((s, i) => { + return { + seriesIndex: i, + yAxisIndex: s.yAxisIndex, + value: [u.data[0][dataIdx], u.data[i + 1][dataIdx]], + seriesName: s.label + } + }) + params = params.filter(item => { + console.log(item.value[1], typeof (item.value[1]) == 'undefined') + const flag = typeof (item.value[1]) == 'undefined' + return !flag + }) + } else if (seriesIdx) { + const obj = self.series[seriesIdx - 1] + params = [{ + seriesIndex: seriesIdx - 1, + yAxisIndex: obj.yAxisIndex, + value: [u.data[0][dataIdx], u.data[seriesIdx][dataIdx]], + seriesName: obj.label + }] + } + isRender = !!params.length + console.log(isRender, 'isRender') + if (!isRender) { + tooltip.style.display = 'none' + over.style.cursor = null + return + } else { + tooltip.style.display = 'block' + over.style.cursor = 'pointer' + } + tooltip.className = 'chart-time-series-tooltip' + let str = '
' + const hasTotal = self.isStack + const decimals = self.chartInfo.param.decimals || 2 + // 区分左y轴右y轴 + params.forEach(item => { + if (self.series[item.seriesIndex]) { + item.yAxisIndex = self.series[item.seriesIndex].yAxisIndex + } + }) + // 排序先展示左y轴 + params.sort((a, b) => a.yAxisIndex - b.yAxisIndex) + // 分割 + const rightYAxisIndex = params.findIndex(item => item.yAxisIndex == 1) + let leftYAxis = [] + let rightYAxis = [] + if (rightYAxisIndex != -1) { + leftYAxis = params.slice(0, rightYAxisIndex) + rightYAxis = params.slice(rightYAxisIndex) + } else { + leftYAxis = params + } + handler(leftYAxis, 'left') + handler(rightYAxis, 'right') + function handler (arr, type) { + console.log(arr, type) + if (!arr.length) { + return + } - tooltip.style.borderColor = self.seriesColor[seriesIdx - 1] - const pctSinceStart = (((u.data[seriesIdx][dataIdx] - u.data[seriesIdx][0]) / u.data[seriesIdx][0]) * 100).toFixed(2) - tooltip.textContent = ( - fmtDate(new Date(u.data[0][dataIdx] * 1e3)) + ' - ' + '\n' + - uPlot.fmtNum(u.data[seriesIdx][dataIdx]) + ' (' + pctSinceStart + '% since start)' - ) + // tooltip排序 + let sortBy + if (self.$lodash.get(self.chartInfo, 'param.enable.tooltip') && self.$lodash.get(self.chartInfo, 'param.tooltip.mode') !== 'single' && self.$lodash.get(self.chartInfo, 'param.tooltip.sort') !== 'none') { + sortBy = self.$lodash.get(self.chartInfo, 'param.tooltip.sort') + } + const previousIndex = arr.findIndex(item => item.seriesName.indexOf('Previous') !== -1) + if (previousIndex != -1) { // 对比状态 + const arrNow = arr.slice(0, previousIndex) + const arrPrevious = arr.slice(previousIndex) + if (sortBy === 'asc') { + arrNow.sort((a, b) => a.value[1] - b.value[1]) + arrPrevious.sort((a, b) => a.value[1] - b.value[1]) + } else if (sortBy === 'desc') { + arrNow.sort((a, b) => b.value[1] - a.value[1]) + arrPrevious.sort((a, b) => b.value[1] - a.value[1]) + } + arr = [...arrNow, ...arrPrevious] + } else { + if (sortBy === 'asc') { + arr.sort((a, b) => a.value[1] - b.value[1]) + } else if (sortBy === 'desc') { + arr.sort((a, b) => b.value[1] - a.value[1]) + } + } + + let sum = 0 + let flag = true + arr.forEach((item, i) => { + let unit = self.chartInfo.unit ? self.chartInfo.unit : 2 + if (type == 'right') { + unit = lodash.get(self, 'chartInfo.param.rightYAxis.unit', 2) + } + const nameArr = item.seriesName.split('-') + // if (nameArr.length > 1) { + // nameArr.splice(nameArr.length - 1, 1) + // } + const seriesName = nameArr.join('-') + if (i === 0 && item.seriesName.indexOf('Previous') === -1 && type == 'left') { + const value = bus.computeTimezone(item.value[0] * 1000) + const tData = new Date(value) + str += '
' + str += bus.timeFormate(tData) + str += '
' + } + // 两条y轴数据分割线 + if (i == 0 && rightYAxisIndex != 0 && type == 'right') { + str += '
' + } + if (item.seriesName.indexOf('Previous') === -1 && type == 'right') { + if (i == 0 && (rightYAxisIndex == 0 || (arr.some(item => item.seriesName.indexOf('Previous') !== -1)))) { + const value = bus.computeTimezone(item.value[0] * 1000) + const tData = new Date(value) + str += '
' + str += bus.timeFormate(tData) + str += '
' + } + } + // Previous分割线 + if (i !== 0 && flag && item.seriesName.indexOf('Previous') !== -1) { + str += '
' + } + if (flag && item.seriesName.indexOf('Previous') !== -1) { + flag = false + const value = bus.computeTimezone(item.value[0] * 1000 - self.minusTime) + const tData = new Date(value) + str += '
' + str += bus.timeFormate(tData) + str += '
' + } + const color = self.colorList[item.seriesIndex] + const previousItem = arr.find((series) => ('Previous ' + item.seriesName) === series.seriesName) + let paramsDot = bus.countDecimals(item.value[1]) + if (paramsDot < decimals) { + paramsDot = decimals + } else if (paramsDot > 6) { + paramsDot = 6 + } + const val = formatScientificNotation(item.value[1], paramsDot) + sum += isNaN(self.numberWithEConvent(val)) ? 0 : parseFloat(self.numberWithEConvent(val)) + let previousDom = '' + if (previousItem) { + const previousVal = formatScientificNotation(previousItem.value[1], paramsDot) + let minusVal = 0 + let operator + if (previousVal <= val) { + minusVal = val - previousVal + operator = '+' + } else { + minusVal = previousVal - val + operator = '-' + } + previousDom = ` + ${operator}${chartDataFormat.getUnit(unit).compute(minusVal, null, -1, decimals)} + ` + } + + // 根据左右轴设置图标 + let className = 'row__color-block' + if (item.yAxisIndex == 0) { + className = 'yAxis-icon nz-icon nz-icon-zuozongzhou' + } else if (item.yAxisIndex == 1) { + className = 'yAxis-icon nz-icon nz-icon-youzongzhou' + } + + // 鼠标悬浮 series data symbol 时,tooltip 中相应的legend 高亮显示 + str += ` +
+
+ + ${seriesName} +
+
+ ${chartDataFormat.getUnit(unit).compute(val, null, -1, decimals)} + ${previousDom} +
+
+ ` + }) + // 显示total + if (hasTotal) { + sum = parseFloat(Number(sum).toFixed(2)) + let unit = self.chartInfo.unit ? self.chartInfo.unit : 2 + let className = 'row__color-block' + let color = '' + if (type == 'left') { + // 判断是否开启rightYAxis 否则显示普通图标 + if (self.series.some(item => item.yAxisIndex == 0)) { + className = 'yAxis-icon nz-icon nz-icon-zuozongzhou' + } + if (!self.stackTotalColor) { + self.stackTotalColor = randomcolor() + } + color = self.stackTotalColor + } else { + unit = lodash.get(self, 'chartInfo.param.rightYAxis.unit', 2) + className = 'yAxis-icon nz-icon nz-icon-youzongzhou' + if (!self.stackTotalColorRight) { + self.stackTotalColorRight = randomcolor() + } + color = self.stackTotalColorRight + } + str += ` +
+
+ + ${self.$t('dashboard.dashboard.chartTotal')} +
+
+ ${chartDataFormat.getUnit(unit).compute(sum, null, -1, decimals)} +
+
+ ` + } + } + str += '
' + tooltip.innerHTML = str } - return { hooks: { ready: [ @@ -151,14 +363,16 @@ export default { let clientX let clientY - + over.addEventListener('mouseleave', () => { + if (!u.cursor._lock) { + hideTooltip() + } + }) over.addEventListener('mousedown', e => { clientX = e.clientX clientY = e.clientY }) - over.addEventListener('mouseup', e => { - // clicked in-place if (e.clientX == clientX && e.clientY == clientY) { if (seriesIdx != null && dataIdx != null) { onclick(u, seriesIdx, dataIdx) @@ -170,11 +384,9 @@ export default { setCursor: [ u => { const c = u.cursor - if (dataIdx != c.idx) { dataIdx = c.idx - - if (seriesIdx != null) { setTooltip(u) } + setTooltip(u) } } ], @@ -182,8 +394,7 @@ export default { (u, sidx) => { if (seriesIdx != sidx) { seriesIdx = sidx - - if (sidx == null) { hideTooltip() } else if (dataIdx != null) { setTooltip(u) } + if (dataIdx != null) { setTooltip(u) } } } ]