if (typeof Element.prototype.matches !== 'function') {

Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.mozMatchesSelector || Element.prototype.webkitMatchesSelector || function matches(selector) {
  var element = this
  var elements = (element.document || element.ownerDocument).querySelectorAll(selector)
  var index = 0

  while (elements[index] && elements[index] !== element) {
    ++index
  }

  return Boolean(elements[index])
}

}

if (typeof Element.prototype.closest !== 'function') {

Element.prototype.closest = function closest(selector) {
  var element = this

  while (element && element.nodeType === 1) {
    if (element.matches(selector)) {
      return element
    }

    element = element.parentNode
  }

  return null
}

}

if (typeof Object.assign !== 'function') {

(function() {
  Object.assign = function(target) {
    'use strict'
    // We must check against these specific cases.
    if (target === undefined || target === null) {
      throw new TypeError('Cannot convert undefined or null to object')
    }

    var output = Object(target)
    for (var index = 1; index < arguments.length; index++) {
      var source = arguments[index]
      if (source !== undefined && source !== null) {
        for (var nextKey in source) {
          if (source.hasOwnProperty(nextKey)) {
            output[nextKey] = source[nextKey]
          }
        }
      }
    }
    return output
  }
})()

}

function EventSource() {

var self = this

self.eventListeners = {}

}

EventSource.prototype.on = function(name, callback) {

var self = this

var listeners = self.eventListeners[name]
if (!listeners)
  listeners = self.eventListeners[name] = []
listeners.push(callback)

}

EventSource.prototype.dispatch = function(name, data) {

var self = this

var listeners = self.eventListeners[name] || []
listeners.forEach(function(c) {
  requestAnimationFrame(function() { c(data) })
})

}

function CanvasView(canvas) {

var self = this

self.canvas = canvas

}

CanvasView.prototype.setDimensions = function(width, height) {

var self = this

if (self.resizeRequestID)
  cancelAnimationFrame(self.resizeRequestID)

self.resizeRequestID = requestAnimationFrame(self.setDimensionsNow.bind(self, width, height))

}

CanvasView.prototype.setDimensionsNow = function(width, height) {

var self = this

if (width === self.width && height === self.height)
  return

self.width = width
self.height = height

self.canvas.style.width = width
self.canvas.style.height = height

var ratio = window.devicePixelRatio || 1
self.canvas.width = width * ratio
self.canvas.height = height * ratio

var ctx = self.canvas.getContext('2d')
ctx.setTransform(1, 0, 0, 1, 0, 0)
ctx.scale(ratio, ratio)

self.repaintNow()

}

CanvasView.prototype.paint = function() { }

CanvasView.prototype.scheduleRepaint = function() {

var self = this

if (self.repaintRequestID)
  return

self.repaintRequestID = requestAnimationFrame(function() {
  self.repaintRequestID = null
  self.repaintNow()
})

}

CanvasView.prototype.repaintNow = function() {

var self = this

self.canvas.getContext('2d').clearRect(0, 0, self.width, self.height)
self.paint()

if (self.repaintRequestID) {
  cancelAnimationFrame(self.repaintRequestID)
  self.repaintRequestID = null
}

}

function Flamechart(canvas, data, dataRange, info) {

var self = this

CanvasView.call(self, canvas)
EventSource.call(self)

self.canvas = canvas
self.data = data
self.dataRange = dataRange
self.info = info

self.viewport = {
  x: dataRange.minX,
  y: dataRange.minY,
  width: dataRange.maxX - dataRange.minX,
  height: dataRange.maxY - dataRange.minY,
}

}

Flamechart.prototype = Object.create(CanvasView.prototype) Flamechart.prototype.constructor = Flamechart Object.assign(Flamechart.prototype, EventSource.prototype)

Flamechart.prototype.xScale = function(x) {

var self = this
return self.widthScale(x - self.viewport.x)

}

Flamechart.prototype.yScale = function(y) {

var self = this
return self.heightScale(y - self.viewport.y)

}

Flamechart.prototype.widthScale = function(width) {

var self = this
return width * self.width / self.viewport.width

}

