@teachinglab/omd 0.6.6 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,72 @@
1
+ import { omdColor } from "./omdColor.js";
2
+ import { jsvgGroup } from "@teachinglab/jsvg";
3
+ import { omdNumberLine } from "./omdNumberLine.js";
4
+
5
+ export class omdDoubleNumberLine extends jsvgGroup
6
+ {
7
+ constructor()
8
+ {
9
+ // initialization
10
+ super();
11
+
12
+ this.type = "omdDoubleNumberLine";
13
+
14
+ this.topNumberLine = new omdNumberLine();
15
+ this.bottomNumberLine = new omdNumberLine();
16
+
17
+ this.spacing = 30;
18
+
19
+ this.updateLayout();
20
+ }
21
+
22
+ loadFromJSON( data )
23
+ {
24
+ // Load spacing first, before updating layout
25
+ if ( typeof data.spacing !== "undefined" ) {
26
+ this.spacing = data.spacing;
27
+ }
28
+
29
+ if ( typeof data.topNumberLine !== "undefined" ) {
30
+ this.topNumberLine.loadFromJSON(data.topNumberLine);
31
+ }
32
+
33
+ if ( typeof data.bottomNumberLine !== "undefined" ) {
34
+ this.bottomNumberLine.loadFromJSON(data.bottomNumberLine);
35
+ }
36
+
37
+ // Don't call updateLayout here - let it be called separately
38
+ // This prevents double-calling and ensures spacing is used
39
+ this.updateLayout();
40
+ }
41
+
42
+ updateLayout()
43
+ {
44
+ this.removeAllChildren();
45
+
46
+ // Calculate the maximum left padding needed to align the start of the lines
47
+ const topLeftPadding = this.topNumberLine.title ? 80 : 20;
48
+ const bottomLeftPadding = this.bottomNumberLine.title ? 80 : 20;
49
+ const maxLeftPadding = Math.max(topLeftPadding, bottomLeftPadding);
50
+
51
+ // Position top number line
52
+ const topXOffset = maxLeftPadding - topLeftPadding;
53
+ this.topNumberLine.setPosition(topXOffset, 0);
54
+ this.addChild(this.topNumberLine);
55
+
56
+ // Position bottom number line - spacing controls the gap
57
+ // If spacing = 0, they overlap. If spacing = 10, there's a 10px gap between them
58
+ const bottomXOffset = maxLeftPadding - bottomLeftPadding;
59
+ this.bottomNumberLine.setPosition(bottomXOffset, this.spacing);
60
+ this.addChild(this.bottomNumberLine);
61
+
62
+ // Set overall dimensions
63
+ const maxWidth = Math.max(
64
+ this.topNumberLine.width + topXOffset,
65
+ this.bottomNumberLine.width + bottomXOffset
66
+ );
67
+ this.width = maxWidth;
68
+ this.height = 70 + this.spacing; // Top line (70px) + spacing
69
+ this.svgObject.setAttribute('viewBox', `0 0 ${this.width} ${this.height}`);
70
+ }
71
+
72
+ }
@@ -0,0 +1,115 @@
1
+
2
+ import { jsvgGroup } from "@teachinglab/jsvg";
3
+ import { omdTapeDiagram } from "./omdTapeDiagram.js";
4
+
5
+ export class omdDoubleTapeDiagram extends jsvgGroup
6
+ {
7
+ constructor()
8
+ {
9
+ // initialization
10
+ super();
11
+
12
+ this.type = "omdDoubleTapeDiagram";
13
+
14
+ this.topTapeDiagram = new omdTapeDiagram();
15
+ this.bottomTapeDiagram = new omdTapeDiagram();
16
+
17
+ this.spacing = 30;
18
+
19
+ this.updateLayout();
20
+ }
21
+
22
+ loadFromJSON( data )
23
+ {
24
+ // Load spacing first, before updating layout
25
+ if ( typeof data.spacing !== "undefined" ) {
26
+ this.spacing = data.spacing;
27
+ }
28
+
29
+ if ( typeof data.topTapeDiagram !== "undefined" ) {
30
+ this.topTapeDiagram.loadFromJSON(data.topTapeDiagram);
31
+ }
32
+
33
+ if ( typeof data.bottomTapeDiagram !== "undefined" ) {
34
+ this.bottomTapeDiagram.loadFromJSON(data.bottomTapeDiagram);
35
+ }
36
+
37
+ this.updateLayout();
38
+ }
39
+
40
+ updateLayout()
41
+ {
42
+ this.removeAllChildren();
43
+
44
+ // Calculate total numeric values for both tapes to determine unit width
45
+ const topTotal = this.calculateTotalValue(this.topTapeDiagram);
46
+ const bottomTotal = this.calculateTotalValue(this.bottomTapeDiagram);
47
+
48
+ // Find the maximum total to determine a consistent unit width
49
+ const maxTotal = Math.max(topTotal, bottomTotal);
50
+ const baseWidth = 300; // Base width for the longest tape
51
+ const unitWidth = maxTotal > 0 ? baseWidth / maxTotal : baseWidth;
52
+
53
+ // Set each tape's width based on its total value
54
+ this.topTapeDiagram.totalWidth = topTotal * unitWidth;
55
+ this.bottomTapeDiagram.totalWidth = bottomTotal * unitWidth;
56
+
57
+ // Force update of both tape diagrams with new widths
58
+ this.topTapeDiagram.updateLayout();
59
+ this.bottomTapeDiagram.updateLayout();
60
+
61
+ // Calculate the maximum left padding needed to align the start of the tapes
62
+ const topLeftPadding = this.topTapeDiagram.title ? 80 : 20;
63
+ const bottomLeftPadding = this.bottomTapeDiagram.title ? 80 : 20;
64
+ const maxLeftPadding = Math.max(topLeftPadding, bottomLeftPadding);
65
+
66
+ // Position top tape diagram
67
+ const topXOffset = maxLeftPadding - topLeftPadding;
68
+ this.topTapeDiagram.setPosition(topXOffset, 0);
69
+ this.addChild(this.topTapeDiagram);
70
+
71
+ // Position bottom tape diagram
72
+ // spacing controls the gap between the bottom of top tape and top of bottom tape
73
+ const bottomXOffset = maxLeftPadding - bottomLeftPadding;
74
+ const bottomYPosition = this.topTapeDiagram.height + this.spacing;
75
+ this.bottomTapeDiagram.setPosition(bottomXOffset, bottomYPosition);
76
+ this.addChild(this.bottomTapeDiagram);
77
+
78
+ // Set overall dimensions
79
+ const maxWidth = Math.max(
80
+ this.topTapeDiagram.width + topXOffset,
81
+ this.bottomTapeDiagram.width + bottomXOffset
82
+ );
83
+ this.width = maxWidth;
84
+ this.height = this.topTapeDiagram.height + this.spacing + this.bottomTapeDiagram.height;
85
+ this.svgObject.setAttribute('viewBox', `0 0 ${this.width} ${this.height}`);
86
+ }
87
+
88
+ calculateTotalValue(tapeDiagram)
89
+ {
90
+ let total = 0;
91
+
92
+ for (const valueData of tapeDiagram.values) {
93
+ let value = "";
94
+
95
+ // Handle both old format (simple values) and new format (objects)
96
+ if (typeof valueData === "object" && valueData !== null) {
97
+ value = valueData.value || "";
98
+ } else {
99
+ value = valueData.toString();
100
+ }
101
+
102
+ // Parse numeric value from string (e.g., "3", "2x", "5y")
103
+ const match = value.match(/^([0-9.]+)?([a-zA-Z]*)$/);
104
+ if (match) {
105
+ const coefficient = match[1] ? parseFloat(match[1]) : (match[2] ? 1 : 1);
106
+ total += coefficient;
107
+ } else {
108
+ total += 1; // Default for unparseable values
109
+ }
110
+ }
111
+
112
+ return total;
113
+ }
114
+
115
+ }
@@ -9,6 +9,7 @@ import { omdVariable } from "./omdVariable.js";
9
9
  import { omdString } from "./omdString.js";
