jmgraph 3.2.20 → 3.2.22

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.
@@ -3,10 +3,10 @@ import {jmList} from "./jmList.js";
3
3
  import {jmProperty} from './jmProperty.js';
4
4
  import {jmShadow} from "./jmShadow.js";
5
5
  import {jmGradient} from "./jmGradient.js";
6
+ import {jmFilter} from "./jmFilter.js";
6
7
  import {jmEvents} from "./jmEvents.js";
7
8
  import {jmControl} from "./jmControl.js";
8
9
  import {jmPath} from "./jmPath.js";
9
- import {jmLayer} from "./jmLayer.js";
10
10
 
11
11
  /**
12
12
  * jmGraph画图类库
@@ -286,9 +286,7 @@ export default class jmGraph extends jmControl {
286
286
  if(shape) {
287
287
  if(!args) args = {};
288
288
  args.graph = this;
289
- let obj = new shape(args);
290
- // 添加到活动图层
291
- this.addShapeToLayer(obj);
289
+ const obj = new shape(args);
292
290
  return obj;
293
291
  }
294
292
  }
@@ -414,13 +412,19 @@ export default class jmGraph extends jmControl {
414
412
  this.context.clearRect(0, 0, w, h);
415
413
  }
416
414
  else if(this.mode === 'webgl' && this.context.clear) {
417
- const color = this.style && this.style.fill? this.utils.hexToRGBA(this.style.fill): {
418
- r: 0,
419
- g: 0,
420
- b: 0,
421
- a: 0
422
- };
423
- this.context.clearColor(color.r, color.g, color.b, color.a); // 设置清空颜色缓冲时的颜色值
415
+ // 缓存 clearColor 对象,避免每帧创建
416
+ if(this.style && this.style.fill) {
417
+ const color = this.utils.hexToRGBA(this.style.fill);
418
+ this.__lastClearColor = color;
419
+ this.context.clearColor(color.r, color.g, color.b, color.a);
420
+ }
421
+ else if(!this.__lastClearColor) {
422
+ this.__lastClearColor = { r: 0, g: 0, b: 0, a: 0 };
423
+ this.context.clearColor(0, 0, 0, 0);
424
+ }
425
+ else {
426
+ this.context.clearColor(this.__lastClearColor.r, this.__lastClearColor.g, this.__lastClearColor.b, this.__lastClearColor.a);
427
+ }
424
428
  this.context.clear(this.context.COLOR_BUFFER_BIT); // 清空颜色缓冲区,也就是清空画布
425
429
  }
426
430
  }
@@ -611,232 +615,6 @@ export default class jmGraph extends jmControl {
611
615
  return this; // 支持链式调用
612
616
  }
613
617
 
614
- /**
615
- * 初始化图层系统
616
- * 创建图层管理的基础结构,包括默认图层
617
- *
618
- * @method initLayers
619
- * @private
620
- */
621
- initLayers() {
622
- if(!this.layers) {
623
- this.layers = new jmList();
624
- // 创建默认图层
625
- const defaultLayer = this.createLayer('Default Layer');
626
- this.activeLayer = defaultLayer;
627
- }
628
- }
629
-
630
- /**
631
- * 创建新图层
632
- * 图层用于组织和管理图形对象,支持可见性和锁定控制
633
- *
634
- * @method createLayer
635
- * @param {string} name 图层名称(必须唯一)
636
- * @param {object} [options] 图层选项
637
- * @param {boolean} [options.visible=true] 图层是否可见
638
- * @param {boolean} [options.locked=false] 图层是否锁定(锁定后不可交互)
639
- * @return {jmLayer} 新创建的图层
640
- */
641
- createLayer(name, options = {}) {
642
- // 参数验证
643
- if(!name || typeof name !== 'string') {
644
- console.warn('jmGraph: createLayer - 图层名称必须是非空字符串');
645
- name = `Layer_${Date.now()}`;
646
- }
647
-
648
- this.initLayers();
649
-
650
- // 检查图层名称是否已存在
651
- const existingLayer = this.getLayer(name);
652
- if(existingLayer) {
653
- console.warn(`jmGraph: 图层 "${name}" 已存在,将返回现有图层`);
654
- return existingLayer;
655
- }
656
-
657
- const layer = new jmLayer({
658
- name: name,
659
- graph: this,
660
- ...options
661
- });
662
-
663
- this.layers.add(layer);
664
- this.children.add(layer);
665
- this.needUpdate = true;
666
- return layer;
667
- }
668
-
669
- /**
670
- * 获取所有图层
671
- *
672
- * @method getLayers
673
- * @return {jmList} 图层列表
674
- */
675
- getLayers() {
676
- this.initLayers();
677
- return this.layers;
678
- }
679
-
680
- /**
681
- * 根据名称获取图层
682
- *
683
- * @method getLayer
684
- * @param {string} name 图层名称
685
- * @return {jmLayer|null} 图层对象,如果不存在则返回null
686
- */
687
- getLayer(name) {
688
- this.initLayers();
689
-
690
- if(!name) return null;
691
-
692
- let result = null;
693
- this.layers.each((i, layer) => {
694
- if(layer.name === name) {
695
- result = layer;
696
- return false; // 找到后停止遍历
697
- }
698
- });
699
- return result;
700
- }
701
-
702
- /**
703
- * 设置活动图层
704
- * 新创建的图形将自动添加到活动图层
705
- *
706
- * @method setActiveLayer
707
- * @param {string|jmLayer} layer 图层名称或图层对象
708
- * @return {jmGraph} 返回当前实例,支持链式调用
709
- */
710
- setActiveLayer(layer) {
711
- this.initLayers();
712
-
713
- // 支持传入图层名称或图层对象
714
- if(typeof layer === 'string') {
715
- layer = this.getLayer(layer);
716
- }
717
-
718
- if(!layer || !(layer instanceof jmLayer)) {
719
- console.warn('jmGraph: setActiveLayer - 无效的图层');
720
- return this;
721
- }
722
-
723
- this.activeLayer = layer;
724
- return this;
725
- }
726
-
727
- /**
728
- * 获取当前活动图层
729
- * 活动图层是新创建图形的默认容器
730
- *
731
- * @method getActiveLayer
732
- * @return {jmLayer} 当前活动图层
733
- */
734
- getActiveLayer() {
735
- this.initLayers();
736
- return this.activeLayer;
737
- }
738
-
739
- /**
740
- * 移除图层
741
- * 删除指定图层及其包含的所有图形
742
- * 注意:默认图层不可删除
743
- *
744
- * @method removeLayer
745
- * @param {string|jmLayer} layer 图层名称或图层对象
746
- * @return {boolean} 是否成功删除
747
- */
748
- removeLayer(layer) {
749
- this.initLayers();
750
-
751
- // 支持传入图层名称或图层对象
752
- if(typeof layer === 'string') {
753
- layer = this.getLayer(layer);
754
- }
755
-
756
- if(!layer) {
757
- console.warn('jmGraph: removeLayer - 图层不存在');
758
- return false;
759
- }
760
-
761
- // 禁止删除默认图层
762
- if(layer.name === 'Default Layer') {
763
- console.warn('jmGraph: 不能删除默认图层');
764
- return false;
765
- }
766
-
767
- // 如果删除的是当前活动图层,切换到默认图层
768
- if(this.activeLayer === layer) {
769
- this.activeLayer = this.getLayer('Default Layer');
770
- }
771
-
772
- this.layers.remove(layer);
773
- this.children.remove(layer);
774
- this.needUpdate = true;
775
- return true;
776
- }
777
-
778
- /**
779
- * 将形状添加到指定图层
780
- * 如果未指定图层,则添加到当前活动图层
781
- *
782
- * @method addShapeToLayer
783
- * @param {jmControl} shape 要添加的形状对象
784
- * @param {string|jmLayer} [layer] 图层名称或图层对象,默认为当前活动图层
785
- * @return {jmGraph} 返回当前实例,支持链式调用
786
- */
787
- addShapeToLayer(shape, layer) {
788
- this.initLayers();
789
-
790
- // 参数验证
791
- if(!shape) {
792
- console.warn('jmGraph: addShapeToLayer - 无效的形状对象');
793
- return this;
794
- }
795
-
796
- // 确定目标图层
797
- if(!layer) {
798
- layer = this.activeLayer;
799
- } else if(typeof layer === 'string') {
800
- layer = this.getLayer(layer);
801
- }
802
-
803
- if(!layer) {
804
- console.warn('jmGraph: addShapeToLayer - 图层不存在');
805
- return this;
806
- }
807
-
808
- layer.children.add(shape);
809
- this.needUpdate = true;
810
- return this;
811
- }
812
-
813
- /**
814
- * 从图层中移除形状
815
- *
816
- * @method removeShapeFromLayer
817
- * @param {jmControl} shape 要移除的形状对象
818
- * @return {jmGraph} 返回当前实例,支持链式调用
819
- */
820
- removeShapeFromLayer(shape) {
821
- if(!shape) {
822
- console.warn('jmGraph: removeShapeFromLayer - 无效的形状对象');
823
- return this;
824
- }
825
-
826
- // 从所有图层中查找并移除
827
- if(this.layers) {
828
- this.layers.each((i, layer) => {
829
- if(layer.children.contains(shape)) {
830
- layer.children.remove(shape);
831
- this.needUpdate = true;
832
- return false; // 找到后停止遍历
833
- }
834
- });
835
- }
836
-
837
- return this;
838
- }
839
-
840
618
  /**
841
619
  * 保存为base64图形数据
842
620
  *
@@ -903,48 +681,27 @@ export default class jmGraph extends jmControl {
903
681
  }
904
682
 
905
683
  /**
906
- * 转换为SVG字符串
907
- * 遍历所有图层和形状,生成SVG标记
908
- *
684
+ * 遍历所有形状,生成SVG标记
685
+ *
909
686
  * @method toSVG
910
687
  * @return {string} SVG字符串
911
688
  */
