diff options
Diffstat (limited to 'tensorflow/tensorboard/components/vz_heatmap_d3v4/vz-heatmap.html')
-rw-r--r-- | tensorflow/tensorboard/components/vz_heatmap_d3v4/vz-heatmap.html | 360 |
1 files changed, 360 insertions, 0 deletions
diff --git a/tensorflow/tensorboard/components/vz_heatmap_d3v4/vz-heatmap.html b/tensorflow/tensorboard/components/vz_heatmap_d3v4/vz-heatmap.html new file mode 100644 index 0000000000..8a3503468f --- /dev/null +++ b/tensorflow/tensorboard/components/vz_heatmap_d3v4/vz-heatmap.html @@ -0,0 +1,360 @@ +<!-- +@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"> +<link rel="import" href="../tf-imports/d3.html"> + +<!-- +`vz-heatmap` A simple heatmap to visualize 2D data using predefined or user +defined colour scheme, with a dependency on d3.js. The heatmap automatically +fits itself to the width of the container it is placed in. + +@element vz-heatmap +@demo demo/index.html +--> +<dom-module id="vz-heatmap"> + <template> + <div> + <canvas id="heatmap" + style="width: 100%; image-rendering:pixelated"></canvas> + </div> + </template> + <script> + Polymer({ + is: 'vz-heatmap', + properties: { + /** + * An 2-element array which defines the lower and upper bound of the + * automatically created color scale for the heatmap. + */ + colors: { + type: Array, + value: ['white', 'steelblue'], + observer: '_onColorsChange' + }, + /** + * A function which returns a hex-formatted color string + * given a number type. + * i.e. (input: number) => string + * + * Passing a colorFunction deactivates the color + * function generated by the `colors` and `values` fields. + */ + colorFunction: { + type: Object, + observer: '_onColorFunctionChange' + }, + /** + * A 2D data array containing the data to be visualized. + */ + data: { + type: Array, + value: [], + observer: '_onDataChange' + }, + /** + * A 2-element array containing the domain of values which + * maps to the color range defined by the `colors` array. + */ + values: { + type: Array, + observer: '_onValuesChange' + }, + _currentDataValid: { + type: Boolean, + value: false + }, + _dataExtent: { + type: Array, + value: [0, 1] + }, + _debug: { + type: Boolean, + value: false + }, + _generatedColorScale: { + type: Object, + value: (function () { + var retFunction = d3.scaleLinear(); + if (this.colors) { + retFunction.range(this.colors) + } + + return retFunction; + })() + }, + _isReady: { + type: Boolean, + value: false + }, + _height: { + type: Number, + value: 0 + }, + _width: { + type: Number, + value: 0 + }, + _useUserColorPickerFunction: { + type: Boolean, + value: false + } + }, + behaviors: [ + Polymer.IronResizableBehavior + ], + listeners: { + 'iron-resize': '_onWidthChange' + }, + _onWidthChange: function () { + // Re-render the chart if the data has already been set. + if (this._currentDataValid) { + this._renderData(); + } + }, + ready: function () { + this._isReady = true; + this._onWidthChange(); + }, + attached: function () { + this._onWidthChange(); + }, + /** + * 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; + } + + // Set flag to indicate data is now valid. + this._currentDataValid = true; + + // Calculate new data extent. + this._updateDataExtent(); + + if (this._isReady) { + this._renderData(); + } + }, + /** + * (Re-)Renders the heatmap. Called when data, color mapping, or width + * changes. + * @private + */ + _renderData: function () { + // Ensure dimensions are up-to-date and valid before starting rendering. + this._updateDimensions(); + if (!this._width || !this._height) return; + + // Ensure the color function is up-to-date. + this._updateColorFunction(); + var width = this._width; + var rows = this.data.length; + var columns = this.data[0].length; + + // Calculate side length of each tile. + var sideLength = width / columns; + + // Set domain and range of the axis. + var colorScale = (this._useUserColorPickerFunction) ? + this.colorFunction : this._generatedColorScale; + + // Clear the canvas. + this.resetCanvas(); + + // Render heatmap. + var ctx = this.$.heatmap.getContext("2d"); + + for (var row = 0; row < rows; row++) { + for (var column = 0; column < columns; column++) { + var value = this.data[row][column]; + // Set location and dimensions. + ctx.fillStyle = colorScale(value); + // Preferred way to set color of individual pixels. + ctx.fillRect(column, row, 1, 1); + } + } + }, + /** + * Observer for this.colors. + * @private + */ + _onColorsChange: function () { + if (Array.isArray(this.colors) && this.colors.length == 2) { + this._updateColorFunction(); + } + this._useUserColorPickerFunction = false; + this._renderData(); + }, + /** + * Observer for this.values. Updates the + * internal color function. If data invalid, internal color scale uses + * the extent of the data as domain. + * @private + */ + _onValuesChange: function () { + // Verify that validity of the new content of values. + // If data not valid, set this.values to undefined, as + // _updateColorFunction will then use the data's extent as the color + // scale's domain. + if (!(Array.isArray(this.values) && this.values.length == 2)) { + this.values = undefined; + } + + this._updateColorFunction(); + this._useUserColorPickerFunction = false; + this._renderData(); + }, + /** + * Observer for this.colorFunction. This field allows the user to + * provide a custom color scale. Updates to this value are ignored, if the new + * value is not a function object. + * @private + */ + _onColorFunctionChange: function () { + if (typeof this.colorFunction === 'function') { + this._useUserColorPickerFunction = true; + this._renderData(); + } else if (this._debug) { + console.log('The colorFunction provided is not of function type, and was not set as default.') + } + }, + /** + * Calculates the extent of the data if it is required by the + * current color function. This function is called when + * properties change which would result in the color scale + * changing, or when the computed color scale is started to be used. + * @private + */ + _updateDataExtent: function () { + if (this._currentDataValid) { + var rows = this.data.length; + var columns = this.data[0].length; + + var min = this.data[0][0]; + var max = this.data[0][0]; + + for (var row = 0; row < rows; row++) { + for (var col = 0; col < columns; col++) { + var currentElement = this.data[row][col]; + + if (currentElement < min) { + min = currentElement; + } else if (currentElement > max) { + max = currentElement; + } + } + } + + this._dataExtent = [min, max]; + } else if (!this._dataExtent) { + this._dataExtent = [0, 1]; + } + }, + /** + * Updates the internal color function only when the number of + * elements in this.colors is equal to the number of elements in + * this.values or if this.values is empty, in which case the + * extent of the data is used. + * @private + */ + _updateColorFunction: function () { + if (Array.isArray(this.colors) && this.colors.length) { + this._generatedColorScale = d3.scaleLinear().range(this.colors); + } + + if (this.values) { + // Check that the number of elements in this.values and + // this.colors have an identical number of elements. + if (this.colors.length === this.values.length) { + this._generatedColorScale.domain(this.values); + } + + // If values reset, and data field contains valid data, set colour scale + // to use the data extent as domain. + } else if (this._currentDataValid) { + this._generatedColorScale.domain(this._dataExtent); + } + }, + _getLinearInterpolation: function (domain, range) { + return d3.scaleLinear().domain(domain).range(range); + }, + /** + * Find side length of each tile in heat map by dividing current width + * by number of columns + */ + _findTileSideLength: function () { + if (this._currentDataValid) { + var numOfColumns = this.data[0].length; + + // Make sure value is finite. + if (numOfColumns === 0) { + var returnValue = 0; + } else { + returnValue = this._width / numOfColumns; + } + return returnValue; + + } else { + if (this._debug == true) { + console.log("WARNING: vz-heatmap is reverting to zero height as passed data is invalid.") + } + return 0; // Fall back to 0 height, if data is invalid. + } + }, + /** + * Verifies whether input data is valid. + * @param newData Number[][] + * @private + */ + _isDataValid: function (newData) { + return Array.isArray(newData) && newData.length && + Array.isArray(newData[0]); + }, + _updateDimensions: function () { + var canvasElement = this.$.heatmap; + + var rows = 0; + var columns = 0; + if (this._currentDataValid) { + rows = this.data.length; + columns = this.data[0].length; + } + + // Recalculate height and width, and ensure data can be rendered. + this._width = canvasElement.parentNode.clientWidth; + this._height = this._findTileSideLength() * rows; + // Update the attribute height and width. + canvasElement.setAttribute('height', rows); + canvasElement.setAttribute('width', columns); + }, + /** + * Function which clears the canvas. + */ + resetCanvas: function () { + // Reset the canvas. + var canvas = this.$.heatmap; + var ctx = canvas.getContext("2d"); + ctx.clearRect(0, 0, canvas.width, canvas.height); + } + }); + </script> +</dom-module> |