feat(图表): 新增对称柱状图

This commit is contained in:
jianneng-fit2cloud 2024-05-11 12:07:06 +08:00
parent c20adb3a61
commit 64b214cbfa
8 changed files with 467 additions and 14 deletions

View File

@ -116,7 +116,9 @@ public class ChartDataManage {
xAxis.addAll(xAxisExt);
}
List<ChartViewFieldDTO> yAxis = new ArrayList<>(view.getYAxis());
if (StringUtils.equalsIgnoreCase(view.getType(), "chart-mix")) {
if (StringUtils.equalsIgnoreCase(view.getType(), "chart-mix")
|| StringUtils.equalsIgnoreCase(view.getType(), "bidirectional-bar")
|| StringUtils.equalsIgnoreCase(view.getType(), "quadrant")) {
List<ChartViewFieldDTO> yAxisExt = new ArrayList<>(view.getYAxisExt());
yAxis.addAll(yAxisExt);
}
@ -124,10 +126,6 @@ public class ChartDataManage {
List<ChartViewFieldDTO> sizeField = getSizeField(view);
yAxis.addAll(sizeField);
}
if (StringUtils.equalsIgnoreCase(view.getType(), "quadrant")) {
List<ChartViewFieldDTO> yAxisExt = new ArrayList<>(view.getYAxisExt());
yAxis.addAll(yAxisExt);
}
boolean skipBarRange = false;
boolean barRangeDate = false;
if (StringUtils.equalsIgnoreCase(view.getType(), "bar-range")) { //针对区间条形图进行处理
@ -777,7 +775,8 @@ public class ChartDataManage {
|| StringUtils.containsIgnoreCase(view.getType(), "gauge")
|| StringUtils.equalsIgnoreCase("liquid", view.getType())) {
mapChart = ChartDataBuild.transNormalChartData(xAxis, yAxis, view, data, isDrill);
} else if (StringUtils.containsIgnoreCase(view.getType(), "chart-mix")) {
} else if (StringUtils.containsIgnoreCase(view.getType(), "chart-mix")
|| StringUtils.containsIgnoreCase(view.getType(), "bidirectional-bar")) {
mapChart = ChartDataBuild.transMixChartDataAntV(xAxis, yAxis, view, data, isDrill);
} else if (StringUtils.containsIgnoreCase(view.getType(), "label")) {
mapChart = ChartDataBuild.transLabelChartData(xAxis, yAxis, view, data, isDrill);

View File

@ -681,6 +681,7 @@ export default {
chart_bar_stack_horizontal: '横向堆叠柱状图',
chart_percentage_bar_stack_horizontal: '横向百分比柱状图',
chart_bar_range: '区间条形图',
chart_bidirectional_bar: '对称柱状图',
chart_line: '基础折线图',
chart_area_stack: '堆叠折线图',
chart_pie: '饼图',

View File

@ -433,7 +433,7 @@ watch(
:change-model="chart.customStyle.xAxis"
@modelChange="val => onChangeXAxisForm(val, 'show')"
name="xAxis"
:title="t('chart.xAxis')"
:title="chart.type === 'bidirectional-bar' ? $t('chart.yAxis') : t('chart.xAxis')"
>
<x-axis-selector
class="attr-selector"
@ -468,7 +468,7 @@ watch(
:change-model="chart.customStyle.yAxis"
@modelChange="val => onChangeYAxisForm(val, 'show')"
name="yAxis"
:title="$t('chart.yAxis')"
:title="chart.type === 'bidirectional-bar' ? $t('chart.xAxis') : $t('chart.yAxis')"
>
<dual-y-axis-selector
class="attr-selector"

View File

@ -71,6 +71,10 @@ const init = () => {
}
state.subAxisForm.position = 'right'
state.subAxisForm.show = state.axisForm.show
if (chart.type === 'bidirectional-bar') {
state.axisForm.position = customStyle.yAxis.position
state.subAxisForm.position = customStyle.yAxisExt.position
}
}
}
@ -81,7 +85,14 @@ onMounted(() => {
<template>
<el-tabs v-model="activeName" id="axis-tabs" stretch>
<el-tab-pane :label="t('chart.yAxisLeft')" name="left">
<el-tab-pane
:label="
chart.type === 'bidirectional-bar'
? t('chart.text_pos_left') + t('chart.xAxis')
: t('chart.yAxisLeft')
"
name="left"
>
<dual-y-axis-selector-inner
style="margin-top: 8px"
v-if="state.axisForm"
@ -89,10 +100,18 @@ onMounted(() => {
:property-inner="propertyInner"
:themes="themes"
type="left"
:chart-type="chart.type"
@on-change-y-axis-form="changeAxisStyle"
/>
</el-tab-pane>
<el-tab-pane :label="t('chart.yAxisRight')" name="right">
<el-tab-pane
:label="
chart.type === 'bidirectional-bar'
? t('chart.text_pos_right') + t('chart.xAxis')
: t('chart.yAxisRight')
"
name="right"
>
<dual-y-axis-selector-inner
style="margin-top: 8px"
v-if="state.subAxisForm"
@ -100,6 +119,7 @@ onMounted(() => {
:property-inner="propertyInner"
:themes="themes"
type="right"
:chart-type="chart.type"
@on-change-y-axis-form="changeSubAxisStyle"
/>
</el-tab-pane>

View File

@ -13,6 +13,7 @@ const props = withDefaults(
form: any
propertyInner?: Array<string>
type?: 'left' | 'right'
chartType?: string
}>(),
{
themes: 'dark',
@ -92,8 +93,14 @@ onMounted(() => {
size="small"
@change="changeAxisStyle('position')"
>
<el-radio :effect="props.themes" label="left">{{ t('chart.text_pos_left') }}</el-radio>
<el-radio :effect="props.themes" label="right">{{ t('chart.text_pos_right') }}</el-radio>
<div v-if="chartType === 'bidirectional-bar'">
<el-radio :effect="props.themes" label="right">{{ t('chart.text_pos_top') }}</el-radio>
<el-radio :effect="props.themes" label="left">{{ t('chart.text_pos_bottom') }}</el-radio>
</div>
<div v-else>
<el-radio :effect="props.themes" label="left">{{ t('chart.text_pos_left') }}</el-radio>
<el-radio :effect="props.themes" label="right">{{ t('chart.text_pos_right') }}</el-radio>
</div>
</el-radio-group>
</el-form-item>
<el-form-item

View File

@ -118,8 +118,18 @@ onMounted(() => {
size="small"
@change="changeAxisStyle('position')"
>
<el-radio :effect="props.themes" label="top">{{ t('chart.text_pos_top') }}</el-radio>
<el-radio :effect="props.themes" label="bottom">{{ t('chart.text_pos_bottom') }}</el-radio>
<div v-if="chart.type === 'bidirectional-bar'">
<el-radio :effect="props.themes" label="top">{{ t('chart.text_pos_left') }}</el-radio>
<el-radio :effect="props.themes" label="bottom">{{
t('chart.text_pos_center')
}}</el-radio>
</div>
<div v-else>
<el-radio :effect="props.themes" label="top">{{ t('chart.text_pos_top') }}</el-radio>
<el-radio :effect="props.themes" label="bottom">{{
t('chart.text_pos_bottom')
}}</el-radio>
</div>
</el-radio-group>
</el-form-item>
<el-form-item

View File

@ -1205,6 +1205,13 @@ export const CHART_TYPE_CONFIGS = [
value: 'bar-range',
title: t('chart.chart_bar_range'),
icon: 'bar-range'
},
{
render: 'antv',
category: 'compare',
value: 'bidirectional-bar',
title: t('chart.chart_bidirectional_bar'),
icon: 'percentage-bar-stack-horizontal'
}
]
},

View File

@ -0,0 +1,409 @@
import {
G2PlotChartView,
G2PlotDrawOptions
} from '@/views/chart/components/js/panel/types/impl/g2plot'
import { cloneDeep, defaultTo, isEmpty, map } from 'lodash-es'
import {
getPadding,
getYAxis,
getYAxisExt,
setGradientColor
} from '@/views/chart/components/js/panel/common/common_antv'
import {
BidirectionalBar as G2BidirectionalBar,
BidirectionalBarOptions
} from '@antv/g2plot/esm/plots/bidirectional-bar'
import { flow, hexColorToRGBA, parseJson } from '@/views/chart/components/js/util'
import { useI18n } from '@/hooks/web/useI18n'
import { valueFormatter } from '@/views/chart/components/js/formatter'
const { t } = useI18n()
/**
* 对称柱状图
*/
export class BidirectionalHorizontalBar extends G2PlotChartView<
BidirectionalBarOptions,
G2BidirectionalBar
> {
axisConfig = {
...this['axisConfig'],
xAxis: {
name: `${t('chart.drag_block_type_axis')} / ${t('chart.dimension')}`,
type: 'd',
limit: 1
},
yAxis: {
name: `${t('chart.drag_block_value_axis')} / ${t('chart.quota')}`,
type: 'q',
limit: 1
},
yAxisExt: {
name: `${t('chart.drag_block_value_axis_ext')} / ${t('chart.quota')}`,
type: 'q',
limit: 1
}
}
axis: AxisType[] = ['xAxis', 'yAxis', 'yAxisExt', 'filter', 'drill', 'extLabel', 'extTooltip']
properties: EditorProperty[] = [
'background-overall-component',
'basic-style-selector',
'x-axis-selector',
'dual-y-axis-selector',
'title-selector',
'legend-selector',
'label-selector',
'tooltip-selector',
'jump-set',
'linkage'
]
propertyInner = {
'background-overall-component': ['all'],
'basic-style-selector': ['colors', 'alpha', 'gradient'],
'x-axis-selector': ['color', 'fontSize', 'position', 'axisLabel', 'axisLine', 'splitLine'],
'y-axis-selector': [
'name',
'position',
'color',
'fontSize',
'axisLabel',
'axisLine',
'splitLine',
'axisValue',
'axisLabelFormatter'
],
'title-selector': [
'title',
'fontSize',
'color',
'hPosition',
'isItalic',
'isBolder',
'remarkShow',
'fontFamily',
'letterSpace',
'fontShadow'
],
'legend-selector': ['icon', 'orient', 'fontSize', 'color', 'hPosition', 'vPosition'],
'function-cfg': ['slider', 'emptyDataStrategy'],
'label-selector': ['hPosition', 'seriesLabelFormatter'],
'tooltip-selector': ['fontSize', 'color', 'backgroundColor', 'seriesTooltipFormatter']
}
drawChart(drawOptions: G2PlotDrawOptions<G2BidirectionalBar>): G2BidirectionalBar {
const { chart, container, action } = drawOptions
if (!chart.data?.data?.length) {
return
}
// data
const data = cloneDeep(chart.data.data)
const data1 = defaultTo(data[0]?.data, [])
const data2 = map(defaultTo(data[1]?.data, []), d => {
return {
...d,
category: d.field,
value: data1.find(item => item.field === d.field)?.value,
valueExt: d.value
}
})
// options
const initOptions: BidirectionalBarOptions = {
xField: 'field',
data: data2,
xAxis: {
label: {
style: {}
},
position: 'bottom'
},
interactions: [{ type: 'active-region' }],
yField: ['value', 'valueExt'],
appendPadding: getPadding(chart)
}
const options = this.setupOptions(chart, initOptions)
// 开始渲染
const newChart = new G2BidirectionalBar(container, options)
newChart.on('interval:click', action)
newChart.on('element:click', ev => {
const sourceData = newChart.options.data.filter(
item =>
item.field === ev.data.data.field &&
item[ev.data.data['series-field-key']] === ev.data.data[ev.data.data['series-field-key']]
)
ev.data.data = {
...ev.data.data,
...sourceData[0]
}
})
return newChart
}
protected configBasicStyle(
chart: Chart,
options: BidirectionalBarOptions
): BidirectionalBarOptions {
const basicStyle = parseJson(chart.customAttr).basicStyle
if (basicStyle.gradient) {
const color = basicStyle.colors?.map((ele, index) => {
const tmp = hexColorToRGBA(ele, basicStyle.alpha)
return setGradientColor(tmp, true, 180 - index * 180)
})
options = {
...options,
color
}
}
return options
}
protected configXAxis(chart: Chart, options: BidirectionalBarOptions): BidirectionalBarOptions {
const tmpOptions = super.configXAxis(chart, options)
if (!tmpOptions.xAxis) {
return tmpOptions
}
if (tmpOptions.xAxis.label) {
tmpOptions.xAxis.label.style = {}
}
return tmpOptions
}
protected configTooltip(chart: Chart, options: BidirectionalBarOptions): BidirectionalBarOptions {
const customAttr: DeepPartial<ChartAttr> = parseJson(chart.customAttr)
const tooltipAttr = customAttr.tooltip
if (!tooltipAttr.show) {
return {
...options,
tooltip: false
}
}
const yAxis = cloneDeep(chart.yAxis)
const yAxisExt = cloneDeep(chart.yAxisExt)
const formatterMap = tooltipAttr.seriesTooltipFormatter
?.filter(i => i.show)
.reduce((pre, next) => {
pre[next.seriesId] = next
return pre
}, {}) as Record<string, SeriesFormatter>
const yaxisObj = item => {
const param = item.data
let yaxis = yAxis[0]
let axisType = 'yAxis'
if (param['series-field-key'] === 'valueExt') {
yaxis = yAxisExt[0]
axisType = 'yAxisExt'
}
return {
id: yaxis.id,
name: yaxis.name,
axisType: axisType,
value: param[param['series-field-key']]
}
}
const tooltip: BidirectionalBarOptions['tooltip'] = {
shared: true,
showTitle: true,
customItems(originalItems) {
if (!tooltipAttr.seriesTooltipFormatter?.length) {
return originalItems
}
const result = []
originalItems
.filter(item => {
const obj = yaxisObj(item)
return formatterMap[obj.id + '-' + obj.axisType]
})
.forEach(item => {
const obj = yaxisObj(item)
const formatter = formatterMap[obj.id + '-' + obj.axisType]
const value = valueFormatter(parseFloat(item.value as string), formatter.formatterCfg)
const name = isEmpty(formatter.chartShowName) ? formatter.name : formatter.chartShowName
result.push({ ...item, name, value })
})
return result
}
}
return {
...options,
tooltip
}
}
protected configLegend(chart: Chart, options: BidirectionalBarOptions): BidirectionalBarOptions {
const o = super.configLegend(chart, options)
if (o.legend) {
o.legend.itemName = {
formatter: (_text: string, _item: any, index: number) => {
const yaxis = chart.yAxis[0]
const yaxisExt = chart.yAxisExt[0]
if (index === 0) {
return yaxis.chartShowName ? yaxis.chartShowName : yaxis.name
}
return yaxisExt.chartShowName ? yaxisExt.chartShowName : yaxisExt.name
}
}
}
return o
}
protected configYAxis(chart: Chart, options: BidirectionalBarOptions): BidirectionalBarOptions {
const yAxis = getYAxis(chart)
let yAxisExt = getYAxisExt(chart)
const tempOption = {
...options
}
if (!yAxis) {
//左右轴都要隐藏
yAxisExt = false
tempOption['yAxis'] = {
value: false,
valueExt: false
}
} else {
tempOption['yAxis'] = {
value: undefined,
valueExt: undefined
}
}
// 处理横轴标题方向不对
if (yAxis && yAxis['title']) {
yAxis['title'].autoRotate = false
}
const yAxisTmp = parseJson(chart.customStyle).yAxis
if (yAxis['label']) {
yAxis['label'].formatter = value => {
return valueFormatter(value, yAxisTmp.axisLabelFormatter)
}
}
const axisValue = yAxisTmp.axisValue
if (!axisValue?.auto) {
tempOption.yAxis.value = {
...yAxis,
min: axisValue.min,
max: axisValue.max,
minLimit: axisValue.min,
maxLimit: axisValue.max,
tickCount: axisValue.splitCount
}
} else {
tempOption.yAxis.value = yAxis
}
const yAxisExtTmp = parseJson(chart.customStyle).yAxisExt
if (yAxisExt['label']) {
yAxisExt['label'].formatter = value => {
return valueFormatter(value, yAxisExtTmp.axisLabelFormatter)
}
}
const axisExtValue = yAxisExtTmp.axisValue
if (!axisExtValue?.auto) {
tempOption.yAxis.valueExt = {
...yAxisExt,
min: axisExtValue.min,
max: axisExtValue.max,
minLimit: axisExtValue.min,
maxLimit: axisExtValue.max,
tickCount: axisExtValue.splitCount
}
} else {
tempOption.yAxis.valueExt = yAxisExt
}
return tempOption
}
setupDefaultOptions(chart: ChartObj): ChartObj {
chart.customStyle.yAxis = {
...chart.customStyle.yAxis,
position: 'left'
}
chart.customStyle.yAxisExt = {
...chart.customStyle.yAxisExt,
position: 'left',
splitLine: chart.customStyle.yAxis.splitLine
}
chart.customAttr.label = {
...chart.customAttr.label,
position: 'right'
}
return chart
}
protected configLabel(chart: Chart, options: BidirectionalBarOptions): BidirectionalBarOptions {
let label
const yAxis = chart.yAxis
const yAxisExt = chart.yAxisExt
const labelAttr = parseJson(chart.customAttr).label
const formatterMap = labelAttr.seriesLabelFormatter?.reduce((pre, next) => {
pre[next.id] = next
return pre
}, {})
let customAttr: DeepPartial<ChartAttr>
if (chart.customAttr) {
customAttr = parseJson(chart.customAttr)
// label
if (customAttr.label) {
const l = customAttr.label
if (l.show) {
label = {
position: l.position,
layout: [{ type: 'limit-in-canvas' }],
style: {
fill: l.color,
fontSize: l.fontSize
},
formatter: param => {
let yaxis = yAxis[0]
let res = param.value
if (param['series-field-key'] === 'valueExt') {
yaxis = yAxisExt[0]
}
const value = param[param['series-field-key']]
const labelCfg = formatterMap?.[yaxis.id] as SeriesFormatter
if (yaxis.formatterCfg) {
res = valueFormatter(value, yaxis.formatterCfg)
}
if (!labelCfg) {
return res
}
if (!labelCfg.show) {
return
}
if (labelCfg) {
res = valueFormatter(value, labelCfg.formatterCfg)
} else {
res = valueFormatter(value, l.labelFormatter)
}
return res
}
}
} else {
label = false
}
}
}
return { ...options, label }
}
protected setupOptions(chart: Chart, options: BidirectionalBarOptions) {
return flow(
this.configTheme,
this.configBasicStyle,
this.configLabel,
this.configTooltip,
this.configLegend,
this.configXAxis,
this.configYAxis,
this.configAnalyse,
this.configSlider
)(chart, options)
}
constructor() {
super('bidirectional-bar', [])
}
}