@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.
Files changed (42) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/es/index.css +209 -104
  3. package/dist/es/index.js +806 -877
  4. package/dist/es/index.js.map +1 -1
  5. package/dist/index.css +209 -104
  6. package/dist/index.d.ts +3 -2
  7. package/dist/index.js +849 -920
  8. package/dist/index.js.map +1 -1
  9. package/dist/mixins/changeable.d.ts +1 -1
  10. package/dist/perseus-api.d.ts +1 -0
  11. package/dist/renderer.d.ts +2 -4
  12. package/dist/server-item-renderer.d.ts +1 -0
  13. package/dist/types.d.ts +4 -0
  14. package/dist/util/geometry.d.ts +2 -0
  15. package/dist/util/graphie.d.ts +107 -1
  16. package/dist/util/math.d.ts +2 -0
  17. package/dist/util/parse-perseus-json.d.ts +3 -3
  18. package/dist/widgets/expression.d.ts +6 -0
  19. package/dist/widgets/label-image.d.ts +2 -0
  20. package/package.json +3 -4
  21. package/src/__testdata__/renderer.testdata.ts +152 -0
  22. package/src/__tests__/renderer.test.tsx +17 -0
  23. package/src/components/math-input.tsx +4 -6
  24. package/src/index.ts +8 -3
  25. package/src/mixins/changeable.ts +1 -1
  26. package/src/renderer.tsx +14 -8
  27. package/src/types.ts +9 -0
  28. package/src/util/geometry.test.ts +75 -0
  29. package/src/util/geometry.ts +19 -0
  30. package/src/util/graphie.test.ts +1291 -0
  31. package/src/util/graphie.ts +832 -784
  32. package/src/util/interactive.ts +23 -51
  33. package/src/util/math.test.ts +11 -0
  34. package/src/util/math.ts +8 -0
  35. package/src/util/parse-perseus-json.test.ts +3 -3
  36. package/src/util/parse-perseus-json.ts +4 -4
  37. package/src/widgets/__tests__/__snapshots__/input-number.test.ts.snap +4 -2
  38. package/src/widgets/__tests__/__snapshots__/numeric-input.test.ts.snap +4 -2
  39. package/src/widgets/__tests__/expression-mobile.test.tsx +8 -5
  40. package/src/widgets/expression.tsx +1 -23
  41. package/src/widgets/label-image.tsx +0 -1
  42. 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.__controller.cursor[mathInput.mathQuillInstance.L];
197
+ const left = mathField.cursor()[mathInput.mathQuillInstance.L];
198
198
  if (left && left.ctrlSeq === "\\cdot ") {
199
- mathField.__controller.backspace();
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(insertBraces(tex));
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
- $__default["default"].each(options, function (prop, val) {
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
- if (val.constructor === Array) {
8830
- // but don't mandate symmetric ranges
8831
- if (val[0].constructor !== Array) {
8832
- options[prop] = [[-val[0], val[0]], [-val[1], val[1]]];
8833
- }
8834
- } else if (typeof val === "number") {
8835
- options[prop] = [[-val, val], [-val, val]];
8836
- }
8764
+ options[prop] = normalizeRange(options[prop]);
8837
8765
  }
8838
- });
8839
- const range = options.range || [[-10, 10], [-10, 10]];
8840
- const gridRange = options.gridRange || options.range;
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 (!___default["default"].isArray(unityLabels)) {
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
- }, function () {
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 = $__default["default"].extend({}, this.currentStyle, processed);
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
- $__default["default"].extend(this.currentStyle, processed);
8982
+ Object.assign(this.currentStyle, processed);
9054
8983
  }