Flamechart.prototype.heightScale = function(height) {

var self = this
return height * self.height / self.viewport.height

}

Flamechart.prototype.frameRect = function(f) {

return {
  x: f.x,
  y: f.y,
  width: f.width,
  height: 1,
}

}

Flamechart.prototype.dataToCanvas = function® {

var self = this

return {
  x: self.xScale(r.x),
  y: self.yScale(r.y),
  width: self.widthScale(r.width),
  height: self.heightScale(r.height),
}

}

Flamechart.prototype.setViewport = function(viewport) {

var self = this

if (self.viewport.x === viewport.x &&
    self.viewport.y === viewport.y &&
    self.viewport.width === viewport.width &&
    self.viewport.height === viewport.height)
  return

self.viewport = viewport

self.scheduleRepaint()

self.dispatch('viewportchanged', { current: viewport })

}

Flamechart.prototype.paint = function(opacity, frames, gemName) {

var self = this

var ctx = self.canvas.getContext('2d')

ctx.strokeStyle = 'rgba(0, 0, 0, 0.2)'

if (self.showLabels) {
  ctx.textBaseline = 'middle'
  ctx.font = '11px ' + getComputedStyle(this.canvas).fontFamily
  // W tends to be one of the widest characters (and if the font is truly
  // fixed-width then any character will do).
  var characterWidth = ctx.measureText('WWWW').width / 4
}

if (typeof opacity === 'undefined')
  opacity = 1

frames = frames || self.data

var blocksByColor = {}

frames.forEach(function(f) {
  if (gemName && f.gemName !== gemName)
    return

  var r = self.dataToCanvas(self.frameRect(f))

  if (r.x >= self.width ||
      r.y >= self.height ||
      (r.x + r.width) <= 0 ||
      (r.y + r.height) <= 0) {
    return
  }

  var i = self.info[f.frame_id]
  var color = colorString(i.color, opacity)
  var colorBlocks = blocksByColor[color]
  if (!colorBlocks)
    colorBlocks = blocksByColor[color] = []
  colorBlocks.push({ rect: r, text: f.frame })
})

var textBlocks = []

Object.keys(blocksByColor).forEach(function(color) {
  ctx.fillStyle = color

  blocksByColor[color].forEach(function(block) {
    if (opacity < 1)
      ctx.clearRect(block.rect.x, block.rect.y, block.rect.width, block.rect.height)

    ctx.fillRect(block.rect.x, block.rect.y, block.rect.width, block.rect.height)

    if (block.rect.width > 4 && block.rect.height > 4)
      ctx.strokeRect(block.rect.x, block.rect.y, block.rect.width, block.rect.height)

    if (!self.showLabels || block.rect.width / characterWidth < 4)
      return

    textBlocks.push(block)
  })
})

ctx.fillStyle = '#000'
textBlocks.forEach(function(block) {
  var text = block.text
  var textRect = Object.assign({}, block.rect)
  textRect.x += 1
  textRect.width -= 2
  if (textRect.width < text.length * characterWidth * 0.75)
    text = centerTruncate(block.text, Math.floor(textRect.width / characterWidth))
  ctx.fillText(text, textRect.x, textRect.y + textRect.height / 2, textRect.width)
})

}

Flamechart.prototype.frameAtPoint = function(x, y) {

var self = this

return self.data.find(function(d) {
  var r = self.dataToCanvas(self.frameRect(d))

  return r.x <= x
    && r.x + r.width >= x
    && r.y <= y
    && r.y + r.height >= y
})

}

function MainFlamechart(canvas, data, dataRange, info) {

var self = this

Flamechart.call(self, canvas, data, dataRange, info)

self.showLabels = true

self.canvas.addEventListener('mousedown', self.onMouseDown.bind(self))
self.canvas.addEventListener('mousemove', self.onMouseMove.bind(self))
self.canvas.addEventListener('mouseout', self.onMouseOut.bind(self))
self.canvas.addEventListener('wheel', self.onWheel.bind(self))

}

MainFlamechart.prototype = Object.create(Flamechart.prototype)

