diff --git a/backend/src/main/java/io/dataease/service/chart/util/ChartDataBuild.java b/backend/src/main/java/io/dataease/service/chart/util/ChartDataBuild.java index d2351a8a3e..7af383f8bd 100644 --- a/backend/src/main/java/io/dataease/service/chart/util/ChartDataBuild.java +++ b/backend/src/main/java/io/dataease/service/chart/util/ChartDataBuild.java @@ -158,12 +158,17 @@ public class ChartDataBuild { } catch (Exception e) { axisChartDataDTO.setValue(new BigDecimal(0)); } - if ("line".equals(view.getType()) && CollectionUtils.isEmpty(xAxisExt)) { - axisChartDataDTO.setCategory(yAxis.get(j).getName()); - } else { - axisChartDataDTO.setCategory(b.toString()); - } + axisChartDataDTO.setCategory(b.toString()); dataList.add(axisChartDataDTO); + + if ("line".equals(view.getType())) { + if (CollectionUtils.isEmpty(xAxisExt)){ + axisChartDataDTO.setCategory(yAxis.get(j).getName()); + } else { + // 多指标只取第一个 + break; + } + } } } map.put("data", dataList); diff --git a/frontend/src/lang/en.js b/frontend/src/lang/en.js index 75277831cc..4cc96873e4 100644 --- a/frontend/src/lang/en.js +++ b/frontend/src/lang/en.js @@ -1446,7 +1446,11 @@ export default { total_sort_none: 'None', total_sort_asc: 'ASC', total_sort_desc: 'DESC', - total_sort_field: 'Sort Field' + total_sort_field: 'Sort Field', + empty_data_strategy: 'Empty Data Strategy', + break_line: 'Disconnection', + set_zero: 'Set Zero', + ignore_data: 'Ignore Data' }, dataset: { spend_time: 'Spend', diff --git a/frontend/src/lang/tw.js b/frontend/src/lang/tw.js index 34cb3bb7f6..1f265b98d2 100644 --- a/frontend/src/lang/tw.js +++ b/frontend/src/lang/tw.js @@ -1446,7 +1446,11 @@ export default { total_sort_none: '無', total_sort_asc: '升序', total_sort_desc: '降序', - total_sort_field: '排序字段' + total_sort_field: '排序字段', + empty_data_strategy: '空值處理', + break_line: '線條斷開', + set_zero: '置為0,線條不斷開', + ignore_data: '跳過空值,不展示' }, dataset: { spend_time: '耗時', diff --git a/frontend/src/lang/zh.js b/frontend/src/lang/zh.js index a29a2b0b17..f9c00523d5 100644 --- a/frontend/src/lang/zh.js +++ b/frontend/src/lang/zh.js @@ -1445,7 +1445,11 @@ export default { total_sort_none: '无', total_sort_asc: '升序', total_sort_desc: '降序', - total_sort_field: '排序字段' + total_sort_field: '排序字段', + empty_data_strategy: '空值处理', + break_line: '线条断开', + set_zero: '置为0,线条不断开', + ignore_data: '跳过空值,不展示' }, dataset: { spend_time: '耗时', diff --git a/frontend/src/views/chart/chart/chart.js b/frontend/src/views/chart/chart/chart.js index a1083cc57a..5f78851539 100644 --- a/frontend/src/views/chart/chart/chart.js +++ b/frontend/src/views/chart/chart/chart.js @@ -438,7 +438,8 @@ export const DEFAULT_FUNCTION_CFG = { sliderRange: [0, 10], sliderBg: '#FFFFFF', sliderFillBg: '#BCD6F1', - sliderTextClolor: '#999999' + sliderTextClolor: '#999999', + emptyDataStrategy: 'breakLine' } export const DEFAULT_THRESHOLD = { gaugeThreshold: '', diff --git a/frontend/src/views/chart/chart/line/line_antv.js b/frontend/src/views/chart/chart/line/line_antv.js index 52cd766dfe..4616c7e695 100644 --- a/frontend/src/views/chart/chart/line/line_antv.js +++ b/frontend/src/views/chart/chart/line/line_antv.js @@ -10,7 +10,8 @@ import { getSlider, getAnalyse } from '@/views/chart/chart/common/common_antv' -import { antVCustomColor } from '@/views/chart/chart/util' +import { antVCustomColor, handleEmptyDataStrategy } from '@/views/chart/chart/util' +import _ from 'lodash' export function baseLineOptionAntV(plot, container, chart, action) { // theme @@ -23,7 +24,7 @@ export function baseLineOptionAntV(plot, container, chart, action) { const xAxis = getXAxis(chart) const yAxis = getYAxis(chart) // data - const data = chart.data.data + const data = _.cloneDeep(chart.data.data) // config const slider = getSlider(chart) const analyse = getAnalyse(chart) @@ -87,7 +88,8 @@ export function baseLineOptionAntV(plot, container, chart, action) { } // custom color options.color = antVCustomColor(chart) - + const emptyDataStrategy = chart.senior ? JSON.parse(chart.senior)?.functionCfg.emptyDataStrategy : 'breakLine' + handleEmptyDataStrategy(emptyDataStrategy, chart, data, options) // 开始渲染 if (plot) { plot.destroy() diff --git a/frontend/src/views/chart/chart/util.js b/frontend/src/views/chart/chart/util.js index c14f28c8d7..ca04a3124f 100644 --- a/frontend/src/views/chart/chart/util.js +++ b/frontend/src/views/chart/chart/util.js @@ -3310,3 +3310,120 @@ export function getRemark(chart) { } export const quotaViews = ['label', 'richTextView', 'text', 'gauge', 'liquid'] + +export function handleEmptyDataStrategy(strategy, chart, data, options) { + if (!data?.length) { + return + } + if (strategy === 'ignoreData') { + handleIgnoreData(chart, data) + return + } + const yaxis = JSON.parse(chart.yaxis) + const extAxis = JSON.parse(chart.xaxisExt) + const multiDimension = yaxis?.length >= 2 || extAxis?.length > 0 + switch (strategy) { + case 'breakLine': { + if (multiDimension) { + // 多维度线条断开 + handleBreakLineMultiDimension(chart, data, options) + } else { + // 单维度线条断开 + options.connectNulls = false + } + break + } + case 'setZero': { + if (multiDimension > 0) { + // 多维度置0 + handleSetZeroMultiDimension(chart, data, options) + } else { + // 单维度置0 + handleSetZeroSingleDimension(chart, data, options) + } + break + } + default: + break + } +} + +function handleBreakLineMultiDimension(chart, data, options) { + options.connectNulls = false + const dimensionInfoMap = new Map() + const subDimensionSet = new Set() + for (let i = 0; i < data.length; i++) { + const item = data[i] + const dimensionInfo = dimensionInfoMap.get(item.field) + if (dimensionInfo) { + dimensionInfo.set.add(item.category) + } else { + dimensionInfoMap.set(item.field, { set: new Set([item.category]), index: i }) + } + subDimensionSet.add(item.category) + } + // Map 是按照插入顺序排序的,所以插入索引往后推 + let insertCount = 0 + dimensionInfoMap.forEach((dimensionInfo, field) => { + if (dimensionInfo.set.size < subDimensionSet.size) { + const toBeFillDimension = [...subDimensionSet].filter(item => !dimensionInfo.set.has(item)) + toBeFillDimension.forEach(dimension => { + data.splice(dimensionInfo.index + insertCount, 0, { + field, + value: null, + category: dimension + }) + }) + insertCount += toBeFillDimension.size + } + }) +} + +function handleSetZeroMultiDimension(chart, data) { + const dimensionInfoMap = new Map() + const subDimensionSet = new Set() + for (let i = 0; i < data.length; i++) { + const item = data[i] + if (item.value === null) { + item.value = 0 + } + const dimensionInfo = dimensionInfoMap.get(item.field) + if (dimensionInfo) { + dimensionInfo.set.add(item.category) + } else { + dimensionInfoMap.set(item.field, { set: new Set([item.category]), index: i }) + } + subDimensionSet.add(item.category) + } + let insertCount = 0 + dimensionInfoMap.forEach((dimensionInfo, field) => { + if (dimensionInfo.set.size < subDimensionSet.size) { + const toBeFillDimension = [...subDimensionSet].filter(item => !dimensionInfo.set.has(item)) + toBeFillDimension.forEach(dimension => { + data.splice(dimensionInfo.index + insertCount, 0, { + field, + value: 0, + category: dimension + }) + }) + insertCount += toBeFillDimension.size + } + }) +} + +function handleSetZeroSingleDimension(chart, data) { + data.forEach(item => { + if (item.value === null) { + item.value = 0 + } + }) +} + +function handleIgnoreData(chart, data) { + for (let i = data.length - 1; i >= 0; i--) { + const item = data[i] + if (item.value === null) { + data.splice(i, 1) + } + } +} diff --git a/frontend/src/views/chart/components/senior/FunctionCfg.vue b/frontend/src/views/chart/components/senior/FunctionCfg.vue index 4c76c7159a..4fe2252ca1 100644 --- a/frontend/src/views/chart/components/senior/FunctionCfg.vue +++ b/frontend/src/views/chart/components/senior/FunctionCfg.vue @@ -67,6 +67,20 @@ @change="changeFunctionCfg" /> + + + {{ $t('chart.break_line') }} + {{ $t('chart.set_zero') }} + {{ $t('chart.ignore_data') }} + + @@ -110,7 +124,7 @@ export default { senior = JSON.parse(chart.senior) } if (senior.functionCfg) { - this.functionForm = senior.functionCfg + this.functionForm = { ...DEFAULT_FUNCTION_CFG, ...senior.functionCfg } } else { this.functionForm = JSON.parse(JSON.stringify(DEFAULT_FUNCTION_CFG)) } @@ -123,7 +137,7 @@ export default { } - diff --git a/frontend/src/views/chart/view/ChartEdit.vue b/frontend/src/views/chart/view/ChartEdit.vue index ae3f8df8fe..bd78ac4b01 100644 --- a/frontend/src/views/chart/view/ChartEdit.vue +++ b/frontend/src/views/chart/view/ChartEdit.vue @@ -2086,7 +2086,7 @@ export default { ele.filter = [] } }) - if (view.type === 'table-pivot' || view.type === 'bar-group') { + if (view.type === 'table-pivot' || view.type === 'bar-group' || (view.render === 'antv' && view.type === 'line')) { view.xaxisExt.forEach(function(ele) { if (!ele.dateStyle || ele.dateStyle === '') { ele.dateStyle = 'y_M_d'