Browse Source

增加饼图例子

caner 11 months ago
parent
commit
0a0b26ddcd
9 changed files with 526 additions and 12 deletions
  1. 3 1
      README.md
  2. 1 0
      package.json
  3. BIN
      public/imgs/14.png
  4. 2 1
      src/App.vue
  5. 239 0
      src/pages/3DPie/Pies.vue
  6. 235 0
      src/pages/3DPie/Ring.vue
  7. 26 0
      src/pages/3DPie/index.vue
  8. 9 0
      src/pages/3DPie/route.ts
  9. 11 10
      src/pages/leaflet/index.vue

+ 3 - 1
README.md

@@ -26,4 +26,6 @@
 13. '/svgAnimate/assets' 静态svg动画同步-Demo
 13. '/svgAnimate/assets' 静态svg动画同步-Demo
 ![image](./public/imgs/13.png)
 ![image](./public/imgs/13.png)
 14. '/svgAnimate/scale' 动态缩放svg动画同步-Demo
 14. '/svgAnimate/scale' 动态缩放svg动画同步-Demo
-![image](./public/imgs/13.png)
+![image](./public/imgs/13.png)
+15. '/3DChart' 3D-Echart 饼图
+![image](./public/imgs/14.png)

+ 1 - 0
package.json

@@ -13,6 +13,7 @@
     "@wangeditor/editor-for-vue": "^5.1.12",
     "@wangeditor/editor-for-vue": "^5.1.12",
     "date-fns": "^4.1.0",
     "date-fns": "^4.1.0",
     "echarts": "^5.5.1",
     "echarts": "^5.5.1",
+    "echarts-gl": "^2.0.9",
     "ezuikit-js": "8.0.2",
     "ezuikit-js": "8.0.2",
     "html2canvas": "^1.4.1",
     "html2canvas": "^1.4.1",
     "jspdf": "^2.5.1",
     "jspdf": "^2.5.1",

BIN
public/imgs/14.png


+ 2 - 1
src/App.vue

@@ -39,7 +39,8 @@ console.log(
 11. '/video' 荧石视频SDK-Demo
 11. '/video' 荧石视频SDK-Demo
 12. '/geo' CAD瓦片反差图-Demo
 12. '/geo' CAD瓦片反差图-Demo
 13. '/svgAnimate/assets' 静态svg动画同步-Demo
 13. '/svgAnimate/assets' 静态svg动画同步-Demo
-14. '/svgAnimate/scale'  动态svg动画同步-Demo`
+14. '/svgAnimate/scale'  动态svg动画同步-Demo
+15. '/3DChart'  3Dechart饼图`
 )
 )
 </script>
 </script>
 <style>
 <style>

+ 239 - 0
src/pages/3DPie/Pies.vue

