blob: 8a1d158d0214ea06316d3f7bf91fdfb9b687e147 [file] [log] [blame]
<!DOCTYPE html>
<!--
Copyright 2016 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
<link rel="import" href="/tracing/base/timing.html">
<link rel="import" href="/tracing/base/unit.html">
<link rel="import" href="/tracing/ui/base/name_line_chart.html">
<link rel="import" href="/tracing/value/ui/histogram_span.html">
<link rel="import" href="/tracing/value/ui/scalar_span.html">
<dom-module id="tr-v-ui-histogram-set-table-cell">
<template>
<style>
#histogram_container {
display: flex;
flex-direction: row;
}
#missing, #empty, #unmergeable, #scalar {
flex-grow: 1;
}
#open_histogram, #close_histogram, #open_histogram svg, #close_histogram svg {
height: 1em;
}
#open_histogram svg {
margin-left: 4px;
stroke-width: 0;
stroke: blue;
fill: blue;
}
:host(:hover) #open_histogram svg {
background: blue;
stroke: white;
fill: white;
}
#scalar {
flex-grow: 1;
white-space: nowrap;
}
#histogram {
flex-grow: 1;
}
#close_histogram svg line {
stroke-width: 18;
stroke: black;
}
#close_histogram:hover svg {
background: black;
}
#close_histogram:hover svg line {
stroke: white;
}
#overview_container {
display: none;
}
</style>
<div id="histogram_container">
<span id="missing">(missing)</span>
<span id="empty">(empty)</span>
<span id="unmergeable">(unmergeable)</span>
<tr-v-ui-scalar-span id="scalar" on-click="openHistogram_"></tr-v-ui-scalar-span>
<span id="open_histogram" on-click="openHistogram_">
<svg viewbox="0 0 128 128">
<rect x="16" y="24" width="32" height="16"/>
<rect x="16" y="56" width="96" height="16"/>
<rect x="16" y="88" width="64" height="16"/>
</svg>
</span>
<span id="histogram"></span>
<span id="close_histogram" on-click="closeHistogram_">
<svg viewbox="0 0 128 128">
<line x1="28" y1="28" x2="100" y2="100"/>
<line x1="28" y1="100" x2="100" y2="28"/>
</svg>
</span>
</div>
<div id="overview_container">
</div>
</template>
</dom-module>
<script>
'use strict';
tr.exportTo('tr.v.ui', function() {
Polymer({
is: 'tr-v-ui-histogram-set-table-cell',
created() {
this.viewState_ = undefined;
this.rootListener_ = this.onRootStateUpdate_.bind(this);
this.row_ = undefined;
this.displayLabel_ = '';
this.histogram_ = undefined;
this.histogramSpan_ = undefined;
this.overviewChart_ = undefined;
this.mwuResult_ = undefined;
},
ready() {
this.addEventListener('click', this.onClick_.bind(this));
},
attached() {
if (this.row) {
this.row.rootViewState.addUpdateListener(this.rootListener_);
}
},
detached() {
this.row.rootViewState.removeUpdateListener(this.rootListener_);
// Don't need to removeUpdateListener for the row and cells; their
// lifetimes are the same as |this|.
},
updateMwu_() {
const referenceHistogram = this.referenceHistogram;
this.mwuResult_ = undefined;
if (!(this.histogram instanceof tr.v.Histogram)) return;
if (!this.histogram.canCompare(referenceHistogram)) return;
this.mwuResult_ = tr.b.math.Statistics.mwu(
this.histogram.sampleValues,
referenceHistogram.sampleValues,
this.row.rootViewState.alpha);
},
build(row, displayLabel, viewState) {
this.row_ = row;
this.displayLabel_ = displayLabel;
this.viewState_ = viewState;
this.histogram_ = this.row.columns.get(displayLabel);
if (this.viewState) {
// this.viewState is undefined when this.histogram_ is undefined.
// In that case, onViewStateUpdate_ wouldn't be able to do anything
// anyway.
this.viewState.addUpdateListener(this.onViewStateUpdate_.bind(this));
}
this.row.viewState.addUpdateListener(this.onRowStateUpdate_.bind(this));
if (this.isAttached) {
this.row.rootViewState.addUpdateListener(this.rootListener_);
}
this.updateMwu_();
// this.histogram_ and this.referenceHistogram might be undefined,
// a HistogramSet of unmergeable Histograms, or a Histogram.
this.updateContents_();
},
updateSignificance_() {
if (!this.mwuResult_) return;
this.$.scalar.significance = this.mwuResult_.significance;
},
get viewState() {
return this.viewState_;
},
get row() {
return this.row_;
},
get histogram() {
return this.histogram_;
},
get referenceHistogram() {
const referenceDisplayLabel =
this.row.rootViewState.referenceDisplayLabel;
if (!referenceDisplayLabel) return undefined;
if (referenceDisplayLabel === this.displayLabel_) return undefined;
return this.row.columns.get(referenceDisplayLabel);
},
get isHistogramOpen() {
return (this.histogramSpan_ !== undefined) &&
(this.$.histogram.style.display === 'block');
},
set isHistogramOpen(open) {
if (!(this.histogram instanceof tr.v.Histogram) ||
(this.histogram.numValues === 0)) {
return;
}
// Unfortunately, we can't use a css attribute for this since this stuff
// is tied up in all the possible states of this.histogram. See
// updateContents_().
this.$.scalar.style.display = open ? 'none' : 'flex';
this.$.open_histogram.style.display = open ? 'none' : 'block';
this.$.close_histogram.style.display = open ? 'block' : 'none';
this.$.histogram.style.display = open ? 'block' : 'none';
// Wait to create the histogram-span until the user wants to display it
// in order to speed up creating lots of histogram-set-table-cells when
// building the table.
if (open && this.histogramSpan_ === undefined) {
this.histogramSpan_ = document.createElement('tr-v-ui-histogram-span');
this.histogramSpan_.viewState = this.viewState;
this.histogramSpan_.rowState = this.row.viewState;
this.histogramSpan_.rootState = this.row.rootViewState;
this.histogramSpan_.build(this.histogram, this.referenceHistogram);
this.$.histogram.appendChild(this.histogramSpan_);
}
this.viewState.isOpen = open;
},
onViewStateUpdate_(event) {
if (event.delta.isOpen) {
this.isHistogramOpen = this.viewState.isOpen;
}
},
onRowStateUpdate_(event) {
if (event.delta.isOverviewed === undefined) return;
if (this.row.viewState.isOverviewed) {
this.showOverview();
} else {
this.hideOverview();
}
},
onRootStateUpdate_(event) {
if (event.delta.referenceDisplayLabel &&
this.histogramSpan_) {
this.histogramSpan_.build(this.histogram, this.referenceHistogram);
}
if (event.delta.displayStatisticName ||
event.delta.referenceDisplayLabel) {
this.updateMwu_();
this.updateContents_();
} else if (event.delta.alpha && this.mwuResult_) {
this.mwuResult_.compare(this.row.rootViewState.alpha);
this.updateSignificance_();
}
if (this.row.viewState.isOverviewed &&
(event.delta.sortColumnIndex ||
event.delta.sortDescending ||
event.delta.displayStatisticName ||
event.delta.referenceDisplayLabel)) {
if (this.overviewChart_ !== undefined) {
this.$.overview_container.removeChild(this.overviewChart_);
this.overviewChart_ = undefined;
}
this.showOverview();
}
},
onClick_(event) {
// Since the histogram-set-table's table doesn't support any kind of
// selection, clicking anywhere within a row that has subRows will
// expand/collapse that row, which can relayout the table and move things
// around. Prevent table relayout by preventing the tr-ui-b-table from
// receiving the click event.
event.stopPropagation();
},
openHistogram_() {
this.isHistogramOpen = true;
tr.b.Timing.instant('histogram-set-table-cell', 'open');
},
closeHistogram_() {
this.isHistogramOpen = false;
tr.b.Timing.instant('histogram-set-table-cell', 'close');
},
updateContents_() {
const isOpen = this.isHistogramOpen;
this.$.empty.style.display = 'none';
this.$.unmergeable.style.display = 'none';
this.$.scalar.style.display = 'none';
this.$.histogram.style.display = 'none';
this.$.close_histogram.style.display = 'none';
this.$.open_histogram.style.visibility = 'hidden';
if (!this.histogram) {
this.$.missing.style.display = 'block';
return;
}
this.$.missing.style.display = 'none';
if (this.histogram instanceof tr.v.HistogramSet) {
this.$.unmergeable.style.display = 'block';
return;
}
if (!(this.histogram instanceof tr.v.Histogram)) {
throw new Error('Invalid Histogram: ' + this.histogram);
}
if (this.histogram.numValues === 0) {
this.$.empty.style.display = 'block';
return;
}
this.$.open_histogram.style.display = 'block';
this.$.open_histogram.style.visibility = 'visible';
this.$.scalar.style.display = 'flex';
this.updateSignificance_();
const referenceHistogram = this.referenceHistogram;
const statName = this.histogram.getAvailableStatisticName(
this.row.rootViewState.displayStatisticName, referenceHistogram);
const statisticScalar = this.histogram.getStatisticScalar(
statName, referenceHistogram);
this.$.scalar.setValueAndUnit(
statisticScalar.value, statisticScalar.unit);
this.isHistogramOpen = isOpen;
},
showOverview() {
this.$.overview_container.style.display = 'block';
if (this.overviewChart_ !== undefined) return;
this.row.sortSubRows();
let referenceDisplayLabel =
this.row.rootViewState.referenceDisplayLabel;
if (referenceDisplayLabel === this.displayLabel_) {
referenceDisplayLabel = undefined;
}
const displayStatisticName = this.row.rootViewState.displayStatisticName;
const data = [];
let unit;
for (const subRow of this.row.subRows) {
const subHist = subRow.columns.get(this.displayLabel_);
if (!(subHist instanceof tr.v.Histogram)) continue;
if (unit === undefined) {
unit = subHist.unit;
} else if (unit !== subHist.unit) {
// The subrows have different units, so the overview chart cannot
// use a single unit to format all of the values, so don't display
// an overview chart at all.
data.splice(0);
break;
}
const refHist = subRow.columns.get(referenceDisplayLabel);
const statName = subHist.getAvailableStatisticName(
displayStatisticName, refHist);
const statScalar = subHist.getStatisticScalar(
statName, refHist);
if (statScalar !== undefined) {
data.push({
x: subRow.name,
y: statScalar.value,
});
}
}
if (data.length < 2) return;
this.overviewChart_ = new tr.ui.b.NameLineChart();
this.$.overview_container.appendChild(this.overviewChart_);
this.overviewChart_.displayXInHover = true;
this.overviewChart_.hideLegend = true;
this.overviewChart_.unit = unit;
this.overviewChart_.overrideDataRange = this.row.overviewDataRange;
this.overviewChart_.data = data;
},
hideOverview() {
this.$.overview_container.style.display = 'none';
}
});
return {
};
});
</script>