@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/index.js
CHANGED
|
@@ -36,6 +36,7 @@ var reactPopper = require('react-popper');
|
|
|
36
36
|
var createReactClass = require('create-react-class');
|
|
37
37
|
var Link = require('@khanacademy/wonder-blocks-link');
|
|
38
38
|
var perseusCore = require('@khanacademy/perseus-core');
|
|
39
|
+
var wonderStuffCore = require('@khanacademy/wonder-stuff-core');
|
|
39
40
|
|
|
40
41
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
41
42
|
|
|
@@ -8536,6 +8537,89 @@ const KhanColors = {
|
|
|
8536
8537
|
DYNAMIC: "#6495ED" // BLUE
|
|
8537
8538
|
};
|
|
8538
8539
|
|
|
8540
|
+
// DrawingTransform is responsible for transforming from "math coordinates"
|
|
8541
|
+
// (on the Cartesian plane) to "canvas coordinates" in pixels.
|
|
8542
|
+
class DrawingTransform {
|
|
8543
|
+
// Scale is measured in pixels per unit on the Cartesian plane.
|
|
8544
|
+
|
|
8545
|
+
constructor(raphael, initialScale, bounds) {
|
|
8546
|
+
this.raphael = raphael;
|
|
8547
|
+
this.bounds = bounds;
|
|
8548
|
+
this.xScale = initialScale[0];
|
|
8549
|
+
this.yScale = initialScale[1];
|
|
8550
|
+
this.setScale(initialScale);
|
|
8551
|
+
}
|
|
8552
|
+
|
|
8553
|
+
// TODO(benchristel): rename these methods to transform* instead of scale*.
|
|
8554
|
+
// They're doing more than scaling! Some of them also translate.
|
|
8555
|
+
scaleVector = point => {
|
|
8556
|
+
if (typeof point === "number") {
|
|
8557
|
+
return this.scaleVector([point, point]);
|
|
8558
|
+
}
|
|
8559
|
+
const x = point[0];
|
|
8560
|
+
const y = point[1];
|
|
8561
|
+
return [x * this.xScale, y * this.yScale];
|
|
8562
|
+
};
|
|
8563
|
+
scalePoint = point => {
|
|
8564
|
+
if (typeof point === "number") {
|
|
8565
|
+
return this.scalePoint([point, point]);
|
|
8566
|
+
}
|
|
8567
|
+
const x = point[0];
|
|
8568
|
+
const y = point[1];
|
|
8569
|
+
return [(x - this.bounds.xMin) * this.xScale, (this.bounds.yMax - y) * this.yScale];
|
|
8570
|
+
};
|
|
8571
|
+
unscalePoint = point => {
|
|
8572
|
+
if (typeof point === "number") {
|
|
8573
|
+
return this.unscalePoint([point, point]);
|
|
8574
|
+
}
|
|
8575
|
+
const x = point[0];
|
|
8576
|
+
const y = point[1];
|
|
8577
|
+
return [x / this.xScale + this.bounds.xMin, this.bounds.yMax - y / this.yScale];
|
|
8578
|
+
};
|
|
8579
|
+
unscaleVector = point => {
|
|
8580
|
+
if (typeof point === "number") {
|
|
8581
|
+
return this.unscaleVector([point, point]);
|
|
8582
|
+
}
|
|
8583
|
+
return [point[0] / this.xScale, point[1] / this.yScale];
|
|
8584
|
+
};
|
|
8585
|
+
setScale = scale => {
|
|
8586
|
+
if (typeof scale === "number") {
|
|
8587
|
+
scale = [scale, scale];
|
|
8588
|
+
}
|
|
8589
|
+
this.xScale = scale[0];
|
|
8590
|
+
this.yScale = scale[1];
|
|
8591
|
+
|
|
8592
|
+
// Update the canvas size
|
|
8593
|
+
this.raphael.setSize(...this.canvasDimensions());
|
|
8594
|
+
};
|
|
8595
|
+
canvasDimensions = () => {
|
|
8596
|
+
return [this.bounds.width() * this.xScale, this.bounds.height() * this.yScale];
|
|
8597
|
+
};
|
|
8598
|
+
pixelsPerUnitX = () => {
|
|
8599
|
+
return this.xScale;
|
|
8600
|
+
};
|
|
8601
|
+
pixelsPerUnitY = () => {
|
|
8602
|
+
return this.yScale;
|
|
8603
|
+
};
|
|
8604
|
+
}
|
|
8605
|
+
|
|
8606
|
+
// Defines the bounds of the visible area of a graph, in Cartesian coordinates.
|
|
8607
|
+
// GraphBounds is immutable.
|
|
8608
|
+
class GraphBounds {
|
|
8609
|
+
constructor(xRange, yRange) {
|
|
8610
|
+
this.xMin = xRange[0];
|
|
8611
|
+
this.xMax = xRange[1];
|
|
8612
|
+
this.yMin = yRange[0];
|
|
8613
|
+
this.yMax = yRange[1];
|
|
8614
|
+
}
|
|
8615
|
+
width() {
|
|
8616
|
+
return this.xMax - this.xMin;
|
|
8617
|
+
}
|
|
8618
|
+
height() {
|
|
8619
|
+
return this.yMax - this.yMin;
|
|
8620
|
+
}
|
|
8621
|
+
}
|
|
8622
|
+
|
|
8539
8623
|
function render(element, container, callback) {
|
|
8540
8624
|
// TODO(LP-11406): Replace this, or callers, with React Portal
|
|
8541
8625
|
// @ts-expect-error - TS2322 - Type 'void' is not assignable to type 'ElementRef<ElementType>'.
|
|
@@ -8563,23 +8647,6 @@ function findChildOrAdd(elem, className) {
|
|
|
8563
8647
|
}
|
|
8564
8648
|
return $child;
|
|
8565
8649
|
}
|
|
8566
|
-
function doCallback(elem, callback) {
|
|
8567
|
-
let tries = 0;
|
|
8568
|
-
(function check() {
|
|
8569
|
-
const height = elem.scrollHeight;
|
|
8570
|
-
// Heuristic to guess if the font has kicked in
|
|
8571
|
-
// so we have box metrics (magic number ick,
|
|
8572
|
-
// but this seems to work mostly-consistently)
|
|
8573
|
-
if (height > 18 || tries >= 10) {
|
|
8574
|
-
callback();
|
|
8575
|
-
} else {
|
|
8576
|
-
tries++;
|
|
8577
|
-
// TODO(jeff, CP-3128): Use Wonder Blocks Timing API
|
|
8578
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
8579
|
-
setTimeout(check, 100);
|
|
8580
|
-
}
|
|
8581
|
-
})();
|
|
8582
|
-
}
|
|
8583
8650
|
var Tex$1 = {
|
|
8584
8651
|
// Process a node and add math inside of it. This attempts to use KaTeX to
|
|
8585
8652
|
// format the math, and if that fails it falls back to MathJax.
|
|
@@ -8601,27 +8668,14 @@ var Tex$1 = {
|
|
|
8601
8668
|
|
|
8602
8669
|
// Only process if it hasn't been done before, or it is forced
|
|
8603
8670
|
if ($elem.attr("data-math-formula") == null || force) {
|
|
8604
|
-
const $
|
|
8605
|
-
|
|
8606
|
-
|
|
8607
|
-
//
|
|
8608
|
-
//
|
|
8609
|
-
|
|
8610
|
-
|
|
8611
|
-
|
|
8612
|
-
const script = $mathjaxHolder.find("script[type='math/tex']")[0];
|
|
8613
|
-
|
|
8614
|
-
// If text wasn't provided, we look in two places
|
|
8615
|
-
if (text == null) {
|
|
8616
|
-
if ($elem.attr("data-math-formula")) {
|
|
8617
|
-
// The old typeset formula
|
|
8618
|
-
// @ts-expect-error - TS2322 - Type 'string | undefined' is not assignable to type 'string'.
|
|
8619
|
-
text = $elem.attr("data-math-formula");
|
|
8620
|
-
} else if (script) {
|
|
8621
|
-
// The contents of the <script> tag
|
|
8622
|
-
// @ts-expect-error - TS2339 - Property 'text' does not exist on type 'HTMLElement'.
|
|
8623
|
-
text = script.text || script.textContent;
|
|
8624
|
-
}
|
|
8671
|
+
const $texHolder = findChildOrAdd($elem, "tex-holder");
|
|
8672
|
+
|
|
8673
|
+
// If text wasn't provided, we use the cached text.
|
|
8674
|
+
// TODO(benchristel): I'm not sure if text can ever be null. It's
|
|
8675
|
+
// possible we don't need this check.
|
|
8676
|
+
if (text == null && $elem.attr("data-math-formula")) {
|
|
8677
|
+
// @ts-expect-error - TS2322 - Type 'string | undefined' is not assignable to type 'string'.
|
|
8678
|
+
text = $elem.attr("data-math-formula");
|
|
8625
8679
|
}
|
|
8626
8680
|
text = text != null ? text + "" : "";
|
|
8627
8681
|
|
|
@@ -8637,41 +8691,9 @@ var Tex$1 = {
|
|
|
8637
8691
|
// that name is already taken.
|
|
8638
8692
|
render( /*#__PURE__*/React__namespace.createElement(TeX, {
|
|
8639
8693
|
children: text,
|
|
8640
|
-
onRender:
|
|
8641
|
-
|
|
8642
|
-
doCallback(elem, callback);
|
|
8643
|
-
}
|
|
8644
|
-
}
|
|
8645
|
-
}), $katexHolder[0]);
|
|
8646
|
-
}
|
|
8647
|
-
},
|
|
8648
|
-
// Function to restore a node to a non-math-processed state
|
|
8649
|
-
cleanupMath: function (elem) {
|
|
8650
|
-
const $elem = $__default["default"](elem);
|
|
8651
|
-
|
|
8652
|
-
// Only mess with it if it's been processed before
|
|
8653
|
-
if ($elem.attr("data-math-formula")) {
|
|
8654
|
-
// Remove MathJax remnants
|
|
8655
|
-
if (typeof MathJax !== "undefined") {
|
|
8656
|
-
const jax = MathJax.Hub.getJaxFor($elem.find("script")[0]);
|
|
8657
|
-
if (jax) {
|
|
8658
|
-
const e = jax.SourceElement();
|
|
8659
|
-
if (e.previousSibling && e.previousSibling.className) {
|
|
8660
|
-
jax.Remove();
|
|
8661
|
-
}
|
|
8662
|
-
}
|
|
8663
|
-
}
|
|
8664
|
-
|
|
8665
|
-
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
8666
|
-
$elem.text($elem.attr("data-math-formula"));
|
|
8667
|
-
$elem.attr("data-math-formula", null);
|
|
8668
|
-
$elem.attr("data-math-type", null);
|
|
8694
|
+
onRender: callback
|
|
8695
|
+
}), $texHolder[0]);
|
|
8669
8696
|
}
|
|
8670
|
-
return elem;
|
|
8671
|
-
},
|
|
8672
|
-
// Function to retrieve the formula of a typeset math node
|
|
8673
|
-
retrieveMathFormula: function (elem) {
|
|
8674
|
-
return $__default["default"](elem).attr("data-math-formula");
|
|
8675
8697
|
}
|
|
8676
8698
|
};
|
|
8677
8699
|
|
|
@@ -8679,23 +8701,6 @@ var Tex$1 = {
|
|
|
8679
8701
|
const {
|
|
8680
8702
|
processMath: processMath$1
|
|
8681
8703
|
} = Tex$1;
|
|
8682
|
-
|
|
8683
|
-
/* Convert cartesian coordinates [x, y] to polar coordinates [r,
|
|
8684
|
-
* theta], with theta in degrees, or in radians if angleInRadians is
|
|
8685
|
-
* specified.
|
|
8686
|
-
*/
|
|
8687
|
-
function cartToPolar(coord, angleInRadians) {
|
|
8688
|
-
const r = Math.sqrt(Math.pow(coord[0], 2) + Math.pow(coord[1], 2));
|
|
8689
|
-
let theta = Math.atan2(coord[1], coord[0]);
|
|
8690
|
-
// convert angle range from [-pi, pi] to [0, 2pi]
|
|
8691
|
-
if (theta < 0) {
|
|
8692
|
-
theta += 2 * Math.PI;
|
|
8693
|
-
}
|
|
8694
|
-
if (!angleInRadians) {
|
|
8695
|
-
theta = theta * 180 / Math.PI;
|
|
8696
|
-
}
|
|
8697
|
-
return [r, theta];
|
|
8698
|
-
}
|
|
8699
8704
|
function polar(r, th) {
|
|
8700
8705
|
if (typeof r === "number") {
|
|
8701
8706
|
r = [r, r];
|
|
@@ -8751,135 +8756,336 @@ const GraphUtils = {
|
|
|
8751
8756
|
},
|
|
8752
8757
|
graphs: {}
|
|
8753
8758
|
};
|
|
8754
|
-
|
|
8755
|
-
|
|
8756
|
-
|
|
8757
|
-
|
|
8758
|
-
});
|
|
8759
|
-
const labelDirections = {
|
|
8760
|
-
center: [-0.5, -0.5],
|
|
8761
|
-
above: [-0.5, -1.0],
|
|
8762
|
-
"above right": [0.0, -1.0],
|
|
8763
|
-
right: [0.0, -0.5],
|
|
8764
|
-
"below right": [0.0, 0.0],
|
|
8765
|
-
below: [-0.5, 0.0],
|
|
8766
|
-
"below left": [-1.0, 0.0],
|
|
8767
|
-
left: [-1.0, -0.5],
|
|
8768
|
-
"above left": [-1.0, -1.0]
|
|
8769
|
-
};
|
|
8770
|
-
|
|
8771
|
-
/**
|
|
8772
|
-
* Safari applies some SVG-specific styles to things that are not SVGs, so we
|
|
8773
|
-
* need to exclude those styles from things that are not SVGs.
|
|
8774
|
-
*
|
|
8775
|
-
* To see this behavior in action, open https://codepen.io/anon/pen/zENEoa in
|
|
8776
|
-
* Safari.
|
|
8777
|
-
*
|
|
8778
|
-
* Usage `$.extend({}, someStyles, SVG_SPECIFIC_STYLE_MASK)`
|
|
8779
|
-
*/
|
|
8780
|
-
const SVG_SPECIFIC_STYLE_MASK = {
|
|
8781
|
-
"stroke-width": null
|
|
8782
|
-
};
|
|
8783
|
-
GraphUtils.createGraphie = function (el) {
|
|
8784
|
-
let xScale = 40;
|
|
8785
|
-
let yScale = 40;
|
|
8786
|
-
let xRange;
|
|
8787
|
-
let yRange;
|
|
8788
|
-
$__default["default"](el).css("position", "relative");
|
|
8789
|
-
const raphael$1 = raphael.exports(el);
|
|
8790
|
-
|
|
8791
|
-
// For a sometimes-reproducible IE8 bug; doesn't affect SVG browsers at all
|
|
8792
|
-
$__default["default"](el).children("div").css("position", "absolute");
|
|
8793
|
-
|
|
8759
|
+
class Graphie$1 {
|
|
8760
|
+
#bounds;
|
|
8761
|
+
#drawingTransform;
|
|
8762
|
+
isMobile = false;
|
|
8794
8763
|
// Set up some reasonable defaults
|
|
8795
|
-
|
|
8764
|
+
currentStyle = {
|
|
8796
8765
|
"stroke-width": 2,
|
|
8797
8766
|
fill: "none"
|
|
8798
8767
|
};
|
|
8799
|
-
|
|
8800
|
-
|
|
8801
|
-
|
|
8802
|
-
|
|
8803
|
-
|
|
8804
|
-
|
|
8805
|
-
|
|
8806
|
-
|
|
8807
|
-
|
|
8808
|
-
|
|
8809
|
-
|
|
8810
|
-
|
|
8811
|
-
|
|
8812
|
-
|
|
8813
|
-
|
|
8814
|
-
|
|
8815
|
-
|
|
8816
|
-
|
|
8817
|
-
|
|
8768
|
+
|
|
8769
|
+
// TODO(benchristel): xpixels and ypixels are never used by this class, but
|
|
8770
|
+
// other code reaches in to access them :(
|
|
8771
|
+
// Refactor to accessor methods that look at this.dimensions.
|
|
8772
|
+
constructor(el) {
|
|
8773
|
+
this.el = el;
|
|
8774
|
+
$__default["default"](el).css("position", "relative");
|
|
8775
|
+
this.raphael = raphael.exports(el);
|
|
8776
|
+
|
|
8777
|
+
// For a sometimes-reproducible IE8 bug; doesn't affect SVG browsers at all
|
|
8778
|
+
$__default["default"](el).children("div").css("position", "absolute");
|
|
8779
|
+
}
|
|
8780
|
+
init(options) {
|
|
8781
|
+
let scale = options.scale || [40, 40];
|
|
8782
|
+
scale = typeof scale === "number" ? [scale, scale] : scale;
|
|
8783
|
+
if (options.range == null) {
|
|
8784
|
+
throw new PerseusError("range should be specified in graph init", Errors.Internal);
|
|
8785
|
+
}
|
|
8786
|
+
this.#bounds = new GraphBounds(...options.range);
|
|
8787
|
+
this.#drawingTransform = new DrawingTransform(this.raphael, scale, this.bounds());
|
|
8788
|
+
const [w, h] = this.drawingTransform().canvasDimensions();
|
|
8789
|
+
$__default["default"](this.el).css({
|
|
8790
|
+
width: w,
|
|
8791
|
+
height: h
|
|
8792
|
+
});
|
|
8793
|
+
this.range = options.range;
|
|
8794
|
+
this.scale = scale;
|
|
8795
|
+
// TODO(benchristel): I don't think dimensions is used. Can we
|
|
8796
|
+
// remove it?
|
|
8797
|
+
this.dimensions = [w, h];
|
|
8798
|
+
this.xpixels = w;
|
|
8799
|
+
this.ypixels = h;
|
|
8800
|
+
this.isMobile = options.isMobile;
|
|
8801
|
+
return this;
|
|
8802
|
+
}
|
|
8803
|
+
|
|
8804
|
+
// Initializes graphie settings for a graph and draws the basic graph
|
|
8805
|
+
// features (axes, grid, tick marks, and axis labels)
|
|
8806
|
+
// Options expected are:
|
|
8807
|
+
// - range: [[a, b], [c, d]] or [a, b]
|
|
8808
|
+
// - scale: [a, b] or number
|
|
8809
|
+
// - gridOpacity: number (0 - 1)
|
|
8810
|
+
// - gridStep: [a, b] or number (relative to units)
|
|
8811
|
+
// - tickStep: [a, b] or number (relative to grid steps)
|
|
8812
|
+
// - tickLen: [a, b] or number (in pixels)
|
|
8813
|
+
// - labelStep: [a, b] or number (relative to tick steps)
|
|
8814
|
+
// - yLabelFormat: fn to format label string for y-axis
|
|
8815
|
+
// - xLabelFormat: fn to format label string for x-axis
|
|
8816
|
+
graphInit(options) {
|
|
8817
|
+
options = options || {};
|
|
8818
|
+
$__default["default"].each(options, function (prop, val) {
|
|
8819
|
+
// allow options to be specified by a single number for shorthand if
|
|
8820
|
+
// the horizontal and vertical components are the same
|
|
8821
|
+
if (
|
|
8822
|
+
// @ts-expect-error - TS2339 - Property 'match' does not exist on type 'string | number | symbol'.
|
|
8823
|
+
!prop.match(/.*Opacity$/) && prop !== "range" && typeof val === "number") {
|
|
8824
|
+
options[prop] = [val, val];
|
|
8825
|
+
}
|
|
8826
|
+
|
|
8827
|
+
// allow symmetric ranges to be specified by the absolute values
|
|
8828
|
+
if (prop === "range" || prop === "gridRange") {
|
|
8829
|
+
if (val.constructor === Array) {
|
|
8830
|
+
// but don't mandate symmetric ranges
|
|
8831
|
+
if (val[0].constructor !== Array) {
|
|
8832
|
+
options[prop] = [[-val[0], val[0]], [-val[1], val[1]]];
|
|
8833
|
+
}
|
|
8834
|
+
} else if (typeof val === "number") {
|
|
8835
|
+
options[prop] = [[-val, val], [-val, val]];
|
|
8836
|
+
}
|
|
8837
|
+
}
|
|
8838
|
+
});
|
|
8839
|
+
const range = options.range || [[-10, 10], [-10, 10]];
|
|
8840
|
+
const gridRange = options.gridRange || options.range;
|
|
8841
|
+
const scale = options.scale || [20, 20];
|
|
8842
|
+
const grid = options.grid != null ? options.grid : true;
|
|
8843
|
+
const gridOpacity = options.gridOpacity || 0.1;
|
|
8844
|
+
const gridStep = options.gridStep || [1, 1];
|
|
8845
|
+
const axes = options.axes != null ? options.axes : true;
|
|
8846
|
+
const axisArrows = options.axisArrows || "";
|
|
8847
|
+
const axisOpacity = options.axisOpacity || 1.0;
|
|
8848
|
+
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])];
|
|
8849
|
+
const axisLabels = options.axisLabels != null ? options.axisLabels : false;
|
|
8850
|
+
const ticks = options.ticks != null ? options.ticks : true;
|
|
8851
|
+
const tickStep = options.tickStep || [2, 2];
|
|
8852
|
+
const tickLen = options.tickLen || [5, 5];
|
|
8853
|
+
const tickOpacity = options.tickOpacity || 1.0;
|
|
8854
|
+
const labels = options.labels || options.labelStep || false;
|
|
8855
|
+
const labelStep = options.labelStep || [1, 1];
|
|
8856
|
+
const labelOpacity = options.labelOpacity || 1.0;
|
|
8857
|
+
let unityLabels = options.unityLabels || false;
|
|
8858
|
+
const labelFormat = options.labelFormat || function (a) {
|
|
8859
|
+
return a;
|
|
8860
|
+
};
|
|
8861
|
+
let xLabelFormat = options.xLabelFormat || labelFormat;
|
|
8862
|
+
let yLabelFormat = options.yLabelFormat || labelFormat;
|
|
8863
|
+
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)]];
|
|
8864
|
+
if (!___default["default"].isArray(unityLabels)) {
|
|
8865
|
+
unityLabels = [unityLabels, unityLabels];
|
|
8818
8866
|
}
|
|
8819
|
-
const
|
|
8820
|
-
|
|
8821
|
-
|
|
8822
|
-
|
|
8823
|
-
|
|
8824
|
-
|
|
8825
|
-
|
|
8867
|
+
const minusIgnorer = function (lf) {
|
|
8868
|
+
return function (a) {
|
|
8869
|
+
return (lf(a) + "").replace(/-(\d)/g, "\\llap{-}$1");
|
|
8870
|
+
};
|
|
8871
|
+
};
|
|
8872
|
+
xLabelFormat = minusIgnorer(xLabelFormat);
|
|
8873
|
+
yLabelFormat = minusIgnorer(yLabelFormat);
|
|
8874
|
+
this.init({
|
|
8875
|
+
range: realRange,
|
|
8876
|
+
scale: scale,
|
|
8877
|
+
isMobile: options.isMobile
|
|
8878
|
+
});
|
|
8879
|
+
|
|
8880
|
+
// draw grid
|
|
8881
|
+
if (grid) {
|
|
8882
|
+
this.grid(gridRange[0], gridRange[1], {
|
|
8883
|
+
stroke: options.isMobile ? KhanColors.GRAY_C : "#000000",
|
|
8884
|
+
opacity: options.isMobile ? 1 : gridOpacity,
|
|
8885
|
+
step: gridStep,
|
|
8886
|
+
strokeWidth: options.isMobile ? 1 : 2
|
|
8887
|
+
});
|
|
8826
8888
|
}
|
|
8827
|
-
|
|
8828
|
-
|
|
8829
|
-
|
|
8830
|
-
|
|
8831
|
-
|
|
8832
|
-
|
|
8833
|
-
|
|
8834
|
-
|
|
8835
|
-
|
|
8836
|
-
|
|
8837
|
-
|
|
8838
|
-
|
|
8889
|
+
|
|
8890
|
+
// draw axes
|
|
8891
|
+
if (axes) {
|
|
8892
|
+
// this is a slight hack until <-> arrowheads work
|
|
8893
|
+
if (axisArrows === "<->" || axisArrows === true) {
|
|
8894
|
+
const thisGraphie = this;
|
|
8895
|
+
this.style({
|
|
8896
|
+
stroke: options.isMobile ? KhanColors.GRAY_G : "#000000",
|
|
8897
|
+
opacity: options.isMobile ? 1 : axisOpacity,
|
|
8898
|
+
strokeWidth: options.isMobile ? 1 : 2,
|
|
8899
|
+
arrows: "->"
|
|
8900
|
+
}, function () {
|
|
8901
|
+
if (range[1][0] < 0 && range[1][1] > 0) {
|
|
8902
|
+
thisGraphie.path([axisCenter, [gridRange[0][0], axisCenter[1]]]);
|
|
8903
|
+
thisGraphie.path([axisCenter, [gridRange[0][1], axisCenter[1]]]);
|
|
8904
|
+
}
|
|
8905
|
+
if (range[0][0] < 0 && range[0][1] > 0) {
|
|
8906
|
+
thisGraphie.path([axisCenter, [axisCenter[0], gridRange[1][0]]]);
|
|
8907
|
+
thisGraphie.path([axisCenter, [axisCenter[0], gridRange[1][1]]]);
|
|
8908
|
+
}
|
|
8909
|
+
});
|
|
8910
|
+
|
|
8911
|
+
// also, we don't support "<-" arrows yet, but why you
|
|
8912
|
+
// would want that on your graph is beyond me.
|
|
8913
|
+
} else if (axisArrows === "->" || axisArrows === "") {
|
|
8914
|
+
const thisGraphie = this;
|
|
8915
|
+
this.style({
|
|
8916
|
+
stroke: "#000000",
|
|
8917
|
+
opacity: axisOpacity,
|
|
8918
|
+
strokeWidth: 2,
|
|
8919
|
+
arrows: axisArrows
|
|
8920
|
+
}, function () {
|
|
8921
|
+
thisGraphie.path([[gridRange[0][0], axisCenter[1]], [gridRange[0][1], axisCenter[1]]]);
|
|
8922
|
+
thisGraphie.path([[axisCenter[0], gridRange[1][0]], [axisCenter[0], gridRange[1][1]]]);
|
|
8923
|
+
});
|
|
8924
|
+
}
|
|
8925
|
+
if (axisLabels && axisLabels.length === 2) {
|
|
8926
|
+
this.label([gridRange[0][1], axisCenter[1]], axisLabels[0], "right");
|
|
8927
|
+
this.label([axisCenter[0], gridRange[1][1]], axisLabels[1], "above");
|
|
8928
|
+
}
|
|
8839
8929
|
}
|
|
8840
|
-
|
|
8841
|
-
|
|
8842
|
-
|
|
8843
|
-
const
|
|
8844
|
-
const
|
|
8845
|
-
|
|
8846
|
-
|
|
8847
|
-
|
|
8930
|
+
|
|
8931
|
+
// draw tick marks
|
|
8932
|
+
if (ticks) {
|
|
8933
|
+
const halfWidthTicks = options.isMobile;
|
|
8934
|
+
const thisGraphie = this;
|
|
8935
|
+
this.style({
|
|
8936
|
+
stroke: options.isMobile ? KhanColors.GRAY_G : "#000000",
|
|
8937
|
+
opacity: options.isMobile ? 1 : tickOpacity,
|
|
8938
|
+
strokeWidth: 1
|
|
8939
|
+
}, function () {
|
|
8940
|
+
// horizontal axis
|
|
8941
|
+
let step = gridStep[0] * tickStep[0];
|
|
8942
|
+
let len = tickLen[0] / scale[1];
|
|
8943
|
+
let start = gridRange[0][0];
|
|
8944
|
+
let stop = gridRange[0][1];
|
|
8945
|
+
if (range[1][0] < 0 && range[1][1] > 0) {
|
|
8946
|
+
for (let x = step + axisCenter[0]; x <= stop; x += step) {
|
|
8947
|
+
if (x < stop || !axisArrows) {
|
|
8948
|
+
thisGraphie.line([x, -len + axisCenter[1]], [x, halfWidthTicks ? 0 : len + axisCenter[1]]);
|
|
8949
|
+
}
|
|
8950
|
+
}
|
|
8951
|
+
for (let x = -step + axisCenter[0]; x >= start; x -= step) {
|
|
8952
|
+
if (x > start || !axisArrows) {
|
|
8953
|
+
thisGraphie.line([x, -len + axisCenter[1]], [x, halfWidthTicks ? 0 : len + axisCenter[1]]);
|
|
8954
|
+
}
|
|
8955
|
+
}
|
|
8956
|
+
}
|
|
8957
|
+
|
|
8958
|
+
// vertical axis
|
|
8959
|
+
step = gridStep[1] * tickStep[1];
|
|
8960
|
+
len = tickLen[1] / scale[0];
|
|
8961
|
+
start = gridRange[1][0];
|
|
8962
|
+
stop = gridRange[1][1];
|
|
8963
|
+
if (range[0][0] < 0 && range[0][1] > 0) {
|
|
8964
|
+
for (let y = step + axisCenter[1]; y <= stop; y += step) {
|
|
8965
|
+
if (y < stop || !axisArrows) {
|
|
8966
|
+
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
8967
|
+
this.line([-len + axisCenter[0], y], [halfWidthTicks ? 0 : len + axisCenter[0], y]);
|
|
8968
|
+
}
|
|
8969
|
+
}
|
|
8970
|
+
for (let y = -step + axisCenter[1]; y >= start; y -= step) {
|
|
8971
|
+
if (y > start || !axisArrows) {
|
|
8972
|
+
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
8973
|
+
this.line([-len + axisCenter[0], y], [halfWidthTicks ? 0 : len + axisCenter[0], y]);
|
|
8974
|
+
}
|
|
8975
|
+
}
|
|
8976
|
+
}
|
|
8848
8977
|
});
|
|
8849
|
-
}
|
|
8850
|
-
|
|
8851
|
-
|
|
8852
|
-
|
|
8853
|
-
|
|
8978
|
+
}
|
|
8979
|
+
|
|
8980
|
+
// draw axis labels
|
|
8981
|
+
if (labels) {
|
|
8982
|
+
const thisGraphie = this;
|
|
8983
|
+
this.style({
|
|
8984
|
+
stroke: options.isMobile ? KhanColors.GRAY_G : "#000000",
|
|
8985
|
+
opacity: options.isMobile ? 1 : labelOpacity
|
|
8986
|
+
}, function () {
|
|
8987
|
+
// horizontal axis
|
|
8988
|
+
let step = gridStep[0] * tickStep[0] * labelStep[0];
|
|
8989
|
+
let start = gridRange[0][0];
|
|
8990
|
+
let stop = gridRange[0][1];
|
|
8991
|
+
const xAxisPosition = axisCenter[0] < 0 ? "above" : "below";
|
|
8992
|
+
const yAxisPosition = axisCenter[0] < 0 ? "right" : "left";
|
|
8993
|
+
const xShowZero = axisCenter[0] === 0 && axisCenter[1] !== 0;
|
|
8994
|
+
const yShowZero = axisCenter[0] !== 0 && axisCenter[1] === 0;
|
|
8995
|
+
const axisOffCenter = axisCenter[0] !== 0 || axisCenter[1] !== 0;
|
|
8996
|
+
const showUnityX = unityLabels[0] || axisOffCenter;
|
|
8997
|
+
const showUnityY = unityLabels[1] || axisOffCenter;
|
|
8998
|
+
|
|
8999
|
+
// positive x-axis
|
|
9000
|
+
for (let x = (xShowZero ? 0 : step) + axisCenter[0]; x <= stop; x += step) {
|
|
9001
|
+
if (x < stop || !axisArrows) {
|
|
9002
|
+
thisGraphie.label([x, axisCenter[1]], xLabelFormat(x), xAxisPosition);
|
|
9003
|
+
}
|
|
9004
|
+
}
|
|
9005
|
+
|
|
9006
|
+
// negative x-axis
|
|
9007
|
+
for (let x = -step * (showUnityX ? 1 : 2) + axisCenter[0]; x >= start; x -= step) {
|
|
9008
|
+
if (x > start || !axisArrows) {
|
|
9009
|
+
thisGraphie.label([x, axisCenter[1]], xLabelFormat(x), xAxisPosition);
|
|
9010
|
+
}
|
|
9011
|
+
}
|
|
9012
|
+
step = gridStep[1] * tickStep[1] * labelStep[1];
|
|
9013
|
+
start = gridRange[1][0];
|
|
9014
|
+
stop = gridRange[1][1];
|
|
9015
|
+
|
|
9016
|
+
// positive y-axis
|
|
9017
|
+
for (let y = (yShowZero ? 0 : step) + axisCenter[1]; y <= stop; y += step) {
|
|
9018
|
+
if (y < stop || !axisArrows) {
|
|
9019
|
+
thisGraphie.label([axisCenter[0], y], yLabelFormat(y), yAxisPosition);
|
|
9020
|
+
}
|
|
9021
|
+
}
|
|
9022
|
+
|
|
9023
|
+
// negative y-axis
|
|
9024
|
+
for (let y = -step * (showUnityY ? 1 : 2) + axisCenter[1]; y >= start; y -= step) {
|
|
9025
|
+
if (y > start || !axisArrows) {
|
|
9026
|
+
thisGraphie.label([axisCenter[0], y], yLabelFormat(y), yAxisPosition);
|
|
9027
|
+
}
|
|
9028
|
+
}
|
|
8854
9029
|
});
|
|
8855
9030
|
}
|
|
8856
|
-
}
|
|
8857
|
-
|
|
8858
|
-
|
|
9031
|
+
}
|
|
9032
|
+
drawingTransform() {
|
|
9033
|
+
if (this.#drawingTransform == null) {
|
|
9034
|
+
throw new Error("Can't get drawingTransform of an uninitialized Graphie");
|
|
9035
|
+
}
|
|
9036
|
+
return this.#drawingTransform;
|
|
9037
|
+
}
|
|
9038
|
+
bounds() {
|
|
9039
|
+
if (this.#bounds == null) {
|
|
9040
|
+
throw new Error("Can't get bounds of an uninitialized Graphie");
|
|
9041
|
+
}
|
|
9042
|
+
return this.#bounds;
|
|
9043
|
+
}
|
|
9044
|
+
style(attrs, fn) {
|
|
9045
|
+
const processed = this.processAttributes(attrs);
|
|
9046
|
+
if (typeof fn === "function") {
|
|
9047
|
+
const oldStyle = this.currentStyle;
|
|
9048
|
+
this.currentStyle = $__default["default"].extend({}, this.currentStyle, processed);
|
|
9049
|
+
const result = fn.call(this);
|
|
9050
|
+
this.currentStyle = oldStyle;
|
|
9051
|
+
return result;
|
|
9052
|
+
}
|
|
9053
|
+
$__default["default"].extend(this.currentStyle, processed);
|
|
9054
|
+
}
|
|
9055
|
+
label(point, text, direction, latex) {}
|
|
9056
|
+
grid(xr, yr, styleAttributes) {}
|
|
9057
|
+
|
|
9058
|
+
// path is a stub that gets overwritten with a function from drawingTools
|
|
9059
|
+
// in createGraphie
|
|
9060
|
+
path(points) {}
|
|
9061
|
+
|
|
9062
|
+
// line is a stub that gets overwritten with a function from drawingTools
|
|
9063
|
+
// in createGraphie
|
|
9064
|
+
line(start, end) {}
|
|
9065
|
+
svgPath = (points, alreadyScaled) => {
|
|
9066
|
+
return $__default["default"].map(points, (point, i) => {
|
|
8859
9067
|
if (point === true) {
|
|
8860
9068
|
return "z";
|
|
8861
9069
|
}
|
|
8862
|
-
const scaled = alreadyScaled ? point : scalePoint(point);
|
|
9070
|
+
const scaled = alreadyScaled ? point : this.scalePoint(point);
|
|
8863
9071
|
return (i === 0 ? "M" : "L") + KhanMath.bound(scaled[0]) + " " + KhanMath.bound(scaled[1]);
|
|
8864
9072
|
}).join("");
|
|
8865
9073
|
};
|
|
8866
|
-
|
|
9074
|
+
svgParabolaPath = (a, b, c) => {
|
|
8867
9075
|
const computeParabola = function (x) {
|
|
8868
9076
|
return (a * x + b) * x + c;
|
|
8869
9077
|
};
|
|
8870
9078
|
|
|
8871
9079
|
// If points are collinear, plot a line instead
|
|
8872
9080
|
if (a === 0) {
|
|
8873
|
-
const points =
|
|
8874
|
-
return [x, computeParabola(x)];
|
|
8875
|
-
});
|
|
9081
|
+
const points = [[this.bounds().xMin, computeParabola(this.bounds().xMin)], [this.bounds().xMax, computeParabola(this.bounds().xMax)]];
|
|
8876
9082
|
// @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
|
|
8877
|
-
return svgPath(points);
|
|
9083
|
+
return this.svgPath(points);
|
|
8878
9084
|
}
|
|
8879
9085
|
|
|
8880
9086
|
// Calculate x coordinates of points on parabola
|
|
8881
9087
|
const xVertex = -b / (2 * a);
|
|
8882
|
-
const distToEdge = Math.max(Math.abs(xVertex -
|
|
9088
|
+
const distToEdge = Math.max(Math.abs(xVertex - this.bounds().xMin), Math.abs(xVertex - this.bounds().xMax));
|
|
8883
9089
|
|
|
8884
9090
|
// To guarantee that drawn parabola to spans the viewport, use a point
|
|
8885
9091
|
// on the edge of the graph furtherest from the vertex
|
|
@@ -8899,11 +9105,11 @@ GraphUtils.createGraphie = function (el) {
|
|
|
8899
9105
|
|
|
8900
9106
|
// Scale and bound
|
|
8901
9107
|
// @ts-expect-error - TS2345 - Argument of type '(point: number | Coord) => Coord' is not assignable to parameter of type 'Iteratee<any[][], any, any[]>'.
|
|
8902
|
-
const points = ___default["default"].map([left, control, right], scalePoint);
|
|
9108
|
+
const points = ___default["default"].map([left, control, right], this.scalePoint);
|
|
8903
9109
|
const values = ___default["default"].map(___default["default"].flatten(points), KhanMath.bound);
|
|
8904
9110
|
return "M" + values[0] + "," + values[1] + " Q" + values[2] + "," + values[3] + " " + values[4] + "," + values[5];
|
|
8905
9111
|
};
|
|
8906
|
-
|
|
9112
|
+
svgSinusoidPath = (a, b, c, d) => {
|
|
8907
9113
|
// Plot a sinusoid of the form: f(x) = a * sin(b * x - c) + d
|
|
8908
9114
|
const quarterPeriod = Math.abs(Math.PI / (2 * b));
|
|
8909
9115
|
const computeSine = function (x) {
|
|
@@ -8912,7 +9118,7 @@ GraphUtils.createGraphie = function (el) {
|
|
|
8912
9118
|
const computeDerivative = function (x) {
|
|
8913
9119
|
return a * b * Math.cos(c - b * x);
|
|
8914
9120
|
};
|
|
8915
|
-
const coordsForOffset =
|
|
9121
|
+
const coordsForOffset = (initial, i) => {
|
|
8916
9122
|
// Return the cubic coordinates (including the two anchor and two
|
|
8917
9123
|
// control points) for the ith portion of the sinusoid.
|
|
8918
9124
|
const x0 = initial + quarterPeriod * i;
|
|
@@ -8925,16 +9131,16 @@ GraphUtils.createGraphie = function (el) {
|
|
|
8925
9131
|
|
|
8926
9132
|
// Zip and scale
|
|
8927
9133
|
// @ts-expect-error - TS2345 - Argument of type '(point: number | Coord) => Coord' is not assignable to parameter of type 'Iteratee<any[][], any, any[]>'.
|
|
8928
|
-
return ___default["default"].map(___default["default"].zip(xCoords, yCoords), scalePoint);
|
|
9134
|
+
return ___default["default"].map(___default["default"].zip(xCoords, yCoords), this.scalePoint);
|
|
8929
9135
|
};
|
|
8930
9136
|
|
|
8931
9137
|
// How many quarter-periods do we need to span the graph?
|
|
8932
|
-
const extent =
|
|
9138
|
+
const extent = this.bounds().width();
|
|
8933
9139
|
const numQuarterPeriods = Math.ceil(extent / quarterPeriod) + 1;
|
|
8934
9140
|
|
|
8935
|
-
// Find starting coordinate: first anchor point curve left of
|
|
9141
|
+
// Find starting coordinate: first anchor point curve left of bounds.xMin
|
|
8936
9142
|
let initial = c / b;
|
|
8937
|
-
const distToEdge = initial -
|
|
9143
|
+
const distToEdge = initial - this.bounds().xMin;
|
|
8938
9144
|
initial -= quarterPeriod * Math.ceil(distToEdge / quarterPeriod);
|
|
8939
9145
|
|
|
8940
9146
|
// First portion of path is special-case, requiring move-to ('M')
|
|
@@ -8946,28 +9152,30 @@ GraphUtils.createGraphie = function (el) {
|
|
|
8946
9152
|
}
|
|
8947
9153
|
return path;
|
|
8948
9154
|
};
|
|
8949
|
-
|
|
8950
|
-
|
|
8951
|
-
|
|
8952
|
-
|
|
9155
|
+
scalePoint = point => {
|
|
9156
|
+
return this.drawingTransform().scalePoint(point);
|
|
9157
|
+
};
|
|
9158
|
+
scaleVector = point => {
|
|
9159
|
+
return this.drawingTransform().scaleVector(point);
|
|
9160
|
+
};
|
|
9161
|
+
unscalePoint = point => {
|
|
9162
|
+
return this.drawingTransform().unscalePoint(point);
|
|
9163
|
+
};
|
|
9164
|
+
unscaleVector = point => {
|
|
9165
|
+
return this.drawingTransform().unscaleVector(point);
|
|
9166
|
+
};
|
|
9167
|
+
processAttributes(attrs) {
|
|
8953
9168
|
const transformers = {
|
|
8954
|
-
scale:
|
|
8955
|
-
|
|
8956
|
-
scale = [scale, scale];
|
|
8957
|
-
}
|
|
8958
|
-
xScale = scale[0];
|
|
8959
|
-
yScale = scale[1];
|
|
8960
|
-
|
|
8961
|
-
// Update the canvas size
|
|
8962
|
-
raphael$1.setSize((xRange[1] - xRange[0]) * xScale, (yRange[1] - yRange[0]) * yScale);
|
|
9169
|
+
scale: scale => {
|
|
9170
|
+
this.drawingTransform().setScale(scale);
|
|
8963
9171
|
},
|
|
8964
|
-
clipRect:
|
|
9172
|
+
clipRect: pair => {
|
|
8965
9173
|
const point = pair[0];
|
|
8966
9174
|
const size = pair[1];
|
|
8967
9175
|
point[1] += size[1]; // because our coordinates are flipped
|
|
8968
9176
|
|
|
8969
9177
|
return {
|
|
8970
|
-
"clip-rect": scalePoint(point).concat(scaleVector(size)).join(" ")
|
|
9178
|
+
"clip-rect": this.scalePoint(point).concat(this.scaleVector(size)).join(" ")
|
|
8971
9179
|
};
|
|
8972
9180
|
},
|
|
8973
9181
|
strokeWidth: function (val) {
|
|
@@ -8975,18 +9183,18 @@ GraphUtils.createGraphie = function (el) {
|
|
|
8975
9183
|
"stroke-width": parseFloat(val)
|
|
8976
9184
|
};
|
|
8977
9185
|
},
|
|
8978
|
-
rx:
|
|
9186
|
+
rx: val => {
|
|
8979
9187
|
return {
|
|
8980
|
-
rx: scaleVector([val, 0])[0]
|
|
9188
|
+
rx: this.scaleVector([val, 0])[0]
|
|
8981
9189
|
};
|
|
8982
9190
|
},
|
|
8983
|
-
ry:
|
|
9191
|
+
ry: val => {
|
|
8984
9192
|
return {
|
|
8985
|
-
ry: scaleVector([0, val])[1]
|
|
9193
|
+
ry: this.scaleVector([0, val])[1]
|
|
8986
9194
|
};
|
|
8987
9195
|
},
|
|
8988
|
-
r:
|
|
8989
|
-
const scaled = scaleVector([val, val]);
|
|
9196
|
+
r: val => {
|
|
9197
|
+
const scaled = this.scaleVector([val, val]);
|
|
8990
9198
|
return {
|
|
8991
9199
|
rx: scaled[0],
|
|
8992
9200
|
ry: scaled[1]
|
|
@@ -9006,7 +9214,66 @@ GraphUtils.createGraphie = function (el) {
|
|
|
9006
9214
|
}
|
|
9007
9215
|
});
|
|
9008
9216
|
return processed;
|
|
9217
|
+
}
|
|
9218
|
+
}
|
|
9219
|
+
GraphUtils.Graphie = Graphie$1;
|
|
9220
|
+
const labelDirections = {
|
|
9221
|
+
center: [-0.5, -0.5],
|
|
9222
|
+
above: [-0.5, -1.0],
|
|
9223
|
+
"above right": [0.0, -1.0],
|
|
9224
|
+
right: [0.0, -0.5],
|
|
9225
|
+
"below right": [0.0, 0.0],
|
|
9226
|
+
below: [-0.5, 0.0],
|
|
9227
|
+
"below left": [-1.0, 0.0],
|
|
9228
|
+
left: [-1.0, -0.5],
|
|
9229
|
+
"above left": [-1.0, -1.0]
|
|
9230
|
+
};
|
|
9231
|
+
|
|
9232
|
+
/**
|
|
9233
|
+
* Safari applies some SVG-specific styles to things that are not SVGs, so we
|
|
9234
|
+
* need to exclude those styles from things that are not SVGs.
|
|
9235
|
+
*
|
|
9236
|
+
* To see this behavior in action, open https://codepen.io/anon/pen/zENEoa in
|
|
9237
|
+
* Safari.
|
|
9238
|
+
*
|
|
9239
|
+
* Usage `$.extend({}, someStyles, SVG_SPECIFIC_STYLE_MASK)`
|
|
9240
|
+
*/
|
|
9241
|
+
const SVG_SPECIFIC_STYLE_MASK = {
|
|
9242
|
+
"stroke-width": null
|
|
9243
|
+
};
|
|
9244
|
+
GraphUtils.createGraphie = function (el) {
|
|
9245
|
+
const thisGraphie = new Graphie$1(el);
|
|
9246
|
+
const setLabelMargins = function (span, size) {
|
|
9247
|
+
const $span = $__default["default"](span);
|
|
9248
|
+
const direction = $span.data("labelDirection");
|
|
9249
|
+
let [width, height] = size;
|
|
9250
|
+
// This can happen when a span
|
|
9251
|
+
// is invisible but we still want to update the CSS. At worst, we will
|
|
9252
|
+
// be off by a few pixels instead of in a different position entirely.
|
|
9253
|
+
if (width === 0 && height === 0) {
|
|
9254
|
+
[width, height] = [1, 1];
|
|
9255
|
+
Log.log("Label size was 0x0 in graphie.js; using 1x1 instead");
|
|
9256
|
+
}
|
|
9257
|
+
$span.css("visibility", "");
|
|
9258
|
+
if (typeof direction === "number") {
|
|
9259
|
+
const x = Math.cos(direction);
|
|
9260
|
+
const y = Math.sin(direction);
|
|
9261
|
+
const scale = Math.min(width / 2 / Math.abs(x), height / 2 / Math.abs(y));
|
|
9262
|
+
$span.css({
|
|
9263
|
+
marginLeft: -width / 2 + x * scale,
|
|
9264
|
+
marginTop: -height / 2 - y * scale
|
|
9265
|
+
});
|
|
9266
|
+
} else {
|
|
9267
|
+
const multipliers = labelDirections[direction || "center"];
|
|
9268
|
+
$span.css({
|
|
9269
|
+
marginLeft: Math.round(width * multipliers[0]),
|
|
9270
|
+
marginTop: Math.round(height * multipliers[1])
|
|
9271
|
+
});
|
|
9272
|
+
}
|
|
9009
9273
|
};
|
|
9274
|
+
|
|
9275
|
+
// `svgPath` is independent of graphie range, so we export it independently
|
|
9276
|
+
GraphUtils.svgPath = thisGraphie.svgPath;
|
|
9010
9277
|
const addArrowheads = function arrows(path) {
|
|
9011
9278
|
const type = path.constructor.prototype;
|
|
9012
9279
|
if (type === raphael.exports.el) {
|
|
@@ -9014,21 +9281,21 @@ GraphUtils.createGraphie = function (el) {
|
|
|
9014
9281
|
const w = path.attr("stroke-width");
|
|
9015
9282
|
const s = 0.6 + 0.4 * w;
|
|
9016
9283
|
const l = path.getTotalLength();
|
|
9017
|
-
const set = raphael
|
|
9018
|
-
const head = raphael
|
|
9284
|
+
const set = thisGraphie.raphael.set();
|
|
9285
|
+
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");
|
|
9019
9286
|
const end = path.getPointAtLength(l - 0.4);
|
|
9020
9287
|
const almostTheEnd = path.getPointAtLength(l - 0.75 * s);
|
|
9021
9288
|
const angle = Math.atan2(end.y - almostTheEnd.y, end.x - almostTheEnd.x) * 180 / Math.PI;
|
|
9022
9289
|
const attrs = path.attr();
|
|
9023
9290
|
delete attrs.path;
|
|
9024
9291
|
let subpath = path.getSubpath(0, l - 0.75 * s);
|
|
9025
|
-
subpath = raphael
|
|
9292
|
+
subpath = thisGraphie.raphael.path(subpath).attr(attrs);
|
|
9026
9293
|
subpath.arrowheadsDrawn = true;
|
|
9027
9294
|
path.remove();
|
|
9028
9295
|
|
|
9029
9296
|
// For some unknown reason 0 doesn't work for the rotation
|
|
9030
9297
|
// origin so we use a tiny number.
|
|
9031
|
-
head.rotate(angle,
|
|
9298
|
+
head.rotate(angle, thisGraphie.isMobile ? 1e-5 : 0.75, 0).scale(s, s, 0.75, 0).translate(almostTheEnd.x, almostTheEnd.y).attr(attrs).attr({
|
|
9032
9299
|
"stroke-linejoin": "round",
|
|
9033
9300
|
"stroke-linecap": "round"
|
|
9034
9301
|
});
|
|
@@ -9044,519 +9311,415 @@ GraphUtils.createGraphie = function (el) {
|
|
|
9044
9311
|
}
|
|
9045
9312
|
return path;
|
|
9046
9313
|
};
|
|
9047
|
-
|
|
9048
|
-
|
|
9049
|
-
|
|
9050
|
-
},
|
|
9051
|
-
// (x, y) is coordinate of bottom left corner
|
|
9052
|
-
rect: function (x, y, width, height) {
|
|
9053
|
-
// Raphael needs (x, y) to be coordinate of upper left corner
|
|
9054
|
-
const corner = scalePoint([x, y + height]);
|
|
9055
|
-
const dims = scaleVector([width, height]);
|
|
9056
|
-
const elem = raphael$1.rect(...corner.concat(dims));
|
|
9057
|
-
if (graphie.isMobile) {
|
|
9058
|
-
elem.node.style.shapeRendering = "crispEdges";
|
|
9059
|
-
}
|
|
9060
|
-
return elem;
|
|
9061
|
-
},
|
|
9062
|
-
ellipse: function (center, radii) {
|
|
9063
|
-
return raphael$1.ellipse(...scalePoint(center).concat(scaleVector(radii)));
|
|
9064
|
-
},
|
|
9065
|
-
fixedEllipse: function (center,
|
|
9066
|
-
// Different type than Coord, this is radiusX, radiusY
|
|
9067
|
-
radii, maxScale, padding) {
|
|
9068
|
-
// Scale point and radius
|
|
9069
|
-
const scaledPoint = scalePoint(center);
|
|
9070
|
-
const scaledRadii = scaleVector(radii);
|
|
9071
|
-
const width = 2 * scaledRadii[0] * maxScale + padding;
|
|
9072
|
-
const height = 2 * scaledRadii[1] * maxScale + padding;
|
|
9073
|
-
|
|
9074
|
-
// Calculate absolute left, top
|
|
9075
|
-
const left = scaledPoint[0] - width / 2;
|
|
9076
|
-
const top = scaledPoint[1] - height / 2;
|
|
9077
|
-
|
|
9078
|
-
// Wrap in <div>
|
|
9079
|
-
const wrapper = document.createElement("div");
|
|
9080
|
-
$__default["default"](wrapper).css({
|
|
9081
|
-
position: "absolute",
|
|
9082
|
-
width: width + "px",
|
|
9083
|
-
height: height + "px",
|
|
9084
|
-
left: left + "px",
|
|
9085
|
-
top: top + "px"
|
|
9086
|
-
});
|
|
9087
|
-
// wrapper.setAttribute("data-graphie-type", "ellipse");
|
|
9314
|
+
function circle(center, radius) {
|
|
9315
|
+
return thisGraphie.raphael.ellipse(...thisGraphie.scalePoint(center).concat(thisGraphie.scaleVector([radius, radius])));
|
|
9316
|
+
}
|
|
9088
9317
|
|
|
9089
|
-
|
|
9090
|
-
|
|
9091
|
-
|
|
9092
|
-
|
|
9093
|
-
|
|
9094
|
-
|
|
9095
|
-
|
|
9096
|
-
|
|
9097
|
-
|
|
9098
|
-
|
|
9099
|
-
|
|
9100
|
-
|
|
9101
|
-
|
|
9102
|
-
|
|
9103
|
-
|
|
9104
|
-
|
|
9105
|
-
|
|
9106
|
-
|
|
9107
|
-
|
|
9108
|
-
|
|
9109
|
-
|
|
9110
|
-
|
|
9111
|
-
|
|
9112
|
-
|
|
9113
|
-
|
|
9114
|
-
|
|
9115
|
-
|
|
9116
|
-
|
|
9117
|
-
|
|
9118
|
-
|
|
9119
|
-
|
|
9120
|
-
|
|
9121
|
-
|
|
9122
|
-
|
|
9123
|
-
|
|
9124
|
-
|
|
9125
|
-
|
|
9126
|
-
const pathBottom = ___default["default"].max(___default["default"].pluck(points, 1));
|
|
9127
|
-
|
|
9128
|
-
// Apply padding to line
|
|
9129
|
-
const padding = [4, 4];
|
|
9130
|
-
|
|
9131
|
-
// Calculate and apply additional offset
|
|
9132
|
-
const extraOffset = [pathLeft, pathTop];
|
|
9133
|
-
|
|
9134
|
-
// Apply padding and offset to points
|
|
9135
|
-
points = ___default["default"].map(points, function (point) {
|
|
9136
|
-
return kmath.vector.add(kmath.vector.subtract(point, extraOffset), kmath.vector.scale(padding, 0.5));
|
|
9137
|
-
});
|
|
9138
|
-
|
|
9139
|
-
// Calculate <div> dimensions
|
|
9140
|
-
const width = pathRight - pathLeft + padding[0];
|
|
9141
|
-
const height = pathBottom - pathTop + padding[1];
|
|
9142
|
-
const left = extraOffset[0] - padding[0] / 2;
|
|
9143
|
-
const top = extraOffset[1] - padding[1] / 2;
|
|
9144
|
-
|
|
9145
|
-
// Create <div>
|
|
9146
|
-
const wrapper = document.createElement("div");
|
|
9147
|
-
$__default["default"](wrapper).css({
|
|
9148
|
-
position: "absolute",
|
|
9149
|
-
width: width + "px",
|
|
9150
|
-
height: height + "px",
|
|
9151
|
-
left: left + "px",
|
|
9152
|
-
top: top + "px",
|
|
9153
|
-
// If user specified a center, set it
|
|
9154
|
-
// NOTE(kevinb): jQuery doesn't like that `transformOrigin `could be `null`
|
|
9155
|
-
// so we cast to `any` here.
|
|
9156
|
-
transformOrigin: center ? width / 2 + center[0] + "px " + (height / 2 + center[1]) + "px" : null
|
|
9157
|
-
});
|
|
9318
|
+
// (x, y) is coordinate of bottom left corner
|
|
9319
|
+
function rect(x, y, width, height) {
|
|
9320
|
+
// Raphael needs (x, y) to be coordinate of upper left corner
|
|
9321
|
+
const corner = thisGraphie.scalePoint([x, y + height]);
|
|
9322
|
+
const dims = thisGraphie.scaleVector([width, height]);
|
|
9323
|
+
const elem = thisGraphie.raphael.rect(...corner.concat(dims));
|
|
9324
|
+
if (thisGraphie.isMobile) {
|
|
9325
|
+
elem.node.style.shapeRendering = "crispEdges";
|
|
9326
|
+
}
|
|
9327
|
+
return elem;
|
|
9328
|
+
}
|
|
9329
|
+
function ellipse(center, radii) {
|
|
9330
|
+
return thisGraphie.raphael.ellipse(...thisGraphie.scalePoint(center).concat(thisGraphie.scaleVector(radii)));
|
|
9331
|
+
}
|
|
9332
|
+
function fixedEllipse(center,
|
|
9333
|
+
// Different type than Coord, this is radiusX, radiusY
|
|
9334
|
+
radii, maxScale, padding) {
|
|
9335
|
+
// Scale point and radius
|
|
9336
|
+
const scaledPoint = thisGraphie.scalePoint(center);
|
|
9337
|
+
const scaledRadii = thisGraphie.scaleVector(radii);
|
|
9338
|
+
const width = 2 * scaledRadii[0] * maxScale + padding;
|
|
9339
|
+
const height = 2 * scaledRadii[1] * maxScale + padding;
|
|
9340
|
+
|
|
9341
|
+
// Calculate absolute left, top
|
|
9342
|
+
const left = scaledPoint[0] - width / 2;
|
|
9343
|
+
const top = scaledPoint[1] - height / 2;
|
|
9344
|
+
|
|
9345
|
+
// Wrap in <div>
|
|
9346
|
+
const wrapper = document.createElement("div");
|
|
9347
|
+
$__default["default"](wrapper).css({
|
|
9348
|
+
position: "absolute",
|
|
9349
|
+
width: width + "px",
|
|
9350
|
+
height: height + "px",
|
|
9351
|
+
left: left + "px",
|
|
9352
|
+
top: top + "px"
|
|
9353
|
+
});
|
|
9354
|
+
// wrapper.setAttribute("data-graphie-type", "ellipse");
|
|
9158
9355
|
|
|
9159
|
-
|
|
9160
|
-
|
|
9356
|
+
// Create Raphael canvas
|
|
9357
|
+
const localRaphael = raphael.exports(wrapper, width, height);
|
|
9358
|
+
const visibleShape = localRaphael.ellipse(width / 2, height / 2, scaledRadii[0], scaledRadii[1]);
|
|
9359
|
+
return {
|
|
9360
|
+
wrapper: wrapper,
|
|
9361
|
+
visibleShape: visibleShape
|
|
9362
|
+
};
|
|
9363
|
+
}
|
|
9364
|
+
function arc(center, radius, startAngle, endAngle, sector) {
|
|
9365
|
+
startAngle = (startAngle % 360 + 360) % 360;
|
|
9366
|
+
endAngle = (endAngle % 360 + 360) % 360;
|
|
9367
|
+
const cent = thisGraphie.scalePoint(center);
|
|
9368
|
+
const radii = thisGraphie.scaleVector(radius);
|
|
9369
|
+
const startVector = polar(radius, startAngle);
|
|
9370
|
+
const endVector = polar(radius, endAngle);
|
|
9371
|
+
const startPoint = thisGraphie.scalePoint([center[0] + startVector[0], center[1] + startVector[1]]);
|
|
9372
|
+
const endPoint = thisGraphie.scalePoint([center[0] + endVector[0], center[1] + endVector[1]]);
|
|
9373
|
+
const largeAngle = ((endAngle - startAngle) % 360 + 360) % 360 > 180;
|
|
9374
|
+
return thisGraphie.raphael.path("M" + startPoint.join(" ") + "A" + radii.join(" ") + " 0 " + (
|
|
9375
|
+
// ellipse rotation
|
|
9376
|
+
largeAngle ? 1 : 0) + " 0 " +
|
|
9377
|
+
// sweep flag
|
|
9378
|
+
endPoint.join(" ") + (sector ? "L" + cent.join(" ") + "z" : ""));
|
|
9379
|
+
}
|
|
9380
|
+
function path(points) {
|
|
9381
|
+
// @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
|
|
9382
|
+
const p = thisGraphie.raphael.path(thisGraphie.svgPath(points));
|
|
9383
|
+
p.graphiePath = points;
|
|
9384
|
+
return p;
|
|
9385
|
+
}
|
|
9386
|
+
function fixedPath(points, center, createPath) {
|
|
9387
|
+
points = ___default["default"].map(points, thisGraphie.scalePoint);
|
|
9388
|
+
center = center ? thisGraphie.scalePoint(center) : null;
|
|
9389
|
+
createPath = createPath || thisGraphie.svgPath;
|
|
9390
|
+
const pathLeft = ___default["default"].min(___default["default"].pluck(points, 0));
|
|
9391
|
+
const pathRight = ___default["default"].max(___default["default"].pluck(points, 0));
|
|
9392
|
+
const pathTop = ___default["default"].min(___default["default"].pluck(points, 1));
|
|
9393
|
+
const pathBottom = ___default["default"].max(___default["default"].pluck(points, 1));
|
|
9394
|
+
|
|
9395
|
+
// Apply padding to line
|
|
9396
|
+
const padding = [4, 4];
|
|
9397
|
+
|
|
9398
|
+
// Calculate and apply additional offset
|
|
9399
|
+
const extraOffset = [pathLeft, pathTop];
|
|
9400
|
+
|
|
9401
|
+
// Apply padding and offset to points
|
|
9402
|
+
points = ___default["default"].map(points, function (point) {
|
|
9403
|
+
return kmath.vector.add(kmath.vector.subtract(point, extraOffset), kmath.vector.scale(padding, 0.5));
|
|
9404
|
+
});
|
|
9405
|
+
|
|
9406
|
+
// Calculate <div> dimensions
|
|
9407
|
+
const width = pathRight - pathLeft + padding[0];
|
|
9408
|
+
const height = pathBottom - pathTop + padding[1];
|
|
9409
|
+
const left = extraOffset[0] - padding[0] / 2;
|
|
9410
|
+
const top = extraOffset[1] - padding[1] / 2;
|
|
9411
|
+
|
|
9412
|
+
// Create <div>
|
|
9413
|
+
const wrapper = document.createElement("div");
|
|
9414
|
+
$__default["default"](wrapper).css({
|
|
9415
|
+
position: "absolute",
|
|
9416
|
+
width: width + "px",
|
|
9417
|
+
height: height + "px",
|
|
9418
|
+
left: left + "px",
|
|
9419
|
+
top: top + "px",
|
|
9420
|
+
// If user specified a center, set it
|
|
9421
|
+
// NOTE(kevinb): jQuery doesn't like that `transformOrigin `could be `null`
|
|
9422
|
+
// so we cast to `any` here.
|
|
9423
|
+
transformOrigin: center ? width / 2 + center[0] + "px " + (height / 2 + center[1]) + "px" : null
|
|
9424
|
+
});
|
|
9161
9425
|
|
|
9162
|
-
|
|
9163
|
-
|
|
9164
|
-
return {
|
|
9165
|
-
wrapper: wrapper,
|
|
9166
|
-
visibleShape: visibleShape
|
|
9167
|
-
};
|
|
9168
|
-
},
|
|
9169
|
-
scaledPath: function (points) {
|
|
9170
|
-
const p = raphael$1.path(svgPath(points, /* alreadyScaled */true));
|
|
9171
|
-
p.graphiePath = points;
|
|
9172
|
-
return p;
|
|
9173
|
-
},
|
|
9174
|
-
line: function (start, end) {
|
|
9175
|
-
const l = this.path([start, end]);
|
|
9176
|
-
if (graphie.isMobile) {
|
|
9177
|
-
l.node.style.shapeRendering = "crispEdges";
|
|
9178
|
-
}
|
|
9179
|
-
return l;
|
|
9180
|
-
},
|
|
9181
|
-
parabola: function (a, b, c) {
|
|
9182
|
-
// Plot a parabola of the form: f(x) = (a * x + b) * x + c
|
|
9183
|
-
return raphael$1.path(svgParabolaPath(a, b, c));
|
|
9184
|
-
},
|
|
9185
|
-
fixedLine: function (start, end, thickness) {
|
|
9186
|
-
// Apply padding to line
|
|
9187
|
-
const padding = [thickness, thickness];
|
|
9188
|
-
|
|
9189
|
-
// Scale points to get values in pixels
|
|
9190
|
-
start = scalePoint(start);
|
|
9191
|
-
end = scalePoint(end);
|
|
9192
|
-
|
|
9193
|
-
// Calculate and apply additional offset
|
|
9194
|
-
const extraOffset = [Math.min(start[0], end[0]), Math.min(start[1], end[1])];
|
|
9195
|
-
|
|
9196
|
-
// Apply padding and offset to start, end points
|
|
9197
|
-
start = kmath.vector.add(kmath.vector.subtract(start, extraOffset), kmath.vector.scale(padding, 0.5));
|
|
9198
|
-
end = kmath.vector.add(kmath.vector.subtract(end, extraOffset), kmath.vector.scale(padding, 0.5));
|
|
9199
|
-
|
|
9200
|
-
// Calculate <div> dimensions
|
|
9201
|
-
const left = extraOffset[0] - padding[0] / 2;
|
|
9202
|
-
const top = extraOffset[1] - padding[1] / 2;
|
|
9203
|
-
const width = Math.abs(start[0] - end[0]) + padding[0];
|
|
9204
|
-
const height = Math.abs(start[1] - end[1]) + padding[1];
|
|
9205
|
-
|
|
9206
|
-
// Create <div>
|
|
9207
|
-
const wrapper = document.createElement("div");
|
|
9208
|
-
$__default["default"](wrapper).css({
|
|
9209
|
-
position: "absolute",
|
|
9210
|
-
width: width + "px",
|
|
9211
|
-
height: height + "px",
|
|
9212
|
-
left: left + "px",
|
|
9213
|
-
top: top + "px",
|
|
9214
|
-
// Outsiders should feel like the line's 'origin' (i.e., for
|
|
9215
|
-
// rotation) is the starting point
|
|
9216
|
-
transformOrigin: start[0] + "px " + start[1] + "px"
|
|
9217
|
-
});
|
|
9218
|
-
|
|
9219
|
-
// Create Raphael canvas
|
|
9220
|
-
const localRaphael = raphael.exports(wrapper, width, height);
|
|
9221
|
-
|
|
9222
|
-
// Calculate path
|
|
9223
|
-
const path = "M" + start[0] + " " + start[1] + " " + "L" + end[0] + " " + end[1];
|
|
9224
|
-
const visibleShape = localRaphael.path(path);
|
|
9225
|
-
visibleShape.graphiePath = [start, end];
|
|
9226
|
-
return {
|
|
9227
|
-
wrapper: wrapper,
|
|
9228
|
-
visibleShape: visibleShape
|
|
9229
|
-
};
|
|
9230
|
-
},
|
|
9231
|
-
sinusoid: function (a, b, c, d) {
|
|
9232
|
-
// Plot a sinusoid of the form: f(x) = a * sin(b * x - c) + d
|
|
9233
|
-
return raphael$1.path(svgSinusoidPath(a, b, c, d));
|
|
9234
|
-
},
|
|
9235
|
-
grid: function (xr, yr) {
|
|
9236
|
-
const step = currentStyle.step || [1, 1];
|
|
9237
|
-
const set = raphael$1.set();
|
|
9238
|
-
let x = step[0] * Math.ceil(xr[0] / step[0]);
|
|
9239
|
-
for (; x <= xr[1]; x += step[0]) {
|
|
9240
|
-
set.push(this.line([x, yr[0]], [x, yr[1]]));
|
|
9241
|
-
}
|
|
9242
|
-
let y = step[1] * Math.ceil(yr[0] / step[1]);
|
|
9243
|
-
for (; y <= yr[1]; y += step[1]) {
|
|
9244
|
-
set.push(this.line([xr[0], y], [xr[1], y]));
|
|
9245
|
-
}
|
|
9246
|
-
return set;
|
|
9247
|
-
},
|
|
9248
|
-
label: function (point, text, direction, latex) {
|
|
9249
|
-
latex = typeof latex === "undefined" || latex;
|
|
9250
|
-
const $span = $__default["default"]("<span>").addClass("graphie-label");
|
|
9251
|
-
const pad = currentStyle["label-distance"];
|
|
9252
|
-
$span.css($__default["default"].extend({}, {
|
|
9253
|
-
position: "absolute",
|
|
9254
|
-
padding: (pad != null ? pad : 7) + "px",
|
|
9255
|
-
color: "black"
|
|
9256
|
-
})).data("labelDirection", direction).appendTo(el);
|
|
9257
|
-
|
|
9258
|
-
// @ts-expect-error - TS2339 - Property 'setPosition' does not exist on type 'JQuery<HTMLElement>'.
|
|
9259
|
-
$span.setPosition = function (point) {
|
|
9260
|
-
const scaledPoint = scalePoint(point);
|
|
9261
|
-
$span.css({
|
|
9262
|
-
left: scaledPoint[0],
|
|
9263
|
-
top: scaledPoint[1]
|
|
9264
|
-
});
|
|
9265
|
-
};
|
|
9426
|
+
// Create Raphael canvas
|
|
9427
|
+
const localRaphael = raphael.exports(wrapper, width, height);
|
|
9266
9428
|
|
|
9267
|
-
|
|
9268
|
-
|
|
9269
|
-
|
|
9429
|
+
// Calculate path
|
|
9430
|
+
const visibleShape = localRaphael.path(createPath(points));
|
|
9431
|
+
return {
|
|
9432
|
+
wrapper: wrapper,
|
|
9433
|
+
visibleShape: visibleShape
|
|
9434
|
+
};
|
|
9435
|
+
}
|
|
9436
|
+
function scaledPath(points) {
|
|
9437
|
+
const p = thisGraphie.raphael.path(thisGraphie.svgPath(points, /* alreadyScaled */true));
|
|
9438
|
+
p.graphiePath = points;
|
|
9439
|
+
return p;
|
|
9440
|
+
}
|
|
9441
|
+
function line(start, end) {
|
|
9442
|
+
const l = path([start, end]);
|
|
9443
|
+
if (thisGraphie.isMobile) {
|
|
9444
|
+
l.node.style.shapeRendering = "crispEdges";
|
|
9445
|
+
}
|
|
9446
|
+
return l;
|
|
9447
|
+
}
|
|
9448
|
+
function parabola(a, b, c) {
|
|
9449
|
+
// Plot a parabola of the form: f(x) = (a * x + b) * x + c
|
|
9450
|
+
return thisGraphie.raphael.path(thisGraphie.svgParabolaPath(a, b, c));
|
|
9451
|
+
}
|
|
9452
|
+
function fixedLine(start, end, thickness) {
|
|
9453
|
+
// Apply padding to line
|
|
9454
|
+
const padding = [thickness, thickness];
|
|
9270
9455
|
|
|
9271
|
-
|
|
9272
|
-
|
|
9273
|
-
|
|
9274
|
-
const width = span.scrollWidth;
|
|
9275
|
-
const height = span.scrollHeight;
|
|
9276
|
-
setLabelMargins(span, [width, height]);
|
|
9277
|
-
});
|
|
9278
|
-
};
|
|
9456
|
+
// Scale points to get values in pixels
|
|
9457
|
+
start = thisGraphie.scalePoint(start);
|
|
9458
|
+
end = thisGraphie.scalePoint(end);
|
|
9279
9459
|
|
|
9280
|
-
|
|
9281
|
-
|
|
9282
|
-
|
|
9460
|
+
// Calculate and apply additional offset
|
|
9461
|
+
const extraOffset = [Math.min(start[0], end[0]), Math.min(start[1], end[1])];
|
|
9462
|
+
|
|
9463
|
+
// Apply padding and offset to start, end points
|
|
9464
|
+
start = kmath.vector.add(kmath.vector.subtract(start, extraOffset), kmath.vector.scale(padding, 0.5));
|
|
9465
|
+
end = kmath.vector.add(kmath.vector.subtract(end, extraOffset), kmath.vector.scale(padding, 0.5));
|
|
9466
|
+
|
|
9467
|
+
// Calculate <div> dimensions
|
|
9468
|
+
const left = extraOffset[0] - padding[0] / 2;
|
|
9469
|
+
const top = extraOffset[1] - padding[1] / 2;
|
|
9470
|
+
const width = Math.abs(start[0] - end[0]) + padding[0];
|
|
9471
|
+
const height = Math.abs(start[1] - end[1]) + padding[1];
|
|
9472
|
+
|
|
9473
|
+
// Create <div>
|
|
9474
|
+
const wrapper = document.createElement("div");
|
|
9475
|
+
$__default["default"](wrapper).css({
|
|
9476
|
+
position: "absolute",
|
|
9477
|
+
width: width + "px",
|
|
9478
|
+
height: height + "px",
|
|
9479
|
+
left: left + "px",
|
|
9480
|
+
top: top + "px",
|
|
9481
|
+
// Outsiders should feel like the line's 'origin' (i.e., for
|
|
9482
|
+
// rotation) is the starting point
|
|
9483
|
+
transformOrigin: start[0] + "px " + start[1] + "px"
|
|
9484
|
+
});
|
|
9485
|
+
|
|
9486
|
+
// Create Raphael canvas
|
|
9487
|
+
const localRaphael = raphael.exports(wrapper, width, height);
|
|
9488
|
+
|
|
9489
|
+
// Calculate path
|
|
9490
|
+
const path = "M" + start[0] + " " + start[1] + " " + "L" + end[0] + " " + end[1];
|
|
9491
|
+
const visibleShape = localRaphael.path(path);
|
|
9492
|
+
visibleShape.graphiePath = [start, end];
|
|
9493
|
+
return {
|
|
9494
|
+
wrapper: wrapper,
|
|
9495
|
+
visibleShape: visibleShape
|
|
9496
|
+
};
|
|
9497
|
+
}
|
|
9498
|
+
function sinusoid(a, b, c, d) {
|
|
9499
|
+
// Plot a sinusoid of the form: f(x) = a * sin(b * x - c) + d
|
|
9500
|
+
return thisGraphie.raphael.path(thisGraphie.svgSinusoidPath(a, b, c, d));
|
|
9501
|
+
}
|
|
9502
|
+
function grid(xr, yr) {
|
|
9503
|
+
const step = thisGraphie.currentStyle.step || [1, 1];
|
|
9504
|
+
const set = thisGraphie.raphael.set();
|
|
9505
|
+
let x = step[0] * Math.ceil(xr[0] / step[0]);
|
|
9506
|
+
for (; x <= xr[1]; x += step[0]) {
|
|
9507
|
+
set.push(line([x, yr[0]], [x, yr[1]]));
|
|
9508
|
+
}
|
|
9509
|
+
let y = step[1] * Math.ceil(yr[0] / step[1]);
|
|
9510
|
+
for (; y <= yr[1]; y += step[1]) {
|
|
9511
|
+
set.push(line([xr[0], y], [xr[1], y]));
|
|
9512
|
+
}
|
|
9513
|
+
return set;
|
|
9514
|
+
}
|
|
9515
|
+
function label(point, text, direction, latex) {
|
|
9516
|
+
latex = typeof latex === "undefined" || latex;
|
|
9517
|
+
const $span = $__default["default"]("<span>").addClass("graphie-label");
|
|
9518
|
+
const pad = thisGraphie.currentStyle["label-distance"];
|
|
9519
|
+
$span.css($__default["default"].extend({}, {
|
|
9520
|
+
position: "absolute",
|
|
9521
|
+
padding: (pad != null ? pad : 7) + "px",
|
|
9522
|
+
color: "black"
|
|
9523
|
+
})).data("labelDirection", direction).appendTo(thisGraphie.el);
|
|
9524
|
+
|
|
9525
|
+
// @ts-expect-error - TS2339 - Property 'setPosition' does not exist on type 'JQuery<HTMLElement>'.
|
|
9526
|
+
$span.setPosition = function (point) {
|
|
9527
|
+
const scaledPoint = thisGraphie.scalePoint(point);
|
|
9528
|
+
$span.css({
|
|
9529
|
+
left: scaledPoint[0],
|
|
9530
|
+
top: scaledPoint[1]
|
|
9531
|
+
});
|
|
9532
|
+
};
|
|
9533
|
+
|
|
9534
|
+
// @ts-expect-error - TS2339 - Property 'setPosition' does not exist on type 'JQuery<HTMLElement>'.
|
|
9535
|
+
$span.setPosition(point);
|
|
9536
|
+
const span = $span[0];
|
|
9537
|
+
|
|
9538
|
+
// @ts-expect-error - TS2339 - Property 'processMath' does not exist on type 'JQuery<HTMLElement>'.
|
|
9539
|
+
$span.processMath = function (math, force) {
|
|
9540
|
+
processMath$1(span, math, force, function () {
|
|
9283
9541
|
const width = span.scrollWidth;
|
|
9284
9542
|
const height = span.scrollHeight;
|
|
9285
9543
|
setLabelMargins(span, [width, height]);
|
|
9286
|
-
};
|
|
9287
|
-
|
|
9288
|
-
// @ts-expect-error - TS2339 - Property 'processMath' does not exist on type 'JQuery<HTMLElement>'.
|
|
9289
|
-
$span.processMath(text, /* force */false);
|
|
9290
|
-
} else {
|
|
9291
|
-
// @ts-expect-error - TS2339 - Property 'processText' does not exist on type 'JQuery<HTMLElement>'.
|
|
9292
|
-
$span.processText(text);
|
|
9293
|
-
}
|
|
9294
|
-
return $span;
|
|
9295
|
-
},
|
|
9296
|
-
plotParametric: function (fn, range, shade, fn2) {
|
|
9297
|
-
// Note: fn2 should only be set if 'shade' is true, as it denotes
|
|
9298
|
-
// the function between which fn should have its area shaded.
|
|
9299
|
-
// In general, plotParametric shouldn't be used to shade the area
|
|
9300
|
-
// between two arbitrary parametrics functions over an interval,
|
|
9301
|
-
// as the method assumes that fn and fn2 are both of the form
|
|
9302
|
-
// fn(t) = (t, fn'(t)) for some initial fn'.
|
|
9303
|
-
fn2 = fn2 || function (t) {
|
|
9304
|
-
return [t, 0];
|
|
9305
|
-
};
|
|
9544
|
+
});
|
|
9545
|
+
};
|
|
9306
9546
|
|
|
9307
|
-
|
|
9308
|
-
|
|
9309
|
-
|
|
9310
|
-
|
|
9311
|
-
const
|
|
9312
|
-
|
|
9313
|
-
|
|
9314
|
-
|
|
9315
|
-
|
|
9316
|
-
|
|
9317
|
-
|
|
9318
|
-
|
|
9319
|
-
|
|
9320
|
-
|
|
9321
|
-
|
|
9322
|
-
|
|
9323
|
-
|
|
9324
|
-
|
|
9325
|
-
|
|
9326
|
-
|
|
9327
|
-
|
|
9328
|
-
|
|
9329
|
-
|
|
9330
|
-
|
|
9331
|
-
|
|
9332
|
-
|
|
9333
|
-
|
|
9334
|
-
|
|
9335
|
-
|
|
9336
|
-
|
|
9337
|
-
|
|
9338
|
-
|
|
9339
|
-
|
|
9340
|
-
|
|
9341
|
-
|
|
9342
|
-
|
|
9343
|
-
|
|
9344
|
-
|
|
9345
|
-
|
|
9346
|
-
|
|
9347
|
-
|
|
9348
|
-
|
|
9349
|
-
|
|
9350
|
-
|
|
9351
|
-
|
|
9547
|
+
// @ts-expect-error - TS2339 - Property 'processText' does not exist on type 'JQuery<HTMLElement>'.
|
|
9548
|
+
$span.processText = function (text) {
|
|
9549
|
+
$span.html(text);
|
|
9550
|
+
const width = span.scrollWidth;
|
|
9551
|
+
const height = span.scrollHeight;
|
|
9552
|
+
setLabelMargins(span, [width, height]);
|
|
9553
|
+
};
|
|
9554
|
+
if (latex) {
|
|
9555
|
+
// @ts-expect-error - TS2339 - Property 'processMath' does not exist on type 'JQuery<HTMLElement>'.
|
|
9556
|
+
$span.processMath(text, /* force */false);
|
|
9557
|
+
} else {
|
|
9558
|
+
// @ts-expect-error - TS2339 - Property 'processText' does not exist on type 'JQuery<HTMLElement>'.
|
|
9559
|
+
$span.processText(text);
|
|
9560
|
+
}
|
|
9561
|
+
return $span;
|
|
9562
|
+
}
|
|
9563
|
+
function plotParametric(fn, range, shade) {
|
|
9564
|
+
let fn2 = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : t => [t, 0];
|
|
9565
|
+
// Note: fn2 should only be set if 'shade' is true, as it denotes
|
|
9566
|
+
// the function between which fn should have its area shaded.
|
|
9567
|
+
// In general, plotParametric shouldn't be used to shade the area
|
|
9568
|
+
// between two arbitrary parametrics functions over an interval,
|
|
9569
|
+
// as the method assumes that fn and fn2 are both of the form
|
|
9570
|
+
// fn(t) = (t, fn'(t)) for some initial fn'.
|
|
9571
|
+
|
|
9572
|
+
// We truncate to 500,000, since anything bigger causes
|
|
9573
|
+
// overflow in the firefox svg renderer. This is safe
|
|
9574
|
+
// since 500,000 is outside the viewport anyway. We
|
|
9575
|
+
// write these functions the way we do to handle undefined.
|
|
9576
|
+
const clipper = xy => {
|
|
9577
|
+
if (Math.abs(xy[1]) > 500000) {
|
|
9578
|
+
return [xy[0], Math.min(Math.max(xy[1], -500000), 500000)];
|
|
9579
|
+
}
|
|
9580
|
+
return xy;
|
|
9581
|
+
};
|
|
9582
|
+
const clippedFn = x => clipper(fn(x));
|
|
9583
|
+
const clippedFn2 = x => clipper(fn2(x));
|
|
9584
|
+
if (!thisGraphie.currentStyle.strokeLinejoin) {
|
|
9585
|
+
thisGraphie.currentStyle.strokeLinejoin = "round";
|
|
9586
|
+
}
|
|
9587
|
+
if (!thisGraphie.currentStyle.strokeLinecap) {
|
|
9588
|
+
thisGraphie.currentStyle.strokeLinecap = "round";
|
|
9589
|
+
}
|
|
9590
|
+
const min = range[0];
|
|
9591
|
+
const max = range[1];
|
|
9592
|
+
let step = (max - min) / (thisGraphie.currentStyle["plot-points"] || 800);
|
|
9593
|
+
if (step === 0) {
|
|
9594
|
+
step = 1;
|
|
9595
|
+
}
|
|
9596
|
+
const paths = thisGraphie.raphael.set();
|
|
9597
|
+
let points = [];
|
|
9598
|
+
let lastDiff = GraphUtils.coordDiff(clippedFn(min), clippedFn2(min));
|
|
9599
|
+
let lastFlip = min;
|
|
9600
|
+
for (let t = min; t <= max; t += step) {
|
|
9601
|
+
const top = clippedFn(t);
|
|
9602
|
+
const bottom = clippedFn2(t);
|
|
9603
|
+
const diff = GraphUtils.coordDiff(top, bottom);
|
|
9604
|
+
|
|
9605
|
+
// Find points where it flips
|
|
9606
|
+
// Create path that sketches area between the two functions
|
|
9607
|
+
if (
|
|
9608
|
+
// if there is an asymptote here, meaning that the graph
|
|
9609
|
+
// switches signs and has a large difference
|
|
9610
|
+
diff[1] < 0 !== lastDiff[1] < 0 && Math.abs(diff[1] - lastDiff[1]) > 2 * thisGraphie.drawingTransform().pixelsPerUnitY() ||
|
|
9611
|
+
// or the function is undefined
|
|
9612
|
+
isNaN(diff[1])) {
|
|
9613
|
+
// split the path at this point, and draw it
|
|
9614
|
+
if (shade) {
|
|
9615
|
+
// @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
|
|
9616
|
+
points.push(top);
|
|
9352
9617
|
|
|
9353
|
-
|
|
9354
|
-
|
|
9355
|
-
// @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
|
|
9356
|
-
points.push(clippedFn2(u));
|
|
9357
|
-
}
|
|
9358
|
-
lastFlip = t;
|
|
9359
|
-
}
|
|
9360
|
-
paths.push(this.path(points));
|
|
9361
|
-
// restart the path, excluding this point
|
|
9362
|
-
points = [];
|
|
9363
|
-
if (shade) {
|
|
9618
|
+
// backtrack to draw paired function
|
|
9619
|
+
for (let u = t - step; u >= lastFlip; u -= step) {
|
|
9364
9620
|
// @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
|
|
9365
|
-
points.push(
|
|
9621
|
+
points.push(clippedFn2(u));
|
|
9366
9622
|
}
|
|
9367
|
-
|
|
9368
|
-
// otherwise, just add the point to the path
|
|
9369
|
-
// @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
|
|
9370
|
-
points.push(top);
|
|
9623
|
+
lastFlip = t;
|
|
9371
9624
|
}
|
|
9372
|
-
|
|
9373
|
-
|
|
9374
|
-
|
|
9375
|
-
|
|
9376
|
-
for (let u = max - step; u >= lastFlip; u -= step) {
|
|
9625
|
+
paths.push(path(points));
|
|
9626
|
+
// restart the path, excluding this point
|
|
9627
|
+
points = [];
|
|
9628
|
+
if (shade) {
|
|
9377
9629
|
// @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
|
|
9378
|
-
points.push(
|
|
9630
|
+
points.push(top);
|
|
9379
9631
|
}
|
|
9632
|
+
} else {
|
|
9633
|
+
// otherwise, just add the point to the path
|
|
9634
|
+
// @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
|
|
9635
|
+
points.push(top);
|
|
9380
9636
|
}
|
|
9381
|
-
|
|
9382
|
-
|
|
9383
|
-
|
|
9384
|
-
|
|
9385
|
-
|
|
9386
|
-
|
|
9387
|
-
|
|
9388
|
-
// There is probably a better heuristic for this
|
|
9389
|
-
if (!currentStyle["plot-points"]) {
|
|
9390
|
-
currentStyle["plot-points"] = 2 * (max - min) * xScale;
|
|
9391
|
-
}
|
|
9392
|
-
return this.plotParametric(function (th) {
|
|
9393
|
-
return polar(fn(th), th * 180 / Math.PI);
|
|
9394
|
-
}, range);
|
|
9395
|
-
},
|
|
9396
|
-
plot: function (fn, range, swapAxes, shade, fn2) {
|
|
9397
|
-
const min = range[0];
|
|
9398
|
-
const max = range[1];
|
|
9399
|
-
if (!currentStyle["plot-points"]) {
|
|
9400
|
-
currentStyle["plot-points"] = 2 * (max - min) * xScale;
|
|
9401
|
-
}
|
|
9402
|
-
if (swapAxes) {
|
|
9403
|
-
if (fn2) {
|
|
9404
|
-
// TODO(charlie): support swapped axis area shading
|
|
9405
|
-
throw new PerseusError("Can't shade area between functions with swapped axes.", Errors.Internal);
|
|
9406
|
-
}
|
|
9407
|
-
return this.plotParametric(function (y) {
|
|
9408
|
-
return [fn(y), y];
|
|
9409
|
-
}, range, shade);
|
|
9637
|
+
lastDiff = diff;
|
|
9638
|
+
}
|
|
9639
|
+
if (shade) {
|
|
9640
|
+
// backtrack to draw paired function
|
|
9641
|
+
for (let u = max - step; u >= lastFlip; u -= step) {
|
|
9642
|
+
// @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
|
|
9643
|
+
points.push(clippedFn2(u));
|
|
9410
9644
|
}
|
|
9645
|
+
}
|
|
9646
|
+
paths.push(path(points));
|
|
9647
|
+
return paths;
|
|
9648
|
+
}
|
|
9649
|
+
function plot(fn, range, swapAxes, shade, fn2) {
|
|
9650
|
+
const min = range[0];
|
|
9651
|
+
const max = range[1];
|
|
9652
|
+
if (!thisGraphie.currentStyle["plot-points"]) {
|
|
9653
|
+
thisGraphie.currentStyle["plot-points"] = 2 * (max - min) * thisGraphie.drawingTransform().pixelsPerUnitX();
|
|
9654
|
+
}
|
|
9655
|
+
if (swapAxes) {
|
|
9411
9656
|
if (fn2) {
|
|
9412
|
-
|
|
9413
|
-
|
|
9414
|
-
return [x, fn(x)];
|
|
9415
|
-
}, range, shade, function (x) {
|
|
9416
|
-
return [x, fn2(x)];
|
|
9417
|
-
});
|
|
9418
|
-
}
|
|
9419
|
-
throw new PerseusError("fn2 should only be set when 'shade' is True.", Errors.Internal);
|
|
9657
|
+
// TODO(charlie): support swapped axis area shading
|
|
9658
|
+
throw new PerseusError("Can't shade area between functions with swapped axes.", Errors.Internal);
|
|
9420
9659
|
}
|
|
9421
|
-
return
|
|
9422
|
-
return [
|
|
9660
|
+
return plotParametric(function (y) {
|
|
9661
|
+
return [fn(y), y];
|
|
9423
9662
|
}, range, shade);
|
|
9424
|
-
}
|
|
9425
|
-
|
|
9426
|
-
|
|
9427
|
-
|
|
9428
|
-
* Calls plotParametric.
|
|
9429
|
-
*
|
|
9430
|
-
* @param {[]} fnArray array of functions which when called
|
|
9431
|
-
* with a parameter i return the value of
|
|
9432
|
-
* the function at i
|
|
9433
|
-
* @param {[]} rangeArray array of ranges over which the
|
|
9434
|
-
* corresponding functions are defined
|
|
9435
|
-
* @return {Set<any>} set of paths
|
|
9436
|
-
*/
|
|
9437
|
-
plotPiecewise: function (fnArray, rangeArray) {
|
|
9438
|
-
const paths = raphael$1.set();
|
|
9439
|
-
const self = this;
|
|
9440
|
-
___default["default"].times(fnArray.length, function (i) {
|
|
9441
|
-
const fn = fnArray[i];
|
|
9442
|
-
const range = rangeArray[i];
|
|
9443
|
-
const fnPaths = self.plotParametric(function (x) {
|
|
9663
|
+
}
|
|
9664
|
+
if (fn2) {
|
|
9665
|
+
if (shade) {
|
|
9666
|
+
return plotParametric(function (x) {
|
|
9444
9667
|
return [x, fn(x)];
|
|
9445
|
-
}, range)
|
|
9446
|
-
|
|
9447
|
-
paths.push(fnPath);
|
|
9668
|
+
}, range, shade, function (x) {
|
|
9669
|
+
return [x, fn2(x)];
|
|
9448
9670
|
});
|
|
9449
|
-
});
|
|
9450
|
-
return paths;
|
|
9451
|
-
},
|
|
9452
|
-
/**
|
|
9453
|
-
* Given an array of coordinates of the form [x, y], create and
|
|
9454
|
-
* return a Raphael set of Raphael circle objects at those
|
|
9455
|
-
* coordinates
|
|
9456
|
-
*
|
|
9457
|
-
* @param {Array<[number, number]>} endpointArray
|
|
9458
|
-
* @return {Set<any>} set of circles
|
|
9459
|
-
*/
|
|
9460
|
-
plotEndpointCircles: function (endpointArray) {
|
|
9461
|
-
const circles = raphael$1.set();
|
|
9462
|
-
const self = this;
|
|
9463
|
-
___default["default"].each(endpointArray, function (coord, i) {
|
|
9464
|
-
circles.push(self.circle(coord, 0.15));
|
|
9465
|
-
});
|
|
9466
|
-
return circles;
|
|
9467
|
-
},
|
|
9468
|
-
plotAsymptotes: function (fn, range) {
|
|
9469
|
-
const min = range[0];
|
|
9470
|
-
const max = range[1];
|
|
9471
|
-
const step = (max - min) / (currentStyle["plot-points"] || 800);
|
|
9472
|
-
const asymptotes = raphael$1.set();
|
|
9473
|
-
let lastVal = fn(min);
|
|
9474
|
-
for (let t = min; t <= max; t += step) {
|
|
9475
|
-
const funcVal = fn(t);
|
|
9476
|
-
if (funcVal < 0 !== lastVal < 0 && Math.abs(funcVal - lastVal) > 2 * yScale) {
|
|
9477
|
-
asymptotes.push(this.line([t, yScale], [t, -yScale]));
|
|
9478
|
-
}
|
|
9479
|
-
lastVal = funcVal;
|
|
9480
|
-
}
|
|
9481
|
-
return asymptotes;
|
|
9482
|
-
}
|
|
9483
|
-
};
|
|
9484
|
-
const graphie = new Graphie$1();
|
|
9485
|
-
___default["default"].extend(graphie, {
|
|
9486
|
-
raphael: raphael$1,
|
|
9487
|
-
init: function (options) {
|
|
9488
|
-
let scale = options.scale || [40, 40];
|
|
9489
|
-
scale = typeof scale === "number" ? [scale, scale] : scale;
|
|
9490
|
-
xScale = scale[0];
|
|
9491
|
-
yScale = scale[1];
|
|
9492
|
-
if (options.range == null) {
|
|
9493
|
-
throw new PerseusError("range should be specified in graph init", Errors.Internal);
|
|
9494
|
-
}
|
|
9495
|
-
xRange = options.range[0];
|
|
9496
|
-
yRange = options.range[1];
|
|
9497
|
-
const w = (xRange[1] - xRange[0]) * xScale;
|
|
9498
|
-
const h = (yRange[1] - yRange[0]) * yScale;
|
|
9499
|
-
raphael$1.setSize(w, h);
|
|
9500
|
-
$__default["default"](el).css({
|
|
9501
|
-
width: w,
|
|
9502
|
-
height: h
|
|
9503
|
-
});
|
|
9504
|
-
this.range = options.range;
|
|
9505
|
-
this.scale = scale;
|
|
9506
|
-
this.dimensions = [w, h];
|
|
9507
|
-
this.xpixels = w;
|
|
9508
|
-
this.ypixels = h;
|
|
9509
|
-
this.isMobile = options.isMobile;
|
|
9510
|
-
return this;
|
|
9511
|
-
},
|
|
9512
|
-
style: function (attrs, fn) {
|
|
9513
|
-
const processed = processAttributes(attrs);
|
|
9514
|
-
if (typeof fn === "function") {
|
|
9515
|
-
const oldStyle = currentStyle;
|
|
9516
|
-
currentStyle = $__default["default"].extend({}, currentStyle, processed);
|
|
9517
|
-
const result = fn.call(graphie);
|
|
9518
|
-
currentStyle = oldStyle;
|
|
9519
|
-
return result;
|
|
9520
9671
|
}
|
|
9521
|
-
|
|
9522
|
-
}
|
|
9523
|
-
|
|
9524
|
-
|
|
9525
|
-
|
|
9526
|
-
|
|
9527
|
-
|
|
9528
|
-
|
|
9529
|
-
|
|
9530
|
-
|
|
9531
|
-
|
|
9532
|
-
|
|
9533
|
-
|
|
9534
|
-
|
|
9672
|
+
throw new PerseusError("fn2 should only be set when 'shade' is True.", Errors.Internal);
|
|
9673
|
+
}
|
|
9674
|
+
return plotParametric(function (x) {
|
|
9675
|
+
return [x, fn(x)];
|
|
9676
|
+
}, range, shade);
|
|
9677
|
+
}
|
|
9678
|
+
const drawingTools = {
|
|
9679
|
+
circle,
|
|
9680
|
+
rect,
|
|
9681
|
+
ellipse,
|
|
9682
|
+
fixedEllipse,
|
|
9683
|
+
arc,
|
|
9684
|
+
path,
|
|
9685
|
+
fixedPath,
|
|
9686
|
+
scaledPath,
|
|
9687
|
+
line,
|
|
9688
|
+
parabola,
|
|
9689
|
+
fixedLine,
|
|
9690
|
+
sinusoid,
|
|
9691
|
+
grid,
|
|
9692
|
+
label,
|
|
9693
|
+
plotParametric,
|
|
9694
|
+
plot
|
|
9695
|
+
};
|
|
9696
|
+
function graphify(drawingFn) {
|
|
9697
|
+
return function () {
|
|
9535
9698
|
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
9536
9699
|
args[_key] = arguments[_key];
|
|
9537
9700
|
}
|
|
9538
9701
|
const last = args[args.length - 1];
|
|
9539
|
-
const oldStyle = currentStyle;
|
|
9702
|
+
const oldStyle = thisGraphie.currentStyle;
|
|
9540
9703
|
let result;
|
|
9541
9704
|
|
|
9542
9705
|
// The last argument is probably trying to change the style
|
|
9543
9706
|
if (typeof last === "object" && !___default["default"].isArray(last)) {
|
|
9544
|
-
currentStyle = {
|
|
9545
|
-
...currentStyle,
|
|
9546
|
-
...processAttributes(last)
|
|
9707
|
+
thisGraphie.currentStyle = {
|
|
9708
|
+
...thisGraphie.currentStyle,
|
|
9709
|
+
...thisGraphie.processAttributes(last)
|
|
9547
9710
|
};
|
|
9548
9711
|
const rest = [].slice.call(args, 0, args.length - 1);
|
|
9549
|
-
result =
|
|
9712
|
+
result = drawingFn(...rest);
|
|
9550
9713
|
} else {
|
|
9551
|
-
currentStyle = $__default["default"].extend({}, currentStyle);
|
|
9552
|
-
result =
|
|
9714
|
+
thisGraphie.currentStyle = $__default["default"].extend({}, thisGraphie.currentStyle);
|
|
9715
|
+
result = drawingFn(...args);
|
|
9553
9716
|
}
|
|
9554
9717
|
|
|
9555
9718
|
// Bad heuristic for recognizing Raphael elements and sets
|
|
9556
9719
|
const type = result.constructor.prototype;
|
|
9557
9720
|
if (type === raphael.exports.el || type === raphael.exports.st) {
|
|
9558
|
-
result.attr(currentStyle);
|
|
9559
|
-
if (currentStyle.arrows) {
|
|
9721
|
+
result.attr(thisGraphie.currentStyle);
|
|
9722
|
+
if (thisGraphie.currentStyle.arrows) {
|
|
9560
9723
|
result = addArrowheads(result);
|
|
9561
9724
|
}
|
|
9562
9725
|
} else if (result instanceof $__default["default"]) {
|
|
@@ -9564,256 +9727,18 @@ GraphUtils.createGraphie = function (el) {
|
|
|
9564
9727
|
// does not contain SVG.
|
|
9565
9728
|
// @ts-expect-error - TS2339 - Property 'css' does not exist on type '{}'.
|
|
9566
9729
|
result.css({
|
|
9567
|
-
...currentStyle,
|
|
9730
|
+
...thisGraphie.currentStyle,
|
|
9568
9731
|
...SVG_SPECIFIC_STYLE_MASK
|
|
9569
9732
|
});
|
|
9570
9733
|
}
|
|
9571
|
-
currentStyle = oldStyle;
|
|
9734
|
+
thisGraphie.currentStyle = oldStyle;
|
|
9572
9735
|
return result;
|
|
9573
9736
|
};
|
|
9737
|
+
}
|
|
9738
|
+
$__default["default"].each(drawingTools, function (name) {
|
|
9739
|
+
thisGraphie[name] = graphify(drawingTools[name]);
|
|
9574
9740
|
});
|
|
9575
|
-
|
|
9576
|
-
// Initializes graphie settings for a graph and draws the basic graph
|
|
9577
|
-
// features (axes, grid, tick marks, and axis labels)
|
|
9578
|
-
// Options expected are:
|
|
9579
|
-
// - range: [[a, b], [c, d]] or [a, b]
|
|
9580
|
-
// - scale: [a, b] or number
|
|
9581
|
-
// - gridOpacity: number (0 - 1)
|
|
9582
|
-
// - gridStep: [a, b] or number (relative to units)
|
|
9583
|
-
// - tickStep: [a, b] or number (relative to grid steps)
|
|
9584
|
-
// - tickLen: [a, b] or number (in pixels)
|
|
9585
|
-
// - labelStep: [a, b] or number (relative to tick steps)
|
|
9586
|
-
// - yLabelFormat: fn to format label string for y-axis
|
|
9587
|
-
// - xLabelFormat: fn to format label string for x-axis
|
|
9588
|
-
// - smartLabelPositioning: true or false to ignore minus sign
|
|
9589
|
-
graphie.graphInit = function (options) {
|
|
9590
|
-
options = options || {};
|
|
9591
|
-
$__default["default"].each(options, function (prop, val) {
|
|
9592
|
-
// allow options to be specified by a single number for shorthand if
|
|
9593
|
-
// the horizontal and vertical components are the same
|
|
9594
|
-
if (
|
|
9595
|
-
// @ts-expect-error - TS2339 - Property 'match' does not exist on type 'string | number | symbol'.
|
|
9596
|
-
!prop.match(/.*Opacity$/) && prop !== "range" && typeof val === "number") {
|
|
9597
|
-
options[prop] = [val, val];
|
|
9598
|
-
}
|
|
9599
|
-
|
|
9600
|
-
// allow symmetric ranges to be specified by the absolute values
|
|
9601
|
-
if (prop === "range" || prop === "gridRange") {
|
|
9602
|
-
if (val.constructor === Array) {
|
|
9603
|
-
// but don't mandate symmetric ranges
|
|
9604
|
-
if (val[0].constructor !== Array) {
|
|
9605
|
-
options[prop] = [[-val[0], val[0]], [-val[1], val[1]]];
|
|
9606
|
-
}
|
|
9607
|
-
} else if (typeof val === "number") {
|
|
9608
|
-
options[prop] = [[-val, val], [-val, val]];
|
|
9609
|
-
}
|
|
9610
|
-
}
|
|
9611
|
-
});
|
|
9612
|
-
const range = options.range || [[-10, 10], [-10, 10]];
|
|
9613
|
-
const gridRange = options.gridRange || options.range;
|
|
9614
|
-
const scale = options.scale || [20, 20];
|
|
9615
|
-
const grid = options.grid != null ? options.grid : true;
|
|
9616
|
-
const gridOpacity = options.gridOpacity || 0.1;
|
|
9617
|
-
const gridStep = options.gridStep || [1, 1];
|
|
9618
|
-
const axes = options.axes != null ? options.axes : true;
|
|
9619
|
-
const axisArrows = options.axisArrows || "";
|
|
9620
|
-
const axisOpacity = options.axisOpacity || 1.0;
|
|
9621
|
-
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])];
|
|
9622
|
-
const axisLabels = options.axisLabels != null ? options.axisLabels : false;
|
|
9623
|
-
const ticks = options.ticks != null ? options.ticks : true;
|
|
9624
|
-
const tickStep = options.tickStep || [2, 2];
|
|
9625
|
-
const tickLen = options.tickLen || [5, 5];
|
|
9626
|
-
const tickOpacity = options.tickOpacity || 1.0;
|
|
9627
|
-
const labels = options.labels || options.labelStep || false;
|
|
9628
|
-
const labelStep = options.labelStep || [1, 1];
|
|
9629
|
-
const labelOpacity = options.labelOpacity || 1.0;
|
|
9630
|
-
let unityLabels = options.unityLabels || false;
|
|
9631
|
-
const labelFormat = options.labelFormat || function (a) {
|
|
9632
|
-
return a;
|
|
9633
|
-
};
|
|
9634
|
-
let xLabelFormat = options.xLabelFormat || labelFormat;
|
|
9635
|
-
let yLabelFormat = options.yLabelFormat || labelFormat;
|
|
9636
|
-
const smartLabelPositioning = options.smartLabelPositioning != null ? options.smartLabelPositioning : true;
|
|
9637
|
-
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)]];
|
|
9638
|
-
if (!___default["default"].isArray(unityLabels)) {
|
|
9639
|
-
unityLabels = [unityLabels, unityLabels];
|
|
9640
|
-
}
|
|
9641
|
-
if (smartLabelPositioning) {
|
|
9642
|
-
const minusIgnorer = function (lf) {
|
|
9643
|
-
return function (a) {
|
|
9644
|
-
return (lf(a) + "").replace(/-(\d)/g, "\\llap{-}$1");
|
|
9645
|
-
};
|
|
9646
|
-
};
|
|
9647
|
-
xLabelFormat = minusIgnorer(xLabelFormat);
|
|
9648
|
-
yLabelFormat = minusIgnorer(yLabelFormat);
|
|
9649
|
-
}
|
|
9650
|
-
this.init({
|
|
9651
|
-
range: realRange,
|
|
9652
|
-
scale: scale,
|
|
9653
|
-
isMobile: options.isMobile
|
|
9654
|
-
});
|
|
9655
|
-
|
|
9656
|
-
// draw grid
|
|
9657
|
-
if (grid) {
|
|
9658
|
-
this.grid(gridRange[0], gridRange[1], {
|
|
9659
|
-
stroke: options.isMobile ? KhanColors.GRAY_C : "#000000",
|
|
9660
|
-
opacity: options.isMobile ? 1 : gridOpacity,
|
|
9661
|
-
step: gridStep,
|
|
9662
|
-
strokeWidth: options.isMobile ? 1 : 2
|
|
9663
|
-
});
|
|
9664
|
-
}
|
|
9665
|
-
|
|
9666
|
-
// draw axes
|
|
9667
|
-
if (axes) {
|
|
9668
|
-
// this is a slight hack until <-> arrowheads work
|
|
9669
|
-
if (axisArrows === "<->" || axisArrows === true) {
|
|
9670
|
-
this.style({
|
|
9671
|
-
stroke: options.isMobile ? KhanColors.GRAY_G : "#000000",
|
|
9672
|
-
opacity: options.isMobile ? 1 : axisOpacity,
|
|
9673
|
-
strokeWidth: options.isMobile ? 1 : 2,
|
|
9674
|
-
arrows: "->"
|
|
9675
|
-
}, function () {
|
|
9676
|
-
if (range[1][0] < 0 && range[1][1] > 0) {
|
|
9677
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
9678
|
-
this.path([axisCenter, [gridRange[0][0], axisCenter[1]]]);
|
|
9679
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
9680
|
-
this.path([axisCenter, [gridRange[0][1], axisCenter[1]]]);
|
|
9681
|
-
}
|
|
9682
|
-
if (range[0][0] < 0 && range[0][1] > 0) {
|
|
9683
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
9684
|
-
this.path([axisCenter, [axisCenter[0], gridRange[1][0]]]);
|
|
9685
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
9686
|
-
this.path([axisCenter, [axisCenter[0], gridRange[1][1]]]);
|
|
9687
|
-
}
|
|
9688
|
-
});
|
|
9689
|
-
|
|
9690
|
-
// also, we don't support "<-" arrows yet, but why you
|
|
9691
|
-
// would want that on your graph is beyond me.
|
|
9692
|
-
} else if (axisArrows === "->" || axisArrows === "") {
|
|
9693
|
-
this.style({
|
|
9694
|
-
stroke: "#000000",
|
|
9695
|
-
opacity: axisOpacity,
|
|
9696
|
-
strokeWidth: 2,
|
|
9697
|
-
arrows: axisArrows
|
|
9698
|
-
}, function () {
|
|
9699
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
9700
|
-
this.path([[gridRange[0][0], axisCenter[1]], [gridRange[0][1], axisCenter[1]]]);
|
|
9701
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
9702
|
-
this.path([[axisCenter[0], gridRange[1][0]], [axisCenter[0], gridRange[1][1]]]);
|
|
9703
|
-
});
|
|
9704
|
-
}
|
|
9705
|
-
if (axisLabels && axisLabels.length === 2) {
|
|
9706
|
-
this.label([gridRange[0][1], axisCenter[1]], axisLabels[0], "right");
|
|
9707
|
-
this.label([axisCenter[0], gridRange[1][1]], axisLabels[1], "above");
|
|
9708
|
-
}
|
|
9709
|
-
}
|
|
9710
|
-
|
|
9711
|
-
// draw tick marks
|
|
9712
|
-
if (ticks) {
|
|
9713
|
-
const halfWidthTicks = options.isMobile;
|
|
9714
|
-
this.style({
|
|
9715
|
-
stroke: options.isMobile ? KhanColors.GRAY_G : "#000000",
|
|
9716
|
-
opacity: options.isMobile ? 1 : tickOpacity,
|
|
9717
|
-
strokeWidth: 1
|
|
9718
|
-
}, function () {
|
|
9719
|
-
// horizontal axis
|
|
9720
|
-
let step = gridStep[0] * tickStep[0];
|
|
9721
|
-
let len = tickLen[0] / scale[1];
|
|
9722
|
-
let start = gridRange[0][0];
|
|
9723
|
-
let stop = gridRange[0][1];
|
|
9724
|
-
if (range[1][0] < 0 && range[1][1] > 0) {
|
|
9725
|
-
for (let x = step + axisCenter[0]; x <= stop; x += step) {
|
|
9726
|
-
if (x < stop || !axisArrows) {
|
|
9727
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
9728
|
-
this.line([x, -len + axisCenter[1]], [x, halfWidthTicks ? 0 : len + axisCenter[1]]);
|
|
9729
|
-
}
|
|
9730
|
-
}
|
|
9731
|
-
for (let x = -step + axisCenter[0]; x >= start; x -= step) {
|
|
9732
|
-
if (x > start || !axisArrows) {
|
|
9733
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
9734
|
-
this.line([x, -len + axisCenter[1]], [x, halfWidthTicks ? 0 : len + axisCenter[1]]);
|
|
9735
|
-
}
|
|
9736
|
-
}
|
|
9737
|
-
}
|
|
9738
|
-
|
|
9739
|
-
// vertical axis
|
|
9740
|
-
step = gridStep[1] * tickStep[1];
|
|
9741
|
-
len = tickLen[1] / scale[0];
|
|
9742
|
-
start = gridRange[1][0];
|
|
9743
|
-
stop = gridRange[1][1];
|
|
9744
|
-
if (range[0][0] < 0 && range[0][1] > 0) {
|
|
9745
|
-
for (let y = step + axisCenter[1]; y <= stop; y += step) {
|
|
9746
|
-
if (y < stop || !axisArrows) {
|
|
9747
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
9748
|
-
this.line([-len + axisCenter[0], y], [halfWidthTicks ? 0 : len + axisCenter[0], y]);
|
|
9749
|
-
}
|
|
9750
|
-
}
|
|
9751
|
-
for (let y = -step + axisCenter[1]; y >= start; y -= step) {
|
|
9752
|
-
if (y > start || !axisArrows) {
|
|
9753
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
9754
|
-
this.line([-len + axisCenter[0], y], [halfWidthTicks ? 0 : len + axisCenter[0], y]);
|
|
9755
|
-
}
|
|
9756
|
-
}
|
|
9757
|
-
}
|
|
9758
|
-
});
|
|
9759
|
-
}
|
|
9760
|
-
|
|
9761
|
-
// draw axis labels
|
|
9762
|
-
if (labels) {
|
|
9763
|
-
this.style({
|
|
9764
|
-
stroke: options.isMobile ? KhanColors.GRAY_G : "#000000",
|
|
9765
|
-
opacity: options.isMobile ? 1 : labelOpacity
|
|
9766
|
-
}, function () {
|
|
9767
|
-
// horizontal axis
|
|
9768
|
-
let step = gridStep[0] * tickStep[0] * labelStep[0];
|
|
9769
|
-
let start = gridRange[0][0];
|
|
9770
|
-
let stop = gridRange[0][1];
|
|
9771
|
-
const xAxisPosition = axisCenter[0] < 0 ? "above" : "below";
|
|
9772
|
-
const yAxisPosition = axisCenter[0] < 0 ? "right" : "left";
|
|
9773
|
-
const xShowZero = axisCenter[0] === 0 && axisCenter[1] !== 0;
|
|
9774
|
-
const yShowZero = axisCenter[0] !== 0 && axisCenter[1] === 0;
|
|
9775
|
-
const axisOffCenter = axisCenter[0] !== 0 || axisCenter[1] !== 0;
|
|
9776
|
-
const showUnityX = unityLabels[0] || axisOffCenter;
|
|
9777
|
-
const showUnityY = unityLabels[1] || axisOffCenter;
|
|
9778
|
-
|
|
9779
|
-
// positive x-axis
|
|
9780
|
-
for (let x = (xShowZero ? 0 : step) + axisCenter[0]; x <= stop; x += step) {
|
|
9781
|
-
if (x < stop || !axisArrows) {
|
|
9782
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
9783
|
-
this.label([x, axisCenter[1]], xLabelFormat(x), xAxisPosition);
|
|
9784
|
-
}
|
|
9785
|
-
}
|
|
9786
|
-
|
|
9787
|
-
// negative x-axis
|
|
9788
|
-
for (let x = -step * (showUnityX ? 1 : 2) + axisCenter[0]; x >= start; x -= step) {
|
|
9789
|
-
if (x > start || !axisArrows) {
|
|
9790
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
9791
|
-
this.label([x, axisCenter[1]], xLabelFormat(x), xAxisPosition);
|
|
9792
|
-
}
|
|
9793
|
-
}
|
|
9794
|
-
step = gridStep[1] * tickStep[1] * labelStep[1];
|
|
9795
|
-
start = gridRange[1][0];
|
|
9796
|
-
stop = gridRange[1][1];
|
|
9797
|
-
|
|
9798
|
-
// positive y-axis
|
|
9799
|
-
for (let y = (yShowZero ? 0 : step) + axisCenter[1]; y <= stop; y += step) {
|
|
9800
|
-
if (y < stop || !axisArrows) {
|
|
9801
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
9802
|
-
this.label([axisCenter[0], y], yLabelFormat(y), yAxisPosition);
|
|
9803
|
-
}
|
|
9804
|
-
}
|
|
9805
|
-
|
|
9806
|
-
// negative y-axis
|
|
9807
|
-
for (let y = -step * (showUnityY ? 1 : 2) + axisCenter[1]; y >= start; y -= step) {
|
|
9808
|
-
if (y > start || !axisArrows) {
|
|
9809
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
9810
|
-
this.label([axisCenter[0], y], yLabelFormat(y), yAxisPosition);
|
|
9811
|
-
}
|
|
9812
|
-
}
|
|
9813
|
-
});
|
|
9814
|
-
}
|
|
9815
|
-
};
|
|
9816
|
-
return graphie;
|
|
9741
|
+
return thisGraphie;
|
|
9817
9742
|
};
|
|
9818
9743
|
|
|
9819
9744
|
/* eslint-disable */
|
|
@@ -13403,7 +13328,7 @@ ___default["default"].extend(MovableAngle.prototype, {
|
|
|
13403
13328
|
let angle = GraphUtils.findAngle(newPoint, vertex);
|
|
13404
13329
|
angle = Math.round((angle - snapOffset) / snap) * snap + snapOffset;
|
|
13405
13330
|
const distance = GraphUtils.getDistance(newPoint, vertex);
|
|
13406
|
-
return addPoints(vertex,
|
|
13331
|
+
return addPoints(vertex, polar(distance, angle));
|
|
13407
13332
|
}
|
|
13408
13333
|
return true;
|
|
13409
13334
|
};
|
|
@@ -18503,6 +18428,27 @@ const registerEditors = editorsToRegister => {
|
|
|
18503
18428
|
editors[editor.widgetName] = editor;
|
|
18504
18429
|
});
|
|
18505
18430
|
};
|
|
18431
|
+
|
|
18432
|
+
/**
|
|
18433
|
+
*
|
|
18434
|
+
* @param name - the widget that you are trying to replace
|
|
18435
|
+
* @param replacementName - the name of the widget that takes its place
|
|
18436
|
+
*
|
|
18437
|
+
* e.g. replaceEditor("transformer", "always-correct") will make it so the
|
|
18438
|
+
* transformer widget is replaced by the always correct widget
|
|
18439
|
+
*/
|
|
18440
|
+
const replaceEditor = (name, replacementName) => {
|
|
18441
|
+
const substituteEditor = editors[replacementName];
|
|
18442
|
+
if (!substituteEditor && Log) {
|
|
18443
|
+
const errorMsg = `Failed to replace editor ${name} with ${replacementName}`;
|
|
18444
|
+
Log.error(errorMsg, Errors.Internal);
|
|
18445
|
+
return;
|
|
18446
|
+
}
|
|
18447
|
+
editors[name] = substituteEditor;
|
|
18448
|
+
if (Log) {
|
|
18449
|
+
Log.log(`INFO: Replacing editor ${name} with ${replacementName}`);
|
|
18450
|
+
}
|
|
18451
|
+
};
|
|
18506
18452
|
const getWidget = name => {
|
|
18507
18453
|
// TODO(alex): Consider referring to these as renderers to avoid
|
|
18508
18454
|
// overloading "widget"
|
|
@@ -18531,7 +18477,7 @@ const getVersion = name => {
|
|
|
18531
18477
|
minor: 0
|
|
18532
18478
|
};
|
|
18533
18479
|
}
|
|
18534
|
-
return
|
|
18480
|
+
return;
|
|
18535
18481
|
};
|
|
18536
18482
|
const getVersionVector = () => {
|
|
18537
18483
|
const version = {};
|
|
@@ -18815,6 +18761,7 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
|
|
|
18815
18761
|
registerWidgets: registerWidgets,
|
|
18816
18762
|
replaceWidget: replaceWidget,
|
|
18817
18763
|
registerEditors: registerEditors,
|
|
18764
|
+
replaceEditor: replaceEditor,
|
|
18818
18765
|
getWidget: getWidget,
|
|
18819
18766
|
getEditor: getEditor,
|
|
18820
18767
|
getTransform: getTransform,
|
|
@@ -24505,7 +24452,7 @@ var Definition$1 = {
|
|
|
24505
24452
|
|
|
24506
24453
|
// The props are type `any` on purpose so that this can receive props
|
|
24507
24454
|
// from any deprecated widget
|
|
24508
|
-
class
|
|
24455
|
+
class DeprecatedStandin extends React__default["default"].Component {
|
|
24509
24456
|
static validate(userInput, rubric) {
|
|
24510
24457
|
// Since this mean to replace an existing widget the learner
|
|
24511
24458
|
// WILL earn points for this widget
|
|
@@ -24520,7 +24467,7 @@ class AutoCorrect extends React__default["default"].Component {
|
|
|
24520
24467
|
return {};
|
|
24521
24468
|
};
|
|
24522
24469
|
simpleValidate = rubric => {
|
|
24523
|
-
return
|
|
24470
|
+
return DeprecatedStandin.validate(this.getUserInput(), rubric);
|
|
24524
24471
|
};
|
|
24525
24472
|
render() {
|
|
24526
24473
|
return /*#__PURE__*/React__default["default"].createElement("div", {
|
|
@@ -24529,16 +24476,17 @@ class AutoCorrect extends React__default["default"].Component {
|
|
|
24529
24476
|
paddingBottom: 8
|
|
24530
24477
|
}
|
|
24531
24478
|
}, /*#__PURE__*/React__default["default"].createElement(Banner__default["default"], {
|
|
24532
|
-
text: i18n__namespace._("
|
|
24533
|
-
kind: "
|
|
24479
|
+
text: i18n__namespace._("Sorry, this part of the question is no longer available. 😅 Don't worry, you won't be graded on this part. Keep going!"),
|
|
24480
|
+
kind: "info",
|
|
24534
24481
|
layout: "full-width"
|
|
24535
24482
|
}));
|
|
24536
24483
|
}
|
|
24537
24484
|
}
|
|
24538
|
-
var DeprecatedStandin = {
|
|
24485
|
+
var DeprecatedStandin$1 = {
|
|
24539
24486
|
name: "deprecated-standin",
|
|
24540
24487
|
displayName: "Deprecated Standin",
|
|
24541
|
-
widget:
|
|
24488
|
+
widget: DeprecatedStandin,
|
|
24489
|
+
hidden: true
|
|
24542
24490
|
};
|
|
24543
24491
|
|
|
24544
24492
|
class Dropdown extends React__namespace.Component {
|
|
@@ -29113,7 +29061,7 @@ class InteractiveGraph extends React__namespace.Component {
|
|
|
29113
29061
|
// Solve for side by using the law of sines
|
|
29114
29062
|
const side = Math.sin(innerAngles[1] * Math.PI / 180) / Math.sin(innerAngles[2] * Math.PI / 180) * knownSide;
|
|
29115
29063
|
const outerAngle = GraphUtils.findAngle(coords[rel(1)], coords[rel(-1)]);
|
|
29116
|
-
const offset =
|
|
29064
|
+
const offset = polar(side, outerAngle + (onLeft ? 1 : -1) * innerAngles[0]);
|
|
29117
29065
|
return this.graphie?.addPoints(coords[rel(-1)], offset);
|
|
29118
29066
|
}
|
|
29119
29067
|
// @ts-expect-error - TS2339 - Property 'snapTo' does not exist on type 'PerseusGraphType'.
|
|
@@ -29137,7 +29085,7 @@ class InteractiveGraph extends React__namespace.Component {
|
|
|
29137
29085
|
const innerAngle = lawOfCosines(sides[0], sides[2], sides[1]);
|
|
29138
29086
|
const outerAngle = GraphUtils.findAngle(coords[rel(1)], coords[rel(-1)]);
|
|
29139
29087
|
const onLeft = sign(ccw(coords[rel(-1)], coords[rel(1)], coords[i])) === 1;
|
|
29140
|
-
const offset =
|
|
29088
|
+
const offset = polar(sides[0], outerAngle + (onLeft ? 1 : -1) * innerAngle);
|
|
29141
29089
|
return this.graphie?.addPoints(coords[rel(-1)], offset);
|
|
29142
29090
|
}
|
|
29143
29091
|
// Snap to grid (already done)
|
|
@@ -31160,7 +31108,7 @@ const AnswerPill = props => {
|
|
|
31160
31108
|
id: idFactory.get("perseus-label-image-widget-answer-pill"),
|
|
31161
31109
|
onClick: correct ? undefined : onClick,
|
|
31162
31110
|
ref: ref,
|
|
31163
|
-
style: [style, popperStyle, styles$h.
|
|
31111
|
+
style: [style, popperStyle, styles$h.pill, correct && styles$h.correct, incorrect && styles$h.incorrect, (focused || hovered) && BringToFront]
|
|
31164
31112
|
}, /*#__PURE__*/React__namespace.createElement(Renderer, {
|
|
31165
31113
|
content: answerString,
|
|
31166
31114
|
inline: true
|
|
@@ -31174,6 +31122,11 @@ const styles$h = aphrodite.StyleSheet.create({
|
|
|
31174
31122
|
},
|
|
31175
31123
|
incorrect: {
|
|
31176
31124
|
backgroundColor: Color__default["default"].offBlack64
|
|
31125
|
+
},
|
|
31126
|
+
pill: {
|
|
31127
|
+
// Reset the Pill's default height in order to account
|
|
31128
|
+
// for multi-line pills.
|
|
31129
|
+
height: "auto"
|
|
31177
31130
|
}
|
|
31178
31131
|
});
|
|
31179
31132
|
|
|
@@ -44105,7 +44058,7 @@ var Video$1 = {
|
|
|
44105
44058
|
};
|
|
44106
44059
|
|
|
44107
44060
|
/* eslint-disable import/no-named-as-default */
|
|
44108
|
-
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];
|
|
44061
|
+
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];
|
|
44109
44062
|
|
|
44110
44063
|
/**
|
|
44111
44064
|
* This should be called by all clients, specifying whether extra widgets are
|
|
@@ -44123,7 +44076,7 @@ const init = function (options) {
|
|
|
44123
44076
|
// This section replaces widgets
|
|
44124
44077
|
// When it's time to replace the transformer widget
|
|
44125
44078
|
// uncomment this line
|
|
44126
|
-
// Widgets.replaceWidget("transformer", "
|
|
44079
|
+
// Widgets.replaceWidget("transformer", "deprecated-standin");
|
|
44127
44080
|
|
|
44128
44081
|
// Pass skipMathJax: true if MathJax is already loaded and configured.
|
|
44129
44082
|
const skipMathJax = options.skipMathJax;
|
|
@@ -44149,7 +44102,7 @@ const init = function (options) {
|
|
|
44149
44102
|
|
|
44150
44103
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
44151
44104
|
const libName = "@khanacademy/perseus";
|
|
44152
|
-
const libVersion = "
|
|
44105
|
+
const libVersion = "18.0.1";
|
|
44153
44106
|
perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
44154
44107
|
|
|
44155
44108
|
const apiVersion = {
|
|
@@ -46726,6 +46679,417 @@ const registerAllWidgetsForTesting = () => {
|
|
|
46726
46679
|
// Widgets.replaceWidget("transformer", "deprecated-standin");
|
|
46727
46680
|
};
|
|
46728
46681
|
|
|
46682
|
+
/**
|
|
46683
|
+
* This function extracts the answers from the widgets.
|
|
46684
|
+
*
|
|
46685
|
+
* For each widget type, we first check to make sure the path to the answer exists,
|
|
46686
|
+
* then we extract the answer and add it to the list of answers.
|
|
46687
|
+
*
|
|
46688
|
+
* A list is returned because some widgets, like multi-select radio widgets and groups
|
|
46689
|
+
* can have multiple answers.
|
|
46690
|
+
*
|
|
46691
|
+
* @param {PerseusRenderer["widgets"]} widgets
|
|
46692
|
+
* @returns a list of strings that are the answers to the widgets
|
|
46693
|
+
*/
|
|
46694
|
+
function getAnswersFromWidgets(widgets) {
|
|
46695
|
+
const answers = [];
|
|
46696
|
+
wonderStuffCore.keys(widgets).forEach(widgetID => {
|
|
46697
|
+
const widget = widgets[widgetID];
|
|
46698
|
+
if (!widget.options) {
|
|
46699
|
+
return;
|
|
46700
|
+
}
|
|
46701
|
+
switch (widget.type) {
|
|
46702
|
+
case "radio":
|
|
46703
|
+
const radio = widget;
|
|
46704
|
+
const options = radio.options;
|
|
46705
|
+
// Answer is the content of the correct choice
|
|
46706
|
+
if (options?.choices?.length) {
|
|
46707
|
+
for (const choice of options.choices) {
|
|
46708
|
+
if (choice?.correct) {
|
|
46709
|
+
answers.push(choice.content);
|
|
46710
|
+
}
|
|
46711
|
+
}
|
|
46712
|
+
}
|
|
46713
|
+
break;
|
|
46714
|
+
case "categorizer":
|
|
46715
|
+
// Answer is a list of items mapped to their category
|
|
46716
|
+
// e.g. ['item 1: category 1', 'item 2: category 2']
|
|
46717
|
+
const categorizer = widget;
|
|
46718
|
+
if (categorizer.options?.categories && categorizer.options?.items && categorizer.options?.values) {
|
|
46719
|
+
const categories = categorizer.options?.categories;
|
|
46720
|
+
const items = categorizer.options?.items;
|
|
46721
|
+
const values = categorizer.options?.values;
|
|
46722
|
+
answers.push(...values.map((value, index) => `${items[index]}: ${categories[value]}`));
|
|
46723
|
+
}
|
|
46724
|
+
break;
|
|
46725
|
+
case "dropdown":
|
|
46726
|
+
// Answer is the content of correct dropdown option
|
|
46727
|
+
const dropdown = widget;
|
|
46728
|
+
if (dropdown.options?.choices) {
|
|
46729
|
+
for (const choice of dropdown.options.choices) {
|
|
46730
|
+
if (choice.correct) {
|
|
46731
|
+
answers.push(choice.content);
|
|
46732
|
+
}
|
|
46733
|
+
}
|
|
46734
|
+
}
|
|
46735
|
+
break;
|
|
46736
|
+
case "numeric-input":
|
|
46737
|
+
// Answer is the numeric value cast to a string
|
|
46738
|
+
const numericInput = widget;
|
|
46739
|
+
if (numericInput.options?.answers) {
|
|
46740
|
+
for (const ans of numericInput.options.answers) {
|
|
46741
|
+
if (ans.status === "correct" && ans.value != null) {
|
|
46742
|
+
answers.push(ans.value.toString());
|
|
46743
|
+
}
|
|
46744
|
+
}
|
|
46745
|
+
}
|
|
46746
|
+
break;
|
|
46747
|
+
case "input-number":
|
|
46748
|
+
// Answer is the correct value
|
|
46749
|
+
const inputNumber = widget;
|
|
46750
|
+
if (inputNumber.options?.value) {
|
|
46751
|
+
answers.push(inputNumber.options.value.toString());
|
|
46752
|
+
}
|
|
46753
|
+
break;
|
|
46754
|
+
case "expression":
|
|
46755
|
+
// Answer is a list of potential correct expressions,
|
|
46756
|
+
// since there can be multiple correct forms of the expression
|
|
46757
|
+
const expression = widget;
|
|
46758
|
+
if (expression.options?.answerForms) {
|
|
46759
|
+
answers.push(...expression.options.answerForms.map(answer => answer.value));
|
|
46760
|
+
}
|
|
46761
|
+
break;
|
|
46762
|
+
case "group":
|
|
46763
|
+
case "graded-group":
|
|
46764
|
+
// Groups can contain multiple widgets, so we recursively
|
|
46765
|
+
// call this function to get the nested answers from the
|
|
46766
|
+
// group's widgets
|
|
46767
|
+
const gradedGroup = widget;
|
|
46768
|
+
if (gradedGroup.options?.widgets) {
|
|
46769
|
+
answers.push(...getAnswersFromWidgets(gradedGroup.options.widgets));
|
|
46770
|
+
}
|
|
46771
|
+
break;
|
|
46772
|
+
case "plotter":
|
|
46773
|
+
// Answer is a map of the correct value for each category
|
|
46774
|
+
// e.g. '{Soccer: 35, Basketball: 20, Golf: 45, Volleyball: 15,
|
|
46775
|
+
// Tennis: 30}'
|
|
46776
|
+
const plotter = widget;
|
|
46777
|
+
if (plotter.options?.categories && plotter.options?.correct && plotter.options.categories.length === plotter.options.correct.length) {
|
|
46778
|
+
const {
|
|
46779
|
+
categories,
|
|
46780
|
+
correct
|
|
46781
|
+
} = plotter.options;
|
|
46782
|
+
answers.push(`{${categories.map((category, index) => `${category}: ${correct[index]}`).join(", ")}}`);
|
|
46783
|
+
}
|
|
46784
|
+
break;
|
|
46785
|
+
case "interactive-graph":
|
|
46786
|
+
case "grapher":
|
|
46787
|
+
// Answer is a list of points that should be on the graph
|
|
46788
|
+
// E.g. 'There should be point(s) on [(-1, 1), (1, 1)]'
|
|
46789
|
+
const grapher = widget;
|
|
46790
|
+
// @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'.
|
|
46791
|
+
if (grapher.options?.correct?.coords) {
|
|
46792
|
+
answers.push(
|
|
46793
|
+
// @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'.
|
|
46794
|
+
`There should be point(s) on [${grapher.options.correct?.coords.join("], [")}]`);
|
|
46795
|
+
}
|
|
46796
|
+
break;
|
|
46797
|
+
case "orderer":
|
|
46798
|
+
// Answer is the content of the each option
|
|
46799
|
+
// in the correct order on newlines
|
|
46800
|
+
// E.g. 'item 1\nitem 2\nitem 3'
|
|
46801
|
+
const orderer = widget;
|
|
46802
|
+
if (orderer.options?.correctOptions) {
|
|
46803
|
+
answers.push(orderer.options.correctOptions.map(option => option.content).join("\n"));
|
|
46804
|
+
}
|
|
46805
|
+
break;
|
|
46806
|
+
case "sorter":
|
|
46807
|
+
// Answer is the content of each option in the correct order
|
|
46808
|
+
// separated by commas
|
|
46809
|
+
// E.g. 'item 1, item 2, item 3'
|
|
46810
|
+
const sorter = widget;
|
|
46811
|
+
if (sorter.options?.correct) {
|
|
46812
|
+
answers.push(sorter.options.correct.join(", "));
|
|
46813
|
+
}
|
|
46814
|
+
break;
|
|
46815
|
+
case "label-image":
|
|
46816
|
+
// Answer is a list of labels, their positions, and their answer
|
|
46817
|
+
// E.g. '{label: "label 1", position: {x: 0.5, y: 0.5}, answer: "answer 1"}'
|
|
46818
|
+
const labelImage = widget;
|
|
46819
|
+
if (labelImage.options?.markers) {
|
|
46820
|
+
answers.push(...labelImage.options.markers.map(m => `{label: "${m.label}", position: {${m.x},${m.y}}, answer: "${m.answers.join(", ")}"}`));
|
|
46821
|
+
}
|
|
46822
|
+
break;
|
|
46823
|
+
case "number-line":
|
|
46824
|
+
// Answer is the correct x position on the number line
|
|
46825
|
+
// E.g. '0.5'
|
|
46826
|
+
const numberLine = widget;
|
|
46827
|
+
if (numberLine.options?.correctX != null) {
|
|
46828
|
+
answers.push(numberLine.options.correctX.toString());
|
|
46829
|
+
}
|
|
46830
|
+
break;
|
|
46831
|
+
case "matrix":
|
|
46832
|
+
// Answer is a list of rows representing the matrix
|
|
46833
|
+
// E.g. '[[1, 2], [3, 4]]'
|
|
46834
|
+
const matrix = widget;
|
|
46835
|
+
if (matrix.options?.answers) {
|
|
46836
|
+
answers.push(`[${matrix.options.answers.join("], [")}]`);
|
|
46837
|
+
}
|
|
46838
|
+
break;
|
|
46839
|
+
case "matcher":
|
|
46840
|
+
// Answer is a table of the left and right items
|
|
46841
|
+
// E.g. '| Left | Right |\n| --- | --- |\n| left item 1 | right item 1 |\n| left item 2 | right item 2 |'
|
|
46842
|
+
const matcher = widget;
|
|
46843
|
+
if (matcher.options?.left && matcher.options?.right) {
|
|
46844
|
+
const {
|
|
46845
|
+
left,
|
|
46846
|
+
right
|
|
46847
|
+
} = matcher.options;
|
|
46848
|
+
const [leftHeader, rightHeader] = matcher.options.labels;
|
|
46849
|
+
const tableHeader = `| ${leftHeader} | ${rightHeader} |\n| --- | --- |`;
|
|
46850
|
+
const tableRows = left.map((leftItem, index) => {
|
|
46851
|
+
return `| ${leftItem} | ${right[index]} |`;
|
|
46852
|
+
});
|
|
46853
|
+
const table = [tableHeader, ...tableRows].join("\n");
|
|
46854
|
+
answers.push(table);
|
|
46855
|
+
}
|
|
46856
|
+
break;
|
|
46857
|
+
}
|
|
46858
|
+
});
|
|
46859
|
+
return answers;
|
|
46860
|
+
}
|
|
46861
|
+
|
|
46862
|
+
/**
|
|
46863
|
+
* Inject a string equivalent of the widgets into the content.
|
|
46864
|
+
*
|
|
46865
|
+
* Content may contain Perseus widgets, that looks like this: '[[☃ Radio 1]]'.
|
|
46866
|
+
* This function replaces those widgets with its equivalent string.
|
|
46867
|
+
*
|
|
46868
|
+
* @param {string} content
|
|
46869
|
+
* @param {PerseusRenderer["widgets"]} widgets
|
|
46870
|
+
* @returns
|
|
46871
|
+
*/
|
|
46872
|
+
function injectWidgets(content, widgets) {
|
|
46873
|
+
// The types for taskProgress.itemData are not well defined,
|
|
46874
|
+
// so there is a chance that widgets or content could be undefined.
|
|
46875
|
+
// In that scenario, simply exit early and return the content as is.
|
|
46876
|
+
if (!content) {
|
|
46877
|
+
return "";
|
|
46878
|
+
}
|
|
46879
|
+
if (!widgets) {
|
|
46880
|
+
return content;
|
|
46881
|
+
}
|
|
46882
|
+
let context = content;
|
|
46883
|
+
wonderStuffCore.keys(widgets).forEach(widgetID => {
|
|
46884
|
+
const widget = widgets[widgetID];
|
|
46885
|
+
if (!widget.options) {
|
|
46886
|
+
return;
|
|
46887
|
+
}
|
|
46888
|
+
switch (widget.type) {
|
|
46889
|
+
case "radio":
|
|
46890
|
+
// Replace radio with the radio options
|
|
46891
|
+
// '[[☃ Radio 1]]' -> 'option 1\noption 2\noption 3'
|
|
46892
|
+
const radio = widget;
|
|
46893
|
+
if (radio.options?.choices?.length) {
|
|
46894
|
+
let radioContext = radio.options.choices.map(choice => choice.content).join("\n");
|
|
46895
|
+
if (radio.options?.randomize) {
|
|
46896
|
+
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";
|
|
46897
|
+
}
|
|
46898
|
+
context = context.replace(`[[☃ ${widgetID}]]`, radioContext);
|
|
46899
|
+
}
|
|
46900
|
+
break;
|
|
46901
|
+
case "image":
|
|
46902
|
+
// Replace image with the image's alt text
|
|
46903
|
+
// '[[☃ Image 1]]' -> '<img id="Image 1" alt="The text for this image">'
|
|
46904
|
+
const image = widget;
|
|
46905
|
+
if (image.options?.alt) {
|
|
46906
|
+
context = context.replace(`[[☃ ${widgetID}]]`, `<img id="${widgetID}" alt="${image.options.alt}">`);
|
|
46907
|
+
}
|
|
46908
|
+
break;
|
|
46909
|
+
case "label-image":
|
|
46910
|
+
// Replace label-image with the label-image's alt text
|
|
46911
|
+
// '[[☃ 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]'
|
|
46912
|
+
const labelImage = widget;
|
|
46913
|
+
if (labelImage.options?.imageAlt) {
|
|
46914
|
+
context = context.replace(`[[☃ ${widgetID}]]`, `[An image with dots that user needs to label. Label choices: [${labelImage.options.choices.join(", ")}]. Image alt text: ${labelImage.options?.imageAlt ?? ""}]`);
|
|
46915
|
+
}
|
|
46916
|
+
break;
|
|
46917
|
+
case "explanation":
|
|
46918
|
+
// Replace explaintion with the explaination text
|
|
46919
|
+
// Note: explainations can have widgets inside of them too
|
|
46920
|
+
// '[[☃ Explanation 1]]' -> 'This is the hidden explanation text'
|
|
46921
|
+
const explanation = widget;
|
|
46922
|
+
if (explanation.options?.explanation) {
|
|
46923
|
+
context = context.replace(`[[☃ ${widgetID}]]`, injectWidgets(explanation.options.explanation, explanation.options.widgets));
|
|
46924
|
+
}
|
|
46925
|
+
break;
|
|
46926
|
+
case "passage":
|
|
46927
|
+
// Replace passage with the passage text
|
|
46928
|
+
// '[[☃ Passage 1]]' -> '# Passage Title\n\nPassage text'
|
|
46929
|
+
const passage = widget;
|
|
46930
|
+
if (passage.options?.passageTitle || passage.options?.passageText) {
|
|
46931
|
+
const {
|
|
46932
|
+
passageTitle,
|
|
46933
|
+
passageText
|
|
46934
|
+
} = passage.options;
|
|
46935
|
+
context = context.replace(`[[☃ ${widgetID}]]`, `# ${passageTitle}\n\n${passageText}`);
|
|
46936
|
+
}
|
|
46937
|
+
break;
|
|
46938
|
+
case "group":
|
|
46939
|
+
case "graded-group":
|
|
46940
|
+
// Replace group with the group's nested content
|
|
46941
|
+
// '[[☃ Group 1]]' -> 'Nested question\n[[☃ Radio 1]]'
|
|
46942
|
+
// -> 'Nested question\noption 1\noption 2\noption 3'
|
|
46943
|
+
const group = widget;
|
|
46944
|
+
if (group.options?.widgets && group.options.content) {
|
|
46945
|
+
context = context.replace(`[[☃ ${widgetID}]]`, injectWidgets(group.options.content, group.options.widgets));
|
|
46946
|
+
}
|
|
46947
|
+
break;
|
|
46948
|
+
case "graded-group-set":
|
|
46949
|
+
// Replace graded-group-set with the the nested groups
|
|
46950
|
+
// '[[☃ Graded Group Set 1]]'
|
|
46951
|
+
// -> 'Nested question 1\noption 1\noption 2\noption 3\nNested question 2\noption 1\noption 2\noption 3'
|
|
46952
|
+
const gradedGroup = widget;
|
|
46953
|
+
if (gradedGroup.options?.gradedGroups) {
|
|
46954
|
+
const gradedGroups = gradedGroup.options.gradedGroups;
|
|
46955
|
+
const gradedGroupsContent = gradedGroups.reduce((acc, group) => {
|
|
46956
|
+
if (group.widgets && group.content) {
|
|
46957
|
+
acc += injectWidgets(group.content, group.widgets) + "\n";
|
|
46958
|
+
}
|
|
46959
|
+
return acc;
|
|
46960
|
+
}, "");
|
|
46961
|
+
context = context.replace(`[[☃ ${widgetID}]]`, gradedGroupsContent);
|
|
46962
|
+
}
|
|
46963
|
+
break;
|
|
46964
|
+
case "categorizer":
|
|
46965
|
+
// Replace categorizer with the options and categories
|
|
46966
|
+
// '[[☃ Categorizer 1]]' -> '''
|
|
46967
|
+
// For each item, select the correct category. Categories: category 1, category 2, category 3.
|
|
46968
|
+
// Items:
|
|
46969
|
+
// item 1
|
|
46970
|
+
// item 2
|
|
46971
|
+
// '''
|
|
46972
|
+
const categorizer = widget;
|
|
46973
|
+
if (categorizer.options?.categories && categorizer.options.items) {
|
|
46974
|
+
const categories = categorizer.options.categories;
|
|
46975
|
+
const items = categorizer.options.items;
|
|
46976
|
+
context = context.replace(`[[☃ ${widgetID}]]`, `For each item, select the correct category. Categories: ${categories.join(", ")}.\nItems:\n${items.join("\n")}\n`);
|
|
46977
|
+
}
|
|
46978
|
+
break;
|
|
46979
|
+
case "dropdown":
|
|
46980
|
+
// Replace dropdown with the dropdown options
|
|
46981
|
+
// '[[☃ Dropdown 1]]' -> '[option 1 | option 2 | option 3]'
|
|
46982
|
+
const dropdown = widget;
|
|
46983
|
+
if (dropdown.options?.choices) {
|
|
46984
|
+
const choices = dropdown.options.choices.map(choice => choice.content);
|
|
46985
|
+
context = context.replace(`[[☃ ${widgetID}]]`, `[${choices.join(" | ")}]`);
|
|
46986
|
+
}
|
|
46987
|
+
break;
|
|
46988
|
+
case "definition":
|
|
46989
|
+
// Replace definition with the word that is being defined
|
|
46990
|
+
// NOTE(chase): this does not include the definition itself,
|
|
46991
|
+
// because I believe the AI can infer that without wasting extra tokens
|
|
46992
|
+
// '[[☃ Definition 1]]' -> 'word'
|
|
46993
|
+
const definition = widget;
|
|
46994
|
+
if (definition.options?.togglePrompt) {
|
|
46995
|
+
context = context.replace(`[[☃ ${widgetID}]]`, definition.options.togglePrompt);
|
|
46996
|
+
}
|
|
46997
|
+
break;
|
|
46998
|
+
case "orderer":
|
|
46999
|
+
// Replace orderer with the orderer options
|
|
47000
|
+
// '[[☃ Orderer 1]]' -> 'option 1\noption 2\noption 3'
|
|
47001
|
+
const orderer = widget;
|
|
47002
|
+
if (orderer.options?.options) {
|
|
47003
|
+
context = context.replace(`[[☃ ${widgetID}]]`, orderer.options.options.map(option => option.content).join("\n"));
|
|
47004
|
+
}
|
|
47005
|
+
break;
|
|
47006
|
+
case "sorter":
|
|
47007
|
+
// Replace sorter with the options
|
|
47008
|
+
// NOTE(chase): These are already in the correct order
|
|
47009
|
+
// '[[☃ Sorter 1]]' -> '[option 1 | option 2 | option 3]'
|
|
47010
|
+
const sorter = widget;
|
|
47011
|
+
if (sorter.options?.correct) {
|
|
47012
|
+
const choices = sorter.options.correct;
|
|
47013
|
+
context = context.replace(`[[☃ ${widgetID}]]`, `[${choices.join(" | ")}]`);
|
|
47014
|
+
}
|
|
47015
|
+
break;
|
|
47016
|
+
case "interactive-graph":
|
|
47017
|
+
// Replace interactive-graph with the graph's range
|
|
47018
|
+
// '[[☃ Interactive Graph 1]]' -> '[Graph with an x range of -10 to 10 and y range of -10 to 10]'
|
|
47019
|
+
const interactiveGraph = widget;
|
|
47020
|
+
if (interactiveGraph.options?.range.length === 2) {
|
|
47021
|
+
const [x, y] = interactiveGraph.options.range;
|
|
47022
|
+
context = context.replace(`[[☃ ${widgetID}]]`, `[Graph with an x range of ${x[0]} to ${x[1]} and y range of ${y[0]} to ${y[1]}]`);
|
|
47023
|
+
}
|
|
47024
|
+
break;
|
|
47025
|
+
case "number-line":
|
|
47026
|
+
// Replace number-line with the number line's range, step, and initial position
|
|
47027
|
+
// '[[☃ Number Line 1]]' -> '[Number line with a range of -10 to 10, a step of 1, and an initial position of 0]'
|
|
47028
|
+
const numberLine = widget;
|
|
47029
|
+
if (numberLine.options?.range.length === 2 && numberLine.options?.tickStep && numberLine.options?.initialX) {
|
|
47030
|
+
const [min, max] = numberLine.options.range;
|
|
47031
|
+
const step = numberLine.options.tickStep;
|
|
47032
|
+
const initialPosition = numberLine.options.initialX;
|
|
47033
|
+
context = context.replace(`[[☃ ${widgetID}]]`, `[Number line with a range of ${min} to ${max}, a step of ${step}, and an initial position of ${initialPosition}]`);
|
|
47034
|
+
}
|
|
47035
|
+
break;
|
|
47036
|
+
case "matrix":
|
|
47037
|
+
// Replace matrix with the matrix's dimensions
|
|
47038
|
+
// '[[☃ Matrix 1]]' -> '[Matrix with 2 rows and 3 columns. The user can click on each cell to enter a value]'
|
|
47039
|
+
const matrix = widget;
|
|
47040
|
+
if (matrix.options?.matrixBoardSize.length === 2) {
|
|
47041
|
+
const [rows, columns] = matrix.options.matrixBoardSize;
|
|
47042
|
+
context = context.replace(`[[☃ ${widgetID}]]`, `[Matrix with ${rows} rows and ${columns} columns. The user can click on each cell to enter a value]`);
|
|
47043
|
+
}
|
|
47044
|
+
break;
|
|
47045
|
+
case "matcher":
|
|
47046
|
+
// Replace matcher with a table representing the left and right columns
|
|
47047
|
+
// '[[☃ Matcher 1]]' -> '| Left Column | Right Column |\n| --- | --- |\n| option 1 | option 2 |\n| option 3 | option 4 |'
|
|
47048
|
+
const matcher = widget;
|
|
47049
|
+
if (matcher.options?.left && matcher.options?.right) {
|
|
47050
|
+
const {
|
|
47051
|
+
left,
|
|
47052
|
+
right
|
|
47053
|
+
} = matcher.options;
|
|
47054
|
+
const [leftHeader, rightHeader] = matcher.options.labels;
|
|
47055
|
+
const tableHeader = `| ${leftHeader} | ${rightHeader} |\n| --- | --- |`;
|
|
47056
|
+
const tableRows = left.map((leftItem, index) => {
|
|
47057
|
+
return `| ${leftItem} | ${right[index]} |`;
|
|
47058
|
+
});
|
|
47059
|
+
const table = [tableHeader, ...tableRows].join("\n");
|
|
47060
|
+
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.";
|
|
47061
|
+
context = context.replace(`[[☃ ${widgetID}]]`, `${matcherWidgetExplanation}\n${table}`);
|
|
47062
|
+
}
|
|
47063
|
+
break;
|
|
47064
|
+
case "numeric-input":
|
|
47065
|
+
case "input-number":
|
|
47066
|
+
case "expression":
|
|
47067
|
+
// These widgets are just input fields, so we replace them with a ?
|
|
47068
|
+
// 'Solve 2+2 [[☃ Numeric Input 1]]' -> 'Solve 2+2 ?'
|
|
47069
|
+
// (we've found that using "?" gives the best results on fill-
|
|
47070
|
+
// in-the-blank questions).
|
|
47071
|
+
context = context.replace(`[[☃ ${widgetID}]]`, "?");
|
|
47072
|
+
break;
|
|
47073
|
+
default:
|
|
47074
|
+
// Tell the bot that it doesn't understand this widget
|
|
47075
|
+
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.]]`);
|
|
47076
|
+
}
|
|
47077
|
+
});
|
|
47078
|
+
return context;
|
|
47079
|
+
}
|
|
47080
|
+
|
|
47081
|
+
/**
|
|
47082
|
+
* Helper to parse Perseus JSON
|
|
47083
|
+
* Why not just use JSON.parse? We want:
|
|
47084
|
+
* - To make sure types are correct
|
|
47085
|
+
* - To give us a central place to validate/transform output if needed
|
|
47086
|
+
* @param {string} input - the stringified Perseus JSON
|
|
47087
|
+
* @returns {PerseusItem} the parsed PerseusItem object
|
|
47088
|
+
*/
|
|
47089
|
+
function parsePerseusJSON(input) {
|
|
47090
|
+
return JSON.parse(input);
|
|
47091
|
+
}
|
|
47092
|
+
|
|
46729
47093
|
const EditorJsonify = {
|
|
46730
47094
|
serialize: function () {
|
|
46731
47095
|
// Omit props that get passed to all widgets
|
|
@@ -46783,10 +47147,12 @@ exports.buildMapper = buildMapper;
|
|
|
46783
47147
|
exports.components = components;
|
|
46784
47148
|
exports.constants = constants;
|
|
46785
47149
|
exports.displaySigFigs = displaySigFigs;
|
|
47150
|
+
exports.getAnswersFromWidgets = getAnswersFromWidgets;
|
|
46786
47151
|
exports.globalConstants = globalConstants;
|
|
46787
47152
|
exports.globalStyles = globalStyles;
|
|
46788
47153
|
exports.icons = iconPaths;
|
|
46789
47154
|
exports.init = init;
|
|
47155
|
+
exports.injectWidgets = injectWidgets;
|
|
46790
47156
|
exports.interactiveSizes = interactiveSizes$1;
|
|
46791
47157
|
exports.isItemRenderableByVersion = isItemRenderableByVersion;
|
|
46792
47158
|
exports.itemDataVersion = itemDataVersion;
|
|
@@ -46794,6 +47160,7 @@ exports.itemToTree = itemToTree;
|
|
|
46794
47160
|
exports.itemVersion = ItemVersion;
|
|
46795
47161
|
exports.libVersion = libVersion;
|
|
46796
47162
|
exports.mediaQueries = mediaQueries;
|
|
47163
|
+
exports.parsePerseusJSON = parsePerseusJSON;
|
|
46797
47164
|
exports.plotterPlotTypes = plotterPlotTypes;
|
|
46798
47165
|
exports.preprocessTex = preprocessTex;
|
|
46799
47166
|
exports.registerAllWidgetsForTesting = registerAllWidgetsForTesting;
|