diff options
author | 2016-08-30 06:03:45 -0800 | |
---|---|---|
committer | 2016-08-30 07:17:41 -0700 | |
commit | 7d60da46d62f3aa691a37039b2a532f8729cba39 (patch) | |
tree | 16086705c90c42ba420972ec5254fe8590dac3c6 | |
parent | 090b230efb6662c073c3ae31c20699f95e2f83ed (diff) |
Move vz-data-summary into TensorBoard.
Change: 131705660
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 + <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 + <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'; +} |