@teachinglab/omd 0.2.6 → 0.2.8
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/canvas/ui/toolbar.js +0 -15
- package/omd/config/omdConfigManager.js +2 -2
- package/omd/core/omdEquationStack.js +11 -2
- package/omd/display/omdDisplay.js +494 -80
- package/omd/nodes/omdEquationNode.js +46 -6
- package/omd/step-visualizer/omdStepVisualizer.js +387 -42
- package/omd/step-visualizer/omdStepVisualizerLayout.js +654 -11
- package/omd/step-visualizer/omdStepVisualizerTextBoxes.js +46 -8
- package/omd/utils/omdStepVisualizerInteractiveSteps.js +318 -121
- package/package.json +1 -1
- package/src/omdBalanceHanger.js +31 -1
- package/src/omdColor.js +1 -0
- package/src/omdCoordinatePlane.js +53 -3
- package/src/omdMetaExpression.js +8 -4
- package/src/omdTable.js +182 -52
|
@@ -1005,7 +1005,7 @@ _propagateBackgroundStyle(style, visited = new Set()) {
|
|
|
1005
1005
|
case 'table':
|
|
1006
1006
|
return this._renderToTable(mergedOptions);
|
|
1007
1007
|
case 'hanger':
|
|
1008
|
-
return this._renderToHanger();
|
|
1008
|
+
return this._renderToHanger(mergedOptions);
|
|
1009
1009
|
case 'tileequation': {
|
|
1010
1010
|
const leftExpr = this.getLeft().toString();
|
|
1011
1011
|
const rightExpr = this.getRight().toString();
|
|
@@ -1089,6 +1089,11 @@ _propagateBackgroundStyle(style, visited = new Set()) {
|
|
|
1089
1089
|
tickInterval: (options.tickInterval !== undefined) ? options.tickInterval : 1,
|
|
1090
1090
|
forceAllTickLabels: (options.forceAllTickLabels !== undefined) ? options.forceAllTickLabels : true,
|
|
1091
1091
|
showTickLabels: (options.showTickLabels !== undefined) ? options.showTickLabels : true,
|
|
1092
|
+
// Background customization options
|
|
1093
|
+
backgroundColor: (options.backgroundColor !== undefined) ? options.backgroundColor : undefined,
|
|
1094
|
+
backgroundCornerRadius: (options.backgroundCornerRadius !== undefined) ? options.backgroundCornerRadius : undefined,
|
|
1095
|
+
backgroundOpacity: (options.backgroundOpacity !== undefined) ? options.backgroundOpacity : undefined,
|
|
1096
|
+
showBackground: (options.showBackground !== undefined) ? options.showBackground : undefined,
|
|
1092
1097
|
graphEquations,
|
|
1093
1098
|
lineSegments: [],
|
|
1094
1099
|
dotValues: [],
|
|
@@ -1113,7 +1118,17 @@ _propagateBackgroundStyle(style, visited = new Set()) {
|
|
|
1113
1118
|
equation: `y = ${expr}`,
|
|
1114
1119
|
xMin: options.xMin,
|
|
1115
1120
|
xMax: options.xMax,
|
|
1116
|
-
stepSize: options.stepSize
|
|
1121
|
+
stepSize: options.stepSize,
|
|
1122
|
+
// Background customization options
|
|
1123
|
+
backgroundColor: (options.backgroundColor !== undefined) ? options.backgroundColor : undefined,
|
|
1124
|
+
backgroundCornerRadius: (options.backgroundCornerRadius !== undefined) ? options.backgroundCornerRadius : undefined,
|
|
1125
|
+
backgroundOpacity: (options.backgroundOpacity !== undefined) ? options.backgroundOpacity : undefined,
|
|
1126
|
+
showBackground: (options.showBackground !== undefined) ? options.showBackground : undefined,
|
|
1127
|
+
// Alternating row color options
|
|
1128
|
+
alternatingRowColors: (options.alternatingRowColors !== undefined) ? options.alternatingRowColors : undefined,
|
|
1129
|
+
evenRowColor: (options.evenRowColor !== undefined) ? options.evenRowColor : undefined,
|
|
1130
|
+
oddRowColor: (options.oddRowColor !== undefined) ? options.oddRowColor : undefined,
|
|
1131
|
+
alternatingRowOpacity: (options.alternatingRowOpacity !== undefined) ? options.alternatingRowOpacity : undefined
|
|
1117
1132
|
};
|
|
1118
1133
|
} else if (options.side === 'right') {
|
|
1119
1134
|
const expr = this._normalizeExpressionString(this.getRight().toString());
|
|
@@ -1124,7 +1139,17 @@ _propagateBackgroundStyle(style, visited = new Set()) {
|
|
|
1124
1139
|
equation: `y = ${expr}`,
|
|
1125
1140
|
xMin: options.xMin,
|
|
1126
1141
|
xMax: options.xMax,
|
|
1127
|
-
stepSize: options.stepSize
|
|
1142
|
+
stepSize: options.stepSize,
|
|
1143
|
+
// Background customization options
|
|
1144
|
+
backgroundColor: (options.backgroundColor !== undefined) ? options.backgroundColor : undefined,
|
|
1145
|
+
backgroundCornerRadius: (options.backgroundCornerRadius !== undefined) ? options.backgroundCornerRadius : undefined,
|
|
1146
|
+
backgroundOpacity: (options.backgroundOpacity !== undefined) ? options.backgroundOpacity : undefined,
|
|
1147
|
+
showBackground: (options.showBackground !== undefined) ? options.showBackground : undefined,
|
|
1148
|
+
// Alternating row color options
|
|
1149
|
+
alternatingRowColors: (options.alternatingRowColors !== undefined) ? options.alternatingRowColors : undefined,
|
|
1150
|
+
evenRowColor: (options.evenRowColor !== undefined) ? options.evenRowColor : undefined,
|
|
1151
|
+
oddRowColor: (options.oddRowColor !== undefined) ? options.oddRowColor : undefined,
|
|
1152
|
+
alternatingRowOpacity: (options.alternatingRowOpacity !== undefined) ? options.alternatingRowOpacity : undefined
|
|
1128
1153
|
};
|
|
1129
1154
|
}
|
|
1130
1155
|
|
|
@@ -1155,7 +1180,17 @@ _propagateBackgroundStyle(style, visited = new Set()) {
|
|
|
1155
1180
|
omdType: "table",
|
|
1156
1181
|
title: `Equation Table: ${this.toString()}`,
|
|
1157
1182
|
headers,
|
|
1158
|
-
data
|
|
1183
|
+
data,
|
|
1184
|
+
// Background customization options
|
|
1185
|
+
backgroundColor: (options.backgroundColor !== undefined) ? options.backgroundColor : undefined,
|
|
1186
|
+
backgroundCornerRadius: (options.backgroundCornerRadius !== undefined) ? options.backgroundCornerRadius : undefined,
|
|
1187
|
+
backgroundOpacity: (options.backgroundOpacity !== undefined) ? options.backgroundOpacity : undefined,
|
|
1188
|
+
showBackground: (options.showBackground !== undefined) ? options.showBackground : undefined,
|
|
1189
|
+
// Alternating row color options
|
|
1190
|
+
alternatingRowColors: (options.alternatingRowColors !== undefined) ? options.alternatingRowColors : undefined,
|
|
1191
|
+
evenRowColor: (options.evenRowColor !== undefined) ? options.evenRowColor : undefined,
|
|
1192
|
+
oddRowColor: (options.oddRowColor !== undefined) ? options.oddRowColor : undefined,
|
|
1193
|
+
alternatingRowOpacity: (options.alternatingRowOpacity !== undefined) ? options.alternatingRowOpacity : undefined
|
|
1159
1194
|
};
|
|
1160
1195
|
}
|
|
1161
1196
|
|
|
@@ -1184,7 +1219,7 @@ _propagateBackgroundStyle(style, visited = new Set()) {
|
|
|
1184
1219
|
* @returns {Object} JSON configuration for omdBalanceHanger
|
|
1185
1220
|
* @private
|
|
1186
1221
|
*/
|
|
1187
|
-
_renderToHanger() {
|
|
1222
|
+
_renderToHanger(options = {}) {
|
|
1188
1223
|
// Convert equation sides to hanger representation
|
|
1189
1224
|
const leftValues = this._convertToHangerValues(this.getLeft());
|
|
1190
1225
|
const rightValues = this._convertToHangerValues(this.getRight());
|
|
@@ -1193,7 +1228,12 @@ _propagateBackgroundStyle(style, visited = new Set()) {
|
|
|
1193
1228
|
omdType: "balanceHanger",
|
|
1194
1229
|
leftValues: leftValues,
|
|
1195
1230
|
rightValues: rightValues,
|
|
1196
|
-
tilt: "none" // Equations should be balanced by definition
|
|
1231
|
+
tilt: "none", // Equations should be balanced by definition
|
|
1232
|
+
// Background customization options
|
|
1233
|
+
backgroundColor: (options.backgroundColor !== undefined) ? options.backgroundColor : undefined,
|
|
1234
|
+
backgroundCornerRadius: (options.backgroundCornerRadius !== undefined) ? options.backgroundCornerRadius : undefined,
|
|
1235
|
+
backgroundOpacity: (options.backgroundOpacity !== undefined) ? options.backgroundOpacity : undefined,
|
|
1236
|
+
showBackground: (options.showBackground !== undefined) ? options.showBackground : undefined
|
|
1197
1237
|
};
|
|
1198
1238
|
}
|
|
1199
1239
|
|
|
@@ -15,16 +15,22 @@ import { jsvgLayoutGroup, jsvgEllipse, jsvgLine } from '@teachinglab/jsvg';
|
|
|
15
15
|
* @extends omdEquationSequenceNode
|
|
16
16
|
*/
|
|
17
17
|
export class omdStepVisualizer extends omdEquationSequenceNode {
|
|
18
|
-
constructor(steps) {
|
|
18
|
+
constructor(steps, styling = {}) {
|
|
19
19
|
super(steps);
|
|
20
20
|
|
|
21
|
+
// Store styling options with defaults
|
|
22
|
+
this.styling = this._mergeWithDefaults(styling || {});
|
|
23
|
+
|
|
21
24
|
// Visual elements for step tracking
|
|
22
25
|
this.stepDots = [];
|
|
23
26
|
this.stepLines = [];
|
|
24
27
|
this.visualContainer = new jsvgLayoutGroup();
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
this.
|
|
28
|
+
|
|
29
|
+
// Use styling values for these properties
|
|
30
|
+
this.dotRadius = this.styling.dotRadius;
|
|
31
|
+
this.lineWidth = this.styling.lineWidth;
|
|
32
|
+
this.visualSpacing = this.styling.visualSpacing;
|
|
33
|
+
|
|
28
34
|
this.activeDotIndex = -1;
|
|
29
35
|
this.dotsClickable = true;
|
|
30
36
|
this.nodeToStepMap = new Map();
|
|
@@ -32,15 +38,311 @@ export class omdStepVisualizer extends omdEquationSequenceNode {
|
|
|
32
38
|
// Highlighting system
|
|
33
39
|
this.stepVisualizerHighlights = new Set();
|
|
34
40
|
this.highlighting = new omdStepVisualizerHighlighting(this);
|
|
35
|
-
|
|
41
|
+
|
|
42
|
+
// Pass textbox options through styling parameter
|
|
43
|
+
const textBoxOptions = this.styling.textBoxOptions || {};
|
|
44
|
+
this.textBoxManager = new omdStepVisualizerTextBoxes(this, this.highlighting, textBoxOptions);
|
|
36
45
|
this.layoutManager = new omdStepVisualizerLayout(this);
|
|
37
46
|
|
|
38
47
|
this.addChild(this.visualContainer);
|
|
39
48
|
this._initializeVisualElements();
|
|
49
|
+
|
|
50
|
+
// Set default filter level to show only major steps (stepMark = 0)
|
|
51
|
+
// This ensures intermediate steps are hidden and expansion dots can be created
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
if (this.setFilterLevel && typeof this.setFilterLevel === 'function') {
|
|
56
|
+
this.setFilterLevel(0);
|
|
57
|
+
|
|
58
|
+
} else {
|
|
59
|
+
|
|
60
|
+
}
|
|
61
|
+
|
|
40
62
|
this.computeDimensions();
|
|
41
63
|
this.updateLayout();
|
|
42
64
|
}
|
|
43
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Public: programmatically toggle a dot (simulate user click behavior)
|
|
68
|
+
* @param {number} dotIndex
|
|
69
|
+
*/
|
|
70
|
+
toggleDot(dotIndex) {
|
|
71
|
+
if (typeof dotIndex !== 'number') return;
|
|
72
|
+
if (dotIndex < 0 || dotIndex >= this.stepDots.length) return;
|
|
73
|
+
const dot = this.stepDots[dotIndex];
|
|
74
|
+
this._handleDotClick(dot, dotIndex);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Public: close currently active dot textbox if any
|
|
79
|
+
*/
|
|
80
|
+
closeActiveDot() {
|
|
81
|
+
// Always clear all boxes to be safe (even if activeDotIndex already reset)
|
|
82
|
+
try {
|
|
83
|
+
const before = this.textBoxManager?.stepTextBoxes?.length || 0;
|
|
84
|
+
this._clearActiveDot();
|
|
85
|
+
if (this.textBoxManager && typeof this.textBoxManager.clearAllTextBoxes === 'function') {
|
|
86
|
+
this.textBoxManager.clearAllTextBoxes();
|
|
87
|
+
}
|
|
88
|
+
const after = this.textBoxManager?.stepTextBoxes?.length || 0;
|
|
89
|
+
if (before !== after) console.log('omdStepVisualizer: closeActiveDot removed text boxes', before, '->', after);
|
|
90
|
+
} catch (e) { /* no-op */ }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Public: close all text boxes (future-proof; currently only one can be active)
|
|
95
|
+
*/
|
|
96
|
+
closeAllTextBoxes() {
|
|
97
|
+
this.closeActiveDot();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Public: force close EVERYTHING related to active explanation UI
|
|
102
|
+
*/
|
|
103
|
+
forceCloseAll() {
|
|
104
|
+
this.closeActiveDot();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Merges user styling with default styling options
|
|
109
|
+
* @param {Object} userStyling - User-provided styling options
|
|
110
|
+
* @returns {Object} Merged styling object with defaults
|
|
111
|
+
* @private
|
|
112
|
+
*/
|
|
113
|
+
_mergeWithDefaults(userStyling) {
|
|
114
|
+
const defaults = {
|
|
115
|
+
// Dot styling
|
|
116
|
+
dotColor: omdColor.stepColor,
|
|
117
|
+
dotRadius: getDotRadius(0),
|
|
118
|
+
dotStrokeWidth: 2,
|
|
119
|
+
activeDotColor: omdColor.explainColor,
|
|
120
|
+
expansionDotScale: 0.4,
|
|
121
|
+
|
|
122
|
+
// Line styling
|
|
123
|
+
lineColor: omdColor.stepColor,
|
|
124
|
+
lineWidth: 2,
|
|
125
|
+
activeLineColor: omdColor.explainColor,
|
|
126
|
+
|
|
127
|
+
// Colors
|
|
128
|
+
explainColor: omdColor.explainColor,
|
|
129
|
+
highlightColor: omdColor.hiliteColor,
|
|
130
|
+
|
|
131
|
+
// Layout
|
|
132
|
+
visualSpacing: 30,
|
|
133
|
+
fixedVisualizerPosition: 250,
|
|
134
|
+
dotVerticalOffset: 15,
|
|
135
|
+
|
|
136
|
+
// Text boxes
|
|
137
|
+
textBoxOptions: {
|
|
138
|
+
backgroundColor: omdColor.white,
|
|
139
|
+
borderColor: omdColor.lightGray,
|
|
140
|
+
borderWidth: 1,
|
|
141
|
+
borderRadius: 5,
|
|
142
|
+
padding: 8, // Minimal padding for tight fit
|
|
143
|
+
fontSize: 14,
|
|
144
|
+
fontFamily: 'Albert Sans, Arial, sans-serif',
|
|
145
|
+
maxWidth: 300, // More reasonable width for compact layout
|
|
146
|
+
dropShadow: true
|
|
147
|
+
// Removed zIndex and position from defaults - these should only apply to container
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
// Visual effects
|
|
151
|
+
enableAnimations: true,
|
|
152
|
+
hoverEffects: true,
|
|
153
|
+
|
|
154
|
+
// Background styling (inherited from equation styling)
|
|
155
|
+
backgroundColor: null,
|
|
156
|
+
cornerRadius: null,
|
|
157
|
+
pill: null
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
return this._deepMerge(defaults, userStyling);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Deep merge two objects
|
|
165
|
+
* @param {Object} target - Target object
|
|
166
|
+
* @param {Object} source - Source object
|
|
167
|
+
* @returns {Object} Merged object
|
|
168
|
+
* @private
|
|
169
|
+
*/
|
|
170
|
+
_deepMerge(target, source) {
|
|
171
|
+
const result = { ...target };
|
|
172
|
+
|
|
173
|
+
for (const key in source) {
|
|
174
|
+
if (source.hasOwnProperty(key)) {
|
|
175
|
+
if (typeof source[key] === 'object' && source[key] !== null && !Array.isArray(source[key])) {
|
|
176
|
+
result[key] = this._deepMerge(result[key] || {}, source[key]);
|
|
177
|
+
} else {
|
|
178
|
+
result[key] = source[key];
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return result;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Updates the styling options and applies them to existing visual elements
|
|
188
|
+
* @param {Object} newStyling - New styling options to apply
|
|
189
|
+
*/
|
|
190
|
+
setStyling(newStyling) {
|
|
191
|
+
this.styling = this._mergeWithDefaults({ ...this.styling, ...newStyling });
|
|
192
|
+
|
|
193
|
+
// Update instance properties that are used elsewhere
|
|
194
|
+
this.dotRadius = this.styling.dotRadius;
|
|
195
|
+
this.lineWidth = this.styling.lineWidth;
|
|
196
|
+
this.visualSpacing = this.styling.visualSpacing;
|
|
197
|
+
|
|
198
|
+
this._applyStylingToExistingElements();
|
|
199
|
+
|
|
200
|
+
// Update layout spacing if changed
|
|
201
|
+
if (newStyling.visualSpacing !== undefined) {
|
|
202
|
+
this.visualSpacing = this.styling.visualSpacing;
|
|
203
|
+
}
|
|
204
|
+
if (newStyling.fixedVisualizerPosition !== undefined && this.layoutManager) {
|
|
205
|
+
this.layoutManager.setFixedVisualizerPosition(this.styling.fixedVisualizerPosition);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Refresh layout and visual elements
|
|
209
|
+
this.updateLayout();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Gets the current styling options
|
|
214
|
+
* @returns {Object} Current styling configuration
|
|
215
|
+
*/
|
|
216
|
+
getStyling() {
|
|
217
|
+
return { ...this.styling };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Sets a specific styling property
|
|
222
|
+
* @param {string} property - The property to set (supports dot notation like 'textBoxOptions.backgroundColor')
|
|
223
|
+
* @param {any} value - The value to set
|
|
224
|
+
*/
|
|
225
|
+
setStyleProperty(property, value) {
|
|
226
|
+
const keys = property.split('.');
|
|
227
|
+
const lastKey = keys.pop();
|
|
228
|
+
const target = keys.reduce((obj, key) => {
|
|
229
|
+
if (!obj[key]) obj[key] = {};
|
|
230
|
+
return obj[key];
|
|
231
|
+
}, this.styling);
|
|
232
|
+
|
|
233
|
+
target[lastKey] = value;
|
|
234
|
+
|
|
235
|
+
// Update instance properties if they were changed
|
|
236
|
+
if (property === 'dotRadius') this.dotRadius = value;
|
|
237
|
+
if (property === 'lineWidth') this.lineWidth = value;
|
|
238
|
+
if (property === 'visualSpacing') this.visualSpacing = value;
|
|
239
|
+
if (property === 'fixedVisualizerPosition' && this.layoutManager) {
|
|
240
|
+
this.layoutManager.setFixedVisualizerPosition(value);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
this._applyStylingToExistingElements();
|
|
244
|
+
this.updateLayout();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Gets a specific styling property
|
|
249
|
+
* @param {string} property - The property to get (supports dot notation)
|
|
250
|
+
* @returns {any} The property value
|
|
251
|
+
*/
|
|
252
|
+
getStyleProperty(property) {
|
|
253
|
+
return property.split('.').reduce((obj, key) => obj?.[key], this.styling);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Applies current styling to all existing visual elements
|
|
258
|
+
* @private
|
|
259
|
+
*/
|
|
260
|
+
_applyStylingToExistingElements() {
|
|
261
|
+
// Update dots
|
|
262
|
+
this.stepDots.forEach((dot, index) => {
|
|
263
|
+
if (dot && dot.equationRef) {
|
|
264
|
+
const isActive = this.activeDotIndex === index;
|
|
265
|
+
const color = isActive ? this.styling.activeDotColor : this.styling.dotColor;
|
|
266
|
+
dot.setFillColor(color);
|
|
267
|
+
dot.setStrokeColor(color);
|
|
268
|
+
dot.setStrokeWidth(this.styling.dotStrokeWidth);
|
|
269
|
+
|
|
270
|
+
// Update radius based on step mark
|
|
271
|
+
const stepMark = dot.equationRef.stepMark ?? 0;
|
|
272
|
+
const radius = this.styling.dotRadius || getDotRadius(stepMark);
|
|
273
|
+
dot.setWidthAndHeight(radius * 2, radius * 2);
|
|
274
|
+
dot.radius = radius;
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// Update lines
|
|
279
|
+
this.stepLines.forEach((line, index) => {
|
|
280
|
+
if (line) {
|
|
281
|
+
const isActive = this.activeDotIndex >= 0 &&
|
|
282
|
+
(line.toDotIndex === this.activeDotIndex || line.fromDotIndex === this.activeDotIndex);
|
|
283
|
+
const color = isActive ? this.styling.activeLineColor : this.styling.lineColor;
|
|
284
|
+
line.setStrokeColor(color);
|
|
285
|
+
line.setStrokeWidth(this.styling.lineWidth);
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// Update expansion dots
|
|
290
|
+
if (this.layoutManager && this.layoutManager.expansionDots) {
|
|
291
|
+
this.layoutManager.expansionDots.forEach(expansionDot => {
|
|
292
|
+
if (expansionDot) {
|
|
293
|
+
const baseRadius = this.styling.dotRadius || getDotRadius(0);
|
|
294
|
+
const radius = Math.max(3, baseRadius * this.styling.expansionDotScale);
|
|
295
|
+
expansionDot.setWidthAndHeight(radius * 2, radius * 2);
|
|
296
|
+
expansionDot.radius = radius;
|
|
297
|
+
expansionDot.setFillColor(this.styling.dotColor);
|
|
298
|
+
expansionDot.setStrokeColor(this.styling.dotColor);
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Update text box styling if manager exists
|
|
304
|
+
if (this.textBoxManager && typeof this.textBoxManager.updateStyling === 'function') {
|
|
305
|
+
this.textBoxManager.updateStyling(this.styling.textBoxOptions);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Sets the visual background style (inherits from equation styling)
|
|
311
|
+
* @param {Object} style - Background style options
|
|
312
|
+
*/
|
|
313
|
+
setBackgroundStyle(style = {}) {
|
|
314
|
+
this.styling.backgroundColor = style.backgroundColor || this.styling.backgroundColor;
|
|
315
|
+
this.styling.cornerRadius = style.cornerRadius || this.styling.cornerRadius;
|
|
316
|
+
this.styling.pill = style.pill !== undefined ? style.pill : this.styling.pill;
|
|
317
|
+
|
|
318
|
+
// Apply to equation background if this step visualizer has equation styling
|
|
319
|
+
if (typeof this.setDefaultEquationBackground === 'function') {
|
|
320
|
+
this.setDefaultEquationBackground(style);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Gets the current background style
|
|
326
|
+
* @returns {Object} Current background style
|
|
327
|
+
*/
|
|
328
|
+
getBackgroundStyle() {
|
|
329
|
+
return {
|
|
330
|
+
backgroundColor: this.styling.backgroundColor,
|
|
331
|
+
cornerRadius: this.styling.cornerRadius,
|
|
332
|
+
pill: this.styling.pill
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Sets the fixed position for the step visualizer
|
|
338
|
+
* @param {number} position - The x position from the left edge where the visualizer should be positioned
|
|
339
|
+
*/
|
|
340
|
+
setFixedVisualizerPosition(position) {
|
|
341
|
+
if (this.layoutManager) {
|
|
342
|
+
this.layoutManager.setFixedVisualizerPosition(position);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
44
346
|
/**
|
|
45
347
|
* Force rebuild visual container (dots/lines) from scratch
|
|
46
348
|
*/
|
|
@@ -77,7 +379,7 @@ export class omdStepVisualizer extends omdEquationSequenceNode {
|
|
|
77
379
|
});
|
|
78
380
|
|
|
79
381
|
this.layoutManager.updateVisualZOrder();
|
|
80
|
-
this.layoutManager.updateVisualLayout();
|
|
382
|
+
this.layoutManager.updateVisualLayout(true);
|
|
81
383
|
}
|
|
82
384
|
|
|
83
385
|
/**
|
|
@@ -85,12 +387,14 @@ export class omdStepVisualizer extends omdEquationSequenceNode {
|
|
|
85
387
|
* @private
|
|
86
388
|
*/
|
|
87
389
|
_createStepDot(equation, index) {
|
|
88
|
-
const
|
|
390
|
+
const stepMark = equation.stepMark ?? 0;
|
|
391
|
+
const radius = this.styling.dotRadius || getDotRadius(stepMark);
|
|
89
392
|
const dot = new jsvgEllipse();
|
|
90
393
|
dot.setWidthAndHeight(radius * 2, radius * 2);
|
|
91
|
-
|
|
92
|
-
dot.
|
|
93
|
-
dot.
|
|
394
|
+
const dotColor = this.styling.dotColor;
|
|
395
|
+
dot.setFillColor(dotColor);
|
|
396
|
+
dot.setStrokeColor(dotColor);
|
|
397
|
+
dot.setStrokeWidth(this.styling.dotStrokeWidth);
|
|
94
398
|
dot.radius = radius;
|
|
95
399
|
|
|
96
400
|
dot.equationRef = equation;
|
|
@@ -114,8 +418,9 @@ export class omdStepVisualizer extends omdEquationSequenceNode {
|
|
|
114
418
|
*/
|
|
115
419
|
_createStepLine(fromIndex, toIndex) {
|
|
116
420
|
const line = new jsvgLine();
|
|
117
|
-
|
|
118
|
-
line.
|
|
421
|
+
const lineColor = this.styling.lineColor;
|
|
422
|
+
line.setStrokeColor(lineColor);
|
|
423
|
+
line.setStrokeWidth(this.styling.lineWidth);
|
|
119
424
|
|
|
120
425
|
line.fromDotIndex = fromIndex;
|
|
121
426
|
line.toDotIndex = toIndex;
|
|
@@ -151,30 +456,39 @@ export class omdStepVisualizer extends omdEquationSequenceNode {
|
|
|
151
456
|
* Override addStep to update visual elements when new steps are added
|
|
152
457
|
*/
|
|
153
458
|
addStep(step, options = {}) {
|
|
154
|
-
|
|
459
|
+
// Call parent first to add the step properly
|
|
460
|
+
super.addStep(step, options);
|
|
461
|
+
|
|
462
|
+
// Now create visual elements for equation nodes only
|
|
155
463
|
if (step instanceof omdEquationNode) {
|
|
156
|
-
this
|
|
157
|
-
const
|
|
158
|
-
const newIndex = equations.length - 1;
|
|
159
|
-
this.steps.pop();
|
|
160
|
-
createdDot = this._createStepDot(step, newIndex);
|
|
161
|
-
step.findAllNodes().forEach(node => {
|
|
162
|
-
this.nodeToStepMap.set(node.id, newIndex);
|
|
163
|
-
});
|
|
464
|
+
// Find the actual index of this equation in the steps array
|
|
465
|
+
const equationIndex = this.steps.filter(s => s instanceof omdEquationNode).indexOf(step);
|
|
164
466
|
|
|
165
|
-
if (
|
|
166
|
-
this.
|
|
467
|
+
if (equationIndex >= 0) {
|
|
468
|
+
const createdDot = this._createStepDot(step, equationIndex);
|
|
469
|
+
|
|
470
|
+
// Update the node to step mapping
|
|
471
|
+
step.findAllNodes().forEach(node => {
|
|
472
|
+
this.nodeToStepMap.set(node.id, equationIndex);
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
// Create connecting line if this isn't the first equation
|
|
476
|
+
if (equationIndex > 0) {
|
|
477
|
+
this._createStepLine(equationIndex - 1, equationIndex);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// After stepMark is set, adjust dot radius
|
|
481
|
+
if (createdDot) {
|
|
482
|
+
const radius = getDotRadius(step.stepMark ?? 0);
|
|
483
|
+
createdDot.setWidthAndHeight(radius * 2, radius * 2);
|
|
484
|
+
createdDot.radius = radius;
|
|
485
|
+
}
|
|
167
486
|
}
|
|
168
487
|
}
|
|
169
488
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if(createdDot){
|
|
174
|
-
const radius=getDotRadius(step.stepMark??0);
|
|
175
|
-
createdDot.setWidthAndHeight(radius*2,radius*2);
|
|
176
|
-
createdDot.radius=radius;
|
|
177
|
-
}
|
|
489
|
+
// Update layout after adding the step
|
|
490
|
+
this.computeDimensions();
|
|
491
|
+
this.updateLayout();
|
|
178
492
|
}
|
|
179
493
|
|
|
180
494
|
/**
|
|
@@ -189,13 +503,16 @@ export class omdStepVisualizer extends omdEquationSequenceNode {
|
|
|
189
503
|
*/
|
|
190
504
|
computeDimensions() {
|
|
191
505
|
super.computeDimensions();
|
|
506
|
+
// Store original dimensions before visualizer expansion
|
|
192
507
|
this.sequenceWidth = this.width;
|
|
508
|
+
this.sequenceHeight = this.height;
|
|
193
509
|
|
|
194
|
-
//
|
|
195
|
-
if (this.stepDots && this.stepDots.length > 0) {
|
|
510
|
+
// Set width to include the fixed visualizer position plus visualizer width
|
|
511
|
+
if (this.stepDots && this.stepDots.length > 0 && this.layoutManager) {
|
|
196
512
|
const containerWidth = this.dotRadius * 3;
|
|
197
|
-
const
|
|
198
|
-
this.
|
|
513
|
+
const fixedVisualizerPosition = this.layoutManager.fixedVisualizerPosition || 250;
|
|
514
|
+
const totalWidth = fixedVisualizerPosition + this.visualSpacing + containerWidth;
|
|
515
|
+
this.setWidthAndHeight(totalWidth, this.height);
|
|
199
516
|
}
|
|
200
517
|
}
|
|
201
518
|
|
|
@@ -203,14 +520,26 @@ export class omdStepVisualizer extends omdEquationSequenceNode {
|
|
|
203
520
|
* Override updateLayout to update visual elements as well
|
|
204
521
|
*/
|
|
205
522
|
updateLayout() {
|
|
523
|
+
|
|
524
|
+
|
|
206
525
|
super.updateLayout();
|
|
207
526
|
|
|
208
527
|
// Only update visual layout if layoutManager is initialized
|
|
209
528
|
if (this.layoutManager) {
|
|
210
|
-
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
this.layoutManager.updateVisualLayout(true); // Allow repositioning for main layout updates
|
|
532
|
+
|
|
533
|
+
|
|
211
534
|
this.layoutManager.updateVisualVisibility();
|
|
535
|
+
|
|
536
|
+
|
|
212
537
|
this.layoutManager.updateAllLinePositions();
|
|
538
|
+
} else {
|
|
539
|
+
|
|
213
540
|
}
|
|
541
|
+
|
|
542
|
+
|
|
214
543
|
}
|
|
215
544
|
|
|
216
545
|
/**
|
|
@@ -313,30 +642,46 @@ export class omdStepVisualizer extends omdEquationSequenceNode {
|
|
|
313
642
|
this.activeDot = this.stepDots[dotIndex];
|
|
314
643
|
|
|
315
644
|
const dot = this.stepDots[dotIndex];
|
|
316
|
-
|
|
317
|
-
dot.
|
|
645
|
+
const explainColor = this.styling.activeDotColor;
|
|
646
|
+
dot.setFillColor(explainColor);
|
|
647
|
+
dot.setStrokeColor(explainColor);
|
|
318
648
|
|
|
319
|
-
this.setLineAboveColor(dotIndex,
|
|
649
|
+
this.setLineAboveColor(dotIndex, this.styling.activeLineColor);
|
|
320
650
|
this.textBoxManager.createTextBoxForDot(dotIndex);
|
|
651
|
+
|
|
652
|
+
// Temporarily disable equation repositioning for simple dot state changes
|
|
653
|
+
const originalRepositioning = this.layoutManager.allowEquationRepositioning;
|
|
654
|
+
this.layoutManager.allowEquationRepositioning = false;
|
|
321
655
|
this.layoutManager.updateVisualZOrder();
|
|
656
|
+
this.layoutManager.allowEquationRepositioning = originalRepositioning;
|
|
322
657
|
}
|
|
323
658
|
|
|
324
659
|
/**
|
|
325
660
|
* Clears the currently active dot
|
|
326
661
|
* @private
|
|
662
|
+
*/
|
|
663
|
+
/**
|
|
664
|
+
* Clears the currently active dot
|
|
665
|
+
* @private
|
|
327
666
|
*/
|
|
328
667
|
_clearActiveDot() {
|
|
329
668
|
try {
|
|
330
669
|
if (this.activeDotIndex !== -1) {
|
|
331
670
|
const dot = this.stepDots[this.activeDotIndex];
|
|
332
|
-
|
|
333
|
-
dot.
|
|
671
|
+
const dotColor = this.styling.dotColor;
|
|
672
|
+
dot.setFillColor(dotColor);
|
|
673
|
+
dot.setStrokeColor(dotColor);
|
|
334
674
|
|
|
335
|
-
this.setLineAboveColor(this.activeDotIndex,
|
|
675
|
+
this.setLineAboveColor(this.activeDotIndex, this.styling.lineColor);
|
|
336
676
|
this.textBoxManager.removeTextBoxForDot(this.activeDotIndex);
|
|
337
677
|
|
|
338
678
|
this.highlighting.clearHighlights();
|
|
679
|
+
|
|
680
|
+
// Temporarily disable equation repositioning for simple dot state changes
|
|
681
|
+
const originalRepositioning = this.layoutManager.allowEquationRepositioning;
|
|
682
|
+
this.layoutManager.allowEquationRepositioning = false;
|
|
339
683
|
this.layoutManager.updateVisualZOrder();
|
|
684
|
+
this.layoutManager.allowEquationRepositioning = originalRepositioning;
|
|
340
685
|
|
|
341
686
|
this.activeDot = null;
|
|
342
687
|
this.activeDotIndex = -1;
|