From 1a625780b228984547555d5fa1a9c0be962ab8b6 Mon Sep 17 00:00:00 2001 From: wisonic Date: Wed, 13 Aug 2025 19:06:36 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=9B=BE=E8=A1=A8):=20=E5=A0=86=E5=8F=A0?= =?UTF-8?q?=E6=9D=A1=E5=BD=A2=E5=9B=BE/=E6=9F=B1=E7=8A=B6=E5=9B=BE?= =?UTF-8?q?=E5=8F=AF=E4=BB=A5=E8=AE=BE=E7=BD=AE=E5=9B=BE=E4=BE=8B=E6=8E=92?= =?UTF-8?q?=E5=BA=8F=20#16424?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/LegendSelector.vue | 22 ++- .../components/js/panel/charts/bar/bar.ts | 130 +++++++++++++++++- .../js/panel/charts/bar/horizontal-bar.ts | 120 +++++++++++++++- 3 files changed, 259 insertions(+), 13 deletions(-) diff --git a/core/core-frontend/src/views/chart/components/editor/editor-style/components/LegendSelector.vue b/core/core-frontend/src/views/chart/components/editor/editor-style/components/LegendSelector.vue index 53e406b298..662c602849 100644 --- a/core/core-frontend/src/views/chart/components/editor/editor-style/components/LegendSelector.vue +++ b/core/core-frontend/src/views/chart/components/editor/editor-style/components/LegendSelector.vue @@ -103,6 +103,18 @@ const changeMisc = prop => { } const legendSort = ref() +const sortAxis = computed(() => { + if (props.chart.type === 'line') { + return 'xAxisExt' + } + return 'extStack' +}) +const legendSortDisabled = computed(() => { + if (props.chart?.type === 'line') { + return !props.chart.xAxisExt?.length + } + return !props.chart?.extStack?.length +}) const init = () => { legendSort.value?.blur() const chart = JSON.parse(JSON.stringify(props.chart)) @@ -234,7 +246,11 @@ const getMapCustomRange = index => { const customSort = [] const changeLegendSort = sort => { if (sort === 'custom') { - state.customSortField = cloneDeep(props.chart.xAxisExt?.[0]) + if (props.chart.type === 'line') { + state.customSortField = cloneDeep(props.chart.xAxisExt?.[0]) + } else { + state.customSortField = cloneDeep(props.chart.extStack?.[0]) + } if (!state.customSortField) { return } @@ -718,7 +734,7 @@ onMounted(() => { v-model="state.legendForm.sort" size="small" :effect="themes" - :disabled="!chart.xAxisExt?.length" + :disabled="legendSortDisabled" ref="legendSort" @change="changeLegendSort" > @@ -744,7 +760,7 @@ onMounted(() => { class="dialog-css custom_sort_dialog" > { * 堆叠柱状图 */ export class StackBar extends Bar { - properties = BAR_EDITOR_PROPERTY.filter(ele => ele !== 'threshold') + properties: EditorProperty[] = BAR_EDITOR_PROPERTY.filter(ele => ele !== 'threshold') propertyInner = { ...this['propertyInner'], 'label-selector': [ @@ -286,7 +292,8 @@ export class StackBar extends Bar { 'tooltipFormatter', 'show', 'carousel' - ] + ], + 'legend-selector': [...BAR_EDITOR_PROPERTY_INNER['legend-selector'], 'legendSort'] } protected configLabel(chart: Chart, options: ColumnOptions): ColumnOptions { let label = getLabel(chart) @@ -393,6 +400,113 @@ export class StackBar extends Bar { return options } + protected configSortedLegend(chart: Chart, options: ColumnOptions): ColumnOptions { + const optionTmp = super.configLegend(chart, options) + if (!optionTmp.legend) { + return optionTmp + } + const extStack = chart.extStack[0] + if (extStack?.customSort?.length > 0) { + // 图例自定义排序 + const sort = extStack.customSort ?? [] + if (sort?.length) { + // 用值域限定排序,有可能出现新数据但是未出现在图表上,所以这边要遍历一下子维度,加到后面,让新数据显示出来 + const data = optionTmp.data + const cats = + data?.reduce((p, n) => { + const cat = n['category'] + if (cat && !p.includes(cat)) { + p.push(cat) + } + return p + }, []) || [] + const values = sort.reduce((p, n) => { + if (cats.includes(n)) { + const index = cats.indexOf(n) + if (index !== -1) { + cats.splice(index, 1) + } + p.push(n) + } + return p + }, []) + cats.length > 0 && values.push(...cats) + optionTmp.meta = { + ...optionTmp.meta, + category: { + type: 'cat', + values + } + } + } + } + + const customStyle = parseJson(chart.customStyle) + let size + if (customStyle && customStyle.legend) { + size = defaults(JSON.parse(JSON.stringify(customStyle.legend)), DEFAULT_LEGEND_STYLE).size + } else { + size = DEFAULT_LEGEND_STYLE.size + } + + optionTmp.legend.marker.style = style => { + return { + r: size, + fill: style.fill + } + } + const { sort, customSort, icon } = customStyle.legend + if (sort && sort !== 'none' && chart.extStack.length) { + const customAttr = parseJson(chart.customAttr) + const { basicStyle } = customAttr + const seriesMap = + basicStyle.seriesColor?.reduce((p, n) => { + p[n.id] = n + return p + }, {}) || {} + const dupCheck = new Set() + const colors = optionTmp.color ?? optionTmp.theme.styleSheet.paletteQualitative10 + const items = optionTmp.data?.reduce((arr, item) => { + if (!dupCheck.has(item.category)) { + const fill = seriesMap[item.category]?.color ?? colors[dupCheck.size % colors.length] + dupCheck.add(item.category) + arr.push({ + name: item.category, + value: item.category, + marker: { + symbol: icon, + style: { + r: size, + fill: isAlphaColor(fill) ? fill : convertToAlphaColor(fill, basicStyle.alpha) + } + } + }) + } + return arr + }, []) + if (sort !== 'custom') { + items.sort((a, b) => { + return sort !== 'desc' ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name) + }) + } else { + const tmp = [] + ;(customSort || []).forEach(item => { + const index = items.findIndex(i => i.name === item) + if (index !== -1) { + tmp.push(items[index]) + items.splice(index, 1) + } + }) + items.unshift(...tmp) + } + optionTmp.legend.items = items + if (extStack?.customSort?.length > 0) { + delete optionTmp.meta?.category.values + } + } + return optionTmp + } + public setupSeriesColor(chart: ChartObj, data?: any[]): ChartBasicStyle['seriesColor'] { return setUpStackSeriesColor(chart, data) } @@ -406,7 +520,7 @@ export class StackBar extends Bar { this.configBasicStyle, this.configLabel, this.configTooltip, - this.configLegend, + this.configSortedLegend, this.configXAxis, this.configYAxis, this.configSlider, @@ -437,7 +551,8 @@ export class GroupBar extends StackBar { properties = BAR_EDITOR_PROPERTY propertyInner = { ...this['propertyInner'], - 'label-selector': [...BAR_EDITOR_PROPERTY_INNER['label-selector'], 'vPosition', 'showExtremum'] + 'label-selector': [...BAR_EDITOR_PROPERTY_INNER['label-selector'], 'vPosition', 'showExtremum'], + 'legend-selector': BAR_EDITOR_PROPERTY_INNER['legend-selector'] } axisConfig = { ...this['axisConfig'], @@ -589,7 +704,8 @@ export class GroupBar extends StackBar { export class GroupStackBar extends StackBar { propertyInner = { ...this['propertyInner'], - 'label-selector': [...BAR_EDITOR_PROPERTY_INNER['label-selector'], 'vPosition'] + 'label-selector': [...BAR_EDITOR_PROPERTY_INNER['label-selector'], 'vPosition'], + 'legend-selector': BAR_EDITOR_PROPERTY_INNER['legend-selector'] } protected configTheme(chart: Chart, options: ColumnOptions): ColumnOptions { const baseOptions = super.configTheme(chart, options) diff --git a/core/core-frontend/src/views/chart/components/js/panel/charts/bar/horizontal-bar.ts b/core/core-frontend/src/views/chart/components/js/panel/charts/bar/horizontal-bar.ts index 2d720e5afc..29947104c0 100644 --- a/core/core-frontend/src/views/chart/components/js/panel/charts/bar/horizontal-bar.ts +++ b/core/core-frontend/src/views/chart/components/js/panel/charts/bar/horizontal-bar.ts @@ -12,10 +12,12 @@ import { setGradientColor, TOOLTIP_TPL } from '@/views/chart/components/js/panel/common/common_antv' -import { cloneDeep } from 'lodash-es' +import { cloneDeep, defaults } from 'lodash-es' import { + convertToAlphaColor, flow, hexColorToRGBA, + isAlphaColor, parseJson, setUpStackSeriesColor } from '@/views/chart/components/js/util' @@ -27,7 +29,11 @@ import { } from '@/views/chart/components/js/panel/charts/bar/common' import type { Datum } from '@antv/g2plot/esm/types/common' import { useI18n } from '@/hooks/web/useI18n' -import { DEFAULT_BASIC_STYLE, DEFAULT_LABEL } from '@/views/chart/components/editor/util/chart' +import { + DEFAULT_BASIC_STYLE, + DEFAULT_LABEL, + DEFAULT_LEGEND_STYLE +} from '@/views/chart/components/editor/util/chart' import { Group } from '@antv/g-canvas' const { t } = useI18n() @@ -311,7 +317,8 @@ export class HorizontalStackBar extends HorizontalBar { propertyInner = { ...this['propertyInner'], 'label-selector': ['color', 'fontSize', 'hPosition', 'labelFormatter'], - 'tooltip-selector': ['fontSize', 'color', 'backgroundColor', 'tooltipFormatter', 'show'] + 'tooltip-selector': ['fontSize', 'color', 'backgroundColor', 'tooltipFormatter', 'show'], + 'legend-selector': [...BAR_EDITOR_PROPERTY_INNER['legend-selector'], 'legendSort'] } protected configLabel(chart: Chart, options: BarOptions): BarOptions { const baseOptions = super.configLabel(chart, options) @@ -406,6 +413,113 @@ export class HorizontalStackBar extends HorizontalBar { return options } + protected configLegend(chart: Chart, options: BarOptions): BarOptions { + const optionTmp = super.configLegend(chart, options) + if (!optionTmp.legend) { + return optionTmp + } + const extStack = chart.extStack[0] + if (extStack?.customSort?.length > 0) { + // 图例自定义排序 + const sort = extStack.customSort ?? [] + if (sort?.length) { + // 用值域限定排序,有可能出现新数据但是未出现在图表上,所以这边要遍历一下子维度,加到后面,让新数据显示出来 + const data = optionTmp.data + const cats = + data?.reduce((p, n) => { + const cat = n['category'] + if (cat && !p.includes(cat)) { + p.push(cat) + } + return p + }, []) || [] + const values = sort.reduce((p, n) => { + if (cats.includes(n)) { + const index = cats.indexOf(n) + if (index !== -1) { + cats.splice(index, 1) + } + p.push(n) + } + return p + }, []) + cats.length > 0 && values.push(...cats) + optionTmp.meta = { + ...optionTmp.meta, + category: { + type: 'cat', + values + } + } + } + } + + const customStyle = parseJson(chart.customStyle) + let size + if (customStyle && customStyle.legend) { + size = defaults(JSON.parse(JSON.stringify(customStyle.legend)), DEFAULT_LEGEND_STYLE).size + } else { + size = DEFAULT_LEGEND_STYLE.size + } + + optionTmp.legend.marker.style = style => { + return { + r: size, + fill: style.fill + } + } + const { sort, customSort, icon } = customStyle.legend + if (sort && sort !== 'none' && chart.extStack.length) { + const customAttr = parseJson(chart.customAttr) + const { basicStyle } = customAttr + const seriesMap = + basicStyle.seriesColor?.reduce((p, n) => { + p[n.id] = n + return p + }, {}) || {} + const dupCheck = new Set() + const colors = optionTmp.color ?? optionTmp.theme.styleSheet.paletteQualitative10 + const items = optionTmp.data?.reduce((arr, item) => { + if (!dupCheck.has(item.category)) { + const fill = seriesMap[item.category]?.color ?? colors[dupCheck.size % colors.length] + dupCheck.add(item.category) + arr.push({ + name: item.category, + value: item.category, + marker: { + symbol: icon, + style: { + r: size, + fill: isAlphaColor(fill) ? fill : convertToAlphaColor(fill, basicStyle.alpha) + } + } + }) + } + return arr + }, []) + if (sort !== 'custom') { + items.sort((a, b) => { + return sort !== 'desc' ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name) + }) + } else { + const tmp = [] + ;(customSort || []).forEach(item => { + const index = items.findIndex(i => i.name === item) + if (index !== -1) { + tmp.push(items[index]) + items.splice(index, 1) + } + }) + items.unshift(...tmp) + } + optionTmp.legend.items = items + if (extStack?.customSort?.length > 0) { + delete optionTmp.meta?.category.values + } + } + return optionTmp + } + protected setupOptions(chart: Chart, options: BarOptions): BarOptions { return flow( this.configTheme,