@rws-framework/ai-tools 0.0.1

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 (82) hide show
  1. package/.bin/add-v.sh +10 -0
  2. package/.bin/emerge.sh +11 -0
  3. package/.emerge-vis-output/rws-server/emerge-file_result_dependency_graph.graphml +2067 -0
  4. package/.emerge-vis-output/rws-server/emerge-filesystem_graph.graphml +1505 -0
  5. package/.emerge-vis-output/rws-server/emerge-statistics-and-metrics.json +1 -0
  6. package/.emerge-vis-output/rws-server/emerge-statistics-metrics.txt +1147 -0
  7. package/.emerge-vis-output/rws-server/html/emerge.html +501 -0
  8. package/.emerge-vis-output/rws-server/html/jsconfig.json +9 -0
  9. package/.emerge-vis-output/rws-server/html/resources/css/custom.css +211 -0
  10. package/.emerge-vis-output/rws-server/html/resources/js/emerge_common.js +45 -0
  11. package/.emerge-vis-output/rws-server/html/resources/js/emerge_data.js +13 -0
  12. package/.emerge-vis-output/rws-server/html/resources/js/emerge_git.js +1414 -0
  13. package/.emerge-vis-output/rws-server/html/resources/js/emerge_graph.js +539 -0
  14. package/.emerge-vis-output/rws-server/html/resources/js/emerge_heatmap.js +220 -0
  15. package/.emerge-vis-output/rws-server/html/resources/js/emerge_hull.js +180 -0
  16. package/.emerge-vis-output/rws-server/html/resources/js/emerge_main.js +1003 -0
  17. package/.emerge-vis-output/rws-server/html/resources/js/emerge_search.js +71 -0
  18. package/.emerge-vis-output/rws-server/html/resources/js/emerge_ui.js +199 -0
  19. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/css/bootstrap-grid.css +4124 -0
  20. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/css/bootstrap-grid.css.map +1 -0
  21. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/css/bootstrap-grid.min.css +7 -0
  22. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/css/bootstrap-grid.min.css.map +1 -0
  23. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/css/bootstrap-grid.rtl.css +4123 -0
  24. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/css/bootstrap-grid.rtl.css.map +1 -0
  25. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/css/bootstrap-grid.rtl.min.css +7 -0
  26. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/css/bootstrap-grid.rtl.min.css.map +1 -0
  27. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/css/bootstrap-reboot.css +488 -0
  28. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/css/bootstrap-reboot.css.map +1 -0
  29. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/css/bootstrap-reboot.min.css +7 -0
  30. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/css/bootstrap-reboot.min.css.map +1 -0
  31. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/css/bootstrap-reboot.rtl.css +485 -0
  32. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/css/bootstrap-reboot.rtl.css.map +1 -0
  33. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/css/bootstrap-reboot.rtl.min.css +7 -0
  34. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/css/bootstrap-reboot.rtl.min.css.map +1 -0
  35. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/css/bootstrap-utilities.css +4266 -0
  36. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/css/bootstrap-utilities.css.map +1 -0
  37. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/css/bootstrap-utilities.min.css +7 -0
  38. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/css/bootstrap-utilities.min.css.map +1 -0
  39. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/css/bootstrap-utilities.rtl.css +4257 -0
  40. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/css/bootstrap-utilities.rtl.css.map +1 -0
  41. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/css/bootstrap-utilities.rtl.min.css +7 -0
  42. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/css/bootstrap-utilities.rtl.min.css.map +1 -0
  43. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/css/bootstrap.css +10878 -0
  44. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/css/bootstrap.css.map +1 -0
  45. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/css/bootstrap.min.css +7 -0
  46. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/css/bootstrap.min.css.map +1 -0
  47. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/css/bootstrap.rtl.css +10842 -0
  48. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/css/bootstrap.rtl.css.map +1 -0
  49. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/css/bootstrap.rtl.min.css +7 -0
  50. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/css/bootstrap.rtl.min.css.map +1 -0
  51. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/js/bootstrap.bundle.js +7075 -0
  52. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/js/bootstrap.bundle.js.map +1 -0
  53. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/js/bootstrap.bundle.min.js +7 -0
  54. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/js/bootstrap.bundle.min.js.map +1 -0
  55. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/js/bootstrap.esm.js +5202 -0
  56. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/js/bootstrap.esm.js.map +1 -0
  57. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/js/bootstrap.esm.min.js +7 -0
  58. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/js/bootstrap.esm.min.js.map +1 -0
  59. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/js/bootstrap.js +5249 -0
  60. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/js/bootstrap.js.map +1 -0
  61. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/js/bootstrap.min.js +7 -0
  62. package/.emerge-vis-output/rws-server/html/vendors/bootstrap/js/bootstrap.min.js.map +1 -0
  63. package/.emerge-vis-output/rws-server/html/vendors/d3/d3.v7.8.4.min.js +2 -0
  64. package/.emerge-vis-output/rws-server/html/vendors/d3/d3.v7.min.js +2 -0
  65. package/.emerge-vis-output/rws-server/html/vendors/dark-mode-switch/css/dark-mode.css +148 -0
  66. package/.emerge-vis-output/rws-server/html/vendors/dark-mode-switch/js/dark-mode-switch.min.js +1 -0
  67. package/.emerge-vis-output/rws-server/html/vendors/daterangepicker/daterangepicker.css +410 -0
  68. package/.emerge-vis-output/rws-server/html/vendors/daterangepicker/daterangepicker.min.js +8 -0
  69. package/.emerge-vis-output/rws-server/html/vendors/daterangepicker/moment.min.js +7 -0
  70. package/.emerge-vis-output/rws-server/html/vendors/hull/hull.js +373 -0
  71. package/.emerge-vis-output/rws-server/html/vendors/jquery/jquery-3.6.0.min.js +2 -0
  72. package/.emerge-vis-output/rws-server/html/vendors/popper/popper.min.js +6 -0
  73. package/.emerge-vis-output/rws-server/html/vendors/simpleheat/simpleheat.js +141 -0
  74. package/.eslintrc.json +53 -0
  75. package/README.md +862 -0
  76. package/package.json +49 -0
  77. package/src/index.ts +22 -0
  78. package/src/models/convo/ConvoLoader.ts +415 -0
  79. package/src/models/convo/VectorStore.ts +33 -0
  80. package/src/models/prompts/_prompt.ts +388 -0
  81. package/src/services/VectorStoreService.ts +15 -0
  82. package/tsconfig.json +24 -0