@@ -0,0 +1,239 @@
+<template>
+  <div
+    id="echart2"
+    class="chart"
+  />
+</template>
+
+<script setup lang='ts'>
+import { ref, onMounted, nextTick } from 'vue'
+import * as echarts from 'echarts'
+import 'echarts-gl'
+import { EChartsType } from 'echarts'
+
+const props = withDefaults(defineProps<{
+    option?: any[]
+}>(), {
+  option: () => [
+
+    {
+      name: '',
+      value: 160,
+      itemStyle: {
+        color: 'rgba(100, 170, 220, 1)'
+      }
+    },
+    {
+      name: '',
+      value: 200,
+      itemStyle: {
+        color: 'rgba(190, 190, 230,1)'
+      }
+    },
+    {
+      name: '',
+      value: 150,
+      itemStyle: {
+        color: 'rgba(167, 240, 190, 1)'
+      }
+    },
+
+    {
+      name: '',
+      value: 100,
+      itemStyle: {
+        color: 'rgba(110, 215, 200, 1)'
+      }
+    },
+    {
+      name: '',
+      value: 140,
+      itemStyle: {
+        color: 'rgba(220, 220,175, 1)'
+      }
+    }
+
+  ]
+
+})
+
+const chart = ref(null as unknown as EChartsType)
+// 计算曲面
+function getParametricEquation(startRatio: number, endRatio: number, isSelected: boolean, isHovered: boolean, k: number, height: number) {
+  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) {
+    isSelected = false
+  }
+
+  // 通过扇形内径/外径的值,换算出辅助参数 k(默认值 1/3)
+  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: number, v: number) {
+      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: number, v: number) {
+      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: number, v: number) {
+      if (u < -Math.PI * 0.5) {
+        return Math.sin(u)
+      }
+      if (u > Math.PI * 2.5) {
+        return Math.sin(u) * height * 0.1
+      }
+      // 当前图形的高度是Z根据h(每个value的值决定的)
+      return Math.sin(v) > 0 ? 1 * height * 0.1 : -1
+    }
+  }
+}
+
+// 生成模拟 3D 饼图的配置项
+function getPie3D(pieData: string | any[], internalDiameterRatio: number) {
+  const series = [] as any[]
+  let sumValue = 0
+  let startValue = 0
+  let endValue = 0
+  const legendData = [] as any[]
+  const k = typeof internalDiameterRatio !== 'undefined' ? (1 - internalDiameterRatio) / (1 + internalDiameterRatio) : 1 / 3
+
+  // 为每一个饼图数据,生成一个 series-surface 配置
+  for (let i = 0; i < pieData.length; i++) {
+    const el = pieData[i]
+    sumValue += el.value
+    series.push({
+      name: el.name || i,
+      type: 'surface',
+      parametric: true,
+      wireframe: {
+        show: false
+      },
+      pieData: el,
+      pieStatus: {
+        selected: false,
+        hovered: false,
+        k
+      },
+      itemStyle: {
+        color: el.itemStyle?.color || undefined,
+        opacity: el.itemStyle?.opacity || undefined
+      }
+    })
+  }
+
+  // 使用上一次遍历时,计算出的数据和 sumValue,调用 getParametricEquation 函数,
+  // 向每个 series-surface 传入不同的参数方程 series-surface.parametricEquation,也就是实现每一个扇形。
+  for (let i = 0; i < series.length; i++) {
+    const el = series[i]
+    endValue = startValue + el.pieData.value
+    el.pieData.startRatio = startValue / sumValue
+    el.pieData.endRatio = endValue / sumValue
+    el.parametricEquation = getParametricEquation(el.pieData.startRatio, el.pieData.endRatio, false, false, k, 10)
+    startValue = endValue
+    legendData.push(el.name)
+  }
+  return series
+}
+
+// 初始化
+const echartInit = () => {
+  const dom = document.getElementById('echart2')
+  chart.value = echarts.init(dom)
+  const series = getPie3D(props.option, 0)// 第二个参数控制圆环粗细
+  chart.value.setOption({
+    legend: {
+      show: false
+    },
+    animation: true,
+    tooltip: {
+      // 划过显示的内容
+      formatter: (params: { seriesName: string; color: any; seriesIndex: string | number; }) => {
+        if (params.seriesName !== 'mouseoutSeries' && params.seriesName !== 'pie2d') {
+          return `${params.seriesName}<br/><span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:${params.color};"></span>${`${series[params.seriesIndex].pieData.value}件`}`
+        }
+      },
+      textStyle: {
+        fontSize: 14
+      }
+    },
+    xAxis3D: {
+      min: -1,
+      max: 1
+    },
+    yAxis3D: {
+      min: -1,
+      max: 1
+    },
+    zAxis3D: {
+      min: -1,
+      max: 1
+    },
+    grid3D: {
+      show: false,
+      boxHeight: 25, // 修改立体饼图的高度
+      top: '-10%',
+      left: '-10%',
+      viewControl: {
+        // 3d效果可以放大、旋转等,
+        alpha: 35, // 饼图翻转的程度
+        beta: 30,
+        rotateSensitivity: 1,
+        zoomSensitivity: 1,
+        panSensitivity: 1,
+        autoRotate: false, // 是否自动旋转
+        distance: 350// 距离越小看到的饼图越大
+      }
+    },
+    series
+  })
+}
+
+onMounted(() => nextTick(() => echartInit()))
+
+</script>
+
+<style lang="scss" scoped>
+.chart {
+    width: 100%;
+    height: 100%;
+}
+</style>

+ 235 - 0
src/pages/3DPie/Ring.vue

