forked from github/dataease
feat(图表): 堆叠柱状图支持显示总计 #10895
This commit is contained in:
parent
c212b39e97
commit
bc16a24d72
@ -844,6 +844,22 @@ declare interface ChartLabelAttr {
|
|||||||
* 转化率标签
|
* 转化率标签
|
||||||
*/
|
*/
|
||||||
conversionTag: ConversionTagAtt
|
conversionTag: ConversionTagAtt
|
||||||
|
/**
|
||||||
|
* 堆叠柱状图显示总计
|
||||||
|
*/
|
||||||
|
showTotal: boolean
|
||||||
|
/**
|
||||||
|
* 总计标签字体大小
|
||||||
|
*/
|
||||||
|
totalFontSize: number
|
||||||
|
/**
|
||||||
|
* 总计标签字体颜色
|
||||||
|
*/
|
||||||
|
totalColor: string
|
||||||
|
/**
|
||||||
|
* 总计标签格式化设置
|
||||||
|
*/
|
||||||
|
totalFormatter: BaseFormatter
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 提示设置
|
* 提示设置
|
||||||
|
@ -220,12 +220,13 @@ const COMPUTED_DEFAULT_LABEL = computed(() => {
|
|||||||
return DEFAULT_LABEL
|
return DEFAULT_LABEL
|
||||||
})
|
})
|
||||||
|
|
||||||
const state = reactive<{ labelForm: ChartLabelAttr | any }>({
|
const state = reactive<{ labelForm: DeepPartial<ChartLabelAttr> }>({
|
||||||
labelForm: {
|
labelForm: {
|
||||||
quotaLabelFormatter: DEFAULT_LABEL.quotaLabelFormatter,
|
quotaLabelFormatter: DEFAULT_LABEL.quotaLabelFormatter,
|
||||||
seriesLabelFormatter: [],
|
seriesLabelFormatter: [],
|
||||||
labelFormatter: DEFAULT_LABEL.labelFormatter,
|
labelFormatter: DEFAULT_LABEL.labelFormatter,
|
||||||
conversionTag: DEFAULT_LABEL.conversionTag
|
conversionTag: DEFAULT_LABEL.conversionTag,
|
||||||
|
totalFormatter: DEFAULT_LABEL.totalFormatter
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -662,6 +663,149 @@ const conversionPrecision = [
|
|||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
<el-form-item v-if="showProperty('showTotal')" class="form-item" :class="'form-item-' + themes">
|
||||||
|
<el-checkbox
|
||||||
|
size="small"
|
||||||
|
:effect="themes"
|
||||||
|
v-model="state.labelForm.showTotal"
|
||||||
|
@change="changeLabelAttr('showTotal')"
|
||||||
|
:label="t('chart.total_show')"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<template v-if="false && showProperty('totalFormatter')">
|
||||||
|
<el-divider class="m-divider" :class="{ 'divider-dark': themes === 'dark' }" />
|
||||||
|
<div v-show="state.labelForm.showTotal">
|
||||||
|
<el-space>
|
||||||
|
<el-form-item
|
||||||
|
class="form-item"
|
||||||
|
:class="'form-item-' + themes"
|
||||||
|
v-if="showProperty('totalColor')"
|
||||||
|
:label="t('chart.text')"
|
||||||
|
>
|
||||||
|
<el-color-picker
|
||||||
|
:effect="themes"
|
||||||
|
v-model="state.labelForm.totalColor"
|
||||||
|
class="color-picker-style"
|
||||||
|
:predefine="COLOR_PANEL"
|
||||||
|
@change="changeLabelAttr('totalColor')"
|
||||||
|
is-custom
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
class="form-item"
|
||||||
|
:class="'form-item-' + themes"
|
||||||
|
v-if="showProperty('totalFontSize')"
|
||||||
|
>
|
||||||
|
<template #label> </template>
|
||||||
|
<el-tooltip content="字号" :effect="toolTip" placement="top">
|
||||||
|
<el-select
|
||||||
|
size="small"
|
||||||
|
style="width: 108px"
|
||||||
|
:effect="themes"
|
||||||
|
v-model.number="state.labelForm.totalFontSize"
|
||||||
|
:placeholder="t('chart.text_fontsize')"
|
||||||
|
@change="changeLabelAttr('totalFontSize')"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="option in fontSizeList"
|
||||||
|
:key="option.value"
|
||||||
|
:label="option.name"
|
||||||
|
:value="option.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-tooltip>
|
||||||
|
</el-form-item>
|
||||||
|
</el-space>
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('chart.value_formatter_type')"
|
||||||
|
class="form-item"
|
||||||
|
:class="'form-item-' + themes"
|
||||||
|
>
|
||||||
|
<el-select
|
||||||
|
size="small"
|
||||||
|
:effect="themes"
|
||||||
|
v-model="state.labelForm.totalFormatter.type"
|
||||||
|
@change="changeLabelAttr('totalFormatter.type')"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="type in formatterType"
|
||||||
|
:key="type.value"
|
||||||
|
:label="$t('chart.' + type.name)"
|
||||||
|
:value="type.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
v-if="state.labelForm.totalFormatter && state.labelForm.totalFormatter.type !== 'auto'"
|
||||||
|
:label="$t('chart.value_formatter_decimal_count')"
|
||||||
|
class="form-item"
|
||||||
|
:class="'form-item-' + themes"
|
||||||
|
>
|
||||||
|
<el-input-number
|
||||||
|
controls-position="right"
|
||||||
|
:effect="themes"
|
||||||
|
v-model="state.labelForm.totalFormatter.decimalCount"
|
||||||
|
:precision="0"
|
||||||
|
:min="0"
|
||||||
|
:max="10"
|
||||||
|
@change="changeLabelAttr('totalFormatter.decimalCount')"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-row
|
||||||
|
:gutter="8"
|
||||||
|
v-if="state.labelForm.totalFormatter && state.labelForm.totalFormatter.type !== 'percent'"
|
||||||
|
>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('chart.value_formatter_unit')"
|
||||||
|
class="form-item"
|
||||||
|
:class="'form-item-' + themes"
|
||||||
|
>
|
||||||
|
<el-select
|
||||||
|
size="small"
|
||||||
|
:effect="themes"
|
||||||
|
v-model="state.labelForm.totalFormatter.unit"
|
||||||
|
:placeholder="$t('chart.pls_select_field')"
|
||||||
|
@change="changeLabelAttr('totalFormatter.unit')"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in unitType"
|
||||||
|
:key="item.value"
|
||||||
|
:label="$t('chart.' + item.name)"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('chart.value_formatter_suffix')"
|
||||||
|
class="form-item"
|
||||||
|
:class="'form-item-' + themes"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
:effect="themes"
|
||||||
|
v-model="state.labelForm.totalFormatter.suffix"
|
||||||
|
clearable
|
||||||
|
:placeholder="$t('commons.input_content')"
|
||||||
|
@change="changeLabelAttr('totalFormatter.suffix')"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-form-item class="form-item" :class="'form-item-' + themes">
|
||||||
|
<el-checkbox
|
||||||
|
size="small"
|
||||||
|
:effect="themes"
|
||||||
|
v-model="state.labelForm.totalFormatter.thousandSeparator"
|
||||||
|
@change="changeLabelAttr('totalFormatter.thousandSeparator')"
|
||||||
|
:label="t('chart.value_formatter_thousand_separator')"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<el-form-item
|
<el-form-item
|
||||||
class="form-item"
|
class="form-item"
|
||||||
|
@ -335,7 +335,11 @@ export const DEFAULT_LABEL: ChartLabelAttr = {
|
|||||||
show: false,
|
show: false,
|
||||||
precision: 2,
|
precision: 2,
|
||||||
text: '转化率'
|
text: '转化率'
|
||||||
}
|
},
|
||||||
|
showTotal: false,
|
||||||
|
totalFontSize: 12,
|
||||||
|
totalColor: '#FFF',
|
||||||
|
totalFormatter: formatterItem
|
||||||
}
|
}
|
||||||
export const DEFAULT_TOOLTIP: ChartTooltipAttr = {
|
export const DEFAULT_TOOLTIP: ChartTooltipAttr = {
|
||||||
show: true,
|
show: true,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import type { Column, ColumnOptions } from '@antv/g2plot/esm/plots/column'
|
import type { Column, ColumnOptions } from '@antv/g2plot/esm/plots/column'
|
||||||
import { cloneDeep, isEmpty } from 'lodash-es'
|
import { cloneDeep, each, groupBy, isEmpty } from 'lodash-es'
|
||||||
import {
|
import {
|
||||||
G2PlotChartView,
|
G2PlotChartView,
|
||||||
G2PlotDrawOptions
|
G2PlotDrawOptions
|
||||||
@ -12,7 +12,7 @@ import {
|
|||||||
setUpStackSeriesColor
|
setUpStackSeriesColor
|
||||||
} from '@/views/chart/components/js/util'
|
} from '@/views/chart/components/js/util'
|
||||||
import type { Datum } from '@antv/g2plot'
|
import type { Datum } from '@antv/g2plot'
|
||||||
import { valueFormatter } from '@/views/chart/components/js/formatter'
|
import { formatterItem, valueFormatter } from '@/views/chart/components/js/formatter'
|
||||||
import {
|
import {
|
||||||
BAR_AXIS_TYPE,
|
BAR_AXIS_TYPE,
|
||||||
BAR_EDITOR_PROPERTY,
|
BAR_EDITOR_PROPERTY,
|
||||||
@ -264,7 +264,14 @@ export class Bar extends G2PlotChartView<ColumnOptions, Column> {
|
|||||||
export class StackBar extends Bar {
|
export class StackBar extends Bar {
|
||||||
propertyInner = {
|
propertyInner = {
|
||||||
...this['propertyInner'],
|
...this['propertyInner'],
|
||||||
'label-selector': [...BAR_EDITOR_PROPERTY_INNER['label-selector'], 'vPosition'],
|
'label-selector': [
|
||||||
|
...BAR_EDITOR_PROPERTY_INNER['label-selector'],
|
||||||
|
'vPosition',
|
||||||
|
'showTotal',
|
||||||
|
'totalColor',
|
||||||
|
'totalFontSize',
|
||||||
|
'totalFormatter'
|
||||||
|
],
|
||||||
'tooltip-selector': ['fontSize', 'color', 'backgroundColor', 'tooltipFormatter', 'show']
|
'tooltip-selector': ['fontSize', 'color', 'backgroundColor', 'tooltipFormatter', 'show']
|
||||||
}
|
}
|
||||||
protected configLabel(chart: Chart, options: ColumnOptions): ColumnOptions {
|
protected configLabel(chart: Chart, options: ColumnOptions): ColumnOptions {
|
||||||
@ -340,13 +347,58 @@ export class StackBar extends Bar {
|
|||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected configTotalLabel(chart: Chart, options: ColumnOptions): ColumnOptions {
|
||||||
|
if (!options.label) {
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
const { label } = parseJson(chart.customAttr)
|
||||||
|
if (label.showTotal) {
|
||||||
|
const formatterCfg = label.labelFormatter ?? formatterItem
|
||||||
|
each(groupBy(options.data, 'field'), (values, key) => {
|
||||||
|
const total = values.reduce((a, b) => a + b.value, 0)
|
||||||
|
const value = valueFormatter(total, formatterCfg)
|
||||||
|
if (!options.annotations) {
|
||||||
|
options = {
|
||||||
|
...options,
|
||||||
|
annotations: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
options.annotations.push({
|
||||||
|
type: 'text',
|
||||||
|
position: [key, total],
|
||||||
|
content: `${value}`,
|
||||||
|
style: {
|
||||||
|
textAlign: 'center',
|
||||||
|
fontSize: label.fontSize,
|
||||||
|
fill: label.color
|
||||||
|
},
|
||||||
|
offsetY: -(parseInt(label.fontSize as unknown as string) / 2)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
|
||||||
public setupSeriesColor(chart: ChartObj, data?: any[]): ChartBasicStyle['seriesColor'] {
|
public setupSeriesColor(chart: ChartObj, data?: any[]): ChartBasicStyle['seriesColor'] {
|
||||||
return setUpStackSeriesColor(chart, data)
|
return setUpStackSeriesColor(chart, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected setupOptions(chart: Chart, options: ColumnOptions): ColumnOptions {
|
protected setupOptions(chart: Chart, options: ColumnOptions): ColumnOptions {
|
||||||
const tmp = super.setupOptions(chart, options)
|
return flow(
|
||||||
return flow(this.configData)(chart, tmp, {}, this)
|
this.configTheme,
|
||||||
|
this.configEmptyDataStrategy,
|
||||||
|
this.configColor,
|
||||||
|
this.configBasicStyle,
|
||||||
|
this.configLabel,
|
||||||
|
this.configTooltip,
|
||||||
|
this.configLegend,
|
||||||
|
this.configXAxis,
|
||||||
|
this.configYAxis,
|
||||||
|
this.configSlider,
|
||||||
|
this.configAnalyse,
|
||||||
|
this.configData,
|
||||||
|
this.configTotalLabel
|
||||||
|
)(chart, options, {}, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(name = 'bar-stack') {
|
constructor(name = 'bar-stack') {
|
||||||
@ -439,6 +491,10 @@ export class GroupBar extends StackBar {
|
|||||||
* 分组堆叠柱状图
|
* 分组堆叠柱状图
|
||||||
*/
|
*/
|
||||||
export class GroupStackBar extends StackBar {
|
export class GroupStackBar extends StackBar {
|
||||||
|
propertyInner = {
|
||||||
|
...this['propertyInner'],
|
||||||
|
'label-selector': [...BAR_EDITOR_PROPERTY_INNER['label-selector'], 'vPosition']
|
||||||
|
}
|
||||||
protected configTheme(chart: Chart, options: ColumnOptions): ColumnOptions {
|
protected configTheme(chart: Chart, options: ColumnOptions): ColumnOptions {
|
||||||
const baseOptions = super.configTheme(chart, options)
|
const baseOptions = super.configTheme(chart, options)
|
||||||
const baseTheme = baseOptions.theme as object
|
const baseTheme = baseOptions.theme as object
|
||||||
|
@ -27,6 +27,7 @@ import {
|
|||||||
handleEmptyDataStrategy,
|
handleEmptyDataStrategy,
|
||||||
setupSeriesColor
|
setupSeriesColor
|
||||||
} from '../../../util'
|
} from '../../../util'
|
||||||
|
import { Options } from '@antv/g2plot'
|
||||||
|
|
||||||
export interface G2PlotDrawOptions<O> extends AntVDrawOptions<O> {
|
export interface G2PlotDrawOptions<O> extends AntVDrawOptions<O> {
|
||||||
/**
|
/**
|
||||||
@ -127,7 +128,10 @@ export abstract class G2PlotChartView<
|
|||||||
|
|
||||||
protected configAnalyse(chart: Chart, options: O): O {
|
protected configAnalyse(chart: Chart, options: O): O {
|
||||||
const annotations = getAnalyse(chart)
|
const annotations = getAnalyse(chart)
|
||||||
return { ...options, annotations }
|
return {
|
||||||
|
...options,
|
||||||
|
annotations: [...annotations, ...((options as unknown as Options).annotations || [])]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected configAnalyseHorizontal(chart: Chart, options: O): O {
|
protected configAnalyseHorizontal(chart: Chart, options: O): O {
|
||||||
|
Loading…
Reference in New Issue
Block a user