- add sources.
[platform/framework/web/crosswalk.git] / src / tools / deep_memory_profiler / visualizer / static / graph-view.js
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.
4
5 /**
6  * This is a view class showing flot graph.
7  * @param {Object} profiler Must have addListener method.
8  * @construct
9  */
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));
15 };
16
17 /**
18  * Generate lines for flot plotting.
19  * @param {Array.<Object>} models
20  * @return {Array.<Object>}
21  * @private
22  */
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]);
33       });
34     } else {
35       treeNode.name = snapNode.name;
36     }
37   }
38
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);
43       });
44     } else {
45       if (!(id in categories)) {
46         categories[id] = {
47           name: node.name,
48           data: []
49         };
50         for (var i = 0; i < models.length; ++i)
51           categories[id].data.push([models[i].time - models[0].time, 0]);
52       }
53     }
54   }
55
56   function getLineValues(snapNode, index, categories) {
57     if ('children' in snapNode) {
58       snapNode.children.forEach(function(child) {
59         getLineValues(child, index, categories);
60       });
61     } else {
62       categories[snapNode.id].data[index][1] = snapNode.size;
63     }
64   }
65
66   // TODO(dmikurube): Remove this function after adding "color" attribute
67   //     in each category.
68   function getHashColorCode(id) {
69     var color = 0;
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)
74       color = '0' + color;
75     return '#' + color;
76   }
77
78   var categoryTree = {};
79   models.forEach(function(model) {
80     mergeCategoryTree(model, categoryTree);
81   });
82   // Convert layout of categories from tree style to hash map style.
83   var categoryMap = {};
84   getCategoriesMap(categoryTree, '', categoryMap);
85   // Get size of each category.
86   models.forEach(function(model, index) {
87     getLineValues(model, index, categoryMap);
88   });
89
90   return Object.keys(categoryMap).map(function(id) {
91     return {
92       color: getHashColorCode(id),
93       data: categoryMap[id].data,
94       id: id,
95       label: categoryMap[id].name
96     };
97   });
98 };
99
100 /**
101  * Update graph view when model updated.
102  * TODO(junjianx): use redraw function to improve perfomance.
103  * @param {Array.<Object>} models
104  * @private
105  */
106 GraphView.prototype.redraw_ = function(models) {
107   var self = this;
108   var data = this.generateLines_(models);
109   if (!this.graph_) {
110     var $graph = $(this.placeholder_);
111     this.graph_ = $.plot($graph, data, {
112       series: {
113         stack: true,
114         lines: { show: true, fill: true }
115       },
116       grid: {
117         hoverable: true,
118         clickable: true
119       }
120     });
121
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) {
131         return point[0];
132       }), pos.x);
133       if (lines.length <= 1 || right === lines.length || right === 0)
134         return;
135
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;
141         var leftPoint = {
142           x: line[left][0],
143           y: (leftPoint ? leftPoint.y : 0) + line[left][1]
144         };
145         var rightPoint = {
146           x: line[right][0],
147           y: (rightPoint ? rightPoint.y : 0) + line[right][1]
148         };
149
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)
154           break;
155       }
156
157       // If pos.y is higher than all lines, return.
158       if (i === lines.length) {
159         self.profiler_.setSelected(null);
160         return;
161       }
162
163       self.profiler_.setSelected(lines[i].id, pos);
164     });
165   } else {
166     this.graph_.setData(data);
167     this.graph_.setupGrid();
168     this.graph_.draw();
169   }
170 };