diff --git a/core/core-frontend/src/views/chart/components/editor/drag-item/DimensionItem.vue b/core/core-frontend/src/views/chart/components/editor/drag-item/DimensionItem.vue index ded677ab78..95038740d9 100644 --- a/core/core-frontend/src/views/chart/components/editor/drag-item/DimensionItem.vue +++ b/core/core-frontend/src/views/chart/components/editor/drag-item/DimensionItem.vue @@ -206,6 +206,9 @@ const showSort = computed(() => { return !isChartMix || isDimensionType }) const showSortPriority = computed(() => { + if (props.chart.type === 'line' && props.type === 'dimensionExt') { + return false + } if (props.chart.type.includes('chart-mix')) { return false } diff --git a/core/core-frontend/src/views/chart/components/js/extremumUitl.ts b/core/core-frontend/src/views/chart/components/js/extremumUitl.ts index 5af1e1656c..39d6779eee 100644 --- a/core/core-frontend/src/views/chart/components/js/extremumUitl.ts +++ b/core/core-frontend/src/views/chart/components/js/extremumUitl.ts @@ -40,6 +40,8 @@ const getRgbaColorLastRgba = (rgbaString: string) => { return lastRGBA } +const EXTREMUM_LABEL_MARK_TYPES = ['point', 'interval'] + /** * 判断图表类型是否支持最值标注(极值标签) * @param chart - 图表配置对象 @@ -94,7 +96,7 @@ export const extremumEvt = ( const chartData = options.children ? options.children : [options] // 遍历所有 series,为标签注入 HTML 和样式 chartData - .filter(item => item.labels?.length) + .filter(item => item.labels?.length && EXTREMUM_LABEL_MARK_TYPES.includes(item.type)) .forEach(item => { item.labels.forEach(label => { const oldPosition = label.position || 'top' diff --git a/core/core-frontend/src/views/chart/components/js/panel/charts/g2/bar/stack-bar.ts b/core/core-frontend/src/views/chart/components/js/panel/charts/g2/bar/stack-bar.ts index b11ad82d3a..1fd8736f5e 100644 --- a/core/core-frontend/src/views/chart/components/js/panel/charts/g2/bar/stack-bar.ts +++ b/core/core-frontend/src/views/chart/components/js/panel/charts/g2/bar/stack-bar.ts @@ -75,7 +75,7 @@ export class StackBar extends Bar { fill: labelAttr.color, fontSize: labelAttr.fontSize, ...position, - formatter: (value, _data) => valueFormatter(value, labelAttr.labelFormatter), + formatter: value => valueFormatter(value, labelAttr.labelFormatter), ...transform }) } @@ -83,19 +83,38 @@ export class StackBar extends Bar { if (labelAttr.showTotal) { const formatterCfg = labelAttr.labelFormatter ?? formatterItem const groupedData = groupBy(options.data, 'field') - for (const [key, values] of Object.entries(groupedData)) { + const totalData = Object.entries(groupedData).map(([key, values]) => { const total = values.reduce((a, b) => a + b.value, 0) - const value = valueFormatter(total, formatterCfg) + return { + field: key, + value: total, + totalLabel: valueFormatter(total, formatterCfg) + } + }) + if (totalData.length) { children.push({ - type: 'text', - data: [key, total], - style: { - text: value, - textAlign: 'center', - dy: -10, - fill: labelAttr.color, - fontSize: labelAttr.fontSize + type: 'point', + data: totalData, + encode: { + x: 'field', + y: 'value' }, + style: { + opacity: 0 + }, + labels: [ + { + text: 'totalLabel', + fillOpacity: 1, + fill: labelAttr.color, + fontSize: labelAttr.fontSize, + position: 'top', + dx: 0, + dy: -10, + ...transform, + textAlign: 'center' + } + ], tooltip: false }) } diff --git a/core/core-frontend/src/views/chart/components/js/panel/charts/g2/bar/stack-horizontal-bar.ts b/core/core-frontend/src/views/chart/components/js/panel/charts/g2/bar/stack-horizontal-bar.ts index 28065b145c..c5e032fe1a 100644 --- a/core/core-frontend/src/views/chart/components/js/panel/charts/g2/bar/stack-horizontal-bar.ts +++ b/core/core-frontend/src/views/chart/components/js/panel/charts/g2/bar/stack-horizontal-bar.ts @@ -1,4 +1,7 @@ -import { BAR_EDITOR_PROPERTY } from '@/views/chart/components/js/panel/charts/g2/bar/common' +import { + BAR_EDITOR_PROPERTY, + BAR_EDITOR_PROPERTY_INNER +} from '@/views/chart/components/js/panel/charts/g2/bar/common' import { flow, parseJson } from '@/views/chart/components/js/util' import { createTooltipWrapper, @@ -11,9 +14,9 @@ import { TOOLTIP_ITEM_TPL, TOOLTIP_TITLE_TPL } from '@/views/chart/components/js/panel/common/common_antv' -import { valueFormatter } from '@/views/chart/components/js/formatter' +import { formatterItem, valueFormatter } from '@/views/chart/components/js/formatter' import { HorizontalBar } from '@/views/chart/components/js/panel/charts/g2/bar/horizontal-bar' -import { isEmpty } from 'lodash-es' +import { groupBy, isEmpty } from 'lodash-es' const { t } = useI18n() @@ -33,7 +36,15 @@ export class HorizontalStackBar extends HorizontalBar { } propertyInner = { ...this['propertyInner'], - 'label-selector': ['color', 'fontSize', 'hPosition', 'labelFormatter'], + 'label-selector': [ + ...BAR_EDITOR_PROPERTY_INNER['label-selector'], + 'hPosition', + 'showTotal', + 'totalColor', + 'totalFontSize', + 'totalFormatter', + 'showStackQuota' + ], 'tooltip-selector': ['fontSize', 'color', 'backgroundColor', 'tooltipFormatter', 'show'] } @@ -53,23 +64,64 @@ export class HorizontalStackBar extends HorizontalBar { ...(labelAttr.fullDisplay ? [] : [{ type: 'overlapHide' }]) ] - const labels = [ - { + const labels = [] + if (labelAttr.showStackQuota ?? true) { + labels.push({ text: 'value', fillOpacity: 1, fill: labelAttr.color, fontSize: labelAttr.fontSize, ...position, - formatter: (value, _data) => { - debugger + formatter: value => { if (value === null || value === undefined) { return '' } return valueFormatter(value, labelAttr.labelFormatter) }, transform + }) + } + + if (labelAttr.showTotal) { + const formatterCfg = labelAttr.labelFormatter ?? formatterItem + const groupedData = groupBy(options.data, 'field') + const totalData = Object.entries(groupedData).map(([key, values]) => { + const total = values.reduce((a, b) => a + b.value, 0) + return { + field: key, + value: total, + totalLabel: valueFormatter(total, formatterCfg) + } + }) + if (totalData.length) { + children.push({ + type: 'point', + data: totalData, + encode: { + x: 'field', + y: 'value' + }, + coordinate: { transform: [{ type: 'transpose' }] }, + style: { + opacity: 0 + }, + labels: [ + { + text: 'totalLabel', + fillOpacity: 1, + fill: labelAttr.color, + fontSize: labelAttr.fontSize, + position: 'right', + dx: 4, + dy: 0, + transform, + textAlign: 'start' + } + ], + tooltip: false + }) } - ] + } return { ...options, diff --git a/core/core-frontend/src/views/chart/components/js/panel/charts/g2/line/area.ts b/core/core-frontend/src/views/chart/components/js/panel/charts/g2/line/area.ts index 0437bb1596..6acaf08342 100644 --- a/core/core-frontend/src/views/chart/components/js/panel/charts/g2/line/area.ts +++ b/core/core-frontend/src/views/chart/components/js/panel/charts/g2/line/area.ts @@ -15,7 +15,14 @@ import { } from '@/views/chart/components/js/util' import { cloneDeep, defaultsDeep, isEmpty, merge } from 'lodash-es' import { valueFormatter } from '@/views/chart/components/js/formatter' -import { LINE_AXIS_TYPE, LINE_EDITOR_PROPERTY, LINE_EDITOR_PROPERTY_INNER } from './common' +import { + configStackOrderByYAxis, + configYAxisSeriesLegendDomain, + LINE_AXIS_TYPE, + LINE_EDITOR_PROPERTY, + LINE_EDITOR_PROPERTY_INNER, + sortTooltipItemsByYAxis +} from './common' import { useI18n } from '@/hooks/web/useI18n' import { addExtremumText, extremumEvt } from '@/views/chart/components/js/extremumUitl' import { Chart as G2Chart, G2Spec } from '@antv/g2' @@ -383,6 +390,7 @@ export class Area extends G2ChartView { lineLineDash, label: xAxis.axisLabel.show, labelFill: xAxis.axisLabel.color, + labelOpacity: 1, labelFillOpacity: 1, labelFontSize: xAxis.axisLabel.fontSize, tick: xAxis.axisLabel.show, @@ -436,6 +444,7 @@ export class Area extends G2ChartView { lineLineDash, label: yAxis.axisLabel.show, labelFill: yAxis.axisLabel.color, + labelOpacity: 1, labelFillOpacity: 1, labelFontSize: yAxis.axisLabel.fontSize, tick: false, @@ -501,7 +510,7 @@ export class Area extends G2ChartView { } } defaultsDeep(options, tmpLegend) - return options + return configYAxisSeriesLegendDomain(chart, options) } protected configAssistLine(chart: Chart, options: G2Spec): G2Spec { @@ -616,7 +625,7 @@ export class Area extends G2ChartView { } const result = [] const head = originalItems[0] - tooltipItems.forEach(item => { + sortTooltipItemsByYAxis(chart, tooltipItems).forEach(item => { if (item.value === null || item.value === undefined) { return } @@ -855,6 +864,10 @@ export class StackArea extends Area { return options } + protected configLegend(chart: Chart, options: G2Spec): G2Spec { + return super.configLegend(chart, configStackOrderByYAxis(chart, options)) + } + protected configTooltip(chart: Chart, options: G2Spec): G2Spec { const customAttr: DeepPartial = parseJson(chart.customAttr) const tooltipAttr = customAttr.tooltip @@ -874,7 +887,7 @@ export class StackArea extends Area { render: (e, { title, items }) => { const titleHtml = TOOLTIP_TITLE_TPL.replace('{title}', title) const result = [] - items.forEach(item => { + sortTooltipItemsByYAxis(chart, items).forEach(item => { if (item.value === null || item.value === undefined) { return } diff --git a/core/core-frontend/src/views/chart/components/js/panel/charts/g2/line/common.ts b/core/core-frontend/src/views/chart/components/js/panel/charts/g2/line/common.ts index ffc424174c..02435fdd30 100644 --- a/core/core-frontend/src/views/chart/components/js/panel/charts/g2/line/common.ts +++ b/core/core-frontend/src/views/chart/components/js/panel/charts/g2/line/common.ts @@ -1,3 +1,5 @@ +import type { G2Spec } from '@antv/g2' + export const LINE_EDITOR_PROPERTY: EditorProperty[] = [ 'background-overall-component', 'border-style', @@ -72,3 +74,125 @@ export const LINE_AXIS_TYPE: AxisType[] = [ 'extLabel', 'extTooltip' ] + +const Y_AXIS_SERIES_ORDER_NOT_FOUND = Number.MAX_SAFE_INTEGER + +/** + * 只有系列来自值轴指标时才按指标顺序处理;有分组/堆叠维度时,系列来自维度值,保留原逻辑 + */ +export const isYAxisSeriesChart = (chart: Chart) => { + return !!chart.yAxis?.length && !chart.xAxisExt?.length && !chart.extStack?.length +} + +/** + * 指标系列在图表数据里的名称来自字段展示名,未设置展示名时使用字段名 + */ +export const getYAxisSeriesName = axis => axis.chartShowName || axis.name + +/** + * 为 legend 生成值轴指标顺序的 color domain,同时保留数据里未匹配到的新系列 + */ +export const getYAxisSeriesDomain = (chart: Chart, data: any[] = []) => { + const domain = [] + chart.yAxis?.forEach(axis => { + const name = getYAxisSeriesName(axis) + if (name && !domain.includes(name)) { + domain.push(name) + } + }) + data.forEach(item => { + if (item.category && !domain.includes(item.category)) { + domain.push(item.category) + } + }) + return domain +} + +/** + * 建立指标 id/显示名到字段下标的映射,供 tooltip 和堆叠层级排序复用 + */ +export const getYAxisOrderMap = (chart: Chart) => { + const orderMap = new Map() + chart.yAxis?.forEach((axis, index) => { + orderMap.set(axis.id, index) + const name = getYAxisSeriesName(axis) + if (name) { + orderMap.set(name, index) + } + }) + return orderMap +} + +/** + * 根据指标字段顺序获取当前系列排序值,未命中时放到最后并保持原相对顺序 + */ +export const getYAxisSeriesOrder = (orderMap: Map, item) => { + return ( + orderMap.get(item.quotaList?.[0]?.id) ?? + orderMap.get(item.category) ?? + Y_AXIS_SERIES_ORDER_NOT_FOUND + ) +} + +/** + * 对 tooltip items 按右侧指标字段自上而下排序 + */ +export const sortTooltipItemsByYAxis = (chart: Chart, items: any[]) => { + if (!isYAxisSeriesChart(chart)) { + return items + } + const orderMap = getYAxisOrderMap(chart) + return items + .map((item, index) => ({ + item, + index, + order: getYAxisSeriesOrder(orderMap, item) + })) + .sort((a, b) => a.order - b.order || a.index - b.index) + .map(({ item }) => item) +} + +/** + * 对 legend 的 color domain 按右侧指标字段自上而下排序,不覆盖已有颜色映射等样式配置 + */ +export const configYAxisSeriesLegendDomain = (chart: Chart, options: G2Spec) => { + if (!isYAxisSeriesChart(chart)) { + return options + } + const domain = getYAxisSeriesDomain(chart, options.data?.value) + if (!domain.length) { + return options + } + const scale = options.scale ?? {} + options.scale = { + ...scale, + color: { + ...(scale.color ?? {}), + domain + } + } + return options +} + +/** + * G2 正值堆叠是从下往上累加,堆叠折线需要反向使用指标下标来实现视觉自上而下对齐 + */ +export const configStackOrderByYAxis = (chart: Chart, options: G2Spec) => { + if (!isYAxisSeriesChart(chart)) { + return options + } + const orderMap = getYAxisOrderMap(chart) + options.transform = (options.transform ?? []).map(transform => { + if (transform.type !== 'stackY') { + return transform + } + return { + ...transform, + orderBy: item => { + const order = getYAxisSeriesOrder(orderMap, item) + return order === Y_AXIS_SERIES_ORDER_NOT_FOUND ? order : -order + } + } + }) + return options +} diff --git a/core/core-frontend/src/views/chart/components/js/panel/charts/g2/line/line.ts b/core/core-frontend/src/views/chart/components/js/panel/charts/g2/line/line.ts index 474750717a..69da45002c 100644 --- a/core/core-frontend/src/views/chart/components/js/panel/charts/g2/line/line.ts +++ b/core/core-frontend/src/views/chart/components/js/panel/charts/g2/line/line.ts @@ -14,7 +14,13 @@ import { } from '@/views/chart/components/js/util' import { cloneDeep, defaultsDeep, isEmpty, merge } from 'lodash-es' import { valueFormatter } from '@/views/chart/components/js/formatter' -import { LINE_AXIS_TYPE, LINE_EDITOR_PROPERTY, LINE_EDITOR_PROPERTY_INNER } from './common' +import { + configYAxisSeriesLegendDomain, + LINE_AXIS_TYPE, + LINE_EDITOR_PROPERTY, + LINE_EDITOR_PROPERTY_INNER, + sortTooltipItemsByYAxis +} from './common' import { useI18n } from '@/hooks/web/useI18n' import { Chart as G2Chart, G2Spec } from '@antv/g2' import { DEFAULT_YAXIS_STYLE } from '@/views/chart/components/editor/util/chart' @@ -368,6 +374,7 @@ export class Line extends G2ChartView { lineLineDash, label: xAxis.axisLabel.show, labelFill: xAxis.axisLabel.color, + labelOpacity: 1, labelFillOpacity: 1, labelFontSize: xAxis.axisLabel.fontSize, grid: xAxis.splitLine.show, @@ -420,6 +427,7 @@ export class Line extends G2ChartView { lineLineDash, label: yAxis.axisLabel.show, labelFill: yAxis.axisLabel.color, + labelOpacity: 1, labelFillOpacity: 1, labelFontSize: yAxis.axisLabel.fontSize, grid: yAxis.splitLine.show, @@ -566,7 +574,7 @@ export class Line extends G2ChartView { defaultsDeep(options, scaleOpt) } } - return options + return configYAxisSeriesLegendDomain(chart, options) } protected configAssistLine(chart: Chart, options: G2Spec): G2Spec { @@ -683,7 +691,7 @@ export class Line extends G2ChartView { } const result = [] const head = originalItems[0] - tooltipItems.forEach(item => { + sortTooltipItemsByYAxis(chart, tooltipItems).forEach(item => { if (item.value === null || item.value === undefined) { return }