feat(图表): 气泡地图支持自定义区域

This commit is contained in:
wisonic 2024-11-27 21:52:10 +08:00
parent f2b0248783
commit 7ccb42a809
2 changed files with 247 additions and 32 deletions

View File

@ -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<ChoroplethOptions, Choropleth> {
}
async drawChart(drawOption: L7PlotDrawOptions<Choropleth>): Promise<Choropleth> {
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<ChoroplethOptions, Choropleth> {
// 禁用线上地图数据
customFetchGeoData: () => null
}
const context = { drawOption, geoJson }
const context: Record<string, any> = { 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<ChoroplethOptions, Choropleth> {
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<ChoroplethOptions, Choropleth> {
private getDotLayer(
chart: Chart,
geoJson: FeatureCollection,
drawOption: L7PlotDrawOptions<Choropleth>
drawOption: L7PlotDrawOptions<Choropleth>,
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<ChoroplethOptions, Choropleth> {
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<string, SeriesFormatter>
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<ChoroplethOptions, Choropleth> {
return options
}
protected configCustomArea(
chart: Chart,
options: ChoroplethOptions,
context: Record<string, any>
): 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<ChoroplethOptions, Choropleth> {
this.configLabel,
this.configStyle,
this.configTooltip,
this.configBasicStyle
)(chart, options, context)
this.configBasicStyle,
this.configCustomArea
)(chart, options, context, this)
}
}

View File

@ -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 + ''