@khanacademy/perseus 19.0.0 → 19.1.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 +37 -0
- package/dist/es/index.css +209 -104
- package/dist/es/index.js +806 -877
- package/dist/es/index.js.map +1 -1
- package/dist/index.css +209 -104
- package/dist/index.d.ts +3 -2
- package/dist/index.js +849 -920
- package/dist/index.js.map +1 -1
- package/dist/mixins/changeable.d.ts +1 -1
- package/dist/perseus-api.d.ts +1 -0
- package/dist/renderer.d.ts +2 -4
- package/dist/server-item-renderer.d.ts +1 -0
- package/dist/types.d.ts +4 -0
- package/dist/util/geometry.d.ts +2 -0
- package/dist/util/graphie.d.ts +107 -1
- package/dist/util/math.d.ts +2 -0
- package/dist/util/parse-perseus-json.d.ts +3 -3
- package/dist/widgets/expression.d.ts +6 -0
- package/dist/widgets/label-image.d.ts +2 -0
- package/package.json +3 -4
- package/src/__testdata__/renderer.testdata.ts +152 -0
- package/src/__tests__/renderer.test.tsx +17 -0
- package/src/components/math-input.tsx +4 -6
- package/src/index.ts +8 -3
- package/src/mixins/changeable.ts +1 -1
- package/src/renderer.tsx +14 -8
- package/src/types.ts +9 -0
- package/src/util/geometry.test.ts +75 -0
- package/src/util/geometry.ts +19 -0
- package/src/util/graphie.test.ts +1291 -0
- package/src/util/graphie.ts +832 -784
- package/src/util/interactive.ts +23 -51
- package/src/util/math.test.ts +11 -0
- package/src/util/math.ts +8 -0
- package/src/util/parse-perseus-json.test.ts +3 -3
- package/src/util/parse-perseus-json.ts +4 -4
- package/src/widgets/__tests__/__snapshots__/input-number.test.ts.snap +4 -2
- package/src/widgets/__tests__/__snapshots__/numeric-input.test.ts.snap +4 -2
- package/src/widgets/__tests__/expression-mobile.test.tsx +8 -5
- package/src/widgets/expression.tsx +1 -23
- package/src/widgets/label-image.tsx +0 -1
- package/tsconfig-build.tsbuildinfo +1 -1
package/dist/index.js
CHANGED
|
@@ -20,6 +20,7 @@ var $ = require('jquery');
|
|
|
20
20
|
var PropTypes = require('prop-types');
|
|
21
21
|
var ReactDOM = require('react-dom');
|
|
22
22
|
var kmath = require('@khanacademy/kmath');
|
|
23
|
+
var wonderStuffCore = require('@khanacademy/wonder-stuff-core');
|
|
23
24
|
var wonderBlocksProgressSpinner = require('@khanacademy/wonder-blocks-progress-spinner');
|
|
24
25
|
var SimpleMarkdown = require('@khanacademy/simple-markdown');
|
|
25
26
|
var pureMarkdown = require('@khanacademy/pure-markdown');
|
|
@@ -36,7 +37,6 @@ var reactPopper = require('react-popper');
|
|
|
36
37
|
var createReactClass = require('create-react-class');
|
|
37
38
|
var Link = require('@khanacademy/wonder-blocks-link');
|
|
38
39
|
var perseusCore = require('@khanacademy/perseus-core');
|
|
39
|
-
var wonderStuffCore = require('@khanacademy/wonder-stuff-core');
|
|
40
40
|
|
|
41
41
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
42
42
|
|
|
@@ -194,9 +194,9 @@ class MathInput extends React__namespace.Component {
|
|
|
194
194
|
// We do this by modifying internal MathQuill state
|
|
195
195
|
// directly, instead of waiting for `.latex()` to be
|
|
196
196
|
// called in `componentDidUpdate()`.
|
|
197
|
-
const left = mathField.
|
|
197
|
+
const left = mathField.cursor()[mathInput.mathQuillInstance.L];
|
|
198
198
|
if (left && left.ctrlSeq === "\\cdot ") {
|
|
199
|
-
mathField.
|
|
199
|
+
mathField.controller().backspace();
|
|
200
200
|
mathField.cmd("\\times");
|
|
201
201
|
}
|
|
202
202
|
} else {
|
|
@@ -1227,6 +1227,12 @@ const KhanMath = {
|
|
|
1227
1227
|
return String(number);
|
|
1228
1228
|
}
|
|
1229
1229
|
};
|
|
1230
|
+
function sum$1(array) {
|
|
1231
|
+
return array.reduce(add$3, 0);
|
|
1232
|
+
}
|
|
1233
|
+
function add$3(a, b) {
|
|
1234
|
+
return a + b;
|
|
1235
|
+
}
|
|
1230
1236
|
|
|
1231
1237
|
/* eslint-disable no-useless-escape */
|
|
1232
1238
|
const MAXERROR_EPSILON = Math.pow(2, -42);
|
|
@@ -1919,27 +1925,6 @@ const KhanAnswerTypes = {
|
|
|
1919
1925
|
|
|
1920
1926
|
const ERROR_TITLE = i18n__namespace._("Oops!");
|
|
1921
1927
|
const ERROR_MESSAGE = i18n__namespace._("Sorry, I don't understand that!");
|
|
1922
|
-
const insertBraces = value => {
|
|
1923
|
-
// HACK(alex): Make sure that all LaTeX super/subscripts are wrapped
|
|
1924
|
-
// in curly braces to avoid the mismatch between KAS and LaTeX sup/sub
|
|
1925
|
-
// parsing.
|
|
1926
|
-
//
|
|
1927
|
-
// What exactly is this mismatch? Due to its heritage of parsing plain
|
|
1928
|
-
// text math from <OldExpression />, KAS parses "x^12" as x^(12).
|
|
1929
|
-
// This is both generally what the user expects to happen, and is
|
|
1930
|
-
// consistent with other computer algebra systems. It is NOT
|
|
1931
|
-
// consistent with LaTeX however, where x^12 is equivalent to x^{1}2.
|
|
1932
|
-
//
|
|
1933
|
-
// Since the only LaTeX we parse comes from MathQuill, this wouldn't
|
|
1934
|
-
// be a problem if MathQuill just always gave us the latter version
|
|
1935
|
-
// (with explicit braces). However, instead it always gives the former.
|
|
1936
|
-
// This behavior is baked in pretty deep; my naive attempts at changing
|
|
1937
|
-
// it triggered all sorts of confusing errors. So instead we just make
|
|
1938
|
-
// sure to add in any missing braces before grading MathQuill input.
|
|
1939
|
-
//
|
|
1940
|
-
// TODO(alex): Properly hack MathQuill to always use explicit braces.
|
|
1941
|
-
return value.replace(/([_^])([^{])/g, "$1{$2}");
|
|
1942
|
-
};
|
|
1943
1928
|
const anglicizeOperators = tex => {
|
|
1944
1929
|
// sen is used instead of sin in some languages, e.g. Portuguese.
|
|
1945
1930
|
// To ensure that answers in various languages are graded correctly, we
|
|
@@ -1947,7 +1932,7 @@ const anglicizeOperators = tex => {
|
|
|
1947
1932
|
return tex.replace(/\\operatorname{sen}/g, "\\sin ");
|
|
1948
1933
|
};
|
|
1949
1934
|
const normalizeTex = tex => {
|
|
1950
|
-
return anglicizeOperators(
|
|
1935
|
+
return anglicizeOperators(tex);
|
|
1951
1936
|
};
|
|
1952
1937
|
const deriveKeypadVersion = apiOptions => {
|
|
1953
1938
|
// We can derive which version of the keypad is in use. This is
|
|
@@ -8708,54 +8693,6 @@ function polar(r, th) {
|
|
|
8708
8693
|
th = th * Math.PI / 180;
|
|
8709
8694
|
return [r[0] * Math.cos(th), r[1] * Math.sin(th)];
|
|
8710
8695
|
}
|
|
8711
|
-
const GraphUtils = {
|
|
8712
|
-
unscaledSvgPath: function (points) {
|
|
8713
|
-
// If this is an empty closed path, return "" instead of "z", which
|
|
8714
|
-
// would give an error
|
|
8715
|
-
if (points[0] === true) {
|
|
8716
|
-
return "";
|
|
8717
|
-
}
|
|
8718
|
-
return $__default["default"].map(points, function (point, i) {
|
|
8719
|
-
if (point === true) {
|
|
8720
|
-
return "z";
|
|
8721
|
-
}
|
|
8722
|
-
return (i === 0 ? "M" : "L") + point[0] + " " + point[1];
|
|
8723
|
-
}).join("");
|
|
8724
|
-
},
|
|
8725
|
-
getDistance: function (point1, point2) {
|
|
8726
|
-
return kmath.point.distanceToPoint(point1, point2);
|
|
8727
|
-
},
|
|
8728
|
-
/**
|
|
8729
|
-
* Return the difference between two sets of coordinates
|
|
8730
|
-
*/
|
|
8731
|
-
coordDiff: function (startCoord, endCoord) {
|
|
8732
|
-
return ___default["default"].map(endCoord, function (val, i) {
|
|
8733
|
-
return endCoord[i] - startCoord[i];
|
|
8734
|
-
});
|
|
8735
|
-
},
|
|
8736
|
-
/**
|
|
8737
|
-
* Round the given coordinates to a given snap value
|
|
8738
|
-
* (e.g., nearest 0.2 increment)
|
|
8739
|
-
*/
|
|
8740
|
-
snapCoord: function (coord, snap) {
|
|
8741
|
-
return ___default["default"].map(coord, function (val, i) {
|
|
8742
|
-
return KhanMath.roundToNearest(snap[i], val);
|
|
8743
|
-
});
|
|
8744
|
-
},
|
|
8745
|
-
// Find the angle in degrees between two or three points
|
|
8746
|
-
findAngle: function (point1, point2, vertex) {
|
|
8747
|
-
if (vertex === undefined) {
|
|
8748
|
-
const x = point1[0] - point2[0];
|
|
8749
|
-
const y = point1[1] - point2[1];
|
|
8750
|
-
if (!x && !y) {
|
|
8751
|
-
return 0;
|
|
8752
|
-
}
|
|
8753
|
-
return (180 + Math.atan2(-y, -x) * 180 / Math.PI + 360) % 360;
|
|
8754
|
-
}
|
|
8755
|
-
return GraphUtils.findAngle(point1, vertex) - GraphUtils.findAngle(point2, vertex);
|
|
8756
|
-
},
|
|
8757
|
-
graphs: {}
|
|
8758
|
-
};
|
|
8759
8696
|
class Graphie$1 {
|
|
8760
8697
|
#bounds;
|
|
8761
8698
|
#drawingTransform;
|
|
@@ -8797,7 +8734,7 @@ class Graphie$1 {
|
|
|
8797
8734
|
this.dimensions = [w, h];
|
|
8798
8735
|
this.xpixels = w;
|
|
8799
8736
|
this.ypixels = h;
|
|
8800
|
-
this.isMobile = options.isMobile;
|
|
8737
|
+
this.isMobile = options.isMobile ?? false;
|
|
8801
8738
|
return this;
|
|
8802
8739
|
}
|
|
8803
8740
|
|
|
@@ -8815,33 +8752,24 @@ class Graphie$1 {
|
|
|
8815
8752
|
// - xLabelFormat: fn to format label string for x-axis
|
|
8816
8753
|
graphInit(options) {
|
|
8817
8754
|
options = options || {};
|
|
8818
|
-
|
|
8755
|
+
for (const [prop, val] of wonderStuffCore.entries(options)) {
|
|
8819
8756
|
// allow options to be specified by a single number for shorthand if
|
|
8820
8757
|
// 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") {
|
|
8758
|
+
if (!prop.match(/.*Opacity$/) && prop !== "range" && typeof val === "number") {
|
|
8824
8759
|
options[prop] = [val, val];
|
|
8825
8760
|
}
|
|
8826
8761
|
|
|
8827
8762
|
// allow symmetric ranges to be specified by the absolute values
|
|
8828
8763
|
if (prop === "range" || prop === "gridRange") {
|
|
8829
|
-
|
|
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
|
-
}
|
|
8764
|
+
options[prop] = normalizeRange(options[prop]);
|
|
8837
8765
|
}
|
|
8838
|
-
}
|
|
8839
|
-
const range = options.range || [[-10, 10], [-10, 10]];
|
|
8840
|
-
const gridRange = options.gridRange ||
|
|
8766
|
+
}
|
|
8767
|
+
const range = normalizeRange(options.range || [[-10, 10], [-10, 10]]);
|
|
8768
|
+
const gridRange = normalizeRange(options.gridRange || range);
|
|
8841
8769
|
const scale = options.scale || [20, 20];
|
|
8842
8770
|
const grid = options.grid != null ? options.grid : true;
|
|
8843
8771
|
const gridOpacity = options.gridOpacity || 0.1;
|
|
8844
|
-
const gridStep = options.gridStep || [1, 1];
|
|
8772
|
+
const gridStep = toPair(options.gridStep || [1, 1]);
|
|
8845
8773
|
const axes = options.axes != null ? options.axes : true;
|
|
8846
8774
|
const axisArrows = options.axisArrows || "";
|
|
8847
8775
|
const axisOpacity = options.axisOpacity || 1.0;
|
|
@@ -8861,7 +8789,7 @@ class Graphie$1 {
|
|
|
8861
8789
|
let xLabelFormat = options.xLabelFormat || labelFormat;
|
|
8862
8790
|
let yLabelFormat = options.yLabelFormat || labelFormat;
|
|
8863
8791
|
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 (!
|
|
8792
|
+
if (!Array.isArray(unityLabels)) {
|
|
8865
8793
|
unityLabels = [unityLabels, unityLabels];
|
|
8866
8794
|
}
|
|
8867
8795
|
const minusIgnorer = function (lf) {
|
|
@@ -8936,7 +8864,7 @@ class Graphie$1 {
|
|
|
8936
8864
|
stroke: options.isMobile ? KhanColors.GRAY_G : "#000000",
|
|
8937
8865
|
opacity: options.isMobile ? 1 : tickOpacity,
|
|
8938
8866
|
strokeWidth: 1
|
|
8939
|
-
},
|
|
8867
|
+
}, () => {
|
|
8940
8868
|
// horizontal axis
|
|
8941
8869
|
let step = gridStep[0] * tickStep[0];
|
|
8942
8870
|
let len = tickLen[0] / scale[1];
|
|
@@ -8963,13 +8891,11 @@ class Graphie$1 {
|
|
|
8963
8891
|
if (range[0][0] < 0 && range[0][1] > 0) {
|
|
8964
8892
|
for (let y = step + axisCenter[1]; y <= stop; y += step) {
|
|
8965
8893
|
if (y < stop || !axisArrows) {
|
|
8966
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
8967
8894
|
this.line([-len + axisCenter[0], y], [halfWidthTicks ? 0 : len + axisCenter[0], y]);
|
|
8968
8895
|
}
|
|
8969
8896
|
}
|
|
8970
8897
|
for (let y = -step + axisCenter[1]; y >= start; y -= step) {
|
|
8971
8898
|
if (y > start || !axisArrows) {
|
|
8972
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
8973
8899
|
this.line([-len + axisCenter[0], y], [halfWidthTicks ? 0 : len + axisCenter[0], y]);
|
|
8974
8900
|
}
|
|
8975
8901
|
}
|
|
@@ -9045,369 +8971,151 @@ class Graphie$1 {
|
|
|
9045
8971
|
const processed = this.processAttributes(attrs);
|
|
9046
8972
|
if (typeof fn === "function") {
|
|
9047
8973
|
const oldStyle = this.currentStyle;
|
|
9048
|
-
this.currentStyle =
|
|
8974
|
+
this.currentStyle = {
|
|
8975
|
+
...this.currentStyle,
|
|
8976
|
+
...processed
|
|
8977
|
+
};
|
|
9049
8978
|
const result = fn.call(this);
|
|
9050
8979
|
this.currentStyle = oldStyle;
|
|
9051
8980
|
return result;
|
|
9052
8981
|
}
|
|
9053
|
-
|
|
8982
|
+
Object.assign(this.currentStyle, processed);
|
|
9054
8983
|
}
|
|
9055
|
-
|
|
9056
|
-
|
|
9057
|
-
|
|
9058
|
-
|
|
9059
|
-
|
|
9060
|
-
|
|
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) => {
|
|
9067
|
-
if (point === true) {
|
|
9068
|
-
return "z";
|
|
9069
|
-
}
|
|
9070
|
-
const scaled = alreadyScaled ? point : this.scalePoint(point);
|
|
9071
|
-
return (i === 0 ? "M" : "L") + KhanMath.bound(scaled[0]) + " " + KhanMath.bound(scaled[1]);
|
|
9072
|
-
}).join("");
|
|
9073
|
-
};
|
|
9074
|
-
svgParabolaPath = (a, b, c) => {
|
|
9075
|
-
const computeParabola = function (x) {
|
|
9076
|
-
return (a * x + b) * x + c;
|
|
9077
|
-
};
|
|
9078
|
-
|
|
9079
|
-
// If points are collinear, plot a line instead
|
|
9080
|
-
if (a === 0) {
|
|
9081
|
-
const points = [[this.bounds().xMin, computeParabola(this.bounds().xMin)], [this.bounds().xMax, computeParabola(this.bounds().xMax)]];
|
|
9082
|
-
// @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
|
|
9083
|
-
return this.svgPath(points);
|
|
9084
|
-
}
|
|
9085
|
-
|
|
9086
|
-
// Calculate x coordinates of points on parabola
|
|
9087
|
-
const xVertex = -b / (2 * a);
|
|
9088
|
-
const distToEdge = Math.max(Math.abs(xVertex - this.bounds().xMin), Math.abs(xVertex - this.bounds().xMax));
|
|
9089
|
-
|
|
9090
|
-
// To guarantee that drawn parabola to spans the viewport, use a point
|
|
9091
|
-
// on the edge of the graph furtherest from the vertex
|
|
9092
|
-
const xPoint = xVertex + distToEdge;
|
|
9093
|
-
|
|
9094
|
-
// Compute parabola and other point on the curve
|
|
9095
|
-
const vertex = [xVertex, computeParabola(xVertex)];
|
|
9096
|
-
const point = [xPoint, computeParabola(xPoint)];
|
|
9097
|
-
|
|
9098
|
-
// Calculate SVG 'control' point, defined by spec
|
|
9099
|
-
const control = [vertex[0], vertex[1] - (point[1] - vertex[1])];
|
|
9100
|
-
|
|
9101
|
-
// Calculate mirror points across parabola's axis of symmetry
|
|
9102
|
-
const dx = Math.abs(vertex[0] - point[0]);
|
|
9103
|
-
const left = [vertex[0] - dx, point[1]];
|
|
9104
|
-
const right = [vertex[0] + dx, point[1]];
|
|
9105
|
-
|
|
9106
|
-
// Scale and bound
|
|
9107
|
-
// @ts-expect-error - TS2345 - Argument of type '(point: number | Coord) => Coord' is not assignable to parameter of type 'Iteratee<any[][], any, any[]>'.
|
|
9108
|
-
const points = ___default["default"].map([left, control, right], this.scalePoint);
|
|
9109
|
-
const values = ___default["default"].map(___default["default"].flatten(points), KhanMath.bound);
|
|
9110
|
-
return "M" + values[0] + "," + values[1] + " Q" + values[2] + "," + values[3] + " " + values[4] + "," + values[5];
|
|
9111
|
-
};
|
|
9112
|
-
svgSinusoidPath = (a, b, c, d) => {
|
|
9113
|
-
// Plot a sinusoid of the form: f(x) = a * sin(b * x - c) + d
|
|
9114
|
-
const quarterPeriod = Math.abs(Math.PI / (2 * b));
|
|
9115
|
-
const computeSine = function (x) {
|
|
9116
|
-
return a * Math.sin(b * x - c) + d;
|
|
9117
|
-
};
|
|
9118
|
-
const computeDerivative = function (x) {
|
|
9119
|
-
return a * b * Math.cos(c - b * x);
|
|
9120
|
-
};
|
|
9121
|
-
const coordsForOffset = (initial, i) => {
|
|
9122
|
-
// Return the cubic coordinates (including the two anchor and two
|
|
9123
|
-
// control points) for the ith portion of the sinusoid.
|
|
9124
|
-
const x0 = initial + quarterPeriod * i;
|
|
9125
|
-
const x1 = x0 + quarterPeriod;
|
|
9126
|
-
|
|
9127
|
-
// Interpolate using derivative technique
|
|
9128
|
-
// See: http://stackoverflow.com/questions/13932704/how-to-draw-sine-waves-with-svg-js
|
|
9129
|
-
const xCoords = [x0, x0 * 2 / 3 + x1 * 1 / 3, x0 * 1 / 3 + x1 * 2 / 3, x1];
|
|
9130
|
-
const yCoords = [computeSine(x0), computeSine(x0) + computeDerivative(x0) * (x1 - x0) / 3, computeSine(x1) - computeDerivative(x1) * (x1 - x0) / 3, computeSine(x1)];
|
|
9131
|
-
|
|
9132
|
-
// Zip and scale
|
|
9133
|
-
// @ts-expect-error - TS2345 - Argument of type '(point: number | Coord) => Coord' is not assignable to parameter of type 'Iteratee<any[][], any, any[]>'.
|
|
9134
|
-
return ___default["default"].map(___default["default"].zip(xCoords, yCoords), this.scalePoint);
|
|
9135
|
-
};
|
|
9136
|
-
|
|
9137
|
-
// How many quarter-periods do we need to span the graph?
|
|
9138
|
-
const extent = this.bounds().width();
|
|
9139
|
-
const numQuarterPeriods = Math.ceil(extent / quarterPeriod) + 1;
|
|
9140
|
-
|
|
9141
|
-
// Find starting coordinate: first anchor point curve left of bounds.xMin
|
|
9142
|
-
let initial = c / b;
|
|
9143
|
-
const distToEdge = initial - this.bounds().xMin;
|
|
9144
|
-
initial -= quarterPeriod * Math.ceil(distToEdge / quarterPeriod);
|
|
9145
|
-
|
|
9146
|
-
// First portion of path is special-case, requiring move-to ('M')
|
|
9147
|
-
let coords = coordsForOffset(initial, 0);
|
|
9148
|
-
let path = "M" + coords[0][0] + "," + coords[0][1] + " C" + coords[1][0] + "," + coords[1][1] + " " + coords[2][0] + "," + coords[2][1] + " " + coords[3][0] + "," + coords[3][1];
|
|
9149
|
-
for (let i = 1; i < numQuarterPeriods; i++) {
|
|
9150
|
-
coords = coordsForOffset(initial, i);
|
|
9151
|
-
path += " C" + coords[1][0] + "," + coords[1][1] + " " + coords[2][0] + "," + coords[2][1] + " " + coords[3][0] + "," + coords[3][1];
|
|
9152
|
-
}
|
|
9153
|
-
return path;
|
|
9154
|
-
};
|
|
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) {
|
|
9168
|
-
const transformers = {
|
|
9169
|
-
scale: scale => {
|
|
9170
|
-
this.drawingTransform().setScale(scale);
|
|
9171
|
-
},
|
|
9172
|
-
clipRect: pair => {
|
|
9173
|
-
const point = pair[0];
|
|
9174
|
-
const size = pair[1];
|
|
9175
|
-
point[1] += size[1]; // because our coordinates are flipped
|
|
9176
|
-
|
|
9177
|
-
return {
|
|
9178
|
-
"clip-rect": this.scalePoint(point).concat(this.scaleVector(size)).join(" ")
|
|
9179
|
-
};
|
|
9180
|
-
},
|
|
9181
|
-
strokeWidth: function (val) {
|
|
9182
|
-
return {
|
|
9183
|
-
"stroke-width": parseFloat(val)
|
|
9184
|
-
};
|
|
9185
|
-
},
|
|
9186
|
-
rx: val => {
|
|
9187
|
-
return {
|
|
9188
|
-
rx: this.scaleVector([val, 0])[0]
|
|
9189
|
-
};
|
|
9190
|
-
},
|
|
9191
|
-
ry: val => {
|
|
9192
|
-
return {
|
|
9193
|
-
ry: this.scaleVector([0, val])[1]
|
|
9194
|
-
};
|
|
9195
|
-
},
|
|
9196
|
-
r: val => {
|
|
9197
|
-
const scaled = this.scaleVector([val, val]);
|
|
9198
|
-
return {
|
|
9199
|
-
rx: scaled[0],
|
|
9200
|
-
ry: scaled[1]
|
|
9201
|
-
};
|
|
8984
|
+
grid(xr, yr, style) {
|
|
8985
|
+
return this.withStyle(style, () => {
|
|
8986
|
+
const step = this.currentStyle.step || [1, 1];
|
|
8987
|
+
const set = this.raphael.set();
|
|
8988
|
+
let x = step[0] * Math.ceil(xr[0] / step[0]);
|
|
8989
|
+
for (; x <= xr[1]; x += step[0]) {
|
|
8990
|
+
set.push(this.line([x, yr[0]], [x, yr[1]]));
|
|
9202
8991
|
}
|
|
9203
|
-
|
|
9204
|
-
|
|
9205
|
-
|
|
9206
|
-
const transformer = transformers[key];
|
|
9207
|
-
if (typeof transformer === "function") {
|
|
9208
|
-
$__default["default"].extend(processed, transformer(value));
|
|
9209
|
-
} else {
|
|
9210
|
-
const dasherized = key
|
|
9211
|
-
// @ts-expect-error - TS2339 - Property 'replace' does not exist on type 'string | number | symbol'.
|
|
9212
|
-
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2").replace(/([a-z\d])([A-Z])/g, "$1-$2").toLowerCase();
|
|
9213
|
-
processed[dasherized] = value;
|
|
8992
|
+
let y = step[1] * Math.ceil(yr[0] / step[1]);
|
|
8993
|
+
for (; y <= yr[1]; y += step[1]) {
|
|
8994
|
+
set.push(this.line([xr[0], y], [xr[1], y]));
|
|
9214
8995
|
}
|
|
8996
|
+
return set;
|
|
9215
8997
|
});
|
|
9216
|
-
return processed;
|
|
9217
8998
|
}
|
|
9218
|
-
|
|
9219
|
-
|
|
9220
|
-
|
|
9221
|
-
|
|
9222
|
-
|
|
9223
|
-
|
|
9224
|
-
|
|
9225
|
-
|
|
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
|
-
};
|
|
8999
|
+
arc(center, radius, startAngle, endAngle, sector, style) {
|
|
9000
|
+
return this.withStyle(style, () => {
|
|
9001
|
+
startAngle = (startAngle % 360 + 360) % 360;
|
|
9002
|
+
endAngle = (endAngle % 360 + 360) % 360;
|
|
9003
|
+
const cent = this.scalePoint(center);
|
|
9004
|
+
const radii = this.scaleVector(radius);
|
|
9005
|
+
const startVector = polar(radius, startAngle);
|
|
9006
|
+
const endVector = polar(radius, endAngle);
|
|
9231
9007
|
|
|
9232
|
-
|
|
9233
|
-
|
|
9234
|
-
|
|
9235
|
-
|
|
9236
|
-
|
|
9237
|
-
|
|
9238
|
-
|
|
9239
|
-
|
|
9240
|
-
|
|
9241
|
-
|
|
9242
|
-
|
|
9243
|
-
|
|
9244
|
-
|
|
9245
|
-
|
|
9246
|
-
|
|
9247
|
-
|
|
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
|
-
}
|
|
9273
|
-
};
|
|
9274
|
-
|
|
9275
|
-
// `svgPath` is independent of graphie range, so we export it independently
|
|
9276
|
-
GraphUtils.svgPath = thisGraphie.svgPath;
|
|
9277
|
-
const addArrowheads = function arrows(path) {
|
|
9278
|
-
const type = path.constructor.prototype;
|
|
9279
|
-
if (type === raphael.exports.el) {
|
|
9280
|
-
if (path.type === "path" && typeof path.arrowheadsDrawn === "undefined") {
|
|
9281
|
-
const w = path.attr("stroke-width");
|
|
9282
|
-
const s = 0.6 + 0.4 * w;
|
|
9283
|
-
const l = path.getTotalLength();
|
|
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");
|
|
9286
|
-
const end = path.getPointAtLength(l - 0.4);
|
|
9287
|
-
const almostTheEnd = path.getPointAtLength(l - 0.75 * s);
|
|
9288
|
-
const angle = Math.atan2(end.y - almostTheEnd.y, end.x - almostTheEnd.x) * 180 / Math.PI;
|
|
9289
|
-
const attrs = path.attr();
|
|
9290
|
-
delete attrs.path;
|
|
9291
|
-
let subpath = path.getSubpath(0, l - 0.75 * s);
|
|
9292
|
-
subpath = thisGraphie.raphael.path(subpath).attr(attrs);
|
|
9293
|
-
subpath.arrowheadsDrawn = true;
|
|
9294
|
-
path.remove();
|
|
9295
|
-
|
|
9296
|
-
// For some unknown reason 0 doesn't work for the rotation
|
|
9297
|
-
// origin so we use a tiny number.
|
|
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({
|
|
9299
|
-
"stroke-linejoin": "round",
|
|
9300
|
-
"stroke-linecap": "round"
|
|
9301
|
-
});
|
|
9302
|
-
head.arrowheadsDrawn = true;
|
|
9303
|
-
set.push(subpath);
|
|
9304
|
-
set.push(head);
|
|
9305
|
-
return set;
|
|
9306
|
-
}
|
|
9307
|
-
} else if (type === raphael.exports.st) {
|
|
9308
|
-
for (let i = 0, l = path.items.length; i < l; i++) {
|
|
9309
|
-
arrows(path.items[i]);
|
|
9310
|
-
}
|
|
9311
|
-
}
|
|
9312
|
-
return path;
|
|
9313
|
-
};
|
|
9314
|
-
function circle(center, radius) {
|
|
9315
|
-
return thisGraphie.raphael.ellipse(...thisGraphie.scalePoint(center).concat(thisGraphie.scaleVector([radius, radius])));
|
|
9008
|
+
// We round the coordinates to make testing easier, because trig
|
|
9009
|
+
// operations return unround numbers.
|
|
9010
|
+
// TODO(benchristel): move rounding to scalePoint?
|
|
9011
|
+
const round = x => kmath.number.round(x, 6);
|
|
9012
|
+
const startPoint = this.scalePoint([round(center[0] + startVector[0]), round(center[1] + startVector[1])]);
|
|
9013
|
+
const endPoint = this.scalePoint([round(center[0] + endVector[0]), round(center[1] + endVector[1])]);
|
|
9014
|
+
const largeAngle = ((endAngle - startAngle) % 360 + 360) % 360 > 180;
|
|
9015
|
+
return this.raphael.path("M" + startPoint.join(" ") + "A" + radii.join(" ") + " 0 " + (
|
|
9016
|
+
// ellipse rotation
|
|
9017
|
+
largeAngle ? 1 : 0) + " 0 " +
|
|
9018
|
+
// sweep flag
|
|
9019
|
+
endPoint.join(" ") + (sector ? "L" + cent.join(" ") + "z" : ""));
|
|
9020
|
+
});
|
|
9021
|
+
}
|
|
9022
|
+
circle(center, radius, style) {
|
|
9023
|
+
return this.withStyle(style, () => this.raphael.ellipse(...this.scalePoint(center), ...this.scaleVector([radius, radius])));
|
|
9316
9024
|
}
|
|
9317
9025
|
|
|
9318
9026
|
// (x, y) is coordinate of bottom left corner
|
|
9319
|
-
|
|
9320
|
-
|
|
9321
|
-
|
|
9322
|
-
|
|
9323
|
-
|
|
9324
|
-
|
|
9325
|
-
|
|
9326
|
-
|
|
9327
|
-
|
|
9027
|
+
rect(x, y, width, height, style) {
|
|
9028
|
+
return this.withStyle(style, () => {
|
|
9029
|
+
// Raphael needs (x, y) to be coordinate of upper left corner
|
|
9030
|
+
const corner = this.scalePoint([x, y + height]);
|
|
9031
|
+
const dims = this.scaleVector([width, height]);
|
|
9032
|
+
const elem = this.raphael.rect(...corner.concat(dims));
|
|
9033
|
+
if (this.isMobile) {
|
|
9034
|
+
elem.node.style.shapeRendering = "crispEdges";
|
|
9035
|
+
}
|
|
9036
|
+
return elem;
|
|
9037
|
+
});
|
|
9328
9038
|
}
|
|
9329
|
-
|
|
9330
|
-
return
|
|
9039
|
+
ellipse(center, radii, style) {
|
|
9040
|
+
return this.withStyle(style, () => this.raphael.ellipse(...this.scalePoint(center).concat(this.scaleVector(radii))));
|
|
9331
9041
|
}
|
|
9332
|
-
|
|
9042
|
+
fixedEllipse(center,
|
|
9333
9043
|
// Different type than Coord, this is radiusX, radiusY
|
|
9334
|
-
radii, maxScale, padding) {
|
|
9335
|
-
|
|
9336
|
-
|
|
9337
|
-
|
|
9338
|
-
|
|
9339
|
-
|
|
9340
|
-
|
|
9341
|
-
|
|
9342
|
-
|
|
9343
|
-
|
|
9344
|
-
|
|
9345
|
-
|
|
9346
|
-
|
|
9347
|
-
|
|
9348
|
-
|
|
9349
|
-
|
|
9350
|
-
|
|
9351
|
-
|
|
9352
|
-
|
|
9353
|
-
|
|
9354
|
-
|
|
9044
|
+
radii, maxScale, padding, style) {
|
|
9045
|
+
return this.withStyle(style, () => {
|
|
9046
|
+
// Scale point and radius
|
|
9047
|
+
const scaledPoint = this.scalePoint(center);
|
|
9048
|
+
const scaledRadii = this.scaleVector(radii);
|
|
9049
|
+
const width = 2 * scaledRadii[0] * maxScale + padding;
|
|
9050
|
+
const height = 2 * scaledRadii[1] * maxScale + padding;
|
|
9051
|
+
|
|
9052
|
+
// Calculate absolute left, top
|
|
9053
|
+
const left = scaledPoint[0] - width / 2;
|
|
9054
|
+
const top = scaledPoint[1] - height / 2;
|
|
9055
|
+
|
|
9056
|
+
// Wrap in <div>
|
|
9057
|
+
const wrapper = document.createElement("div");
|
|
9058
|
+
$__default["default"](wrapper).css({
|
|
9059
|
+
position: "absolute",
|
|
9060
|
+
width: width + "px",
|
|
9061
|
+
height: height + "px",
|
|
9062
|
+
left: left + "px",
|
|
9063
|
+
top: top + "px"
|
|
9064
|
+
});
|
|
9065
|
+
// wrapper.setAttribute("data-graphie-type", "ellipse");
|
|
9355
9066
|
|
|
9356
|
-
|
|
9357
|
-
|
|
9358
|
-
|
|
9359
|
-
|
|
9360
|
-
|
|
9361
|
-
|
|
9362
|
-
|
|
9067
|
+
// Create Raphael canvas
|
|
9068
|
+
const localRaphael = raphael.exports(wrapper, width, height);
|
|
9069
|
+
const visibleShape = localRaphael.ellipse(width / 2, height / 2, scaledRadii[0], scaledRadii[1]);
|
|
9070
|
+
return {
|
|
9071
|
+
wrapper: wrapper,
|
|
9072
|
+
visibleShape: visibleShape
|
|
9073
|
+
};
|
|
9074
|
+
});
|
|
9363
9075
|
}
|
|
9364
|
-
|
|
9365
|
-
|
|
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));
|
|
9076
|
+
unstyledPath(points) {
|
|
9077
|
+
const p = this.raphael.path(this.svgPath(points));
|
|
9383
9078
|
p.graphiePath = points;
|
|
9384
9079
|
return p;
|
|
9385
9080
|
}
|
|
9386
|
-
|
|
9387
|
-
|
|
9388
|
-
|
|
9389
|
-
|
|
9390
|
-
|
|
9391
|
-
|
|
9392
|
-
|
|
9393
|
-
|
|
9081
|
+
path(points, style) {
|
|
9082
|
+
return this.withStyle(style, () => {
|
|
9083
|
+
return this.unstyledPath(points);
|
|
9084
|
+
});
|
|
9085
|
+
}
|
|
9086
|
+
|
|
9087
|
+
// fixedPath is a stub that gets overwritten with a function from
|
|
9088
|
+
// drawingTools in createGraphie
|
|
9089
|
+
fixedPath(points, center, createPath) {
|
|
9090
|
+
points = points.map(this.scalePoint);
|
|
9091
|
+
center = center ? this.scalePoint(center) : null;
|
|
9092
|
+
createPath = createPath || this.svgPath;
|
|
9093
|
+
|
|
9094
|
+
// Compute bounding box
|
|
9095
|
+
const xs = points.map(p => p[0]);
|
|
9096
|
+
const ys = points.map(p => p[1]);
|
|
9097
|
+
const pathLeft = Math.min(...xs);
|
|
9098
|
+
const pathRight = Math.max(...xs);
|
|
9099
|
+
const pathTop = Math.min(...ys);
|
|
9100
|
+
const pathBottom = Math.max(...ys);
|
|
9394
9101
|
|
|
9395
9102
|
// Apply padding to line
|
|
9396
9103
|
const padding = [4, 4];
|
|
9397
9104
|
|
|
9398
9105
|
// Calculate and apply additional offset
|
|
9399
|
-
const
|
|
9106
|
+
const topLeftOfBoundingBox = [pathLeft, pathTop];
|
|
9400
9107
|
|
|
9401
|
-
// Apply padding and offset to points
|
|
9402
|
-
|
|
9403
|
-
|
|
9108
|
+
// Apply padding and offset to points to convert from
|
|
9109
|
+
// canvas coordinates to pixel coordinates relative to bounding box
|
|
9110
|
+
points = points.map(function (point) {
|
|
9111
|
+
return kmath.vector.add(kmath.vector.subtract(point, topLeftOfBoundingBox), kmath.vector.scale(padding, 0.5));
|
|
9404
9112
|
});
|
|
9405
9113
|
|
|
9406
9114
|
// Calculate <div> dimensions
|
|
9407
9115
|
const width = pathRight - pathLeft + padding[0];
|
|
9408
9116
|
const height = pathBottom - pathTop + padding[1];
|
|
9409
|
-
const left =
|
|
9410
|
-
const top =
|
|
9117
|
+
const left = topLeftOfBoundingBox[0] - padding[0] / 2;
|
|
9118
|
+
const top = topLeftOfBoundingBox[1] - padding[1] / 2;
|
|
9411
9119
|
|
|
9412
9120
|
// Create <div>
|
|
9413
9121
|
const wrapper = document.createElement("div");
|
|
@@ -9433,29 +9141,32 @@ GraphUtils.createGraphie = function (el) {
|
|
|
9433
9141
|
visibleShape: visibleShape
|
|
9434
9142
|
};
|
|
9435
9143
|
}
|
|
9436
|
-
|
|
9437
|
-
|
|
9438
|
-
|
|
9439
|
-
|
|
9144
|
+
scaledPath(points, style) {
|
|
9145
|
+
return this.withStyle(style, () => {
|
|
9146
|
+
const p = this.raphael.path(this.svgPath(points, /* alreadyScaled */true));
|
|
9147
|
+
p.graphiePath = points;
|
|
9148
|
+
return p;
|
|
9149
|
+
});
|
|
9440
9150
|
}
|
|
9441
|
-
|
|
9442
|
-
|
|
9443
|
-
|
|
9444
|
-
|
|
9445
|
-
|
|
9446
|
-
|
|
9151
|
+
line(start, end, style) {
|
|
9152
|
+
return this.withStyle(style, () => {
|
|
9153
|
+
const l = this.unstyledPath([start, end]);
|
|
9154
|
+
if (this.isMobile) {
|
|
9155
|
+
l.node.style.shapeRendering = "crispEdges";
|
|
9156
|
+
}
|
|
9157
|
+
return l;
|
|
9158
|
+
});
|
|
9447
9159
|
}
|
|
9448
|
-
|
|
9449
|
-
|
|
9450
|
-
return thisGraphie.raphael.path(thisGraphie.svgParabolaPath(a, b, c));
|
|
9160
|
+
parabola(a, b, c, style) {
|
|
9161
|
+
return this.withStyle(style, () => this.raphael.path(this.svgParabolaPath(a, b, c)));
|
|
9451
9162
|
}
|
|
9452
|
-
|
|
9163
|
+
fixedLine(start, end, thickness) {
|
|
9453
9164
|
// Apply padding to line
|
|
9454
9165
|
const padding = [thickness, thickness];
|
|
9455
9166
|
|
|
9456
9167
|
// Scale points to get values in pixels
|
|
9457
|
-
start =
|
|
9458
|
-
end =
|
|
9168
|
+
start = this.scalePoint(start);
|
|
9169
|
+
end = this.scalePoint(end);
|
|
9459
9170
|
|
|
9460
9171
|
// Calculate and apply additional offset
|
|
9461
9172
|
const extraOffset = [Math.min(start[0], end[0]), Math.min(start[1], end[1])];
|
|
@@ -9495,250 +9206,469 @@ GraphUtils.createGraphie = function (el) {
|
|
|
9495
9206
|
visibleShape: visibleShape
|
|
9496
9207
|
};
|
|
9497
9208
|
}
|
|
9498
|
-
|
|
9209
|
+
sinusoid(a, b, c, d, style) {
|
|
9210
|
+
return this.withStyle(style, () =>
|
|
9499
9211
|
// Plot a sinusoid of the form: f(x) = a * sin(b * x - c) + d
|
|
9500
|
-
|
|
9501
|
-
}
|
|
9502
|
-
|
|
9503
|
-
const
|
|
9504
|
-
const
|
|
9505
|
-
|
|
9506
|
-
|
|
9507
|
-
|
|
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);
|
|
9212
|
+
this.raphael.path(this.svgSinusoidPath(a, b, c, d)));
|
|
9213
|
+
}
|
|
9214
|
+
label = (point, text, direction, arg4, arg5) => {
|
|
9215
|
+
const style = typeof arg4 === "object" ? arg4 : arg5;
|
|
9216
|
+
const latex = typeof arg4 === "boolean" ? arg4 : true;
|
|
9217
|
+
return this.withStyle(style, () => {
|
|
9218
|
+
const $span = $__default["default"]("<span>").addClass("graphie-label");
|
|
9219
|
+
const pad = this.currentStyle["label-distance"];
|
|
9528
9220
|
$span.css({
|
|
9529
|
-
|
|
9530
|
-
|
|
9531
|
-
|
|
9532
|
-
|
|
9221
|
+
position: "absolute",
|
|
9222
|
+
padding: (pad != null ? pad : 7) + "px",
|
|
9223
|
+
color: "black"
|
|
9224
|
+
}).data("labelDirection", direction).appendTo(this.el);
|
|
9225
|
+
|
|
9226
|
+
// @ts-expect-error - TS2339 - Property 'setPosition' does not exist on type 'JQuery<HTMLElement>'.
|
|
9227
|
+
$span.setPosition = point => {
|
|
9228
|
+
const scaledPoint = this.scalePoint(point);
|
|
9229
|
+
$span.css({
|
|
9230
|
+
left: scaledPoint[0],
|
|
9231
|
+
top: scaledPoint[1]
|
|
9232
|
+
});
|
|
9233
|
+
};
|
|
9533
9234
|
|
|
9534
|
-
|
|
9535
|
-
|
|
9536
|
-
|
|
9235
|
+
// @ts-expect-error - TS2339 - Property 'setPosition' does not exist on type 'JQuery<HTMLElement>'.
|
|
9236
|
+
$span.setPosition(point);
|
|
9237
|
+
const span = $span[0];
|
|
9238
|
+
|
|
9239
|
+
// @ts-expect-error - TS2339 - Property 'processMath' does not exist on type 'JQuery<HTMLElement>'.
|
|
9240
|
+
$span.processMath = function (math, force) {
|
|
9241
|
+
processMath$1(span, math, force, function () {
|
|
9242
|
+
const width = span.scrollWidth;
|
|
9243
|
+
const height = span.scrollHeight;
|
|
9244
|
+
setLabelMargins(span, [width, height]);
|
|
9245
|
+
});
|
|
9246
|
+
};
|
|
9537
9247
|
|
|
9538
|
-
|
|
9539
|
-
|
|
9540
|
-
|
|
9248
|
+
// @ts-expect-error - TS2339 - Property 'processText' does not exist on type 'JQuery<HTMLElement>'.
|
|
9249
|
+
$span.processText = function (text) {
|
|
9250
|
+
$span.html(text);
|
|
9541
9251
|
const width = span.scrollWidth;
|
|
9542
9252
|
const height = span.scrollHeight;
|
|
9543
9253
|
setLabelMargins(span, [width, height]);
|
|
9544
|
-
}
|
|
9545
|
-
|
|
9546
|
-
|
|
9547
|
-
|
|
9548
|
-
|
|
9549
|
-
|
|
9550
|
-
|
|
9551
|
-
|
|
9552
|
-
|
|
9553
|
-
};
|
|
9554
|
-
|
|
9555
|
-
|
|
9556
|
-
|
|
9557
|
-
|
|
9558
|
-
//
|
|
9559
|
-
|
|
9560
|
-
|
|
9561
|
-
|
|
9562
|
-
|
|
9563
|
-
|
|
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);
|
|
9617
|
-
|
|
9618
|
-
// backtrack to draw paired function
|
|
9619
|
-
for (let u = t - step; u >= lastFlip; u -= step) {
|
|
9620
|
-
// @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
|
|
9621
|
-
points.push(clippedFn2(u));
|
|
9622
|
-
}
|
|
9623
|
-
lastFlip = t;
|
|
9254
|
+
};
|
|
9255
|
+
if (latex) {
|
|
9256
|
+
// @ts-expect-error - TS2339 - Property 'processMath' does not exist on type 'JQuery<HTMLElement>'.
|
|
9257
|
+
$span.processMath(text, /* force */false);
|
|
9258
|
+
} else {
|
|
9259
|
+
// @ts-expect-error - TS2339 - Property 'processText' does not exist on type 'JQuery<HTMLElement>'.
|
|
9260
|
+
$span.processText(text);
|
|
9261
|
+
}
|
|
9262
|
+
return $span;
|
|
9263
|
+
});
|
|
9264
|
+
};
|
|
9265
|
+
plotParametric(fn, range, style) {
|
|
9266
|
+
return this.withStyle(style, () => {
|
|
9267
|
+
// We truncate to 500,000, since anything bigger causes
|
|
9268
|
+
// overflow in the firefox svg renderer. This is safe
|
|
9269
|
+
// since 500,000 is outside the viewport anyway. We
|
|
9270
|
+
// write these functions the way we do to handle undefined.
|
|
9271
|
+
const clip = xy => {
|
|
9272
|
+
if (Math.abs(xy[1]) > 500000) {
|
|
9273
|
+
return [xy[0], Math.min(Math.max(xy[1], -500000), 500000)];
|
|
9624
9274
|
}
|
|
9625
|
-
|
|
9626
|
-
|
|
9627
|
-
|
|
9628
|
-
|
|
9629
|
-
|
|
9630
|
-
|
|
9275
|
+
return xy;
|
|
9276
|
+
};
|
|
9277
|
+
const clippedFn = x => clip(fn(x));
|
|
9278
|
+
const min = range[0];
|
|
9279
|
+
const max = range[1];
|
|
9280
|
+
let step = (max - min) / (this.currentStyle["plot-points"] || 800);
|
|
9281
|
+
if (step === 0) {
|
|
9282
|
+
step = 1;
|
|
9283
|
+
}
|
|
9284
|
+
const paths = this.raphael.set();
|
|
9285
|
+
let points = [];
|
|
9286
|
+
let lastY = clippedFn(min)[1];
|
|
9287
|
+
for (let t = min; t <= max; t += step) {
|
|
9288
|
+
const point = clippedFn(t);
|
|
9289
|
+
const y = point[1];
|
|
9290
|
+
|
|
9291
|
+
// Find points where it flips
|
|
9292
|
+
if (
|
|
9293
|
+
// if there is an asymptote here, meaning that the graph
|
|
9294
|
+
// switches signs and has a large difference
|
|
9295
|
+
y > 0 !== lastY > 0 && Math.abs(y - lastY) > 2 * this.drawingTransform().pixelsPerUnitY() ||
|
|
9296
|
+
// or the function is undefined
|
|
9297
|
+
isNaN(y)) {
|
|
9298
|
+
// split the path at this point, and draw it
|
|
9299
|
+
paths.push(this.unstyledPath(points));
|
|
9300
|
+
// restart the path, excluding this point
|
|
9301
|
+
points = [];
|
|
9302
|
+
} else {
|
|
9303
|
+
// otherwise, just add the point to the path
|
|
9304
|
+
points.push(point);
|
|
9631
9305
|
}
|
|
9632
|
-
|
|
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);
|
|
9306
|
+
lastY = y;
|
|
9636
9307
|
}
|
|
9637
|
-
|
|
9638
|
-
|
|
9639
|
-
|
|
9640
|
-
|
|
9641
|
-
|
|
9642
|
-
|
|
9643
|
-
|
|
9308
|
+
paths.push(this.unstyledPath(points));
|
|
9309
|
+
return paths;
|
|
9310
|
+
});
|
|
9311
|
+
}
|
|
9312
|
+
plot(fn, range, style) {
|
|
9313
|
+
return this.withStyle(style, () => {
|
|
9314
|
+
const min = range[0];
|
|
9315
|
+
const max = range[1];
|
|
9316
|
+
if (!this.currentStyle["plot-points"]) {
|
|
9317
|
+
this.currentStyle["plot-points"] = 2 * (max - min) * this.drawingTransform().pixelsPerUnitX();
|
|
9644
9318
|
}
|
|
9645
|
-
|
|
9646
|
-
|
|
9647
|
-
|
|
9319
|
+
const parametricFn = x => [x, fn(x)];
|
|
9320
|
+
return this.plotParametric(parametricFn, range);
|
|
9321
|
+
});
|
|
9648
9322
|
}
|
|
9649
|
-
|
|
9650
|
-
|
|
9651
|
-
|
|
9652
|
-
|
|
9653
|
-
|
|
9323
|
+
svgPath = (points, alreadyScaled) => {
|
|
9324
|
+
return points.map((point, i) => {
|
|
9325
|
+
if (point === true) {
|
|
9326
|
+
return "z";
|
|
9327
|
+
}
|
|
9328
|
+
const scaled = alreadyScaled ? point : this.scalePoint(point);
|
|
9329
|
+
return (i === 0 ? "M" : "L") + KhanMath.bound(scaled[0]) + " " + KhanMath.bound(scaled[1]);
|
|
9330
|
+
}).join("");
|
|
9331
|
+
};
|
|
9332
|
+
svgParabolaPath = (a, b, c) => {
|
|
9333
|
+
const computeParabola = function (x) {
|
|
9334
|
+
return (a * x + b) * x + c;
|
|
9335
|
+
};
|
|
9336
|
+
|
|
9337
|
+
// If points are collinear, plot a line instead
|
|
9338
|
+
if (a === 0) {
|
|
9339
|
+
const points = [[this.bounds().xMin, computeParabola(this.bounds().xMin)], [this.bounds().xMax, computeParabola(this.bounds().xMax)]];
|
|
9340
|
+
return this.svgPath(points);
|
|
9654
9341
|
}
|
|
9655
|
-
|
|
9656
|
-
|
|
9657
|
-
|
|
9658
|
-
|
|
9342
|
+
|
|
9343
|
+
// Calculate x coordinates of points on parabola
|
|
9344
|
+
const xVertex = -b / (2 * a);
|
|
9345
|
+
const distToEdge = Math.max(Math.abs(xVertex - this.bounds().xMin), Math.abs(xVertex - this.bounds().xMax));
|
|
9346
|
+
|
|
9347
|
+
// To guarantee that drawn parabola to spans the viewport, use a point
|
|
9348
|
+
// on the edge of the graph furtherest from the vertex
|
|
9349
|
+
const xPoint = xVertex + distToEdge;
|
|
9350
|
+
|
|
9351
|
+
// Compute parabola and other point on the curve
|
|
9352
|
+
const vertex = [xVertex, computeParabola(xVertex)];
|
|
9353
|
+
const point = [xPoint, computeParabola(xPoint)];
|
|
9354
|
+
|
|
9355
|
+
// Calculate SVG 'control' point, defined by spec
|
|
9356
|
+
const control = [vertex[0], vertex[1] - (point[1] - vertex[1])];
|
|
9357
|
+
|
|
9358
|
+
// Calculate mirror points across parabola's axis of symmetry
|
|
9359
|
+
const dx = Math.abs(vertex[0] - point[0]);
|
|
9360
|
+
const left = [vertex[0] - dx, point[1]];
|
|
9361
|
+
const right = [vertex[0] + dx, point[1]];
|
|
9362
|
+
|
|
9363
|
+
// Scale and bound
|
|
9364
|
+
const points = [left, control, right].map(this.scalePoint);
|
|
9365
|
+
const values = points.flat().map(KhanMath.bound);
|
|
9366
|
+
return "M" + values[0] + "," + values[1] + " Q" + values[2] + "," + values[3] + " " + values[4] + "," + values[5];
|
|
9367
|
+
};
|
|
9368
|
+
svgSinusoidPath = (a, b, c, d) => {
|
|
9369
|
+
// Plot a sinusoid of the form: f(x) = a * sin(b * x - c) + d
|
|
9370
|
+
const quarterPeriod = Math.abs(Math.PI / (2 * b));
|
|
9371
|
+
const computeSine = function (x) {
|
|
9372
|
+
return a * Math.sin(b * x - c) + d;
|
|
9373
|
+
};
|
|
9374
|
+
const computeDerivative = function (x) {
|
|
9375
|
+
return a * b * Math.cos(c - b * x);
|
|
9376
|
+
};
|
|
9377
|
+
const coordsForOffset = (initial, i) => {
|
|
9378
|
+
// Return the cubic coordinates (including the two anchor and two
|
|
9379
|
+
// control points) for the ith portion of the sinusoid.
|
|
9380
|
+
const x0 = initial + quarterPeriod * i;
|
|
9381
|
+
const x1 = x0 + quarterPeriod;
|
|
9382
|
+
|
|
9383
|
+
// Interpolate using derivative technique
|
|
9384
|
+
// See: http://stackoverflow.com/questions/13932704/how-to-draw-sine-waves-with-svg-js
|
|
9385
|
+
const xCoords = [x0, x0 * 2 / 3 + x1 * 1 / 3, x0 * 1 / 3 + x1 * 2 / 3, x1];
|
|
9386
|
+
const yCoords = [computeSine(x0), computeSine(x0) + computeDerivative(x0) * (x1 - x0) / 3, computeSine(x1) - computeDerivative(x1) * (x1 - x0) / 3, computeSine(x1)];
|
|
9387
|
+
const points = kmath.vector.zip(xCoords, yCoords);
|
|
9388
|
+
return points.map(this.scalePoint);
|
|
9389
|
+
};
|
|
9390
|
+
|
|
9391
|
+
// How many quarter-periods do we need to span the graph?
|
|
9392
|
+
const extent = this.bounds().width();
|
|
9393
|
+
const numQuarterPeriods = Math.ceil(extent / quarterPeriod) + 1;
|
|
9394
|
+
|
|
9395
|
+
// Find starting coordinate: first anchor point curve left of bounds.xMin
|
|
9396
|
+
let initial = c / b;
|
|
9397
|
+
const distToEdge = initial - this.bounds().xMin;
|
|
9398
|
+
initial -= quarterPeriod * Math.ceil(distToEdge / quarterPeriod);
|
|
9399
|
+
|
|
9400
|
+
// First portion of path is special-case, requiring move-to ('M')
|
|
9401
|
+
let coords = coordsForOffset(initial, 0);
|
|
9402
|
+
let path = "M" + coords[0][0] + "," + coords[0][1] + " C" + coords[1][0] + "," + coords[1][1] + " " + coords[2][0] + "," + coords[2][1] + " " + coords[3][0] + "," + coords[3][1];
|
|
9403
|
+
for (let i = 1; i < numQuarterPeriods; i++) {
|
|
9404
|
+
coords = coordsForOffset(initial, i);
|
|
9405
|
+
path += " C" + coords[1][0] + "," + coords[1][1] + " " + coords[2][0] + "," + coords[2][1] + " " + coords[3][0] + "," + coords[3][1];
|
|
9406
|
+
}
|
|
9407
|
+
return path;
|
|
9408
|
+
};
|
|
9409
|
+
withStyle(style, fn) {
|
|
9410
|
+
const oldStyle = this.currentStyle;
|
|
9411
|
+
this.currentStyle = {
|
|
9412
|
+
...this.currentStyle,
|
|
9413
|
+
...this.processAttributes(style)
|
|
9414
|
+
};
|
|
9415
|
+
const result = this.postprocessDrawingResult(fn());
|
|
9416
|
+
this.currentStyle = oldStyle;
|
|
9417
|
+
return result;
|
|
9418
|
+
}
|
|
9419
|
+
postprocessDrawingResult(result) {
|
|
9420
|
+
// Bad heuristic for recognizing Raphael elements and sets
|
|
9421
|
+
const type = result.constructor.prototype;
|
|
9422
|
+
if (type === raphael.exports.el || type === raphael.exports.st) {
|
|
9423
|
+
result.attr(this.currentStyle);
|
|
9424
|
+
if (this.currentStyle.arrows) {
|
|
9425
|
+
result = this.addArrowheads(result);
|
|
9659
9426
|
}
|
|
9660
|
-
|
|
9661
|
-
|
|
9662
|
-
|
|
9427
|
+
} else if (result instanceof $__default["default"]) {
|
|
9428
|
+
// We assume that if it's not a Raphael element/set, it
|
|
9429
|
+
// does not contain SVG.
|
|
9430
|
+
// @ts-expect-error - TS2339 - Property 'css' does not exist on type '{}'.
|
|
9431
|
+
result.css({
|
|
9432
|
+
...this.currentStyle,
|
|
9433
|
+
...SVG_SPECIFIC_STYLE_MASK
|
|
9434
|
+
});
|
|
9663
9435
|
}
|
|
9664
|
-
|
|
9665
|
-
|
|
9666
|
-
|
|
9667
|
-
|
|
9668
|
-
|
|
9669
|
-
|
|
9436
|
+
return result;
|
|
9437
|
+
}
|
|
9438
|
+
addArrowheads(path) {
|
|
9439
|
+
const type = path.constructor.prototype;
|
|
9440
|
+
if (type === raphael.exports.el) {
|
|
9441
|
+
if (path.type === "path" && typeof path.arrowheadsDrawn === "undefined") {
|
|
9442
|
+
const w = path.attr("stroke-width");
|
|
9443
|
+
const s = 0.6 + 0.4 * w;
|
|
9444
|
+
const l = path.getTotalLength();
|
|
9445
|
+
const set = this.raphael.set();
|
|
9446
|
+
const head = this.raphael.path(this.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");
|
|
9447
|
+
const end = path.getPointAtLength(l - 0.4);
|
|
9448
|
+
const almostTheEnd = path.getPointAtLength(l - 0.75 * s);
|
|
9449
|
+
const angle = Math.atan2(end.y - almostTheEnd.y, end.x - almostTheEnd.x) * 180 / Math.PI;
|
|
9450
|
+
const attrs = path.attr();
|
|
9451
|
+
delete attrs.path;
|
|
9452
|
+
let subpath = path.getSubpath(0, l - 0.75 * s);
|
|
9453
|
+
subpath = this.raphael.path(subpath).attr(attrs);
|
|
9454
|
+
subpath.arrowheadsDrawn = true;
|
|
9455
|
+
path.remove();
|
|
9456
|
+
|
|
9457
|
+
// For some unknown reason 0 doesn't work for the rotation
|
|
9458
|
+
// origin so we use a tiny number.
|
|
9459
|
+
head.rotate(angle, this.isMobile ? 1e-5 : 0.75, 0).scale(s, s, 0.75, 0).translate(almostTheEnd.x, almostTheEnd.y).attr(attrs).attr({
|
|
9460
|
+
"stroke-linejoin": "round",
|
|
9461
|
+
"stroke-linecap": "round"
|
|
9670
9462
|
});
|
|
9463
|
+
head.arrowheadsDrawn = true;
|
|
9464
|
+
set.push(subpath);
|
|
9465
|
+
set.push(head);
|
|
9466
|
+
return set;
|
|
9467
|
+
}
|
|
9468
|
+
} else if (type === raphael.exports.st) {
|
|
9469
|
+
for (let i = 0, l = path.items.length; i < l; i++) {
|
|
9470
|
+
this.addArrowheads(path.items[i]);
|
|
9671
9471
|
}
|
|
9672
|
-
throw new PerseusError("fn2 should only be set when 'shade' is True.", Errors.Internal);
|
|
9673
9472
|
}
|
|
9674
|
-
return
|
|
9675
|
-
return [x, fn(x)];
|
|
9676
|
-
}, range, shade);
|
|
9473
|
+
return path;
|
|
9677
9474
|
}
|
|
9678
|
-
|
|
9679
|
-
|
|
9680
|
-
|
|
9681
|
-
|
|
9682
|
-
|
|
9683
|
-
|
|
9684
|
-
|
|
9685
|
-
|
|
9686
|
-
|
|
9687
|
-
|
|
9688
|
-
|
|
9689
|
-
|
|
9690
|
-
|
|
9691
|
-
|
|
9692
|
-
|
|
9693
|
-
|
|
9694
|
-
|
|
9695
|
-
|
|
9696
|
-
|
|
9697
|
-
|
|
9698
|
-
|
|
9699
|
-
args[_key] = arguments[_key];
|
|
9700
|
-
}
|
|
9701
|
-
const last = args[args.length - 1];
|
|
9702
|
-
const oldStyle = thisGraphie.currentStyle;
|
|
9703
|
-
let result;
|
|
9475
|
+
scalePoint = point => {
|
|
9476
|
+
return this.drawingTransform().scalePoint(point);
|
|
9477
|
+
};
|
|
9478
|
+
scaleVector = point => {
|
|
9479
|
+
return this.drawingTransform().scaleVector(point);
|
|
9480
|
+
};
|
|
9481
|
+
unscalePoint = point => {
|
|
9482
|
+
return this.drawingTransform().unscalePoint(point);
|
|
9483
|
+
};
|
|
9484
|
+
unscaleVector = point => {
|
|
9485
|
+
return this.drawingTransform().unscaleVector(point);
|
|
9486
|
+
};
|
|
9487
|
+
processAttributes(attrs) {
|
|
9488
|
+
const transformers = {
|
|
9489
|
+
scale: scale => {
|
|
9490
|
+
this.drawingTransform().setScale(scale);
|
|
9491
|
+
},
|
|
9492
|
+
clipRect: pair => {
|
|
9493
|
+
const point = pair[0];
|
|
9494
|
+
const size = pair[1];
|
|
9495
|
+
point[1] += size[1]; // because our coordinates are flipped
|
|
9704
9496
|
|
|
9705
|
-
|
|
9706
|
-
|
|
9707
|
-
|
|
9708
|
-
|
|
9709
|
-
|
|
9497
|
+
return {
|
|
9498
|
+
"clip-rect": this.scalePoint(point).concat(this.scaleVector(size)).join(" ")
|
|
9499
|
+
};
|
|
9500
|
+
},
|
|
9501
|
+
strokeWidth: function (val) {
|
|
9502
|
+
return {
|
|
9503
|
+
"stroke-width": parseFloat(val)
|
|
9504
|
+
};
|
|
9505
|
+
},
|
|
9506
|
+
rx: val => {
|
|
9507
|
+
return {
|
|
9508
|
+
rx: this.scaleVector([val, 0])[0]
|
|
9509
|
+
};
|
|
9510
|
+
},
|
|
9511
|
+
ry: val => {
|
|
9512
|
+
return {
|
|
9513
|
+
ry: this.scaleVector([0, val])[1]
|
|
9514
|
+
};
|
|
9515
|
+
},
|
|
9516
|
+
r: val => {
|
|
9517
|
+
const scaled = this.scaleVector([val, val]);
|
|
9518
|
+
return {
|
|
9519
|
+
rx: scaled[0],
|
|
9520
|
+
ry: scaled[1]
|
|
9710
9521
|
};
|
|
9711
|
-
const rest = [].slice.call(args, 0, args.length - 1);
|
|
9712
|
-
result = drawingFn(...rest);
|
|
9713
|
-
} else {
|
|
9714
|
-
thisGraphie.currentStyle = $__default["default"].extend({}, thisGraphie.currentStyle);
|
|
9715
|
-
result = drawingFn(...args);
|
|
9716
|
-
}
|
|
9717
|
-
|
|
9718
|
-
// Bad heuristic for recognizing Raphael elements and sets
|
|
9719
|
-
const type = result.constructor.prototype;
|
|
9720
|
-
if (type === raphael.exports.el || type === raphael.exports.st) {
|
|
9721
|
-
result.attr(thisGraphie.currentStyle);
|
|
9722
|
-
if (thisGraphie.currentStyle.arrows) {
|
|
9723
|
-
result = addArrowheads(result);
|
|
9724
|
-
}
|
|
9725
|
-
} else if (result instanceof $__default["default"]) {
|
|
9726
|
-
// We assume that if it's not a Raphael element/set, it
|
|
9727
|
-
// does not contain SVG.
|
|
9728
|
-
// @ts-expect-error - TS2339 - Property 'css' does not exist on type '{}'.
|
|
9729
|
-
result.css({
|
|
9730
|
-
...thisGraphie.currentStyle,
|
|
9731
|
-
...SVG_SPECIFIC_STYLE_MASK
|
|
9732
|
-
});
|
|
9733
9522
|
}
|
|
9734
|
-
thisGraphie.currentStyle = oldStyle;
|
|
9735
|
-
return result;
|
|
9736
9523
|
};
|
|
9524
|
+
const processed = {};
|
|
9525
|
+
Object.entries(attrs || {}).forEach(function (_ref) {
|
|
9526
|
+
let [key, value] = _ref;
|
|
9527
|
+
const transformer = transformers[key];
|
|
9528
|
+
if (typeof transformer === "function") {
|
|
9529
|
+
Object.assign(processed, transformer(value));
|
|
9530
|
+
} else {
|
|
9531
|
+
const dasherized = String(key).replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2").replace(/([a-z\d])([A-Z])/g, "$1-$2").toLowerCase();
|
|
9532
|
+
processed[dasherized] = value;
|
|
9533
|
+
}
|
|
9534
|
+
});
|
|
9535
|
+
return processed;
|
|
9737
9536
|
}
|
|
9738
|
-
|
|
9739
|
-
|
|
9740
|
-
|
|
9741
|
-
|
|
9537
|
+
}
|
|
9538
|
+
const labelDirections = {
|
|
9539
|
+
center: [-0.5, -0.5],
|
|
9540
|
+
above: [-0.5, -1.0],
|
|
9541
|
+
"above right": [0.0, -1.0],
|
|
9542
|
+
right: [0.0, -0.5],
|
|
9543
|
+
"below right": [0.0, 0.0],
|
|
9544
|
+
below: [-0.5, 0.0],
|
|
9545
|
+
"below left": [-1.0, 0.0],
|
|
9546
|
+
left: [-1.0, -0.5],
|
|
9547
|
+
"above left": [-1.0, -1.0]
|
|
9548
|
+
};
|
|
9549
|
+
|
|
9550
|
+
// A range for a Graphie can be specified in one of three forms:
|
|
9551
|
+
// - A pair of intervals, [[xMin, xMax], [yMin, yMax]]. This is the
|
|
9552
|
+
// "normalized" form.
|
|
9553
|
+
// - A pair of magnitudes for the x and y ranges, respectively. E.g. [5, 10]
|
|
9554
|
+
// is equivalent to [[-5, 5], [-10, 10]].
|
|
9555
|
+
// - A single magnitude to be used for both x and y. E.g. 10 is equivalent to
|
|
9556
|
+
// [[-10, 10], [-10, 10]]
|
|
9557
|
+
// Overload normalizeRange to specify that it only returns undefined if its
|
|
9558
|
+
// argument is undefined.
|
|
9559
|
+
// normalizeRange converts a RangeSpecifier into a pair of Intervals that give
|
|
9560
|
+
// the x and y ranges, respectively
|
|
9561
|
+
function normalizeRange(range) {
|
|
9562
|
+
function normalizeInterval(magnitude) {
|
|
9563
|
+
if (typeof magnitude === "number") {
|
|
9564
|
+
return [-magnitude, magnitude];
|
|
9565
|
+
} else {
|
|
9566
|
+
return magnitude;
|
|
9567
|
+
}
|
|
9568
|
+
}
|
|
9569
|
+
function getXAndYRanges(range) {
|
|
9570
|
+
if (Array.isArray(range)) {
|
|
9571
|
+
return range;
|
|
9572
|
+
} else {
|
|
9573
|
+
return [range, range];
|
|
9574
|
+
}
|
|
9575
|
+
}
|
|
9576
|
+
if (range == null) {
|
|
9577
|
+
return range;
|
|
9578
|
+
}
|
|
9579
|
+
const [xRange, yRange] = getXAndYRanges(range);
|
|
9580
|
+
return [normalizeInterval(xRange), normalizeInterval(yRange)];
|
|
9581
|
+
}
|
|
9582
|
+
function toPair(x) {
|
|
9583
|
+
if (Array.isArray(x)) {
|
|
9584
|
+
return x;
|
|
9585
|
+
}
|
|
9586
|
+
return [x, x];
|
|
9587
|
+
}
|
|
9588
|
+
|
|
9589
|
+
/**
|
|
9590
|
+
* Safari applies some SVG-specific styles to things that are not SVGs, so we
|
|
9591
|
+
* need to exclude those styles from things that are not SVGs.
|
|
9592
|
+
*
|
|
9593
|
+
* To see this behavior in action, open https://codepen.io/anon/pen/zENEoa in
|
|
9594
|
+
* Safari.
|
|
9595
|
+
*
|
|
9596
|
+
* Usage `{...someStyles, ...SVG_SPECIFIC_STYLE_MASK}`
|
|
9597
|
+
*/
|
|
9598
|
+
const SVG_SPECIFIC_STYLE_MASK = {
|
|
9599
|
+
"stroke-width": null
|
|
9600
|
+
};
|
|
9601
|
+
const setLabelMargins = function (span, size) {
|
|
9602
|
+
const $span = $__default["default"](span);
|
|
9603
|
+
const direction = $span.data("labelDirection");
|
|
9604
|
+
let [width, height] = size;
|
|
9605
|
+
// This can happen when a span
|
|
9606
|
+
// is invisible but we still want to update the CSS. At worst, we will
|
|
9607
|
+
// be off by a few pixels instead of in a different position entirely.
|
|
9608
|
+
if (width === 0 && height === 0) {
|
|
9609
|
+
[width, height] = [1, 1];
|
|
9610
|
+
Log.log("Label size was 0x0 in graphie.js; using 1x1 instead");
|
|
9611
|
+
}
|
|
9612
|
+
$span.css("visibility", "");
|
|
9613
|
+
if (typeof direction === "number") {
|
|
9614
|
+
const x = Math.cos(direction);
|
|
9615
|
+
const y = Math.sin(direction);
|
|
9616
|
+
const scale = Math.min(width / 2 / Math.abs(x), height / 2 / Math.abs(y));
|
|
9617
|
+
$span.css({
|
|
9618
|
+
marginLeft: -width / 2 + x * scale,
|
|
9619
|
+
marginTop: -height / 2 - y * scale
|
|
9620
|
+
});
|
|
9621
|
+
} else {
|
|
9622
|
+
const multipliers = labelDirections[direction || "center"];
|
|
9623
|
+
$span.css({
|
|
9624
|
+
marginLeft: Math.round(width * multipliers[0]),
|
|
9625
|
+
marginTop: Math.round(height * multipliers[1])
|
|
9626
|
+
});
|
|
9627
|
+
}
|
|
9628
|
+
};
|
|
9629
|
+
const GraphUtils = {
|
|
9630
|
+
Graphie: Graphie$1,
|
|
9631
|
+
createGraphie: function (el) {
|
|
9632
|
+
return new Graphie$1(el);
|
|
9633
|
+
},
|
|
9634
|
+
unscaledSvgPath: function (points) {
|
|
9635
|
+
// If this is an empty closed path, return "" instead of "z", which
|
|
9636
|
+
// would give an error
|
|
9637
|
+
if (points[0] === true) {
|
|
9638
|
+
return "";
|
|
9639
|
+
}
|
|
9640
|
+
return points.map(function (point, i) {
|
|
9641
|
+
if (point === true) {
|
|
9642
|
+
return "z";
|
|
9643
|
+
}
|
|
9644
|
+
return (i === 0 ? "M" : "L") + point[0] + " " + point[1];
|
|
9645
|
+
}).join("");
|
|
9646
|
+
},
|
|
9647
|
+
getDistance: function (point1, point2) {
|
|
9648
|
+
return kmath.point.distanceToPoint(point1, point2);
|
|
9649
|
+
},
|
|
9650
|
+
/**
|
|
9651
|
+
* Round the given coordinates to a given snap value
|
|
9652
|
+
* (e.g., nearest 0.2 increment)
|
|
9653
|
+
*/
|
|
9654
|
+
snapCoord: function (coord, snap) {
|
|
9655
|
+
return coord.map(function (val, i) {
|
|
9656
|
+
return KhanMath.roundToNearest(snap[i], val);
|
|
9657
|
+
});
|
|
9658
|
+
},
|
|
9659
|
+
// Find the angle in degrees between two or three points
|
|
9660
|
+
findAngle: function (point1, point2, vertex) {
|
|
9661
|
+
if (vertex === undefined) {
|
|
9662
|
+
const x = point1[0] - point2[0];
|
|
9663
|
+
const y = point1[1] - point2[1];
|
|
9664
|
+
if (!x && !y) {
|
|
9665
|
+
return 0;
|
|
9666
|
+
}
|
|
9667
|
+
return (180 + Math.atan2(-y, -x) * 180 / Math.PI + 360) % 360;
|
|
9668
|
+
}
|
|
9669
|
+
return GraphUtils.findAngle(point1, vertex) - GraphUtils.findAngle(point2, vertex);
|
|
9670
|
+
},
|
|
9671
|
+
graphs: {}
|
|
9742
9672
|
};
|
|
9743
9673
|
|
|
9744
9674
|
/* eslint-disable */
|
|
@@ -10456,15 +10386,65 @@ const WrappedPath = function (graphie, points, options) {
|
|
|
10456
10386
|
};
|
|
10457
10387
|
___default["default"].extend(WrappedPath.prototype, WrappedDefaults);
|
|
10458
10388
|
|
|
10459
|
-
|
|
10389
|
+
/**
|
|
10390
|
+
* A collection of geomtry-related utility functions
|
|
10391
|
+
*/
|
|
10460
10392
|
const {
|
|
10461
|
-
|
|
10462
|
-
|
|
10463
|
-
|
|
10464
|
-
|
|
10465
|
-
|
|
10466
|
-
|
|
10393
|
+
eq: eq$1,
|
|
10394
|
+
deepEq: deepEq$4
|
|
10395
|
+
} = Util;
|
|
10396
|
+
|
|
10397
|
+
// This should really be a readonly tuple of [number, number]
|
|
10398
|
+
|
|
10399
|
+
// a, b, c
|
|
10400
|
+
|
|
10401
|
+
function sign(val) {
|
|
10402
|
+
if (eq$1(val, 0)) {
|
|
10403
|
+
return 0;
|
|
10404
|
+
}
|
|
10405
|
+
return val > 0 ? 1 : -1;
|
|
10406
|
+
}
|
|
10407
|
+
function ccw(a, b, c) {
|
|
10408
|
+
return (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1]);
|
|
10409
|
+
}
|
|
10410
|
+
function collinear(a, b, c) {
|
|
10411
|
+
return eq$1(ccw(a, b, c), 0);
|
|
10412
|
+
}
|
|
10413
|
+
|
|
10414
|
+
// Given rect bounding points A and B, whether point C is inside the rect
|
|
10415
|
+
function pointInRect(a, b, c) {
|
|
10416
|
+
return c[0] <= Math.max(a[0], b[0]) && c[0] >= Math.min(a[0], b[0]) && c[1] <= Math.max(a[1], b[1]) && c[1] >= Math.min(a[1], b[1]);
|
|
10417
|
+
}
|
|
10418
|
+
|
|
10419
|
+
// Whether line segment AB intersects line segment CD
|
|
10420
|
+
// http://www.geeksforgeeks.org/check-if-two-given-line-segments-intersect/
|
|
10421
|
+
function intersects(ab, cd) {
|
|
10422
|
+
const triplets = [[ab[0], ab[1], cd[0]], [ab[0], ab[1], cd[1]], [cd[0], cd[1], ab[0]], [cd[0], cd[1], ab[1]]];
|
|
10423
|
+
const orientations = ___default["default"].map(triplets, function (triplet) {
|
|
10424
|
+
return sign(ccw(...triplet));
|
|
10425
|
+
});
|
|
10426
|
+
if (orientations[0] !== orientations[1] && orientations[2] !== orientations[3]) {
|
|
10427
|
+
return true;
|
|
10428
|
+
}
|
|
10429
|
+
for (let i = 0; i < 4; i++) {
|
|
10430
|
+
if (orientations[i] === 0 && pointInRect(...triplets[i])) {
|
|
10431
|
+
return true;
|
|
10432
|
+
}
|
|
10433
|
+
}
|
|
10434
|
+
return false;
|
|
10435
|
+
}
|
|
10436
|
+
function vector(a, b) {
|
|
10437
|
+
return ___default["default"].map(___default["default"].zip(a, b), function (pair) {
|
|
10438
|
+
return pair[0] - pair[1];
|
|
10439
|
+
});
|
|
10440
|
+
}
|
|
10441
|
+
function reverseVector(vector) {
|
|
10442
|
+
return [-vector[0], -vector[1]];
|
|
10467
10443
|
}
|
|
10444
|
+
|
|
10445
|
+
// Returns whether connecting the given sequence of `points` forms a clockwise
|
|
10446
|
+
// path (assuming a closed loop, where the last point connects back to the
|
|
10447
|
+
// first).
|
|
10468
10448
|
function clockwise$1(points) {
|
|
10469
10449
|
const segments = ___default["default"].zip(points, points.slice(1).concat(points.slice(0, 1)));
|
|
10470
10450
|
const areas = ___default["default"].map(segments, function (segment) {
|
|
@@ -10474,17 +10454,165 @@ function clockwise$1(points) {
|
|
|
10474
10454
|
});
|
|
10475
10455
|
return sum$1(areas) > 0;
|
|
10476
10456
|
}
|
|
10477
|
-
|
|
10478
|
-
|
|
10479
|
-
|
|
10480
|
-
|
|
10481
|
-
|
|
10457
|
+
function magnitude(v) {
|
|
10458
|
+
return Math.sqrt(___default["default"].reduce(v, function (memo, el) {
|
|
10459
|
+
// @ts-expect-error - TS2345 - Argument of type 'Coord' is not assignable to parameter of type 'number'.
|
|
10460
|
+
return memo + Math.pow(el, 2);
|
|
10461
|
+
}, 0));
|
|
10482
10462
|
}
|
|
10483
|
-
function
|
|
10484
|
-
return ___default["default"].
|
|
10485
|
-
return
|
|
10463
|
+
function dotProduct(a, b) {
|
|
10464
|
+
return ___default["default"].reduce(___default["default"].zip(a, b), function (memo, pair) {
|
|
10465
|
+
return memo + pair[0] * pair[1];
|
|
10466
|
+
}, 0);
|
|
10467
|
+
}
|
|
10468
|
+
function sideLengths(coords) {
|
|
10469
|
+
const segments = ___default["default"].zip(coords, rotate(coords));
|
|
10470
|
+
return segments.map(function (segment) {
|
|
10471
|
+
// @ts-expect-error - TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'readonly Coord[]'. | TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
|
|
10472
|
+
return magnitude(vector(...segment));
|
|
10473
|
+
});
|
|
10474
|
+
}
|
|
10475
|
+
|
|
10476
|
+
// Based on http://math.stackexchange.com/a/151149
|
|
10477
|
+
function angleMeasures(coords) {
|
|
10478
|
+
const triplets = ___default["default"].zip(rotate(coords, -1), coords, rotate(coords, 1));
|
|
10479
|
+
const offsets = ___default["default"].map(triplets, function (triplet) {
|
|
10480
|
+
const p = vector(triplet[1], triplet[0]);
|
|
10481
|
+
const q = vector(triplet[2], triplet[1]);
|
|
10482
|
+
// @ts-expect-error - TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'Coord'. | TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'readonly Coord[]'. | TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'readonly Coord[]'.
|
|
10483
|
+
const raw = Math.acos(dotProduct(p, q) / (magnitude(p) * magnitude(q)));
|
|
10484
|
+
// @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
|
|
10485
|
+
return sign(ccw(...triplet)) > 0 ? raw : -raw;
|
|
10486
|
+
});
|
|
10487
|
+
const sum = ___default["default"].reduce(offsets, function (memo, arg) {
|
|
10488
|
+
return memo + arg;
|
|
10489
|
+
}, 0);
|
|
10490
|
+
return ___default["default"].map(offsets, function (offset) {
|
|
10491
|
+
return sum > 0 ? Math.PI - offset : Math.PI + offset;
|
|
10486
10492
|
});
|
|
10487
10493
|
}
|
|
10494
|
+
|
|
10495
|
+
// Whether two polygons are similar (or if specified, congruent)
|
|
10496
|
+
function similar(coords1, coords2, tolerance) {
|
|
10497
|
+
if (coords1.length !== coords2.length) {
|
|
10498
|
+
return false;
|
|
10499
|
+
}
|
|
10500
|
+
const n = coords1.length;
|
|
10501
|
+
const angles1 = angleMeasures(coords1);
|
|
10502
|
+
const angles2 = angleMeasures(coords2);
|
|
10503
|
+
const sides1 = sideLengths(coords1);
|
|
10504
|
+
const sides2 = sideLengths(coords2);
|
|
10505
|
+
for (let i = 0; i < 2 * n; i++) {
|
|
10506
|
+
let angles = angles2.slice();
|
|
10507
|
+
let sides = sides2.slice();
|
|
10508
|
+
|
|
10509
|
+
// Reverse angles and sides to allow matching reflected polygons
|
|
10510
|
+
if (i >= n) {
|
|
10511
|
+
angles.reverse();
|
|
10512
|
+
sides.reverse();
|
|
10513
|
+
// Since sides are calculated from two coordinates,
|
|
10514
|
+
// simply reversing results in an off by one error
|
|
10515
|
+
// @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
|
|
10516
|
+
sides = rotate(sides, 1);
|
|
10517
|
+
}
|
|
10518
|
+
|
|
10519
|
+
// @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
|
|
10520
|
+
angles = rotate(angles, i);
|
|
10521
|
+
// @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
|
|
10522
|
+
sides = rotate(sides, i);
|
|
10523
|
+
if (deepEq$4(angles1, angles)) {
|
|
10524
|
+
const sidePairs = ___default["default"].zip(sides1, sides);
|
|
10525
|
+
const factors = ___default["default"].map(sidePairs, function (pair) {
|
|
10526
|
+
return pair[0] / pair[1];
|
|
10527
|
+
});
|
|
10528
|
+
const same = ___default["default"].all(factors, function (factor) {
|
|
10529
|
+
return eq$1(factors[0], factor);
|
|
10530
|
+
});
|
|
10531
|
+
const congruentEnough = ___default["default"].all(sidePairs, function (pair) {
|
|
10532
|
+
return kmath.number.equal(pair[0], pair[1], tolerance);
|
|
10533
|
+
});
|
|
10534
|
+
if (same && congruentEnough) {
|
|
10535
|
+
return true;
|
|
10536
|
+
}
|
|
10537
|
+
}
|
|
10538
|
+
}
|
|
10539
|
+
return false;
|
|
10540
|
+
}
|
|
10541
|
+
|
|
10542
|
+
// Given triangle with sides ABC return angle opposite side C in degrees
|
|
10543
|
+
function lawOfCosines(a, b, c) {
|
|
10544
|
+
return Math.acos((a * a + b * b - c * c) / (2 * a * b)) * 180 / Math.PI;
|
|
10545
|
+
}
|
|
10546
|
+
function canonicalSineCoefficients$1(_ref) {
|
|
10547
|
+
let [amplitude, angularFrequency, phase, verticalOffset] = _ref;
|
|
10548
|
+
// For a curve of the form f(x) = a * Sin(b * x - c) + d,
|
|
10549
|
+
// this function ensures that a, b > 0, and c is its
|
|
10550
|
+
// smallest possible positive value.
|
|
10551
|
+
|
|
10552
|
+
// Guarantee a > 0
|
|
10553
|
+
if (amplitude < 0) {
|
|
10554
|
+
amplitude *= -1;
|
|
10555
|
+
angularFrequency *= -1;
|
|
10556
|
+
phase *= -1;
|
|
10557
|
+
}
|
|
10558
|
+
const period = 2 * Math.PI;
|
|
10559
|
+
// Guarantee b > 0
|
|
10560
|
+
if (angularFrequency < 0) {
|
|
10561
|
+
angularFrequency *= -1;
|
|
10562
|
+
phase *= -1;
|
|
10563
|
+
phase += period / 2;
|
|
10564
|
+
}
|
|
10565
|
+
|
|
10566
|
+
// Guarantee c is smallest possible positive value
|
|
10567
|
+
while (phase > 0) {
|
|
10568
|
+
phase -= period;
|
|
10569
|
+
}
|
|
10570
|
+
while (phase < 0) {
|
|
10571
|
+
phase += period;
|
|
10572
|
+
}
|
|
10573
|
+
return [amplitude, angularFrequency, phase, verticalOffset];
|
|
10574
|
+
}
|
|
10575
|
+
|
|
10576
|
+
// e.g. rotate([1, 2, 3]) -> [2, 3, 1]
|
|
10577
|
+
function rotate(array, n) {
|
|
10578
|
+
n = typeof n === "undefined" ? 1 : n % array.length;
|
|
10579
|
+
return array.slice(n).concat(array.slice(0, n));
|
|
10580
|
+
}
|
|
10581
|
+
function getLineEquation(first, second) {
|
|
10582
|
+
if (eq$1(first[0], second[0])) {
|
|
10583
|
+
return "x = " + first[0].toFixed(3);
|
|
10584
|
+
}
|
|
10585
|
+
const m = (second[1] - first[1]) / (second[0] - first[0]);
|
|
10586
|
+
const b = first[1] - m * first[0];
|
|
10587
|
+
return "y = " + m.toFixed(3) + "x + " + b.toFixed(3);
|
|
10588
|
+
}
|
|
10589
|
+
|
|
10590
|
+
// Stolen from the wikipedia article
|
|
10591
|
+
// http://en.wikipedia.org/wiki/Line-line_intersection
|
|
10592
|
+
function getLineIntersection(
|
|
10593
|
+
// TODO(LP-10725): update these to be 2-tuples
|
|
10594
|
+
firstPoints, secondPoints) {
|
|
10595
|
+
const x1 = firstPoints[0][0];
|
|
10596
|
+
const y1 = firstPoints[0][1];
|
|
10597
|
+
const x2 = firstPoints[1][0];
|
|
10598
|
+
const y2 = firstPoints[1][1];
|
|
10599
|
+
const x3 = secondPoints[0][0];
|
|
10600
|
+
const y3 = secondPoints[0][1];
|
|
10601
|
+
const x4 = secondPoints[1][0];
|
|
10602
|
+
const y4 = secondPoints[1][1];
|
|
10603
|
+
const determinant = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
|
|
10604
|
+
if (Math.abs(determinant) < 1e-9) {
|
|
10605
|
+
return "Lines are parallel";
|
|
10606
|
+
}
|
|
10607
|
+
const x = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / determinant;
|
|
10608
|
+
const y = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / determinant;
|
|
10609
|
+
return "Intersection: (" + x.toFixed(3) + ", " + y.toFixed(3) + ")";
|
|
10610
|
+
}
|
|
10611
|
+
|
|
10612
|
+
/* eslint-disable @babel/no-invalid-this */
|
|
10613
|
+
const {
|
|
10614
|
+
getCanUse3dTransform
|
|
10615
|
+
} = InteractiveUtil;
|
|
10488
10616
|
function scaledDistanceFromAngle(angle) {
|
|
10489
10617
|
const a = 3.51470560176242 * 20;
|
|
10490
10618
|
const b = 0.5687298702748785 * 20;
|
|
@@ -10661,10 +10789,10 @@ ___default["default"].extend(GraphUtils.Graphie.prototype, {
|
|
|
10661
10789
|
let sRadius = sPadding + scaledDistanceFromAngle(angle);
|
|
10662
10790
|
const temp = [];
|
|
10663
10791
|
if (Math.abs(angle - 90) < 1e-9 && options.showRightAngleMarker) {
|
|
10664
|
-
const v1 =
|
|
10665
|
-
const v2 =
|
|
10792
|
+
const v1 = kmath.vector.add(sVertex, scaledPolarDeg(sRadius, startAngle));
|
|
10793
|
+
const v2 = kmath.vector.add(sVertex, scaledPolarDeg(sRadius, endAngle));
|
|
10666
10794
|
sRadius *= Math.SQRT2;
|
|
10667
|
-
const v3 =
|
|
10795
|
+
const v3 = kmath.vector.add(sVertex, scaledPolarDeg(sRadius, halfAngle));
|
|
10668
10796
|
___default["default"].each([v1, v2], function (v) {
|
|
10669
10797
|
// @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
|
|
10670
10798
|
temp.push(graphie.scaledPath([v, v3], options.style));
|
|
@@ -10685,7 +10813,7 @@ ___default["default"].extend(GraphUtils.Graphie.prototype, {
|
|
|
10685
10813
|
text = text.replace(match[0], KhanMath.toFixedApprox(angle, precision) + "^{\\circ}");
|
|
10686
10814
|
}
|
|
10687
10815
|
const sOffset = scaledPolarDeg(sRadius + 15, halfAngle);
|
|
10688
|
-
const sPosition =
|
|
10816
|
+
const sPosition = kmath.vector.add(sVertex, sOffset);
|
|
10689
10817
|
const position = graphie.unscalePoint(sPosition);
|
|
10690
10818
|
|
|
10691
10819
|
// Reuse label if possible
|
|
@@ -10736,7 +10864,7 @@ ___default["default"].extend(GraphUtils.Graphie.prototype, {
|
|
|
10736
10864
|
const sOffset = sSpacing * (i - (n - 1) / 2);
|
|
10737
10865
|
const sOffsetVector = scaledPolarRad(sOffset, parallelAngle);
|
|
10738
10866
|
const sHeightVector = scaledPolarRad(sHeight, perpendicularAngle);
|
|
10739
|
-
const sPath = [
|
|
10867
|
+
const sPath = [kmath.vector.add(sMidpoint, sOffsetVector, sHeightVector), kmath.vector.add(sMidpoint, sOffsetVector, reverseVector(sHeightVector))];
|
|
10740
10868
|
|
|
10741
10869
|
// @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
|
|
10742
10870
|
temp.push(graphie.scaledPath(sPath, style));
|
|
@@ -10763,7 +10891,7 @@ ___default["default"].extend(GraphUtils.Graphie.prototype, {
|
|
|
10763
10891
|
if (start !== p1) {
|
|
10764
10892
|
sOffsetVector = reverseVector(sOffsetVector);
|
|
10765
10893
|
}
|
|
10766
|
-
const sEnd =
|
|
10894
|
+
const sEnd = kmath.vector.add(sMidpoint, sOffsetVector);
|
|
10767
10895
|
|
|
10768
10896
|
// @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
|
|
10769
10897
|
temp.push(graphie.scaledPath([sStart, sEnd], style));
|
|
@@ -10779,7 +10907,7 @@ ___default["default"].extend(GraphUtils.Graphie.prototype, {
|
|
|
10779
10907
|
}
|
|
10780
10908
|
const sOffset = 20;
|
|
10781
10909
|
const sOffsetVector = scaledPolarRad(sOffset, perpendicularAngle);
|
|
10782
|
-
const sPosition =
|
|
10910
|
+
const sPosition = kmath.vector.add(sMidpoint, sOffsetVector);
|
|
10783
10911
|
const position = graphie.unscalePoint(sPosition);
|
|
10784
10912
|
|
|
10785
10913
|
// Reuse label if possible
|
|
@@ -10837,7 +10965,7 @@ ___default["default"].extend(GraphUtils.Graphie.prototype, {
|
|
|
10837
10965
|
}
|
|
10838
10966
|
const sRadius = 10 + scaledDistanceFromAngle(360 - angle);
|
|
10839
10967
|
const sOffsetVector = scaledPolarDeg(sRadius, halfAngle);
|
|
10840
|
-
const sPosition =
|
|
10968
|
+
const sPosition = kmath.vector.add(sVertex, sOffsetVector);
|
|
10841
10969
|
const position = graphie.unscalePoint(sPosition);
|
|
10842
10970
|
|
|
10843
10971
|
// Reuse label if possible
|
|
@@ -12688,7 +12816,7 @@ ___default["default"].extend(GraphUtils.Graphie.prototype, {
|
|
|
12688
12816
|
// @ts-expect-error - TS2350 - Only a void function can be called with the 'new' keyword.
|
|
12689
12817
|
return new Ruler(this, options || {});
|
|
12690
12818
|
},
|
|
12691
|
-
addPoints:
|
|
12819
|
+
addPoints: kmath.vector.add
|
|
12692
12820
|
});
|
|
12693
12821
|
function Protractor(graph, center) {
|
|
12694
12822
|
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
@@ -13042,7 +13170,7 @@ function Ruler(graphie, options) {
|
|
|
13042
13170
|
set.push(label);
|
|
13043
13171
|
}
|
|
13044
13172
|
});
|
|
13045
|
-
const mouseTarget = graphie.mouselayer.path(
|
|
13173
|
+
const mouseTarget = graphie.mouselayer.path(graphie.svgPath([leftBottom, [left, top], rightTop, [right, bottom], /* closed */true]));
|
|
13046
13174
|
mouseTarget.attr({
|
|
13047
13175
|
fill: "#000",
|
|
13048
13176
|
opacity: 0,
|
|
@@ -13286,12 +13414,12 @@ ___default["default"].extend(MovableAngle.prototype, {
|
|
|
13286
13414
|
points[1].onMove = function (x, y) {
|
|
13287
13415
|
const oldVertex = points[1].coord;
|
|
13288
13416
|
const newVertex = [x, y];
|
|
13289
|
-
const delta =
|
|
13417
|
+
const delta = kmath.vector.add(newVertex, reverseVector(oldVertex));
|
|
13290
13418
|
let valid = true;
|
|
13291
13419
|
const newPoints = {};
|
|
13292
13420
|
___default["default"].each([0, 2], function (i) {
|
|
13293
13421
|
const oldPoint = points[i].coord;
|
|
13294
|
-
let newPoint =
|
|
13422
|
+
let newPoint = kmath.vector.add(oldPoint, delta);
|
|
13295
13423
|
let angle = GraphUtils.findAngle(newVertex, newPoint);
|
|
13296
13424
|
angle *= Math.PI / 180;
|
|
13297
13425
|
newPoint = graphie.constrainToBoundsOnAngle(newPoint, 10, angle);
|
|
@@ -13328,7 +13456,7 @@ ___default["default"].extend(MovableAngle.prototype, {
|
|
|
13328
13456
|
let angle = GraphUtils.findAngle(newPoint, vertex);
|
|
13329
13457
|
angle = Math.round((angle - snapOffset) / snap) * snap + snapOffset;
|
|
13330
13458
|
const distance = GraphUtils.getDistance(newPoint, vertex);
|
|
13331
|
-
return
|
|
13459
|
+
return kmath.vector.add(vertex, polar(distance, angle));
|
|
13332
13460
|
}
|
|
13333
13461
|
return true;
|
|
13334
13462
|
};
|
|
@@ -13479,7 +13607,7 @@ ___default["default"].extend(MovableAngle.prototype, {
|
|
|
13479
13607
|
|
|
13480
13608
|
/* eslint-disable @babel/no-invalid-this */
|
|
13481
13609
|
const nestedMap$1 = Util.nestedMap;
|
|
13482
|
-
const deepEq$
|
|
13610
|
+
const deepEq$3 = Util.deepEq;
|
|
13483
13611
|
|
|
13484
13612
|
/**
|
|
13485
13613
|
* A base class for all Graphie Movables
|
|
@@ -13576,7 +13704,7 @@ const createSimpleClass = function (addFunction) {
|
|
|
13576
13704
|
this._prevProps = this.props;
|
|
13577
13705
|
},
|
|
13578
13706
|
modify: function (graphie) {
|
|
13579
|
-
if (!deepEq$
|
|
13707
|
+
if (!deepEq$3(this.props, this._prevProps)) {
|
|
13580
13708
|
this.remove();
|
|
13581
13709
|
this.add(graphie);
|
|
13582
13710
|
this._prevProps = this.props;
|
|
@@ -16039,7 +16167,7 @@ var Movables = {
|
|
|
16039
16167
|
const GraphieMovable = GraphieClasses.GraphieMovable;
|
|
16040
16168
|
const createGraphie = GraphUtils.createGraphie;
|
|
16041
16169
|
const {
|
|
16042
|
-
deepEq: deepEq$
|
|
16170
|
+
deepEq: deepEq$2,
|
|
16043
16171
|
nestedMap
|
|
16044
16172
|
} = Util;
|
|
16045
16173
|
const {
|
|
@@ -16074,7 +16202,7 @@ class Graphie extends React__namespace.Component {
|
|
|
16074
16202
|
if (this.props.setup !== prevProps.setup) {
|
|
16075
16203
|
Log.error("<Graphie> was given a new setup function. " + "This is a bad idea; please refactor your code to give " + "the same setup function reference to <Graphie> on " + "every render.", Errors.Internal);
|
|
16076
16204
|
}
|
|
16077
|
-
if (!deepEq$
|
|
16205
|
+
if (!deepEq$2(this.props.options, prevProps.options) || !deepEq$2(this.props.box, prevProps.box) || !deepEq$2(this.props.range, prevProps.range)) {
|
|
16078
16206
|
this._setupGraphie();
|
|
16079
16207
|
}
|
|
16080
16208
|
this._updateMovables();
|
|
@@ -19164,10 +19292,19 @@ class Renderer extends React__namespace.Component {
|
|
|
19164
19292
|
});
|
|
19165
19293
|
};
|
|
19166
19294
|
_getAllWidgetsStartProps = (allWidgetInfo, props) => {
|
|
19167
|
-
|
|
19168
|
-
|
|
19169
|
-
|
|
19295
|
+
const {
|
|
19296
|
+
apiOptions,
|
|
19297
|
+
problemNum
|
|
19298
|
+
} = props;
|
|
19299
|
+
const widgetsStartProps = {};
|
|
19300
|
+
wonderStuffCore.entries(allWidgetInfo).forEach(_ref => {
|
|
19301
|
+
let [key, widgetInfo] = _ref;
|
|
19302
|
+
widgetsStartProps[key] = getRendererPropsForWidgetInfo(widgetInfo, problemNum);
|
|
19170
19303
|
});
|
|
19304
|
+
|
|
19305
|
+
// Call the onWidgetStartProps callback if it exists
|
|
19306
|
+
apiOptions?.onWidgetStartProps?.(widgetsStartProps);
|
|
19307
|
+
return widgetsStartProps;
|
|
19171
19308
|
};
|
|
19172
19309
|
|
|
19173
19310
|
// This is only used in _getWidgetInfo as a fallback if widgetId
|
|
@@ -19735,10 +19872,10 @@ class Renderer extends React__namespace.Component {
|
|
|
19735
19872
|
}, /*#__PURE__*/React__namespace.createElement(ErrorBoundary, null, /*#__PURE__*/React__namespace.createElement("div", {
|
|
19736
19873
|
className: "perseus-block-math-inner",
|
|
19737
19874
|
style: innerStyle
|
|
19738
|
-
}, /*#__PURE__*/React__namespace.createElement(context$1.Consumer, null,
|
|
19875
|
+
}, /*#__PURE__*/React__namespace.createElement(context$1.Consumer, null, _ref2 => {
|
|
19739
19876
|
let {
|
|
19740
19877
|
setAssetStatus
|
|
19741
|
-
} =
|
|
19878
|
+
} = _ref2;
|
|
19742
19879
|
return /*#__PURE__*/React__namespace.createElement(Tex, {
|
|
19743
19880
|
setAssetStatus: setAssetStatus
|
|
19744
19881
|
}, content);
|
|
@@ -19760,10 +19897,10 @@ class Renderer extends React__namespace.Component {
|
|
|
19760
19897
|
// wrap to the next line
|
|
19761
19898
|
whiteSpace: "nowrap"
|
|
19762
19899
|
}
|
|
19763
|
-
}, /*#__PURE__*/React__namespace.createElement(ErrorBoundary, null, /*#__PURE__*/React__namespace.createElement("span", null), /*#__PURE__*/React__namespace.createElement(context$1.Consumer, null,
|
|
19900
|
+
}, /*#__PURE__*/React__namespace.createElement(ErrorBoundary, null, /*#__PURE__*/React__namespace.createElement("span", null), /*#__PURE__*/React__namespace.createElement(context$1.Consumer, null, _ref3 => {
|
|
19764
19901
|
let {
|
|
19765
19902
|
setAssetStatus
|
|
19766
|
-
} =
|
|
19903
|
+
} = _ref3;
|
|
19767
19904
|
return /*#__PURE__*/React__namespace.createElement(Tex, {
|
|
19768
19905
|
onRender: this.props.onRender,
|
|
19769
19906
|
setAssetStatus: setAssetStatus
|
|
@@ -19791,10 +19928,10 @@ class Renderer extends React__namespace.Component {
|
|
|
19791
19928
|
const responsive = !state.inTable;
|
|
19792
19929
|
return /*#__PURE__*/React__namespace.createElement(ErrorBoundary, {
|
|
19793
19930
|
key: state.key
|
|
19794
|
-
}, /*#__PURE__*/React__namespace.createElement(context$1.Consumer, null,
|
|
19931
|
+
}, /*#__PURE__*/React__namespace.createElement(context$1.Consumer, null, _ref4 => {
|
|
19795
19932
|
let {
|
|
19796
19933
|
setAssetStatus
|
|
19797
|
-
} =
|
|
19934
|
+
} = _ref4;
|
|
19798
19935
|
return /*#__PURE__*/React__namespace.createElement(SvgImage, _extends({
|
|
19799
19936
|
setAssetStatus: setAssetStatus
|
|
19800
19937
|
// @ts-expect-error - TS2322 - Type 'string | null | undefined' is not assignable to type 'string | undefined'.
|
|
@@ -25554,7 +25691,7 @@ const DEFAULT_BACKGROUND_IMAGE = {
|
|
|
25554
25691
|
|
|
25555
25692
|
// TODO(charlie): These really need to go into a utility file as they're being
|
|
25556
25693
|
// used by both interactive-graph and now grapher.
|
|
25557
|
-
function canonicalSineCoefficients
|
|
25694
|
+
function canonicalSineCoefficients(coeffs) {
|
|
25558
25695
|
// For a curve of the form f(x) = a * Sin(b * x - c) + d,
|
|
25559
25696
|
// this function ensures that a, b > 0, and c is its
|
|
25560
25697
|
// smallest possible positive value.
|
|
@@ -25735,7 +25872,7 @@ const Sinusoid = ___default["default"].extend({}, PlotDefaults, {
|
|
|
25735
25872
|
return "y = " + a.toFixed(3) + " sin(" + b.toFixed(3) + "x - " + c.toFixed(3) + ") + " + d.toFixed(3);
|
|
25736
25873
|
},
|
|
25737
25874
|
areEqual: function (coeffs1, coeffs2) {
|
|
25738
|
-
return Util.deepEq(canonicalSineCoefficients
|
|
25875
|
+
return Util.deepEq(canonicalSineCoefficients(coeffs1), canonicalSineCoefficients(coeffs2));
|
|
25739
25876
|
}
|
|
25740
25877
|
});
|
|
25741
25878
|
const Tangent = ___default["default"].extend({}, PlotDefaults, {
|
|
@@ -27970,213 +28107,6 @@ class Graph extends React__namespace.Component {
|
|
|
27970
28107
|
}
|
|
27971
28108
|
}
|
|
27972
28109
|
|
|
27973
|
-
/**
|
|
27974
|
-
* A collection of geomtry-related utility functions
|
|
27975
|
-
*/
|
|
27976
|
-
const {
|
|
27977
|
-
eq: eq$1,
|
|
27978
|
-
deepEq: deepEq$2
|
|
27979
|
-
} = Util;
|
|
27980
|
-
|
|
27981
|
-
// This should really be a readonly tuple of [number, number]
|
|
27982
|
-
|
|
27983
|
-
// a, b, c
|
|
27984
|
-
|
|
27985
|
-
function sign(val) {
|
|
27986
|
-
if (eq$1(val, 0)) {
|
|
27987
|
-
return 0;
|
|
27988
|
-
}
|
|
27989
|
-
return val > 0 ? 1 : -1;
|
|
27990
|
-
}
|
|
27991
|
-
function ccw(a, b, c) {
|
|
27992
|
-
return (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1]);
|
|
27993
|
-
}
|
|
27994
|
-
function collinear(a, b, c) {
|
|
27995
|
-
return eq$1(ccw(a, b, c), 0);
|
|
27996
|
-
}
|
|
27997
|
-
|
|
27998
|
-
// Given rect bounding points A and B, whether point C is inside the rect
|
|
27999
|
-
function pointInRect(a, b, c) {
|
|
28000
|
-
return c[0] <= Math.max(a[0], b[0]) && c[0] >= Math.min(a[0], b[0]) && c[1] <= Math.max(a[1], b[1]) && c[1] >= Math.min(a[1], b[1]);
|
|
28001
|
-
}
|
|
28002
|
-
|
|
28003
|
-
// Whether line segment AB intersects line segment CD
|
|
28004
|
-
// http://www.geeksforgeeks.org/check-if-two-given-line-segments-intersect/
|
|
28005
|
-
function intersects(ab, cd) {
|
|
28006
|
-
const triplets = [[ab[0], ab[1], cd[0]], [ab[0], ab[1], cd[1]], [cd[0], cd[1], ab[0]], [cd[0], cd[1], ab[1]]];
|
|
28007
|
-
const orientations = ___default["default"].map(triplets, function (triplet) {
|
|
28008
|
-
return sign(ccw(...triplet));
|
|
28009
|
-
});
|
|
28010
|
-
if (orientations[0] !== orientations[1] && orientations[2] !== orientations[3]) {
|
|
28011
|
-
return true;
|
|
28012
|
-
}
|
|
28013
|
-
for (let i = 0; i < 4; i++) {
|
|
28014
|
-
if (orientations[i] === 0 && pointInRect(...triplets[i])) {
|
|
28015
|
-
return true;
|
|
28016
|
-
}
|
|
28017
|
-
}
|
|
28018
|
-
return false;
|
|
28019
|
-
}
|
|
28020
|
-
function vector(a, b) {
|
|
28021
|
-
return ___default["default"].map(___default["default"].zip(a, b), function (pair) {
|
|
28022
|
-
return pair[0] - pair[1];
|
|
28023
|
-
});
|
|
28024
|
-
}
|
|
28025
|
-
function magnitude(v) {
|
|
28026
|
-
return Math.sqrt(___default["default"].reduce(v, function (memo, el) {
|
|
28027
|
-
// @ts-expect-error - TS2345 - Argument of type 'Coord' is not assignable to parameter of type 'number'.
|
|
28028
|
-
return memo + Math.pow(el, 2);
|
|
28029
|
-
}, 0));
|
|
28030
|
-
}
|
|
28031
|
-
function dotProduct(a, b) {
|
|
28032
|
-
return ___default["default"].reduce(___default["default"].zip(a, b), function (memo, pair) {
|
|
28033
|
-
return memo + pair[0] * pair[1];
|
|
28034
|
-
}, 0);
|
|
28035
|
-
}
|
|
28036
|
-
function sideLengths(coords) {
|
|
28037
|
-
const segments = ___default["default"].zip(coords, rotate(coords));
|
|
28038
|
-
return segments.map(function (segment) {
|
|
28039
|
-
// @ts-expect-error - TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'readonly Coord[]'. | TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
|
|
28040
|
-
return magnitude(vector(...segment));
|
|
28041
|
-
});
|
|
28042
|
-
}
|
|
28043
|
-
|
|
28044
|
-
// Based on http://math.stackexchange.com/a/151149
|
|
28045
|
-
function angleMeasures(coords) {
|
|
28046
|
-
const triplets = ___default["default"].zip(rotate(coords, -1), coords, rotate(coords, 1));
|
|
28047
|
-
const offsets = ___default["default"].map(triplets, function (triplet) {
|
|
28048
|
-
const p = vector(triplet[1], triplet[0]);
|
|
28049
|
-
const q = vector(triplet[2], triplet[1]);
|
|
28050
|
-
// @ts-expect-error - TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'Coord'. | TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'readonly Coord[]'. | TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'readonly Coord[]'.
|
|
28051
|
-
const raw = Math.acos(dotProduct(p, q) / (magnitude(p) * magnitude(q)));
|
|
28052
|
-
// @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
|
|
28053
|
-
return sign(ccw(...triplet)) > 0 ? raw : -raw;
|
|
28054
|
-
});
|
|
28055
|
-
const sum = ___default["default"].reduce(offsets, function (memo, arg) {
|
|
28056
|
-
return memo + arg;
|
|
28057
|
-
}, 0);
|
|
28058
|
-
return ___default["default"].map(offsets, function (offset) {
|
|
28059
|
-
return sum > 0 ? Math.PI - offset : Math.PI + offset;
|
|
28060
|
-
});
|
|
28061
|
-
}
|
|
28062
|
-
|
|
28063
|
-
// Whether two polygons are similar (or if specified, congruent)
|
|
28064
|
-
function similar(coords1, coords2, tolerance) {
|
|
28065
|
-
if (coords1.length !== coords2.length) {
|
|
28066
|
-
return false;
|
|
28067
|
-
}
|
|
28068
|
-
const n = coords1.length;
|
|
28069
|
-
const angles1 = angleMeasures(coords1);
|
|
28070
|
-
const angles2 = angleMeasures(coords2);
|
|
28071
|
-
const sides1 = sideLengths(coords1);
|
|
28072
|
-
const sides2 = sideLengths(coords2);
|
|
28073
|
-
for (let i = 0; i < 2 * n; i++) {
|
|
28074
|
-
let angles = angles2.slice();
|
|
28075
|
-
let sides = sides2.slice();
|
|
28076
|
-
|
|
28077
|
-
// Reverse angles and sides to allow matching reflected polygons
|
|
28078
|
-
if (i >= n) {
|
|
28079
|
-
angles.reverse();
|
|
28080
|
-
sides.reverse();
|
|
28081
|
-
// Since sides are calculated from two coordinates,
|
|
28082
|
-
// simply reversing results in an off by one error
|
|
28083
|
-
// @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
|
|
28084
|
-
sides = rotate(sides, 1);
|
|
28085
|
-
}
|
|
28086
|
-
|
|
28087
|
-
// @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
|
|
28088
|
-
angles = rotate(angles, i);
|
|
28089
|
-
// @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
|
|
28090
|
-
sides = rotate(sides, i);
|
|
28091
|
-
if (deepEq$2(angles1, angles)) {
|
|
28092
|
-
const sidePairs = ___default["default"].zip(sides1, sides);
|
|
28093
|
-
const factors = ___default["default"].map(sidePairs, function (pair) {
|
|
28094
|
-
return pair[0] / pair[1];
|
|
28095
|
-
});
|
|
28096
|
-
const same = ___default["default"].all(factors, function (factor) {
|
|
28097
|
-
return eq$1(factors[0], factor);
|
|
28098
|
-
});
|
|
28099
|
-
const congruentEnough = ___default["default"].all(sidePairs, function (pair) {
|
|
28100
|
-
return kmath.number.equal(pair[0], pair[1], tolerance);
|
|
28101
|
-
});
|
|
28102
|
-
if (same && congruentEnough) {
|
|
28103
|
-
return true;
|
|
28104
|
-
}
|
|
28105
|
-
}
|
|
28106
|
-
}
|
|
28107
|
-
return false;
|
|
28108
|
-
}
|
|
28109
|
-
|
|
28110
|
-
// Given triangle with sides ABC return angle opposite side C in degrees
|
|
28111
|
-
function lawOfCosines(a, b, c) {
|
|
28112
|
-
return Math.acos((a * a + b * b - c * c) / (2 * a * b)) * 180 / Math.PI;
|
|
28113
|
-
}
|
|
28114
|
-
function canonicalSineCoefficients(_ref) {
|
|
28115
|
-
let [amplitude, angularFrequency, phase, verticalOffset] = _ref;
|
|
28116
|
-
// For a curve of the form f(x) = a * Sin(b * x - c) + d,
|
|
28117
|
-
// this function ensures that a, b > 0, and c is its
|
|
28118
|
-
// smallest possible positive value.
|
|
28119
|
-
|
|
28120
|
-
// Guarantee a > 0
|
|
28121
|
-
if (amplitude < 0) {
|
|
28122
|
-
amplitude *= -1;
|
|
28123
|
-
angularFrequency *= -1;
|
|
28124
|
-
phase *= -1;
|
|
28125
|
-
}
|
|
28126
|
-
const period = 2 * Math.PI;
|
|
28127
|
-
// Guarantee b > 0
|
|
28128
|
-
if (angularFrequency < 0) {
|
|
28129
|
-
angularFrequency *= -1;
|
|
28130
|
-
phase *= -1;
|
|
28131
|
-
phase += period / 2;
|
|
28132
|
-
}
|
|
28133
|
-
|
|
28134
|
-
// Guarantee c is smallest possible positive value
|
|
28135
|
-
while (phase > 0) {
|
|
28136
|
-
phase -= period;
|
|
28137
|
-
}
|
|
28138
|
-
while (phase < 0) {
|
|
28139
|
-
phase += period;
|
|
28140
|
-
}
|
|
28141
|
-
return [amplitude, angularFrequency, phase, verticalOffset];
|
|
28142
|
-
}
|
|
28143
|
-
|
|
28144
|
-
// e.g. rotate([1, 2, 3]) -> [2, 3, 1]
|
|
28145
|
-
function rotate(array, n) {
|
|
28146
|
-
n = typeof n === "undefined" ? 1 : n % array.length;
|
|
28147
|
-
return array.slice(n).concat(array.slice(0, n));
|
|
28148
|
-
}
|
|
28149
|
-
function getLineEquation(first, second) {
|
|
28150
|
-
if (eq$1(first[0], second[0])) {
|
|
28151
|
-
return "x = " + first[0].toFixed(3);
|
|
28152
|
-
}
|
|
28153
|
-
const m = (second[1] - first[1]) / (second[0] - first[0]);
|
|
28154
|
-
const b = first[1] - m * first[0];
|
|
28155
|
-
return "y = " + m.toFixed(3) + "x + " + b.toFixed(3);
|
|
28156
|
-
}
|
|
28157
|
-
|
|
28158
|
-
// Stolen from the wikipedia article
|
|
28159
|
-
// http://en.wikipedia.org/wiki/Line-line_intersection
|
|
28160
|
-
function getLineIntersection(
|
|
28161
|
-
// TODO(LP-10725): update these to be 2-tuples
|
|
28162
|
-
firstPoints, secondPoints) {
|
|
28163
|
-
const x1 = firstPoints[0][0];
|
|
28164
|
-
const y1 = firstPoints[0][1];
|
|
28165
|
-
const x2 = firstPoints[1][0];
|
|
28166
|
-
const y2 = firstPoints[1][1];
|
|
28167
|
-
const x3 = secondPoints[0][0];
|
|
28168
|
-
const y3 = secondPoints[0][1];
|
|
28169
|
-
const x4 = secondPoints[1][0];
|
|
28170
|
-
const y4 = secondPoints[1][1];
|
|
28171
|
-
const determinant = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
|
|
28172
|
-
if (Math.abs(determinant) < 1e-9) {
|
|
28173
|
-
return "Lines are parallel";
|
|
28174
|
-
}
|
|
28175
|
-
const x = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / determinant;
|
|
28176
|
-
const y = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / determinant;
|
|
28177
|
-
return "Intersection: (" + x.toFixed(3) + ", " + y.toFixed(3) + ")";
|
|
28178
|
-
}
|
|
28179
|
-
|
|
28180
28110
|
/* eslint-disable @babel/no-invalid-this, react/no-unsafe, react/sort-comp */
|
|
28181
28111
|
const {
|
|
28182
28112
|
DeprecationMixin
|
|
@@ -29849,8 +29779,8 @@ class InteractiveGraph extends React__namespace.Component {
|
|
|
29849
29779
|
const correctCoeffs = this.getSinusoidCoefficients(
|
|
29850
29780
|
// @ts-expect-error - TS2345 - Argument of type 'readonly Coord[] | undefined' is not assignable to parameter of type 'readonly Coord[]'.
|
|
29851
29781
|
rubric.correct.coords);
|
|
29852
|
-
const canonicalGuessCoeffs = canonicalSineCoefficients(guessCoeffs);
|
|
29853
|
-
const canonicalCorrectCoeffs = canonicalSineCoefficients(correctCoeffs);
|
|
29782
|
+
const canonicalGuessCoeffs = canonicalSineCoefficients$1(guessCoeffs);
|
|
29783
|
+
const canonicalCorrectCoeffs = canonicalSineCoefficients$1(correctCoeffs);
|
|
29854
29784
|
// If the canonical coefficients match, it's correct.
|
|
29855
29785
|
if (deepEq$1(canonicalGuessCoeffs, canonicalCorrectCoeffs)) {
|
|
29856
29786
|
return {
|
|
@@ -31569,7 +31499,6 @@ class LabelImage extends React__namespace.Component {
|
|
|
31569
31499
|
|
|
31570
31500
|
// Update Perseus widget state with user selected answers without
|
|
31571
31501
|
// triggering interaction events for listeners.
|
|
31572
|
-
// @ts-expect-error - TS2345 - Argument of type 'null' is not assignable to parameter of type '(() => unknown) | undefined'.
|
|
31573
31502
|
onChange({
|
|
31574
31503
|
markers: updatedMarkers
|
|
31575
31504
|
}, null, true);
|
|
@@ -41687,7 +41616,7 @@ const init = function (options) {
|
|
|
41687
41616
|
|
|
41688
41617
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
41689
41618
|
const libName = "@khanacademy/perseus";
|
|
41690
|
-
const libVersion = "19.
|
|
41619
|
+
const libVersion = "19.1.1";
|
|
41691
41620
|
perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
41692
41621
|
|
|
41693
41622
|
const apiVersion = {
|
|
@@ -44659,15 +44588,15 @@ function injectWidgets(content, widgets) {
|
|
|
44659
44588
|
}
|
|
44660
44589
|
|
|
44661
44590
|
/**
|
|
44662
|
-
* Helper to parse
|
|
44591
|
+
* Helper to parse PerseusItem JSON
|
|
44663
44592
|
* Why not just use JSON.parse? We want:
|
|
44664
44593
|
* - To make sure types are correct
|
|
44665
44594
|
* - To give us a central place to validate/transform output if needed
|
|
44666
|
-
* @param {string}
|
|
44595
|
+
* @param {string} json - the stringified PerseusItem JSON
|
|
44667
44596
|
* @returns {PerseusItem} the parsed PerseusItem object
|
|
44668
44597
|
*/
|
|
44669
|
-
function
|
|
44670
|
-
return JSON.parse(
|
|
44598
|
+
function parsePerseusItem(json) {
|
|
44599
|
+
return JSON.parse(json);
|
|
44671
44600
|
}
|
|
44672
44601
|
|
|
44673
44602
|
const EditorJsonify = {
|
|
@@ -44739,7 +44668,7 @@ exports.itemToTree = itemToTree;
|
|
|
44739
44668
|
exports.itemVersion = ItemVersion;
|
|
44740
44669
|
exports.libVersion = libVersion;
|
|
44741
44670
|
exports.mediaQueries = mediaQueries;
|
|
44742
|
-
exports.
|
|
44671
|
+
exports.parsePerseusItem = parsePerseusItem;
|
|
44743
44672
|
exports.plotterPlotTypes = plotterPlotTypes;
|
|
44744
44673
|
exports.preprocessTex = preprocessTex;
|
|
44745
44674
|
exports.registerAllWidgetsForTesting = registerAllWidgetsForTesting;
|