MainFlamechart.prototype.setDimensionsNow = function(width, height) {

var self = this

var viewport = Object.assign({}, self.viewport)
viewport.height = height / 16
self.setViewport(viewport)

CanvasView.prototype.setDimensionsNow.call(self, width, height)

}

MainFlamechart.prototype.onMouseDown = function(e) {

var self = this

if (e.button !== 0)
  return

captureMouse({
  mouseup: self.onMouseUp.bind(self),
  mousemove: self.onMouseMove.bind(self),
})

var clientRect = self.canvas.getBoundingClientRect()
var currentX = e.clientX - clientRect.left
var currentY = e.clientY - clientRect.top

self.dragging = true
self.dragInfo = {
  mouse: { x: currentX, y: currentY },
  viewport: { x: self.viewport.x, y: self.viewport.y },
}

e.preventDefault()

}

MainFlamechart.prototype.onMouseUp = function(e) {

var self = this

if (!self.dragging)
  return

releaseCapture()

self.dragging = false
e.preventDefault()

}

MainFlamechart.prototype.onMouseMove = function(e) {

var self = this

var clientRect = self.canvas.getBoundingClientRect()
var currentX = e.clientX - clientRect.left
var currentY = e.clientY - clientRect.top

if (self.dragging) {
  var viewport = Object.assign({}, self.viewport)
  viewport.x = self.dragInfo.viewport.x - (currentX - self.dragInfo.mouse.x) * viewport.width / self.width
  viewport.y = self.dragInfo.viewport.y - (currentY - self.dragInfo.mouse.y) * viewport.height / self.height
  viewport.x = Math.min(self.dataRange.maxX - viewport.width, Math.max(self.dataRange.minX, viewport.x))
  viewport.y = Math.min(self.dataRange.maxY - viewport.height, Math.max(self.dataRange.minY, viewport.y))
  self.setViewport(viewport)
  return
}

var frame = self.frameAtPoint(currentX, currentY)
self.setHoveredFrame(frame)

}

MainFlamechart.prototype.onMouseOut = function() {

var self = this

if (self.dragging)
  return

self.setHoveredFrame(null)

}

MainFlamechart.prototype.onWheel = function(e) {

var self = this

var deltaX = e.deltaX
var deltaY = e.deltaY

if (e.deltaMode == WheelEvent.prototype.DOM_DELTA_LINE) {
  deltaX *= 11
  deltaY *= 11
}

if (e.shiftKey) {
  if ('webkitDirectionInvertedFromDevice' in e) {
    if (e.webkitDirectionInvertedFromDevice)
      deltaY *= -1
  } else if (/Mac OS X/.test(navigator.userAgent)) {
    // Assume that most Mac users have "Scroll direction: Natural" enabled.
    deltaY *= -1
  }

  var mouseWheelZoomSpeed = 1 / 120
  self.handleZoomGesture(Math.pow(1.2, -(deltaY || deltaX) * mouseWheelZoomSpeed), e.offsetX)
  e.preventDefault()
  return
}

var viewport = Object.assign({}, self.viewport)
viewport.x += deltaX * viewport.width / (self.dataRange.maxX - self.dataRange.minX)
viewport.x = Math.min(self.dataRange.maxX - viewport.width, Math.max(self.dataRange.minX, viewport.x))
viewport.y += (deltaY / 8) * viewport.height / (self.dataRange.maxY - self.dataRange.minY)
viewport.y = Math.min(self.dataRange.maxY - viewport.height, Math.max(self.dataRange.minY, viewport.y))
self.setViewport(viewport)
e.preventDefault()

}

MainFlamechart.prototype.handleZoomGesture = function(zoom, originX) {

var self = this

var viewport = Object.assign({}, self.viewport)
var ratioX = originX / self.width

var newWidth = Math.min(viewport.width / zoom, self.dataRange.maxX - self.dataRange.minX)
viewport.x = Math.max(self.dataRange.minX, viewport.x + (viewport.width - newWidth) * ratioX)
viewport.width = Math.min(newWidth, self.dataRange.maxX - viewport.x)

self.setViewport(viewport)

}

