BigW Consortium Gitlab

branch-graph.js.coffee 8.25 KB
Newer Older
Sato Hiroyuki committed
1 2 3 4 5 6 7
class BranchGraph
  constructor: (@element, @options) ->
    @preparedCommits = {}
    @mtime = 0
    @mspace = 0
    @parents = {}
    @colors = ["#000"]
Sato Hiroyuki committed
8
    @offsetX = 150
Sato Hiroyuki committed
9
    @offsetY = 20
10
    @unitTime = 30
Sato Hiroyuki committed
11
    @unitSpace = 10
Sato Hiroyuki committed
12
    @prev_start = -1
Sato Hiroyuki committed
13
    @load()
14

Sato Hiroyuki committed
15 16 17 18 19 20 21 22 23 24
  load: ->
    $.ajax
      url: @options.url
      method: "get"
      dataType: "json"
      success: $.proxy((data) ->
        $(".loading", @element).hide()
        @prepareData data.days, data.commits
        @buildGraph()
      , this)
25

Sato Hiroyuki committed
26 27
  prepareData: (@days, @commits) ->
    @collectParents()
Sato Hiroyuki committed
28 29 30 31 32 33 34
    @graphHeight = $(@element).height()
    @graphWidth = $(@element).width()
    ch = Math.max(@graphHeight, @offsetY + @unitTime * @mtime + 150)
    cw = Math.max(@graphWidth, @offsetX + @unitSpace * @mspace + 300)
    @r = Raphael(@element.get(0), cw, ch)
    @top = @r.set()
    @barHeight = Math.max(@graphHeight, @unitTime * @days.length + 320)
Sato Hiroyuki committed
35 36 37 38

    for c in @commits
      c.isParent = true  if c.id of @parents
      @preparedCommits[c.id] = c
Sato Hiroyuki committed
39
      @markCommit(c)
Sato Hiroyuki committed
40 41 42 43 44 45 46 47 48

    @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
49
        @mspace = Math.max(@mspace, p[1])
Sato Hiroyuki committed
50 51 52 53 54 55 56 57 58 59 60

  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: ->
Sato Hiroyuki committed
61
    r = @r
Sato Hiroyuki committed
62 63
    cuday = 0
    cumonth = ""
Sato Hiroyuki committed
64

65 66
    r.rect(0, 0, 40, @barHeight).attr fill: "#222"
    r.rect(40, 0, 30, @barHeight).attr fill: "#444"
Sato Hiroyuki committed
67 68 69 70

    for day, mm in @days
      if cuday isnt day[0]
        # Dates
71
        r.text(55, @offsetY + @unitTime * mm, day[0])
Sato Hiroyuki committed
72 73
          .attr(
            font: "12px Monaco, monospace"
74
            fill: "#BBB"
Sato Hiroyuki committed
75 76 77 78 79
          )
        cuday = day[0]

      if cumonth isnt day[1]
        # Months
80
        r.text(20, @offsetY + @unitTime * mm, day[1])
Sato Hiroyuki committed
81 82
          .attr(
            font: "12px Monaco, monospace"
83
            fill: "#EEE"
Sato Hiroyuki committed
84 85 86
          )
        cumonth = day[1]

Sato Hiroyuki committed
87
    @renderPartialGraph()
Sato Hiroyuki committed
88

Sato Hiroyuki committed
89
    @bindEvents()
Sato Hiroyuki committed
90

Sato Hiroyuki committed
91 92 93 94 95
  renderPartialGraph: ->
    start = Math.floor((@element.scrollTop() - @offsetY) / @unitTime) - 10
    start = 0 if start < 0
    end = start + 40
    end = @commits.length if @commits.length < end
Sato Hiroyuki committed
96

Sato Hiroyuki committed
97 98
    if @prev_start == -1 or Math.abs(@prev_start - start) > 10
      i = start
99

Sato Hiroyuki committed
100
      @prev_start = start
101

Sato Hiroyuki committed
102 103 104
      while i < end
        commit = @commits[i]
        i += 1
