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
|
MapMouseEvent
|
||||||
} from '@/views/chart/components/js/panel/charts/map/common'
|
} from '@/views/chart/components/js/panel/charts/map/common'
|
||||||
import { flow, getGeoJsonFile, hexColorToRGBA, parseJson } from '@/views/chart/components/js/util'
|
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 { FeatureCollection } from '@antv/l7plot/dist/esm/plots/choropleth/types'
|
||||||
import {
|
import {
|
||||||
handleGeoJson,
|
handleGeoJson,
|
||||||
@ -22,6 +22,9 @@ import {
|
|||||||
import { valueFormatter } from '@/views/chart/components/js/formatter'
|
import { valueFormatter } from '@/views/chart/components/js/formatter'
|
||||||
import { deepCopy } from '@/utils/utils'
|
import { deepCopy } from '@/utils/utils'
|
||||||
import { configCarouselTooltip } from '@/views/chart/components/js/panel/charts/map/tooltip-carousel'
|
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()
|
const { t } = useI18n()
|
||||||
|
|
||||||
@ -53,12 +56,23 @@ export class BubbleMap extends L7PlotChartView<ChoroplethOptions, Choropleth> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async drawChart(drawOption: L7PlotDrawOptions<Choropleth>): Promise<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) {
|
if (!areaId) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
chart.container = container
|
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 = {
|
let options: ChoroplethOptions = {
|
||||||
preserveDrawingBuffer: true,
|
preserveDrawingBuffer: true,
|
||||||
map: {
|
map: {
|
||||||
@ -101,19 +115,27 @@ export class BubbleMap extends L7PlotChartView<ChoroplethOptions, Choropleth> {
|
|||||||
// 禁用线上地图数据
|
// 禁用线上地图数据
|
||||||
customFetchGeoData: () => null
|
customFetchGeoData: () => null
|
||||||
}
|
}
|
||||||
const context = { drawOption, geoJson }
|
const context: Record<string, any> = { drawOption, geoJson, customSubArea }
|
||||||
options = this.setupOptions(chart, options, context)
|
options = this.setupOptions(chart, options, context)
|
||||||
|
|
||||||
const tooltip = deepCopy(options.tooltip)
|
const tooltip = deepCopy(options.tooltip)
|
||||||
options = { ...options, tooltip: { ...tooltip, showComponent: false } }
|
options = { ...options, tooltip: { ...tooltip, showComponent: false } }
|
||||||
const view = new Choropleth(container, options)
|
const view = new Choropleth(container, options)
|
||||||
const dotLayer = this.getDotLayer(chart, geoJson, drawOption)
|
const dotLayer = this.getDotLayer(chart, geoJson, drawOption, customSubArea)
|
||||||
dotLayer.options = { ...dotLayer.options, tooltip }
|
if (!areaId.startsWith('custom_')) {
|
||||||
|
dotLayer.options = { ...dotLayer.options, tooltip }
|
||||||
|
}
|
||||||
this.configZoomButton(chart, view)
|
this.configZoomButton(chart, view)
|
||||||
mapRendering(container)
|
mapRendering(container)
|
||||||
view.once('loaded', () => {
|
view.once('loaded', () => {
|
||||||
// 修改地图鼠标样式为默认
|
// 修改地图鼠标样式为默认
|
||||||
view.scene.map._canvasContainer.lastElementChild.style.cursor = 'default'
|
view.scene.map._canvasContainer.lastElementChild.style.cursor = 'default'
|
||||||
|
const { layers } = context
|
||||||
|
if (layers) {
|
||||||
|
layers.forEach(l => {
|
||||||
|
view.addLayer(l)
|
||||||
|
})
|
||||||
|
}
|
||||||
dotLayer.addToScene(view.scene)
|
dotLayer.addToScene(view.scene)
|
||||||
dotLayer.once('add', () => {
|
dotLayer.once('add', () => {
|
||||||
mapRendered(container)
|
mapRendered(container)
|
||||||
@ -121,15 +143,22 @@ export class BubbleMap extends L7PlotChartView<ChoroplethOptions, Choropleth> {
|
|||||||
view.scene.map['keyboard'].disable()
|
view.scene.map['keyboard'].disable()
|
||||||
dotLayer.on('dotLayer:click', (ev: MapMouseEvent) => {
|
dotLayer.on('dotLayer:click', (ev: MapMouseEvent) => {
|
||||||
const data = ev.feature.properties
|
const data = ev.feature.properties
|
||||||
const adcode = view.currentDistrictData.features.find(
|
let adcode, scope
|
||||||
i => i.properties.name === ev.feature.properties.name
|
if (areaId.startsWith('custom_')) {
|
||||||
)?.properties.adcode
|
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({
|
action({
|
||||||
x: ev.x,
|
x: ev.x,
|
||||||
y: ev.y,
|
y: ev.y,
|
||||||
data: {
|
data: {
|
||||||
data,
|
data,
|
||||||
extra: { adcode: adcode }
|
extra: { adcode, scope }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -144,28 +173,14 @@ export class BubbleMap extends L7PlotChartView<ChoroplethOptions, Choropleth> {
|
|||||||
private getDotLayer(
|
private getDotLayer(
|
||||||
chart: Chart,
|
chart: Chart,
|
||||||
geoJson: FeatureCollection,
|
geoJson: FeatureCollection,
|
||||||
drawOption: L7PlotDrawOptions<Choropleth>
|
drawOption: L7PlotDrawOptions<Choropleth>,
|
||||||
|
customSubArea: CustomGeoSubArea[]
|
||||||
): IPlotLayer {
|
): IPlotLayer {
|
||||||
const areaMap = chart.data?.data?.reduce((obj, value) => {
|
const { areaId } = drawOption
|
||||||
obj[value['field']] = { value: value.value, data: value }
|
const { basicStyle, tooltip } = parseJson(chart.customAttr)
|
||||||
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 { bubbleCfg } = parseJson(chart.senior)
|
const { bubbleCfg } = parseJson(chart.senior)
|
||||||
const { offsetHeight, offsetWidth } = document.getElementById(drawOption.container)
|
const { offsetHeight, offsetWidth } = document.getElementById(drawOption.container)
|
||||||
|
const dotData = []
|
||||||
const options: DotOptions = {
|
const options: DotOptions = {
|
||||||
source: {
|
source: {
|
||||||
data: dotData,
|
data: dotData,
|
||||||
@ -190,7 +205,120 @@ export class BubbleMap extends L7PlotChartView<ChoroplethOptions, Choropleth> {
|
|||||||
state: {
|
state: {
|
||||||
active: { color: 'rgba(30,90,255,1)' }
|
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) {
|
if (bubbleCfg && bubbleCfg.enable) {
|
||||||
return new Dot({
|
return new Dot({
|
||||||
@ -247,6 +375,91 @@ export class BubbleMap extends L7PlotChartView<ChoroplethOptions, Choropleth> {
|
|||||||
return options
|
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(
|
protected setupOptions(
|
||||||
chart: Chart,
|
chart: Chart,
|
||||||
options: ChoroplethOptions,
|
options: ChoroplethOptions,
|
||||||
@ -257,7 +470,8 @@ export class BubbleMap extends L7PlotChartView<ChoroplethOptions, Choropleth> {
|
|||||||
this.configLabel,
|
this.configLabel,
|
||||||
this.configStyle,
|
this.configStyle,
|
||||||
this.configTooltip,
|
this.configTooltip,
|
||||||
this.configBasicStyle
|
this.configBasicStyle,
|
||||||
)(chart, options, context)
|
this.configCustomArea
|
||||||
|
)(chart, options, context, this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -183,6 +183,7 @@ const calcData = async (view, callback) => {
|
|||||||
emit('onDrillFilters', res?.drillFilters)
|
emit('onDrillFilters', res?.drillFilters)
|
||||||
if (!res?.drillFilters?.length) {
|
if (!res?.drillFilters?.length) {
|
||||||
dynamicAreaId.value = ''
|
dynamicAreaId.value = ''
|
||||||
|
scope = null
|
||||||
} else {
|
} else {
|
||||||
const extra = view.chartExtRequest?.drill?.[res?.drillFilters?.length - 1].extra
|
const extra = view.chartExtRequest?.drill?.[res?.drillFilters?.length - 1].extra
|
||||||
dynamicAreaId.value = extra?.adcode + ''
|
dynamicAreaId.value = extra?.adcode + ''
|
||||||
|
Loading…
Reference in New Issue
Block a user