mirror of https://github.com/apache/superset.git
feat: improve color consistency (save all labels) (#19038)
This commit is contained in:
parent
e1d0b83885
commit
dc575080d7
|
@ -108,3 +108,5 @@ release.json
|
||||||
messages.mo
|
messages.mo
|
||||||
|
|
||||||
docker/requirements-local.txt
|
docker/requirements-local.txt
|
||||||
|
|
||||||
|
cache/
|
||||||
|
|
|
@ -136,6 +136,7 @@
|
||||||
"rison": "^0.1.1",
|
"rison": "^0.1.1",
|
||||||
"scroll-into-view-if-needed": "^2.2.28",
|
"scroll-into-view-if-needed": "^2.2.28",
|
||||||
"shortid": "^2.2.6",
|
"shortid": "^2.2.6",
|
||||||
|
"tinycolor2": "^1.4.2",
|
||||||
"urijs": "^1.19.8",
|
"urijs": "^1.19.8",
|
||||||
"use-immer": "^0.6.0",
|
"use-immer": "^0.6.0",
|
||||||
"use-query-params": "^1.1.9",
|
"use-query-params": "^1.1.9",
|
||||||
|
@ -201,6 +202,7 @@
|
||||||
"@types/rison": "0.0.6",
|
"@types/rison": "0.0.6",
|
||||||
"@types/shortid": "^0.0.29",
|
"@types/shortid": "^0.0.29",
|
||||||
"@types/sinon": "^9.0.5",
|
"@types/sinon": "^9.0.5",
|
||||||
|
"@types/tinycolor2": "^1.4.3",
|
||||||
"@types/yargs": "12 - 15",
|
"@types/yargs": "12 - 15",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.3.0",
|
"@typescript-eslint/eslint-plugin": "^5.3.0",
|
||||||
"@typescript-eslint/parser": "^5.3.0",
|
"@typescript-eslint/parser": "^5.3.0",
|
||||||
|
@ -22525,6 +22527,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/@types/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz",
|
||||||
"integrity": "sha512-AQ6zewa0ucLJvtUi5HsErbOFKAcQfRLt9zFLlUOvcXBy2G36a+ZDpCHSGdzJVUD8aNURtIjh9aSjCStNMRCcRQ=="
|
"integrity": "sha512-AQ6zewa0ucLJvtUi5HsErbOFKAcQfRLt9zFLlUOvcXBy2G36a+ZDpCHSGdzJVUD8aNURtIjh9aSjCStNMRCcRQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/tinycolor2": {
|
||||||
|
"version": "1.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/tinycolor2/-/tinycolor2-1.4.3.tgz",
|
||||||
|
"integrity": "sha512-Kf1w9NE5HEgGxCRyIcRXR/ZYtDv0V8FVPtYHwLxl0O+maGX0erE77pQlD0gpP+/KByMZ87mOA79SjifhSB3PjQ=="
|
||||||
|
},
|
||||||
"node_modules/@types/uglify-js": {
|
"node_modules/@types/uglify-js": {
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.0.4.tgz",
|
||||||
|
@ -53688,9 +53695,9 @@
|
||||||
"integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
|
"integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
|
||||||
},
|
},
|
||||||
"node_modules/tinycolor2": {
|
"node_modules/tinycolor2": {
|
||||||
"version": "1.4.1",
|
"version": "1.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.2.tgz",
|
||||||
"integrity": "sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g=",
|
"integrity": "sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "*"
|
"node": "*"
|
||||||
}
|
}
|
||||||
|
@ -58702,6 +58709,7 @@
|
||||||
"@types/prop-types": "^15.7.2",
|
"@types/prop-types": "^15.7.2",
|
||||||
"@types/rison": "0.0.6",
|
"@types/rison": "0.0.6",
|
||||||
"@types/seedrandom": "^2.4.28",
|
"@types/seedrandom": "^2.4.28",
|
||||||
|
"@types/tinycolor2": "^1.4.3",
|
||||||
"@vx/responsive": "^0.0.199",
|
"@vx/responsive": "^0.0.199",
|
||||||
"csstype": "^2.6.4",
|
"csstype": "^2.6.4",
|
||||||
"d3-format": "^1.3.2",
|
"d3-format": "^1.3.2",
|
||||||
|
@ -75807,6 +75815,7 @@
|
||||||
"@types/prop-types": "^15.7.2",
|
"@types/prop-types": "^15.7.2",
|
||||||
"@types/rison": "0.0.6",
|
"@types/rison": "0.0.6",
|
||||||
"@types/seedrandom": "^2.4.28",
|
"@types/seedrandom": "^2.4.28",
|
||||||
|
"@types/tinycolor2": "^1.4.3",
|
||||||
"@vx/responsive": "^0.0.199",
|
"@vx/responsive": "^0.0.199",
|
||||||
"csstype": "^2.6.4",
|
"csstype": "^2.6.4",
|
||||||
"d3-format": "^1.3.2",
|
"d3-format": "^1.3.2",
|
||||||
|
@ -77790,6 +77799,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/@types/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz",
|
||||||
"integrity": "sha512-AQ6zewa0ucLJvtUi5HsErbOFKAcQfRLt9zFLlUOvcXBy2G36a+ZDpCHSGdzJVUD8aNURtIjh9aSjCStNMRCcRQ=="
|
"integrity": "sha512-AQ6zewa0ucLJvtUi5HsErbOFKAcQfRLt9zFLlUOvcXBy2G36a+ZDpCHSGdzJVUD8aNURtIjh9aSjCStNMRCcRQ=="
|
||||||
},
|
},
|
||||||
|
"@types/tinycolor2": {
|
||||||
|
"version": "1.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/tinycolor2/-/tinycolor2-1.4.3.tgz",
|
||||||
|
"integrity": "sha512-Kf1w9NE5HEgGxCRyIcRXR/ZYtDv0V8FVPtYHwLxl0O+maGX0erE77pQlD0gpP+/KByMZ87mOA79SjifhSB3PjQ=="
|
||||||
|
},
|
||||||
"@types/uglify-js": {
|
"@types/uglify-js": {
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.0.4.tgz",
|
||||||
|
@ -102080,9 +102094,9 @@
|
||||||
"integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
|
"integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
|
||||||
},
|
},
|
||||||
"tinycolor2": {
|
"tinycolor2": {
|
||||||
"version": "1.4.1",
|
"version": "1.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.2.tgz",
|
||||||
"integrity": "sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g="
|
"integrity": "sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA=="
|
||||||
},
|
},
|
||||||
"tinyqueue": {
|
"tinyqueue": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
|
|
|
@ -196,6 +196,7 @@
|
||||||
"rison": "^0.1.1",
|
"rison": "^0.1.1",
|
||||||
"scroll-into-view-if-needed": "^2.2.28",
|
"scroll-into-view-if-needed": "^2.2.28",
|
||||||
"shortid": "^2.2.6",
|
"shortid": "^2.2.6",
|
||||||
|
"tinycolor2": "^1.4.2",
|
||||||
"urijs": "^1.19.8",
|
"urijs": "^1.19.8",
|
||||||
"use-immer": "^0.6.0",
|
"use-immer": "^0.6.0",
|
||||||
"use-query-params": "^1.1.9",
|
"use-query-params": "^1.1.9",
|
||||||
|
@ -261,6 +262,7 @@
|
||||||
"@types/rison": "0.0.6",
|
"@types/rison": "0.0.6",
|
||||||
"@types/shortid": "^0.0.29",
|
"@types/shortid": "^0.0.29",
|
||||||
"@types/sinon": "^9.0.5",
|
"@types/sinon": "^9.0.5",
|
||||||
|
"@types/tinycolor2": "^1.4.3",
|
||||||
"@types/yargs": "12 - 15",
|
"@types/yargs": "12 - 15",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.3.0",
|
"@typescript-eslint/eslint-plugin": "^5.3.0",
|
||||||
"@typescript-eslint/parser": "^5.3.0",
|
"@typescript-eslint/parser": "^5.3.0",
|
||||||
|
|
|
@ -205,6 +205,9 @@ const linear_color_scheme: SharedControlConfig<'ColorSchemeControl'> = {
|
||||||
renderTrigger: true,
|
renderTrigger: true,
|
||||||
schemes: () => sequentialSchemeRegistry.getMap(),
|
schemes: () => sequentialSchemeRegistry.getMap(),
|
||||||
isLinear: true,
|
isLinear: true,
|
||||||
|
mapStateToProps: state => ({
|
||||||
|
dashboardId: state?.form_data?.dashboardId,
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
const secondary_metric: SharedControlConfig<'MetricsControl'> = {
|
const secondary_metric: SharedControlConfig<'MetricsControl'> = {
|
||||||
|
|
|
@ -42,6 +42,7 @@
|
||||||
"@types/math-expression-evaluator": "^1.2.1",
|
"@types/math-expression-evaluator": "^1.2.1",
|
||||||
"@types/rison": "0.0.6",
|
"@types/rison": "0.0.6",
|
||||||
"@types/seedrandom": "^2.4.28",
|
"@types/seedrandom": "^2.4.28",
|
||||||
|
"@types/tinycolor2": "^1.4.3",
|
||||||
"@types/fetch-mock": "^7.3.3",
|
"@types/fetch-mock": "^7.3.3",
|
||||||
"@types/enzyme": "^3.10.5",
|
"@types/enzyme": "^3.10.5",
|
||||||
"@types/prop-types": "^15.7.2",
|
"@types/prop-types": "^15.7.2",
|
||||||
|
|
|
@ -22,12 +22,12 @@ import { scaleOrdinal, ScaleOrdinal } from 'd3-scale';
|
||||||
import { ExtensibleFunction } from '../models';
|
import { ExtensibleFunction } from '../models';
|
||||||
import { ColorsLookup } from './types';
|
import { ColorsLookup } from './types';
|
||||||
import stringifyAndTrim from './stringifyAndTrim';
|
import stringifyAndTrim from './stringifyAndTrim';
|
||||||
|
import getSharedLabelColor from './SharedLabelColorSingleton';
|
||||||
|
|
||||||
// Use type augmentation to correct the fact that
|
// Use type augmentation to correct the fact that
|
||||||
// an instance of CategoricalScale is also a function
|
// an instance of CategoricalScale is also a function
|
||||||
|
|
||||||
interface CategoricalColorScale {
|
interface CategoricalColorScale {
|
||||||
(x: { toString(): string }): string;
|
(x: { toString(): string }, y?: number): string;
|
||||||
}
|
}
|
||||||
|
|
||||||
class CategoricalColorScale extends ExtensibleFunction {
|
class CategoricalColorScale extends ExtensibleFunction {
|
||||||
|
@ -46,7 +46,7 @@ class CategoricalColorScale extends ExtensibleFunction {
|
||||||
* (usually CategoricalColorNamespace) and supersede this.forcedColors
|
* (usually CategoricalColorNamespace) and supersede this.forcedColors
|
||||||
*/
|
*/
|
||||||
constructor(colors: string[], parentForcedColors?: ColorsLookup) {
|
constructor(colors: string[], parentForcedColors?: ColorsLookup) {
|
||||||
super((value: string) => this.getColor(value));
|
super((value: string, sliceId?: number) => this.getColor(value, sliceId));
|
||||||
|
|
||||||
this.colors = colors;
|
this.colors = colors;
|
||||||
this.scale = scaleOrdinal<{ toString(): string }, string>();
|
this.scale = scaleOrdinal<{ toString(): string }, string>();
|
||||||
|
@ -55,20 +55,27 @@ class CategoricalColorScale extends ExtensibleFunction {
|
||||||
this.forcedColors = {};
|
this.forcedColors = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
getColor(value?: string) {
|
getColor(value?: string, sliceId?: number) {
|
||||||
const cleanedValue = stringifyAndTrim(value);
|
const cleanedValue = stringifyAndTrim(value);
|
||||||
|
const sharedLabelColor = getSharedLabelColor();
|
||||||
|
|
||||||
const parentColor =
|
const parentColor =
|
||||||
this.parentForcedColors && this.parentForcedColors[cleanedValue];
|
this.parentForcedColors && this.parentForcedColors[cleanedValue];
|
||||||
if (parentColor) {
|
if (parentColor) {
|
||||||
|
sharedLabelColor.addSlice(cleanedValue, parentColor, sliceId);
|
||||||
return parentColor;
|
return parentColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
const forcedColor = this.forcedColors[cleanedValue];
|
const forcedColor = this.forcedColors[cleanedValue];
|
||||||
if (forcedColor) {
|
if (forcedColor) {
|
||||||
|
sharedLabelColor.addSlice(cleanedValue, forcedColor, sliceId);
|
||||||
return forcedColor;
|
return forcedColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.scale(cleanedValue);
|
const color = this.scale(cleanedValue);
|
||||||
|
sharedLabelColor.addSlice(cleanedValue, color, sliceId);
|
||||||
|
|
||||||
|
return color;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,130 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import tinycolor from 'tinycolor2';
|
||||||
|
import { CategoricalColorNamespace } from '.';
|
||||||
|
import makeSingleton from '../utils/makeSingleton';
|
||||||
|
|
||||||
|
export class SharedLabelColor {
|
||||||
|
sliceLabelColorMap: Record<number, Record<string, string | undefined>>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// { sliceId1: { label1: color1 }, sliceId2: { label2: color2 } }
|
||||||
|
this.sliceLabelColorMap = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
getColorMap(
|
||||||
|
colorNamespace?: string,
|
||||||
|
colorScheme?: string,
|
||||||
|
updateColorScheme?: boolean,
|
||||||
|
) {
|
||||||
|
if (colorScheme) {
|
||||||
|
const categoricalNamespace =
|
||||||
|
CategoricalColorNamespace.getNamespace(colorNamespace);
|
||||||
|
const colors = categoricalNamespace.getScale(colorScheme).range();
|
||||||
|
const sharedLabels = this.getSharedLabels();
|
||||||
|
const generatedColors: tinycolor.Instance[] = [];
|
||||||
|
let sharedLabelMap;
|
||||||
|
|
||||||
|
if (sharedLabels.length) {
|
||||||
|
const multiple = Math.ceil(sharedLabels.length / colors.length);
|
||||||
|
const ext = 5;
|
||||||
|
const analogousColors = colors.map(color => {
|
||||||
|
const result = tinycolor(color).analogous(multiple + ext);
|
||||||
|
return result.slice(ext);
|
||||||
|
});
|
||||||
|
|
||||||
|
// [[A, AA, AAA], [B, BB, BBB]] => [A, B, AA, BB, AAA, BBB]
|
||||||
|
while (analogousColors[analogousColors.length - 1]?.length) {
|
||||||
|
analogousColors.forEach(colors =>
|
||||||
|
generatedColors.push(colors.shift() as tinycolor.Instance),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
sharedLabelMap = sharedLabels.reduce(
|
||||||
|
(res, label, index) => ({
|
||||||
|
...res,
|
||||||
|
[label.toString()]: generatedColors[index]?.toHexString(),
|
||||||
|
}),
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const labelMap = Object.keys(this.sliceLabelColorMap).reduce(
|
||||||
|
(res, sliceId) => {
|
||||||
|
const colorScale = categoricalNamespace.getScale(colorScheme);
|
||||||
|
return {
|
||||||
|
...res,
|
||||||
|
...Object.keys(this.sliceLabelColorMap[sliceId]).reduce(
|
||||||
|
(res, label) => ({
|
||||||
|
...res,
|
||||||
|
[label]: updateColorScheme
|
||||||
|
? colorScale(label)
|
||||||
|
: this.sliceLabelColorMap[sliceId][label],
|
||||||
|
}),
|
||||||
|
{},
|
||||||
|
),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...labelMap,
|
||||||
|
...sharedLabelMap,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
addSlice(label: string, color: string, sliceId?: number) {
|
||||||
|
if (!sliceId) return;
|
||||||
|
this.sliceLabelColorMap[sliceId] = {
|
||||||
|
...this.sliceLabelColorMap[sliceId],
|
||||||
|
[label]: color,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
removeSlice(sliceId: number) {
|
||||||
|
delete this.sliceLabelColorMap[sliceId];
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this.sliceLabelColorMap = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
getSharedLabels() {
|
||||||
|
const tempLabels = new Set<string>();
|
||||||
|
const result = new Set<string>();
|
||||||
|
Object.keys(this.sliceLabelColorMap).forEach(sliceId => {
|
||||||
|
const colorMap = this.sliceLabelColorMap[sliceId];
|
||||||
|
Object.keys(colorMap).forEach(label => {
|
||||||
|
if (tempLabels.has(label) && !result.has(label)) {
|
||||||
|
result.add(label);
|
||||||
|
} else {
|
||||||
|
tempLabels.add(label);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return [...result];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getInstance = makeSingleton(SharedLabelColor);
|
||||||
|
|
||||||
|
export default getInstance;
|
|
@ -32,5 +32,9 @@ export * from './SequentialScheme';
|
||||||
export { default as ColorSchemeRegistry } from './ColorSchemeRegistry';
|
export { default as ColorSchemeRegistry } from './ColorSchemeRegistry';
|
||||||
export * from './colorSchemes';
|
export * from './colorSchemes';
|
||||||
export * from './utils';
|
export * from './utils';
|
||||||
|
export {
|
||||||
|
default as getSharedLabelColor,
|
||||||
|
SharedLabelColor,
|
||||||
|
} from './SharedLabelColorSingleton';
|
||||||
|
|
||||||
export const BRAND_COLOR = '#00A699';
|
export const BRAND_COLOR = '#00A699';
|
||||||
|
|
|
@ -0,0 +1,110 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
CategoricalScheme,
|
||||||
|
getCategoricalSchemeRegistry,
|
||||||
|
getSharedLabelColor,
|
||||||
|
SharedLabelColor,
|
||||||
|
} from '@superset-ui/core';
|
||||||
|
|
||||||
|
describe('SharedLabelColor', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
getCategoricalSchemeRegistry()
|
||||||
|
.registerValue(
|
||||||
|
'testColors',
|
||||||
|
new CategoricalScheme({
|
||||||
|
id: 'testColors',
|
||||||
|
colors: ['red', 'green', 'blue'],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.registerValue(
|
||||||
|
'testColors2',
|
||||||
|
new CategoricalScheme({
|
||||||
|
id: 'testColors2',
|
||||||
|
colors: ['yellow', 'green', 'blue'],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
getSharedLabelColor().clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has default value out-of-the-box', () => {
|
||||||
|
expect(getSharedLabelColor()).toBeInstanceOf(SharedLabelColor);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('.addSlice(value, color, sliceId)', () => {
|
||||||
|
it('should add to valueSliceMap when first adding label', () => {
|
||||||
|
const sharedLabelColor = getSharedLabelColor();
|
||||||
|
sharedLabelColor.addSlice('a', 'red', 1);
|
||||||
|
expect(sharedLabelColor.sliceLabelColorMap).toHaveProperty('1', {
|
||||||
|
a: 'red',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('do nothing when sliceId is undefined', () => {
|
||||||
|
const sharedLabelColor = getSharedLabelColor();
|
||||||
|
sharedLabelColor.addSlice('a', 'red');
|
||||||
|
expect(sharedLabelColor.sliceLabelColorMap).toEqual({});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('.remove(sliceId)', () => {
|
||||||
|
it('should remove sliceId', () => {
|
||||||
|
const sharedLabelColor = getSharedLabelColor();
|
||||||
|
sharedLabelColor.addSlice('a', 'red', 1);
|
||||||
|
sharedLabelColor.removeSlice(1);
|
||||||
|
expect(sharedLabelColor.sliceLabelColorMap).toEqual({});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('.getColorMap(namespace, scheme, updateColorScheme)', () => {
|
||||||
|
it('return undefined when scheme is undefined', () => {
|
||||||
|
const sharedLabelColor = getSharedLabelColor();
|
||||||
|
const colorMap = sharedLabelColor.getColorMap();
|
||||||
|
expect(colorMap).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('return undefined value if pass updateColorScheme', () => {
|
||||||
|
const sharedLabelColor = getSharedLabelColor();
|
||||||
|
sharedLabelColor.addSlice('a', 'red', 1);
|
||||||
|
sharedLabelColor.addSlice('b', 'blue', 2);
|
||||||
|
const colorMap = sharedLabelColor.getColorMap('', 'testColors2', true);
|
||||||
|
expect(colorMap).toEqual({ a: 'yellow', b: 'yellow' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('return color value if not pass updateColorScheme', () => {
|
||||||
|
const sharedLabelColor = getSharedLabelColor();
|
||||||
|
sharedLabelColor.addSlice('a', 'red', 1);
|
||||||
|
sharedLabelColor.addSlice('b', 'blue', 2);
|
||||||
|
const colorMap = sharedLabelColor.getColorMap('', 'testColors');
|
||||||
|
expect(colorMap).toEqual({ a: 'red', b: 'blue' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('return color value if shared label exit', () => {
|
||||||
|
const sharedLabelColor = getSharedLabelColor();
|
||||||
|
sharedLabelColor.addSlice('a', 'red', 1);
|
||||||
|
sharedLabelColor.addSlice('a', 'blue', 2);
|
||||||
|
const colorMap = sharedLabelColor.getColorMap('', 'testColors');
|
||||||
|
expect(colorMap).not.toEqual({});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -36,7 +36,7 @@ const propTypes = {
|
||||||
};
|
};
|
||||||
|
|
||||||
function Chord(element, props) {
|
function Chord(element, props) {
|
||||||
const { data, width, height, numberFormat, colorScheme } = props;
|
const { data, width, height, numberFormat, colorScheme, sliceId } = props;
|
||||||
|
|
||||||
element.innerHTML = '';
|
element.innerHTML = '';
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ function Chord(element, props) {
|
||||||
.append('path')
|
.append('path')
|
||||||
.attr('id', (d, i) => `group${i}`)
|
.attr('id', (d, i) => `group${i}`)
|
||||||
.attr('d', arc)
|
.attr('d', arc)
|
||||||
.style('fill', (d, i) => colorFn(nodes[i]));
|
.style('fill', (d, i) => colorFn(nodes[i], sliceId));
|
||||||
|
|
||||||
// Add a text label.
|
// Add a text label.
|
||||||
const groupText = group.append('text').attr('x', 6).attr('dy', 15);
|
const groupText = group.append('text').attr('x', 6).attr('dy', 15);
|
||||||
|
@ -121,7 +121,7 @@ function Chord(element, props) {
|
||||||
.on('mouseover', d => {
|
.on('mouseover', d => {
|
||||||
chord.classed('fade', p => p !== d);
|
chord.classed('fade', p => p !== d);
|
||||||
})
|
})
|
||||||
.style('fill', d => colorFn(nodes[d.source.index]))
|
.style('fill', d => colorFn(nodes[d.source.index], sliceId))
|
||||||
.attr('d', path);
|
.attr('d', path);
|
||||||
|
|
||||||
// Add an elaborate mouseover title for each chord.
|
// Add an elaborate mouseover title for each chord.
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
*/
|
*/
|
||||||
export default function transformProps(chartProps) {
|
export default function transformProps(chartProps) {
|
||||||
const { width, height, formData, queriesData } = chartProps;
|
const { width, height, formData, queriesData } = chartProps;
|
||||||
const { yAxisFormat, colorScheme } = formData;
|
const { yAxisFormat, colorScheme, sliceId } = formData;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
colorScheme,
|
colorScheme,
|
||||||
|
@ -26,5 +26,6 @@ export default function transformProps(chartProps) {
|
||||||
height,
|
height,
|
||||||
numberFormat: yAxisFormat,
|
numberFormat: yAxisFormat,
|
||||||
width,
|
width,
|
||||||
|
sliceId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ import { extent as d3Extent } from 'd3-array';
|
||||||
import {
|
import {
|
||||||
getNumberFormatter,
|
getNumberFormatter,
|
||||||
getSequentialSchemeRegistry,
|
getSequentialSchemeRegistry,
|
||||||
|
CategoricalColorNamespace,
|
||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
import countries, { countryOptions } from './countries';
|
import countries, { countryOptions } from './countries';
|
||||||
import './CountryMap.css';
|
import './CountryMap.css';
|
||||||
|
@ -45,17 +46,29 @@ const propTypes = {
|
||||||
const maps = {};
|
const maps = {};
|
||||||
|
|
||||||
function CountryMap(element, props) {
|
function CountryMap(element, props) {
|
||||||
const { data, width, height, country, linearColorScheme, numberFormat } =
|
const {
|
||||||
props;
|
data,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
country,
|
||||||
|
linearColorScheme,
|
||||||
|
numberFormat,
|
||||||
|
colorScheme,
|
||||||
|
sliceId,
|
||||||
|
} = props;
|
||||||
|
|
||||||
const container = element;
|
const container = element;
|
||||||
const format = getNumberFormatter(numberFormat);
|
const format = getNumberFormatter(numberFormat);
|
||||||
const colorScale = getSequentialSchemeRegistry()
|
const linearColorScale = getSequentialSchemeRegistry()
|
||||||
.get(linearColorScheme)
|
.get(linearColorScheme)
|
||||||
.createLinearScale(d3Extent(data, v => v.metric));
|
.createLinearScale(d3Extent(data, v => v.metric));
|
||||||
|
const colorScale = CategoricalColorNamespace.getScale(colorScheme);
|
||||||
|
|
||||||
const colorMap = {};
|
const colorMap = {};
|
||||||
data.forEach(d => {
|
data.forEach(d => {
|
||||||
colorMap[d.country_id] = colorScale(d.metric);
|
colorMap[d.country_id] = colorScheme
|
||||||
|
? colorScale(d.country_id, sliceId)
|
||||||
|
: linearColorScale(d.metric);
|
||||||
});
|
});
|
||||||
const colorFn = d => colorMap[d.properties.ISO] || 'none';
|
const colorFn = d => colorMap[d.properties.ISO] || 'none';
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,13 @@
|
||||||
*/
|
*/
|
||||||
export default function transformProps(chartProps) {
|
export default function transformProps(chartProps) {
|
||||||
const { width, height, formData, queriesData } = chartProps;
|
const { width, height, formData, queriesData } = chartProps;
|
||||||
const { linearColorScheme, numberFormat, selectCountry } = formData;
|
const {
|
||||||
|
linearColorScheme,
|
||||||
|
numberFormat,
|
||||||
|
selectCountry,
|
||||||
|
colorScheme,
|
||||||
|
sliceId,
|
||||||
|
} = formData;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
width,
|
width,
|
||||||
|
@ -27,5 +33,7 @@ export default function transformProps(chartProps) {
|
||||||
country: selectCountry ? String(selectCountry).toLowerCase() : null,
|
country: selectCountry ? String(selectCountry).toLowerCase() : null,
|
||||||
linearColorScheme,
|
linearColorScheme,
|
||||||
numberFormat,
|
numberFormat,
|
||||||
|
colorScheme,
|
||||||
|
sliceId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,13 +71,14 @@ class CustomHistogram extends React.PureComponent {
|
||||||
xAxisLabel,
|
xAxisLabel,
|
||||||
yAxisLabel,
|
yAxisLabel,
|
||||||
showLegend,
|
showLegend,
|
||||||
|
sliceId,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const colorFn = CategoricalColorNamespace.getScale(colorScheme);
|
const colorFn = CategoricalColorNamespace.getScale(colorScheme);
|
||||||
const keys = data.map(d => d.key);
|
const keys = data.map(d => d.key);
|
||||||
const colorScale = scaleOrdinal({
|
const colorScale = scaleOrdinal({
|
||||||
domain: keys,
|
domain: keys,
|
||||||
range: keys.map(x => colorFn(x)),
|
range: keys.map(x => colorFn(x, sliceId)),
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -27,6 +27,7 @@ export default function transformProps(chartProps) {
|
||||||
xAxisLabel,
|
xAxisLabel,
|
||||||
yAxisLabel,
|
yAxisLabel,
|
||||||
showLegend,
|
showLegend,
|
||||||
|
sliceId,
|
||||||
} = formData;
|
} = formData;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -41,5 +42,6 @@ export default function transformProps(chartProps) {
|
||||||
xAxisLabel,
|
xAxisLabel,
|
||||||
yAxisLabel,
|
yAxisLabel,
|
||||||
showLegend,
|
showLegend,
|
||||||
|
sliceId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,6 +119,7 @@ function Icicle(element, props) {
|
||||||
partitionThreshold,
|
partitionThreshold,
|
||||||
useRichTooltip,
|
useRichTooltip,
|
||||||
timeSeriesOption = 'not_time',
|
timeSeriesOption = 'not_time',
|
||||||
|
sliceId,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const div = d3.select(element);
|
const div = d3.select(element);
|
||||||
|
@ -385,7 +386,7 @@ function Icicle(element, props) {
|
||||||
|
|
||||||
// Apply color scheme
|
// Apply color scheme
|
||||||
g.selectAll('rect').style('fill', d => {
|
g.selectAll('rect').style('fill', d => {
|
||||||
d.color = colorFn(d.name);
|
d.color = colorFn(d.name, sliceId);
|
||||||
|
|
||||||
return d.color;
|
return d.color;
|
||||||
});
|
});
|
||||||
|
|
|
@ -30,6 +30,7 @@ export default function transformProps(chartProps) {
|
||||||
partitionThreshold,
|
partitionThreshold,
|
||||||
richTooltip,
|
richTooltip,
|
||||||
timeSeriesOption,
|
timeSeriesOption,
|
||||||
|
sliceId,
|
||||||
} = formData;
|
} = formData;
|
||||||
const { verboseMap } = datasource;
|
const { verboseMap } = datasource;
|
||||||
|
|
||||||
|
@ -48,5 +49,6 @@ export default function transformProps(chartProps) {
|
||||||
timeSeriesOption,
|
timeSeriesOption,
|
||||||
useLogScale: logScale,
|
useLogScale: logScale,
|
||||||
useRichTooltip: richTooltip,
|
useRichTooltip: richTooltip,
|
||||||
|
sliceId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,6 +76,7 @@ function Rose(element, props) {
|
||||||
numberFormat,
|
numberFormat,
|
||||||
useRichTooltip,
|
useRichTooltip,
|
||||||
useAreaProportions,
|
useAreaProportions,
|
||||||
|
sliceId,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const div = d3.select(element);
|
const div = d3.select(element);
|
||||||
|
@ -120,10 +121,10 @@ function Rose(element, props) {
|
||||||
.map(v => ({
|
.map(v => ({
|
||||||
key: v.name,
|
key: v.name,
|
||||||
value: v.value,
|
value: v.value,
|
||||||
color: colorFn(v.name),
|
color: colorFn(v.name, sliceId),
|
||||||
highlight: v.id === d.arcId,
|
highlight: v.id === d.arcId,
|
||||||
}))
|
}))
|
||||||
: [{ key: d.name, value: d.val, color: colorFn(d.name) }];
|
: [{ key: d.name, value: d.val, color: colorFn(d.name, sliceId) }];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
key: 'Date',
|
key: 'Date',
|
||||||
|
@ -132,7 +133,7 @@ function Rose(element, props) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
legend.width(width).color(d => colorFn(d.key));
|
legend.width(width).color(d => colorFn(d.key, sliceId));
|
||||||
legendWrap.datum(legendData(datum)).call(legend);
|
legendWrap.datum(legendData(datum)).call(legend);
|
||||||
|
|
||||||
tooltip.headerFormatter(timeFormat).valueFormatter(format);
|
tooltip.headerFormatter(timeFormat).valueFormatter(format);
|
||||||
|
@ -378,7 +379,7 @@ function Rose(element, props) {
|
||||||
const arcs = ae
|
const arcs = ae
|
||||||
.append('path')
|
.append('path')
|
||||||
.attr('class', 'arc')
|
.attr('class', 'arc')
|
||||||
.attr('fill', d => colorFn(d.name))
|
.attr('fill', d => colorFn(d.name, sliceId))
|
||||||
.attr('d', arc);
|
.attr('d', arc);
|
||||||
|
|
||||||
function mousemove() {
|
function mousemove() {
|
||||||
|
|
|
@ -24,6 +24,7 @@ export default function transformProps(chartProps) {
|
||||||
numberFormat,
|
numberFormat,
|
||||||
richTooltip,
|
richTooltip,
|
||||||
roseAreaProportion,
|
roseAreaProportion,
|
||||||
|
sliceId,
|
||||||
} = formData;
|
} = formData;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -35,5 +36,6 @@ export default function transformProps(chartProps) {
|
||||||
numberFormat,
|
numberFormat,
|
||||||
useAreaProportions: roseAreaProportion,
|
useAreaProportions: roseAreaProportion,
|
||||||
useRichTooltip: richTooltip,
|
useRichTooltip: richTooltip,
|
||||||
|
sliceId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,7 +84,7 @@ function computeGraph(links) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function SankeyLoop(element, props) {
|
function SankeyLoop(element, props) {
|
||||||
const { data, width, height, colorScheme } = props;
|
const { data, width, height, colorScheme, sliceId } = props;
|
||||||
const color = CategoricalColorNamespace.getScale(colorScheme);
|
const color = CategoricalColorNamespace.getScale(colorScheme);
|
||||||
const margin = { ...defaultMargin, ...props.margin };
|
const margin = { ...defaultMargin, ...props.margin };
|
||||||
const innerWidth = width - margin.left - margin.right;
|
const innerWidth = width - margin.left - margin.right;
|
||||||
|
@ -109,7 +109,7 @@ function SankeyLoop(element, props) {
|
||||||
value / sValue,
|
value / sValue,
|
||||||
)})`,
|
)})`,
|
||||||
)
|
)
|
||||||
.linkColor(d => color(d.source.name));
|
.linkColor(d => color(d.source.name, sliceId));
|
||||||
|
|
||||||
const div = select(element);
|
const div = select(element);
|
||||||
div.selectAll('*').remove();
|
div.selectAll('*').remove();
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
*/
|
*/
|
||||||
export default function transformProps(chartProps) {
|
export default function transformProps(chartProps) {
|
||||||
const { width, height, formData, queriesData, margin } = chartProps;
|
const { width, height, formData, queriesData, margin } = chartProps;
|
||||||
const { colorScheme } = formData;
|
const { colorScheme, sliceId } = formData;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
width,
|
width,
|
||||||
|
@ -26,5 +26,6 @@ export default function transformProps(chartProps) {
|
||||||
data: queriesData[0].data,
|
data: queriesData[0].data,
|
||||||
colorScheme,
|
colorScheme,
|
||||||
margin,
|
margin,
|
||||||
|
sliceId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ const propTypes = {
|
||||||
const formatNumber = getNumberFormatter(NumberFormats.FLOAT);
|
const formatNumber = getNumberFormatter(NumberFormats.FLOAT);
|
||||||
|
|
||||||
function Sankey(element, props) {
|
function Sankey(element, props) {
|
||||||
const { data, width, height, colorScheme } = props;
|
const { data, width, height, colorScheme, sliceId } = props;
|
||||||
const div = d3.select(element);
|
const div = d3.select(element);
|
||||||
div.classed(`superset-legacy-chart-sankey`, true);
|
div.classed(`superset-legacy-chart-sankey`, true);
|
||||||
const margin = {
|
const margin = {
|
||||||
|
@ -219,7 +219,7 @@ function Sankey(element, props) {
|
||||||
.attr('width', sankey.nodeWidth())
|
.attr('width', sankey.nodeWidth())
|
||||||
.style('fill', d => {
|
.style('fill', d => {
|
||||||
const name = d.name || 'N/A';
|
const name = d.name || 'N/A';
|
||||||
d.color = colorFn(name.replace(/ .*/, ''));
|
d.color = colorFn(name, sliceId);
|
||||||
|
|
||||||
return d.color;
|
return d.color;
|
||||||
})
|
})
|
||||||
|
|
|
@ -20,7 +20,7 @@ import { getLabelFontSize } from './utils';
|
||||||
|
|
||||||
export default function transformProps(chartProps) {
|
export default function transformProps(chartProps) {
|
||||||
const { width, height, formData, queriesData } = chartProps;
|
const { width, height, formData, queriesData } = chartProps;
|
||||||
const { colorScheme } = formData;
|
const { colorScheme, sliceId } = formData;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
width,
|
width,
|
||||||
|
@ -28,5 +28,6 @@ export default function transformProps(chartProps) {
|
||||||
data: queriesData[0].data,
|
data: queriesData[0].data,
|
||||||
colorScheme,
|
colorScheme,
|
||||||
fontSize: getLabelFontSize(width),
|
fontSize: getLabelFontSize(width),
|
||||||
|
sliceId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -170,6 +170,7 @@ function Sunburst(element, props) {
|
||||||
linearColorScheme,
|
linearColorScheme,
|
||||||
metrics,
|
metrics,
|
||||||
numberFormat,
|
numberFormat,
|
||||||
|
sliceId,
|
||||||
} = props;
|
} = props;
|
||||||
const responsiveClass = getResponsiveContainerClass(width);
|
const responsiveClass = getResponsiveContainerClass(width);
|
||||||
const isSmallWidth = responsiveClass === 's';
|
const isSmallWidth = responsiveClass === 's';
|
||||||
|
@ -287,7 +288,7 @@ function Sunburst(element, props) {
|
||||||
.attr('points', breadcrumbPoints)
|
.attr('points', breadcrumbPoints)
|
||||||
.style('fill', d =>
|
.style('fill', d =>
|
||||||
colorByCategory
|
colorByCategory
|
||||||
? categoricalColorScale(d.name)
|
? categoricalColorScale(d.name, sliceId)
|
||||||
: linearColorScale(d.m2 / d.m1),
|
: linearColorScale(d.m2 / d.m1),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -300,7 +301,7 @@ function Sunburst(element, props) {
|
||||||
// Make text white or black based on the lightness of the background
|
// Make text white or black based on the lightness of the background
|
||||||
const col = d3.hsl(
|
const col = d3.hsl(
|
||||||
colorByCategory
|
colorByCategory
|
||||||
? categoricalColorScale(d.name)
|
? categoricalColorScale(d.name, sliceId)
|
||||||
: linearColorScale(d.m2 / d.m1),
|
: linearColorScale(d.m2 / d.m1),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -489,7 +490,7 @@ function Sunburst(element, props) {
|
||||||
// For efficiency, filter nodes to keep only those large enough to see.
|
// For efficiency, filter nodes to keep only those large enough to see.
|
||||||
const nodes = partition.nodes(root).filter(d => d.dx > 0.005); // 0.005 radians = 0.29 degrees
|
const nodes = partition.nodes(root).filter(d => d.dx > 0.005); // 0.005 radians = 0.29 degrees
|
||||||
|
|
||||||
if (metrics[0] !== metrics[1] && metrics[1]) {
|
if (metrics[0] !== metrics[1] && metrics[1] && !colorScheme) {
|
||||||
colorByCategory = false;
|
colorByCategory = false;
|
||||||
const ext = d3.extent(nodes, d => d.m2 / d.m1);
|
const ext = d3.extent(nodes, d => d.m2 / d.m1);
|
||||||
linearColorScale = getSequentialSchemeRegistry()
|
linearColorScale = getSequentialSchemeRegistry()
|
||||||
|
@ -507,7 +508,7 @@ function Sunburst(element, props) {
|
||||||
.attr('fill-rule', 'evenodd')
|
.attr('fill-rule', 'evenodd')
|
||||||
.style('fill', d =>
|
.style('fill', d =>
|
||||||
colorByCategory
|
colorByCategory
|
||||||
? categoricalColorScale(d.name)
|
? categoricalColorScale(d.name, sliceId)
|
||||||
: linearColorScale(d.m2 / d.m1),
|
: linearColorScale(d.m2 / d.m1),
|
||||||
)
|
)
|
||||||
.style('opacity', 1)
|
.style('opacity', 1)
|
||||||
|
|
|
@ -18,7 +18,8 @@
|
||||||
*/
|
*/
|
||||||
export default function transformProps(chartProps) {
|
export default function transformProps(chartProps) {
|
||||||
const { width, height, formData, queriesData, datasource } = chartProps;
|
const { width, height, formData, queriesData, datasource } = chartProps;
|
||||||
const { colorScheme, linearColorScheme, metric, secondaryMetric } = formData;
|
const { colorScheme, linearColorScheme, metric, secondaryMetric, sliceId } =
|
||||||
|
formData;
|
||||||
|
|
||||||
const returnProps = {
|
const returnProps = {
|
||||||
width,
|
width,
|
||||||
|
@ -27,6 +28,7 @@ export default function transformProps(chartProps) {
|
||||||
colorScheme,
|
colorScheme,
|
||||||
linearColorScheme,
|
linearColorScheme,
|
||||||
metrics: [metric, secondaryMetric],
|
metrics: [metric, secondaryMetric],
|
||||||
|
sliceId,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (datasource && datasource.metrics) {
|
if (datasource && datasource.metrics) {
|
||||||
|
|
|
@ -87,6 +87,7 @@ function Treemap(element, props) {
|
||||||
numberFormat,
|
numberFormat,
|
||||||
colorScheme,
|
colorScheme,
|
||||||
treemapRatio,
|
treemapRatio,
|
||||||
|
sliceId,
|
||||||
} = props;
|
} = props;
|
||||||
const div = d3Select(element);
|
const div = d3Select(element);
|
||||||
div.classed('superset-legacy-chart-treemap', true);
|
div.classed('superset-legacy-chart-treemap', true);
|
||||||
|
@ -138,7 +139,7 @@ function Treemap(element, props) {
|
||||||
.attr('id', d => `rect-${d.data.name}`)
|
.attr('id', d => `rect-${d.data.name}`)
|
||||||
.attr('width', d => d.x1 - d.x0)
|
.attr('width', d => d.x1 - d.x0)
|
||||||
.attr('height', d => d.y1 - d.y0)
|
.attr('height', d => d.y1 - d.y0)
|
||||||
.style('fill', d => colorFn(d.depth));
|
.style('fill', d => colorFn(d.depth, sliceId));
|
||||||
|
|
||||||
cell
|
cell
|
||||||
.append('clipPath')
|
.append('clipPath')
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
*/
|
*/
|
||||||
export default function transformProps(chartProps) {
|
export default function transformProps(chartProps) {
|
||||||
const { width, height, formData, queriesData } = chartProps;
|
const { width, height, formData, queriesData } = chartProps;
|
||||||
const { colorScheme, treemapRatio } = formData;
|
const { colorScheme, treemapRatio, sliceId } = formData;
|
||||||
let { numberFormat } = formData;
|
let { numberFormat } = formData;
|
||||||
|
|
||||||
if (!numberFormat && chartProps.datasource && chartProps.datasource.metrics) {
|
if (!numberFormat && chartProps.datasource && chartProps.datasource.metrics) {
|
||||||
|
@ -39,5 +39,6 @@ export default function transformProps(chartProps) {
|
||||||
colorScheme,
|
colorScheme,
|
||||||
numberFormat,
|
numberFormat,
|
||||||
treemapRatio,
|
treemapRatio,
|
||||||
|
sliceId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ import { extent as d3Extent } from 'd3-array';
|
||||||
import {
|
import {
|
||||||
getNumberFormatter,
|
getNumberFormatter,
|
||||||
getSequentialSchemeRegistry,
|
getSequentialSchemeRegistry,
|
||||||
|
CategoricalColorNamespace,
|
||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
import Datamap from 'datamaps/dist/datamaps.world.min';
|
import Datamap from 'datamaps/dist/datamaps.world.min';
|
||||||
|
|
||||||
|
@ -55,6 +56,8 @@ function WorldMap(element, props) {
|
||||||
showBubbles,
|
showBubbles,
|
||||||
linearColorScheme,
|
linearColorScheme,
|
||||||
color,
|
color,
|
||||||
|
colorScheme,
|
||||||
|
sliceId,
|
||||||
} = props;
|
} = props;
|
||||||
const div = d3.select(element);
|
const div = d3.select(element);
|
||||||
div.classed('superset-legacy-chart-world-map', true);
|
div.classed('superset-legacy-chart-world-map', true);
|
||||||
|
@ -69,15 +72,24 @@ function WorldMap(element, props) {
|
||||||
.domain([extRadius[0], extRadius[1]])
|
.domain([extRadius[0], extRadius[1]])
|
||||||
.range([1, maxBubbleSize]);
|
.range([1, maxBubbleSize]);
|
||||||
|
|
||||||
const colorScale = getSequentialSchemeRegistry()
|
const linearColorScale = getSequentialSchemeRegistry()
|
||||||
.get(linearColorScheme)
|
.get(linearColorScheme)
|
||||||
.createLinearScale(d3Extent(filteredData, d => d.m1));
|
.createLinearScale(d3Extent(filteredData, d => d.m1));
|
||||||
|
|
||||||
const processedData = filteredData.map(d => ({
|
const colorScale = CategoricalColorNamespace.getScale(colorScheme);
|
||||||
...d,
|
|
||||||
radius: radiusScale(Math.sqrt(d.m2)),
|
const processedData = filteredData.map(d => {
|
||||||
fillColor: colorScale(d.m1),
|
let color = linearColorScale(d.m1);
|
||||||
}));
|
if (colorScheme) {
|
||||||
|
// use color scheme instead
|
||||||
|
color = colorScale(d.name, sliceId);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...d,
|
||||||
|
radius: radiusScale(Math.sqrt(d.m2)),
|
||||||
|
fillColor: color,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
const mapData = {};
|
const mapData = {};
|
||||||
processedData.forEach(d => {
|
processedData.forEach(d => {
|
||||||
|
|
|
@ -106,6 +106,7 @@ const config: ControlPanelConfig = {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
['color_picker'],
|
['color_picker'],
|
||||||
|
['color_scheme'],
|
||||||
['linear_color_scheme'],
|
['linear_color_scheme'],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -126,6 +127,9 @@ const config: ControlPanelConfig = {
|
||||||
color_picker: {
|
color_picker: {
|
||||||
label: t('Bubble Color'),
|
label: t('Bubble Color'),
|
||||||
},
|
},
|
||||||
|
color_scheme: {
|
||||||
|
label: t('Categorical Color Scheme'),
|
||||||
|
},
|
||||||
linear_color_scheme: {
|
linear_color_scheme: {
|
||||||
label: t('Country Color Scheme'),
|
label: t('Country Color Scheme'),
|
||||||
},
|
},
|
||||||
|
|
|
@ -20,8 +20,14 @@ import { rgb } from 'd3-color';
|
||||||
|
|
||||||
export default function transformProps(chartProps) {
|
export default function transformProps(chartProps) {
|
||||||
const { width, height, formData, queriesData } = chartProps;
|
const { width, height, formData, queriesData } = chartProps;
|
||||||
const { maxBubbleSize, showBubbles, linearColorScheme, colorPicker } =
|
const {
|
||||||
formData;
|
maxBubbleSize,
|
||||||
|
showBubbles,
|
||||||
|
linearColorScheme,
|
||||||
|
colorPicker,
|
||||||
|
colorScheme,
|
||||||
|
sliceId,
|
||||||
|
} = formData;
|
||||||
const { r, g, b } = colorPicker;
|
const { r, g, b } = colorPicker;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -32,5 +38,7 @@ export default function transformProps(chartProps) {
|
||||||
showBubbles,
|
showBubbles,
|
||||||
linearColorScheme,
|
linearColorScheme,
|
||||||
color: rgb(r, g, b).hex(),
|
color: rgb(r, g, b).hex(),
|
||||||
|
colorScheme,
|
||||||
|
sliceId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,7 @@ function getCategories(fd, data) {
|
||||||
if (d.cat_color != null && !categories.hasOwnProperty(d.cat_color)) {
|
if (d.cat_color != null && !categories.hasOwnProperty(d.cat_color)) {
|
||||||
let color;
|
let color;
|
||||||
if (fd.dimension) {
|
if (fd.dimension) {
|
||||||
color = hexToRGB(colorFn(d.cat_color), c.a * 255);
|
color = hexToRGB(colorFn(d.cat_color, fd.sliceId), c.a * 255);
|
||||||
} else {
|
} else {
|
||||||
color = fixedColor;
|
color = fixedColor;
|
||||||
}
|
}
|
||||||
|
@ -212,7 +212,7 @@ export default class CategoricalDeckGLContainer extends React.PureComponent {
|
||||||
return data.map(d => {
|
return data.map(d => {
|
||||||
let color;
|
let color;
|
||||||
if (fd.dimension) {
|
if (fd.dimension) {
|
||||||
color = hexToRGB(colorFn(d.cat_color), c.a * 255);
|
color = hexToRGB(colorFn(d.cat_color, fd.sliceId), c.a * 255);
|
||||||
|
|
||||||
return { ...d, color };
|
return { ...d, color };
|
||||||
}
|
}
|
||||||
|
|
|
@ -313,6 +313,7 @@ function nvd3Vis(element, props) {
|
||||||
yAxis2ShowMinMax = false,
|
yAxis2ShowMinMax = false,
|
||||||
yField,
|
yField,
|
||||||
yIsLogScale,
|
yIsLogScale,
|
||||||
|
sliceId,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const isExplore = document.querySelector('#explorer-container') !== null;
|
const isExplore = document.querySelector('#explorer-container') !== null;
|
||||||
|
@ -670,7 +671,9 @@ function nvd3Vis(element, props) {
|
||||||
);
|
);
|
||||||
} else if (vizType !== 'bullet') {
|
} else if (vizType !== 'bullet') {
|
||||||
const colorFn = getScale(colorScheme);
|
const colorFn = getScale(colorScheme);
|
||||||
chart.color(d => d.color || colorFn(cleanColorInput(d[colorKey])));
|
chart.color(
|
||||||
|
d => d.color || colorFn(cleanColorInput(d[colorKey]), sliceId),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isVizTypes(['line', 'area', 'bar', 'dist_bar']) && useRichTooltip) {
|
if (isVizTypes(['line', 'area', 'bar', 'dist_bar']) && useRichTooltip) {
|
||||||
|
|
|
@ -94,6 +94,7 @@ export default function transformProps(chartProps) {
|
||||||
yAxisShowminmax,
|
yAxisShowminmax,
|
||||||
yAxis2Showminmax,
|
yAxis2Showminmax,
|
||||||
yLogScale,
|
yLogScale,
|
||||||
|
sliceId,
|
||||||
} = formData;
|
} = formData;
|
||||||
|
|
||||||
let {
|
let {
|
||||||
|
@ -195,5 +196,6 @@ export default function transformProps(chartProps) {
|
||||||
yAxis2ShowMinMax: yAxis2Showminmax,
|
yAxis2ShowMinMax: yAxis2Showminmax,
|
||||||
yField: y,
|
yField: y,
|
||||||
yIsLogScale: yLogScale,
|
yIsLogScale: yLogScale,
|
||||||
|
sliceId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,6 +63,7 @@ export default function transformProps(
|
||||||
xAxisTitleMargin,
|
xAxisTitleMargin,
|
||||||
yAxisTitleMargin,
|
yAxisTitleMargin,
|
||||||
yAxisTitlePosition,
|
yAxisTitlePosition,
|
||||||
|
sliceId,
|
||||||
} = formData as BoxPlotQueryFormData;
|
} = formData as BoxPlotQueryFormData;
|
||||||
const colorFn = CategoricalColorNamespace.getScale(colorScheme as string);
|
const colorFn = CategoricalColorNamespace.getScale(colorScheme as string);
|
||||||
const numberFormatter = getNumberFormatter(numberFormat);
|
const numberFormatter = getNumberFormatter(numberFormat);
|
||||||
|
@ -98,9 +99,9 @@ export default function transformProps(
|
||||||
datum[`${metric}__outliers`],
|
datum[`${metric}__outliers`],
|
||||||
],
|
],
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: colorFn(groupbyLabel),
|
color: colorFn(groupbyLabel, sliceId),
|
||||||
opacity: isFiltered ? OpacityEnum.SemiTransparent : 0.6,
|
opacity: isFiltered ? OpacityEnum.SemiTransparent : 0.6,
|
||||||
borderColor: colorFn(groupbyLabel),
|
borderColor: colorFn(groupbyLabel, sliceId),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -138,7 +139,7 @@ export default function transformProps(
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: colorFn(groupbyLabel),
|
color: colorFn(groupbyLabel, sliceId),
|
||||||
opacity: isFiltered
|
opacity: isFiltered
|
||||||
? OpacityEnum.SemiTransparent
|
? OpacityEnum.SemiTransparent
|
||||||
: OpacityEnum.NonTransparent,
|
: OpacityEnum.NonTransparent,
|
||||||
|
|
|
@ -103,6 +103,7 @@ export default function transformProps(
|
||||||
showLabels,
|
showLabels,
|
||||||
showLegend,
|
showLegend,
|
||||||
emitFilter,
|
emitFilter,
|
||||||
|
sliceId,
|
||||||
}: EchartsFunnelFormData = {
|
}: EchartsFunnelFormData = {
|
||||||
...DEFAULT_LEGEND_FORM_DATA,
|
...DEFAULT_LEGEND_FORM_DATA,
|
||||||
...DEFAULT_FUNNEL_FORM_DATA,
|
...DEFAULT_FUNNEL_FORM_DATA,
|
||||||
|
@ -145,7 +146,7 @@ export default function transformProps(
|
||||||
value: datum[metricLabel],
|
value: datum[metricLabel],
|
||||||
name,
|
name,
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: colorFn(name),
|
color: colorFn(name, sliceId),
|
||||||
opacity: isFiltered
|
opacity: isFiltered
|
||||||
? OpacityEnum.SemiTransparent
|
? OpacityEnum.SemiTransparent
|
||||||
: OpacityEnum.NonTransparent,
|
: OpacityEnum.NonTransparent,
|
||||||
|
|
|
@ -107,6 +107,7 @@ export default function transformProps(
|
||||||
intervalColorIndices,
|
intervalColorIndices,
|
||||||
valueFormatter,
|
valueFormatter,
|
||||||
emitFilter,
|
emitFilter,
|
||||||
|
sliceId,
|
||||||
}: EchartsGaugeFormData = { ...DEFAULT_GAUGE_FORM_DATA, ...formData };
|
}: EchartsGaugeFormData = { ...DEFAULT_GAUGE_FORM_DATA, ...formData };
|
||||||
const data = (queriesData[0]?.data || []) as DataRecord[];
|
const data = (queriesData[0]?.data || []) as DataRecord[];
|
||||||
const numberFormatter = getNumberFormatter(numberFormat);
|
const numberFormatter = getNumberFormatter(numberFormat);
|
||||||
|
@ -147,7 +148,7 @@ export default function transformProps(
|
||||||
value: data_point[getMetricLabel(metric as QueryFormMetric)] as number,
|
value: data_point[getMetricLabel(metric as QueryFormMetric)] as number,
|
||||||
name,
|
name,
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: colorFn(index),
|
color: colorFn(index, sliceId),
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
offsetCenter: [
|
offsetCenter: [
|
||||||
|
@ -175,7 +176,7 @@ export default function transformProps(
|
||||||
item = {
|
item = {
|
||||||
...item,
|
...item,
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: colorFn(index),
|
color: colorFn(index, sliceId),
|
||||||
opacity: OpacityEnum.SemiTransparent,
|
opacity: OpacityEnum.SemiTransparent,
|
||||||
},
|
},
|
||||||
detail: {
|
detail: {
|
||||||
|
|
|
@ -184,6 +184,7 @@ export default function transformProps(chartProps: ChartProps): EchartsProps {
|
||||||
baseEdgeWidth,
|
baseEdgeWidth,
|
||||||
baseNodeSize,
|
baseNodeSize,
|
||||||
edgeSymbol,
|
edgeSymbol,
|
||||||
|
sliceId,
|
||||||
}: EchartsGraphFormData = { ...DEFAULT_GRAPH_FORM_DATA, ...formData };
|
}: EchartsGraphFormData = { ...DEFAULT_GRAPH_FORM_DATA, ...formData };
|
||||||
|
|
||||||
const metricLabel = getMetricLabel(metric);
|
const metricLabel = getMetricLabel(metric);
|
||||||
|
@ -264,7 +265,7 @@ export default function transformProps(chartProps: ChartProps): EchartsProps {
|
||||||
type: 'graph',
|
type: 'graph',
|
||||||
categories: categoryList.map(c => ({
|
categories: categoryList.map(c => ({
|
||||||
name: c,
|
name: c,
|
||||||
itemStyle: { color: colorFn(c) },
|
itemStyle: { color: colorFn(c, sliceId) },
|
||||||
})),
|
})),
|
||||||
layout,
|
layout,
|
||||||
force: {
|
force: {
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
import { QueryFormData } from '@superset-ui/core';
|
||||||
import { GraphNodeItemOption } from 'echarts/types/src/chart/graph/GraphSeries';
|
import { GraphNodeItemOption } from 'echarts/types/src/chart/graph/GraphSeries';
|
||||||
import { SeriesTooltipOption } from 'echarts/types/src/util/types';
|
import { SeriesTooltipOption } from 'echarts/types/src/util/types';
|
||||||
import {
|
import {
|
||||||
|
@ -27,32 +28,34 @@ import {
|
||||||
|
|
||||||
export type EdgeSymbol = 'none' | 'circle' | 'arrow';
|
export type EdgeSymbol = 'none' | 'circle' | 'arrow';
|
||||||
|
|
||||||
export type EchartsGraphFormData = EchartsLegendFormData & {
|
export type EchartsGraphFormData = QueryFormData &
|
||||||
source: string;
|
EchartsLegendFormData & {
|
||||||
target: string;
|
source: string;
|
||||||
sourceCategory?: string;
|
target: string;
|
||||||
targetCategory?: string;
|
sourceCategory?: string;
|
||||||
colorScheme?: string;
|
targetCategory?: string;
|
||||||
metric?: string;
|
colorScheme?: string;
|
||||||
layout?: 'none' | 'circular' | 'force';
|
metric?: string;
|
||||||
roam: boolean | 'scale' | 'move';
|
layout?: 'none' | 'circular' | 'force';
|
||||||
draggable: boolean;
|
roam: boolean | 'scale' | 'move';
|
||||||
selectedMode?: boolean | 'multiple' | 'single';
|
draggable: boolean;
|
||||||
showSymbolThreshold: number;
|
selectedMode?: boolean | 'multiple' | 'single';
|
||||||
repulsion: number;
|
showSymbolThreshold: number;
|
||||||
gravity: number;
|
repulsion: number;
|
||||||
baseNodeSize: number;
|
gravity: number;
|
||||||
baseEdgeWidth: number;
|
baseNodeSize: number;
|
||||||
edgeLength: number;
|
baseEdgeWidth: number;
|
||||||
edgeSymbol: string;
|
edgeLength: number;
|
||||||
friction: number;
|
edgeSymbol: string;
|
||||||
};
|
friction: number;
|
||||||
|
};
|
||||||
|
|
||||||
export type EChartGraphNode = Omit<GraphNodeItemOption, 'value'> & {
|
export type EChartGraphNode = Omit<GraphNodeItemOption, 'value'> & {
|
||||||
value: number;
|
value: number;
|
||||||
tooltip?: Pick<SeriesTooltipOption, 'formatter'>;
|
tooltip?: Pick<SeriesTooltipOption, 'formatter'>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
export const DEFAULT_FORM_DATA: EchartsGraphFormData = {
|
export const DEFAULT_FORM_DATA: EchartsGraphFormData = {
|
||||||
...DEFAULT_LEGEND_FORM_DATA,
|
...DEFAULT_LEGEND_FORM_DATA,
|
||||||
source: '',
|
source: '',
|
||||||
|
|
|
@ -128,6 +128,7 @@ export default function transformProps(
|
||||||
xAxisTitleMargin,
|
xAxisTitleMargin,
|
||||||
yAxisTitleMargin,
|
yAxisTitleMargin,
|
||||||
yAxisTitlePosition,
|
yAxisTitlePosition,
|
||||||
|
sliceId,
|
||||||
}: EchartsMixedTimeseriesFormData = { ...DEFAULT_FORM_DATA, ...formData };
|
}: EchartsMixedTimeseriesFormData = { ...DEFAULT_FORM_DATA, ...formData };
|
||||||
|
|
||||||
const colorScale = CategoricalColorNamespace.getScale(colorScheme as string);
|
const colorScale = CategoricalColorNamespace.getScale(colorScheme as string);
|
||||||
|
@ -177,6 +178,7 @@ export default function transformProps(
|
||||||
yAxisIndex,
|
yAxisIndex,
|
||||||
filterState,
|
filterState,
|
||||||
seriesKey: entry.name,
|
seriesKey: entry.name,
|
||||||
|
sliceId,
|
||||||
});
|
});
|
||||||
if (transformedSeries) series.push(transformedSeries);
|
if (transformedSeries) series.push(transformedSeries);
|
||||||
});
|
});
|
||||||
|
@ -195,6 +197,7 @@ export default function transformProps(
|
||||||
seriesKey: primarySeries.has(entry.name as string)
|
seriesKey: primarySeries.has(entry.name as string)
|
||||||
? `${entry.name} (1)`
|
? `${entry.name} (1)`
|
||||||
: entry.name,
|
: entry.name,
|
||||||
|
sliceId,
|
||||||
});
|
});
|
||||||
if (transformedSeries) series.push(transformedSeries);
|
if (transformedSeries) series.push(transformedSeries);
|
||||||
});
|
});
|
||||||
|
@ -203,7 +206,9 @@ export default function transformProps(
|
||||||
.filter((layer: AnnotationLayer) => layer.show)
|
.filter((layer: AnnotationLayer) => layer.show)
|
||||||
.forEach((layer: AnnotationLayer) => {
|
.forEach((layer: AnnotationLayer) => {
|
||||||
if (isFormulaAnnotationLayer(layer))
|
if (isFormulaAnnotationLayer(layer))
|
||||||
series.push(transformFormulaAnnotation(layer, data1, colorScale));
|
series.push(
|
||||||
|
transformFormulaAnnotation(layer, data1, colorScale, sliceId),
|
||||||
|
);
|
||||||
else if (isIntervalAnnotationLayer(layer)) {
|
else if (isIntervalAnnotationLayer(layer)) {
|
||||||
series.push(
|
series.push(
|
||||||
...transformIntervalAnnotation(
|
...transformIntervalAnnotation(
|
||||||
|
@ -211,11 +216,18 @@ export default function transformProps(
|
||||||
data1,
|
data1,
|
||||||
annotationData,
|
annotationData,
|
||||||
colorScale,
|
colorScale,
|
||||||
|
sliceId,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else if (isEventAnnotationLayer(layer)) {
|
} else if (isEventAnnotationLayer(layer)) {
|
||||||
series.push(
|
series.push(
|
||||||
...transformEventAnnotation(layer, data1, annotationData, colorScale),
|
...transformEventAnnotation(
|
||||||
|
layer,
|
||||||
|
data1,
|
||||||
|
annotationData,
|
||||||
|
colorScale,
|
||||||
|
sliceId,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
} else if (isTimeseriesAnnotationLayer(layer)) {
|
} else if (isTimeseriesAnnotationLayer(layer)) {
|
||||||
series.push(
|
series.push(
|
||||||
|
|
|
@ -109,6 +109,7 @@ export default function transformProps(
|
||||||
showLegend,
|
showLegend,
|
||||||
showLabelsThreshold,
|
showLabelsThreshold,
|
||||||
emitFilter,
|
emitFilter,
|
||||||
|
sliceId,
|
||||||
}: EchartsPieFormData = {
|
}: EchartsPieFormData = {
|
||||||
...DEFAULT_LEGEND_FORM_DATA,
|
...DEFAULT_LEGEND_FORM_DATA,
|
||||||
...DEFAULT_PIE_FORM_DATA,
|
...DEFAULT_PIE_FORM_DATA,
|
||||||
|
@ -162,7 +163,7 @@ export default function transformProps(
|
||||||
value: datum[metricLabel],
|
value: datum[metricLabel],
|
||||||
name,
|
name,
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: colorFn(name),
|
color: colorFn(name, sliceId),
|
||||||
opacity: isFiltered
|
opacity: isFiltered
|
||||||
? OpacityEnum.SemiTransparent
|
? OpacityEnum.SemiTransparent
|
||||||
: OpacityEnum.NonTransparent,
|
: OpacityEnum.NonTransparent,
|
||||||
|
|
|
@ -91,6 +91,7 @@ export default function transformProps(
|
||||||
showLegend,
|
showLegend,
|
||||||
isCircle,
|
isCircle,
|
||||||
columnConfig,
|
columnConfig,
|
||||||
|
sliceId,
|
||||||
}: EchartsRadarFormData = {
|
}: EchartsRadarFormData = {
|
||||||
...DEFAULT_LEGEND_FORM_DATA,
|
...DEFAULT_LEGEND_FORM_DATA,
|
||||||
...DEFAULT_RADAR_FORM_DATA,
|
...DEFAULT_RADAR_FORM_DATA,
|
||||||
|
@ -154,7 +155,7 @@ export default function transformProps(
|
||||||
value: metricLabels.map(metricLabel => datum[metricLabel]),
|
value: metricLabels.map(metricLabel => datum[metricLabel]),
|
||||||
name: joinedName,
|
name: joinedName,
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: colorFn(joinedName),
|
color: colorFn(joinedName, sliceId),
|
||||||
opacity: isFiltered
|
opacity: isFiltered
|
||||||
? OpacityEnum.Transparent
|
? OpacityEnum.Transparent
|
||||||
: OpacityEnum.NonTransparent,
|
: OpacityEnum.NonTransparent,
|
||||||
|
|
|
@ -125,6 +125,7 @@ export default function transformProps(
|
||||||
xAxisTitleMargin,
|
xAxisTitleMargin,
|
||||||
yAxisTitleMargin,
|
yAxisTitleMargin,
|
||||||
yAxisTitlePosition,
|
yAxisTitlePosition,
|
||||||
|
sliceId,
|
||||||
}: EchartsTimeseriesFormData = { ...DEFAULT_FORM_DATA, ...formData };
|
}: EchartsTimeseriesFormData = { ...DEFAULT_FORM_DATA, ...formData };
|
||||||
|
|
||||||
const colorScale = CategoricalColorNamespace.getScale(colorScheme as string);
|
const colorScale = CategoricalColorNamespace.getScale(colorScheme as string);
|
||||||
|
@ -198,6 +199,7 @@ export default function transformProps(
|
||||||
showValueIndexes,
|
showValueIndexes,
|
||||||
thresholdValues,
|
thresholdValues,
|
||||||
richTooltip,
|
richTooltip,
|
||||||
|
sliceId,
|
||||||
});
|
});
|
||||||
if (transformedSeries) series.push(transformedSeries);
|
if (transformedSeries) series.push(transformedSeries);
|
||||||
});
|
});
|
||||||
|
@ -217,7 +219,9 @@ export default function transformProps(
|
||||||
.filter((layer: AnnotationLayer) => layer.show)
|
.filter((layer: AnnotationLayer) => layer.show)
|
||||||
.forEach((layer: AnnotationLayer) => {
|
.forEach((layer: AnnotationLayer) => {
|
||||||
if (isFormulaAnnotationLayer(layer))
|
if (isFormulaAnnotationLayer(layer))
|
||||||
series.push(transformFormulaAnnotation(layer, data, colorScale));
|
series.push(
|
||||||
|
transformFormulaAnnotation(layer, data, colorScale, sliceId),
|
||||||
|
);
|
||||||
else if (isIntervalAnnotationLayer(layer)) {
|
else if (isIntervalAnnotationLayer(layer)) {
|
||||||
series.push(
|
series.push(
|
||||||
...transformIntervalAnnotation(
|
...transformIntervalAnnotation(
|
||||||
|
@ -225,11 +229,18 @@ export default function transformProps(
|
||||||
data,
|
data,
|
||||||
annotationData,
|
annotationData,
|
||||||
colorScale,
|
colorScale,
|
||||||
|
sliceId,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else if (isEventAnnotationLayer(layer)) {
|
} else if (isEventAnnotationLayer(layer)) {
|
||||||
series.push(
|
series.push(
|
||||||
...transformEventAnnotation(layer, data, annotationData, colorScale),
|
...transformEventAnnotation(
|
||||||
|
layer,
|
||||||
|
data,
|
||||||
|
annotationData,
|
||||||
|
colorScale,
|
||||||
|
sliceId,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
} else if (isTimeseriesAnnotationLayer(layer)) {
|
} else if (isTimeseriesAnnotationLayer(layer)) {
|
||||||
series.push(
|
series.push(
|
||||||
|
|
|
@ -84,6 +84,7 @@ export function transformSeries(
|
||||||
thresholdValues?: number[];
|
thresholdValues?: number[];
|
||||||
richTooltip?: boolean;
|
richTooltip?: boolean;
|
||||||
seriesKey?: OptionName;
|
seriesKey?: OptionName;
|
||||||
|
sliceId?: number;
|
||||||
},
|
},
|
||||||
): SeriesOption | undefined {
|
): SeriesOption | undefined {
|
||||||
const { name } = series;
|
const { name } = series;
|
||||||
|
@ -105,6 +106,7 @@ export function transformSeries(
|
||||||
thresholdValues = [],
|
thresholdValues = [],
|
||||||
richTooltip,
|
richTooltip,
|
||||||
seriesKey,
|
seriesKey,
|
||||||
|
sliceId,
|
||||||
} = opts;
|
} = opts;
|
||||||
const contexts = seriesContexts[name || ''] || [];
|
const contexts = seriesContexts[name || ''] || [];
|
||||||
const hasForecast =
|
const hasForecast =
|
||||||
|
@ -151,7 +153,7 @@ export function transformSeries(
|
||||||
}
|
}
|
||||||
// forcing the colorScale to return a different color for same metrics across different queries
|
// forcing the colorScale to return a different color for same metrics across different queries
|
||||||
const itemStyle = {
|
const itemStyle = {
|
||||||
color: colorScale(seriesKey || forecastSeries.name),
|
color: colorScale(seriesKey || forecastSeries.name, sliceId),
|
||||||
opacity,
|
opacity,
|
||||||
};
|
};
|
||||||
let emphasis = {};
|
let emphasis = {};
|
||||||
|
@ -244,13 +246,14 @@ export function transformFormulaAnnotation(
|
||||||
layer: FormulaAnnotationLayer,
|
layer: FormulaAnnotationLayer,
|
||||||
data: TimeseriesDataRecord[],
|
data: TimeseriesDataRecord[],
|
||||||
colorScale: CategoricalColorScale,
|
colorScale: CategoricalColorScale,
|
||||||
|
sliceId?: number,
|
||||||
): SeriesOption {
|
): SeriesOption {
|
||||||
const { name, color, opacity, width, style } = layer;
|
const { name, color, opacity, width, style } = layer;
|
||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
id: name,
|
id: name,
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: color || colorScale(name),
|
color: color || colorScale(name, sliceId),
|
||||||
},
|
},
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
opacity: parseAnnotationOpacity(opacity),
|
opacity: parseAnnotationOpacity(opacity),
|
||||||
|
@ -269,6 +272,7 @@ export function transformIntervalAnnotation(
|
||||||
data: TimeseriesDataRecord[],
|
data: TimeseriesDataRecord[],
|
||||||
annotationData: AnnotationData,
|
annotationData: AnnotationData,
|
||||||
colorScale: CategoricalColorScale,
|
colorScale: CategoricalColorScale,
|
||||||
|
sliceId?: number,
|
||||||
): SeriesOption[] {
|
): SeriesOption[] {
|
||||||
const series: SeriesOption[] = [];
|
const series: SeriesOption[] = [];
|
||||||
const annotations = extractRecordAnnotations(layer, annotationData);
|
const annotations = extractRecordAnnotations(layer, annotationData);
|
||||||
|
@ -323,7 +327,7 @@ export function transformIntervalAnnotation(
|
||||||
markArea: {
|
markArea: {
|
||||||
silent: false,
|
silent: false,
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: color || colorScale(name),
|
color: color || colorScale(name, sliceId),
|
||||||
opacity: parseAnnotationOpacity(opacity || AnnotationOpacity.Medium),
|
opacity: parseAnnotationOpacity(opacity || AnnotationOpacity.Medium),
|
||||||
emphasis: {
|
emphasis: {
|
||||||
opacity: 0.8,
|
opacity: 0.8,
|
||||||
|
@ -342,6 +346,7 @@ export function transformEventAnnotation(
|
||||||
data: TimeseriesDataRecord[],
|
data: TimeseriesDataRecord[],
|
||||||
annotationData: AnnotationData,
|
annotationData: AnnotationData,
|
||||||
colorScale: CategoricalColorScale,
|
colorScale: CategoricalColorScale,
|
||||||
|
sliceId?: number,
|
||||||
): SeriesOption[] {
|
): SeriesOption[] {
|
||||||
const series: SeriesOption[] = [];
|
const series: SeriesOption[] = [];
|
||||||
const annotations = extractRecordAnnotations(layer, annotationData);
|
const annotations = extractRecordAnnotations(layer, annotationData);
|
||||||
|
@ -359,7 +364,7 @@ export function transformEventAnnotation(
|
||||||
const lineStyle: LineStyleOption & DefaultStatesMixin['emphasis'] = {
|
const lineStyle: LineStyleOption & DefaultStatesMixin['emphasis'] = {
|
||||||
width,
|
width,
|
||||||
type: style as ZRLineType,
|
type: style as ZRLineType,
|
||||||
color: color || colorScale(name),
|
color: color || colorScale(name, sliceId),
|
||||||
opacity: parseAnnotationOpacity(opacity),
|
opacity: parseAnnotationOpacity(opacity),
|
||||||
emphasis: {
|
emphasis: {
|
||||||
width: width ? width + 1 : width,
|
width: width ? width + 1 : width,
|
||||||
|
|
|
@ -127,6 +127,7 @@ export default function transformProps(
|
||||||
showUpperLabels,
|
showUpperLabels,
|
||||||
dashboardId,
|
dashboardId,
|
||||||
emitFilter,
|
emitFilter,
|
||||||
|
sliceId,
|
||||||
}: EchartsTreemapFormData = {
|
}: EchartsTreemapFormData = {
|
||||||
...DEFAULT_TREEMAP_FORM_DATA,
|
...DEFAULT_TREEMAP_FORM_DATA,
|
||||||
...formData,
|
...formData,
|
||||||
|
@ -223,7 +224,7 @@ export default function transformProps(
|
||||||
colorSaturation: COLOR_SATURATION,
|
colorSaturation: COLOR_SATURATION,
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
borderColor: BORDER_COLOR,
|
borderColor: BORDER_COLOR,
|
||||||
color: colorFn(`${child.name}`),
|
color: colorFn(`${child.name}`, sliceId),
|
||||||
borderWidth: BORDER_WIDTH,
|
borderWidth: BORDER_WIDTH,
|
||||||
gapWidth: GAP_WIDTH,
|
gapWidth: GAP_WIDTH,
|
||||||
},
|
},
|
||||||
|
@ -259,7 +260,7 @@ export default function transformProps(
|
||||||
show: false,
|
show: false,
|
||||||
},
|
},
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: CategoricalColorNamespace.getColor(),
|
color: '#1FA8C9',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
@ -25,7 +25,12 @@ import {
|
||||||
DeriveEncoding,
|
DeriveEncoding,
|
||||||
Encoder,
|
Encoder,
|
||||||
} from 'encodable';
|
} from 'encodable';
|
||||||
import { SupersetThemeProps, withTheme, seedRandom } from '@superset-ui/core';
|
import {
|
||||||
|
SupersetThemeProps,
|
||||||
|
withTheme,
|
||||||
|
seedRandom,
|
||||||
|
CategoricalColorScale,
|
||||||
|
} from '@superset-ui/core';
|
||||||
|
|
||||||
export const ROTATION = {
|
export const ROTATION = {
|
||||||
flat: () => 0,
|
flat: () => 0,
|
||||||
|
@ -58,6 +63,7 @@ export interface WordCloudProps extends WordCloudVisualProps {
|
||||||
data: PlainObject[];
|
data: PlainObject[];
|
||||||
height: number;
|
height: number;
|
||||||
width: number;
|
width: number;
|
||||||
|
sliceId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WordCloudState {
|
export interface WordCloudState {
|
||||||
|
@ -210,12 +216,15 @@ class WordCloud extends React.PureComponent<
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { scaleFactor } = this.state;
|
const { scaleFactor } = this.state;
|
||||||
const { width, height, encoding } = this.props;
|
const { width, height, encoding, sliceId } = this.props;
|
||||||
const { words } = this.state;
|
const { words } = this.state;
|
||||||
|
|
||||||
const encoder = this.createEncoder(encoding);
|
const encoder = this.createEncoder(encoding);
|
||||||
encoder.channels.color.setDomainFromDataset(words);
|
encoder.channels.color.setDomainFromDataset(words);
|
||||||
|
|
||||||
|
const { getValueFromDatum } = encoder.channels.color;
|
||||||
|
const colorFn = encoder.channels.color.scale as CategoricalColorScale;
|
||||||
|
|
||||||
const viewBoxWidth = width * scaleFactor;
|
const viewBoxWidth = width * scaleFactor;
|
||||||
const viewBoxHeight = height * scaleFactor;
|
const viewBoxHeight = height * scaleFactor;
|
||||||
|
|
||||||
|
@ -234,7 +243,7 @@ class WordCloud extends React.PureComponent<
|
||||||
fontSize={`${w.size}px`}
|
fontSize={`${w.size}px`}
|
||||||
fontWeight={w.weight}
|
fontWeight={w.weight}
|
||||||
fontFamily={w.font}
|
fontFamily={w.font}
|
||||||
fill={encoder.channels.color.encodeDatum(w, '')}
|
fill={colorFn(getValueFromDatum(w) as string, sliceId)}
|
||||||
textAnchor="middle"
|
textAnchor="middle"
|
||||||
transform={`translate(${w.x}, ${w.y}) rotate(${w.rotate})`}
|
transform={`translate(${w.x}, ${w.y}) rotate(${w.rotate})`}
|
||||||
>
|
>
|
||||||
|
|
|
@ -43,6 +43,7 @@ export default function transformProps(chartProps: ChartProps): WordCloudProps {
|
||||||
series,
|
series,
|
||||||
sizeFrom = 0,
|
sizeFrom = 0,
|
||||||
sizeTo,
|
sizeTo,
|
||||||
|
sliceId,
|
||||||
} = formData as LegacyWordCloudFormData;
|
} = formData as LegacyWordCloudFormData;
|
||||||
|
|
||||||
const metricLabel = getMetricLabel(metric);
|
const metricLabel = getMetricLabel(metric);
|
||||||
|
@ -77,5 +78,6 @@ export default function transformProps(chartProps: ChartProps): WordCloudProps {
|
||||||
height,
|
height,
|
||||||
rotation,
|
rotation,
|
||||||
width,
|
width,
|
||||||
|
sliceId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ import { WordCloudFormData } from '../types';
|
||||||
|
|
||||||
export default function transformProps(chartProps: ChartProps): WordCloudProps {
|
export default function transformProps(chartProps: ChartProps): WordCloudProps {
|
||||||
const { width, height, formData, queriesData } = chartProps;
|
const { width, height, formData, queriesData } = chartProps;
|
||||||
const { encoding, rotation } = formData as WordCloudFormData;
|
const { encoding, rotation, sliceId } = formData as WordCloudFormData;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data: queriesData[0].data,
|
data: queriesData[0].data,
|
||||||
|
@ -31,5 +31,6 @@ export default function transformProps(chartProps: ChartProps): WordCloudProps {
|
||||||
height,
|
height,
|
||||||
rotation,
|
rotation,
|
||||||
width,
|
width,
|
||||||
|
sliceId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,7 @@ const propTypes = {
|
||||||
// and merged with extra filter that current dashboard applying
|
// and merged with extra filter that current dashboard applying
|
||||||
formData: PropTypes.object.isRequired,
|
formData: PropTypes.object.isRequired,
|
||||||
labelColors: PropTypes.object,
|
labelColors: PropTypes.object,
|
||||||
|
sharedLabelColors: PropTypes.object,
|
||||||
width: PropTypes.number,
|
width: PropTypes.number,
|
||||||
height: PropTypes.number,
|
height: PropTypes.number,
|
||||||
setControlValue: PropTypes.func,
|
setControlValue: PropTypes.func,
|
||||||
|
@ -70,6 +71,7 @@ const propTypes = {
|
||||||
onFilterMenuOpen: PropTypes.func,
|
onFilterMenuOpen: PropTypes.func,
|
||||||
onFilterMenuClose: PropTypes.func,
|
onFilterMenuClose: PropTypes.func,
|
||||||
ownState: PropTypes.object,
|
ownState: PropTypes.object,
|
||||||
|
postTransformProps: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
const BLANK = {};
|
const BLANK = {};
|
||||||
|
|
|
@ -31,6 +31,7 @@ const propTypes = {
|
||||||
initialValues: PropTypes.object,
|
initialValues: PropTypes.object,
|
||||||
formData: PropTypes.object.isRequired,
|
formData: PropTypes.object.isRequired,
|
||||||
labelColors: PropTypes.object,
|
labelColors: PropTypes.object,
|
||||||
|
sharedLabelColors: PropTypes.object,
|
||||||
height: PropTypes.number,
|
height: PropTypes.number,
|
||||||
width: PropTypes.number,
|
width: PropTypes.number,
|
||||||
setControlValue: PropTypes.func,
|
setControlValue: PropTypes.func,
|
||||||
|
@ -48,6 +49,7 @@ const propTypes = {
|
||||||
onFilterMenuOpen: PropTypes.func,
|
onFilterMenuOpen: PropTypes.func,
|
||||||
onFilterMenuClose: PropTypes.func,
|
onFilterMenuClose: PropTypes.func,
|
||||||
ownState: PropTypes.object,
|
ownState: PropTypes.object,
|
||||||
|
postTransformProps: PropTypes.func,
|
||||||
source: PropTypes.oneOf(['dashboard', 'explore']),
|
source: PropTypes.oneOf(['dashboard', 'explore']),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -107,6 +109,7 @@ class ChartRenderer extends React.Component {
|
||||||
nextProps.width !== this.props.width ||
|
nextProps.width !== this.props.width ||
|
||||||
nextProps.triggerRender ||
|
nextProps.triggerRender ||
|
||||||
nextProps.labelColors !== this.props.labelColors ||
|
nextProps.labelColors !== this.props.labelColors ||
|
||||||
|
nextProps.sharedLabelColors !== this.props.sharedLabelColors ||
|
||||||
nextProps.formData.color_scheme !== this.props.formData.color_scheme ||
|
nextProps.formData.color_scheme !== this.props.formData.color_scheme ||
|
||||||
nextProps.cacheBusterProp !== this.props.cacheBusterProp
|
nextProps.cacheBusterProp !== this.props.cacheBusterProp
|
||||||
);
|
);
|
||||||
|
@ -192,6 +195,7 @@ class ChartRenderer extends React.Component {
|
||||||
filterState,
|
filterState,
|
||||||
formData,
|
formData,
|
||||||
queriesResponse,
|
queriesResponse,
|
||||||
|
postTransformProps,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
// It's bad practice to use unprefixed `vizType` as classnames for chart
|
// It's bad practice to use unprefixed `vizType` as classnames for chart
|
||||||
|
@ -260,6 +264,7 @@ class ChartRenderer extends React.Component {
|
||||||
onRenderSuccess={this.handleRenderSuccess}
|
onRenderSuccess={this.handleRenderSuccess}
|
||||||
onRenderFailure={this.handleRenderFailure}
|
onRenderFailure={this.handleRenderFailure}
|
||||||
noResults={noResultsComponent}
|
noResults={noResultsComponent}
|
||||||
|
postTransformProps={postTransformProps}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,21 @@ import { ChartConfiguration, DashboardInfo } from '../reducers/types';
|
||||||
|
|
||||||
export const DASHBOARD_INFO_UPDATED = 'DASHBOARD_INFO_UPDATED';
|
export const DASHBOARD_INFO_UPDATED = 'DASHBOARD_INFO_UPDATED';
|
||||||
|
|
||||||
|
export function updateColorSchema(
|
||||||
|
metadata: Record<string, any>,
|
||||||
|
labelColors: Record<string, string>,
|
||||||
|
) {
|
||||||
|
const categoricalNamespace = CategoricalColorNamespace.getNamespace(
|
||||||
|
metadata?.color_namespace,
|
||||||
|
);
|
||||||
|
const colorMap = isString(labelColors)
|
||||||
|
? JSON.parse(labelColors)
|
||||||
|
: labelColors;
|
||||||
|
Object.keys(colorMap).forEach(label => {
|
||||||
|
categoricalNamespace.setColor(label, colorMap[label]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// updates partially changed dashboard info
|
// updates partially changed dashboard info
|
||||||
export function dashboardInfoChanged(newInfo: { metadata: any }) {
|
export function dashboardInfoChanged(newInfo: { metadata: any }) {
|
||||||
const { metadata } = newInfo;
|
const { metadata } = newInfo;
|
||||||
|
@ -33,14 +48,12 @@ export function dashboardInfoChanged(newInfo: { metadata: any }) {
|
||||||
|
|
||||||
categoricalNamespace.resetColors();
|
categoricalNamespace.resetColors();
|
||||||
|
|
||||||
|
if (metadata?.shared_label_colors) {
|
||||||
|
updateColorSchema(metadata, metadata?.shared_label_colors);
|
||||||
|
}
|
||||||
|
|
||||||
if (metadata?.label_colors) {
|
if (metadata?.label_colors) {
|
||||||
const labelColors = metadata.label_colors;
|
updateColorSchema(metadata, metadata?.label_colors);
|
||||||
const colorMap = isString(labelColors)
|
|
||||||
? JSON.parse(labelColors)
|
|
||||||
: labelColors;
|
|
||||||
Object.keys(colorMap).forEach(label => {
|
|
||||||
categoricalNamespace.setColor(label, colorMap[label]);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return { type: DASHBOARD_INFO_UPDATED, newInfo };
|
return { type: DASHBOARD_INFO_UPDATED, newInfo };
|
||||||
|
|
|
@ -47,17 +47,19 @@ function setUnsavedChangesAfterAction(action) {
|
||||||
dispatch(result);
|
dispatch(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { dashboardLayout, dashboardState } = getState();
|
||||||
|
|
||||||
const isComponentLevelEvent =
|
const isComponentLevelEvent =
|
||||||
result.type === UPDATE_COMPONENTS &&
|
result.type === UPDATE_COMPONENTS &&
|
||||||
result.payload &&
|
result.payload &&
|
||||||
result.payload.nextComponents;
|
result.payload.nextComponents;
|
||||||
// trigger dashboardFilters state update if dashboard layout is changed.
|
// trigger dashboardFilters state update if dashboard layout is changed.
|
||||||
if (!isComponentLevelEvent) {
|
if (!isComponentLevelEvent) {
|
||||||
const components = getState().dashboardLayout.present;
|
const components = dashboardLayout.present;
|
||||||
dispatch(updateLayoutComponents(components));
|
dispatch(updateLayoutComponents(components));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!getState().dashboardState.hasUnsavedChanges) {
|
if (!dashboardState.hasUnsavedChanges) {
|
||||||
dispatch(setUnsavedChanges(true));
|
dispatch(setUnsavedChanges(true));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -18,7 +18,12 @@
|
||||||
*/
|
*/
|
||||||
/* eslint camelcase: 0 */
|
/* eslint camelcase: 0 */
|
||||||
import { ActionCreators as UndoActionCreators } from 'redux-undo';
|
import { ActionCreators as UndoActionCreators } from 'redux-undo';
|
||||||
import { ensureIsArray, t, SupersetClient } from '@superset-ui/core';
|
import {
|
||||||
|
ensureIsArray,
|
||||||
|
t,
|
||||||
|
SupersetClient,
|
||||||
|
getSharedLabelColor,
|
||||||
|
} from '@superset-ui/core';
|
||||||
import {
|
import {
|
||||||
addChart,
|
addChart,
|
||||||
removeChart,
|
removeChart,
|
||||||
|
@ -67,6 +72,11 @@ export function removeSlice(sliceId) {
|
||||||
return { type: REMOVE_SLICE, sliceId };
|
return { type: REMOVE_SLICE, sliceId };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const RESET_SLICE = 'RESET_SLICE';
|
||||||
|
export function resetSlice() {
|
||||||
|
return { type: RESET_SLICE };
|
||||||
|
}
|
||||||
|
|
||||||
const FAVESTAR_BASE_URL = '/superset/favstar/Dashboard';
|
const FAVESTAR_BASE_URL = '/superset/favstar/Dashboard';
|
||||||
export const TOGGLE_FAVE_STAR = 'TOGGLE_FAVE_STAR';
|
export const TOGGLE_FAVE_STAR = 'TOGGLE_FAVE_STAR';
|
||||||
export function toggleFaveStar(isStarred) {
|
export function toggleFaveStar(isStarred) {
|
||||||
|
@ -232,6 +242,7 @@ export function saveDashboardRequest(data, id, saveType) {
|
||||||
color_scheme: data.metadata?.color_scheme || '',
|
color_scheme: data.metadata?.color_scheme || '',
|
||||||
expanded_slices: data.metadata?.expanded_slices || {},
|
expanded_slices: data.metadata?.expanded_slices || {},
|
||||||
label_colors: data.metadata?.label_colors || {},
|
label_colors: data.metadata?.label_colors || {},
|
||||||
|
shared_label_colors: data.metadata?.shared_label_colors || {},
|
||||||
refresh_frequency: data.metadata?.refresh_frequency || 0,
|
refresh_frequency: data.metadata?.refresh_frequency || 0,
|
||||||
timed_refresh_immune_slices:
|
timed_refresh_immune_slices:
|
||||||
data.metadata?.timed_refresh_immune_slices || [],
|
data.metadata?.timed_refresh_immune_slices || [],
|
||||||
|
@ -495,6 +506,28 @@ export function addSliceToDashboard(id, component) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function postAddSliceFromDashboard() {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const {
|
||||||
|
dashboardInfo: { metadata },
|
||||||
|
dashboardState,
|
||||||
|
} = getState();
|
||||||
|
|
||||||
|
if (dashboardState?.updateSlice && dashboardState?.editMode) {
|
||||||
|
metadata.shared_label_colors = getSharedLabelColor().getColorMap(
|
||||||
|
metadata?.color_namespace,
|
||||||
|
metadata?.color_scheme,
|
||||||
|
);
|
||||||
|
dispatch(
|
||||||
|
dashboardInfoChanged({
|
||||||
|
metadata,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
dispatch(resetSlice());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function removeSliceFromDashboard(id) {
|
export function removeSliceFromDashboard(id) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const sliceEntity = getState().sliceEntities.slices[id];
|
const sliceEntity = getState().sliceEntities.slices[id];
|
||||||
|
@ -504,6 +537,20 @@ export function removeSliceFromDashboard(id) {
|
||||||
|
|
||||||
dispatch(removeSlice(id));
|
dispatch(removeSlice(id));
|
||||||
dispatch(removeChart(id));
|
dispatch(removeChart(id));
|
||||||
|
|
||||||
|
const {
|
||||||
|
dashboardInfo: { metadata },
|
||||||
|
} = getState();
|
||||||
|
getSharedLabelColor().removeSlice(id);
|
||||||
|
metadata.shared_label_colors = getSharedLabelColor().getColorMap(
|
||||||
|
metadata?.color_namespace,
|
||||||
|
metadata?.color_scheme,
|
||||||
|
);
|
||||||
|
dispatch(
|
||||||
|
dashboardInfoChanged({
|
||||||
|
metadata,
|
||||||
|
}),
|
||||||
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,11 @@ describe('dashboardState actions', () => {
|
||||||
sliceIds: [filterId],
|
sliceIds: [filterId],
|
||||||
hasUnsavedChanges: true,
|
hasUnsavedChanges: true,
|
||||||
},
|
},
|
||||||
dashboardInfo: {},
|
dashboardInfo: {
|
||||||
|
metadata: {
|
||||||
|
color_scheme: 'supersetColors',
|
||||||
|
},
|
||||||
|
},
|
||||||
sliceEntities,
|
sliceEntities,
|
||||||
dashboardFilters: emptyFilters,
|
dashboardFilters: emptyFilters,
|
||||||
dashboardLayout: {
|
dashboardLayout: {
|
||||||
|
@ -116,6 +120,6 @@ describe('dashboardState actions', () => {
|
||||||
|
|
||||||
const removeFilter = dispatch.getCall(0).args[0];
|
const removeFilter = dispatch.getCall(0).args[0];
|
||||||
removeFilter(dispatch, getState);
|
removeFilter(dispatch, getState);
|
||||||
expect(dispatch.getCall(3).args[0].type).toBe(REMOVE_FILTER);
|
expect(dispatch.getCall(4).args[0].type).toBe(REMOVE_FILTER);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,12 +17,7 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
import { isString } from 'lodash';
|
import { Behavior, getChartMetadataRegistry } from '@superset-ui/core';
|
||||||
import {
|
|
||||||
Behavior,
|
|
||||||
CategoricalColorNamespace,
|
|
||||||
getChartMetadataRegistry,
|
|
||||||
} from '@superset-ui/core';
|
|
||||||
|
|
||||||
import { chart } from 'src/components/Chart/chartReducer';
|
import { chart } from 'src/components/Chart/chartReducer';
|
||||||
import { initSliceEntities } from 'src/dashboard/reducers/sliceEntities';
|
import { initSliceEntities } from 'src/dashboard/reducers/sliceEntities';
|
||||||
|
@ -59,6 +54,7 @@ import { FILTER_BOX_MIGRATION_STATES } from 'src/explore/constants';
|
||||||
import { FeatureFlag, isFeatureEnabled } from '../../featureFlags';
|
import { FeatureFlag, isFeatureEnabled } from '../../featureFlags';
|
||||||
import extractUrlParams from '../util/extractUrlParams';
|
import extractUrlParams from '../util/extractUrlParams';
|
||||||
import getNativeFilterConfig from '../util/filterboxMigrationHelper';
|
import getNativeFilterConfig from '../util/filterboxMigrationHelper';
|
||||||
|
import { updateColorSchema } from './dashboardInfo';
|
||||||
|
|
||||||
export const HYDRATE_DASHBOARD = 'HYDRATE_DASHBOARD';
|
export const HYDRATE_DASHBOARD = 'HYDRATE_DASHBOARD';
|
||||||
|
|
||||||
|
@ -92,19 +88,14 @@ export const hydrateDashboard =
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (metadata?.shared_label_colors) {
|
||||||
|
updateColorSchema(metadata, metadata?.shared_label_colors);
|
||||||
|
}
|
||||||
|
|
||||||
// Priming the color palette with user's label-color mapping provided in
|
// Priming the color palette with user's label-color mapping provided in
|
||||||
// the dashboard's JSON metadata
|
// the dashboard's JSON metadata
|
||||||
if (metadata?.label_colors) {
|
if (metadata?.label_colors) {
|
||||||
const namespace = metadata.color_namespace;
|
updateColorSchema(metadata, metadata?.label_colors);
|
||||||
const colorMap = isString(metadata.label_colors)
|
|
||||||
? JSON.parse(metadata.label_colors)
|
|
||||||
: metadata.label_colors;
|
|
||||||
const categoricalNamespace =
|
|
||||||
CategoricalColorNamespace.getNamespace(namespace);
|
|
||||||
|
|
||||||
Object.keys(colorMap).forEach(label => {
|
|
||||||
categoricalNamespace.setColor(label, colorMap[label]);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// dashboard layout
|
// dashboard layout
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { styled, t } from '@superset-ui/core';
|
import { styled, t, getSharedLabelColor } from '@superset-ui/core';
|
||||||
import ButtonGroup from 'src/components/ButtonGroup';
|
import ButtonGroup from 'src/components/ButtonGroup';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -356,6 +356,15 @@ class Header extends React.PureComponent {
|
||||||
? currentRefreshFrequency
|
? currentRefreshFrequency
|
||||||
: dashboardInfo.metadata?.refresh_frequency;
|
: dashboardInfo.metadata?.refresh_frequency;
|
||||||
|
|
||||||
|
const currentColorScheme =
|
||||||
|
dashboardInfo?.metadata?.color_scheme || colorScheme;
|
||||||
|
const currentColorNamespace =
|
||||||
|
dashboardInfo?.metadata?.color_namespace || colorNamespace;
|
||||||
|
const currentSharedLabelColors = getSharedLabelColor().getColorMap(
|
||||||
|
currentColorNamespace,
|
||||||
|
currentColorScheme,
|
||||||
|
);
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
certified_by: dashboardInfo.certified_by,
|
certified_by: dashboardInfo.certified_by,
|
||||||
certification_details: dashboardInfo.certification_details,
|
certification_details: dashboardInfo.certification_details,
|
||||||
|
@ -367,11 +376,11 @@ class Header extends React.PureComponent {
|
||||||
slug,
|
slug,
|
||||||
metadata: {
|
metadata: {
|
||||||
...dashboardInfo?.metadata,
|
...dashboardInfo?.metadata,
|
||||||
color_namespace:
|
color_namespace: currentColorNamespace,
|
||||||
dashboardInfo?.metadata?.color_namespace || colorNamespace,
|
color_scheme: currentColorScheme,
|
||||||
color_scheme: dashboardInfo?.metadata?.color_scheme || colorScheme,
|
|
||||||
positions,
|
positions,
|
||||||
refresh_frequency: refreshFrequency,
|
refresh_frequency: refreshFrequency,
|
||||||
|
shared_label_colors: currentSharedLabelColors,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ import {
|
||||||
SupersetClient,
|
SupersetClient,
|
||||||
getCategoricalSchemeRegistry,
|
getCategoricalSchemeRegistry,
|
||||||
ensureIsArray,
|
ensureIsArray,
|
||||||
|
getSharedLabelColor,
|
||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
|
|
||||||
import Modal from 'src/components/Modal';
|
import Modal from 'src/components/Modal';
|
||||||
|
@ -169,7 +170,11 @@ const PropertiesModal = ({
|
||||||
if (metadata?.positions) {
|
if (metadata?.positions) {
|
||||||
delete metadata.positions;
|
delete metadata.positions;
|
||||||
}
|
}
|
||||||
setJsonMetadata(metadata ? jsonStringify(metadata) : '');
|
const metaDataCopy = { ...metadata };
|
||||||
|
if (metaDataCopy?.shared_label_colors) {
|
||||||
|
delete metaDataCopy.shared_label_colors;
|
||||||
|
}
|
||||||
|
setJsonMetadata(metaDataCopy ? jsonStringify(metaDataCopy) : '');
|
||||||
},
|
},
|
||||||
[form],
|
[form],
|
||||||
);
|
);
|
||||||
|
@ -282,12 +287,25 @@ const PropertiesModal = ({
|
||||||
form.getFieldsValue();
|
form.getFieldsValue();
|
||||||
let currentColorScheme = colorScheme;
|
let currentColorScheme = colorScheme;
|
||||||
let colorNamespace = '';
|
let colorNamespace = '';
|
||||||
|
let currentJsonMetadata = jsonMetadata;
|
||||||
|
|
||||||
// color scheme in json metadata has precedence over selection
|
// color scheme in json metadata has precedence over selection
|
||||||
if (jsonMetadata?.length) {
|
if (currentJsonMetadata?.length) {
|
||||||
const metadata = JSON.parse(jsonMetadata);
|
const metadata = JSON.parse(currentJsonMetadata);
|
||||||
currentColorScheme = metadata?.color_scheme || colorScheme;
|
currentColorScheme = metadata?.color_scheme || colorScheme;
|
||||||
colorNamespace = metadata?.color_namespace || '';
|
colorNamespace = metadata?.color_namespace || '';
|
||||||
|
|
||||||
|
// filter shared_label_color from user input
|
||||||
|
if (metadata?.shared_label_colors) {
|
||||||
|
delete metadata.shared_label_colors;
|
||||||
|
}
|
||||||
|
const colorMap = getSharedLabelColor().getColorMap(
|
||||||
|
colorNamespace,
|
||||||
|
currentColorScheme,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
metadata.shared_label_colors = colorMap;
|
||||||
|
currentJsonMetadata = jsonStringify(metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
onColorSchemeChange(currentColorScheme, {
|
onColorSchemeChange(currentColorScheme, {
|
||||||
|
@ -304,7 +322,7 @@ const PropertiesModal = ({
|
||||||
id: dashboardId,
|
id: dashboardId,
|
||||||
title,
|
title,
|
||||||
slug,
|
slug,
|
||||||
jsonMetadata,
|
jsonMetadata: currentJsonMetadata,
|
||||||
owners,
|
owners,
|
||||||
colorScheme: currentColorScheme,
|
colorScheme: currentColorScheme,
|
||||||
colorNamespace,
|
colorNamespace,
|
||||||
|
@ -323,7 +341,7 @@ const PropertiesModal = ({
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
dashboard_title: title,
|
dashboard_title: title,
|
||||||
slug: slug || null,
|
slug: slug || null,
|
||||||
json_metadata: jsonMetadata || null,
|
json_metadata: currentJsonMetadata || null,
|
||||||
owners: (owners || []).map(o => o.id),
|
owners: (owners || []).map(o => o.id),
|
||||||
certified_by: certifiedBy || null,
|
certified_by: certifiedBy || null,
|
||||||
certification_details:
|
certification_details:
|
||||||
|
|
|
@ -56,6 +56,7 @@ const propTypes = {
|
||||||
chart: chartPropShape.isRequired,
|
chart: chartPropShape.isRequired,
|
||||||
formData: PropTypes.object.isRequired,
|
formData: PropTypes.object.isRequired,
|
||||||
labelColors: PropTypes.object,
|
labelColors: PropTypes.object,
|
||||||
|
sharedLabelColors: PropTypes.object,
|
||||||
datasource: PropTypes.object,
|
datasource: PropTypes.object,
|
||||||
slice: slicePropShape.isRequired,
|
slice: slicePropShape.isRequired,
|
||||||
sliceName: PropTypes.string.isRequired,
|
sliceName: PropTypes.string.isRequired,
|
||||||
|
@ -81,6 +82,7 @@ const propTypes = {
|
||||||
addDangerToast: PropTypes.func.isRequired,
|
addDangerToast: PropTypes.func.isRequired,
|
||||||
ownState: PropTypes.object,
|
ownState: PropTypes.object,
|
||||||
filterState: PropTypes.object,
|
filterState: PropTypes.object,
|
||||||
|
postTransformProps: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
|
@ -319,6 +321,7 @@ export default class Chart extends React.Component {
|
||||||
filters,
|
filters,
|
||||||
formData,
|
formData,
|
||||||
labelColors,
|
labelColors,
|
||||||
|
sharedLabelColors,
|
||||||
updateSliceName,
|
updateSliceName,
|
||||||
sliceName,
|
sliceName,
|
||||||
toggleExpandSlice,
|
toggleExpandSlice,
|
||||||
|
@ -334,6 +337,7 @@ export default class Chart extends React.Component {
|
||||||
handleToggleFullSize,
|
handleToggleFullSize,
|
||||||
isFullSize,
|
isFullSize,
|
||||||
filterboxMigrationState,
|
filterboxMigrationState,
|
||||||
|
postTransformProps,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const { width } = this.state;
|
const { width } = this.state;
|
||||||
|
@ -449,6 +453,7 @@ export default class Chart extends React.Component {
|
||||||
initialValues={initialValues}
|
initialValues={initialValues}
|
||||||
formData={formData}
|
formData={formData}
|
||||||
labelColors={labelColors}
|
labelColors={labelColors}
|
||||||
|
sharedLabelColors={sharedLabelColors}
|
||||||
ownState={ownState}
|
ownState={ownState}
|
||||||
filterState={filterState}
|
filterState={filterState}
|
||||||
queriesResponse={chart.queriesResponse}
|
queriesResponse={chart.queriesResponse}
|
||||||
|
@ -457,6 +462,7 @@ export default class Chart extends React.Component {
|
||||||
vizType={slice.viz_type}
|
vizType={slice.viz_type}
|
||||||
isDeactivatedViz={isDeactivatedViz}
|
isDeactivatedViz={isDeactivatedViz}
|
||||||
filterboxMigrationState={filterboxMigrationState}
|
filterboxMigrationState={filterboxMigrationState}
|
||||||
|
postTransformProps={postTransformProps}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -69,6 +69,7 @@ const propTypes = {
|
||||||
updateComponents: PropTypes.func.isRequired,
|
updateComponents: PropTypes.func.isRequired,
|
||||||
handleComponentDrop: PropTypes.func.isRequired,
|
handleComponentDrop: PropTypes.func.isRequired,
|
||||||
setFullSizeChartId: PropTypes.func.isRequired,
|
setFullSizeChartId: PropTypes.func.isRequired,
|
||||||
|
postAddSliceFromDashboard: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
|
@ -197,6 +198,7 @@ class ChartHolder extends React.Component {
|
||||||
this.handleDeleteComponent = this.handleDeleteComponent.bind(this);
|
this.handleDeleteComponent = this.handleDeleteComponent.bind(this);
|
||||||
this.handleUpdateSliceName = this.handleUpdateSliceName.bind(this);
|
this.handleUpdateSliceName = this.handleUpdateSliceName.bind(this);
|
||||||
this.handleToggleFullSize = this.handleToggleFullSize.bind(this);
|
this.handleToggleFullSize = this.handleToggleFullSize.bind(this);
|
||||||
|
this.handlePostTransformProps = this.handlePostTransformProps.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -251,6 +253,11 @@ class ChartHolder extends React.Component {
|
||||||
setFullSizeChartId(isFullSize ? null : chartId);
|
setFullSizeChartId(isFullSize ? null : chartId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handlePostTransformProps(props) {
|
||||||
|
this.props.postAddSliceFromDashboard();
|
||||||
|
return props;
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { isFocused } = this.state;
|
const { isFocused } = this.state;
|
||||||
const {
|
const {
|
||||||
|
@ -364,6 +371,7 @@ class ChartHolder extends React.Component {
|
||||||
isComponentVisible={isComponentVisible}
|
isComponentVisible={isComponentVisible}
|
||||||
handleToggleFullSize={this.handleToggleFullSize}
|
handleToggleFullSize={this.handleToggleFullSize}
|
||||||
isFullSize={isFullSize}
|
isFullSize={isFullSize}
|
||||||
|
postTransformProps={this.handlePostTransformProps}
|
||||||
/>
|
/>
|
||||||
{editMode && (
|
{editMode && (
|
||||||
<HoverMenu position="top">
|
<HoverMenu position="top">
|
||||||
|
|
|
@ -62,6 +62,7 @@ function mapStateToProps(
|
||||||
PLACEHOLDER_DATASOURCE;
|
PLACEHOLDER_DATASOURCE;
|
||||||
const { colorScheme, colorNamespace } = dashboardState;
|
const { colorScheme, colorNamespace } = dashboardState;
|
||||||
const labelColors = dashboardInfo?.metadata?.label_colors || {};
|
const labelColors = dashboardInfo?.metadata?.label_colors || {};
|
||||||
|
const sharedLabelColors = dashboardInfo?.metadata?.shared_label_colors || {};
|
||||||
// note: this method caches filters if possible to prevent render cascades
|
// note: this method caches filters if possible to prevent render cascades
|
||||||
const formData = getFormDataWithExtraFilters({
|
const formData = getFormDataWithExtraFilters({
|
||||||
layout: dashboardLayout.present,
|
layout: dashboardLayout.present,
|
||||||
|
@ -76,6 +77,7 @@ function mapStateToProps(
|
||||||
nativeFilters,
|
nativeFilters,
|
||||||
dataMask,
|
dataMask,
|
||||||
labelColors,
|
labelColors,
|
||||||
|
sharedLabelColors,
|
||||||
});
|
});
|
||||||
|
|
||||||
formData.dashboardId = dashboardInfo.id;
|
formData.dashboardId = dashboardInfo.id;
|
||||||
|
@ -84,6 +86,7 @@ function mapStateToProps(
|
||||||
chart,
|
chart,
|
||||||
datasource,
|
datasource,
|
||||||
labelColors,
|
labelColors,
|
||||||
|
sharedLabelColors,
|
||||||
slice: sliceEntities.slices[id],
|
slice: sliceEntities.slices[id],
|
||||||
timeout: dashboardInfo.common.conf.SUPERSET_WEBSERVER_TIMEOUT,
|
timeout: dashboardInfo.common.conf.SUPERSET_WEBSERVER_TIMEOUT,
|
||||||
filters: getActiveFilters() || EMPTY_OBJECT,
|
filters: getActiveFilters() || EMPTY_OBJECT,
|
||||||
|
|
|
@ -37,6 +37,7 @@ import {
|
||||||
setDirectPathToChild,
|
setDirectPathToChild,
|
||||||
setActiveTabs,
|
setActiveTabs,
|
||||||
setFullSizeChartId,
|
setFullSizeChartId,
|
||||||
|
postAddSliceFromDashboard,
|
||||||
} from 'src/dashboard/actions/dashboardState';
|
} from 'src/dashboard/actions/dashboardState';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
|
@ -111,6 +112,7 @@ function mapDispatchToProps(dispatch) {
|
||||||
setFullSizeChartId,
|
setFullSizeChartId,
|
||||||
setActiveTabs,
|
setActiveTabs,
|
||||||
logEvent,
|
logEvent,
|
||||||
|
postAddSliceFromDashboard,
|
||||||
},
|
},
|
||||||
dispatch,
|
dispatch,
|
||||||
);
|
);
|
||||||
|
|
|
@ -17,7 +17,14 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import React, { FC, useRef, useEffect, useState } from 'react';
|
import React, { FC, useRef, useEffect, useState } from 'react';
|
||||||
import { FeatureFlag, isFeatureEnabled, t, useTheme } from '@superset-ui/core';
|
import {
|
||||||
|
CategoricalColorNamespace,
|
||||||
|
FeatureFlag,
|
||||||
|
getSharedLabelColor,
|
||||||
|
isFeatureEnabled,
|
||||||
|
t,
|
||||||
|
useTheme,
|
||||||
|
} from '@superset-ui/core';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { Global } from '@emotion/react';
|
import { Global } from '@emotion/react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
@ -222,6 +229,18 @@ const DashboardPage: FC = () => {
|
||||||
return () => {};
|
return () => {};
|
||||||
}, [css]);
|
}, [css]);
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() => () => {
|
||||||
|
// clean up label color
|
||||||
|
const categoricalNamespace = CategoricalColorNamespace.getNamespace(
|
||||||
|
metadata?.color_namespace,
|
||||||
|
);
|
||||||
|
categoricalNamespace.resetColors();
|
||||||
|
getSharedLabelColor().clear();
|
||||||
|
},
|
||||||
|
[metadata?.color_namespace],
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (datasetsApiError) {
|
if (datasetsApiError) {
|
||||||
addDangerToast(
|
addDangerToast(
|
||||||
|
|
|
@ -39,6 +39,7 @@ import {
|
||||||
UNSET_FOCUSED_FILTER_FIELD,
|
UNSET_FOCUSED_FILTER_FIELD,
|
||||||
SET_ACTIVE_TABS,
|
SET_ACTIVE_TABS,
|
||||||
SET_FULL_SIZE_CHART_ID,
|
SET_FULL_SIZE_CHART_ID,
|
||||||
|
RESET_SLICE,
|
||||||
ON_FILTERS_REFRESH,
|
ON_FILTERS_REFRESH,
|
||||||
ON_FILTERS_REFRESH_SUCCESS,
|
ON_FILTERS_REFRESH_SUCCESS,
|
||||||
} from '../actions/dashboardState';
|
} from '../actions/dashboardState';
|
||||||
|
@ -58,6 +59,7 @@ export default function dashboardStateReducer(state = {}, action) {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
sliceIds: Array.from(updatedSliceIds),
|
sliceIds: Array.from(updatedSliceIds),
|
||||||
|
updateSlice: true,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[REMOVE_SLICE]() {
|
[REMOVE_SLICE]() {
|
||||||
|
@ -70,6 +72,12 @@ export default function dashboardStateReducer(state = {}, action) {
|
||||||
sliceIds: Array.from(updatedSliceIds),
|
sliceIds: Array.from(updatedSliceIds),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
[RESET_SLICE]() {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
updateSlice: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
[TOGGLE_FAVE_STAR]() {
|
[TOGGLE_FAVE_STAR]() {
|
||||||
return { ...state, isStarred: action.isStarred };
|
return { ...state, isStarred: action.isStarred };
|
||||||
},
|
},
|
||||||
|
@ -116,6 +124,7 @@ export default function dashboardStateReducer(state = {}, action) {
|
||||||
maxUndoHistoryExceeded: false,
|
maxUndoHistoryExceeded: false,
|
||||||
editMode: false,
|
editMode: false,
|
||||||
updatedColorScheme: false,
|
updatedColorScheme: false,
|
||||||
|
updateSlice: false,
|
||||||
// server-side returns last_modified_time for latest change
|
// server-side returns last_modified_time for latest change
|
||||||
lastModifiedTime: action.lastModifiedTime,
|
lastModifiedTime: action.lastModifiedTime,
|
||||||
};
|
};
|
||||||
|
|
|
@ -28,6 +28,7 @@ import {
|
||||||
TOGGLE_EXPAND_SLICE,
|
TOGGLE_EXPAND_SLICE,
|
||||||
TOGGLE_FAVE_STAR,
|
TOGGLE_FAVE_STAR,
|
||||||
UNSET_FOCUSED_FILTER_FIELD,
|
UNSET_FOCUSED_FILTER_FIELD,
|
||||||
|
RESET_SLICE,
|
||||||
} from 'src/dashboard/actions/dashboardState';
|
} from 'src/dashboard/actions/dashboardState';
|
||||||
|
|
||||||
import dashboardStateReducer from 'src/dashboard/reducers/dashboardState';
|
import dashboardStateReducer from 'src/dashboard/reducers/dashboardState';
|
||||||
|
@ -43,7 +44,7 @@ describe('dashboardState reducer', () => {
|
||||||
{ sliceIds: [1] },
|
{ sliceIds: [1] },
|
||||||
{ type: ADD_SLICE, slice: { slice_id: 2 } },
|
{ type: ADD_SLICE, slice: { slice_id: 2 } },
|
||||||
),
|
),
|
||||||
).toEqual({ sliceIds: [1, 2] });
|
).toEqual({ sliceIds: [1, 2], updateSlice: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should remove a slice', () => {
|
it('should remove a slice', () => {
|
||||||
|
@ -55,6 +56,12 @@ describe('dashboardState reducer', () => {
|
||||||
).toEqual({ sliceIds: [1], filters: {} });
|
).toEqual({ sliceIds: [1], filters: {} });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should reset updateSlice', () => {
|
||||||
|
expect(
|
||||||
|
dashboardStateReducer({ updateSlice: true }, { type: RESET_SLICE }),
|
||||||
|
).toEqual({ updateSlice: false });
|
||||||
|
});
|
||||||
|
|
||||||
it('should toggle fav star', () => {
|
it('should toggle fav star', () => {
|
||||||
expect(
|
expect(
|
||||||
dashboardStateReducer(
|
dashboardStateReducer(
|
||||||
|
|
|
@ -46,6 +46,7 @@ export interface GetFormDataWithExtraFiltersArguments {
|
||||||
dataMask: DataMaskStateWithId;
|
dataMask: DataMaskStateWithId;
|
||||||
nativeFilters: NativeFiltersState;
|
nativeFilters: NativeFiltersState;
|
||||||
labelColors?: Record<string, string>;
|
labelColors?: Record<string, string>;
|
||||||
|
sharedLabelColors?: Record<string, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// this function merge chart's formData with dashboard filters value,
|
// this function merge chart's formData with dashboard filters value,
|
||||||
|
@ -63,6 +64,7 @@ export default function getFormDataWithExtraFilters({
|
||||||
layout,
|
layout,
|
||||||
dataMask,
|
dataMask,
|
||||||
labelColors,
|
labelColors,
|
||||||
|
sharedLabelColors,
|
||||||
}: GetFormDataWithExtraFiltersArguments) {
|
}: GetFormDataWithExtraFiltersArguments) {
|
||||||
// if dashboard metadata + filters have not changed, use cache if possible
|
// if dashboard metadata + filters have not changed, use cache if possible
|
||||||
const cachedFormData = cachedFormdataByChart[sliceId];
|
const cachedFormData = cachedFormdataByChart[sliceId];
|
||||||
|
@ -77,6 +79,9 @@ export default function getFormDataWithExtraFilters({
|
||||||
areObjectsEqual(cachedFormData?.label_colors, labelColors, {
|
areObjectsEqual(cachedFormData?.label_colors, labelColors, {
|
||||||
ignoreUndefined: true,
|
ignoreUndefined: true,
|
||||||
}) &&
|
}) &&
|
||||||
|
areObjectsEqual(cachedFormData?.shared_label_colors, sharedLabelColors, {
|
||||||
|
ignoreUndefined: true,
|
||||||
|
}) &&
|
||||||
!!cachedFormData &&
|
!!cachedFormData &&
|
||||||
areObjectsEqual(cachedFormData?.dataMask, dataMask, {
|
areObjectsEqual(cachedFormData?.dataMask, dataMask, {
|
||||||
ignoreUndefined: true,
|
ignoreUndefined: true,
|
||||||
|
@ -108,6 +113,7 @@ export default function getFormDataWithExtraFilters({
|
||||||
const formData = {
|
const formData = {
|
||||||
...chart.formData,
|
...chart.formData,
|
||||||
label_colors: labelColors,
|
label_colors: labelColors,
|
||||||
|
shared_label_colors: sharedLabelColors,
|
||||||
...(colorScheme && { color_scheme: colorScheme }),
|
...(colorScheme && { color_scheme: colorScheme }),
|
||||||
extra_filters: getEffectiveExtraFilters(filters),
|
extra_filters: getEffectiveExtraFilters(filters),
|
||||||
...extraData,
|
...extraData,
|
||||||
|
|
|
@ -156,13 +156,23 @@ export class ExploreChartHeader extends React.PureComponent {
|
||||||
|
|
||||||
if (dashboard && dashboard.json_metadata) {
|
if (dashboard && dashboard.json_metadata) {
|
||||||
// setting the chart to use the dashboard custom label colors if any
|
// setting the chart to use the dashboard custom label colors if any
|
||||||
const labelColors =
|
const metadata = JSON.parse(dashboard.json_metadata);
|
||||||
JSON.parse(dashboard.json_metadata).label_colors || {};
|
const sharedLabelColors = metadata.shared_label_colors || {};
|
||||||
|
const customLabelColors = metadata.label_colors || {};
|
||||||
|
const mergedLabelColors = {
|
||||||
|
...sharedLabelColors,
|
||||||
|
...customLabelColors,
|
||||||
|
};
|
||||||
|
|
||||||
const categoricalNamespace =
|
const categoricalNamespace =
|
||||||
CategoricalColorNamespace.getNamespace();
|
CategoricalColorNamespace.getNamespace();
|
||||||
|
|
||||||
Object.keys(labelColors).forEach(label => {
|
Object.keys(mergedLabelColors).forEach(label => {
|
||||||
categoricalNamespace.setColor(label, labelColors[label]);
|
categoricalNamespace.setColor(
|
||||||
|
label,
|
||||||
|
mergedLabelColors[label],
|
||||||
|
metadata.color_scheme,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -265,6 +265,7 @@ class DashboardDAO(BaseDAO):
|
||||||
md["refresh_frequency"] = data.get("refresh_frequency", 0)
|
md["refresh_frequency"] = data.get("refresh_frequency", 0)
|
||||||
md["color_scheme"] = data.get("color_scheme", "")
|
md["color_scheme"] = data.get("color_scheme", "")
|
||||||
md["label_colors"] = data.get("label_colors", {})
|
md["label_colors"] = data.get("label_colors", {})
|
||||||
|
md["shared_label_colors"] = data.get("shared_label_colors", {})
|
||||||
|
|
||||||
dashboard.json_metadata = json.dumps(md)
|
dashboard.json_metadata = json.dumps(md)
|
||||||
|
|
||||||
|
|
|
@ -128,6 +128,7 @@ class DashboardJSONMetadataSchema(Schema):
|
||||||
color_namespace = fields.Str(allow_none=True)
|
color_namespace = fields.Str(allow_none=True)
|
||||||
positions = fields.Dict(allow_none=True)
|
positions = fields.Dict(allow_none=True)
|
||||||
label_colors = fields.Dict()
|
label_colors = fields.Dict()
|
||||||
|
shared_label_colors = fields.Dict()
|
||||||
# used for v0 import/export
|
# used for v0 import/export
|
||||||
import_time = fields.Integer()
|
import_time = fields.Integer()
|
||||||
remote_id = fields.Integer()
|
remote_id = fields.Integer()
|
||||||
|
|
|
@ -72,7 +72,7 @@ class TestDashboardApi(SupersetTestCase, ApiOwnersTestCaseMixin, InsertChartMixi
|
||||||
"slug": "slug1_changed",
|
"slug": "slug1_changed",
|
||||||
"position_json": '{"b": "B"}',
|
"position_json": '{"b": "B"}',
|
||||||
"css": "css_changed",
|
"css": "css_changed",
|
||||||
"json_metadata": '{"refresh_frequency": 30, "timed_refresh_immune_slices": [], "expanded_slices": {}, "color_scheme": "", "label_colors": {}}',
|
"json_metadata": '{"refresh_frequency": 30, "timed_refresh_immune_slices": [], "expanded_slices": {}, "color_scheme": "", "label_colors": {}, "shared_label_colors": {}}',
|
||||||
"published": False,
|
"published": False,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue