git-visualiser 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +15 -0
  2. data/bin/git_vis +19 -0
  3. data/lib/application/application.rb +82 -0
  4. data/lib/application/coffee_engine.rb +20 -0
  5. data/lib/application/images/ajax-loader.gif +0 -0
  6. data/lib/application/javascripts/branch_graph.coffee +424 -0
  7. data/lib/application/javascripts/commit_graph.coffee +442 -0
  8. data/lib/application/javascripts/common.coffee +50 -0
  9. data/lib/application/javascripts/d3.min.js +5 -0
  10. data/lib/application/javascripts/data_convert.coffee +56 -0
  11. data/lib/application/javascripts/jquery.min.js +6 -0
  12. data/lib/application/javascripts/moment.min.js +6 -0
  13. data/lib/application/javascripts/namespace.coffee +2 -0
  14. data/lib/application/sass_engine.rb +14 -0
  15. data/lib/application/stylesheets/scss/app.scss +172 -0
  16. data/lib/application/stylesheets/scss/flat_ui/_config.sass +16 -0
  17. data/lib/application/stylesheets/scss/flat_ui/_icon-font-24.sass +91 -0
  18. data/lib/application/stylesheets/scss/flat_ui/_icon-font.sass +91 -0
  19. data/lib/application/stylesheets/scss/flat_ui/_mixins.sass +96 -0
  20. data/lib/application/stylesheets/scss/flat_ui/_spaces.sass +129 -0
  21. data/lib/application/stylesheets/scss/flat_ui/flat-ui.sass +39 -0
  22. data/lib/application/stylesheets/scss/flat_ui/modules/_btn.sass +73 -0
  23. data/lib/application/stylesheets/scss/flat_ui/modules/_checkbox-and-radio.sass +86 -0
  24. data/lib/application/stylesheets/scss/flat_ui/modules/_demo.sass +228 -0
  25. data/lib/application/stylesheets/scss/flat_ui/modules/_footer.sass +57 -0
  26. data/lib/application/stylesheets/scss/flat_ui/modules/_input.sass +66 -0
  27. data/lib/application/stylesheets/scss/flat_ui/modules/_login.sass +95 -0
  28. data/lib/application/stylesheets/scss/flat_ui/modules/_navbar.sass +152 -0
  29. data/lib/application/stylesheets/scss/flat_ui/modules/_pager.sass +56 -0
  30. data/lib/application/stylesheets/scss/flat_ui/modules/_pagination.sass +75 -0
  31. data/lib/application/stylesheets/scss/flat_ui/modules/_palette.sass +88 -0
  32. data/lib/application/stylesheets/scss/flat_ui/modules/_progress.sass +29 -0
  33. data/lib/application/stylesheets/scss/flat_ui/modules/_select.sass +163 -0
  34. data/lib/application/stylesheets/scss/flat_ui/modules/_share.sass +34 -0
  35. data/lib/application/stylesheets/scss/flat_ui/modules/_tagsinput.sass +91 -0
  36. data/lib/application/stylesheets/scss/flat_ui/modules/_tile.sass +42 -0
  37. data/lib/application/stylesheets/scss/flat_ui/modules/_todo.sass +77 -0
  38. data/lib/application/stylesheets/scss/flat_ui/modules/_toggle.sass +85 -0
  39. data/lib/application/stylesheets/scss/flat_ui/modules/_tooltip.sass +45 -0
  40. data/lib/application/stylesheets/scss/flat_ui/modules/_type.sass +43 -0
  41. data/lib/application/stylesheets/scss/flat_ui/modules/_ui-slider.sass +44 -0
  42. data/lib/application/stylesheets/scss/flat_ui/modules/_video.sass +358 -0
  43. data/lib/application/views/authors_list.haml +11 -0
  44. data/lib/application/views/index.haml +48 -0
  45. data/lib/application/visualisation.rb +156 -0
  46. metadata +87 -0
