@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,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
+
1414
+ }