Procházet zdrojové kódy

色卡功能完善

lichunyang před 2 měsíci
rodič
revize
80f8ccea0a

+ 21 - 25
src/components/cloudChart/dialog/CloudMapDialog.vue

@@ -166,11 +166,11 @@ const color2 = ref("#0000ff")
 
 // 可用标量选项
 const availableScalars = ref([
-  { label: "压力系数", value: "CoefPressure" },
-  { label: "马赫数", value: "Mach" },
-  { label: "X方向速度", value: "VelocityX" },
-  { label: "Y方向速度", value: "VelocityY" },
-  { label: "Z方向速度", value: "VelocityZ" }
+  { label: "CoefPressure", value: "CoefPressure" },
+  { label: "Mach", value: "Mach" },
+  { label: "VelocityX", value: "VelocityX" },
+  { label: "VelocityY", value: "VelocityY" },
+  { label: "VelocityZ", value: "VelocityZ" }
 ])
 
 // 数据范围类型选项
@@ -283,27 +283,20 @@ const updateMinValue = () => {
 
 // 更新颜色条间隔
 const updateColorBarIntervals = () => {
-  if (!props.threeSceneRef) return
-
-  // 解析颜色范围字符串 (1, 0, 0) => [1, 0, 0]
-  const parseColor = (str) => {
-    const matches = str.match(/\(([^)]+)\)/)
-    if (!matches) return [1, 0, 0] // 默认红色
-    return matches[1].split(",").map((v) => parseFloat(v.trim()))
-  }
+  if (!props.threeSceneRef) return;
 
   props.threeSceneRef.updateColorBar({
     intervals: calculateIntervals(),
     colorRange: {
       min: parseFloat(cloudConfig.value.min),
-      max: parseFloat(cloudConfig.value.max),
-      minColor: parseColor(cloudConfig.value.mincv), // 新增最小值颜色
-      maxColor: parseColor(cloudConfig.value.maxcv) // 新增最大值颜色
+      max: parseFloat(cloudConfig.value.max)
     },
     variable: cloudConfig.value.scalarname,
-    title: getScalarLabel(cloudConfig.value.scalarname)
-  })
-}
+    title: getScalarLabel(cloudConfig.value.scalarname),
+    gradientStart: parseColorString(cloudConfig.value.mincv), // 渐变起点颜色
+    gradientEnd: parseColorString(cloudConfig.value.maxcv)    // 渐变终点颜色
+  });
+};
 
 // 添加辅助函数获取变量标签
 const getScalarLabel = (value) => {
@@ -315,11 +308,10 @@ const getScalarLabel = (value) => {
 
 // 解析颜色字符串 "(r, g, b)" 为数组 [r, g, b]
 const parseColorString = (str) => {
-  const matches = str.match(/\(([^)]+)\)/)
-  if (!matches) return [0, 0, 1] // 默认蓝色
-
-  return matches[1].split(",").map(Number)
-}
+    const matches = str.match(/\(([^)]+)\)/);
+    if (!matches) return [0, 0, 1];
+    return matches[1].split(',').map(v => parseFloat(v.trim()));
+};
 
 // 计算等分点
 const calculateIntervals = () => {
@@ -349,7 +341,11 @@ const handleConfirm = () => {
   updateColorBarIntervals()
 
   // 再提交配置
-  emit("confirm", cloudConfig.value)
+  emit("confirm", { 
+        ...cloudConfig.value,
+        gradientStart: parseColorString(cloudConfig.value.mincv),
+        gradientEnd: parseColorString(cloudConfig.value.maxcv)
+    });
   emit("update:modelValue", false)
 }
 

+ 251 - 99
src/components/cloudChart/dialog/ColorCardDialog.vue

@@ -11,8 +11,8 @@
   >
     <div class="numberinput">
       <el-form label-position="left" class="color-card-form">
-        <el-form-item label="" :label-width="formLabelWidth1">
-          <el-checkbox label="显示色卡" v-model="skvalue.check1"></el-checkbox>
+        <el-form-item label="显示色卡:" :label-width="formLabelWidth1">
+          <el-checkbox v-model="skvalue.check1" label="显示色卡"></el-checkbox>
         </el-form-item>
 
         <el-form-item label="朝向:" :label-width="formLabelWidth1">
@@ -29,44 +29,70 @@
         <el-row :gutter="10">
           <el-col :span="10">
             <el-form-item label="X(0-1):" :label-width="formLabelWidth2">
-              <el-input v-model="skvalue.X"></el-input>
+              <el-input-number
+                v-model="skvalue.X"
+                :min="0"
+                :max="1"
+                :step="0.01"
+                controls-position="right"
+              ></el-input-number>
             </el-form-item>
           </el-col>
           <el-col :span="10">
             <el-form-item label="Y(0-1):" :label-width="formLabelWidth2">
-              <el-input v-model="skvalue.Y"></el-input>
+              <el-input-number
+                v-model="skvalue.Y"
+                :min="0"
+                :max="1"
+                :step="0.01"
+                controls-position="right"
+              ></el-input-number>
             </el-form-item>
           </el-col>
           <el-col :span="4">
-            <el-button style="width: 100%">更新</el-button>
+            <el-button style="width: 100%" @click="handleUpdate">更新</el-button>
           </el-col>
         </el-row>
 
         <el-row :gutter="10">
           <el-col :span="10">
             <el-form-item label="宽度(0-1):" :label-width="formLabelWidth2">
-              <el-input v-model="skvalue.width"></el-input>
+              <el-input-number
+                v-model="skvalue.width"
+                :min="0"
+                :max="1"
+                :step="0.01"
+                controls-position="right"
+              ></el-input-number>
             </el-form-item>
           </el-col>
           <el-col :span="10">
             <el-form-item label="高度(0-1):" :label-width="formLabelWidth2">
-              <el-input v-model="skvalue.height"></el-input>
+              <el-input-number
+                v-model="skvalue.height"
+                :min="0"
+                :max="1"
+                :step="0.01"
+                controls-position="right"
+              ></el-input-number>
             </el-form-item>
           </el-col>
           <el-col :span="4">
-            <el-button style="width: 100%">更新</el-button>
+            <el-button style="width: 100%" @click="handleUpdate">更新</el-button>
           </el-col>
         </el-row>
 
         <el-form-item label="跳过层级:" :label-width="formLabelWidth1">
           <el-input-number
-            v-model="skvalue.skipc"
+            v-model="skvalue.skipLevels"
+            :min="1"
+            :max="10"
             controls-position="right"
           ></el-input-number>
         </el-form-item>
 
         <el-form-item label="字体:" :label-width="formLabelWidth1">
-          <el-row style="width: 100%" gutter="10">
+          <el-row style="width: 100%" :gutter="10">
             <el-col :span="20">
               <el-select v-model="skvalue.font">
                 <el-option
@@ -87,6 +113,8 @@
                   line-height: 32px;
                   text-align: center;
                 "
+                :class="{ active: skvalue.italic }"
+                @click="toggleItalic"
               >
                 <i style="font-style: italic; font-size: 24px">I</i>
               </el-button>
@@ -101,6 +129,8 @@
                   line-height: 32px;
                   text-align: center;
                 "
+                :class="{ active: skvalue.bold }"
+                @click="toggleBold"
               >
                 <b style="font-weight: bold; font-size: 24px">B</b>
               </el-button>
@@ -109,7 +139,7 @@
         </el-form-item>
 
         <el-form-item label="字体大小:" :label-width="formLabelWidth1">
-          <el-select v-model="skvalue.fontsize">
+          <el-select v-model="skvalue.fontSize">
             <el-option
               v-for="item in fontsizeoptions"
               :key="item.value"
@@ -120,7 +150,7 @@
         </el-form-item>
 
         <el-form-item label="数据格式:" :label-width="formLabelWidth1">
-          <el-select v-model="skvalue.dataformat">
+          <el-select v-model="skvalue.dataFormat">
             <el-option
               v-for="item in dataformatoptions"
               :key="item.value"
@@ -132,19 +162,21 @@
 
         <el-form-item label="精度:" :label-width="formLabelWidth1">
           <el-input-number
-            v-model="skvalue.jingdu"
+            v-model="skvalue.precision"
+            :min="0"
+            :max="6"
             controls-position="right"
           ></el-input-number>
         </el-form-item>
 
-        <el-form-item label="" :label-width="formLabelWidth1">
-          <el-checkbox label="显示标题" v-model="skvalue.check2"></el-checkbox>
+        <el-form-item label="显示标题:" :label-width="formLabelWidth1">
+          <el-checkbox v-model="skvalue.showTitle" label="显示标题"></el-checkbox>
         </el-form-item>
 
         <el-form-item label="标题文本:" :label-width="formLabelWidth1">
-          <el-row>
+          <el-row :gutter="10">
             <el-col :span="12">
-              <el-select v-model="skvalue.texttitle">
+              <el-select v-model="skvalue.titleSource">
                 <el-option
                   v-for="item in texttitleoptions"
                   :key="item.value"
@@ -154,15 +186,19 @@
               </el-select>
             </el-col>
             <el-col :span="12">
-              <el-input v-model="skvalue.customTitle"></el-input>
+              <el-input
+                v-model="skvalue.customTitle"
+                :disabled="skvalue.titleSource !== 'custom'"
+                placeholder="请输入自定义标题"
+              ></el-input>
             </el-col>
           </el-row>
         </el-form-item>
 
         <el-form-item label="标题字体:" :label-width="formLabelWidth1">
-          <el-row style="width: 100%" gutter="10">
+          <el-row style="width: 100%" :gutter="10">
             <el-col :span="20">
-              <el-select v-model="skvalue.titlefont">
+              <el-select v-model="skvalue.titleFont">
                 <el-option
                   v-for="item in titlefontoptions"
                   :key="item.value"
@@ -181,6 +217,8 @@
                   line-height: 32px;
                   text-align: center;
                 "
+                :class="{ active: skvalue.titleItalic }"
+                @click="toggleTitleItalic"
               >
                 <i style="font-style: italic; font-size: 24px">I</i>
               </el-button>
@@ -195,6 +233,8 @@
                   line-height: 32px;
                   text-align: center;
                 "
+                :class="{ active: skvalue.titleBold }"
+                @click="toggleTitleBold"
               >
                 <b style="font-weight: bold; font-size: 24px">B</b>
               </el-button>
@@ -202,8 +242,8 @@
           </el-row>
         </el-form-item>
 
-        <el-form-item label="字体大小:" :label-width="formLabelWidth1">
-          <el-select v-model="skvalue.fontsize2">
+        <el-form-item label="标题字体大小:" :label-width="formLabelWidth1">
+          <el-select v-model="skvalue.titleFontSize">
             <el-option
               v-for="item in fontsizeoptions"
               :key="item.value"
@@ -218,112 +258,221 @@
 </template>
 
 <script setup>
-import { ref, defineProps, defineEmits, watch } from "vue"
-import SubDialog from "./SubDialog.vue"
+import { ref, defineProps, defineEmits, watch } from "vue";
+import SubDialog from "./SubDialog.vue";
 
 const props = defineProps({
   modelValue: Boolean,
+  renderer: Object,
+  threeSceneRef: Object,
+  scene: Object,
+  pltData: Object,
   initialData: {
     type: Object,
     default: () => ({
       check1: true,
-      orientation: 'vertical',
-      X: 0.8,
-      Y: 0.05,
-      width: 0.15,
-      height: 0.4,
+      orientation: "vertical",
+      X: 0.85,
+      Y: 0.85,
+      width: 0.18,
+      height: 0.35,
       skipLevels: 2,
-      font: 'Arial',
-      fontSize: 12,
-      dataFormat: 'scientific',
+      font: "Arial",
+      fontSize: 16,
+      dataFormat: "scientific",
       precision: 2,
-      showTitle: true,
-      titleSource: 'variable',
-      customTitle: '',
-      titleFont: 'Arial',
-      titleFontSize: 16,
+      showTitle: true, // 默认显示标题
+      titleSource: "variable",
+      customTitle: "",
+      titleFont: "Arial",
+      titleFontSize: 20,
       bold: false,
-      italic: false
-    })
-  }
-})
+      italic: false,
+      titleBold: false,
+      titleItalic: false,
+    }),
+  },
+});
 