Sato Hiroyuki committed
105

Sato Hiroyuki committed
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
        if commit.hasDrawn isnt true
          x = @offsetX + @unitSpace * (@mspace - commit.space)
          y = @offsetY + @unitTime * commit.time

          @drawDot(x, y, commit)

          @drawLines(x, y, commit)

          @appendLabel(x, y, commit)

          @appendAnchor(x, y, commit)

          commit.hasDrawn = true

      @top.toFront()
Sato Hiroyuki committed
121 122 123 124

  bindEvents: ->
    drag = {}
    element = @element
125 126 127

    $(element).scroll (event) =>
      @renderPartialGraph()
Sato Hiroyuki committed
128 129

    $(window).on
Sato Hiroyuki committed
130
      keydown: (event) =>
Sato Hiroyuki committed
131 132 133 134 135 136 137 138
        # 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
Sato Hiroyuki committed
139 140 141 142
        @renderPartialGraph()

  appendLabel: (x, y, commit) ->
    return unless commit.refs
Sato Hiroyuki committed
143

Sato Hiroyuki committed
144
    r = @r
Sato Hiroyuki committed
145
    shortrefs = commit.refs
Sato Hiroyuki committed
146 147
    # Truncate if longer than 15 chars
    shortrefs = shortrefs.substr(0, 15) + "…"  if shortrefs.length > 17
Sato Hiroyuki committed
148
    text = r.text(x + 4, y, shortrefs).attr(
149
      "text-anchor": "start"
Sato Hiroyuki committed
150 151
      font: "10px Monaco, monospace"
      fill: "#FFF"
Sato Hiroyuki committed
152
      title: commit.refs
Sato Hiroyuki committed
153 154 155
    )
    textbox = text.getBBox()
    # Create rectangle based on the size of the textbox
Sato Hiroyuki committed
156
    rect = r.rect(x, y - 7, textbox.width + 5, textbox.height + 5, 4).attr(
Sato Hiroyuki committed
157
      fill: "#000"
158
      "fill-opacity": .5
Sato Hiroyuki committed
159 160
      stroke: "none"
    )
161
    triangle = r.path(["M", x - 5, y, "L", x - 15, y - 4, "L", x - 15, y + 4, "Z"]).attr(
Sato Hiroyuki committed
162
      fill: "#000"
163
      "fill-opacity": .5
Sato Hiroyuki committed
164 165
      stroke: "none"
    )
166 167 168 169

    label = r.set(rect, text)
    label.transform(["t", -rect.getBBox().width - 15, 0])

Sato Hiroyuki committed
170 171
    # Set text to front
    text.toFront()
Koen Punt committed
172

Sato Hiroyuki committed
173
  appendAnchor: (x, y, commit) ->
Sato Hiroyuki committed
174
    r = @r
Sato Hiroyuki committed
175
    top = @top
Sato Hiroyuki committed
176 177 178 179
    options = @options
    anchor = r.circle(x, y, 10).attr(
      fill: "#000"
      opacity: 0
Koen Punt committed
180
      cursor: "pointer"
Sato Hiroyuki committed
181 182 183
    ).click(->
      window.open options.commit_url.replace("%s", commit.id), "_blank"
    ).hover(->
184
      @tooltip = r.commitTooltip(x + 5, y, commit)
Sato Hiroyuki committed
185 186 187 188 189 190
      top.push @tooltip.insertBefore(this)
    , ->
      @tooltip and @tooltip.remove() and delete @tooltip
    )
    top.push anchor

191
  drawDot: (x, y, commit) ->
Sato Hiroyuki committed
192
    r = @r
193 194 195 196
    r.circle(x, y, 3).attr(
      fill: @colors[commit.space]
      stroke: "none"
    )
