class BranchGraph constructor: (@element, @options) -> @preparedCommits = {} @mtime = 0 @mspace = 0 @parents = {} @colors = ["#000"] @load() load: -> $.ajax url: @options.url method: "get" dataType: "json" success: $.proxy((data) -> $(".loading", @element).hide() @prepareData data.days, data.commits @buildGraph() , this) prepareData: (@days, @commits) -> @collectParents() @mtime += 4 @mspace += 10 for c in @commits c.isParent = true if c.id of @parents @preparedCommits[c.id] = c @collectColors() collectParents: -> for c in @commits @mtime = Math.max(@mtime, c.time) @mspace = Math.max(@mspace, c.space) for p in c.parents @parents[p[0]] = true collectColors: -> k = 0 while k < @mspace @colors.push Raphael.getColor(.8) # Skipping a few colors in the spectrum to get more contrast between colors Raphael.getColor() Raphael.getColor() k++ buildGraph: -> graphWidth = $(@element).width() ch = @mspace * 20 + 100 cw = Math.max(graphWidth, @mtime * 20 + 260) r = Raphael(@element.get(0), cw, ch) top = r.set() cuday = 0 cumonth = "" @offsetX = 20 @offsetY = 60 barWidth = Math.max(graphWidth, @days.length * 20 + 320) @scrollLeft = cw @raphael = r r.rect(0, 0, barWidth, 20).attr fill: "#222" r.rect(0, 20, barWidth, 20).attr fill: "#444" for day, mm in @days if cuday isnt day[0] # Dates r.text(@offsetX + mm * 20, 31, day[0]) .attr( font: "12px Monaco, monospace" fill: "#DDD" ) cuday = day[0] if cumonth isnt day[1] # Months r.text(@offsetX + mm * 20, 11, day[1]) .attr( font: "12px Monaco, monospace" fill: "#EEE" ) cumonth = day[1] for commit in @commits x = @offsetX + 20 * commit.time y = @offsetY + 10 * commit.space @drawDot(x, y, commit) @drawLines(x, y, commit) @appendLabel(x, y, commit.refs) if commit.refs @appendAnchor(top, commit, x, y) @markCommit(x, y, commit, graphWidth) top.toFront() @element.scrollLeft @scrollLeft @bindEvents() bindEvents: -> drag = {} element = @element dragger = (event) -> element.scrollLeft drag.sl - (event.clientX - drag.x) element.scrollTop drag.st - (event.clientY - drag.y) element.on mousedown: (event) -> drag = x: event.clientX y: event.clientY st: element.scrollTop() sl: element.scrollLeft() $(window).on "mousemove", dragger $(window).on mouseup: -> $(window).off "mousemove", dragger keydown: (event) -> # left element.scrollLeft element.scrollLeft() - 50 if event.keyCode is 37 # top element.scrollTop element.scrollTop() - 50 if event.keyCode is 38 # right element.scrollLeft element.scrollLeft() + 50 if event.keyCode is 39 # bottom element.scrollTop element.scrollTop() + 50 if event.keyCode is 40 appendLabel: (x, y, refs) -> r = @raphael shortrefs = refs # Truncate if longer than 15 chars shortrefs = shortrefs.substr(0, 15) + "…" if shortrefs.length > 17 text = r.text(x + 5, y + 8 + 10, shortrefs).attr( font: "10px Monaco, monospace" fill: "#FFF" title: refs ) textbox = text.getBBox() text.transform ["t", textbox.height / -4, textbox.width / 2 + 5, "r90"] # Create rectangle based on the size of the textbox rect = r.rect(x, y, textbox.width + 15, textbox.height + 5, 4).attr( fill: "#000" "fill-opacity": .7 stroke: "none" ) triangle = r.path(["M", x, y + 5, "L", x + 4, y + 15, "L", x - 4, y + 15, "Z"]).attr( fill: "#000" "fill-opacity": .7 stroke: "none" ) # Rotate and reposition rectangle over text rect.transform ["r", 90, x, y, "t", 15, -9] # Set text to front text.toFront() appendAnchor: (top, commit, x, y) -> r = @raphael options = @options anchor = r.circle(x, y, 10).attr( fill: "#000" opacity: 0 cursor: "pointer" ).click(-> window.open options.commit_url.replace("%s", commit.id), "_blank" ).hover(-> @tooltip = r.commitTooltip(x, y + 5, commit) top.push @tooltip.insertBefore(this) , -> @tooltip and @tooltip.remove() and delete @tooltip ) top.push anchor drawDot: (x, y, commit) -> r = @raphael r.circle(x, y, 3).attr( fill: @colors[commit.space] stroke: "none" ) drawLines: (x, y, commit) -> r = @raphael for parent in commit.parents parentCommit = @preparedCommits[parent[0]] parentX = @offsetX + 20 * parentCommit.time parentY1 = @offsetY + 10 * parentCommit.space parentY2 = @offsetY + 10 * parent[1] if parentCommit.space is commit.space and parentCommit.space is parent[1] r.path(["M", x, y, "L", parentX, parentY1]).attr( stroke: @colors[parentCommit.space] "stroke-width": 2 ) else if parentCommit.space < commit.space if y is parentY2 r.path(["M", x - 5, y, "l-5,-2,0,4,5,-2", "L", x - 10, y, "L", x - 15, parentY2, "L", parentX + 5, parentY2, "L", parentX, parentY1]).attr( stroke: @colors[commit.space] "stroke-width": 2 ) else r.path(["M", x - 3, y - 6, "l-4,-3,4,-2,0,5", "L", x - 5, y - 10, "L", x - 10, parentY2, "L", parentX + 5, parentY2, "L", parentX, parentY1]).attr( stroke: @colors[commit.space] "stroke-width": 2 ) else r.path(["M", x - 3, y + 6, "l-4,3,4,2,0,-5", "L", x - 5, y + 10, "L", x - 10, parentY2, "L", parentX + 5, parentY2, "L", parentX, parentY1]).attr( stroke: @colors[parentCommit.space] "stroke-width": 2 ) markCommit: (x, y, commit, graphWidth) -> if commit.id is @options.commit_id r = @raphael r.path(["M", x, y - 5, "L", x + 4, y - 15, "L", x - 4, y - 15, "Z"]).attr( fill: "#000" "fill-opacity": .7 stroke: "none" ) # Displayed in the center @scrollLeft = x - graphWidth / 2 Raphael::commitTooltip = (x, y, commit) -> boxWidth = 300 boxHeight = 200 icon = @image(commit.author.icon, x, y, 20, 20) nameText = @text(x + 25, y + 10, commit.author.name) idText = @text(x, y + 35, commit.id) messageText = @text(x, y + 50, commit.message) textSet = @set(icon, nameText, idText, messageText).attr( "text-anchor": "start" font: "12px Monaco, monospace" ) nameText.attr( font: "14px Arial" "font-weight": "bold" ) idText.attr fill: "#AAA" @textWrap messageText, boxWidth - 50 rect = @rect(x - 10, y - 10, boxWidth, 100, 4).attr( fill: "#FFF" stroke: "#000" "stroke-linecap": "round" "stroke-width": 2 ) tooltip = @set(rect, textSet) rect.attr( height: tooltip.getBBox().height + 10 width: tooltip.getBBox().width + 10 ) tooltip.transform ["t", 20, 20] tooltip Raphael::textWrap = (t, width) -> content = t.attr("text") abc = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" t.attr text: abc letterWidth = t.getBBox().width / abc.length t.attr text: content words = content.split(" ") x = 0 s = [] for word in words if x + (word.length * letterWidth) > width s.push "\n" x = 0 x += word.length * letterWidth s.push word + " " t.attr text: s.join("") b = t.getBBox() h = Math.abs(b.y2) - Math.abs(b.y) + 1 t.attr y: b.y + h @BranchGraph = BranchGraph