@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.
Files changed (487) hide show
  1. package/dist/components/graphie.d.ts +9 -1
  2. package/dist/es/index.js +255 -122
  3. package/dist/es/index.js.map +1 -1
  4. package/dist/index.js +261 -126
  5. package/dist/index.js.map +1 -1
  6. package/dist/interactive2/interactive-util.d.ts +1 -1
  7. package/dist/interactive2/movable-point-options.d.ts +5 -4
  8. package/dist/interactive2/movable.d.ts +90 -13
  9. package/dist/interactive2/types.d.ts +6 -0
  10. package/dist/util/graphie.d.ts +5 -0
  11. package/dist/widgets/expression.d.ts +4 -4
  12. package/package.json +11 -8
  13. package/.babelrc.js +0 -8
  14. package/.eslintrc.js +0 -12
  15. package/CHANGELOG.md +0 -1422
  16. package/src/__stories__/article-renderer.stories.tsx +0 -100
  17. package/src/__stories__/hints-renderer.stories.tsx +0 -55
  18. package/src/__stories__/server-item-renderer.stories.tsx +0 -44
  19. package/src/__testdata__/article-renderer.testdata.ts +0 -212
  20. package/src/__testdata__/graphie.testdata.ts +0 -51
  21. package/src/__testdata__/renderer.testdata.ts +0 -274
  22. package/src/__testdata__/server-item-renderer.testdata.ts +0 -171
  23. package/src/__tests__/__snapshots__/perseus-markdown.test.ts.snap +0 -507
  24. package/src/__tests__/__snapshots__/renderer.test.tsx.snap +0 -525
  25. package/src/__tests__/__snapshots__/server-item-renderer.test.tsx.snap +0 -285
  26. package/src/__tests__/a11y.test.ts +0 -303
  27. package/src/__tests__/article-renderer.test.tsx +0 -130
  28. package/src/__tests__/error-boundary.test.tsx +0 -76
  29. package/src/__tests__/extract-perseus-data.test.ts +0 -1027
  30. package/src/__tests__/init.test.ts +0 -10
  31. package/src/__tests__/jipt-paragraphs.test.ts +0 -32
  32. package/src/__tests__/mock-asset-loading-widget.tsx +0 -53
  33. package/src/__tests__/mock-widget.tsx +0 -33
  34. package/src/__tests__/perseus-markdown.test.ts +0 -764
  35. package/src/__tests__/renderability.test.ts +0 -480
  36. package/src/__tests__/renderer-api.test.tsx +0 -302
  37. package/src/__tests__/renderer.test.tsx +0 -1816
  38. package/src/__tests__/server-item-renderer.test.tsx +0 -582
  39. package/src/__tests__/test-items/image-item.ts +0 -24
  40. package/src/__tests__/test-items/input-number-1-item.ts +0 -26
  41. package/src/__tests__/test-items/input-number-2-item.ts +0 -38
  42. package/src/__tests__/test-items/table-item.ts +0 -29
  43. package/src/__tests__/tex-wrangler.test.ts +0 -82
  44. package/src/__tests__/util.test.ts +0 -40
  45. package/src/__tests__/widget-container.test.tsx +0 -126
  46. package/src/__tests__/widgets.test.ts +0 -91
  47. package/src/a11y.ts +0 -45
  48. package/src/all-widgets.ts +0 -9
  49. package/src/article-renderer.tsx +0 -237
  50. package/src/asset-context.ts +0 -30
  51. package/src/basic-widgets.ts +0 -16
  52. package/src/components/__stories__/button-group.stories.tsx +0 -55
  53. package/src/components/__stories__/fixed-to-responsive.stories.tsx +0 -141
  54. package/src/components/__stories__/graph.stories.tsx +0 -28
  55. package/src/components/__stories__/graphie.stories.tsx +0 -33
  56. package/src/components/__stories__/hud.stories.tsx +0 -35
  57. package/src/components/__stories__/icon.stories.tsx +0 -63
  58. package/src/components/__stories__/image-loader.stories.tsx +0 -63
  59. package/src/components/__stories__/info-tip.stories.tsx +0 -31
  60. package/src/components/__stories__/inline-icon.stories.tsx +0 -40
  61. package/src/components/__stories__/input-with-examples.stories.tsx +0 -53
  62. package/src/components/__stories__/lint.stories.tsx +0 -86
  63. package/src/components/__stories__/math-input.stories.tsx +0 -43
  64. package/src/components/__stories__/math-output.stories.tsx +0 -23
  65. package/src/components/__stories__/multi-button-group.stories.tsx +0 -66
  66. package/src/components/__stories__/number-input.stories.tsx +0 -41
  67. package/src/components/__stories__/prop-check-box.stories.tsx +0 -65
  68. package/src/components/__stories__/range-input.stories.tsx +0 -29
  69. package/src/components/__stories__/simple-keypad-input.stories.tsx +0 -27
  70. package/src/components/__stories__/sortable.stories.tsx +0 -77
  71. package/src/components/__stories__/stub-tag-editor.stories.tsx +0 -45
  72. package/src/components/__stories__/svg-image.stories.tsx +0 -74
  73. package/src/components/__stories__/tex.stories.tsx +0 -23
  74. package/src/components/__stories__/text-input.stories.tsx +0 -33
  75. package/src/components/__stories__/text-list-editor.stories.tsx +0 -32
  76. package/src/components/__stories__/tooltip.stories.tsx +0 -52
  77. package/src/components/__stories__/zoomable-tex.stories.tsx +0 -41
  78. package/src/components/__stories__/zoomable.stories.tsx +0 -49
  79. package/src/components/__testdata__/sorter.testdata.ts +0 -19
  80. package/src/components/__tests__/__snapshots__/sortable.test.tsx.snap +0 -163
  81. package/src/components/__tests__/__snapshots__/sorter.test.tsx.snap +0 -265
  82. package/src/components/__tests__/math-input.test.tsx +0 -147
  83. package/src/components/__tests__/number-input.test.tsx +0 -128
  84. package/src/components/__tests__/sortable.test.tsx +0 -269
  85. package/src/components/__tests__/sorter.test.tsx +0 -114
  86. package/src/components/__tests__/zoomable.test.tsx +0 -399
  87. package/src/components/button-group.tsx +0 -130
  88. package/src/components/constants.ts +0 -14
  89. package/src/components/fixed-to-responsive.tsx +0 -169
  90. package/src/components/graph.tsx +0 -444
  91. package/src/components/graphie-classes.ts +0 -153
  92. package/src/components/graphie-movables.ts +0 -338
  93. package/src/components/graphie.tsx +0 -392
  94. package/src/components/highlighting/highlightable-content.tsx +0 -240
  95. package/src/components/highlighting/highlights.ts +0 -207
  96. package/src/components/highlighting/ranges.ts +0 -304
  97. package/src/components/highlighting/types.ts +0 -73
  98. package/src/components/highlighting/ui/highlight-renderer.tsx +0 -172
  99. package/src/components/highlighting/ui/highlight-set-renderer.tsx +0 -185
  100. package/src/components/highlighting/ui/highlight-tooltip.tsx +0 -141
  101. package/src/components/highlighting/ui/highlighting-ui.tsx +0 -143
  102. package/src/components/highlighting/ui/selection-tracker.tsx +0 -186
  103. package/src/components/highlighting/ui/types.ts +0 -21
  104. package/src/components/highlighting/ui/util.ts +0 -165
  105. package/src/components/highlighting/word-indexer.tsx +0 -152
  106. package/src/components/hud.tsx +0 -167
  107. package/src/components/icon.tsx +0 -141
  108. package/src/components/image-loader.tsx +0 -177
  109. package/src/components/info-tip/info-tip.tsx +0 -144
  110. package/src/components/info-tip.tsx +0 -39
  111. package/src/components/inline-icon.tsx +0 -60
  112. package/src/components/input-with-examples.tsx +0 -249
  113. package/src/components/lint.tsx +0 -459
  114. package/src/components/math-input.tsx +0 -474
  115. package/src/components/math-output.tsx +0 -134
  116. package/src/components/multi-button-group.tsx +0 -135
  117. package/src/components/number-input.tsx +0 -249
  118. package/src/components/prop-check-box.tsx +0 -79
  119. package/src/components/range-input.tsx +0 -60
  120. package/src/components/simple-keypad-input.tsx +0 -88
  121. package/src/components/sortable.tsx +0 -958
  122. package/src/components/stub-tag-editor.tsx +0 -53
  123. package/src/components/svg-image.tsx +0 -779
  124. package/src/components/tex.tsx +0 -67
  125. package/src/components/text-input.tsx +0 -129
  126. package/src/components/text-list-editor.tsx +0 -166
  127. package/src/components/tooltip.tsx +0 -410
  128. package/src/components/visibility-observer/__stories__/visibility-observer.stories.tsx +0 -141
  129. package/src/components/visibility-observer/create-visibility-observer.ts +0 -108
  130. package/src/components/visibility-observer/get-scroll-parent.ts +0 -46
  131. package/src/components/visibility-observer/visibility-observer.tsx +0 -124
  132. package/src/components/zoomable-tex.tsx +0 -78
  133. package/src/components/zoomable.tsx +0 -304
  134. package/src/components.ts +0 -15
  135. package/src/definition-context.tsx +0 -59
  136. package/src/dependencies.ts +0 -40
  137. package/src/error-boundary.tsx +0 -70
  138. package/src/extra-widgets.ts +0 -75
  139. package/src/hint-renderer.tsx +0 -151
  140. package/src/hints-renderer.tsx +0 -372
  141. package/src/icon-paths.ts +0 -119
  142. package/src/index.ts +0 -171
  143. package/src/init.ts +0 -56
  144. package/src/interaction-tracker.ts +0 -62
  145. package/src/interactive2/__tests__/movable-mock.ts +0 -43
  146. package/src/interactive2/__tests__/movable-point.test.ts +0 -144
  147. package/src/interactive2/__tests__/movable.test.ts +0 -157
  148. package/src/interactive2/interactive-util.test.ts +0 -93
  149. package/src/interactive2/interactive-util.ts +0 -159
  150. package/src/interactive2/movable-helper-methods.ts +0 -166
  151. package/src/interactive2/movable-line-options.ts +0 -419
  152. package/src/interactive2/movable-line.ts +0 -307
  153. package/src/interactive2/movable-point-options.ts +0 -212
  154. package/src/interactive2/movable-point.tsx +0 -585
  155. package/src/interactive2/movable-polygon-options.ts +0 -389
  156. package/src/interactive2/movable-polygon.ts +0 -360
  157. package/src/interactive2/movable.ts +0 -279
  158. package/src/interactive2/objective_.ts +0 -79
  159. package/src/interactive2/types.ts +0 -2
  160. package/src/interactive2/wrapped-defaults.ts +0 -87
  161. package/src/interactive2/wrapped-ellipse.ts +0 -94
  162. package/src/interactive2/wrapped-line.ts +0 -116
  163. package/src/interactive2/wrapped-path.ts +0 -51
  164. package/src/interactive2.ts +0 -70
  165. package/src/item-version.ts +0 -10
  166. package/src/jipt-paragraphs.ts +0 -49
  167. package/src/jquery.mobile.vmouse.js +0 -545
  168. package/src/loading-context.ts +0 -21
  169. package/src/logging/log.test.ts +0 -39
  170. package/src/logging/log.ts +0 -97
  171. package/src/mixins/__tests__/widget-prop-denylist.test.ts +0 -21
  172. package/src/mixins/changeable.ts +0 -133
  173. package/src/mixins/editor-jsonify.ts +0 -13
  174. package/src/mixins/widget-jsonify-deprecated.ts +0 -23
  175. package/src/mixins/widget-prop-denylist.ts +0 -48
  176. package/src/multi-items/__stories__/multi-renderer.stories.tsx +0 -83
  177. package/src/multi-items/__testdata__/multi-renderer.testdata.ts +0 -172
  178. package/src/multi-items/__tests__/__snapshots__/multi-renderer.test.tsx.snap +0 -1164
  179. package/src/multi-items/__tests__/items.test.ts +0 -240
  180. package/src/multi-items/__tests__/multi-renderer.test.tsx +0 -914
  181. package/src/multi-items/__tests__/prop-type-builders.test.ts +0 -133
  182. package/src/multi-items/__tests__/shapes.test.ts +0 -43
  183. package/src/multi-items/__tests__/trees.test.ts +0 -317
  184. package/src/multi-items/item-types.ts +0 -56
  185. package/src/multi-items/items.ts +0 -176
  186. package/src/multi-items/multi-renderer.tsx +0 -569
  187. package/src/multi-items/prop-type-builders.ts +0 -106
  188. package/src/multi-items/shape-types.ts +0 -52
  189. package/src/multi-items/shapes.ts +0 -52
  190. package/src/multi-items/tree-types.ts +0 -47
  191. package/src/multi-items/trees.ts +0 -360
  192. package/src/multi-items.ts +0 -75
  193. package/src/perseus-api.tsx +0 -188
  194. package/src/perseus-env.ts +0 -27
  195. package/src/perseus-error.ts +0 -17
  196. package/src/perseus-markdown.tsx +0 -405
  197. package/src/perseus-types.ts +0 -1366
  198. package/src/perseus-version.ts +0 -9
  199. package/src/question-paragraph.tsx +0 -34
  200. package/src/renderability.ts +0 -109
  201. package/src/renderer.tsx +0 -2000
  202. package/src/server-item-renderer.tsx +0 -495
  203. package/src/sigfigs.ts +0 -490
  204. package/src/styles/articles.less +0 -749
  205. package/src/styles/constants.ts +0 -94
  206. package/src/styles/global-constants.ts +0 -343
  207. package/src/styles/global-styles.ts +0 -306
  208. package/src/styles/khan-exercise.css +0 -1468
  209. package/src/styles/media-queries.ts +0 -47
  210. package/src/styles/perseus-renderer.less +0 -732
  211. package/src/styles/shared.ts +0 -130
  212. package/src/styles/util.less +0 -47
  213. package/src/styles/variables.boostrap.less +0 -277
  214. package/src/styles/variables.less +0 -56
  215. package/src/styles/variables.shared.less +0 -340
  216. package/src/styles/widgets/categorizer.less +0 -38
  217. package/src/styles/widgets/dropdown.less +0 -3
  218. package/src/styles/widgets/expression.less +0 -65
  219. package/src/styles/widgets/graded-group.less +0 -36
  220. package/src/styles/widgets/grapher.less +0 -12
  221. package/src/styles/widgets/group.less +0 -19
  222. package/src/styles/widgets/image.less +0 -54
  223. package/src/styles/widgets/interactive-graph.less +0 -64
  224. package/src/styles/widgets/label-image.less +0 -57
  225. package/src/styles/widgets/matcher.less +0 -8
  226. package/src/styles/widgets/matrix.less +0 -126
  227. package/src/styles/widgets/measurer.less +0 -20
  228. package/src/styles/widgets/orderer.less +0 -98
  229. package/src/styles/widgets/passage.less +0 -154
  230. package/src/styles/widgets/plotter.less +0 -31
  231. package/src/styles/widgets/radio.less +0 -58
  232. package/src/styles/widgets/reaction-diagram.less +0 -35
  233. package/src/styles/widgets/sequence.less +0 -5
  234. package/src/styles/widgets/sortable.less +0 -113
  235. package/src/styles/widgets/table.less +0 -57
  236. package/src/styles/widgets/unit.less +0 -15
  237. package/src/tex-wrangler.ts +0 -182
  238. package/src/translation-linter.ts +0 -126
  239. package/src/traversal.ts +0 -138
  240. package/src/types.ts +0 -513
  241. package/src/util/a11y.ts +0 -23
  242. package/src/util/answer-types.test.ts +0 -114
  243. package/src/util/answer-types.ts +0 -876
  244. package/src/util/colors.ts +0 -93
  245. package/src/util/drawing-transform.test.ts +0 -67
  246. package/src/util/drawing-transform.ts +0 -102
  247. package/src/util/extract-perseus-data.ts +0 -539
  248. package/src/util/fix-passage-refs.ts +0 -121
  249. package/src/util/geometry.test.ts +0 -75
  250. package/src/util/geometry.ts +0 -312
  251. package/src/util/graph-bounds.ts +0 -23
  252. package/src/util/graph-utils.ts +0 -15
  253. package/src/util/graphie.test.ts +0 -1277
  254. package/src/util/graphie.ts +0 -1566
  255. package/src/util/interactive.ts +0 -4172
  256. package/src/util/interval.ts +0 -4
  257. package/src/util/katex-preprocess.ts +0 -14
  258. package/src/util/math.test.ts +0 -11
  259. package/src/util/math.ts +0 -294
  260. package/src/util/mobile-native-utils.test.ts +0 -22
  261. package/src/util/mobile-native-utils.ts +0 -12
  262. package/src/util/parse-perseus-json.test.ts +0 -10
  263. package/src/util/parse-perseus-json.ts +0 -13
  264. package/src/util/react-render.tsx +0 -27
  265. package/src/util/register-all-widgets-for-testing.ts +0 -13
  266. package/src/util/scroll-utils.ts +0 -155
  267. package/src/util/sizing-utils.ts +0 -46
  268. package/src/util/tex.ts +0 -73
  269. package/src/util/url-utils.ts +0 -34
  270. package/src/util.graphie.test.ts +0 -45
  271. package/src/util.graphie.ts +0 -59
  272. package/src/util.ts +0 -966
  273. package/src/version.ts +0 -10
  274. package/src/widget-container.tsx +0 -199
  275. package/src/widgets/__stories__/categorizer.stories.tsx +0 -14
  276. package/src/widgets/__stories__/cs-program.stories.tsx +0 -14
  277. package/src/widgets/__stories__/definition.stories.tsx +0 -114
  278. package/src/widgets/__stories__/dropdown.stories.tsx +0 -14
  279. package/src/widgets/__stories__/explanation.stories.tsx +0 -18
  280. package/src/widgets/__stories__/expression.stories.tsx +0 -145
  281. package/src/widgets/__stories__/graded-group-set.stories.tsx +0 -31
  282. package/src/widgets/__stories__/graded-group.stories.tsx +0 -31
  283. package/src/widgets/__stories__/grapher.stories.tsx +0 -46
  284. package/src/widgets/__stories__/group.stories.tsx +0 -14
  285. package/src/widgets/__stories__/iframe.stories.tsx +0 -14
  286. package/src/widgets/__stories__/image.stories.tsx +0 -85
  287. package/src/widgets/__stories__/input-number.stories.tsx +0 -101
  288. package/src/widgets/__stories__/interaction.stories.tsx +0 -14
  289. package/src/widgets/__stories__/interactive-graph.stories.tsx +0 -60
  290. package/src/widgets/__stories__/label-image.stories.tsx +0 -130
  291. package/src/widgets/__stories__/matcher.stories.tsx +0 -14
  292. package/src/widgets/__stories__/matrix.stories.tsx +0 -14
  293. package/src/widgets/__stories__/number-line.stories.tsx +0 -14
  294. package/src/widgets/__stories__/numeric-input.stories.tsx +0 -105
  295. package/src/widgets/__stories__/orderer.stories.tsx +0 -18
  296. package/src/widgets/__stories__/passage-ref.stories.tsx +0 -33
  297. package/src/widgets/__stories__/passage.stories.tsx +0 -39
  298. package/src/widgets/__stories__/plotter.stories.tsx +0 -27
  299. package/src/widgets/__stories__/radio.stories.tsx +0 -86
  300. package/src/widgets/__stories__/random-widgets.stories.tsx +0 -59
  301. package/src/widgets/__stories__/sorter.stories.tsx +0 -14
  302. package/src/widgets/__stories__/test-keypad-context-wrapper.tsx +0 -66
  303. package/src/widgets/__stories__/unit.stories.tsx +0 -28
  304. package/src/widgets/__stories__/video-transcript-link.stories.tsx +0 -23
  305. package/src/widgets/__stories__/video.stories.tsx +0 -22
  306. package/src/widgets/__testdata__/base-radio.testdata.ts +0 -21
  307. package/src/widgets/__testdata__/categorizer.testdata.ts +0 -89
  308. package/src/widgets/__testdata__/cs-program.testdata.ts +0 -27
  309. package/src/widgets/__testdata__/dropdown.testdata.ts +0 -71
  310. package/src/widgets/__testdata__/explanation.testdata.ts +0 -85
  311. package/src/widgets/__testdata__/expression.testdata.ts +0 -198
  312. package/src/widgets/__testdata__/graded-group-set.testdata.ts +0 -356
  313. package/src/widgets/__testdata__/graded-group.testdata.ts +0 -77
  314. package/src/widgets/__testdata__/grapher.testdata.ts +0 -374
  315. package/src/widgets/__testdata__/group.testdata.ts +0 -153
  316. package/src/widgets/__testdata__/iframe.testdata.ts +0 -35
  317. package/src/widgets/__testdata__/image.testdata.ts +0 -36
  318. package/src/widgets/__testdata__/input-number.testdata.ts +0 -72
  319. package/src/widgets/__testdata__/interaction.testdata.ts +0 -262
  320. package/src/widgets/__testdata__/interactive-graph-random.testdata.ts +0 -269
  321. package/src/widgets/__testdata__/interactive-graph.testdata.ts +0 -644
  322. package/src/widgets/__testdata__/label-image.testdata.ts +0 -308
  323. package/src/widgets/__testdata__/matcher.testdata.ts +0 -33
  324. package/src/widgets/__testdata__/matrix.testdata.ts +0 -28
  325. package/src/widgets/__testdata__/number-line.testdata.ts +0 -30
  326. package/src/widgets/__testdata__/numeric-input.testdata.ts +0 -265
  327. package/src/widgets/__testdata__/orderer.testdata.ts +0 -123
  328. package/src/widgets/__testdata__/passage-ref.testdata.ts +0 -101
  329. package/src/widgets/__testdata__/passage.testdata.ts +0 -95
  330. package/src/widgets/__testdata__/plotter.testdata.ts +0 -36
  331. package/src/widgets/__testdata__/radio.testdata.ts +0 -324
  332. package/src/widgets/__testdata__/randomizers.ts +0 -51
  333. package/src/widgets/__testdata__/sorter.testdata.ts +0 -19
  334. package/src/widgets/__testdata__/video.testdata.ts +0 -61
  335. package/src/widgets/__tests__/__snapshots__/categorizer.test.ts.snap +0 -940
  336. package/src/widgets/__tests__/__snapshots__/cs-program.test.ts.snap +0 -67
  337. package/src/widgets/__tests__/__snapshots__/definition.test.ts.snap +0 -97
  338. package/src/widgets/__tests__/__snapshots__/dropdown.test.ts.snap +0 -133
  339. package/src/widgets/__tests__/__snapshots__/explanation.test.ts.snap +0 -425
  340. package/src/widgets/__tests__/__snapshots__/graded-group-set-jipt.test.ts.snap +0 -729
  341. package/src/widgets/__tests__/__snapshots__/graded-group-set.test.ts.snap +0 -339
  342. package/src/widgets/__tests__/__snapshots__/graded-group.test.ts.snap +0 -801
  343. package/src/widgets/__tests__/__snapshots__/grapher.test.ts.snap +0 -2673
  344. package/src/widgets/__tests__/__snapshots__/group.test.tsx.snap +0 -1211
  345. package/src/widgets/__tests__/__snapshots__/iframe.test.ts.snap +0 -61
  346. package/src/widgets/__tests__/__snapshots__/image.test.ts.snap +0 -378
  347. package/src/widgets/__tests__/__snapshots__/input-number.test.ts.snap +0 -495
  348. package/src/widgets/__tests__/__snapshots__/interaction.test.ts.snap +0 -292
  349. package/src/widgets/__tests__/__snapshots__/interactive-graph.test.ts.snap +0 -2889
  350. package/src/widgets/__tests__/__snapshots__/matcher.test.tsx.snap +0 -883
  351. package/src/widgets/__tests__/__snapshots__/matrix.test.ts.snap +0 -514
  352. package/src/widgets/__tests__/__snapshots__/number-line.test.ts.snap +0 -814
  353. package/src/widgets/__tests__/__snapshots__/numeric-input.test.ts.snap +0 -470
  354. package/src/widgets/__tests__/__snapshots__/orderer.test.ts.snap +0 -243
  355. package/src/widgets/__tests__/__snapshots__/passage-ref.test.ts.snap +0 -434
  356. package/src/widgets/__tests__/__snapshots__/passage.test.tsx.snap +0 -1956
  357. package/src/widgets/__tests__/__snapshots__/plotter.test.tsx.snap +0 -827
  358. package/src/widgets/__tests__/__snapshots__/radio.test.ts.snap +0 -4237
  359. package/src/widgets/__tests__/__snapshots__/unit.test.tsx.snap +0 -20
  360. package/src/widgets/__tests__/__snapshots__/video.test.ts.snap +0 -153
  361. package/src/widgets/__tests__/categorizer.test.ts +0 -203
  362. package/src/widgets/__tests__/cs-program.test.ts +0 -43
  363. package/src/widgets/__tests__/definition.test.ts +0 -136
  364. package/src/widgets/__tests__/dropdown.test.ts +0 -105
  365. package/src/widgets/__tests__/explanation.test.ts +0 -198
  366. package/src/widgets/__tests__/expression-mobile.test.tsx +0 -363
  367. package/src/widgets/__tests__/expression.test.tsx +0 -484
  368. package/src/widgets/__tests__/graded-group-set-jipt.test.ts +0 -30
  369. package/src/widgets/__tests__/graded-group-set.test.ts +0 -288
  370. package/src/widgets/__tests__/graded-group.test.ts +0 -232
  371. package/src/widgets/__tests__/grapher.cypress.ts +0 -576
  372. package/src/widgets/__tests__/grapher.test.ts +0 -33
  373. package/src/widgets/__tests__/group.test.tsx +0 -510
  374. package/src/widgets/__tests__/iframe.test.ts +0 -45
  375. package/src/widgets/__tests__/image.test.ts +0 -62
  376. package/src/widgets/__tests__/input-number.test.ts +0 -354
  377. package/src/widgets/__tests__/interaction.test.ts +0 -31
  378. package/src/widgets/__tests__/interactive-graph.cypress.ts +0 -815
  379. package/src/widgets/__tests__/interactive-graph.test.ts +0 -106
  380. package/src/widgets/__tests__/matcher.test.tsx +0 -176
  381. package/src/widgets/__tests__/matrix.test.ts +0 -82
  382. package/src/widgets/__tests__/number-line.test.ts +0 -79
  383. package/src/widgets/__tests__/numeric-input.test.ts +0 -575
  384. package/src/widgets/__tests__/orderer.test.ts +0 -73
  385. package/src/widgets/__tests__/passage-ref.test.ts +0 -168
  386. package/src/widgets/__tests__/passage.test.tsx +0 -224
  387. package/src/widgets/__tests__/plotter.test.tsx +0 -69
  388. package/src/widgets/__tests__/radio/base-radio.test.tsx +0 -470
  389. package/src/widgets/__tests__/radio/choice-icon.test.tsx +0 -90
  390. package/src/widgets/__tests__/radio/choice.test.tsx +0 -310
  391. package/src/widgets/__tests__/radio/focus-ring.test.tsx +0 -22
  392. package/src/widgets/__tests__/radio/option-status.test.tsx +0 -77
  393. package/src/widgets/__tests__/radio.test.ts +0 -807
  394. package/src/widgets/__tests__/randomizers.test.ts +0 -53
  395. package/src/widgets/__tests__/renderQuestion.tsx +0 -101
  396. package/src/widgets/__tests__/unit.test.tsx +0 -168
  397. package/src/widgets/__tests__/video.test.ts +0 -69
  398. package/src/widgets/categorizer.tsx +0 -344
  399. package/src/widgets/cs-program.tsx +0 -227
  400. package/src/widgets/definition.tsx +0 -121
  401. package/src/widgets/deprecated-standin/__stories__/deprecated-standin.stories.tsx +0 -30
  402. package/src/widgets/deprecated-standin/__tests__/__snapshots__/deprecated-standin.test.ts.snap +0 -70
  403. package/src/widgets/deprecated-standin/__tests__/deprecated-standin.test.ts +0 -93
  404. package/src/widgets/deprecated-standin/index.tsx +0 -58
  405. package/src/widgets/dropdown.tsx +0 -142
  406. package/src/widgets/explanation.tsx +0 -317
  407. package/src/widgets/expression.tsx +0 -707
  408. package/src/widgets/graded-group-answer-bar.tsx +0 -142
  409. package/src/widgets/graded-group-set.tsx +0 -338
  410. package/src/widgets/graded-group.tsx +0 -471
  411. package/src/widgets/grapher/util.tsx +0 -799
  412. package/src/widgets/grapher.tsx +0 -660
  413. package/src/widgets/group.tsx +0 -195
  414. package/src/widgets/iframe.tsx +0 -195
  415. package/src/widgets/image.tsx +0 -312
  416. package/src/widgets/input-number.tsx +0 -412
  417. package/src/widgets/interaction/arrow-picker.tsx +0 -38
  418. package/src/widgets/interaction/color-picker.tsx +0 -69
  419. package/src/widgets/interaction/dash-picker.tsx +0 -45
  420. package/src/widgets/interaction/element-container.tsx +0 -100
  421. package/src/widgets/interaction.tsx +0 -826
  422. package/src/widgets/interactive-graph.tsx +0 -2597
  423. package/src/widgets/label-image/__stories__/answer-choices.stories.tsx +0 -91
  424. package/src/widgets/label-image/__stories__/answer-pill.stories.tsx +0 -62
  425. package/src/widgets/label-image/__stories__/marker.stories.tsx +0 -171
  426. package/src/widgets/label-image/__tests__/answer-pill.test.tsx +0 -75
  427. package/src/widgets/label-image/__tests__/hide-answers-toggle.test.tsx +0 -39
  428. package/src/widgets/label-image/answer-choices.tsx +0 -88
  429. package/src/widgets/label-image/answer-pill.tsx +0 -103
  430. package/src/widgets/label-image/hide-answers-toggle.tsx +0 -40
  431. package/src/widgets/label-image/marker.tsx +0 -268
  432. package/src/widgets/label-image/strings.ts +0 -5
  433. package/src/widgets/label-image/types.ts +0 -27
  434. package/src/widgets/label-image.test.ts +0 -928
  435. package/src/widgets/label-image.tsx +0 -819
  436. package/src/widgets/lights-puzzle.tsx +0 -359
  437. package/src/widgets/matcher.tsx +0 -298
  438. package/src/widgets/matrix.tsx +0 -634
  439. package/src/widgets/measurer.tsx +0 -222
  440. package/src/widgets/molecule/__tests__/layout.test.ts +0 -162
  441. package/src/widgets/molecule/__tests__/parser.test.ts +0 -155
  442. package/src/widgets/molecule/molecule-drawing.ts +0 -173
  443. package/src/widgets/molecule/molecule-layout.ts +0 -362
  444. package/src/widgets/molecule/smiles-parser.ts +0 -296
  445. package/src/widgets/molecule.tsx +0 -172
  446. package/src/widgets/number-line.tsx +0 -862
  447. package/src/widgets/numeric-input.tsx +0 -576
  448. package/src/widgets/orderer.tsx +0 -786
  449. package/src/widgets/passage/__tests__/passage-markdown.test.ts +0 -376
  450. package/src/widgets/passage/passage-markdown.tsx +0 -475
  451. package/src/widgets/passage-ref-target.tsx +0 -76
  452. package/src/widgets/passage-ref.tsx +0 -213
  453. package/src/widgets/passage.tsx +0 -582
  454. package/src/widgets/plotter.tsx +0 -1194
  455. package/src/widgets/radio/__stories__/base-radio.stories.tsx +0 -196
  456. package/src/widgets/radio/__stories__/choice-icon.stories.tsx +0 -166
  457. package/src/widgets/radio/__stories__/choice-none-above.stories.tsx +0 -40
  458. package/src/widgets/radio/__stories__/choice.stories.tsx +0 -137
  459. package/src/widgets/radio/__stories__/focus-ring.stories.tsx +0 -43
  460. package/src/widgets/radio/__stories__/option-status.stories.tsx +0 -103
  461. package/src/widgets/radio/base-radio.tsx +0 -511
  462. package/src/widgets/radio/choice-a11y-text.ts +0 -61
  463. package/src/widgets/radio/choice-icon/choice-icon.tsx +0 -218
  464. package/src/widgets/radio/choice-icon/cross-out-line.tsx +0 -51
  465. package/src/widgets/radio/choice-icon/shared-styles.ts +0 -15
  466. package/src/widgets/radio/choice-none-above.tsx +0 -56
  467. package/src/widgets/radio/choice.jsx.fixture.ts +0 -45
  468. package/src/widgets/radio/choice.tsx +0 -359
  469. package/src/widgets/radio/focus-ring.tsx +0 -57
  470. package/src/widgets/radio/option-status.tsx +0 -98
  471. package/src/widgets/radio/util.test.ts +0 -18
  472. package/src/widgets/radio/util.ts +0 -31
  473. package/src/widgets/radio/widget.tsx +0 -520
  474. package/src/widgets/radio.ts +0 -157
  475. package/src/widgets/reaction-diagram.tsx +0 -144
  476. package/src/widgets/sequence.tsx +0 -142
  477. package/src/widgets/simulator.tsx +0 -829
  478. package/src/widgets/sorter.tsx +0 -155
  479. package/src/widgets/table.tsx +0 -393
  480. package/src/widgets/unit.tsx +0 -374
  481. package/src/widgets/video-transcript-link.test.tsx +0 -213
  482. package/src/widgets/video-transcript-link.tsx +0 -84
  483. package/src/widgets/video.tsx +0 -135
  484. package/src/widgets.ts +0 -489
  485. package/src/zoom.ts +0 -555
  486. package/tsconfig-build.json +0 -26
  487. package/tsconfig-build.tsbuildinfo +0 -1
