|
@@ -8,12 +8,15 @@ import { CSS2DRenderer, CSS2DObject } from 'three/addons/renderers/CSS2DRenderer
|
|
|
import { Interaction } from 'three.interaction/src/index.js';
|
|
|
import * as d3 from 'd3-geo'
|
|
|
import gsap from 'gsap'
|
|
|
+import MapLegend from './MapLegend.vue'
|
|
|
import { onMounted, ref, watch, nextTick } from 'vue';
|
|
|
-
|
|
|
const BASE_URL = "/src/assets"
|
|
|
|
|
|
export default {
|
|
|
name: 'Map3DChart',
|
|
|
+ components: {
|
|
|
+ MapLegend
|
|
|
+ },
|
|
|
props: {
|
|
|
debugger: {
|
|
|
type: Boolean,
|
|
@@ -35,7 +38,7 @@ export default {
|
|
|
areaColor: {
|
|
|
type: Function,
|
|
|
default: function (params) {
|
|
|
- return params ? params.meta.分区颜色 : ''
|
|
|
+ return params ? (params.meta ? params.meta.分区颜色 : params.color) : ''
|
|
|
}
|
|
|
}
|
|
|
},
|
|
@@ -51,7 +54,6 @@ export default {
|
|
|
let selectName = null;
|
|
|
let selectPrevColor = null;
|
|
|
var map = new THREE.Group();
|
|
|
- let needReloadColor = []
|
|
|
const COLOR_MAP = {
|
|
|
"红色": 0xff4540,
|
|
|
"橙色": 0xff8934,
|
|
@@ -62,7 +64,7 @@ export default {
|
|
|
|
|
|
|
|
|
let textureLoader = new THREE.TextureLoader(); //纹理贴图加载器
|
|
|
- const cacheMap = new Map()
|
|
|
+ let cacheMap = {}
|
|
|
const mapData = ref(getHeatMapData(props.mapHeatData))
|
|
|
function getHeatMapData(val) {
|
|
|
return val.reduce((config, current) => {
|
|
@@ -80,22 +82,75 @@ export default {
|
|
|
|
|
|
watch(() => props.mapHeatData, (val) => {
|
|
|
mapData.value = getHeatMapData(val)
|
|
|
+ Object.keys( mapData.value ).forEach(key => {
|
|
|
+ const area = cacheMap[key];
|
|
|
+ area && area?.mesh.material[0].color.set(getAreaColor(key))
|
|
|
+ })
|
|
|
nextTick(() => {
|
|
|
- Object.keys( mapData.value ).forEach(key => {
|
|
|
- const area = cacheMap.get(key);
|
|
|
- area && area?.mesh.material[0].color.set(getAreaColor(key))
|
|
|
- })
|
|
|
+ restart();
|
|
|
})
|
|
|
}, {
|
|
|
deep: true
|
|
|
})
|
|
|
watch(() => props.qx, (val) => {
|
|
|
- const area = cacheMap.get(val);
|
|
|
- if (area && val !== selectName.element.innerHTML) {
|
|
|
+ const area = cacheMap[val];
|
|
|
+ if (area && (selectName === null || val !== selectName.element.innerHTML )) {
|
|
|
clickArea(area.mesh, area.name)
|
|
|
}
|
|
|
+ if (val === "重庆市" && selectName && selectMesh) {
|
|
|
+ clickArea(selectMesh, selectName)
|
|
|
+ }
|
|
|
})
|
|
|
|
|
|
+ let timer = null;
|
|
|
+ let index = 0
|
|
|
+ // 定时任务刷新
|
|
|
+ function start() {
|
|
|
+ const keys = Object.keys(cacheMap)
|
|
|
+ const size = keys.length
|
|
|
+
|
|
|
+ function inc() {
|
|
|
+ index += 1
|
|
|
+ }
|
|
|
+ function get() {
|
|
|
+ return index
|
|
|
+ }
|
|
|
+ function loop() {
|
|
|
+ if (mapData.value) {
|
|
|
+ const idx = get() % size
|
|
|
+ const item = cacheMap[keys[idx]]
|
|
|
+ if (item) {
|
|
|
+ Object.values(cacheMap).forEach(key => {
|
|
|
+ key.info.element.style.opacity = 0;
|
|
|
+ })
|
|
|
+ item.info.element.innerHTML = props.formatter(mapData.value[keys[idx]])
|
|
|
+ item.info.element.style.opacity = 1;
|
|
|
+ inc();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ loop()
|
|
|
+ timer = setInterval(() => {
|
|
|
+ loop()
|
|
|
+ }, 1500)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 重启函数
|
|
|
+ function restart() {
|
|
|
+ stop();
|
|
|
+ start();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 定制定时任务
|
|
|
+ function stop() {
|
|
|
+ if (timer) {
|
|
|
+ Object.values(cacheMap).forEach(key => {
|
|
|
+ // key.info.visible = false
|
|
|
+ key.info.element.style.opacity = 0;
|
|
|
+ })
|
|
|
+ clearInterval(timer)
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
onMounted(() => {
|
|
|
init()
|
|
@@ -113,20 +168,15 @@ export default {
|
|
|
new Interaction(renderer, scene, camera);
|
|
|
initGeoJson();
|
|
|
initDebugger();
|
|
|
- // 添加物体
|
|
|
- // const cubeGeometry = new THREE.BoxGeometry(1,1,1)
|
|
|
- // const material = new THREE.MeshBasicMaterial({
|
|
|
- // color: '#ffff00'
|
|
|
- // })
|
|
|
- // // 根据几何体和材质创建物体
|
|
|
- // const cube =new THREE.Mesh(cubeGeometry, material)
|
|
|
- // // 将几何体创建到场景当中
|
|
|
- // scene.add(cube)
|
|
|
+ container.addEventListener('mouseleave', () => {
|
|
|
+ restart()
|
|
|
+ })
|
|
|
}
|
|
|
|
|
|
function initScene() {
|
|
|
// 1、创建场景
|
|
|
scene = new THREE.Scene()
|
|
|
+ scene.background = null
|
|
|
}
|
|
|
// 初始化灯光
|
|
|
function initLights() {
|
|
@@ -165,7 +215,7 @@ export default {
|
|
|
renderer = new THREE.WebGLRenderer({antialias: true, alpha: true})
|
|
|
renderer.setPixelRatio(window.devicePixelRatio);
|
|
|
renderer.setSize(WIDTH, HEIGHT)
|
|
|
- renderer.setClearColor(0x070b13, 1);
|
|
|
+ renderer.setClearColor(0);
|
|
|
container.appendChild(renderer.domElement)
|
|
|
// 初始化CSS2DRenderer
|
|
|
labelRenderer = new CSS2DRenderer();
|
|
@@ -210,21 +260,13 @@ export default {
|
|
|
loader.load(`${BASE_URL}/json/cq.json`, function (data) {
|
|
|
let jsonData = JSON.parse(data);
|
|
|
initMap(jsonData);
|
|
|
-
|
|
|
- setTimeout(() => {
|
|
|
- needReloadColor.forEach(item => {
|
|
|
- item?.mesh.material[0].color.set(getAreaColor(item.name))
|
|
|
- })
|
|
|
- needReloadColor = []
|
|
|
- })
|
|
|
})
|
|
|
}
|
|
|
|
|
|
function clickArea(mesh, name) {
|
|
|
if (selectMesh === null) {
|
|
|
- selectPrevColor = mesh.material[0].color.getHexString();
|
|
|
gsap.to(mesh.position, {
|
|
|
- y: 8,
|
|
|
+ y: 5,
|
|
|
duration: .3
|
|
|
});
|
|
|
gsap.to(name.element.style, {
|
|
@@ -232,7 +274,7 @@ export default {
|
|
|
duration: .3
|
|
|
})
|
|
|
selectMesh = mesh;
|
|
|
- selectName = name
|
|
|
+ selectName = name;
|
|
|
} else if (selectMesh === mesh) {
|
|
|
gsap.to(selectMesh.position, {
|
|
|
y: 1.5,
|
|
@@ -251,7 +293,7 @@ export default {
|
|
|
duration: .3
|
|
|
});
|
|
|
gsap.to(mesh.position, {
|
|
|
- y: 8,
|
|
|
+ y: 5,
|
|
|
duration: .3
|
|
|
})
|
|
|
if (selectName) {
|
|
@@ -266,17 +308,19 @@ export default {
|
|
|
})
|
|
|
prevMesh = selectMesh
|
|
|
selectMesh = mesh;
|
|
|
- selectPrevColor = mesh.material[0].color.getHexString();
|
|
|
selectName = name
|
|
|
}
|
|
|
prevMesh && prevMesh.material[0].color.set(`#${selectPrevColor}`)
|
|
|
+ if (selectMesh && mesh.material[0].color.getHexString() !== 'ffffff') {
|
|
|
+ selectPrevColor = mesh.material[0].color.getHexString()
|
|
|
+ }
|
|
|
selectMesh && selectMesh.material[0].color.set('#ffffff')
|
|
|
}
|
|
|
/**
|
|
|
* 根据json生成地图
|
|
|
* @param chinaJson
|
|
|
*/
|
|
|
- function initMap(chinaJson) {
|
|
|
+ async function initMap(chinaJson) {
|
|
|
let textureMap=textureLoader.load(`${BASE_URL}/images/map/map_gray.jpg`);
|
|
|
let texturedispMap=textureLoader.load(`${BASE_URL}/images/map/map-disp.jpg`);
|
|
|
let texturefxMap=textureLoader.load(`${BASE_URL}/images/map/map-fx.jpg`);
|
|
@@ -304,11 +348,11 @@ export default {
|
|
|
dashed: false,
|
|
|
alphaToCoverage: true,
|
|
|
} );
|
|
|
- // 新建一个省份容器:用来存放省份对应的模型和轮廓线
|
|
|
- const meshArrs = new THREE.Object3D()
|
|
|
- cacheMap.clear()
|
|
|
+ // 新建一个省份容器:用来存放省份对应的模型和轮廓线
|
|
|
+ const meshArrs = new THREE.Object3D()
|
|
|
+ cacheMap = {}
|
|
|
// d3-geo转化坐标
|
|
|
- const projection = d3.geoMercator().center([107.70084090820312, 29.942008602258]).scale(1740).translate([0, 0]);
|
|
|
+ const projection = d3.geoMercator().center([107.70084090820312, 29.942008602258]).scale(2000).translate([0, 0]);
|
|
|
// 遍历省份构建模型
|
|
|
chinaJson.features.forEach(elem => {
|
|
|
const province = new THREE.Object3D()
|
|
@@ -341,6 +385,7 @@ export default {
|
|
|
map: textureMap,
|
|
|
displacementMap: texturedispMap,
|
|
|
displacementScale: .5,
|
|
|
+ color: getAreaColor(properties.name),
|
|
|
combine: THREE.MultiplyOperation,
|
|
|
transparent: true,
|
|
|
opacity: .9,
|
|
@@ -367,24 +412,25 @@ export default {
|
|
|
}
|
|
|
})
|
|
|
mesh.on('mouseover', () => {
|
|
|
+ // 停止定时任务
|
|
|
+ stop();
|
|
|
+ // 刷新数据
|
|
|
info.element.innerHTML = props.formatter(mapData.value[properties.name])
|
|
|
- info.visible = true
|
|
|
+ // container
|
|
|
+ console.log(container, info.element)// 显示节点
|
|
|
+ // const parent = container.getBoundingClientRect()
|
|
|
+ // const sub = info.element.getBoundingClientRect();
|
|
|
+ info.element.style.opacity = 1;
|
|
|
})
|
|
|
mesh.on('mouseout', () => {
|
|
|
- info.visible = false
|
|
|
+ info.element.style.opacity = 0;
|
|
|
})
|
|
|
|
|
|
- if (material.color.getHexString() === 'ffffff') {
|
|
|
- needReloadColor.push({
|
|
|
- mesh,
|
|
|
- name: properties.name
|
|
|
- })
|
|
|
- }
|
|
|
- cacheMap.set(properties.name, {
|
|
|
+ cacheMap[properties.name] = {
|
|
|
mesh,
|
|
|
name,
|
|
|
info
|
|
|
- })
|
|
|
+ }
|
|
|
//Line2
|
|
|
linGeometry.setPositions( positions );
|
|
|
linGeometry.setColors( colors );
|
|
@@ -405,6 +451,9 @@ export default {
|
|
|
map.add(meshArrs)
|
|
|
})
|
|
|
scene.add(map)
|
|
|
+ return Promise.resolve({
|
|
|
+ cacheMap
|
|
|
+ })
|
|
|
}
|
|
|
|
|
|
function initLightPoint(properties,projection){
|
|
@@ -426,9 +475,7 @@ export default {
|
|
|
function createTextPoint(x,z,areaName){
|
|
|
let tag = document.createElement('div')
|
|
|
tag.innerHTML = areaName
|
|
|
- // tag.className = className
|
|
|
tag.style.pointerEvents = 'none'
|
|
|
- // tag.style.visibility = 'hidden'
|
|
|
tag.style.position = 'absolute'
|
|
|
tag.style.color = '#fff'
|
|
|
let label = new CSS2DObject(tag)
|
|
@@ -445,21 +492,20 @@ export default {
|
|
|
tag.innerHTML = areaName
|
|
|
tag.style.pointerEvents = 'none'
|
|
|
tag.style.position = 'absolute'
|
|
|
+ tag.style.top = '-120px'
|
|
|
tag.style.border = '1px solid rgba(61, 115, 255, 0.72)'
|
|
|
tag.style.borderRadius = '4px'
|
|
|
tag.style.backgroundColor = '#000000'
|
|
|
tag.style.color = '#fff'
|
|
|
+ tag.style.opacity = 0;
|
|
|
let label = new CSS2DObject(tag)
|
|
|
- label.element.innerHTML = ""
|
|
|
- label.element.style.visibility = 'visible'
|
|
|
- label.position.set(x,20,z)
|
|
|
- label.position.z-=3
|
|
|
- label.visible = false
|
|
|
+ // label.element.style.visibility = 'visible'
|
|
|
+ label.position.set(x,10,z)
|
|
|
+ // label.position.z-=3
|
|
|
+ // label.visible = false
|
|
|
scene.add(label)
|
|
|
return label;
|
|
|
}
|
|
|
-
|
|
|
-
|
|
|
return {
|
|
|
mapTestBox
|
|
|
}
|
|
@@ -468,7 +514,9 @@ export default {
|
|
|
</script>
|
|
|
|
|
|
<template >
|
|
|
- <div ref="mapTestBox" class="map-chart-3d"></div>
|
|
|
+ <div ref="mapTestBox" class="map-chart-3d">
|
|
|
+ <MapLegend />
|
|
|
+ </div>
|
|
|
</template>
|
|
|
|
|
|
<style scoped lang='less'>
|