|
19 | 19 | <link rel="import" href="../paper-icon-button/paper-icon-button.html">
|
20 | 20 | <link rel="import" href="../paper-item/paper-item.html">
|
21 | 21 | <link rel="import" href="../paper-menu/paper-menu.html">
|
| 22 | +<link rel="import" href="../paper-spinner/paper-spinner-lite.html"> |
22 | 23 | <link rel="import" href="../polymer/polymer.html">
|
23 | 24 | <link rel="import" href="../tf-backend/tf-backend.html">
|
24 | 25 | <link rel="import" href="../tf-card-heading/tf-card-heading.html">
|
|
32 | 33 | <dom-module id="tf-scalar-chart">
|
33 | 34 | <template>
|
34 | 35 | <tf-card-heading title="[[tag]]"></tf-card-heading>
|
35 |
| - <vz-line-chart |
36 |
| - x-type="[[xType]]" |
37 |
| - color-scale="[[_runsColorScale]]" |
38 |
| - smoothing-enabled="[[smoothingEnabled]]" |
39 |
| - smoothing-weight="[[smoothingWeight]]" |
40 |
| - tooltip-sorting-method="[[tooltipSortingMethod]]" |
41 |
| - ignore-y-outliers="[[ignoreYOutliers]]" |
42 |
| - ></vz-line-chart> |
| 36 | + <div id="chart-and-spinner-container"> |
| 37 | + <vz-line-chart |
| 38 | + x-type="[[xType]]" |
| 39 | + color-scale="[[_runsColorScale]]" |
| 40 | + smoothing-enabled="[[smoothingEnabled]]" |
| 41 | + smoothing-weight="[[smoothingWeight]]" |
| 42 | + tooltip-sorting-method="[[tooltipSortingMethod]]" |
| 43 | + ignore-y-outliers="[[ignoreYOutliers]]" |
| 44 | + style="[[_computeLineChartStyle(_loading)]]" |
| 45 | + ></vz-line-chart> |
| 46 | + <template is="dom-if" if="[[_loading]]"> |
| 47 | + <div id="loading-spinner-container"> |
| 48 | + <paper-spinner-lite active></paper-spinner-lite> |
| 49 | + </div> |
| 50 | + </template> |
| 51 | + </div> |
43 | 52 | <div style="display: flex; flex-direction: row;">
|
44 | 53 | <paper-icon-button
|
45 | 54 | selected$="[[_expanded]]"
|
|
52 | 61 | on-tap="_toggleLogScale"
|
53 | 62 | title="Toggle y-axis log scale"
|
54 | 63 | ></paper-icon-button>
|
| 64 | + <paper-icon-button |
| 65 | + icon="settings-overscan" |
| 66 | + on-tap="_resetDomain" |
| 67 | + title="Fit domain to data" |
| 68 | + ></paper-icon-button> |
55 | 69 | <span style="flex-grow: 1"></span>
|
56 | 70 | <template is="dom-if" if="[[showDownloadLinks]]">
|
57 | 71 | <div class="download-links">
|
|
89 | 103 | height: 400px;
|
90 | 104 | width: 100%;
|
91 | 105 | }
|
| 106 | + #chart-and-spinner-container { |
| 107 | + display: flex; |
| 108 | + flex-grow: 1; |
| 109 | + position: relative; |
| 110 | + } |
| 111 | + #loading-spinner-container { |
| 112 | + align-items: center; |
| 113 | + bottom: 0; |
| 114 | + display: flex; |
| 115 | + display: flex; |
| 116 | + justify-content: center; |
| 117 | + left: 0; |
| 118 | + pointer-events: none; |
| 119 | + position: absolute; |
| 120 | + right: 0; |
| 121 | + top: 0; |
| 122 | + } |
92 | 123 | vz-line-chart {
|
93 | 124 | -webkit-user-select: none;
|
94 | 125 | -moz-user-select: none;
|
|
167 | 198 | value: () => ({scale: runsColorScale}),
|
168 | 199 | },
|
169 | 200 |
|
| 201 | + _loading: { |
| 202 | + type: Boolean, |
| 203 | + value: false, |
| 204 | + }, |
| 205 | + |
| 206 | + _resetDomainOnNextLoad: { |
| 207 | + type: Boolean, |
| 208 | + value: true, |
| 209 | + }, |
| 210 | + |
170 | 211 | _canceller: {
|
171 | 212 | type: Object,
|
172 | 213 | value: () => new Canceller(),
|
|
199 | 240 | },
|
200 | 241 | },
|
201 | 242 | observers: [
|
202 |
| - 'reload(tag)', |
203 |
| - '_changeSeries(runs.*)' |
| 243 | + '_tagChanged(_attached, tag)', |
| 244 | + '_runsChanged(_attached, runs.*)' |
204 | 245 | ],
|
| 246 | + created() { |
| 247 | + this._loadData = _.debounce( |
| 248 | + this._loadData, 100, {leading: true, trailing: true}); |
| 249 | + }, |
205 | 250 | attached() {
|
206 | 251 | this._attached = true;
|
207 | 252 | this._changeSeries();
|
|
210 | 255 | this._loadedRuns = {};
|
211 | 256 | this._loadData();
|
212 | 257 | },
|
213 |
| - _loadData() { |
214 |
| - if (!this._attached) { |
| 258 | + _tagChanged(attached, tagUpdateRecord) { |
| 259 | + this._loadedRuns = {}; |
| 260 | + this._resetDomainOnNextLoad = true; |
| 261 | + this._loadData(); |
| 262 | + }, |
| 263 | + _runsChanged(attached, runsUpdateRecord) { |
| 264 | + if (!attached) { |
215 | 265 | return;
|
216 | 266 | }
|
217 |
| - // |
218 |
| - // Before updating, cancel any network-pending updates, to |
219 |
| - // prevent race conditions where older data stomps newer data. |
220 |
| - this._canceller.cancelAll(); |
221 |
| - this.runs.forEach(run => { |
222 |
| - if (this._loadedRuns[run]) { |
| 267 | + this.$$('vz-line-chart').setVisibleSeries(this.runs); |
| 268 | + this._loadData(); |
| 269 | + }, |
| 270 | + _loadData() { |
| 271 | + this.async(() => { |
| 272 | + if (!this._attached) { |
223 | 273 | return;
|
224 | 274 | }
|
225 |
| - this._loadedRuns[run] = true; |
226 |
| - const url = this._scalarUrl(this.tag, run); |
227 |
| - const updateSeries = this._canceller.cancellable(result => { |
228 |
| - if (result.cancelled) { |
229 |
| - return; |
| 275 | + this._loading = true; |
| 276 | + // |
| 277 | + // Before updating, cancel any network-pending updates, to |
| 278 | + // prevent race conditions where older data stomps newer data. |
| 279 | + this._canceller.cancelAll(); |
| 280 | + const tag = this.tag; |
| 281 | + const runPromises = this.runs.map(run => { |
| 282 | + if (this._loadedRuns[run]) { |
| 283 | + return Promise.resolve(); |
| 284 | + } |
| 285 | + const url = this._scalarUrl(tag, run); |
| 286 | + const updateSeries = this._canceller.cancellable(result => { |
| 287 | + if (result.cancelled) { |
| 288 | + return; |
| 289 | + } |
| 290 | + const data = result.value; |
| 291 | + const formattedData = data.map(datum => ({ |
| 292 | + wall_time: new Date(datum[0] * 1000), |
| 293 | + step: datum[1], |
| 294 | + scalar: datum[2], |
| 295 | + })); |
| 296 | + if (tag === this.tag) { |
| 297 | + // Only update the runs cache for the current tag. If we |
| 298 | + // load data for Tag A, then the tag changes to Tag B |
| 299 | + // while requests are still in flight, these requests |
| 300 | + // should not poison the cache. |
| 301 | + this._loadedRuns[run] = true; |
| 302 | + } |
| 303 | + this.$$('vz-line-chart').setSeriesData(run, formattedData); |
| 304 | + }); |
| 305 | + return this.requestManager.request(url).then(updateSeries); |
| 306 | + }); |
| 307 | + const finish = this._canceller.cancellable(result => { |
| 308 | + if (!result.cancelled) { |
| 309 | + this._loading = false; |
| 310 | + const chart = this.$$('vz-line-chart'); |
| 311 | + if (runPromises.length > 0 && this._resetDomainOnNextLoad) { |
| 312 | + // (Don't unset _resetDomainOnNextLoad when we didn't |
| 313 | + // load any runs: this has the effect that if all our |
| 314 | + // runs are deselected, then we toggle them all on, we |
| 315 | + // properly size the domain once all the data is loaded |
| 316 | + // instead of just when we're first rendered.) |
| 317 | + this._resetDomainOnNextLoad = false; |
| 318 | + chart.resetDomain(); |
| 319 | + } |
| 320 | + chart.redraw(); |
230 | 321 | }
|
231 |
| - const data = result.value; |
232 |
| - const formattedData = data.map(datum => ({ |
233 |
| - wall_time: new Date(datum[0] * 1000), |
234 |
| - step: datum[1], |
235 |
| - scalar: datum[2], |
236 |
| - })); |
237 |
| - this.$$('vz-line-chart').setSeriesData(run, formattedData); |
238 | 322 | });
|
239 |
| - this.requestManager.request(url).then(updateSeries); |
| 323 | + return Promise.all(runPromises).then(finish); |
240 | 324 | });
|
241 | 325 | },
|
242 | 326 | _changeSeries() {
|
|
261 | 345 | this.redraw();
|
262 | 346 | },
|
263 | 347 |
|
| 348 | + _computeLineChartStyle(loading) { |
| 349 | + return loading ? 'opacity: 0.3;' : ''; |
| 350 | + }, |
| 351 | + |
| 352 | + _resetDomain() { |
| 353 | + const chart = this.$$('vz-line-chart'); |
| 354 | + if (chart) { |
| 355 | + chart.resetDomain(); |
| 356 | + } |
| 357 | + }, |
| 358 | + |
264 | 359 | _csvUrl(run) {
|
265 | 360 | return this._scalarUrl(this.tag, run) + '&format=csv';
|
266 | 361 | },
|
|
0 commit comments