1<!DOCTYPE html> 2<!-- 3Copyright (c) 2014 The Chromium Authors. All rights reserved. 4Use of this source code is governed by a BSD-style license that can be 5found in the LICENSE file. 6--> 7 8<link rel="import" href="/tracing/base/color_scheme.html"> 9<link rel="import" href="/tracing/ui/base/d3.html"> 10<link rel="import" href="/tracing/ui/base/ui.html"> 11 12<style> 13 * /deep/ .chart-base #title { 14 font-size: 16pt; 15 } 16 17 * /deep/ .chart-base { 18 font-size: 12pt; 19 -webkit-user-select: none; 20 cursor: default; 21 } 22 23 * /deep/ .chart-base .axis path, 24 * /deep/ .chart-base .axis line { 25 fill: none; 26 shape-rendering: crispEdges; 27 stroke: #000; 28 } 29</style> 30 31<template id="chart-base-template"> 32 <svg> <!-- svg tag is dropped by ChartBase.decorate. --> 33 <g xmlns="http://www.w3.org/2000/svg" id="chart-area"> 34 <g class="x axis"></g> 35 <g class="y axis"></g> 36 <text id="title"></text> 37 </g> 38 </svg> 39</template> 40 41<script> 42'use strict'; 43 44tr.exportTo('tr.ui.b', function() { 45 var THIS_DOC = document.currentScript.ownerDocument; 46 47 var svgNS = 'http://www.w3.org/2000/svg'; 48 var ColorScheme = tr.b.ColorScheme; 49 50 function getColorOfKey(key, selected) { 51 var id = ColorScheme.getColorIdForGeneralPurposeString(key); 52 if (selected) 53 id += ColorScheme.properties.brightenedOffsets[0]; 54 return ColorScheme.colorsAsStrings[id]; 55 } 56 57 /** 58 * A virtual base class for basic charts that provides X and Y axes, if 59 * needed, a title, and legend. 60 * 61 * @constructor 62 */ 63 var ChartBase = tr.ui.b.define('svg', undefined, svgNS); 64 65 ChartBase.prototype = { 66 __proto__: HTMLUnknownElement.prototype, 67 68 decorate: function() { 69 this.classList.add('chart-base'); 70 this.chartTitle_ = undefined; 71 this.seriesKeys_ = undefined; 72 this.width_ = 400; 73 this.height_ = 300; 74 75 // This should use tr.ui.b.instantiateTemplate. However, creating 76 // svg-namespaced elements inside a template isn't possible. Thus, this 77 // hack. 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)); 82 83 // svg likes to take over width & height properties for some reason. This 84 // works around it. 85 Object.defineProperty( 86 this, 'width', { 87 get: function() { 88 return this.width_; 89 }, 90 set: function(width) { 91 this.width_ = width; 92 this.updateContents_(); 93 } 94 }); 95 Object.defineProperty( 96 this, 'height', { 97 get: function() { 98 return this.height_; 99 }, 100 set: function(height) { 101 this.height_ = height; 102 this.updateContents_(); 103 } 104 }); 105 }, 106 107 get chartTitle() { 108 return this.chartTitle_; 109 }, 110 111 set chartTitle(chartTitle) { 112 this.chartTitle_ = chartTitle; 113 this.updateContents_(); 114 }, 115 116 get chartAreaElement() { 117 return this.querySelector('#chart-area'); 118 }, 119 120 setSize: function(size) { 121 this.width_ = size.width; 122 this.height_ = size.height; 123 this.updateContents_(); 124 }, 125 126 getMargin_: function() { 127 var margin = {top: 20, right: 20, bottom: 30, left: 50}; 128 if (this.chartTitle_) 129 margin.top += 20; 130 return margin; 131 }, 132 133 get margin() { 134 return this.getMargin_(); 135 }, 136 137 get chartAreaSize() { 138 var margin = this.margin; 139 return { 140 width: this.width_ - margin.left - margin.right, 141 height: this.height_ - margin.top - margin.bottom 142 }; 143 }, 144 145 getLegendKeys_: function() { 146 throw new Error('Not implemented'); 147 }, 148 149 updateScales_: function() { 150 throw new Error('Not implemented'); 151 }, 152 153 updateContents_: function() { 154 var margin = this.margin; 155 156 var thisSel = d3.select(this); 157 thisSel.attr('width', this.width_); 158 thisSel.attr('height', this.height_); 159 160 var chartAreaSel = d3.select(this.chartAreaElement); 161 chartAreaSel.attr('transform', 162 'translate(' + margin.left + ',' + margin.top + ')'); 163 164 this.updateScales_(); 165 this.updateTitle_(chartAreaSel); 166 this.updateLegend_(); 167 }, 168 169 updateTitle_: function(chartAreaSel) { 170 var titleSel = chartAreaSel.select('#title'); 171 if (!this.chartTitle_) { 172 titleSel.style('display', 'none'); 173 return; 174 } 175 var width = this.chartAreaSize.width; 176 titleSel.attr('transform', 'translate(' + width * 0.5 + ',-5)') 177 .style('display', undefined) 178 .style('text-anchor', 'middle') 179 .attr('class', 'title') 180 .attr('width', width) 181 .text(this.chartTitle_); 182 }, 183 184 // TODO(charliea): We should change updateLegend_ so that it ellipsizes the 185 // series names after a certain point. Otherwise, the series names start 186 // dipping below the x-axis and continue on outside of the viewport. 187 updateLegend_: function() { 188 var keys = this.getLegendKeys_(); 189 if (keys === undefined) 190 return; 191 192 var chartAreaSel = d3.select(this.chartAreaElement); 193 var chartAreaSize = this.chartAreaSize; 194 195 var legendEntriesSel = chartAreaSel.selectAll('.legend') 196 .data(keys.slice().reverse()); 197 198 legendEntriesSel.enter() 199 .append('g') 200 .attr('class', 'legend') 201 .attr('transform', function(d, i) { 202 return 'translate(0,' + i * 20 + ')'; 203 }) 204 .append('text').text(function(key) { 205 return key; 206 }); 207 legendEntriesSel.exit().remove(); 208 209 legendEntriesSel.attr('x', chartAreaSize.width - 18) 210 .attr('width', 18) 211 .attr('height', 18) 212 .style('fill', function(key) { 213 var selected = this.currentHighlightedLegendKey === key; 214 return getColorOfKey(key, selected); 215 }.bind(this)); 216 217 legendEntriesSel.selectAll('text') 218 .attr('x', chartAreaSize.width - 24) 219 .attr('y', 9) 220 .attr('dy', '.35em') 221 .style('text-anchor', 'end') 222 .text(function(d) { return d; }); 223 }, 224 225 get highlightedLegendKey() { 226 return this.highlightedLegendKey_; 227 }, 228 229 set highlightedLegendKey(highlightedLegendKey) { 230 this.highlightedLegendKey_ = highlightedLegendKey; 231 this.updateHighlight_(); 232 }, 233 234 get currentHighlightedLegendKey() { 235 if (this.tempHighlightedLegendKey_) 236 return this.tempHighlightedLegendKey_; 237 return this.highlightedLegendKey_; 238 }, 239 240 pushTempHighlightedLegendKey: function(key) { 241 if (this.tempHighlightedLegendKey_) 242 throw new Error('push cannot nest'); 243 this.tempHighlightedLegendKey_ = key; 244 this.updateHighlight_(); 245 }, 246 247 popTempHighlightedLegendKey: function(key) { 248 if (this.tempHighlightedLegendKey_ != key) 249 throw new Error('pop cannot happen'); 250 this.tempHighlightedLegendKey_ = undefined; 251 this.updateHighlight_(); 252 }, 253 254 updateHighlight_: function() { 255 // Update label colors. 256 var chartAreaSel = d3.select(this.chartAreaElement); 257 var legendEntriesSel = chartAreaSel.selectAll('.legend'); 258 259 var that = this; 260 legendEntriesSel.each(function(key) { 261 var highlighted = key == that.currentHighlightedLegendKey; 262 var color = getColorOfKey(key, highlighted); 263 this.style.fill = color; 264 if (highlighted) 265 this.style.fontWeight = 'bold'; 266 else 267 this.style.fontWeight = ''; 268 }); 269 } 270 }; 271 272 return { 273 getColorOfKey: getColorOfKey, 274 ChartBase: ChartBase 275 }; 276}); 277</script> 278