@@ -0,0 +1,1003 @@
1
+
2
+ // Some JS code is derived/borrowed or heavily inspired from demos/examples by the following people:
3
+ // Mike Bostock - https://github.com/mbostock, https://bost.ocks.org/mike/
4
+ // Tom Roth - https://bl.ocks.org/puzzler10/4438752bb93f45dc5ad5214efaa12e4a
5
+ // Ma'moun othman/@mamounothman - https://stackoverflow.com/questions/61800343/d3-js-version-5-chart-to-pdf
6
+ // Sam Leach/@SamuelLeach - https://gist.github.com/samuelleach/5497403
7
+ // Pranav C Balan - https://stackoverflow.com/questions/41287778/get-all-possible-object-keys-from-a-list-of-objects-javascript-typescript
8
+ // If I missed someone or gave wrong credit, please contact me and I'll update this.
9
+
10
+ const activeNodeLabelColor = hexToRGB("#333333", 0.6)
11
+ const passiveNodeLabelColor = hexToRGB("#333333", 0.2)
12
+ const dmActiveNodeLabelColor = hexToRGB("#DDDDDD", 0.6)
13
+ const dmPassiveNodeLabelColor = hexToRGB("#DDDDDD", 0.2)
14
+
15
+ const activeEdgeColor = hexToRGB("#AAAAAA", 1.0);
16
+ const passiveEdgeColor = hexToRGB("#AAAAAA", 0.2);
17
+ const dmActiveEdgeColor = hexToRGB("#888888", 0.8)
18
+ const dmPassiveEdgeColor = hexToRGB("#888888", 0.2)
19
+
20
+ const toolTipMetricItemTextColor = hexToRGB("#333333", 0.7);
21
+ const toolTipMetricItemBoxColor = hexToRGB("#333333", 1.0);
22
+ const toolTipMetricItemBoxFillColor = hexToRGB("#f7f7f7", 1.0);
23
+
24
+ const activeSelectionColor = '#FF0000'
25
+ const directoryNodeColor = '#3b8cff'
26
+ const fileNodeColor = '#d1e3ff'
27
+ const defaultNodeColor = '#1f77b4'
28
+ const semanticHeaderYellow = '#f5bc42'
29
+ const contributorsPurple = '#ff00ff'
30
+ const changeCouplingColor = '#ff0000'
31
+
32
+ let metricNameMap = {
33
+ "metric_ws_complexity_in_file" : "whitespace complexity",
34
+ "metric_number_of_methods_in_file" : "number of methods (file)",
35
+ "metric_number_of_methods_in_entity" : "number of methods (entity)",
36
+ "metric_sloc_in_file" : "source lines of code (file)",
37
+ "metric_sloc_in_entity" : "source lines of code (entity)",
38
+ "metric_fan_in_dependency_graph" : "fan in (dependency graph)",
39
+ "metric_fan_out_dependency_graph" : "fan out (dependency graph)",
40
+ "metric_fan_in_inheritance_graph" : "fan in (inheritance graph)",
41
+ "metric_fan_out_inheritance_graph" : "fan out (inheritance graph)",
42
+ "metric_fan_in_complete_graph" : "fan in (complete graph)",
43
+ "metric_fan_out_complete_graph" : "fan out (complete graph)",
44
+
45
+ //"metric_file_result_dependency_graph_louvain_modularity_in_file" : null,
46
+
47
+ "metric_git_code_churn" : "code churn (git)",
48
+ "metric_git_ws_complexity" : "whitespace complexity (git)",
49
+ "metric_git_sloc" : "source lines of code (git)",
50
+
51
+ // "metric_git_contributors" : "contributors (git)",
52
+
53
+ "metric_git_number_authors": "number of authors (git)",
54
+ "metric_fan_in_complete_graph" : "fan in (complete graph)",
55
+ "metric_fan_out_complete_graph" : "fan out (complete graph)"
56
+ }
57
+
58
+ /**
59
+ * * MARK: - Math constants
60
+ */
61
+ const TWO_TIMES_PI = 2 * Math.PI
62
+ const ONE_THIRD_TWO_TIMES_PI = (1.0 / 3.0) * TWO_TIMES_PI
63
+
64
+ /**
65
+ * * MARK: - UI workarounds
66
+ */
67
+ // workaround from https://stackoverflow.com/questions/6985507/one-time-page-refresh-after-first-page-load to fix strange safari full screen loading problems
68
+ let userAgent = navigator.userAgent.toLowerCase();
69
+ if (userAgent.includes('safari')) {
70
+ window.onload = function() {
71
+ if (!window.location.hash) {
72
+ window.location = window.location + '#loaded';
73
+ window.location.reload();
74
+ }
75
+ }
76
+ }
77
+
78
+ // Workaround to prevent buttons that trigger modals to stay focused after dismiss (https://stackoverflow.com/questions/30322918/bootstrap-modal-restores-button-focus-on-close)
79
+ $('body').on('hidden.bs.modal', '.modal', function() {
80
+ $('#buttonShowOverallMetrics').blur();
81
+ $('#buttonShowOverallStatistics').blur();
82
+ });
83
+
84
+ // cancel the node search by pressing the escape key
85
+ $(document).keyup(function(e) {
86
+ if (e.key === "Escape") {
87
+ cancelNodeSearch()
88
+ }
89
+ });
90
+
91
+ function setDarkMode(mode) {
92
+ if (mode == true) {
93
+ darkMode = true
94
+ currentActiveNodeLabelColor = dmActiveNodeLabelColor
95
+ currentPassiveNodeLabelColor = dmPassiveNodeLabelColor
96
+ currentActiveEdgeColor = dmActiveEdgeColor
97
+ currentPassiveEdgeColor = dmPassiveEdgeColor
98
+ simulationUpdate()
99
+ } else {
100
+ darkMode = false
101
+ currentActiveNodeLabelColor = activeNodeLabelColor
102
+ currentPassiveNodeLabelColor = passiveNodeLabelColor
103
+ currentActiveEdgeColor = activeEdgeColor
104
+ currentPassiveEdgeColor = passiveEdgeColor
105
+ simulationUpdate()
106
+ }
107
+ }
108
+
109
+ const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]')
110
+ const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl))
111
+
112
+ let heatmapMerged = false
113
+ let heatmapActive = false
114
+ let heatmapChurn = false
115
+ let heatmapHotspot = false
116
+
117
+ let selectedNodesMap = {}
118
+ var unselectedNodesOpacity = 0.20
119
+ let fadeUnselectedNodes = false
120
+ let currentActiveNodeLabelColor = activeNodeLabelColor
121
+ let currentPassiveNodeLabelColor = passiveNodeLabelColor
122
+
123
+ let currentActiveEdgeColor = activeEdgeColor
124
+ let currentPassiveEdgeColor = passiveEdgeColor
125
+
126
+ let dateRangePickerFrom = ""
127
+ let dateRangePickerTo = ""
128
+ let fileResultPrefix = ""
129
+ let fileResultPrefixFull = ""
130
+
131
+ let includeGitMetrics = false
132
+ let commit_dates = []
133
+ let commit_first_date = ""
134
+ let commit_last_date = ""
135
+
136
+ if (typeof commit_metrics !== 'undefined') {
137
+ fileResultPrefix = commit_metrics[0].file_result_prefix // TODO: pass/extract as/from extra common git metrics dict
138
+ fileResultPrefixFull = commit_metrics[0].file_result_prefix_full
139
+ commit_dates = commit_metrics.map(x => x.date);
140
+ commit_first_date = commit_dates[0]
141
+ commit_last_date = commit_dates[commit_dates.length - 1]
142
+ dateRangePickerFrom = commit_first_date
143
+ dateRangePickerTo = commit_last_date
144
+ includeGitMetrics = true
145
+ }
146
+
147
+ let gitMetricsIndexFrom = 0
148
+ let gitMetricsIndexTo = 0
149
+
150
+ let nodeColorMap = {}
151
+
152
+ const nodeStrokeStyle = "#333333";
153
+
154
+ let darkMode = false
155
+ let isSearching = false
156
+ let addSemanticSearch = false
157
+ let addContributorSearch = false
158
+
159
+ let hoverCoupling = false
160
+
161
+ let searchString = ""
162
+
163
+ let searchTerms = []
164
+ let searchResults = 0
165
+
166
+ const radius = 7;
167
+ const height = window.innerHeight * 2;
168
+ const graphWidth = window.innerWidth * 2;
169
+
170
+ const maxClusterHulls = 20
171
+ let selectedClusterHullIds = []
172
+ let hoveredClusterHullId = undefined
173
+
174
+ let nodeLabelsEnabled = false
175
+
176
+ let currentTranslation = {
177
+ horizontal: 0,
178
+ vertical: 0,
179
+ lastDirection: ""
180
+ }
181
+
182
+ let activeMetrics
183
+ let currentMetricKeys
184
+
185
+ let currentGraphType
186
+ let closeNode;
187
+
188
+ function setDirection(direction) {
189
+ currentTranslation.lastDirection = direction
190
+ }
191
+
192
+ function toggleNodeLabels() {
193
+ nodeLabelsEnabled = !nodeLabelsEnabled
194
+ simulationUpdate()
195
+ }
196
+
197
+ let zoom_handler = d3.zoom()
198
+ .on("zoom", zoomed);
199
+
200
+ let graphCanvas = d3.select('#graphDiv').append('canvas')
201
+ .attr('width', graphWidth + 'px')
202
+ .attr('height', height + 'px')
203
+ .attr('id', 'mainCanvas')
204
+ .node();
205
+
206
+ let graphData = {}
207
+ let currentGraph = ''
208
+
209
+ let clusterMap = {}
210
+ let clusterMetricsMap = {}
211
+
212
+ let statistics
213
+ let overall_metric_results
214
+
215
+ initAppConfig()
216
+ initHeatmapSwitches()
217
+
218
+ initHoverCouplingSwitch()
219
+
220
+ initSemanticSearchSwitch()
221
+ initContributorSearchSwitch()
222
+
223
+ setInitialDarkMode()
224
+ prepareGraphStructures()
225
+ initAppUI()
226
+
227
+ let context = graphCanvas.getContext('2d');
228
+
229
+ // nice fix from https://stackoverflow.com/questions/8696631/canvas-drawings-like-lines-are-blurry to remove blurry drawing
230
+ graphCanvas.style.height = (height / 2) + "px";
231
+ graphCanvas.style.width = (graphWidth / 2) + "px";
232
+ graphCanvas.getContext('2d').scale(2, 2);
233
+
234
+ let div = d3.select("body").append("div")
235
+ .attr("class", "tooltip")
236
+ .style("opacity", 0);
237
+
238
+ let currentChargeForce = -500
239
+ let currentLinkDistance = 20
240
+ let simulation
241
+
242
+ let transform = d3.zoomIdentity;
243
+
244
+ // heatmap
245
+ var heat = simpleheat('mainCanvas');
246
+
247
+ // bring back d3 schemeCategory20 to live
248
+ const schemeCategory20 = "1f77b4aec7e8ff7f0effbb782ca02c98df8ad62728ff98969467bdc5b0d58c564bc49c94e377c2f7b6d27f7f7fc7c7c7bcbd22dbdb8d17becf9edae5"
249
+
250
+ function d3ColorExport(specifier) {
251
+ let n = specifier.length / 6 | 0,
252
+ colors = new Array(n),
253
+ i = 0;
254
+ while (i < n) colors[i] = "#" + specifier.slice(i * 6, ++i * 6);
255
+ return colors;
256
+ }
257
+
258
+ // important to define the domain for the ordinal scale
259
+ const color = d3.scaleOrdinal(d3ColorExport(schemeCategory20))
260
+ .domain([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19])
261
+
262
+ function initModals() {
263
+ const modal = document.getElementById('timeSeriesModal')
264
+ modal.addEventListener('hidden.bs.modal', event => {
265
+ d3.select("#timeSeriesComplexityChart").remove();
266
+ d3.select("#timeSeriesChurnChart").remove();
267
+ d3.select("#timeSeriesSlocChart").remove();
268
+ })
269
+
270
+ const chordModal = document.getElementById('changeCouplingModal')
271
+ chordModal.addEventListener('hidden.bs.modal', event => {
272
+ d3.select("#svg_change_coupling_chord_diagram").remove();
273
+ })
274
+ }
275
+
276
+ function setInitialDarkMode() {
277
+ if (document.body.getAttribute("data-theme") == "dark") {
278
+ currentActiveNodeLabelColor = dmActiveNodeLabelColor
279
+ currentPassiveNodeLabelColor = dmPassiveNodeLabelColor
280
+ currentActiveEdgeColor = dmActiveEdgeColor
281
+ currentPassiveEdgeColor = dmPassiveEdgeColor
282
+ darkMode = true
283
+ } else {
284
+ currentActiveNodeLabelColor = activeNodeLabelColor
285
+ currentPassiveNodeLabelColor = passiveNodeLabelColor
286
+ currentActiveEdgeColor = activeEdgeColor
287
+ currentPassiveEdgeColor = passiveEdgeColor
288
+ darkMode = false
289
+ }
290
+ }
291
+
292
+ /**
293
+ * * MARK: - Translations on canvas
294
+ */
295
+ function translateCanvas(direction) {
296
+ switch (direction) {
297
+ case 'left':
298
+ transform.x += 100
299
+ simulationUpdate()
300
+ break;
301
+
302
+ case 'down':
303
+ transform.y -= 100
304
+ simulationUpdate()
305
+ break;
306
+
307
+ case 'right':
308
+ transform.x -= 100
309
+ simulationUpdate()
310
+ break;
311
+
312
+ case 'up':
313
+ transform.y += 100
314
+ simulationUpdate()
315
+ break;
316
+ }
317
+ }
318
+
319
+ /**
320
+ * * MARK: - Adjusting style
321
+ */
322
+
323
+ function unselectedOpacityChange(val) {
324
+ unselectedNodesOpacity = val / 100.0
325
+ simulationUpdate()
326
+ }
327
+
328
+ /**
329
+ * * MARK: - Zooming and scaling on canvas
330
+ */
331
+ function zoomed(event) {
332
+ transform = event.transform;
333
+ simulationUpdate();
334
+ }
335
+
336
+ function zoomIn() {
337
+ d3.select(graphCanvas)
338
+ .call(zoom_handler.scaleBy, 2)
339
+ .call(d3.zoom().scaleExtent([1 / 10, 8]).on("zoom", zoomed))
340
+ simulationUpdate()
341
+ }
342
+
343
+ function zoomOut() {
344
+ d3.select(graphCanvas)
345
+ .call(zoom_handler.scaleBy, 0.5)
346
+ .call(d3.zoom().scaleExtent([1 / 10, 8]).on("zoom", zoomed))
347
+ simulationUpdate()
348
+ }
349
+
350
+ startWithGraph('file_result_dependency_graph')
351
+
352
+ zoomOut() // initialliy zoom out a bit
353
+
354
+ /**
355
+ * * MARK: - Called on startup an d every time you change a graph
356
+ */
357
+ function startWithGraph(graphType, chargeForce = currentChargeForce, linkDistance = currentLinkDistance) {
358
+
359
+ activeMetrics = []
360
+ currentMetricKeys = []
361
+
362
+ currentGraphType = graphType
363
+
364
+ currentLinkDistance = linkDistance
365
+ currentChargeForce = chargeForce
366
+ currentLinkDistance = linkDistance
367
+
368
+ resetSimulationData()
369
+
370
+ currentGraph = JSON.parse(JSON.stringify(graphData[graphType]['graph']))
371
+ statistics = graphData[graphType]['statistics']
372
+ overall_metric_results = graphData[graphType]['overall_metric_results']
373
+ clusterMetricsMap = graphData[graphType]['cluster_metrics_map']
374
+
375
+ addGitMetricToFileNodes()
376
+
377
+ createStatistics();
378
+ createOverallMetricResults();
379
+
380
+ currentGraph.nodes.forEach(function(d, i) {
381
+ d.radius = radius
382
+
383
+ if (!d.hasOwnProperty('metrics')) {
384
+ d.metrics = {}
385
+ }
386
+
387
+ for (let key in d) {
388
+
389
+ if (key.includes('metric_')) {
390
+ d.metrics[key] = d[key]
391
+ if (!currentMetricKeys.includes(key)) {
392
+ // do not include tag/tfidf metrics in the 'apply metrics' dropDown menu
393
+ if (!key.includes('metric_tag')) {
394
+ currentMetricKeys.push(key)
395
+ }
396
+ }
397
+ }
398
+ }
399
+ });
400
+
401
+ setupGraphClustersById();
402
+
403
+ createClusterHullMenu();
404
+ createMetricsMenuEntries();
405
+
406
+ updateAppUI()
407
+
408
+ initNodeColorMap();
409
+
410
+ enableSearchInput();
411
+ enableNodeSelection();
412
+
413
+ addToolTipsToMetricEntries();
414
+ addTooltipToHoverCoupling();
415
+ addTooltipToContributorSearch();
416
+ addToolTipsToHeatMap();
417
+ addToolTipToShortcuts();
418
+ addTooltipUnselectedOpacity();
419
+ addTooltipSemanticSearch();
420
+ addTooltipClusterHulls();
421
+
422
+ currentGraph.links.forEach((d) => {
423
+ linkedByIndex[`${d.source},${d.target}`] = true;
424
+ });
425
+
426
+ simulation = d3.forceSimulation()
427
+ .force("center", d3.forceCenter(graphWidth / 4, height / 4))
428
+ .force("x", d3.forceX(graphWidth / 2).strength(0.1))
429
+ .force("y", d3.forceY(height / 2).strength(0.1))
430
+ .force("charge", d3.forceManyBody().strength(currentChargeForce))
431
+ .force("link", d3.forceLink()
432
+ .strength(1)
433
+ .distance(currentLinkDistance)
434
+ .id(function(d) {
435
+ return d.id;
436
+ }))
437
+ .alphaTarget(0)
438
+ .alphaDecay(0.05)
439
+
440
+ addGraphTypeSelectionToMenu()
441
+
442
+ addTooltipProjectInfo();
443
+ addTooltipGraphInfo();
444
+
445
+ d3.select(graphCanvas)
446
+ .call(d3.drag().subject(dragsubject).on("start", dragstarted).on("drag", dragged).on("end", dragended))
447
+ .call(d3.zoom().scaleExtent([1 / 10, 8]).on("zoom", zoomed))
448
+
449
+ function dragsubject(event) {
450
+
451
+ let i,
452
+ x = transform.invertX(event.x),
453
+ y = transform.invertY(event.y),
454
+ dx,
455
+ dy;
456
+
457
+ for (i = currentGraph.nodes.length - 1; i >= 0; --i) {
458
+ node = currentGraph.nodes[i];
459
+ dx = x - node.x;
460
+ dy = y - node.y;
461
+
462
+ if (dx * dx + dy * dy < radius * radius) {
463
+
464
+ node.x = transform.applyX(node.x);
465
+ node.y = transform.applyY(node.y);
466
+
467
+ return node;
468
+ }
469
+ }
470
+ }
471
+
472
+ function dragstarted(event) {
473
+ if (!event.active) simulation.alphaTarget(0.3).restart();
474
+ event.subject.fx = transform.invertX(event.x);
475
+ event.subject.fy = transform.invertY(event.y);
476
+ }
477
+
478
+ function dragged(event) {
479
+ event.subject.fx = transform.invertX(event.x);
480
+ event.subject.fy = transform.invertY(event.y);
481
+ }
482
+
483
+ function dragended(event) {
484
+ if (!event.active) simulation.alphaTarget(0);
485
+ event.subject.fx = null;
486
+ event.subject.fy = null;
487
+ }
488
+
489
+ d3.select("canvas").on("mousemove", (event) => {
490
+ let p = d3.pointer(event);
491
+ let invX = transform.invertX(p[0])
492
+ let invY = transform.invertY(p[1])
493
+
494
+ let foundNode = simulation.find(transform.invertX(p[0]), transform.invertY(p[1]));
495
+
496
+ // check within a close area if hovered point is nearby of foundNode
497
+ if ((Math.abs(foundNode.x - invX) < radius) && (Math.abs(foundNode.y - invY) < radius)) {
498
+ closeNode = simulation.find(transform.invertX(p[0]), transform.invertY(p[1]));
499
+ } else {
500
+ closeNode = null
501
+ }
502
+
503
+ simulationUpdate();
504
+ })
505
+
506
+ simulation.nodes(currentGraph.nodes)
507
+ .on("tick", simulationUpdate);
508
+
509
+ simulation.force("link")
510
+ .links(currentGraph.links);
511
+ }
512
+
513
+ // Based on https://bl.ocks.org/jodyphelan/5dc989637045a0f48418101423378fbd
514
+ function simulationUpdate() {
515
+
516
+ context.save();
517
+
518
+ context.clearRect(0, 0, graphWidth, height);
519
+ context.translate(transform.x, transform.y);
520
+ context.scale(transform.k, transform.k);
521
+
522
+ // draw heatmap
523
+ if (mergedHeatmapIsActive() || normalHeatmapIsActive() || churnHeatmapIsActive() || hotspotHeatmapIsActive()) {
524
+ drawHeatMap(context)
525
+ }
526
+
527
+ // this is pretty cpu hungry
528
+ drawHulls(context)
529
+
530
+ // draw edges
531
+ drawEdges(context)
532
+
533
+ // Draw nodes
534
+ drawNodes(context)
535
+
536
+ context.restore();
537
+ }
538
+
539
+ /**
540
+ * * MARK: - Handling of data structures
541
+ */
542
+ function prepareGraphStructures() {
543
+ if (typeof file_result_dependency_graph !== 'undefined') {
544
+ graphData['file_result_dependency_graph'] = {}
545
+ graphData['file_result_dependency_graph']['graph'] = file_result_dependency_graph
546
+ graphData['file_result_dependency_graph']['statistics'] = file_result_dependency_graph_statistics
547
+ graphData['file_result_dependency_graph']['overall_metric_results'] =
548
+ file_result_dependency_graph_overall_metric_results
549
+ graphData['file_result_dependency_graph']['cluster_metrics_map'] = file_result_dependency_graph_cluster_metrics_map
550
+ currentGraphType = 'file_result_dependency_graph'
551
+ }
552
+
553
+ if (typeof entity_result_dependency_graph !== 'undefined') {
554
+ graphData['entity_result_dependency_graph'] = {}
555
+ graphData['entity_result_dependency_graph']['graph'] = entity_result_dependency_graph
556
+ graphData['entity_result_dependency_graph']['statistics'] = entity_result_dependency_graph_statistics
557
+ graphData['entity_result_dependency_graph']['overall_metric_results'] =
558
+ entity_result_dependency_graph_overall_metric_results
559
+ graphData['entity_result_dependency_graph']['cluster_metrics_map'] = entity_result_dependency_graph_cluster_metrics_map
560
+ currentGraphType = 'entity_result_dependency_graph'
561
+ }
562
+
563
+ if (typeof entity_result_inheritance_graph !== 'undefined') {
564
+ graphData['entity_result_inheritance_graph'] = {}
565
+ graphData['entity_result_inheritance_graph']['graph'] = entity_result_inheritance_graph
566
+ graphData['entity_result_inheritance_graph']['statistics'] = entity_result_inheritance_graph_statistics
567
+ graphData['entity_result_inheritance_graph']['overall_metric_results'] =
568
+ entity_result_inheritance_graph_overall_metric_results
569
+ graphData['entity_result_inheritance_graph']['cluster_metrics_map'] = entity_result_inheritance_graph_cluster_metrics_map
570
+ }
571
+
572
+ if (typeof entity_result_complete_graph !== 'undefined') {
573
+ graphData['entity_result_complete_graph'] = {}
574
+ graphData['entity_result_complete_graph']['graph'] = entity_result_complete_graph
575
+ graphData['entity_result_complete_graph']['statistics'] = entity_result_complete_graph_statistics
576
+ graphData['entity_result_complete_graph']['overall_metric_results'] =
577
+ entity_result_complete_graph_overall_metric_results
578
+ graphData['entity_result_complete_graph']['cluster_metrics_map'] = entity_result_complete_graph_cluster_metrics_map
579
+ }
580
+
581
+ if (typeof filesystem_graph !== 'undefined') {
582
+ graphData['filesystem_graph'] = {}
583
+ graphData['filesystem_graph']['graph'] = filesystem_graph
584
+ graphData['filesystem_graph']['statistics'] = filesystem_graph_statistics
585
+ graphData['filesystem_graph']['overall_metric_results'] = filesystem_graph_overall_metric_results
586
+ graphData['filesystem_graph']['cluster_metrics_map'] = filesystem_graph_cluster_metrics_map
587
+ }
588
+ }
589
+
590
+ function resetSimulationData() {
591
+ if (simulation !== undefined) {
592
+ simulation.stop()
593
+ }
594
+
595
+ metricKeys = nodesData = linksData = []
596
+ simulation = undefined
597
+ }
598
+
599
+ // based on https://bocoup.com/blog/smoothly-animate-thousands-of-points-with-html5-canvas-and-d3 / Peter Beshai
600
+ // basically animates the increase/decrease of the node radius based on chosen metrics
601
+ function animateRadiusWithMetric(metricName) {
602
+
603
+ let addedMetric = true
604
+ if (activeMetrics.includes(metricName)) {
605
+ removeItemAll(activeMetrics, metricName)
606
+ addedMetric = false
607
+ } else {
608
+ activeMetrics.push(metricName)
609
+ addedMetric = true
610
+ }
611
+
612
+ // console.log(metricName)
613
+ const duration = 250;
614
+ const ease = d3.easeCubic;
615
+
616
+ timer = d3.timer((elapsed) => {
617
+ // compute how far through the animation we are (0 to 1)
618
+ const t = Math.min(1, ease(elapsed / duration));
619
+
620
+ // update point positions (interpolate between source and target)
621
+ currentGraph.nodes.forEach(node => {
622
+ if (metricName in node.metrics) {
623
+ // this resets all nodes back to the default radius
624
+ let newRadius = 0
625
+
626
+ // now interpolate for every x between f(0) and f(1): f(x) = f(0) * (1-x) + f(1) * x
627
+ if (addedMetric) {
628
+ newRadius = node.radius * (1 - t) + (node.radius + (node.metrics[metricName] * analysis_config['metrics']['radius_multiplication'][metricName] )) * t;
629
+
630
+ if (newRadius > node.radius) {
631
+ node.radius = newRadius
632
+ }
633
+
634
+ } else {
635
+ newRadius = node.radius * (1 - t) + (node.radius - (node.metrics[metricName] * analysis_config['metrics']['radius_multiplication'][metricName] )) * t;
636
+ if (newRadius > radius) {
637
+ node.radius = newRadius
638
+ } else {
639
+ node.radius = radius
640
+ }
641
+ }
642
+ }
643
+ });
644
+
645
+ // if this animation is over
646
+ if (t === 1) {
647
+ // always make sure that node sizes return to default if no metric is active
648
+ if (activeMetrics.length == 0) {
649
+ currentGraph.nodes.forEach(node => {
650
+ node.radius = radius
651
+ })
652
+ }
653
+
654
+ // stop this timer since we are done animating.
655
+ timer.stop();
656
+ }
657
+
658
+ // update what is drawn on screen
659
+ simulationUpdate();
660
+ });
661
+ }
662
+
663
+ /**
664
+ * * MARK: - Create/update the HTML/Bootstrap UI
665
+ */
666
+
667
+ function cancelNodeSearch() {
668
+ $('#inputNodeSearch').val('')
669
+ $('#inputNodeSearchLabel').text('Search inactive')
670
+ searchString = ""
671
+
672
+ searchTerms = []
673
+
674
+ isSearching = false
675
+ simulationUpdate()
676
+ }
677
+
678
+ // setup keyboard shortcut keys for node selection
679
+ // shift + 's' key: select/unselect node
680
+ // shift + 'e' key: expand selected nodes level deeper
681
+ // shift + 'h' key: expand hovered nodes level deeper
682
+ // shift + 'r' key: reset current selection
683
+ // shift + 'f' key: fade unselected nodes
684
+ function enableNodeSelection() {
685
+ let keySelectUnselect = 'S'
686
+ let keyExpandSelection = 'E'
687
+ let keyExpandHoveredNode = 'H'
688
+ let keyResetCurrentSelection = 'R'
689
+ let keyFadeUnselectedNodes = 'F'
690
+
691
+ d3.select('body')
692
+ .on("keydown", function(event) {
693
+
694
+ if (event.key == keySelectUnselect) {
695
+ if (closeNode != null) {
696
+ if (closeNode.id.toLowerCase() in selectedNodesMap) {
697
+ delete selectedNodesMap[closeNode.id.toLowerCase()]
698
+ } else {
699
+ selectedNodesMap[closeNode.id.toLowerCase()] = true
700
+ }
701
+ simulationUpdate()
702
+ }
703
+ }
704
+
705
+ if (event.key == keyExpandSelection) {
706
+ if (selectedNodesMap.length != 0) {
707
+ let newSelectedNodesMap = {...selectedNodesMap}
708
+ currentGraph.links.forEach(function(d) {
709
+ if (d.source.id.toLowerCase() in selectedNodesMap || d.target.id.toLowerCase() in selectedNodesMap) {
710
+ newSelectedNodesMap[d.source.id.toLowerCase()] = true
711
+ newSelectedNodesMap[d.target.id.toLowerCase()] = true
712
+ }
713
+ })
714
+ selectedNodesMap = newSelectedNodesMap
715
+ simulationUpdate()
716
+ }
717
+ }
718
+
719
+ if (event.key == keyExpandHoveredNode) {
720
+ if (closeNode != null) {
721
+ selectedNodesMap[closeNode.id.toLowerCase()] = true
722
+ let newSelectedNodesMap = {...selectedNodesMap}
723
+ currentGraph.links.forEach(function(d) {
724
+ if (d.source.id == closeNode.id || d.target.id == closeNode.id) {
725
+ newSelectedNodesMap[d.source.id.toLowerCase()] = true
726
+ newSelectedNodesMap[d.target.id.toLowerCase()] = true
727
+ }
728
+ })
729
+ selectedNodesMap = newSelectedNodesMap
730
+ simulationUpdate()
731
+ }
732
+ }
733
+
734
+ if (event.key == keyResetCurrentSelection) {
735
+ selectedNodesMap = {}
736
+ simulationUpdate()
737
+ }
738
+
739
+ if (event.key == keyFadeUnselectedNodes) {
740
+ fadeUnselectedNodes = !fadeUnselectedNodes
741
+ if (fadeUnselectedNodes == true) {
742
+ $("#li-unselected-opacity").removeClass('d-none');
743
+ $("#unselected-opacity").removeClass('d-none');
744
+ $('#fadeUnselectedNodesLabelText').html('<b>f fading unselected nodes</b>')
745
+
746
+ } else {
747
+ $("#li-unselected-opacity").addClass('d-none');
748
+ $("#unselected-opacity").addClass('d-none');
749
+ $('#fadeUnselectedNodesLabelText').html('<b>f</b> fade unselected nodes')
750
+ }
751
+ simulationUpdate()
752
+ }
753
+ });
754
+ }
755
+
756
+ function enableSearchInput() {
757
+ $('#inputNodeSearchLabel').text('Search inactive')
758
+ $('#inputNodeSearch').on('keyup change', function() {
759
+ searchString = $(this).val().toLowerCase()
760
+
761
+ searchTerms = searchString.split(" ")
762
+ searchTerms = searchTerms.filter(Boolean);
763
+ // console.log(searchTerms)
764
+
765
+ searchResults = 0
766
+ if (searchString.length > 0 && searchTerms.length > 0) {
767
+ isSearching = true
768
+ simulationUpdate()
769
+ $('#inputNodeSearchLabel').text(searchResults + ' nodes found')
770
+
771
+ } else {
772
+ isSearching = false
773
+ simulationUpdate()
774
+ $('#inputNodeSearchLabel').text('Search inactive')
775
+ }
776
+ })
777
+
778
+ $('#inputNodeSearchCancel').on('click', function() {
779
+ cancelNodeSearch()
780
+ })
781
+ }
782
+
783
+ function createStatistics() {
784
+ // create statistics table
785
+ statistics_html = ""
786
+ for (let key in statistics) {
787
+ if (statistics.hasOwnProperty(key)) {
788
+ statistics_html += "<tr>"
789
+
790
+ statistics_html += "<td>"
791
+ statistics_html += key
792
+ statistics_html += "</td>"
793
+
794
+ statistics_html += "<td>"
795
+ statistics_html += statistics[key]
796
+ statistics_html += "</td>"
797
+
798
+ statistics_html += "</tr>"
799
+ }
800
+ }
801
+ d3.select("#tbody-statistics").html(statistics_html)
802
+ }
803
+
804
+ function createOverallMetricResults() {
805
+ // create metrics table
806
+ metrics_html = ""
807
+ for (let key in overall_metric_results) {
808
+ if (overall_metric_results.hasOwnProperty(key)) {
809
+ metrics_html += "<tr>"
810
+
811
+ metrics_html += "<td>"
812
+ metrics_html += key
813
+ metrics_html += "</td>"
814
+
815
+ metrics_html += "<td>"
816
+
817
+ let valueString = String(overall_metric_results[key])
818
+ if (valueString.length > 30) {
819
+ valueString = valueString.substring(0, 32) + '...';
820
+ }
821
+
822
+ metrics_html += String(valueString)
823
+ metrics_html += "</td>"
824
+
825
+ metrics_html += "</tr>"
826
+ }
827
+ }
828
+ d3.select("#tbody-metrics").html(metrics_html)
829
+ }
830
+
831
+ function createMetricsMenuEntries() {
832
+ // create apply metrics menu entries
833
+ applyMetricHtml = ""
834
+
835
+ console.log(currentMetricKeys)
836
+
837
+ for (let key in currentMetricKeys) {
838
+
839
+ if ( Object.keys(metricNameMap).includes(currentMetricKeys[key]) ) {
840
+
841
+ applyMetricHtml += '<li> &nbsp; <input data-value="'
842
+ applyMetricHtml += currentMetricKeys[key]
843
+ applyMetricHtml += '" type="checkbox" onclick="animateRadiusWithMetric(\''
844
+ applyMetricHtml += currentMetricKeys[key]
845
+ applyMetricHtml += '\');"/>&nbsp; <span id="'
846
+ applyMetricHtml += '" style="font-size:10px;">'
847
+
848
+ let visibleMetricName = metricNameMap[currentMetricKeys[key]]
849
+
850
+ applyMetricHtml += visibleMetricName
851
+ applyMetricHtml += '</span> <small><span id="'
852
+ applyMetricHtml += 'badge_' + currentMetricKeys[key]
853
+ applyMetricHtml += '" data-bs-toggle="tooltip" data-bs-placement="bottom" title="" class="badge badge-primary badge-pill text-bg-primary"> ?</span> </small> &nbsp;</li>'
854
+ }
855
+ }
856
+
857
+ d3.select("#dropdown-apply-metric").html(applyMetricHtml)
858
+ }
859
+
860
+ function getAnalysisName() {
861
+ let analysisName = analysis_config['analysis_name']
862
+ if (analysisName.length > 24) {
863
+ analysisName = analysisName.slice(0, 24) + '...'
864
+ }
865
+ return analysisName
866
+ }
867
+
868
+ function addGraphTypeSelectionToMenu() {
869
+ graphSelectHtml = ""
870
+ for (let key in graphData) {
871
+ graphSelectHtml += '<button class="dropdown-item btn-sm" style="font-size: 10px;" type="button" onclick="startWithGraph(\''
872
+ graphSelectHtml += key
873
+ graphSelectHtml += '\');">'
874
+ graphSelectHtml += key.replace(/_/gi, " ")
875
+ graphSelectHtml += "</button>"
876
+ }
877
+
878
+ d3.select("#dropdown-graph").html(graphSelectHtml)
879
+ d3.select("#selectedGraph").text(currentGraphType.replace(/_/gi, " ").slice(0, 20) + '...')
880
+ }
881
+
882
+ function increaseCurrentChargeForce() {
883
+ if (currentChargeForce < -50) {
884
+ currentChargeForce += 50
885
+ d3.select("#chargeForce").text(currentChargeForce)
886
+ simulation.force("charge", d3.forceManyBody().strength(currentChargeForce))
887
+ simulation.alpha(1).restart();
888
+ }
889
+ }
890
+
891
+ function decreaseCurrentChargeForce() {
892
+ currentChargeForce -= 50
893
+ d3.select("#chargeForce").text(currentChargeForce)
894
+ simulation.force("charge", d3.forceManyBody().strength(currentChargeForce))
895
+ simulation.alpha(1).restart();
896
+ }
897
+
898
+ /**
899
+ * * MARK: - heatmap
900
+ */
901
+
902
+ function normalHeatmapIsActive() { return heatmapActive }
903
+ function mergedHeatmapIsActive() { return heatmapMerged }
904
+ function churnHeatmapIsActive() { return heatmapChurn }
905
+ function hotspotHeatmapIsActive() { return heatmapHotspot }
906
+
907
+ function initHoverCouplingSwitch() {
908
+ $("#switchHoverCoupling").on('change', function() {
909
+ hoverCoupling = $(this).is(':checked');
910
+ simulationUpdate();
911
+ })
912
+ }
913
+
914
+ function initSemanticSearchSwitch() {
915
+ $("#switchAddSemanticSearch").on('change', function() {
916
+ addSemanticSearch = $(this).is(':checked');
917
+ searchResults = 0
918
+ simulationUpdate();
919
+ $('#inputNodeSearchLabel').text(searchResults + ' nodes found')
920
+ })
921
+ }
922
+
923
+ function initContributorSearchSwitch() {
924
+ $("#switchAddContributorSearch").on('change', function() {
925
+ addContributorSearch = $(this).is(':checked');
926
+ searchResults = 0
927
+ simulationUpdate();
928
+ $('#inputNodeSearchLabel').text(searchResults + ' nodes found')
929
+ })
930
+ }
931
+
932
+ function initAppConfig() {
933
+ $('#configEmergeVersion').text('Emerge ' + analysis_config['emerge_version'])
934
+ }
935
+
936
+ function updateAppUI() {
937
+ if (fadeUnselectedNodes == true) {
938
+ $("#li-unselected-opacity").removeClass('d-none');
939
+ $("#unselected-opacity").removeClass('d-none');
940
+ } else {
941
+ $("#li-unselected-opacity").addClass('d-none');
942
+ $("#unselected-opacity").addClass('d-none');
943
+ }
944
+
945
+ // currently only show git functionality for file dependency graphs
946
+ if (currentGraphType.includes('file_result_dependency_graph') && includeGitMetrics) {
947
+ $("#formSwitchChurnHeatmap").removeClass('d-none');
948
+ $("#formSwitchHotspotHeatmap").removeClass('d-none');
949
+ $("#button_complexity_churn").removeClass('d-none');
950
+ $("#button_change_coupling").removeClass('d-none');
951
+ $("#container_git_settings").removeClass('d-none');
952
+ $("#formSwitchHoverCoupling").removeClass('d-none');
953
+ $("#formSwitchAddContributorSearch").removeClass('d-none')
954
+ } else {
955
+ $("#formSwitchChurnHeatmap").addClass('d-none');
956
+ $("#formSwitchHotspotHeatmap").addClass('d-none');
957
+ $("#button_complexity_churn").addClass('d-none');
958
+ $("#button_change_coupling").addClass('d-none');
959
+ $("#container_git_settings").addClass('d-none');
960
+ $("#formSwitchHoverCoupling").addClass('d-none');
961
+ $("#formSwitchAddContributorSearch").addClass('d-none');
962
+ }
963
+ }
964
+
965
+ function initAppUI() {
966
+ updateAppUI()
967
+ if (includeGitMetrics) {
968
+ initGitMetricsForDateRange()
969
+ initDateRangeUI()
970
+ }
971
+
972
+ initModals()
973
+ }
974
+
975
+ /**
976
+ * * MARK: - Helper functions
977
+ */
978
+
979
+ // borrowed from https://stackoverflow.com/questions/5767325/how-can-i-remove-a-specific-item-from-an-array
980
+ function removeItemAll(arr, value) {
981
+ let i = 0;
982
+ while (i < arr.length) {
983
+ if (arr[i] === value) {
984
+ arr.splice(i, 1);
985
+ } else {
986
+ ++i;
987
+ }
988
+ }
989
+ return arr;
990
+ }
991
+
992
+ //https://stackoverflow.com/questions/21646738/convert-hex-to-rgba
993
+ function hexToRGB(hex, alpha) {
994
+ let r = parseInt(hex.slice(1, 3), 16),
995
+ g = parseInt(hex.slice(3, 5), 16),
996
+ b = parseInt(hex.slice(5, 7), 16);
997
+
998
+ if (alpha) {
999
+ return "rgba(" + r + ", " + g + ", " + b + ", " + alpha + ")";
1000
+ } else {
1001
+ return "rgb(" + r + ", " + g + ", " + b + ")";
1002
+ }
1003
+ }