@khanacademy/perseus 7.2.0 → 7.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/es/index.css +61 -61
- package/dist/es/index.js +50 -119
- package/dist/es/index.js.map +1 -1
- package/dist/index.css +61 -61
- package/dist/index.js +39 -77
- package/dist/index.js.map +1 -1
- package/dist/widgets/plotter.d.ts +47 -13
- package/dist/widgets/plotter.js.flow +47 -13
- package/package.json +2 -2
- package/src/__tests__/item-renderer.test.tsx +1 -1
- package/src/components/__stories__/image-loader.stories.tsx +1 -0
- package/src/components/graph.tsx +1 -0
- package/src/components/icon.tsx +1 -0
- package/src/components/image-loader.tsx +1 -0
- package/src/components/number-input.tsx +1 -0
- package/src/components/sortable.tsx +1 -0
- package/src/components/svg-image.tsx +1 -0
- package/src/components/tex-buttons.tsx +1 -0
- package/src/components/text-input.tsx +1 -0
- package/src/components/text-list-editor.tsx +1 -0
- package/src/components/zoomable.tsx +1 -0
- package/src/hints-renderer.tsx +1 -0
- package/src/index.ts +1 -0
- package/src/item-renderer.tsx +1 -0
- package/src/mixins/provide-keypad.tsx +1 -0
- package/src/multi-items/multi-renderer.tsx +1 -0
- package/src/renderer.tsx +1 -0
- package/src/server-item-renderer.tsx +1 -0
- package/src/styles/articles.less +8 -4
- package/src/styles/perseus-renderer.less +47 -38
- package/src/styles/util.less +1 -1
- package/src/styles/variables.boostrap.less +158 -182
- package/src/styles/variables.less +20 -20
- package/src/styles/variables.shared.less +162 -168
- package/src/styles/widgets/categorizer.less +1 -2
- package/src/styles/widgets/expression.less +7 -4
- package/src/styles/widgets/grapher.less +2 -1
- package/src/styles/widgets/image.less +3 -2
- package/src/styles/widgets/interactive-graph.less +4 -2
- package/src/styles/widgets/label-image.less +2 -1
- package/src/styles/widgets/matrix.less +11 -7
- package/src/styles/widgets/orderer.less +2 -1
- package/src/styles/widgets/passage.less +2 -1
- package/src/styles/widgets/plotter.less +4 -3
- package/src/styles/widgets/radio.less +4 -2
- package/src/styles/widgets/sortable.less +1 -1
- package/src/styles/widgets/table.less +2 -1
- package/src/styles/widgets/transformer.less +2 -1
- package/src/styles/widgets/unit.less +2 -1
- package/src/widgets/__stories__/plotter.stories.tsx +27 -0
- package/src/widgets/__testdata__/plotter.testdata.ts +36 -0
- package/src/widgets/__tests__/__snapshots__/plotter.test.tsx.snap +863 -0
- package/src/widgets/__tests__/plotter.test.tsx +65 -0
- package/src/widgets/categorizer.tsx +1 -0
- package/src/widgets/graded-group-set.tsx +1 -0
- package/src/widgets/graded-group.tsx +1 -0
- package/src/widgets/interaction.tsx +1 -0
- package/src/widgets/label-image/marker.tsx +1 -0
- package/src/widgets/matcher.tsx +1 -0
- package/src/widgets/orderer.tsx +1 -0
- package/src/widgets/passage-ref-target.tsx +1 -0
- package/src/widgets/plotter.tsx +108 -98
- package/src/widgets/radio/base-radio.tsx +1 -0
- package/src/widgets/radio/choice-icon/choice-icon.tsx +1 -0
- package/src/widgets/sorter.tsx +1 -0
- package/src/widgets/transformer.tsx +1 -0
- package/tsconfig-build.tsbuildinfo +1 -1
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import "@testing-library/jest-dom";
|
|
2
|
+
import {screen, render} from "@testing-library/react";
|
|
3
|
+
import React from "react";
|
|
4
|
+
|
|
5
|
+
import {testDependencies} from "../../../../../testing/test-dependencies";
|
|
6
|
+
import {setDependencies} from "../../dependencies";
|
|
7
|
+
import {APIOptions} from "../../types";
|
|
8
|
+
import {question1} from "../__testdata__/plotter.testdata";
|
|
9
|
+
import {Plotter} from "../plotter";
|
|
10
|
+
|
|
11
|
+
import {renderQuestion} from "./renderQuestion";
|
|
12
|
+
|
|
13
|
+
describe("plotter widget", () => {
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
setDependencies(testDependencies);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("should snapshot basic question", () => {
|
|
19
|
+
// Arrange and Act
|
|
20
|
+
const {container} = renderQuestion(question1);
|
|
21
|
+
|
|
22
|
+
// Assert
|
|
23
|
+
expect(container).toMatchSnapshot("initial render");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("should show drag text when not static", () => {
|
|
27
|
+
// Arrange and Act
|
|
28
|
+
const apiOptions: APIOptions = {isMobile: true};
|
|
29
|
+
render(
|
|
30
|
+
<Plotter
|
|
31
|
+
static={false}
|
|
32
|
+
trackInteraction={() => {}}
|
|
33
|
+
onChange={() => {}}
|
|
34
|
+
apiOptions={apiOptions}
|
|
35
|
+
starting={[0]}
|
|
36
|
+
markings={""}
|
|
37
|
+
/>,
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
// Assert
|
|
41
|
+
expect(
|
|
42
|
+
screen.getByText("Drag handles to make graph"),
|
|
43
|
+
).toBeInTheDocument();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("should not show drag text when static", () => {
|
|
47
|
+
// Arrange and Act
|
|
48
|
+
const apiOptions: APIOptions = {isMobile: true};
|
|
49
|
+
render(
|
|
50
|
+
<Plotter
|
|
51
|
+
static={true}
|
|
52
|
+
trackInteraction={() => {}}
|
|
53
|
+
onChange={() => {}}
|
|
54
|
+
apiOptions={apiOptions}
|
|
55
|
+
starting={[0]}
|
|
56
|
+
markings={""}
|
|
57
|
+
/>,
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// Assert
|
|
61
|
+
expect(
|
|
62
|
+
screen.queryByText("Drag handles to make graph"),
|
|
63
|
+
).not.toBeInTheDocument();
|
|
64
|
+
});
|
|
65
|
+
});
|
package/src/widgets/matcher.tsx
CHANGED
package/src/widgets/orderer.tsx
CHANGED
package/src/widgets/plotter.tsx
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
/* eslint-disable
|
|
1
|
+
/* eslint-disable react/no-unsafe */
|
|
2
2
|
import * as i18n from "@khanacademy/wonder-blocks-i18n";
|
|
3
3
|
import $ from "jquery";
|
|
4
|
-
import PropTypes from "prop-types";
|
|
5
4
|
import * as React from "react";
|
|
6
5
|
import ReactDOM from "react-dom";
|
|
7
6
|
import _ from "underscore";
|
|
@@ -14,38 +13,60 @@ import KhanColors from "../util/colors";
|
|
|
14
13
|
import GraphUtils from "../util/graph-utils";
|
|
15
14
|
import KhanMath from "../util/math";
|
|
16
15
|
|
|
17
|
-
import type {WidgetExports} from "../types";
|
|
16
|
+
import type {APIOptions, WidgetExports} from "../types";
|
|
18
17
|
|
|
19
18
|
const {deepEq} = Util;
|
|
20
19
|
|
|
21
|
-
const BAR = "bar"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
scaleY:
|
|
35
|
-
maxY:
|
|
36
|
-
snapsPerLine:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
20
|
+
const BAR = "bar";
|
|
21
|
+
const LINE = "line";
|
|
22
|
+
const PIC = "pic";
|
|
23
|
+
const HISTOGRAM = "histogram";
|
|
24
|
+
const DOTPLOT = "dotplot";
|
|
25
|
+
|
|
26
|
+
const plotTypes = [BAR, LINE, PIC, HISTOGRAM, DOTPLOT];
|
|
27
|
+
type PlotType = typeof plotTypes[number];
|
|
28
|
+
|
|
29
|
+
type Props = {
|
|
30
|
+
type: PlotType;
|
|
31
|
+
labels: ReadonlyArray<string>;
|
|
32
|
+
categories: ReadonlyArray<string | number>;
|
|
33
|
+
scaleY: number;
|
|
34
|
+
maxY: number;
|
|
35
|
+
snapsPerLine: number;
|
|
36
|
+
picSize: number;
|
|
37
|
+
picBoxHeight: number;
|
|
38
|
+
picUrl: string;
|
|
39
|
+
plotDimensions: ReadonlyArray<number>;
|
|
40
|
+
labelInterval: number;
|
|
41
|
+
|
|
42
|
+
starting: Array<number>;
|
|
43
|
+
static: boolean;
|
|
44
|
+
markings: any;
|
|
45
|
+
onChange: any;
|
|
46
|
+
trackInteraction: () => void;
|
|
47
|
+
apiOptions: APIOptions;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
type DefaultProps = {
|
|
51
|
+
type: Props["type"];
|
|
52
|
+
labels: Props["labels"];
|
|
53
|
+
categories: Props["categories"];
|
|
54
|
+
scaleY: Props["scaleY"];
|
|
55
|
+
maxY: Props["maxY"];
|
|
56
|
+
snapsPerLine: Props["snapsPerLine"];
|
|
57
|
+
picSize: Props["picSize"];
|
|
58
|
+
picBoxHeight: Props["picBoxHeight"];
|
|
59
|
+
picUrl: Props["picUrl"];
|
|
60
|
+
plotDimensions: Props["plotDimensions"];
|
|
61
|
+
labelInterval: Props["labelInterval"];
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
type State = {
|
|
65
|
+
values: Array<number>;
|
|
66
|
+
categoryHeights: Record<string, number>;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export class Plotter extends React.Component<Props, State> {
|
|
49
70
|
// @ts-expect-error [FEI-5003] - TS2564 - Property 'shouldSetupGraphie' has no initializer and is not definitely assigned in the constructor.
|
|
50
71
|
shouldSetupGraphie: boolean;
|
|
51
72
|
_isMounted = false;
|
|
@@ -53,14 +74,7 @@ class Plotter extends React.Component<any, any> {
|
|
|
53
74
|
hairlineRange: any;
|
|
54
75
|
graphie: any;
|
|
55
76
|
|
|
56
|
-
static
|
|
57
|
-
onChange: PropTypes.func.isRequired,
|
|
58
|
-
trackInteraction: PropTypes.func.isRequired,
|
|
59
|
-
// TODO(alex): Figure out why lint chokes on this line
|
|
60
|
-
// ...widgetPropTypes,
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
static defaultProps: any = {
|
|
77
|
+
static defaultProps: DefaultProps = {
|
|
64
78
|
type: BAR,
|
|
65
79
|
labels: ["", ""],
|
|
66
80
|
categories: [""],
|
|
@@ -77,7 +91,7 @@ class Plotter extends React.Component<any, any> {
|
|
|
77
91
|
labelInterval: 1,
|
|
78
92
|
};
|
|
79
93
|
|
|
80
|
-
state:
|
|
94
|
+
state: State = {
|
|
81
95
|
values: this.props.starting || [1],
|
|
82
96
|
|
|
83
97
|
// The measured rendered height of category strings. Used to calculate
|
|
@@ -86,57 +100,13 @@ class Plotter extends React.Component<any, any> {
|
|
|
86
100
|
categoryHeights: {},
|
|
87
101
|
};
|
|
88
102
|
|
|
89
|
-
DOT_PLOT_POINT_SIZE: () => number = () => {
|
|
90
|
-
return this.props.apiOptions.isMobile ? 6 : 4;
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
DOT_PLOT_POINT_PADDING: () => number = () => {
|
|
94
|
-
return 8;
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
DOT_TICK_POINT_SIZE: () => number = () => {
|
|
98
|
-
return 2;
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
render(): React.ReactNode {
|
|
102
|
-
// TODO(kevinb) actually compute the size of the graphie correctly and
|
|
103
|
-
// make it that size so we don't have to add extra padding. The value
|
|
104
|
-
// was determined by eye-balling the layout. :(
|
|
105
|
-
const paddingForBottomLabel = 75;
|
|
106
|
-
const style = {
|
|
107
|
-
marginBottom: this.props.labels[0] ? paddingForBottomLabel : 0,
|
|
108
|
-
} as const;
|
|
109
|
-
|
|
110
|
-
return (
|
|
111
|
-
<div
|
|
112
|
-
className={
|
|
113
|
-
"perseus-widget-plotter graphie " +
|
|
114
|
-
ApiClassNames.INTERACTIVE
|
|
115
|
-
}
|
|
116
|
-
// eslint-disable-next-line react/no-string-refs
|
|
117
|
-
ref="graphieDiv"
|
|
118
|
-
style={style}
|
|
119
|
-
/>
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
componentDidUpdate(prevProps: any, prevState: any) {
|
|
124
|
-
this.shouldSetupGraphie =
|
|
125
|
-
this.shouldSetupGraphie ||
|
|
126
|
-
!_.isEqual(this.state.categoryHeights, prevState.categoryHeights);
|
|
127
|
-
|
|
128
|
-
if (this.shouldSetupGraphie) {
|
|
129
|
-
this.setupGraphie(prevState);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
103
|
componentDidMount() {
|
|
134
104
|
this._isMounted = true;
|
|
135
105
|
|
|
136
106
|
this.setupGraphie(this.state);
|
|
137
107
|
}
|
|
138
108
|
|
|
139
|
-
UNSAFE_componentWillReceiveProps(nextProps:
|
|
109
|
+
UNSAFE_componentWillReceiveProps(nextProps: Props) {
|
|
140
110
|
const props = [
|
|
141
111
|
"type",
|
|
142
112
|
"labels",
|
|
@@ -151,10 +121,7 @@ class Plotter extends React.Component<any, any> {
|
|
|
151
121
|
|
|
152
122
|
this.shouldSetupGraphie = _.any(
|
|
153
123
|
props,
|
|
154
|
-
|
|
155
|
-
// @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
156
|
-
return !_.isEqual(this.props[prop], nextProps[prop]);
|
|
157
|
-
},
|
|
124
|
+
(prop) => !_.isEqual(this.props[prop], nextProps[prop]),
|
|
158
125
|
this,
|
|
159
126
|
);
|
|
160
127
|
|
|
@@ -167,10 +134,32 @@ class Plotter extends React.Component<any, any> {
|
|
|
167
134
|
}
|
|
168
135
|
}
|
|
169
136
|
|
|
137
|
+
componentDidUpdate(prevProps: Props, prevState: State) {
|
|
138
|
+
this.shouldSetupGraphie =
|
|
139
|
+
this.shouldSetupGraphie ||
|
|
140
|
+
!_.isEqual(this.state.categoryHeights, prevState.categoryHeights);
|
|
141
|
+
|
|
142
|
+
if (this.shouldSetupGraphie) {
|
|
143
|
+
this.setupGraphie(prevState);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
170
147
|
componentWillUnmount() {
|
|
171
148
|
this._isMounted = false;
|
|
172
149
|
}
|
|
173
150
|
|
|
151
|
+
DOT_PLOT_POINT_SIZE: () => number = () => {
|
|
152
|
+
return this.props.apiOptions.isMobile ? 6 : 4;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
DOT_PLOT_POINT_PADDING: () => number = () => {
|
|
156
|
+
return 8;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
DOT_TICK_POINT_SIZE: () => number = () => {
|
|
160
|
+
return 2;
|
|
161
|
+
};
|
|
162
|
+
|
|
174
163
|
setupGraphie: (arg1: any) => void = (prevState) => {
|
|
175
164
|
const self = this;
|
|
176
165
|
self.shouldSetupGraphie = false;
|
|
@@ -186,11 +175,11 @@ class Plotter extends React.Component<any, any> {
|
|
|
186
175
|
self.graphie.pics = [];
|
|
187
176
|
self.graphie.dotTicks = [];
|
|
188
177
|
|
|
189
|
-
const isBar = self.props.type === BAR
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
178
|
+
const isBar = self.props.type === BAR;
|
|
179
|
+
const isLine = self.props.type === LINE;
|
|
180
|
+
const isPic = self.props.type === PIC;
|
|
181
|
+
const isHistogram = self.props.type === HISTOGRAM;
|
|
182
|
+
const isDotplot = self.props.type === DOTPLOT;
|
|
194
183
|
|
|
195
184
|
const isTiledPlot = isPic || isDotplot;
|
|
196
185
|
|
|
@@ -264,7 +253,6 @@ class Plotter extends React.Component<any, any> {
|
|
|
264
253
|
if (isMobile && (isBar || isTiledPlot)) {
|
|
265
254
|
const maxCategoryHeight = Math.max(
|
|
266
255
|
0,
|
|
267
|
-
// @ts-expect-error [FEI-5003] - TS2345 - Argument of type 'unknown' is not assignable to parameter of type 'number'.
|
|
268
256
|
...Object.values(self.state.categoryHeights),
|
|
269
257
|
);
|
|
270
258
|
|
|
@@ -340,7 +328,7 @@ class Plotter extends React.Component<any, any> {
|
|
|
340
328
|
}
|
|
341
329
|
}
|
|
342
330
|
|
|
343
|
-
if ((isBar || isLine) && isMobile) {
|
|
331
|
+
if ((isBar || isLine) && isMobile && !this.props.static) {
|
|
344
332
|
self.graphie.dragPrompt = graphie
|
|
345
333
|
.label(
|
|
346
334
|
[c.dimX / 2, c.dimY / 2],
|
|
@@ -721,8 +709,8 @@ class Plotter extends React.Component<any, any> {
|
|
|
721
709
|
|
|
722
710
|
if (isHistogram) {
|
|
723
711
|
// Scale dividers between buckets
|
|
724
|
-
const leftDivider = config.graph.dividers[i - 1]
|
|
725
|
-
|
|
712
|
+
const leftDivider = config.graph.dividers[i - 1];
|
|
713
|
+
const rightDivider = config.graph.dividers[i];
|
|
726
714
|
|
|
727
715
|
if (leftDivider) {
|
|
728
716
|
const divHeight = Math.min(
|
|
@@ -1173,6 +1161,28 @@ class Plotter extends React.Component<any, any> {
|
|
|
1173
1161
|
// @ts-expect-error [FEI-5003] - TS2339 - Property 'validate' does not exist on type 'typeof Plotter'.
|
|
1174
1162
|
return Plotter.validate(this.getUserInput(), rubric);
|
|
1175
1163
|
};
|
|
1164
|
+
|
|
1165
|
+
render(): React.ReactNode {
|
|
1166
|
+
// TODO(kevinb) actually compute the size of the graphie correctly and
|
|
1167
|
+
// make it that size so we don't have to add extra padding. The value
|
|
1168
|
+
// was determined by eye-balling the layout. :(
|
|
1169
|
+
const paddingForBottomLabel = 75;
|
|
1170
|
+
const style = {
|
|
1171
|
+
marginBottom: this.props.labels[0] ? paddingForBottomLabel : 0,
|
|
1172
|
+
} as const;
|
|
1173
|
+
|
|
1174
|
+
return (
|
|
1175
|
+
<div
|
|
1176
|
+
className={
|
|
1177
|
+
"perseus-widget-plotter graphie " +
|
|
1178
|
+
ApiClassNames.INTERACTIVE
|
|
1179
|
+
}
|
|
1180
|
+
// eslint-disable-next-line react/no-string-refs
|
|
1181
|
+
ref="graphieDiv"
|
|
1182
|
+
style={style}
|
|
1183
|
+
/>
|
|
1184
|
+
);
|
|
1185
|
+
}
|
|
1176
1186
|
}
|
|
1177
1187
|
|
|
1178
1188
|
_.extend(Plotter, {
|
package/src/widgets/sorter.tsx
CHANGED