@khanacademy/perseus 19.1.5 → 19.1.7

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