@khanacademy/perseus 19.1.4 → 19.1.6
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/dist/components/graphie.d.ts +9 -1
- package/dist/es/index.js +255 -122
- package/dist/es/index.js.map +1 -1
- package/dist/index.js +261 -126
- package/dist/index.js.map +1 -1
- package/dist/interactive2/interactive-util.d.ts +1 -1
- package/dist/interactive2/movable-point-options.d.ts +5 -4
- package/dist/interactive2/movable.d.ts +90 -13
- package/dist/interactive2/types.d.ts +6 -0
- package/dist/util/graphie.d.ts +5 -0
- package/dist/widgets/expression.d.ts +4 -4
- package/package.json +11 -8
- package/.babelrc.js +0 -8
- package/.eslintrc.js +0 -12
- package/CHANGELOG.md +0 -1422
- package/src/__stories__/article-renderer.stories.tsx +0 -100
- package/src/__stories__/hints-renderer.stories.tsx +0 -55
- package/src/__stories__/server-item-renderer.stories.tsx +0 -44
- package/src/__testdata__/article-renderer.testdata.ts +0 -212
- package/src/__testdata__/graphie.testdata.ts +0 -51
- package/src/__testdata__/renderer.testdata.ts +0 -274
- package/src/__testdata__/server-item-renderer.testdata.ts +0 -171
- package/src/__tests__/__snapshots__/perseus-markdown.test.ts.snap +0 -507
- package/src/__tests__/__snapshots__/renderer.test.tsx.snap +0 -525
- package/src/__tests__/__snapshots__/server-item-renderer.test.tsx.snap +0 -285
- package/src/__tests__/a11y.test.ts +0 -303
- package/src/__tests__/article-renderer.test.tsx +0 -130
- package/src/__tests__/error-boundary.test.tsx +0 -76
- package/src/__tests__/extract-perseus-data.test.ts +0 -1027
- package/src/__tests__/init.test.ts +0 -10
- package/src/__tests__/jipt-paragraphs.test.ts +0 -32
- package/src/__tests__/mock-asset-loading-widget.tsx +0 -53
- package/src/__tests__/mock-widget.tsx +0 -33
- package/src/__tests__/perseus-markdown.test.ts +0 -764
- package/src/__tests__/renderability.test.ts +0 -480
- package/src/__tests__/renderer-api.test.tsx +0 -302
- package/src/__tests__/renderer.test.tsx +0 -1816
- package/src/__tests__/server-item-renderer.test.tsx +0 -582
- package/src/__tests__/test-items/image-item.ts +0 -24
- package/src/__tests__/test-items/input-number-1-item.ts +0 -26
- package/src/__tests__/test-items/input-number-2-item.ts +0 -38
- package/src/__tests__/test-items/table-item.ts +0 -29
- package/src/__tests__/tex-wrangler.test.ts +0 -82
- package/src/__tests__/util.test.ts +0 -40
- package/src/__tests__/widget-container.test.tsx +0 -126
- package/src/__tests__/widgets.test.ts +0 -91
- package/src/a11y.ts +0 -45
- package/src/all-widgets.ts +0 -9
- package/src/article-renderer.tsx +0 -237
- package/src/asset-context.ts +0 -30
- package/src/basic-widgets.ts +0 -16
- package/src/components/__stories__/button-group.stories.tsx +0 -55
- package/src/components/__stories__/fixed-to-responsive.stories.tsx +0 -141
- package/src/components/__stories__/graph.stories.tsx +0 -28
- package/src/components/__stories__/graphie.stories.tsx +0 -33
- package/src/components/__stories__/hud.stories.tsx +0 -35
- package/src/components/__stories__/icon.stories.tsx +0 -63
- package/src/components/__stories__/image-loader.stories.tsx +0 -63
- package/src/components/__stories__/info-tip.stories.tsx +0 -31
- package/src/components/__stories__/inline-icon.stories.tsx +0 -40
- package/src/components/__stories__/input-with-examples.stories.tsx +0 -53
- package/src/components/__stories__/lint.stories.tsx +0 -86
- package/src/components/__stories__/math-input.stories.tsx +0 -43
- package/src/components/__stories__/math-output.stories.tsx +0 -23
- package/src/components/__stories__/multi-button-group.stories.tsx +0 -66
- package/src/components/__stories__/number-input.stories.tsx +0 -41
- package/src/components/__stories__/prop-check-box.stories.tsx +0 -65
- package/src/components/__stories__/range-input.stories.tsx +0 -29
- package/src/components/__stories__/simple-keypad-input.stories.tsx +0 -27
- package/src/components/__stories__/sortable.stories.tsx +0 -77
- package/src/components/__stories__/stub-tag-editor.stories.tsx +0 -45
- package/src/components/__stories__/svg-image.stories.tsx +0 -74
- package/src/components/__stories__/tex.stories.tsx +0 -23
- package/src/components/__stories__/text-input.stories.tsx +0 -33
- package/src/components/__stories__/text-list-editor.stories.tsx +0 -32
- package/src/components/__stories__/tooltip.stories.tsx +0 -52
- package/src/components/__stories__/zoomable-tex.stories.tsx +0 -41
- package/src/components/__stories__/zoomable.stories.tsx +0 -49
- package/src/components/__testdata__/sorter.testdata.ts +0 -19
- package/src/components/__tests__/__snapshots__/sortable.test.tsx.snap +0 -163
- package/src/components/__tests__/__snapshots__/sorter.test.tsx.snap +0 -265
- package/src/components/__tests__/math-input.test.tsx +0 -147
- package/src/components/__tests__/number-input.test.tsx +0 -128
- package/src/components/__tests__/sortable.test.tsx +0 -269
- package/src/components/__tests__/sorter.test.tsx +0 -114
- package/src/components/__tests__/zoomable.test.tsx +0 -399
- package/src/components/button-group.tsx +0 -130
- package/src/components/constants.ts +0 -14
- package/src/components/fixed-to-responsive.tsx +0 -169
- package/src/components/graph.tsx +0 -444
- package/src/components/graphie-classes.ts +0 -153
- package/src/components/graphie-movables.ts +0 -338
- package/src/components/graphie.tsx +0 -392
- package/src/components/highlighting/highlightable-content.tsx +0 -240
- package/src/components/highlighting/highlights.ts +0 -207
- package/src/components/highlighting/ranges.ts +0 -304
- package/src/components/highlighting/types.ts +0 -73
- package/src/components/highlighting/ui/highlight-renderer.tsx +0 -172
- package/src/components/highlighting/ui/highlight-set-renderer.tsx +0 -185
- package/src/components/highlighting/ui/highlight-tooltip.tsx +0 -141
- package/src/components/highlighting/ui/highlighting-ui.tsx +0 -143
- package/src/components/highlighting/ui/selection-tracker.tsx +0 -186
- package/src/components/highlighting/ui/types.ts +0 -21
- package/src/components/highlighting/ui/util.ts +0 -165
- package/src/components/highlighting/word-indexer.tsx +0 -152
- package/src/components/hud.tsx +0 -167
- package/src/components/icon.tsx +0 -141
- package/src/components/image-loader.tsx +0 -177
- package/src/components/info-tip/info-tip.tsx +0 -144
- package/src/components/info-tip.tsx +0 -39
- package/src/components/inline-icon.tsx +0 -60
- package/src/components/input-with-examples.tsx +0 -249
- package/src/components/lint.tsx +0 -459
- package/src/components/math-input.tsx +0 -474
- package/src/components/math-output.tsx +0 -134
- package/src/components/multi-button-group.tsx +0 -135
- package/src/components/number-input.tsx +0 -249
- package/src/components/prop-check-box.tsx +0 -79
- package/src/components/range-input.tsx +0 -60
- package/src/components/simple-keypad-input.tsx +0 -88
- package/src/components/sortable.tsx +0 -958
- package/src/components/stub-tag-editor.tsx +0 -53
- package/src/components/svg-image.tsx +0 -779
- package/src/components/tex.tsx +0 -67
- package/src/components/text-input.tsx +0 -129
- package/src/components/text-list-editor.tsx +0 -166
- package/src/components/tooltip.tsx +0 -410
- package/src/components/visibility-observer/__stories__/visibility-observer.stories.tsx +0 -141
- package/src/components/visibility-observer/create-visibility-observer.ts +0 -108
- package/src/components/visibility-observer/get-scroll-parent.ts +0 -46
- package/src/components/visibility-observer/visibility-observer.tsx +0 -124
- package/src/components/zoomable-tex.tsx +0 -78
- package/src/components/zoomable.tsx +0 -304
- package/src/components.ts +0 -15
- package/src/definition-context.tsx +0 -59
- package/src/dependencies.ts +0 -40
- package/src/error-boundary.tsx +0 -70
- package/src/extra-widgets.ts +0 -75
- package/src/hint-renderer.tsx +0 -151
- package/src/hints-renderer.tsx +0 -372
- package/src/icon-paths.ts +0 -119
- package/src/index.ts +0 -171
- package/src/init.ts +0 -56
- package/src/interaction-tracker.ts +0 -62
- package/src/interactive2/__tests__/movable-mock.ts +0 -43
- package/src/interactive2/__tests__/movable-point.test.ts +0 -144
- package/src/interactive2/__tests__/movable.test.ts +0 -157
- package/src/interactive2/interactive-util.test.ts +0 -93
- package/src/interactive2/interactive-util.ts +0 -159
- package/src/interactive2/movable-helper-methods.ts +0 -166
- package/src/interactive2/movable-line-options.ts +0 -419
- package/src/interactive2/movable-line.ts +0 -307
- package/src/interactive2/movable-point-options.ts +0 -212
- package/src/interactive2/movable-point.tsx +0 -585
- package/src/interactive2/movable-polygon-options.ts +0 -389
- package/src/interactive2/movable-polygon.ts +0 -360
- package/src/interactive2/movable.ts +0 -279
- package/src/interactive2/objective_.ts +0 -79
- package/src/interactive2/types.ts +0 -2
- package/src/interactive2/wrapped-defaults.ts +0 -87
- package/src/interactive2/wrapped-ellipse.ts +0 -94
- package/src/interactive2/wrapped-line.ts +0 -116
- package/src/interactive2/wrapped-path.ts +0 -51
- package/src/interactive2.ts +0 -70
- package/src/item-version.ts +0 -10
- package/src/jipt-paragraphs.ts +0 -49
- package/src/jquery.mobile.vmouse.js +0 -545
- package/src/loading-context.ts +0 -21
- package/src/logging/log.test.ts +0 -39
- package/src/logging/log.ts +0 -97
- package/src/mixins/__tests__/widget-prop-denylist.test.ts +0 -21
- package/src/mixins/changeable.ts +0 -133
- package/src/mixins/editor-jsonify.ts +0 -13
- package/src/mixins/widget-jsonify-deprecated.ts +0 -23
- package/src/mixins/widget-prop-denylist.ts +0 -48
- package/src/multi-items/__stories__/multi-renderer.stories.tsx +0 -83
- package/src/multi-items/__testdata__/multi-renderer.testdata.ts +0 -172
- package/src/multi-items/__tests__/__snapshots__/multi-renderer.test.tsx.snap +0 -1164
- package/src/multi-items/__tests__/items.test.ts +0 -240
- package/src/multi-items/__tests__/multi-renderer.test.tsx +0 -914
- package/src/multi-items/__tests__/prop-type-builders.test.ts +0 -133
- package/src/multi-items/__tests__/shapes.test.ts +0 -43
- package/src/multi-items/__tests__/trees.test.ts +0 -317
- package/src/multi-items/item-types.ts +0 -56
- package/src/multi-items/items.ts +0 -176
- package/src/multi-items/multi-renderer.tsx +0 -569
- package/src/multi-items/prop-type-builders.ts +0 -106
- package/src/multi-items/shape-types.ts +0 -52
- package/src/multi-items/shapes.ts +0 -52
- package/src/multi-items/tree-types.ts +0 -47
- package/src/multi-items/trees.ts +0 -360
- package/src/multi-items.ts +0 -75
- package/src/perseus-api.tsx +0 -188
- package/src/perseus-env.ts +0 -27
- package/src/perseus-error.ts +0 -17
- package/src/perseus-markdown.tsx +0 -405
- package/src/perseus-types.ts +0 -1366
- package/src/perseus-version.ts +0 -9
- package/src/question-paragraph.tsx +0 -34
- package/src/renderability.ts +0 -109
- package/src/renderer.tsx +0 -2000
- package/src/server-item-renderer.tsx +0 -495
- package/src/sigfigs.ts +0 -490
- package/src/styles/articles.less +0 -749
- package/src/styles/constants.ts +0 -94
- package/src/styles/global-constants.ts +0 -343
- package/src/styles/global-styles.ts +0 -306
- package/src/styles/khan-exercise.css +0 -1468
- package/src/styles/media-queries.ts +0 -47
- package/src/styles/perseus-renderer.less +0 -732
- package/src/styles/shared.ts +0 -130
- package/src/styles/util.less +0 -47
- package/src/styles/variables.boostrap.less +0 -277
- package/src/styles/variables.less +0 -56
- package/src/styles/variables.shared.less +0 -340
- package/src/styles/widgets/categorizer.less +0 -38
- package/src/styles/widgets/dropdown.less +0 -3
- package/src/styles/widgets/expression.less +0 -65
- package/src/styles/widgets/graded-group.less +0 -36
- package/src/styles/widgets/grapher.less +0 -12
- package/src/styles/widgets/group.less +0 -19
- package/src/styles/widgets/image.less +0 -54
- package/src/styles/widgets/interactive-graph.less +0 -64
- package/src/styles/widgets/label-image.less +0 -57
- package/src/styles/widgets/matcher.less +0 -8
- package/src/styles/widgets/matrix.less +0 -126
- package/src/styles/widgets/measurer.less +0 -20
- package/src/styles/widgets/orderer.less +0 -98
- package/src/styles/widgets/passage.less +0 -154
- package/src/styles/widgets/plotter.less +0 -31
- package/src/styles/widgets/radio.less +0 -58
- package/src/styles/widgets/reaction-diagram.less +0 -35
- package/src/styles/widgets/sequence.less +0 -5
- package/src/styles/widgets/sortable.less +0 -113
- package/src/styles/widgets/table.less +0 -57
- package/src/styles/widgets/unit.less +0 -15
- package/src/tex-wrangler.ts +0 -182
- package/src/translation-linter.ts +0 -126
- package/src/traversal.ts +0 -138
- package/src/types.ts +0 -513
- package/src/util/a11y.ts +0 -23
- package/src/util/answer-types.test.ts +0 -114
- package/src/util/answer-types.ts +0 -876
- package/src/util/colors.ts +0 -93
- package/src/util/drawing-transform.test.ts +0 -67
- package/src/util/drawing-transform.ts +0 -102
- package/src/util/extract-perseus-data.ts +0 -539
- package/src/util/fix-passage-refs.ts +0 -121
- package/src/util/geometry.test.ts +0 -75
- package/src/util/geometry.ts +0 -312
- package/src/util/graph-bounds.ts +0 -23
- package/src/util/graph-utils.ts +0 -15
- package/src/util/graphie.test.ts +0 -1277
- package/src/util/graphie.ts +0 -1566
- package/src/util/interactive.ts +0 -4172
- package/src/util/interval.ts +0 -4
- package/src/util/katex-preprocess.ts +0 -14
- package/src/util/math.test.ts +0 -11
- package/src/util/math.ts +0 -294
- package/src/util/mobile-native-utils.test.ts +0 -22
- package/src/util/mobile-native-utils.ts +0 -12
- package/src/util/parse-perseus-json.test.ts +0 -10
- package/src/util/parse-perseus-json.ts +0 -13
- package/src/util/react-render.tsx +0 -27
- package/src/util/register-all-widgets-for-testing.ts +0 -13
- package/src/util/scroll-utils.ts +0 -155
- package/src/util/sizing-utils.ts +0 -46
- package/src/util/tex.ts +0 -73
- package/src/util/url-utils.ts +0 -34
- package/src/util.graphie.test.ts +0 -45
- package/src/util.graphie.ts +0 -59
- package/src/util.ts +0 -966
- package/src/version.ts +0 -10
- package/src/widget-container.tsx +0 -199
- package/src/widgets/__stories__/categorizer.stories.tsx +0 -14
- package/src/widgets/__stories__/cs-program.stories.tsx +0 -14
- package/src/widgets/__stories__/definition.stories.tsx +0 -114
- package/src/widgets/__stories__/dropdown.stories.tsx +0 -14
- package/src/widgets/__stories__/explanation.stories.tsx +0 -18
- package/src/widgets/__stories__/expression.stories.tsx +0 -145
- package/src/widgets/__stories__/graded-group-set.stories.tsx +0 -31
- package/src/widgets/__stories__/graded-group.stories.tsx +0 -31
- package/src/widgets/__stories__/grapher.stories.tsx +0 -46
- package/src/widgets/__stories__/group.stories.tsx +0 -14
- package/src/widgets/__stories__/iframe.stories.tsx +0 -14
- package/src/widgets/__stories__/image.stories.tsx +0 -85
- package/src/widgets/__stories__/input-number.stories.tsx +0 -101
- package/src/widgets/__stories__/interaction.stories.tsx +0 -14
- package/src/widgets/__stories__/interactive-graph.stories.tsx +0 -60
- package/src/widgets/__stories__/label-image.stories.tsx +0 -130
- package/src/widgets/__stories__/matcher.stories.tsx +0 -14
- package/src/widgets/__stories__/matrix.stories.tsx +0 -14
- package/src/widgets/__stories__/number-line.stories.tsx +0 -14
- package/src/widgets/__stories__/numeric-input.stories.tsx +0 -105
- package/src/widgets/__stories__/orderer.stories.tsx +0 -18
- package/src/widgets/__stories__/passage-ref.stories.tsx +0 -33
- package/src/widgets/__stories__/passage.stories.tsx +0 -39
- package/src/widgets/__stories__/plotter.stories.tsx +0 -27
- package/src/widgets/__stories__/radio.stories.tsx +0 -86
- package/src/widgets/__stories__/random-widgets.stories.tsx +0 -59
- package/src/widgets/__stories__/sorter.stories.tsx +0 -14
- package/src/widgets/__stories__/test-keypad-context-wrapper.tsx +0 -66
- package/src/widgets/__stories__/unit.stories.tsx +0 -28
- package/src/widgets/__stories__/video-transcript-link.stories.tsx +0 -23
- package/src/widgets/__stories__/video.stories.tsx +0 -22
- package/src/widgets/__testdata__/base-radio.testdata.ts +0 -21
- package/src/widgets/__testdata__/categorizer.testdata.ts +0 -89
- package/src/widgets/__testdata__/cs-program.testdata.ts +0 -27
- package/src/widgets/__testdata__/dropdown.testdata.ts +0 -71
- package/src/widgets/__testdata__/explanation.testdata.ts +0 -85
- package/src/widgets/__testdata__/expression.testdata.ts +0 -198
- package/src/widgets/__testdata__/graded-group-set.testdata.ts +0 -356
- package/src/widgets/__testdata__/graded-group.testdata.ts +0 -77
- package/src/widgets/__testdata__/grapher.testdata.ts +0 -374
- package/src/widgets/__testdata__/group.testdata.ts +0 -153
- package/src/widgets/__testdata__/iframe.testdata.ts +0 -35
- package/src/widgets/__testdata__/image.testdata.ts +0 -36
- package/src/widgets/__testdata__/input-number.testdata.ts +0 -72
- package/src/widgets/__testdata__/interaction.testdata.ts +0 -262
- package/src/widgets/__testdata__/interactive-graph-random.testdata.ts +0 -269
- package/src/widgets/__testdata__/interactive-graph.testdata.ts +0 -644
- package/src/widgets/__testdata__/label-image.testdata.ts +0 -308
- package/src/widgets/__testdata__/matcher.testdata.ts +0 -33
- package/src/widgets/__testdata__/matrix.testdata.ts +0 -28
- package/src/widgets/__testdata__/number-line.testdata.ts +0 -30
- package/src/widgets/__testdata__/numeric-input.testdata.ts +0 -265
- package/src/widgets/__testdata__/orderer.testdata.ts +0 -123
- package/src/widgets/__testdata__/passage-ref.testdata.ts +0 -101
- package/src/widgets/__testdata__/passage.testdata.ts +0 -95
- package/src/widgets/__testdata__/plotter.testdata.ts +0 -36
- package/src/widgets/__testdata__/radio.testdata.ts +0 -324
- package/src/widgets/__testdata__/randomizers.ts +0 -51
- package/src/widgets/__testdata__/sorter.testdata.ts +0 -19
- package/src/widgets/__testdata__/video.testdata.ts +0 -61
- package/src/widgets/__tests__/__snapshots__/categorizer.test.ts.snap +0 -940
- package/src/widgets/__tests__/__snapshots__/cs-program.test.ts.snap +0 -67
- package/src/widgets/__tests__/__snapshots__/definition.test.ts.snap +0 -97
- package/src/widgets/__tests__/__snapshots__/dropdown.test.ts.snap +0 -133
- package/src/widgets/__tests__/__snapshots__/explanation.test.ts.snap +0 -425
- package/src/widgets/__tests__/__snapshots__/graded-group-set-jipt.test.ts.snap +0 -729
- package/src/widgets/__tests__/__snapshots__/graded-group-set.test.ts.snap +0 -339
- package/src/widgets/__tests__/__snapshots__/graded-group.test.ts.snap +0 -801
- package/src/widgets/__tests__/__snapshots__/grapher.test.ts.snap +0 -2673
- package/src/widgets/__tests__/__snapshots__/group.test.tsx.snap +0 -1211
- package/src/widgets/__tests__/__snapshots__/iframe.test.ts.snap +0 -61
- package/src/widgets/__tests__/__snapshots__/image.test.ts.snap +0 -378
- package/src/widgets/__tests__/__snapshots__/input-number.test.ts.snap +0 -495
- package/src/widgets/__tests__/__snapshots__/interaction.test.ts.snap +0 -292
- package/src/widgets/__tests__/__snapshots__/interactive-graph.test.ts.snap +0 -2889
- package/src/widgets/__tests__/__snapshots__/matcher.test.tsx.snap +0 -883
- package/src/widgets/__tests__/__snapshots__/matrix.test.ts.snap +0 -514
- package/src/widgets/__tests__/__snapshots__/number-line.test.ts.snap +0 -814
- package/src/widgets/__tests__/__snapshots__/numeric-input.test.ts.snap +0 -470
- package/src/widgets/__tests__/__snapshots__/orderer.test.ts.snap +0 -243
- package/src/widgets/__tests__/__snapshots__/passage-ref.test.ts.snap +0 -434
- package/src/widgets/__tests__/__snapshots__/passage.test.tsx.snap +0 -1956
- package/src/widgets/__tests__/__snapshots__/plotter.test.tsx.snap +0 -827
- package/src/widgets/__tests__/__snapshots__/radio.test.ts.snap +0 -4237
- package/src/widgets/__tests__/__snapshots__/unit.test.tsx.snap +0 -20
- package/src/widgets/__tests__/__snapshots__/video.test.ts.snap +0 -153
- package/src/widgets/__tests__/categorizer.test.ts +0 -203
- package/src/widgets/__tests__/cs-program.test.ts +0 -43
- package/src/widgets/__tests__/definition.test.ts +0 -136
- package/src/widgets/__tests__/dropdown.test.ts +0 -105
- package/src/widgets/__tests__/explanation.test.ts +0 -198
- package/src/widgets/__tests__/expression-mobile.test.tsx +0 -363
- package/src/widgets/__tests__/expression.test.tsx +0 -484
- package/src/widgets/__tests__/graded-group-set-jipt.test.ts +0 -30
- package/src/widgets/__tests__/graded-group-set.test.ts +0 -288
- package/src/widgets/__tests__/graded-group.test.ts +0 -232
- package/src/widgets/__tests__/grapher.cypress.ts +0 -576
- package/src/widgets/__tests__/grapher.test.ts +0 -33
- package/src/widgets/__tests__/group.test.tsx +0 -510
- package/src/widgets/__tests__/iframe.test.ts +0 -45
- package/src/widgets/__tests__/image.test.ts +0 -62
- package/src/widgets/__tests__/input-number.test.ts +0 -354
- package/src/widgets/__tests__/interaction.test.ts +0 -31
- package/src/widgets/__tests__/interactive-graph.cypress.ts +0 -815
- package/src/widgets/__tests__/interactive-graph.test.ts +0 -106
- package/src/widgets/__tests__/matcher.test.tsx +0 -176
- package/src/widgets/__tests__/matrix.test.ts +0 -82
- package/src/widgets/__tests__/number-line.test.ts +0 -79
- package/src/widgets/__tests__/numeric-input.test.ts +0 -575
- package/src/widgets/__tests__/orderer.test.ts +0 -73
- package/src/widgets/__tests__/passage-ref.test.ts +0 -168
- package/src/widgets/__tests__/passage.test.tsx +0 -224
- package/src/widgets/__tests__/plotter.test.tsx +0 -69
- package/src/widgets/__tests__/radio/base-radio.test.tsx +0 -470
- package/src/widgets/__tests__/radio/choice-icon.test.tsx +0 -90
- package/src/widgets/__tests__/radio/choice.test.tsx +0 -310
- package/src/widgets/__tests__/radio/focus-ring.test.tsx +0 -22
- package/src/widgets/__tests__/radio/option-status.test.tsx +0 -77
- package/src/widgets/__tests__/radio.test.ts +0 -807
- package/src/widgets/__tests__/randomizers.test.ts +0 -53
- package/src/widgets/__tests__/renderQuestion.tsx +0 -101
- package/src/widgets/__tests__/unit.test.tsx +0 -168
- package/src/widgets/__tests__/video.test.ts +0 -69
- package/src/widgets/categorizer.tsx +0 -344
- package/src/widgets/cs-program.tsx +0 -227
- package/src/widgets/definition.tsx +0 -121
- package/src/widgets/deprecated-standin/__stories__/deprecated-standin.stories.tsx +0 -30
- package/src/widgets/deprecated-standin/__tests__/__snapshots__/deprecated-standin.test.ts.snap +0 -70
- package/src/widgets/deprecated-standin/__tests__/deprecated-standin.test.ts +0 -93
- package/src/widgets/deprecated-standin/index.tsx +0 -58
- package/src/widgets/dropdown.tsx +0 -142
- package/src/widgets/explanation.tsx +0 -317
- package/src/widgets/expression.tsx +0 -707
- package/src/widgets/graded-group-answer-bar.tsx +0 -142
- package/src/widgets/graded-group-set.tsx +0 -338
- package/src/widgets/graded-group.tsx +0 -471
- package/src/widgets/grapher/util.tsx +0 -799
- package/src/widgets/grapher.tsx +0 -660
- package/src/widgets/group.tsx +0 -195
- package/src/widgets/iframe.tsx +0 -195
- package/src/widgets/image.tsx +0 -312
- package/src/widgets/input-number.tsx +0 -412
- package/src/widgets/interaction/arrow-picker.tsx +0 -38
- package/src/widgets/interaction/color-picker.tsx +0 -69
- package/src/widgets/interaction/dash-picker.tsx +0 -45
- package/src/widgets/interaction/element-container.tsx +0 -100
- package/src/widgets/interaction.tsx +0 -826
- package/src/widgets/interactive-graph.tsx +0 -2597
- package/src/widgets/label-image/__stories__/answer-choices.stories.tsx +0 -91
- package/src/widgets/label-image/__stories__/answer-pill.stories.tsx +0 -62
- package/src/widgets/label-image/__stories__/marker.stories.tsx +0 -171
- package/src/widgets/label-image/__tests__/answer-pill.test.tsx +0 -75
- package/src/widgets/label-image/__tests__/hide-answers-toggle.test.tsx +0 -39
- package/src/widgets/label-image/answer-choices.tsx +0 -88
- package/src/widgets/label-image/answer-pill.tsx +0 -103
- package/src/widgets/label-image/hide-answers-toggle.tsx +0 -40
- package/src/widgets/label-image/marker.tsx +0 -268
- package/src/widgets/label-image/strings.ts +0 -5
- package/src/widgets/label-image/types.ts +0 -27
- package/src/widgets/label-image.test.ts +0 -928
- package/src/widgets/label-image.tsx +0 -819
- package/src/widgets/lights-puzzle.tsx +0 -359
- package/src/widgets/matcher.tsx +0 -298
- package/src/widgets/matrix.tsx +0 -634
- package/src/widgets/measurer.tsx +0 -222
- package/src/widgets/molecule/__tests__/layout.test.ts +0 -162
- package/src/widgets/molecule/__tests__/parser.test.ts +0 -155
- package/src/widgets/molecule/molecule-drawing.ts +0 -173
- package/src/widgets/molecule/molecule-layout.ts +0 -362
- package/src/widgets/molecule/smiles-parser.ts +0 -296
- package/src/widgets/molecule.tsx +0 -172
- package/src/widgets/number-line.tsx +0 -862
- package/src/widgets/numeric-input.tsx +0 -576
- package/src/widgets/orderer.tsx +0 -786
- package/src/widgets/passage/__tests__/passage-markdown.test.ts +0 -376
- package/src/widgets/passage/passage-markdown.tsx +0 -475
- package/src/widgets/passage-ref-target.tsx +0 -76
- package/src/widgets/passage-ref.tsx +0 -213
- package/src/widgets/passage.tsx +0 -582
- package/src/widgets/plotter.tsx +0 -1194
- package/src/widgets/radio/__stories__/base-radio.stories.tsx +0 -196
- package/src/widgets/radio/__stories__/choice-icon.stories.tsx +0 -166
- package/src/widgets/radio/__stories__/choice-none-above.stories.tsx +0 -40
- package/src/widgets/radio/__stories__/choice.stories.tsx +0 -137
- package/src/widgets/radio/__stories__/focus-ring.stories.tsx +0 -43
- package/src/widgets/radio/__stories__/option-status.stories.tsx +0 -103
- package/src/widgets/radio/base-radio.tsx +0 -511
- package/src/widgets/radio/choice-a11y-text.ts +0 -61
- package/src/widgets/radio/choice-icon/choice-icon.tsx +0 -218
- package/src/widgets/radio/choice-icon/cross-out-line.tsx +0 -51
- package/src/widgets/radio/choice-icon/shared-styles.ts +0 -15
- package/src/widgets/radio/choice-none-above.tsx +0 -56
- package/src/widgets/radio/choice.jsx.fixture.ts +0 -45
- package/src/widgets/radio/choice.tsx +0 -359
- package/src/widgets/radio/focus-ring.tsx +0 -57
- package/src/widgets/radio/option-status.tsx +0 -98
- package/src/widgets/radio/util.test.ts +0 -18
- package/src/widgets/radio/util.ts +0 -31
- package/src/widgets/radio/widget.tsx +0 -520
- package/src/widgets/radio.ts +0 -157
- package/src/widgets/reaction-diagram.tsx +0 -144
- package/src/widgets/sequence.tsx +0 -142
- package/src/widgets/simulator.tsx +0 -829
- package/src/widgets/sorter.tsx +0 -155
- package/src/widgets/table.tsx +0 -393
- package/src/widgets/unit.tsx +0 -374
- package/src/widgets/video-transcript-link.test.tsx +0 -213
- package/src/widgets/video-transcript-link.tsx +0 -84
- package/src/widgets/video.tsx +0 -135
- package/src/widgets.ts +0 -489
- package/src/zoom.ts +0 -555
- package/tsconfig-build.json +0 -26
- package/tsconfig-build.tsbuildinfo +0 -1
package/src/util/graphie.ts
DELETED
|
@@ -1,1566 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @babel/no-invalid-this */
|
|
2
|
-
import {
|
|
3
|
-
point as kpoint,
|
|
4
|
-
vector as kvector,
|
|
5
|
-
number as knumber,
|
|
6
|
-
} from "@khanacademy/kmath";
|
|
7
|
-
import {entries} from "@khanacademy/wonder-stuff-core";
|
|
8
|
-
import $ from "jquery";
|
|
9
|
-
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
10
|
-
import Raphael from "raphael";
|
|
11
|
-
|
|
12
|
-
// Minify Raphael ourselves because IE8 has a problem with the 1.5.2 minified
|
|
13
|
-
// release
|
|
14
|
-
// http://groups.google.com/group/raphaeljs/browse_thread/thread/c34c75ad8d431544
|
|
15
|
-
|
|
16
|
-
import {Errors, Log} from "../logging/log";
|
|
17
|
-
import {PerseusError} from "../perseus-error";
|
|
18
|
-
|
|
19
|
-
import KhanColors from "./colors";
|
|
20
|
-
import {DrawingTransform} from "./drawing-transform";
|
|
21
|
-
import {GraphBounds} from "./graph-bounds";
|
|
22
|
-
import KhanMath from "./math";
|
|
23
|
-
import Tex from "./tex";
|
|
24
|
-
|
|
25
|
-
import type {Interval} from "./interval";
|
|
26
|
-
import type {Coord} from "../interactive2/types";
|
|
27
|
-
|
|
28
|
-
const {processMath} = Tex;
|
|
29
|
-
|
|
30
|
-
export function polar(r: number | Coord, th: number): Coord {
|
|
31
|
-
if (typeof r === "number") {
|
|
32
|
-
r = [r, r];
|
|
33
|
-
}
|
|
34
|
-
th = (th * Math.PI) / 180;
|
|
35
|
-
return [r[0] * Math.cos(th), r[1] * Math.sin(th)];
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
interface RaphaelElement {
|
|
39
|
-
type: string;
|
|
40
|
-
attrs: Record<string, any>;
|
|
41
|
-
node: {
|
|
42
|
-
style: {
|
|
43
|
-
shapeRendering: "crispEdges" | "geometricPrecision";
|
|
44
|
-
};
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
type PositionedShape = {wrapper: HTMLDivElement; visibleShape: RaphaelElement};
|
|
49
|
-
|
|
50
|
-
export type StyleParams = {
|
|
51
|
-
fill?: string;
|
|
52
|
-
labelDistance?: number;
|
|
53
|
-
opacity?: number;
|
|
54
|
-
step?: Coord;
|
|
55
|
-
stroke?: string;
|
|
56
|
-
strokeWidth?: number;
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
type LabelPosition =
|
|
60
|
-
| "center"
|
|
61
|
-
| "above"
|
|
62
|
-
| "below"
|
|
63
|
-
| "right"
|
|
64
|
-
| "left"
|
|
65
|
-
| "above right"
|
|
66
|
-
| "above left"
|
|
67
|
-
| "below right"
|
|
68
|
-
| "below left";
|
|
69
|
-
|
|
70
|
-
interface LabelMethod {
|
|
71
|
-
(point: Coord, text: string, position: LabelPosition): any;
|
|
72
|
-
(
|
|
73
|
-
point: Coord,
|
|
74
|
-
text: string,
|
|
75
|
-
position: LabelPosition,
|
|
76
|
-
renderTex: boolean,
|
|
77
|
-
): any;
|
|
78
|
-
(
|
|
79
|
-
point: Coord,
|
|
80
|
-
text: string,
|
|
81
|
-
position: LabelPosition,
|
|
82
|
-
style: StyleParams,
|
|
83
|
-
): any;
|
|
84
|
-
(
|
|
85
|
-
point: Coord,
|
|
86
|
-
text: string,
|
|
87
|
-
position: LabelPosition,
|
|
88
|
-
renderTex: boolean,
|
|
89
|
-
style: StyleParams,
|
|
90
|
-
): any;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export class Graphie {
|
|
94
|
-
el: Element;
|
|
95
|
-
#bounds?: GraphBounds;
|
|
96
|
-
#drawingTransform?: DrawingTransform;
|
|
97
|
-
raphael?: any;
|
|
98
|
-
isMobile = false;
|
|
99
|
-
// Set up some reasonable defaults
|
|
100
|
-
currentStyle: any = {
|
|
101
|
-
"stroke-width": 2,
|
|
102
|
-
fill: "none",
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
range?: [Interval, Interval];
|
|
106
|
-
scale?: Coord;
|
|
107
|
-
dimensions?: Coord;
|
|
108
|
-
// TODO(benchristel): xpixels and ypixels are never used by this class, but
|
|
109
|
-
// other code reaches in to access them :(
|
|
110
|
-
// Refactor to accessor methods that look at this.dimensions.
|
|
111
|
-
xpixels?: number;
|
|
112
|
-
ypixels?: number;
|
|
113
|
-
|
|
114
|
-
constructor(el: Element) {
|
|
115
|
-
this.el = el;
|
|
116
|
-
$(el).css("position", "relative");
|
|
117
|
-
this.raphael = Raphael(el);
|
|
118
|
-
|
|
119
|
-
// For a sometimes-reproducible IE8 bug; doesn't affect SVG browsers at all
|
|
120
|
-
$(el).children("div").css("position", "absolute");
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
init(options: {
|
|
124
|
-
range: [Interval, Interval];
|
|
125
|
-
scale?: number | Coord;
|
|
126
|
-
isMobile?: boolean;
|
|
127
|
-
}) {
|
|
128
|
-
let scale = options.scale || [40, 40];
|
|
129
|
-
scale = typeof scale === "number" ? [scale, scale] : scale;
|
|
130
|
-
|
|
131
|
-
// TODO(jeremy): This can be removed once we're confident that callers
|
|
132
|
-
// obey the Typescript types.
|
|
133
|
-
if (options.range == null) {
|
|
134
|
-
throw new PerseusError(
|
|
135
|
-
"range should be specified in graph init",
|
|
136
|
-
Errors.Internal,
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
this.#bounds = new GraphBounds(...options.range);
|
|
141
|
-
|
|
142
|
-
this.#drawingTransform = new DrawingTransform(
|
|
143
|
-
this.raphael,
|
|
144
|
-
scale,
|
|
145
|
-
this.bounds(),
|
|
146
|
-
);
|
|
147
|
-
|
|
148
|
-
const [w, h] = this.drawingTransform().canvasDimensions();
|
|
149
|
-
|
|
150
|
-
$(this.el).css({
|
|
151
|
-
width: w,
|
|
152
|
-
height: h,
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
this.range = options.range;
|
|
156
|
-
this.scale = scale;
|
|
157
|
-
// TODO(benchristel): I don't think dimensions is used. Can we
|
|
158
|
-
// remove it?
|
|
159
|
-
this.dimensions = [w, h];
|
|
160
|
-
this.xpixels = w;
|
|
161
|
-
this.ypixels = h;
|
|
162
|
-
|
|
163
|
-
this.isMobile = options.isMobile ?? false;
|
|
164
|
-
|
|
165
|
-
return this;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Initializes graphie settings for a graph and draws the basic graph
|
|
169
|
-
// features (axes, grid, tick marks, and axis labels)
|
|
170
|
-
// Options expected are:
|
|
171
|
-
// - range: [[a, b], [c, d]] or [a, b]
|
|
172
|
-
// - scale: [a, b] or number
|
|
173
|
-
// - gridOpacity: number (0 - 1)
|
|
174
|
-
// - gridStep: [a, b] or number (relative to units)
|
|
175
|
-
// - tickStep: [a, b] or number (relative to grid steps)
|
|
176
|
-
// - tickLen: [a, b] or number (in pixels)
|
|
177
|
-
// - labelStep: [a, b] or number (relative to tick steps)
|
|
178
|
-
// - yLabelFormat: fn to format label string for y-axis
|
|
179
|
-
// - xLabelFormat: fn to format label string for x-axis
|
|
180
|
-
graphInit(options: {
|
|
181
|
-
range?: [Interval, Interval] | Coord;
|
|
182
|
-
grid?: boolean;
|
|
183
|
-
gridRange?: [Interval, Interval] | Coord;
|
|
184
|
-
scale: Coord | number;
|
|
185
|
-
axes?: boolean;
|
|
186
|
-
axisArrows?: "<->" | "->" | true | "";
|
|
187
|
-
axisOpacity?: number;
|
|
188
|
-
axisCenter?: Coord;
|
|
189
|
-
axisLabels?: [string, string] | false;
|
|
190
|
-
gridOpacity?: number;
|
|
191
|
-
gridStep?: Coord | number;
|
|
192
|
-
ticks?: boolean;
|
|
193
|
-
tickStep?: Coord | number;
|
|
194
|
-
tickLen?: Coord | number;
|
|
195
|
-
tickOpacity?: number;
|
|
196
|
-
labels?: boolean;
|
|
197
|
-
labelStep?: number;
|
|
198
|
-
labelOpacity?: number;
|
|
199
|
-
labelFormat?: (a: number) => string;
|
|
200
|
-
yLabelFormat?: (y: number) => string;
|
|
201
|
-
xLabelFormat?: (x: number) => string;
|
|
202
|
-
unityLabels?: boolean | [boolean, boolean];
|
|
203
|
-
isMobile?: boolean;
|
|
204
|
-
}) {
|
|
205
|
-
options = options || {};
|
|
206
|
-
|
|
207
|
-
for (const [prop, val] of entries(options)) {
|
|
208
|
-
// allow options to be specified by a single number for shorthand if
|
|
209
|
-
// the horizontal and vertical components are the same
|
|
210
|
-
if (
|
|
211
|
-
!prop.match(/.*Opacity$/) &&
|
|
212
|
-
prop !== "range" &&
|
|
213
|
-
typeof val === "number"
|
|
214
|
-
) {
|
|
215
|
-
options[prop] = [val, val];
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// allow symmetric ranges to be specified by the absolute values
|
|
219
|
-
if (prop === "range" || prop === "gridRange") {
|
|
220
|
-
options[prop] = normalizeRange(options[prop]);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
const range = normalizeRange(
|
|
225
|
-
options.range || [
|
|
226
|
-
[-10, 10],
|
|
227
|
-
[-10, 10],
|
|
228
|
-
],
|
|
229
|
-
);
|
|
230
|
-
const gridRange = normalizeRange(options.gridRange || range);
|
|
231
|
-
const scale = options.scale || [20, 20];
|
|
232
|
-
const grid = options.grid != null ? options.grid : true;
|
|
233
|
-
const gridOpacity = options.gridOpacity || 0.1;
|
|
234
|
-
const gridStep = toPair(options.gridStep || [1, 1]);
|
|
235
|
-
const axes = options.axes != null ? options.axes : true;
|
|
236
|
-
const axisArrows = options.axisArrows || "";
|
|
237
|
-
const axisOpacity = options.axisOpacity || 1.0;
|
|
238
|
-
const axisCenter = options.axisCenter || [
|
|
239
|
-
Math.min(Math.max(range[0][0], 0), range[0][1]),
|
|
240
|
-
Math.min(Math.max(range[1][0], 0), range[1][1]),
|
|
241
|
-
];
|
|
242
|
-
const axisLabels =
|
|
243
|
-
options.axisLabels != null ? options.axisLabels : false;
|
|
244
|
-
const ticks = options.ticks != null ? options.ticks : true;
|
|
245
|
-
const tickStep = options.tickStep || [2, 2];
|
|
246
|
-
const tickLen = options.tickLen || [5, 5];
|
|
247
|
-
const tickOpacity = options.tickOpacity || 1.0;
|
|
248
|
-
const labels = options.labels || options.labelStep || false;
|
|
249
|
-
const labelStep = options.labelStep || [1, 1];
|
|
250
|
-
const labelOpacity = options.labelOpacity || 1.0;
|
|
251
|
-
let unityLabels = options.unityLabels || false;
|
|
252
|
-
const labelFormat =
|
|
253
|
-
options.labelFormat ||
|
|
254
|
-
function (a: any) {
|
|
255
|
-
return a;
|
|
256
|
-
};
|
|
257
|
-
let xLabelFormat = options.xLabelFormat || labelFormat;
|
|
258
|
-
let yLabelFormat = options.yLabelFormat || labelFormat;
|
|
259
|
-
const realRange = [
|
|
260
|
-
[
|
|
261
|
-
range[0][0] - (range[0][0] > 0 ? 1 : 0),
|
|
262
|
-
range[0][1] + (range[0][1] < 0 ? 1 : 0),
|
|
263
|
-
],
|
|
264
|
-
[
|
|
265
|
-
range[1][0] - (range[1][0] > 0 ? 1 : 0),
|
|
266
|
-
range[1][1] + (range[1][1] < 0 ? 1 : 0),
|
|
267
|
-
],
|
|
268
|
-
] as [Interval, Interval];
|
|
269
|
-
|
|
270
|
-
if (!Array.isArray(unityLabels)) {
|
|
271
|
-
unityLabels = [unityLabels, unityLabels];
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
const minusIgnorer = function (lf: (a: number) => string) {
|
|
275
|
-
return function (a: number) {
|
|
276
|
-
return (lf(a) + "").replace(/-(\d)/g, "\\llap{-}$1");
|
|
277
|
-
};
|
|
278
|
-
};
|
|
279
|
-
|
|
280
|
-
xLabelFormat = minusIgnorer(xLabelFormat);
|
|
281
|
-
yLabelFormat = minusIgnorer(yLabelFormat);
|
|
282
|
-
|
|
283
|
-
this.init({
|
|
284
|
-
range: realRange,
|
|
285
|
-
scale: scale,
|
|
286
|
-
isMobile: options.isMobile,
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
// draw grid
|
|
290
|
-
if (grid) {
|
|
291
|
-
this.grid(gridRange[0], gridRange[1], {
|
|
292
|
-
stroke: options.isMobile ? KhanColors.GRAY_C : "#000000",
|
|
293
|
-
opacity: options.isMobile ? 1 : gridOpacity,
|
|
294
|
-
step: gridStep,
|
|
295
|
-
strokeWidth: options.isMobile ? 1 : 2,
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// draw axes
|
|
300
|
-
if (axes) {
|
|
301
|
-
// this is a slight hack until <-> arrowheads work
|
|
302
|
-
if (axisArrows === "<->" || axisArrows === true) {
|
|
303
|
-
const thisGraphie = this;
|
|
304
|
-
this.style(
|
|
305
|
-
{
|
|
306
|
-
stroke: options.isMobile
|
|
307
|
-
? KhanColors.GRAY_G
|
|
308
|
-
: "#000000",
|
|
309
|
-
opacity: options.isMobile ? 1 : axisOpacity,
|
|
310
|
-
strokeWidth: options.isMobile ? 1 : 2,
|
|
311
|
-
arrows: "->",
|
|
312
|
-
},
|
|
313
|
-
function () {
|
|
314
|
-
if (range[1][0] < 0 && range[1][1] > 0) {
|
|
315
|
-
thisGraphie.path([
|
|
316
|
-
axisCenter,
|
|
317
|
-
[gridRange[0][0], axisCenter[1]],
|
|
318
|
-
]);
|
|
319
|
-
thisGraphie.path([
|
|
320
|
-
axisCenter,
|
|
321
|
-
[gridRange[0][1], axisCenter[1]],
|
|
322
|
-
]);
|
|
323
|
-
}
|
|
324
|
-
if (range[0][0] < 0 && range[0][1] > 0) {
|
|
325
|
-
thisGraphie.path([
|
|
326
|
-
axisCenter,
|
|
327
|
-
[axisCenter[0], gridRange[1][0]],
|
|
328
|
-
]);
|
|
329
|
-
thisGraphie.path([
|
|
330
|
-
axisCenter,
|
|
331
|
-
[axisCenter[0], gridRange[1][1]],
|
|
332
|
-
]);
|
|
333
|
-
}
|
|
334
|
-
},
|
|
335
|
-
);
|
|
336
|
-
|
|
337
|
-
// also, we don't support "<-" arrows yet, but why you
|
|
338
|
-
// would want that on your graph is beyond me.
|
|
339
|
-
} else if (axisArrows === "->" || axisArrows === "") {
|
|
340
|
-
const thisGraphie = this;
|
|
341
|
-
this.style(
|
|
342
|
-
{
|
|
343
|
-
stroke: "#000000",
|
|
344
|
-
opacity: axisOpacity,
|
|
345
|
-
strokeWidth: 2,
|
|
346
|
-
arrows: axisArrows,
|
|
347
|
-
},
|
|
348
|
-
function () {
|
|
349
|
-
thisGraphie.path([
|
|
350
|
-
[gridRange[0][0], axisCenter[1]],
|
|
351
|
-
[gridRange[0][1], axisCenter[1]],
|
|
352
|
-
]);
|
|
353
|
-
thisGraphie.path([
|
|
354
|
-
[axisCenter[0], gridRange[1][0]],
|
|
355
|
-
[axisCenter[0], gridRange[1][1]],
|
|
356
|
-
]);
|
|
357
|
-
},
|
|
358
|
-
);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
if (axisLabels && axisLabels.length === 2) {
|
|
362
|
-
this.label(
|
|
363
|
-
[gridRange[0][1], axisCenter[1]],
|
|
364
|
-
axisLabels[0],
|
|
365
|
-
"right",
|
|
366
|
-
);
|
|
367
|
-
this.label(
|
|
368
|
-
[axisCenter[0], gridRange[1][1]],
|
|
369
|
-
axisLabels[1],
|
|
370
|
-
"above",
|
|
371
|
-
);
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// draw tick marks
|
|
376
|
-
if (ticks) {
|
|
377
|
-
const halfWidthTicks = options.isMobile;
|
|
378
|
-
const thisGraphie = this;
|
|
379
|
-
this.style(
|
|
380
|
-
{
|
|
381
|
-
stroke: options.isMobile ? KhanColors.GRAY_G : "#000000",
|
|
382
|
-
opacity: options.isMobile ? 1 : tickOpacity,
|
|
383
|
-
strokeWidth: 1,
|
|
384
|
-
},
|
|
385
|
-
() => {
|
|
386
|
-
// horizontal axis
|
|
387
|
-
let step = gridStep[0] * tickStep[0];
|
|
388
|
-
let len = tickLen[0] / scale[1];
|
|
389
|
-
let start = gridRange[0][0];
|
|
390
|
-
let stop = gridRange[0][1];
|
|
391
|
-
|
|
392
|
-
if (range[1][0] < 0 && range[1][1] > 0) {
|
|
393
|
-
for (
|
|
394
|
-
let x = step + axisCenter[0];
|
|
395
|
-
x <= stop;
|
|
396
|
-
x += step
|
|
397
|
-
) {
|
|
398
|
-
if (x < stop || !axisArrows) {
|
|
399
|
-
thisGraphie.line(
|
|
400
|
-
[x, -len + axisCenter[1]],
|
|
401
|
-
[
|
|
402
|
-
x,
|
|
403
|
-
halfWidthTicks
|
|
404
|
-
? 0
|
|
405
|
-
: len + axisCenter[1],
|
|
406
|
-
],
|
|
407
|
-
);
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
for (
|
|
412
|
-
let x = -step + axisCenter[0];
|
|
413
|
-
x >= start;
|
|
414
|
-
x -= step
|
|
415
|
-
) {
|
|
416
|
-
if (x > start || !axisArrows) {
|
|
417
|
-
thisGraphie.line(
|
|
418
|
-
[x, -len + axisCenter[1]],
|
|
419
|
-
[
|
|
420
|
-
x,
|
|
421
|
-
halfWidthTicks
|
|
422
|
-
? 0
|
|
423
|
-
: len + axisCenter[1],
|
|
424
|
-
],
|
|
425
|
-
);
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
// vertical axis
|
|
431
|
-
step = gridStep[1] * tickStep[1];
|
|
432
|
-
len = tickLen[1] / scale[0];
|
|
433
|
-
start = gridRange[1][0];
|
|
434
|
-
stop = gridRange[1][1];
|
|
435
|
-
|
|
436
|
-
if (range[0][0] < 0 && range[0][1] > 0) {
|
|
437
|
-
for (
|
|
438
|
-
let y = step + axisCenter[1];
|
|
439
|
-
y <= stop;
|
|
440
|
-
y += step
|
|
441
|
-
) {
|
|
442
|
-
if (y < stop || !axisArrows) {
|
|
443
|
-
this.line(
|
|
444
|
-
[-len + axisCenter[0], y],
|
|
445
|
-
[
|
|
446
|
-
halfWidthTicks
|
|
447
|
-
? 0
|
|
448
|
-
: len + axisCenter[0],
|
|
449
|
-
y,
|
|
450
|
-
],
|
|
451
|
-
);
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
for (
|
|
456
|
-
let y = -step + axisCenter[1];
|
|
457
|
-
y >= start;
|
|
458
|
-
y -= step
|
|
459
|
-
) {
|
|
460
|
-
if (y > start || !axisArrows) {
|
|
461
|
-
this.line(
|
|
462
|
-
[-len + axisCenter[0], y],
|
|
463
|
-
[
|
|
464
|
-
halfWidthTicks
|
|
465
|
-
? 0
|
|
466
|
-
: len + axisCenter[0],
|
|
467
|
-
y,
|
|
468
|
-
],
|
|
469
|
-
);
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
},
|
|
474
|
-
);
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
// draw axis labels
|
|
478
|
-
if (labels) {
|
|
479
|
-
const thisGraphie = this;
|
|
480
|
-
this.style(
|
|
481
|
-
{
|
|
482
|
-
stroke: options.isMobile ? KhanColors.GRAY_G : "#000000",
|
|
483
|
-
opacity: options.isMobile ? 1 : labelOpacity,
|
|
484
|
-
},
|
|
485
|
-
function () {
|
|
486
|
-
// horizontal axis
|
|
487
|
-
let step = gridStep[0] * tickStep[0] * labelStep[0];
|
|
488
|
-
let start = gridRange[0][0];
|
|
489
|
-
let stop = gridRange[0][1];
|
|
490
|
-
const xAxisPosition = axisCenter[0] < 0 ? "above" : "below";
|
|
491
|
-
const yAxisPosition = axisCenter[0] < 0 ? "right" : "left";
|
|
492
|
-
const xShowZero =
|
|
493
|
-
axisCenter[0] === 0 && axisCenter[1] !== 0;
|
|
494
|
-
const yShowZero =
|
|
495
|
-
axisCenter[0] !== 0 && axisCenter[1] === 0;
|
|
496
|
-
const axisOffCenter =
|
|
497
|
-
axisCenter[0] !== 0 || axisCenter[1] !== 0;
|
|
498
|
-
const showUnityX = unityLabels[0] || axisOffCenter;
|
|
499
|
-
const showUnityY = unityLabels[1] || axisOffCenter;
|
|
500
|
-
|
|
501
|
-
// positive x-axis
|
|
502
|
-
for (
|
|
503
|
-
let x = (xShowZero ? 0 : step) + axisCenter[0];
|
|
504
|
-
x <= stop;
|
|
505
|
-
x += step
|
|
506
|
-
) {
|
|
507
|
-
if (x < stop || !axisArrows) {
|
|
508
|
-
thisGraphie.label(
|
|
509
|
-
[x, axisCenter[1]],
|
|
510
|
-
xLabelFormat(x),
|
|
511
|
-
xAxisPosition,
|
|
512
|
-
);
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
// negative x-axis
|
|
517
|
-
for (
|
|
518
|
-
let x = -step * (showUnityX ? 1 : 2) + axisCenter[0];
|
|
519
|
-
x >= start;
|
|
520
|
-
x -= step
|
|
521
|
-
) {
|
|
522
|
-
if (x > start || !axisArrows) {
|
|
523
|
-
thisGraphie.label(
|
|
524
|
-
[x, axisCenter[1]],
|
|
525
|
-
xLabelFormat(x),
|
|
526
|
-
xAxisPosition,
|
|
527
|
-
);
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
step = gridStep[1] * tickStep[1] * labelStep[1];
|
|
532
|
-
start = gridRange[1][0];
|
|
533
|
-
stop = gridRange[1][1];
|
|
534
|
-
|
|
535
|
-
// positive y-axis
|
|
536
|
-
for (
|
|
537
|
-
let y = (yShowZero ? 0 : step) + axisCenter[1];
|
|
538
|
-
y <= stop;
|
|
539
|
-
y += step
|
|
540
|
-
) {
|
|
541
|
-
if (y < stop || !axisArrows) {
|
|
542
|
-
thisGraphie.label(
|
|
543
|
-
[axisCenter[0], y],
|
|
544
|
-
yLabelFormat(y),
|
|
545
|
-
yAxisPosition,
|
|
546
|
-
);
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
// negative y-axis
|
|
551
|
-
for (
|
|
552
|
-
let y = -step * (showUnityY ? 1 : 2) + axisCenter[1];
|
|
553
|
-
y >= start;
|
|
554
|
-
y -= step
|
|
555
|
-
) {
|
|
556
|
-
if (y > start || !axisArrows) {
|
|
557
|
-
thisGraphie.label(
|
|
558
|
-
[axisCenter[0], y],
|
|
559
|
-
yLabelFormat(y),
|
|
560
|
-
yAxisPosition,
|
|
561
|
-
);
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
},
|
|
565
|
-
);
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
private drawingTransform(): DrawingTransform {
|
|
570
|
-
if (this.#drawingTransform == null) {
|
|
571
|
-
throw new Error(
|
|
572
|
-
"Can't get drawingTransform of an uninitialized Graphie",
|
|
573
|
-
);
|
|
574
|
-
}
|
|
575
|
-
return this.#drawingTransform;
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
private bounds(): GraphBounds {
|
|
579
|
-
if (this.#bounds == null) {
|
|
580
|
-
throw new Error("Can't get bounds of an uninitialized Graphie");
|
|
581
|
-
}
|
|
582
|
-
return this.#bounds;
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
style<T>(attrs: any, fn?: (this: Graphie) => T): T | undefined {
|
|
586
|
-
const processed = this.processAttributes(attrs);
|
|
587
|
-
|
|
588
|
-
if (typeof fn === "function") {
|
|
589
|
-
const oldStyle = this.currentStyle;
|
|
590
|
-
this.currentStyle = {...this.currentStyle, ...processed};
|
|
591
|
-
const result = fn.call(this);
|
|
592
|
-
this.currentStyle = oldStyle;
|
|
593
|
-
return result;
|
|
594
|
-
}
|
|
595
|
-
Object.assign(this.currentStyle, processed);
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
grid(xr: Interval, yr: Interval, style?: StyleParams): unknown {
|
|
599
|
-
return this.withStyle(style, () => {
|
|
600
|
-
const step: any = this.currentStyle.step || [1, 1];
|
|
601
|
-
const set = this.raphael.set();
|
|
602
|
-
|
|
603
|
-
let x = step[0] * Math.ceil(xr[0] / step[0]);
|
|
604
|
-
for (; x <= xr[1]; x += step[0]) {
|
|
605
|
-
set.push(this.line([x, yr[0]], [x, yr[1]]));
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
let y = step[1] * Math.ceil(yr[0] / step[1]);
|
|
609
|
-
for (; y <= yr[1]; y += step[1]) {
|
|
610
|
-
set.push(this.line([xr[0], y], [xr[1], y]));
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
return set;
|
|
614
|
-
});
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
arc(
|
|
618
|
-
center: Coord,
|
|
619
|
-
radius: Coord,
|
|
620
|
-
startAngle: number,
|
|
621
|
-
endAngle: number,
|
|
622
|
-
sector: boolean,
|
|
623
|
-
style?: StyleParams,
|
|
624
|
-
): RaphaelElement {
|
|
625
|
-
return this.withStyle(style, () => {
|
|
626
|
-
startAngle = ((startAngle % 360) + 360) % 360;
|
|
627
|
-
endAngle = ((endAngle % 360) + 360) % 360;
|
|
628
|
-
|
|
629
|
-
const cent = this.scalePoint(center);
|
|
630
|
-
const radii = this.scaleVector(radius);
|
|
631
|
-
const startVector = polar(radius, startAngle);
|
|
632
|
-
const endVector = polar(radius, endAngle);
|
|
633
|
-
|
|
634
|
-
// We round the coordinates to make testing easier, because trig
|
|
635
|
-
// operations return unround numbers.
|
|
636
|
-
// TODO(benchristel): move rounding to scalePoint?
|
|
637
|
-
const round = (x) => knumber.round(x, 6);
|
|
638
|
-
const startPoint = this.scalePoint([
|
|
639
|
-
round(center[0] + startVector[0]),
|
|
640
|
-
round(center[1] + startVector[1]),
|
|
641
|
-
]);
|
|
642
|
-
const endPoint = this.scalePoint([
|
|
643
|
-
round(center[0] + endVector[0]),
|
|
644
|
-
round(center[1] + endVector[1]),
|
|
645
|
-
]);
|
|
646
|
-
|
|
647
|
-
const largeAngle =
|
|
648
|
-
(((endAngle - startAngle) % 360) + 360) % 360 > 180;
|
|
649
|
-
|
|
650
|
-
return this.raphael.path(
|
|
651
|
-
"M" +
|
|
652
|
-
startPoint.join(" ") +
|
|
653
|
-
"A" +
|
|
654
|
-
radii.join(" ") +
|
|
655
|
-
" 0 " + // ellipse rotation
|
|
656
|
-
(largeAngle ? 1 : 0) +
|
|
657
|
-
" 0 " + // sweep flag
|
|
658
|
-
endPoint.join(" ") +
|
|
659
|
-
(sector ? "L" + cent.join(" ") + "z" : ""),
|
|
660
|
-
);
|
|
661
|
-
});
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
circle(center: Coord, radius: number, style?: StyleParams) {
|
|
665
|
-
return this.withStyle(style, () =>
|
|
666
|
-
this.raphael.ellipse(
|
|
667
|
-
...this.scalePoint(center),
|
|
668
|
-
...this.scaleVector([radius, radius]),
|
|
669
|
-
),
|
|
670
|
-
);
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
// (x, y) is coordinate of bottom left corner
|
|
674
|
-
rect(x, y, width, height, style?: StyleParams) {
|
|
675
|
-
return this.withStyle(style, () => {
|
|
676
|
-
// Raphael needs (x, y) to be coordinate of upper left corner
|
|
677
|
-
const corner = this.scalePoint([x, y + height]);
|
|
678
|
-
const dims = this.scaleVector([width, height]);
|
|
679
|
-
const elem = this.raphael.rect(...corner.concat(dims));
|
|
680
|
-
|
|
681
|
-
if (this.isMobile) {
|
|
682
|
-
elem.node.style.shapeRendering = "crispEdges";
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
return elem;
|
|
686
|
-
});
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
ellipse(center, radii, style?: StyleParams) {
|
|
690
|
-
return this.withStyle(style, () =>
|
|
691
|
-
this.raphael.ellipse(
|
|
692
|
-
...this.scalePoint(center).concat(this.scaleVector(radii)),
|
|
693
|
-
),
|
|
694
|
-
);
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
fixedEllipse(
|
|
698
|
-
center: number | Coord,
|
|
699
|
-
// Different type than Coord, this is radiusX, radiusY
|
|
700
|
-
radii: number | [number, number],
|
|
701
|
-
maxScale: number,
|
|
702
|
-
padding: number,
|
|
703
|
-
style?: StyleParams,
|
|
704
|
-
): PositionedShape {
|
|
705
|
-
return this.withStyle(style, () => {
|
|
706
|
-
// Scale point and radius
|
|
707
|
-
const scaledPoint = this.scalePoint(center);
|
|
708
|
-
const scaledRadii = this.scaleVector(radii);
|
|
709
|
-
|
|
710
|
-
const width = 2 * scaledRadii[0] * maxScale + padding;
|
|
711
|
-
const height = 2 * scaledRadii[1] * maxScale + padding;
|
|
712
|
-
|
|
713
|
-
// Calculate absolute left, top
|
|
714
|
-
const left = scaledPoint[0] - width / 2;
|
|
715
|
-
const top = scaledPoint[1] - height / 2;
|
|
716
|
-
|
|
717
|
-
// Wrap in <div>
|
|
718
|
-
const wrapper = document.createElement("div");
|
|
719
|
-
$(wrapper).css({
|
|
720
|
-
position: "absolute",
|
|
721
|
-
width: width + "px",
|
|
722
|
-
height: height + "px",
|
|
723
|
-
left: left + "px",
|
|
724
|
-
top: top + "px",
|
|
725
|
-
});
|
|
726
|
-
// wrapper.setAttribute("data-graphie-type", "ellipse");
|
|
727
|
-
|
|
728
|
-
// Create Raphael canvas
|
|
729
|
-
const localRaphael = Raphael(wrapper, width, height);
|
|
730
|
-
const visibleShape = localRaphael.ellipse(
|
|
731
|
-
width / 2,
|
|
732
|
-
height / 2,
|
|
733
|
-
scaledRadii[0],
|
|
734
|
-
scaledRadii[1],
|
|
735
|
-
);
|
|
736
|
-
|
|
737
|
-
return {
|
|
738
|
-
wrapper: wrapper,
|
|
739
|
-
visibleShape: visibleShape,
|
|
740
|
-
};
|
|
741
|
-
});
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
private unstyledPath(points: Coord[]): RaphaelElement {
|
|
745
|
-
const p = this.raphael.path(this.svgPath(points));
|
|
746
|
-
p.graphiePath = points;
|
|
747
|
-
return p;
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
path(points: Coord[], style?: StyleParams): RaphaelElement {
|
|
751
|
-
return this.withStyle(style, () => {
|
|
752
|
-
return this.unstyledPath(points);
|
|
753
|
-
});
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
// fixedPath is a stub that gets overwritten with a function from
|
|
757
|
-
// drawingTools in createGraphie
|
|
758
|
-
fixedPath(
|
|
759
|
-
points: Coord[],
|
|
760
|
-
center: Coord | null,
|
|
761
|
-
createPath: (scaledPoints: Coord[]) => string,
|
|
762
|
-
): PositionedShape {
|
|
763
|
-
points = points.map(this.scalePoint);
|
|
764
|
-
center = center ? this.scalePoint(center) : null;
|
|
765
|
-
createPath = createPath || this.svgPath;
|
|
766
|
-
|
|
767
|
-
// Compute bounding box
|
|
768
|
-
const xs = points.map((p) => p[0]);
|
|
769
|
-
const ys = points.map((p) => p[1]);
|
|
770
|
-
const pathLeft = Math.min(...xs);
|
|
771
|
-
const pathRight = Math.max(...xs);
|
|
772
|
-
const pathTop = Math.min(...ys);
|
|
773
|
-
const pathBottom = Math.max(...ys);
|
|
774
|
-
|
|
775
|
-
// Apply padding to line
|
|
776
|
-
const padding: Coord = [4, 4];
|
|
777
|
-
|
|
778
|
-
// Calculate and apply additional offset
|
|
779
|
-
const topLeftOfBoundingBox: Coord = [pathLeft, pathTop];
|
|
780
|
-
|
|
781
|
-
// Apply padding and offset to points to convert from
|
|
782
|
-
// canvas coordinates to pixel coordinates relative to bounding box
|
|
783
|
-
points = points.map(function (point) {
|
|
784
|
-
return kvector.add(
|
|
785
|
-
kvector.subtract(point, topLeftOfBoundingBox),
|
|
786
|
-
kvector.scale(padding, 0.5),
|
|
787
|
-
);
|
|
788
|
-
});
|
|
789
|
-
|
|
790
|
-
// Calculate <div> dimensions
|
|
791
|
-
const width = pathRight - pathLeft + padding[0];
|
|
792
|
-
const height = pathBottom - pathTop + padding[1];
|
|
793
|
-
const left = topLeftOfBoundingBox[0] - padding[0] / 2;
|
|
794
|
-
const top = topLeftOfBoundingBox[1] - padding[1] / 2;
|
|
795
|
-
|
|
796
|
-
// Create <div>
|
|
797
|
-
const wrapper = document.createElement("div");
|
|
798
|
-
$(wrapper).css({
|
|
799
|
-
position: "absolute",
|
|
800
|
-
width: width + "px",
|
|
801
|
-
height: height + "px",
|
|
802
|
-
left: left + "px",
|
|
803
|
-
top: top + "px",
|
|
804
|
-
// If user specified a center, set it
|
|
805
|
-
// NOTE(kevinb): jQuery doesn't like that `transformOrigin `could be `null`
|
|
806
|
-
// so we cast to `any` here.
|
|
807
|
-
transformOrigin: center
|
|
808
|
-
? width / 2 +
|
|
809
|
-
center[0] +
|
|
810
|
-
"px " +
|
|
811
|
-
(height / 2 + center[1]) +
|
|
812
|
-
"px"
|
|
813
|
-
: null,
|
|
814
|
-
} as any);
|
|
815
|
-
|
|
816
|
-
// Create Raphael canvas
|
|
817
|
-
const localRaphael = Raphael(wrapper, width, height);
|
|
818
|
-
|
|
819
|
-
// Calculate path
|
|
820
|
-
const visibleShape = localRaphael.path(createPath(points));
|
|
821
|
-
|
|
822
|
-
return {
|
|
823
|
-
wrapper: wrapper,
|
|
824
|
-
visibleShape: visibleShape,
|
|
825
|
-
};
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
scaledPath(points: Coord[], style?: StyleParams): RaphaelElement {
|
|
829
|
-
return this.withStyle(style, () => {
|
|
830
|
-
const p = this.raphael.path(
|
|
831
|
-
this.svgPath(points, /* alreadyScaled */ true),
|
|
832
|
-
);
|
|
833
|
-
p.graphiePath = points;
|
|
834
|
-
return p;
|
|
835
|
-
});
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
line(start: Coord, end: Coord, style?: StyleParams): RaphaelElement {
|
|
839
|
-
return this.withStyle(style, () => {
|
|
840
|
-
const l = this.unstyledPath([start, end]);
|
|
841
|
-
|
|
842
|
-
if (this.isMobile) {
|
|
843
|
-
l.node.style.shapeRendering = "crispEdges";
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
return l;
|
|
847
|
-
});
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
parabola(
|
|
851
|
-
a: number,
|
|
852
|
-
b: number,
|
|
853
|
-
c: number,
|
|
854
|
-
style?: StyleParams,
|
|
855
|
-
): RaphaelElement {
|
|
856
|
-
return this.withStyle(style, () =>
|
|
857
|
-
this.raphael.path(this.svgParabolaPath(a, b, c)),
|
|
858
|
-
);
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
fixedLine(start: Coord, end: Coord, thickness: number): PositionedShape {
|
|
862
|
-
// Apply padding to line
|
|
863
|
-
const padding: Coord = [thickness, thickness];
|
|
864
|
-
|
|
865
|
-
// Scale points to get values in pixels
|
|
866
|
-
start = this.scalePoint(start);
|
|
867
|
-
end = this.scalePoint(end);
|
|
868
|
-
|
|
869
|
-
// Calculate and apply additional offset
|
|
870
|
-
const extraOffset: Coord = [
|
|
871
|
-
Math.min(start[0], end[0]),
|
|
872
|
-
Math.min(start[1], end[1]),
|
|
873
|
-
];
|
|
874
|
-
|
|
875
|
-
// Apply padding and offset to start, end points
|
|
876
|
-
start = kvector.add(
|
|
877
|
-
kvector.subtract(start, extraOffset),
|
|
878
|
-
kvector.scale(padding, 0.5),
|
|
879
|
-
);
|
|
880
|
-
end = kvector.add(
|
|
881
|
-
kvector.subtract(end, extraOffset),
|
|
882
|
-
kvector.scale(padding, 0.5),
|
|
883
|
-
);
|
|
884
|
-
|
|
885
|
-
// Calculate <div> dimensions
|
|
886
|
-
const left = extraOffset[0] - padding[0] / 2;
|
|
887
|
-
const top = extraOffset[1] - padding[1] / 2;
|
|
888
|
-
const width = Math.abs(start[0] - end[0]) + padding[0];
|
|
889
|
-
const height = Math.abs(start[1] - end[1]) + padding[1];
|
|
890
|
-
|
|
891
|
-
// Create <div>
|
|
892
|
-
const wrapper = document.createElement("div");
|
|
893
|
-
$(wrapper).css({
|
|
894
|
-
position: "absolute",
|
|
895
|
-
width: width + "px",
|
|
896
|
-
height: height + "px",
|
|
897
|
-
left: left + "px",
|
|
898
|
-
top: top + "px",
|
|
899
|
-
// Outsiders should feel like the line's 'origin' (i.e., for
|
|
900
|
-
// rotation) is the starting point
|
|
901
|
-
transformOrigin: start[0] + "px " + start[1] + "px",
|
|
902
|
-
});
|
|
903
|
-
|
|
904
|
-
// Create Raphael canvas
|
|
905
|
-
const localRaphael = Raphael(wrapper, width, height);
|
|
906
|
-
|
|
907
|
-
// Calculate path
|
|
908
|
-
const path =
|
|
909
|
-
"M" + start[0] + " " + start[1] + " " + "L" + end[0] + " " + end[1];
|
|
910
|
-
const visibleShape = localRaphael.path(path);
|
|
911
|
-
visibleShape.graphiePath = [start, end];
|
|
912
|
-
|
|
913
|
-
return {
|
|
914
|
-
wrapper: wrapper,
|
|
915
|
-
visibleShape: visibleShape,
|
|
916
|
-
};
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
sinusoid(
|
|
920
|
-
a: number,
|
|
921
|
-
b: number,
|
|
922
|
-
c: number,
|
|
923
|
-
d: number,
|
|
924
|
-
style?: StyleParams,
|
|
925
|
-
): RaphaelElement {
|
|
926
|
-
return this.withStyle(style, () =>
|
|
927
|
-
// Plot a sinusoid of the form: f(x) = a * sin(b * x - c) + d
|
|
928
|
-
this.raphael.path(this.svgSinusoidPath(a, b, c, d)),
|
|
929
|
-
);
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
label: LabelMethod = (
|
|
933
|
-
point: Coord,
|
|
934
|
-
text: string,
|
|
935
|
-
direction:
|
|
936
|
-
| "center"
|
|
937
|
-
| "above"
|
|
938
|
-
| "below"
|
|
939
|
-
| "right"
|
|
940
|
-
| "left"
|
|
941
|
-
| "above right"
|
|
942
|
-
| "above left"
|
|
943
|
-
| "below right"
|
|
944
|
-
| "below left",
|
|
945
|
-
arg4?: boolean | StyleParams,
|
|
946
|
-
arg5?: StyleParams,
|
|
947
|
-
): any => {
|
|
948
|
-
const style = typeof arg4 === "object" ? arg4 : arg5;
|
|
949
|
-
const latex = typeof arg4 === "boolean" ? arg4 : true;
|
|
950
|
-
return this.withStyle(style, () => {
|
|
951
|
-
const $span = $("<span>").addClass("graphie-label");
|
|
952
|
-
|
|
953
|
-
const pad = this.currentStyle["label-distance"];
|
|
954
|
-
|
|
955
|
-
$span
|
|
956
|
-
.css({
|
|
957
|
-
position: "absolute",
|
|
958
|
-
padding: (pad != null ? pad : 7) + "px",
|
|
959
|
-
color: "black",
|
|
960
|
-
})
|
|
961
|
-
.data("labelDirection", direction)
|
|
962
|
-
.appendTo(this.el);
|
|
963
|
-
|
|
964
|
-
// @ts-expect-error - TS2339 - Property 'setPosition' does not exist on type 'JQuery<HTMLElement>'.
|
|
965
|
-
$span.setPosition = (point) => {
|
|
966
|
-
const scaledPoint = this.scalePoint(point);
|
|
967
|
-
$span.css({
|
|
968
|
-
left: scaledPoint[0],
|
|
969
|
-
top: scaledPoint[1],
|
|
970
|
-
});
|
|
971
|
-
};
|
|
972
|
-
|
|
973
|
-
// @ts-expect-error - TS2339 - Property 'setPosition' does not exist on type 'JQuery<HTMLElement>'.
|
|
974
|
-
$span.setPosition(point);
|
|
975
|
-
|
|
976
|
-
const span = $span[0];
|
|
977
|
-
|
|
978
|
-
// @ts-expect-error - TS2339 - Property 'processMath' does not exist on type 'JQuery<HTMLElement>'.
|
|
979
|
-
$span.processMath = function (math, force) {
|
|
980
|
-
processMath(span, math, force, function () {
|
|
981
|
-
const width = span.scrollWidth;
|
|
982
|
-
const height = span.scrollHeight;
|
|
983
|
-
setLabelMargins(span, [width, height]);
|
|
984
|
-
});
|
|
985
|
-
};
|
|
986
|
-
|
|
987
|
-
// @ts-expect-error - TS2339 - Property 'processText' does not exist on type 'JQuery<HTMLElement>'.
|
|
988
|
-
$span.processText = function (text: string) {
|
|
989
|
-
$span.html(text);
|
|
990
|
-
const width = span.scrollWidth;
|
|
991
|
-
const height = span.scrollHeight;
|
|
992
|
-
setLabelMargins(span, [width, height]);
|
|
993
|
-
};
|
|
994
|
-
|
|
995
|
-
if (latex) {
|
|
996
|
-
// @ts-expect-error - TS2339 - Property 'processMath' does not exist on type 'JQuery<HTMLElement>'.
|
|
997
|
-
$span.processMath(text, /* force */ false);
|
|
998
|
-
} else {
|
|
999
|
-
// @ts-expect-error - TS2339 - Property 'processText' does not exist on type 'JQuery<HTMLElement>'.
|
|
1000
|
-
$span.processText(text);
|
|
1001
|
-
}
|
|
1002
|
-
|
|
1003
|
-
return $span;
|
|
1004
|
-
});
|
|
1005
|
-
};
|
|
1006
|
-
|
|
1007
|
-
plotParametric(
|
|
1008
|
-
fn: (t: number) => Coord,
|
|
1009
|
-
range: Interval,
|
|
1010
|
-
style?: StyleParams,
|
|
1011
|
-
): RaphaelElement {
|
|
1012
|
-
return this.withStyle(style, () => {
|
|
1013
|
-
// We truncate to 500,000, since anything bigger causes
|
|
1014
|
-
// overflow in the firefox svg renderer. This is safe
|
|
1015
|
-
// since 500,000 is outside the viewport anyway. We
|
|
1016
|
-
// write these functions the way we do to handle undefined.
|
|
1017
|
-
const clip = (xy) => {
|
|
1018
|
-
if (Math.abs(xy[1]) > 500000) {
|
|
1019
|
-
return [xy[0], Math.min(Math.max(xy[1], -500000), 500000)];
|
|
1020
|
-
}
|
|
1021
|
-
return xy;
|
|
1022
|
-
};
|
|
1023
|
-
const clippedFn = (x) => clip(fn(x));
|
|
1024
|
-
|
|
1025
|
-
const min = range[0];
|
|
1026
|
-
const max = range[1];
|
|
1027
|
-
let step = (max - min) / (this.currentStyle["plot-points"] || 800);
|
|
1028
|
-
if (step === 0) {
|
|
1029
|
-
step = 1;
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
const paths = this.raphael.set();
|
|
1033
|
-
let points: Coord[] = [];
|
|
1034
|
-
let lastY = clippedFn(min)[1];
|
|
1035
|
-
|
|
1036
|
-
for (let t = min; t <= max; t += step) {
|
|
1037
|
-
const point = clippedFn(t);
|
|
1038
|
-
const y = point[1];
|
|
1039
|
-
|
|
1040
|
-
// Find points where it flips
|
|
1041
|
-
if (
|
|
1042
|
-
// if there is an asymptote here, meaning that the graph
|
|
1043
|
-
// switches signs and has a large difference
|
|
1044
|
-
(y > 0 !== lastY > 0 &&
|
|
1045
|
-
Math.abs(y - lastY) >
|
|
1046
|
-
2 * this.drawingTransform().pixelsPerUnitY()) ||
|
|
1047
|
-
// or the function is undefined
|
|
1048
|
-
isNaN(y)
|
|
1049
|
-
) {
|
|
1050
|
-
// split the path at this point, and draw it
|
|
1051
|
-
paths.push(this.unstyledPath(points));
|
|
1052
|
-
// restart the path, excluding this point
|
|
1053
|
-
points = [];
|
|
1054
|
-
} else {
|
|
1055
|
-
// otherwise, just add the point to the path
|
|
1056
|
-
points.push(point);
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
lastY = y;
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
|
-
paths.push(this.unstyledPath(points));
|
|
1063
|
-
|
|
1064
|
-
return paths;
|
|
1065
|
-
});
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
plot(
|
|
1069
|
-
fn: (x: number) => number,
|
|
1070
|
-
range: Interval,
|
|
1071
|
-
style?: StyleParams,
|
|
1072
|
-
): RaphaelElement {
|
|
1073
|
-
return this.withStyle(style, () => {
|
|
1074
|
-
const min = range[0];
|
|
1075
|
-
const max = range[1];
|
|
1076
|
-
if (!this.currentStyle["plot-points"]) {
|
|
1077
|
-
this.currentStyle["plot-points"] =
|
|
1078
|
-
2 * (max - min) * this.drawingTransform().pixelsPerUnitX();
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
|
-
const parametricFn = (x): Coord => [x, fn(x)];
|
|
1082
|
-
return this.plotParametric(parametricFn, range);
|
|
1083
|
-
});
|
|
1084
|
-
}
|
|
1085
|
-
|
|
1086
|
-
svgPath = (points: (Coord | true)[], alreadyScaled?: boolean) => {
|
|
1087
|
-
return points
|
|
1088
|
-
.map((point, i) => {
|
|
1089
|
-
if (point === true) {
|
|
1090
|
-
return "z";
|
|
1091
|
-
}
|
|
1092
|
-
const scaled = alreadyScaled ? point : this.scalePoint(point);
|
|
1093
|
-
return (
|
|
1094
|
-
(i === 0 ? "M" : "L") +
|
|
1095
|
-
KhanMath.bound(scaled[0]) +
|
|
1096
|
-
" " +
|
|
1097
|
-
KhanMath.bound(scaled[1])
|
|
1098
|
-
);
|
|
1099
|
-
})
|
|
1100
|
-
.join("");
|
|
1101
|
-
};
|
|
1102
|
-
|
|
1103
|
-
svgParabolaPath = (a: number, b: number, c: number) => {
|
|
1104
|
-
const computeParabola = function (x) {
|
|
1105
|
-
return (a * x + b) * x + c;
|
|
1106
|
-
};
|
|
1107
|
-
|
|
1108
|
-
// If points are collinear, plot a line instead
|
|
1109
|
-
if (a === 0) {
|
|
1110
|
-
const points: Coord[] = [
|
|
1111
|
-
[this.bounds().xMin, computeParabola(this.bounds().xMin)],
|
|
1112
|
-
[this.bounds().xMax, computeParabola(this.bounds().xMax)],
|
|
1113
|
-
];
|
|
1114
|
-
return this.svgPath(points);
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
|
-
// Calculate x coordinates of points on parabola
|
|
1118
|
-
const xVertex = -b / (2 * a);
|
|
1119
|
-
const distToEdge = Math.max(
|
|
1120
|
-
Math.abs(xVertex - this.bounds().xMin),
|
|
1121
|
-
Math.abs(xVertex - this.bounds().xMax),
|
|
1122
|
-
);
|
|
1123
|
-
|
|
1124
|
-
// To guarantee that drawn parabola to spans the viewport, use a point
|
|
1125
|
-
// on the edge of the graph furtherest from the vertex
|
|
1126
|
-
const xPoint = xVertex + distToEdge;
|
|
1127
|
-
|
|
1128
|
-
// Compute parabola and other point on the curve
|
|
1129
|
-
const vertex = [xVertex, computeParabola(xVertex)];
|
|
1130
|
-
const point = [xPoint, computeParabola(xPoint)];
|
|
1131
|
-
|
|
1132
|
-
// Calculate SVG 'control' point, defined by spec
|
|
1133
|
-
const control: Coord = [vertex[0], vertex[1] - (point[1] - vertex[1])];
|
|
1134
|
-
|
|
1135
|
-
// Calculate mirror points across parabola's axis of symmetry
|
|
1136
|
-
const dx = Math.abs(vertex[0] - point[0]);
|
|
1137
|
-
const left: Coord = [vertex[0] - dx, point[1]];
|
|
1138
|
-
const right: Coord = [vertex[0] + dx, point[1]];
|
|
1139
|
-
|
|
1140
|
-
// Scale and bound
|
|
1141
|
-
const points = [left, control, right].map(this.scalePoint);
|
|
1142
|
-
const values = points.flat().map(KhanMath.bound);
|
|
1143
|
-
return (
|
|
1144
|
-
"M" +
|
|
1145
|
-
values[0] +
|
|
1146
|
-
"," +
|
|
1147
|
-
values[1] +
|
|
1148
|
-
" Q" +
|
|
1149
|
-
values[2] +
|
|
1150
|
-
"," +
|
|
1151
|
-
values[3] +
|
|
1152
|
-
" " +
|
|
1153
|
-
values[4] +
|
|
1154
|
-
"," +
|
|
1155
|
-
values[5]
|
|
1156
|
-
);
|
|
1157
|
-
};
|
|
1158
|
-
|
|
1159
|
-
svgSinusoidPath = (a: number, b: number, c: number, d: number) => {
|
|
1160
|
-
// Plot a sinusoid of the form: f(x) = a * sin(b * x - c) + d
|
|
1161
|
-
const quarterPeriod = Math.abs(Math.PI / (2 * b));
|
|
1162
|
-
|
|
1163
|
-
const computeSine = function (x: number) {
|
|
1164
|
-
return a * Math.sin(b * x - c) + d;
|
|
1165
|
-
};
|
|
1166
|
-
|
|
1167
|
-
const computeDerivative = function (x) {
|
|
1168
|
-
return a * b * Math.cos(c - b * x);
|
|
1169
|
-
};
|
|
1170
|
-
|
|
1171
|
-
const coordsForOffset = (initial: number, i: number) => {
|
|
1172
|
-
// Return the cubic coordinates (including the two anchor and two
|
|
1173
|
-
// control points) for the ith portion of the sinusoid.
|
|
1174
|
-
const x0 = initial + quarterPeriod * i;
|
|
1175
|
-
const x1 = x0 + quarterPeriod;
|
|
1176
|
-
|
|
1177
|
-
// Interpolate using derivative technique
|
|
1178
|
-
// See: http://stackoverflow.com/questions/13932704/how-to-draw-sine-waves-with-svg-js
|
|
1179
|
-
const xCoords = [
|
|
1180
|
-
x0,
|
|
1181
|
-
(x0 * 2) / 3 + (x1 * 1) / 3,
|
|
1182
|
-
(x0 * 1) / 3 + (x1 * 2) / 3,
|
|
1183
|
-
x1,
|
|
1184
|
-
];
|
|
1185
|
-
const yCoords = [
|
|
1186
|
-
computeSine(x0),
|
|
1187
|
-
computeSine(x0) + (computeDerivative(x0) * (x1 - x0)) / 3,
|
|
1188
|
-
computeSine(x1) - (computeDerivative(x1) * (x1 - x0)) / 3,
|
|
1189
|
-
computeSine(x1),
|
|
1190
|
-
];
|
|
1191
|
-
|
|
1192
|
-
const points = kvector.zip(xCoords, yCoords);
|
|
1193
|
-
return points.map(this.scalePoint);
|
|
1194
|
-
};
|
|
1195
|
-
|
|
1196
|
-
// How many quarter-periods do we need to span the graph?
|
|
1197
|
-
const extent = this.bounds().width();
|
|
1198
|
-
const numQuarterPeriods = Math.ceil(extent / quarterPeriod) + 1;
|
|
1199
|
-
|
|
1200
|
-
// Find starting coordinate: first anchor point curve left of bounds.xMin
|
|
1201
|
-
let initial = c / b;
|
|
1202
|
-
const distToEdge = initial - this.bounds().xMin;
|
|
1203
|
-
initial -= quarterPeriod * Math.ceil(distToEdge / quarterPeriod);
|
|
1204
|
-
|
|
1205
|
-
// First portion of path is special-case, requiring move-to ('M')
|
|
1206
|
-
let coords = coordsForOffset(initial, 0);
|
|
1207
|
-
let path =
|
|
1208
|
-
"M" +
|
|
1209
|
-
coords[0][0] +
|
|
1210
|
-
"," +
|
|
1211
|
-
coords[0][1] +
|
|
1212
|
-
" C" +
|
|
1213
|
-
coords[1][0] +
|
|
1214
|
-
"," +
|
|
1215
|
-
coords[1][1] +
|
|
1216
|
-
" " +
|
|
1217
|
-
coords[2][0] +
|
|
1218
|
-
"," +
|
|
1219
|
-
coords[2][1] +
|
|
1220
|
-
" " +
|
|
1221
|
-
coords[3][0] +
|
|
1222
|
-
"," +
|
|
1223
|
-
coords[3][1];
|
|
1224
|
-
for (let i = 1; i < numQuarterPeriods; i++) {
|
|
1225
|
-
coords = coordsForOffset(initial, i);
|
|
1226
|
-
path +=
|
|
1227
|
-
" C" +
|
|
1228
|
-
coords[1][0] +
|
|
1229
|
-
"," +
|
|
1230
|
-
coords[1][1] +
|
|
1231
|
-
" " +
|
|
1232
|
-
coords[2][0] +
|
|
1233
|
-
"," +
|
|
1234
|
-
coords[2][1] +
|
|
1235
|
-
" " +
|
|
1236
|
-
coords[3][0] +
|
|
1237
|
-
"," +
|
|
1238
|
-
coords[3][1];
|
|
1239
|
-
}
|
|
1240
|
-
|
|
1241
|
-
return path;
|
|
1242
|
-
};
|
|
1243
|
-
|
|
1244
|
-
private withStyle<T>(style: StyleParams | undefined, fn: () => T): T {
|
|
1245
|
-
const oldStyle = this.currentStyle;
|
|
1246
|
-
this.currentStyle = {
|
|
1247
|
-
...this.currentStyle,
|
|
1248
|
-
...this.processAttributes(style),
|
|
1249
|
-
};
|
|
1250
|
-
const result = this.postprocessDrawingResult(fn());
|
|
1251
|
-
this.currentStyle = oldStyle;
|
|
1252
|
-
return result;
|
|
1253
|
-
}
|
|
1254
|
-
|
|
1255
|
-
private postprocessDrawingResult(result: any): any {
|
|
1256
|
-
// Bad heuristic for recognizing Raphael elements and sets
|
|
1257
|
-
const type = result.constructor.prototype;
|
|
1258
|
-
if (type === Raphael.el || type === Raphael.st) {
|
|
1259
|
-
result.attr(this.currentStyle);
|
|
1260
|
-
|
|
1261
|
-
if (this.currentStyle.arrows) {
|
|
1262
|
-
result = this.addArrowheads(result);
|
|
1263
|
-
}
|
|
1264
|
-
} else if (result instanceof $) {
|
|
1265
|
-
// We assume that if it's not a Raphael element/set, it
|
|
1266
|
-
// does not contain SVG.
|
|
1267
|
-
// @ts-expect-error - TS2339 - Property 'css' does not exist on type '{}'.
|
|
1268
|
-
result.css({
|
|
1269
|
-
...this.currentStyle,
|
|
1270
|
-
...SVG_SPECIFIC_STYLE_MASK,
|
|
1271
|
-
});
|
|
1272
|
-
}
|
|
1273
|
-
|
|
1274
|
-
return result;
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
|
-
private addArrowheads(path: any) {
|
|
1278
|
-
const type = path.constructor.prototype;
|
|
1279
|
-
|
|
1280
|
-
if (type === Raphael.el) {
|
|
1281
|
-
if (
|
|
1282
|
-
path.type === "path" &&
|
|
1283
|
-
typeof path.arrowheadsDrawn === "undefined"
|
|
1284
|
-
) {
|
|
1285
|
-
const w = path.attr("stroke-width");
|
|
1286
|
-
const s = 0.6 + 0.4 * w;
|
|
1287
|
-
const l = path.getTotalLength();
|
|
1288
|
-
const set = this.raphael.set();
|
|
1289
|
-
const head = this.raphael.path(
|
|
1290
|
-
this.isMobile
|
|
1291
|
-
? "M-4,4 C-4,4 -0.25,0 -0.25,0 C-0.25,0 -4,-4 -4,-4"
|
|
1292
|
-
: "M-3 4 C-2.75 2.5 0 0.25 0.75 0C0 -0.25 -2.75 -2.5 -3 -4",
|
|
1293
|
-
);
|
|
1294
|
-
const end = path.getPointAtLength(l - 0.4);
|
|
1295
|
-
const almostTheEnd = path.getPointAtLength(l - 0.75 * s);
|
|
1296
|
-
const angle =
|
|
1297
|
-
(Math.atan2(
|
|
1298
|
-
end.y - almostTheEnd.y,
|
|
1299
|
-
end.x - almostTheEnd.x,
|
|
1300
|
-
) *
|
|
1301
|
-
180) /
|
|
1302
|
-
Math.PI;
|
|
1303
|
-
const attrs = path.attr();
|
|
1304
|
-
delete attrs.path;
|
|
1305
|
-
|
|
1306
|
-
let subpath = path.getSubpath(0, l - 0.75 * s);
|
|
1307
|
-
subpath = this.raphael.path(subpath).attr(attrs);
|
|
1308
|
-
subpath.arrowheadsDrawn = true;
|
|
1309
|
-
path.remove();
|
|
1310
|
-
|
|
1311
|
-
// For some unknown reason 0 doesn't work for the rotation
|
|
1312
|
-
// origin so we use a tiny number.
|
|
1313
|
-
head.rotate(angle, this.isMobile ? 1e-5 : 0.75, 0)
|
|
1314
|
-
.scale(s, s, 0.75, 0)
|
|
1315
|
-
.translate(almostTheEnd.x, almostTheEnd.y)
|
|
1316
|
-
.attr(attrs)
|
|
1317
|
-
.attr({
|
|
1318
|
-
"stroke-linejoin": "round",
|
|
1319
|
-
"stroke-linecap": "round",
|
|
1320
|
-
});
|
|
1321
|
-
|
|
1322
|
-
head.arrowheadsDrawn = true;
|
|
1323
|
-
set.push(subpath);
|
|
1324
|
-
set.push(head);
|
|
1325
|
-
return set;
|
|
1326
|
-
}
|
|
1327
|
-
} else if (type === Raphael.st) {
|
|
1328
|
-
for (let i = 0, l = path.items.length; i < l; i++) {
|
|
1329
|
-
this.addArrowheads(path.items[i]);
|
|
1330
|
-
}
|
|
1331
|
-
}
|
|
1332
|
-
return path;
|
|
1333
|
-
}
|
|
1334
|
-
|
|
1335
|
-
scalePoint = (point: number | Coord): Coord => {
|
|
1336
|
-
return this.drawingTransform().scalePoint(point);
|
|
1337
|
-
};
|
|
1338
|
-
|
|
1339
|
-
scaleVector = (point: number | Coord) => {
|
|
1340
|
-
return this.drawingTransform().scaleVector(point);
|
|
1341
|
-
};
|
|
1342
|
-
|
|
1343
|
-
unscalePoint = (point: Coord) => {
|
|
1344
|
-
return this.drawingTransform().unscalePoint(point);
|
|
1345
|
-
};
|
|
1346
|
-
|
|
1347
|
-
unscaleVector = (point: Coord) => {
|
|
1348
|
-
return this.drawingTransform().unscaleVector(point);
|
|
1349
|
-
};
|
|
1350
|
-
|
|
1351
|
-
private processAttributes(attrs: Record<string, any> | undefined) {
|
|
1352
|
-
const transformers = {
|
|
1353
|
-
scale: (scale) => {
|
|
1354
|
-
this.drawingTransform().setScale(scale);
|
|
1355
|
-
},
|
|
1356
|
-
|
|
1357
|
-
clipRect: (pair) => {
|
|
1358
|
-
const point = pair[0];
|
|
1359
|
-
const size = pair[1];
|
|
1360
|
-
point[1] += size[1]; // because our coordinates are flipped
|
|
1361
|
-
|
|
1362
|
-
return {
|
|
1363
|
-
"clip-rect": this.scalePoint(point)
|
|
1364
|
-
.concat(this.scaleVector(size))
|
|
1365
|
-
.join(" "),
|
|
1366
|
-
};
|
|
1367
|
-
},
|
|
1368
|
-
|
|
1369
|
-
strokeWidth: function (val) {
|
|
1370
|
-
return {"stroke-width": parseFloat(val)};
|
|
1371
|
-
},
|
|
1372
|
-
|
|
1373
|
-
rx: (val) => {
|
|
1374
|
-
return {rx: this.scaleVector([val, 0])[0]};
|
|
1375
|
-
},
|
|
1376
|
-
|
|
1377
|
-
ry: (val) => {
|
|
1378
|
-
return {ry: this.scaleVector([0, val])[1]};
|
|
1379
|
-
},
|
|
1380
|
-
|
|
1381
|
-
r: (val) => {
|
|
1382
|
-
const scaled = this.scaleVector([val, val]);
|
|
1383
|
-
return {rx: scaled[0], ry: scaled[1]};
|
|
1384
|
-
},
|
|
1385
|
-
} as const;
|
|
1386
|
-
|
|
1387
|
-
const processed: Record<string, any> = {};
|
|
1388
|
-
Object.entries(attrs || {}).forEach(function ([key, value]) {
|
|
1389
|
-
const transformer = transformers[key];
|
|
1390
|
-
|
|
1391
|
-
if (typeof transformer === "function") {
|
|
1392
|
-
Object.assign(processed, transformer(value));
|
|
1393
|
-
} else {
|
|
1394
|
-
const dasherized = String(key)
|
|
1395
|
-
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2")
|
|
1396
|
-
.replace(/([a-z\d])([A-Z])/g, "$1-$2")
|
|
1397
|
-
.toLowerCase();
|
|
1398
|
-
processed[dasherized] = value;
|
|
1399
|
-
}
|
|
1400
|
-
});
|
|
1401
|
-
|
|
1402
|
-
return processed;
|
|
1403
|
-
}
|
|
1404
|
-
}
|
|
1405
|
-
|
|
1406
|
-
const labelDirections = {
|
|
1407
|
-
center: [-0.5, -0.5],
|
|
1408
|
-
above: [-0.5, -1.0],
|
|
1409
|
-
"above right": [0.0, -1.0],
|
|
1410
|
-
right: [0.0, -0.5],
|
|
1411
|
-
"below right": [0.0, 0.0],
|
|
1412
|
-
below: [-0.5, 0.0],
|
|
1413
|
-
"below left": [-1.0, 0.0],
|
|
1414
|
-
left: [-1.0, -0.5],
|
|
1415
|
-
"above left": [-1.0, -1.0],
|
|
1416
|
-
} as const;
|
|
1417
|
-
|
|
1418
|
-
// A range for a Graphie can be specified in one of three forms:
|
|
1419
|
-
// - A pair of intervals, [[xMin, xMax], [yMin, yMax]]. This is the
|
|
1420
|
-
// "normalized" form.
|
|
1421
|
-
// - A pair of magnitudes for the x and y ranges, respectively. E.g. [5, 10]
|
|
1422
|
-
// is equivalent to [[-5, 5], [-10, 10]].
|
|
1423
|
-
// - A single magnitude to be used for both x and y. E.g. 10 is equivalent to
|
|
1424
|
-
// [[-10, 10], [-10, 10]]
|
|
1425
|
-
type RangeSpecifier = [Interval, Interval] | Coord | number;
|
|
1426
|
-
|
|
1427
|
-
// Overload normalizeRange to specify that it only returns undefined if its
|
|
1428
|
-
// argument is undefined.
|
|
1429
|
-
export function normalizeRange(range: RangeSpecifier): [Interval, Interval];
|
|
1430
|
-
export function normalizeRange(
|
|
1431
|
-
range: RangeSpecifier | undefined,
|
|
1432
|
-
): [Interval, Interval] | undefined;
|
|
1433
|
-
|
|
1434
|
-
// normalizeRange converts a RangeSpecifier into a pair of Intervals that give
|
|
1435
|
-
// the x and y ranges, respectively
|
|
1436
|
-
export function normalizeRange(
|
|
1437
|
-
range: RangeSpecifier | undefined,
|
|
1438
|
-
): [Interval, Interval] | undefined {
|
|
1439
|
-
function normalizeInterval(magnitude: number | Interval): Interval {
|
|
1440
|
-
if (typeof magnitude === "number") {
|
|
1441
|
-
return [-magnitude, magnitude];
|
|
1442
|
-
} else {
|
|
1443
|
-
return magnitude;
|
|
1444
|
-
}
|
|
1445
|
-
}
|
|
1446
|
-
|
|
1447
|
-
function getXAndYRanges(
|
|
1448
|
-
range: RangeSpecifier,
|
|
1449
|
-
): Coord | [Interval, Interval] {
|
|
1450
|
-
if (Array.isArray(range)) {
|
|
1451
|
-
return range;
|
|
1452
|
-
} else {
|
|
1453
|
-
return [range, range];
|
|
1454
|
-
}
|
|
1455
|
-
}
|
|
1456
|
-
|
|
1457
|
-
if (range == null) {
|
|
1458
|
-
return range;
|
|
1459
|
-
}
|
|
1460
|
-
|
|
1461
|
-
const [xRange, yRange] = getXAndYRanges(range);
|
|
1462
|
-
return [normalizeInterval(xRange), normalizeInterval(yRange)];
|
|
1463
|
-
}
|
|
1464
|
-
|
|
1465
|
-
function toPair(x: number | [number, number]): [number, number] {
|
|
1466
|
-
if (Array.isArray(x)) {
|
|
1467
|
-
return x;
|
|
1468
|
-
}
|
|
1469
|
-
return [x, x];
|
|
1470
|
-
}
|
|
1471
|
-
|
|
1472
|
-
/**
|
|
1473
|
-
* Safari applies some SVG-specific styles to things that are not SVGs, so we
|
|
1474
|
-
* need to exclude those styles from things that are not SVGs.
|
|
1475
|
-
*
|
|
1476
|
-
* To see this behavior in action, open https://codepen.io/anon/pen/zENEoa in
|
|
1477
|
-
* Safari.
|
|
1478
|
-
*
|
|
1479
|
-
* Usage `{...someStyles, ...SVG_SPECIFIC_STYLE_MASK}`
|
|
1480
|
-
*/
|
|
1481
|
-
const SVG_SPECIFIC_STYLE_MASK = {
|
|
1482
|
-
"stroke-width": null,
|
|
1483
|
-
} as const;
|
|
1484
|
-
|
|
1485
|
-
const setLabelMargins = function (span: HTMLElement, size: Coord): void {
|
|
1486
|
-
const $span = $(span);
|
|
1487
|
-
const direction = $span.data("labelDirection");
|
|
1488
|
-
let [width, height] = size;
|
|
1489
|
-
// This can happen when a span
|
|
1490
|
-
// is invisible but we still want to update the CSS. At worst, we will
|
|
1491
|
-
// be off by a few pixels instead of in a different position entirely.
|
|
1492
|
-
if (width === 0 && height === 0) {
|
|
1493
|
-
[width, height] = [1, 1];
|
|
1494
|
-
Log.log("Label size was 0x0 in graphie.js; using 1x1 instead");
|
|
1495
|
-
}
|
|
1496
|
-
$span.css("visibility", "");
|
|
1497
|
-
|
|
1498
|
-
if (typeof direction === "number") {
|
|
1499
|
-
const x = Math.cos(direction);
|
|
1500
|
-
const y = Math.sin(direction);
|
|
1501
|
-
|
|
1502
|
-
const scale = Math.min(
|
|
1503
|
-
width / 2 / Math.abs(x),
|
|
1504
|
-
height / 2 / Math.abs(y),
|
|
1505
|
-
);
|
|
1506
|
-
|
|
1507
|
-
$span.css({
|
|
1508
|
-
marginLeft: -width / 2 + x * scale,
|
|
1509
|
-
marginTop: -height / 2 - y * scale,
|
|
1510
|
-
});
|
|
1511
|
-
} else {
|
|
1512
|
-
const multipliers = labelDirections[direction || "center"];
|
|
1513
|
-
$span.css({
|
|
1514
|
-
marginLeft: Math.round(width * multipliers[0]),
|
|
1515
|
-
marginTop: Math.round(height * multipliers[1]),
|
|
1516
|
-
});
|
|
1517
|
-
}
|
|
1518
|
-
};
|
|
1519
|
-
|
|
1520
|
-
const GraphUtils = {
|
|
1521
|
-
Graphie,
|
|
1522
|
-
|
|
1523
|
-
createGraphie: function (el: Element): Graphie {
|
|
1524
|
-
return new Graphie(el);
|
|
1525
|
-
},
|
|
1526
|
-
|
|
1527
|
-
unscaledSvgPath: function (points: (Coord | true)[]): string {
|
|
1528
|
-
// If this is an empty closed path, return "" instead of "z", which
|
|
1529
|
-
// would give an error
|
|
1530
|
-
if (points[0] === true) {
|
|
1531
|
-
return "";
|
|
1532
|
-
}
|
|
1533
|
-
return points
|
|
1534
|
-
.map(function (point, i) {
|
|
1535
|
-
if (point === true) {
|
|
1536
|
-
return "z";
|
|
1537
|
-
}
|
|
1538
|
-
return (i === 0 ? "M" : "L") + point[0] + " " + point[1];
|
|
1539
|
-
})
|
|
1540
|
-
.join("");
|
|
1541
|
-
},
|
|
1542
|
-
|
|
1543
|
-
getDistance: function (point1: Coord, point2: Coord): number {
|
|
1544
|
-
return kpoint.distanceToPoint(point1, point2);
|
|
1545
|
-
},
|
|
1546
|
-
|
|
1547
|
-
// Find the angle in degrees between two or three points
|
|
1548
|
-
findAngle: function (point1: Coord, point2: Coord, vertex?: Coord) {
|
|
1549
|
-
if (vertex === undefined) {
|
|
1550
|
-
const x = point1[0] - point2[0];
|
|
1551
|
-
const y = point1[1] - point2[1];
|
|
1552
|
-
if (!x && !y) {
|
|
1553
|
-
return 0;
|
|
1554
|
-
}
|
|
1555
|
-
return (180 + (Math.atan2(-y, -x) * 180) / Math.PI + 360) % 360;
|
|
1556
|
-
}
|
|
1557
|
-
return (
|
|
1558
|
-
GraphUtils.findAngle(point1, vertex) -
|
|
1559
|
-
GraphUtils.findAngle(point2, vertex)
|
|
1560
|
-
);
|
|
1561
|
-
},
|
|
1562
|
-
|
|
1563
|
-
graphs: {},
|
|
1564
|
-
};
|
|
1565
|
-
|
|
1566
|
-
export default GraphUtils;
|