mirror of
https://github.com/dataease/dataease.git
synced 2025-02-25 03:52:59 +08:00
feat(图表): 气泡地图支持自定义区域
This commit is contained in:
parent
f2b0248783
commit
7ccb42a809
@ -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)
|
||||
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(
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -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 + ''
|
||||
|
Loading…
Reference in New Issue
Block a user