@khanacademy/perseus 13.1.0 → 14.0.0

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 (43) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/dist/components/svg-image.d.ts +10 -9
  3. package/dist/es/index.css +0 -7
  4. package/dist/es/index.js +37 -31
  5. package/dist/es/index.js.map +1 -1
  6. package/dist/index.css +0 -7
  7. package/dist/index.js +61 -49
  8. package/dist/index.js.map +1 -1
  9. package/dist/renderer.d.ts +2 -2
  10. package/dist/widgets/matcher.d.ts +1 -1
  11. package/package.json +35 -35
  12. package/src/__stories__/hints-renderer.stories.tsx +41 -44
  13. package/src/__tests__/__snapshots__/renderer.test.tsx.snap +9 -33
  14. package/src/__tests__/article-renderer.test.tsx +2 -3
  15. package/src/__tests__/renderer-api.test.tsx +35 -52
  16. package/src/components/graph.tsx +14 -8
  17. package/src/components/svg-image.tsx +13 -11
  18. package/src/mixins/provide-keypad.tsx +0 -1
  19. package/src/perseus-types.ts +8 -3
  20. package/src/renderer.tsx +2 -2
  21. package/src/styles/khan-exercise.css +0 -2
  22. package/src/styles/perseus-renderer.less +0 -7
  23. package/src/widgets/__stories__/random-widgets.stories.tsx +59 -0
  24. package/src/widgets/__stories__/test-keypad-context-wrapper.tsx +0 -1
  25. package/src/widgets/__testdata__/categorizer.testdata.ts +40 -0
  26. package/src/widgets/__testdata__/dropdown.testdata.ts +38 -0
  27. package/src/widgets/__testdata__/explanation.testdata.ts +30 -0
  28. package/src/widgets/__testdata__/expression.testdata.ts +82 -0
  29. package/src/widgets/__testdata__/interactive-graph-random.testdata.ts +269 -0
  30. package/src/widgets/__testdata__/radio.testdata.ts +63 -0
  31. package/src/widgets/__testdata__/randomizers.ts +51 -0
  32. package/src/widgets/__tests__/__snapshots__/dropdown.test.ts.snap +6 -22
  33. package/src/widgets/__tests__/__snapshots__/graded-group-set-jipt.test.ts.snap +3 -3
  34. package/src/widgets/__tests__/__snapshots__/graded-group-set.test.ts.snap +1 -1
  35. package/src/widgets/__tests__/__snapshots__/graded-group.test.ts.snap +1 -1
  36. package/src/widgets/__tests__/__snapshots__/radio.test.ts.snap +4 -4
  37. package/src/widgets/__tests__/expression-mobile.test.tsx +0 -1
  38. package/src/widgets/__tests__/matcher.test.tsx +1 -1
  39. package/src/widgets/__tests__/randomizers.test.ts +53 -0
  40. package/src/widgets/image.tsx +27 -20
  41. package/src/widgets/matcher.tsx +1 -1
  42. package/src/widgets/radio/choice.tsx +1 -0
  43. package/tsconfig-build.tsbuildinfo +1 -1
