<!DOCTYPE html> <html> <head> <script src=“code.jquery.com/jquery-1.9.1.min.js”>> <script src=“cdnjs.cloudflare.com/ajax/libs/d3/3.0.8/d3.min.js”>> <!– <script src=“cdnjs.cloudflare.com/ajax/libs/sugar/1.3.8/sugar.min.js”>> –> <meta charset=utf-8 /> <title>Flame Graph of Page</title> <style>

.info {height: 40px;}
.legend div {
  display: block;
  float: left;
  width: 150px;
  margin: 0 8px 8px;
  padding: 4px;
  height: 50px;
}

</style> </head> <body>

<div class="graph"></div>
<div class="info"></div>
<div class="legend"></div>

<script>

var data = DATA ; var maxX = 0; var maxY = 0;

debounce = function(func, wait, trickle) {

var timeout;

timeout = null;
return function() {
  var args, context, currentWait, later;
  context = this;
  args = arguments;
  later = function() {
    timeout = null;
    return func.apply(context, args);
  };

  if (timeout && trickle) {
    // already queued, let it through
    return;
  }

  if (typeof wait === "function") {
    currentWait = wait();
  } else {
    currentWait = wait;
  }

  if (timeout) {
    clearTimeout(timeout);
  }

  timeout = setTimeout(later, currentWait);
  return timeout;
};

};

var guessGem = function(frame) {

var split = frame.split('/gems/'); 
if(split.length == 1) {
  split = frame.split('/app/');
  if(split.length == 1) {
    split = frame.split('/lib/');
  }

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

}

var guessMethod = function(frame) {

var split = frame.split('`');
if(split.length == 2) {
  return split[1].split("'")[0];
}
return '?';

}

var guessFile = function(frame) {

var split = frame.split(".rb:");
if(split.length == 2) {
  split = split[0].split('/');
  return split[split.length - 1];
}
return "";

}

$.each(data, function(){

maxX = Math.max(maxX, this.x + this.width);
maxY = Math.max(maxY, this.y);
this.shortName =    guessGem(this.frame) + " " + guessFile(this.frame) + " "     guessMethod(this.frame);

});

var width = $(window).width(); var height = $(window).height() / 1.2;

$('.graph').width(width).height(height);

var xScale = d3.scale.linear()

    .domain([0, maxX])
.range([0, width]);

var yScale = d3.scale.linear()

.domain([0, maxY])
.range([0,height]);

var realHeight = 0; var debouncedHeightCheck = debounce(function(){

if (realHeight > 15) {
  svg.selectAll('text').attr('display','show');
} else {
  svg.selectAll('text').attr('display','none');
}

}, 200);

function zoom() {

svg.attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")"); 

realHeight = yScale(1) * d3.event.scale;
debouncedHeightCheck();

}

var svg = d3.select(“.graph”)

.append("svg")
.attr("width", "100%")
.attr("height", "100%")
.attr("pointer-events", "all")
.append('svg:g')
.call(d3.behavior.zoom().on("zoom", zoom))
.append('svg:g');

// so zoom works everywhere svg.append(“rect”)

.attr("x",function(d) { return xScale(0); })
.attr("y",function(d) { return yScale(0);})
.attr("width", function(d){return xScale(maxX);})
.attr("height", yScale(maxY))
.attr("fill", "white");

var color = function() {

var r = parseInt(205 + Math.random() * 50);
var g = parseInt(Math.random() * 230);
var b = parseInt(Math.random() * 55);
return "rgb(" + r + "," + g + "," + b + ")";

} var info = {};

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

var o = {}, a = []
for (var i = 0; i < this.length; i++) o[this[i]] = 1
for (var e in o) a.push(e)
return a

}

var samplePercent = function(samples){

return "(" + samples + 
  " sample" + (samples == 1 ? "" : "s") + " - " + 
  ((samples / maxX) * 100).toFixed(2) + "%)";

}

var mouseover = function(d) {

var i = info[d.frame];
$('.info').text( d.frame + " " + samplePercent(i.samples.length));
d3.selectAll(i.nodes)
   .attr('opacity',0.5);

};

var mouseout = function(d) {

var i = info[d.frame];
$('.info').text("");
d3.selectAll(i.nodes)
   .attr('opacity',1);

};

// stackoverflow.com/a/7419630 var rainbow = function(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;
}
var c = "#" + ("00" + (~ ~(r * 255)).toString(16)).slice(-2) + ("00" + (~ ~(g * 255)).toString(16)).slice(-2) + ("00" + (~ ~(b * 255)).toString(16)).slice(-2);
return (c);

}

// assign some colors, analyze samples per gem var gemStats = {}

$.each(data, function(){

var gem = guessGem(this.frame);
var stat = gemStats[gem];

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

stat.frames.push(this.frame);
for(var j=0; j < this.width; j++){
  stat.samples.push(this.x + j);
}

});

var totalGems = 0; $.each(gemStats, function(){totalGems++;});

var currentIndex = 0; $.each(gemStats, function(k,stat){

stat.color = rainbow(totalGems, currentIndex);
stat.samples = stat.samples.getUnique();

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

currentIndex += 1;

});

// see: bl.ocks.org/mundhradevang/1387786 function fontSize(d,i) {

var size = yScale(1) / 3;
// var words = d.shortName.split(' ');
var word = d.shortName; // words[0];
var width = xScale(d.width+100);
var height = yScale(1);
var length = 0;
d3.select(this).style("font-size", size + "px").text(word);
while(((this.getBBox().width >= width) || (this.getBBox().height >= height)) && (size > 12))
 {
  size -= 0.1;
  d3.select(this).style("font-size", size + "px");
 }

 d3.select(this).attr("dy", size);

}

svg.selectAll(“g”)

    .data(data)
    .enter()
.append("g")
.each(function(){
  d3.select(this)
  .append("rect")
  .attr("x",function(d) { return xScale(d.x-1); })
  .attr("y",function(d) { return yScale(maxY - d.y);})
  .attr("width", function(d){return xScale(d.width);})
  .attr("height", yScale(1))
  .attr("fill", function(d){
    var i = info[d.frame];
    if(!i) {
      info[d.frame] = i = {nodes: [], samples: [], color: color()};
    }
    i.nodes.push(this);
    for(var j=0; j < d.width; j++){
      i.samples.push(d.x + j);
    }
    return i.color;
  })
  .on("mouseover", mouseover)
  .on("mouseout", mouseout);

  d3.select(this)
    .append("text")
    .attr("x",function(d) { return xScale(d.x - 0.98); })
    .attr("y",function(d) { return yScale(maxY - d.y);})
    .on("mouseover", mouseover)
    .on("mouseout", mouseout)
    .each(fontSize)
    .attr("display", "none");

});

// Samples may overlap on the same line for (var r in info) {

if (info[r].samples) {
  info[r].samples = info[r].samples.getUnique();
}

};

// render the legend $.each(gemStats, function(k,v){

var node = $("<div></div>")
  .css("background-color", v.color)
  .text(k + " " + samplePercent(v.samples.length)) ;
$('.legend').append(node);

});

</script>

</body> </html>