|
@@ -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) {
|