Преглед на файлове

feat: 自主管理3D图标

TwoKe945 преди 1 година
родител
ревизия
9ef68575f6
променени са 4 файла, в които са добавени 431 реда и са изтрити 2 реда
  1. 1 0
      app/package.json
  2. BIN
      app/src/assets/1.png
  3. 430 1
      app/src/components/PieChart3D.vue
  4. 0 1
      app/src/views/HomeView.vue

+ 1 - 0
app/package.json

@@ -11,6 +11,7 @@
   },
   "dependencies": {
     "@zhgkpt/components": "workspace:^1.0.0",
+    "echarts-gl": "^2.0.9",
     "less": "^4.1.3",
     "lib-flexible": "^0.3.2",
     "pinia": "^2.0.16",

BIN
app/src/assets/1.png


+ 430 - 1
app/src/components/PieChart3D.vue

@@ -1,10 +1,439 @@
 <script setup>
+import { onMounted, ref } from 'vue'
+import * as echarts from 'echarts'
+import 'echarts-gl'
+
+let chart, option;
+const pieChart3d = ref()
+onMounted(() => {
+  console.log(pieChart3d)
+  chart = echarts.init(pieChart3d.value);
+  // 传入数据生成 option
+  option = getPie3D(
+    [
+      {
+        name: "与管理单位(物业)",
+        value: 100,
+      },
+      {
+        name: "与管理单位(非物业)",
+        value: 12,
+      },
+      {
+        name: "居民自主管理",
+        value: 46,
+      },
+      {
+        name: "街道社区代表",
+        value: 95,
+      },
+      {
+        name: "其他",
+        value: 80,
+      },
+      {
+        name: "无管理主体",
+        value: 62,
+      }
+    ],
+    0.75
+  );
+
+  // 监听鼠标事件,实现饼图选中效果(单选),近似实现高亮(放大)效果。
+  let hoveredIndex = "";
+  // 准备重新渲染扇形所需的参数
+  let isSelected;
+  let isHovered;
+  let startRatio;
+  let endRatio;
+  let k;
+
+  // 监听 mouseover,近似实现高亮(放大)效果
+  chart.on("mouseover", function (params) {
+
+    // 如果触发 mouseover 的扇形当前已高亮,则不做操作
+    if (hoveredIndex === params.seriesIndex) {
+      return;
+
+      // 否则进行高亮及必要的取消高亮操作
+    } else {
+      // 如果当前有高亮的扇形,取消其高亮状态(对 option 更新)
+      if (hoveredIndex !== "") {
+        // 从 option.series 中读取重新渲染扇形所需的参数,将是否高亮设置为 false。
+        isSelected = option.series[hoveredIndex].pieStatus.selected;
+        isHovered = false;
+        startRatio = option.series[hoveredIndex].pieData.startRatio;
+        endRatio = option.series[hoveredIndex].pieData.endRatio;
+        k = option.series[hoveredIndex].pieStatus.k;
+
+        // 对当前点击的扇形,执行取消高亮操作(对 option 更新)
+        option.series[hoveredIndex].parametricEquation =
+          getParametricEquation(
+            startRatio,
+            endRatio,
+            isSelected,
+            isHovered,
+            k,
+            option.series[hoveredIndex].pieData.value
+          );
+        option.series[hoveredIndex].pieStatus.hovered = isHovered;
+
+        // 将此前记录的上次选中的扇形对应的系列号 seriesIndex 清空
+        hoveredIndex = "";
+      }
+
+      // 如果触发 mouseover 的扇形不是透明圆环,将其高亮(对 option 更新)
+      if (params.seriesName !== "mouseoutSeries") {
+        // 从 option.series 中读取重新渲染扇形所需的参数,将是否高亮设置为 true。
+        isSelected = option.series[params.seriesIndex].pieStatus.selected;
+        isHovered = true;
+        startRatio = option.series[params.seriesIndex].pieData.startRatio;
+        endRatio = option.series[params.seriesIndex].pieData.endRatio;
+        k = option.series[params.seriesIndex].pieStatus.k;
+
+        // 对当前点击的扇形,执行高亮操作(对 option 更新)
+        option.series[params.seriesIndex].parametricEquation =
+          getParametricEquation(
+            startRatio,
+            endRatio,
+            isSelected,
+            isHovered,
+            k,
+            option.series[params.seriesIndex].pieData.value + 5
+          );
+        option.series[params.seriesIndex].pieStatus.hovered = isHovered;
+
+        // 记录上次高亮的扇形对应的系列号 seriesIndex
+        hoveredIndex = params.seriesIndex;
+      }
+
+      // 使用更新后的 option,渲染图表
+      chart.setOption(option);
+    }
+  });
+
+  // 修正取消高亮失败的 bug
+  chart.on("globalout", function () {
+    if (hoveredIndex !== "") {
+      // 从 option.series 中读取重新渲染扇形所需的参数,将是否高亮设置为 true。
+      isSelected = option.series[hoveredIndex].pieStatus.selected;
+      isHovered = false;
+      k = option.series[hoveredIndex].pieStatus.k;
+      startRatio = option.series[hoveredIndex].pieData.startRatio;
+      endRatio = option.series[hoveredIndex].pieData.endRatio;
+
+      // 对当前点击的扇形,执行取消高亮操作(对 option 更新)
+      option.series[hoveredIndex].parametricEquation = getParametricEquation(
+        startRatio,
+        endRatio,
+        isSelected,
+        isHovered,
+        k,
+        option.series[hoveredIndex].pieData.value
+      );
+      option.series[hoveredIndex].pieStatus.hovered = isHovered;
+
+      // 将此前记录的上次选中的扇形对应的系列号 seriesIndex 清空
+      hoveredIndex = "";
+    }
+
+    // 使用更新后的 option,渲染图表
+    chart.setOption(option);
+  });
+
+  if (option && typeof option === "object") {
+    chart.setOption(option);
+  }
+
+})
+
+// 生成扇形的曲面参数方程
+function getParametricEquation(
+  startRatio,
+  endRatio,
+  isSelected,
+  isHovered,
+  k,
+  h
+) {
+  // 计算
+  const midRatio = (startRatio + endRatio) / 2;
+  const startRadian = startRatio * Math.PI * 2;
+  const endRadian = endRatio * Math.PI * 2;
+  const midRadian = midRatio * Math.PI * 2;
+
+  // 如果只有一个扇形,则不实现选中效果。
+  if (startRatio === 0 && endRatio === 1) {
+    // eslint-disable-next-line no-param-reassign
+    isSelected = false;
+  }
+
+  // 通过扇形内径/外径的值,换算出辅助参数 k(默认值 1/3)
+  // eslint-disable-next-line no-param-reassign
+  k = typeof k !== "undefined" ? k : 1 / 3;
+
+  // 计算选中效果分别在 x 轴、y 轴方向上的位移(未选中,则位移均为 0)
+  const offsetX = isSelected ? Math.cos(midRadian) * 0.1 : 0;
+  const offsetY = isSelected ? Math.sin(midRadian) * 0.1 : 0;
+
+  // 计算高亮效果的放大比例(未高亮,则比例为 1)
+  const hoverRate = isHovered ? 1.05 : 1;
+
+  // 返回曲面参数方程
+  return {
+    u: {
+      min: -Math.PI,
+      max: Math.PI * 3,
+      step: Math.PI / 32,
+    },
+
+    v: {
+      min: 0,
+      max: Math.PI * 2,
+      step: Math.PI / 20,
+    },
+
+    x(u, v) {
+      if (u < startRadian) {
+        return (
+          offsetX +
+          Math.cos(startRadian) * (1 + Math.cos(v) * k) * hoverRate
+        );
+      }
+      if (u > endRadian) {
+        return (
+          offsetX + Math.cos(endRadian) * (1 + Math.cos(v) * k) * hoverRate
+        );
+      }
+      return offsetX + Math.cos(u) * (1 + Math.cos(v) * k) * hoverRate;
+    },
+
+    y(u, v) {
+      if (u < startRadian) {
+        return (
+          offsetY +
+          Math.sin(startRadian) * (1 + Math.cos(v) * k) * hoverRate
+        );
+      }
+      if (u > endRadian) {
+        return (
+          offsetY + Math.sin(endRadian) * (1 + Math.cos(v) * k) * hoverRate
+        );
+      }
+      return offsetY + Math.sin(u) * (1 + Math.cos(v) * k) * hoverRate;
+    },
+
+    z(u, v) {
+      if (u < -Math.PI * 0.5) {
+        return Math.sin(u);
+      }
+      if (u > Math.PI * 2.5) {
+        return Math.sin(u) * h * 0.1;
+      }
+      // 当前图形的高度是Z根据h(每个value的值决定的)
+      return Math.sin(v) > 0 ? 1 * h * 0.1 : -1;
+    },
+  };
+}
+// 生成模拟 3D 饼图的配置项
+function getPie3D(pieData, internalDiameterRatio) {
+  const series = [];
+  // 总和
+  let sumValue = 0;
+  let startValue = 0;
+  let endValue = 0;
+  const legendData = [];
+  const k =
+    typeof internalDiameterRatio !== "undefined"
+      ? (1 - internalDiameterRatio) / (1 + internalDiameterRatio)
+      : 1 / 3;
+  // 为每一个饼图数据,生成一个 series-surface 配置
+  for (let i = 0; i < pieData.length; i += 1) {
+    sumValue += pieData[i].value;
+    console.log(pieData[i].name)
+    const seriesItem = {
+      name:
+        typeof pieData[i].name === "undefined"
+          ? `series${i}`
+          : pieData[i].name,
+      type: "surface",
+      parametric: true,
+      wireframe: {
+        show: false,
+      },
+      pieData: pieData[i],
+      pieStatus: {
+        selected: false,
+        hovered: false,
+        k,
+      },
+    };
+    if (typeof pieData[i].itemStyle !== "undefined") {
+      const { itemStyle } = pieData[i];
+      typeof pieData[i].itemStyle.color !== "undefined"
+        ? (itemStyle.color = pieData[i].itemStyle.color)
+        : null;
+      typeof pieData[i].itemStyle.opacity !== "undefined"
+        ? (itemStyle.opacity = pieData[i].itemStyle.opacity)
+        : null;
+      seriesItem.itemStyle = itemStyle;
+    }
+    series.push(seriesItem);
+  }
+  // 使用上一次遍历时,计算出的数据和 sumValue,调用 getParametricEquation 函数,
+  // 向每个 series-surface 传入不同的参数方程 series-surface.parametricEquation,也就是实现每一个扇形。
+  for (let i = 0; i < series.length; i += 1) {
+    endValue = startValue + series[i].pieData.value;
+    series[i].pieData.startRatio = startValue / sumValue;
+    series[i].pieData.endRatio = endValue / sumValue;
+    series[i].parametricEquation = getParametricEquation(
+      series[i].pieData.startRatio,
+      series[i].pieData.endRatio,
+      false,
+      false,
+      k,
+      // ,使除了第一个之外的值都是10
+      series[i].pieData.value === series[0].pieData.value ? 35 : 10
+    );
+    startValue = endValue;
+    legendData.push(series[i].name);
+  }
+  // 准备待返回的配置项,把准备好的 legendData、series 传入。
+  const option = {
+    color: [
+      "#1162fe",
+      "#3fc865",
+      "#a8fce3",
+      "#5ef8c9",
+      "#5ed3f8",
+      "#5e92f8"
+    ],
+    legend: {
+      // type: "scroll",
+      data: legendData,
+      icon: "roundRect",
+      orient: 'vertical',
+      padding: 5,
+      itemGap: 20,
+      top: 0,
+      right: 0,
+      itemWidth: 10, // 设置宽度
+      itemHeight: 10, // 设置高度
+      selectedMode: true,
+      textStyle: {
+        color: "#FFFFFF",
+        fontSize: 14,
+        lineHeight: 14,
+        rich: {
+          a: {
+            // verticalAlign: "middle",
+          },
+        },
+        padding: [0, 0, -3, 0],
+      },
+    },
+    tooltip: {
+      formatter: (params) => {
+        if (params.seriesName !== "mouseoutSeries") {
+          return `${params.seriesName
+            }<br/><span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:${params.color
+            };"></span>${option.series[params.seriesIndex].pieData.value}`;
+        }
+        return "";
+      },
+    },
+    xAxis3D: {
+      min: -1,
+      max: 1,
+    },
+    yAxis3D: {
+      min: -1,
+      max: 1,
+    },
+    zAxis3D: {
+      min: -1,
+      max: "dataMax",
+    },
+    grid3D: {
+      show: false,
+      boxHeight: 16,
+      top: "-17%",
+      left: "-16%",
+      viewControl: {
+        // 3d效果可以放大、旋转等,请自己去查看官方配置
+        alpha: 27,
+        beta: 70, //旋转角度
+        rotateSensitivity: 1,
+        zoomSensitivity: 0,
+        panSensitivity: 0,
+        // autoRotate: true,
+        distance: 150,
+      },
+      // 后处理特效可以为画面添加高光、景深、环境光遮蔽(SSAO)、调色等效果。可以让整个画面更富有质感。
+      postEffect: {
+        // 配置这项会出现锯齿,请自己去查看官方配置有办法解决
+        enable: false,
+        bloom: {
+          enable: true,
+          bloomIntensity: 0.1,
+        },
+        SSAO: {
+          enable: true,
+          quality: "medium",
+          radius: 2,
+        },
+      },
+    },
+    series,
+  };
+  return option;
+}
 
 </script>
 
 <template >
-  <div ref=""></div>
+  <div class="container">
+    <div ref="pieChart3d" class="chart"></div>
+  </div>
 </template>
 
 <style scoped lang='less'>
+
+.container {
+    width: 440px;
+    height: 264px;
+    position: relative;
+  }
+
+  .container::after {
+    content: "";
+    position: absolute;
+    width: 290px;
+    height: 198px;
+    background: url("../assets/1.png") no-repeat;
+    background-size: 100% 100%;
+    left: calc(50% - 145px);
+    bottom: 1%;
+    left: 0;
+  }
+
+  .container::before {
+    content: attr(data-num);
+    position: absolute;
+    font-size: 18px;
+    font-family: YouSheBiaoTiHei;
+    color: #ffffff;
+    line-height: 42px;
+    text-shadow: 0px 2px 6px rgba(0, 0, 0, 0.5);
+    left: 50%;
+    transform: translate(-50%);
+    top: 30%;
+  }
+
+  .chart {
+    width: 440px;
+    height: 100%;
+    position: relative;
+    z-index: 1;
+  }
 </style>

+ 0 - 1
app/src/views/HomeView.vue

@@ -70,7 +70,6 @@
   </div>
 </template>
 <script>
-import * as echarts from 'echarts'
 import Headers from "@/components/Headers.vue";
 import FirstHight from "./components/FirstHight.vue";
 import AlarmingSituationDynamics from './components/AlarmingSituationDynamics.vue'