git-visualiser 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 (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)