912
689
  toSVG() {
913
690
  // SVG头部,包含命名空间和画布尺寸
914
691
  let svg = `<svg width="${this.width}" height="${this.height}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${this.width} ${this.height}">`;
915
-
692
+
916
693
  // 添加背景色(如果有)
917
694
  if(this.style && this.style.fill) {
918
695
  svg += `<rect width="100%" height="100%" fill="${this.style.fill}"/>`;
919
696
  }
920
-
921
- // 遍历所有图层
922
- if(this.layers) {
923
- this.layers.each((i, layer) => {
924
- if(layer.visible) {
925
- // 添加图层组,方便管理
926
- svg += `<g id="${layer.name}" opacity="${layer.opacity || 1}">`;
927
-
928
- // 遍历图层中的所有形状
929
- layer.children.each((j, shape) => {
930
- if(shape.toSVG) {
931
- svg += shape.toSVG();
932
- }
933
- });
934
-
935
- svg += '</g>';
936
- }
937
- });
938
- }
939
- else {
940
- // 遍历直接添加的形状(兼容没有图层系统的情况)
941
- this.children.each((i, shape) => {
942
- if(shape.toSVG) {
943
- svg += shape.toSVG();
944
- }
945
- });
946
- }
947
-
697
+
698
+ // 遍历所有直接添加的形状
699
+ this.children.each((i, shape) => {
700
+ if(shape.toSVG) {
701
+ svg += shape.toSVG();
702
+ }
703
+ });
704
+
948
705
  svg += '</svg>';
949
706
  return svg;
950
707
  }
