@teachinglab/omd 0.6.1 → 0.6.3
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 +257 -251
- package/README.old.md +137 -137
- package/canvas/core/canvasConfig.js +202 -202
- package/canvas/drawing/segment.js +167 -167
- package/canvas/drawing/stroke.js +385 -385
- package/canvas/events/eventManager.js +444 -444
- package/canvas/events/pointerEventHandler.js +262 -262
- package/canvas/index.js +48 -48
- package/canvas/tools/PointerTool.js +71 -71
- package/canvas/tools/tool.js +222 -222
- package/canvas/utils/boundingBox.js +377 -377
- package/canvas/utils/mathUtils.js +258 -258
- package/docs/api/configuration-options.md +198 -198
- package/docs/api/eventManager.md +82 -82
- package/docs/api/focusFrameManager.md +144 -144
- package/docs/api/index.md +105 -105
- package/docs/api/main.md +62 -62
- package/docs/api/omdBinaryExpressionNode.md +86 -86
- package/docs/api/omdCanvas.md +83 -83
- package/docs/api/omdConfigManager.md +112 -112
- package/docs/api/omdConstantNode.md +52 -52
- package/docs/api/omdDisplay.md +87 -87
- package/docs/api/omdEquationNode.md +174 -174
- package/docs/api/omdEquationSequenceNode.md +258 -258
- package/docs/api/omdEquationStack.md +192 -192
- package/docs/api/omdFunctionNode.md +82 -82
- package/docs/api/omdGroupNode.md +78 -78
- package/docs/api/omdHelpers.md +87 -87
- package/docs/api/omdLeafNode.md +85 -85
- package/docs/api/omdNode.md +201 -201
- package/docs/api/omdOperationDisplayNode.md +117 -117
- package/docs/api/omdOperatorNode.md +91 -91
- package/docs/api/omdParenthesisNode.md +133 -133
- package/docs/api/omdPopup.md +191 -191
- package/docs/api/omdPowerNode.md +131 -131
- package/docs/api/omdRationalNode.md +144 -144
- package/docs/api/omdSequenceNode.md +128 -128
- package/docs/api/omdSimplification.md +78 -78
- package/docs/api/omdSqrtNode.md +144 -144
- package/docs/api/omdStepVisualizer.md +146 -146
- package/docs/api/omdStepVisualizerHighlighting.md +65 -65
- package/docs/api/omdStepVisualizerInteractiveSteps.md +108 -108
- package/docs/api/omdStepVisualizerLayout.md +70 -70
- package/docs/api/omdStepVisualizerNodeUtils.md +140 -140
- package/docs/api/omdStepVisualizerTextBoxes.md +76 -76
- package/docs/api/omdToolbar.md +130 -130
- package/docs/api/omdTranscriptionService.md +95 -95
- package/docs/api/omdTreeDiff.md +169 -169
- package/docs/api/omdUnaryExpressionNode.md +137 -137
- package/docs/api/omdUtilities.md +82 -82
- package/docs/api/omdVariableNode.md +123 -123
- package/docs/api/selectTool.md +74 -74
- package/docs/api/simplificationEngine.md +97 -97
- package/docs/api/simplificationRules.md +76 -76
- package/docs/api/simplificationUtils.md +64 -64
- package/docs/api/transcribe.md +43 -43
- package/docs/api-reference.md +85 -85
- package/docs/index.html +453 -453
- package/docs/index.md +38 -38
- package/docs/omd-objects.md +258 -258
- package/index.js +79 -79
- package/jsvg/index.js +3 -0
- package/jsvg/jsvg.js +898 -898
- package/jsvg/jsvgComponents.js +357 -358
- package/npm-docs/DOCUMENTATION_SUMMARY.md +220 -220
- package/npm-docs/README.md +251 -251
- package/npm-docs/api/api-reference.md +85 -85
- package/npm-docs/api/configuration-options.md +198 -198
- package/npm-docs/api/eventManager.md +82 -82
- package/npm-docs/api/expression-nodes.md +561 -561
- package/npm-docs/api/focusFrameManager.md +144 -144
- package/npm-docs/api/index.md +105 -105
- package/npm-docs/api/main.md +62 -62
- package/npm-docs/api/omdBinaryExpressionNode.md +86 -86
- package/npm-docs/api/omdCanvas.md +83 -83
- package/npm-docs/api/omdConfigManager.md +112 -112
- package/npm-docs/api/omdConstantNode.md +52 -52
- package/npm-docs/api/omdDisplay.md +87 -87
- package/npm-docs/api/omdEquationNode.md +174 -174
- package/npm-docs/api/omdEquationSequenceNode.md +258 -258
- package/npm-docs/api/omdEquationStack.md +192 -192
- package/npm-docs/api/omdFunctionNode.md +82 -82
- package/npm-docs/api/omdGroupNode.md +78 -78
- package/npm-docs/api/omdHelpers.md +87 -87
- package/npm-docs/api/omdLeafNode.md +85 -85
- package/npm-docs/api/omdNode.md +201 -201
- package/npm-docs/api/omdOperationDisplayNode.md +117 -117
- package/npm-docs/api/omdOperatorNode.md +91 -91
- package/npm-docs/api/omdParenthesisNode.md +133 -133
- package/npm-docs/api/omdPopup.md +191 -191
- package/npm-docs/api/omdPowerNode.md +131 -131
- package/npm-docs/api/omdRationalNode.md +144 -144
- package/npm-docs/api/omdSequenceNode.md +128 -128
- package/npm-docs/api/omdSimplification.md +78 -78
- package/npm-docs/api/omdSqrtNode.md +144 -144
- package/npm-docs/api/omdStepVisualizer.md +146 -146
- package/npm-docs/api/omdStepVisualizerHighlighting.md +65 -65
- package/npm-docs/api/omdStepVisualizerInteractiveSteps.md +108 -108
- package/npm-docs/api/omdStepVisualizerLayout.md +70 -70
- package/npm-docs/api/omdStepVisualizerNodeUtils.md +140 -140
- package/npm-docs/api/omdStepVisualizerTextBoxes.md +76 -76
- package/npm-docs/api/omdToolbar.md +130 -130
- package/npm-docs/api/omdTranscriptionService.md +95 -95
- package/npm-docs/api/omdTreeDiff.md +169 -169
- package/npm-docs/api/omdUnaryExpressionNode.md +137 -137
- package/npm-docs/api/omdUtilities.md +82 -82
- package/npm-docs/api/omdVariableNode.md +123 -123
- package/npm-docs/api/selectTool.md +74 -74
- package/npm-docs/api/simplificationEngine.md +97 -97
- package/npm-docs/api/simplificationRules.md +76 -76
- package/npm-docs/api/simplificationUtils.md +64 -64
- package/npm-docs/api/transcribe.md +43 -43
- package/npm-docs/guides/equations.md +854 -854
- package/npm-docs/guides/factory-functions.md +354 -354
- package/npm-docs/guides/getting-started.md +318 -318
- package/npm-docs/guides/quick-examples.md +525 -525
- package/npm-docs/guides/visualizations.md +682 -682
- package/npm-docs/index.html +12 -0
- package/npm-docs/json-schemas.md +826 -826
- package/omd/config/omdConfigManager.js +279 -267
- package/omd/core/index.js +158 -158
- package/omd/core/omdEquationStack.js +606 -547
- package/omd/core/omdUtilities.js +113 -113
- package/omd/display/omdDisplay.js +1045 -963
- package/omd/display/omdToolbar.js +501 -501
- package/omd/nodes/omdBinaryExpressionNode.js +459 -459
- package/omd/nodes/omdConstantNode.js +141 -141
- package/omd/nodes/omdEquationNode.js +1327 -1327
- package/omd/nodes/omdFunctionNode.js +351 -351
- package/omd/nodes/omdGroupNode.js +67 -67
- package/omd/nodes/omdLeafNode.js +76 -76
- package/omd/nodes/omdNode.js +556 -556
- package/omd/nodes/omdOperationDisplayNode.js +321 -321
- package/omd/nodes/omdOperatorNode.js +108 -108
- package/omd/nodes/omdParenthesisNode.js +292 -292
- package/omd/nodes/omdPowerNode.js +235 -235
- package/omd/nodes/omdRationalNode.js +295 -295
- package/omd/nodes/omdSqrtNode.js +307 -307
- package/omd/nodes/omdUnaryExpressionNode.js +227 -227
- package/omd/nodes/omdVariableNode.js +122 -122
- package/omd/simplification/omdSimplification.js +140 -140
- package/omd/simplification/omdSimplificationEngine.js +887 -887
- package/omd/simplification/package.json +5 -5
- package/omd/simplification/rules/binaryRules.js +1037 -1037
- package/omd/simplification/rules/functionRules.js +111 -111
- package/omd/simplification/rules/index.js +48 -48
- package/omd/simplification/rules/parenthesisRules.js +19 -19
- package/omd/simplification/rules/powerRules.js +143 -143
- package/omd/simplification/rules/rationalRules.js +725 -725
- package/omd/simplification/rules/sqrtRules.js +48 -48
- package/omd/simplification/rules/unaryRules.js +37 -37
- package/omd/simplification/simplificationRules.js +31 -31
- package/omd/simplification/simplificationUtils.js +1055 -1055
- package/omd/step-visualizer/omdStepVisualizer.js +947 -947
- package/omd/step-visualizer/omdStepVisualizerHighlighting.js +246 -246
- package/omd/step-visualizer/omdStepVisualizerLayout.js +892 -892
- package/omd/step-visualizer/omdStepVisualizerTextBoxes.js +200 -200
- package/omd/utils/aiNextEquationStep.js +106 -106
- package/omd/utils/omdNodeOverlay.js +638 -638
- package/omd/utils/omdPopup.js +1203 -1203
- package/omd/utils/omdStepVisualizerInteractiveSteps.js +684 -684
- package/omd/utils/omdStepVisualizerNodeUtils.js +267 -267
- package/omd/utils/omdTranscriptionService.js +123 -123
- package/omd/utils/omdTreeDiff.js +733 -733
- package/package.json +59 -57
- package/readme.html +184 -120
- package/src/index.js +74 -74
- package/src/json-schemas.md +576 -576
- package/src/omd-json-samples.js +147 -147
- package/src/omdApp.js +391 -391
- package/src/omdAppCanvas.js +335 -335
- package/src/omdBalanceHanger.js +199 -199
- package/src/omdColor.js +13 -13
- package/src/omdCoordinatePlane.js +541 -541
- package/src/omdExpression.js +115 -115
- package/src/omdFactory.js +150 -150
- package/src/omdFunction.js +114 -114
- package/src/omdMetaExpression.js +290 -290
- package/src/omdNaturalExpression.js +563 -563
- package/src/omdNode.js +383 -383
- package/src/omdNumber.js +52 -52
- package/src/omdNumberLine.js +114 -112
- package/src/omdNumberTile.js +118 -118
- package/src/omdOperator.js +72 -72
- package/src/omdPowerExpression.js +91 -91
- package/src/omdProblem.js +259 -259
- package/src/omdRatioChart.js +251 -251
- package/src/omdRationalExpression.js +114 -114
- package/src/omdSampleData.js +215 -215
- package/src/omdShapes.js +512 -512
- package/src/omdSpinner.js +151 -151
- package/src/omdString.js +49 -49
- package/src/omdTable.js +498 -498
- package/src/omdTapeDiagram.js +244 -244
- package/src/omdTerm.js +91 -91
- package/src/omdTileEquation.js +349 -349
- package/src/omdUtils.js +84 -84
- package/src/omdVariable.js +51 -51
package/src/omdTable.js
CHANGED
|
@@ -1,499 +1,499 @@
|
|
|
1
|
-
import { omdColor } from "./omdColor.js";
|
|
2
|
-
import { jsvgGroup, jsvgRect, jsvgTextBox, jsvgClipMask } 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
|
-
// Background customization properties
|
|
31
|
-
this.backgroundColor = omdColor.lightGray;
|
|
32
|
-
this.backgroundCornerRadius = 15;
|
|
33
|
-
this.backgroundOpacity = 1.0;
|
|
34
|
-
this.showBackground = true;
|
|
35
|
-
|
|
36
|
-
// Alternating row color properties
|
|
37
|
-
this.alternatingRowColors = [omdColor.mediumGray, omdColor.lightGray]; // Should be an array of colors or null
|
|
38
|
-
this.headerBackgroundColor = omdColor.lightGray;
|
|
39
|
-
this.cellBackgroundColor = "white";
|
|
40
|
-
|
|
41
|
-
// Legacy properties for backward compatibility
|
|
42
|
-
this.evenRowColor = "rgba(255,255,255,0.5)";
|
|
43
|
-
this.oddRowColor = "transparent";
|
|
44
|
-
this.alternatingRowOpacity = 1.0;
|
|
45
|
-
|
|
46
|
-
this.updateLayout();
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Estimate title width in pixels based on font size and text length
|
|
50
|
-
estimateTitleWidth()
|
|
51
|
-
{
|
|
52
|
-
if (!this.title || this.title.length === 0) return 0;
|
|
53
|
-
const titleFontSize = this.headerFontSize + 2;
|
|
54
|
-
const padding = 40; // side padding inside the title text box
|
|
55
|
-
const minWidth = 200;
|
|
56
|
-
const estimated = Math.round(this.title.length * (titleFontSize * 0.6)) + padding;
|
|
57
|
-
return Math.max(minWidth, estimated);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
loadFromJSON( data )
|
|
61
|
-
{
|
|
62
|
-
if ( typeof data.equation != "undefined" )
|
|
63
|
-
this.equation = data.equation;
|
|
64
|
-
|
|
65
|
-
if ( typeof data.data != "undefined" )
|
|
66
|
-
this.data = data.data;
|
|
67
|
-
|
|
68
|
-
if ( typeof data.headers != "undefined" )
|
|
69
|
-
this.headers = data.headers;
|
|
70
|
-
|
|
71
|
-
if ( typeof data.xMin != "undefined" )
|
|
72
|
-
this.xMin = data.xMin;
|
|
73
|
-
|
|
74
|
-
if ( typeof data.xMax != "undefined" )
|
|
75
|
-
this.xMax = data.xMax;
|
|
76
|
-
|
|
77
|
-
if ( typeof data.stepSize != "undefined" )
|
|
78
|
-
this.stepSize = data.stepSize;
|
|
79
|
-
|
|
80
|
-
if ( typeof data.title != "undefined" )
|
|
81
|
-
this.title = data.title;
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if ( typeof data.fontSize != "undefined" )
|
|
85
|
-
this.fontSize = data.fontSize;
|
|
86
|
-
|
|
87
|
-
if ( typeof data.headerFontSize != "undefined" )
|
|
88
|
-
this.headerFontSize = data.headerFontSize;
|
|
89
|
-
|
|
90
|
-
if ( typeof data.fontFamily != "undefined" )
|
|
91
|
-
this.fontFamily = data.fontFamily;
|
|
92
|
-
|
|
93
|
-
if ( typeof data.headerFontFamily != "undefined" )
|
|
94
|
-
this.headerFontFamily = data.headerFontFamily;
|
|
95
|
-
|
|
96
|
-
if ( typeof data.cellHeight != "undefined" )
|
|
97
|
-
this.cellHeight = data.cellHeight;
|
|
98
|
-
|
|
99
|
-
if ( typeof data.headerHeight != "undefined" )
|
|
100
|
-
this.headerHeight = data.headerHeight;
|
|
101
|
-
|
|
102
|
-
if ( typeof data.minCellWidth != "undefined" )
|
|
103
|
-
this.minCellWidth = data.minCellWidth;
|
|
104
|
-
|
|
105
|
-
if ( typeof data.maxCellWidth != "undefined" )
|
|
106
|
-
this.maxCellWidth = data.maxCellWidth;
|
|
107
|
-
|
|
108
|
-
if ( typeof data.padding != "undefined" )
|
|
109
|
-
this.padding = data.padding;
|
|
110
|
-
|
|
111
|
-
// Load background customization properties
|
|
112
|
-
if ( typeof data.backgroundColor != "undefined" )
|
|
113
|
-
this.backgroundColor = data.backgroundColor;
|
|
114
|
-
if ( typeof data.backgroundCornerRadius != "undefined" )
|
|
115
|
-
this.backgroundCornerRadius = data.backgroundCornerRadius;
|
|
116
|
-
if ( typeof data.backgroundOpacity != "undefined" )
|
|
117
|
-
this.backgroundOpacity = data.backgroundOpacity;
|
|
118
|
-
if ( typeof data.showBackground != "undefined" )
|
|
119
|
-
this.showBackground = data.showBackground;
|
|
120
|
-
|
|
121
|
-
// Load alternating row color properties
|
|
122
|
-
if ( typeof data.alternatingRowColors != "undefined" ) {
|
|
123
|
-
this.alternatingRowColors = data.alternatingRowColors;
|
|
124
|
-
console.log('LoadFromJSON - Setting alternatingRowColors to:', this.alternatingRowColors);
|
|
125
|
-
}
|
|
126
|
-
if ( typeof data.evenRowColor != "undefined" )
|
|
127
|
-
this.evenRowColor = data.evenRowColor;
|
|
128
|
-
if ( typeof data.oddRowColor != "undefined" )
|
|
129
|
-
this.oddRowColor = data.oddRowColor;
|
|
130
|
-
if ( typeof data.alternatingRowOpacity != "undefined" )
|
|
131
|
-
this.alternatingRowOpacity = data.alternatingRowOpacity;
|
|
132
|
-
|
|
133
|
-
this.updateLayout();
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
setEquation( equation )
|
|
137
|
-
{
|
|
138
|
-
this.equation = equation;
|
|
139
|
-
this.updateLayout();
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
setData( data, headers )
|
|
143
|
-
{
|
|
144
|
-
this.data = data;
|
|
145
|
-
if ( headers )
|
|
146
|
-
this.headers = headers;
|
|
147
|
-
this.updateLayout();
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
calculateOptimalCellWidth(columnIndex)
|
|
151
|
-
{
|
|
152
|
-
let maxLength = (this.headers[columnIndex] ?? '').toString().length;
|
|
153
|
-
|
|
154
|
-
// Assume rows are arrays aligned with headers
|
|
155
|
-
for (let row of this.data) {
|
|
156
|
-
const cellValue = row[columnIndex];
|
|
157
|
-
if (cellValue !== null && cellValue !== undefined) {
|
|
158
|
-
maxLength = Math.max(maxLength, cellValue.toString().length);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Estimate width based on character count (approximate 8 pixels per character)
|
|
163
|
-
const estimatedWidth = Math.max(maxLength * 8 + this.padding * 2, this.minCellWidth);
|
|
164
|
-
return Math.min(estimatedWidth, this.maxCellWidth);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
generateDataFromEquation()
|
|
168
|
-
{
|
|
169
|
-
if (!this.equation || this.equation.trim().length === 0) return;
|
|
170
|
-
|
|
171
|
-
// Clear existing data and set headers
|
|
172
|
-
this.data = [];
|
|
173
|
-
this.headers = ['x', 'y'];
|
|
174
|
-
|
|
175
|
-
// Basic normalization for inline math
|
|
176
|
-
let expression = this.equation;
|
|
177
|
-
if (expression.toLowerCase().startsWith('y=')) {
|
|
178
|
-
expression = expression.substring(2).trim();
|
|
179
|
-
}
|
|
180
|
-
expression = expression
|
|
181
|
-
.replace(/(\d)([a-z])/gi, '$1*$2')
|
|
182
|
-
.replace(/([a-z])(\d)/gi, '$1*$2')
|
|
183
|
-
.replace(/\^/g, '**');
|
|
184
|
-
|
|
185
|
-
const evaluateExpression = new Function('x', `return ${expression};`);
|
|
186
|
-
|
|
187
|
-
for (let x = this.xMin; x <= this.xMax; x += this.stepSize) {
|
|
188
|
-
let y = evaluateExpression(x);
|
|
189
|
-
y = Math.round(y * 100) / 100;
|
|
190
|
-
this.data.push([x, y]);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
updateLayout()
|
|
195
|
-
{
|
|
196
|
-
console.log('updateLayout called - alternatingRowColors:', this.alternatingRowColors);
|
|
197
|
-
console.log('alternatingRowColors type:', typeof this.alternatingRowColors);
|
|
198
|
-
console.log('alternatingRowColors isArray:', Array.isArray(this.alternatingRowColors));
|
|
199
|
-
|
|
200
|
-
this.removeAllChildren();
|
|
201
|
-
|
|
202
|
-
// If an equation is provided, generate data before measuring/layout
|
|
203
|
-
if (this.equation && this.equation.length > 0) {
|
|
204
|
-
this.generateDataFromEquation();
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Calculate table dimensions
|
|
208
|
-
const numCols = this.headers.length;
|
|
209
|
-
const numRows = this.data.length;
|
|
210
|
-
let cellWidths = [];
|
|
211
|
-
let totalWidth = 0;
|
|
212
|
-
for (let col = 0; col < numCols; col++) {
|
|
213
|
-
const width = this.calculateOptimalCellWidth(col);
|
|
214
|
-
cellWidths.push(width);
|
|
215
|
-
totalWidth += width;
|
|
216
|
-
}
|
|
217
|
-
this.width = totalWidth;
|
|
218
|
-
const titleOffset = (this.title && this.title.length > 0) ? 30 : 0;
|
|
219
|
-
const bodyHeight = this.headerHeight + numRows * this.cellHeight;
|
|
220
|
-
const totalHeight = titleOffset + bodyHeight;
|
|
221
|
-
this.height = totalHeight;
|
|
222
|
-
|
|
223
|
-
// Compute a display width that ensures the title is not clipped,
|
|
224
|
-
// without changing column widths or table background.
|
|
225
|
-
const titleBoxWidth = this.estimateTitleWidth();
|
|
226
|
-
const displayWidth = Math.max(this.width, titleBoxWidth);
|
|
227
|
-
const contentOffsetX = Math.max(0, (displayWidth - this.width) / 2);
|
|
228
|
-
|
|
229
|
-
// Create a clipped group for rounded corners if corner radius is specified
|
|
230
|
-
let tableContentGroup;
|
|
231
|
-
if (this.backgroundCornerRadius > 0) {
|
|
232
|
-
// Create clip mask with rounded corners
|
|
233
|
-
const clipMask = new jsvgClipMask(this.width, bodyHeight, this.backgroundCornerRadius);
|
|
234
|
-
clipMask.setPosition(contentOffsetX, titleOffset);
|
|
235
|
-
this.addChild(clipMask);
|
|
236
|
-
|
|
237
|
-
// Table content will be added to the clip mask
|
|
238
|
-
tableContentGroup = clipMask;
|
|
239
|
-
|
|
240
|
-
// Create table background inside the clip mask (so it gets rounded corners)
|
|
241
|
-
if (this.showBackground) {
|
|
242
|
-
const tableBg = new jsvgRect();
|
|
243
|
-
tableBg.setWidthAndHeight(this.width, bodyHeight);
|
|
244
|
-
tableBg.setFillColor(this.backgroundColor);
|
|
245
|
-
if (this.backgroundOpacity < 1.0) {
|
|
246
|
-
tableBg.setOpacity(this.backgroundOpacity);
|
|
247
|
-
}
|
|
248
|
-
tableBg.setStrokeWidth(0);
|
|
249
|
-
tableBg.setPosition(0, 0); // Relative to clip mask
|
|
250
|
-
tableContentGroup.addChild(tableBg);
|
|
251
|
-
}
|
|
252
|
-
} else {
|
|
253
|
-
// No clipping needed, create background and use the main group
|
|
254
|
-
if (this.showBackground) {
|
|
255
|
-
const tableBg = new jsvgRect();
|
|
256
|
-
tableBg.setWidthAndHeight(this.width, bodyHeight);
|
|
257
|
-
tableBg.setFillColor(this.backgroundColor);
|
|
258
|
-
if (this.backgroundOpacity < 1.0) {
|
|
259
|
-
tableBg.setOpacity(this.backgroundOpacity);
|
|
260
|
-
}
|
|
261
|
-
tableBg.setStrokeWidth(0);
|
|
262
|
-
tableBg.setPosition(contentOffsetX, titleOffset);
|
|
263
|
-
this.addChild(tableBg);
|
|
264
|
-
}
|
|
265
|
-
tableContentGroup = this;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// Remove the separate footer rectangle since the main background now covers everything
|
|
269
|
-
|
|
270
|
-
// Generate data from equation if provided; otherwise assume valid data/headers
|
|
271
|
-
if (this.equation && this.equation.length > 0) {
|
|
272
|
-
this.generateDataFromEquation();
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
let currentY = 0;
|
|
276
|
-
// Add title if provided
|
|
277
|
-
if (this.title && this.title.length > 0) {
|
|
278
|
-
var titleText = new jsvgTextBox();
|
|
279
|
-
// Use an expanded text box width (only for title) to avoid clipping
|
|
280
|
-
titleText.setWidthAndHeight(titleBoxWidth, 25);
|
|
281
|
-
titleText.setText(this.title);
|
|
282
|
-
titleText.setFontFamily(this.headerFontFamily);
|
|
283
|
-
titleText.setFontColor("black");
|
|
284
|
-
titleText.setFontSize(this.headerFontSize + 2);
|
|
285
|
-
titleText.setAlignment("center");
|
|
286
|
-
titleText.setVerticalCentering();
|
|
287
|
-
// Center the title within the display width (table is centered within display)
|
|
288
|
-
const titleX = Math.max(0, (displayWidth - titleBoxWidth) / 2);
|
|
289
|
-
titleText.setPosition(titleX, currentY);
|
|
290
|
-
titleText.setFontWeight(600);
|
|
291
|
-
this.addChild(titleText);
|
|
292
|
-
currentY += 30;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
// Create header row
|
|
296
|
-
let currentX = 0;
|
|
297
|
-
const headerY = 0; // Relative to the clipped content group
|
|
298
|
-
|
|
299
|
-
// First create a full-width header background if using alternating colors
|
|
300
|
-
if (this.alternatingRowColors && Array.isArray(this.alternatingRowColors) && this.alternatingRowColors.length > 0) {
|
|
301
|
-
console.log('Creating header background with color:', this.alternatingRowColors[0]);
|
|
302
|
-
const headerBg = new jsvgRect();
|
|
303
|
-
headerBg.setWidthAndHeight(this.width, this.headerHeight);
|
|
304
|
-
headerBg.setFillColor(this.alternatingRowColors[0]); // Headers use first color
|
|
305
|
-
headerBg.setCornerRadius(0);
|
|
306
|
-
headerBg.setStrokeWidth(0);
|
|
307
|
-
headerBg.setPosition(0, headerY);
|
|
308
|
-
tableContentGroup.addChild(headerBg);
|
|
309
|
-
} else {
|
|
310
|
-
console.log('NOT creating header background - alternatingRowColors:', this.alternatingRowColors);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
for (let col = 0; col < numCols; col++) {
|
|
314
|
-
const cellWidth = cellWidths[col];
|
|
315
|
-
|
|
316
|
-
// Only create individual header background if NOT using alternating colors
|
|
317
|
-
if (!this.alternatingRowColors || !Array.isArray(this.alternatingRowColors) || this.alternatingRowColors.length === 0) {
|
|
318
|
-
var headerRect = new jsvgRect();
|
|
319
|
-
headerRect.setWidthAndHeight(cellWidth, this.headerHeight);
|
|
320
|
-
headerRect.setFillColor(this.headerBackgroundColor || omdColor.lightGray);
|
|
321
|
-
headerRect.setCornerRadius(0);
|
|
322
|
-
headerRect.setStrokeWidth(0);
|
|
323
|
-
headerRect.setPosition(currentX, headerY);
|
|
324
|
-
tableContentGroup.addChild(headerRect);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
const headerText = this.createHeaderTextBox(
|
|
328
|
-
cellWidth,
|
|
329
|
-
this.headerHeight,
|
|
330
|
-
this.headers[col] || `Col ${col + 1}`
|
|
331
|
-
);
|
|
332
|
-
headerText.setPosition(currentX, headerY);
|
|
333
|
-
tableContentGroup.addChild(headerText);
|
|
334
|
-
currentX += cellWidth;
|
|
335
|
-
}
|
|
336
|
-
currentY += this.headerHeight;
|
|
337
|
-
|
|
338
|
-
// Create data rows with alternating colors
|
|
339
|
-
for (let row = 0; row < numRows; row++) {
|
|
340
|
-
const rowData = this.data[row];
|
|
341
|
-
let currentX = 0;
|
|
342
|
-
const rowY = this.headerHeight + row * this.cellHeight; // Relative to clipped content group
|
|
343
|
-
|
|
344
|
-
// Create row background with alternating colors if enabled
|
|
345
|
-
if (this.alternatingRowColors && Array.isArray(this.alternatingRowColors) && this.alternatingRowColors.length > 0) {
|
|
346
|
-
const colorIndex = (row + 1) % this.alternatingRowColors.length; // +1 to account for header
|
|
347
|
-
const rowColor = this.alternatingRowColors[colorIndex];
|
|
348
|
-
|
|
349
|
-
console.log(`Creating row ${row} background with color:`, rowColor, 'at position:', 0, rowY);
|
|
350
|
-
|
|
351
|
-
var barRect = new jsvgRect();
|
|
352
|
-
barRect.setWidthAndHeight(this.width, this.cellHeight);
|
|
353
|
-
barRect.setFillColor(rowColor);
|
|
354
|
-
if (this.backgroundOpacity < 1.0) {
|
|
355
|
-
barRect.setOpacity(this.backgroundOpacity);
|
|
356
|
-
}
|
|
357
|
-
// No corner radius on individual row backgrounds - clip mask handles the rounding
|
|
358
|
-
barRect.setCornerRadius(0);
|
|
359
|
-
barRect.setStrokeWidth(0);
|
|
360
|
-
barRect.setPosition(0, rowY);
|
|
361
|
-
tableContentGroup.addChild(barRect);
|
|
362
|
-
} else {
|
|
363
|
-
console.log(`Row ${row}: No alternating colors - alternatingRowColors:`, this.alternatingRowColors);
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
for (let col = 0; col < numCols; col++) {
|
|
367
|
-
const cellWidth = cellWidths[col];
|
|
368
|
-
const cellText = this.createBodyTextBox(cellWidth, this.cellHeight, "");
|
|
369
|
-
const cellValue = rowData[col];
|
|
370
|
-
cellText.setText((cellValue ?? '').toString());
|
|
371
|
-
cellText.setPosition(currentX, rowY);
|
|
372
|
-
tableContentGroup.addChild(cellText);
|
|
373
|
-
currentX += cellWidth;
|
|
374
|
-
}
|
|
375
|
-
currentY += this.cellHeight;
|
|
376
|
-
}
|
|
377
|
-
// Draw vertical dividing lines at each column boundary (except the far right edge)
|
|
378
|
-
if (numCols > 1) {
|
|
379
|
-
let x = 0;
|
|
380
|
-
for (let col = 0; col < numCols - 1; col++) {
|
|
381
|
-
x += cellWidths[col];
|
|
382
|
-
const vline = new jsvgRect();
|
|
383
|
-
vline.setWidthAndHeight(1, Math.max(0, bodyHeight - 1));
|
|
384
|
-
vline.setFillColor("black");
|
|
385
|
-
vline.setCornerRadius(0);
|
|
386
|
-
vline.setOpacity(0.5);
|
|
387
|
-
vline.setStrokeWidth(0);
|
|
388
|
-
vline.setPosition(x, 0); // Relative to the clipped content group
|
|
389
|
-
tableContentGroup.addChild(vline);
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
// Use displayWidth for the viewBox so the title never clips,
|
|
393
|
-
// but keep the table background at the original table width
|
|
394
|
-
this.setWidthAndHeight(displayWidth, totalHeight);
|
|
395
|
-
this.svgObject.setAttribute("viewBox", `0 0 ${displayWidth} ${totalHeight}`);
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
// ===== Helpers for consistent styling (match other components) =====
|
|
399
|
-
createHeaderTextBox(width, height, text) {
|
|
400
|
-
const tb = new jsvgTextBox();
|
|
401
|
-
tb.setWidthAndHeight(width, height);
|
|
402
|
-
tb.setText(text);
|
|
403
|
-
tb.setFontFamily(this.headerFontFamily);
|
|
404
|
-
tb.setFontColor("black");
|
|
405
|
-
tb.setFontSize(this.headerFontSize);
|
|
406
|
-
tb.setAlignment("center");
|
|
407
|
-
tb.setVerticalCentering();
|
|
408
|
-
tb.setFontWeight(600);
|
|
409
|
-
return tb;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
createBodyTextBox(width, height, text) {
|
|
413
|
-
const tb = new jsvgTextBox();
|
|
414
|
-
tb.setWidthAndHeight(width, height);
|
|
415
|
-
tb.setText(text);
|
|
416
|
-
tb.setFontFamily(this.fontFamily);
|
|
417
|
-
tb.setFontColor("black");
|
|
418
|
-
tb.setFontSize(this.fontSize);
|
|
419
|
-
tb.setAlignment("center");
|
|
420
|
-
tb.setVerticalCentering();
|
|
421
|
-
tb.setFontWeight(400);
|
|
422
|
-
return tb;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
addRow( rowData )
|
|
426
|
-
{
|
|
427
|
-
this.data.push( rowData );
|
|
428
|
-
this.updateLayout();
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
setHeaders( headers )
|
|
432
|
-
{
|
|
433
|
-
this.headers = headers;
|
|
434
|
-
this.updateLayout();
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
setFont( fontFamily, headerFontFamily )
|
|
438
|
-
{
|
|
439
|
-
this.fontFamily = fontFamily;
|
|
440
|
-
if ( headerFontFamily )
|
|
441
|
-
this.headerFontFamily = headerFontFamily;
|
|
442
|
-
else
|
|
443
|
-
this.headerFontFamily = fontFamily;
|
|
444
|
-
this.updateLayout();
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
clearData()
|
|
448
|
-
{
|
|
449
|
-
this.data = [];
|
|
450
|
-
this.updateLayout();
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
// Background customization methods
|
|
454
|
-
setBackgroundColor(color) {
|
|
455
|
-
this.backgroundColor = color;
|
|
456
|
-
this.updateLayout();
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
setBackgroundCornerRadius(radius) {
|
|
460
|
-
this.backgroundCornerRadius = radius;
|
|
461
|
-
this.updateLayout();
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
setBackgroundOpacity(opacity) {
|
|
465
|
-
this.backgroundOpacity = Math.max(0, Math.min(1, opacity));
|
|
466
|
-
this.updateLayout();
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
setShowBackground(show) {
|
|
470
|
-
this.showBackground = show;
|
|
471
|
-
this.updateLayout();
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
setBackgroundStyle(options = {}) {
|
|
475
|
-
if (options.backgroundColor !== undefined) this.backgroundColor = options.backgroundColor;
|
|
476
|
-
if (options.cornerRadius !== undefined) this.backgroundCornerRadius = options.cornerRadius;
|
|
477
|
-
if (options.opacity !== undefined) this.backgroundOpacity = Math.max(0, Math.min(1, options.opacity));
|
|
478
|
-
if (options.show !== undefined) this.showBackground = options.show;
|
|
479
|
-
this.updateLayout();
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
// Alternating row colors methods
|
|
483
|
-
setAlternatingRowColors(colors) {
|
|
484
|
-
console.log('setAlternatingRowColors called with:', colors);
|
|
485
|
-
this.alternatingRowColors = colors;
|
|
486
|
-
console.log('alternatingRowColors set to:', this.alternatingRowColors);
|
|
487
|
-
this.updateLayout();
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
setHeaderBackgroundColor(color) {
|
|
491
|
-
this.headerBackgroundColor = color;
|
|
492
|
-
this.updateLayout();
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
setCellBackgroundColor(color) {
|
|
496
|
-
this.cellBackgroundColor = color;
|
|
497
|
-
this.updateLayout();
|
|
498
|
-
}
|
|
1
|
+
import { omdColor } from "./omdColor.js";
|
|
2
|
+
import { jsvgGroup, jsvgRect, jsvgTextBox, jsvgClipMask } 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
|
+
// Background customization properties
|
|
31
|
+
this.backgroundColor = omdColor.lightGray;
|
|
32
|
+
this.backgroundCornerRadius = 15;
|
|
33
|
+
this.backgroundOpacity = 1.0;
|
|
34
|
+
this.showBackground = true;
|
|
35
|
+
|
|
36
|
+
// Alternating row color properties
|
|
37
|
+
this.alternatingRowColors = [omdColor.mediumGray, omdColor.lightGray]; // Should be an array of colors or null
|
|
38
|
+
this.headerBackgroundColor = omdColor.lightGray;
|
|
39
|
+
this.cellBackgroundColor = "white";
|
|
40
|
+
|
|
41
|
+
// Legacy properties for backward compatibility
|
|
42
|
+
this.evenRowColor = "rgba(255,255,255,0.5)";
|
|
43
|
+
this.oddRowColor = "transparent";
|
|
44
|
+
this.alternatingRowOpacity = 1.0;
|
|
45
|
+
|
|
46
|
+
this.updateLayout();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Estimate title width in pixels based on font size and text length
|
|
50
|
+
estimateTitleWidth()
|
|
51
|
+
{
|
|
52
|
+
if (!this.title || this.title.length === 0) return 0;
|
|
53
|
+
const titleFontSize = this.headerFontSize + 2;
|
|
54
|
+
const padding = 40; // side padding inside the title text box
|
|
55
|
+
const minWidth = 200;
|
|
56
|
+
const estimated = Math.round(this.title.length * (titleFontSize * 0.6)) + padding;
|
|
57
|
+
return Math.max(minWidth, estimated);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
loadFromJSON( data )
|
|
61
|
+
{
|
|
62
|
+
if ( typeof data.equation != "undefined" )
|
|
63
|
+
this.equation = data.equation;
|
|
64
|
+
|
|
65
|
+
if ( typeof data.data != "undefined" )
|
|
66
|
+
this.data = data.data;
|
|
67
|
+
|
|
68
|
+
if ( typeof data.headers != "undefined" )
|
|
69
|
+
this.headers = data.headers;
|
|
70
|
+
|
|
71
|
+
if ( typeof data.xMin != "undefined" )
|
|
72
|
+
this.xMin = data.xMin;
|
|
73
|
+
|
|
74
|
+
if ( typeof data.xMax != "undefined" )
|
|
75
|
+
this.xMax = data.xMax;
|
|
76
|
+
|
|
77
|
+
if ( typeof data.stepSize != "undefined" )
|
|
78
|
+
this.stepSize = data.stepSize;
|
|
79
|
+
|
|
80
|
+
if ( typeof data.title != "undefined" )
|
|
81
|
+
this.title = data.title;
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
if ( typeof data.fontSize != "undefined" )
|
|
85
|
+
this.fontSize = data.fontSize;
|
|
86
|
+
|
|
87
|
+
if ( typeof data.headerFontSize != "undefined" )
|
|
88
|
+
this.headerFontSize = data.headerFontSize;
|
|
89
|
+
|
|
90
|
+
if ( typeof data.fontFamily != "undefined" )
|
|
91
|
+
this.fontFamily = data.fontFamily;
|
|
92
|
+
|
|
93
|
+
if ( typeof data.headerFontFamily != "undefined" )
|
|
94
|
+
this.headerFontFamily = data.headerFontFamily;
|
|
95
|
+
|
|
96
|
+
if ( typeof data.cellHeight != "undefined" )
|
|
97
|
+
this.cellHeight = data.cellHeight;
|
|
98
|
+
|
|
99
|
+
if ( typeof data.headerHeight != "undefined" )
|
|
100
|
+
this.headerHeight = data.headerHeight;
|
|
101
|
+
|
|
102
|
+
if ( typeof data.minCellWidth != "undefined" )
|
|
103
|
+
this.minCellWidth = data.minCellWidth;
|
|
104
|
+
|
|
105
|
+
if ( typeof data.maxCellWidth != "undefined" )
|
|
106
|
+
this.maxCellWidth = data.maxCellWidth;
|
|
107
|
+
|
|
108
|
+
if ( typeof data.padding != "undefined" )
|
|
109
|
+
this.padding = data.padding;
|
|
110
|
+
|
|
111
|
+
// Load background customization properties
|
|
112
|
+
if ( typeof data.backgroundColor != "undefined" )
|
|
113
|
+
this.backgroundColor = data.backgroundColor;
|
|
114
|
+
if ( typeof data.backgroundCornerRadius != "undefined" )
|
|
115
|
+
this.backgroundCornerRadius = data.backgroundCornerRadius;
|
|
116
|
+
if ( typeof data.backgroundOpacity != "undefined" )
|
|
117
|
+
this.backgroundOpacity = data.backgroundOpacity;
|
|
118
|
+
if ( typeof data.showBackground != "undefined" )
|
|
119
|
+
this.showBackground = data.showBackground;
|
|
120
|
+
|
|
121
|
+
// Load alternating row color properties
|
|
122
|
+
if ( typeof data.alternatingRowColors != "undefined" ) {
|
|
123
|
+
this.alternatingRowColors = data.alternatingRowColors;
|
|
124
|
+
console.log('LoadFromJSON - Setting alternatingRowColors to:', this.alternatingRowColors);
|
|
125
|
+
}
|
|
126
|
+
if ( typeof data.evenRowColor != "undefined" )
|
|
127
|
+
this.evenRowColor = data.evenRowColor;
|
|
128
|
+
if ( typeof data.oddRowColor != "undefined" )
|
|
129
|
+
this.oddRowColor = data.oddRowColor;
|
|
130
|
+
if ( typeof data.alternatingRowOpacity != "undefined" )
|
|
131
|
+
this.alternatingRowOpacity = data.alternatingRowOpacity;
|
|
132
|
+
|
|
133
|
+
this.updateLayout();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
setEquation( equation )
|
|
137
|
+
{
|
|
138
|
+
this.equation = equation;
|
|
139
|
+
this.updateLayout();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
setData( data, headers )
|
|
143
|
+
{
|
|
144
|
+
this.data = data;
|
|
145
|
+
if ( headers )
|
|
146
|
+
this.headers = headers;
|
|
147
|
+
this.updateLayout();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
calculateOptimalCellWidth(columnIndex)
|
|
151
|
+
{
|
|
152
|
+
let maxLength = (this.headers[columnIndex] ?? '').toString().length;
|
|
153
|
+
|
|
154
|
+
// Assume rows are arrays aligned with headers
|
|
155
|
+
for (let row of this.data) {
|
|
156
|
+
const cellValue = row[columnIndex];
|
|
157
|
+
if (cellValue !== null && cellValue !== undefined) {
|
|
158
|
+
maxLength = Math.max(maxLength, cellValue.toString().length);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Estimate width based on character count (approximate 8 pixels per character)
|
|
163
|
+
const estimatedWidth = Math.max(maxLength * 8 + this.padding * 2, this.minCellWidth);
|
|
164
|
+
return Math.min(estimatedWidth, this.maxCellWidth);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
generateDataFromEquation()
|
|
168
|
+
{
|
|
169
|
+
if (!this.equation || this.equation.trim().length === 0) return;
|
|
170
|
+
|
|
171
|
+
// Clear existing data and set headers
|
|
172
|
+
this.data = [];
|
|
173
|
+
this.headers = ['x', 'y'];
|
|
174
|
+
|
|
175
|
+
// Basic normalization for inline math
|
|
176
|
+
let expression = this.equation;
|
|
177
|
+
if (expression.toLowerCase().startsWith('y=')) {
|
|
178
|
+
expression = expression.substring(2).trim();
|
|
179
|
+
}
|
|
180
|
+
expression = expression
|
|
181
|
+
.replace(/(\d)([a-z])/gi, '$1*$2')
|
|
182
|
+
.replace(/([a-z])(\d)/gi, '$1*$2')
|
|
183
|
+
.replace(/\^/g, '**');
|
|
184
|
+
|
|
185
|
+
const evaluateExpression = new Function('x', `return ${expression};`);
|
|
186
|
+
|
|
187
|
+
for (let x = this.xMin; x <= this.xMax; x += this.stepSize) {
|
|
188
|
+
let y = evaluateExpression(x);
|
|
189
|
+
y = Math.round(y * 100) / 100;
|
|
190
|
+
this.data.push([x, y]);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
updateLayout()
|
|
195
|
+
{
|
|
196
|
+
console.log('updateLayout called - alternatingRowColors:', this.alternatingRowColors);
|
|
197
|
+
console.log('alternatingRowColors type:', typeof this.alternatingRowColors);
|
|
198
|
+
console.log('alternatingRowColors isArray:', Array.isArray(this.alternatingRowColors));
|
|
199
|
+
|
|
200
|
+
this.removeAllChildren();
|
|
201
|
+
|
|
202
|
+
// If an equation is provided, generate data before measuring/layout
|
|
203
|
+
if (this.equation && this.equation.length > 0) {
|
|
204
|
+
this.generateDataFromEquation();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Calculate table dimensions
|
|
208
|
+
const numCols = this.headers.length;
|
|
209
|
+
const numRows = this.data.length;
|
|
210
|
+
let cellWidths = [];
|
|
211
|
+
let totalWidth = 0;
|
|
212
|
+
for (let col = 0; col < numCols; col++) {
|
|
213
|
+
const width = this.calculateOptimalCellWidth(col);
|
|
214
|
+
cellWidths.push(width);
|
|
215
|
+
totalWidth += width;
|
|
216
|
+
}
|
|
217
|
+
this.width = totalWidth;
|
|
218
|
+
const titleOffset = (this.title && this.title.length > 0) ? 30 : 0;
|
|
219
|
+
const bodyHeight = this.headerHeight + numRows * this.cellHeight;
|
|
220
|
+
const totalHeight = titleOffset + bodyHeight;
|
|
221
|
+
this.height = totalHeight;
|
|
222
|
+
|
|
223
|
+
// Compute a display width that ensures the title is not clipped,
|
|
224
|
+
// without changing column widths or table background.
|
|
225
|
+
const titleBoxWidth = this.estimateTitleWidth();
|
|
226
|
+
const displayWidth = Math.max(this.width, titleBoxWidth);
|
|
227
|
+
const contentOffsetX = Math.max(0, (displayWidth - this.width) / 2);
|
|
228
|
+
|
|
229
|
+
// Create a clipped group for rounded corners if corner radius is specified
|
|
230
|
+
let tableContentGroup;
|
|
231
|
+
if (this.backgroundCornerRadius > 0) {
|
|
232
|
+
// Create clip mask with rounded corners
|
|
233
|
+
const clipMask = new jsvgClipMask(this.width, bodyHeight, this.backgroundCornerRadius);
|
|
234
|
+
clipMask.setPosition(contentOffsetX, titleOffset);
|
|
235
|
+
this.addChild(clipMask);
|
|
236
|
+
|
|
237
|
+
// Table content will be added to the clip mask
|
|
238
|
+
tableContentGroup = clipMask;
|
|
239
|
+
|
|
240
|
+
// Create table background inside the clip mask (so it gets rounded corners)
|
|
241
|
+
if (this.showBackground) {
|
|
242
|
+
const tableBg = new jsvgRect();
|
|
243
|
+
tableBg.setWidthAndHeight(this.width, bodyHeight);
|
|
244
|
+
tableBg.setFillColor(this.backgroundColor);
|
|
245
|
+
if (this.backgroundOpacity < 1.0) {
|
|
246
|
+
tableBg.setOpacity(this.backgroundOpacity);
|
|
247
|
+
}
|
|
248
|
+
tableBg.setStrokeWidth(0);
|
|
249
|
+
tableBg.setPosition(0, 0); // Relative to clip mask
|
|
250
|
+
tableContentGroup.addChild(tableBg);
|
|
251
|
+
}
|
|
252
|
+
} else {
|
|
253
|
+
// No clipping needed, create background and use the main group
|
|
254
|
+
if (this.showBackground) {
|
|
255
|
+
const tableBg = new jsvgRect();
|
|
256
|
+
tableBg.setWidthAndHeight(this.width, bodyHeight);
|
|
257
|
+
tableBg.setFillColor(this.backgroundColor);
|
|
258
|
+
if (this.backgroundOpacity < 1.0) {
|
|
259
|
+
tableBg.setOpacity(this.backgroundOpacity);
|
|
260
|
+
}
|
|
261
|
+
tableBg.setStrokeWidth(0);
|
|
262
|
+
tableBg.setPosition(contentOffsetX, titleOffset);
|
|
263
|
+
this.addChild(tableBg);
|
|
264
|
+
}
|
|
265
|
+
tableContentGroup = this;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Remove the separate footer rectangle since the main background now covers everything
|
|
269
|
+
|
|
270
|
+
// Generate data from equation if provided; otherwise assume valid data/headers
|
|
271
|
+
if (this.equation && this.equation.length > 0) {
|
|
272
|
+
this.generateDataFromEquation();
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
let currentY = 0;
|
|
276
|
+
// Add title if provided
|
|
277
|
+
if (this.title && this.title.length > 0) {
|
|
278
|
+
var titleText = new jsvgTextBox();
|
|
279
|
+
// Use an expanded text box width (only for title) to avoid clipping
|
|
280
|
+
titleText.setWidthAndHeight(titleBoxWidth, 25);
|
|
281
|
+
titleText.setText(this.title);
|
|
282
|
+
titleText.setFontFamily(this.headerFontFamily);
|
|
283
|
+
titleText.setFontColor("black");
|
|
284
|
+
titleText.setFontSize(this.headerFontSize + 2);
|
|
285
|
+
titleText.setAlignment("center");
|
|
286
|
+
titleText.setVerticalCentering();
|
|
287
|
+
// Center the title within the display width (table is centered within display)
|
|
288
|
+
const titleX = Math.max(0, (displayWidth - titleBoxWidth) / 2);
|
|
289
|
+
titleText.setPosition(titleX, currentY);
|
|
290
|
+
titleText.setFontWeight(600);
|
|
291
|
+
this.addChild(titleText);
|
|
292
|
+
currentY += 30;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Create header row
|
|
296
|
+
let currentX = 0;
|
|
297
|
+
const headerY = 0; // Relative to the clipped content group
|
|
298
|
+
|
|
299
|
+
// First create a full-width header background if using alternating colors
|
|
300
|
+
if (this.alternatingRowColors && Array.isArray(this.alternatingRowColors) && this.alternatingRowColors.length > 0) {
|
|
301
|
+
console.log('Creating header background with color:', this.alternatingRowColors[0]);
|
|
302
|
+
const headerBg = new jsvgRect();
|
|
303
|
+
headerBg.setWidthAndHeight(this.width, this.headerHeight);
|
|
304
|
+
headerBg.setFillColor(this.alternatingRowColors[0]); // Headers use first color
|
|
305
|
+
headerBg.setCornerRadius(0);
|
|
306
|
+
headerBg.setStrokeWidth(0);
|
|
307
|
+
headerBg.setPosition(0, headerY);
|
|
308
|
+
tableContentGroup.addChild(headerBg);
|
|
309
|
+
} else {
|
|
310
|
+
console.log('NOT creating header background - alternatingRowColors:', this.alternatingRowColors);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
for (let col = 0; col < numCols; col++) {
|
|
314
|
+
const cellWidth = cellWidths[col];
|
|
315
|
+
|
|
316
|
+
// Only create individual header background if NOT using alternating colors
|
|
317
|
+
if (!this.alternatingRowColors || !Array.isArray(this.alternatingRowColors) || this.alternatingRowColors.length === 0) {
|
|
318
|
+
var headerRect = new jsvgRect();
|
|
319
|
+
headerRect.setWidthAndHeight(cellWidth, this.headerHeight);
|
|
320
|
+
headerRect.setFillColor(this.headerBackgroundColor || omdColor.lightGray);
|
|
321
|
+
headerRect.setCornerRadius(0);
|
|
322
|
+
headerRect.setStrokeWidth(0);
|
|
323
|
+
headerRect.setPosition(currentX, headerY);
|
|
324
|
+
tableContentGroup.addChild(headerRect);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const headerText = this.createHeaderTextBox(
|
|
328
|
+
cellWidth,
|
|
329
|
+
this.headerHeight,
|
|
330
|
+
this.headers[col] || `Col ${col + 1}`
|
|
331
|
+
);
|
|
332
|
+
headerText.setPosition(currentX, headerY);
|
|
333
|
+
tableContentGroup.addChild(headerText);
|
|
334
|
+
currentX += cellWidth;
|
|
335
|
+
}
|
|
336
|
+
currentY += this.headerHeight;
|
|
337
|
+
|
|
338
|
+
// Create data rows with alternating colors
|
|
339
|
+
for (let row = 0; row < numRows; row++) {
|
|
340
|
+
const rowData = this.data[row];
|
|
341
|
+
let currentX = 0;
|
|
342
|
+
const rowY = this.headerHeight + row * this.cellHeight; // Relative to clipped content group
|
|
343
|
+
|
|
344
|
+
// Create row background with alternating colors if enabled
|
|
345
|
+
if (this.alternatingRowColors && Array.isArray(this.alternatingRowColors) && this.alternatingRowColors.length > 0) {
|
|
346
|
+
const colorIndex = (row + 1) % this.alternatingRowColors.length; // +1 to account for header
|
|
347
|
+
const rowColor = this.alternatingRowColors[colorIndex];
|
|
348
|
+
|
|
349
|
+
console.log(`Creating row ${row} background with color:`, rowColor, 'at position:', 0, rowY);
|
|
350
|
+
|
|
351
|
+
var barRect = new jsvgRect();
|
|
352
|
+
barRect.setWidthAndHeight(this.width, this.cellHeight);
|
|
353
|
+
barRect.setFillColor(rowColor);
|
|
354
|
+
if (this.backgroundOpacity < 1.0) {
|
|
355
|
+
barRect.setOpacity(this.backgroundOpacity);
|
|
356
|
+
}
|
|
357
|
+
// No corner radius on individual row backgrounds - clip mask handles the rounding
|
|
358
|
+
barRect.setCornerRadius(0);
|
|
359
|
+
barRect.setStrokeWidth(0);
|
|
360
|
+
barRect.setPosition(0, rowY);
|
|
361
|
+
tableContentGroup.addChild(barRect);
|
|
362
|
+
} else {
|
|
363
|
+
console.log(`Row ${row}: No alternating colors - alternatingRowColors:`, this.alternatingRowColors);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
for (let col = 0; col < numCols; col++) {
|
|
367
|
+
const cellWidth = cellWidths[col];
|
|
368
|
+
const cellText = this.createBodyTextBox(cellWidth, this.cellHeight, "");
|
|
369
|
+
const cellValue = rowData[col];
|
|
370
|
+
cellText.setText((cellValue ?? '').toString());
|
|
371
|
+
cellText.setPosition(currentX, rowY);
|
|
372
|
+
tableContentGroup.addChild(cellText);
|
|
373
|
+
currentX += cellWidth;
|
|
374
|
+
}
|
|
375
|
+
currentY += this.cellHeight;
|
|
376
|
+
}
|
|
377
|
+
// Draw vertical dividing lines at each column boundary (except the far right edge)
|
|
378
|
+
if (numCols > 1) {
|
|
379
|
+
let x = 0;
|
|
380
|
+
for (let col = 0; col < numCols - 1; col++) {
|
|
381
|
+
x += cellWidths[col];
|
|
382
|
+
const vline = new jsvgRect();
|
|
383
|
+
vline.setWidthAndHeight(1, Math.max(0, bodyHeight - 1));
|
|
384
|
+
vline.setFillColor("black");
|
|
385
|
+
vline.setCornerRadius(0);
|
|
386
|
+
vline.setOpacity(0.5);
|
|
387
|
+
vline.setStrokeWidth(0);
|
|
388
|
+
vline.setPosition(x, 0); // Relative to the clipped content group
|
|
389
|
+
tableContentGroup.addChild(vline);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
// Use displayWidth for the viewBox so the title never clips,
|
|
393
|
+
// but keep the table background at the original table width
|
|
394
|
+
this.setWidthAndHeight(displayWidth, totalHeight);
|
|
395
|
+
this.svgObject.setAttribute("viewBox", `0 0 ${displayWidth} ${totalHeight}`);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// ===== Helpers for consistent styling (match other components) =====
|
|
399
|
+
createHeaderTextBox(width, height, text) {
|
|
400
|
+
const tb = new jsvgTextBox();
|
|
401
|
+
tb.setWidthAndHeight(width, height);
|
|
402
|
+
tb.setText(text);
|
|
403
|
+
tb.setFontFamily(this.headerFontFamily);
|
|
404
|
+
tb.setFontColor("black");
|
|
405
|
+
tb.setFontSize(this.headerFontSize);
|
|
406
|
+
tb.setAlignment("center");
|
|
407
|
+
tb.setVerticalCentering();
|
|
408
|
+
tb.setFontWeight(600);
|
|
409
|
+
return tb;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
createBodyTextBox(width, height, text) {
|
|
413
|
+
const tb = new jsvgTextBox();
|
|
414
|
+
tb.setWidthAndHeight(width, height);
|
|
415
|
+
tb.setText(text);
|
|
416
|
+
tb.setFontFamily(this.fontFamily);
|
|
417
|
+
tb.setFontColor("black");
|
|
418
|
+
tb.setFontSize(this.fontSize);
|
|
419
|
+
tb.setAlignment("center");
|
|
420
|
+
tb.setVerticalCentering();
|
|
421
|
+
tb.setFontWeight(400);
|
|
422
|
+
return tb;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
addRow( rowData )
|
|
426
|
+
{
|
|
427
|
+
this.data.push( rowData );
|
|
428
|
+
this.updateLayout();
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
setHeaders( headers )
|
|
432
|
+
{
|
|
433
|
+
this.headers = headers;
|
|
434
|
+
this.updateLayout();
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
setFont( fontFamily, headerFontFamily )
|
|
438
|
+
{
|
|
439
|
+
this.fontFamily = fontFamily;
|
|
440
|
+
if ( headerFontFamily )
|
|
441
|
+
this.headerFontFamily = headerFontFamily;
|
|
442
|
+
else
|
|
443
|
+
this.headerFontFamily = fontFamily;
|
|
444
|
+
this.updateLayout();
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
clearData()
|
|
448
|
+
{
|
|
449
|
+
this.data = [];
|
|
450
|
+
this.updateLayout();
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Background customization methods
|
|
454
|
+
setBackgroundColor(color) {
|
|
455
|
+
this.backgroundColor = color;
|
|
456
|
+
this.updateLayout();
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
setBackgroundCornerRadius(radius) {
|
|
460
|
+
this.backgroundCornerRadius = radius;
|
|
461
|
+
this.updateLayout();
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
setBackgroundOpacity(opacity) {
|
|
465
|
+
this.backgroundOpacity = Math.max(0, Math.min(1, opacity));
|
|
466
|
+
this.updateLayout();
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
setShowBackground(show) {
|
|
470
|
+
this.showBackground = show;
|
|
471
|
+
this.updateLayout();
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
setBackgroundStyle(options = {}) {
|
|
475
|
+
if (options.backgroundColor !== undefined) this.backgroundColor = options.backgroundColor;
|
|
476
|
+
if (options.cornerRadius !== undefined) this.backgroundCornerRadius = options.cornerRadius;
|
|
477
|
+
if (options.opacity !== undefined) this.backgroundOpacity = Math.max(0, Math.min(1, options.opacity));
|
|
478
|
+
if (options.show !== undefined) this.showBackground = options.show;
|
|
479
|
+
this.updateLayout();
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Alternating row colors methods
|
|
483
|
+
setAlternatingRowColors(colors) {
|
|
484
|
+
console.log('setAlternatingRowColors called with:', colors);
|
|
485
|
+
this.alternatingRowColors = colors;
|
|
486
|
+
console.log('alternatingRowColors set to:', this.alternatingRowColors);
|
|
487
|
+
this.updateLayout();
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
setHeaderBackgroundColor(color) {
|
|
491
|
+
this.headerBackgroundColor = color;
|
|
492
|
+
this.updateLayout();
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
setCellBackgroundColor(color) {
|
|
496
|
+
this.cellBackgroundColor = color;
|
|
497
|
+
this.updateLayout();
|
|
498
|
+
}
|
|
499
499
|
}
|