diff options
author | 2016-07-11 13:18:42 -0800 | |
---|---|---|
committer | 2016-07-11 14:31:25 -0700 | |
commit | 77c19a8bd77a69791ef4e8d81b43116da59a87ae (patch) | |
tree | 33e009309261df32b1804990de95e9f809f80351 | |
parent | b45a6f2f2429760a2302ebde42302d20738abbf4 (diff) |
Split out the obsolete histogram chart from tf-chart, so we can refactor the line chart on its own.
Change: 127127152
7 files changed, 449 insertions, 236 deletions
diff --git a/tensorflow/tensorboard/components/tf-event-dashboard/tf-chart-helpers.ts b/tensorflow/tensorboard/components/tf-event-dashboard/tf-chart-helpers.ts new file mode 100644 index 0000000000..8295fd84b1 --- /dev/null +++ b/tensorflow/tensorboard/components/tf-event-dashboard/tf-chart-helpers.ts @@ -0,0 +1,156 @@ +/* Copyright 2015 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. +==============================================================================*/ +/* tslint:disable:no-namespace variable-name */ +module TF.ChartHelpers { + export type DataFn = (run: string, tag: string) => + Promise<Array<Backend.Datum>>; + + export let Y_TOOLTIP_FORMATTER_PRECISION = 4; + export let STEP_FORMATTER_PRECISION = 4; + export let Y_AXIS_FORMATTER_PRECISION = 3; + export let TOOLTIP_Y_PIXEL_OFFSET = 20; + export let TOOLTIP_CIRCLE_SIZE = 4; + export let NAN_SYMBOL_SIZE = 6; + + export interface Point { + x: number; // pixel space + y: number; // pixel space + datum: TF.Backend.ScalarDatum; + dataset: Plottable.Dataset; + } + + /* Create a formatter function that will switch between exponential and + * regular display depending on the scale of the number being formatted, + * and show `digits` significant digits. + */ + export function multiscaleFormatter(digits: number): ((v: number) => string) { + return (v: number) => { + let absv = Math.abs(v); + if (absv < 1E-15) { + // Sometimes zero-like values get an annoying representation + absv = 0; + } + let f: (x: number) => string; + if (absv >= 1E4) { + f = d3.format('.' + digits + 'e'); + } else if (absv > 0 && absv < 0.01) { + f = d3.format('.' + digits + 'e'); + } else { + f = d3.format('.' + digits + 'g'); + } + return f(v); + }; + } + + export function accessorize(key: string): Plottable.Accessor<number> { + return (d: any, index: number, dataset: Plottable.Dataset) => d[key]; + } + + export interface XComponents { + /* tslint:disable */ + scale: Plottable.Scales.Linear|Plottable.Scales.Time, + axis: Plottable.Axes.Numeric|Plottable.Axes.Time, + accessor: Plottable.Accessor<number|Date>, + /* tslint:enable */ + } + + export let stepFormatter = + Plottable.Formatters.siSuffix(STEP_FORMATTER_PRECISION); + export function stepX(): XComponents { + let scale = new Plottable.Scales.Linear(); + let axis = new Plottable.Axes.Numeric(scale, 'bottom'); + axis.formatter(stepFormatter); + return { + scale: scale, + axis: axis, + accessor: (d: Backend.Datum) => d.step, + }; + } + + export let timeFormatter = Plottable.Formatters.time('%a %b %e, %H:%M:%S'); + + export function wallX(): XComponents { + let scale = new Plottable.Scales.Time(); + return { + scale: scale, + axis: new Plottable.Axes.Time(scale, 'bottom'), + accessor: (d: Backend.Datum) => d.wall_time, + }; + } + export let relativeAccessor = + (d: any, index: number, dataset: Plottable.Dataset) => { + // We may be rendering the final-point datum for scatterplot. + // If so, we will have already provided the 'relative' property + if (d.relative != null) { + return d.relative; + } + let data = dataset.data(); + // I can't imagine how this function would be called when the data is + // empty (after all, it iterates over the data), but lets guard just + // to be safe. + let first = data.length > 0 ? +data[0].wall_time : 0; + return (+d.wall_time - first) / (60 * 60 * 1000); // ms to hours + }; + + export let relativeFormatter = (n: number) => { + // we will always show 2 units of precision, e.g days and hours, or + // minutes and seconds, but not hours and minutes and seconds + let ret = ''; + let days = Math.floor(n / 24); + n -= (days * 24); + if (days) { + ret += days + 'd '; + } + let hours = Math.floor(n); + n -= hours; + n *= 60; + if (hours || days) { + ret += hours + 'h '; + } + let minutes = Math.floor(n); + n -= minutes; + n *= 60; + if (minutes || hours || days) { + ret += minutes + 'm '; + } + let seconds = Math.floor(n); + return ret + seconds + 's'; + }; + export function relativeX(): XComponents { + let scale = new Plottable.Scales.Linear(); + return { + scale: scale, + axis: new Plottable.Axes.Numeric(scale, 'bottom'), + accessor: relativeAccessor, + }; + } + + // a very literal definition of NaN: true for NaN for a non-number type + // or null, etc. False for Infinity or -Infinity + export let isNaN = (x) => +x !== x; + + export function getXComponents(xType: string): XComponents { + switch (xType) { + case 'step': + return stepX(); + case 'wall_time': + return wallX(); + case 'relative': + return relativeX(); + default: + throw new Error('invalid xType: ' + xType); + } + } +} diff --git a/tensorflow/tensorboard/components/tf-event-dashboard/tf-chart.html b/tensorflow/tensorboard/components/tf-event-dashboard/tf-chart.html index ef274aa276..49f9d89544 100644 --- a/tensorflow/tensorboard/components/tf-event-dashboard/tf-chart.html +++ b/tensorflow/tensorboard/components/tf-event-dashboard/tf-chart.html @@ -99,6 +99,7 @@ The wall_time is serialized as seconds since epoch. </template> <script src="dragZoomInteraction.js"></script> <script src="tf-chart.js"></script> + <script src="tf-chart-helpers.js"></script> <script> Polymer({ is: "tf-chart", @@ -113,7 +114,7 @@ The wall_time is serialized as seconds since epoch. _initialized: Boolean, }, observers: [ - "_makeChart(type, tag, dataProvider, xType, colorScale, _initialized)", + "_makeChart(tag, dataProvider, xType, colorScale, _initialized)", "_changeRuns(_chart, selectedRuns.*)" ], _changeRuns: function(chart) { @@ -129,24 +130,14 @@ The wall_time is serialized as seconds since epoch. reload: function() { this._chart.reload(); }, - _constructor: function(type) { - if (type === "scalar") { - return TF.LineChart; - } else if (type === "compressedHistogram") { - return TF.HistogramChart; - } else { - throw new Error("Unrecognized chart type"); - } - }, - _makeChart: function(type, tag, dataProvider, xType, colorScale, _initialized) { + _makeChart: function(tag, dataProvider, xType, colorScale, _initialized) { if (!_initialized) { return; } if (this._chart) this._chart.destroy(); - var cns = this._constructor(type); var tooltip = d3.select(this.$.tooltip); this.scopeSubtree(this.$.tooltip, true); - var chart = new cns(tag, dataProvider, xType, colorScale, tooltip); + var chart = new TF.LineChart(tag, dataProvider, xType, colorScale, tooltip); var svg = d3.select(this.$.chartsvg); this.async(function() { chart.renderTo(svg); diff --git a/tensorflow/tensorboard/components/tf-event-dashboard/tf-chart.ts b/tensorflow/tensorboard/components/tf-event-dashboard/tf-chart.ts index c05e184dfe..91c2b7b23f 100644 --- a/tensorflow/tensorboard/components/tf-event-dashboard/tf-chart.ts +++ b/tensorflow/tensorboard/components/tf-event-dashboard/tf-chart.ts @@ -12,26 +12,11 @@ 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. ==============================================================================*/ -module TF { - export type DataFn = (run: string, tag: string) => - Promise<Array<Backend.Datum>>; - - let Y_TOOLTIP_FORMATTER_PRECISION = 4; - let STEP_FORMATTER_PRECISION = 4; - let Y_AXIS_FORMATTER_PRECISION = 3; - let TOOLTIP_Y_PIXEL_OFFSET = 20; - let TOOLTIP_CIRCLE_SIZE = 4; - let NAN_SYMBOL_SIZE = 6; - - interface Point { - x: number; // pixel space - y: number; // pixel space - datum: TF.Backend.ScalarDatum; - dataset: Plottable.Dataset; - } +/* tslint:disable:no-namespace variable-name */ +module TF { export class BaseChart { - protected dataFn: DataFn; + protected dataFn: TF.ChartHelpers.DataFn; protected tag: string; private run2datasets: {[run: string]: Plottable.Dataset}; protected runs: string[]; @@ -50,7 +35,7 @@ module TF { protected tooltip: d3.Selection<any>; protected dzl: Plottable.DragZoomLayer; constructor( - tag: string, dataFn: DataFn, xType: string, + tag: string, dataFn: TF.ChartHelpers.DataFn, xType: string, colorScale: Plottable.Scales.Color, tooltip: d3.Selection<any>) { this.dataFn = dataFn; this.run2datasets = {}; @@ -94,14 +79,15 @@ module TF { if (this.outer) { this.outer.destroy(); } - let xComponents = getXComponents(xType); + let xComponents = TF.ChartHelpers.getXComponents(xType); this.xAccessor = xComponents.accessor; this.xScale = xComponents.scale; this.xAxis = xComponents.axis; this.xAxis.margin(0).tickLabelPadding(3); this.yScale = new Plottable.Scales.Linear(); this.yAxis = new Plottable.Axes.Numeric(this.yScale, 'left'); - let yFormatter = multiscaleFormatter(Y_AXIS_FORMATTER_PRECISION); + let yFormatter = TF.ChartHelpers.multiscaleFormatter( + TF.ChartHelpers.Y_AXIS_FORMATTER_PRECISION); this.yAxis.margin(0).tickLabelPadding(5).formatter(yFormatter); this.yAxis.usesTextWidthApproximation(true); @@ -148,7 +134,7 @@ module TF { private nanDataset: Plottable.Dataset; constructor( - tag: string, dataFn: DataFn, xType: string, + tag: string, dataFn: TF.ChartHelpers.DataFn, xType: string, colorScale: Plottable.Scales.Color, tooltip: d3.Selection<any>) { super(tag, dataFn, xType, colorScale, tooltip); this.datasets = []; @@ -180,7 +166,7 @@ module TF { scatterPlot.y(this.yAccessor, yScale); scatterPlot.attr('fill', (d: any) => this.colorScale.scale(d.run)); scatterPlot.attr('opacity', 1); - scatterPlot.size(TOOLTIP_CIRCLE_SIZE * 2); + scatterPlot.size(TF.ChartHelpers.TOOLTIP_CIRCLE_SIZE * 2); scatterPlot.datasets([this.lastPointsDataset]); this.scatterPlot = scatterPlot; @@ -189,7 +175,7 @@ module TF { nanDisplay.y((x) => x.displayY, yScale); nanDisplay.attr('fill', (d: any) => this.colorScale.scale(d.run)); nanDisplay.attr('opacity', 1); - nanDisplay.size(NAN_SYMBOL_SIZE * 2); + nanDisplay.size(TF.ChartHelpers.NAN_SYMBOL_SIZE * 2); nanDisplay.datasets([this.nanDataset]); nanDisplay.symbol(Plottable.SymbolFactories.triangleUp); this.nanDisplay = nanDisplay; @@ -212,7 +198,8 @@ module TF { let idx = nonNanData.length - 1; datum = nonNanData[idx]; datum.run = d.metadata().run; - datum.relative = relativeAccessor(datum, -1, d); + datum.relative = + TF.ChartHelpers.relativeAccessor(datum, -1, d); } return datum; }) @@ -242,7 +229,7 @@ module TF { } else { data[i].run = d.metadata().run; data[i].displayY = displayY; - data[i].relative = relativeAccessor(data[i], -1, d); + data[i].relative = TF.ChartHelpers.relativeAccessor(data[i], -1, d); nanData.push(data[i]); } } @@ -281,7 +268,7 @@ module TF { if (!enabled) { return; } - let target: Point = { + let target: TF.ChartHelpers.Point = { x: p.x, y: p.y, datum: null, @@ -296,10 +283,11 @@ module TF { (p) => p != null && Plottable.Utils.DOM.intersectsBBox(p.x, p.y, centerBBox)); let pts: any = pointsComponent.content().selectAll('.point').data( - pointsToCircle, (p: Point) => p.dataset.metadata().run); + pointsToCircle, + (p: TF.ChartHelpers.Point) => p.dataset.metadata().run); if (points.length !== 0) { pts.enter().append('circle').classed('point', true); - pts.attr('r', TOOLTIP_CIRCLE_SIZE) + pts.attr('r', TF.ChartHelpers.TOOLTIP_CIRCLE_SIZE) .attr('cx', (p) => p.x) .attr('cy', (p) => p.y) .style('stroke', 'none') @@ -318,12 +306,14 @@ module TF { return group; } - private drawTooltips(points: Point[], target: Point) { + private drawTooltips( + points: TF.ChartHelpers.Point[], target: TF.ChartHelpers.Point) { // Formatters for value, step, and wall_time this.scatterPlot.attr('opacity', 0); - let valueFormatter = multiscaleFormatter(Y_TOOLTIP_FORMATTER_PRECISION); + let valueFormatter = TF.ChartHelpers.multiscaleFormatter( + TF.ChartHelpers.Y_TOOLTIP_FORMATTER_PRECISION); - let dist = (p: Point) => + let dist = (p: TF.ChartHelpers.Point) => Math.pow(p.x - target.x, 2) + Math.pow(p.y - target.y, 2); let closestDist = _.min(points.map(dist)); points = _.sortBy(points, (d) => d.dataset.metadata().run); @@ -368,10 +358,13 @@ module TF { rows.append('td').text( (d) => isNaN(d.datum.scalar) ? 'NaN' : valueFormatter(d.datum.scalar)); - rows.append('td').text((d) => stepFormatter(d.datum.step)); - rows.append('td').text((d) => timeFormatter(d.datum.wall_time)); rows.append('td').text( - (d) => relativeFormatter(relativeAccessor(d.datum, -1, d.dataset))); + (d) => TF.ChartHelpers.stepFormatter(d.datum.step)); + rows.append('td').text( + (d) => TF.ChartHelpers.timeFormatter(d.datum.wall_time)); + rows.append('td').text( + (d) => TF.ChartHelpers.relativeFormatter( + TF.ChartHelpers.relativeAccessor(d.datum, -1, d.dataset))); // compute left position let documentWidth = document.body.clientWidth; @@ -383,18 +376,23 @@ module TF { Math.min(0, documentWidth - parentRect.left - nodeRect.width - 60); this.tooltip.style('left', left + 'px'); // compute top position - if (parentRect.bottom + nodeRect.height + TOOLTIP_Y_PIXEL_OFFSET < + if (parentRect.bottom + nodeRect.height + + TF.ChartHelpers.TOOLTIP_Y_PIXEL_OFFSET < document.body.clientHeight) { - this.tooltip.style('top', parentRect.bottom + TOOLTIP_Y_PIXEL_OFFSET); + this.tooltip.style( + 'top', parentRect.bottom + TF.ChartHelpers.TOOLTIP_Y_PIXEL_OFFSET); } else { - this.tooltip.style('bottom', parentRect.top - TOOLTIP_Y_PIXEL_OFFSET); + this.tooltip.style( + 'bottom', parentRect.top - TF.ChartHelpers.TOOLTIP_Y_PIXEL_OFFSET); } this.tooltip.style('opacity', 1); } - private findClosestPoint(target: Point, dataset: Plottable.Dataset): Point { - let points: Point[] = dataset.data().map((d, i) => { + private findClosestPoint( + target: TF.ChartHelpers.Point, + dataset: Plottable.Dataset): TF.ChartHelpers.Point { + let points: TF.ChartHelpers.Point[] = dataset.data().map((d, i) => { let x = this.xAccessor(d, i, dataset); let y = this.yAccessor(d, i, dataset); return { @@ -404,7 +402,8 @@ module TF { dataset: dataset, }; }); - let idx: number = _.sortedIndex(points, target, (p: Point) => p.x); + let idx: number = + _.sortedIndex(points, target, (p: TF.ChartHelpers.Point) => p.x); if (idx === points.length) { return points[points.length - 1]; } else if (idx === 0) { @@ -427,181 +426,4 @@ module TF { this.linePlot.datasets(this.datasets); } } - - export class HistogramChart extends BaseChart { - private plots: Plottable.XYPlot<number | Date, number>[]; - constructor( - tag: string, dataFn: DataFn, xType: string, - colorScale: Plottable.Scales.Color, tooltip: d3.Selection<any>) { - super(tag, dataFn, xType, colorScale, tooltip); - this.buildChart(xType); - } - - public changeRuns(runs: string[]) { - super.changeRuns(runs); - let datasets = runs.map((r) => this.getDataset(r)); - this.plots.forEach((p) => p.datasets(datasets)); - } - - protected buildPlot(xAccessor, xScale, yScale): Plottable.Component { - let percents = [0, 228, 1587, 3085, 5000, 6915, 8413, 9772, 10000]; - let opacities = _.range(percents.length - 1) - .map((i) => (percents[i + 1] - percents[i]) / 2500); - let accessors = percents.map((p, i) => (datum) => datum[i][1]); - let median = 4; - let medianAccessor = accessors[median]; - - let plots = _.range(accessors.length - 1).map((i) => { - let p = new Plottable.Plots.Area<number|Date>(); - p.x(xAccessor, xScale); - - let y0 = i > median ? accessors[i] : accessors[i + 1]; - let y = i > median ? accessors[i + 1] : accessors[i]; - p.y(y, yScale); - p.y0(y0); - p.attr( - 'fill', (d: any, i: number, dataset: Plottable.Dataset) => - this.colorScale.scale(dataset.metadata().run)); - p.attr( - 'stroke', (d: any, i: number, dataset: Plottable.Dataset) => - this.colorScale.scale(dataset.metadata().run)); - p.attr('stroke-weight', (d: any, i: number, m: any) => '0.5px'); - p.attr('stroke-opacity', () => opacities[i]); - p.attr('fill-opacity', () => opacities[i]); - return p; - }); - - let medianPlot = new Plottable.Plots.Line<number|Date>(); - medianPlot.x(xAccessor, xScale); - medianPlot.y(medianAccessor, yScale); - medianPlot.attr( - 'stroke', - (d: any, i: number, m: any) => this.colorScale.scale(m.run)); - - this.plots = plots; - return new Plottable.Components.Group(plots); - } - } - - /* Create a formatter function that will switch between exponential and - * regular display depending on the scale of the number being formatted, - * and show `digits` significant digits. - */ - function multiscaleFormatter(digits: number): ((v: number) => string) { - return (v: number) => { - let absv = Math.abs(v); - if (absv < 1E-15) { - // Sometimes zero-like values get an annoying representation - absv = 0; - } - let f: (x: number) => string; - if (absv >= 1E4) { - f = d3.format('.' + digits + 'e'); - } else if (absv > 0 && absv < 0.01) { - f = d3.format('.' + digits + 'e'); - } else { - f = d3.format('.' + digits + 'g'); - } - return f(v); - }; - } - - function accessorize(key: string): Plottable.Accessor<number> { - return (d: any, index: number, dataset: Plottable.Dataset) => d[key]; - } - - interface XComponents { - /* tslint:disable */ - scale: Plottable.Scales.Linear | Plottable.Scales.Time, - axis: Plottable.Axes.Numeric | Plottable.Axes.Time, - accessor: Plottable.Accessor<number | Date>, - /* tslint:enable */ - } - - let stepFormatter = Plottable.Formatters.siSuffix(STEP_FORMATTER_PRECISION); - function stepX(): XComponents { - let scale = new Plottable.Scales.Linear(); - let axis = new Plottable.Axes.Numeric(scale, 'bottom'); - axis.formatter(stepFormatter); - return { - scale: scale, - axis: axis, - accessor: (d: Backend.Datum) => d.step, - }; - } - - let timeFormatter = Plottable.Formatters.time('%a %b %e, %H:%M:%S'); - - function wallX(): XComponents { - let scale = new Plottable.Scales.Time(); - return { - scale: scale, - axis: new Plottable.Axes.Time(scale, 'bottom'), - accessor: (d: Backend.Datum) => d.wall_time, - }; - } - let relativeAccessor = - (d: any, index: number, dataset: Plottable.Dataset) => { - // We may be rendering the final-point datum for scatterplot. - // If so, we will have already provided the 'relative' property - if (d.relative != null) { - return d.relative; - } - let data = dataset.data(); - // I can't imagine how this function would be called when the data is - // empty (after all, it iterates over the data), but lets guard just - // to be safe. - let first = data.length > 0 ? +data[0].wall_time : 0; - return (+d.wall_time - first) / (60 * 60 * 1000); // ms to hours - }; - - let relativeFormatter = (n: number) => { - // we will always show 2 units of precision, e.g days and hours, or - // minutes and seconds, but not hours and minutes and seconds - let ret = ''; - let days = Math.floor(n / 24); - n -= (days * 24); - if (days) { - ret += days + 'd '; - } - let hours = Math.floor(n); - n -= hours; - n *= 60; - if (hours || days) { - ret += hours + 'h '; - } - let minutes = Math.floor(n); - n -= minutes; - n *= 60; - if (minutes || hours || days) { - ret += minutes + 'm '; - } - let seconds = Math.floor(n); - return ret + seconds + 's'; - }; - function relativeX(): XComponents { - let scale = new Plottable.Scales.Linear(); - return { - scale: scale, - axis: new Plottable.Axes.Numeric(scale, 'bottom'), - accessor: relativeAccessor, - }; - } - - // a very literal definition of NaN: true for NaN for a non-number type - // or null, etc. False for Infinity or -Infinity - let isNaN = (x) => +x !== x; - - function getXComponents(xType: string): XComponents { - switch (xType) { - case 'step': - return stepX(); - case 'wall_time': - return wallX(); - case 'relative': - return relativeX(); - default: - throw new Error('invalid xType: ' + xType); - } - } } diff --git a/tensorflow/tensorboard/components/tf-event-dashboard/tf-event-dashboard.html b/tensorflow/tensorboard/components/tf-event-dashboard/tf-event-dashboard.html index 1a8fc5990a..bd5d061a47 100644 --- a/tensorflow/tensorboard/components/tf-event-dashboard/tf-event-dashboard.html +++ b/tensorflow/tensorboard/components/tf-event-dashboard/tf-event-dashboard.html @@ -79,7 +79,6 @@ The #center div contains tf-charts embedded inside tf-collapsable-panes. <tf-chart tag="[[tag]]" data-provider="[[dataProvider]]" - type="scalar" id="chart" selected-runs="[[validRuns(tag, selectedRuns.*, run2tag.*)]]" x-type="[[xType]]" diff --git a/tensorflow/tensorboard/components/tf-histogram-dashboard/tf-histogram-dashboard.html b/tensorflow/tensorboard/components/tf-histogram-dashboard/tf-histogram-dashboard.html index 3b6373b52a..371ea80f80 100644 --- a/tensorflow/tensorboard/components/tf-histogram-dashboard/tf-histogram-dashboard.html +++ b/tensorflow/tensorboard/components/tf-histogram-dashboard/tf-histogram-dashboard.html @@ -4,7 +4,7 @@ <link rel="import" href="../tf-color-scale/tf-color-scale.html"> <link rel="import" href="../tf-dashboard-common/tf-dashboard.html"> <link rel="import" href="../tf-categorizer/tf-categorizer.html"> -<link rel="import" href="../tf-event-dashboard/tf-chart.html"> +<link rel="import" href="tf-obsolete-histogram-chart.html"> <link rel="import" href="../tf-collapsable-pane/tf-collapsable-pane.html"> <link rel="import" href="../iron-collapse/iron-collapse.html"> <link rel="import" href="../paper-icon-button/paper-icon-button.html"> @@ -75,9 +75,8 @@ The #center div contains tf-charts embedded inside tf-collapsable-panes. <div class="card"> <span class="card-title">[[tag]]</span> <div class="card-content"> - <tf-chart + <tf-obsolete-histogram-chart tag="[[tag]]" - type="compressedHistogram" id="chart" selected-runs="[[_array(run)]]" x-type="[[xType]]" @@ -85,7 +84,7 @@ The #center div contains tf-charts embedded inside tf-collapsable-panes. color-scale="[[colorScale]]" on-keyup="toggleSelected" tabindex="2" - ></tf-chart> + ></tf-obsolete-histogram-chart> <paper-icon-button class="expand-button" icon="fullscreen" diff --git a/tensorflow/tensorboard/components/tf-histogram-dashboard/tf-obsolete-histogram-chart.html b/tensorflow/tensorboard/components/tf-histogram-dashboard/tf-obsolete-histogram-chart.html new file mode 100644 index 0000000000..a60ad05771 --- /dev/null +++ b/tensorflow/tensorboard/components/tf-histogram-dashboard/tf-obsolete-histogram-chart.html @@ -0,0 +1,93 @@ +<link rel="import" href="../polymer/polymer.html"> +<link rel="import" href="../tf-imports/plottable.html"> +<link rel="import" href="../tf-imports/lodash.html"> + +<!-- +tf-chart (TFChart) creates an element that draws a line chart for displaying event values. +It has the following settable properties: + tag: (required, string) - the name of the tag to load for this chart + selectedRuns: (required, string[]) - the runs the chart should display + xType: (required, string) - the way to display x values - allows "step" or "wall_time" + dataCoordinator: (required, TF.DataCoordinator) - the data coordinator for talking to backend + colorScale: (required, Plottable.Scales.Color) - maps from runs to colors: (required, function) - allows the chart to modify tooltips + +It exposes the following methods: + redraw() - cause the chart to re-render (useful if e.g. container size changed) + +The data is expected to be an array of [wall_time, step, value] arrays. +The wall_time is serialized as seconds since epoch. +--> +<dom-module id="tf-obsolete-histogram-chart"> + <template> + <svg id="chartsvg"></svg> + <style> + :host { + -webkit-user-select: none; + -moz-user-select: none; + display: flex; + flex-direction: column; + flex-grow: 1; + flex-shrink: 1; + position: relative; + } + svg { + -webkit-user-select: none; + -moz-user-select: none; + flex-grow: 1; + flex-shrink: 1; + } + + </style> + </template> + <script src="tf-obsolete-histogram-chart.js"></script> + <script src="../tf-event-dashboard/tf-chart-helpers.js"></script> + <script> + Polymer({ + is: "tf-obsolete-histogram-chart", + properties: { + _chart: Object, + colorScale: Object, + tag: String, + selectedRuns: Array, + xType: String, + dataProvider: Function, + _initialized: Boolean, + }, + observers: [ + "_makeChart(tag, dataProvider, xType, colorScale, _initialized)", + "_changeRuns(_chart, selectedRuns.*)" + ], + _changeRuns: function(chart) { + if (chart.tag !== this.tag) { + return; // hack around some weird polymer bug :( + } + this._chart.changeRuns(this.selectedRuns); + this.redraw(); + }, + redraw: function() { + this._chart.redraw(); + }, + reload: function() { + this._chart.reload(); + }, + _makeChart: function(tag, dataProvider, xType, colorScale, _initialized) { + if (!_initialized) { + return; + } + if (this._chart) this._chart.destroy(); + var chart = new TF.HistogramChart(tag, dataProvider, xType, colorScale); + var svg = d3.select(this.$.chartsvg); + this.async(function() { + chart.renderTo(svg); + this._chart = chart; + }, 350); + }, + attached: function() { + this._initialized = true; + }, + detached: function() { + this._initialized = false; + } + }); + </script> +</dom-module> diff --git a/tensorflow/tensorboard/components/tf-histogram-dashboard/tf-obsolete-histogram-chart.ts b/tensorflow/tensorboard/components/tf-histogram-dashboard/tf-obsolete-histogram-chart.ts new file mode 100644 index 0000000000..c30d855cb0 --- /dev/null +++ b/tensorflow/tensorboard/components/tf-histogram-dashboard/tf-obsolete-histogram-chart.ts @@ -0,0 +1,153 @@ +/* Copyright 2015 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. +==============================================================================*/ +/* tslint:disable:no-namespace variable-name */ + +module TF { + export class HistogramChart { + protected dataFn: TF.ChartHelpers.DataFn; + protected tag: string; + private run2datasets: {[run: string]: Plottable.Dataset}; + protected runs: string[]; + + protected xAccessor: Plottable.Accessor<number|Date>; + protected xScale: Plottable.QuantitativeScale<number|Date>; + protected yScale: Plottable.QuantitativeScale<number>; + protected gridlines: Plottable.Components.Gridlines; + protected center: Plottable.Components.Group; + protected xAxis: Plottable.Axes.Numeric|Plottable.Axes.Time; + protected yAxis: Plottable.Axes.Numeric; + protected xLabel: Plottable.Components.AxisLabel; + protected yLabel: Plottable.Components.AxisLabel; + protected outer: Plottable.Components.Table; + protected colorScale: Plottable.Scales.Color; + protected tooltip: d3.Selection<any>; + private plots: Plottable.XYPlot<number|Date, number>[]; + + constructor( + tag: string, dataFn: TF.ChartHelpers.DataFn, xType: string, + colorScale: Plottable.Scales.Color, tooltip: d3.Selection<any>) { + this.dataFn = dataFn; + this.run2datasets = {}; + this.tag = tag; + this.colorScale = colorScale; + this.tooltip = tooltip; + this.buildChart(xType); + } + + /** + * Change the runs on the chart. The work of actually setting the dataset + * on the plot is deferred to the subclass because it is impl-specific. + * Changing runs automatically triggers a reload; this ensures that the + * newly selected run will have data, and that all the runs will be current + * (it would be weird if one run was ahead of the others, and the display + * depended on the order in which runs were added) + */ + public changeRuns(runs: string[]) { + this.runs = runs; + this.reload(); + let datasets = runs.map((r) => this.getDataset(r)); + this.plots.forEach((p) => p.datasets(datasets)); + } + + /** + * Reload data for each run in view. + */ + public reload() { + this.runs.forEach((run) => { + let dataset = this.getDataset(run); + this.dataFn(this.tag, run).then((x) => dataset.data(x)); + }); + } + + protected getDataset(run: string) { + if (this.run2datasets[run] === undefined) { + this.run2datasets[run] = + new Plottable.Dataset([], {run: run, tag: this.tag}); + } + return this.run2datasets[run]; + } + + protected buildChart(xType: string) { + if (this.outer) { + this.outer.destroy(); + } + let xComponents = TF.ChartHelpers.getXComponents(xType); + this.xAccessor = xComponents.accessor; + this.xScale = xComponents.scale; + this.xAxis = xComponents.axis; + this.xAxis.margin(0).tickLabelPadding(3); + this.yScale = new Plottable.Scales.Linear(); + this.yAxis = new Plottable.Axes.Numeric(this.yScale, 'left'); + let yFormatter = TF.ChartHelpers.multiscaleFormatter( + TF.ChartHelpers.Y_AXIS_FORMATTER_PRECISION); + this.yAxis.margin(0).tickLabelPadding(5).formatter(yFormatter); + this.yAxis.usesTextWidthApproximation(true); + + let center = this.buildPlot(this.xAccessor, this.xScale, this.yScale); + + this.gridlines = + new Plottable.Components.Gridlines(this.xScale, this.yScale); + + this.center = new Plottable.Components.Group([this.gridlines, center]); + this.outer = new Plottable.Components.Table( + [[this.yAxis, this.center], [null, this.xAxis]]); + } + + protected buildPlot(xAccessor, xScale, yScale): Plottable.Component { + let percents = [0, 228, 1587, 3085, 5000, 6915, 8413, 9772, 10000]; + let opacities = _.range(percents.length - 1) + .map((i) => (percents[i + 1] - percents[i]) / 2500); + let accessors = percents.map((p, i) => (datum) => datum[i][1]); + let median = 4; + let medianAccessor = accessors[median]; + + let plots = _.range(accessors.length - 1).map((i) => { + let p = new Plottable.Plots.Area<number|Date>(); + p.x(xAccessor, xScale); + + let y0 = i > median ? accessors[i] : accessors[i + 1]; + let y = i > median ? accessors[i + 1] : accessors[i]; + p.y(y, yScale); + p.y0(y0); + p.attr( + 'fill', (d: any, i: number, dataset: Plottable.Dataset) => + this.colorScale.scale(dataset.metadata().run)); + p.attr( + 'stroke', (d: any, i: number, dataset: Plottable.Dataset) => + this.colorScale.scale(dataset.metadata().run)); + p.attr('stroke-weight', (d: any, i: number, m: any) => '0.5px'); + p.attr('stroke-opacity', () => opacities[i]); + p.attr('fill-opacity', () => opacities[i]); + return p; + }); + + let medianPlot = new Plottable.Plots.Line<number|Date>(); + medianPlot.x(xAccessor, xScale); + medianPlot.y(medianAccessor, yScale); + medianPlot.attr( + 'stroke', + (d: any, i: number, m: any) => this.colorScale.scale(m.run)); + + this.plots = plots; + return new Plottable.Components.Group(plots); + } + + public renderTo(target: d3.Selection<any>) { this.outer.renderTo(target); } + + public redraw() { this.outer.redraw(); } + + protected destroy() { this.outer.destroy(); } + } +} |