674 lines
16 KiB
Markdown
674 lines
16 KiB
Markdown
# 罗盘组件详细设计文档
|
||
|
||
## 1. 概述
|
||
|
||
本组件实现了一个可配置的圆盘罗盘可视化系统,支持将圆盘分为多个同心圆环和扇区,并在每个区域精确放置文字或图形。
|
||
|
||
## 2. 数学模型
|
||
|
||
### 2.1 坐标系统
|
||
|
||
- **角度约定**:0° 位于正北(上方),顺时针方向为正
|
||
- **SVG 坐标系**:
|
||
- x 轴:向右为正
|
||
- y 轴:向下为正
|
||
- 原点:画布中心
|
||
|
||
### 2.2 参数定义
|
||
|
||
#### 角度参数
|
||
- `n`:总扇区数(pie 数量)
|
||
- `angle[i]`:第 i 个分度线的角度,i ∈ [0...n]
|
||
- `wide(i) = angle[i] - angle[i-1]`:第 i 块扇区的角度宽度
|
||
|
||
#### 半径参数
|
||
- `m`:总圆环数
|
||
- `radius(j)`:第 j 个圆环的半径,j ∈ [0...m]
|
||
- `height(j) = radius(j) - radius(j-1)`:第 j 层圆环的高度
|
||
|
||
#### 区域定义
|
||
- `layer(j)`:第 j 层,由圆环 j 和圆环 j-1 之间形成
|
||
- `pie(i)`:第 i 个扇区,由分度线 i-1 和分度线 i 之间形成
|
||
- `sector(j,i)`:第 j 层第 i 个扇区的区域
|
||
|
||
### 2.3 形心计算公式
|
||
|
||
对于圆环扇形区域 `sector(j,i)`,其形心位置计算如下:
|
||
|
||
#### 输入参数
|
||
- `rInner = radius(j-1)`:内半径
|
||
- `rOuter = radius(j)`:外半径
|
||
- `aStart = angle[i-1]`:起始角度(度)
|
||
- `aEnd = angle[i]`:结束角度(度)
|
||
|
||
#### 计算步骤
|
||
|
||
1. **角度归一化**
|
||
```
|
||
a₁ = normalize(aStart) // 转换到 [0, 360)
|
||
a₂ = normalize(aEnd)
|
||
Δ = a₂ - a₁ // 若 < 0,则加 360
|
||
```
|
||
|
||
2. **中心角度**
|
||
```
|
||
aMid = a₁ + Δ/2 // 扇区中心角度
|
||
```
|
||
|
||
3. **形心半径**(面积加权质心)
|
||
```
|
||
radialFactor = (2/3) × (r₂³ - r₁³) / (r₂² - r₁²)
|
||
sinc(x) = sin(x) / x // x ≠ 0 时
|
||
ρ = radialFactor × sinc(Δ/2)
|
||
```
|
||
|
||
其中:
|
||
- r₁ = rInner
|
||
- r₂ = rOuter
|
||
- Δ 为弧度制
|
||
|
||
4. **极坐标转笛卡尔坐标**
|
||
```
|
||
cx = ρ × sin(aMid)
|
||
cy = -ρ × cos(aMid) // 注意:y 坐标取负以适应 SVG 坐标系
|
||
```
|
||
|
||
#### 输出结果
|
||
- `(cx, cy)`:形心的笛卡尔坐标
|
||
- `ρ`:形心的极坐标半径
|
||
- `aMid`:形心的极坐标角度
|
||
|
||
### 2.4 特殊情况处理
|
||
|
||
1. **纯扇形**(rInner = 0)
|
||
- 公式仍然适用,内圆退化为点
|
||
|
||
2. **跨越 0° 的扇区**
|
||
- 例如:aStart = 315°, aEnd = 45°
|
||
- 通过归一化自动处理:Δ = 45 - 315 + 360 = 90°
|
||
|
||
3. **退化情况**
|
||
- rInner ≥ rOuter:返回 (0, 0)
|
||
- Δ = 0:返回 (0, 0)
|
||
|
||
## 3. 核心算法实现
|
||
|
||
### 3.1 极坐标转换
|
||
|
||
```typescript
|
||
function polarToXY(aDeg: number, r: number): {x: number, y: number} {
|
||
const a = (aDeg * Math.PI) / 180;
|
||
return {
|
||
x: r * Math.sin(a),
|
||
y: -r * Math.cos(a) // y 坐标取负
|
||
};
|
||
}
|
||
```
|
||
|
||
### 3.2 角度归一化
|
||
|
||
```typescript
|
||
function normalizeDeg(deg: number): number {
|
||
const d = deg % 360;
|
||
return d < 0 ? d + 360 : d;
|
||
}
|
||
```
|
||
|
||
### 3.3 形心计算
|
||
|
||
```typescript
|
||
function annularSectorCentroid(params: {
|
||
rInner: number,
|
||
rOuter: number,
|
||
aStartDeg: number,
|
||
aEndDeg: number
|
||
}): CentroidResult {
|
||
const { rInner, rOuter, aStartDeg, aEndDeg } = params;
|
||
|
||
// 1. 角度归一化
|
||
const a1 = normalizeDeg(aStartDeg);
|
||
const a2 = normalizeDeg(aEndDeg);
|
||
let deltaDeg = a2 - a1;
|
||
if (deltaDeg < 0) deltaDeg += 360;
|
||
|
||
// 2. 中心角度
|
||
const aMidDeg = normalizeDeg(a1 + deltaDeg / 2);
|
||
const aMidRad = (aMidDeg * Math.PI) / 180;
|
||
|
||
// 3. 边界检查
|
||
if (rOuter <= rInner || deltaDeg === 0) {
|
||
return { cx: 0, cy: 0, rho: 0, aMidDeg, aMidRad, deltaDeg };
|
||
}
|
||
|
||
// 4. 形心半径计算
|
||
const delta = (deltaDeg * Math.PI) / 180;
|
||
const radialFactor = (2 / 3) *
|
||
((rOuter ** 3 - rInner ** 3) / (rOuter ** 2 - rInner ** 2));
|
||
const half = delta / 2;
|
||
const sinc = half === 0 ? 1 : Math.sin(half) / half;
|
||
const rho = radialFactor * sinc;
|
||
|
||
// 5. 坐标转换
|
||
const p = polarToXY(aMidDeg, rho);
|
||
return { cx: p.x, cy: p.y, rho, aMidDeg, aMidRad, deltaDeg };
|
||
}
|
||
```
|
||
|
||
## 4. 扇区路径生成
|
||
|
||
### 4.1 SVG 路径绘制
|
||
|
||
圆环扇形使用 SVG 的 `<path>` 元素绘制,包含以下步骤:
|
||
|
||
1. 移动到外弧起点
|
||
2. 绘制外弧(arc to)
|
||
3. 连线到内弧终点
|
||
4. 绘制内弧(arc to)
|
||
5. 闭合路径
|
||
|
||
```typescript
|
||
function annularSectorPath(
|
||
rInner: number,
|
||
rOuter: number,
|
||
aStartDeg: number,
|
||
aEndDeg: number
|
||
): string {
|
||
// 计算关键点
|
||
const p1 = polarToXY(aStart, rOuter); // 外弧起点
|
||
const p2 = polarToXY(aEnd, rOuter); // 外弧终点
|
||
const p3 = polarToXY(aEnd, rInner); // 内弧终点
|
||
const p4 = polarToXY(aStart, rInner); // 内弧起点
|
||
|
||
// 确定 large-arc-flag
|
||
const largeArc = delta > 180 ? 1 : 0;
|
||
|
||
// 生成路径
|
||
return [
|
||
`M ${p1.x} ${p1.y}`, // 起点
|
||
`A ${rOuter} ${rOuter} 0 ${largeArc} 1 ${p2.x} ${p2.y}`, // 外弧
|
||
`L ${p3.x} ${p3.y}`, // 连线
|
||
`A ${rInner} ${rInner} 0 ${largeArc} 0 ${p4.x} ${p4.y}`, // 内弧
|
||
"Z" // 闭合
|
||
].join(" ");
|
||
}
|
||
```
|
||
|
||
### 4.2 特殊情况:纯扇形
|
||
|
||
当 `rInner ≈ 0` 时,内弧退化为圆心点:
|
||
|
||
```typescript
|
||
if (rInner <= 0.000001) {
|
||
return [
|
||
`M ${p1.x} ${p1.y}`,
|
||
`A ${rOuter} ${rOuter} 0 ${largeArc} 1 ${p2.x} ${p2.y}`,
|
||
`L 0 0`, // 连线到圆心
|
||
"Z"
|
||
].join(" ");
|
||
}
|
||
```
|
||
|
||
## 5. 文字排列算法
|
||
|
||
### 5.1 文字路径生成
|
||
|
||
文字沿圆弧排列,使用 SVG 的 `<textPath>` 功能。路径使用形心半径:
|
||
|
||
```typescript
|
||
function generateTextPath(
|
||
rInner: number,
|
||
rOuter: number,
|
||
aStartDeg: number,
|
||
aEndDeg: number,
|
||
reverse: boolean
|
||
): string {
|
||
// 使用形心半径作为文字路径
|
||
const centroid = annularSectorCentroid({
|
||
rInner, rOuter, aStartDeg, aEndDeg
|
||
});
|
||
const rMid = centroid.rho; // 形心半径
|
||
|
||
const p1 = polarToXY(aStart, rMid);
|
||
const p2 = polarToXY(aEnd, rMid);
|
||
const largeArc = delta > 180 ? 1 : 0;
|
||
|
||
if (reverse) {
|
||
// 反向路径(左半圆)
|
||
return `M ${p2.x} ${p2.y} A ${rMid} ${rMid} 0 ${largeArc} 0 ${p1.x} ${p1.y}`;
|
||
} else {
|
||
// 正向路径(右半圆)
|
||
return `M ${p1.x} ${p1.y} A ${rMid} ${rMid} 0 ${largeArc} 1 ${p2.x} ${p2.y}`;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5.2 文字方向判断
|
||
|
||
为保持文字"头朝外",需要判断是否使用反向路径:
|
||
|
||
```typescript
|
||
// 左半圆(90° ~ 270°)需要反向路径
|
||
const needReverse = aMidDeg > 90 && aMidDeg < 270;
|
||
```
|
||
|
||
**原理**:
|
||
- 右半圆(0°~90°, 270°~360°):正向路径,文字自然朝外
|
||
- 左半圆(90°~270°):反向路径,避免文字倒置
|
||
|
||
### 5.3 文字长度适配
|
||
|
||
根据层数自动调整文字长度:
|
||
|
||
```typescript
|
||
// 第 j 层显示 (j+1) × 2 个字
|
||
const textLength = (layerIndex + 1) * 2;
|
||
const label = '测'.repeat(textLength);
|
||
```
|
||
|
||
示例:
|
||
- 第1层:2个字
|
||
- 第2层:4个字
|
||
- 第3层:6个字
|
||
|
||
## 6. 颜色生成策略
|
||
|
||
当前实现以主题色板为核心:`colorRef` 优先解析为主题色名,未命中时退回原值。
|
||
|
||
**扇区填色优先级**:
|
||
1. 扇区级 `colorRef`
|
||
2. 层级规律填色(`layer.colorRef` + `num/interval/patternOffset`)
|
||
3. 全局 `background`
|
||
|
||
**刻度环颜色**:
|
||
- `degreeRing.colorRef` 存在时,渲染环形背景填充(作为刻度环背景)。
|
||
- `tickColor` / `ringColor` 显式提供则优先使用;未提供时根据背景计算对比色。
|
||
- `opacity` 同时作用于环形背景和内外圆环描边。
|
||
|
||
## 7. 组件架构
|
||
|
||
### 7.1 模块划分
|
||
|
||
```
|
||
src/
|
||
├── types.ts # TypeScript 类型定义
|
||
├── utils.ts # 纯函数工具集
|
||
├── constants.ts # 配置常量
|
||
├── composables/
|
||
│ └── useLuopan.ts # 组合函数(业务逻辑)
|
||
├── Luopan.vue # 主组件
|
||
├── utils.test.ts # 单元测试
|
||
└── index.ts # 入口文件
|
||
```
|
||
|
||
### 7.2 数据流
|
||
|
||
```
|
||
配置数据 (Example)
|
||
↓
|
||
useLuopan (计算扇区数据)
|
||
↓
|
||
Sector[] (包含形心、路径、颜色等)
|
||
↓
|
||
Luopan.vue (渲染 SVG)
|
||
```
|
||
|
||
### 7.3 关键类型定义
|
||
|
||
```typescript
|
||
// 配置
|
||
interface Example {
|
||
name: string;
|
||
angles: number[]; // 角度分割点 [0, 30, 60, ..., 360]
|
||
radii: number[]; // 圆环半径 [60, 120, 180]
|
||
}
|
||
|
||
// 扇区数据
|
||
interface Sector {
|
||
key: string;
|
||
layerIndex: number;
|
||
pieIndex: number;
|
||
rInner: number;
|
||
rOuter: number;
|
||
aStart: number;
|
||
aEnd: number;
|
||
aMidDeg: number;
|
||
aMidRad: number;
|
||
cx: number; // 形心 x
|
||
cy: number; // 形心 y
|
||
fill: string;
|
||
label: string;
|
||
path: string; // 扇区路径
|
||
textPath: string; // 文字路径
|
||
textPathId: string;
|
||
needReverse: boolean;
|
||
}
|
||
```
|
||
|
||
## 8. 使用示例
|
||
|
||
### 8.1 基本使用
|
||
|
||
```vue
|
||
<template>
|
||
<Luopan :size="520" />
|
||
</template>
|
||
|
||
<script setup>
|
||
import { Luopan } from './src';
|
||
</script>
|
||
```
|
||
|
||
### 8.2 自定义配置
|
||
|
||
```typescript
|
||
import { useLuopan } from './src';
|
||
import { ref } from 'vue';
|
||
|
||
const customExample = ref({
|
||
name: '自定义罗盘',
|
||
angles: [0, 45, 90, 135, 180, 225, 270, 315, 360],
|
||
radii: [80, 160, 240]
|
||
});
|
||
|
||
const { sectors } = useLuopan(customExample);
|
||
```
|
||
|
||
### 8.3 访问工具函数
|
||
|
||
```typescript
|
||
import {
|
||
polarToXY,
|
||
annularSectorCentroid,
|
||
generateSectorColor
|
||
} from './src';
|
||
|
||
// 计算特定区域的形心
|
||
const centroid = annularSectorCentroid({
|
||
rInner: 50,
|
||
rOuter: 100,
|
||
aStartDeg: 0,
|
||
aEndDeg: 30
|
||
});
|
||
|
||
console.log(`形心位置: (${centroid.cx}, ${centroid.cy})`);
|
||
```
|
||
|
||
## 9. 测试
|
||
|
||
### 9.1 单元测试覆盖
|
||
|
||
- ✅ 极坐标转换(4个方向)
|
||
- ✅ 角度归一化(负数、大于360)
|
||
- ✅ 形心计算(纯扇形、圆环、跨0°)
|
||
- ✅ 路径生成(正常、退化情况)
|
||
- ✅ 颜色生成(色相、亮度)
|
||
|
||
### 9.2 运行测试
|
||
|
||
```bash
|
||
npm run test # 运行测试
|
||
npm run test:ui # 测试 UI
|
||
npm run test:coverage # 覆盖率报告
|
||
```
|
||
|
||
## 10. 性能优化
|
||
|
||
### 10.1 计算优化
|
||
|
||
- 使用 Vue 的 `computed` 缓存计算结果
|
||
- 避免重复计算:形心、路径等一次生成
|
||
- 三角函数结果复用
|
||
|
||
### 10.2 渲染优化
|
||
|
||
- 使用 `key` 属性优化列表渲染
|
||
- SVG 路径字符串预生成,避免模板内计算
|
||
- 辅助线可选显示,减少 DOM 节点
|
||
|
||
## 11. 扩展性
|
||
|
||
### 11.1 支持的扩展
|
||
|
||
- ✅ 自定义角度分割
|
||
- ✅ 自定义圆环半径
|
||
- ✅ 自定义颜色方案
|
||
- ✅ 自定义文字内容
|
||
- 🔲 图标/图片放置(预留 `<foreignObject>`)
|
||
- 🔲 交互事件(点击、悬停)
|
||
- 🔲 动画效果
|
||
|
||
### 11.2 API 设计原则
|
||
|
||
- 纯函数优先,便于测试
|
||
- 类型安全,完整的 TypeScript 支持
|
||
- 组合式 API,灵活复用
|
||
- 渐进式增强,保持简单使用
|
||
|
||
## 12. 文字布局与自适应
|
||
|
||
### 12.1 文字布局配置常量
|
||
|
||
所有文字布局相关的魔法数字已集中在 `TEXT_LAYOUT_CONFIG` 中管理:
|
||
|
||
```typescript
|
||
export const TEXT_LAYOUT_CONFIG = {
|
||
/** 字符间距系数:1.2 表示字符实际占用高度 = fontSize × 1.2 */
|
||
CHAR_SPACING_RATIO: 1.2,
|
||
|
||
/** 径向留白比例:0.8 表示文字占用 80%,上下各留 10% */
|
||
RADIAL_PADDING_RATIO: 0.8,
|
||
|
||
/** 切向留白比例:0.85 表示文字占用 85%,左右各留 7.5% */
|
||
TANGENT_PADDING_RATIO: 0.85,
|
||
|
||
/** 小角度扇区字体缩放:防止弧线弯曲导致视觉溢出 */
|
||
SMALL_ANGLE_SCALE: {
|
||
TINY_THRESHOLD: 15, // < 15° 应用 70% 缩放
|
||
TINY_SCALE: 0.7,
|
||
SMALL_THRESHOLD: 30, // 15°-30° 应用 85% 缩放
|
||
SMALL_SCALE: 0.85,
|
||
},
|
||
|
||
/** 字体大小限制 */
|
||
FONT_SIZE: {
|
||
MIN: 3, // 最小 3px
|
||
MAX: 24, // 最大 24px
|
||
},
|
||
|
||
/** 竖排文字字符数范围 */
|
||
VERTICAL_TEXT: {
|
||
MIN_CHARS: 1,
|
||
MAX_CHARS: 4,
|
||
},
|
||
}
|
||
```
|
||
|
||
### 12.2 字体大小自适应算法
|
||
|
||
#### 横排文字(沿弧线)
|
||
|
||
字体大小同时受径向高度和弧长双重约束:
|
||
|
||
```typescript
|
||
// 1. 径向高度约束
|
||
maxByHeight = radialHeight × RADIAL_PADDING_RATIO
|
||
|
||
// 2. 弧长宽度约束
|
||
arcLength = rMid × deltaDeg × π/180
|
||
availableArcLength = arcLength × TANGENT_PADDING_RATIO
|
||
maxByWidth = availableArcLength / (textLength × 1.1)
|
||
|
||
// 3. 取较小值
|
||
calculatedSize = min(maxByHeight, maxByWidth)
|
||
|
||
// 4. 小角度额外缩放
|
||
if (deltaDeg < 15°) calculatedSize × 0.7
|
||
else if (deltaDeg < 30°) calculatedSize × 0.85
|
||
```
|
||
|
||
**关键点**:
|
||
- `1.1` 系数包含字符宽度(1.0) + 字符间距(0.1)
|
||
- 小角度缩放是因为弧线弯曲使文字在视觉上"更胖"
|
||
|
||
#### 竖排文字(沿径向)
|
||
|
||
竖排文字沿径向排列,受不同约束:
|
||
|
||
```typescript
|
||
// 1. 径向高度约束(主要)
|
||
availableHeight = radialHeight × RADIAL_PADDING_RATIO
|
||
maxByHeight = availableHeight / (textLength × CHAR_SPACING_RATIO)
|
||
|
||
// 2. 最内侧字符的弧长约束(防止最里面的字溢出)
|
||
estimatedFontSize = radialHeight × 0.5
|
||
innerMostRadius = rInner + estimatedFontSize / 2
|
||
innerArcLength = innerMostRadius × deltaDeg × π/180
|
||
availableArcLength = innerArcLength × TANGENT_PADDING_RATIO
|
||
maxByWidth = availableArcLength // 单个字符宽度
|
||
|
||
// 3. 取较小值
|
||
calculatedSize = min(maxByHeight, maxByWidth)
|
||
```
|
||
|
||
**关键点**:
|
||
- 最内侧字符位置的半径最小,弧长最短,是最严格的宽度限制
|
||
- 不需要小角度缩放(竖排不受弧线弯曲影响)
|
||
|
||
### 12.3 竖排文字路径生成
|
||
|
||
竖排文字路径是沿径向的直线,需要特殊处理不同场景:
|
||
|
||
#### 普通圆环扇区(rInner > 0)
|
||
|
||
```typescript
|
||
// 以形心或中点为中心,向两边延伸
|
||
rMid = centroid.rho // 或 (rInner + rOuter) / 2
|
||
requiredPathLength = textLength × CHAR_SPACING_RATIO × fontSize
|
||
halfPath = requiredPathLength / 2
|
||
|
||
// 确保不超出边界
|
||
safeHalfPath = min(
|
||
halfPath,
|
||
radialHeight × RADIAL_PADDING_RATIO / 2,
|
||
rMid - rInner,
|
||
rOuter - rMid
|
||
)
|
||
|
||
finalStartR = rMid + safeHalfPath // 外端
|
||
finalEndR = rMid - safeHalfPath // 内端
|
||
```
|
||
|
||
#### 从圆心开始的扇区(rInner = 0)
|
||
|
||
对于最内层扇区,形心会偏向外侧,需要特殊处理:
|
||
|
||
```typescript
|
||
if (rInner === 0) {
|
||
// 检查以形心居中是否会溢出外圆
|
||
if (rMid + halfPath > rOuter) {
|
||
// 会溢出:改为从外圆向内延伸
|
||
finalStartR = rOuter
|
||
finalEndR = rOuter - actualPathLength
|
||
} else {
|
||
// 不会溢出:正常使用形心居中
|
||
finalStartR = rMid + halfPath
|
||
finalEndR = rMid - halfPath
|
||
}
|
||
}
|
||
```
|
||
|
||
**原理**:
|
||
- 最内层的形心通常在 r = 2/3 × rOuter 附近(对于纯扇形)
|
||
- 如果以形心为中心,向外空间只有 1/3 × rOuter
|
||
- 当文字较多时,会向外溢出
|
||
- 因此优先尝试形心居中,溢出时回退到从外圆向内
|
||
|
||
### 12.4 文字字符数计算
|
||
|
||
#### 横排文字
|
||
|
||
基于扇区尺寸和层数综合决定:
|
||
|
||
```typescript
|
||
estimatedFontSize = radialHeight × 0.6
|
||
charWidth = estimatedFontSize × 1.15
|
||
availableWidth = arcWidth × 0.8
|
||
maxChars = floor(availableWidth / charWidth)
|
||
|
||
layerBasedLength = max(2, layerIndex + 1) // 外层字多一些
|
||
textLength = max(1, min(6, min(maxChars, layerBasedLength)))
|
||
```
|
||
|
||
#### 竖排文字
|
||
|
||
两步计算法,确保字符数与字体大小匹配:
|
||
|
||
```typescript
|
||
// 步骤1:用中等字符数估算字体大小
|
||
tempFontSize = calculateSectorFontSize(rInner, rOuter, aStart, aEnd, 2, isVertical=true)
|
||
|
||
// 步骤2:根据字体大小计算实际能容纳的字符数(1-4个)
|
||
textLength = calculateVerticalTextLength(rInner, rOuter, tempFontSize, 1, 4)
|
||
|
||
// 步骤3:用实际字符数重新精确计算字体大小
|
||
finalFontSize = calculateSectorFontSize(rInner, rOuter, aStart, aEnd, textLength, isVertical=true)
|
||
```
|
||
|
||
### 12.5 横排文字路径生成
|
||
|
||
横排文字沿弧线排列,路径保持完整(不缩短),padding 通过字体大小计算实现:
|
||
|
||
```typescript
|
||
// 路径从 aStart 到 aEnd 的完整弧线
|
||
rMid = centroid.rho // 或 (rInner + rOuter) / 2
|
||
p1 = polarToXY(aStart, rMid)
|
||
p2 = polarToXY(aEnd, rMid)
|
||
|
||
// 弧线路径
|
||
if (reverse) {
|
||
path = `M ${p2.x} ${p2.y} A ${rMid} ${rMid} 0 ${largeArc} 0 ${p1.x} ${p1.y}`
|
||
} else {
|
||
path = `M ${p1.x} ${p1.y} A ${rMid} ${rMid} 0 ${largeArc} 1 ${p2.x} ${p2.y}`
|
||
}
|
||
```
|
||
|
||
文字通过 `<textPath>` 的 `startOffset="50%"` 和 `text-anchor="middle"` 在路径上居中。
|
||
|
||
### 12.6 调试与验证
|
||
|
||
如需调试特定扇区的计算结果,可临时添加日志:
|
||
|
||
```typescript
|
||
// 在 useLuopan.ts 中
|
||
if (j === 0 && aStart >= 225 && aStart <= 260) {
|
||
console.log(`扇区 [${aStart}-${aEnd}°, 层${j}]:`, {
|
||
radialHeight, arcWidth, textLength, fontSize
|
||
});
|
||
}
|
||
```
|
||
|
||
## 13. 参考资料
|
||
|
||
### 12.1 数学原理
|
||
|
||
- 圆环扇形形心公式:基于面积加权的质心计算
|
||
- SVG 坐标系统:W3C SVG 规范
|
||
- 极坐标系统:标准数学定义
|
||
|
||
### 12.2 技术栈
|
||
|
||
- Vue 3.4+
|
||
- TypeScript 5.3+
|
||
- Vite 5.0+
|
||
- Vitest 1.2+
|
||
|
||
---
|
||
|
||
**版本**: 1.1.0
|
||
**最后更新**: 2026年1月20日
|
||
**主要变更**:
|
||
- 新增文字布局配置常量集中管理
|
||
- 优化字体大小自适应算法
|
||
- 完善竖排文字特殊场景处理
|
||
- 修复最内层扇区文字溢出问题
|