@@ -0,0 +1,442 @@
1
+ class CommitGraph
2
+ constructor: () ->
3
+ @initializeControls()
4
+
5
+ load: (@branch_name) ->
6
+ @initializeD3()
7
+ @getGraphData()
8
+ @getHistData()
9
+ @loadUI()
10
+
11
+ initializeD3: ->
12
+ # set up SVG for D3
13
+ @width = @height = $("#commits-display").width()
14
+ tcolors = d3.scale.category10()
15
+ @body = d3.select("body")
16
+
17
+ @svg = @body.select("#commits-display")
18
+ .append("svg")
19
+ .attr("width", @width)
20
+ .attr("height", @height)
21
+
22
+ @force = d3.layout.force().on("tick", @tick).charge((d) ->
23
+ #(if d._children then -d.size / 100 else -40)
24
+ -400
25
+ ).linkDistance((d) ->
26
+ # (if d.target._children then 50 else 25)
27
+ 30
28
+ ).size([@height, @width])
29
+ @mouseover = false
30
+ @drag_in_progress = false
31
+
32
+ getGraphData: ->
33
+ vis = @
34
+ $.get "/commit_diff_stats.json", {ref: @branch_name}, (data) ->
35
+ vis.initializeGraphData(data)
36
+
37
+ getHistData: ->
38
+ vis = @
39
+ $.get "/commits.json", {ref: @branch_name}, (history_data) ->
40
+ vis.initHistoryGraph(history_data)
41
+
42
+ loadUI: ->
43
+ Visualisation.hideBranchesGraph()
44
+ Visualisation.showCommitsGraph()
45
+ Visualisation.hideBranchesSidebar(Visualisation.showCommitsToolbar)
46
+ $("#branch_name").text("Commits for " + @branch_name)
47
+ $("#commit_sha").text('')
48
+ $("#commit_author").text('')
49
+ $("#commit_message").text('')
50
+ if @branch_name.length > 30
51
+ $("#branch_name").css("font-size": "18px")
52
+ if @branch_name.length > 48
53
+ $("#branch_name").css("font-size": "15px")
54
+
55
+ initializeControls: ->
56
+ vis = @
57
+ $("#clear_history_filters").click (event) ->
58
+ event.preventDefault()
59
+ vis.clear_filter()
60
+
61
+ $("#back-to-branches-btn").click (event) ->
62
+ event.preventDefault()
63
+ vis.svg.remove()
64
+ vis.history_svg.remove()
65
+ Visualisation.hideCommitsGraph()
66
+ Visualisation.showBranchesGraph()
67
+ Visualisation.hideCommitsToolbar(Visualisation.showBranchesSidebar)
68
+
69
+ initializeGraphData: (diff_stats) ->
70
+ @diff_stats = diff_stats
71
+ diff_tree = Visualisation.convertDiffStatsToTree(diff_stats)
72
+
73
+ @diff_tree = diff_tree
74
+
75
+ #fix the root node
76
+ @diff_tree.fixed = true
77
+ @diff_tree.root = true
78
+ @diff_tree.x = @width / 2
79
+ @diff_tree.y = window.innerHeight / 2
80
+ @diff_tree.name = @branch_name
81
+
82
+ @link = @svg.append('svg:g').selectAll('path')
83
+ @node = @svg.append('svg:g').selectAll('g')
84
+
85
+ @update()
86
+
87
+ update: () =>
88
+ @nodes = @flatten(@diff_tree)
89
+ @links = d3.layout.tree().links(@nodes)
90
+ @total = @nodes.length || 1
91
+ @root = @nodes[@nodes.length-1]
92
+ @total_size = @root.add + @root.del
93
+
94
+ # remove existing text (will readd it afterwards to be sure it's on top)
95
+ # @svg.selectAll("text").remove()
96
+
97
+ # Restart the force layout
98
+ @force.gravity(0.1)
99
+ .nodes(@nodes)
100
+ .links(@links)
101
+
102
+ # Update the links
103
+ @link = @link.data(@links, (d) ->
104
+ d.target.id #link by id not name
105
+ )
106
+
107
+ # Enter any new links
108
+ @link.enter().append("svg:path").attr("class", "link").attr("x1", (d) ->
109
+ d.source.x
110
+ ).attr("y1", (d) ->
111
+ d.source.y
112
+ ).attr("x2", (d) ->
113
+ d.target.x
114
+ ).attr "y2", (d) ->
115
+ d.target.y
116
+
117
+ vis = @
118
+ # Exit any old links.
119
+ @link.exit().remove()
120
+
121
+ # Update the nodes
122
+ @node = @node.data(@nodes, (d) ->
123
+ d.id #nodes are known by id
124
+ )
125
+
126
+ @node.selectAll("circle")
127
+ .classed("collapsed", (d) ->
128
+ (if d._children then 1 else 0)
129
+ ).classed("fixed", (d) ->
130
+ d.fixed
131
+ ).attr("r", (d) ->
132
+ vis.node_size(d)
133
+ )
134
+
135
+ @node.selectAll("text")
136
+ .attr("x", (d) -> vis.node_size(d)+5)
137
+ .attr("y", (d) -> vis.node_size(d)/2)
138
+
139
+ @node.transition().attr "r", (d) -> vis.node_size(d)
140
+
141
+ @node_drag = d3.behavior.drag().on("dragstart", @dragstart).on("drag", @dragmove).on("dragend", @dragend)
142
+
143
+ # Enter any new nodes
144
+ g = @node.enter().append("svg:g")
145
+ g.attr("id", (d) -> nodeId(d.name))
146
+ .attr("class", "node_group")
147
+ .append("svg:circle")
148
+ .attr("class", "node")
149
+ .classed("fixed", (d) -> d.fixed)
150
+ .attr("r", (d) -> vis.node_size(d))
151
+ .style("fill", (d) -> (d3.rgb(vis.node_colour(d))))
152
+ .style("stroke", (d) -> d3.rgb(vis.node_colour(d)).darker().toString())
153
+ .on("mouseover", (d) ->
154
+ return if vis.drag_in_progress || vis.filter_active
155
+ vis.mouseover = true
156
+ vis.mouseover_node = d
157
+ d3.selectAll("circle").filter((d2) -> d != d2).transition().style "opacity", "0.25"
158
+ d3.selectAll("text").filter((d2) -> d != d2).transition().style "opacity", "0.10"
159
+ d3.selectAll("path").filter((d2) -> d != d2).transition().style "opacity", "0.10")
160
+ .on("mouseout", (d) ->
161
+ return if vis.drag_in_progress || vis.filter_active
162
+ vis.mouseover = false
163
+ d3.selectAll("circle").transition().style "opacity", "1"
164
+ d3.selectAll("text").transition().style "opacity", "1"
165
+ d3.selectAll("path").transition().style "opacity", "1")
166
+ .on("click", (d) ->
167
+ if d.root
168
+ vis.svg.selectAll("circle").each (d, i) ->
169
+ d.fixed = false if !d.root
170
+ else
171
+ d.fixed = false if d.fixed
172
+ vis.update()
173
+ ).on("dblclick", (d) ->
174
+ vis.toggle_children(d)
175
+ )
176
+
177
+ g.call(@node_drag)
178
+ # .on("mouseover", @mouseover)
179
+ # .on("mouseout", @mouseout)
180
+
181
+ # show node IDs
182
+ g.append("svg:text")
183
+ .attr("x", (d) -> vis.node_size(d)+5)
184
+ .attr("y", (d) -> vis.node_size(d)/2)
185
+ .attr("class", "name")
186
+ .text (d) ->
187
+ if (d.children || d._children) && !d.root
188
+ dir = '/ '
189
+ else
190
+ dir = ' '
191
+ d.name + dir + d.add + " / " + d.del
192
+
193
+ # Exit any old nodes
194
+ @node.exit().remove()
195
+
196
+ #remove the loading div here
197
+ $("#vis-loading").hide();
198
+ @force.start()
199
+
200
+ # @all_nodes = @nodes
201
+ # @all_links = @links
202
+ # @all_branches = @branches
203
+ # @all_branch_names = @branch_names
204
+
205
+ toggle_children: (node) ->
206
+ # Toggle children on click.
207
+ if node.children
208
+ node._children = node.children
209
+ node.children = null
210
+ else
211
+ node.children = node._children
212
+ node._children = null
213
+ @update()
214
+
215
+ dragstart: (d, i) =>
216
+ @drag_in_progress = true
217
+ @force.stop() # stops the force auto positioning before you start dragging
218
+
219
+ dragmove: (d, i) =>
220
+ d.px += d3.event.dx
221
+ d.py += d3.event.dy
222
+ d.x += d3.event.dx
223
+ d.y += d3.event.dy
224
+ @tick() # this is the key to make it work together with updating both px,py,x,y on d !
225
+
226
+ dragend: (d, i) =>
227
+ d.fixed = true # of course set the node to fixed so the force doesn't include the node in its auto positioning stuff
228
+ @mouseover = true #still inside the dropped node
229
+ @drag_in_progress = false
230
+ @update()
231
+ @force.resume()
232
+
233
+ flatten: (root) ->
234
+ nodes = []
235
+ i = 0
236
+
237
+ recurse = (node) ->
238
+ if node.children
239
+ stats = node.children.reduce((p, v) ->
240
+ p = {add: 0, del: 0} if p == 0
241
+ stat = recurse(v)
242
+ addv = p.add + stat.add
243
+ delv = p.del + stat.del
244
+ return {add: addv, del: delv}
245
+ , 0)
246
+ node.add = stats.add
247
+ node.del = stats.del
248
+ node.id = ++i if !node.id
249
+ nodes.push(node)
250
+ return {add: node.add, del: node.del}
251
+
252
+ root_stats = recurse(root)
253
+ root.add = root_stats.add || 0
254
+ root.del = root_stats.del || 0
255
+ nodes
256
+
257
+ resetMouseVars: ->
258
+ @mousedown_node = null
259
+ @mouseup_node = null
260
+ @mousedown_link = null
261
+
262
+ # update force layout (called automatically each iteration)
263
+ tick: =>
264
+ vis = @
265
+ @link.attr "d", (d) ->
266
+ deltaX = d.target.x - d.source.x
267
+ deltaY = d.target.y - d.source.y
268
+ dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY)
269
+ normX = deltaX / dist
270
+ normY = deltaY / dist
271
+ sourcePadding = vis.node_size(d.source)
272
+ targetPadding = vis.node_size(d.target)
273
+ sourceX = d.source.x + (sourcePadding * normX)
274
+ sourceY = d.source.y + (sourcePadding * normY)
275
+ targetX = d.target.x - (targetPadding * normX)
276
+ targetY = d.target.y - (targetPadding * normY)
277
+ "M" + sourceX + "," + sourceY + "L" + targetX + "," + targetY
278
+
279
+ @node.attr "transform", (d) ->
280
+ "translate(" + d.x + "," + d.y + ")"
281
+
282
+ initHistoryGraph: (commit_history) ->
283
+ # id => {sha, date, num, author, message}
284
+ # num is the commit number for a date, 1 <= num <= count(commits_for_date)
285
+
286
+ wd = $("#history-graph").width()
287
+ ht = $("#history-graph").height()
288
+
289
+ # get min/max dates in range, date is sorted in descending date order (newest first)
290
+ max_date = new Date()#getDate(commit_history[0])
291
+ min_date = getDate(commit_history[commit_history.length-1])
292
+
293
+ #map dates/nums onto x and y scales
294
+ x_scale = d3.time.scale()
295
+ .domain([d3.time.day.offset(min_date, -1), d3.time.day.offset(max_date, 1)])
296
+ .range([20, wd])
297
+
298
+ y_scale = d3.scale.linear()
299
+ .domain([0, 10])
300
+ .range([ht-20, 0])
301
+
302
+ xAxis = d3.svg.axis()
303
+ .scale(x_scale)
304
+ .orient('bottom')
305
+ .ticks(5)
306
+ .tickFormat(d3.time.format('%d %b %y'))
307
+ .tickSize(1)
308
+
309
+ yAxis = d3.svg.axis()
310
+ .scale(y_scale)
311
+ .ticks(4)
312
+ .orient("left")
313
+ .tickSize(1)
314
+
315
+ #create scatterplot
316
+ @history_svg = d3.select("#history-graph")
317
+ .append("svg")
318
+ .attr("width", wd+40)
319
+ .attr("height", ht+20)
320
+
321
+ #add x axis
322
+ @history_svg.append('svg:g')
323
+ .attr("transform", "translate(0," + ht + ")")
324
+ .attr("class", "x axis")
325
+ .call(xAxis)
326
+
327
+ #add y axis
328
+ @history_svg.append('svg:g')
329
+ .attr("transform", "translate(20, 20)")
330
+ .attr("class", "y axis")
331
+ .call(yAxis)
332
+
333
+ @hist_node = @history_svg.append('svg:g').selectAll("g")
334
+
335
+ #nodes are known by sha hash
336
+ @hist_node = @hist_node.data commit_history, (d) -> d.id
337
+
338
+ vis = @
339
+ g = @hist_node.enter().append('svg:g')
340
+ g.append('svg:circle')
341
+ .attr('cx', (d) -> x_scale(getDate(d)))
342
+ .attr('cy', (d) -> y_scale(d.num)+20)
343
+ .attr("class", "hist_node")
344
+ .attr("id", (d) -> d.id)
345
+ .attr("r", (d) -> 5)
346
+ .style("fill", (d) -> '#1F77B4')
347
+ .style("opacity", (d) -> d.selected ? '100%' : '50%')
348
+ #.style("stroke", (d) -> d3.rgb(vis.node_colour(d)).darker().toString())
349
+ .on("mouseover", (d) ->
350
+ # vis.history_svg.selectAll("circle").filter((d2) -> d != d2).transition().style "opacity", "0.25"
351
+ # append("svg:text").attr("x", 30).attr("y", 4).attr("class", "name").text (d) ->
352
+ # d.sha
353
+ )
354
+ .on("mouseout", (d) ->
355
+ # vis.history_svg.selectAll("circle").filter((d2) -> d != d2).transition().style "opacity", "1"
356
+ )
357
+ .on("click", (d) ->
358
+ vis.history_svg.selectAll("circle").filter((d2) -> d != d2).transition().style "fill", "#1F77B4"
359
+ d3.select(this).transition().style "fill", "#6ACD72"
360
+ $("#commit_sha").text("Commit: " + d.sha)
361
+ $("#commit_author").text("Made by: " + d.author + ", on " + moment(getDate(d)).format("dddd MMMM Do YYYY"))
362
+ $("#commit_message").text(d.message)
363
+ vis.filter_commit(d.sha)
364
+ )
365
+
366
+ clear_filter: () ->
367
+ $("#commit_sha").text('')
368
+ $("#commit_author").text('')
369
+ $("#commit_message").text('')
370
+ @svg.selectAll('g.node_group').transition().style "opacity", "1"
371
+ @svg.selectAll('g.node_group').select('text').transition().style "opacity", "1"
372
+ @svg.selectAll('path').transition().style "opacity", "1"
373
+ @history_svg.selectAll('circle').transition().style 'fill', '#1F77B4'
374
+
375
+ filter_commit: (commit_sha) ->
376
+ vis = @
377
+ filter_nodes = null
378
+
379
+ $.get "/commit_diff_stats.json", {ref: commit_sha}, (history_data) ->
380
+ # simple filter, replaces current graph with commit graph
381
+ # vis.svg.selectAll('g').remove()
382
+ # vis.initializeGraphData(history_data)
383
+ vis.filter_commit_nodes(history_data)
384
+
385
+ filter_commit_nodes: (commit_stats) ->
386
+ return if commit_stats == null
387
+ filter_nodes = @flatten(Visualisation.convertDiffStatsToTree(commit_stats))
388
+ vis = @
389
+ # filter out all nodes
390
+ vis.svg.selectAll('g.node_group').transition().style "opacity", "0.25"
391
+ vis.svg.selectAll('g.node_group').select('text').transition().style "opacity", "0"
392
+ vis.svg.selectAll('path').transition().style "opacity", "0.1"
393
+
394
+ filtered_names = $.map(filter_nodes, (node) ->
395
+ return if !node.name
396
+ nodeId(node.name)
397
+ )
398
+ filtered_names.push(nodeId(@branch_name))
399
+
400
+ $.each filtered_names, (i, id_name) ->
401
+ return if $("#" + id_name) == []
402
+ dom_node = vis.svg.selectAll(('#' + id_name))
403
+ dom_node.transition().style "opacity", "1"
404
+ dom_node.select('text').transition().style "opacity", "1"
405
+
406
+ vis.svg.selectAll('path').filter((d) ->
407
+ source_name = nodeId(d.source.name)
408
+ target_name = nodeId(d.target.name)
409
+ return true if ($.inArray(source_name, filtered_names) > -1 && $.inArray(target_name, filtered_names) > -1)
410
+ false
411
+ ).transition().style "opacity", "1"
412
+
413
+ @filter_active = true
414
+
415
+ node_colour: (node) ->
416
+ if !node.colour
417
+ if node.root
418
+ node.colour = "#1f77b4"
419
+ else
420
+ node.colour = "hsl(" + parseInt(360 / @total * node.id, 10) + ",90%,70%)"
421
+ return node.colour
422
+
423
+ node_size: (node_data) ->
424
+ return 10 if node_data.children
425
+ # rad = node_data.add + node_data.del
426
+ # if node_data._children
427
+ # size = Math.pow(size, 2/5) if node_data._children
428
+ rad = ((node_data.add + node_data.del) / @total_size) * 100
429
+
430
+ rad = 5 if rad < 5
431
+ rad = 30 if rad > 30
432
+ return 5 if !rad
433
+ return rad
434
+
435
+ getDate = (d) ->
436
+ new Date(d.date)
437
+
438
+ nodeId = (name) ->
439
+ "node_" + name.replace(/[.\/]/g, '-')
440
+
441
+
442
+ Visualisation.CommitGraph = CommitGraph
@@ -0,0 +1,50 @@
1
+ Visualisation.showBranchesSidebar = () ->
2
+ sidebar = $("#sidebar-branches")
3
+ showSidebar(sidebar)
4
+
5
+ Visualisation.hideBranchesSidebar = (callback) ->
6
+ sidebar = $("#sidebar-branches")
7
+ hideSidebar(sidebar, callback)
8
+
9
+ Visualisation.showCommitsSidebar = () ->
10
+ sidebar = $("#sidebar-commits")
11
+ showSidebar(sidebar)
12
+
13
+ Visualisation.hideCommitsSidebar = (callback) ->
14
+ sidebar = $("#sidebar-commits")
15
+ hideSidebar(sidebar, callback)
16
+
17
+ Visualisation.showBranchesGraph = () ->
18
+ $("#branches-display").css("position": "static").show()
19
+
20
+ Visualisation.hideBranchesGraph = () ->
21
+ $("#branches-display").css("position": "absolute").hide()
22
+
23
+ Visualisation.showCommitsGraph = () ->
24
+ $("#commits-display").show()
25
+
26
+ Visualisation.hideCommitsGraph = () ->
27
+ $("#commits-display").hide()
28
+
29
+ Visualisation.showCommitsToolbar = () ->
30
+ $("#commits-toolbar").show()
31
+ ht = $("#commits-toolbar").outerHeight()
32
+ $("#commits-toolbar").css("bottom": "#{-ht}px").animate({bottom: "0px"}, 500).css("display": "block")
33
+
34
+ Visualisation.hideCommitsToolbar = (callback) ->
35
+ ht = $("#commits-toolbar").outerHeight()
36
+ $("#commits-toolbar").css("top": "auto")
37
+ $("#commits-toolbar").animate({bottom: -ht}, 500, ->
38
+ $(this).css("display": "none")
39
+ callback.call()
40
+ )
41
+
42
+ hideSidebar = (sidebar, callback) ->
43
+ wd = sidebar.outerWidth()
44
+ sidebar.css("position": "absolute")
45
+ sidebar.animate({ left: -wd } , 500, callback)
46
+
47
+ showSidebar = (sidebar) ->
48
+ wd = sidebar.outerWidth()
49
+ sidebar.css("position": "fixed", "display": "block", "left": "#{-wd}px")
50
+ sidebar.animate({ left: "0px"}, 500)