@@ -534,7 +534,7 @@ export type PerseusInteractiveGraphWidgetOptions = {
534
534
  showTooltips?: boolean;
535
535
  // The unit to show on the ruler. e.g. "mm", "cm", "m", "km", "in", "ft", "yd", "mi"
536
536
  rulerLabel: string;
537
- // How many ticks to show on the ruler. e.g. 1, 2, 4, 8, 10, 16
537
+ // How many ticks to show on the ruler. e.g. 1, 2, 4, 8, 10, 16. Must be an integer.
538
538
  rulerTicks: number;
539
539
  // The X and Y coordinate ranges for the view of the graph. default: [[-10, 10], [-10, 10]]
540
540
  // NOTE(kevinb): perseus_data.go defines this as Array<Array<number>>
@@ -577,6 +577,7 @@ export type PerseusGraphTypeAngle = {
577
577
  snapDegrees?: number;
578
578
  // How to match the answer. If missing, defaults to exact matching.
579
579
  match?: "congruent";
580
+ // must have 3 coords - ie [Coord, Coord, Coord]
580
581
  coords?: ReadonlyArray<Coord>;
581
582
  };
582
583
 
@@ -588,11 +589,13 @@ export type PerseusGraphTypeCircle = {
588
589
 
589
590
  export type PerseusGraphTypeLinear = {
590
591
  type: "linear";
592
+ // expects 2 coords
591
593
  coords?: ReadonlyArray<Coord>;
592
594
  } & PerseusGraphTypeCommon;
593
595
 
594
596
  export type PerseusGraphTypeLinearSystem = {
595
597
  type: "linear-system";
598
+ // expects 2 sets of 2 coords
596
599
  coords?: ReadonlyArray<ReadonlyArray<Coord>>;
597
600
  } & PerseusGraphTypeCommon;
598
601
 
@@ -620,6 +623,7 @@ export type PerseusGraphTypePolygon = {
620
623
 
621
624
  export type PerseusGraphTypeQuadratic = {
622
625
  type: "quadratic";
626
+ // expects a list of 3 coords
623
627
  coords?: ReadonlyArray<Coord>;
624
628
  } & PerseusGraphTypeCommon;
625
629
 
@@ -627,18 +631,19 @@ export type PerseusGraphTypeSegment = {
627
631
  type: "segment";
628
632
  // The number of segments if a "segment" type. default: 1. Max: 6
629
633
  numSegments?: number;
630
- // A list of segments (each segment is a list of coordinates).
631
- // Length should match the `numSegments` value.
634
+ // Expects a list of Coord tuples. Length should match the `numSegments` value.
632
635
  coords?: ReadonlyArray<ReadonlyArray<Coord>>;
633
636
  } & PerseusGraphTypeCommon;
634
637
 
635
638
  export type PerseusGraphTypeSinusoid = {
636
639
  type: "sinusoid";
640
+ // Expects a list of 2 Coords
637
641
  coords?: ReadonlyArray<Coord>;
638
642
  } & PerseusGraphTypeCommon;
639
643
 
640
644
  export type PerseusGraphTypeRay = {
641
645
  type: "ray";
646
+ // Expects a list of 2 Coords
642
647
  coords?: ReadonlyArray<Coord>;
643
648
  } & PerseusGraphTypeCommon;
644
649
 
package/src/renderer.tsx CHANGED
@@ -132,7 +132,7 @@ export type Widget = {
132
132
  path: FocusPath,
133
133
  newValue: string,
134
134
  // TODO(jeremy): I think this is actually a callback
135
- focus: () => unknown,
135
+ focus?: () => unknown,
136
136
  ) => void;
137
137
  getUserInput?: () => WidgetUserInput | null | undefined;
138
138
  simpleValidate?: (
@@ -1701,7 +1701,7 @@ class Renderer extends React.Component<Props, State> {
1701
1701
  setInputValue: (
1702
1702
  path: FocusPath,
1703
1703
  newValue: string,
1704
- focus: () => unknown,
1704
+ focus?: () => unknown,
1705
1705
  ) => void = (path, newValue, focus) => {
1706
1706
  // @ts-expect-error - TS2345 - Argument of type 'FocusPath' is not assignable to parameter of type 'List<any>'.
1707
1707
  const widgetId = _.first(path);
@@ -130,8 +130,6 @@ div.definition {
130
130
 
131
131
  #problemarea {
132
132
  font-size: 14px;
133
- width: 70%;
134
- min-height: 378px;
135
133
  position: relative;
136
134
  float: left;
137
135
  padding-bottom: 38px;
@@ -96,13 +96,6 @@
96
96
  }
97
97
 
98
98
  .framework-perseus {
99
- #problemarea {
100
- input,
101
- button {
102
- .perseus-interactive;
103
- }
104
- }
105
-
106
99
  div.paragraph {
107
100
  font-family: "Lato", sans-serif;
108
101
  font-weight: 400;
@@ -0,0 +1,59 @@
1
+ import Button from "@khanacademy/wonder-blocks-button";
2
+ import * as React from "react";
3
+
4
+ import {RendererWithDebugUI} from "../../../../../testing/renderer-with-debug-ui";
5
+ import {randomCategorizerGenerator} from "../__testdata__/categorizer.testdata";
6
+ import {randomDropdownGenerator} from "../__testdata__/dropdown.testdata";
7
+ import {randomExplanationGenerator} from "../__testdata__/explanation.testdata";
8
+ import {randomExpressionGenerator} from "../__testdata__/expression.testdata";
9
+ import {randomInteractiveGraphGenerator} from "../__testdata__/interactive-graph-random.testdata";
10
+ import {randomRadioGenerator} from "../__testdata__/radio.testdata";
11
+
12
+ import type {PerseusRenderer} from "@khanacademy/perseus";
13
+
14
+ export default {
15
+ title: "Perseus/Randomized Widgets",
16
+ };
17
+
18
+ const RandomizedWidgetContainer = (
19
+ randomizer: () => PerseusRenderer,
20
+ ): React.ReactElement => {
21
+ const [question, setQuestion] = React.useState(randomizer());
22
+ const randomize = () => {
23
+ setQuestion(randomizer());
24
+ };
25
+
26
+ return (
27
+ <div>
28
+ <Button size="small" style={{marginBottom: 24}} onClick={randomize}>
29
+ Randomize
30
+ </Button>
31
+
32
+ <RendererWithDebugUI question={question} />
33
+ </div>
34
+ );
35
+ };
36
+
37
+ export const RandomCategorizer = (): React.ReactElement => {
38
+ return RandomizedWidgetContainer(randomCategorizerGenerator);
39
+ };
40
+
41
+ export const RandomDropdown = (): React.ReactElement => {
42
+ return RandomizedWidgetContainer(randomDropdownGenerator);
43
+ };
44
+
45
+ export const RandomExplanation = (): React.ReactElement => {
46
+ return RandomizedWidgetContainer(randomExplanationGenerator);
47
+ };
48
+
49
+ export const RandomExpression = (): React.ReactElement => {
50
+ return RandomizedWidgetContainer(randomExpressionGenerator);
51
+ };
52
+
53
+ export const RandomInteractiveGraph = (): React.ReactElement => {
54
+ return RandomizedWidgetContainer(randomInteractiveGraphGenerator);
55
+ };
56
+
57
+ export const RandomRadio = (): React.ReactElement => {
58
+ return RandomizedWidgetContainer(randomRadioGenerator);
59
+ };
@@ -17,7 +17,6 @@ const Footer = (): React.ReactElement => {
17
17
  onElementMounted={setKeypadElement}
18
18
  onDismiss={() => renderer && renderer.blur()}
19
19
  style={styles.keypad}
20
- useV2Keypad={true}
21
20
  onAnalyticsEvent={async (e) => {
22
21
  action("onAnalyticsEvent")(e);
23
22
  }}
@@ -1,3 +1,10 @@
1
+ import {
2
+ arrayOfLength,
3
+ randomBoolean,
4
+ randomInteger,
5
+ randomSentence,
6
+ } from "./randomizers";
7
+
1
8
  import type {PerseusRenderer} from "../../perseus-types";
2
9
 
3
10
  export const question1: PerseusRenderer = {
@@ -47,3 +54,36 @@ export const question1: PerseusRenderer = {
47
54
  },
48
55
  },
49
56
  };
57
+
58
+ export const randomCategorizerGenerator = (): PerseusRenderer => {
59
+ const questionText = randomSentence(35);
60
+ const numItems = randomInteger(2, 7);
61
+ const numCategories = randomInteger(2, 6);
62
+
63
+ return {
64
+ content: `${questionText}\n\n\n[[\u2603 categorizer 1]]`,
65
+ images: {},
66
+ widgets: {
67
+ "categorizer 1": {
68
+ version: {major: 0, minor: 0},
69
+ type: "categorizer",
70
+ graded: randomBoolean(),
71
+ alignment: "default",
72
+ options: {
73
+ items: arrayOfLength(numItems).map(() =>
74
+ randomSentence(12),
75
+ ),
76
+ values: arrayOfLength(numItems).map(() =>
77
+ randomInteger(0, numCategories - 1),
78
+ ),
79
+ randomizeItems: randomBoolean(),
80
+ categories: arrayOfLength(numCategories).map(() =>
81
+ randomSentence(7),
82
+ ),
83
+ highlightLint: randomBoolean(),
84
+ static: randomBoolean(0.05),
85
+ },
86
+ },
87
+ },
88
+ };
89
+ };
@@ -1,3 +1,10 @@
1
+ import {
2
+ arrayOfLength,
3
+ randomBoolean,
4
+ randomInteger,
5
+ randomSentence,
6
+ } from "./randomizers";
7
+
1
8
  import type {PerseusRenderer} from "../../perseus-types";
2
9
 
3
10
  export const question1: PerseusRenderer = {
@@ -31,3 +38,34 @@ export const question1: PerseusRenderer = {
31
38
  },
32
39
  },
33
40
  };
41
+
42
+ export const randomDropdownGenerator = (): PerseusRenderer => {
43
+ const numChoices = randomInteger(2, 16);
44
+ const correctIndex = randomInteger(0, numChoices - 1);
45
+ return {
46
+ content: `${randomSentence(20)} [[☃ dropdown 1]] ${randomSentence(10)}`,
47
+ images: {},
48
+ widgets: {
49
+ "dropdown 1": {
50
+ type: "dropdown",
51
+ alignment: "default",
52
+ static: randomBoolean(0.05),
53
+ graded: randomBoolean(),
54
+ options: {
55
+ static: randomBoolean(0.05),
56
+ placeholder: randomSentence(10),
57
+ choices: arrayOfLength(numChoices).map((_, i) => {
58
+ return {
59
+ content: randomSentence(10),
60
+ correct: i === correctIndex,
61
+ };
62
+ }),
63
+ },
64
+ version: {
65
+ major: 0,
66
+ minor: 0,
67
+ },
68
+ },
69
+ },
70
+ };
71
+ };
@@ -1,3 +1,5 @@
1
+ import {randomBoolean, randomSentence} from "./randomizers";
2
+
1
3
  import type {PerseusRenderer} from "../../perseus-types";
2
4
 
3
5
  export const question1: PerseusRenderer = {
@@ -53,3 +55,31 @@ export const question2: PerseusRenderer = {
53
55
  },
54
56
  },
55
57
  };
58
+
59
+ export const randomExplanationGenerator = (): PerseusRenderer => {
60
+ return {
61
+ content: `${randomSentence(
62
+ 50,
63
+ )}\n[[\u2603 explanation 1]]\n${randomSentence(50)}`,
64
+ images: {},
65
+ widgets: {
66
+ "explanation 1": {
67
+ graded: randomBoolean(),
68
+ version: {
69
+ major: 0,
70
+ minor: 0,
71
+ },
72
+ static: randomBoolean(0.05),
73
+ type: "explanation",
74
+ options: {
75
+ hidePrompt: `${randomSentence(20)}`,
76
+ widgets: {},
77
+ explanation: `${randomSentence(50)}`,
78
+ static: randomBoolean(0.05),
79
+ showPrompt: `${randomSentence(7)}`,
80
+ },
81
+ alignment: "default",
82
+ },
83
+ },
84
+ };
85
+ };
@@ -1,7 +1,19 @@
1
+ import {
2
+ arrayOfLength,
3
+ randomBoolean,
4
+ randomElement,
5
+ randomInteger,
6
+ randomLetter,
7
+ randomSentence,
8
+ randomWord,
9
+ } from "./randomizers";
10
+
1
11
  import type {
2
12
  PerseusExpressionWidgetOptions,
3
13
  Version,
4
14
  PerseusItem,
15
+ PerseusRenderer,
16
+ ExpressionWidget,
5
17
  } from "../../perseus-types";
6
18
 
7
19
  const createItemJson = (
@@ -96,3 +108,73 @@ export const expressionItem3: PerseusItem = createItemJson(
96
108
  minor: 0,
97
109
  },
98
110
  );
111
+
112
+ export const randomExpressionGenerator = (): PerseusRenderer => {
113
+ const randomButtonSet = [
114
+ "basic",
115
+ "basic+div",
116
+ "trig",
117
+ "prealgebra",
118
+ "logarithms",
119
+ "basic relations",
120
+ "advanced relations",
121
+ ]
122
+ .sort(() => {
123
+ return randomBoolean() ? 1 : -1;
124
+ })
125
+ .slice(0, randomInteger(1, 6));
126
+
127
+ const randomFunctionSet = arrayOfLength(randomInteger(0, 6)).map(
128
+ randomLetter,
129
+ );
130
+
131
+ return {
132
+ content: `${randomSentence(20)} [[☃ expression 1]]`,
133
+ images: {},
134
+ widgets: {
135
+ "expression 1": {
136
+ type: "expression",
137
+ graded: randomBoolean(),
138
+ version: {
139
+ major: 1,
140
+ minor: 0,
141
+ },
142
+ static: randomBoolean(0.05),
143
+ options: {
144
+ answerForms: [
145
+ {
146
+ considered: randomElement([
147
+ "correct",
148
+ "wrong",
149
+ "ungraded",
150
+ ]),
151
+ form: randomBoolean(),
152
+ simplify: randomBoolean(),
153
+ value: randomWord(),
154
+ },
155
+ {
156
+ considered: randomElement([
157
+ "correct",
158
+ "wrong",
159
+ "ungraded",
160
+ ]),
161
+ form: randomBoolean(),
162
+ simplify: randomBoolean(),
163
+ value: randomWord(),
164
+ },
165
+ ],
166
+ times: randomBoolean(),
167
+ buttonSets: randomButtonSet,
168
+ functions: randomFunctionSet,
169
+ buttonsVisible: randomElement([
170
+ "always",
171
+ "never",
172
+ "focused",
173
+ undefined,
174
+ ]),
175
+ alignment: "default",
176
+ },
177
+ } as ExpressionWidget,
178
+ },
179
+ };
180
+ };
@@ -0,0 +1,269 @@
1
+ import {
2
+ arrayOfLength,
3
+ randomBoolean,
4
+ randomElement,
5
+ randomInteger,
6
+ randomLetter,
7
+ randomWord,
8
+ } from "./randomizers";
9
+
10
+ import type {Coord} from "../../interactive2/types";
11
+ import type {
12
+ PerseusGraphType,
13
+ PerseusGraphTypeAngle,
14
+ PerseusGraphTypeCircle,
15
+ PerseusGraphTypeLinear,
16
+ PerseusGraphTypeLinearSystem,
17
+ PerseusGraphTypePoint,
18
+ PerseusGraphTypePolygon,
19
+ PerseusGraphTypeQuadratic,
20
+ PerseusGraphTypeRay,
21
+ PerseusGraphTypeSegment,
22
+ PerseusGraphTypeSinusoid,
23
+ PerseusImageBackground,
24
+ PerseusRenderer,
25
+ } from "../../perseus-types";
26
+
27
+ const randomCoord = (): Coord => {
28
+ return [randomInteger(1, 100), randomInteger(1, 100)];
29
+ };
30
+
31
+ const randomGraphTypeAngle = (): PerseusGraphTypeAngle => {
32
+ return {
33
+ type: "angle",
34
+ showAngles: randomBoolean(),
35
+ allowReflexAngles: randomBoolean(),
36
+ angleOffsetDeg: randomInteger(1, 90),
37
+ snapDegrees: randomInteger(1, 90),
38
+ match: "congruent",
39
+ // must be 3 coords for an angle graph
40
+ coords: arrayOfLength(3).map(randomCoord),
41
+ };
42
+ };
43
+
44
+ const randomGraphTypeCircle = (): PerseusGraphTypeCircle => {
45
+ return {
46
+ type: "circle",
47
+ center: randomCoord(),
48
+ radius: randomInteger(1, 20),
49
+ };
50
+ };
51
+
52
+ const randomGraphTypeLinear = (): PerseusGraphTypeLinear => {
53
+ return {
54
+ type: "linear",
55
+ coords: arrayOfLength(2).map(randomCoord),
56
+ };
57
+ };
58
+
59
+ const randomGraphTypeLinearSystem = (): PerseusGraphTypeLinearSystem => {
60
+ return {
61
+ type: "linear-system",
62
+ coords: arrayOfLength(2).map(() => arrayOfLength(2).map(randomCoord)),
63
+ };
64
+ };
65
+
66
+ const randomGraphTypePoint = (): PerseusGraphTypePoint => {
67
+ let minCoords = 1;
68
+ const numPoints = randomElement(["unlimited", randomInteger(1, 10)]);
69
+ if (typeof numPoints === "number") {
70
+ minCoords = numPoints;
71
+ }
72
+ return {
73
+ type: "point",
74
+ numPoints: typeof numPoints === "number" ? numPoints : "unlimited",
75
+ coords: arrayOfLength(randomInteger(minCoords, 10)).map(randomCoord),
76
+ };
77
+ };
78
+
79
+ const randomGraphTypePolygon = (): PerseusGraphTypePolygon => {
80
+ // Note Jhead - I saw this create at least one broken graph but didn't figure out why
81
+ return {
82
+ type: "polygon",
83
+ numSides: randomElement([
84
+ "unlimited",
85
+ undefined,
86
+ randomInteger(3, 30),
87
+ randomInteger(3, 30),
88
+ ]),
89
+ showAngles: randomBoolean(),
90
+ showSides: randomBoolean(),
91
+ snapTo: randomElement(["grid", "angles", "sides"]),
92
+ match: randomElement(["similar", "congruent", "approx", undefined]),
93
+ coords: arrayOfLength(randomInteger(3, 20)).map(randomCoord),
94
+ };
95
+ };
96
+
97
+ const randomGraphTypeQuadratic = (): PerseusGraphTypeQuadratic => {
98
+ return {
99
+ type: "quadratic",
100
+ coords: arrayOfLength(3).map(randomCoord),
101
+ };
102
+ };
103
+
104
+ const randomGraphTypeRay = (): PerseusGraphTypeRay => {
105
+ return {
106
+ type: "ray",
107
+ coords: arrayOfLength(2).map(randomCoord),
108
+ };
109
+ };
110
+
111
+ const randomGraphTypeSegment = (): PerseusGraphTypeSegment => {
112
+ const numSegments = randomInteger(1, 6);
113
+ return {
114
+ type: "segment",
115
+ numSegments,
116
+ coords: arrayOfLength(numSegments).map(() => [
117
+ randomCoord(),
118
+ randomCoord(),
119
+ ]),
120
+ };
121
+ };
122
+
123
+ const randomGraphTypeSinusoid = (): PerseusGraphTypeSinusoid => {
124
+ return {
125
+ type: "sinusoid",
126
+ coords: arrayOfLength(2).map(randomCoord),
127
+ };
128
+ };
129
+
130
+ export const randomBackgroundImage = (): PerseusImageBackground => {
131
+ const height = randomInteger(200, 500);
132
+ const width = randomInteger(200, 500);
133
+ const graphieUrl =
134
+ "web+graphie://ka-perseus-graphie.s3.amazonaws.com/a1b7a05c177742523250b64a3995c9b37aac3399";
135
+ const placeKittenUrl = `https://placekitten.com/g/${width}/${height}`;
136
+
137
+ return {
138
+ scale: randomInteger(8, 12) / 10,
139
+ bottom: randomInteger(0, 10) - 5,
140
+ url: randomElement([graphieUrl, placeKittenUrl, null]),
141
+ height,
142
+ width,
143
+ left: randomInteger(0, 10) - 5,
144
+ };
145
+ };
146
+
147
+ export const randomInteractiveGraphGenerator = (
148
+ type?: string,
149
+ ): PerseusRenderer => {
150
+ const randomRange = (): [number, number] => {
151
+ const val1 = randomInteger(0, 400) - 200;
152
+ const val2 = randomInteger(0, 400) - 200;
153
+ if (val1 < val2) {
154
+ return [val1, val2];
155
+ }
156
+ return [val2, val1];
157
+ };
158
+
159
+ const graphType =
160
+ type ||
161
+ randomElement([
162
+ "angle",
163
+ "circle",
164
+ "linear",
165
+ "linear-system",
166
+ "point",
167
+ "polygon",
168
+ "quadratic",
169
+ "ray",
170
+ "segment",
171
+ "sinusoid",
172
+ ]);
173
+
174
+ let graph: PerseusGraphType;
175
+ let correct: PerseusGraphType;
176
+
177
+ switch (graphType) {
178
+ case "angle": {
179
+ graph = randomGraphTypeAngle();
180
+ correct = randomGraphTypeAngle();
181
+ break;
182
+ }
183
+ case "circle": {
184
+ graph = randomGraphTypeCircle();
185
+ correct = randomGraphTypeCircle();
186
+ break;
187
+ }
188
+ case "linear": {
189
+ graph = randomGraphTypeLinear();
190
+ correct = randomGraphTypeLinear();
191
+ break;
192
+ }
193
+ case "linear-system": {
194
+ graph = randomGraphTypeLinearSystem();
195
+ correct = randomGraphTypeLinearSystem();
196
+ break;
197
+ }
198
+ case "point": {
199
+ graph = randomGraphTypePoint();
200
+ correct = randomGraphTypePoint();
201
+ break;
202
+ }
203
+ case "polygon": {
204
+ graph = randomGraphTypePolygon();
205
+ correct = randomGraphTypePolygon();
206
+ break;
207
+ }
208
+ case "quadratic": {
209
+ graph = randomGraphTypeQuadratic();
210
+ correct = randomGraphTypeQuadratic();
211
+ break;
212
+ }
213
+ case "ray": {
214
+ graph = randomGraphTypeRay();
215
+ correct = randomGraphTypeRay();
216
+ break;
217
+ }
218
+ case "segment": {
219
+ graph = randomGraphTypeSegment();
220
+ correct = randomGraphTypeSegment();
221
+ break;
222
+ }
223
+ case "sinusoid": {
224
+ graph = randomGraphTypeSinusoid();
225
+ correct = randomGraphTypeSinusoid();
226
+ break;
227
+ }
228
+ default: {
229
+ graph = randomGraphTypeAngle();
230
+ correct = randomGraphTypeAngle();
231
+ }
232
+ }
233
+
234
+ return {
235
+ content: `[[\u2603 interactive-graph 1]]`,
236
+ images: {},
237
+ widgets: {
238
+ "interactive-graph 1": {
239
+ graded: randomBoolean(),
240
+ version: {
241
+ major: 0,
242
+ minor: 0,
243
+ },
244
+ static: false, //always false for this widget
245
+ type: "interactive-graph",
246
+ options: {
247
+ rulerTicks: randomInteger(1, 100),
248
+ showProtractor: randomBoolean(0.2),
249
+ graph,
250
+ snapStep: [
251
+ randomInteger(1, 10) / 10,
252
+ randomInteger(1, 10) / 10,
253
+ ],
254
+ labels: [randomLetter() || "x", randomLetter() || "y"],
255
+ step: [randomInteger(1, 10), randomInteger(1, 10)],
256
+ gridStep: [randomInteger(1, 10), randomInteger(1, 10)],
257
+ backgroundImage: randomBackgroundImage(),
258
+ range: [randomRange(), randomRange()],
259
+ showRuler: randomBoolean(0.2),
260
+ markings:
261
+ randomElement(["none", "graph", "grid"]) || "none",
262
+ rulerLabel: randomElement(["", randomWord()]) || "",
263
+ correct,
264
+ },
265
+ alignment: "default",
266
+ },
267
+ },
268
+ };
269
+ };