jmgraph 3.2.27 → 3.2.29

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.
Files changed (42) hide show
  1. package/dist/jmgraph.core.min.js +1 -1
  2. package/dist/jmgraph.core.min.js.map +1 -1
  3. package/dist/jmgraph.js +2657 -415
  4. package/dist/jmgraph.min.js +1 -1
  5. package/package.json +1 -1
  6. package/src/core/jmControl.js +824 -127
  7. package/src/core/jmEvents.js +154 -0
  8. package/src/core/jmFilter.js +38 -1
  9. package/src/core/jmGradient.js +47 -2
  10. package/src/core/jmGraph.js +51 -7
  11. package/src/core/jmLayer.js +34 -2
  12. package/src/core/jmList.js +167 -0
  13. package/src/core/jmObject.js +128 -8
  14. package/src/core/jmPath.js +43 -5
  15. package/src/core/jmProperty.js +181 -2
  16. package/src/core/jmShadow.js +36 -7
  17. package/src/core/jmUtils.js +149 -12
  18. package/src/lib/webgl/base.js +211 -83
  19. package/src/lib/webgl/core/buffer.js +43 -12
  20. package/src/lib/webgl/core/mapSize.js +16 -7
  21. package/src/lib/webgl/core/mapType.js +41 -22
  22. package/src/lib/webgl/core/program.js +94 -54
  23. package/src/lib/webgl/core/shader.js +20 -8
  24. package/src/lib/webgl/core/texture.js +55 -32
  25. package/src/lib/webgl/gradient.js +49 -17
  26. package/src/lib/webgl/index.js +173 -24
  27. package/src/lib/webgl/path.js +61 -12
  28. package/src/shapes/jmArc.js +48 -2
  29. package/src/shapes/jmArrow.js +35 -2
  30. package/src/shapes/jmArrowLine.js +33 -2
  31. package/src/shapes/jmBezier.js +50 -4
  32. package/src/shapes/jmCircle.js +35 -2
  33. package/src/shapes/jmEllipse.js +29 -3
  34. package/src/shapes/jmHArc.js +39 -2
  35. package/src/shapes/jmImage.js +49 -3
  36. package/src/shapes/jmLabel.js +41 -2
  37. package/src/shapes/jmLine.js +42 -2
  38. package/src/shapes/jmPolygon.js +42 -3
  39. package/src/shapes/jmPrismatic.js +34 -2
  40. package/src/shapes/jmRect.js +45 -3
  41. package/src/shapes/jmResize.js +42 -4
  42. package/src/shapes/jmStar.js +38 -4
@@ -1,4 +1,22 @@
1
1
 
2
+ /**
3
+ * @fileoverview jmGraph 控件基类
4
+ *
5
+ * jmControl 是 jmGraph 库中所有可视化控件的基类,继承自 jmProperty。
6
+ * 提供了完整的控件生命周期管理:
7
+ * - 样式系统:支持填充、描边、阴影、渐变、滤镜等
8
+ * - 变换系统:支持平移、旋转、缩放
9
+ * - 事件系统:支持鼠标、键盘、触摸事件
10
+ * - 渲染系统:支持 Canvas 2D 和 WebGL 双渲染模式
11
+ * - 层级管理:支持 zIndex 排序和父子关系
12
+ * - 碰撞检测:支持点击测试和命中区域
13
+ *
14
+ * @module jmControl
15
+ * @extends jmProperty
16
+ * @author jmGraph Team
17
+ * @license MIT
18
+ */
19
+
2
20
  import {jmUtils} from "./jmUtils.js";
3
21
  import {jmList} from "./jmList.js";
4
22
  import {jmGradient} from "./jmGradient.js";
@@ -7,51 +25,164 @@ import {jmFilter} from "./jmFilter.js";
7
25
  import {jmProperty} from "./jmProperty.js";
8
26
  import WebglPath from "../lib/webgl/path.js";
9
27
 
28
+ /**
29
+ * 样式名称映射表
30
+ *
31
+ * 将简化的样式名称映射到 Canvas API 的标准属性名。
32
+ * 例如:'fill' -> 'fillStyle', 'stroke' -> 'strokeStyle'
33
+ *
34
+ * @constant {Object.<string, string>}
35
+ * @private
36
+ */
10
37
  const jmStyleMap = {
11
- 'fill':'fillStyle',
12
- 'fillImage':'fillImage',
13
- 'stroke':'strokeStyle',
14
- 'shadow.blur':'shadowBlur',
15
- 'shadow.x':'shadowOffsetX',
16
- 'shadow.y':'shadowOffsetY',
17
- 'shadow.color':'shadowColor',
18
- 'lineWidth' : 'lineWidth',
19
- 'miterLimit': 'miterLimit',
20
- 'fillStyle' : 'fillStyle',
21
- 'strokeStyle' : 'strokeStyle',
22
- 'font' : 'font',
23
- 'opacity' : 'globalAlpha',
24
- 'textAlign' : 'textAlign',
25
- 'textBaseline' : 'textBaseline',
26
- 'shadowBlur' : 'shadowBlur',
27
- 'shadowOffsetX' : 'shadowOffsetX',
28
- 'shadowOffsetY' : 'shadowOffsetY',
29
- 'shadowColor' : 'shadowColor',
30
- 'lineJoin': 'lineJoin',
31
- 'lineCap':'lineCap',
32
- 'lineDashOffset': 'lineDashOffset',
33
- 'globalCompositeOperation': 'globalCompositeOperation'
38
+ 'fill':'fillStyle', // 填充颜色
39
+ 'fillImage':'fillImage', // 填充图片
40
+ 'stroke':'strokeStyle', // 描边颜色
41
+ 'shadow.blur':'shadowBlur', // 阴影模糊度
42
+ 'shadow.x':'shadowOffsetX', // 阴影X偏移
43
+ 'shadow.y':'shadowOffsetY', // 阴影Y偏移
44
+ 'shadow.color':'shadowColor', // 阴影颜色
45
+ 'lineWidth' : 'lineWidth', // 线宽
46
+ 'miterLimit': 'miterLimit', // 斜接限制
47
+ 'fillStyle' : 'fillStyle', // 填充样式
48
+ 'strokeStyle' : 'strokeStyle',// 描边样式
49
+ 'font' : 'font', // 字体
50
+ 'opacity' : 'globalAlpha', // 透明度
51
+ 'textAlign' : 'textAlign', // 文本对齐
52
+ 'textBaseline' : 'textBaseline', // 文本基线
53
+ 'shadowBlur' : 'shadowBlur', // 阴影模糊
54
+ 'shadowOffsetX' : 'shadowOffsetX', // 阴影X偏移
55
+ 'shadowOffsetY' : 'shadowOffsetY', // 阴影Y偏移
56
+ 'shadowColor' : 'shadowColor', // 阴影颜色
57
+ 'lineJoin': 'lineJoin', // 线条连接样式
58
+ 'lineCap':'lineCap', // 线条端点样式
59
+ 'lineDashOffset': 'lineDashOffset', // 虚线偏移
60
+ 'globalCompositeOperation': 'globalCompositeOperation' // 合成操作
34
61
  };
35
62
 
