Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / third_party / trace-viewer / trace_viewer / cc / picture_ops_chart_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="stylesheet" href="/cc/picture_ops_chart_view.css">
9 <link rel="import" href="/tvcm/ui/dom_helpers.html">
10
11 <script>
12 'use strict';
13
14 tvcm.exportTo('cc', function() {
15   var BAR_PADDING = 1;
16   var BAR_WIDTH = 5;
17   var CHART_PADDING_LEFT = 65;
18   var CHART_PADDING_RIGHT = 30;
19   var CHART_PADDING_BOTTOM = 35;
20   var CHART_PADDING_TOP = 20;
21   var AXIS_PADDING_LEFT = 55;
22   var AXIS_PADDING_RIGHT = 30;
23   var AXIS_PADDING_BOTTOM = 35;
24   var AXIS_PADDING_TOP = 20;
25   var AXIS_TICK_SIZE = 5;
26   var AXIS_LABEL_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 PictureOpsChartView = tvcm.ui.define('picture-ops-chart-view');
37
38   PictureOpsChartView.prototype = {
39     __proto__: HTMLUnknownElement.prototype,
40
41     decorate: function() {
42       this.picture_ = undefined;
43       this.pictureOps_ = undefined;
44       this.opCosts_ = undefined;
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.selectedOpIndex_ = undefined;
53       this.chartWidth_ = 0;
54       this.chartHeight_ = 0;
55       this.dimensionsHaveChanged_ = true;
56
57       this.currentBarMouseOverTarget_ = undefined;
58
59       this.ninetyFifthPercentileCost_ = 0;
60       this.totalOpCost_ = 0;
61
62       this.chart_.addEventListener('click', this.onClick_.bind(this));
63       this.chart_.addEventListener('mousemove', this.onMouseMove_.bind(this));
64
65       this.usePercentileScale_ = false;
66       this.usePercentileScaleCheckbox_ = tvcm.ui.createCheckBox(
67           this, 'usePercentileScale',
68           'PictureOpsChartView.usePercentileScale', false,
69           'Limit to 95%-ile');
70       this.usePercentileScaleCheckbox_.classList.add('use-percentile-scale');
71       this.appendChild(this.usePercentileScaleCheckbox_);
72     },
73
74     get dimensionsHaveChanged() {
75       return this.dimensionsHaveChanged_;
76     },
77
78     set dimensionsHaveChanged(dimensionsHaveChanged) {
79       this.dimensionsHaveChanged_ = dimensionsHaveChanged;
80     },
81
82     get usePercentileScale() {
83       return this.usePercentileScale_;
84     },
85
86     set usePercentileScale(usePercentileScale) {
87       this.usePercentileScale_ = usePercentileScale;
88       this.drawChartContents_();
89     },
90
91     get numOps() {
92       return this.opCosts_.length;
93     },
94
95     get selectedOpIndex() {
96       return this.selectedOpIndex_;
97     },
98
99     set selectedOpIndex(selectedOpIndex) {
100       if (selectedOpIndex < 0) throw new Error('Invalid index');
101       if (selectedOpIndex >= this.numOps) throw new Error('Invalid index');
102
103       this.selectedOpIndex_ = selectedOpIndex;
104     },
105
106     get picture() {
107       return this.picture_;
108     },
109
110     set picture(picture) {
111       this.picture_ = picture;
112       this.pictureOps_ = picture.tagOpsWithTimings(picture.getOps());
113       this.currentBarMouseOverTarget_ = undefined;
114       this.processPictureData_();
115       this.dimensionsHaveChanged = true;
116     },
117
118     processPictureData_: function() {
119       if (this.pictureOps_ === undefined)
120         return;
121
122       var totalOpCost = 0;
123
124       // Take a copy of the picture ops data for sorting.
125       this.opCosts_ = this.pictureOps_.map(function(op) {
126         totalOpCost += op.cmd_time;
127         return op.cmd_time;
128       });
129       this.opCosts_.sort();
130
131       var ninetyFifthPercentileCostIndex = Math.floor(
132           this.opCosts_.length * 0.95);
133       this.ninetyFifthPercentileCost_ =
134           this.opCosts_[ninetyFifthPercentileCostIndex];
135       this.maxCost_ = this.opCosts_[this.opCosts_.length - 1];
136
137       this.totalOpCost_ = totalOpCost;
138     },
139
140     extractBarIndex_: function(e) {
141
142       var index = undefined;
143
144       if (this.pictureOps_ === undefined ||
145           this.pictureOps_.length === 0)
146         return index;
147
148       var x = e.offsetX;
149       var y = e.offsetY;
150
151       var totalBarWidth = (BAR_WIDTH + BAR_PADDING) * this.pictureOps_.length;
152
153       var chartLeft = CHART_PADDING_LEFT;
154       var chartTop = 0;
155       var chartBottom = this.chartHeight_ - CHART_PADDING_BOTTOM;
156       var chartRight = chartLeft + totalBarWidth;
157
158       if (x < chartLeft || x > chartRight || y < chartTop || y > chartBottom)
159         return index;
160
161       index = Math.floor((x - chartLeft) / totalBarWidth *
162           this.pictureOps_.length);
163
164       index = tvcm.clamp(index, 0, this.pictureOps_.length - 1);
165
166       return index;
167     },
168
169     onClick_: function(e) {
170
171       var barClicked = this.extractBarIndex_(e);
172
173       if (barClicked === undefined)
174         return;
175
176       // If we click on the already selected item we should deselect.
177       if (barClicked === this.selectedOpIndex)
178         this.selectedOpIndex = undefined;
179       else
180         this.selectedOpIndex = barClicked;
181
182       e.preventDefault();
183
184       tvcm.dispatchSimpleEvent(this, 'selection-changed', false);
185     },
186
187     onMouseMove_: function(e) {
188
189       var lastBarMouseOverTarget = this.currentBarMouseOverTarget_;
190       this.currentBarMouseOverTarget_ = this.extractBarIndex_(e);
191
192       if (this.currentBarMouseOverTarget_ === lastBarMouseOverTarget)
193         return;
194
195       this.drawChartContents_();
196     },
197
198     scrollSelectedItemIntoViewIfNecessary: function() {
199
200       if (this.selectedOpIndex === undefined)
201         return;
202
203       var width = this.offsetWidth;
204       var left = this.scrollLeft;
205       var right = left + width;
206       var targetLeft = CHART_PADDING_LEFT +
207           (BAR_WIDTH + BAR_PADDING) * this.selectedOpIndex;
208
209       if (targetLeft > left && targetLeft < right)
210         return;
211
212       this.scrollLeft = (targetLeft - width * 0.5);
213     },
214
215     updateChartContents: function() {
216
217       if (this.dimensionsHaveChanged)
218         this.updateChartDimensions_();
219
220       this.drawChartContents_();
221     },
222
223     updateChartDimensions_: function() {
224
225       if (!this.pictureOps_)
226         return;
227
228       var width = CHART_PADDING_LEFT + CHART_PADDING_RIGHT +
229           ((BAR_WIDTH + BAR_PADDING) * this.pictureOps_.length);
230
231       if (width < this.offsetWidth)
232         width = this.offsetWidth;
233
234       // Allow the element to be its natural size as set by flexbox, then lock
235       // the width in before we set the width of the canvas.
236       this.chartWidth_ = width;
237       this.chartHeight_ = this.getBoundingClientRect().height;
238
239       // Scale up the canvas according to the devicePixelRatio, then reduce it
240       // down again via CSS. Finally we apply a scale to the canvas so that
241       // things are drawn at the correct size.
242       this.chart_.width = this.chartWidth_ * this.chartScale_;
243       this.chart_.height = this.chartHeight_ * this.chartScale_;
244
245       this.chart_.style.width = this.chartWidth_ + 'px';
246       this.chart_.style.height = this.chartHeight_ + 'px';
247
248       this.chartCtx_.scale(this.chartScale_, this.chartScale_);
249
250       this.dimensionsHaveChanged = false;
251     },
252
253     drawChartContents_: function() {
254
255       this.clearChartContents_();
256
257       if (this.pictureOps_ === undefined ||
258           this.pictureOps_.length === 0 ||
259           this.pictureOps_[0].cmd_time === undefined) {
260
261         this.showNoTimingDataMessage_();
262         return;
263       }
264
265       this.drawSelection_();
266       this.drawBars_();
267       this.drawChartAxes_();
268       this.drawLinesAtTickMarks_();
269       this.drawLineAtBottomOfChart_();
270
271       if (this.currentBarMouseOverTarget_ === undefined)
272         return;
273
274       this.drawTooltip_();
275     },
276
277     drawSelection_: function() {
278
279       if (this.selectedOpIndex === undefined)
280         return;
281
282       var width = (BAR_WIDTH + BAR_PADDING) * this.selectedOpIndex;
283       this.chartCtx_.fillStyle = 'rgb(223, 235, 230)';
284       this.chartCtx_.fillRect(CHART_PADDING_LEFT, CHART_PADDING_TOP,
285           width, this.chartHeight_ - CHART_PADDING_TOP - CHART_PADDING_BOTTOM);
286     },
287
288     drawChartAxes_: function() {
289
290       var min = this.opCosts_[0];
291       var max = this.opCosts_[this.opCosts_.length - 1];
292       var height = this.chartHeight_ - AXIS_PADDING_TOP - AXIS_PADDING_BOTTOM;
293
294       var tickYInterval = height / (VERTICAL_TICKS - 1);
295       var tickYPosition = 0;
296       var tickValInterval = (max - min) / (VERTICAL_TICKS - 1);
297       var tickVal = 0;
298
299       this.chartCtx_.fillStyle = '#333';
300       this.chartCtx_.strokeStyle = '#777';
301       this.chartCtx_.save();
302
303       // Translate half a pixel to avoid blurry lines.
304       this.chartCtx_.translate(0.5, 0.5);
305
306       // Sides.
307       this.chartCtx_.beginPath();
308       this.chartCtx_.moveTo(AXIS_PADDING_LEFT, AXIS_PADDING_TOP);
309       this.chartCtx_.lineTo(AXIS_PADDING_LEFT, this.chartHeight_ -
310           AXIS_PADDING_BOTTOM);
311       this.chartCtx_.lineTo(this.chartWidth_ - AXIS_PADDING_RIGHT,
312           this.chartHeight_ - AXIS_PADDING_BOTTOM);
313       this.chartCtx_.stroke();
314       this.chartCtx_.closePath();
315
316       // Y-axis ticks.
317       this.chartCtx_.translate(AXIS_PADDING_LEFT, AXIS_PADDING_TOP);
318
319       this.chartCtx_.font = '10px Arial';
320       this.chartCtx_.textAlign = 'right';
321       this.chartCtx_.textBaseline = 'middle';
322
323       this.chartCtx_.beginPath();
324       for (var t = 0; t < VERTICAL_TICKS; t++) {
325
326         tickYPosition = Math.round(t * tickYInterval);
327         tickVal = (max - t * tickValInterval).toFixed(4);
328
329         this.chartCtx_.moveTo(0, tickYPosition);
330         this.chartCtx_.lineTo(-AXIS_TICK_SIZE, tickYPosition);
331         this.chartCtx_.fillText(tickVal,
332             -AXIS_TICK_SIZE - AXIS_LABEL_PADDING, tickYPosition);
333
334       }
335
336       this.chartCtx_.stroke();
337       this.chartCtx_.closePath();
338
339       this.chartCtx_.restore();
340     },
341
342     drawLinesAtTickMarks_: function() {
343
344       var height = this.chartHeight_ - AXIS_PADDING_TOP - AXIS_PADDING_BOTTOM;
345       var width = this.chartWidth_ - AXIS_PADDING_LEFT - AXIS_PADDING_RIGHT;
346       var tickYInterval = height / (VERTICAL_TICKS - 1);
347       var tickYPosition = 0;
348
349       this.chartCtx_.save();
350
351       this.chartCtx_.translate(AXIS_PADDING_LEFT + 0.5, AXIS_PADDING_TOP + 0.5);
352       this.chartCtx_.beginPath();
353       this.chartCtx_.strokeStyle = 'rgba(0,0,0,0.05)';
354
355       for (var t = 0; t < VERTICAL_TICKS; t++) {
356         tickYPosition = Math.round(t * tickYInterval);
357
358         this.chartCtx_.moveTo(0, tickYPosition);
359         this.chartCtx_.lineTo(width, tickYPosition);
360         this.chartCtx_.stroke();
361       }
362
363       this.chartCtx_.restore();
364       this.chartCtx_.closePath();
365     },
366
367     drawLineAtBottomOfChart_: function() {
368       this.chartCtx_.strokeStyle = '#AAA';
369       this.chartCtx_.beginPath();
370       this.chartCtx_.moveTo(0, this.chartHeight_ - 0.5);
371       this.chartCtx_.lineTo(this.chartWidth_, this.chartHeight_ - 0.5);
372       this.chartCtx_.stroke();
373       this.chartCtx_.closePath();
374     },
375
376     drawTooltip_: function() {
377
378       var tooltipData = this.pictureOps_[this.currentBarMouseOverTarget_];
379       var tooltipTitle = tooltipData.cmd_string;
380       var tooltipTime = tooltipData.cmd_time.toFixed(4);
381       var toolTipTimePercentage =
382           ((tooltipData.cmd_time / this.totalOpCost_) * 100).toFixed(2);
383
384       var tooltipWidth = 120;
385       var tooltipHeight = 40;
386       var chartInnerWidth = this.chartWidth_ - CHART_PADDING_RIGHT -
387           CHART_PADDING_LEFT;
388       var barWidth = BAR_WIDTH + BAR_PADDING;
389       var tooltipOffset = Math.round((tooltipWidth - barWidth) * 0.5);
390
391       var left = CHART_PADDING_LEFT + this.currentBarMouseOverTarget_ *
392           barWidth - tooltipOffset;
393       var top = Math.round((this.chartHeight_ - tooltipHeight) * 0.5);
394
395       this.chartCtx_.save();
396
397       this.chartCtx_.shadowOffsetX = 0;
398       this.chartCtx_.shadowOffsetY = 5;
399       this.chartCtx_.shadowBlur = 4;
400       this.chartCtx_.shadowColor = 'rgba(0,0,0,0.4)';
401
402       this.chartCtx_.strokeStyle = '#888';
403       this.chartCtx_.fillStyle = '#EEE';
404       this.chartCtx_.fillRect(left, top, tooltipWidth, tooltipHeight);
405
406       this.chartCtx_.shadowColor = 'transparent';
407       this.chartCtx_.translate(0.5, 0.5);
408       this.chartCtx_.strokeRect(left, top, tooltipWidth, tooltipHeight);
409
410       this.chartCtx_.restore();
411
412       this.chartCtx_.fillStyle = '#222';
413       this.chartCtx_.textAlign = 'left';
414       this.chartCtx_.textBaseline = 'top';
415       this.chartCtx_.font = '800 12px Arial';
416       this.chartCtx_.fillText(tooltipTitle, left + 8, top + 8);
417
418       this.chartCtx_.fillStyle = '#555';
419       this.chartCtx_.font = '400 italic 10px Arial';
420       this.chartCtx_.fillText(tooltipTime + 'ms (' +
421           toolTipTimePercentage + '%)', left + 8, top + 22);
422     },
423
424     drawBars_: function() {
425
426       var op;
427       var opColor = 0;
428       var opHeight = 0;
429       var opWidth = BAR_WIDTH + BAR_PADDING;
430       var opHover = false;
431
432       var bottom = this.chartHeight_ - CHART_PADDING_BOTTOM;
433       var maxHeight = this.chartHeight_ - CHART_PADDING_BOTTOM -
434           CHART_PADDING_TOP;
435
436       var maxValue;
437       if (this.usePercentileScale)
438         maxValue = this.ninetyFifthPercentileCost_;
439       else
440         maxValue = this.maxCost_;
441
442       for (var b = 0; b < this.pictureOps_.length; b++) {
443
444         op = this.pictureOps_[b];
445         opHeight = Math.round(
446             (op.cmd_time / maxValue) * maxHeight);
447         opHeight = Math.max(opHeight, 1);
448         opHover = (b === this.currentBarMouseOverTarget_);
449         opColor = this.getOpColor_(op.cmd_string, opHover);
450
451         if (b === this.selectedOpIndex)
452           this.chartCtx_.fillStyle = '#FFFF00';
453         else
454           this.chartCtx_.fillStyle = opColor;
455
456         this.chartCtx_.fillRect(CHART_PADDING_LEFT + b * opWidth,
457             bottom - opHeight, BAR_WIDTH, opHeight);
458       }
459
460     },
461
462     getOpColor_: function(opName, hover) {
463
464       var characters = opName.split('');
465
466       var hue = characters.reduce(this.reduceNameToHue, 0) % 360;
467       var saturation = 30;
468       var lightness = hover ? '75%' : '50%';
469
470       return 'hsl(' + hue + ', ' + saturation + '%, ' + lightness + '%)';
471     },
472
473     reduceNameToHue: function(previousValue, currentValue, index, array) {
474       // Get the char code and apply a magic adjustment value so we get
475       // pretty colors from around the rainbow.
476       return Math.round(previousValue + currentValue.charCodeAt(0) *
477           HUE_CHAR_CODE_ADJUSTMENT);
478     },
479
480     clearChartContents_: function() {
481       this.chartCtx_.clearRect(0, 0, this.chartWidth_, this.chartHeight_);
482     },
483
484     showNoTimingDataMessage_: function() {
485       this.chartCtx_.font = '800 italic 14px Arial';
486       this.chartCtx_.fillStyle = '#333';
487       this.chartCtx_.textAlign = 'center';
488       this.chartCtx_.textBaseline = 'middle';
489       this.chartCtx_.fillText('No timing data available.',
490           this.chartWidth_ * 0.5, this.chartHeight_ * 0.5);
491     }
492   };
493
494   return {
495     PictureOpsChartView: PictureOpsChartView
496   };
497 });
498 </script>