Эх сурвалжийг харах

色卡显示,基础功能

lichunyang 2 сар өмнө
parent
commit
c462c37333

+ 13 - 0
src/components/ThreeScene/ThreeScene.vue

@@ -7,6 +7,7 @@
 <script setup>
 import { ref, onMounted, onUnmounted } from "vue"
 import { useThree } from "@/composables/useThree"
+
 import { defineExpose } from "vue"
 import * as THREE from "three"
 
@@ -121,4 +122,16 @@ canvas {
   width: 100%;
   height: 100%;
 }
+
+.color-bar {
+  transition: box-shadow 0.2s ease;
+}
+
+.color-bar:hover {
+  box-shadow: 0 0 10px rgba(0,0,0,0.3);
+}
+
+.color-bar.dragging {
+  box-shadow: 0 0 15px rgba(0,0,0,0.5);
+}
 </style>

+ 0 - 19
src/components/cloudChart/index.vue

@@ -291,7 +291,6 @@ const handleCloudMapConfirm = (data) => {
 
 const handleColorCardConfirm = (data) => {
   colorCardConfig.value = data
-  applyColorCardConfig()
   colorCardDialogVisible.value = false
 }
 
@@ -489,24 +488,6 @@ function getUrl(channelNo = "service") {
   }
   return url
 }
-
-// 应用色卡配置
-const applyColorCardConfig = () => {
-  if (!pltData.value || !colorCardConfig.value) return
-
-  pltData.value.config = pltData.value.config || {}
-  pltData.value.config.colorCard = {
-    ...colorCardConfig.value,
-    visible: colorCardConfig.value.check1,
-    showTitle: colorCardConfig.value.check2
-  }
-
-  if (threeSceneRef.value) {
-    threeSceneRef.value.updateColorMapping(null, {
-      colorCard: pltData.value.config.colorCard
-    })
-  }
-}
 </script>
 
 <style scoped>

+ 43 - 0
src/composables/useThree.js

