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.
@@ -9,6 +9,40 @@ class WebglPath extends WebglBase {
9
9
  this.needCut = option.needCut || false;
10
10
  this.control = option.control;
11
11
  this.points = [];
12
+ // 缓存 buffer 和纹理,避免每帧创建/销毁
13
+ this.__cachedBuffers = [];
14
+ this.__cachedTexture = null;
15
+ this.__cachedTextureKey = null;
16
+ }
17
+
18
+ // 释放缓存的 WebGL 资源
19
+ dispose() {
20
+ for(const buf of this.__cachedBuffers) {
21
+ this.deleteBuffer(buf);
22
+ }
23
+ this.__cachedBuffers = [];
24
+ if(this.__cachedTexture) {
25
+ this.deleteTexture(this.__cachedTexture);
26
+ this.__cachedTexture = null;
27
+ this.__cachedTextureKey = null;
28
+ }
29
+ }
30
+
31
+ // 获取或创建 buffer,优先复用缓存
32
+ getOrCreateBuffer(data, attr) {
33
+ let buffer = this.__cachedBuffers.find(b => b.attr === attr);
34
+ if(buffer) {
35
+ const gl = this.context;
36
+ const float32 = new Float32Array(data);
37
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer.buffer);
38
+ gl.bufferData(gl.ARRAY_BUFFER, float32, gl.DYNAMIC_DRAW);
39
+ buffer.data = data;
40
+ return buffer;
41
+ }
42
+ buffer = this.createFloat32Buffer(data);
43
+ buffer.attr = attr;
44
+ this.__cachedBuffers.push(buffer);
45
+ return buffer;
12
46
  }
13
47
 
14
48
  // 应用变换到点
@@ -63,6 +97,7 @@ class WebglPath extends WebglBase {
63
97
  endDraw() {
64
98
  if(this.points) delete this.points;
65
99
  if(this.pathPoints) delete this.pathPoints;
100
+ // 缓存的纹理保留到下次绘制(渐变可能不变)
66
101
  }
67
102
 
68
103
  // 图形封闭
@@ -74,21 +109,47 @@ class WebglPath extends WebglBase {
74
109
  }
75
110
  }
76
111
 
77
- // 绘制点数组
112
+ // 绘制点数组(使用 DYNAMIC_DRAW 复用 buffer,避免每帧 create/delete)
78
113
  writePoints(points, attr = this.program.attrs.a_position) {
79
-
80
114
  const fixedPoints = [];
81
- for(const p of points) {
82
- // 应用变换矩阵
83
- const transformedPoint = this.applyTransform(p);
84
- fixedPoints.push(
85
- transformedPoint.x + this.parentAbsoluteBounds.left,
86
- transformedPoint.y + this.parentAbsoluteBounds.top
87
- );
88
- }
89
- const vertexBuffer = this.createFloat32Buffer(fixedPoints);
115
+ const [a, b, c, d, tx, ty] = this.transformMatrix;
116
+ const isIdentity = (a === 1 && b === 0 && c === 0 && d === 1 && tx === 0 && ty === 0);
117
+ const offsetLeft = this.parentAbsoluteBounds.left;
118
+ const offsetTop = this.parentAbsoluteBounds.top;
119
+
120
+ if(isIdentity) {
121
+ // 单位矩阵时直接加偏移,避免逐点调用 applyTransform
122
+ for(let i = 0; i < points.length; i++) {
123
+ fixedPoints.push(points[i].x + offsetLeft, points[i].y + offsetTop);
124
+ }
125
+ } else {
126
+ for(const p of points) {
127
+ const transformedPoint = this.applyTransform(p);
128
+ fixedPoints.push(
129
+ transformedPoint.x + offsetLeft,
130
+ transformedPoint.y + offsetTop
131
+ );
132
+ }
133
+ }
134
+ const float32 = new Float32Array(fixedPoints);
135
+ const gl = this.context;
136
+
137
+ // 复用已有 buffer 或创建新的
138
+ if(this.__cachedBuffers.length > 0) {
139
+ // 找一个同 attr 的 buffer 复用
140
+ let buffer = this.__cachedBuffers.find(b => b.attr === attr);
141
+ if(buffer) {
142
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer.buffer);
143
+ gl.bufferData(gl.ARRAY_BUFFER, float32, gl.DYNAMIC_DRAW);
144
+ buffer.data = fixedPoints;
145
+ this.writeVertexAttrib(buffer, attr, 2, 0, 0);
146
+ return buffer;
147
+ }
148
+ }
149
+ const vertexBuffer = this.createFloat32Buffer(float32, gl.ARRAY_BUFFER, gl.DYNAMIC_DRAW);
90
150
  this.writeVertexAttrib(vertexBuffer, attr, 2, 0, 0);
