From 2fdccceee74f49ba274f1a12bda5f9017f680418 Mon Sep 17 00:00:00 2001 From: wisonic-s <51065359+wisonic-s@users.noreply.github.com> Date: Tue, 29 Apr 2025 18:11:34 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=9B=BE=E8=A1=A8):=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E4=BB=AA=E8=A1=A8=E7=9B=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../js/panel/charts/g2/numberic/gauge.ts | 388 ++++++++++++++++++ 1 file changed, 388 insertions(+) create mode 100644 core/core-frontend/src/views/chart/components/js/panel/charts/g2/numberic/gauge.ts diff --git a/core/core-frontend/src/views/chart/components/js/panel/charts/g2/numberic/gauge.ts b/core/core-frontend/src/views/chart/components/js/panel/charts/g2/numberic/gauge.ts new file mode 100644 index 0000000000..45e7883170 --- /dev/null +++ b/core/core-frontend/src/views/chart/components/js/panel/charts/g2/numberic/gauge.ts @@ -0,0 +1,388 @@ +import { flow, hexColorToRGBA, parseJson } from '@/views/chart/components/js/util' +import { + DEFAULT_LABEL, + DEFAULT_MISC, + DEFAULT_THRESHOLD, + getScaleValue +} from '@/views/chart/components/editor/util/chart' +import { valueFormatter } from '@/views/chart/components/js/formatter' +import { setGradientColor } from '@/views/chart/components/js/panel/common/common_antv' +import { useI18n } from '@/hooks/web/useI18n' +import { defaultsDeep } from 'lodash-es' +import { G2Spec, Chart as G2Chart } from '@antv/g2' +import { G2ChartView, G2DrawOptions } from '../../../types/impl/g2' +import { RuntimeOptions } from '@antv/g2/lib/api/runtime' + +const { t } = useI18n() + +const DEFAULT_DATA = [] +export class Gauge extends G2ChartView { + properties: EditorProperty[] = [ + 'background-overall-component', + 'border-style', + 'basic-style-selector', + 'label-selector', + 'misc-selector', + 'title-selector', + 'threshold' + ] + propertyInner: EditorPropertyInner = { + 'background-overall-component': ['all'], + 'border-style': ['all'], + 'basic-style-selector': ['colors', 'alpha', 'gradient', 'gaugeAxisLine', 'gaugePercentLabel'], + 'label-selector': ['fontSize', 'color', 'labelFormatter'], + 'title-selector': [ + 'title', + 'fontSize', + 'color', + 'hPosition', + 'isItalic', + 'isBolder', + 'remarkShow', + 'fontFamily', + 'letterSpace', + 'fontShadow' + ], + 'misc-selector': [ + 'gaugeMinType', + 'gaugeMinField', + 'gaugeMin', + 'gaugeMaxType', + 'gaugeMaxField', + 'gaugeMax', + 'gaugeStartAngle', + 'gaugeEndAngle' + ], + threshold: ['gaugeThreshold'] + } + axis: AxisType[] = ['yAxis', 'filter'] + axisConfig: AxisConfig = { + yAxis: { + name: `${t('chart.drag_block_gauge_angel')} / ${t('chart.quota')}`, + type: 'q', + limit: 1 + } + } + + async drawChart(drawOptions: G2DrawOptions): Promise { + const { chart, container, scale, action } = drawOptions + if (!chart.data?.series || !chart.yAxis.length) { + return + } + // options + const initOptions: G2Spec = { + type: 'gauge', + autoFit: true, + axis: { + y: { + labelDirection: 'positive', + tickDirection: 'positive' + } + }, + legend: false, + coordinate: { + type: 'radial', + innerRadius: 1, + outerRadius: 1.15 + } + } + const options = this.setupOptions(chart, initOptions, { scale }) + const newChart = new G2Chart({ container }) + newChart.options(options) + newChart.on('afterrender', () => { + action({ + from: 'gauge', + data: { + type: 'gauge', + max: chart.data?.series[0]?.data[0] + } + }) + }) + const hasNoneData = chart.data?.series.some(s => !s.data?.[0]) + this.configEmptyDataStyle(hasNoneData ? [] : [1], container, newChart) + if (hasNoneData) { + return + } + return newChart + } + + protected configMisc(chart: Chart, options: G2Spec, context: Record): G2Spec { + const customAttr = parseJson(chart.customAttr) + const data = chart.data.series[0].data[0] + let min, max, startAngle, endAngle + if (customAttr.misc) { + const misc = customAttr.misc + if (misc.gaugeMinType === 'dynamic' && misc.gaugeMaxType === 'dynamic') { + min = chart.data?.series[chart.data?.series.length - 2]?.data[0] + max = chart.data?.series[chart.data?.series.length - 1]?.data[0] + } else if (misc.gaugeMinType !== 'dynamic' && misc.gaugeMaxType === 'dynamic') { + min = misc.gaugeMin || misc.gaugeMin === 0 ? misc.gaugeMin : DEFAULT_MISC.gaugeMin + max = chart.data?.series[chart.data?.series.length - 1]?.data[0] + } else if (misc.gaugeMinType === 'dynamic' && misc.gaugeMaxType !== 'dynamic') { + min = chart.data?.series[chart.data?.series.length - 1]?.data[0] + max = misc.gaugeMax ? misc.gaugeMax : DEFAULT_MISC.gaugeMax + } else { + min = misc.gaugeMin || misc.gaugeMin === 0 ? misc.gaugeMin : DEFAULT_MISC.gaugeMin + max = misc.gaugeMax + ? misc.gaugeMax + : chart.data?.series[chart.data?.series.length - 1]?.data[0] + } + startAngle = (misc.gaugeStartAngle * Math.PI) / 180 + endAngle = (misc.gaugeEndAngle * Math.PI) / 180 + context.min = min + context.max = max + } + const percent = (parseFloat(data) - parseFloat(min)) / (parseFloat(max) - parseFloat(min)) + const tmp: G2Spec = { + data: { + value: { + percent + } + }, + coordinate: { + startAngle, + endAngle + } + } + defaultsDeep(options, tmp) + return options + } + + protected configTheme(chart: Chart, options: G2Spec): G2Spec { + const customAttr = parseJson(chart.customAttr) + const colors: string[] = [] + if (customAttr.basicStyle) { + const basicStyle = customAttr.basicStyle + basicStyle.colors.forEach(ele => { + colors.push(hexColorToRGBA(ele, basicStyle.alpha)) + }) + } + const customStyle = parseJson(chart.customStyle) + let bgColor + if (customStyle.background) { + bgColor = hexColorToRGBA(customStyle.background.color, customStyle.background.alpha) + } + const theme = { + color: colors[0], + category10: colors, + category20: colors, + view: { + viewFill: bgColor + } + } + return { ...options, theme } + } + + private configRange(chart: Chart, options: G2Spec, context: Record): G2Spec { + const { scale } = context + const thresholds = [] + let index = 0 + let flag = false + let hasThreshold = false + const theme = options.theme as any + const customAttr = parseJson(chart.customAttr) + if (customAttr.basicStyle.gradient) { + const colorList = (theme.category10 || []).map(ele => { + return setGradientColor(ele, true) + }) + theme.category10 = colorList + } + if (chart.senior) { + const senior = parseJson(chart.senior) + const threshold = senior.threshold ?? DEFAULT_THRESHOLD + if (threshold.enable && threshold.gaugeThreshold) { + hasThreshold = true + const arr = threshold.gaugeThreshold.split(',') + for (let i = 0; i < arr.length; i++) { + const ele = arr[i] + const p = parseFloat(ele) / 100 + thresholds.push(p) + if (!flag && options.data.value.percent <= p) { + flag = true + index = i + } + } + if (!flag) { + index = arr.length + } + } + } + thresholds.push(1) + let rangOptions: G2Spec + if (hasThreshold) { + rangOptions = { + data: { + value: { + thresholds + } + }, + scale: { + color: { + range: theme.category10.slice(0, thresholds.length) + } + }, + style: { + pointerStroke: theme.category10[index % theme.category10.length], + pinStroke: theme.category10[index % theme.category10.length], + pinR: getScaleValue(10, scale) + } + } + } else { + rangOptions = { + scale: { + color: { + range: theme.category10.slice(0, 1) + } + }, + style: { + pointerStroke: theme.category10[0], + pinStroke: theme.category10[0], + pinR: getScaleValue(10, scale) + } + } + } + return defaultsDeep(options, rangOptions) + } + + protected configLabel(chart: Chart, options: G2Spec, context?: Record): G2Spec { + const customAttr = parseJson(chart.customAttr) + const data = chart.data.series[0].data[0] + const label = customAttr.label + const labelFormatter = label.labelFormatter ?? DEFAULT_LABEL.labelFormatter + if (label.show) { + let proportionOffsetY = 0 + if (label.childrenShow) { + const labelTitleOption = { + style: { + textFontSize: label.fontSize, + textFill: label.color, + textContent: () => { + let value + if (labelFormatter.type === 'percent') { + value = options.data.value.percent + } else { + value = data + } + return valueFormatter(value, labelFormatter) + } + } + } + proportionOffsetY = label.fontSize + defaultsDeep(options, labelTitleOption) + } + if (label.proportionSeriesFormatter.show) { + const { min, max } = context + const proportionFormatter = label.proportionSeriesFormatter + const labelProportionOption = { + type: 'text', + style: { + text: () => { + const proportionValue = ((parseFloat(data) - min) / (max - min)) * 100 + return ( + t('chart.proportion') + + ': ' + + proportionValue.toFixed(proportionFormatter.formatterCfg.decimalCount) + + '%' + ) + }, + x: '50%', + y: '60%', + dy: proportionOffsetY, + fontSize: proportionFormatter.fontSize, + fill: proportionFormatter.color, + textAlign: 'center' + }, + tooltip: false + } + options = { + type: 'view', + autoFit: true, + children: [options, labelProportionOption] + } + } + } else { + defaultsDeep(options, { + style: { + textContent: '' + } + }) + } + return options + } + + protected configAxis( + chart: Chart, + options: RuntimeOptions, + context: Record + ): G2Spec { + const customAttr = parseJson(chart.customAttr) + const { gaugeAxisLine, gaugePercentLabel } = customAttr.basicStyle + const { min, max } = context + const labelFormatter = customAttr.label.labelFormatter ?? DEFAULT_LABEL.labelFormatter + const axisOption = { + axis: { + y: { + tickLength: (_, id) => { + if (id % 5 === 0) { + return 15 + } + return 10 + }, + tickCount: 25, + tickMethod: (min, max, count) => { + const ticks = [] + for (let i = 0; i <= count; i++) { + ticks.push((min + ((max - min) * i) / count).toFixed(2)) + } + return ticks + }, + labelDirection: 'positive', + tickDirection: 'positive', + labelFormatter: (v, id) => { + if (gaugeAxisLine === false) { + return ' ' + } + if (id % 5 !== 0) { + return '' + } + if (gaugePercentLabel === false) { + const resultV = v === '0' ? min : v === '1' ? max : min + (max - min) * v + return labelFormatter.type === 'value' + ? valueFormatter(resultV, labelFormatter) + : resultV + } + return v === '0' ? v : v * 100 + '%' + } + } + } + } + return defaultsDeep(options, axisOption) + } + + setupDefaultOptions(chart: ChartObj): ChartObj { + chart.customAttr.label = { + ...chart.customAttr.label, + show: true, + labelFormatter: { + type: 'value', + thousandSeparator: true, + decimalCount: 0, + unit: 1 + } + } + return chart + } + + protected setupOptions(chart: Chart, options: G2Spec, context: Record): G2Spec { + return flow( + this.configTheme, + this.configMisc, + this.configRange, + this.configAxis, + this.configLabel + )(chart, options, context) + } + constructor() { + super('gauge', DEFAULT_DATA) + } +}