@teachinglab/omd 0.1.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.
- package/README.md +138 -0
- package/canvas/core/canvasConfig.js +203 -0
- package/canvas/core/omdCanvas.js +475 -0
- package/canvas/drawing/segment.js +168 -0
- package/canvas/drawing/stroke.js +386 -0
- package/canvas/events/eventManager.js +435 -0
- package/canvas/events/pointerEventHandler.js +263 -0
- package/canvas/features/focusFrameManager.js +287 -0
- package/canvas/index.js +49 -0
- package/canvas/tools/eraserTool.js +322 -0
- package/canvas/tools/pencilTool.js +319 -0
- package/canvas/tools/selectTool.js +457 -0
- package/canvas/tools/tool.js +223 -0
- package/canvas/tools/toolManager.js +394 -0
- package/canvas/ui/cursor.js +438 -0
- package/canvas/ui/toolbar.js +304 -0
- package/canvas/utils/boundingBox.js +378 -0
- package/canvas/utils/mathUtils.js +259 -0
- package/docs/api/configuration-options.md +104 -0
- package/docs/api/eventManager.md +68 -0
- package/docs/api/focusFrameManager.md +150 -0
- package/docs/api/index.md +91 -0
- package/docs/api/main.md +58 -0
- package/docs/api/omdBinaryExpressionNode.md +227 -0
- package/docs/api/omdCanvas.md +142 -0
- package/docs/api/omdConfigManager.md +192 -0
- package/docs/api/omdConstantNode.md +117 -0
- package/docs/api/omdDisplay.md +121 -0
- package/docs/api/omdEquationNode.md +161 -0
- package/docs/api/omdEquationSequenceNode.md +301 -0
- package/docs/api/omdEquationStack.md +139 -0
- package/docs/api/omdFunctionNode.md +141 -0
- package/docs/api/omdGroupNode.md +182 -0
- package/docs/api/omdHelpers.md +96 -0
- package/docs/api/omdLeafNode.md +163 -0
- package/docs/api/omdNode.md +101 -0
- package/docs/api/omdOperationDisplayNode.md +139 -0
- package/docs/api/omdOperatorNode.md +127 -0
- package/docs/api/omdParenthesisNode.md +122 -0
- package/docs/api/omdPopup.md +117 -0
- package/docs/api/omdPowerNode.md +127 -0
- package/docs/api/omdRationalNode.md +128 -0
- package/docs/api/omdSequenceNode.md +128 -0
- package/docs/api/omdSimplification.md +110 -0
- package/docs/api/omdSqrtNode.md +79 -0
- package/docs/api/omdStepVisualizer.md +115 -0
- package/docs/api/omdStepVisualizerHighlighting.md +61 -0
- package/docs/api/omdStepVisualizerInteractiveSteps.md +129 -0
- package/docs/api/omdStepVisualizerLayout.md +60 -0
- package/docs/api/omdStepVisualizerNodeUtils.md +140 -0
- package/docs/api/omdStepVisualizerTextBoxes.md +68 -0
- package/docs/api/omdToolbar.md +102 -0
- package/docs/api/omdTranscriptionService.md +76 -0
- package/docs/api/omdTreeDiff.md +134 -0
- package/docs/api/omdUnaryExpressionNode.md +174 -0
- package/docs/api/omdUtilities.md +70 -0
- package/docs/api/omdVariableNode.md +148 -0
- package/docs/api/selectTool.md +74 -0
- package/docs/api/simplificationEngine.md +98 -0
- package/docs/api/simplificationRules.md +77 -0
- package/docs/api/simplificationUtils.md +64 -0
- package/docs/api/transcribe.md +43 -0
- package/docs/api-reference.md +85 -0
- package/docs/index.html +454 -0
- package/docs/user-guide.md +9 -0
- package/index.js +67 -0
- package/omd/config/omdConfigManager.js +267 -0
- package/omd/core/index.js +150 -0
- package/omd/core/omdEquationStack.js +347 -0
- package/omd/core/omdUtilities.js +115 -0
- package/omd/display/omdDisplay.js +443 -0
- package/omd/display/omdToolbar.js +502 -0
- package/omd/nodes/omdBinaryExpressionNode.js +460 -0
- package/omd/nodes/omdConstantNode.js +142 -0
- package/omd/nodes/omdEquationNode.js +1223 -0
- package/omd/nodes/omdEquationSequenceNode.js +1273 -0
- package/omd/nodes/omdFunctionNode.js +352 -0
- package/omd/nodes/omdGroupNode.js +68 -0
- package/omd/nodes/omdLeafNode.js +77 -0
- package/omd/nodes/omdNode.js +557 -0
- package/omd/nodes/omdOperationDisplayNode.js +322 -0
- package/omd/nodes/omdOperatorNode.js +109 -0
- package/omd/nodes/omdParenthesisNode.js +293 -0
- package/omd/nodes/omdPowerNode.js +236 -0
- package/omd/nodes/omdRationalNode.js +295 -0
- package/omd/nodes/omdSqrtNode.js +308 -0
- package/omd/nodes/omdUnaryExpressionNode.js +178 -0
- package/omd/nodes/omdVariableNode.js +123 -0
- package/omd/simplification/omdSimplification.js +171 -0
- package/omd/simplification/omdSimplificationEngine.js +886 -0
- package/omd/simplification/package.json +6 -0
- package/omd/simplification/rules/binaryRules.js +1037 -0
- package/omd/simplification/rules/functionRules.js +111 -0
- package/omd/simplification/rules/index.js +48 -0
- package/omd/simplification/rules/parenthesisRules.js +19 -0
- package/omd/simplification/rules/powerRules.js +143 -0
- package/omd/simplification/rules/rationalRules.js +475 -0
- package/omd/simplification/rules/sqrtRules.js +48 -0
- package/omd/simplification/rules/unaryRules.js +37 -0
- package/omd/simplification/simplificationRules.js +32 -0
- package/omd/simplification/simplificationUtils.js +1056 -0
- package/omd/step-visualizer/omdStepVisualizer.js +597 -0
- package/omd/step-visualizer/omdStepVisualizerHighlighting.js +206 -0
- package/omd/step-visualizer/omdStepVisualizerLayout.js +245 -0
- package/omd/step-visualizer/omdStepVisualizerTextBoxes.js +163 -0
- package/omd/utils/omdNodeOverlay.js +638 -0
- package/omd/utils/omdPopup.js +1084 -0
- package/omd/utils/omdStepVisualizerInteractiveSteps.js +491 -0
- package/omd/utils/omdStepVisualizerNodeUtils.js +268 -0
- package/omd/utils/omdTranscriptionService.js +125 -0
- package/omd/utils/omdTreeDiff.js +734 -0
- package/package.json +46 -0
- package/src/index.js +62 -0
- package/src/json-schemas.md +109 -0
- package/src/omd-json-samples.js +115 -0
- package/src/omd.js +109 -0
- package/src/omdApp.js +391 -0
- package/src/omdAppCanvas.js +336 -0
- package/src/omdBalanceHanger.js +172 -0
- package/src/omdColor.js +13 -0
- package/src/omdCoordinatePlane.js +467 -0
- package/src/omdEquation.js +125 -0
- package/src/omdExpression.js +104 -0
- package/src/omdFunction.js +113 -0
- package/src/omdMetaExpression.js +287 -0
- package/src/omdNaturalExpression.js +564 -0
- package/src/omdNode.js +384 -0
- package/src/omdNumber.js +53 -0
- package/src/omdNumberLine.js +107 -0
- package/src/omdNumberTile.js +119 -0
- package/src/omdOperator.js +73 -0
- package/src/omdPowerExpression.js +92 -0
- package/src/omdProblem.js +55 -0
- package/src/omdRatioChart.js +232 -0
- package/src/omdRationalExpression.js +115 -0
- package/src/omdSampleData.js +215 -0
- package/src/omdShapes.js +476 -0
- package/src/omdSpinner.js +148 -0
- package/src/omdString.js +39 -0
- package/src/omdTable.js +369 -0
- package/src/omdTapeDiagram.js +245 -0
- package/src/omdTerm.js +92 -0
- package/src/omdTileEquation.js +349 -0
- package/src/omdVariable.js +51 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { omdColor } from "./omdColor.js";
|
|
2
|
+
import { jsvgGroup, jsvgEllipse, jsvgPath, jsvgLine, jsvgTextBox } from "@teachinglab/jsvg";
|
|
3
|
+
|
|
4
|
+
export class omdSpinner extends jsvgGroup
|
|
5
|
+
{
|
|
6
|
+
constructor()
|
|
7
|
+
{
|
|
8
|
+
// initialization
|
|
9
|
+
super();
|
|
10
|
+
|
|
11
|
+
this.type = "omdNumberLine";
|
|
12
|
+
this.size = "medium";
|
|
13
|
+
|
|
14
|
+
this.divisions = 5;
|
|
15
|
+
this.arrowPosition = 1;
|
|
16
|
+
this.updateLayout();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
loadFromJSON( data )
|
|
20
|
+
{
|
|
21
|
+
if ( typeof data.divisions != "undefined" )
|
|
22
|
+
this.divisions = data.divisions;
|
|
23
|
+
|
|
24
|
+
if ( typeof data.arrowPosition != "undefined" )
|
|
25
|
+
this.arrowPosition = data.arrowPosition;
|
|
26
|
+
|
|
27
|
+
if ( typeof data.size != "undefined" )
|
|
28
|
+
this.size = data.size;
|
|
29
|
+
|
|
30
|
+
this.updateLayout();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
setDivisions( D )
|
|
34
|
+
{
|
|
35
|
+
this.divisions = D;
|
|
36
|
+
this.updateLayout();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
setRenderType( R )
|
|
40
|
+
{
|
|
41
|
+
this.renderType = R;
|
|
42
|
+
this.updateLayout();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
setSize( S )
|
|
46
|
+
{
|
|
47
|
+
this.size = S;
|
|
48
|
+
this.updateLayout();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
setArrowPosition( index )
|
|
52
|
+
{
|
|
53
|
+
this.arrowPosition = index;
|
|
54
|
+
this.updateLayout();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
updateLayout()
|
|
58
|
+
{
|
|
59
|
+
this.removeAllChildren();
|
|
60
|
+
|
|
61
|
+
// holder group
|
|
62
|
+
var G = new jsvgGroup();
|
|
63
|
+
this.addChild( G );
|
|
64
|
+
|
|
65
|
+
var circleSize = 120;
|
|
66
|
+
var textSize = 14;
|
|
67
|
+
if ( this.size == "large" )
|
|
68
|
+
{
|
|
69
|
+
circleSize = 120;
|
|
70
|
+
textSize = 14;
|
|
71
|
+
}
|
|
72
|
+
if ( this.size == "medium" )
|
|
73
|
+
{
|
|
74
|
+
circleSize = 80;
|
|
75
|
+
textSize = 12;
|
|
76
|
+
}
|
|
77
|
+
if ( this.size == "small" )
|
|
78
|
+
{
|
|
79
|
+
circleSize = 40;
|
|
80
|
+
textSize = 10;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// make circle
|
|
84
|
+
var C = new jsvgEllipse();
|
|
85
|
+
C.setFillColor( omdColor.mediumGray );
|
|
86
|
+
C.setWidthAndHeight( circleSize, circleSize );
|
|
87
|
+
G.addChild( C );
|
|
88
|
+
|
|
89
|
+
// offset circle position
|
|
90
|
+
G.setPosition( circleSize/2, circleSize/2 );
|
|
91
|
+
|
|
92
|
+
// make division lines
|
|
93
|
+
var total = this.divisions;
|
|
94
|
+
var dA = Math.PI*2.0 / total;
|
|
95
|
+
for( var i=0; i<total; i++ )
|
|
96
|
+
{
|
|
97
|
+
var A = i * dA - Math.PI/2.0;
|
|
98
|
+
var pX = Math.cos(A) * circleSize/2;
|
|
99
|
+
var pY = Math.sin(A) * circleSize/2;
|
|
100
|
+
var L = new jsvgLine();
|
|
101
|
+
L.setStrokeColor("white");
|
|
102
|
+
L.setEndpoints( 0, 0, pX, pY );
|
|
103
|
+
G.addChild( L );
|
|
104
|
+
|
|
105
|
+
// make tick numbers
|
|
106
|
+
var A = (i+0.5) * dA - Math.PI/2.0;
|
|
107
|
+
var pX = Math.cos(A) * circleSize*0.40;
|
|
108
|
+
var pY = Math.sin(A) * circleSize*0.40;
|
|
109
|
+
var tickText = new jsvgTextBox();
|
|
110
|
+
tickText.setWidthAndHeight( 30,30 );
|
|
111
|
+
tickText.setText ( (i+1).toString() );
|
|
112
|
+
tickText.setFontFamily( "Albert Sans" );
|
|
113
|
+
tickText.setFontColor( "black" );
|
|
114
|
+
tickText.setFontSize( textSize );
|
|
115
|
+
tickText.setAlignment("center");
|
|
116
|
+
tickText.setPosition( pX-15, pY-7 );
|
|
117
|
+
G.addChild( tickText );
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
// make arrow
|
|
123
|
+
var D = circleSize*0.40;
|
|
124
|
+
this.arrow = new jsvgPath();
|
|
125
|
+
this.arrow.addPoint( 0,0 );
|
|
126
|
+
this.arrow.addPoint( D*0.8,D*0.1 );
|
|
127
|
+
this.arrow.addPoint( D,0 );
|
|
128
|
+
this.arrow.addPoint( D*0.8,D*-0.1 );
|
|
129
|
+
this.arrow.addPoint( 0,0 );
|
|
130
|
+
this.arrow.updatePath();
|
|
131
|
+
this.arrow.setFillColor( "black" );
|
|
132
|
+
this.arrow.setOpacity( 0.80 );
|
|
133
|
+
G.addChild( this.arrow );
|
|
134
|
+
|
|
135
|
+
// set arrow position
|
|
136
|
+
var A = -90 + 360.0 / this.divisions * (this.arrowPosition-0.5);
|
|
137
|
+
this.arrow.setRotation( A );
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
// var L = new jsvgLine();
|
|
142
|
+
// var lineLength = circleSize*0.4;
|
|
143
|
+
// L.setEndpoints( 0,0, lineLength,0 );
|
|
144
|
+
// L.setStrokeColor("black");
|
|
145
|
+
// L.setStrokeWidth(2);
|
|
146
|
+
// L.setRotation( -22.5 );
|
|
147
|
+
}
|
|
148
|
+
}
|
package/src/omdString.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
|
|
2
|
+
import { omdColor } from "./omdColor.js";
|
|
3
|
+
import { omdMetaExpression } from "./omdMetaExpression.js"
|
|
4
|
+
|
|
5
|
+
export class omdString extends omdMetaExpression
|
|
6
|
+
{
|
|
7
|
+
constructor( V = 'string' )
|
|
8
|
+
{
|
|
9
|
+
// initialization
|
|
10
|
+
super();
|
|
11
|
+
|
|
12
|
+
this.type = "omdVariable";
|
|
13
|
+
|
|
14
|
+
this.numText = new jsvgTextBox();
|
|
15
|
+
this.numText.setWidthAndHeight( 30,30 );
|
|
16
|
+
this.numText.setText ( this.name );
|
|
17
|
+
this.numText.setFontFamily( "Albert Sans" );
|
|
18
|
+
this.numText.setFontColor( "black" );
|
|
19
|
+
this.numText.setFontSize( 18 );
|
|
20
|
+
this.numText.setVerticalCentering();
|
|
21
|
+
this.numText.setAlignment("center");
|
|
22
|
+
// this.numText.div.style.border = "1px solid black";
|
|
23
|
+
this.addChild( this.numText );
|
|
24
|
+
|
|
25
|
+
this.setName( V );
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
setName( newName )
|
|
29
|
+
{
|
|
30
|
+
this.name = newName;
|
|
31
|
+
|
|
32
|
+
var W = 15 + this.name.length*10;
|
|
33
|
+
this.backRect.setWidthAndHeight( W, 30 );
|
|
34
|
+
this.numText.setWidthAndHeight( W, 30 );
|
|
35
|
+
this.numText.setText ( this.name );
|
|
36
|
+
|
|
37
|
+
this.setWidthAndHeight( this.backRect.width, this.backRect.height );
|
|
38
|
+
}
|
|
39
|
+
}
|
package/src/omdTable.js
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import { omdColor } from "./omdColor.js";
|
|
2
|
+
import { jsvgGroup, jsvgRect, jsvgTextBox } from "@teachinglab/jsvg";
|
|
3
|
+
|
|
4
|
+
export class omdTable extends jsvgGroup
|
|
5
|
+
{
|
|
6
|
+
constructor()
|
|
7
|
+
{
|
|
8
|
+
// initialization
|
|
9
|
+
super();
|
|
10
|
+
|
|
11
|
+
this.type = "omdTable";
|
|
12
|
+
|
|
13
|
+
this.equation = "";
|
|
14
|
+
this.data = [];
|
|
15
|
+
this.headers = ['x', 'y'];
|
|
16
|
+
this.xMin = -5;
|
|
17
|
+
this.xMax = 5;
|
|
18
|
+
this.stepSize = 1;
|
|
19
|
+
this.title = "";
|
|
20
|
+
this.fontSize = 14;
|
|
21
|
+
this.headerFontSize = 16;
|
|
22
|
+
this.fontFamily = "Albert Sans";
|
|
23
|
+
this.headerFontFamily = "Albert Sans";
|
|
24
|
+
this.cellHeight = 35;
|
|
25
|
+
this.headerHeight = 40;
|
|
26
|
+
this.minCellWidth = 80;
|
|
27
|
+
this.maxCellWidth = 300;
|
|
28
|
+
this.padding = 10;
|
|
29
|
+
|
|
30
|
+
this.updateLayout();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Estimate title width in pixels based on font size and text length
|
|
34
|
+
estimateTitleWidth()
|
|
35
|
+
{
|
|
36
|
+
if (!this.title || this.title.length === 0) return 0;
|
|
37
|
+
const titleFontSize = this.headerFontSize + 2;
|
|
38
|
+
const padding = 40; // side padding inside the title text box
|
|
39
|
+
const minWidth = 200;
|
|
40
|
+
const estimated = Math.round(this.title.length * (titleFontSize * 0.6)) + padding;
|
|
41
|
+
return Math.max(minWidth, estimated);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
loadFromJSON( data )
|
|
45
|
+
{
|
|
46
|
+
if ( typeof data.equation != "undefined" )
|
|
47
|
+
this.equation = data.equation;
|
|
48
|
+
|
|
49
|
+
if ( typeof data.data != "undefined" )
|
|
50
|
+
this.data = data.data;
|
|
51
|
+
|
|
52
|
+
if ( typeof data.headers != "undefined" )
|
|
53
|
+
this.headers = data.headers;
|
|
54
|
+
|
|
55
|
+
if ( typeof data.xMin != "undefined" )
|
|
56
|
+
this.xMin = data.xMin;
|
|
57
|
+
|
|
58
|
+
if ( typeof data.xMax != "undefined" )
|
|
59
|
+
this.xMax = data.xMax;
|
|
60
|
+
|
|
61
|
+
if ( typeof data.stepSize != "undefined" )
|
|
62
|
+
this.stepSize = data.stepSize;
|
|
63
|
+
|
|
64
|
+
if ( typeof data.title != "undefined" )
|
|
65
|
+
this.title = data.title;
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
if ( typeof data.fontSize != "undefined" )
|
|
69
|
+
this.fontSize = data.fontSize;
|
|
70
|
+
|
|
71
|
+
if ( typeof data.headerFontSize != "undefined" )
|
|
72
|
+
this.headerFontSize = data.headerFontSize;
|
|
73
|
+
|
|
74
|
+
if ( typeof data.fontFamily != "undefined" )
|
|
75
|
+
this.fontFamily = data.fontFamily;
|
|
76
|
+
|
|
77
|
+
if ( typeof data.headerFontFamily != "undefined" )
|
|
78
|
+
this.headerFontFamily = data.headerFontFamily;
|
|
79
|
+
|
|
80
|
+
if ( typeof data.cellHeight != "undefined" )
|
|
81
|
+
this.cellHeight = data.cellHeight;
|
|
82
|
+
|
|
83
|
+
if ( typeof data.headerHeight != "undefined" )
|
|
84
|
+
this.headerHeight = data.headerHeight;
|
|
85
|
+
|
|
86
|
+
if ( typeof data.minCellWidth != "undefined" )
|
|
87
|
+
this.minCellWidth = data.minCellWidth;
|
|
88
|
+
|
|
89
|
+
if ( typeof data.maxCellWidth != "undefined" )
|
|
90
|
+
this.maxCellWidth = data.maxCellWidth;
|
|
91
|
+
|
|
92
|
+
if ( typeof data.padding != "undefined" )
|
|
93
|
+
this.padding = data.padding;
|
|
94
|
+
|
|
95
|
+
this.updateLayout();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
setEquation( equation )
|
|
99
|
+
{
|
|
100
|
+
this.equation = equation;
|
|
101
|
+
this.updateLayout();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
setData( data, headers )
|
|
105
|
+
{
|
|
106
|
+
this.data = data;
|
|
107
|
+
if ( headers )
|
|
108
|
+
this.headers = headers;
|
|
109
|
+
this.updateLayout();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
calculateOptimalCellWidth(columnIndex)
|
|
113
|
+
{
|
|
114
|
+
let maxLength = (this.headers[columnIndex] ?? '').toString().length;
|
|
115
|
+
|
|
116
|
+
// Assume rows are arrays aligned with headers
|
|
117
|
+
for (let row of this.data) {
|
|
118
|
+
const cellValue = row[columnIndex];
|
|
119
|
+
if (cellValue !== null && cellValue !== undefined) {
|
|
120
|
+
maxLength = Math.max(maxLength, cellValue.toString().length);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Estimate width based on character count (approximate 8 pixels per character)
|
|
125
|
+
const estimatedWidth = Math.max(maxLength * 8 + this.padding * 2, this.minCellWidth);
|
|
126
|
+
return Math.min(estimatedWidth, this.maxCellWidth);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
generateDataFromEquation()
|
|
130
|
+
{
|
|
131
|
+
if (!this.equation || this.equation.trim().length === 0) return;
|
|
132
|
+
|
|
133
|
+
// Clear existing data and set headers
|
|
134
|
+
this.data = [];
|
|
135
|
+
this.headers = ['x', 'y'];
|
|
136
|
+
|
|
137
|
+
// Basic normalization for inline math
|
|
138
|
+
let expression = this.equation;
|
|
139
|
+
if (expression.toLowerCase().startsWith('y=')) {
|
|
140
|
+
expression = expression.substring(2).trim();
|
|
141
|
+
}
|
|
142
|
+
expression = expression
|
|
143
|
+
.replace(/(\d)([a-z])/gi, '$1*$2')
|
|
144
|
+
.replace(/([a-z])(\d)/gi, '$1*$2')
|
|
145
|
+
.replace(/\^/g, '**');
|
|
146
|
+
|
|
147
|
+
const evaluateExpression = new Function('x', `return ${expression};`);
|
|
148
|
+
|
|
149
|
+
for (let x = this.xMin; x <= this.xMax; x += this.stepSize) {
|
|
150
|
+
let y = evaluateExpression(x);
|
|
151
|
+
y = Math.round(y * 100) / 100;
|
|
152
|
+
this.data.push([x, y]);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
updateLayout()
|
|
157
|
+
{
|
|
158
|
+
this.removeAllChildren();
|
|
159
|
+
|
|
160
|
+
// If an equation is provided, generate data before measuring/layout
|
|
161
|
+
if (this.equation && this.equation.length > 0) {
|
|
162
|
+
this.generateDataFromEquation();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Calculate table dimensions
|
|
166
|
+
const numCols = this.headers.length;
|
|
167
|
+
const numRows = this.data.length;
|
|
168
|
+
let cellWidths = [];
|
|
169
|
+
let totalWidth = 0;
|
|
170
|
+
for (let col = 0; col < numCols; col++) {
|
|
171
|
+
const width = this.calculateOptimalCellWidth(col);
|
|
172
|
+
cellWidths.push(width);
|
|
173
|
+
totalWidth += width;
|
|
174
|
+
}
|
|
175
|
+
this.width = totalWidth;
|
|
176
|
+
const titleOffset = (this.title && this.title.length > 0) ? 30 : 0;
|
|
177
|
+
const bodyHeight = this.headerHeight + numRows * this.cellHeight;
|
|
178
|
+
const totalHeight = titleOffset + bodyHeight;
|
|
179
|
+
this.height = totalHeight;
|
|
180
|
+
|
|
181
|
+
// Compute a display width that ensures the title is not clipped,
|
|
182
|
+
// without changing column widths or table background.
|
|
183
|
+
const titleBoxWidth = this.estimateTitleWidth();
|
|
184
|
+
const displayWidth = Math.max(this.width, titleBoxWidth);
|
|
185
|
+
|
|
186
|
+
// Table background with corner radius (all four corners, covers full height)
|
|
187
|
+
const tableBg = new jsvgRect();
|
|
188
|
+
tableBg.setWidthAndHeight(this.width, bodyHeight);
|
|
189
|
+
tableBg.setFillColor(omdColor.lightGray);
|
|
190
|
+
tableBg.setCornerRadius(15);
|
|
191
|
+
tableBg.setStrokeWidth(0);
|
|
192
|
+
const contentOffsetX = Math.max(0, (displayWidth - this.width) / 2);
|
|
193
|
+
tableBg.setPosition(contentOffsetX, titleOffset);
|
|
194
|
+
this.addChild(tableBg);
|
|
195
|
+
|
|
196
|
+
// Draw a rounded footer rectangle under the last row to provide bottom rounded corners
|
|
197
|
+
if (numRows > 0) {
|
|
198
|
+
const footer = new jsvgRect();
|
|
199
|
+
footer.setWidthAndHeight(this.width, this.cellHeight);
|
|
200
|
+
footer.setFillColor(omdColor.lightGray);
|
|
201
|
+
footer.setCornerRadius(15);
|
|
202
|
+
footer.setStrokeWidth(0);
|
|
203
|
+
const footerY = titleOffset + this.headerHeight + (numRows - 1) * this.cellHeight;
|
|
204
|
+
footer.setPosition(contentOffsetX, footerY);
|
|
205
|
+
this.addChild(footer);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Generate data from equation if provided; otherwise assume valid data/headers
|
|
209
|
+
if (this.equation && this.equation.length > 0) {
|
|
210
|
+
this.generateDataFromEquation();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
let currentY = 0;
|
|
214
|
+
// Add title if provided
|
|
215
|
+
if (this.title && this.title.length > 0) {
|
|
216
|
+
var titleText = new jsvgTextBox();
|
|
217
|
+
// Use an expanded text box width (only for title) to avoid clipping
|
|
218
|
+
titleText.setWidthAndHeight(titleBoxWidth, 25);
|
|
219
|
+
titleText.setText(this.title);
|
|
220
|
+
titleText.setFontFamily(this.headerFontFamily);
|
|
221
|
+
titleText.setFontColor("black");
|
|
222
|
+
titleText.setFontSize(this.headerFontSize + 2);
|
|
223
|
+
titleText.setAlignment("center");
|
|
224
|
+
titleText.setVerticalCentering();
|
|
225
|
+
// Center the title within the display width (table is centered within display)
|
|
226
|
+
const titleX = Math.max(0, (displayWidth - titleBoxWidth) / 2);
|
|
227
|
+
titleText.setPosition(titleX, currentY);
|
|
228
|
+
titleText.setFontWeight(600);
|
|
229
|
+
this.addChild(titleText);
|
|
230
|
+
currentY += 30;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Create header row (lightGray, no border, no rounded corners for cells)
|
|
234
|
+
let currentX = 0;
|
|
235
|
+
for (let col = 0; col < numCols; col++) {
|
|
236
|
+
const cellWidth = cellWidths[col];
|
|
237
|
+
var headerRect = new jsvgRect();
|
|
238
|
+
headerRect.setWidthAndHeight(cellWidth, this.headerHeight);
|
|
239
|
+
headerRect.setFillColor(omdColor.lightGray);
|
|
240
|
+
// Use rx/ry for top corners only on first and last header cells
|
|
241
|
+
if (col === 0 && numCols === 1) {
|
|
242
|
+
headerRect.setCornerRadius(15); // single column, round all corners
|
|
243
|
+
} else if (col === 0) {
|
|
244
|
+
headerRect.setCornerRadius(15); // round top-left
|
|
245
|
+
} else if (col === numCols - 1) {
|
|
246
|
+
headerRect.setCornerRadius(15); // round top-right
|
|
247
|
+
} else {
|
|
248
|
+
headerRect.setCornerRadius(0);
|
|
249
|
+
}
|
|
250
|
+
headerRect.setStrokeWidth(0);
|
|
251
|
+
headerRect.setPosition(currentX + contentOffsetX, currentY);
|
|
252
|
+
this.addChild(headerRect);
|
|
253
|
+
const headerText = this.createHeaderTextBox(
|
|
254
|
+
cellWidth,
|
|
255
|
+
this.headerHeight,
|
|
256
|
+
this.headers[col] || `Col ${col + 1}`
|
|
257
|
+
);
|
|
258
|
+
headerText.setPosition(currentX + contentOffsetX, currentY);
|
|
259
|
+
this.addChild(headerText);
|
|
260
|
+
currentX += cellWidth;
|
|
261
|
+
}
|
|
262
|
+
currentY += this.headerHeight;
|
|
263
|
+
|
|
264
|
+
// Create data rows with alternating colors
|
|
265
|
+
for (let row = 0; row < numRows; row++) {
|
|
266
|
+
const rowData = this.data[row];
|
|
267
|
+
let currentX = 0;
|
|
268
|
+
// Alternating bar: odd rows white 50% opacity, even rows transparent
|
|
269
|
+
if (row % 2 === 0) {
|
|
270
|
+
var barRect = new jsvgRect();
|
|
271
|
+
barRect.setWidthAndHeight(this.width, this.cellHeight);
|
|
272
|
+
barRect.setFillColor("rgba(255,255,255,0.5)");
|
|
273
|
+
// Round the bottom corners on the last row to respect the table background rounding
|
|
274
|
+
if (row === numRows - 1) {
|
|
275
|
+
barRect.setCornerRadius(15);
|
|
276
|
+
} else {
|
|
277
|
+
barRect.setCornerRadius(0);
|
|
278
|
+
}
|
|
279
|
+
barRect.setStrokeWidth(0);
|
|
280
|
+
barRect.setPosition(contentOffsetX, currentY);
|
|
281
|
+
this.addChild(barRect);
|
|
282
|
+
}
|
|
283
|
+
for (let col = 0; col < numCols; col++) {
|
|
284
|
+
const cellWidth = cellWidths[col];
|
|
285
|
+
const cellText = this.createBodyTextBox(cellWidth, this.cellHeight, "");
|
|
286
|
+
const cellValue = rowData[col];
|
|
287
|
+
cellText.setText((cellValue ?? '').toString());
|
|
288
|
+
cellText.setPosition(currentX + contentOffsetX, currentY);
|
|
289
|
+
this.addChild(cellText);
|
|
290
|
+
currentX += cellWidth;
|
|
291
|
+
}
|
|
292
|
+
currentY += this.cellHeight;
|
|
293
|
+
}
|
|
294
|
+
// Draw vertical dividing lines at each column boundary (except the far right edge)
|
|
295
|
+
if (numCols > 1) {
|
|
296
|
+
let x = 0;
|
|
297
|
+
for (let col = 0; col < numCols - 1; col++) {
|
|
298
|
+
x += cellWidths[col];
|
|
299
|
+
const vline = new jsvgRect();
|
|
300
|
+
vline.setWidthAndHeight(1, Math.max(0, bodyHeight - 1));
|
|
301
|
+
vline.setFillColor("black");
|
|
302
|
+
vline.setCornerRadius(0);
|
|
303
|
+
vline.setOpacity(0.5);
|
|
304
|
+
vline.setStrokeWidth(0);
|
|
305
|
+
vline.setPosition(x + contentOffsetX, titleOffset);
|
|
306
|
+
this.addChild(vline);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
// Use displayWidth for the viewBox so the title never clips,
|
|
310
|
+
// but keep the table background at the original table width
|
|
311
|
+
this.setWidthAndHeight(displayWidth, totalHeight);
|
|
312
|
+
this.svgObject.setAttribute("viewBox", `0 0 ${displayWidth} ${totalHeight}`);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// ===== Helpers for consistent styling (match other components) =====
|
|
316
|
+
createHeaderTextBox(width, height, text) {
|
|
317
|
+
const tb = new jsvgTextBox();
|
|
318
|
+
tb.setWidthAndHeight(width, height);
|
|
319
|
+
tb.setText(text);
|
|
320
|
+
tb.setFontFamily(this.headerFontFamily);
|
|
321
|
+
tb.setFontColor("black");
|
|
322
|
+
tb.setFontSize(this.headerFontSize);
|
|
323
|
+
tb.setAlignment("center");
|
|
324
|
+
tb.setVerticalCentering();
|
|
325
|
+
tb.setFontWeight(600);
|
|
326
|
+
return tb;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
createBodyTextBox(width, height, text) {
|
|
330
|
+
const tb = new jsvgTextBox();
|
|
331
|
+
tb.setWidthAndHeight(width, height);
|
|
332
|
+
tb.setText(text);
|
|
333
|
+
tb.setFontFamily(this.fontFamily);
|
|
334
|
+
tb.setFontColor("black");
|
|
335
|
+
tb.setFontSize(this.fontSize);
|
|
336
|
+
tb.setAlignment("center");
|
|
337
|
+
tb.setVerticalCentering();
|
|
338
|
+
tb.setFontWeight(400);
|
|
339
|
+
return tb;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
addRow( rowData )
|
|
343
|
+
{
|
|
344
|
+
this.data.push( rowData );
|
|
345
|
+
this.updateLayout();
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
setHeaders( headers )
|
|
349
|
+
{
|
|
350
|
+
this.headers = headers;
|
|
351
|
+
this.updateLayout();
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
setFont( fontFamily, headerFontFamily )
|
|
355
|
+
{
|
|
356
|
+
this.fontFamily = fontFamily;
|
|
357
|
+
if ( headerFontFamily )
|
|
358
|
+
this.headerFontFamily = headerFontFamily;
|
|
359
|
+
else
|
|
360
|
+
this.headerFontFamily = fontFamily;
|
|
361
|
+
this.updateLayout();
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
clearData()
|
|
365
|
+
{
|
|
366
|
+
this.data = [];
|
|
367
|
+
this.updateLayout();
|
|
368
|
+
}
|
|
369
|
+
}
|