@teachinglab/omd 0.6.7 → 0.7.1
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/README.md +1 -1
- package/canvas/core/omdCanvas.js +4 -0
- package/canvas/features/resizeHandleManager.js +4 -0
- package/canvas/tools/SelectTool.js +8 -2
- package/canvas/ui/toolbar.js +40 -23
- package/docs/api/index.md +1 -1
- package/docs/api-reference.md +1 -1
- package/docs/index.html +2 -2
- package/docs/index.md +1 -1
- package/index.js +3 -2
- package/jsvg/jsvgComponents.js +2 -1
- package/npm-docs/README.md +1 -1
- package/npm-docs/api/api-reference.md +1 -1
- package/npm-docs/api/index.md +1 -1
- package/npm-docs/guides/getting-started.md +1 -1
- package/npm-docs/json-schemas.md +12 -27
- package/omd/core/omdEquationStack.js +4 -4
- package/omd/display/omdDisplay.js +4 -13
- package/omd/display/omdToolbar.js +1 -1
- package/omd/nodes/omdFunctionNode.js +5 -1
- package/package.json +1 -1
- package/readme.html +2 -2
- package/src/index.js +13 -2
- package/src/json-schemas.md +15 -9
- package/src/omdEquation.js +64 -1
- package/README.old.md +0 -138
package/README.md
CHANGED
package/canvas/core/omdCanvas.js
CHANGED
|
@@ -204,6 +204,10 @@ export class omdCanvas {
|
|
|
204
204
|
addStroke(stroke) {
|
|
205
205
|
const id = `stroke_${++this.strokeCounter}`;
|
|
206
206
|
stroke.id = id;
|
|
207
|
+
if (stroke.element) {
|
|
208
|
+
stroke.element.setAttribute('data-stroke-id', id);
|
|
209
|
+
stroke.element.id = id;
|
|
210
|
+
}
|
|
207
211
|
this.strokes.set(id, stroke);
|
|
208
212
|
this.drawingLayer.appendChild(stroke.element);
|
|
209
213
|
|
|
@@ -72,7 +72,10 @@ export class SelectTool extends Tool {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
const segmentSelection = this._findSegmentAtPoint(event.x, event.y);
|
|
75
|
-
|
|
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();
|
package/canvas/ui/toolbar.js
CHANGED
|
@@ -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.
|
|
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.
|
|
74
|
-
const toolbarHeight = this.
|
|
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
|
-
|
|
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 =
|
|
113
|
-
const spacing =
|
|
114
|
-
const padding =
|
|
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.
|
|
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/docs/api/index.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# OMD Library Entry Point
|
|
2
2
|
|
|
3
|
-
This module (`omd/core/index.js`) serves as the main entry point for the OMD (
|
|
3
|
+
This module (`omd/core/index.js`) serves as the main entry point for the OMD (Open Math Display) library. It re-exports all core classes, visualization components, and utility functions, making them easily accessible from a single import.
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
package/docs/api-reference.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# OMD Library API Reference
|
|
2
2
|
|
|
3
|
-
> This is the complete API reference for the OMD (
|
|
3
|
+
> This is the complete API reference for the OMD (Open Math Display) library. Use the table of contents below to navigate to the detailed documentation for each module and class.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
package/docs/index.html
CHANGED
|
@@ -244,7 +244,7 @@
|
|
|
244
244
|
<body>
|
|
245
245
|
<div class="header">
|
|
246
246
|
<h1>OMD Library Documentation</h1>
|
|
247
|
-
<p>
|
|
247
|
+
<p>Open Math Display - Interactive Mathematical Expression Rendering</p>
|
|
248
248
|
<span class="version-badge">v1.0.0</span>
|
|
249
249
|
</div>
|
|
250
250
|
|
|
@@ -304,7 +304,7 @@
|
|
|
304
304
|
<main class="content">
|
|
305
305
|
<section id="overview">
|
|
306
306
|
<h2>Overview</h2>
|
|
307
|
-
<p>OMD (
|
|
307
|
+
<p>OMD (Open Math Display) is a powerful JavaScript library for rendering and manipulating mathematical expressions. It provides a flexible and intuitive API for creating interactive math visualizations.</p>
|
|
308
308
|
|
|
309
309
|
<div class="quick-links">
|
|
310
310
|
<a href="../examples/index.html" class="quick-link">
|
package/docs/index.md
CHANGED
package/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* OMD Library - Main Entry Point
|
|
3
3
|
*
|
|
4
|
-
* This is the main entry point for the OMD (
|
|
4
|
+
* This is the main entry point for the OMD (Open Math Display) library.
|
|
5
5
|
* It exports all core OMD components and visualization tools from a single endpoint.
|
|
6
6
|
*
|
|
7
7
|
* Usage:
|
|
@@ -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
|
+
};
|
package/jsvg/jsvgComponents.js
CHANGED
|
@@ -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
|
+
}
|
package/npm-docs/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# OMD Library API Reference
|
|
2
2
|
|
|
3
|
-
> This is the complete API reference for the OMD (
|
|
3
|
+
> This is the complete API reference for the OMD (Open Math Display) library. Use the table of contents below to navigate to the detailed documentation for each module and class.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
package/npm-docs/api/index.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# OMD Library Entry Point
|
|
2
2
|
|
|
3
|
-
This module (`omd/core/index.js`) serves as the main entry point for the OMD (
|
|
3
|
+
This module (`omd/core/index.js`) serves as the main entry point for the OMD (Open Math Display) library. It re-exports all core classes, visualization components, and utility functions, making them easily accessible from a single import.
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
package/npm-docs/json-schemas.md
CHANGED
|
@@ -460,54 +460,39 @@ Tabular data display with customizable styling.
|
|
|
460
460
|
|
|
461
461
|
#### `omdEquation`
|
|
462
462
|
|
|
463
|
-
Complete mathematical equations
|
|
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
|
-
**
|
|
465
|
+
**Preferred schema (string form):**
|
|
466
466
|
```json
|
|
467
467
|
{
|
|
468
|
-
"equation": "
|
|
468
|
+
"equation": "sin(x) + 2 = 3"
|
|
469
469
|
}
|
|
470
470
|
```
|
|
471
471
|
|
|
472
|
-
**
|
|
472
|
+
**Structured fallback (legacy):**
|
|
473
473
|
```typescript
|
|
474
474
|
{
|
|
475
|
-
leftExpression
|
|
476
|
-
rightExpression
|
|
477
|
-
equation
|
|
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
|
-
//
|
|
491
|
+
// Preferred string form (math.js parsing)
|
|
507
492
|
const eq = new omdEquation();
|
|
508
|
-
eq.loadFromJSON({ equation: '
|
|
493
|
+
eq.loadFromJSON({ equation: 'sqrt(x+1) = 4', fontSize: 32 });
|
|
509
494
|
|
|
510
|
-
//
|
|
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 }
|
|
@@ -240,7 +240,7 @@ export class omdEquationStack extends jsvgGroup {
|
|
|
240
240
|
const effectivePadding = (typeof padding === 'number') ? padding : this.overlayPadding;
|
|
241
241
|
|
|
242
242
|
// Compute top-left of toolbar in container coordinates using UN-SCALED toolbar size
|
|
243
|
-
// because we counter-scale the toolbar by 1/s to keep constant
|
|
243
|
+
// because we counter-scale the toolbar by 1/s to keep constant Open size.
|
|
244
244
|
let containerX = (containerWidth - toolbarWidth) / 2;
|
|
245
245
|
let containerY = containerHeight - toolbarHeight - effectivePadding;
|
|
246
246
|
// Snap to integer pixels to avoid subpixel jitter when scaling
|
|
@@ -259,7 +259,7 @@ export class omdEquationStack extends jsvgGroup {
|
|
|
259
259
|
const svgViewBox = rootSVG?.getAttribute?.('viewBox') || 'unknown';
|
|
260
260
|
|
|
261
261
|
|
|
262
|
-
// Counter-scale the toolbar so it remains a constant
|
|
262
|
+
// Counter-scale the toolbar so it remains a constant Open size
|
|
263
263
|
if (typeof toolbarGroup.setScale === 'function') {
|
|
264
264
|
toolbarGroup.setScale(1 / s);
|
|
265
265
|
}
|
|
@@ -287,7 +287,7 @@ export class omdEquationStack extends jsvgGroup {
|
|
|
287
287
|
* @param {number} [opts.offsetX=0] - Horizontal offset in screen pixels (positive -> right)
|
|
288
288
|
* @param {number} [opts.offsetY=0] - Vertical offset in screen pixels (positive -> down)
|
|
289
289
|
* @param {number} [opts.padding=16] - Padding from edges when computing anchor
|
|
290
|
-
* @param {boolean} [opts.counterScale=true] - Whether to counter-scale the child to keep constant
|
|
290
|
+
* @param {boolean} [opts.counterScale=true] - Whether to counter-scale the child to keep constant Open size
|
|
291
291
|
* @param {boolean} [opts.addToStack=true] - Whether to add the child to this stack's children (default true)
|
|
292
292
|
* @param {{x:number,y:number}|null} [opts.customCoords=null] - If anchor==='custom', use these screen coords
|
|
293
293
|
* @returns {object|null} The child or null if not applicable
|
|
@@ -352,7 +352,7 @@ export class omdEquationStack extends jsvgGroup {
|
|
|
352
352
|
const x = (containerX - stackX) / s;
|
|
353
353
|
const y = (containerY - stackY) / s;
|
|
354
354
|
|
|
355
|
-
// Optionally counter-scale child to keep constant
|
|
355
|
+
// Optionally counter-scale child to keep constant Open size
|
|
356
356
|
if (counterScale && child && typeof child.setScale === 'function') {
|
|
357
357
|
try { child.setScale(1 / s); } catch (_) {}
|
|
358
358
|
}
|
|
@@ -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
|
-
|
|
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.
|
|
986
|
+
this.removeChild(this.node);
|
|
996
987
|
this.node = null;
|
|
997
988
|
}
|
|
998
989
|
}
|
|
@@ -165,7 +165,7 @@ export class omdToolbar {
|
|
|
165
165
|
}
|
|
166
166
|
|
|
167
167
|
if (popupGroup) {
|
|
168
|
-
// Attach to toolbar group so it inherits toolbar counter-scaling (keeps constant
|
|
168
|
+
// Attach to toolbar group so it inherits toolbar counter-scaling (keeps constant Open size)
|
|
169
169
|
this.elements.toolbarGroup.addChild(popupGroup);
|
|
170
170
|
this.state.activePopup = { type: popupType, group: popupGroup };
|
|
171
171
|
// Ensure the toolbar and popup are on top of all siblings inside the SVG
|
|
@@ -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
package/readme.html
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>OMD (
|
|
6
|
+
<title>OMD (Open Math Display) - OMD Documentation</title>
|
|
7
7
|
<style>
|
|
8
8
|
body {
|
|
9
9
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
@@ -145,7 +145,7 @@
|
|
|
145
145
|
</div>
|
|
146
146
|
|
|
147
147
|
<div class="content">
|
|
148
|
-
<h1 id="omd-on-screen-math-display">OMD (
|
|
148
|
+
<h1 id="omd-on-screen-math-display">OMD (Open Math Display)</h1>
|
|
149
149
|
<blockquote>
|
|
150
150
|
<p>A JavaScript library for creating interactive mathematical interfaces in web applications</p>
|
|
151
151
|
</blockquote>
|
package/src/index.js
CHANGED
|
@@ -26,6 +26,12 @@ export { omdFunction } from './omdFunction.js';
|
|
|
26
26
|
export { omdApp } from './omdApp.js';
|
|
27
27
|
export { omdAppCanvas } from './omdAppCanvas.js';
|
|
28
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';
|
|
29
35
|
|
|
30
36
|
// OMD Utilities and Helpers
|
|
31
37
|
export { omdColor } from './omdColor.js';
|
|
@@ -73,6 +79,11 @@ export default {
|
|
|
73
79
|
app: {
|
|
74
80
|
omdApp: () => import('./omdApp.js').then(m => m.omdApp),
|
|
75
81
|
omdAppCanvas: () => import('./omdAppCanvas.js').then(m => m.omdAppCanvas),
|
|
76
|
-
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
|
+
}
|
|
77
87
|
}
|
|
78
|
-
};
|
|
88
|
+
};
|
|
89
|
+
// Force reload
|
package/src/json-schemas.md
CHANGED
|
@@ -677,9 +677,16 @@ This document provides schemas and examples for the `loadFromJSON` method used i
|
|
|
677
677
|
|
|
678
678
|
## 20. `omdEquation`
|
|
679
679
|
|
|
680
|
-
`omdEquation` represents a mathematical equation
|
|
680
|
+
`omdEquation` represents a mathematical equation and now uses the same math.js-powered parser/renderer as the interactive core (functions, rationals, roots, etc.).
|
|
681
681
|
|
|
682
|
-
###
|
|
682
|
+
### Preferred schema (string form)
|
|
683
|
+
```json
|
|
684
|
+
{
|
|
685
|
+
"equation": "sin(x) + 2 = 3"
|
|
686
|
+
}
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
### Structured fallback (legacy)
|
|
683
690
|
```json
|
|
684
691
|
{
|
|
685
692
|
"leftExpression": "object",
|
|
@@ -688,11 +695,10 @@ This document provides schemas and examples for the `loadFromJSON` method used i
|
|
|
688
695
|
}
|
|
689
696
|
```
|
|
690
697
|
|
|
691
|
-
###
|
|
698
|
+
### Examples
|
|
692
699
|
```json
|
|
693
|
-
{
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
```
|
|
700
|
+
{ "equation": "sin(x) + 2 = 3" }
|
|
701
|
+
```
|
|
702
|
+
```json
|
|
703
|
+
{ "equation": "(x^2 + 3x - 4)/(2x) = 5" }
|
|
704
|
+
```
|
package/src/omdEquation.js
CHANGED
|
@@ -9,6 +9,7 @@ import { omdVariable } from "./omdVariable.js";
|
|
|
9
9
|
import { omdString } from "./omdString.js";
|
|
10
10
|
import { omdNumber } from "./omdNumber.js";
|
|
11
11
|
import { parseEquationString } from "./omdUtils.js";
|
|
12
|
+
import { omdEquationNode } from "../omd/nodes/omdEquationNode.js";
|
|
12
13
|
|
|
13
14
|
export class omdEquation extends omdMetaExpression
|
|
14
15
|
{
|
|
@@ -37,12 +38,24 @@ export class omdEquation extends omdMetaExpression
|
|
|
37
38
|
|
|
38
39
|
this.rightHolder = new jsvgGroup();
|
|
39
40
|
this.equationStack.addChild( this.rightHolder );
|
|
41
|
+
|
|
42
|
+
this.equationNode = null;
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
// make an equation (x + 2) = (2x - 3)
|
|
43
46
|
|
|
44
47
|
loadFromJSON( data )
|
|
45
48
|
{
|
|
49
|
+
// Prefer math.js parsing into omdEquationNode for richer rendering (functions, rationals, roots)
|
|
50
|
+
if (typeof data.equation === 'string' && data.equation.trim()) {
|
|
51
|
+
try {
|
|
52
|
+
this._renderWithEquationNode(data.equation, data.fontSize);
|
|
53
|
+
return;
|
|
54
|
+
} catch (e) {
|
|
55
|
+
console.warn('⚠️ omdEquation math.js render failed, falling back to legacy parsing:', e?.message || e);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
46
59
|
// Helper function to fix operator symbols in termsAndOpers arrays
|
|
47
60
|
function fixOperatorSymbols(expressionData) {
|
|
48
61
|
if (expressionData && expressionData.termsAndOpers && Array.isArray(expressionData.termsAndOpers)) {
|
|
@@ -136,6 +149,37 @@ export class omdEquation extends omdMetaExpression
|
|
|
136
149
|
this.updateLayout();
|
|
137
150
|
}
|
|
138
151
|
|
|
152
|
+
_renderWithEquationNode(equationString, fontSize) {
|
|
153
|
+
if (typeof math === 'undefined' || typeof math.parse !== 'function') {
|
|
154
|
+
throw new Error('math.js is required to parse equation strings');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const eqNode = omdEquationNode.fromString(equationString);
|
|
158
|
+
if (typeof fontSize === 'number' && eqNode.setFontSize) {
|
|
159
|
+
eqNode.setFontSize(fontSize);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (eqNode.hideBackgroundByDefault) eqNode.hideBackgroundByDefault();
|
|
163
|
+
if (eqNode.computeDimensions) eqNode.computeDimensions();
|
|
164
|
+
if (eqNode.updateLayout) eqNode.updateLayout();
|
|
165
|
+
|
|
166
|
+
// Clear the legacy stack and render just the parsed node
|
|
167
|
+
if (typeof this.equationStack.removeAllChildren === 'function') {
|
|
168
|
+
this.equationStack.removeAllChildren();
|
|
169
|
+
} else {
|
|
170
|
+
this.equationStack.childList = [];
|
|
171
|
+
}
|
|
172
|
+
this.equationStack.addChild(eqNode);
|
|
173
|
+
this.equationStack.setSpacer(0);
|
|
174
|
+
|
|
175
|
+
this.equationNode = eqNode;
|
|
176
|
+
this.leftExpression = null;
|
|
177
|
+
this.rightExpression = null;
|
|
178
|
+
|
|
179
|
+
this.centerEquation = false;
|
|
180
|
+
this.updateLayout();
|
|
181
|
+
}
|
|
182
|
+
|
|
139
183
|
|
|
140
184
|
setLeftAndRightExpressions( leftExp, rightExp )
|
|
141
185
|
{
|
|
@@ -156,6 +200,25 @@ export class omdEquation extends omdMetaExpression
|
|
|
156
200
|
|
|
157
201
|
updateLayout()
|
|
158
202
|
{
|
|
203
|
+
if (this.equationNode) {
|
|
204
|
+
const node = this.equationNode;
|
|
205
|
+
if (node.computeDimensions) node.computeDimensions();
|
|
206
|
+
if (node.updateLayout) node.updateLayout();
|
|
207
|
+
|
|
208
|
+
this.equationStack.doHorizontalLayout();
|
|
209
|
+
this.equationStack.setPosition(this.inset, this.inset);
|
|
210
|
+
|
|
211
|
+
const W = node.width || this.equationStack.width;
|
|
212
|
+
const H = node.height || this.equationStack.height;
|
|
213
|
+
|
|
214
|
+
this.backRect.setWidthAndHeight( W + this.inset*2, H + this.inset*2 );
|
|
215
|
+
this.setWidthAndHeight( this.backRect.width, this.backRect.height );
|
|
216
|
+
this.width = this.backRect.width;
|
|
217
|
+
this.height = this.backRect.height;
|
|
218
|
+
this.svgObject.setAttribute('viewBox', `0 0 ${this.width} ${this.height}`);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
159
222
|
this.leftHolder.setWidthAndHeight( this.leftExpression.width, this.leftExpression.height );
|
|
160
223
|
this.rightHolder.setWidthAndHeight( this.rightExpression.width, this.rightExpression.height );
|
|
161
224
|
|
|
@@ -186,4 +249,4 @@ export class omdEquation extends omdMetaExpression
|
|
|
186
249
|
}
|
|
187
250
|
}
|
|
188
251
|
|
|
189
|
-
}
|
|
252
|
+
}
|
package/README.old.md
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
# OMD (On-screen Math Display)
|
|
2
|
-
|
|
3
|
-
OMD is a JavaScript library for creating interactive mathematical interfaces in web applications. Build everything from simple equation displays to complex step-by-step solution systems with rich visual feedback and user interaction.
|
|
4
|
-
|
|
5
|
-

|
|
6
|
-
|
|
7
|
-
## Features
|
|
8
|
-
|
|
9
|
-
### **Interactive Math Rendering**
|
|
10
|
-
- High-quality SVG-based mathematical notation
|
|
11
|
-
- Real-time expression manipulation and visualization
|
|
12
|
-
- Automatic layout and alignment for complex equations
|
|
13
|
-
|
|
14
|
-
### **Step-by-Step Solutions**
|
|
15
|
-
- Visual step tracking with detailed explanations
|
|
16
|
-
- Simplification engine with rule-based transformations
|
|
17
|
-
- Provenance tracking for highlighting related elements
|
|
18
|
-
|
|
19
|
-
### **Rich UI Components**
|
|
20
|
-
- Built-in toolbar for common mathematical operations
|
|
21
|
-
- Drag & drop interface for intuitive manipulation
|
|
22
|
-
- Customizable canvas for multi-expression layouts
|
|
23
|
-
|
|
24
|
-
### **Educational Features**
|
|
25
|
-
- Interactive learning experiences
|
|
26
|
-
- Progressive step revelation
|
|
27
|
-
- Visual operation feedback and highlighting
|
|
28
|
-
|
|
29
|
-
## Installation
|
|
30
|
-
|
|
31
|
-
### npm
|
|
32
|
-
```bash
|
|
33
|
-
npm install @teachinglab/omd
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
## Basic Usage
|
|
37
|
-
```javascript
|
|
38
|
-
import { omdDisplay } from '@teachinglab/omd';
|
|
39
|
-
|
|
40
|
-
// Create a math display
|
|
41
|
-
const container = document.getElementById('math-container');
|
|
42
|
-
const display = new omdDisplay(container);
|
|
43
|
-
|
|
44
|
-
// Render an equation
|
|
45
|
-
display.render('2x + 3 = 11');
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
### Step-by-Step Solutions
|
|
49
|
-
```javascript
|
|
50
|
-
import { omdEquationStack, omdEquationNode } from '@teachinglab/omd';
|
|
51
|
-
|
|
52
|
-
// Create solution steps
|
|
53
|
-
const steps = [
|
|
54
|
-
omdEquationNode.fromString('2x + 3 = 11'),
|
|
55
|
-
omdEquationNode.fromString('2x = 8'),
|
|
56
|
-
omdEquationNode.fromString('x = 4')
|
|
57
|
-
];
|
|
58
|
-
|
|
59
|
-
// Create interactive equation stack
|
|
60
|
-
const stack = new omdEquationStack(steps, {
|
|
61
|
-
toolbar: true,
|
|
62
|
-
stepVisualizer: true
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
display.render(stack);
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
## Core Concepts
|
|
69
|
-
|
|
70
|
-
### **Nodes** - Building Blocks
|
|
71
|
-
Every mathematical element is a node in an expression tree:
|
|
72
|
-
- `omdEquationNode` - Complete equations (e.g., `2x + 3 = 11`)
|
|
73
|
-
- `omdConstantNode` - Numbers (e.g., `5`, `3.14`)
|
|
74
|
-
- `omdVariableNode` - Variables (e.g., `x`, `y`)
|
|
75
|
-
- `omdBinaryExpressionNode` - Operations (e.g., `+`, `-`, `*`, `/`)
|
|
76
|
-
|
|
77
|
-
### **Sequences** - Solution Steps
|
|
78
|
-
Group related equations for step-by-step solving:
|
|
79
|
-
```javascript
|
|
80
|
-
const sequence = new omdEquationSequenceNode([
|
|
81
|
-
equation1, equation2, equation3
|
|
82
|
-
]);
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
### **Display** - Rendering Engine
|
|
86
|
-
Handles layout, centering, and visualization:
|
|
87
|
-
```javascript
|
|
88
|
-
const display = new omdDisplay(container, {
|
|
89
|
-
fontSize: 36,
|
|
90
|
-
centerContent: true
|
|
91
|
-
});
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
## Interactive Examples
|
|
95
|
-
|
|
96
|
-
Explore OMD's capabilities with our comprehensive examples:
|
|
97
|
-
|
|
98
|
-
| Category | Example | Description |
|
|
99
|
-
|----------|---------|-------------|
|
|
100
|
-
| **Getting Started** | [Minimal](examples/minimal.html) | Basic equation rendering |
|
|
101
|
-
| | [Simple Usage](examples/simple-usage.html) | Interactive features |
|
|
102
|
-
| **Advanced** | [Expression Playground](examples/expression-playground.html) | Full manipulation interface |
|
|
103
|
-
| | [Drag & Drop](examples/drag-and-drop-playground.html) | Intuitive interaction |
|
|
104
|
-
| **Educational** | [Worked Solutions](examples/worked-solution.html) | Step-by-step solving |
|
|
105
|
-
| | [Kids Interactive](examples/kids-interactive.html) | Child-friendly interface |
|
|
106
|
-
| **Components** | [Equation Stack](examples/equation-stack-test.html) | Stacked equations |
|
|
107
|
-
| | [Canvas Demo](examples/canvas-multiple-nodes.html) | Multi-expression layouts |
|
|
108
|
-
|
|
109
|
-
**[Browse All Examples](examples/index.html)**
|
|
110
|
-
|
|
111
|
-
## Documentation
|
|
112
|
-
|
|
113
|
-
| Resource | Description |
|
|
114
|
-
|----------|-------------|
|
|
115
|
-
| **[API Reference](docs/api-reference.md)** | Complete component documentation |
|
|
116
|
-
| **[User Guide](docs/user-guide.md)** | Getting started and tutorials |
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
## Architecture
|
|
120
|
-
|
|
121
|
-
```
|
|
122
|
-
OMD Library Structure
|
|
123
|
-
├── Display Layer (omdDisplay)
|
|
124
|
-
├── Node System (Expression tree components)
|
|
125
|
-
├── UI Components (Toolbar, Step visualizer)
|
|
126
|
-
├── Core Systems (Simplification, Layout)
|
|
127
|
-
└── Utilities (Configuration, Helpers)
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
## Dependencies
|
|
131
|
-
|
|
132
|
-
- **JSVG** - High-performance SVG rendering
|
|
133
|
-
- **math.js** - Mathematical expression parsing
|
|
134
|
-
- **Modern Browser** - ES6 modules, SVG support
|
|
135
|
-
|
|
136
|
-
---
|
|
137
|
-
|
|
138
|
-
**Ready to get started?** Check out our [examples](examples/index.html) or dive into the [documentation](docs/api-reference.md)!
|