-const emit = defineEmits(["update:modelValue", "confirm", "cancel"])
+const emit = defineEmits(["update:modelValue", "confirm", "cancel"]);
 
-// 将初始值转换为响应式对象
-const colorCardConfig = ref({...props.initialData})
+// 响应式表单数据,初始化时与 initialData 同步
+const skvalue = ref({ ...props.initialData });
 
-// 监听初始值变化
-watch(() => props.initialData, (newVal) => {
-  colorCardConfig.value = {...newVal}
-}, { deep: true })
+// 监听 initialData 变化
+watch(
+  () => props.initialData,
+  (newVal) => {
+    skvalue.value = { ...newVal };
+  },
+  { deep: true }
+);
 
 // 表单标签宽度
-let formLabelWidth1 = ref(140)
-let formLabelWidth2 = ref(100)
-
-// 表单数据
-let skvalue = ref({
-  check1: '1',
-  orientation: '竖直',
-  X: '0.85',      // 位置更靠右
-  Y: '0.85',      // 位置更靠下
-  width: '0.18',  // 增大宽度(原0.15)
-  height: '0.35',  // 增大高度(原0.4)
-  skipc: '2',
-  font: '微软雅黑',
-  fontsize: '16',  // 增大字体(原15)
-  dataformat: '科学计数法',
-  jingdu: '2',
-  check2: '1',
-  texttitle: '使用变量名',
-  titlefont: 'Arial',
-  fontsize2: '20', // 增大标题字体
-})
+const formLabelWidth1 = ref("140px");
+const formLabelWidth2 = ref("100px");
 
 // 下拉选项数据