10
10
  import { omdNumber } from "./omdNumber.js";
11
11
  import { parseEquationString } from "./omdUtils.js";
12
+ import { omdEquationNode } from "../omd/nodes/omdEquationNode.js";
12
13
 
13
14
  export class omdEquation extends omdMetaExpression
14
15
  {
@@ -37,12 +38,24 @@ export class omdEquation extends omdMetaExpression
37
38
 
38
39
  this.rightHolder = new jsvgGroup();
39
40
  this.equationStack.addChild( this.rightHolder );
41
+
42
+ this.equationNode = null;
40
43
  }
41
44
 
42
45
  // make an equation (x + 2) = (2x - 3)
43
46
 
44
47
  loadFromJSON( data )
45
48
  {
49
+ // Prefer math.js parsing into omdEquationNode for richer rendering (functions, rationals, roots)
50
+ if (typeof data.equation === 'string' && data.equation.trim()) {
51
+ try {
52
+ this._renderWithEquationNode(data.equation, data.fontSize);
53
+ return;
54
+ } catch (e) {
55
+ console.warn('⚠️ omdEquation math.js render failed, falling back to legacy parsing:', e?.message || e);
56
+ }
57
+ }
58
+
46
59
  // Helper function to fix operator symbols in termsAndOpers arrays
47
60
  function fixOperatorSymbols(expressionData) {
48
61
  if (expressionData && expressionData.termsAndOpers && Array.isArray(expressionData.termsAndOpers)) {
@@ -136,6 +149,37 @@ export class omdEquation extends omdMetaExpression
136
149
  this.updateLayout();
137
150
  }
138
151
 
152
+ _renderWithEquationNode(equationString, fontSize) {
153
+ if (typeof math === 'undefined' || typeof math.parse !== 'function') {
154
+ throw new Error('math.js is required to parse equation strings');
155
+ }
156
+
157
+ const eqNode = omdEquationNode.fromString(equationString);
158
+ if (typeof fontSize === 'number' && eqNode.setFontSize) {
159
+ eqNode.setFontSize(fontSize);
160
+ }
161
+
162
+ if (eqNode.hideBackgroundByDefault) eqNode.hideBackgroundByDefault();
163
+ if (eqNode.computeDimensions) eqNode.computeDimensions();
164
+ if (eqNode.updateLayout) eqNode.updateLayout();
165
+
166
+ // Clear the legacy stack and render just the parsed node
167
+ if (typeof this.equationStack.removeAllChildren === 'function') {
168
+ this.equationStack.removeAllChildren();
169
+ } else {
170
+ this.equationStack.childList = [];
171
+ }
172
+ this.equationStack.addChild(eqNode);
173
+ this.equationStack.setSpacer(0);
174
+
175
+ this.equationNode = eqNode;
176
+ this.leftExpression = null;
177
+ this.rightExpression = null;
178
+
179
+ this.centerEquation = false;
180
+ this.updateLayout();
181
+ }
182
+
139
183
 
140
184
  setLeftAndRightExpressions( leftExp, rightExp )
141
185
  {
@@ -156,6 +200,25 @@ export class omdEquation extends omdMetaExpression
156
200
 
157
201
  updateLayout()
158
202
  {
203
+ if (this.equationNode) {
204
+ const node = this.equationNode;
205
+ if (node.computeDimensions) node.computeDimensions();
206
+ if (node.updateLayout) node.updateLayout();
207
+
208
+ this.equationStack.doHorizontalLayout();
209
+ this.equationStack.setPosition(this.inset, this.inset);
210
+
211
+ const W = node.width || this.equationStack.width;
212
+ const H = node.height || this.equationStack.height;
213
+
214
+ this.backRect.setWidthAndHeight( W + this.inset*2, H + this.inset*2 );
215
+ this.setWidthAndHeight( this.backRect.width, this.backRect.height );
216
+ this.width = this.backRect.width;
217
+ this.height = this.backRect.height;
218
+ this.svgObject.setAttribute('viewBox', `0 0 ${this.width} ${this.height}`);
219
+ return;
220
+ }
221
+
159
222
  this.leftHolder.setWidthAndHeight( this.leftExpression.width, this.leftExpression.height );
160
223
  this.rightHolder.setWidthAndHeight( this.rightExpression.width, this.rightExpression.height );
161
224
 
@@ -186,4 +249,4 @@ export class omdEquation extends omdMetaExpression
186
249
  }
187
250
  }
188
251
 
189
- }
252
+ }
package/src/omdFactory.js CHANGED
@@ -18,8 +18,10 @@
18
18
  import { omdBalanceHanger } from './omdBalanceHanger.js';
