@rws-framework/client 2.13.1 → 2.13.3

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 (288) hide show
  1. package/.bin/add-v.sh +9 -9
  2. package/.bin/emerge.sh +15 -15
  3. package/.emerge-fast.yaml +46 -46
  4. package/.emerge-typescript-template.yaml +23 -23
  5. package/.emerge-vis-output/fast-element/emerge-file_result_dependency_graph.graphml +693 -693
  6. package/.emerge-vis-output/fast-element/emerge-filesystem_graph.graphml +705 -705
  7. package/.emerge-vis-output/fast-element/emerge-statistics-metrics.txt +440 -440
  8. package/.emerge-vis-output/fast-element/html/emerge.html +500 -500
  9. package/.emerge-vis-output/fast-element/html/jsconfig.json +8 -8
  10. package/.emerge-vis-output/fast-element/html/resources/css/custom.css +211 -211
  11. package/.emerge-vis-output/fast-element/html/resources/js/emerge_common.js +44 -44
  12. package/.emerge-vis-output/fast-element/html/resources/js/emerge_data.js +13 -13
  13. package/.emerge-vis-output/fast-element/html/resources/js/emerge_git.js +1413 -1413
  14. package/.emerge-vis-output/fast-element/html/resources/js/emerge_graph.js +539 -539
  15. package/.emerge-vis-output/fast-element/html/resources/js/emerge_heatmap.js +219 -219
  16. package/.emerge-vis-output/fast-element/html/resources/js/emerge_hull.js +179 -179
  17. package/.emerge-vis-output/fast-element/html/resources/js/emerge_main.js +1002 -1002
  18. package/.emerge-vis-output/fast-element/html/resources/js/emerge_search.js +71 -71
  19. package/.emerge-vis-output/fast-element/html/resources/js/emerge_ui.js +199 -199
  20. package/.emerge-vis-output/fast-element/html/vendors/bootstrap/css/bootstrap-grid.css +4123 -4123
  21. package/.emerge-vis-output/fast-element/html/vendors/bootstrap/css/bootstrap-grid.min.css +6 -6
  22. package/.emerge-vis-output/fast-element/html/vendors/bootstrap/css/bootstrap-grid.rtl.css +4122 -4122
  23. package/.emerge-vis-output/fast-element/html/vendors/bootstrap/css/bootstrap-grid.rtl.min.css +6 -6
  24. package/.emerge-vis-output/fast-element/html/vendors/bootstrap/css/bootstrap-reboot.css +487 -487
  25. package/.emerge-vis-output/fast-element/html/vendors/bootstrap/css/bootstrap-reboot.min.css +6 -6
  26. package/.emerge-vis-output/fast-element/html/vendors/bootstrap/css/bootstrap-reboot.rtl.css +484 -484
  27. package/.emerge-vis-output/fast-element/html/vendors/bootstrap/css/bootstrap-reboot.rtl.min.css +6 -6
  28. package/.emerge-vis-output/fast-element/html/vendors/bootstrap/css/bootstrap-utilities.css +4265 -4265
  29. package/.emerge-vis-output/fast-element/html/vendors/bootstrap/css/bootstrap-utilities.min.css +6 -6
  30. package/.emerge-vis-output/fast-element/html/vendors/bootstrap/css/bootstrap-utilities.rtl.css +4256 -4256
  31. package/.emerge-vis-output/fast-element/html/vendors/bootstrap/css/bootstrap-utilities.rtl.min.css +6 -6
  32. package/.emerge-vis-output/fast-element/html/vendors/bootstrap/css/bootstrap.css +10877 -10877
  33. package/.emerge-vis-output/fast-element/html/vendors/bootstrap/css/bootstrap.min.css +6 -6
  34. package/.emerge-vis-output/fast-element/html/vendors/bootstrap/css/bootstrap.rtl.css +10841 -10841
  35. package/.emerge-vis-output/fast-element/html/vendors/bootstrap/css/bootstrap.rtl.min.css +6 -6
  36. package/.emerge-vis-output/fast-element/html/vendors/bootstrap/js/bootstrap.bundle.js +7075 -7075
  37. package/.emerge-vis-output/fast-element/html/vendors/bootstrap/js/bootstrap.bundle.min.js +6 -6
  38. package/.emerge-vis-output/fast-element/html/vendors/bootstrap/js/bootstrap.esm.js +5202 -5202
  39. package/.emerge-vis-output/fast-element/html/vendors/bootstrap/js/bootstrap.esm.min.js +6 -6
  40. package/.emerge-vis-output/fast-element/html/vendors/bootstrap/js/bootstrap.js +5249 -5249
  41. package/.emerge-vis-output/fast-element/html/vendors/bootstrap/js/bootstrap.min.js +6 -6
  42. package/.emerge-vis-output/fast-element/html/vendors/d3/d3.v7.8.4.min.js +2 -2
  43. package/.emerge-vis-output/fast-element/html/vendors/d3/d3.v7.min.js +2 -2
  44. package/.emerge-vis-output/fast-element/html/vendors/dark-mode-switch/css/dark-mode.css +148 -148
  45. package/.emerge-vis-output/fast-element/html/vendors/daterangepicker/daterangepicker.min.js +7 -7
  46. package/.emerge-vis-output/fast-element/html/vendors/daterangepicker/moment.min.js +6 -6
  47. package/.emerge-vis-output/fast-element/html/vendors/hull/hull.js +373 -373
  48. package/.emerge-vis-output/fast-element/html/vendors/jquery/jquery-3.6.0.min.js +2 -2
  49. package/.emerge-vis-output/fast-element/html/vendors/popper/popper.min.js +6 -6
  50. package/.emerge-vis-output/fast-element/html/vendors/simpleheat/simpleheat.js +141 -141
  51. package/.emerge-vis-output/fast-foundation/emerge-file_result_dependency_graph.graphml +4043 -4043
  52. package/.emerge-vis-output/fast-foundation/emerge-filesystem_graph.graphml +4538 -4538
  53. package/.emerge-vis-output/fast-foundation/emerge-statistics-metrics.txt +2510 -2510
  54. package/.emerge-vis-output/fast-foundation/html/emerge.html +500 -500
  55. package/.emerge-vis-output/fast-foundation/html/jsconfig.json +8 -8
  56. package/.emerge-vis-output/fast-foundation/html/resources/css/custom.css +211 -211
  57. package/.emerge-vis-output/fast-foundation/html/resources/js/emerge_common.js +44 -44
  58. package/.emerge-vis-output/fast-foundation/html/resources/js/emerge_data.js +13 -13
  59. package/.emerge-vis-output/fast-foundation/html/resources/js/emerge_git.js +1413 -1413
  60. package/.emerge-vis-output/fast-foundation/html/resources/js/emerge_graph.js +539 -539
  61. package/.emerge-vis-output/fast-foundation/html/resources/js/emerge_heatmap.js +219 -219
  62. package/.emerge-vis-output/fast-foundation/html/resources/js/emerge_hull.js +179 -179
  63. package/.emerge-vis-output/fast-foundation/html/resources/js/emerge_main.js +1002 -1002
  64. package/.emerge-vis-output/fast-foundation/html/resources/js/emerge_search.js +71 -71
  65. package/.emerge-vis-output/fast-foundation/html/resources/js/emerge_ui.js +199 -199
  66. package/.emerge-vis-output/fast-foundation/html/vendors/bootstrap/css/bootstrap-grid.css +4123 -4123
  67. package/.emerge-vis-output/fast-foundation/html/vendors/bootstrap/css/bootstrap-grid.min.css +6 -6
  68. package/.emerge-vis-output/fast-foundation/html/vendors/bootstrap/css/bootstrap-grid.rtl.css +4122 -4122
  69. package/.emerge-vis-output/fast-foundation/html/vendors/bootstrap/css/bootstrap-grid.rtl.min.css +6 -6
  70. package/.emerge-vis-output/fast-foundation/html/vendors/bootstrap/css/bootstrap-reboot.css +487 -487
  71. package/.emerge-vis-output/fast-foundation/html/vendors/bootstrap/css/bootstrap-reboot.min.css +6 -6
  72. package/.emerge-vis-output/fast-foundation/html/vendors/bootstrap/css/bootstrap-reboot.rtl.css +484 -484
  73. package/.emerge-vis-output/fast-foundation/html/vendors/bootstrap/css/bootstrap-reboot.rtl.min.css +6 -6
  74. package/.emerge-vis-output/fast-foundation/html/vendors/bootstrap/css/bootstrap-utilities.css +4265 -4265
  75. package/.emerge-vis-output/fast-foundation/html/vendors/bootstrap/css/bootstrap-utilities.min.css +6 -6
  76. package/.emerge-vis-output/fast-foundation/html/vendors/bootstrap/css/bootstrap-utilities.rtl.css +4256 -4256
  77. package/.emerge-vis-output/fast-foundation/html/vendors/bootstrap/css/bootstrap-utilities.rtl.min.css +6 -6
  78. package/.emerge-vis-output/fast-foundation/html/vendors/bootstrap/css/bootstrap.css +10877 -10877
  79. package/.emerge-vis-output/fast-foundation/html/vendors/bootstrap/css/bootstrap.min.css +6 -6
  80. package/.emerge-vis-output/fast-foundation/html/vendors/bootstrap/css/bootstrap.rtl.css +10841 -10841
  81. package/.emerge-vis-output/fast-foundation/html/vendors/bootstrap/css/bootstrap.rtl.min.css +6 -6
  82. package/.emerge-vis-output/fast-foundation/html/vendors/bootstrap/js/bootstrap.bundle.js +7075 -7075
  83. package/.emerge-vis-output/fast-foundation/html/vendors/bootstrap/js/bootstrap.bundle.min.js +6 -6
  84. package/.emerge-vis-output/fast-foundation/html/vendors/bootstrap/js/bootstrap.esm.js +5202 -5202
  85. package/.emerge-vis-output/fast-foundation/html/vendors/bootstrap/js/bootstrap.esm.min.js +6 -6
  86. package/.emerge-vis-output/fast-foundation/html/vendors/bootstrap/js/bootstrap.js +5249 -5249
  87. package/.emerge-vis-output/fast-foundation/html/vendors/bootstrap/js/bootstrap.min.js +6 -6
  88. package/.emerge-vis-output/fast-foundation/html/vendors/d3/d3.v7.8.4.min.js +2 -2
  89. package/.emerge-vis-output/fast-foundation/html/vendors/d3/d3.v7.min.js +2 -2
  90. package/.emerge-vis-output/fast-foundation/html/vendors/dark-mode-switch/css/dark-mode.css +148 -148
  91. package/.emerge-vis-output/fast-foundation/html/vendors/daterangepicker/daterangepicker.min.js +7 -7
  92. package/.emerge-vis-output/fast-foundation/html/vendors/daterangepicker/moment.min.js +6 -6
  93. package/.emerge-vis-output/fast-foundation/html/vendors/hull/hull.js +373 -373
  94. package/.emerge-vis-output/fast-foundation/html/vendors/jquery/jquery-3.6.0.min.js +2 -2
  95. package/.emerge-vis-output/fast-foundation/html/vendors/popper/popper.min.js +6 -6
  96. package/.emerge-vis-output/fast-foundation/html/vendors/simpleheat/simpleheat.js +141 -141
  97. package/.emerge-vis-output/rws-client/emerge-file_result_dependency_graph.graphml +1143 -1143
  98. package/.emerge-vis-output/rws-client/emerge-filesystem_graph.graphml +1003 -1003
  99. package/.emerge-vis-output/rws-client/emerge-statistics-metrics.txt +655 -655
  100. package/.emerge-vis-output/rws-client/html/emerge.html +500 -500
  101. package/.emerge-vis-output/rws-client/html/jsconfig.json +8 -8
  102. package/.emerge-vis-output/rws-client/html/resources/css/custom.css +211 -211
  103. package/.emerge-vis-output/rws-client/html/resources/js/emerge_common.js +44 -44
  104. package/.emerge-vis-output/rws-client/html/resources/js/emerge_data.js +13 -13
  105. package/.emerge-vis-output/rws-client/html/resources/js/emerge_git.js +1413 -1413
  106. package/.emerge-vis-output/rws-client/html/resources/js/emerge_graph.js +539 -539
  107. package/.emerge-vis-output/rws-client/html/resources/js/emerge_heatmap.js +219 -219
  108. package/.emerge-vis-output/rws-client/html/resources/js/emerge_hull.js +179 -179
  109. package/.emerge-vis-output/rws-client/html/resources/js/emerge_main.js +1002 -1002
  110. package/.emerge-vis-output/rws-client/html/resources/js/emerge_search.js +71 -71
  111. package/.emerge-vis-output/rws-client/html/resources/js/emerge_ui.js +199 -199
  112. package/.emerge-vis-output/rws-client/html/vendors/bootstrap/css/bootstrap-grid.css +4123 -4123
  113. package/.emerge-vis-output/rws-client/html/vendors/bootstrap/css/bootstrap-grid.min.css +6 -6
  114. package/.emerge-vis-output/rws-client/html/vendors/bootstrap/css/bootstrap-grid.rtl.css +4122 -4122
  115. package/.emerge-vis-output/rws-client/html/vendors/bootstrap/css/bootstrap-grid.rtl.min.css +6 -6
  116. package/.emerge-vis-output/rws-client/html/vendors/bootstrap/css/bootstrap-reboot.css +487 -487
  117. package/.emerge-vis-output/rws-client/html/vendors/bootstrap/css/bootstrap-reboot.min.css +6 -6
  118. package/.emerge-vis-output/rws-client/html/vendors/bootstrap/css/bootstrap-reboot.rtl.css +484 -484
  119. package/.emerge-vis-output/rws-client/html/vendors/bootstrap/css/bootstrap-reboot.rtl.min.css +6 -6
  120. package/.emerge-vis-output/rws-client/html/vendors/bootstrap/css/bootstrap-utilities.css +4265 -4265
  121. package/.emerge-vis-output/rws-client/html/vendors/bootstrap/css/bootstrap-utilities.min.css +6 -6
  122. package/.emerge-vis-output/rws-client/html/vendors/bootstrap/css/bootstrap-utilities.rtl.css +4256 -4256
  123. package/.emerge-vis-output/rws-client/html/vendors/bootstrap/css/bootstrap-utilities.rtl.min.css +6 -6
  124. package/.emerge-vis-output/rws-client/html/vendors/bootstrap/css/bootstrap.css +10877 -10877
  125. package/.emerge-vis-output/rws-client/html/vendors/bootstrap/css/bootstrap.min.css +6 -6
  126. package/.emerge-vis-output/rws-client/html/vendors/bootstrap/css/bootstrap.rtl.css +10841 -10841
  127. package/.emerge-vis-output/rws-client/html/vendors/bootstrap/css/bootstrap.rtl.min.css +6 -6
  128. package/.emerge-vis-output/rws-client/html/vendors/bootstrap/js/bootstrap.bundle.js +7075 -7075
  129. package/.emerge-vis-output/rws-client/html/vendors/bootstrap/js/bootstrap.bundle.min.js +6 -6
  130. package/.emerge-vis-output/rws-client/html/vendors/bootstrap/js/bootstrap.esm.js +5202 -5202
  131. package/.emerge-vis-output/rws-client/html/vendors/bootstrap/js/bootstrap.esm.min.js +6 -6
  132. package/.emerge-vis-output/rws-client/html/vendors/bootstrap/js/bootstrap.js +5249 -5249
  133. package/.emerge-vis-output/rws-client/html/vendors/bootstrap/js/bootstrap.min.js +6 -6
  134. package/.emerge-vis-output/rws-client/html/vendors/d3/d3.v7.8.4.min.js +2 -2
  135. package/.emerge-vis-output/rws-client/html/vendors/d3/d3.v7.min.js +2 -2
  136. package/.emerge-vis-output/rws-client/html/vendors/dark-mode-switch/css/dark-mode.css +148 -148
  137. package/.emerge-vis-output/rws-client/html/vendors/daterangepicker/daterangepicker.min.js +7 -7
  138. package/.emerge-vis-output/rws-client/html/vendors/daterangepicker/moment.min.js +6 -6
  139. package/.emerge-vis-output/rws-client/html/vendors/hull/hull.js +373 -373
  140. package/.emerge-vis-output/rws-client/html/vendors/jquery/jquery-3.6.0.min.js +2 -2
  141. package/.emerge-vis-output/rws-client/html/vendors/popper/popper.min.js +6 -6
  142. package/.emerge-vis-output/rws-client/html/vendors/simpleheat/simpleheat.js +141 -141
  143. package/.eslintrc.json +51 -51
  144. package/.gitmodules +2 -2
  145. package/.setup/.eslintrc.json +11 -11
  146. package/.setup/_base.eslintrc.json +48 -48
  147. package/.setup/tsconfig.json +28 -28
  148. package/PLUGINS.MD +188 -188
  149. package/README.md +1023 -1023
  150. package/_tools.js +396 -396
  151. package/builder/vite/index.ts +4 -4
  152. package/{cfg/build_steps → builder}/vite/loaders/html.ts +11 -11
  153. package/{cfg/build_steps → builder}/vite/loaders/index.ts +8 -8
  154. package/{cfg/build_steps → builder}/vite/loaders/loader.type.ts +27 -27
  155. package/{cfg/build_steps → builder}/vite/loaders/scss.ts +32 -32
  156. package/{cfg/build_steps → builder}/vite/loaders/ts.ts +309 -312
  157. package/builder/vite/rws.vite.config.ts +56 -49
  158. package/{cfg/build_steps → builder}/vite/rws_scss_plugin.ts +61 -60
  159. package/{cfg/build_steps → builder}/vite/scss/_compiler.ts +95 -94
  160. package/{cfg/build_steps → builder}/vite/scss/_fonts.ts +80 -80
  161. package/{cfg/build_steps → builder}/vite/scss/_fs.ts +83 -81
  162. package/{cfg/build_steps → builder}/vite/scss/_import.ts +191 -184
  163. package/builder/webpack/after/copy.js +78 -78
  164. package/builder/webpack/after/sw.js +13 -13
  165. package/builder/webpack/index.js +10 -10
  166. package/builder/webpack/loaders/rws_fast_html_loader.js +17 -17
  167. package/builder/webpack/loaders/rws_fast_scss_loader.js +43 -43
  168. package/builder/webpack/loaders/rws_fast_ts_loader.js +96 -96
  169. package/builder/webpack/loaders/ts/html_error.js +19 -19
  170. package/builder/webpack/rws.webpack.config.js +169 -169
  171. package/builder/webpack/rws_scss_plugin.js +62 -62
  172. package/builder/webpack/rws_webpack_plugin.js +137 -137
  173. package/builder/webpack/scss/_compiler.js +97 -97
  174. package/builder/webpack/scss/_fonts.js +80 -80
  175. package/builder/webpack/scss/_fs.js +84 -84
  176. package/builder/webpack/scss/_import.js +186 -186
  177. package/cfg/_default.cfg.js +26 -26
  178. package/cfg/_storage.d.ts +22 -22
  179. package/cfg/_storage.js +42 -42
  180. package/cfg/build_steps/vite/_build_config.ts +105 -105
  181. package/cfg/build_steps/vite/_env_defines.ts +17 -17
  182. package/cfg/build_steps/vite/_loaders.ts +187 -184
  183. package/cfg/build_steps/vite/index.ts +2 -2
  184. package/cfg/build_steps/vite/types.ts +7 -7
  185. package/cfg/build_steps/webpack/_actions.js +85 -85
  186. package/cfg/build_steps/webpack/_aliases.js +9 -9
  187. package/cfg/build_steps/webpack/_build_config.js +76 -76
  188. package/cfg/build_steps/webpack/_cache.js +98 -98
  189. package/cfg/build_steps/webpack/_component_handling.js +60 -60
  190. package/cfg/build_steps/webpack/_dev_servers.js +14 -14
  191. package/cfg/build_steps/webpack/_env_defines.js +18 -18
  192. package/cfg/build_steps/webpack/_info.js +18 -18
  193. package/cfg/build_steps/webpack/_loaders.js +223 -223
  194. package/cfg/build_steps/webpack/_plugins.js +102 -102
  195. package/cfg/build_steps/webpack/_production.js +37 -37
  196. package/cfg/build_steps/webpack/_rws_externals.js +68 -68
  197. package/cfg/build_steps/webpack/_timing.js +52 -52
  198. package/cfg/build_steps/webpack/_webpack_config.js +58 -58
  199. package/cfg/tsconfigSetup.js +150 -150
  200. package/console.js +86 -86
  201. package/docs/assets/26e93147f10415a0ed4a.svg +6 -6
  202. package/docs/assets/75c9471662e97ee24f29.svg +7 -7
  203. package/docs/assets/db90e4df2373980c497d.svg +9 -9
  204. package/docs/assets/highlight.css +127 -127
  205. package/docs/assets/main.js +59 -59
  206. package/docs/assets/style.css +1414 -1414
  207. package/docs/classes/ApiServiceInstance.html +25 -25
  208. package/docs/classes/ConfigServiceInstance.html +21 -21
  209. package/docs/classes/DOMServiceInstance.html +17 -17
  210. package/docs/classes/NotifyServiceInstance.html +15 -15
  211. package/docs/classes/RWSClientInstance.html +45 -45
  212. package/docs/classes/RWSPlugin.html +13 -13
  213. package/docs/classes/RWSService.html +10 -10
  214. package/docs/classes/RWSViewComponent.html +1022 -1022
  215. package/docs/classes/ServiceWorkerServiceInstance.html +13 -13
  216. package/docs/classes/UtilsServiceInstance.html +13 -13
  217. package/docs/functions/attr.html +5 -5
  218. package/docs/functions/observable.html +3 -3
  219. package/docs/index.html +179 -179
  220. package/docs/interfaces/HTMLTag.html +3 -3
  221. package/docs/interfaces/IRWSConfig.html +20 -20
  222. package/docs/interfaces/IRWSDecoratorOptions.html +5 -5
  223. package/docs/interfaces/IRWSHttpRoute.html +2 -2
  224. package/docs/interfaces/IRWSPrefixedHTTProutes.html +2 -2
  225. package/docs/interfaces/IRWSUser.html +5 -5
  226. package/docs/modules.html +45 -45
  227. package/foundation/index.js +1 -1
  228. package/foundation/rws-foundation.d.ts +7 -7
  229. package/foundation/rws-foundation.js +7 -7
  230. package/package.json +108 -108
  231. package/service_worker/src/_service_worker.ts +83 -83
  232. package/service_worker/tsconfig.json +20 -20
  233. package/service_worker/webpack.config.js +66 -66
  234. package/src/client/components.ts +67 -67
  235. package/src/client/config.ts +165 -165
  236. package/src/client/services.ts +27 -27
  237. package/src/client.ts +212 -212
  238. package/src/components/_attrs/_default_observable_accessor.ts +8 -8
  239. package/src/components/_attrs/_extended_accessor.ts +40 -40
  240. package/src/components/_attrs/_external_handler.ts +7 -7
  241. package/src/components/_attrs/_external_observable_accessor.ts +8 -8
  242. package/src/components/_attrs/angular-attr.ts +34 -34
  243. package/src/components/_attrs/external-attr.ts +59 -59
  244. package/src/components/_attrs/external-observable.ts +52 -52
  245. package/src/components/_attrs/sanitize-html.ts +80 -80
  246. package/src/components/_component.ts +245 -245
  247. package/src/components/_container.ts +15 -15
  248. package/src/components/_decorator.ts +122 -122
  249. package/src/components/_decorators/RWSFillBuild.ts +54 -54
  250. package/src/components/_decorators/RWSInject.ts +49 -49
  251. package/src/components/_decorators/RWSService.ts +11 -11
  252. package/src/components/_decorators/_di.ts +15 -15
  253. package/src/components/_definitions.ts +64 -64
  254. package/src/components/_event_handling.ts +40 -40
  255. package/src/components/index.ts +14 -14
  256. package/src/components/loader/component.ts +12 -12
  257. package/src/components/loader/styles/layout.scss +13 -13
  258. package/src/components/progress/component.ts +54 -54
  259. package/src/components/progress/styles/layout.scss +90 -90
  260. package/src/components/progress/template.html +24 -24
  261. package/src/components/uploader/component.ts +83 -83
  262. package/src/components/uploader/styles/layout.scss +130 -130
  263. package/src/components/uploader/template.html +16 -16
  264. package/src/index.ts +93 -93
  265. package/src/plugins/_builder.js +31 -31
  266. package/src/plugins/_plugin.ts +63 -63
  267. package/src/services/ApiService.ts +242 -242
  268. package/src/services/ConfigService.ts +134 -134
  269. package/src/services/DOMService.ts +100 -100
  270. package/src/services/NotifyService.ts +47 -47
  271. package/src/services/ServiceWorkerService.ts +60 -60
  272. package/src/services/UtilsService.ts +60 -60
  273. package/src/services/_service.ts +64 -64
  274. package/src/styles/_darkreader.scss +30 -30
  275. package/src/styles/_grid.scss +75 -75
  276. package/src/styles/_misc.scss +8 -8
  277. package/src/styles/_scrollbars.scss +40 -40
  278. package/src/styles/includes.scss +4 -4
  279. package/src/types/IRWSConfig.ts +30 -30
  280. package/src/types/IRWSPlugin.ts +18 -18
  281. package/src/types/IRWSUser.ts +6 -6
  282. package/src/types/IRWSViewComponent.ts +36 -36
  283. package/src/types/RWSNotify.ts +6 -6
  284. package/src/types/RWSWindow.ts +41 -41
  285. package/tsconfig.json +30 -30
  286. package/typedoc.json +13 -13
  287. package/types/declarations.d.ts +8 -8
  288. package/types/docs-typings.d.ts +15 -15
