Home | History | Annotate | Download | only in base
      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 
      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 
     44 tr.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