@@ -0,0 +1,235 @@
+<template>
+  <div
+    id="echart1"
+    class="chart"
+  />
+</template>
+
+<script setup lang='ts'>
+import { ref, onMounted, nextTick } from 'vue'
+import * as echarts from 'echarts'
+import 'echarts-gl'
+import { EChartsType } from 'echarts'
+
+const props = withDefaults(defineProps<{
+  option?: any[]
+}>(), {
+  option: () => [
+
+    {
+      name: '',
+      value: 160,
+      itemStyle: {
+        color: 'rgba(100, 170, 220, 1)'
+      }
+    },
+    {
+      name: '',
+      value: 200,
+      itemStyle: {
+        color: 'rgba(190, 190, 230,1)'
+      }
+    },
+    {
+      name: '',
+      value: 150,
+      itemStyle: {
+        color: 'rgba(167, 240, 190, 1)'
+      }
+    },
+
+    {
+      name: '',
+      value: 100,
+      itemStyle: {
+        color: 'rgba(110, 215, 200, 1)'
+      }
+    },
+    {
+      name: '',
+      value: 140,
+      itemStyle: {
+        color: 'rgba(220, 220,175, 1)'
+      }
+    }
+
+  ]
+})
+const chart = ref(null as unknown as EChartsType)
+
+// 计算曲面
+function getParametricEquation(startRatio: number, endRatio: number, isSelected: boolean, isHovered: boolean, k: number, height: number) {
+  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) {
+    isSelected = false
+  }
+
+  // 通过扇形内径/外径的值,换算出辅助参数 k(默认值 1/3)
+  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: number, v: number) {
+      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: number, v: number) {
+      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: number, v: number) {
+      if (u < -Math.PI * 0.5) {
+        return Math.sin(u)
+      }
+      if (u > Math.PI * 2.5) {
+        return Math.sin(u)
+      }
+      return Math.sin(v) > 0 ? 1 * height : -1
+    }
+  }
+}
+
+// 生成模拟 3D 饼图的配置项
+function getPie3D(pieData: string | any[], internalDiameterRatio: number) {
+  const series = [] as any[]
+  let sumValue = 0
+  let startValue = 0
+  let endValue = 0
+  const legendData = [] as any[]
+  const k = typeof internalDiameterRatio !== 'undefined' ? (1 - internalDiameterRatio) / (1 + internalDiameterRatio) : 1 / 3
+
+  // 为每一个饼图数据,生成一个 series-surface 配置
+  for (let i = 0; i < pieData.length; i++) {
+    const el = pieData[i]
+    sumValue += el.value
+    series.push({
+      name: el.name || i,
+      type: 'surface',
+      parametric: true,
+      wireframe: {
+        show: false
+      },
+      pieData: el,
+      pieStatus: {
+        selected: false,
+        hovered: false,
+        k
+      },
+      itemStyle: {
+        color: el.itemStyle?.color || undefined,
+        opacity: el.itemStyle?.opacity || undefined
+      }
+    })
+  }
+
+  // 使用上一次遍历时,计算出的数据和 sumValue,调用 getParametricEquation 函数,
+  // 向每个 series-surface 传入不同的参数方程 series-surface.parametricEquation,也就是实现每一个扇形。
+  for (let i = 0; i < series.length; i++) {
+    const el = series[i]
+    endValue = startValue + el.pieData.value
+    el.pieData.startRatio = startValue / sumValue
+    el.pieData.endRatio = endValue / sumValue
+    el.parametricEquation = getParametricEquation(el.pieData.startRatio, el.pieData.endRatio, false, false, k, el.pieData.value)
+    startValue = endValue
+    legendData.push(el.name)
+  }
+  return series
+}
+
+// 初始化
+const echartInit = () => {
+  const dom = document.getElementById('echart1')
+  chart.value = echarts.init(dom)
+  const series = getPie3D(props.option, 0.85)// 第二个参数控制圆环粗细
+  chart.value.setOption({
+    legend: {
+      show: false
+    },
+    animation: true,
+    tooltip: {
+      // 划过显示的内容
+      formatter: (params: { seriesName: string; color: any; seriesIndex: string | number; }) => {
+        if (params.seriesName !== 'mouseoutSeries' && params.seriesName !== 'pie2d') {
+          return `${params.seriesName}<br/><span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:${params.color};"></span>${`${series[params.seriesIndex].pieData.value}件`}`
+        }
+      },
+      textStyle: {
+        fontSize: 14
+      }
+    },
+    xAxis3D: {
+      min: -1,
+      max: 1
+    },
+    yAxis3D: {
+      min: -1,
+      max: 1
+    },
+    zAxis3D: {
+      min: -1,
+      max: 1
+    },
+    grid3D: {
+      show: false,
+      boxHeight: 0.2, // 环形图的高度,可自己调节
+      top: '-10%',
+      left: '-10%',
+      // environment: "rgba(255,255,255,0)",
+      viewControl: {
+        distance: 180, // 调整视角到主体的距离,类似调整zoom
+        alpha: 23, // 角度
+        beta: 30,
+        autoRotate: false, // 自动旋转
+        rotateSensitivity: 1, // 设置为0无法旋转
+        zoomSensitivity: 1, // 设置为0无法缩放
+        panSensitivity: 1 // 设置为0无法平移
+      }
+    },
+    series
+  })
+}
+
+onMounted(() => nextTick(() => echartInit()))
+</script>
+<style lang="scss" scoped>
+.chart {
+  width: 100%;
+  height: 100%;
+}
+</style>

