You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
414 lines
12 KiB
414 lines
12 KiB
import {CSSProperties, useEffect, useRef, useState} from 'react'; |
|
import * as echarts from 'echarts'; |
|
import {MapSeriesOption} from 'echarts'; |
|
import {useInterval, useSize} from 'ahooks'; |
|
import { |
|
ceil, |
|
isBoolean, |
|
isEmpty, |
|
maxBy, |
|
merge, |
|
uniqBy, |
|
size, |
|
gte, |
|
last, |
|
omit, |
|
map, |
|
} from 'lodash'; |
|
import {Empty} from 'antd'; |
|
// @ts-ignore |
|
import {getAMapLoader} from '@component/amap'; |
|
// @ts-ignore |
|
import classes from './style.module.css'; |
|
import {LOADING_COLOR, MAP_COLORS} from './type'; |
|
import getFeatures from './getFeatures'; |
|
// 通过 ComposeOption 来组合出一个只有必须组件和图表的 Option 类型 |
|
type ECOption = echarts.ComposeOption<MapSeriesOption>; |
|
|
|
type Grid = { |
|
name: string; |
|
adcode: string | number; |
|
[key: string]: string | number; |
|
}; |
|
|
|
export interface MapProps { |
|
service: (params: any) => Promise<any>; |
|
color?: Array<string>; |
|
options?: ECOption; |
|
conditions?: any; |
|
breadcrumbClassName?: any; |
|
lastLevel?: string; |
|
goDown?: boolean; |
|
roam?: boolean; |
|
isCarousel?: boolean; |
|
isKeepGrid?: boolean; |
|
searchKey?: string; |
|
loadingType?: any; |
|
style?: CSSProperties; |
|
toolbarloop: 'top5' | 'all'; |
|
onDownCallBack?: (n: Grid, c?: string | number, o?: object) => void; |
|
} |
|
|
|
export function ChartsMap({ |
|
service, |
|
color = MAP_COLORS, |
|
conditions, |
|
options = {}, |
|
goDown = true, |
|
lastLevel = 'district', |
|
onDownCallBack, |
|
breadcrumbClassName, |
|
roam = false, |
|
isCarousel = false, |
|
style = {}, |
|
searchKey = '', |
|
loadingType = {color: LOADING_COLOR}, |
|
isKeepGrid = false, |
|
toolbarloop = 'top5' |
|
}: MapProps) { |
|
const ele = useRef<any>(null); |
|
|
|
const echartsRef = useRef<any>(null); |
|
|
|
const echartsInstance = useRef<any>(null); |
|
|
|
const [empty, setEmpty] = useState<any>(undefined); |
|
|
|
const [breadcrumb, setBreadcrumb] = useState<Array<Grid>>([]); |
|
|
|
const [interval, setInterval] = useState<number | undefined>(undefined); |
|
|
|
const [time, setTime] = useState<number | undefined>(undefined); |
|
|
|
const count = useRef(0); |
|
|
|
useInterval(() => { |
|
function getDataLength() { |
|
const l = size(echartsInstance.current.getOption().series[0]?.data); |
|
if (toolbarloop === 'top5') { |
|
return gte(l, 5) ? 5 : l; |
|
} else { |
|
return l; |
|
} |
|
} |
|
|
|
echartsInstance.current.dispatchAction({ |
|
type: 'hideTip', |
|
}); |
|
|
|
echartsInstance.current.dispatchAction({ |
|
type: 'downplay', |
|
seriesIndex: 0, |
|
}); |
|
|
|
echartsInstance.current.dispatchAction({ |
|
type: 'highlight', |
|
seriesIndex: 0, |
|
dataIndex: count.current % getDataLength(), |
|
}); |
|
|
|
echartsInstance.current.dispatchAction({ |
|
type: 'showTip', |
|
seriesIndex: 0, |
|
dataIndex: count.current % getDataLength(), |
|
position: () => { |
|
}, |
|
}); |
|
if (gte(count.current, toolbarloop === 'top5'?4:getDataLength()-1)) { |
|
count.current = 0; |
|
} else { |
|
count.current++; |
|
} |
|
}, interval); |
|
|
|
function clear(c?: boolean) { |
|
if (c) { |
|
count.current = 0; |
|
} |
|
|
|
setInterval(undefined); |
|
|
|
echartsInstance.current.dispatchAction({ |
|
type: 'hideTip', |
|
}); |
|
|
|
echartsInstance.current.dispatchAction({ |
|
type: 'downplay', |
|
seriesIndex: 0, |
|
}); |
|
} |
|
|
|
function handleBreadcrumb(grid: Grid, key: number) { |
|
setBreadcrumb(breadcrumb.slice(0, key + 1)); |
|
|
|
echartsInstance.current?.resetOption(grid); |
|
|
|
onDownCallBack && onDownCallBack(grid); |
|
} |
|
|
|
const responsive = useSize(ele.current); |
|
|
|
useInterval(() => { |
|
echartsInstance.current?.resetOption(last(breadcrumb)); |
|
}, time); |
|
|
|
useEffect(() => { |
|
echartsInstance.current?.resize(); |
|
}, [JSON.stringify(responsive)]); |
|
|
|
useEffect(() => { |
|
const params = Object.assign( |
|
conditions, |
|
isKeepGrid ? omit(last(breadcrumb), 'name') : null, |
|
); |
|
|
|
if (!echartsRef.current) { |
|
echartsRef.current = echarts.init(ele.current); |
|
} |
|
echartsRef.current.showLoading(Object.assign({text: ''}, loadingType)); |
|
|
|
(async function init() { |
|
const geoJson: any = await getFeatures(params.adcode); |
|
if (isEmpty(geoJson)) { |
|
echartsInstance.current?.clear(); |
|
echartsRef.current.hideLoading(); |
|
setEmpty(true); |
|
return; |
|
} |
|
const properties = geoJson?.currentFeature?.properties; |
|
const {adcode, name} = properties; |
|
echarts.registerMap(name, geoJson); |
|
if (!echartsInstance.current) { |
|
echartsInstance.current = initMap( |
|
echartsRef.current, |
|
{ |
|
color, |
|
service, |
|
goDown: goDown, // 是否下钻 |
|
lastLevel: lastLevel, |
|
mapName: name, // 地图名 |
|
mapCode: adcode, // 地图code |
|
callback: onDownCallBack, |
|
setBreadcrumb, |
|
roam, |
|
searchKey, |
|
loadingType, |
|
}, |
|
options, |
|
); |
|
} |
|
if (!isEmpty(params) && echartsInstance.current) { |
|
try { |
|
const _params = { |
|
name, // 地图名 |
|
adcode: adcode, // 地图code |
|
[searchKey]: params[searchKey], // 地图code |
|
}; |
|
if (isKeepGrid) { |
|
setBreadcrumb( |
|
uniqBy([...breadcrumb, _params], (item) => String(item.adcode)), |
|
); |
|
} else { |
|
setBreadcrumb([_params]); |
|
} |
|
|
|
function callback() { |
|
setInterval(5000); |
|
setTime(300000); |
|
} |
|
|
|
echartsInstance.current?.resetOption( |
|
_params, |
|
conditions, |
|
isCarousel && callback, |
|
); |
|
|
|
if (isCarousel) { |
|
clear(true); |
|
|
|
echartsInstance.current.on('mouseover', () => { |
|
clear(); |
|
}); |
|
|
|
echartsInstance.current.on('mouseout', () => { |
|
setInterval(5000); |
|
}); |
|
} |
|
} catch (e) { |
|
} |
|
} |
|
})(); |
|
}, map(Object.values(conditions), String)); |
|
return ( |
|
<div |
|
style={{width: '100%', height: '100%', position: 'relative', ...style}} |
|
> |
|
{isBoolean(empty) && empty ? ( |
|
<Empty |
|
image={Empty.PRESENTED_IMAGE_SIMPLE} |
|
description="该地区暂无地图轮廓" |
|
/> |
|
) : ( |
|
<div ref={ele} style={{width: '100%', height: '100%'}}/> |
|
)} |
|
<div style={{position: 'absolute', top: 10, left: 30, zIndex: 10}}> |
|
<ul className={[classes.map_breadcrumb, breadcrumbClassName].join(' ')}> |
|
{breadcrumb.map((item, key) => ( |
|
<li key={item.adcode} onClick={() => handleBreadcrumb(item, key)}> |
|
{key > 0 && key < breadcrumb?.length && '-'} |
|
{item?.name} |
|
</li> |
|
))} |
|
</ul> |
|
</div> |
|
</div> |
|
); |
|
} |
|
|
|
export function initMap(chart: any, customOptions: any, options: any) { |
|
let cacheConditions = {}; |
|
const option = merge( |
|
{ |
|
backgroundColor: '#F5F5F5', // 画布背景色 |
|
geo: { |
|
show: true, |
|
map: customOptions.mapName, |
|
layoutSize: '100%', |
|
zoom: 1.2, |
|
roam: customOptions.roam, |
|
scaleLimit: { |
|
min: 1.2, |
|
max: 10, |
|
}, |
|
label: { |
|
show: false, |
|
}, |
|
itemStyle: { |
|
borderColor: '#c5dff9', |
|
borderWidth: 1, |
|
}, |
|
emphasis: { |
|
label: { |
|
show: false, |
|
}, |
|
itemStyle: { |
|
//区域 |
|
areaColor: '#FFDF34', |
|
}, |
|
}, |
|
select: { |
|
label: { |
|
show: false, |
|
}, |
|
itemStyle: { |
|
areaColor: '#BDBDBD', |
|
}, |
|
}, |
|
}, |
|
visualMap: { |
|
min: 0, |
|
bottom: 70, |
|
type: 'continuous', |
|
max: 100, |
|
text: ['高', '低'], |
|
top: 'bottom', |
|
itemWidth: 8, |
|
itemHeight: 320, |
|
calculable: true, |
|
realtime: false, |
|
show: true, |
|
inRange: { |
|
color: customOptions.color, |
|
}, |
|
}, |
|
series: [ |
|
{ |
|
type: 'map', |
|
map: customOptions.mapName, |
|
geoIndex: 0, |
|
seriesIndex: 0, |
|
}, |
|
{ |
|
type: 'effectScatter', |
|
coordinateSystem: 'geo', |
|
showEffectOn: 'render', |
|
seriesIndex: 1, |
|
roam: customOptions.roam, |
|
}, |
|
], |
|
}, |
|
options, |
|
); |
|
chart.setOption(option); |
|
// 添加事件 |
|
chart.on('click', async function (params: any) { |
|
const grid = params.data; |
|
if (!grid?.adcode) { |
|
return; |
|
} |
|
const {adcode, name} = grid; |
|
const geoJson: any = await getFeatures(adcode); |
|
if (isEmpty(geoJson)) { |
|
return; |
|
} |
|
const level = geoJson?.currentFeature?.properties?.level; |
|
|
|
if (level === customOptions.lastLevel) { |
|
return; |
|
} |
|
echarts.registerMap(name, geoJson); |
|
customOptions.setBreadcrumb((d: any) => { |
|
return uniqBy( |
|
[ |
|
...d, |
|
{ |
|
adcode: adcode, |
|
name, |
|
[customOptions.searchKey]: grid[customOptions.searchKey], |
|
}, |
|
], |
|
'adcode', |
|
); |
|
}); |
|
|
|
customOptions.callback && customOptions.callback(grid, option, chart); |
|
|
|
chart.resetOption({ |
|
adcode: adcode, |
|
name, |
|
[customOptions.searchKey]: grid[customOptions.searchKey], |
|
}); |
|
}); |
|
|
|
chart.resetOption = async function ( |
|
grid: Grid, |
|
conditions = cacheConditions, |
|
carousel: any, |
|
) { |
|
try { |
|
const {name, adcode} = grid; |
|
cacheConditions = {...conditions}; |
|
option.geo.map = name; |
|
chart.clear(); |
|
chart.showLoading(Object.assign({text: ''}, customOptions.loadingType)); |
|
const data = await customOptions.service({ |
|
...cacheConditions, |
|
adcode, |
|
[customOptions.searchKey]: grid[customOptions.searchKey], |
|
}); |
|
chart.hideLoading(); |
|
const maxValue: any = maxBy(data, 'value'); |
|
option.visualMap.max = ceil(maxValue?.value) || 100; |
|
option.series[0].data = data; |
|
chart.setOption(option); |
|
carousel && carousel(); |
|
} catch (e) { |
|
console.log(e, 'error'); |
|
} |
|
}; |
|
|
|
window.onresize = function () { |
|
chart?.resize(); |
|
}; |
|
return chart; |
|
}
|
|
|