[Maps] show field type icons in data driven styling field select (#55166)
* [Maps] show field icons in data driven styling field select * only show origin group label when there is more then one origin * review feedback Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
b78c1b1042
commit
04374e665c
|
@ -14,9 +14,7 @@ import { ColorStopsCategorical } from './color_stops_categorical';
|
|||
const CUSTOM_COLOR_MAP = 'CUSTOM_COLOR_MAP';
|
||||
|
||||
export class ColorMapSelect extends Component {
|
||||
state = {
|
||||
selected: '',
|
||||
};
|
||||
state = {};
|
||||
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
if (nextProps.customColorMap === prevState.prevPropsCustomColorMap) {
|
||||
|
@ -41,10 +39,7 @@ export class ColorMapSelect extends Component {
|
|||
_onCustomColorMapChange = ({ colorStops, isInvalid }) => {
|
||||
// Manage invalid custom color map in local state
|
||||
if (isInvalid) {
|
||||
const newState = {
|
||||
customColorMap: colorStops,
|
||||
};
|
||||
this.setState(newState);
|
||||
this.setState({ customColorMap: colorStops });
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -56,35 +51,34 @@ export class ColorMapSelect extends Component {
|
|||
};
|
||||
|
||||
_renderColorStopsInput() {
|
||||
let colorStopsInput;
|
||||
if (this.props.useCustomColorMap) {
|
||||
if (this.props.colorMapType === COLOR_MAP_TYPE.ORDINAL) {
|
||||
colorStopsInput = (
|
||||
<Fragment>
|
||||
<EuiSpacer size="s" />
|
||||
<ColorStopsOrdinal
|
||||
colorStops={this.state.customColorMap}
|
||||
onChange={this._onCustomColorMapChange}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
} else if (this.props.colorMapType === COLOR_MAP_TYPE.CATEGORICAL) {
|
||||
colorStopsInput = (
|
||||
<Fragment>
|
||||
<EuiSpacer size="s" />
|
||||
<ColorStopsCategorical
|
||||
colorStops={this.state.customColorMap}
|
||||
onChange={this._onCustomColorMapChange}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
if (!this.props.useCustomColorMap) {
|
||||
return null;
|
||||
}
|
||||
return colorStopsInput;
|
||||
|
||||
if (this.props.colorMapType === COLOR_MAP_TYPE.ORDINAL) {
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiSpacer size="s" />
|
||||
<ColorStopsOrdinal
|
||||
colorStops={this.state.customColorMap}
|
||||
onChange={this._onCustomColorMapChange}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiSpacer size="s" />
|
||||
<ColorStopsCategorical
|
||||
colorStops={this.state.customColorMap}
|
||||
onChange={this._onCustomColorMapChange}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const colorStopsInput = this._renderColorStopsInput();
|
||||
const colorMapOptionsWithCustom = [
|
||||
{
|
||||
value: CUSTOM_COLOR_MAP,
|
||||
|
@ -110,7 +104,7 @@ export class ColorMapSelect extends Component {
|
|||
valueOfSelected={valueOfSelected}
|
||||
hasDividers={true}
|
||||
/>
|
||||
{colorStopsInput}
|
||||
{this._renderColorStopsInput()}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -13,87 +13,56 @@ import { CATEGORICAL_DATA_TYPES, COLOR_MAP_TYPE } from '../../../../../../common
|
|||
import { COLOR_GRADIENTS, COLOR_PALETTES } from '../../../color_utils';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export class DynamicColorForm extends React.Component {
|
||||
state = {
|
||||
colorMapType: COLOR_MAP_TYPE.ORDINAL,
|
||||
export function DynamicColorForm({
|
||||
fields,
|
||||
onDynamicStyleChange,
|
||||
staticDynamicSelect,
|
||||
styleProperty,
|
||||
}) {
|
||||
const styleOptions = styleProperty.getOptions();
|
||||
|
||||
const onColorMapSelect = ({ color, customColorMap, type, useCustomColorMap }) => {
|
||||
const newColorOptions = {
|
||||
...styleOptions,
|
||||
type,
|
||||
};
|
||||
if (type === COLOR_MAP_TYPE.ORDINAL) {
|
||||
newColorOptions.useCustomColorRamp = useCustomColorMap;
|
||||
newColorOptions.customColorRamp = customColorMap;
|
||||
newColorOptions.color = color;
|
||||
} else {
|
||||
newColorOptions.useCustomColorPalette = useCustomColorMap;
|
||||
newColorOptions.customColorPalette = customColorMap;
|
||||
newColorOptions.colorCategory = color;
|
||||
}
|
||||
|
||||
onDynamicStyleChange(styleProperty.getStyleName(), newColorOptions);
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._isMounted = false;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._isMounted = false;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._isMounted = true;
|
||||
this._loadColorMapType();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this._loadColorMapType();
|
||||
}
|
||||
|
||||
async _loadColorMapType() {
|
||||
const field = this.props.styleProperty.getField();
|
||||
if (!field) {
|
||||
return;
|
||||
}
|
||||
const dataType = await field.getDataType();
|
||||
const colorMapType = CATEGORICAL_DATA_TYPES.includes(dataType)
|
||||
? COLOR_MAP_TYPE.CATEGORICAL
|
||||
: COLOR_MAP_TYPE.ORDINAL;
|
||||
if (this._isMounted && this.state.colorMapType !== colorMapType) {
|
||||
this.setState({ colorMapType }, () => {
|
||||
const options = this.props.styleProperty.getOptions();
|
||||
this.props.onDynamicStyleChange(this.props.styleProperty.getStyleName(), {
|
||||
...options,
|
||||
type: colorMapType,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_getColorSelector() {
|
||||
const { onDynamicStyleChange, styleProperty } = this.props;
|
||||
const styleOptions = styleProperty.getOptions();
|
||||
const onFieldChange = async ({ field }) => {
|
||||
const { name, origin, type } = field;
|
||||
onDynamicStyleChange(styleProperty.getStyleName(), {
|
||||
...styleOptions,
|
||||
field: { name, origin },
|
||||
type: CATEGORICAL_DATA_TYPES.includes(type)
|
||||
? COLOR_MAP_TYPE.CATEGORICAL
|
||||
: COLOR_MAP_TYPE.ORDINAL,
|
||||
});
|
||||
};
|
||||
|
||||
const renderColorMapSelect = () => {
|
||||
if (!styleOptions.field || !styleOptions.field.name) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
let colorSelect;
|
||||
const onColorChange = colorOptions => {
|
||||
const newColorOptions = {
|
||||
type: colorOptions.type,
|
||||
};
|
||||
if (colorOptions.type === COLOR_MAP_TYPE.ORDINAL) {
|
||||
newColorOptions.useCustomColorRamp = colorOptions.useCustomColorMap;
|
||||
newColorOptions.customColorRamp = colorOptions.customColorMap;
|
||||
newColorOptions.color = colorOptions.color;
|
||||
} else {
|
||||
newColorOptions.useCustomColorPalette = colorOptions.useCustomColorMap;
|
||||
newColorOptions.customColorPalette = colorOptions.customColorMap;
|
||||
newColorOptions.colorCategory = colorOptions.color;
|
||||
}
|
||||
|
||||
onDynamicStyleChange(styleProperty.getStyleName(), {
|
||||
...styleOptions,
|
||||
...newColorOptions,
|
||||
});
|
||||
};
|
||||
|
||||
if (this.state.colorMapType === COLOR_MAP_TYPE.ORDINAL) {
|
||||
const customOptionLabel = i18n.translate('xpack.maps.style.customColorRampLabel', {
|
||||
defaultMessage: 'Custom color ramp',
|
||||
});
|
||||
colorSelect = (
|
||||
if (styleOptions.type === COLOR_MAP_TYPE.ORDINAL) {
|
||||
return (
|
||||
<ColorMapSelect
|
||||
colorMapOptions={COLOR_GRADIENTS}
|
||||
customOptionLabel={customOptionLabel}
|
||||
onChange={options => onColorChange(options)}
|
||||
customOptionLabel={i18n.translate('xpack.maps.style.customColorRampLabel', {
|
||||
defaultMessage: 'Custom color ramp',
|
||||
})}
|
||||
onChange={onColorMapSelect}
|
||||
colorMapType={COLOR_MAP_TYPE.ORDINAL}
|
||||
color={styleOptions.color}
|
||||
customColorMap={styleOptions.customColorRamp}
|
||||
|
@ -101,52 +70,39 @@ export class DynamicColorForm extends React.Component {
|
|||
compressed
|
||||
/>
|
||||
);
|
||||
} else if (this.state.colorMapType === COLOR_MAP_TYPE.CATEGORICAL) {
|
||||
const customOptionLabel = i18n.translate('xpack.maps.style.customColorPaletteLabel', {
|
||||
defaultMessage: 'Custom color palette',
|
||||
});
|
||||
colorSelect = (
|
||||
<ColorMapSelect
|
||||
colorMapOptions={COLOR_PALETTES}
|
||||
customOptionLabel={customOptionLabel}
|
||||
onChange={options => onColorChange(options)}
|
||||
colorMapType={COLOR_MAP_TYPE.CATEGORICAL}
|
||||
color={styleOptions.colorCategory}
|
||||
customColorMap={styleOptions.customColorPalette}
|
||||
useCustomColorMap={_.get(styleOptions, 'useCustomColorPalette', false)}
|
||||
compressed
|
||||
/>
|
||||
);
|
||||
}
|
||||
return colorSelect;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { fields, onDynamicStyleChange, staticDynamicSelect, styleProperty } = this.props;
|
||||
const styleOptions = styleProperty.getOptions();
|
||||
const onFieldChange = options => {
|
||||
const field = options.field;
|
||||
onDynamicStyleChange(styleProperty.getStyleName(), { ...styleOptions, field });
|
||||
};
|
||||
|
||||
const colorSelect = this._getColorSelector();
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiFlexGroup gutterSize="none" justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>{staticDynamicSelect}</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<FieldSelect
|
||||
fields={fields}
|
||||
selectedFieldName={_.get(styleOptions, 'field.name')}
|
||||
onChange={onFieldChange}
|
||||
compressed
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="s" />
|
||||
{colorSelect}
|
||||
</Fragment>
|
||||
<ColorMapSelect
|
||||
colorMapOptions={COLOR_PALETTES}
|
||||
customOptionLabel={i18n.translate('xpack.maps.style.customColorPaletteLabel', {
|
||||
defaultMessage: 'Custom color palette',
|
||||
})}
|
||||
onChange={onColorMapSelect}
|
||||
colorMapType={COLOR_MAP_TYPE.CATEGORICAL}
|
||||
color={styleOptions.colorCategory}
|
||||
customColorMap={styleOptions.customColorPalette}
|
||||
useCustomColorMap={_.get(styleOptions, 'useCustomColorPalette', false)}
|
||||
compressed
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiFlexGroup gutterSize="none" justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>{staticDynamicSelect}</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<FieldSelect
|
||||
fields={fields}
|
||||
selectedFieldName={_.get(styleOptions, 'field.name')}
|
||||
onChange={onFieldChange}
|
||||
compressed
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="s" />
|
||||
{renderColorMapSelect()}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -7,9 +7,67 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
import { EuiComboBox } from '@elastic/eui';
|
||||
import { SOURCE_DATA_ID_ORIGIN } from '../../../../../common/constants';
|
||||
import { EuiComboBox, EuiHighlight } from '@elastic/eui';
|
||||
import { FIELD_ORIGIN } from '../../../../../common/constants';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FieldIcon } from '../../../../../../../../../src/plugins/kibana_react/public';
|
||||
|
||||
function renderOption(option, searchValue, contentClassName) {
|
||||
return (
|
||||
<span className={contentClassName}>
|
||||
<FieldIcon type={option.value.type} size="m" useColor />
|
||||
|
||||
<EuiHighlight search={searchValue}>{option.label}</EuiHighlight>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function groupFieldsByOrigin(fields) {
|
||||
const fieldsByOriginMap = new Map();
|
||||
fields.forEach(field => {
|
||||
if (fieldsByOriginMap.has(field.origin)) {
|
||||
const fieldsList = fieldsByOriginMap.get(field.origin);
|
||||
fieldsList.push(field);
|
||||
fieldsByOriginMap.set(field.origin, fieldsList);
|
||||
} else {
|
||||
fieldsByOriginMap.set(field.origin, [field]);
|
||||
}
|
||||
});
|
||||
|
||||
function fieldsListToOptions(fieldsList) {
|
||||
return fieldsList
|
||||
.map(field => {
|
||||
return { value: field, label: field.label };
|
||||
})
|
||||
.sort((a, b) => {
|
||||
return a.label.toLowerCase().localeCompare(b.label.toLowerCase());
|
||||
});
|
||||
}
|
||||
|
||||
if (fieldsByOriginMap.size === 1) {
|
||||
// do not show origin group if all fields are from same origin
|
||||
const onlyOriginKey = fieldsByOriginMap.keys().next().value;
|
||||
const fieldsList = fieldsByOriginMap.get(onlyOriginKey);
|
||||
return fieldsListToOptions(fieldsList);
|
||||
}
|
||||
|
||||
const optionGroups = [];
|
||||
fieldsByOriginMap.forEach((fieldsList, fieldOrigin) => {
|
||||
optionGroups.push({
|
||||
label: i18n.translate('xpack.maps.style.fieldSelect.OriginLabel', {
|
||||
defaultMessage: 'Fields from {fieldOrigin}',
|
||||
values: { fieldOrigin },
|
||||
}),
|
||||
options: fieldsListToOptions(fieldsList),
|
||||
});
|
||||
});
|
||||
|
||||
optionGroups.sort((a, b) => {
|
||||
return a.label.toLowerCase().localeCompare(b.label.toLowerCase());
|
||||
});
|
||||
|
||||
return optionGroups;
|
||||
}
|
||||
|
||||
export function FieldSelect({ fields, selectedFieldName, onChange, ...rest }) {
|
||||
const onFieldChange = selectedFields => {
|
||||
|
@ -18,39 +76,6 @@ export function FieldSelect({ fields, selectedFieldName, onChange, ...rest }) {
|
|||
});
|
||||
};
|
||||
|
||||
const groupFieldsByOrigin = () => {
|
||||
const fieldsByOriginMap = new Map();
|
||||
fields.forEach(field => {
|
||||
if (fieldsByOriginMap.has(field.origin)) {
|
||||
const fieldsList = fieldsByOriginMap.get(field.origin);
|
||||
fieldsList.push(field);
|
||||
fieldsByOriginMap.set(field.origin, fieldsList);
|
||||
} else {
|
||||
fieldsByOriginMap.set(field.origin, [field]);
|
||||
}
|
||||
});
|
||||
|
||||
const optionGroups = [];
|
||||
fieldsByOriginMap.forEach((fieldsList, fieldOrigin) => {
|
||||
optionGroups.push({
|
||||
label: fieldOrigin,
|
||||
options: fieldsList
|
||||
.map(field => {
|
||||
return { value: field, label: field.label };
|
||||
})
|
||||
.sort((a, b) => {
|
||||
return a.label.toLowerCase().localeCompare(b.label.toLowerCase());
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
optionGroups.sort((a, b) => {
|
||||
return a.label.toLowerCase().localeCompare(b.label.toLowerCase());
|
||||
});
|
||||
|
||||
return optionGroups;
|
||||
};
|
||||
|
||||
let selectedOption;
|
||||
if (selectedFieldName) {
|
||||
selectedOption = fields.find(field => {
|
||||
|
@ -61,7 +86,7 @@ export function FieldSelect({ fields, selectedFieldName, onChange, ...rest }) {
|
|||
return (
|
||||
<EuiComboBox
|
||||
selectedOptions={selectedOption ? [selectedOption] : []}
|
||||
options={groupFieldsByOrigin()}
|
||||
options={groupFieldsByOrigin(fields)}
|
||||
onChange={onFieldChange}
|
||||
singleSelection={{ asPlainText: true }}
|
||||
isClearable={false}
|
||||
|
@ -69,6 +94,7 @@ export function FieldSelect({ fields, selectedFieldName, onChange, ...rest }) {
|
|||
placeholder={i18n.translate('xpack.maps.styles.vector.selectFieldPlaceholder', {
|
||||
defaultMessage: 'Select a field',
|
||||
})}
|
||||
renderOption={renderOption}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
|
@ -76,7 +102,8 @@ export function FieldSelect({ fields, selectedFieldName, onChange, ...rest }) {
|
|||
|
||||
export const fieldShape = PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
origin: PropTypes.oneOf(['join', SOURCE_DATA_ID_ORIGIN]).isRequired,
|
||||
origin: PropTypes.oneOf(Object.values(FIELD_ORIGIN)).isRequired,
|
||||
type: PropTypes.string.isRequired,
|
||||
});
|
||||
|
||||
FieldSelect.propTypes = {
|
||||
|
|
|
@ -61,6 +61,7 @@ export class VectorStyleEditor extends Component {
|
|||
label: await field.getLabel(),
|
||||
name: field.getName(),
|
||||
origin: field.getOrigin(),
|
||||
type: await field.getDataType(),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue