From 55c1943a148409ffdd298b3aaf23ae04fbf3b0af Mon Sep 17 00:00:00 2001 From: wisonic-s Date: Mon, 28 Jul 2025 18:18:29 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=9B=BE=E8=A1=A8):=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=A0=86=E5=8F=A0=E6=9F=B1=E7=BA=BF=E7=BB=84=E5=90=88=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../js/panel/charts/g2/mix/mix-stack.ts | 979 ++++++++++++++++++ 1 file changed, 979 insertions(+) create mode 100644 core/core-frontend/src/views/chart/components/js/panel/charts/g2/mix/mix-stack.ts diff --git a/core/core-frontend/src/views/chart/components/js/panel/charts/g2/mix/mix-stack.ts b/core/core-frontend/src/views/chart/components/js/panel/charts/g2/mix/mix-stack.ts new file mode 100644 index 0000000000..cfe6894388 --- /dev/null +++ b/core/core-frontend/src/views/chart/components/js/panel/charts/g2/mix/mix-stack.ts @@ -0,0 +1,979 @@ +import { G2ChartView, G2DrawOptions } from '../../../types/impl/g2' +import { flow, hexColorToRGBA, parseJson } from '@/views/chart/components/js/util' +import { cloneDeep, defaultsDeep, isEmpty, merge, random } from 'lodash-es' +import { valueFormatter } from '@/views/chart/components/js/formatter' +import { useI18n } from '@/hooks/web/useI18n' +import { Chart as G2Chart, G2Spec } from '@antv/g2' +import { + DEFAULT_BASIC_STYLE, + DEFAULT_YAXIS_STYLE +} from '@/views/chart/components/editor/util/chart' +import { setGradientColor, TOOLTIP_ITEM_TPL, TOOLTIP_TITLE_TPL } from '../../../common/common_antv' +import { CHART_MIX_EDITOR_PROPERTY, CHART_MIX_EDITOR_PROPERTY_INNER } from './common' +import { registerSymbol, Symbols } from '@antv/g2/esm/utils/marker' + +const { t } = useI18n() +/** + * 柱线混合图 + */ +export class StackLineMix extends G2ChartView { + properties: EditorProperty[] = CHART_MIX_EDITOR_PROPERTY + propertyInner: EditorPropertyInner = { + ...CHART_MIX_EDITOR_PROPERTY_INNER, + 'legend-selector': ['icon', 'fontSize', 'color', 'hPosition', 'vPosition'], + 'label-selector': ['vPosition', 'seriesLabelFormatter'], + 'tooltip-selector': [ + 'fontSize', + 'color', + 'backgroundColor', + 'show', + 'seriesTooltipFormatter', + 'carousel' + ] + } + + axis: AxisType[] = [ + 'xAxis', + 'yAxis', + 'drill', + 'filter', + 'extLabel', + 'extTooltip', + 'xAxisExtRight', + 'yAxisExt', + 'extStack' + ] + + axisConfig: AxisConfig = { + xAxis: { + name: `${t('chart.drag_block_type_axis')} / ${t('chart.dimension')}`, + type: 'd' + }, + extStack: { + name: `${t('chart.stack_item')} / ${t('chart.dimension')}`, + type: 'd', + limit: 1, + allowEmpty: true + }, + yAxis: { + name: `${t('chart.drag_block_value_axis_left')} / ${t('chart.column_quota')}`, + limit: 1, + type: 'q' + }, + extBubble: { + //用这个字段存放右轴分类 + name: `${t('chart.drag_block_type_axis_right')} / ${t('chart.dimension')}`, + limit: 1, + type: 'd', + allowEmpty: true + }, + yAxisExt: { + name: `${t('chart.drag_block_value_axis_right')} / ${t('chart.line_quota')}`, + limit: 1, + type: 'q', + allowEmpty: true + } + } + + EMPTY_MARKER = () => [] + + protected getLeftType(): string { + return 'column' + } + protected getRightType(): string { + return 'line' + } + + async drawChart(drawOptions: G2DrawOptions): Promise { + const { chart, action, container } = drawOptions + chart.container = container + if (!chart.data?.left?.data?.length && !chart.data?.right?.data?.length) { + return + } + const [left] = cloneDeep(chart.data?.left?.data) + const [right] = cloneDeep(chart.data?.right?.data) + + // options + const initOptions: G2Spec = { + type: 'spaceFlex', + ratio: [1], + direction: 'col', + autoFit: true, + children: [ + { + type: 'view', + key: 'chart', + legend: false, + children: [ + { + type: 'interval', + data: left.data, + encode: { + x: 'field', + y: 'value', + color: 'category' + }, + axis: { + y: { + position: 'left' + } + }, + scale: { + y: { + key: 'left' + } + }, + transform: [{ type: 'stackY' }] + }, + { + type: 'line', + data: right.data, + encode: { + x: 'field', + y: 'value', + series: 'category', + color: 'category' + }, + scale: { + y: { + key: 'right' + } + }, + axis: { + y: { + position: 'right' + } + }, + style: { + connect: false + } + }, + { + type: 'point', + data: right.data, + encode: { + x: 'field', + y: 'value', + color: 'category' + }, + scale: { + y: { + key: 'right' + } + }, + tooltip: false + } + ] + } + ] + } + const newChart = new G2Chart({ container }) + const options = this.setupOptions(chart, initOptions, { + chartObj: newChart, + leftData: left.data, + rightData: right.data + }) + + newChart.on('point:click', action) + newChart.on('interval:click', action) + newChart.options(options) + // extremumEvt(newChart, chart, options, container) + // configPlotTooltipEvent(chart, newChart) + return newChart + } + + protected configBasicStyle(chart: Chart, options: G2Spec, context: Record): G2Spec { + const { basicStyle } = parseJson(chart.customAttr) + const leftCat = [] + const { extStack, extBubble, yAxis, yAxisExt } = chart + const [intervalMark, lineMark, pointMark] = options.children[0].children + if (!extStack?.length) { + leftCat.push(yAxis[0]?.chartShowName ?? yAxis[0]?.name) + } else { + const { leftData } = context + leftData.forEach(d => d.category && !leftCat.includes(d.category) && leftCat.push(d.category)) + } + const leftColorMap = leftCat.reduce((acc, cur, index) => { + acc[cur] = hexColorToRGBA( + basicStyle.colors[index % basicStyle.colors.length], + basicStyle.alpha + ) + return acc + }, {}) + if (basicStyle.seriesColor?.length) { + if (!extStack?.length) { + const ySeries = basicStyle.seriesColor.find(s => s.id === yAxis[0]?.id) + if (ySeries) { + leftColorMap[yAxis[0]?.chartShowName ?? yAxis[0]?.name] = hexColorToRGBA( + ySeries.color, + basicStyle.alpha + ) + } + } else { + basicStyle.seriesColor.forEach(s => { + if (leftColorMap[s.id]) { + leftColorMap[s.id] = hexColorToRGBA(s.color, basicStyle.alpha) + } + }) + } + } + const leftRelations = [] + Object.entries(leftColorMap).forEach(([key, value]) => { + if (basicStyle.gradient) { + value = setGradientColor(value as string, true, 270) + } + leftRelations.push([key, value]) + }) + const leftRange = basicStyle.colors.map(c => { + const color = hexColorToRGBA(c, basicStyle.alpha) + if (basicStyle.gradient) { + return setGradientColor(color, true, 270) + } + return color + }) + const leftColorScale = { + scale: { + color: { + key: 'left-color', + type: 'ordinal', + independent: true, + range: leftRange, + relations: leftRelations + } + } + } + merge(intervalMark, leftColorScale) + const rightCat = [] + if (!extBubble?.length) { + rightCat.push(yAxisExt[0]?.chartShowName ?? yAxisExt[0]?.name) + } else { + const { rightData } = context + rightData.forEach( + d => d.category && !rightCat.includes(d.category) && rightCat.push(d.category) + ) + } + const rightColorMap = rightCat.reduce((acc, cur, index) => { + acc[cur] = hexColorToRGBA( + basicStyle.subColors[index % basicStyle.subColors.length], + basicStyle.subAlpha + ) + return acc + }, {}) + if (basicStyle.subSeriesColor?.length) { + if (!extBubble?.length) { + const yExtSeries = basicStyle.subSeriesColor.find(s => s.id === yAxisExt[0]?.id) + if (yExtSeries) { + rightColorMap[yAxisExt[0]?.chartShowName ?? yAxisExt[0]?.name] = hexColorToRGBA( + yExtSeries.color, + basicStyle.subAlpha + ) + } + } else { + basicStyle.subSeriesColor.forEach(s => { + if (rightColorMap[s.id]) { + rightColorMap[s.id] = hexColorToRGBA(s.color, basicStyle.subAlpha) + } + }) + } + } + const rightRelations = [] + Object.entries(rightColorMap).forEach(entry => { + rightRelations.push(entry) + }) + const rightRange = basicStyle.subColors.map(c => hexColorToRGBA(c, basicStyle.subAlpha)) + const rightColorScale = { + scale: { + color: { + type: 'ordinal', + independent: true, + domain: rightCat, + range: rightRange, + relations: rightRelations + } + } + } + merge(lineMark, rightColorScale, { + scale: { + series: { + type: 'ordinal', + independent: true, + domain: rightCat, + range: rightRange, + relations: rightRelations + } + } + }) + merge(pointMark, rightColorScale) + merge(intervalMark, { + style: { + columnWidthRatio: basicStyle.columnWidthRatio / 100 + } + }) + if (basicStyle.radiusColumnBar === 'roundAngle') { + merge(intervalMark, { + style: { + radius: 20 + } + }) + } + if (basicStyle.radiusColumnBar === 'topRoundAngle') { + merge(intervalMark, { + style: { + radiusTopLeft: 20, + radiusTopRight: 20 + } + }) + } + merge(lineMark, { + style: { + lineWidth: basicStyle.lineWidth + }, + encode: { + shape: basicStyle.lineSmooth ? 'smooth' : 'line' + } + }) + merge(pointMark, { + encode: { + shape: basicStyle.lineSymbol, + size: basicStyle.lineSymbolSize ? basicStyle.lineSymbolSize : 0.01 + }, + style: { + opacity: basicStyle.lineSymbolSize === 0 ? 0 : 1, + fillOpacity: basicStyle.lineSymbolSize === 0 ? 0 : 1, + strokeOpacity: basicStyle.lineSymbolSize === 0 ? 0 : 1, + lineWidth: 0 + } + }) + return options + } + + protected configLegend(chart: Chart, options: G2Spec): G2Spec { + const { legend } = parseJson(chart.customStyle) + if (!legend.show) { + return options + } + const [intervalMark, lineMark] = options.children[0].children + const leftRelations = intervalMark.scale.color.relations + const rightRelations = lineMark.scale.color.relations + const unionRelations = [...leftRelations, ...rightRelations] + const legendMark = { + position: 'top', + type: 'legends', + key: 'legend', + scale: { + color: { + type: 'ordinal', + domain: [], + range: [], + relations: unionRelations + } + }, + layout: { + justifyContent: 'center', + alignItems: 'center' + }, + itemMarker: legend.icon, + itemMarkerSize: legend.size, + itemLabelFontSize: legend.fontSize, + itemLabelFill: legend.color + } + unionRelations.forEach(([key, value]) => { + legendMark.scale.color.domain.push(key) + legendMark.scale.color.range.push(value) + }) + if (legend.hPosition === 'center') { + options.direction = 'col' + legendMark.maxRows = 1 + if (legend.vPosition === 'top') { + legendMark.position = 'top' + options.ratio = [1, 20] + options.children.unshift(legendMark) + } + if (legend.vPosition === 'bottom') { + legendMark.position = 'bottom' + options.ratio = [20, 1] + options.children.push(legendMark) + } + return options + } + if (legend.vPosition === 'center') { + options.direction = 'row' + legendMark.maxCols = 1 + if (legend.hPosition === 'left') { + legendMark.position = 'left' + options.ratio = [1, 20] + options.children.unshift(legendMark) + } + if (legend.hPosition === 'right') { + legendMark.position = 'right' + options.ratio = [20, 1] + options.children.push(legendMark) + } + return options + } + legendMark.maxRows = 1 + if (legend.vPosition === 'top') { + legendMark.position = 'top' + options.ratio = [1, 20] + options.children.unshift(legendMark) + } + if (legend.vPosition === 'bottom') { + legendMark.position = 'bottom' + options.ratio = [20, 1] + options.children.push(legendMark) + } + if (legend.hPosition === 'left') { + legendMark.layout.justifyContent = 'flex-start' + } + if (legend.hPosition === 'right') { + legendMark.layout.justifyContent = 'flex-end' + } + return options + } + + protected configLabel(chart: Chart, options: G2Spec): G2Spec { + const { label } = parseJson(chart.customAttr) + if (!label.show) { + return options + } + const seriesMap = label.seriesLabelFormatter?.reduce((acc, cur) => { + acc[cur.id] = cur + return acc + }, {}) + const labelOpt = { + labels: [ + { + text: d => { + if (!label.seriesLabelFormatter?.length) { + return d.value + } + const labelCfg = seriesMap?.[d.quotaList[0].id] as SeriesFormatter + if (!labelCfg) { + return d.value + } + if (!labelCfg.show) { + return '' + } + return valueFormatter(d.value, labelCfg.formatterCfg) + }, + style: { + fillOpacity: 1, + fontSize: d => { + if (!label.seriesLabelFormatter?.length) { + return 12 + } + const labelCfg = seriesMap?.[d.quotaList[0].id] as SeriesFormatter + if (!labelCfg) { + return 12 + } + if (!labelCfg.show) { + return 0 + } + return labelCfg.fontSize + }, + fill: d => { + if (!label.seriesLabelFormatter?.length) { + return 'black' + } + const labelCfg = seriesMap?.[d.quotaList[0].id] as SeriesFormatter + if (!labelCfg?.show) { + return 'black' + } + return labelCfg.color + }, + position: label.position === 'middle' ? 'inside' : label.position + }, + textBaseline: { + top: 'bottom', + middle: 'middle', + bottom: 'top' + }[label.position], + transform: label.fullDisplay + ? [{ type: 'exceedAdjust' }] + : [{ type: 'overlapHide' }, { type: 'overlapDodgeY' }, { type: 'exceedAdjust' }], + fontFamily: chart.fontFamily + } + ] + } + const [intervalMark, _, pointMark] = options.children.find(c => c.key === 'chart').children + if (!label.seriesLabelFormatter?.length) { + defaultsDeep(intervalMark, labelOpt) + defaultsDeep(pointMark, labelOpt) + } else { + const showLeft = label.seriesLabelFormatter.some(c => c.id === chart.yAxis[0]?.id && c.show) + const showRight = label.seriesLabelFormatter.some( + c => c.id === chart.yAxisExt[0]?.id && c.show + ) + if (showLeft) { + defaultsDeep(intervalMark, labelOpt) + } + if (showRight) { + defaultsDeep(pointMark, labelOpt) + } + } + return options + } + + protected configTooltip(chart: Chart, options: G2Spec, context: Record): G2Spec { + const { tooltip } = parseJson(chart.customAttr) + const [intervalMark, lineMark] = options.children.find(c => c.key === 'chart').children + if (!tooltip.show) { + defaultsDeep(intervalMark, { tooltip: false }) + defaultsDeep(lineMark, { tooltip: false }) + return options + } + const chartObj = context.chartObj as G2Chart + const formatterMap = tooltip.seriesTooltipFormatter + ?.filter(i => i.show) + .reduce((pre, next) => { + pre[next.id] = next + return pre + }, {}) as Record + let g2TooltipWrapper = document.getElementById('G2-TOOLTIP-WRAPPER') + if (!g2TooltipWrapper) { + g2TooltipWrapper = document.createElement('div') + g2TooltipWrapper.id = 'G2-TOOLTIP-WRAPPER' + g2TooltipWrapper.style.position = 'absolute' + g2TooltipWrapper.style.pointerEvents = 'none' + g2TooltipWrapper.style.zIndex = '9999' + document.body.appendChild(g2TooltipWrapper) + } + const yAxis = chart.yAxis + const tooltipOptions: G2Spec = { + tooltip: d => d, + interaction: { + tooltip: { + crosshairsLineDash: [4, 4], + mount: g2TooltipWrapper, + css: { + '.g2-tooltip': { + background: tooltip.backgroundColor + }, + '.g2-tooltip-title': { + color: tooltip.color, + 'font-size': `${tooltip.fontSize}px` + }, + '.g2-tooltip-list-item-name-label': { + color: tooltip.color, + 'font-size': `${tooltip.fontSize}px` + }, + '.g2-tooltip-list-item-value': { + color: tooltip.color, + 'font-size': `${tooltip.fontSize}px` + } + }, + render: (_, { title, items }) => { + const titleHtml = TOOLTIP_TITLE_TPL.replace('{title}', title) + if (tooltip.seriesTooltipFormatter?.length) { + items = items.filter(i => formatterMap[i.quotaList[0].id]) + } + const result = [] + const view = chartObj.getContext().views.find(v => v.key === 'chart') + items.forEach(item => { + const formatterCfg = + formatterMap[item.quotaList[0].id]?.formatterCfg ?? yAxis[0].formatterCfg + const value = valueFormatter(item.value, formatterCfg) + const color = view.scale.color.map(item.category) ?? item.color + const name = item.category + result.push({ value, color, name }) + }) + const itemsHtml = result + .map(item => { + const marker = item.color + const label = item.name + const value = item.value + return TOOLTIP_ITEM_TPL.replace('{marker}', marker) + .replace('{label}', label) + .replace('{value}', value) + }) + .join('') + const listHtml = `
    ${itemsHtml}
` + return `${titleHtml}${listHtml}` + } + } + } + } + defaultsDeep(lineMark, tooltipOptions) + defaultsDeep(intervalMark, { tooltip: d => d }) + return options + } + + protected configXAxis(chart: Chart, options: G2Spec, context: Record): G2Spec { + const { xAxis } = parseJson(chart.customStyle) + const view = options.children.find(c => c.key === 'chart') + // 固定 x 轴顺序 + const { leftData: xAxisData } = context + const xAxisSort = xAxisData.map(d => d.field) + defaultsDeep(view, { + scale: { + x: { + compare: (a, b) => { + return xAxisSort.indexOf(a) - xAxisSort.indexOf(b) + } + } + } + }) + if (!xAxis.show) { + const axisHide = { + axis: { + x: false + } + } + defaultsDeep(view, axisHide) + return options + } + let lineLineDash = undefined + if (xAxis.axisLine.lineStyle.style === 'dashed') { + lineLineDash = [10, 8] + } + if (xAxis.axisLine.lineStyle.style === 'dotted') { + lineLineDash = [1, 2] + } + let gridLineDash = undefined + if (xAxis.splitLine.lineStyle.style === 'dashed') { + gridLineDash = [10, 8] + } + if (xAxis.splitLine.lineStyle.style === 'dotted') { + gridLineDash = [1, 2] + } + const axisStyle = { + axis: { + x: { + position: xAxis.position, + title: xAxis.nameShow === false || isEmpty(xAxis.name) ? false : xAxis.name, + titleFontSize: xAxis.fontSize, + titleFill: xAxis.color, + line: xAxis.axisLine.show, + lineStroke: xAxis.axisLine.lineStyle.color, + lineStrokeOpacity: 1, + lineLineWidth: xAxis.axisLine.lineStyle.width, + lineLineDash, + label: xAxis.axisLabel.show, + labelFill: xAxis.axisLabel.color, + labelFillOpacity: 1, + labelFontSize: xAxis.axisLabel.fontSize, + grid: xAxis.splitLine.show, + gridStroke: xAxis.splitLine.lineStyle.color, + gridStrokeOpacity: 1, + gridLineWidth: xAxis.splitLine.lineStyle.width, + gridLineDash, + labelTransform: `rotate(${xAxis.axisLabel.rotate || 0})` + } + } + } + defaultsDeep(view, axisStyle) + return options + } + + protected configYAxis(chart: Chart, options: G2Spec): G2Spec { + const { yAxis, yAxisExt } = parseJson(chart.customStyle) + const [intervalMark, lineMark, pointMark] = options.children.find( + c => c.key === 'chart' + ).children + if (!yAxis.show) { + intervalMark.axis.y = false + lineMark.axis.y = false + return options + } + const yAxisOption = this.getAxis(yAxis) + const yAxisExtOption = this.getAxis(yAxisExt) + merge(intervalMark, { + axis: { + y: { + ...yAxisOption, + position: 'left' + } + } + }) + merge(lineMark, { + axis: { + y: { + ...yAxisExtOption, + position: 'right' + } + } + }) + if (yAxis.axisValue.auto === false) { + merge(intervalMark, { + scale: { + y: { + domain: [yAxis.axisValue.min, yAxis.axisValue.max] + } + }, + axis: { + y: { + tickCount: yAxis.axisValue.splitCount, + tickMethod: (min, max, count) => { + const step = (max - min) / (count - 1) + const ticks = [] + for (let i = 0; i < count; i++) { + ticks.push(min + i * step) + } + return ticks + } + } + } + }) + } + if (yAxisExt.axisValue.auto === false) { + const scaleOpt = { + scale: { + y: { + independent: true, + domain: [yAxisExt.axisValue.min, yAxisExt.axisValue.max] + } + } + } + merge(lineMark, scaleOpt, { + axis: { + y: { + tickCount: yAxisExt.axisValue.splitCount, + tickMethod: (min, max, count) => { + const step = (max - min) / (count - 1) + const ticks = [] + for (let i = 0; i < count; i++) { + ticks.push(min + i * step) + } + return ticks + } + } + } + }) + merge(pointMark, scaleOpt) + } + return options + } + + private randomString(length: number): string { + const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' + let result = '' + for (let i = 0; i < length; i++) { + const randomIndex = Math.floor(Math.random() * chars.length) + result += chars[randomIndex] + } + return result + } + + protected configAssistLine(chart: Chart, options: G2Spec): G2Spec { + const { assistLineCfg } = parseJson(chart.senior) + if (!assistLineCfg.enable || !assistLineCfg.assistLine?.length) { + return options + } + const splitLineData = [[], []] + const splitDynamicFields = [[], []] + assistLineCfg.assistLine?.forEach(item => { + const lineData = splitLineData[item.yAxisType === 'left' ? 0 : 1] + const dynamicFields = splitDynamicFields[item.yAxisType === 'left' ? 0 : 1] + // 固定值 + if (item.field === '0') { + lineData.push({ ...item, value: parseFloat(item.value) }) + } + // 动态值 + if (item.field === '1') { + dynamicFields.push(item.fieldId) + } + }) + const assistLineData = [ + ...(chart.data.left.dynamicAssistLines ?? []), + ...(chart.data.right.dynamicAssistLines ?? []) + ] + assistLineData.forEach(d => { + const fields = d.yAxisType === 'left' ? splitDynamicFields[0] : splitDynamicFields[1] + if (fields.includes(d.fieldId)) { + splitLineData[d.yAxisType === 'left' ? 0 : 1].push({ ...d, value: parseFloat(d.value) }) + } + }) + const { yAxis, yAxisExt } = parseJson(chart.customStyle) + const yAxisFormatterCfg = yAxis.axisLabelFormatter ?? DEFAULT_YAXIS_STYLE.axisLabelFormatter + const yAxisExtFormatterCfg = + yAxisExt.axisLabelFormatter ?? DEFAULT_YAXIS_STYLE.axisLabelFormatter + const view = options.children.find(c => c.key === 'chart') + const randomAssistColorScale = this.randomString(6) + splitLineData.forEach((lineData, index) => { + if (lineData.length) { + const assistLineMark: G2Spec = { + type: 'lineY', + encode: { y: 'value', color: () => randomAssistColorScale }, + scale: { + y: { + key: index === 0 ? 'left' : 'right' + } + }, + data: lineData, + style: { + stroke: d => d.color, + lineDash: d => + d.lineType === 'solid' ? [] : d.lineType === 'dashed' ? [10, 8] : [1, 2], + opacity: 1 + }, + labels: [ + { + text: d => { + const value = valueFormatter( + parseFloat(d.value), + index === 0 ? yAxisFormatterCfg : yAxisExtFormatterCfg + ) + return d.name ? `${d.name}: ${value}` : value + }, + style: { + fontSize: d => parseInt(d.fontSize), + fill: d => d.color, + fillOpacity: 1 + }, + textBaseline: 'bottom', + position: index === 0 ? 'left' : 'right', + transform: [{ type: 'overlapHide' }, { type: 'exceedAdjust' }], + fontFamily: chart.fontFamily + } + ], + tooltip: false, + legend: false + } + view.children.push(assistLineMark) + } + }) + const assistFlag = splitLineData.some(l => l.length > 0) + const { legend } = parseJson(chart.customStyle) + // 处理 legend 点击辅助线会消失,创建一个隐藏的 legend 项 + if (assistFlag && legend.show) { + const legendMark = options.children.find(c => c.key === 'legend') + legendMark.scale.color.domain.push(randomAssistColorScale) + legendMark.scale.color.relations.push([randomAssistColorScale, '#000000']) + if (!Symbols.has('empty')) { + registerSymbol('empty', this.EMPTY_MARKER) + } + const originMarker = legendMark.itemMarker + merge(legendMark, { + itemMarker: d => { + if (d === randomAssistColorScale || d.id === randomAssistColorScale) { + return 'empty' + } + return originMarker + }, + itemLabelText: d => { + if (d.id === randomAssistColorScale) { + return '' + } + return d.id + } + }) + } + return options + } + + public setupDefaultOptions(chart: ChartObj): ChartObj { + const { customAttr, senior } = chart + if ( + senior.functionCfg.emptyDataStrategy == undefined || + senior.functionCfg.emptyDataStrategy === 'ignoreData' + ) { + senior.functionCfg.emptyDataStrategy = 'breakLine' + } + return chart + } + + public setupSeriesColor(chart: ChartObj, data?: any[]): ChartBasicStyle['seriesColor'] { + const result: ChartBasicStyle['seriesColor'] = [] + const seriesSet = new Set() + const colors = chart.customAttr.basicStyle.colors + const { yAxis, extStack } = chart + if (extStack?.length) { + data?.forEach(d => { + if (d.value === null || d.category === null || seriesSet.has(d.category)) { + return + } + seriesSet.add(d.category) + result.push({ + id: d.category, + name: d.category, + color: colors[(seriesSet.size - 1) % colors.length] + }) + }) + } else { + yAxis?.forEach(axis => { + if (seriesSet.has(axis.id)) { + return + } + seriesSet.add(axis.id) + result.push({ + id: axis.id, + name: axis.chartShowName ?? axis.name, + color: colors[(seriesSet.size - 1) % colors.length] + }) + }) + } + return result + } + + public setupSubSeriesColor(chart: ChartObj, data?: any[]): ChartBasicStyle['seriesColor'] { + const result: ChartBasicStyle['seriesColor'] = [] + const seriesSet = new Set() + const colors = chart.customAttr.basicStyle.subColors ?? CHART_MIX_DEFAULT_BASIC_STYLE.subColors + const { yAxisExt, extBubble } = chart + if (extBubble?.length) { + data?.forEach(d => { + if (d.value === null || d.category === null || seriesSet.has(d.category)) { + return + } + seriesSet.add(d.category) + result.push({ + id: d.category, + name: d.category, + color: colors[(seriesSet.size - 1) % colors.length] + }) + }) + } else { + yAxisExt?.forEach(axis => { + if (seriesSet.has(axis.id)) { + return + } + seriesSet.add(axis.id) + result.push({ + id: axis.id, + name: axis.chartShowName ?? axis.name, + color: colors[(seriesSet.size - 1) % colors.length] + }) + }) + } + return result + } + + protected setupOptions(chart: Chart, options: G2Spec, context: Record): G2Spec { + return flow( + this.configBasicStyle, + this.configLegend, + this.configLabel, + this.configTooltip, + this.configXAxis, + this.configYAxis, + this.configAssistLine + )(chart, options, context, this) + } + + constructor(name = 'chart-mix-stack') { + super(name, []) + this.EMPTY_MARKER.style = [] + } +} + +export const CHART_MIX_DEFAULT_BASIC_STYLE = { + ...DEFAULT_BASIC_STYLE, + subAlpha: 100, + subColorScheme: 'fast', + subSeriesColor: [], + subColors: [ + '#fae800', + '#00c039', + '#0482dc', + '#bb9581', + '#ff7701', + '#9c5ec3', + '#00ccdf', + '#00c039', + '#ff7701' + ], + leftLineWidth: 2, + leftLineSymbol: 'circle', + leftLineSymbolSize: 4, + leftLineSmooth: true +}