Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / third_party / trace-viewer / third_party / tvcm / src / tvcm / ui / sunburst_chart.html
1 <!DOCTYPE html>
2 <!--
3 Copyright (c) 2014 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 <link rel="import" href="/tvcm/range.html">
8 <link rel="import" href="/tvcm/ui/d3.html">
9 <link rel="import" href="/tvcm/ui/dom_helpers.html">
10 <link rel="import" href="/tvcm/ui/chart_base.html">
11 <link rel="stylesheet" href="/tvcm/ui/sunburst_chart.css">
12 <script>
13 'use strict';
14
15 tvcm.exportTo('tvcm.ui', function() {
16   var ChartBase = tvcm.ui.ChartBase;
17   var getColorOfKey = tvcm.ui.getColorOfKey;
18
19   var MIN_RADIUS = 100;
20
21   /**
22    * @constructor
23    */
24   var SunburstChart = tvcm.ui.define('sunburst-chart', ChartBase);
25
26   SunburstChart.prototype = {
27     __proto__: ChartBase.prototype,
28
29     decorate: function() {
30       ChartBase.prototype.decorate.call(this);
31       this.classList.add('sunburst-chart');
32
33       this.data_ = undefined;
34       this.seriesKeys_ = undefined;
35
36       this.yDomainMin_ = 0.0;
37       this.yDomainMax_ = 0.0;
38       this.xDomainScale_ = undefined;
39       this.yDomainScale_ = undefined;
40       this.radius_ = undefined;
41       this.arc_ = undefined;
42       this.selectedNode_ = null;
43       this.clickStack_ = undefined;
44       this.vis_ = undefined;
45       this.nodes_ = undefined;
46       this.minX_ = 0.0;
47       this.maxX_ = 1.0;
48       this.minY_ = 0.0;
49       this.clickedY_ = 0;
50
51       var chartAreaSel = d3.select(this.chartAreaElement);
52       this.legendSel_ = chartAreaSel.append('g');
53
54       var pieGroupSel = chartAreaSel.append('g')
55         .attr('class', 'pie-group');
56       this.pieGroup_ = pieGroupSel.node();
57
58       this.backSel_ = pieGroupSel.append('g');
59
60
61       this.pathsGroup_ = pieGroupSel.append('g')
62         .attr('class', 'paths')
63         .node();
64     },
65
66     get data() {
67       return this.data_;
68     },
69
70
71     /**
72      * @param {Data} Data for the chart, where data must be of the
73      * form {category: str, name: str, (size: number or children: [])} .
74      */
75     set data(data) {
76       this.data_ = data;
77       this.updateContents_();
78     },
79
80     get margin() {
81       var margin = {top: 0, right: 0, bottom: 0, left: 0};
82       if (this.chartTitle_)
83         margin.top += 40;
84       return margin;
85     },
86
87     set selectedNodeID(id) {
88       this.zoomToID_(id);
89     },
90
91     get selectedNodeID() {
92       if (this.selectedNode_ != null)
93         return this.selectedNode_.id;
94       return null;
95     },
96
97     get selectedNode() {
98       if (this.selectedNode_ != null)
99         return this.selectedNode_;
100       return null;
101     },
102
103     getMinSize: function() {
104       if (!tvcm.ui.isElementAttachedToDocument(this))
105         throw new Error('Cannot measure when unattached');
106       this.updateContents_();
107
108       var titleWidth = this.querySelector(
109           '#title').getBoundingClientRect().width;
110       var margin = this.margin;
111       var marginWidth = margin.left + margin.right;
112       var marginHeight = margin.top + margin.bottom;
113
114       // TODO(vmiura): Calc this when we're done with layout.
115       return {
116         width: 600,
117         height: 600
118       };
119     },
120
121     getLegendKeys_: function() {
122       // This class creates its own legend, instead of using ChartBase.
123       return undefined;
124     },
125
126     updateScales_: function(width, height) {
127       if (this.data_ === undefined)
128         return;
129     },
130
131     // Interpolate the scales!
132     arcTween_: function(minX, maxX, minY) {
133       var that = this;
134       var xd, yd, yr;
135
136       if (minY > 0) {
137         xd = d3.interpolate(that.xDomainScale_.domain(), [minX, maxX]);
138         yd = d3.interpolate(
139             that.yDomainScale_.domain(), [minY, that.yDomainMax_]);
140         yr = d3.interpolate(that.yDomainScale_.range(), [50, that.radius_]);
141       }
142       else {
143         xd = d3.interpolate(that.xDomainScale_.domain(), [minX, maxX]);
144         yd = d3.interpolate(that.yDomainScale_.domain(),
145                             [that.yDomainMin_, that.yDomainMax_]);
146         yr = d3.interpolate(that.yDomainScale_.range(), [50, that.radius_]);
147       }
148
149       return function(d, i) {
150         return i ? function(t) { return that.arc_(d); }
151             : function(t) {
152               that.xDomainScale_.domain(xd(t));
153               that.yDomainScale_.domain(yd(t)).range(yr(t));
154               return that.arc_(d);
155             };
156       };
157     },
158
159     getNodeById_: function(id) {
160       if (!this.nodes_)
161         return null;
162
163       if (id < 0 || id > this.nodes_.length)
164         return null;
165
166       return this.nodes_[id];
167     },
168
169     zoomOut_: function() {
170       if (this.clickStack_.length > 1) {
171         this.clickStack_.pop();
172         this.selectedNodeID = this.clickStack_[this.clickStack_.length - 1];
173       }
174     },
175
176     zoomToID_: function(id) {
177       var d = this.getNodeById_(id);
178
179       if (d) {
180         this.clickedY_ = d.y;
181         this.minX_ = d.x;
182         this.maxX_ = d.x + d.dx;
183         this.minY_ = d.y;
184       }
185       else {
186         this.clickedY_ = -1;
187         this.minX_ = 0.0;
188         this.maxX_ = 1.0;
189         this.minY_ = 0.0;
190       }
191
192       this.selectedNode_ = d;
193       this.redrawSegments_(this.minX_, this.maxX_, this.minY_);
194       var path = this.vis_.selectAll('path');
195
196       path.transition()
197         .duration(750)
198         .attrTween('d', this.arcTween_(this.minX_, this.maxX_, this.minY_));
199
200       this.showBreadcrumbs_(d);
201
202       var e = new Event('node-selected');
203       e.node = d;
204       this.dispatchEvent(e);
205     },
206
207     click_: function(d) {
208       if (d3.event.shiftKey) {
209         // Zoom partially onto the selected range
210         var diff_x = (this.maxX_ - this.minX_) * 0.5;
211         this.minX_ = d.x + d.dx * 0.5 - diff_x * 0.5;
212         this.minX_ = this.minX_ < 0.0 ? 0.0 : this.minX_;
213         this.maxX_ = this.minX_ + diff_x;
214         this.maxX_ = this.maxX_ > 1.0 ? 1.0 : this.maxX_;
215         this.minX_ = this.maxX_ - diff_x;
216
217         this.selectedNode_ = d;
218         this.redrawSegments_(this.minX_, this.maxX_, this.minY_);
219
220         var path = this.vis_.selectAll('path');
221         path.transition()
222           .duration(750)
223           .attrTween('d', this.arcTween_(this.minX_, this.maxX_, this.minY_));
224
225         return;
226       }
227
228       if (this.clickStack_[this.clickStack_.length - 1] != d.id) {
229         this.clickStack_.push(d.id);
230         this.selectedNodeID = d.id;
231       }
232     },
233
234     // Given a node in a partition layout, return an array of all of its
235     // ancestor nodes, highest first, but excluding the root.
236     getAncestors_: function(node) {
237       var path = [];
238       var current = node;
239       while (current.parent) {
240         path.unshift(current);
241         current = current.parent;
242       }
243       return path;
244     },
245
246     showBreadcrumbs_: function(d) {
247       var sequenceArray = this.getAncestors_(d);
248
249       // Fade all the segments.
250       this.vis_.selectAll('path')
251         .style('opacity', function(d) {
252             return sequenceArray.indexOf(d) >= 0 ? 0.7 : 1.0;
253           });
254
255       var e = new Event('node-highlighted');
256       e.node = d;
257       this.dispatchEvent(e);
258
259       //if (this.data_.onNodeHighlighted != undefined)
260       //  this.data_.onNodeHighlighted(this, d);
261     },
262
263     mouseOver_: function(d) {
264       this.showBreadcrumbs_(d);
265     },
266
267     // Restore everything to full opacity when moving off the
268     // visualization.
269     mouseLeave_: function(d) {
270       var that = this;
271       // Hide the breadcrumb trail
272       if (that.selectedNode_ != null)
273         that.showBreadcrumbs_(that.selectedNode_);
274       else {
275         // Deactivate all segments during transition.
276         that.vis_.selectAll('path')
277           .on('mouseover', null);
278
279         // Transition each segment to full opacity and then reactivate it.
280         that.vis_.selectAll('path')
281           .transition()
282           .duration(300)
283           .style('opacity', 1)
284           .each('end', function() {
285               d3.select(that).on('mouseover', function(d) {
286                 that.mouseOver_(d);
287               });
288             });
289       }
290     },
291
292     // Update visible segments between new min/max ranges.
293     redrawSegments_: function(minX, maxX, minY) {
294       var that = this;
295       var scale = maxX - minX;
296       var visible_nodes = that.nodes_.filter(function(d) {
297         return d.depth &&
298             (d.y >= minY) &&
299             (d.x < maxX) &&
300             (d.x + d.dx > minX) &&
301             (d.dx / scale > 0.001);
302       });
303       var path = that.vis_.data([that.data_.nodes]).selectAll('path')
304         .data(visible_nodes, function(d) { return d.id; });
305
306       path.enter().insert('svg:path')
307         .attr('d', that.arc_)
308         .attr('fill-rule', 'evenodd')
309         .style('fill', function(dd) { return getColorOfKey(dd.category); })
310         .style('opacity', 1.0)
311         .on('mouseover', function(d) { that.mouseOver_(d); })
312         .on('click', function(d) { that.click_(d); });
313
314       path.exit().remove();
315       return path;
316     },
317
318     updateContents_: function() {
319       ChartBase.prototype.updateContents_.call(this);
320       if (!this.data_)
321         return;
322
323       var that = this;
324
325       // Partition data into d3 nodes.
326       var partition = d3.layout.partition()
327           .size([1, 1])
328           .value(function(d) { return d.size; });
329       that.nodes_ = partition.nodes(that.data_.nodes);
330
331       // Allocate an id to each node.  Gather all categories.
332       var categoryDict = {};
333       that.nodes_.forEach(function f(d, i) {
334         d.id = i;
335         categoryDict[d.category] = null;
336       });
337
338       // Create legend.
339       var li = {
340         w: 85, h: 20, s: 3, r: 3
341       };
342
343       var legend = that.legendSel_.append('svg:svg')
344           .attr('width', li.w)
345           .attr('height', d3.keys(categoryDict).length * (li.h + li.s));
346
347       var g = legend.selectAll('g')
348           .data(d3.keys(categoryDict))
349           .enter().append('svg:g')
350           .attr('transform', function(d, i) {
351             return 'translate(0,' + i * (li.h + li.s) + ')';
352           });
353
354       g.append('svg:rect')
355           .attr('rx', li.r)
356           .attr('ry', li.r)
357           .attr('width', li.w)
358           .attr('height', li.h)
359           .style('fill', function(d) { return getColorOfKey(d); });
360
361       g.append('svg:text')
362           .attr('x', li.w / 2)
363           .attr('y', li.h / 2)
364           .attr('dy', '0.35em')
365           .attr('text-anchor', 'middle')
366           .attr('fill', '#fff')
367           .attr('font-size', '12px')
368           .text(function(d) { return d; });
369
370       // Create sunburst visualization.
371       var width = that.chartAreaSize.width;
372       var height = that.chartAreaSize.height;
373       that.radius_ = Math.max(MIN_RADIUS, Math.min(width, height) / 2);
374
375       d3.select(that.pieGroup_).attr(
376           'transform',
377           'translate(' + width / 2 + ',' + height / 2 + ')');
378
379       that.selectedNode_ = null;
380       that.clickStack_ = new Array();
381       that.clickStack_.push(0);
382
383       var depth = 1.0 + d3.max(that.nodes_, function(d) { return d.depth; });
384       that.yDomainMin_ = 1.0 / depth;
385       that.yDomainMax_ = Math.min(Math.max(depth, 20), 50) / depth;
386
387       that.xDomainScale_ = d3.scale.linear()
388           .range([0, 2 * Math.PI]);
389
390       that.yDomainScale_ = d3.scale.sqrt()
391           .domain([that.yDomainMin_, that.yDomainMax_])
392           .range([50, that.radius_]);
393
394       that.arc_ = d3.svg.arc()
395           .startAngle(function(d) {
396             return Math.max(0, Math.min(2 * Math.PI, that.xDomainScale_(d.x)));
397           })
398           .endAngle(function(d) {
399             return Math.max(0,
400                 Math.min(2 * Math.PI, that.xDomainScale_(d.x + d.dx)));
401           })
402           .innerRadius(function(d) {
403             return Math.max(0, that.yDomainScale_((d.y)));
404           })
405           .outerRadius(function(d) {
406             return Math.max(0, that.yDomainScale_((d.y + d.dy)));
407           });
408
409
410       // Bounding circle underneath the sunburst, to make it easier to detect
411       // when the mouse leaves the parent g.
412       that.backSel_.append('svg:circle')
413           .attr('r', that.radius_)
414           .style('opacity', 0.0)
415           .on('click', function() { that.zoomOut_(); });
416
417
418       that.vis_ = d3.select(that.pathsGroup_);
419       that.selectedNodeID = 0;
420       that.vis_.on('mouseleave', function(d) { that.mouseLeave_(d); });
421     },
422
423     updateHighlight_: function() {
424       ChartBase.prototype.updateHighlight_.call(this);
425       // Update color of pie segments.
426       var pathsGroupSel = d3.select(this.pathsGroup_);
427       var that = this;
428       pathsGroupSel.selectAll('.arc').each(function(d, i) {
429         var origData = that.data_[i];
430         var highlighted = origData.label == that.currentHighlightedLegendKey;
431         var color = getColorOfKey(origData.label, highlighted);
432         this.style.fill = getColorOfKey(origData.label, highlighted);
433       });
434     }
435   };
436
437   return {
438     SunburstChart: SunburstChart
439   };
440 });
441 </script>