mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Maps] Add ability to invert color ramp and size (#143307)
* convert color forms to TS * switch for setting invert * reverse colors * convert size components to TS * invert size switch * invert mb size expression * invert size legend * invert ordinal legend * invert colors in color picker * update jest snapshot * add unit tests for inverting color ramp creation * review feedback Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
7d10edcc8f
commit
ee326591a3
20 changed files with 393 additions and 170 deletions
|
@ -78,6 +78,7 @@ export type ColorDynamicOptions = {
|
|||
customColorRamp?: OrdinalColorStop[];
|
||||
useCustomColorRamp?: boolean;
|
||||
dataMappingFunction?: DATA_MAPPING_FUNCTION;
|
||||
invert?: boolean;
|
||||
|
||||
// category color properties
|
||||
colorCategory?: string; // TODO move color category palettes to constants and make ENUM type
|
||||
|
@ -176,6 +177,7 @@ export type SizeDynamicOptions = {
|
|||
maxSize: number;
|
||||
field?: StylePropertyField;
|
||||
fieldMetaOptions: FieldMetaOptions;
|
||||
invert?: boolean;
|
||||
};
|
||||
|
||||
export type SizeStaticOptions = {
|
||||
|
|
|
@ -13,7 +13,7 @@ import {
|
|||
} from './color_palettes';
|
||||
|
||||
describe('getColorPalette', () => {
|
||||
it('Should create RGB color ramp', () => {
|
||||
test('Should create RGB color ramp', () => {
|
||||
expect(getColorPalette('Blues')).toEqual([
|
||||
'#ecf1f7',
|
||||
'#d9e3ef',
|
||||
|
@ -28,14 +28,14 @@ describe('getColorPalette', () => {
|
|||
});
|
||||
|
||||
describe('getColorRampCenterColor', () => {
|
||||
it('Should get center color from color ramp', () => {
|
||||
test('Should get center color from color ramp', () => {
|
||||
expect(getColorRampCenterColor('Blues')).toBe('#9eb9d8');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getOrdinalMbColorRampStops', () => {
|
||||
it('Should create color stops for custom range', () => {
|
||||
expect(getOrdinalMbColorRampStops('Blues', 0, 1000)).toEqual([
|
||||
test('Should create color stops', () => {
|
||||
expect(getOrdinalMbColorRampStops('Blues', 0, 1000, false)).toEqual([
|
||||
0,
|
||||
'#ecf1f7',
|
||||
125,
|
||||
|
@ -55,13 +55,34 @@ describe('getOrdinalMbColorRampStops', () => {
|
|||
]);
|
||||
});
|
||||
|
||||
it('Should snap to end of color stops for identical range', () => {
|
||||
expect(getOrdinalMbColorRampStops('Blues', 23, 23)).toEqual([23, '#6092c0']);
|
||||
test('Should create inverted color stops', () => {
|
||||
expect(getOrdinalMbColorRampStops('Blues', 0, 1000, true)).toEqual([
|
||||
0,
|
||||
'#6092c0',
|
||||
125,
|
||||
'#769fc8',
|
||||
250,
|
||||
'#8bacd0',
|
||||
375,
|
||||
'#9eb9d8',
|
||||
500,
|
||||
'#b2c7df',
|
||||
625,
|
||||
'#c5d5e7',
|
||||
750,
|
||||
'#d9e3ef',
|
||||
875,
|
||||
'#ecf1f7',
|
||||
]);
|
||||
});
|
||||
|
||||
test('Should snap to end of color stops for identical range', () => {
|
||||
expect(getOrdinalMbColorRampStops('Blues', 23, 23, false)).toEqual([23, '#6092c0']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPercentilesMbColorRampStops', () => {
|
||||
it('Should create color stops for custom range', () => {
|
||||
test('Should create color stops', () => {
|
||||
const percentiles = [
|
||||
{ percentile: '50.0', value: 5567.83 },
|
||||
{ percentile: '75.0', value: 8069 },
|
||||
|
@ -69,7 +90,7 @@ describe('getPercentilesMbColorRampStops', () => {
|
|||
{ percentile: '95.0', value: 11145.5 },
|
||||
{ percentile: '99.0', value: 16958.18 },
|
||||
];
|
||||
expect(getPercentilesMbColorRampStops('Blues', percentiles)).toEqual([
|
||||
expect(getPercentilesMbColorRampStops('Blues', percentiles, false)).toEqual([
|
||||
5567.83,
|
||||
'#e0e8f2',
|
||||
8069,
|
||||
|
@ -82,4 +103,26 @@ describe('getPercentilesMbColorRampStops', () => {
|
|||
'#6092c0',
|
||||
]);
|
||||
});
|
||||
|
||||
test('Should create inverted color stops', () => {
|
||||
const percentiles = [
|
||||
{ percentile: '50.0', value: 5567.83 },
|
||||
{ percentile: '75.0', value: 8069 },
|
||||
{ percentile: '90.0', value: 9581.13 },
|
||||
{ percentile: '95.0', value: 11145.5 },
|
||||
{ percentile: '99.0', value: 16958.18 },
|
||||
];
|
||||
expect(getPercentilesMbColorRampStops('Blues', percentiles, true)).toEqual([
|
||||
5567.83,
|
||||
'#6092c0',
|
||||
8069,
|
||||
'#82a7cd',
|
||||
9581.13,
|
||||
'#a2bcd9',
|
||||
11145.5,
|
||||
'#c2d2e6',
|
||||
16958.18,
|
||||
'#e0e8f2',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -153,7 +153,7 @@ export function getColorPalette(colorPaletteId: string): string[] {
|
|||
const colorPalette = COLOR_PALETTES.find(({ value }: COLOR_PALETTE) => {
|
||||
return value === colorPaletteId;
|
||||
});
|
||||
return colorPalette ? (colorPalette.palette as string[]) : [];
|
||||
return colorPalette ? [...(colorPalette.palette as string[])] : [];
|
||||
}
|
||||
|
||||
export function getColorRampCenterColor(colorPaletteId: string): string | null {
|
||||
|
@ -169,7 +169,8 @@ export function getColorRampCenterColor(colorPaletteId: string): string | null {
|
|||
export function getOrdinalMbColorRampStops(
|
||||
colorPaletteId: string | null,
|
||||
min: number,
|
||||
max: number
|
||||
max: number,
|
||||
invert: boolean
|
||||
): Array<number | string> | null {
|
||||
if (!colorPaletteId) {
|
||||
return null;
|
||||
|
@ -180,6 +181,10 @@ export function getOrdinalMbColorRampStops(
|
|||
}
|
||||
|
||||
const palette = getColorPalette(colorPaletteId);
|
||||
if (invert) {
|
||||
palette.reverse();
|
||||
}
|
||||
|
||||
if (palette.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
@ -203,7 +208,8 @@ export function getOrdinalMbColorRampStops(
|
|||
// [ stop_input_1: number, stop_output_1: color, stop_input_n: number, stop_output_n: color ]
|
||||
export function getPercentilesMbColorRampStops(
|
||||
colorPaletteId: string | null,
|
||||
percentiles: PercentilesFieldMeta
|
||||
percentiles: PercentilesFieldMeta,
|
||||
invert: boolean
|
||||
): Array<number | string> | null {
|
||||
if (!colorPaletteId) {
|
||||
return null;
|
||||
|
@ -213,13 +219,17 @@ export function getPercentilesMbColorRampStops(
|
|||
return value === colorPaletteId;
|
||||
});
|
||||
|
||||
return paletteObject
|
||||
? paletteObject
|
||||
.getPalette(percentiles.length)
|
||||
.reduce((accu: Array<number | string>, stopColor: string, idx: number) => {
|
||||
return [...accu, percentiles[idx].value, stopColor];
|
||||
}, [])
|
||||
: null;
|
||||
if (!paletteObject) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const palette = paletteObject.getPalette(percentiles.length);
|
||||
if (invert) {
|
||||
palette.reverse();
|
||||
}
|
||||
return palette.reduce((accu: Array<number | string>, stopColor: string, idx: number) => {
|
||||
return [...accu, percentiles[idx].value, stopColor];
|
||||
}, []);
|
||||
}
|
||||
|
||||
export function getLinearGradient(colorStrings: string[]): string {
|
||||
|
|
|
@ -15,6 +15,7 @@ interface Props {
|
|||
maxLabel: string | number;
|
||||
propertyLabel: string;
|
||||
fieldLabel: string;
|
||||
invert: boolean;
|
||||
}
|
||||
|
||||
export function RangedStyleLegendRow({
|
||||
|
@ -23,6 +24,7 @@ export function RangedStyleLegendRow({
|
|||
maxLabel,
|
||||
propertyLabel,
|
||||
fieldLabel,
|
||||
invert,
|
||||
}: Props) {
|
||||
return (
|
||||
<div>
|
||||
|
@ -41,12 +43,12 @@ export function RangedStyleLegendRow({
|
|||
<EuiFlexGroup gutterSize="xs" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={true}>
|
||||
<EuiText size="xs">
|
||||
<small>{minLabel}</small>
|
||||
<small>{invert ? maxLabel : minLabel}</small>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={true}>
|
||||
<EuiText textAlign="right" size="xs">
|
||||
<small>{maxLabel}</small>
|
||||
<small>{invert ? minLabel : maxLabel}</small>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -58,6 +58,7 @@ export class HeatmapLegend extends Component<Props, State> {
|
|||
})}
|
||||
propertyLabel={HEATMAP_COLOR_RAMP_LABEL}
|
||||
fieldLabel={this.state.label}
|
||||
invert={false}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -96,7 +96,8 @@ export class HeatmapStyle implements IStyle {
|
|||
const colorStops = getOrdinalMbColorRampStops(
|
||||
this._descriptor.colorRampName,
|
||||
MIN_RANGE,
|
||||
MAX_RANGE
|
||||
MAX_RANGE,
|
||||
false
|
||||
);
|
||||
if (colorStops) {
|
||||
mbMap.setPaintProperty(layerId, 'heatmap-color', [
|
||||
|
|
|
@ -129,16 +129,26 @@ export class ColorMapSelect extends Component {
|
|||
);
|
||||
}
|
||||
|
||||
_getColorPalettes() {
|
||||
if (this.props.colorMapType === COLOR_MAP_TYPE.CATEGORICAL) {
|
||||
return CATEGORICAL_COLOR_PALETTES;
|
||||
}
|
||||
|
||||
return this.props.invert
|
||||
? NUMERICAL_COLOR_PALETTES.map((paletteProps) => {
|
||||
return {
|
||||
...paletteProps,
|
||||
palette: [...paletteProps.palette].reverse(),
|
||||
};
|
||||
})
|
||||
: NUMERICAL_COLOR_PALETTES;
|
||||
}
|
||||
|
||||
_renderColorMapSelections() {
|
||||
if (this.props.isCustomOnly) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const palettes =
|
||||
this.props.colorMapType === COLOR_MAP_TYPE.ORDINAL
|
||||
? NUMERICAL_COLOR_PALETTES
|
||||
: CATEGORICAL_COLOR_PALETTES;
|
||||
|
||||
const palettesWithCustom = [
|
||||
{
|
||||
value: CUSTOM_COLOR_MAP,
|
||||
|
@ -153,7 +163,7 @@ export class ColorMapSelect extends Component {
|
|||
type: 'text',
|
||||
'data-test-subj': `colorMapSelectOption_${CUSTOM_COLOR_MAP}`,
|
||||
},
|
||||
...palettes,
|
||||
...this._getColorPalettes(),
|
||||
];
|
||||
|
||||
const toggle = this.props.showColorMapTypeToggle ? (
|
||||
|
|
|
@ -6,12 +6,40 @@
|
|||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import React, { Fragment } from 'react';
|
||||
import React, { ChangeEvent, ReactNode } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFormRow,
|
||||
EuiSpacer,
|
||||
EuiSwitch,
|
||||
EuiSwitchEvent,
|
||||
} from '@elastic/eui';
|
||||
import { FieldSelect } from '../field_select';
|
||||
// @ts-expect-error
|
||||
import { ColorMapSelect } from './color_map_select';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
|
||||
import { OtherCategoryColorPicker } from './other_category_color_picker';
|
||||
import { CATEGORICAL_DATA_TYPES, COLOR_MAP_TYPE } from '../../../../../../common/constants';
|
||||
import {
|
||||
CategoryColorStop,
|
||||
ColorDynamicOptions,
|
||||
OrdinalColorStop,
|
||||
} from '../../../../../../common/descriptor_types';
|
||||
import {
|
||||
CATEGORICAL_DATA_TYPES,
|
||||
COLOR_MAP_TYPE,
|
||||
VECTOR_STYLES,
|
||||
} from '../../../../../../common/constants';
|
||||
import { StyleField } from '../../style_fields_helper';
|
||||
import { DynamicColorProperty } from '../../properties/dynamic_color_property';
|
||||
|
||||
interface Props {
|
||||
fields: StyleField[];
|
||||
onDynamicStyleChange: (propertyName: VECTOR_STYLES, options: ColorDynamicOptions) => void;
|
||||
staticDynamicSelect?: ReactNode;
|
||||
styleProperty: DynamicColorProperty;
|
||||
swatches: string[];
|
||||
}
|
||||
|
||||
export function DynamicColorForm({
|
||||
fields,
|
||||
|
@ -19,10 +47,20 @@ export function DynamicColorForm({
|
|||
staticDynamicSelect,
|
||||
styleProperty,
|
||||
swatches,
|
||||
}) {
|
||||
}: Props) {
|
||||
const styleOptions = styleProperty.getOptions();
|
||||
|
||||
const onColorMapSelect = ({ color, customColorMap, type, useCustomColorMap }) => {
|
||||
const onColorMapSelect = ({
|
||||
color,
|
||||
customColorMap,
|
||||
type,
|
||||
useCustomColorMap,
|
||||
}: {
|
||||
color?: null | string;
|
||||
customColorMap?: OrdinalColorStop[] | CategoryColorStop[];
|
||||
type: COLOR_MAP_TYPE;
|
||||
useCustomColorMap: boolean;
|
||||
}) => {
|
||||
const newColorOptions = {
|
||||
...styleOptions,
|
||||
type,
|
||||
|
@ -30,7 +68,7 @@ export function DynamicColorForm({
|
|||
if (type === COLOR_MAP_TYPE.ORDINAL) {
|
||||
newColorOptions.useCustomColorRamp = useCustomColorMap;
|
||||
if (customColorMap) {
|
||||
newColorOptions.customColorRamp = customColorMap;
|
||||
newColorOptions.customColorRamp = customColorMap as OrdinalColorStop[];
|
||||
}
|
||||
if (color) {
|
||||
newColorOptions.color = color;
|
||||
|
@ -38,7 +76,7 @@ export function DynamicColorForm({
|
|||
} else {
|
||||
newColorOptions.useCustomColorPalette = useCustomColorMap;
|
||||
if (customColorMap) {
|
||||
newColorOptions.customColorPalette = customColorMap;
|
||||
newColorOptions.customColorPalette = customColorMap as CategoryColorStop[];
|
||||
}
|
||||
if (color) {
|
||||
newColorOptions.colorCategory = color;
|
||||
|
@ -48,7 +86,11 @@ export function DynamicColorForm({
|
|||
onDynamicStyleChange(styleProperty.getStyleName(), newColorOptions);
|
||||
};
|
||||
|
||||
const onFieldChange = async ({ field }) => {
|
||||
const onFieldChange = ({ field }: { field: StyleField | null }) => {
|
||||
if (!field) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { name, origin, type: fieldType } = field;
|
||||
const defaultColorMapType = CATEGORICAL_DATA_TYPES.includes(fieldType)
|
||||
? COLOR_MAP_TYPE.CATEGORICAL
|
||||
|
@ -60,21 +102,28 @@ export function DynamicColorForm({
|
|||
});
|
||||
};
|
||||
|
||||
const onColorMapTypeChange = async (e) => {
|
||||
const colorMapType = e.target.value;
|
||||
const onColorMapTypeChange = (e: ChangeEvent<HTMLSelectElement>) => {
|
||||
const colorMapType = e.target.value as COLOR_MAP_TYPE;
|
||||
onDynamicStyleChange(styleProperty.getStyleName(), {
|
||||
...styleOptions,
|
||||
type: colorMapType,
|
||||
});
|
||||
};
|
||||
|
||||
const onOtherCategoryColorChange = (color) => {
|
||||
const onOtherCategoryColorChange = (color: string) => {
|
||||
onDynamicStyleChange(styleProperty.getStyleName(), {
|
||||
...styleOptions,
|
||||
otherCategoryColor: color,
|
||||
});
|
||||
};
|
||||
|
||||
const onInvertChange = (event: EuiSwitchEvent) => {
|
||||
onDynamicStyleChange(styleProperty.getStyleName(), {
|
||||
...styleOptions,
|
||||
invert: event.target.checked,
|
||||
});
|
||||
};
|
||||
|
||||
const getField = () => {
|
||||
const fieldName = styleProperty.getFieldName();
|
||||
if (!fieldName) {
|
||||
|
@ -92,10 +141,11 @@ export function DynamicColorForm({
|
|||
return null;
|
||||
}
|
||||
|
||||
const invert = styleOptions.invert === undefined ? false : styleOptions.invert;
|
||||
const showColorMapTypeToggle = !CATEGORICAL_DATA_TYPES.includes(field.type);
|
||||
|
||||
if (styleProperty.isOrdinal()) {
|
||||
return (
|
||||
return styleProperty.isOrdinal() ? (
|
||||
<>
|
||||
<ColorMapSelect
|
||||
isCustomOnly={!field.supportsAutoDomain}
|
||||
onChange={onColorMapSelect}
|
||||
|
@ -107,37 +157,49 @@ export function DynamicColorForm({
|
|||
styleProperty={styleProperty}
|
||||
showColorMapTypeToggle={showColorMapTypeToggle}
|
||||
swatches={swatches}
|
||||
invert={invert}
|
||||
/>
|
||||
);
|
||||
} else if (styleProperty.isCategorical()) {
|
||||
return (
|
||||
<>
|
||||
<ColorMapSelect
|
||||
isCustomOnly={!field.supportsAutoDomain}
|
||||
onColorMapTypeChange={onColorMapTypeChange}
|
||||
onChange={onColorMapSelect}
|
||||
colorMapType={COLOR_MAP_TYPE.CATEGORICAL}
|
||||
colorPaletteId={styleOptions.colorCategory}
|
||||
customColorMap={styleOptions.customColorPalette}
|
||||
useCustomColorMap={_.get(styleOptions, 'useCustomColorPalette', false)}
|
||||
styleProperty={styleProperty}
|
||||
showColorMapTypeToggle={showColorMapTypeToggle}
|
||||
swatches={swatches}
|
||||
/>
|
||||
<OtherCategoryColorPicker
|
||||
onChange={onOtherCategoryColorChange}
|
||||
color={styleOptions.otherCategoryColor}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
{!!styleOptions.useCustomColorRamp ? null : (
|
||||
<EuiFormRow display="columnCompressedSwitch">
|
||||
<EuiSwitch
|
||||
label={i18n.translate('xpack.maps.style.revereseColorsLabel', {
|
||||
defaultMessage: `Reverse colors`,
|
||||
})}
|
||||
checked={invert}
|
||||
onChange={onInvertChange}
|
||||
compressed
|
||||
/>
|
||||
</EuiFormRow>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ColorMapSelect
|
||||
isCustomOnly={!field.supportsAutoDomain}
|
||||
onColorMapTypeChange={onColorMapTypeChange}
|
||||
onChange={onColorMapSelect}
|
||||
colorMapType={COLOR_MAP_TYPE.CATEGORICAL}
|
||||
colorPaletteId={styleOptions.colorCategory}
|
||||
customColorMap={styleOptions.customColorPalette}
|
||||
useCustomColorMap={_.get(styleOptions, 'useCustomColorPalette', false)}
|
||||
styleProperty={styleProperty}
|
||||
showColorMapTypeToggle={showColorMapTypeToggle}
|
||||
swatches={swatches}
|
||||
invert={false}
|
||||
/>
|
||||
<OtherCategoryColorPicker
|
||||
onChange={onOtherCategoryColorChange}
|
||||
color={styleOptions.otherCategoryColor}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<>
|
||||
<EuiFlexGroup gutterSize="xs" justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false} className="mapStyleSettings__fixedBox">
|
||||
{staticDynamicSelect}
|
||||
{staticDynamicSelect ? staticDynamicSelect : null}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<FieldSelect
|
||||
|
@ -151,6 +213,6 @@ export function DynamicColorForm({
|
|||
</EuiFlexGroup>
|
||||
<EuiSpacer size="s" />
|
||||
{renderColorMapSelect()}
|
||||
</Fragment>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -5,24 +5,34 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { ReactNode } from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { VECTOR_STYLES } from '../../../../../../common/constants';
|
||||
import { ColorStaticOptions } from '../../../../../../common/descriptor_types';
|
||||
import { MbValidatedColorPicker } from './mb_validated_color_picker';
|
||||
import { StaticColorProperty } from '../../properties/static_color_property';
|
||||
|
||||
interface Props {
|
||||
onStaticStyleChange: (propertyName: VECTOR_STYLES, options: ColorStaticOptions) => void;
|
||||
staticDynamicSelect?: ReactNode;
|
||||
styleProperty: StaticColorProperty;
|
||||
swatches: string[];
|
||||
}
|
||||
|
||||
export function StaticColorForm({
|
||||
onStaticStyleChange,
|
||||
staticDynamicSelect,
|
||||
styleProperty,
|
||||
swatches,
|
||||
}) {
|
||||
const onColorChange = (color) => {
|
||||
}: Props) {
|
||||
const onColorChange = (color: string) => {
|
||||
onStaticStyleChange(styleProperty.getStyleName(), { color });
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="xs" justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false} className="mapStyleSettings__fixedBox">
|
||||
{staticDynamicSelect}
|
||||
{staticDynamicSelect ? staticDynamicSelect : null}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<MbValidatedColorPicker
|
|
@ -9,10 +9,10 @@ import React from 'react';
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Props, StylePropEditor } from '../style_prop_editor';
|
||||
// @ts-expect-error
|
||||
import { DynamicColorForm } from './dynamic_color_form';
|
||||
// @ts-expect-error
|
||||
import { StaticColorForm } from './static_color_form';
|
||||
import { DynamicColorProperty } from '../../properties/dynamic_color_property';
|
||||
import { StaticColorProperty } from '../../properties/static_color_property';
|
||||
import { ColorDynamicOptions, ColorStaticOptions } from '../../../../../../common/descriptor_types';
|
||||
|
||||
type ColorEditorProps = Omit<Props<ColorStaticOptions, ColorDynamicOptions>, 'children'> & {
|
||||
|
@ -21,9 +21,9 @@ type ColorEditorProps = Omit<Props<ColorStaticOptions, ColorDynamicOptions>, 'ch
|
|||
|
||||
export function VectorStyleColorEditor(props: ColorEditorProps) {
|
||||
const colorForm = props.styleProperty.isDynamic() ? (
|
||||
<DynamicColorForm {...props} />
|
||||
<DynamicColorForm {...props} styleProperty={props.styleProperty as DynamicColorProperty} />
|
||||
) : (
|
||||
<StaticColorForm {...props} />
|
||||
<StaticColorForm {...props} styleProperty={props.styleProperty as StaticColorProperty} />
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -91,6 +91,7 @@ export class MarkerSizeLegend extends Component<Props, State> {
|
|||
if (!fieldMeta || !options) {
|
||||
return null;
|
||||
}
|
||||
const invert = options.invert === undefined ? false : options.invert;
|
||||
|
||||
const circleStyle = {
|
||||
fillOpacity: 0,
|
||||
|
@ -136,14 +137,18 @@ export class MarkerSizeLegend extends Component<Props, State> {
|
|||
// Markers interpolated by area instead of radius to be more consistent with how the human eye+brain perceive shapes
|
||||
// and their visual relevance
|
||||
// This function mirrors output of maplibre expression created from DynamicSizeProperty.getMbSizeExpression
|
||||
const value = Math.pow(percentage * Math.sqrt(fieldMeta!.delta), 2) + fieldMeta!.min;
|
||||
const scaledWidth = Math.pow(percentage * Math.sqrt(fieldMeta!.delta), 2);
|
||||
const value = invert ? fieldMeta!.max - scaledWidth : scaledWidth + fieldMeta!.min;
|
||||
return fieldMeta!.delta > 3 ? Math.round(value) : value;
|
||||
}
|
||||
|
||||
const markers = [];
|
||||
|
||||
if (fieldMeta.delta > 0) {
|
||||
const smallestMarker = makeMarker(options.minSize, this._formatValue(fieldMeta.min));
|
||||
const smallestMarker = makeMarker(
|
||||
options.minSize,
|
||||
this._formatValue(invert ? fieldMeta.max : fieldMeta.min)
|
||||
);
|
||||
markers.push(smallestMarker);
|
||||
|
||||
const markerDelta = options.maxSize - options.minSize;
|
||||
|
@ -156,7 +161,10 @@ export class MarkerSizeLegend extends Component<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
const largestMarker = makeMarker(options.maxSize, this._formatValue(fieldMeta.max));
|
||||
const largestMarker = makeMarker(
|
||||
options.maxSize,
|
||||
this._formatValue(invert ? fieldMeta.min : fieldMeta.max)
|
||||
);
|
||||
markers.push(largestMarker);
|
||||
|
||||
return (
|
||||
|
|
|
@ -138,6 +138,9 @@ export class OrdinalLegend extends Component<Props, State> {
|
|||
this.props.style.isFieldMetaEnabled() && fieldMeta.isMaxOutsideStdRange ? `> ${max}` : max;
|
||||
}
|
||||
|
||||
const options = this.props.style.getOptions();
|
||||
const invert = options.invert === undefined ? false : options.invert;
|
||||
|
||||
return (
|
||||
<RangedStyleLegendRow
|
||||
header={header}
|
||||
|
@ -145,6 +148,7 @@ export class OrdinalLegend extends Component<Props, State> {
|
|||
maxLabel={maxLabel}
|
||||
propertyLabel={this.props.style.getDisplayStyleName()}
|
||||
fieldLabel={this.state.label}
|
||||
invert={invert}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,66 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { Fragment } from 'react';
|
||||
import { FieldSelect } from '../field_select';
|
||||
import { SizeRangeSelector } from './size_range_selector';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
|
||||
|
||||
export function DynamicSizeForm({
|
||||
fields,
|
||||
onDynamicStyleChange,
|
||||
staticDynamicSelect,
|
||||
styleProperty,
|
||||
}) {
|
||||
const styleOptions = styleProperty.getOptions();
|
||||
|
||||
const onFieldChange = ({ field }) => {
|
||||
onDynamicStyleChange(styleProperty.getStyleName(), { ...styleOptions, field });
|
||||
};
|
||||
|
||||
const onSizeRangeChange = ({ minSize, maxSize }) => {
|
||||
onDynamicStyleChange(styleProperty.getStyleName(), {
|
||||
...styleOptions,
|
||||
minSize,
|
||||
maxSize,
|
||||
});
|
||||
};
|
||||
|
||||
let sizeRange;
|
||||
if (styleOptions.field && styleOptions.field.name) {
|
||||
sizeRange = (
|
||||
<SizeRangeSelector
|
||||
onChange={onSizeRangeChange}
|
||||
minSize={styleOptions.minSize}
|
||||
maxSize={styleOptions.maxSize}
|
||||
showLabels
|
||||
compressed
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiFlexGroup gutterSize="xs" justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false} className="mapStyleSettings__fixedBox">
|
||||
{staticDynamicSelect}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<FieldSelect
|
||||
styleName={styleProperty.getStyleName()}
|
||||
fields={fields}
|
||||
selectedFieldName={styleProperty.getFieldName()}
|
||||
onChange={onFieldChange}
|
||||
compressed
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="s" />
|
||||
{sizeRange}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { ReactNode } from 'react';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFormRow,
|
||||
EuiSpacer,
|
||||
EuiSwitch,
|
||||
EuiSwitchEvent,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FieldSelect } from '../field_select';
|
||||
import { SizeRangeSelector } from './size_range_selector';
|
||||
import { SizeDynamicOptions } from '../../../../../../common/descriptor_types';
|
||||
import { VECTOR_STYLES } from '../../../../../../common/constants';
|
||||
import { DynamicSizeProperty } from '../../properties/dynamic_size_property';
|
||||
import { StyleField } from '../../style_fields_helper';
|
||||
|
||||
interface Props {
|
||||
fields: StyleField[];
|
||||
onDynamicStyleChange: (propertyName: VECTOR_STYLES, options: SizeDynamicOptions) => void;
|
||||
staticDynamicSelect?: ReactNode;
|
||||
styleProperty: DynamicSizeProperty;
|
||||
}
|
||||
|
||||
export function DynamicSizeForm({
|
||||
fields,
|
||||
onDynamicStyleChange,
|
||||
staticDynamicSelect,
|
||||
styleProperty,
|
||||
}: Props) {
|
||||
const styleOptions = styleProperty.getOptions();
|
||||
|
||||
const onFieldChange = ({ field }: { field: StyleField | null }) => {
|
||||
if (field) {
|
||||
onDynamicStyleChange(styleProperty.getStyleName(), {
|
||||
...styleOptions,
|
||||
field: { name: field.name, origin: field.origin },
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onSizeRangeChange = ({ minSize, maxSize }: { minSize: number; maxSize: number }) => {
|
||||
onDynamicStyleChange(styleProperty.getStyleName(), {
|
||||
...styleOptions,
|
||||
minSize,
|
||||
maxSize,
|
||||
});
|
||||
};
|
||||
|
||||
const onInvertChange = (event: EuiSwitchEvent) => {
|
||||
onDynamicStyleChange(styleProperty.getStyleName(), {
|
||||
...styleOptions,
|
||||
invert: event.target.checked,
|
||||
});
|
||||
};
|
||||
|
||||
let sizeRange;
|
||||
if (styleOptions.field && styleOptions.field.name) {
|
||||
sizeRange = (
|
||||
<>
|
||||
<SizeRangeSelector
|
||||
onChange={onSizeRangeChange}
|
||||
minSize={styleOptions.minSize}
|
||||
maxSize={styleOptions.maxSize}
|
||||
showLabels
|
||||
compressed
|
||||
/>
|
||||
<EuiFormRow display="columnCompressedSwitch">
|
||||
<EuiSwitch
|
||||
label={i18n.translate('xpack.maps.style.revereseSizeLabel', {
|
||||
defaultMessage: `Reverse size`,
|
||||
})}
|
||||
checked={!!styleOptions.invert}
|
||||
onChange={onInvertChange}
|
||||
compressed
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFlexGroup gutterSize="xs" justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false} className="mapStyleSettings__fixedBox">
|
||||
{staticDynamicSelect}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<FieldSelect
|
||||
styleName={styleProperty.getStyleName()}
|
||||
fields={fields}
|
||||
selectedFieldName={styleProperty.getFieldName()}
|
||||
onChange={onFieldChange}
|
||||
compressed
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="s" />
|
||||
{sizeRange}
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -6,13 +6,19 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ValidatedDualRange } from '@kbn/kibana-react-plugin/public';
|
||||
import { MIN_SIZE, MAX_SIZE } from '../../vector_style_defaults';
|
||||
import { EuiDualRangeProps } from '@elastic/eui/src/components/form/range/dual_range';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { MIN_SIZE, MAX_SIZE } from '../../vector_style_defaults';
|
||||
|
||||
export function SizeRangeSelector({ minSize, maxSize, onChange, ...rest }) {
|
||||
const onSizeChange = ([min, max]) => {
|
||||
interface Props extends Omit<EuiDualRangeProps, 'value' | 'onChange' | 'min' | 'max'> {
|
||||
minSize: number;
|
||||
maxSize: number;
|
||||
onChange: ({ maxSize, minSize }: { maxSize: number; minSize: number }) => void;
|
||||
}
|
||||
|
||||
export function SizeRangeSelector({ minSize, maxSize, onChange, ...rest }: Props) {
|
||||
const onSizeChange = ([min, max]: [string, string]) => {
|
||||
onChange({
|
||||
minSize: Math.max(MIN_SIZE, parseInt(min, 10)),
|
||||
maxSize: Math.min(MAX_SIZE, parseInt(max, 10)),
|
||||
|
@ -37,9 +43,3 @@ export function SizeRangeSelector({ minSize, maxSize, onChange, ...rest }) {
|
|||
/>
|
||||
);
|
||||
}
|
||||
|
||||
SizeRangeSelector.propTypes = {
|
||||
minSize: PropTypes.number.isRequired,
|
||||
maxSize: PropTypes.number.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
};
|
|
@ -5,13 +5,23 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { ValidatedRange } from '../../../../../components/validated_range';
|
||||
import React, { ReactNode } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
// @ts-expect-error
|
||||
import { ValidatedRange } from '../../../../../components/validated_range';
|
||||
import { SizeStaticOptions } from '../../../../../../common/descriptor_types';
|
||||
import { VECTOR_STYLES } from '../../../../../../common/constants';
|
||||
import { StaticSizeProperty } from '../../properties/static_size_property';
|
||||
|
||||
export function StaticSizeForm({ onStaticStyleChange, staticDynamicSelect, styleProperty }) {
|
||||
const onSizeChange = (size) => {
|
||||
interface Props {
|
||||
onStaticStyleChange: (propertyName: VECTOR_STYLES, options: SizeStaticOptions) => void;
|
||||
staticDynamicSelect?: ReactNode;
|
||||
styleProperty: StaticSizeProperty;
|
||||
}
|
||||
|
||||
export function StaticSizeForm({ onStaticStyleChange, staticDynamicSelect, styleProperty }: Props) {
|
||||
const onSizeChange = (size: number) => {
|
||||
onStaticStyleChange(styleProperty.getStyleName(), { size });
|
||||
};
|
||||
|
|
@ -8,19 +8,19 @@
|
|||
import React from 'react';
|
||||
|
||||
import { Props, StylePropEditor } from '../style_prop_editor';
|
||||
// @ts-expect-error
|
||||
import { DynamicSizeForm } from './dynamic_size_form';
|
||||
// @ts-expect-error
|
||||
import { StaticSizeForm } from './static_size_form';
|
||||
import { SizeDynamicOptions, SizeStaticOptions } from '../../../../../../common/descriptor_types';
|
||||
import { DynamicSizeProperty } from '../../properties/dynamic_size_property';
|
||||
import { StaticSizeProperty } from '../../properties/static_size_property';
|
||||
|
||||
type SizeEditorProps = Omit<Props<SizeStaticOptions, SizeDynamicOptions>, 'children'>;
|
||||
|
||||
export function VectorStyleSizeEditor(props: SizeEditorProps) {
|
||||
const sizeForm = props.styleProperty.isDynamic() ? (
|
||||
<DynamicSizeForm {...props} />
|
||||
<DynamicSizeForm {...props} styleProperty={props.styleProperty as DynamicSizeProperty} />
|
||||
) : (
|
||||
<StaticSizeForm {...props} />
|
||||
<StaticSizeForm {...props} styleProperty={props.styleProperty as StaticSizeProperty} />
|
||||
);
|
||||
|
||||
return <StylePropEditor {...props}>{sizeForm}</StylePropEditor>;
|
||||
|
|
|
@ -142,6 +142,7 @@ export class DynamicColorProperty extends DynamicStyleProperty<ColorDynamicOptio
|
|||
}
|
||||
|
||||
_getOrdinalColorMbExpression() {
|
||||
const invert = this._options.invert === undefined ? false : this._options.invert;
|
||||
const targetName = this.getMbFieldName();
|
||||
if (this._options.useCustomColorRamp) {
|
||||
if (!this._options.customColorRamp || !this._options.customColorRamp.length) {
|
||||
|
@ -177,7 +178,8 @@ export class DynamicColorProperty extends DynamicStyleProperty<ColorDynamicOptio
|
|||
|
||||
const colorStops = getPercentilesMbColorRampStops(
|
||||
this._options.color ? this._options.color : null,
|
||||
percentilesFieldMeta
|
||||
percentilesFieldMeta,
|
||||
invert
|
||||
);
|
||||
if (!colorStops) {
|
||||
return null;
|
||||
|
@ -205,7 +207,8 @@ export class DynamicColorProperty extends DynamicStyleProperty<ColorDynamicOptio
|
|||
const colorStops = getOrdinalMbColorRampStops(
|
||||
this._options.color ? this._options.color : null,
|
||||
rangeFieldMeta.min,
|
||||
rangeFieldMeta.max
|
||||
rangeFieldMeta.max,
|
||||
invert
|
||||
);
|
||||
if (!colorStops) {
|
||||
return null;
|
||||
|
@ -350,6 +353,7 @@ export class DynamicColorProperty extends DynamicStyleProperty<ColorDynamicOptio
|
|||
}
|
||||
|
||||
_getOrdinalBreaks(symbolId?: string, svg?: string): Break[] {
|
||||
const invert = this._options.invert === undefined ? false : this._options.invert;
|
||||
let colorStops: Array<number | string> | null = null;
|
||||
let getValuePrefix: ((i: number, isNext: boolean) => string) | null = null;
|
||||
if (this._options.useCustomColorRamp) {
|
||||
|
@ -364,7 +368,8 @@ export class DynamicColorProperty extends DynamicStyleProperty<ColorDynamicOptio
|
|||
}
|
||||
colorStops = getPercentilesMbColorRampStops(
|
||||
this._options.color ? this._options.color : null,
|
||||
percentilesFieldMeta
|
||||
percentilesFieldMeta,
|
||||
invert
|
||||
);
|
||||
getValuePrefix = function (i: number, isNext: boolean) {
|
||||
const percentile = isNext
|
||||
|
@ -379,7 +384,9 @@ export class DynamicColorProperty extends DynamicStyleProperty<ColorDynamicOptio
|
|||
return [];
|
||||
}
|
||||
if (rangeFieldMeta.delta === 0) {
|
||||
const colors = getColorPalette(this._options.color);
|
||||
const colors = invert
|
||||
? getColorPalette(this._options.color).reverse()
|
||||
: getColorPalette(this._options.color);
|
||||
// map to last color.
|
||||
return [
|
||||
{
|
||||
|
@ -393,7 +400,8 @@ export class DynamicColorProperty extends DynamicStyleProperty<ColorDynamicOptio
|
|||
colorStops = getOrdinalMbColorRampStops(
|
||||
this._options.color ? this._options.color : null,
|
||||
rangeFieldMeta.min,
|
||||
rangeFieldMeta.max
|
||||
rangeFieldMeta.max,
|
||||
invert
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -228,6 +228,7 @@ exports[`renderLegendDetailRow Should render line width simple range 1`] = `
|
|||
</React.Fragment>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
invert={false}
|
||||
maxLabel="100_format"
|
||||
minLabel="0_format"
|
||||
propertyLabel="Border width"
|
||||
|
|
|
@ -112,17 +112,25 @@ export class DynamicSizeProperty extends DynamicStyleProperty<SizeDynamicOptions
|
|||
this.getStyleName() === VECTOR_STYLES.ICON_SIZE && this._isSymbolizedAsIcon
|
||||
? this._options.minSize / HALF_MAKI_ICON_SIZE
|
||||
: this._options.minSize;
|
||||
const stops =
|
||||
rangeFieldMeta.min === rangeFieldMeta.max
|
||||
? [maxValueStopInput, maxRangeStopOutput]
|
||||
const invert = this._options.invert === undefined ? false : this._options.invert;
|
||||
function getStopsWithoutRange() {
|
||||
return invert
|
||||
? [maxValueStopInput, minRangeStopOutput]
|
||||
: [maxValueStopInput, maxRangeStopOutput];
|
||||
}
|
||||
function getStops() {
|
||||
return invert
|
||||
? [minValueStopInput, maxRangeStopOutput, maxValueStopInput, minRangeStopOutput]
|
||||
: [minValueStopInput, minRangeStopOutput, maxValueStopInput, maxRangeStopOutput];
|
||||
}
|
||||
const stops = rangeFieldMeta.min === rangeFieldMeta.max ? getStopsWithoutRange() : getStops();
|
||||
|
||||
const valueExpression = makeMbClampedNumberExpression({
|
||||
lookupFunction: this.getMbLookupFunction(),
|
||||
maxValue: rangeFieldMeta.max,
|
||||
minValue: rangeFieldMeta.min,
|
||||
fieldName: this.getMbFieldName(),
|
||||
fallback: rangeFieldMeta.min,
|
||||
fallback: invert ? rangeFieldMeta.max : rangeFieldMeta.min,
|
||||
});
|
||||
const valueShiftExpression =
|
||||
rangeFieldMeta.min < 1 ? ['+', valueExpression, valueShift] : valueExpression;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue