faet(视图): AntV 折线图支持趋势线

This commit is contained in:
wisonic-s 2024-03-20 20:23:05 +08:00
parent 599fb1d9e5
commit 12b33f2528
21 changed files with 735 additions and 163 deletions

View File

@ -1643,7 +1643,16 @@ export default {
word_size_range: 'Word Size Range',
word_spacing: 'Word Spacing',
axis_multi_select_tip: 'Hold down the Ctrl/Cmd or Shift key and click to select more than one',
needs_to_be_integer: 'Needs to be an integer'
needs_to_be_integer: 'Needs to be an integer',
regression_poly: 'Polynomial',
regression_linear: 'Linear',
regression_exp: 'Exponential',
regression_log: 'Logarithmic',
regression_quad: 'Quadratic',
regression_pow: 'Power law',
regression_loess: 'LOESS',
regression_algo: 'Algorithm',
trend_line: 'Trend Line'
},
dataset: {
scope_edit: 'Effective only when editing',

View File

@ -1635,7 +1635,16 @@ export default {
word_size_range: '字號區間',
word_spacing: '文字間隔',
axis_multi_select_tip: '按住 Ctrl/Cmd 鍵或者 Shift 鍵再點擊可多選',
needs_to_be_integer: '需要為整數'
needs_to_be_integer: '需要為整數',
regression_poly: '多項式',
regression_linear: '線性',
regression_exp: '指數',
regression_log: '對數',
regression_quad: '二次項',
regression_pow: '冪函數',
regression_loess: '局部加權',
regression_algo: '算法',
trend_line: '趨勢線'
},
dataset: {
scope_edit: '僅編輯時生效',

View File

@ -1635,7 +1635,16 @@ export default {
word_size_range: '字号区间',
word_spacing: '文字间隔',
axis_multi_select_tip: '按住 Ctrl/Cmd 键或者 Shift 键再点击可多选',
needs_to_be_integer: '需要为整数'
needs_to_be_integer: '需要为整数',
regression_poly: '多项式',
regression_linear: '线性',
regression_exp: '指数',
regression_log: '对数',
regression_quad: '二次项',
regression_pow: '幂函数',
regression_loess: '局部加权',
regression_algo: '算法',
trend_line: '趋势线'
},
dataset: {
scope_edit: '仅编辑时生效',

View File

@ -16,7 +16,7 @@ import {
import { antVCustomColor, getColors, handleEmptyDataStrategy, hexColorToRGBA } from '@/views/chart/chart/util'
import { cloneDeep, find } from 'lodash-es'
export function baseBarOptionAntV(plot, container, chart, action, isGroup, isStack) {
export function baseBarOptionAntV(container, chart, action, isGroup, isStack) {
// theme
const theme = getTheme(chart)
// attr
@ -132,19 +132,15 @@ export function baseBarOptionAntV(plot, container, chart, action, isGroup, isSta
}
// 开始渲染
if (plot) {
plot.destroy()
}
plot = new Column(container, options)
const plot = new Column(container, options)
plot.off('interval:click')
plot.on('interval:click', action)
// 处理 tooltip 被其他视图遮挡
configPlotTooltipEvent(chart, plot)
return plot
}
export function hBaseBarOptionAntV(plot, container, chart, action, isGroup, isStack) {
export function hBaseBarOptionAntV(container, chart, action, isGroup, isStack) {
// theme
const theme = getTheme(chart)
// attr
@ -252,20 +248,15 @@ export function hBaseBarOptionAntV(plot, container, chart, action, isGroup, isSt
handleEmptyDataStrategy(emptyDataStrategy, chart, data, options)
}
// 开始渲染
if (plot) {
plot.destroy()
}
plot = new Bar(container, options)
const plot = new Bar(container, options)
plot.off('interval:click')
plot.on('interval:click', action)
// 处理 tooltip 被其他视图遮挡
configPlotTooltipEvent(chart, plot)
return plot
}
export function timeRangeBarOptionAntV(plot, container, chart, action) {
export function timeRangeBarOptionAntV(container, chart, action) {
const ifAggregate = !!chart.aggregate
// theme
@ -446,11 +437,7 @@ export function timeRangeBarOptionAntV(plot, container, chart, action) {
handleEmptyDataStrategy(emptyDataStrategy, chart, data, options)
}
// 开始渲染
if (plot) {
plot.destroy()
}
plot = new Bar(container, options)
const plot = new Bar(container, options)
plot.off('interval:click')
plot.on('interval:click', action)
@ -459,7 +446,7 @@ export function timeRangeBarOptionAntV(plot, container, chart, action) {
return plot
}
export function baseBidirectionalBarOptionAntV(plot, container, chart, action, isGroup, isStack) {
export function baseBidirectionalBarOptionAntV(container, chart, action, isGroup, isStack) {
// theme
const theme = getTheme(chart)
// attr
@ -542,13 +529,8 @@ export function baseBidirectionalBarOptionAntV(plot, container, chart, action, i
if (meta) {
options.meta = meta
}
// 开始渲染
if (plot) {
plot.destroy()
}
plot = new BidirectionalBar(container, options)
const plot = new BidirectionalBar(container, options)
plot.off('interval:click')
plot.on('interval:click', action)
// 处理 tooltip 被其他视图遮挡
configPlotTooltipEvent(chart, plot)

View File

@ -3,7 +3,15 @@ import { formatterItem, valueFormatter } from '@/views/chart/chart/formatter'
import { DEFAULT_XAXIS_STYLE, DEFAULT_YAXIS_EXT_STYLE, DEFAULT_YAXIS_STYLE } from '@/views/chart/chart/chart'
import { equalsAny, includesAny } from '@/utils/StringUtils'
import i18n from '@/lang'
import {
regressionExp,
regressionPoly,
regressionLinear,
regressionLoess,
regressionLog,
regressionPow,
regressionQuad
} from 'd3-regression/dist/d3-regression.esm'
export function getPadding(chart) {
if (chart.drill) {
return [0, 10, 26, 10]
@ -1258,3 +1266,82 @@ export const configTopN = (data, chart) => {
}, initOtherItem)
data.push(initOtherItem)
}
const REGRESSION_ALGO_MAP = {
poly: regressionPoly,
linear: regressionLinear,
exp: regressionExp,
log: regressionLog,
quad: regressionQuad,
pow: regressionPow,
loess: regressionLoess
}
export function configPlotTrendLine(chart, plot) {
const senior = JSON.parse(chart.senior)
if (!senior?.trendLine?.length || !chart.data?.data?.length) {
return
}
const originData = chart.data.data
const originFieldDataMap = {}
originData.forEach(item => {
if (item.quotaList?.length) {
const quota = item.quotaList[0]
if (!originFieldDataMap[quota.id]) {
originFieldDataMap[quota.id] = []
}
originFieldDataMap[quota.id].push(item.value)
}
})
const trendResultData = {}
const totalData = []
const trendLineMap = senior.trendLine.reduce((p, n) => {
const fieldData = originFieldDataMap[n.fieldId]
if (!fieldData?.length) {
return p
}
const regAlgo = REGRESSION_ALGO_MAP[n.algoType]()
.x((_, i) => i)
.y(d => d)
const result = regAlgo(fieldData)
trendResultData[n.fieldId] = result
result.forEach(item => {
totalData.push({ index: item[0], value: item[1], color: n.color, field: n.fieldId })
})
p[n.fieldId] = n
return p
}, {})
if (!totalData.length) {
return
}
const regLine = plot.chart.createView()
plot.once('afterrender', () => {
for (const fieldId in trendResultData) {
const trendLine = trendLineMap[fieldId]
const trendData = trendResultData[fieldId]
regLine.annotation().text({
content: trendLine.name,
position: [0, trendData[0][1]],
style: {
textBaseline: 'bottom',
fill: trendLine.color,
fontSize: trendLine.fontSize ?? 20,
fontWeight: 300
},
offsetY: 10
})
}
regLine.axis(false);
regLine.data(totalData);
regLine.line()
.position('index*value')
.color('color', color => color)
.style('field',field => {
const trend = trendLineMap[field]
return {
stroke: trend?.color ?? 'grey',
lineDash: trend?.lineType ? getLineDash(trend.lineType) : [0, 0]
}
})
.tooltip(false)
regLine.render()
})
}

View File

@ -9,7 +9,7 @@ import {
import { Funnel } from '@antv/g2plot'
import { antVCustomColor } from '@/views/chart/chart/util'
export function baseFunnelOptionAntV(plot, container, chart, action) {
export function baseFunnelOptionAntV(container, chart, action) {
// theme
const theme = getTheme(chart)
// attr
@ -53,13 +53,8 @@ export function baseFunnelOptionAntV(plot, container, chart, action) {
// custom color
options.color = antVCustomColor(chart)
// 开始渲染
if (plot) {
plot.destroy()
}
plot = new Funnel(container, options)
const plot = new Funnel(container, options)
plot.off('interval:click')
plot.on('interval:click', action)
// 处理 tooltip 被其他视图遮挡
configPlotTooltipEvent(chart, plot)

View File

@ -6,7 +6,7 @@ import { valueFormatter } from '@/views/chart/chart/formatter'
let labelFormatter = null
export function baseGaugeOptionAntV(plot, container, chart, action, scale = 1) {
export function baseGaugeOptionAntV(container, chart, action, scale = 1) {
let min, max, labelContent, startAngel, endAngel
// theme
const theme = getTheme(chart)
@ -170,11 +170,5 @@ export function baseGaugeOptionAntV(plot, container, chart, action, scale = 1) {
}
}
// 开始渲染
if (plot) {
plot.destroy()
}
plot = new Gauge(container, options)
return plot
return new Gauge(container, options)
}

View File

@ -10,12 +10,13 @@ import {
getSlider,
getAnalyse,
setGradientColor,
configPlotTooltipEvent
configPlotTooltipEvent,
configPlotTrendLine
} from '@/views/chart/chart/common/common_antv'
import { antVCustomColor, handleEmptyDataStrategy } from '@/views/chart/chart/util'
import _ from 'lodash'
export function baseLineOptionAntV(plot, container, chart, action) {
export function baseLineOptionAntV(container, chart, action) {
// theme
const theme = getTheme(chart)
// attr
@ -103,20 +104,17 @@ export function baseLineOptionAntV(plot, container, chart, action) {
}
handleEmptyDataStrategy(emptyDataStrategy, chart, data, options)
}
// 开始渲染
if (plot) {
plot.destroy()
}
plot = new Line(container, options)
const plot = new Line(container, options)
plot.off('point:click')
plot.on('point:click', action)
// 趋势线
configPlotTrendLine(chart, plot)
// 处理 tooltip 被其他视图遮挡
configPlotTooltipEvent(chart, plot)
return plot
}
export function baseAreaOptionAntV(plot, container, chart, action, isStack) {
export function baseAreaOptionAntV(container, chart, action, isStack) {
// theme
const theme = getTheme(chart)
// attr
@ -214,13 +212,8 @@ export function baseAreaOptionAntV(plot, container, chart, action, isStack) {
handleEmptyDataStrategy(emptyDataStrategy, chart, data, options)
}
// 开始渲染
if (plot) {
plot.destroy()
}
plot = new Area(container, options)
const plot = new Area(container, options)
plot.off('point:click')
plot.on('point:click', action)
// 处理 tooltip 被其他视图遮挡
configPlotTooltipEvent(chart, plot)

View File

@ -5,7 +5,7 @@ import { valueFormatter } from '@/views/chart/chart/formatter'
let labelFormatter = null
export function baseLiquid(plot, container, chart) {
export function baseLiquid(container, chart) {
let value = 0
const colors = []
let max, radius, bgColor, shape, labelContent, liquidStyle, originVal = 0
@ -80,11 +80,7 @@ export function baseLiquid(plot, container, chart) {
bgColor = hexColorToRGBA(customStyle.background.color, customStyle.background.alpha)
}
}
// 开始渲染
if (plot) {
plot.destroy()
}
plot = new Liquid(container, {
return new Liquid(container, {
theme: {
styleSheet: {
brandColor: colors[0],
@ -101,5 +97,4 @@ export function baseLiquid(plot, container, chart) {
},
liquidStyle
})
return plot
}

View File

@ -12,7 +12,7 @@ import {
import { Mix } from '@antv/g2plot'
import { hexColorToRGBA } from '@/views/chart/chart/util'
export function baseMixOptionAntV(plot, container, chart, action) {
export function baseMixOptionAntV(container, chart, action) {
// theme
const theme = getTheme(chart)
// attr
@ -98,15 +98,9 @@ export function baseMixOptionAntV(plot, container, chart, action) {
tooltip: { shared: true }
}
// 开始渲染
if (plot) {
plot.destroy()
}
plot = new Mix(container, options)
const plot = new Mix(container, options)
plot.off('point:click')
plot.on('point:click', action)
plot.off('interval:click')
plot.on('interval:click', action)
// 处理 tooltip 被其他视图遮挡
configPlotTooltipEvent(chart, plot)

View File

@ -11,7 +11,7 @@ import { Pie, Rose } from '@antv/g2plot'
import { antVCustomColor } from '@/views/chart/chart/util'
import { configTopN } from '@/views/chart/chart/common/common_antv'
export function basePieOptionAntV(plot, container, chart, action) {
export function basePieOptionAntV(container, chart, action) {
// theme
const theme = getTheme(chart)
// attr
@ -85,20 +85,15 @@ export function basePieOptionAntV(plot, container, chart, action) {
options.color = antVCustomColor(chart)
// topN
configTopN(data, chart)
// 开始渲染
if (plot) {
plot.destroy()
}
plot = new Pie(container, options)
const plot = new Pie(container, options)
plot.off('interval:click')
plot.on('interval:click', action)
// 处理 tooltip 被其他视图遮挡
configPlotTooltipEvent(chart, plot)
return plot
}
export function basePieRoseOptionAntV(plot, container, chart, action) {
export function basePieRoseOptionAntV(container, chart, action) {
// theme
const theme = getTheme(chart)
// attr
@ -163,13 +158,8 @@ export function basePieRoseOptionAntV(plot, container, chart, action) {
// custom color
options.color = antVCustomColor(chart)
// 开始渲染
if (plot) {
plot.destroy()
}
plot = new Rose(container, options)
const plot = new Rose(container, options)
plot.off('interval:click')
plot.on('interval:click', action)
// 处理 tooltip 被其他视图遮挡
configPlotTooltipEvent(chart, plot)

View File

@ -10,7 +10,7 @@ import { Radar } from '@antv/g2plot'
import { antVCustomColor } from '@/views/chart/chart/util'
import { minBy, maxBy } from 'lodash'
export function baseRadarOptionAntV(plot, container, chart, action) {
export function baseRadarOptionAntV(container, chart, action) {
// theme
const theme = getTheme(chart)
// attr
@ -145,11 +145,7 @@ export function baseRadarOptionAntV(plot, container, chart, action) {
// custom color
options.color = antVCustomColor(chart)
// 开始渲染
if (plot) {
plot.destroy()
}
plot = new Radar(container, options)
const plot = new Radar(container, options)
plot.off('point:click')
plot.on('point:click', action)

View File

@ -16,7 +16,7 @@ import {
import { Scatter } from '@antv/g2plot'
import { antVCustomColor } from '@/views/chart/chart/util'
export function baseScatterOptionAntV(plot, container, chart, action) {
export function baseScatterOptionAntV(container, chart, action) {
// theme
const theme = getTheme(chart)
// attr
@ -87,11 +87,7 @@ export function baseScatterOptionAntV(plot, container, chart, action) {
// custom color
options.color = antVCustomColor(chart)
// 开始渲染
if (plot) {
plot.destroy()
}
plot = new Scatter(container, options)
const plot = new Scatter(container, options)
plot.off('point:click')
plot.on('point:click', action)

View File

@ -8,7 +8,7 @@ import {
} from '@/views/chart/chart/common/common_antv'
import { Treemap } from '@antv/g2plot'
export function baseTreemapOptionAntV(plot, container, chart, action) {
export function baseTreemapOptionAntV(container, chart, action) {
// theme
const theme = getTheme(chart)
// attr
@ -50,13 +50,8 @@ export function baseTreemapOptionAntV(plot, container, chart, action) {
}
]
}
// 开始渲染
if (plot) {
plot.destroy()
}
plot = new Treemap(container, options)
const plot = new Treemap(container, options)
plot.off('polygon:click')
plot.on('polygon:click', action)
// 处理 tooltip 被其他视图遮挡
configPlotTooltipEvent(chart, plot)

View File

@ -3776,6 +3776,7 @@ export function handleEmptyDataStrategy(strategy, chart, data, options) {
function handleBreakLineMultiDimension(chart, data) {
const dimensionInfoMap = new Map()
const subDimensionSet = new Set()
const catQuotaMap = {}
for (let i = 0; i < data.length; i++) {
const item = data[i]
const dimensionInfo = dimensionInfoMap.get(item.field)
@ -3785,6 +3786,9 @@ function handleBreakLineMultiDimension(chart, data) {
dimensionInfoMap.set(item.field, { set: new Set([item.category]), index: i })
}
subDimensionSet.add(item.category)
if (!catQuotaMap[item.category]) {
catQuotaMap[item.category] = item.quotaList
}
}
// Map 是按照插入顺序排序的,所以插入索引往后推
let insertCount = 0
@ -3796,7 +3800,8 @@ function handleBreakLineMultiDimension(chart, data) {
data.splice(dimensionInfo.index + insertCount + subInsertIndex, 0, {
field,
value: null,
category: dimension
category: dimension,
quotaList: catQuotaMap[dimension]
})
}
subInsertIndex++
@ -3809,6 +3814,7 @@ function handleBreakLineMultiDimension(chart, data) {
function handleSetZeroMultiDimension(chart, data) {
const dimensionInfoMap = new Map()
const subDimensionSet = new Set()
const catQuotaMap = {}
for (let i = 0; i < data.length; i++) {
const item = data[i]
if (item.value === null) {
@ -3821,6 +3827,9 @@ function handleSetZeroMultiDimension(chart, data) {
dimensionInfoMap.set(item.field, { set: new Set([item.category]), index: i })
}
subDimensionSet.add(item.category)
if (!catQuotaMap[item.category]) {
catQuotaMap[item.category] = item.quotaList
}
}
let insertCount = 0
dimensionInfoMap.forEach((dimensionInfo, field) => {
@ -3831,7 +3840,8 @@ function handleSetZeroMultiDimension(chart, data) {
data.splice(dimensionInfo.index + insertCount + subInsertIndex, 0, {
field,
value: 0,
category: dimension
category: dimension,
quotaList: catQuotaMap[dimension]
})
}
subInsertIndex++

View File

@ -11,7 +11,7 @@ import {
import { Waterfall } from '@antv/g2plot'
import { formatterItem, valueFormatter } from '@/views/chart/chart/formatter'
export function baseWaterfallOptionAntV(plot, container, chart, action) {
export function baseWaterfallOptionAntV(container, chart, action) {
// theme
const theme = getTheme(chart)
// attr
@ -101,13 +101,8 @@ export function baseWaterfallOptionAntV(plot, container, chart, action) {
}
}
// 开始渲染
if (plot) {
plot.destroy()
}
plot = new Waterfall(container, options)
const plot = new Waterfall(container, options)
plot.off('interval:click')
plot.on('interval:click', action)
// 处理 tooltip 被其他视图遮挡
configPlotTooltipEvent(chart, plot)

View File

@ -6,7 +6,7 @@ import {
} from '@/views/chart/chart/common/common_antv'
import { WordCloud } from '@antv/g2plot'
export function baseWordCloudOptionAntV(plot, container, chart, action) {
export function baseWordCloudOptionAntV(container, chart, action) {
// theme
const theme = getTheme(chart)
// attr
@ -42,12 +42,7 @@ export function baseWordCloudOptionAntV(plot, container, chart, action) {
]
}
// 开始渲染
if (plot) {
plot.destroy()
}
plot = new WordCloud(container, options)
plot.off('point:click')
const plot = new WordCloud(container, options)
plot.on('point:click', action)
// 处理 tooltip 被其他视图遮挡
configPlotTooltipEvent(chart, plot)

View File

@ -119,7 +119,6 @@ export default {
background: ''
},
title_show: true,
antVRenderStatus: false,
linkageActiveParam: null,
linkageActiveHistory: false,
remarkCfg: {
@ -160,7 +159,7 @@ export default {
}
},
beforeDestroy() {
if (this.myChart.container) {
if (this.myChart?.container) {
if (typeof this.myChart.container.getAttribute === 'function') {
clear(this.myChart.container)
}
@ -191,7 +190,7 @@ export default {
methods: {
reDrawView() {
this.linkageActiveHistory = false
this.myChart.render()
this.myChart?.render()
},
linkageActivePre() {
if (this.linkageActiveHistory) {
@ -238,7 +237,6 @@ export default {
},
async drawView() {
const chart = JSON.parse(JSON.stringify(this.chart))
this.antVRenderStatus = true
if (!chart.data || (!chart.data.data && !chart.data.series)) {
chart.data = {
data: [{}],
@ -249,57 +247,53 @@ export default {
]
}
}
this.myChart?.destroy()
if (chart.type === 'bar') {
this.myChart = baseBarOptionAntV(this.myChart, this.chartId, chart, this.antVAction, true, false)
this.myChart = baseBarOptionAntV(this.chartId, chart, this.antVAction, true, false)
} else if (chart.type === 'bar-group') {
this.myChart = baseBarOptionAntV(this.myChart, this.chartId, chart, this.antVAction, true, false)
this.myChart = baseBarOptionAntV(this.chartId, chart, this.antVAction, true, false)
} else if (equalsAny(chart.type, 'bar-stack', 'percentage-bar-stack')) {
this.myChart = baseBarOptionAntV(this.myChart, this.chartId, chart, this.antVAction, false, true)
this.myChart = baseBarOptionAntV(this.chartId, chart, this.antVAction, false, true)
} else if (chart.type === 'bar-group-stack') {
this.myChart = baseBarOptionAntV(this.myChart, this.chartId, chart, this.antVAction, true, true)
this.myChart = baseBarOptionAntV(this.chartId, chart, this.antVAction, true, true)
} else if (chart.type === 'bar-horizontal') {
this.myChart = hBaseBarOptionAntV(this.myChart, this.chartId, chart, this.antVAction, true, false)
this.myChart = hBaseBarOptionAntV(this.chartId, chart, this.antVAction, true, false)
} else if (equalsAny(chart.type, 'bar-stack-horizontal', 'percentage-bar-stack-horizontal')) {
this.myChart = hBaseBarOptionAntV(this.myChart, this.chartId, chart, this.antVAction, false, true)
this.myChart = hBaseBarOptionAntV(this.chartId, chart, this.antVAction, false, true)
} else if (equalsAny(chart.type, 'bar-time-range')) {
this.myChart = timeRangeBarOptionAntV(this.myChart, this.chartId, chart, this.antVAction)
this.myChart = timeRangeBarOptionAntV(this.chartId, chart, this.antVAction)
} else if (chart.type === 'line') {
this.myChart = baseLineOptionAntV(this.myChart, this.chartId, chart, this.antVAction)
this.myChart = baseLineOptionAntV(this.chartId, chart, this.antVAction)
} else if (chart.type === 'area') {
this.myChart = baseAreaOptionAntV(this.myChart, this.chartId, chart, this.antVAction, false)
this.myChart = baseAreaOptionAntV(this.chartId, chart, this.antVAction, false)
} else if (chart.type === 'line-stack') {
this.myChart = baseAreaOptionAntV(this.myChart, this.chartId, chart, this.antVAction, true)
this.myChart = baseAreaOptionAntV(this.chartId, chart, this.antVAction, true)
} else if (chart.type === 'scatter') {
this.myChart = baseScatterOptionAntV(this.myChart, this.chartId, chart, this.antVAction)
this.myChart = baseScatterOptionAntV(this.chartId, chart, this.antVAction)
} else if (chart.type === 'radar') {
this.myChart = baseRadarOptionAntV(this.myChart, this.chartId, chart, this.antVAction)
this.myChart = baseRadarOptionAntV(this.chartId, chart, this.antVAction)
} else if (chart.type === 'gauge') {
this.myChart = baseGaugeOptionAntV(this.myChart, this.chartId, chart, this.antVAction, this.scale)
this.myChart = baseGaugeOptionAntV(this.chartId, chart, this.antVAction, this.scale)
} else if (chart.type === 'pie' || chart.type === 'pie-donut') {
this.myChart = basePieOptionAntV(this.myChart, this.chartId, chart, this.antVAction)
this.myChart = basePieOptionAntV(this.chartId, chart, this.antVAction)
} else if (chart.type === 'pie-rose' || chart.type === 'pie-donut-rose') {
this.myChart = basePieRoseOptionAntV(this.myChart, this.chartId, chart, this.antVAction)
this.myChart = basePieRoseOptionAntV(this.chartId, chart, this.antVAction)
} else if (chart.type === 'funnel') {
this.myChart = baseFunnelOptionAntV(this.myChart, this.chartId, chart, this.antVAction)
this.myChart = baseFunnelOptionAntV(this.chartId, chart, this.antVAction)
} else if (chart.type === 'treemap') {
this.myChart = baseTreemapOptionAntV(this.myChart, this.chartId, chart, this.antVAction)
this.myChart = baseTreemapOptionAntV(this.chartId, chart, this.antVAction)
} else if (chart.type === 'liquid') {
this.myChart = baseLiquid(this.myChart, this.chartId, chart)
this.myChart = baseLiquid(this.chartId, chart)
} else if (chart.type === 'waterfall') {
this.myChart = baseWaterfallOptionAntV(this.myChart, this.chartId, chart, this.antVAction)
this.myChart = baseWaterfallOptionAntV(this.chartId, chart, this.antVAction)
} else if (chart.type === 'word-cloud') {
this.myChart = baseWordCloudOptionAntV(this.myChart, this.chartId, chart, this.antVAction)
this.myChart = baseWordCloudOptionAntV(this.chartId, chart, this.antVAction)
} else if (chart.type === 'chart-mix') {
this.myChart = baseMixOptionAntV(this.myChart, this.chartId, chart, this.antVAction)
this.myChart = baseMixOptionAntV(this.chartId, chart, this.antVAction)
} else if (chart.type === 'flow-map') {
this.myChart = await baseFlowMapOption(this.myChart, this.chartId, chart, this.antVAction)
this.myChart = await baseFlowMapOption(this.chartId, chart, this.antVAction)
} else if (chart.type === 'bidirectional-bar') {
this.myChart = baseBidirectionalBarOptionAntV(this.myChart, this.chartId, chart, this.antVAction)
} else {
if (this.myChart) {
this.antVRenderStatus = false
this.myChart.destroy()
}
this.myChart = baseBidirectionalBarOptionAntV(this.chartId, chart, this.antVAction)
}
if (this.myChart && !equalsAny(chart.type, 'liquid', 'flow-map') && this.searchCount > 0) {
@ -324,11 +318,9 @@ export default {
}
}
if (this.antVRenderStatus) {
this.myChart.render()
if (this.linkageActiveHistory) {
this.linkageActive()
}
this.myChart?.render()
if (this.linkageActiveHistory) {
this.linkageActive()
}
this.setBackGroundBorder()
},

View File

@ -0,0 +1,217 @@
<template>
<div style="width: 100%;padding: 0 18px;">
<el-col>
<el-button
:title="$t('chart.edit')"
icon="el-icon-edit"
type="text"
size="small"
style="width: 24px;margin-left: 4px;"
@click="editLine"
/>
<el-col>
<el-row
v-for="(item,index) in trendLine"
:key="index"
class="line-style"
>
<el-col :span="8">
<span :title="item.name">{{ item.name }}</span>
</el-col>
<el-col :span="8">
{{ item.fieldName }}
</el-col>
<el-col
:span="8"
>
{{ $t(`chart.regression_${item.algoType}`)}}
</el-col>
</el-row>
</el-col>
</el-col>
<el-dialog
v-if="editLineDialog"
v-dialogDrag
:title="$t('chart.trend_line')"
:visible="editLineDialog"
:show-close="false"
width="1000px"
class="dialog-css"
>
<trend-line-edit
:line="trendLine"
:quota-fields="quotaData"
@onTrendLineChange="lineChange"
/>
<div
slot="footer"
class="dialog-footer"
>
<el-button
size="mini"
@click="closeEditLine"
>{{ $t('chart.cancel') }}</el-button>
<el-button
type="primary"
size="mini"
@click="changeLine"
>{{ $t('chart.confirm') }}</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import TrendLineEdit from '@/views/chart/components/senior/dialog/TrendLineEdit'
export default {
name: 'TrendLine',
components: { TrendLineEdit },
props: {
chart: {
type: Object,
required: true
},
quotaData: {
type: Array,
required: true
}
},
data() {
return {
trendLine: [],
editLineDialog: false,
lineArr: [],
quotaFields: []
}
},
watch: {
'chart': {
handler: function() {
this.initData()
}
}
},
mounted() {
this.initData()
},
methods: {
initData() {
const chart = JSON.parse(JSON.stringify(this.chart))
if (chart.senior) {
let senior = null
if (Object.prototype.toString.call(chart.senior) === '[object Object]') {
senior = JSON.parse(JSON.stringify(chart.senior))
} else {
senior = JSON.parse(chart.senior)
}
if (senior.trendLine) {
this.trendLine.splice(0, this.trendLine.length, ...senior.trendLine)
} else {
this.trendLine.splice(0)
}
this.lineArr = JSON.parse(JSON.stringify(this.trendLine))
}
},
changeTrendLine() {
this.$emit('onTrendLineChange', this.trendLine)
},
lineChange(val) {
this.lineArr = val
},
editLine() {
this.editLineDialog = true
},
closeEditLine() {
this.editLineDialog = false
},
changeLine() {
// check line config
for (let i = 0; i < this.lineArr.length; i++) {
const ele = this.lineArr[i]
if (!ele.name) {
this.$message({
message: this.$t('chart.name_can_not_empty'),
type: 'error',
showClose: true
})
return
}
if (!ele.fieldId) {
this.$message({
message: this.$t('chart.field_not_empty'),
type: 'error',
showClose: true
})
return
}
}
this.trendLine = JSON.parse(JSON.stringify(this.lineArr))
this.changeTrendLine()
this.closeEditLine()
}
}
}
</script>
<style scoped>
.shape-item{
padding: 6px;
border: none;
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
}
.form-item-slider ::v-deep .el-form-item__label{
font-size: 12px;
line-height: 38px;
}
.form-item ::v-deep .el-form-item__label{
font-size: 12px;
}
.el-select-dropdown__item{
padding: 0 20px;
}
span{
font-size: 12px
}
.el-form-item{
margin-bottom: 6px;
}
.switch-style{
position: absolute;
right: 10px;
margin-top: -4px;
}
.color-picker-style{
cursor: pointer;
z-index: 1003;
}
.line-style{
}
.line-style ::v-deep span{
display: inline-block;
width: 100%;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
padding: 0 10px;
}
.dialog-css ::v-deep .el-dialog__title {
font-size: 14px;
}
.dialog-css ::v-deep .el-dialog__header {
padding: 20px 20px 0;
}
.dialog-css ::v-deep .el-dialog__body {
padding: 10px 20px 20px;
}
</style>

View File

@ -0,0 +1,297 @@
<template>
<el-col>
<el-button
icon="el-icon-plus"
circle
size="mini"
style="margin-bottom: 10px;"
@click="addLine"
/>
<div style="max-height: 50vh;overflow-y: auto;">
<el-row
v-for="(item,index) in lineArr"
:key="index"
class="line-item"
>
<el-col :span="4">
<el-input
v-model="item.name"
class="value-item"
style="width: 90% !important;"
:placeholder="$t('chart.name')"
size="mini"
clearable
@change="changeTrendLine"
/>
</el-col>
<el-col :span="4">
<el-select
v-model="item.fieldId"
size="mini"
class="select-item"
:placeholder="$t('chart.field')"
@change="changeTrendLineField(item)"
>
<el-option
v-for="quota in quotaData"
:key="quota.id"
:label="quota.name"
:value="quota.id"
>
<span style="float: left">
<svg-icon
v-if="quota.deType === 0"
icon-class="field_text"
class="field-icon-text"
/>
<svg-icon
v-if="quota.deType === 1"
icon-class="field_time"
class="field-icon-time"
/>
<svg-icon
v-if="quota.deType === 2 || quota.deType === 3"
icon-class="field_value"
class="field-icon-value"
/>
<svg-icon
v-if="quota.deType === 5"
icon-class="field_location"
class="field-icon-location"
/>
</span>
<span style="float: left; color: #8492a6; font-size: 12px">{{ quota.name }}</span>
</el-option>
</el-select>
</el-col>
<el-col :span="3">
<el-select
v-model="item.algoType"
size="mini"
class="select-item"
style="margin-left: 10px;"
:placeholder="$t('chart.regression_algo')"
@change="changeTrendLine"
>
<el-option
v-for="option in algoOptions"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-select>
</el-col>
<el-col :span="3">
<el-select
v-model="item.fontSize"
size="mini"
class="select-item"
style="margin-left: 10px;"
:placeholder="$t('chart.text_fontsize')"
@change="changeTrendLine"
>
<el-option
v-for="option in fontSize"
:key="option.value"
:label="option.name"
:value="option.value"
/>
</el-select>
</el-col>
<el-col :span="3">
<el-select
v-model="item.lineType"
size="mini"
class="select-item"
@change="changeTrendLine"
>
<el-option
v-for="opt in lineOptions"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-select>
</el-col>
<el-col
:span="1"
style="text-align: center;"
>
<el-color-picker
v-model="item.color"
class="color-picker-style"
:predefine="predefineColors"
@change="changeTrendLine"
/>
</el-col>
<el-col :span="1">
<el-button
type="text"
icon="el-icon-delete"
circle
style="float: right"
@click="removeLine(index)"
/>
</el-col>
</el-row>
</div>
</el-col>
</template>
<script>
import { COLOR_PANEL } from '@/views/chart/chart/chart'
export default {
name: 'TrendLineEdit',
props: {
line: {
type: Array,
required: true
},
quotaFields: {
type: Array,
required: true
}
},
data() {
return {
lineArr: [],
lineObj: {
name: '趋势线',
algoType: 'poly',
fieldId: '',
fieldName: '',
lineType: 'solid',
color: '#ff0000',
fontSize: 10
},
algoOptions: [
{ label: this.$t('chart.regression_poly'), value: 'poly' },
{ label: this.$t('chart.regression_linear'), value: 'linear' },
{ label: this.$t('chart.regression_exp'), value: 'exp' },
{ label: this.$t('chart.regression_log'), value: 'log' },
{ label: this.$t('chart.regression_quad'), value: 'quad' },
{ label: this.$t('chart.regression_pow'), value: 'pow' },
{ label: this.$t('chart.regression_loess'), value: 'loess' },
],
lineOptions: [
{ label: this.$t('chart.line_type_solid'), value: 'solid' },
{ label: this.$t('chart.line_type_dashed'), value: 'dashed' },
{ label: this.$t('chart.line_type_dotted'), value: 'dotted' }
],
predefineColors: COLOR_PANEL,
quotaData: [],
fontSize: []
}
},
watch: {
'quotaFields': function() {
this.initField()
}
},
mounted() {
this.initField()
this.init()
},
methods: {
initField() {
this.quotaData = this.quotaFields.filter(ele => !ele.chartId && ele.id !== 'count')
},
init() {
this.lineArr = JSON.parse(JSON.stringify(this.line))
for (let i = 10; i <= 60; i = i + 2) {
this.fontSize.push({
name: i + '',
value: i
})
}
},
addLine() {
const obj = {
...this.lineObj,
fieldId: this.quotaData ? this.quotaData[0]?.id : null,
fieldName: this.quotaData ? this.quotaData[0]?.name : null
}
this.lineArr.push(JSON.parse(JSON.stringify(obj)))
this.changeTrendLine()
},
removeLine(index) {
this.lineArr.splice(index, 1)
this.changeTrendLine()
},
changeTrendLine() {
this.$emit('onTrendLineChange', this.lineArr)
},
changeTrendLineField(item) {
const curField = this.getQuotaField(item.fieldId)
item.fieldName = curField.name
this.changeTrendLine()
},
getQuotaField(id) {
if (!id) {
return {}
}
const fields = this.quotaData.filter(ele => {
return ele.id === id
})
if (fields.length === 0) {
return {}
} else {
return fields[0]
}
}
}
}
</script>
<style scoped>
.line-item {
width: 100%;
border-radius: 4px;
border: 1px solid #DCDFE6;
padding: 4px 14px;
margin-bottom: 10px;
display: flex;
justify-content: left;
align-items: center;
}
.form-item ::v-deep .el-form-item__label {
font-size: 12px;
}
span {
font-size: 12px;
}
.value-item {
position: relative;
display: inline-block;
width: 100px !important;
}
.select-item {
position: relative;
display: inline-block;
width: 100px !important;
}
.el-select-dropdown__item {
padding: 0 20px;
font-size: 12px;
}
.color-picker-style{
cursor: pointer;
z-index: 1003;
width: 28px;
height: 28px;
margin-top: 6px;
}
.color-picker-style ::v-deep .el-color-picker__trigger{
width: 28px;
height: 28px;
}
</style>

View File

@ -1344,6 +1344,18 @@
@onThresholdChange="onThresholdChange"
/>
</el-collapse-item>
<el-collapse-item
v-if="showTrendLineCfg"
name="trend-line"
:title="$t('chart.trend_line')"
>
<trend-line
class="attr-selector"
:chart="chart"
:quota-data="view.yaxis"
@onTrendLineChange="onTrendLineChange"
/>
</el-collapse-item>
</el-collapse>
</el-row>
@ -1918,6 +1930,8 @@ import CalcChartFieldEdit from '@/views/chart/view/CalcChartFieldEdit'
import { equalsAny, includesAny } from '@/utils/StringUtils'
import PositionAdjust from '@/views/chart/view/PositionAdjust'
import MarkMapDataEditor from '@/views/chart/components/map/MarkMapDataEditor'
import TrendLine from '@/views/chart/components/senior/TrendLine'
export default {
name: 'ChartEdit',
components: {
@ -1955,7 +1969,8 @@ export default {
DrillPath,
PluginCom,
MapMapping,
MarkMapDataEditor
MarkMapDataEditor,
TrendLine
},
provide() {
return {
@ -2021,6 +2036,7 @@ export default {
senior: {
functionCfg: DEFAULT_FUNCTION_CFG,
assistLine: [],
trendLine: [],
threshold: DEFAULT_THRESHOLD
},
customFilter: {},
@ -2166,6 +2182,9 @@ export default {
showAssistLineCfg() {
return includesAny(this.view.type, 'bar', 'line', 'area', 'mix') || this.view.type === 'scatter'
},
showTrendLineCfg() {
return this.view.render === 'antv' && equalsAny(this.view.type, 'line')
},
showThresholdCfg() {
if (this.view.type === 'bidirectional-bar') {
return false
@ -3040,12 +3059,15 @@ export default {
this.view.senior.assistLine = val
this.calcData()
},
onTrendLineChange(val) {
this.view.senior.trendLine = val
this.calcData()
},
onThresholdChange(val) {
this.view.senior.threshold = val
this.calcData()
},
onScrollChange(val) {
this.view.senior.scrollCfg = val
this.calcStyle()