@teachinglab/omd 0.6.6 → 0.7.0

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.
@@ -37,6 +37,10 @@ export class ResizeHandleManager {
37
37
  if (!element || !element.classList.contains('omd-item')) {
38
38
  return;
39
39
  }
40
+
41
+ if (element?.dataset?.locked === 'true') {
42
+ return;
43
+ }
40
44
 
41
45
  this.selectedElement = element;
42
46
  this._createSelectionBorder();
@@ -72,7 +72,10 @@ export class SelectTool extends Tool {
72
72
  }
73
73
 
74
74
  const segmentSelection = this._findSegmentAtPoint(event.x, event.y);
75
- const omdElement = this._findOMDElementAtPoint(event.x, event.y);
75
+ let omdElement = this._findOMDElementAtPoint(event.x, event.y);
76
+ if (omdElement?.dataset?.locked === 'true') {
77
+ omdElement = null;
78
+ }
76
79
 
77
80
  if (segmentSelection) {
78
81
  // Clicking on a stroke segment - clear OMD selection and handle segment selection
@@ -473,8 +476,11 @@ export class SelectTool extends Tool {
473
476
 
474
477
  // Check all OMD items in the layer
475
478
  const omdItems = omdLayer.querySelectorAll('.omd-item');
476
-
479
+
477
480
  for (const item of omdItems) {
481
+ if (item?.dataset?.locked === 'true') {
482
+ continue;
483
+ }
478
484
  try {
479
485
  // Get the bounding box of the item
480
486
  const bbox = item.getBBox();
@@ -11,6 +11,9 @@ export class Toolbar {
11
11
  this.buttons = new Map();
12
12
  this.activeButton = null;
13
13
  this.omdColor = omdColor; // Use omdColor for consistent styling
14
+ this.toolbarWidth = 64;
15
+ this.toolbarHeight = 28;
16
+ this.customPosition = null;
14
17
 
15
18
 
16
19
 
@@ -44,8 +47,7 @@ export class Toolbar {
44
47
 
45
48
 
46
49
  // Initial size, will be updated after buttons are created
47
- this.background.setWidthAndHeight(100, 54);
48
- this.background.setCornerRadius(27); // Pill shape
50
+ this._setToolbarSize(this.toolbarWidth, this.toolbarHeight);
49
51
  this.background.setFillColor(this.omdColor.mediumGray); // Modern dark, semi-transparent
50
52
 
51
53
 
@@ -58,7 +60,7 @@ export class Toolbar {
58
60
 
59
61
 
60
62
  // Position the toolbar at bottom center
61
- this._updatePosition();``
63
+ this._updatePosition();
62
64
 
63
65
  // Add to main SVG so it is rendered
64
66
  this.canvas.svg.appendChild(this.toolbarGroup.svgObject);
@@ -70,16 +72,12 @@ export class Toolbar {
70
72
  */
71
73
  _updatePosition() {
72
74
  const canvasRect = this.canvas.container.getBoundingClientRect();
73
- const toolbarWidth = this.background.width;
74
- const toolbarHeight = this.background.height;
75
+ const toolbarWidth = this.toolbarWidth;
76
+ const toolbarHeight = this.toolbarHeight;
75
77
  // Bottom center, 24px from bottom
76
78
  const x = (canvasRect.width - toolbarWidth) / 2;
77
79
  const y = canvasRect.height - toolbarHeight - 24;
78
-
79
- // Ensure toolbar stays within canvas bounds
80
- const clampedX = Math.max(0, Math.min(x, canvasRect.width - toolbarWidth));
81
- const clampedY = Math.max(0, Math.min(y, canvasRect.height - toolbarHeight));
82
- this.toolbarGroup.setPosition(x, y);
80
+ this._applyPosition(x, y);
83
81
 
84
82
  // Debug the SVG object
85
83
 
@@ -88,14 +86,7 @@ export class Toolbar {
88
86
  this.toolbarGroup.svgObject.style.pointerEvents = 'auto';
89
87
 
90
88
  // Fix the viewBox to include the toolbar position
91
- const bgWidth = this.background.width;
92
- const bgHeight = this.background.height;
93
- const viewBoxX = x;
94
- const viewBoxY = y;
95
- const viewBoxWidth = Math.max(500, x + bgWidth);
96
- const viewBoxHeight = Math.max(500, y + bgHeight);
97
-
98
- this.toolbarGroup.svgObject.setAttribute('viewBox', `${viewBoxX} ${viewBoxY} ${viewBoxWidth} ${viewBoxHeight}`);
89
+ this._updateViewBox(x, y);
99
90
 
100
91
  // Don't set x/y attributes - let setPosition handle it via transform
101
92
  // this.toolbarGroup.svgObject.setAttribute('x', x);
@@ -109,9 +100,9 @@ export class Toolbar {
109
100
  _createToolButtons() {
110
101
  const tools = this.canvas.toolManager.getAllToolMetadata();
111
102
 
112
- const buttonSize = 48;
113
- const spacing = 8;
114
- const padding = 6;
103
+ const buttonSize = 24;
104
+ const spacing = 4;
105
+ const padding = 4;
115
106
  let xPos = padding;
116
107
  const yPos = padding;
117
108
  tools.forEach(toolMeta => {
@@ -124,8 +115,7 @@ export class Toolbar {
124
115
  // Remove last spacing
125
116
  const totalWidth = xPos - spacing + padding;
126
117
  const totalHeight = buttonSize + 2 * padding;
127
- this.background.setWidthAndHeight(totalWidth, totalHeight);
128
- this.background.setCornerRadius(totalHeight / 2);
118
+ this._setToolbarSize(totalWidth, totalHeight);
129
119
  // Reposition after sizing
130
120
  this._updatePosition();
131
121
 
@@ -215,6 +205,33 @@ export class Toolbar {
215
205
  this.activeButton = null;
216
206
  }
217
207
  }
208
+
209
+ _setToolbarSize(width, height) {
210
+ this.toolbarWidth = width;
211
+ this.toolbarHeight = height;
212
+ this.background.setWidthAndHeight(width, height);
213
+ this.background.setCornerRadius(height / 2);
214
+ }
215
+
216
+ _applyPosition(x, y) {
217
+ const canvasRect = this.canvas.container.getBoundingClientRect();
218
+ const clampedX = Math.max(0, Math.min(x, canvasRect.width - this.toolbarWidth));
219
+ const clampedY = Math.max(0, Math.min(y, canvasRect.height - this.toolbarHeight));
220
+ this.customPosition = { x: clampedX, y: clampedY };
221
+ this.toolbarGroup.setPosition(clampedX, clampedY);
222
+ this._updateViewBox(clampedX, clampedY);
223
+ }
224
+
225
+ _updateViewBox(x, y) {
226
+ this.toolbarGroup.svgObject.setAttribute('viewBox', `${x} ${y} ${this.toolbarWidth} ${this.toolbarHeight}`);
227
+ }
228
+
229
+ setBoundaryPosition(lineY) {
230
+ const canvasRect = this.canvas.container.getBoundingClientRect();
231
+ const x = (canvasRect.width - this.toolbarWidth) / 2;
232
+ const y = lineY - this.toolbarHeight;
233
+ this._applyPosition(x, y);
234
+ }
218
235
 
219
236
  /**
220
237
  * Add custom button to toolbar
package/index.js CHANGED
@@ -29,6 +29,7 @@ export { EventManager } from './canvas/events/eventManager.js';
29
29
 
30
30
  // Export utility components
31
31
  export { omdNodeOverlay, omdNodeOverlayPresets } from './omd/utils/omdNodeOverlay.js';
32
+ export { omdTranscriptionService } from './omd/utils/omdTranscriptionService.js';
32
33
 
33
34
  // Re-export the most commonly used components for easy access
34
35
  export { omdTable } from './src/omdTable.js';
@@ -77,4 +78,4 @@ export default {
77
78
  const { omdDisplay } = await import('./omd/core/index.js');
78
79
  return new omdDisplay(container);
79
80
  }
80
- };
81
+ };
@@ -1,4 +1,5 @@
1
1
  import { jsvgGroup, jsvgRect, jsvgTextLine, jsvgImage, jsvgClipMask } from './jsvg.js';
2
+ export * from './jsvg.js';
2
3
 
3
4
  // ================ jsvgButton ================================= //
4
5
 
@@ -355,4 +356,4 @@ export class jsvgScrollbox extends jsvgGroup
355
356
  }
356
357
  }
357
358
 
358
- }
359
+ }
@@ -460,54 +460,39 @@ Tabular data display with customizable styling.
460
460
 
461
461
  #### `omdEquation`
462
462
 
463
- Complete mathematical equations with left and right sides.
463
+ Complete mathematical equations. The visual component now uses the same math.js-powered parser and renderer as the interactive core (functions, rationals, roots, etc.).
464
464
 
465
- **Schema (String Form):**
465
+ **Preferred schema (string form):**
466
466
  ```json
467
467
  {
468
- "equation": "2x + 3 = 11"
468
+ "equation": "sin(x) + 2 = 3"
469
469
  }
470
470
  ```
471
471
 
472
- **Schema (Structured Form):**
472
+ **Structured fallback (legacy):**
473
473
  ```typescript
474
474
  {
475
- leftExpression: Expression | string,
476
- rightExpression: Expression | string,
477
- equation?: string
475
+ leftExpression?: Expression | string,
476
+ rightExpression?: Expression | string,
477
+ equation: string
478
478
  }
479
479
  ```
480
480
 
481
481
  **Examples:**
482
482
  ```json
483
- {
484
- "equation": "2x + 3 = 11"
485
- }
483
+ { "equation": "sin(x) + 2 = 3" }
486
484
  ```
487
-
488
485
  ```json
489
- {
490
- "leftExpression": {
491
- "termsAndOpers": [
492
- { "omdType": "term", "coefficient": 2, "variable": "x" },
493
- { "omdType": "operator", "operator": "+" },
494
- { "omdType": "number", "value": 3 }
495
- ]
496
- },
497
- "rightExpression": {
498
- "omdType": "number",
499
- "value": 11
500
- }
501
- }
486
+ { "equation": "(x^2 + 3x - 4)/(2x) = 5" }
502
487
  ```
503
488
 
504
489
  **Usage:**
505
490
  ```javascript
506
- // Simple string form
491
+ // Preferred string form (math.js parsing)
507
492
  const eq = new omdEquation();
508
- eq.loadFromJSON({ equation: '2x + 3 = 11' });
493
+ eq.loadFromJSON({ equation: 'sqrt(x+1) = 4', fontSize: 32 });
509
494
 
510
- // Structured form
495
+ // Legacy structured form (if you already have parsed pieces)
511
496
  eq.loadFromJSON({
512
497
  leftExpression: { omdType: 'term', coefficient: 2, variable: 'x' },
513
498
  rightExpression: { omdType: 'number', value: 11 }
@@ -55,6 +55,8 @@ export class omdDisplay {
55
55
  const height = this.container.offsetHeight || 600;
56
56
 
57
57
  this.svg.setViewbox(width, height);
58
+ this.svg.svgObject.style.width = '100%';
59
+ this.svg.svgObject.style.height = '100%';
58
60
  this.svg.svgObject.style.verticalAlign = "middle";
59
61
  // Enable internal scrolling via native SVG scrolling if content overflows
60
62
  this.svg.svgObject.style.overflow = 'hidden';
@@ -768,18 +770,7 @@ export class omdDisplay {
768
770
  render(expression) {
769
771
  // Clear previous node
770
772
  if (this.node) {
771
- if (this._contentGroup && this.node && this.node.svgObject) {
772
- try {
773
- if (this.node.svgObject.parentNode === this._contentGroup) {
774
- this._contentGroup.removeChild(this.node.svgObject);
775
- }
776
- } catch (e) {
777
- // Fallback to svg remove
778
- this.svg.removeChild(this.node);
779
- }
780
- } else {
781
- this.svg.removeChild(this.node);
782
- }
773
+ this.removeChild(this.node);
783
774
  }
784
775
 
785
776
  // Create node from expression
@@ -992,7 +983,7 @@ export class omdDisplay {
992
983
  */
993
984
  clear() {
994
985
  if (this.node) {
995
- this.svg.removeChild(this.node);
986
+ this.removeChild(this.node);
996
987
  this.node = null;
997
988
  }
998
989
  }
@@ -100,6 +100,7 @@ export class omdFunctionNode extends omdNode {
100
100
  // Calculate dimensions using getTextBounds for consistency
101
101
  const ratio = fontSize / this.getRootFontSize();
102
102
  const spacing = 2 * ratio;
103
+ const parenSpacing = 2 * ratio; // Spacing between function name and opening parenthesis
103
104
 
104
105
  const functionNameBounds = getTextBounds(this.functionName, fontSize);
105
106
  const openParenBounds = getTextBounds('(', fontSize);
@@ -117,7 +118,7 @@ export class omdFunctionNode extends omdNode {
117
118
  }
118
119
  });
119
120
 
120
- const totalWidth = functionNameBounds.width + openParenBounds.width + totalArgWidth + closeParenBounds.width + (spacing * 2);
121
+ const totalWidth = functionNameBounds.width + parenSpacing + openParenBounds.width + totalArgWidth + closeParenBounds.width + (spacing * 2);
121
122
  const totalHeight = Math.max(maxArgHeight, functionNameBounds.height, openParenBounds.height, closeParenBounds.height) + 4 * ratio;
122
123
 
123
124
  this.setWidthAndHeight(totalWidth, totalHeight);
@@ -132,6 +133,7 @@ export class omdFunctionNode extends omdNode {
132
133
  const argFontSize = fontSize * 5/6;
133
134
  const ratio = fontSize / this.getRootFontSize();
134
135
  const spacing = 2 * ratio;
136
+ const parenSpacing = 2 * ratio; // Spacing between function name and opening parenthesis
135
137
 
136
138
  let currentX = 0;
137
139
  const textY = this.height / 2;
@@ -140,6 +142,8 @@ export class omdFunctionNode extends omdNode {
140
142
  this.functionNameElement.setPosition(currentX, textY);
141
143
  currentX += getTextBounds(this.functionName, fontSize).width;
142
144
 
145
+ currentX += parenSpacing;
146
+
143
147
  // Position opening parenthesis
144
148
  this.openParenElement.setPosition(currentX, textY);
145
149
  currentX += getTextBounds('(', fontSize).width;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teachinglab/omd",
3
- "version": "0.6.6",
3
+ "version": "0.7.0",
4
4
  "description": "omd",
5
5
  "main": "./index.js",
6
6
  "module": "./index.js",
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
@@ -24,6 +26,12 @@ export { omdFunction } from './omdFunction.js';
24
26
  export { omdApp } from './omdApp.js';
25
27
  export { omdAppCanvas } from './omdAppCanvas.js';
26
28
  export { omd } from './omd.js';
29
+ export { omdDisplay } from '../omd/display/omdDisplay.js';
30
+ export { omdEquationNode } from '../omd/nodes/omdEquationNode.js';
31
+ export { omdEquationStack } from '../omd/core/omdEquationStack.js';
32
+ export { omdEquationSequenceNode } from '../omd/nodes/omdEquationSequenceNode.js';
33
+ export { omdConstantNode } from '../omd/nodes/omdConstantNode.js';
34
+ export { omdNodeOverlay, omdNodeOverlayPresets } from '../omd/utils/omdNodeOverlay.js';
27
35
 
28
36
  // OMD Utilities and Helpers
29
37
  export { omdColor } from './omdColor.js';
@@ -52,7 +60,8 @@ export default {
52
60
  omdTapeDiagram: () => import('./omdTapeDiagram.js').then(m => m.omdTapeDiagram),
53
61
  omdTileEquation: () => import('./omdTileEquation.js').then(m => m.omdTileEquation),
54
62
  omdRatioChart: () => import('./omdRatioChart.js').then(m => m.omdRatioChart),
55
- omdNumberLine: () => import('./omdNumberLine.js').then(m => m.omdNumberLine)
63
+ omdNumberLine: () => import('./omdNumberLine.js').then(m => m.omdNumberLine),
64
+ omdDoubleNumberLine: () => import('./omdDoubleNumberLine.js').then(m => m.omdDoubleNumberLine)
56
65
  },
57
66
 
58
67
  // Mathematical expressions
@@ -70,6 +79,11 @@ export default {
70
79
  app: {
71
80
  omdApp: () => import('./omdApp.js').then(m => m.omdApp),
72
81
  omdAppCanvas: () => import('./omdAppCanvas.js').then(m => m.omdAppCanvas),
73
- omd: () => import('./omd.js').then(m => m.omd)
82
+ omd: () => import('./omd.js').then(m => m.omd),
83
+ async createDisplay(container) {
84
+ const { omdDisplay } = await import('../omd/core/index.js');
85
+ return new omdDisplay(container);
86
+ }
74
87
  }
75
- };
88
+ };
89
+ // Force reload
@@ -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
- "values": ["array"],
109
- "showValues": "boolean",
110
- "colors": ["array"],
111
- "labelSet": ["array"],
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
- "values": [1, 2, 3],
120
- "showValues": true,
121
- "colors": ["#FF0000", "#00FF00", "#0000FF"],
118
+ "title": "Distance",
119
+ "totalWidth": 300,
120
+ "values": ["2x", "3", "x"],
122
121
  "labelSet": [
123
- { "startIndex": 0, "endIndex": 2, "label": "Example Label", "showBelow": true }
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
- "unitWidth": 30
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 optional dots.
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
- "min": "number",
407
- "max": "number",
408
- "dotValues": ["array"],
409
- "label": "string"
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
- "dotValues": [1, 5, 7],
419
- "label": "Number Line"
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
 
@@ -556,9 +677,16 @@ This document provides schemas and examples for the `loadFromJSON` method used i
556
677
 
557
678
  ## 20. `omdEquation`
558
679
 
559
- `omdEquation` represents a mathematical equation, such as "x + 2 = 5".
680
+ `omdEquation` represents a mathematical equation and now uses the same math.js-powered parser/renderer as the interactive core (functions, rationals, roots, etc.).
560
681
 
561
- ### Schema
682
+ ### Preferred schema (string form)
683
+ ```json
684
+ {
685
+ "equation": "sin(x) + 2 = 3"
686
+ }
687
+ ```
688
+
689
+ ### Structured fallback (legacy)
562
690
  ```json
563
691
  {
564
692
  "leftExpression": "object",
@@ -567,11 +695,10 @@ This document provides schemas and examples for the `loadFromJSON` method used i
567
695
  }
568
696
  ```
569
697
 
570
- ### Example
698
+ ### Examples
571
699
  ```json
572
- {
573
- "leftExpression": { "omdType": "term", "coefficient": 1, "variable": "x", "exponent": 1 },
574
- "rightExpression": { "omdType": "number", "value": 5 },
575
- "equation": "x + 2 = 5"
576
- }
577
- ```
700
+ { "equation": "sin(x) + 2 = 3" }
701
+ ```
702
+ ```json
703
+ { "equation": "(x^2 + 3x - 4)/(2x) = 5" }
704
+ ```
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;