From c8488c335bc925d43c4ba47a4821a543779da67e Mon Sep 17 00:00:00 2001 From: jianneng-fit2cloud Date: Thu, 12 Sep 2024 21:08:07 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E8=A7=86=E5=9B=BE):=20=E6=94=AF=E6=8C=81K?= =?UTF-8?q?=E7=BA=BF=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/frontend/src/icons/svg/stock-line.svg | 11 + core/frontend/src/lang/en.js | 1 + core/frontend/src/lang/tw.js | 1 + core/frontend/src/lang/zh.js | 1 + .../src/views/chart/chart/bar/bar_antv.js | 208 ++++++++- .../views/chart/chart/bar/stock_line_util.js | 423 ++++++++++++++++++ core/frontend/src/views/chart/chart/util.js | 69 ++- .../chart/components/ChartComponentG2.vue | 10 +- .../chart/components/senior/FunctionCfg.vue | 18 +- .../src/views/chart/view/ChartEdit.vue | 2 +- .../src/views/chart/view/ChartType.vue | 4 + 11 files changed, 739 insertions(+), 9 deletions(-) create mode 100644 core/frontend/src/icons/svg/stock-line.svg create mode 100644 core/frontend/src/views/chart/chart/bar/stock_line_util.js diff --git a/core/frontend/src/icons/svg/stock-line.svg b/core/frontend/src/icons/svg/stock-line.svg new file mode 100644 index 0000000000..b241a48968 --- /dev/null +++ b/core/frontend/src/icons/svg/stock-line.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/core/frontend/src/lang/en.js b/core/frontend/src/lang/en.js index 6764dc5008..baa7c3ac88 100644 --- a/core/frontend/src/lang/en.js +++ b/core/frontend/src/lang/en.js @@ -1412,6 +1412,7 @@ export default { chart_bar_stack_horizontal: 'Stack Horizontal Bar', chart_percentage_bar_stack_horizontal: 'Horizontal Percentage Stack Bar', chart_bidirectional_bar: 'Bidirectional Bar', + chart_stock_line: 'Stock Line', chart_line: 'Base Line', chart_line_stack: 'Stack Line', chart_pie: 'Pie', diff --git a/core/frontend/src/lang/tw.js b/core/frontend/src/lang/tw.js index 466cf44667..2914c48904 100644 --- a/core/frontend/src/lang/tw.js +++ b/core/frontend/src/lang/tw.js @@ -1411,6 +1411,7 @@ export default { chart_bar_stack_horizontal: '橫嚮堆疊柱狀圖', chart_percentage_bar_stack_horizontal: '橫嚮百分比柱狀圖', chart_bidirectional_bar: '對稱柱狀圖', + chart_stock_line: 'K 線圖', chart_line: '基礎摺線圖', chart_line_stack: '堆疊摺線圖', chart_pie: '餅圖', diff --git a/core/frontend/src/lang/zh.js b/core/frontend/src/lang/zh.js index 70508fc6fa..7a3ac36706 100644 --- a/core/frontend/src/lang/zh.js +++ b/core/frontend/src/lang/zh.js @@ -1408,6 +1408,7 @@ export default { chart_bar_stack_horizontal: '横向堆叠柱状图', chart_percentage_bar_stack_horizontal: '横向百分比柱状图', chart_bidirectional_bar: '对称柱状图', + chart_stock_line: 'K 线图', chart_line: '基础折线图', chart_line_stack: '堆叠折线图', chart_pie: '饼图', diff --git a/core/frontend/src/views/chart/chart/bar/bar_antv.js b/core/frontend/src/views/chart/chart/bar/bar_antv.js index ec183ee775..809d81e0f4 100644 --- a/core/frontend/src/views/chart/chart/bar/bar_antv.js +++ b/core/frontend/src/views/chart/chart/bar/bar_antv.js @@ -1,4 +1,4 @@ -import { Column, Bar, BidirectionalBar } from '@antv/g2plot' +import {Column, Bar, BidirectionalBar, Mix} from '@antv/g2plot' import { getTheme, getLabel, @@ -17,6 +17,14 @@ import { import { antVCustomColor, getColors, handleEmptyDataStrategy, hexColorToRGBA, handleStackSort } from '@/views/chart/chart/util' import { cloneDeep, find, groupBy, each } from 'lodash-es' import { formatterItem, valueFormatter } from '@/views/chart/chart/formatter' +import { + calculateMinMax, + calculateMovingAverage, + configXAxis, configYAxis, + configBasicStyle, + configTooltip, + registerEvent, customConfigEmptyDataStrategy +} from "@/views/chart/chart/bar/stock_line_util"; export function baseBarOptionAntV(container, chart, action, isGroup, isStack) { // theme @@ -587,3 +595,201 @@ export function baseBidirectionalBarOptionAntV(container, chart, action, isGroup configPlotTooltipEvent(chart, plot) return plot } + +export function stockLineOptionAntV(container, chart, action) { + if (!chart.data?.data?.length) { + return + } + const xAxis = JSON.parse(chart.xaxis) + const yAxis = JSON.parse(chart.yaxis) + if (yAxis.length !== 4) { + return + } + const theme = getTheme(chart) + const legend = getLegend(chart) + const basicStyle = JSON.parse(chart.customAttr).color + const colors = [] + const alpha = basicStyle.alpha + basicStyle.colors.forEach(ele => { + colors.push(hexColorToRGBA(ele, alpha)) + }) + const data = cloneDeep(chart.data?.tableRow ?? []) + + // 时间字段 + const xAxisDataeaseName = xAxis[0].dataeaseName + const averages = [5, 10, 20, 60, 120, 180] + const legendItems = [ + { + name: '日K', + value: 'k', + marker: { + symbol: (x, y, r) => { + const width = r * 1 + const height = r + return [ + // 矩形框 + ['M', x - width - 1 / 2, y - height / 2], + ['L', x + width + 1 / 2, y - height / 2], + ['L', x + width + 1 / 2, y + height / 2], + ['L', x - width - 1 / 2, y + height / 2], + ['Z'], + // 中线 + ['M', x, y + 10 / 2], + ['L', x, y - 10 / 2] + ] + }, + style: { fill: 'red', stroke: 'red', lineWidth: 2 } + } + } + ] + // 计算均线数据 + const averagesLineData = new Map() + averages.forEach(item => { + averagesLineData.set('ma' + item, calculateMovingAverage(data, item, chart)) + }) + + // 将均线数据设置到主数据中 + data.forEach((item) => { + const date = item[xAxisDataeaseName] + for (const [key, value] of averagesLineData) { + item[key] = value.find(m => m[xAxisDataeaseName] === date)?.value + } + }) + + const averageLines = [] + let index = 0 + const start = 0.5 + const end = 1 + const startIndex = Math.floor(start * data.length) + const endIndex = Math.ceil(end * data.length) + const filteredData = data.slice(startIndex, endIndex) + const { maxValue, minValue } = calculateMinMax(filteredData) + for (const key of averagesLineData.keys()) { + index++ + averageLines.push({ + type: 'line', + top: true, + options: { + smooth: false, + xField: xAxisDataeaseName, + yField: key, + color: colors[index - 1], + xAxis: null, + yAxis: { + label: false, + min: minValue, + max: maxValue, + grid: null, + line: null + }, + lineStyle: { + lineWidth: 2 + } + } + }) + legendItems.push({ + name: key.toUpperCase(), + value: key, + marker: { symbol: 'hyphen', style: { stroke: colors[index - 1], lineWidth: 2 } } + }) + } + const axis =JSON.parse(chart.xaxis) ?? [] + let dateFormat + const dateSplit = axis[0]?.datePattern === 'date_split' ? '/' : '-' + switch (axis[0]?.dateStyle) { + case 'y': + dateFormat = 'YYYY' + break + case 'y_M': + dateFormat = 'YYYY' + dateSplit + 'MM' + break + case 'y_M_d': + dateFormat = 'YYYY' + dateSplit + 'MM' + dateSplit + 'DD' + break + case 'y_M_d_H': + dateFormat = 'YYYY' + dateSplit + 'MM' + dateSplit + 'DD' + ' HH' + break + case 'y_M_d_H_m': + dateFormat = 'YYYY' + dateSplit + 'MM' + dateSplit + 'DD' + ' HH:mm' + break + case 'y_M_d_H_m_s': + dateFormat = 'YYYY' + dateSplit + 'MM' + dateSplit + 'DD' + ' HH:mm:ss' + break + default: + dateFormat = 'YYYY-MM-dd HH:mm:ss' + } + let option = { + data, + theme, + appendPadding: getPadding(chart), + slider: { + start: 0.5, + end: 1 + }, + plots: [ + ...averageLines, + { + type: 'stock', + top: true, + options: { + meta: { + [xAxisDataeaseName]: { + mask: dateFormat + } + }, + stockStyle: { + stroke: 'black', + lineWidth: 0.5 + }, + xField: xAxisDataeaseName, + yField: [ + yAxis[0].dataeaseName, + yAxis[1].dataeaseName, + yAxis[2].dataeaseName, + yAxis[3].dataeaseName + ], + legend: !legend?false:{ + position: 'top', + custom: true, + items: legendItems + }, + fallingFill: hexColorToRGBA('#ef5350', alpha), + risingFill: hexColorToRGBA('#26a69a', alpha), + } + } + ] + } + option = configBasicStyle(chart, option) + option = configXAxis(chart, option) + option = configYAxis(chart, option) + option = configTooltip(chart, option) + option = customConfigEmptyDataStrategy(chart,option) + const plot = new Mix(container, option) + registerEvent(data, plot, averagesLineData) + plot.on('schema:click', evt => { + const selectSchema = evt.data.data[xAxisDataeaseName] + const paramData = cloneDeep(chart.data?.data ?? []) + const selectData = paramData.filter(item => item.field === selectSchema) + const quotaList = [] + selectData.forEach(item => { + quotaList.push({ ...item.quotaList[0], value: item.value }) + }) + if (selectData.length) { + const param = { + x: evt.x, + y: evt.y, + data: { + data: { + ...evt.data.data, + value: quotaList[0].value, + name: selectSchema, + dimensionList: selectData[0].dimensionList, + quotaList: quotaList + } + } + } + action(param) + } + }) + return plot +} diff --git a/core/frontend/src/views/chart/chart/bar/stock_line_util.js b/core/frontend/src/views/chart/chart/bar/stock_line_util.js new file mode 100644 index 0000000000..03be84d0d5 --- /dev/null +++ b/core/frontend/src/views/chart/chart/bar/stock_line_util.js @@ -0,0 +1,423 @@ +import {getXAxis, getYAxis} from '@/views/chart/chart/common/common_antv' +import { valueFormatter } from '@/views/chart/chart/formatter' +import {cloneDeep} from "lodash"; +import {handleEmptyDataStrategy} from "@/views/chart/chart/util"; + +/** + * 计算收盘价平均值 + * @param data + * @param dayCount + * @param chart + */ +export const calculateMovingAverage = (data, dayCount, chart) => { + const xAxis = JSON.parse(chart.xaxis) + const yAxis = JSON.parse(chart.yaxis) + // 时间字段 + const xAxisDataeaseName = xAxis[0].dataeaseName + // 收盘价字段 + const yAxisDataeaseName = yAxis[1].dataeaseName + const result = [] + for (let i = 0; i < data.length; i++) { + if (i < dayCount) { + result.push({ + [xAxisDataeaseName]: data[i][xAxisDataeaseName], + value: null + }) + } else { + const sum = data + .slice(i - dayCount + 1, i + 1) + .reduce((sum, item) => sum + item[yAxisDataeaseName], 0) + result.push({ + [xAxisDataeaseName]: data[i][xAxisDataeaseName], + value: parseFloat((sum / dayCount).toFixed(3)) + }) + } + } + return result +} + +/** + * 获取数据集合中对象属性值的最大最小值 + * @param data + */ +export const calculateMinMax = data => { + return data.reduce( + (acc, current) => { + // 获取 current 对象的所有属性值 + const values = Object.values(current) + // 过滤出数字值 + const numericValues = values.filter(value => typeof value === 'number') ?? [] + // 找到 current 对象的数字属性值中的最大值和最小值 + // 如果存在数字值,则计算当前对象的最大值和最小值 + if (numericValues.length > 0) { + const currentMax = Math.max(...numericValues) + const currentMin = Math.min(...numericValues) + // 更新全局最大值和最小值 + acc.maxValue = Math.max(acc.maxValue, currentMax) + acc.minValue = Math.min(acc.minValue, currentMin) + } + return acc + }, + { maxValue: Number.NEGATIVE_INFINITY, minValue: Number.POSITIVE_INFINITY } + ) +} + +/** + * 注册图表事件 + * @param data + * @param plot + * @param averagesLineData + */ +export const registerEvent = (data, plot, averagesLineData) => { + // 监听图例点击事件,显示隐藏 + let risingVisible = true + plot.on('legend-item:click', evt => { + const { value } = evt.target.get('delegateObject').item + if (value === 'k') { + risingVisible = !risingVisible + plot.chart.geometries.forEach(geom => { + if (geom.type === 'schema') { + geom.changeVisible(risingVisible) + } + }) + } else { + const lines = plot.chart.geometries.filter(item => item.type === 'line') + const points = plot.chart.geometries.filter(item => item.type === 'point') + let lineIndex = 0 + for (const key of averagesLineData.keys()) { + lineIndex++ + if (key === value) { + lines[lineIndex - 1].changeVisible(!lines[lineIndex - 1].visible) + points[lineIndex - 1].changeVisible(!points[lineIndex - 1].visible) + } + } + } + }) + // 监听图表渲染事件 + plot.on('afterrender', e => { + let first = false + if (plot.chart.options.slider.start === 0.5 && plot.chart.options.slider.end === 1) { + first = true + } + if (e.view?.options?.scales) { + const startIndex = Math.floor(0.5 * data.length) + const endIndex = Math.ceil(1 * data.length) + const filteredData = data.slice(startIndex, endIndex) + const { maxValue, minValue } = calculateMinMax( + first ? filteredData : e.view.filteredData + ) + const a = e.view.options.scales + Object.keys(a).forEach(item => { + if (a[item].max) { + a[item].max = maxValue + } + if (a[item].min) { + a[item].min = minValue + } + }) + } + }) + // 监听图例组点击事件,设置缩放 + plot.on('legend-item-group:click', e => { + if (e.view?.options?.scales) { + const { maxValue, minValue } = calculateMinMax(e.view.filteredData) + const a = e.view.options.scales + Object.keys(a).forEach(item => { + if (a[item].max) { + a[item].max = maxValue + } + if (a[item].min) { + a[item].min = minValue + } + }) + } + }) + // 监听滑块事件,设置缩放 + plot.on('slider:valuechanged', e => { + const start = e.gEvent.currentTarget.cfg.component.cfg.start + const end = e.gEvent.currentTarget.cfg.component.cfg.end + plot.chart.options.slider.start = start + plot.chart.options.slider.end = end + const startIndex = Math.floor(start * data.length) + const endIndex = Math.ceil(end * data.length) + const filteredData = data.slice(startIndex, endIndex) + const { maxValue, minValue } = calculateMinMax(filteredData) + const a = e.view.options.scales + Object.keys(a).forEach(item => { + if (a[item].max) { + a[item].max = maxValue + } + if (a[item].min) { + a[item].min = minValue + } + }) + }) +} + +export const configBasicStyle = (chart, options) => { + // size + const customAttr = JSON.parse(chart.customAttr) + const s = JSON.parse(JSON.stringify(customAttr.size)) + const smooth = s.lineSmooth + const point = { + size: s.lineSymbolSize, + shape: s.lineSymbol + } + const lineStyle = { + lineWidth: s.lineWidth + } + const plots = [] + options.plots.forEach(item => { + if (item.type === 'line') { + plots.push({ ...item, options: { ...item.options, smooth, point, lineStyle } }) + } + if (item.type === 'stock') { + plots.push({ ...item }) + } + }) + return { + ...options, + plots + } +} + +export const configTooltip = (chart, options)=> { + const tooltipAttr = JSON.parse(chart.customAttr).tooltip + const newPlots = [] + const linePlotList = options.plots.filter(item => item.type === 'line') + linePlotList.forEach(item => { + newPlots.push(item) + }) + const stockPlot = options.plots.filter(item => item.type === 'stock')[0] + if (!tooltipAttr.show) { + const stockOption = { + ...stockPlot.options, + tooltip: { + showContent: false + } + } + newPlots.push({ ...stockPlot, options: stockOption }) + return { + ...options, + plots: newPlots + } + } + + const showFiled = chart.data.fields + const yAxis = cloneDeep(JSON.parse(chart.yaxis)) + const customTooltipItems = originalItems => { + const formattedItems = originalItems.map(item => { + const fieldObj = showFiled.find(q => q.dataeaseName === item.name) + const displayName = fieldObj?.chartShowName || fieldObj?.name || item.name + const formattedName = displayName.startsWith('ma') ? displayName.toUpperCase() : displayName + if(!yAxis[0].formatterCfg){ + yAxis[0].formatterCfg = { + type: 'value', // auto,value,percent + unit: 1, // 换算单位 + suffix: '', // 单位后缀 + decimalCount: 3, // 小数位数 + thousandSeparator: true// 千分符 + } + } + if(yAxis[0].formatterCfg.type === 'auto'){ + yAxis[0].formatterCfg.type = 'value' + yAxis[0].formatterCfg.decimalCount = 3 + } + const formattedValue = valueFormatter(item.value, yAxis[0].formatterCfg) + return { + ...item, + name: formattedName, + value: formattedValue, + color: item.color + } + }) + + const hasKLine = formattedItems.some(item => !item.name.startsWith('MA')) + const kLines = formattedItems.filter(item => !item.name.startsWith('MA')) + return hasKLine + ? [ + { name: '日K', value: '', marker: true, color: kLines[0]?.color }, + ...kLines, + ...formattedItems.filter(item => item.name.startsWith('MA')) + ] + : formattedItems + } + const formatTooltipItem = (item) => { + const size = item.name.startsWith('MA') || !item.value ? 10 : 5 + const markerMarginRight = item.name.startsWith('MA') || !item.value ? 5 : 9 + const markerMarginLeft = item.name.startsWith('MA') || !item.value ? 0 : 2 + return ` +
  • +
    + +
    +
    + ${item.name} + ${item.name.startsWith('MA') && item.value === '0' ? '-' : item.value} +
    +
  • + ` + } + const generateCustomTooltipContent = (title, items) => { + return ` +
    +
    ${title}
    + +
    + ` + } + const stockOption = { + ...stockPlot.options, + tooltip: { + showMarkers: true, + showCrosshairs: true, + showNil: true, + crosshairs: { + follow: true, + text: (axisType, value, data) => { + if (axisType === 'y') { + return { content: value ? value.toFixed(0) : value } + } + return { content: data[0].title, position: 'end' } + } + }, + showContent: true, + customItems: customTooltipItems, + customContent: generateCustomTooltipContent + } + } + newPlots.push({ ...stockPlot, options: stockOption }) + return { + ...options, + plots: newPlots + } +} + +export const configXAxis = (chart, options) => { + const xAxisOptions = getXAxis(chart) + if (!xAxisOptions) { + options.plots.forEach(item => { + if(item.type === 'stock'){ + item.options.xAxis = false + } + }) + return options + } + const newPlots = [] + const linePlotList = options.plots.filter(item => item.type === 'line') + + const stockPlot = options.plots.filter(item => item.type === 'stock')[0] + const newStockPlot = { + ...stockPlot, + options: { + ...stockPlot.options, + xAxis: xAxisOptions + ? { + ...stockPlot.options['xAxis'], + ...xAxisOptions + } + : { + label: false, + line: null + } + } + } + newPlots.push(newStockPlot) + linePlotList.forEach(item => { + newPlots.push(item) + }) + return { + ...options, + plots: newPlots + } +} + +export const configYAxis = (chart, options) => { + const yAxisOptions = getYAxis(chart) + if (!yAxisOptions) { + options.plots.forEach(item => { + if(item.type === 'stock'){ + item.options.yAxis = false + } + }) + return options + } + const yAxis = JSON.parse(chart.yaxis) + const newPlots = [] + const linePlotList = options.plots.filter(item => item.type === 'line') + + const stockPlot = options.plots.filter(item => item.type === 'stock')[0] + let label = false + if (yAxisOptions.label) { + label = { + ...yAxisOptions.label + } + } + const newStockPlot = { + ...stockPlot, + options: { + ...stockPlot.options, + yAxis: label + ? { + ...stockPlot.options['yAxis'], + ...yAxisOptions, + label + } + : { + label, + grid: null, + line: null + } + } + } + newPlots.push(newStockPlot) + linePlotList.forEach(item => { + newPlots.push(item) + }) + return { + ...options, + plots: newPlots + } +} + +export const customConfigEmptyDataStrategy = (chart, options) => { + const { data } = options + if (!data?.length) { + return options + } + const strategy = JSON.parse(chart.senior)?.functionCfg?.emptyDataStrategy + if (strategy === 'ignoreData') { + for (let i = data.length - 1; i >= 0; i--) { + const item = data[i] + Object.keys(item).forEach(key => { + if (key.startsWith('C_') && item[key] === null) { + data.splice(i, 1) + } + }) + } + } + const updateValues = (strategy, data) => { + data.forEach(obj => { + Object.keys(obj).forEach(key => { + if (key.startsWith('C_') && obj[key] === null) { + obj[key] = strategy === 'breakLine' ? null : 0 + } + }) + }) + } + if (strategy === 'breakLine' || strategy === 'setZero') { + updateValues(strategy, data) + } + return options +} diff --git a/core/frontend/src/views/chart/chart/util.js b/core/frontend/src/views/chart/chart/util.js index bbaf1b0b2f..f577e96c70 100644 --- a/core/frontend/src/views/chart/chart/util.js +++ b/core/frontend/src/views/chart/chart/util.js @@ -1738,7 +1738,74 @@ export const TYPE_CONFIGS = [ ] } }, - + { + render: 'antv', + category: 'chart.chart_type_compare', + value: 'stock-line', + title: 'chart.chart_stock_line', + icon: 'stock-line', + properties: [ + 'color-selector', + 'size-selector', + 'tooltip-selector-ant-v', + 'x-axis-selector-ant-v', + 'y-axis-selector-ant-v', + 'title-selector-ant-v', + 'legend-selector-ant-v' + ], + propertyInner: { + 'color-selector': [ + 'value', + 'colorPanel', + 'customColor', + 'alpha' + ], + 'size-selector': [ + 'lineWidth', + 'lineSymbol', + 'lineSymbolSize', + 'lineSmooth' + ], + 'tooltip-selector-ant-v': [ + 'show', + 'textStyle' + ], + 'x-axis-selector-ant-v': [ + 'show', + 'position', + 'splitLine', + 'axisForm', + 'axisLabel' + ], + 'y-axis-selector-ant-v': [ + 'show', + 'position', + 'name', + 'nameTextStyle', + 'axisValue', + 'splitLine', + 'axisForm', + 'axisLabel' + ], + 'title-selector-ant-v': [ + 'show', + 'title', + 'fontSize', + 'color', + 'hPosition', + 'isItalic', + 'isBolder', + 'remarkShow', + 'fontFamily', + 'letterSpace', + 'fontShadow' + ], + 'legend-selector-ant-v': [ + 'show', + 'textStyle', + ] + } + }, { render: 'antv', category: 'chart.chart_type_distribute', diff --git a/core/frontend/src/views/chart/components/ChartComponentG2.vue b/core/frontend/src/views/chart/components/ChartComponentG2.vue index 49a66692b0..d571a0b302 100644 --- a/core/frontend/src/views/chart/components/ChartComponentG2.vue +++ b/core/frontend/src/views/chart/components/ChartComponentG2.vue @@ -45,7 +45,13 @@ import { baseLiquid } from '@/views/chart/chart/liquid/liquid' import { uuid } from 'vue-uuid' import ViewTrackBar from '@/components/canvas/components/editor/ViewTrackBar' import { adjustPosition, getRemark, hexColorToRGBA } from '@/views/chart/chart/util' -import { baseBarOptionAntV, hBaseBarOptionAntV, baseBidirectionalBarOptionAntV, timeRangeBarOptionAntV } from '@/views/chart/chart/bar/bar_antv' +import { + baseBarOptionAntV, + hBaseBarOptionAntV, + baseBidirectionalBarOptionAntV, + timeRangeBarOptionAntV, + stockLineOptionAntV +} from '@/views/chart/chart/bar/bar_antv' import { baseAreaOptionAntV, baseLineOptionAntV } from '@/views/chart/chart/line/line_antv' import { basePieOptionAntV, basePieRoseOptionAntV } from '@/views/chart/chart/pie/pie_antv' import { baseScatterOptionAntV } from '@/views/chart/chart/scatter/scatter_antv' @@ -298,6 +304,8 @@ export default { this.myChart = await baseFlowMapOption(this.chartId, chart, this.antVAction) } else if (chart.type === 'bidirectional-bar') { this.myChart = baseBidirectionalBarOptionAntV(this.chartId, chart, this.antVAction) + } else if (chart.type === 'stock-line'){ + this.myChart = stockLineOptionAntV(this.chartId, chart, this.antVAction) } if (this.myChart && !equalsAny(chart.type, 'liquid', 'flow-map') && this.searchCount > 0) { diff --git a/core/frontend/src/views/chart/components/senior/FunctionCfg.vue b/core/frontend/src/views/chart/components/senior/FunctionCfg.vue index e5b99b5135..44faea9835 100644 --- a/core/frontend/src/views/chart/components/senior/FunctionCfg.vue +++ b/core/frontend/src/views/chart/components/senior/FunctionCfg.vue @@ -78,7 +78,7 @@ v-model="functionForm.emptyDataStrategy" @change="changeFunctionCfg" > - {{ $t('chart.break_line') }} + {{ $t('chart.break_line') }} {{ $t('chart.set_zero') }} import { DEFAULT_FUNCTION_CFG, COLOR_PANEL } from '../../chart/chart' import { equalsAny, includesAny } from '@/utils/StringUtils' +import {cloneDeep} from "lodash"; export default { name: 'FunctionCfg', @@ -134,7 +135,7 @@ export default { showSlider() { return this.chart.type !== 'bidirectional-bar' && !equalsAny(this.chart.type, 'map') && - !includesAny(this.chart.type, 'table', 'text') + !includesAny(this.chart.type, 'table', 'text','stock-line') }, showEmptyStrategy() { return (this.chart.render === 'antv' && @@ -148,7 +149,10 @@ export default { return this.showEmptyStrategy && includesAny(this.chart.type, 'table') && this.functionForm.emptyDataStrategy !== 'breakLine' - } + }, + showBreakOption() { + return !equalsAny(this.chart.type, 'stock-line') + }, }, watch: { 'chart': { @@ -170,10 +174,14 @@ export default { } else { senior = JSON.parse(chart.senior) } + const defaultFunctionCfg = cloneDeep(DEFAULT_FUNCTION_CFG) + if (equalsAny(this.chart.type, 'stock-line')) { + defaultFunctionCfg.emptyDataStrategy = 'setZero' + } if (senior.functionCfg) { - this.functionForm = { ...DEFAULT_FUNCTION_CFG, ...senior.functionCfg } + this.functionForm = { ...defaultFunctionCfg, ...senior.functionCfg } } else { - this.functionForm = JSON.parse(JSON.stringify(DEFAULT_FUNCTION_CFG)) + this.functionForm = JSON.parse(JSON.stringify(defaultFunctionCfg)) } this.initFieldCtrl() } diff --git a/core/frontend/src/views/chart/view/ChartEdit.vue b/core/frontend/src/views/chart/view/ChartEdit.vue index 9ff6c500c2..d579a0de56 100644 --- a/core/frontend/src/views/chart/view/ChartEdit.vue +++ b/core/frontend/src/views/chart/view/ChartEdit.vue @@ -2194,7 +2194,7 @@ export default { return equalsAny(this.view.type, 'table-normal', 'table-info', 'table-pivot') }, showAnalyseCfg() { - if (this.view.type === 'bidirectional-bar' || this.view.type === 'bar-time-range') { + if (this.view.type === 'bidirectional-bar' || this.view.type === 'bar-time-range' || this.view.type === 'stock-line') { return false } return includesAny(this.view.type, 'bar', 'line', 'area', 'gauge', 'liquid') || diff --git a/core/frontend/src/views/chart/view/ChartType.vue b/core/frontend/src/views/chart/view/ChartType.vue index 56385af178..81a9a79df9 100644 --- a/core/frontend/src/views/chart/view/ChartType.vue +++ b/core/frontend/src/views/chart/view/ChartType.vue @@ -161,6 +161,10 @@ export default { margin:5px; } +.el-radio:last-child{ + margin-right: 5px; +} + .radio-row{ width: 100%; display: flex;