diff --git a/core/core-frontend/src/views/chart/components/js/extremumUitl.ts b/core/core-frontend/src/views/chart/components/js/extremumUitl.ts new file mode 100644 index 0000000000..7adbf89877 --- /dev/null +++ b/core/core-frontend/src/views/chart/components/js/extremumUitl.ts @@ -0,0 +1,224 @@ +import { valueFormatter } from '@/views/chart/components/js/formatter' +import { parseJson } from '@/views/chart/components/js/util' +import { isEmpty } from 'lodash-es' + +export const clearExtremum = chart => { + // 清除图表标注 + const pointElement = document.getElementById('point_' + chart.id) + if (pointElement) { + pointElement.remove() + pointElement.parentNode?.removeChild(pointElement) + } +} + +/** + * 判断给定的RGBA字符串表示的颜色是亮色还是暗色 + * 通过计算RGB颜色值的加权平均值(灰度值),判断颜色的明暗 + * 如果给定的字符串不包含有效的RGBA值,则原样返回该字符串 + * + * @param rgbaString 一个RGBA颜色字符串,例如 "rgba(255, 255, 255, 1)" + * @param greyValue 灰度值默认128 + * @returns 如果计算出的灰度值大于等于128,则返回true,表示亮色;否则返回false,表示暗色。 + * 如果rgbaString不包含有效的RGBA值,则返回原字符串 + */ +const isColorLight = (rgbaString: string, greyValue = 128) => { + const lastRGBA = getRgbaColorLastRgba(rgbaString) + if (!isEmpty(lastRGBA)) { + // 计算灰度值的公式 + const grayLevel = lastRGBA.r * 0.299 + lastRGBA.g * 0.587 + lastRGBA.b * 0.114 + return grayLevel >= greyValue + } else { + return false + } +} + +/** + * 从给定的rgba颜色字符串中提取最后一个rgba值 + * @param rgbaString 包含一个或多个rgba颜色值的字符串 + * @returns 返回最后一个解析出的rgba对象,如果未找到rgba值,则返回null + */ +const getRgbaColorLastRgba = (rgbaString: string) => { + const rgbaPattern = /rgba\((\d+),\s*(\d+),\s*(\d+),\s*([\d.]+)\)/g + let match: string[] + let lastRGBA = null + while ((match = rgbaPattern.exec(rgbaString)) !== null) { + const r = parseInt(match[1]) + const g = parseInt(match[2]) + const b = parseInt(match[3]) + const a = parseFloat(match[4]) + lastRGBA = { r, g, b, a } + } + return lastRGBA +} + +function createExtremumDiv(id, value, formatterCfg, chartId) { + // 装标注的div + const parentElement = document.getElementById('point_' + chartId) + if (parentElement) { + // 标注div + const element = document.getElementById(id) + if (element) { + return + } + const div = document.createElement('div') + div.id = id + div.setAttribute( + 'style', + `width: auto; + height: auto; + border-radius: 2px; + position: relative; + padding: 4px 5px 4px 5px; + display:none; + transform: translateX(-50%); + white-space:nowrap;` + ) + div.textContent = valueFormatter(value, formatterCfg) + const span = document.createElement('span') + span.setAttribute( + 'style', + `display: block; + width: 0px; + height: 0px; + border: 4px solid transparent; + border-top-color: red; + position: absolute; + left: calc(50% - 4px); + margin-top:4px;` + ) + div.appendChild(span) + parentElement.appendChild(div) + } +} +/** + * 没有子类别字段的图表 + * @param chart + */ +const noChildrenFieldChart = chart => { + return ['area', 'bar'].includes(chart.type) +} + +export const extremumEvt = (newChart, chart, _options, container) => { + chart.container = container + newChart.on('afterrender', ev => { + createExtremumPoint(chart, ev) + }) +} + +const findMinMax = (data): { minItem; maxItem } => { + return data.reduce( + ({ minItem, maxItem }, currentItem) => { + if (minItem === undefined || currentItem._origin.value < minItem._origin.value) { + minItem = currentItem + } + if (maxItem === undefined || currentItem._origin.value > maxItem._origin.value) { + maxItem = currentItem + } + return { minItem, maxItem } + }, + { minItem: undefined, maxItem: undefined } + ) +} +export const createExtremumPoint = (chart, ev) => { + // 获取标注样式 + const { label: labelAttr, basicStyle } = parseJson(chart.customAttr) + const pointSize = basicStyle.lineSymbolSize + const { yAxis } = parseJson(chart) + clearExtremum(chart) + // 创建标注父元素 + const divParentElement = document.getElementById('point_' + chart.id) + if (!divParentElement) { + const divParent = document.createElement('div') + divParent.id = 'point_' + chart.id + divParent.style.position = 'fixed' + divParent.style.zIndex = '1' + // 将父标注加入到图表中 + const containerElement = document.getElementById(chart.container) + containerElement.insertBefore(divParent, containerElement.firstChild) + } + let geometriesDataArray = [] + // 获取数据点 + const intervalPoint = ev.view + .getGeometries() + .find((intervalItem: { type: string }) => intervalItem.type === 'interval') + if (intervalPoint) { + geometriesDataArray = intervalPoint.dataArray + } + const pointPoint = ev.view + .getGeometries() + .find((pointItem: { type: string }) => pointItem.type === 'point') + if (pointPoint) { + geometriesDataArray = pointPoint.dataArray + } + geometriesDataArray?.forEach(pointObjList => { + if (pointObjList && pointObjList.length > 0) { + const pointObj = pointObjList[0] + const { minItem, maxItem } = findMinMax(pointObjList.reverse()) + let attr + let showExtremum = false + if (noChildrenFieldChart(chart) || yAxis.length > 1) { + const seriesLabelFormatter = labelAttr.seriesLabelFormatter.find( + d => d.name === pointObj._origin.category + ) + showExtremum = seriesLabelFormatter?.showExtremum + attr = seriesLabelFormatter + } else { + showExtremum = labelAttr.seriesLabelFormatter[0]?.showExtremum + attr = labelAttr.seriesLabelFormatter[0] + } + const fontSize = attr ? attr.fontSize : labelAttr.fontSize + const maxKey = 'point_' + pointObj._origin.category + '-' + maxItem._origin.value + const minKey = 'point_' + pointObj._origin.category + '-' + minItem._origin.value + // 最值标注 + if (showExtremum) { + createExtremumDiv( + maxKey, + maxItem._origin.value, + attr ? attr.formatterCfg : labelAttr.labelFormatter, + chart.id + ) + createExtremumDiv( + minKey, + minItem._origin.value, + attr ? attr.formatterCfg : labelAttr.labelFormatter, + chart.id + ) + pointObjList.forEach(point => { + const pointElement = document.getElementById( + 'point_' + point._origin.category + '-' + point._origin.value + ) + if (pointElement) { + pointElement.style.position = 'absolute' + pointElement.style.position = 'absolute' + pointElement.style.top = + (point.y[1] ? point.y[1] : point.y) - + (fontSize + (pointSize ? pointSize : 0) + 12) + + 'px' + pointElement.style.left = point.x + 'px' + pointElement.style.zIndex = '10' + pointElement.style.fontSize = fontSize + 'px' + pointElement.style.lineHeight = fontSize + 'px' + // 渐变颜色时需要获取最后一个rgba的值作为背景 + const { r, b, g, a } = getRgbaColorLastRgba(point.color) + pointElement.style.backgroundColor = 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')' + pointElement.style.color = isColorLight(point.color) ? '#000' : '#fff' + pointElement.children[0]['style'].borderTopColor = + 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')' + pointElement.style.display = 'table' + } + }) + } else { + removeDivElement(maxKey) + removeDivElement(minKey) + } + } + }) + + function removeDivElement(key) { + const element = document.getElementById(key) + if (element) { + element.remove() + element.parentNode?.removeChild(element) + } + } +} diff --git a/core/core-frontend/src/views/chart/components/js/panel/charts/bar/bar.ts b/core/core-frontend/src/views/chart/components/js/panel/charts/bar/bar.ts index 43e4b0f65c..2a78ab9aa0 100644 --- a/core/core-frontend/src/views/chart/components/js/panel/charts/bar/bar.ts +++ b/core/core-frontend/src/views/chart/components/js/panel/charts/bar/bar.ts @@ -8,8 +8,6 @@ import { flow, hexColorToRGBA, parseJson, - registerExtremumPointEvt, - setExtremumPosition, setUpGroupSeriesColor, setUpStackSeriesColor } from '@/views/chart/components/js/util' @@ -23,6 +21,7 @@ import { import { getPadding, setGradientColor } from '@/views/chart/components/js/panel/common/common_antv' import { useI18n } from '@/hooks/web/useI18n' import { DEFAULT_LABEL } from '@/views/chart/components/editor/util/chart' +import { clearExtremum, extremumEvt } from '@/views/chart/components/js/extremumUitl' const { t } = useI18n() const DEFAULT_DATA: any[] = [] @@ -97,6 +96,7 @@ export class Bar extends G2PlotChartView { drawChart(drawOptions: G2PlotDrawOptions): Column { const { chart, container, action } = drawOptions if (!chart?.data?.data?.length) { + clearExtremum(chart) return } const data = cloneDeep(drawOptions.chart.data?.data) @@ -109,7 +109,7 @@ export class Bar extends G2PlotChartView { const newChart = new Column(container, options) newChart.on('interval:click', action) - registerExtremumPointEvt(newChart, chart, options, container) + extremumEvt(newChart, chart, options, container) return newChart } @@ -121,7 +121,7 @@ export class Bar extends G2PlotChartView { label: false } } - const { label: labelAttr, basicStyle } = parseJson(chart.customAttr) + const { label: labelAttr } = parseJson(chart.customAttr) const formatterMap = labelAttr.seriesLabelFormatter?.reduce((pre, next) => { pre[next.id] = next return pre @@ -131,7 +131,7 @@ export class Bar extends G2PlotChartView { const label = { fields: [], ...tmpOptions.label, - formatter: (data: Datum, point) => { + formatter: (data: Datum, _point) => { if (!labelAttr.seriesLabelFormatter?.length) { return data.value } @@ -139,31 +139,24 @@ export class Bar extends G2PlotChartView { if (!labelCfg) { return data.value } - let showLabel = true - if (labelCfg.showExtremum) { - showLabel = setExtremumPosition(data, point, chart, labelCfg, basicStyle.lineSymbolSize) - } if (!labelCfg.show) { return } - if (showLabel) { - const value = valueFormatter(data.value, labelCfg.formatterCfg) - const group = new G2PlotChartView.engine.Group({}) - group.addShape({ - type: 'text', - attrs: { - x: 0, - y: 0, - text: value, - textAlign: 'start', - textBaseline: 'top', - fontSize: labelCfg.fontSize, - fill: labelCfg.color - } - }) - return group - } - return null + const value = valueFormatter(data.value, labelCfg.formatterCfg) + const group = new G2PlotChartView.engine.Group({}) + group.addShape({ + type: 'text', + attrs: { + x: 0, + y: 0, + text: value, + textAlign: 'start', + textBaseline: 'top', + fontSize: labelCfg.fontSize, + fill: labelCfg.color + } + }) + return group } } return { @@ -352,20 +345,13 @@ export class GroupBar extends StackBar { if (!baseOptions.label) { return baseOptions } - const { label: labelAttr, basicStyle } = parseJson(chart.customAttr) + const { label: labelAttr } = parseJson(chart.customAttr) baseOptions.label.style.fill = labelAttr.color const label = { ...baseOptions.label, - formatter: function (param: Datum, point) { - let showLabel = true - if (labelAttr.showExtremum) { - showLabel = setExtremumPosition(param, point, chart, labelAttr, basicStyle.lineSymbolSize) - } - if (!labelAttr.childrenShow) { - return null - } + formatter: function (param: Datum, _point) { const value = valueFormatter(param.value, labelAttr.labelFormatter) - return showLabel ? value : null + return labelAttr.childrenShow ? value : null } } return { diff --git a/core/core-frontend/src/views/chart/components/js/panel/charts/line/area.ts b/core/core-frontend/src/views/chart/components/js/panel/charts/line/area.ts index eff83e3b9d..7687880de2 100644 --- a/core/core-frontend/src/views/chart/components/js/panel/charts/line/area.ts +++ b/core/core-frontend/src/views/chart/components/js/panel/charts/line/area.ts @@ -9,8 +9,6 @@ import { flow, hexColorToRGBA, parseJson, - registerExtremumPointEvt, - setExtremumPosition, setUpStackSeriesColor } from '@/views/chart/components/js/util' import { valueFormatter } from '@/views/chart/components/js/formatter' @@ -23,6 +21,7 @@ import { Label } from '@antv/g2plot/lib/types/label' import { Datum } from '@antv/g2plot/esm/types/common' import { useI18n } from '@/hooks/web/useI18n' import { DEFAULT_LABEL } from '@/views/chart/components/editor/util/chart' +import { clearExtremum, extremumEvt } from '@/views/chart/components/js/extremumUitl' const { t } = useI18n() const DEFAULT_DATA = [] @@ -98,6 +97,7 @@ export class Area extends G2PlotChartView { drawChart(drawOptions: G2PlotDrawOptions): G2Area { const { chart, container, action } = drawOptions if (!chart.data.data?.length) { + clearExtremum(chart) return } // data @@ -114,7 +114,7 @@ export class Area extends G2PlotChartView { const newChart = new G2Area(container, options) newChart.on('point:click', action) - registerExtremumPointEvt(newChart, chart, options, container) + extremumEvt(newChart, chart, options, container) return newChart } @@ -126,7 +126,7 @@ export class Area extends G2PlotChartView { label: false } } - const { label: labelAttr, basicStyle } = parseJson(chart.customAttr) + const { label: labelAttr } = parseJson(chart.customAttr) const formatterMap = labelAttr.seriesLabelFormatter?.reduce((pre, next) => { pre[next.id] = next return pre @@ -135,7 +135,7 @@ export class Area extends G2PlotChartView { const label = { fields: [], ...tmpOptions.label, - formatter: (data: Datum, point) => { + formatter: (data: Datum, _point) => { if (!labelAttr.seriesLabelFormatter?.length) { return data.value } @@ -143,34 +143,24 @@ export class Area extends G2PlotChartView { if (!labelCfg) { return data.value } - let showLabel = true - if (labelCfg.showExtremum) { - showLabel = setExtremumPosition(data, point, chart, labelCfg, basicStyle.lineSymbolSize) - } if (!labelCfg.show) { return } - const has = chart.filteredData?.filter( - item => JSON.stringify(item) === JSON.stringify(data) - ) - if (has?.length > 0 && showLabel) { - const value = valueFormatter(data.value, labelCfg.formatterCfg) - const group = new G2PlotChartView.engine.Group({}) - group.addShape({ - type: 'text', - attrs: { - x: 0, - y: 0, - text: value, - textAlign: 'start', - textBaseline: 'top', - fontSize: labelCfg.fontSize, - fill: labelCfg.color - } - }) - return group - } - return null + const value = valueFormatter(data.value, labelCfg.formatterCfg) + const group = new G2PlotChartView.engine.Group({}) + group.addShape({ + type: 'text', + attrs: { + x: 0, + y: 0, + text: value, + textAlign: 'start', + textBaseline: 'top', + fontSize: labelCfg.fontSize, + fill: labelCfg.color + } + }) + return group } } return { diff --git a/core/core-frontend/src/views/chart/components/js/panel/charts/line/line.ts b/core/core-frontend/src/views/chart/components/js/panel/charts/line/line.ts index 72814f811f..8ecd160a16 100644 --- a/core/core-frontend/src/views/chart/components/js/panel/charts/line/line.ts +++ b/core/core-frontend/src/views/chart/components/js/panel/charts/line/line.ts @@ -8,8 +8,6 @@ import { flow, hexColorToRGBA, parseJson, - registerExtremumPointEvt, - setExtremumPosition, setUpGroupSeriesColor } from '@/views/chart/components/js/util' import { cloneDeep, isEmpty } from 'lodash-es' @@ -22,6 +20,7 @@ import { import { Datum } from '@antv/g2plot/esm/types/common' import { useI18n } from '@/hooks/web/useI18n' import { DEFAULT_LABEL } from '@/views/chart/components/editor/util/chart' +import { clearExtremum, extremumEvt } from '@/views/chart/components/js/extremumUitl' const { t } = useI18n() const DEFAULT_DATA = [] @@ -50,6 +49,7 @@ export class Line extends G2PlotChartView { drawChart(drawOptions: G2PlotDrawOptions): G2Line { const { chart, action, container } = drawOptions if (!chart.data.data?.length) { + clearExtremum(chart) return } const data = cloneDeep(chart.data.data) @@ -109,7 +109,7 @@ export class Line extends G2PlotChartView { const newChart = new G2Line(container, options) newChart.on('point:click', action) - registerExtremumPointEvt(newChart, chart, options, container) + extremumEvt(newChart, chart, options, container) return newChart } @@ -121,7 +121,7 @@ export class Line extends G2PlotChartView { label: false } } - const { label: labelAttr, basicStyle } = parseJson(chart.customAttr) + const { label: labelAttr } = parseJson(chart.customAttr) const formatterMap = labelAttr.seriesLabelFormatter?.reduce((pre, next) => { pre[next.id] = next return pre @@ -132,7 +132,7 @@ export class Line extends G2PlotChartView { ...tmpOptions.label, offsetY: -8, layout: [{ type: 'hide-overlap' }, { type: 'limit-in-plot' }], - formatter: (data: Datum, point) => { + formatter: (data: Datum, _point) => { if (!labelAttr.seriesLabelFormatter?.length) { return data.value } @@ -140,34 +140,24 @@ export class Line extends G2PlotChartView { if (!labelCfg) { return data.value } - let showLabel = true - if (labelCfg.showExtremum) { - showLabel = setExtremumPosition(data, point, chart, labelCfg, basicStyle.lineSymbolSize) - } if (!labelCfg.show) { return } - const has = chart.filteredData?.filter( - item => JSON.stringify(item) === JSON.stringify(data) - ) - if (has?.length > 0 && showLabel) { - const value = valueFormatter(data.value, labelCfg.formatterCfg) - const group = new G2PlotChartView.engine.Group({}) - group.addShape({ - type: 'text', - attrs: { - x: 0, - y: 0, - text: value, - textAlign: 'start', - textBaseline: 'top', - fontSize: labelCfg.fontSize, - fill: labelCfg.color - } - }) - return group - } - return null + const value = valueFormatter(data.value, labelCfg.formatterCfg) + const group = new G2PlotChartView.engine.Group({}) + group.addShape({ + type: 'text', + attrs: { + x: 0, + y: 0, + text: value, + textAlign: 'start', + textBaseline: 'top', + fontSize: labelCfg.fontSize, + fill: labelCfg.color + } + }) + return group } } return { diff --git a/core/core-frontend/src/views/chart/components/js/util.ts b/core/core-frontend/src/views/chart/components/js/util.ts index 3be44aa8b5..446be4268d 100644 --- a/core/core-frontend/src/views/chart/components/js/util.ts +++ b/core/core-frontend/src/views/chart/components/js/util.ts @@ -942,415 +942,6 @@ export function setUpSingleDimensionSeriesColor( return result } -/** - * 注册极值点事件处理函数 - * 该函数用于在新建的图表上注册极值点显示的事件处理逻辑,根据图表类型和配置数据处理极值点的显示 - * - * @param newChart 新建的图表对象,用于绑定事件 - * @param chart 原有的图表对象,用于存储处理后的数据和配置 - * @param options 图表的配置选项,包含数据和各种配置项 - * @param container 图表的容器,用于设置图表的容器 - */ -export const registerExtremumPointEvt = (newChart, chart, options, container) => { - chart.container = container - const { label: labelAttr } = parseJson(chart.customAttr) - let seriesFields = [] - // 针对不是序列字段的图表,通过获取分类字段的值作为序列字段,在标签配置时使用 - const seriesFieldObjs = [] - // 分组柱状图这种字段分类的图表,需要按照分类字段的值作为序列字段 - const xAxisExt = chart.xAxisExt || [] - if (['bar-group'].includes(chart.type) || xAxisExt.length > 0) { - seriesFields = [...new Set(options.data.map(item => item.category))] - if (xAxisExt.length === 0) { - seriesFields = ['@'] - } - seriesFields.forEach(field => { - seriesFieldObjs.push({ - dataeaseName: - xAxisExt.length === 0 - ? 'f_' + chart.xAxis[0].dataeaseName + '_' + field - : chart.xAxisExt[0]?.dataeaseName + '_' + field, - name: field, - showExtremum: true, - formatterCfg: labelAttr.labelFormatter - }) - }) - chart.seriesFieldObjs = seriesFieldObjs - } else { - seriesFields = chart.yAxis.map(item => item.name) - } - // 筛选数据区间,默认所有数据 - let filterDataRange = [0, options.data.length - 1] - const senior = parseJson(chart.senior) - // 高级配置了缩略轴,按照缩略轴默认配置进行区间配置 - if (senior.functionCfg) { - if (senior.functionCfg.sliderShow) { - const cfg = { - start: senior.functionCfg.sliderRange[0] / 100, - end: senior.functionCfg.sliderRange[1] / 100 - } - const dataLength = options.data.length / seriesFields.length - // 使用round方法,与antv 内置过滤数据方式一致,否则会出现数据区间错误 - const startIndex = Math.round(cfg.start * (dataLength - 1)) - const endIndex = Math.round(cfg.end * (dataLength - 1)) - filterDataRange = [startIndex, endIndex] - } - } - // 通过区间筛选的数据 - const filteredData = [] - // 如果是根据字段值分类的图表时,并且没有子类别时 - if (seriesFieldObjs.length > 0 && chart.xAxisExt[0]) { - // 按照字段值分类维度聚合数据 - const fieldGroupList = options.data.reduce((groups, item) => { - const field = item.field - if (!groups[field]) { - groups[field] = [] - } - groups[field].push(item) - return groups - }, {}) - // 需要重新计算数据区间,因为数据区间是根据字段值分类维度聚合的数据 - if (senior.functionCfg) { - if (senior.functionCfg.sliderShow) { - const cfg = { - start: senior.functionCfg.sliderRange[0] / 100, - end: senior.functionCfg.sliderRange[1] / 100 - } - const dataLength = Object.keys(fieldGroupList).length - const startIndex = Math.round(cfg.start * (dataLength - 1)) - const endIndex = Math.round(cfg.end * (dataLength - 1)) - filterDataRange = [startIndex, endIndex] - Object.keys(fieldGroupList) - .slice(filterDataRange[0], filterDataRange[1] + 1) - .forEach(field => { - filteredData.push(...fieldGroupList[field]) - }) - } - } - } else { - seriesFields.forEach(field => { - const seriesFieldData = options.data.filter( - item => (item.category === '' ? '@' : item.category) === field - ) - filteredData.push(...seriesFieldData.slice(filterDataRange[0], filterDataRange[1] + 1)) - }) - } - chart.filteredData = filteredData - if (options.legend) { - newChart.on('legend-item:click', ev => { - hideExtremumPoint(ev, chart) - }) - } - if (options.slider) { - newChart.once('slider:valuechanged', _ev => { - newChart.on('beforerender', ev => { - sliderHandleExtremumPoint(ev, chart, options) - }) - }) - } - configExtremum(chart) -} - -/** - * 创建极值point - * @param key - * @param value - * @param formatterCfg - * @param chartId - */ -const createExtremumPointDiv = (key, value, formatterCfg, chartId) => { - const id = key.split('@')[1] + '_' + value - const parentElement = document.getElementById(chartId) - if (parentElement) { - const element = document.getElementById(id) - if (!element) { - const div = document.createElement('div') - div.id = id - div.setAttribute( - 'style', - `width: auto; - height: auto; - border-radius: 2px; - position: relative; - padding: 4px 5px 4px 5px; - display:none; - transform: translateX(-50%); - white-space:nowrap;` - ) - div.textContent = valueFormatter(value, formatterCfg) - const span = document.createElement('span') - span.setAttribute( - 'style', - `display: block; - width: 0px; - height: 0px; - border: 4px solid transparent; - border-top-color: red; - position: absolute; - left: calc(50% - 4px); - margin-top:4px;` - ) - div.appendChild(span) - parentElement.appendChild(div) - } - } -} - -/** - * 根据序列字段以及数据获取极值 - * @param seriesLabelFormatter - * @param data - * @param chartId - */ -export const getExtremumValues = (seriesLabelFormatter, data, chartId) => { - const extremumValues = new Map() - seriesLabelFormatter.forEach((item: any) => { - if (!data.length || !item.showExtremum) return - const filteredData = data.filter(d => (d.category == '' ? '@' : d.category) === item.name) - const maxValue = Math.max(...filteredData.map(d => d.value)) - const minValue = Math.min(...filteredData.map(d => d.value)) - const maxObj = filteredData.find(d => d.value === maxValue) - const minObj = filteredData.find(d => d.value === minValue) - extremumValues.set(item.name + '@' + item.dataeaseName + '_' + chartId, { - cfg: item.formatterCfg, - value: [maxObj, minObj] - }) - }) - return extremumValues -} - -/** - * 配置极值点dom - * @param chart - */ -export const configExtremum = (chart: Chart) => { - let customAttr: DeepPartial - // 清除图表标注 - const pointElement = document.getElementById('point_' + chart.id) - if (pointElement) { - pointElement.remove() - pointElement.parentNode?.removeChild(pointElement) - } - if (chart.customAttr) { - customAttr = parseJson(chart.customAttr) - // label - if (customAttr.label?.show) { - const label = customAttr.label - let seriesLabelFormatter = [] - if (chart.seriesFieldObjs && chart.seriesFieldObjs.length > 0) { - seriesLabelFormatter = chart.seriesFieldObjs - } else { - seriesLabelFormatter = deepCopy(label.seriesLabelFormatter) - } - if (seriesLabelFormatter.length > 0) { - chart.extremumValues = getExtremumValues( - seriesLabelFormatter, - chart.filteredData && chart.filteredData.length > 0 - ? chart.filteredData - : chart.data.data, - chart.id - ) - // 创建标注父元素 - const divParent = document.createElement('div') - divParent.id = 'point_' + chart.id - divParent.style.position = 'fixed' - divParent.style.zIndex = '1' - const containerElement = document.getElementById(chart.container) - containerElement.insertBefore(divParent, containerElement.firstChild) - chart.extremumValues?.forEach((value, key) => { - value.value?.forEach(extremumValue => { - if (extremumValue) { - createExtremumPointDiv(key, extremumValue.value, value.cfg, 'point_' + chart.id) - } - }) - }) - } - } - } -} - -/** - * 设置极值位置,并返回是否显示原始标签值 - * @param data 数据点数据 - * @param point 数据点信息 - * @param chart - * @param labelCfg 标签样式 - * @param pointSize 数据点大小 - */ -export const setExtremumPosition = (data, point, chart, labelCfg, pointSize) => { - if (chart.extremumValues) { - v: for (const [key, value] of chart.extremumValues.entries()) { - for (let i = 0; i < value.value.length; i++) { - if ( - value.value[i] && - data.category === value.value[i].category && - data.value === value.value[i].value - ) { - const id = key.split('@')[1] + '_' + data.value - const element = document.getElementById(id) - if (element) { - element.style.position = 'absolute' - element.style.top = - (point.y[1] ? point.y[1] : point.y) - - (labelCfg.fontSize + (pointSize ? pointSize : 0) + 12) + - 'px' - element.style.left = point.x + 'px' - element.style.zIndex = '10' - element.style.fontSize = labelCfg.fontSize + 'px' - element.style.lineHeight = labelCfg.fontSize + 'px' - // 渐变颜色时需要获取最后一个rgba的值作为背景 - const { r, b, g, a } = getRgbaColorLastRgba(point.color) - element.style.backgroundColor = 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')' - element.style.color = isColorLight(point.color) ? '#000' : '#fff' - element.children[0]['style'].borderTopColor = - 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')' - element.style.display = 'table' - return false - } - } - } - } - } - return true -} - -/** - * 判断给定的RGBA字符串表示的颜色是亮色还是暗色 - * 通过计算RGB颜色值的加权平均值(灰度值),判断颜色的明暗 - * 如果给定的字符串不包含有效的RGBA值,则原样返回该字符串 - * - * @param rgbaString 一个RGBA颜色字符串,例如 "rgba(255, 255, 255, 1)" - * @param greyValue 灰度值默认128 - * @returns 如果计算出的灰度值大于等于128,则返回true,表示亮色;否则返回false,表示暗色。 - * 如果rgbaString不包含有效的RGBA值,则返回原字符串 - */ -const isColorLight = (rgbaString: string, greyValue = 128) => { - const lastRGBA = getRgbaColorLastRgba(rgbaString) - if (!isEmpty(lastRGBA)) { - // 计算灰度值的公式 - const grayLevel = lastRGBA.r * 0.299 + lastRGBA.g * 0.587 + lastRGBA.b * 0.114 - return grayLevel >= greyValue - } else { - return false - } -} - -/** - * 从给定的rgba颜色字符串中提取最后一个rgba值 - * @param rgbaString 包含一个或多个rgba颜色值的字符串 - * @returns 返回最后一个解析出的rgba对象,如果未找到rgba值,则返回null - */ -const getRgbaColorLastRgba = (rgbaString: string) => { - const rgbaPattern = /rgba\((\d+),\s*(\d+),\s*(\d+),\s*([\d.]+)\)/g - let match: string[] - let lastRGBA = null - while ((match = rgbaPattern.exec(rgbaString)) !== null) { - const r = parseInt(match[1]) - const g = parseInt(match[2]) - const b = parseInt(match[3]) - const a = parseFloat(match[4]) - lastRGBA = { r, g, b, a } - } - return lastRGBA -} - -/** - * 隐藏图表中的极端数据点 - * 根据图例的选中状态,动态隐藏或显示图表中对应数据点的详细信息div - * @param ev 图表的事件对象,包含图例的选中状态 - * @param chart 图表实例,用于获取图表的配置和数据 - */ -export const hideExtremumPoint = (ev, chart) => { - // 获取图例中被取消选中的项,这些项对应的数据点将被隐藏 - const hideLegendObj = ev.view - .getController('legend') - .components[0].component.cfg.items.filter(l => l.unchecked) - - // 遍历图表数据,对每个数据点进行处理 - chart.data.data.forEach(item => { - // 根据图表的系列字段配置,获取数据点对应的dataeaseName - let dataeaseName = '' - if (chart.seriesFieldObjs && chart.seriesFieldObjs.length > 0) { - dataeaseName = chart.seriesFieldObjs.find( - obj => obj.name === (item.category === '' ? item.field : item.category) - )?.dataeaseName - } else { - dataeaseName = chart.data.fields.find( - field => field.id === item.quotaList[0].id - )?.dataeaseName - } - // 根据数据点的信息生成唯一id,用于查找对应的详细信息div - const divElementId = `${dataeaseName}_${chart.id}_${item.value}` - const divElement = document.getElementById(divElementId) - // 如果找到了对应的数据点详细信息div,则根据图例的选中状态,动态隐藏或显示该div - if (divElement) { - const shouldHide = hideLegendObj?.some( - hide => hide.id === (item.category === '' ? item.field : item.category) - ) - divElement.style.display = shouldHide ? 'none' : 'table' - } - }) -} - -/** - * 根据滑动操作更新图表的显示数据 - * 此函数用于处理滑动组件的操作事件,根据滑动组件的位置,动态更新图表显示的数据范围 - * @param ev 滑动操作的事件对象,包含当前滑动位置的信息 - * @param chart 图表对象,用于更新图表的显示数据 - * @param options 滑动组件的配置选项,包含滑动组件的初始数据等信息 - */ -export const sliderHandleExtremumPoint = (ev, chart, options) => { - let seriesFields = [] - // 如果chart中存在seriesFieldObjs且不为空,则使用seriesFieldObjs中的name作为系列字段 - // 否则,使用yAxis中的name作为系列字段 - if (chart.seriesFieldObjs && chart.seriesFieldObjs.length > 0) { - seriesFields = chart.seriesFieldObjs.map(item => item.name) - } else { - seriesFields = chart.yAxis.map(item => item.name) - } - // 筛选当前视图中已过滤的数据的类别,并去重 - const filteredDataSeriesFields = [ - ...new Set(ev.view.filteredData.map(({ category }) => category)) - ] - // 如果筛选后的数据类别的数量与系列字段的数量相等,说明所有数据都被筛选出来了 - // 此时直接使用视图中的过滤数据 - if (filteredDataSeriesFields.length === seriesFields.length) { - chart.filteredData = ev.view.filteredData - } else { - // 否则,找出当前筛选位置的起始和结束数据对象 - // 获取筛选后的数据的起止索引 - const objList = ev.view.filteredData.filter( - item => item.category === filteredDataSeriesFields[0] - ) - const startObj = objList[0] - const endObj = objList[objList.length - 1] - let start = 0 - let end = 0 - // 遍历options中的数据,找到起始和结束索引 - options.data - .filter(item => filteredDataSeriesFields[0] === item.category) - .forEach((item, index) => { - if (JSON.stringify(startObj) === JSON.stringify(item)) { - start = index - } - if (JSON.stringify(endObj) === JSON.stringify(item)) { - end = index - } - }) - const filteredData = [] - // 重新计算被隐藏的序列字段数据 - seriesFields - .filter(field => !filteredDataSeriesFields.includes(field)) - ?.forEach(field => { - const seriesFieldData = options.data.filter(item => item.category === field) - filteredData.push(...seriesFieldData.slice(start, end + 1)) - }) - // 将筛选出的数据与当前视图中的过滤数据合并,更新图表的显示数据 - chart.filteredData = [...filteredData, ...ev.view.filteredData] - } - configExtremum(chart) -} - export function isAlphaColor(color: string): boolean { if (!color?.trim()) { return false