Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(53)

Side by Side Diff: tracing/tracing/ui/base/chart_base.html

Issue 2162963002: [polymer] Merge of master into polymer10-migration (Closed) Base URL: git@github.com:catapult-project/catapult.git@polymer10-migration
Patch Set: Merge polymer10-migration int polymer10-merge Created 4 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « tracing/tracing/ui/base/bar_chart_test.html ('k') | tracing/tracing/ui/base/chart_base_2d.html » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 <!DOCTYPE html> 1 <!DOCTYPE html>
2 <!-- 2 <!--
3 Copyright (c) 2014 The Chromium Authors. All rights reserved. 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 4 Use of this source code is governed by a BSD-style license that can be
5 found in the LICENSE file. 5 found in the LICENSE file.
6 --> 6 -->
7 7
8 <link rel="import" href="/tracing/base/color_scheme.html"> 8 <link rel="import" href="/tracing/base/color_scheme.html">
9 <link rel="import" href="/tracing/ui/analysis/analysis_link.html">
9 <link rel="import" href="/tracing/ui/base/d3.html"> 10 <link rel="import" href="/tracing/ui/base/d3.html">
10 <link rel="import" href="/tracing/ui/base/ui.html"> 11 <link rel="import" href="/tracing/ui/base/ui.html">
11 12
13 <dom-module id="tr-ui-b-chart-legend-key">
14 <template>
15 <style>
16 #checkbox {
17 margin: 0;
18 visibility: hidden;
19 vertical-align: text-top;
20 }
21 #label, #link {
22 white-space: nowrap;
23 text-overflow: ellipsis;
24 overflow: hidden;
25 display: inline-block;
26 }
27 </style>
28
29 <input type=checkbox id="checkbox" checked>
30 <tr-ui-a-analysis-link id="link"></tr-ui-a-analysis-link>
31 <label id="label"></label>
32 </template>
33 </dom-module>
34 <script>
35 'use strict';
36 Polymer({
37 is: 'tr-ui-b-chart-legend-key',
38
39 ready: function() {
40 this.$.checkbox.addEventListener(
41 'change', this.onCheckboxChange_.bind(this));
42 },
43
44 /**
45 * Dispatch an event when the checkbox is toggled.
46 * The checkbox is visible when optional is set to true.
47 */
48 onCheckboxChange_: function() {
49 tr.b.dispatchSimpleEvent(this, tr.ui.b.DataSeriesEnableChangeEventType,
50 true, false,
51 {key: Polymer.dom(this).textContent, enabled: this.enabled});
52 },
53
54 set textContent(t) {
55 Polymer.dom(this.$.label).textContent = t;
56 Polymer.dom(this.$.link).textContent = t;
57 this.updateContents_();
58 },
59
60 set width(w) {
61 w -= 20; // reserve 20px for the checkbox
62 this.$.link.style.width = w + 'px';
63 this.$.label.style.width = w + 'px';
64 },
65
66 get textContent() {
67 return Polymer.dom(this.$.label).textContent;
68 },
69
70 /**
71 * When a legend-key is "optional", then its checkbox is visible to allow
72 * the user to enable/disable the data series for the key.
73 * See ChartBase.customizeOptionalSeries().
74 *
75 * @param {boolean} optional
76 */
77 set optional(optional) {
78 this.$.checkbox.style.visibility = optional ? 'visible' : 'hidden';
79 },
80
81 get optional() {
82 return this.$.checkbox.style.visibility === 'visible';
83 },
84
85 set enabled(enabled) {
86 this.$.checkbox.checked = enabled ? 'checked' : '';
87 },
88
89 get enabled() {
90 return this.$.checkbox.checked;
91 },
92
93 set color(c) {
94 this.$.label.style.color = c;
95 this.$.link.color = c;
96 },
97
98 /**
99 * When target is defined, label is hidden and link is shown.
100 * When the link is clicked, then a RequestSelectionChangeEvent is
101 * dispatched containing the target.
102 * When target is undefined, label is shown and link is hidden, so that the
103 * link is not clickable.
104 * See ChartBase.customizeLegendTargets().
105 */
106 set target(target) {
107 this.$.link.setSelectionAndContent(
108 target, Polymer.dom(this.$.label).textContent);
109 this.updateContents_();
110 },
111
112 get target() {
113 return this.$.link.selection;
114 },
115
116 updateContents_: function() {
117 this.$.link.style.display = this.target ? '' : 'none';
118 this.$.label.style.display = this.target ? 'none' : '';
119 this.$.label.htmlFor = this.optional ? 'checkbox' : '';
120 }
121 });
122 </script>
123
12 <style> 124 <style>
13 * /deep/ .chart-base #title { 125 * /deep/ .chart-base #title {
14 font-size: 16pt; 126 font-size: 16pt;
15 } 127 }
16 128
17 * /deep/ .chart-base { 129 * /deep/ .chart-base {
18 font-size: 12pt;
19 -webkit-user-select: none; 130 -webkit-user-select: none;
20 cursor: default; 131 cursor: default;
21 } 132 }
22 133
23 * /deep/ .chart-base .axis path, 134 * /deep/ .chart-base .axis path,
24 * /deep/ .chart-base .axis line { 135 * /deep/ .chart-base .axis line {
25 fill: none; 136 fill: none;
26 shape-rendering: crispEdges; 137 shape-rendering: crispEdges;
27 stroke: #000; 138 stroke: #000;
28 } 139 }
140
141 * /deep/ .chart-base .legend body {
142 margin: 0;
143 }
29 </style> 144 </style>
30 145
31 <template id="chart-base-template"> 146 <template id="chart-base-template">
32 <svg> <!-- svg tag is dropped by ChartBase.decorate. --> 147 <svg> <!-- svg tag is dropped by ChartBase.decorate. -->
33 <g xmlns="http://www.w3.org/2000/svg" id="chart-area"> 148 <g xmlns="http://www.w3.org/2000/svg" id="chart-area">
34 <g class="x axis"></g> 149 <g class="x axis"></g>
35 <g class="y axis"></g> 150 <g class="y axis"></g>
36 <text id="title"></text> 151 <text id="title"></text>
37 </g> 152 </g>
38 </svg> 153 </svg>
39 </template> 154 </template>
40 155
41 <script> 156 <script>
42 'use strict'; 157 'use strict';
43 158
44 tr.exportTo('tr.ui.b', function() { 159 tr.exportTo('tr.ui.b', function() {
160 var DataSeriesEnableChangeEventType = 'data-series-enabled-change';
161
45 var THIS_DOC = document.currentScript.ownerDocument; 162 var THIS_DOC = document.currentScript.ownerDocument;
46 163
47 var svgNS = 'http://www.w3.org/2000/svg'; 164 var svgNS = 'http://www.w3.org/2000/svg';
48 var ColorScheme = tr.b.ColorScheme; 165 var ColorScheme = tr.b.ColorScheme;
49 166
50 function getColorOfKey(key, selected) { 167 function getColorOfKey(key, selected) {
51 var id = ColorScheme.getColorIdForGeneralPurposeString(key); 168 var id = ColorScheme.getColorIdForGeneralPurposeString(key);
52 if (selected) 169 if (selected)
53 id += ColorScheme.properties.brightenedOffsets[0]; 170 id += ColorScheme.properties.brightenedOffsets[0];
54 return ColorScheme.colorsAsStrings[id]; 171 return ColorScheme.colorsAsStrings[id];
55 } 172 }
56 173
174 function DataSeries(key) {
175 this.key_ = key;
176 this.target_ = undefined;
177 this.optional_ = false;
178 this.enabled_ = true;
179 }
180
181 DataSeries.prototype = {
182 get key() {
183 return this.key_;
184 },
185
186 get optional() {
187 return this.optional_;
188 },
189
190 set optional(optional) {
191 this.optional_ = optional;
192 },
193
194 get enabled() {
195 return this.enabled_;
196 },
197
198 set enabled(enabled) {
199 // If the caller is disabling a data series, but it wasn't optional, then
200 // force it to be optional.
201 if (!this.optional && !enabled)
202 this.optional = true;
203 this.enabled_ = enabled;
204 },
205
206 get target() {
207 return this.target_;
208 },
209
210 set target(t) {
211 this.target_ = t;
212 }
213 };
214
57 /** 215 /**
58 * A virtual base class for basic charts that provides X and Y axes, if 216 * A virtual base class for basic charts that provides X and Y axes, if
59 * needed, a title, and legend. 217 * needed, a title, and legend.
60 * 218 *
61 * @constructor 219 * @constructor
62 */ 220 */
63 var ChartBase = tr.ui.b.define('svg', undefined, svgNS); 221 var ChartBase = tr.ui.b.define('svg', undefined, svgNS);
64 222
65 ChartBase.prototype = { 223 ChartBase.prototype = {
66 __proto__: HTMLDivElement.prototype, 224 __proto__: HTMLUnknownElement.prototype,
225
226 getDataSeries: function(key) {
227 if (!this.seriesByKey_.has(key))
228 this.seriesByKey_.set(key, new DataSeries(key));
229 return this.seriesByKey_.get(key);
230 },
67 231
68 decorate: function() { 232 decorate: function() {
69 Polymer.dom(this).classList.add('chart-base'); 233 Polymer.dom(this).classList.add('chart-base');
70 this.chartTitle_ = undefined; 234 this.chartTitle_ = undefined;
71 this.seriesKeys_ = undefined; 235 this.seriesByKey_ = new Map();
72 this.width_ = 400; 236 this.width_ = 400;
73 this.height_ = 300; 237 this.height_ = 300;
238 this.margin = {top: 20, right: 72, bottom: 30, left: 50};
239 this.hideLegend_ = false;
74 240
75 // This should use tr.ui.b.instantiateTemplate. However, creating 241 // This should use tr.ui.b.instantiateTemplate. However, creating
76 // svg-namespaced elements inside a template isn't possible. Thus, this 242 // svg-namespaced elements inside a template isn't possible. Thus, this
77 // hack. 243 // hack.
78 var template = Polymer.dom(THIS_DOC) 244 var template =
79 .querySelector('#chart-base-template'); 245 Polymer.dom(THIS_DOC).querySelector('#chart-base-template');
80 var svgEl = Polymer.dom(template.content).querySelector('svg'); 246 var svgEl = Polymer.dom(template.content).querySelector('svg');
81 for (var i = 0; i < svgEl.children.length; i++) 247 for (var i = 0; i < Polymer.dom(svgEl).children.length; i++)
82 Polymer.dom(this).appendChild(svgEl.children[i].cloneNode(true)); 248 Polymer.dom(this).appendChild(
249 Polymer.dom(svgEl.children[i]).cloneNode(true));
83 250
84 // svg likes to take over width & height properties for some reason. This 251 // svg likes to take over width & height properties for some reason. This
85 // works around it. 252 // works around it.
86 Object.defineProperty( 253 Object.defineProperty(
87 this, 'width', { 254 this, 'width', {
88 get: function() { 255 get: function() {
89 return this.width_; 256 return this.width_;
90 }, 257 },
91 set: function(width) { 258 set: function(width) {
92 this.width_ = width; 259 this.width_ = width;
93 this.updateContents_(); 260 this.updateContents_();
94 } 261 }
95 }); 262 });
96 Object.defineProperty( 263 Object.defineProperty(
97 this, 'height', { 264 this, 'height', {
98 get: function() { 265 get: function() {
99 return this.height_; 266 return this.height_;
100 }, 267 },
101 set: function(height) { 268 set: function(height) {
102 this.height_ = height; 269 this.height_ = height;
103 this.updateContents_(); 270 this.updateContents_();
104 } 271 }
105 }); 272 });
273 this.addEventListener(DataSeriesEnableChangeEventType,
274 this.onDataSeriesEnableChange_.bind(this));
275 },
276
277 get hideLegend() {
278 return this.hideLegend_;
279 },
280
281 set hideLegend(h) {
282 this.hideLegend_ = h;
283 this.updateContents_();
284 },
285
286 isSeriesEnabled: function(key) {
287 return this.getDataSeries(key).enabled;
288 },
289
290 onDataSeriesEnableChange_: function(event) {
291 this.getDataSeries(event.key).enabled = event.enabled;
292 this.updateContents_();
106 }, 293 },
107 294
108 get chartTitle() { 295 get chartTitle() {
109 return this.chartTitle_; 296 return this.chartTitle_;
110 }, 297 },
111 298
112 set chartTitle(chartTitle) { 299 set chartTitle(chartTitle) {
300 if (chartTitle && !this.chartTitle_)
301 this.margin.top += this.titleMarginPx;
302 else if (this.chartTitle_ && !chartTitle)
303 this.margin.top -= this.titleMarginPx;
304
113 this.chartTitle_ = chartTitle; 305 this.chartTitle_ = chartTitle;
114 this.updateContents_(); 306 this.updateContents_();
115 }, 307 },
116 308
309 get titleMarginPx() {
310 return 20;
311 },
312
117 get chartAreaElement() { 313 get chartAreaElement() {
118 return Polymer.dom(this).querySelector('#chart-area'); 314 return Polymer.dom(this).querySelector('#chart-area');
119 }, 315 },
120 316
121 setSize: function(size) { 317 setSize: function(size) {
122 this.width_ = size.width; 318 this.width_ = size.width;
123 this.height_ = size.height; 319 this.height_ = size.height;
124 this.updateContents_(); 320 this.updateContents_();
125 }, 321 },
126 322
127 getMargin_: function() {
128 var margin = {top: 20, right: 20, bottom: 30, left: 50};
129 if (this.chartTitle_)
130 margin.top += 20;
131 return margin;
132 },
133
134 get margin() {
135 return this.getMargin_();
136 },
137
138 get chartAreaSize() { 323 get chartAreaSize() {
139 var margin = this.margin;
140 return { 324 return {
141 width: this.width_ - margin.left - margin.right, 325 width: this.width_ - this.margin.left - this.margin.right,
142 height: this.height_ - margin.top - margin.bottom 326 height: this.height_ - this.margin.top - this.margin.bottom
143 }; 327 };
144 }, 328 },
145 329
146 getLegendKeys_: function() { 330 /**
147 throw new Error('Not implemented'); 331 * Legend keys can be clickable links instead of plain text.
332 * When a legend key link is clicked, a RequestSelectionChangeEvent is
333 * dispatched containing arbitrary data. ChartBase calls that arbitrary data
334 * the "target" of the legend key link.
335 * In order to turn the legend key for the 'foo' data series into a
336 * clickable link, call customizeLegendTargets({foo: target}). When the user
337 * clicks on the legend key link for 'foo', then a
338 * RequestSelectionChangeEvent will be dispatched, and its |selection| field
339 * will be the |target| value for the 'foo' key in |delta|.
340 *
341 * @param {!Object} delta
342 */
343 customizeLegendTargets: function(delta) {
344 tr.b.iterItems(delta, function(key, value) {
345 this.getDataSeries(key).target = value;
346 }, this);
347 },
348
349 /**
350 * Optional data series can be enabled and disabled using checkboxes.
351 * In order to allow the user to enable/disabled the 'foo' data series,
352 * call customizeOptionalSeries({foo: true}). This will show a checkbox
353 * next to the 'foo' legend key. When the user toggles the checkbox, then a
354 * DataSeriesEnableChangeEvent will be dispatched with its |key| = 'foo'.
355 * ChartBase listens for that event and updates |isSeriesEnabled('foo')| to
356 * reflect the state of that checkbox, and calls updateContents_().
357 * Subclasses are responsible for implementing updateContents_() in order to
358 * hiding disabled data series -- see BarChart.
359 *
360 * @param {!Object} delta
361 */
362 customizeOptionalSeries: function(delta) {
363 tr.b.iterItems(delta, function(key, value) {
364 this.getDataSeries(key).optional = value;
365 }, this);
366 },
367
368 /**
369 * Data series can be enabled and disabled.
370 * See customizeOptionalSeries() in order to allow the user to
371 * enable/disable data series manually. Callers may call
372 * customizeEnabledSeries({foo: false}) in order to automatically
373 * disable the 'foo' data series, for example.
374 *
375 * @param {!Object} delta
376 */
377 customizeEnabledSeries: function(delta) {
378 tr.b.iterItems(delta, function(key, value) {
379 this.getDataSeries(key).enabled = value;
380 }, this);
148 }, 381 },
149 382
150 updateScales_: function() { 383 updateScales_: function() {
151 throw new Error('Not implemented'); 384 throw new Error('Not implemented');
152 }, 385 },
153 386
154 updateContents_: function() { 387 updateContents_: function() {
155 var margin = this.margin;
156
157 var thisSel = d3.select(this); 388 var thisSel = d3.select(this);
158 thisSel.attr('width', this.width_); 389 thisSel.attr('width', this.width_);
159 thisSel.attr('height', this.height_); 390 thisSel.attr('height', this.height_);
160 391
161 var chartAreaSel = d3.select(this.chartAreaElement); 392 var chartAreaSel = d3.select(this.chartAreaElement);
162 chartAreaSel.attr('transform', 393 chartAreaSel.attr('transform',
163 'translate(' + margin.left + ',' + margin.top + ')'); 394 'translate(' + this.margin.left + ',' + this.margin.top + ')');
164 395
165 this.updateScales_(); 396 this.updateScales_();
166 this.updateTitle_(chartAreaSel); 397 this.updateTitle_(chartAreaSel);
167 this.updateLegend_(); 398 this.updateLegend_();
168 }, 399 },
169 400
170 updateTitle_: function(chartAreaSel) { 401 updateTitle_: function(chartAreaSel) {
171 var titleSel = chartAreaSel.select('#title'); 402 var titleSel = chartAreaSel.select('#title');
172 if (!this.chartTitle_) { 403 if (!this.chartTitle_) {
173 titleSel.style('display', 'none'); 404 titleSel.style('display', 'none');
174 return; 405 return;
175 } 406 }
176 var width = this.chartAreaSize.width; 407 var width = this.chartAreaSize.width;
177 titleSel.attr('transform', 'translate(' + width * 0.5 + ',-5)') 408 titleSel.attr('transform', 'translate(' + width * 0.5 + ',-5)')
178 .style('display', undefined) 409 .style('display', undefined)
179 .style('text-anchor', 'middle') 410 .style('text-anchor', 'middle')
180 .attr('class', 'title') 411 .attr('class', 'title')
181 .attr('width', width) 412 .attr('width', width)
182 .text(this.chartTitle_); 413 .text(this.chartTitle_);
183 }, 414 },
184 415
185 // TODO(charliea): We should change updateLegend_ so that it ellipsizes the
186 // series names after a certain point. Otherwise, the series names start
187 // dipping below the x-axis and continue on outside of the viewport.
188 updateLegend_: function() { 416 updateLegend_: function() {
189 var keys = this.getLegendKeys_(); 417 var chartAreaSel = d3.select(this.chartAreaElement);
190 if (keys === undefined) 418 chartAreaSel.selectAll('.legend').remove();
419 if (this.hideLegend)
191 return; 420 return;
192 421
193 var chartAreaSel = d3.select(this.chartAreaElement); 422 var series = [...this.seriesByKey_.values()].reverse();
194 var chartAreaSize = this.chartAreaSize; 423 var legendEntriesSel = chartAreaSel.selectAll('.legend').data(series);
195 424
196 var legendEntriesSel = chartAreaSel.selectAll('.legend') 425 var width = this.margin.right - 2;
197 .data(keys.slice().reverse());
198
199 legendEntriesSel.enter() 426 legendEntriesSel.enter()
200 .append('g') 427 .append('foreignObject')
201 .attr('class', 'legend') 428 .attr('class', 'legend')
202 .attr('transform', function(d, i) { 429 .attr('x', this.chartAreaSize.width + 2)
203 return 'translate(0,' + i * 20 + ')'; 430 .attr('width', width)
431 .attr('height', 18)
432 .attr('transform', function(series, i) {
433 return 'translate(0,' + i * 18 + ')';
204 }) 434 })
205 .append('text').text(function(key) { 435 .append('xhtml:body')
206 return key; 436 .append('tr-ui-b-chart-legend-key')
207 }); 437 .property('color', function(series) {
438 var selected = this.currentHighlightedLegendKey === series.key;
439 return getColorOfKey(series.key, selected);
440 }.bind(this))
441 .property('width', width)
442 .property('target', function(series) { return series.target; })
443 .property('optional', function(series) { return series.optional; })
444 .property('enabled', function(series) { return series.enabled; })
445 .text(function(series) { return series.key; });
208 legendEntriesSel.exit().remove(); 446 legendEntriesSel.exit().remove();
209
210 legendEntriesSel.attr('x', chartAreaSize.width - 18)
211 .attr('width', 18)
212 .attr('height', 18)
213 .style('fill', function(key) {
214 var selected = this.currentHighlightedLegendKey === key;
215 return getColorOfKey(key, selected);
216 }.bind(this));
217
218 legendEntriesSel.selectAll('text')
219 .attr('x', chartAreaSize.width - 24)
220 .attr('y', 9)
221 .attr('dy', '.35em')
222 .style('text-anchor', 'end')
223 .text(function(d) { return d; });
224 }, 447 },
225 448
226 get highlightedLegendKey() { 449 get highlightedLegendKey() {
227 return this.highlightedLegendKey_; 450 return this.highlightedLegendKey_;
228 }, 451 },
229 452
230 set highlightedLegendKey(highlightedLegendKey) { 453 set highlightedLegendKey(highlightedLegendKey) {
231 this.highlightedLegendKey_ = highlightedLegendKey; 454 this.highlightedLegendKey_ = highlightedLegendKey;
232 this.updateHighlight_(); 455 this.updateHighlight_();
233 }, 456 },
(...skipping 30 matching lines...) Expand all
264 this.style.fill = color; 487 this.style.fill = color;
265 if (highlighted) 488 if (highlighted)
266 this.style.fontWeight = 'bold'; 489 this.style.fontWeight = 'bold';
267 else 490 else
268 this.style.fontWeight = ''; 491 this.style.fontWeight = '';
269 }); 492 });
270 } 493 }
271 }; 494 };
272 495
273 return { 496 return {
497 DataSeriesEnableChangeEventType: DataSeriesEnableChangeEventType,
274 getColorOfKey: getColorOfKey, 498 getColorOfKey: getColorOfKey,
275 ChartBase: ChartBase 499 ChartBase: ChartBase
276 }; 500 };
277 }); 501 });
278 </script> 502 </script>
OLDNEW
« no previous file with comments | « tracing/tracing/ui/base/bar_chart_test.html ('k') | tracing/tracing/ui/base/chart_base_2d.html » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698