From 37855f034d1b8fa2cc82be6b2b5ae5d22b6d49a9 Mon Sep 17 00:00:00 2001 From: ulleo Date: Mon, 15 Apr 2024 16:49:22 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=9B=BE=E8=A1=A8):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E5=8F=8C=E8=BD=B4=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #7377 #8710 --- core/core-frontend/src/locales/zh-CN.ts | 7 +- .../src/models/chart/editor.d.ts | 1 + .../components/editor/drag-item/QuotaItem.vue | 61 +++ .../editor/editor-style/ChartStyle.vue | 26 + .../components/DualYAxisSelector.vue | 144 +++++ .../components/DualYAxisSelectorInner.vue | 495 ++++++++++++++++++ .../editor-style/components/LabelSelector.vue | 19 +- .../views/chart/components/editor/index.vue | 108 +++- .../chart/components/editor/util/chart.ts | 24 +- .../panel/charts/others/chart-mix-common.ts | 69 +++ .../js/panel/charts/others/chart-mix.ts | 389 ++++++++++++++ .../components/js/panel/common/common_antv.ts | 92 ++++ 12 files changed, 1417 insertions(+), 18 deletions(-) create mode 100644 core/core-frontend/src/views/chart/components/editor/editor-style/components/DualYAxisSelector.vue create mode 100644 core/core-frontend/src/views/chart/components/editor/editor-style/components/DualYAxisSelectorInner.vue create mode 100644 core/core-frontend/src/views/chart/components/js/panel/charts/others/chart-mix-common.ts create mode 100644 core/core-frontend/src/views/chart/components/js/panel/charts/others/chart-mix.ts diff --git a/core/core-frontend/src/locales/zh-CN.ts b/core/core-frontend/src/locales/zh-CN.ts index 60bf1d1df4..b97ec283cc 100644 --- a/core/core-frontend/src/locales/zh-CN.ts +++ b/core/core-frontend/src/locales/zh-CN.ts @@ -595,6 +595,8 @@ export default { formatter_plc: '内容格式为空时,显示默认格式', xAxis: '横轴', yAxis: '纵轴', + yAxisLeft: '左纵轴', + yAxisRight: '右纵轴', position: '位置', rotate: '角度', name: '名称', @@ -752,6 +754,8 @@ export default { chart_style: '样式', drag_block_type_axis: '类别轴', drag_block_value_axis: '值轴', + drag_block_value_axis_left: '左值轴', + drag_block_value_axis_right: '右值轴', drag_block_table_data_column: '数据列', drag_block_pie_angel: '扇区角度', drag_block_pie_label: '扇区标签', @@ -782,7 +786,7 @@ export default { custom_case: '自定义', last_layer: '当前已经是最后一级', radar_size: '大小', - chart_mix: '组合图', + chart_mix: '柱线组合图', axis_value: '轴值', axis_value_min: '最小值', axis_value_max: '最大值', @@ -831,6 +835,7 @@ export default { chart_type_compare: '柱形图', chart_type_distribute: '分布图', chart_type_relation: '关系图', + chart_type_dual_axes: '双轴图', chart_type_space: '地图', preview: '上一步', next: '下一步', diff --git a/core/core-frontend/src/models/chart/editor.d.ts b/core/core-frontend/src/models/chart/editor.d.ts index 847d6498d3..64dd622bce 100644 --- a/core/core-frontend/src/models/chart/editor.d.ts +++ b/core/core-frontend/src/models/chart/editor.d.ts @@ -5,6 +5,7 @@ declare type EditorProperty = | 'tooltip-selector' | 'x-axis-selector' | 'y-axis-selector' + | 'dual-y-axis-selector' | 'title-selector' | 'legend-selector' | 'table-header-selector' diff --git a/core/core-frontend/src/views/chart/components/editor/drag-item/QuotaItem.vue b/core/core-frontend/src/views/chart/components/editor/drag-item/QuotaItem.vue index bd35b97d46..4d3922c712 100644 --- a/core/core-frontend/src/views/chart/components/editor/drag-item/QuotaItem.vue +++ b/core/core-frontend/src/views/chart/components/editor/drag-item/QuotaItem.vue @@ -178,6 +178,11 @@ const beforeSort = type => { } } +const switchChartType = param => { + item.value.chartType = param.type + emit('onQuotaItemChange', item.value) +} + const summary = param => { item.value.summary = param.type emit('onQuotaItemChange', item.value) @@ -189,6 +194,12 @@ const beforeSummary = type => { } } +const beforeSwitchType = type => { + return { + type: type + } +} + const showRename = () => { item.value.index = props.index item.value.renameType = props.type @@ -334,6 +345,56 @@ onMounted(() => { class="drop-style" :class="themes === 'dark' ? 'dark-dimension-quota' : ''" > + + + + + + + + {{ t('chart.chart_type') }} + + + + + + + + { state.initReady && emit('onChangeYAxisForm', val, prop) } +const onChangeYAxisExtForm = (val, prop) => { + console.log(val, prop) + state.initReady && emit('onChangeYAxisExtForm', val, prop) +} + const onTextChange = (val, prop) => { state.initReady && emit('onTextChange', val, prop) } @@ -442,6 +449,25 @@ watch( @onChangeYAxisForm="onChangeYAxisForm" /> + + + + diff --git a/core/core-frontend/src/views/chart/components/editor/editor-style/components/DualYAxisSelector.vue b/core/core-frontend/src/views/chart/components/editor/editor-style/components/DualYAxisSelector.vue new file mode 100644 index 0000000000..545c0549f4 --- /dev/null +++ b/core/core-frontend/src/views/chart/components/editor/editor-style/components/DualYAxisSelector.vue @@ -0,0 +1,144 @@ + + + + + diff --git a/core/core-frontend/src/views/chart/components/editor/editor-style/components/DualYAxisSelectorInner.vue b/core/core-frontend/src/views/chart/components/editor/editor-style/components/DualYAxisSelectorInner.vue new file mode 100644 index 0000000000..be6d31e6e9 --- /dev/null +++ b/core/core-frontend/src/views/chart/components/editor/editor-style/components/DualYAxisSelectorInner.vue @@ -0,0 +1,495 @@ + + + + + diff --git a/core/core-frontend/src/views/chart/components/editor/editor-style/components/LabelSelector.vue b/core/core-frontend/src/views/chart/components/editor/editor-style/components/LabelSelector.vue index a9f77c23ef..e1c1b0f3c4 100644 --- a/core/core-frontend/src/views/chart/components/editor/editor-style/components/LabelSelector.vue +++ b/core/core-frontend/src/views/chart/components/editor/editor-style/components/LabelSelector.vue @@ -4,7 +4,7 @@ import { useI18n } from '@/hooks/web/useI18n' import { COLOR_PANEL, DEFAULT_LABEL } from '@/views/chart/components/editor/util/chart' import { ElSpace } from 'element-plus-secondary' import { formatterType, unitType } from '../../../js/formatter' -import { defaultsDeep, cloneDeep, intersection } from 'lodash-es' +import { defaultsDeep, cloneDeep, intersection, union, defaultTo } from 'lodash-es' import { includesAny } from '../../util/StringUtils' import { fieldType } from '@/utils/attr' import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain' @@ -37,8 +37,12 @@ watch( }, { deep: true } ) +const yAxis = computed(() => { + return union(defaultTo(props.chart.yAxis, []), defaultTo(props.chart.yAxisExt, [])) +}) + watch( - [() => props.chart.yAxis, () => props.chart.type], + [() => yAxis.value, () => props.chart.type], () => { initSeriesLabel() }, @@ -46,10 +50,7 @@ watch( ) const curSeriesFormatter = ref>({}) const formatterEditable = computed(() => { - return ( - showProperty('seriesLabelFormatter') && - (props.chart.yAxis?.length || props.chart.yAxisExt?.length) - ) + return showProperty('seriesLabelFormatter') && yAxis.value?.length }) const formatterSelector = ref() // 初始化系列标签 @@ -59,17 +60,17 @@ const initSeriesLabel = () => { return } const formatter = state.labelForm.seriesLabelFormatter - const yAxis = props.chart.yAxis + const seriesAxisMap = formatter.reduce((pre, next) => { pre[next.id] = next return pre }, {}) formatter.splice(0, formatter.length) - if (!yAxis.length) { + if (!yAxis.value.length) { curSeriesFormatter.value = {} return } - const axisMap = yAxis.reduce((pre, next) => { + const axisMap = yAxis.value.reduce((pre, next) => { let tmp = { ...next, show: true, diff --git a/core/core-frontend/src/views/chart/components/editor/index.vue b/core/core-frontend/src/views/chart/components/editor/index.vue index 6434e2c98b..73bc57bfd6 100644 --- a/core/core-frontend/src/views/chart/components/editor/index.vue +++ b/core/core-frontend/src/views/chart/components/editor/index.vue @@ -42,7 +42,7 @@ import CustomSortEdit from '@/views/chart/components/editor/drag-item/components import { snapshotStoreWithOut } from '@/store/modules/data-visualization/snapshot' import CalcFieldEdit from '@/views/visualized/data/dataset/form/CalcFieldEdit.vue' import { getFieldName, guid } from '@/views/visualized/data/dataset/form/util' -import { cloneDeep, get } from 'lodash-es' +import { cloneDeep, forEach, get } from 'lodash-es' import { deleteField, saveField } from '@/api/dataset' import { getWorldTree } from '@/api/map' import chartViewManager from '@/views/chart/components/js/panel' @@ -494,12 +494,44 @@ const addAxis = (e, axis: AxisType) => { emitter.emit('removeAxis', { axisType: 'yAxis', axis, editType: 'remove' }) } } - if (view.value.type === 'indicator') { + if (view.value.type === 'chart-mix') { + if (axis === 'yAxis') { + if (view.value.yAxisExt.length > 0) { + const chartType = view.value.yAxisExt[0].chartType + forEach(view.value.yAxis, axis => { + if (chartType === 'bar') { + axis.chartType = 'line' + } else if (chartType === 'line') { + axis.chartType = 'bar' + } + }) + } + } else if (axis === 'yAxisExt') { + if (view.value.yAxis.length > 0) { + const chartType = view.value.yAxis[0].chartType + forEach(view.value.yAxisExt, axis => { + if (chartType === 'bar') { + axis.chartType = 'line' + } else if (chartType === 'line') { + axis.chartType = 'bar' + } + }) + } + } + } + + if (view.value.type === 'indicator' || view.value.type === 'chart-mix') { if (view.value?.yAxis?.length > 1) { const axis = view.value.yAxis.splice(1) emitter.emit('removeAxis', { axisType: 'yAxis', axis, editType: 'remove' }) } } + if (view.value.type === 'chart-mix') { + if (view.value?.yAxisExt?.length > 1) { + const axis = view.value.yAxisExt.splice(1) + emitter.emit('removeAxis', { axisType: 'yAxisExt', axis, editType: 'remove' }) + } + } } const addXaxis = e => { @@ -518,6 +550,10 @@ const addYaxis = e => { addAxis(e, 'yAxis') } +const addYaxisExt = e => { + addAxis(e, 'yAxisExt') +} + const addExtBubble = e => { addAxis(e, 'extBubble') } @@ -748,6 +784,12 @@ const onChangeYAxisForm = val => { renderChart(view.value) } +const onChangeYAxisExtForm = val => { + console.log('onChangeYAxisExtForm', val) + view.value.customStyle.yAxisExt = val + renderChart(view.value) +} + const onChangeMiscStyleForm = val => { view.value.customStyle.misc = val renderChart(view.value) @@ -822,7 +864,15 @@ const closeRename = () => { } const removeItems = ( - _type: 'xAxis' | 'xAxisExt' | 'extStack' | 'yAxis' | 'extBubble' | 'customFilter' | 'drillFields' + _type: + | 'xAxis' + | 'xAxisExt' + | 'extStack' + | 'yAxis' + | 'yAxisExt' + | 'extBubble' + | 'customFilter' + | 'drillFields' ) => { recordSnapshotInfo('calcData') let axis = [] @@ -839,6 +889,9 @@ const removeItems = ( case 'yAxis': axis = view.value.yAxis?.splice(0) break + case 'yAxisExt': + axis = view.value.yAxisExt?.splice(0) + break case 'extBubble': axis = view.value.extBubble?.splice(0) break @@ -1516,6 +1569,54 @@ const onRefreshChange = val => { + + +
+ + {{ chartViewInstance.axisConfig.yAxisExt.name }} + + + + + + +
+ + + + +
@@ -1792,6 +1893,7 @@ const onRefreshChange = val => { @onTooltipChange="onTooltipChange" @onChangeXAxisForm="onChangeXAxisForm" @onChangeYAxisForm="onChangeYAxisForm" + @onChangeYAxisExtForm="onChangeYAxisExtForm" @onTextChange="onTextChange" @onIndicatorChange="onIndicatorChange" @onIndicatorNameChange="onIndicatorNameChange" diff --git a/core/core-frontend/src/views/chart/components/editor/util/chart.ts b/core/core-frontend/src/views/chart/components/editor/util/chart.ts index 589a241cfb..954c6fec97 100644 --- a/core/core-frontend/src/views/chart/components/editor/util/chart.ts +++ b/core/core-frontend/src/views/chart/components/editor/util/chart.ts @@ -568,7 +568,7 @@ export const DEFAULT_YAXIS_EXT_STYLE: ChartAxisStyle = { } }, splitLine: { - show: true, + show: false, lineStyle: { color: '#cccccc', width: 1, @@ -577,10 +577,10 @@ export const DEFAULT_YAXIS_EXT_STYLE: ChartAxisStyle = { }, axisValue: { auto: true, - min: null, - max: null, - split: null, - splitCount: null + min: 10, + max: 100, + split: 10, + splitCount: 10 }, axisLabelFormatter: { type: 'auto', @@ -1297,6 +1297,20 @@ export const CHART_TYPE_CONFIGS = [ } ] }, + { + category: 'dual_axes', + title: t('chart.chart_type_dual_axes'), + display: 'show', + details: [ + { + render: 'antv', + category: 'dual_axes', + value: 'chart-mix', + title: t('chart.chart_mix'), + icon: 'chart-mix' + } + ] + }, { category: 'other', title: '富文本', diff --git a/core/core-frontend/src/views/chart/components/js/panel/charts/others/chart-mix-common.ts b/core/core-frontend/src/views/chart/components/js/panel/charts/others/chart-mix-common.ts new file mode 100644 index 0000000000..c7bdb86726 --- /dev/null +++ b/core/core-frontend/src/views/chart/components/js/panel/charts/others/chart-mix-common.ts @@ -0,0 +1,69 @@ +export const CHART_MIX_EDITOR_PROPERTY: EditorProperty[] = [ + 'background-overall-component', + 'basic-style-selector', + 'x-axis-selector', + 'dual-y-axis-selector', + 'title-selector', + 'legend-selector', + 'label-selector', + 'tooltip-selector', + 'assist-line', + 'function-cfg', + 'jump-set', + 'linkage' +] +export const CHART_MIX_EDITOR_PROPERTY_INNER: EditorPropertyInner = { + 'background-overall-component': ['all'], + 'label-selector': ['fontSize', 'color'], + 'tooltip-selector': ['fontSize', 'color', 'backgroundColor'], + 'basic-style-selector': [ + 'colors', + 'alpha', + 'lineWidth', + 'lineSymbol', + 'lineSymbolSize', + 'lineSmooth' + ], + 'x-axis-selector': [ + 'name', + 'color', + 'fontSize', + 'position', + 'axisLabel', + 'axisLine', + 'splitLine' + ], + 'y-axis-selector': [ + 'name', + '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'] +} + +export const CHART_MIX_AXIS_TYPE: AxisType[] = [ + 'xAxis', + 'yAxis', + 'drill', + 'filter', + 'extLabel', + 'extTooltip' +] diff --git a/core/core-frontend/src/views/chart/components/js/panel/charts/others/chart-mix.ts b/core/core-frontend/src/views/chart/components/js/panel/charts/others/chart-mix.ts new file mode 100644 index 0000000000..7f011112f4 --- /dev/null +++ b/core/core-frontend/src/views/chart/components/js/panel/charts/others/chart-mix.ts @@ -0,0 +1,389 @@ +import { + G2PlotChartView, + G2PlotDrawOptions +} from '@/views/chart/components/js/panel/types/impl/g2plot' +import { DualAxes, DualAxesOptions } from '@antv/g2plot/esm/plots/dual-axes' +import { getLabel, getPadding, getYAxis, getYAxisExt } from '../../common/common_antv' +import { flow, hexColorToRGBA, parseJson } from '@/views/chart/components/js/util' +import { cloneDeep, isEmpty, defaultTo, map } from 'lodash-es' +import { valueFormatter } from '@/views/chart/components/js/formatter' +import { + CHART_MIX_AXIS_TYPE, + CHART_MIX_EDITOR_PROPERTY, + CHART_MIX_EDITOR_PROPERTY_INNER +} from './chart-mix-common' +import { Datum } from '@antv/g2plot/esm/types/common' +import { useI18n } from '@/hooks/web/useI18n' +import { DEFAULT_LABEL } from '@/views/chart/components/editor/util/chart' + +const { t } = useI18n() +const DEFAULT_DATA = [] + +/** + * 柱线混合图 + */ +export class ColumnLineMix extends G2PlotChartView { + properties = CHART_MIX_EDITOR_PROPERTY + propertyInner = { + ...CHART_MIX_EDITOR_PROPERTY_INNER, + 'label-selector': ['vPosition', 'seriesLabelFormatter'], + 'tooltip-selector': [ + ...CHART_MIX_EDITOR_PROPERTY_INNER['tooltip-selector'], + 'seriesTooltipFormatter' + ] + } + axis: AxisType[] = [...CHART_MIX_AXIS_TYPE, 'yAxisExt'] + axisConfig = { + ...this['axisConfig'], + yAxis: { + name: `${t('chart.drag_block_value_axis_left')} / ${t('chart.quota')}`, + type: 'q' + }, + yAxisExt: { + name: `${t('chart.drag_block_value_axis_right')} / ${t('chart.quota')}`, + type: 'q' + } + } + drawChart(drawOptions: G2PlotDrawOptions): DualAxes { + const { chart, action, container } = drawOptions + if (!chart.data.data?.length) { + return + } + const data = cloneDeep(chart.data.data) + + const data1Type = data[0]?.type === 'bar' ? 'column' : data[0]?.type + const data2Type = data[1]?.type === 'bar' ? 'column' : data[1]?.type + + const data1 = defaultTo(data[0]?.data, []) + const data2 = map(defaultTo(data[1]?.data, []), d => { + return { + ...d, + valueExt: d.value + } + }) + // custom color + const customAttr = parseJson(chart.customAttr) + const color = customAttr.basicStyle.colors + // options + const initOptions: DualAxesOptions = { + data: [data1, data2], + xField: 'field', + yField: ['value', 'valueExt'], //这里不能设置成一样的 + appendPadding: getPadding(chart), + color, + geometryOptions: [ + { + geometry: data1Type + }, + { + geometry: data2Type + } + ], + interactions: [ + { + type: 'legend-active', + cfg: { + start: [{ trigger: 'legend-item:mouseenter', action: ['element-active:reset'] }], + end: [{ trigger: 'legend-item:mouseleave', action: ['element-active:reset'] }] + } + }, + { + type: 'legend-filter', + cfg: { + start: [ + { + trigger: 'legend-item:click', + action: [ + 'list-unchecked:toggle', + 'data-filter:filter', + 'element-active:reset', + 'element-highlight:reset' + ] + } + ] + } + }, + { + type: 'tooltip', + cfg: { + start: [{ trigger: 'point:mousemove', action: 'tooltip:show' }], + end: [{ trigger: 'point:mouseleave', action: 'tooltip:hide' }] + } + }, + { + type: 'active-region', + cfg: { + start: [{ trigger: 'element:mousemove', action: 'active-region:show' }], + end: [{ trigger: 'element:mouseleave', action: 'active-region:hide' }] + } + } + ] + } + const options = this.setupOptions(chart, initOptions) + // 开始渲染 + const newChart = new DualAxes(container, options) + + newChart.on('point:click', action) + + return newChart + } + + protected configLabel(chart: Chart, options: DualAxesOptions): DualAxesOptions { + const tempLabel = getLabel(chart) + const tmpOption = { ...options } + if (!tempLabel) { + if (tmpOption.geometryOptions) { + tmpOption.geometryOptions[0].label = false + tmpOption.geometryOptions[1].label = false + } + return tmpOption + } + + const labelAttr = parseJson(chart.customAttr).label + const formatterMap = labelAttr.seriesLabelFormatter?.reduce((pre, next) => { + pre[next.id] = next + return pre + }, {}) + tempLabel.style.fill = DEFAULT_LABEL.color + const label = { + fields: [], + ...tempLabel, + offsetY: -8, + formatter: (data: Datum) => { + if (!labelAttr.seriesLabelFormatter?.length) { + return data.value + } + const labelCfg = formatterMap?.[data.quotaList[0].id] as SeriesFormatter + if (!labelCfg) { + return data.value + } + if (!labelCfg.show) { + return + } + const value = valueFormatter(data.value, labelCfg.formatterCfg) + const group = new G2PlotChartView.engine.Group({}) + group.addShape({ + type: 'text', + attrs: { + x: 0, + y: 0, + text: value, + textAlign: 'start', + textBaseline: 'top', + fontSize: labelCfg.fontSize, + fill: labelCfg.color + } + }) + return group + } + } + if (tmpOption.geometryOptions) { + tmpOption.geometryOptions[0].label = label + tmpOption.geometryOptions[1].label = label + } + return tmpOption + } + + protected configBasicStyle(chart: Chart, options: DualAxesOptions): DualAxesOptions { + // size + const customAttr: DeepPartial = parseJson(chart.customAttr) + const s = JSON.parse(JSON.stringify(customAttr.basicStyle)) + const smooth = s.lineSmooth + const point = { + size: s.lineSymbolSize, + shape: s.lineSymbol + } + const lineStyle = { + lineWidth: s.lineWidth + } + const tempOption = { + ...options, + smooth, + point, + lineStyle + } + if (tempOption.geometryOptions) { + tempOption.geometryOptions[0].smooth = smooth + tempOption.geometryOptions[0].point = point + tempOption.geometryOptions[0].lineStyle = lineStyle + + tempOption.geometryOptions[1].smooth = smooth + tempOption.geometryOptions[1].point = point + tempOption.geometryOptions[1].lineStyle = lineStyle + } + return tempOption + } + + protected configCustomColors(chart: Chart, options: DualAxesOptions): DualAxesOptions { + const basicStyle = parseJson(chart.customAttr).basicStyle + const color = basicStyle.colors.map(item => hexColorToRGBA(item, basicStyle.alpha)) + return { + ...options, + color + } + } + + protected configYAxis(chart: Chart, options: DualAxesOptions): DualAxesOptions { + const yAxis = getYAxis(chart) + const yAxisExt = getYAxisExt(chart) + + const tempOption = { + ...options + } + + if (!yAxis) { + //左右轴都要隐藏 + tempOption.yAxis = { + value: false, + valueExt: false + } + } else { + tempOption.yAxis = { + value: undefined, + valueExt: undefined + } + } + + 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 + } + + protected configTooltip(chart: Chart, options: DualAxesOptions): DualAxesOptions { + const customAttr: DeepPartial = parseJson(chart.customAttr) + const tooltipAttr = customAttr.tooltip + if (!tooltipAttr.show) { + return { + ...options, + tooltip: false + } + } + const xAxisExt = chart.xAxisExt + const formatterMap = tooltipAttr.seriesTooltipFormatter + ?.filter(i => i.show) + .reduce((pre, next) => { + pre[next.id] = next + return pre + }, {}) as Record + const tooltip: DualAxesOptions['tooltip'] = { + shared: true, + showTitle: true, + customItems(originalItems) { + if (!tooltipAttr.seriesTooltipFormatter?.length) { + return originalItems + } + const head = originalItems[0] + // 非原始数据 + if (!head.data.quotaList) { + return originalItems + } + const result = [] + originalItems + .filter(item => formatterMap[item.data.quotaList[0].id]) + .forEach(item => { + const formatter = formatterMap[item.data.quotaList[0].id] + const value = valueFormatter(parseFloat(item.value as string), formatter.formatterCfg) + let name = isEmpty(formatter.chartShowName) ? formatter.name : formatter.chartShowName + if (xAxisExt?.length > 0) { + name = item.data.category + } + result.push({ ...item, name, value }) + }) + head.data.dynamicTooltipValue?.forEach(item => { + const formatter = formatterMap[item.fieldId] + if (formatter) { + const value = valueFormatter(parseFloat(item.value), formatter.formatterCfg) + const name = isEmpty(formatter.chartShowName) ? formatter.name : formatter.chartShowName + result.push({ color: 'grey', name, value }) + } + }) + return result + } + } + return { + ...options, + tooltip + } + } + + protected configLegend(chart: Chart, options: DualAxesOptions): DualAxesOptions { + const o = super.configLegend(chart, options) + if (o.legend) { + const data = chart.data.data + o.legend.itemName = { + formatter: (text: string, item: any, index: number) => { + let name = undefined + if (index === 0 && text === 'value') { + name = data[0]?.name + } else if (index === 1 && text === 'valueExt') { + name = data[1]?.name + } + if (name === undefined) { + return text + } else { + return name + } + } + } + } + return o + } + + protected setupOptions(chart: Chart, options: DualAxesOptions): DualAxesOptions { + return flow( + this.configTheme, + this.configLabel, + this.configTooltip, + this.configBasicStyle, + this.configCustomColors, + this.configLegend, + this.configXAxis, + this.configYAxis, + this.configSlider, + this.configAnalyse, + this.configEmptyDataStrategy + )(chart, options) + } + + constructor(name = 'chart-mix') { + super(name, DEFAULT_DATA) + } +} diff --git a/core/core-frontend/src/views/chart/components/js/panel/common/common_antv.ts b/core/core-frontend/src/views/chart/components/js/panel/common/common_antv.ts index 22d6887aaf..cd9f18bf51 100644 --- a/core/core-frontend/src/views/chart/components/js/panel/common/common_antv.ts +++ b/core/core-frontend/src/views/chart/components/js/panel/common/common_antv.ts @@ -506,6 +506,98 @@ export function getYAxis(chart: Chart) { return axis } +export function getYAxisExt(chart: Chart) { + let axis: Record | boolean = {} + const yAxis = parseJson(chart.customStyle).yAxisExt + if (!yAxis.show) { + return false + } + const title = + yAxis.name && yAxis.name !== '' + ? { + text: yAxis.name, + style: { + fill: yAxis.color, + fontSize: yAxis.fontSize + }, + spacing: 8 + } + : null + const grid = yAxis.splitLine.show + ? { + line: { + style: { + stroke: yAxis.splitLine.lineStyle.color, + lineWidth: yAxis.splitLine.lineStyle.width + } + } + } + : null + const axisCfg = yAxis.axisLine ? yAxis.axisLine : DEFAULT_YAXIS_STYLE.axisLine + const line = axisCfg.show + ? { + style: { + stroke: axisCfg.lineStyle.color, + lineWidth: axisCfg.lineStyle.width + } + } + : null + const tickLine = axisCfg.show + ? { + style: { + stroke: axisCfg.lineStyle.color + } + } + : null + const rotate = yAxis.axisLabel.rotate + let textAlign = 'end' + let textBaseline = 'middle' + if (yAxis.position === 'right') { + textAlign = 'start' + if (Math.abs(rotate) > 75) { + textAlign = 'center' + } + if (rotate > 75) { + textBaseline = 'bottom' + } + if (rotate < -75) { + textBaseline = 'top' + } + } + if (yAxis.position === 'left') { + if (Math.abs(rotate) > 75) { + textAlign = 'center' + } + if (rotate > 75) { + textBaseline = 'top' + } + if (rotate < -75) { + textBaseline = 'bottom' + } + } + const label = yAxis.axisLabel.show + ? { + rotate: (rotate * Math.PI) / 180, + style: { + fill: yAxis.axisLabel.color, + fontSize: yAxis.axisLabel.fontSize, + textBaseline, + textAlign + } + } + : null + + axis = { + position: yAxis.position, + title, + grid, + label, + line, + tickLine + } + return axis +} + export function getSlider(chart: Chart) { let cfg const senior = parseJson(chart.senior)