@khanacademy/perseus 17.8.0 → 18.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.
- package/CHANGELOG.md +31 -0
- package/dist/es/index.css +0 -4
- package/dist/es/index.js +1416 -1020
- package/dist/es/index.js.map +1 -1
- package/dist/index.css +0 -4
- package/dist/index.d.ts +4 -2
- package/dist/index.js +1315 -953
- package/dist/index.js.map +1 -1
- package/dist/perseus-types.d.ts +4 -3
- package/dist/renderer.d.ts +6 -6
- package/dist/server-item-renderer.d.ts +2 -4
- package/dist/strings.js +1 -1
- package/dist/types.d.ts +19 -2
- package/dist/util/drawing-transform.d.ts +21 -0
- package/dist/util/extract-perseus-data.d.ts +26 -0
- package/dist/util/graph-bounds.d.ts +9 -0
- package/dist/util/graphie.d.ts +2 -0
- package/dist/util/interval.d.ts +1 -0
- package/dist/util/parse-perseus-json.d.ts +10 -0
- package/dist/util/tex.d.ts +1 -3
- package/dist/widgets/deprecated-standin/index.d.ts +4 -4
- package/dist/widgets/passage.d.ts +3 -3
- package/dist/widgets.d.ts +16 -6
- package/package.json +2 -2
- package/src/__tests__/extract-perseus-data.test.ts +1027 -0
- package/src/index.ts +6 -1
- package/src/init.ts +1 -1
- package/src/perseus-types.ts +4 -3
- package/src/renderer.tsx +12 -9
- package/src/server-item-renderer.tsx +2 -4
- package/src/styles/widgets/image.less +0 -3
- package/src/types.ts +19 -16
- package/src/util/drawing-transform.test.ts +67 -0
- package/src/util/drawing-transform.ts +102 -0
- package/src/util/extract-perseus-data.ts +539 -0
- package/src/util/graph-bounds.ts +23 -0
- package/src/util/graphie.ts +1167 -1258
- package/src/util/interactive.ts +2 -2
- package/src/util/interval.ts +4 -0
- package/src/util/parse-perseus-json.test.ts +10 -0
- package/src/util/parse-perseus-json.ts +13 -0
- package/src/util/tex.ts +11 -84
- package/src/widgets/__stories__/image.stories.tsx +12 -26
- package/src/widgets/__tests__/__snapshots__/grapher.test.ts.snap +72 -288
- package/src/widgets/__tests__/__snapshots__/interaction.test.ts.snap +5 -20
- package/src/widgets/__tests__/__snapshots__/interactive-graph.test.ts.snap +32 -128
- package/src/widgets/__tests__/__snapshots__/number-line.test.ts.snap +22 -88
- package/src/widgets/__tests__/__snapshots__/plotter.test.tsx.snap +12 -48
- package/src/widgets/deprecated-standin/__stories__/deprecated-standin.stories.tsx +8 -12
- package/src/widgets/deprecated-standin/__tests__/__snapshots__/deprecated-standin.test.ts.snap +6 -7
- package/src/widgets/deprecated-standin/index.tsx +8 -7
- package/src/widgets/interactive-graph.tsx +3 -2
- package/src/widgets/passage.tsx +5 -7
- package/src/widgets.ts +34 -10
- package/tsconfig-build.tsbuildinfo +1 -1
package/src/index.ts
CHANGED
|
@@ -80,6 +80,11 @@ export {default as KhanColors} from "./util/colors";
|
|
|
80
80
|
export {default as preprocessTex} from "./util/katex-preprocess";
|
|
81
81
|
export {registerAllWidgetsForTesting} from "./util/register-all-widgets-for-testing";
|
|
82
82
|
export * as SizingUtils from "./util/sizing-utils";
|
|
83
|
+
export {
|
|
84
|
+
getAnswersFromWidgets,
|
|
85
|
+
injectWidgets,
|
|
86
|
+
} from "./util/extract-perseus-data";
|
|
87
|
+
export {default as parsePerseusJSON} from "./util/parse-perseus-json";
|
|
83
88
|
|
|
84
89
|
/**
|
|
85
90
|
* Mixins
|
|
@@ -116,7 +121,6 @@ export type {
|
|
|
116
121
|
VideoKind,
|
|
117
122
|
WidgetDict,
|
|
118
123
|
WidgetExports,
|
|
119
|
-
WidgetInfo,
|
|
120
124
|
} from "./types";
|
|
121
125
|
export type {ParsedValue} from "./util";
|
|
122
126
|
export type {
|
|
@@ -130,6 +134,7 @@ export type {
|
|
|
130
134
|
PerseusPlotterWidgetOptions,
|
|
131
135
|
PerseusRadioWidgetOptions,
|
|
132
136
|
PerseusRenderer,
|
|
137
|
+
PerseusWidget,
|
|
133
138
|
} from "./perseus-types";
|
|
134
139
|
export type {Coord} from "./interactive2/types";
|
|
135
140
|
export type {MarkerType} from "./widgets/label-image/types";
|
package/src/init.ts
CHANGED
|
@@ -24,7 +24,7 @@ const init = function (options: PerseusOptions): Promise<undefined> {
|
|
|
24
24
|
// This section replaces widgets
|
|
25
25
|
// When it's time to replace the transformer widget
|
|
26
26
|
// uncomment this line
|
|
27
|
-
// Widgets.replaceWidget("transformer", "
|
|
27
|
+
// Widgets.replaceWidget("transformer", "deprecated-standin");
|
|
28
28
|
|
|
29
29
|
// Pass skipMathJax: true if MathJax is already loaded and configured.
|
|
30
30
|
const skipMathJax = options.skipMathJax;
|
package/src/perseus-types.ts
CHANGED
|
@@ -97,7 +97,7 @@ type Widget<Type extends string, Options> = {
|
|
|
97
97
|
// NOTE: perseus_data.go says this is required even though it isn't necessary.
|
|
98
98
|
alignment?: string;
|
|
99
99
|
// Options specific to the type field of the widget. See Perseus*WidgetOptions for more details
|
|
100
|
-
options: Options
|
|
100
|
+
options: Options;
|
|
101
101
|
// Only used by interactive child widgets (line, point, etc) to identify the components
|
|
102
102
|
// NOTE: perseus_data.go says this is required even though it isn't necessary.
|
|
103
103
|
key?: number;
|
|
@@ -704,13 +704,14 @@ export type PerseusMatcherWidgetOptions = {
|
|
|
704
704
|
padding: boolean;
|
|
705
705
|
};
|
|
706
706
|
|
|
707
|
+
export type PerseusMatrixWidgetAnswers = ReadonlyArray<ReadonlyArray<number>>;
|
|
707
708
|
export type PerseusMatrixWidgetOptions = {
|
|
708
709
|
// Translatable Text; Shown before the matrix
|
|
709
710
|
prefix: string;
|
|
710
711
|
// Translatable Text; Shown after the matrix
|
|
711
712
|
suffix: string;
|
|
712
713
|
// A data matrix representing the "correct" answers to be entered into the matrix
|
|
713
|
-
answers:
|
|
714
|
+
answers: PerseusMatrixWidgetAnswers;
|
|
714
715
|
// The coordinate location of the cursor position at start. default: [0, 0]
|
|
715
716
|
cursorPosition: ReadonlyArray<number>;
|
|
716
717
|
// The coordinate size of the matrix. Only supports 2-dimensional matrix. default: [3, 3]
|
|
@@ -821,7 +822,7 @@ export type PerseusNumberLineWidgetOptions = {
|
|
|
821
822
|
// The correct relative value. default: "eq". options: "eq", "lt", "gt", "le", "ge"
|
|
822
823
|
correctRel: string | null | undefined;
|
|
823
824
|
// This is the correct answer. The answer is validated (as right or wrong) by using only the end position of the point and the relation (=, <, >, ≤, ≥).
|
|
824
|
-
correctX: number
|
|
825
|
+
correctX: number;
|
|
825
826
|
// This controls the initial position of the point along the number line
|
|
826
827
|
initialX: number | null | undefined;
|
|
827
828
|
// Show tooltips
|
package/src/renderer.tsx
CHANGED
|
@@ -30,14 +30,17 @@ import WidgetContainer from "./widget-container";
|
|
|
30
30
|
import * as Widgets from "./widgets";
|
|
31
31
|
|
|
32
32
|
import type {DependenciesContext} from "./dependencies";
|
|
33
|
-
import type {
|
|
33
|
+
import type {
|
|
34
|
+
PerseusRenderer,
|
|
35
|
+
PerseusWidget,
|
|
36
|
+
PerseusWidgetOptions,
|
|
37
|
+
} from "./perseus-types";
|
|
34
38
|
import type {
|
|
35
39
|
APIOptions,
|
|
36
40
|
APIOptionsWithDefaults,
|
|
37
41
|
FilterCriterion,
|
|
38
42
|
FocusPath,
|
|
39
43
|
PerseusScore,
|
|
40
|
-
WidgetInfo,
|
|
41
44
|
WidgetProps,
|
|
42
45
|
} from "./types";
|
|
43
46
|
import type {LinterContextProps} from "@khanacademy/perseus-linter";
|
|
@@ -179,7 +182,7 @@ type Props = Partial<React.ContextType<typeof DependenciesContext>> & {
|
|
|
179
182
|
type State = {
|
|
180
183
|
translationLintErrors: ReadonlyArray<string>;
|
|
181
184
|
widgetInfo: Readonly<{
|
|
182
|
-
[id: string]:
|
|
185
|
+
[id: string]: PerseusWidget | null | undefined;
|
|
183
186
|
}>;
|
|
184
187
|
widgetProps: Readonly<{
|
|
185
188
|
[id: string]: any | null | undefined;
|
|
@@ -461,7 +464,7 @@ class Renderer extends React.Component<Props, State> {
|
|
|
461
464
|
|
|
462
465
|
// @ts-expect-error - TS2322 - Type '(props: Props) => Partial<Record<string, CategorizerWidget | CSProgramWidget | DefinitionWidget | DropdownWidget | ... 35 more ... | VideoWidget>>' is not assignable to type '(props: Props) => { [key: string]: PerseusWidget; }'.
|
|
463
466
|
_getAllWidgetsInfo: (props: Props) => {
|
|
464
|
-
[key: string]:
|
|
467
|
+
[key: string]: PerseusWidget;
|
|
465
468
|
} = (props: Props) => {
|
|
466
469
|
// @ts-expect-error - TS2345 - Argument of type '{ [key: string]: PerseusWidget; }' is not assignable to parameter of type 'Partial<Record<string, CategorizerWidget>>'.
|
|
467
470
|
return mapObject(props.widgets, (widgetInfo, widgetId) => {
|
|
@@ -483,7 +486,7 @@ class Renderer extends React.Component<Props, State> {
|
|
|
483
486
|
|
|
484
487
|
_getAllWidgetsStartProps: (
|
|
485
488
|
allWidgetInfo: {
|
|
486
|
-
[key: string]:
|
|
489
|
+
[key: string]: PerseusWidget;
|
|
487
490
|
},
|
|
488
491
|
props: Props,
|
|
489
492
|
) => any = (allWidgetInfo, props) => {
|
|
@@ -519,9 +522,9 @@ class Renderer extends React.Component<Props, State> {
|
|
|
519
522
|
};
|
|
520
523
|
};
|
|
521
524
|
|
|
522
|
-
_getWidgetInfo: (widgetId: string) =>
|
|
525
|
+
_getWidgetInfo: (widgetId: string) => PerseusWidget = (
|
|
523
526
|
widgetId: string,
|
|
524
|
-
):
|
|
527
|
+
): PerseusWidget => {
|
|
525
528
|
return (
|
|
526
529
|
this.state.widgetInfo[widgetId] ||
|
|
527
530
|
this._getDefaultWidgetInfo(widgetId)
|
|
@@ -816,14 +819,14 @@ class Renderer extends React.Component<Props, State> {
|
|
|
816
819
|
const widgetId = filterCriterion;
|
|
817
820
|
filterFunc = (
|
|
818
821
|
id: string,
|
|
819
|
-
widgetInfo:
|
|
822
|
+
widgetInfo: PerseusWidget,
|
|
820
823
|
widget?: Widget | null,
|
|
821
824
|
) => id === widgetId;
|
|
822
825
|
} else {
|
|
823
826
|
const widgetType = filterCriterion;
|
|
824
827
|
filterFunc = (
|
|
825
828
|
id: string,
|
|
826
|
-
widgetInfo:
|
|
829
|
+
widgetInfo: PerseusWidget,
|
|
827
830
|
widget?: Widget | null,
|
|
828
831
|
) => {
|
|
829
832
|
return widgetInfo.type === widgetType;
|
|
@@ -21,6 +21,7 @@ import {ApiOptions} from "./perseus-api";
|
|
|
21
21
|
import Renderer from "./renderer";
|
|
22
22
|
import Util from "./util";
|
|
23
23
|
|
|
24
|
+
import type {PerseusItem} from "./perseus-types";
|
|
24
25
|
import type {APIOptions, FocusPath, PerseusDependenciesV2} from "./types";
|
|
25
26
|
import type {KeypadAPI} from "@khanacademy/math-input";
|
|
26
27
|
import type {
|
|
@@ -36,10 +37,7 @@ const {mapObject} = Objective;
|
|
|
36
37
|
type OwnProps = {
|
|
37
38
|
apiOptions: APIOptions;
|
|
38
39
|
hintsVisible?: number;
|
|
39
|
-
item:
|
|
40
|
-
hints: ReadonlyArray<any>;
|
|
41
|
-
question: any;
|
|
42
|
-
};
|
|
40
|
+
item: PerseusItem;
|
|
43
41
|
linterContext: LinterContextProps;
|
|
44
42
|
problemNum?: number;
|
|
45
43
|
reviewMode?: boolean;
|
package/src/types.ts
CHANGED
|
@@ -415,12 +415,15 @@ export type WidgetExports<
|
|
|
415
415
|
// the `validate` method.
|
|
416
416
|
getWidget?: () => T;
|
|
417
417
|
accessible?: boolean | ((props: any) => boolean);
|
|
418
|
+
/** Supresses widget from showing up in the dropdown in the content editor */
|
|
418
419
|
hidden?: boolean;
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
420
|
+
/**
|
|
421
|
+
The widget version. Any time the _major_ version changes, the widget
|
|
422
|
+
should provide a new entry in the propUpgrades map to migrate from the
|
|
423
|
+
older version to the current (new) version. Minor version changes must
|
|
424
|
+
be backwards compatible with previous minor versions widget options.
|
|
425
|
+
This key defaults to `{major: 0, minor: 0}` if not provided.
|
|
426
|
+
*/
|
|
424
427
|
version?: Version;
|
|
425
428
|
supportedAlignments?: ReadonlyArray<Alignment>;
|
|
426
429
|
defaultAlignment?: Alignment;
|
|
@@ -430,17 +433,19 @@ export type WidgetExports<
|
|
|
430
433
|
|
|
431
434
|
traverseChildWidgets?: any; // (Props, traverseRenderer) => NewProps,
|
|
432
435
|
|
|
433
|
-
|
|
436
|
+
/** transforms the widget options to the props used to render the widget */
|
|
434
437
|
transform?: WidgetTransform;
|
|
435
|
-
|
|
436
|
-
|
|
438
|
+
/** transforms the widget options to the props used to render the widget for
|
|
439
|
+
static renders */
|
|
437
440
|
staticTransform?: WidgetTransform; // this is a function of some sort,
|
|
438
441
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
442
|
+
/**
|
|
443
|
+
A map of major version numbers (as a string, eg "1") to a function that
|
|
444
|
+
migrates from the _previous_ major version.
|
|
445
|
+
Example:
|
|
446
|
+
propUpgrades: {'1': (options) => ({...options})}
|
|
447
|
+
would migrate from major version 0 to 1.
|
|
448
|
+
*/
|
|
444
449
|
propUpgrades?: {
|
|
445
450
|
[key: string]: (arg1: any) => any;
|
|
446
451
|
}; // OldProps => NewProps,
|
|
@@ -448,13 +453,11 @@ export type WidgetExports<
|
|
|
448
453
|
widget: T;
|
|
449
454
|
}>;
|
|
450
455
|
|
|
451
|
-
export type WidgetInfo = PerseusWidget;
|
|
452
|
-
|
|
453
456
|
export type FilterCriterion =
|
|
454
457
|
| string
|
|
455
458
|
| ((
|
|
456
459
|
id: string,
|
|
457
|
-
widgetInfo:
|
|
460
|
+
widgetInfo: PerseusWidget,
|
|
458
461
|
widget?: Widget | null | undefined,
|
|
459
462
|
) => boolean);
|
|
460
463
|
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import {DrawingTransform} from "./drawing-transform";
|
|
2
|
+
import {GraphBounds} from "./graph-bounds";
|
|
3
|
+
|
|
4
|
+
import type {Coord} from "../interactive2/types";
|
|
5
|
+
|
|
6
|
+
describe("DrawingTransform", () => {
|
|
7
|
+
describe("with bounds of -10 to 10, and 5px per unit", () => {
|
|
8
|
+
let transform: DrawingTransform;
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
const bounds = new GraphBounds([-10, 10], [-10, 10]);
|
|
11
|
+
// Each unit in both dimensions is 5 pixels, so the whole graph
|
|
12
|
+
// is 100px by 100px
|
|
13
|
+
const scale: [number, number] = [5, 5];
|
|
14
|
+
transform = new DrawingTransform({setSize() {}}, scale, bounds);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const testPoints: [string, Coord, Coord][] = [
|
|
18
|
+
["lower left corner", [-10, -10], [0, 100]],
|
|
19
|
+
["upper left corner", [-10, 10], [0, 0]],
|
|
20
|
+
["lower right corner", [10, -10], [100, 100]],
|
|
21
|
+
["upper right corner", [10, 10], [100, 0]],
|
|
22
|
+
["origin", [0, 0], [50, 50]],
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
it.each(testPoints)(
|
|
26
|
+
"transforms the point at the %s from math coords to canvas coords",
|
|
27
|
+
(_, cartesian, canvas) => {
|
|
28
|
+
expect(transform.scalePoint(cartesian)).toEqual(canvas);
|
|
29
|
+
},
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
it.each(testPoints)(
|
|
33
|
+
"transforms the point at the %s from canvas coords to math coords",
|
|
34
|
+
(_, cartesian, canvas) => {
|
|
35
|
+
expect(transform.unscalePoint(canvas)).toEqual(cartesian);
|
|
36
|
+
},
|
|
37
|
+
);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("updates the size of the Raphael canvas when setScale is called with a number", () => {
|
|
41
|
+
const raphael = {setSize: jest.fn()};
|
|
42
|
+
const scale: Coord = [40, 40];
|
|
43
|
+
const bounds = new GraphBounds([-10, 10], [-10, 10]);
|
|
44
|
+
const transform = new DrawingTransform(raphael, scale, bounds);
|
|
45
|
+
expect(raphael.setSize).toHaveBeenCalledWith(800, 800);
|
|
46
|
+
|
|
47
|
+
// Act
|
|
48
|
+
transform.setScale(5);
|
|
49
|
+
|
|
50
|
+
// Assert
|
|
51
|
+
expect(raphael.setSize).toHaveBeenCalledWith(100, 100);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("updates the size of the Raphael canvas when setScale is called with dimensions", () => {
|
|
55
|
+
const raphael = {setSize: jest.fn()};
|
|
56
|
+
const scale: Coord = [40, 40];
|
|
57
|
+
const bounds = new GraphBounds([-5, 5], [-5, 5]);
|
|
58
|
+
const transform = new DrawingTransform(raphael, scale, bounds);
|
|
59
|
+
expect(raphael.setSize).toHaveBeenCalledWith(400, 400);
|
|
60
|
+
|
|
61
|
+
// Act
|
|
62
|
+
transform.setScale([3, 7]);
|
|
63
|
+
|
|
64
|
+
// Assert
|
|
65
|
+
expect(raphael.setSize).toHaveBeenCalledWith(30, 70);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type {GraphBounds} from "./graph-bounds";
|
|
2
|
+
import type {Coord} from "../interactive2/types";
|
|
3
|
+
|
|
4
|
+
interface Raphael {
|
|
5
|
+
setSize(width: number, height: number);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// DrawingTransform is responsible for transforming from "math coordinates"
|
|
9
|
+
// (on the Cartesian plane) to "canvas coordinates" in pixels.
|
|
10
|
+
export class DrawingTransform {
|
|
11
|
+
raphael: Raphael;
|
|
12
|
+
|
|
13
|
+
// Scale is measured in pixels per unit on the Cartesian plane.
|
|
14
|
+
xScale: number;
|
|
15
|
+
yScale: number;
|
|
16
|
+
bounds: GraphBounds;
|
|
17
|
+
|
|
18
|
+
constructor(
|
|
19
|
+
raphael: Raphael,
|
|
20
|
+
initialScale: [number, number],
|
|
21
|
+
bounds: GraphBounds,
|
|
22
|
+
) {
|
|
23
|
+
this.raphael = raphael;
|
|
24
|
+
this.bounds = bounds;
|
|
25
|
+
this.xScale = initialScale[0];
|
|
26
|
+
this.yScale = initialScale[1];
|
|
27
|
+
this.setScale(initialScale);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// TODO(benchristel): rename these methods to transform* instead of scale*.
|
|
31
|
+
// They're doing more than scaling! Some of them also translate.
|
|
32
|
+
scaleVector = (point: number | Coord) => {
|
|
33
|
+
if (typeof point === "number") {
|
|
34
|
+
return this.scaleVector([point, point]);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const x = point[0];
|
|
38
|
+
const y = point[1];
|
|
39
|
+
return [x * this.xScale, y * this.yScale];
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
scalePoint = (point: number | Coord): Coord => {
|
|
43
|
+
if (typeof point === "number") {
|
|
44
|
+
return this.scalePoint([point, point]);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const x = point[0];
|
|
48
|
+
const y = point[1];
|
|
49
|
+
return [
|
|
50
|
+
(x - this.bounds.xMin) * this.xScale,
|
|
51
|
+
(this.bounds.yMax - y) * this.yScale,
|
|
52
|
+
];
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
unscalePoint = (point: number | Coord) => {
|
|
56
|
+
if (typeof point === "number") {
|
|
57
|
+
return this.unscalePoint([point, point]);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const x = point[0];
|
|
61
|
+
const y = point[1];
|
|
62
|
+
return [
|
|
63
|
+
x / this.xScale + this.bounds.xMin,
|
|
64
|
+
this.bounds.yMax - y / this.yScale,
|
|
65
|
+
];
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
unscaleVector = (point: number | Coord) => {
|
|
69
|
+
if (typeof point === "number") {
|
|
70
|
+
return this.unscaleVector([point, point]);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return [point[0] / this.xScale, point[1] / this.yScale];
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
setScale = (scale: number | Coord) => {
|
|
77
|
+
if (typeof scale === "number") {
|
|
78
|
+
scale = [scale, scale];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
this.xScale = scale[0];
|
|
82
|
+
this.yScale = scale[1];
|
|
83
|
+
|
|
84
|
+
// Update the canvas size
|
|
85
|
+
this.raphael.setSize(...this.canvasDimensions());
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
canvasDimensions = (): Coord => {
|
|
89
|
+
return [
|
|
90
|
+
this.bounds.width() * this.xScale,
|
|
91
|
+
this.bounds.height() * this.yScale,
|
|
92
|
+
];
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
pixelsPerUnitX = (): number => {
|
|
96
|
+
return this.xScale;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
pixelsPerUnitY = (): number => {
|
|
100
|
+
return this.yScale;
|
|
101
|
+
};
|
|
102
|
+
}
|