-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.html
302 lines (249 loc) · 10.1 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
<html>
<meta charset="UTF-8">
<title>desipartart | Design Analysis by VCS</title>
<head>
<script src="lib/visjs/vis-network/7.3.6/dist/vis-network.js"></script>
<link href="lib/visjs/vis-network/7.3.6/dist/vis-network.min.css" rel="stylesheet" type="text/css"/>
<script src="lib/plotly/plotly-latest.min.js"></script>
<style type="text/css">
#mynetwork {
width: 800px;
height: 600px;
border: 2px solid lightgray;
}
</style>
<script src="desipartart.js"></script>
<script>
'use strict';
function visJsDemo() {
// create an array with nodes
const nodes = new vis.DataSet([
{ id: 1, label: 'Node 1' },
{ id: 2, label: 'Node 2' },
{ id: 3, label: 'Node 3' },
{ id: 4, label: 'Node 4' },
{ id: 5, label: 'Node 5' }
]);
// create an array with edges
const edges = new vis.DataSet([
{ from: 1, to: 3, width: 1 },
{ from: 1, to: 2, width: 6 },
{ from: 2, to: 4, width: 1 },
{ from: 2, to: 5, width: 3 },
{ from: 2, to: 3, width: 1 },
]);
// create a network
const container = document.getElementById('mynetwork');
const data = { nodes, edges };
const options = {};
const network = new vis.Network(container, data, options);
}
function getSetSeed() {
const useSeedInput = document.getElementById('useSeed');
if (useSeedInput.checked) {
const seedInput = document.getElementById('seed');
return parseInt(seedInput.value, 10);
}
return NaN;
}
function updateSeed(seed) {
console.log('Seed used:' + seed);
const seedInput = document.getElementById('seed');
seedInput.value = seed;
}
function displayChanges(hackedGraph) {
console.log(hackedGraph);
// TODO: ICM 2020-02-05: Can we use a map transformation?
const rawNodes = [];
hackedGraph.nodesByFile.forEach(function (value, key) {
rawNodes.push({ id: value.id, label: AYESEEEM.desipartart.filenameOf(value.name) });
});
console.log(rawNodes);
// create an array with nodes
const nodes = new vis.DataSet(rawNodes);
// Convert weight to width
const rawEdges = [];
hackedGraph.edges.forEach(function (e) {
rawEdges.push({ from: e.from, to: e.to, width: e.weight });
});
console.log(rawEdges);
// create an array with edges
const edges = new vis.DataSet(rawEdges);
// create a network
const container = document.getElementById('mynetwork');
const data = { nodes, edges };
const options = {};
const seed = getSetSeed();
if (seed) {
options.layout = { randomSeed: seed };
}
const network = new vis.Network(container, data, options);
updateSeed(network.getSeed());
}
function findMaxWeight(hackedGraph) {
const groupedByWeightAsc = AYESEEEM.desipartart.edgesGroupedByWeight(hackedGraph);
// TODO: ICM 2020-03-08: Would a better way be to sort the edges directly?
const uniqueWeights = [...groupedByWeightAsc.keys()];
console.log('uniqueWeights');
console.log(uniqueWeights);
// assert - sorted in asc order
// So last value is max
const mostBoundFilesWeight = uniqueWeights[uniqueWeights.length - 1];
console.log('mostBoundFilesWeight');
console.log(mostBoundFilesWeight);
return mostBoundFilesWeight;
}
function empty(domElement) {
while (domElement.firstChild) {
domElement.firstChild.remove();
}
}
function listFilesBoundWithWeight(groupedByWeight, weight) {
const weightElement = document.getElementById('bound-weight');
weightElement.textContent = weight;
const list = document.getElementById('most-bound');
empty(list);
const boundFilesByWeight = groupedByWeight.get(weight);
console.log('boundFilesByWeight');
console.log(boundFilesByWeight);
// There can be multiple edges with the given weight
boundFilesByWeight.forEach(function (edge) {
const item = document.createElement('li');
// TODO: ICM 2020-03-08: Replace node IDs with file names
const newContent = document.createTextNode('' +
edge.from +
' and ' + edge.to);
item.appendChild(newContent);
list.insertBefore(item, null);
});
}
function analyseGraph(hackedGraph) {
const groupedByWeight = AYESEEEM.desipartart.edgesGroupedByWeight(hackedGraph);
console.log('edges grouped by weight:');
console.log(groupedByWeight);
const histogram = {};
groupedByWeight.forEach((value, key) => histogram[key] = value.length);
console.log('number of changes per edge - histogram:');
console.log(histogram);
const weight = findMaxWeight(hackedGraph);
const weightElement = document.getElementById('most-bound-weight');
weightElement.textContent = weight;
listFilesBoundWithWeight(groupedByWeight, weight);
// Plot Histogram
const nonSelfEdges = hackedGraph.getNonSelfEdges();
const weights = nonSelfEdges.map(e => e.weight);
const trace = {
x: weights,
type: 'histogram',
};
const data = [ trace ];
Plotly.newPlot('changes-per-edge', data);
const myPlot = document.getElementById('changes-per-edge');
myPlot.on('plotly_click', function (data) {
// relies on histogram grouping weight individually, not in bins
const weight = data.points[0].x;
listFilesBoundWithWeight(groupedByWeight, weight);
});
}
const openFile = function (event) {
const input = event.target;
const reader = new FileReader();
reader.onload = function () {
const text = reader.result;
const file = input.files[0];
console.log('File name: ' + file.name + '\n'
+ 'type: ' + file.type + '\n'
+ 'size: ' + file.size + ' bytes\n'
+ 'starts with: ' + text.substr(0, text.indexOf('\n'))
);
// `git log` seems to produce Linux line endings, even on Windows,
// so that makes things simpler to process
const lines = text.split('\n');
const commits = AYESEEEM.desipartart.processGitLog(lines);
AYESEEEM.desipartart.analyseCommits(commits);
const hackedGraph = AYESEEEM.desipartart.processCommits(commits);
analyseGraph(hackedGraph);
// Remove self-links
// TODO: ICM 2020-02-06: Consider whether this should be done earlier
// TODO: ICM 2020-02-06: make optional
hackedGraph.edges = hackedGraph.edges.filter(edge => edge.from !== edge.to);
// TODO: ICM 2020-02-08: Add to an extracted graph object
function hasNoEdges(nodeId) {
const possible = hackedGraph.edges.find(edge => (edge.from === nodeId || edge.to === nodeId));
return possible === undefined;
}
// TODO: ICM 2020-02-09: Add to an extracted graph object
function deleteById(nodeId) {
hackedGraph.nodesByFile.forEach(function (value, key) {
const node = hackedGraph.nodesByFile.get(key);
if (nodeId === node.id) {
hackedGraph.nodesByFile.delete(key);
}
});
}
// Remove unconnected files
// TODO: ICM 2020-02-09: make optional
const unconnectedNodes = [];
hackedGraph.nodesByFile.forEach(function (value, key) {
const node = hackedGraph.nodesByFile.get(key);
if (hasNoEdges(node.id)) {
unconnectedNodes.push(node);
}
});
console.log('' + unconnectedNodes.length + ' unconnected Nodes:');
console.log(unconnectedNodes);
unconnectedNodes.forEach(node => deleteById(node.id));
//visJsDemo();
displayChanges(hackedGraph);
};
reader.readAsText(input.files[0]);
};
</script>
</head>
<body>
<h1><code>desipartart</code> - Design Analysis by VCS</h1>
<dl><dt><code>desipartart</code></dt><dd>Design Partition is Art</dd></dl>
<p>Load a suitable git log file. The recommended command is based on this
suggestion from
<a href="https://github.com/adamtornhill/code-maat">code-maat's <samp>README.md</samp></a>:
</p>
<pre><code>
git log --all --numstat --date=short --pretty=format:'--%h--%ad--%aN' --no-renames --after=YYYY-MM-DD
</code></pre>
<p>In our case, we will play with whether or not to use
<samp>--no-renames</samp>, starting <em>with</em> it.
And we will redirect the output into a suitable text file.
<strong>And</strong>, we want the total history, at least while we
experiment.
Apart from anything else, this will guarantee that we will pick
up every single in the system.
So we don't use <samp>--after</samp>.
</p>
<pre><code>
git log --all --numstat --date=short --pretty=format:'--%h--%ad--%aN' --no-renames > code-maat--log.txt
</code></pre>
<p>We will also experiment with using the <samp>--reverse</samp> option,
because we're thinking we'll want to process the log in historical order?
</p>
<pre><code>
git log --reverse --all --numstat --date=short --pretty=format:'--%h--%ad--%aN' --no-renames > code-maat--log--reversed.txt
</code></pre>
<p><strong>Load a file...</strong></p>
<input type='file' accept='text/plain' onchange='openFile(event)'><br>
<input type="checkbox" disabled checked>Exclude unconnected files
<input type="number" id="seed">
<label for="seed">Layout seed</label>
<input type="checkbox" id="useSeed" >
<label for="useSeed">Use this seed</label>
<p>Histogram of number of changes/edge:</p>
<div id='changes-per-edge'><!-- histogram of number of changes/edge --></div>
<p>Most Bound (file changed together the most): <span id='most-bound-weight'></span> times.</p>
<p>Bound (file changed together) <span id='bound-weight'></span> times: (click on histogram above)</p>
<ul id='most-bound'>
<!-- entries will be added -->
</ul>
<p><strong>Wait (quite) a while, and you should see a graph... or check the console (F12?) to see something's happened.</strong></p>
<div id="mynetwork"></div>
<a href="./SpecRunner.html">Unit tests</a>
</body>
</html>