BigW Consortium Gitlab

branch-graph.js.coffee 8.47 KB
Newer Older
1
class @BranchGraph
Sato Hiroyuki committed
2 3 4 5 6 7
  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

    for day, mm in @days
69
      if cuday isnt day[0] || cumonth isnt day[1]
Sato Hiroyuki committed
70
        # 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
  renderPartialGraph: ->
    start = Math.floor((@element.scrollTop() - @offsetY) / @unitTime) - 10
93 94 95
    if start < 0
      isGraphEdge = true
      start = 0
Sato Hiroyuki committed
96
    end = start + 40
97 98 99
    if @commits.length < end
      isGraphEdge = true
      end = @commits.length
Sato Hiroyuki committed
100

101
    if @prev_start == -1 or Math.abs(@prev_start - start) > 10 or isGraphEdge
Sato Hiroyuki committed
102
      i = start
103

Sato Hiroyuki committed
104
      @prev_start = start
105

Sato Hiroyuki committed
106 107 108
      while i < end
        commit = @commits[i]
        i += 1
Sato Hiroyuki committed
109

Sato Hiroyuki committed
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
        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
125 126 127

  bindEvents: ->
    element = @element
128 129 130

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

132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
  scrollDown: =>
    @element.scrollTop @element.scrollTop() + 50
    @renderPartialGraph()

  scrollUp: =>
    @element.scrollTop @element.scrollTop() - 50
    @renderPartialGraph()

  scrollLeft: =>
    @element.scrollLeft @element.scrollLeft() - 50
    @renderPartialGraph()

  scrollRight: =>
    @element.scrollLeft @element.scrollLeft() + 50
    @renderPartialGraph()

  scrollBottom: =>
    @element.scrollTop @element.find('svg').height()

  scrollTop: =>
    @element.scrollTop 0
Sato Hiroyuki committed
153 154 155

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

Sato Hiroyuki committed
157
    r = @r
Sato Hiroyuki committed
158
    shortrefs = commit.refs
Sato Hiroyuki committed
159 160
    # Truncate if longer than 15 chars
    shortrefs = shortrefs.substr(0, 15) + "…"  if shortrefs.length > 17
Sato Hiroyuki committed
161
    text = r.text(x + 4, y, shortrefs).attr(
162
      "text-anchor": "start"
Sato Hiroyuki committed
163 164
      font: "10px Monaco, monospace"
      fill: "#FFF"
Sato Hiroyuki committed
165
      title: commit.refs
Sato Hiroyuki committed
166 167 168
    )
    textbox = text.getBBox()
    # Create rectangle based on the size of the textbox
Sato Hiroyuki committed
169
    rect = r.rect(x, y - 7, textbox.width + 5, textbox.height + 5, 4).attr(
Sato Hiroyuki committed
170
      fill: "#000"
171
      "fill-opacity": .5
Sato Hiroyuki committed
172 173
      stroke: "none"
    )
174
    triangle = r.path(["M", x - 5, y, "L", x - 15, y - 4, "L", x - 15, y + 4, "Z"]).attr(
Sato Hiroyuki committed
175
      fill: "#000"
176
      "fill-opacity": .5
Sato Hiroyuki committed
177 178
      stroke: "none"
    )
179 180 181 182

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

Sato Hiroyuki committed
183 184
    # Set text to front
    text.toFront()
Koen Punt committed
185

Sato Hiroyuki committed
186
  appendAnchor: (x, y, commit) ->
Sato Hiroyuki committed
187
    r = @r
Sato Hiroyuki committed
188
    top = @top
Sato Hiroyuki committed
189 190 191 192
    options = @options
    anchor = r.circle(x, y, 10).attr(
      fill: "#000"
      opacity: 0
Koen Punt committed
193
      cursor: "pointer"
Sato Hiroyuki committed
194 195 196
    ).click(->
      window.open options.commit_url.replace("%s", commit.id), "_blank"
    ).hover(->
197
      @tooltip = r.commitTooltip(x + 5, y, commit)
Sato Hiroyuki committed
198 199 200 201 202 203
      top.push @tooltip.insertBefore(this)
    , ->
      @tooltip and @tooltip.remove() and delete @tooltip
    )
    top.push anchor

