Skip to content

Commit 9859579

Browse files
Merge pull request #59 from Demonstrandum/cursor/cross-tab-state-sync-0c38
Cross-tab state sync
2 parents c35337b + db6fc7c commit 9859579

14 files changed

Lines changed: 326 additions & 85 deletions

File tree

tensorbored/components/tf_color_scale/colorScale.ts

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,39 +16,48 @@ import * as d3 from 'd3';
1616
import {BaseStore} from '../tf_backend/baseStore';
1717
import {experimentsStore} from '../tf_backend/experimentsStore';
1818
import {runsStore} from '../tf_backend/runsStore';
19+
import {standard} from './palettes';
20+
21+
export const RUN_COLOR_MAP_CHANGED_EVENT = 'tb-run-color-map-changed';
1922

2023
/**
21-
* Read the run-name → hex-color map from NgRx (exposed on window by
22-
* RunsEffects). This is the single source of truth used by time-series.
24+
* Read the run-name → hex-color map seeded on window by the NgRx
25+
* RunsEffects syncPolymerRunColorMap$ effect. Returns null before the
26+
* effect has fired for the first time.
2327
*/
24-
function readColorMap(): Record<string, string> {
25-
const live = (window as any).__tbRunColorMap as
26-
| Record<string, string>
27-
| undefined;
28-
if (!live) {
29-
throw new Error('Missing run color map on window.__tbRunColorMap');
30-
}
31-
return live;
28+
function readColorMap(): Record<string, string> | null {
29+
return ((window as any).__tbRunColorMap as Record<string, string>) ?? null;
3230
}
3331

3432
export class ColorScale {
3533
private identifiers = d3.map();
3634

35+
constructor(private readonly palette: string[] = standard) {}
36+
3737
public setDomain(strings: string[]): this {
3838
this.identifiers = d3.map();
39-
// Module-level initialization runs before the NgRx bridge seeds
40-
// window.__tbRunColorMap. During that phase the run domain is empty.
41-
// Keep strict behavior for non-empty domains only.
4239
if (strings.length === 0) {
4340
return this;
4441
}
4542
const stored = readColorMap();
46-
strings.forEach((s) => {
47-
if (stored[s] === undefined) {
48-
throw new Error(`Missing run color for "${s}" in shared color map`);
49-
}
50-
this.identifiers.set(s, stored[s]);
51-
});
43+
if (stored) {
44+
strings.forEach((s, i) => {
45+
const color = stored[s];
46+
if (color !== undefined) {
47+
this.identifiers.set(s, color);
48+
} else {
49+
console.error(`ColorScale: run "${s}" missing from shared color map`);
50+
this.identifiers.set(s, this.palette[i % this.palette.length]);
51+
}
52+
});
53+
} else {
54+
// NgRx bridge has not seeded window.__tbRunColorMap yet.
55+
// Fall back to the static palette so getColor never fails for
56+
// runs that were passed to setDomain.
57+
strings.forEach((s, i) => {
58+
this.identifiers.set(s, this.palette[i % this.palette.length]);
59+
});
60+
}
5261
return this;
5362
}
5463

@@ -70,7 +79,7 @@ function createAutoUpdateColorScale(
7079
}
7180
store.addListener(update);
7281
// Re-read colors when the NgRx store subscription updates them.
73-
window.addEventListener('tb-run-color-map-changed', update);
82+
window.addEventListener(RUN_COLOR_MAP_CHANGED_EVENT, update);
7483
update();
7584
return (runName) => colorScale.getColor(runName);
7685
}

tensorbored/components/tf_line_chart_data_loader/tf-line-chart-data-loader.ts

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ import * as Plottable from 'plottable';
1919
import '../polymer/irons_and_papers';
2020
import {LegacyElementMixin} from '../polymer/legacy_element_mixin';
2121
import {RequestManager} from '../tf_backend/requestManager';
22-
import {runsColorScale} from '../tf_color_scale/colorScale';
22+
import {
23+
RUN_COLOR_MAP_CHANGED_EVENT,
24+
runsColorScale,
25+
} from '../tf_color_scale/colorScale';
2326
import {DataLoaderBehavior} from '../tf_dashboard_common/data-loader-behavior';
2427
import {
2528
AxisScaleType,
@@ -55,7 +58,7 @@ const cascadingRedraw = _.throttle(function internalRedraw() {
5558
x._maybeRenderedInBadState = false;
5659
}
5760
window.cancelAnimationFrame(redrawRaf);
58-
window.requestAnimationFrame(internalRedraw);
61+
redrawRaf = window.requestAnimationFrame(internalRedraw);
5962
}, 100);
6063

6164
// A component that fetches data from the TensorBoard server and renders it into
@@ -140,6 +143,53 @@ class _TfLineChartDataLoader<ScalarMetadata>
140143
`;
141144

142145
private _redrawRaf: number | null = null;
146+
private _resizeObserver: ResizeObserver | null = null;
147+
private _runColorMapChangedListener: (() => void) | null = null;
148+
private _lastObservedWidth = -1;
149+
private _lastObservedHeight = -1;
150+
151+
override connectedCallback() {
152+
super.connectedCallback();
153+
this._runColorMapChangedListener = () => {
154+
if (this.active) {
155+
if (!redrawQueue.includes(this)) {
156+
redrawQueue.push(this);
157+
}
158+
cascadingRedraw();
159+
} else {
160+
this._maybeRenderedInBadState = true;
161+
}
162+
};
163+
window.addEventListener(
164+
RUN_COLOR_MAP_CHANGED_EVENT,
165+
this._runColorMapChangedListener
166+
);
167+
this._resizeObserver = new ResizeObserver((entries) => {
168+
if (entries.length === 0) return;
169+
const {width, height} = entries[0].contentRect;
170+
const roundedWidth = Math.round(width);
171+
const roundedHeight = Math.round(height);
172+
if (
173+
roundedWidth === this._lastObservedWidth &&
174+
roundedHeight === this._lastObservedHeight
175+
) {
176+
return;
177+
}
178+
this._lastObservedWidth = roundedWidth;
179+
this._lastObservedHeight = roundedHeight;
180+
181+
if (roundedWidth <= 0 || roundedHeight <= 0 || !this.active) {
182+
this._maybeRenderedInBadState = true;
183+
return;
184+
}
185+
186+
if (!redrawQueue.includes(this)) {
187+
redrawQueue.push(this);
188+
}
189+
cascadingRedraw();
190+
});
191+
this._resizeObserver.observe(this);
192+
}
143193

144194
@property({
145195
type: Boolean,
@@ -231,6 +281,17 @@ class _TfLineChartDataLoader<ScalarMetadata>
231281
}
232282

233283
override disconnectedCallback() {
284+
if (this._runColorMapChangedListener !== null) {
285+
window.removeEventListener(
286+
RUN_COLOR_MAP_CHANGED_EVENT,
287+
this._runColorMapChangedListener
288+
);
289+
this._runColorMapChangedListener = null;
290+
}
291+
if (this._resizeObserver !== null) {
292+
this._resizeObserver.disconnect();
293+
this._resizeObserver = null;
294+
}
234295
super.disconnectedCallback();
235296
if (this._redrawRaf !== null) cancelAnimationFrame(this._redrawRaf);
236297
}

tensorbored/components/tf_runs_selector/tf-runs-selector.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ import {LegacyElementMixin} from '../polymer/legacy_element_mixin';
2020
import * as baseStore from '../tf_backend/baseStore';
2121
import {environmentStore} from '../tf_backend/environmentStore';
2222
import {runsStore} from '../tf_backend/runsStore';
23-
import {runsColorScale} from '../tf_color_scale/colorScale';
23+
import {
24+
RUN_COLOR_MAP_CHANGED_EVENT,
25+
runsColorScale,
26+
} from '../tf_color_scale/colorScale';
2427
import '../tf_dashboard_common/tf-multi-checkbox';
2528
import '../tf_wbr_string/tf-wbr-string';
2629

@@ -245,6 +248,7 @@ class TfRunsSelector extends LegacyElementMixin(PolymerElement) {
245248
_envStoreListener: baseStore.ListenKey;
246249

247250
private _selectionChangedListener: (() => void) | null = null;
251+
private _runColorMapChangedListener: (() => void) | null = null;
248252
private _syncingFromStorage = false;
249253

250254
override attached() {
@@ -264,6 +268,15 @@ class TfRunsSelector extends LegacyElementMixin(PolymerElement) {
264268
'tb-run-selection-changed',
265269
this._selectionChangedListener
266270
);
271+
272+
this._runColorMapChangedListener = () => {
273+
this.set('coloring', {getColor: runsColorScale});
274+
(this.$.multiCheckbox as any).synchronizeColors();
275+
};
276+
window.addEventListener(
277+
RUN_COLOR_MAP_CHANGED_EVENT,
278+
this._runColorMapChangedListener
279+
);
267280
}
268281

269282
private _syncFromStorage() {
@@ -282,6 +295,13 @@ class TfRunsSelector extends LegacyElementMixin(PolymerElement) {
282295
);
283296
this._selectionChangedListener = null;
284297
}
298+
if (this._runColorMapChangedListener) {
299+
window.removeEventListener(
300+
RUN_COLOR_MAP_CHANGED_EVENT,
301+
this._runColorMapChangedListener
302+
);
303+
this._runColorMapChangedListener = null;
304+
}
285305
}
286306

287307
_toggleAll() {

tensorbored/plugins/audio/tf_audio_dashboard/tf-audio-loader.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ import {getRouter} from '../../../components/tf_backend/router';
2323
import '../../../components/tf_card_heading/tf-card-heading';
2424
import '../../../components/tf_card_heading/tf-card-heading-style';
2525
import {formatDate} from '../../../components/tf_card_heading/util';
26-
import {runsColorScale} from '../../../components/tf_color_scale/colorScale';
26+
import {
27+
RUN_COLOR_MAP_CHANGED_EVENT,
28+
runsColorScale,
29+
} from '../../../components/tf_color_scale/colorScale';
2730
import '../../../components/tf_dashboard_common/tensorboard-color';
2831
import '../../../components/tf_markdown_view/tf-markdown-view';
2932

@@ -171,8 +174,11 @@ class _TfAudioLoader
171174
_stepIndex: number;
172175

173176
_attached: boolean = false;
177+
@property({type: Number})
178+
_runColorVersion = 0;
179+
private _runColorMapChangedListener: (() => void) | null = null;
174180

175-
@computed('run')
181+
@computed('run', '_runColorVersion')
176182
get _runColor(): string {
177183
var run = this.run;
178184
return runsColorScale(run);
@@ -216,10 +222,27 @@ class _TfAudioLoader
216222
}
217223

218224
override attached() {
225+
this._runColorMapChangedListener = () => {
226+
this._runColorVersion += 1;
227+
};
228+
window.addEventListener(
229+
RUN_COLOR_MAP_CHANGED_EVENT,
230+
this._runColorMapChangedListener
231+
);
219232
this._attached = true;
220233
this.reload();
221234
}
222235

236+
override detached() {
237+
if (this._runColorMapChangedListener) {
238+
window.removeEventListener(
239+
RUN_COLOR_MAP_CHANGED_EVENT,
240+
this._runColorMapChangedListener
241+
);
242+
this._runColorMapChangedListener = null;
243+
}
244+
}
245+
223246
@observe('run', 'tag')
224247
_reloadOnRunTagChange() {
225248
this.reload();

tensorbored/plugins/image/tf_image_dashboard/tf-image-loader.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ import {getRouter} from '../../../components/tf_backend/router';
2323
import '../../../components/tf_card_heading/tf-card-heading';
2424
import '../../../components/tf_card_heading/tf-card-heading-style';
2525
import {formatDate} from '../../../components/tf_card_heading/util';
26-
import {runsColorScale} from '../../../components/tf_color_scale/colorScale';
26+
import {
27+
RUN_COLOR_MAP_CHANGED_EVENT,
28+
runsColorScale,
29+
} from '../../../components/tf_color_scale/colorScale';
2730
import '../../../components/tf_dashboard_common/tensorboard-color';
2831

2932
@customElement('tf-image-loader')
@@ -232,7 +235,10 @@ class TfImageLoader extends LegacyElementMixin(PolymerElement) {
232235
type: Boolean,
233236
})
234237
_isImageLoading: boolean = false;
235-
@computed('run')
238+
@property({type: Number})
239+
_runColorVersion = 0;
240+
private _runColorMapChangedListener: (() => void) | null = null;
241+
@computed('run', '_runColorVersion')
236242
get _runColor(): string {
237243
var run = this.run;
238244
return runsColorScale(run);
@@ -284,8 +290,24 @@ class TfImageLoader extends LegacyElementMixin(PolymerElement) {
284290
return this.actualSize ? 'true' : 'false';
285291
}
286292
override attached() {
293+
this._runColorMapChangedListener = () => {
294+
this._runColorVersion += 1;
295+
};
296+
window.addEventListener(
297+
RUN_COLOR_MAP_CHANGED_EVENT,
298+
this._runColorMapChangedListener
299+
);
287300
this.reload();
288301
}
302+
override detached() {
303+
if (this._runColorMapChangedListener) {
304+
window.removeEventListener(
305+
RUN_COLOR_MAP_CHANGED_EVENT,
306+
this._runColorMapChangedListener
307+
);
308+
this._runColorMapChangedListener = null;
309+
}
310+
}
289311
@observe('run', 'tag')
290312
reload() {
291313
if (!this.isAttached) {

tensorbored/plugins/pr_curve/tf_pr_curve_dashboard/tf-pr-curve-card.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ import {RequestManager} from '../../../components/tf_backend/requestManager';
2323
import {getRouter} from '../../../components/tf_backend/router';
2424
import {addParams} from '../../../components/tf_backend/urlPathHelpers';
2525
import '../../../components/tf_card_heading/tf-card-heading';
26-
import {runsColorScale} from '../../../components/tf_color_scale/colorScale';
26+
import {
27+
RUN_COLOR_MAP_CHANGED_EVENT,
28+
runsColorScale,
29+
} from '../../../components/tf_color_scale/colorScale';
2730
import {RequestDataCallback} from '../../../components/tf_dashboard_common/data-loader-behavior';
2831
import '../../../components/tf_line_chart_data_loader/tf-line-chart-data-loader';
2932
import * as vz_chart_helpers from '../../../components/vz_chart_helpers/vz-chart-helpers';
@@ -81,7 +84,7 @@ export class TfPrCurveCard extends PolymerElement {
8184
<div class="legend-row">
8285
<div
8386
class="color-box"
84-
style="background: [[_computeRunColor(run)]];"
87+
style="background: [[_computeRunColor(run, _runColorVersion)]];"
8588
></div>
8689
[[run]] is at
8790
<span class="step-label-text">
@@ -198,6 +201,9 @@ export class TfPrCurveCard extends PolymerElement {
198201

199202
@property({type: Boolean})
200203
_attached: boolean;
204+
@property({type: Number})
205+
_runColorVersion = 0;
206+
private _runColorMapChangedListener: (() => void) | null = null;
201207

202208
@property({type: Object})
203209
_xComponentsCreationMethod = () => {
@@ -306,19 +312,37 @@ export class TfPrCurveCard extends PolymerElement {
306312
};
307313
}
308314

309-
_computeRunColor(run) {
315+
_computeRunColor(run, _) {
310316
return runsColorScale(run);
311317
}
312318

313319
connectedCallback() {
314320
super.connectedCallback();
321+
this._runColorMapChangedListener = () => {
322+
this._runColorVersion += 1;
323+
};
324+
window.addEventListener(
325+
RUN_COLOR_MAP_CHANGED_EVENT,
326+
this._runColorMapChangedListener
327+
);
315328
// Defer reloading until after we're attached, because that ensures that
316329
// the requestManager has been set from above. (Polymer is tricky
317330
// sometimes)
318331
this._attached = true;
319332
this.reload();
320333
}
321334

335+
disconnectedCallback() {
336+
if (this._runColorMapChangedListener) {
337+
window.removeEventListener(
338+
RUN_COLOR_MAP_CHANGED_EVENT,
339+
this._runColorMapChangedListener
340+
);
341+
this._runColorMapChangedListener = null;
342+
}
343+
super.disconnectedCallback();
344+
}
345+
322346
_getChartDataLoader() {
323347
// tslint:disable-next-line:no-unnecessary-type-assertion
324348
return this.shadowRoot?.querySelector('tf-line-chart-data-loader') as any; // TfLineChartDataLoader

0 commit comments

Comments
 (0)