1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
6 * This is a view class showing flot graph.
7 * @param {Object} profiler Must have addListener method.
10 var GraphView = function(profiler) {
11 this.profiler_ = profiler;
12 this.placeholder_ = '#graph-div';
13 // Update graph view and menu view when profiler model changed.
14 profiler.addListener('changed', this.redraw_.bind(this));
18 * Generate lines for flot plotting.
19 * @param {Array.<Object>} models
20 * @return {Array.<Object>}
23 GraphView.prototype.generateLines_ = function(models) {
24 function mergeCategoryTree(snapNode, treeNode) {
25 if ('children' in snapNode) {
26 // If |snapNode| is not a leaf node, we should go deeper.
27 if (!('children' in treeNode))
28 treeNode.children = {};
29 snapNode.children.forEach(function(child) {
30 if (!(child.id in treeNode.children))
31 treeNode.children[child.id] = {};
32 mergeCategoryTree(child, treeNode.children[child.id]);
35 treeNode.name = snapNode.name;
39 function getCategoriesMap(node, id, categories) {
40 if ('children' in node) {
41 Object.keys(node.children).forEach(function(id) {
42 getCategoriesMap(node.children[id], id, categories);
45 if (!(id in categories)) {
50 for (var i = 0; i < models.length; ++i)
51 categories[id].data.push([models[i].time - models[0].time, 0]);
56 function getLineValues(snapNode, index, categories) {
57 if ('children' in snapNode) {
58 snapNode.children.forEach(function(child) {
59 getLineValues(child, index, categories);
62 categories[snapNode.id].data[index][1] = snapNode.size;
66 // TODO(dmikurube): Remove this function after adding "color" attribute
68 function getHashColorCode(id) {
70 for (var i = 0; i < id.length; ++i)
71 color = (color * 0x57 + id.charCodeAt(i)) & 0xffffff;
72 color = color.toString(16);
73 while (color.length < 6)
78 var categoryTree = {};
79 models.forEach(function(model) {
80 mergeCategoryTree(model, categoryTree);
82 // Convert layout of categories from tree style to hash map style.
84 getCategoriesMap(categoryTree, '', categoryMap);
85 // Get size of each category.
86 models.forEach(function(model, index) {
87 getLineValues(model, index, categoryMap);
90 return Object.keys(categoryMap).map(function(id) {
92 color: getHashColorCode(id),
93 data: categoryMap[id].data,
95 label: categoryMap[id].name
101 * Update graph view when model updated.
102 * TODO(junjianx): use redraw function to improve perfomance.
103 * @param {Array.<Object>} models
106 GraphView.prototype.redraw_ = function(models) {
108 var data = this.generateLines_(models);
110 var $graph = $(this.placeholder_);
111 this.graph_ = $.plot($graph, data, {
114 lines: { show: true, fill: true }
122 // Bind click event so that user can select category by clicking stack
123 // area. It firstly checks x range which clicked point is in, and all lines
124 // share same x values, so it is checked only once at first. Secondly, it
125 // checked y range by accumulated y values because this is a stack graph.
126 $graph.bind('plotclick', function(event, pos, item) {
127 // Get newest lines data from graph.
128 var lines = self.graph_.getData();
129 // If only <=1 line exists or axis area clicked, return.
130 var right = binarySearch.call(lines[0].data.map(function(point) {
133 if (lines.length <= 1 || right === lines.length || right === 0)
136 // Calculate interpolate y value of every line.
137 for (var i = 0; i < lines.length; ++i) {
138 var line = lines[i].data;
139 // [left, right] is the range including clicked point.
140 var left = right - 1;
143 y: (leftPoint ? leftPoint.y : 0) + line[left][1]
147 y: (rightPoint ? rightPoint.y : 0) + line[right][1]
150 // Calculate slope of the linear equation.
151 var slope = (rightPoint.y - leftPoint.y) / (rightPoint.x - leftPoint.x);
152 var interpolateY = slope * (pos.x - rightPoint.x) + rightPoint.y;
153 if (interpolateY >= pos.y)
157 // If pos.y is higher than all lines, return.
158 if (i === lines.length) {
159 self.profiler_.setSelected(null);
163 self.profiler_.setSelected(lines[i].id, pos);
166 this.graph_.setData(data);
167 this.graph_.setupGrid();