@@ -1,4172 +0,0 @@
1
- /* eslint-disable @babel/no-invalid-this */
2
- /**
3
- * Interactive graphie utilities.
4
- *
5
- * This file exposes a couple functions, but mostly it adds functions to the
6
- * `Graphie` prototype for dealing with interactive graphie elements.
7
- */
8
-
9
- // TODO(emily): This file breaks our line length limits like nobody's business.
10
- // Figure out how to fix that.
11
- import {
12
- vector as kvector,
13
- point as kpoint,
14
- line as kline,
15
- } from "@khanacademy/kmath";
16
- import $ from "jquery";
17
- // eslint-disable-next-line import/no-extraneous-dependencies
18
- import Raphael from "raphael";
19
- import _ from "underscore";
20
-
21
- // NOTE(jeresig): We include a special copy of jQuery vmouse so
22
- // that Perseus will continue to work in mobile native devices
23
- // (this should have no impact in the browser)
24
- // eslint-disable-next-line import/no-unassigned-import
25
- import "../jquery.mobile.vmouse";
26
- import InteractiveUtil from "../interactive2/interactive-util";
27
- import WrappedEllipse from "../interactive2/wrapped-ellipse";
28
- import WrappedLine from "../interactive2/wrapped-line";
29
- import WrappedPath from "../interactive2/wrapped-path";
30
- import {Errors} from "../logging/log";
31
- import {PerseusError} from "../perseus-error";
32
-
33
- import KhanColors from "./colors";
34
- import {clockwise, reverseVector} from "./geometry";
35
- import GraphUtils, {polar} from "./graphie";
36
- import KhanMath from "./math";
37
-
38
- import type {Coord} from "../interactive2/types";
39
-
40
- export type MouseHandler = (position: Coord) => void;
41
-
42
- const {getCanUse3dTransform} = InteractiveUtil;
43
-
44
- function scaledDistanceFromAngle(angle: number) {
45
- const a = 3.51470560176242 * 20;
46
- const b = 0.5687298702748785 * 20;
47
- const c = -0.037587715462826674;
48
- return (a - b) * Math.exp(c * angle) + b;
49
- }
50
-
51
- function scaledPolarRad(radius: number, radians: number): Coord {
52
- return [
53
- radius * Math.cos(radians),
54
- radius * Math.sin(radians) * -1, // SVG flips y axis
55
- ];
56
- }
57
-
58
- function scaledPolarDeg(radius: number, degrees): Coord {
59
- const radians = (degrees * Math.PI) / 180;
60
- return scaledPolarRad(radius, radians);
61
- }
62
-
63
- // Global dragging state
64
- let dragging = false;
65
-
66
- const InteractiveUtils: any = {
67
- // Useful for shapes that are only sometimes drawn. If a shape isn't
68
- // needed, it can be replaced with bogusShape which just has stub methods
69
- // that successfully do nothing.
70
- // The alternative would be 'if..typeof' checks all over the place.
71
- bogusShape: {
72
- animate: function () {},
73
- attr: function () {},
74
- remove: function () {},
75
- },
76
- };
77
-
78
- _.extend(GraphUtils.Graphie.prototype, {
79
- // graphie puts text spans on top of the SVG, which looks good, but gets
80
- // in the way of mouse events. This adds another SVG element on top
81
- // of everything else where we can add invisible shapes with mouse
82
- // handlers wherever we want.
83
- addMouseLayer: function (options: {
84
- onClick?: MouseHandler;
85
- onMouseMove?: MouseHandler;
86
- onMouseDown?: MouseHandler;
87
- onMouseOver?: MouseHandler;
88
- onMouseOut?: MouseHandler;
89
- allowScratchpad?: boolean;
90
- setDrawingAreaAvailable?: (available: boolean) => void;
91
- }) {
92
- const graph = this;
93
- options = _.extend(
94
- {
95
- allowScratchpad: false,
96
- setDrawingAreaAvailable: function () {},
97
- },
98
- options,
99
- );
100
-
101
- const mouselayerZIndex = 2;
102
- graph.mouselayer = Raphael(
103
- graph.raphael.canvas.parentNode,
104
- graph.xpixels,
105
- graph.ypixels,
106
- );
107
- $(graph.mouselayer.canvas).css("z-index", mouselayerZIndex);
108
- if (
109
- options.onClick ||
110
- options.onMouseDown ||
111
- options.onMouseMove ||
112
- options.onMouseOver ||
113
- options.onMouseOut
114
- ) {
115
- const canvasClickTarget = graph.mouselayer
116
- .rect(0, 0, graph.xpixels, graph.ypixels)
117
- .attr({
118
- fill: "#000",
119
- opacity: 0,
120
- });
121
- let isClickingCanvas = false;
122
-
123
- $(graph.mouselayer.canvas).on("vmousedown", function (e) {
124
- if (e.target === canvasClickTarget[0]) {
125
- if (options.onMouseDown) {
126
- options.onMouseDown(graph.getMouseCoord(e));
127
- }
128
- isClickingCanvas = true;
129
-
130
- if (options.onMouseMove) {
131
- const handler = options.onMouseMove;
132
- $(document).bind("vmousemove.mouseLayer", function (e) {
133
- if (isClickingCanvas) {
134
- e.preventDefault();
135
- handler(graph.getMouseCoord(e));
136
- }
137
- });
138
- }
139
-
140
- $(document).bind("vmouseup.mouseLayer", function (e) {
141
- $(document).unbind(".mouseLayer");
142
-
143
- // Only register clicks that started on the canvas,
144
- // and not on another mouseLayer target
145
- if (isClickingCanvas && options.onClick) {
146
- options.onClick(graph.getMouseCoord(e));
147
- }
148
- isClickingCanvas = false;
149
- });
150
- }
151
- });
152
- if (options.onMouseOver) {
153
- const handler = options.onMouseOver;
154
- $(graph.mouselayer.canvas).on("vmouseover", function (e) {
155
- handler(graph.getMouseCoord(e));
156
- });
157
- }
158
- if (options.onMouseOut) {
159
- const handler = options.onMouseOut;
160
- $(graph.mouselayer.canvas).on("vmouseout", function (e) {
161
- handler(graph.getMouseCoord(e));
162
- });
163
- }
164
- }
165
- if (!options.allowScratchpad) {
166
- options.setDrawingAreaAvailable?.(false);
167
- }
168
-
169
- // Add mouse and visible wrapper layers for DOM-node-wrapped movables
170
- graph._mouselayerWrapper = document.createElement("div");
171
- $(graph._mouselayerWrapper).css({
172
- position: "absolute",
173
- left: 0,
174
- top: 0,
175
- zIndex: mouselayerZIndex,
176
- });
177
-
178
- graph._visiblelayerWrapper = document.createElement("div");
179
- $(graph._visiblelayerWrapper).css({
180
- position: "absolute",
181
- left: 0,
182
- top: 0,
183
- });
184
-
185
- const el = graph.raphael.canvas.parentNode;
186
- el.appendChild(graph._visiblelayerWrapper);
187
- el.appendChild(graph._mouselayerWrapper);
188
-
189
- // Add functions for adding to wrappers
190
- graph.addToMouseLayerWrapper = function (el: any) {
191
- this._mouselayerWrapper.appendChild(el);
192
- };
193
- graph.addToVisibleLayerWrapper = function (el: any) {
194
- this._visiblelayerWrapper.appendChild(el);
195
- };
196
- },
197
-
198
- /**
199
- * Get mouse coordinates in pixels
200
- */
201
- getMousePx: function (event) {
202
- const graphie = this;
203
-
204
- const mouseX =
205
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
206
- event.pageX - $(graphie.raphael.canvas.parentNode).offset().left;
207
- const mouseY =
208
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
209
- event.pageY - $(graphie.raphael.canvas.parentNode).offset().top;
210
-
211
- return [mouseX, mouseY];
212
- },
213
-
214
- /**
215
- * Get mouse coordinates in graph coordinates
216
- */
217
- getMouseCoord: function (event): Coord {
218
- return this.unscalePoint(this.getMousePx(event));
219
- },
220
-
221
- /**
222
- * Unlike all other Graphie-related code, the following three functions use
223
- * a lot of scaled coordinates (so that labels appear the same size
224
- * regardless of current shape/figure scale). These are prefixed with 's'.
225
- */
226
- labelAngle: function (options) {
227
- const graphie = this;
228
-
229
- _.defaults(options, {
230
- point1: [0, 0],
231
- vertex: [0, 0],
232
- point3: [0, 0],
233
- label: null,
234
- numArcs: 1,
235
- showRightAngleMarker: true,
236
- pushOut: 0,
237
- clockwise: false,
238
- style: {},
239
- });
240
-
241
- let text = options.text === undefined ? "" : options.text;
242
- const vertex = options.vertex;
243
- const sVertex = graphie.scalePoint(vertex);
244
- let p1;
245
- let p3;
246
- if (options.clockwise) {
247
- p1 = options.point1;
248
- p3 = options.point3;
249
- } else {
250
- p1 = options.point3;
251
- p3 = options.point1;
252
- }
253
-
254
- const startAngle = GraphUtils.findAngle(p1, vertex);
255
- const endAngle = GraphUtils.findAngle(p3, vertex);
256
- const angle = (endAngle + 360 - startAngle) % 360;
257
- const halfAngle = (startAngle + angle / 2) % 360;
258
- const sPadding = 5 * options.pushOut;
259
- let sRadius = sPadding + scaledDistanceFromAngle(angle);
260
- const temp: Array<never> = [];
261
-
262
- if (Math.abs(angle - 90) < 1e-9 && options.showRightAngleMarker) {
263
- const v1 = kvector.add(
264
- sVertex,
265
- scaledPolarDeg(sRadius, startAngle),
266
- );
267
- const v2 = kvector.add(sVertex, scaledPolarDeg(sRadius, endAngle));
268
-
269
- sRadius *= Math.SQRT2;
270
- const v3 = kvector.add(sVertex, scaledPolarDeg(sRadius, halfAngle));
271
-
272
- _.each([v1, v2], function (v) {
273
- // @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
274
- temp.push(graphie.scaledPath([v, v3], options.style));
275
- });
276
- } else {
277
- // Draw arcs
278
- _.times(options.numArcs, function (i) {
279
- temp.push(
280
- // @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
281
- graphie.arc(
282
- vertex,
283
- graphie.unscaleVector(sRadius),
284
- startAngle,
285
- endAngle,
286
- options.style,
287
- ),
288
- );
289
- sRadius += 3;
290
- });
291
- }
292
-
293
- if (text) {
294
- const match = text.match(/\$deg(\d)?/);
295
- if (match) {
296
- const precision = match[1] || 1;
297
- text = text.replace(
298
- match[0],
299
- KhanMath.toFixedApprox(angle, precision) + "^{\\circ}",
300
- );
301
- }
302
-
303
- const sOffset = scaledPolarDeg(sRadius + 15, halfAngle);
304
- const sPosition = kvector.add(sVertex, sOffset);
305
- const position = graphie.unscalePoint(sPosition);
306
-
307
- // Reuse label if possible
308
- if (options.label) {
309
- options.label.setPosition(position);
310
- options.label.processMath(text, /* force */ true);
311
- } else {
312
- graphie.label(position, text, "center", options.style);
313
- }
314
- }
315
-
316
- return temp;
317
- },
318
-
319
- labelSide: function (options) {
320
- const graphie = this;
321
-
322
- _.defaults(options, {
323
- point1: [0, 0],
324
- point2: [0, 0],
325
- label: null,
326
- text: "",
327
- numTicks: 0,
328
- numArrows: 0,
329
- clockwise: false,
330
- style: {},
331
- });
332
-
333
- let p1;
334
- let p2;
335
- if (options.clockwise) {
336
- p1 = options.point1;
337
- p2 = options.point2;
338
- } else {
339
- p1 = options.point2;
340
- p2 = options.point1;
341
- }
342
-
343
- const midpoint = [(p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2];
344
- const sMidpoint = graphie.scalePoint(midpoint);
345
- const parallelAngle = Math.atan2(p2[1] - p1[1], p2[0] - p1[0]);
346
- const perpendicularAngle = parallelAngle + Math.PI / 2;
347
- const temp: Array<never> = [];
348
- let sCumulativeOffset = 0;
349
-
350
- if (options.numTicks) {
351
- const n = options.numTicks;
352
-
353
- const sSpacing = 5;
354
- const sHeight = 5;
355
-
356
- const style = _.extend({}, options.style, {
357
- strokeWidth: 2,
358
- });
359
-
360
- _.times(n, function (i) {
361
- const sOffset = sSpacing * (i - (n - 1) / 2);
362
-
363
- const sOffsetVector = scaledPolarRad(sOffset, parallelAngle);
364
- const sHeightVector = scaledPolarRad(
365
- sHeight,
366
- perpendicularAngle,
367
- );
368
-
369
- const sPath = [
370
- kvector.add(sMidpoint, sOffsetVector, sHeightVector),
371
- kvector.add(
372
- sMidpoint,
373
- sOffsetVector,
374
- reverseVector(sHeightVector),
375
- ),
376
- ];
377
-
378
- // @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
379
- temp.push(graphie.scaledPath(sPath, style));
380
- });
381
-
382
- sCumulativeOffset += sSpacing * (n - 1) + 15;
383
- }
384
-
385
- if (options.numArrows) {
386
- const n = options.numArrows;
387
-
388
- const start = [p1, p2].sort(function (a, b) {
389
- if (a[1] === b[1]) {
390
- return a[0] - b[0];
391
- }
392
- return a[1] - b[1];
393
- })[0];
394
- const sStart = graphie.scalePoint(start);
395
-
396
- const style = _.extend({}, options.style, {
397
- arrows: "->",
398
- strokeWidth: 2,
399
- });
400
-
401
- const sSpacing = 5;
402
-
403
- _.times(n, function (i) {
404
- const sOffset = sCumulativeOffset + sSpacing * i;
405
- let sOffsetVector = scaledPolarRad(sOffset, parallelAngle);
406
-
407
- if (start !== p1) {
408
- sOffsetVector = reverseVector(sOffsetVector);
409
- }
410
-
411
- const sEnd = kvector.add(sMidpoint, sOffsetVector);
412
-
413
- // @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
414
- temp.push(graphie.scaledPath([sStart, sEnd], style));
415
- });
416
- }
417
-
418
- let text = options.text;
419
- if (text) {
420
- const match = text.match(/\$len(\d)?/);
421
- if (match) {
422
- const distance = GraphUtils.getDistance(p1, p2);
423
- const precision = match[1] || 1;
424
- text = text.replace(
425
- match[0],
426
- KhanMath.toFixedApprox(distance, precision),
427
- );
428
- }
429
-
430
- const sOffset = 20;
431
- const sOffsetVector = scaledPolarRad(sOffset, perpendicularAngle);
432
- const sPosition = kvector.add(sMidpoint, sOffsetVector);
433
- const position = graphie.unscalePoint(sPosition);
434
-
435
- // Reuse label if possible
436
- if (options.label) {
437
- options.label.setPosition(position);
438
- options.label.processMath(text, /* force */ true);
439
- } else {
440
- graphie.label(position, text, "center", options.style);
441
- }
442
- }
443
-
444
- return temp;
445
- },
446
-
447
- /* Can also be used to label points that aren't vertices */
448
- labelVertex: function (options) {
449
- const graphie = this;
450
-
451
- _.defaults(options, {
452
- point1: null,
453
- vertex: [0, 0],
454
- point3: null,
455
- label: null,
456
- text: "",
457
- clockwise: false,
458
- style: {},
459
- });
460
-
461
- if (!options.text) {
462
- return;
463
- }
464
-
465
- const vertex = options.vertex;
466
- const sVertex = graphie.scalePoint(vertex);
467
- let p1;
468
- let p3;
469
- if (options.clockwise) {
470
- p1 = options.point1;
471
- p3 = options.point3;
472
- } else {
473
- p1 = options.point3;
474
- p3 = options.point1;
475
- }
476
-
477
- let angle = 135;
478
- let halfAngle;
479
- if (p1 && p3) {
480
- const startAngle = GraphUtils.findAngle(p1, vertex);
481
- const endAngle = GraphUtils.findAngle(p3, vertex);
482
- angle = (endAngle + 360 - startAngle) % 360;
483
- halfAngle = (startAngle + angle / 2 + 180) % 360;
484
- } else if (p1) {
485
- const parallelAngle = GraphUtils.findAngle(vertex, p1);
486
- halfAngle = parallelAngle + 90;
487
- } else if (p3) {
488
- const parallelAngle = GraphUtils.findAngle(p3, vertex);
489
- halfAngle = parallelAngle + 90;
490
- } else {
491
- // Standalone point
492
- halfAngle = 135;
493
- }
494
-
495
- const sRadius = 10 + scaledDistanceFromAngle(360 - angle);
496
- const sOffsetVector = scaledPolarDeg(sRadius, halfAngle);
497
- const sPosition = kvector.add(sVertex, sOffsetVector);
498
- const position = graphie.unscalePoint(sPosition);
499
-
500
- // Reuse label if possible
501
- if (options.label) {
502
- options.label.setPosition(position);
503
- options.label.processMath(options.text, /* force */ true);
504
- } else {
505
- graphie.label(position, options.text, "center", options.style);
506
- }
507
- },
508
-
509
- // Add a point to the graph that can be dragged around.
510
- // It allows automatic constraints on its movement as well as automatically
511
- // managing line segments that terminate at the point.
512
- //
513
- // Options can be set to control how the point behaves:
514
- // coord[]:
515
- // The initial position of the point
516
- // snapX, snapY:
517
- // The minimum increment the point can be moved
518
- //
519
- // The return value is an object that can be used to manipulate the point:
520
- // The coordX and coordY properties tell you the current position
521
- //
522
- // By adding an onMove() method to the returned object, you can install an
523
- // event handler that gets called every time the user moves the point.
524
- //
525
- // The returned object also provides a moveTo(x,y) method that will move
526
- // the point to a specific coordinate
527
- //
528
- // Constraints can be set on the on the returned object:
529
- //
530
- // - Set point to be immovable:
531
- // movablePoint.constraints.fixed = true
532
- //
533
- // - Constrain point to a fixed distance from another point. The resulting
534
- // point will move in a circle:
535
- // movablePoint.fixedDistance = {
536
- // dist: 2,
537
- // point: point1
538
- // }
539
- //
540
- // - Constrain point to a line defined by a fixed angle between it and
541
- // two other points:
542
- // movablePoint.fixedAngle = {
543
- // angle: 45,
544
- // vertex: point1,
545
- // ref: point2
546
- // }
547
- //
548
- // - Confined the point to traveling in a vertical or horizontal line,
549
- // respectively
550
- // movablePoint.constrainX = true;
551
- // movablePoint.constrainY = true;
552
- //
553
- // - Connect a movableLineSegment to a movablePoint. The point is attached
554
- // to a specific end of the line segment by adding the segment either to
555
- // the list of lines that start at the point or the list of lines that
556
- // end at the point (movableLineSegment can do this for you):
557
- // movablePoint.lineStarts.push(movableLineSegment);
558
- // - or -
559
- // movablePoint.lineEnds.push(movableLineSegment);
560
- //
561
- // - Connect a movablePolygon to a movablePoint in exacty the same way:
562
- // movablePoint.polygonVertices.push(movablePolygon);
563
- //
564
- addMovablePoint: function (options) {
565
- const movablePoint = $.extend(
566
- true,
567
- {
568
- graph: this,
569
- coord: [0, 0],
570
- snapX: 0,
571
- snapY: 0,
572
- pointSize: 4,
573
- highlight: false,
574
- dragging: false,
575
- visible: true,
576
- bounded: true,
577
- constraints: {
578
- fixed: false,
579
- constrainX: false,
580
- constrainY: false,
581
- fixedAngle: {},
582
- fixedDistance: {},
583
- },
584
- lineStarts: [],
585
- lineEnds: [],
586
- polygonVertices: [],
587
- normalStyle: {},
588
- highlightStyle: {
589
- fill: KhanColors.INTERACTING,
590
- stroke: KhanColors.INTERACTING,
591
- },
592
- labelStyle: {
593
- color: KhanColors.INTERACTIVE,
594
- },
595
- vertexLabel: "",
596
- mouseTarget: null,
597
- },
598
- options,
599
- );
600
-
601
- const normalColor = movablePoint.constraints.fixed
602
- ? KhanColors.DYNAMIC
603
- : KhanColors.INTERACTIVE;
604
- movablePoint.normalStyle = _.extend(
605
- {},
606
- {
607
- fill: normalColor,
608
- stroke: normalColor,
609
- },
610
- options.normalStyle,
611
- );
612
-
613
- // deprecated: don't use coordX/coordY; use coord[]
614
- if (options.coordX !== undefined) {
615
- movablePoint.coord[0] = options.coordX;
616
- }
617
- if (options.coordY !== undefined) {
618
- movablePoint.coord[1] = options.coordY;
619
- }
620
-
621
- const graph = movablePoint.graph;
622
-
623
- const applySnapAndConstraints = function (coord: any) {
624
- // coord should be the scaled coordinate
625
-
626
- // move point away from edge of graph unless it's invisible or fixed
627
- if (
628
- movablePoint.visible &&
629
- movablePoint.bounded &&
630
- !movablePoint.constraints.fixed
631
- ) {
632
- // can't go beyond 10 pixels from the edge
633
- coord = graph.constrainToBounds(coord, 10);
634
- }
635
-
636
- let coordX = coord[0];
637
- let coordY = coord[1];
638
-
639
- // snap coordinates to grid
640
- if (movablePoint.snapX !== 0) {
641
- coordX =
642
- Math.round(coordX / movablePoint.snapX) *
643
- movablePoint.snapX;
644
- }
645
- if (movablePoint.snapY !== 0) {
646
- coordY =
647
- Math.round(coordY / movablePoint.snapY) *
648
- movablePoint.snapY;
649
- }
650
-
651
- // snap to points around circle
652
- if (movablePoint.constraints.fixedDistance.snapPoints) {
653
- const mouse = graph.scalePoint(coord);
654
- let mouseX = mouse[0];
655
- let mouseY = mouse[1];
656
-
657
- const snapRadians =
658
- (2 * Math.PI) /
659
- movablePoint.constraints.fixedDistance.snapPoints;
660
- const radius = movablePoint.constraints.fixedDistance.dist;
661
-
662
- const centerCoord =
663
- movablePoint.constraints.fixedDistance.point;
664
- const centerX =
665
- (centerCoord[0] - graph.range[0][0]) * graph.scale[0];
666
- const centerY =
667
- (-centerCoord[1] + graph.range[1][1]) * graph.scale[1];
668
-
669
- let mouseXrel = mouseX - centerX;
670
- let mouseYrel = -mouseY + centerY;
671
- let radians = Math.atan(mouseYrel / mouseXrel);
672
- const outsideArcTanRange = mouseXrel < 0;
673
-
674
- // adjust so that angles increase from 0 to 2 pi as you go
675
- // around the circle
676
- if (outsideArcTanRange) {
677
- radians += Math.PI;
678
- }
679
-
680
- // perform the snap
681
- radians = Math.round(radians / snapRadians) * snapRadians;
682
-
683
- // convert from radians back to pixels
684
- mouseXrel = radius * Math.cos(radians);
685
- mouseYrel = radius * Math.sin(radians);
686
- // convert back to coordinates relative to graphie canvas
687
- mouseX = mouseXrel + centerX;
688
- mouseY = -mouseYrel + centerY;
689
- coordX = KhanMath.roundTo(
690
- 5,
691
- mouseX / graph.scale[0] + graph.range[0][0],
692
- );
693
- coordY = KhanMath.roundTo(
694
- 5,
695
- graph.range[1][1] - mouseY / graph.scale[1],
696
- );
697
- }
698
-
699
- const result = movablePoint.applyConstraint([coordX, coordY]);
700
- return result;
701
- };
702
-
703
- // Using the passed coordinates, apply any constraints and return
704
- // the closest coordinates that match the constraints.
705
- movablePoint.applyConstraint = function (
706
- coord: any,
707
- extraConstraints: any,
708
- override: any,
709
- ) {
710
- let newCoord = coord.slice();
711
- const constraints: Record<string, any> = {};
712
- if (override) {
713
- $.extend(
714
- constraints,
715
- {
716
- fixed: false,
717
- constrainX: false,
718
- constrainY: false,
719
- fixedAngle: {},
720
- fixedDistance: {},
721
- },
722
- extraConstraints,
723
- );
724
- } else {
725
- $.extend(constraints, this.constraints, extraConstraints);
726
- }
727
-
728
- // constrain to vertical movement
729
- if (constraints.constrainX) {
730
- newCoord = [this.coord[0], coord[1]];
731
-
732
- // constrain to horizontal movement
733
- } else if (constraints.constrainY) {
734
- newCoord = [coord[0], this.coord[1]];
735
-
736
- // both distance and angle are constrained
737
- } else if (
738
- typeof constraints.fixedAngle.angle === "number" &&
739
- typeof constraints.fixedDistance.dist === "number"
740
- ) {
741
- const vertex =
742
- constraints.fixedAngle.vertex.coord ||
743
- constraints.fixedAngle.vertex;
744
- const ref =
745
- constraints.fixedAngle.ref.coord ||
746
- constraints.fixedAngle.ref;
747
- const distPoint =
748
- constraints.fixedDistance.point.coord ||
749
- constraints.fixedDistance.point;
750
-
751
- const constrainedAngle =
752
- ((constraints.fixedAngle.angle +
753
- GraphUtils.findAngle(ref, vertex)) *
754
- Math.PI) /
755
- 180;
756
- const length = constraints.fixedDistance.dist;
757
- newCoord[0] =
758
- length * Math.cos(constrainedAngle) + distPoint[0];
759
- newCoord[1] =
760
- length * Math.sin(constrainedAngle) + distPoint[1];
761
-
762
- // angle is constrained
763
- } else if (typeof constraints.fixedAngle.angle === "number") {
764
- const vertex =
765
- constraints.fixedAngle.vertex.coord ||
766
- constraints.fixedAngle.vertex;
767
- const ref =
768
- constraints.fixedAngle.ref.coord ||
769
- constraints.fixedAngle.ref;
770
-
771
- const constrainedAngle =
772
- ((constraints.fixedAngle.angle +
773
- GraphUtils.findAngle(ref, vertex)) *
774
- Math.PI) /
775
- 180;
776
- const angle =
777
- (GraphUtils.findAngle(coord, vertex) * Math.PI) / 180;
778
- const distance = GraphUtils.getDistance(coord, vertex);
779
- let length = distance * Math.cos(constrainedAngle - angle);
780
- length = length < 1.0 ? 1.0 : length;
781
- newCoord[0] = length * Math.cos(constrainedAngle) + vertex[0];
782
- newCoord[1] = length * Math.sin(constrainedAngle) + vertex[1];
783
-
784
- // distance is constrained
785
- } else if (typeof constraints.fixedDistance.dist === "number") {
786
- const distPoint =
787
- constraints.fixedDistance.point.coord ||
788
- constraints.fixedDistance.point;
789
-
790
- let angle = GraphUtils.findAngle(coord, distPoint);
791
- const length = constraints.fixedDistance.dist;
792
- angle = (angle * Math.PI) / 180;
793
- newCoord[0] = length * Math.cos(angle) + distPoint[0];
794
- newCoord[1] = length * Math.sin(angle) + distPoint[1];
795
-
796
- // point is fixed
797
- } else if (constraints.fixed) {
798
- newCoord = movablePoint.coord;
799
- }
800
- return newCoord;
801
- };
802
-
803
- movablePoint.coord = applySnapAndConstraints(movablePoint.coord);
804
-
805
- const highlightScale = 2;
806
-
807
- if (movablePoint.visible) {
808
- graph.style(movablePoint.normalStyle, function () {
809
- const radii = [
810
- movablePoint.pointSize / graph.scale[0],
811
- movablePoint.pointSize / graph.scale[1],
812
- ];
813
- const options = {
814
- maxScale: highlightScale,
815
- // Add in 2px of padding to avoid clipping at the edges.
816
- padding: 2,
817
- } as const;
818
- movablePoint.visibleShape = new WrappedEllipse(
819
- graph,
820
- movablePoint.coord,
821
- // @ts-expect-error - TS2345 - Argument of type 'number[]' is not assignable to parameter of type '[number, number]'.
822
- radii,
823
- options,
824
- );
825
- movablePoint.visibleShape.attr(
826
- _.omit(movablePoint.normalStyle, "scale"),
827
- );
828
- movablePoint.visibleShape.toFront();
829
- });
830
- }
831
- movablePoint.normalStyle.scale = 1;
832
- movablePoint.highlightStyle.scale = highlightScale;
833
-
834
- if (movablePoint.vertexLabel) {
835
- movablePoint.labeledVertex = this.label(
836
- [0, 0],
837
- "",
838
- "center",
839
- movablePoint.labelStyle,
840
- );
841
- }
842
-
843
- movablePoint.drawLabel = function () {
844
- if (movablePoint.vertexLabel) {
845
- movablePoint.graph.labelVertex({
846
- vertex: movablePoint.coord,
847
- label: movablePoint.labeledVertex,
848
- text: movablePoint.vertexLabel,
849
- style: movablePoint.labelStyle,
850
- });
851
- }
852
- };
853
-
854
- movablePoint.drawLabel();
855
-
856
- movablePoint.grab = function (offset) {
857
- // The offset for the gesture. When provided, the movable point will
858
- // track the mouse's position, plus this offset. This is typically
859
- // used to lock the distance between a user's finger and the movable
860
- // point, when dragging.
861
- offset = offset || [0, 0];
862
-
863
- $(document).bind(
864
- "vmousemove.point vmouseup.point",
865
- function (event) {
866
- event.preventDefault();
867
- movablePoint.dragging = true;
868
- dragging = true;
869
-
870
- // Adjust the target coordinate by accounting for the gesture's
871
- // offset.
872
- let coord = kvector.add(graph.getMouseCoord(event), offset);
873
-
874
- coord = applySnapAndConstraints(coord);
875
- let coordX = coord[0];
876
- let coordY = coord[1];
877
- let mouseX;
878
- let mouseY;
879
-
880
- if (event.type === "vmousemove") {
881
- let doMove = true;
882
- // The caller has the option of adding an onMove() method
883
- // to the movablePoint object we return as a sort of event
884
- // handler. By returning false from onMove(), the move
885
- // can be vetoed, providing custom constraints on where
886
- // the point can be moved. By returning array [x, y], the
887
- // move can be overridden
888
- if (_.isFunction(movablePoint.onMove)) {
889
- const result = movablePoint.onMove(coordX, coordY);
890
- if (result === false) {
891
- doMove = false;
892
- }
893
- if (_.isArray(result)) {
894
- coordX = result[0];
895
- coordY = result[1];
896
- }
897
- }
898
- // coord{X|Y} may have been modified by constraints or
899
- // onMove handler; adjust mouse{X|Y} to match
900
- mouseX = (coordX - graph.range[0][0]) * graph.scale[0];
901
- mouseY = (-coordY + graph.range[1][1]) * graph.scale[1];
902
-
903
- if (doMove) {
904
- const point = graph.unscalePoint([mouseX, mouseY]);
905
- movablePoint.visibleShape.moveTo(point);
906
- movablePoint.mouseTarget.moveTo(point);
907
- movablePoint.coord = [coordX, coordY];
908
- movablePoint.updateLineEnds();
909
- $(movablePoint).trigger("move");
910
- }
911
-
912
- movablePoint.drawLabel();
913
- } else if (event.type === "vmouseup") {
914
- $(document).unbind(".point");
915
- movablePoint.dragging = false;
916
- dragging = false;
917
- if (_.isFunction(movablePoint.onMoveEnd)) {
918
- const result = movablePoint.onMoveEnd(
919
- coordX,
920
- coordY,
921
- );
922
- if (_.isArray(result)) {
923
- coordX = result[0];
924
- coordY = result[1];
925
- mouseX =
926
- (coordX - graph.range[0][0]) *
927
- graph.scale[0];
928
- mouseY =
929
- (-coordY + graph.range[1][1]) *
930
- graph.scale[1];
931
- const point = graph.unscalePoint([
932
- mouseX,
933
- mouseY,
934
- ]);
935
- movablePoint.visibleShape.moveTo(point);
936
- movablePoint.mouseTarget.moveTo(point);
937
- movablePoint.coord = [coordX, coordY];
938
- }
939
- }
940
- if (!movablePoint.highlight) {
941
- movablePoint.visibleShape.animate(
942
- movablePoint.normalStyle,
943
- 50,
944
- );
945
- if (movablePoint.onUnhighlight) {
946
- movablePoint.onUnhighlight();
947
- }
948
- }
949
- }
950
- },
951
- );
952
- };
953
-
954
- if (movablePoint.visible && !movablePoint.constraints.fixed) {
955
- // the invisible shape in front of the point that gets mouse events
956
- if (!movablePoint.mouseTarget) {
957
- const radii = graph.unscaleVector(16);
958
- const options = {
959
- mouselayer: true,
960
- padding: 0,
961
- disableMouseEventsOnWrapper: true,
962
- interactiveKindForTesting: "movable-point",
963
- } as const;
964
- movablePoint.mouseTarget = new WrappedEllipse(
965
- graph,
966
- movablePoint.coord,
967
- radii,
968
- options,
969
- );
970
- movablePoint.mouseTarget.attr({fill: "#000", opacity: 0.0});
971
- }
972
-
973
- const $mouseTarget = $(movablePoint.mouseTarget.getMouseTarget());
974
- $mouseTarget.css("cursor", "move");
975
- $mouseTarget.bind(
976
- "vmousedown vmouseover vmouseout",
977
- function (event) {
978
- if (event.type === "vmouseover") {
979
- movablePoint.highlight = true;
980
- if (!dragging) {
981
- movablePoint.visibleShape.animate(
982
- movablePoint.highlightStyle,
983
- 50,
984
- );
985
- if (movablePoint.onHighlight) {
986
- movablePoint.onHighlight();
987
- }
988
- }
989
- } else if (event.type === "vmouseout") {
990
- movablePoint.highlight = false;
991
- if (!movablePoint.dragging && !dragging) {
992
- movablePoint.visibleShape.animate(
993
- movablePoint.normalStyle,
994
- 50,
995
- );
996
- if (movablePoint.onUnhighlight) {
997
- movablePoint.onUnhighlight();
998
- }
999
- }
1000
- } else if (
1001
- event.type === "vmousedown" &&
1002
- (event.which === 1 || event.which === 0)
1003
- ) {
1004
- event.preventDefault();
1005
-
1006
- // The offset between the cursor or finger and the initial
1007
- // coordinates of the point. This is tracked so as to avoid
1008
- // locking the moving point to the user's finger on touch
1009
- // devices, which would obscure it, no matter how large we
1010
- // made the touch target. Instead, we respect the offset at
1011
- // which the point was grabbed for the entirety of the
1012
- // gesture, if it's a touch-based interaction.
1013
- const startCoord = movablePoint.coord;
1014
- const startMouseCoord = graph.getMouseCoord(event);
1015
- const isMouse = !("ontouchstart" in window);
1016
- const touchOffset = isMouse
1017
- ? [0, 0]
1018
- : kvector.subtract(startCoord, startMouseCoord);
1019
-
1020
- movablePoint.grab(touchOffset);
1021
- }
1022
- },
1023
- );
1024
- }
1025
-
1026
- // Method to let the caller animate the point to a new position.
1027
- // Useful as part of a hint to show the user the correct place
1028
- // to put the point.
1029
- movablePoint.moveTo = function (
1030
- coordX: any,
1031
- coordY: any,
1032
- updateLines: any,
1033
- ) {
1034
- const distance = GraphUtils.getDistance(
1035
- this.graph.scalePoint([coordX, coordY]),
1036
- this.graph.scalePoint(this.coord),
1037
- );
1038
-
1039
- const time = distance * 5;
1040
-
1041
- const cb =
1042
- updateLines &&
1043
- function (coord: any) {
1044
- movablePoint.coord = coord;
1045
- movablePoint.updateLineEnds();
1046
- };
1047
- this.visibleShape.animateTo([coordX, coordY], time, cb);
1048
- this.mouseTarget.animateTo([coordX, coordY], time, cb);
1049
- this.coord = [coordX, coordY];
1050
- if (_.isFunction(this.onMove)) {
1051
- this.onMove(coordX, coordY);
1052
- }
1053
- };
1054
-
1055
- // After moving the point, call this to update all line segments
1056
- // terminating at the point
1057
- movablePoint.updateLineEnds = function () {
1058
- $(this.lineStarts).each(function () {
1059
- this.coordA = movablePoint.coord;
1060
- this.transform();
1061
- });
1062
- $(this.lineEnds).each(function () {
1063
- this.coordZ = movablePoint.coord;
1064
- this.transform();
1065
- });
1066
- $(this.polygonVertices).each(function () {
1067
- this.transform();
1068
- });
1069
- };
1070
-
1071
- // Put the point at a new position without any checks, animation,
1072
- // or callbacks
1073
- movablePoint.setCoord = function (coord: any) {
1074
- if (this.visible) {
1075
- this.visibleShape.moveTo(coord);
1076
- if (this.mouseTarget != null) {
1077
- this.mouseTarget.moveTo(coord);
1078
- }
1079
- }
1080
- this.coord = coord.slice();
1081
- };
1082
-
1083
- // Put the point at the new position, checking that it is
1084
- // within the graph's bounds
1085
- movablePoint.setCoordConstrained = function (coord: any) {
1086
- this.setCoord(applySnapAndConstraints(coord));
1087
- };
1088
-
1089
- // Change z-order to back
1090
- movablePoint.toBack = function () {
1091
- if (this.visible) {
1092
- if (this.mouseTarget != null) {
1093
- this.mouseTarget.toBack();
1094
- }
1095
- this.visibleShape.toBack();
1096
- }
1097
- };
1098
-
1099
- // Change z-order to front
1100
- movablePoint.toFront = function () {
1101
- if (this.visible) {
1102
- if (this.mouseTarget != null) {
1103
- this.mouseTarget.toFront();
1104
- }
1105
- this.visibleShape.toFront();
1106
- }
1107
- };
1108
-
1109
- movablePoint.remove = function () {
1110
- if (this.visibleShape) {
1111
- this.visibleShape.remove();
1112
- }
1113
- if (this.mouseTarget) {
1114
- this.mouseTarget.remove();
1115
- }
1116
- if (this.labeledVertex) {
1117
- this.labeledVertex.remove();
1118
- }
1119
- };
1120
-
1121
- return movablePoint;
1122
- },
1123
-
1124
- // MovableLineSegment is a line segment that can be dragged around the
1125
- // screen. By attaching a smartPoint to each (or one) end, the ends can be
1126
- // manipulated individually.
1127
- //
1128
- // To use with smartPoints, add the smartPoints first, then:
1129
- // addMovableLineSegment({ pointA: smartPoint1, pointZ: smartPoint2 });
1130
- // Or just one end:
1131
- // addMovableLineSegment({ pointA: smartPoint, coordZ: [0, 0] });
1132
- //
1133
- // Include "fixed: true" in the options if you don't want the entire line
1134
- // to be draggable (you can still use points to make the endpoints
1135
- // draggable)
1136
- //
1137
- // The returned object includes the following properties/methods:
1138
- //
1139
- // - lineSegment.coordA / lineSegment.coordZ
1140
- // The coordinates of each end of the line segment
1141
- //
1142
- // - lineSegment.transform(syncToPoints)
1143
- // Repositions the line segment. Call after changing coordA
1144
- // and/or coordZ, or pass syncToPoints = true to use the current
1145
- // position of the corresponding smartPoints, if the segment was
1146
- // defined using smartPoints
1147
- //
1148
- addMovableLineSegment: function (options: any) {
1149
- const lineSegment = $.extend(
1150
- {
1151
- graph: this,
1152
- coordA: [0, 0],
1153
- coordZ: [1, 1],
1154
- snapX: 0,
1155
- snapY: 0,
1156
- fixed: false,
1157
- ticks: 0,
1158
- normalStyle: {},
1159
- highlightStyle: {
1160
- stroke: KhanColors.INTERACTING,
1161
- "stroke-width": 6,
1162
- },
1163
- labelStyle: {
1164
- stroke: KhanColors.INTERACTIVE,
1165
- color: KhanColors.INTERACTIVE,
1166
- },
1167
- highlight: false,
1168
- dragging: false,
1169
- tick: [],
1170
- extendLine: false,
1171
- extendRay: false,
1172
- constraints: {
1173
- fixed: false,
1174
- constrainX: false,
1175
- constrainY: false,
1176
- },
1177
- sideLabel: "",
1178
- vertexLabels: [],
1179
- numArrows: 0,
1180
- numTicks: 0,
1181
- movePointsWithLine: false,
1182
- },
1183
- options,
1184
- );
1185
-
1186
- const normalColor = lineSegment.fixed
1187
- ? KhanColors.DYNAMIC
1188
- : KhanColors.INTERACTIVE;
1189
- lineSegment.normalStyle = _.extend(
1190
- {},
1191
- {
1192
- "stroke-width": 2,
1193
- stroke: normalColor,
1194
- },
1195
- options.normalStyle,
1196
- );
1197
- // arrowStyle should be kept in sync with styling of the line
1198
- lineSegment.arrowStyle = _.extend({}, lineSegment.normalStyle, {
1199
- color: lineSegment.normalStyle.stroke,
1200
- });
1201
-
1202
- // If the line segment is defined by movablePoints, coordA/coordZ are
1203
- // owned by the points, otherwise they're owned by us
1204
- if (options.pointA !== undefined) {
1205
- lineSegment.coordA = options.pointA.coord;
1206
- lineSegment.pointA.lineStarts.push(lineSegment);
1207
- } else if (options.coordA !== undefined) {
1208
- lineSegment.coordA = options.coordA.slice();
1209
- }
1210
-
1211
- if (options.pointZ !== undefined) {
1212
- lineSegment.coordZ = options.pointZ.coord;
1213
- lineSegment.pointZ.lineEnds.push(lineSegment);
1214
- } else if (options.coordA !== undefined) {
1215
- lineSegment.coordA = lineSegment.coordA.slice();
1216
- }
1217
-
1218
- const graph = lineSegment.graph;
1219
-
1220
- graph.style(lineSegment.normalStyle);
1221
- for (let i = 0; i < lineSegment.ticks; ++i) {
1222
- lineSegment.tick[i] = InteractiveUtils.bogusShape;
1223
- }
1224
- let path = GraphUtils.unscaledSvgPath([
1225
- [0, 0],
1226
- [1, 0],
1227
- ]);
1228
- for (let i = 0; i < lineSegment.ticks; ++i) {
1229
- const tickoffset =
1230
- 0.5 - (lineSegment.ticks - 1 + i * 2) / graph.scale[0];
1231
- // TODO(kevinb) figure out why path isn't being used
1232
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
1233
- path += GraphUtils.unscaledSvgPath([
1234
- [tickoffset, -7],
1235
- [tickoffset, 7],
1236
- ]);
1237
- }
1238
-
1239
- options = {
1240
- thickness: Math.max(
1241
- lineSegment.normalStyle["stroke-width"],
1242
- lineSegment.highlightStyle["stroke-width"],
1243
- ),
1244
- };
1245
- lineSegment.visibleLine = new WrappedLine(
1246
- graph,
1247
- [0, 0],
1248
- [1, 0],
1249
- options,
1250
- );
1251
- lineSegment.visibleLine.attr(lineSegment.normalStyle);
1252
-
1253
- // Add mouse target
1254
- if (!lineSegment.fixed) {
1255
- const options = {
1256
- thickness: 30,
1257
- mouselayer: true,
1258
- interactiveKindForTesting: "movable-line",
1259
- } as const;
1260
- lineSegment.mouseTarget = new WrappedLine(
1261
- graph,
1262
- [0, 0],
1263
- [1, 0],
1264
- options,
1265
- );
1266
- lineSegment.mouseTarget.attr({fill: "#000", opacity: 0.0});
1267
- }
1268
-
1269
- // Reposition the line segment. Call after changing coordA and/or
1270
- // coordZ, or pass syncToPoints = true to use the current position of
1271
- // the corresponding movablePoints, if the segment was defined using
1272
- // movablePoints
1273
- lineSegment.transform = function (syncToPoints) {
1274
- if (syncToPoints) {
1275
- if (typeof this.pointA === "object") {
1276
- this.coordA = this.pointA.coord;
1277
- }
1278
- if (typeof this.pointZ === "object") {
1279
- this.coordZ = this.pointZ.coord;
1280
- }
1281
- }
1282
-
1283
- const getScaledAngle = function (line) {
1284
- const scaledA = line.graph.scalePoint(line.coordA);
1285
- const scaledZ = line.graph.scalePoint(line.coordZ);
1286
- return kvector.polarDegFromCart(
1287
- kvector.subtract(scaledZ, scaledA),
1288
- )[1];
1289
- };
1290
-
1291
- const getClipPoint = function (graph: any, coord, angle: number) {
1292
- graph = lineSegment.graph;
1293
- const xExtent = graph.range[0][1] - graph.range[0][0];
1294
- const yExtent = graph.range[1][1] - graph.range[1][0];
1295
-
1296
- const distance = xExtent + yExtent;
1297
- const angleVec = graph.unscaleVector(
1298
- kvector.cartFromPolarDeg(1, angle),
1299
- );
1300
- const distVec = kvector.scale(
1301
- kvector.normalize(angleVec),
1302
- distance,
1303
- );
1304
- const farCoord = kvector.add(coord, distVec);
1305
- const scaledAngle = kvector.polarDegFromCart(angleVec)[1];
1306
- const clipPoint = graph.constrainToBoundsOnAngle(
1307
- farCoord,
1308
- 4,
1309
- (scaledAngle * Math.PI) / 180,
1310
- );
1311
- return clipPoint;
1312
- };
1313
-
1314
- const angle = getScaledAngle(this);
1315
- let start = this.coordA;
1316
- let end = this.coordZ;
1317
-
1318
- // Extend start, end if necessary (i.e., if not a line segment)
1319
- if (this.extendLine) {
1320
- start = getClipPoint(graph, start, 360 - angle);
1321
- end = getClipPoint(graph, end, (540 - angle) % 360);
1322
- } else if (this.extendRay) {
1323
- end = getClipPoint(graph, start, 360 - angle);
1324
- }
1325
-
1326
- const elements = [this.visibleLine];
1327
- if (!this.fixed) {
1328
- elements.push(this.mouseTarget);
1329
- }
1330
- _.each(elements, function (element) {
1331
- element.moveTo(start, end);
1332
- });
1333
-
1334
- const createArrow = function (graph: any, style) {
1335
- const center = [0.75, 0];
1336
- let points = [
1337
- [-3, 4],
1338
- [-2.75, 2.5],
1339
- [0, 0.25],
1340
- center,
1341
- [0, -0.25],
1342
- [-2.75, -2.5],
1343
- [-3, -4],
1344
- ];
1345
-
1346
- const scale = 1.4;
1347
- points = _.map(points, function (point) {
1348
- const pv = kvector.subtract(point, center);
1349
- const pvScaled = kvector.scale(pv, scale);
1350
- return kvector.add(center, pvScaled);
1351
- });
1352
-
1353
- const createCubicPath = function (points: any) {
1354
- let path = "M" + points[0][0] + " " + points[0][1];
1355
- for (let i = 1; i < points.length; i += 3) {
1356
- path +=
1357
- "C" +
1358
- points[i][0] +
1359
- " " +
1360
- points[i][1] +
1361
- " " +
1362
- points[i + 1][0] +
1363
- " " +
1364
- points[i + 1][1] +
1365
- " " +
1366
- points[i + 2][0] +
1367
- " " +
1368
- points[i + 2][1];
1369
- }
1370
- return path;
1371
- };
1372
-
1373
- const unscaledPoints = _.map(points, graph.unscalePoint);
1374
- const options = {
1375
- center: graph.unscalePoint(center),
1376
- createPath: createCubicPath,
1377
- } as const;
1378
- const arrowHead = new WrappedPath(
1379
- graph,
1380
- unscaledPoints,
1381
- options,
1382
- );
1383
- arrowHead.attr(
1384
- _.extend(
1385
- {
1386
- "stroke-linejoin": "round",
1387
- "stroke-linecap": "round",
1388
- "stroke-dasharray": "",
1389
- },
1390
- style,
1391
- ),
1392
- );
1393
-
1394
- // Add custom function for transforming arrowheads that
1395
- // accounts for center, scaling, etc.
1396
- arrowHead.toCoordAtAngle = function (
1397
- coord: Coord,
1398
- angle: number,
1399
- ): void {
1400
- const clipPoint = graph.scalePoint(
1401
- getClipPoint(graph, coord, angle),
1402
- );
1403
- const do3dTransform = getCanUse3dTransform();
1404
- arrowHead.transform(
1405
- "translateX(" +
1406
- (clipPoint[0] + scale * center[0]) +
1407
- "px) " +
1408
- "translateY(" +
1409
- (clipPoint[1] + scale * center[1]) +
1410
- "px) " +
1411
- (do3dTransform ? "translateZ(0) " : "") +
1412
- "rotate(" +
1413
- (360 - KhanMath.bound(angle)) +
1414
- "deg)",
1415
- );
1416
- };
1417
-
1418
- return arrowHead;
1419
- };
1420
-
1421
- // Add arrows
1422
- if (this._arrows == null) {
1423
- this._arrows = [];
1424
-
1425
- if (this.extendLine) {
1426
- this._arrows.push(createArrow(graph, this.normalStyle));
1427
- this._arrows.push(createArrow(graph, this.normalStyle));
1428
- } else if (this.extendRay) {
1429
- this._arrows.push(createArrow(graph, this.normalStyle));
1430
- }
1431
- }
1432
-
1433
- const coordForArrow = [this.coordA, this.coordZ];
1434
- const angleForArrow = [360 - angle, (540 - angle) % 360];
1435
- _.each(this._arrows, function (arrow, i) {
1436
- arrow.toCoordAtAngle(coordForArrow[i], angleForArrow[i]);
1437
- });
1438
-
1439
- // Temporary objects: array of SVG nodes that get recreated on drag
1440
- _.invoke(this.temp, "remove");
1441
- this.temp = [];
1442
-
1443
- const isClockwise =
1444
- this.coordA[0] < this.coordZ[0] ||
1445
- (this.coordA[0] === this.coordZ[0] &&
1446
- this.coordA[1] > this.coordZ[1]);
1447
-
1448
- // Update side label
1449
- if (this.sideLabel) {
1450
- this.temp.push(
1451
- this.graph.labelSide({
1452
- point1: this.coordA,
1453
- point2: this.coordZ,
1454
- label: this.labeledSide,
1455
- text: this.sideLabel,
1456
- numArrows: this.numArrows,
1457
- numTicks: this.numTicks,
1458
- clockwise: isClockwise,
1459
- style: this.labelStyle,
1460
- }),
1461
- );
1462
- }
1463
-
1464
- // Update vertex labels
1465
- if (this.vertexLabels.length) {
1466
- this.graph.labelVertex({
1467
- vertex: this.coordA,
1468
- point3: this.coordZ,
1469
- label: this.labeledVertices[0],
1470
- text: this.vertexLabels[0],
1471
- clockwise: isClockwise,
1472
- style: this.labelStyle,
1473
- });
1474
-
1475
- this.graph.labelVertex({
1476
- point1: this.coordA,
1477
- vertex: this.coordZ,
1478
- label: this.labeledVertices[1],
1479
- text: this.vertexLabels[1],
1480
- clockwise: isClockwise,
1481
- style: this.labelStyle,
1482
- });
1483
- }
1484
-
1485
- this.temp = _.flatten(this.temp);
1486
- };
1487
-
1488
- // Change z-order to back;
1489
- lineSegment.toBack = function () {
1490
- if (!lineSegment.fixed) {
1491
- lineSegment.mouseTarget.toBack();
1492
- }
1493
- lineSegment.visibleLine.toBack();
1494
- };
1495
-
1496
- // Change z-order to front
1497
- lineSegment.toFront = function () {
1498
- if (!lineSegment.fixed) {
1499
- lineSegment.mouseTarget.toFront();
1500
- }
1501
- lineSegment.visibleLine.toFront();
1502
- };
1503
-
1504
- lineSegment.remove = function () {
1505
- if (!lineSegment.fixed) {
1506
- lineSegment.mouseTarget.remove();
1507
- }
1508
- lineSegment.visibleLine.remove();
1509
- if (lineSegment.labeledSide) {
1510
- lineSegment.labeledSide.remove();
1511
- }
1512
- if (lineSegment.labeledVertices) {
1513
- _.invoke(lineSegment.labeledVertices, "remove");
1514
- }
1515
- if (lineSegment._arrows) {
1516
- _.invoke(lineSegment._arrows, "remove");
1517
- }
1518
- if (lineSegment.temp.length) {
1519
- _.invoke(lineSegment.temp, "remove");
1520
- }
1521
- };
1522
-
1523
- lineSegment.hide = function () {
1524
- lineSegment.visibleLine.hide();
1525
- if (lineSegment.temp.length) {
1526
- _.invoke(lineSegment.temp, "hide");
1527
- }
1528
- if (lineSegment._arrows) {
1529
- _.invoke(lineSegment._arrows, "hide");
1530
- }
1531
- };
1532
-
1533
- lineSegment.show = function () {
1534
- lineSegment.visibleLine.show();
1535
- if (lineSegment.temp.length) {
1536
- _.invoke(lineSegment.temp, "show");
1537
- }
1538
- if (lineSegment._arrows) {
1539
- _.invoke(lineSegment._arrows, "show");
1540
- }
1541
- };
1542
-
1543
- if (lineSegment.sideLabel) {
1544
- lineSegment.labeledSide = this.label(
1545
- [0, 0],
1546
- "",
1547
- "center",
1548
- lineSegment.labelStyle,
1549
- );
1550
- }
1551
-
1552
- if (lineSegment.vertexLabels.length) {
1553
- lineSegment.labeledVertices = _.map(
1554
- lineSegment.vertexLabels,
1555
- function (label) {
1556
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
1557
- return this.label(
1558
- [0, 0],
1559
- "",
1560
- "center",
1561
- lineSegment.labelStyle,
1562
- );
1563
- },
1564
- this,
1565
- );
1566
- }
1567
-
1568
- if (!lineSegment.fixed && !lineSegment.constraints.fixed) {
1569
- const $mouseTarget = $(lineSegment.mouseTarget.getMouseTarget());
1570
- $mouseTarget.css("cursor", "move");
1571
- $mouseTarget.bind(
1572
- "vmousedown vmouseover vmouseout",
1573
- function (event) {
1574
- if (event.type === "vmouseover") {
1575
- if (!dragging) {
1576
- lineSegment.highlight = true;
1577
- lineSegment.visibleLine.animate(
1578
- lineSegment.highlightStyle,
1579
- 50,
1580
- );
1581
- lineSegment.arrowStyle = _.extend(
1582
- {},
1583
- lineSegment.arrowStyle,
1584
- {
1585
- color: lineSegment.highlightStyle.stroke,
1586
- stroke: lineSegment.highlightStyle.stroke,
1587
- },
1588
- );
1589
- lineSegment.transform();
1590
- }
1591
- } else if (event.type === "vmouseout") {
1592
- lineSegment.highlight = false;
1593
- if (!lineSegment.dragging) {
1594
- lineSegment.visibleLine.animate(
1595
- lineSegment.normalStyle,
1596
- 50,
1597
- );
1598
- lineSegment.arrowStyle = _.extend(
1599
- {},
1600
- lineSegment.arrowStyle,
1601
- {
1602
- color: lineSegment.normalStyle.stroke,
1603
- stroke: lineSegment.normalStyle.stroke,
1604
- },
1605
- );
1606
- lineSegment.transform();
1607
- }
1608
- } else if (
1609
- event.type === "vmousedown" &&
1610
- (event.which === 1 || event.which === 0)
1611
- ) {
1612
- event.preventDefault();
1613
- let coordX =
1614
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
1615
- (event.pageX -
1616
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
1617
- $(graph.raphael.canvas.parentNode).offset()
1618
- .left) /
1619
- graph.scale[0] +
1620
- graph.range[0][0];
1621
- let coordY =
1622
- graph.range[1][1] -
1623
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
1624
- (event.pageY -
1625
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
1626
- $(graph.raphael.canvas.parentNode).offset()
1627
- .top) /
1628
- graph.scale[1];
1629
- if (lineSegment.snapX > 0) {
1630
- coordX =
1631
- Math.round(coordX / lineSegment.snapX) *
1632
- lineSegment.snapX;
1633
- }
1634
- if (lineSegment.snapY > 0) {
1635
- coordY =
1636
- Math.round(coordY / lineSegment.snapY) *
1637
- lineSegment.snapY;
1638
- }
1639
- const mouseOffsetA = [
1640
- lineSegment.coordA[0] - coordX,
1641
- lineSegment.coordA[1] - coordY,
1642
- ];
1643
- const mouseOffsetZ = [
1644
- lineSegment.coordZ[0] - coordX,
1645
- lineSegment.coordZ[1] - coordY,
1646
- ];
1647
-
1648
- const offsetLeft = -Math.min(
1649
- graph.scaleVector(mouseOffsetA)[0],
1650
- graph.scaleVector(mouseOffsetZ)[0],
1651
- );
1652
- const offsetRight = Math.max(
1653
- graph.scaleVector(mouseOffsetA)[0],
1654
- graph.scaleVector(mouseOffsetZ)[0],
1655
- );
1656
- const offsetTop = Math.max(
1657
- graph.scaleVector(mouseOffsetA)[1],
1658
- graph.scaleVector(mouseOffsetZ)[1],
1659
- );
1660
- const offsetBottom = -Math.min(
1661
- graph.scaleVector(mouseOffsetA)[1],
1662
- graph.scaleVector(mouseOffsetZ)[1],
1663
- );
1664
-
1665
- $(document).bind(
1666
- "vmousemove.lineSegment vmouseup.lineSegment",
1667
- function (event) {
1668
- event.preventDefault();
1669
- lineSegment.dragging = true;
1670
- dragging = true;
1671
-
1672
- let mouseX =
1673
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
1674
- event.pageX -
1675
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
1676
- $(graph.raphael.canvas.parentNode).offset()
1677
- .left;
1678
- let mouseY =
1679
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
1680
- event.pageY -
1681
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
1682
- $(graph.raphael.canvas.parentNode).offset()
1683
- .top;
1684
- // no part of the line segment can go beyond 10
1685
- // pixels from the edge
1686
- mouseX = Math.max(
1687
- offsetLeft + 10,
1688
- Math.min(
1689
- graph.xpixels - 10 - offsetRight,
1690
- mouseX,
1691
- ),
1692
- );
1693
- mouseY = Math.max(
1694
- offsetTop + 10,
1695
- Math.min(
1696
- graph.ypixels - 10 - offsetBottom,
1697
- mouseY,
1698
- ),
1699
- );
1700
-
1701
- let coordX =
1702
- mouseX / graph.scale[0] + graph.range[0][0];
1703
- let coordY =
1704
- graph.range[1][1] - mouseY / graph.scale[1];
1705
- if (lineSegment.snapX > 0) {
1706
- coordX =
1707
- Math.round(coordX / lineSegment.snapX) *
1708
- lineSegment.snapX;
1709
- }
1710
- if (lineSegment.snapY > 0) {
1711
- coordY =
1712
- Math.round(coordY / lineSegment.snapY) *
1713
- lineSegment.snapY;
1714
- }
1715
-
1716
- if (event.type === "vmousemove") {
1717
- if (lineSegment.constraints.constrainX) {
1718
- coordX =
1719
- lineSegment.coordA[0] -
1720
- mouseOffsetA[0];
1721
- }
1722
- if (lineSegment.constraints.constrainY) {
1723
- coordY =
1724
- lineSegment.coordA[1] -
1725
- mouseOffsetA[1];
1726
- }
1727
- const dX =
1728
- coordX +
1729
- mouseOffsetA[0] -
1730
- lineSegment.coordA[0];
1731
- const dY =
1732
- coordY +
1733
- mouseOffsetA[1] -
1734
- lineSegment.coordA[1];
1735
- lineSegment.coordA = [
1736
- coordX + mouseOffsetA[0],
1737
- coordY + mouseOffsetA[1],
1738
- ];
1739
- lineSegment.coordZ = [
1740
- coordX + mouseOffsetZ[0],
1741
- coordY + mouseOffsetZ[1],
1742
- ];
1743
- lineSegment.transform();
1744
-
1745
- if (lineSegment.movePointsWithLine) {
1746
- // If the points are movablePoints, adjust
1747
- // their coordinates when the line itself is
1748
- // dragged
1749
- if (
1750
- typeof lineSegment.pointA ===
1751
- "object"
1752
- ) {
1753
- lineSegment.pointA.setCoord([
1754
- lineSegment.pointA.coord[0] +
1755
- dX,
1756
- lineSegment.pointA.coord[1] +
1757
- dY,
1758
- ]);
1759
- }
1760
- if (
1761
- typeof lineSegment.pointZ ===
1762
- "object"
1763
- ) {
1764
- lineSegment.pointZ.setCoord([
1765
- lineSegment.pointZ.coord[0] +
1766
- dX,
1767
- lineSegment.pointZ.coord[1] +
1768
- dY,
1769
- ]);
1770
- }
1771
- }
1772
-
1773
- if (_.isFunction(lineSegment.onMove)) {
1774
- lineSegment.onMove(dX, dY);
1775
- }
1776
- } else if (event.type === "vmouseup") {
1777
- $(document).unbind(".lineSegment");
1778
- lineSegment.dragging = false;
1779
- dragging = false;
1780
- if (!lineSegment.highlight) {
1781
- lineSegment.visibleLine.animate(
1782
- lineSegment.normalStyle,
1783
- 50,
1784
- );
1785
- lineSegment.arrowStyle = _.extend(
1786
- {},
1787
- lineSegment.arrowStyle,
1788
- {
1789
- color: lineSegment.normalStyle
1790
- .stroke,
1791
- stroke: lineSegment.normalStyle
1792
- .stroke,
1793
- },
1794
- );
1795
- lineSegment.transform();
1796
- }
1797
- if (_.isFunction(lineSegment.onMoveEnd)) {
1798
- lineSegment.onMoveEnd();
1799
- }
1800
- }
1801
-
1802
- $(lineSegment).trigger("move");
1803
- },
1804
- );
1805
- }
1806
- },
1807
- );
1808
- }
1809
-
1810
- if (lineSegment.pointA !== undefined) {
1811
- lineSegment.pointA.toFront();
1812
- }
1813
- if (lineSegment.pointZ !== undefined) {
1814
- lineSegment.pointZ.toFront();
1815
- }
1816
- lineSegment.transform();
1817
- return lineSegment;
1818
- },
1819
-
1820
- // MovablePolygon is a polygon that can be dragged around the screen.
1821
- // By attaching a smartPoint to each vertex, the points can be
1822
- // manipulated individually.
1823
- //
1824
- // To use with smartPoints, add the smartPoints first, then:
1825
- // addMovablePolygon({points: [...]});
1826
- //
1827
- // Include "fixed: true" in the options if you don't want the entire
1828
- // polygon to be draggable (you can still use points to make the
1829
- // vertices draggable)
1830
- //
1831
- // The returned object includes the following properties/methods:
1832
- //
1833
- // - polygon.points
1834
- // The polygon's dynamic smartPoints and static coordinates, mixed.
1835
- //
1836
- // - polygon.coords
1837
- // The polygon's current coordinates (generated, don't edit).
1838
- //
1839
- // - polygon.transform()
1840
- // Repositions the polygon. Call after changing any points.
1841
- //
1842
- addMovablePolygon: function (options) {
1843
- const graphie = this;
1844
-
1845
- const polygon = $.extend(
1846
- {
1847
- snapX: 0,
1848
- snapY: 0,
1849
- fixed: false,
1850
- constrainToGraph: true,
1851
- normalStyle: {},
1852
- highlightStyle: {
1853
- stroke: KhanColors.INTERACTING,
1854
- "stroke-width": 2,
1855
- fill: KhanColors.INTERACTING,
1856
- "fill-opacity": 0.05,
1857
- },
1858
- pointHighlightStyle: {
1859
- fill: KhanColors.INTERACTING,
1860
- stroke: KhanColors.INTERACTING,
1861
- },
1862
- labelStyle: {
1863
- stroke: KhanColors.DYNAMIC,
1864
- "stroke-width": 1,
1865
- color: KhanColors.DYNAMIC,
1866
- },
1867
- angleLabels: [],
1868
- showRightAngleMarkers: [],
1869
- sideLabels: [],
1870
- vertexLabels: [],
1871
- numArcs: [],
1872
- numArrows: [],
1873
- numTicks: [],
1874
- updateOnPointMove: true,
1875
- closed: true,
1876
- },
1877
- _.omit(options, "points"),
1878
- );
1879
-
1880
- const normalColor = polygon.fixed
1881
- ? KhanColors.DYNAMIC
1882
- : KhanColors.INTERACTIVE;
1883
- polygon.normalStyle = _.extend(
1884
- {
1885
- "stroke-width": 2,
1886
- "fill-opacity": 0,
1887
- fill: normalColor,
1888
- stroke: normalColor,
1889
- },
1890
- options.normalStyle,
1891
- );
1892
-
1893
- // don't deep copy the points array with $.extend;
1894
- // we may want to append to it later for click-to-add-points
1895
- polygon.points = options.points;
1896
-
1897
- const isPoint = function (coordOrPoint: any) {
1898
- return !_.isArray(coordOrPoint);
1899
- };
1900
-
1901
- polygon.update = function () {
1902
- const n = polygon.points.length;
1903
-
1904
- // Update coords
1905
- polygon.coords = _.map(polygon.points, function (coordOrPoint, i) {
1906
- if (isPoint(coordOrPoint)) {
1907
- return coordOrPoint.coord;
1908
- }
1909
- return coordOrPoint;
1910
- });
1911
-
1912
- // Calculate bounding box
1913
- polygon.left = _.min(_.pluck(polygon.coords, 0));
1914
- polygon.right = _.max(_.pluck(polygon.coords, 0));
1915
- polygon.top = _.max(_.pluck(polygon.coords, 1));
1916
- polygon.bottom = _.min(_.pluck(polygon.coords, 1));
1917
-
1918
- let scaledCoords = _.map(polygon.coords, function (coord) {
1919
- return graphie.scalePoint(coord);
1920
- });
1921
-
1922
- // Create path
1923
- if (polygon.closed) {
1924
- scaledCoords.push(true);
1925
- } else {
1926
- // For open polygons, concatenate a reverse of the path,
1927
- // to remove the inside area of the path, which would
1928
- // otherwise be clickable (even if the closing line segment
1929
- // wasn't drawn
1930
- scaledCoords = scaledCoords.concat(
1931
- _.clone(scaledCoords).reverse(),
1932
- );
1933
- }
1934
- polygon.path = GraphUtils.unscaledSvgPath(scaledCoords);
1935
-
1936
- // Temporary objects
1937
- _.invoke(polygon.temp, "remove");
1938
- polygon.temp = [];
1939
-
1940
- const isClockwise = clockwise(polygon.coords);
1941
-
1942
- // Update angle labels
1943
- if (
1944
- polygon.angleLabels.length ||
1945
- polygon.showRightAngleMarkers.length
1946
- ) {
1947
- _.each(polygon.labeledAngles, function (label, i) {
1948
- polygon.temp.push(
1949
- graphie.labelAngle({
1950
- point1: polygon.coords[(i - 1 + n) % n],
1951
- vertex: polygon.coords[i],
1952
- point3: polygon.coords[(i + 1) % n],
1953
- label: label,
1954
- text: polygon.angleLabels[i],
1955
- showRightAngleMarker:
1956
- polygon.showRightAngleMarkers[i],
1957
- numArcs: polygon.numArcs[i],
1958
- clockwise: isClockwise,
1959
- style: polygon.labelStyle,
1960
- }),
1961
- );
1962
- });
1963
- }
1964
-
1965
- // Update side labels
1966
- if (polygon.sideLabels.length) {
1967
- _.each(polygon.labeledSides, function (label, i) {
1968
- polygon.temp.push(
1969
- graphie.labelSide({
1970
- point1: polygon.coords[i],
1971
- point2: polygon.coords[(i + 1) % n],
1972
- label: label,
1973
- text: polygon.sideLabels[i],
1974
- numArrows: polygon.numArrows[i],
1975
- numTicks: polygon.numTicks[i],
1976
- clockwise: isClockwise,
1977
- style: polygon.labelStyle,
1978
- }),
1979
- );
1980
- });
1981
- }
1982
-
1983
- // Update vertex labels
1984
- if (polygon.vertexLabels.length) {
1985
- _.each(polygon.labeledVertices, function (label, i) {
1986
- graphie.labelVertex({
1987
- point1: polygon.coords[(i - 1 + n) % n],
1988
- vertex: polygon.coords[i],
1989
- point3: polygon.coords[(i + 1) % n],
1990
- label: label,
1991
- text: polygon.vertexLabels[i],
1992
- clockwise: isClockwise,
1993
- style: polygon.labelStyle,
1994
- });
1995
- });
1996
- }
1997
-
1998
- polygon.temp = _.flatten(polygon.temp);
1999
- };
2000
-
2001
- polygon.transform = function () {
2002
- polygon.update();
2003
-
2004
- polygon.visibleShape.attr({path: polygon.path});
2005
-
2006
- if (!polygon.fixed) {
2007
- polygon.mouseTarget.attr({path: polygon.path});
2008
- }
2009
- };
2010
-
2011
- polygon.remove = function () {
2012
- polygon.visibleShape.remove();
2013
-
2014
- if (!polygon.fixed) {
2015
- polygon.mouseTarget.remove();
2016
- }
2017
-
2018
- if (polygon.labeledAngles) {
2019
- _.invoke(polygon.labeledAngles, "remove");
2020
- }
2021
-
2022
- if (polygon.labeledSides) {
2023
- _.invoke(polygon.labeledSides, "remove");
2024
- }
2025
-
2026
- if (polygon.labeledVertices) {
2027
- _.invoke(polygon.labeledVertices, "remove");
2028
- }
2029
-
2030
- if (polygon.temp.length) {
2031
- _.invoke(polygon.temp, "remove");
2032
- }
2033
- };
2034
-
2035
- polygon.toBack = function () {
2036
- if (!polygon.fixed) {
2037
- polygon.mouseTarget.toBack();
2038
- }
2039
-
2040
- polygon.visibleShape.toBack();
2041
- };
2042
-
2043
- polygon.toFront = function () {
2044
- if (!polygon.fixed) {
2045
- polygon.mouseTarget.toFront();
2046
- }
2047
-
2048
- polygon.visibleShape.toFront();
2049
- };
2050
-
2051
- // Setup
2052
-
2053
- if (polygon.updateOnPointMove) {
2054
- _.each(_.filter(polygon.points, isPoint), function (coordOrPoint) {
2055
- coordOrPoint.polygonVertices.push(polygon);
2056
- });
2057
- }
2058
-
2059
- polygon.coords = new Array(polygon.points.length);
2060
-
2061
- if (polygon.angleLabels.length) {
2062
- const numLabels = Math.max(
2063
- polygon.angleLabels.length,
2064
- polygon.showRightAngleMarkers.length,
2065
- );
2066
- polygon.labeledAngles = _.times(
2067
- numLabels,
2068
- function () {
2069
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
2070
- return this.label([0, 0], "", "center", polygon.labelStyle);
2071
- },
2072
- this,
2073
- );
2074
- }
2075
-
2076
- if (polygon.sideLabels.length) {
2077
- polygon.labeledSides = _.map(
2078
- polygon.sideLabels,
2079
- function (label) {
2080
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
2081
- return this.label([0, 0], "", "center", polygon.labelStyle);
2082
- },
2083
- this,
2084
- );
2085
- }
2086
-
2087
- if (polygon.vertexLabels.length) {
2088
- polygon.labeledVertices = _.map(
2089
- polygon.vertexLabels,
2090
- function (label) {
2091
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
2092
- return this.label([0, 0], "", "center", polygon.labelStyle);
2093
- },
2094
- this,
2095
- );
2096
- }
2097
-
2098
- polygon.update();
2099
-
2100
- polygon.visibleShape = graphie.raphael.path(polygon.path);
2101
- polygon.visibleShape.attr(polygon.normalStyle);
2102
-
2103
- if (!polygon.fixed) {
2104
- polygon.mouseTarget = graphie.mouselayer.path(polygon.path);
2105
- polygon.mouseTarget.attr({
2106
- fill: "#000",
2107
- opacity: 0,
2108
- cursor: "move",
2109
- });
2110
-
2111
- $(polygon.mouseTarget[0]).bind(
2112
- "vmousedown vmouseover vmouseout",
2113
- function (event) {
2114
- if (event.type === "vmouseover") {
2115
- if (!dragging || polygon.dragging) {
2116
- polygon.highlight = true;
2117
- polygon.visibleShape.animate(
2118
- polygon.highlightStyle,
2119
- 50,
2120
- );
2121
- _.each(
2122
- _.filter(polygon.points, isPoint),
2123
- function (point) {
2124
- point.visibleShape.animate(
2125
- polygon.pointHighlightStyle,
2126
- 50,
2127
- );
2128
- },
2129
- );
2130
- }
2131
- } else if (event.type === "vmouseout") {
2132
- polygon.highlight = false;
2133
- if (!polygon.dragging) {
2134
- polygon.visibleShape.animate(
2135
- polygon.normalStyle,
2136
- 50,
2137
- );
2138
- const points = _.filter(polygon.points, isPoint);
2139
- if (!_.any(_.pluck(points, "dragging"))) {
2140
- _.each(points, function (point) {
2141
- point.visibleShape.animate(
2142
- point.normalStyle,
2143
- 50,
2144
- );
2145
- });
2146
- }
2147
- }
2148
- } else if (
2149
- event.type === "vmousedown" &&
2150
- (event.which === 1 || event.which === 0)
2151
- ) {
2152
- event.preventDefault();
2153
-
2154
- _.each(
2155
- _.filter(polygon.points, isPoint),
2156
- function (point) {
2157
- point.dragging = true;
2158
- },
2159
- );
2160
-
2161
- let startX =
2162
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
2163
- (event.pageX -
2164
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
2165
- $(graphie.raphael.canvas.parentNode).offset()
2166
- .left) /
2167
- graphie.scale[0] +
2168
- graphie.range[0][0];
2169
- let startY =
2170
- graphie.range[1][1] -
2171
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
2172
- (event.pageY -
2173
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
2174
- $(graphie.raphael.canvas.parentNode).offset()
2175
- .top) /
2176
- graphie.scale[1];
2177
- if (polygon.snapX > 0) {
2178
- startX =
2179
- Math.round(startX / polygon.snapX) *
2180
- polygon.snapX;
2181
- }
2182
- if (polygon.snapY > 0) {
2183
- startY =
2184
- Math.round(startY / polygon.snapY) *
2185
- polygon.snapY;
2186
- }
2187
- let lastX = startX;
2188
- let lastY = startY;
2189
-
2190
- const polygonCoords = polygon.coords.slice();
2191
-
2192
- const offsetLeft =
2193
- (startX - polygon.left) * graphie.scale[0];
2194
- const offsetRight =
2195
- (polygon.right - startX) * graphie.scale[0];
2196
- const offsetTop =
2197
- (polygon.top - startY) * graphie.scale[1];
2198
- const offsetBottom =
2199
- (startY - polygon.bottom) * graphie.scale[1];
2200
-
2201
- $(document).bind(
2202
- "vmousemove.polygon vmouseup.polygon",
2203
- function (event) {
2204
- event.preventDefault();
2205
-
2206
- polygon.dragging = true;
2207
- dragging = true;
2208
-
2209
- let mouseX =
2210
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
2211
- event.pageX -
2212
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
2213
- $(
2214
- graphie.raphael.canvas.parentNode,
2215
- ).offset().left;
2216
- let mouseY =
2217
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
2218
- event.pageY -
2219
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
2220
- $(
2221
- graphie.raphael.canvas.parentNode,
2222
- ).offset().top;
2223
-
2224
- // no part of the polygon can go beyond 10 pixels from
2225
- // the edge
2226
- if (polygon.constrainToGraph) {
2227
- mouseX = Math.max(
2228
- offsetLeft + 10,
2229
- Math.min(
2230
- graphie.xpixels - 10 - offsetRight,
2231
- mouseX,
2232
- ),
2233
- );
2234
- mouseY = Math.max(
2235
- offsetTop + 10,
2236
- Math.min(
2237
- graphie.ypixels - 10 - offsetBottom,
2238
- mouseY,
2239
- ),
2240
- );
2241
- }
2242
-
2243
- let currentX =
2244
- mouseX / graphie.scale[0] +
2245
- graphie.range[0][0];
2246
- let currentY =
2247
- graphie.range[1][1] -
2248
- mouseY / graphie.scale[1];
2249
- if (polygon.snapX > 0) {
2250
- currentX =
2251
- Math.round(currentX / polygon.snapX) *
2252
- polygon.snapX;
2253
- }
2254
- if (polygon.snapY > 0) {
2255
- currentY =
2256
- Math.round(currentY / polygon.snapY) *
2257
- polygon.snapY;
2258
- }
2259
-
2260
- if (event.type === "vmousemove") {
2261
- let dX = currentX - startX;
2262
- let dY = currentY - startY;
2263
-
2264
- let doMove = true;
2265
- if (_.isFunction(polygon.onMove)) {
2266
- const onMoveResult = polygon.onMove(
2267
- dX,
2268
- dY,
2269
- );
2270
- if (onMoveResult === false) {
2271
- doMove = false;
2272
- } else if (_.isArray(onMoveResult)) {
2273
- dX = onMoveResult[0];
2274
- dY = onMoveResult[1];
2275
- currentX = startX + dX;
2276
- currentY = startY + dY;
2277
- }
2278
- }
2279
-
2280
- const increment = function (i: any) {
2281
- return [
2282
- polygonCoords[i][0] + dX,
2283
- polygonCoords[i][1] + dY,
2284
- ];
2285
- };
2286
-
2287
- if (doMove) {
2288
- _.each(
2289
- polygon.points,
2290
- function (coordOrPoint, i) {
2291
- if (isPoint(coordOrPoint)) {
2292
- coordOrPoint.setCoord(
2293
- increment(i),
2294
- );
2295
- } else {
2296
- polygon.points[i] =
2297
- increment(i);
2298
- }
2299
- },
2300
- );
2301
-
2302
- polygon.transform();
2303
-
2304
- $(polygon).trigger("move");
2305
-
2306
- lastX = currentX;
2307
- lastY = currentY;
2308
- }
2309
- } else if (event.type === "vmouseup") {
2310
- $(document).unbind(".polygon");
2311
-
2312
- const points = _.filter(
2313
- polygon.points,
2314
- isPoint,
2315
- );
2316
- _.each(points, function (point) {
2317
- point.dragging = false;
2318
- });
2319
-
2320
- polygon.dragging = false;
2321
- dragging = false;
2322
- if (!polygon.highlight) {
2323
- polygon.visibleShape.animate(
2324
- polygon.normalStyle,
2325
- 50,
2326
- );
2327
-
2328
- _.each(points, function (point) {
2329
- point.visibleShape.animate(
2330
- point.normalStyle,
2331
- 50,
2332
- );
2333
- });
2334
- }
2335
- if (_.isFunction(polygon.onMoveEnd)) {
2336
- polygon.onMoveEnd(
2337
- lastX - startX,
2338
- lastY - startY,
2339
- );
2340
- }
2341
- }
2342
- },
2343
- );
2344
- }
2345
- },
2346
- );
2347
- }
2348
-
2349
- // Bring any movable points to the front
2350
- _.invoke(_.filter(polygon.points, isPoint), "toFront");
2351
-
2352
- return polygon;
2353
- },
2354
-
2355
- /**
2356
- * Constrain a point to be within the graph (including padding).
2357
- * If outside graph, point's x and y coordinates are clamped within
2358
- * the graph.
2359
- */
2360
- constrainToBounds: function (point, padding) {
2361
- const lower = this.unscalePoint([padding, this.ypixels - padding]);
2362
- const upper = this.unscalePoint([this.xpixels - padding, padding]);
2363
- const coordX = Math.max(lower[0], Math.min(upper[0], point[0]));
2364
- const coordY = Math.max(lower[1], Math.min(upper[1], point[1]));
2365
- return [coordX, coordY];
2366
- },
2367
-
2368
- /**
2369
- * Constrain a point to be within the graph (including padding).
2370
- * If outside graph, point is moved along the ray specified by angle
2371
- * until inside graph.
2372
- */
2373
- constrainToBoundsOnAngle: function (point, padding, angle) {
2374
- const lower = this.unscalePoint([padding, this.ypixels - padding]);
2375
- const upper = this.unscalePoint([this.xpixels - padding, padding]);
2376
-
2377
- let result = point.slice();
2378
-
2379
- if (result[0] < lower[0]) {
2380
- result = [
2381
- lower[0],
2382
- result[1] + (lower[0] - result[0]) * Math.tan(angle),
2383
- ];
2384
- } else if (result[0] > upper[0]) {
2385
- result = [
2386
- upper[0],
2387
- result[1] - (result[0] - upper[0]) * Math.tan(angle),
2388
- ];
2389
- }
2390
-
2391
- if (result[1] < lower[1]) {
2392
- result = [
2393
- result[0] + (lower[1] - result[1]) / Math.tan(angle),
2394
- lower[1],
2395
- ];
2396
- } else if (result[1] > upper[1]) {
2397
- result = [
2398
- result[0] - (result[1] - upper[1]) / Math.tan(angle),
2399
- upper[1],
2400
- ];
2401
- }
2402
-
2403
- return result;
2404
- },
2405
-
2406
- // MovableAngle is an angle that can be dragged around the screen.
2407
- // By attaching a smartPoint to the vertex and ray control points, the
2408
- // rays can be manipulated individually.
2409
- //
2410
- // Use only with smartPoints; add the smartPoints first, then:
2411
- // addMovableAngle({points: [...]});
2412
- //
2413
- // The rays can be controlled to snap on degrees (more useful than snapping
2414
- // on coordinates) by setting snapDegrees to a positive integer.
2415
- //
2416
- // The returned object includes the following properties/methods:
2417
- //
2418
- // - movableAngle.points
2419
- // The movableAngle's dynamic smartPoints.
2420
- //
2421
- // - movableAngle.coords
2422
- // The movableAngle's current coordinates (generated, don't edit).
2423
- //
2424
- addMovableAngle: function (options) {
2425
- return new MovableAngle(this, options);
2426
- },
2427
-
2428
- // center: movable point
2429
- // radius: int
2430
- // circ: graphie circle
2431
- // perim: invisible mouse target for dragging/changing radius
2432
- addCircleGraph: function (options) {
2433
- const graphie = this;
2434
- const circle = $.extend(
2435
- {
2436
- center: [0, 0],
2437
- radius: 2,
2438
- snapX: 0.5,
2439
- snapY: 0.5,
2440
- snapRadius: 0.5,
2441
- minRadius: 1,
2442
- centerConstraints: {},
2443
- centerNormalStyle: {},
2444
- centerHighlightStyle: {
2445
- stroke: KhanColors.INTERACTING,
2446
- fill: KhanColors.INTERACTING,
2447
- },
2448
- circleNormalStyle: {
2449
- stroke: KhanColors.INTERACTIVE,
2450
- "fill-opacity": 0,
2451
- },
2452
- circleHighlightStyle: {
2453
- stroke: KhanColors.INTERACTING,
2454
- fill: KhanColors.INTERACTING,
2455
- "fill-opacity": 0.05,
2456
- },
2457
- },
2458
- options,
2459
- );
2460
-
2461
- const normalColor = circle.centerConstraints.fixed
2462
- ? KhanColors.DYNAMIC
2463
- : KhanColors.INTERACTIVE;
2464
- const centerNormalStyle = options ? options.centerNormalStyle : null;
2465
- circle.centerNormalStyle = _.extend(
2466
- {},
2467
- {
2468
- fill: normalColor,
2469
- stroke: normalColor,
2470
- },
2471
- centerNormalStyle,
2472
- );
2473
-
2474
- circle.centerPoint = graphie.addMovablePoint({
2475
- graph: graphie,
2476
- coord: circle.center,
2477
- normalStyle: circle.centerNormalStyle,
2478
- snapX: circle.snapX,
2479
- snapY: circle.snapY,
2480
- constraints: circle.centerConstraints,
2481
- });
2482
- circle.circ = graphie.circle(
2483
- circle.center,
2484
- circle.radius,
2485
- circle.circleNormalStyle,
2486
- );
2487
- circle.perim = graphie.mouselayer
2488
- .circle(
2489
- graphie.scalePoint(circle.center)[0],
2490
- graphie.scalePoint(circle.center)[1],
2491
- graphie.scaleVector(circle.radius)[0],
2492
- )
2493
- .attr({
2494
- "stroke-width": 20,
2495
- opacity: 0.002, // This is as close to 0 as MSIE will allow
2496
- });
2497
-
2498
- // Highlight circle circumference on center point hover
2499
- if (!circle.centerConstraints.fixed) {
2500
- $(circle.centerPoint.mouseTarget.getMouseTarget()).on(
2501
- "vmouseover vmouseout",
2502
- function (event) {
2503
- if (
2504
- circle.centerPoint.highlight ||
2505
- circle.centerPoint.dragging
2506
- ) {
2507
- circle.circ.animate(circle.circleHighlightStyle, 50);
2508
- } else {
2509
- circle.circ.animate(circle.circleNormalStyle, 50);
2510
- }
2511
- },
2512
- );
2513
- }
2514
-
2515
- circle.toFront = function () {
2516
- circle.circ.toFront();
2517
- circle.perim.toFront();
2518
- circle.centerPoint.visibleShape.toFront();
2519
- if (!circle.centerConstraints.fixed) {
2520
- circle.centerPoint.mouseTarget.toFront();
2521
- }
2522
- };
2523
-
2524
- circle.centerPoint.onMove = function (x: any, y: any) {
2525
- circle.toFront();
2526
- circle.circ.attr({
2527
- cx: graphie.scalePoint(x)[0],
2528
- cy: graphie.scalePoint(y)[1],
2529
- });
2530
- circle.perim.attr({
2531
- cx: graphie.scalePoint(x)[0],
2532
- cy: graphie.scalePoint(y)[1],
2533
- });
2534
- if (circle.onMove) {
2535
- circle.onMove(x, y);
2536
- }
2537
- };
2538
-
2539
- $(circle.centerPoint).on("move", function () {
2540
- circle.center = this.coord;
2541
- $(circle).trigger("move");
2542
- });
2543
-
2544
- // circle.setCenter(x, y) moves the circle to the specified
2545
- // x, y coordinate as if the user had dragged it there.
2546
- circle.setCenter = function (x: any, y: any) {
2547
- circle.centerPoint.setCoord([x, y]);
2548
- circle.centerPoint.onMove(x, y);
2549
- circle.center = [x, y];
2550
- };
2551
-
2552
- // circle.setRadius(r) sets the circle's radius to the specified
2553
- // value as if the user had dragged it there.
2554
- circle.setRadius = function (r: any) {
2555
- circle.radius = r;
2556
-
2557
- circle.perim.attr({
2558
- r: graphie.scaleVector(r)[0],
2559
- });
2560
- circle.circ.attr({
2561
- rx: graphie.scaleVector(r)[0],
2562
- ry: graphie.scaleVector(r)[1],
2563
- });
2564
- };
2565
-
2566
- circle.remove = function () {
2567
- circle.centerPoint.remove();
2568
- circle.circ.remove();
2569
- circle.perim.remove();
2570
- };
2571
-
2572
- // Define a set of axes using polar coordinates to specify
2573
- // which resizing cursor we want to show based on where the
2574
- // mouse position lies in relation to the circle's center.
2575
- // The first two columns in cursorAxes refer to the minimum
2576
- // and maximum angle values bounding a circle sector, and
2577
- // the third column refers to the cursor name that will be
2578
- // applied if the mouse position falls inside the given sector.
2579
- const cursorAxes: Array<[number, number, string]> = [
2580
- [Math.PI * -1.0, Math.PI * -0.875, "ew-resize"],
2581
- [Math.PI * -0.875, Math.PI * -0.625, "nesw-resize"],
2582
- [Math.PI * -0.625, Math.PI * -0.375, "ns-resize"],
2583
- [Math.PI * -0.375, Math.PI * -0.125, "nwse-resize"],
2584
- [Math.PI * -0.125, Math.PI * 0.125, "ew-resize"],
2585
- [Math.PI * 0.125, Math.PI * 0.375, "nesw-resize"],
2586
- [Math.PI * 0.375, Math.PI * 0.625, "ns-resize"],
2587
- [Math.PI * 0.625, Math.PI * 0.875, "nwse-resize"],
2588
- [Math.PI * 0.875, Math.PI * 1.0, "ew-resize"],
2589
- ];
2590
-
2591
- // When the mouse moves along the circle's perimeter, we
2592
- // dynamically set a CSS rule to show the correct
2593
- // bidirectional cursor so a student knows they can resize
2594
- // our circle. To do this, we convert the x and y coordinates
2595
- // of the mouse position into polar coordinates and use the
2596
- // defined cursorAxes above to set our rule.
2597
- $(circle.perim.node).on("vmousemove", (event) => {
2598
- let [x, y] = this.getMouseCoord(event);
2599
-
2600
- x -= circle.center[0];
2601
- y -= circle.center[1];
2602
-
2603
- const theta = Math.atan2(y, x);
2604
-
2605
- cursorAxes.forEach(function (axes) {
2606
- const [min, max, cursorName] = axes;
2607
- if (theta >= min && theta < max) {
2608
- $(circle.perim.node).css("cursor", cursorName);
2609
- }
2610
- });
2611
- });
2612
-
2613
- // Set a default resizing-friendly cursor to be safe.
2614
- $(circle.perim.node).css("cursor", "nesw-resize");
2615
-
2616
- // Prevent the page from scrolling when we grab and drag the circle on
2617
- // a mobile device.
2618
- circle.perim.node.addEventListener(
2619
- "touchstart",
2620
- function (event) {
2621
- event.preventDefault();
2622
- },
2623
- {passive: false},
2624
- );
2625
-
2626
- $(circle.perim.node).on(
2627
- "vmouseover vmouseout vmousedown",
2628
- function (event) {
2629
- if (event.type === "vmouseover") {
2630
- circle.highlight = true;
2631
- if (!dragging) {
2632
- // TODO(jack): Figure out why this doesn't work
2633
- // for circleHighlightStyle's that change
2634
- // stroke-dasharray
2635
- circle.circ.animate(circle.circleHighlightStyle, 50);
2636
- circle.centerPoint.visibleShape.animate(
2637
- circle.centerHighlightStyle,
2638
- 50,
2639
- );
2640
- }
2641
- } else if (event.type === "vmouseout") {
2642
- circle.highlight = false;
2643
- if (!circle.dragging && !circle.centerPoint.dragging) {
2644
- circle.circ.animate(circle.circleNormalStyle, 50);
2645
- circle.centerPoint.visibleShape.animate(
2646
- circle.centerNormalStyle,
2647
- 50,
2648
- );
2649
- }
2650
- } else if (
2651
- event.type === "vmousedown" &&
2652
- (event.which === 1 || event.which === 0)
2653
- ) {
2654
- event.preventDefault();
2655
- circle.toFront();
2656
- const startRadius = circle.radius;
2657
-
2658
- $(document).on("vmousemove vmouseup", function (event) {
2659
- event.preventDefault();
2660
- circle.dragging = true;
2661
- dragging = true;
2662
-
2663
- if (event.type === "vmousemove") {
2664
- const coord = graphie.constrainToBounds(
2665
- graphie.getMouseCoord(event),
2666
- 10,
2667
- );
2668
-
2669
- let radius = GraphUtils.getDistance(
2670
- circle.centerPoint.coord,
2671
- coord,
2672
- );
2673
- radius = Math.max(
2674
- circle.minRadius,
2675
- Math.round(radius / circle.snapRadius) *
2676
- circle.snapRadius,
2677
- );
2678
- const oldRadius = circle.radius;
2679
- let doResize = true;
2680
- if (circle.onResize) {
2681
- const onResizeResult = circle.onResize(
2682
- radius,
2683
- oldRadius,
2684
- );
2685
- if (_.isNumber(onResizeResult)) {
2686
- radius = onResizeResult;
2687
- } else if (onResizeResult === false) {
2688
- doResize = false;
2689
- }
2690
- }
2691
- if (doResize) {
2692
- circle.setRadius(radius);
2693
- $(circle).trigger("move");
2694
- }
2695
- } else if (event.type === "vmouseup") {
2696
- $(document).off("vmousemove vmouseup");
2697
- circle.dragging = false;
2698
- dragging = false;
2699
- if (circle.onResizeEnd) {
2700
- circle.onResizeEnd(circle.radius, startRadius);
2701
- }
2702
- }
2703
- });
2704
- }
2705
- },
2706
- );
2707
-
2708
- return circle;
2709
- },
2710
-
2711
- addRotateHandle: (function () {
2712
- const drawRotateHandle = function (
2713
- graphie,
2714
- center: any,
2715
- radius,
2716
- halfWidth,
2717
- lengthAngle,
2718
- angle,
2719
- interacting,
2720
- ) {
2721
- const getRotateHandlePoint = function (
2722
- offset: number,
2723
- distanceFromArrowMidline,
2724
- ) {
2725
- const distFromRotationCenter =
2726
- radius + distanceFromArrowMidline;
2727
- const vec = kvector.cartFromPolarDeg(
2728
- distFromRotationCenter,
2729
- angle + offset,
2730
- );
2731
- const absolute = kvector.add(center, vec);
2732
- const pixels = graphie.scalePoint(absolute);
2733
- return pixels[0] + "," + pixels[1];
2734
- };
2735
-
2736
- const innerR = graphie.scaleVector(radius - halfWidth);
2737
- const outerR = graphie.scaleVector(radius + halfWidth);
2738
-
2739
- // Draw the double-headed arrow thing that shows users where to
2740
- // click and drag to rotate
2741
- return graphie.raphael
2742
- .path(
2743
- // upper arrowhead
2744
- " M" +
2745
- getRotateHandlePoint(lengthAngle, -halfWidth) +
2746
- " L" +
2747
- getRotateHandlePoint(lengthAngle, -3 * halfWidth) +
2748
- " L" +
2749
- getRotateHandlePoint(2 * lengthAngle, 0) +
2750
- " L" +
2751
- getRotateHandlePoint(lengthAngle, 3 * halfWidth) +
2752
- " L" +
2753
- getRotateHandlePoint(lengthAngle, halfWidth) +
2754
- // outer arc
2755
- " A" +
2756
- outerR[0] +
2757
- "," +
2758
- outerR[1] +
2759
- ",0,0,1," +
2760
- getRotateHandlePoint(-lengthAngle, halfWidth) +
2761
- // lower arrowhead
2762
- " L" +
2763
- getRotateHandlePoint(-lengthAngle, 3 * halfWidth) +
2764
- " L" +
2765
- getRotateHandlePoint(-2 * lengthAngle, 0) +
2766
- " L" +
2767
- getRotateHandlePoint(-lengthAngle, -3 * halfWidth) +
2768
- " L" +
2769
- getRotateHandlePoint(-lengthAngle, -halfWidth) +
2770
- // inner arc
2771
- " A" +
2772
- innerR[0] +
2773
- "," +
2774
- innerR[1] +
2775
- ",0,0,0," +
2776
- getRotateHandlePoint(lengthAngle, -halfWidth) +
2777
- " Z",
2778
- )
2779
- .attr({
2780
- stroke: null,
2781
- fill: interacting
2782
- ? KhanColors.INTERACTING
2783
- : KhanColors.INTERACTIVE,
2784
- });
2785
- };
2786
-
2787
- return function (options: any) {
2788
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
2789
- const graph = this;
2790
-
2791
- let rotatePoint = options.center;
2792
- const radius = options.radius;
2793
- const lengthAngle = options.lengthAngle || 30;
2794
- const hideArrow = options.hideArrow || false;
2795
- const mouseTarget = options.mouseTarget;
2796
- const id = _.uniqueId("rotateHandle");
2797
-
2798
- // Normalize rotatePoint into something that always looks
2799
- // like a movablePoint
2800
- if (_.isArray(rotatePoint)) {
2801
- rotatePoint = {
2802
- coord: rotatePoint,
2803
- };
2804
- }
2805
-
2806
- const rotateHandle = graph.addMovablePoint({
2807
- coord: kpoint.addVector(
2808
- rotatePoint.coord,
2809
- kvector.cartFromPolarDeg(radius, options.angleDeg || 0),
2810
- ),
2811
- constraints: {
2812
- fixedDistance: {
2813
- dist: radius,
2814
- point: rotatePoint,
2815
- },
2816
- },
2817
- mouseTarget: mouseTarget,
2818
- });
2819
-
2820
- // move the rotatePoint in front of the rotateHandle to avoid
2821
- // confusing clicking/scaling of the rotateHandle when the user
2822
- // intends to click on the rotatePoint
2823
- rotatePoint.toFront();
2824
-
2825
- let rotatePointPrevCoord = rotatePoint.coord;
2826
- let rotateHandlePrevCoord = rotateHandle.coord;
2827
- let rotateHandleStartCoord = rotateHandlePrevCoord;
2828
- let isRotating = false;
2829
- let isHovering = false;
2830
- let drawnRotateHandle;
2831
-
2832
- const redrawRotateHandle = function (
2833
- handleCoord: Array<never> | ReadonlyArray<number>,
2834
- ) {
2835
- if (hideArrow) {
2836
- return; // Don't draw anything!
2837
- }
2838
-
2839
- const handleVec = kvector.subtract(
2840
- handleCoord,
2841
- rotatePoint.coord,
2842
- );
2843
- const handlePolar = kvector.polarDegFromCart(handleVec);
2844
- const angle = handlePolar[1];
2845
-
2846
- if (drawnRotateHandle) {
2847
- drawnRotateHandle.remove();
2848
- }
2849
-
2850
- drawnRotateHandle = drawRotateHandle(
2851
- graph,
2852
- rotatePoint.coord,
2853
- options.radius,
2854
- isRotating || isHovering
2855
- ? options.hoverWidth / 2
2856
- : options.width / 2,
2857
- lengthAngle,
2858
- angle,
2859
- isRotating || isHovering,
2860
- );
2861
- };
2862
-
2863
- // when the rotation center moves, we need to move
2864
- // the rotationHandle as well, or it will end up out
2865
- // of sync
2866
- $(rotatePoint).on("move." + id, function () {
2867
- const delta = kvector.subtract(
2868
- rotatePoint.coord,
2869
- rotatePointPrevCoord,
2870
- );
2871
-
2872
- rotateHandle.setCoord(kvector.add(rotateHandle.coord, delta));
2873
-
2874
- redrawRotateHandle(rotateHandle.coord);
2875
-
2876
- rotatePointPrevCoord = rotatePoint.coord;
2877
- rotateHandle.constraints.fixedDistance.point = rotatePoint;
2878
- rotateHandlePrevCoord = rotateHandle.coord;
2879
- });
2880
-
2881
- // Rotate polygon with rotateHandle
2882
- rotateHandle.onMove = function (x, y) {
2883
- if (!isRotating) {
2884
- rotateHandleStartCoord = rotateHandlePrevCoord;
2885
- isRotating = true;
2886
- }
2887
-
2888
- let coord = [x, y];
2889
-
2890
- if (options.onMove) {
2891
- const oldPolar = kvector.polarDegFromCart(
2892
- kvector.subtract(
2893
- rotateHandlePrevCoord,
2894
- rotatePoint.coord,
2895
- ),
2896
- );
2897
- const newPolar = kvector.polarDegFromCart(
2898
- kvector.subtract(coord, rotatePoint.coord),
2899
- );
2900
-
2901
- const oldAngle = oldPolar[1];
2902
- const newAngle = newPolar[1];
2903
- let result = options.onMove(newAngle, oldAngle);
2904
- if (result != null && result !== true) {
2905
- if (result === false) {
2906
- result = oldAngle;
2907
- }
2908
- coord = kvector.add(
2909
- rotatePoint.coord,
2910
- kvector.cartFromPolarDeg(oldPolar[0], result),
2911
- );
2912
- }
2913
- }
2914
-
2915
- redrawRotateHandle(coord);
2916
-
2917
- rotateHandlePrevCoord = coord;
2918
- return coord;
2919
- };
2920
-
2921
- rotateHandle.onMoveEnd = function () {
2922
- isRotating = false;
2923
- redrawRotateHandle(rotateHandle.coord);
2924
- if (options.onMoveEnd) {
2925
- const oldPolar = kvector.polarDegFromCart(
2926
- kvector.subtract(
2927
- rotateHandleStartCoord,
2928
- rotatePoint.coord,
2929
- ),
2930
- );
2931
- const newPolar = kvector.polarDegFromCart(
2932
- kvector.subtract(rotateHandle.coord, rotatePoint.coord),
2933
- );
2934
- options.onMoveEnd(newPolar[1], oldPolar[1]);
2935
- }
2936
- };
2937
-
2938
- // Remove the default dot added by the movablePoint since we have
2939
- // our double-arrow thing
2940
- rotateHandle.visibleShape.remove();
2941
-
2942
- if (!mouseTarget) {
2943
- // Make the default mouse target bigger to encompass the whole
2944
- // area around the double-arrow thing
2945
- rotateHandle.mouseTarget.attr({scale: 2});
2946
- }
2947
-
2948
- const $mouseTarget = $(rotateHandle.mouseTarget.getMouseTarget());
2949
- $mouseTarget.bind("vmouseover", function (e) {
2950
- isHovering = true;
2951
- redrawRotateHandle(rotateHandle.coord);
2952
- });
2953
- $mouseTarget.bind("vmouseout", function (e) {
2954
- isHovering = false;
2955
- redrawRotateHandle(rotateHandle.coord);
2956
- });
2957
-
2958
- redrawRotateHandle(rotateHandle.coord);
2959
-
2960
- const oldRemove = rotateHandle.remove;
2961
- rotateHandle.remove = function () {
2962
- oldRemove.call(rotateHandle);
2963
- if (drawnRotateHandle) {
2964
- drawnRotateHandle.remove();
2965
- }
2966
- $(rotatePoint).off("move." + id);
2967
- };
2968
-
2969
- rotateHandle.update = function () {
2970
- redrawRotateHandle(rotateHandle.coord);
2971
- };
2972
-
2973
- return rotateHandle;
2974
- };
2975
- })(),
2976
-
2977
- addReflectButton: (function () {
2978
- const drawButton = function (
2979
- graphie,
2980
- buttonCoord: kpoint.Point,
2981
- lineCoords: Array<any> | Array<never> | Array<any | Array<number>>,
2982
- size: number,
2983
- distanceFromCenter: number,
2984
- leftStyle: any,
2985
- rightStyle: any,
2986
- ) {
2987
- // Avoid invalid lines
2988
- if (kpoint.equal(lineCoords[0], lineCoords[1])) {
2989
- lineCoords = [
2990
- lineCoords[0],
2991
- kpoint.addVector(lineCoords[0], [1, 1]),
2992
- ];
2993
- }
2994
-
2995
- const lineDirection = kvector.normalize(
2996
- kvector.subtract(lineCoords[1], lineCoords[0]),
2997
- );
2998
-
2999
- const lineVec = kvector.scale(lineDirection, size / 2);
3000
-
3001
- const centerVec = kvector.scale(lineDirection, distanceFromCenter);
3002
- const leftCenterVec = kvector.rotateDeg(centerVec, 90);
3003
- const rightCenterVec = kvector.rotateDeg(centerVec, -90);
3004
-
3005
- const negLineVec = kvector.negate(lineVec);
3006
- const leftVec = kvector.rotateDeg(lineVec, 90);
3007
- const rightVec = kvector.rotateDeg(lineVec, -90);
3008
-
3009
- const leftCenter = kpoint.addVectors(buttonCoord, leftCenterVec);
3010
- const rightCenter = kpoint.addVectors(buttonCoord, rightCenterVec);
3011
-
3012
- const leftCoord1 = kpoint.addVectors(
3013
- buttonCoord,
3014
- leftCenterVec,
3015
- lineVec,
3016
- leftVec,
3017
- );
3018
- const leftCoord2 = kpoint.addVectors(
3019
- buttonCoord,
3020
- leftCenterVec,
3021
- negLineVec,
3022
- leftVec,
3023
- );
3024
- const rightCoord1 = kpoint.addVectors(
3025
- buttonCoord,
3026
- rightCenterVec,
3027
- lineVec,
3028
- rightVec,
3029
- );
3030
- const rightCoord2 = kpoint.addVectors(
3031
- buttonCoord,
3032
- rightCenterVec,
3033
- negLineVec,
3034
- rightVec,
3035
- );
3036
-
3037
- const leftButton = graphie.path(
3038
- [leftCenter, leftCoord1, leftCoord2, true],
3039
- leftStyle,
3040
- );
3041
- const rightButton = graphie.path(
3042
- [rightCenter, rightCoord1, rightCoord2, true],
3043
- rightStyle,
3044
- );
3045
-
3046
- return {
3047
- remove: function () {
3048
- leftButton.remove();
3049
- rightButton.remove();
3050
- },
3051
- };
3052
- };
3053
-
3054
- return function (options: any) {
3055
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3056
- const graphie = this;
3057
-
3058
- const line = options.line;
3059
-
3060
- const button = graphie.addMovablePoint({
3061
- constraints: options.constraints,
3062
- coord: kline.midpoint([line.pointA.coord, line.pointZ.coord]),
3063
- snapX: graphie.snap[0],
3064
- snapY: graphie.snap[1],
3065
- onMove: function (x, y) {
3066
- // Don't allow the button to actually move. This is a hack
3067
- // around the inability to both set a point as fixed AND
3068
- // allow it to be clicked.
3069
- return false;
3070
- },
3071
- onMoveEnd: function (x, y) {
3072
- if (options.onMoveEnd) {
3073
- options.onMoveEnd.call(this, x, y);
3074
- }
3075
- },
3076
- });
3077
-
3078
- let isHovering = false;
3079
- let isFlipped = false;
3080
- let currentlyDrawnButton;
3081
-
3082
- const isHighlight = function () {
3083
- return isHovering;
3084
- };
3085
-
3086
- const styles = _.map([0, 1], function (isHighlight) {
3087
- const baseStyle = isHighlight
3088
- ? options.highlightStyle
3089
- : options.normalStyle;
3090
-
3091
- return _.map([0, 1], function (opacity) {
3092
- return _.defaults(
3093
- {
3094
- "fill-opacity": opacity,
3095
- },
3096
- baseStyle,
3097
- );
3098
- });
3099
- });
3100
-
3101
- const getStyle = function (isRight: number | boolean) {
3102
- if (isFlipped) {
3103
- isRight = !isRight;
3104
- }
3105
- return styles[+isHighlight()][+isRight];
3106
- };
3107
-
3108
- const redraw = function (
3109
- coord: kpoint.Point,
3110
- lineCoords: Array<any> | Array<never>,
3111
- ) {
3112
- if (currentlyDrawnButton) {
3113
- currentlyDrawnButton.remove();
3114
- }
3115
- currentlyDrawnButton = drawButton(
3116
- graphie,
3117
- coord,
3118
- lineCoords,
3119
- isHighlight() ? options.size * 1.5 : options.size,
3120
- isHighlight() ? options.size * 0.125 : 0.25,
3121
- getStyle(0),
3122
- getStyle(1),
3123
- );
3124
- };
3125
-
3126
- const update = function (coordA, coordZ) {
3127
- coordA = coordA || line.pointA.coord;
3128
- coordZ = coordZ || line.pointZ.coord;
3129
-
3130
- const buttonCoord = kline.midpoint([coordA, coordZ]);
3131
- button.setCoord(buttonCoord);
3132
-
3133
- redraw(buttonCoord, [coordA, coordZ]);
3134
- };
3135
-
3136
- $(line).on("move", _.bind(update, button, null, null));
3137
-
3138
- const $mouseTarget = $(button.mouseTarget.getMouseTarget());
3139
- $mouseTarget.on("vclick", function () {
3140
- const result = options.onClick();
3141
- if (result !== false) {
3142
- isFlipped = !isFlipped;
3143
- redraw(button.coord, [
3144
- line.pointA.coord,
3145
- line.pointZ.coord,
3146
- ]);
3147
- }
3148
- });
3149
-
3150
- // Bring the reflection line handles in front of the button, so
3151
- // that if we drag the reflectPoints really close together, we can
3152
- // still move the handles away from each other, rather than only
3153
- // being able to apply the reflection.
3154
- line.pointA.toFront();
3155
- line.pointZ.toFront();
3156
-
3157
- // Replace the visual point with the double triangle thing
3158
- button.visibleShape.remove();
3159
- const pointScale = graphie.scaleVector(options.size)[0] / 20;
3160
- button.mouseTarget.attr({scale: 1.5 * pointScale});
3161
- $mouseTarget.css("cursor", "pointer");
3162
-
3163
- // Make the arrow-thing grow and shrink with mouseover/out
3164
- $mouseTarget.bind("vmouseover", function (e) {
3165
- isHovering = true;
3166
- redraw(button.coord, [line.pointA.coord, line.pointZ.coord]);
3167
- });
3168
- $mouseTarget.bind("vmouseout", function (e) {
3169
- isHovering = false;
3170
- redraw(button.coord, [line.pointA.coord, line.pointZ.coord]);
3171
- });
3172
-
3173
- const oldButtonRemove = button.remove;
3174
- button.remove = function () {
3175
- currentlyDrawnButton.remove();
3176
- oldButtonRemove.call(button);
3177
- };
3178
-
3179
- button.update = update;
3180
- button.isFlipped = function () {
3181
- return isFlipped;
3182
- };
3183
-
3184
- // @ts-expect-error - TS2554 - Expected 2 arguments, but got 0.
3185
- update();
3186
- return button;
3187
- };
3188
- })(),
3189
-
3190
- protractor: function (center) {
3191
- // @ts-expect-error - TS2350 - Only a void function can be called with the 'new' keyword.
3192
- return new Protractor(this, center);
3193
- },
3194
-
3195
- ruler: function (options) {
3196
- // @ts-expect-error - TS2350 - Only a void function can be called with the 'new' keyword.
3197
- return new Ruler(this, options || {});
3198
- },
3199
-
3200
- addPoints: kvector.add,
3201
- });
3202
-
3203
- function Protractor(graph: any, center: any) {
3204
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3205
- this.set = graph.raphael.set();
3206
-
3207
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3208
- this.cx = center[0];
3209
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3210
- this.cy = center[1];
3211
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3212
- const pro = this;
3213
-
3214
- const r = graph.unscaleVector(180.5)[0];
3215
- const imgPos = graph.scalePoint([
3216
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3217
- this.cx - r,
3218
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3219
- this.cy + r - graph.unscaleVector(10.5)[1],
3220
- ]);
3221
- const image = graph.mouselayer.image(
3222
- "https://ka-perseus-graphie.s3.amazonaws.com/e9d032f2ab8b95979f674fbfa67056442ba1ff6a.png",
3223
- imgPos[0],
3224
- imgPos[1],
3225
- 360,
3226
- 180,
3227
- );
3228
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3229
- this.set.push(image);
3230
-
3231
- // Prevent the page from scrolling when we grab and drag the image on a
3232
- // mobile device.
3233
- image.node.addEventListener(
3234
- "touchstart",
3235
- function (event) {
3236
- event.preventDefault();
3237
- },
3238
- {passive: false},
3239
- );
3240
-
3241
- const arrowHelper = function (angle: number, pixelsFromEdge: number) {
3242
- const scaledRadius = graph.scaleVector(r);
3243
- scaledRadius[0] -= 16;
3244
- scaledRadius[1] -= 16;
3245
- const scaledCenter = graph.scalePoint(center);
3246
- const x =
3247
- Math.sin(((angle + 90) * Math.PI) / 180) *
3248
- (scaledRadius[0] + pixelsFromEdge) +
3249
- scaledCenter[0];
3250
- const y =
3251
- Math.cos(((angle + 90) * Math.PI) / 180) *
3252
- (scaledRadius[1] + pixelsFromEdge) +
3253
- scaledCenter[1];
3254
- return x + "," + y;
3255
- };
3256
-
3257
- const arrow = graph.raphael
3258
- .path(
3259
- " M" +
3260
- arrowHelper(180, 6) +
3261
- " L" +
3262
- arrowHelper(180, 2) +
3263
- " L" +
3264
- arrowHelper(183, 10) +
3265
- " L" +
3266
- arrowHelper(180, 18) +
3267
- " L" +
3268
- arrowHelper(180, 14) +
3269
- " A" +
3270
- (graph.scaleVector(r)[0] + 10) +
3271
- "," +
3272
- (graph.scaleVector(r)[1] + 10) +
3273
- ",0,0,1," +
3274
- arrowHelper(170, 14) +
3275
- " L" +
3276
- arrowHelper(170, 18) +
3277
- " L" +
3278
- arrowHelper(167, 10) +
3279
- " L" +
3280
- arrowHelper(170, 2) +
3281
- " L" +
3282
- arrowHelper(170, 6) +
3283
- " A" +
3284
- (graph.scaleVector(r)[0] + 10) +
3285
- "," +
3286
- (graph.scaleVector(r)[1] + 10) +
3287
- ",0,0,0," +
3288
- arrowHelper(180, 6) +
3289
- " Z",
3290
- )
3291
- .attr({
3292
- stroke: null,
3293
- fill: KhanColors.INTERACTIVE,
3294
- });
3295
-
3296
- // add it to the set so it translates with everything else
3297
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3298
- this.set.push(arrow);
3299
-
3300
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3301
- this.centerPoint = graph.addMovablePoint({
3302
- coord: center,
3303
- visible: false,
3304
- });
3305
-
3306
- // Use a movablePoint for rotation
3307
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3308
- this.rotateHandle = graph.addMovablePoint({
3309
- bounded: false,
3310
- coord: [
3311
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3312
- Math.sin((275 * Math.PI) / 180) * r + this.cx,
3313
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3314
- Math.cos((275 * Math.PI) / 180) * r + this.cy,
3315
- ],
3316
- onMove: function (x, y) {
3317
- const angle =
3318
- (Math.atan2(
3319
- pro.centerPoint.coord[1] - y,
3320
- pro.centerPoint.coord[0] - x,
3321
- ) *
3322
- 180) /
3323
- Math.PI;
3324
- pro.rotate(-angle - 5, true);
3325
- },
3326
- });
3327
-
3328
- // Add a constraint so the point moves in a circle
3329
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3330
- this.rotateHandle.constraints.fixedDistance.dist = r;
3331
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation. | TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3332
- this.rotateHandle.constraints.fixedDistance.point = this.centerPoint;
3333
-
3334
- // Remove the default dot added by the movablePoint since we have our
3335
- // double-arrow thing
3336
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3337
- this.rotateHandle.visibleShape.remove();
3338
- // Make the mouse target bigger to encompass the whole area around the
3339
- // double-arrow thing
3340
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3341
- this.rotateHandle.mouseTarget.attr({scale: 2.0});
3342
-
3343
- let isDragging = false;
3344
- let isHovering = false;
3345
- const isHighlight = function () {
3346
- return isHovering || isDragging;
3347
- };
3348
-
3349
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3350
- const self = this;
3351
- const $mouseTarget = $(self.rotateHandle.mouseTarget.getMouseTarget());
3352
- $mouseTarget.bind("vmousedown", function (event) {
3353
- isDragging = true;
3354
- $mouseTarget.css("cursor", "-webkit-grabbing");
3355
- $mouseTarget.css("cursor", "grabbing");
3356
- arrow.animate({scale: 1.5, fill: KhanColors.INTERACTING}, 50);
3357
-
3358
- $(document).bind("vmouseup.rotateHandle", function (event) {
3359
- isDragging = false;
3360
- $mouseTarget.css("cursor", "-webkit-grab");
3361
- $mouseTarget.css("cursor", "grab");
3362
-
3363
- if (!isHighlight()) {
3364
- arrow.animate({scale: 1.0, fill: KhanColors.INTERACTIVE}, 50);
3365
- }
3366
-
3367
- $(document).unbind("vmouseup.rotateHandle");
3368
- });
3369
- });
3370
-
3371
- $mouseTarget.bind("vmouseover", function (event) {
3372
- isHovering = true;
3373
- arrow.animate({scale: 1.5, fill: KhanColors.INTERACTING}, 50);
3374
- });
3375
- $mouseTarget.bind("vmouseout", function (event) {
3376
- isHovering = false;
3377
- if (!isHighlight()) {
3378
- arrow.animate({scale: 1.0, fill: KhanColors.INTERACTIVE}, 50);
3379
- }
3380
- });
3381
-
3382
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3383
- const setNodes = $.map(this.set, function (el) {
3384
- return el.node;
3385
- });
3386
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3387
- this.makeTranslatable = function makeTranslatable() {
3388
- $(setNodes).css("cursor", "move");
3389
- $mouseTarget.css("cursor", "-webkit-grab");
3390
- $mouseTarget.css("cursor", "grab");
3391
-
3392
- $(setNodes).bind("vmousedown", function (event) {
3393
- event.preventDefault();
3394
- let startx =
3395
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
3396
- event.pageX - $(graph.raphael.canvas.parentNode).offset().left;
3397
- let starty =
3398
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
3399
- event.pageY - $(graph.raphael.canvas.parentNode).offset().top;
3400
-
3401
- $(document).bind("vmousemove.protractor", function (event) {
3402
- let mouseX =
3403
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
3404
- event.pageX -
3405
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
3406
- $(graph.raphael.canvas.parentNode).offset().left;
3407
- let mouseY =
3408
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
3409
- event.pageY -
3410
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
3411
- $(graph.raphael.canvas.parentNode).offset().top;
3412
- // can't go beyond 10 pixels from the edge
3413
- mouseX = Math.max(10, Math.min(graph.xpixels - 10, mouseX));
3414
- mouseY = Math.max(10, Math.min(graph.ypixels - 10, mouseY));
3415
-
3416
- const dx = mouseX - startx;
3417
- const dy = mouseY - starty;
3418
-
3419
- $.each(pro.set.items, function () {
3420
- this.translate(dx, dy);
3421
- });
3422
- pro.centerPoint.setCoord([
3423
- pro.centerPoint.coord[0] + dx / graph.scale[0],
3424
- pro.centerPoint.coord[1] - dy / graph.scale[1],
3425
- ]);
3426
- pro.rotateHandle.setCoord([
3427
- pro.rotateHandle.coord[0] + dx / graph.scale[0],
3428
- pro.rotateHandle.coord[1] - dy / graph.scale[1],
3429
- ]);
3430
- startx = mouseX;
3431
- starty = mouseY;
3432
- });
3433
-
3434
- $(document).one("vmouseup", function (event) {
3435
- $(document).unbind("vmousemove.protractor");
3436
- });
3437
- });
3438
- };
3439
-
3440
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3441
- this.rotation = 0;
3442
-
3443
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3444
- this.rotate = function (offset: any, absolute: any) {
3445
- const center = graph.scalePoint(this.centerPoint.coord);
3446
-
3447
- if (absolute) {
3448
- this.rotation = 0;
3449
- }
3450
-
3451
- this.set.rotate(this.rotation + offset, center[0], center[1]);
3452
- this.rotation = this.rotation + offset;
3453
-
3454
- return this;
3455
- };
3456
-
3457
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3458
- this.moveTo = function moveTo(x: any, y: any) {
3459
- const start = graph.scalePoint(pro.centerPoint.coord);
3460
- const end = graph.scalePoint([x, y]);
3461
- const time = GraphUtils.getDistance(start, end) * 2;
3462
-
3463
- $({x: start[0], y: start[1]}).animate(
3464
- {x: end[0], y: end[1]},
3465
- {
3466
- duration: time,
3467
- step: function (now, fx) {
3468
- let dx = 0;
3469
- let dy = 0;
3470
- if (fx.prop === "x") {
3471
- dx = now - graph.scalePoint(pro.centerPoint.coord)[0];
3472
- } else if (fx.prop === "y") {
3473
- dy = now - graph.scalePoint(pro.centerPoint.coord)[1];
3474
- }
3475
- $.each(pro.set.items, function () {
3476
- this.translate(dx, dy);
3477
- });
3478
- pro.centerPoint.setCoord([
3479
- pro.centerPoint.coord[0] + dx / graph.scale[0],
3480
- pro.centerPoint.coord[1] - dy / graph.scale[1],
3481
- ]);
3482
- pro.rotateHandle.setCoord([
3483
- pro.rotateHandle.coord[0] + dx / graph.scale[0],
3484
- pro.rotateHandle.coord[1] - dy / graph.scale[1],
3485
- ]);
3486
- },
3487
- },
3488
- );
3489
- };
3490
-
3491
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3492
- this.rotateTo = function rotateTo(angle: any) {
3493
- if (Math.abs(this.rotation - angle) > 180) {
3494
- this.rotation += 360;
3495
- }
3496
- const time = Math.abs(this.rotation - angle) * 5;
3497
- $({0: this.rotation}).animate(
3498
- {0: angle},
3499
- {
3500
- duration: time,
3501
- step: function (now, fx) {
3502
- pro.rotate(now, true);
3503
- pro.rotateHandle.setCoord([
3504
- Math.sin(((now + 275) * Math.PI) / 180) * r +
3505
- pro.centerPoint.coord[0],
3506
- Math.cos(((now + 275) * Math.PI) / 180) * r +
3507
- pro.centerPoint.coord[1],
3508
- ]);
3509
- },
3510
- },
3511
- );
3512
- };
3513
-
3514
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3515
- this.remove = function () {
3516
- this.set.remove();
3517
- };
3518
-
3519
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3520
- this.makeTranslatable();
3521
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3522
- return this;
3523
- }
3524
-
3525
- function Ruler(graphie: any, options: any) {
3526
- _.defaults(options, {
3527
- center: [0, 0],
3528
- pixelsPerUnit: 40,
3529
- ticksPerUnit: 10, // 10 or power of 2
3530
- units: 10, // the length the ruler can measure
3531
- label: "", // e.g "cm" (the shorter, the better)
3532
- style: {
3533
- fill: null,
3534
- stroke: KhanColors.GRAY,
3535
- },
3536
- });
3537
-
3538
- const light = _.extend({}, options.style, {strokeWidth: 1});
3539
- const bold = _.extend({}, options.style, {strokeWidth: 2});
3540
-
3541
- const width = options.units * options.pixelsPerUnit;
3542
- const height = 50;
3543
-
3544
- const leftBottom = graphie.unscalePoint(
3545
- kvector.subtract(
3546
- graphie.scalePoint(options.center),
3547
- kvector.scale([width, -height], 0.5),
3548
- ),
3549
- );
3550
-
3551
- const graphieUnitsPerUnit = options.pixelsPerUnit / graphie.scale[0];
3552
- const graphieUnitsHeight = height / graphie.scale[0];
3553
-
3554
- const rightTop = kvector.add(leftBottom, [
3555
- options.units * graphieUnitsPerUnit,
3556
- graphieUnitsHeight,
3557
- ]);
3558
-
3559
- const tickHeight = 1.0;
3560
- let tickHeightMap;
3561
-
3562
- if (options.ticksPerUnit === 10) {
3563
- // decimal, as on a centimeter ruler
3564
- tickHeightMap = {
3565
- 10: tickHeight,
3566
- 5: tickHeight * 0.55,
3567
- 1: tickHeight * 0.35,
3568
- };
3569
- } else {
3570
- const sizes = [1, 0.6, 0.45, 0.3];
3571
-
3572
- tickHeightMap = {};
3573
- for (let i = options.ticksPerUnit; i >= 1; i /= 2) {
3574
- tickHeightMap[i] = tickHeight * (sizes.shift() || 0.2);
3575
- }
3576
- }
3577
-
3578
- const tickFrequencies = _.keys(tickHeightMap).sort(function (a, b) {
3579
- // @ts-expect-error - TS2362 - The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type. | TS2363 - The right-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.
3580
- return b - a;
3581
- });
3582
-
3583
- function getTickHeight(i: any): number {
3584
- for (let k = 0; k < tickFrequencies.length; k++) {
3585
- const key = tickFrequencies[k];
3586
- // @ts-expect-error - TS2363 - The right-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.
3587
- if (i % key === 0) {
3588
- return tickHeightMap[key];
3589
- }
3590
- }
3591
- return 0;
3592
- }
3593
-
3594
- const left = leftBottom[0];
3595
- const bottom = leftBottom[1];
3596
- const right = rightTop[0];
3597
- const top = rightTop[1];
3598
-
3599
- const numTicks = options.units * options.ticksPerUnit + 1;
3600
-
3601
- const set = graphie.raphael.set();
3602
-
3603
- const px = 1 / graphie.scale[0];
3604
- set.push(graphie.line([left - px, bottom], [right + px, bottom], bold));
3605
- set.push(graphie.line([left - px, top], [right + px, top], bold));
3606
-
3607
- _.times(numTicks, function (i) {
3608
- const n = i / options.ticksPerUnit;
3609
- const x = left + n * graphieUnitsPerUnit;
3610
- const height = getTickHeight(i) * graphieUnitsHeight;
3611
-
3612
- const style = i === 0 || i === numTicks - 1 ? bold : light;
3613
- set.push(graphie.line([x, bottom], [x, bottom + height], style));
3614
-
3615
- if (n % 1 === 0) {
3616
- const coord = graphie.scalePoint([x, top]);
3617
- let text;
3618
- let offset;
3619
-
3620
- if (n === 0) {
3621
- // Unit label
3622
- text = options.label;
3623
- offset =
3624
- {
3625
- mm: 13,
3626
- cm: 11,
3627
- m: 8,
3628
- km: 11,
3629
- in: 8,
3630
- ft: 8,
3631
- yd: 10,
3632
- mi: 10,
3633
- }[text] || 3 * text.toString().length;
3634
- } else {
3635
- // Tick label
3636
- text = n;
3637
- offset = -3 * (n.toString().length + 1);
3638
- }
3639
- const label = graphie.raphael.text(
3640
- coord[0] + offset,
3641
- coord[1] + 10,
3642
- text,
3643
- );
3644
- label.attr({
3645
- "font-family": "KaTeX_Main",
3646
- "font-size": "12px",
3647
- color: "#444",
3648
- });
3649
- set.push(label);
3650
- }
3651
- });
3652
-
3653
- const mouseTarget = graphie.mouselayer.path(
3654
- graphie.svgPath([
3655
- leftBottom,
3656
- [left, top],
3657
- rightTop,
3658
- [right, bottom],
3659
- /* closed */ true,
3660
- ]),
3661
- );
3662
- mouseTarget.attr({
3663
- fill: "#000",
3664
- opacity: 0,
3665
- stroke: "#000",
3666
- "stroke-width": 2,
3667
- });
3668
- set.push(mouseTarget);
3669
-
3670
- // Prevent the page from scrolling when we grab and drag the ruler on a
3671
- // mobile device.
3672
- mouseTarget.node.addEventListener(
3673
- "touchstart",
3674
- function (event) {
3675
- event.preventDefault();
3676
- },
3677
- {passive: false},
3678
- );
3679
-
3680
- const setNodes = $.map(set, function (el) {
3681
- return el.node;
3682
- });
3683
- $(setNodes).css("cursor", "move");
3684
-
3685
- $(setNodes).bind("vmousedown", function (event) {
3686
- event.preventDefault();
3687
- let startx =
3688
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
3689
- event.pageX - $(graphie.raphael.canvas.parentNode).offset().left;
3690
- let starty =
3691
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
3692
- event.pageY - $(graphie.raphael.canvas.parentNode).offset().top;
3693
-
3694
- $(document).bind("vmousemove.ruler", function (event) {
3695
- let mouseX =
3696
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
3697
- event.pageX -
3698
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
3699
- $(graphie.raphael.canvas.parentNode).offset().left;
3700
- let mouseY =
3701
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
3702
- event.pageY - $(graphie.raphael.canvas.parentNode).offset().top;
3703
- // can't go beyond 10 pixels from the edge
3704
- mouseX = Math.max(10, Math.min(graphie.xpixels - 10, mouseX));
3705
- mouseY = Math.max(10, Math.min(graphie.ypixels - 10, mouseY));
3706
-
3707
- const dx = mouseX - startx;
3708
- const dy = mouseY - starty;
3709
-
3710
- set.translate(dx, dy);
3711
- leftBottomHandle.setCoord([
3712
- leftBottomHandle.coord[0] + dx / graphie.scale[0],
3713
- leftBottomHandle.coord[1] - dy / graphie.scale[1],
3714
- ]);
3715
- rightBottomHandle.setCoord([
3716
- rightBottomHandle.coord[0] + dx / graphie.scale[0],
3717
- rightBottomHandle.coord[1] - dy / graphie.scale[1],
3718
- ]);
3719
-
3720
- startx = mouseX;
3721
- starty = mouseY;
3722
- });
3723
-
3724
- $(document).one("vmouseup", function (event) {
3725
- $(document).unbind("vmousemove.ruler");
3726
- });
3727
- });
3728
-
3729
- const leftBottomHandle = graphie.addMovablePoint({
3730
- coord: leftBottom,
3731
- normalStyle: {
3732
- fill: KhanColors.INTERACTIVE,
3733
- "fill-opacity": 0,
3734
- stroke: KhanColors.INTERACTIVE,
3735
- },
3736
- highlightStyle: {
3737
- fill: KhanColors.INTERACTING,
3738
- "fill-opacity": 0.1,
3739
- stroke: KhanColors.INTERACTING,
3740
- },
3741
- pointSize: 6, // or 8 maybe?
3742
- onMove: function (x, y) {
3743
- const dy = rightBottomHandle.coord[1] - y;
3744
- const dx = rightBottomHandle.coord[0] - x;
3745
- const angle = (Math.atan2(dy, dx) * 180) / Math.PI;
3746
- const center = kvector.scale(
3747
- kvector.add([x, y], rightBottomHandle.coord),
3748
- 0.5,
3749
- );
3750
- const scaledCenter = graphie.scalePoint(center);
3751
- const oldCenter = kvector.scale(
3752
- kvector.add(leftBottomHandle.coord, rightBottomHandle.coord),
3753
- 0.5,
3754
- );
3755
- const scaledOldCenter = graphie.scalePoint(oldCenter);
3756
- const diff = kvector.subtract(scaledCenter, scaledOldCenter);
3757
- set.rotate(-angle, scaledOldCenter[0], scaledOldCenter[1]);
3758
- set.translate(diff[0], diff[1]);
3759
- },
3760
- });
3761
- const rightBottomHandle = graphie.addMovablePoint({
3762
- coord: [right, bottom],
3763
- normalStyle: {
3764
- fill: KhanColors.INTERACTIVE,
3765
- "fill-opacity": 0,
3766
- stroke: KhanColors.INTERACTIVE,
3767
- },
3768
- highlightStyle: {
3769
- fill: KhanColors.INTERACTING,
3770
- "fill-opacity": 0.1,
3771
- stroke: KhanColors.INTERACTING,
3772
- },
3773
- pointSize: 6, // or 8 maybe?
3774
- onMove: function (x, y) {
3775
- const dy = y - leftBottomHandle.coord[1];
3776
- const dx = x - leftBottomHandle.coord[0];
3777
- const angle = (Math.atan2(dy, dx) * 180) / Math.PI;
3778
- const center = kvector.scale(
3779
- kvector.add([x, y], leftBottomHandle.coord),
3780
- 0.5,
3781
- );
3782
- const scaledCenter = graphie.scalePoint(center);
3783
- const oldCenter = kvector.scale(
3784
- kvector.add(leftBottomHandle.coord, rightBottomHandle.coord),
3785
- 0.5,
3786
- );
3787
- const scaledOldCenter = graphie.scalePoint(oldCenter);
3788
- const diff = kvector.subtract(scaledCenter, scaledOldCenter);
3789
- set.rotate(-angle, scaledOldCenter[0], scaledOldCenter[1]);
3790
- set.translate(diff[0], diff[1]);
3791
- },
3792
- });
3793
-
3794
- // Make each handle rotate the ruler about the other one
3795
- leftBottomHandle.constraints.fixedDistance.dist = width / graphie.scale[0];
3796
- leftBottomHandle.constraints.fixedDistance.point = rightBottomHandle;
3797
- rightBottomHandle.constraints.fixedDistance.dist = width / graphie.scale[0];
3798
- rightBottomHandle.constraints.fixedDistance.point = leftBottomHandle;
3799
-
3800
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3801
- this.remove = function () {
3802
- set.remove();
3803
- leftBottomHandle.remove();
3804
- rightBottomHandle.remove();
3805
- };
3806
-
3807
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3808
- return this;
3809
- }
3810
-
3811
- function MovableAngle(graphie: any, options: any) {
3812
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3813
- this.graphie = graphie;
3814
-
3815
- // TODO(alex): Move standard colors from math.js to somewhere else
3816
- // so that they are available when this file is first parsed
3817
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3818
- _.extend(this, options);
3819
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3820
- _.defaults(this, {
3821
- normalStyle: {
3822
- stroke: KhanColors.INTERACTIVE,
3823
- "stroke-width": 2,
3824
- fill: KhanColors.INTERACTIVE,
3825
- },
3826
- highlightStyle: {
3827
- stroke: KhanColors.INTERACTING,
3828
- "stroke-width": 2,
3829
- fill: KhanColors.INTERACTING,
3830
- },
3831
- labelStyle: {
3832
- stroke: KhanColors.DYNAMIC,
3833
- "stroke-width": 1,
3834
- color: KhanColors.DYNAMIC,
3835
- },
3836
- angleStyle: {
3837
- stroke: KhanColors.DYNAMIC,
3838
- "stroke-width": 1,
3839
- color: KhanColors.DYNAMIC,
3840
- },
3841
- allowReflex: true, // not on MovableAngle.prototype so that
3842
- // it is not overridden by undefined
3843
- });
3844
-
3845
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation. | TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3846
- if (!this.points || this.points.length !== 3) {
3847
- throw new PerseusError(
3848
- "MovableAngle requires 3 points",
3849
- Errors.InvalidInput,
3850
- );
3851
- }
3852
-
3853
- // Handle coordinates that are not MovablePoints (i.e. [2, 4])
3854
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3855
- this.points = _.map(
3856
- options.points,
3857
- function (point) {
3858
- if (_.isArray(point)) {
3859
- return graphie.addMovablePoint({
3860
- coord: point,
3861
- visible: false,
3862
- constraints: {
3863
- fixed: true,
3864
- },
3865
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3866
- normalStyle: this.normalStyle,
3867
- });
3868
- }
3869
- return point;
3870
- },
3871
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3872
- this,
3873
- );
3874
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation. | TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3875
- this.coords = _.pluck(this.points, "coord");
3876
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3877
- if (this.reflex == null) {
3878
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3879
- if (this.allowReflex) {
3880
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation. | TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation. | TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3881
- this.reflex = this._getClockwiseAngle(this.coords) > 180;
3882
- } else {
3883
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3884
- this.reflex = false;
3885
- }
3886
- }
3887
-
3888
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3889
- this.rays = _.map(
3890
- [0, 2],
3891
- function (i) {
3892
- return graphie.addMovableLineSegment({
3893
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3894
- pointA: this.points[1],
3895
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3896
- pointZ: this.points[i],
3897
- fixed: true,
3898
- extendRay: true,
3899
- });
3900
- },
3901
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3902
- this,
3903
- );
3904
-
3905
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3906
- this.temp = [];
3907
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation. | TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3908
- this.labeledAngle = graphie.label([0, 0], "", "center", this.labelStyle);
3909
-
3910
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3911
- if (!this.fixed) {
3912
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3913
- this.addMoveHandlers();
3914
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3915
- this.addHighlightHandlers();
3916
- }
3917
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
3918
- this.update();
3919
- }
3920
-
3921
- _.extend(MovableAngle.prototype, {
3922
- points: [],
3923
- snapDegrees: 0,
3924
- snapOffsetDeg: 0,
3925
- angleLabel: "",
3926
- numArcs: 1,
3927
- pushOut: 0,
3928
- fixed: false,
3929
-
3930
- addMoveHandlers: function () {
3931
- const graphie = this.graphie;
3932
-
3933
- function tooClose(point1: Array<never>, point2: Array<never>) {
3934
- const safeDistance = 30;
3935
- const distance = GraphUtils.getDistance(
3936
- graphie.scalePoint(point1),
3937
- graphie.scalePoint(point2),
3938
- );
3939
- return distance < safeDistance;
3940
- }
3941
-
3942
- const points = this.points;
3943
-
3944
- // Drag the vertex to move the entire angle
3945
- points[1].onMove = function (x: number, y: number) {
3946
- const oldVertex = points[1].coord;
3947
- const newVertex: Coord = [x, y];
3948
- const delta = kvector.add(newVertex, reverseVector(oldVertex));
3949
-
3950
- let valid = true;
3951
- const newPoints: Record<string, any> = {};
3952
- _.each([0, 2], function (i) {
3953
- const oldPoint = points[i].coord;
3954
- let newPoint = kvector.add(oldPoint, delta);
3955
-
3956
- let angle = GraphUtils.findAngle(newVertex, newPoint);
3957
- angle *= Math.PI / 180;
3958
- newPoint = graphie.constrainToBoundsOnAngle(
3959
- newPoint,
3960
- 10,
3961
- angle,
3962
- );
3963
- newPoints[i] = newPoint;
3964
-
3965
- // @ts-expect-error - TS2345 - Argument of type 'any[]' is not assignable to parameter of type 'never[]'.
3966
- if (tooClose(newVertex, newPoint)) {
3967
- valid = false;
3968
- }
3969
- });
3970
-
3971
- // Only move points if all new positions are valid
3972
- if (valid) {
3973
- _.each(newPoints, function (newPoint, i) {
3974
- points[i].setCoord(newPoint);
3975
- });
3976
- }
3977
- return valid;
3978
- };
3979
-
3980
- const snap = this.snapDegrees;
3981
- const snapOffset = this.snapOffsetDeg;
3982
-
3983
- // Drag ray control points to move each ray individually
3984
- _.each([0, 2], function (i) {
3985
- points[i].onMove = function (x: number, y: number) {
3986
- const newPoint: Coord = [x, y];
3987
- const vertex = points[1].coord;
3988
-
3989
- // @ts-expect-error - TS2345 - Argument of type 'any[]' is not assignable to parameter of type 'never[]'.
3990
- if (tooClose(vertex, newPoint)) {
3991
- return false;
3992
- }
3993
- if (snap) {
3994
- let angle = GraphUtils.findAngle(newPoint, vertex);
3995
- angle =
3996
- Math.round((angle - snapOffset) / snap) * snap +
3997
- snapOffset;
3998
- const distance = GraphUtils.getDistance(newPoint, vertex);
3999
- return kvector.add(vertex, polar(distance, angle));
4000
- }
4001
- return true;
4002
- };
4003
- });
4004
-
4005
- // Expose only a single move event
4006
- $(points).on(
4007
- "move",
4008
- function () {
4009
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
4010
- this.update();
4011
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
4012
- $(this).trigger("move");
4013
- }.bind(this),
4014
- );
4015
- },
4016
-
4017
- addHighlightHandlers: function () {
4018
- const vertex = this.points[1];
4019
-
4020
- vertex.onHighlight = function () {
4021
- _.each(
4022
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
4023
- this.points,
4024
- function (point) {
4025
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
4026
- point.visibleShape.animate(this.highlightStyle, 50);
4027
- },
4028
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
4029
- this,
4030
- );
4031
- _.each(
4032
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
4033
- this.rays,
4034
- function (ray) {
4035
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
4036
- ray.visibleLine.animate(this.highlightStyle, 50);
4037
- ray.arrowStyle = _.extend({}, ray.arrowStyle, {
4038
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
4039
- color: this.highlightStyle.stroke,
4040
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
4041
- stroke: this.highlightStyle.stroke,
4042
- });
4043
- },
4044
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
4045
- this,
4046
- );
4047
-
4048
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation. | TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
4049
- this.angleStyle = _.extend({}, this.angleStyle, {
4050
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
4051
- color: this.highlightStyle.stroke,
4052
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
4053
- stroke: this.highlightStyle.stroke,
4054
- });
4055
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
4056
- this.update();
4057
- }.bind(this);
4058
-
4059
- vertex.onUnhighlight = function () {
4060
- _.each(
4061
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
4062
- this.points,
4063
- function (point) {
4064
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
4065
- point.visibleShape.animate(this.normalStyle, 50);
4066
- },
4067
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
4068
- this,
4069
- );
4070
- _.each(
4071
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
4072
- this.rays,
4073
- function (ray) {
4074
- ray.visibleLine.animate(ray.normalStyle, 50);
4075
- ray.arrowStyle = _.extend({}, ray.arrowStyle, {
4076
- color: ray.normalStyle.stroke,
4077
- stroke: ray.normalStyle.stroke,
4078
- });
4079
- },
4080
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
4081
- this,
4082
- );
4083
-
4084
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation. | TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
4085
- this.angleStyle = _.extend({}, this.angleStyle, {
4086
- color: KhanColors.DYNAMIC,
4087
- stroke: KhanColors.DYNAMIC,
4088
- });
4089
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
4090
- this.update();
4091
- }.bind(this);
4092
- },
4093
-
4094
- /**
4095
- * Returns the angle in [0, 360) degrees created by the
4096
- * coords when interpreted in a clockwise direction.
4097
- */
4098
- _getClockwiseAngle: function (coords) {
4099
- const rawAngle = GraphUtils.findAngle(
4100
- // The order of these is "weird" to match what a clockwise
4101
- // order is in graphie.labelAngle
4102
- coords[2], // from the second point
4103
- coords[0], // clockwise to the first point
4104
- coords[1], // the vertex parameter is last
4105
- );
4106
- const clockwiseAngle = rawAngle + (360 % 360);
4107
-
4108
- return clockwiseAngle;
4109
- },
4110
-
4111
- isReflex: function () {
4112
- return this.reflex;
4113
- },
4114
-
4115
- isClockwise: function () {
4116
- const clockwiseReflexive = this._getClockwiseAngle(this.coords) > 180;
4117
- return clockwiseReflexive === this.reflex;
4118
- },
4119
-
4120
- getClockwiseCoords: function () {
4121
- if (this.isClockwise()) {
4122
- return _.clone(this.coords);
4123
- }
4124
- return _.clone(this.coords).reverse();
4125
- },
4126
-
4127
- update: function (shouldChangeReflexivity) {
4128
- const prevCoords = this.coords;
4129
- this.coords = _.pluck(this.points, "coord");
4130
-
4131
- // Update lines
4132
- _.invoke(this.points, "updateLineEnds");
4133
-
4134
- const prevAngle = this._getClockwiseAngle(prevCoords);
4135
- const angle = this._getClockwiseAngle(this.coords);
4136
- const prevClockwiseReflexive = prevAngle > 180;
4137
- const clockwiseReflexive = angle > 180;
4138
-
4139
- if (this.allowReflex) {
4140
- if (shouldChangeReflexivity == null) {
4141
- shouldChangeReflexivity =
4142
- prevClockwiseReflexive !== clockwiseReflexive &&
4143
- Math.abs(angle - prevAngle) < 180;
4144
- }
4145
-
4146
- if (shouldChangeReflexivity) {
4147
- this.reflex = !this.reflex;
4148
- }
4149
- }
4150
-
4151
- _.invoke(this.temp, "remove");
4152
- this.temp = this.graphie.labelAngle({
4153
- point1: this.coords[0],
4154
- vertex: this.coords[1],
4155
- point3: this.coords[2],
4156
- label: this.labeledAngle,
4157
- text: this.angleLabel,
4158
- numArcs: this.numArcs,
4159
- pushOut: this.pushOut,
4160
- clockwise: this.reflex === clockwiseReflexive,
4161
- style: this.angleStyle,
4162
- });
4163
- },
4164
-
4165
- remove: function () {
4166
- _.invoke(this.rays, "remove");
4167
- _.invoke(this.temp, "remove");
4168
- this.labeledAngle.remove();
4169
- },
4170
- });
4171
-
4172
- export default InteractiveUtils;