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.
7 <link rel="import" href="/tvcm/ui.html">
8 <link rel="import" href="/tvcm/ui/color_scheme.html">
9 <link rel="import" href="/tvcm/ui/d3.html">
18 -webkit-user-select: none;
22 .chart-base .axis path,
23 .chart-base .axis line {
25 shape-rendering: crispEdges;
30 <template id="chart-base-template">
31 <svg> <!-- svg tag is dropped by ChartBase.decorate. -->
32 <g xmlns="http://www.w3.org/2000/svg" id="chart-area">
33 <g class="x axis"></g>
34 <g class="y axis"></g>
35 <text id="title"></text>
43 tvcm.exportTo('tvcm.ui', function() {
44 var THIS_DOC = document.currentScript.ownerDocument;
46 var svgNS = 'http://www.w3.org/2000/svg';
47 var highlightIdBoost = tvcm.ui.getColorPaletteHighlightIdBoost();
49 function getColorOfKey(key, selected) {
50 var id = tvcm.ui.getStringColorId(key);
52 id += highlightIdBoost;
53 return tvcm.ui.getColorPalette()[id];
57 * A virtual base class for basic charts that provides X and Y axes, if
58 * needed, a title, and legend.
62 var ChartBase = tvcm.ui.define('svg', undefined, svgNS);
64 ChartBase.prototype = {
65 __proto__: HTMLUnknownElement.prototype,
67 decorate: function() {
68 this.classList.add('chart-base');
69 this.chartTitle_ = undefined;
70 this.data_ = undefined;
71 this.seriesKeys_ = undefined;
75 // This should use tvcm.instantiateTemplate. However, creating
76 // svg-namespaced elements inside a template isn't possible. Thus, this
78 var template = THIS_DOC.querySelector('#chart-base-template');
79 var svgEl = template.content.querySelector('svg');
80 for (var i = 0; i < svgEl.children.length; i++)
81 this.appendChild(svgEl.children[i].cloneNode(true));
83 // svg likes to take over width & height properties for some reason. This
85 Object.defineProperty(
90 set: function(width) {
92 this.updateContents_();
95 Object.defineProperty(
100 set: function(height) {
101 this.height_ = height;
102 this.updateContents_();
111 set chartTitle(chartTitle) {
112 this.chartTitle_ = chartTitle;
113 this.updateContents_();
116 get chartAreaElement() {
117 return this.querySelector('#chart-area');
124 setSize: function(size) {
125 this.width_ = size.width;
126 this.height_ = size.height;
127 this.updateContents_();
131 var margin = {top: 20, right: 20, bottom: 30, left: 50};
132 if (this.chartTitle_)
137 get chartAreaSize() {
138 var margin = this.margin;
140 width: this.width_ - margin.left - margin.right,
141 height: this.height_ - margin.top - margin.bottom
145 getLegendKeys_: function() {
146 throw new Error('Not implemented');
149 updateScales_: function(width, height) {
150 throw new Error('Not implemented');
153 updateContents_: function() {
154 var margin = this.margin;
155 var width = this.chartAreaSize.width;
156 var height = this.chartAreaSize.height;
158 var thisSel = d3.select(this);
159 thisSel.attr('width', this.width_);
160 thisSel.attr('height', this.height_);
162 var chartAreaSel = d3.select(this.chartAreaElement);
165 'translate(' + margin.left + ',' + margin.top + ')');
167 this.updateScales_(width, height);
170 if (this.xScale_ && this.yScale_) {
171 var xAxisRenderer = d3.svg.axis()
175 var yAxisRenderer = d3.svg.axis()
179 chartAreaSel.select('.x.axis')
180 .attr('transform', 'translate(0,' + height + ')')
181 .call(xAxisRenderer);
183 chartAreaSel.select('.y.axis')
184 .call(yAxisRenderer);
188 var titleSel = chartAreaSel.select('#title');
189 if (this.chartTitle_) {
190 titleSel.attr('transform', 'translate(' + width * 0.5 + ',-5)')
191 .style('display', undefined)
192 .style('text-anchor', 'middle')
193 .attr('class', 'title')
194 .attr('width', width)
195 .text(this.chartTitle_);
197 titleSel.style('display', 'none');
201 this.updateLegend_();
204 updateLegend_: function() {
205 var keys = this.getLegendKeys_();
206 if (keys === undefined)
209 var chartAreaSel = d3.select(this.chartAreaElement);
210 var chartAreaSize = this.chartAreaSize;
212 var legendEntriesSel = chartAreaSel.selectAll('.legend')
213 .data(keys.slice().reverse());
215 legendEntriesSel.enter()
217 .attr('class', 'legend')
218 .attr('transform', function(d, i) {
219 return 'translate(0,' + i * 20 + ')';
220 }).append('text').text(function(key) {
223 legendEntriesSel.exit().remove();
225 legendEntriesSel.attr('x', chartAreaSize.width - 18)
228 .style('fill', function(key) {
229 var selected = this.currentHighlightedLegendKey === key;
230 return getColorOfKey(key, selected);
233 legendEntriesSel.selectAll('text')
234 .attr('x', chartAreaSize.width - 24)
237 .style('text-anchor', 'end')
238 .text(function(d) { return d; });
241 get highlightedLegendKey() {
242 return this.highlightedLegendKey_;
245 set highlightedLegendKey(highlightedLegendKey) {
246 this.highlightedLegendKey_ = highlightedLegendKey;
247 this.updateHighlight_();
250 get currentHighlightedLegendKey() {
251 if (this.tempHighlightedLegendKey_)
252 return this.tempHighlightedLegendKey_;
253 return this.highlightedLegendKey_;
256 pushTempHighlightedLegendKey: function(key) {
257 if (this.tempHighlightedLegendKey_)
258 throw new Error('push cannot nest');
259 this.tempHighlightedLegendKey_ = key;
260 this.updateHighlight_();
263 popTempHighlightedLegendKey: function(key) {
264 if (this.tempHighlightedLegendKey_ != key)
265 throw new Error('pop cannot happen');
266 this.tempHighlightedLegendKey_ = undefined;
267 this.updateHighlight_();
270 updateHighlight_: function() {
271 // Update label colors.
272 var chartAreaSel = d3.select(this.chartAreaElement);
273 var legendEntriesSel = chartAreaSel.selectAll('.legend');
276 legendEntriesSel.each(function(key) {
277 var highlighted = key == that.currentHighlightedLegendKey;
278 var color = getColorOfKey(key, highlighted);
279 this.style.fill = color;
281 this.style.fontWeight = 'bold';
283 this.style.fontWeight = '';
289 getColorOfKey: getColorOfKey,