浏览代码

fix: 优化donutchart

李志伟 2 周之前
父节点
当前提交
f574c06e46
共有 1 个文件被更改,包括 122 次插入81 次删除
  1. 122 81
      src/components/view_file/vendors/pptx/PPT.vue

+ 122 - 81
src/components/view_file/vendors/pptx/PPT.vue

@@ -2,7 +2,7 @@
  * @Author: LiZhiWei
  * @Date: 2025-04-10 14:38:27
  * @LastEditors: LiZhiWei
- * @LastEditTime: 2025-04-23 17:48:31
+ * @LastEditTime: 2025-04-27 08:40:47
  * @Description:
 -->
 <template>
@@ -4429,7 +4429,7 @@ export default {
           this.drawDonutChart(svg, element, chartWidth, chartHeight)
           break
         default:
-            // 占位符
+          // 占位符
           svg.innerHTML = `<text x="${element.width / 2}" y="${
             element.height / 2
           }" text-anchor="middle" fill="#999" font-size="12px">
@@ -4442,103 +4442,144 @@ export default {
       return el
     },
     drawDonutChart(svg, element, chartWidth, chartHeight) {
-      // 计算饼图中心点和半径
-      const cx = element.width / 2
-      const cy = element.height / 2
-      const radius = Math.min(chartWidth, chartHeight) / 2
-
-      // 计算所有数据总和
-      const total = element.data[0].values.reduce(
-        (sum, item) => sum + item.y,
-        0
-      )
-
-      // 创建颜色数组
-      const colors = element.colors || [
-        "#5B9BD5",
-        "#ED7D31",
-        "#A5A5A5",
-        "#FFC000",
+      // 数据验证
+      if (!element?.data?.[0]?.values) return
+
+      const series = element.data[0]
+      const values = series.values.map((v) => Number(v.y) || 0)
+      const total = values.reduce((sum, v) => sum + v, 0)
+      if (total === 0) return
+
+      // 基础配置
+      const cx = chartWidth / 2
+      const cy = chartHeight / 2
+      // 保持较大的外半径
+      const outerR = (Math.min(chartWidth, chartHeight) / 2) * 0.98
+
+      // 处理空心大小
+      const holeSize = element.holeSize ? parseInt(element.holeSize) : 50
+      const innerR = outerR * (holeSize / 100)
+
+      // 默认颜色和自定义颜色处理
+      const defaultColors = [
+        "#4e79a7",
+        "#f28e2b",
+        "#e15759",
+        "#76b7b2",
+        "#59a14f",
+        "#edc949",
+        "#af7aa1",
+        "#ff9da7",
       ]
+      const colors =
+        element.colors?.map(
+          (color, i) => color || defaultColors[i % defaultColors.length]
+        ) || defaultColors
+
+      // 标签数据
+      const labels = series.xlabels || {}
+
+      // 创建图表组
+      const chartGroup = document.createElementNS(
+        "http://www.w3.org/2000/svg",
+        "g"
+      )
+      chartGroup.setAttribute("transform", `translate(${cx},${cy})`)
 
-      let startAngle = 0
-      element.data[0].values.forEach((item, index) => {
-        // 计算扇形角度
-        const angle = (item.y / total) * Math.PI * 2
+      // 绘制扇形
+      let startAngle = -Math.PI / 2 // 从顶部开始绘制
+      values.forEach((value, index) => {
+        if (value <= 0) return
 
-        // 计算扇形路径
+        const percentage = value / total
+        const angle = percentage * Math.PI * 2
         const endAngle = startAngle + angle
-        const x1 = cx + radius * Math.cos(startAngle)
-        const y1 = cy + radius * Math.sin(startAngle)
-        const x2 = cx + radius * Math.cos(endAngle)
-        const y2 = cy + radius * Math.sin(endAngle)
 
-        // 创建扇形路径
+        // 计算路径点 (相对于组中心 0,0)
+        const x1 = outerR * Math.cos(startAngle)
+        const y1 = outerR * Math.sin(startAngle)
+        const x2 = outerR * Math.cos(endAngle)
+        const y2 = outerR * Math.sin(endAngle)
+        const x3 = innerR * Math.cos(endAngle)
+        const y3 = innerR * Math.sin(endAngle)
+        const x4 = innerR * Math.cos(startAngle)
+        const y4 = innerR * Math.sin(startAngle)
+
+        // 构建路径
         const path = document.createElementNS(
           "http://www.w3.org/2000/svg",
           "path"
         )
-        const largeArcFlag = angle > Math.PI ? 1 : 0
-
-        // 根据是否为环形图决定内圆半径
-        const innerRadius =
-          element.chartType === "doughnutChart" ? radius * 0.6 : 0
-        if (element.chartType === "doughnutChart") {
-          // 环形图路径
-          const ix1 = cx + innerRadius * Math.cos(startAngle)
-          const iy1 = cy + innerRadius * Math.sin(startAngle)
-          const ix2 = cx + innerRadius * Math.cos(endAngle)
-          const iy2 = cy + innerRadius * Math.sin(endAngle)
-
-          path.setAttribute(
-            "d",
-            `
-            M ${x1} ${y1}
-            A ${radius} ${radius} 0 ${largeArcFlag} 1 ${x2} ${y2}
-            L ${ix2} ${iy2}
-            A ${innerRadius} ${innerRadius} 0 ${largeArcFlag} 0 ${ix1} ${iy1}
-            Z
-          `
-          )
-        } else {
-          // 饼图路径
-          path.setAttribute(
-            "d",
-            `
-            M ${cx} ${cy}
-            L ${x1} ${y1}
-            A ${radius} ${radius} 0 ${largeArcFlag} 1 ${x2} ${y2}
-            Z
-          `
-          )
-        }
+        const largeArc = angle > Math.PI ? 1 : 0
+        const d = [
+          `M ${x1} ${y1}`,
+          `A ${outerR} ${outerR} 0 ${largeArc} 1 ${x2} ${y2}`,
+          `L ${x3} ${y3}`,
+          `A ${innerR} ${innerR} 0 ${largeArc} 0 ${x4} ${y4}`,
+          "Z",
+        ].join(" ")
+
+        path.setAttribute("d", d)
+        path.setAttribute("fill", colors[index % colors.length]) // 确保颜色循环使用
+
+        // 添加交互效果
+        path.style.transition = "transform 0.2s"
+        path.addEventListener("mouseover", () => {
+          path.style.transform = "scale(1.03)"
+        })
+        path.addEventListener("mouseout", () => {
+          path.style.transform = "scale(1)"
+        })
 
-        // 设置填充色
-        path.setAttribute("fill", colors[index % colors.length])
-        path.setAttribute("stroke", "#fff")
-        path.setAttribute("stroke-width", "1")
-        svg.appendChild(path)
+        chartGroup.appendChild(path)
 
-        // 添加数值标签
-        const labelAngle = startAngle + angle / 2
-        const labelRadius = radius * 1.1
-        const labelX = cx + labelRadius * Math.cos(labelAngle)
-        const labelY = cy + labelRadius * Math.sin(labelAngle)
+        // 添加标签
+        const midAngle = startAngle + angle / 2
+        const labelR = outerR * 1.1
+        const labelX = labelR * Math.cos(midAngle)
+        const labelY = labelR * Math.sin(midAngle)
 
-        const label = document.createElementNS(
+        const text = document.createElementNS(
           "http://www.w3.org/2000/svg",
           "text"
         )
-        label.setAttribute("x", labelX)
-        label.setAttribute("y", labelY)
-        label.setAttribute("text-anchor", "middle")
-        label.setAttribute("alignment-baseline", "middle")
-        label.setAttribute("font-size", "12px")
-        label.textContent = `${((item.y / total) * 100).toFixed(1)}%`
-        svg.appendChild(label)
+        text.setAttribute("x", labelX)
+        text.setAttribute("y", labelY)
+        text.setAttribute("text-anchor", labelX < 0 ? "end" : "start")
+        text.setAttribute("dominant-baseline", "middle")
+        text.setAttribute("fill", "#555")
+        text.setAttribute("font-size", "9")
+        text.setAttribute("font-weight", "normal")
+        const tspanLabel = document.createElementNS(
+          "http://www.w3.org/2000/svg",
+          "tspan"
+        )
+        tspanLabel.setAttribute("x", labelX)
+        tspanLabel.textContent = labels[index] || `类别 ${index + 1}`
+        text.appendChild(tspanLabel)
+        const tspanPercent = document.createElementNS(
+          "http://www.w3.org/2000/svg",
+          "tspan"
+        )
+        tspanPercent.setAttribute("x", labelX)
+        tspanPercent.setAttribute("dy", "1.2em")
+        tspanPercent.textContent = `${(percentage * 100).toFixed(1)}%`
+        text.appendChild(tspanPercent)
+
+        chartGroup.appendChild(text)
 
         startAngle = endAngle
       })
+
+      svg.appendChild(chartGroup)
+      const padding = 50
+      svg.setAttribute(
+        "viewBox",
+        `${-cx / 2} ${-cy / 2} ${chartWidth + padding * 2} ${
+          chartHeight + padding * 2
+        }`
+      )
+      svg.style.overflow = "visible"
     },
     // 绘制柱状图
     drawBarChart(svg, element, options) {