@@ -26,6 +26,7 @@ import { ObjectManager } from '@/utils/three/objects/objectManager';
 import { CSS2DRenderer, CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer.js';
 import { XyzHandler } from '@/utils/three/objects/fileHandlers/XyzHandler.js';
 import { PltHandler } from '@/utils/three/dataHandlers/PltHandler.js';
+import { ColorBar } from "@/utils/three/controls/ColorBar.js"
 
 let gui, animationId, helpers, lights;
 export function useThree(canvasRef, props) {
@@ -37,6 +38,7 @@ export function useThree(canvasRef, props) {
   const cubeRef = ref(null);
   const screenArrow = ref(null);
   const dataHandlers = ref({});
+  const colorBar = ref(null);
 
   const init = () => {
 
@@ -67,6 +69,18 @@ export function useThree(canvasRef, props) {
       controls.value = setupControls(camera.value, renderer.value.domElement);
     }
 
+    // 创建颜色条
+    colorBar.value = markRaw(new ColorBar(scene.value, camera.value, canvasRef.value.parentElement, {
+      width: 30,
+      height: 200,
+      position: 'bottom-right',
+      title: 'Pressure',
+      colorMap: 'rainbow',
+      min: 0,
+      max: 1,
+      draggable: true
+    }));
+
     // // 辅助工具
     // if (props.showHelpers) {
     //   helpers = setupHelpers(scene.value, {
@@ -139,6 +153,17 @@ export function useThree(canvasRef, props) {
         colorMap: 'rainbow',
         scalarVariable: 'CoefPressure' // 默认使用压力系数
       });
+
+      const ranges = calculateGlobalRanges(newData);
+      const variable = 'CoefPressure'; // 或从options中获取
+
+      if (colorBar.value && ranges[variable]) {
+        colorBar.value.update(
+          ranges[variable].min,
+          ranges[variable].max,
+          variable
+        );
+      }
       fitCameraToScene(camera.value, scene.value, {
         padding: 1.8,       // 比默认1.5稍宽松
         forceTopView: false, // 自动判断
@@ -150,6 +175,20 @@ export function useThree(canvasRef, props) {
     }
   };
 
+  const calculateGlobalRanges = (data) => {
+    const ranges = {};
+    data?.zones?.forEach(zone => {
+      Object.entries(zone.variables || {}).forEach(([name, values]) => {
+        if (!ranges[name]) {
+          ranges[name] = { min: Infinity, max: -Infinity };
+        }
+        ranges[name].min = Math.min(ranges[name].min, ...values);
+        ranges[name].max = Math.max(ranges[name].max, ...values);
+      });
+    });
+    return ranges;
+  };
+
   const setupEventListeners = () => {
     const pltHandler = dataHandlers.value.plt;
     if (!pltHandler) return;
@@ -300,6 +339,10 @@ export function useThree(canvasRef, props) {
     if (gui) {
       gui.destroy();
     }
+    if (colorBar.value) {
+      colorBar.value.dispose();
+      colorBar.value = null;
+    }
 
     disposeScene(scene.value);
     disposeCamera(camera.value);

+ 223 - 0
src/utils/three/controls/ColorBar.js

@@ -0,0 +1,223 @@
+import { draggable } from 'element-plus/es/components/color-picker/src/utils/draggable.mjs';
+import * as THREE from 'three';
+import { CSS2DRenderer, CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer';
+
+export class ColorBar {
+    constructor(scene, camera, containerElement, options = {}) {
+        this.scene = scene;
+        this.camera = camera;
+        this.containerElement = containerElement;
+        this.options = {
+            width: 10,
+            height: 300,
+            position: 'right',
+            title: 'Value',
+            colorMap: 'rainbow',
+            min: 0,
+            max: 1,
+            draggable: true,
+            ...options
+        };
+
+        this.init();
+        if (this.options.draggable) {
+            this.dragCleanup = this.enableDrag();
+        }
+    }
+
+    init() {
+        // 创建主容器
+        this.container = document.createElement('div');
+        this.container.style.position = 'absolute';
+        this.container.style.pointerEvents = 'none';
+        this.container.style.display = 'flex';
+        this.container.style.flexDirection = 'column'; // 垂直堆叠,标题在上
+
+        // 标题元素(顶部)
+        this.titleElement = document.createElement('div');
+        this.titleElement.style.textAlign = 'center';
+        this.titleElement.style.fontWeight = 'bold';
+        this.titleElement.style.fontSize = '11px';
+        this.titleElement.style.marginBottom = '5px'; // 与颜色条间隔
+        this.titleElement.textContent = this.options.title;
+
+        // 子容器(颜色条和标签水平排列)
+        this.subContainer = document.createElement('div');
+        this.subContainer.style.display = 'flex';
+        this.subContainer.style.alignItems = 'flex-start';
+
+        // 颜色条元素
+        this.barElement = document.createElement('div');
+        this.barElement.className = 'color-bar';
+        this.barElement.style.width = `${this.options.width}px`;
+        this.barElement.style.height = `${this.options.height}px`;
+        this.barElement.style.background = this.createGradient();
+        this.barElement.style.borderRadius = '2px';
+        this.barElement.style.boxShadow = '0 2px 8px rgba(0,0,0,0.2)';
+
+        // 标签容器(右侧垂直排列)
+        this.labelsElement = document.createElement('div');
+        this.labelsElement.style.display = 'flex';
+        this.labelsElement.style.flexDirection = 'column';
+        this.labelsElement.style.justifyContent = 'space-between';
+        this.labelsElement.style.height = `${this.options.height}px`;
+        this.labelsElement.style.marginLeft = '8px';
+        this.labelsElement.style.fontSize = '11px';
+        this.labelsElement.innerHTML = `
+            <span>${this.options.max.toFixed(2)}</span>
+            <span>${((this.options.min + this.options.max) / 2).toFixed(2)}</span>
+            <span>${this.options.min.toFixed(2)}</span>
+        `;
+
+        // 组装 DOM
+        this.subContainer.appendChild(this.barElement);
+        this.subContainer.appendChild(this.labelsElement);
+        this.container.appendChild(this.titleElement);
+        this.container.appendChild(this.subContainer);
+        this.containerElement.appendChild(this.container);
+
+        // 设置位置
+        this.updatePosition();
+        window.addEventListener('resize', () => this.updatePosition());
+
+        if (this.options.draggable) {
+            this.container.style.cursor = 'move';
+            this.container.style.userSelect = 'none';
+            this.container.style.pointerEvents = 'auto';
+            this.container.title = '拖拽移动位置';
+        }
+    }
+
+    createGradient() {
+        const stops = [];
+        for (let i = 0; i <= 100; i += 10) {
+            const t = i / 100;
+            const color = this.getColor(t);
+            stops.push(`${color} ${i}%`);
+        }
+        return `linear-gradient(to top, ${stops.join(', ')})`; // 从下到上渐变
+    }
+
+    getColor(t) {
+        const [r, g, b] = this.options.colorMap === 'rainbow'
+            ? this.rainbowColorMap(t)
+            : this.jetColorMap(t);
+        return `rgb(${Math.round(r * 255)}, ${Math.round(g * 255)}, ${Math.round(b * 255)})`;
+    }
+
+    rainbowColorMap(t) {
+        if (t < 0.25) {
+            return [0, t * 4, 1];
+        } else if (t < 0.5) {
+            return [0, 1, 1 - (t - 0.25) * 4];
+        } else if (t < 0.75) {
+            return [(t - 0.5) * 4, 1, 0];
+        } else {
+            return [1, 1 - (t - 0.75) * 4, 0];
+        }
+    }
+
+    jetColorMap(t) {
+        if (t < 0.125) {
+            return [0, 0, 0.5 + t * 4];
+        } else if (t < 0.375) {
+            return [0, (t - 0.125) * 4, 1];
+        } else if (t < 0.625) {
+            return [(t - 0.375) * 4, 1, 1 - (t - 0.375) * 4];
+        } else if (t < 0.875) {
+            return [1, 1 - (t - 0.625) * 4, 0];
+        } else {
+            return [1 - (t - 0.875) * 4, 0, 0];
+        }
+    }
+
+    updatePosition() {
+        if (!this.containerElement || !this.container) return;
+
+        const { position, width, height } = this.options;
+        const padding = 15;
+        const containerRect = this.containerElement.getBoundingClientRect();
+        const titleHeight = 20; // 估算标题高度
+
+        let x, y;
+        switch (position) {
+            case 'left':
+                x = padding;
+                y = (containerRect.height - height - titleHeight) / 2; // 考虑标题高度
+                break;
+            case 'right':
+            default:
+                x = containerRect.width - width - padding - 40;
+                y = (containerRect.height - height - titleHeight) / 2;
+                break;
+        }
+
+        this.container.style.left = `${x}px`;
+        this.container.style.top = `${y}px`;
+    }
+
+    update(min, max, title) {
+        this.options.min = min;
+        this.options.max = max;
+        if (title) this.options.title = title;
+
+        this.barElement.style.background = this.createGradient();
+        this.labelsElement.innerHTML = `
+            <span>${max.toFixed(2)}</span>
+            <span>${((min + max) / 2).toFixed(2)}</span>
+            <span>${min.toFixed(2)}</span>
+        `;
+        this.titleElement.textContent = title || this.options.title;
+    }
+
+    enableDrag() {
+        let isDragging = false;
+        let offsetX, offsetY;
+
+        const onMouseDown = (e) => {
+            isDragging = true;
+            const rect = this.container.getBoundingClientRect();
+            offsetX = e.clientX - rect.left;
+            offsetY = e.clientY - rect.top;
+            e.stopPropagation();
+            e.preventDefault();
+        };
+
+        const onMouseMove = (e) => {
+            if (!isDragging) return;
+            const containerRect = this.containerElement.getBoundingClientRect();
+            const maxX = containerRect.width - this.container.offsetWidth;
+            const maxY = containerRect.height - this.container.offsetHeight;
+            let newX = e.clientX - offsetX - containerRect.left;
+            let newY = e.clientY - offsetY - containerRect.top;
+            newX = Math.max(0, Math.min(newX, maxX));
+            newY = Math.max(0, Math.min(newY, maxY));
+            this.container.style.left = `${newX}px`;
+            this.container.style.top = `${newY}px`;
+        };
+
+        const onMouseUp = () => {
+            isDragging = false;
+        };
+
+        this.container.addEventListener('mousedown', onMouseDown);
+        document.addEventListener('mousemove', onMouseMove);
+        document.addEventListener('mouseup', onMouseUp);
+
+        return () => {
+            this.container.removeEventListener('mousedown', onMouseDown);
+            document.removeEventListener('mousemove', onMouseMove);
+            document.removeEventListener('mouseup', onMouseUp);
+        };
+    }
+
+    dispose() {
+        if (this.dragCleanup) this.dragCleanup();
+        this.container.remove();
+        window.removeEventListener('resize', () => this.updatePosition());
+        if (this.labelObject) {
+            this.scene.remove(this.labelObject);
+            this.labelObject = null;
+        }
+    }
+}