jmgraph 3.2.19 → 3.2.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +174 -2
- package/dist/jmgraph.core.min.js +1 -1
- package/dist/jmgraph.core.min.js.map +1 -1
- package/dist/jmgraph.js +1640 -135
- package/dist/jmgraph.min.js +1 -1
- package/index.js +13 -2
- package/package.json +1 -1
- package/src/core/jmGraph.js +453 -4
- package/src/core/jmLayer.js +142 -0
- package/src/core/jmPath.js +55 -0
- package/src/shapes/jmEllipse.js +91 -0
- package/src/shapes/jmLabel.js +127 -15
- package/src/shapes/jmPolygon.js +129 -0
- package/src/shapes/jmStar.js +160 -0
- package/example/ball.html +0 -217
- package/example/base.html +0 -112
- package/example/canvas.html +0 -54
- package/example/cell.html +0 -284
- package/example/controls/arc.html +0 -129
- package/example/controls/arrowline.html +0 -78
- package/example/controls/bezier.html +0 -299
- package/example/controls/img.html +0 -97
- package/example/controls/label.html +0 -87
- package/example/controls/line.html +0 -173
- package/example/controls/prismatic.html +0 -63
- package/example/controls/rect.html +0 -64
- package/example/controls/resize.html +0 -112
- package/example/controls/test.html +0 -360
- package/example/es.html +0 -70
- package/example/es5module.html +0 -63
- package/example/heartarc.html +0 -116
- package/example/index.html +0 -47
- package/example/js/require.js +0 -5
- package/example/love/img/bling/bling.tps +0 -265
- package/example/love/img/bling.json +0 -87
- package/example/love/img/bling.tps +0 -295
- package/example/love/img/doc/bling.gif +0 -0
- package/example/love/img/love.json +0 -95
- package/example/love/img/love.tps +0 -315
- package/example/love/img/qq/qq.tps +0 -399
- package/example/love/img/qq.json +0 -242
- package/example/love/index.html +0 -40
- package/example/love/js/game.js +0 -558
- package/example/music.html +0 -211
- package/example/node/test.js +0 -138
- package/example/pdf.html +0 -187
- package/example/progress.html +0 -173
- package/example/pso.html +0 -148
- package/example/sort.html +0 -805
- package/example/tweenjs.html +0 -84
- package/example/webgl.html +0 -278
- package/example/xfj/img/dr_die.gif +0 -0
- package/example/xfj/index.html +0 -332
- package/example/xfj/shake.js +0 -49
- package/example/xfj/testori.html +0 -76
package/src/core/jmPath.js
CHANGED
|
@@ -28,6 +28,61 @@ export default class jmPath extends jmControl {
|
|
|
28
28
|
set points(v) {
|
|
29
29
|
this.needUpdate = true;
|
|
30
30
|
return this.property('points', v);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 转换为SVG路径
|
|
35
|
+
*
|
|
36
|
+
* @method toSVG
|
|
37
|
+
* @return {string} SVG路径字符串
|
|
38
|
+
*/
|
|
39
|
+
toSVG() {
|
|
40
|
+
if(!this.points || this.points.length === 0) return '';
|
|
41
|
+
|
|
42
|
+
let pathData = '';
|
|
43
|
+
const points = this.points;
|
|
44
|
+
|
|
45
|
+
// 移动到起点
|
|
46
|
+
pathData += `M ${points[0].x} ${points[0].y}`;
|
|
47
|
+
|
|
48
|
+
// 绘制路径
|
|
49
|
+
for(let i = 1; i < points.length; i++) {
|
|
50
|
+
const p = points[i];
|
|
51
|
+
if(p.m) {
|
|
52
|
+
// 移动到新位置
|
|
53
|
+
pathData += ` M ${p.x} ${p.y}`;
|
|
54
|
+
} else {
|
|
55
|
+
// 直线到
|
|
56
|
+
pathData += ` L ${p.x} ${p.y}`;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 如果是封闭路径
|
|
61
|
+
if(this.style && this.style.close) {
|
|
62
|
+
pathData += ' Z';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 构建SVG元素
|
|
66
|
+
let svg = '<path d="' + pathData + '"';
|
|
67
|
+
|
|
68
|
+
// 添加样式
|
|
69
|
+
if(this.style) {
|
|
70
|
+
if(this.style.fill) {
|
|
71
|
+
svg += ' fill="' + this.style.fill + '"';
|
|
72
|
+
}
|
|
73
|
+
if(this.style.stroke) {
|
|
74
|
+
svg += ' stroke="' + this.style.stroke + '"';
|
|
75
|
+
}
|
|
76
|
+
if(this.style.lineWidth) {
|
|
77
|
+
svg += ' stroke-width="' + this.style.lineWidth + '"';
|
|
78
|
+
}
|
|
79
|
+
if(this.style.opacity) {
|
|
80
|
+
svg += ' opacity="' + this.style.opacity + '"';
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
svg += '/>';
|
|
85
|
+
return svg;
|
|
31
86
|
}
|
|
32
87
|
|
|
33
88
|
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import {jmArc} from "./jmArc.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 画椭圆
|
|
5
|
+
* 椭圆是通过缩放圆形来实现的,支持完整的椭圆和椭圆弧
|
|
6
|
+
* 可以指定起始角度和结束角度来绘制椭圆弧
|
|
7
|
+
*
|
|
8
|
+
* @class jmEllipse
|
|
9
|
+
* @extends jmArc
|
|
10
|
+
* @param {object} params 椭圆的参数
|
|
11
|
+
* @param {object} [params.center={x:0,y:0}] 椭圆中心点坐标
|
|
12
|
+
* @param {number} [params.width=100] 椭圆宽度(长轴直径)
|
|
13
|
+
* @param {number} [params.height=60] 椭圆高度(短轴直径)
|
|
14
|
+
* @param {number} [params.startAngle=0] 起始角度(弧度)
|
|
15
|
+
* @param {number} [params.endAngle=Math.PI*2] 结束角度(弧度)
|
|
16
|
+
* @param {boolean} [params.anticlockwise=false] 是否逆时针绘制
|
|
17
|
+
*/
|
|
18
|
+
export default class jmEllipse extends jmArc {
|
|
19
|
+
|
|
20
|
+
constructor(params, t='jmEllipse') {
|
|
21
|
+
params = params || {};
|
|
22
|
+
params.isRegular = true; // 标记为规则图形
|
|
23
|
+
super(params, t);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 初始化图形点
|
|
28
|
+
* 为WebGL模式生成控制点,2D模式使用draw方法直接绘制
|
|
29
|
+
*
|
|
30
|
+
* @method initPoints
|
|
31
|
+
* @private
|
|
32
|
+
* @for jmEllipse
|
|
33
|
+
*/
|
|
34
|
+
initPoints() {
|
|
35
|
+
// WebGL模式使用父类的点生成方法
|
|
36
|
+
if(this.graph.mode === 'webgl') {
|
|
37
|
+
return super.initPoints();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 2D模式:生成4个控制点用于边界计算
|
|
41
|
+
// 这些点不是实际的绘制点,而是用于碰撞检测和边界计算
|
|
42
|
+
let location = this.getLocation();
|
|
43
|
+
|
|
44
|
+
this.points = [];
|
|
45
|
+
this.points.push({x:location.center.x - location.width/2, y:location.center.y}); // 左
|
|
46
|
+
this.points.push({x:location.center.x, y:location.center.y - location.height/2}); // 上
|
|
47
|
+
this.points.push({x:location.center.x + location.width/2, y:location.center.y}); // 右
|
|
48
|
+
this.points.push({x:location.center.x, y:location.center.y + location.height/2}); // 下
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 重写基类画图,此处为画一个椭圆
|
|
53
|
+
* 使用Canvas的变换功能(平移和缩放)来绘制椭圆
|
|
54
|
+
*
|
|
55
|
+
* @method draw
|
|
56
|
+
*/
|
|
57
|
+
draw() {
|
|
58
|
+
// WebGL模式使用父类的绘制方法
|
|
59
|
+
if(this.graph.mode === 'webgl') {
|
|
60
|
+
return super.draw();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 获取边界和位置信息
|
|
64
|
+
let bounds = this.parent && this.parent.absoluteBounds ? this.parent.absoluteBounds : this.absoluteBounds;
|
|
65
|
+
let location = this.getLocation();
|
|
66
|
+
|
|
67
|
+
// 获取椭圆弧参数
|
|
68
|
+
let start = this.startAngle || 0;
|
|
69
|
+
let end = this.endAngle || Math.PI * 2;
|
|
70
|
+
let anticlockwise = this.anticlockwise || false;
|
|
71
|
+
|
|
72
|
+
// 椭圆绘制:通过变换圆形来实现
|
|
73
|
+
// 1. 保存当前绘图状态
|
|
74
|
+
this.context.save();
|
|
75
|
+
|
|
76
|
+
// 2. 平移到椭圆中心
|
|
77
|
+
this.context.translate(location.center.x + bounds.left, location.center.y + bounds.top);
|
|
78
|
+
|
|
79
|
+
// 3. 缩放坐标系,使圆形变为椭圆
|
|
80
|
+
// 将X轴缩放width/2,Y轴缩放height/2,这样单位圆就变成了椭圆
|
|
81
|
+
this.context.scale(location.width/2, location.height/2);
|
|
82
|
+
|
|
83
|
+
// 4. 绘制单位圆(会被缩放成椭圆)
|
|
84
|
+
this.context.arc(0, 0, 1, start, end, anticlockwise);
|
|
85
|
+
|
|
86
|
+
// 5. 恢复绘图状态
|
|
87
|
+
this.context.restore();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export { jmEllipse };
|
package/src/shapes/jmLabel.js
CHANGED
|
@@ -108,43 +108,137 @@ export default class jmLabel extends jmControl {
|
|
|
108
108
|
|
|
109
109
|
/**
|
|
110
110
|
* 测试获取文本所占大小
|
|
111
|
-
*
|
|
111
|
+
* 计算文本渲染所需的宽度和高度,支持自动换行
|
|
112
|
+
*
|
|
112
113
|
* @method testSize
|
|
113
|
-
* @return {object} 含文本大小的对象
|
|
114
|
+
* @return {object} 含文本大小的对象 {width, height}
|
|
114
115
|
*/
|
|
115
116
|
testSize() {
|
|
117
|
+
// 使用缓存提高性能,避免重复计算
|
|
116
118
|
if(this.__size) return this.__size;
|
|
117
119
|
|
|
118
|
-
if(this.webglControl)
|
|
120
|
+
if(this.webglControl) {
|
|
121
|
+
this.__size = this.webglControl.testSize(this.text, this.style);
|
|
122
|
+
}
|
|
119
123
|
else {
|
|
120
124
|
this.context.save && this.context.save();
|
|
121
|
-
|
|
125
|
+
|
|
126
|
+
// 设置字体样式用于测量
|
|
122
127
|
this.setStyle({
|
|
123
128
|
font: this.style.font || (this.style.fontSize + 'px ' + this.style.fontFamily)
|
|
124
129
|
});
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
130
|
+
|
|
131
|
+
// 计算文本尺寸
|
|
132
|
+
if(this.style.maxWidth && this.text) {
|
|
133
|
+
// 文本换行处理
|
|
134
|
+
const lines = this.wrapText(this.text, this.style.maxWidth);
|
|
135
|
+
let maxWidth = 0;
|
|
136
|
+
|
|
137
|
+
// 找出最宽的一行
|
|
138
|
+
for(let line of lines) {
|
|
139
|
+
const width = this.context.measureText(line).width;
|
|
140
|
+
if(width > maxWidth) maxWidth = width;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// 计算总高度(行数 × 行高)
|
|
144
|
+
const lineHeight = this.style.lineHeight || this.style.fontSize * 1.2;
|
|
145
|
+
this.__size = {
|
|
146
|
+
width: maxWidth,
|
|
147
|
+
height: lineHeight * lines.length
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
// 单行文本
|
|
152
|
+
this.__size = this.context.measureText ?
|
|
153
|
+
this.context.measureText(this.text) :
|
|
154
|
+
{width: 15};
|
|
155
|
+
this.__size.height = this.style.fontSize ? this.style.fontSize : 15;
|
|
156
|
+
}
|
|
157
|
+
|
|
129
158
|
this.context.restore && this.context.restore();
|
|
130
|
-
this.__size.height = this.style.fontSize?this.style.fontSize:15;
|
|
131
159
|
}
|
|
132
160
|
|
|
161
|
+
// 设置默认宽高
|
|
133
162
|
if(!this.width) this.width = this.__size.width;
|
|
134
163
|
if(!this.height) this.height = this.__size.height;
|
|
135
164
|
|
|
136
165
|
return this.__size;
|
|
137
166
|
}
|
|
138
167
|
|
|
168
|
+
/**
|
|
169
|
+
* 文本换行处理
|
|
170
|
+
* 根据最大宽度将文本分割成多行
|
|
171
|
+
* 支持中英文混合文本,优先在空格处换行
|
|
172
|
+
*
|
|
173
|
+
* @method wrapText
|
|
174
|
+
* @param {string} text 文本内容
|
|
175
|
+
* @param {number} maxWidth 最大宽度(像素)
|
|
176
|
+
* @return {array} 换行后的文本数组
|
|
177
|
+
*/
|
|
178
|
+
wrapText(text, maxWidth) {
|
|
179
|
+
// 参数验证
|
|
180
|
+
if(!text || !maxWidth) return [text || ''];
|
|
181
|
+
|
|
182
|
+
// 检查缓存,避免重复计算
|
|
183
|
+
const cacheKey = `${text}_${maxWidth}`;
|
|
184
|
+
if(this.__wrapTextCache && this.__wrapTextCache.key === cacheKey) {
|
|
185
|
+
return this.__wrapTextCache.lines;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const lines = [];
|
|
189
|
+
|
|
190
|
+
// 先按换行符分割
|
|
191
|
+
const paragraphs = text.split('\n');
|
|
192
|
+
|
|
193
|
+
for(let paragraph of paragraphs) {
|
|
194
|
+
// 如果段落为空,添加空行
|
|
195
|
+
if(!paragraph) {
|
|
196
|
+
lines.push('');
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// 按空格分割单词
|
|
201
|
+
const words = paragraph.split(' ');
|
|
202
|
+
let currentLine = words[0];
|
|
203
|
+
|
|
204
|
+
for(let i = 1; i < words.length; i++) {
|
|
205
|
+
const word = words[i];
|
|
206
|
+
const testLine = currentLine + ' ' + word;
|
|
207
|
+
const metrics = this.context.measureText(testLine);
|
|
208
|
+
const testWidth = metrics.width;
|
|
209
|
+
|
|
210
|
+
if(testWidth <= maxWidth) {
|
|
211
|
+
// 当前行还能容纳这个单词
|
|
212
|
+
currentLine = testLine;
|
|
213
|
+
} else {
|
|
214
|
+
// 当前行已满,保存当前行并开始新行
|
|
215
|
+
if(currentLine) lines.push(currentLine);
|
|
216
|
+
currentLine = word;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// 添加最后一行
|
|
221
|
+
if(currentLine) lines.push(currentLine);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// 缓存结果
|
|
225
|
+
this.__wrapTextCache = {
|
|
226
|
+
key: cacheKey,
|
|
227
|
+
lines: lines
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
return lines;
|
|
231
|
+
}
|
|
232
|
+
|
|
139
233
|
/**
|
|
140
234
|
* 根据位置偏移画字符串
|
|
141
235
|
*
|
|
142
236
|
* @method draw
|
|
143
237
|
*/
|
|
144
|
-
draw() {
|
|
238
|
+
draw() {
|
|
145
239
|
|
|
146
240
|
//获取当前控件的绝对位置
|
|
147
|
-
let bounds = this.parent && this.parent.absoluteBounds?this.parent.absoluteBounds:this.absoluteBounds;
|
|
241
|
+
let bounds = this.parent && this.parent.absoluteBounds?this.parent.absoluteBounds:this.absoluteBounds;
|
|
148
242
|
const size = this.testSize();
|
|
149
243
|
let location = this.location;
|
|
150
244
|
let x = location.left + bounds.left;
|
|
@@ -184,7 +278,16 @@ export default class jmLabel extends jmControl {
|
|
|
184
278
|
}
|
|
185
279
|
else if(this.style.fill && this.context.fillText) {
|
|
186
280
|
if(this.style.maxWidth) {
|
|
187
|
-
|
|
281
|
+
// 绘制换行文本
|
|
282
|
+
const lines = this.wrapText(txt, this.style.maxWidth);
|
|
283
|
+
const lineHeight = this.style.fontSize;
|
|
284
|
+
// 调整起始Y位置以支持垂直对齐
|
|
285
|
+
const startY = y - (lines.length - 1) * lineHeight / 2;
|
|
286
|
+
|
|
287
|
+
for(let i = 0; i < lines.length; i++) {
|
|
288
|
+
const lineY = startY + i * lineHeight;
|
|
289
|
+
this.context.fillText(lines[i], x, lineY);
|
|
290
|
+
}
|
|
188
291
|
}
|
|
189
292
|
else {
|
|
190
293
|
this.context.fillText(txt,x,y);
|
|
@@ -192,7 +295,16 @@ export default class jmLabel extends jmControl {
|
|
|
192
295
|
}
|
|
193
296
|
else if(this.context.strokeText) {
|
|
194
297
|
if(this.style.maxWidth) {
|
|
195
|
-
|
|
298
|
+
// 绘制换行文本
|
|
299
|
+
const lines = this.wrapText(txt, this.style.maxWidth);
|
|
300
|
+
const lineHeight = this.style.fontSize;
|
|
301
|
+
// 调整起始Y位置以支持垂直对齐
|
|
302
|
+
const startY = y - (lines.length - 1) * lineHeight / 2;
|
|
303
|
+
|
|
304
|
+
for(let i = 0; i < lines.length; i++) {
|
|
305
|
+
const lineY = startY + i * lineHeight;
|
|
306
|
+
this.context.strokeText(lines[i], x, lineY);
|
|
307
|
+
}
|
|
196
308
|
}
|
|
197
309
|
else {
|
|
198
310
|
this.context.strokeText(txt,x,y);
|
|
@@ -223,7 +335,7 @@ export default class jmLabel extends jmControl {
|
|
|
223
335
|
}
|
|
224
336
|
|
|
225
337
|
if(this.style.border.left) {
|
|
226
|
-
this.context.moveTo(this.points[3].x + bounds.left,this.points[3].y + bounds.top);
|
|
338
|
+
this.context.moveTo(this.points[3].x + bounds.left,this.points[3].y + bounds.top);
|
|
227
339
|
this.context.lineTo(this.points[0].x + bounds.left,this.points[0].y + bounds.top);
|
|
228
340
|
}
|
|
229
341
|
}
|
|
@@ -259,7 +371,7 @@ export default class jmLabel extends jmControl {
|
|
|
259
371
|
}
|
|
260
372
|
points.length && this.webglControl && this.webglControl.stroke(points);
|
|
261
373
|
}
|
|
262
|
-
}
|
|
374
|
+
}
|
|
263
375
|
}
|
|
264
376
|
|
|
265
377
|
endDraw() {
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import {jmPath} from "../core/jmPath.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 画多边形
|
|
5
|
+
* 支持规则多边形(正多边形)和自定义多边形
|
|
6
|
+
* 规则多边形通过边数和半径自动计算顶点,自定义多边形通过顶点数组定义
|
|
7
|
+
*
|
|
8
|
+
* @class jmPolygon
|
|
9
|
+
* @extends jmPath
|
|
10
|
+
* @param {object} params 多边形的参数
|
|
11
|
+
* @param {array} [params.points] 自定义顶点数组,如果提供则忽略sides和radius
|
|
12
|
+
* @param {number} [params.sides=3] 多边形边数(3-100)
|
|
13
|
+
* @param {number} [params.radius=50] 多边形半径(像素)
|
|
14
|
+
* @param {object} [params.center={x:0,y:0}] 多边形中心点坐标
|
|
15
|
+
*/
|
|
16
|
+
export default class jmPolygon extends jmPath {
|
|
17
|
+
|
|
18
|
+
constructor(params, t='jmPolygon') {
|
|
19
|
+
params = params || {};
|
|
20
|
+
params.isRegular = true; // 标记为规则图形,便于优化渲染
|
|
21
|
+
super(params, t);
|
|
22
|
+
|
|
23
|
+
// 参数验证和初始化
|
|
24
|
+
this.sides = params.sides || params.points?.length || 3;
|
|
25
|
+
this.radius = params.radius || 50;
|
|
26
|
+
this.center = params.center || {x: 0, y: 0};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 设定或获取多边形边数
|
|
31
|
+
* 边数决定了多边形的形状,最小为3(三角形)
|
|
32
|
+
*
|
|
33
|
+
* @property sides
|
|
34
|
+
* @for jmPolygon
|
|
35
|
+
* @type {number}
|
|
36
|
+
*/
|
|
37
|
+
get sides() {
|
|
38
|
+
return this.property('sides');
|
|
39
|
+
}
|
|
40
|
+
set sides(v) {
|
|
41
|
+
// 参数验证:边数必须在3-100之间
|
|
42
|
+
if(typeof v !== 'number' || isNaN(v) || v < 3) {
|
|
43
|
+
console.warn('jmPolygon: sides must be a number >= 3');
|
|
44
|
+
v = 3;
|
|
45
|
+
}
|
|
46
|
+
if(v > 100) {
|
|
47
|
+
console.warn('jmPolygon: sides should not exceed 100 for performance reasons');
|
|
48
|
+
v = 100;
|
|
49
|
+
}
|
|
50
|
+
this.needUpdate = true;
|
|
51
|
+
return this.property('sides', Math.floor(v)); // 确保是整数
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 设定或获取多边形半径
|
|
56
|
+
* 半径是从中心点到顶点的距离
|
|
57
|
+
*
|
|
58
|
+
* @property radius
|
|
59
|
+
* @for jmPolygon
|
|
60
|
+
* @type {number}
|
|
61
|
+
*/
|
|
62
|
+
get radius() {
|
|
63
|
+
return this.property('radius');
|
|
64
|
+
}
|
|
65
|
+
set radius(v) {
|
|
66
|
+
// 参数验证:半径必须为正数
|
|
67
|
+
if(typeof v !== 'number' || isNaN(v) || v <= 0) {
|
|
68
|
+
console.warn('jmPolygon: radius must be a positive number');
|
|
69
|
+
v = 1;
|
|
70
|
+
}
|
|
71
|
+
this.needUpdate = true;
|
|
72
|
+
return this.property('radius', v);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 设定或获取多边形中心
|
|
77
|
+
* 中心点是多边形的几何中心
|
|
78
|
+
*
|
|
79
|
+
* @property center
|
|
80
|
+
* @for jmPolygon
|
|
81
|
+
* @type {object}
|
|
82
|
+
*/
|
|
83
|
+
get center() {
|
|
84
|
+
return this.property('center');
|
|
85
|
+
}
|
|
86
|
+
set center(v) {
|
|
87
|
+
// 参数验证:中心点必须包含x和y属性
|
|
88
|
+
if(!v || typeof v.x !== 'number' || typeof v.y !== 'number') {
|
|
89
|
+
console.warn('jmPolygon: center must be an object with x and y properties');
|
|
90
|
+
v = {x: 0, y: 0};
|
|
91
|
+
}
|
|
92
|
+
this.needUpdate = true;
|
|
93
|
+
return this.property('center', v);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 初始化图形点
|
|
98
|
+
* 如果提供了自定义顶点,则使用自定义顶点
|
|
99
|
+
* 否则根据边数和半径自动计算规则多边形的顶点
|
|
100
|
+
*
|
|
101
|
+
* @method initPoints
|
|
102
|
+
* @private
|
|
103
|
+
* @for jmPolygon
|
|
104
|
+
*/
|
|
105
|
+
initPoints() {
|
|
106
|
+
// 如果提供了自定义顶点,直接使用
|
|
107
|
+
if (this.points && this.points.length > 0) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 计算规则多边形的顶点
|
|
112
|
+
const points = [];
|
|
113
|
+
const sides = this.sides;
|
|
114
|
+
const radius = this.radius;
|
|
115
|
+
const center = this.center;
|
|
116
|
+
|
|
117
|
+
// 从顶部开始绘制(-90度),顺时针方向
|
|
118
|
+
for (let i = 0; i < sides; i++) {
|
|
119
|
+
const angle = (i / sides) * Math.PI * 2 - Math.PI / 2;
|
|
120
|
+
const x = center.x + Math.cos(angle) * radius;
|
|
121
|
+
const y = center.y + Math.sin(angle) * radius;
|
|
122
|
+
points.push({x, y});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
this.points = points;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export { jmPolygon };
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import {jmPath} from "../core/jmPath.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 画星形
|
|
5
|
+
* 支持自定义顶点数和内外半径,创建各种星形图案
|
|
6
|
+
* 星形由交替的外半径和内半径顶点组成
|
|
7
|
+
*
|
|
8
|
+
* @class jmStar
|
|
9
|
+
* @extends jmPath
|
|
10
|
+
* @param {object} params 星形的参数
|
|
11
|
+
* @param {array} [params.points] 自定义顶点数组,如果提供则忽略其他参数
|
|
12
|
+
* @param {number} [params.points=5] 星形顶点数(角数,3-50)
|
|
13
|
+
* @param {number} [params.radius=50] 星形外半径(从中心到尖角的距离)
|
|
14
|
+
* @param {number} [params.innerRadius=25] 星形内半径(从中心到凹陷处的距离)
|
|
15
|
+
* @param {object} [params.center={x:0,y:0}] 星形中心点坐标
|
|
16
|
+
*/
|
|
17
|
+
export default class jmStar extends jmPath {
|
|
18
|
+
|
|
19
|
+
constructor(params, t='jmStar') {
|
|
20
|
+
params = params || {};
|
|
21
|
+
params.isRegular = true; // 标记为规则图形
|
|
22
|
+
super(params, t);
|
|
23
|
+
|
|
24
|
+
// 参数验证和初始化
|
|
25
|
+
this.pointsCount = params.points || 5;
|
|
26
|
+
this.radius = params.radius || 50;
|
|
27
|
+
this.innerRadius = params.innerRadius || 25;
|
|
28
|
+
this.center = params.center || {x: 0, y: 0};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 设定或获取星形顶点数(角数)
|
|
33
|
+
* 顶点数决定了星形的角数,例如5表示五角星
|
|
34
|
+
*
|
|
35
|
+
* @property pointsCount
|
|
36
|
+
* @for jmStar
|
|
37
|
+
* @type {number}
|
|
38
|
+
*/
|
|
39
|
+
get pointsCount() {
|
|
40
|
+
return this.property('pointsCount');
|
|
41
|
+
}
|
|
42
|
+
set pointsCount(v) {
|
|
43
|
+
// 参数验证:顶点数必须在3-50之间
|
|
44
|
+
if(typeof v !== 'number' || isNaN(v) || v < 3) {
|
|
45
|
+
console.warn('jmStar: pointsCount must be a number >= 3');
|
|
46
|
+
v = 3;
|
|
47
|
+
}
|
|
48
|
+
if(v > 50) {
|
|
49
|
+
console.warn('jmStar: pointsCount should not exceed 50 for performance reasons');
|
|
50
|
+
v = 50;
|
|
51
|
+
}
|
|
52
|
+
this.needUpdate = true;
|
|
53
|
+
return this.property('pointsCount', Math.floor(v)); // 确保是整数
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 设定或获取星形外半径
|
|
58
|
+
* 外半径是从中心到尖角的距离
|
|
59
|
+
*
|
|
60
|
+
* @property radius
|
|
61
|
+
* @for jmStar
|
|
62
|
+
* @type {number}
|
|
63
|
+
*/
|
|
64
|
+
get radius() {
|
|
65
|
+
return this.property('radius');
|
|
66
|
+
}
|
|
67
|
+
set radius(v) {
|
|
68
|
+
// 参数验证:半径必须为正数
|
|
69
|
+
if(typeof v !== 'number' || isNaN(v) || v <= 0) {
|
|
70
|
+
console.warn('jmStar: radius must be a positive number');
|
|
71
|
+
v = 1;
|
|
72
|
+
}
|
|
73
|
+
this.needUpdate = true;
|
|
74
|
+
return this.property('radius', v);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 设定或获取星形内半径
|
|
79
|
+
* 内半径是从中心到凹陷处的距离
|
|
80
|
+
* 内半径应该小于外半径,否则会产生奇怪的形状
|
|
81
|
+
*
|
|
82
|
+
* @property innerRadius
|
|
83
|
+
* @for jmStar
|
|
84
|
+
* @type {number}
|
|
85
|
+
*/
|
|
86
|
+
get innerRadius() {
|
|
87
|
+
return this.property('innerRadius');
|
|
88
|
+
}
|
|
89
|
+
set innerRadius(v) {
|
|
90
|
+
// 参数验证:内半径必须为正数
|
|
91
|
+
if(typeof v !== 'number' || isNaN(v) || v <= 0) {
|
|
92
|
+
console.warn('jmStar: innerRadius must be a positive number');
|
|
93
|
+
v = 1;
|
|
94
|
+
}
|
|
95
|
+
// 警告:内半径不应大于外半径
|
|
96
|
+
if(v >= this.radius) {
|
|
97
|
+
console.warn('jmStar: innerRadius should be less than radius for proper star shape');
|
|
98
|
+
}
|
|
99
|
+
this.needUpdate = true;
|
|
100
|
+
return this.property('innerRadius', v);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 设定或获取星形中心
|
|
105
|
+
* 中心点是星形的几何中心
|
|
106
|
+
*
|
|
107
|
+
* @property center
|
|
108
|
+
* @for jmStar
|
|
109
|
+
* @type {object}
|
|
110
|
+
*/
|
|
111
|
+
get center() {
|
|
112
|
+
return this.property('center');
|
|
113
|
+
}
|
|
114
|
+
set center(v) {
|
|
115
|
+
// 参数验证:中心点必须包含x和y属性
|
|
116
|
+
if(!v || typeof v.x !== 'number' || typeof v.y !== 'number') {
|
|
117
|
+
console.warn('jmStar: center must be an object with x and y properties');
|
|
118
|
+
v = {x: 0, y: 0};
|
|
119
|
+
}
|
|
120
|
+
this.needUpdate = true;
|
|
121
|
+
return this.property('center', v);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* 初始化图形点
|
|
126
|
+
* 计算星形的顶点坐标,交替使用外半径和内半径
|
|
127
|
+
*
|
|
128
|
+
* @method initPoints
|
|
129
|
+
* @private
|
|
130
|
+
* @for jmStar
|
|
131
|
+
*/
|
|
132
|
+
initPoints() {
|
|
133
|
+
// 如果提供了自定义顶点,直接使用
|
|
134
|
+
if (this.points && this.points.length > 0) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// 计算星形顶点
|
|
139
|
+
const points = [];
|
|
140
|
+
const pointsCount = this.pointsCount;
|
|
141
|
+
const radius = this.radius;
|
|
142
|
+
const innerRadius = this.innerRadius;
|
|
143
|
+
const center = this.center;
|
|
144
|
+
|
|
145
|
+
// 星形有2倍顶点数的点(外半径和内半径交替)
|
|
146
|
+
// 从顶部开始绘制(-90度),顺时针方向
|
|
147
|
+
for (let i = 0; i < pointsCount * 2; i++) {
|
|
148
|
+
const angle = (i / pointsCount) * Math.PI - Math.PI / 2;
|
|
149
|
+
// 偶数索引使用外半径,奇数索引使用内半径
|
|
150
|
+
const r = i % 2 === 0 ? radius : innerRadius;
|
|
151
|
+
const x = center.x + Math.cos(angle) * r;
|
|
152
|
+
const y = center.y + Math.sin(angle) * r;
|
|
153
|
+
points.push({x, y});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
this.points = points;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export { jmStar };
|