diff --git a/tensorbored/components/tf_color_scale/colorScale.ts b/tensorbored/components/tf_color_scale/colorScale.ts index 9b105263a04..242e9554a19 100644 --- a/tensorbored/components/tf_color_scale/colorScale.ts +++ b/tensorbored/components/tf_color_scale/colorScale.ts @@ -16,39 +16,46 @@ import * as d3 from 'd3'; import {BaseStore} from '../tf_backend/baseStore'; import {experimentsStore} from '../tf_backend/experimentsStore'; import {runsStore} from '../tf_backend/runsStore'; +import {standard} from './palettes'; /** - * Read the run-name → hex-color map from NgRx (exposed on window by - * RunsEffects). This is the single source of truth used by time-series. + * Read the run-name → hex-color map seeded on window by the NgRx + * RunsEffects syncPolymerRunColorMap$ effect. Returns null before the + * effect has fired for the first time. */ -function readColorMap(): Record { - const live = (window as any).__tbRunColorMap as - | Record - | undefined; - if (!live) { - throw new Error('Missing run color map on window.__tbRunColorMap'); - } - return live; +function readColorMap(): Record | null { + return ((window as any).__tbRunColorMap as Record) ?? null; } export class ColorScale { private identifiers = d3.map(); + constructor(private readonly palette: string[] = standard) {} + public setDomain(strings: string[]): this { this.identifiers = d3.map(); - // Module-level initialization runs before the NgRx bridge seeds - // window.__tbRunColorMap. During that phase the run domain is empty. - // Keep strict behavior for non-empty domains only. if (strings.length === 0) { return this; } const stored = readColorMap(); - strings.forEach((s) => { - if (stored[s] === undefined) { - throw new Error(`Missing run color for "${s}" in shared color map`); - } - this.identifiers.set(s, stored[s]); - }); + if (stored) { + strings.forEach((s, i) => { + const color = stored[s]; + if (color !== undefined) { + this.identifiers.set(s, color); + } else { + console.error(`ColorScale: run "${s}" missing from shared color map`); + this.identifiers.set(s, this.palette[i % this.palette.length]); + } + }); + } else { + // NgRx bridge has not seeded window.__tbRunColorMap yet. + // Fall back to the static palette so getColor never fails for + // runs that were passed to setDomain. + strings.forEach((s, i) => { + this.identifiers.set(s, this.palette[i % this.palette.length]); + }); + } return this; } diff --git a/tensorbored/webapp/settings/_redux/settings_selectors.ts b/tensorbored/webapp/settings/_redux/settings_selectors.ts index 82ad9e287af..c1e1b46ae9f 100644 --- a/tensorbored/webapp/settings/_redux/settings_selectors.ts +++ b/tensorbored/webapp/settings/_redux/settings_selectors.ts @@ -15,11 +15,20 @@ limitations under the License. import {createFeatureSelector, createSelector} from '@ngrx/store'; import {DataLoadState} from '../../types/data'; import {ColorPalette} from '../../util/colors'; -import {SettingsState, SETTINGS_FEATURE_KEY} from './settings_types'; +import { + SettingsState, + SETTINGS_FEATURE_KEY, + initialState, +} from './settings_types'; -const selectSettingsState = +const selectSettingsStateRaw = createFeatureSelector(SETTINGS_FEATURE_KEY); +const selectSettingsState = createSelector( + selectSettingsStateRaw, + (state): SettingsState => state ?? initialState +); + export const getSettingsLoadState = createSelector( selectSettingsState, (state: SettingsState): DataLoadState => {