From f7aadb877d751e624cfac9f9248168063300ae8a Mon Sep 17 00:00:00 2001 From: ulleo Date: Wed, 29 May 2024 11:45:43 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=9B=BE=E8=A1=A8):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E7=83=AD=E5=8A=9B=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #8108 --- .../chart/manage/ChartDataManage.java | 2 + .../dataease/chart/utils/ChartDataBuild.java | 72 +++ core/core-frontend/src/locales/zh-CN.ts | 7 + .../src/models/chart/chart-attr.d.ts | 3 + .../components/BasicStyleSelector.vue | 473 ++++++++++-------- .../chart/components/editor/util/chart.ts | 10 + .../js/panel/charts/map/heat-map.ts | 120 +++++ 7 files changed, 479 insertions(+), 208 deletions(-) create mode 100644 core/core-frontend/src/views/chart/components/js/panel/charts/map/heat-map.ts diff --git a/core/core-backend/src/main/java/io/dataease/chart/manage/ChartDataManage.java b/core/core-backend/src/main/java/io/dataease/chart/manage/ChartDataManage.java index d37ef830d2..c75ede9ddc 100644 --- a/core/core-backend/src/main/java/io/dataease/chart/manage/ChartDataManage.java +++ b/core/core-backend/src/main/java/io/dataease/chart/manage/ChartDataManage.java @@ -788,6 +788,8 @@ public class ChartDataManage { mapChart = ChartDataBuild.transMixChartDataAntV(xAxis, yAxis, view, data, isDrill); } else if (StringUtils.equalsIgnoreCase(view.getType(), "bar-range")) { mapChart = ChartDataBuild.transBarRangeDataAntV(skipBarRange, barRangeDate, xAxisBase, xAxis, yAxis, view, data, isDrill); + } else if(StringUtils.equalsIgnoreCase(view.getType(), "heat-map")){ + mapChart = ChartDataBuild.transHeatMapChartDataAntV(xAxisBase, xAxis, yAxis, view, data, isDrill); } else { mapChart = ChartDataBuild.transChartDataAntV(xAxis, yAxis, view, data, isDrill); } diff --git a/core/core-backend/src/main/java/io/dataease/chart/utils/ChartDataBuild.java b/core/core-backend/src/main/java/io/dataease/chart/utils/ChartDataBuild.java index f73b9b7b4f..9772403f02 100644 --- a/core/core-backend/src/main/java/io/dataease/chart/utils/ChartDataBuild.java +++ b/core/core-backend/src/main/java/io/dataease/chart/utils/ChartDataBuild.java @@ -4,6 +4,7 @@ import io.dataease.api.chart.dto.*; import io.dataease.i18n.Lang; import io.dataease.i18n.Translator; import io.dataease.utils.IDUtils; +import io.dataease.utils.JsonUtil; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.ObjectUtils; @@ -113,6 +114,77 @@ public class ChartDataBuild { return map; } + public static Map transHeatMapChartDataAntV(List xAxisBase, List xAxis, List yAxis, ChartViewDTO view, List data, boolean isDrill) { + Map map = new HashMap<>(); + List> dataList = new ArrayList<>(); + + if (xAxisBase.size() != 2) { + map.put("data", dataList); + return map; + } + + for (int i1 = 0; i1 < data.size(); i1++) { + String[] row = data.get(i1); + + StringBuilder a = new StringBuilder(); + if (isDrill) { + a.append(row[xAxis.size() - 1]); + } else { + for (int i = 0; i < xAxis.size(); i++) { + if (i == xAxis.size() - 1) { + a.append(row[i]); + } else { + a.append(row[i]).append("\n"); + } + } + } + + // yAxis最后的数据对应extLabel和extTooltip,将他们从yAxis中去掉,同时转换成动态值 + int size = xAxis.size() + yAxis.size(); + int extSize = view.getExtLabel().size() + view.getExtTooltip().size(); + + for (int i = xAxis.size(); i < size - extSize; i++) { + AxisChartDataAntVDTO axisChartDataDTO = new AxisChartDataAntVDTO(); + axisChartDataDTO.setField(a.toString()); + axisChartDataDTO.setName(a.toString()); + + List dimensionList = new ArrayList<>(); + List quotaList = new ArrayList<>(); + + for (int j = 0; j < xAxis.size(); j++) { + ChartDimensionDTO chartDimensionDTO = new ChartDimensionDTO(); + chartDimensionDTO.setId(xAxis.get(j).getId()); + chartDimensionDTO.setValue(row[j]); + dimensionList.add(chartDimensionDTO); + } + axisChartDataDTO.setDimensionList(dimensionList); + + int j = i - xAxis.size(); + ChartQuotaDTO chartQuotaDTO = new ChartQuotaDTO(); + chartQuotaDTO.setId(yAxis.get(j).getId()); + quotaList.add(chartQuotaDTO); + axisChartDataDTO.setQuotaList(quotaList); + try { + axisChartDataDTO.setValue(StringUtils.isEmpty(row[i]) ? null : new BigDecimal(row[i])); + } catch (Exception e) { + axisChartDataDTO.setValue(new BigDecimal(0)); + } + axisChartDataDTO.setCategory(StringUtils.defaultIfBlank(yAxis.get(j).getChartShowName(), yAxis.get(j).getName())); + buildDynamicValue(view, axisChartDataDTO, row, size, extSize); + + Map object = JsonUtil.parse((String) JsonUtil.toJSONString(axisChartDataDTO) , HashMap.class); + + object.put("x", new BigDecimal(row[0])); + object.put("y", new BigDecimal(row[1])); + + dataList.add(object); + } + + } + map.put("data", dataList); + return map; + } + public static Map transBaseGroupDataAntV(List xAxisBase, List xAxis, List xAxisExt, List yAxis, ChartViewDTO view, List data, boolean isDrill) { Map map = new HashMap<>(); diff --git a/core/core-frontend/src/locales/zh-CN.ts b/core/core-frontend/src/locales/zh-CN.ts index b228bc2756..6430a3f3d7 100644 --- a/core/core-frontend/src/locales/zh-CN.ts +++ b/core/core-frontend/src/locales/zh-CN.ts @@ -449,6 +449,7 @@ export default { condition_style: '标记样式', longitude: '经度', latitude: '纬度', + longitude_and_latitude: '经度纬度', gradient: '渐变', layer_controller: '指标切换', show_zoom: '显示缩放按钮', @@ -1060,6 +1061,7 @@ export default { step: '步长(px)', no_function: '函数尚未支持直接引用,请在字段表达式中手动输入。', chart_flow_map: '流向地图', + chart_heat_map: '热力地图', start_point: '起点经纬度', end_point: '终点经纬度', line: '线条', @@ -1078,10 +1080,15 @@ export default { map_style_darkblue: '极夜蓝', map_style_wine: '酱籽', map_line_type: '类型', + type: '类型', map_line_width: '线条宽度', map_line_height: '线条高度', map_line_linear: '渐变', map_line_animate: '动画', + heatmap_classics: '经典热力图', + heatmap3D: '3D热力图', + heatMapIntensity: '热力强度', + heatMapRadius: '热力点半径', map_line_animate_duration: '动画间隔', map_line_animate_interval: '轨迹间隔', map_line_animate_trail_length: '轨迹长度', 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 1a86f4292e..b630c10c8a 100644 --- a/core/core-frontend/src/models/chart/chart-attr.d.ts +++ b/core/core-frontend/src/models/chart/chart-attr.d.ts @@ -168,6 +168,9 @@ declare interface ChartBasicStyle { * 地图主题风格 */ mapStyle: string + heatMapType?: string + heatMapIntensity?: number + heatMapRadius?: number /** * 地图边线颜色 */ diff --git a/core/core-frontend/src/views/chart/components/editor/editor-style/components/BasicStyleSelector.vue b/core/core-frontend/src/views/chart/components/editor/editor-style/components/BasicStyleSelector.vue index 5d8d92da8a..3948613f58 100644 --- a/core/core-frontend/src/views/chart/components/editor/editor-style/components/BasicStyleSelector.vue +++ b/core/core-frontend/src/views/chart/components/editor/editor-style/components/BasicStyleSelector.vue @@ -187,6 +187,10 @@ const mapStyleOptions = [ { name: t('chart.map_style_blue'), value: 'blue' }, { name: t('chart.map_style_wine'), value: 'wine' } ] +const heatMapTypeOptions = [ + { name: t('chart.heatmap_classics'), value: 'heatmap' }, + { name: t('chart.heatmap3D'), value: 'heatmap3D' } +] const flowLineTypeOptions = [ { name: t('chart.map_line_type_line'), value: 'line' }, @@ -286,233 +290,286 @@ onMounted(() => { -
-
+ + + + + +
+ + + + + + + + + +
+ + + + + + + + +
+
+
+ + + + + + + + + +
+ + + + + + +
+ + + + + {{ t('chart.line') + t('chart.map_line_linear') }} + + + + +
+ + - - - + :trigger-width="108" + :predefine="predefineColors" + @change="changeMisc('mapLineSourceColor')" + /> - -
- - - - - - - - -
-
-
- - + - - - - - - -
- - - - - - - - -
- - - - - {{ t('chart.line') + t('chart.map_line_linear') }} - + @change="changeMisc('mapLineTargetColor')" + /> -
- - - - - - - - - - - - -
-
- - - - - - - -
-
- - - - - - - - - - - - - - - -
- - - - - {{ t('chart.line') + t('chart.map_line_animate') }} - - - - -
- - - - - - - - -
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + +
+ + + + + {{ t('chart.line') + t('chart.map_line_animate') }} + + + + +
+ + + + + + + + +
+
+
+ + + + + + + + +
+
+ + + + + + + +
diff --git a/core/core-frontend/src/views/chart/components/editor/util/chart.ts b/core/core-frontend/src/views/chart/components/editor/util/chart.ts index e21cc7ca77..64cfd829c7 100644 --- a/core/core-frontend/src/views/chart/components/editor/util/chart.ts +++ b/core/core-frontend/src/views/chart/components/editor/util/chart.ts @@ -1317,6 +1317,13 @@ export const CHART_TYPE_CONFIGS = [ value: 'flow-map', title: t('chart.chart_flow_map'), icon: 'flow-map' + }, + { + render: 'antv', + category: 'map', + value: 'heat-map', + title: t('chart.chart_heat_map'), + icon: 'heat-map' } ] }, @@ -1421,6 +1428,9 @@ export const DEFAULT_BASIC_STYLE: ChartBasicStyle = { scatterSymbolSize: 8, radarShape: 'polygon', mapStyle: 'normal', + heatMapType: 'heatmap', + heatMapIntensity: 2, + heatMapRadius: 20, areaBorderColor: '#EBEEF5', areaBaseColor: '#ffffff', mapSymbolOpacity: 0.7, diff --git a/core/core-frontend/src/views/chart/components/js/panel/charts/map/heat-map.ts b/core/core-frontend/src/views/chart/components/js/panel/charts/map/heat-map.ts new file mode 100644 index 0000000000..8f801f092e --- /dev/null +++ b/core/core-frontend/src/views/chart/components/js/panel/charts/map/heat-map.ts @@ -0,0 +1,120 @@ +import { useI18n } from '@/hooks/web/useI18n' +import { + L7ChartView, + L7Config, + L7DrawConfig, + L7Wrapper +} from '@/views/chart/components/js/panel/types/impl/l7' +import { MAP_EDITOR_PROPERTY_INNER } from '@/views/chart/components/js/panel/charts/map/common' +import { flow, parseJson } from '@/views/chart/components/js/util' +import { deepCopy } from '@/utils/utils' +import { GaodeMap } from '@antv/l7-maps' +import { Scene } from '@antv/l7-scene' +import { HeatmapLayer } from '@antv/l7-layers' +import { queryMapKeyApi } from '@/api/setting/sysParameter' +import { DEFAULT_BASIC_STYLE } from '@/views/chart/components/editor/util/chart' +const { t } = useI18n() + +/** + * 流向地图 + */ +export class HeatMap extends L7ChartView { + properties: EditorProperty[] = [ + 'background-overall-component', + 'basic-style-selector', + 'title-selector' + ] + propertyInner: EditorPropertyInner = { + ...MAP_EDITOR_PROPERTY_INNER, + 'basic-style-selector': ['colors', 'heatMapStyle', 'zoom'] + } + axis: AxisType[] = ['xAxis', 'yAxis', 'filter'] + axisConfig: AxisConfig = { + xAxis: { + name: `${t('chart.longitude_and_latitude')} / ${t('chart.dimension')}`, + type: 'd', + limit: 2 + }, + yAxis: { + name: `${t('chart.chart_data')} / ${t('chart.quota')}`, + type: 'q', + limit: 1 + } + } + constructor() { + super('heat-map', []) + } + + async drawChart(drawOption: L7DrawConfig) { + const { chart, container } = drawOption + const xAxis = deepCopy(chart.xAxis) + const yAxis = deepCopy(chart.yAxis) + let basicStyle + let miscStyle + if (chart.customAttr) { + basicStyle = parseJson(chart.customAttr).basicStyle + miscStyle = parseJson(chart.customAttr).misc + } + console.log(basicStyle) + 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 || yAxis?.length < 1) { + return new L7Wrapper(scene, undefined) + } + console.log(chart.data?.data) + const config: L7Config = new HeatmapLayer({ + name: 'line', + blend: 'normal', + autoFit: true + }) + .source(chart.data?.data, { + parser: { + type: 'json', + x: 'x', + y: 'y' + } + }) + .size('value', [0, 1.0]) // weight映射通道 + .shape(basicStyle.heatMapType ?? DEFAULT_BASIC_STYLE.heatMapType) + + config.style({ + intensity: basicStyle.heatMapIntensity ?? DEFAULT_BASIC_STYLE.heatMapIntensity, + radius: basicStyle.heatMapRadius ?? DEFAULT_BASIC_STYLE.heatMapRadius, + rampColors: { + colors: basicStyle.colors.reverse(), + positions: [0, 0.11, 0.22, 0.33, 0.44, 0.55, 0.66, 0.77, 0.88, 1.0] + } + }) + + 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) + } +}