63
+ /**
64
+ * jmGraph 控件基类
65
+ *
66
+ * jmControl 是所有可视化图形控件的基类,提供了完整的图形渲染和交互能力。
67
+ *
68
+ * **核心功能:**
69
+ *
70
+ * 1. **样式系统**
71
+ * - 支持填充色、描边色、渐变、图片填充
72
+ * - 支持阴影、滤镜、混合模式
73
+ * - 支持虚线、线宽、线帽等线条样式
74
+ *
75
+ * 2. **变换系统**
76
+ * - 支持 translate(平移)
77
+ * - 支持 rotation(旋转)
78
+ * - 支持 transform(矩阵变换)
79
+ *
80
+ * 3. **事件系统**
81
+ * - 鼠标事件:mousedown, mouseup, mousemove, click, dblclick
82
+ * - 触摸事件:touchstart, touchmove, touchend
83
+ * - 焦点事件:mouseover, mouseleave, touchover, touchleave
84
+ * - 自定义事件:支持任意事件类型
85
+ *
86
+ * 4. **渲染系统**
87
+ * - 自动选择 Canvas 2D 或 WebGL 渲染器
88
+ * - 支持脏矩形优化
89
+ * - 支持层级排序(zIndex)
90
+ *
91
+ * 5. **碰撞检测**
92
+ * - 支持点在多边形内判断
93
+ * - 支持自定义命中区域
94
+ * - 支持旋转后的碰撞检测
95
+ *
96
+ * @class jmControl
97
+ * @extends jmProperty
98
+ *
99
+ * @example
100
+ * // 创建自定义控件
101
+ * class MyShape extends jmControl {
102
+ * constructor(params) {
103
+ * super(params, 'myShape');
104
+ * }
105
+ *
106
+ * // 重写绘制方法
107
+ * draw() {
108
+ * // 自定义绘制逻辑
109
+ * }
110
+ * }
111
+ *
112
+ * // 使用控件
113
+ * const shape = new MyShape({
114
+ * position: { x: 100, y: 100 },
115
+ * width: 50,
116
+ * height: 50,
117
+ * style: {
118
+ * fill: 'red',
119
+ * stroke: 'black',
120
+ * lineWidth: 2
121
+ * }
122
+ * });
123
+ * graph.children.add(shape);
124
+ */
36
125
  export default class jmControl extends jmProperty {
37
126
 
127
+ /**
128
+ * 构造函数
129
+ *
130
+ * 创建一个新的控件实例。子类应该调用 super(params, 'typeName') 来设置类型名称。
131
+ *
132
+ * @constructor
133
+ * @param {Object} [params] - 控件初始化参数
134
+ * @param {Object} [params.style] - 样式对象,包含填充、描边等属性
135
+ * @param {number} [params.width=0] - 控件宽度
136
+ * @param {number} [params.height=0] - 控件高度
137
+ * @param {Object} [params.position] - 控件位置 {x, y}
138
+ * @param {jmGraph} [params.graph] - 所属画布实例
139
+ * @param {number} [params.zIndex=0] - 层级顺序
140
+ * @param {boolean} [params.interactive=false] - 是否响应交互事件
141
+ * @param {Object} [params.hitArea] - 自定义命中区域 {x, y, width, height}
142
+ * @param {boolean} [params.isRegular] - 是否为规则图形(WebGL优化)
143
+ * @param {boolean} [params.needCut] - 是否需要裁剪(WebGL)
144
+ * @param {string} [t] - 控件类型名称,默认使用类名
145
+ *
146
+ * @example
147
+ * // 创建矩形控件
148
+ * const rect = new jmControl({
149
+ * position: { x: 10, y: 10 },
150
+ * width: 100,
151
+ * height: 50,
152
+ * style: {
153
+ * fill: '#ff0000',
154
+ * stroke: '#000000',
155
+ * lineWidth: 2
156
+ * },
157
+ * interactive: true
158
+ * }, 'jmRect');
159
+ */
38
160
  constructor(params, t) {
39
161
  params = params||{};
40
162
  super(params);
163
+ // 设置控件类型标识
41
164
  this.property('type', t || new.target.name);
165
+ // 初始化样式对象
42
166
  this.style = params && params.style ? params.style : {};
167
+ // 设置尺寸
43
168
  this.width = params.width || 0;
44
169
  this.height = params.height || 0;
170
+ // 自定义命中区域(用于点击测试)
45
171
  this.hitArea = params.hitArea || null;
46
172
 
173
+ // 设置位置
47
174
  if(params.position) {
48
175
  this.position = params.position;
49
176
  }
50
177
 
178
+ // 关联画布
51
179
  this.graph = params.graph || null;
180
+ // 层级顺序(用于排序)
52
181
  this.zIndex = params.zIndex || 0;
182
+ // 是否响应交互事件
53
183
  this.interactive = typeof params.interactive == 'undefined'? false : params.interactive;
54
184
 
185
+ // WebGL 模式下创建对应的渲染控制器
55
186
  if(this.mode === 'webgl') {
56
187
  this.webglControl = new WebglPath(this.graph, {
57
188
  style: this.style,
@@ -61,17 +192,47 @@ export default class jmControl extends jmProperty {
61
192
  });
62
193
  }
63
194
 
195
+ // 执行初始化
64
196
  this.initializing();
65
197
 
198
+ // 别名:on 等同于 bind
66
199
  this.on = this.bind;
67
200
 
201
+ // 保存原始参数
68
202
  this.option = params;
69
203
  }
70
204
 
205
+ /**
206
+ * 控件类型标识
207
+ *
208
+ * 用于类型检查和调试,由构造函数自动设置。
209
+ *
210
+ * @type {string}
211
+ * @readonly
212
+ *
213
+ * @example
214
+ * console.log(rect.type); // 'jmRect'
215
+ * if(control.type === 'jmCircle') { ... }
216
+ */
71
217
  get type() {
72
218
  return this.property('type');
73
219
  }
74
220
 
221
+ /**
222
+ * 绘图上下文
223
+ *
224
+ * 获取当前控件的 Canvas 2D 或 WebGL 渲染上下文。
225
+ * 如果控件本身不是 jmGraph,会返回所属 graph 的上下文。
226
+ *
227
+ * @type {CanvasRenderingContext2D|WebGLRenderingContext}
228
+ * @readonly
229
+ *
230
+ * @example
231
+ * // 获取上下文并绘制
232
+ * const ctx = control.context;
233
+ * ctx.fillStyle = 'red';
234
+ * ctx.fillRect(0, 0, 100, 100);
235
+ */
75
236
  get context() {
76
237
  let s = this.property('context');
77
238
  if(s) return s;
@@ -86,6 +247,35 @@ export default class jmControl extends jmProperty {
86
247
  return this.property('context', v);
87
248
  }
88
249
 
250
+ /**
251
+ * 样式对象
252
+ *
253
+ * 控件的视觉样式配置,包括:
254
+ * - fill: 填充颜色或渐变
255
+ * - stroke: 描边颜色
256
+ * - lineWidth: 线宽
257
+ * - shadow: 阴影配置
258
+ * - font: 字体(文本控件)
259
+ * - opacity: 透明度
260
+ *
261
+ * 设置新样式会触发 needUpdate。
262
+ *
263
+ * @type {Object}
264
+ *
265
+ * @example
266
+ * // 设置样式
267
+ * control.style = {
268
+ * fill: '#ff0000',
269
+ * stroke: '#000000',
270
+ * lineWidth: 2,
271
+ * shadow: {
272
+ * blur: 10,
273
+ * x: 5,
274
+ * y: 5,
275
+ * color: 'rgba(0,0,0,0.5)'
276
+ * }
277
+ * };
278
+ */
89
279
  get style() {
90
280
  let s = this.property('style');
91
281
  if(!s) s = this.property('style', {});
@@ -96,6 +286,22 @@ export default class jmControl extends jmProperty {
96
286
  return this.property('style', v);
97
287
  }
98
288
 
289
+ /**
290
+ * 是否可见
291
+ *
292
+ * 控制控件是否参与渲染和事件响应。
293
+ * 不可见的控件不会被绘制,也不会响应鼠标/触摸事件。
294
+ *
295
+ * @type {boolean}
296
+ * @default true
297
+ *
298
+ * @example
299
+ * // 隐藏控件
300
+ * control.visible = false;
301
+ *
302
+ * // 显示控件
303
+ * control.visible = true;
304
+ */
99
305
  get visible() {
100
306
  let s = this.property('visible');
101
307
  if(typeof s == 'undefined') s = this.property('visible', true);
@@ -106,6 +312,22 @@ export default class jmControl extends jmProperty {
106
312
  return this.property('visible', v);
107
313
  }
108
314
 
315
+ /**
316
+ * 是否响应交互事件
317
+ *
318
+ * 设置为 true 时,控件会响应鼠标和触摸事件。
319
+ * 设置为 false 时,事件会穿透到下层控件。
320
+ *
321
+ * @type {boolean}
322
+ * @default false
323
+ *
324
+ * @example
325
+ * // 启用交互
326
+ * control.interactive = true;
327
+ * control.bind('click', (evt) => {
328
+ * console.log('clicked!');
329
+ * });
330
+ */
109
331
  get interactive() {
110
332
  const s = this.property('interactive');
111
333
  return s;
@@ -114,6 +336,23 @@ export default class jmControl extends jmProperty {
114
336
  return this.property('interactive', v);
115
337
  }
116
338
 
339
+ /**
340
+ * 自定义命中区域
341
+ *
342
+ * 用于点击测试的自定义区域,格式为 {x, y, width, height}。
343
+ * 如果设置,点击测试会使用此区域而非实际图形边界。
344
+ *
345
+ * @type {Object|null}
346
+ *
347
+ * @example
348
+ * // 设置更大的点击区域
349
+ * control.hitArea = {
350
+ * x: -10,
351
+ * y: -10,
352
+ * width: control.width + 20,
353
+ * height: control.height + 20
354
+ * };
355
+ */
117
356
  get hitArea() {
118
357
  const s = this.property('hitArea');
119
358
  return s;
@@ -122,6 +361,26 @@ export default class jmControl extends jmProperty {
122
361
  return this.property('hitArea', v);
123
362
  }
124
363
 
364
+ /**
365
+ * 子控件列表
366
+ *
367
+ * 当前控件的所有子控件。子控件会按 zIndex 排序后绘制。
368
+ * 添加子控件时会自动建立父子关系。
369
+ *
370
+ * @type {jmList}
371
+ *
372
+ * @example
373
+ * // 添加子控件
374
+ * parent.children.add(child);
375
+ *
376
+ * // 移除子控件
377
+ * parent.children.remove(child);
378
+ *
379
+ * // 遍历子控件
380
+ * parent.children.each((i, child) => {
381
+ * console.log(child);
382
+ * });
383
+ */
125
384
  get children() {
126
385
  let s = this.property('children');
127
386
  if(!s) s = this.property('children', new jmList());
@@ -132,6 +391,21 @@ export default class jmControl extends jmProperty {
132
391
  return this.property('children', v);
133
392
  }
134
393
 
394
+ /**
395
+ * 控件宽度
396
+ *
397
+ * 可以是具体数值或百分比字符串(如 '50%')。
398
+ * 百分比会相对于父容器宽度计算。
399
+ *
400
+ * @type {number|string}
401
+ *
402
+ * @example
403
+ * // 设置固定宽度
404
+ * control.width = 100;
405
+ *
406
+ * // 设置百分比宽度
407
+ * control.width = '50%';
408
+ */
135
409
  get width() {
136
410
  let s = this.property('width');
137
411
  if(typeof s == 'undefined') s = this.property('width', 0);
@@ -142,6 +416,21 @@ export default class jmControl extends jmProperty {
142
416
  return this.property('width', v);
143
417
  }
144
418
 
419
+ /**
420
+ * 控件高度
421
+ *
422
+ * 可以是具体数值或百分比字符串(如 '50%')。
423
+ * 百分比会相对于父容器高度计算。
424
+ *
425
+ * @type {number|string}
426
+ *
427
+ * @example
428
+ * // 设置固定高度
429
+ * control.height = 100;
430
+ *
431
+ * // 设置百分比高度
432
+ * control.height = '50%';
433
+ */
145
434
  get height() {
146
435
  let s = this.property('height');
147
436
  if(typeof s == 'undefined') s = this.property('height', 0);
@@ -152,6 +441,22 @@ export default class jmControl extends jmProperty {
152
441
  return this.property('height', v);
153
442
  }
154
443
 
444
+ /**
445
+ * 层级顺序
446
+ *
447
+ * 控制控件的绘制顺序,值越大越靠上。
448
+ * 设置 zIndex 会触发子控件重新排序。
449
+ *
450
+ * @type {number}
451
+ * @default 0
452
+ *
453
+ * @example
454
+ * // 将控件置于最上层
455
+ * control.zIndex = 100;
456
+ *
457
+ * // 将控件置于最下层
458
+ * control.zIndex = -1;
459
+ */
155
460
  get zIndex() {
156
461
  let s = this.property('zIndex');
157
462
  if(!s) s = this.property('zIndex', 0);
@@ -164,6 +469,21 @@ export default class jmControl extends jmProperty {
164
469
  return v;
165
470
  }
166
471
 
472
+ /**
473
+ * 鼠标样式
474
+ *
475
+ * 鼠标悬停在控件上时显示的光标样式。
476
+ * 常用值:'default', 'pointer', 'move', 'text', 'crosshair'
477
+ *
478
+ * @type {string}
479
+ *
480
+ * @example
481
+ * // 设置为手型指针
482
+ * control.cursor = 'pointer';
483
+ *
484
+ * // 设置为移动样式
485
+ * control.cursor = 'move';
486
+ */
167
487
  set cursor(cur) {
168
488
  const graph = this.graph;
169
489
  if(graph) {
@@ -177,18 +497,35 @@ export default class jmControl extends jmProperty {
177
497
  }
178
498
  }
179
499
 
500
+ /**
501
+ * 初始化控件
502
+ *
503
+ * 在构造函数末尾调用,用于设置子控件管理逻辑。
504
+ * 重写了 children 的 add、remove、sort、clear 方法,
505
+ * 实现自动的父子关系维护和脏标记传播。
506
+ *
507
+ * @method initializing
508
+ * @protected
509
+ */
180
510
  initializing() {
181
511
 
182
512
  const self = this;
183
513
  this.children = this.children || new jmList();
184
514
  const oadd = this.children.add;
185
515
 
516
+ /**
517
+ * 重写 add 方法,自动建立父子关系
518
+ * @param {jmControl} obj - 要添加的子控件
519
+ * @returns {jmControl} 添加的子控件
520
+ */
186
521
  this.children.add = function(obj) {
187
522
  if(typeof obj === 'object') {
523
+ // 如果对象已有父级,先从原父级移除
188
524
  if(obj.parent && obj.parent != self && obj.parent.children) {
189
525
  obj.parent.children.remove(obj);
190
526
  }
191
527
  obj.parent = self;
528
+ // 如果已存在,先移除再添加
192
529
  if(this.contain(obj)) {
193
530
  this.oremove(obj);
194
531
  }
@@ -196,6 +533,7 @@ export default class jmControl extends jmProperty {
196
533
  obj.emit('add', obj);
197
534
 
198
535
  self.needUpdate = true;
536
+ // 传播 graph 引用
199
537
  if(self.graph) obj.graph = self.graph;
200
538
  this.sort();
201
539
  return obj;
@@ -203,6 +541,10 @@ export default class jmControl extends jmProperty {
203
541
  };
204
542
  this.children.oremove= this.children.remove;
205
543
 
544
+ /**
545
+ * 重写 remove 方法,清理父子关系
546
+ * @param {jmControl} obj - 要移除的子控件
547
+ */
206
548
  this.children.remove = function(obj) {
207
549
  if(typeof obj === 'object') {
208
550
  obj.parent = null;
@@ -213,6 +555,9 @@ export default class jmControl extends jmProperty {
213
555
  }
214
556
  };
215
557
 
558
+ /**
559
+ * 按 zIndex 排序子控件
560
+ */
216
561
  this.children.sort = function() {
217
562
  const levelItems = {};
218
563
  this.each(function(i, obj) {
@@ -232,6 +577,10 @@ export default class jmControl extends jmProperty {
232
577
  oadd.call(this, levelItems[index]);
233
578
  }
234
579
  }
580
+
581
+ /**
582
+ * 清空所有子控件
583
+ */
235
584
  this.children.clear = function() {
236
585
  this.each(function(i,obj) {
237
586
  this.remove(obj);
@@ -240,15 +589,55 @@ export default class jmControl extends jmProperty {
240
589
  this.needUpdate = true;
241
590
  }
242
591
 
592
+ /**
593
+ * 设置控件样式到绘图上下文
594
+ *
595
+ * 将样式对象应用到 Canvas 上下文,支持:
596
+ * - 基础样式:fill, stroke, lineWidth, opacity 等
597
+ * - 阴影效果:shadow.blur, shadow.x, shadow.y, shadow.color
598
+ * - 渐变填充:支持线性渐变和径向渐变
599
+ * - 变换效果:rotation(旋转)、translate(平移)、transform(矩阵变换)
600
+ * - 高级效果:lineDash(虚线)、filter(滤镜)、clipPath(裁剪)、mask(遮罩)
601
+ *
602
+ * @method setStyle
603
+ * @param {Object} [style] - 要应用的样式对象,默认使用 this.style
604
+ *
605
+ * @example
606
+ * // 应用样式
607
+ * control.setStyle({
608
+ * fill: '#ff0000',
609
+ * stroke: '#000000',
610
+ * lineWidth: 2,
611
+ * shadow: {
612
+ * blur: 10,
613
+ * x: 5,
614
+ * y: 5,
615
+ * color: 'rgba(0,0,0,0.5)'
616
+ * }
617
+ * });
618
+ *
619
+ * // 使用渐变
620
+ * control.setStyle({
621
+ * fill: 'linear-gradient(0,0,100,0,#ff0000,#0000ff)'
622
+ * });
623
+ */
243
624
  setStyle(style) {
244
625
  if(!style) {
245
626
  style = this.style;
246
627
  }
247
628
  if(!style) return;
248
629
 
630
+ /**
631
+ * 内部样式设置函数
632
+ * @param {*} styleValue - 样式值
633
+ * @param {string} name - 样式名称
634
+ * @param {string} [mpkey] - 映射键名
635
+ * @private
636
+ */
249
637
  const __setStyle = (style, name, mpkey) => {
250
638
  if(style) {
251
639
  let styleValue = style;
640
+ // 支持函数形式的样式值
252
641
  if(typeof styleValue === 'function') {
253
642
  try {
254
643
  styleValue = styleValue.call(this);
@@ -261,12 +650,14 @@ export default class jmControl extends jmProperty {
261
650
  let t = typeof styleValue;
262
651
  let mpname = jmStyleMap[mpkey || name];
263
652
 
653
+ // 处理渐变
264
654
  if((styleValue instanceof jmGradient) || (t == 'string' && styleValue.indexOf('-gradient') > -1)) {
265
655
  if(t == 'string' && styleValue.indexOf('-gradient') > -1) {
266
656
  styleValue = new jmGradient(styleValue);
267
657
  }
268
658
  __setStyle(styleValue.toGradient(this), mpname||name);
269
659
  }
660
+ // 处理标准样式映射
270
661
  else if(mpname) {
271
662
  if(this.webglControl) {
272
663
  this.webglControl.setStyle(mpname, styleValue);
@@ -278,8 +669,10 @@ export default class jmControl extends jmProperty {
278
669
  this.context[mpname] = styleValue;
279
670
  }
280
671
  }
672
+ // 处理特殊样式
281
673
  else {
282
674
  switch(name) {
675
+ // 阴影样式
283
676
  case 'shadow' : {
284
677
  if(t == 'string') {
285
678
  __setStyle(new jmShadow(styleValue), name);
@@ -290,9 +683,11 @@ export default class jmControl extends jmProperty {
290
683
  }
291
684
  break;
292
685
  }
686
+ // 平移变换
293
687
  case 'translate' : {
294
688
  break;
295
689
  }
690
+ // 旋转变换
296
691
  case 'rotation' : {
297
692
  if(typeof styleValue.angle === 'undefined' || isNaN(styleValue.angle)) break;
298
693
  styleValue = this.getRotation(styleValue);
@@ -306,6 +701,7 @@ export default class jmControl extends jmProperty {
306
701
  this.context.translate && this.context.translate(-this.__translateAbsolutePosition.x, -this.__translateAbsolutePosition.y);
307
702
  break;
308
703
  }
704
+ // 矩阵变换
309
705
  case 'transform' : {
310
706
  if(!this.context.transform) break;
311
707
  if(Array.isArray(styleValue)) {
@@ -323,13 +719,19 @@ export default class jmControl extends jmProperty {
323
719
  }
324
720
  break;
325
721
  }
722
+ // 鼠标样式
326
723
  case 'cursor' : {
327
724
  this.cursor = styleValue;
328
725
  break;
329
726
  }
330
727
  // ===== 新增样式特性 =====
331
728
 
332
- // 虚线样式:支持自定义lineDash模式 (如 [5, 3, 2] 或 "5,3,2")
729
+ /**
730
+ * 虚线样式
731
+ * 支持数组格式 [5, 3, 2] 或字符串格式 "5,3,2"
732
+ * @example
733
+ * style: { lineDash: [5, 3] } // 5px实线,3px空白
734
+ */
333
735
  case 'lineDash' : {
334
736
  if(!this.context.setLineDash) break;
335
737
  let dash;
@@ -353,7 +755,14 @@ export default class jmControl extends jmProperty {
353
755
  this.context.lineDashOffset = Number(styleValue) || 0;
354
756
  break;
355
757
  }
356
- // CSS滤镜效果 (blur, grayscale, sepia, brightness, contrast, saturate, hue-rotate, invert, opacity)
758
+ /**
759
+ * CSS滤镜效果
760
+ * 支持 blur, grayscale, sepia, brightness, contrast, saturate, hue-rotate, invert, opacity
761
+ * @example
762
+ * style: { filter: 'blur(5px) grayscale(50%)' }
763
+ * // 或使用对象
764
+ * style: { filter: { blur: 5, grayscale: 0.5 } }
765
+ */
357
766
  case 'filter' : {
358
767
  if(this.context.filter === undefined) break;
359
768
  if(styleValue instanceof jmFilter) {
@@ -367,13 +776,23 @@ export default class jmControl extends jmProperty {
367
776
  }
368
777
  break;
369
778
  }
370
- // 混合模式 (source-over, multiply, screen, overlay, darken, lighten, etc.)
779
+ /**
780
+ * 混合模式
781
+ * 常用值:source-over, multiply, screen, overlay, darken, lighten
782
+ * @example
783
+ * style: { globalCompositeOperation: 'multiply' }
784
+ */
371
785
  case 'globalCompositeOperation' : {
372
786
  if(!this.context.globalCompositeOperation) break;
373
787
  this.context.globalCompositeOperation = styleValue;
374
788
  break;
375
789
  }
376
- // 裁剪路径:通过canvas clip实现
790
+ /**
791
+ * 裁剪路径
792
+ * 通过 canvas clip 实现裁剪效果
793
+ * @example
794
+ * style: { clipPath: clipShape } // clipShape 是一个图形控件
795
+ */
377
796
  case 'clipPath' : {
378
797
  if(!this.context.clip) break;
379
798
  // clipPath可以是一个图形控件实例
@@ -396,7 +815,12 @@ export default class jmControl extends jmProperty {
396
815
  }
397
816
  break;
398
817
  }
399
- // 遮罩效果:通过globalCompositeOperation + destination-in实现
818
+ /**
819
+ * 遮罩效果
820
+ * 通过 globalCompositeOperation + destination-in 实现
821
+ * @example
822
+ * style: { mask: maskShape } // maskShape 是一个图形控件
823
+ */
400
824
  case 'mask' : {
401
825
  if(!this.context.globalCompositeOperation) break;
402
826
  // mask是一个图形控件实例,在绘制前需要先应用mask
@@ -404,7 +828,7 @@ export default class jmControl extends jmProperty {
404
828
  this.__mask = styleValue;
405
829
  break;
406
830
  }
407
- // 图片阴影描边阴影(WebGL纹理canvas用)
831
+ // 阴影相关样式(WebGL兼容)
408
832
  case 'shadowColor' : {
409
833
  if(this.webglControl) {
410
834
  this.webglControl.setStyle('shadowColor', styleValue);
@@ -446,21 +870,27 @@ export default class jmControl extends jmProperty {
446
870
  }
447
871
  }
448
872
 
873
+ // 应用平移变换
449
874
  if(this.translate) {
450
875
  __setStyle(this.translate, 'translate');
451
876
  }
877
+ // 应用矩阵变换
452
878
  if(this.transform) {
453
879
  __setStyle(this.transform, 'transform');
454
880
  }
881
+ // 遍历应用所有样式
455
882
  for(let k in style) {
456
883
  if(k === 'constructor') continue;
457
884
  let t = typeof style[k];
885
+ // 自动转换渐变字符串
458
886
  if(t == 'string' && style[k].indexOf('-gradient') > -1) {
459
887
  style[k] = new jmGradient(style[k]);
460
888
  }
889
+ // 自动转换阴影字符串
461
890
  else if(t == 'string' && k == 'shadow') {
462
891
  style[k] = new jmShadow(style[k]);
463
892
  }
893
+ // 自动转换滤镜字符串
464
894
  else if(t == 'string' && k == 'filter') {
465
895
  style[k] = new jmFilter(style[k]);
466
896
  }
@@ -469,13 +899,29 @@ export default class jmControl extends jmProperty {
469
899
  }
470
900
 
471
901
  /**
472
- * 获取当前控件的边界
473
- * 通过分析控件的描点或位置加宽高得到为方形的边界
474
- *
902
+ * 获取当前控件的边界矩形
903
+ *
904
+ * 通过分析控件的描点或位置加宽高得到边界矩形。
905
+ * 对于 jmGraph,边界为画布尺寸。
906
+ * 对于有 points 的控件,边界为所有点的最小包围矩形。
907
+ *
475
908
  * @method getBounds
476
- * @for jmControl
477
- * @param {boolean} [isReset=false] 是否强制重新计算
478
- * @return {object} 控件的边界描述对象(left,top,right,bottom,width,height)
909
+ * @param {boolean} [isReset=false] - 是否强制重新计算(忽略缓存)
910
+ * @returns {Object} 边界对象
911
+ * @returns {number} returns.left - 左边界 X 坐标
912
+ * @returns {number} returns.top - 上边界 Y 坐标
913
+ * @returns {number} returns.right - 右边界 X 坐标
914
+ * @returns {number} returns.bottom - 下边界 Y 坐标
915
+ * @returns {number} returns.width - 宽度
916
+ * @returns {number} returns.height - 高度
917
+ *
918
+ * @example
919
+ * // 获取边界
920
+ * const bounds = control.getBounds();
921
+ * console.log(`宽: ${bounds.width}, 高: ${bounds.height}`);
922
+ *
923
+ * // 强制重新计算
924
+ * const newBounds = control.getBounds(true);
479
925
  */
480
926
  getBounds(isReset) {
481
927
  //如果当次计算过,则不重复计算
@@ -498,6 +944,7 @@ export default class jmControl extends jmProperty {
498
944
  rect.bottom = this.height;
499
945
  }
500
946
  }
947
+ // 根据 points 计算边界
501
948
  else if(this.points && this.points.length > 0) {
502
949
  for(const p of this.points) {
503
950
  if(typeof rect.left === 'undefined' || rect.left > p.x) {
@@ -515,6 +962,7 @@ export default class jmControl extends jmProperty {
515
962
  }
516
963
  }
517
964
  }
965
+ // 根据位置和尺寸计算边界
518
966
  else if(this.getLocation) {
519
967
  let p = this.getLocation();
520
968
  if(p) {
@@ -524,10 +972,10 @@ export default class jmControl extends jmProperty {
524
972
  rect.bottom = p.top + p.height;
525
973
  }
526
974
  }
527
- if(!rect.left) rect.left = 0;
528
- if(!rect.top) rect.top = 0;
529
- if(!rect.right) rect.right = 0;
530
- if(!rect.bottom) rect.bottom = 0;
975
+ if(rect.left === undefined) rect.left = 0;
976
+ if(rect.top === undefined) rect.top = 0;
977
+ if(rect.right === undefined) rect.right = 0;
978
+ if(rect.bottom === undefined) rect.bottom = 0;
531
979
  rect.width = rect.right - rect.left;
532
980
  rect.height = rect.bottom - rect.top;
533
981
 
@@ -535,7 +983,23 @@ export default class jmControl extends jmProperty {
535
983
  }
536
984
 
537
985
  /**
538
- * 获取被旋转后的边界
986
+ * 获取旋转后的边界矩形
987
+ *
988
+ * 计算控件旋转后的最小包围矩形。
989
+ * 当控件有旋转变换时,实际占据的空间会发生变化。
990
+ *
991
+ * @method getRotationBounds
992
+ * @param {Object} [rotation] - 旋转参数,默认使用 style.rotation
993
+ * @param {number} rotation.x - 旋转中心 X(相对于控件)
994
+ * @param {number} rotation.y - 旋转中心 Y(相对于控件)
995
+ * @param {number} rotation.angle - 旋转角度(弧度)
996
+ * @param {Object} [bounds] - 基础边界,默认使用 getBounds()
997
+ * @returns {Object} 旋转后的边界对象
998
+ *
999
+ * @example
1000
+ * // 获取旋转后的边界
1001
+ * const bounds = control.getRotationBounds();
1002
+ * console.log(`旋转后宽度: ${bounds.width}`);
539
1003
  */
540
1004
  getRotationBounds(rotation=null) {
541
1005
  rotation = rotation || this.getRotation();
@@ -602,11 +1066,26 @@ export default class jmControl extends jmProperty {
602
1066
  }
603
1067
 
604
1068
  /**
605
- * 获取当前控件的位置相关参数
606
- * 解析百分比和margin参数
607
- *
1069
+ * 获取当前控件的位置参数
1070
+ *
1071
+ * 解析百分比和 margin 参数,返回标准化的位置信息。
1072
+ * 支持百分比定位(如 '50%')和 margin 偏移。
1073
+ *
608
1074
  * @method getLocation
609
- * @return {object} 当前控件位置参数,包括中心点坐标,右上角坐标,宽高
1075
+ * @returns {Object} 位置参数对象
1076
+ * @returns {number} returns.left - 左边距
1077
+ * @returns {number} returns.top - 上边距
1078
+ * @returns {number} returns.width - 宽度
1079
+ * @returns {number} returns.height - 高度
1080
+ * @returns {Object} [returns.position] - 位置对象 {x, y}
1081
+ * @returns {Object} [returns.center] - 中心点
1082
+ * @returns {Object} [returns.start] - 起点(线条类)
1083
+ * @returns {Object} [returns.end] - 终点(线条类)
1084
+ * @returns {number} [returns.radius] - 半径(圆形类)
1085
+ *
1086
+ * @example
1087
+ * const loc = control.getLocation();
1088
+ * console.log(`位置: (${loc.left}, ${loc.top})`);
610
1089
  */
611
1090
  getLocation() {
612
1091
  //如果已经计算过则直接返回
@@ -682,8 +1161,26 @@ export default class jmControl extends jmProperty {
682
1161
  }
683
1162
 
684
1163
  /**
685
- * 获取当前控制的旋转信息
686
- * @returns {object} 旋转中心和角度
1164
+ * 获取当前控件的旋转信息
1165
+ *
1166
+ * 解析旋转参数,支持百分比形式的旋转中心。
1167
+ * 如果控件本身没有旋转,会继承父级的旋转。
1168
+ *
1169
+ * @method getRotation
1170
+ * @param {Object} [rotation] - 旋转参数,默认使用 style.rotation
1171
+ * @param {Object} [bounds] - 基础边界
1172
+ * @returns {Object} 旋转信息
1173
+ * @returns {number} returns.x - 旋转中心 X(相对于控件)
1174
+ * @returns {number} returns.y - 旋转中心 Y(相对于控件)
1175
+ * @returns {number} returns.angle - 旋转角度(弧度)
1176
+ * @returns {Object} returns.bounds - 控件边界
1177
+ *
1178
+ * @example
1179
+ * // 获取旋转信息
1180
+ * const rot = control.getRotation();
1181
+ * if(rot.angle) {
1182
+ * console.log(`旋转角度: ${rot.angle} 弧度`);
1183
+ * }
687
1184
  */
688
1185
  getRotation(rotation, bounds = null) {
689
1186
  rotation = rotation || jmUtils.clone(this.style.rotation);
@@ -716,7 +1213,20 @@ export default class jmControl extends jmProperty {
716
1213
 
717
1214
  }
718
1215
 
719
- // 计算位移偏移量
1216
+ /**
1217
+ * 计算位移偏移量
1218
+ *
1219
+ * 解析 translate 样式,支持百分比形式。
1220
+ *
1221
+ * @method getTranslate
1222
+ * @param {Object} [translate] - 平移参数,默认使用 style.translate
1223
+ * @param {Object} [bounds] - 参考边界
1224
+ * @returns {Object} 平移信息 {x, y}
1225
+ *
1226
+ * @example
1227
+ * const trans = control.getTranslate();
1228
+ * console.log(`平移: (${trans.x}, ${trans.y})`);
1229
+ */
720
1230
  getTranslate(translate, bounds = null) {
721
1231
  translate = translate || this.style.translate;
722
1232
  if(!translate) return {x: 0, y: 0};
@@ -738,9 +1248,15 @@ export default class jmControl extends jmProperty {
738
1248
 
739
1249
  /**
740
1250
  * 移除当前控件
741
- * 如果是VML元素,则调用其删除元素
742
- *
743
- * @method remove
1251
+ *
1252
+ * 从父控件的子控件列表中移除当前控件。
1253
+ * 移除后会触发 needUpdate 重绘。
1254
+ *
1255
+ * @method remove
1256
+ *
1257
+ * @example
1258
+ * // 移除控件
1259
+ * control.remove();
744
1260
  */
745
1261
  remove() {
746
1262
  if(this.parent) {
@@ -750,13 +1266,19 @@ export default class jmControl extends jmProperty {
750
1266
 
751
1267
  /**
752
1268
  * 对控件进行平移
1269
+ *
753
1270
  * 遍历控件所有描点或位置,设置其偏移量。
754
- *
1271
+ * 支持移动 position、center、start、end、points 等属性。
1272
+ *
755
1273
  * @method offset
756
- * @param {number} x x轴偏移量
757
- * @param {number} y y轴偏移量
758
- * @param {boolean} [trans] 是否传递,监听者可以通过此属性是否决定是否响应移动事件,默认=true
759
- * @param {object} [evt] 如果是事件触发,则传递move事件参数
1274
+ * @param {number} x - X 轴偏移量
1275
+ * @param {number} y - Y 轴偏移量
1276
+ * @param {boolean} [trans=true] - 是否传递给监听者
1277
+ * @param {Object} [evt] - 如果是事件触发,传递 move 事件参数
1278
+ *
1279
+ * @example
1280
+ * // 向右移动 10px,向下移动 5px
1281
+ * control.offset(10, 5);
760
1282
  */
761
1283
  offset(x, y, trans, evt) {
762
1284
  trans = trans === false?false:true;
@@ -824,11 +1346,17 @@ export default class jmControl extends jmProperty {
824
1346
  }
825
1347
 
826
1348
  /**
827
- * 获取控件相对于画布的绝对边界,
828
- * 与getBounds不同的是:getBounds获取的是相对于父容器的边界.
829
- *
1349
+ * 获取控件相对于画布的绝对边界
1350
+ *
1351
+ * 与 getBounds 不同的是:getBounds 获取的是相对于父容器的边界,
1352
+ * 而 getAbsoluteBounds 获取的是相对于画布的边界。
1353
+ *
830
1354
  * @method getAbsoluteBounds
831
- * @return {object} 边界对象(left,top,right,bottom,width,height)
1355
+ * @returns {Object} 绝对边界对象
1356
+ *
1357
+ * @example
1358
+ * const absBounds = control.getAbsoluteBounds();
1359
+ * console.log(`画布上的位置: (${absBounds.left}, ${absBounds.top})`);
832
1360
  */
833
1361
  getAbsoluteBounds() {
834
1362
  //当前控件的边界,
@@ -850,10 +1378,14 @@ export default class jmControl extends jmProperty {
850
1378
  }
851
1379
 
852
1380
  /**
853
- * 把当前控制内部坐标转为canvas绝对定位坐标
1381
+ * 把当前控件内部坐标转为画布绝对坐标
854
1382
  *
855
1383
  * @method toAbsolutePoint
856
- * @param {x: number, y: number} 内部坐标
1384
+ * @param {Object} point - 内部坐标 {x, y}
1385
+ * @returns {Object} 绝对坐标
1386
+ *
1387
+ * @example
1388
+ * const absPoint = control.toAbsolutePoint({x: 10, y: 10});
857
1389
  */
858
1390
  toAbsolutePoint(point) {
859
1391
  if(point.x || point.y) {
@@ -866,8 +1398,14 @@ export default class jmControl extends jmProperty {
866
1398
  }
867
1399
 
868
1400
  /**
869
- * 把绝对定位坐标转为当前控件坐标系内
870
- * @param {*} point
1401
+ * 把画布绝对坐标转为当前控件坐标系内
1402
+ *
1403
+ * @method toLocalPosition
1404
+ * @param {Object} point - 绝对坐标
1405
+ * @returns {Object|false} 相对坐标,如果无法转换返回 false
1406
+ *
1407
+ * @example
1408
+ * const localPoint = control.toLocalPosition({x: 100, y: 100});
871
1409
  */
872
1410
  toLocalPosition(point) {
873
1411
 
@@ -881,9 +1419,21 @@ export default class jmControl extends jmProperty {
881
1419
 
882
1420
  /**
883
1421
  * 画控件前初始化
884
- * 执行beginPath开始控件的绘制
1422
+ *
1423
+ * 执行 beginPath 开始控件的绘制路径。
1424
+ * 重置位置信息缓存,确保使用最新的位置数据。
885
1425
  *
886
1426
  * @method beginDraw
1427
+ * @protected
1428
+ *
1429
+ * @example
1430
+ * // 子类重写时需要调用父类方法
1431
+ * class MyShape extends jmControl {
1432
+ * beginDraw() {
1433
+ * super.beginDraw();
1434
+ * // 自定义初始化逻辑
1435
+ * }
1436
+ * }
887
1437
  */
888
1438
  beginDraw() {
889
1439
  this.getLocation(true);//重置位置信息
@@ -893,8 +1443,18 @@ export default class jmControl extends jmProperty {
893
1443
 
894
1444
  /**
895
1445
  * 结束控件绘制
896
- *
1446
+ *
1447
+ * 根据样式执行 fill 或 stroke 操作。
1448
+ * 如果设置了 close 样式,会先闭合路径。
1449
+ *
897
1450
  * @method endDraw
1451
+ * @protected
1452
+ *
1453
+ * @example
1454
+ * // 绘制流程
1455
+ * control.beginDraw();
1456
+ * control.draw();
1457
+ * control.endDraw();
898
1458
  */
899
1459
  endDraw() {
900
1460
  //如果当前为封闭路径
@@ -931,10 +1491,24 @@ export default class jmControl extends jmProperty {
931
1491
  }
932
1492
 
933
1493
  /**
934
- * 绘制控件
935
- * 在画布上描点
1494
+ * 绘制控件路径
1495
+ *
1496
+ * 在画布上绘制控件的路径点。
1497
+ * 子类应该重写此方法实现自定义绘制逻辑。
936
1498
  *
937
1499
  * @method draw
1500
+ * @protected
1501
+ *
1502
+ * @example
1503
+ * // 子类重写绘制方法
1504
+ * class MyShape extends jmControl {
1505
+ * draw() {
1506
+ * const ctx = this.context;
1507
+ * ctx.moveTo(0, 0);
1508
+ * ctx.lineTo(100, 100);
1509
+ * // ... 更多绘制逻辑
1510
+ * }
1511
+ * }
938
1512
  */
939
1513
  draw() {
940
1514
  if(this.points && this.points.length > 0) {
@@ -962,10 +1536,23 @@ export default class jmControl extends jmProperty {
962
1536
  }
963
1537
 
964
1538
  /**
965
- * 绘制当前控件
966
- * 协调控件的绘制,先从其子控件开始绘制,再往上冒。
967
- *
1539
+ * 绘制当前控件及其子控件
1540
+ *
1541
+ * 协调控件的绘制流程:
1542
+ * 1. 检查可见性
1543
+ * 2. 初始化点数据
1544
+ * 3. 计算边界
1545
+ * 4. 应用样式
1546
+ * 5. 绘制自身
1547
+ * 6. 绘制子控件
1548
+ * 7. 触发事件
1549
+ *
968
1550
  * @method paint
1551
+ * @param {boolean} [v] - 是否可见,false 时跳过绘制
1552
+ *
1553
+ * @example
1554
+ * // 手动触发重绘
1555
+ * control.paint();
969
1556
  */
970
1557
  paint(v) {
971
1558
  if(v !== false && this.visible !== false) {
@@ -1040,23 +1627,50 @@ export default class jmControl extends jmProperty {
1040
1627
  }
1041
1628
 
1042
1629
  /**
1043
- * 获取指定事件的集合
1044
- * 比如mousedown,mouseup等
1045
- *
1630
+ * 获取指定事件的监听器集合
1631
+ *
1632
+ * 返回绑定到指定事件名称的所有事件处理函数。
1633
+ *
1046
1634
  * @method getEvent
1047
- * @param {string} name 事件名称
1048
- * @return {list} 事件委托的集合
1635
+ * @param {string} name - 事件名称(如 'click', 'mousedown')
1636
+ * @returns {jmList|null} 事件处理函数集合,不存在则返回 null
1637
+ *
1638
+ * @example
1639
+ * const handlers = control.getEvent('click');
1640
+ * if(handlers) {
1641
+ * console.log(`有 ${handlers.count()} 个点击事件处理器`);
1642
+ * }
1049
1643
  */
1050
1644
  getEvent(name) {
1051
1645
  return this.__events?this.__events[name]:null;
1052
1646
  }
1053
1647
 
1054
1648
  /**
1055
- * 绑定控件的事件
1056
- *
1649
+ * 绑定控件事件
1650
+ *
1651
+ * 为控件添加事件监听器。支持同时绑定多个事件(用空格分隔)。
1652
+ * 同一个处理函数不会被重复添加。
1653
+ *
1057
1654
  * @method bind
1058
- * @param {string} name 事件名称
1059
- * @param {function} handle 事件委托
1655
+ * @param {string} name - 事件名称,多个事件用空格分隔
1656
+ * @param {Function} handle - 事件处理函数
1657
+ * @returns {void}
1658
+ *
1659
+ * @example
1660
+ * // 绑定单个事件
1661
+ * control.bind('click', (evt) => {
1662
+ * console.log('被点击了', evt);
1663
+ * });
1664
+ *
1665
+ * // 绑定多个事件
1666
+ * control.bind('mousedown mouseup', (evt) => {
1667
+ * console.log('鼠标事件', evt);
1668
+ * });
1669
+ *
1670
+ * // 使用 on 别名
1671
+ * control.on('mousemove', (evt) => {
1672
+ * console.log('鼠标移动', evt.position);
1673
+ * });
1060
1674
  */
1061
1675
  bind(name, handle) {
1062
1676
  if(name && name.indexOf(' ') > -1) {
@@ -1083,11 +1697,23 @@ export default class jmControl extends jmProperty {
1083
1697
  }
1084
1698
 
1085
1699
  /**
1086
- * 移除控件的事件
1087
- *
1088
- * @method unbind
1089
- * @param {string} name 事件名称
1090
- * @param {function} handle 从控件中移除事件的委托
1700
+ * 移除控件事件
1701
+ *
1702
+ * 移除已绑定的事件处理函数。如果不指定处理函数,则移除该事件的所有处理函数。
1703
+ *
1704
+ * @method unbind
1705
+ * @param {string} name - 事件名称,多个事件用空格分隔
1706
+ * @param {Function} [handle] - 要移除的事件处理函数,不指定则移除所有
1707
+ *
1708
+ * @example
1709
+ * // 移除特定处理函数
1710
+ * control.unbind('click', myHandler);
1711
+ *
1712
+ * // 移除所有点击事件
1713
+ * control.unbind('click');
1714
+ *
1715
+ * // 移除多个事件
1716
+ * control.unbind('mousedown mouseup');
1091
1717
  */
1092
1718
  unbind(name, handle) {
1093
1719
  if(name && name.indexOf(' ') > -1) {
@@ -1106,12 +1732,22 @@ export default class jmControl extends jmProperty {
1106
1732
 
1107
1733
 
1108
1734
  /**
1109
- * 执行监听回调
1735
+ * 触发事件
1736
+ *
1737
+ * 执行指定事件的所有监听器。
1738
+ * 支持传递多个参数给事件处理函数。
1110
1739
  *
1111
1740
  * @method emit
1112
- * @for jmControl
1113
- * @param {string} name 触发事件的名称
1114
- * @param {array} args 事件参数数组
1741
+ * @param {string} name - 事件名称
1742
+ * @param {...*} args - 传递给事件处理函数的参数
1743
+ * @returns {jmControl} 返回 this 以支持链式调用
1744
+ *
1745
+ * @example
1746
+ * // 触发自定义事件
1747
+ * control.emit('customEvent', { data: 'value' });
1748
+ *
1749
+ * // 触发带多个参数的事件
1750
+ * control.emit('dataChange', oldValue, newValue);
1115
1751
  */
1116
1752
  emit(...args) {
1117
1753
  // 避免每帧 args.slice(1) 分配临时数组
@@ -1127,15 +1763,21 @@ export default class jmControl extends jmProperty {
1127
1763
  }
1128
1764
 
1129
1765
  /**
1130
- * 独立执行事件委托
1131
- *
1766
+ * 执行事件处理函数
1767
+ *
1768
+ * 内部方法,用于执行指定事件的所有监听器。
1769
+ * 如果任一处理函数返回 false,会设置 args.cancel = true。
1770
+ *
1132
1771
  * @method runEventHandle
1133
- * @param {string} 将执行的事件名称
1134
- * @param {object} 事件执行的参数,包括触发事件的对象和位置
1772
+ * @param {string} name - 事件名称
1773
+ * @param {Array|Object} args - 事件参数
1774
+ * @returns {boolean} 是否被取消
1775
+ * @protected
1135
1776
  */
1136
1777
  runEventHandle(name, args) {
1137
1778
  let events = this.getEvent(name);
1138
1779
  if(events) {
1780
+
1139
1781
  var self = this;
1140
1782
  if(!Array.isArray(args)) args = [args];
1141
1783
  events.each(function(i, handle) {
@@ -1149,25 +1791,35 @@ export default class jmControl extends jmProperty {
1149
1791
  }
1150
1792
 
1151
1793
  /**
1152
- * 检 查坐标是否落在当前控件区域中..true=在区域内
1153
- *
1794
+ * 检查坐标是否落在当前控件区域中
1795
+ *
1796
+ * 用于点击测试和碰撞检测。
1797
+ * 支持旋转后的碰撞检测,以及自定义命中区域。
1798
+ *
1154
1799
  * @method checkPoint
1155
- * @param {point} p 位置参数
1156
- * @param {number} [pad] 可选参数,表示线条多远内都算在线上
1157
- * @return {boolean} 当前位置如果在区域内则为true,否则为false。
1800
+ * @param {Object} p - 要检测的点坐标
1801
+ * @param {number} p.x - X 坐标
1802
+ * @param {number} p.y - Y 坐标
1803
+ * @param {number} [pad] - 容差范围,默认使用 lineWidth 或 1
1804
+ * @returns {boolean} 点是否在控件区域内
1805
+ *
1806
+ * @example
1807
+ * // 检查点击位置
1808
+ * graph.bind('click', (evt) => {
1809
+ * if(control.checkPoint(evt.position)) {
1810
+ * console.log('点击了控件');
1811
+ * }
1812
+ * });
1158
1813
  */
1159
1814
  checkPoint(p, pad) {
1160
1815
  //jmGraph 需要判断dom位置
1161
1816
  if(this.type == 'jmGraph') {
1162
1817
  //获取dom位置
1163
- let position = this.getPosition();
1164
- // 由于高清屏会有放大坐标,所以这里用pagex就只能用真实的canvas大小
1165
- const right = position.left + this.width;
1166
- const bottom = position.top + this.height;
1167
- if(p.x > right || p.x < position.left) {
1818
+ const position = this.getPosition();
1819
+ if(p.pageX > position.right || p.pageX < position.left) {
1168
1820
  return false;
1169
1821
  }
1170
- if(p.y > bottom || p.y < position.top) {
1822
+ if(p.pageY > position.bottom || p.pageY < position.top) {
1171
1823
  return false;
1172
1824
  }
1173
1825
  return true;
@@ -1254,15 +1906,23 @@ export default class jmControl extends jmProperty {
1254
1906
 
1255
1907
 
1256
1908
  /**
1257
- * 触发控件事件,组合参数并按控件层级关系执行事件冒泡。
1258
- *
1909
+ * 触发控件事件并执行事件冒泡
1910
+ *
1911
+ * 组合事件参数,按控件层级关系执行事件冒泡。
1912
+ * 事件从最上层的子控件开始触发,向上冒泡到父控件。
1913
+ *
1259
1914
  * @method raiseEvent
1260
- * @param {string} name 事件名称
1261
- * @param {object} args 事件执行参数
1262
- * @return {boolean} 如果事件被组止冒泡则返回false,否则返回true
1915
+ * @param {string} name - 事件名称
1916
+ * @param {Object} args - 原生事件对象
1917
+ * @returns {boolean} 如果事件被阻止冒泡则返回 false,否则返回 true
1918
+ *
1919
+ * @example
1920
+ * // 通常由框架内部调用,用户一般不需要直接调用
1921
+ * // 框架会自动处理鼠标/触摸事件
1263
1922
  */
1264
1923
  raiseEvent(name, args) {
1265
1924
  if(this.visible === false) return ;//如果不显示则不响应事件
1925
+
1266
1926
  if(!args.position) {
1267
1927
  const graph = this.graph;
1268
1928
  args.isWXMiniApp = graph.isWXMiniApp;
@@ -1310,6 +1970,10 @@ export default class jmControl extends jmProperty {
1310
1970
 
1311
1971
  // 是否在当前控件内操作
1312
1972
  const inpos = this.interactive !== false && this.checkPoint(args.position);
1973
+
1974
+ if(name === 'mousemove' && this.type == 'jmGraph' && !inpos) {
1975
+ console.log('mousemove out', args.position, abounds);
1976
+ }
1313
1977
 
1314
1978
  //事件发生在边界内或健盘事件发生在画布中才触发
1315
1979
  if(inpos) {
@@ -1340,9 +2004,14 @@ export default class jmControl extends jmProperty {
1340
2004
  }
1341
2005
 
1342
2006
  /**
1343
- * 执行事件,并进行冒泡
1344
- * @param {string} name 事件名称
1345
- * @param {object} args 事件参数
2007
+ * 执行事件并进行冒泡
2008
+ *
2009
+ * 内部方法,用于执行事件处理并添加到事件路径。
2010
+ *
2011
+ * @method runEventAndPopEvent
2012
+ * @param {string} name - 事件名称
2013
+ * @param {Object} args - 事件参数
2014
+ * @protected
1346
2015
  */
1347
2016
  runEventAndPopEvent(name, args) {
1348
2017
 
@@ -1368,23 +2037,38 @@ export default class jmControl extends jmProperty {
1368
2037
 
1369
2038
  /**
1370
2039
  * 清空控件指定事件
1371
- *
2040
+ *
2041
+ * 移除指定事件名称下的所有事件处理函数。
2042
+ *
1372
2043
  * @method clearEvents
1373
- * @param {string} name 需要清除的事件名称
2044
+ * @param {string} name - 需要清除的事件名称
2045
+ *
2046
+ * @example
2047
+ * // 清除所有点击事件
2048
+ * control.clearEvents('click');
1374
2049
  */
1375
2050
  clearEvents(name) {
1376
- var eventCollection = this.getEvent(name) ;
2051
+ var eventCollection = this.getEvent(name);
1377
2052
  if(eventCollection) {
1378
- eventCollection.clear;
2053
+ eventCollection.clear();
1379
2054
  }
1380
2055
  }
1381
2056
 
1382
2057
  /**
1383
- * 查找其父级类型为type的元素,直到找到指定的对象或到最顶级控件后返回空。
1384
- *
1385
- * @method findParent
1386
- * @param {object} 类型名称或类型对象
1387
- * @return {object} 指定类型的实例
2058
+ * 查找指定类型的父级控件
2059
+ *
2060
+ * 沿着父级链向上查找,直到找到指定类型的控件或到达最顶级。
2061
+ *
2062
+ * @method findParent
2063
+ * @param {string|Function} type - 类型名称(字符串)或类构造函数
2064
+ * @returns {jmControl|null} 找到的父级控件实例,未找到返回 null
2065
+ *
2066
+ * @example
2067
+ * // 查找 jmGraph 实例
2068
+ * const graph = control.findParent('jmGraph');
2069
+ *
2070
+ * // 查找特定类的实例
2071
+ * const parent = control.findParent(MyCustomControl);
1388
2072
  */
1389
2073
  findParent(type) {
1390
2074
  //如果为类型名称,则返回名称相同的类型对象
@@ -1402,12 +2086,26 @@ export default class jmControl extends jmProperty {
1402
2086
  }
1403
2087
 
1404
2088
  /**
1405
- * 设定是否可以移动
1406
- * 此方法需指定jmgraph或在控件添加到jmgraph后再调用才能生效。
1407
- *
2089
+ * 设置控件是否可拖动
2090
+ *
2091
+ * 启用或禁用控件的拖动功能。
2092
+ * 拖动时会触发 movestart、move、moveend 事件。
2093
+ *
1408
2094
  * @method canMove
1409
- * @param {boolean} m true=可以移动,false=不可移动或清除移动。
1410
- * @param {jmGraph} [graph] 当前画布,如果为空的话必需是已加入画布的控件,否则得指定画布。
2095
+ * @param {boolean} m - true 启用拖动,false 禁用拖动
2096
+ * @param {jmGraph} [graph] - 画布实例,如果控件已添加到画布可省略
2097
+ * @returns {jmControl} 返回 this 以支持链式调用
2098
+ *
2099
+ * @example
2100
+ * // 启用拖动
2101
+ * control.canMove(true);
2102
+ *
2103
+ * // 禁用拖动
2104
+ * control.canMove(false);
2105
+ *
2106
+ * // 监听拖动事件
2107
+ * control.on('movestart', (evt) => console.log('开始拖动'));
2108
+ * control.on('moveend', (evt) => console.log('结束拖动'));
1411
2109
  */
1412
2110
  canMove(m, graph) {
1413
2111
  if(!this.__mvMonitor) {
@@ -1433,7 +2131,6 @@ export default class jmControl extends jmProperty {
1433
2131
  //if(evt.path && evt.path.indexOf(_this)>-1) {
1434
2132
  // _this.cursor('move');
1435
2133
  //}
1436
-
1437
2134
  if(_this.__mvMonitor.mouseDown) {
1438
2135
  _this.parent.bounds = null;
1439
2136
  //let parentbounds = _this.parent.getAbsoluteBounds();
@@ -1467,7 +2164,7 @@ export default class jmControl extends jmProperty {
1467
2164
  _this.offset(offsetx, offsety, true, evt);
1468
2165
  if(offsetx) _this.__mvMonitor.curposition.x = evt.position.offsetX;
1469
2166
  if(offsety) _this.__mvMonitor.curposition.y = evt.position.offsetY;
1470
- //console.log(offsetx + '.' + offsety);
2167
+ //console.log('mouse move',offsetx + '.' + offsety);
1471
2168
  }
1472
2169
  return false;
1473
2170
  }