-let orientationoptions = ref([
-  { label:'竖直', value: '竖直'},
-  { label:'水平', value: '水平'},
-])
+const orientationoptions = ref([
+  { label: "竖直", value: "vertical" },
+  { label: "水平", value: "horizontal" },
+]);
 
-let fontoptions = ref([
-  { label:'微软雅黑', value: '微软雅黑'},
-])
+const fontoptions = ref([
+  { label: "Arial", value: "Arial" },
+  { label: "微软雅黑", value: "Microsoft YaHei" },
+  { label: "Times New Roman", value: "Times New Roman" },
+  { label: "Helvetica", value: "Helvetica" },
+]);
 
-let fontsizeoptions = ref([
-  { label:'15', value: '15'},
-])
+const fontsizeoptions = ref([
+  { label: "12", value: 12 },
+  { label: "14", value: 14 },
+  { label: "16", value: 16 },
+  { label: "18", value: 18 },
+  { label: "20", value: 20 },
+]);
 
 const dataformatoptions = ref([
   { value: "float", label: "浮点数" },
-  { value: "scientific", label: "科学计数法" }
-])
+  { value: "scientific", label: "科学计数法" },
+]);
 
 const texttitleoptions = ref([
-  { value: "default", label: "默认标题" },
-  { value: "custom", label: "自定义" }
-])
+  { value: "variable", label: "使用变量名" },
+  { value: "custom", label: "自定义" },
+]);
+
+const titlefontoptions = ref([
+  { label: "Arial", value: "Arial" },
+  { label: "微软雅黑", value: "Microsoft YaHei" },
+  { label: "Times New Roman", value: "Times New Roman" },
+  { label: "Helvetica", value: "Helvetica" },
+]);
+
+// 切换字体斜体
+const toggleItalic = () => {
+  skvalue.value.italic = !skvalue.value.italic;
+};
+
+// 切换字体加粗
+const toggleBold = () => {
+  skvalue.value.bold = !skvalue.value.bold;
+};
+
+// 切换标题字体斜体
+const toggleTitleItalic = () => {
+  skvalue.value.titleItalic = !skvalue.value.titleItalic;
+};
+
+// 切换标题字体加粗
+const toggleTitleBold = () => {
+  skvalue.value.titleBold = !skvalue.value.titleBold;
+};
 