MainFlamechart.prototype.setHoveredFrame = function(frame) {

var self = this

if (frame === self.hoveredFrame)
  return

var previous = self.hoveredFrame
self.hoveredFrame = frame

self.dispatch('hoveredframechanged', { previous: previous, current: self.hoveredFrame })

}

function OverviewFlamechart(container, viewportOverlay, data, dataRange, info) {

var self = this

Flamechart.call(self, container.querySelector('.overview'), data, dataRange, info)

self.container = container

self.showLabels = false

self.viewportOverlay = viewportOverlay

self.canvas.addEventListener('mousedown', self.onMouseDown.bind(self))
self.viewportOverlay.addEventListener('mousedown', self.onOverlayMouseDown.bind(self))

}

OverviewFlamechart.prototype = Object.create(Flamechart.prototype)

OverviewFlamechart.prototype.setViewportOverlayRect = function® {

var self = this

self.viewportOverlayRect = r

r = self.dataToCanvas(r)
r.width = Math.max(2, r.width)
r.height = Math.max(2, r.height)

if ('transform' in self.viewportOverlay.style) {
  self.viewportOverlay.style.transform = 'translate(' + r.x + 'px, ' + r.y + 'px) scale(' + r.width + ', ' + r.height + ')'
} else {
  self.viewportOverlay.style.left = r.x
  self.viewportOverlay.style.top = r.y
  self.viewportOverlay.style.width = r.width
  self.viewportOverlay.style.height = r.height
}

}

OverviewFlamechart.prototype.onMouseDown = function(e) {

var self = this

captureMouse({
  mouseup: self.onMouseUp.bind(self),
  mousemove: self.onMouseMove.bind(self),
})

self.dragging = true
self.dragStartX = e.clientX - self.canvas.getBoundingClientRect().left

self.handleDragGesture(e)

e.preventDefault()

}

OverviewFlamechart.prototype.onMouseUp = function(e) {

var self = this

if (!self.dragging)
  return

releaseCapture()

self.dragging = false

self.handleDragGesture(e)

e.preventDefault()

}

OverviewFlamechart.prototype.onMouseMove = function(e) {

var self = this

if (!self.dragging)
  return

self.handleDragGesture(e)

e.preventDefault()

}

OverviewFlamechart.prototype.handleDragGesture = function(e) {

var self = this

var clientRect = self.canvas.getBoundingClientRect()
var currentX = e.clientX - clientRect.left
var currentY = e.clientY - clientRect.top

if (self.dragCurrentX === currentX)
  return

self.dragCurrentX = currentX

var minX = Math.min(self.dragStartX, self.dragCurrentX)
var maxX = Math.max(self.dragStartX, self.dragCurrentX)

var rect = Object.assign({}, self.viewportOverlayRect)
rect.x = minX / self.width * self.viewport.width + self.viewport.x
rect.width = Math.max(self.viewport.width / 1000, (maxX - minX) / self.width * self.viewport.width)

rect.y = Math.max(self.viewport.y, Math.min(self.viewport.height - self.viewport.y, currentY / self.height * self.viewport.height + self.viewport.y - rect.height / 2))

self.setViewportOverlayRect(rect)
self.dispatch('overlaychanged', { current: self.viewportOverlayRect })

}

OverviewFlamechart.prototype.onOverlayMouseDown = function(e) {

var self = this

captureMouse({
  mouseup: self.onOverlayMouseUp.bind(self),
  mousemove: self.onOverlayMouseMove.bind(self),
})

self.overlayDragging = true
self.overlayDragInfo = {
  mouse: { x: e.clientX, y: e.clientY },
  rect: Object.assign({}, self.viewportOverlayRect),
}
self.viewportOverlay.classList.add('moving')

self.handleOverlayDragGesture(e)

e.preventDefault()

}

OverviewFlamechart.prototype.onOverlayMouseUp = function(e) {

var self = this

if (!self.overlayDragging)
  return

releaseCapture()

self.overlayDragging = false
self.viewportOverlay.classList.remove('moving')

self.handleOverlayDragGesture(e)

e.preventDefault()

}

OverviewFlamechart.prototype.onOverlayMouseMove = function(e) {

var self = this

if (!self.overlayDragging)
  return

self.handleOverlayDragGesture(e)

e.preventDefault()

}

