From f45931fac8c90f1dfd4becda699726d7892a923e Mon Sep 17 00:00:00 2001 From: Ryan Keairns Date: Sat, 22 Sep 2018 13:56:50 -0500 Subject: [PATCH] adds colorPalette service --- CHANGELOG.md | 2 +- src-docs/src/components/guide_components.scss | 32 ++++ src-docs/src/routes.js | 10 +- .../src/views/color_palette/color_palette.js | 38 ++++ .../color_palette/color_palette_custom.js | 52 ++++++ .../color_palette/color_palette_example.js | 79 +++++++++ .../color_palette/color_palette_histogram.js | 56 ++++++ src/services/color/color_palette.js | 165 ++++++++++++++++++ src/services/color/eui_palettes.js | 77 ++++++++ src/services/color/index.js | 2 + src/services/index.js | 2 + 11 files changed, 511 insertions(+), 4 deletions(-) create mode 100644 src-docs/src/views/color_palette/color_palette.js create mode 100644 src-docs/src/views/color_palette/color_palette_custom.js create mode 100644 src-docs/src/views/color_palette/color_palette_example.js create mode 100644 src-docs/src/views/color_palette/color_palette_histogram.js create mode 100644 src/services/color/color_palette.js create mode 100644 src/services/color/eui_palettes.js diff --git a/CHANGELOG.md b/CHANGELOG.md index c4ac55d76596..66abc25eb60d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ## [`master`](https://github.com/elastic/eui/tree/master) -No public interface changes since `4.2.0`. +- Added a new `colorPalette` service for retrieving and generating color arrays for use in charts ([#1209](https://github.com/elastic/eui/pull/1209)) ## [`4.2.0`](https://github.com/elastic/eui/tree/v4.2.0) diff --git a/src-docs/src/components/guide_components.scss b/src-docs/src/components/guide_components.scss index 87aa190e3969..885858863c86 100644 --- a/src-docs/src/components/guide_components.scss +++ b/src-docs/src/components/guide_components.scss @@ -150,6 +150,21 @@ $guideZLevelHighest: $euiZLevel9 + 1000; outline: solid 2px purple; } +.guideColorPalette__swatch { + + span { + height: $euiSize; + width: $euiSizeL; + } + + &:first-child span { + border-radius: $euiBorderRadius 0 0 $euiBorderRadius; + } + + &:last-child span { + border-radius: 0 $euiBorderRadius $euiBorderRadius 0; + } +} @import "../views/guidelines/index"; @import "guide_section/index"; @@ -193,4 +208,21 @@ $guideZLevelHighest: $euiZLevel9 + 1000; .guidePageContent { margin-left: 0; } + + .euiFlexGroup--responsive > .euiFlexItem.guideColorPalette__swatch { + margin-bottom: 0 !important; + + span { + height: $euiSize; + width: $euiSizeL; + } + + &:first-child span { + border-radius: $euiBorderRadius $euiBorderRadius 0 0; + } + + &:last-child span { + border-radius: 0 0 $euiBorderRadius $euiBorderRadius; + } + } } diff --git a/src-docs/src/routes.js b/src-docs/src/routes.js index 032c8d639f12..cc725d307c61 100644 --- a/src-docs/src/routes.js +++ b/src-docs/src/routes.js @@ -36,6 +36,9 @@ import WritingGuidelines // Services +import { ColorPaletteExample } + from './views/color_palette/color_palette_example'; + import { IsColorDarkExample } from './views/is_color_dark/is_color_dark_example'; @@ -395,17 +398,18 @@ const navigation = [{ name: 'Utilities', items: [ AccessibilityExample, + ColorPaletteExample, CopyExample, - ResponsiveExample, + UtilityClassesExample, DelayHideExample, ErrorBoundaryExample, HighlightExample, IsColorDarkExample, + MutationObserverExample, OutsideClickDetectorExample, PortalExample, + ResponsiveExample, ToggleExample, - UtilityClassesExample, - MutationObserverExample, WindowEventExample, ].map(example => createExample(example)), }, { diff --git a/src-docs/src/views/color_palette/color_palette.js b/src-docs/src/views/color_palette/color_palette.js new file mode 100644 index 000000000000..de6251c7d7b9 --- /dev/null +++ b/src-docs/src/views/color_palette/color_palette.js @@ -0,0 +1,38 @@ +import React, { Fragment } from 'react'; + +import { + EuiFlexGroup, + EuiFlexItem, + EuiTitle, + EuiSpacer, +} from '../../../../src/components'; + +import { + colorPalette, + palettes, +} from '../../../../src/services'; + +const availablePalettes = Object.keys(palettes); + +export default () => ( + + { + availablePalettes.map((paletteName, i) => ( +
+

