aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Robin Nabel <rnabel@google.com>2016-08-30 06:03:45 -0800
committerGravatar TensorFlower Gardener <gardener@tensorflow.org>2016-08-30 07:17:41 -0700
commit7d60da46d62f3aa691a37039b2a532f8729cba39 (patch)
tree16086705c90c42ba420972ec5254fe8590dac3c6
parent090b230efb6662c073c3ae31c20699f95e2f83ed (diff)
Move vz-data-summary into TensorBoard.
Change: 131705660
-rw-r--r--tensorflow/BUILD1
-rw-r--r--tensorflow/tensorboard/components/vz-data-summary/BUILD34
-rw-r--r--tensorflow/tensorboard/components/vz-data-summary/demo.html164
-rw-r--r--tensorflow/tensorboard/components/vz-data-summary/demo.ts39
-rw-r--r--tensorflow/tensorboard/components/vz-data-summary/index.html34
-rw-r--r--tensorflow/tensorboard/components/vz-data-summary/typings.d.ts25
-rw-r--r--tensorflow/tensorboard/components/vz-data-summary/vz-data-summary.html46
-rw-r--r--tensorflow/tensorboard/components/vz-data-summary/vz-data-summary.ts464
8 files changed, 807 insertions, 0 deletions
diff --git a/tensorflow/BUILD b/tensorflow/BUILD
index 51f694ef3c..0cc99ff54b 100644
--- a/tensorflow/BUILD
+++ b/tensorflow/BUILD
@@ -149,6 +149,7 @@ filegroup(
"//tensorflow/tensorboard/app:all_files",
"//tensorflow/tensorboard/backend:all_files",
"//tensorflow/tensorboard/components:all_files",
+ "//tensorflow/tensorboard/components/vz-data-summary:all_files",
"//tensorflow/tensorboard/lib:all_files",
"//tensorflow/tensorboard/lib/python:all_files",
"//tensorflow/tensorboard/scripts:all_files",
diff --git a/tensorflow/tensorboard/components/vz-data-summary/BUILD b/tensorflow/tensorboard/components/vz-data-summary/BUILD
new file mode 100644
index 0000000000..9743d70d94
--- /dev/null
+++ b/tensorflow/tensorboard/components/vz-data-summary/BUILD
@@ -0,0 +1,34 @@
+# Copyright 2016 The TensorFlow Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the 'License');
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an 'AS IS' BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# =============================================================================
+
+# Description:
+# Package for the data-summary vz-element.
+package(default_visibility = ["//tensorflow:internal"])
+
+licenses(["notice"]) # Apache 2.0
+
+exports_files(["LICENSE"])
+
+filegroup(
+ name = "all_files",
+ srcs = glob(
+ ["**/*"],
+ exclude = [
+ "**/METADATA",
+ "**/OWNERS",
+ ],
+ ),
+ visibility = ["//tensorflow:__subpackages__"],
+)
diff --git a/tensorflow/tensorboard/components/vz-data-summary/demo.html b/tensorflow/tensorboard/components/vz-data-summary/demo.html
new file mode 100644
index 0000000000..ba475bf57c
--- /dev/null
+++ b/tensorflow/tensorboard/components/vz-data-summary/demo.html
@@ -0,0 +1,164 @@
+<!DOCTYPE html>
+<!--
+@license
+Copyright 2016 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>Data Summary examples</title>
+ <link rel="import" href="vz-data-summary.html">
+ <link rel="import" href="../iron-demo-helpers/demo-snippet.html">
+</head>
+<body>
+<h1>TF Graph example sizes.</h1>
+<div style="display:flex">60 x 10&nbsp;
+ <div style="width: 60px">
+ <vz-data-summary data="[2, 2, 2, 2, 2, 2]"
+ height-width-ratio="0.16">
+ </vz-data-summary>
+ </div>
+</div>
+<div style="display:flex">60 x 8&nbsp;&nbsp;&nbsp;
+ <div style="width: 60px">
+ <vz-data-summary data="[1, 2, 3, 6, 1, 1]"
+ height-width-ratio="0.12">
+ </vz-data-summary>
+ </div>
+</div>
+<h1>Initialized with data.</h1>
+<div style="width: 30%">
+ <demo-snippet>
+ <template>
+ <vz-data-summary id="first_test" data="[2, 2, 2, 2, 2, 2]">
+ </vz-data-summary>
+ </template>
+ </demo-snippet>
+</div>
+<h1>Initializes with data and labels.</h1>
+<div style="width: 30%">
+ <demo-snippet>
+ <template>
+ <vz-data-summary id="second_test"
+ data="[2, 2, 3, 4, 3, 6]"
+ labels="[0, 1, 2, 3, 4, 5]">
+ </vz-data-summary>
+ </template>
+ </demo-snippet>
+</div>
+<h1>Initializes with data and labels, overflowing labels.</h1>
+<div style="width: 30%">
+ <demo-snippet>
+ <template>
+ <vz-data-summary id="third_test"
+ data="[2, 2, 2, 2, 2, 2]"
+ labels='["fits", "fits", "fits", "OVERFLOW", "fits", "OVERFLOW"]'>
+ </vz-data-summary>
+ </template>
+ </demo-snippet>
+</div>
+<h1>Small demo, setting height/width=0.2, and
+ height/width=0.1.</h1>
+<div style="width: 80px">
+ <demo-snippet>
+ <template>
+ <vz-data-summary id="fourth_test"
+ data="[2, 2, 2, 2, 2, 2]"
+ height-width-ratio="0.2">
+ </vz-data-summary>
+ </template>
+ </demo-snippet>
+ <demo-snippet>
+ <template>
+ <vz-data-summary data="[2, 2, 2, 2, 2, 2]"
+ height-width-ratio="0.1">
+ </vz-data-summary>
+ </template>
+ </demo-snippet>
+</div>
+<h1>Different sizes, illustrate font hidden if too small.</h1>
+<div style="display: flex">
+ <div style="width: 400px">
+ <demo-snippet>
+ <template>
+ <vz-data-summary data="[2, 2, 2, 2, 2, 2]"
+ height-width-ratio="0.2">
+ </vz-data-summary>
+ </template>
+ </demo-snippet>
+ </div>
+ <div style="width: 200px">
+ <demo-snippet>
+ <template>
+ <vz-data-summary data="[2, 2, 2, 2, 2, 2]"
+ height-width-ratio="0.2">
+ </vz-data-summary>
+ </template>
+ </demo-snippet>
+ </div>
+</div>
+<h1>Initialize with data and colors.</h1>
+<div style="width: 30%">
+ <demo-snippet>
+ <template>
+ <vz-data-summary data="[2, 4, 5, 6, 3, 1]"
+ colors='["#22b0dc", "azure", "#808080 ", "#ddd", "#ffdb00", "#c00"]'>
+ </vz-data-summary>
+ </template>
+ </demo-snippet>
+</div>
+<h1>Length != 6</h1>
+<div style="width: 30%">
+ <demo-snippet>
+ <template>
+ <vz-data-summary data="[12, 3, 55]"
+ colors='["#22b0dc", "azure", "#808080"]'
+ labels='["0", "1", "2"]'>
+ </vz-data-summary>
+ </template>
+ </demo-snippet>
+</div>
+<h1>Initialized with large text.</h1>
+<div style="width: 30%">
+ <demo-snippet>
+ <template>
+ <vz-data-summary data="[5, 1, 1, 1, 1, 1]"
+ height-to-font-ratio="0.8">
+ </vz-data-summary>
+ </template>
+ </demo-snippet>
+</div>
+<h1>Illustrate min-slice-width setting.</h1>
+<h3>Not yet implemented.</h3>
+<div style="width: 30%">
+ <demo-snippet>
+ <template>
+ <vz-data-summary data="[200, 200, 200, 200, 200, 1]"
+ labels="[0, 1, 2, 3, 4, 5]">
+ </vz-data-summary>
+ </template>
+ </demo-snippet>
+</div>
+<div style="width: 50%">
+ <h1>Dynamically adding chart to existing SVG.</h1>
+ <demo-snippet>
+ <template>
+ <svg id="chartGroupExample"></svg>
+ </template>
+ </demo-snippet>
+</div>
+<script src="vz-data-summary.js"></script>
+</body>
+</html>
diff --git a/tensorflow/tensorboard/components/vz-data-summary/demo.ts b/tensorflow/tensorboard/components/vz-data-summary/demo.ts
new file mode 100644
index 0000000000..11a2d2636b
--- /dev/null
+++ b/tensorflow/tensorboard/components/vz-data-summary/demo.ts
@@ -0,0 +1,39 @@
+/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
+
+ Licensed under the Apache License, Version 2.0 (the 'License');
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an 'AS IS' BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ =============================================================================*/
+import {attachChartGroup} from './vz-data-summary';
+/**
+ * Function which tries to attach a <g> element to the demo SVG, and waits
+ * until the SVG element is created.
+ */
+function runDemoCode() {
+ let element = d3.select('#chartGroupExample').node() as HTMLElement;
+ if (element !== null) {
+ let data = [200, 200, 200, 200, 200, 100];
+
+ attachChartGroup(data, 300, element);
+ } else {
+ scheduleFunc(runDemoCode); // Make code tail-recursive.
+ }
+}
+
+/**
+ * Function which is used to run a function in the future.
+ * @param callback - The function to be called after the timeout.
+ */
+function scheduleFunc(callback) {
+ setTimeout(callback, 200);
+}
+
+runDemoCode();
diff --git a/tensorflow/tensorboard/components/vz-data-summary/index.html b/tensorflow/tensorboard/components/vz-data-summary/index.html
new file mode 100644
index 0000000000..bc714021e3
--- /dev/null
+++ b/tensorflow/tensorboard/components/vz-data-summary/index.html
@@ -0,0 +1,34 @@
+<!doctype html>
+<!--
+@license
+Copyright 2016 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<html>
+ <head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <style>
+ body {
+ margin: 0;
+ }
+ </style>
+ <link rel="import" href="../iron-component-page/iron-component-page.html">
+ </head>
+ <body>
+
+ <iron-component-page></iron-component-page>
+
+ </body>
+</html> \ No newline at end of file
diff --git a/tensorflow/tensorboard/components/vz-data-summary/typings.d.ts b/tensorflow/tensorboard/components/vz-data-summary/typings.d.ts
new file mode 100644
index 0000000000..84ec11a309
--- /dev/null
+++ b/tensorflow/tensorboard/components/vz-data-summary/typings.d.ts
@@ -0,0 +1,25 @@
+/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
+
+ Licensed under the Apache License, Version 2.0 (the 'License');
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an 'AS IS' BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ =============================================================================*/
+/**
+ * @fileoverview Typings that are missing or otherwise unavailable. Ideally
+ * these should be committed upstream and wind their way back through the
+ * third_party import process.
+ */
+
+declare module polymer {
+ interface PolymerStatic {
+ IronResizableBehavior: any;
+ }
+}
diff --git a/tensorflow/tensorboard/components/vz-data-summary/vz-data-summary.html b/tensorflow/tensorboard/components/vz-data-summary/vz-data-summary.html
new file mode 100644
index 0000000000..317c7a49f1
--- /dev/null
+++ b/tensorflow/tensorboard/components/vz-data-summary/vz-data-summary.html
@@ -0,0 +1,46 @@
+<!--
+@license
+Copyright 2016 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<link rel='import' href='../polymer/polymer.html'>
+<link rel='import'
+ href='../iron-resizable-behavior/iron-resizable-behavior.html'>
+<script src='../d3-library/d3.js'></script>
+
+<!--
+`vz-data-summary` A simple row chart which is designed to provide a quick
+overview of the values within a tensor. This element takes a n element data
+array which is interpreted as containing the number of elements for each
+passed label. The default is a 6 element data array representing number of
+items in a dataset which are negative infinity, negative, zero, positive,
+positive infinity and NaN.
+
+@element vz-data-summary
+@demo demo/index.html
+-->
+
+<dom-module id='vz-data-summary'>
+ <template>
+ <style>
+ #summary {
+ width: 100%;
+ shape-rendering: crispEdges;
+ }
+ </style>
+ <div>
+ <svg id='summary'></svg>
+ </div>
+ </template>
+</dom-module>
diff --git a/tensorflow/tensorboard/components/vz-data-summary/vz-data-summary.ts b/tensorflow/tensorboard/components/vz-data-summary/vz-data-summary.ts
new file mode 100644
index 0000000000..7a8c68ee4e
--- /dev/null
+++ b/tensorflow/tensorboard/components/vz-data-summary/vz-data-summary.ts
@@ -0,0 +1,464 @@
+/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
+
+ Licensed under the Apache License, Version 2.0 (the 'License');
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an 'AS IS' BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ =============================================================================*/
+const svgNS = 'http://www.w3.org/2000/svg';
+
+// The values below are the global defaults for labels, colors, etc. They
+// have to be defined here, in order to be referenced as fallback values for
+// the parameters of createChartGroup() and as initial values for the internal
+// fields in the Polymer element.
+let labels = ['-Inf', '-', '0', '+', '+Inf', 'NaN'];
+let colors = ['#22b0dc', '#1c1c1c', '#808080 ', '#ddd', '#ffdb00', '#c00'];
+let heightWidthRatio = 0.2;
+let heightToFontRatio = 1 / 6;
+
+export function attachChartGroup(
+ data: Array<number>, width: number, parentElement: Element,
+ _labels = labels, _colors = colors, _heightWidthRatio = heightWidthRatio,
+ _heightToFontRatio = heightToFontRatio) {
+ // Calculate the total of all entries in the data array.
+ let dataSum = data.reduce(function(prevResult, currValue) {
+ return prevResult + currValue;
+ });
+ let height = width * _heightWidthRatio;
+
+ // Render data.
+ let outerGroup = document.createElementNS(svgNS, 'g');
+ Polymer.dom(parentElement).appendChild(outerGroup);
+
+ let currentOffset = 0 as number;
+ let currentDataLength = data.length as number;
+
+ for (let slice = 0; slice < currentDataLength; slice++) {
+ let sliceGroup = document.createElementNS(svgNS, 'g');
+ Polymer.dom(outerGroup).appendChild(sliceGroup);
+ let percentage = data[slice] / dataSum;
+ let sliceWidth = percentage * width;
+
+ // Create rectangle.
+ let rect = createRect(currentOffset, sliceWidth, height, _colors[slice]);
+
+ // Add to new rectangle to SVG.
+ Polymer.dom(sliceGroup).appendChild(rect);
+
+ // Add text to rectangle.
+ let text = createText(
+ currentOffset, sliceWidth, height, _labels[slice], _colors[slice],
+ sliceGroup, _heightToFontRatio);
+
+ // Add tooltip to rectangle and text.
+ let rectTitle = createTitle(_labels[slice], data[slice]);
+ let textTitle = createTitle(_labels[slice], data[slice]);
+ Polymer.dom(rect).appendChild(rectTitle);
+ Polymer.dom(text).appendChild(textTitle);
+
+ currentOffset += sliceWidth;
+ }
+ return outerGroup;
+}
+
+function createText(
+ currentOffset: number, sliceWidth: number, height: number, label: string,
+ color: string, group: Element, heightToFontRatio: number) {
+ // Add text to rectangle.
+ let text = document.createElementNS(svgNS, 'text');
+ Polymer.dom(group).appendChild(text);
+
+ text.innerHTML = label;
+
+ // Set location.
+ text.setAttribute('x', (currentOffset + sliceWidth / 2).toString());
+ text.setAttribute('y', (height / 2).toString());
+ // Set text properties.
+ let textColor = getTextColor(color);
+ text.setAttribute('fill', textColor);
+ // Center text.
+ text.setAttribute('text-anchor', 'middle');
+ text.setAttribute('dominant-baseline', 'middle');
+ // Font size.
+ let fontSize = height * heightToFontRatio;
+ text.setAttribute('font-size', fontSize.toString());
+ let textWidth = text.getBoundingClientRect().width;
+ let textHeight = text.getBoundingClientRect().height;
+ // Hide text if text is wider than the slice.
+ if (textWidth > sliceWidth || textHeight > height || fontSize < 7) {
+ text.innerHTML = '';
+ }
+
+ return text;
+}
+
+function createTitle(label: string, numberOfEntries: number) {
+ let title = document.createElementNS(svgNS, 'title');
+ title.innerHTML = label + ': ' + numberOfEntries.toString();
+
+ return title;
+}
+
+function createRect(
+ currentOffset: number, sliceWidth: number, height: number, color: string) {
+ let rect = document.createElementNS(svgNS, 'rect') as HTMLElement;
+
+ // Set location.
+ rect.setAttribute('x', currentOffset.toString());
+ rect.setAttribute('y', '0');
+ // Set dimensions.
+ rect.setAttribute('width', sliceWidth.toString());
+ rect.setAttribute('height', height.toString());
+ // Set colour.
+ rect.setAttribute('fill', color);
+
+ return rect;
+}
+
+function getTextColor(hexTripletColor: string) {
+ let color = hexTripletColor;
+ if (color.substring(0, 1) !== '#') { // Lookup hex from name.
+ let convertedHex = colorToHex(color);
+ if (convertedHex) {
+ color = convertedHex;
+ } else {
+ // RGB string is currently not handled.
+ console.log(
+ 'WARNING: Could not convert color to hex,' +
+ 'please specify color as name or hex string.');
+ return 'black';
+ }
+ }
+
+ color = color.substring(1); // Remove #.
+ if (color.length === 3) { // If short hex format is used.
+ color = color.split('').reduce(
+ // Double every character.
+ function(initial: string, current: string) {
+ return initial + current + current;
+ },
+ '' // Initial value.
+ );
+ }
+
+ let colorInt = parseInt(color, 16); // Convert to integer.
+ let r = (colorInt & 0xFF0000) >> 16; // Extract each color component.
+ let g = (colorInt & 0x00FF00) >> 8;
+ let b = colorInt & 0x0000FF;
+ // Calculate human perceptible luminance.
+ let alpha = 1 - (0.299 * r + 0.587 * g + 0.114 * b) / 255;
+
+ if (alpha < 0.5) {
+ return 'black';
+ } else {
+ return 'white';
+ }
+}
+
+/**
+ * Renders a pixel using the provided color string to an unattached canvas,
+ * then reads and returns the rgb image data of the pixel.
+ *
+ * @param color The color string to be converted.
+ *
+ * @example
+ * // Returns [0, 0, 255, 255]
+ * colorToRGBA('blue')
+ *
+ * @returns Returns the rgba colors as an array, i.e.
+ * [r, g, b, a]
+ */
+function colorToRgba(color: string) {
+ let canvas = document.createElement('canvas');
+ canvas.height = 1;
+ canvas.width = 1;
+
+ let ctx = canvas.getContext('2d');
+ ctx.fillStyle = color;
+ ctx.fillRect(0, 0, 1, 1);
+ let retArray = [] as number[];
+ let imageData = ctx.getImageData(0, 0, 1, 1).data;
+ imageData.forEach(function(d: number) { // Copy data into standard Array.
+ retArray.push(d);
+ });
+ return retArray;
+}
+
+/**
+ * Turns a number (0-255) into a 2-character hex number (00-ff).
+ * @param num
+ *
+ * @returns The converted string.
+ */
+function numToHex(num: number) {
+ return ('0' + num.toString(16)).slice(-2);
+}
+
+/**
+ * Converts any color string to its hex representation.
+ * @param color The color string to be converted.
+ *
+ * @example
+ * // Returns '#0000ff'
+ * colorToHex('blue')
+ *
+ * @returns The hex color string.
+ */
+function colorToHex(color: string) {
+ let rgba = colorToRgba(color) as Array<number|string>;
+ return '#' +
+ rgba.slice(0, 3) // Remove alpha channel.
+ .map(function(value: number) { return numToHex(value); })
+ .join('');
+}
+Polymer({
+ is: 'vz-data-summary',
+ properties: {
+ colors: {
+ type: Array,
+ // Has to match number of elements in the data array.
+ observer: '_onColorsChange'
+ },
+ data: {type: Array, value: [], observer: '_onDataChange'},
+ heightWidthRatio:
+ {type: Number, value: heightWidthRatio, observer: '_onRatioChange'},
+ heightToFontRatio: {
+ type: Number,
+ value: heightToFontRatio,
+ observer: '_onFontRatioChange'
+ },
+ labels: {type: Array, observer: '_onLabelChange'},
+ _colors: {
+ type: Array,
+ value: colors,
+ },
+ _data: Array,
+ _labels: {type: Array, value: labels},
+ _dataSum: {type: Number},
+ _drawRequested: {type: Boolean, value: false},
+ _height: {type: String},
+ _isReady: {type: Boolean, value: false},
+ _width: {type: Number}
+ },
+ behaviors: [Polymer.IronResizableBehavior],
+ listeners: {'iron-resize': '_onWidthChange'},
+ ready: function() {
+ this._isReady = true;
+ this._updateDimensions();
+ // Trigger rendering if draw was requested before element was ready.
+ if (this._drawRequested) {
+ this._renderData();
+ }
+ },
+ attached: function() {
+ if (this._isReady) {
+ this._updateInternalVariables();
+ this._renderData();
+ }
+ },
+ /**
+ * Observer for this.colors.
+ * @private
+ */
+ _onColorsChange: function() {
+ // Verify passed array is valid.
+ if (this._isColorsValid()) {
+ // Copy over the array to the internal field if it has the correct
+ // length.
+ this._updateInternalVariables();
+ this._renderData();
+ }
+ },
+ /**
+ * Data change handler, if new data is valid, sets flag to indicate data
+ * now valid, updates the data extent and renders the new data.
+ */
+ _onDataChange: function() {
+ // Validate new data.
+ if (!this._isDataValid(this.data)) {
+ return;
+ }
+
+ this._updateInternalVariables(); // Update the internal variables.
+
+ // Calculate the sum.
+ this._dataSum =
+ this.data.reduce(function(prevResult: number, currValue: number) {
+ return prevResult + currValue;
+ });
+
+ this._renderData();
+ },
+ /**
+ * Observer for this.labels. Validates that this.labels is an array of
+ * 6 elements before copying its contents to an internal array.
+ */
+ _onLabelChange: function() {
+ if (this._isLabelsValid()) {
+ this._updateInternalVariables();
+ this._renderData();
+ }
+ },
+ _onFontRatioChange: function() {
+ if ((typeof this.heightToFontRatio) === 'number') {
+ this._renderData();
+ }
+ },
+ /**
+ * Observer for this.heightWidthRatio.
+ */
+ _onRatioChange: function() {
+ if (this.heightWidthRatio) {
+ this._renderData();
+ }
+ },
+ /**
+ * Observer for width change. Depends on iron-resizable-behavior.
+ */
+ _onWidthChange: function() {
+ this._width = this.$.summary.parentNode.width;
+ this._renderData();
+ },
+ _internalFieldsValid: function() {
+ return !!(this._data && this._labels && this._colors);
+ },
+ /**
+ * Renders data, if element is ready, the dimensions are set, and valid
+ * data exists.
+ * @private
+ */
+ _renderData: function() {
+ if (!this._isReady || !this._internalFieldsValid()) {
+ this._drawRequested = true;
+ return;
+ }
+
+ // Ensure dimensions are up-to-date and valid before starting rendering.
+ this._updateDimensions();
+ if (!this._width || !this._height) {
+ return;
+ }
+
+ // Find element to append heat map to and determine dimensions.
+ let svgSelection = this.$.summary as SVGElement;
+
+ // Clear the SVG.
+ this.resetSVG();
+
+ // Render data.
+ attachChartGroup(
+ this._data, this._width, svgSelection, this._labels, this._colors,
+ this.heightWidthRatio, this.heightToFontRatio);
+ },
+ /**
+ * Verifies whether input data is valid.
+ * @param [internal] {bool} Whether to check the internal or external
+ * field.
+ * @private
+ */
+ _isDataValid: function(internal: boolean = false) {
+ return this._validateArrayByName('data', internal, isNumeric);
+ },
+ _isColorsValid: function(internal: boolean = false) {
+ return this._validateArrayByName('colors', internal, isString);
+ },
+ _isLabelsValid: function(internal: boolean = false) {
+ return this._validateArrayByName('labels', internal, isString);
+ },
+ _validateArrayByName: function(
+ name: string, internal: boolean, typeChecker: CheckingFunctionType) {
+ if (internal) {
+ name = '_' + name;
+ }
+ return _isValidArray(this[name], typeChecker);
+ },
+ _updateInternalVariables: function() {
+ // If all new data is valid.
+ let isDataValid = this._isDataValid();
+ let isColorsValid = this._isColorsValid();
+ let isLabelsValid = this._isLabelsValid();
+
+ if (isDataValid && isColorsValid && isLabelsValid) {
+ // Ensure they all have the same length.
+ let length = this.data.length;
+ if (length === this.labels.length && length === this.colors.length) {
+ this._data = this.data.slice();
+ this._colors = this.colors.slice();
+ this._labels = this.labels.slice();
+ }
+ } else { // Check whether any new fields have the correct length.
+ let internalDataValid = this._isDataValid(true);
+ let internalColorsValid = this._isColorsValid(true);
+ let internalLabelsValid = this._isLabelsValid(true);
+ length = (internalDataValid && this._data.length) ||
+ (internalColorsValid && this._colors.length) ||
+ (internalLabelsValid && this._labels.length);
+
+ this._updateIfSameLength(length, 'data');
+ this._updateIfSameLength(length, 'colors');
+ this._updateIfSameLength(length, 'labels');
+ }
+ },
+ /**
+ * Updates dimensions based on the width of the parent element.
+ * @private
+ */
+ _updateDimensions: function() {
+ let svgSelection = this.$.summary as SVGElement;
+
+ // Recalculate height and width, and ensure data can be rendered.
+ this._width = svgSelection.parentElement.clientWidth;
+ this._height = this._width * this.heightWidthRatio;
+ // Update the attribute height and width.
+ svgSelection.setAttribute('height', this._height);
+ svgSelection.setAttribute('width', this._width);
+ },
+ /**
+ * Called when either colors, data, or labels are called. Copies calues
+ * into internal fields iff the length of all three arrays is equal.
+ */
+ _updateIfSameLength: function(length: number, name: string) {
+ if (this[name] && this[name].length === length) {
+ this['_' + name] = this[name].slice();
+ }
+ },
+ /**
+ * Resets the SVG. Used by _renderData() but is exposed to provide the
+ * user more control of the SVG's contents.
+ */
+ resetSVG: function() {
+ // Reset the SVG.
+ (this.$.summary as SVGElement).innerHTML = '';
+ }
+});
+
+/**
+ * Helper method for _isDataValid, _isColorsValid, _isLabelsValid. Check
+ * whether the passed data is a) an array, b) the provided function
+ * returns true for every element of the array.
+ */
+interface CheckingFunctionType {
+ (value: any, index: number, array: any[]): boolean;
+}
+
+function _isValidArray(
+ newData: Object[], typeCheckingFunction: CheckingFunctionType) {
+ return Array.isArray(newData) &&
+ // Returns true is every element in the array is numeric.
+ newData.every(typeCheckingFunction);
+}
+
+function isNumeric(n: any) {
+ return !isNaN(parseFloat(n)) && isFinite(n);
+}
+
+function isString(s: any) {
+ return (typeof s) === 'string';
+}