class TfScalarDashboard extends LegacyElementMixin()

in tensorboard/plugins/scalar/tf_scalar_dashboard/tf-scalar-dashboard.ts [48:400]


class TfScalarDashboard extends LegacyElementMixin(ArrayUpdateHelper) {
  static readonly template = html`
    <tf-dashboard-layout>
      <div class="sidebar" slot="sidebar">
        <div class="settings">
          <div class="sidebar-section">
            <div class="line-item">
              <paper-checkbox
                id="show-download-links"
                checked="{{_showDownloadLinks}}"
                >Show data download links</paper-checkbox
              >
            </div>
            <div class="line-item">
              <paper-checkbox
                id="ignore-y-outlier"
                checked="{{_ignoreYOutliers}}"
                >Ignore outliers in chart scaling</paper-checkbox
              >
            </div>
            <div id="tooltip-sorting">
              <div>Tooltip sorting method:</div>
              <paper-dropdown-menu
                no-label-float
                selected-item-label="{{_tooltipSortingMethod}}"
              >
                <paper-listbox
                  class="dropdown-content"
                  selected="0"
                  slot="dropdown-content"
                >
                  <paper-item>default</paper-item>
                  <paper-item>descending</paper-item>
                  <paper-item>ascending</paper-item>
                  <paper-item>nearest</paper-item>
                </paper-listbox>
              </paper-dropdown-menu>
            </div>
          </div>
          <div class="sidebar-section">
            <tf-smoothing-input
              weight="{{_smoothingWeight}}"
              step="0.001"
              min="0"
              max="0.999"
            ></tf-smoothing-input>
          </div>
          <div class="sidebar-section">
            <tf-option-selector
              id="x-type-selector"
              name="Horizontal Axis"
              selected-id="{{_xType}}"
            >
              <paper-button id="step">step</paper-button
              ><!--
            --><paper-button id="relative">relative</paper-button
              ><!--
            --><paper-button id="wall_time">wall</paper-button>
            </tf-option-selector>
          </div>
        </div>
        <div class="sidebar-section runs-selector">
          <tf-runs-selector selected-runs="{{_selectedRuns}}">
          </tf-runs-selector>
        </div>
      </div>
      <div class="center" slot="center">
        <template is="dom-if" if="[[_dataNotFound]]">
          <div class="no-data-warning">
            <h3>No scalar data was found.</h3>
            <p>Probable causes:</p>
            <ul>
              <li>You haven’t written any scalar data to your event files.</li>
              <li>TensorBoard can’t find your event files.</li>
            </ul>

            <p>
              If you’re new to using TensorBoard, and want to find out how to
              add data and set up your event files, check out the
              <a
                href="https://github.com/tensorflow/tensorboard/blob/master/README.md"
                >README</a
              >
              and perhaps the
              <a
                href="https://www.tensorflow.org/get_started/summaries_and_tensorboard"
                >TensorBoard tutorial</a
              >.
            </p>

            <p>
              If you think TensorBoard is configured properly, please see
              <a
                href="https://github.com/tensorflow/tensorboard/blob/master/README.md#my-tensorboard-isnt-showing-any-data-whats-wrong"
                >the section of the README devoted to missing data problems</a
              >
              and consider filing an issue on GitHub.
            </p>
          </div>
        </template>
        <template is="dom-if" if="[[!_dataNotFound]]">
          <tf-tag-filterer tag-filter="{{_tagFilter}}"></tf-tag-filterer>
          <template is="dom-repeat" items="[[_categories]]" as="category">
            <tf-category-paginated-view
              category="[[category]]"
              initial-opened="[[_shouldOpen(index)]]"
              get-category-item-key="[[_getCategoryItemKey]]"
            >
              <template>
                <tf-scalar-card
                  active="[[active]]"
                  data-to-load="[[item.series]]"
                  ignore-y-outliers="[[_ignoreYOutliers]]"
                  multi-experiments="[[_getMultiExperiments(dataSelection)]]"
                  request-manager="[[_requestManager]]"
                  show-download-links="[[_showDownloadLinks]]"
                  smoothing-enabled="[[_smoothingEnabled]]"
                  smoothing-weight="[[_smoothingWeight]]"
                  tag-metadata="[[_tagMetadata(category, _runToTagInfo, item)]]"
                  tag="[[item.tag]]"
                  tooltip-sorting-method="[[_tooltipSortingMethod]]"
                  x-type="[[_xType]]"
                  batch-size="[[featureFlags.scalarsBatchSize]]"
                  in-colab="[[featureFlags.inColab]]"
                ></tf-scalar-card>
              </template>
            </tf-category-paginated-view>
          </template>
        </template>
      </div>
    </tf-dashboard-layout>

    <style include="dashboard-style"></style>
    <style>
      #tooltip-sorting {
        align-items: center;
        display: flex;
        font-size: 14px;
        margin-top: 15px;
      }

      #tooltip-sorting paper-dropdown-menu {
        margin-left: 10px;
        --paper-input-container-focus-color: var(--tb-orange-strong);
        width: 105px;
      }

      .line-item {
        display: block;
        padding-top: 5px;
      }
      .no-data-warning {
        max-width: 540px;
        margin: 80px auto 0 auto;
      }
      .center {
        overflow-x: hidden;
      }
    </style>
  `;

  @property({type: Boolean})
  reloadOnReady: boolean = true;

  @property({type: Object})
  featureFlags?: FeatureFlags;