{paletteName}

+ + + { + colorPalette(paletteName).map((hexCode, j) => ( + + + + )) + } + + +
+ )) + } +
+); diff --git a/src-docs/src/views/color_palette/color_palette_custom.js b/src-docs/src/views/color_palette/color_palette_custom.js new file mode 100644 index 000000000000..097dd348cbd3 --- /dev/null +++ b/src-docs/src/views/color_palette/color_palette_custom.js @@ -0,0 +1,52 @@ +import React, { Fragment } from 'react'; + +import { + EuiFlexGroup, + EuiFlexItem, + EuiTitle, + EuiSpacer, +} from '../../../../src/components'; + +import { + colorPalette, +} from '../../../../src/services'; + +export default () => ( + +

Custom red to blue

+ + + { + colorPalette('custom', 'FF0000', '#00FFFF', 25).map((hexCode, j) => ( + + + + )) + } + + +

Custom yellow to green

+ + + { + colorPalette('custom', '#F7EE55', '#4EB265', 20).map((hexCode, k) => ( + + + + )) + } + + +

Custom green to red

+ + + { + colorPalette('custom', '#4EB265', '#920000').map((hexCode, l) => ( + + + + )) + } + +
+); diff --git a/src-docs/src/views/color_palette/color_palette_example.js b/src-docs/src/views/color_palette/color_palette_example.js new file mode 100644 index 000000000000..727ee96dce94 --- /dev/null +++ b/src-docs/src/views/color_palette/color_palette_example.js @@ -0,0 +1,79 @@ +import React from 'react'; + +import { renderToHtml } from '../../services'; + +import { + GuideSectionTypes, +} from '../../components'; + +import { + EuiCode, +} from '../../../../src/components'; + +import ColorPalette from './color_palette'; +const colorPaletteSource = require('!!raw-loader!./color_palette'); +const colorPaletteHtml = renderToHtml(ColorPalette); + +import ColorPaletteCustom from './color_palette_custom'; +const colorPaletteCustomSource = require('!!raw-loader!./color_palette_custom'); +const colorPaletteCustomHtml = renderToHtml(ColorPaletteCustom); + +import ColorPaletteHistogram from './color_palette_histogram'; +const colorPaletteHistogramSource = require('!!raw-loader!./color_palette_histogram'); +const colorPaletteHistogramHtml = renderToHtml(ColorPaletteHistogram); + +export const ColorPaletteExample = { + title: 'Color Palettes', + sections: [{ + title: 'Preset color palettes', + source: [{ + type: GuideSectionTypes.JS, + code: colorPaletteSource, + }, { + type: GuideSectionTypes.HTML, + code: colorPaletteHtml, + }], + text: ( +

+ Use the colorPalette service to obtain an array of + hexidecimal color codes for a given palette such as + colorPalette('color_blind'), then apply them to UI + elements such as charts. +

+ ), + demo: , + }, { + title: 'Custom color palettes', + source: [{ + type: GuideSectionTypes.JS, + code: colorPaletteCustomSource, + }, { + type: GuideSectionTypes.HTML, + code: colorPaletteCustomHtml, + }], + text: ( +

+ Generate a custom palette of any length from two hexidecimal color + codes such as + colorPalette('custom', 'FF0000', '#00FFFF', 25). +

+ ), + demo: , + }, { + title: 'Chart example', + source: [{ + type: GuideSectionTypes.JS, + code: colorPaletteHistogramSource, + }, { + type: GuideSectionTypes.HTML, + code: colorPaletteHistogramHtml, + }], + text: ( +

+ Apply the results of colorPalette to the + color prop of EUI chart components. +

+ ), + demo: , + }], +}; diff --git a/src-docs/src/views/color_palette/color_palette_histogram.js b/src-docs/src/views/color_palette/color_palette_histogram.js new file mode 100644 index 000000000000..4209ac2fcd0b --- /dev/null +++ b/src-docs/src/views/color_palette/color_palette_histogram.js @@ -0,0 +1,56 @@ +import React, { Component, Fragment } from 'react'; + +import { + EuiSeriesChart, + EuiHistogramSeries, + EuiSeriesChartUtils, +} from '../../../../src/experimental'; +import { + colorPalette, +} from '../../../../src/services/color/color_palette'; + +const { SCALE } = EuiSeriesChartUtils; +const timestamp = Date.now(); +const ONE_HOUR = 3600000; +const margins = { + top: 10, + left: 80, + right: 0, + bottom: 20, +}; +const colors = colorPalette('custom', 'FF0000', '#00FFFF', 6); + +function randomizeData(size = 24, max = 8) { + return new Array(size) + .fill(0) + .map((d, i) => ({ + x0: ONE_HOUR * i, + x: ONE_HOUR * (i + 1), + y: Math.floor(Math.random() * max), + })) + .map(el => ({ + x0: el.x0 + timestamp, + x: el.x + timestamp, + y: el.y, + })); +} +function buildData(series) { + const max = Math.ceil(Math.random() * 1000000); + return new Array(series).fill(0).map(() => randomizeData(20, max)); +} +export default class Example extends Component { + state = { + series: 6, + data: buildData(6), + }; + render() { + const { data } = this.state; + return ( + + + {data.map((d, i) => )} + + + ); + } +} diff --git a/src/services/color/color_palette.js b/src/services/color/color_palette.js new file mode 100644 index 000000000000..fdfbbad60689 --- /dev/null +++ b/src/services/color/color_palette.js @@ -0,0 +1,165 @@ +import { palettes } from './eui_palettes'; + +/** + * This function takes a color palette name and returns an array of hex color + * codes for use in UI elements such as charts. + * + * @param {string} paletteName Required. The name of the palette being requested + * Set paletteName to "custom" and provide two hex color codes for a custom palette + * @param {string} hexStart The beginning hexidecimal color code + * @param {string} hexEnd The ending hexidecimal color code + * @param {number} len The number of colors in the resulting array (default 10) + * @returns {Array} Returns an array of hexidecimal color codes + */ + +function colorPalette(paletteName, hexStart, hexEnd, len = 10) { + if (typeof paletteName !== 'undefined' && paletteName !== 'custom') { + try { + const palette = palettes[paletteName]; // get the palette + const hexColors = palette.colors; // get the palette colors + return hexColors; + } catch(e) { + const availablePalettes = Object.keys(palettes); + throw new Error(`${paletteName} is not a valid palette name. Please select from ${availablePalettes}`); + } + } else if (paletteName === 'custom') { + if (isHex(hexStart) && isHex(hexEnd)) { + const hex1 = formatHex(hexStart); + const hex2 = formatHex(hexEnd); + const customColors = generatePalette(hex1, hex2, len); // generate custom palette + return customColors; + } else { + throw new Error('Please provide two valid hex color codes.'); + } + } +} + +/** + * Check if argument is a valid 3 or 6 character hexidecimal color code + */ +function isHex(value) { + return /^#?([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(value); +} + +/** + * Check if value can be interpreted as a number for subsequent math operations + */ +function isValid(c) { + let valid = 'n'; + if ((!isNaN(c[0])) && (!isNaN(c[1])) && (!isNaN(c[2]))) {valid = 'y';} + return valid; +} + +/** + * Calculate and construct the hexideciaml color code from RGB values + */ +function createHex(c) { + let result = ''; + let k = 0; + let val = 0; + let piece; + const d = 1; + const base = 16; + for (k = 0; k < 3; k++) { + val = Math.round(c[k] / d); + piece = val.toString(base); // Converts to radix 16 based value (0-9, A-F) + if (piece.length < 2) {piece = `0${piece}`;} + result = result + piece; + } + result = `#${result.toUpperCase()}`; // Return in #RRGGBB fomat + return result; +} + +/** + * Create the color object for manipulation by other functions + */ +class Color { + constructor(r, g, b) { + this.r = r; // Red value + this.g = g; // Green value + this.b = b; // Blue value + this.collection = new Array(r, g, b); + this.valid = isValid(this.collection); + this.text = createHex(this.collection); + } +} + +/** + * Convert hexideciaml color into an array of RGB integer values + */ +function colorParse(color) { + const m = 1; + const base = 16; + let a; + let b; + let c = color.toUpperCase(); + let col = c.replace(/[\#\(]*/i, ''); + + if (col.length === 3) { + a = col.substr(0, 1); + b = col.substr(1, 1); + c = col.substr(2, 1); + col = a + a + b + b + c + c; + } + const num = new Array(col.substr(0, 2), col.substr(2, 2), col.substr(4, 2)); + const ret = new Array(parseInt(num[0], base) * m, parseInt(num[1], base) * m, parseInt(num[2], base) * m); + return(ret); +} + +/** + * Format hexideciaml inputs to #RRGGBB + */ +function formatHex(hex) { + let cleanHex = hex; + if (cleanHex.length === 3 || cleanHex.length === 6) { + cleanHex = `#${cleanHex}`; + } + if (cleanHex.length === 4) { + cleanHex = cleanHex.split(''); + cleanHex = cleanHex[0] + cleanHex[1] + cleanHex[1] + cleanHex[2] + cleanHex[2] + cleanHex[3] + cleanHex[3]; + } + return cleanHex; +} + +/** + * Calculate the step increment for each piece of the hexidecimal color code + */ +function stepCalc(st, cStart, cEnd) { + const steps = st; + const step = new Array(3); + step[0] = (cEnd.r - cStart.r) / steps; // Calc step amount for red value + step[1] = (cEnd.g - cStart.g) / steps; // Calc step amount for green value + step[2] = (cEnd.b - cStart.b) / steps; // Calc step amount for blue value + + return step; +} + +/** + * Generate a custom plette array from two hexidecimal color code values + */ +function generatePalette(start, end, len) { + const colorArray = new Array(); + const hexPalette = new Array(); + const count = len - 1; + const startHex = colorParse(start); + const endHex = colorParse(end); + let i = 1; + let step = new Array(3); + colorArray[0] = new Color(startHex[0], startHex[1], startHex[2]); // first color + colorArray[count] = new Color(endHex[0], endHex[1], endHex[2]); // last color + step = stepCalc(count, colorArray[0], colorArray[count]); // get array of step increments + hexPalette[0] = colorArray[0].text; // set the first index value of the array + for (i = 1; i < count; i++) { + // set the intermediate index values of the array + const r = (colorArray[0].r + (step[0] * i)); + const g = (colorArray[0].g + (step[1] * i)); + const b = (colorArray[0].b + (step[2] * i)); + colorArray[i] = new Color(r, g, b); + hexPalette[i] = colorArray[i].text; + } // all the colors in between + hexPalette[count] = colorArray[count].text; // set the last index value of the array + + return hexPalette; +} + +export { colorPalette }; diff --git a/src/services/color/eui_palettes.js b/src/services/color/eui_palettes.js new file mode 100644 index 000000000000..bdc70ffc37e1 --- /dev/null +++ b/src/services/color/eui_palettes.js @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const palettes = { + color_blind: { + colors: [ + '#1ea593', + '#2b70f7', + '#ce0060', + '#38007e', + '#fca5d3', + '#f37020', + '#e49e29', + '#b0916f', + '#7b000b', + '#34130c', + ], + }, + color_blind_15: { + colors: [ + '#000000', + '#004949', + '#009292', + '#ff6db6', + '#ffb6db', + '#490092', + '#006ddb', + '#b66dff', + '#6db6ff', + '#b6dbff', + '#920000', + '#924900', + '#db6d00', + '#24ff24', + '#ffff6d', + ] + }, + eui_colors: { + colors: [ + '#0079A5', + '#017F75', + '#E5830E', + '#A30000', + '#DD0A73', + ] + }, + eui_colors_light: { + colors: [ + '#009ED8', + '#01B2A4', + '#F39B33', + '#D60000', + '#F5258C', + ] + }, + spectrum: { + colors: [ + '#882E72', + '#B178A6', + '#D6C1DE', + '#1965B0', + '#5289C7', + '#7BAFDE', + '#4EB265', + '#90C987', + '#CAE0AB', + '#F7EE55', + '#F6C141', + '#F1932D', + '#E8601C', + '#DC050C', + ] + } +}; diff --git a/src/services/color/index.js b/src/services/color/index.js index 0c4908fe99b4..c31c6b8da199 100644 --- a/src/services/color/index.js +++ b/src/services/color/index.js @@ -3,3 +3,5 @@ export { hexToRgb } from './hex_to_rgb'; export { rgbToHex } from './rgb_to_hex'; export { calculateContrast, calculateLuminance } from './luminance_and_contrast'; export { VISUALIZATION_COLORS, DEFAULT_VISUALIZATION_COLOR } from './visualization_colors'; +export { colorPalette } from './color_palette'; +export { palettes } from './eui_palettes'; diff --git a/src/services/index.js b/src/services/index.js index de7a563cc2a1..aabefafa4cdb 100644 --- a/src/services/index.js +++ b/src/services/index.js @@ -23,6 +23,8 @@ export { rgbToHex, VISUALIZATION_COLORS, DEFAULT_VISUALIZATION_COLOR, + colorPalette, + palettes, } from './color'; export {