Merge remote-tracking branch 'origin/dev-v3' into dev-v3

This commit is contained in:
wangjiahao
2026-06-03 13:42:35 +08:00
11 changed files with 233 additions and 77 deletions

View File

@@ -32,9 +32,15 @@ import {
} from '@/views/chart/components/editor/util/chart'
import {
createTooltipWrapper,
getSeriesTooltipFormatter,
getSeriesTooltipFormatterMap,
getStackTooltipGroupName,
getTooltipItemFormatter,
handleEmptyDataStrategy,
isSeriesTooltipFormatterShown,
isTooltipItemShown,
renderGroupedTooltipItems,
ChildSpec,
tooltipCss,
tooltipMaxHeight,
Transform,
@@ -45,6 +51,8 @@ import G2TooltipCarousel from '@/views/chart/components/js/G2TooltipCarousel'
const { t } = useI18n()
const DEFAULT_DATA: any[] = []
const FULL_COLUMN_WIDTH_PADDING = 0.01
const PERCENTAGE_FULL_COLUMN_WIDTH_PADDING = 0.002
/**
* 柱状图
@@ -248,12 +256,7 @@ export class Bar extends G2ChartView<ViewSpec, G2Column> {
if (!tooltipAttr.show) {
return options
}
const formatterMap = tooltipAttr.seriesTooltipFormatter
?.filter(i => i.show)
.reduce((pre, next) => {
pre[next.id] = next
return pre
}, {}) as Record<string, SeriesFormatter>
const formatterMap = getSeriesTooltipFormatterMap(tooltipAttr)
const tooltipOptions: ViewSpec = {
tooltip: d => d,
interaction: {
@@ -268,24 +271,34 @@ export class Bar extends G2ChartView<ViewSpec, G2Column> {
const titleHtml = TOOLTIP_TITLE_TPL.replace('{title}', title)
let tooltipItems = originalItems
if (tooltipAttr.seriesTooltipFormatter?.length) {
tooltipItems = originalItems.filter(item => formatterMap[item.quotaList[0].id])
// 只隐藏明确配置为不展示的字段,避免过期 formatter 漏掉新指标
tooltipItems = originalItems.filter(item =>
isTooltipItemShown(formatterMap, item, 'yAxis')
)
}
const result = []
const head = originalItems[0]
tooltipItems.forEach(item => {
const formatter = formatterMap[item.quotaList[0].id] ?? yAxis[0]
const formatter = getTooltipItemFormatter(formatterMap, item, yAxis, 'yAxis')
const value =
item.value === null || item.value === undefined
? ''
: valueFormatter(item.value, formatter.formatterCfg)
const name = isEmpty(formatter.chartShowName)
? formatter.name
: valueFormatter(
item.value,
formatter?.formatterCfg ?? tooltipAttr.tooltipFormatter
)
const name = isEmpty(formatter?.chartShowName)
? formatter?.name ?? item.name
: formatter.chartShowName
result.push({ ...item, name, value })
})
head.dynamicTooltipValue?.forEach(item => {
const formatter = formatterMap[item.fieldId]
if (formatter) {
const formatter = getSeriesTooltipFormatter(
formatterMap,
item.fieldId,
chart.extTooltip
)
if (formatter && isSeriesTooltipFormatterShown(formatterMap, item.fieldId)) {
const value =
item.value === null || item.value === undefined
? ''
@@ -337,7 +350,7 @@ export class Bar extends G2ChartView<ViewSpec, G2Column> {
colors.push(color ? color : hexColorToRGBA(ele, basicStyle.alpha))
})
}
const scale = {
const scale: Record<string, any> = {
color: {
range: colors
},
@@ -377,19 +390,17 @@ export class Bar extends G2ChartView<ViewSpec, G2Column> {
radius: 0
}
}
let columnWidthRatio
const _v = basicStyle.columnWidthRatio ?? DEFAULT_BASIC_STYLE.columnWidthRatio
if (_v >= 1 && _v <= 100) {
columnWidthRatio = _v / 100.0
} else if (_v < 1) {
columnWidthRatio = 1 / 100.0
} else if (_v > 100) {
columnWidthRatio = 1
}
const columnWidthRatio = this.getColumnWidthRatio(basicStyle)
const columnPadding = this.getColumnPadding(columnWidthRatio)
let transform = children[0].transform
if (columnWidthRatio) {
// 100% 时保留极小 band 间距,避免 transpose 条形图贴边
scale.x.padding = columnPadding
scale.x.paddingInner = columnPadding
transform = this.configDodgePadding(transform, columnPadding)
style = {
...style,
columnWidthRatio
columnWidthRatio: this.getStyleColumnWidthRatio(columnPadding)
}
}
return {
@@ -398,6 +409,7 @@ export class Bar extends G2ChartView<ViewSpec, G2Column> {
{
...children[0],
scale,
transform,
style
},
...children.slice(1)
@@ -405,6 +417,49 @@ export class Bar extends G2ChartView<ViewSpec, G2Column> {
}
}
protected getColumnWidthRatio(basicStyle: DeepPartial<ChartBasicStyle>): number {
// 兼容历史异常配置,保持样式面板 1-100% 的有效范围
const value = basicStyle.columnWidthRatio ?? DEFAULT_BASIC_STYLE.columnWidthRatio
if (value >= 1 && value <= 100) {
return value / 100.0
}
if (value < 1) {
return 1 / 100.0
}
return 1
}
protected getColumnPadding(columnWidthRatio: number): number {
return Math.max(1 - columnWidthRatio, this.getFullColumnWidthPadding())
}
protected getFullColumnWidthPadding(): number {
if (this.name.startsWith('percentage-bar-stack')) {
return PERCENTAGE_FULL_COLUMN_WIDTH_PADDING
}
return FULL_COLUMN_WIDTH_PADDING
}
protected getStyleColumnWidthRatio(columnPadding: number): number {
return 1 - columnPadding
}
protected configDodgePadding(
transforms: ChildSpec['transform'],
padding: number
): ChildSpec['transform'] {
if (!transforms?.length) {
return transforms
}
if (padding > this.getFullColumnWidthPadding()) {
return transforms
}
// dodgeX 会生成 series band单独控制多指标柱之间的组内间距
return transforms.map(transform =>
transform.type === 'dodgeX' ? { ...transform, padding } : transform
)
}
protected configLegend(chart: Chart, options: ViewSpec): ViewSpec {
const { children } = options
return {

View File

@@ -283,6 +283,82 @@ export function tooltipMaxHeight(chart: Chart) {
return `max-height: ${maxHeight}px;max-width: ${chartRect.width / 2}px;`
}
export function getSeriesTooltipFormatterMap(tooltipAttr?: DeepPartial<ChartTooltipAttr>) {
return (tooltipAttr?.seriesTooltipFormatter || []).reduce((pre, next) => {
if (!next?.id) {
return pre
}
const formatter = next as SeriesFormatter
// 同一字段可出现在不同指标槽位,优先用 seriesId 区分具体槽位。
if (formatter.seriesId) {
pre[formatter.seriesId] = formatter
}
if (!formatter.seriesId || formatter.seriesId === formatter.id) {
pre[formatter.id] = formatter
}
return pre
}, {} as Record<string, SeriesFormatter>)
}
export function getTooltipItemFieldId(item?: any) {
return item?.quotaList?.[0]?.id ?? item?.data?.quotaList?.[0]?.id ?? item?.fieldId
}
// 旧图表样式可能带着过期 formatter当前字段缺少配置时按默认展示处理。
export function isSeriesTooltipFormatterShown(
formatterMap: Record<string, SeriesFormatter>,
fieldId?: string,
axisType?: AxisType
) {
if (!fieldId) {
return true
}
if (axisType) {
// 带槽位的主指标不能被旧的 id 级 show:false 误隐藏。
const seriesKey = `${fieldId}-${axisType}`
return Object.prototype.hasOwnProperty.call(formatterMap, seriesKey)
? formatterMap[seriesKey]?.show !== false
: true
}
if (Object.prototype.hasOwnProperty.call(formatterMap, fieldId)) {
return formatterMap[fieldId]?.show !== false
}
return true
}
export function isTooltipItemShown(
formatterMap: Record<string, SeriesFormatter>,
item?: any,
axisType?: AxisType
) {
return isSeriesTooltipFormatterShown(formatterMap, getTooltipItemFieldId(item), axisType)
}
// 带槽位的 formatter 缺项时回退到当前轴字段,避免旧 id 级配置污染新指标。
export function getSeriesTooltipFormatter(
formatterMap: Record<string, SeriesFormatter>,
fieldId?: string,
fields: Partial<Axis>[] = [],
axisType?: AxisType
) {
const field = fields.find(field => field?.id === fieldId)
const seriesId = (field as Partial<SeriesFormatter>)?.seriesId
if (axisType && fieldId) {
const seriesKey = `${fieldId}-${axisType}`
return formatterMap[seriesKey] || (seriesId && formatterMap[seriesId]) || field
}
return (fieldId && formatterMap[fieldId]) || (seriesId && formatterMap[seriesId]) || field
}
export function getTooltipItemFormatter(
formatterMap: Record<string, SeriesFormatter>,
item: any,
fields: Partial<Axis>[] = [],
axisType?: AxisType
) {
return getSeriesTooltipFormatter(formatterMap, getTooltipItemFieldId(item), fields, axisType)
}
// 将字段显示名降级到原始字段名,避免 tooltip 分组标题为空
export function getFieldDisplayName(field?: Partial<ChartViewField>) {
return field?.chartShowName || field?.name || ''

View File

@@ -22,7 +22,6 @@ import {
setGradientColor
} from '@/views/chart/components/js/panel/common/common_antv'
import { valueFormatter } from '@/views/chart/components/js/formatter'
import { DEFAULT_BASIC_STYLE } from '@/views/chart/components/editor/util/chart'
import { defaultsDeep } from 'lodash-es'
const { t } = useI18n()
@@ -85,19 +84,12 @@ export class HorizontalBar extends Bar {
...(basicStyle.radiusColumnBar !== 'topRoundAngle' &&
basicStyle.radiusColumnBar !== 'roundAngle' && { radius: 0 })
} as any
let columnWidthRatio: number | undefined
const _v = basicStyle.columnWidthRatio ?? DEFAULT_BASIC_STYLE.columnWidthRatio
if (_v >= 1 && _v <= 100) {
columnWidthRatio = _v / 100.0
} else if (_v < 1) {
columnWidthRatio = 1 / 100.0
} else if (_v > 100) {
columnWidthRatio = 1
}
const columnWidthRatio = this.getColumnWidthRatio(basicStyle)
const columnPadding = this.getColumnPadding(columnWidthRatio)
if (columnWidthRatio) {
style = {
...style,
columnWidthRatio: columnWidthRatio
columnWidthRatio: this.getStyleColumnWidthRatio(columnPadding)
}
}
if (
@@ -110,6 +102,13 @@ export class HorizontalBar extends Bar {
paddingInner: 0.01
}
}
// 横向条形图同样通过 x band 控制分类宽度,需要同步外层和 dodgeX 组内间距
children[0].scale.x = {
...(children[0].scale.x || {}),
padding: columnPadding,
paddingInner: columnPadding
}
children[0].transform = this.configDodgePadding(children[0].transform, columnPadding)
children[0].scale.color.range = colors
children[0].scale.y.nice = true
children[0].style = { ...children[0].style, ...style }

View File

@@ -200,10 +200,8 @@ export class PercentageStackBar extends GroupStackBar {
constructor(name = 'percentage-bar-stack') {
super(name)
this.intervalOptions.encode = {
...this.intervalOptions.encode,
series: d => d.group
}
// 百分比堆叠没有分组槽位,移除 series band避免默认 series padding 放大柱间距
delete this.intervalOptions.encode.series
// 百分比堆叠与普通堆叠保持同向层级,避免 tooltip 顺序和视觉层级相反
this.intervalOptions.transform = [{ type: 'stackY', reverse: true }, { type: 'normalizeY' }]
this.axis = [...BAR_AXIS_TYPE, 'extStack']

View File

@@ -202,10 +202,8 @@ export class PercentageStackBar extends HorizontalStackBar {
constructor(name = 'percentage-bar-stack-horizontal') {
super(name)
this.intervalOptions.encode = {
...this.intervalOptions.encode,
series: d => d.group
}
// 百分比堆叠没有分组槽位,移除 series band避免默认 series padding 放大条间距
delete this.intervalOptions.encode.series
this.intervalOptions.transform = [{ type: 'stackY' }, { type: 'normalizeY' }]
this.axis = [...BAR_AXIS_TYPE, 'extStack']
}

View File

@@ -21,7 +21,6 @@ import {
TOOLTIP_ITEM_TPL,
TOOLTIP_TITLE_TPL
} from '@/views/chart/components/js/panel/common/common_antv'
import { DEFAULT_BASIC_STYLE } from '@/views/chart/components/editor/util/chart'
import { valueFormatter } from '@/views/chart/components/js/formatter'
import { HorizontalStackBar } from '@/views/chart/components/js/panel/charts/g2/bar/stack-horizontal-bar'
@@ -218,18 +217,20 @@ export class ProgressBar extends HorizontalStackBar {
const { children } = superOptions
children[0].encode.color = color1[0]
children[1].encode.color = color1[1]
let barWidthRatio
const _v = basicStyle.columnWidthRatio ?? DEFAULT_BASIC_STYLE.columnWidthRatio
if (_v >= 1 && _v <= 100) {
barWidthRatio = _v / 100.0
} else if (_v < 1) {
barWidthRatio = 1 / 100.0
} else if (_v > 100) {
barWidthRatio = 1
}
if (barWidthRatio) {
children[0].style = { ...children[0].style, columnWidthRatio: barWidthRatio }
children[1].style = { ...children[1].style, columnWidthRatio: barWidthRatio }
const columnWidthRatio = this.getColumnWidthRatio(basicStyle)
const columnPadding = this.getColumnPadding(columnWidthRatio)
const styleColumnWidthRatio = this.getStyleColumnWidthRatio(columnPadding)
if (styleColumnWidthRatio) {
children[0].style = { ...children[0].style, columnWidthRatio: styleColumnWidthRatio }
children[1].scale = {
...children[1].scale,
x: {
...(children[1].scale?.x || {}),
padding: columnPadding,
paddingInner: columnPadding
}
}
children[1].style = { ...children[1].style, columnWidthRatio: styleColumnWidthRatio }
}
return superOptions
}

View File

@@ -341,11 +341,10 @@ export class Radar extends G2ChartView {
const customAttr: DeepPartial<ChartAttr> = parseJson(chart.customAttr)
const tooltipAttr = customAttr.tooltip
const yAxis = chart.yAxis
const lineMark = options.children[0]
if (!tooltipAttr.show) {
return {
...options,
tooltip: false
}
defaultsDeep(lineMark, { tooltip: false })
return options
}
const formatterMap = tooltipAttr.seriesTooltipFormatter
?.filter(i => i.show)
@@ -362,7 +361,6 @@ export class Radar extends G2ChartView {
g2TooltipWrapper.style.zIndex = '9999'
document.body.appendChild(g2TooltipWrapper)
}
const lineMark = options.children[0]
const tooltipOptions: G2Spec = {
tooltip: d => d,
interaction: {

View File

@@ -38,8 +38,13 @@ import { registerSymbol, Symbols } from '@antv/g2/esm/utils/marker'
import G2TooltipCarousel from '@/views/chart/components/js/G2TooltipCarousel'
import {
createTooltipWrapper,
getSeriesTooltipFormatter,
getSeriesTooltipFormatterMap,
getStackTooltipGroupName,
getTooltipItemFormatter,
renderGroupedTooltipItems,
isSeriesTooltipFormatterShown,
isTooltipItemShown,
tooltipCss,
tooltipMaxHeight
} from '../bar/barUtil'
@@ -608,12 +613,7 @@ export class Area extends G2ChartView {
defaultsDeep(lineMark, { tooltip: false })
return options
}
const formatterMap = tooltipAttr.seriesTooltipFormatter
?.filter(i => i.show)
.reduce((pre, next) => {
pre[next.id] = next
return pre
}, {}) as Record<string, SeriesFormatter>
const formatterMap = getSeriesTooltipFormatterMap(tooltipAttr)
const yAxis = chart.yAxis
const tooltipOptions: G2Spec = {
tooltip: d => d,
@@ -623,11 +623,14 @@ export class Area extends G2ChartView {
mount: createTooltipWrapper(chart),
css: tooltipCss(tooltipAttr),
enterable: true,
render: (e, { title, items: originalItems }) => {
render: (_e, { title, items: originalItems }) => {
const titleHtml = TOOLTIP_TITLE_TPL.replace('{title}', title)
let tooltipItems = originalItems
if (tooltipAttr.seriesTooltipFormatter?.length) {
tooltipItems = originalItems.filter(item => formatterMap[item.quotaList[0].id])
// 只隐藏明确配置为不展示的字段,避免过期 formatter 漏掉新指标。
tooltipItems = originalItems.filter(item =>
isTooltipItemShown(formatterMap, item, 'yAxis')
)
}
const result = []
const head = originalItems[0]
@@ -635,13 +638,20 @@ export class Area extends G2ChartView {
if (item.value === null || item.value === undefined) {
return
}
const formatter = formatterMap[item.quotaList[0].id] ?? yAxis[0]
const value = valueFormatter(item.value, formatter.formatterCfg)
const formatter = getTooltipItemFormatter(formatterMap, item, yAxis, 'yAxis')
const value = valueFormatter(
item.value,
formatter?.formatterCfg ?? tooltipAttr.tooltipFormatter
)
result.push({ ...item, name: item.category, value })
})
head.dynamicTooltipValue?.forEach(item => {
const formatter = formatterMap[item.fieldId]
if (formatter) {
const formatter = getSeriesTooltipFormatter(
formatterMap,
item.fieldId,
chart.extTooltip
)
if (formatter && isSeriesTooltipFormatterShown(formatterMap, item.fieldId)) {
const value = valueFormatter(parseFloat(item.value), formatter.formatterCfg)
const name = isEmpty(formatter.chartShowName)
? formatter.name

View File

@@ -1,4 +1,5 @@
import { useI18n } from '@/hooks/web/useI18n'
import type { G2Spec } from '@antv/g2'
const { t } = useI18n()
@@ -61,3 +62,19 @@ export const PIE_AXIS_CONFIG: AxisConfig = {
limit: 1
}
}
export const configSingleSectorScale = (options: G2Spec, data: any[]): G2Spec => {
if (data.length !== 1) {
return options
}
options.scale = {
...options.scale,
x: {
...options.scale?.x,
padding: 0,
paddingInner: 0,
paddingOuter: 0
}
}
return options
}

View File

@@ -11,6 +11,7 @@ import {
PIE_EDITOR_PROPERTY,
PIE_EDITOR_PROPERTY_INNER
} from '@/views/chart/components/js/panel/charts/g2plot/pie/common'
import { configSingleSectorScale } from './common'
import {
getTooltipSeriesTotalMap,
handleChartDashboardHidden,
@@ -136,8 +137,9 @@ export class Pie extends G2ChartView {
}
data.push(initOtherItem)
}
options.coordinate.outerRadius = basicStyle.radius / 100
return options
const singleSectorOptions = configSingleSectorScale(options, data)
singleSectorOptions.coordinate.outerRadius = basicStyle.radius / 100
return singleSectorOptions
}
protected configColor(chart: Chart, options: G2Spec): G2Spec {

View File

@@ -11,6 +11,7 @@ import {
PIE_EDITOR_PROPERTY,
PIE_EDITOR_PROPERTY_INNER
} from '@/views/chart/components/js/panel/charts/g2plot/pie/common'
import { configSingleSectorScale } from './common'
import {
getTooltipSeriesTotalMap,
handleChartDashboardHidden,
@@ -141,8 +142,9 @@ export class Rose extends G2ChartView {
}
data.push(initOtherItem)
}
options.coordinate.outerRadius = basicStyle.radius / 100
return options
const singleSectorOptions = configSingleSectorScale(options, data)
singleSectorOptions.coordinate.outerRadius = basicStyle.radius / 100
return singleSectorOptions
}
protected configColor(chart: Chart, options: G2Spec): G2Spec {