@@ -994,7 +751,7 @@ export default class jmGraph extends jmControl {
994
751
  // 触发刷新事件
995
752
  self.emit('update', time);
996
753
 
997
- self.__requestAnimationFrameFunHandler && self.cancelAnimationFrame(self.__requestAnimationFrameFunHandler);
754
+ // 直接 requestAnimationFrame,无需先 cancel
998
755
  self.__requestAnimationFrameFunHandler = self.requestAnimationFrame(update);
999
756
  if(callback) callback();
1000
757
  }
@@ -1010,15 +767,15 @@ export default class jmGraph extends jmControl {
1010
767
  }
1011
768
  }
1012
769
 
1013
- export {
1014
- jmGraph,
770
+ export {
771
+ jmGraph,
1015
772
  jmUtils,
1016
773
  jmList,
1017
774
  jmProperty,
1018
775
  jmShadow,
1019
776
  jmGradient,
777
+ jmFilter,
1020
778
  jmEvents,
1021
779
  jmControl,
1022
780
  jmPath,
1023
- jmLayer,
1024
781
  };
@@ -165,66 +165,75 @@ export default class jmUtils {
165
165
  * @param {function} copyHandler 复制对象回调,如果返回undefined,就走后面的逻辑,否则到这里中止
166
166
  * @return {object} 参数source的拷贝对象
167
167
  */
168
- static clone(source, target, deep = false, copyHandler = null, deepIndex = 0) {
168
+ static clone(source, target, deep = false, copyHandler = null, deepIndex = 0, cloned = null) {
169
169
  // 如果有指定回调,则用回调处理,否则走后面的复制逻辑
170
170
  if(typeof copyHandler === 'function') {
171
171
  const obj = copyHandler(source, deep, deepIndex);
172
172
  if(obj) return obj;
173
173
  }
174
- deepIndex++; // 每执行一次,需要判断最大拷贝深度
174
+
175
+ // 首次调用时初始化克隆映射表(用于处理循环引用)
176
+ if(!cloned) cloned = new WeakMap();
175
177
 
176
178
  if(typeof target === 'boolean') {
177
179
  deep = target;
178
180
  target = undefined;
179
181
  }
180
182
 
181
- // 超过100拷贝深度,直接返回
182
- if(deepIndex > 100) {
183
- return target;
183
+ // 非对象直接返回
184
+ if(!source || typeof source !== 'object') {
185
+ return typeof target !== 'undefined' ? target : source;
184
186
  }
185
187
 
186
- if(source && typeof source === 'object') {
187
- target = target || {};
188
+ // 如果source已经被克隆过,直接返回之前的克隆对象,打破循环引用
189
+ if(cloned.has(source)) return cloned.get(source);
188
190
 
191
+ // 数组处理
192
+ if(Array.isArray(source)) {
189
193
  //如果为当前泛型,则直接new
190
194
  if(this.isType(source, jmList)) {
191
195
  return new jmList(source);
192
196
  }
193
- else if(Array.isArray(source)) {
194
- //如果是深度复,则拷贝每个对象
195
- if(deep) {
196
- let dest = [];
197
- for(let i=0; i<source.length; i++) {
198
- dest.push(this.clone(source[i], target[i], deep, copyHandler, deepIndex));
199
- }
200
- return dest;
201
- }
202
- return source.slice(0);
203
- }
204
-
205
- if(source.__proto__) target.__proto__ = source.__proto__;
206
-
207
- for(let k in source) {
208
- if(k === 'constructor') continue;
209
- const v = source[k];
210
- // 不复制页面元素和class对象
211
- if(v && (v.tagName || v.getContext)) {
212
- target[k] = v;
213
- continue;
214
- }
215
-
216
- // 如果不是对象和空,则采用target的属性
217
- if(typeof target[k] === 'object' || typeof target[k] === 'undefined') {
218
- target[k] = this.clone(v, target[k], deep, copyHandler, deepIndex);
197
+ if(deep) {
198
+ let dest = [];
199
+ cloned.set(source, dest);
200
+ for(let i = 0; i < source.length; i++) {
201
+ dest.push(this.clone(source[i], undefined, deep, copyHandler, deepIndex + 1, cloned));
219
202
  }
203
+ return dest;
220
204
  }
221
- return target;
205
+ return source.slice(0);
222
206
  }
223
- else if(typeof target != 'undefined') {
224
- return target;
207
+
208
+ // 不复制页面元素和class对象(如jmControl实例等复杂对象保持引用)
209
+ if(source.tagName || source.getContext || source.emit) {
210
+ return source;
225
211
  }
226
212
 
227
- return source;
213
+ // 普通对象处理
214
+ target = target || {};
215
+ cloned.set(source, target);
216
+
217
+ // 保持原型链一致
218
+ if(source.__proto__) target.__proto__ = source.__proto__;
219
+
220
+ // 遍历自身可枚举属性(字符串键 + Symbol键),避免触发原型链上宿主对象的getter
221
+ const keys = Object.keys(source).concat(Object.getOwnPropertySymbols(source));
222
+ for(const k of keys) {
223
+ if(k === 'constructor') continue;
224
+ const v = source[k];
225
+ // 不复制页面元素和class对象
226
+ if(v && (v.tagName || v.getContext || v.emit)) {
227
+ target[k] = v;
228
+ continue;
229
+ }
230
+
231
+ // 如果不是对象和空,则采用target的属性
232
+ if(typeof target[k] === 'object' || typeof target[k] === 'undefined') {
233
+ target[k] = this.clone(v, target[k], deep, copyHandler, deepIndex + 1, cloned);
234
+ }
235
+ }
236
+ return target;
228
237
  }
229
238
 
230
239
  /**
@@ -265,7 +265,6 @@ class WeblBase {
265
265
 
266
266
  // 创建程序
267
267
  createProgram(vertexSrc, fragmentSrc) {
268
- this.context.lineWidth(1);
269
268
  return createProgram(this.context, vertexSrc, fragmentSrc);
270
269
  }
271
270
 
@@ -378,7 +377,9 @@ class WeblBase {
378
377
  // polygonIndices 顶点索引,
379
378
  earCutPointsToTriangles(points) {
380
379
  this.earCutCache = this.earCutCache || (this.earCutCache = {});
381
- const key = JSON.stringify(points);
380
+ // 快速缓存 key:用长度和首尾点坐标
381
+ const len = points.length;
382
+ const key = len + '_' + points[0].x + '_' + points[0].y + '_' + points[len-1].x + '_' + points[len-1].y;
382
383
  if (this.earCutCache[key]) return this.earCutCache[key];
383
384
 
384
385
  const ps = this.earCutPoints(points);// 切割得到3角色顶点索引,
@@ -473,31 +474,11 @@ class WeblBase {
473
474
 
474
475
  this.textureContext.fillStyle = fillStyle;
475
476
 
476
- this.textureContext.beginPath();
477
+ // 规则图形用 fillRect,比 beginPath/lineTo/fill 快
477
478
  if(!points || !points.length) {
478
- points = [];
479
- points.push({
480
- x: bounds.left,
481
- y: bounds.top
482
- });
483
- points.push({
484
- x: bounds.left + bounds.width,
485
- y: bounds.top
486
- });
487
- points.push({
488
- x: bounds.left + bounds.width,
489
- y: bounds.top + bounds.height
490
- });
491
- points.push({
492
- x: bounds.left,
493
- y: bounds.top + bounds.height
494
- });
495
- points.push({
496
- x: bounds.left,
497
- y: bounds.top
498
- });
499
- }
500
- if(points && points.length) {
479
+ this.textureContext.fillRect(0, 0, bounds.width, bounds.height);
480
+ } else {
481
+ this.textureContext.beginPath();
501
482
  for(const p of points) {
502
483
  //移至当前坐标
503
484
  if(p.m) {
@@ -506,17 +487,10 @@ class WeblBase {
506
487
  else {
507
488
  this.textureContext.lineTo(p.x - bounds.left, p.y - bounds.top);
508
489
  }
509
- }
510
- }
511
- else {
512
- this.textureContext.moveTo(0, 0);
513
- this.textureContext.lineTo(bounds.width, 0);
514
- this.textureContext.lineTo(bounds.width, bounds.height);
515
- this.textureContext.lineTo(0, bounds.height);
516
- this.textureContext.lineTo(0, 0);
490
+ }
491
+ this.textureContext.closePath();
492
+ this.textureContext.fill();
517
493
  }
518
- this.textureContext.closePath();
519
- this.textureContext.fill();
520
494
 
521
495
  const data = this.textureContext.getImageData(0, 0, canvas.width, canvas.height);
522
496
  return {
@@ -53,8 +53,14 @@ class WebglGradient {
53
53
 
54
54
  // 转为渐变为纹理
55
55
  toImageData(control, bounds, points=null) {
56
- //const key = this.key || this.toString();
57
- //if(WebglGradientTextureCache[key]) return WebglGradientTextureCache[key];
56
+ // 缓存基于渐变参数(不含 bounds,因为同一个渐变只是位置不同时纹理相同)
57
+ const gradientKey = this.toString();
58
+ if(this.__cachedData && this.__cacheKey === gradientKey &&
59
+ this.__cachedData.data && this.__cachedData.data.width === Math.ceil(bounds.width) &&
60
+ this.__cachedData.data.data && this.__cachedData.data.data.height === Math.ceil(bounds.height)) {
61
+ return this.__cachedData;
62
+ }
63
+
58
64
  if(!control.textureContext) {
59
65
  return null;
60
66
  }
@@ -72,11 +78,18 @@ class WebglGradient {
72
78
 
73
79
  const data = control.toFillTexture(gradient, bounds, points);
74
80
 
75
- //WebglGradientTextureCache[key] = data;
81
+ this.__cachedData = data;
82
+ this.__cacheKey = gradientKey;
76
83
 
77
84
  return data;
78
85
  }
79
86
 
87
+ // 当渐变参数变化时使缓存失效
88
+ invalidateCache() {
89
+ this.__cachedData = null;
90
+ this.__cacheKey = null;
91
+ }
92
+
80
93
  // 根据绘制图形的坐标计算出对应点的颜色
81
94
  /*
82
95
  toPointColors(points) {
@@ -105,12 +105,13 @@ class webgl {
105
105
  // 由具体的绘制方法处理
106
106
  }
107
107
 
108
- // 测量文本宽度
108
+ // 测量文本宽度(复用纹理 canvas 的 context)
109
109
  measureText(text) {
110
- // 使用 2D canvas 测量
110
+ const ctx = this.base.textureContext;
111
+ if(ctx && ctx.measureText) return ctx.measureText(text);
111
112
  const canvas = document.createElement('canvas');
112
- const ctx = canvas.getContext('2d');
113
- return ctx.measureText(text);
113
+ const ctx2 = canvas.getContext('2d');
114
+ return ctx2.measureText(text);
114
115
  }
115
116
 
116
117
  // 创建线性渐变