197 198 199 200 201 202 203 204 205
    r.rect(@offsetX + @unitSpace * @mspace + 10, y - 10, 20, 20).attr(
      fill: "url(#{commit.author.icon})"
      stroke: @colors[commit.space]
      "stroke-width": 2
    )
    r.text(@offsetX + @unitSpace * @mspace + 35, y, commit.message.split("\n")[0]).attr(
      "text-anchor": "start"
      font: "14px Monaco, monospace"
    )
206 207

  drawLines: (x, y, commit) ->
Sato Hiroyuki committed
208
    r = @r
209
    for parent, i in commit.parents
210
      parentCommit = @preparedCommits[parent[0]]
Sato Hiroyuki committed
211 212 213
      parentY = @offsetY + @unitTime * parentCommit.time
      parentX1 = @offsetX + @unitSpace * (@mspace - parentCommit.space)
      parentX2 = @offsetX + @unitSpace * (@mspace - parent[1])
214

215 216 217 218 219 220 221 222 223
      # Set line color
      if parentCommit.space <= commit.space
        color = @colors[commit.space]

      else
        color = @colors[parentCommit.space]

      # Build line shape
      if parent[1] is commit.space
Sato Hiroyuki committed
224 225
        offset = [0, 5]
        arrow = "l-2,5,4,0,-2,-5,0,5"
226

227
      else if parent[1] < commit.space
Sato Hiroyuki committed
228 229
        offset = [3, 3]
        arrow = "l5,0,-2,4,-3,-4,4,2"
230 231

      else
Sato Hiroyuki committed
232 233
        offset = [-3, 3]
        arrow = "l-5,0,2,4,3,-4,-4,2"
234 235

      # Start point
Sato Hiroyuki committed
236
      route = ["M", x + offset[0], y + offset[1]]
237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256

      # Add arrow if not first parent
      if i > 0
        route.push(arrow)

      # Circumvent if overlap
      if commit.space isnt parentCommit.space or commit.space isnt parent[1]
        route.push(
          "L", parentX2, y + 10,
          "L", parentX2, parentY - 5,
        )

      # End point
      route.push("L", parentX1, parentY)

      r
        .path(route)
        .attr(
          stroke: color
          "stroke-width": 2)
257

Sato Hiroyuki committed
258
  markCommit: (commit) ->
259
    if commit.id is @options.commit_id
Sato Hiroyuki committed
260
      r = @r
Sato Hiroyuki committed
261 262
      x = @offsetX + @unitSpace * (@mspace - commit.space)
      y = @offsetY + @unitTime * commit.time
263
      r.path(["M", x + 5, y, "L", x + 15, y + 4, "L", x + 15, y - 4, "Z"]).attr(
264
        fill: "#000"
265
        "fill-opacity": .5
266 267 268
        stroke: "none"
      )
      # Displayed in the center
Sato Hiroyuki committed
269
      @element.scrollTop(y - @graphHeight / 2)
270

Sato Hiroyuki committed
271 272 273 274 275 276 277 278 279 280 281 282 283
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"
284
    "font-weight": "bold"
Sato Hiroyuki committed
285
  )
286

Sato Hiroyuki committed
287 288 289 290 291 292
  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"
293
    "stroke-width": 2
Sato Hiroyuki committed
294 295 296 297 298 299 300 301 302
  )
  tooltip = @set(rect, textSet)
  rect.attr(
    height: tooltip.getBBox().height + 10
    width: tooltip.getBBox().width + 10
  )

  tooltip.transform ["t", 20, 20]
  tooltip
303

Sato Hiroyuki committed
304 305 306 307 308 309 310 311 312
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 = []
313

Sato Hiroyuki committed
314 315 316 317 318 319
  for word in words
    if x + (word.length * letterWidth) > width
      s.push "\n"
      x = 0
    x += word.length * letterWidth
    s.push word + " "
320

Sato Hiroyuki committed
321 322 323 324
  t.attr text: s.join("")
  b = t.getBBox()
  h = Math.abs(b.y2) - Math.abs(b.y) + 1
  t.attr y: b.y + h
325

Sato Hiroyuki committed
326
@BranchGraph = BranchGraph