From 8cc1aab59f1087b695a00de905d4f366a67cf5d8 Mon Sep 17 00:00:00 2001 From: jianneng-fit2cloud Date: Wed, 22 May 2024 20:31:45 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=9B=BE=E8=A1=A8):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E6=B5=81=E5=90=91=E5=9C=B0=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/assets/svg/bar-bidirectional-dark.svg | 11 + .../src/assets/svg/bar-bidirectional.svg | 11 + .../src/assets/svg/bar-progress-dark.svg | 11 + .../src/assets/svg/bar-progress.svg | 11 + .../src/assets/svg/flow-map-origin.svg | 69 +++++ .../core-frontend/src/assets/svg/flow-map.svg | 79 +++++ .../data-visualization/canvas/Shape.vue | 2 +- .../src/models/chart/chart-attr.d.ts | 6 +- .../editor/editor-style/ChartStyle.vue | 1 + .../components/BasicStyleSelector.vue | 273 ++++++++++++++++-- .../chart/components/editor/util/chart.ts | 11 +- .../js/panel/charts/map/flow-map.ts | 138 +++++++++ .../components/js/panel/common/common_antv.ts | 12 +- .../components/js/panel/types/impl/l7.ts | 95 ++++++ .../chart/components/js/panel/types/index.ts | 1 + .../views/components/ChartComponentG2Plot.vue | 25 +- .../views/chart/components/views/index.vue | 9 +- 17 files changed, 725 insertions(+), 40 deletions(-) create mode 100644 core/core-frontend/src/assets/svg/bar-bidirectional-dark.svg create mode 100644 core/core-frontend/src/assets/svg/bar-bidirectional.svg create mode 100644 core/core-frontend/src/assets/svg/bar-progress-dark.svg create mode 100644 core/core-frontend/src/assets/svg/bar-progress.svg create mode 100644 core/core-frontend/src/assets/svg/flow-map-origin.svg create mode 100644 core/core-frontend/src/assets/svg/flow-map.svg create mode 100644 core/core-frontend/src/views/chart/components/js/panel/charts/map/flow-map.ts create mode 100644 core/core-frontend/src/views/chart/components/js/panel/types/impl/l7.ts diff --git a/core/core-frontend/src/assets/svg/bar-bidirectional-dark.svg b/core/core-frontend/src/assets/svg/bar-bidirectional-dark.svg new file mode 100644 index 0000000000..f95fe09fb2 --- /dev/null +++ b/core/core-frontend/src/assets/svg/bar-bidirectional-dark.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/core/core-frontend/src/assets/svg/bar-bidirectional.svg b/core/core-frontend/src/assets/svg/bar-bidirectional.svg new file mode 100644 index 0000000000..31f5b63283 --- /dev/null +++ b/core/core-frontend/src/assets/svg/bar-bidirectional.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/core/core-frontend/src/assets/svg/bar-progress-dark.svg b/core/core-frontend/src/assets/svg/bar-progress-dark.svg new file mode 100644 index 0000000000..2f0ea808ae --- /dev/null +++ b/core/core-frontend/src/assets/svg/bar-progress-dark.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/core/core-frontend/src/assets/svg/bar-progress.svg b/core/core-frontend/src/assets/svg/bar-progress.svg new file mode 100644 index 0000000000..c75e98a48a --- /dev/null +++ b/core/core-frontend/src/assets/svg/bar-progress.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/core/core-frontend/src/assets/svg/flow-map-origin.svg b/core/core-frontend/src/assets/svg/flow-map-origin.svg new file mode 100644 index 0000000000..eaef7f8ea5 --- /dev/null +++ b/core/core-frontend/src/assets/svg/flow-map-origin.svg @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/core-frontend/src/assets/svg/flow-map.svg b/core/core-frontend/src/assets/svg/flow-map.svg new file mode 100644 index 0000000000..6c691c031b --- /dev/null +++ b/core/core-frontend/src/assets/svg/flow-map.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/core-frontend/src/components/data-visualization/canvas/Shape.vue b/core/core-frontend/src/components/data-visualization/canvas/Shape.vue index 0f8e108d05..a8559c8291 100644 --- a/core/core-frontend/src/components/data-visualization/canvas/Shape.vue +++ b/core/core-frontend/src/components/data-visualization/canvas/Shape.vue @@ -294,7 +294,7 @@ const active = computed(() => { }) const boardMoveActive = computed(() => { - const CHARTS = ['map', 'bubble-map', 'table-info', 'table-normal', 'table-pivot'] + const CHARTS = ['flow-map', 'map', 'bubble-map', 'table-info', 'table-normal', 'table-pivot'] return CHARTS.includes(element.value.innerType) }) diff --git a/core/core-frontend/src/models/chart/chart-attr.d.ts b/core/core-frontend/src/models/chart/chart-attr.d.ts index 35ff617b66..63f6bb9e23 100644 --- a/core/core-frontend/src/models/chart/chart-attr.d.ts +++ b/core/core-frontend/src/models/chart/chart-attr.d.ts @@ -232,7 +232,7 @@ declare interface ChartBasicStyle { /** * 对称柱状图方向 */ - layout: 'horizontal' | 'vertical' + layout?: 'horizontal' | 'vertical' } /** * 表头属性 @@ -482,6 +482,10 @@ declare interface ChartMiscAttr { * 地图线条宽度 */ mapLineWidth: number + /** + * 流向地图动画 + */ + mapLineAnimate?: boolean /** * 流向地图动画间隔 */ diff --git a/core/core-frontend/src/views/chart/components/editor/editor-style/ChartStyle.vue b/core/core-frontend/src/views/chart/components/editor/editor-style/ChartStyle.vue index 893bb3795f..eb18ae6c5d 100644 --- a/core/core-frontend/src/views/chart/components/editor/editor-style/ChartStyle.vue +++ b/core/core-frontend/src/views/chart/components/editor/editor-style/ChartStyle.vue @@ -221,6 +221,7 @@ watch( :themes="themes" :chart="chart" @onBasicStyleChange="onBasicStyleChange" + @onMiscChange="onMiscChange" /> import { onMounted, PropType, reactive, watch } from 'vue' -import { COLOR_PANEL, DEFAULT_BASIC_STYLE } from '@/views/chart/components/editor/util/chart' +import { + COLOR_PANEL, + DEFAULT_BASIC_STYLE, + DEFAULT_MISC +} from '@/views/chart/components/editor/util/chart' import { useI18n } from '@/hooks/web/useI18n' import CustomColorStyleSelect from '@/views/chart/components/editor/editor-style/components/CustomColorStyleSelect.vue' import { cloneDeep, defaultsDeep } from 'lodash-es' @@ -28,6 +32,7 @@ const showProperty = prop => props.propertyInner?.includes(prop) const predefineColors = COLOR_PANEL const state = reactive({ basicStyleForm: JSON.parse(JSON.stringify(DEFAULT_BASIC_STYLE)) as ChartBasicStyle, + miscForm: JSON.parse(JSON.stringify(DEFAULT_MISC)) as ChartMiscAttr, customColor: null, colorIndex: 0, fieldColumnWidth: { @@ -38,6 +43,7 @@ const state = reactive({ watch( [ () => props.chart.customAttr.basicStyle, + () => props.chart.customAttr.misc, () => props.chart.customAttr.tableHeader, () => props.chart.xAxis, () => props.chart.yAxis @@ -47,14 +53,19 @@ watch( }, { deep: true } ) -const emit = defineEmits(['onBasicStyleChange']) +const emit = defineEmits(['onBasicStyleChange', 'onMiscChange']) const changeBasicStyle = (prop?: string, requestData = false) => { emit('onBasicStyleChange', { data: state.basicStyleForm, requestData }, prop) } +const changeMisc = prop => { + emit('onMiscChange', { data: state.miscForm, requestData: true }, prop) +} const init = () => { const basicStyle = cloneDeep(props.chart.customAttr.basicStyle) + const miscStyle = cloneDeep(props.chart.customAttr.misc) configCompat(basicStyle) state.basicStyleForm = defaultsDeep(basicStyle, cloneDeep(DEFAULT_BASIC_STYLE)) as ChartBasicStyle + state.miscForm = defaultsDeep(miscStyle, cloneDeep(DEFAULT_MISC)) as ChartMiscAttr if (!state.customColor) { state.customColor = state.basicStyleForm.colors[0] state.colorIndex = 0 @@ -177,6 +188,12 @@ const mapStyleOptions = [ { name: t('chart.map_style_wine'), value: 'wine' } ] +const flowLineTypeOptions = [ + { name: t('chart.map_line_type_line'), value: 'line' }, + { name: t('chart.map_line_type_arc'), value: 'arc' }, + { name: t('chart.map_line_type_arc_3d'), value: 'arc3d' } +] + onMounted(() => { init() }) @@ -268,7 +285,236 @@ onMounted(() => { {{ t('chart.vertical') }} - + +
+
+ + + + + + + + + +
+ + + + + + + + +
+
+
+ + + + + + + + + +
+ + + + + + + + +
+ + + + + {{ t('chart.line') + t('chart.map_line_linear') }} + + + + +
+ + + + + + + + + + + + +
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + +
+ + + + + {{ t('chart.line') + t('chart.map_line_animate') }} + + + + +
+ + + + + + + + +
+
+
+ @@ -658,27 +904,6 @@ onMounted(() => { - - - - - - - { + properties: EditorProperty[] = [ + 'background-overall-component', + 'basic-style-selector', + 'title-selector' + ] + propertyInner: EditorPropertyInner = { + ...MAP_EDITOR_PROPERTY_INNER, + 'basic-style-selector': ['mapStyle', 'zoom'] + } + axis: AxisType[] = ['xAxis', 'xAxisExt', 'filter'] + axisConfig: AxisConfig = { + xAxis: { + name: `起点经纬度 / ${t('chart.dimension')}`, + type: 'd', + limit: 2 + }, + xAxisExt: { + name: `终点经纬度 / ${t('chart.dimension')}`, + type: 'd', + limit: 2 + } + } + constructor() { + super('flow-map', []) + } + + async drawChart(drawOption: L7DrawConfig) { + const { chart, container } = drawOption + const xAxis = deepCopy(chart.xAxis) + const xAxisExt = deepCopy(chart.xAxisExt) + let basicStyle + let miscStyle + if (chart.customAttr) { + basicStyle = parseJson(chart.customAttr).basicStyle + miscStyle = parseJson(chart.customAttr).misc + } + const flowLineStyle = { + type: miscStyle.mapLineType, + size: miscStyle.mapLineWidth, + animate: miscStyle.mapLineAnimate, + animateDuration: miscStyle.mapLineAnimateDuration, + gradient: miscStyle.mapLineGradient, + sourceColor: miscStyle.mapLineSourceColor, + targetColor: miscStyle.mapLineTargetColor, + alpha: basicStyle.alpha + } + const mapStyle = `amap://styles/${basicStyle.mapStyle ? basicStyle.mapStyle : 'normal'}` + const key = await this.getMapKey() + // 底层 + const scene = new Scene({ + id: container, + logoVisible: false, + map: new GaodeMap({ + token: key ?? undefined, + style: mapStyle, + pitch: miscStyle.mapPitch, + zoom: 2.5 + }) + }) + if (xAxis?.length < 2 || xAxisExt?.length < 2) { + return new L7Wrapper(scene, undefined) + } + const config: L7Config = new LineLayer({ + name: 'line', + blend: 'normal', + autoFit: true + }) + .source(chart.data?.tableRow ? chart.data.tableRow : [], { + parser: { + type: 'json', + x: xAxis[0].dataeaseName, + y: xAxis[1].dataeaseName, + x1: xAxisExt[0].dataeaseName, + y1: xAxisExt[1].dataeaseName + } + }) + .size(flowLineStyle.size) + .shape(flowLineStyle.type) + .animate({ + enable: flowLineStyle.animate, + duration: flowLineStyle.animateDuration, + interval: 1, + trailLength: 1 + }) + if (flowLineStyle.gradient) { + config.style({ + sourceColor: flowLineStyle.sourceColor, + targetColor: flowLineStyle.targetColor, + opacity: flowLineStyle.alpha / 100 + }) + } else { + config + .style({ + opacity: flowLineStyle.alpha / 100 + }) + .color(flowLineStyle.sourceColor) + } + this.configZoomButton(chart, scene) + return new L7Wrapper(scene, config) + } + + getMapKey = async () => { + const key = 'online-map-key' + if (!localStorage.getItem(key)) { + await queryMapKeyApi().then(res => localStorage.setItem(key, res.data)) + } + return localStorage.getItem(key) + } + + setupDefaultOptions(chart: ChartObj): ChartObj { + chart.customAttr.misc.mapLineAnimate = true + return chart + } + + protected setupOptions(chart: Chart, config: L7Config): L7Config { + return flow(this.configEmptyDataStrategy)(chart, config) + } +} diff --git a/core/core-frontend/src/views/chart/components/js/panel/common/common_antv.ts b/core/core-frontend/src/views/chart/components/js/panel/common/common_antv.ts index c174ca9a86..a2e3f53e06 100644 --- a/core/core-frontend/src/views/chart/components/js/panel/common/common_antv.ts +++ b/core/core-frontend/src/views/chart/components/js/panel/common/common_antv.ts @@ -26,6 +26,7 @@ import type { Plot as L7Plot, PlotOptions } from '@antv/l7plot/dist/esm' import { Zoom } from '@antv/l7' import { createL7Icon } from '@antv/l7-component/es/utils/icon' import { DOM } from '@antv/l7-utils' +import { Scene } from '@antv/l7-scene' export function getPadding(chart: Chart): number[] { if (chart.drill) { @@ -1023,7 +1024,7 @@ class CustomZoom extends Zoom { this['updateDisabled']() } } -export function configL7Zoom(chart: Chart, plot: L7Plot) { +export function configL7Zoom(chart: Chart, plot: L7Plot | Scene) { const { basicStyle } = parseJson(chart.customAttr) if ( (basicStyle.suspension === false && basicStyle.showZoom === undefined) || @@ -1031,14 +1032,15 @@ export function configL7Zoom(chart: Chart, plot: L7Plot) { ) { return } - plot.once('loaded', () => { + const plotScene = plot instanceof Scene ? plot : plot.scene + plotScene.once('loaded', () => { const zoomOptions = { - initZoom: plot.scene.getZoom(), - center: plot.scene.getCenter(), + initZoom: plotScene.getZoom(), + center: plotScene.getCenter(), buttonColor: basicStyle.zoomButtonColor, buttonBackground: basicStyle.zoomBackground } as any - plot.scene.addControl(new CustomZoom(zoomOptions)) + plotScene.addControl(new CustomZoom(zoomOptions)) }) } diff --git a/core/core-frontend/src/views/chart/components/js/panel/types/impl/l7.ts b/core/core-frontend/src/views/chart/components/js/panel/types/impl/l7.ts new file mode 100644 index 0000000000..de31e95dfa --- /dev/null +++ b/core/core-frontend/src/views/chart/components/js/panel/types/impl/l7.ts @@ -0,0 +1,95 @@ +import { Scene } from '@antv/l7-scene' +import { + AntVAbstractChartView, + AntVDrawOptions, + ChartLibraryType, + ChartWrapper +} from '@/views/chart/components/js/panel/types' +import { cloneDeep } from 'lodash-es' +import { parseJson } from '@/views/chart/components/js/util' +import { ILayer } from '@antv/l7plot' +import { configL7Zoom } from '@/views/chart/components/js/panel/common/common_antv' + +export type L7DrawConfig

= AntVDrawOptions

+export interface L7Config extends ILayer { + handleConfig?: (arg0: Scene) => void + [key: string]: string | any +} +export class L7Wrapper< + O extends L7Config | Array, + S extends Scene +> extends ChartWrapper { + private readonly config: O | Array + private readonly scene: S | null = null + constructor(scene: S, l7config: O | Array | undefined) { + super() + this.chartInstance = scene + this.config = l7config + this.scene = scene + } + destroy = () => { + if (!this.chartInstance) { + return + } + this.chartInstance?.destroy() + } + render = () => { + if (this.scene && this.config) { + this.scene.on('loaded', () => { + if (Array.isArray(this.config)) { + this.config?.forEach(p => { + this.handleConfig(p) + }) + } else { + this.handleConfig(this.config) + } + }) + } + } + + handleConfig = (config: L7Config) => { + if (config.handleConfig) { + config.handleConfig?.(this.scene) + } else { + this.scene.addLayer(config) + } + } +} +export abstract class L7ChartView< + S extends Scene, + O extends L7Config +> extends AntVAbstractChartView { + public abstract drawChart(drawOption: L7DrawConfig): L7Wrapper | any + + protected configEmptyDataStrategy(chart: Chart, options: O): O { + const { functionCfg } = parseJson(chart.senior) + const emptyDataStrategy = functionCfg.emptyDataStrategy + if (!emptyDataStrategy || emptyDataStrategy === 'breakLine') { + return options + } + const data = cloneDeep(options.sourceOption.data) + if (emptyDataStrategy === 'setZero') { + data.forEach(item => { + item.value === null && (item.value = 0) + }) + } + if (emptyDataStrategy === 'ignoreData') { + for (let i = data.length - 1; i >= 0; i--) { + if (data[i].value === null) { + data.splice(i, 1) + } + } + } + options.sourceOption.data = data + return options + } + + protected configZoomButton(chart: Chart, plot: S) { + configL7Zoom(chart, plot) + } + + protected constructor(name: string, defaultData: any[]) { + super(ChartLibraryType.L7, name, defaultData) + } + protected abstract setupOptions(chart: Chart, options: O): O +} diff --git a/core/core-frontend/src/views/chart/components/js/panel/types/index.ts b/core/core-frontend/src/views/chart/components/js/panel/types/index.ts index 8049fa54c6..b527c00c95 100644 --- a/core/core-frontend/src/views/chart/components/js/panel/types/index.ts +++ b/core/core-frontend/src/views/chart/components/js/panel/types/index.ts @@ -11,6 +11,7 @@ export enum ChartRenderType { export enum ChartLibraryType { G2_PLOT = 'g2plot', L7_PLOT = 'l7plot', + L7 = 'l7', ECHARTS = 'echarts', S2 = 's2', RICH_TEXT = 'rich-text', diff --git a/core/core-frontend/src/views/chart/components/views/components/ChartComponentG2Plot.vue b/core/core-frontend/src/views/chart/components/views/components/ChartComponentG2Plot.vue index 2881b696e9..b953c53b94 100644 --- a/core/core-frontend/src/views/chart/components/views/components/ChartComponentG2Plot.vue +++ b/core/core-frontend/src/views/chart/components/views/components/ChartComponentG2Plot.vue @@ -17,6 +17,7 @@ import { customAttrTrans, customStyleTrans, recursionTransObj } from '@/utils/ca import { deepCopy } from '@/utils/utils' import { trackBarStyleCheck } from '@/utils/canvasUtils' import { useEmitt } from '@/hooks/web/useEmitt' +import { L7ChartView } from '@/views/chart/components/js/panel/types/impl/l7' const dvMainStore = dvMainStoreWithOut() const { nowPanelTrackInfo, nowPanelJumpInfo, mobileInPc, embeddedCallBack } = @@ -116,7 +117,7 @@ const calcData = async (view, callback) => { callback?.() }) } else { - if (['bubble-map', 'map'].includes(view.type)) { + if (['bubble-map', 'map', 'flow-map'].includes(view.type)) { await renderChart(view, callback) } callback?.() @@ -141,6 +142,9 @@ const renderChart = async (view, callback?) => { case ChartLibraryType.L7_PLOT: await renderL7Plot(chart, chartView as L7PlotChartView, callback) break + case ChartLibraryType.L7: + await renderL7(chart, chartView as L7ChartView, callback) + break case ChartLibraryType.G2_PLOT: renderG2Plot(chart, chartView as G2PlotChartView) callback?.() @@ -199,6 +203,23 @@ const renderL7Plot = async (chart: ChartObj, chartView: L7PlotChartView, callback) => { + mapL7Timer && clearTimeout(mapL7Timer) + mapL7Timer = setTimeout(async () => { + myChart?.destroy() + myChart = await chartView.drawChart({ + chartObj: myChart, + container: containerId, + chart: chart, + action + }) + myChart?.render() + callback?.() + emit('resetLoading') + }, 500) +} + const pointClickTrans = () => { if (embeddedCallBack.value === 'yes') { trackClick('pointClick') @@ -333,7 +354,7 @@ defineExpose({ }) let resizeObserver const TOLERANCE = 0.01 -const RESIZE_MONITOR_CHARTS = ['map', 'bubble-map'] +const RESIZE_MONITOR_CHARTS = ['map', 'bubble-map', 'flow-map'] onMounted(() => { const containerDom = document.getElementById(containerId) const { offsetWidth, offsetHeight } = containerDom diff --git a/core/core-frontend/src/views/chart/components/views/index.vue b/core/core-frontend/src/views/chart/components/views/index.vue index ff4d63e4ca..242d8d19fe 100644 --- a/core/core-frontend/src/views/chart/components/views/index.vue +++ b/core/core-frontend/src/views/chart/components/views/index.vue @@ -40,7 +40,6 @@ import { Base64 } from 'js-base64' import DeRichTextView from '@/custom-component/rich-text/DeRichTextView.vue' import ChartEmptyInfo from '@/views/chart/components/views/components/ChartEmptyInfo.vue' import { snapshotStoreWithOut } from '@/store/modules/data-visualization/snapshot' -import { useAppStoreWithOut } from '@/store/modules/app' const { wsCache } = useCache() const chartComponent = ref() @@ -105,7 +104,6 @@ const props = defineProps({ }) const dynamicAreaId = ref('') const { view, showPosition, element, active, searchCount, scale } = toRefs(props) -const appStore = useAppStoreWithOut() const titleShow = computed( () => @@ -242,7 +240,6 @@ watch([() => curComponent.value], () => { }) } }) -const isDataEaseBi = computed(() => appStore.getIsDataEaseBi) const chartExtRequest = shallowRef(null) provide('chartExtRequest', chartExtRequest) @@ -568,7 +565,7 @@ const chartAreaShow = computed(() => { return true } if (view.value.customAttr.map.id) { - const MAP_CHARTS = ['map', 'bubble-map'] + const MAP_CHARTS = ['map', 'bubble-map', 'flow-map'] if (MAP_CHARTS.includes(view.value.type)) { return true } @@ -726,7 +723,9 @@ const titleIconStyle = computed(() => { :view="view" :show-position="showPosition" :element="element" - v-else-if="showChartView(ChartLibraryType.G2_PLOT, ChartLibraryType.L7_PLOT)" + v-else-if=" + showChartView(ChartLibraryType.G2_PLOT, ChartLibraryType.L7_PLOT, ChartLibraryType.L7) + " ref="chartComponent" @onChartClick="chartClick" @onPointClick="onPointClick"