feat(视图-AntV折线图): 折线图支持隐藏无数据的点

AntV折线图支持对空数据进行处理,可选线条断开,置0或者跳过空值
https://www.tapd.cn/55578866/prong/stories/view/1155578866001009888
This commit is contained in:
wisonic-s 2022-11-18 15:43:32 +08:00
parent 99835212cc
commit d32043ef8b
9 changed files with 174 additions and 15 deletions

View File

@ -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);

View File

@ -1441,7 +1441,11 @@ export default {
proportion: 'Proportion',
label_content: 'Label Content',
percent: 'Percent',
table_index_desc: 'Index Header Name'
table_index_desc: 'Index Header Name',
empty_data_strategy: 'Empty Data Strategy',
break_line: 'Disconnection',
set_zero: 'Set Zero',
ignore_data: 'Ignore Data'
},
dataset: {
spend_time: 'Spend',

View File

@ -1441,7 +1441,11 @@ export default {
proportion: '佔比',
label_content: '標籤展示',
percent: '占比',
table_index_desc: '表頭名稱'
table_index_desc: '表頭名稱',
empty_data_strategy: '空值處理',
break_line: '線條斷開',
set_zero: '置為0線條不斷開',
ignore_data: '跳過空值,不展示'
},
dataset: {
spend_time: '耗時',

View File

@ -1440,7 +1440,11 @@ export default {
proportion: '占比',
label_content: '标签展示',
percent: '占比',
table_index_desc: '表头名称'
table_index_desc: '表头名称',
empty_data_strategy: '空值处理',
break_line: '线条断开',
set_zero: '置为0,线条不断开',
ignore_data: '跳过空值,不展示'
},
dataset: {
spend_time: '耗时',

View File

@ -434,7 +434,8 @@ export const DEFAULT_FUNCTION_CFG = {
sliderRange: [0, 10],
sliderBg: '#FFFFFF',
sliderFillBg: '#BCD6F1',
sliderTextClolor: '#999999'
sliderTextClolor: '#999999',
emptyDataStrategy: 'breakLine'
}
export const DEFAULT_THRESHOLD = {
gaugeThreshold: '',

View File

@ -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()

View File

@ -3304,3 +3304,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)
}
}
}

View File

@ -67,6 +67,20 @@
@change="changeFunctionCfg"
/>
</el-form-item>
<el-form-item
v-show="chart.render === 'antv' && chart.type === 'line'"
:label="$t('chart.empty_data_strategy')"
class="form-item"
>
<el-radio-group
v-model="functionForm.emptyDataStrategy"
@change="changeFunctionCfg"
>
<el-radio :label="'breakLine'">{{ $t('chart.break_line') }}</el-radio>
<el-radio :label="'setZero'">{{ $t('chart.set_zero') }}</el-radio>
<el-radio :label="'ignoreData'">{{ $t('chart.ignore_data') }}</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
</el-col>
</div>
@ -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 {
}
</script>
<style scoped>
<style scoped lang="scss">
.shape-item{
padding: 6px;
border: none;
@ -158,4 +172,12 @@ span{
cursor: pointer;
z-index: 1003;
}
.form-item ::v-deep .el-radio-group{
display: flex;
flex-direction: row;
flex-wrap: wrap;
label {
line-height: 28px;
}
}
</style>

View File

@ -2059,7 +2059,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'