var OUTLIER_THRESHOLD = 0.05; var MARGIN_SIZE = 0.10; Heatmap.prototype.calculate = function(data) { this.cleanUpData(data); this.calculateBounds(); this.calculateHeatmap(); this.drawTraces = []; for (var i = 0; i < data.length; ++i) this.drawTraces.push(false); }; Heatmap.prototype.cleanUpData = function(data) { // Data, indexed by revision and trace. this.traces = {}; for (var bot in data) { var bot_data = data[bot]; for (var run_index = 0; run_index < bot_data.length; ++run_index) { var run_data = bot_data[run_index]; if (run_data == null) continue var stories_data = run_data['user_story_runs']; for (var story_index = 0; story_index < stories_data.length; ++story_index) { var story_data = stories_data[story_index]; var story_name = story_data['user_story']; if (story_name == 'summary') continue values = story_data['values']; var index = bot_data.length - run_index - 1; if (!this.traces[index]) this.traces[index] = {}; this.traces[index][story_index] = values; } } } this.revisions = Object.keys(this.traces).sort(); }; Heatmap.prototype.calculateBounds = function() { var values = []; for (var revision in this.traces) for (var trace in this.traces[revision]) for (var value of this.traces[revision][trace]) values.push(value); // Exclude OUTLIER_THRESHOLD% of the points. values.sort(function(a, b) {return a - b}); this.min = percentile(values, OUTLIER_THRESHOLD / 2); this.max = percentile(values, -OUTLIER_THRESHOLD / 2); // Ease bounds by adding margins. var margin = (this.max - this.min) * MARGIN_SIZE; this.min -= margin; this.max += margin; }; Heatmap.prototype.calculateHeatmap = function() { this.data = {}; for (var revision in this.traces) { for (var trace in this.traces[revision]) { for (var value of this.traces[revision][trace]) { var bucket = this.findBucket(value); if (this.data[revision] == null) this.data[revision] = {}; if (this.data[revision][bucket] == null) this.data[revision][bucket] = []; this.data[revision][bucket].push(trace); } } } }; Heatmap.prototype.findBucket = function(value) { var bucket = Math.floor(mapRange(value, this.min, this.max, 0, this.resolution)); return constrain(bucket, 0, this.resolution - 1); };