feat(图表): 堆叠条形图/柱状图可以设置图例排序 #16424

This commit is contained in:
wisonic
2025-08-13 19:06:36 +08:00
committed by wisonic-s
parent c607ca294a
commit 1a625780b2
3 changed files with 259 additions and 13 deletions

View File

@@ -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"
>
<custom-sort-edit
field-type="xAxisExt"
:field-type="sortAxis"
:chart="chart"
:field="state.customSortField"
:origin-sort-list="state.legendForm.customSort"

View File

@@ -1,12 +1,14 @@
import type { Column, ColumnOptions } from '@antv/g2plot/esm/plots/column'
import { cloneDeep, each, groupBy, isEmpty } from 'lodash-es'
import { cloneDeep, defaults, each, groupBy, isEmpty } from 'lodash-es'
import {
G2PlotChartView,
G2PlotDrawOptions
} from '@/views/chart/components/js/panel/types/impl/g2plot'
import {
convertToAlphaColor,
flow,
hexColorToRGBA,
isAlphaColor,
parseJson,
setUpGroupSeriesColor,
setUpStackSeriesColor
@@ -28,7 +30,11 @@ import {
TOOLTIP_TPL
} from '@/views/chart/components/js/panel/common/common_antv'
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 { clearExtremum, extremumEvt } from '@/views/chart/components/js/extremumUitl'
import { Group } from '@antv/g-canvas'
@@ -267,7 +273,7 @@ export class Bar extends G2PlotChartView<ColumnOptions, Column> {
* 堆叠柱状图
*/
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)

View File

@@ -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,