@@ -1,1414 +1,1414 @@
1
- let changeCouplingMapForDateRange = {}
2
-
3
- function clickDateRangePickerCancel() {
4
- console.log("reset date range picker")
5
- if (includeGitMetrics) {
6
- initDateRangeUI()
7
- dateRangePickerFrom = commit_first_date
8
- dateRangePickerTo = commit_last_date
9
- initGitMetricsForDateRange()
10
- showToastDateRangeUpdate()
11
- }
12
- }
13
-
14
- function initGitMetricsForDateRange() {
15
- gitMetricsIndexFrom = commit_dates.indexOf( dateRangePickerFrom );
16
- gitMetricsIndexTo = commit_dates.lastIndexOf( dateRangePickerTo );
17
- // console.log("found first index: " + gitMetricsIndexFrom + " for `from` " + dateRangePickerFrom)
18
- // console.log("found last index: " + gitMetricsIndexTo + " for `to` " + dateRangePickerTo)
19
- changeCouplingMapForDateRange = calculateCouplingForDateRange()
20
- // console.log(changeCouplingMapForDateRange)
21
- // console.log(changeCouplingMapForDateRange)
22
- addGitMetricToFileNodes()
23
- }
24
-
25
- function nodeNamesHaveChangeCoupling(sourceName, targetName) {
26
- for (const [key, value] of Object.entries(changeCouplingMapForDateRange)) {
27
- if (sourceName.includes( key ) ) {
28
- for (const k of changeCouplingMapForDateRange[key]) {
29
- if (targetName.includes(k)) {
30
- return true
31
- }
32
- }
33
- }
34
- }
35
-
36
- return false
37
- }
38
-
39
- function calculateCouplingForDateRange() {
40
- let totalChangeCouplingDict = {}
41
-
42
- for (let i = gitMetricsIndexFrom; i < gitMetricsIndexTo; i++) {
43
- let couplingLinks = commit_metrics[i].links
44
-
45
- if (couplingLinks.length > 0) {
46
- for (nextChangeCouplingDict of couplingLinks) {
47
-
48
- // source -> target
49
- if ( !(nextChangeCouplingDict.source in totalChangeCouplingDict) ) {
50
- totalChangeCouplingDict[nextChangeCouplingDict.source] = new Set();
51
- totalChangeCouplingDict[nextChangeCouplingDict.source].add(nextChangeCouplingDict.target)
52
- } else {
53
- totalChangeCouplingDict[nextChangeCouplingDict.source].add(nextChangeCouplingDict.target)
54
- }
55
-
56
- // target -> source
57
- if ( !(nextChangeCouplingDict.target in totalChangeCouplingDict) ) {
58
- totalChangeCouplingDict[nextChangeCouplingDict.target] = new Set();
59
- totalChangeCouplingDict[nextChangeCouplingDict.target].add(nextChangeCouplingDict.source)
60
- } else {
61
- totalChangeCouplingDict[nextChangeCouplingDict.target].add(nextChangeCouplingDict.source)
62
- }
63
- }
64
- }
65
- }
66
- return totalChangeCouplingDict
67
- }
68
-
69
- function calculateFileChurnForDateRange() {
70
- let totalFileChurnDict = {}
71
-
72
- for (let i = gitMetricsIndexFrom; i < gitMetricsIndexTo; i++) {
73
- let nextChurnDict = commit_metrics[i].churn
74
- totalFileChurnDict = mergeDicts(totalFileChurnDict, nextChurnDict)
75
- }
76
-
77
- return totalFileChurnDict
78
- }
79
-
80
- function calculateSlocForDateRange() {
81
- let totalSlocDict = {}
82
-
83
- for (let i = gitMetricsIndexFrom; i < gitMetricsIndexTo; i++) {
84
- let nextSlocDict = commit_metrics[i].sloc
85
- totalSlocDict = mergeDictsToMostCurrentValues(totalSlocDict, nextSlocDict)
86
- }
87
-
88
- return totalSlocDict
89
- }
90
-
91
- function calculateWhiteSpaceComplexityForDateRange() {
92
- let totalWhiteSpaceComplexityDict = {}
93
-
94
- for (let i = gitMetricsIndexFrom; i < gitMetricsIndexTo; i++) {
95
- let nextWhiteSpaceComplexityDict = commit_metrics[i].ws_complexity
96
- totalWhiteSpaceComplexityDict = mergeDictsToMostCurrentValues(totalWhiteSpaceComplexityDict, nextWhiteSpaceComplexityDict)
97
- }
98
-
99
- return totalWhiteSpaceComplexityDict
100
- }
101
-
102
- function calculateAuthorsForDateRange() {
103
- let totalFileAuthorsDict = {}
104
-
105
- for (let i = gitMetricsIndexFrom; i < gitMetricsIndexTo; i++) {
106
- let nextFileAuthorsDict = commit_metrics[i].files_author_map
107
- totalFileAuthorsDict = mergeDicts(totalFileAuthorsDict, nextFileAuthorsDict)
108
- }
109
- return totalFileAuthorsDict
110
- }
111
-
112
- function addGitMetricToFileNodes() {
113
- if (currentGraphType.includes('file_result_dependency_graph')) {
114
- let fileChurnMap = calculateFileChurnForDateRange()
115
- let whiteSpaceComplexityMap = calculateWhiteSpaceComplexityForDateRange()
116
- let slocMap = calculateSlocForDateRange()
117
- let authorsMap = calculateAuthorsForDateRange()
118
-
119
- // console.log(authorsMap)
120
- // console.log(whiteSpaceComplexityMap)
121
- // console.log("fileResultPrefix: " + fileResultPrefix)
122
-
123
- currentGraph.nodes.forEach(function(node, i) {
124
-
125
- // housekeeping git code churn
126
- delete node['metric_git_code_churn']
127
- if (node.hasOwnProperty('metrics')) {
128
- delete node.metrics['metric_git_code_churn']
129
- }
130
-
131
- // housekeeping git ws complexity
132
- delete node['metric_git_ws_complexity']
133
- if (node.hasOwnProperty('metrics')) {
134
- delete node.metrics['metric_git_ws_complexity']
135
- }
136
-
137
- // housekeeping git number of file authors
138
- delete node['metric_git_number_authors']
139
- if (node.hasOwnProperty('metrics')) {
140
- delete node.metrics['metric_git_number_authors']
141
- }
142
- delete node['metric_git_main_contrib']
143
- if (node.hasOwnProperty('metrics')) {
144
- delete node.metrics['metric_git_main_contrib']
145
- }
146
-
147
- // housekeeping file contributors
148
- delete node['metric_git_contributors']
149
- if (node.hasOwnProperty('metrics')) {
150
- delete node.metrics['metric_git_contributors']
151
- }
152
-
153
- // housekeeping git sloc
154
- delete node['metric_git_sloc']
155
- if (node.hasOwnProperty('metrics')) {
156
- delete node.metrics['metric_git_sloc']
157
- }
158
-
159
- if (!node.hasOwnProperty('metrics')) {
160
- node.metrics = {}
161
- }
162
-
163
- let nodeFileName = node.id.split("/").pop();
164
-
165
- let nodeSearchPath = ""
166
- if (fileResultPrefix === "") {
167
- nodeSearchPath = node.id
168
- } else {
169
- nodeSearchPath = fileResultPrefix + "/" + node.id
170
- }
171
-
172
- // add git code churn
173
- for (const [key, value] of Object.entries(fileChurnMap)) {
174
- if (nodeSearchPath.includes(key)) {
175
- node['metric_git_code_churn'] = value
176
- node.metrics['metric_git_code_churn'] = value
177
- }
178
- }
179
-
180
- // add git whitespace complexity
181
- for (const [key, value] of Object.entries(whiteSpaceComplexityMap)) {
182
- if (nodeSearchPath.includes(key)) {
183
- node['metric_git_ws_complexity'] = value
184
- node.metrics['metric_git_ws_complexity'] = value
185
- }
186
- }
187
-
188
- // add git sloc
189
- for (const [key, value] of Object.entries(slocMap)) {
190
- if (nodeSearchPath.includes(key)) {
191
- node['metric_git_sloc'] = value
192
- node.metrics['metric_git_sloc'] = value
193
- }
194
- }
195
-
196
- // add git number authors
197
- for (const [key, value] of Object.entries(authorsMap)) {
198
- if (nodeSearchPath.includes(key)) {
199
- node['metric_git_contributors'] = value
200
- node.metrics['metric_git_contributors'] = value
201
- }
202
- }
203
-
204
- // add all git contributors to file
205
- for (const [key, value] of Object.entries(authorsMap)) {
206
- if (nodeSearchPath.includes(key)) {
207
- node['metric_git_contributors'] = Object.keys(value)
208
- node.metrics['metric_git_contributors'] = Object.keys(value)
209
- node['metric_git_number_authors'] = Object.keys(value).length
210
- node.metrics['metric_git_number_authors'] = Object.keys(value).length
211
- }
212
- }
213
-
214
- });
215
- }
216
- }
217
-
218
- function mainContributor(obj={}, asc=true) {
219
- let biggestChurn = 0
220
- let authorBiggestChurn = ''
221
-
222
- for (let key in obj) {
223
- if (obj[key] > biggestChurn) {
224
- biggestChurn = obj[key]
225
- authorBiggestChurn = key
226
- }
227
- }
228
-
229
- return authorBiggestChurn
230
- }
231
-
232
- // daterangepicker for git date range
233
- function initDateRangeUI() {
234
- $('input[name="daterange"]').daterangepicker({
235
- "startDate": commit_first_date,
236
- "endDate": commit_last_date,
237
- "minDate": commit_first_date,
238
- "maxDate": commit_last_date,
239
-
240
- // ranges: {
241
- // 'Last 3 days': [moment().subtract(2, 'days'), moment()],
242
- // 'Last 10 days': [moment().subtract(9, 'days'), moment()],
243
- // 'Last 30 days': [moment().subtract(29, 'days'), moment()],
244
- // 'Last 60 days': [moment().subtract(59, 'days'), moment()],
245
- // 'This month': [moment().startOf('month'), moment().endOf('month')],
246
- // 'Last month': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')]
247
- // },
248
-
249
- isInvalidDate: function(date) {
250
- if ( commit_dates.includes(date.format('DD/MM/YYYY')) ) {
251
- return false
252
- } else {
253
- return true
254
- }
255
- },
256
-
257
- "locale": {
258
- "format": "DD/MM/YYYY",
259
- "separator": " - ",
260
- "applyLabel": "Apply",
261
- "cancelLabel": "Cancel",
262
- "fromLabel": "From",
263
- "toLabel": "To",
264
- "customRangeLabel": "Custom",
265
- "weekLabel": "W",
266
- "daysOfWeek": [
267
- "Su",
268
- "Mo",
269
- "Tu",
270
- "We",
271
- "Th",
272
- "Fr",
273
- "Sa"
274
- ],
275
- "monthNames": [
276
- "January",
277
- "February",
278
- "March",
279
- "April",
280
- "May",
281
- "June",
282
- "July",
283
- "August",
284
- "September",
285
- "October",
286
- "November",
287
- "December"
288
- ],
289
- "firstDay": 1
290
- },
291
-
292
- opens: 'left'
293
- }, function(start, end, label) {
294
- console.log("A new date selection was made: " + start.format('DD/MM/YYYY') + ' to ' + end.format('DD/MM/YYYY'));
295
- dateRangePickerFrom = start.format('DD/MM/YYYY')
296
- dateRangePickerTo = end.format('DD/MM/YYYY')
297
-
298
- // console.log(dateRangePickerFrom)
299
- // console.log(dateRangePickerTo)
300
-
301
- initGitMetricsForDateRange()
302
- showToastDateRangeUpdate()
303
- })
304
- }
305
-
306
- function showToastDateRangeUpdate() {
307
- const toastLiveExample = document.getElementById('toastDateRangeUpdated')
308
- const toast = new bootstrap.Toast(toastLiveExample)
309
- toast.show()
310
- }
311
-
312
- // Copyright 2021 Observable, Inc.
313
- // Released under the ISC license.
314
- // https://observablehq.com/@d3/multi-line-chart
315
- function LineChart(data, {
316
- x = ([x]) => x, // given d in data, returns the (temporal) x-value
317
- y = ([, y]) => y, // given d in data, returns the (quantitative) y-value
318
- z = () => 1, // given d in data, returns the (categorical) z-value
319
- title, // given d in data, returns the title text
320
- defined, // for gaps in data
321
- curve = d3.curveLinear, // method of interpolation between points
322
- marginTop = 20, // top margin, in pixels
323
- marginRight = 30, // right margin, in pixels
324
- marginBottom = 30, // bottom margin, in pixels
325
- marginLeft = 40, // left margin, in pixels
326
- width = 640, // outer width, in pixels
327
- height = 400, // outer height, in pixels
328
- xType = d3.scaleUtc, // type of x-scale
329
- xDomain, // [xmin, xmax]
330
- xRange = [marginLeft, width - marginRight], // [left, right]
331
- yType = d3.scaleLinear, // type of y-scale
332
- yDomain, // [ymin, ymax]
333
- yRange = [height - marginBottom, marginTop], // [bottom, top]
334
- yFormat, // a format specifier string for the y-axis
335
- yLabel, // a label for the y-axis
336
- zDomain, // array of z-values
337
- color = "currentColor", // stroke color of line, as a constant or a function of *z*
338
- strokeLinecap, // stroke line cap of line
339
- strokeLinejoin, // stroke line join of line
340
- strokeWidth = 1.0, // stroke width of line
341
- strokeOpacity, // stroke opacity of line
342
- mixBlendMode = "multiply", // blend mode of lines
343
- voronoi, // show a Voronoi overlay? (for debugging)
344
- id
345
- } = {}) {
346
- // Compute values.
347
-
348
- let textColor = "#FFF"
349
- if (!darkMode) { textColor = "#333333"}
350
-
351
- const X = d3.map(data, x);
352
- const Y = d3.map(data, y);
353
- const Z = d3.map(data, z);
354
- const O = d3.map(data, d => d);
355
- if (defined === undefined) defined = (d, i) => !isNaN(X[i]) && !isNaN(Y[i]) ;
356
- const D = d3.map(data, defined);
357
-
358
- // Compute default domains, and unique the z-domain.
359
- if (xDomain === undefined) xDomain = d3.extent(X);
360
- if (yDomain === undefined) yDomain = [0, d3.max(Y, d => typeof d === "string" ? +d : d)];
361
- if (zDomain === undefined) zDomain = Z;
362
- zDomain = new d3.InternSet(zDomain);
363
-
364
- // Omit any data not present in the z-domain.
365
- const I = d3.range(X.length).filter(i => zDomain.has(Z[i]));
366
-
367
- // Construct scales and axes.
368
- const xScale = xType(xDomain, xRange);
369
- const yScale = yType(yDomain, yRange);
370
- const xAxis = d3.axisBottom(xScale).ticks(width / 80).tickSizeOuter(0);
371
- const yAxis = d3.axisLeft(yScale).ticks(height / 60, yFormat);
372
-
373
- // Compute titles.
374
- const T = title === undefined ? Z : title === null ? null : d3.map(data, title);
375
-
376
- // Construct a line generator.
377
- const line = d3.line()
378
- .defined(i => D[i])
379
- .curve(curve)
380
- .x(i => xScale(X[i]))
381
- .y(i => yScale(Y[i]));
382
-
383
- const svg = d3.create("svg")
384
- .attr("id", id)
385
- .attr("width", width)
386
- .attr("height", height)
387
- .attr("viewBox", [0, 0, width, height])
388
- .attr("style", "max-width: 100%; height: auto; height: intrinsic;")
389
- .style("-webkit-tap-highlight-color", "transparent")
390
- .on("pointerenter", pointerentered)
391
- .on("pointermove", pointermoved)
392
- .on("pointerleave", pointerleft)
393
- .on("touchstart", event => event.preventDefault());
394
-
395
- // An optional Voronoi display (for fun).
396
- if (voronoi) svg.append("path")
397
- .attr("fill", "none")
398
- .attr("stroke", "#ccc")
399
- .attr("d", d3.Delaunay
400
- .from(I, i => xScale(X[i]), i => yScale(Y[i]))
401
- .voronoi([0, 0, width, height])
402
- .render());
403
-
404
- svg.append("g")
405
- .attr("transform", `translate(0,${height - marginBottom})`)
406
- .call(xAxis);
407
-
408
- svg.append("g")
409
- .attr("transform", `translate(${marginLeft},0)`)
410
- .call(yAxis)
411
- .call(g => g.select(".domain").remove())
412
- .call(voronoi ? () => {} : g => g.selectAll(".tick line").clone()
413
- .attr("x2", width - marginLeft - marginRight)
414
- .attr("stroke-opacity", 0.1))
415
- .call(g => g.append("text")
416
- .attr("x", -marginLeft)
417
- .attr("y", 10)
418
- .attr("fill", "currentColor")
419
- .attr("text-anchor", "start")
420
- .text(yLabel));
421
-
422
- const path = svg.append("g")
423
- .attr("fill", "none")
424
- .attr("stroke", typeof color === "string" ? color : null)
425
- .attr("stroke-linecap", strokeLinecap)
426
- .attr("stroke-linejoin", strokeLinejoin)
427
- .attr("stroke-width", strokeWidth)
428
- .attr("stroke-opacity", strokeOpacity)
429
- .selectAll("path")
430
- .data(d3.group(I, i => Z[i]))
431
- .join("path")
432
- //.style("mix-blend-mode", mixBlendMode)
433
- .attr("stroke", typeof color === "function" ? ([z]) => color(z) : null)
434
- .attr("d", ([, I]) => line(I));
435
-
436
- const dot = svg.append("g")
437
- .attr("display", "none")
438
- .attr("fill", "red");
439
-
440
- dot.append("circle")
441
- .attr("r", 2.5);
442
-
443
- dot.append("text")
444
- .attr("font-family", "sans-serif")
445
- .attr("font-size", 10)
446
- .attr("text-anchor", "middle")
447
- .attr("fill", textColor)
448
- .attr("y", -8);
449
-
450
- function pointermoved(event) {
451
- let strokeColor = "#323232"
452
- if (!darkMode) { strokeColor = "#828282"}
453
-
454
- const [xm, ym] = d3.pointer(event);
455
- const i = d3.least(I, i => Math.hypot(xScale(X[i]) - xm, yScale(Y[i]) - ym)); // closest point
456
- path.style("stroke", ([z]) => Z[i] === z ? null : strokeColor).filter(([z]) => Z[i] === z).raise();
457
- dot.attr("transform", `translate(${xScale(X[i])},${yScale(Y[i])})`);
458
- if (T) dot.select("text").text(T[i]);
459
- svg.property("value", O[i]).dispatch("input", {bubbles: true});
460
- }
461
-
462
- function pointerentered() {
463
- path.style("mix-blend-mode", null).style("stroke", "lightyellow");
464
- dot.attr("display", null);
465
- }
466
-
467
- function pointerleft() {
468
- //path.style("mix-blend-mode", mixBlendMode).style("stroke", null);
469
- path.style("stroke", "lightsteelblue")
470
- dot.attr("display", "none");
471
- svg.node().value = null;
472
- svg.dispatch("input", {bubbles: true});
473
- }
474
-
475
- return Object.assign(svg.node(), {value: null});
476
- }
477
-
478
- function generateTimeSeriesChart() {
479
-
480
- let timeSeriesComplexityTotal = {}
481
- let timeSeriesSlocTotal = {}
482
- let timeSeriesChurnTotal = {}
483
- let timeSeriesComplexity = []
484
- let timeSeriesSloc = []
485
- let timeSeriesChurn = []
486
-
487
- for (let i = gitMetricsIndexFrom; i < gitMetricsIndexTo; i++) {
488
-
489
- // prepare complexity data for chart
490
- for (const [key, value] of Object.entries(commit_metrics[i].ws_complexity)) {
491
-
492
- let filter = true
493
- if (Object.keys(selectedNodesMap).length > 0) {
494
- for (const [selectedNode, v] of Object.entries(selectedNodesMap)) {
495
- if (selectedNode.includes(key.toLowerCase())) {
496
- filter = false
497
- }
498
- }
499
- } else {
500
- filter = false
501
- }
502
-
503
- if (filter == true) {
504
- continue
505
- }
506
-
507
- for (const [file, complexity] of Object.entries(timeSeriesComplexityTotal)) {
508
- if (file !== key) {
509
- timeSeriesComplexity.push(
510
- {
511
- 'filepath' : file,
512
- 'wscomplexity' : complexity,
513
- 'date': commit_metrics[i].exact_date.replace(/_/g, "-")
514
- }
515
- )
516
- }
517
- }
518
-
519
- timeSeriesComplexityTotal[key] = value
520
-
521
- let timeSeriesComplexityEntry = {
522
- 'filepath' : key,
523
- 'wscomplexity' : value,
524
- 'date': commit_metrics[i].exact_date.replace(/_/g, "-") // TODO ...
525
- }
526
- timeSeriesComplexity.push(timeSeriesComplexityEntry)
527
- }
528
-
529
- // prepare sloc data for chart
530
- for (const [key, value] of Object.entries(commit_metrics[i].sloc)) {
531
-
532
- let filter = true
533
- if (Object.keys(selectedNodesMap).length > 0) {
534
- for (const [selectedNode, v] of Object.entries(selectedNodesMap)) {
535
- if (selectedNode.includes(key.toLowerCase())) {
536
- filter = false
537
- }
538
- }
539
- } else {
540
- filter = false
541
- }
542
-
543
- if (filter == true) {
544
- continue
545
- }
546
-
547
- for (const [file, sloc] of Object.entries(timeSeriesSlocTotal)) {
548
- if (file !== key) {
549
- timeSeriesSloc.push(
550
- {
551
- 'filepath' : file,
552
- 'sloc' : sloc,
553
- 'date': commit_metrics[i].exact_date.replace(/_/g, "-")
554
- }
555
- )
556
- }
557
- }
558
-
559
- timeSeriesSlocTotal[key] = value
560
-
561
- let timeSeriesSlocEntry = {
562
- 'filepath' : key,
563
- 'sloc' : value,
564
- 'date': commit_metrics[i].exact_date.replace(/_/g, "-") // TODO ...
565
- }
566
- timeSeriesSloc.push(timeSeriesSlocEntry)
567
- }
568
-
569
-
570
- // prepare churn data for chart
571
- for (const [key, value] of Object.entries(commit_metrics[i].churn)) {
572
- let filter = true
573
- if (Object.keys(selectedNodesMap).length > 0) {
574
- for (const [selectedNode, v] of Object.entries(selectedNodesMap)) {
575
- if (selectedNode.includes(key.toLowerCase())) {
576
- filter = false
577
- }
578
- }
579
- } else {
580
- filter = false
581
- }
582
-
583
- if (filter == true) {
584
- continue
585
- }
586
-
587
- for (const [file, churn] of Object.entries(timeSeriesChurnTotal)) {
588
- if (file !== key) {
589
- timeSeriesChurn.push(
590
- {
591
- 'filepath' : file,
592
- 'churn' : churn,
593
- 'date': commit_metrics[i].exact_date.replace(/_/g, "-")
594
- }
595
- )
596
- }
597
- }
598
-
599
- timeSeriesChurnTotal[key] = value
600
-
601
- let timeSeriesChurnEntry = {
602
- 'filepath' : key,
603
- 'churn' : value,
604
- 'date': commit_metrics[i].exact_date.replace(/_/g, "-") // TODO ...
605
- }
606
- timeSeriesChurn.push(timeSeriesChurnEntry)
607
- }
608
- }
609
-
610
- let complexityChart = LineChart(timeSeriesComplexity, {
611
- x: d => Date.parse(d.date),
612
- y: d => d.wscomplexity,
613
- z: d => d.filepath,
614
- yLabel: "Whitespace Complexity",
615
- width: 1000,
616
- height: 300,
617
- color: "lightsteelblue",
618
- voronoi: false,
619
- id: "timeSeriesComplexityChart"
620
- })
621
-
622
- let slocChart = LineChart(timeSeriesSloc, {
623
- x: d => Date.parse(d.date),
624
- y: d => d.sloc,
625
- z: d => d.filepath,
626
- yLabel: "SLOC",
627
- width: 1000,
628
- height: 300,
629
- color: "lightsteelblue",
630
- voronoi: false,
631
- id: "timeSeriesSlocChart"
632
- })
633
-
634
- let churnChart = LineChart(timeSeriesChurn, {
635
- x: d => Date.parse(d.date),
636
- y: d => d.churn,
637
- z: d => d.filepath,
638
- yLabel: "Code churn",
639
- width: 1000,
640
- height: 300,
641
- color: "lightsteelblue",
642
- voronoi: false,
643
- id: "timeSeriesChurnChart"
644
- })
645
-
646
- document.getElementById("my_dataviz").appendChild(complexityChart);
647
- document.getElementById("time_series_sloc").appendChild(slocChart);
648
- document.getElementById("my_dataviz2").appendChild(churnChart);
649
- }
650
-
651
- function generateChangeCouplingChart() {
652
-
653
- let flows = []
654
- let locations = []
655
- let locationId = 0
656
-
657
- let locationColorMap = {}
658
-
659
- for (let i = gitMetricsIndexFrom; i < gitMetricsIndexTo; i++) {
660
-
661
- // prepare change coupling data for chart
662
- if (commit_metrics[i].links.length > 0) {
663
-
664
- for (link of commit_metrics[i].links) {
665
-
666
- const matchingSourceKey = link.source
667
- const matchingTargetKey = link.target
668
-
669
- let filter = true
670
- if (Object.keys(selectedNodesMap).length > 0) {
671
- for (const [selectedNode, v] of Object.entries(selectedNodesMap)) {
672
- if ( selectedNode.includes(matchingSourceKey.toLowerCase()) || selectedNode.includes(matchingTargetKey.toLowerCase()) ) {
673
- filter = false
674
- }
675
- }
676
- } else {
677
- filter = false
678
- }
679
-
680
- if (filter == true) {
681
- continue
682
- }
683
-
684
- if ( !(locations.find(e => e.name === matchingSourceKey)) ) {
685
-
686
- if ( !(matchingSourceKey in locationColorMap) ) {
687
- let randomColor = "#000000".replace(/0/g,function(){return (~~(Math.random()*16)).toString(16);});
688
- locationColorMap[matchingSourceKey] = randomColor
689
- }
690
-
691
- let location = {
692
- 'id': locationId,
693
- 'name': matchingSourceKey,
694
- 'color': locationColorMap[matchingSourceKey]
695
- }
696
- locations.push(location)
697
-
698
- let flow = {
699
- 'from' : locationId,
700
- 'to' : locationId,
701
- 'quantity': 0
702
-
703
- }
704
- flows.push(flow)
705
- locationId += 1
706
- }
707
-
708
- if ( !(locations.find(e => e.name === matchingTargetKey)) ) {
709
-
710
- if ( !(matchingTargetKey in locationColorMap) ) {
711
-
712
- // find the corresponding node color
713
- for (const [key, value] of Object.entries(nodeColorMap)) {
714
- if (key.includes(matchingTargetKey)) {
715
- locationColorMap[matchingTargetKey] = value
716
- }
717
- }
718
-
719
- // if (matchingTargetKey in nodeColorMap) {
720
- // locationColorMap[matchingTargetKey] = nodeColorMap[matchingTargetKey]
721
- // } else {
722
- // console.log("should not happen")
723
- // locationColorMap[matchingTargetKey] = '#FFFFFF' // "#000000".replace(/0/g,function(){return (~~(Math.random()*16)).toString(16);});
724
- // }
725
- }
726
-
727
- let location = {
728
- 'id': locationId,
729
- 'name': matchingTargetKey,
730
- 'color': locationColorMap[matchingTargetKey]
731
- }
732
-
733
- locations.push(location)
734
- let flow = {
735
- 'from' : locationId,
736
- 'to' : locationId,
737
- 'quantity': 0
738
-
739
- }
740
- flows.push(flow)
741
- locationId += 1
742
- }
743
-
744
- const iSource = locations.findIndex(e => e.name === matchingSourceKey);
745
- const iTarget = locations.findIndex(e => e.name === matchingTargetKey);
746
-
747
- if (iSource > -1 && iTarget > -1) {
748
- let flow = {
749
- 'from' : locations[iSource].id,
750
- 'to' : locations[iTarget].id,
751
- 'quantity': 15
752
- }
753
- flows.push(flow)
754
- }
755
-
756
- }
757
-
758
- }
759
-
760
- }
761
-
762
- for (let n = 0; n < locationId; n++) {
763
- for (let m = 0; m < locationId; m++) {
764
- if ( !(flows.find(e => e.from === n && e.to === m )) ) {
765
- let flow = {
766
- 'from' : n,
767
- 'to' : m,
768
- 'quantity': 0
769
- }
770
- flows.push(flow)
771
- }
772
- }
773
- }
774
-
775
- // Borrowed from this great blog entry:
776
- // https://blog.noser.com/d3-js-chord-diagramm-teil-2-benutzerdefinierte-sortierung-und-kurvenformen/
777
-
778
- var matrix = [];
779
-
780
- //Map list of data to matrix
781
- flows.forEach(function (flow) {
782
-
783
- //Initialize sub-array if not yet exists
784
- if (!matrix[flow.to]) {
785
- matrix[flow.to] = [];
786
- }
787
-
788
- matrix[flow.to][flow.from] = flow.quantity;
789
- });
790
-
791
- /*//////////////////////////////////////////////////////////
792
- /////////////// Initiate Chord Diagram /////////////////////
793
- //////////////////////////////////////////////////////////*/
794
- let size = 900;
795
- let dr = 40; //radial translation for group names
796
- let dx = 20; //horizontal translation for group names
797
- let margin = { top: 0, right: 50, bottom: 50, left: 50 };
798
- let chordWidth = (size + 200) - margin.left - margin.right;
799
- let chordHeight = size - margin.top - margin.bottom;
800
- let innerRadius = Math.min(chordWidth, chordHeight) * .39;
801
- let outerRadius = innerRadius * 1.08;
802
-
803
- let root = d3.select("#change_coupling_chord_diagram");
804
-
805
- //Generate tooltip already, but keep it invisible for now.
806
- var toolTip = root.append("div")
807
- .classed("tooltip", true)
808
- .style("opacity", 0)
809
- .style("position", "absolute")
810
- .style("text-align", "center")
811
- .style("padding", "6px")
812
- .style("font", "10px sans-serif")
813
- .style("color", "black")
814
- .style("background", "silver")
815
- .style("border", "1px solid gray")
816
- .style("border-radius", "8px")
817
- .style("pointer-events", "none");
818
-
819
- var focusedChordGroupIndex = null;
820
-
821
- /*Initiate the SVG*/
822
- //D3.js v3!
823
- var svg = root.append("svg:svg")
824
- .attr("width", chordWidth + margin.left + margin.right)
825
- .attr("height", chordHeight + margin.top + margin.bottom)
826
- .attr("id", "svg_change_coupling_chord_diagram");
827
-
828
- var container = svg.append("g")
829
- .attr("transform", "translate(" +
830
- (margin.left + chordWidth / 2) + "," +
831
- (margin.top + chordHeight / 2) + ")");
832
-
833
- var chord = customChordLayout()
834
- .padding(0.04)
835
- .sortSubgroups(d3.descending) /*sort the chords inside an arc from high to low*/
836
- .sortChords(d3.ascending) /*which chord should be shown on top when chords cross. Now the largest chord is at the top*/
837
- .matrix(matrix);
838
-
839
- /*//////////////////////////////////////////////////////////
840
- ////////////////// Draw outer Arcs /////////////////////////
841
- //////////////////////////////////////////////////////////*/
842
- var arc = d3.arc()
843
- .innerRadius(innerRadius)
844
- .outerRadius(outerRadius);
845
-
846
- var g = container.selectAll("g.group")
847
- .data(chord.groups)
848
- .enter()
849
- .append("svg:g")
850
- .attr("class", function (d) { return "group group-" + locations[d.index].id; });
851
-
852
- g.append("svg:path")
853
- .attr("d", arc)
854
- .style("fill", function (d) {
855
- return locations[d.index].color;
856
- })
857
- .style("stroke", function (d) {
858
- return d3.rgb(locations[d.index].color).brighter();
859
- })
860
- .on("click", function (event, d) { highlightChords(d.index) }) // .on("click", function (d) { highlightChords(d.index) })
861
- .on("mouseover", function(event, i) {
862
- showArcToolTip(event, i);
863
- })
864
- .on("mouseout", function(d) { hideToolTip() });
865
-
866
- /*//////////////////////////////////////////////////////////
867
- //////////////// Initiate inner chords /////////////////////
868
- //////////////////////////////////////////////////////////*/
869
- var chords = container.selectAll("path.chord")
870
- .data(chord.chords)
871
- .enter()
872
- .append("svg:path")
873
- .attr("class", function (d) {
874
- return "chord chord-source-" + d.source.index + " chord-target-" + d.target.index;
875
- })
876
- .attr("d", customChordPathGenerator().radius(innerRadius))
877
- //Change the fill to reference the unique gradient ID
878
- //of the source-target combination
879
- .style("fill", function (d) {
880
- return "url(#chordGradient-" + d.source.index + "-" + d.target.index + ")";
881
- })
882
- .style("stroke", function (d) {
883
- return "url(#chordGradient-" + d.source.index + "-" + d.target.index + ")";
884
- })
885
- .style("fill-opacity", "0.7")
886
- .on("mouseover", function(event, i) {
887
- if (focusedChordGroupIndex === null ||
888
- i.source.index === focusedChordGroupIndex ||
889
- i.target.index === focusedChordGroupIndex) {
890
- if (focusedChordGroupIndex === null) {
891
- d3.selectAll(".chord")
892
- .style("fill-opacity", 0.2)
893
- .style("stroke-opacity", 0.2);
894
- d3.select(this).style("fill-opacity", 1);
895
- }
896
- else {
897
- d3.selectAll(".chord.chord-source-" + focusedChordGroupIndex + ", " +
898
- ".chord.chord-target-" + focusedChordGroupIndex)
899
- .style("fill-opacity", 0.2)
900
- .style("stroke-opacity", 0.2);
901
- d3.select(this).style("fill-opacity", 1);
902
- }
903
-
904
- showChordToolTip(event, i);
905
- }
906
- })
907
- .on("mouseout", function(d) {
908
- if (focusedChordGroupIndex === null) {
909
- d3.selectAll(".chord")
910
- .style("fill-opacity", 0.7)
911
- .style("stroke-opacity", 1);
912
- }
913
- else {
914
- d3.selectAll(".chord.chord-source-" + focusedChordGroupIndex + ", " +
915
- ".chord.chord-target-" + focusedChordGroupIndex)
916
- .style("fill-opacity", 0.7)
917
- .style("stroke-opacity", 1);
918
- }
919
-
920
- hideToolTip();
921
- });
922
-
923
- //Cf https://www.visualcinnamon.com/2016/06/orientation-gradient-d3-chord-diagram
924
- //Create a gradient definition for each chord
925
- var grads = svg.append("defs").selectAll("linearGradient")
926
- .data(chord.chords)
927
- .enter().append("linearGradient")
928
- //Create a unique gradient id per chord: e.g. "chordGradient-0-4"
929
- .attr("id", function (d) {
930
- return "chordGradient-" + d.source.index + "-" + d.target.index;
931
- })
932
- //Instead of the object bounding box, use the entire SVG for setting locations
933
- //in pixel locations instead of percentages (which is more typical)
934
- .attr("gradientUnits", "userSpaceOnUse")
935
- //The full mathematical formula to find the x and y locations
936
- .attr("x1", function (d, i) {
937
- return innerRadius * Math.cos((d.source.endAngle - d.source.startAngle) / 2 +
938
- d.source.startAngle - Math.PI / 2);
939
- })
940
- .attr("y1", function (d, i) {
941
- return innerRadius * Math.sin((d.source.endAngle - d.source.startAngle) / 2 +
942
- d.source.startAngle - Math.PI / 2);
943
- })
944
- .attr("x2", function (d, i) {
945
- return innerRadius * Math.cos((d.target.endAngle - d.target.startAngle) / 2 +
946
- d.target.startAngle - Math.PI / 2);
947
- })
948
- .attr("y2", function (d, i) {
949
- return innerRadius * Math.sin((d.target.endAngle - d.target.startAngle) / 2 +
950
- d.target.startAngle - Math.PI / 2);
951
- });
952
-
953
- //Set the starting color (at 0%)
954
- grads.append("stop")
955
- .attr("offset", "0%")
956
- .attr("stop-color", function (d) { return locations[d.source.index].color; });
957
-
958
- //Set the ending color (at 100%)
959
- grads.append("stop")
960
- .attr("offset", "100%")
961
- .attr("stop-color", function (d) { return locations[d.target.index].color; });
962
-
963
-
964
- /*//////////////////////////////////////////////////////////
965
- ////////////////// Initiate Ticks //////////////////////////
966
- //////////////////////////////////////////////////////////*/
967
- var ticks = g.append("svg:g")
968
- .selectAll("g.ticks")
969
- .data(groupTicks)
970
- .enter().append("svg:g")
971
- .attr("transform", function (d) {
972
- return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")"
973
- + "translate(" + outerRadius + 40 + ",0)";
974
- });
975
-
976
- /*Append the tick around the arcs*/
977
- ticks.append("svg:line")
978
- .attr("x1", 1)
979
- .attr("y1", 0)
980
- .attr("x2", 6)
981
- .attr("y2", 0)
982
- .attr("class", "ticks")
983
- .style("stroke", "#FFF")
984
- .style("stroke-width", "1.5px");
985
-
986
- let labelColor = "#FFF"
987
- if (!darkMode) { labelColor = "#333333"}
988
-
989
- /*Add the labels for the ticks*/
990
- ticks.append("svg:text")
991
- .attr("class", "tickLabels")
992
- .attr("x", 12)
993
- .attr("dy", ".35em")
994
- .style("font-size", "10px")
995
- .style("font-family", "sans-serif")
996
- .attr("fill", labelColor)
997
- .attr("transform", function (d) {
998
- return d.angle > Math.PI ? "rotate(180)translate(-25)" : null;
999
- })
1000
- .style("text-anchor", function (d) {
1001
- return d.angle > Math.PI ? "end" : null;
1002
- })
1003
- //.text(function (d) { return d.label; });
1004
-
1005
- /*//////////////////////////////////////////////////////////
1006
- ////////////////// Initiate Names //////////////////////////
1007
- //////////////////////////////////////////////////////////*/
1008
- g.append("svg:text")
1009
- .each(function (d) { d.angle = (d.startAngle + d.endAngle) / 2; })
1010
- .attr("dy", ".35em")
1011
- .attr("class", "titles")
1012
- .style("font-size", "10px")
1013
- .style("font-family", "sans-serif")
1014
- .attr("fill", labelColor)
1015
- .attr("text-anchor", function (d) {
1016
- return d.angle > Math.PI ? "end" : null;
1017
- })
1018
- .attr("transform", function (d) {
1019
- var r = outerRadius + dr;
1020
- var angle = d.angle + ((3 *Math.PI) / 2);
1021
- var x = r * Math.cos(angle);
1022
- var y = r * Math.sin(angle);
1023
-
1024
- if (d.angle > Math.PI) {
1025
- x -= dx;
1026
- }
1027
- else {
1028
- x += dx;
1029
- }
1030
-
1031
- return "translate(" + x + ", " + y + ")";
1032
- })
1033
- .text(function (d, i) {
1034
- if (locations[i].name.includes("/")) {
1035
- return locations[i].name.substring(locations[i].name.lastIndexOf('/') + 1)
1036
- } else {
1037
- return locations[i].name
1038
- }
1039
- });
1040
-
1041
- /*Lines from labels to arcs*/
1042
- /*part in radial direction*/
1043
- this.g.append("line")
1044
- .attr("x1", function (d) {
1045
- return outerRadius * Math.cos(d.angle + ((3 * Math.PI) / 2));
1046
- })
1047
- .attr("y1", function (d) {
1048
- return outerRadius * Math.sin(d.angle + ((3 * Math.PI) / 2));
1049
- })
1050
- .attr("x2", function (d) {
1051
- return (outerRadius + dr) * Math.cos(d.angle + ((3 * Math.PI) / 2));
1052
- })
1053
- .attr("y2", function (d) {
1054
- return (outerRadius + dr) * Math.sin(d.angle + ((3 * Math.PI) / 2));
1055
- })
1056
- .style("stroke", "#FFF")
1057
- .style("stroke-width", "0.5px");
1058
-
1059
- /*horizontal part*/
1060
- this.g.append("line")
1061
- .attr("x1", function (d) {
1062
- return (outerRadius + dr) * Math.cos(d.angle + ((3 * Math.PI) / 2));
1063
- })
1064
- .attr("y1", function (d) {
1065
- return (outerRadius + dr) * Math.sin(d.angle + ((3 * Math.PI) / 2));
1066
- })
1067
- .attr("x2", function (d) {
1068
- var x = (outerRadius + dr) * Math.cos(d.angle + ((3 * Math.PI) / 2));
1069
- if (d.angle > Math.PI) {
1070
- x -= dx - 5;
1071
- }
1072
- else {
1073
- x += dx - 5;
1074
- }
1075
- return x;
1076
- })
1077
- .attr("y2", function (d) {
1078
- return (outerRadius + dr) * Math.sin(d.angle + ((3 * Math.PI) / 2));
1079
- })
1080
- .style("stroke", "#FFF")
1081
- .style("stroke-width", "0.5px");
1082
-
1083
- /*//////////////////////////////////////////////////////////
1084
- ////////////////// Extra Functions /////////////////////////
1085
- //////////////////////////////////////////////////////////*/
1086
-
1087
- /*Returns an array of tick angles and labels, given a group*/
1088
- function groupTicks(d) {
1089
- var anglePerPerson = (d.endAngle - d.startAngle) / d.value;
1090
- return d3.range(0, d.value, 100).map(function (v, i) {
1091
- return {
1092
- angle: v * anglePerPerson + d.startAngle,
1093
- label: i % 5 ? null : v //Each 5th tick has a label
1094
- };
1095
- });
1096
- };
1097
-
1098
- //Hides all chords except the chords connecting to the subgroup /
1099
- //location of the given index.
1100
- function highlightChords(index) {
1101
- //If this subgroup is already highlighted, toggle all chords back on.
1102
- if (focusedChordGroupIndex === index) {
1103
- showAllChords();
1104
- return;
1105
- }
1106
-
1107
- hideAllChords();
1108
-
1109
- //Show only the ones with source or target == index
1110
- d3.selectAll(".chord-source-" + index + ", .chord-target-" + index)
1111
- .transition().duration(500)
1112
- .style("fill-opacity", "0.7")
1113
- .style("stroke-opacity", "1");
1114
-
1115
- focusedChordGroupIndex = index;
1116
- };
1117
-
1118
- function showAllChords() {
1119
- svg.selectAll("path.chord")
1120
- .transition().duration(500)
1121
- .style("fill-opacity", "0.7")
1122
- .style("stroke-opacity", "1");
1123
-
1124
- focusedChordGroupIndex = null;
1125
- };
1126
-
1127
- function hideAllChords() {
1128
- svg.selectAll("path.chord")
1129
- .transition().duration(500)
1130
- .style("fill-opacity", "0")
1131
- .style("stroke-opacity", "0");
1132
- };
1133
-
1134
- function showChordToolTip(event, chord) {
1135
- var prompt = "";
1136
-
1137
- // if (chord.source.index !== chord.target.index) {
1138
- // prompt += chord.source.value + " Kunden gingen von " +
1139
- // locations[chord.target.index].name + " nach " +
1140
- // locations[chord.source.index].name + ".";
1141
- // prompt += "<br>";
1142
- // prompt += chord.target.value + " Kunden gingen von " +
1143
- // locations[chord.source.index].name + " nach " +
1144
- // locations[chord.target.index].name + ".";
1145
- // }
1146
- // else {
1147
- // prompt += chord.source.value + " Kunden blieben in " +
1148
- // locations[chord.source.index].name + ".";
1149
- // }
1150
-
1151
- prompt += locations[chord.target.index].name + "<br>" + "... changed together with ... " + "<br>" + locations[chord.source.index].name + ".";
1152
-
1153
- const[x, y] = d3.pointer(event);
1154
-
1155
- toolTip
1156
- .style("opacity", 1)
1157
- .style("font-size", "10px")
1158
- .html(prompt)
1159
- .style("left", x - toolTip.node().getBoundingClientRect().width / 32 + "px") // .style("left", d3.event.pageX - toolTip.node().getBoundingClientRect().width / 2 + "px")
1160
- .style("top", y + 300 + "px"); // .style("top", (d3.event.pageY - 50) + "px");
1161
- };
1162
-
1163
- function showArcToolTip(event, arc) {
1164
- const[x, y] = d3.pointer(event);
1165
- // console.log(locations)
1166
- // console.log(arc)
1167
-
1168
- var prompt = locations[arc.index].name + "."; //Math.round(arc.value)
1169
-
1170
- toolTip
1171
- .style("opacity", 1)
1172
- .html(prompt)
1173
- .style("left", x + toolTip.node().getBoundingClientRect().width + "px")
1174
- .style("top", y + 300 + "px");
1175
- };
1176
-
1177
- function hideToolTip() {
1178
- toolTip.style("opacity", 0);
1179
- };
1180
-
1181
- ////////////////////////////////////////////////////////////
1182
- //////////// Custom Chord Layout Function //////////////////
1183
- /////// Places the Chords in the visually best order ///////
1184
- ///////////////// to reduce overlap ////////////////////////
1185
- ////////////////////////////////////////////////////////////
1186
- //////// Slightly adjusted by Nadieh Bremer ////////////////
1187
- //////////////// VisualCinnamon.com ////////////////////////
1188
- ////////////////////////////////////////////////////////////
1189
- ////// Original from the d3.layout.chord() function ////////
1190
- ///////////////// from the d3.js library ///////////////////
1191
- //////////////// Created by Mike Bostock ///////////////////
1192
- ////////////////////////////////////////////////////////////
1193
- function customChordLayout() {
1194
- var ε = 1e-6, ε2 = ε * ε, π = Math.PI, τ = 2 * π, τε = τ - ε, halfπ = π / 2, d3_radians = π / 180, d3_degrees = 180 / π;
1195
- var chord = {}, chords, groups, matrix, n, padding = 0, sortGroups, sortSubgroups, sortChords;
1196
- function relayout() {
1197
- var subgroups = {}, groupSums = [], groupIndex = d3.range(n), subgroupIndex = [], k, x, x0, i, j;
1198
- var numSeq;
1199
- chords = [];
1200
- groups = [];
1201
- k = 0, i = -1;
1202
-
1203
- while (++i < n) {
1204
- x = 0, j = -1, numSeq = [];
1205
- while (++j < n) {
1206
- x += matrix[i][j];
1207
- }
1208
- groupSums.push(x);
1209
- //////////////////////////////////////
1210
- ////////////// New part //////////////
1211
- //////////////////////////////////////
1212
- for (var m = 0; m < n; m++) {
1213
- numSeq[m] = (n + (i - 1) - m) % n;
1214
- }
1215
- subgroupIndex.push(numSeq);
1216
- //////////////////////////////////////
1217
- ////////// End new part /////////////
1218
- //////////////////////////////////////
1219
- k += x;
1220
- }//while
1221
-
1222
- k = (τ - padding * n) / k;
1223
- x = 0, i = -1;
1224
- while (++i < n) {
1225
- x0 = x, j = -1;
1226
- while (++j < n) {
1227
- var di = groupIndex[i], dj = subgroupIndex[di][j], v = matrix[di][dj], a0 = x, a1 = x += v * k;
1228
- subgroups[di + "-" + dj] = {
1229
- index: di,
1230
- subindex: dj,
1231
- startAngle: a0,
1232
- endAngle: a1,
1233
- value: v
1234
- };
1235
- }//while
1236
-
1237
- groups[di] = {
1238
- index: di,
1239
- startAngle: x0,
1240
- endAngle: x,
1241
- value: (x - x0) / k
1242
- };
1243
- x += padding;
1244
- }//while
1245
-
1246
- i = -1;
1247
- while (++i < n) {
1248
- j = i - 1;
1249
- while (++j < n) {
1250
- var source = subgroups[i + "-" + j], target = subgroups[j + "-" + i];
1251
- if (source.value || target.value) {
1252
- chords.push(source.value < target.value ? {
1253
- source: target,
1254
- target: source
1255
- } : {
1256
- source: source,
1257
- target: target
1258
- });
1259
- }//if
1260
- }//while
1261
- }//while
1262
- if (sortChords) resort();
1263
- }//function relayout
1264
-
1265
- function resort() {
1266
- chords.sort(function (a, b) {
1267
- return sortChords((a.source.value + a.target.value) / 2, (b.source.value + b.target.value) / 2);
1268
- });
1269
- }
1270
- chord.matrix = function (x) {
1271
- if (!arguments.length) return matrix;
1272
- n = (matrix = x) && matrix.length;
1273
- chords = groups = null;
1274
- return chord;
1275
- };
1276
- chord.padding = function (x) {
1277
- if (!arguments.length) return padding;
1278
- padding = x;
1279
- chords = groups = null;
1280
- return chord;
1281
- };
1282
- chord.sortGroups = function (x) {
1283
- if (!arguments.length) return sortGroups;
1284
- sortGroups = x;
1285
- chords = groups = null;
1286
- return chord;
1287
- };
1288
- chord.sortSubgroups = function (x) {
1289
- if (!arguments.length) return sortSubgroups;
1290
- sortSubgroups = x;
1291
- chords = null;
1292
- return chord;
1293
- };
1294
- chord.sortChords = function (x) {
1295
- if (!arguments.length) return sortChords;
1296
- sortChords = x;
1297
- if (chords) resort();
1298
- return chord;
1299
- };
1300
- chord.chords = function () {
1301
- if (!chords) relayout();
1302
- return chords;
1303
- };
1304
- chord.groups = function () {
1305
- if (!groups) relayout();
1306
- return groups;
1307
- };
1308
- return chord;
1309
- };
1310
-
1311
- ////////////////////////////////////////////////////////////
1312
- //////////// Custom Chord Path Generator ///////////////////
1313
- ///////// Uses cubic bezier curves with quadratic //////////
1314
- /////// spread of control points to minimise overlap ///////
1315
- ////////////////// of adjacent chords. /////////////////////
1316
- ////////////////////////////////////////////////////////////
1317
- /////// Original from the d3.svg.chord() function //////////
1318
- ///////////////// from the d3.js library ///////////////////
1319
- //////////////// Created by Mike Bostock ///////////////////
1320
- ////////////////////////////////////////////////////////////
1321
- function customChordPathGenerator() {
1322
- var source = function(d) { return d.source; };
1323
- var target = function(d) { return d.target; };
1324
- var radius = function(d) { return d.radius; };
1325
- var startAngle = function(d) { return d.startAngle; };
1326
- var endAngle = function(d) { return d.endAngle; };
1327
-
1328
- function chord(d, i) {
1329
- var s = subgroup(this, source, d, i),
1330
- t = subgroup(this, target, d, i);
1331
-
1332
- var path = "M" + s.p0
1333
- + arc(s.r, s.p1, s.a1 - s.a0) + (equals(s, t)
1334
- ? curve(s.r, s.p1, s.a1, s.r, s.p0, s.a0)
1335
- : curve(s.r, s.p1, s.a1, t.r, t.p0, t.a0)
1336
- + arc(t.r, t.p1, t.a1 - t.a0)
1337
- + curve(t.r, t.p1, t.a1, s.r, s.p0, s.a0))
1338
- + "Z";
1339
-
1340
- return path;
1341
- }
1342
-
1343
- function subgroup(self, f, d, i) {
1344
- var subgroup = f.call(self, d, i),
1345
- r = radius.call(self, subgroup, i),
1346
- a0 = startAngle.call(self, subgroup, i) - (Math.PI / 2),
1347
- a1 = endAngle.call(self, subgroup, i) - (Math.PI / 2);
1348
-
1349
- return {
1350
- r: r,
1351
- a0: a0,
1352
- a1: a1,
1353
- p0: [r * Math.cos(a0), r * Math.sin(a0)],
1354
- p1: [r * Math.cos(a1), r * Math.sin(a1)]
1355
- };
1356
- }
1357
-
1358
- function equals(a, b) {
1359
- return a.a0 == b.a0 && a.a1 == b.a1;
1360
- }
1361
-
1362
- function arc(r, p, a) {
1363
- return "A" + r + "," + r + " 0 " + +(a > Math.PI) + ",1 " + p;
1364
- }
1365
-
1366
- function curve(r0, p0, a0, r1, p1, a1) {
1367
- var deltaAngle = Math.abs(mod((a1 - a0 + Math.PI), (2 * Math.PI)) - Math.PI);
1368
- var radialControlPointScale = Math.pow((Math.PI - deltaAngle) / Math.PI, 2) * 0.9;
1369
- var controlPoint1 = [p0[0] * radialControlPointScale, p0[1] * radialControlPointScale];
1370
- var controlPoint2 = [p1[0] * radialControlPointScale, p1[1] * radialControlPointScale];
1371
- var cubicBezierSvg = "C " + controlPoint1[0] + " " + controlPoint1[1] + ", " +
1372
- controlPoint2[0] + " " + controlPoint2[1] + ", " +
1373
- p1[0] + " " + p1[1];
1374
- return cubicBezierSvg;
1375
- }
1376
-
1377
- function mod(a, n) {
1378
- return (a % n + n) % n;
1379
- }
1380
-
1381
- chord.radius = function(v) {
1382
- if (!arguments.length) return radius;
1383
- radius = typeof v === "function" ? v : function() { return v; };
1384
- return chord;
1385
- };
1386
-
1387
- chord.source = function(v) {
1388
- if (!arguments.length) return source;
1389
- source = typeof v === "function" ? v : function() { return v; };
1390
- return chord;
1391
- };
1392
-
1393
- chord.target = function(v) {
1394
- if (!arguments.length) return target;
1395
- target = typeof v === "function" ? v : function() { return v; };
1396
- return chord;
1397
- };
1398
-
1399
- chord.startAngle = function(v) {
1400
- if (!arguments.length) return startAngle;
1401
- startAngle = typeof v === "function" ? v : function() { return v; };
1402
- return chord;
1403
- };
1404
-
1405
- chord.endAngle = function(v) {
1406
- if (!arguments.length) return endAngle;
1407
- endAngle = typeof v === "function" ? v : function() { return v; };
1408
- return chord;
1409
- };
1410
-
1411
- return chord;
1412
- }
1413
-
1
+ let changeCouplingMapForDateRange = {}
2
+
3
+ function clickDateRangePickerCancel() {
4
+ console.log("reset date range picker")
5
+ if (includeGitMetrics) {
6
+ initDateRangeUI()
7
+ dateRangePickerFrom = commit_first_date
8
+ dateRangePickerTo = commit_last_date
9
+ initGitMetricsForDateRange()
10
+ showToastDateRangeUpdate()
11
+ }
12
+ }
13
+
14
+ function initGitMetricsForDateRange() {
15
+ gitMetricsIndexFrom = commit_dates.indexOf( dateRangePickerFrom );
16
+ gitMetricsIndexTo = commit_dates.lastIndexOf( dateRangePickerTo );
17
+ // console.log("found first index: " + gitMetricsIndexFrom + " for `from` " + dateRangePickerFrom)
18
+ // console.log("found last index: " + gitMetricsIndexTo + " for `to` " + dateRangePickerTo)
19
+ changeCouplingMapForDateRange = calculateCouplingForDateRange()
20
+ // console.log(changeCouplingMapForDateRange)
21
+ // console.log(changeCouplingMapForDateRange)
22
+ addGitMetricToFileNodes()
23
+ }
24
+
25
+ function nodeNamesHaveChangeCoupling(sourceName, targetName) {
26
+ for (const [key, value] of Object.entries(changeCouplingMapForDateRange)) {
27
+ if (sourceName.includes( key ) ) {
28
+ for (const k of changeCouplingMapForDateRange[key]) {
29
+ if (targetName.includes(k)) {
30
+ return true
31
+ }
32
+ }
33
+ }
34
+ }
35
+
36
+ return false
37
+ }
38
+
39
+ function calculateCouplingForDateRange() {
40
+ let totalChangeCouplingDict = {}
41
+
42
+ for (let i = gitMetricsIndexFrom; i < gitMetricsIndexTo; i++) {
43
+ let couplingLinks = commit_metrics[i].links
44
+
45
+ if (couplingLinks.length > 0) {
46
+ for (nextChangeCouplingDict of couplingLinks) {
47
+
48
+ // source -> target
49
+ if ( !(nextChangeCouplingDict.source in totalChangeCouplingDict) ) {
50
+ totalChangeCouplingDict[nextChangeCouplingDict.source] = new Set();
51
+ totalChangeCouplingDict[nextChangeCouplingDict.source].add(nextChangeCouplingDict.target)
52
+ } else {
53
+ totalChangeCouplingDict[nextChangeCouplingDict.source].add(nextChangeCouplingDict.target)
54
+ }
55
+
56
+ // target -> source
57
+ if ( !(nextChangeCouplingDict.target in totalChangeCouplingDict) ) {
58
+ totalChangeCouplingDict[nextChangeCouplingDict.target] = new Set();
59
+ totalChangeCouplingDict[nextChangeCouplingDict.target].add(nextChangeCouplingDict.source)
60
+ } else {
61
+ totalChangeCouplingDict[nextChangeCouplingDict.target].add(nextChangeCouplingDict.source)
62
+ }
63
+ }
64
+ }
65
+ }
66
+ return totalChangeCouplingDict
67
+ }
68
+
69
+ function calculateFileChurnForDateRange() {
70
+ let totalFileChurnDict = {}
71
+
72
+ for (let i = gitMetricsIndexFrom; i < gitMetricsIndexTo; i++) {
73
+ let nextChurnDict = commit_metrics[i].churn
74
+ totalFileChurnDict = mergeDicts(totalFileChurnDict, nextChurnDict)
75
+ }
76
+
77
+ return totalFileChurnDict
78
+ }
79
+
80
+ function calculateSlocForDateRange() {
81
+ let totalSlocDict = {}
82
+
83
+ for (let i = gitMetricsIndexFrom; i < gitMetricsIndexTo; i++) {
84
+ let nextSlocDict = commit_metrics[i].sloc
85
+ totalSlocDict = mergeDictsToMostCurrentValues(totalSlocDict, nextSlocDict)
86
+ }
87
+
88
+ return totalSlocDict
89
+ }
90
+
91
+ function calculateWhiteSpaceComplexityForDateRange() {
92
+ let totalWhiteSpaceComplexityDict = {}
93
+
94
+ for (let i = gitMetricsIndexFrom; i < gitMetricsIndexTo; i++) {
95
+ let nextWhiteSpaceComplexityDict = commit_metrics[i].ws_complexity
96
+ totalWhiteSpaceComplexityDict = mergeDictsToMostCurrentValues(totalWhiteSpaceComplexityDict, nextWhiteSpaceComplexityDict)
97
+ }
98
+
99
+ return totalWhiteSpaceComplexityDict
100
+ }
101
+
102
+ function calculateAuthorsForDateRange() {
103
+ let totalFileAuthorsDict = {}
104
+
105
+ for (let i = gitMetricsIndexFrom; i < gitMetricsIndexTo; i++) {
106
+ let nextFileAuthorsDict = commit_metrics[i].files_author_map
107
+ totalFileAuthorsDict = mergeDicts(totalFileAuthorsDict, nextFileAuthorsDict)
108
+ }
109
+ return totalFileAuthorsDict
110
+ }
111
+
112
+ function addGitMetricToFileNodes() {
113
+ if (currentGraphType.includes('file_result_dependency_graph')) {
114
+ let fileChurnMap = calculateFileChurnForDateRange()
115
+ let whiteSpaceComplexityMap = calculateWhiteSpaceComplexityForDateRange()
116
+ let slocMap = calculateSlocForDateRange()
117
+ let authorsMap = calculateAuthorsForDateRange()
118
+
119
+ // console.log(authorsMap)
120
+ // console.log(whiteSpaceComplexityMap)
121
+ // console.log("fileResultPrefix: " + fileResultPrefix)
122
+
123
+ currentGraph.nodes.forEach(function(node, i) {
124
+
125
+ // housekeeping git code churn
126
+ delete node['metric_git_code_churn']
127
+ if (node.hasOwnProperty('metrics')) {
128
+ delete node.metrics['metric_git_code_churn']
129
+ }
130
+
131
+ // housekeeping git ws complexity
132
+ delete node['metric_git_ws_complexity']
133
+ if (node.hasOwnProperty('metrics')) {
134
+ delete node.metrics['metric_git_ws_complexity']
135
+ }
136
+
137
+ // housekeeping git number of file authors
138
+ delete node['metric_git_number_authors']
139
+ if (node.hasOwnProperty('metrics')) {
140
+ delete node.metrics['metric_git_number_authors']
141
+ }
142
+ delete node['metric_git_main_contrib']
143
+ if (node.hasOwnProperty('metrics')) {
144
+ delete node.metrics['metric_git_main_contrib']
145
+ }
146
+
147
+ // housekeeping file contributors
148
+ delete node['metric_git_contributors']
149
+ if (node.hasOwnProperty('metrics')) {
150
+ delete node.metrics['metric_git_contributors']
151
+ }
152
+
153
+ // housekeeping git sloc
154
+ delete node['metric_git_sloc']
155
+ if (node.hasOwnProperty('metrics')) {
156
+ delete node.metrics['metric_git_sloc']
157
+ }
158
+
159
+ if (!node.hasOwnProperty('metrics')) {
160
+ node.metrics = {}
161
+ }
162
+
163
+ let nodeFileName = node.id.split("/").pop();
164
+
165
+ let nodeSearchPath = ""
166
+ if (fileResultPrefix === "") {
167
+ nodeSearchPath = node.id
168
+ } else {
169
+ nodeSearchPath = fileResultPrefix + "/" + node.id
170
+ }
171
+
172
+ // add git code churn
173
+ for (const [key, value] of Object.entries(fileChurnMap)) {
174
+ if (nodeSearchPath.includes(key)) {
175
+ node['metric_git_code_churn'] = value
176
+ node.metrics['metric_git_code_churn'] = value
177
+ }
178
+ }
179
+
180
+ // add git whitespace complexity
181
+ for (const [key, value] of Object.entries(whiteSpaceComplexityMap)) {
182
+ if (nodeSearchPath.includes(key)) {
183
+ node['metric_git_ws_complexity'] = value
184
+ node.metrics['metric_git_ws_complexity'] = value
185
+ }
186
+ }
187
+
188
+ // add git sloc
189
+ for (const [key, value] of Object.entries(slocMap)) {
190
+ if (nodeSearchPath.includes(key)) {
191
+ node['metric_git_sloc'] = value
192
+ node.metrics['metric_git_sloc'] = value
193
+ }
194
+ }
195
+
196
+ // add git number authors
197
+ for (const [key, value] of Object.entries(authorsMap)) {
198
+ if (nodeSearchPath.includes(key)) {
199
+ node['metric_git_contributors'] = value
200
+ node.metrics['metric_git_contributors'] = value
201
+ }
202
+ }
203
+
204
+ // add all git contributors to file
205
+ for (const [key, value] of Object.entries(authorsMap)) {
206
+ if (nodeSearchPath.includes(key)) {
207
+ node['metric_git_contributors'] = Object.keys(value)
208
+ node.metrics['metric_git_contributors'] = Object.keys(value)
209
+ node['metric_git_number_authors'] = Object.keys(value).length
210
+ node.metrics['metric_git_number_authors'] = Object.keys(value).length
211
+ }
212
+ }
213
+
214
+ });
215
+ }
216
+ }
217
+
218
+ function mainContributor(obj={}, asc=true) {
219
+ let biggestChurn = 0
220
+ let authorBiggestChurn = ''
221
+
222
+ for (let key in obj) {
223
+ if (obj[key] > biggestChurn) {
224
+ biggestChurn = obj[key]
225
+ authorBiggestChurn = key
226
+ }
227
+ }
228
+
229
+ return authorBiggestChurn
230
+ }
231
+
232
+ // daterangepicker for git date range
233
+ function initDateRangeUI() {
234
+ $('input[name="daterange"]').daterangepicker({
235
+ "startDate": commit_first_date,
236
+ "endDate": commit_last_date,
237
+ "minDate": commit_first_date,
238
+ "maxDate": commit_last_date,
239
+
240
+ // ranges: {
241
+ // 'Last 3 days': [moment().subtract(2, 'days'), moment()],
242
+ // 'Last 10 days': [moment().subtract(9, 'days'), moment()],
243
+ // 'Last 30 days': [moment().subtract(29, 'days'), moment()],
244
+ // 'Last 60 days': [moment().subtract(59, 'days'), moment()],
245
+ // 'This month': [moment().startOf('month'), moment().endOf('month')],
246
+ // 'Last month': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')]
247
+ // },
248
+
249
+ isInvalidDate: function(date) {
250
+ if ( commit_dates.includes(date.format('DD/MM/YYYY')) ) {
251
+ return false
252
+ } else {
253
+ return true
254
+ }
255
+ },
256
+
257
+ "locale": {
258
+ "format": "DD/MM/YYYY",
259
+ "separator": " - ",
260
+ "applyLabel": "Apply",
261
+ "cancelLabel": "Cancel",
262
+ "fromLabel": "From",
263
+ "toLabel": "To",
264
+ "customRangeLabel": "Custom",
265
+ "weekLabel": "W",
266
+ "daysOfWeek": [
267
+ "Su",
268
+ "Mo",
269
+ "Tu",
270
+ "We",
271
+ "Th",
272
+ "Fr",
273
+ "Sa"
274
+ ],
275
+ "monthNames": [
276
+ "January",
277
+ "February",
278
+ "March",
279
+ "April",
280
+ "May",
281
+ "June",
282
+ "July",
283
+ "August",
284
+ "September",
285
+ "October",
286
+ "November",
287
+ "December"
288
+ ],
289
+ "firstDay": 1
290
+ },
291
+
292
+ opens: 'left'
293
+ }, function(start, end, label) {
294
+ console.log("A new date selection was made: " + start.format('DD/MM/YYYY') + ' to ' + end.format('DD/MM/YYYY'));
295
+ dateRangePickerFrom = start.format('DD/MM/YYYY')
296
+ dateRangePickerTo = end.format('DD/MM/YYYY')
297
+
298
+ // console.log(dateRangePickerFrom)
299
+ // console.log(dateRangePickerTo)
300
+
301
+ initGitMetricsForDateRange()
302
+ showToastDateRangeUpdate()
303
+ })
304
+ }
305
+
306
+ function showToastDateRangeUpdate() {
307
+ const toastLiveExample = document.getElementById('toastDateRangeUpdated')
308
+ const toast = new bootstrap.Toast(toastLiveExample)
309
+ toast.show()
310
+ }
311
+
312
+ // Copyright 2021 Observable, Inc.
313
+ // Released under the ISC license.
314
+ // https://observablehq.com/@d3/multi-line-chart
315
+ function LineChart(data, {
316
+ x = ([x]) => x, // given d in data, returns the (temporal) x-value
317
+ y = ([, y]) => y, // given d in data, returns the (quantitative) y-value
318
+ z = () => 1, // given d in data, returns the (categorical) z-value
319
+ title, // given d in data, returns the title text
320
+ defined, // for gaps in data
321
+ curve = d3.curveLinear, // method of interpolation between points
322
+ marginTop = 20, // top margin, in pixels
323
+ marginRight = 30, // right margin, in pixels
324
+ marginBottom = 30, // bottom margin, in pixels
325
+ marginLeft = 40, // left margin, in pixels
326
+ width = 640, // outer width, in pixels
327
+ height = 400, // outer height, in pixels
328
+ xType = d3.scaleUtc, // type of x-scale
329
+ xDomain, // [xmin, xmax]
330
+ xRange = [marginLeft, width - marginRight], // [left, right]
331
+ yType = d3.scaleLinear, // type of y-scale
332
+ yDomain, // [ymin, ymax]
333
+ yRange = [height - marginBottom, marginTop], // [bottom, top]
334
+ yFormat, // a format specifier string for the y-axis
335
+ yLabel, // a label for the y-axis
336
+ zDomain, // array of z-values
337
+ color = "currentColor", // stroke color of line, as a constant or a function of *z*
338
+ strokeLinecap, // stroke line cap of line
339
+ strokeLinejoin, // stroke line join of line
340
+ strokeWidth = 1.0, // stroke width of line
341
+ strokeOpacity, // stroke opacity of line
342
+ mixBlendMode = "multiply", // blend mode of lines
343
+ voronoi, // show a Voronoi overlay? (for debugging)
344
+ id
345
+ } = {}) {
346
+ // Compute values.
347
+
348
+ let textColor = "#FFF"
349
+ if (!darkMode) { textColor = "#333333"}
350
+
351
+ const X = d3.map(data, x);
352
+ const Y = d3.map(data, y);
353
+ const Z = d3.map(data, z);
354
+ const O = d3.map(data, d => d);
355
+ if (defined === undefined) defined = (d, i) => !isNaN(X[i]) && !isNaN(Y[i]) ;
356
+ const D = d3.map(data, defined);
357
+
358
+ // Compute default domains, and unique the z-domain.
359
+ if (xDomain === undefined) xDomain = d3.extent(X);
360
+ if (yDomain === undefined) yDomain = [0, d3.max(Y, d => typeof d === "string" ? +d : d)];
361
+ if (zDomain === undefined) zDomain = Z;
362
+ zDomain = new d3.InternSet(zDomain);
363
+
364
+ // Omit any data not present in the z-domain.
365
+ const I = d3.range(X.length).filter(i => zDomain.has(Z[i]));
366
+
367
+ // Construct scales and axes.
368
+ const xScale = xType(xDomain, xRange);
369
+ const yScale = yType(yDomain, yRange);
370
+ const xAxis = d3.axisBottom(xScale).ticks(width / 80).tickSizeOuter(0);
371
+ const yAxis = d3.axisLeft(yScale).ticks(height / 60, yFormat);
372
+
373
+ // Compute titles.
374
+ const T = title === undefined ? Z : title === null ? null : d3.map(data, title);
375
+
376
+ // Construct a line generator.
377
+ const line = d3.line()
378
+ .defined(i => D[i])
379
+ .curve(curve)
380
+ .x(i => xScale(X[i]))
381
+ .y(i => yScale(Y[i]));
382
+
383
+ const svg = d3.create("svg")
384
+ .attr("id", id)
385
+ .attr("width", width)
386
+ .attr("height", height)
387
+ .attr("viewBox", [0, 0, width, height])
388
+ .attr("style", "max-width: 100%; height: auto; height: intrinsic;")
389
+ .style("-webkit-tap-highlight-color", "transparent")
390
+ .on("pointerenter", pointerentered)
391
+ .on("pointermove", pointermoved)
392
+ .on("pointerleave", pointerleft)
393
+ .on("touchstart", event => event.preventDefault());
394
+
395
+ // An optional Voronoi display (for fun).
396
+ if (voronoi) svg.append("path")
397
+ .attr("fill", "none")
398
+ .attr("stroke", "#ccc")
399
+ .attr("d", d3.Delaunay
400
+ .from(I, i => xScale(X[i]), i => yScale(Y[i]))
401
+ .voronoi([0, 0, width, height])
402
+ .render());
403
+
404
+ svg.append("g")
405
+ .attr("transform", `translate(0,${height - marginBottom})`)
406
+ .call(xAxis);
407
+
408
+ svg.append("g")
409
+ .attr("transform", `translate(${marginLeft},0)`)
410
+ .call(yAxis)
411
+ .call(g => g.select(".domain").remove())
412
+ .call(voronoi ? () => {} : g => g.selectAll(".tick line").clone()
413
+ .attr("x2", width - marginLeft - marginRight)
414
+ .attr("stroke-opacity", 0.1))
415
+ .call(g => g.append("text")
416
+ .attr("x", -marginLeft)
417
+ .attr("y", 10)
418
+ .attr("fill", "currentColor")
419
+ .attr("text-anchor", "start")
420
+ .text(yLabel));
421
+
422
+ const path = svg.append("g")
423
+ .attr("fill", "none")
424
+ .attr("stroke", typeof color === "string" ? color : null)
425
+ .attr("stroke-linecap", strokeLinecap)
426
+ .attr("stroke-linejoin", strokeLinejoin)
427
+ .attr("stroke-width", strokeWidth)
428
+ .attr("stroke-opacity", strokeOpacity)
429
+ .selectAll("path")
430
+ .data(d3.group(I, i => Z[i]))
431
+ .join("path")
432
+ //.style("mix-blend-mode", mixBlendMode)
433
+ .attr("stroke", typeof color === "function" ? ([z]) => color(z) : null)
434
+ .attr("d", ([, I]) => line(I));
435
+
436
+ const dot = svg.append("g")
437
+ .attr("display", "none")
438
+ .attr("fill", "red");
439
+
440
+ dot.append("circle")
441
+ .attr("r", 2.5);
442
+
443
+ dot.append("text")
444
+ .attr("font-family", "sans-serif")
445
+ .attr("font-size", 10)
446
+ .attr("text-anchor", "middle")
447
+ .attr("fill", textColor)
448
+ .attr("y", -8);
449
+
450
+ function pointermoved(event) {
451
+ let strokeColor = "#323232"
452
+ if (!darkMode) { strokeColor = "#828282"}
453
+
454
+ const [xm, ym] = d3.pointer(event);
455
+ const i = d3.least(I, i => Math.hypot(xScale(X[i]) - xm, yScale(Y[i]) - ym)); // closest point
456
+ path.style("stroke", ([z]) => Z[i] === z ? null : strokeColor).filter(([z]) => Z[i] === z).raise();
457
+ dot.attr("transform", `translate(${xScale(X[i])},${yScale(Y[i])})`);
458
+ if (T) dot.select("text").text(T[i]);
459
+ svg.property("value", O[i]).dispatch("input", {bubbles: true});
460
+ }
461
+
462
+ function pointerentered() {
463
+ path.style("mix-blend-mode", null).style("stroke", "lightyellow");
464
+ dot.attr("display", null);
465
+ }
466
+
467
+ function pointerleft() {
468
+ //path.style("mix-blend-mode", mixBlendMode).style("stroke", null);
469
+ path.style("stroke", "lightsteelblue")
470
+ dot.attr("display", "none");
471
+ svg.node().value = null;
472
+ svg.dispatch("input", {bubbles: true});
473
+ }
474
+
475
+ return Object.assign(svg.node(), {value: null});
476
+ }
477
+
478
+ function generateTimeSeriesChart() {
479
+
480
+ let timeSeriesComplexityTotal = {}
481
+ let timeSeriesSlocTotal = {}
482
+ let timeSeriesChurnTotal = {}
483
+ let timeSeriesComplexity = []
484
+ let timeSeriesSloc = []
485
+ let timeSeriesChurn = []
486
+
487
+ for (let i = gitMetricsIndexFrom; i < gitMetricsIndexTo; i++) {
488
+
489
+ // prepare complexity data for chart
490
+ for (const [key, value] of Object.entries(commit_metrics[i].ws_complexity)) {
491
+
492
+ let filter = true
493
+ if (Object.keys(selectedNodesMap).length > 0) {
494
+ for (const [selectedNode, v] of Object.entries(selectedNodesMap)) {
495
+ if (selectedNode.includes(key.toLowerCase())) {
496
+ filter = false
497
+ }
498
+ }
499
+ } else {
500
+ filter = false
501
+ }
502
+
503
+ if (filter == true) {
504
+ continue
505
+ }
506
+
507
+ for (const [file, complexity] of Object.entries(timeSeriesComplexityTotal)) {
508
+ if (file !== key) {
509
+ timeSeriesComplexity.push(
510
+ {
511
+ 'filepath' : file,
512
+ 'wscomplexity' : complexity,
513
+ 'date': commit_metrics[i].exact_date.replace(/_/g, "-")
514
+ }
515
+ )
516
+ }
517
+ }
518
+
519
+ timeSeriesComplexityTotal[key] = value
520
+
521
+ let timeSeriesComplexityEntry = {
522
+ 'filepath' : key,
523
+ 'wscomplexity' : value,
524
+ 'date': commit_metrics[i].exact_date.replace(/_/g, "-") // TODO ...
525
+ }
526
+ timeSeriesComplexity.push(timeSeriesComplexityEntry)
527
+ }
528
+
529
+ // prepare sloc data for chart
530
+ for (const [key, value] of Object.entries(commit_metrics[i].sloc)) {
531
+
532
+ let filter = true
533
+ if (Object.keys(selectedNodesMap).length > 0) {
534
+ for (const [selectedNode, v] of Object.entries(selectedNodesMap)) {
535
+ if (selectedNode.includes(key.toLowerCase())) {
536
+ filter = false
537
+ }
538
+ }
539
+ } else {
540
+ filter = false
541
+ }
542
+
543
+ if (filter == true) {
544
+ continue
545
+ }
546
+
547
+ for (const [file, sloc] of Object.entries(timeSeriesSlocTotal)) {
548
+ if (file !== key) {
549
+ timeSeriesSloc.push(
550
+ {
551
+ 'filepath' : file,
552
+ 'sloc' : sloc,
553
+ 'date': commit_metrics[i].exact_date.replace(/_/g, "-")
554
+ }
555
+ )
556
+ }
557
+ }
558
+
559
+ timeSeriesSlocTotal[key] = value
560
+
561
+ let timeSeriesSlocEntry = {
562
+ 'filepath' : key,
563
+ 'sloc' : value,
564
+ 'date': commit_metrics[i].exact_date.replace(/_/g, "-") // TODO ...
565
+ }
566
+ timeSeriesSloc.push(timeSeriesSlocEntry)
567
+ }
568
+
569
+
570
+ // prepare churn data for chart
571
+ for (const [key, value] of Object.entries(commit_metrics[i].churn)) {
572
+ let filter = true
573
+ if (Object.keys(selectedNodesMap).length > 0) {
574
+ for (const [selectedNode, v] of Object.entries(selectedNodesMap)) {
575
+ if (selectedNode.includes(key.toLowerCase())) {
576
+ filter = false
577
+ }
578
+ }
579
+ } else {
580
+ filter = false
581
+ }
582
+
583
+ if (filter == true) {
584
+ continue
585
+ }
586
+
587
+ for (const [file, churn] of Object.entries(timeSeriesChurnTotal)) {
588
+ if (file !== key) {
589
+ timeSeriesChurn.push(
590
+ {
591
+ 'filepath' : file,
592
+ 'churn' : churn,
593
+ 'date': commit_metrics[i].exact_date.replace(/_/g, "-")
594
+ }
595
+ )
596
+ }
597
+ }
598
+
599
+ timeSeriesChurnTotal[key] = value
600
+
601
+ let timeSeriesChurnEntry = {
602
+ 'filepath' : key,
603
+ 'churn' : value,
604
+ 'date': commit_metrics[i].exact_date.replace(/_/g, "-") // TODO ...
605
+ }
606
+ timeSeriesChurn.push(timeSeriesChurnEntry)
607
+ }
608
+ }
609
+
610
+ let complexityChart = LineChart(timeSeriesComplexity, {
611
+ x: d => Date.parse(d.date),
612
+ y: d => d.wscomplexity,
613
+ z: d => d.filepath,
614
+ yLabel: "Whitespace Complexity",
615
+ width: 1000,
616
+ height: 300,
617
+ color: "lightsteelblue",
618
+ voronoi: false,
619
+ id: "timeSeriesComplexityChart"
620
+ })
621
+
622
+ let slocChart = LineChart(timeSeriesSloc, {
623
+ x: d => Date.parse(d.date),
624
+ y: d => d.sloc,
625
+ z: d => d.filepath,
626
+ yLabel: "SLOC",
627
+ width: 1000,
628
+ height: 300,
629
+ color: "lightsteelblue",
630
+ voronoi: false,
631
+ id: "timeSeriesSlocChart"
632
+ })
633
+
634
+ let churnChart = LineChart(timeSeriesChurn, {
635
+ x: d => Date.parse(d.date),
636
+ y: d => d.churn,
637
+ z: d => d.filepath,
638
+ yLabel: "Code churn",
639
+ width: 1000,
640
+ height: 300,
641
+ color: "lightsteelblue",
642
+ voronoi: false,
643
+ id: "timeSeriesChurnChart"
644
+ })
645
+
646
+ document.getElementById("my_dataviz").appendChild(complexityChart);
647
+ document.getElementById("time_series_sloc").appendChild(slocChart);
648
+ document.getElementById("my_dataviz2").appendChild(churnChart);
649
+ }
650
+
651
+ function generateChangeCouplingChart() {
652
+
653
+ let flows = []
654
+ let locations = []
655
+ let locationId = 0
656
+
657
+ let locationColorMap = {}
658
+
659
+ for (let i = gitMetricsIndexFrom; i < gitMetricsIndexTo; i++) {
660
+
661
+ // prepare change coupling data for chart
662
+ if (commit_metrics[i].links.length > 0) {
663
+
664
+ for (link of commit_metrics[i].links) {
665
+
666
+ const matchingSourceKey = link.source
667
+ const matchingTargetKey = link.target
668
+
669
+ let filter = true
670
+ if (Object.keys(selectedNodesMap).length > 0) {
671
+ for (const [selectedNode, v] of Object.entries(selectedNodesMap)) {
672
+ if ( selectedNode.includes(matchingSourceKey.toLowerCase()) || selectedNode.includes(matchingTargetKey.toLowerCase()) ) {
673
+ filter = false
674
+ }
675
+ }
676
+ } else {
677
+ filter = false
678
+ }
679
+
680
+ if (filter == true) {
681
+ continue
682
+ }
683
+
684
+ if ( !(locations.find(e => e.name === matchingSourceKey)) ) {
685
+
686
+ if ( !(matchingSourceKey in locationColorMap) ) {
687
+ let randomColor = "#000000".replace(/0/g,function(){return (~~(Math.random()*16)).toString(16);});
688
+ locationColorMap[matchingSourceKey] = randomColor
689
+ }
690
+
691
+ let location = {
692
+ 'id': locationId,
693
+ 'name': matchingSourceKey,
694
+ 'color': locationColorMap[matchingSourceKey]
695
+ }
696
+ locations.push(location)
697
+
698
+ let flow = {
699
+ 'from' : locationId,
700
+ 'to' : locationId,
701
+ 'quantity': 0
702
+
703
+ }
704
+ flows.push(flow)
705
+ locationId += 1
706
+ }
707
+
708
+ if ( !(locations.find(e => e.name === matchingTargetKey)) ) {
709
+
710
+ if ( !(matchingTargetKey in locationColorMap) ) {
711
+
712
+ // find the corresponding node color
713
+ for (const [key, value] of Object.entries(nodeColorMap)) {
714
+ if (key.includes(matchingTargetKey)) {
715
+ locationColorMap[matchingTargetKey] = value
716
+ }
717
+ }
718
+
719
+ // if (matchingTargetKey in nodeColorMap) {
720
+ // locationColorMap[matchingTargetKey] = nodeColorMap[matchingTargetKey]
721
+ // } else {
722
+ // console.log("should not happen")
723
+ // locationColorMap[matchingTargetKey] = '#FFFFFF' // "#000000".replace(/0/g,function(){return (~~(Math.random()*16)).toString(16);});
724
+ // }
725
+ }
726
+
727
+ let location = {
728
+ 'id': locationId,
729
+ 'name': matchingTargetKey,
730
+ 'color': locationColorMap[matchingTargetKey]
731
+ }
732
+
733
+ locations.push(location)
734
+ let flow = {
735
+ 'from' : locationId,
736
+ 'to' : locationId,
737
+ 'quantity': 0
738
+
739
+ }
740
+ flows.push(flow)
741
+ locationId += 1
742
+ }
743
+
744
+ const iSource = locations.findIndex(e => e.name === matchingSourceKey);
745
+ const iTarget = locations.findIndex(e => e.name === matchingTargetKey);
746
+
747
+ if (iSource > -1 && iTarget > -1) {
748
+ let flow = {
749
+ 'from' : locations[iSource].id,
750
+ 'to' : locations[iTarget].id,
751
+ 'quantity': 15
752
+ }
753
+ flows.push(flow)
754
+ }
755
+
756
+ }
757
+
758
+ }
759
+
760
+ }
761
+
762
+ for (let n = 0; n < locationId; n++) {
763
+ for (let m = 0; m < locationId; m++) {
764
+ if ( !(flows.find(e => e.from === n && e.to === m )) ) {
765
+ let flow = {
766
+ 'from' : n,
767
+ 'to' : m,
768
+ 'quantity': 0
769
+ }
770
+ flows.push(flow)
771
+ }
772
+ }
773
+ }
774
+
775
+ // Borrowed from this great blog entry:
776
+ // https://blog.noser.com/d3-js-chord-diagramm-teil-2-benutzerdefinierte-sortierung-und-kurvenformen/
777
+
778
+ var matrix = [];
779
+
780
+ //Map list of data to matrix
781
+ flows.forEach(function (flow) {
782
+
783
+ //Initialize sub-array if not yet exists
784
+ if (!matrix[flow.to]) {
785
+ matrix[flow.to] = [];
786
+ }
787
+
788
+ matrix[flow.to][flow.from] = flow.quantity;
789
+ });
790
+
791
+ /*//////////////////////////////////////////////////////////
792
+ /////////////// Initiate Chord Diagram /////////////////////
793
+ //////////////////////////////////////////////////////////*/
794
+ let size = 900;
795
+ let dr = 40; //radial translation for group names
796
+ let dx = 20; //horizontal translation for group names
797
+ let margin = { top: 0, right: 50, bottom: 50, left: 50 };
798
+ let chordWidth = (size + 200) - margin.left - margin.right;
799
+ let chordHeight = size - margin.top - margin.bottom;
800
+ let innerRadius = Math.min(chordWidth, chordHeight) * .39;
801
+ let outerRadius = innerRadius * 1.08;
802
+
803
+ let root = d3.select("#change_coupling_chord_diagram");
804
+
805
+ //Generate tooltip already, but keep it invisible for now.
806
+ var toolTip = root.append("div")
807
+ .classed("tooltip", true)
808
+ .style("opacity", 0)
809
+ .style("position", "absolute")
810
+ .style("text-align", "center")
811
+ .style("padding", "6px")
812
+ .style("font", "10px sans-serif")
813
+ .style("color", "black")
814
+ .style("background", "silver")
815
+ .style("border", "1px solid gray")
816
+ .style("border-radius", "8px")
817
+ .style("pointer-events", "none");
818
+
819
+ var focusedChordGroupIndex = null;
820
+
821
+ /*Initiate the SVG*/
822
+ //D3.js v3!
823
+ var svg = root.append("svg:svg")
824
+ .attr("width", chordWidth + margin.left + margin.right)
825
+ .attr("height", chordHeight + margin.top + margin.bottom)
826
+ .attr("id", "svg_change_coupling_chord_diagram");
827
+
828
+ var container = svg.append("g")
829
+ .attr("transform", "translate(" +
830
+ (margin.left + chordWidth / 2) + "," +
831
+ (margin.top + chordHeight / 2) + ")");
832
+
833
+ var chord = customChordLayout()
834
+ .padding(0.04)
835
+ .sortSubgroups(d3.descending) /*sort the chords inside an arc from high to low*/
836
+ .sortChords(d3.ascending) /*which chord should be shown on top when chords cross. Now the largest chord is at the top*/
837
+ .matrix(matrix);
838
+
839
+ /*//////////////////////////////////////////////////////////
840
+ ////////////////// Draw outer Arcs /////////////////////////
841
+ //////////////////////////////////////////////////////////*/
842
+ var arc = d3.arc()
843
+ .innerRadius(innerRadius)
844
+ .outerRadius(outerRadius);
845
+
846
+ var g = container.selectAll("g.group")
847
+ .data(chord.groups)
848
+ .enter()
849
+ .append("svg:g")
850
+ .attr("class", function (d) { return "group group-" + locations[d.index].id; });
851
+
852
+ g.append("svg:path")
853
+ .attr("d", arc)
854
+ .style("fill", function (d) {
855
+ return locations[d.index].color;
856
+ })
857
+ .style("stroke", function (d) {
858
+ return d3.rgb(locations[d.index].color).brighter();
859
+ })
860
+ .on("click", function (event, d) { highlightChords(d.index) }) // .on("click", function (d) { highlightChords(d.index) })
861
+ .on("mouseover", function(event, i) {
862
+ showArcToolTip(event, i);
863
+ })
864
+ .on("mouseout", function(d) { hideToolTip() });
865
+
866
+ /*//////////////////////////////////////////////////////////
867
+ //////////////// Initiate inner chords /////////////////////
868
+ //////////////////////////////////////////////////////////*/
869
+ var chords = container.selectAll("path.chord")
870
+ .data(chord.chords)
871
+ .enter()
872
+ .append("svg:path")
873
+ .attr("class", function (d) {
874
+ return "chord chord-source-" + d.source.index + " chord-target-" + d.target.index;
875
+ })
876
+ .attr("d", customChordPathGenerator().radius(innerRadius))
877
+ //Change the fill to reference the unique gradient ID
878
+ //of the source-target combination
879
+ .style("fill", function (d) {
880
+ return "url(#chordGradient-" + d.source.index + "-" + d.target.index + ")";
881
+ })
882
+ .style("stroke", function (d) {
883
+ return "url(#chordGradient-" + d.source.index + "-" + d.target.index + ")";
884
+ })
885
+ .style("fill-opacity", "0.7")
886
+ .on("mouseover", function(event, i) {
887
+ if (focusedChordGroupIndex === null ||
888
+ i.source.index === focusedChordGroupIndex ||
889
+ i.target.index === focusedChordGroupIndex) {
890
+ if (focusedChordGroupIndex === null) {
891
+ d3.selectAll(".chord")
892
+ .style("fill-opacity", 0.2)
893
+ .style("stroke-opacity", 0.2);
894
+ d3.select(this).style("fill-opacity", 1);
895
+ }
896
+ else {
897
+ d3.selectAll(".chord.chord-source-" + focusedChordGroupIndex + ", " +
898
+ ".chord.chord-target-" + focusedChordGroupIndex)
899
+ .style("fill-opacity", 0.2)
900
+ .style("stroke-opacity", 0.2);
901
+ d3.select(this).style("fill-opacity", 1);
902
+ }
903
+
904
+ showChordToolTip(event, i);
905
+ }
906
+ })
907
+ .on("mouseout", function(d) {
908
+ if (focusedChordGroupIndex === null) {
909
+ d3.selectAll(".chord")
910
+ .style("fill-opacity", 0.7)
911
+ .style("stroke-opacity", 1);
912
+ }
913
+ else {
914
+ d3.selectAll(".chord.chord-source-" + focusedChordGroupIndex + ", " +
915
+ ".chord.chord-target-" + focusedChordGroupIndex)
916
+ .style("fill-opacity", 0.7)
917
+ .style("stroke-opacity", 1);
918
+ }
919
+
920
+ hideToolTip();
921
+ });
922
+
923
+ //Cf https://www.visualcinnamon.com/2016/06/orientation-gradient-d3-chord-diagram
924
+ //Create a gradient definition for each chord
925
+ var grads = svg.append("defs").selectAll("linearGradient")
926
+ .data(chord.chords)
927
+ .enter().append("linearGradient")
928
+ //Create a unique gradient id per chord: e.g. "chordGradient-0-4"
929
+ .attr("id", function (d) {
930
+ return "chordGradient-" + d.source.index + "-" + d.target.index;
931
+ })
932
+ //Instead of the object bounding box, use the entire SVG for setting locations
933
+ //in pixel locations instead of percentages (which is more typical)
934
+ .attr("gradientUnits", "userSpaceOnUse")
935
+ //The full mathematical formula to find the x and y locations
936
+ .attr("x1", function (d, i) {
937
+ return innerRadius * Math.cos((d.source.endAngle - d.source.startAngle) / 2 +
938
+ d.source.startAngle - Math.PI / 2);
939
+ })
940
+ .attr("y1", function (d, i) {
941
+ return innerRadius * Math.sin((d.source.endAngle - d.source.startAngle) / 2 +
942
+ d.source.startAngle - Math.PI / 2);
943
+ })
944
+ .attr("x2", function (d, i) {
945
+ return innerRadius * Math.cos((d.target.endAngle - d.target.startAngle) / 2 +
946
+ d.target.startAngle - Math.PI / 2);
947
+ })
948
+ .attr("y2", function (d, i) {
949
+ return innerRadius * Math.sin((d.target.endAngle - d.target.startAngle) / 2 +
950
+ d.target.startAngle - Math.PI / 2);
951
+ });
952
+
953
+ //Set the starting color (at 0%)
954
+ grads.append("stop")
955
+ .attr("offset", "0%")
956
+ .attr("stop-color", function (d) { return locations[d.source.index].color; });
957
+
958
+ //Set the ending color (at 100%)
959
+ grads.append("stop")
960
+ .attr("offset", "100%")
961
+ .attr("stop-color", function (d) { return locations[d.target.index].color; });
962
+
963
+
964
+ /*//////////////////////////////////////////////////////////
965
+ ////////////////// Initiate Ticks //////////////////////////
966
+ //////////////////////////////////////////////////////////*/
967
+ var ticks = g.append("svg:g")
968
+ .selectAll("g.ticks")
969
+ .data(groupTicks)
970
+ .enter().append("svg:g")
971
+ .attr("transform", function (d) {
972
+ return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")"
973
+ + "translate(" + outerRadius + 40 + ",0)";
974
+ });
975
+
976
+ /*Append the tick around the arcs*/
977
+ ticks.append("svg:line")
978
+ .attr("x1", 1)
979
+ .attr("y1", 0)
980
+ .attr("x2", 6)
981
+ .attr("y2", 0)
982
+ .attr("class", "ticks")
983
+ .style("stroke", "#FFF")
984
+ .style("stroke-width", "1.5px");
985
+
986
+ let labelColor = "#FFF"
987
+ if (!darkMode) { labelColor = "#333333"}
988
+
989
+ /*Add the labels for the ticks*/
990
+ ticks.append("svg:text")
991
+ .attr("class", "tickLabels")
992
+ .attr("x", 12)
993
+ .attr("dy", ".35em")
994
+ .style("font-size", "10px")
995
+ .style("font-family", "sans-serif")
996
+ .attr("fill", labelColor)
997
+ .attr("transform", function (d) {
998
+ return d.angle > Math.PI ? "rotate(180)translate(-25)" : null;
999
+ })
1000
+ .style("text-anchor", function (d) {
1001
+ return d.angle > Math.PI ? "end" : null;
1002
+ })
1003
+ //.text(function (d) { return d.label; });
1004
+
1005
+ /*//////////////////////////////////////////////////////////
1006
+ ////////////////// Initiate Names //////////////////////////
1007
+ //////////////////////////////////////////////////////////*/
1008
+ g.append("svg:text")
1009
+ .each(function (d) { d.angle = (d.startAngle + d.endAngle) / 2; })
1010
+ .attr("dy", ".35em")
1011
+ .attr("class", "titles")
1012
+ .style("font-size", "10px")
1013
+ .style("font-family", "sans-serif")
1014
+ .attr("fill", labelColor)
1015
+ .attr("text-anchor", function (d) {
1016
+ return d.angle > Math.PI ? "end" : null;
1017
+ })
1018
+ .attr("transform", function (d) {
1019
+ var r = outerRadius + dr;
1020
+ var angle = d.angle + ((3 *Math.PI) / 2);
1021
+ var x = r * Math.cos(angle);
1022
+ var y = r * Math.sin(angle);
1023
+
1024
+ if (d.angle > Math.PI) {
1025
+ x -= dx;
1026
+ }
1027
+ else {
1028
+ x += dx;
1029
+ }
1030
+
1031
+ return "translate(" + x + ", " + y + ")";
1032
+ })
1033
+ .text(function (d, i) {
1034
+ if (locations[i].name.includes("/")) {
1035
+ return locations[i].name.substring(locations[i].name.lastIndexOf('/') + 1)
1036
+ } else {
1037
+ return locations[i].name
1038
+ }
1039
+ });
1040
+
1041
+ /*Lines from labels to arcs*/
1042
+ /*part in radial direction*/
1043
+ this.g.append("line")
1044
+ .attr("x1", function (d) {
1045
+ return outerRadius * Math.cos(d.angle + ((3 * Math.PI) / 2));
1046
+ })
1047
+ .attr("y1", function (d) {
1048
+ return outerRadius * Math.sin(d.angle + ((3 * Math.PI) / 2));
1049
+ })
1050
+ .attr("x2", function (d) {
1051
+ return (outerRadius + dr) * Math.cos(d.angle + ((3 * Math.PI) / 2));
1052
+ })
1053
+ .attr("y2", function (d) {
1054
+ return (outerRadius + dr) * Math.sin(d.angle + ((3 * Math.PI) / 2));
1055
+ })
1056
+ .style("stroke", "#FFF")
1057
+ .style("stroke-width", "0.5px");
1058
+
1059
+ /*horizontal part*/
1060
+ this.g.append("line")
1061
+ .attr("x1", function (d) {
1062
+ return (outerRadius + dr) * Math.cos(d.angle + ((3 * Math.PI) / 2));
1063
+ })
1064
+ .attr("y1", function (d) {
1065
+ return (outerRadius + dr) * Math.sin(d.angle + ((3 * Math.PI) / 2));
1066
+ })
1067
+ .attr("x2", function (d) {
1068
+ var x = (outerRadius + dr) * Math.cos(d.angle + ((3 * Math.PI) / 2));
1069
+ if (d.angle > Math.PI) {
1070
+ x -= dx - 5;
1071
+ }
1072
+ else {
1073
+ x += dx - 5;
1074
+ }
1075
+ return x;
1076
+ })
1077
+ .attr("y2", function (d) {
1078
+ return (outerRadius + dr) * Math.sin(d.angle + ((3 * Math.PI) / 2));
1079
+ })
1080
+ .style("stroke", "#FFF")
1081
+ .style("stroke-width", "0.5px");
1082
+
1083
+ /*//////////////////////////////////////////////////////////
1084
+ ////////////////// Extra Functions /////////////////////////
1085
+ //////////////////////////////////////////////////////////*/
1086
+
1087
+ /*Returns an array of tick angles and labels, given a group*/
1088
+ function groupTicks(d) {
1089
+ var anglePerPerson = (d.endAngle - d.startAngle) / d.value;
1090
+ return d3.range(0, d.value, 100).map(function (v, i) {
1091
+ return {
1092
+ angle: v * anglePerPerson + d.startAngle,
1093
+ label: i % 5 ? null : v //Each 5th tick has a label
1094
+ };
1095
+ });
1096
+ };
1097
+
1098
+ //Hides all chords except the chords connecting to the subgroup /
1099
+ //location of the given index.
1100
+ function highlightChords(index) {
1101
+ //If this subgroup is already highlighted, toggle all chords back on.
1102
+ if (focusedChordGroupIndex === index) {
1103
+ showAllChords();
1104
+ return;
1105
+ }
1106
+
1107
+ hideAllChords();
1108
+
1109
+ //Show only the ones with source or target == index
1110
+ d3.selectAll(".chord-source-" + index + ", .chord-target-" + index)
1111
+ .transition().duration(500)
1112
+ .style("fill-opacity", "0.7")
1113
+ .style("stroke-opacity", "1");
1114
+
1115
+ focusedChordGroupIndex = index;
1116
+ };
1117
+
1118
+ function showAllChords() {
1119
+ svg.selectAll("path.chord")
1120
+ .transition().duration(500)
1121
+ .style("fill-opacity", "0.7")
1122
+ .style("stroke-opacity", "1");
1123
+
1124
+ focusedChordGroupIndex = null;
1125
+ };
1126
+
1127
+ function hideAllChords() {
1128
+ svg.selectAll("path.chord")
1129
+ .transition().duration(500)
1130
+ .style("fill-opacity", "0")
1131
+ .style("stroke-opacity", "0");
1132
+ };
1133
+
1134
+ function showChordToolTip(event, chord) {
1135
+ var prompt = "";
1136
+
1137
+ // if (chord.source.index !== chord.target.index) {
1138
+ // prompt += chord.source.value + " Kunden gingen von " +
1139
+ // locations[chord.target.index].name + " nach " +
1140
+ // locations[chord.source.index].name + ".";
1141
+ // prompt += "<br>";
1142
+ // prompt += chord.target.value + " Kunden gingen von " +
1143
+ // locations[chord.source.index].name + " nach " +
1144
+ // locations[chord.target.index].name + ".";
1145
+ // }
1146
+ // else {
1147
+ // prompt += chord.source.value + " Kunden blieben in " +
1148
+ // locations[chord.source.index].name + ".";
1149
+ // }
1150
+
1151
+ prompt += locations[chord.target.index].name + "<br>" + "... changed together with ... " + "<br>" + locations[chord.source.index].name + ".";
1152
+
1153
+ const[x, y] = d3.pointer(event);
1154
+
1155
+ toolTip
1156
+ .style("opacity", 1)
1157
+ .style("font-size", "10px")
1158
+ .html(prompt)
1159
+ .style("left", x - toolTip.node().getBoundingClientRect().width / 32 + "px") // .style("left", d3.event.pageX - toolTip.node().getBoundingClientRect().width / 2 + "px")
1160
+ .style("top", y + 300 + "px"); // .style("top", (d3.event.pageY - 50) + "px");
1161
+ };
1162
+
1163
+ function showArcToolTip(event, arc) {
1164
+ const[x, y] = d3.pointer(event);
1165
+ // console.log(locations)
1166
+ // console.log(arc)
1167
+
1168
+ var prompt = locations[arc.index].name + "."; //Math.round(arc.value)
1169
+
1170
+ toolTip
1171
+ .style("opacity", 1)
1172
+ .html(prompt)
1173
+ .style("left", x + toolTip.node().getBoundingClientRect().width + "px")
1174
+ .style("top", y + 300 + "px");
1175
+ };
1176
+
1177
+ function hideToolTip() {
1178
+ toolTip.style("opacity", 0);
1179
+ };
1180
+
1181
+ ////////////////////////////////////////////////////////////
1182
+ //////////// Custom Chord Layout Function //////////////////
1183
+ /////// Places the Chords in the visually best order ///////
1184
+ ///////////////// to reduce overlap ////////////////////////
1185
+ ////////////////////////////////////////////////////////////
1186
+ //////// Slightly adjusted by Nadieh Bremer ////////////////
1187
+ //////////////// VisualCinnamon.com ////////////////////////
1188
+ ////////////////////////////////////////////////////////////
1189
+ ////// Original from the d3.layout.chord() function ////////
1190
+ ///////////////// from the d3.js library ///////////////////
1191
+ //////////////// Created by Mike Bostock ///////////////////
1192
+ ////////////////////////////////////////////////////////////
1193
+ function customChordLayout() {
1194
+ var ε = 1e-6, ε2 = ε * ε, π = Math.PI, τ = 2 * π, τε = τ - ε, halfπ = π / 2, d3_radians = π / 180, d3_degrees = 180 / π;
1195
+ var chord = {}, chords, groups, matrix, n, padding = 0, sortGroups, sortSubgroups, sortChords;
1196
+ function relayout() {
1197
+ var subgroups = {}, groupSums = [], groupIndex = d3.range(n), subgroupIndex = [], k, x, x0, i, j;
1198
+ var numSeq;
1199
+ chords = [];
1200
+ groups = [];
1201
+ k = 0, i = -1;
1202
+
1203
+ while (++i < n) {
1204
+ x = 0, j = -1, numSeq = [];
1205
+ while (++j < n) {
1206
+ x += matrix[i][j];
1207
+ }
1208
+ groupSums.push(x);
1209
+ //////////////////////////////////////
1210
+ ////////////// New part //////////////
1211
+ //////////////////////////////////////
1212
+ for (var m = 0; m < n; m++) {
1213
+ numSeq[m] = (n + (i - 1) - m) % n;
1214
+ }
1215
+ subgroupIndex.push(numSeq);
1216
+ //////////////////////////////////////
1217
+ ////////// End new part /////////////
1218
+ //////////////////////////////////////
1219
+ k += x;
1220
+ }//while
1221
+
1222
+ k = (τ - padding * n) / k;
1223
+ x = 0, i = -1;
1224
+ while (++i < n) {
1225
+ x0 = x, j = -1;
1226
+ while (++j < n) {
1227
+ var di = groupIndex[i], dj = subgroupIndex[di][j], v = matrix[di][dj], a0 = x, a1 = x += v * k;
1228
+ subgroups[di + "-" + dj] = {
1229
+ index: di,
1230
+ subindex: dj,
1231
+ startAngle: a0,
1232
+ endAngle: a1,
1233
+ value: v
1234
+ };
1235
+ }//while
1236
+
1237
+ groups[di] = {
1238
+ index: di,
1239
+ startAngle: x0,
1240
+ endAngle: x,
1241
+ value: (x - x0) / k
1242
+ };
1243
+ x += padding;
1244
+ }//while
1245
+
1246
+ i = -1;
1247
+ while (++i < n) {
1248
+ j = i - 1;
1249
+ while (++j < n) {
1250
+ var source = subgroups[i + "-" + j], target = subgroups[j + "-" + i];
1251
+ if (source.value || target.value) {
1252
+ chords.push(source.value < target.value ? {
1253
+ source: target,
1254
+ target: source
1255
+ } : {
1256
+ source: source,
1257
+ target: target
1258
+ });
1259
+ }//if
1260
+ }//while
1261
+ }//while
1262
+ if (sortChords) resort();
1263
+ }//function relayout
1264
+
1265
+ function resort() {
1266
+ chords.sort(function (a, b) {
1267
+ return sortChords((a.source.value + a.target.value) / 2, (b.source.value + b.target.value) / 2);
1268
+ });
1269
+ }
1270
+ chord.matrix = function (x) {
1271
+ if (!arguments.length) return matrix;
1272
+ n = (matrix = x) && matrix.length;
1273
+ chords = groups = null;
1274
+ return chord;
1275
+ };
1276
+ chord.padding = function (x) {
1277
+ if (!arguments.length) return padding;
1278
+ padding = x;
1279
+ chords = groups = null;
1280
+ return chord;
1281
+ };
1282
+ chord.sortGroups = function (x) {
1283
+ if (!arguments.length) return sortGroups;
1284
+ sortGroups = x;
1285
+ chords = groups = null;
1286
+ return chord;
1287
+ };
1288
+ chord.sortSubgroups = function (x) {
1289
+ if (!arguments.length) return sortSubgroups;
1290
+ sortSubgroups = x;
1291
+ chords = null;
1292
+ return chord;
1293
+ };
1294
+ chord.sortChords = function (x) {
1295
+ if (!arguments.length) return sortChords;
1296
+ sortChords = x;
1297
+ if (chords) resort();
1298
+ return chord;
1299
+ };
1300
+ chord.chords = function () {
1301
+ if (!chords) relayout();
1302
+ return chords;
1303
+ };
1304
+ chord.groups = function () {
1305
+ if (!groups) relayout();
1306
+ return groups;
1307
+ };
1308
+ return chord;
1309
+ };
1310
+
1311
+ ////////////////////////////////////////////////////////////
1312
+ //////////// Custom Chord Path Generator ///////////////////
1313
+ ///////// Uses cubic bezier curves with quadratic //////////
1314
+ /////// spread of control points to minimise overlap ///////
1315
+ ////////////////// of adjacent chords. /////////////////////
1316
+ ////////////////////////////////////////////////////////////
1317
+ /////// Original from the d3.svg.chord() function //////////
1318
+ ///////////////// from the d3.js library ///////////////////
1319
+ //////////////// Created by Mike Bostock ///////////////////
1320
+ ////////////////////////////////////////////////////////////
1321
+ function customChordPathGenerator() {
1322
+ var source = function(d) { return d.source; };
1323
+ var target = function(d) { return d.target; };
1324
+ var radius = function(d) { return d.radius; };
1325
+ var startAngle = function(d) { return d.startAngle; };
1326
+ var endAngle = function(d) { return d.endAngle; };
1327
+
1328
+ function chord(d, i) {
1329
+ var s = subgroup(this, source, d, i),
1330
+ t = subgroup(this, target, d, i);
1331
+
1332
+ var path = "M" + s.p0
1333
+ + arc(s.r, s.p1, s.a1 - s.a0) + (equals(s, t)
1334
+ ? curve(s.r, s.p1, s.a1, s.r, s.p0, s.a0)
1335
+ : curve(s.r, s.p1, s.a1, t.r, t.p0, t.a0)
1336
+ + arc(t.r, t.p1, t.a1 - t.a0)
1337
+ + curve(t.r, t.p1, t.a1, s.r, s.p0, s.a0))
1338
+ + "Z";
1339
+
1340
+ return path;
1341
+ }
1342
+
1343
+ function subgroup(self, f, d, i) {
1344
+ var subgroup = f.call(self, d, i),
1345
+ r = radius.call(self, subgroup, i),
1346
+ a0 = startAngle.call(self, subgroup, i) - (Math.PI / 2),
1347
+ a1 = endAngle.call(self, subgroup, i) - (Math.PI / 2);
1348
+
1349
+ return {
1350
+ r: r,
1351
+ a0: a0,
1352
+ a1: a1,
1353
+ p0: [r * Math.cos(a0), r * Math.sin(a0)],
1354
+ p1: [r * Math.cos(a1), r * Math.sin(a1)]
1355
+ };
1356
+ }
1357
+
1358
+ function equals(a, b) {
1359
+ return a.a0 == b.a0 && a.a1 == b.a1;
1360
+ }
1361
+
1362
+ function arc(r, p, a) {
1363
+ return "A" + r + "," + r + " 0 " + +(a > Math.PI) + ",1 " + p;
1364
+ }
1365
+
1366
+ function curve(r0, p0, a0, r1, p1, a1) {
1367
+ var deltaAngle = Math.abs(mod((a1 - a0 + Math.PI), (2 * Math.PI)) - Math.PI);
1368
+ var radialControlPointScale = Math.pow((Math.PI - deltaAngle) / Math.PI, 2) * 0.9;
1369
+ var controlPoint1 = [p0[0] * radialControlPointScale, p0[1] * radialControlPointScale];
1370
+ var controlPoint2 = [p1[0] * radialControlPointScale, p1[1] * radialControlPointScale];
1371
+ var cubicBezierSvg = "C " + controlPoint1[0] + " " + controlPoint1[1] + ", " +
1372
+ controlPoint2[0] + " " + controlPoint2[1] + ", " +
1373
+ p1[0] + " " + p1[1];
1374
+ return cubicBezierSvg;
1375
+ }
1376
+
1377
+ function mod(a, n) {
1378
+ return (a % n + n) % n;
1379
+ }
1380
+
1381
+ chord.radius = function(v) {
1382
+ if (!arguments.length) return radius;
1383
+ radius = typeof v === "function" ? v : function() { return v; };
1384
+ return chord;
1385
+ };
1386
+
1387
+ chord.source = function(v) {
1388
+ if (!arguments.length) return source;
1389
+ source = typeof v === "function" ? v : function() { return v; };
1390
+ return chord;
1391
+ };
1392
+
1393
+ chord.target = function(v) {
1394
+ if (!arguments.length) return target;
1395
+ target = typeof v === "function" ? v : function() { return v; };
1396
+ return chord;
1397
+ };
1398
+
1399
+ chord.startAngle = function(v) {
1400
+ if (!arguments.length) return startAngle;
1401
+ startAngle = typeof v === "function" ? v : function() { return v; };
1402
+ return chord;
1403
+ };
1404
+
1405
+ chord.endAngle = function(v) {
1406
+ if (!arguments.length) return endAngle;
1407
+ endAngle = typeof v === "function" ? v : function() { return v; };
1408
+ return chord;
1409
+ };
1410
+
1411
+ return chord;
1412
+ }
1413
+
1414
1414
  }