204
  drawDot: (x, y, commit) ->
Sato Hiroyuki committed
205
    r = @r
206 207 208 209
    r.circle(x, y, 3).attr(
      fill: @colors[commit.space]
      stroke: "none"
    )
Jeroen van Baarsen committed
210 211 212 213

    avatar_box_x = @offsetX + @unitSpace * @mspace + 10
    avatar_box_y = y - 10
    r.rect(avatar_box_x, avatar_box_y, 20, 20).attr(
214 215 216
      stroke: @colors[commit.space]
      "stroke-width": 2
    )
217
    r.image(commit.author.icon, avatar_box_x, avatar_box_y, 20, 20)
218 219 220 221
    r.text(@offsetX + @unitSpace * @mspace + 35, y, commit.message.split("\n")[0]).attr(
      "text-anchor": "start"
      font: "14px Monaco, monospace"
    )
222 223

  drawLines: (x, y, commit) ->
Sato Hiroyuki committed
224
    r = @r
225
    for parent, i in commit.parents
226
      parentCommit = @preparedCommits[parent[0]]
Sato Hiroyuki committed
227 228 229
      parentY = @offsetY + @unitTime * parentCommit.time
      parentX1 = @offsetX + @unitSpace * (@mspace - parentCommit.space)
      parentX2 = @offsetX + @unitSpace * (@mspace - parent[1])
230

231 232 233 234 235 236 237 238 239
      # 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
240 241
        offset = [0, 5]
        arrow = "l-2,5,4,0,-2,-5,0,5"
242

243
      else if parent[1] < commit.space
Sato Hiroyuki committed
244 245
        offset = [3, 3]
        arrow = "l5,0,-2,4,-3,-4,4,2"
246 247

      else
Sato Hiroyuki committed
248 249
        offset = [-3, 3]
        arrow = "l-5,0,2,4,3,-4,-4,2"
250 251

      # Start point
Sato Hiroyuki committed
252
      route = ["M", x + offset[0], y + offset[1]]
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272

      # 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)
273

Sato Hiroyuki committed
274
  markCommit: (commit) ->
275
    if commit.id is @options.commit_id
Sato Hiroyuki committed
276
      r = @r
Sato Hiroyuki committed
277 278
      x = @offsetX + @unitSpace * (@mspace - commit.space)
      y = @offsetY + @unitTime * commit.time
279
      r.path(["M", x + 5, y, "L", x + 15, y + 4, "L", x + 15, y - 4, "Z"]).attr(
280
        fill: "#000"
281
        "fill-opacity": .5
282 283 284
        stroke: "none"
      )
      # Displayed in the center
Sato Hiroyuki committed
285
      @element.scrollTop(y - @graphHeight / 2)
286

Sato Hiroyuki committed
287 288 289
Raphael::commitTooltip = (x, y, commit) ->
  boxWidth = 300
  boxHeight = 200
290
  icon = @image(gon.relative_url_root + commit.author.icon, x, y, 20, 20)
Sato Hiroyuki committed
291 292 293 294 295 296 297 298 299
  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"
300
    "font-weight": "bold"
Sato Hiroyuki committed
301
  )
302

Sato Hiroyuki committed
303 304 305 306 307 308
  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"
309
    "stroke-width": 2
Sato Hiroyuki committed
310 311 312 313 314 315 316 317 318
  )
  tooltip = @set(rect, textSet)
  rect.attr(
    height: tooltip.getBBox().height + 10
    width: tooltip.getBBox().width + 10
  )

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

Sato Hiroyuki committed
320 321 322 323 324 325 326 327 328
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 = []
329

Sato Hiroyuki committed
330 331 332 333 334 335
  for word in words
    if x + (word.length * letterWidth) > width
      s.push "\n"
      x = 0
    x += word.length * letterWidth
    s.push word + " "
336

Sato Hiroyuki committed
337 338 339 340
  t.attr text: s.join("")
  b = t.getBBox()
  h = Math.abs(b.y2) - Math.abs(b.y) + 1
  t.attr y: b.y + h