+ 26 - 0
src/pages/3DPie/index.vue

@@ -0,0 +1,26 @@
+<template>
+  <div class="box">
+    <Ring />
+    <Pie />
+  </div>
+</template>
+
+<script setup lang='ts'>
+import Ring from './Ring.vue'
+import Pie from './Pies.vue'
+
+</script>
+
+<style lang="scss" scoped>
+  .box{
+    width: 100vw;
+    height: 100vh;
+    display: flex;
+    flex-wrap: wrap;
+    background: black;
+    &>div {
+        width: 30%;
+        height: 300px;
+    }
+  }
+</style>

+ 9 - 0
src/pages/3DPie/route.ts

@@ -0,0 +1,9 @@
+import { RouteRecordRaw } from 'vue-router'
+
+export default {
+  path: '/3DChart',
+  meta: {
+    authorize: true
+  },
+  component: () => import('./index.vue')
+} as RouteRecordRaw

+ 11 - 10
src/pages/leaflet/index.vue

@@ -181,6 +181,7 @@ import {
   Ref, ref, watch
   Ref, ref, watch
 } from 'vue'
 } from 'vue'
 import '@/../node_modules/leaflet/dist/leaflet.css'
 import '@/../node_modules/leaflet/dist/leaflet.css'
+import { LatLngExpression } from 'leaflet';
 // ----- 修复L 增加tooltip 缩放报错 重要!!
 // ----- 修复L 增加tooltip 缩放报错 重要!!
 
 
 (L.Tooltip as any).prototype._animateZoom = function (e: { zoom: any; center: any; }) {
 (L.Tooltip as any).prototype._animateZoom = function (e: { zoom: any; center: any; }) {
@@ -449,16 +450,16 @@ function drawMarker(item: { latLng: number[], icon: string, children: any, label
   marker.addTo(Map.value)
   marker.addTo(Map.value)
   marker.addEventListener('click', (event) => {
   marker.addEventListener('click', (event) => {
     console.log('marker点击', event)
     console.log('marker点击', event)
-    const { options: { title } } = event.target
-    const path = userData.getUserData().permission.find((el: { url: string }) => el.url === '/objHome')
-    if (path) {
-      store.setCurrentTunnel({ ...title })
-      store.setCurrentUuid(null)
-      localStorage.setItem('currentTunnel', JSON.stringify({ ...title }))
-      router.push('/objHome')
-    } else {
-      window.$notification.warning({ title: '你没有权限,请联系管理员!', duration: 2000 })
-    }
+    // const { options: { title } } = event.target
+    // const path = userData.getUserData().permission.find((el: { url: string }) => el.url === '/objHome')
+    // if (path) {
+    //   // store.setCurrentTunnel({ ...title })
+    //   // store.setCurrentUuid(null)
+    //   localStorage.setItem('currentTunnel', JSON.stringify({ ...title }))
+    //   // router.push('/objHome')
+    // } else {
+    //   window.$notification.warning({ title: '你没有权限,请联系管理员!', duration: 2000 })
+    // }
   })
   })
   return marker
   return marker
 }
 }