OverviewFlamechart.prototype.handleOverlayDragGesture = function(e) {

var self = this

var deltaX = (e.clientX - self.overlayDragInfo.mouse.x) / self.width * self.viewport.width
var deltaY = (e.clientY - self.overlayDragInfo.mouse.y) / self.height * self.viewport.height

var rect = Object.assign({}, self.overlayDragInfo.rect)
rect.x += deltaX
rect.y += deltaY
rect.x = Math.max(self.viewport.x, Math.min(self.viewport.x + self.viewport.width - rect.width, rect.x))
rect.y = Math.max(self.viewport.y, Math.min(self.viewport.y + self.viewport.height - rect.height, rect.y))

self.setViewportOverlayRect(rect)
self.dispatch('overlaychanged', { current: self.viewportOverlayRect })

}

function FlamegraphView(data, info, sortedGems) {

var self = this

self.data = data
self.info = info

self.dataRange = self.computeDataRange()

self.mainChart = new MainFlamechart(document.querySelector('.flamegraph'), data, self.dataRange, info)
self.overview = new OverviewFlamechart(document.querySelector('.overview-container'), document.querySelector('.overview-viewport-overlay'), data, self.dataRange, info)
self.infoElement = document.querySelector('.info')

self.mainChart.on('hoveredframechanged', self.onHoveredFrameChanged.bind(self))
self.mainChart.on('viewportchanged', self.onViewportChanged.bind(self))
self.overview.on('overlaychanged', self.onOverlayChanged.bind(self))

var legend = document.querySelector('.legend')
self.renderLegend(legend, sortedGems)

legend.addEventListener('mousemove', self.onLegendMouseMove.bind(self))
legend.addEventListener('mouseout', self.onLegendMouseOut.bind(self))

window.addEventListener('resize', self.updateDimensions.bind(self))

self.updateDimensions()

}

FlamegraphView.prototype.updateDimensions = function() {

var self = this

var margin = {top: 10, right: 10, bottom: 10, left: 10}
var width = window.innerWidth - 200 - margin.left - margin.right
var mainChartHeight = Math.ceil(window.innerHeight * 0.80) - margin.top - margin.bottom
var overviewHeight = Math.floor(window.innerHeight * 0.20) - 60 - margin.top - margin.bottom

self.mainChart.setDimensions(width + margin.left + margin.right, mainChartHeight + margin.top + margin.bottom)
self.overview.setDimensions(width + margin.left + margin.right, overviewHeight + margin.top + margin.bottom)
self.overview.setViewportOverlayRect(self.mainChart.viewport)

}

FlamegraphView.prototype.computeDataRange = function() {

var self = this

var range = { minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity }
self.data.forEach(function(d) {
  range.minX = Math.min(range.minX, d.x)
  range.minY = Math.min(range.minY, d.y)
  range.maxX = Math.max(range.maxX, d.x + d.width)
  range.maxY = Math.max(range.maxY, d.y + 1)
})

return range

}

FlamegraphView.prototype.onHoveredFrameChanged = function(data) {

var self = this

self.updateInfo(data.current)

if (data.previous)
  self.repaintFrames(1, self.info[data.previous.frame_id].frames)

if (data.current)
  self.repaintFrames(0.5, self.info[data.current.frame_id].frames)

}

FlamegraphView.prototype.repaintFrames = function(opacity, frames) {

var self = this

self.mainChart.paint(opacity, frames)
self.overview.paint(opacity, frames)

}

