@khanacademy/perseus 17.8.0 → 18.0.1
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/CHANGELOG.md +40 -0
- package/dist/es/index.css +0 -4
- package/dist/es/index.js +1422 -1021
- package/dist/es/index.js.map +1 -1
- package/dist/index.css +0 -4
- package/dist/index.d.ts +4 -2
- package/dist/index.js +1321 -954
- package/dist/index.js.map +1 -1
- package/dist/perseus-types.d.ts +4 -3
- package/dist/renderer.d.ts +6 -6
- package/dist/server-item-renderer.d.ts +2 -4
- package/dist/strings.js +1 -1
- package/dist/types.d.ts +19 -2
- package/dist/util/drawing-transform.d.ts +21 -0
- package/dist/util/extract-perseus-data.d.ts +26 -0
- package/dist/util/graph-bounds.d.ts +9 -0
- package/dist/util/graphie.d.ts +2 -0
- package/dist/util/interval.d.ts +1 -0
- package/dist/util/parse-perseus-json.d.ts +10 -0
- package/dist/util/tex.d.ts +1 -3
- package/dist/widgets/deprecated-standin/index.d.ts +4 -4
- package/dist/widgets/passage.d.ts +3 -3
- package/dist/widgets.d.ts +16 -6
- package/package.json +9 -9
- package/src/__tests__/extract-perseus-data.test.ts +1027 -0
- package/src/index.ts +6 -1
- package/src/init.ts +1 -1
- package/src/perseus-types.ts +4 -3
- package/src/renderer.tsx +12 -9
- package/src/server-item-renderer.tsx +2 -4
- package/src/styles/widgets/image.less +0 -3
- package/src/types.ts +19 -16
- package/src/util/drawing-transform.test.ts +67 -0
- package/src/util/drawing-transform.ts +102 -0
- package/src/util/extract-perseus-data.ts +539 -0
- package/src/util/graph-bounds.ts +23 -0
- package/src/util/graphie.ts +1167 -1258
- package/src/util/interactive.ts +2 -2
- package/src/util/interval.ts +4 -0
- package/src/util/parse-perseus-json.test.ts +10 -0
- package/src/util/parse-perseus-json.ts +13 -0
- package/src/util/tex.ts +11 -84
- package/src/widgets/__stories__/image.stories.tsx +12 -26
- package/src/widgets/__tests__/__snapshots__/grapher.test.ts.snap +72 -288
- package/src/widgets/__tests__/__snapshots__/interaction.test.ts.snap +5 -20
- package/src/widgets/__tests__/__snapshots__/interactive-graph.test.ts.snap +32 -128
- package/src/widgets/__tests__/__snapshots__/number-line.test.ts.snap +22 -88
- package/src/widgets/__tests__/__snapshots__/plotter.test.tsx.snap +12 -48
- package/src/widgets/deprecated-standin/__stories__/deprecated-standin.stories.tsx +8 -12
- package/src/widgets/deprecated-standin/__tests__/__snapshots__/deprecated-standin.test.ts.snap +6 -7
- package/src/widgets/deprecated-standin/index.tsx +8 -7
- package/src/widgets/interactive-graph.tsx +3 -2
- package/src/widgets/label-image/answer-pill.tsx +6 -1
- package/src/widgets/passage.tsx +5 -7
- package/src/widgets.ts +34 -10
- package/tsconfig-build.tsbuildinfo +1 -1
package/dist/es/index.js
CHANGED
|
@@ -35,6 +35,7 @@ import { Popper } from 'react-popper';
|
|
|
35
35
|
import createReactClass from 'create-react-class';
|
|
36
36
|
import Link from '@khanacademy/wonder-blocks-link';
|
|
37
37
|
import { addLibraryVersionToPerseusDebug } from '@khanacademy/perseus-core';
|
|
38
|
+
import { keys } from '@khanacademy/wonder-stuff-core';
|
|
38
39
|
|
|
39
40
|
function _extends() {
|
|
40
41
|
_extends = Object.assign ? Object.assign.bind() : function (target) {
|
|
@@ -4221,6 +4222,18 @@ const InteractiveUtil = {
|
|
|
4221
4222
|
}
|
|
4222
4223
|
};
|
|
4223
4224
|
|
|
4225
|
+
function _classPrivateFieldBase(receiver, privateKey) {
|
|
4226
|
+
if (!Object.prototype.hasOwnProperty.call(receiver, privateKey)) {
|
|
4227
|
+
throw new TypeError("attempted to use private field on non-instance");
|
|
4228
|
+
}
|
|
4229
|
+
return receiver;
|
|
4230
|
+
}
|
|
4231
|
+
|
|
4232
|
+
var id$1 = 0;
|
|
4233
|
+
function _classPrivateFieldKey(name) {
|
|
4234
|
+
return "__private_" + id$1++ + "_" + name;
|
|
4235
|
+
}
|
|
4236
|
+
|
|
4224
4237
|
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
|
|
4225
4238
|
|
|
4226
4239
|
function getDefaultExportFromCjs (x) {
|
|
@@ -8511,6 +8524,95 @@ const KhanColors = {
|
|
|
8511
8524
|
DYNAMIC: "#6495ED" // BLUE
|
|
8512
8525
|
};
|
|
8513
8526
|
|
|
8527
|
+
// DrawingTransform is responsible for transforming from "math coordinates"
|
|
8528
|
+
// (on the Cartesian plane) to "canvas coordinates" in pixels.
|
|
8529
|
+
class DrawingTransform {
|
|
8530
|
+
constructor(raphael, initialScale, bounds) {
|
|
8531
|
+
this.raphael = void 0;
|
|
8532
|
+
// Scale is measured in pixels per unit on the Cartesian plane.
|
|
8533
|
+
this.xScale = void 0;
|
|
8534
|
+
this.yScale = void 0;
|
|
8535
|
+
this.bounds = void 0;
|
|
8536
|
+
// TODO(benchristel): rename these methods to transform* instead of scale*.
|
|
8537
|
+
// They're doing more than scaling! Some of them also translate.
|
|
8538
|
+
this.scaleVector = point => {
|
|
8539
|
+
if (typeof point === "number") {
|
|
8540
|
+
return this.scaleVector([point, point]);
|
|
8541
|
+
}
|
|
8542
|
+
const x = point[0];
|
|
8543
|
+
const y = point[1];
|
|
8544
|
+
return [x * this.xScale, y * this.yScale];
|
|
8545
|
+
};
|
|
8546
|
+
this.scalePoint = point => {
|
|
8547
|
+
if (typeof point === "number") {
|
|
8548
|
+
return this.scalePoint([point, point]);
|
|
8549
|
+
}
|
|
8550
|
+
const x = point[0];
|
|
8551
|
+
const y = point[1];
|
|
8552
|
+
return [(x - this.bounds.xMin) * this.xScale, (this.bounds.yMax - y) * this.yScale];
|
|
8553
|
+
};
|
|
8554
|
+
this.unscalePoint = point => {
|
|
8555
|
+
if (typeof point === "number") {
|
|
8556
|
+
return this.unscalePoint([point, point]);
|
|
8557
|
+
}
|
|
8558
|
+
const x = point[0];
|
|
8559
|
+
const y = point[1];
|
|
8560
|
+
return [x / this.xScale + this.bounds.xMin, this.bounds.yMax - y / this.yScale];
|
|
8561
|
+
};
|
|
8562
|
+
this.unscaleVector = point => {
|
|
8563
|
+
if (typeof point === "number") {
|
|
8564
|
+
return this.unscaleVector([point, point]);
|
|
8565
|
+
}
|
|
8566
|
+
return [point[0] / this.xScale, point[1] / this.yScale];
|
|
8567
|
+
};
|
|
8568
|
+
this.setScale = scale => {
|
|
8569
|
+
if (typeof scale === "number") {
|
|
8570
|
+
scale = [scale, scale];
|
|
8571
|
+
}
|
|
8572
|
+
this.xScale = scale[0];
|
|
8573
|
+
this.yScale = scale[1];
|
|
8574
|
+
|
|
8575
|
+
// Update the canvas size
|
|
8576
|
+
this.raphael.setSize(...this.canvasDimensions());
|
|
8577
|
+
};
|
|
8578
|
+
this.canvasDimensions = () => {
|
|
8579
|
+
return [this.bounds.width() * this.xScale, this.bounds.height() * this.yScale];
|
|
8580
|
+
};
|
|
8581
|
+
this.pixelsPerUnitX = () => {
|
|
8582
|
+
return this.xScale;
|
|
8583
|
+
};
|
|
8584
|
+
this.pixelsPerUnitY = () => {
|
|
8585
|
+
return this.yScale;
|
|
8586
|
+
};
|
|
8587
|
+
this.raphael = raphael;
|
|
8588
|
+
this.bounds = bounds;
|
|
8589
|
+
this.xScale = initialScale[0];
|
|
8590
|
+
this.yScale = initialScale[1];
|
|
8591
|
+
this.setScale(initialScale);
|
|
8592
|
+
}
|
|
8593
|
+
}
|
|
8594
|
+
|
|
8595
|
+
// Defines the bounds of the visible area of a graph, in Cartesian coordinates.
|
|
8596
|
+
// GraphBounds is immutable.
|
|
8597
|
+
class GraphBounds {
|
|
8598
|
+
constructor(xRange, yRange) {
|
|
8599
|
+
this.xMin = void 0;
|
|
8600
|
+
this.xMax = void 0;
|
|
8601
|
+
this.yMin = void 0;
|
|
8602
|
+
this.yMax = void 0;
|
|
8603
|
+
this.xMin = xRange[0];
|
|
8604
|
+
this.xMax = xRange[1];
|
|
8605
|
+
this.yMin = yRange[0];
|
|
8606
|
+
this.yMax = yRange[1];
|
|
8607
|
+
}
|
|
8608
|
+
width() {
|
|
8609
|
+
return this.xMax - this.xMin;
|
|
8610
|
+
}
|
|
8611
|
+
height() {
|
|
8612
|
+
return this.yMax - this.yMin;
|
|
8613
|
+
}
|
|
8614
|
+
}
|
|
8615
|
+
|
|
8514
8616
|
function render(element, container, callback) {
|
|
8515
8617
|
// TODO(LP-11406): Replace this, or callers, with React Portal
|
|
8516
8618
|
// @ts-expect-error - TS2322 - Type 'void' is not assignable to type 'ElementRef<ElementType>'.
|
|
@@ -8538,23 +8640,6 @@ function findChildOrAdd(elem, className) {
|
|
|
8538
8640
|
}
|
|
8539
8641
|
return $child;
|
|
8540
8642
|
}
|
|
8541
|
-
function doCallback(elem, callback) {
|
|
8542
|
-
let tries = 0;
|
|
8543
|
-
(function check() {
|
|
8544
|
-
const height = elem.scrollHeight;
|
|
8545
|
-
// Heuristic to guess if the font has kicked in
|
|
8546
|
-
// so we have box metrics (magic number ick,
|
|
8547
|
-
// but this seems to work mostly-consistently)
|
|
8548
|
-
if (height > 18 || tries >= 10) {
|
|
8549
|
-
callback();
|
|
8550
|
-
} else {
|
|
8551
|
-
tries++;
|
|
8552
|
-
// TODO(jeff, CP-3128): Use Wonder Blocks Timing API
|
|
8553
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
8554
|
-
setTimeout(check, 100);
|
|
8555
|
-
}
|
|
8556
|
-
})();
|
|
8557
|
-
}
|
|
8558
8643
|
var Tex$1 = {
|
|
8559
8644
|
// Process a node and add math inside of it. This attempts to use KaTeX to
|
|
8560
8645
|
// format the math, and if that fails it falls back to MathJax.
|
|
@@ -8576,27 +8661,14 @@ var Tex$1 = {
|
|
|
8576
8661
|
|
|
8577
8662
|
// Only process if it hasn't been done before, or it is forced
|
|
8578
8663
|
if ($elem.attr("data-math-formula") == null || force) {
|
|
8579
|
-
const $
|
|
8580
|
-
|
|
8581
|
-
|
|
8582
|
-
//
|
|
8583
|
-
//
|
|
8584
|
-
|
|
8585
|
-
|
|
8586
|
-
|
|
8587
|
-
const script = $mathjaxHolder.find("script[type='math/tex']")[0];
|
|
8588
|
-
|
|
8589
|
-
// If text wasn't provided, we look in two places
|
|
8590
|
-
if (text == null) {
|
|
8591
|
-
if ($elem.attr("data-math-formula")) {
|
|
8592
|
-
// The old typeset formula
|
|
8593
|
-
// @ts-expect-error - TS2322 - Type 'string | undefined' is not assignable to type 'string'.
|
|
8594
|
-
text = $elem.attr("data-math-formula");
|
|
8595
|
-
} else if (script) {
|
|
8596
|
-
// The contents of the <script> tag
|
|
8597
|
-
// @ts-expect-error - TS2339 - Property 'text' does not exist on type 'HTMLElement'.
|
|
8598
|
-
text = script.text || script.textContent;
|
|
8599
|
-
}
|
|
8664
|
+
const $texHolder = findChildOrAdd($elem, "tex-holder");
|
|
8665
|
+
|
|
8666
|
+
// If text wasn't provided, we use the cached text.
|
|
8667
|
+
// TODO(benchristel): I'm not sure if text can ever be null. It's
|
|
8668
|
+
// possible we don't need this check.
|
|
8669
|
+
if (text == null && $elem.attr("data-math-formula")) {
|
|
8670
|
+
// @ts-expect-error - TS2322 - Type 'string | undefined' is not assignable to type 'string'.
|
|
8671
|
+
text = $elem.attr("data-math-formula");
|
|
8600
8672
|
}
|
|
8601
8673
|
text = text != null ? text + "" : "";
|
|
8602
8674
|
|
|
@@ -8612,64 +8684,15 @@ var Tex$1 = {
|
|
|
8612
8684
|
// that name is already taken.
|
|
8613
8685
|
render( /*#__PURE__*/React.createElement(TeX, {
|
|
8614
8686
|
children: text,
|
|
8615
|
-
onRender:
|
|
8616
|
-
|
|
8617
|
-
doCallback(elem, callback);
|
|
8618
|
-
}
|
|
8619
|
-
}
|
|
8620
|
-
}), $katexHolder[0]);
|
|
8621
|
-
}
|
|
8622
|
-
},
|
|
8623
|
-
// Function to restore a node to a non-math-processed state
|
|
8624
|
-
cleanupMath: function (elem) {
|
|
8625
|
-
const $elem = $(elem);
|
|
8626
|
-
|
|
8627
|
-
// Only mess with it if it's been processed before
|
|
8628
|
-
if ($elem.attr("data-math-formula")) {
|
|
8629
|
-
// Remove MathJax remnants
|
|
8630
|
-
if (typeof MathJax !== "undefined") {
|
|
8631
|
-
const jax = MathJax.Hub.getJaxFor($elem.find("script")[0]);
|
|
8632
|
-
if (jax) {
|
|
8633
|
-
const e = jax.SourceElement();
|
|
8634
|
-
if (e.previousSibling && e.previousSibling.className) {
|
|
8635
|
-
jax.Remove();
|
|
8636
|
-
}
|
|
8637
|
-
}
|
|
8638
|
-
}
|
|
8639
|
-
|
|
8640
|
-
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
8641
|
-
$elem.text($elem.attr("data-math-formula"));
|
|
8642
|
-
$elem.attr("data-math-formula", null);
|
|
8643
|
-
$elem.attr("data-math-type", null);
|
|
8687
|
+
onRender: callback
|
|
8688
|
+
}), $texHolder[0]);
|
|
8644
8689
|
}
|
|
8645
|
-
return elem;
|
|
8646
|
-
},
|
|
8647
|
-
// Function to retrieve the formula of a typeset math node
|
|
8648
|
-
retrieveMathFormula: function (elem) {
|
|
8649
|
-
return $(elem).attr("data-math-formula");
|
|
8650
8690
|
}
|
|
8651
8691
|
};
|
|
8652
8692
|
|
|
8653
8693
|
const {
|
|
8654
8694
|
processMath: processMath$1
|
|
8655
8695
|
} = Tex$1;
|
|
8656
|
-
|
|
8657
|
-
/* Convert cartesian coordinates [x, y] to polar coordinates [r,
|
|
8658
|
-
* theta], with theta in degrees, or in radians if angleInRadians is
|
|
8659
|
-
* specified.
|
|
8660
|
-
*/
|
|
8661
|
-
function cartToPolar(coord, angleInRadians) {
|
|
8662
|
-
const r = Math.sqrt(Math.pow(coord[0], 2) + Math.pow(coord[1], 2));
|
|
8663
|
-
let theta = Math.atan2(coord[1], coord[0]);
|
|
8664
|
-
// convert angle range from [-pi, pi] to [0, 2pi]
|
|
8665
|
-
if (theta < 0) {
|
|
8666
|
-
theta += 2 * Math.PI;
|
|
8667
|
-
}
|
|
8668
|
-
if (!angleInRadians) {
|
|
8669
|
-
theta = theta * 180 / Math.PI;
|
|
8670
|
-
}
|
|
8671
|
-
return [r, theta];
|
|
8672
|
-
}
|
|
8673
8696
|
function polar(r, th) {
|
|
8674
8697
|
if (typeof r === "number") {
|
|
8675
8698
|
r = [r, r];
|
|
@@ -8725,223 +8748,440 @@ const GraphUtils = {
|
|
|
8725
8748
|
},
|
|
8726
8749
|
graphs: {}
|
|
8727
8750
|
};
|
|
8728
|
-
|
|
8729
|
-
|
|
8730
|
-
|
|
8731
|
-
|
|
8732
|
-
|
|
8733
|
-
|
|
8734
|
-
|
|
8735
|
-
|
|
8736
|
-
|
|
8737
|
-
|
|
8738
|
-
|
|
8739
|
-
|
|
8740
|
-
|
|
8741
|
-
|
|
8742
|
-
|
|
8743
|
-
|
|
8751
|
+
var _bounds = /*#__PURE__*/_classPrivateFieldKey("bounds");
|
|
8752
|
+
var _drawingTransform = /*#__PURE__*/_classPrivateFieldKey("drawingTransform");
|
|
8753
|
+
class Graphie$1 {
|
|
8754
|
+
constructor(el) {
|
|
8755
|
+
this.el = void 0;
|
|
8756
|
+
Object.defineProperty(this, _bounds, {
|
|
8757
|
+
writable: true,
|
|
8758
|
+
value: void 0
|
|
8759
|
+
});
|
|
8760
|
+
Object.defineProperty(this, _drawingTransform, {
|
|
8761
|
+
writable: true,
|
|
8762
|
+
value: void 0
|
|
8763
|
+
});
|
|
8764
|
+
this.raphael = void 0;
|
|
8765
|
+
this.isMobile = false;
|
|
8766
|
+
// Set up some reasonable defaults
|
|
8767
|
+
this.currentStyle = {
|
|
8768
|
+
"stroke-width": 2,
|
|
8769
|
+
fill: "none"
|
|
8770
|
+
};
|
|
8771
|
+
this.range = void 0;
|
|
8772
|
+
this.scale = void 0;
|
|
8773
|
+
this.dimensions = void 0;
|
|
8774
|
+
// TODO(benchristel): xpixels and ypixels are never used by this class, but
|
|
8775
|
+
// other code reaches in to access them :(
|
|
8776
|
+
// Refactor to accessor methods that look at this.dimensions.
|
|
8777
|
+
this.xpixels = void 0;
|
|
8778
|
+
this.ypixels = void 0;
|
|
8779
|
+
this.svgPath = (points, alreadyScaled) => {
|
|
8780
|
+
return $.map(points, (point, i) => {
|
|
8781
|
+
if (point === true) {
|
|
8782
|
+
return "z";
|
|
8783
|
+
}
|
|
8784
|
+
const scaled = alreadyScaled ? point : this.scalePoint(point);
|
|
8785
|
+
return (i === 0 ? "M" : "L") + KhanMath.bound(scaled[0]) + " " + KhanMath.bound(scaled[1]);
|
|
8786
|
+
}).join("");
|
|
8787
|
+
};
|
|
8788
|
+
this.svgParabolaPath = (a, b, c) => {
|
|
8789
|
+
const computeParabola = function computeParabola(x) {
|
|
8790
|
+
return (a * x + b) * x + c;
|
|
8791
|
+
};
|
|
8744
8792
|
|
|
8745
|
-
|
|
8746
|
-
|
|
8747
|
-
|
|
8748
|
-
|
|
8749
|
-
|
|
8750
|
-
* Safari.
|
|
8751
|
-
*
|
|
8752
|
-
* Usage `$.extend({}, someStyles, SVG_SPECIFIC_STYLE_MASK)`
|
|
8753
|
-
*/
|
|
8754
|
-
const SVG_SPECIFIC_STYLE_MASK = {
|
|
8755
|
-
"stroke-width": null
|
|
8756
|
-
};
|
|
8757
|
-
GraphUtils.createGraphie = function (el) {
|
|
8758
|
-
let xScale = 40;
|
|
8759
|
-
let yScale = 40;
|
|
8760
|
-
let xRange;
|
|
8761
|
-
let yRange;
|
|
8762
|
-
$(el).css("position", "relative");
|
|
8763
|
-
const raphael$1 = raphael.exports(el);
|
|
8764
|
-
|
|
8765
|
-
// For a sometimes-reproducible IE8 bug; doesn't affect SVG browsers at all
|
|
8766
|
-
$(el).children("div").css("position", "absolute");
|
|
8767
|
-
|
|
8768
|
-
// Set up some reasonable defaults
|
|
8769
|
-
let currentStyle = {
|
|
8770
|
-
"stroke-width": 2,
|
|
8771
|
-
fill: "none"
|
|
8772
|
-
};
|
|
8773
|
-
const scaleVector = function scaleVector(point) {
|
|
8774
|
-
if (typeof point === "number") {
|
|
8775
|
-
return scaleVector([point, point]);
|
|
8776
|
-
}
|
|
8777
|
-
const x = point[0];
|
|
8778
|
-
const y = point[1];
|
|
8779
|
-
return [x * xScale, y * yScale];
|
|
8780
|
-
};
|
|
8781
|
-
const scalePoint = function scalePoint(point) {
|
|
8782
|
-
if (typeof point === "number") {
|
|
8783
|
-
return scalePoint([point, point]);
|
|
8784
|
-
}
|
|
8785
|
-
const x = point[0];
|
|
8786
|
-
const y = point[1];
|
|
8787
|
-
return [(x - xRange[0]) * xScale, (yRange[1] - y) * yScale];
|
|
8788
|
-
};
|
|
8789
|
-
const unscalePoint = function unscalePoint(point) {
|
|
8790
|
-
if (typeof point === "number") {
|
|
8791
|
-
return unscalePoint([point, point]);
|
|
8792
|
-
}
|
|
8793
|
-
const x = point[0];
|
|
8794
|
-
const y = point[1];
|
|
8795
|
-
return [x / xScale + xRange[0], yRange[1] - y / yScale];
|
|
8796
|
-
};
|
|
8797
|
-
const unscaleVector = function unscaleVector(point) {
|
|
8798
|
-
if (typeof point === "number") {
|
|
8799
|
-
return unscaleVector([point, point]);
|
|
8800
|
-
}
|
|
8801
|
-
return [point[0] / xScale, point[1] / yScale];
|
|
8802
|
-
};
|
|
8803
|
-
const setLabelMargins = function setLabelMargins(span, size) {
|
|
8804
|
-
const $span = $(span);
|
|
8805
|
-
const direction = $span.data("labelDirection");
|
|
8806
|
-
let [width, height] = size;
|
|
8807
|
-
// This can happen when a span
|
|
8808
|
-
// is invisible but we still want to update the CSS. At worst, we will
|
|
8809
|
-
// be off by a few pixels instead of in a different position entirely.
|
|
8810
|
-
if (width === 0 && height === 0) {
|
|
8811
|
-
[width, height] = [1, 1];
|
|
8812
|
-
Log.log("Label size was 0x0 in graphie.js; using 1x1 instead");
|
|
8813
|
-
}
|
|
8814
|
-
$span.css("visibility", "");
|
|
8815
|
-
if (typeof direction === "number") {
|
|
8816
|
-
const x = Math.cos(direction);
|
|
8817
|
-
const y = Math.sin(direction);
|
|
8818
|
-
const scale = Math.min(width / 2 / Math.abs(x), height / 2 / Math.abs(y));
|
|
8819
|
-
$span.css({
|
|
8820
|
-
marginLeft: -width / 2 + x * scale,
|
|
8821
|
-
marginTop: -height / 2 - y * scale
|
|
8822
|
-
});
|
|
8823
|
-
} else {
|
|
8824
|
-
const multipliers = labelDirections[direction || "center"];
|
|
8825
|
-
$span.css({
|
|
8826
|
-
marginLeft: Math.round(width * multipliers[0]),
|
|
8827
|
-
marginTop: Math.round(height * multipliers[1])
|
|
8828
|
-
});
|
|
8829
|
-
}
|
|
8830
|
-
};
|
|
8831
|
-
const svgPath = function svgPath(points, alreadyScaled) {
|
|
8832
|
-
return $.map(points, function (point, i) {
|
|
8833
|
-
if (point === true) {
|
|
8834
|
-
return "z";
|
|
8793
|
+
// If points are collinear, plot a line instead
|
|
8794
|
+
if (a === 0) {
|
|
8795
|
+
const _points = [[this.bounds().xMin, computeParabola(this.bounds().xMin)], [this.bounds().xMax, computeParabola(this.bounds().xMax)]];
|
|
8796
|
+
// @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
|
|
8797
|
+
return this.svgPath(_points);
|
|
8835
8798
|
}
|
|
8836
|
-
const scaled = alreadyScaled ? point : scalePoint(point);
|
|
8837
|
-
return (i === 0 ? "M" : "L") + KhanMath.bound(scaled[0]) + " " + KhanMath.bound(scaled[1]);
|
|
8838
|
-
}).join("");
|
|
8839
|
-
};
|
|
8840
|
-
const svgParabolaPath = function svgParabolaPath(a, b, c) {
|
|
8841
|
-
const computeParabola = function computeParabola(x) {
|
|
8842
|
-
return (a * x + b) * x + c;
|
|
8843
|
-
};
|
|
8844
8799
|
|
|
8845
|
-
|
|
8846
|
-
|
|
8847
|
-
const
|
|
8848
|
-
return [x, computeParabola(x)];
|
|
8849
|
-
});
|
|
8850
|
-
// @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
|
|
8851
|
-
return svgPath(_points);
|
|
8852
|
-
}
|
|
8800
|
+
// Calculate x coordinates of points on parabola
|
|
8801
|
+
const xVertex = -b / (2 * a);
|
|
8802
|
+
const distToEdge = Math.max(Math.abs(xVertex - this.bounds().xMin), Math.abs(xVertex - this.bounds().xMax));
|
|
8853
8803
|
|
|
8854
|
-
|
|
8855
|
-
|
|
8856
|
-
|
|
8804
|
+
// To guarantee that drawn parabola to spans the viewport, use a point
|
|
8805
|
+
// on the edge of the graph furtherest from the vertex
|
|
8806
|
+
const xPoint = xVertex + distToEdge;
|
|
8857
8807
|
|
|
8858
|
-
|
|
8859
|
-
|
|
8860
|
-
|
|
8808
|
+
// Compute parabola and other point on the curve
|
|
8809
|
+
const vertex = [xVertex, computeParabola(xVertex)];
|
|
8810
|
+
const point = [xPoint, computeParabola(xPoint)];
|
|
8861
8811
|
|
|
8862
|
-
|
|
8863
|
-
|
|
8864
|
-
const point = [xPoint, computeParabola(xPoint)];
|
|
8812
|
+
// Calculate SVG 'control' point, defined by spec
|
|
8813
|
+
const control = [vertex[0], vertex[1] - (point[1] - vertex[1])];
|
|
8865
8814
|
|
|
8866
|
-
|
|
8867
|
-
|
|
8815
|
+
// Calculate mirror points across parabola's axis of symmetry
|
|
8816
|
+
const dx = Math.abs(vertex[0] - point[0]);
|
|
8817
|
+
const left = [vertex[0] - dx, point[1]];
|
|
8818
|
+
const right = [vertex[0] + dx, point[1]];
|
|
8868
8819
|
|
|
8869
|
-
|
|
8870
|
-
|
|
8871
|
-
|
|
8872
|
-
|
|
8820
|
+
// Scale and bound
|
|
8821
|
+
// @ts-expect-error - TS2345 - Argument of type '(point: number | Coord) => Coord' is not assignable to parameter of type 'Iteratee<any[][], any, any[]>'.
|
|
8822
|
+
const points = _.map([left, control, right], this.scalePoint);
|
|
8823
|
+
const values = _.map(_.flatten(points), KhanMath.bound);
|
|
8824
|
+
return "M" + values[0] + "," + values[1] + " Q" + values[2] + "," + values[3] + " " + values[4] + "," + values[5];
|
|
8825
|
+
};
|
|
8826
|
+
this.svgSinusoidPath = (a, b, c, d) => {
|
|
8827
|
+
// Plot a sinusoid of the form: f(x) = a * sin(b * x - c) + d
|
|
8828
|
+
const quarterPeriod = Math.abs(Math.PI / (2 * b));
|
|
8829
|
+
const computeSine = function computeSine(x) {
|
|
8830
|
+
return a * Math.sin(b * x - c) + d;
|
|
8831
|
+
};
|
|
8832
|
+
const computeDerivative = function computeDerivative(x) {
|
|
8833
|
+
return a * b * Math.cos(c - b * x);
|
|
8834
|
+
};
|
|
8835
|
+
const coordsForOffset = (initial, i) => {
|
|
8836
|
+
// Return the cubic coordinates (including the two anchor and two
|
|
8837
|
+
// control points) for the ith portion of the sinusoid.
|
|
8838
|
+
const x0 = initial + quarterPeriod * i;
|
|
8839
|
+
const x1 = x0 + quarterPeriod;
|
|
8840
|
+
|
|
8841
|
+
// Interpolate using derivative technique
|
|
8842
|
+
// See: http://stackoverflow.com/questions/13932704/how-to-draw-sine-waves-with-svg-js
|
|
8843
|
+
const xCoords = [x0, x0 * 2 / 3 + x1 * 1 / 3, x0 * 1 / 3 + x1 * 2 / 3, x1];
|
|
8844
|
+
const yCoords = [computeSine(x0), computeSine(x0) + computeDerivative(x0) * (x1 - x0) / 3, computeSine(x1) - computeDerivative(x1) * (x1 - x0) / 3, computeSine(x1)];
|
|
8845
|
+
|
|
8846
|
+
// Zip and scale
|
|
8847
|
+
// @ts-expect-error - TS2345 - Argument of type '(point: number | Coord) => Coord' is not assignable to parameter of type 'Iteratee<any[][], any, any[]>'.
|
|
8848
|
+
return _.map(_.zip(xCoords, yCoords), this.scalePoint);
|
|
8849
|
+
};
|
|
8873
8850
|
|
|
8874
|
-
|
|
8875
|
-
|
|
8876
|
-
|
|
8877
|
-
|
|
8878
|
-
|
|
8879
|
-
|
|
8880
|
-
|
|
8881
|
-
|
|
8882
|
-
|
|
8883
|
-
|
|
8884
|
-
|
|
8851
|
+
// How many quarter-periods do we need to span the graph?
|
|
8852
|
+
const extent = this.bounds().width();
|
|
8853
|
+
const numQuarterPeriods = Math.ceil(extent / quarterPeriod) + 1;
|
|
8854
|
+
|
|
8855
|
+
// Find starting coordinate: first anchor point curve left of bounds.xMin
|
|
8856
|
+
let initial = c / b;
|
|
8857
|
+
const distToEdge = initial - this.bounds().xMin;
|
|
8858
|
+
initial -= quarterPeriod * Math.ceil(distToEdge / quarterPeriod);
|
|
8859
|
+
|
|
8860
|
+
// First portion of path is special-case, requiring move-to ('M')
|
|
8861
|
+
let coords = coordsForOffset(initial, 0);
|
|
8862
|
+
let path = "M" + coords[0][0] + "," + coords[0][1] + " C" + coords[1][0] + "," + coords[1][1] + " " + coords[2][0] + "," + coords[2][1] + " " + coords[3][0] + "," + coords[3][1];
|
|
8863
|
+
for (let i = 1; i < numQuarterPeriods; i++) {
|
|
8864
|
+
coords = coordsForOffset(initial, i);
|
|
8865
|
+
path += " C" + coords[1][0] + "," + coords[1][1] + " " + coords[2][0] + "," + coords[2][1] + " " + coords[3][0] + "," + coords[3][1];
|
|
8866
|
+
}
|
|
8867
|
+
return path;
|
|
8868
|
+
};
|
|
8869
|
+
this.scalePoint = point => {
|
|
8870
|
+
return this.drawingTransform().scalePoint(point);
|
|
8885
8871
|
};
|
|
8886
|
-
|
|
8887
|
-
return
|
|
8872
|
+
this.scaleVector = point => {
|
|
8873
|
+
return this.drawingTransform().scaleVector(point);
|
|
8888
8874
|
};
|
|
8889
|
-
|
|
8890
|
-
|
|
8891
|
-
|
|
8892
|
-
|
|
8893
|
-
|
|
8875
|
+
this.unscalePoint = point => {
|
|
8876
|
+
return this.drawingTransform().unscalePoint(point);
|
|
8877
|
+
};
|
|
8878
|
+
this.unscaleVector = point => {
|
|
8879
|
+
return this.drawingTransform().unscaleVector(point);
|
|
8880
|
+
};
|
|
8881
|
+
this.el = el;
|
|
8882
|
+
$(el).css("position", "relative");
|
|
8883
|
+
this.raphael = raphael.exports(el);
|
|
8894
8884
|
|
|
8895
|
-
|
|
8896
|
-
|
|
8897
|
-
|
|
8898
|
-
|
|
8885
|
+
// For a sometimes-reproducible IE8 bug; doesn't affect SVG browsers at all
|
|
8886
|
+
$(el).children("div").css("position", "absolute");
|
|
8887
|
+
}
|
|
8888
|
+
init(options) {
|
|
8889
|
+
let scale = options.scale || [40, 40];
|
|
8890
|
+
scale = typeof scale === "number" ? [scale, scale] : scale;
|
|
8891
|
+
if (options.range == null) {
|
|
8892
|
+
throw new PerseusError("range should be specified in graph init", Errors.Internal);
|
|
8893
|
+
}
|
|
8894
|
+
_classPrivateFieldBase(this, _bounds)[_bounds] = new GraphBounds(...options.range);
|
|
8895
|
+
_classPrivateFieldBase(this, _drawingTransform)[_drawingTransform] = new DrawingTransform(this.raphael, scale, this.bounds());
|
|
8896
|
+
const [w, h] = this.drawingTransform().canvasDimensions();
|
|
8897
|
+
$(this.el).css({
|
|
8898
|
+
width: w,
|
|
8899
|
+
height: h
|
|
8900
|
+
});
|
|
8901
|
+
this.range = options.range;
|
|
8902
|
+
this.scale = scale;
|
|
8903
|
+
// TODO(benchristel): I don't think dimensions is used. Can we
|
|
8904
|
+
// remove it?
|
|
8905
|
+
this.dimensions = [w, h];
|
|
8906
|
+
this.xpixels = w;
|
|
8907
|
+
this.ypixels = h;
|
|
8908
|
+
this.isMobile = options.isMobile;
|
|
8909
|
+
return this;
|
|
8910
|
+
}
|
|
8899
8911
|
|
|
8900
|
-
|
|
8901
|
-
|
|
8902
|
-
|
|
8912
|
+
// Initializes graphie settings for a graph and draws the basic graph
|
|
8913
|
+
// features (axes, grid, tick marks, and axis labels)
|
|
8914
|
+
// Options expected are:
|
|
8915
|
+
// - range: [[a, b], [c, d]] or [a, b]
|
|
8916
|
+
// - scale: [a, b] or number
|
|
8917
|
+
// - gridOpacity: number (0 - 1)
|
|
8918
|
+
// - gridStep: [a, b] or number (relative to units)
|
|
8919
|
+
// - tickStep: [a, b] or number (relative to grid steps)
|
|
8920
|
+
// - tickLen: [a, b] or number (in pixels)
|
|
8921
|
+
// - labelStep: [a, b] or number (relative to tick steps)
|
|
8922
|
+
// - yLabelFormat: fn to format label string for y-axis
|
|
8923
|
+
// - xLabelFormat: fn to format label string for x-axis
|
|
8924
|
+
graphInit(options) {
|
|
8925
|
+
options = options || {};
|
|
8926
|
+
$.each(options, function (prop, val) {
|
|
8927
|
+
// allow options to be specified by a single number for shorthand if
|
|
8928
|
+
// the horizontal and vertical components are the same
|
|
8929
|
+
if (
|
|
8930
|
+
// @ts-expect-error - TS2339 - Property 'match' does not exist on type 'string | number | symbol'.
|
|
8931
|
+
!prop.match(/.*Opacity$/) && prop !== "range" && typeof val === "number") {
|
|
8932
|
+
options[prop] = [val, val];
|
|
8933
|
+
}
|
|
8934
|
+
|
|
8935
|
+
// allow symmetric ranges to be specified by the absolute values
|
|
8936
|
+
if (prop === "range" || prop === "gridRange") {
|
|
8937
|
+
if (val.constructor === Array) {
|
|
8938
|
+
// but don't mandate symmetric ranges
|
|
8939
|
+
if (val[0].constructor !== Array) {
|
|
8940
|
+
options[prop] = [[-val[0], val[0]], [-val[1], val[1]]];
|
|
8941
|
+
}
|
|
8942
|
+
} else if (typeof val === "number") {
|
|
8943
|
+
options[prop] = [[-val, val], [-val, val]];
|
|
8944
|
+
}
|
|
8945
|
+
}
|
|
8946
|
+
});
|
|
8947
|
+
const range = options.range || [[-10, 10], [-10, 10]];
|
|
8948
|
+
const gridRange = options.gridRange || options.range;
|
|
8949
|
+
const scale = options.scale || [20, 20];
|
|
8950
|
+
const grid = options.grid != null ? options.grid : true;
|
|
8951
|
+
const gridOpacity = options.gridOpacity || 0.1;
|
|
8952
|
+
const gridStep = options.gridStep || [1, 1];
|
|
8953
|
+
const axes = options.axes != null ? options.axes : true;
|
|
8954
|
+
const axisArrows = options.axisArrows || "";
|
|
8955
|
+
const axisOpacity = options.axisOpacity || 1.0;
|
|
8956
|
+
const axisCenter = options.axisCenter || [Math.min(Math.max(range[0][0], 0), range[0][1]), Math.min(Math.max(range[1][0], 0), range[1][1])];
|
|
8957
|
+
const axisLabels = options.axisLabels != null ? options.axisLabels : false;
|
|
8958
|
+
const ticks = options.ticks != null ? options.ticks : true;
|
|
8959
|
+
const tickStep = options.tickStep || [2, 2];
|
|
8960
|
+
const tickLen = options.tickLen || [5, 5];
|
|
8961
|
+
const tickOpacity = options.tickOpacity || 1.0;
|
|
8962
|
+
const labels = options.labels || options.labelStep || false;
|
|
8963
|
+
const labelStep = options.labelStep || [1, 1];
|
|
8964
|
+
const labelOpacity = options.labelOpacity || 1.0;
|
|
8965
|
+
let unityLabels = options.unityLabels || false;
|
|
8966
|
+
const labelFormat = options.labelFormat || function (a) {
|
|
8967
|
+
return a;
|
|
8968
|
+
};
|
|
8969
|
+
let xLabelFormat = options.xLabelFormat || labelFormat;
|
|
8970
|
+
let yLabelFormat = options.yLabelFormat || labelFormat;
|
|
8971
|
+
const realRange = [[range[0][0] - (range[0][0] > 0 ? 1 : 0), range[0][1] + (range[0][1] < 0 ? 1 : 0)], [range[1][0] - (range[1][0] > 0 ? 1 : 0), range[1][1] + (range[1][1] < 0 ? 1 : 0)]];
|
|
8972
|
+
if (!_.isArray(unityLabels)) {
|
|
8973
|
+
unityLabels = [unityLabels, unityLabels];
|
|
8974
|
+
}
|
|
8975
|
+
const minusIgnorer = function minusIgnorer(lf) {
|
|
8976
|
+
return function (a) {
|
|
8977
|
+
return (lf(a) + "").replace(/-(\d)/g, "\\llap{-}$1");
|
|
8978
|
+
};
|
|
8903
8979
|
};
|
|
8980
|
+
xLabelFormat = minusIgnorer(xLabelFormat);
|
|
8981
|
+
yLabelFormat = minusIgnorer(yLabelFormat);
|
|
8982
|
+
this.init({
|
|
8983
|
+
range: realRange,
|
|
8984
|
+
scale: scale,
|
|
8985
|
+
isMobile: options.isMobile
|
|
8986
|
+
});
|
|
8904
8987
|
|
|
8905
|
-
//
|
|
8906
|
-
|
|
8907
|
-
|
|
8988
|
+
// draw grid
|
|
8989
|
+
if (grid) {
|
|
8990
|
+
this.grid(gridRange[0], gridRange[1], {
|
|
8991
|
+
stroke: options.isMobile ? KhanColors.GRAY_C : "#000000",
|
|
8992
|
+
opacity: options.isMobile ? 1 : gridOpacity,
|
|
8993
|
+
step: gridStep,
|
|
8994
|
+
strokeWidth: options.isMobile ? 1 : 2
|
|
8995
|
+
});
|
|
8996
|
+
}
|
|
8908
8997
|
|
|
8909
|
-
//
|
|
8910
|
-
|
|
8911
|
-
|
|
8912
|
-
|
|
8998
|
+
// draw axes
|
|
8999
|
+
if (axes) {
|
|
9000
|
+
// this is a slight hack until <-> arrowheads work
|
|
9001
|
+
if (axisArrows === "<->" || axisArrows === true) {
|
|
9002
|
+
const thisGraphie = this;
|
|
9003
|
+
this.style({
|
|
9004
|
+
stroke: options.isMobile ? KhanColors.GRAY_G : "#000000",
|
|
9005
|
+
opacity: options.isMobile ? 1 : axisOpacity,
|
|
9006
|
+
strokeWidth: options.isMobile ? 1 : 2,
|
|
9007
|
+
arrows: "->"
|
|
9008
|
+
}, function () {
|
|
9009
|
+
if (range[1][0] < 0 && range[1][1] > 0) {
|
|
9010
|
+
thisGraphie.path([axisCenter, [gridRange[0][0], axisCenter[1]]]);
|
|
9011
|
+
thisGraphie.path([axisCenter, [gridRange[0][1], axisCenter[1]]]);
|
|
9012
|
+
}
|
|
9013
|
+
if (range[0][0] < 0 && range[0][1] > 0) {
|
|
9014
|
+
thisGraphie.path([axisCenter, [axisCenter[0], gridRange[1][0]]]);
|
|
9015
|
+
thisGraphie.path([axisCenter, [axisCenter[0], gridRange[1][1]]]);
|
|
9016
|
+
}
|
|
9017
|
+
});
|
|
8913
9018
|
|
|
8914
|
-
|
|
8915
|
-
|
|
8916
|
-
|
|
8917
|
-
|
|
8918
|
-
|
|
8919
|
-
|
|
9019
|
+
// also, we don't support "<-" arrows yet, but why you
|
|
9020
|
+
// would want that on your graph is beyond me.
|
|
9021
|
+
} else if (axisArrows === "->" || axisArrows === "") {
|
|
9022
|
+
const thisGraphie = this;
|
|
9023
|
+
this.style({
|
|
9024
|
+
stroke: "#000000",
|
|
9025
|
+
opacity: axisOpacity,
|
|
9026
|
+
strokeWidth: 2,
|
|
9027
|
+
arrows: axisArrows
|
|
9028
|
+
}, function () {
|
|
9029
|
+
thisGraphie.path([[gridRange[0][0], axisCenter[1]], [gridRange[0][1], axisCenter[1]]]);
|
|
9030
|
+
thisGraphie.path([[axisCenter[0], gridRange[1][0]], [axisCenter[0], gridRange[1][1]]]);
|
|
9031
|
+
});
|
|
9032
|
+
}
|
|
9033
|
+
if (axisLabels && axisLabels.length === 2) {
|
|
9034
|
+
this.label([gridRange[0][1], axisCenter[1]], axisLabels[0], "right");
|
|
9035
|
+
this.label([axisCenter[0], gridRange[1][1]], axisLabels[1], "above");
|
|
9036
|
+
}
|
|
8920
9037
|
}
|
|
8921
|
-
return path;
|
|
8922
|
-
};
|
|
8923
9038
|
|
|
8924
|
-
|
|
8925
|
-
|
|
8926
|
-
|
|
8927
|
-
|
|
8928
|
-
|
|
8929
|
-
|
|
8930
|
-
|
|
9039
|
+
// draw tick marks
|
|
9040
|
+
if (ticks) {
|
|
9041
|
+
const halfWidthTicks = options.isMobile;
|
|
9042
|
+
const thisGraphie = this;
|
|
9043
|
+
this.style({
|
|
9044
|
+
stroke: options.isMobile ? KhanColors.GRAY_G : "#000000",
|
|
9045
|
+
opacity: options.isMobile ? 1 : tickOpacity,
|
|
9046
|
+
strokeWidth: 1
|
|
9047
|
+
}, function () {
|
|
9048
|
+
// horizontal axis
|
|
9049
|
+
let step = gridStep[0] * tickStep[0];
|
|
9050
|
+
let len = tickLen[0] / scale[1];
|
|
9051
|
+
let start = gridRange[0][0];
|
|
9052
|
+
let stop = gridRange[0][1];
|
|
9053
|
+
if (range[1][0] < 0 && range[1][1] > 0) {
|
|
9054
|
+
for (let x = step + axisCenter[0]; x <= stop; x += step) {
|
|
9055
|
+
if (x < stop || !axisArrows) {
|
|
9056
|
+
thisGraphie.line([x, -len + axisCenter[1]], [x, halfWidthTicks ? 0 : len + axisCenter[1]]);
|
|
9057
|
+
}
|
|
9058
|
+
}
|
|
9059
|
+
for (let x = -step + axisCenter[0]; x >= start; x -= step) {
|
|
9060
|
+
if (x > start || !axisArrows) {
|
|
9061
|
+
thisGraphie.line([x, -len + axisCenter[1]], [x, halfWidthTicks ? 0 : len + axisCenter[1]]);
|
|
9062
|
+
}
|
|
9063
|
+
}
|
|
8931
9064
|
}
|
|
8932
|
-
xScale = scale[0];
|
|
8933
|
-
yScale = scale[1];
|
|
8934
9065
|
|
|
8935
|
-
//
|
|
8936
|
-
|
|
9066
|
+
// vertical axis
|
|
9067
|
+
step = gridStep[1] * tickStep[1];
|
|
9068
|
+
len = tickLen[1] / scale[0];
|
|
9069
|
+
start = gridRange[1][0];
|
|
9070
|
+
stop = gridRange[1][1];
|
|
9071
|
+
if (range[0][0] < 0 && range[0][1] > 0) {
|
|
9072
|
+
for (let y = step + axisCenter[1]; y <= stop; y += step) {
|
|
9073
|
+
if (y < stop || !axisArrows) {
|
|
9074
|
+
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
9075
|
+
this.line([-len + axisCenter[0], y], [halfWidthTicks ? 0 : len + axisCenter[0], y]);
|
|
9076
|
+
}
|
|
9077
|
+
}
|
|
9078
|
+
for (let y = -step + axisCenter[1]; y >= start; y -= step) {
|
|
9079
|
+
if (y > start || !axisArrows) {
|
|
9080
|
+
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
9081
|
+
this.line([-len + axisCenter[0], y], [halfWidthTicks ? 0 : len + axisCenter[0], y]);
|
|
9082
|
+
}
|
|
9083
|
+
}
|
|
9084
|
+
}
|
|
9085
|
+
});
|
|
9086
|
+
}
|
|
9087
|
+
|
|
9088
|
+
// draw axis labels
|
|
9089
|
+
if (labels) {
|
|
9090
|
+
const thisGraphie = this;
|
|
9091
|
+
this.style({
|
|
9092
|
+
stroke: options.isMobile ? KhanColors.GRAY_G : "#000000",
|
|
9093
|
+
opacity: options.isMobile ? 1 : labelOpacity
|
|
9094
|
+
}, function () {
|
|
9095
|
+
// horizontal axis
|
|
9096
|
+
let step = gridStep[0] * tickStep[0] * labelStep[0];
|
|
9097
|
+
let start = gridRange[0][0];
|
|
9098
|
+
let stop = gridRange[0][1];
|
|
9099
|
+
const xAxisPosition = axisCenter[0] < 0 ? "above" : "below";
|
|
9100
|
+
const yAxisPosition = axisCenter[0] < 0 ? "right" : "left";
|
|
9101
|
+
const xShowZero = axisCenter[0] === 0 && axisCenter[1] !== 0;
|
|
9102
|
+
const yShowZero = axisCenter[0] !== 0 && axisCenter[1] === 0;
|
|
9103
|
+
const axisOffCenter = axisCenter[0] !== 0 || axisCenter[1] !== 0;
|
|
9104
|
+
const showUnityX = unityLabels[0] || axisOffCenter;
|
|
9105
|
+
const showUnityY = unityLabels[1] || axisOffCenter;
|
|
9106
|
+
|
|
9107
|
+
// positive x-axis
|
|
9108
|
+
for (let x = (xShowZero ? 0 : step) + axisCenter[0]; x <= stop; x += step) {
|
|
9109
|
+
if (x < stop || !axisArrows) {
|
|
9110
|
+
thisGraphie.label([x, axisCenter[1]], xLabelFormat(x), xAxisPosition);
|
|
9111
|
+
}
|
|
9112
|
+
}
|
|
9113
|
+
|
|
9114
|
+
// negative x-axis
|
|
9115
|
+
for (let x = -step * (showUnityX ? 1 : 2) + axisCenter[0]; x >= start; x -= step) {
|
|
9116
|
+
if (x > start || !axisArrows) {
|
|
9117
|
+
thisGraphie.label([x, axisCenter[1]], xLabelFormat(x), xAxisPosition);
|
|
9118
|
+
}
|
|
9119
|
+
}
|
|
9120
|
+
step = gridStep[1] * tickStep[1] * labelStep[1];
|
|
9121
|
+
start = gridRange[1][0];
|
|
9122
|
+
stop = gridRange[1][1];
|
|
9123
|
+
|
|
9124
|
+
// positive y-axis
|
|
9125
|
+
for (let y = (yShowZero ? 0 : step) + axisCenter[1]; y <= stop; y += step) {
|
|
9126
|
+
if (y < stop || !axisArrows) {
|
|
9127
|
+
thisGraphie.label([axisCenter[0], y], yLabelFormat(y), yAxisPosition);
|
|
9128
|
+
}
|
|
9129
|
+
}
|
|
9130
|
+
|
|
9131
|
+
// negative y-axis
|
|
9132
|
+
for (let y = -step * (showUnityY ? 1 : 2) + axisCenter[1]; y >= start; y -= step) {
|
|
9133
|
+
if (y > start || !axisArrows) {
|
|
9134
|
+
thisGraphie.label([axisCenter[0], y], yLabelFormat(y), yAxisPosition);
|
|
9135
|
+
}
|
|
9136
|
+
}
|
|
9137
|
+
});
|
|
9138
|
+
}
|
|
9139
|
+
}
|
|
9140
|
+
drawingTransform() {
|
|
9141
|
+
if (_classPrivateFieldBase(this, _drawingTransform)[_drawingTransform] == null) {
|
|
9142
|
+
throw new Error("Can't get drawingTransform of an uninitialized Graphie");
|
|
9143
|
+
}
|
|
9144
|
+
return _classPrivateFieldBase(this, _drawingTransform)[_drawingTransform];
|
|
9145
|
+
}
|
|
9146
|
+
bounds() {
|
|
9147
|
+
if (_classPrivateFieldBase(this, _bounds)[_bounds] == null) {
|
|
9148
|
+
throw new Error("Can't get bounds of an uninitialized Graphie");
|
|
9149
|
+
}
|
|
9150
|
+
return _classPrivateFieldBase(this, _bounds)[_bounds];
|
|
9151
|
+
}
|
|
9152
|
+
style(attrs, fn) {
|
|
9153
|
+
const processed = this.processAttributes(attrs);
|
|
9154
|
+
if (typeof fn === "function") {
|
|
9155
|
+
const oldStyle = this.currentStyle;
|
|
9156
|
+
this.currentStyle = $.extend({}, this.currentStyle, processed);
|
|
9157
|
+
const result = fn.call(this);
|
|
9158
|
+
this.currentStyle = oldStyle;
|
|
9159
|
+
return result;
|
|
9160
|
+
}
|
|
9161
|
+
$.extend(this.currentStyle, processed);
|
|
9162
|
+
}
|
|
9163
|
+
label(point, text, direction, latex) {}
|
|
9164
|
+
grid(xr, yr, styleAttributes) {}
|
|
9165
|
+
|
|
9166
|
+
// path is a stub that gets overwritten with a function from drawingTools
|
|
9167
|
+
// in createGraphie
|
|
9168
|
+
path(points) {}
|
|
9169
|
+
|
|
9170
|
+
// line is a stub that gets overwritten with a function from drawingTools
|
|
9171
|
+
// in createGraphie
|
|
9172
|
+
line(start, end) {}
|
|
9173
|
+
processAttributes(attrs) {
|
|
9174
|
+
const transformers = {
|
|
9175
|
+
scale: scale => {
|
|
9176
|
+
this.drawingTransform().setScale(scale);
|
|
8937
9177
|
},
|
|
8938
|
-
clipRect:
|
|
9178
|
+
clipRect: pair => {
|
|
8939
9179
|
const point = pair[0];
|
|
8940
9180
|
const size = pair[1];
|
|
8941
9181
|
point[1] += size[1]; // because our coordinates are flipped
|
|
8942
9182
|
|
|
8943
9183
|
return {
|
|
8944
|
-
"clip-rect": scalePoint(point).concat(scaleVector(size)).join(" ")
|
|
9184
|
+
"clip-rect": this.scalePoint(point).concat(this.scaleVector(size)).join(" ")
|
|
8945
9185
|
};
|
|
8946
9186
|
},
|
|
8947
9187
|
strokeWidth: function (val) {
|
|
@@ -8949,18 +9189,18 @@ GraphUtils.createGraphie = function (el) {
|
|
|
8949
9189
|
"stroke-width": parseFloat(val)
|
|
8950
9190
|
};
|
|
8951
9191
|
},
|
|
8952
|
-
rx:
|
|
9192
|
+
rx: val => {
|
|
8953
9193
|
return {
|
|
8954
|
-
rx: scaleVector([val, 0])[0]
|
|
9194
|
+
rx: this.scaleVector([val, 0])[0]
|
|
8955
9195
|
};
|
|
8956
9196
|
},
|
|
8957
|
-
ry:
|
|
9197
|
+
ry: val => {
|
|
8958
9198
|
return {
|
|
8959
|
-
ry: scaleVector([0, val])[1]
|
|
9199
|
+
ry: this.scaleVector([0, val])[1]
|
|
8960
9200
|
};
|
|
8961
9201
|
},
|
|
8962
|
-
r:
|
|
8963
|
-
const scaled = scaleVector([val, val]);
|
|
9202
|
+
r: val => {
|
|
9203
|
+
const scaled = this.scaleVector([val, val]);
|
|
8964
9204
|
return {
|
|
8965
9205
|
rx: scaled[0],
|
|
8966
9206
|
ry: scaled[1]
|
|
@@ -8980,7 +9220,66 @@ GraphUtils.createGraphie = function (el) {
|
|
|
8980
9220
|
}
|
|
8981
9221
|
});
|
|
8982
9222
|
return processed;
|
|
9223
|
+
}
|
|
9224
|
+
}
|
|
9225
|
+
GraphUtils.Graphie = Graphie$1;
|
|
9226
|
+
const labelDirections = {
|
|
9227
|
+
center: [-0.5, -0.5],
|
|
9228
|
+
above: [-0.5, -1.0],
|
|
9229
|
+
"above right": [0.0, -1.0],
|
|
9230
|
+
right: [0.0, -0.5],
|
|
9231
|
+
"below right": [0.0, 0.0],
|
|
9232
|
+
below: [-0.5, 0.0],
|
|
9233
|
+
"below left": [-1.0, 0.0],
|
|
9234
|
+
left: [-1.0, -0.5],
|
|
9235
|
+
"above left": [-1.0, -1.0]
|
|
9236
|
+
};
|
|
9237
|
+
|
|
9238
|
+
/**
|
|
9239
|
+
* Safari applies some SVG-specific styles to things that are not SVGs, so we
|
|
9240
|
+
* need to exclude those styles from things that are not SVGs.
|
|
9241
|
+
*
|
|
9242
|
+
* To see this behavior in action, open https://codepen.io/anon/pen/zENEoa in
|
|
9243
|
+
* Safari.
|
|
9244
|
+
*
|
|
9245
|
+
* Usage `$.extend({}, someStyles, SVG_SPECIFIC_STYLE_MASK)`
|
|
9246
|
+
*/
|
|
9247
|
+
const SVG_SPECIFIC_STYLE_MASK = {
|
|
9248
|
+
"stroke-width": null
|
|
9249
|
+
};
|
|
9250
|
+
GraphUtils.createGraphie = function (el) {
|
|
9251
|
+
const thisGraphie = new Graphie$1(el);
|
|
9252
|
+
const setLabelMargins = function setLabelMargins(span, size) {
|
|
9253
|
+
const $span = $(span);
|
|
9254
|
+
const direction = $span.data("labelDirection");
|
|
9255
|
+
let [width, height] = size;
|
|
9256
|
+
// This can happen when a span
|
|
9257
|
+
// is invisible but we still want to update the CSS. At worst, we will
|
|
9258
|
+
// be off by a few pixels instead of in a different position entirely.
|
|
9259
|
+
if (width === 0 && height === 0) {
|
|
9260
|
+
[width, height] = [1, 1];
|
|
9261
|
+
Log.log("Label size was 0x0 in graphie.js; using 1x1 instead");
|
|
9262
|
+
}
|
|
9263
|
+
$span.css("visibility", "");
|
|
9264
|
+
if (typeof direction === "number") {
|
|
9265
|
+
const x = Math.cos(direction);
|
|
9266
|
+
const y = Math.sin(direction);
|
|
9267
|
+
const scale = Math.min(width / 2 / Math.abs(x), height / 2 / Math.abs(y));
|
|
9268
|
+
$span.css({
|
|
9269
|
+
marginLeft: -width / 2 + x * scale,
|
|
9270
|
+
marginTop: -height / 2 - y * scale
|
|
9271
|
+
});
|
|
9272
|
+
} else {
|
|
9273
|
+
const multipliers = labelDirections[direction || "center"];
|
|
9274
|
+
$span.css({
|
|
9275
|
+
marginLeft: Math.round(width * multipliers[0]),
|
|
9276
|
+
marginTop: Math.round(height * multipliers[1])
|
|
9277
|
+
});
|
|
9278
|
+
}
|
|
8983
9279
|
};
|
|
9280
|
+
|
|
9281
|
+
// `svgPath` is independent of graphie range, so we export it independently
|
|
9282
|
+
GraphUtils.svgPath = thisGraphie.svgPath;
|
|
8984
9283
|
const addArrowheads = function arrows(path) {
|
|
8985
9284
|
const type = path.constructor.prototype;
|
|
8986
9285
|
if (type === raphael.exports.el) {
|
|
@@ -8988,21 +9287,21 @@ GraphUtils.createGraphie = function (el) {
|
|
|
8988
9287
|
const w = path.attr("stroke-width");
|
|
8989
9288
|
const s = 0.6 + 0.4 * w;
|
|
8990
9289
|
const l = path.getTotalLength();
|
|
8991
|
-
const set = raphael
|
|
8992
|
-
const head = raphael
|
|
9290
|
+
const set = thisGraphie.raphael.set();
|
|
9291
|
+
const head = thisGraphie.raphael.path(thisGraphie.isMobile ? "M-4,4 C-4,4 -0.25,0 -0.25,0 C-0.25,0 -4,-4 -4,-4" : "M-3 4 C-2.75 2.5 0 0.25 0.75 0C0 -0.25 -2.75 -2.5 -3 -4");
|
|
8993
9292
|
const end = path.getPointAtLength(l - 0.4);
|
|
8994
9293
|
const almostTheEnd = path.getPointAtLength(l - 0.75 * s);
|
|
8995
9294
|
const angle = Math.atan2(end.y - almostTheEnd.y, end.x - almostTheEnd.x) * 180 / Math.PI;
|
|
8996
9295
|
const attrs = path.attr();
|
|
8997
9296
|
delete attrs.path;
|
|
8998
9297
|
let subpath = path.getSubpath(0, l - 0.75 * s);
|
|
8999
|
-
subpath = raphael
|
|
9298
|
+
subpath = thisGraphie.raphael.path(subpath).attr(attrs);
|
|
9000
9299
|
subpath.arrowheadsDrawn = true;
|
|
9001
9300
|
path.remove();
|
|
9002
9301
|
|
|
9003
9302
|
// For some unknown reason 0 doesn't work for the rotation
|
|
9004
9303
|
// origin so we use a tiny number.
|
|
9005
|
-
head.rotate(angle,
|
|
9304
|
+
head.rotate(angle, thisGraphie.isMobile ? 1e-5 : 0.75, 0).scale(s, s, 0.75, 0).translate(almostTheEnd.x, almostTheEnd.y).attr(attrs).attr({
|
|
9006
9305
|
"stroke-linejoin": "round",
|
|
9007
9306
|
"stroke-linecap": "round"
|
|
9008
9307
|
});
|
|
@@ -9018,767 +9317,424 @@ GraphUtils.createGraphie = function (el) {
|
|
|
9018
9317
|
}
|
|
9019
9318
|
return path;
|
|
9020
9319
|
};
|
|
9021
|
-
|
|
9022
|
-
|
|
9023
|
-
|
|
9024
|
-
},
|
|
9025
|
-
// (x, y) is coordinate of bottom left corner
|
|
9026
|
-
rect: function (x, y, width, height) {
|
|
9027
|
-
// Raphael needs (x, y) to be coordinate of upper left corner
|
|
9028
|
-
const corner = scalePoint([x, y + height]);
|
|
9029
|
-
const dims = scaleVector([width, height]);
|
|
9030
|
-
const elem = raphael$1.rect(...corner.concat(dims));
|
|
9031
|
-
if (graphie.isMobile) {
|
|
9032
|
-
elem.node.style.shapeRendering = "crispEdges";
|
|
9033
|
-
}
|
|
9034
|
-
return elem;
|
|
9035
|
-
},
|
|
9036
|
-
ellipse: function (center, radii) {
|
|
9037
|
-
return raphael$1.ellipse(...scalePoint(center).concat(scaleVector(radii)));
|
|
9038
|
-
},
|
|
9039
|
-
fixedEllipse: function (center,
|
|
9040
|
-
// Different type than Coord, this is radiusX, radiusY
|
|
9041
|
-
radii, maxScale, padding) {
|
|
9042
|
-
// Scale point and radius
|
|
9043
|
-
const scaledPoint = scalePoint(center);
|
|
9044
|
-
const scaledRadii = scaleVector(radii);
|
|
9045
|
-
const width = 2 * scaledRadii[0] * maxScale + padding;
|
|
9046
|
-
const height = 2 * scaledRadii[1] * maxScale + padding;
|
|
9047
|
-
|
|
9048
|
-
// Calculate absolute left, top
|
|
9049
|
-
const left = scaledPoint[0] - width / 2;
|
|
9050
|
-
const top = scaledPoint[1] - height / 2;
|
|
9051
|
-
|
|
9052
|
-
// Wrap in <div>
|
|
9053
|
-
const wrapper = document.createElement("div");
|
|
9054
|
-
$(wrapper).css({
|
|
9055
|
-
position: "absolute",
|
|
9056
|
-
width: width + "px",
|
|
9057
|
-
height: height + "px",
|
|
9058
|
-
left: left + "px",
|
|
9059
|
-
top: top + "px"
|
|
9060
|
-
});
|
|
9061
|
-
// wrapper.setAttribute("data-graphie-type", "ellipse");
|
|
9320
|
+
function circle(center, radius) {
|
|
9321
|
+
return thisGraphie.raphael.ellipse(...thisGraphie.scalePoint(center).concat(thisGraphie.scaleVector([radius, radius])));
|
|
9322
|
+
}
|
|
9062
9323
|
|
|
9063
|
-
|
|
9064
|
-
|
|
9065
|
-
|
|
9066
|
-
|
|
9067
|
-
|
|
9068
|
-
|
|
9069
|
-
|
|
9070
|
-
|
|
9071
|
-
|
|
9072
|
-
|
|
9073
|
-
|
|
9074
|
-
|
|
9075
|
-
|
|
9076
|
-
|
|
9077
|
-
|
|
9078
|
-
|
|
9079
|
-
|
|
9080
|
-
|
|
9081
|
-
|
|
9082
|
-
|
|
9083
|
-
|
|
9084
|
-
|
|
9085
|
-
|
|
9086
|
-
|
|
9087
|
-
|
|
9088
|
-
|
|
9089
|
-
|
|
9090
|
-
|
|
9091
|
-
|
|
9092
|
-
|
|
9093
|
-
|
|
9094
|
-
|
|
9095
|
-
|
|
9096
|
-
|
|
9097
|
-
|
|
9098
|
-
|
|
9099
|
-
|
|
9100
|
-
const pathBottom = _.max(_.pluck(points, 1));
|
|
9101
|
-
|
|
9102
|
-
// Apply padding to line
|
|
9103
|
-
const padding = [4, 4];
|
|
9104
|
-
|
|
9105
|
-
// Calculate and apply additional offset
|
|
9106
|
-
const extraOffset = [pathLeft, pathTop];
|
|
9107
|
-
|
|
9108
|
-
// Apply padding and offset to points
|
|
9109
|
-
points = _.map(points, function (point) {
|
|
9110
|
-
return vector$1.add(vector$1.subtract(point, extraOffset), vector$1.scale(padding, 0.5));
|
|
9111
|
-
});
|
|
9112
|
-
|
|
9113
|
-
// Calculate <div> dimensions
|
|
9114
|
-
const width = pathRight - pathLeft + padding[0];
|
|
9115
|
-
const height = pathBottom - pathTop + padding[1];
|
|
9116
|
-
const left = extraOffset[0] - padding[0] / 2;
|
|
9117
|
-
const top = extraOffset[1] - padding[1] / 2;
|
|
9118
|
-
|
|
9119
|
-
// Create <div>
|
|
9120
|
-
const wrapper = document.createElement("div");
|
|
9121
|
-
$(wrapper).css({
|
|
9122
|
-
position: "absolute",
|
|
9123
|
-
width: width + "px",
|
|
9124
|
-
height: height + "px",
|
|
9125
|
-
left: left + "px",
|
|
9126
|
-
top: top + "px",
|
|
9127
|
-
// If user specified a center, set it
|
|
9128
|
-
// NOTE(kevinb): jQuery doesn't like that `transformOrigin `could be `null`
|
|
9129
|
-
// so we cast to `any` here.
|
|
9130
|
-
transformOrigin: center ? width / 2 + center[0] + "px " + (height / 2 + center[1]) + "px" : null
|
|
9131
|
-
});
|
|
9324
|
+
// (x, y) is coordinate of bottom left corner
|
|
9325
|
+
function rect(x, y, width, height) {
|
|
9326
|
+
// Raphael needs (x, y) to be coordinate of upper left corner
|
|
9327
|
+
const corner = thisGraphie.scalePoint([x, y + height]);
|
|
9328
|
+
const dims = thisGraphie.scaleVector([width, height]);
|
|
9329
|
+
const elem = thisGraphie.raphael.rect(...corner.concat(dims));
|
|
9330
|
+
if (thisGraphie.isMobile) {
|
|
9331
|
+
elem.node.style.shapeRendering = "crispEdges";
|
|
9332
|
+
}
|
|
9333
|
+
return elem;
|
|
9334
|
+
}
|
|
9335
|
+
function ellipse(center, radii) {
|
|
9336
|
+
return thisGraphie.raphael.ellipse(...thisGraphie.scalePoint(center).concat(thisGraphie.scaleVector(radii)));
|
|
9337
|
+
}
|
|
9338
|
+
function fixedEllipse(center,
|
|
9339
|
+
// Different type than Coord, this is radiusX, radiusY
|
|
9340
|
+
radii, maxScale, padding) {
|
|
9341
|
+
// Scale point and radius
|
|
9342
|
+
const scaledPoint = thisGraphie.scalePoint(center);
|
|
9343
|
+
const scaledRadii = thisGraphie.scaleVector(radii);
|
|
9344
|
+
const width = 2 * scaledRadii[0] * maxScale + padding;
|
|
9345
|
+
const height = 2 * scaledRadii[1] * maxScale + padding;
|
|
9346
|
+
|
|
9347
|
+
// Calculate absolute left, top
|
|
9348
|
+
const left = scaledPoint[0] - width / 2;
|
|
9349
|
+
const top = scaledPoint[1] - height / 2;
|
|
9350
|
+
|
|
9351
|
+
// Wrap in <div>
|
|
9352
|
+
const wrapper = document.createElement("div");
|
|
9353
|
+
$(wrapper).css({
|
|
9354
|
+
position: "absolute",
|
|
9355
|
+
width: width + "px",
|
|
9356
|
+
height: height + "px",
|
|
9357
|
+
left: left + "px",
|
|
9358
|
+
top: top + "px"
|
|
9359
|
+
});
|
|
9360
|
+
// wrapper.setAttribute("data-graphie-type", "ellipse");
|
|
9132
9361
|
|
|
9133
|
-
|
|
9134
|
-
|
|
9362
|
+
// Create Raphael canvas
|
|
9363
|
+
const localRaphael = raphael.exports(wrapper, width, height);
|
|
9364
|
+
const visibleShape = localRaphael.ellipse(width / 2, height / 2, scaledRadii[0], scaledRadii[1]);
|
|
9365
|
+
return {
|
|
9366
|
+
wrapper: wrapper,
|
|
9367
|
+
visibleShape: visibleShape
|
|
9368
|
+
};
|
|
9369
|
+
}
|
|
9370
|
+
function arc(center, radius, startAngle, endAngle, sector) {
|
|
9371
|
+
startAngle = (startAngle % 360 + 360) % 360;
|
|
9372
|
+
endAngle = (endAngle % 360 + 360) % 360;
|
|
9373
|
+
const cent = thisGraphie.scalePoint(center);
|
|
9374
|
+
const radii = thisGraphie.scaleVector(radius);
|
|
9375
|
+
const startVector = polar(radius, startAngle);
|
|
9376
|
+
const endVector = polar(radius, endAngle);
|
|
9377
|
+
const startPoint = thisGraphie.scalePoint([center[0] + startVector[0], center[1] + startVector[1]]);
|
|
9378
|
+
const endPoint = thisGraphie.scalePoint([center[0] + endVector[0], center[1] + endVector[1]]);
|
|
9379
|
+
const largeAngle = ((endAngle - startAngle) % 360 + 360) % 360 > 180;
|
|
9380
|
+
return thisGraphie.raphael.path("M" + startPoint.join(" ") + "A" + radii.join(" ") + " 0 " + (
|
|
9381
|
+
// ellipse rotation
|
|
9382
|
+
largeAngle ? 1 : 0) + " 0 " +
|
|
9383
|
+
// sweep flag
|
|
9384
|
+
endPoint.join(" ") + (sector ? "L" + cent.join(" ") + "z" : ""));
|
|
9385
|
+
}
|
|
9386
|
+
function path(points) {
|
|
9387
|
+
// @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
|
|
9388
|
+
const p = thisGraphie.raphael.path(thisGraphie.svgPath(points));
|
|
9389
|
+
p.graphiePath = points;
|
|
9390
|
+
return p;
|
|
9391
|
+
}
|
|
9392
|
+
function fixedPath(points, center, createPath) {
|
|
9393
|
+
points = _.map(points, thisGraphie.scalePoint);
|
|
9394
|
+
center = center ? thisGraphie.scalePoint(center) : null;
|
|
9395
|
+
createPath = createPath || thisGraphie.svgPath;
|
|
9396
|
+
const pathLeft = _.min(_.pluck(points, 0));
|
|
9397
|
+
const pathRight = _.max(_.pluck(points, 0));
|
|
9398
|
+
const pathTop = _.min(_.pluck(points, 1));
|
|
9399
|
+
const pathBottom = _.max(_.pluck(points, 1));
|
|
9400
|
+
|
|
9401
|
+
// Apply padding to line
|
|
9402
|
+
const padding = [4, 4];
|
|
9403
|
+
|
|
9404
|
+
// Calculate and apply additional offset
|
|
9405
|
+
const extraOffset = [pathLeft, pathTop];
|
|
9406
|
+
|
|
9407
|
+
// Apply padding and offset to points
|
|
9408
|
+
points = _.map(points, function (point) {
|
|
9409
|
+
return vector$1.add(vector$1.subtract(point, extraOffset), vector$1.scale(padding, 0.5));
|
|
9410
|
+
});
|
|
9135
9411
|
|
|
9136
|
-
|
|
9137
|
-
|
|
9138
|
-
|
|
9139
|
-
|
|
9140
|
-
|
|
9141
|
-
};
|
|
9142
|
-
},
|
|
9143
|
-
scaledPath: function (points) {
|
|
9144
|
-
const p = raphael$1.path(svgPath(points, /* alreadyScaled */true));
|
|
9145
|
-
p.graphiePath = points;
|
|
9146
|
-
return p;
|
|
9147
|
-
},
|
|
9148
|
-
line: function (start, end) {
|
|
9149
|
-
const l = this.path([start, end]);
|
|
9150
|
-
if (graphie.isMobile) {
|
|
9151
|
-
l.node.style.shapeRendering = "crispEdges";
|
|
9152
|
-
}
|
|
9153
|
-
return l;
|
|
9154
|
-
},
|
|
9155
|
-
parabola: function (a, b, c) {
|
|
9156
|
-
// Plot a parabola of the form: f(x) = (a * x + b) * x + c
|
|
9157
|
-
return raphael$1.path(svgParabolaPath(a, b, c));
|
|
9158
|
-
},
|
|
9159
|
-
fixedLine: function (start, end, thickness) {
|
|
9160
|
-
// Apply padding to line
|
|
9161
|
-
const padding = [thickness, thickness];
|
|
9162
|
-
|
|
9163
|
-
// Scale points to get values in pixels
|
|
9164
|
-
start = scalePoint(start);
|
|
9165
|
-
end = scalePoint(end);
|
|
9166
|
-
|
|
9167
|
-
// Calculate and apply additional offset
|
|
9168
|
-
const extraOffset = [Math.min(start[0], end[0]), Math.min(start[1], end[1])];
|
|
9169
|
-
|
|
9170
|
-
// Apply padding and offset to start, end points
|
|
9171
|
-
start = vector$1.add(vector$1.subtract(start, extraOffset), vector$1.scale(padding, 0.5));
|
|
9172
|
-
end = vector$1.add(vector$1.subtract(end, extraOffset), vector$1.scale(padding, 0.5));
|
|
9173
|
-
|
|
9174
|
-
// Calculate <div> dimensions
|
|
9175
|
-
const left = extraOffset[0] - padding[0] / 2;
|
|
9176
|
-
const top = extraOffset[1] - padding[1] / 2;
|
|
9177
|
-
const width = Math.abs(start[0] - end[0]) + padding[0];
|
|
9178
|
-
const height = Math.abs(start[1] - end[1]) + padding[1];
|
|
9179
|
-
|
|
9180
|
-
// Create <div>
|
|
9181
|
-
const wrapper = document.createElement("div");
|
|
9182
|
-
$(wrapper).css({
|
|
9183
|
-
position: "absolute",
|
|
9184
|
-
width: width + "px",
|
|
9185
|
-
height: height + "px",
|
|
9186
|
-
left: left + "px",
|
|
9187
|
-
top: top + "px",
|
|
9188
|
-
// Outsiders should feel like the line's 'origin' (i.e., for
|
|
9189
|
-
// rotation) is the starting point
|
|
9190
|
-
transformOrigin: start[0] + "px " + start[1] + "px"
|
|
9191
|
-
});
|
|
9192
|
-
|
|
9193
|
-
// Create Raphael canvas
|
|
9194
|
-
const localRaphael = raphael.exports(wrapper, width, height);
|
|
9195
|
-
|
|
9196
|
-
// Calculate path
|
|
9197
|
-
const path = "M" + start[0] + " " + start[1] + " " + "L" + end[0] + " " + end[1];
|
|
9198
|
-
const visibleShape = localRaphael.path(path);
|
|
9199
|
-
visibleShape.graphiePath = [start, end];
|
|
9200
|
-
return {
|
|
9201
|
-
wrapper: wrapper,
|
|
9202
|
-
visibleShape: visibleShape
|
|
9203
|
-
};
|
|
9204
|
-
},
|
|
9205
|
-
sinusoid: function (a, b, c, d) {
|
|
9206
|
-
// Plot a sinusoid of the form: f(x) = a * sin(b * x - c) + d
|
|
9207
|
-
return raphael$1.path(svgSinusoidPath(a, b, c, d));
|
|
9208
|
-
},
|
|
9209
|
-
grid: function (xr, yr) {
|
|
9210
|
-
const step = currentStyle.step || [1, 1];
|
|
9211
|
-
const set = raphael$1.set();
|
|
9212
|
-
let x = step[0] * Math.ceil(xr[0] / step[0]);
|
|
9213
|
-
for (; x <= xr[1]; x += step[0]) {
|
|
9214
|
-
set.push(this.line([x, yr[0]], [x, yr[1]]));
|
|
9215
|
-
}
|
|
9216
|
-
let y = step[1] * Math.ceil(yr[0] / step[1]);
|
|
9217
|
-
for (; y <= yr[1]; y += step[1]) {
|
|
9218
|
-
set.push(this.line([xr[0], y], [xr[1], y]));
|
|
9219
|
-
}
|
|
9220
|
-
return set;
|
|
9221
|
-
},
|
|
9222
|
-
label: function (point, text, direction, latex) {
|
|
9223
|
-
latex = typeof latex === "undefined" || latex;
|
|
9224
|
-
const $span = $("<span>").addClass("graphie-label");
|
|
9225
|
-
const pad = currentStyle["label-distance"];
|
|
9226
|
-
$span.css($.extend({}, {
|
|
9227
|
-
position: "absolute",
|
|
9228
|
-
padding: (pad != null ? pad : 7) + "px",
|
|
9229
|
-
color: "black"
|
|
9230
|
-
})).data("labelDirection", direction).appendTo(el);
|
|
9231
|
-
|
|
9232
|
-
// @ts-expect-error - TS2339 - Property 'setPosition' does not exist on type 'JQuery<HTMLElement>'.
|
|
9233
|
-
$span.setPosition = function (point) {
|
|
9234
|
-
const scaledPoint = scalePoint(point);
|
|
9235
|
-
$span.css({
|
|
9236
|
-
left: scaledPoint[0],
|
|
9237
|
-
top: scaledPoint[1]
|
|
9238
|
-
});
|
|
9239
|
-
};
|
|
9412
|
+
// Calculate <div> dimensions
|
|
9413
|
+
const width = pathRight - pathLeft + padding[0];
|
|
9414
|
+
const height = pathBottom - pathTop + padding[1];
|
|
9415
|
+
const left = extraOffset[0] - padding[0] / 2;
|
|
9416
|
+
const top = extraOffset[1] - padding[1] / 2;
|
|
9240
9417
|
|
|
9241
|
-
|
|
9242
|
-
|
|
9243
|
-
|
|
9418
|
+
// Create <div>
|
|
9419
|
+
const wrapper = document.createElement("div");
|
|
9420
|
+
$(wrapper).css({
|
|
9421
|
+
position: "absolute",
|
|
9422
|
+
width: width + "px",
|
|
9423
|
+
height: height + "px",
|
|
9424
|
+
left: left + "px",
|
|
9425
|
+
top: top + "px",
|
|
9426
|
+
// If user specified a center, set it
|
|
9427
|
+
// NOTE(kevinb): jQuery doesn't like that `transformOrigin `could be `null`
|
|
9428
|
+
// so we cast to `any` here.
|
|
9429
|
+
transformOrigin: center ? width / 2 + center[0] + "px " + (height / 2 + center[1]) + "px" : null
|
|
9430
|
+
});
|
|
9244
9431
|
|
|
9245
|
-
|
|
9246
|
-
|
|
9247
|
-
processMath$1(span, math, force, function () {
|
|
9248
|
-
const width = span.scrollWidth;
|
|
9249
|
-
const height = span.scrollHeight;
|
|
9250
|
-
setLabelMargins(span, [width, height]);
|
|
9251
|
-
});
|
|
9252
|
-
};
|
|
9432
|
+
// Create Raphael canvas
|
|
9433
|
+
const localRaphael = raphael.exports(wrapper, width, height);
|
|
9253
9434
|
|
|
9254
|
-
|
|
9255
|
-
|
|
9256
|
-
|
|
9435
|
+
// Calculate path
|
|
9436
|
+
const visibleShape = localRaphael.path(createPath(points));
|
|
9437
|
+
return {
|
|
9438
|
+
wrapper: wrapper,
|
|
9439
|
+
visibleShape: visibleShape
|
|
9440
|
+
};
|
|
9441
|
+
}
|
|
9442
|
+
function scaledPath(points) {
|
|
9443
|
+
const p = thisGraphie.raphael.path(thisGraphie.svgPath(points, /* alreadyScaled */true));
|
|
9444
|
+
p.graphiePath = points;
|
|
9445
|
+
return p;
|
|
9446
|
+
}
|
|
9447
|
+
function line(start, end) {
|
|
9448
|
+
const l = path([start, end]);
|
|
9449
|
+
if (thisGraphie.isMobile) {
|
|
9450
|
+
l.node.style.shapeRendering = "crispEdges";
|
|
9451
|
+
}
|
|
9452
|
+
return l;
|
|
9453
|
+
}
|
|
9454
|
+
function parabola(a, b, c) {
|
|
9455
|
+
// Plot a parabola of the form: f(x) = (a * x + b) * x + c
|
|
9456
|
+
return thisGraphie.raphael.path(thisGraphie.svgParabolaPath(a, b, c));
|
|
9457
|
+
}
|
|
9458
|
+
function fixedLine(start, end, thickness) {
|
|
9459
|
+
// Apply padding to line
|
|
9460
|
+
const padding = [thickness, thickness];
|
|
9461
|
+
|
|
9462
|
+
// Scale points to get values in pixels
|
|
9463
|
+
start = thisGraphie.scalePoint(start);
|
|
9464
|
+
end = thisGraphie.scalePoint(end);
|
|
9465
|
+
|
|
9466
|
+
// Calculate and apply additional offset
|
|
9467
|
+
const extraOffset = [Math.min(start[0], end[0]), Math.min(start[1], end[1])];
|
|
9468
|
+
|
|
9469
|
+
// Apply padding and offset to start, end points
|
|
9470
|
+
start = vector$1.add(vector$1.subtract(start, extraOffset), vector$1.scale(padding, 0.5));
|
|
9471
|
+
end = vector$1.add(vector$1.subtract(end, extraOffset), vector$1.scale(padding, 0.5));
|
|
9472
|
+
|
|
9473
|
+
// Calculate <div> dimensions
|
|
9474
|
+
const left = extraOffset[0] - padding[0] / 2;
|
|
9475
|
+
const top = extraOffset[1] - padding[1] / 2;
|
|
9476
|
+
const width = Math.abs(start[0] - end[0]) + padding[0];
|
|
9477
|
+
const height = Math.abs(start[1] - end[1]) + padding[1];
|
|
9478
|
+
|
|
9479
|
+
// Create <div>
|
|
9480
|
+
const wrapper = document.createElement("div");
|
|
9481
|
+
$(wrapper).css({
|
|
9482
|
+
position: "absolute",
|
|
9483
|
+
width: width + "px",
|
|
9484
|
+
height: height + "px",
|
|
9485
|
+
left: left + "px",
|
|
9486
|
+
top: top + "px",
|
|
9487
|
+
// Outsiders should feel like the line's 'origin' (i.e., for
|
|
9488
|
+
// rotation) is the starting point
|
|
9489
|
+
transformOrigin: start[0] + "px " + start[1] + "px"
|
|
9490
|
+
});
|
|
9491
|
+
|
|
9492
|
+
// Create Raphael canvas
|
|
9493
|
+
const localRaphael = raphael.exports(wrapper, width, height);
|
|
9494
|
+
|
|
9495
|
+
// Calculate path
|
|
9496
|
+
const path = "M" + start[0] + " " + start[1] + " " + "L" + end[0] + " " + end[1];
|
|
9497
|
+
const visibleShape = localRaphael.path(path);
|
|
9498
|
+
visibleShape.graphiePath = [start, end];
|
|
9499
|
+
return {
|
|
9500
|
+
wrapper: wrapper,
|
|
9501
|
+
visibleShape: visibleShape
|
|
9502
|
+
};
|
|
9503
|
+
}
|
|
9504
|
+
function sinusoid(a, b, c, d) {
|
|
9505
|
+
// Plot a sinusoid of the form: f(x) = a * sin(b * x - c) + d
|
|
9506
|
+
return thisGraphie.raphael.path(thisGraphie.svgSinusoidPath(a, b, c, d));
|
|
9507
|
+
}
|
|
9508
|
+
function grid(xr, yr) {
|
|
9509
|
+
const step = thisGraphie.currentStyle.step || [1, 1];
|
|
9510
|
+
const set = thisGraphie.raphael.set();
|
|
9511
|
+
let x = step[0] * Math.ceil(xr[0] / step[0]);
|
|
9512
|
+
for (; x <= xr[1]; x += step[0]) {
|
|
9513
|
+
set.push(line([x, yr[0]], [x, yr[1]]));
|
|
9514
|
+
}
|
|
9515
|
+
let y = step[1] * Math.ceil(yr[0] / step[1]);
|
|
9516
|
+
for (; y <= yr[1]; y += step[1]) {
|
|
9517
|
+
set.push(line([xr[0], y], [xr[1], y]));
|
|
9518
|
+
}
|
|
9519
|
+
return set;
|
|
9520
|
+
}
|
|
9521
|
+
function label(point, text, direction, latex) {
|
|
9522
|
+
latex = typeof latex === "undefined" || latex;
|
|
9523
|
+
const $span = $("<span>").addClass("graphie-label");
|
|
9524
|
+
const pad = thisGraphie.currentStyle["label-distance"];
|
|
9525
|
+
$span.css($.extend({}, {
|
|
9526
|
+
position: "absolute",
|
|
9527
|
+
padding: (pad != null ? pad : 7) + "px",
|
|
9528
|
+
color: "black"
|
|
9529
|
+
})).data("labelDirection", direction).appendTo(thisGraphie.el);
|
|
9530
|
+
|
|
9531
|
+
// @ts-expect-error - TS2339 - Property 'setPosition' does not exist on type 'JQuery<HTMLElement>'.
|
|
9532
|
+
$span.setPosition = function (point) {
|
|
9533
|
+
const scaledPoint = thisGraphie.scalePoint(point);
|
|
9534
|
+
$span.css({
|
|
9535
|
+
left: scaledPoint[0],
|
|
9536
|
+
top: scaledPoint[1]
|
|
9537
|
+
});
|
|
9538
|
+
};
|
|
9539
|
+
|
|
9540
|
+
// @ts-expect-error - TS2339 - Property 'setPosition' does not exist on type 'JQuery<HTMLElement>'.
|
|
9541
|
+
$span.setPosition(point);
|
|
9542
|
+
const span = $span[0];
|
|
9543
|
+
|
|
9544
|
+
// @ts-expect-error - TS2339 - Property 'processMath' does not exist on type 'JQuery<HTMLElement>'.
|
|
9545
|
+
$span.processMath = function (math, force) {
|
|
9546
|
+
processMath$1(span, math, force, function () {
|
|
9257
9547
|
const width = span.scrollWidth;
|
|
9258
9548
|
const height = span.scrollHeight;
|
|
9259
9549
|
setLabelMargins(span, [width, height]);
|
|
9260
|
-
};
|
|
9261
|
-
|
|
9262
|
-
// @ts-expect-error - TS2339 - Property 'processMath' does not exist on type 'JQuery<HTMLElement>'.
|
|
9263
|
-
$span.processMath(text, /* force */false);
|
|
9264
|
-
} else {
|
|
9265
|
-
// @ts-expect-error - TS2339 - Property 'processText' does not exist on type 'JQuery<HTMLElement>'.
|
|
9266
|
-
$span.processText(text);
|
|
9267
|
-
}
|
|
9268
|
-
return $span;
|
|
9269
|
-
},
|
|
9270
|
-
plotParametric: function (fn, range, shade, fn2) {
|
|
9271
|
-
// Note: fn2 should only be set if 'shade' is true, as it denotes
|
|
9272
|
-
// the function between which fn should have its area shaded.
|
|
9273
|
-
// In general, plotParametric shouldn't be used to shade the area
|
|
9274
|
-
// between two arbitrary parametrics functions over an interval,
|
|
9275
|
-
// as the method assumes that fn and fn2 are both of the form
|
|
9276
|
-
// fn(t) = (t, fn'(t)) for some initial fn'.
|
|
9277
|
-
fn2 = fn2 || function (t) {
|
|
9278
|
-
return [t, 0];
|
|
9279
|
-
};
|
|
9550
|
+
});
|
|
9551
|
+
};
|
|
9280
9552
|
|
|
9281
|
-
|
|
9282
|
-
|
|
9283
|
-
|
|
9284
|
-
|
|
9285
|
-
const
|
|
9286
|
-
|
|
9287
|
-
|
|
9288
|
-
|
|
9289
|
-
|
|
9290
|
-
|
|
9291
|
-
|
|
9292
|
-
|
|
9293
|
-
|
|
9294
|
-
|
|
9295
|
-
|
|
9296
|
-
|
|
9297
|
-
|
|
9298
|
-
|
|
9299
|
-
|
|
9300
|
-
|
|
9301
|
-
|
|
9302
|
-
|
|
9303
|
-
|
|
9304
|
-
|
|
9305
|
-
|
|
9306
|
-
|
|
9307
|
-
|
|
9308
|
-
|
|
9309
|
-
|
|
9310
|
-
|
|
9311
|
-
|
|
9312
|
-
|
|
9313
|
-
|
|
9314
|
-
|
|
9315
|
-
|
|
9316
|
-
|
|
9317
|
-
|
|
9318
|
-
|
|
9319
|
-
|
|
9320
|
-
|
|
9321
|
-
|
|
9322
|
-
|
|
9323
|
-
|
|
9324
|
-
|
|
9325
|
-
|
|
9553
|
+
// @ts-expect-error - TS2339 - Property 'processText' does not exist on type 'JQuery<HTMLElement>'.
|
|
9554
|
+
$span.processText = function (text) {
|
|
9555
|
+
$span.html(text);
|
|
9556
|
+
const width = span.scrollWidth;
|
|
9557
|
+
const height = span.scrollHeight;
|
|
9558
|
+
setLabelMargins(span, [width, height]);
|
|
9559
|
+
};
|
|
9560
|
+
if (latex) {
|
|
9561
|
+
// @ts-expect-error - TS2339 - Property 'processMath' does not exist on type 'JQuery<HTMLElement>'.
|
|
9562
|
+
$span.processMath(text, /* force */false);
|
|
9563
|
+
} else {
|
|
9564
|
+
// @ts-expect-error - TS2339 - Property 'processText' does not exist on type 'JQuery<HTMLElement>'.
|
|
9565
|
+
$span.processText(text);
|
|
9566
|
+
}
|
|
9567
|
+
return $span;
|
|
9568
|
+
}
|
|
9569
|
+
function plotParametric(fn, range, shade, fn2 = t => [t, 0]) {
|
|
9570
|
+
// Note: fn2 should only be set if 'shade' is true, as it denotes
|
|
9571
|
+
// the function between which fn should have its area shaded.
|
|
9572
|
+
// In general, plotParametric shouldn't be used to shade the area
|
|
9573
|
+
// between two arbitrary parametrics functions over an interval,
|
|
9574
|
+
// as the method assumes that fn and fn2 are both of the form
|
|
9575
|
+
// fn(t) = (t, fn'(t)) for some initial fn'.
|
|
9576
|
+
|
|
9577
|
+
// We truncate to 500,000, since anything bigger causes
|
|
9578
|
+
// overflow in the firefox svg renderer. This is safe
|
|
9579
|
+
// since 500,000 is outside the viewport anyway. We
|
|
9580
|
+
// write these functions the way we do to handle undefined.
|
|
9581
|
+
const clipper = xy => {
|
|
9582
|
+
if (Math.abs(xy[1]) > 500000) {
|
|
9583
|
+
return [xy[0], Math.min(Math.max(xy[1], -500000), 500000)];
|
|
9584
|
+
}
|
|
9585
|
+
return xy;
|
|
9586
|
+
};
|
|
9587
|
+
const clippedFn = x => clipper(fn(x));
|
|
9588
|
+
const clippedFn2 = x => clipper(fn2(x));
|
|
9589
|
+
if (!thisGraphie.currentStyle.strokeLinejoin) {
|
|
9590
|
+
thisGraphie.currentStyle.strokeLinejoin = "round";
|
|
9591
|
+
}
|
|
9592
|
+
if (!thisGraphie.currentStyle.strokeLinecap) {
|
|
9593
|
+
thisGraphie.currentStyle.strokeLinecap = "round";
|
|
9594
|
+
}
|
|
9595
|
+
const min = range[0];
|
|
9596
|
+
const max = range[1];
|
|
9597
|
+
let step = (max - min) / (thisGraphie.currentStyle["plot-points"] || 800);
|
|
9598
|
+
if (step === 0) {
|
|
9599
|
+
step = 1;
|
|
9600
|
+
}
|
|
9601
|
+
const paths = thisGraphie.raphael.set();
|
|
9602
|
+
let points = [];
|
|
9603
|
+
let lastDiff = GraphUtils.coordDiff(clippedFn(min), clippedFn2(min));
|
|
9604
|
+
let lastFlip = min;
|
|
9605
|
+
for (let t = min; t <= max; t += step) {
|
|
9606
|
+
const top = clippedFn(t);
|
|
9607
|
+
const bottom = clippedFn2(t);
|
|
9608
|
+
const diff = GraphUtils.coordDiff(top, bottom);
|
|
9609
|
+
|
|
9610
|
+
// Find points where it flips
|
|
9611
|
+
// Create path that sketches area between the two functions
|
|
9612
|
+
if (
|
|
9613
|
+
// if there is an asymptote here, meaning that the graph
|
|
9614
|
+
// switches signs and has a large difference
|
|
9615
|
+
diff[1] < 0 !== lastDiff[1] < 0 && Math.abs(diff[1] - lastDiff[1]) > 2 * thisGraphie.drawingTransform().pixelsPerUnitY() ||
|
|
9616
|
+
// or the function is undefined
|
|
9617
|
+
isNaN(diff[1])) {
|
|
9618
|
+
// split the path at this point, and draw it
|
|
9619
|
+
if (shade) {
|
|
9620
|
+
// @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
|
|
9621
|
+
points.push(top);
|
|
9326
9622
|
|
|
9327
|
-
|
|
9328
|
-
|
|
9329
|
-
// @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
|
|
9330
|
-
points.push(clippedFn2(u));
|
|
9331
|
-
}
|
|
9332
|
-
lastFlip = t;
|
|
9333
|
-
}
|
|
9334
|
-
paths.push(this.path(points));
|
|
9335
|
-
// restart the path, excluding this point
|
|
9336
|
-
points = [];
|
|
9337
|
-
if (shade) {
|
|
9623
|
+
// backtrack to draw paired function
|
|
9624
|
+
for (let u = t - step; u >= lastFlip; u -= step) {
|
|
9338
9625
|
// @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
|
|
9339
|
-
points.push(
|
|
9626
|
+
points.push(clippedFn2(u));
|
|
9340
9627
|
}
|
|
9341
|
-
|
|
9342
|
-
// otherwise, just add the point to the path
|
|
9343
|
-
// @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
|
|
9344
|
-
points.push(top);
|
|
9628
|
+
lastFlip = t;
|
|
9345
9629
|
}
|
|
9346
|
-
|
|
9347
|
-
|
|
9348
|
-
|
|
9349
|
-
|
|
9350
|
-
for (let u = max - step; u >= lastFlip; u -= step) {
|
|
9630
|
+
paths.push(path(points));
|
|
9631
|
+
// restart the path, excluding this point
|
|
9632
|
+
points = [];
|
|
9633
|
+
if (shade) {
|
|
9351
9634
|
// @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
|
|
9352
|
-
points.push(
|
|
9635
|
+
points.push(top);
|
|
9353
9636
|
}
|
|
9637
|
+
} else {
|
|
9638
|
+
// otherwise, just add the point to the path
|
|
9639
|
+
// @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
|
|
9640
|
+
points.push(top);
|
|
9354
9641
|
}
|
|
9355
|
-
|
|
9356
|
-
|
|
9357
|
-
|
|
9358
|
-
|
|
9359
|
-
|
|
9360
|
-
|
|
9361
|
-
|
|
9362
|
-
// There is probably a better heuristic for this
|
|
9363
|
-
if (!currentStyle["plot-points"]) {
|
|
9364
|
-
currentStyle["plot-points"] = 2 * (max - min) * xScale;
|
|
9365
|
-
}
|
|
9366
|
-
return this.plotParametric(function (th) {
|
|
9367
|
-
return polar(fn(th), th * 180 / Math.PI);
|
|
9368
|
-
}, range);
|
|
9369
|
-
},
|
|
9370
|
-
plot: function (fn, range, swapAxes, shade, fn2) {
|
|
9371
|
-
const min = range[0];
|
|
9372
|
-
const max = range[1];
|
|
9373
|
-
if (!currentStyle["plot-points"]) {
|
|
9374
|
-
currentStyle["plot-points"] = 2 * (max - min) * xScale;
|
|
9375
|
-
}
|
|
9376
|
-
if (swapAxes) {
|
|
9377
|
-
if (fn2) {
|
|
9378
|
-
// TODO(charlie): support swapped axis area shading
|
|
9379
|
-
throw new PerseusError("Can't shade area between functions with swapped axes.", Errors.Internal);
|
|
9380
|
-
}
|
|
9381
|
-
return this.plotParametric(function (y) {
|
|
9382
|
-
return [fn(y), y];
|
|
9383
|
-
}, range, shade);
|
|
9642
|
+
lastDiff = diff;
|
|
9643
|
+
}
|
|
9644
|
+
if (shade) {
|
|
9645
|
+
// backtrack to draw paired function
|
|
9646
|
+
for (let u = max - step; u >= lastFlip; u -= step) {
|
|
9647
|
+
// @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
|
|
9648
|
+
points.push(clippedFn2(u));
|
|
9384
9649
|
}
|
|
9650
|
+
}
|
|
9651
|
+
paths.push(path(points));
|
|
9652
|
+
return paths;
|
|
9653
|
+
}
|
|
9654
|
+
function plot(fn, range, swapAxes, shade, fn2) {
|
|
9655
|
+
const min = range[0];
|
|
9656
|
+
const max = range[1];
|
|
9657
|
+
if (!thisGraphie.currentStyle["plot-points"]) {
|
|
9658
|
+
thisGraphie.currentStyle["plot-points"] = 2 * (max - min) * thisGraphie.drawingTransform().pixelsPerUnitX();
|
|
9659
|
+
}
|
|
9660
|
+
if (swapAxes) {
|
|
9385
9661
|
if (fn2) {
|
|
9386
|
-
|
|
9387
|
-
|
|
9388
|
-
return [x, fn(x)];
|
|
9389
|
-
}, range, shade, function (x) {
|
|
9390
|
-
return [x, fn2(x)];
|
|
9391
|
-
});
|
|
9392
|
-
}
|
|
9393
|
-
throw new PerseusError("fn2 should only be set when 'shade' is True.", Errors.Internal);
|
|
9662
|
+
// TODO(charlie): support swapped axis area shading
|
|
9663
|
+
throw new PerseusError("Can't shade area between functions with swapped axes.", Errors.Internal);
|
|
9394
9664
|
}
|
|
9395
|
-
return
|
|
9396
|
-
return [
|
|
9665
|
+
return plotParametric(function (y) {
|
|
9666
|
+
return [fn(y), y];
|
|
9397
9667
|
}, range, shade);
|
|
9398
|
-
}
|
|
9399
|
-
|
|
9400
|
-
|
|
9401
|
-
|
|
9402
|
-
* Calls plotParametric.
|
|
9403
|
-
*
|
|
9404
|
-
* @param {[]} fnArray array of functions which when called
|
|
9405
|
-
* with a parameter i return the value of
|
|
9406
|
-
* the function at i
|
|
9407
|
-
* @param {[]} rangeArray array of ranges over which the
|
|
9408
|
-
* corresponding functions are defined
|
|
9409
|
-
* @return {Set<any>} set of paths
|
|
9410
|
-
*/
|
|
9411
|
-
plotPiecewise: function (fnArray, rangeArray) {
|
|
9412
|
-
const paths = raphael$1.set();
|
|
9413
|
-
const self = this;
|
|
9414
|
-
_.times(fnArray.length, function (i) {
|
|
9415
|
-
const fn = fnArray[i];
|
|
9416
|
-
const range = rangeArray[i];
|
|
9417
|
-
const fnPaths = self.plotParametric(function (x) {
|
|
9668
|
+
}
|
|
9669
|
+
if (fn2) {
|
|
9670
|
+
if (shade) {
|
|
9671
|
+
return plotParametric(function (x) {
|
|
9418
9672
|
return [x, fn(x)];
|
|
9419
|
-
}, range)
|
|
9420
|
-
|
|
9421
|
-
paths.push(fnPath);
|
|
9673
|
+
}, range, shade, function (x) {
|
|
9674
|
+
return [x, fn2(x)];
|
|
9422
9675
|
});
|
|
9423
|
-
});
|
|
9424
|
-
return paths;
|
|
9425
|
-
},
|
|
9426
|
-
/**
|
|
9427
|
-
* Given an array of coordinates of the form [x, y], create and
|
|
9428
|
-
* return a Raphael set of Raphael circle objects at those
|
|
9429
|
-
* coordinates
|
|
9430
|
-
*
|
|
9431
|
-
* @param {Array<[number, number]>} endpointArray
|
|
9432
|
-
* @return {Set<any>} set of circles
|
|
9433
|
-
*/
|
|
9434
|
-
plotEndpointCircles: function (endpointArray) {
|
|
9435
|
-
const circles = raphael$1.set();
|
|
9436
|
-
const self = this;
|
|
9437
|
-
_.each(endpointArray, function (coord, i) {
|
|
9438
|
-
circles.push(self.circle(coord, 0.15));
|
|
9439
|
-
});
|
|
9440
|
-
return circles;
|
|
9441
|
-
},
|
|
9442
|
-
plotAsymptotes: function (fn, range) {
|
|
9443
|
-
const min = range[0];
|
|
9444
|
-
const max = range[1];
|
|
9445
|
-
const step = (max - min) / (currentStyle["plot-points"] || 800);
|
|
9446
|
-
const asymptotes = raphael$1.set();
|
|
9447
|
-
let lastVal = fn(min);
|
|
9448
|
-
for (let t = min; t <= max; t += step) {
|
|
9449
|
-
const funcVal = fn(t);
|
|
9450
|
-
if (funcVal < 0 !== lastVal < 0 && Math.abs(funcVal - lastVal) > 2 * yScale) {
|
|
9451
|
-
asymptotes.push(this.line([t, yScale], [t, -yScale]));
|
|
9452
|
-
}
|
|
9453
|
-
lastVal = funcVal;
|
|
9454
9676
|
}
|
|
9455
|
-
|
|
9677
|
+
throw new PerseusError("fn2 should only be set when 'shade' is True.", Errors.Internal);
|
|
9456
9678
|
}
|
|
9679
|
+
return plotParametric(function (x) {
|
|
9680
|
+
return [x, fn(x)];
|
|
9681
|
+
}, range, shade);
|
|
9682
|
+
}
|
|
9683
|
+
const drawingTools = {
|
|
9684
|
+
circle,
|
|
9685
|
+
rect,
|
|
9686
|
+
ellipse,
|
|
9687
|
+
fixedEllipse,
|
|
9688
|
+
arc,
|
|
9689
|
+
path,
|
|
9690
|
+
fixedPath,
|
|
9691
|
+
scaledPath,
|
|
9692
|
+
line,
|
|
9693
|
+
parabola,
|
|
9694
|
+
fixedLine,
|
|
9695
|
+
sinusoid,
|
|
9696
|
+
grid,
|
|
9697
|
+
label,
|
|
9698
|
+
plotParametric,
|
|
9699
|
+
plot
|
|
9457
9700
|
};
|
|
9458
|
-
|
|
9459
|
-
|
|
9460
|
-
raphael: raphael$1,
|
|
9461
|
-
init: function (options) {
|
|
9462
|
-
let scale = options.scale || [40, 40];
|
|
9463
|
-
scale = typeof scale === "number" ? [scale, scale] : scale;
|
|
9464
|
-
xScale = scale[0];
|
|
9465
|
-
yScale = scale[1];
|
|
9466
|
-
if (options.range == null) {
|
|
9467
|
-
throw new PerseusError("range should be specified in graph init", Errors.Internal);
|
|
9468
|
-
}
|
|
9469
|
-
xRange = options.range[0];
|
|
9470
|
-
yRange = options.range[1];
|
|
9471
|
-
const w = (xRange[1] - xRange[0]) * xScale;
|
|
9472
|
-
const h = (yRange[1] - yRange[0]) * yScale;
|
|
9473
|
-
raphael$1.setSize(w, h);
|
|
9474
|
-
$(el).css({
|
|
9475
|
-
width: w,
|
|
9476
|
-
height: h
|
|
9477
|
-
});
|
|
9478
|
-
this.range = options.range;
|
|
9479
|
-
this.scale = scale;
|
|
9480
|
-
this.dimensions = [w, h];
|
|
9481
|
-
this.xpixels = w;
|
|
9482
|
-
this.ypixels = h;
|
|
9483
|
-
this.isMobile = options.isMobile;
|
|
9484
|
-
return this;
|
|
9485
|
-
},
|
|
9486
|
-
style: function (attrs, fn) {
|
|
9487
|
-
const processed = processAttributes(attrs);
|
|
9488
|
-
if (typeof fn === "function") {
|
|
9489
|
-
const oldStyle = currentStyle;
|
|
9490
|
-
currentStyle = $.extend({}, currentStyle, processed);
|
|
9491
|
-
const result = fn.call(graphie);
|
|
9492
|
-
currentStyle = oldStyle;
|
|
9493
|
-
return result;
|
|
9494
|
-
}
|
|
9495
|
-
$.extend(currentStyle, processed);
|
|
9496
|
-
},
|
|
9497
|
-
scalePoint: scalePoint,
|
|
9498
|
-
scaleVector: scaleVector,
|
|
9499
|
-
unscalePoint: unscalePoint,
|
|
9500
|
-
unscaleVector: unscaleVector,
|
|
9501
|
-
// Custom SVG path functions that are dependent on graphie range
|
|
9502
|
-
// `svgPath`, while independent of range, is exported for consistency
|
|
9503
|
-
svgPath: svgPath,
|
|
9504
|
-
svgParabolaPath: svgParabolaPath,
|
|
9505
|
-
svgSinusoidPath: svgSinusoidPath
|
|
9506
|
-
});
|
|
9507
|
-
$.each(drawingTools, function (name) {
|
|
9508
|
-
graphie[name] = function (...args) {
|
|
9701
|
+
function graphify(drawingFn) {
|
|
9702
|
+
return function (...args) {
|
|
9509
9703
|
const last = args[args.length - 1];
|
|
9510
|
-
const oldStyle = currentStyle;
|
|
9704
|
+
const oldStyle = thisGraphie.currentStyle;
|
|
9511
9705
|
let result;
|
|
9512
9706
|
|
|
9513
9707
|
// The last argument is probably trying to change the style
|
|
9514
9708
|
if (typeof last === "object" && !_.isArray(last)) {
|
|
9515
|
-
currentStyle = _extends({}, currentStyle, processAttributes(last));
|
|
9709
|
+
thisGraphie.currentStyle = _extends({}, thisGraphie.currentStyle, thisGraphie.processAttributes(last));
|
|
9516
9710
|
const rest = [].slice.call(args, 0, args.length - 1);
|
|
9517
|
-
result =
|
|
9711
|
+
result = drawingFn(...rest);
|
|
9518
9712
|
} else {
|
|
9519
|
-
currentStyle = $.extend({}, currentStyle);
|
|
9520
|
-
result =
|
|
9713
|
+
thisGraphie.currentStyle = $.extend({}, thisGraphie.currentStyle);
|
|
9714
|
+
result = drawingFn(...args);
|
|
9521
9715
|
}
|
|
9522
9716
|
|
|
9523
9717
|
// Bad heuristic for recognizing Raphael elements and sets
|
|
9524
9718
|
const type = result.constructor.prototype;
|
|
9525
9719
|
if (type === raphael.exports.el || type === raphael.exports.st) {
|
|
9526
|
-
result.attr(currentStyle);
|
|
9527
|
-
if (currentStyle.arrows) {
|
|
9720
|
+
result.attr(thisGraphie.currentStyle);
|
|
9721
|
+
if (thisGraphie.currentStyle.arrows) {
|
|
9528
9722
|
result = addArrowheads(result);
|
|
9529
9723
|
}
|
|
9530
9724
|
} else if (result instanceof $) {
|
|
9531
9725
|
// We assume that if it's not a Raphael element/set, it
|
|
9532
9726
|
// does not contain SVG.
|
|
9533
9727
|
// @ts-expect-error - TS2339 - Property 'css' does not exist on type '{}'.
|
|
9534
|
-
result.css(_extends({}, currentStyle, SVG_SPECIFIC_STYLE_MASK));
|
|
9728
|
+
result.css(_extends({}, thisGraphie.currentStyle, SVG_SPECIFIC_STYLE_MASK));
|
|
9535
9729
|
}
|
|
9536
|
-
currentStyle = oldStyle;
|
|
9730
|
+
thisGraphie.currentStyle = oldStyle;
|
|
9537
9731
|
return result;
|
|
9538
9732
|
};
|
|
9733
|
+
}
|
|
9734
|
+
$.each(drawingTools, function (name) {
|
|
9735
|
+
thisGraphie[name] = graphify(drawingTools[name]);
|
|
9539
9736
|
});
|
|
9540
|
-
|
|
9541
|
-
// Initializes graphie settings for a graph and draws the basic graph
|
|
9542
|
-
// features (axes, grid, tick marks, and axis labels)
|
|
9543
|
-
// Options expected are:
|
|
9544
|
-
// - range: [[a, b], [c, d]] or [a, b]
|
|
9545
|
-
// - scale: [a, b] or number
|
|
9546
|
-
// - gridOpacity: number (0 - 1)
|
|
9547
|
-
// - gridStep: [a, b] or number (relative to units)
|
|
9548
|
-
// - tickStep: [a, b] or number (relative to grid steps)
|
|
9549
|
-
// - tickLen: [a, b] or number (in pixels)
|
|
9550
|
-
// - labelStep: [a, b] or number (relative to tick steps)
|
|
9551
|
-
// - yLabelFormat: fn to format label string for y-axis
|
|
9552
|
-
// - xLabelFormat: fn to format label string for x-axis
|
|
9553
|
-
// - smartLabelPositioning: true or false to ignore minus sign
|
|
9554
|
-
graphie.graphInit = function (options) {
|
|
9555
|
-
options = options || {};
|
|
9556
|
-
$.each(options, function (prop, val) {
|
|
9557
|
-
// allow options to be specified by a single number for shorthand if
|
|
9558
|
-
// the horizontal and vertical components are the same
|
|
9559
|
-
if (
|
|
9560
|
-
// @ts-expect-error - TS2339 - Property 'match' does not exist on type 'string | number | symbol'.
|
|
9561
|
-
!prop.match(/.*Opacity$/) && prop !== "range" && typeof val === "number") {
|
|
9562
|
-
options[prop] = [val, val];
|
|
9563
|
-
}
|
|
9564
|
-
|
|
9565
|
-
// allow symmetric ranges to be specified by the absolute values
|
|
9566
|
-
if (prop === "range" || prop === "gridRange") {
|
|
9567
|
-
if (val.constructor === Array) {
|
|
9568
|
-
// but don't mandate symmetric ranges
|
|
9569
|
-
if (val[0].constructor !== Array) {
|
|
9570
|
-
options[prop] = [[-val[0], val[0]], [-val[1], val[1]]];
|
|
9571
|
-
}
|
|
9572
|
-
} else if (typeof val === "number") {
|
|
9573
|
-
options[prop] = [[-val, val], [-val, val]];
|
|
9574
|
-
}
|
|
9575
|
-
}
|
|
9576
|
-
});
|
|
9577
|
-
const range = options.range || [[-10, 10], [-10, 10]];
|
|
9578
|
-
const gridRange = options.gridRange || options.range;
|
|
9579
|
-
const scale = options.scale || [20, 20];
|
|
9580
|
-
const grid = options.grid != null ? options.grid : true;
|
|
9581
|
-
const gridOpacity = options.gridOpacity || 0.1;
|
|
9582
|
-
const gridStep = options.gridStep || [1, 1];
|
|
9583
|
-
const axes = options.axes != null ? options.axes : true;
|
|
9584
|
-
const axisArrows = options.axisArrows || "";
|
|
9585
|
-
const axisOpacity = options.axisOpacity || 1.0;
|
|
9586
|
-
const axisCenter = options.axisCenter || [Math.min(Math.max(range[0][0], 0), range[0][1]), Math.min(Math.max(range[1][0], 0), range[1][1])];
|
|
9587
|
-
const axisLabels = options.axisLabels != null ? options.axisLabels : false;
|
|
9588
|
-
const ticks = options.ticks != null ? options.ticks : true;
|
|
9589
|
-
const tickStep = options.tickStep || [2, 2];
|
|
9590
|
-
const tickLen = options.tickLen || [5, 5];
|
|
9591
|
-
const tickOpacity = options.tickOpacity || 1.0;
|
|
9592
|
-
const labels = options.labels || options.labelStep || false;
|
|
9593
|
-
const labelStep = options.labelStep || [1, 1];
|
|
9594
|
-
const labelOpacity = options.labelOpacity || 1.0;
|
|
9595
|
-
let unityLabels = options.unityLabels || false;
|
|
9596
|
-
const labelFormat = options.labelFormat || function (a) {
|
|
9597
|
-
return a;
|
|
9598
|
-
};
|
|
9599
|
-
let xLabelFormat = options.xLabelFormat || labelFormat;
|
|
9600
|
-
let yLabelFormat = options.yLabelFormat || labelFormat;
|
|
9601
|
-
const smartLabelPositioning = options.smartLabelPositioning != null ? options.smartLabelPositioning : true;
|
|
9602
|
-
const realRange = [[range[0][0] - (range[0][0] > 0 ? 1 : 0), range[0][1] + (range[0][1] < 0 ? 1 : 0)], [range[1][0] - (range[1][0] > 0 ? 1 : 0), range[1][1] + (range[1][1] < 0 ? 1 : 0)]];
|
|
9603
|
-
if (!_.isArray(unityLabels)) {
|
|
9604
|
-
unityLabels = [unityLabels, unityLabels];
|
|
9605
|
-
}
|
|
9606
|
-
if (smartLabelPositioning) {
|
|
9607
|
-
const minusIgnorer = function minusIgnorer(lf) {
|
|
9608
|
-
return function (a) {
|
|
9609
|
-
return (lf(a) + "").replace(/-(\d)/g, "\\llap{-}$1");
|
|
9610
|
-
};
|
|
9611
|
-
};
|
|
9612
|
-
xLabelFormat = minusIgnorer(xLabelFormat);
|
|
9613
|
-
yLabelFormat = minusIgnorer(yLabelFormat);
|
|
9614
|
-
}
|
|
9615
|
-
this.init({
|
|
9616
|
-
range: realRange,
|
|
9617
|
-
scale: scale,
|
|
9618
|
-
isMobile: options.isMobile
|
|
9619
|
-
});
|
|
9620
|
-
|
|
9621
|
-
// draw grid
|
|
9622
|
-
if (grid) {
|
|
9623
|
-
this.grid(gridRange[0], gridRange[1], {
|
|
9624
|
-
stroke: options.isMobile ? KhanColors.GRAY_C : "#000000",
|
|
9625
|
-
opacity: options.isMobile ? 1 : gridOpacity,
|
|
9626
|
-
step: gridStep,
|
|
9627
|
-
strokeWidth: options.isMobile ? 1 : 2
|
|
9628
|
-
});
|
|
9629
|
-
}
|
|
9630
|
-
|
|
9631
|
-
// draw axes
|
|
9632
|
-
if (axes) {
|
|
9633
|
-
// this is a slight hack until <-> arrowheads work
|
|
9634
|
-
if (axisArrows === "<->" || axisArrows === true) {
|
|
9635
|
-
this.style({
|
|
9636
|
-
stroke: options.isMobile ? KhanColors.GRAY_G : "#000000",
|
|
9637
|
-
opacity: options.isMobile ? 1 : axisOpacity,
|
|
9638
|
-
strokeWidth: options.isMobile ? 1 : 2,
|
|
9639
|
-
arrows: "->"
|
|
9640
|
-
}, function () {
|
|
9641
|
-
if (range[1][0] < 0 && range[1][1] > 0) {
|
|
9642
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
9643
|
-
this.path([axisCenter, [gridRange[0][0], axisCenter[1]]]);
|
|
9644
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
9645
|
-
this.path([axisCenter, [gridRange[0][1], axisCenter[1]]]);
|
|
9646
|
-
}
|
|
9647
|
-
if (range[0][0] < 0 && range[0][1] > 0) {
|
|
9648
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
9649
|
-
this.path([axisCenter, [axisCenter[0], gridRange[1][0]]]);
|
|
9650
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
9651
|
-
this.path([axisCenter, [axisCenter[0], gridRange[1][1]]]);
|
|
9652
|
-
}
|
|
9653
|
-
});
|
|
9654
|
-
|
|
9655
|
-
// also, we don't support "<-" arrows yet, but why you
|
|
9656
|
-
// would want that on your graph is beyond me.
|
|
9657
|
-
} else if (axisArrows === "->" || axisArrows === "") {
|
|
9658
|
-
this.style({
|
|
9659
|
-
stroke: "#000000",
|
|
9660
|
-
opacity: axisOpacity,
|
|
9661
|
-
strokeWidth: 2,
|
|
9662
|
-
arrows: axisArrows
|
|
9663
|
-
}, function () {
|
|
9664
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
9665
|
-
this.path([[gridRange[0][0], axisCenter[1]], [gridRange[0][1], axisCenter[1]]]);
|
|
9666
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
9667
|
-
this.path([[axisCenter[0], gridRange[1][0]], [axisCenter[0], gridRange[1][1]]]);
|
|
9668
|
-
});
|
|
9669
|
-
}
|
|
9670
|
-
if (axisLabels && axisLabels.length === 2) {
|
|
9671
|
-
this.label([gridRange[0][1], axisCenter[1]], axisLabels[0], "right");
|
|
9672
|
-
this.label([axisCenter[0], gridRange[1][1]], axisLabels[1], "above");
|
|
9673
|
-
}
|
|
9674
|
-
}
|
|
9675
|
-
|
|
9676
|
-
// draw tick marks
|
|
9677
|
-
if (ticks) {
|
|
9678
|
-
const halfWidthTicks = options.isMobile;
|
|
9679
|
-
this.style({
|
|
9680
|
-
stroke: options.isMobile ? KhanColors.GRAY_G : "#000000",
|
|
9681
|
-
opacity: options.isMobile ? 1 : tickOpacity,
|
|
9682
|
-
strokeWidth: 1
|
|
9683
|
-
}, function () {
|
|
9684
|
-
// horizontal axis
|
|
9685
|
-
let step = gridStep[0] * tickStep[0];
|
|
9686
|
-
let len = tickLen[0] / scale[1];
|
|
9687
|
-
let start = gridRange[0][0];
|
|
9688
|
-
let stop = gridRange[0][1];
|
|
9689
|
-
if (range[1][0] < 0 && range[1][1] > 0) {
|
|
9690
|
-
for (let x = step + axisCenter[0]; x <= stop; x += step) {
|
|
9691
|
-
if (x < stop || !axisArrows) {
|
|
9692
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
9693
|
-
this.line([x, -len + axisCenter[1]], [x, halfWidthTicks ? 0 : len + axisCenter[1]]);
|
|
9694
|
-
}
|
|
9695
|
-
}
|
|
9696
|
-
for (let x = -step + axisCenter[0]; x >= start; x -= step) {
|
|
9697
|
-
if (x > start || !axisArrows) {
|
|
9698
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
9699
|
-
this.line([x, -len + axisCenter[1]], [x, halfWidthTicks ? 0 : len + axisCenter[1]]);
|
|
9700
|
-
}
|
|
9701
|
-
}
|
|
9702
|
-
}
|
|
9703
|
-
|
|
9704
|
-
// vertical axis
|
|
9705
|
-
step = gridStep[1] * tickStep[1];
|
|
9706
|
-
len = tickLen[1] / scale[0];
|
|
9707
|
-
start = gridRange[1][0];
|
|
9708
|
-
stop = gridRange[1][1];
|
|
9709
|
-
if (range[0][0] < 0 && range[0][1] > 0) {
|
|
9710
|
-
for (let y = step + axisCenter[1]; y <= stop; y += step) {
|
|
9711
|
-
if (y < stop || !axisArrows) {
|
|
9712
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
9713
|
-
this.line([-len + axisCenter[0], y], [halfWidthTicks ? 0 : len + axisCenter[0], y]);
|
|
9714
|
-
}
|
|
9715
|
-
}
|
|
9716
|
-
for (let y = -step + axisCenter[1]; y >= start; y -= step) {
|
|
9717
|
-
if (y > start || !axisArrows) {
|
|
9718
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
9719
|
-
this.line([-len + axisCenter[0], y], [halfWidthTicks ? 0 : len + axisCenter[0], y]);
|
|
9720
|
-
}
|
|
9721
|
-
}
|
|
9722
|
-
}
|
|
9723
|
-
});
|
|
9724
|
-
}
|
|
9725
|
-
|
|
9726
|
-
// draw axis labels
|
|
9727
|
-
if (labels) {
|
|
9728
|
-
this.style({
|
|
9729
|
-
stroke: options.isMobile ? KhanColors.GRAY_G : "#000000",
|
|
9730
|
-
opacity: options.isMobile ? 1 : labelOpacity
|
|
9731
|
-
}, function () {
|
|
9732
|
-
// horizontal axis
|
|
9733
|
-
let step = gridStep[0] * tickStep[0] * labelStep[0];
|
|
9734
|
-
let start = gridRange[0][0];
|
|
9735
|
-
let stop = gridRange[0][1];
|
|
9736
|
-
const xAxisPosition = axisCenter[0] < 0 ? "above" : "below";
|
|
9737
|
-
const yAxisPosition = axisCenter[0] < 0 ? "right" : "left";
|
|
9738
|
-
const xShowZero = axisCenter[0] === 0 && axisCenter[1] !== 0;
|
|
9739
|
-
const yShowZero = axisCenter[0] !== 0 && axisCenter[1] === 0;
|
|
9740
|
-
const axisOffCenter = axisCenter[0] !== 0 || axisCenter[1] !== 0;
|
|
9741
|
-
const showUnityX = unityLabels[0] || axisOffCenter;
|
|
9742
|
-
const showUnityY = unityLabels[1] || axisOffCenter;
|
|
9743
|
-
|
|
9744
|
-
// positive x-axis
|
|
9745
|
-
for (let x = (xShowZero ? 0 : step) + axisCenter[0]; x <= stop; x += step) {
|
|
9746
|
-
if (x < stop || !axisArrows) {
|
|
9747
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
9748
|
-
this.label([x, axisCenter[1]], xLabelFormat(x), xAxisPosition);
|
|
9749
|
-
}
|
|
9750
|
-
}
|
|
9751
|
-
|
|
9752
|
-
// negative x-axis
|
|
9753
|
-
for (let x = -step * (showUnityX ? 1 : 2) + axisCenter[0]; x >= start; x -= step) {
|
|
9754
|
-
if (x > start || !axisArrows) {
|
|
9755
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
9756
|
-
this.label([x, axisCenter[1]], xLabelFormat(x), xAxisPosition);
|
|
9757
|
-
}
|
|
9758
|
-
}
|
|
9759
|
-
step = gridStep[1] * tickStep[1] * labelStep[1];
|
|
9760
|
-
start = gridRange[1][0];
|
|
9761
|
-
stop = gridRange[1][1];
|
|
9762
|
-
|
|
9763
|
-
// positive y-axis
|
|
9764
|
-
for (let y = (yShowZero ? 0 : step) + axisCenter[1]; y <= stop; y += step) {
|
|
9765
|
-
if (y < stop || !axisArrows) {
|
|
9766
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
9767
|
-
this.label([axisCenter[0], y], yLabelFormat(y), yAxisPosition);
|
|
9768
|
-
}
|
|
9769
|
-
}
|
|
9770
|
-
|
|
9771
|
-
// negative y-axis
|
|
9772
|
-
for (let y = -step * (showUnityY ? 1 : 2) + axisCenter[1]; y >= start; y -= step) {
|
|
9773
|
-
if (y > start || !axisArrows) {
|
|
9774
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
9775
|
-
this.label([axisCenter[0], y], yLabelFormat(y), yAxisPosition);
|
|
9776
|
-
}
|
|
9777
|
-
}
|
|
9778
|
-
});
|
|
9779
|
-
}
|
|
9780
|
-
};
|
|
9781
|
-
return graphie;
|
|
9737
|
+
return thisGraphie;
|
|
9782
9738
|
};
|
|
9783
9739
|
|
|
9784
9740
|
/* eslint-disable */
|
|
@@ -13368,7 +13324,7 @@ _.extend(MovableAngle.prototype, {
|
|
|
13368
13324
|
let angle = GraphUtils.findAngle(newPoint, vertex);
|
|
13369
13325
|
angle = Math.round((angle - snapOffset) / snap) * snap + snapOffset;
|
|
13370
13326
|
const distance = GraphUtils.getDistance(newPoint, vertex);
|
|
13371
|
-
return addPoints(vertex,
|
|
13327
|
+
return addPoints(vertex, polar(distance, angle));
|
|
13372
13328
|
}
|
|
13373
13329
|
return true;
|
|
13374
13330
|
};
|
|
@@ -18436,6 +18392,27 @@ const registerEditors = editorsToRegister => {
|
|
|
18436
18392
|
editors[editor.widgetName] = editor;
|
|
18437
18393
|
});
|
|
18438
18394
|
};
|
|
18395
|
+
|
|
18396
|
+
/**
|
|
18397
|
+
*
|
|
18398
|
+
* @param name - the widget that you are trying to replace
|
|
18399
|
+
* @param replacementName - the name of the widget that takes its place
|
|
18400
|
+
*
|
|
18401
|
+
* e.g. replaceEditor("transformer", "always-correct") will make it so the
|
|
18402
|
+
* transformer widget is replaced by the always correct widget
|
|
18403
|
+
*/
|
|
18404
|
+
const replaceEditor = (name, replacementName) => {
|
|
18405
|
+
const substituteEditor = editors[replacementName];
|
|
18406
|
+
if (!substituteEditor && Log) {
|
|
18407
|
+
const errorMsg = `Failed to replace editor ${name} with ${replacementName}`;
|
|
18408
|
+
Log.error(errorMsg, Errors.Internal);
|
|
18409
|
+
return;
|
|
18410
|
+
}
|
|
18411
|
+
editors[name] = substituteEditor;
|
|
18412
|
+
if (Log) {
|
|
18413
|
+
Log.log(`INFO: Replacing editor ${name} with ${replacementName}`);
|
|
18414
|
+
}
|
|
18415
|
+
};
|
|
18439
18416
|
const getWidget = name => {
|
|
18440
18417
|
// TODO(alex): Consider referring to these as renderers to avoid
|
|
18441
18418
|
// overloading "widget"
|
|
@@ -18464,7 +18441,7 @@ const getVersion = name => {
|
|
|
18464
18441
|
minor: 0
|
|
18465
18442
|
};
|
|
18466
18443
|
}
|
|
18467
|
-
return
|
|
18444
|
+
return;
|
|
18468
18445
|
};
|
|
18469
18446
|
const getVersionVector = () => {
|
|
18470
18447
|
const version = {};
|
|
@@ -18745,6 +18722,7 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
|
|
|
18745
18722
|
registerWidgets: registerWidgets,
|
|
18746
18723
|
replaceWidget: replaceWidget,
|
|
18747
18724
|
registerEditors: registerEditors,
|
|
18725
|
+
replaceEditor: replaceEditor,
|
|
18748
18726
|
getWidget: getWidget,
|
|
18749
18727
|
getEditor: getEditor,
|
|
18750
18728
|
getTransform: getTransform,
|
|
@@ -24370,14 +24348,14 @@ var Definition$1 = {
|
|
|
24370
24348
|
|
|
24371
24349
|
// The props are type `any` on purpose so that this can receive props
|
|
24372
24350
|
// from any deprecated widget
|
|
24373
|
-
class
|
|
24351
|
+
class DeprecatedStandin extends React__default.Component {
|
|
24374
24352
|
constructor(...args) {
|
|
24375
24353
|
super(...args);
|
|
24376
24354
|
this.getUserInput = () => {
|
|
24377
24355
|
return {};
|
|
24378
24356
|
};
|
|
24379
24357
|
this.simpleValidate = rubric => {
|
|
24380
|
-
return
|
|
24358
|
+
return DeprecatedStandin.validate(this.getUserInput(), rubric);
|
|
24381
24359
|
};
|
|
24382
24360
|
}
|
|
24383
24361
|
static validate(userInput, rubric) {
|
|
@@ -24397,16 +24375,17 @@ class AutoCorrect extends React__default.Component {
|
|
|
24397
24375
|
paddingBottom: 8
|
|
24398
24376
|
}
|
|
24399
24377
|
}, /*#__PURE__*/React__default.createElement(Banner, {
|
|
24400
|
-
text: i18n._("
|
|
24401
|
-
kind: "
|
|
24378
|
+
text: i18n._("Sorry, this part of the question is no longer available. 😅 Don't worry, you won't be graded on this part. Keep going!"),
|
|
24379
|
+
kind: "info",
|
|
24402
24380
|
layout: "full-width"
|
|
24403
24381
|
}));
|
|
24404
24382
|
}
|
|
24405
24383
|
}
|
|
24406
|
-
var DeprecatedStandin = {
|
|
24384
|
+
var DeprecatedStandin$1 = {
|
|
24407
24385
|
name: "deprecated-standin",
|
|
24408
24386
|
displayName: "Deprecated Standin",
|
|
24409
|
-
widget:
|
|
24387
|
+
widget: DeprecatedStandin,
|
|
24388
|
+
hidden: true
|
|
24410
24389
|
};
|
|
24411
24390
|
|
|
24412
24391
|
class Dropdown extends React.Component {
|
|
@@ -28895,7 +28874,7 @@ class InteractiveGraph extends React.Component {
|
|
|
28895
28874
|
if (
|
|
28896
28875
|
// @ts-expect-error - TS2339 - Property 'snapTo' does not exist on type 'PerseusGraphType'.
|
|
28897
28876
|
this.props.graph.snapTo === "angles" && this.points.length > 2) {
|
|
28898
|
-
var _this$graphie5
|
|
28877
|
+
var _this$graphie5;
|
|
28899
28878
|
// Snap to whole degree interior angles
|
|
28900
28879
|
|
|
28901
28880
|
const angles = _.map(angleMeasures(coords), function (rad) {
|
|
@@ -28925,12 +28904,12 @@ class InteractiveGraph extends React.Component {
|
|
|
28925
28904
|
// Solve for side by using the law of sines
|
|
28926
28905
|
const side = Math.sin(innerAngles[1] * Math.PI / 180) / Math.sin(innerAngles[2] * Math.PI / 180) * knownSide;
|
|
28927
28906
|
const outerAngle = GraphUtils.findAngle(coords[rel(1)], coords[rel(-1)]);
|
|
28928
|
-
const offset =
|
|
28929
|
-
return (_this$
|
|
28907
|
+
const offset = polar(side, outerAngle + (onLeft ? 1 : -1) * innerAngles[0]);
|
|
28908
|
+
return (_this$graphie5 = this.graphie) == null ? void 0 : _this$graphie5.addPoints(coords[rel(-1)], offset);
|
|
28930
28909
|
}
|
|
28931
28910
|
// @ts-expect-error - TS2339 - Property 'snapTo' does not exist on type 'PerseusGraphType'.
|
|
28932
28911
|
if (this.props.graph.snapTo === "sides" && this.points.length > 1) {
|
|
28933
|
-
var _this$
|
|
28912
|
+
var _this$graphie6;
|
|
28934
28913
|
// Snap to whole unit side measures
|
|
28935
28914
|
|
|
28936
28915
|
const sides = _.map([[coords[rel(-1)], coords[i]], [coords[i], coords[rel(1)]], [coords[rel(-1)], coords[rel(1)]]], function (coords) {
|
|
@@ -28950,8 +28929,8 @@ class InteractiveGraph extends React.Component {
|
|
|
28950
28929
|
const innerAngle = lawOfCosines(sides[0], sides[2], sides[1]);
|
|
28951
28930
|
const outerAngle = GraphUtils.findAngle(coords[rel(1)], coords[rel(-1)]);
|
|
28952
28931
|
const onLeft = sign(ccw(coords[rel(-1)], coords[rel(1)], coords[i])) === 1;
|
|
28953
|
-
const offset =
|
|
28954
|
-
return (_this$
|
|
28932
|
+
const offset = polar(sides[0], outerAngle + (onLeft ? 1 : -1) * innerAngle);
|
|
28933
|
+
return (_this$graphie6 = this.graphie) == null ? void 0 : _this$graphie6.addPoints(coords[rel(-1)], offset);
|
|
28955
28934
|
}
|
|
28956
28935
|
// Snap to grid (already done)
|
|
28957
28936
|
return true;
|
|
@@ -31029,7 +31008,7 @@ const AnswerPill = props => {
|
|
|
31029
31008
|
id: idFactory.get("perseus-label-image-widget-answer-pill"),
|
|
31030
31009
|
onClick: correct ? undefined : onClick,
|
|
31031
31010
|
ref: ref,
|
|
31032
|
-
style: [style, popperStyle, styles$h.
|
|
31011
|
+
style: [style, popperStyle, styles$h.pill, correct && styles$h.correct, incorrect && styles$h.incorrect, (focused || hovered) && BringToFront]
|
|
31033
31012
|
}, /*#__PURE__*/React.createElement(Renderer, {
|
|
31034
31013
|
content: answerString,
|
|
31035
31014
|
inline: true
|
|
@@ -31042,6 +31021,11 @@ const styles$h = StyleSheet.create({
|
|
|
31042
31021
|
},
|
|
31043
31022
|
incorrect: {
|
|
31044
31023
|
backgroundColor: Color.offBlack64
|
|
31024
|
+
},
|
|
31025
|
+
pill: {
|
|
31026
|
+
// Reset the Pill's default height in order to account
|
|
31027
|
+
// for multi-line pills.
|
|
31028
|
+
height: "auto"
|
|
31045
31029
|
}
|
|
31046
31030
|
});
|
|
31047
31031
|
|
|
@@ -43958,7 +43942,7 @@ var Video$1 = {
|
|
|
43958
43942
|
};
|
|
43959
43943
|
|
|
43960
43944
|
/* eslint-disable import/no-named-as-default */
|
|
43961
|
-
var extraWidgets = [Categorizer$1, CSProgram$1, Dropdown$1, Explanation$1, Definition$1, Grapher$1, GradedGroup$1, GradedGroupSet$1, Group$1, Iframe$1, Image$1, Interactive, InteractiveGraph$1, LabelImage$1, LightsPuzzle$1, Matrix$1, Matcher$1, Measurer$1, Molecule$1, NumberLine$1, Orderer$1, Passage$1, PassageRef$1, PassageRefTarget$1, Plotter$1, ReactionDiagram, Sequence$1, Simulator$1, Sorter$1, Table$1, Transformer$1, Unit, Video$1, DeprecatedStandin];
|
|
43945
|
+
var extraWidgets = [Categorizer$1, CSProgram$1, Dropdown$1, Explanation$1, Definition$1, Grapher$1, GradedGroup$1, GradedGroupSet$1, Group$1, Iframe$1, Image$1, Interactive, InteractiveGraph$1, LabelImage$1, LightsPuzzle$1, Matrix$1, Matcher$1, Measurer$1, Molecule$1, NumberLine$1, Orderer$1, Passage$1, PassageRef$1, PassageRefTarget$1, Plotter$1, ReactionDiagram, Sequence$1, Simulator$1, Sorter$1, Table$1, Transformer$1, Unit, Video$1, DeprecatedStandin$1];
|
|
43962
43946
|
|
|
43963
43947
|
/**
|
|
43964
43948
|
* This should be called by all clients, specifying whether extra widgets are
|
|
@@ -43976,7 +43960,7 @@ const init = function init(options) {
|
|
|
43976
43960
|
// This section replaces widgets
|
|
43977
43961
|
// When it's time to replace the transformer widget
|
|
43978
43962
|
// uncomment this line
|
|
43979
|
-
// Widgets.replaceWidget("transformer", "
|
|
43963
|
+
// Widgets.replaceWidget("transformer", "deprecated-standin");
|
|
43980
43964
|
|
|
43981
43965
|
// Pass skipMathJax: true if MathJax is already loaded and configured.
|
|
43982
43966
|
const skipMathJax = options.skipMathJax;
|
|
@@ -44002,7 +43986,7 @@ const init = function init(options) {
|
|
|
44002
43986
|
|
|
44003
43987
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
44004
43988
|
const libName = "@khanacademy/perseus";
|
|
44005
|
-
const libVersion = "
|
|
43989
|
+
const libVersion = "18.0.1";
|
|
44006
43990
|
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
44007
43991
|
|
|
44008
43992
|
const apiVersion = {
|
|
@@ -46588,6 +46572,423 @@ const registerAllWidgetsForTesting = () => {
|
|
|
46588
46572
|
// Widgets.replaceWidget("transformer", "deprecated-standin");
|
|
46589
46573
|
};
|
|
46590
46574
|
|
|
46575
|
+
/**
|
|
46576
|
+
* This function extracts the answers from the widgets.
|
|
46577
|
+
*
|
|
46578
|
+
* For each widget type, we first check to make sure the path to the answer exists,
|
|
46579
|
+
* then we extract the answer and add it to the list of answers.
|
|
46580
|
+
*
|
|
46581
|
+
* A list is returned because some widgets, like multi-select radio widgets and groups
|
|
46582
|
+
* can have multiple answers.
|
|
46583
|
+
*
|
|
46584
|
+
* @param {PerseusRenderer["widgets"]} widgets
|
|
46585
|
+
* @returns a list of strings that are the answers to the widgets
|
|
46586
|
+
*/
|
|
46587
|
+
function getAnswersFromWidgets(widgets) {
|
|
46588
|
+
const answers = [];
|
|
46589
|
+
keys(widgets).forEach(widgetID => {
|
|
46590
|
+
var _options$choices, _categorizer$options, _categorizer$options2, _categorizer$options3, _dropdown$options, _numericInput$options, _inputNumber$options, _expression$options, _gradedGroup$options, _plotter$options, _plotter$options2, _grapher$options, _orderer$options, _sorter$options, _labelImage$options, _numberLine$options, _matrix$options, _matcher$options, _matcher$options2;
|
|
46591
|
+
const widget = widgets[widgetID];
|
|
46592
|
+
if (!widget.options) {
|
|
46593
|
+
return;
|
|
46594
|
+
}
|
|
46595
|
+
switch (widget.type) {
|
|
46596
|
+
case "radio":
|
|
46597
|
+
const radio = widget;
|
|
46598
|
+
const options = radio.options;
|
|
46599
|
+
// Answer is the content of the correct choice
|
|
46600
|
+
if (options != null && (_options$choices = options.choices) != null && _options$choices.length) {
|
|
46601
|
+
for (const choice of options.choices) {
|
|
46602
|
+
if (choice != null && choice.correct) {
|
|
46603
|
+
answers.push(choice.content);
|
|
46604
|
+
}
|
|
46605
|
+
}
|
|
46606
|
+
}
|
|
46607
|
+
break;
|
|
46608
|
+
case "categorizer":
|
|
46609
|
+
// Answer is a list of items mapped to their category
|
|
46610
|
+
// e.g. ['item 1: category 1', 'item 2: category 2']
|
|
46611
|
+
const categorizer = widget;
|
|
46612
|
+
if ((_categorizer$options = categorizer.options) != null && _categorizer$options.categories && (_categorizer$options2 = categorizer.options) != null && _categorizer$options2.items && (_categorizer$options3 = categorizer.options) != null && _categorizer$options3.values) {
|
|
46613
|
+
var _categorizer$options4, _categorizer$options5, _categorizer$options6;
|
|
46614
|
+
const categories = (_categorizer$options4 = categorizer.options) == null ? void 0 : _categorizer$options4.categories;
|
|
46615
|
+
const items = (_categorizer$options5 = categorizer.options) == null ? void 0 : _categorizer$options5.items;
|
|
46616
|
+
const values = (_categorizer$options6 = categorizer.options) == null ? void 0 : _categorizer$options6.values;
|
|
46617
|
+
answers.push(...values.map((value, index) => `${items[index]}: ${categories[value]}`));
|
|
46618
|
+
}
|
|
46619
|
+
break;
|
|
46620
|
+
case "dropdown":
|
|
46621
|
+
// Answer is the content of correct dropdown option
|
|
46622
|
+
const dropdown = widget;
|
|
46623
|
+
if ((_dropdown$options = dropdown.options) != null && _dropdown$options.choices) {
|
|
46624
|
+
for (const choice of dropdown.options.choices) {
|
|
46625
|
+
if (choice.correct) {
|
|
46626
|
+
answers.push(choice.content);
|
|
46627
|
+
}
|
|
46628
|
+
}
|
|
46629
|
+
}
|
|
46630
|
+
break;
|
|
46631
|
+
case "numeric-input":
|
|
46632
|
+
// Answer is the numeric value cast to a string
|
|
46633
|
+
const numericInput = widget;
|
|
46634
|
+
if ((_numericInput$options = numericInput.options) != null && _numericInput$options.answers) {
|
|
46635
|
+
for (const ans of numericInput.options.answers) {
|
|
46636
|
+
if (ans.status === "correct" && ans.value != null) {
|
|
46637
|
+
answers.push(ans.value.toString());
|
|
46638
|
+
}
|
|
46639
|
+
}
|
|
46640
|
+
}
|
|
46641
|
+
break;
|
|
46642
|
+
case "input-number":
|
|
46643
|
+
// Answer is the correct value
|
|
46644
|
+
const inputNumber = widget;
|
|
46645
|
+
if ((_inputNumber$options = inputNumber.options) != null && _inputNumber$options.value) {
|
|
46646
|
+
answers.push(inputNumber.options.value.toString());
|
|
46647
|
+
}
|
|
46648
|
+
break;
|
|
46649
|
+
case "expression":
|
|
46650
|
+
// Answer is a list of potential correct expressions,
|
|
46651
|
+
// since there can be multiple correct forms of the expression
|
|
46652
|
+
const expression = widget;
|
|
46653
|
+
if ((_expression$options = expression.options) != null && _expression$options.answerForms) {
|
|
46654
|
+
answers.push(...expression.options.answerForms.map(answer => answer.value));
|
|
46655
|
+
}
|
|
46656
|
+
break;
|
|
46657
|
+
case "group":
|
|
46658
|
+
case "graded-group":
|
|
46659
|
+
// Groups can contain multiple widgets, so we recursively
|
|
46660
|
+
// call this function to get the nested answers from the
|
|
46661
|
+
// group's widgets
|
|
46662
|
+
const gradedGroup = widget;
|
|
46663
|
+
if ((_gradedGroup$options = gradedGroup.options) != null && _gradedGroup$options.widgets) {
|
|
46664
|
+
answers.push(...getAnswersFromWidgets(gradedGroup.options.widgets));
|
|
46665
|
+
}
|
|
46666
|
+
break;
|
|
46667
|
+
case "plotter":
|
|
46668
|
+
// Answer is a map of the correct value for each category
|
|
46669
|
+
// e.g. '{Soccer: 35, Basketball: 20, Golf: 45, Volleyball: 15,
|
|
46670
|
+
// Tennis: 30}'
|
|
46671
|
+
const plotter = widget;
|
|
46672
|
+
if ((_plotter$options = plotter.options) != null && _plotter$options.categories && (_plotter$options2 = plotter.options) != null && _plotter$options2.correct && plotter.options.categories.length === plotter.options.correct.length) {
|
|
46673
|
+
const {
|
|
46674
|
+
categories,
|
|
46675
|
+
correct
|
|
46676
|
+
} = plotter.options;
|
|
46677
|
+
answers.push(`{${categories.map((category, index) => `${category}: ${correct[index]}`).join(", ")}}`);
|
|
46678
|
+
}
|
|
46679
|
+
break;
|
|
46680
|
+
case "interactive-graph":
|
|
46681
|
+
case "grapher":
|
|
46682
|
+
// Answer is a list of points that should be on the graph
|
|
46683
|
+
// E.g. 'There should be point(s) on [(-1, 1), (1, 1)]'
|
|
46684
|
+
const grapher = widget;
|
|
46685
|
+
// @ts-expect-error - TS2339 - Property 'coords' does not exist on type '{ type: "absolute_value"; coords: [Coord, Coord]; } | { type: "exponential"; asymptote: [Coord, Coord]; coords: [Coord, Coord]; } | { type: "linear"; coords: [...]; } | ... 4 more ... | PerseusGraphType'.
|
|
46686
|
+
if ((_grapher$options = grapher.options) != null && (_grapher$options = _grapher$options.correct) != null && _grapher$options.coords) {
|
|
46687
|
+
var _grapher$options$corr;
|
|
46688
|
+
answers.push(
|
|
46689
|
+
// @ts-expect-error - TS2339 - Property 'coords' does not exist on type '{ type: "absolute_value"; coords: [Coord, Coord]; } | { type: "exponential"; asymptote: [Coord, Coord]; coords: [Coord, Coord]; } | { type: "linear"; coords: [...]; } | ... 4 more ... | PerseusGraphType'.
|
|
46690
|
+
`There should be point(s) on [${(_grapher$options$corr = grapher.options.correct) == null ? void 0 : _grapher$options$corr.coords.join("], [")}]`);
|
|
46691
|
+
}
|
|
46692
|
+
break;
|
|
46693
|
+
case "orderer":
|
|
46694
|
+
// Answer is the content of the each option
|
|
46695
|
+
// in the correct order on newlines
|
|
46696
|
+
// E.g. 'item 1\nitem 2\nitem 3'
|
|
46697
|
+
const orderer = widget;
|
|
46698
|
+
if ((_orderer$options = orderer.options) != null && _orderer$options.correctOptions) {
|
|
46699
|
+
answers.push(orderer.options.correctOptions.map(option => option.content).join("\n"));
|
|
46700
|
+
}
|
|
46701
|
+
break;
|
|
46702
|
+
case "sorter":
|
|
46703
|
+
// Answer is the content of each option in the correct order
|
|
46704
|
+
// separated by commas
|
|
46705
|
+
// E.g. 'item 1, item 2, item 3'
|
|
46706
|
+
const sorter = widget;
|
|
46707
|
+
if ((_sorter$options = sorter.options) != null && _sorter$options.correct) {
|
|
46708
|
+
answers.push(sorter.options.correct.join(", "));
|
|
46709
|
+
}
|
|
46710
|
+
break;
|
|
46711
|
+
case "label-image":
|
|
46712
|
+
// Answer is a list of labels, their positions, and their answer
|
|
46713
|
+
// E.g. '{label: "label 1", position: {x: 0.5, y: 0.5}, answer: "answer 1"}'
|
|
46714
|
+
const labelImage = widget;
|
|
46715
|
+
if ((_labelImage$options = labelImage.options) != null && _labelImage$options.markers) {
|
|
46716
|
+
answers.push(...labelImage.options.markers.map(m => `{label: "${m.label}", position: {${m.x},${m.y}}, answer: "${m.answers.join(", ")}"}`));
|
|
46717
|
+
}
|
|
46718
|
+
break;
|
|
46719
|
+
case "number-line":
|
|
46720
|
+
// Answer is the correct x position on the number line
|
|
46721
|
+
// E.g. '0.5'
|
|
46722
|
+
const numberLine = widget;
|
|
46723
|
+
if (((_numberLine$options = numberLine.options) == null ? void 0 : _numberLine$options.correctX) != null) {
|
|
46724
|
+
answers.push(numberLine.options.correctX.toString());
|
|
46725
|
+
}
|
|
46726
|
+
break;
|
|
46727
|
+
case "matrix":
|
|
46728
|
+
// Answer is a list of rows representing the matrix
|
|
46729
|
+
// E.g. '[[1, 2], [3, 4]]'
|
|
46730
|
+
const matrix = widget;
|
|
46731
|
+
if ((_matrix$options = matrix.options) != null && _matrix$options.answers) {
|
|
46732
|
+
answers.push(`[${matrix.options.answers.join("], [")}]`);
|
|
46733
|
+
}
|
|
46734
|
+
break;
|
|
46735
|
+
case "matcher":
|
|
46736
|
+
// Answer is a table of the left and right items
|
|
46737
|
+
// E.g. '| Left | Right |\n| --- | --- |\n| left item 1 | right item 1 |\n| left item 2 | right item 2 |'
|
|
46738
|
+
const matcher = widget;
|
|
46739
|
+
if ((_matcher$options = matcher.options) != null && _matcher$options.left && (_matcher$options2 = matcher.options) != null && _matcher$options2.right) {
|
|
46740
|
+
const {
|
|
46741
|
+
left,
|
|
46742
|
+
right
|
|
46743
|
+
} = matcher.options;
|
|
46744
|
+
const [leftHeader, rightHeader] = matcher.options.labels;
|
|
46745
|
+
const tableHeader = `| ${leftHeader} | ${rightHeader} |\n| --- | --- |`;
|
|
46746
|
+
const tableRows = left.map((leftItem, index) => {
|
|
46747
|
+
return `| ${leftItem} | ${right[index]} |`;
|
|
46748
|
+
});
|
|
46749
|
+
const table = [tableHeader, ...tableRows].join("\n");
|
|
46750
|
+
answers.push(table);
|
|
46751
|
+
}
|
|
46752
|
+
break;
|
|
46753
|
+
}
|
|
46754
|
+
});
|
|
46755
|
+
return answers;
|
|
46756
|
+
}
|
|
46757
|
+
|
|
46758
|
+
/**
|
|
46759
|
+
* Inject a string equivalent of the widgets into the content.
|
|
46760
|
+
*
|
|
46761
|
+
* Content may contain Perseus widgets, that looks like this: '[[☃ Radio 1]]'.
|
|
46762
|
+
* This function replaces those widgets with its equivalent string.
|
|
46763
|
+
*
|
|
46764
|
+
* @param {string} content
|
|
46765
|
+
* @param {PerseusRenderer["widgets"]} widgets
|
|
46766
|
+
* @returns
|
|
46767
|
+
*/
|
|
46768
|
+
function injectWidgets(content, widgets) {
|
|
46769
|
+
// The types for taskProgress.itemData are not well defined,
|
|
46770
|
+
// so there is a chance that widgets or content could be undefined.
|
|
46771
|
+
// In that scenario, simply exit early and return the content as is.
|
|
46772
|
+
if (!content) {
|
|
46773
|
+
return "";
|
|
46774
|
+
}
|
|
46775
|
+
if (!widgets) {
|
|
46776
|
+
return content;
|
|
46777
|
+
}
|
|
46778
|
+
let context = content;
|
|
46779
|
+
keys(widgets).forEach(widgetID => {
|
|
46780
|
+
var _radio$options, _image$options, _labelImage$options2, _explanation$options, _passage$options, _passage$options2, _group$options, _gradedGroup$options2, _categorizer$options7, _dropdown$options2, _definition$options, _orderer$options2, _sorter$options2, _interactiveGraph$opt, _numberLine$options2, _numberLine$options3, _numberLine$options4, _matrix$options2, _matcher$options3, _matcher$options4;
|
|
46781
|
+
const widget = widgets[widgetID];
|
|
46782
|
+
if (!widget.options) {
|
|
46783
|
+
return;
|
|
46784
|
+
}
|
|
46785
|
+
switch (widget.type) {
|
|
46786
|
+
case "radio":
|
|
46787
|
+
// Replace radio with the radio options
|
|
46788
|
+
// '[[☃ Radio 1]]' -> 'option 1\noption 2\noption 3'
|
|
46789
|
+
const radio = widget;
|
|
46790
|
+
if ((_radio$options = radio.options) != null && (_radio$options = _radio$options.choices) != null && _radio$options.length) {
|
|
46791
|
+
var _radio$options2;
|
|
46792
|
+
let radioContext = radio.options.choices.map(choice => choice.content).join("\n");
|
|
46793
|
+
if ((_radio$options2 = radio.options) != null && _radio$options2.randomize) {
|
|
46794
|
+
radioContext += "\nThose options are displayed in a different order to the user. If the user says the letter, number, or ordinal number, always ask them clarify which option they are referring to.\n";
|
|
46795
|
+
}
|
|
46796
|
+
context = context.replace(`[[☃ ${widgetID}]]`, radioContext);
|
|
46797
|
+
}
|
|
46798
|
+
break;
|
|
46799
|
+
case "image":
|
|
46800
|
+
// Replace image with the image's alt text
|
|
46801
|
+
// '[[☃ Image 1]]' -> '<img id="Image 1" alt="The text for this image">'
|
|
46802
|
+
const image = widget;
|
|
46803
|
+
if ((_image$options = image.options) != null && _image$options.alt) {
|
|
46804
|
+
context = context.replace(`[[☃ ${widgetID}]]`, `<img id="${widgetID}" alt="${image.options.alt}">`);
|
|
46805
|
+
}
|
|
46806
|
+
break;
|
|
46807
|
+
case "label-image":
|
|
46808
|
+
// Replace label-image with the label-image's alt text
|
|
46809
|
+
// '[[☃ Label Image 1]]' -> '[An image with dots that user needs to label. Label choices: [choice 1, choice 2]. Image alt text: The text for this image]'
|
|
46810
|
+
const labelImage = widget;
|
|
46811
|
+
if ((_labelImage$options2 = labelImage.options) != null && _labelImage$options2.imageAlt) {
|
|
46812
|
+
var _labelImage$options$i, _labelImage$options3;
|
|
46813
|
+
context = context.replace(`[[☃ ${widgetID}]]`, `[An image with dots that user needs to label. Label choices: [${labelImage.options.choices.join(", ")}]. Image alt text: ${(_labelImage$options$i = (_labelImage$options3 = labelImage.options) == null ? void 0 : _labelImage$options3.imageAlt) != null ? _labelImage$options$i : ""}]`);
|
|
46814
|
+
}
|
|
46815
|
+
break;
|
|
46816
|
+
case "explanation":
|
|
46817
|
+
// Replace explaintion with the explaination text
|
|
46818
|
+
// Note: explainations can have widgets inside of them too
|
|
46819
|
+
// '[[☃ Explanation 1]]' -> 'This is the hidden explanation text'
|
|
46820
|
+
const explanation = widget;
|
|
46821
|
+
if ((_explanation$options = explanation.options) != null && _explanation$options.explanation) {
|
|
46822
|
+
context = context.replace(`[[☃ ${widgetID}]]`, injectWidgets(explanation.options.explanation, explanation.options.widgets));
|
|
46823
|
+
}
|
|
46824
|
+
break;
|
|
46825
|
+
case "passage":
|
|
46826
|
+
// Replace passage with the passage text
|
|
46827
|
+
// '[[☃ Passage 1]]' -> '# Passage Title\n\nPassage text'
|
|
46828
|
+
const passage = widget;
|
|
46829
|
+
if ((_passage$options = passage.options) != null && _passage$options.passageTitle || (_passage$options2 = passage.options) != null && _passage$options2.passageText) {
|
|
46830
|
+
const {
|
|
46831
|
+
passageTitle,
|
|
46832
|
+
passageText
|
|
46833
|
+
} = passage.options;
|
|
46834
|
+
context = context.replace(`[[☃ ${widgetID}]]`, `# ${passageTitle}\n\n${passageText}`);
|
|
46835
|
+
}
|
|
46836
|
+
break;
|
|
46837
|
+
case "group":
|
|
46838
|
+
case "graded-group":
|
|
46839
|
+
// Replace group with the group's nested content
|
|
46840
|
+
// '[[☃ Group 1]]' -> 'Nested question\n[[☃ Radio 1]]'
|
|
46841
|
+
// -> 'Nested question\noption 1\noption 2\noption 3'
|
|
46842
|
+
const group = widget;
|
|
46843
|
+
if ((_group$options = group.options) != null && _group$options.widgets && group.options.content) {
|
|
46844
|
+
context = context.replace(`[[☃ ${widgetID}]]`, injectWidgets(group.options.content, group.options.widgets));
|
|
46845
|
+
}
|
|
46846
|
+
break;
|
|
46847
|
+
case "graded-group-set":
|
|
46848
|
+
// Replace graded-group-set with the the nested groups
|
|
46849
|
+
// '[[☃ Graded Group Set 1]]'
|
|
46850
|
+
// -> 'Nested question 1\noption 1\noption 2\noption 3\nNested question 2\noption 1\noption 2\noption 3'
|
|
46851
|
+
const gradedGroup = widget;
|
|
46852
|
+
if ((_gradedGroup$options2 = gradedGroup.options) != null && _gradedGroup$options2.gradedGroups) {
|
|
46853
|
+
const gradedGroups = gradedGroup.options.gradedGroups;
|
|
46854
|
+
const gradedGroupsContent = gradedGroups.reduce((acc, group) => {
|
|
46855
|
+
if (group.widgets && group.content) {
|
|
46856
|
+
acc += injectWidgets(group.content, group.widgets) + "\n";
|
|
46857
|
+
}
|
|
46858
|
+
return acc;
|
|
46859
|
+
}, "");
|
|
46860
|
+
context = context.replace(`[[☃ ${widgetID}]]`, gradedGroupsContent);
|
|
46861
|
+
}
|
|
46862
|
+
break;
|
|
46863
|
+
case "categorizer":
|
|
46864
|
+
// Replace categorizer with the options and categories
|
|
46865
|
+
// '[[☃ Categorizer 1]]' -> '''
|
|
46866
|
+
// For each item, select the correct category. Categories: category 1, category 2, category 3.
|
|
46867
|
+
// Items:
|
|
46868
|
+
// item 1
|
|
46869
|
+
// item 2
|
|
46870
|
+
// '''
|
|
46871
|
+
const categorizer = widget;
|
|
46872
|
+
if ((_categorizer$options7 = categorizer.options) != null && _categorizer$options7.categories && categorizer.options.items) {
|
|
46873
|
+
const categories = categorizer.options.categories;
|
|
46874
|
+
const items = categorizer.options.items;
|
|
46875
|
+
context = context.replace(`[[☃ ${widgetID}]]`, `For each item, select the correct category. Categories: ${categories.join(", ")}.\nItems:\n${items.join("\n")}\n`);
|
|
46876
|
+
}
|
|
46877
|
+
break;
|
|
46878
|
+
case "dropdown":
|
|
46879
|
+
// Replace dropdown with the dropdown options
|
|
46880
|
+
// '[[☃ Dropdown 1]]' -> '[option 1 | option 2 | option 3]'
|
|
46881
|
+
const dropdown = widget;
|
|
46882
|
+
if ((_dropdown$options2 = dropdown.options) != null && _dropdown$options2.choices) {
|
|
46883
|
+
const choices = dropdown.options.choices.map(choice => choice.content);
|
|
46884
|
+
context = context.replace(`[[☃ ${widgetID}]]`, `[${choices.join(" | ")}]`);
|
|
46885
|
+
}
|
|
46886
|
+
break;
|
|
46887
|
+
case "definition":
|
|
46888
|
+
// Replace definition with the word that is being defined
|
|
46889
|
+
// NOTE(chase): this does not include the definition itself,
|
|
46890
|
+
// because I believe the AI can infer that without wasting extra tokens
|
|
46891
|
+
// '[[☃ Definition 1]]' -> 'word'
|
|
46892
|
+
const definition = widget;
|
|
46893
|
+
if ((_definition$options = definition.options) != null && _definition$options.togglePrompt) {
|
|
46894
|
+
context = context.replace(`[[☃ ${widgetID}]]`, definition.options.togglePrompt);
|
|
46895
|
+
}
|
|
46896
|
+
break;
|
|
46897
|
+
case "orderer":
|
|
46898
|
+
// Replace orderer with the orderer options
|
|
46899
|
+
// '[[☃ Orderer 1]]' -> 'option 1\noption 2\noption 3'
|
|
46900
|
+
const orderer = widget;
|
|
46901
|
+
if ((_orderer$options2 = orderer.options) != null && _orderer$options2.options) {
|
|
46902
|
+
context = context.replace(`[[☃ ${widgetID}]]`, orderer.options.options.map(option => option.content).join("\n"));
|
|
46903
|
+
}
|
|
46904
|
+
break;
|
|
46905
|
+
case "sorter":
|
|
46906
|
+
// Replace sorter with the options
|
|
46907
|
+
// NOTE(chase): These are already in the correct order
|
|
46908
|
+
// '[[☃ Sorter 1]]' -> '[option 1 | option 2 | option 3]'
|
|
46909
|
+
const sorter = widget;
|
|
46910
|
+
if ((_sorter$options2 = sorter.options) != null && _sorter$options2.correct) {
|
|
46911
|
+
const choices = sorter.options.correct;
|
|
46912
|
+
context = context.replace(`[[☃ ${widgetID}]]`, `[${choices.join(" | ")}]`);
|
|
46913
|
+
}
|
|
46914
|
+
break;
|
|
46915
|
+
case "interactive-graph":
|
|
46916
|
+
// Replace interactive-graph with the graph's range
|
|
46917
|
+
// '[[☃ Interactive Graph 1]]' -> '[Graph with an x range of -10 to 10 and y range of -10 to 10]'
|
|
46918
|
+
const interactiveGraph = widget;
|
|
46919
|
+
if (((_interactiveGraph$opt = interactiveGraph.options) == null ? void 0 : _interactiveGraph$opt.range.length) === 2) {
|
|
46920
|
+
const [x, y] = interactiveGraph.options.range;
|
|
46921
|
+
context = context.replace(`[[☃ ${widgetID}]]`, `[Graph with an x range of ${x[0]} to ${x[1]} and y range of ${y[0]} to ${y[1]}]`);
|
|
46922
|
+
}
|
|
46923
|
+
break;
|
|
46924
|
+
case "number-line":
|
|
46925
|
+
// Replace number-line with the number line's range, step, and initial position
|
|
46926
|
+
// '[[☃ Number Line 1]]' -> '[Number line with a range of -10 to 10, a step of 1, and an initial position of 0]'
|
|
46927
|
+
const numberLine = widget;
|
|
46928
|
+
if (((_numberLine$options2 = numberLine.options) == null ? void 0 : _numberLine$options2.range.length) === 2 && (_numberLine$options3 = numberLine.options) != null && _numberLine$options3.tickStep && (_numberLine$options4 = numberLine.options) != null && _numberLine$options4.initialX) {
|
|
46929
|
+
const [min, max] = numberLine.options.range;
|
|
46930
|
+
const step = numberLine.options.tickStep;
|
|
46931
|
+
const initialPosition = numberLine.options.initialX;
|
|
46932
|
+
context = context.replace(`[[☃ ${widgetID}]]`, `[Number line with a range of ${min} to ${max}, a step of ${step}, and an initial position of ${initialPosition}]`);
|
|
46933
|
+
}
|
|
46934
|
+
break;
|
|
46935
|
+
case "matrix":
|
|
46936
|
+
// Replace matrix with the matrix's dimensions
|
|
46937
|
+
// '[[☃ Matrix 1]]' -> '[Matrix with 2 rows and 3 columns. The user can click on each cell to enter a value]'
|
|
46938
|
+
const matrix = widget;
|
|
46939
|
+
if (((_matrix$options2 = matrix.options) == null ? void 0 : _matrix$options2.matrixBoardSize.length) === 2) {
|
|
46940
|
+
const [rows, columns] = matrix.options.matrixBoardSize;
|
|
46941
|
+
context = context.replace(`[[☃ ${widgetID}]]`, `[Matrix with ${rows} rows and ${columns} columns. The user can click on each cell to enter a value]`);
|
|
46942
|
+
}
|
|
46943
|
+
break;
|
|
46944
|
+
case "matcher":
|
|
46945
|
+
// Replace matcher with a table representing the left and right columns
|
|
46946
|
+
// '[[☃ Matcher 1]]' -> '| Left Column | Right Column |\n| --- | --- |\n| option 1 | option 2 |\n| option 3 | option 4 |'
|
|
46947
|
+
const matcher = widget;
|
|
46948
|
+
if ((_matcher$options3 = matcher.options) != null && _matcher$options3.left && (_matcher$options4 = matcher.options) != null && _matcher$options4.right) {
|
|
46949
|
+
const {
|
|
46950
|
+
left,
|
|
46951
|
+
right
|
|
46952
|
+
} = matcher.options;
|
|
46953
|
+
const [leftHeader, rightHeader] = matcher.options.labels;
|
|
46954
|
+
const tableHeader = `| ${leftHeader} | ${rightHeader} |\n| --- | --- |`;
|
|
46955
|
+
const tableRows = left.map((leftItem, index) => {
|
|
46956
|
+
return `| ${leftItem} | ${right[index]} |`;
|
|
46957
|
+
});
|
|
46958
|
+
const table = [tableHeader, ...tableRows].join("\n");
|
|
46959
|
+
const matcherWidgetExplanation = "The user needs to move items in the right column to match the correct option on the left. The order of items on the right side will be different from what the user sees.";
|
|
46960
|
+
context = context.replace(`[[☃ ${widgetID}]]`, `${matcherWidgetExplanation}\n${table}`);
|
|
46961
|
+
}
|
|
46962
|
+
break;
|
|
46963
|
+
case "numeric-input":
|
|
46964
|
+
case "input-number":
|
|
46965
|
+
case "expression":
|
|
46966
|
+
// These widgets are just input fields, so we replace them with a ?
|
|
46967
|
+
// 'Solve 2+2 [[☃ Numeric Input 1]]' -> 'Solve 2+2 ?'
|
|
46968
|
+
// (we've found that using "?" gives the best results on fill-
|
|
46969
|
+
// in-the-blank questions).
|
|
46970
|
+
context = context.replace(`[[☃ ${widgetID}]]`, "?");
|
|
46971
|
+
break;
|
|
46972
|
+
default:
|
|
46973
|
+
// Tell the bot that it doesn't understand this widget
|
|
46974
|
+
context = context.replace(`[[☃ ${widgetID}]]`, `[[Unsupported ${widget.type} widget: Explain to the user that you are unable to understand the content in this widget and ask them to describe it.]]`);
|
|
46975
|
+
}
|
|
46976
|
+
});
|
|
46977
|
+
return context;
|
|
46978
|
+
}
|
|
46979
|
+
|
|
46980
|
+
/**
|
|
46981
|
+
* Helper to parse Perseus JSON
|
|
46982
|
+
* Why not just use JSON.parse? We want:
|
|
46983
|
+
* - To make sure types are correct
|
|
46984
|
+
* - To give us a central place to validate/transform output if needed
|
|
46985
|
+
* @param {string} input - the stringified Perseus JSON
|
|
46986
|
+
* @returns {PerseusItem} the parsed PerseusItem object
|
|
46987
|
+
*/
|
|
46988
|
+
function parsePerseusJSON(input) {
|
|
46989
|
+
return JSON.parse(input);
|
|
46990
|
+
}
|
|
46991
|
+
|
|
46591
46992
|
const EditorJsonify = {
|
|
46592
46993
|
serialize: function () {
|
|
46593
46994
|
// Omit props that get passed to all widgets
|
|
@@ -46596,5 +46997,5 @@ const EditorJsonify = {
|
|
|
46596
46997
|
}
|
|
46597
46998
|
};
|
|
46598
46999
|
|
|
46599
|
-
export { ApiOptions, ArrowPicker, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, ColorPicker, DashPicker, dependencies as Dependencies, EditorJsonify, ElementContainer, Errors, Expression, util as GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, ItemExtras, JiptParagraphs, KhanColors, KhanMath, context as LoadingContext, Log, Matrix$1 as MatrixWidget, MultiItems, NumericInput$1 as NumericInput, PerseusError, PerseusExpressionAnswerFormConsidered, PerseusMarkdown, Plotter$1 as PlotterWidget, Radio, Renderer, ref as ServerItemRenderer, sizingUtils as SizingUtils, Table$1 as TableWidget, Transformer$1 as TransformerWidget, Util, denylist as WIDGET_PROP_DENYLIST, WidgetJsonifyDeprecated, widgets$1 as Widgets, apiVersion, buildEmptyItemTreeForShape, buildMapper, components, constants, displaySigFigs, globalConstants, globalStyles, iconPaths as icons, init, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, itemDataVersion, itemToTree, ItemVersion as itemVersion, libVersion, mediaQueries, plotterPlotTypes, preprocessTex, registerAllWidgetsForTesting, shapes, traverse, violatingWidgets, allWidgets as widgets };
|
|
47000
|
+
export { ApiOptions, ArrowPicker, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, ColorPicker, DashPicker, dependencies as Dependencies, EditorJsonify, ElementContainer, Errors, Expression, util as GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, ItemExtras, JiptParagraphs, KhanColors, KhanMath, context as LoadingContext, Log, Matrix$1 as MatrixWidget, MultiItems, NumericInput$1 as NumericInput, PerseusError, PerseusExpressionAnswerFormConsidered, PerseusMarkdown, Plotter$1 as PlotterWidget, Radio, Renderer, ref as ServerItemRenderer, sizingUtils as SizingUtils, Table$1 as TableWidget, Transformer$1 as TransformerWidget, Util, denylist as WIDGET_PROP_DENYLIST, WidgetJsonifyDeprecated, widgets$1 as Widgets, apiVersion, buildEmptyItemTreeForShape, buildMapper, components, constants, displaySigFigs, getAnswersFromWidgets, globalConstants, globalStyles, iconPaths as icons, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, itemDataVersion, itemToTree, ItemVersion as itemVersion, libVersion, mediaQueries, parsePerseusJSON, plotterPlotTypes, preprocessTex, registerAllWidgetsForTesting, shapes, traverse, violatingWidgets, allWidgets as widgets };
|
|
46600
47001
|
//# sourceMappingURL=index.js.map
|