diff --git a/core/core-frontend/src/views/chart/components/js/panel/charts/map/bubble-map.ts b/core/core-frontend/src/views/chart/components/js/panel/charts/map/bubble-map.ts index 7542b16ea7..b90f488d15 100644 --- a/core/core-frontend/src/views/chart/components/js/panel/charts/map/bubble-map.ts +++ b/core/core-frontend/src/views/chart/components/js/panel/charts/map/bubble-map.ts @@ -12,7 +12,7 @@ import { MapMouseEvent } from '@/views/chart/components/js/panel/charts/map/common' import { flow, getGeoJsonFile, hexColorToRGBA, parseJson } from '@/views/chart/components/js/util' -import { cloneDeep } from 'lodash-es' +import { cloneDeep, isEmpty } from 'lodash-es' import { FeatureCollection } from '@antv/l7plot/dist/esm/plots/choropleth/types' import { handleGeoJson, @@ -22,6 +22,9 @@ import { import { valueFormatter } from '@/views/chart/components/js/formatter' import { deepCopy } from '@/utils/utils' import { configCarouselTooltip } from '@/views/chart/components/js/panel/charts/map/tooltip-carousel' +import { getCustomGeoArea } from '@/api/map' +import { TextLayer } from '@antv/l7plot/dist/esm' +import { centroid } from '@turf/centroid' const { t } = useI18n() @@ -53,12 +56,23 @@ export class BubbleMap extends L7PlotChartView { } async drawChart(drawOption: L7PlotDrawOptions): Promise { - const { chart, level, areaId, container, action } = drawOption + const { chart, level, areaId, container, action, scope } = drawOption if (!areaId) { return } chart.container = container - const geoJson = cloneDeep(await getGeoJsonFile(areaId)) + let geoJson = {} as FeatureCollection + let customSubArea: CustomGeoSubArea[] = [] + if (areaId.startsWith('custom_') || scope) { + customSubArea = (await getCustomGeoArea(areaId)).data || [] + customSubArea.forEach(a => (a.scopeArr = a.scope?.split(',') || [])) + geoJson = cloneDeep(await getGeoJsonFile('156')) + if (scope) { + geoJson.features = geoJson.features.filter(f => scope.includes('156' + f.properties.adcode)) + } + } else { + geoJson = cloneDeep(await getGeoJsonFile(areaId)) + } let options: ChoroplethOptions = { preserveDrawingBuffer: true, map: { @@ -101,19 +115,27 @@ export class BubbleMap extends L7PlotChartView { // 禁用线上地图数据 customFetchGeoData: () => null } - const context = { drawOption, geoJson } + const context: Record = { drawOption, geoJson, customSubArea } options = this.setupOptions(chart, options, context) const tooltip = deepCopy(options.tooltip) options = { ...options, tooltip: { ...tooltip, showComponent: false } } const view = new Choropleth(container, options) - const dotLayer = this.getDotLayer(chart, geoJson, drawOption) - dotLayer.options = { ...dotLayer.options, tooltip } + const dotLayer = this.getDotLayer(chart, geoJson, drawOption, customSubArea) + if (!areaId.startsWith('custom_')) { + dotLayer.options = { ...dotLayer.options, tooltip } + } this.configZoomButton(chart, view) mapRendering(container) view.once('loaded', () => { // 修改地图鼠标样式为默认 view.scene.map._canvasContainer.lastElementChild.style.cursor = 'default' + const { layers } = context + if (layers) { + layers.forEach(l => { + view.addLayer(l) + }) + } dotLayer.addToScene(view.scene) dotLayer.once('add', () => { mapRendered(container) @@ -121,15 +143,22 @@ export class BubbleMap extends L7PlotChartView { view.scene.map['keyboard'].disable() dotLayer.on('dotLayer:click', (ev: MapMouseEvent) => { const data = ev.feature.properties - const adcode = view.currentDistrictData.features.find( - i => i.properties.name === ev.feature.properties.name - )?.properties.adcode + let adcode, scope + if (areaId.startsWith('custom_')) { + adcode = '156' + const area = customSubArea.find(a => a.name === data.name) + scope = area?.scopeArr + } else { + adcode = view.currentDistrictData.features.find( + i => i.properties.name === ev.feature.properties.name + )?.properties.adcode + } action({ x: ev.x, y: ev.y, data: { data, - extra: { adcode: adcode } + extra: { adcode, scope } } }) }) @@ -144,28 +173,14 @@ export class BubbleMap extends L7PlotChartView { private getDotLayer( chart: Chart, geoJson: FeatureCollection, - drawOption: L7PlotDrawOptions + drawOption: L7PlotDrawOptions, + customSubArea: CustomGeoSubArea[] ): IPlotLayer { - const areaMap = chart.data?.data?.reduce((obj, value) => { - obj[value['field']] = { value: value.value, data: value } - return obj - }, {}) - const dotData = [] - geoJson.features.forEach(item => { - const name = item.properties['name'] - if (areaMap?.[name]?.value) { - dotData.push({ - x: item.properties['centroid'][0], - y: item.properties['centroid'][1], - size: areaMap[name].value, - properties: areaMap[name].data, - name: name - }) - } - }) - const { basicStyle } = parseJson(chart.customAttr) + const { areaId } = drawOption + const { basicStyle, tooltip } = parseJson(chart.customAttr) const { bubbleCfg } = parseJson(chart.senior) const { offsetHeight, offsetWidth } = document.getElementById(drawOption.container) + const dotData = [] const options: DotOptions = { source: { data: dotData, @@ -190,7 +205,120 @@ export class BubbleMap extends L7PlotChartView { state: { active: { color: 'rgba(30,90,255,1)' } }, - tooltip: {} + tooltip: { + showComponent: tooltip.show + } + } + if (areaId.startsWith('custom_')) { + const geoJsonMap = geoJson.features.reduce((p, n) => { + if (n.properties['adcode']) { + p['156' + n.properties['adcode']] = n + } + return p + }, {}) + const { areaMapping } = parseJson(chart.senior) + const customAreaMap = customSubArea.reduce((p, n) => { + const mappedName = areaMapping?.[areaId]?.[n.name] + if (mappedName) { + n.name = mappedName + } + p[n.name] = n + return p + }, {}) + chart.data?.data?.forEach(d => { + const area = customAreaMap[d.name] + if (area) { + const areaJsonArr = [] + area.scopeArr?.forEach(adcode => { + const json = geoJsonMap[adcode] + json && areaJsonArr.push(json) + }) + if (areaJsonArr.length) { + const areaJson: FeatureCollection = { + type: 'FeatureCollection', + features: areaJsonArr + } + const center = centroid(areaJson) + dotData.push({ + name: area.name, + size: d.value, + properties: d, + x: center.geometry.coordinates[0], + y: center.geometry.coordinates[1] + }) + } + } + }) + if (options.tooltip && options.tooltip.showComponent) { + options.tooltip.items = ['name', 'adcode', 'value'] + options.tooltip.customTitle = ({ name }) => { + return name + } + const formatterMap = tooltip.seriesTooltipFormatter + ?.filter(i => i.show) + .reduce((pre, next) => { + pre[next.id] = next + return pre + }, {}) as Record + options.tooltip.customItems = originalItem => { + const result = [] + if (isEmpty(formatterMap)) { + return result + } + const head = originalItem.properties + const formatter = formatterMap[head.quotaList?.[0]?.id] + if (!isEmpty(formatter)) { + const originValue = parseFloat(head.value as string) + const value = valueFormatter(originValue, formatter.formatterCfg) + const name = isEmpty(formatter.chartShowName) ? formatter.name : formatter.chartShowName + result.push({ ...head, name, value: `${value ?? ''}` }) + } + head.dynamicTooltipValue?.forEach(item => { + const formatter = formatterMap[item.fieldId] + if (formatter) { + const value = valueFormatter(parseFloat(item.value), formatter.formatterCfg) + const name = isEmpty(formatter.chartShowName) + ? formatter.name + : formatter.chartShowName + result.push({ color: 'grey', name, value: `${value ?? ''}` }) + } + }) + return result + } + options.tooltip.domStyles = { + 'l7plot-tooltip': { + 'background-color': tooltip.backgroundColor, + 'font-size': `${tooltip.fontSize}px`, + 'line-height': 1.6 + }, + 'l7plot-tooltip__name': { + color: tooltip.color + }, + 'l7plot-tooltip__value': { + color: tooltip.color + }, + 'l7plot-tooltip__title': { + color: tooltip.color + } + } + } + } else { + const areaMap = chart.data?.data?.reduce((obj, value) => { + obj[value['field']] = { value: value.value, data: value } + return obj + }, {}) + geoJson.features.forEach(item => { + const name = item.properties['name'] + if (areaMap?.[name]?.value) { + dotData.push({ + x: item.properties['centroid'][0], + y: item.properties['centroid'][1], + size: areaMap[name].value, + properties: areaMap[name].data, + name: name + }) + } + }) } if (bubbleCfg && bubbleCfg.enable) { return new Dot({ @@ -247,6 +375,91 @@ export class BubbleMap extends L7PlotChartView { return options } + protected configCustomArea( + chart: Chart, + options: ChoroplethOptions, + context: Record + ): ChoroplethOptions { + const { drawOption, customSubArea, geoJson } = context + if (!drawOption.areaId.startsWith('custom_')) { + return options + } + const customAttr = parseJson(chart.customAttr) + const { label } = customAttr + const data = chart.data?.data + const areaMap = data?.reduce((obj, value) => { + obj[value['field']] = value + return obj + }, {}) + //处理label + options.label = false + if (label.show) { + const geoJsonMap = geoJson.features.reduce((p, n) => { + if (n.properties['adcode']) { + p['156' + n.properties['adcode']] = n + } + return p + }, {}) + const { areaMapping } = parseJson(chart.senior) + const labelLocation = [] + customSubArea.forEach(area => { + const areaJsonArr = [] + area.scopeArr?.forEach(adcode => { + const json = geoJsonMap[adcode] + json && areaJsonArr.push(json) + }) + if (areaJsonArr.length) { + const areaJson: FeatureCollection = { + type: 'FeatureCollection', + features: areaJsonArr + } + const content = [] + if (label.showDimension) { + const mappedName = areaMapping?.[drawOption.areaId]?.[area.name] + if (mappedName) { + area.name = mappedName + } + content.push(area.name) + } + if (label.showQuota) { + areaMap[area.name] && + content.push(valueFormatter(areaMap[area.name].value, label.quotaLabelFormatter)) + } + const center = centroid(areaJson) + labelLocation.push({ + name: content.join('\n\n'), + x: center.geometry.coordinates[0], + y: center.geometry.coordinates[1] + }) + } + }) + const areaLabelLayer = new TextLayer({ + name: 'areaLabelLayer', + source: { + data: labelLocation, + parser: { + type: 'json', + x: 'x', + y: 'y' + } + }, + field: 'name', + zIndex: 0.06, + style: { + fill: label.color, + fontSize: label.fontSize, + opacity: 1, + fontWeight: 'bold', + textAnchor: 'center', + textAllowOverlap: label.fullDisplay, + padding: !label.fullDisplay ? [2, 2] : undefined + } + }) + context.layers = [areaLabelLayer] + } + return options + } + protected setupOptions( chart: Chart, options: ChoroplethOptions, @@ -257,7 +470,8 @@ export class BubbleMap extends L7PlotChartView { this.configLabel, this.configStyle, this.configTooltip, - this.configBasicStyle - )(chart, options, context) + this.configBasicStyle, + this.configCustomArea + )(chart, options, context, this) } } 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 032def29d7..0986d6581f 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 @@ -183,6 +183,7 @@ const calcData = async (view, callback) => { emit('onDrillFilters', res?.drillFilters) if (!res?.drillFilters?.length) { dynamicAreaId.value = '' + scope = null } else { const extra = view.chartExtRequest?.drill?.[res?.drillFilters?.length - 1].extra dynamicAreaId.value = extra?.adcode + ''