Explicitly add python-numpy dependency to install-build-deps.
[chromium-blink-merge.git] / tools / deep_memory_profiler / visualizer / static / graph-view.js
blob2cd596e8e79981bd666ee2ad33db4f823986d377
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.
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));
17 /**
18 * Generate lines for flot plotting.
19 * @param {Array.<Object>} models
20 * @return {Array.<Object>}
21 * @private
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;
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: []
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);
60 });
61 } else {
62 categories[snapNode.id].data[index][1] = snapNode.size;
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;
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 });
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
97 });
101 * Update graph view when model updated.
102 * TODO(junjianx): use redraw function to improve perfomance.
103 * @param {Array.<Object>} models
104 * @private
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 }
116 grid: {
117 hoverable: true,
118 clickable: 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) {
131 return point[0];
132 }), pos.x);
133 if (lines.length <= 1 || right === lines.length || right === 0)
134 return;
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]
145 var rightPoint = {
146 x: line[right][0],
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)
154 break;
157 // If pos.y is higher than all lines, return.
158 if (i === lines.length) {
159 self.profiler_.setSelected(null);
160 return;
163 self.profiler_.setSelected(lines[i].id, pos);
165 } else {
166 this.graph_.setData(data);
167 this.graph_.setupGrid();
168 this.graph_.draw();