91
151
  vertexBuffer.attr = attr;
152
+ this.__cachedBuffers.push(vertexBuffer);
92
153
  return vertexBuffer;
93
154
  }
94
155
 
@@ -370,9 +431,10 @@ class WebglPath extends WebglBase {
370
431
 
371
432
  // 分割成一个个规则的三角形,不规则的多边形不全割的话纹理就会没法正确覆盖
372
433
  getTriangles(points) {
373
-
374
434
  this.trianglesCache = this.trianglesCache||(this.trianglesCache={});
375
- const key = JSON.stringify(points);
435
+ // 快速缓存 key:用长度和首尾点坐标(比 JSON.stringify 快几个数量级)
436
+ const len = points.length;
437
+ const key = len + '_' + points[0].x + '_' + points[0].y + '_' + points[len-1].x + '_' + points[len-1].y;
376
438
  if(this.trianglesCache[key]) return this.trianglesCache[key];
377
439
 
378
440
  const res = [];
@@ -411,10 +473,9 @@ class WebglPath extends WebglBase {
411
473
  points = regular? points : this.pathToPoints(points);
412
474
  const buffer = this.writePoints(points);
413
475
  this.context.drawArrays(regular? this.context.LINE_LOOP: this.context.POINTS, 0, points.length);
414
- this.deleteBuffer(buffer);
476
+ // buffer 由 endDraw 统一清理
415
477
  }
416
- colorBuffer && this.deleteBuffer(colorBuffer);
417
- colorBuffer && this.disableVertexAttribArray(colorBuffer.attr);
478
+ colorBuffer && this.disableVertexAttribArray(colorBuffer && colorBuffer.attr);
418
479
  }
419
480
 
420
481
  // 填充图形
@@ -445,8 +506,7 @@ class WebglPath extends WebglBase {
445
506
 
446
507
  this.fillPolygons(points);
447
508
 
448
- colorBuffer && this.deleteBuffer(colorBuffer);
449
- colorBuffer && this.disableVertexAttribArray(colorBuffer.attr);
509
+ colorBuffer && this.disableVertexAttribArray(colorBuffer && colorBuffer.attr);
450
510
 
451
511
  }
452
512
 
@@ -456,8 +516,24 @@ class WebglPath extends WebglBase {
456
516
  fillImage(img, points, bounds) {
457
517
  if(!img) return;
458
518
 
459
- // 设置纹理
460
- const texture = img instanceof ImageData? this.createDataTexture(img) : this.createImgTexture(img);
519
+ // 对于 ImageData,生成缓存 key(基于渐变参数或 bounds),复用纹理
520
+ let texture = null;
521
+ if(img instanceof ImageData) {
522
+ const key = `${img.width}_${img.height}_${bounds.width}_${bounds.height}_${bounds.left}_${bounds.top}`;
523
+ if(this.__cachedTexture && this.__cachedTextureKey === key) {
524
+ texture = this.__cachedTexture;
525
+ } else {
526
+ texture = this.createDataTexture(img);
527
+ // 释放旧纹理
528
+ if(this.__cachedTexture) {
529
+ this.deleteTexture(this.__cachedTexture);
530
+ }
531
+ this.__cachedTexture = texture;
532
+ this.__cachedTextureKey = key;
533
+ }
534
+ } else {
535
+ texture = this.createImgTexture(img);
536
+ }
461
537
  this.context.uniform1i(this.program.uniforms.u_sample.location, 0); // 纹理单元传递给着色器
462
538
 
463
539
  // 指定纹理区域尺寸
@@ -470,7 +546,10 @@ class WebglPath extends WebglBase {
470
546
 
471
547
  this.fillTexture(points);
472
548
 
473
- this.deleteTexture(texture);
549
+ // 仅对非缓存纹理(非 ImageData)立即删除
550
+ if(!(img instanceof ImageData)) {
551
+ this.deleteTexture(texture);
552
+ }
474
553
  }
475
554
 
476
555
  fillTexture(points) {
@@ -486,23 +565,67 @@ class WebglPath extends WebglBase {
486
565
 
487
566
  // 进行多边形填充
488
567
  fillPolygons(points, isTexture = false) {
489
- if(points.length > 3) {
490
- const triangles = this.needCut? this.earCutPointsToTriangles(points): this.getTriangles(points);
491
- if(triangles.length) {
492
- for(const triangle of triangles) {
493
- this.fillPolygons(triangle, isTexture);// 这里就变成了规则的图形了
494
- }
495
- }
496
- }
497
- else {
568
+ if(points.length <= 3) {
569
+ // 3个点以下的三角形直接画
498
570
  const buffer = this.writePoints(points);
499
- // 纹理坐标
500
571
  const coordBuffer = isTexture? this.writePoints(points, this.program.attrs.a_text_coord): null;
572
+ this.context.drawArrays(this.context.TRIANGLE_FAN, 0, points.length);
573
+ return;
574
+ }
501
575
 
576
+ // 规则图形(凸多边形,如圆):直接用 TRIANGLE_FAN 一次性绘制,无需 earcut
577
+ if(this.isRegular) {
578
+ const buffer = this.writePoints(points);
579
+ const coordBuffer = isTexture? this.writePoints(points, this.program.attrs.a_text_coord): null;
502
580
  this.context.drawArrays(this.context.TRIANGLE_FAN, 0, points.length);
503
- this.deleteBuffer(buffer);
504
- coordBuffer && this.deleteBuffer(coordBuffer);
581
+ return;
505
582
  }
583
+
584
+ // 不规则图形:需要 earcut 三角化后,合并为一个大的顶点缓冲区,单次 drawArrays
585
+ const triangles = this.needCut? this.earCutPointsToTriangles(points): this.getTriangles(points);
586
+ if(!triangles.length) return;
587
+
588
+ // 合并所有三角形的顶点到一个数组
589
+ const allVertices = [];
590
+ const allTexCoords = [];
591
+ for(const triangle of triangles) {
592
+ for(const p of triangle) {
593
+ allVertices.push(p.x, p.y);
594
+ if(isTexture) allTexCoords.push(p.x, p.y);
595
+ }
596
+ }
597
+
598
+ // 一次性上传所有数据并绘制
599
+ const vertexData = new Float32Array(allVertices);
600
+ const gl = this.context;
601
+
602
+ // 复用或创建 position buffer
603
+ let posBuffer = this.__cachedBuffers.find(b => b.attr === this.program.attrs.a_position);
604
+ if(!posBuffer) {
605
+ posBuffer = this.createFloat32Buffer(vertexData, gl.ARRAY_BUFFER, gl.DYNAMIC_DRAW);
606
+ posBuffer.attr = this.program.attrs.a_position;
607
+ this.__cachedBuffers.push(posBuffer);
608
+ } else {
609
+ gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer.buffer);
610
+ gl.bufferData(gl.ARRAY_BUFFER, vertexData, gl.DYNAMIC_DRAW);
611
+ }
612
+ this.writeVertexAttrib(posBuffer, this.program.attrs.a_position, 2, 0, 0);
613
+
614
+ if(isTexture && allTexCoords.length) {
615
+ const texData = new Float32Array(allTexCoords);
616
+ let texBuffer = this.__cachedBuffers.find(b => b.attr === this.program.attrs.a_text_coord);
617
+ if(!texBuffer) {
618
+ texBuffer = this.createFloat32Buffer(texData, gl.ARRAY_BUFFER, gl.DYNAMIC_DRAW);
619
+ texBuffer.attr = this.program.attrs.a_text_coord;
620
+ this.__cachedBuffers.push(texBuffer);
621
+ } else {
622
+ gl.bindBuffer(gl.ARRAY_BUFFER, texBuffer.buffer);
623
+ gl.bufferData(gl.ARRAY_BUFFER, texData, gl.DYNAMIC_DRAW);
624
+ }
625
+ this.writeVertexAttrib(texBuffer, this.program.attrs.a_text_coord, 2, 0, 0);
626
+ }
627
+
628
+ gl.drawArrays(gl.TRIANGLES, 0, allVertices.length / 2);
506
629
  }
507
630
 
508
631
  // 填充图形
@@ -22,7 +22,7 @@ export default class jmLabel extends jmControl {
22
22
  this.style.textAlign = this.style.textAlign || 'left';
23
23
  //文字垂直对齐
24
24
  this.style.textBaseline = this.style.textBaseline || 'middle';
25
- this.text = params.text || '';
25
+ this.text = params.text || params.value || '';
26
26
 
27
27
  this.center = params.center || null;
28
28
  }
@@ -311,67 +311,6 @@ export default class jmLabel extends jmControl {
311
311
  }
312
312
  }
313
313
  }
314
- //如果有指定边框,则画出边框
315
- if(this.style.border) {
316
- //如果指定了边框样式
317
- if(this.style.border.style) {
318
- this.context.save && this.context.save();
319
- this.setStyle(this.style.border.style);
320
- }
321
- if(this.mode === '2d') {
322
- this.context.moveTo(this.points[0].x + bounds.left,this.points[0].y + bounds.top);
323
- if(this.style.border.top) {
324
- this.context.lineTo(this.points[1].x + bounds.left,this.points[1].y + bounds.top);
325
- }
326
-
327
- if(this.style.border.right) {
328
- this.context.moveTo(this.points[1].x + bounds.left,this.points[1].y + bounds.top);
329
- this.context.lineTo(this.points[2].x + bounds.left,this.points[2].y + bounds.top);
330
- }
331
-
332
- if(this.style.border.bottom) {
333
- this.context.moveTo(this.points[2].x + bounds.left,this.points[2].y + bounds.top);
334
- this.context.lineTo(this.points[3].x + bounds.left,this.points[3].y + bounds.top);
335
- }
336
-
337
- if(this.style.border.left) {
338
- this.context.moveTo(this.points[3].x + bounds.left,this.points[3].y + bounds.top);
339
- this.context.lineTo(this.points[0].x + bounds.left,this.points[0].y + bounds.top);
340
- }
341
- }
342
- else {
343
- const points = [];
344
- if(this.style.border.top) {
345
- points.push(this.points[0]);
346
- points.push(this.points[1]);
347
- }
348
-
349
- if(this.style.border.right) {
350
- points.push({
351
- ...this.points[1],
352
- m: true
353
- });
354
- points.push(this.points[2]);
355
- }
356
-
357
- if(this.style.border.bottom) {
358
- points.push({
359
- ...this.points[2],
360
- m: true
361
- });
362
- points.push(this.points[3]);
363
- }
364
-
365
- if(this.style.border.left) {
366
- points.push({
367
- ...this.points[3],
368
- m: true
369
- });
370
- points.push(this.points[0]);
371
- }
372
- points.length && this.webglControl && this.webglControl.stroke(points);
373
- }
374
- }
375
314
  }
376
315
 
377
316
  endDraw() {
@@ -8,6 +8,7 @@ import {jmLine} from './jmLine.js';
8
8
  * @class jmRect
9
9
  * @extends jmPath
10
10
  * @param {object} params 参数 position=矩形左上角顶点坐标,width=宽,height=高,radius=边角弧度
11
+ * radius支持数字(四角相同)或对象 { topLeft, topRight, bottomRight, bottomLeft }
11
12
  */
12
13
  export default class jmRect extends jmPath {
13
14
 
@@ -17,12 +18,24 @@ export default class jmRect extends jmPath {
17
18
  super(params, t);
18
19
 
19
20
  this.style.close = true;
20
- this.radius = params.radius || this.style.radius || 0;
21
+ const r = params.radius || this.style.radius || this.style.borderRadius || 0;
22
+ if(typeof r === 'object' && r !== null) {
23
+ // 四角独立圆角
24
+ this.radius = {
25
+ topLeft: Number(r.topLeft) || 0,
26
+ topRight: Number(r.topRight) || 0,
27
+ bottomRight: Number(r.bottomRight) || 0,
28
+ bottomLeft: Number(r.bottomLeft) || 0
29
+ };
30
+ }
31
+ else {
32
+ this.radius = r;
33
+ }
21
34
  }
22
35
  /**
23
- * 圆角半径
36
+ * 圆角半径,支持数字或四角独立对象
24
37
  * @property radius
25
- * @type {number}
38
+ * @type {number|object}
26
39
  */
27
40
  get radius() {
28
41
  return this.property('radius');
@@ -30,6 +43,36 @@ export default class jmRect extends jmPath {
30
43
  set radius(v) {
31
44
  this.needUpdate = true;
32
45
  return this.property('radius', v);
46
+ }
47
+
48
+ /**
49
+ * 获取规范化的圆角值(四角独立)
50
+ * @returns {object} { topLeft, topRight, bottomRight, bottomLeft }
51
+ */
52
+ getNormalizedRadius() {
53
+ const r = this.radius;
54
+ if(typeof r === 'number') {
55
+ const v = Math.max(0, r);
56
+ return { topLeft: v, topRight: v, bottomRight: v, bottomLeft: v };
57
+ }
58
+ if(typeof r === 'object' && r !== null) {
59
+ return {
60
+ topLeft: Math.max(0, Number(r.topLeft) || 0),
61
+ topRight: Math.max(0, Number(r.topRight) || 0),
62
+ bottomRight: Math.max(0, Number(r.bottomRight) || 0),
63
+ bottomLeft: Math.max(0, Number(r.bottomLeft) || 0)
64
+ };
65
+ }
66
+ return { topLeft: 0, topRight: 0, bottomRight: 0, bottomLeft: 0 };
67
+ }
68
+
69
+ /**
70
+ * 检查是否有圆角
71
+ * @returns {boolean}
72
+ */
73
+ hasRadius() {
74
+ const nr = this.getNormalizedRadius();
75
+ return nr.topLeft > 0 || nr.topRight > 0 || nr.bottomRight > 0 || nr.bottomLeft > 0;
33
76
  }
34
77
 
35
78
  /**
@@ -92,7 +135,7 @@ export default class jmRect extends jmPath {
92
135
 
93
136
  /**
94
137
  * 初始化图形点
95
- * 如果有边角弧度则类型圆绝计算其描点
138
+ * 支持四角独立圆角,借助圆弧对象计算描点
96
139
  *
97
140
  * @method initPoints
98
141
  * @private
@@ -109,32 +152,67 @@ export default class jmRect extends jmPath {
109
152
  this.dottedLine = this.graph.createShape(jmLine, {style: this.style});
110
153
  }
111
154
 
112
- //如果有边界弧度则借助圆弧对象计算描点
113
- if(location.radius && location.radius < location.width/2 && location.radius < location.height/2) {
155
+ const nr = this.getNormalizedRadius();
156
+ const hasRadius = this.hasRadius();
157
+
158
+ // 如果有圆角(支持四角独立),借助圆弧对象计算描点
159
+ if(hasRadius) {
114
160
  let q = Math.PI / 2;
115
- let arc = this.graph.createShape(jmArc,{radius:location.radius,anticlockwise:false});
116
- arc.center = {x:location.left + location.radius,y:location.top+location.radius};
117
- arc.startAngle = Math.PI;
118
- arc.endAngle = Math.PI + q;
119
- let ps1 = arc.initPoints();
120
-
121
- arc = this.graph.createShape(jmArc,{radius:location.radius,anticlockwise:false});
122
- arc.center = {x:p2.x - location.radius,y:p2.y + location.radius};
123
- arc.startAngle = Math.PI + q;
124
- arc.endAngle = Math.PI * 2;
125
- let ps2 = arc.initPoints();
126
-
127
- arc = this.graph.createShape(jmArc,{radius:location.radius,anticlockwise:false});
128
- arc.center = {x:p3.x - location.radius,y:p3.y - location.radius};
129
- arc.startAngle = 0;
130
- arc.endAngle = q;
131
- let ps3 = arc.initPoints();
132
-
133
- arc = this.graph.createShape(jmArc,{radius:location.radius,anticlockwise:false});
134
- arc.center = {x:p4.x + location.radius,y:p4.y - location.radius};
135
- arc.startAngle = q;
136
- arc.endAngle = Math.PI;
137
- let ps4 = arc.initPoints();
161
+
162
+ // 限制圆角不超过短边的一半
163
+ const maxR = Math.min(location.width / 2, location.height / 2);
164
+ const rtl = Math.min(nr.topLeft, maxR);
165
+ const rtr = Math.min(nr.topRight, maxR);
166
+ const rbr = Math.min(nr.bottomRight, maxR);
167
+ const rbl = Math.min(nr.bottomLeft, maxR);
168
+
169
+ // 左上角圆弧
170
+ if(rtl > 0) {
171
+ let arc = this.graph.createShape(jmArc,{radius:rtl,anticlockwise:false});
172
+ arc.center = {x:location.left + rtl, y:location.top + rtl};
173
+ arc.startAngle = Math.PI;
174
+ arc.endAngle = Math.PI + q;
175
+ var ps1 = arc.initPoints();
176
+ }
177
+ else {
178
+ var ps1 = [p1];
179
+ }
180
+
181
+ // 右上角圆弧
182
+ if(rtr > 0) {
183
+ let arc = this.graph.createShape(jmArc,{radius:rtr,anticlockwise:false});
184
+ arc.center = {x:p2.x - rtr, y:p2.y + rtr};
185
+ arc.startAngle = Math.PI + q;
186
+ arc.endAngle = Math.PI * 2;
187
+ var ps2 = arc.initPoints();
188
+ }
189
+ else {
190
+ var ps2 = [p2];
191
+ }
192
+
193
+ // 右下角圆弧
194
+ if(rbr > 0) {
195
+ let arc = this.graph.createShape(jmArc,{radius:rbr,anticlockwise:false});
196
+ arc.center = {x:p3.x - rbr, y:p3.y - rbr};
197
+ arc.startAngle = 0;
198
+ arc.endAngle = q;
199
+ var ps3 = arc.initPoints();
200
+ }
201
+ else {
202
+ var ps3 = [p3];
203
+ }
204
+
205
+ // 左下角圆弧
206
+ if(rbl > 0) {
207
+ let arc = this.graph.createShape(jmArc,{radius:rbl,anticlockwise:false});
208
+ arc.center = {x:p4.x + rbl, y:p4.y - rbl};
209
+ arc.startAngle = q;
210
+ arc.endAngle = Math.PI;
211
+ var ps4 = arc.initPoints();
212
+ }
213
+ else {
214
+ var ps4 = [p4];
215
+ }
138
216
  this.points = ps1.concat(ps2,ps3,ps4);
139
217
  }
140
218
  else {