jmgraph 3.2.20 → 3.2.21

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
  }
@@ -611,232 +609,6 @@ export default class jmGraph extends jmControl {
611
609
  return this; // 支持链式调用
612
610
  }
613
611
 
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
612
  /**
841
613
  * 保存为base64图形数据
842
614
  *
@@ -903,48 +675,27 @@ export default class jmGraph extends jmControl {
903
675
  }
904
676
 
905
677
  /**
906
- * 转换为SVG字符串
907
- * 遍历所有图层和形状,生成SVG标记
908
- *
678
+ * 遍历所有形状,生成SVG标记
679
+ *
909
680
  * @method toSVG
910
681
  * @return {string} SVG字符串
911
682
  */
912
683
  toSVG() {
913
684
  // SVG头部,包含命名空间和画布尺寸
914
685
  let svg = `<svg width="${this.width}" height="${this.height}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${this.width} ${this.height}">`;
915
-
686
+
916
687
  // 添加背景色(如果有)
917
688
  if(this.style && this.style.fill) {
918
689
  svg += `<rect width="100%" height="100%" fill="${this.style.fill}"/>`;
919
690
  }
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
-
691
+
692
+ // 遍历所有直接添加的形状
693
+ this.children.each((i, shape) => {
694
+ if(shape.toSVG) {
695
+ svg += shape.toSVG();
696
+ }
697
+ });
698
+
948
699
  svg += '</svg>';
949
700
  return svg;
950
701
  }
@@ -1010,15 +761,15 @@ export default class jmGraph extends jmControl {
1010
761
  }
1011
762
  }
1012
763
 
1013
- export {
1014
- jmGraph,
764
+ export {
765
+ jmGraph,
1015
766
  jmUtils,
1016
767
  jmList,
1017
768
  jmProperty,
1018
769
  jmShadow,
1019
770
  jmGradient,
771
+ jmFilter,
1020
772
  jmEvents,
1021
773
  jmControl,
1022
774
  jmPath,
1023
- jmLayer,
1024
775
  };
@@ -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
  // 创建线性渐变