@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/es/index.js CHANGED
@@ -19,6 +19,7 @@ import PropTypes from 'prop-types';
19
19
  import * as ReactDOM from 'react-dom';
20
20
  import ReactDOM__default from 'react-dom';
21
21
  import { number, point, vector as vector$1, line } from '@khanacademy/kmath';
22
+ import { entries, keys } from '@khanacademy/wonder-stuff-core';
22
23
  import { CircularSpinner } from '@khanacademy/wonder-blocks-progress-spinner';
23
24
  import SimpleMarkdown from '@khanacademy/simple-markdown';
24
25
  import { pureMarkdownRules } from '@khanacademy/pure-markdown';
@@ -35,7 +36,6 @@ import { Popper } from 'react-popper';
35
36
  import createReactClass from 'create-react-class';
36
37
  import Link from '@khanacademy/wonder-blocks-link';
37
38
  import { addLibraryVersionToPerseusDebug } from '@khanacademy/perseus-core';
38
- import { keys } from '@khanacademy/wonder-stuff-core';
39
39
 
40
40
  function _extends() {
41
41
  _extends = Object.assign ? Object.assign.bind() : function (target) {
@@ -140,9 +140,9 @@ class MathInput extends React.Component {
140
140
  // We do this by modifying internal MathQuill state
141
141
  // directly, instead of waiting for `.latex()` to be
142
142
  // called in `componentDidUpdate()`.
143
- const left = mathField.__controller.cursor[mathQuillInstance.L];
143
+ const left = mathField.cursor()[mathQuillInstance.L];
144
144
  if (left && left.ctrlSeq === "\\cdot ") {
145
- mathField.__controller.backspace();
145
+ mathField.controller().backspace();
146
146
  mathField.cmd("\\times");
147
147
  }
148
148
  } else {
@@ -1202,6 +1202,12 @@ const KhanMath = {
1202
1202
  return String(number$1);
1203
1203
  }
1204
1204
  };
1205
+ function sum$1(array) {
1206
+ return array.reduce(add$3, 0);
1207
+ }
1208
+ function add$3(a, b) {
1209
+ return a + b;
1210
+ }
1205
1211
 
1206
1212
  /* eslint-disable no-useless-escape */
1207
1213
  const MAXERROR_EPSILON = Math.pow(2, -42);
@@ -1894,27 +1900,6 @@ const KhanAnswerTypes = {
1894
1900
  const _excluded$4 = ["scoring"];
1895
1901
  const ERROR_TITLE = i18n._("Oops!");
1896
1902
  const ERROR_MESSAGE = i18n._("Sorry, I don't understand that!");
1897
- const insertBraces = value => {
1898
- // HACK(alex): Make sure that all LaTeX super/subscripts are wrapped
1899
- // in curly braces to avoid the mismatch between KAS and LaTeX sup/sub
1900
- // parsing.
1901
- //
1902
- // What exactly is this mismatch? Due to its heritage of parsing plain
1903
- // text math from <OldExpression />, KAS parses "x^12" as x^(12).
1904
- // This is both generally what the user expects to happen, and is
1905
- // consistent with other computer algebra systems. It is NOT
1906
- // consistent with LaTeX however, where x^12 is equivalent to x^{1}2.
1907
- //
1908
- // Since the only LaTeX we parse comes from MathQuill, this wouldn't
1909
- // be a problem if MathQuill just always gave us the latter version
1910
- // (with explicit braces). However, instead it always gives the former.
1911
- // This behavior is baked in pretty deep; my naive attempts at changing
1912
- // it triggered all sorts of confusing errors. So instead we just make
1913
- // sure to add in any missing braces before grading MathQuill input.
1914
- //
1915
- // TODO(alex): Properly hack MathQuill to always use explicit braces.
1916
- return value.replace(/([_^])([^{])/g, "$1{$2}");
1917
- };
1918
1903
  const anglicizeOperators = tex => {
1919
1904
  // sen is used instead of sin in some languages, e.g. Portuguese.
1920
1905
  // To ensure that answers in various languages are graded correctly, we
@@ -1922,7 +1907,7 @@ const anglicizeOperators = tex => {
1922
1907
  return tex.replace(/\\operatorname{sen}/g, "\\sin ");
1923
1908
  };
1924
1909
  const normalizeTex = tex => {
1925
- return anglicizeOperators(insertBraces(tex));
1910
+ return anglicizeOperators(tex);
1926
1911
  };
1927
1912
  const deriveKeypadVersion = apiOptions => {
1928
1913
  // We can derive which version of the keypad is in use. This is
@@ -8700,54 +8685,6 @@ function polar(r, th) {
8700
8685
  th = th * Math.PI / 180;
8701
8686
  return [r[0] * Math.cos(th), r[1] * Math.sin(th)];
8702
8687
  }
8703
- const GraphUtils = {
8704
- unscaledSvgPath: function (points) {
8705
- // If this is an empty closed path, return "" instead of "z", which
8706
- // would give an error
8707
- if (points[0] === true) {
8708
- return "";
8709
- }
8710
- return $.map(points, function (point, i) {
8711
- if (point === true) {
8712
- return "z";
8713
- }
8714
- return (i === 0 ? "M" : "L") + point[0] + " " + point[1];
8715
- }).join("");
8716
- },
8717
- getDistance: function (point1, point2) {
8718
- return point.distanceToPoint(point1, point2);
8719
- },
8720
- /**
8721
- * Return the difference between two sets of coordinates
8722
- */
8723
- coordDiff: function (startCoord, endCoord) {
8724
- return _.map(endCoord, function (val, i) {
8725
- return endCoord[i] - startCoord[i];
8726
- });
8727
- },
8728
- /**
8729
- * Round the given coordinates to a given snap value
8730
- * (e.g., nearest 0.2 increment)
8731
- */
8732
- snapCoord: function (coord, snap) {
8733
- return _.map(coord, function (val, i) {
8734
- return KhanMath.roundToNearest(snap[i], val);
8735
- });
8736
- },
8737
- // Find the angle in degrees between two or three points
8738
- findAngle: function (point1, point2, vertex) {
8739
- if (vertex === undefined) {
8740
- const x = point1[0] - point2[0];
8741
- const y = point1[1] - point2[1];
8742
- if (!x && !y) {
8743
- return 0;
8744
- }
8745
- return (180 + Math.atan2(-y, -x) * 180 / Math.PI + 360) % 360;
8746
- }
8747
- return GraphUtils.findAngle(point1, vertex) - GraphUtils.findAngle(point2, vertex);
8748
- },
8749
- graphs: {}
8750
- };
8751
8688
  var _bounds = /*#__PURE__*/_classPrivateFieldKey("bounds");
8752
8689
  var _drawingTransform = /*#__PURE__*/_classPrivateFieldKey("drawingTransform");
8753
8690
  class Graphie$1 {
@@ -8776,8 +8713,59 @@ class Graphie$1 {
8776
8713
  // Refactor to accessor methods that look at this.dimensions.
8777
8714
  this.xpixels = void 0;
8778
8715
  this.ypixels = void 0;
8716
+ this.label = (point, text, direction, arg4, arg5) => {
8717
+ const style = typeof arg4 === "object" ? arg4 : arg5;
8718
+ const latex = typeof arg4 === "boolean" ? arg4 : true;
8719
+ return this.withStyle(style, () => {
8720
+ const $span = $("<span>").addClass("graphie-label");
8721
+ const pad = this.currentStyle["label-distance"];
8722
+ $span.css({
8723
+ position: "absolute",
8724
+ padding: (pad != null ? pad : 7) + "px",
8725
+ color: "black"
8726
+ }).data("labelDirection", direction).appendTo(this.el);
8727
+
8728
+ // @ts-expect-error - TS2339 - Property 'setPosition' does not exist on type 'JQuery<HTMLElement>'.
8729
+ $span.setPosition = point => {
8730
+ const scaledPoint = this.scalePoint(point);
8731
+ $span.css({
8732
+ left: scaledPoint[0],
8733
+ top: scaledPoint[1]
8734
+ });
8735
+ };
8736
+
8737
+ // @ts-expect-error - TS2339 - Property 'setPosition' does not exist on type 'JQuery<HTMLElement>'.
8738
+ $span.setPosition(point);
8739
+ const span = $span[0];
8740
+
8741
+ // @ts-expect-error - TS2339 - Property 'processMath' does not exist on type 'JQuery<HTMLElement>'.
8742
+ $span.processMath = function (math, force) {
8743
+ processMath$1(span, math, force, function () {
8744
+ const width = span.scrollWidth;
8745
+ const height = span.scrollHeight;
8746
+ setLabelMargins(span, [width, height]);
8747
+ });
8748
+ };
8749
+
8750
+ // @ts-expect-error - TS2339 - Property 'processText' does not exist on type 'JQuery<HTMLElement>'.
8751
+ $span.processText = function (text) {
8752
+ $span.html(text);
8753
+ const width = span.scrollWidth;
8754
+ const height = span.scrollHeight;
8755
+ setLabelMargins(span, [width, height]);
8756
+ };
8757
+ if (latex) {
8758
+ // @ts-expect-error - TS2339 - Property 'processMath' does not exist on type 'JQuery<HTMLElement>'.
8759
+ $span.processMath(text, /* force */false);
8760
+ } else {
8761
+ // @ts-expect-error - TS2339 - Property 'processText' does not exist on type 'JQuery<HTMLElement>'.
8762
+ $span.processText(text);
8763
+ }
8764
+ return $span;
8765
+ });
8766
+ };
8779
8767
  this.svgPath = (points, alreadyScaled) => {
8780
- return $.map(points, (point, i) => {
8768
+ return points.map((point, i) => {
8781
8769
  if (point === true) {
8782
8770
  return "z";
8783
8771
  }
@@ -8793,7 +8781,6 @@ class Graphie$1 {
8793
8781
  // If points are collinear, plot a line instead
8794
8782
  if (a === 0) {
8795
8783
  const _points = [[this.bounds().xMin, computeParabola(this.bounds().xMin)], [this.bounds().xMax, computeParabola(this.bounds().xMax)]];
8796
- // @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
8797
8784
  return this.svgPath(_points);
8798
8785
  }
8799
8786
 
@@ -8818,9 +8805,8 @@ class Graphie$1 {
8818
8805
  const right = [vertex[0] + dx, point[1]];
8819
8806
 
8820
8807
  // Scale and bound
8821
- // @ts-expect-error - TS2345 - Argument of type '(point: number | Coord) => Coord' is not assignable to parameter of type 'Iteratee<any[][], any, any[]>'.
8822
- const points = _.map([left, control, right], this.scalePoint);
8823
- const values = _.map(_.flatten(points), KhanMath.bound);
8808
+ const points = [left, control, right].map(this.scalePoint);
8809
+ const values = points.flat().map(KhanMath.bound);
8824
8810
  return "M" + values[0] + "," + values[1] + " Q" + values[2] + "," + values[3] + " " + values[4] + "," + values[5];
8825
8811
  };
8826
8812
  this.svgSinusoidPath = (a, b, c, d) => {
@@ -8842,10 +8828,8 @@ class Graphie$1 {
8842
8828
  // See: http://stackoverflow.com/questions/13932704/how-to-draw-sine-waves-with-svg-js
8843
8829
  const xCoords = [x0, x0 * 2 / 3 + x1 * 1 / 3, x0 * 1 / 3 + x1 * 2 / 3, x1];
8844
8830
  const yCoords = [computeSine(x0), computeSine(x0) + computeDerivative(x0) * (x1 - x0) / 3, computeSine(x1) - computeDerivative(x1) * (x1 - x0) / 3, computeSine(x1)];
8845
-
8846
- // Zip and scale
8847
- // @ts-expect-error - TS2345 - Argument of type '(point: number | Coord) => Coord' is not assignable to parameter of type 'Iteratee<any[][], any, any[]>'.
8848
- return _.map(_.zip(xCoords, yCoords), this.scalePoint);
8831
+ const points = vector$1.zip(xCoords, yCoords);
8832
+ return points.map(this.scalePoint);
8849
8833
  };
8850
8834
 
8851
8835
  // How many quarter-periods do we need to span the graph?
@@ -8886,6 +8870,7 @@ class Graphie$1 {
8886
8870
  $(el).children("div").css("position", "absolute");
8887
8871
  }
8888
8872
  init(options) {
8873
+ var _options$isMobile;
8889
8874
  let scale = options.scale || [40, 40];
8890
8875
  scale = typeof scale === "number" ? [scale, scale] : scale;
8891
8876
  if (options.range == null) {
@@ -8905,7 +8890,7 @@ class Graphie$1 {
8905
8890
  this.dimensions = [w, h];
8906
8891
  this.xpixels = w;
8907
8892
  this.ypixels = h;
8908
- this.isMobile = options.isMobile;
8893
+ this.isMobile = (_options$isMobile = options.isMobile) != null ? _options$isMobile : false;
8909
8894
  return this;
8910
8895
  }
8911
8896
 
@@ -8923,33 +8908,24 @@ class Graphie$1 {
8923
8908
  // - xLabelFormat: fn to format label string for x-axis
8924
8909
  graphInit(options) {
8925
8910
  options = options || {};
8926
- $.each(options, function (prop, val) {
8911
+ for (const [prop, val] of entries(options)) {
8927
8912
  // allow options to be specified by a single number for shorthand if
8928
8913
  // the horizontal and vertical components are the same
8929
- if (
8930
- // @ts-expect-error - TS2339 - Property 'match' does not exist on type 'string | number | symbol'.
8931
- !prop.match(/.*Opacity$/) && prop !== "range" && typeof val === "number") {
8914
+ if (!prop.match(/.*Opacity$/) && prop !== "range" && typeof val === "number") {
8932
8915
  options[prop] = [val, val];
8933
8916
  }
8934
8917
 
8935
8918
  // allow symmetric ranges to be specified by the absolute values
8936
8919
  if (prop === "range" || prop === "gridRange") {
8937
- if (val.constructor === Array) {
8938
- // but don't mandate symmetric ranges
8939
- if (val[0].constructor !== Array) {
8940
- options[prop] = [[-val[0], val[0]], [-val[1], val[1]]];
8941
- }
8942
- } else if (typeof val === "number") {
8943
- options[prop] = [[-val, val], [-val, val]];
8944
- }
8920
+ options[prop] = normalizeRange(options[prop]);
8945
8921
  }
8946
- });
8947
- const range = options.range || [[-10, 10], [-10, 10]];
8948
- const gridRange = options.gridRange || options.range;
8922
+ }
8923
+ const range = normalizeRange(options.range || [[-10, 10], [-10, 10]]);
8924
+ const gridRange = normalizeRange(options.gridRange || range);
8949
8925
  const scale = options.scale || [20, 20];
8950
8926
  const grid = options.grid != null ? options.grid : true;
8951
8927
  const gridOpacity = options.gridOpacity || 0.1;
8952
- const gridStep = options.gridStep || [1, 1];
8928
+ const gridStep = toPair(options.gridStep || [1, 1]);
8953
8929
  const axes = options.axes != null ? options.axes : true;
8954
8930
  const axisArrows = options.axisArrows || "";
8955
8931
  const axisOpacity = options.axisOpacity || 1.0;
@@ -8969,7 +8945,7 @@ class Graphie$1 {
8969
8945
  let xLabelFormat = options.xLabelFormat || labelFormat;
8970
8946
  let yLabelFormat = options.yLabelFormat || labelFormat;
8971
8947
  const realRange = [[range[0][0] - (range[0][0] > 0 ? 1 : 0), range[0][1] + (range[0][1] < 0 ? 1 : 0)], [range[1][0] - (range[1][0] > 0 ? 1 : 0), range[1][1] + (range[1][1] < 0 ? 1 : 0)]];
8972
- if (!_.isArray(unityLabels)) {
8948
+ if (!Array.isArray(unityLabels)) {
8973
8949
  unityLabels = [unityLabels, unityLabels];
8974
8950
  }
8975
8951
  const minusIgnorer = function minusIgnorer(lf) {
@@ -9044,7 +9020,7 @@ class Graphie$1 {
9044
9020
  stroke: options.isMobile ? KhanColors.GRAY_G : "#000000",
9045
9021
  opacity: options.isMobile ? 1 : tickOpacity,
9046
9022
  strokeWidth: 1
9047
- }, function () {
9023
+ }, () => {
9048
9024
  // horizontal axis
9049
9025
  let step = gridStep[0] * tickStep[0];
9050
9026
  let len = tickLen[0] / scale[1];
@@ -9071,13 +9047,11 @@ class Graphie$1 {
9071
9047
  if (range[0][0] < 0 && range[0][1] > 0) {
9072
9048
  for (let y = step + axisCenter[1]; y <= stop; y += step) {
9073
9049
  if (y < stop || !axisArrows) {
9074
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
9075
9050
  this.line([-len + axisCenter[0], y], [halfWidthTicks ? 0 : len + axisCenter[0], y]);
9076
9051
  }
9077
9052
  }
9078
9053
  for (let y = -step + axisCenter[1]; y >= start; y -= step) {
9079
9054
  if (y > start || !axisArrows) {
9080
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
9081
9055
  this.line([-len + axisCenter[0], y], [halfWidthTicks ? 0 : len + axisCenter[0], y]);
9082
9056
  }
9083
9057
  }
@@ -9153,155 +9127,346 @@ class Graphie$1 {
9153
9127
  const processed = this.processAttributes(attrs);
9154
9128
  if (typeof fn === "function") {
9155
9129
  const oldStyle = this.currentStyle;
9156
- this.currentStyle = $.extend({}, this.currentStyle, processed);
9130
+ this.currentStyle = _extends({}, this.currentStyle, processed);
9157
9131
  const result = fn.call(this);
9158
9132
  this.currentStyle = oldStyle;
9159
9133
  return result;
9160
9134
  }
9161
- $.extend(this.currentStyle, processed);
9135
+ Object.assign(this.currentStyle, processed);
9136
+ }
9137
+ grid(xr, yr, style) {
9138
+ return this.withStyle(style, () => {
9139
+ const step = this.currentStyle.step || [1, 1];
9140
+ const set = this.raphael.set();
9141
+ let x = step[0] * Math.ceil(xr[0] / step[0]);
9142
+ for (; x <= xr[1]; x += step[0]) {
9143
+ set.push(this.line([x, yr[0]], [x, yr[1]]));
9144
+ }
9145
+ let y = step[1] * Math.ceil(yr[0] / step[1]);
9146
+ for (; y <= yr[1]; y += step[1]) {
9147
+ set.push(this.line([xr[0], y], [xr[1], y]));
9148
+ }
9149
+ return set;
9150
+ });
9151
+ }
9152
+ arc(center, radius, startAngle, endAngle, sector, style) {
9153
+ return this.withStyle(style, () => {
9154
+ startAngle = (startAngle % 360 + 360) % 360;
9155
+ endAngle = (endAngle % 360 + 360) % 360;
9156
+ const cent = this.scalePoint(center);
9157
+ const radii = this.scaleVector(radius);
9158
+ const startVector = polar(radius, startAngle);
9159
+ const endVector = polar(radius, endAngle);
9160
+
9161
+ // We round the coordinates to make testing easier, because trig
9162
+ // operations return unround numbers.
9163
+ // TODO(benchristel): move rounding to scalePoint?
9164
+ const round = x => number.round(x, 6);
9165
+ const startPoint = this.scalePoint([round(center[0] + startVector[0]), round(center[1] + startVector[1])]);
9166
+ const endPoint = this.scalePoint([round(center[0] + endVector[0]), round(center[1] + endVector[1])]);
9167
+ const largeAngle = ((endAngle - startAngle) % 360 + 360) % 360 > 180;
9168
+ return this.raphael.path("M" + startPoint.join(" ") + "A" + radii.join(" ") + " 0 " + (
9169
+ // ellipse rotation
9170
+ largeAngle ? 1 : 0) + " 0 " +
9171
+ // sweep flag
9172
+ endPoint.join(" ") + (sector ? "L" + cent.join(" ") + "z" : ""));
9173
+ });
9174
+ }
9175
+ circle(center, radius, style) {
9176
+ return this.withStyle(style, () => this.raphael.ellipse(...this.scalePoint(center), ...this.scaleVector([radius, radius])));
9162
9177
  }
9163
- label(point, text, direction, latex) {}
9164
- grid(xr, yr, styleAttributes) {}
9165
9178
 
9166
- // path is a stub that gets overwritten with a function from drawingTools
9167
- // in createGraphie
9168
- path(points) {}
9179
+ // (x, y) is coordinate of bottom left corner
9180
+ rect(x, y, width, height, style) {
9181
+ return this.withStyle(style, () => {
9182
+ // Raphael needs (x, y) to be coordinate of upper left corner
9183
+ const corner = this.scalePoint([x, y + height]);
9184
+ const dims = this.scaleVector([width, height]);
9185
+ const elem = this.raphael.rect(...corner.concat(dims));
9186
+ if (this.isMobile) {
9187
+ elem.node.style.shapeRendering = "crispEdges";
9188
+ }
9189
+ return elem;
9190
+ });
9191
+ }
9192
+ ellipse(center, radii, style) {
9193
+ return this.withStyle(style, () => this.raphael.ellipse(...this.scalePoint(center).concat(this.scaleVector(radii))));
9194
+ }
9195
+ fixedEllipse(center,
9196
+ // Different type than Coord, this is radiusX, radiusY
9197
+ radii, maxScale, padding, style) {
9198
+ return this.withStyle(style, () => {
9199
+ // Scale point and radius
9200
+ const scaledPoint = this.scalePoint(center);
9201
+ const scaledRadii = this.scaleVector(radii);
9202
+ const width = 2 * scaledRadii[0] * maxScale + padding;
9203
+ const height = 2 * scaledRadii[1] * maxScale + padding;
9204
+
9205
+ // Calculate absolute left, top
9206
+ const left = scaledPoint[0] - width / 2;
9207
+ const top = scaledPoint[1] - height / 2;
9208
+
9209
+ // Wrap in <div>
9210
+ const wrapper = document.createElement("div");
9211
+ $(wrapper).css({
9212
+ position: "absolute",
9213
+ width: width + "px",
9214
+ height: height + "px",
9215
+ left: left + "px",
9216
+ top: top + "px"
9217
+ });
9218
+ // wrapper.setAttribute("data-graphie-type", "ellipse");
9169
9219
 
9170
- // line is a stub that gets overwritten with a function from drawingTools
9171
- // in createGraphie
9172
- line(start, end) {}
9173
- processAttributes(attrs) {
9174
- const transformers = {
9175
- scale: scale => {
9176
- this.drawingTransform().setScale(scale);
9177
- },
9178
- clipRect: pair => {
9179
- const point = pair[0];
9180
- const size = pair[1];
9181
- point[1] += size[1]; // because our coordinates are flipped
9220
+ // Create Raphael canvas
9221
+ const localRaphael = raphael.exports(wrapper, width, height);
9222
+ const visibleShape = localRaphael.ellipse(width / 2, height / 2, scaledRadii[0], scaledRadii[1]);
9223
+ return {
9224
+ wrapper: wrapper,
9225
+ visibleShape: visibleShape
9226
+ };
9227
+ });
9228
+ }
9229
+ unstyledPath(points) {
9230
+ const p = this.raphael.path(this.svgPath(points));
9231
+ p.graphiePath = points;
9232
+ return p;
9233
+ }
9234
+ path(points, style) {
9235
+ return this.withStyle(style, () => {
9236
+ return this.unstyledPath(points);
9237
+ });
9238
+ }
9182
9239
 
9183
- return {
9184
- "clip-rect": this.scalePoint(point).concat(this.scaleVector(size)).join(" ")
9185
- };
9186
- },
9187
- strokeWidth: function (val) {
9188
- return {
9189
- "stroke-width": parseFloat(val)
9190
- };
9191
- },
9192
- rx: val => {
9193
- return {
9194
- rx: this.scaleVector([val, 0])[0]
9195
- };
9196
- },
9197
- ry: val => {
9198
- return {
9199
- ry: this.scaleVector([0, val])[1]
9200
- };
9201
- },
9202
- r: val => {
9203
- const scaled = this.scaleVector([val, val]);
9204
- return {
9205
- rx: scaled[0],
9206
- ry: scaled[1]
9207
- };
9208
- }
9240
+ // fixedPath is a stub that gets overwritten with a function from
9241
+ // drawingTools in createGraphie
9242
+ fixedPath(points, center, createPath) {
9243
+ points = points.map(this.scalePoint);
9244
+ center = center ? this.scalePoint(center) : null;
9245
+ createPath = createPath || this.svgPath;
9246
+
9247
+ // Compute bounding box
9248
+ const xs = points.map(p => p[0]);
9249
+ const ys = points.map(p => p[1]);
9250
+ const pathLeft = Math.min(...xs);
9251
+ const pathRight = Math.max(...xs);
9252
+ const pathTop = Math.min(...ys);
9253
+ const pathBottom = Math.max(...ys);
9254
+
9255
+ // Apply padding to line
9256
+ const padding = [4, 4];
9257
+
9258
+ // Calculate and apply additional offset
9259
+ const topLeftOfBoundingBox = [pathLeft, pathTop];
9260
+
9261
+ // Apply padding and offset to points to convert from
9262
+ // canvas coordinates to pixel coordinates relative to bounding box
9263
+ points = points.map(function (point) {
9264
+ return vector$1.add(vector$1.subtract(point, topLeftOfBoundingBox), vector$1.scale(padding, 0.5));
9265
+ });
9266
+
9267
+ // Calculate <div> dimensions
9268
+ const width = pathRight - pathLeft + padding[0];
9269
+ const height = pathBottom - pathTop + padding[1];
9270
+ const left = topLeftOfBoundingBox[0] - padding[0] / 2;
9271
+ const top = topLeftOfBoundingBox[1] - padding[1] / 2;
9272
+
9273
+ // Create <div>
9274
+ const wrapper = document.createElement("div");
9275
+ $(wrapper).css({
9276
+ position: "absolute",
9277
+ width: width + "px",
9278
+ height: height + "px",
9279
+ left: left + "px",
9280
+ top: top + "px",
9281
+ // If user specified a center, set it
9282
+ // NOTE(kevinb): jQuery doesn't like that `transformOrigin `could be `null`
9283
+ // so we cast to `any` here.
9284
+ transformOrigin: center ? width / 2 + center[0] + "px " + (height / 2 + center[1]) + "px" : null
9285
+ });
9286
+
9287
+ // Create Raphael canvas
9288
+ const localRaphael = raphael.exports(wrapper, width, height);
9289
+
9290
+ // Calculate path
9291
+ const visibleShape = localRaphael.path(createPath(points));
9292
+ return {
9293
+ wrapper: wrapper,
9294
+ visibleShape: visibleShape
9209
9295
  };
9210
- const processed = {};
9211
- $.each(attrs || {}, function (key, value) {
9212
- const transformer = transformers[key];
9213
- if (typeof transformer === "function") {
9214
- $.extend(processed, transformer(value));
9215
- } else {
9216
- const dasherized = key
9217
- // @ts-expect-error - TS2339 - Property 'replace' does not exist on type 'string | number | symbol'.
9218
- .replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2").replace(/([a-z\d])([A-Z])/g, "$1-$2").toLowerCase();
9219
- processed[dasherized] = value;
9296
+ }
9297
+ scaledPath(points, style) {
9298
+ return this.withStyle(style, () => {
9299
+ const p = this.raphael.path(this.svgPath(points, /* alreadyScaled */true));
9300
+ p.graphiePath = points;
9301
+ return p;
9302
+ });
9303
+ }
9304
+ line(start, end, style) {
9305
+ return this.withStyle(style, () => {
9306
+ const l = this.unstyledPath([start, end]);
9307
+ if (this.isMobile) {
9308
+ l.node.style.shapeRendering = "crispEdges";
9220
9309
  }
9310
+ return l;
9221
9311
  });
9222
- return processed;
9223
9312
  }
9224
- }
9225
- GraphUtils.Graphie = Graphie$1;
9226
- const labelDirections = {
9227
- center: [-0.5, -0.5],
9228
- above: [-0.5, -1.0],
9229
- "above right": [0.0, -1.0],
9230
- right: [0.0, -0.5],
9231
- "below right": [0.0, 0.0],
9232
- below: [-0.5, 0.0],
9233
- "below left": [-1.0, 0.0],
9234
- left: [-1.0, -0.5],
9235
- "above left": [-1.0, -1.0]
9236
- };
9313
+ parabola(a, b, c, style) {
9314
+ return this.withStyle(style, () => this.raphael.path(this.svgParabolaPath(a, b, c)));
9315
+ }
9316
+ fixedLine(start, end, thickness) {
9317
+ // Apply padding to line
9318
+ const padding = [thickness, thickness];
9237
9319
 
9238
- /**
9239
- * Safari applies some SVG-specific styles to things that are not SVGs, so we
9240
- * need to exclude those styles from things that are not SVGs.
9241
- *
9242
- * To see this behavior in action, open https://codepen.io/anon/pen/zENEoa in
9243
- * Safari.
9244
- *
9245
- * Usage `$.extend({}, someStyles, SVG_SPECIFIC_STYLE_MASK)`
9246
- */
9247
- const SVG_SPECIFIC_STYLE_MASK = {
9248
- "stroke-width": null
9249
- };
9250
- GraphUtils.createGraphie = function (el) {
9251
- const thisGraphie = new Graphie$1(el);
9252
- const setLabelMargins = function setLabelMargins(span, size) {
9253
- const $span = $(span);
9254
- const direction = $span.data("labelDirection");
9255
- let [width, height] = size;
9256
- // This can happen when a span
9257
- // is invisible but we still want to update the CSS. At worst, we will
9258
- // be off by a few pixels instead of in a different position entirely.
9259
- if (width === 0 && height === 0) {
9260
- [width, height] = [1, 1];
9261
- Log.log("Label size was 0x0 in graphie.js; using 1x1 instead");
9262
- }
9263
- $span.css("visibility", "");
9264
- if (typeof direction === "number") {
9265
- const x = Math.cos(direction);
9266
- const y = Math.sin(direction);
9267
- const scale = Math.min(width / 2 / Math.abs(x), height / 2 / Math.abs(y));
9268
- $span.css({
9269
- marginLeft: -width / 2 + x * scale,
9270
- marginTop: -height / 2 - y * scale
9271
- });
9272
- } else {
9273
- const multipliers = labelDirections[direction || "center"];
9274
- $span.css({
9275
- marginLeft: Math.round(width * multipliers[0]),
9276
- marginTop: Math.round(height * multipliers[1])
9277
- });
9278
- }
9279
- };
9320
+ // Scale points to get values in pixels
9321
+ start = this.scalePoint(start);
9322
+ end = this.scalePoint(end);
9323
+
9324
+ // Calculate and apply additional offset
9325
+ const extraOffset = [Math.min(start[0], end[0]), Math.min(start[1], end[1])];
9326
+
9327
+ // Apply padding and offset to start, end points
9328
+ start = vector$1.add(vector$1.subtract(start, extraOffset), vector$1.scale(padding, 0.5));
9329
+ end = vector$1.add(vector$1.subtract(end, extraOffset), vector$1.scale(padding, 0.5));
9280
9330
 
9281
- // `svgPath` is independent of graphie range, so we export it independently
9282
- GraphUtils.svgPath = thisGraphie.svgPath;
9283
- const addArrowheads = function arrows(path) {
9331
+ // Calculate <div> dimensions
9332
+ const left = extraOffset[0] - padding[0] / 2;
9333
+ const top = extraOffset[1] - padding[1] / 2;
9334
+ const width = Math.abs(start[0] - end[0]) + padding[0];
9335
+ const height = Math.abs(start[1] - end[1]) + padding[1];
9336
+
9337
+ // Create <div>
9338
+ const wrapper = document.createElement("div");
9339
+ $(wrapper).css({
9340
+ position: "absolute",
9341
+ width: width + "px",
9342
+ height: height + "px",
9343
+ left: left + "px",
9344
+ top: top + "px",
9345
+ // Outsiders should feel like the line's 'origin' (i.e., for
9346
+ // rotation) is the starting point
9347
+ transformOrigin: start[0] + "px " + start[1] + "px"
9348
+ });
9349
+
9350
+ // Create Raphael canvas
9351
+ const localRaphael = raphael.exports(wrapper, width, height);
9352
+
9353
+ // Calculate path
9354
+ const path = "M" + start[0] + " " + start[1] + " " + "L" + end[0] + " " + end[1];
9355
+ const visibleShape = localRaphael.path(path);
9356
+ visibleShape.graphiePath = [start, end];
9357
+ return {
9358
+ wrapper: wrapper,
9359
+ visibleShape: visibleShape
9360
+ };
9361
+ }
9362
+ sinusoid(a, b, c, d, style) {
9363
+ return this.withStyle(style, () =>
9364
+ // Plot a sinusoid of the form: f(x) = a * sin(b * x - c) + d
9365
+ this.raphael.path(this.svgSinusoidPath(a, b, c, d)));
9366
+ }
9367
+ plotParametric(fn, range, style) {
9368
+ return this.withStyle(style, () => {
9369
+ // We truncate to 500,000, since anything bigger causes
9370
+ // overflow in the firefox svg renderer. This is safe
9371
+ // since 500,000 is outside the viewport anyway. We
9372
+ // write these functions the way we do to handle undefined.
9373
+ const clip = xy => {
9374
+ if (Math.abs(xy[1]) > 500000) {
9375
+ return [xy[0], Math.min(Math.max(xy[1], -500000), 500000)];
9376
+ }
9377
+ return xy;
9378
+ };
9379
+ const clippedFn = x => clip(fn(x));
9380
+ const min = range[0];
9381
+ const max = range[1];
9382
+ let step = (max - min) / (this.currentStyle["plot-points"] || 800);
9383
+ if (step === 0) {
9384
+ step = 1;
9385
+ }
9386
+ const paths = this.raphael.set();
9387
+ let points = [];
9388
+ let lastY = clippedFn(min)[1];
9389
+ for (let t = min; t <= max; t += step) {
9390
+ const point = clippedFn(t);
9391
+ const y = point[1];
9392
+
9393
+ // Find points where it flips
9394
+ if (
9395
+ // if there is an asymptote here, meaning that the graph
9396
+ // switches signs and has a large difference
9397
+ y > 0 !== lastY > 0 && Math.abs(y - lastY) > 2 * this.drawingTransform().pixelsPerUnitY() ||
9398
+ // or the function is undefined
9399
+ isNaN(y)) {
9400
+ // split the path at this point, and draw it
9401
+ paths.push(this.unstyledPath(points));
9402
+ // restart the path, excluding this point
9403
+ points = [];
9404
+ } else {
9405
+ // otherwise, just add the point to the path
9406
+ points.push(point);
9407
+ }
9408
+ lastY = y;
9409
+ }
9410
+ paths.push(this.unstyledPath(points));
9411
+ return paths;
9412
+ });
9413
+ }
9414
+ plot(fn, range, style) {
9415
+ return this.withStyle(style, () => {
9416
+ const min = range[0];
9417
+ const max = range[1];
9418
+ if (!this.currentStyle["plot-points"]) {
9419
+ this.currentStyle["plot-points"] = 2 * (max - min) * this.drawingTransform().pixelsPerUnitX();
9420
+ }
9421
+ const parametricFn = x => [x, fn(x)];
9422
+ return this.plotParametric(parametricFn, range);
9423
+ });
9424
+ }
9425
+ withStyle(style, fn) {
9426
+ const oldStyle = this.currentStyle;
9427
+ this.currentStyle = _extends({}, this.currentStyle, this.processAttributes(style));
9428
+ const result = this.postprocessDrawingResult(fn());
9429
+ this.currentStyle = oldStyle;
9430
+ return result;
9431
+ }
9432
+ postprocessDrawingResult(result) {
9433
+ // Bad heuristic for recognizing Raphael elements and sets
9434
+ const type = result.constructor.prototype;
9435
+ if (type === raphael.exports.el || type === raphael.exports.st) {
9436
+ result.attr(this.currentStyle);
9437
+ if (this.currentStyle.arrows) {
9438
+ result = this.addArrowheads(result);
9439
+ }
9440
+ } else if (result instanceof $) {
9441
+ // We assume that if it's not a Raphael element/set, it
9442
+ // does not contain SVG.
9443
+ // @ts-expect-error - TS2339 - Property 'css' does not exist on type '{}'.
9444
+ result.css(_extends({}, this.currentStyle, SVG_SPECIFIC_STYLE_MASK));
9445
+ }
9446
+ return result;
9447
+ }
9448
+ addArrowheads(path) {
9284
9449
  const type = path.constructor.prototype;
9285
9450
  if (type === raphael.exports.el) {
9286
9451
  if (path.type === "path" && typeof path.arrowheadsDrawn === "undefined") {
9287
9452
  const w = path.attr("stroke-width");
9288
9453
  const s = 0.6 + 0.4 * w;
9289
9454
  const l = path.getTotalLength();
9290
- const set = thisGraphie.raphael.set();
9291
- const head = thisGraphie.raphael.path(thisGraphie.isMobile ? "M-4,4 C-4,4 -0.25,0 -0.25,0 C-0.25,0 -4,-4 -4,-4" : "M-3 4 C-2.75 2.5 0 0.25 0.75 0C0 -0.25 -2.75 -2.5 -3 -4");
9455
+ const set = this.raphael.set();
9456
+ 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");
9292
9457
  const end = path.getPointAtLength(l - 0.4);
9293
9458
  const almostTheEnd = path.getPointAtLength(l - 0.75 * s);
9294
9459
  const angle = Math.atan2(end.y - almostTheEnd.y, end.x - almostTheEnd.x) * 180 / Math.PI;
9295
9460
  const attrs = path.attr();
9296
9461
  delete attrs.path;
9297
9462
  let subpath = path.getSubpath(0, l - 0.75 * s);
9298
- subpath = thisGraphie.raphael.path(subpath).attr(attrs);
9463
+ subpath = this.raphael.path(subpath).attr(attrs);
9299
9464
  subpath.arrowheadsDrawn = true;
9300
9465
  path.remove();
9301
9466
 
9302
9467
  // For some unknown reason 0 doesn't work for the rotation
9303
9468
  // origin so we use a tiny number.
9304
- head.rotate(angle, thisGraphie.isMobile ? 1e-5 : 0.75, 0).scale(s, s, 0.75, 0).translate(almostTheEnd.x, almostTheEnd.y).attr(attrs).attr({
9469
+ head.rotate(angle, this.isMobile ? 1e-5 : 0.75, 0).scale(s, s, 0.75, 0).translate(almostTheEnd.x, almostTheEnd.y).attr(attrs).attr({
9305
9470
  "stroke-linejoin": "round",
9306
9471
  "stroke-linecap": "round"
9307
9472
  });
@@ -9312,429 +9477,195 @@ GraphUtils.createGraphie = function (el) {
9312
9477
  }
9313
9478
  } else if (type === raphael.exports.st) {
9314
9479
  for (let i = 0, l = path.items.length; i < l; i++) {
9315
- arrows(path.items[i]);
9480
+ this.addArrowheads(path.items[i]);
9316
9481
  }
9317
9482
  }
9318
9483
  return path;
9319
- };
9320
- function circle(center, radius) {
9321
- return thisGraphie.raphael.ellipse(...thisGraphie.scalePoint(center).concat(thisGraphie.scaleVector([radius, radius])));
9322
- }
9323
-
9324
- // (x, y) is coordinate of bottom left corner
9325
- function rect(x, y, width, height) {
9326
- // Raphael needs (x, y) to be coordinate of upper left corner
9327
- const corner = thisGraphie.scalePoint([x, y + height]);
9328
- const dims = thisGraphie.scaleVector([width, height]);
9329
- const elem = thisGraphie.raphael.rect(...corner.concat(dims));
9330
- if (thisGraphie.isMobile) {
9331
- elem.node.style.shapeRendering = "crispEdges";
9332
- }
9333
- return elem;
9334
- }
9335
- function ellipse(center, radii) {
9336
- return thisGraphie.raphael.ellipse(...thisGraphie.scalePoint(center).concat(thisGraphie.scaleVector(radii)));
9337
9484
  }
9338
- function fixedEllipse(center,
9339
- // Different type than Coord, this is radiusX, radiusY
9340
- radii, maxScale, padding) {
9341
- // Scale point and radius
9342
- const scaledPoint = thisGraphie.scalePoint(center);
9343
- const scaledRadii = thisGraphie.scaleVector(radii);
9344
- const width = 2 * scaledRadii[0] * maxScale + padding;
9345
- const height = 2 * scaledRadii[1] * maxScale + padding;
9346
-
9347
- // Calculate absolute left, top
9348
- const left = scaledPoint[0] - width / 2;
9349
- const top = scaledPoint[1] - height / 2;
9350
-
9351
- // Wrap in <div>
9352
- const wrapper = document.createElement("div");
9353
- $(wrapper).css({
9354
- position: "absolute",
9355
- width: width + "px",
9356
- height: height + "px",
9357
- left: left + "px",
9358
- top: top + "px"
9359
- });
9360
- // wrapper.setAttribute("data-graphie-type", "ellipse");
9485
+ processAttributes(attrs) {
9486
+ const transformers = {
9487
+ scale: scale => {
9488
+ this.drawingTransform().setScale(scale);
9489
+ },
9490
+ clipRect: pair => {
9491
+ const point = pair[0];
9492
+ const size = pair[1];
9493
+ point[1] += size[1]; // because our coordinates are flipped
9361
9494
 
9362
- // Create Raphael canvas
9363
- const localRaphael = raphael.exports(wrapper, width, height);
9364
- const visibleShape = localRaphael.ellipse(width / 2, height / 2, scaledRadii[0], scaledRadii[1]);
9365
- return {
9366
- wrapper: wrapper,
9367
- visibleShape: visibleShape
9495
+ return {
9496
+ "clip-rect": this.scalePoint(point).concat(this.scaleVector(size)).join(" ")
9497
+ };
9498
+ },
9499
+ strokeWidth: function (val) {
9500
+ return {
9501
+ "stroke-width": parseFloat(val)
9502
+ };
9503
+ },
9504
+ rx: val => {
9505
+ return {
9506
+ rx: this.scaleVector([val, 0])[0]
9507
+ };
9508
+ },
9509
+ ry: val => {
9510
+ return {
9511
+ ry: this.scaleVector([0, val])[1]
9512
+ };
9513
+ },
9514
+ r: val => {
9515
+ const scaled = this.scaleVector([val, val]);
9516
+ return {
9517
+ rx: scaled[0],
9518
+ ry: scaled[1]
9519
+ };
9520
+ }
9368
9521
  };
9369
- }
9370
- function arc(center, radius, startAngle, endAngle, sector) {
9371
- startAngle = (startAngle % 360 + 360) % 360;
9372
- endAngle = (endAngle % 360 + 360) % 360;
9373
- const cent = thisGraphie.scalePoint(center);
9374
- const radii = thisGraphie.scaleVector(radius);
9375
- const startVector = polar(radius, startAngle);
9376
- const endVector = polar(radius, endAngle);
9377
- const startPoint = thisGraphie.scalePoint([center[0] + startVector[0], center[1] + startVector[1]]);
9378
- const endPoint = thisGraphie.scalePoint([center[0] + endVector[0], center[1] + endVector[1]]);
9379
- const largeAngle = ((endAngle - startAngle) % 360 + 360) % 360 > 180;
9380
- return thisGraphie.raphael.path("M" + startPoint.join(" ") + "A" + radii.join(" ") + " 0 " + (
9381
- // ellipse rotation
9382
- largeAngle ? 1 : 0) + " 0 " +
9383
- // sweep flag
9384
- endPoint.join(" ") + (sector ? "L" + cent.join(" ") + "z" : ""));
9385
- }
9386
- function path(points) {
9387
- // @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
9388
- const p = thisGraphie.raphael.path(thisGraphie.svgPath(points));
9389
- p.graphiePath = points;
9390
- return p;
9391
- }
9392
- function fixedPath(points, center, createPath) {
9393
- points = _.map(points, thisGraphie.scalePoint);
9394
- center = center ? thisGraphie.scalePoint(center) : null;
9395
- createPath = createPath || thisGraphie.svgPath;
9396
- const pathLeft = _.min(_.pluck(points, 0));
9397
- const pathRight = _.max(_.pluck(points, 0));
9398
- const pathTop = _.min(_.pluck(points, 1));
9399
- const pathBottom = _.max(_.pluck(points, 1));
9400
-
9401
- // Apply padding to line
9402
- const padding = [4, 4];
9403
-
9404
- // Calculate and apply additional offset
9405
- const extraOffset = [pathLeft, pathTop];
9406
-
9407
- // Apply padding and offset to points
9408
- points = _.map(points, function (point) {
9409
- return vector$1.add(vector$1.subtract(point, extraOffset), vector$1.scale(padding, 0.5));
9410
- });
9411
-
9412
- // Calculate <div> dimensions
9413
- const width = pathRight - pathLeft + padding[0];
9414
- const height = pathBottom - pathTop + padding[1];
9415
- const left = extraOffset[0] - padding[0] / 2;
9416
- const top = extraOffset[1] - padding[1] / 2;
9417
-
9418
- // Create <div>
9419
- const wrapper = document.createElement("div");
9420
- $(wrapper).css({
9421
- position: "absolute",
9422
- width: width + "px",
9423
- height: height + "px",
9424
- left: left + "px",
9425
- top: top + "px",
9426
- // If user specified a center, set it
9427
- // NOTE(kevinb): jQuery doesn't like that `transformOrigin `could be `null`
9428
- // so we cast to `any` here.
9429
- transformOrigin: center ? width / 2 + center[0] + "px " + (height / 2 + center[1]) + "px" : null
9522
+ const processed = {};
9523
+ Object.entries(attrs || {}).forEach(function ([key, value]) {
9524
+ const transformer = transformers[key];
9525
+ if (typeof transformer === "function") {
9526
+ Object.assign(processed, transformer(value));
9527
+ } else {
9528
+ const dasherized = String(key).replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2").replace(/([a-z\d])([A-Z])/g, "$1-$2").toLowerCase();
9529
+ processed[dasherized] = value;
9530
+ }
9430
9531
  });
9431
-
9432
- // Create Raphael canvas
9433
- const localRaphael = raphael.exports(wrapper, width, height);
9434
-
9435
- // Calculate path
9436
- const visibleShape = localRaphael.path(createPath(points));
9437
- return {
9438
- wrapper: wrapper,
9439
- visibleShape: visibleShape
9440
- };
9532
+ return processed;
9441
9533
  }
9442
- function scaledPath(points) {
9443
- const p = thisGraphie.raphael.path(thisGraphie.svgPath(points, /* alreadyScaled */true));
9444
- p.graphiePath = points;
9445
- return p;
9534
+ }
9535
+ const labelDirections = {
9536
+ center: [-0.5, -0.5],
9537
+ above: [-0.5, -1.0],
9538
+ "above right": [0.0, -1.0],
9539
+ right: [0.0, -0.5],
9540
+ "below right": [0.0, 0.0],
9541
+ below: [-0.5, 0.0],
9542
+ "below left": [-1.0, 0.0],
9543
+ left: [-1.0, -0.5],
9544
+ "above left": [-1.0, -1.0]
9545
+ };
9546
+
9547
+ // A range for a Graphie can be specified in one of three forms:
9548
+ // - A pair of intervals, [[xMin, xMax], [yMin, yMax]]. This is the
9549
+ // "normalized" form.
9550
+ // - A pair of magnitudes for the x and y ranges, respectively. E.g. [5, 10]
9551
+ // is equivalent to [[-5, 5], [-10, 10]].
9552
+ // - A single magnitude to be used for both x and y. E.g. 10 is equivalent to
9553
+ // [[-10, 10], [-10, 10]]
9554
+ // Overload normalizeRange to specify that it only returns undefined if its
9555
+ // argument is undefined.
9556
+ // normalizeRange converts a RangeSpecifier into a pair of Intervals that give
9557
+ // the x and y ranges, respectively
9558
+ function normalizeRange(range) {
9559
+ function normalizeInterval(magnitude) {
9560
+ if (typeof magnitude === "number") {
9561
+ return [-magnitude, magnitude];
9562
+ } else {
9563
+ return magnitude;
9564
+ }
9446
9565
  }
9447
- function line(start, end) {
9448
- const l = path([start, end]);
9449
- if (thisGraphie.isMobile) {
9450
- l.node.style.shapeRendering = "crispEdges";
9566
+ function getXAndYRanges(range) {
9567
+ if (Array.isArray(range)) {
9568
+ return range;
9569
+ } else {
9570
+ return [range, range];
9451
9571
  }
9452
- return l;
9453
9572
  }
9454
- function parabola(a, b, c) {
9455
- // Plot a parabola of the form: f(x) = (a * x + b) * x + c
9456
- return thisGraphie.raphael.path(thisGraphie.svgParabolaPath(a, b, c));
9573
+ if (range == null) {
9574
+ return range;
9457
9575
  }
9458
- function fixedLine(start, end, thickness) {
9459
- // Apply padding to line
9460
- const padding = [thickness, thickness];
9461
-
9462
- // Scale points to get values in pixels
9463
- start = thisGraphie.scalePoint(start);
9464
- end = thisGraphie.scalePoint(end);
9465
-
9466
- // Calculate and apply additional offset
9467
- const extraOffset = [Math.min(start[0], end[0]), Math.min(start[1], end[1])];
9468
-
9469
- // Apply padding and offset to start, end points
9470
- start = vector$1.add(vector$1.subtract(start, extraOffset), vector$1.scale(padding, 0.5));
9471
- end = vector$1.add(vector$1.subtract(end, extraOffset), vector$1.scale(padding, 0.5));
9472
-
9473
- // Calculate <div> dimensions
9474
- const left = extraOffset[0] - padding[0] / 2;
9475
- const top = extraOffset[1] - padding[1] / 2;
9476
- const width = Math.abs(start[0] - end[0]) + padding[0];
9477
- const height = Math.abs(start[1] - end[1]) + padding[1];
9576
+ const [xRange, yRange] = getXAndYRanges(range);
9577
+ return [normalizeInterval(xRange), normalizeInterval(yRange)];
9578
+ }
9579
+ function toPair(x) {
9580
+ if (Array.isArray(x)) {
9581
+ return x;
9582
+ }
9583
+ return [x, x];
9584
+ }
9478
9585
 
9479
- // Create <div>
9480
- const wrapper = document.createElement("div");
9481
- $(wrapper).css({
9482
- position: "absolute",
9483
- width: width + "px",
9484
- height: height + "px",
9485
- left: left + "px",
9486
- top: top + "px",
9487
- // Outsiders should feel like the line's 'origin' (i.e., for
9488
- // rotation) is the starting point
9489
- transformOrigin: start[0] + "px " + start[1] + "px"
9586
+ /**
9587
+ * Safari applies some SVG-specific styles to things that are not SVGs, so we
9588
+ * need to exclude those styles from things that are not SVGs.
9589
+ *
9590
+ * To see this behavior in action, open https://codepen.io/anon/pen/zENEoa in
9591
+ * Safari.
9592
+ *
9593
+ * Usage `{...someStyles, ...SVG_SPECIFIC_STYLE_MASK}`
9594
+ */
9595
+ const SVG_SPECIFIC_STYLE_MASK = {
9596
+ "stroke-width": null
9597
+ };
9598
+ const setLabelMargins = function setLabelMargins(span, size) {
9599
+ const $span = $(span);
9600
+ const direction = $span.data("labelDirection");
9601
+ let [width, height] = size;
9602
+ // This can happen when a span
9603
+ // is invisible but we still want to update the CSS. At worst, we will
9604
+ // be off by a few pixels instead of in a different position entirely.
9605
+ if (width === 0 && height === 0) {
9606
+ [width, height] = [1, 1];
9607
+ Log.log("Label size was 0x0 in graphie.js; using 1x1 instead");
9608
+ }
9609
+ $span.css("visibility", "");
9610
+ if (typeof direction === "number") {
9611
+ const x = Math.cos(direction);
9612
+ const y = Math.sin(direction);
9613
+ const scale = Math.min(width / 2 / Math.abs(x), height / 2 / Math.abs(y));
9614
+ $span.css({
9615
+ marginLeft: -width / 2 + x * scale,
9616
+ marginTop: -height / 2 - y * scale
9617
+ });
9618
+ } else {
9619
+ const multipliers = labelDirections[direction || "center"];
9620
+ $span.css({
9621
+ marginLeft: Math.round(width * multipliers[0]),
9622
+ marginTop: Math.round(height * multipliers[1])
9490
9623
  });
9491
-
9492
- // Create Raphael canvas
9493
- const localRaphael = raphael.exports(wrapper, width, height);
9494
-
9495
- // Calculate path
9496
- const path = "M" + start[0] + " " + start[1] + " " + "L" + end[0] + " " + end[1];
9497
- const visibleShape = localRaphael.path(path);
9498
- visibleShape.graphiePath = [start, end];
9499
- return {
9500
- wrapper: wrapper,
9501
- visibleShape: visibleShape
9502
- };
9503
9624
  }
9504
- function sinusoid(a, b, c, d) {
9505
- // Plot a sinusoid of the form: f(x) = a * sin(b * x - c) + d
9506
- return thisGraphie.raphael.path(thisGraphie.svgSinusoidPath(a, b, c, d));
9507
- }
9508
- function grid(xr, yr) {
9509
- const step = thisGraphie.currentStyle.step || [1, 1];
9510
- const set = thisGraphie.raphael.set();
9511
- let x = step[0] * Math.ceil(xr[0] / step[0]);
9512
- for (; x <= xr[1]; x += step[0]) {
9513
- set.push(line([x, yr[0]], [x, yr[1]]));
9514
- }
9515
- let y = step[1] * Math.ceil(yr[0] / step[1]);
9516
- for (; y <= yr[1]; y += step[1]) {
9517
- set.push(line([xr[0], y], [xr[1], y]));
9518
- }
9519
- return set;
9520
- }
9521
- function label(point, text, direction, latex) {
9522
- latex = typeof latex === "undefined" || latex;
9523
- const $span = $("<span>").addClass("graphie-label");
9524
- const pad = thisGraphie.currentStyle["label-distance"];
9525
- $span.css($.extend({}, {
9526
- position: "absolute",
9527
- padding: (pad != null ? pad : 7) + "px",
9528
- color: "black"
9529
- })).data("labelDirection", direction).appendTo(thisGraphie.el);
9530
-
9531
- // @ts-expect-error - TS2339 - Property 'setPosition' does not exist on type 'JQuery<HTMLElement>'.
9532
- $span.setPosition = function (point) {
9533
- const scaledPoint = thisGraphie.scalePoint(point);
9534
- $span.css({
9535
- left: scaledPoint[0],
9536
- top: scaledPoint[1]
9537
- });
9538
- };
9539
-
9540
- // @ts-expect-error - TS2339 - Property 'setPosition' does not exist on type 'JQuery<HTMLElement>'.
9541
- $span.setPosition(point);
9542
- const span = $span[0];
9543
-
9544
- // @ts-expect-error - TS2339 - Property 'processMath' does not exist on type 'JQuery<HTMLElement>'.
9545
- $span.processMath = function (math, force) {
9546
- processMath$1(span, math, force, function () {
9547
- const width = span.scrollWidth;
9548
- const height = span.scrollHeight;
9549
- setLabelMargins(span, [width, height]);
9550
- });
9551
- };
9552
-
9553
- // @ts-expect-error - TS2339 - Property 'processText' does not exist on type 'JQuery<HTMLElement>'.
9554
- $span.processText = function (text) {
9555
- $span.html(text);
9556
- const width = span.scrollWidth;
9557
- const height = span.scrollHeight;
9558
- setLabelMargins(span, [width, height]);
9559
- };
9560
- if (latex) {
9561
- // @ts-expect-error - TS2339 - Property 'processMath' does not exist on type 'JQuery<HTMLElement>'.
9562
- $span.processMath(text, /* force */false);
9563
- } else {
9564
- // @ts-expect-error - TS2339 - Property 'processText' does not exist on type 'JQuery<HTMLElement>'.
9565
- $span.processText(text);
9566
- }
9567
- return $span;
9568
- }
9569
- function plotParametric(fn, range, shade, fn2 = t => [t, 0]) {
9570
- // Note: fn2 should only be set if 'shade' is true, as it denotes
9571
- // the function between which fn should have its area shaded.
9572
- // In general, plotParametric shouldn't be used to shade the area
9573
- // between two arbitrary parametrics functions over an interval,
9574
- // as the method assumes that fn and fn2 are both of the form
9575
- // fn(t) = (t, fn'(t)) for some initial fn'.
9576
-
9577
- // We truncate to 500,000, since anything bigger causes
9578
- // overflow in the firefox svg renderer. This is safe
9579
- // since 500,000 is outside the viewport anyway. We
9580
- // write these functions the way we do to handle undefined.
9581
- const clipper = xy => {
9582
- if (Math.abs(xy[1]) > 500000) {
9583
- return [xy[0], Math.min(Math.max(xy[1], -500000), 500000)];
9584
- }
9585
- return xy;
9586
- };
9587
- const clippedFn = x => clipper(fn(x));
9588
- const clippedFn2 = x => clipper(fn2(x));
9589
- if (!thisGraphie.currentStyle.strokeLinejoin) {
9590
- thisGraphie.currentStyle.strokeLinejoin = "round";
9591
- }
9592
- if (!thisGraphie.currentStyle.strokeLinecap) {
9593
- thisGraphie.currentStyle.strokeLinecap = "round";
9594
- }
9595
- const min = range[0];
9596
- const max = range[1];
9597
- let step = (max - min) / (thisGraphie.currentStyle["plot-points"] || 800);
9598
- if (step === 0) {
9599
- step = 1;
9600
- }
9601
- const paths = thisGraphie.raphael.set();
9602
- let points = [];
9603
- let lastDiff = GraphUtils.coordDiff(clippedFn(min), clippedFn2(min));
9604
- let lastFlip = min;
9605
- for (let t = min; t <= max; t += step) {
9606
- const top = clippedFn(t);
9607
- const bottom = clippedFn2(t);
9608
- const diff = GraphUtils.coordDiff(top, bottom);
9609
-
9610
- // Find points where it flips
9611
- // Create path that sketches area between the two functions
9612
- if (
9613
- // if there is an asymptote here, meaning that the graph
9614
- // switches signs and has a large difference
9615
- diff[1] < 0 !== lastDiff[1] < 0 && Math.abs(diff[1] - lastDiff[1]) > 2 * thisGraphie.drawingTransform().pixelsPerUnitY() ||
9616
- // or the function is undefined
9617
- isNaN(diff[1])) {
9618
- // split the path at this point, and draw it
9619
- if (shade) {
9620
- // @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
9621
- points.push(top);
9622
-
9623
- // backtrack to draw paired function
9624
- for (let u = t - step; u >= lastFlip; u -= step) {
9625
- // @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
9626
- points.push(clippedFn2(u));
9627
- }
9628
- lastFlip = t;
9629
- }
9630
- paths.push(path(points));
9631
- // restart the path, excluding this point
9632
- points = [];
9633
- if (shade) {
9634
- // @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
9635
- points.push(top);
9636
- }
9637
- } else {
9638
- // otherwise, just add the point to the path
9639
- // @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
9640
- points.push(top);
9641
- }
9642
- lastDiff = diff;
9625
+ };
9626
+ const GraphUtils = {
9627
+ Graphie: Graphie$1,
9628
+ createGraphie: function (el) {
9629
+ return new Graphie$1(el);
9630
+ },
9631
+ unscaledSvgPath: function (points) {
9632
+ // If this is an empty closed path, return "" instead of "z", which
9633
+ // would give an error
9634
+ if (points[0] === true) {
9635
+ return "";
9643
9636
  }
9644
- if (shade) {
9645
- // backtrack to draw paired function
9646
- for (let u = max - step; u >= lastFlip; u -= step) {
9647
- // @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
9648
- points.push(clippedFn2(u));
9637
+ return points.map(function (point, i) {
9638
+ if (point === true) {
9639
+ return "z";
9649
9640
  }
9650
- }
9651
- paths.push(path(points));
9652
- return paths;
9653
- }
9654
- function plot(fn, range, swapAxes, shade, fn2) {
9655
- const min = range[0];
9656
- const max = range[1];
9657
- if (!thisGraphie.currentStyle["plot-points"]) {
9658
- thisGraphie.currentStyle["plot-points"] = 2 * (max - min) * thisGraphie.drawingTransform().pixelsPerUnitX();
9659
- }
9660
- if (swapAxes) {
9661
- if (fn2) {
9662
- // TODO(charlie): support swapped axis area shading
9663
- throw new PerseusError("Can't shade area between functions with swapped axes.", Errors.Internal);
9641
+ return (i === 0 ? "M" : "L") + point[0] + " " + point[1];
9642
+ }).join("");
9643
+ },
9644
+ getDistance: function (point1, point2) {
9645
+ return point.distanceToPoint(point1, point2);
9646
+ },
9647
+ /**
9648
+ * Round the given coordinates to a given snap value
9649
+ * (e.g., nearest 0.2 increment)
9650
+ */
9651
+ snapCoord: function (coord, snap) {
9652
+ return coord.map(function (val, i) {
9653
+ return KhanMath.roundToNearest(snap[i], val);
9654
+ });
9655
+ },
9656
+ // Find the angle in degrees between two or three points
9657
+ findAngle: function (point1, point2, vertex) {
9658
+ if (vertex === undefined) {
9659
+ const x = point1[0] - point2[0];
9660
+ const y = point1[1] - point2[1];
9661
+ if (!x && !y) {
9662
+ return 0;
9664
9663
  }
9665
- return plotParametric(function (y) {
9666
- return [fn(y), y];
9667
- }, range, shade);
9664
+ return (180 + Math.atan2(-y, -x) * 180 / Math.PI + 360) % 360;
9668
9665
  }
9669
- if (fn2) {
9670
- if (shade) {
9671
- return plotParametric(function (x) {
9672
- return [x, fn(x)];
9673
- }, range, shade, function (x) {
9674
- return [x, fn2(x)];
9675
- });
9676
- }
9677
- throw new PerseusError("fn2 should only be set when 'shade' is True.", Errors.Internal);
9678
- }
9679
- return plotParametric(function (x) {
9680
- return [x, fn(x)];
9681
- }, range, shade);
9682
- }
9683
- const drawingTools = {
9684
- circle,
9685
- rect,
9686
- ellipse,
9687
- fixedEllipse,
9688
- arc,
9689
- path,
9690
- fixedPath,
9691
- scaledPath,
9692
- line,
9693
- parabola,
9694
- fixedLine,
9695
- sinusoid,
9696
- grid,
9697
- label,
9698
- plotParametric,
9699
- plot
9700
- };
9701
- function graphify(drawingFn) {
9702
- return function (...args) {
9703
- const last = args[args.length - 1];
9704
- const oldStyle = thisGraphie.currentStyle;
9705
- let result;
9706
-
9707
- // The last argument is probably trying to change the style
9708
- if (typeof last === "object" && !_.isArray(last)) {
9709
- thisGraphie.currentStyle = _extends({}, thisGraphie.currentStyle, thisGraphie.processAttributes(last));
9710
- const rest = [].slice.call(args, 0, args.length - 1);
9711
- result = drawingFn(...rest);
9712
- } else {
9713
- thisGraphie.currentStyle = $.extend({}, thisGraphie.currentStyle);
9714
- result = drawingFn(...args);
9715
- }
9716
-
9717
- // Bad heuristic for recognizing Raphael elements and sets
9718
- const type = result.constructor.prototype;
9719
- if (type === raphael.exports.el || type === raphael.exports.st) {
9720
- result.attr(thisGraphie.currentStyle);
9721
- if (thisGraphie.currentStyle.arrows) {
9722
- result = addArrowheads(result);
9723
- }
9724
- } else if (result instanceof $) {
9725
- // We assume that if it's not a Raphael element/set, it
9726
- // does not contain SVG.
9727
- // @ts-expect-error - TS2339 - Property 'css' does not exist on type '{}'.
9728
- result.css(_extends({}, thisGraphie.currentStyle, SVG_SPECIFIC_STYLE_MASK));
9729
- }
9730
- thisGraphie.currentStyle = oldStyle;
9731
- return result;
9732
- };
9733
- }
9734
- $.each(drawingTools, function (name) {
9735
- thisGraphie[name] = graphify(drawingTools[name]);
9736
- });
9737
- return thisGraphie;
9666
+ return GraphUtils.findAngle(point1, vertex) - GraphUtils.findAngle(point2, vertex);
9667
+ },
9668
+ graphs: {}
9738
9669
  };
9739
9670
 
9740
9671
  /* eslint-disable */
@@ -10452,15 +10383,65 @@ const WrappedPath = function WrappedPath(graphie, points, options) {
10452
10383
  };
10453
10384
  _.extend(WrappedPath.prototype, WrappedDefaults);
10454
10385
 
10455
- /* eslint-disable @babel/no-invalid-this */
10386
+ /**
10387
+ * A collection of geomtry-related utility functions
10388
+ */
10456
10389
  const {
10457
- getCanUse3dTransform
10458
- } = InteractiveUtil;
10459
- function sum$1(array) {
10460
- return _.reduce(array, function (memo, arg) {
10461
- return memo + arg;
10462
- }, 0);
10390
+ eq: eq$1,
10391
+ deepEq: deepEq$4
10392
+ } = Util;
10393
+
10394
+ // This should really be a readonly tuple of [number, number]
10395
+
10396
+ // a, b, c
10397
+
10398
+ function sign(val) {
10399
+ if (eq$1(val, 0)) {
10400
+ return 0;
10401
+ }
10402
+ return val > 0 ? 1 : -1;
10403
+ }
10404
+ function ccw(a, b, c) {
10405
+ return (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1]);
10406
+ }
10407
+ function collinear(a, b, c) {
10408
+ return eq$1(ccw(a, b, c), 0);
10463
10409
  }
10410
+
10411
+ // Given rect bounding points A and B, whether point C is inside the rect
10412
+ function pointInRect(a, b, c) {
10413
+ 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]);
10414
+ }
10415
+
10416
+ // Whether line segment AB intersects line segment CD
10417
+ // http://www.geeksforgeeks.org/check-if-two-given-line-segments-intersect/
10418
+ function intersects(ab, cd) {
10419
+ 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]]];
10420
+ const orientations = _.map(triplets, function (triplet) {
10421
+ return sign(ccw(...triplet));
10422
+ });
10423
+ if (orientations[0] !== orientations[1] && orientations[2] !== orientations[3]) {
10424
+ return true;
10425
+ }
10426
+ for (let i = 0; i < 4; i++) {
10427
+ if (orientations[i] === 0 && pointInRect(...triplets[i])) {
10428
+ return true;
10429
+ }
10430
+ }
10431
+ return false;
10432
+ }
10433
+ function vector(a, b) {
10434
+ return _.map(_.zip(a, b), function (pair) {
10435
+ return pair[0] - pair[1];
10436
+ });
10437
+ }
10438
+ function reverseVector(vector) {
10439
+ return [-vector[0], -vector[1]];
10440
+ }
10441
+
10442
+ // Returns whether connecting the given sequence of `points` forms a clockwise
10443
+ // path (assuming a closed loop, where the last point connects back to the
10444
+ // first).
10464
10445
  function clockwise$1(points) {
10465
10446
  const segments = _.zip(points, points.slice(1).concat(points.slice(0, 1)));
10466
10447
  const areas = _.map(segments, function (segment) {
@@ -10470,17 +10451,164 @@ function clockwise$1(points) {
10470
10451
  });
10471
10452
  return sum$1(areas) > 0;
10472
10453
  }
10473
-
10474
- /* vector-add multiple [x, y] coords/vectors */
10475
- function addPoints(...points) {
10476
- const zipped = _.zip(...points);
10477
- return _.map(zipped, sum$1);
10454
+ function magnitude(v) {
10455
+ return Math.sqrt(_.reduce(v, function (memo, el) {
10456
+ // @ts-expect-error - TS2345 - Argument of type 'Coord' is not assignable to parameter of type 'number'.
10457
+ return memo + Math.pow(el, 2);
10458
+ }, 0));
10478
10459
  }
10479
- function reverseVector(vector) {
10480
- return _.map(vector, function (coord) {
10481
- return coord * -1;
10460
+ function dotProduct(a, b) {
10461
+ return _.reduce(_.zip(a, b), function (memo, pair) {
10462
+ return memo + pair[0] * pair[1];
10463
+ }, 0);
10464
+ }
10465
+ function sideLengths(coords) {
10466
+ const segments = _.zip(coords, rotate(coords));
10467
+ return segments.map(function (segment) {
10468
+ // @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.
10469
+ return magnitude(vector(...segment));
10482
10470
  });
10483
10471
  }
10472
+
10473
+ // Based on http://math.stackexchange.com/a/151149
10474
+ function angleMeasures(coords) {
10475
+ const triplets = _.zip(rotate(coords, -1), coords, rotate(coords, 1));
10476
+ const offsets = _.map(triplets, function (triplet) {
10477
+ const p = vector(triplet[1], triplet[0]);
10478
+ const q = vector(triplet[2], triplet[1]);
10479
+ // @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[]'.
10480
+ const raw = Math.acos(dotProduct(p, q) / (magnitude(p) * magnitude(q)));
10481
+ // @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
10482
+ return sign(ccw(...triplet)) > 0 ? raw : -raw;
10483
+ });
10484
+ const sum = _.reduce(offsets, function (memo, arg) {
10485
+ return memo + arg;
10486
+ }, 0);
10487
+ return _.map(offsets, function (offset) {
10488
+ return sum > 0 ? Math.PI - offset : Math.PI + offset;
10489
+ });
10490
+ }
10491
+
10492
+ // Whether two polygons are similar (or if specified, congruent)
10493
+ function similar(coords1, coords2, tolerance) {
10494
+ if (coords1.length !== coords2.length) {
10495
+ return false;
10496
+ }
10497
+ const n = coords1.length;
10498
+ const angles1 = angleMeasures(coords1);
10499
+ const angles2 = angleMeasures(coords2);
10500
+ const sides1 = sideLengths(coords1);
10501
+ const sides2 = sideLengths(coords2);
10502
+ for (let i = 0; i < 2 * n; i++) {
10503
+ let angles = angles2.slice();
10504
+ let sides = sides2.slice();
10505
+
10506
+ // Reverse angles and sides to allow matching reflected polygons
10507
+ if (i >= n) {
10508
+ angles.reverse();
10509
+ sides.reverse();
10510
+ // Since sides are calculated from two coordinates,
10511
+ // simply reversing results in an off by one error
10512
+ // @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
10513
+ sides = rotate(sides, 1);
10514
+ }
10515
+
10516
+ // @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
10517
+ angles = rotate(angles, i);
10518
+ // @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
10519
+ sides = rotate(sides, i);
10520
+ if (deepEq$4(angles1, angles)) {
10521
+ const sidePairs = _.zip(sides1, sides);
10522
+ const factors = _.map(sidePairs, function (pair) {
10523
+ return pair[0] / pair[1];
10524
+ });
10525
+ const same = _.all(factors, function (factor) {
10526
+ return eq$1(factors[0], factor);
10527
+ });
10528
+ const congruentEnough = _.all(sidePairs, function (pair) {
10529
+ return number.equal(pair[0], pair[1], tolerance);
10530
+ });
10531
+ if (same && congruentEnough) {
10532
+ return true;
10533
+ }
10534
+ }
10535
+ }
10536
+ return false;
10537
+ }
10538
+
10539
+ // Given triangle with sides ABC return angle opposite side C in degrees
10540
+ function lawOfCosines(a, b, c) {
10541
+ return Math.acos((a * a + b * b - c * c) / (2 * a * b)) * 180 / Math.PI;
10542
+ }
10543
+ function canonicalSineCoefficients$1([amplitude, angularFrequency, phase, verticalOffset]) {
10544
+ // For a curve of the form f(x) = a * Sin(b * x - c) + d,
10545
+ // this function ensures that a, b > 0, and c is its
10546
+ // smallest possible positive value.
10547
+
10548
+ // Guarantee a > 0
10549
+ if (amplitude < 0) {
10550
+ amplitude *= -1;
10551
+ angularFrequency *= -1;
10552
+ phase *= -1;
10553
+ }
10554
+ const period = 2 * Math.PI;
10555
+ // Guarantee b > 0
10556
+ if (angularFrequency < 0) {
10557
+ angularFrequency *= -1;
10558
+ phase *= -1;
10559
+ phase += period / 2;
10560
+ }
10561
+
10562
+ // Guarantee c is smallest possible positive value
10563
+ while (phase > 0) {
10564
+ phase -= period;
10565
+ }
10566
+ while (phase < 0) {
10567
+ phase += period;
10568
+ }
10569
+ return [amplitude, angularFrequency, phase, verticalOffset];
10570
+ }
10571
+
10572
+ // e.g. rotate([1, 2, 3]) -> [2, 3, 1]
10573
+ function rotate(array, n) {
10574
+ n = typeof n === "undefined" ? 1 : n % array.length;
10575
+ return array.slice(n).concat(array.slice(0, n));
10576
+ }
10577
+ function getLineEquation(first, second) {
10578
+ if (eq$1(first[0], second[0])) {
10579
+ return "x = " + first[0].toFixed(3);
10580
+ }
10581
+ const m = (second[1] - first[1]) / (second[0] - first[0]);
10582
+ const b = first[1] - m * first[0];
10583
+ return "y = " + m.toFixed(3) + "x + " + b.toFixed(3);
10584
+ }
10585
+
10586
+ // Stolen from the wikipedia article
10587
+ // http://en.wikipedia.org/wiki/Line-line_intersection
10588
+ function getLineIntersection(
10589
+ // TODO(LP-10725): update these to be 2-tuples
10590
+ firstPoints, secondPoints) {
10591
+ const x1 = firstPoints[0][0];
10592
+ const y1 = firstPoints[0][1];
10593
+ const x2 = firstPoints[1][0];
10594
+ const y2 = firstPoints[1][1];
10595
+ const x3 = secondPoints[0][0];
10596
+ const y3 = secondPoints[0][1];
10597
+ const x4 = secondPoints[1][0];
10598
+ const y4 = secondPoints[1][1];
10599
+ const determinant = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
10600
+ if (Math.abs(determinant) < 1e-9) {
10601
+ return "Lines are parallel";
10602
+ }
10603
+ const x = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / determinant;
10604
+ const y = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / determinant;
10605
+ return "Intersection: (" + x.toFixed(3) + ", " + y.toFixed(3) + ")";
10606
+ }
10607
+
10608
+ /* eslint-disable @babel/no-invalid-this */
10609
+ const {
10610
+ getCanUse3dTransform
10611
+ } = InteractiveUtil;
10484
10612
  function scaledDistanceFromAngle(angle) {
10485
10613
  const a = 3.51470560176242 * 20;
10486
10614
  const b = 0.5687298702748785 * 20;
@@ -10657,10 +10785,10 @@ _.extend(GraphUtils.Graphie.prototype, {
10657
10785
  let sRadius = sPadding + scaledDistanceFromAngle(angle);
10658
10786
  const temp = [];
10659
10787
  if (Math.abs(angle - 90) < 1e-9 && options.showRightAngleMarker) {
10660
- const v1 = addPoints(sVertex, scaledPolarDeg(sRadius, startAngle));
10661
- const v2 = addPoints(sVertex, scaledPolarDeg(sRadius, endAngle));
10788
+ const v1 = vector$1.add(sVertex, scaledPolarDeg(sRadius, startAngle));
10789
+ const v2 = vector$1.add(sVertex, scaledPolarDeg(sRadius, endAngle));
10662
10790
  sRadius *= Math.SQRT2;
10663
- const v3 = addPoints(sVertex, scaledPolarDeg(sRadius, halfAngle));
10791
+ const v3 = vector$1.add(sVertex, scaledPolarDeg(sRadius, halfAngle));
10664
10792
  _.each([v1, v2], function (v) {
10665
10793
  // @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
10666
10794
  temp.push(graphie.scaledPath([v, v3], options.style));
@@ -10681,7 +10809,7 @@ _.extend(GraphUtils.Graphie.prototype, {
10681
10809
  text = text.replace(match[0], KhanMath.toFixedApprox(angle, precision) + "^{\\circ}");
10682
10810
  }
10683
10811
  const sOffset = scaledPolarDeg(sRadius + 15, halfAngle);
10684
- const sPosition = addPoints(sVertex, sOffset);
10812
+ const sPosition = vector$1.add(sVertex, sOffset);
10685
10813
  const position = graphie.unscalePoint(sPosition);
10686
10814
 
10687
10815
  // Reuse label if possible
@@ -10732,7 +10860,7 @@ _.extend(GraphUtils.Graphie.prototype, {
10732
10860
  const sOffset = sSpacing * (i - (n - 1) / 2);
10733
10861
  const sOffsetVector = scaledPolarRad(sOffset, parallelAngle);
10734
10862
  const sHeightVector = scaledPolarRad(sHeight, perpendicularAngle);
10735
- const sPath = [addPoints(sMidpoint, sOffsetVector, sHeightVector), addPoints(sMidpoint, sOffsetVector, reverseVector(sHeightVector))];
10863
+ const sPath = [vector$1.add(sMidpoint, sOffsetVector, sHeightVector), vector$1.add(sMidpoint, sOffsetVector, reverseVector(sHeightVector))];
10736
10864
 
10737
10865
  // @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
10738
10866
  temp.push(graphie.scaledPath(sPath, style));
@@ -10759,7 +10887,7 @@ _.extend(GraphUtils.Graphie.prototype, {
10759
10887
  if (start !== p1) {
10760
10888
  sOffsetVector = reverseVector(sOffsetVector);
10761
10889
  }
10762
- const sEnd = addPoints(sMidpoint, sOffsetVector);
10890
+ const sEnd = vector$1.add(sMidpoint, sOffsetVector);
10763
10891
 
10764
10892
  // @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
10765
10893
  temp.push(graphie.scaledPath([sStart, sEnd], style));
@@ -10775,7 +10903,7 @@ _.extend(GraphUtils.Graphie.prototype, {
10775
10903
  }
10776
10904
  const sOffset = 20;
10777
10905
  const sOffsetVector = scaledPolarRad(sOffset, perpendicularAngle);
10778
- const sPosition = addPoints(sMidpoint, sOffsetVector);
10906
+ const sPosition = vector$1.add(sMidpoint, sOffsetVector);
10779
10907
  const position = graphie.unscalePoint(sPosition);
10780
10908
 
10781
10909
  // Reuse label if possible
@@ -10833,7 +10961,7 @@ _.extend(GraphUtils.Graphie.prototype, {
10833
10961
  }
10834
10962
  const sRadius = 10 + scaledDistanceFromAngle(360 - angle);
10835
10963
  const sOffsetVector = scaledPolarDeg(sRadius, halfAngle);
10836
- const sPosition = addPoints(sVertex, sOffsetVector);
10964
+ const sPosition = vector$1.add(sVertex, sOffsetVector);
10837
10965
  const position = graphie.unscalePoint(sPosition);
10838
10966
 
10839
10967
  // Reuse label if possible
@@ -12684,7 +12812,7 @@ _.extend(GraphUtils.Graphie.prototype, {
12684
12812
  // @ts-expect-error - TS2350 - Only a void function can be called with the 'new' keyword.
12685
12813
  return new Ruler(this, options || {});
12686
12814
  },
12687
- addPoints: addPoints
12815
+ addPoints: vector$1.add
12688
12816
  });
12689
12817
  function Protractor(graph, center) {
12690
12818
  // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
@@ -13038,7 +13166,7 @@ function Ruler(graphie, options) {
13038
13166
  set.push(label);
13039
13167
  }
13040
13168
  });
13041
- const mouseTarget = graphie.mouselayer.path(GraphUtils.svgPath([leftBottom, [left, top], rightTop, [right, bottom], /* closed */true]));
13169
+ const mouseTarget = graphie.mouselayer.path(graphie.svgPath([leftBottom, [left, top], rightTop, [right, bottom], /* closed */true]));
13042
13170
  mouseTarget.attr({
13043
13171
  fill: "#000",
13044
13172
  opacity: 0,
@@ -13282,12 +13410,12 @@ _.extend(MovableAngle.prototype, {
13282
13410
  points[1].onMove = function (x, y) {
13283
13411
  const oldVertex = points[1].coord;
13284
13412
  const newVertex = [x, y];
13285
- const delta = addPoints(newVertex, reverseVector(oldVertex));
13413
+ const delta = vector$1.add(newVertex, reverseVector(oldVertex));
13286
13414
  let valid = true;
13287
13415
  const newPoints = {};
13288
13416
  _.each([0, 2], function (i) {
13289
13417
  const oldPoint = points[i].coord;
13290
- let newPoint = addPoints(oldPoint, delta);
13418
+ let newPoint = vector$1.add(oldPoint, delta);
13291
13419
  let angle = GraphUtils.findAngle(newVertex, newPoint);
13292
13420
  angle *= Math.PI / 180;
13293
13421
  newPoint = graphie.constrainToBoundsOnAngle(newPoint, 10, angle);
@@ -13324,7 +13452,7 @@ _.extend(MovableAngle.prototype, {
13324
13452
  let angle = GraphUtils.findAngle(newPoint, vertex);
13325
13453
  angle = Math.round((angle - snapOffset) / snap) * snap + snapOffset;
13326
13454
  const distance = GraphUtils.getDistance(newPoint, vertex);
13327
- return addPoints(vertex, polar(distance, angle));
13455
+ return vector$1.add(vertex, polar(distance, angle));
13328
13456
  }
13329
13457
  return true;
13330
13458
  };
@@ -13475,7 +13603,7 @@ _.extend(MovableAngle.prototype, {
13475
13603
 
13476
13604
  /* eslint-disable @babel/no-invalid-this */
13477
13605
  const nestedMap$1 = Util.nestedMap;
13478
- const deepEq$4 = Util.deepEq;
13606
+ const deepEq$3 = Util.deepEq;
13479
13607
 
13480
13608
  /**
13481
13609
  * A base class for all Graphie Movables
@@ -13572,7 +13700,7 @@ const createSimpleClass = function createSimpleClass(addFunction) {
13572
13700
  this._prevProps = this.props;
13573
13701
  },
13574
13702
  modify: function (graphie) {
13575
- if (!deepEq$4(this.props, this._prevProps)) {
13703
+ if (!deepEq$3(this.props, this._prevProps)) {
13576
13704
  this.remove();
13577
13705
  this.add(graphie);
13578
13706
  this._prevProps = this.props;
@@ -16029,7 +16157,7 @@ var Movables = {
16029
16157
  const GraphieMovable = GraphieClasses.GraphieMovable;
16030
16158
  const createGraphie = GraphUtils.createGraphie;
16031
16159
  const {
16032
- deepEq: deepEq$3,
16160
+ deepEq: deepEq$2,
16033
16161
  nestedMap
16034
16162
  } = Util;
16035
16163
  const {
@@ -16275,7 +16403,7 @@ class Graphie extends React.Component {
16275
16403
  if (this.props.setup !== prevProps.setup) {
16276
16404
  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);
16277
16405
  }
16278
- if (!deepEq$3(this.props.options, prevProps.options) || !deepEq$3(this.props.box, prevProps.box) || !deepEq$3(this.props.range, prevProps.range)) {
16406
+ if (!deepEq$2(this.props.options, prevProps.options) || !deepEq$2(this.props.box, prevProps.box) || !deepEq$2(this.props.range, prevProps.range)) {
16279
16407
  this._setupGraphie();
16280
16408
  }
16281
16409
  this._updateMovables();
@@ -18965,10 +19093,18 @@ class Renderer extends React.Component {
18965
19093
  });
18966
19094
  };
18967
19095
  this._getAllWidgetsStartProps = (allWidgetInfo, props) => {
18968
- // @ts-expect-error - TS2345 - Argument of type '{ [key: string]: PerseusWidget; }' is not assignable to parameter of type 'Partial<Record<string, CategorizerWidget>>'.
18969
- return mapObject$1(allWidgetInfo, widgetInfo => {
18970
- return getRendererPropsForWidgetInfo(widgetInfo, props.problemNum);
19096
+ const {
19097
+ apiOptions,
19098
+ problemNum
19099
+ } = props;
19100
+ const widgetsStartProps = {};
19101
+ entries(allWidgetInfo).forEach(([key, widgetInfo]) => {
19102
+ widgetsStartProps[key] = getRendererPropsForWidgetInfo(widgetInfo, problemNum);
18971
19103
  });
19104
+
19105
+ // Call the onWidgetStartProps callback if it exists
19106
+ apiOptions == null || apiOptions.onWidgetStartProps == null || apiOptions.onWidgetStartProps(widgetsStartProps);
19107
+ return widgetsStartProps;
18972
19108
  };
18973
19109
  // This is only used in _getWidgetInfo as a fallback if widgetId
18974
19110
  // doesn't exist in this.state.widgetInfo. It doesn't get run as
@@ -25443,7 +25579,7 @@ const DEFAULT_BACKGROUND_IMAGE = {
25443
25579
 
25444
25580
  // TODO(charlie): These really need to go into a utility file as they're being
25445
25581
  // used by both interactive-graph and now grapher.
25446
- function canonicalSineCoefficients$1(coeffs) {
25582
+ function canonicalSineCoefficients(coeffs) {
25447
25583
  // For a curve of the form f(x) = a * Sin(b * x - c) + d,
25448
25584
  // this function ensures that a, b > 0, and c is its
25449
25585
  // smallest possible positive value.
@@ -25624,7 +25760,7 @@ const Sinusoid = _.extend({}, PlotDefaults, {
25624
25760
  return "y = " + a.toFixed(3) + " sin(" + b.toFixed(3) + "x - " + c.toFixed(3) + ") + " + d.toFixed(3);
25625
25761
  },
25626
25762
  areEqual: function (coeffs1, coeffs2) {
25627
- return Util.deepEq(canonicalSineCoefficients$1(coeffs1), canonicalSineCoefficients$1(coeffs2));
25763
+ return Util.deepEq(canonicalSineCoefficients(coeffs1), canonicalSineCoefficients(coeffs2));
25628
25764
  }
25629
25765
  });
25630
25766
  const Tangent = _.extend({}, PlotDefaults, {
@@ -27842,212 +27978,6 @@ Graph.defaultProps = {
27842
27978
  isMobile: false
27843
27979
  };
27844
27980
 
27845
- /**
27846
- * A collection of geomtry-related utility functions
27847
- */
27848
- const {
27849
- eq: eq$1,
27850
- deepEq: deepEq$2
27851
- } = Util;
27852
-
27853
- // This should really be a readonly tuple of [number, number]
27854
-
27855
- // a, b, c
27856
-
27857
- function sign(val) {
27858
- if (eq$1(val, 0)) {
27859
- return 0;
27860
- }
27861
- return val > 0 ? 1 : -1;
27862
- }
27863
- function ccw(a, b, c) {
27864
- return (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1]);
27865
- }
27866
- function collinear(a, b, c) {
27867
- return eq$1(ccw(a, b, c), 0);
27868
- }
27869
-
27870
- // Given rect bounding points A and B, whether point C is inside the rect
27871
- function pointInRect(a, b, c) {
27872
- 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]);
27873
- }
27874
-
27875
- // Whether line segment AB intersects line segment CD
27876
- // http://www.geeksforgeeks.org/check-if-two-given-line-segments-intersect/
27877
- function intersects(ab, cd) {
27878
- 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]]];
27879
- const orientations = _.map(triplets, function (triplet) {
27880
- return sign(ccw(...triplet));
27881
- });
27882
- if (orientations[0] !== orientations[1] && orientations[2] !== orientations[3]) {
27883
- return true;
27884
- }
27885
- for (let i = 0; i < 4; i++) {
27886
- if (orientations[i] === 0 && pointInRect(...triplets[i])) {
27887
- return true;
27888
- }
27889
- }
27890
- return false;
27891
- }
27892
- function vector(a, b) {
27893
- return _.map(_.zip(a, b), function (pair) {
27894
- return pair[0] - pair[1];
27895
- });
27896
- }
27897
- function magnitude(v) {
27898
- return Math.sqrt(_.reduce(v, function (memo, el) {
27899
- // @ts-expect-error - TS2345 - Argument of type 'Coord' is not assignable to parameter of type 'number'.
27900
- return memo + Math.pow(el, 2);
27901
- }, 0));
27902
- }
27903
- function dotProduct(a, b) {
27904
- return _.reduce(_.zip(a, b), function (memo, pair) {
27905
- return memo + pair[0] * pair[1];
27906
- }, 0);
27907
- }
27908
- function sideLengths(coords) {
27909
- const segments = _.zip(coords, rotate(coords));
27910
- return segments.map(function (segment) {
27911
- // @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.
27912
- return magnitude(vector(...segment));
27913
- });
27914
- }
27915
-
27916
- // Based on http://math.stackexchange.com/a/151149
27917
- function angleMeasures(coords) {
27918
- const triplets = _.zip(rotate(coords, -1), coords, rotate(coords, 1));
27919
- const offsets = _.map(triplets, function (triplet) {
27920
- const p = vector(triplet[1], triplet[0]);
27921
- const q = vector(triplet[2], triplet[1]);
27922
- // @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[]'.
27923
- const raw = Math.acos(dotProduct(p, q) / (magnitude(p) * magnitude(q)));
27924
- // @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
27925
- return sign(ccw(...triplet)) > 0 ? raw : -raw;
27926
- });
27927
- const sum = _.reduce(offsets, function (memo, arg) {
27928
- return memo + arg;
27929
- }, 0);
27930
- return _.map(offsets, function (offset) {
27931
- return sum > 0 ? Math.PI - offset : Math.PI + offset;
27932
- });
27933
- }
27934
-
27935
- // Whether two polygons are similar (or if specified, congruent)
27936
- function similar(coords1, coords2, tolerance) {
27937
- if (coords1.length !== coords2.length) {
27938
- return false;
27939
- }
27940
- const n = coords1.length;
27941
- const angles1 = angleMeasures(coords1);
27942
- const angles2 = angleMeasures(coords2);
27943
- const sides1 = sideLengths(coords1);
27944
- const sides2 = sideLengths(coords2);
27945
- for (let i = 0; i < 2 * n; i++) {
27946
- let angles = angles2.slice();
27947
- let sides = sides2.slice();
27948
-
27949
- // Reverse angles and sides to allow matching reflected polygons
27950
- if (i >= n) {
27951
- angles.reverse();
27952
- sides.reverse();
27953
- // Since sides are calculated from two coordinates,
27954
- // simply reversing results in an off by one error
27955
- // @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
27956
- sides = rotate(sides, 1);
27957
- }
27958
-
27959
- // @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
27960
- angles = rotate(angles, i);
27961
- // @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
27962
- sides = rotate(sides, i);
27963
- if (deepEq$2(angles1, angles)) {
27964
- const sidePairs = _.zip(sides1, sides);
27965
- const factors = _.map(sidePairs, function (pair) {
27966
- return pair[0] / pair[1];
27967
- });
27968
- const same = _.all(factors, function (factor) {
27969
- return eq$1(factors[0], factor);
27970
- });
27971
- const congruentEnough = _.all(sidePairs, function (pair) {
27972
- return number.equal(pair[0], pair[1], tolerance);
27973
- });
27974
- if (same && congruentEnough) {
27975
- return true;
27976
- }
27977
- }
27978
- }
27979
- return false;
27980
- }
27981
-
27982
- // Given triangle with sides ABC return angle opposite side C in degrees
27983
- function lawOfCosines(a, b, c) {
27984
- return Math.acos((a * a + b * b - c * c) / (2 * a * b)) * 180 / Math.PI;
27985
- }
27986
- function canonicalSineCoefficients([amplitude, angularFrequency, phase, verticalOffset]) {
27987
- // For a curve of the form f(x) = a * Sin(b * x - c) + d,
27988
- // this function ensures that a, b > 0, and c is its
27989
- // smallest possible positive value.
27990
-
27991
- // Guarantee a > 0
27992
- if (amplitude < 0) {
27993
- amplitude *= -1;
27994
- angularFrequency *= -1;
27995
- phase *= -1;
27996
- }
27997
- const period = 2 * Math.PI;
27998
- // Guarantee b > 0
27999
- if (angularFrequency < 0) {
28000
- angularFrequency *= -1;
28001
- phase *= -1;
28002
- phase += period / 2;
28003
- }
28004
-
28005
- // Guarantee c is smallest possible positive value
28006
- while (phase > 0) {
28007
- phase -= period;
28008
- }
28009
- while (phase < 0) {
28010
- phase += period;
28011
- }
28012
- return [amplitude, angularFrequency, phase, verticalOffset];
28013
- }
28014
-
28015
- // e.g. rotate([1, 2, 3]) -> [2, 3, 1]
28016
- function rotate(array, n) {
28017
- n = typeof n === "undefined" ? 1 : n % array.length;
28018
- return array.slice(n).concat(array.slice(0, n));
28019
- }
28020
- function getLineEquation(first, second) {
28021
- if (eq$1(first[0], second[0])) {
28022
- return "x = " + first[0].toFixed(3);
28023
- }
28024
- const m = (second[1] - first[1]) / (second[0] - first[0]);
28025
- const b = first[1] - m * first[0];
28026
- return "y = " + m.toFixed(3) + "x + " + b.toFixed(3);
28027
- }
28028
-
28029
- // Stolen from the wikipedia article
28030
- // http://en.wikipedia.org/wiki/Line-line_intersection
28031
- function getLineIntersection(
28032
- // TODO(LP-10725): update these to be 2-tuples
28033
- firstPoints, secondPoints) {
28034
- const x1 = firstPoints[0][0];
28035
- const y1 = firstPoints[0][1];
28036
- const x2 = firstPoints[1][0];
28037
- const y2 = firstPoints[1][1];
28038
- const x3 = secondPoints[0][0];
28039
- const y3 = secondPoints[0][1];
28040
- const x4 = secondPoints[1][0];
28041
- const y4 = secondPoints[1][1];
28042
- const determinant = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
28043
- if (Math.abs(determinant) < 1e-9) {
28044
- return "Lines are parallel";
28045
- }
28046
- const x = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / determinant;
28047
- const y = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / determinant;
28048
- return "Intersection: (" + x.toFixed(3) + ", " + y.toFixed(3) + ")";
28049
- }
28050
-
28051
27981
  const {
28052
27982
  DeprecationMixin
28053
27983
  } = Util;
@@ -29738,8 +29668,8 @@ class InteractiveGraph extends React.Component {
29738
29668
  const correctCoeffs = this.getSinusoidCoefficients(
29739
29669
  // @ts-expect-error - TS2345 - Argument of type 'readonly Coord[] | undefined' is not assignable to parameter of type 'readonly Coord[]'.
29740
29670
  rubric.correct.coords);
29741
- const canonicalGuessCoeffs = canonicalSineCoefficients(guessCoeffs);
29742
- const canonicalCorrectCoeffs = canonicalSineCoefficients(correctCoeffs);
29671
+ const canonicalGuessCoeffs = canonicalSineCoefficients$1(guessCoeffs);
29672
+ const canonicalCorrectCoeffs = canonicalSineCoefficients$1(correctCoeffs);
29743
29673
  // If the canonical coefficients match, it's correct.
29744
29674
  if (deepEq$1(canonicalGuessCoeffs, canonicalCorrectCoeffs)) {
29745
29675
  return {
@@ -31468,7 +31398,6 @@ class LabelImage extends React.Component {
31468
31398
 
31469
31399
  // Update Perseus widget state with user selected answers without
31470
31400
  // triggering interaction events for listeners.
31471
- // @ts-expect-error - TS2345 - Argument of type 'null' is not assignable to parameter of type '(() => unknown) | undefined'.
31472
31401
  onChange({
31473
31402
  markers: updatedMarkers
31474
31403
  }, null, true);
@@ -41572,7 +41501,7 @@ const init = function init(options) {
41572
41501
 
41573
41502
  // This file is processed by a Rollup plugin (replace) to inject the production
41574
41503
  const libName = "@khanacademy/perseus";
41575
- const libVersion = "19.0.0";
41504
+ const libVersion = "19.1.1";
41576
41505
  addLibraryVersionToPerseusDebug(libName, libVersion);
41577
41506
 
41578
41507
  const apiVersion = {
@@ -44559,15 +44488,15 @@ function injectWidgets(content, widgets) {
44559
44488
  }
44560
44489
 
44561
44490
  /**
44562
- * Helper to parse Perseus JSON
44491
+ * Helper to parse PerseusItem JSON
44563
44492
  * Why not just use JSON.parse? We want:
44564
44493
  * - To make sure types are correct
44565
44494
  * - To give us a central place to validate/transform output if needed
44566
- * @param {string} input - the stringified Perseus JSON
44495
+ * @param {string} json - the stringified PerseusItem JSON
44567
44496
  * @returns {PerseusItem} the parsed PerseusItem object
44568
44497
  */
44569
- function parsePerseusJSON(input) {
44570
- return JSON.parse(input);
44498
+ function parsePerseusItem(json) {
44499
+ return JSON.parse(json);
44571
44500
  }
44572
44501
 
44573
44502
  const EditorJsonify = {
@@ -44578,5 +44507,5 @@ const EditorJsonify = {
44578
44507
  }
44579
44508
  };
44580
44509
 
44581
- export { ApiOptions, ArrowPicker, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, ColorPicker, DashPicker, dependencies as Dependencies, EditorJsonify, ElementContainer, Errors, Expression, util as GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, ItemExtras, JiptParagraphs, KhanColors, KhanMath, context as LoadingContext, Log, Matrix$1 as MatrixWidget, MultiItems, NumericInput$1 as NumericInput, PerseusError, PerseusExpressionAnswerFormConsidered, PerseusMarkdown, Plotter$1 as PlotterWidget, Radio, Renderer, ref as ServerItemRenderer, sizingUtils as SizingUtils, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, WidgetJsonifyDeprecated, widgets$1 as Widgets, apiVersion, buildEmptyItemTreeForShape, buildMapper, components, constants, displaySigFigs, getAnswersFromWidgets, globalConstants, globalStyles, iconPaths as icons, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, itemDataVersion, itemToTree, ItemVersion as itemVersion, libVersion, mediaQueries, parsePerseusJSON, plotterPlotTypes, preprocessTex, registerAllWidgetsForTesting, shapes, traverse, violatingWidgets, allWidgets as widgets };
44510
+ export { ApiOptions, ArrowPicker, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, ColorPicker, DashPicker, dependencies as Dependencies, EditorJsonify, ElementContainer, Errors, Expression, util as GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, ItemExtras, JiptParagraphs, KhanColors, KhanMath, context as LoadingContext, Log, Matrix$1 as MatrixWidget, MultiItems, NumericInput$1 as NumericInput, PerseusError, PerseusExpressionAnswerFormConsidered, PerseusMarkdown, Plotter$1 as PlotterWidget, Radio, Renderer, ref as ServerItemRenderer, sizingUtils as SizingUtils, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, WidgetJsonifyDeprecated, widgets$1 as Widgets, apiVersion, buildEmptyItemTreeForShape, buildMapper, components, constants, displaySigFigs, getAnswersFromWidgets, globalConstants, globalStyles, iconPaths as icons, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, itemDataVersion, itemToTree, ItemVersion as itemVersion, libVersion, mediaQueries, parsePerseusItem, plotterPlotTypes, preprocessTex, registerAllWidgetsForTesting, shapes, traverse, violatingWidgets, allWidgets as widgets };
44582
44511
  //# sourceMappingURL=index.js.map