diff --git a/website/src/components/controls/ControlsGroup.tsx b/website/src/components/controls/ControlsGroup.tsx index 6ef5e7d3b4..2b3b094227 100644 --- a/website/src/components/controls/ControlsGroup.tsx +++ b/website/src/components/controls/ControlsGroup.tsx @@ -26,9 +26,10 @@ import { } from './specialized' import { BlendModeControl, + BulletColorsControl, + ColorInterpolatorsControl, ContinuousColorsControl, ColorPickerControl, - ColorsControl, OrdinalColorsControl, OpacityControl, InheritedColorControl, @@ -217,20 +218,6 @@ const ControlSwitcher = memo( /> ) - case 'colors': - return ( - - ) - case 'inheritedColor': return ( ) + case 'color_interpolators': + return ( + + ) + + case 'bullet_colors': + return ( + + ) + default: throw new Error( `invalid control type: ${controlConfig!.type} for property: ${property.name}` diff --git a/website/src/components/controls/colors/BulletColorsControl.tsx b/website/src/components/controls/colors/BulletColorsControl.tsx new file mode 100644 index 0000000000..453b45d265 --- /dev/null +++ b/website/src/components/controls/colors/BulletColorsControl.tsx @@ -0,0 +1,59 @@ +import React, { useCallback } from 'react' +import { ChartProperty, Flavor } from '../../../types' +import { ControlContext, BulletColorsControlConfig } from '../types' +import { Control, PropertyHeader, Help, Select } from '../ui' +import { + ColorSchemeSelectOption, + ColorSchemeSelectValue, + useBulletColors, +} from './colorSchemeSelect' + +interface QuantizeColorsControlProps { + id: string + property: ChartProperty + flavors: Flavor[] + currentFlavor: Flavor + config: BulletColorsControlConfig + onChange: (value: string) => void + value: string + context?: ControlContext +} + +export const BulletColorsControl = ({ + id, + property, + flavors, + currentFlavor, + value: _value, + onChange, + context, +}: QuantizeColorsControlProps) => { + const options = useBulletColors() + + const handleChange = useCallback(value => onChange(value.value), [onChange]) + const value = options.find(({ value: v }) => v === _value) + + return ( + + + + {property.help} + + ) +} diff --git a/website/src/components/controls/colors/ColorsControl.tsx b/website/src/components/controls/colors/ColorsControl.tsx deleted file mode 100644 index 7ad4c76fc1..0000000000 --- a/website/src/components/controls/colors/ColorsControl.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import React, { useCallback } from 'react' -import range from 'lodash/range' -import { - colorSchemeIds, - colorSchemes, - colorInterpolatorIds, - colorInterpolators, -} from '@nivo/colors' -// @ts-ignore -import { components } from 'react-select' -import { ChartProperty, Flavor } from '../../../types' -import { ColorsControlConfig, ControlContext } from '../types' -import { Control, PropertyHeader, Help, Select } from '../ui' -import { ColorsControlItem } from './ColorsControlItem' - -const colors = colorSchemeIds.map(id => ({ - id, - colors: colorSchemes[id], -})) - -const sequentialColors = colorInterpolatorIds.map(id => ({ - id: `seq:${id}`, - colors: range(0, 1, 0.05).map(t => colorInterpolators[id](t)), -})) - -const SingleValue = (props: any) => ( - - - -) - -const Option = (props: any) => ( - - - -) - -interface ColorsControlProps { - id: string - property: ChartProperty - flavors: Flavor[] - currentFlavor: Flavor - onChange: any - value: string - config: ColorsControlConfig - context?: ControlContext -} - -export const ColorsControl = ({ - id, - property, - flavors, - currentFlavor, - value, - config, - onChange, -}: ColorsControlProps) => { - const handleChange = useCallback( - value => { - onChange(value.value) - }, - [onChange] - ) - - let options: any[] = colors - if (config.includeSequential === true) { - options = options.concat(sequentialColors) - } - options = options.map(({ id, colors }) => ({ - label: id, - value: id, - colors, - })) - const selectedOption = options.find(o => o.value === value) - - return ( - - - ({ - label: id, - value: id, - colors, - }))} - optionRenderer={renderOption} - valueRenderer={renderValue} - onChange={handleColorsChange} + options={options} + onChange={handleChange} value={value} + isSearchable clearable={false} + components={{ + SingleValue: ColorSchemeSelectValue, + Option: ColorSchemeSelectOption, + }} /> {property.help} diff --git a/website/src/components/controls/colors/colorSchemeSelect.tsx b/website/src/components/controls/colors/colorSchemeSelect.tsx new file mode 100644 index 0000000000..99aadc3033 --- /dev/null +++ b/website/src/components/controls/colors/colorSchemeSelect.tsx @@ -0,0 +1,171 @@ +import React, { useMemo } from 'react' +import range from 'lodash/range' +import { startCase } from 'lodash' +// @ts-ignore +import { components } from 'react-select' +// @ts-ignore +import { quantizeColorScales } from '@nivo/core' +import { + ColorSchemeId, + ColorInterpolatorId, + colorInterpolators, + colorInterpolatorIds, + isCategoricalColorScheme, + isDivergingColorScheme, + isSequentialColorScheme, + colorSchemes, + colorSchemeIds, +} from '@nivo/colors' +import { ColorsControlItem } from './ColorsControlItem' + +export const getColorSchemeType = (scheme: ColorSchemeId | ColorInterpolatorId) => { + let type = '' + if (isCategoricalColorScheme(scheme as ColorSchemeId)) { + type = 'Categorical' + } else if (isDivergingColorScheme(scheme as ColorSchemeId)) { + type = 'Diverging' + } else if (isSequentialColorScheme(scheme as ColorSchemeId)) { + type = 'Sequential' + } + + return type +} + +export const humanizeColorSchemeId = (schemeId: ColorSchemeId | ColorInterpolatorId) => { + const parts = schemeId.split('_').map(startCase) + + return parts.join(' → ') +} + +export const getColorSchemeLabel = (scheme: ColorSchemeId | ColorInterpolatorId) => { + const type = getColorSchemeType(scheme) + + return `${type ? `${type}: ` : ''}${humanizeColorSchemeId(scheme)}` +} + +export const getColorSchemeSwatches = (scheme: ColorSchemeId | ColorInterpolatorId) => { + let colors: string[] = [] + if (isCategoricalColorScheme(scheme as ColorSchemeId)) { + colors = colorSchemes[scheme as ColorSchemeId] as string[] + } else if (isDivergingColorScheme(scheme as ColorSchemeId)) { + colors = colorSchemes[scheme as ColorSchemeId][11] as string[] + } else if (isSequentialColorScheme(scheme as ColorSchemeId)) { + colors = colorSchemes[scheme as ColorSchemeId][9] as string[] + } + + return colors +} + +export const getColorInterpolatorSwatches = (interpolator: ColorInterpolatorId) => + range(0, 1.1, 0.1).map(t => colorInterpolators[interpolator](t)) as string[] + +export const getInterpolatorConfig = (interpolatorId: ColorInterpolatorId) => ({ + id: getColorSchemeLabel(interpolatorId), + colors: getColorInterpolatorSwatches(interpolatorId), +}) + +export const ColorSchemeSelectValue = (props: any) => ( + + + +) + +export const ColorSchemeSelectOption = (props: any) => ( + + + +) + +export const useOrdinalColorSchemes = () => + useMemo( + () => + colorSchemeIds.map(scheme => ({ + label: getColorSchemeLabel(scheme), + value: scheme, + colors: getColorSchemeSwatches(scheme), + })), + [] + ) + +export const useColorInterpolators = () => + useMemo( + () => + colorInterpolatorIds.map(scheme => ({ + label: getColorSchemeLabel(scheme), + value: scheme, + colors: getColorInterpolatorSwatches(scheme), + })), + [] + ) + +const legacyThemesMapping: Partial> = { + brown_blueGreen: 'BrBG', + blue_green: 'BuGn', + blue_purple: 'BuPu', + green_blue: 'GnBu', + orange_red: 'OrRd', + purpleRed_green: 'PRGn', + pink_yellowGreen: 'PiYG', + purple_blue: 'PuBu', + purple_blue_green: 'PuBuGn', + purple_orange: 'PuOr', + purple_red: 'PuRd', + red_blue: 'RdBu', + red_grey: 'RdGy', + red_purple: 'RdPu', + red_yellow_blue: 'RdYlBu', + red_yellow_green: 'RdYlGn', + yellow_green: 'YlGn', + yellow_green_blue: 'YlGnBu', + yellow_orange_brown: 'YlOrBr', + yellow_orange_red: 'YlOrRd', + blues: 'blues', + greens: 'greens', + greys: 'greys', + nivo: 'nivo', + oranges: 'oranges', + purples: 'purples', + reds: 'reds', + spectral: 'spectral', +} + +export const useLegacyQuantizeColors = () => { + const allSchemes = useOrdinalColorSchemes() + + return useMemo(() => { + const filtered: { + label: string + value: string + colors: string[] + }[] = [] + + allSchemes.forEach(scheme => { + const legacyId = legacyThemesMapping[scheme.value] + + if (legacyId !== undefined) { + filtered.push({ + ...scheme, + value: legacyId, + }) + } + }) + + return filtered + }, [allSchemes]) +} + +export const useBulletColors = () => { + const schemes = useOrdinalColorSchemes() + + const interpolators = useColorInterpolators() + const mappedInterpolators = useMemo( + () => + interpolators.map(interpolator => ({ + ...interpolator, + value: `seq:${interpolator.value}`, + })), + [interpolators] + ) + + return useMemo(() => [...schemes, ...mappedInterpolators], [schemes, mappedInterpolators]) +} diff --git a/website/src/components/controls/colors/humanizeColorSchemeId.ts b/website/src/components/controls/colors/humanizeColorSchemeId.ts deleted file mode 100644 index 8bc8ced93f..0000000000 --- a/website/src/components/controls/colors/humanizeColorSchemeId.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { startCase } from 'lodash' -import { ColorSchemeId } from '@nivo/colors' - -export const humanizeColorSchemeId = (schemeId: ColorSchemeId) => { - const parts = schemeId.split('_').map(startCase) - - return parts.join(' → ') -} diff --git a/website/src/components/controls/colors/index.ts b/website/src/components/controls/colors/index.ts index d068975f6c..2cf4400d36 100644 --- a/website/src/components/controls/colors/index.ts +++ b/website/src/components/controls/colors/index.ts @@ -1,6 +1,7 @@ export * from './BlendModeControl' +export * from './BulletColorsControl' +export * from './ColorInterpolatorsControl' export * from './ColorPickerControl' -export * from './ColorsControl' export * from './ContinuousColorsControl' export * from './InheritedColorControl' export * from './OpacityControl' diff --git a/website/src/components/controls/types.ts b/website/src/components/controls/types.ts index 435fb86dad..71197db10c 100644 --- a/website/src/components/controls/types.ts +++ b/website/src/components/controls/types.ts @@ -131,11 +131,6 @@ export interface TextControlConfig { disabled?: boolean } -export interface ColorsControlConfig { - type: 'colors' - includeSequential?: boolean -} - export interface AnnotationsControlConfig { type: 'annotations' createDefaults: AnnotationMatcher @@ -145,6 +140,14 @@ export interface ContinuousColorsControlConfig { type: 'continuous_colors' } +export interface ColorInterpolatorsControlConfig { + type: 'color_interpolators' +} + +export interface BulletColorsControlConfig { + type: 'bullet_colors' +} + export type ControlConfig = | SwitchControlAttrs | RangeControlConfig @@ -167,9 +170,10 @@ export type ControlConfig = | ObjectControlConfig | ArrayControlConfig | TextControlConfig - | ColorsControlConfig | AnnotationsControlConfig | ContinuousColorsControlConfig + | ColorInterpolatorsControlConfig + | BulletColorsControlConfig export interface ControlContext { path: string[] diff --git a/website/src/components/controls/ui/Radio.tsx b/website/src/components/controls/ui/Radio.tsx index 06294cd397..565f811807 100644 --- a/website/src/components/controls/ui/Radio.tsx +++ b/website/src/components/controls/ui/Radio.tsx @@ -39,7 +39,7 @@ const Container = styled.div<{ font-size: 14px; border-right-width: 0; border-bottom-width: 0; - max-width: 240px; + max-width: 320px; ` const Item = styled.label<{ isSelected: boolean }>` diff --git a/website/src/data/components/bullet/props.ts b/website/src/data/components/bullet/props.ts index dcbd861f1d..84208b5360 100644 --- a/website/src/data/components/bullet/props.ts +++ b/website/src/data/components/bullet/props.ts @@ -198,8 +198,7 @@ const props: ChartProperty[] = [ defaultValue: defaultProps.rangeColors, group: 'Style', control: { - type: 'colors', - includeSequential: true, + type: 'bullet_colors', }, }, { @@ -248,8 +247,7 @@ const props: ChartProperty[] = [ defaultValue: defaultProps.measureColors, group: 'Style', control: { - type: 'colors', - includeSequential: true, + type: 'bullet_colors', }, }, { @@ -274,8 +272,7 @@ const props: ChartProperty[] = [ defaultValue: defaultProps.markerColors, group: 'Style', control: { - type: 'colors', - includeSequential: true, + type: 'bullet_colors', }, }, {