@teachinglab/omd 0.6.5 → 0.6.7
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.
- package/index.js +0 -3
- package/package.json +2 -2
- package/src/index.js +4 -1
- package/src/json-schemas.md +139 -18
- package/src/omd.js +8 -0
- package/src/omdDoubleNumberLine.js +72 -0
- package/src/omdDoubleTapeDiagram.js +115 -0
- package/src/omdFactory.js +4 -0
- package/src/omdNumberLine.js +173 -54
- package/src/omdTapeDiagram.js +161 -87
package/index.js
CHANGED
|
@@ -11,9 +11,6 @@
|
|
|
11
11
|
* const { omdTable } = await import('@teachinglab/omd')
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
// Ensure math.js is available globally before loading modules that rely on window/global math
|
|
15
|
-
import './omd/utils/registerMathGlobal.js';
|
|
16
|
-
|
|
17
14
|
// Import everything first to ensure proper loading order
|
|
18
15
|
import * as omdCore from './omd/core/index.js';
|
|
19
16
|
import * as omdCanvas from './canvas/index.js';
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teachinglab/omd",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.7",
|
|
4
4
|
"description": "omd",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"module": "./index.js",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": "./index.js",
|
|
10
|
+
"./register-math": "./omd/utils/registerMathGlobal.js",
|
|
10
11
|
"./package.json": "./package.json"
|
|
11
12
|
},
|
|
12
13
|
"files": [
|
|
@@ -56,4 +57,3 @@
|
|
|
56
57
|
"vite": "^5.4.0"
|
|
57
58
|
}
|
|
58
59
|
}
|
|
59
|
-
|
package/src/index.js
CHANGED
|
@@ -3,11 +3,13 @@ export { omdTable } from './omdTable.js';
|
|
|
3
3
|
export { omdBalanceHanger } from './omdBalanceHanger.js';
|
|
4
4
|
export { omdCoordinatePlane } from './omdCoordinatePlane.js';
|
|
5
5
|
export { omdTapeDiagram } from './omdTapeDiagram.js';
|
|
6
|
+
export { omdDoubleTapeDiagram } from './omdDoubleTapeDiagram.js';
|
|
6
7
|
export { omdTileEquation } from './omdTileEquation.js';
|
|
7
8
|
|
|
8
9
|
// OMD Charts and Diagrams
|
|
9
10
|
export { omdRatioChart } from './omdRatioChart.js';
|
|
10
11
|
export { omdNumberLine } from './omdNumberLine.js';
|
|
12
|
+
export { omdDoubleNumberLine } from './omdDoubleNumberLine.js';
|
|
11
13
|
export { omdNumberTile } from './omdNumberTile.js';
|
|
12
14
|
|
|
13
15
|
// OMD Mathematical Components
|
|
@@ -52,7 +54,8 @@ export default {
|
|
|
52
54
|
omdTapeDiagram: () => import('./omdTapeDiagram.js').then(m => m.omdTapeDiagram),
|
|
53
55
|
omdTileEquation: () => import('./omdTileEquation.js').then(m => m.omdTileEquation),
|
|
54
56
|
omdRatioChart: () => import('./omdRatioChart.js').then(m => m.omdRatioChart),
|
|
55
|
-
omdNumberLine: () => import('./omdNumberLine.js').then(m => m.omdNumberLine)
|
|
57
|
+
omdNumberLine: () => import('./omdNumberLine.js').then(m => m.omdNumberLine),
|
|
58
|
+
omdDoubleNumberLine: () => import('./omdDoubleNumberLine.js').then(m => m.omdDoubleNumberLine)
|
|
56
59
|
},
|
|
57
60
|
|
|
58
61
|
// Mathematical expressions
|
package/src/json-schemas.md
CHANGED
|
@@ -105,24 +105,85 @@ This document provides schemas and examples for the `loadFromJSON` method used i
|
|
|
105
105
|
### Schema
|
|
106
106
|
```json
|
|
107
107
|
{
|
|
108
|
-
"
|
|
109
|
-
"
|
|
110
|
-
"
|
|
111
|
-
"
|
|
112
|
-
"unitWidth": "number"
|
|
108
|
+
"title": "string (optional)",
|
|
109
|
+
"values": ["array (required) - can be strings or objects with {value, showLabel, color}"],
|
|
110
|
+
"labelSet": ["array (optional) - objects with {startIndex, endIndex, label, showBelow}"],
|
|
111
|
+
"totalWidth": "number (optional, default: 300)"
|
|
113
112
|
}
|
|
114
113
|
```
|
|
115
114
|
|
|
116
|
-
### Example
|
|
115
|
+
### Example with Simple Values
|
|
117
116
|
```json
|
|
118
117
|
{
|
|
119
|
-
"
|
|
120
|
-
"
|
|
121
|
-
"
|
|
118
|
+
"title": "Distance",
|
|
119
|
+
"totalWidth": 300,
|
|
120
|
+
"values": ["2x", "3", "x"],
|
|
122
121
|
"labelSet": [
|
|
123
|
-
{ "startIndex": 0, "endIndex":
|
|
122
|
+
{ "startIndex": 0, "endIndex": 3, "label": "Total: 3x + 3", "showBelow": true },
|
|
123
|
+
{ "startIndex": 0, "endIndex": 1, "label": "2x", "showBelow": false }
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Example with Value Objects
|
|
129
|
+
```json
|
|
130
|
+
{
|
|
131
|
+
"title": "Pencils",
|
|
132
|
+
"totalWidth": 320,
|
|
133
|
+
"values": [
|
|
134
|
+
{ "value": "5", "showLabel": true, "color": "#93c5fd" },
|
|
135
|
+
{ "value": "5", "showLabel": true, "color": "#93c5fd" },
|
|
136
|
+
{ "value": "5", "showLabel": false, "color": "#fca5a5" }
|
|
124
137
|
],
|
|
125
|
-
"
|
|
138
|
+
"labelSet": [
|
|
139
|
+
{ "startIndex": 0, "endIndex": 3, "label": "15 total", "showBelow": true }
|
|
140
|
+
]
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## 4b. `omdDoubleTapeDiagram`
|
|
147
|
+
|
|
148
|
+
`omdDoubleTapeDiagram` represents two tape diagrams aligned by their start points.
|
|
149
|
+
|
|
150
|
+
### Schema
|
|
151
|
+
```json
|
|
152
|
+
{
|
|
153
|
+
"topTapeDiagram": "object (omdTapeDiagram)",
|
|
154
|
+
"bottomTapeDiagram": "object (omdTapeDiagram)",
|
|
155
|
+
"spacing": "number (optional, default: 10)"
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Example
|
|
160
|
+
```json
|
|
161
|
+
{
|
|
162
|
+
"topTapeDiagram": {
|
|
163
|
+
"title": "Pencils",
|
|
164
|
+
"totalWidth": 320,
|
|
165
|
+
"values": [
|
|
166
|
+
{ "value": "5", "color": "#93c5fd" },
|
|
167
|
+
{ "value": "5", "color": "#93c5fd" },
|
|
168
|
+
{ "value": "5", "color": "#93c5fd" }
|
|
169
|
+
],
|
|
170
|
+
"labelSet": [
|
|
171
|
+
{ "startIndex": 0, "endIndex": 3, "label": "15 total", "showBelow": true }
|
|
172
|
+
]
|
|
173
|
+
},
|
|
174
|
+
"bottomTapeDiagram": {
|
|
175
|
+
"title": "Cost",
|
|
176
|
+
"totalWidth": 320,
|
|
177
|
+
"values": [
|
|
178
|
+
{ "value": "$2", "color": "#fca5a5" },
|
|
179
|
+
{ "value": "$2", "color": "#fca5a5" },
|
|
180
|
+
{ "value": "$2", "color": "#fca5a5" }
|
|
181
|
+
],
|
|
182
|
+
"labelSet": [
|
|
183
|
+
{ "startIndex": 0, "endIndex": 3, "label": "$6 total", "showBelow": true }
|
|
184
|
+
]
|
|
185
|
+
},
|
|
186
|
+
"spacing": 80
|
|
126
187
|
}
|
|
127
188
|
```
|
|
128
189
|
|
|
@@ -398,25 +459,85 @@ This document provides schemas and examples for the `loadFromJSON` method used i
|
|
|
398
459
|
|
|
399
460
|
## 15. `omdNumberLine`
|
|
400
461
|
|
|
401
|
-
`omdNumberLine` represents a number line with labeled ticks and
|
|
462
|
+
`omdNumberLine` represents a number line with labeled ticks, optional title, custom increments, units, arrows, and special numbers.
|
|
402
463
|
|
|
403
464
|
### Schema
|
|
404
465
|
```json
|
|
405
466
|
{
|
|
406
|
-
"
|
|
407
|
-
"
|
|
408
|
-
"
|
|
409
|
-
"
|
|
467
|
+
"title": "string (optional)",
|
|
468
|
+
"min": "number (required)",
|
|
469
|
+
"max": "number (required)",
|
|
470
|
+
"increment": "number (optional, default: 1)",
|
|
471
|
+
"showLeftArrow": "boolean (optional, default: false)",
|
|
472
|
+
"showRightArrow": "boolean (optional, default: false)",
|
|
473
|
+
"units": "string (optional)",
|
|
474
|
+
"hideDefaultNumbers": "boolean (optional, default: false)",
|
|
475
|
+
"specialNumbers": ["array (optional)"],
|
|
476
|
+
"totalWidth": "number (optional, default: 320)",
|
|
477
|
+
"dotValues": ["array (optional)"]
|
|
410
478
|
}
|
|
411
479
|
```
|
|
412
480
|
|
|
413
481
|
### Example
|
|
414
482
|
```json
|
|
415
483
|
{
|
|
484
|
+
"title": "Distance",
|
|
416
485
|
"min": 0,
|
|
417
486
|
"max": 10,
|
|
418
|
-
"
|
|
419
|
-
"
|
|
487
|
+
"increment": 1,
|
|
488
|
+
"units": " cm",
|
|
489
|
+
"dotValues": [1, 5, 7]
|
|
490
|
+
}
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
### Example with Special Numbers
|
|
494
|
+
```json
|
|
495
|
+
{
|
|
496
|
+
"title": "Height",
|
|
497
|
+
"min": 0,
|
|
498
|
+
"max": 100,
|
|
499
|
+
"increment": 10,
|
|
500
|
+
"specialNumbers": [25, 75],
|
|
501
|
+
"units": " m",
|
|
502
|
+
"showRightArrow": true,
|
|
503
|
+
"hideDefaultNumbers": false,
|
|
504
|
+
"totalWidth": 400
|
|
505
|
+
}
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
---
|
|
509
|
+
|
|
510
|
+
## 15b. `omdDoubleNumberLine`
|
|
511
|
+
|
|
512
|
+
`omdDoubleNumberLine` represents two number lines aligned by their start points, useful for showing proportional relationships.
|
|
513
|
+
|
|
514
|
+
### Schema
|
|
515
|
+
```json
|
|
516
|
+
{
|
|
517
|
+
"topNumberLine": "object (omdNumberLine)",
|
|
518
|
+
"bottomNumberLine": "object (omdNumberLine)",
|
|
519
|
+
"spacing": "number (optional, default: 10)"
|
|
520
|
+
}
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
### Example
|
|
524
|
+
```json
|
|
525
|
+
{
|
|
526
|
+
"topNumberLine": {
|
|
527
|
+
"title": "Hours",
|
|
528
|
+
"min": 0,
|
|
529
|
+
"max": 10,
|
|
530
|
+
"showRightArrow": true,
|
|
531
|
+
"increment": 1
|
|
532
|
+
},
|
|
533
|
+
"bottomNumberLine": {
|
|
534
|
+
"showRightArrow": true,
|
|
535
|
+
"title": "Miles",
|
|
536
|
+
"min": 0,
|
|
537
|
+
"max": 50,
|
|
538
|
+
"increment": 10
|
|
539
|
+
},
|
|
540
|
+
"spacing": 15
|
|
420
541
|
}
|
|
421
542
|
```
|
|
422
543
|
|
package/src/omd.js
CHANGED
|
@@ -12,7 +12,9 @@ import { omdEquation } from "./omdEquation.js";
|
|
|
12
12
|
import { omdEquationNode } from "../omd/nodes/omdEquationNode.js";
|
|
13
13
|
import { omdFunction } from "./omdFunction.js";
|
|
14
14
|
import { omdNumberLine } from "./omdNumberLine.js";
|
|
15
|
+
import { omdDoubleNumberLine } from "./omdDoubleNumberLine.js";
|
|
15
16
|
import { omdTapeDiagram } from "./omdTapeDiagram.js";
|
|
17
|
+
import { omdDoubleTapeDiagram } from "./omdDoubleTapeDiagram.js";
|
|
16
18
|
import { omdBalanceHanger } from "./omdBalanceHanger.js";
|
|
17
19
|
import { omdNumberTile } from "./omdNumberTile.js";
|
|
18
20
|
import { omdRatioChart } from "./omdRatioChart.js";
|
|
@@ -122,12 +124,18 @@ export class omd extends jsvgContainer
|
|
|
122
124
|
case "numberLine":
|
|
123
125
|
N = new omdNumberLine();
|
|
124
126
|
break;
|
|
127
|
+
case "doubleNumberLine":
|
|
128
|
+
N = new omdDoubleNumberLine();
|
|
129
|
+
break;
|
|
125
130
|
case "balanceHanger":
|
|
126
131
|
N = new omdBalanceHanger();
|
|
127
132
|
break;
|
|
128
133
|
case "tapeDiagram":
|
|
129
134
|
N = new omdTapeDiagram();
|
|
130
135
|
break;
|
|
136
|
+
case "doubleTapeDiagram":
|
|
137
|
+
N = new omdDoubleTapeDiagram();
|
|
138
|
+
break;
|
|
131
139
|
case "numberTile":
|
|
132
140
|
N = new omdNumberTile();
|
|
133
141
|
break;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { omdColor } from "./omdColor.js";
|
|
2
|
+
import { jsvgGroup } from "@teachinglab/jsvg";
|
|
3
|
+
import { omdNumberLine } from "./omdNumberLine.js";
|
|
4
|
+
|
|
5
|
+
export class omdDoubleNumberLine extends jsvgGroup
|
|
6
|
+
{
|
|
7
|
+
constructor()
|
|
8
|
+
{
|
|
9
|
+
// initialization
|
|
10
|
+
super();
|
|
11
|
+
|
|
12
|
+
this.type = "omdDoubleNumberLine";
|
|
13
|
+
|
|
14
|
+
this.topNumberLine = new omdNumberLine();
|
|
15
|
+
this.bottomNumberLine = new omdNumberLine();
|
|
16
|
+
|
|
17
|
+
this.spacing = 30;
|
|
18
|
+
|
|
19
|
+
this.updateLayout();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
loadFromJSON( data )
|
|
23
|
+
{
|
|
24
|
+
// Load spacing first, before updating layout
|
|
25
|
+
if ( typeof data.spacing !== "undefined" ) {
|
|
26
|
+
this.spacing = data.spacing;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if ( typeof data.topNumberLine !== "undefined" ) {
|
|
30
|
+
this.topNumberLine.loadFromJSON(data.topNumberLine);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if ( typeof data.bottomNumberLine !== "undefined" ) {
|
|
34
|
+
this.bottomNumberLine.loadFromJSON(data.bottomNumberLine);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Don't call updateLayout here - let it be called separately
|
|
38
|
+
// This prevents double-calling and ensures spacing is used
|
|
39
|
+
this.updateLayout();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
updateLayout()
|
|
43
|
+
{
|
|
44
|
+
this.removeAllChildren();
|
|
45
|
+
|
|
46
|
+
// Calculate the maximum left padding needed to align the start of the lines
|
|
47
|
+
const topLeftPadding = this.topNumberLine.title ? 80 : 20;
|
|
48
|
+
const bottomLeftPadding = this.bottomNumberLine.title ? 80 : 20;
|
|
49
|
+
const maxLeftPadding = Math.max(topLeftPadding, bottomLeftPadding);
|
|
50
|
+
|
|
51
|
+
// Position top number line
|
|
52
|
+
const topXOffset = maxLeftPadding - topLeftPadding;
|
|
53
|
+
this.topNumberLine.setPosition(topXOffset, 0);
|
|
54
|
+
this.addChild(this.topNumberLine);
|
|
55
|
+
|
|
56
|
+
// Position bottom number line - spacing controls the gap
|
|
57
|
+
// If spacing = 0, they overlap. If spacing = 10, there's a 10px gap between them
|
|
58
|
+
const bottomXOffset = maxLeftPadding - bottomLeftPadding;
|
|
59
|
+
this.bottomNumberLine.setPosition(bottomXOffset, this.spacing);
|
|
60
|
+
this.addChild(this.bottomNumberLine);
|
|
61
|
+
|
|
62
|
+
// Set overall dimensions
|
|
63
|
+
const maxWidth = Math.max(
|
|
64
|
+
this.topNumberLine.width + topXOffset,
|
|
65
|
+
this.bottomNumberLine.width + bottomXOffset
|
|
66
|
+
);
|
|
67
|
+
this.width = maxWidth;
|
|
68
|
+
this.height = 70 + this.spacing; // Top line (70px) + spacing
|
|
69
|
+
this.svgObject.setAttribute('viewBox', `0 0 ${this.width} ${this.height}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
|
|
2
|
+
import { jsvgGroup } from "@teachinglab/jsvg";
|
|
3
|
+
import { omdTapeDiagram } from "./omdTapeDiagram.js";
|
|
4
|
+
|
|
5
|
+
export class omdDoubleTapeDiagram extends jsvgGroup
|
|
6
|
+
{
|
|
7
|
+
constructor()
|
|
8
|
+
{
|
|
9
|
+
// initialization
|
|
10
|
+
super();
|
|
11
|
+
|
|
12
|
+
this.type = "omdDoubleTapeDiagram";
|
|
13
|
+
|
|
14
|
+
this.topTapeDiagram = new omdTapeDiagram();
|
|
15
|
+
this.bottomTapeDiagram = new omdTapeDiagram();
|
|
16
|
+
|
|
17
|
+
this.spacing = 30;
|
|
18
|
+
|
|
19
|
+
this.updateLayout();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
loadFromJSON( data )
|
|
23
|
+
{
|
|
24
|
+
// Load spacing first, before updating layout
|
|
25
|
+
if ( typeof data.spacing !== "undefined" ) {
|
|
26
|
+
this.spacing = data.spacing;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if ( typeof data.topTapeDiagram !== "undefined" ) {
|
|
30
|
+
this.topTapeDiagram.loadFromJSON(data.topTapeDiagram);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if ( typeof data.bottomTapeDiagram !== "undefined" ) {
|
|
34
|
+
this.bottomTapeDiagram.loadFromJSON(data.bottomTapeDiagram);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
this.updateLayout();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
updateLayout()
|
|
41
|
+
{
|
|
42
|
+
this.removeAllChildren();
|
|
43
|
+
|
|
44
|
+
// Calculate total numeric values for both tapes to determine unit width
|
|
45
|
+
const topTotal = this.calculateTotalValue(this.topTapeDiagram);
|
|
46
|
+
const bottomTotal = this.calculateTotalValue(this.bottomTapeDiagram);
|
|
47
|
+
|
|
48
|
+
// Find the maximum total to determine a consistent unit width
|
|
49
|
+
const maxTotal = Math.max(topTotal, bottomTotal);
|
|
50
|
+
const baseWidth = 300; // Base width for the longest tape
|
|
51
|
+
const unitWidth = maxTotal > 0 ? baseWidth / maxTotal : baseWidth;
|
|
52
|
+
|
|
53
|
+
// Set each tape's width based on its total value
|
|
54
|
+
this.topTapeDiagram.totalWidth = topTotal * unitWidth;
|
|
55
|
+
this.bottomTapeDiagram.totalWidth = bottomTotal * unitWidth;
|
|
56
|
+
|
|
57
|
+
// Force update of both tape diagrams with new widths
|
|
58
|
+
this.topTapeDiagram.updateLayout();
|
|
59
|
+
this.bottomTapeDiagram.updateLayout();
|
|
60
|
+
|
|
61
|
+
// Calculate the maximum left padding needed to align the start of the tapes
|
|
62
|
+
const topLeftPadding = this.topTapeDiagram.title ? 80 : 20;
|
|
63
|
+
const bottomLeftPadding = this.bottomTapeDiagram.title ? 80 : 20;
|
|
64
|
+
const maxLeftPadding = Math.max(topLeftPadding, bottomLeftPadding);
|
|
65
|
+
|
|
66
|
+
// Position top tape diagram
|
|
67
|
+
const topXOffset = maxLeftPadding - topLeftPadding;
|
|
68
|
+
this.topTapeDiagram.setPosition(topXOffset, 0);
|
|
69
|
+
this.addChild(this.topTapeDiagram);
|
|
70
|
+
|
|
71
|
+
// Position bottom tape diagram
|
|
72
|
+
// spacing controls the gap between the bottom of top tape and top of bottom tape
|
|
73
|
+
const bottomXOffset = maxLeftPadding - bottomLeftPadding;
|
|
74
|
+
const bottomYPosition = this.topTapeDiagram.height + this.spacing;
|
|
75
|
+
this.bottomTapeDiagram.setPosition(bottomXOffset, bottomYPosition);
|
|
76
|
+
this.addChild(this.bottomTapeDiagram);
|
|
77
|
+
|
|
78
|
+
// Set overall dimensions
|
|
79
|
+
const maxWidth = Math.max(
|
|
80
|
+
this.topTapeDiagram.width + topXOffset,
|
|
81
|
+
this.bottomTapeDiagram.width + bottomXOffset
|
|
82
|
+
);
|
|
83
|
+
this.width = maxWidth;
|
|
84
|
+
this.height = this.topTapeDiagram.height + this.spacing + this.bottomTapeDiagram.height;
|
|
85
|
+
this.svgObject.setAttribute('viewBox', `0 0 ${this.width} ${this.height}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
calculateTotalValue(tapeDiagram)
|
|
89
|
+
{
|
|
90
|
+
let total = 0;
|
|
91
|
+
|
|
92
|
+
for (const valueData of tapeDiagram.values) {
|
|
93
|
+
let value = "";
|
|
94
|
+
|
|
95
|
+
// Handle both old format (simple values) and new format (objects)
|
|
96
|
+
if (typeof valueData === "object" && valueData !== null) {
|
|
97
|
+
value = valueData.value || "";
|
|
98
|
+
} else {
|
|
99
|
+
value = valueData.toString();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Parse numeric value from string (e.g., "3", "2x", "5y")
|
|
103
|
+
const match = value.match(/^([0-9.]+)?([a-zA-Z]*)$/);
|
|
104
|
+
if (match) {
|
|
105
|
+
const coefficient = match[1] ? parseFloat(match[1]) : (match[2] ? 1 : 1);
|
|
106
|
+
total += coefficient;
|
|
107
|
+
} else {
|
|
108
|
+
total += 1; // Default for unparseable values
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return total;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
}
|
package/src/omdFactory.js
CHANGED
|
@@ -18,8 +18,10 @@
|
|
|
18
18
|
import { omdBalanceHanger } from './omdBalanceHanger.js';
|
|
19
19
|
import { omdTable } from './omdTable.js';
|
|
20
20
|
import { omdTapeDiagram } from './omdTapeDiagram.js';
|
|
21
|
+
import { omdDoubleTapeDiagram } from './omdDoubleTapeDiagram.js';
|
|
21
22
|
import { omdCoordinatePlane } from './omdCoordinatePlane.js';
|
|
22
23
|
import { omdNumberLine } from './omdNumberLine.js';
|
|
24
|
+
import { omdDoubleNumberLine } from './omdDoubleNumberLine.js';
|
|
23
25
|
import { omdNumberTile } from './omdNumberTile.js';
|
|
24
26
|
import { omdRatioChart } from './omdRatioChart.js';
|
|
25
27
|
import { omdTileEquation } from './omdTileEquation.js';
|
|
@@ -48,8 +50,10 @@ const OMD_TYPE_MAP = {
|
|
|
48
50
|
'balanceHanger': omdBalanceHanger,
|
|
49
51
|
'table': omdTable,
|
|
50
52
|
'tapeDiagram': omdTapeDiagram,
|
|
53
|
+
'doubleTapeDiagram': omdDoubleTapeDiagram,
|
|
51
54
|
'coordinatePlane': omdCoordinatePlane,
|
|
52
55
|
'numberLine': omdNumberLine,
|
|
56
|
+
'doubleNumberLine': omdDoubleNumberLine,
|
|
53
57
|
'numberTile': omdNumberTile,
|
|
54
58
|
'ratioChart': omdRatioChart,
|
|
55
59
|
'tileEquation': omdTileEquation,
|
package/src/omdNumberLine.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
import { omdColor } from "./omdColor.js";
|
|
3
|
-
import { jsvgGroup, jsvgRect, jsvgLine, jsvgTextBox, jsvgEllipse } from "@teachinglab/jsvg";
|
|
3
|
+
import { jsvgGroup, jsvgRect, jsvgLine, jsvgTextBox, jsvgEllipse, jsvgPath } from "@teachinglab/jsvg";
|
|
4
4
|
|
|
5
5
|
export class omdNumberLine extends jsvgGroup
|
|
6
6
|
{
|
|
@@ -11,8 +11,16 @@ export class omdNumberLine extends jsvgGroup
|
|
|
11
11
|
|
|
12
12
|
this.type = "omdNumberLine";
|
|
13
13
|
|
|
14
|
+
this.title = "";
|
|
14
15
|
this.min = 0;
|
|
15
16
|
this.max = 10;
|
|
17
|
+
this.increment = 1;
|
|
18
|
+
this.showLeftArrow = false;
|
|
19
|
+
this.showRightArrow = false;
|
|
20
|
+
this.units = "";
|
|
21
|
+
this.hideDefaultNumbers = false;
|
|
22
|
+
this.specialNumbers = [];
|
|
23
|
+
this.totalWidth = 320;
|
|
16
24
|
this.dotValues = [];
|
|
17
25
|
this.label = "";
|
|
18
26
|
this.updateLayout();
|
|
@@ -20,16 +28,40 @@ export class omdNumberLine extends jsvgGroup
|
|
|
20
28
|
|
|
21
29
|
loadFromJSON( data )
|
|
22
30
|
{
|
|
23
|
-
if ( typeof data.
|
|
31
|
+
if ( typeof data.title !== "undefined" )
|
|
32
|
+
this.title = data.title;
|
|
33
|
+
|
|
34
|
+
if ( typeof data.min !== "undefined" )
|
|
24
35
|
this.min = data.min;
|
|
25
36
|
|
|
26
|
-
if ( typeof data.max
|
|
37
|
+
if ( typeof data.max !== "undefined" )
|
|
27
38
|
this.max = data.max;
|
|
28
39
|
|
|
29
|
-
if ( typeof data.
|
|
40
|
+
if ( typeof data.increment !== "undefined" )
|
|
41
|
+
this.increment = data.increment;
|
|
42
|
+
|
|
43
|
+
if ( typeof data.showLeftArrow !== "undefined" )
|
|
44
|
+
this.showLeftArrow = data.showLeftArrow;
|
|
45
|
+
|
|
46
|
+
if ( typeof data.showRightArrow !== "undefined" )
|
|
47
|
+
this.showRightArrow = data.showRightArrow;
|
|
48
|
+
|
|
49
|
+
if ( typeof data.units !== "undefined" )
|
|
50
|
+
this.units = data.units;
|
|
51
|
+
|
|
52
|
+
if ( typeof data.hideDefaultNumbers !== "undefined" )
|
|
53
|
+
this.hideDefaultNumbers = data.hideDefaultNumbers;
|
|
54
|
+
|
|
55
|
+
if ( typeof data.specialNumbers !== "undefined" )
|
|
56
|
+
this.specialNumbers = data.specialNumbers;
|
|
57
|
+
|
|
58
|
+
if ( typeof data.totalWidth !== "undefined" )
|
|
59
|
+
this.totalWidth = data.totalWidth;
|
|
60
|
+
|
|
61
|
+
if ( typeof data.dotValues !== "undefined" )
|
|
30
62
|
this.dotValues = data.dotValues;
|
|
31
63
|
|
|
32
|
-
if ( typeof data.label
|
|
64
|
+
if ( typeof data.label !== "undefined" )
|
|
33
65
|
this.label = data.label;
|
|
34
66
|
|
|
35
67
|
this.updateLayout();
|
|
@@ -52,63 +84,150 @@ export class omdNumberLine extends jsvgGroup
|
|
|
52
84
|
{
|
|
53
85
|
this.removeAllChildren();
|
|
54
86
|
|
|
87
|
+
const leftPadding = this.title ? 80 : 20;
|
|
88
|
+
const rightPadding = 20;
|
|
89
|
+
const arrowSize = 10;
|
|
90
|
+
const tickOverhang = 8; // How much the line extends past the end ticks
|
|
91
|
+
const lineWidth = this.totalWidth;
|
|
92
|
+
// Calculate usable width (space for ticks) - subtract space for arrows only
|
|
93
|
+
const usableLineWidth = lineWidth - (this.showLeftArrow ? arrowSize : 0) - (this.showRightArrow ? arrowSize : 0);
|
|
94
|
+
|
|
55
95
|
// Set proper dimensions and viewBox for positioning
|
|
56
|
-
this.width =
|
|
96
|
+
this.width = leftPadding + lineWidth + rightPadding;
|
|
57
97
|
this.height = 70;
|
|
58
98
|
this.svgObject.setAttribute('viewBox', `0 0 ${this.width} ${this.height}`);
|
|
59
99
|
|
|
60
|
-
//
|
|
100
|
+
// Add title if present
|
|
101
|
+
if (this.title) {
|
|
102
|
+
const titleText = new jsvgTextBox();
|
|
103
|
+
titleText.setWidthAndHeight(70, 30);
|
|
104
|
+
titleText.setFontFamily("Albert Sans");
|
|
105
|
+
titleText.setFontColor("black");
|
|
106
|
+
titleText.setFontSize(12);
|
|
107
|
+
titleText.setAlignment("left");
|
|
108
|
+
titleText.setText(this.title);
|
|
109
|
+
titleText.setPosition(5, 20);
|
|
110
|
+
this.addChild(titleText);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Calculate line position and width
|
|
114
|
+
// Line starts at: leftPadding + (arrow space if present), and extends past ticks by tickOverhang on each end
|
|
115
|
+
const lineStartX = leftPadding + (this.showLeftArrow ? arrowSize : 0);
|
|
116
|
+
const lineActualWidth = usableLineWidth; // This is the space between arrows (or edges), ticks go here with overhang
|
|
117
|
+
|
|
118
|
+
// Draw main line (extends past the first and last ticks by tickOverhang)
|
|
61
119
|
this.line = new jsvgRect();
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
this.line.
|
|
66
|
-
this.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
120
|
+
this.line.setWidthAndHeight(lineActualWidth, 5);
|
|
121
|
+
this.line.setPosition(lineStartX, 22.5);
|
|
122
|
+
this.line.setFillColor(omdColor.mediumGray);
|
|
123
|
+
this.line.setCornerRadius(2.5);
|
|
124
|
+
this.addChild(this.line);
|
|
125
|
+
|
|
126
|
+
// Draw left arrow if needed
|
|
127
|
+
if (this.showLeftArrow) {
|
|
128
|
+
// Cover the rounded corner with a rectangle
|
|
129
|
+
const coverRect = new jsvgRect();
|
|
130
|
+
coverRect.setWidthAndHeight(3, 5);
|
|
131
|
+
coverRect.setPosition(lineStartX, 22.5);
|
|
132
|
+
coverRect.setFillColor(omdColor.mediumGray);
|
|
133
|
+
this.addChild(coverRect);
|
|
134
|
+
|
|
135
|
+
const leftArrow = new jsvgPath();
|
|
136
|
+
const arrowY = 25;
|
|
137
|
+
const arrowX = leftPadding; // Arrow tip position
|
|
138
|
+
leftArrow.addPoint(arrowX + arrowSize, arrowY - 5);
|
|
139
|
+
leftArrow.addPoint(arrowX, arrowY);
|
|
140
|
+
leftArrow.addPoint(arrowX + arrowSize, arrowY + 5);
|
|
141
|
+
leftArrow.addPoint(arrowX + arrowSize, arrowY - 5); // Close the path
|
|
142
|
+
leftArrow.updatePath();
|
|
143
|
+
leftArrow.setFillColor(omdColor.mediumGray);
|
|
144
|
+
leftArrow.setStrokeWidth(0);
|
|
145
|
+
leftArrow.path.setAttribute("fill", omdColor.mediumGray);
|
|
146
|
+
this.addChild(leftArrow);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Draw right arrow if needed
|
|
150
|
+
if (this.showRightArrow) {
|
|
151
|
+
// Cover the rounded corner with a rectangle
|
|
152
|
+
const coverRect = new jsvgRect();
|
|
153
|
+
coverRect.setWidthAndHeight(3, 5);
|
|
154
|
+
coverRect.setPosition(lineStartX + lineActualWidth - 3, 22.5);
|
|
155
|
+
coverRect.setFillColor(omdColor.mediumGray);
|
|
156
|
+
this.addChild(coverRect);
|
|
157
|
+
|
|
158
|
+
const rightArrow = new jsvgPath();
|
|
159
|
+
const arrowY = 25;
|
|
160
|
+
const arrowX = leftPadding + lineWidth - arrowSize; // Arrow tip position
|
|
161
|
+
rightArrow.addPoint(arrowX, arrowY - 5);
|
|
162
|
+
rightArrow.addPoint(arrowX + arrowSize, arrowY);
|
|
163
|
+
rightArrow.addPoint(arrowX, arrowY + 5);
|
|
164
|
+
rightArrow.addPoint(arrowX, arrowY - 5); // Close the path
|
|
165
|
+
rightArrow.updatePath();
|
|
166
|
+
rightArrow.setFillColor(omdColor.mediumGray);
|
|
167
|
+
rightArrow.setStrokeWidth(0);
|
|
168
|
+
rightArrow.path.setAttribute("fill", omdColor.mediumGray);
|
|
169
|
+
this.addChild(rightArrow);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Collect all numbers that should be displayed
|
|
173
|
+
const numbersToShow = new Set();
|
|
174
|
+
|
|
175
|
+
// Add increment-based numbers if not hidden
|
|
176
|
+
if (!this.hideDefaultNumbers) {
|
|
177
|
+
for (let i = this.min; i <= this.max; i += this.increment) {
|
|
178
|
+
numbersToShow.add(i);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Add special numbers
|
|
183
|
+
for (const num of this.specialNumbers) {
|
|
184
|
+
if (num >= this.min && num <= this.max) {
|
|
185
|
+
numbersToShow.add(num);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Draw ticks and labels for all numbers
|
|
190
|
+
const sortedNumbers = Array.from(numbersToShow).sort((a, b) => a - b);
|
|
191
|
+
// Ticks are positioned with tickOverhang on both ends
|
|
192
|
+
const tickBaseX = lineStartX + tickOverhang;
|
|
193
|
+
const tickSpan = usableLineWidth - 2 * tickOverhang; // Space for ticks between the overhangs
|
|
194
|
+
for (const value of sortedNumbers) {
|
|
195
|
+
const normalized = (value - this.min) / (this.max - this.min);
|
|
196
|
+
const pX = tickBaseX + normalized * tickSpan;
|
|
197
|
+
|
|
198
|
+
// Draw tick
|
|
199
|
+
const tick = new jsvgLine();
|
|
200
|
+
tick.setStrokeColor("black");
|
|
201
|
+
tick.setStrokeWidth(1);
|
|
202
|
+
tick.setEndpoints(pX, 20, pX, 30);
|
|
203
|
+
this.addChild(tick);
|
|
204
|
+
|
|
205
|
+
// Draw label
|
|
206
|
+
const tickText = new jsvgTextBox();
|
|
207
|
+
tickText.setWidthAndHeight(40, 30);
|
|
208
|
+
tickText.setFontFamily("Albert Sans");
|
|
209
|
+
tickText.setFontColor("black");
|
|
210
|
+
tickText.setFontSize(10);
|
|
91
211
|
tickText.setAlignment("center");
|
|
92
|
-
|
|
93
|
-
tickText.
|
|
94
|
-
|
|
212
|
+
const labelText = this.units ? `${value}${this.units}` : value.toString();
|
|
213
|
+
tickText.setText(labelText);
|
|
214
|
+
tickText.setPosition(pX - 20, 32);
|
|
215
|
+
this.addChild(tickText);
|
|
95
216
|
}
|
|
96
217
|
|
|
97
|
-
//
|
|
98
|
-
for(
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
dot.
|
|
108
|
-
dot.
|
|
109
|
-
|
|
110
|
-
dot.setPosition( pX, 25 );
|
|
111
|
-
this.addChild( dot );
|
|
218
|
+
// Draw dots
|
|
219
|
+
for (const V of this.dotValues) {
|
|
220
|
+
if (V < this.min || V > this.max) continue;
|
|
221
|
+
|
|
222
|
+
const normalized = (V - this.min) / (this.max - this.min);
|
|
223
|
+
const pX = tickBaseX + normalized * tickSpan;
|
|
224
|
+
|
|
225
|
+
const dot = new jsvgEllipse();
|
|
226
|
+
dot.setFillColor("black");
|
|
227
|
+
dot.setStrokeWidth(0);
|
|
228
|
+
dot.setWidthAndHeight(8, 8);
|
|
229
|
+
dot.setPosition(pX, 25);
|
|
230
|
+
this.addChild(dot);
|
|
112
231
|
}
|
|
113
232
|
}
|
|
114
233
|
|
package/src/omdTapeDiagram.js
CHANGED
|
@@ -111,30 +111,26 @@ export class omdTapeDiagram extends jsvgGroup
|
|
|
111
111
|
|
|
112
112
|
this.type = "omdTapeDiagram";
|
|
113
113
|
|
|
114
|
+
this.title = "";
|
|
114
115
|
this.values = [];
|
|
115
|
-
this.showValues = true;
|
|
116
|
-
this.colors = [];
|
|
117
116
|
this.labelSet = [];
|
|
118
|
-
this.
|
|
117
|
+
this.totalWidth = 300;
|
|
119
118
|
this.updateLayout();
|
|
120
119
|
}
|
|
121
120
|
|
|
122
121
|
loadFromJSON( data )
|
|
123
122
|
{
|
|
124
|
-
if ( typeof data.
|
|
125
|
-
this.
|
|
123
|
+
if ( typeof data.title !== "undefined" )
|
|
124
|
+
this.title = data.title;
|
|
126
125
|
|
|
127
|
-
if ( typeof data.
|
|
128
|
-
this.
|
|
129
|
-
|
|
130
|
-
if ( typeof data.colors != "undefined" )
|
|
131
|
-
this.colors = data.colors;
|
|
126
|
+
if ( typeof data.values !== "undefined" )
|
|
127
|
+
this.values = data.values;
|
|
132
128
|
|
|
133
|
-
if ( typeof data.labelSet
|
|
129
|
+
if ( typeof data.labelSet !== "undefined" )
|
|
134
130
|
this.labelSet = data.labelSet;
|
|
135
131
|
|
|
136
|
-
if ( typeof data.
|
|
137
|
-
this.
|
|
132
|
+
if ( typeof data.totalWidth !== "undefined" )
|
|
133
|
+
this.totalWidth = data.totalWidth;
|
|
138
134
|
|
|
139
135
|
this.updateLayout();
|
|
140
136
|
}
|
|
@@ -142,104 +138,182 @@ export class omdTapeDiagram extends jsvgGroup
|
|
|
142
138
|
setValues( newValues )
|
|
143
139
|
{
|
|
144
140
|
this.values = newValues;
|
|
141
|
+
this.updateLayout();
|
|
145
142
|
}
|
|
146
143
|
|
|
147
144
|
updateLayout()
|
|
148
145
|
{
|
|
149
146
|
this.removeAllChildren();
|
|
150
147
|
|
|
151
|
-
|
|
148
|
+
const leftPadding = this.title ? 80 : 20;
|
|
149
|
+
const rightPadding = 20;
|
|
150
|
+
const titleWidth = 70;
|
|
151
|
+
|
|
152
|
+
// Add title if present
|
|
153
|
+
if (this.title) {
|
|
154
|
+
const titleText = new jsvgTextBox();
|
|
155
|
+
titleText.setWidthAndHeight(titleWidth, 30);
|
|
156
|
+
titleText.setFontFamily("Albert Sans");
|
|
157
|
+
titleText.setFontColor("black");
|
|
158
|
+
titleText.setFontSize(12);
|
|
159
|
+
titleText.setAlignment("left");
|
|
160
|
+
titleText.setText(this.title);
|
|
161
|
+
titleText.setPosition(5, 5);
|
|
162
|
+
this.addChild(titleText);
|
|
163
|
+
}
|
|
152
164
|
|
|
153
|
-
//
|
|
154
|
-
|
|
165
|
+
// Parse values and calculate proportional widths
|
|
166
|
+
const parsedValues = [];
|
|
167
|
+
let totalNumericValue = 0;
|
|
168
|
+
|
|
169
|
+
for (const valueData of this.values) {
|
|
170
|
+
let value = "";
|
|
171
|
+
let showLabel = true;
|
|
172
|
+
let color = omdColor.lightGray;
|
|
173
|
+
let numericValue = 1; // default for non-numeric
|
|
174
|
+
|
|
175
|
+
// Handle both old format (simple values) and new format (objects)
|
|
176
|
+
if (typeof valueData === "object" && valueData !== null) {
|
|
177
|
+
value = valueData.value || "";
|
|
178
|
+
showLabel = valueData.showLabel !== undefined ? valueData.showLabel : true;
|
|
179
|
+
color = valueData.color || omdColor.lightGray;
|
|
180
|
+
} else {
|
|
181
|
+
value = valueData.toString();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Parse numeric value from string (e.g., "3", "2x", "5y")
|
|
185
|
+
// Extract coefficient from expressions like "2x", "3", "0.5y"
|
|
186
|
+
const match = value.match(/^([0-9.]+)?([a-zA-Z]*)$/);
|
|
187
|
+
if (match) {
|
|
188
|
+
const coefficient = match[1] ? parseFloat(match[1]) : (match[2] ? 1 : 1);
|
|
189
|
+
const variable = match[2] || "";
|
|
190
|
+
numericValue = coefficient;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
parsedValues.push({ value, showLabel, color, numericValue });
|
|
194
|
+
totalNumericValue += numericValue;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Calculate width for each segment based on proportion
|
|
198
|
+
var pX = leftPadding;
|
|
155
199
|
var indexPositions = [];
|
|
156
|
-
|
|
157
|
-
{
|
|
200
|
+
|
|
201
|
+
for (const parsed of parsedValues) {
|
|
158
202
|
indexPositions.push(pX);
|
|
159
203
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
{
|
|
164
|
-
W = 20 + value.length*10;
|
|
165
|
-
}
|
|
166
|
-
else
|
|
167
|
-
{
|
|
168
|
-
W = value * this.unitWidth;
|
|
169
|
-
}
|
|
170
|
-
|
|
204
|
+
// Calculate proportional width
|
|
205
|
+
const proportion = totalNumericValue > 0 ? parsed.numericValue / totalNumericValue : 1 / parsedValues.length;
|
|
206
|
+
const segmentWidth = this.totalWidth * proportion;
|
|
171
207
|
|
|
172
|
-
//
|
|
208
|
+
// Make box
|
|
173
209
|
var box = new jsvgRect();
|
|
174
|
-
box.setWidthAndHeight(
|
|
210
|
+
box.setWidthAndHeight(segmentWidth, 30);
|
|
175
211
|
box.setCornerRadius(5);
|
|
176
|
-
box.setStrokeColor(
|
|
177
|
-
box.setStrokeWidth(
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
212
|
+
box.setStrokeColor("white");
|
|
213
|
+
box.setStrokeWidth(1);
|
|
214
|
+
box.setFillColor(parsed.color);
|
|
215
|
+
box.setPosition(pX, 0);
|
|
216
|
+
this.addChild(box);
|
|
217
|
+
|
|
218
|
+
// Make box text (if showLabel is true)
|
|
219
|
+
if (parsed.showLabel) {
|
|
220
|
+
var boxText = new jsvgTextBox();
|
|
221
|
+
boxText.setWidthAndHeight(segmentWidth, 30);
|
|
222
|
+
boxText.setFontFamily("Albert Sans");
|
|
223
|
+
boxText.setFontColor("black");
|
|
224
|
+
boxText.setFontSize(18);
|
|
225
|
+
boxText.setAlignment("center");
|
|
226
|
+
boxText.setVerticalCentering();
|
|
227
|
+
boxText.setText(parsed.value);
|
|
228
|
+
boxText.setPosition(pX, 0);
|
|
229
|
+
this.addChild(boxText);
|
|
184
230
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
box.setPosition( pX, 0 );
|
|
188
|
-
this.addChild( box );
|
|
189
|
-
|
|
190
|
-
// make box text
|
|
191
|
-
var boxText = new jsvgTextBox();
|
|
192
|
-
boxText.setWidthAndHeight( W,30 );
|
|
193
|
-
boxText.setText ( this.name );
|
|
194
|
-
boxText.setFontFamily( "Albert Sans" );
|
|
195
|
-
boxText.setFontColor( "black" );
|
|
196
|
-
boxText.setFontSize( 18 );
|
|
197
|
-
boxText.setAlignment("center");
|
|
198
|
-
boxText.setVerticalCentering();
|
|
199
|
-
boxText.setText( value.toString() );
|
|
200
|
-
boxText.setPosition( pX, 0 );
|
|
201
|
-
this.addChild( boxText );
|
|
202
|
-
|
|
203
|
-
pX += W;
|
|
231
|
+
|
|
232
|
+
pX += segmentWidth;
|
|
204
233
|
}
|
|
205
234
|
|
|
206
235
|
indexPositions.push(pX);
|
|
207
236
|
|
|
208
|
-
// Calculate
|
|
209
|
-
var contentWidth = pX
|
|
210
|
-
var contentHeight = 30;
|
|
211
|
-
var
|
|
237
|
+
// Calculate dimensions
|
|
238
|
+
var contentWidth = pX - leftPadding;
|
|
239
|
+
var contentHeight = 30;
|
|
240
|
+
var topLabelSpace = 0;
|
|
241
|
+
var bottomLabelSpace = 0;
|
|
212
242
|
|
|
213
|
-
//
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
else
|
|
219
|
-
labelHeight = Math.max(labelHeight, 30); // 20 offset above + 10 buffer
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
if ( labelHeight > 0 )
|
|
223
|
-
contentHeight += labelHeight;
|
|
243
|
+
// Sort labels by span length (number of segments they cover)
|
|
244
|
+
const sortedLabels = this.labelSet.slice().map((labelData, index) => ({
|
|
245
|
+
data: labelData,
|
|
246
|
+
span: (labelData.endIndex || 0) - (labelData.startIndex || 0)
|
|
247
|
+
})).sort((a, b) => a.span - b.span); // Shortest first
|
|
224
248
|
|
|
225
|
-
//
|
|
226
|
-
|
|
249
|
+
// Track occupied label layers to prevent overlap
|
|
250
|
+
const topLayers = [];
|
|
251
|
+
const bottomLayers = [];
|
|
252
|
+
|
|
253
|
+
// Make label text
|
|
254
|
+
for ( const item of sortedLabels )
|
|
227
255
|
{
|
|
228
|
-
|
|
229
|
-
T
|
|
256
|
+
const labelData = item.data;
|
|
257
|
+
const T = new omdTapeLabel();
|
|
230
258
|
T.setIndexPositions( indexPositions );
|
|
231
259
|
T.loadFromJSON( labelData );
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
260
|
+
|
|
261
|
+
if ( T.showBelow ) {
|
|
262
|
+
// Find the lowest available layer for this label
|
|
263
|
+
let layer = 0;
|
|
264
|
+
const start = labelData.startIndex || 0;
|
|
265
|
+
const end = labelData.endIndex || 0;
|
|
266
|
+
|
|
267
|
+
while (layer < bottomLayers.length) {
|
|
268
|
+
const conflicts = bottomLayers[layer].some(occupied =>
|
|
269
|
+
!(end <= occupied.start || start >= occupied.end)
|
|
270
|
+
);
|
|
271
|
+
if (!conflicts) break;
|
|
272
|
+
layer++;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (layer === bottomLayers.length) {
|
|
276
|
+
bottomLayers.push([]);
|
|
277
|
+
}
|
|
278
|
+
bottomLayers[layer].push({ start, end });
|
|
279
|
+
|
|
280
|
+
const yPos = 40 + (layer * 35); // Stack labels with 35px spacing
|
|
281
|
+
T.setPosition(0, yPos);
|
|
282
|
+
bottomLabelSpace = Math.max(bottomLabelSpace, yPos + 30);
|
|
283
|
+
} else {
|
|
284
|
+
// Find the highest available layer for this label
|
|
285
|
+
let layer = 0;
|
|
286
|
+
const start = labelData.startIndex || 0;
|
|
287
|
+
const end = labelData.endIndex || 0;
|
|
288
|
+
|
|
289
|
+
while (layer < topLayers.length) {
|
|
290
|
+
const conflicts = topLayers[layer].some(occupied =>
|
|
291
|
+
!(end <= occupied.start || start >= occupied.end)
|
|
292
|
+
);
|
|
293
|
+
if (!conflicts) break;
|
|
294
|
+
layer++;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (layer === topLayers.length) {
|
|
298
|
+
topLayers.push([]);
|
|
299
|
+
}
|
|
300
|
+
topLayers[layer].push({ start, end });
|
|
301
|
+
|
|
302
|
+
const yPos = -10 - (layer * 35); // Stack labels upward with 35px spacing
|
|
303
|
+
T.setPosition(0, yPos);
|
|
304
|
+
topLabelSpace = Math.max(topLabelSpace, (layer + 1) * 35);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
this.addChild( T );
|
|
237
308
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
this.
|
|
241
|
-
//
|
|
242
|
-
|
|
309
|
+
// Set proper dimensions including space for labels above and below
|
|
310
|
+
this.width = leftPadding + contentWidth + rightPadding;
|
|
311
|
+
this.height = topLabelSpace + contentHeight + bottomLabelSpace;
|
|
312
|
+
// Adjust viewBox to show everything including title and labels
|
|
313
|
+
const viewBoxY = -topLabelSpace;
|
|
314
|
+
const viewBoxHeight = this.height;
|
|
315
|
+
this.svgObject.setAttribute("viewBox", `0 ${viewBoxY} ${this.width} ${viewBoxHeight}`);
|
|
316
|
+
this.svgObject.setAttribute("viewBox", `0 ${-topLabelSpace} ${this.width} ${this.height}`);
|
|
243
317
|
}
|
|
244
318
|
|
|
245
319
|
}
|