jmgraph 3.2.16 → 3.2.18

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 (77) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +251 -428
  3. package/build/gulpfile.js +142 -142
  4. package/build/package-lock.json +10666 -0
  5. package/build/package.json +71 -71
  6. package/dev.js +9 -9
  7. package/dist/jmgraph.core.min.js +1 -1
  8. package/dist/jmgraph.core.min.js.map +1 -1
  9. package/dist/jmgraph.js +3500 -2668
  10. package/dist/jmgraph.min.js +1 -1
  11. package/example/ball.html +216 -216
  12. package/example/base.html +111 -111
  13. package/example/canvas.html +53 -53
  14. package/example/cell.html +283 -283
  15. package/example/controls/arc.html +128 -128
  16. package/example/controls/arrowline.html +77 -77
  17. package/example/controls/bezier.html +298 -298
  18. package/example/controls/img.html +96 -96
  19. package/example/controls/label.html +86 -86
  20. package/example/controls/line.html +172 -172
  21. package/example/controls/prismatic.html +62 -62
  22. package/example/controls/rect.html +63 -63
  23. package/example/controls/resize.html +111 -111
  24. package/example/controls/test.html +359 -359
  25. package/example/es.html +69 -69
  26. package/example/es5module.html +62 -63
  27. package/example/heartarc.html +115 -115
  28. package/example/index.html +46 -46
  29. package/example/js/require.js +4 -4
  30. package/example/love/img/bling/bling.tps +265 -265
  31. package/example/love/img/bling.json +87 -87
  32. package/example/love/img/bling.tps +295 -295
  33. package/example/love/img/love.json +95 -95
  34. package/example/love/img/love.tps +315 -315
  35. package/example/love/img/qq/qq.tps +399 -399
  36. package/example/love/img/qq.json +242 -242
  37. package/example/love/index.html +40 -40
  38. package/example/love/js/game.js +558 -558
  39. package/example/music.html +210 -210
  40. package/example/node/test.js +137 -137
  41. package/example/pdf.html +186 -186
  42. package/example/progress.html +172 -172
  43. package/example/pso.html +147 -147
  44. package/example/sort.html +804 -815
  45. package/example/tweenjs.html +83 -83
  46. package/example/webgl.html +278 -278
  47. package/example/xfj/index.html +331 -331
  48. package/example/xfj/shake.js +48 -48
  49. package/example/xfj/testori.html +75 -75
  50. package/index.js +99 -99
  51. package/package.json +58 -56
  52. package/src/core/jmControl.js +1376 -1531
  53. package/src/core/jmEvents.js +240 -281
  54. package/src/core/jmGradient.js +231 -231
  55. package/src/core/jmGraph.js +569 -569
  56. package/src/core/jmList.js +92 -157
  57. package/src/core/jmObject.js +83 -103
  58. package/src/core/jmPath.js +35 -35
  59. package/src/core/jmProperty.js +71 -110
  60. package/src/core/jmShadow.js +65 -65
  61. package/src/core/jmUtils.js +906 -919
  62. package/src/lib/earcut.js +680 -680
  63. package/src/lib/earcut.md +73 -73
  64. package/src/lib/webgl/base.js +522 -452
  65. package/src/lib/webgl/core/buffer.js +48 -48
  66. package/src/lib/webgl/core/mapSize.js +40 -40
  67. package/src/lib/webgl/core/mapType.js +43 -43
  68. package/src/lib/webgl/core/program.js +138 -138
  69. package/src/lib/webgl/core/shader.js +13 -13
  70. package/src/lib/webgl/core/texture.js +60 -60
  71. package/src/lib/webgl/gradient.js +168 -168
  72. package/src/lib/webgl/index.js +137 -11
  73. package/src/lib/webgl/path.js +568 -561
  74. package/src/shapes/jmArrowLine.js +36 -36
  75. package/src/shapes/jmImage.js +244 -244
  76. package/src/shapes/jmLabel.js +271 -271
  77. package/src/shapes/jmResize.js +332 -330
