forked from github/dataease
feat(视图-AntV折线图): 折线图支持隐藏无数据的点
AntV折线图支持对空数据进行处理,可选线条断开,置0或者跳过空值 https://www.tapd.cn/55578866/prong/stories/view/1155578866001009888
This commit is contained in:
parent
99835212cc
commit
d32043ef8b
@ -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);
|
||||
|
@ -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',
|
||||
|
@ -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: '耗時',
|
||||
|
@ -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: '耗时',
|
||||
|
@ -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: '',
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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'
|
||||
|
Loading…
Reference in New Issue
Block a user