FlamegraphView.prototype.updateInfo = function(frame) {

var self = this

if (!frame) {
  self.infoElement.style.backgroundColor = ''
  self.infoElement.querySelector('.frame').textContent = ''
  self.infoElement.querySelector('.file').textContent = ''
  self.infoElement.querySelector('.samples').textContent = ''
  self.infoElement.querySelector('.exclusive').textContent = ''
  return
}

var i = self.info[frame.frame_id]
var shortFile = frame.file.replace(/^.+\/(gems|app|lib|config|jobs)/, '$1')
var sData = self.samplePercentRaw(i.samples.length, frame.topFrame ? frame.topFrame.exclusiveCount : 0)

self.infoElement.style.backgroundColor = colorString(i.color, 1)
self.infoElement.querySelector('.frame').textContent = frame.frame
self.infoElement.querySelector('.file').textContent = shortFile
self.infoElement.querySelector('.samples').textContent = sData[0] + ' samples (' + sData[1] + '%)'
if (sData[3])
  self.infoElement.querySelector('.exclusive').textContent = sData[2] + ' exclusive (' + sData[3] + '%)'
else
  self.infoElement.querySelector('.exclusive').textContent = ''

}

FlamegraphView.prototype.samplePercentRaw = function(samples, exclusive) {

var self = this

var ret = [samples, ((samples / self.dataRange.maxX) * 100).toFixed(2)]
if (exclusive)
  ret = ret.concat([exclusive, ((exclusive / self.dataRange.maxX) * 100).toFixed(2)])
return ret

}

FlamegraphView.prototype.onViewportChanged = function(data) {

var self = this

self.overview.setViewportOverlayRect(data.current)

}

FlamegraphView.prototype.onOverlayChanged = function(data) {

var self = this

self.mainChart.setViewport(data.current)

}

FlamegraphView.prototype.renderLegend = function(element, sortedGems) {

var self = this

var fragment = document.createDocumentFragment()

sortedGems.forEach(function(gem) {
  var sData = self.samplePercentRaw(gem.samples.length)
  var node = document.createElement('div')
  node.className = 'legend-gem'
  node.setAttribute('data-gem-name', gem.name)
  node.style.backgroundColor = colorString(gem.color, 1)

  var span = document.createElement('span')
  span.style.float = 'right'
  span.textContent = sData[0] + 'x'
  span.appendChild(document.createElement('br'))
  span.appendChild(document.createTextNode(sData[1] + '%'))
  node.appendChild(span)

  var name = document.createElement('div')
  name.className = 'name'
  name.textContent = gem.name
  name.appendChild(document.createElement('br'))
  name.appendChild(document.createTextNode('\u00a0'))
  node.appendChild(name)

  fragment.appendChild(node)
})

element.appendChild(fragment)

}

FlamegraphView.prototype.onLegendMouseMove = function(e) {

var self = this

var gemElement = e.target.closest('.legend-gem')
var gemName = gemElement.getAttribute('data-gem-name')

if (self.hoveredGemName === gemName)
  return

if (self.hoveredGemName) {
  self.mainChart.paint(1, null, self.hoveredGemName)
  self.overview.paint(1, null, self.hoveredGemName)
}

self.hoveredGemName = gemName

self.mainChart.paint(0.5, null, self.hoveredGemName)
self.overview.paint(0.5, null, self.hoveredGemName)

}

FlamegraphView.prototype.onLegendMouseOut = function() {

var self = this

if (!self.hoveredGemName)
  return

self.mainChart.paint(1, null, self.hoveredGemName)
self.overview.paint(1, null, self.hoveredGemName)
self.hoveredGemName = null

}

var capturingListeners = null function captureMouse(listeners) {

if (capturingListeners)
  releaseCapture()

for (var name in listeners)
  document.addEventListener(name, listeners[name], true)
capturingListeners = listeners

}

function releaseCapture() {

if (!capturingListeners)
  return

for (var name in capturingListeners)
  document.removeEventListener(name, capturingListeners[name], true)
capturingListeners = null

}

function guessGem(frame) {

var split = frame.split('/gems/')
if (split.length === 1) {
  split = frame.split('/app/')
  if (split.length === 1) {
    split = frame.split('/lib/')
  } else {
    return split[split.length - 1].split('/')[0]
  }

  split = split[Math.max(split.length - 2, 0)].split('/')
  return split[split.length - 1].split(':')[0]
}
else
{
  return split[split.length - 1].split('/')[0].split('-', 2)[0]
}

}

function color() {

var r = parseInt(205 + Math.random() * 50)
var g = parseInt(Math.random() * 230)
var b = parseInt(Math.random() * 55)
return [r, g, b]

}