19
19
  import { omdTable } from './omdTable.js';
20
20
  import { omdTapeDiagram } from './omdTapeDiagram.js';
21
+ import { omdDoubleTapeDiagram } from './omdDoubleTapeDiagram.js';
21
22
  import { omdCoordinatePlane } from './omdCoordinatePlane.js';
22
23
  import { omdNumberLine } from './omdNumberLine.js';
24
+ import { omdDoubleNumberLine } from './omdDoubleNumberLine.js';
23
25
  import { omdNumberTile } from './omdNumberTile.js';
24
26
  import { omdRatioChart } from './omdRatioChart.js';
25
27
  import { omdTileEquation } from './omdTileEquation.js';
@@ -48,8 +50,10 @@ const OMD_TYPE_MAP = {
48
50
  'balanceHanger': omdBalanceHanger,
49
51
  'table': omdTable,
50
52
  'tapeDiagram': omdTapeDiagram,
53
+ 'doubleTapeDiagram': omdDoubleTapeDiagram,
51
54
  'coordinatePlane': omdCoordinatePlane,
52
55
  'numberLine': omdNumberLine,
56
+ 'doubleNumberLine': omdDoubleNumberLine,
53
57
  'numberTile': omdNumberTile,
54
58
  'ratioChart': omdRatioChart,
55
59
  'tileEquation': omdTileEquation,
@@ -1,6 +1,6 @@
1
1
 
2
2
  import { omdColor } from "./omdColor.js";
3
- import { jsvgGroup, jsvgRect, jsvgLine, jsvgTextBox, jsvgEllipse } from "@teachinglab/jsvg";
3
+ import { jsvgGroup, jsvgRect, jsvgLine, jsvgTextBox, jsvgEllipse, jsvgPath } from "@teachinglab/jsvg";
4
4
 
5
5
  export class omdNumberLine extends jsvgGroup
6
6
  {
@@ -11,8 +11,16 @@ export class omdNumberLine extends jsvgGroup
11
11
 
12
12
  this.type = "omdNumberLine";
13
13
 
14
+ this.title = "";
14
15
  this.min = 0;
15
16
  this.max = 10;
17
+ this.increment = 1;
18
+ this.showLeftArrow = false;
19
+ this.showRightArrow = false;
20
+ this.units = "";
21
+ this.hideDefaultNumbers = false;
22
+ this.specialNumbers = [];
23
+ this.totalWidth = 320;
16
24
  this.dotValues = [];
17
25
  this.label = "";
18
26
  this.updateLayout();
@@ -20,16 +28,40 @@ export class omdNumberLine extends jsvgGroup
20
28
 
21
29
  loadFromJSON( data )
22
30
  {
23
- if ( typeof data.min != "undefined" )
31
+ if ( typeof data.title !== "undefined" )
32
+ this.title = data.title;
33
+
34
+ if ( typeof data.min !== "undefined" )
24
35
  this.min = data.min;
25
36
 
26
- if ( typeof data.max != "undefined" )
37
+ if ( typeof data.max !== "undefined" )
27
38
  this.max = data.max;
28
39
 
29
- if ( typeof data.dotValues != "undefined" )
40
+ if ( typeof data.increment !== "undefined" )
41
+ this.increment = data.increment;
42
+
43
+ if ( typeof data.showLeftArrow !== "undefined" )
44
+ this.showLeftArrow = data.showLeftArrow;
45
+
46
+ if ( typeof data.showRightArrow !== "undefined" )
47
+ this.showRightArrow = data.showRightArrow;
48
+
49
+ if ( typeof data.units !== "undefined" )
50
+ this.units = data.units;
51
+
52
+ if ( typeof data.hideDefaultNumbers !== "undefined" )
53
+ this.hideDefaultNumbers = data.hideDefaultNumbers;
54
+
55
+ if ( typeof data.specialNumbers !== "undefined" )
56
+ this.specialNumbers = data.specialNumbers;
57
+
58
+ if ( typeof data.totalWidth !== "undefined" )
59
+ this.totalWidth = data.totalWidth;
60
+
61
+ if ( typeof data.dotValues !== "undefined" )
30
62
  this.dotValues = data.dotValues;
31
63
 
32
- if ( typeof data.label != "undefined" )
64
+ if ( typeof data.label !== "undefined" )
33
65
  this.label = data.label;
34
66
 
35
67
  this.updateLayout();
@@ -52,63 +84,150 @@ export class omdNumberLine extends jsvgGroup
52
84
  {
53
85
  this.removeAllChildren();
54
86
 
87
+ const leftPadding = this.title ? 80 : 20;
88
+ const rightPadding = 20;
89
+ const arrowSize = 10;
90
+ const tickOverhang = 8; // How much the line extends past the end ticks
91
+ const lineWidth = this.totalWidth;
92
+ // Calculate usable width (space for ticks) - subtract space for arrows only
93
+ const usableLineWidth = lineWidth - (this.showLeftArrow ? arrowSize : 0) - (this.showRightArrow ? arrowSize : 0);
94
+
55
95
  // Set proper dimensions and viewBox for positioning
56
- this.width = 360;
96
+ this.width = leftPadding + lineWidth + rightPadding;
57
97
  this.height = 70;
58
98
  this.svgObject.setAttribute('viewBox', `0 0 ${this.width} ${this.height}`);
59
99
 
60
- // make line
100
+ // Add title if present
101
+ if (this.title) {
102
+ const titleText = new jsvgTextBox();
103
+ titleText.setWidthAndHeight(70, 30);
104
+ titleText.setFontFamily("Albert Sans");
105
+ titleText.setFontColor("black");
106
+ titleText.setFontSize(12);
107
+ titleText.setAlignment("left");
108
+ titleText.setText(this.title);
109
+ titleText.setPosition(5, 20);
110
+ this.addChild(titleText);
111
+ }
112
+
113
+ // Calculate line position and width
114
+ // Line starts at: leftPadding + (arrow space if present), and extends past ticks by tickOverhang on each end
115
+ const lineStartX = leftPadding + (this.showLeftArrow ? arrowSize : 0);
116
+ const lineActualWidth = usableLineWidth; // This is the space between arrows (or edges), ticks go here with overhang
117
+
118
+ // Draw main line (extends past the first and last ticks by tickOverhang)
61
119
  this.line = new jsvgRect();
62
- // this.line.setStrokeColor( "black" );
63
- // this.line.setStrokeWidth( 1 );
64
- // this.line.setEndpoints( 0,0, 300, 0 );
65
- this.line.setWidthAndHeight(320,5);
66
- this.line.setPosition( 20, 22.5 );
67
- this.line.setFillColor( omdColor.mediumGray );
68
- this.line.setCornerRadius( 2.5 );
69
- this.addChild( this.line );
70
-
71
- // make ticks with text
72
- for( var i=this.min; i<=this.max; i++ )
73
- {
74
- var N = i - this.min;
75
- var dX = 300 / (this.max - this.min);
76
-
77
- var pX = 30 + N*dX;
78
-
79
- var tick = new jsvgLine();
80
- tick.setStrokeColor( "black" );
81
- tick.setStrokeWidth( 1 );
82
- tick.setEndpoints( pX, 20, pX, 30 );
83
- this.addChild( tick );
84
-
85
- var tickText = new jsvgTextBox();
86
- tickText.setWidthAndHeight( 30,30 );
87
- tickText.setText ( this.name );
88
- tickText.setFontFamily( "Albert Sans" );
89
- tickText.setFontColor( "black" );
90
- tickText.setFontSize( 10 );
120
+ this.line.setWidthAndHeight(lineActualWidth, 5);
121
+ this.line.setPosition(lineStartX, 22.5);
122
+ this.line.setFillColor(omdColor.mediumGray);
123
+ this.line.setCornerRadius(2.5);
124
+ this.addChild(this.line);
125
+
126
+ // Draw left arrow if needed
127
+ if (this.showLeftArrow) {
128
+ // Cover the rounded corner with a rectangle
129
+ const coverRect = new jsvgRect();
130
+ coverRect.setWidthAndHeight(3, 5);
131
+ coverRect.setPosition(lineStartX, 22.5);
132
+ coverRect.setFillColor(omdColor.mediumGray);
133
+ this.addChild(coverRect);
134
+
135
+ const leftArrow = new jsvgPath();
136
+ const arrowY = 25;
137
+ const arrowX = leftPadding; // Arrow tip position
138
+ leftArrow.addPoint(arrowX + arrowSize, arrowY - 5);
139
+ leftArrow.addPoint(arrowX, arrowY);
140
+ leftArrow.addPoint(arrowX + arrowSize, arrowY + 5);
141
+ leftArrow.addPoint(arrowX + arrowSize, arrowY - 5); // Close the path
142
+ leftArrow.updatePath();
143
+ leftArrow.setFillColor(omdColor.mediumGray);
144
+ leftArrow.setStrokeWidth(0);
145
+ leftArrow.path.setAttribute("fill", omdColor.mediumGray);
146
+ this.addChild(leftArrow);
147
+ }
148
+
149
+ // Draw right arrow if needed
150
+ if (this.showRightArrow) {
151
+ // Cover the rounded corner with a rectangle
152
+ const coverRect = new jsvgRect();
153
+ coverRect.setWidthAndHeight(3, 5);
154
+ coverRect.setPosition(lineStartX + lineActualWidth - 3, 22.5);
155
+ coverRect.setFillColor(omdColor.mediumGray);
156
+ this.addChild(coverRect);
157
+
158
+ const rightArrow = new jsvgPath();
159
+ const arrowY = 25;
160
+ const arrowX = leftPadding + lineWidth - arrowSize; // Arrow tip position
161
+ rightArrow.addPoint(arrowX, arrowY - 5);
162
+ rightArrow.addPoint(arrowX + arrowSize, arrowY);
163
+ rightArrow.addPoint(arrowX, arrowY + 5);
164
+ rightArrow.addPoint(arrowX, arrowY - 5); // Close the path
165
+ rightArrow.updatePath();
166
+ rightArrow.setFillColor(omdColor.mediumGray);
167
+ rightArrow.setStrokeWidth(0);
168
+ rightArrow.path.setAttribute("fill", omdColor.mediumGray);
169
+ this.addChild(rightArrow);
170
+ }
171
+
172
+ // Collect all numbers that should be displayed
173
+ const numbersToShow = new Set();
174
+
175
+ // Add increment-based numbers if not hidden
176
+ if (!this.hideDefaultNumbers) {
177
+ for (let i = this.min; i <= this.max; i += this.increment) {
178
+ numbersToShow.add(i);
179
+ }
180
+ }
181
+
182
+ // Add special numbers
183
+ for (const num of this.specialNumbers) {
184
+ if (num >= this.min && num <= this.max) {
185
+ numbersToShow.add(num);
186
+ }
187
+ }
188
+
189
+ // Draw ticks and labels for all numbers
190
+ const sortedNumbers = Array.from(numbersToShow).sort((a, b) => a - b);
191
+ // Ticks are positioned with tickOverhang on both ends
192
+ const tickBaseX = lineStartX + tickOverhang;
193
+ const tickSpan = usableLineWidth - 2 * tickOverhang; // Space for ticks between the overhangs
194
+ for (const value of sortedNumbers) {
195
+ const normalized = (value - this.min) / (this.max - this.min);
196
+ const pX = tickBaseX + normalized * tickSpan;
197
+
198
+ // Draw tick
199
+ const tick = new jsvgLine();
200
+ tick.setStrokeColor("black");
201
+ tick.setStrokeWidth(1);
202
+ tick.setEndpoints(pX, 20, pX, 30);
203
+ this.addChild(tick);
204
+
205
+ // Draw label
206
+ const tickText = new jsvgTextBox();
207
+ tickText.setWidthAndHeight(40, 30);
208
+ tickText.setFontFamily("Albert Sans");
209
+ tickText.setFontColor("black");
210
+ tickText.setFontSize(10);
91
211
  tickText.setAlignment("center");
92
- tickText.setText( i.toString() );
93
- tickText.setPosition( pX-15, 32 );
94
- this.addChild( tickText );
212
+ const labelText = this.units ? `${value}${this.units}` : value.toString();
213
+ tickText.setText(labelText);
214
+ tickText.setPosition(pX - 20, 32);
215
+ this.addChild(tickText);
95
216
  }
96
217
 
97
- // make dots
98
- for( var i=0; i<this.dotValues.length; i++ )
99
- {
100
- var V = this.dotValues[i];
101
-
102
- var N = V - this.min;
103
- var dX = 300 / (this.max - this.min);
104
- var pX = 30 + N*dX;
105
-
106
- var dot = new jsvgEllipse();
107
- dot.setFillColor( "black" );
108
- dot.setStrokeWidth( 0 );
109
- dot.setWidthAndHeight( 9,9 );
110
- dot.setPosition( pX, 25 );
111
- this.addChild( dot );
218
+ // Draw dots
219
+ for (const V of this.dotValues) {
220
+ if (V < this.min || V > this.max) continue;
221
+
222
+ const normalized = (V - this.min) / (this.max - this.min);
223
+ const pX = tickBaseX + normalized * tickSpan;
224
+
225
+ const dot = new jsvgEllipse();
226
+ dot.setFillColor("black");
227
+ dot.setStrokeWidth(0);
228
+ dot.setWidthAndHeight(8, 8);
229
+ dot.setPosition(pX, 25);
230
+ this.addChild(dot);
112
231
  }
113
232
  }
114
233