-let titlefontoptions = ref([
-  { label:'Arial', value: 'Arial'},
-])
+// 更新按钮:实时预览颜色条
+const handleUpdate = () => {
+  const config = {
+    check1: skvalue.value.check1,
+    orientation: skvalue.value.orientation,
+    X: Number(skvalue.value.X),
+    Y: Number(skvalue.value.Y),
+    width: Number(skvalue.value.width),
+    height: Number(skvalue.value.height),
+    skipLevels: Number(skvalue.value.skipLevels),
+    font: skvalue.value.font,
+    fontSize: Number(skvalue.value.fontSize),
+    dataFormat: skvalue.value.dataFormat,
+    precision: Number(skvalue.value.precision),
+    showTitle: skvalue.value.showTitle,
+    titleSource: skvalue.value.titleSource,
+    customTitle: skvalue.value.customTitle,
+    titleFont: skvalue.value.titleFont,
+    titleFontSize: Number(skvalue.value.titleFontSize),
+    bold: skvalue.value.bold,
+    italic: skvalue.value.italic,
+    titleBold: skvalue.value.titleBold,
+    titleItalic: skvalue.value.titleItalic,
+  };
+  if (props.threeSceneRef && props.threeSceneRef.updateColorBar) {
+    props.threeSceneRef.updateColorBar({
+      colorRange: props.pltData?.zones?.[0]?.variables?.["CoefPressure"]
+        ? {
+            min: Math.min(...props.pltData.zones[0].variables["CoefPressure"]),
+            max: Math.max(...props.pltData.zones[0].variables["CoefPressure"]),
+          }
+        : { min: 0, max: 1 },
+      title: config.titleSource === "custom" ? config.customTitle : "CoefPressure",
+      intervals: generateIntervals(config.skipLevels, config.precision),
+      font: config.font,
+      fontSize: config.fontSize,
+      bold: config.bold,
+      italic: config.italic,
+      titleFont: config.titleFont,
+      titleFontSize: config.titleFontSize,
+      titleBold: config.titleBold,
+      titleItalic: config.titleItalic,
+      width: config.width * 100,
+      height: config.height * 300,
+      position: config.orientation === "vertical" ? "right" : "left",
+      X: config.X,
+      Y: config.Y,
+      draggable: true,
+      check1: config.check1,
+      showTitle: config.showTitle,
+    });
+  }
+};
+
+// 生成标签间隔
+const generateIntervals = (skipLevels, precision) => {
+  const min = props.pltData?.zones?.[0]?.variables?.["CoefPressure"]
+    ? Math.min(...props.pltData.zones[0].variables["CoefPressure"])
+    : 0;
+  const max = props.pltData?.zones?.[0]?.variables?.["CoefPressure"]
+    ? Math.max(...props.pltData.zones[0].variables["CoefPressure"])
+    : 1;
+  const step = (max - min) / (skipLevels + 1 || 1);
+  const intervals = [];
+  for (let i = 0; i <= skipLevels + 1; i++) {
+    intervals.push((min + i * step).toFixed(precision));
+  }
+  return intervals;
+};
 
 // 确认操作
 const handleConfirm = () => {
-  emit("confirm", {
-    ...colorCardConfig.value,
-    // 转换朝向值为Three.js可用的值
-    orientation: colorCardConfig.value.orientation === '竖直' ? 'vertical' : 'horizontal'
-  })
-  emit("update:modelValue", false)
-}
+  const config = {
+    check1: skvalue.value.check1,
+    orientation: skvalue.value.orientation,
+    X: Number(skvalue.value.X),
+    Y: Number(skvalue.value.Y),
+    width: Number(skvalue.value.width),
+    height: Number(skvalue.value.height),
+    skipLevels: Number(skvalue.value.skipLevels),
+    font: skvalue.value.font,
+    fontSize: Number(skvalue.value.fontSize),
+    dataFormat: skvalue.value.dataFormat,
+    precision: Number(skvalue.value.precision),
+    showTitle: skvalue.value.showTitle,
+    titleSource: skvalue.value.titleSource,
+    customTitle: skvalue.value.customTitle,
+    titleFont: skvalue.value.titleFont,
+    titleFontSize: Number(skvalue.value.titleFontSize),
+    bold: skvalue.value.bold,
+    italic: skvalue.value.italic,
+    titleBold: skvalue.value.titleBold,
+    titleItalic: skvalue.value.titleItalic,
+  };
+  emit("confirm", config);
+  emit("update:modelValue", false);
+};
 
 // 取消操作
 const handleCancel = () => {
-  emit("cancel")
-  emit("update:modelValue", false)
-}
+  emit("cancel");
+  emit("update:modelValue", false);
+};
 </script>
 
 <style scoped>
@@ -333,13 +482,16 @@ const handleCancel = () => {
   max-height: 100%;
 }
 
-/* 调整表单项间距 */
 .el-form-item {
   margin-bottom: 8px;
 }
 
-/* 调整行内元素间距 */
 .el-row {
   margin-bottom: 10px;
 }
+
+.el-button.active {
+  background-color: #409eff;
+  color: white;
+}
 </style>

+ 136 - 36
src/components/cloudChart/index.vue

@@ -25,7 +25,10 @@
         <el-row gutter="20">
           <el-col :span="2"></el-col>
           <el-col v-for="(item, index) in cloudbtnbox" :key="index" :span="4">
-            <el-button style="width: 100%" @click="handleButtonClick(item.btnname)">
+            <el-button
+              style="width: 100%"
+              @click="handleButtonClick(item.btnname)"
+            >
               <el-image
                 :src="getImgPath(item.url)"
                 alt="img"
@@ -37,7 +40,7 @@
           </el-col>
         </el-row>
       </div>
-      
+
       <!-- 文件选择对话框 -->
       <FileSelectDialog
         v-model="fileSelectDialogVisible"
@@ -48,7 +51,7 @@
         @confirm="handleFileSelectConfirm"
         @cancel="fileSelectDialogVisible = false"
       />
-      
+
       <!-- 域对话框 -->
       <DomainDialog
         v-model="domainDialogVisible"
@@ -62,7 +65,7 @@
         @confirm="handleDomainConfirm"
         @cancel="domainDialogVisible = false"
       />
-      
+
       <!-- 云图对话框 -->
       <CloudMapDialog
         v-model="cloudMapDialogVisible"
@@ -76,7 +79,7 @@
         @confirm="handleCloudMapConfirm"
         @cancel="cloudMapDialogVisible = false"
       />
-      
+
       <!-- 色卡对话框 -->
       <ColorCardDialog
         v-model="colorCardDialogVisible"
@@ -84,10 +87,32 @@
         :three-scene-ref="threeSceneRef"
         :scene="scene"
         :plt-data="pltData"