// stackoverflow.com/a/7419630 function rainbow(numOfSteps, step) {

  // This function generates vibrant, "evenly spaced" colours (i.e. no clustering). This is ideal for creating easily distiguishable vibrant markers in Google Maps and other apps.
  // Adam Cole, 2011-Sept-14
  // HSV to RBG adapted from: http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
var r, g, b
var h = step / numOfSteps
var i = ~~(h * 6)
var f = h * 6 - i
var q = 1 - f
switch (i % 6) {
  case 0: r = 1, g = f, b = 0; break
  case 1: r = q, g = 1, b = 0; break
  case 2: r = 0, g = 1, b = f; break
  case 3: r = 0, g = q, b = 1; break
  case 4: r = f, g = 0, b = 1; break
  case 5: r = 1, g = 0, b = q; break
}
return [Math.floor(r * 255), Math.floor(g * 255), Math.floor(b * 255)]

}

function colorString(color, opacity) {

if (typeof opacity === 'undefined')
  opacity = 1
return 'rgba(' + color.join(',') + ',' + opacity + ')'

}

// stackoverflow.com/questions/1960473/unique-values-in-an-array function getUnique(orig) {

var o = {}
for (var i = 0; i < orig.length; i++) o[orig[i]] = 1
return Object.keys(o)

}

function centerTruncate(text, maxLength) {

var charactersToKeep = maxLength - 1
if (charactersToKeep <= 0)
  return ''
if (text.length <= charactersToKeep)
  return text

var prefixLength = Math.ceil(charactersToKeep / 2)
var suffixLength = charactersToKeep - prefixLength
var prefix = text.substr(0, prefixLength)
var suffix = suffixLength > 0 ? text.substr(-suffixLength) : ''

return [prefix, '\u2026', suffix].join('')

}

function flamegraph(data) {

var info = {}
data.forEach(function(d) {
  var i = info[d.frame_id]
  if (!i)
    info[d.frame_id] = i = {frames: [], samples: [], color: color()}
  i.frames.push(d)
  for (var j = 0; j < d.width; j++) {
    i.samples.push(d.x + j)
  }
})

// Samples may overlap on the same line
for (var r in info) {
  if (info[r].samples) {
    info[r].samples = getUnique(info[r].samples)
  }
}

// assign some colors, analyze samples per gem
var gemStats = {}
var topFrames = {}
var lastFrame = {frame: 'd52e04d-df28-41ed-a215-b6ec840a8ea5', x: -1}

data.forEach(function(d) {
  var gem = guessGem(d.file)
  var stat = gemStats[gem]
  d.gemName = gem

  if (!stat) {
    gemStats[gem] = stat = {name: gem, samples: [], frames: []}
  }

  stat.frames.push(d.frame_id)
  for (var j = 0; j < d.width; j++) {
    stat.samples.push(d.x + j)
  }
  // This assumes the traversal is in order
  if (lastFrame.x !== d.x) {
    var topFrame = topFrames[lastFrame.frame_id]
    if (!topFrame) {
      topFrames[lastFrame.frame_id] = topFrame = {exclusiveCount: 0}
    }
    topFrame.exclusiveCount += 1
    lastFrame.topFrame = topFrame
  }
  lastFrame = d
})

var topFrame = topFrames[lastFrame.frame_id]
if (!topFrame) {
  topFrames[lastFrame.frame_id] = topFrame = {exclusiveCount: 0}
}
topFrame.exclusiveCount += 1
lastFrame.topFrame = topFrame

var totalGems = 0
for (var k in gemStats) {
  totalGems++
  gemStats[k].samples = getUnique(gemStats[k].samples)
}

var gemsSorted = Object.keys(gemStats).map(function(k) { return gemStats[k] })
gemsSorted.sort(function(a, b) { return b.samples.length - a.samples.length })

var currentIndex = 0
gemsSorted.forEach(function(stat) {
  stat.color = rainbow(totalGems, currentIndex)
  currentIndex += 1

  for (var x = 0; x < stat.frames.length; x++) {
    info[stat.frames[x]].color = stat.color
  }
})

new FlamegraphView(data, info, gemsSorted)

}