From e197f3e7c248aa638c9d54ce2afbf8d1ff884bb4 Mon Sep 17 00:00:00 2001 From: jianneng-fit2cloud Date: Thu, 4 Jun 2026 19:46:39 +0800 Subject: [PATCH] =?UTF-8?q?fix(=E5=9B=BE=E8=A1=A8):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E7=BB=84=E5=90=88=E5=9B=BE=E5=9B=BE=E4=BE=8B=E4=BD=8D=E7=BD=AE?= =?UTF-8?q?=E8=BF=87=E4=BA=8E=E8=B4=B4=E8=BF=91=E5=9B=BE=E8=A1=A8=E5=86=85?= =?UTF-8?q?=E5=AE=B9=E5=8C=BA=E5=9F=9F=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../js/panel/charts/g2/mix/common.ts | 160 ++++++++++++++++++ .../js/panel/charts/g2/mix/mix-dual-line.ts | 87 +--------- .../js/panel/charts/g2/mix/mix-group.ts | 87 +--------- .../js/panel/charts/g2/mix/mix-stack.ts | 87 +--------- 4 files changed, 178 insertions(+), 243 deletions(-) diff --git a/core/core-frontend/src/views/chart/components/js/panel/charts/g2/mix/common.ts b/core/core-frontend/src/views/chart/components/js/panel/charts/g2/mix/common.ts index 4efc1c8563..138da468f7 100644 --- a/core/core-frontend/src/views/chart/components/js/panel/charts/g2/mix/common.ts +++ b/core/core-frontend/src/views/chart/components/js/panel/charts/g2/mix/common.ts @@ -1,3 +1,8 @@ +import type { G2Spec } from '@antv/g2' +import { parseJson } from '@/views/chart/components/js/util' + +type MixLegendRelation = [string, string] + export const CHART_MIX_EDITOR_PROPERTY: EditorProperty[] = [ 'background-overall-component', 'border-style', @@ -74,3 +79,158 @@ export const CHART_MIX_AXIS_TYPE: AxisType[] = [ 'extLabel', 'extTooltip' ] + +export const configMixCustomLegend = ( + chart: Chart, + options: G2Spec, + leftRelations: MixLegendRelation[] = [], + rightRelations: MixLegendRelation[] = [] +): G2Spec => { + const { legend } = parseJson(chart.customStyle) || {} + if (!legend?.show || !options.children?.length) { + return options + } + const unionRelations = [...leftRelations, ...rightRelations].filter( + ([key, value]) => key !== undefined && key !== null && Boolean(value) + ) + if (!unionRelations.length) { + return options + } + const hPosition = ['left', 'center', 'right'].includes(legend.hPosition) + ? legend.hPosition + : 'center' + const rawVPosition = ['top', 'center', 'bottom'].includes(legend.vPosition) + ? legend.vPosition + : 'bottom' + const vPosition = hPosition === 'center' && rawVPosition === 'center' ? 'top' : rawVPosition + const getPositiveNumber = (value: unknown, defaultValue: number) => { + const numberValue = Number(value) + return Number.isFinite(numberValue) && numberValue > 0 ? numberValue : defaultValue + } + const legendFontSize = getPositiveNumber(legend.fontSize, 12) + const legendMarkerSize = getPositiveNumber(legend.size, 4) + const legendIcon = legend.icon || 'circle' + const legendColor = legend.color || '#333333' + const legendChartGap = 8 + const getLegendChartGap = (direction: 'col' | 'row', legendFirst = false) => + direction === 'col' && !legendFirst ? 4 : legendChartGap + const getLegendRatio = (direction: 'col' | 'row', legendFirst = false) => { + const chartContainer = chart.container as unknown + const containerDom = + typeof document === 'undefined' || !chartContainer + ? undefined + : typeof chartContainer === 'string' + ? document.getElementById(chartContainer) + : typeof (chartContainer as HTMLElement).getBoundingClientRect === 'function' + ? (chartContainer as HTMLElement) + : undefined + const containerRect = containerDom?.getBoundingClientRect() + const mainSize = direction === 'col' ? containerRect?.height : containerRect?.width + const getTextWidth = text => { + return Array.from(`${text ?? ''}`).reduce((width, char) => { + return width + (char.charCodeAt(0) > 255 ? legendFontSize : legendFontSize * 0.6) + }, 0) + } + const crossGap = getLegendChartGap(direction, legendFirst) + // spaceFlex 按比例切分子层,这里把图例字号/图标尺寸换算成近似像素层高,避免图例放大后覆盖绘图区 + const legendLineSize = Math.ceil(Math.max(legendFontSize * 1.3, legendMarkerSize) + crossGap) + const legendMainSize = + direction === 'col' + ? Math.max(24, legendLineSize) + : Math.max( + 80, + ...unionRelations.map(([name]) => getTextWidth(name) + legendMarkerSize + 40) + ) + if (!mainSize || mainSize <= 0) { + const fallbackLegendRatio = Math.max(2, Math.ceil(legendMainSize / 16)) + return legendFirst ? [fallbackLegendRatio, 20] : [20, fallbackLegendRatio] + } + // ratio 使用像素等价值,让图例层随内容增长,同时至少给绘图区保留 1px,避免极小容器下异常 + const safeLegendSize = Math.max(1, Math.min(legendMainSize, mainSize - 1)) + const chartMainSize = Math.max(mainSize - safeLegendSize, 1) + return legendFirst ? [safeLegendSize, chartMainSize] : [chartMainSize, safeLegendSize] + } + // 双轴组合图左右 mark 使用独立 color scale,G2 内置 legend 无法直接合并,因此手工生成 legends 子层 + const legendMark: any = { + position: 'top', + type: 'legends', + key: 'legend', + scale: { + color: { + type: 'ordinal', + domain: [], + range: [], + relations: unionRelations + } + }, + layout: { + justifyContent: 'center', + alignItems: 'center' + }, + crossPadding: 0, + itemMarker: legendIcon, + itemMarkerSize: legendMarkerSize, + itemLabelFontSize: legendFontSize, + itemLabelFill: legendColor, + itemLabelOpacity: 1, + itemLabelFillOpacity: 1 + } + unionRelations.forEach(([key, value]) => { + legendMark.scale.color.domain.push(key) + legendMark.scale.color.range.push(value) + }) + if (hPosition === 'center') { + options.direction = 'col' + legendMark.maxRows = 1 + if (vPosition === 'top') { + legendMark.position = 'top' + legendMark.crossPadding = getLegendChartGap('col', true) + options.ratio = getLegendRatio('col', true) + options.children.unshift(legendMark) + } + if (vPosition === 'bottom') { + legendMark.position = 'bottom' + legendMark.crossPadding = getLegendChartGap('col') + options.ratio = getLegendRatio('col') + options.children.push(legendMark) + } + return options + } + if (vPosition === 'center') { + options.direction = 'row' + legendMark.maxCols = 1 + if (hPosition === 'left') { + legendMark.position = 'left' + legendMark.crossPadding = getLegendChartGap('row', true) + options.ratio = getLegendRatio('row', true) + options.children.unshift(legendMark) + } + if (hPosition === 'right') { + legendMark.position = 'right' + legendMark.crossPadding = getLegendChartGap('row') + options.ratio = getLegendRatio('row') + options.children.push(legendMark) + } + return options + } + legendMark.maxRows = 1 + if (vPosition === 'top') { + legendMark.position = 'top' + legendMark.crossPadding = getLegendChartGap('col', true) + options.ratio = getLegendRatio('col', true) + options.children.unshift(legendMark) + } + if (vPosition === 'bottom') { + legendMark.position = 'bottom' + legendMark.crossPadding = getLegendChartGap('col') + options.ratio = getLegendRatio('col') + options.children.push(legendMark) + } + if (hPosition === 'left') { + legendMark.layout.justifyContent = 'flex-start' + } + if (hPosition === 'right') { + legendMark.layout.justifyContent = 'flex-end' + } + return options +} diff --git a/core/core-frontend/src/views/chart/components/js/panel/charts/g2/mix/mix-dual-line.ts b/core/core-frontend/src/views/chart/components/js/panel/charts/g2/mix/mix-dual-line.ts index 949ef757b3..600ccacc8f 100644 --- a/core/core-frontend/src/views/chart/components/js/panel/charts/g2/mix/mix-dual-line.ts +++ b/core/core-frontend/src/views/chart/components/js/panel/charts/g2/mix/mix-dual-line.ts @@ -20,7 +20,11 @@ import { TOOLTIP_ITEM_TPL, TOOLTIP_TITLE_TPL } from '../../../common/common_antv' -import { CHART_MIX_EDITOR_PROPERTY, CHART_MIX_EDITOR_PROPERTY_INNER } from './common' +import { + CHART_MIX_EDITOR_PROPERTY, + CHART_MIX_EDITOR_PROPERTY_INNER, + configMixCustomLegend +} from './common' import { registerSymbol, Symbols } from '@antv/g2/esm/utils/marker' import G2TooltipCarousel from '@/views/chart/components/js/G2TooltipCarousel' import { @@ -456,89 +460,10 @@ export class GroupLineMix extends G2ChartView { } protected configLegend(chart: Chart, options: G2Spec): G2Spec { - const { legend } = parseJson(chart.customStyle) - if (!legend.show) { - return options - } const [leftLineMark, _, lineMark] = options.children[0].children const leftRelations = leftLineMark.scale.color.relations const rightRelations = lineMark.scale.color.relations - const unionRelations = [...leftRelations, ...rightRelations] - const legendMark = { - position: 'top', - type: 'legends', - key: 'legend', - scale: { - color: { - type: 'ordinal', - domain: [], - range: [], - relations: unionRelations - } - }, - layout: { - justifyContent: 'center', - alignItems: 'center' - }, - itemMarker: legend.icon, - itemMarkerSize: legend.size, - itemLabelFontSize: legend.fontSize, - itemLabelFill: legend.color, - itemLabelOpacity: 1, - itemLabelFillOpacity: 1 - } - unionRelations.forEach(([key, value]) => { - legendMark.scale.color.domain.push(key) - legendMark.scale.color.range.push(value) - }) - if (legend.hPosition === 'center') { - options.direction = 'col' - legendMark.maxRows = 1 - if (legend.vPosition === 'top') { - legendMark.position = 'top' - options.ratio = [1, 20] - options.children.unshift(legendMark) - } - if (legend.vPosition === 'bottom') { - legendMark.position = 'bottom' - options.ratio = [20, 1] - options.children.push(legendMark) - } - return options - } - if (legend.vPosition === 'center') { - options.direction = 'row' - legendMark.maxCols = 1 - if (legend.hPosition === 'left') { - legendMark.position = 'left' - options.ratio = [1, 20] - options.children.unshift(legendMark) - } - if (legend.hPosition === 'right') { - legendMark.position = 'right' - options.ratio = [20, 1] - options.children.push(legendMark) - } - return options - } - legendMark.maxRows = 1 - if (legend.vPosition === 'top') { - legendMark.position = 'top' - options.ratio = [1, 20] - options.children.unshift(legendMark) - } - if (legend.vPosition === 'bottom') { - legendMark.position = 'bottom' - options.ratio = [20, 1] - options.children.push(legendMark) - } - if (legend.hPosition === 'left') { - legendMark.layout.justifyContent = 'flex-start' - } - if (legend.hPosition === 'right') { - legendMark.layout.justifyContent = 'flex-end' - } - return options + return configMixCustomLegend(chart, options, leftRelations, rightRelations) } protected configLabel(chart: Chart, options: G2Spec): G2Spec { diff --git a/core/core-frontend/src/views/chart/components/js/panel/charts/g2/mix/mix-group.ts b/core/core-frontend/src/views/chart/components/js/panel/charts/g2/mix/mix-group.ts index db8639b4fd..63910ef669 100644 --- a/core/core-frontend/src/views/chart/components/js/panel/charts/g2/mix/mix-group.ts +++ b/core/core-frontend/src/views/chart/components/js/panel/charts/g2/mix/mix-group.ts @@ -23,7 +23,11 @@ import { TOOLTIP_ITEM_TPL, TOOLTIP_TITLE_TPL } from '../../../common/common_antv' -import { CHART_MIX_EDITOR_PROPERTY, CHART_MIX_EDITOR_PROPERTY_INNER } from './common' +import { + CHART_MIX_EDITOR_PROPERTY, + CHART_MIX_EDITOR_PROPERTY_INNER, + configMixCustomLegend +} from './common' import { registerSymbol, Symbols } from '@antv/g2/esm/utils/marker' import G2TooltipCarousel from '@/views/chart/components/js/G2TooltipCarousel' import { @@ -402,89 +406,10 @@ export class GroupLineMix extends G2ChartView { } protected configLegend(chart: Chart, options: G2Spec): G2Spec { - const { legend } = parseJson(chart.customStyle) - if (!legend.show) { - return options - } const [intervalMark, lineMark] = options.children[0].children const leftRelations = intervalMark.scale.color.relations const rightRelations = lineMark.scale.color.relations - const unionRelations = [...leftRelations, ...rightRelations] - const legendMark = { - position: 'top', - type: 'legends', - key: 'legend', - scale: { - color: { - type: 'ordinal', - domain: [], - range: [], - relations: unionRelations - } - }, - layout: { - justifyContent: 'center', - alignItems: 'center' - }, - itemMarker: legend.icon, - itemMarkerSize: legend.size, - itemLabelFontSize: legend.fontSize, - itemLabelFill: legend.color, - itemLabelOpacity: 1, - itemLabelFillOpacity: 1 - } - unionRelations.forEach(([key, value]) => { - legendMark.scale.color.domain.push(key) - legendMark.scale.color.range.push(value) - }) - if (legend.hPosition === 'center') { - options.direction = 'col' - legendMark.maxRows = 1 - if (legend.vPosition === 'top') { - legendMark.position = 'top' - options.ratio = [1, 20] - options.children.unshift(legendMark) - } - if (legend.vPosition === 'bottom') { - legendMark.position = 'bottom' - options.ratio = [20, 1] - options.children.push(legendMark) - } - return options - } - if (legend.vPosition === 'center') { - options.direction = 'row' - legendMark.maxCols = 1 - if (legend.hPosition === 'left') { - legendMark.position = 'left' - options.ratio = [1, 20] - options.children.unshift(legendMark) - } - if (legend.hPosition === 'right') { - legendMark.position = 'right' - options.ratio = [20, 1] - options.children.push(legendMark) - } - return options - } - legendMark.maxRows = 1 - if (legend.vPosition === 'top') { - legendMark.position = 'top' - options.ratio = [1, 20] - options.children.unshift(legendMark) - } - if (legend.vPosition === 'bottom') { - legendMark.position = 'bottom' - options.ratio = [20, 1] - options.children.push(legendMark) - } - if (legend.hPosition === 'left') { - legendMark.layout.justifyContent = 'flex-start' - } - if (legend.hPosition === 'right') { - legendMark.layout.justifyContent = 'flex-end' - } - return options + return configMixCustomLegend(chart, options, leftRelations, rightRelations) } protected configLabel(chart: Chart, options: G2Spec): G2Spec { diff --git a/core/core-frontend/src/views/chart/components/js/panel/charts/g2/mix/mix-stack.ts b/core/core-frontend/src/views/chart/components/js/panel/charts/g2/mix/mix-stack.ts index 44dde6beb7..651f6be2a3 100644 --- a/core/core-frontend/src/views/chart/components/js/panel/charts/g2/mix/mix-stack.ts +++ b/core/core-frontend/src/views/chart/components/js/panel/charts/g2/mix/mix-stack.ts @@ -22,7 +22,11 @@ import { TOOLTIP_ITEM_TPL, TOOLTIP_TITLE_TPL } from '../../../common/common_antv' -import { CHART_MIX_EDITOR_PROPERTY, CHART_MIX_EDITOR_PROPERTY_INNER } from './common' +import { + CHART_MIX_EDITOR_PROPERTY, + CHART_MIX_EDITOR_PROPERTY_INNER, + configMixCustomLegend +} from './common' import { registerSymbol, Symbols } from '@antv/g2/esm/utils/marker' import G2TooltipCarousel from '@/views/chart/components/js/G2TooltipCarousel' import { @@ -396,89 +400,10 @@ export class StackLineMix extends G2ChartView { } protected configLegend(chart: Chart, options: G2Spec): G2Spec { - const { legend } = parseJson(chart.customStyle) - if (!legend.show) { - return options - } const [intervalMark, lineMark] = options.children[0].children const leftRelations = intervalMark.scale.color.relations const rightRelations = lineMark.scale.color.relations - const unionRelations = [...leftRelations, ...rightRelations] - const legendMark = { - position: 'top', - type: 'legends', - key: 'legend', - scale: { - color: { - type: 'ordinal', - domain: [], - range: [], - relations: unionRelations - } - }, - layout: { - justifyContent: 'center', - alignItems: 'center' - }, - itemMarker: legend.icon, - itemMarkerSize: legend.size, - itemLabelFontSize: legend.fontSize, - itemLabelFill: legend.color, - itemLabelOpacity: 1, - itemLabelFillOpacity: 1 - } - unionRelations.forEach(([key, value]) => { - legendMark.scale.color.domain.push(key) - legendMark.scale.color.range.push(value) - }) - if (legend.hPosition === 'center') { - options.direction = 'col' - legendMark.maxRows = 1 - if (legend.vPosition === 'top') { - legendMark.position = 'top' - options.ratio = [1, 20] - options.children.unshift(legendMark) - } - if (legend.vPosition === 'bottom') { - legendMark.position = 'bottom' - options.ratio = [20, 1] - options.children.push(legendMark) - } - return options - } - if (legend.vPosition === 'center') { - options.direction = 'row' - legendMark.maxCols = 1 - if (legend.hPosition === 'left') { - legendMark.position = 'left' - options.ratio = [1, 20] - options.children.unshift(legendMark) - } - if (legend.hPosition === 'right') { - legendMark.position = 'right' - options.ratio = [20, 1] - options.children.push(legendMark) - } - return options - } - legendMark.maxRows = 1 - if (legend.vPosition === 'top') { - legendMark.position = 'top' - options.ratio = [1, 20] - options.children.unshift(legendMark) - } - if (legend.vPosition === 'bottom') { - legendMark.position = 'bottom' - options.ratio = [20, 1] - options.children.push(legendMark) - } - if (legend.hPosition === 'left') { - legendMark.layout.justifyContent = 'flex-start' - } - if (legend.hPosition === 'right') { - legendMark.layout.justifyContent = 'flex-end' - } - return options + return configMixCustomLegend(chart, options, leftRelations, rightRelations) } protected configLabel(chart: Chart, options: G2Spec): G2Spec {