@khanacademy/perseus-core 3.1.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/index.js +890 -376
- package/dist/es/index.js.map +1 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +894 -375
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +12 -0
- package/dist/utils/deep-clone.d.ts +3 -0
- package/dist/utils/equality.d.ts +9 -0
- package/dist/utils/get-decimal-separator.d.ts +5 -0
- package/dist/utils/get-matrix-size.d.ts +2 -0
- package/dist/utils/grapher-types.d.ts +58 -0
- package/dist/utils/grapher-util.d.ts +20 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -43,395 +43,103 @@ const addLibraryVersionToPerseusDebug = (libraryName, libraryVersion) => {
|
|
|
43
43
|
}
|
|
44
44
|
};
|
|
45
45
|
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
const libVersion = "3.1.0";
|
|
49
|
-
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
46
|
+
// Current version.
|
|
47
|
+
var VERSION = '1.13.3';
|
|
50
48
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
Unknown: "Unknown",
|
|
59
|
-
/**
|
|
60
|
-
* @property {ErrorKind} Internal The error is internal to the executing code.
|
|
61
|
-
*/
|
|
62
|
-
Internal: "Internal",
|
|
63
|
-
/**
|
|
64
|
-
* @property {ErrorKind} InvalidInput There was a problem with the provided
|
|
65
|
-
* input, such as the wrong format or a null value.
|
|
66
|
-
*/
|
|
67
|
-
InvalidInput: "InvalidInput",
|
|
68
|
-
/**
|
|
69
|
-
* @property {ErrorKind} NotAllowed There was a problem due to the state of
|
|
70
|
-
* the system not matching the requested operation or input. For example,
|
|
71
|
-
* trying to create a username that is valid, but is already taken by
|
|
72
|
-
* another user. Use {@link InvalidInput} instead when the input isn't
|
|
73
|
-
* valid regardless of the state of the system. Use {@link NotFound} when
|
|
74
|
-
* the failure is due to not being able to find a resource.
|
|
75
|
-
*/
|
|
76
|
-
NotAllowed: "NotAllowed",
|
|
77
|
-
/**
|
|
78
|
-
* @property {ErrorKind} TransientService There was a problem when making a
|
|
79
|
-
* request to a service.
|
|
80
|
-
*/
|
|
81
|
-
TransientService: "TransientService",
|
|
82
|
-
/**
|
|
83
|
-
* @property {ErrorKind} Service There was a non-transient problem when
|
|
84
|
-
* making a request to service.
|
|
85
|
-
*/
|
|
86
|
-
Service: "Service"
|
|
87
|
-
});
|
|
49
|
+
// Establish the root object, `window` (`self`) in the browser, `global`
|
|
50
|
+
// on the server, or `this` in some virtual machines. We use `self`
|
|
51
|
+
// instead of `window` for `WebWorker` support.
|
|
52
|
+
var root = (typeof self == 'object' && self.self === self && self) ||
|
|
53
|
+
(typeof global == 'object' && global.global === global && global) ||
|
|
54
|
+
Function('return this')() ||
|
|
55
|
+
{};
|
|
88
56
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
57
|
+
// Save bytes in the minified (but not gzipped) version:
|
|
58
|
+
var ArrayProto = Array.prototype, ObjProto = Object.prototype;
|
|
59
|
+
var SymbolProto = typeof Symbol !== 'undefined' ? Symbol.prototype : null;
|
|
92
60
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
this.kind = kind;
|
|
99
|
-
this.metadata = options?.metadata;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
61
|
+
// Create quick reference variables for speed access to core prototypes.
|
|
62
|
+
var push = ArrayProto.push,
|
|
63
|
+
slice = ArrayProto.slice,
|
|
64
|
+
toString = ObjProto.toString,
|
|
65
|
+
hasOwnProperty = ObjProto.hasOwnProperty;
|
|
102
66
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
* This file, and the types in it, represents the "data schema" that Perseus
|
|
107
|
-
* uses. The @khanacademy/perseus-editor package edits and produces objects
|
|
108
|
-
* that conform to the types in this file. Similarly, the top-level renderers
|
|
109
|
-
* in @khanacademy/perseus, consume objects that conform to these types.
|
|
110
|
-
*
|
|
111
|
-
* WARNING: This file should not import any types from elsewhere so that it is
|
|
112
|
-
* easy to reason about changes that alter the Perseus schema. This helps
|
|
113
|
-
* ensure that it is not changed accidentally when upgrading a dependant
|
|
114
|
-
* package or other part of Perseus code. Note that TypeScript does type
|
|
115
|
-
* checking via something called "structural typing". This means that as long
|
|
116
|
-
* as the shape of a type matches, the name it goes by doesn't matter. As a
|
|
117
|
-
* result, a `Coord` type that looks like this `[x: number, y: number]` is
|
|
118
|
-
* _identical_, in TypeScript's eyes, to this `Vector2` type `[x: number, y:
|
|
119
|
-
* number]`. Also, with tuples, the labels for each entry is ignored, so `[x:
|
|
120
|
-
* number, y: number]` is compatible with `[min: number, max: number]`. The
|
|
121
|
-
* labels are for humans, not TypeScript. :)
|
|
122
|
-
*
|
|
123
|
-
* If you make changes to types in this file, be very sure that:
|
|
124
|
-
*
|
|
125
|
-
* a) the changes are backwards compatible. If they are not, old data from
|
|
126
|
-
* previous versions of the "schema" could become unrenderable, or worse,
|
|
127
|
-
* introduce hard-to-diagnose bugs.
|
|
128
|
-
* b) the parsing code (`util/parse-perseus-json/`) is updated to handle
|
|
129
|
-
* the new format _as well as_ the old format.
|
|
130
|
-
*/
|
|
67
|
+
// Modern feature detection.
|
|
68
|
+
var supportsArrayBuffer = typeof ArrayBuffer !== 'undefined',
|
|
69
|
+
supportsDataView = typeof DataView !== 'undefined';
|
|
131
70
|
|
|
132
|
-
//
|
|
71
|
+
// All **ECMAScript 5+** native function implementations that we hope to use
|
|
72
|
+
// are declared here.
|
|
73
|
+
var nativeIsArray = Array.isArray,
|
|
74
|
+
nativeKeys = Object.keys,
|
|
75
|
+
nativeCreate = Object.create,
|
|
76
|
+
nativeIsView = supportsArrayBuffer && ArrayBuffer.isView;
|
|
133
77
|
|
|
134
|
-
//
|
|
78
|
+
// Create references to these builtin functions because we override them.
|
|
79
|
+
var _isNaN = isNaN,
|
|
80
|
+
_isFinite = isFinite;
|
|
135
81
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
* There should be one key/value pair for each supported widget. If you create
|
|
141
|
-
* a new widget, an entry should be added to this interface. Note that this
|
|
142
|
-
* only registers the widget options type, you'll also need to register the
|
|
143
|
-
* widget so that it's available at runtime (@see
|
|
144
|
-
* {@link file://./widgets.ts#registerWidget}).
|
|
145
|
-
*
|
|
146
|
-
* Importantly, the key should be the name that is used in widget IDs. For most
|
|
147
|
-
* widgets that is the same as the widget option's `type` field. In cases where
|
|
148
|
-
* a widget has been deprecated and replaced with the deprecated-standin
|
|
149
|
-
* widget, it should be the original widget type!
|
|
150
|
-
*
|
|
151
|
-
* If you define the widget outside of this package, you can still add the new
|
|
152
|
-
* widget to this interface by writing the following in that package that
|
|
153
|
-
* contains the widget. TypeScript will merge that definition of the
|
|
154
|
-
* `PerseusWidgets` with the one defined below.
|
|
155
|
-
*
|
|
156
|
-
* ```typescript
|
|
157
|
-
* declare module "@khanacademy/perseus" {
|
|
158
|
-
* interface PerseusWidgetTypes {
|
|
159
|
-
* // A new widget
|
|
160
|
-
* "new-awesomeness": MyAwesomeNewWidget;
|
|
161
|
-
*
|
|
162
|
-
* // A deprecated widget
|
|
163
|
-
* "super-old-widget": DeprecatedStandinWidget;
|
|
164
|
-
* }
|
|
165
|
-
* }
|
|
166
|
-
*
|
|
167
|
-
* // The new widget's options definition
|
|
168
|
-
* type MyAwesomeNewWidget = WidgetOptions<'new-awesomeness', MyAwesomeNewWidgetOptions>;
|
|
169
|
-
*
|
|
170
|
-
* // The deprecated widget's options definition
|
|
171
|
-
* type SuperOldWidget = WidgetOptions<'super-old-widget', object>;
|
|
172
|
-
* ```
|
|
173
|
-
*
|
|
174
|
-
* This interface can be extended through the magic of TypeScript "Declaration
|
|
175
|
-
* merging". Specifically, we augment this module and extend this interface.
|
|
176
|
-
*
|
|
177
|
-
* @see {@link https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation}
|
|
178
|
-
*/
|
|
82
|
+
// Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed.
|
|
83
|
+
var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString');
|
|
84
|
+
var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString',
|
|
85
|
+
'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString'];
|
|
179
86
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
* for a set of widgets defined in a `PerseusItem` but can also be useful to
|
|
183
|
-
* represent a function parameter where only `widgets` from a `PerseusItem` are
|
|
184
|
-
* needed. Today Widget IDs are made up of the widget type and an incrementing
|
|
185
|
-
* integer (eg. `interactive-graph 1` or `radio 3`). It is suggested to avoid
|
|
186
|
-
* reading/parsing the widget id to derive any information from it, except in
|
|
187
|
-
* the case of this map.
|
|
188
|
-
*
|
|
189
|
-
* @see {@link PerseusWidgetTypes} additional widgets can be added to this map type
|
|
190
|
-
* by augmenting the PerseusWidgetTypes with new widget types!
|
|
191
|
-
*/
|
|
87
|
+
// The largest integer that can be represented exactly.
|
|
88
|
+
var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
|
|
192
89
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
90
|
+
// Some functions take a variable number of arguments, or a few expected
|
|
91
|
+
// arguments at the beginning and then a variable number of values to operate
|
|
92
|
+
// on. This helper accumulates all remaining arguments past the function’s
|
|
93
|
+
// argument length (or an explicit `startIndex`), into an array that becomes
|
|
94
|
+
// the last argument. Similar to ES6’s "rest parameter".
|
|
95
|
+
function restArguments(func, startIndex) {
|
|
96
|
+
startIndex = startIndex == null ? func.length - 1 : +startIndex;
|
|
97
|
+
return function() {
|
|
98
|
+
var length = Math.max(arguments.length - startIndex, 0),
|
|
99
|
+
rest = Array(length),
|
|
100
|
+
index = 0;
|
|
101
|
+
for (; index < length; index++) {
|
|
102
|
+
rest[index] = arguments[index + startIndex];
|
|
103
|
+
}
|
|
104
|
+
switch (startIndex) {
|
|
105
|
+
case 0: return func.call(this, rest);
|
|
106
|
+
case 1: return func.call(this, arguments[0], rest);
|
|
107
|
+
case 2: return func.call(this, arguments[0], arguments[1], rest);
|
|
108
|
+
}
|
|
109
|
+
var args = Array(startIndex + 1);
|
|
110
|
+
for (index = 0; index < startIndex; index++) {
|
|
111
|
+
args[index] = arguments[index];
|
|
112
|
+
}
|
|
113
|
+
args[startIndex] = rest;
|
|
114
|
+
return func.apply(this, args);
|
|
115
|
+
};
|
|
116
|
+
}
|
|
200
117
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
118
|
+
// Is a given variable an object?
|
|
119
|
+
function isObject(obj) {
|
|
120
|
+
var type = typeof obj;
|
|
121
|
+
return type === 'function' || (type === 'object' && !!obj);
|
|
122
|
+
}
|
|
205
123
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
"chi2Table",
|
|
211
|
-
// The user might benefit from a monthly payments calculator. Provided on Khan Academy when true
|
|
212
|
-
"financialCalculatorMonthlyPayment",
|
|
213
|
-
// The user might benefit from a total amount calculator. Provided on Khan Academy when true
|
|
214
|
-
"financialCalculatorTotalAmount",
|
|
215
|
-
// The user might benefit from a time to pay off calculator. Provided on Khan Academy when true
|
|
216
|
-
"financialCalculatorTimeToPayOff",
|
|
217
|
-
// The user might benefit from using a Periodic Table of Elements. Provided on Khan Academy when true
|
|
218
|
-
"periodicTable",
|
|
219
|
-
// The user might benefit from using a Periodic Table of Elements with key. Provided on Khan Academy when true
|
|
220
|
-
"periodicTableWithKey",
|
|
221
|
-
// The user might benefit from using a statistics T Table like https://www.statisticshowto.com/tables/t-distribution-table/
|
|
222
|
-
"tTable",
|
|
223
|
-
// The user might benefit from using a statistics Z Table like https://www.ztable.net/
|
|
224
|
-
"zTable"];
|
|
124
|
+
// Is a given value equal to null?
|
|
125
|
+
function isNull(obj) {
|
|
126
|
+
return obj === null;
|
|
127
|
+
}
|
|
225
128
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
129
|
+
// Is a given variable undefined?
|
|
130
|
+
function isUndefined(obj) {
|
|
131
|
+
return obj === void 0;
|
|
132
|
+
}
|
|
230
133
|
|
|
231
|
-
//
|
|
134
|
+
// Is a given value a boolean?
|
|
135
|
+
function isBoolean(obj) {
|
|
136
|
+
return obj === true || obj === false || toString.call(obj) === '[object Boolean]';
|
|
137
|
+
}
|
|
232
138
|
|
|
233
|
-
//
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
// prettier-ignore
|
|
238
|
-
|
|
239
|
-
// prettier-ignore
|
|
240
|
-
|
|
241
|
-
// prettier-ignore
|
|
242
|
-
|
|
243
|
-
// prettier-ignore
|
|
244
|
-
|
|
245
|
-
// prettier-ignore
|
|
246
|
-
|
|
247
|
-
// prettier-ignore
|
|
248
|
-
|
|
249
|
-
// prettier-ignore
|
|
250
|
-
|
|
251
|
-
// prettier-ignore
|
|
252
|
-
|
|
253
|
-
// prettier-ignore
|
|
254
|
-
|
|
255
|
-
// prettier-ignore
|
|
256
|
-
|
|
257
|
-
// prettier-ignore
|
|
258
|
-
|
|
259
|
-
// prettier-ignore
|
|
260
|
-
|
|
261
|
-
// prettier-ignore
|
|
262
|
-
|
|
263
|
-
// prettier-ignore
|
|
264
|
-
|
|
265
|
-
// prettier-ignore
|
|
266
|
-
|
|
267
|
-
// prettier-ignore
|
|
268
|
-
|
|
269
|
-
// prettier-ignore
|
|
270
|
-
|
|
271
|
-
// prettier-ignore
|
|
272
|
-
|
|
273
|
-
// prettier-ignore
|
|
274
|
-
|
|
275
|
-
// prettier-ignore
|
|
276
|
-
|
|
277
|
-
// prettier-ignore
|
|
278
|
-
|
|
279
|
-
// prettier-ignore
|
|
280
|
-
|
|
281
|
-
// prettier-ignore
|
|
282
|
-
|
|
283
|
-
// prettier-ignore
|
|
284
|
-
|
|
285
|
-
// prettier-ignore
|
|
286
|
-
|
|
287
|
-
// prettier-ignore
|
|
288
|
-
|
|
289
|
-
// prettier-ignore
|
|
290
|
-
|
|
291
|
-
// prettier-ignore
|
|
292
|
-
|
|
293
|
-
// prettier-ignore
|
|
294
|
-
|
|
295
|
-
// prettier-ignore
|
|
296
|
-
|
|
297
|
-
// prettier-ignore
|
|
298
|
-
|
|
299
|
-
//prettier-ignore
|
|
300
|
-
|
|
301
|
-
/**
|
|
302
|
-
* A background image applied to various widgets.
|
|
303
|
-
*/
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
* The type of markings to display on the graph.
|
|
307
|
-
* - axes: shows the axes without the gride lines
|
|
308
|
-
* - graph: shows the axes and the grid lines
|
|
309
|
-
* - grid: shows only the grid lines
|
|
310
|
-
* - none: shows no markings
|
|
311
|
-
*/
|
|
312
|
-
|
|
313
|
-
const PerseusExpressionAnswerFormConsidered = ["correct", "wrong", "ungraded"];
|
|
314
|
-
|
|
315
|
-
// 2D range: xMin, xMax, yMin, yMax
|
|
316
|
-
|
|
317
|
-
const lockedFigureColorNames = ["blue", "green", "grayH", "purple", "pink", "orange", "red"];
|
|
318
|
-
const lockedFigureColors = {
|
|
319
|
-
blue: "#3D7586",
|
|
320
|
-
green: "#447A53",
|
|
321
|
-
grayH: "#3B3D45",
|
|
322
|
-
purple: "#594094",
|
|
323
|
-
pink: "#B25071",
|
|
324
|
-
red: "#D92916",
|
|
325
|
-
orange: "#946700"
|
|
326
|
-
};
|
|
327
|
-
const lockedFigureFillStyles = {
|
|
328
|
-
none: 0,
|
|
329
|
-
white: 1,
|
|
330
|
-
translucent: 0.4,
|
|
331
|
-
solid: 1
|
|
332
|
-
};
|
|
333
|
-
|
|
334
|
-
// Not associated with a specific figure
|
|
335
|
-
|
|
336
|
-
const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
|
|
337
|
-
|
|
338
|
-
// Current version.
|
|
339
|
-
var VERSION = '1.13.3';
|
|
340
|
-
|
|
341
|
-
// Establish the root object, `window` (`self`) in the browser, `global`
|
|
342
|
-
// on the server, or `this` in some virtual machines. We use `self`
|
|
343
|
-
// instead of `window` for `WebWorker` support.
|
|
344
|
-
var root = (typeof self == 'object' && self.self === self && self) ||
|
|
345
|
-
(typeof global == 'object' && global.global === global && global) ||
|
|
346
|
-
Function('return this')() ||
|
|
347
|
-
{};
|
|
348
|
-
|
|
349
|
-
// Save bytes in the minified (but not gzipped) version:
|
|
350
|
-
var ArrayProto = Array.prototype, ObjProto = Object.prototype;
|
|
351
|
-
var SymbolProto = typeof Symbol !== 'undefined' ? Symbol.prototype : null;
|
|
352
|
-
|
|
353
|
-
// Create quick reference variables for speed access to core prototypes.
|
|
354
|
-
var push = ArrayProto.push,
|
|
355
|
-
slice = ArrayProto.slice,
|
|
356
|
-
toString = ObjProto.toString,
|
|
357
|
-
hasOwnProperty = ObjProto.hasOwnProperty;
|
|
358
|
-
|
|
359
|
-
// Modern feature detection.
|
|
360
|
-
var supportsArrayBuffer = typeof ArrayBuffer !== 'undefined',
|
|
361
|
-
supportsDataView = typeof DataView !== 'undefined';
|
|
362
|
-
|
|
363
|
-
// All **ECMAScript 5+** native function implementations that we hope to use
|
|
364
|
-
// are declared here.
|
|
365
|
-
var nativeIsArray = Array.isArray,
|
|
366
|
-
nativeKeys = Object.keys,
|
|
367
|
-
nativeCreate = Object.create,
|
|
368
|
-
nativeIsView = supportsArrayBuffer && ArrayBuffer.isView;
|
|
369
|
-
|
|
370
|
-
// Create references to these builtin functions because we override them.
|
|
371
|
-
var _isNaN = isNaN,
|
|
372
|
-
_isFinite = isFinite;
|
|
373
|
-
|
|
374
|
-
// Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed.
|
|
375
|
-
var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString');
|
|
376
|
-
var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString',
|
|
377
|
-
'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString'];
|
|
378
|
-
|
|
379
|
-
// The largest integer that can be represented exactly.
|
|
380
|
-
var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
|
|
381
|
-
|
|
382
|
-
// Some functions take a variable number of arguments, or a few expected
|
|
383
|
-
// arguments at the beginning and then a variable number of values to operate
|
|
384
|
-
// on. This helper accumulates all remaining arguments past the function’s
|
|
385
|
-
// argument length (or an explicit `startIndex`), into an array that becomes
|
|
386
|
-
// the last argument. Similar to ES6’s "rest parameter".
|
|
387
|
-
function restArguments(func, startIndex) {
|
|
388
|
-
startIndex = startIndex == null ? func.length - 1 : +startIndex;
|
|
389
|
-
return function() {
|
|
390
|
-
var length = Math.max(arguments.length - startIndex, 0),
|
|
391
|
-
rest = Array(length),
|
|
392
|
-
index = 0;
|
|
393
|
-
for (; index < length; index++) {
|
|
394
|
-
rest[index] = arguments[index + startIndex];
|
|
395
|
-
}
|
|
396
|
-
switch (startIndex) {
|
|
397
|
-
case 0: return func.call(this, rest);
|
|
398
|
-
case 1: return func.call(this, arguments[0], rest);
|
|
399
|
-
case 2: return func.call(this, arguments[0], arguments[1], rest);
|
|
400
|
-
}
|
|
401
|
-
var args = Array(startIndex + 1);
|
|
402
|
-
for (index = 0; index < startIndex; index++) {
|
|
403
|
-
args[index] = arguments[index];
|
|
404
|
-
}
|
|
405
|
-
args[startIndex] = rest;
|
|
406
|
-
return func.apply(this, args);
|
|
407
|
-
};
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
// Is a given variable an object?
|
|
411
|
-
function isObject(obj) {
|
|
412
|
-
var type = typeof obj;
|
|
413
|
-
return type === 'function' || (type === 'object' && !!obj);
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
// Is a given value equal to null?
|
|
417
|
-
function isNull(obj) {
|
|
418
|
-
return obj === null;
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
// Is a given variable undefined?
|
|
422
|
-
function isUndefined(obj) {
|
|
423
|
-
return obj === void 0;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
// Is a given value a boolean?
|
|
427
|
-
function isBoolean(obj) {
|
|
428
|
-
return obj === true || obj === false || toString.call(obj) === '[object Boolean]';
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
// Is a given value a DOM element?
|
|
432
|
-
function isElement(obj) {
|
|
433
|
-
return !!(obj && obj.nodeType === 1);
|
|
434
|
-
}
|
|
139
|
+
// Is a given value a DOM element?
|
|
140
|
+
function isElement(obj) {
|
|
141
|
+
return !!(obj && obj.nodeType === 1);
|
|
142
|
+
}
|
|
435
143
|
|
|
436
144
|
// Internal function for creating a `toString`-based type tester.
|
|
437
145
|
function tagTester(name) {
|
|
@@ -2359,6 +2067,811 @@ var _ = mixin(allExports);
|
|
|
2359
2067
|
// Legacy Node.js API.
|
|
2360
2068
|
_._ = _;
|
|
2361
2069
|
|
|
2070
|
+
function getMatrixSize(matrix) {
|
|
2071
|
+
const matrixSize = [1, 1];
|
|
2072
|
+
|
|
2073
|
+
// We need to find the widest row and tallest column to get the correct
|
|
2074
|
+
// matrix size.
|
|
2075
|
+
_(matrix).each((matrixRow, row) => {
|
|
2076
|
+
let rowWidth = 0;
|
|
2077
|
+
_(matrixRow).each((matrixCol, col) => {
|
|
2078
|
+
if (matrixCol != null && matrixCol.toString().length) {
|
|
2079
|
+
rowWidth = col + 1;
|
|
2080
|
+
}
|
|
2081
|
+
});
|
|
2082
|
+
|
|
2083
|
+
// Matrix width:
|
|
2084
|
+
matrixSize[1] = Math.max(matrixSize[1], rowWidth);
|
|
2085
|
+
|
|
2086
|
+
// Matrix height:
|
|
2087
|
+
if (rowWidth > 0) {
|
|
2088
|
+
matrixSize[0] = Math.max(matrixSize[0], row + 1);
|
|
2089
|
+
}
|
|
2090
|
+
});
|
|
2091
|
+
return matrixSize;
|
|
2092
|
+
}
|
|
2093
|
+
|
|
2094
|
+
/**
|
|
2095
|
+
* Get the character used for separating decimals.
|
|
2096
|
+
*/
|
|
2097
|
+
const getDecimalSeparator = locale => {
|
|
2098
|
+
switch (locale) {
|
|
2099
|
+
// TODO(somewhatabstract): Remove this when Chrome supports the `ka`
|
|
2100
|
+
// locale properly.
|
|
2101
|
+
// https://github.com/formatjs/formatjs/issues/1526#issuecomment-559891201
|
|
2102
|
+
//
|
|
2103
|
+
// Supported locales in Chrome:
|
|
2104
|
+
// https://source.chromium.org/chromium/chromium/src/+/master:third_party/icu/scripts/chrome_ui_languages.list
|
|
2105
|
+
case "ka":
|
|
2106
|
+
return ",";
|
|
2107
|
+
default:
|
|
2108
|
+
const numberWithDecimalSeparator = 1.1;
|
|
2109
|
+
// TODO(FEI-3647): Update to use .formatToParts() once we no longer have to
|
|
2110
|
+
// support Safari 12.
|
|
2111
|
+
const match = new Intl.NumberFormat(locale).format(numberWithDecimalSeparator)
|
|
2112
|
+
// 0x661 is ARABIC-INDIC DIGIT ONE
|
|
2113
|
+
// 0x6F1 is EXTENDED ARABIC-INDIC DIGIT ONE
|
|
2114
|
+
.match(/[^\d\u0661\u06F1]/);
|
|
2115
|
+
return match?.[0] ?? ".";
|
|
2116
|
+
}
|
|
2117
|
+
};
|
|
2118
|
+
|
|
2119
|
+
/**
|
|
2120
|
+
* APPROXIMATE equality on numbers and primitives.
|
|
2121
|
+
*/
|
|
2122
|
+
function approximateEqual(x, y) {
|
|
2123
|
+
if (typeof x === "number" && typeof y === "number") {
|
|
2124
|
+
return Math.abs(x - y) < 1e-9;
|
|
2125
|
+
}
|
|
2126
|
+
return x === y;
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
/**
|
|
2130
|
+
* Deep APPROXIMATE equality on primitives, numbers, arrays, and objects.
|
|
2131
|
+
* Recursive.
|
|
2132
|
+
*/
|
|
2133
|
+
function approximateDeepEqual(x, y) {
|
|
2134
|
+
if (Array.isArray(x) && Array.isArray(y)) {
|
|
2135
|
+
if (x.length !== y.length) {
|
|
2136
|
+
return false;
|
|
2137
|
+
}
|
|
2138
|
+
for (let i = 0; i < x.length; i++) {
|
|
2139
|
+
if (!approximateDeepEqual(x[i], y[i])) {
|
|
2140
|
+
return false;
|
|
2141
|
+
}
|
|
2142
|
+
}
|
|
2143
|
+
return true;
|
|
2144
|
+
}
|
|
2145
|
+
if (Array.isArray(x) || Array.isArray(y)) {
|
|
2146
|
+
return false;
|
|
2147
|
+
}
|
|
2148
|
+
if (typeof x === "function" && typeof y === "function") {
|
|
2149
|
+
return approximateEqual(x, y);
|
|
2150
|
+
}
|
|
2151
|
+
if (typeof x === "function" || typeof y === "function") {
|
|
2152
|
+
return false;
|
|
2153
|
+
}
|
|
2154
|
+
if (typeof x === "object" && typeof y === "object" && !!x && !!y) {
|
|
2155
|
+
return x === y || _.all(x, function (v, k) {
|
|
2156
|
+
// @ts-expect-error - TS2536 - Type 'CollectionKey<T>' cannot be used to index type 'T'.
|
|
2157
|
+
return approximateDeepEqual(y[k], v);
|
|
2158
|
+
}) && _.all(y, function (v, k) {
|
|
2159
|
+
// @ts-expect-error - TS2536 - Type 'CollectionKey<T>' cannot be used to index type 'T'.
|
|
2160
|
+
return approximateDeepEqual(x[k], v);
|
|
2161
|
+
});
|
|
2162
|
+
}
|
|
2163
|
+
if (typeof x === "object" && !!x || typeof y === "object" && !!y) {
|
|
2164
|
+
return false;
|
|
2165
|
+
}
|
|
2166
|
+
return approximateEqual(x, y);
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
// TODO(benchristel): in the future, we may want to make deepClone work for
|
|
2170
|
+
// Record<string, Cloneable> as well. Currently, it only does arrays.
|
|
2171
|
+
|
|
2172
|
+
function deepClone(obj) {
|
|
2173
|
+
if (Array.isArray(obj)) {
|
|
2174
|
+
return obj.map(deepClone);
|
|
2175
|
+
}
|
|
2176
|
+
return obj;
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
const MOVABLES = {
|
|
2180
|
+
PLOT: "PLOT",
|
|
2181
|
+
PARABOLA: "PARABOLA",
|
|
2182
|
+
SINUSOID: "SINUSOID"
|
|
2183
|
+
};
|
|
2184
|
+
|
|
2185
|
+
// TODO(charlie): These really need to go into a utility file as they're being
|
|
2186
|
+
// used by both interactive-graph and now grapher.
|
|
2187
|
+
function canonicalSineCoefficients(coeffs) {
|
|
2188
|
+
// For a curve of the form f(x) = a * Sin(b * x - c) + d,
|
|
2189
|
+
// this function ensures that a, b > 0, and c is its
|
|
2190
|
+
// smallest possible positive value.
|
|
2191
|
+
let amplitude = coeffs[0];
|
|
2192
|
+
let angularFrequency = coeffs[1];
|
|
2193
|
+
let phase = coeffs[2];
|
|
2194
|
+
const verticalOffset = coeffs[3];
|
|
2195
|
+
|
|
2196
|
+
// Guarantee a > 0
|
|
2197
|
+
if (amplitude < 0) {
|
|
2198
|
+
amplitude *= -1;
|
|
2199
|
+
angularFrequency *= -1;
|
|
2200
|
+
phase *= -1;
|
|
2201
|
+
}
|
|
2202
|
+
const period = 2 * Math.PI;
|
|
2203
|
+
// Guarantee b > 0
|
|
2204
|
+
if (angularFrequency < 0) {
|
|
2205
|
+
angularFrequency *= -1;
|
|
2206
|
+
phase *= -1;
|
|
2207
|
+
phase += period / 2;
|
|
2208
|
+
}
|
|
2209
|
+
|
|
2210
|
+
// Guarantee c is smallest possible positive value
|
|
2211
|
+
while (phase > 0) {
|
|
2212
|
+
phase -= period;
|
|
2213
|
+
}
|
|
2214
|
+
while (phase < 0) {
|
|
2215
|
+
phase += period;
|
|
2216
|
+
}
|
|
2217
|
+
return [amplitude, angularFrequency, phase, verticalOffset];
|
|
2218
|
+
}
|
|
2219
|
+
function canonicalTangentCoefficients(coeffs) {
|
|
2220
|
+
// For a curve of the form f(x) = a * Tan(b * x - c) + d,
|
|
2221
|
+
// this function ensures that a, b > 0, and c is its
|
|
2222
|
+
// smallest possible positive value.
|
|
2223
|
+
let amplitude = coeffs[0];
|
|
2224
|
+
let angularFrequency = coeffs[1];
|
|
2225
|
+
let phase = coeffs[2];
|
|
2226
|
+
const verticalOffset = coeffs[3];
|
|
2227
|
+
|
|
2228
|
+
// Guarantee a > 0
|
|
2229
|
+
if (amplitude < 0) {
|
|
2230
|
+
amplitude *= -1;
|
|
2231
|
+
angularFrequency *= -1;
|
|
2232
|
+
phase *= -1;
|
|
2233
|
+
}
|
|
2234
|
+
const period = Math.PI;
|
|
2235
|
+
// Guarantee b > 0
|
|
2236
|
+
if (angularFrequency < 0) {
|
|
2237
|
+
angularFrequency *= -1;
|
|
2238
|
+
phase *= -1;
|
|
2239
|
+
phase += period / 2;
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2242
|
+
// Guarantee c is smallest possible positive value
|
|
2243
|
+
while (phase > 0) {
|
|
2244
|
+
phase -= period;
|
|
2245
|
+
}
|
|
2246
|
+
while (phase < 0) {
|
|
2247
|
+
phase += period;
|
|
2248
|
+
}
|
|
2249
|
+
return [amplitude, angularFrequency, phase, verticalOffset];
|
|
2250
|
+
}
|
|
2251
|
+
const PlotDefaults = {
|
|
2252
|
+
areEqual: function (coeffs1, coeffs2) {
|
|
2253
|
+
return approximateDeepEqual(coeffs1, coeffs2);
|
|
2254
|
+
},
|
|
2255
|
+
movable: MOVABLES.PLOT,
|
|
2256
|
+
getPropsForCoeffs: function (coeffs) {
|
|
2257
|
+
return {
|
|
2258
|
+
// @ts-expect-error - TS2339 - Property 'getFunctionForCoeffs' does not exist on type '{ readonly areEqual: (coeffs1: any, coeffs2: any) => boolean; readonly Movable: any; readonly getPropsForCoeffs: (coeffs: any) => any; }'.
|
|
2259
|
+
fn: _.partial(this.getFunctionForCoeffs, coeffs)
|
|
2260
|
+
};
|
|
2261
|
+
}
|
|
2262
|
+
};
|
|
2263
|
+
const Linear = _.extend({}, PlotDefaults, {
|
|
2264
|
+
url: "https://ka-perseus-graphie.s3.amazonaws.com/67aaf581e6d9ef9038c10558a1f70ac21c11c9f8.png",
|
|
2265
|
+
defaultCoords: [[0.25, 0.75], [0.75, 0.75]],
|
|
2266
|
+
getCoefficients: function (coords) {
|
|
2267
|
+
const p1 = coords[0];
|
|
2268
|
+
const p2 = coords[1];
|
|
2269
|
+
const denom = p2[0] - p1[0];
|
|
2270
|
+
const num = p2[1] - p1[1];
|
|
2271
|
+
if (denom === 0) {
|
|
2272
|
+
return;
|
|
2273
|
+
}
|
|
2274
|
+
const m = num / denom;
|
|
2275
|
+
const b = p2[1] - m * p2[0];
|
|
2276
|
+
return [m, b];
|
|
2277
|
+
},
|
|
2278
|
+
getFunctionForCoeffs: function (coeffs, x) {
|
|
2279
|
+
const m = coeffs[0];
|
|
2280
|
+
const b = coeffs[1];
|
|
2281
|
+
return m * x + b;
|
|
2282
|
+
},
|
|
2283
|
+
getEquationString: function (coords) {
|
|
2284
|
+
const coeffs = this.getCoefficients(coords);
|
|
2285
|
+
const m = coeffs[0];
|
|
2286
|
+
const b = coeffs[1];
|
|
2287
|
+
return "y = " + m.toFixed(3) + "x + " + b.toFixed(3);
|
|
2288
|
+
}
|
|
2289
|
+
});
|
|
2290
|
+
const Quadratic = _.extend({}, PlotDefaults, {
|
|
2291
|
+
url: "https://ka-perseus-graphie.s3.amazonaws.com/e23d36e6fc29ee37174e92c9daba2a66677128ab.png",
|
|
2292
|
+
defaultCoords: [[0.5, 0.5], [0.75, 0.75]],
|
|
2293
|
+
movable: MOVABLES.PARABOLA,
|
|
2294
|
+
getCoefficients: function (coords) {
|
|
2295
|
+
const p1 = coords[0];
|
|
2296
|
+
const p2 = coords[1];
|
|
2297
|
+
|
|
2298
|
+
// Parabola with vertex (h, k) has form: y = a * (h - k)^2 + k
|
|
2299
|
+
const h = p1[0];
|
|
2300
|
+
const k = p1[1];
|
|
2301
|
+
|
|
2302
|
+
// Use these to calculate familiar a, b, c
|
|
2303
|
+
const a = (p2[1] - k) / ((p2[0] - h) * (p2[0] - h));
|
|
2304
|
+
const b = -2 * h * a;
|
|
2305
|
+
const c = a * h * h + k;
|
|
2306
|
+
return [a, b, c];
|
|
2307
|
+
},
|
|
2308
|
+
getFunctionForCoeffs: function (coeffs, x) {
|
|
2309
|
+
const a = coeffs[0];
|
|
2310
|
+
const b = coeffs[1];
|
|
2311
|
+
const c = coeffs[2];
|
|
2312
|
+
return (a * x + b) * x + c;
|
|
2313
|
+
},
|
|
2314
|
+
getPropsForCoeffs: function (coeffs) {
|
|
2315
|
+
return {
|
|
2316
|
+
a: coeffs[0],
|
|
2317
|
+
b: coeffs[1],
|
|
2318
|
+
c: coeffs[2]
|
|
2319
|
+
};
|
|
2320
|
+
},
|
|
2321
|
+
getEquationString: function (coords) {
|
|
2322
|
+
const coeffs = this.getCoefficients(coords);
|
|
2323
|
+
const a = coeffs[0];
|
|
2324
|
+
const b = coeffs[1];
|
|
2325
|
+
const c = coeffs[2];
|
|
2326
|
+
return "y = " + a.toFixed(3) + "x^2 + " + b.toFixed(3) + "x + " + c.toFixed(3);
|
|
2327
|
+
}
|
|
2328
|
+
});
|
|
2329
|
+
const Sinusoid = _.extend({}, PlotDefaults, {
|
|
2330
|
+
url: "https://ka-perseus-graphie.s3.amazonaws.com/3d68e7718498475f53b206c2ab285626baf8857e.png",
|
|
2331
|
+
defaultCoords: [[0.5, 0.5], [0.6, 0.6]],
|
|
2332
|
+
movable: MOVABLES.SINUSOID,
|
|
2333
|
+
getCoefficients: function (coords) {
|
|
2334
|
+
const p1 = coords[0];
|
|
2335
|
+
const p2 = coords[1];
|
|
2336
|
+
const a = p2[1] - p1[1];
|
|
2337
|
+
const b = Math.PI / (2 * (p2[0] - p1[0]));
|
|
2338
|
+
const c = p1[0] * b;
|
|
2339
|
+
const d = p1[1];
|
|
2340
|
+
return [a, b, c, d];
|
|
2341
|
+
},
|
|
2342
|
+
getFunctionForCoeffs: function (coeffs, x) {
|
|
2343
|
+
const a = coeffs[0];
|
|
2344
|
+
const b = coeffs[1];
|
|
2345
|
+
const c = coeffs[2];
|
|
2346
|
+
const d = coeffs[3];
|
|
2347
|
+
return a * Math.sin(b * x - c) + d;
|
|
2348
|
+
},
|
|
2349
|
+
getPropsForCoeffs: function (coeffs) {
|
|
2350
|
+
return {
|
|
2351
|
+
a: coeffs[0],
|
|
2352
|
+
b: coeffs[1],
|
|
2353
|
+
c: coeffs[2],
|
|
2354
|
+
d: coeffs[3]
|
|
2355
|
+
};
|
|
2356
|
+
},
|
|
2357
|
+
getEquationString: function (coords) {
|
|
2358
|
+
const coeffs = this.getCoefficients(coords);
|
|
2359
|
+
const a = coeffs[0];
|
|
2360
|
+
const b = coeffs[1];
|
|
2361
|
+
const c = coeffs[2];
|
|
2362
|
+
const d = coeffs[3];
|
|
2363
|
+
return "y = " + a.toFixed(3) + " sin(" + b.toFixed(3) + "x - " + c.toFixed(3) + ") + " + d.toFixed(3);
|
|
2364
|
+
},
|
|
2365
|
+
areEqual: function (coeffs1, coeffs2) {
|
|
2366
|
+
return approximateDeepEqual(canonicalSineCoefficients(coeffs1), canonicalSineCoefficients(coeffs2));
|
|
2367
|
+
}
|
|
2368
|
+
});
|
|
2369
|
+
const Tangent = _.extend({}, PlotDefaults, {
|
|
2370
|
+
url: "https://ka-perseus-graphie.s3.amazonaws.com/7db80d23c35214f98659fe1cf0765811c1bbfbba.png",
|
|
2371
|
+
defaultCoords: [[0.5, 0.5], [0.75, 0.75]],
|
|
2372
|
+
getCoefficients: function (coords) {
|
|
2373
|
+
const p1 = coords[0];
|
|
2374
|
+
const p2 = coords[1];
|
|
2375
|
+
const a = p2[1] - p1[1];
|
|
2376
|
+
const b = Math.PI / (4 * (p2[0] - p1[0]));
|
|
2377
|
+
const c = p1[0] * b;
|
|
2378
|
+
const d = p1[1];
|
|
2379
|
+
return [a, b, c, d];
|
|
2380
|
+
},
|
|
2381
|
+
getFunctionForCoeffs: function (coeffs, x) {
|
|
2382
|
+
const a = coeffs[0];
|
|
2383
|
+
const b = coeffs[1];
|
|
2384
|
+
const c = coeffs[2];
|
|
2385
|
+
const d = coeffs[3];
|
|
2386
|
+
return a * Math.tan(b * x - c) + d;
|
|
2387
|
+
},
|
|
2388
|
+
getEquationString: function (coords) {
|
|
2389
|
+
const coeffs = this.getCoefficients(coords);
|
|
2390
|
+
const a = coeffs[0];
|
|
2391
|
+
const b = coeffs[1];
|
|
2392
|
+
const c = coeffs[2];
|
|
2393
|
+
const d = coeffs[3];
|
|
2394
|
+
return "y = " + a.toFixed(3) + " sin(" + b.toFixed(3) + "x - " + c.toFixed(3) + ") + " + d.toFixed(3);
|
|
2395
|
+
},
|
|
2396
|
+
areEqual: function (coeffs1, coeffs2) {
|
|
2397
|
+
return approximateDeepEqual(canonicalTangentCoefficients(coeffs1), canonicalTangentCoefficients(coeffs2));
|
|
2398
|
+
}
|
|
2399
|
+
});
|
|
2400
|
+
const Exponential = _.extend({}, PlotDefaults, {
|
|
2401
|
+
url: "https://ka-perseus-graphie.s3.amazonaws.com/9cbfad55525e3ce755a31a631b074670a5dad611.png",
|
|
2402
|
+
defaultCoords: [[0.5, 0.55], [0.75, 0.75]],
|
|
2403
|
+
defaultAsymptote: [[0, 0.5], [1.0, 0.5]],
|
|
2404
|
+
/**
|
|
2405
|
+
* Add extra constraints for movement of the points or asymptote (below):
|
|
2406
|
+
* newCoord: [x, y]
|
|
2407
|
+
* The end position of the point or asymptote endpoint
|
|
2408
|
+
* oldCoord: [x, y]
|
|
2409
|
+
* The old position of the point or asymptote endpoint
|
|
2410
|
+
* coords:
|
|
2411
|
+
* An array of coordinates representing the proposed end configuration
|
|
2412
|
+
* of the plot coordinates.
|
|
2413
|
+
* asymptote:
|
|
2414
|
+
* An array of coordinates representing the proposed end configuration
|
|
2415
|
+
* of the asymptote.
|
|
2416
|
+
*
|
|
2417
|
+
* Return: either a coordinate (to be used as the resulting coordinate of
|
|
2418
|
+
* the move) or a boolean, where `true` uses newCoord as the resulting
|
|
2419
|
+
* coordinate, and `false` uses oldCoord as the resulting coordinate.
|
|
2420
|
+
*/
|
|
2421
|
+
extraCoordConstraint: function (newCoord, oldCoord, coords, asymptote, graph) {
|
|
2422
|
+
const y = asymptote[0][1];
|
|
2423
|
+
return _.all(coords, coord => coord[1] !== y);
|
|
2424
|
+
},
|
|
2425
|
+
extraAsymptoteConstraint: function (newCoord, oldCoord, coords, asymptote, graph) {
|
|
2426
|
+
const y = newCoord[1];
|
|
2427
|
+
const isValid = _.all(coords, coord => coord[1] > y) || _.all(coords, coord => coord[1] < y);
|
|
2428
|
+
if (isValid) {
|
|
2429
|
+
return [oldCoord[0], y];
|
|
2430
|
+
}
|
|
2431
|
+
// Snap the asymptote as close as possible, i.e., if the user moves
|
|
2432
|
+
// the mouse really quickly into an invalid region
|
|
2433
|
+
const oldY = oldCoord[1];
|
|
2434
|
+
const wasBelow = _.all(coords, coord => coord[1] > oldY);
|
|
2435
|
+
if (wasBelow) {
|
|
2436
|
+
const bottomMost = _.min(_.map(coords, coord => coord[1]));
|
|
2437
|
+
return [oldCoord[0], bottomMost - graph.snapStep[1]];
|
|
2438
|
+
}
|
|
2439
|
+
const topMost = _.max(_.map(coords, coord => coord[1]));
|
|
2440
|
+
return [oldCoord[0], topMost + graph.snapStep[1]];
|
|
2441
|
+
},
|
|
2442
|
+
allowReflectOverAsymptote: true,
|
|
2443
|
+
getCoefficients: function (coords, asymptote) {
|
|
2444
|
+
const p1 = coords[0];
|
|
2445
|
+
const p2 = coords[1];
|
|
2446
|
+
const c = asymptote[0][1];
|
|
2447
|
+
const b = Math.log((p1[1] - c) / (p2[1] - c)) / (p1[0] - p2[0]);
|
|
2448
|
+
const a = (p1[1] - c) / Math.exp(b * p1[0]);
|
|
2449
|
+
return [a, b, c];
|
|
2450
|
+
},
|
|
2451
|
+
getFunctionForCoeffs: function (coeffs, x) {
|
|
2452
|
+
const a = coeffs[0];
|
|
2453
|
+
const b = coeffs[1];
|
|
2454
|
+
const c = coeffs[2];
|
|
2455
|
+
return a * Math.exp(b * x) + c;
|
|
2456
|
+
},
|
|
2457
|
+
getEquationString: function (coords, asymptote) {
|
|
2458
|
+
if (!asymptote) {
|
|
2459
|
+
return null;
|
|
2460
|
+
}
|
|
2461
|
+
const coeffs = this.getCoefficients(coords, asymptote);
|
|
2462
|
+
const a = coeffs[0];
|
|
2463
|
+
const b = coeffs[1];
|
|
2464
|
+
const c = coeffs[2];
|
|
2465
|
+
return "y = " + a.toFixed(3) + "e^(" + b.toFixed(3) + "x) + " + c.toFixed(3);
|
|
2466
|
+
}
|
|
2467
|
+
});
|
|
2468
|
+
const Logarithm = _.extend({}, PlotDefaults, {
|
|
2469
|
+
url: "https://ka-perseus-graphie.s3.amazonaws.com/f6491e99d34af34d924bfe0231728ad912068dc3.png",
|
|
2470
|
+
defaultCoords: [[0.55, 0.5], [0.75, 0.75]],
|
|
2471
|
+
defaultAsymptote: [[0.5, 0], [0.5, 1.0]],
|
|
2472
|
+
extraCoordConstraint: function (newCoord, oldCoord, coords, asymptote, graph) {
|
|
2473
|
+
const x = asymptote[0][0];
|
|
2474
|
+
return _.all(coords, coord => coord[0] !== x) && coords[0][1] !== coords[1][1];
|
|
2475
|
+
},
|
|
2476
|
+
extraAsymptoteConstraint: function (newCoord, oldCoord, coords, asymptote, graph) {
|
|
2477
|
+
const x = newCoord[0];
|
|
2478
|
+
const isValid = _.all(coords, coord => coord[0] > x) || _.all(coords, coord => coord[0] < x);
|
|
2479
|
+
if (isValid) {
|
|
2480
|
+
return [x, oldCoord[1]];
|
|
2481
|
+
}
|
|
2482
|
+
// Snap the asymptote as close as possible, i.e., if the user moves
|
|
2483
|
+
// the mouse really quickly into an invalid region
|
|
2484
|
+
const oldX = oldCoord[0];
|
|
2485
|
+
const wasLeft = _.all(coords, coord => coord[0] > oldX);
|
|
2486
|
+
if (wasLeft) {
|
|
2487
|
+
const leftMost = _.min(_.map(coords, coord => coord[0]));
|
|
2488
|
+
return [leftMost - graph.snapStep[0], oldCoord[1]];
|
|
2489
|
+
}
|
|
2490
|
+
const rightMost = _.max(_.map(coords, coord => coord[0]));
|
|
2491
|
+
return [rightMost + graph.snapStep[0], oldCoord[1]];
|
|
2492
|
+
},
|
|
2493
|
+
allowReflectOverAsymptote: true,
|
|
2494
|
+
getCoefficients: function (coords, asymptote) {
|
|
2495
|
+
// It's easiest to calculate the logarithm's coefficients by thinking
|
|
2496
|
+
// about it as the inverse of the exponential, so we flip x and y and
|
|
2497
|
+
// perform some algebra on the coefficients. This also unifies the
|
|
2498
|
+
// logic between the two 'models'.
|
|
2499
|
+
const flip = coord => [coord[1], coord[0]];
|
|
2500
|
+
const inverseCoeffs = Exponential.getCoefficients(_.map(coords, flip), _.map(asymptote, flip));
|
|
2501
|
+
if (inverseCoeffs) {
|
|
2502
|
+
const c = -inverseCoeffs[2] / inverseCoeffs[0];
|
|
2503
|
+
const b = 1 / inverseCoeffs[0];
|
|
2504
|
+
const a = 1 / inverseCoeffs[1];
|
|
2505
|
+
return [a, b, c];
|
|
2506
|
+
}
|
|
2507
|
+
},
|
|
2508
|
+
getFunctionForCoeffs: function (coeffs, x, asymptote) {
|
|
2509
|
+
const a = coeffs[0];
|
|
2510
|
+
const b = coeffs[1];
|
|
2511
|
+
const c = coeffs[2];
|
|
2512
|
+
return a * Math.log(b * x + c);
|
|
2513
|
+
},
|
|
2514
|
+
getEquationString: function (coords, asymptote) {
|
|
2515
|
+
if (!asymptote) {
|
|
2516
|
+
return null;
|
|
2517
|
+
}
|
|
2518
|
+
const coeffs = this.getCoefficients(coords, asymptote);
|
|
2519
|
+
const a = coeffs[0];
|
|
2520
|
+
const b = coeffs[1];
|
|
2521
|
+
const c = coeffs[2];
|
|
2522
|
+
return "y = ln(" + a.toFixed(3) + "x + " + b.toFixed(3) + ") + " + c.toFixed(3);
|
|
2523
|
+
}
|
|
2524
|
+
});
|
|
2525
|
+
const AbsoluteValue = _.extend({}, PlotDefaults, {
|
|
2526
|
+
url: "https://ka-perseus-graphie.s3.amazonaws.com/8256a630175a0cb1d11de223d6de0266daf98721.png",
|
|
2527
|
+
defaultCoords: [[0.5, 0.5], [0.75, 0.75]],
|
|
2528
|
+
getCoefficients: function (coords) {
|
|
2529
|
+
const p1 = coords[0];
|
|
2530
|
+
const p2 = coords[1];
|
|
2531
|
+
const denom = p2[0] - p1[0];
|
|
2532
|
+
const num = p2[1] - p1[1];
|
|
2533
|
+
if (denom === 0) {
|
|
2534
|
+
return;
|
|
2535
|
+
}
|
|
2536
|
+
let m = Math.abs(num / denom);
|
|
2537
|
+
if (p2[1] < p1[1]) {
|
|
2538
|
+
m *= -1;
|
|
2539
|
+
}
|
|
2540
|
+
const horizontalOffset = p1[0];
|
|
2541
|
+
const verticalOffset = p1[1];
|
|
2542
|
+
return [m, horizontalOffset, verticalOffset];
|
|
2543
|
+
},
|
|
2544
|
+
getFunctionForCoeffs: function (coeffs, x) {
|
|
2545
|
+
const m = coeffs[0];
|
|
2546
|
+
const horizontalOffset = coeffs[1];
|
|
2547
|
+
const verticalOffset = coeffs[2];
|
|
2548
|
+
return m * Math.abs(x - horizontalOffset) + verticalOffset;
|
|
2549
|
+
},
|
|
2550
|
+
getEquationString: function (coords) {
|
|
2551
|
+
const coeffs = this.getCoefficients(coords);
|
|
2552
|
+
const m = coeffs[0];
|
|
2553
|
+
const horizontalOffset = coeffs[1];
|
|
2554
|
+
const verticalOffset = coeffs[2];
|
|
2555
|
+
return "y = " + m.toFixed(3) + "| x - " + horizontalOffset.toFixed(3) + "| + " + verticalOffset.toFixed(3);
|
|
2556
|
+
}
|
|
2557
|
+
});
|
|
2558
|
+
|
|
2559
|
+
/* Utility functions for dealing with graphing interfaces. */
|
|
2560
|
+
const functionTypeMapping = {
|
|
2561
|
+
linear: Linear,
|
|
2562
|
+
quadratic: Quadratic,
|
|
2563
|
+
sinusoid: Sinusoid,
|
|
2564
|
+
tangent: Tangent,
|
|
2565
|
+
exponential: Exponential,
|
|
2566
|
+
logarithm: Logarithm,
|
|
2567
|
+
absolute_value: AbsoluteValue
|
|
2568
|
+
};
|
|
2569
|
+
const allTypes = _.keys(functionTypeMapping);
|
|
2570
|
+
function functionForType(type) {
|
|
2571
|
+
// @ts-expect-error: TypeScript doesn't know how to use deal with generics
|
|
2572
|
+
// and conditional types in this way.
|
|
2573
|
+
return functionTypeMapping[type];
|
|
2574
|
+
}
|
|
2575
|
+
|
|
2576
|
+
var grapherUtil = /*#__PURE__*/Object.freeze({
|
|
2577
|
+
__proto__: null,
|
|
2578
|
+
MOVABLES: MOVABLES,
|
|
2579
|
+
allTypes: allTypes,
|
|
2580
|
+
functionForType: functionForType
|
|
2581
|
+
});
|
|
2582
|
+
|
|
2583
|
+
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
2584
|
+
const libName = "@khanacademy/perseus-core";
|
|
2585
|
+
const libVersion = "3.2.0";
|
|
2586
|
+
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
2587
|
+
|
|
2588
|
+
/**
|
|
2589
|
+
* @typedef {Object} Errors utility for referencing the Perseus error taxonomy.
|
|
2590
|
+
*/
|
|
2591
|
+
const Errors = Object.freeze({
|
|
2592
|
+
/**
|
|
2593
|
+
* @property {ErrorKind} Unknown The kind of error is not known.
|
|
2594
|
+
*/
|
|
2595
|
+
Unknown: "Unknown",
|
|
2596
|
+
/**
|
|
2597
|
+
* @property {ErrorKind} Internal The error is internal to the executing code.
|
|
2598
|
+
*/
|
|
2599
|
+
Internal: "Internal",
|
|
2600
|
+
/**
|
|
2601
|
+
* @property {ErrorKind} InvalidInput There was a problem with the provided
|
|
2602
|
+
* input, such as the wrong format or a null value.
|
|
2603
|
+
*/
|
|
2604
|
+
InvalidInput: "InvalidInput",
|
|
2605
|
+
/**
|
|
2606
|
+
* @property {ErrorKind} NotAllowed There was a problem due to the state of
|
|
2607
|
+
* the system not matching the requested operation or input. For example,
|
|
2608
|
+
* trying to create a username that is valid, but is already taken by
|
|
2609
|
+
* another user. Use {@link InvalidInput} instead when the input isn't
|
|
2610
|
+
* valid regardless of the state of the system. Use {@link NotFound} when
|
|
2611
|
+
* the failure is due to not being able to find a resource.
|
|
2612
|
+
*/
|
|
2613
|
+
NotAllowed: "NotAllowed",
|
|
2614
|
+
/**
|
|
2615
|
+
* @property {ErrorKind} TransientService There was a problem when making a
|
|
2616
|
+
* request to a service.
|
|
2617
|
+
*/
|
|
2618
|
+
TransientService: "TransientService",
|
|
2619
|
+
/**
|
|
2620
|
+
* @property {ErrorKind} Service There was a non-transient problem when
|
|
2621
|
+
* making a request to service.
|
|
2622
|
+
*/
|
|
2623
|
+
Service: "Service"
|
|
2624
|
+
});
|
|
2625
|
+
|
|
2626
|
+
/**
|
|
2627
|
+
* @type {ErrorKind} The kind of error being reported
|
|
2628
|
+
*/
|
|
2629
|
+
|
|
2630
|
+
class PerseusError extends Error {
|
|
2631
|
+
kind;
|
|
2632
|
+
metadata;
|
|
2633
|
+
constructor(message, kind, options) {
|
|
2634
|
+
super(message);
|
|
2635
|
+
this.kind = kind;
|
|
2636
|
+
this.metadata = options?.metadata;
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2640
|
+
/**
|
|
2641
|
+
* The Perseus "data schema" file.
|
|
2642
|
+
*
|
|
2643
|
+
* This file, and the types in it, represents the "data schema" that Perseus
|
|
2644
|
+
* uses. The @khanacademy/perseus-editor package edits and produces objects
|
|
2645
|
+
* that conform to the types in this file. Similarly, the top-level renderers
|
|
2646
|
+
* in @khanacademy/perseus, consume objects that conform to these types.
|
|
2647
|
+
*
|
|
2648
|
+
* WARNING: This file should not import any types from elsewhere so that it is
|
|
2649
|
+
* easy to reason about changes that alter the Perseus schema. This helps
|
|
2650
|
+
* ensure that it is not changed accidentally when upgrading a dependant
|
|
2651
|
+
* package or other part of Perseus code. Note that TypeScript does type
|
|
2652
|
+
* checking via something called "structural typing". This means that as long
|
|
2653
|
+
* as the shape of a type matches, the name it goes by doesn't matter. As a
|
|
2654
|
+
* result, a `Coord` type that looks like this `[x: number, y: number]` is
|
|
2655
|
+
* _identical_, in TypeScript's eyes, to this `Vector2` type `[x: number, y:
|
|
2656
|
+
* number]`. Also, with tuples, the labels for each entry is ignored, so `[x:
|
|
2657
|
+
* number, y: number]` is compatible with `[min: number, max: number]`. The
|
|
2658
|
+
* labels are for humans, not TypeScript. :)
|
|
2659
|
+
*
|
|
2660
|
+
* If you make changes to types in this file, be very sure that:
|
|
2661
|
+
*
|
|
2662
|
+
* a) the changes are backwards compatible. If they are not, old data from
|
|
2663
|
+
* previous versions of the "schema" could become unrenderable, or worse,
|
|
2664
|
+
* introduce hard-to-diagnose bugs.
|
|
2665
|
+
* b) the parsing code (`util/parse-perseus-json/`) is updated to handle
|
|
2666
|
+
* the new format _as well as_ the old format.
|
|
2667
|
+
*/
|
|
2668
|
+
|
|
2669
|
+
// TODO(FEI-4010): Remove `Perseus` prefix for all types here
|
|
2670
|
+
|
|
2671
|
+
// Same name as Mafs
|
|
2672
|
+
|
|
2673
|
+
/**
|
|
2674
|
+
* Our core set of Perseus widgets.
|
|
2675
|
+
*
|
|
2676
|
+
* This interface is the basis for "registering" all Perseus widget types.
|
|
2677
|
+
* There should be one key/value pair for each supported widget. If you create
|
|
2678
|
+
* a new widget, an entry should be added to this interface. Note that this
|
|
2679
|
+
* only registers the widget options type, you'll also need to register the
|
|
2680
|
+
* widget so that it's available at runtime (@see
|
|
2681
|
+
* {@link file://./widgets.ts#registerWidget}).
|
|
2682
|
+
*
|
|
2683
|
+
* Importantly, the key should be the name that is used in widget IDs. For most
|
|
2684
|
+
* widgets that is the same as the widget option's `type` field. In cases where
|
|
2685
|
+
* a widget has been deprecated and replaced with the deprecated-standin
|
|
2686
|
+
* widget, it should be the original widget type!
|
|
2687
|
+
*
|
|
2688
|
+
* If you define the widget outside of this package, you can still add the new
|
|
2689
|
+
* widget to this interface by writing the following in that package that
|
|
2690
|
+
* contains the widget. TypeScript will merge that definition of the
|
|
2691
|
+
* `PerseusWidgets` with the one defined below.
|
|
2692
|
+
*
|
|
2693
|
+
* ```typescript
|
|
2694
|
+
* declare module "@khanacademy/perseus" {
|
|
2695
|
+
* interface PerseusWidgetTypes {
|
|
2696
|
+
* // A new widget
|
|
2697
|
+
* "new-awesomeness": MyAwesomeNewWidget;
|
|
2698
|
+
*
|
|
2699
|
+
* // A deprecated widget
|
|
2700
|
+
* "super-old-widget": DeprecatedStandinWidget;
|
|
2701
|
+
* }
|
|
2702
|
+
* }
|
|
2703
|
+
*
|
|
2704
|
+
* // The new widget's options definition
|
|
2705
|
+
* type MyAwesomeNewWidget = WidgetOptions<'new-awesomeness', MyAwesomeNewWidgetOptions>;
|
|
2706
|
+
*
|
|
2707
|
+
* // The deprecated widget's options definition
|
|
2708
|
+
* type SuperOldWidget = WidgetOptions<'super-old-widget', object>;
|
|
2709
|
+
* ```
|
|
2710
|
+
*
|
|
2711
|
+
* This interface can be extended through the magic of TypeScript "Declaration
|
|
2712
|
+
* merging". Specifically, we augment this module and extend this interface.
|
|
2713
|
+
*
|
|
2714
|
+
* @see {@link https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation}
|
|
2715
|
+
*/
|
|
2716
|
+
|
|
2717
|
+
/**
|
|
2718
|
+
* A map of widget IDs to widget options. This is most often used as the type
|
|
2719
|
+
* for a set of widgets defined in a `PerseusItem` but can also be useful to
|
|
2720
|
+
* represent a function parameter where only `widgets` from a `PerseusItem` are
|
|
2721
|
+
* needed. Today Widget IDs are made up of the widget type and an incrementing
|
|
2722
|
+
* integer (eg. `interactive-graph 1` or `radio 3`). It is suggested to avoid
|
|
2723
|
+
* reading/parsing the widget id to derive any information from it, except in
|
|
2724
|
+
* the case of this map.
|
|
2725
|
+
*
|
|
2726
|
+
* @see {@link PerseusWidgetTypes} additional widgets can be added to this map type
|
|
2727
|
+
* by augmenting the PerseusWidgetTypes with new widget types!
|
|
2728
|
+
*/
|
|
2729
|
+
|
|
2730
|
+
/**
|
|
2731
|
+
* A "PerseusItem" is a classic Perseus item. It is rendered by the
|
|
2732
|
+
* `ServerItemRenderer` and the layout is pre-set.
|
|
2733
|
+
*
|
|
2734
|
+
* To render more complex Perseus items, see the `Item` type in the multi item
|
|
2735
|
+
* area.
|
|
2736
|
+
*/
|
|
2737
|
+
|
|
2738
|
+
/**
|
|
2739
|
+
* A "PerseusArticle" is an item that is meant to be rendered as an article.
|
|
2740
|
+
* This item is never scored and is rendered by the `ArticleRenderer`.
|
|
2741
|
+
*/
|
|
2742
|
+
|
|
2743
|
+
const ItemExtras = [
|
|
2744
|
+
// The user might benefit from using a Scientific Calculator. Provided on Khan Academy when true
|
|
2745
|
+
"calculator",
|
|
2746
|
+
// The user might benefit from using a statistics Chi Squared Table like https://people.richland.edu/james/lecture/m170/tbl-chi.html
|
|
2747
|
+
"chi2Table",
|
|
2748
|
+
// The user might benefit from a monthly payments calculator. Provided on Khan Academy when true
|
|
2749
|
+
"financialCalculatorMonthlyPayment",
|
|
2750
|
+
// The user might benefit from a total amount calculator. Provided on Khan Academy when true
|
|
2751
|
+
"financialCalculatorTotalAmount",
|
|
2752
|
+
// The user might benefit from a time to pay off calculator. Provided on Khan Academy when true
|
|
2753
|
+
"financialCalculatorTimeToPayOff",
|
|
2754
|
+
// The user might benefit from using a Periodic Table of Elements. Provided on Khan Academy when true
|
|
2755
|
+
"periodicTable",
|
|
2756
|
+
// The user might benefit from using a Periodic Table of Elements with key. Provided on Khan Academy when true
|
|
2757
|
+
"periodicTableWithKey",
|
|
2758
|
+
// The user might benefit from using a statistics T Table like https://www.statisticshowto.com/tables/t-distribution-table/
|
|
2759
|
+
"tTable",
|
|
2760
|
+
// The user might benefit from using a statistics Z Table like https://www.ztable.net/
|
|
2761
|
+
"zTable"];
|
|
2762
|
+
|
|
2763
|
+
/**
|
|
2764
|
+
* The type representing the common structure of all widget's options. The
|
|
2765
|
+
* `Options` generic type represents the widget-specific option data.
|
|
2766
|
+
*/
|
|
2767
|
+
|
|
2768
|
+
// prettier-ignore
|
|
2769
|
+
|
|
2770
|
+
// prettier-ignore
|
|
2771
|
+
|
|
2772
|
+
// prettier-ignore
|
|
2773
|
+
|
|
2774
|
+
// prettier-ignore
|
|
2775
|
+
|
|
2776
|
+
// prettier-ignore
|
|
2777
|
+
|
|
2778
|
+
// prettier-ignore
|
|
2779
|
+
|
|
2780
|
+
// prettier-ignore
|
|
2781
|
+
|
|
2782
|
+
// prettier-ignore
|
|
2783
|
+
|
|
2784
|
+
// prettier-ignore
|
|
2785
|
+
|
|
2786
|
+
// prettier-ignore
|
|
2787
|
+
|
|
2788
|
+
// prettier-ignore
|
|
2789
|
+
|
|
2790
|
+
// prettier-ignore
|
|
2791
|
+
|
|
2792
|
+
// prettier-ignore
|
|
2793
|
+
|
|
2794
|
+
// prettier-ignore
|
|
2795
|
+
|
|
2796
|
+
// prettier-ignore
|
|
2797
|
+
|
|
2798
|
+
// prettier-ignore
|
|
2799
|
+
|
|
2800
|
+
// prettier-ignore
|
|
2801
|
+
|
|
2802
|
+
// prettier-ignore
|
|
2803
|
+
|
|
2804
|
+
// prettier-ignore
|
|
2805
|
+
|
|
2806
|
+
// prettier-ignore
|
|
2807
|
+
|
|
2808
|
+
// prettier-ignore
|
|
2809
|
+
|
|
2810
|
+
// prettier-ignore
|
|
2811
|
+
|
|
2812
|
+
// prettier-ignore
|
|
2813
|
+
|
|
2814
|
+
// prettier-ignore
|
|
2815
|
+
|
|
2816
|
+
// prettier-ignore
|
|
2817
|
+
|
|
2818
|
+
// prettier-ignore
|
|
2819
|
+
|
|
2820
|
+
// prettier-ignore
|
|
2821
|
+
|
|
2822
|
+
// prettier-ignore
|
|
2823
|
+
|
|
2824
|
+
// prettier-ignore
|
|
2825
|
+
|
|
2826
|
+
// prettier-ignore
|
|
2827
|
+
|
|
2828
|
+
// prettier-ignore
|
|
2829
|
+
|
|
2830
|
+
// prettier-ignore
|
|
2831
|
+
|
|
2832
|
+
// prettier-ignore
|
|
2833
|
+
|
|
2834
|
+
// prettier-ignore
|
|
2835
|
+
|
|
2836
|
+
//prettier-ignore
|
|
2837
|
+
|
|
2838
|
+
/**
|
|
2839
|
+
* A background image applied to various widgets.
|
|
2840
|
+
*/
|
|
2841
|
+
|
|
2842
|
+
/**
|
|
2843
|
+
* The type of markings to display on the graph.
|
|
2844
|
+
* - axes: shows the axes without the gride lines
|
|
2845
|
+
* - graph: shows the axes and the grid lines
|
|
2846
|
+
* - grid: shows only the grid lines
|
|
2847
|
+
* - none: shows no markings
|
|
2848
|
+
*/
|
|
2849
|
+
|
|
2850
|
+
const PerseusExpressionAnswerFormConsidered = ["correct", "wrong", "ungraded"];
|
|
2851
|
+
|
|
2852
|
+
// 2D range: xMin, xMax, yMin, yMax
|
|
2853
|
+
|
|
2854
|
+
const lockedFigureColorNames = ["blue", "green", "grayH", "purple", "pink", "orange", "red"];
|
|
2855
|
+
const lockedFigureColors = {
|
|
2856
|
+
blue: "#3D7586",
|
|
2857
|
+
green: "#447A53",
|
|
2858
|
+
grayH: "#3B3D45",
|
|
2859
|
+
purple: "#594094",
|
|
2860
|
+
pink: "#B25071",
|
|
2861
|
+
red: "#D92916",
|
|
2862
|
+
orange: "#946700"
|
|
2863
|
+
};
|
|
2864
|
+
const lockedFigureFillStyles = {
|
|
2865
|
+
none: 0,
|
|
2866
|
+
white: 1,
|
|
2867
|
+
translucent: 0.4,
|
|
2868
|
+
solid: 1
|
|
2869
|
+
};
|
|
2870
|
+
|
|
2871
|
+
// Not associated with a specific figure
|
|
2872
|
+
|
|
2873
|
+
const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
|
|
2874
|
+
|
|
2362
2875
|
/**
|
|
2363
2876
|
* _ utilities for objects
|
|
2364
2877
|
*/
|
|
@@ -2404,10 +2917,16 @@ const mapObject = function (obj, lambda) {
|
|
|
2404
2917
|
};
|
|
2405
2918
|
|
|
2406
2919
|
exports.Errors = Errors;
|
|
2920
|
+
exports.GrapherUtil = grapherUtil;
|
|
2407
2921
|
exports.ItemExtras = ItemExtras;
|
|
2408
2922
|
exports.PerseusError = PerseusError;
|
|
2409
2923
|
exports.PerseusExpressionAnswerFormConsidered = PerseusExpressionAnswerFormConsidered;
|
|
2410
2924
|
exports.addLibraryVersionToPerseusDebug = addLibraryVersionToPerseusDebug;
|
|
2925
|
+
exports.approximateDeepEqual = approximateDeepEqual;
|
|
2926
|
+
exports.approximateEqual = approximateEqual;
|
|
2927
|
+
exports.deepClone = deepClone;
|
|
2928
|
+
exports.getDecimalSeparator = getDecimalSeparator;
|
|
2929
|
+
exports.getMatrixSize = getMatrixSize;
|
|
2411
2930
|
exports.libVersion = libVersion;
|
|
2412
2931
|
exports.lockedFigureColorNames = lockedFigureColorNames;
|
|
2413
2932
|
exports.lockedFigureColors = lockedFigureColors;
|