14a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan SinclairHeatmap.prototype.draw = function() {
24a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  this.drawHeatmap();
34a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  for (var i = 0; i < this.drawTraces.length; ++i)
44a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if (this.drawTraces[i])
54a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      this.drawTrace(i, 1);
64a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair};
74a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
84a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan SinclairHeatmap.prototype.drawHeatmap = function() {
94a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  this.context.clearRect(0, 0, this.w, this.h);
104a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
114a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  this.context.save();
124a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  this.context.scale(this.w / this.revisions.length, this.h / this.resolution);
134a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
144a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  var counts = [];
154a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  for (var time = 0; time < this.revisions.length; ++time) {
164a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    var revision = this.revisions[time];
174a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    for (var bucket in this.data[revision]) {
184a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      counts.push(this.data[revision][bucket].length);
194a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    }
204a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  }
214a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  counts.sort(function(a, b) {return a - b});
224a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  var cutoff = percentile(counts, 0.9);
234a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  if (cutoff < 2)
244a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    cutoff = 2;
254a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
264a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  for (var time = 0; time < this.revisions.length; ++time) {
274a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    var revision = this.revisions[time];
284a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    for (var bucket in this.data[revision]) {
294a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      var count = this.data[revision][bucket].length;
304a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
314a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      // Calculate average color across all traces in bucket.
324a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      var r = 0, g = 0, b = 0;
334a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      for (var i = 0; i < this.data[revision][bucket].length; ++i) {
344a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        var trace = this.data[revision][bucket][i];
354a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        r += nthColor(trace)[0];
364a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        g += nthColor(trace)[1];
374a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        b += nthColor(trace)[2];
384a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      }
394a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      r /= count, g /= count, b /= count;
404a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      var brightness = mapRange(count / cutoff, 0, 1, 2, 0.5);
414a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
424a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      // Draw!
434a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      this.context.fillStyle = calculateColor(r, g, b, 1, brightness);
444a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      this.context.fillRect(time, bucket, 1, 1);
454a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    }
464a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  }
474a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
484a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  this.context.restore();
494a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair};
504a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
514a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan SinclairHeatmap.prototype.drawTrace = function(trace, opacity) {
524a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  this.drawTraceLine(trace, 4, calculateColor(255, 255, 255, opacity, 1));
534a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  var color = calculateColor(nthColor(trace)[0], nthColor(trace)[1], nthColor(trace)[2], opacity, 1);
544a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  this.drawTraceLine(trace, 2, color);
554a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair};
564a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
574a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan SinclairHeatmap.prototype.drawTraceLine = function(trace, width, color) {
584a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  var revisionWidth = this.w / this.revisions.length;
594a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
604a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  this.context.save();
614a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  this.context.lineJoin = 'round';
624a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  this.context.lineWidth = width;
634a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  this.context.strokeStyle = color;
644a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  this.context.translate(revisionWidth / 2, 0);
654a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  this.context.beginPath();
664a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
674a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  var started = false;
684a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  for (var time = 0; time < this.revisions.length; ++time) {
694a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    var values = this.traces[this.revisions[time]][trace];
704a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    var sum = 0;
714a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    for (var value of values)
724a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      sum += value;
734a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    var value = sum / values.length;
744a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
754a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    var bucket = mapRange(value, this.min, this.max, 0, this.h);
764a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if (started) {
774a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      this.context.lineTo(revisionWidth * time, bucket);
784a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    } else {
794a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      this.context.moveTo(revisionWidth * time, bucket);
804a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      started = true;
814a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    }
824a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  }
834a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
844a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  this.context.stroke();
854a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  this.context.restore();
864a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair}
874a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
884a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan SinclairHeatmap.prototype.scaleCanvas = function() {
894a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  this.canvas.width = this.canvas.clientWidth * window.devicePixelRatio;
904a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  this.canvas.height = this.canvas.clientHeight * window.devicePixelRatio;
914a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  this.context.scale(window.devicePixelRatio, window.devicePixelRatio);
924a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
934a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  this.w = this.canvas.clientWidth, this.h = this.canvas.clientHeight;
944a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
954a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  // Flip canvas.
964a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  this.context.scale(1, -1);
974a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  this.context.translate(0, -this.h);
984a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair};
99