Skip to content

Commit

Permalink
fix(Dashboard): Keep track of charts origin color schemes
Browse files Browse the repository at this point in the history
  • Loading branch information
geido committed Oct 17, 2024
1 parent 66ba4fb commit 208a929
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,7 @@ describe('Dashboard edit', () => {
// label Anthony
cy.get('[data-test-chart-name="Trends"] .line .nv-legend-symbol')
.eq(2)
.should('have.css', 'fill', 'rgb(108, 69, 146)');
.should('have.css', 'fill', 'rgb(0, 128, 246)');

// open main tab and nested tab
openTab(0, 0);
Expand All @@ -518,7 +518,7 @@ describe('Dashboard edit', () => {
'[data-test-chart-name="Top 10 California Names Timeseries"] .line .nv-legend-symbol',
)
.first()
.should('have.css', 'fill', 'rgb(108, 69, 146)');
.should('have.css', 'fill', 'rgb(0, 128, 246)');
});

it('should apply the color scheme across main tabs', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
* under the License.
*/

import { getCategoricalSchemeRegistry } from '.';
import { makeSingleton } from '../utils';
import CategoricalColorNamespace from './CategoricalColorNamespace';

export enum LabelsColorMapSource {
Dashboard,
Expand All @@ -38,7 +40,16 @@ export class LabelsColorMap {
this.source = LabelsColorMapSource.Dashboard;
}

updateColorMap(categoricalNamespace: any, colorScheme?: string) {
/**
* Wipes out the color map and updates it with the new color scheme.
*
* @param categoricalNamespace - the namespace to use for color mapping
* @param colorScheme - color scheme
*/
updateColorMap(
categoricalNamespace: CategoricalColorNamespace,
colorScheme?: string,
) {
const newColorMap = new Map();
this.colorMap.clear();
this.chartsLabelsMap.forEach((chartConfig, sliceId) => {
Expand All @@ -58,6 +69,16 @@ export class LabelsColorMap {
return this.colorMap;
}

/**
*
* Called individually by each plugin via getColor fn.
*
* @param label - the label name
* @param color - the color
* @param sliceId - the chart id
* @param colorScheme - the color scheme
*
*/
addSlice(
label: string,
color: string,
Expand All @@ -75,12 +96,34 @@ export class LabelsColorMap {
labels.push(label);
this.chartsLabelsMap.set(sliceId, {
labels,
scheme: colorScheme,
scheme: this.chartsLabelsMap.get(sliceId)?.scheme || colorScheme,
});
}
this.colorMap.set(label, color);
}

/**
* Keeps track of original colors of charts in their formData.
*
* @param sliceId - chart id
* @param colorScheme - color scheme
*/
setSliceOriginColorScheme(sliceId: number, colorScheme?: string) {
const categoricalSchemes = getCategoricalSchemeRegistry();
const fallbackColorScheme =
categoricalSchemes.getDefaultKey()?.toString() ?? 'supersetColors';

this.chartsLabelsMap.set(sliceId, {
labels: this.chartsLabelsMap.get(sliceId)?.labels || [],
scheme: colorScheme || fallbackColorScheme,
});
}

/**
* Remove a slice from the color map.
*
* @param sliceId - the chart
*/
removeSlice(sliceId: number) {
if (this.source !== LabelsColorMapSource.Dashboard) return;

Expand All @@ -96,6 +139,9 @@ export class LabelsColorMap {
this.colorMap = newColorMap;
}

/**
* Clear the shared labels color map.
*/
clear() {
this.chartsLabelsMap.clear();
this.colorMap.clear();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,29 @@ describe('LabelsColorMap', () => {
expect(getLabelsColorMap()).toBeInstanceOf(LabelsColorMap);
});

describe('.setSliceOriginColorScheme(sliceId, scheme)', () => {
it('should set slice color scheme', () => {
const labelsColorMap = getLabelsColorMap();
labelsColorMap.setSliceOriginColorScheme(1, 'testColors');
expect(labelsColorMap.chartsLabelsMap.get(1)?.scheme).toEqual(
'testColors',
);
});
it('should fallback to default color scheme if not provided', () => {
const labelsColorMap = getLabelsColorMap();
labelsColorMap.setSliceOriginColorScheme(1);
expect(labelsColorMap.chartsLabelsMap.get(1)?.scheme).toEqual(
'SUPERSET_DEFAULT',
);
});
it('should re-use existing labels when setting color scheme', () => {
const labelsColorMap = getLabelsColorMap();
labelsColorMap.addSlice('a', 'red', 1);
labelsColorMap.setSliceOriginColorScheme(1, 'testColors');
expect(labelsColorMap.chartsLabelsMap.get(1)?.labels).toEqual(['a']);
});
});

describe('.addSlice(value, color, sliceId)', () => {
it('should add to sliceLabelColorMap when first adding label', () => {
const labelsColorMap = getLabelsColorMap();
Expand Down Expand Up @@ -98,6 +121,24 @@ describe('LabelsColorMap', () => {
labelsColorMap.addSlice('a', 'red', 1);
expect(Object.fromEntries(labelsColorMap.chartsLabelsMap)).toEqual({});
});

it('should use set slice color scheme via setSliceOriginColorScheme', () => {
const labelsColorMap = getLabelsColorMap();
labelsColorMap.setSliceOriginColorScheme(1, 'testColors2');
labelsColorMap.addSlice('a', 'red', 1);
expect(labelsColorMap.chartsLabelsMap.get(1)?.scheme).toEqual(
'testColors2',
);
});

it('should use given color scheme when not set via setSliceOriginColorScheme', () => {
const labelsColorMap = getLabelsColorMap();
labelsColorMap.setSliceOriginColorScheme(2, 'testColors2');
labelsColorMap.addSlice('a', 'red', 1, 'testColors');
expect(labelsColorMap.chartsLabelsMap.get(1)?.scheme).toEqual(
'testColors',
);
});
});

describe('.remove(sliceId)', () => {
Expand Down
120 changes: 66 additions & 54 deletions superset-frontend/src/dashboard/actions/dashboardState.js
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,7 @@ export function addSliceToDashboard(id) {
dispatch(fetchDatasourceMetadata(form_data.datasource)),
]).then(() => {
dispatch(addSlice(selectedSlice));
getLabelsColorMap().setSliceOriginColorScheme(id, form_data.color_scheme);
});
};
}
Expand Down Expand Up @@ -686,62 +687,73 @@ const updateDashboardMetadata = async (id, metadata, dispatch) => {
dispatch(dashboardInfoChanged({ metadata }));
};

export const updateDashboardLabelsColor = () => async (dispatch, getState) => {
const {
dashboardInfo: { id, metadata },
} = getState();
const categoricalSchemes = getCategoricalSchemeRegistry();
const colorScheme = metadata?.color_scheme;
const colorSchemeRegistry = categoricalSchemes.get(
metadata?.color_scheme,
true,
);
const defaultScheme = categoricalSchemes.defaultKey;
const fallbackScheme = defaultScheme?.toString() || 'supersetColors';
const colorSchemeDomain = metadata?.color_scheme_domain || [];

try {
const updatedMetadata = { ...metadata };
let updatedScheme = metadata?.color_scheme;

// Color scheme does not exist anymore, fallback to default
if (colorScheme && !colorSchemeRegistry) {
updatedScheme = fallbackScheme;
updatedMetadata.color_scheme = updatedScheme;
updatedMetadata.color_scheme_domain = getColorSchemeDomain(colorScheme);

dispatch(setColorScheme(updatedScheme));
// must re-apply colors from fresh labels color map
applyColors(updatedMetadata, true);
}
export const updateDashboardLabelsColor =
charts => async (dispatch, getState) => {
const {
dashboardInfo: { id, metadata },
} = getState();
const categoricalSchemes = getCategoricalSchemeRegistry();
const sharedColorMapInstance = getLabelsColorMap();
const colorScheme = metadata?.color_scheme;
const colorSchemeRegistry = categoricalSchemes.get(
metadata?.color_scheme,
true,
);
const defaultScheme = categoricalSchemes.defaultKey;
const fallbackScheme = defaultScheme?.toString() || 'supersetColors';
const colorSchemeDomain = metadata?.color_scheme_domain || [];

try {
const updatedMetadata = { ...metadata };
let updatedScheme = metadata?.color_scheme;

// track original color scheme for each chart
charts.forEach(chart =>
sharedColorMapInstance.setSliceOriginColorScheme(
chart.id,
chart.form_data?.color_scheme,
),
);

// stored labels color map and applied might differ
// new data may appear in the map (data changes)
// or new slices may appear while changing tabs
const isMapSynced = isLabelsColorMapSynced(metadata);
if (!isMapSynced) {
// re-apply a fresh labels color map
applyColors(updatedMetadata, false, true);
// pull and store the just applied labels color map
updatedMetadata.shared_label_colors = getLabelsColorMapEntries();
}
// Color scheme does not exist anymore, fallback to default
if (colorScheme && !colorSchemeRegistry) {
updatedScheme = fallbackScheme;
updatedMetadata.color_scheme = updatedScheme;
updatedMetadata.color_scheme_domain = getColorSchemeDomain(colorScheme);

// the stored color domain registry and fresh might differ at this point
const freshColorSchemeDomain = getColorSchemeDomain(colorScheme);
const isRegistrySynced =
colorSchemeDomain.toString() === freshColorSchemeDomain.toString();
dispatch(setColorScheme(updatedScheme));
// must re-apply colors from fresh labels color map
applyColors(updatedMetadata, true);
}

if (colorScheme && !isRegistrySynced) {
updatedMetadata.color_scheme_domain = freshColorSchemeDomain;
}
// stored labels color map and applied might differ
// new data may appear in the map (data changes)
// or new slices may appear while changing tabs
// requires merging the stored and applied maps
const isMapSynced = isLabelsColorMapSynced(metadata);
if (!isMapSynced) {
// re-apply a fresh labels color map
applyColors(updatedMetadata, false, true);
// pull and store the just applied labels color map
updatedMetadata.shared_label_colors = getLabelsColorMapEntries();
}

if (
(colorScheme && (!colorSchemeRegistry || !isRegistrySynced)) ||
!isMapSynced
) {
await updateDashboardMetadata(id, updatedMetadata, dispatch);
// the stored color domain registry and fresh might differ at this point
const freshColorSchemeDomain = getColorSchemeDomain(colorScheme);
const isRegistrySynced =
colorSchemeDomain.toString() === freshColorSchemeDomain.toString();

if (colorScheme && !isRegistrySynced) {
updatedMetadata.color_scheme_domain = freshColorSchemeDomain;
}

if (
(colorScheme && (!colorSchemeRegistry || !isRegistrySynced)) ||
!isMapSynced
) {
await updateDashboardMetadata(id, updatedMetadata, dispatch);
}
} catch (error) {
console.error('Failed to update dashboard color settings:', error);
}
} catch (error) {
console.error('Failed to update dashboard color settings:', error);
}
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { pick } from 'lodash';
import Tabs from 'src/components/Tabs';
import DashboardGrid from 'src/dashboard/containers/DashboardGrid';
import {
Chart,
DashboardInfo,
DashboardLayout,
LayoutItem,
Expand Down Expand Up @@ -85,8 +86,13 @@ const DashboardContainer: FC<DashboardContainerProps> = ({ topLevelTabs }) => {
const directPathToChild = useSelector<RootState, string[]>(
state => state.dashboardState.directPathToChild,
);
const chartIds = useSelector<RootState, number[]>(state =>
Object.values(state.charts).map(chart => chart.id),
const charts = useSelector<RootState, Chart[]>(state =>
Object.values(state.charts),
);

const chartIds: number[] = useMemo(
() => charts.map(chart => chart.id),
[charts],
);

const prevTabIndexRef = useRef();
Expand Down Expand Up @@ -144,9 +150,11 @@ const DashboardContainer: FC<DashboardContainerProps> = ({ topLevelTabs }) => {
// verify freshness of color map on tab change
// and when loading for first time
setTimeout(() => {
dispatch(updateDashboardLabelsColor());
if (charts.length) {
dispatch(updateDashboardLabelsColor(charts));
}
}, 500);
}, [directPathToChild, dispatch]);
}, [directPathToChild, dispatch, charts]);

useEffect(() => {
const labelsColorMap = getLabelsColorMap();
Expand Down
10 changes: 1 addition & 9 deletions superset-frontend/src/utils/colorScheme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,18 +115,10 @@ export const applyColors = (
const colorNameSpace = getColorNamespace(metadata?.color_namespace);
const categoricalNamespace =
CategoricalColorNamespace.getNamespace(colorNameSpace);
let colorScheme = metadata?.color_scheme;
const colorScheme = metadata?.color_scheme;
const customLabelColors = metadata?.label_colors || {};
// when scheme unset, update only custom label colors
const labelsColorMap = metadata?.shared_label_colors || {};

if (!colorScheme) {
// a fallback color scheme must be set to generate shared label colors
const categoricalSchemes = getCategoricalSchemeRegistry();
colorScheme =
categoricalSchemes.getDefaultKey()?.toString() ?? 'supersetColors';
}

if (fresh) {
// reset forced colors (custom labels + labels color map)
categoricalNamespace.resetColors();
Expand Down

0 comments on commit 208a929

Please sign in to comment.