  @property({
    type: Boolean,
    notify: true,
    observer: '_showDownloadLinksObserver',
  })
  _showDownloadLinks: boolean = tf_storage
    .getBooleanInitializer('_showDownloadLinks', {
      defaultValue: false,
      useLocalStorage: true,
    })
    .call(this);

  @property({
    type: Number,
    notify: true,
    observer: '_smoothingWeightObserver',
  })
  _smoothingWeight: number = tf_storage
    .getNumberInitializer('_smoothingWeight', {
      defaultValue: 0.6,
    })
    .call(this);

  @property({
    type: Boolean,
    observer: '_ignoreYOutliersObserver',
  })
  _ignoreYOutliers: boolean = tf_storage
    .getBooleanInitializer('_ignoreYOutliers', {
      defaultValue: true,
      useLocalStorage: true,
    })
    .call(this);

  @property({type: String})
  _xType: string = vz_chart_helpers.XType.STEP;

  @property({type: Array})
  _selectedRuns: string[] = [];

  @property({type: Object})
  _runToTagInfo: object;

  @property({type: Boolean})
  _dataNotFound: boolean;

  @property({type: String})
  _tagFilter: string = '';

  // Categories must only be computed after _dataNotFound is found to be
  // true and then polymer DOM templating responds to that finding. We
  // thus use this property to guard when categories are computed.
  @property({type: Boolean})
  _categoriesDomReady: boolean;

  @property({type: Array})
  _categories: string[] = [];

  // Items show multiple runs, so exclude runs from category item keys for
  // efficient template reuse.
  @property({type: Object})
  _getCategoryItemKey: object = (item) => item.tag;

  @property({type: Object})
  _requestManager: RequestManager = new RequestManager(50);

  _showDownloadLinksObserver = tf_storage.getBooleanObserver(
    '_showDownloadLinks',
    {defaultValue: false, useLocalStorage: true}
  );

  _smoothingWeightObserver = tf_storage.getNumberObserver('_smoothingWeight', {
    defaultValue: 0.6,
  });

  _ignoreYOutliersObserver = tf_storage.getBooleanObserver('_ignoreYOutliers', {
    defaultValue: true,
    useLocalStorage: true,
  });

  @computed('_smoothingWeight')
  get _smoothingEnabled(): boolean {
    var _smoothingWeight = this._smoothingWeight;
    return _smoothingWeight > 0;
  }

  _getCategoryKey(category) {
    return category.metadata.type ==
      tf_categorization_utils.CategoryType.SEARCH_RESULTS
      ? ''
      : category.name;
  }

  _shouldOpen(index) {
    return index <= 2;
  }

  ready() {
    super.ready();
    if (this.reloadOnReady) this.reload();
  }

  reload() {
    this._fetchTags().then(() => {
      this._reloadCharts();
    });
  }

  _fetchTags() {
    const url = getRouter().pluginRoute('scalars', '/tags');
    return this._requestManager.request(url).then((runToTagInfo) => {
      if (_.isEqual(runToTagInfo, this._runToTagInfo)) {
        // No need to update anything if there are no changes.
        return;
      }
      const runToTag = _.mapValues(runToTagInfo, (x) => Object.keys(x));
      const tags = getTags(runToTag);
      this.set('_dataNotFound', tags.length === 0);
      this.set('_runToTagInfo', runToTagInfo);
      this.async(() => {
        // See the comment above `_categoriesDomReady`.
        this.set('_categoriesDomReady', true);
      });
    });
  }

  _reloadCharts() {
    this.root.querySelectorAll('tf-scalar-card').forEach((chart) => {
      (chart as TfScalarCard).reload();
    });
  }

  @observe(
    '_runToTagInfo',
    '_selectedRuns',
    '_tagFilter',
    '_categoriesDomReady'
  )
  _updateCategories() {
    var runToTagInfo = this._runToTagInfo;
    var selectedRuns = this._selectedRuns;
    var tagFilter = this._tagFilter;
    let categories;
    let query = tagFilter;
    const runToTag = _.mapValues(runToTagInfo, (x) => Object.keys(x));
    categories = tf_categorization_utils.categorizeTags(
      runToTag as tf_categorization_utils.RunToTag,
      selectedRuns,
      query
    );
    categories.forEach((category) => {
      category.items = category.items.map((item) => ({
        tag: item.tag,
        series: item.runs.map((run) => ({run, tag: item.tag})),
      }));
    });
    this.updateArrayProp('_categories', categories, this._getCategoryKey);
  }

  _tagMetadata(category, runToTagsInfo, item) {
    const categoryName = category.name as string;
    const tag = item.tag;
    const runToTagInfo = {};
    item.series.forEach(({run}) => {
      runToTagInfo[run] = runToTagsInfo[run][tag];
    });
    // All new-style scalar tags include the `/scalar_summary`
    // suffix. We can trim that from the display name.
    const defaultDisplayName = tag.replace(/\/scalar_summary$/, '');
    let {description, displayName} = tf_utils.aggregateTagInfo(
      runToTagInfo,
      defaultDisplayName
    );
    // If category name is a prefix group, strip the prefix from the name
    // of the scalar-card if name != prefix.
    if (
      category.metadata.type ==
        tf_categorization_utils.CategoryType.PREFIX_GROUP &&
      displayName.startsWith(categoryName + '/')
    ) {
      // + 1 to strip off the separator.
      displayName = displayName.slice(categoryName.length + 1);
    }
    return {description, displayName};
  }
}