9055
- label(point, text, direction, latex) {}
9056
- grid(xr, yr, styleAttributes) {}
9057
-
9058
- // path is a stub that gets overwritten with a function from drawingTools
9059
- // in createGraphie
9060
- path(points) {}
9061
-
9062
- // line is a stub that gets overwritten with a function from drawingTools
9063
- // in createGraphie
9064
- line(start, end) {}
9065
- svgPath = (points, alreadyScaled) => {
9066
- return $__default["default"].map(points, (point, i) => {
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
- const processed = {};
9205
- $__default["default"].each(attrs || {}, function (key, value) {
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
- GraphUtils.Graphie = Graphie$1;
9220
- const labelDirections = {
9221
- center: [-0.5, -0.5],
9222
- above: [-0.5, -1.0],
9223
- "above right": [0.0, -1.0],
9224
- right: [0.0, -0.5],
9225
- "below right": [0.0, 0.0],
9226
- below: [-0.5, 0.0],
9227
- "below left": [-1.0, 0.0],
9228
- left: [-1.0, -0.5],
9229
- "above left": [-1.0, -1.0]
9230
- };
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
- * Safari applies some SVG-specific styles to things that are not SVGs, so we
9234
- * need to exclude those styles from things that are not SVGs.
9235
- *
9236
- * To see this behavior in action, open https://codepen.io/anon/pen/zENEoa in
9237
- * Safari.
9238
- *
9239
- * Usage `$.extend({}, someStyles, SVG_SPECIFIC_STYLE_MASK)`
9240
- */
9241
- const SVG_SPECIFIC_STYLE_MASK = {
9242
- "stroke-width": null
9243
- };
9244
- GraphUtils.createGraphie = function (el) {
9245
- const thisGraphie = new Graphie$1(el);
9246
- const setLabelMargins = function (span, size) {
9247
- const $span = $__default["default"](span);
9248
- const direction = $span.data("labelDirection");
9249
- let [width, height] = size;
9250
- // This can happen when a span
9251
- // is invisible but we still want to update the CSS. At worst, we will
9252
- // be off by a few pixels instead of in a different position entirely.
9253
- if (width === 0 && height === 0) {
9254
- [width, height] = [1, 1];
9255
- Log.log("Label size was 0x0 in graphie.js; using 1x1 instead");
9256
- }
9257
- $span.css("visibility", "");
9258
- if (typeof direction === "number") {
9259
- const x = Math.cos(direction);
9260
- const y = Math.sin(direction);
9261
- const scale = Math.min(width / 2 / Math.abs(x), height / 2 / Math.abs(y));
9262
- $span.css({
9263
- marginLeft: -width / 2 + x * scale,
9264
- marginTop: -height / 2 - y * scale
9265
- });
9266
- } else {
9267
- const multipliers = labelDirections[direction || "center"];
9268
- $span.css({
9269
- marginLeft: Math.round(width * multipliers[0]),
9270
- marginTop: Math.round(height * multipliers[1])
9271
- });
9272
- }
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
- function rect(x, y, width, height) {
9320
- // Raphael needs (x, y) to be coordinate of upper left corner
9321
- const corner = thisGraphie.scalePoint([x, y + height]);
9322
- const dims = thisGraphie.scaleVector([width, height]);
9323
- const elem = thisGraphie.raphael.rect(...corner.concat(dims));
9324
- if (thisGraphie.isMobile) {
9325
- elem.node.style.shapeRendering = "crispEdges";
9326
- }
9327
- return elem;
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
- function ellipse(center, radii) {
9330
- return thisGraphie.raphael.ellipse(...thisGraphie.scalePoint(center).concat(thisGraphie.scaleVector(radii)));
9039
+ ellipse(center, radii, style) {
9040
+ return this.withStyle(style, () => this.raphael.ellipse(...this.scalePoint(center).concat(this.scaleVector(radii))));
9331
9041
  }
9332
- function fixedEllipse(center,
9042
+ fixedEllipse(center,
9333
9043
  // Different type than Coord, this is radiusX, radiusY
9334
- radii, maxScale, padding) {
9335
- // Scale point and radius
9336
- const scaledPoint = thisGraphie.scalePoint(center);
9337
- const scaledRadii = thisGraphie.scaleVector(radii);
9338
- const width = 2 * scaledRadii[0] * maxScale + padding;
9339
- const height = 2 * scaledRadii[1] * maxScale + padding;
9340
-
9341
- // Calculate absolute left, top
9342
- const left = scaledPoint[0] - width / 2;
9343
- const top = scaledPoint[1] - height / 2;
9344
-
9345
- // Wrap in <div>
9346
- const wrapper = document.createElement("div");
9347
- $__default["default"](wrapper).css({
9348
- position: "absolute",
9349
- width: width + "px",
9350
- height: height + "px",
9351
- left: left + "px",
9352
- top: top + "px"
9353
- });
9354
- // wrapper.setAttribute("data-graphie-type", "ellipse");
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
- // Create Raphael canvas
9357
- const localRaphael = raphael.exports(wrapper, width, height);
9358
- const visibleShape = localRaphael.ellipse(width / 2, height / 2, scaledRadii[0], scaledRadii[1]);
9359
- return {
9360
- wrapper: wrapper,
9361
- visibleShape: visibleShape
9362
- };
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
- function arc(center, radius, startAngle, endAngle, sector) {
9365
- startAngle = (startAngle % 360 + 360) % 360;
9366
- endAngle = (endAngle % 360 + 360) % 360;
9367
- const cent = thisGraphie.scalePoint(center);
9368
- const radii = thisGraphie.scaleVector(radius);
9369
- const startVector = polar(radius, startAngle);
9370
- const endVector = polar(radius, endAngle);
9371
- const startPoint = thisGraphie.scalePoint([center[0] + startVector[0], center[1] + startVector[1]]);
9372
- const endPoint = thisGraphie.scalePoint([center[0] + endVector[0], center[1] + endVector[1]]);
9373
- const largeAngle = ((endAngle - startAngle) % 360 + 360) % 360 > 180;
9374
- return thisGraphie.raphael.path("M" + startPoint.join(" ") + "A" + radii.join(" ") + " 0 " + (
9375
- // ellipse rotation
9376
- largeAngle ? 1 : 0) + " 0 " +
9377
- // sweep flag
9378
- endPoint.join(" ") + (sector ? "L" + cent.join(" ") + "z" : ""));
9379
- }
9380
- function path(points) {
9381
- // @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
9382
- const p = thisGraphie.raphael.path(thisGraphie.svgPath(points));
9076
+ unstyledPath(points) {
9077
+ const p = this.raphael.path(this.svgPath(points));
9383
9078
  p.graphiePath = points;
9384
9079
  return p;
9385
9080
  }
9386
- function fixedPath(points, center, createPath) {
9387
- points = ___default["default"].map(points, thisGraphie.scalePoint);
9388
- center = center ? thisGraphie.scalePoint(center) : null;
9389
- createPath = createPath || thisGraphie.svgPath;
9390
- const pathLeft = ___default["default"].min(___default["default"].pluck(points, 0));
9391
- const pathRight = ___default["default"].max(___default["default"].pluck(points, 0));
9392
- const pathTop = ___default["default"].min(___default["default"].pluck(points, 1));
9393
- const pathBottom = ___default["default"].max(___default["default"].pluck(points, 1));
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 extraOffset = [pathLeft, pathTop];
9106
+ const topLeftOfBoundingBox = [pathLeft, pathTop];
9400
9107
 
9401
- // Apply padding and offset to points
9402
- points = ___default["default"].map(points, function (point) {
9403
- return kmath.vector.add(kmath.vector.subtract(point, extraOffset), kmath.vector.scale(padding, 0.5));
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 = extraOffset[0] - padding[0] / 2;
9410
- const top = extraOffset[1] - padding[1] / 2;
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
- function scaledPath(points) {
9437
- const p = thisGraphie.raphael.path(thisGraphie.svgPath(points, /* alreadyScaled */true));
9438
- p.graphiePath = points;
9439
- return p;
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
- function line(start, end) {
9442
- const l = path([start, end]);
9443
- if (thisGraphie.isMobile) {
9444
- l.node.style.shapeRendering = "crispEdges";
9445
- }
9446
- return l;
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
- function parabola(a, b, c) {
9449
- // Plot a parabola of the form: f(x) = (a * x + b) * x + c
9450
- return thisGraphie.raphael.path(thisGraphie.svgParabolaPath(a, b, c));
9160
+ parabola(a, b, c, style) {
9161
+ return this.withStyle(style, () => this.raphael.path(this.svgParabolaPath(a, b, c)));
9451
9162
  }
9452
- function fixedLine(start, end, thickness) {
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 = thisGraphie.scalePoint(start);
9458
- end = thisGraphie.scalePoint(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
- function sinusoid(a, b, c, d) {
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
- return thisGraphie.raphael.path(thisGraphie.svgSinusoidPath(a, b, c, d));
9501
- }
9502
- function grid(xr, yr) {
9503
- const step = thisGraphie.currentStyle.step || [1, 1];
9504
- const set = thisGraphie.raphael.set();
9505
- let x = step[0] * Math.ceil(xr[0] / step[0]);
9506
- for (; x <= xr[1]; x += step[0]) {
9507
- set.push(line([x, yr[0]], [x, yr[1]]));
9508
- }
9509
- let y = step[1] * Math.ceil(yr[0] / step[1]);
9510
- for (; y <= yr[1]; y += step[1]) {
9511
- set.push(line([xr[0], y], [xr[1], y]));
9512
- }
9513
- return set;
9514
- }
9515
- function label(point, text, direction, latex) {
9516
- latex = typeof latex === "undefined" || latex;
9517
- const $span = $__default["default"]("<span>").addClass("graphie-label");
9518
- const pad = thisGraphie.currentStyle["label-distance"];
9519
- $span.css($__default["default"].extend({}, {
9520
- position: "absolute",
9521
- padding: (pad != null ? pad : 7) + "px",
9522
- color: "black"
9523
- })).data("labelDirection", direction).appendTo(thisGraphie.el);
9524
-
9525
- // @ts-expect-error - TS2339 - Property 'setPosition' does not exist on type 'JQuery<HTMLElement>'.
9526
- $span.setPosition = function (point) {
9527
- const scaledPoint = thisGraphie.scalePoint(point);
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
- left: scaledPoint[0],
9530
- top: scaledPoint[1]
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
- // @ts-expect-error - TS2339 - Property 'setPosition' does not exist on type 'JQuery<HTMLElement>'.
9535
- $span.setPosition(point);
9536
- const span = $span[0];
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
- // @ts-expect-error - TS2339 - Property 'processMath' does not exist on type 'JQuery<HTMLElement>'.
9539
- $span.processMath = function (math, force) {
9540
- processMath$1(span, math, force, function () {
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
- // @ts-expect-error - TS2339 - Property 'processText' does not exist on type 'JQuery<HTMLElement>'.
9548
- $span.processText = function (text) {
9549
- $span.html(text);
9550
- const width = span.scrollWidth;
9551
- const height = span.scrollHeight;
9552
- setLabelMargins(span, [width, height]);
9553
- };
9554
- if (latex) {
9555
- // @ts-expect-error - TS2339 - Property 'processMath' does not exist on type 'JQuery<HTMLElement>'.
9556
- $span.processMath(text, /* force */false);
9557
- } else {
9558
- // @ts-expect-error - TS2339 - Property 'processText' does not exist on type 'JQuery<HTMLElement>'.
9559
- $span.processText(text);
9560
- }
9561
- return $span;
9562
- }
9563
- function plotParametric(fn, range, shade) {
9564
- let fn2 = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : t => [t, 0];
9565
- // Note: fn2 should only be set if 'shade' is true, as it denotes
9566
- // the function between which fn should have its area shaded.
9567
- // In general, plotParametric shouldn't be used to shade the area
9568
- // between two arbitrary parametrics functions over an interval,
9569
- // as the method assumes that fn and fn2 are both of the form
9570
- // fn(t) = (t, fn'(t)) for some initial fn'.
9571
-
9572
- // We truncate to 500,000, since anything bigger causes
9573
- // overflow in the firefox svg renderer. This is safe
9574
- // since 500,000 is outside the viewport anyway. We
9575
- // write these functions the way we do to handle undefined.
9576
- const clipper = xy => {
9577
- if (Math.abs(xy[1]) > 500000) {
9578
- return [xy[0], Math.min(Math.max(xy[1], -500000), 500000)];
9579
- }
9580
- return xy;
9581
- };
9582
- const clippedFn = x => clipper(fn(x));
9583
- const clippedFn2 = x => clipper(fn2(x));
9584
- if (!thisGraphie.currentStyle.strokeLinejoin) {
9585
- thisGraphie.currentStyle.strokeLinejoin = "round";
9586
- }
9587
- if (!thisGraphie.currentStyle.strokeLinecap) {
9588
- thisGraphie.currentStyle.strokeLinecap = "round";
9589
- }
9590
- const min = range[0];
9591
- const max = range[1];
9592
- let step = (max - min) / (thisGraphie.currentStyle["plot-points"] || 800);
9593
- if (step === 0) {
9594
- step = 1;
9595
- }
9596
- const paths = thisGraphie.raphael.set();
9597
- let points = [];
9598
- let lastDiff = GraphUtils.coordDiff(clippedFn(min), clippedFn2(min));
9599
- let lastFlip = min;
9600
- for (let t = min; t <= max; t += step) {
9601
- const top = clippedFn(t);
9602
- const bottom = clippedFn2(t);
9603
- const diff = GraphUtils.coordDiff(top, bottom);
9604
-
9605
- // Find points where it flips
9606
- // Create path that sketches area between the two functions
9607
- if (
9608
- // if there is an asymptote here, meaning that the graph
9609
- // switches signs and has a large difference
9610
- diff[1] < 0 !== lastDiff[1] < 0 && Math.abs(diff[1] - lastDiff[1]) > 2 * thisGraphie.drawingTransform().pixelsPerUnitY() ||
9611
- // or the function is undefined
9612
- isNaN(diff[1])) {
9613
- // split the path at this point, and draw it
9614
- if (shade) {
9615
- // @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
9616
- points.push(top);
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
- paths.push(path(points));
9626
- // restart the path, excluding this point
9627
- points = [];
9628
- if (shade) {
9629
- // @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
9630
- points.push(top);
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
- } else {
9633
- // otherwise, just add the point to the path
9634
- // @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
9635
- points.push(top);
9306
+ lastY = y;
9636
9307
  }
9637
- lastDiff = diff;
9638
- }
9639
- if (shade) {
9640
- // backtrack to draw paired function
9641
- for (let u = max - step; u >= lastFlip; u -= step) {
9642
- // @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
9643
- points.push(clippedFn2(u));
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
- paths.push(path(points));
9647
- return paths;
9319
+ const parametricFn = x => [x, fn(x)];
9320
+ return this.plotParametric(parametricFn, range);
9321
+ });
9648
9322
  }
9649
- function plot(fn, range, swapAxes, shade, fn2) {
9650
- const min = range[0];
9651
- const max = range[1];
9652
- if (!thisGraphie.currentStyle["plot-points"]) {
9653
- thisGraphie.currentStyle["plot-points"] = 2 * (max - min) * thisGraphie.drawingTransform().pixelsPerUnitX();
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
- if (swapAxes) {
9656
- if (fn2) {
9657
- // TODO(charlie): support swapped axis area shading
9658
- throw new PerseusError("Can't shade area between functions with swapped axes.", Errors.Internal);
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
- return plotParametric(function (y) {
9661
- return [fn(y), y];
9662
- }, range, shade);
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
- if (fn2) {
9665
- if (shade) {
9666
- return plotParametric(function (x) {
9667
- return [x, fn(x)];
9668
- }, range, shade, function (x) {
9669
- return [x, fn2(x)];
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 plotParametric(function (x) {
9675
- return [x, fn(x)];
9676
- }, range, shade);
9473
+ return path;
9677
9474
  }
9678
- const drawingTools = {
9679
- circle,
9680
- rect,
9681
- ellipse,
9682
- fixedEllipse,
9683
- arc,
9684
- path,
9685
- fixedPath,
9686
- scaledPath,
9687
- line,
9688
- parabola,
9689
- fixedLine,
9690
- sinusoid,
9691
- grid,
9692
- label,
9693
- plotParametric,
9694
- plot
9695
- };
9696
- function graphify(drawingFn) {
9697
- return function () {
9698
- for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
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
- // The last argument is probably trying to change the style
9706
- if (typeof last === "object" && !___default["default"].isArray(last)) {
9707
- thisGraphie.currentStyle = {
9708
- ...thisGraphie.currentStyle,
9709
- ...thisGraphie.processAttributes(last)
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
- $__default["default"].each(drawingTools, function (name) {
9739
- thisGraphie[name] = graphify(drawingTools[name]);
9740
- });
9741
- return thisGraphie;
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
- /* eslint-disable @babel/no-invalid-this */
10389
+ /**
10390
+ * A collection of geomtry-related utility functions
10391
+ */
10460
10392
  const {
10461
- getCanUse3dTransform
10462
- } = InteractiveUtil;
10463
- function sum$1(array) {
10464
- return ___default["default"].reduce(array, function (memo, arg) {
10465
- return memo + arg;
10466
- }, 0);
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
- /* vector-add multiple [x, y] coords/vectors */
10479
- function addPoints() {
10480
- const zipped = ___default["default"].zip(...arguments);
10481
- return ___default["default"].map(zipped, sum$1);
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 reverseVector(vector) {
10484
- return ___default["default"].map(vector, function (coord) {
10485
- return coord * -1;
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 = addPoints(sVertex, scaledPolarDeg(sRadius, startAngle));
10665
- const v2 = addPoints(sVertex, scaledPolarDeg(sRadius, endAngle));
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 = addPoints(sVertex, scaledPolarDeg(sRadius, halfAngle));
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 = addPoints(sVertex, sOffset);
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 = [addPoints(sMidpoint, sOffsetVector, sHeightVector), addPoints(sMidpoint, sOffsetVector, reverseVector(sHeightVector))];
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 = addPoints(sMidpoint, sOffsetVector);
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 = addPoints(sMidpoint, sOffsetVector);
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 = addPoints(sVertex, sOffsetVector);
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: 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(GraphUtils.svgPath([leftBottom, [left, top], rightTop, [right, bottom], /* closed */true]));
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 = addPoints(newVertex, reverseVector(oldVertex));
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 = addPoints(oldPoint, delta);
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 addPoints(vertex, polar(distance, angle));
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$4 = Util.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$4(this.props, this._prevProps)) {
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$3,
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$3(this.props.options, prevProps.options) || !deepEq$3(this.props.box, prevProps.box) || !deepEq$3(this.props.range, prevProps.range)) {
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
- // @ts-expect-error - TS2345 - Argument of type '{ [key: string]: PerseusWidget; }' is not assignable to parameter of type 'Partial<Record<string, CategorizerWidget>>'.
19168
- return mapObject$1(allWidgetInfo, widgetInfo => {
19169
- return getRendererPropsForWidgetInfo(widgetInfo, props.problemNum);
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, _ref => {
19875
+ }, /*#__PURE__*/React__namespace.createElement(context$1.Consumer, null, _ref2 => {
19739
19876
  let {
19740
19877
  setAssetStatus
19741
- } = _ref;
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, _ref2 => {
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
- } = _ref2;
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, _ref3 => {
19931
+ }, /*#__PURE__*/React__namespace.createElement(context$1.Consumer, null, _ref4 => {
19795
19932
  let {
19796
19933
  setAssetStatus
19797
- } = _ref3;
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$1(coeffs) {
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$1(coeffs1), canonicalSineCoefficients$1(coeffs2));
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.0.0";
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 Perseus JSON
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} input - the stringified Perseus JSON
44595
+ * @param {string} json - the stringified PerseusItem JSON
44667
44596
  * @returns {PerseusItem} the parsed PerseusItem object
44668
44597
  */
44669
- function parsePerseusJSON(input) {
44670
- return JSON.parse(input);
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.parsePerseusJSON = parsePerseusJSON;
44671
+ exports.parsePerseusItem = parsePerseusItem;
44743
44672
  exports.plotterPlotTypes = plotterPlotTypes;
44744
44673
  exports.preprocessTex = preprocessTex;
44745
44674
  exports.registerAllWidgetsForTesting = registerAllWidgetsForTesting;