@@ -1,1532 +1,1377 @@
1
-
2
- import {jmUtils} from "./jmUtils.js";
3
- import {jmList} from "./jmList.js";
4
- import {jmGradient} from "./jmGradient.js";
5
- import {jmShadow} from "./jmShadow.js";
6
- import {jmProperty} from "./jmProperty.js";
7
- import WebglPath from "../lib/webgl/path.js";
8
-
9
- //样式名称,也当做白名单使用
10
- 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',//线交汇处的形状,miter(默认,尖角),bevel(斜角),round(圆角)
31
- 'lineCap':'lineCap' //线条终端点,butt(默认,平),round(圆),square(方)
32
- };
33
-
34
- /**
35
- * 控件基础对象
36
- * 控件的基础属性和方法
37
- *
38
- * @class jmControl
39
- * @extends jmProperty
40
- */
41
- export default class jmControl extends jmProperty {
42
-
43
- constructor(params, t) {
44
- params = params||{};
45
- super(params);
46
- this.property('type', t || new.target.name);
47
- this.style = params && params.style ? params.style : {};
48
- //this.position = params.position || {x:0,y:0};
49
- this.width = params.width || 0;
50
- this.height = params.height || 0;
51
- this.hitArea = params.hitArea || null;
52
- //this.lockSide = params.lockSide || null;
53
-
54
- if(params.position) {
55
- this.position = params.position;
56
- }
57
-
58
- this.graph = params.graph || null;
59
- this.zIndex = params.zIndex || 0;
60
- this.interactive = typeof params.interactive == 'undefined'? false : params.interactive;
61
-
62
- // webgl模式
63
- if(this.mode === 'webgl') {
64
- this.webglControl = new WebglPath(this.graph, {
65
- style: this.style,
66
- control: this,
67
- isRegular: params.isRegular,
68
- needCut: params.needCut
69
- });
70
- }
71
-
72
- this.initializing();
73
-
74
- this.on = this.bind;
75
-
76
- this.option = params;
77
- }
78
-
79
- //# region 定义属性
80
- /**
81
- * 当前对象类型名jmRect
82
- *
83
- * @property type
84
- * @type string
85
- */
86
- get type() {
87
- return this.property('type');
88
- }
89
-
90
- /**
91
- * 当前canvas的context
92
- * @property context
93
- * @type {object}
94
- */
95
- get context() {
96
- let s = this.property('context');
97
- if(s) return s;
98
- else if(this.is('jmGraph') && this.canvas && this.canvas.getContext) {
99
- return this.context = this.canvas.getContext(this.mode || '2d');
100
- }
101
- const g = this.graph;
102
- if(g) return g.context;
103
- return g.canvas.getContext(this.mode || '2d');
104
- }
105
- set context(v) {
106
- return this.property('context', v);
107
- }
108
-
109
- /**
110
- * 样式
111
- * @property style
112
- * @type {object}
113
- */
114
- get style() {
115
- let s = this.property('style');
116
- if(!s) s = this.property('style', {});
117
- return s;
118
- }
119
- set style(v) {
120
- this.needUpdate = true;
121
- return this.property('style', v);
122
- }
123
-
124
- /**
125
- * 当前控件是否可见
126
- * @property visible
127
- * @default true
128
- * @type {boolean}
129
- */
130
- get visible() {
131
- let s = this.property('visible');
132
- if(typeof s == 'undefined') s = this.property('visible', true);
133
- return s;
134
- }
135
- set visible(v) {
136
- this.needUpdate = true;
137
- return this.property('visible', v);
138
- }
139
-
140
- /**
141
- * 当前控件是否是交互式的,如果是则会响应鼠标或touch事件。
142
- * 如果false则不会主动响应,但冒泡的事件依然会得到回调
143
- * @property interactive
144
- * @default false
145
- * @type {boolean}
146
- */
147
- get interactive() {
148
- const s = this.property('interactive');
149
- return s;
150
- }
151
- set interactive(v) {
152
- return this.property('interactive', v);
153
- }
154
-
155
- /**
156
- * 事件命中区域,如果不给定就会自动计算
157
- * 这个区域是相对于当前控件本身的,也就是说从左上角开始 {x:0,y:0}
158
- * @property hitArea
159
- * @default bounds
160
- * @type { x: number, y: number, width: number, height: number}
161
- */
162
- get hitArea() {
163
- const s = this.property('hitArea');
164
- return s;
165
- }
166
- set hitArea(v) {
167
- return this.property('hitArea', v);
168
- }
169
-
170
- /**
171
- * 当前控件的子控件集合
172
- * @property children
173
- * @type {list}
174
- */
175
- get children() {
176
- let s = this.property('children');
177
- if(!s) s = this.property('children', new jmList());
178
- return s;
179
- }
180
- set children(v) {
181
- this.needUpdate = true;
182
- return this.property('children', v);
183
- }
184
-
185
- /**
186
- * 宽度
187
- * @property width
188
- * @type {number}
189
- */
190
- get width() {
191
- let s = this.property('width');
192
- if(typeof s == 'undefined') s = this.property('width', 0);
193
- return s;
194
- }
195
- set width(v) {
196
- this.needUpdate = true;
197
- return this.property('width', v);
198
- }
199
-
200
- /**
201
- * 高度
202
- * @property height
203
- * @type {number}
204
- */
205
- get height() {
206
- let s = this.property('height');
207
- if(typeof s == 'undefined') s = this.property('height', 0);
208
- return s;
209
- }
210
- set height(v) {
211
- this.needUpdate = true;
212
- return this.property('height', v);
213
- }
214
-
215
- /**
216
- * 控件层级关系,发生改变时,需要重新调整排序
217
- * @property zIndex
218
- * @type {number}
219
- */
220
- get zIndex() {
221
- let s = this.property('zIndex');
222
- if(!s) s = this.property('zIndex', 0);
223
- return s;
224
- }
225
- set zIndex(v) {
226
- this.property('zIndex', v);
227
- this.children.sort();//层级发生改变,需要重新排序
228
- this.needUpdate = true;
229
- return v;
230
- }
231
-
232
- /**
233
- * 设置鼠标指针
234
- * css鼠标指针标识,例如:pointer,move等
235
- *
236
- * @property cursor
237
- * @type {string}
238
- */
239
- set cursor(cur) {
240
- var graph = this.graph ;
241
- if(graph) {
242
- graph.css('cursor',cur);
243
- }
244
- }
245
- get cursor() {
246
- var graph = this.graph ;
247
- if(graph) {
248
- return graph.css('cursor');
249
- }
250
- }
251
-
252
- //# end region
253
-
254
- /**
255
- * 初始化对象,设定样式,初始化子控件对象
256
- * 此方法为所有控件需调用的方法
257
- *
258
- * @method initializing
259
- * @for jmControl
260
- */
261
- initializing() {
262
-
263
- const self = this;
264
- //定义子元素集合
265
- this.children = this.children || new jmList();
266
- const oadd = this.children.add;
267
- //当把对象添加到当前控件中时,设定其父节点
268
- this.children.add = function(obj) {
269
- if(typeof obj === 'object') {
270
- if(obj.parent && obj.parent != self && obj.parent.children) {
271
- obj.parent.children.remove(obj);//如果有父节点则从其父节点中移除
272
- }
273
- obj.parent = self;
274
- //如果存在先移除
275
- if(this.contain(obj)) {
276
- this.oremove(obj);
277
- }
278
- oadd.call(this, obj);
279
- obj.emit('add', obj);
280
-
281
- self.needUpdate = true;
282
- if(self.graph) obj.graph = self.graph;
283
- this.sort();//先排序
284
- //self.emit('addChild', obj);
285
- return obj;
286
- }
287
- };
288
- this.children.oremove= this.children.remove;
289
- //当把对象从此控件中移除时,把其父节点置为空
290
- this.children.remove = function(obj) {
291
- if(typeof obj === 'object') {
292
- obj.parent = null;
293
- obj.graph = null;
294
- obj.remove(true);
295
- this.oremove(obj);
296
- self.needUpdate = true;
297
- //self.emit('removeChild', obj, index);
298
- }
299
- };
300
- /**
301
- * 根据控件zIndex排序,越大的越高
302
- */
303
- //const osort = this.children.sort;
304
- this.children.sort = function() {
305
- const levelItems = {};
306
- //提取zindex大于0的元素
307
- //为了保证0的层级不改变,只能把大于0的提出来。
308
- this.each(function(i, obj) {
309
- if(!obj) return;
310
- let zindex = obj.zIndex;
311
- if(!zindex && obj.style && obj.style.zIndex) {
312
- zindex = Number(obj.style.zIndex);
313
- if(isNaN(zindex)) zindex=obj.style.zIndex||0;
314
- }
315
- let items = levelItems[zindex] || (levelItems[zindex] = []);
316
- items.push(obj);
317
- });
318
-
319
- this.splice(0, this.length);
320
-
321
- for(let index in levelItems) {
322
- oadd.call(this, levelItems[index]);
323
- }
324
- /*
325
- osort.call(this, (c1, c2) => {
326
- let zindex1 = c1.zIndex || c1.style.zIndex || 0;
327
- let zindex2 = c2.zIndex || c2.style.zIndex || 0;
328
- return zindex1 - zindex2;
329
- });*/
330
- }
331
- this.children.clear = function() {
332
- this.each(function(i,obj) {
333
- this.remove(obj);
334
- },true);
335
- }
336
- this.needUpdate = true;
337
- }
338
-
339
- /**
340
- * 设定样式到context
341
- * 处理样式映射,转换渐变和阴影对象为标准canvas属性
342
- * 样式一览
343
- | 简化名称 | 原生名称 | 说明
344
- | :- | :- | :- |
345
- | fill | fillStyle | 用于填充绘画的颜色、渐变或模式
346
- | stroke | strokeStyle | 用于笔触的颜色、渐变或模式
347
- | shadow | 没有对应的 | 最终会解析成以下几个属性,格式:'0,0,10,#fff'或g.createShadow(0,0,20,'#000');
348
- | shadow.blur | shadowBlur | 用于阴影的模糊级别
349
- | shadow.x | shadowOffsetX | 阴影距形状的水平距离
350
- | shadow.y | shadowOffsetY | 阴影距形状的垂直距离
351
- | shadow.color | shadowColor | 阴影颜色,格式:'#000'、'#46BF86'、'rgb(255,255,255)'或'rgba(39,72,188,0.5)'
352
- | lineWidth | lineWidth | 当前的线条宽度
353
- | miterLimit | miterLimit | 最大斜接长度
354
- | font | font | 请使用下面的 fontSize 和 fontFamily
355
- | fontSize | font | 字体大小
356
- | fontFamily | font | 字体
357
- | opacity | globalAlpha | 绘图的当前 alpha 或透明值
358
- | textAlign | textAlign | 文本内容的当前对齐方式
359
- | textBaseline | textBaseline | 在绘制文本时使用的当前文本基线
360
- | lineJoin | lineJoin | 两条线相交时,所创建的拐角类型:miter(默认,尖角),bevel(斜角),round(圆角)
361
- | lineCap | lineCap | 线条的结束端点样式:butt(默认,平),round(圆),square(方)
362
- *
363
- * @method setStyle
364
- * @for jmControl
365
- * @private
366
- * @param {style} style 样式对象,如:{fill:'black',stroke:'red'}
367
- */
368
- setStyle(style) {
369
- style = style || jmUtils.clone(this.style, true);
370
- if(!style) return;
371
-
372
- /**
373
- * 样式设定
374
- *
375
- * @method __setStyle
376
- * @private
377
- * @param {jmControl} control 当前样式对应的控件对象
378
- * @param {style} style 样式
379
- * @param {string} name 样式名称
380
- * @param {string} mpkey 样式名称在映射中的key(例如:shadow.blur为模糊值)
381
- */
382
- const __setStyle = (style, name, mpkey) => {
383
-
384
- if(style) {
385
- let styleValue = style;
386
- if(typeof styleValue === 'function') {
387
- try {
388
- styleValue = styleValue.call(this);
389
- }
390
- catch(e) {
391
- console.warn(e);
392
- return;
393
- }
394
- }
395
- let t = typeof styleValue;
396
- let mpname = jmStyleMap[mpkey || name];
397
-
398
- //如果为渐变对象
399
- if((styleValue instanceof jmGradient) || (t == 'string' && styleValue.indexOf('-gradient') > -1)) {
400
- //如果是渐变,则需要转换
401
- if(t == 'string' && styleValue.indexOf('-gradient') > -1) {
402
- styleValue = new jmGradient(styleValue);
403
- }
404
- __setStyle(styleValue.toGradient(this), mpname||name);
405
- }
406
- else if(mpname) {
407
-
408
- if(this.webglControl) {
409
-
410
- this.webglControl.setStyle(mpname, styleValue);
411
- }
412
- else {
413
- //只有存在白名单中才处理
414
- //颜色转换
415
- if(t == 'string' && ['fillStyle', 'strokeStyle', 'shadowColor'].indexOf(mpname) > -1) {
416
- styleValue = jmUtils.toColor(styleValue);
417
- }
418
-
419
- this.context[mpname] = styleValue;
420
- }
421
- }
422
- else {
423
- switch(name) {
424
- //阴影样式
425
- case 'shadow' : {
426
- if(t == 'string') {
427
- __setStyle(new jmShadow(styleValue), name);
428
- break;
429
- }
430
- for(let k in styleValue) {
431
- __setStyle(styleValue[k], k, name + '.' + k);
432
- }
433
- break;
434
- }
435
- //平移
436
- case 'translate' : {
437
- break;
438
- }
439
- //旋转
440
- case 'rotation' : {
441
- if(typeof styleValue.angle === 'undefined' || isNaN(styleValue.angle)) break;
442
- styleValue = this.getRotation(styleValue);
443
-
444
- this.__translateAbsolutePosition = this.toAbsolutePoint({
445
- x: styleValue.x,
446
- y: styleValue.y
447
- });
448
- //旋转,则移位,如果有中心位则按中心旋转,否则按左上角旋转
449
- //这里只有style中的旋转才能生效,不然会导至子控件多次旋转
450
- this.context.translate && this.context.translate(this.__translateAbsolutePosition.x, this.__translateAbsolutePosition.y);
451
- this.context.rotate && this.context.rotate(styleValue.angle);
452
- this.context.translate && this.context.translate(-this.__translateAbsolutePosition.x, -this.__translateAbsolutePosition.y);
453
- break;
454
- }
455
- case 'transform' : {
456
- if(!this.context.transform) break;
457
- if(Array.isArray(styleValue)) {
458
- this.context.transform.apply(this.context, styleValue);
459
- }
460
- else if(typeof styleValue == 'object') {
461
- this.context.transform(
462
- styleValue.scaleX || 1,//水平缩放
463
- styleValue.skewX || 0,//水平倾斜
464
- styleValue.skewY || 0,//垂直倾斜
465
- styleValue.scaleY || 1,//垂直缩放
466
- styleValue.offsetX || 0,//水平位移
467
- styleValue.offsetY || 0//垂直位移
468
- );
469
- }
470
- break;
471
- }
472
- //鼠标指针
473
- case 'cursor' : {
474
- this.cursor = styleValue;
475
- break;
476
- }
477
- }
478
- }
479
- }
480
- }
481
-
482
- //一些特殊属性要先设置,否则会导致顺序不对出现错误的效果
483
- if(this.translate) {
484
- __setStyle(this.translate, 'translate');
485
- }
486
- if(this.transform) {
487
- __setStyle(this.transform, 'transform');
488
- }
489
- //设置样式
490
- for(let k in style) {
491
- if(k === 'constructor') continue;
492
- let t = typeof style[k];
493
- //先处理部分样式,以免每次都需要初始化解析
494
- if(t == 'string' && style[k].indexOf('-gradient') > -1) {
495
- style[k] = new jmGradient(style[k]);
496
- }
497
- else if(t == 'string' && k == 'shadow') {
498
- style[k] = new jmShadow(style[k]);
499
- }
500
- __setStyle(style[k], k);
501
- }
502
- }
503
-
504
- /**
505
- * 获取当前控件的边界
506
- * 通过分析控件的描点或位置加宽高得到为方形的边界
507
- *
508
- * @method getBounds
509
- * @for jmControl
510
- * @param {boolean} [isReset=false] 是否强制重新计算
511
- * @return {object} 控件的边界描述对象(left,top,right,bottom,width,height)
512
- */
513
- getBounds(isReset) {
514
- //如果当次计算过,则不重复计算
515
- if(this.bounds && !isReset) return this.bounds;
516
-
517
- const rect = {}; // left top
518
- //jmGraph,特殊处理
519
- if(this.type == 'jmGraph' && this.canvas) {
520
- if(typeof this.canvas.width === 'function') {
521
- rect.right = this.canvas.width();
522
- }
523
- else if(this.width) {
524
- rect.right = this.width;
525
- }
526
-
527
- if(typeof this.canvas.height === 'function') {
528
- rect.bottom = this.canvas.height();
529
- }
530
- else if(this.height) {
531
- rect.bottom = this.height;
532
- }
533
- }
534
- else if(this.points && this.points.length > 0) {
535
- for(const p of this.points) {
536
- if(typeof rect.left === 'undefined' || rect.left > p.x) {
537
- rect.left = p.x;
538
- }
539
- if(typeof rect.top === 'undefined' || rect.top > p.y) {
540
- rect.top = p.y;
541
- }
542
-
543
- if(typeof rect.right === 'undefined' || rect.right < p.x) {
544
- rect.right = p.x;
545
- }
546
- if(typeof rect.bottom === 'undefined' || rect.bottom < p.y) {
547
- rect.bottom = p.y;
548
- }
549
- }
550
- }
551
- else if(this.getLocation) {
552
- let p = this.getLocation();
553
- if(p) {
554
- rect.left = p.left;
555
- rect.top = p.top;
556
- rect.right = p.left + p.width;
557
- rect.bottom = p.top + p.height;
558
- }
559
- }
560
- if(!rect.left) rect.left = 0;
561
- if(!rect.top) rect.top = 0;
562
- if(!rect.right) rect.right = 0;
563
- if(!rect.bottom) rect.bottom = 0;
564
- rect.width = rect.right - rect.left;
565
- rect.height = rect.bottom - rect.top;
566
-
567
- return this.bounds=rect;
568
- }
569
-
570
- /**
571
- * 获取被旋转后的边界
572
- */
573
- getRotationBounds(rotation=null) {
574
- rotation = rotation || this.getRotation();
575
- const bounds = this.getBounds();
576
- if(!rotation || !rotation.angle) return bounds;
577
-
578
- const rect = {
579
- width: 0,
580
- height: 0,
581
- oldBounds: bounds
582
- }; // left top
583
- let points = [];
584
- if(this.points && this.points.length > 0) {
585
- points = jmUtils.clone(this.points, true); // 深度拷贝
586
- }
587
- else if(this.getLocation) {
588
- const local = this.getLocation();
589
- if(local) {
590
- points.push({
591
- x: local.left,
592
- y: local.top
593
- },{
594
- x: local.left + local.width,
595
- y: local.top
596
- },{
597
- x: local.left + local.width,
598
- y: local.top + local.height
599
- },{
600
- x: local.left,
601
- y: local.top + local.height
602
- });
603
- }
604
- }
605
- points = jmUtils.rotatePoints(points, {
606
- x: rotation.x + bounds.left,
607
- y: rotation.y + bounds.top
608
- }, rotation.angle);// 对现在点进行旋转
609
-
610
- for(const p of points) {
611
- if(typeof rect.left === 'undefined' || rect.left > p.x) {
612
- rect.left = p.x;
613
- }
614
- if(typeof rect.top === 'undefined' || rect.top > p.y) {
615
- rect.top = p.y;
616
- }
617
-
618
- if(typeof rect.right === 'undefined' || rect.right < p.x) {
619
- rect.right = p.x;
620
- }
621
- if(typeof rect.bottom === 'undefined' || rect.bottom < p.y) {
622
- rect.bottom = p.y;
623
- }
624
- }
625
-
626
- if(!rect.left) rect.left = 0;
627
- if(!rect.top) rect.top = 0;
628
- if(!rect.right) rect.right = 0;
629
- if(!rect.bottom) rect.bottom = 0;
630
-
631
- rect.width = rect.right - rect.left;
632
- rect.height = rect.bottom - rect.top;
633
-
634
- return rect;
635
- }
636
-
637
- /**
638
- * 获取当前控件的位置相关参数
639
- * 解析百分比和margin参数
640
- *
641
- * @method getLocation
642
- * @return {object} 当前控件位置参数,包括中心点坐标,右上角坐标,宽高
643
- */
644
- getLocation(clone=true) {
645
- //如果已经计算过则直接返回
646
- //在开画之前会清空此对象
647
- //if(reset !== true && this.location) return this.location;
648
-
649
- let local = this.location = {left: 0,top: 0,width: 0,height: 0};
650
- local.position = typeof this.position == 'function'? this.position(): jmUtils.clone(this.position);
651
- local.center = this.center && typeof this.center === 'function'?this.center(): jmUtils.clone(this.center);//中心
652
- local.start = this.start && typeof this.start === 'function'?this.start(): jmUtils.clone(this.start);//起点
653
- local.end = this.end && typeof this.end === 'function'?this.end(): jmUtils.clone(this.end);//起点
654
- local.radius = this.radius;//半径
655
- local.width = this.width;
656
- local.height = this.height;
657
-
658
- const margin = jmUtils.clone(this.style.margin, {});
659
- margin.left = (margin.left || 0);
660
- margin.top = (margin.top || 0);
661
- margin.right = (margin.right || 0);
662
- margin.bottom = (margin.bottom || 0);
663
-
664
- //如果没有指定位置,但指定了margin。则位置取margin偏移量
665
- if(local.position) {
666
- local.left = local.position.x;
667
- local.top = local.position.y;
668
- }
669
- else {
670
- local.left = margin.left;
671
- local.top = margin.top;
672
- }
673
-
674
- if(this.parent) {
675
- const parentBounds = this.parent.getBounds();
676
-
677
- //处理百分比参数
678
- if(jmUtils.checkPercent(local.left)) {
679
- local.left = jmUtils.percentToNumber(local.left) * parentBounds.width;
680
- }
681
- if(jmUtils.checkPercent(local.top)) {
682
- local.top = jmUtils.percentToNumber(local.top) * parentBounds.height;
683
- }
684
-
685
- //如果没有指定宽度或高度,则按百分之百计算其父宽度或高度
686
- if(jmUtils.checkPercent(local.width)) {
687
- local.width = jmUtils.percentToNumber(local.width) * parentBounds.width;
688
- }
689
- if(jmUtils.checkPercent(local.height)) {
690
- local.height = jmUtils.percentToNumber(local.height) * parentBounds.height;
691
- }
692
- //处理中心点
693
- if(local.center) {
694
- //处理百分比参数
695
- if(jmUtils.checkPercent(local.center.x)) {
696
- local.center.x = jmUtils.percentToNumber(local.center.x) * parentBounds.width;
697
- }
698
- if(jmUtils.checkPercent(local.center.y)) {
699
- local.center.y = jmUtils.percentToNumber(local.center.y) * parentBounds.height;
700
- }
701
- }
702
- if(local.radius) {
703
- //处理百分比参数
704
- if(jmUtils.checkPercent(local.radius)) {
705
- local.radius = jmUtils.percentToNumber(local.radius) * Math.min(parentBounds.width, parentBounds.height);
706
- }
707
- }
708
- }
709
- return local;
710
- }
711
-
712
- /**
713
- * 获取当前控制的旋转信息
714
- * @returns {object} 旋转中心和角度
715
- */
716
- getRotation(rotation, bounds = null) {
717
- rotation = rotation || jmUtils.clone(this.style.rotation);
718
-
719
- if(!rotation) {
720
- //如果本身没有,则可以继承父级的
721
- rotation = this.parent && this.parent.getRotation?this.parent.getRotation():null;
722
- //如果父级有旋转,则把坐标转换为当前控件区域
723
- if(rotation) {
724
- bounds = bounds || this.getBounds();
725
- rotation.x -= bounds.left;
726
- rotation.y -= bounds.top;
727
- }
728
- }
729
- else {
730
- bounds = bounds || this.getBounds();
731
- if(typeof rotation.x === 'undefined') rotation.x = '50%';
732
- if(typeof rotation.y === 'undefined') rotation.y = '50%';
733
- if(jmUtils.checkPercent(rotation.x)) {
734
- rotation.x = jmUtils.percentToNumber(rotation.x) * bounds.width;
735
- }
736
- if(jmUtils.checkPercent(rotation.y)) {
737
- rotation.y = jmUtils.percentToNumber(rotation.y) * bounds.height;
738
- }
739
- }
740
- return {
741
- ...rotation,
742
- bounds
743
- };
744
-
745
- }
746
-
747
- // 计算位移偏移量
748
- getTranslate(translate, bounds = null) {
749
- translate = translate || this.style.translate;
750
- if(!translate) return {x: 0, y: 0};
751
- const result = {
752
- x: translate.x || 0,
753
- y: translate.y || 0
754
- }
755
-
756
- if(jmUtils.checkPercent(result.x)) {
757
- if(!bounds && this.parent) bounds = this.parent.getBounds();
758
- result.x = jmUtils.percentToNumber(result.x) * bounds.width;
759
- }
760
- if(jmUtils.checkPercent(result.y)) {
761
- if(!bounds && this.parent) bounds = this.parent.getBounds();
762
- result.y = jmUtils.percentToNumber(result.y) * bounds.height;
763
- }
764
- return result;
765
- }
766
-
767
- /**
768
- * 移除当前控件
769
- * 如果是VML元素,则调用其删除元素
770
- *
771
- * @method remove
772
- */
773
- remove() {
774
- if(this.parent) {
775
- this.parent.children.remove(this);
776
- }
777
- }
778
-
779
- /**
780
- * 对控件进行平移
781
- * 遍历控件所有描点或位置,设置其偏移量。
782
- *
783
- * @method offset
784
- * @param {number} x x轴偏移量
785
- * @param {number} y y轴偏移量
786
- * @param {boolean} [trans] 是否传递,监听者可以通过此属性是否决定是否响应移动事件,默认=true
787
- * @param {object} [evt] 如果是事件触发,则传递move事件参数
788
- */
789
- offset(x, y, trans, evt) {
790
- trans = trans === false?false:true;
791
- let local = this.getLocation(true);
792
- let offseted = false;
793
-
794
- if(local.position) {
795
- local.left += x;
796
- local.top += y;
797
- // 由于local是clone出来的对象,为了保留位移,则要修改原属性
798
- this.position.x = local.left;
799
- this.position.y = local.top;
800
- offseted = true;
801
- }
802
-
803
- if(local.center) {
804
- this.center.x = local.center.x + x;
805
- this.center.y = local.center.y + y;
806
- offseted = true;
807
- }
808
-
809
- if(local.start && typeof local.start == 'object') {
810
- this.start.x = local.start.x + x;
811
- this.start.y = local.start.y + y;
812
- offseted = true;
813
- }
814
-
815
- if(local.end && typeof local.end == 'object') {
816
- this.end.x = local.end.x + x;
817
- this.end.y = local.end.y + y;
818
- offseted = true;
819
- }
820
-
821
-
822
- if(offseted == false && this.cpoints) {
823
- let p = typeof this.cpoints == 'function'?this.cpoints:this.cpoints;
824
- if(p) {
825
- let len = p.length;
826
- for(let i=0; i < len;i++) {
827
- p[i].x += x;
828
- p[i].y += y;
829
- }
830
- offseted = true;
831
- }
832
- }
833
-
834
- if(offseted == false && this.points) {
835
- let len = this.points.length;
836
- for(let i=0; i < len;i++) {
837
- this.points[i].x += x;
838
- this.points[i].y += y;
839
- }
840
- offseted = true;
841
- }
842
-
843
- //触发控件移动事件
844
- this.emit('move',{
845
- offsetX: x,
846
- offsetY: y,
847
- trans: trans,
848
- evt: evt
849
- });
850
-
851
- this.needUpdate = true;
852
- }
853
-
854
- /**
855
- * 获取控件相对于画布的绝对边界,
856
- * 与getBounds不同的是:getBounds获取的是相对于父容器的边界.
857
- *
858
- * @method getAbsoluteBounds
859
- * @return {object} 边界对象(left,top,right,bottom,width,height)
860
- */
861
- getAbsoluteBounds() {
862
- //当前控件的边界,
863
- let rec = this.getBounds();
864
- if(this.parent && this.parent.absoluteBounds) {
865
- //父容器的绝对边界
866
- let prec = this.parent.absoluteBounds || this.parent.getAbsoluteBounds();
867
-
868
- return {
869
- left : prec.left + rec.left,
870
- top : prec.top + rec.top,
871
- right : prec.left + rec.right,
872
- bottom : prec.top + rec.bottom,
873
- width : rec.width,
874
- height : rec.height
875
- };
876
- }
877
- return rec;
878
- }
879
-
880
- /**
881
- * 把当前控制内部坐标转为canvas绝对定位坐标
882
- *
883
- * @method toAbsolutePoint
884
- * @param {x: number, y: number} 内部坐标
885
- */
886
- toAbsolutePoint(point) {
887
- if(point.x || point.y) {
888
- const bounds = this.absoluteBounds?this.absoluteBounds:this.getAbsoluteBounds();
889
-
890
- point.x = (point.x||0) + bounds.left;
891
- point.y = (point.y||0) + bounds.top;
892
- }
893
- return point;
894
- }
895
-
896
- /**
897
- * 把绝对定位坐标转为当前控件坐标系内
898
- * @param {*} point
899
- */
900
- toLocalPosition(point) {
901
-
902
- const bounds = this.absoluteBounds?this.absoluteBounds:this.getAbsoluteBounds();
903
- if(!bounds) return false;
904
- return {
905
- x: point.x - bounds.left,
906
- y: point.y - bounds.top
907
- };
908
- }
909
-
910
- /**
911
- * 画控件前初始化
912
- * 执行beginPath开始控件的绘制
913
- *
914
- * @method beginDraw
915
- */
916
- beginDraw() {
917
- this.getLocation(true);//重置位置信息
918
- this.context.beginPath && this.context.beginPath();
919
- if(this.webglControl && this.webglControl.beginDraw) this.webglControl.beginDraw();
920
- }
921
-
922
- /**
923
- * 结束控件绘制
924
- *
925
- * @method endDraw
926
- */
927
- endDraw() {
928
- //如果当前为封闭路径
929
- if(this.style.close) {
930
- if(this.webglControl) this.webglControl.closePath();
931
- this.context.closePath && this.context.closePath();
932
- }
933
-
934
- const fill = this.style['fill'] || this.style['fillStyle'];
935
- if(fill) {
936
- if(this.webglControl) {
937
- const bounds = this.getBounds();
938
- this.webglControl.fill(bounds);
939
- }
940
- this.context.fill && this.context.fill();
941
- }
942
- if(this.style['stroke'] || (!fill && !this.is('jmGraph'))) {
943
- if(this.webglControl) this.webglControl.stroke();
944
- this.context.stroke && this.context.stroke();
945
- }
946
-
947
- if(this.webglControl && this.webglControl.endDraw) this.webglControl.endDraw();
948
-
949
- this.needUpdate = false;
950
- }
951
-
952
- /**
953
- * 绘制控件
954
- * 在画布上描点
955
- *
956
- * @method draw
957
- */
958
- draw() {
959
- if(this.points && this.points.length > 0) {
960
- //获取当前控件的绝对位置
961
- const bounds = this.parent && this.parent.absoluteBounds?this.parent.absoluteBounds:this.absoluteBounds;
962
- if(this.webglControl) {
963
- this.webglControl.setParentBounds(bounds);
964
- this.webglControl.draw([
965
- ...this.points
966
- ]);
967
- }
968
- else if(this.context && this.context.moveTo) {
969
- this.context.moveTo(this.points[0].x + bounds.left,this.points[0].y + bounds.top);
970
- let len = this.points.length;
971
- for(let i=1; i < len;i++) {
972
- let p = this.points[i];
973
- //移至当前坐标
974
- if(p.m) {
975
- this.context.moveTo(p.x + bounds.left,p.y + bounds.top);
976
- }
977
- else {
978
- this.context.lineTo(p.x+ bounds.left,p.y + bounds.top);
979
- }
980
- }
981
- }
982
- }
983
- }
984
-
985
- /**
986
- * 绘制当前控件
987
- * 协调控件的绘制,先从其子控件开始绘制,再往上冒。
988
- *
989
- * @method paint
990
- */
991
- paint(v) {
992
- if(v !== false && this.visible !== false) {
993
- if(this.initPoints) this.initPoints();
994
- //计算当前边界
995
- this.bounds = null;
996
- this.absoluteBounds = this.getAbsoluteBounds();
997
- let needDraw = true;//是否需要绘制
998
- if(!this.is('jmGraph') && this.graph) {
999
- if(this.absoluteBounds.left >= this.graph.width) needDraw = false;
1000
- else if(this.absoluteBounds.top >= this.graph.height) needDraw = false;
1001
- else if(this.absoluteBounds.right <= 0) needDraw = false;
1002
- else if(this.absoluteBounds.bottom <= 0) needDraw = false;
1003
- }
1004
-
1005
- this.context.save && this.context.save();
1006
-
1007
- this.emit('beginDraw', this);
1008
-
1009
- this.setStyle();//设定样式
1010
-
1011
- if(needDraw && this.beginDraw) this.beginDraw();
1012
- if(needDraw && this.draw) this.draw();
1013
- if(needDraw && this.endDraw) this.endDraw();
1014
-
1015
- if(this.children) {
1016
- this.children.each(function(i,item) {
1017
- if(item && item.paint) item.paint();
1018
- });
1019
- }
1020
-
1021
- this.emit('endDraw',this);
1022
- this.context.restore && this.context.restore();
1023
-
1024
- this.needUpdate = false;
1025
- }
1026
- }
1027
-
1028
- /**
1029
- * 获取指定事件的集合
1030
- * 比如mousedown,mouseup等
1031
- *
1032
- * @method getEvent
1033
- * @param {string} name 事件名称
1034
- * @return {list} 事件委托的集合
1035
- */
1036
- getEvent(name) {
1037
- return this.__events?this.__events[name]:null;
1038
- }
1039
-
1040
- /**
1041
- * 绑定控件的事件
1042
- *
1043
- * @method bind
1044
- * @param {string} name 事件名称
1045
- * @param {function} handle 事件委托
1046
- */
1047
- bind(name, handle) {
1048
- if(name && name.indexOf(' ') > -1) {
1049
- name = name.split(' ');
1050
- for(let n of name) {
1051
- n && this.bind(n, handle);
1052
- }
1053
- return;
1054
- }
1055
- /**
1056
- * 添加事件的集合
1057
- *
1058
- * @method _setEvent
1059
- * @private
1060
- */
1061
- function _setEvent(name, events) {
1062
- if(!this.__events) this.__events = {};
1063
- return this.__events[name] = events;
1064
- }
1065
- let eventCollection = this.getEvent(name) || _setEvent.call(this, name, new jmList());
1066
- if(!eventCollection.contain(handle)) {
1067
- eventCollection.add(handle);
1068
- }
1069
- }
1070
-
1071
- /**
1072
- * 移除控件的事件
1073
- *
1074
- * @method unbind
1075
- * @param {string} name 事件名称
1076
- * @param {function} handle 从控件中移除事件的委托
1077
- */
1078
- unbind(name, handle) {
1079
- if(name && name.indexOf(' ') > -1) {
1080
- name = name.split(' ');
1081
- for(let n of name) {
1082
- n && this.unbind(n, handle);
1083
- }
1084
- return;
1085
- }
1086
- let eventCollection = this.getEvent(name) ;
1087
- if(eventCollection) {
1088
- if(handle) eventCollection.remove(handle);
1089
- else eventCollection.clear();
1090
- }
1091
- }
1092
-
1093
-
1094
- /**
1095
- * 执行监听回调
1096
- *
1097
- * @method emit
1098
- * @for jmControl
1099
- * @param {string} name 触发事件的名称
1100
- * @param {array} args 事件参数数组
1101
- */
1102
- emit(...args) {
1103
- this.runEventHandle(args[0], args.slice(1));
1104
- return this;
1105
- }
1106
-
1107
- /**
1108
- * 独立执行事件委托
1109
- *
1110
- * @method runEventHandle
1111
- * @param {string} 将执行的事件名称
1112
- * @param {object} 事件执行的参数,包括触发事件的对象和位置
1113
- */
1114
- runEventHandle(name, args) {
1115
- let events = this.getEvent(name);
1116
- if(events) {
1117
- var self = this;
1118
- if(!Array.isArray(args)) args = [args];
1119
- events.each(function(i, handle) {
1120
- //只要有一个事件被阻止,则不再处理同级事件,并设置冒泡被阻断
1121
- if(false === handle.apply(self, args)) {
1122
- args.cancel = true;
1123
- }
1124
- });
1125
- }
1126
- return args.cancel;
1127
- }
1128
-
1129
- /**
1130
- * 查坐标是否落在当前控件区域中..true=在区域内
1131
- *
1132
- * @method checkPoint
1133
- * @param {point} p 位置参数
1134
- * @param {number} [pad] 可选参数,表示线条多远内都算在线上
1135
- * @return {boolean} 当前位置如果在区域内则为true,否则为false
1136
- */
1137
- checkPoint(p, pad) {
1138
- //jmGraph 需要判断dom位置
1139
- if(this.type == 'jmGraph') {
1140
- //获取dom位置
1141
- let position = this.getPosition();
1142
- // 由于高清屏会有放大坐标,所以这里用pagex就只能用真实的canvas大小
1143
- const right = position.left + this.width;
1144
- const bottom = position.top + this.height;
1145
- if(p.x > right || p.x < position.left) {
1146
- return false;
1147
- }
1148
- if(p.y > bottom || p.y < position.top) {
1149
- return false;
1150
- }
1151
- return true;
1152
- }
1153
-
1154
- const bounds = this.getBounds();
1155
- // 如果指定了合中区域,则以命中区域为准
1156
- if(this.hitArea) {
1157
- const hitArea = {
1158
- left: this.hitArea.x + bounds.left,
1159
- top: this.hitArea.y + bounds.top,
1160
- right: this.hitArea.width + bounds.left,
1161
- bottom: this.hitArea.height + bounds.top,
1162
- };
1163
- if(p.x > hitArea.right || p.x < hitArea.left) {
1164
- return false;
1165
- }
1166
- if(p.y > hitArea.bottom || p.y < hitArea.top) {
1167
- return false;
1168
- }
1169
- return true;
1170
- }
1171
-
1172
- let ps = this.points;
1173
- //如果不是路径组成,则采用边界做为顶点
1174
- if(!ps || !ps.length) {
1175
- ps = [];
1176
- ps.push({x: bounds.left, y: bounds.top}); //左上角
1177
- ps.push({x: bounds.right, y: bounds.top});//右上角
1178
- ps.push({x: bounds.right, y: bounds.bottom});//右下角
1179
- ps.push({x: bounds.left, y: bounds.bottom}); //左下
1180
- ps.push({x: bounds.left, y: bounds.top}); //左上角 //闭合
1181
- }
1182
- //如果有指定padding 表示接受区域加宽,命中更易
1183
- pad = Number(pad || this.style['touchPadding'] || this.style['lineWidth'] || 1);
1184
- if(ps && ps.length) {
1185
- const rotation = this.getRotation(null, bounds);//获取当前旋转参数
1186
- //如果有旋转参数,则需要转换坐标再处理
1187
- if(rotation && rotation.angle) {
1188
- ps = jmUtils.clone(ps, true);//拷贝一份数据
1189
- //rotateX ,rotateY 是相对当前控件的位置
1190
- ps = jmUtils.rotatePoints(ps, {
1191
- x: rotation.x + bounds.left,
1192
- y: rotation.y + bounds.top
1193
- }, rotation.angle || 0);
1194
- }
1195
- //如果当前路径不是实心的
1196
- //就只用判断点是否在边上即可
1197
- if(ps.length > 2 && (!this.style['fill'] || this.style['stroke'])) {
1198
- let i = 0;
1199
- const count = ps.length;
1200
- for(let j = i+1; j <= count; j = (++i + 1)) {
1201
- //如果j超出最后一个
1202
- //则当为封闭图形时跟第一点连线处理.否则直接返回false
1203
- if(j == count) {
1204
- if(this.style.close) {
1205
- const r = jmUtils.pointInPolygon(p,[ps[i],ps[0]], pad);
1206
- if(r) return true;
1207
- }
1208
- }
1209
- else {
1210
- //判断是否在点i,j连成的线上
1211
- const s = jmUtils.pointInPolygon(p,[ps[i],ps[j]], pad);
1212
- if(s) return true;
1213
- }
1214
- }
1215
- //不是封闭的图形,则直接返回
1216
- if(!this.style['fill']) return false;
1217
- }
1218
-
1219
- const r = jmUtils.pointInPolygon(p,ps, pad);
1220
- return r;
1221
- }
1222
-
1223
- if(p.x > bounds.right || p.x < bounds.left) {
1224
- return false;
1225
- }
1226
- if(p.y > bounds.bottom || p.y < bounds.top) {
1227
- return false;
1228
- }
1229
-
1230
- return true;
1231
- }
1232
-
1233
-
1234
- /**
1235
- * 触发控件事件,组合参数并按控件层级关系执行事件冒泡。
1236
- *
1237
- * @method raiseEvent
1238
- * @param {string} name 事件名称
1239
- * @param {object} args 事件执行参数
1240
- * @return {boolean} 如果事件被组止冒泡则返回false,否则返回true
1241
- */
1242
- raiseEvent(name, args) {
1243
- if(this.visible === false) return ;//如果不显示则不响应事件
1244
- if(!args.position) {
1245
- const graph = this.graph;
1246
- args.isWXMiniApp = graph.isWXMiniApp;
1247
-
1248
- const srcElement = args.srcElement || args.target;
1249
-
1250
- const position = jmUtils.getEventPosition(args);//初始化事件位置
1251
-
1252
- args = {
1253
- position: position,
1254
- button: args.button == 0 || position.isTouch? 1: args.button,
1255
- keyCode: args.keyCode || args.charCode || args.which,
1256
- ctrlKey: args.ctrlKey,
1257
- cancel : false,
1258
- event: args, // 原生事件
1259
- srcElement : srcElement,
1260
- isWXMiniApp: graph.isWXMiniApp,
1261
- };
1262
- }
1263
- args.path = args.path||[]; //事件冒泡路径
1264
-
1265
- //先执行子元素事件,如果事件没有被阻断,则向上冒泡
1266
- let stoped = false;
1267
- if(this.children) {
1268
- this.children.each(function(j, el) {
1269
- //未被阻止才执行
1270
- if(args.cancel !== true) {
1271
- //如果被阻止冒泡,
1272
- stoped = el.raiseEvent(name, args) === false? true: stoped;
1273
- // 不再响应其它元素
1274
- if(stoped) return false;
1275
- }
1276
- }, true);//按逆序处理
1277
- }
1278
- // 如果已被阻止,不再响应上级事件
1279
- if(stoped) return false;
1280
-
1281
- //获取当前对象的父元素绝对位置
1282
- //生成当前坐标对应的父级元素的相对位置
1283
- let abounds = this.parent && this.parent.absoluteBounds?this.parent.absoluteBounds : this.absoluteBounds;
1284
- if(!abounds) return false;
1285
- //args = jmUtils.clone(args);//参数副本
1286
- args.position.x = args.position.offsetX - abounds.left;
1287
- args.position.y = args.position.offsetY - abounds.top;
1288
-
1289
- // 是否在当前控件内操作
1290
- const inpos = this.interactive !== false && this.checkPoint(args.position);
1291
-
1292
- //事件发生在边界内或健盘事件发生在画布中才触发
1293
- if(inpos) {
1294
- //如果没有指定触发对象,则认为当前为第一触发对象
1295
- if(!args.target) {
1296
- args.target = this;
1297
- }
1298
-
1299
- this.runEventAndPopEvent(name, args);
1300
-
1301
- if(!this.focused && (name === 'mousemove' || name === 'touchmove')) {
1302
- this.focused = true;//表明当前焦点在此控件中
1303
- this.raiseEvent(name === 'mousemove'? 'mouseover': 'touchover', args);
1304
- }
1305
- }
1306
- else {
1307
- //如果焦点不在,且原焦点在,则触发mouseleave事件
1308
- if(this.interactive !== false && !inpos &&
1309
- this.focused &&
1310
- (name === 'mousemove' || name === 'touchmove')) {
1311
-
1312
- this.focused = false;//表明当前焦点离开
1313
- this.runEventHandle(name === 'mousemove'? 'mouseleave' : 'touchleave', args);//执行事件
1314
- }
1315
- }
1316
-
1317
- return args.cancel === false;//如果被阻止则返回false,否则返回true
1318
- }
1319
-
1320
- /**
1321
- * 执行事件,并进行冒泡
1322
- * @param {string} name 事件名称
1323
- * @param {object} args 事件参数
1324
- */
1325
- runEventAndPopEvent(name, args) {
1326
-
1327
- if(args.cancel !== true) {
1328
- // 添加到触发路径
1329
- args.path.push(this);
1330
-
1331
- //如果返回true则阻断冒泡
1332
- this.runEventHandle(name, args);//执行事件
1333
-
1334
- // // 向父节点冒泡事件
1335
- // if(args.cancel !== true && this.parent && this.parent.runEventAndPopEvent) {
1336
- // // 相对位置需要改为父节点的
1337
- // if(args.position) {
1338
- // let bounds = this.parent.getBounds();
1339
- // args.position.x += bounds.left;
1340
- // args.position.y += bounds.top;
1341
- // }
1342
- // this.parent.runEventAndPopEvent(name, args);
1343
- // }
1344
- }
1345
- }
1346
-
1347
- /**
1348
- * 清空控件指定事件
1349
- *
1350
- * @method clearEvents
1351
- * @param {string} name 需要清除的事件名称
1352
- */
1353
- clearEvents(name) {
1354
- var eventCollection = this.getEvent(name) ;
1355
- if(eventCollection) {
1356
- eventCollection.clear;
1357
- }
1358
- }
1359
-
1360
- /**
1361
- * 查找其父级类型为type的元素,直到找到指定的对象或到最顶级控件后返回空。
1362
- *
1363
- * @method findParent
1364
- * @param {object} 类型名称或类型对象
1365
- * @return {object} 指定类型的实例
1366
- */
1367
- findParent(type) {
1368
- //如果为类型名称,则返回名称相同的类型对象
1369
- if(typeof type === 'string') {
1370
- if(this.type == type)
1371
- return this;
1372
- }
1373
- else if(this.is(type)) {
1374
- return this;
1375
- }
1376
- if(this.parent) {
1377
- return this.parent.findParent(type);
1378
- }
1379
- return null;
1380
- }
1381
-
1382
- /**
1383
- * 设定是否可以移动
1384
- * 此方法需指定jmgraph或在控件添加到jmgraph后再调用才能生效。
1385
- *
1386
- * @method canMove
1387
- * @param {boolean} m true=可以移动,false=不可移动或清除移动。
1388
- * @param {jmGraph} [graph] 当前画布,如果为空的话必需是已加入画布的控件,否则得指定画布。
1389
- */
1390
- canMove(m, graph) {
1391
- if(!this.__mvMonitor) {
1392
- /**
1393
- * 控制控件移动对象
1394
- *
1395
- * @property __mvMonitor
1396
- * @private
1397
- */
1398
- this.__mvMonitor = {};
1399
- this.__mvMonitor.mouseDown = false;
1400
- this.__mvMonitor.curposition={x:0,y:0};
1401
- var self = this;
1402
- /**
1403
- * 控件移动鼠标事件
1404
- *
1405
- * @method mv
1406
- * @private
1407
- */
1408
- this.__mvMonitor.mv = function(evt) {
1409
- let _this = self;
1410
- //如果鼠标经过当前可移动控件,则显示可移动指针
1411
- //if(evt.path && evt.path.indexOf(_this)>-1) {
1412
- // _this.cursor('move');
1413
- //}
1414
-
1415
- if(_this.__mvMonitor.mouseDown) {
1416
- _this.parent.bounds = null;
1417
- //let parentbounds = _this.parent.getAbsoluteBounds();
1418
- let offsetx = evt.position.offsetX - _this.__mvMonitor.curposition.x;
1419
- let offsety = evt.position.offsetY - _this.__mvMonitor.curposition.y;
1420
- //console.log(offsetx + ',' + offsety);
1421
- //如果锁定边界
1422
- if(_this.option.lockSide) {
1423
- let thisbounds = _this.bounds || _this.getAbsoluteBounds();
1424
- //检查边界出界
1425
- let outside = jmUtils.checkOutSide(_this.option.lockSide, thisbounds, { x: offsetx, y: offsety });
1426
- if(outside.left < 0 && offsetx < 0) {
1427
- //offsetx -= outside.left;
1428
- offsetx = 0;
1429
- }
1430
- else if(outside.right > 0 && offsetx > 0) {
1431
- //offsetx -= outside.right;
1432
- offsetx = 0;
1433
- }
1434
- if(outside.top < 0 && offsety < 0) {
1435
- //offsety -= outside.top;
1436
- offsety = 0;
1437
- }
1438
- else if(outside.bottom > 0 && offsety > 0) {
1439
- //offsety -= outside.bottom;
1440
- offsety = 0;
1441
- }
1442
- }
1443
-
1444
- if(offsetx || offsety) {
1445
- _this.offset(offsetx, offsety, true, evt);
1446
- if(offsetx) _this.__mvMonitor.curposition.x = evt.position.offsetX;
1447
- if(offsety) _this.__mvMonitor.curposition.y = evt.position.offsetY;
1448
- //console.log(offsetx + '.' + offsety);
1449
- }
1450
- return false;
1451
- }
1452
- }
1453
- /**
1454
- * 控件移动鼠标松开事件
1455
- *
1456
- * @method mu
1457
- * @private
1458
- */
1459
- this.__mvMonitor.mu = function(evt) {
1460
- let _this = self;
1461
- if(_this.__mvMonitor.mouseDown) {
1462
- _this.__mvMonitor.mouseDown = false;
1463
- //_this.cursor('default');
1464
- _this.emit('moveend',{position:_this.__mvMonitor.curposition});
1465
- //return false;
1466
- }
1467
- }
1468
- /**
1469
- * 控件移动鼠标离开事件
1470
- *
1471
- * @method ml
1472
- * @private
1473
- */
1474
- this.__mvMonitor.ml = function() {
1475
- let _this = self;
1476
- if(_this.__mvMonitor.mouseDown) {
1477
- _this.__mvMonitor.mouseDown = false;
1478
- //_this.cursor('default');
1479
- _this.emit('moveend',{position:_this.__mvMonitor.curposition});
1480
- return false;
1481
- }
1482
- }
1483
- /**
1484
- * 控件移动鼠标按下事件
1485
- *
1486
- * @method md
1487
- * @private
1488
- */
1489
- this.__mvMonitor.md = function(evt) {
1490
-
1491
- if(this.__mvMonitor.mouseDown) return;
1492
- if(evt.button == 0 || evt.button == 1) {
1493
- this.__mvMonitor.mouseDown = true;
1494
- //this.cursor('move');
1495
- //var parentbounds = this.parent.absoluteBounds || this.parent.getAbsoluteBounds();
1496
- this.__mvMonitor.curposition.x = evt.position.offsetX;//evt.position.x + parentbounds.left;
1497
- this.__mvMonitor.curposition.y = evt.position.offsetY;//evt.position.y + parentbounds.top;
1498
- //触发控件移动事件
1499
- this.emit('movestart',{position:this.__mvMonitor.curposition});
1500
-
1501
- evt.cancel = true;
1502
- return false;
1503
- }
1504
- }
1505
- }
1506
- graph = graph || this.graph ;//获取最顶级元素画布
1507
-
1508
- if(m !== false) {
1509
- graph.bind('mousemove',this.__mvMonitor.mv);
1510
- graph.bind('mouseup',this.__mvMonitor.mu);
1511
- graph.bind('mouseleave',this.__mvMonitor.ml);
1512
- this.bind('mousedown',this.__mvMonitor.md);
1513
- graph.bind('touchmove',this.__mvMonitor.mv);
1514
- graph.bind('touchend',this.__mvMonitor.mu);
1515
- this.bind('touchstart',this.__mvMonitor.md);
1516
- }
1517
- else {
1518
- graph.unbind('mousemove',this.__mvMonitor.mv);
1519
- graph.unbind('mouseup',this.__mvMonitor.mu);
1520
- graph.unbind('mouseleave',this.__mvMonitor.ml);
1521
- this.unbind('mousedown',this.__mvMonitor.md);
1522
- graph.unbind('touchmove',this.__mvMonitor.mv);
1523
- graph.unbind('touchend',this.__mvMonitor.mu);
1524
- this.unbind('touchstart',this.__mvMonitor.md);
1525
- }
1526
-
1527
- this.interactive = true;// 如果可以移动,则响应事件
1528
- return this;
1529
- }
1530
- };
1531
-
1
+
2
+ import {jmUtils} from "./jmUtils.js";
3
+ import {jmList} from "./jmList.js";
4
+ import {jmGradient} from "./jmGradient.js";
5
+ import {jmShadow} from "./jmShadow.js";
6
+ import {jmProperty} from "./jmProperty.js";
7
+ import WebglPath from "../lib/webgl/path.js";
8
+
9
+ const jmStyleMap = {
10
+ 'fill':'fillStyle',
11
+ 'fillImage':'fillImage',
12
+ 'stroke':'strokeStyle',
13
+ 'shadow.blur':'shadowBlur',
14
+ 'shadow.x':'shadowOffsetX',
15
+ 'shadow.y':'shadowOffsetY',
16
+ 'shadow.color':'shadowColor',
17
+ 'lineWidth' : 'lineWidth',
18
+ 'miterLimit': 'miterLimit',
19
+ 'fillStyle' : 'fillStyle',
20
+ 'strokeStyle' : 'strokeStyle',
21
+ 'font' : 'font',
22
+ 'opacity' : 'globalAlpha',
23
+ 'textAlign' : 'textAlign',
24
+ 'textBaseline' : 'textBaseline',
25
+ 'shadowBlur' : 'shadowBlur',
26
+ 'shadowOffsetX' : 'shadowOffsetX',
27
+ 'shadowOffsetY' : 'shadowOffsetY',
28
+ 'shadowColor' : 'shadowColor',
29
+ 'lineJoin': 'lineJoin',
30
+ 'lineCap':'lineCap'
31
+ };
32
+
33
+ export default class jmControl extends jmProperty {
34
+
35
+ constructor(params, t) {
36
+ params = params||{};
37
+ super(params);
38
+ this.property('type', t || new.target.name);
39
+ this.style = params && params.style ? params.style : {};
40
+ this.width = params.width || 0;
41
+ this.height = params.height || 0;
42
+ this.hitArea = params.hitArea || null;
43
+
44
+ if(params.position) {
45
+ this.position = params.position;
46
+ }
47
+
48
+ this.graph = params.graph || null;
49
+ this.zIndex = params.zIndex || 0;
50
+ this.interactive = typeof params.interactive == 'undefined'? false : params.interactive;
51
+
52
+ if(this.mode === 'webgl') {
53
+ this.webglControl = new WebglPath(this.graph, {
54
+ style: this.style,
55
+ control: this,
56
+ isRegular: params.isRegular,
57
+ needCut: params.needCut
58
+ });
59
+ }
60
+
61
+ this.initializing();
62
+
63
+ this.on = this.bind;
64
+
65
+ this.option = params;
66
+ }
67
+
68
+ get type() {
69
+ return this.property('type');
70
+ }
71
+
72
+ get context() {
73
+ let s = this.property('context');
74
+ if(s) return s;
75
+ else if(this.is('jmGraph') && this.canvas && this.canvas.getContext) {
76
+ return this.context = this.canvas.getContext(this.mode || '2d');
77
+ }
78
+ const g = this.graph;
79
+ if(g) return g.context;
80
+ return g.canvas.getContext(this.mode || '2d');
81
+ }
82
+ set context(v) {
83
+ return this.property('context', v);
84
+ }
85
+
86
+ get style() {
87
+ let s = this.property('style');
88
+ if(!s) s = this.property('style', {});
89
+ return s;
90
+ }
91
+ set style(v) {
92
+ this.needUpdate = true;
93
+ return this.property('style', v);
94
+ }
95
+
96
+ get visible() {
97
+ let s = this.property('visible');
98
+ if(typeof s == 'undefined') s = this.property('visible', true);
99
+ return s;
100
+ }
101
+ set visible(v) {
102
+ this.needUpdate = true;
103
+ return this.property('visible', v);
104
+ }
105
+
106
+ get interactive() {
107
+ const s = this.property('interactive');
108
+ return s;
109
+ }
110
+ set interactive(v) {
111
+ return this.property('interactive', v);
112
+ }
113
+
114
+ get hitArea() {
115
+ const s = this.property('hitArea');
116
+ return s;
117
+ }
118
+ set hitArea(v) {
119
+ return this.property('hitArea', v);
120
+ }
121
+
122
+ get children() {
123
+ let s = this.property('children');
124
+ if(!s) s = this.property('children', new jmList());
125
+ return s;
126
+ }
127
+ set children(v) {
128
+ this.needUpdate = true;
129
+ return this.property('children', v);
130
+ }
131
+
132
+ get width() {
133
+ let s = this.property('width');
134
+ if(typeof s == 'undefined') s = this.property('width', 0);
135
+ return s;
136
+ }
137
+ set width(v) {
138
+ this.needUpdate = true;
139
+ return this.property('width', v);
140
+ }
141
+
142
+ get height() {
143
+ let s = this.property('height');
144
+ if(typeof s == 'undefined') s = this.property('height', 0);
145
+ return s;
146
+ }
147
+ set height(v) {
148
+ this.needUpdate = true;
149
+ return this.property('height', v);
150
+ }
151
+
152
+ get zIndex() {
153
+ let s = this.property('zIndex');
154
+ if(!s) s = this.property('zIndex', 0);
155
+ return s;
156
+ }
157
+ set zIndex(v) {
158
+ this.property('zIndex', v);
159
+ this.children.sort();
160
+ this.needUpdate = true;
161
+ return v;
162
+ }
163
+
164
+ set cursor(cur) {
165
+ const graph = this.graph;
166
+ if(graph) {
167
+ graph.css('cursor',cur);
168
+ }
169
+ }
170
+ get cursor() {
171
+ const graph = this.graph;
172
+ if(graph) {
173
+ return graph.css('cursor');
174
+ }
175
+ }
176
+
177
+ initializing() {
178
+
179
+ const self = this;
180
+ this.children = this.children || new jmList();
181
+ const oadd = this.children.add;
182
+
183
+ this.children.add = function(obj) {
184
+ if(typeof obj === 'object') {
185
+ if(obj.parent && obj.parent != self && obj.parent.children) {
186
+ obj.parent.children.remove(obj);
187
+ }
188
+ obj.parent = self;
189
+ if(this.contain(obj)) {
190
+ this.oremove(obj);
191
+ }
192
+ oadd.call(this, obj);
193
+ obj.emit('add', obj);
194
+
195
+ self.needUpdate = true;
196
+ if(self.graph) obj.graph = self.graph;
197
+ this.sort();
198
+ return obj;
199
+ }
200
+ };
201
+ this.children.oremove= this.children.remove;
202
+
203
+ this.children.remove = function(obj) {
204
+ if(typeof obj === 'object') {
205
+ obj.parent = null;
206
+ obj.graph = null;
207
+ obj.remove(true);
208
+ this.oremove(obj);
209
+ self.needUpdate = true;
210
+ }
211
+ };
212
+
213
+ this.children.sort = function() {
214
+ const levelItems = {};
215
+ this.each(function(i, obj) {
216
+ if(!obj) return;
217
+ let zindex = obj.zIndex;
218
+ if(!zindex && obj.style && obj.style.zIndex) {
219
+ zindex = Number(obj.style.zIndex);
220
+ if(isNaN(zindex)) zindex=obj.style.zIndex||0;
221
+ }
222
+ let items = levelItems[zindex] || (levelItems[zindex] = []);
223
+ items.push(obj);
224
+ });
225
+
226
+ this.splice(0, this.length);
227
+
228
+ for(let index in levelItems) {
229
+ oadd.call(this, levelItems[index]);
230
+ }
231
+ }
232
+ this.children.clear = function() {
233
+ this.each(function(i,obj) {
234
+ this.remove(obj);
235
+ },true);
236
+ }
237
+ this.needUpdate = true;
238
+ }
239
+
240
+ setStyle(style) {
241
+ style = style || jmUtils.clone(this.style, true);
242
+ if(!style) return;
243
+
244
+ const __setStyle = (style, name, mpkey) => {
245
+ if(style) {
246
+ let styleValue = style;
247
+ if(typeof styleValue === 'function') {
248
+ try {
249
+ styleValue = styleValue.call(this);
250
+ }
251
+ catch(e) {
252
+ console.warn(e);
253
+ return;
254
+ }
255
+ }
256
+ let t = typeof styleValue;
257
+ let mpname = jmStyleMap[mpkey || name];
258
+
259
+ if((styleValue instanceof jmGradient) || (t == 'string' && styleValue.indexOf('-gradient') > -1)) {
260
+ if(t == 'string' && styleValue.indexOf('-gradient') > -1) {
261
+ styleValue = new jmGradient(styleValue);
262
+ }
263
+ __setStyle(styleValue.toGradient(this), mpname||name);
264
+ }
265
+ else if(mpname) {
266
+ if(this.webglControl) {
267
+ this.webglControl.setStyle(mpname, styleValue);
268
+ }
269
+ else {
270
+ if(t == 'string' && ['fillStyle', 'strokeStyle', 'shadowColor'].indexOf(mpname) > -1) {
271
+ styleValue = jmUtils.toColor(styleValue);
272
+ }
273
+ this.context[mpname] = styleValue;
274
+ }
275
+ }
276
+ else {
277
+ switch(name) {
278
+ case 'shadow' : {
279
+ if(t == 'string') {
280
+ __setStyle(new jmShadow(styleValue), name);
281
+ break;
282
+ }
283
+ for(let k in styleValue) {
284
+ __setStyle(styleValue[k], k, name + '.' + k);
285
+ }
286
+ break;
287
+ }
288
+ case 'translate' : {
289
+ break;
290
+ }
291
+ case 'rotation' : {
292
+ if(typeof styleValue.angle === 'undefined' || isNaN(styleValue.angle)) break;
293
+ styleValue = this.getRotation(styleValue);
294
+
295
+ this.__translateAbsolutePosition = this.toAbsolutePoint({
296
+ x: styleValue.x,
297
+ y: styleValue.y
298
+ });
299
+ this.context.translate && this.context.translate(this.__translateAbsolutePosition.x, this.__translateAbsolutePosition.y);
300
+ this.context.rotate && this.context.rotate(styleValue.angle);
301
+ this.context.translate && this.context.translate(-this.__translateAbsolutePosition.x, -this.__translateAbsolutePosition.y);
302
+ break;
303
+ }
304
+ case 'transform' : {
305
+ if(!this.context.transform) break;
306
+ if(Array.isArray(styleValue)) {
307
+ this.context.transform.apply(this.context, styleValue);
308
+ }
309
+ else if(typeof styleValue == 'object') {
310
+ this.context.transform(
311
+ styleValue.scaleX || 1,
312
+ styleValue.skewX || 0,
313
+ styleValue.skewY || 0,
314
+ styleValue.scaleY || 1,
315
+ styleValue.offsetX || 0,
316
+ styleValue.offsetY || 0
317
+ );
318
+ }
319
+ break;
320
+ }
321
+ case 'cursor' : {
322
+ this.cursor = styleValue;
323
+ break;
324
+ }
325
+ }
326
+ }
327
+ }
328
+ }
329
+
330
+ if(this.translate) {
331
+ __setStyle(this.translate, 'translate');
332
+ }
333
+ if(this.transform) {
334
+ __setStyle(this.transform, 'transform');
335
+ }
336
+ for(let k in style) {
337
+ if(k === 'constructor') continue;
338
+ let t = typeof style[k];
339
+ if(t == 'string' && style[k].indexOf('-gradient') > -1) {
340
+ style[k] = new jmGradient(style[k]);
341
+ }
342
+ else if(t == 'string' && k == 'shadow') {
343
+ style[k] = new jmShadow(style[k]);
344
+ }
345
+ __setStyle(style[k], k);
346
+ }
347
+ }
348
+
349
+ /**
350
+ * 获取当前控件的边界
351
+ * 通过分析控件的描点或位置加宽高得到为方形的边界
352
+ *
353
+ * @method getBounds
354
+ * @for jmControl
355
+ * @param {boolean} [isReset=false] 是否强制重新计算
356
+ * @return {object} 控件的边界描述对象(left,top,right,bottom,width,height)
357
+ */
358
+ getBounds(isReset) {
359
+ //如果当次计算过,则不重复计算
360
+ if(this.bounds && !isReset) return this.bounds;
361
+
362
+ const rect = {}; // left top
363
+ //jmGraph,特殊处理
364
+ if(this.type == 'jmGraph' && this.canvas) {
365
+ if(typeof this.canvas.width === 'function') {
366
+ rect.right = this.canvas.width();
367
+ }
368
+ else if(this.width) {
369
+ rect.right = this.width;
370
+ }
371
+
372
+ if(typeof this.canvas.height === 'function') {
373
+ rect.bottom = this.canvas.height();
374
+ }
375
+ else if(this.height) {
376
+ rect.bottom = this.height;
377
+ }
378
+ }
379
+ else if(this.points && this.points.length > 0) {
380
+ for(const p of this.points) {
381
+ if(typeof rect.left === 'undefined' || rect.left > p.x) {
382
+ rect.left = p.x;
383
+ }
384
+ if(typeof rect.top === 'undefined' || rect.top > p.y) {
385
+ rect.top = p.y;
386
+ }
387
+
388
+ if(typeof rect.right === 'undefined' || rect.right < p.x) {
389
+ rect.right = p.x;
390
+ }
391
+ if(typeof rect.bottom === 'undefined' || rect.bottom < p.y) {
392
+ rect.bottom = p.y;
393
+ }
394
+ }
395
+ }
396
+ else if(this.getLocation) {
397
+ let p = this.getLocation();
398
+ if(p) {
399
+ rect.left = p.left;
400
+ rect.top = p.top;
401
+ rect.right = p.left + p.width;
402
+ rect.bottom = p.top + p.height;
403
+ }
404
+ }
405
+ if(!rect.left) rect.left = 0;
406
+ if(!rect.top) rect.top = 0;
407
+ if(!rect.right) rect.right = 0;
408
+ if(!rect.bottom) rect.bottom = 0;
409
+ rect.width = rect.right - rect.left;
410
+ rect.height = rect.bottom - rect.top;
411
+
412
+ return this.bounds=rect;
413
+ }
414
+
415
+ /**
416
+ * 获取被旋转后的边界
417
+ */
418
+ getRotationBounds(rotation=null) {
419
+ rotation = rotation || this.getRotation();
420
+ const bounds = this.getBounds();
421
+ if(!rotation || !rotation.angle) return bounds;
422
+
423
+ const rect = {
424
+ width: 0,
425
+ height: 0,
426
+ oldBounds: bounds
427
+ }; // left top
428
+ let points = [];
429
+ if(this.points && this.points.length > 0) {
430
+ points = jmUtils.clone(this.points, true); // 深度拷贝
431
+ }
432
+ else if(this.getLocation) {
433
+ const local = this.getLocation();
434
+ if(local) {
435
+ points.push({
436
+ x: local.left,
437
+ y: local.top
438
+ },{
439
+ x: local.left + local.width,
440
+ y: local.top
441
+ },{
442
+ x: local.left + local.width,
443
+ y: local.top + local.height
444
+ },{
445
+ x: local.left,
446
+ y: local.top + local.height
447
+ });
448
+ }
449
+ }
450
+ points = jmUtils.rotatePoints(points, {
451
+ x: rotation.x + bounds.left,
452
+ y: rotation.y + bounds.top
453
+ }, rotation.angle);// 对现在点进行旋转
454
+
455
+ for(const p of points) {
456
+ if(typeof rect.left === 'undefined' || rect.left > p.x) {
457
+ rect.left = p.x;
458
+ }
459
+ if(typeof rect.top === 'undefined' || rect.top > p.y) {
460
+ rect.top = p.y;
461
+ }
462
+
463
+ if(typeof rect.right === 'undefined' || rect.right < p.x) {
464
+ rect.right = p.x;
465
+ }
466
+ if(typeof rect.bottom === 'undefined' || rect.bottom < p.y) {
467
+ rect.bottom = p.y;
468
+ }
469
+ }
470
+
471
+ if(!rect.left) rect.left = 0;
472
+ if(!rect.top) rect.top = 0;
473
+ if(!rect.right) rect.right = 0;
474
+ if(!rect.bottom) rect.bottom = 0;
475
+
476
+ rect.width = rect.right - rect.left;
477
+ rect.height = rect.bottom - rect.top;
478
+
479
+ return rect;
480
+ }
481
+
482
+ /**
483
+ * 获取当前控件的位置相关参数
484
+ * 解析百分比和margin参数
485
+ *
486
+ * @method getLocation
487
+ * @return {object} 当前控件位置参数,包括中心点坐标,右上角坐标,宽高
488
+ */
489
+ getLocation(clone=true) {
490
+ //如果已经计算过则直接返回
491
+ //在开画之前会清空此对象
492
+ //if(reset !== true && this.location) return this.location;
493
+
494
+ let local = this.location = {left: 0,top: 0,width: 0,height: 0};
495
+ local.position = typeof this.position == 'function'? this.position(): jmUtils.clone(this.position);
496
+ local.center = this.center && typeof this.center === 'function'?this.center(): jmUtils.clone(this.center);//中心
497
+ local.start = this.start && typeof this.start === 'function'?this.start(): jmUtils.clone(this.start);//起点
498
+ local.end = this.end && typeof this.end === 'function'?this.end(): jmUtils.clone(this.end);//起点
499
+ local.radius = this.radius;//半径
500
+ local.width = this.width;
501
+ local.height = this.height;
502
+
503
+ const margin = jmUtils.clone(this.style.margin, {});
504
+ margin.left = (margin.left || 0);
505
+ margin.top = (margin.top || 0);
506
+ margin.right = (margin.right || 0);
507
+ margin.bottom = (margin.bottom || 0);
508
+
509
+ //如果没有指定位置,但指定了margin。则位置取margin偏移量
510
+ if(local.position) {
511
+ local.left = local.position.x;
512
+ local.top = local.position.y;
513
+ }
514
+ else {
515
+ local.left = margin.left;
516
+ local.top = margin.top;
517
+ }
518
+
519
+ if(this.parent) {
520
+ const parentBounds = this.parent.getBounds();
521
+
522
+ //处理百分比参数
523
+ if(jmUtils.checkPercent(local.left)) {
524
+ local.left = jmUtils.percentToNumber(local.left) * parentBounds.width;
525
+ }
526
+ if(jmUtils.checkPercent(local.top)) {
527
+ local.top = jmUtils.percentToNumber(local.top) * parentBounds.height;
528
+ }
529
+
530
+ //如果没有指定宽度或高度,则按百分之百计算其父宽度或高度
531
+ if(jmUtils.checkPercent(local.width)) {
532
+ local.width = jmUtils.percentToNumber(local.width) * parentBounds.width;
533
+ }
534
+ if(jmUtils.checkPercent(local.height)) {
535
+ local.height = jmUtils.percentToNumber(local.height) * parentBounds.height;
536
+ }
537
+ //处理中心点
538
+ if(local.center) {
539
+ //处理百分比参数
540
+ if(jmUtils.checkPercent(local.center.x)) {
541
+ local.center.x = jmUtils.percentToNumber(local.center.x) * parentBounds.width;
542
+ }
543
+ if(jmUtils.checkPercent(local.center.y)) {
544
+ local.center.y = jmUtils.percentToNumber(local.center.y) * parentBounds.height;
545
+ }
546
+ }
547
+ if(local.radius) {
548
+ //处理百分比参数
549
+ if(jmUtils.checkPercent(local.radius)) {
550
+ local.radius = jmUtils.percentToNumber(local.radius) * Math.min(parentBounds.width, parentBounds.height);
551
+ }
552
+ }
553
+ }
554
+ return local;
555
+ }
556
+
557
+ /**
558
+ * 获取当前控制的旋转信息
559
+ * @returns {object} 旋转中心和角度
560
+ */
561
+ getRotation(rotation, bounds = null) {
562
+ rotation = rotation || jmUtils.clone(this.style.rotation);
563
+
564
+ if(!rotation) {
565
+ //如果本身没有,则可以继承父级的
566
+ rotation = this.parent && this.parent.getRotation?this.parent.getRotation():null;
567
+ //如果父级有旋转,则把坐标转换为当前控件区域
568
+ if(rotation) {
569
+ bounds = bounds || this.getBounds();
570
+ rotation.x -= bounds.left;
571
+ rotation.y -= bounds.top;
572
+ }
573
+ }
574
+ else {
575
+ bounds = bounds || this.getBounds();
576
+ if(typeof rotation.x === 'undefined') rotation.x = '50%';
577
+ if(typeof rotation.y === 'undefined') rotation.y = '50%';
578
+ if(jmUtils.checkPercent(rotation.x)) {
579
+ rotation.x = jmUtils.percentToNumber(rotation.x) * bounds.width;
580
+ }
581
+ if(jmUtils.checkPercent(rotation.y)) {
582
+ rotation.y = jmUtils.percentToNumber(rotation.y) * bounds.height;
583
+ }
584
+ }
585
+ return {
586
+ ...rotation,
587
+ bounds
588
+ };
589
+
590
+ }
591
+
592
+ // 计算位移偏移量
593
+ getTranslate(translate, bounds = null) {
594
+ translate = translate || this.style.translate;
595
+ if(!translate) return {x: 0, y: 0};
596
+ const result = {
597
+ x: translate.x || 0,
598
+ y: translate.y || 0
599
+ }
600
+
601
+ if(jmUtils.checkPercent(result.x)) {
602
+ if(!bounds && this.parent) bounds = this.parent.getBounds();
603
+ result.x = jmUtils.percentToNumber(result.x) * bounds.width;
604
+ }
605
+ if(jmUtils.checkPercent(result.y)) {
606
+ if(!bounds && this.parent) bounds = this.parent.getBounds();
607
+ result.y = jmUtils.percentToNumber(result.y) * bounds.height;
608
+ }
609
+ return result;
610
+ }
611
+
612
+ /**
613
+ * 移除当前控件
614
+ * 如果是VML元素,则调用其删除元素
615
+ *
616
+ * @method remove
617
+ */
618
+ remove() {
619
+ if(this.parent) {
620
+ this.parent.children.remove(this);
621
+ }
622
+ }
623
+
624
+ /**
625
+ * 对控件进行平移
626
+ * 遍历控件所有描点或位置,设置其偏移量。
627
+ *
628
+ * @method offset
629
+ * @param {number} x x轴偏移量
630
+ * @param {number} y y轴偏移量
631
+ * @param {boolean} [trans] 是否传递,监听者可以通过此属性是否决定是否响应移动事件,默认=true
632
+ * @param {object} [evt] 如果是事件触发,则传递move事件参数
633
+ */
634
+ offset(x, y, trans, evt) {
635
+ trans = trans === false?false:true;
636
+ let local = this.getLocation(true);
637
+ let offseted = false;
638
+
639
+ if(local.position) {
640
+ local.left += x;
641
+ local.top += y;
642
+ // 由于local是clone出来的对象,为了保留位移,则要修改原属性
643
+ this.position.x = local.left;
644
+ this.position.y = local.top;
645
+ offseted = true;
646
+ }
647
+
648
+ if(local.center) {
649
+ this.center.x = local.center.x + x;
650
+ this.center.y = local.center.y + y;
651
+ offseted = true;
652
+ }
653
+
654
+ if(local.start && typeof local.start == 'object') {
655
+ this.start.x = local.start.x + x;
656
+ this.start.y = local.start.y + y;
657
+ offseted = true;
658
+ }
659
+
660
+ if(local.end && typeof local.end == 'object') {
661
+ this.end.x = local.end.x + x;
662
+ this.end.y = local.end.y + y;
663
+ offseted = true;
664
+ }
665
+
666
+
667
+ if(offseted == false && this.cpoints) {
668
+ let p = typeof this.cpoints == 'function'?this.cpoints:this.cpoints;
669
+ if(p) {
670
+ let len = p.length;
671
+ for(let i=0; i < len;i++) {
672
+ p[i].x += x;
673
+ p[i].y += y;
674
+ }
675
+ offseted = true;
676
+ }
677
+ }
678
+
679
+ if(offseted == false && this.points) {
680
+ let len = this.points.length;
681
+ for(let i=0; i < len;i++) {
682
+ this.points[i].x += x;
683
+ this.points[i].y += y;
684
+ }
685
+ offseted = true;
686
+ }
687
+
688
+ //触发控件移动事件
689
+ this.emit('move',{
690
+ offsetX: x,
691
+ offsetY: y,
692
+ trans: trans,
693
+ evt: evt
694
+ });
695
+
696
+ this.needUpdate = true;
697
+ }
698
+
699
+ /**
700
+ * 获取控件相对于画布的绝对边界,
701
+ * 与getBounds不同的是:getBounds获取的是相对于父容器的边界.
702
+ *
703
+ * @method getAbsoluteBounds
704
+ * @return {object} 边界对象(left,top,right,bottom,width,height)
705
+ */
706
+ getAbsoluteBounds() {
707
+ //当前控件的边界,
708
+ let rec = this.getBounds();
709
+ if(this.parent && this.parent.absoluteBounds) {
710
+ //父容器的绝对边界
711
+ let prec = this.parent.absoluteBounds || this.parent.getAbsoluteBounds();
712
+
713
+ return {
714
+ left : prec.left + rec.left,
715
+ top : prec.top + rec.top,
716
+ right : prec.left + rec.right,
717
+ bottom : prec.top + rec.bottom,
718
+ width : rec.width,
719
+ height : rec.height
720
+ };
721
+ }
722
+ return rec;
723
+ }
724
+
725
+ /**
726
+ * 把当前控制内部坐标转为canvas绝对定位坐标
727
+ *
728
+ * @method toAbsolutePoint
729
+ * @param {x: number, y: number} 内部坐标
730
+ */
731
+ toAbsolutePoint(point) {
732
+ if(point.x || point.y) {
733
+ const bounds = this.absoluteBounds?this.absoluteBounds:this.getAbsoluteBounds();
734
+
735
+ point.x = (point.x||0) + bounds.left;
736
+ point.y = (point.y||0) + bounds.top;
737
+ }
738
+ return point;
739
+ }
740
+
741
+ /**
742
+ * 把绝对定位坐标转为当前控件坐标系内
743
+ * @param {*} point
744
+ */
745
+ toLocalPosition(point) {
746
+
747
+ const bounds = this.absoluteBounds?this.absoluteBounds:this.getAbsoluteBounds();
748
+ if(!bounds) return false;
749
+ return {
750
+ x: point.x - bounds.left,
751
+ y: point.y - bounds.top
752
+ };
753
+ }
754
+
755
+ /**
756
+ * 画控件前初始化
757
+ * 执行beginPath开始控件的绘制
758
+ *
759
+ * @method beginDraw
760
+ */
761
+ beginDraw() {
762
+ this.getLocation(true);//重置位置信息
763
+ this.context.beginPath && this.context.beginPath();
764
+ if(this.webglControl && this.webglControl.beginDraw) this.webglControl.beginDraw();
765
+ }
766
+
767
+ /**
768
+ * 结束控件绘制
769
+ *
770
+ * @method endDraw
771
+ */
772
+ endDraw() {
773
+ //如果当前为封闭路径
774
+ if(this.style.close) {
775
+ if(this.webglControl) this.webglControl.closePath();
776
+ this.context.closePath && this.context.closePath();
777
+ }
778
+
779
+ const fill = this.style['fill'] || this.style['fillStyle'];
780
+ if(fill) {
781
+ if(this.webglControl) {
782
+ const bounds = this.getBounds();
783
+ this.webglControl.fill(bounds);
784
+ }
785
+ this.context.fill && this.context.fill();
786
+ }
787
+ if(this.style['stroke'] || (!fill && !this.is('jmGraph'))) {
788
+ if(this.webglControl) this.webglControl.stroke();
789
+ this.context.stroke && this.context.stroke();
790
+ }
791
+
792
+ if(this.webglControl && this.webglControl.endDraw) this.webglControl.endDraw();
793
+
794
+ this.needUpdate = false;
795
+ }
796
+
797
+ /**
798
+ * 绘制控件
799
+ * 在画布上描点
800
+ *
801
+ * @method draw
802
+ */
803
+ draw() {
804
+ if(this.points && this.points.length > 0) {
805
+ //获取当前控件的绝对位置
806
+ const bounds = this.parent && this.parent.absoluteBounds?this.parent.absoluteBounds:this.absoluteBounds;
807
+ if(this.webglControl) {
808
+ this.webglControl.setParentBounds(bounds);
809
+ this.webglControl.draw([
810
+ ...this.points
811
+ ]);
812
+ }
813
+ else if(this.context && this.context.moveTo) {
814
+ this.context.moveTo(this.points[0].x + bounds.left,this.points[0].y + bounds.top);
815
+ let len = this.points.length;
816
+ for(let i=1; i < len;i++) {
817
+ let p = this.points[i];
818
+ //移至当前坐标
819
+ if(p.m) {
820
+ this.context.moveTo(p.x + bounds.left,p.y + bounds.top);
821
+ }
822
+ else {
823
+ this.context.lineTo(p.x+ bounds.left,p.y + bounds.top);
824
+ }
825
+ }
826
+ }
827
+ }
828
+ }
829
+
830
+ /**
831
+ * 绘制当前控件
832
+ * 协调控件的绘制,先从其子控件开始绘制,再往上冒。
833
+ *
834
+ * @method paint
835
+ */
836
+ paint(v) {
837
+ if(v !== false && this.visible !== false) {
838
+ if(this.initPoints) this.initPoints();
839
+ //计算当前边界
840
+ this.bounds = null;
841
+ this.absoluteBounds = this.getAbsoluteBounds();
842
+ let needDraw = true;//是否需要绘制
843
+ if(!this.is('jmGraph') && this.graph) {
844
+ if(this.absoluteBounds.left >= this.graph.width) needDraw = false;
845
+ else if(this.absoluteBounds.top >= this.graph.height) needDraw = false;
846
+ else if(this.absoluteBounds.right <= 0) needDraw = false;
847
+ else if(this.absoluteBounds.bottom <= 0) needDraw = false;
848
+ }
849
+
850
+ this.context.save && this.context.save();
851
+
852
+ this.emit('beginDraw', this);
853
+
854
+ this.setStyle();//设定样式
855
+
856
+ if(needDraw && this.beginDraw) this.beginDraw();
857
+ if(needDraw && this.draw) this.draw();
858
+ if(needDraw && this.endDraw) this.endDraw();
859
+
860
+ if(this.children) {
861
+ this.children.each(function(i,item) {
862
+ if(item && item.paint) item.paint();
863
+ });
864
+ }
865
+
866
+ this.emit('endDraw',this);
867
+ this.context.restore && this.context.restore();
868
+
869
+ this.needUpdate = false;
870
+ }
871
+ }
872
+
873
+ /**
874
+ * 获取指定事件的集合
875
+ * 比如mousedown,mouseup等
876
+ *
877
+ * @method getEvent
878
+ * @param {string} name 事件名称
879
+ * @return {list} 事件委托的集合
880
+ */
881
+ getEvent(name) {
882
+ return this.__events?this.__events[name]:null;
883
+ }
884
+
885
+ /**
886
+ * 绑定控件的事件
887
+ *
888
+ * @method bind
889
+ * @param {string} name 事件名称
890
+ * @param {function} handle 事件委托
891
+ */
892
+ bind(name, handle) {
893
+ if(name && name.indexOf(' ') > -1) {
894
+ name = name.split(' ');
895
+ for(let n of name) {
896
+ n && this.bind(n, handle);
897
+ }
898
+ return;
899
+ }
900
+ /**
901
+ * 添加事件的集合
902
+ *
903
+ * @method _setEvent
904
+ * @private
905
+ */
906
+ function _setEvent(name, events) {
907
+ if(!this.__events) this.__events = {};
908
+ return this.__events[name] = events;
909
+ }
910
+ let eventCollection = this.getEvent(name) || _setEvent.call(this, name, new jmList());
911
+ if(!eventCollection.contain(handle)) {
912
+ eventCollection.add(handle);
913
+ }
914
+ }
915
+
916
+ /**
917
+ * 移除控件的事件
918
+ *
919
+ * @method unbind
920
+ * @param {string} name 事件名称
921
+ * @param {function} handle 从控件中移除事件的委托
922
+ */
923
+ unbind(name, handle) {
924
+ if(name && name.indexOf(' ') > -1) {
925
+ name = name.split(' ');
926
+ for(let n of name) {
927
+ n && this.unbind(n, handle);
928
+ }
929
+ return;
930
+ }
931
+ let eventCollection = this.getEvent(name) ;
932
+ if(eventCollection) {
933
+ if(handle) eventCollection.remove(handle);
934
+ else eventCollection.clear();
935
+ }
936
+ }
937
+
938
+
939
+ /**
940
+ * 执行监听回调
941
+ *
942
+ * @method emit
943
+ * @for jmControl
944
+ * @param {string} name 触发事件的名称
945
+ * @param {array} args 事件参数数组
946
+ */
947
+ emit(...args) {
948
+ this.runEventHandle(args[0], args.slice(1));
949
+ return this;
950
+ }
951
+
952
+ /**
953
+ * 独立执行事件委托
954
+ *
955
+ * @method runEventHandle
956
+ * @param {string} 将执行的事件名称
957
+ * @param {object} 事件执行的参数,包括触发事件的对象和位置
958
+ */
959
+ runEventHandle(name, args) {
960
+ let events = this.getEvent(name);
961
+ if(events) {
962
+ var self = this;
963
+ if(!Array.isArray(args)) args = [args];
964
+ events.each(function(i, handle) {
965
+ //只要有一个事件被阻止,则不再处理同级事件,并设置冒泡被阻断
966
+ if(false === handle.apply(self, args)) {
967
+ args.cancel = true;
968
+ }
969
+ });
970
+ }
971
+ return args.cancel;
972
+ }
973
+
974
+ /**
975
+ * 查坐标是否落在当前控件区域中..true=在区域内
976
+ *
977
+ * @method checkPoint
978
+ * @param {point} p 位置参数
979
+ * @param {number} [pad] 可选参数,表示线条多远内都算在线上
980
+ * @return {boolean} 当前位置如果在区域内则为true,否则为false。
981
+ */
982
+ checkPoint(p, pad) {
983
+ //jmGraph 需要判断dom位置
984
+ if(this.type == 'jmGraph') {
985
+ //获取dom位置
986
+ let position = this.getPosition();
987
+ // 由于高清屏会有放大坐标,所以这里用pagex就只能用真实的canvas大小
988
+ const right = position.left + this.width;
989
+ const bottom = position.top + this.height;
990
+ if(p.x > right || p.x < position.left) {
991
+ return false;
992
+ }
993
+ if(p.y > bottom || p.y < position.top) {
994
+ return false;
995
+ }
996
+ return true;
997
+ }
998
+
999
+ const bounds = this.getBounds();
1000
+ // 如果指定了合中区域,则以命中区域为准
1001
+ if(this.hitArea) {
1002
+ const hitArea = {
1003
+ left: this.hitArea.x + bounds.left,
1004
+ top: this.hitArea.y + bounds.top,
1005
+ right: this.hitArea.width + bounds.left,
1006
+ bottom: this.hitArea.height + bounds.top,
1007
+ };
1008
+ if(p.x > hitArea.right || p.x < hitArea.left) {
1009
+ return false;
1010
+ }
1011
+ if(p.y > hitArea.bottom || p.y < hitArea.top) {
1012
+ return false;
1013
+ }
1014
+ return true;
1015
+ }
1016
+
1017
+ let ps = this.points;
1018
+ //如果不是路径组成,则采用边界做为顶点
1019
+ if(!ps || !ps.length) {
1020
+ ps = [];
1021
+ ps.push({x: bounds.left, y: bounds.top}); //左上角
1022
+ ps.push({x: bounds.right, y: bounds.top});//右上角
1023
+ ps.push({x: bounds.right, y: bounds.bottom});//右下角
1024
+ ps.push({x: bounds.left, y: bounds.bottom}); //左下
1025
+ ps.push({x: bounds.left, y: bounds.top}); //左上角 //闭合
1026
+ }
1027
+ //如果有指定padding 表示接受区域加宽,命中更易
1028
+ pad = Number(pad || this.style['touchPadding'] || this.style['lineWidth'] || 1);
1029
+ if(ps && ps.length) {
1030
+ const rotation = this.getRotation(null, bounds);//获取当前旋转参数
1031
+ //如果有旋转参数,则需要转换坐标再处理
1032
+ if(rotation && rotation.angle) {
1033
+ ps = jmUtils.clone(ps, true);//拷贝一份数据
1034
+ //rotateX ,rotateY 是相对当前控件的位置
1035
+ ps = jmUtils.rotatePoints(ps, {
1036
+ x: rotation.x + bounds.left,
1037
+ y: rotation.y + bounds.top
1038
+ }, rotation.angle || 0);
1039
+ }
1040
+ //如果当前路径不是实心的
1041
+ //就只用判断点是否在边上即可
1042
+ if(ps.length > 2 && (!this.style['fill'] || this.style['stroke'])) {
1043
+ let i = 0;
1044
+ const count = ps.length;
1045
+ for(let j = i+1; j <= count; j = (++i + 1)) {
1046
+ //如果j超出最后一个
1047
+ //则当为封闭图形时跟第一点连线处理.否则直接返回false
1048
+ if(j == count) {
1049
+ if(this.style.close) {
1050
+ const r = jmUtils.pointInPolygon(p,[ps[i],ps[0]], pad);
1051
+ if(r) return true;
1052
+ }
1053
+ }
1054
+ else {
1055
+ //判断是否在点i,j连成的线上
1056
+ const s = jmUtils.pointInPolygon(p,[ps[i],ps[j]], pad);
1057
+ if(s) return true;
1058
+ }
1059
+ }
1060
+ //不是封闭的图形,则直接返回
1061
+ if(!this.style['fill']) return false;
1062
+ }
1063
+
1064
+ const r = jmUtils.pointInPolygon(p,ps, pad);
1065
+ return r;
1066
+ }
1067
+
1068
+ if(p.x > bounds.right || p.x < bounds.left) {
1069
+ return false;
1070
+ }
1071
+ if(p.y > bounds.bottom || p.y < bounds.top) {
1072
+ return false;
1073
+ }
1074
+
1075
+ return true;
1076
+ }
1077
+
1078
+
1079
+ /**
1080
+ * 触发控件事件,组合参数并按控件层级关系执行事件冒泡。
1081
+ *
1082
+ * @method raiseEvent
1083
+ * @param {string} name 事件名称
1084
+ * @param {object} args 事件执行参数
1085
+ * @return {boolean} 如果事件被组止冒泡则返回false,否则返回true
1086
+ */
1087
+ raiseEvent(name, args) {
1088
+ if(this.visible === false) return ;//如果不显示则不响应事件
1089
+ if(!args.position) {
1090
+ const graph = this.graph;
1091
+ args.isWXMiniApp = graph.isWXMiniApp;
1092
+
1093
+ const srcElement = args.srcElement || args.target;
1094
+
1095
+ const position = jmUtils.getEventPosition(args);//初始化事件位置
1096
+
1097
+ args = {
1098
+ position: position,
1099
+ button: args.button == 0 || position.isTouch? 1: args.button,
1100
+ keyCode: args.keyCode || args.charCode || args.which,
1101
+ ctrlKey: args.ctrlKey,
1102
+ cancel : false,
1103
+ event: args, // 原生事件
1104
+ srcElement : srcElement,
1105
+ isWXMiniApp: graph.isWXMiniApp,
1106
+ };
1107
+ }
1108
+ args.path = args.path||[]; //事件冒泡路径
1109
+
1110
+ //先执行子元素事件,如果事件没有被阻断,则向上冒泡
1111
+ let stoped = false;
1112
+ if(this.children) {
1113
+ this.children.each(function(j, el) {
1114
+ //未被阻止才执行
1115
+ if(args.cancel !== true) {
1116
+ //如果被阻止冒泡,
1117
+ stoped = el.raiseEvent(name, args) === false? true: stoped;
1118
+ // 不再响应其它元素
1119
+ if(stoped) return false;
1120
+ }
1121
+ }, true);//按逆序处理
1122
+ }
1123
+ // 如果已被阻止,不再响应上级事件
1124
+ if(stoped) return false;
1125
+
1126
+ //获取当前对象的父元素绝对位置
1127
+ //生成当前坐标对应的父级元素的相对位置
1128
+ let abounds = this.parent && this.parent.absoluteBounds?this.parent.absoluteBounds : this.absoluteBounds;
1129
+ if(!abounds) return false;
1130
+ //args = jmUtils.clone(args);//参数副本
1131
+ args.position.x = args.position.offsetX - abounds.left;
1132
+ args.position.y = args.position.offsetY - abounds.top;
1133
+
1134
+ // 是否在当前控件内操作
1135
+ const inpos = this.interactive !== false && this.checkPoint(args.position);
1136
+
1137
+ //事件发生在边界内或健盘事件发生在画布中才触发
1138
+ if(inpos) {
1139
+ //如果没有指定触发对象,则认为当前为第一触发对象
1140
+ if(!args.target) {
1141
+ args.target = this;
1142
+ }
1143
+
1144
+ this.runEventAndPopEvent(name, args);
1145
+
1146
+ if(!this.focused && (name === 'mousemove' || name === 'touchmove')) {
1147
+ this.focused = true;//表明当前焦点在此控件中
1148
+ this.raiseEvent(name === 'mousemove'? 'mouseover': 'touchover', args);
1149
+ }
1150
+ }
1151
+ else {
1152
+ //如果焦点不在,且原焦点在,则触发mouseleave事件
1153
+ if(this.interactive !== false && !inpos &&
1154
+ this.focused &&
1155
+ (name === 'mousemove' || name === 'touchmove')) {
1156
+
1157
+ this.focused = false;//表明当前焦点离开
1158
+ this.runEventHandle(name === 'mousemove'? 'mouseleave' : 'touchleave', args);//执行事件
1159
+ }
1160
+ }
1161
+
1162
+ return args.cancel === false;//如果被阻止则返回false,否则返回true
1163
+ }
1164
+
1165
+ /**
1166
+ * 执行事件,并进行冒泡
1167
+ * @param {string} name 事件名称
1168
+ * @param {object} args 事件参数
1169
+ */
1170
+ runEventAndPopEvent(name, args) {
1171
+
1172
+ if(args.cancel !== true) {
1173
+ // 添加到触发路径
1174
+ args.path.push(this);
1175
+
1176
+ //如果返回true则阻断冒泡
1177
+ this.runEventHandle(name, args);//执行事件
1178
+
1179
+ // // 向父节点冒泡事件
1180
+ // if(args.cancel !== true && this.parent && this.parent.runEventAndPopEvent) {
1181
+ // // 相对位置需要改为父节点的
1182
+ // if(args.position) {
1183
+ // let bounds = this.parent.getBounds();
1184
+ // args.position.x += bounds.left;
1185
+ // args.position.y += bounds.top;
1186
+ // }
1187
+ // this.parent.runEventAndPopEvent(name, args);
1188
+ // }
1189
+ }
1190
+ }
1191
+
1192
+ /**
1193
+ * 清空控件指定事件
1194
+ *
1195
+ * @method clearEvents
1196
+ * @param {string} name 需要清除的事件名称
1197
+ */
1198
+ clearEvents(name) {
1199
+ var eventCollection = this.getEvent(name) ;
1200
+ if(eventCollection) {
1201
+ eventCollection.clear;
1202
+ }
1203
+ }
1204
+
1205
+ /**
1206
+ * 查找其父级类型为type的元素,直到找到指定的对象或到最顶级控件后返回空。
1207
+ *
1208
+ * @method findParent
1209
+ * @param {object} 类型名称或类型对象
1210
+ * @return {object} 指定类型的实例
1211
+ */
1212
+ findParent(type) {
1213
+ //如果为类型名称,则返回名称相同的类型对象
1214
+ if(typeof type === 'string') {
1215
+ if(this.type == type)
1216
+ return this;
1217
+ }
1218
+ else if(this.is(type)) {
1219
+ return this;
1220
+ }
1221
+ if(this.parent) {
1222
+ return this.parent.findParent(type);
1223
+ }
1224
+ return null;
1225
+ }
1226
+
1227
+ /**
1228
+ * 设定是否可以移动
1229
+ * 此方法需指定jmgraph或在控件添加到jmgraph后再调用才能生效。
1230
+ *
1231
+ * @method canMove
1232
+ * @param {boolean} m true=可以移动,false=不可移动或清除移动。
1233
+ * @param {jmGraph} [graph] 当前画布,如果为空的话必需是已加入画布的控件,否则得指定画布。
1234
+ */
1235
+ canMove(m, graph) {
1236
+ if(!this.__mvMonitor) {
1237
+ /**
1238
+ * 控制控件移动对象
1239
+ *
1240
+ * @property __mvMonitor
1241
+ * @private
1242
+ */
1243
+ this.__mvMonitor = {};
1244
+ this.__mvMonitor.mouseDown = false;
1245
+ this.__mvMonitor.curposition={x:0,y:0};
1246
+ var self = this;
1247
+ /**
1248
+ * 控件移动鼠标事件
1249
+ *
1250
+ * @method mv
1251
+ * @private
1252
+ */
1253
+ this.__mvMonitor.mv = function(evt) {
1254
+ let _this = self;
1255
+ //如果鼠标经过当前可移动控件,则显示可移动指针
1256
+ //if(evt.path && evt.path.indexOf(_this)>-1) {
1257
+ // _this.cursor('move');
1258
+ //}
1259
+
1260
+ if(_this.__mvMonitor.mouseDown) {
1261
+ _this.parent.bounds = null;
1262
+ //let parentbounds = _this.parent.getAbsoluteBounds();
1263
+ let offsetx = evt.position.offsetX - _this.__mvMonitor.curposition.x;
1264
+ let offsety = evt.position.offsetY - _this.__mvMonitor.curposition.y;
1265
+ //console.log(offsetx + ',' + offsety);
1266
+ //如果锁定边界
1267
+ if(_this.option.lockSide) {
1268
+ let thisbounds = _this.bounds || _this.getAbsoluteBounds();
1269
+ //检查边界出界
1270
+ let outside = jmUtils.checkOutSide(_this.option.lockSide, thisbounds, { x: offsetx, y: offsety });
1271
+ if(outside.left < 0 && offsetx < 0) {
1272
+ //offsetx -= outside.left;
1273
+ offsetx = 0;
1274
+ }
1275
+ else if(outside.right > 0 && offsetx > 0) {
1276
+ //offsetx -= outside.right;
1277
+ offsetx = 0;
1278
+ }
1279
+ if(outside.top < 0 && offsety < 0) {
1280
+ //offsety -= outside.top;
1281
+ offsety = 0;
1282
+ }
1283
+ else if(outside.bottom > 0 && offsety > 0) {
1284
+ //offsety -= outside.bottom;
1285
+ offsety = 0;
1286
+ }
1287
+ }
1288
+
1289
+ if(offsetx || offsety) {
1290
+ _this.offset(offsetx, offsety, true, evt);
1291
+ if(offsetx) _this.__mvMonitor.curposition.x = evt.position.offsetX;
1292
+ if(offsety) _this.__mvMonitor.curposition.y = evt.position.offsetY;
1293
+ //console.log(offsetx + '.' + offsety);
1294
+ }
1295
+ return false;
1296
+ }
1297
+ }
1298
+ /**
1299
+ * 控件移动鼠标松开事件
1300
+ *
1301
+ * @method mu
1302
+ * @private
1303
+ */
1304
+ this.__mvMonitor.mu = function(evt) {
1305
+ let _this = self;
1306
+ if(_this.__mvMonitor.mouseDown) {
1307
+ _this.__mvMonitor.mouseDown = false;
1308
+ //_this.cursor('default');
1309
+ _this.emit('moveend',{position:_this.__mvMonitor.curposition});
1310
+ //return false;
1311
+ }
1312
+ }
1313
+ /**
1314
+ * 控件移动鼠标离开事件
1315
+ *
1316
+ * @method ml
1317
+ * @private
1318
+ */
1319
+ this.__mvMonitor.ml = function() {
1320
+ let _this = self;
1321
+ if(_this.__mvMonitor.mouseDown) {
1322
+ _this.__mvMonitor.mouseDown = false;
1323
+ //_this.cursor('default');
1324
+ _this.emit('moveend',{position:_this.__mvMonitor.curposition});
1325
+ return false;
1326
+ }
1327
+ }
1328
+ /**
1329
+ * 控件移动鼠标按下事件
1330
+ *
1331
+ * @method md
1332
+ * @private
1333
+ */
1334
+ this.__mvMonitor.md = function(evt) {
1335
+
1336
+ if(this.__mvMonitor.mouseDown) return;
1337
+ if(evt.button == 0 || evt.button == 1) {
1338
+ this.__mvMonitor.mouseDown = true;
1339
+ //this.cursor('move');
1340
+ //var parentbounds = this.parent.absoluteBounds || this.parent.getAbsoluteBounds();
1341
+ this.__mvMonitor.curposition.x = evt.position.offsetX;//evt.position.x + parentbounds.left;
1342
+ this.__mvMonitor.curposition.y = evt.position.offsetY;//evt.position.y + parentbounds.top;
1343
+ //触发控件移动事件
1344
+ this.emit('movestart',{position:this.__mvMonitor.curposition});
1345
+
1346
+ evt.cancel = true;
1347
+ return false;
1348
+ }
1349
+ }
1350
+ }
1351
+ graph = graph || this.graph ;//获取最顶级元素画布
1352
+
1353
+ if(m !== false) {
1354
+ graph.bind('mousemove',this.__mvMonitor.mv);
1355
+ graph.bind('mouseup',this.__mvMonitor.mu);
1356
+ graph.bind('mouseleave',this.__mvMonitor.ml);
1357
+ this.bind('mousedown',this.__mvMonitor.md);
1358
+ graph.bind('touchmove',this.__mvMonitor.mv);
1359
+ graph.bind('touchend',this.__mvMonitor.mu);
1360
+ this.bind('touchstart',this.__mvMonitor.md);
1361
+ }
1362
+ else {
1363
+ graph.unbind('mousemove',this.__mvMonitor.mv);
1364
+ graph.unbind('mouseup',this.__mvMonitor.mu);
1365
+ graph.unbind('mouseleave',this.__mvMonitor.ml);
1366
+ this.unbind('mousedown',this.__mvMonitor.md);
1367
+ graph.unbind('touchmove',this.__mvMonitor.mv);
1368
+ graph.unbind('touchend',this.__mvMonitor.mu);
1369
+ this.unbind('touchstart',this.__mvMonitor.md);
1370
+ }
1371
+
1372
+ this.interactive = true;// 如果可以移动,则响应事件
1373
+ return this;
1374
+ }
1375
+ };
1376
+
1532
1377
  export { jmControl };