CloudMapDialog.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. <template>
  2. <SubDialog
  3. :modelValue="modelValue"
  4. @update:modelValue="$emit('update:modelValue', $event)"
  5. title="云图"
  6. width="500"
  7. height="600px"
  8. contentHeight="450px"
  9. @confirm="handleConfirm"
  10. @cancel="handleCancel"
  11. >
  12. <div>
  13. <el-collapse v-model="activeNames">
  14. <el-collapse-item name="1">
  15. <template #title>
  16. <span class="collapse-title">标量</span>
  17. </template>
  18. <el-form label-position="left">
  19. <el-form-item label="名称:" :label-width="formLabelWidth1">
  20. <el-input v-model="cloudConfig.scalarname" disabled></el-input>
  21. </el-form-item>
  22. <el-form-item label="类型:" :label-width="formLabelWidth1">
  23. <el-input v-model="cloudConfig.type" disabled></el-input>
  24. </el-form-item>
  25. <el-form-item label="标量名:" :label-width="formLabelWidth1">
  26. <el-select
  27. v-model="cloudConfig.scalarname"
  28. @change="handleScalarChange"
  29. >
  30. <el-option
  31. v-for="item in availableScalars"
  32. :key="item.value"
  33. :label="item.label"
  34. :value="item.value"
  35. />
  36. </el-select>
  37. </el-form-item>
  38. <el-form-item label="" :label-width="formLabelWidth1">
  39. <el-row>
  40. <el-col :span="12">
  41. <el-checkbox
  42. label="极值"
  43. v-model="cloudConfig.jzcheck"
  44. ></el-checkbox>
  45. </el-col>
  46. <el-col :span="12">
  47. <el-checkbox
  48. label="单元值离散到点"
  49. v-model="cloudConfig.dycheck"
  50. ></el-checkbox>
  51. </el-col>
  52. </el-row>
  53. </el-form-item>
  54. </el-form>
  55. </el-collapse-item>
  56. <el-collapse-item name="2">
  57. <template #title>
  58. <span class="collapse-title">云图间隔</span>
  59. </template>
  60. <el-form label-position="left">
  61. <el-form-item label="名称:" :label-width="formLabelWidth1">
  62. <el-input-number
  63. v-model="cloudConfig.intervalCount"
  64. :min="2"
  65. :max="20"
  66. @change="updateColorBarIntervals"
  67. />
  68. </el-form-item>
  69. <el-form-item label=" " :label-width="formLabelWidth1">
  70. <el-row>
  71. <el-col :span="24">
  72. <el-checkbox
  73. label="平滑云图"
  74. v-model="cloudConfig.smooth"
  75. ></el-checkbox>
  76. </el-col>
  77. </el-row>
  78. </el-form-item>
  79. </el-form>
  80. </el-collapse-item>
  81. <el-collapse-item name="3">
  82. <template #title>
  83. <span class="collapse-title">数据范围</span>
  84. </template>
  85. <el-form label-position="left">
  86. <el-form-item label="数据范围类型:" :label-width="formLabelWidth1">
  87. <el-select v-model="cloudConfig.dataAreaType">
  88. <el-option
  89. v-for="item in dataAreaTypeoptions"
  90. :key="item.value"
  91. :label="item.label"
  92. :value="item.value"
  93. />
  94. </el-select>
  95. </el-form-item>
  96. <el-form-item label="最大值:" :label-width="formLabelWidth1">
  97. <el-input v-model="cloudConfig.max"></el-input>
  98. </el-form-item>
  99. <el-form-item label="最小值:" :label-width="formLabelWidth1">
  100. <el-input v-model="cloudConfig.min"></el-input>
  101. </el-form-item>
  102. </el-form>
  103. </el-collapse-item>
  104. <el-collapse-item name="4">
  105. <template #title>
  106. <span class="collapse-title">色卡颜色范围</span>
  107. </template>
  108. <el-form label-position="left">
  109. <el-form-item label="最大值:" :label-width="formLabelWidth1">
  110. <el-row style="width: 100%">
  111. <el-col :span="22">
  112. <el-input v-model="cloudConfig.maxcv"></el-input>
  113. </el-col>
  114. <el-col :span="2"
  115. ><el-color-picker v-model="color1" @change="updateMaxValue" />
  116. </el-col>
  117. </el-row>
  118. </el-form-item>
  119. <el-form-item label="最小值:" :label-width="formLabelWidth1">
  120. <el-row style="width: 100%">
  121. <el-col :span="22">
  122. <el-input v-model="cloudConfig.mincv"></el-input
  123. ></el-col>
  124. <el-col :span="2"
  125. ><el-color-picker v-model="color2" @change="updateMinValue"
  126. /></el-col>
  127. </el-row>
  128. </el-form-item>
  129. </el-form>
  130. </el-collapse-item>
  131. </el-collapse>
  132. </div>
  133. </SubDialog>
  134. </template>
  135. <script setup>
  136. import { ref, computed, watch, onMounted } from "vue"
  137. import SubDialog from "./SubDialog.vue"
  138. import * as THREE from "three"
  139. import { debounce } from "lodash-es"
  140. const props = defineProps({
  141. modelValue: Boolean,
  142. initialData: {
  143. type: Object,
  144. default: () => ({})
  145. },
  146. threeSceneRef: Object,
  147. pltData: Object
  148. })
  149. const emit = defineEmits(["update:modelValue", "confirm", "cancel"])
  150. // 表单标签宽度
  151. const formLabelWidth1 = "120px"
  152. // 折叠面板当前激活项
  153. const activeNames = ref(["1", "2", "3", "4"])
  154. // 颜色选择器
  155. const color1 = ref("#ff0000")
  156. const color2 = ref("#0000ff")
  157. // 可用标量选项
  158. const availableScalars = ref([
  159. { label: "CoefPressure", value: "CoefPressure" },
  160. { label: "Mach", value: "Mach" },
  161. { label: "VelocityX", value: "VelocityX" },
  162. { label: "VelocityY", value: "VelocityY" },
  163. { label: "VelocityZ", value: "VelocityZ" }
  164. ])
  165. // 数据范围类型选项
  166. const dataAreaTypeoptions = ref([
  167. { label: "当前时间步", value: "当前时间步" },
  168. { label: "所有时间步", value: "所有时间步" },
  169. { label: "固定值", value: "固定值" }
  170. ])
  171. // 表单数据
  172. const cloudConfig = ref({
  173. name: "",
  174. type: "point scalar",
  175. scalarname: "CoefPressure",
  176. jzcheck: false,
  177. dycheck: false,
  178. intervalCount: 3, // 默认3等分
  179. smooth: true,
  180. dataAreaType: "当前时间步",
  181. max: "0.00",
  182. min: "0.00",
  183. maxcv: "(1.00, 0.00, 0.00)",
  184. mincv: "(0.00, 0.00, 1.00)"
  185. })
  186. // 计算当前可用的标量字段
  187. const availableScalarFields = computed(() => {
  188. if (!props.pltData?.zones?.length) return []
  189. // 从第一个zone中获取所有可用的变量
  190. const firstZone = props.pltData.zones[0]
  191. return Object.keys(firstZone.variables || {})
  192. })
  193. // 过滤可用的标量选项
  194. const filteredScalarOptions = computed(() => {
  195. return availableScalars.value.filter((item) =>
  196. availableScalarFields.value.includes(item.value)
  197. )
  198. })
  199. // 初始化时设置默认标量
  200. onMounted(() => {
  201. if (filteredScalarOptions.value.length > 0) {
  202. cloudConfig.value.scalarname = filteredScalarOptions.value[0].value
  203. updateDataRange()
  204. }
  205. })
  206. // 处理标量变化
  207. const handleScalarChange = (selectedScalar) => {
  208. updateDataRange()
  209. updateCloudMap()
  210. }
  211. // 更新数据范围
  212. const updateDataRange = () => {
  213. if (!props.pltData?.zones || !cloudConfig.value.scalarname) return
  214. let min = Infinity
  215. let max = -Infinity
  216. // 遍历所有zone找到最大最小值
  217. props.pltData.zones.forEach((zone) => {
  218. const scalarData = zone.variables?.[cloudConfig.value.scalarname]
  219. if (!scalarData) return
  220. const zoneMin = Math.min(...scalarData)
  221. const zoneMax = Math.max(...scalarData)
  222. min = Math.min(min, zoneMin)
  223. max = Math.max(max, zoneMax)
  224. })
  225. // 更新表单中的范围值
  226. cloudConfig.value.min = min.toFixed(4)
  227. cloudConfig.value.max = max.toFixed(4)
  228. }
  229. // 更新云图显示
  230. const updateCloudMap = debounce(() => {
  231. if (!props.threeSceneRef || !cloudConfig.value.scalarname) return
  232. // 先更新平滑设置
  233. props.threeSceneRef.setSmoothShading(cloudConfig.value.smooth)
  234. // 再更新颜色映射
  235. props.threeSceneRef.updateColorMapping(cloudConfig.value.scalarname, {
  236. colorMap: "rainbow",
  237. min: parseFloat(cloudConfig.value.min),
  238. max: parseFloat(cloudConfig.value.max),
  239. showExtremes: cloudConfig.value.jzcheck,
  240. smooth: cloudConfig.value.smooth // 传递平滑参数
  241. })
  242. }, 300)
  243. // 更新最大值颜色
  244. const updateMaxValue = () => {
  245. const rgb = hexToRgb(color1.value)
  246. cloudConfig.value.maxcv = `(${rgb.r}, ${rgb.g}, ${rgb.b})`
  247. updateColorBarIntervals()
  248. }
  249. // 更新最小值颜色
  250. const updateMinValue = () => {
  251. const rgb = hexToRgb(color2.value)
  252. cloudConfig.value.mincv = `(${rgb.r}, ${rgb.g}, ${rgb.b})`
  253. updateColorBarIntervals()
  254. }
  255. // 更新颜色条间隔
  256. const updateColorBarIntervals = () => {
  257. if (!props.threeSceneRef) return;
  258. props.threeSceneRef.updateColorBar({
  259. intervals: calculateIntervals(),
  260. colorRange: {
  261. min: parseFloat(cloudConfig.value.min),
  262. max: parseFloat(cloudConfig.value.max)
  263. },
  264. variable: cloudConfig.value.scalarname,
  265. title: getScalarLabel(cloudConfig.value.scalarname),
  266. gradientStart: parseColorString(cloudConfig.value.mincv), // 渐变起点颜色
  267. gradientEnd: parseColorString(cloudConfig.value.maxcv) // 渐变终点颜色
  268. });
  269. };
  270. // 添加辅助函数获取变量标签
  271. const getScalarLabel = (value) => {
  272. return (
  273. filteredScalarOptions.value.find((item) => item.value === value)?.label ||
  274. value
  275. )
  276. }
  277. // 解析颜色字符串 "(r, g, b)" 为数组 [r, g, b]
  278. const parseColorString = (str) => {
  279. const matches = str.match(/\(([^)]+)\)/);
  280. if (!matches) return [0, 0, 1];
  281. return matches[1].split(',').map(v => parseFloat(v.trim()));
  282. };
  283. // 计算等分点
  284. const calculateIntervals = () => {
  285. const count = cloudConfig.value.intervalCount
  286. const min = parseFloat(cloudConfig.value.min)
  287. const max = parseFloat(cloudConfig.value.max)
  288. const intervals = []
  289. const step = (max - min) / (count - 1)
  290. for (let i = 0; i < count; i++) {
  291. intervals.push((min + i * step).toFixed(2))
  292. }
  293. return intervals
  294. }
  295. // 16进制颜色转RGB对象
  296. const hexToRgb = (hex) => {
  297. const r = parseInt(hex.slice(1, 3), 16) / 255
  298. const g = parseInt(hex.slice(3, 5), 16) / 255
  299. const b = parseInt(hex.slice(5, 7), 16) / 255
  300. return { r, g, b }
  301. }
  302. // 确认操作
  303. const handleConfirm = () => {
  304. // 先更新颜色条
  305. updateColorBarIntervals()
  306. // 再提交配置
  307. emit("confirm", {
  308. ...cloudConfig.value,
  309. gradientStart: parseColorString(cloudConfig.value.mincv),
  310. gradientEnd: parseColorString(cloudConfig.value.maxcv)
  311. });
  312. emit("update:modelValue", false)
  313. }
  314. // 取消操作
  315. const handleCancel = () => {
  316. emit("cancel")
  317. emit("update:modelValue", false)
  318. }
  319. // 监听pltData变化
  320. watch(
  321. () => props.pltData,
  322. (newData) => {
  323. if (newData) {
  324. updateDataRange()
  325. updateCloudMap()
  326. }
  327. },
  328. { deep: true }
  329. )
  330. </script>
  331. <style scoped>
  332. /* 原有样式保持不变 */
  333. .collapse-title {
  334. font-weight: bold;
  335. font-size: 14px;
  336. }
  337. .el-collapse-item__content {
  338. padding-bottom: 10px;
  339. }
  340. .el-form-item {
  341. margin-bottom: 16px;
  342. }
  343. </style>
  344. <style>
  345. .extreme-label {
  346. transform: translate(-50%, 0);
  347. white-space: nowrap;
  348. pointer-events: none;
  349. }
  350. </style>