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

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;
}