Upstream version 9.37.197.0
[platform/framework/web/crosswalk.git] / src / third_party / trace-viewer / third_party / tvcm / src / tvcm / ui / sunburst_chart.js
1 // Copyright 2014 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 'use strict';
6
7 tvcm.require('tvcm.range');
8 tvcm.require('tvcm.ui.d3');
9 tvcm.require('tvcm.ui.dom_helpers');
10 tvcm.require('tvcm.ui.chart_base');
11
12 tvcm.requireStylesheet('tvcm.ui.sunburst_chart');
13
14 tvcm.exportTo('tvcm.ui', function() {
15   var ChartBase = tvcm.ui.ChartBase;
16   var getColorOfKey = tvcm.ui.getColorOfKey;
17
18   var MIN_RADIUS = 100;
19
20   /**
21    * @constructor
22    */
23   var SunburstChart = tvcm.ui.define('sunburst-chart', ChartBase);
24
25   SunburstChart.prototype = {
26     __proto__: ChartBase.prototype,
27
28     decorate: function() {
29       ChartBase.prototype.decorate.call(this);
30       this.classList.add('sunburst-chart');
31
32       this.data_ = undefined;
33       this.seriesKeys_ = undefined;
34
35       var chartAreaSel = d3.select(this.chartAreaElement);
36       var pieGroupSel = chartAreaSel.append('g')
37         .attr('class', 'pie-group');
38       this.pieGroup_ = pieGroupSel.node();
39
40       this.backSel_ = pieGroupSel.append('g');
41
42       this.pathsGroup_ = pieGroupSel.append('g')
43         .attr('class', 'paths')
44         .node();
45     },
46
47     get data() {
48       return this.data_;
49     },
50
51
52     /**
53      * @param {Data} Data for the chart, where data must be of the
54      * form {category: str, name: str, (size: number or children: [])} .
55      */
56     set data(data) {
57       this.data_ = data;
58       this.updateContents_();
59     },
60
61     get margin() {
62       var margin = {top: 0, right: 0, bottom: 0, left: 0};
63       if (this.chartTitle_)
64         margin.top += 40;
65       return margin;
66     },
67
68     getMinSize: function() {
69       if (!tvcm.ui.isElementAttachedToDocument(this))
70         throw new Error('Cannot measure when unattached');
71       this.updateContents_();
72
73       var titleWidth = this.querySelector(
74           '#title').getBoundingClientRect().width;
75       var margin = this.margin;
76       var marginWidth = margin.left + margin.right;
77       var marginHeight = margin.top + margin.bottom;
78
79       // TODO(vmiura): Calc this when we're done with layout.
80       return {
81         width: 600,
82         height: 600
83       };
84     },
85
86     getLegendKeys_: function() {
87       // This class creates its own legend, instead of using ChartBase.
88       return undefined;
89     },
90
91     updateScales_: function(width, height) {
92       if (this.data_ === undefined)
93         return;
94     },
95
96     updateContents_: function() {
97       ChartBase.prototype.updateContents_.call(this);
98       if (!this.data_)
99         return;
100
101       var width = this.chartAreaSize.width;
102       var height = this.chartAreaSize.height;
103       var radius = Math.max(MIN_RADIUS, Math.min(width, height) / 2);
104
105       d3.select(this.pieGroup_).attr(
106           'transform',
107           'translate(' + width / 2 + ',' + height / 2 + ')');
108
109
110       /////////////////////////////////////////
111       // Mapping of step names to colors.
112       var colors = {
113         'Chrome': '#5687d1',
114         'Kernel': '#7b615c',
115         'GPU Driver': '#ff0000',
116         'Java': '#de783b',
117         'Android': '#6ab975',
118         'Thread': '#aaaaaa',
119         'Standard Lib': '#bbbbbb',
120         '<self>': '#888888',
121         '<unknown>': '#444444'
122       };
123
124       // temp test data
125       var json = this.data_;
126       var partition = d3.layout.partition()
127           .size([1, 1]) // radius * radius
128           .value(function(d) { return d.size; });
129
130       // For efficiency, filter nodes to keep only those large enough to see.
131       var nodes = partition.nodes(json);
132       nodes.forEach(function f(d, i) { d.id = i; });
133       var totalSize = nodes[0].value;
134       var depth = 1.0 + d3.max(nodes, function(d) { return d.depth; });
135       var yDomainMin = 1.0 / depth;
136       var yDomainMax = Math.min(Math.max(depth, 20), 50) / depth;
137
138       var x = d3.scale.linear()
139           .range([0, 2 * Math.PI]);
140
141       var y = d3.scale.sqrt()
142           .domain([yDomainMin, yDomainMax])
143           .range([50, radius]);
144
145       var arc = d3.svg.arc()
146           .startAngle(function(d) {
147             return Math.max(0, Math.min(2 * Math.PI, x(d.x)));
148           })
149           .endAngle(function(d) {
150             return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx)));
151           })
152           .innerRadius(function(d) { return Math.max(0, y((d.y))); })
153           .outerRadius(function(d) { return Math.max(0, y((d.y + d.dy))); });
154
155       // Interpolate the scales!
156       function arcTween(minX, maxX, minY) {
157         var xd, yd, yr;
158
159         if (minY > 0) {
160           xd = d3.interpolate(x.domain(), [minX, maxX]);
161           yd = d3.interpolate(y.domain(), [minY, yDomainMax]);
162           yr = d3.interpolate(y.range(), [50, radius]);
163         }
164         else {
165           xd = d3.interpolate(x.domain(), [minX, maxX]);
166           yd = d3.interpolate(y.domain(), [yDomainMin, yDomainMax]);
167           yr = d3.interpolate(y.range(), [50, radius]);
168         }
169
170         return function(d, i) {
171           return i ? function(t) { return arc(d); }
172               : function(t) {
173                 x.domain(xd(t)); y.domain(yd(t)).range(yr(t)); return arc(d);
174               };
175         };
176       }
177
178
179       var clickedNode = null;
180       var click_stack = new Array();
181       click_stack.push(0);
182
183       function zoomout(d) {
184         if (click_stack.length > 1)
185           click_stack.pop();
186         zoomto(click_stack[click_stack.length - 1]);
187       }
188
189       // Bounding circle underneath the sunburst, to make it easier to detect
190       // when the mouse leaves the parent g.
191       this.backSel_.append('svg:circle')
192           .attr('r', radius)
193           .style('opacity', 0.0)
194           .on('click', zoomout);
195
196
197       var vis = d3.select(this.pathsGroup_);
198
199       function getNode(id) {
200         for (var i = 0; i < nodes.length; i++) {
201           if (nodes[i].id == id)
202             return nodes[i];
203         }
204         return null;
205       }
206
207       var minX = 0.0;
208       var maxX = 1.0;
209       var minY = 0.0;
210       var clickedY = 0;
211
212       function zoomto(id) {
213         var d = getNode(id);
214
215         if (d) {
216           clickedY = d.y;
217           minX = d.x;
218           maxX = d.x + d.dx;
219           minY = d.y;
220         }
221         else {
222           clickedY = -1;
223           minX = 0.0;
224           maxX = 1.0;
225           minY = 0.0;
226         }
227
228         clickedNode = d;
229         redraw(minX, maxX, minY);
230         var path = vis.selectAll('path');
231
232         path.transition()
233           .duration(750)
234           .attrTween('d', arcTween(minX, maxX, minY));
235
236         showBreadcrumbs(d);
237       }
238
239       function click(d) {
240         if (d3.event.shiftKey) {
241           // Zoom partially onto the selected range
242           var diff_x = (maxX - minX) * 0.5;
243           minX = d.x + d.dx * 0.5 - diff_x * 0.5;
244           minX = minX < 0.0 ? 0.0 : minX;
245           maxX = minX + diff_x;
246           maxX = maxX > 1.0 ? 1.0 : maxX;
247           minX = maxX - diff_x;
248
249           redraw(minX, maxX, minY);
250
251           var path = vis.selectAll('path');
252
253           clickedNode = d;
254           path.transition()
255             .duration(750)
256             .attrTween('d', arcTween(minX, maxX, minY));
257
258           return;
259         }
260
261         if (click_stack[click_stack.length - 1] != d.id) {
262           click_stack.push(d.id);
263           zoomto(d.id);
264         }
265       }
266
267       // Given a node in a partition layout, return an array of all of its
268       // ancestor nodes, highest first, but excluding the root.
269       function getAncestors(node) {
270         var path = [];
271         var current = node;
272         while (current.parent) {
273           path.unshift(current);
274           current = current.parent;
275         }
276         return path;
277       }
278
279       function showBreadcrumbs(d) {
280         var sequenceArray = getAncestors(d);
281
282         // Fade all the segments.
283         vis.selectAll('path')
284           .style('opacity', 0.7);
285
286         // Then highlight only those that are an ancestor of the current
287         // segment.
288         vis.selectAll('path')
289           .filter(function(node) {
290               return (sequenceArray.indexOf(node) >= 0);
291             })
292           .style('opacity', 1);
293       }
294
295       function mouseover(d) {
296         showBreadcrumbs(d);
297       }
298
299       // Restore everything to full opacity when moving off the
300       // visualization.
301       function mouseleave(d) {
302         // Hide the breadcrumb trail
303         if (clickedNode != null)
304           showBreadcrumbs(clickedNode);
305         else {
306           // Deactivate all segments during transition.
307           vis.selectAll('path')
308             .on('mouseover', null);
309
310           // Transition each segment to full opacity and then reactivate it.
311           vis.selectAll('path')
312             .transition()
313             .duration(300)
314             .style('opacity', 1)
315             .each('end', function() {
316                 d3.select(this).on('mouseover', mouseover);
317               });
318         }
319       }
320
321       // Update visible segments between new min/max ranges.
322       function redraw(minX, maxX, minY) {
323         var scale = maxX - minX;
324         var visible_nodes = nodes.filter(function(d) {
325           return d.depth &&
326               (d.y >= minY) &&
327               (d.x < maxX) &&
328               (d.x + d.dx > minX) &&
329               (d.dx / scale > 0.001);
330         });
331         var path = vis.data([json]).selectAll('path')
332           .data(visible_nodes, function(d) { return d.id; });
333
334         path.enter().insert('svg:path')
335           .attr('d', arc)
336           .attr('fill-rule', 'evenodd')
337           .style('fill', function(dd) { return colors[dd.category]; })
338           .style('opacity', 0.7)
339           .on('mouseover', mouseover)
340           .on('click', click);
341
342         path.exit().remove();
343         return path;
344       }
345
346       zoomto(0);
347       vis.on('mouseleave', mouseleave);
348     },
349
350     updateHighlight_: function() {
351       ChartBase.prototype.updateHighlight_.call(this);
352       // Update color of pie segments.
353       var pathsGroupSel = d3.select(this.pathsGroup_);
354       var that = this;
355       pathsGroupSel.selectAll('.arc').each(function(d, i) {
356         var origData = that.data_[i];
357         var highlighted = origData.label == that.currentHighlightedLegendKey;
358         var color = getColorOfKey(origData.label, highlighted);
359         this.style.fill = color;
360       });
361     }
362   };
363
364   return {
365     SunburstChart: SunburstChart
366   };
367 });