@gooddata/react-components
Version:
GoodData.UI - A powerful JavaScript library for building analytical applications
334 lines (295 loc) • 9.04 kB
text/typescript
// (C) 2007-2020 GoodData Corporation
import isEmpty = require("lodash/isEmpty");
import isEqual = require("lodash/isEqual");
import isString = require("lodash/isString");
import range = require("lodash/range");
import { AFM, Execution } from "@gooddata/typings";
import { IColor, IColorItem } from "@gooddata/gooddata-js";
import { IColorPalette, IColorPaletteItem, IColorMapping } from "../../../interfaces/Config";
import { IHeaderPredicate, IHeaderPredicateContext } from "../../../interfaces/HeaderPredicate";
import {
IMappingHeader,
isMappingHeaderAttributeItem,
isMappingHeaderAttribute,
} from "../../../interfaces/MappingHeader";
import { getMappingHeaderLocalIdentifier } from "../../../helpers/mappingHeader";
export const WHITE = "rgb(255, 255, 255)";
export const BLACK = "rgb(0, 0, 0)";
export const GRAY = "rgb(201, 213, 223)";
export const AXIS_LINE_COLOR = "#d5d5d5";
export const TRANSPARENT = "transparent";
export const DEFAULT_COLORS = [
"rgb(20,178,226)",
"rgb(0,193,141)",
"rgb(229,77,66)",
"rgb(241,134,0)",
"rgb(171,85,163)",
"rgb(244,213,33)",
"rgb(148,161,174)",
"rgb(107,191,216)",
"rgb(181,136,177)",
"rgb(238,135,128)",
"rgb(241,171,84)",
"rgb(133,209,188)",
"rgb(41,117,170)",
"rgb(4,140,103)",
"rgb(181,60,51)",
"rgb(163,101,46)",
"rgb(140,57,132)",
"rgb(136,219,244)",
"rgb(189,234,222)",
"rgb(239,197,194)",
];
export const DEFAULT_COLOR_PALETTE = [
{
guid: "1",
fill: { r: 20, g: 178, b: 226 },
},
{
guid: "2",
fill: { r: 0, g: 193, b: 141 },
},
{
guid: "3",
fill: { r: 229, g: 77, b: 66 },
},
{
guid: "4",
fill: { r: 241, g: 134, b: 0 },
},
{
guid: "5",
fill: { r: 171, g: 85, b: 163 },
},
{
guid: "6",
fill: { r: 244, g: 213, b: 33 },
},
{
guid: "7",
fill: { r: 148, g: 161, b: 174 },
},
{
guid: "8",
fill: { r: 107, g: 191, b: 216 },
},
{
guid: "9",
fill: { r: 181, g: 136, b: 177 },
},
{
guid: "10",
fill: { r: 238, g: 135, b: 128 },
},
{
guid: "11",
fill: { r: 241, g: 171, b: 84 },
},
{
guid: "12",
fill: { r: 133, g: 209, b: 188 },
},
{
guid: "13",
fill: { r: 41, g: 117, b: 170 },
},
{
guid: "14",
fill: { r: 4, g: 140, b: 103 },
},
{
guid: "15",
fill: { r: 181, g: 60, b: 51 },
},
{
guid: "16",
fill: { r: 163, g: 101, b: 46 },
},
{
guid: "17",
fill: { r: 140, g: 57, b: 132 },
},
{
guid: "18",
fill: { r: 136, g: 219, b: 244 },
},
{
guid: "19",
fill: { r: 189, g: 234, b: 222 },
},
{
guid: "20",
fill: { r: 239, g: 197, b: 194 },
},
];
export const HEATMAP_BLUE_COLOR_PALETTE = [
"rgb(255,255,255)",
"rgb(197,236,248)",
"rgb(138,217,241)",
"rgb(79,198,234)",
"rgb(20,178,226)",
"rgb(22,151,192)",
"rgb(0,110,145)",
];
export const DEFAULT_HEATMAP_BLUE_COLOR: IColor = {
r: 0,
g: 110,
b: 145,
};
export const DEFAULT_BULLET_GRAY_COLOR: IColor = {
r: 217,
g: 220,
b: 226,
};
function lighter(color: number, percent: number) {
const t = percent < 0 ? 0 : 255;
const p = Math.abs(percent);
return Math.round((t - color) * p) + color;
}
function formatColor(red: number, green: number, blue: number, opacity: number = 1): string {
if (opacity === 1) {
return `rgb(${red},${green},${blue})`;
}
return `rgba(${red},${green},${blue},${opacity})`;
}
export function parseRGBColorCode(color: string) {
const f = color.split(",");
const R = parseInt(f[0].slice(4), 10);
const G = parseInt(f[1], 10);
const B = parseInt(f[2], 10);
return { R, G, B };
}
/**
* Source:
* http://stackoverflow.com/questions/5560248/programmatically-lighten-or-darken-a-hex-color-or-rgb-and-blend-colors
*/
export function getLighterColor(color: string, percent: number) {
const { R, G, B } = parseRGBColorCode(color);
return formatColor(lighter(R, percent), lighter(G, percent), lighter(B, percent));
}
export function getLighterColorFromRGB(color: IColor, percent: number) {
const { r, g, b } = color;
return {
r: lighter(r, percent),
g: lighter(g, percent),
b: lighter(b, percent),
};
}
export function normalizeColorToRGB(color: string) {
const hexPattern = /#([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})/i;
return color.replace(hexPattern, (_prefix: string, r: string, g: string, b: string) => {
return `rgb(${[r, g, b].map(value => parseInt(value, 16).toString(10)).join(", ")})`;
});
}
export function getColorPaletteFromColors(colors: string[]): IColorPalette {
try {
return colors.map((color: string, index: number) => {
const { R, G, B } = parseRGBColorCode(normalizeColorToRGB(color));
if (isNaN(R) || isNaN(G) || isNaN(B)) {
throw Error;
}
return {
guid: String(index),
fill: {
r: R,
g: G,
b: B,
},
};
});
} catch (_ignored) {
return DEFAULT_COLOR_PALETTE;
}
}
export function getRgbString(color: IColorPaletteItem): string {
return `rgb(${color.fill.r},${color.fill.g},${color.fill.b})`;
}
export function getValidColorPalette(colors?: string[], colorPalette?: IColorPalette) {
return isEmpty(colorPalette)
? isEmpty(colors)
? DEFAULT_COLOR_PALETTE
: getColorPaletteFromColors(colors)
: colorPalette;
}
export function isCustomPalette(palette: IColorPalette) {
return !isEqual(palette, DEFAULT_COLOR_PALETTE);
}
export function getColorFromMapping(
mappingHeader: IMappingHeader,
colorMapping: IColorMapping[],
executionResponse: Execution.IExecutionResponse,
afm: AFM.IAfm,
): IColorItem {
if (!colorMapping) {
return undefined;
}
const mapping = colorMapping.find(item => item.predicate(mappingHeader, { afm, executionResponse }));
return mapping && mapping.color;
}
export function getColorByGuid(colorPalette: IColorPalette, guid: string, index: number) {
const inPalette = colorPalette.find((item: any) => item.guid === guid);
return inPalette ? inPalette.fill : colorPalette[index % colorPalette.length].fill;
}
export function getRgbStringFromRGB(color: IColor) {
return `rgb(${color.r},${color.g},${color.b})`;
}
export function getColorMappingPredicate(idOrUri: string): IHeaderPredicate {
return (header: IMappingHeader, _context: IHeaderPredicateContext): boolean => {
if (isMappingHeaderAttributeItem(header)) {
return idOrUri ? idOrUri === header.attributeHeaderItem.uri : false;
}
if (isMappingHeaderAttribute(header)) {
return idOrUri ? idOrUri === header.attributeHeader.uri : false;
}
const headerLocalIdentifier = getMappingHeaderLocalIdentifier(header);
return headerLocalIdentifier ? headerLocalIdentifier === idOrUri : false;
};
}
function getCalculatedChannel(channel: number, index: number, step: number): number {
return Math.trunc(channel + index * step);
}
function getCalculatedColors(
count: number,
channels: number[],
steps: number[],
opacity: number = 1,
): string[] {
return range(1, count).map(
(index: number): string =>
formatColor(
getCalculatedChannel(channels[0], index, steps[0]),
getCalculatedChannel(channels[1], index, steps[1]),
getCalculatedChannel(channels[2], index, steps[2]),
opacity,
),
);
}
function getRGBColorCode(color: string | IColor): IColor {
if (isString(color)) {
const { R: r, G: g, B: b } = parseRGBColorCode(color);
return {
r,
g,
b,
};
}
return color;
}
export function getColorPalette(baseColor: string | IColor, opacity: number = 1): string[] {
const colorItemsCount = 6;
const { r, g, b } = getRGBColorCode(baseColor);
const channels = [r, g, b];
const steps = channels.map(channel => (255 - channel) / colorItemsCount);
const generatedColors = getCalculatedColors(colorItemsCount, channels, steps, opacity);
return [...generatedColors.reverse(), formatColor(r, g, b, opacity)];
}
export function rgbToRgba(color: string, opacity: number = 1): string {
const { R, G, B } = parseRGBColorCode(color);
return formatColor(R, G, B, opacity);
}
// For re-exporting in index.ts
// Create object here since TSC can't reexport external types used by getColorMappingPredicate
export default {
getColorByGuid,
getColorMappingPredicate,
};