blob: 6aba25cec5ed70be064654e396584f2729080f17 [file] [log] [blame]
<!DOCTYPE html>
<!--
Copyright (c) 2014 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
<link rel="import" href="/base/ui.html">
<link rel="import" href="/base/ui/color_scheme.html">
<link rel="import" href="/base/ui/d3.html">
<style>
* /deep/ .chart-base #title {
font-size: 16pt;
}
* /deep/ .chart-base {
font-size: 12pt;
-webkit-user-select: none;
cursor: default;
}
* /deep/ .chart-base .axis path,
* /deep/ .chart-base .axis line {
fill: none;
shape-rendering: crispEdges;
stroke: #000;
}
</style>
<template id="chart-base-template">
<svg> <!-- svg tag is dropped by ChartBase.decorate. -->
<g xmlns="http://www.w3.org/2000/svg" id="chart-area">
<g class="x axis"></g>
<g class="y axis"></g>
<text id="title"></text>
</g>
</svg>
</template>
<script>
'use strict';
tr.exportTo('tr.b.ui', function() {
var THIS_DOC = document.currentScript.ownerDocument;
var svgNS = 'http://www.w3.org/2000/svg';
var highlightIdBoost = tr.b.ui.getColorPaletteHighlightIdBoost();
function getColorOfKey(key, selected) {
var id = tr.b.ui.getColorIdForGeneralPurposeString(key);
if (selected)
id += highlightIdBoost;
return tr.b.ui.getColorPalette()[id];
}
/**
* A virtual base class for basic charts that provides X and Y axes, if
* needed, a title, and legend.
*
* @constructor
*/
var ChartBase = tr.b.ui.define('svg', undefined, svgNS);
ChartBase.prototype = {
__proto__: HTMLUnknownElement.prototype,
decorate: function() {
this.classList.add('chart-base');
this.chartTitle_ = undefined;
this.data_ = undefined;
this.seriesKeys_ = undefined;
this.width_ = 400;
this.height_ = 300;
// This should use tr.b.instantiateTemplate. However, creating
// svg-namespaced elements inside a template isn't possible. Thus, this
// hack.
var template = THIS_DOC.querySelector('#chart-base-template');
var svgEl = template.content.querySelector('svg');
for (var i = 0; i < svgEl.children.length; i++)
this.appendChild(svgEl.children[i].cloneNode(true));
// svg likes to take over width & height properties for some reason. This
// works around it.
Object.defineProperty(
this, 'width', {
get: function() {
return this.width_;
},
set: function(width) {
this.width_ = width;
this.updateContents_();
}
});
Object.defineProperty(
this, 'height', {
get: function() {
return this.height_;
},
set: function(height) {
this.height_ = height;
this.updateContents_();
}
});
},
get chartTitle() {
return chartTitle_;
},
set chartTitle(chartTitle) {
this.chartTitle_ = chartTitle;
this.updateContents_();
},
get chartAreaElement() {
return this.querySelector('#chart-area');
},
get data() {
return this.data_;
},
setSize: function(size) {
this.width_ = size.width;
this.height_ = size.height;
this.updateContents_();
},
get margin() {
var margin = {top: 20, right: 20, bottom: 30, left: 50};
if (this.chartTitle_)
margin.top += 20;
return margin;
},
get chartAreaSize() {
var margin = this.margin;
return {
width: this.width_ - margin.left - margin.right,
height: this.height_ - margin.top - margin.bottom
};
},
getLegendKeys_: function() {
throw new Error('Not implemented');
},
updateScales_: function(width, height) {
throw new Error('Not implemented');
},
updateContents_: function() {
var margin = this.margin;
var width = this.chartAreaSize.width;
var height = this.chartAreaSize.height;
var thisSel = d3.select(this);
thisSel.attr('width', this.width_);
thisSel.attr('height', this.height_);
var chartAreaSel = d3.select(this.chartAreaElement);
chartAreaSel.attr(
'transform',
'translate(' + margin.left + ',' + margin.top + ')');
this.updateScales_(width, height);
// Axes.
if (this.xScale_ && this.yScale_) {
var xAxisRenderer = d3.svg.axis()
.scale(this.xScale_)
.orient('bottom');
var yAxisRenderer = d3.svg.axis()
.scale(this.yScale_)
.orient('left');
chartAreaSel.select('.x.axis')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxisRenderer);
chartAreaSel.select('.y.axis')
.call(yAxisRenderer);
}
// Title.
var titleSel = chartAreaSel.select('#title');
if (this.chartTitle_) {
titleSel.attr('transform', 'translate(' + width * 0.5 + ',-5)')
.style('display', undefined)
.style('text-anchor', 'middle')
.attr('class', 'title')
.attr('width', width)
.text(this.chartTitle_);
} else {
titleSel.style('display', 'none');
}
// Basics
this.updateLegend_();
},
updateLegend_: function() {
var keys = this.getLegendKeys_();
if (keys === undefined)
return;
var chartAreaSel = d3.select(this.chartAreaElement);
var chartAreaSize = this.chartAreaSize;
var legendEntriesSel = chartAreaSel.selectAll('.legend')
.data(keys.slice().reverse());
legendEntriesSel.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function(d, i) {
return 'translate(0,' + i * 20 + ')';
}).append('text').text(function(key) {
return key;
});
legendEntriesSel.exit().remove();
legendEntriesSel.attr('x', chartAreaSize.width - 18)
.attr('width', 18)
.attr('height', 18)
.style('fill', function(key) {
var selected = this.currentHighlightedLegendKey === key;
return getColorOfKey(key, selected);
}.bind(this));
legendEntriesSel.selectAll('text')
.attr('x', chartAreaSize.width - 24)
.attr('y', 9)
.attr('dy', '.35em')
.style('text-anchor', 'end')
.text(function(d) { return d; });
},
get highlightedLegendKey() {
return this.highlightedLegendKey_;
},
set highlightedLegendKey(highlightedLegendKey) {
this.highlightedLegendKey_ = highlightedLegendKey;
this.updateHighlight_();
},
get currentHighlightedLegendKey() {
if (this.tempHighlightedLegendKey_)
return this.tempHighlightedLegendKey_;
return this.highlightedLegendKey_;
},
pushTempHighlightedLegendKey: function(key) {
if (this.tempHighlightedLegendKey_)
throw new Error('push cannot nest');
this.tempHighlightedLegendKey_ = key;
this.updateHighlight_();
},
popTempHighlightedLegendKey: function(key) {
if (this.tempHighlightedLegendKey_ != key)
throw new Error('pop cannot happen');
this.tempHighlightedLegendKey_ = undefined;
this.updateHighlight_();
},
updateHighlight_: function() {
// Update label colors.
var chartAreaSel = d3.select(this.chartAreaElement);
var legendEntriesSel = chartAreaSel.selectAll('.legend');
var that = this;
legendEntriesSel.each(function(key) {
var highlighted = key == that.currentHighlightedLegendKey;
var color = getColorOfKey(key, highlighted);
this.style.fill = color;
if (highlighted)
this.style.fontWeight = 'bold';
else
this.style.fontWeight = '';
});
}
};
return {
getColorOfKey: getColorOfKey,
ChartBase: ChartBase
};
});
</script>