@teachinglab/omd 0.7.30 → 0.7.31

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.
@@ -20,6 +20,7 @@ export class ResizeHandleManager {
20
20
  this.selectionBorderColor = '#007bff';
21
21
  this.selectionBorderWidth = 2;
22
22
  this.selectionBorder = null;
23
+ this.selectionBorderPadding = { top: 8, right: 8, bottom: 8, left: 8 };
23
24
 
24
25
  // Resize constraints
25
26
  this.minSize = 20;
@@ -270,11 +271,19 @@ export class ResizeHandleManager {
270
271
  * @param {string} [style.dasharray] - SVG stroke-dasharray value (e.g. '4,2' or 'none')
271
272
  * @param {number} [style.cornerRadius]- rx/ry corner radius of the border rect
272
273
  */
273
- setSelectionStyle({ color, width, dasharray, cornerRadius } = {}) {
274
+ setSelectionStyle({ color, width, dasharray, cornerRadius, padding, paddingX, paddingY, paddingTop, paddingRight, paddingBottom, paddingLeft } = {}) {
274
275
  if (color !== undefined) this.selectionBorderColor = color;
275
276
  if (width !== undefined) this.selectionBorderWidth = width;
276
277
  if (dasharray !== undefined) this.selectionBorderDasharray = dasharray;
277
278
  if (cornerRadius !== undefined) this.selectionBorderCornerRadius = cornerRadius;
279
+ const basePadding = padding ?? null;
280
+ const nextPadding = {
281
+ top: paddingTop ?? paddingY ?? basePadding ?? this.selectionBorderPadding.top,
282
+ right: paddingRight ?? paddingX ?? basePadding ?? this.selectionBorderPadding.right,
283
+ bottom: paddingBottom ?? paddingY ?? basePadding ?? this.selectionBorderPadding.bottom,
284
+ left: paddingLeft ?? paddingX ?? basePadding ?? this.selectionBorderPadding.left
285
+ };
286
+ this.selectionBorderPadding = nextPadding;
278
287
 
279
288
  // Re-apply to live border if one exists
280
289
  if (this.selectionBorder) {
@@ -286,6 +295,7 @@ export class ResizeHandleManager {
286
295
  this.selectionBorder.setAttribute('rx', this.selectionBorderCornerRadius);
287
296
  this.selectionBorder.setAttribute('ry', this.selectionBorderCornerRadius);
288
297
  }
298
+ this._updateSelectionBorder();
289
299
  }
290
300
  }
291
301
 
@@ -385,12 +395,12 @@ export class ResizeHandleManager {
385
395
  if (!this.selectionBorder || !this.selectedElement) return;
386
396
 
387
397
  const bounds = this._getTransformedBounds();
388
- const padding = 3;
398
+ const padding = this.selectionBorderPadding;
389
399
 
390
- this.selectionBorder.setAttribute('x', bounds.x - padding);
391
- this.selectionBorder.setAttribute('y', bounds.y - padding);
392
- this.selectionBorder.setAttribute('width', bounds.width + padding * 2);
393
- this.selectionBorder.setAttribute('height', bounds.height + padding * 2);
400
+ this.selectionBorder.setAttribute('x', bounds.x - padding.left);
401
+ this.selectionBorder.setAttribute('y', bounds.y - padding.top);
402
+ this.selectionBorder.setAttribute('width', bounds.width + padding.left + padding.right);
403
+ this.selectionBorder.setAttribute('height', bounds.height + padding.top + padding.bottom);
394
404
  }
395
405
 
396
406
  /**
@@ -50,6 +50,13 @@ export class PointerTool extends Tool {
50
50
  this.dragStartPoint = null;
51
51
  this.potentialDeselect = null;
52
52
  this.hasSeparatedForDrag = false;
53
+
54
+ this.selectionBoundsPadding = {
55
+ top: options.selectionBoundsPaddingTop ?? options.selectionBoundsPaddingY ?? options.selectionBoundsPadding ?? 14,
56
+ right: options.selectionBoundsPaddingRight ?? options.selectionBoundsPaddingX ?? options.selectionBoundsPadding ?? 14,
57
+ bottom: options.selectionBoundsPaddingBottom ?? options.selectionBoundsPaddingY ?? options.selectionBoundsPadding ?? 14,
58
+ left: options.selectionBoundsPaddingLeft ?? options.selectionBoundsPaddingX ?? options.selectionBoundsPadding ?? 14
59
+ };
53
60
 
54
61
  // Initialize resize handle manager for OMD visuals
55
62
  this.resizeHandleManager = new ResizeHandleManager(canvas);
@@ -595,12 +602,12 @@ export class PointerTool extends Tool {
595
602
  if (!hasPoints) return null;
596
603
 
597
604
  // Add padding to match the visual box
598
- const padding = 8;
605
+ const padding = this.selectionBoundsPadding;
599
606
  return {
600
- x: minX - padding,
601
- y: minY - padding,
602
- width: (maxX + padding) - (minX - padding),
603
- height: (maxY + padding) - (minY - padding)
607
+ x: minX - padding.left,
608
+ y: minY - padding.top,
609
+ width: (maxX + padding.right) - (minX - padding.left),
610
+ height: (maxY + padding.bottom) - (minY - padding.top)
604
611
  };
605
612
  }
606
613
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teachinglab/omd",
3
- "version": "0.7.30",
3
+ "version": "0.7.31",
4
4
  "description": "omd",
5
5
  "main": "./index.js",
6
6
  "module": "./index.js",
package/src/omdShapes.js CHANGED
@@ -2,6 +2,39 @@
2
2
  import { omdColor } from "./omdColor.js";
3
3
  import { jsvgGroup, jsvgPath, jsvgLine, jsvgTextLine, jsvgEllipse } from "@teachinglab/jsvg";
4
4
 
5
+ const DEFAULT_SHAPE_PADDING = 10;
6
+ const LABEL_SHAPE_PADDING = 38;
7
+
8
+ function getShapePadding(showLabels) {
9
+ return showLabels ? LABEL_SHAPE_PADDING : DEFAULT_SHAPE_PADDING;
10
+ }
11
+
12
+ function getClosedPolygonVertices(shapePath) {
13
+ const points = Array.isArray(shapePath?.points) ? shapePath.points : [];
14
+ if (points.length <= 1) return points.slice();
15
+
16
+ const first = points[0];
17
+ const last = points[points.length - 1];
18
+ const isClosed = first.x === last.x && first.y === last.y;
19
+ return isClosed ? points.slice(0, -1) : points.slice();
20
+ }
21
+
22
+ function getPolygonCentroid(points) {
23
+ if (!points.length) return { x: 0, y: 0 };
24
+
25
+ let sumX = 0;
26
+ let sumY = 0;
27
+ for (const point of points) {
28
+ sumX += point.x;
29
+ sumY += point.y;
30
+ }
31
+
32
+ return {
33
+ x: sumX / points.length,
34
+ y: sumY / points.length
35
+ };
36
+ }
37
+
5
38
  export class omdRightTriangle extends jsvgGroup
6
39
  {
7
40
  constructor()
@@ -69,8 +102,9 @@ export class omdRightTriangle extends jsvgGroup
69
102
  updateLayout()
70
103
  {
71
104
  // Center the triangle within the viewBox
72
- const offsetX = 10; // Left margin
73
- const offsetY = this.unitScale * this.verticalLeg + 10; // Bottom margin
105
+ const padding = getShapePadding(this.showLabels);
106
+ const offsetX = padding;
107
+ const offsetY = this.unitScale * this.verticalLeg + padding;
74
108
 
75
109
  this.shapePath.clearPoints();
76
110
  this.shapePath.addPoint(offsetX, offsetY);
@@ -92,8 +126,8 @@ export class omdRightTriangle extends jsvgGroup
92
126
  }
93
127
 
94
128
  // Set viewBox to center the shape
95
- this.width = this.unitScale * this.horizontalLeg + 20;
96
- this.height = this.unitScale * this.verticalLeg + 20;
129
+ this.width = this.unitScale * this.horizontalLeg + padding * 2;
130
+ this.height = this.unitScale * this.verticalLeg + padding * 2;
97
131
  this.svgObject.setAttribute('viewBox', `0 0 ${this.width} ${this.height}`);
98
132
  }
99
133
  }
@@ -150,8 +184,9 @@ export class omdIsoscelesTriangle extends jsvgGroup
150
184
 
151
185
  const baseWidth = this.unitScale * this.triangleBase;
152
186
  const triangleHeight = this.unitScale * this.triangleHeight;
153
- const offsetX = 10;
154
- const offsetY = triangleHeight + 10;
187
+ const padding = getShapePadding(this.showLabels);
188
+ const offsetX = padding;
189
+ const offsetY = triangleHeight + padding;
155
190
 
156
191
  this.shapePath.addPoint( offsetX, offsetY );
157
192
  this.shapePath.addPoint( offsetX + baseWidth, offsetY );
@@ -169,8 +204,8 @@ export class omdIsoscelesTriangle extends jsvgGroup
169
204
  }
170
205
 
171
206
  // Set dimensions and viewBox for API compatibility
172
- this.width = this.unitScale * this.triangleBase + 20;
173
- this.height = this.unitScale * this.triangleHeight + 20;
207
+ this.width = this.unitScale * this.triangleBase + padding * 2;
208
+ this.height = this.unitScale * this.triangleHeight + padding * 2;
174
209
  this.svgObject.setAttribute('viewBox', `0 0 ${this.width} ${this.height}`);
175
210
  }
176
211
  }
@@ -222,8 +257,9 @@ export class omdRectangle extends jsvgGroup
222
257
  updateLayout()
223
258
  {
224
259
  // Center the rectangle within the viewBox
225
- const offsetX = 10;
226
- const offsetY = this.unitScale * this.rectHeight + 10;
260
+ const padding = getShapePadding(this.showLabels);
261
+ const offsetX = padding;
262
+ const offsetY = this.unitScale * this.rectHeight + padding;
227
263
 
228
264
  this.shapePath.clearPoints();
229
265
  this.shapePath.addPoint(offsetX, offsetY);
@@ -245,8 +281,8 @@ export class omdRectangle extends jsvgGroup
245
281
  }
246
282
 
247
283
  // Set viewBox
248
- this.width = this.unitScale * this.rectWidth + 20;
249
- this.height = this.unitScale * this.rectHeight + 20;
284
+ this.width = this.unitScale * this.rectWidth + padding * 2;
285
+ this.height = this.unitScale * this.rectHeight + padding * 2;
250
286
  this.svgObject.setAttribute('viewBox', `0 0 ${this.width} ${this.height}`);
251
287
  }
252
288
  }
@@ -367,19 +403,37 @@ export class omdCircle extends jsvgGroup
367
403
  updateLayout()
368
404
  {
369
405
  const diameter = 2.0 * this.radius * this.unitScale;
406
+ const radiusPx = this.radius * this.unitScale;
407
+ const centerX = radiusPx + 10;
408
+ const centerY = radiusPx + 10;
370
409
 
371
410
  this.shapePath.setWidthAndHeight( diameter, diameter );
372
- this.shapePath.setPosition( this.radius * this.unitScale + 10, this.radius * this.unitScale + 10 );
411
+ this.shapePath.setPosition( centerX, centerY );
373
412
 
374
413
  this.labelsHolder.removeAllChildren();
375
414
  if ( this.showLabels )
376
415
  {
416
+ const angleRadians = -Math.PI / 6;
417
+ const endX = centerX + Math.cos(angleRadians) * radiusPx * 0.82;
418
+ const endY = centerY + Math.sin(angleRadians) * radiusPx * 0.82;
419
+
420
+ const radiusLine = new jsvgLine();
421
+ radiusLine.setStrokeColor("black");
422
+ radiusLine.setStrokeWidth(1.5);
423
+ radiusLine.setEndpoints(centerX, centerY, endX, endY);
424
+ this.labelsHolder.addChild(radiusLine);
425
+
377
426
  const label = new jsvgTextLine();
378
427
  label.setAlignment("center");
379
428
  label.setFontFamily( "Albert Sans" );
380
429
  label.setFontColor( "black" );
381
430
  label.setFontSize( 12 );
382
- label.setPosition( this.radius * this.unitScale + 10, this.radius * this.unitScale + 14 );
431
+ label.svgObject.setAttribute('dominant-baseline', 'middle');
432
+ label.setRotation(angleRadians * 180 / Math.PI);
433
+ label.setPosition(
434
+ (centerX + endX) * 0.5 + 3,
435
+ (centerY + endY) * 0.5 - 4
436
+ );
383
437
  label.setText( `r=${this.radius}` );
384
438
  this.labelsHolder.addChild( label );
385
439
  }
@@ -440,6 +494,10 @@ export class omdRegularPolygon extends jsvgGroup
440
494
  updateLayout()
441
495
  {
442
496
  this.shapePath.clearPoints();
497
+ const radiusPx = this.radius * this.unitScale;
498
+ const padding = this.showLabels ? 48 : 14;
499
+ const centerX = radiusPx + padding;
500
+ const centerY = radiusPx + padding;
443
501
 
444
502
  var angleOffset = 0;
445
503
  if ( this.numberOfSides % 2 == 1 )
@@ -453,8 +511,8 @@ export class omdRegularPolygon extends jsvgGroup
453
511
  {
454
512
  var A = -2.0 * Math.PI / this.numberOfSides * i;
455
513
  A += angleOffset;
456
- var pX = Math.cos(A) * this.radius * this.unitScale;
457
- var pY = Math.sin(A) * this.radius * this.unitScale;
514
+ var pX = centerX + Math.cos(A) * radiusPx;
515
+ var pY = centerY + Math.sin(A) * radiusPx;
458
516
  this.shapePath.addPoint( pX, pY );
459
517
  }
460
518
 
@@ -472,9 +530,9 @@ export class omdRegularPolygon extends jsvgGroup
472
530
  }
473
531
 
474
532
  // Set dimensions and viewBox for API compatibility
475
- this.width = 2.0 * this.radius * this.unitScale + 20;
476
- this.height = 2.0 * this.radius * this.unitScale + 20;
477
- this.svgObject.setAttribute('viewBox', `-${this.width/2} -${this.height/2} ${this.width} ${this.height}`);
533
+ this.width = 2.0 * radiusPx + padding * 2;
534
+ this.height = 2.0 * radiusPx + padding * 2;
535
+ this.svgObject.setAttribute('viewBox', `0 0 ${this.width} ${this.height}`);
478
536
  }
479
537
  }
480
538
 
@@ -489,76 +547,77 @@ export class omdShapeLabelSet extends jsvgGroup
489
547
 
490
548
  initializeWithShapePath( shapePath, labelTextArray=[] )
491
549
  {
492
- // this assumes counterclockwise path
550
+ const polygonPoints = getClosedPolygonVertices(shapePath);
551
+ const centroid = getPolygonCentroid(polygonPoints);
493
552
 
494
553
  // create lines
495
- for( var i=0; i<shapePath.points.length-1; i++ )
554
+ for (let i = 0; i < polygonPoints.length; i++)
496
555
  {
497
- if ( i >= labelTextArray.length || labelTextArray[i].length == 0 )
556
+ if ( i >= labelTextArray.length )
557
+ continue;
558
+
559
+ const labelValue = labelTextArray[i];
560
+ const labelString = String(labelValue ?? "");
561
+ if ( labelString.length === 0 )
498
562
  continue;
499
563
 
500
564
  // get points
501
- var point0 = shapePath.points[i];
502
- var x0 = point0.x;
503
- var y0 = point0.y;
565
+ const point0 = polygonPoints[i];
566
+ const x0 = point0.x;
567
+ const y0 = point0.y;
504
568
 
505
- var point1 = shapePath.points[i+1];
506
- var x1 = point1.x;
507
- var y1 = point1.y;
569
+ const point1 = polygonPoints[(i + 1) % polygonPoints.length];
570
+ const x1 = point1.x;
571
+ const y1 = point1.y;
508
572
 
509
573
  // get normalized vector
510
- var dX = x1 - x0;
511
- var dY = y1 - y0;
512
- var L = Math.sqrt( dX*dX + dY*dY );
574
+ let dX = x1 - x0;
575
+ let dY = y1 - y0;
576
+ const L = Math.sqrt( dX*dX + dY*dY );
513
577
  dX /= L;
514
578
  dY /= L;
515
579
 
516
- // get normal
517
- var normalX = -1.0 * dY;
518
- var normalY = dX;
580
+ // get outward normal based on the polygon centroid rather than path winding
581
+ const centerX = (x0 + x1) / 2.0;
582
+ const centerY = (y0 + y1) / 2.0;
583
+ let normalX = -1.0 * dY;
584
+ let normalY = dX;
585
+ const toMidX = centerX - centroid.x;
586
+ const toMidY = centerY - centroid.y;
587
+ if ( toMidX * normalX + toMidY * normalY < 0 )
588
+ {
589
+ normalX *= -1.0;
590
+ normalY *= -1.0;
591
+ }
519
592
 
520
593
  // make line
521
- var labelLine = new jsvgLine();
522
- var newLineX0 = x0 + normalX * 10.0;
523
- var newLineY0 = y0 + normalY * 10.0;
524
- var newLineX1 = x1 + normalX * 10.0;
525
- var newLineY1 = y1 + normalY * 10.0;
594
+ const labelLine = new jsvgLine();
595
+ const newLineX0 = x0 + normalX * 10.0;
596
+ const newLineY0 = y0 + normalY * 10.0;
597
+ const newLineX1 = x1 + normalX * 10.0;
598
+ const newLineY1 = y1 + normalY * 10.0;
526
599
  labelLine.setEndpoints( newLineX0, newLineY0, newLineX1, newLineY1 );
527
600
  this.addChild( labelLine );
528
601
 
529
- // center point
530
- var centerX = (x0 + x1) / 2.0;
531
- var centerY = (y0 + y1) / 2.0;
532
-
533
- var angle = Math.atan2( dY, dX );
534
- angle *= 180 / Math.PI;
535
- angle += 180.0;
536
- angle = angle % 360.0;
537
- var originalAngle = angle;
538
-
539
- // flip upside-down text
540
- var offset = 0;
541
- if ( angle > 90 && angle < 270 )
542
- {
543
- angle -= 180.0;
544
- offset += 10.0;
545
- }
602
+ let angle = Math.atan2( dY, dX ) * 180 / Math.PI;
603
+ if ( angle > 90 || angle < -90 )
604
+ angle += 180.0;
546
605
 
547
606
  // label text
548
- var textCenterX = centerX + normalX * (15.0+offset);
549
- var textCenterY = centerY + normalY * (15.0+offset);
550
- var labelText = new jsvgTextLine();
607
+ const textCenterX = centerX + normalX * 18.0;
608
+ const textCenterY = centerY + normalY * 18.0;
609
+ const labelText = new jsvgTextLine();
551
610
  labelText.setAlignment("center");
552
611
  labelText.setFontFamily( "Albert Sans" );
553
612
  labelText.setFontColor( "black" );
554
613
  labelText.setFontSize( 12 );
555
614
  labelText.setPosition( textCenterX, textCenterY );
556
615
  labelText.setRotation( angle );
616
+ labelText.svgObject.setAttribute('dominant-baseline', 'middle');
557
617
  this.addChild( labelText );
558
618
 
559
- labelText.setText( labelTextArray[i] );
619
+ labelText.setText( labelString );
560
620
  }
561
621
  }
562
622
 
563
623
  }
564
-