Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / third_party / trace-viewer / trace_viewer / cc / picture_ops_chart_summary_view.html
1 <!DOCTYPE html>
2 <!--
3 Copyright (c) 2013 The Chromium Authors. All rights reserved.
4 Use of this source code is governed by a BSD-style license that can be
5 found in the LICENSE file.
6 -->
7
8 <link rel="import" href="/base/ui.html">
9 <link rel="stylesheet" href="/cc/picture_ops_chart_summary_view.css">
10
11 <script>
12 'use strict';
13
14 tv.exportTo('cc', function() {
15   var OPS_TIMING_ITERATIONS = 3;
16   var CHART_PADDING_LEFT = 65;
17   var CHART_PADDING_RIGHT = 40;
18   var AXIS_PADDING_LEFT = 60;
19   var AXIS_PADDING_RIGHT = 35;
20   var AXIS_PADDING_TOP = 25;
21   var AXIS_PADDING_BOTTOM = 45;
22   var AXIS_LABEL_PADDING = 5;
23   var AXIS_TICK_SIZE = 10;
24   var LABEL_PADDING = 5;
25   var LABEL_INTERLEAVE_OFFSET = 15;
26   var BAR_PADDING = 5;
27   var VERTICAL_TICKS = 5;
28   var HUE_CHAR_CODE_ADJUSTMENT = 5.7;
29
30   /**
31    * Provides a chart showing the cumulative time spent in Skia operations
32    * during picture rasterization.
33    *
34    * @constructor
35    */
36   var PictureOpsChartSummaryView = tv.ui.define(
37       'picture-ops-chart-summary-view');
38
39   PictureOpsChartSummaryView.prototype = {
40     __proto__: HTMLUnknownElement.prototype,
41
42     decorate: function() {
43       this.picture_ = undefined;
44       this.pictureDataProcessed_ = false;
45
46       this.chartScale_ = window.devicePixelRatio;
47
48       this.chart_ = document.createElement('canvas');
49       this.chartCtx_ = this.chart_.getContext('2d');
50       this.appendChild(this.chart_);
51
52       this.opsTimingData_ = [];
53
54       this.chartWidth_ = 0;
55       this.chartHeight_ = 0;
56       this.requiresRedraw_ = true;
57
58       this.currentBarMouseOverTarget_ = null;
59
60       this.chart_.addEventListener('mousemove', this.onMouseMove_.bind(this));
61     },
62
63     get requiresRedraw() {
64       return this.requiresRedraw_;
65     },
66
67     set requiresRedraw(requiresRedraw) {
68       this.requiresRedraw_ = requiresRedraw;
69     },
70
71     get picture() {
72       return this.picture_;
73     },
74
75     set picture(picture) {
76       this.picture_ = picture;
77       this.pictureDataProcessed_ = false;
78
79       if (this.classList.contains('hidden'))
80         return;
81
82       this.processPictureData_();
83       this.requiresRedraw = true;
84       this.updateChartContents();
85     },
86
87     hide: function() {
88       this.classList.add('hidden');
89     },
90
91     show: function() {
92
93       this.classList.remove('hidden');
94
95       if (this.pictureDataProcessed_)
96         return;
97
98       this.processPictureData_();
99       this.requiresRedraw = true;
100       this.updateChartContents();
101
102     },
103
104     onMouseMove_: function(e) {
105
106       var lastBarMouseOverTarget = this.currentBarMouseOverTarget_;
107       this.currentBarMouseOverTarget_ = null;
108
109       var x = e.offsetX;
110       var y = e.offsetY;
111
112       var chartLeft = CHART_PADDING_LEFT;
113       var chartRight = this.chartWidth_ - CHART_PADDING_RIGHT;
114       var chartTop = AXIS_PADDING_TOP;
115       var chartBottom = this.chartHeight_ - AXIS_PADDING_BOTTOM;
116       var chartInnerWidth = chartRight - chartLeft;
117
118       if (x > chartLeft && x < chartRight && y > chartTop && y < chartBottom) {
119
120         this.currentBarMouseOverTarget_ = Math.floor(
121             (x - chartLeft) / chartInnerWidth * this.opsTimingData_.length);
122
123         this.currentBarMouseOverTarget_ = tv.clamp(
124             this.currentBarMouseOverTarget_, 0, this.opsTimingData_.length - 1);
125
126       }
127
128       if (this.currentBarMouseOverTarget_ === lastBarMouseOverTarget)
129         return;
130
131       this.drawChartContents_();
132     },
133
134     updateChartContents: function() {
135
136       if (this.requiresRedraw)
137         this.updateChartDimensions_();
138
139       this.drawChartContents_();
140     },
141
142     updateChartDimensions_: function() {
143       this.chartWidth_ = this.offsetWidth;
144       this.chartHeight_ = this.offsetHeight;
145
146       // Scale up the canvas according to the devicePixelRatio, then reduce it
147       // down again via CSS. Finally we apply a scale to the canvas so that
148       // things are drawn at the correct size.
149       this.chart_.width = this.chartWidth_ * this.chartScale_;
150       this.chart_.height = this.chartHeight_ * this.chartScale_;
151
152       this.chart_.style.width = this.chartWidth_ + 'px';
153       this.chart_.style.height = this.chartHeight_ + 'px';
154
155       this.chartCtx_.scale(this.chartScale_, this.chartScale_);
156     },
157
158     processPictureData_: function() {
159
160       this.resetOpsTimingData_();
161       this.pictureDataProcessed_ = true;
162
163       if (!this.picture_)
164         return;
165
166       var ops = this.picture_.getOps();
167       if (!ops)
168         return;
169
170       ops = this.picture_.tagOpsWithTimings(ops);
171
172       // Check that there are valid times.
173       if (ops[0].cmd_time === undefined)
174         return;
175
176       this.collapseOpsToTimingBuckets_(ops);
177     },
178
179     drawChartContents_: function() {
180
181       this.clearChartContents_();
182
183       if (this.opsTimingData_.length === 0) {
184         this.showNoTimingDataMessage_();
185         return;
186       }
187
188       this.drawChartAxes_();
189       this.drawBars_();
190       this.drawLineAtBottomOfChart_();
191
192       if (this.currentBarMouseOverTarget_ === null)
193         return;
194
195       this.drawTooltip_();
196     },
197
198     drawLineAtBottomOfChart_: function() {
199       this.chartCtx_.strokeStyle = '#AAA';
200       this.chartCtx_.moveTo(0, this.chartHeight_ - 0.5);
201       this.chartCtx_.lineTo(this.chartWidth_, this.chartHeight_ - 0.5);
202       this.chartCtx_.stroke();
203     },
204
205     drawTooltip_: function() {
206
207       var tooltipData = this.opsTimingData_[this.currentBarMouseOverTarget_];
208       var tooltipTitle = tooltipData.cmd_string;
209       var tooltipTime = tooltipData.cmd_time.toFixed(4);
210
211       var tooltipWidth = 110;
212       var tooltipHeight = 40;
213       var chartInnerWidth = this.chartWidth_ - CHART_PADDING_RIGHT -
214           CHART_PADDING_LEFT;
215       var barWidth = chartInnerWidth / this.opsTimingData_.length;
216       var tooltipOffset = Math.round((tooltipWidth - barWidth) * 0.5);
217
218       var left = CHART_PADDING_LEFT + this.currentBarMouseOverTarget_ *
219           barWidth - tooltipOffset;
220       var top = Math.round((this.chartHeight_ - tooltipHeight) * 0.5);
221
222       this.chartCtx_.save();
223
224       this.chartCtx_.shadowOffsetX = 0;
225       this.chartCtx_.shadowOffsetY = 5;
226       this.chartCtx_.shadowBlur = 4;
227       this.chartCtx_.shadowColor = 'rgba(0,0,0,0.4)';
228
229       this.chartCtx_.strokeStyle = '#888';
230       this.chartCtx_.fillStyle = '#EEE';
231       this.chartCtx_.fillRect(left, top, tooltipWidth, tooltipHeight);
232
233       this.chartCtx_.shadowColor = 'transparent';
234       this.chartCtx_.translate(0.5, 0.5);
235       this.chartCtx_.strokeRect(left, top, tooltipWidth, tooltipHeight);
236
237       this.chartCtx_.restore();
238
239       this.chartCtx_.fillStyle = '#222';
240       this.chartCtx_.textBaseline = 'top';
241       this.chartCtx_.font = '800 12px Arial';
242       this.chartCtx_.fillText(tooltipTitle, left + 8, top + 8);
243
244       this.chartCtx_.fillStyle = '#555';
245       this.chartCtx_.textBaseline = 'top';
246       this.chartCtx_.font = '400 italic 10px Arial';
247       this.chartCtx_.fillText('Total: ' + tooltipTime + 'ms',
248           left + 8, top + 22);
249     },
250
251     drawBars_: function() {
252
253       var len = this.opsTimingData_.length;
254       var max = this.opsTimingData_[0].cmd_time;
255       var min = this.opsTimingData_[len - 1].cmd_time;
256
257       var width = this.chartWidth_ - CHART_PADDING_LEFT - CHART_PADDING_RIGHT;
258       var height = this.chartHeight_ - AXIS_PADDING_TOP - AXIS_PADDING_BOTTOM;
259       var barWidth = Math.floor(width / len);
260
261       var opData;
262       var opTiming;
263       var opHeight;
264       var opLabel;
265       var barLeft;
266
267       for (var b = 0; b < len; b++) {
268
269         opData = this.opsTimingData_[b];
270         opTiming = opData.cmd_time / max;
271
272         opHeight = Math.round(Math.max(1, opTiming * height));
273         opLabel = opData.cmd_string;
274         barLeft = CHART_PADDING_LEFT + b * barWidth;
275
276         this.chartCtx_.fillStyle = this.getOpColor_(opLabel);
277
278         this.chartCtx_.fillRect(barLeft + BAR_PADDING, AXIS_PADDING_TOP +
279             height - opHeight, barWidth - 2 * BAR_PADDING, opHeight);
280       }
281
282     },
283
284     getOpColor_: function(opName) {
285
286       var characters = opName.split('');
287       var hue = characters.reduce(this.reduceNameToHue, 0) % 360;
288
289       return 'hsl(' + hue + ', 30%, 50%)';
290     },
291
292     reduceNameToHue: function(previousValue, currentValue, index, array) {
293       // Get the char code and apply a magic adjustment value so we get
294       // pretty colors from around the rainbow.
295       return Math.round(previousValue + currentValue.charCodeAt(0) *
296           HUE_CHAR_CODE_ADJUSTMENT);
297     },
298
299     drawChartAxes_: function() {
300
301       var len = this.opsTimingData_.length;
302       var max = this.opsTimingData_[0].cmd_time;
303       var min = this.opsTimingData_[len - 1].cmd_time;
304
305       var width = this.chartWidth_ - AXIS_PADDING_LEFT - AXIS_PADDING_RIGHT;
306       var height = this.chartHeight_ - AXIS_PADDING_TOP - AXIS_PADDING_BOTTOM;
307
308       var totalBarWidth = this.chartWidth_ - CHART_PADDING_LEFT -
309           CHART_PADDING_RIGHT;
310       var barWidth = Math.floor(totalBarWidth / len);
311       var tickYInterval = height / (VERTICAL_TICKS - 1);
312       var tickYPosition = 0;
313       var tickValInterval = (max - min) / (VERTICAL_TICKS - 1);
314       var tickVal = 0;
315
316       this.chartCtx_.fillStyle = '#333';
317       this.chartCtx_.strokeStyle = '#777';
318       this.chartCtx_.save();
319
320       // Translate half a pixel to avoid blurry lines.
321       this.chartCtx_.translate(0.5, 0.5);
322
323       // Sides.
324
325       this.chartCtx_.save();
326
327       this.chartCtx_.translate(AXIS_PADDING_LEFT, AXIS_PADDING_TOP);
328       this.chartCtx_.moveTo(0, 0);
329       this.chartCtx_.lineTo(0, height);
330       this.chartCtx_.lineTo(width, height);
331
332       // Y-axis ticks.
333       this.chartCtx_.font = '10px Arial';
334       this.chartCtx_.textAlign = 'right';
335       this.chartCtx_.textBaseline = 'middle';
336
337       for (var t = 0; t < VERTICAL_TICKS; t++) {
338
339         tickYPosition = Math.round(t * tickYInterval);
340         tickVal = (max - t * tickValInterval).toFixed(4);
341
342         this.chartCtx_.moveTo(0, tickYPosition);
343         this.chartCtx_.lineTo(-AXIS_TICK_SIZE, tickYPosition);
344         this.chartCtx_.fillText(tickVal,
345             -AXIS_TICK_SIZE - AXIS_LABEL_PADDING, tickYPosition);
346
347       }
348
349       this.chartCtx_.stroke();
350
351       this.chartCtx_.restore();
352
353
354       // Labels.
355
356       this.chartCtx_.save();
357
358       this.chartCtx_.translate(CHART_PADDING_LEFT + Math.round(barWidth * 0.5),
359           AXIS_PADDING_TOP + height + LABEL_PADDING);
360
361       this.chartCtx_.font = '10px Arial';
362       this.chartCtx_.textAlign = 'center';
363       this.chartCtx_.textBaseline = 'top';
364
365       var labelTickLeft;
366       var labelTickBottom;
367       for (var l = 0; l < len; l++) {
368
369         labelTickLeft = Math.round(l * barWidth);
370         labelTickBottom = l % 2 * LABEL_INTERLEAVE_OFFSET;
371
372         this.chartCtx_.save();
373         this.chartCtx_.moveTo(labelTickLeft, -LABEL_PADDING);
374         this.chartCtx_.lineTo(labelTickLeft, labelTickBottom);
375         this.chartCtx_.stroke();
376         this.chartCtx_.restore();
377
378         this.chartCtx_.fillText(this.opsTimingData_[l].cmd_string,
379             labelTickLeft, labelTickBottom);
380       }
381
382       this.chartCtx_.restore();
383
384       this.chartCtx_.restore();
385     },
386
387     clearChartContents_: function() {
388       this.chartCtx_.clearRect(0, 0, this.chartWidth_, this.chartHeight_);
389     },
390
391     showNoTimingDataMessage_: function() {
392       this.chartCtx_.font = '800 italic 14px Arial';
393       this.chartCtx_.fillStyle = '#333';
394       this.chartCtx_.textAlign = 'center';
395       this.chartCtx_.textBaseline = 'middle';
396       this.chartCtx_.fillText('No timing data available.',
397           this.chartWidth_ * 0.5, this.chartHeight_ * 0.5);
398     },
399
400     collapseOpsToTimingBuckets_: function(ops) {
401
402       var opsTimingDataIndexHash_ = {};
403       var timingData = this.opsTimingData_;
404       var op;
405       var opIndex;
406
407       for (var i = 0; i < ops.length; i++) {
408
409         op = ops[i];
410
411         if (op.cmd_time === undefined)
412           continue;
413
414         // Try to locate the entry for the current operation
415         // based on its name. If that fails, then create one for it.
416         opIndex = opsTimingDataIndexHash_[op.cmd_string] || null;
417
418         if (opIndex === null) {
419           timingData.push({
420             cmd_time: 0,
421             cmd_string: op.cmd_string
422           });
423
424           opIndex = timingData.length - 1;
425           opsTimingDataIndexHash_[op.cmd_string] = opIndex;
426         }
427
428         timingData[opIndex].cmd_time += op.cmd_time;
429
430       }
431
432       timingData.sort(this.sortTimingBucketsByOpTimeDescending_);
433
434       this.collapseTimingBucketsToOther_(4);
435     },
436
437     collapseTimingBucketsToOther_: function(count) {
438
439       var timingData = this.opsTimingData_;
440       var otherSource = timingData.splice(count, timingData.length - count);
441       var otherDestination = null;
442
443       if (!otherSource.length)
444         return;
445
446       timingData.push({
447         cmd_time: 0,
448         cmd_string: 'Other'
449       });
450
451       otherDestination = timingData[timingData.length - 1];
452       for (var i = 0; i < otherSource.length; i++) {
453         otherDestination.cmd_time += otherSource[i].cmd_time;
454       }
455     },
456
457     sortTimingBucketsByOpTimeDescending_: function(a, b) {
458       return b.cmd_time - a.cmd_time;
459     },
460
461     resetOpsTimingData_: function() {
462       this.opsTimingData_.length = 0;
463     }
464   };
465
466   return {
467     PictureOpsChartSummaryView: PictureOpsChartSummaryView
468   };
469 });
470 </script>