+        :initial-data="{
+          check1: true,
+          orientation: 'vertical',
+          X: 0.85,
+          Y: 0.85,
+          width: 0.18,
+          height: 0.35,
+          skipLevels: 2,
+          font: 'Arial',
+          fontSize: 16,
+          dataFormat: 'scientific',
+          precision: 2,
+          showTitle: true,
+          titleSource: 'variable',
+          customTitle: '',
+          titleFont: 'Arial',
+          titleFontSize: 20,
+          bold: false,
+          italic: false,
+          titleBold: false,
+          titleItalic: false
+        }"
         @confirm="handleColorCardConfirm"
         @cancel="colorCardDialogVisible = false"
       />
-      
+
       <!-- 等值线对话框 -->
       <ContourDialog
         v-model="contourDialogVisible"
@@ -101,7 +126,7 @@
         @confirm="handleContourConfirm"
         @cancel="contourDialogVisible = false"
       />
-      
+
       <div
         style="overflow: auto"
         v-loading="isLoading"
@@ -215,7 +240,6 @@ const props = defineProps({
 
 const emit = defineEmits(["update:modelValue", "close"])
 
-const colorCardConfig = ref(null)
 let cloudbtnbox = ref([
   { url: "meshFile.png", btnname: "文件选择" },
   { url: "yu.png", btnname: "域" },
@@ -224,6 +248,29 @@ let cloudbtnbox = ref([
   { url: "dengzx.png", btnname: "等值线" }
 ])
 
+const colorCardConfig = ref({
+  check1: true,
+  orientation: "vertical",
+  X: 0.85,
+  Y: 0.85,
+  width: 0.18,
+  height: 0.35,
+  skipLevels: 2,
+  font: "Arial",
+  fontSize: 16,
+  dataFormat: "scientific",
+  precision: 2,
+  showTitle: true,
+  titleSource: "variable",
+  customTitle: "",
+  titleFont: "Arial",
+  titleFontSize: 20,
+  bold: false,
+  italic: false,
+  titleBold: false,
+  titleItalic: false
+})
+
 let isLoading = ref(false)
 let pltData = ref()
 const renderer = ref()
@@ -231,9 +278,9 @@ const pltStore = usePltDataStore()
 
 const contourInitialData = ref({
   ranges: {},
-  currentZone: '',
+  currentZone: "",
   variables: []
-});
+})
 
 // 处理按钮点击
 const handleButtonClick = (btnName) => {
@@ -251,7 +298,7 @@ const handleButtonClick = (btnName) => {
       colorCardDialogVisible.value = true
       break
     case "等值线":
-      openContourDialog();
+      openContourDialog()
       break
     default:
       break
@@ -261,7 +308,7 @@ const handleButtonClick = (btnName) => {
 // 计算所有变量的范围
 const calculateVariableRanges = () => {
   const ranges = {}
-  pltData.value?.zones?.forEach(zone => {
+  pltData.value?.zones?.forEach((zone) => {
     Object.entries(zone.variables || {}).forEach(([name, data]) => {
       if (!ranges[name]) {
         ranges[name] = { min: Infinity, max: -Infinity }
@@ -290,12 +337,65 @@ const handleCloudMapConfirm = (data) => {
 }
 
 const handleColorCardConfirm = (data) => {
-  colorCardConfig.value = data
+  colorCardConfig.value = { ...data } // 保存配置
+  if (
+    data.check1 &&
+    threeSceneRef.value &&
+    threeSceneRef.value.colorBar &&
+    threeSceneRef.value.colorBar.value
+  ) {
+    threeSceneRef.value.updateColorBar({
+      colorRange: pltData.value?.zones?.[0]?.variables?.["CoefPressure"]
+        ? {
+            min: Math.min(...pltData.value.zones[0].variables["CoefPressure"]),
+            max: Math.max(...pltData.value.zones[0].variables["CoefPressure"])
+          }
+        : { min: 0, max: 1 },
+      title: data.titleSource === "custom" ? data.customTitle : "CoefPressure",
+      intervals: generateIntervals(data.skipLevels, data.precision),
+      font: data.font,
+      fontSize: data.fontSize,
+      bold: data.bold,
+      italic: data.italic,
+      titleFont: data.titleFont,
+      titleFontSize: data.titleFontSize,
+      titleBold: data.titleBold,
+      titleItalic: data.titleItalic,
+      width: data.width * 100, // 转换为像素
+      height: data.height * 300, // 转换为像素
+      position: data.orientation === "vertical" ? "right" : "left",
+      X: data.X,
+      Y: data.Y,
+      draggable: true
+    })
+  } else if (
+    threeSceneRef.value &&
+    threeSceneRef.value.colorBar &&
+    threeSceneRef.value.colorBar.value
+  ) {
+    // 隐藏颜色条
+    threeSceneRef.value.colorBar.value.container.style.display = "none"
+  }
   colorCardDialogVisible.value = false
 }
 
+const generateIntervals = (skipLevels, precision) => {
+  const min = pltData.value?.zones?.[0]?.variables?.["CoefPressure"]
+    ? Math.min(...pltData.value.zones[0].variables["CoefPressure"])
+    : 0
+  const max = pltData.value?.zones?.[0]?.variables?.["CoefPressure"]
+    ? Math.max(...pltData.value.zones[0].variables["CoefPressure"])
+    : 1
+  const step = (max - min) / (skipLevels + 1 || 1) // 防止除零
+  const intervals = []
+  for (let i = 0; i <= skipLevels + 1; i++) {
+    intervals.push((min + i * step).toFixed(precision))
+  }
+  return intervals
+}
+
 const handleContourConfirm = (params) => {
-  console.log("原始参数:", JSON.parse(JSON.stringify(params)));
+  console.log("原始参数:", JSON.parse(JSON.stringify(params)))
 
   // 参数标准化处理
   const processedParams = {
@@ -306,42 +406,42 @@ const handleContourConfirm = (params) => {
       minValue: parseFloat(params.options?.minValue) || 0,
       maxValue: parseFloat(params.options?.maxValue) || 1
     }
-  };
+  }
 
-  console.log("处理后的参数:", processedParams);
+  console.log("处理后的参数:", processedParams)
 
   threeSceneRef.value?.generateContoursForAllZones(
     // processedParams.zoneName,
     processedParams.variableName,
     processedParams.options
-  );
-};
+  )
+}
 
 const openContourDialog = () => {
-  contourDialogVisible.value = true;
-  
+  contourDialogVisible.value = true
+
   // 获取当前激活的区域(假设默认显示第一个可见区域)
-  const visibleZones = pltData.value?.zones?.filter(zone => 
+  const visibleZones = pltData.value?.zones?.filter((zone) =>
     threeSceneRef.value?.getZoneVisibility(zone.name)
-  );
-  const currentZone = visibleZones?.[0]?.name || '';
+  )
+  const currentZone = visibleZones?.[0]?.name || ""
 
   // 计算变量范围
   const ranges = pltData.value?.zones?.reduce((acc, zone) => {
     Object.entries(zone.variables || {}).forEach(([name, data]) => {
-      if (!acc[name]) acc[name] = { min: Infinity, max: -Infinity };
-      acc[name].min = Math.min(acc[name].min, ...data);
-      acc[name].max = Math.max(acc[name].max, ...data);
-    });
-    return acc;
-  }, {});
+      if (!acc[name]) acc[name] = { min: Infinity, max: -Infinity }
+      acc[name].min = Math.min(acc[name].min, ...data)
+      acc[name].max = Math.max(acc[name].max, ...data)
+    })
+    return acc
+  }, {})
 
   contourInitialData.value = {
     ranges,
-    currentZone,  // 使用实际获取的区域名
+    currentZone, // 使用实际获取的区域名
     variables: pltData.value?.metadata?.variables || []
-  };
-};
+  }
+}
 
 const getImgPath = (url) => {
   return new URL(`../../assets/img/${url}`, import.meta.url).href
@@ -385,16 +485,16 @@ const getPltData = async (fpid) => {
     console.log("解析后的数据:", parsedData)
 
     // 数据验证
-    parsedData.zones.forEach(zone => {
+    parsedData.zones.forEach((zone) => {
       console.log(`区域 ${zone.name} 数据验证:`, {
         vertexCount: zone.vertices?.length / 3,
         varStatus: Object.entries(zone.variables || {}).map(([name, data]) => ({
           name,
           count: data.length,
-          nanCount: data.filter(v => isNaN(v)).length
+          nanCount: data.filter((v) => isNaN(v)).length
         }))
-      });
-    });
+      })
+    })
 
     pltStore.initialize(parsedData.zones)
     await nextTick()

+ 38 - 15
src/composables/useThree.js

@@ -290,22 +290,45 @@ export function useThree(canvasRef, props) {
   };
 
 const updateColorBar = (params) => {
-  if (colorBar.value) {
-    colorBar.value.update(
-      params.colorRange.min,
-      params.colorRange.max,
-      params.title,
-      {
-        intervals: params.intervals,
-        colorRange: { // 保持参数结构一致
-          min: params.colorRange.min,
-          max: params.colorRange.max,
-          minColor: params.colorRange.minColor,
-          maxColor: params.colorRange.maxColor
-        }
-      }
-    );
+  if (!colorBar.value) {
+    console.warn("ColorBar not initialized");
+    return;
+  }
+  if (params.check1 === false) {
+    colorBar.value.container.style.display = "none";
+    return;
   }
+  colorBar.value.container.style.display = "block";
+  colorBar.value.update(
+    params.colorRange.min,
+    params.colorRange.max,
+    params.title || "CoefPressure", // 默认标题
+    {
+      intervals: params.intervals,
+      font: params.font,
+      fontSize: params.fontSize,
+      bold: params.bold,
+      italic: params.italic,
+      titleFont: params.titleFont,
+      titleFontSize: params.titleFontSize,
+      titleBold: params.titleBold,
+      titleItalic: params.titleItalic,
+      width: params.width,
+      height: params.height,
+      position: params.position,
+      X: params.X,
+      Y: params.Y,
+      draggable: params.draggable,
+      check1: params.check1,
+      showTitle: params.showTitle !== undefined ? params.showTitle : true, // 默认显示标题
+      colorRange: {
+        min: params.colorRange.min,
+        max: params.colorRange.max,
+        minColor: params.colorRange.minColor || [0, 0, 1],
+        maxColor: params.colorRange.maxColor || [1, 0, 0],
+      },
+    }
+  );
 };
 
   const generateContour = (zoneName, variableName, options) => {

+ 267 - 234
src/utils/three/controls/ColorBar.js

@@ -1,255 +1,288 @@
-import { draggable } from 'element-plus/es/components/color-picker/src/utils/draggable.mjs';
+// ColorBar.js
 import * as THREE from 'three';
-import { CSS2DRenderer, CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer';
+import { 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();
-        }
-    }
+  constructor(scene, camera, containerElement, options = {}) {
+    this.scene = scene;
+    this.camera = camera;
+    this.containerElement = containerElement;
+    this.options = {
+      width: 10,
+      height: 300,
+      position: 'right',
+      X: 0.85,
+      Y: 0.85,
+      title: 'Value', // 默认标题
+      colorMap: 'rainbow',
+      min: 0,
+      max: 1,
+      gradientStart: [0, 0, 1],
+      gradientEnd: [1, 0, 0],
+      font: 'Arial',
+      fontSize: 12,
+      bold: false,
+      italic: false,
+      titleFont: 'Arial',
+      titleFontSize: 16,
+      titleBold: false,
+      titleItalic: false,
+      draggable: true,
+      check1: true,
+      showTitle: true,
+      dataFormat: 'scientific',
+      ...options,
+    };
 
-    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 = '拖拽移动位置';
-        }
+    this.init();
+    if (this.options.draggable) {
+      this.dragCleanup = this.enableDrag();
     }
+  }
 
-    // 修改渐变生成以考虑颜色范围
-    createGradient() {
-        const stops = []
-        const rangeMin = this.options.min
-        const rangeMax = this.options.max
-        const dataMin = this.options.dataMin || rangeMin
-        const dataMax = this.options.dataMax || rangeMax
-
-        for (let i = 0; i <= 100; i += 10) {
-            // 计算在数据范围内的相对位置
-            const t = i / 100
-            const dataValue = dataMin + t * (dataMax - dataMin)
-            // 计算在颜色范围内的相对位置
-            const colorT = (dataValue - rangeMin) / (rangeMax - rangeMin)
-            const color = this.getColor(Math.max(0, Math.min(1, colorT)))
-            stops.push(`${color} ${i}%`)
-        }
-        return `linear-gradient(to top, ${stops.join(', ')})`
-    }
+  init() {
+    this.container = document.createElement('div');
+    this.container.style.position = 'absolute';
+    this.container.style.pointerEvents = 'none';
+    this.container.style.display = this.options.check1 ? 'flex' : 'none';
+    this.container.style.flexDirection = this.options.position === 'horizontal' ? 'row' : 'column';
 
-    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)})`;
-    }
+    this.titleElement = document.createElement('div');
+    this.titleElement.style.fontFamily = this.options.titleFont;
+    this.titleElement.style.textAlign = 'center';
+    this.titleElement.style.fontWeight = this.options.titleBold ? 'bold' : 'normal';
+    this.titleElement.style.fontStyle = this.options.titleItalic ? 'italic' : 'normal';
+    this.titleElement.style.fontSize = `${this.options.titleFontSize}px`;
+    this.titleElement.style.marginBottom = this.options.position === 'horizontal' ? '0' : '5px';
+    this.titleElement.style.marginRight = this.options.position === 'horizontal' ? '5px' : '0';
+    this.titleElement.style.display = this.options.showTitle ? 'block' : 'none'; // 控制标题显示
+    this.titleElement.textContent = this.options.title || 'Value'; // 默认标题
 
-    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];
-        }
-    }
+    this.subContainer = document.createElement('div');
+    this.subContainer.style.display = 'flex';
+    this.subContainer.style.alignItems = 'flex-start';
+    this.subContainer.style.flexDirection = this.options.position === 'horizontal' ? 'column' : 'row';
+
+    this.barElement = document.createElement('div');
+    this.barElement.className = 'color-bar';
+    this.barElement.style.width = this.options.position === 'horizontal' ? `${this.options.height}px` : `${this.options.width}px`;
+    this.barElement.style.height = this.options.position === 'horizontal' ? `${this.options.width}px` : `${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.fontFamily = this.options.font;
+    this.labelsElement.style.fontWeight = this.options.bold ? 'bold' : 'normal';
+    this.labelsElement.style.fontStyle = this.options.italic ? 'italic' : 'normal';
+    this.labelsElement.style.flexDirection = this.options.position === 'horizontal' ? 'row' : 'column';
+    this.labelsElement.style.justifyContent = 'space-between';
+    this.labelsElement.style.height = this.options.position === 'horizontal' ? 'auto' : `${this.options.height}px`;
+    this.labelsElement.style.width = this.options.position === 'horizontal' ? `${this.options.height}px` : 'auto';
+    this.labelsElement.style.marginLeft = this.options.position === 'horizontal' ? '0' : '8px';
+    this.labelsElement.style.marginTop = this.options.position === 'horizontal' ? '8px' : '0';
+    this.labelsElement.style.fontSize = `${this.options.fontSize}px`;
+    this.updateLabels();
+
+    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());
 
-    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];
-        }
+    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 = [];
+    const rangeMin = this.options.min;
+    const rangeMax = this.options.max;
+    const dataRange = rangeMax - rangeMin || 1;
 
-    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`;
+    for (let i = 0; i <= 100; i += 10) {
+      const t = i / 100;
+      const value = rangeMin + t * dataRange;
+      if (rangeMin === 0 && rangeMax === 0) {
+        stops.push(`rgb(0, 0, 255) ${i}%`);
+        continue;
+      }
+      const colorPos = (value - rangeMin) / dataRange;
+      const color = this.getColor(Math.max(0, Math.min(1, colorPos)));
+      stops.push(`${color} ${i}%`);
     }
 
-update(min, max, title, options = {}) {
-  // 更新基础属性
-  this.options.min = min;
-  this.options.max = max;
-  this.options.title = title || this.options.title;
-  this.titleElement.textContent = this.options.title;
-
-  // 更新颜色范围(从options.colorRange获取)
-  if (options.colorRange) {
-    this.options.minColor = options.colorRange.minColor || [0, 0, 1];
-    this.options.maxColor = options.colorRange.maxColor || [1, 0, 0];
+    return `linear-gradient(${this.options.position === 'horizontal' ? 'to right' : '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, 0, 1];
+    else if (t < 0.5) return [0, (t - 0.25) * 4, 1];
+    else if (t < 0.75) return [(t - 0.5) * 4, 1, 1 - (t - 0.5) * 4];
+    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];
   }
 
-  // 更新渐变和标签
-  this.barElement.style.background = this.createGradient();
-  this.updateLabels(options.intervals);
-}
-
-    // 支持自定义间隔
-    updateLabels(intervals) {
-        if (intervals && intervals.length > 0) {
-            // 使用传入的间隔
-            this.labelsElement.innerHTML = intervals
-                .map(val => `<span>${Number(val).toFixed(2)}</span>`)
-                .join('')
-        } else {
-            // 默认三等分
-            const mid = (this.options.min + this.options.max) / 2
-            this.labelsElement.innerHTML = `
-        <span>${this.options.max.toFixed(2)}</span>
-        <span>${mid.toFixed(2)}</span>
-        <span>${this.options.min.toFixed(2)}</span>
-      `
-        }
+  updatePosition() {
+    if (!this.containerElement || !this.container) return;
+
+    const { width, height, X, Y } = this.options;
+    const containerRect = this.containerElement.getBoundingClientRect();
+    const titleHeight = this.options.showTitle ? this.options.titleFontSize + 5 : 0;
+    const x = X * (containerRect.width - width);
+    const y = Y * (containerRect.height - height - titleHeight);
+
+    this.container.style.left = `${x}px`;
+    this.container.style.top = `${y}px`;
+  }
+
+  update(min, max, title, options = {}) {
+    this.options.min = min;
+    this.options.max = max;
+    this.options.title = title || this.options.title || 'Value'; // 确保标题不为空
+    this.options.font = options.font || this.options.font;
+    this.options.fontSize = options.fontSize || this.options.fontSize;
+    this.options.bold = options.bold !== undefined ? options.bold : this.options.bold;
+    this.options.italic = options.italic !== undefined ? options.italic : this.options.italic;
+    this.options.titleFont = options.titleFont || this.options.titleFont;
+    this.options.titleFontSize = options.titleFontSize || this.options.titleFontSize;
+    this.options.titleBold = options.titleBold !== undefined ? options.titleBold : this.options.titleBold;
+    this.options.titleItalic = options.titleItalic !== undefined ? options.titleItalic : this.options.titleItalic;
+    this.options.width = options.width || this.options.width;
+    this.options.height = options.height || this.options.height;
+    this.options.position = options.position || this.options.position;
+    this.options.X = options.X || this.options.X;
+    this.options.Y = options.Y || this.options.Y;
+    this.options.draggable = options.draggable !== undefined ? options.draggable : this.options.draggable;
+    this.options.check1 = options.check1 !== undefined ? options.check1 : this.options.check1;
+    this.options.showTitle = options.showTitle !== undefined ? options.showTitle : this.options.showTitle;
+    this.options.dataFormat = options.dataFormat || this.options.dataFormat;
+    this.options.gradientStart = options.colorRange?.minColor || this.options.gradientStart;
+    this.options.gradientEnd = options.colorRange?.maxColor || this.options.gradientEnd;
+
+    this.container.style.display = this.options.check1 ? 'flex' : 'none';
+    this.container.style.flexDirection = this.options.position === 'horizontal' ? 'row' : 'column';
+    this.titleElement.style.display = this.options.showTitle ? 'block' : 'none';
+    this.titleElement.textContent = this.options.showTitle ? this.options.title : '';
+    this.titleElement.style.fontFamily = this.options.titleFont;
+    this.titleElement.style.fontSize = `${this.options.titleFontSize}px`;
+    this.titleElement.style.fontWeight = this.options.titleBold ? 'bold' : 'normal';
+    this.titleElement.style.fontStyle = this.options.titleItalic ? 'italic' : 'normal';
+    this.barElement.style.width = this.options.position === 'horizontal' ? `${this.options.height}px` : `${this.options.width}px`;
+    this.barElement.style.height = this.options.position === 'horizontal' ? `${this.options.width}px` : `${this.options.height}px`;
+    this.barElement.style.background = this.createGradient();
+    this.labelsElement.style.fontFamily = this.options.font;
+    this.labelsElement.style.fontSize = `${this.options.fontSize}px`;
+    this.labelsElement.style.fontWeight = this.options.bold ? 'bold' : 'normal';
+    this.labelsElement.style.fontStyle = this.options.italic ? 'italic' : 'normal';
+    this.labelsElement.style.flexDirection = this.options.position === 'horizontal' ? 'row' : 'column';
+    this.labelsElement.style.height = this.options.position === 'horizontal' ? 'auto' : `${this.options.height}px`;
+    this.labelsElement.style.width = this.options.position === 'horizontal' ? `${this.options.height}px` : 'auto';
+    this.labelsElement.style.marginLeft = this.options.position === 'horizontal' ? '0' : '8px';
+    this.labelsElement.style.marginTop = this.options.position === 'horizontal' ? '8px' : '0';
+    if (options.intervals) {
+      this.updateLabels(options.intervals, options.precision);
     }
+    this.updatePosition();
 
-    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);
-        };
+    if (this.options.draggable && !this.dragCleanup) {
+      this.dragCleanup = this.enableDrag();
+    } else if (!this.options.draggable && this.dragCleanup) {
+      this.dragCleanup();
+      this.dragCleanup = null;
+      this.container.style.cursor = 'default';
+      this.container.style.pointerEvents = 'none';
     }
+  }
 
-    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;
-        }
+  updateLabels(intervals, precision = 2) {
+    if (intervals && intervals.length > 0) {
+      this.labelsElement.innerHTML = intervals
+        .map((val) => `<span>${this.options.dataFormat === 'scientific' ? Number(val).toExponential(precision) : Number(val).toFixed(precision)}</span>`)
+        .join('');
+    } else {
+      const mid = (this.options.min + this.options.max) / 2;
+      const format = this.options.dataFormat === 'scientific' ? (val) => val.toExponential(precision) : (val) => val.toFixed(precision);
+      this.labelsElement.innerHTML = `
+        <span>${format(this.options.max)}</span>
+        <span>${format(mid)}</span>
+        <span>${format(this.options.min)}</span>
+      `;
     }
+  }
+
+  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`;
+      this.options.X = newX / (containerRect.width - this.options.width);
+      this.options.Y = newY / (containerRect.height - this.options.height);
+    };
+
+    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;
+    }
+  }
 }