update at 2026-01-22 18:43:01
This commit is contained in:
137
src/degreeRing.ts
Normal file
137
src/degreeRing.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import type { DegreeRingConfig, TickMark, DegreeLabel, DegreeRingData } from './types';
|
||||
import { generateTextPath, polarToXY } from './utils';
|
||||
|
||||
// 根据刻度级别计算长度,后续由 clamp 处理最小值
|
||||
const resolveTickLength = (config: DegreeRingConfig, type: TickMark['type']): number => {
|
||||
const step = config.tickLengthStep ?? 0;
|
||||
if (type === 'major') return config.tickLength;
|
||||
if (type === 'minor') return config.tickLength - step;
|
||||
return config.tickLength - 2 * step;
|
||||
};
|
||||
|
||||
const clampTickLength = (value: number): number => (value < 1 ? 1 : value);
|
||||
|
||||
export function buildDegreeRing(config: DegreeRingConfig): DegreeRingData {
|
||||
const ticks: TickMark[] = [];
|
||||
const { rInner, rOuter, mode } = config;
|
||||
const labelFontSize = 8;
|
||||
|
||||
const majorTick = Math.max(1, config.majorTick);
|
||||
const minorTick = Math.max(1, config.minorTick);
|
||||
const microTick = Math.max(1, config.microTick);
|
||||
|
||||
for (let angle = 0; angle < 360; angle++) {
|
||||
let type: TickMark['type'] | null = null;
|
||||
|
||||
if (angle % majorTick === 0) {
|
||||
type = 'major';
|
||||
} else if (angle % minorTick === 0) {
|
||||
type = 'minor';
|
||||
} else if (angle % microTick === 0) {
|
||||
type = 'micro';
|
||||
}
|
||||
|
||||
if (!type) continue;
|
||||
|
||||
const length = clampTickLength(resolveTickLength(config, type));
|
||||
|
||||
// 预计算坐标,避免渲染时重复三角计算
|
||||
if (mode === 'inner') {
|
||||
const start = polarToXY(angle, rInner);
|
||||
const end = polarToXY(angle, rInner + length);
|
||||
ticks.push({
|
||||
angle,
|
||||
type,
|
||||
length,
|
||||
startR: rInner,
|
||||
endR: rInner + length,
|
||||
x1: start.x,
|
||||
y1: start.y,
|
||||
x2: end.x,
|
||||
y2: end.y,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mode === 'outer') {
|
||||
const start = polarToXY(angle, rOuter - length);
|
||||
const end = polarToXY(angle, rOuter);
|
||||
ticks.push({
|
||||
angle,
|
||||
type,
|
||||
length,
|
||||
startR: rOuter - length,
|
||||
endR: rOuter,
|
||||
x1: start.x,
|
||||
y1: start.y,
|
||||
x2: end.x,
|
||||
y2: end.y,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// both: 同角度生成内外两条刻度线
|
||||
const innerStart = polarToXY(angle, rInner);
|
||||
const innerEnd = polarToXY(angle, rInner + length);
|
||||
ticks.push({
|
||||
angle,
|
||||
type,
|
||||
length,
|
||||
startR: rInner,
|
||||
endR: rInner + length,
|
||||
x1: innerStart.x,
|
||||
y1: innerStart.y,
|
||||
x2: innerEnd.x,
|
||||
y2: innerEnd.y,
|
||||
});
|
||||
const outerStart = polarToXY(angle, rOuter - length);
|
||||
const outerEnd = polarToXY(angle, rOuter);
|
||||
ticks.push({
|
||||
angle,
|
||||
type,
|
||||
length,
|
||||
startR: rOuter - length,
|
||||
endR: rOuter,
|
||||
x1: outerStart.x,
|
||||
y1: outerStart.y,
|
||||
x2: outerEnd.x,
|
||||
y2: outerEnd.y,
|
||||
});
|
||||
}
|
||||
|
||||
const labels: DegreeLabel[] = [];
|
||||
if (config.showDegree === 1) {
|
||||
for (let angle = 0; angle < 360; angle += majorTick) {
|
||||
const r = (rInner + rOuter) / 2;
|
||||
const text = angle.toString();
|
||||
const estimatedArcLength = text.length * labelFontSize * 1.1;
|
||||
const estimatedSpan = r > 0 ? (estimatedArcLength / r) * (180 / Math.PI) : 0;
|
||||
const maxSpan = Math.max(majorTick * 0.9, 4);
|
||||
const span = Math.min(Math.max(estimatedSpan, 4), maxSpan);
|
||||
const aStart = angle - span / 2;
|
||||
const aEnd = angle + span / 2;
|
||||
|
||||
// 使用 textPath 保持度数方向与扇区文字一致
|
||||
labels.push({
|
||||
angle,
|
||||
text,
|
||||
r,
|
||||
fontSize: labelFontSize,
|
||||
textPathId: `degree-label-${angle}`,
|
||||
textPath: generateTextPath(r, r, aStart, aEnd, 'middle'),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
ticks,
|
||||
tickColor: config.tickColor,
|
||||
ring: {
|
||||
rInner,
|
||||
rOuter,
|
||||
color: config.ringColor,
|
||||
opacity: config.opacity,
|
||||
},
|
||||
labels: labels.length > 0 ? labels : undefined,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user