[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:
Nathan Reese 2020-01-21 18:52:13 -05:00 committed by GitHub
parent b78c1b1042
commit 04374e665c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 165 additions and 187 deletions

View file

@ -14,9 +14,7 @@ import { ColorStopsCategorical } from './color_stops_categorical';
const CUSTOM_COLOR_MAP = 'CUSTOM_COLOR_MAP'; const CUSTOM_COLOR_MAP = 'CUSTOM_COLOR_MAP';
export class ColorMapSelect extends Component { export class ColorMapSelect extends Component {
state = { state = {};
selected: '',
};
static getDerivedStateFromProps(nextProps, prevState) { static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.customColorMap === prevState.prevPropsCustomColorMap) { if (nextProps.customColorMap === prevState.prevPropsCustomColorMap) {
@ -41,10 +39,7 @@ export class ColorMapSelect extends Component {
_onCustomColorMapChange = ({ colorStops, isInvalid }) => { _onCustomColorMapChange = ({ colorStops, isInvalid }) => {
// Manage invalid custom color map in local state // Manage invalid custom color map in local state
if (isInvalid) { if (isInvalid) {
const newState = { this.setState({ customColorMap: colorStops });
customColorMap: colorStops,
};
this.setState(newState);
return; return;
} }
@ -56,35 +51,34 @@ export class ColorMapSelect extends Component {
}; };
_renderColorStopsInput() { _renderColorStopsInput() {
let colorStopsInput; if (!this.props.useCustomColorMap) {
if (this.props.useCustomColorMap) { return null;
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>
);
}
} }
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() { render() {
const colorStopsInput = this._renderColorStopsInput();
const colorMapOptionsWithCustom = [ const colorMapOptionsWithCustom = [
{ {
value: CUSTOM_COLOR_MAP, value: CUSTOM_COLOR_MAP,
@ -110,7 +104,7 @@ export class ColorMapSelect extends Component {
valueOfSelected={valueOfSelected} valueOfSelected={valueOfSelected}
hasDividers={true} hasDividers={true}
/> />
{colorStopsInput} {this._renderColorStopsInput()}
</Fragment> </Fragment>
); );
} }

View file

@ -13,87 +13,56 @@ import { CATEGORICAL_DATA_TYPES, COLOR_MAP_TYPE } from '../../../../../../common
import { COLOR_GRADIENTS, COLOR_PALETTES } from '../../../color_utils'; import { COLOR_GRADIENTS, COLOR_PALETTES } from '../../../color_utils';
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
export class DynamicColorForm extends React.Component { export function DynamicColorForm({
state = { fields,
colorMapType: COLOR_MAP_TYPE.ORDINAL, 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() { const onFieldChange = async ({ field }) => {
super(); const { name, origin, type } = field;
this._isMounted = false; onDynamicStyleChange(styleProperty.getStyleName(), {
} ...styleOptions,
field: { name, origin },
componentWillUnmount() { type: CATEGORICAL_DATA_TYPES.includes(type)
this._isMounted = false; ? COLOR_MAP_TYPE.CATEGORICAL
} : COLOR_MAP_TYPE.ORDINAL,
});
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 renderColorMapSelect = () => {
if (!styleOptions.field || !styleOptions.field.name) { if (!styleOptions.field || !styleOptions.field.name) {
return; return null;
} }
let colorSelect; if (styleOptions.type === COLOR_MAP_TYPE.ORDINAL) {
const onColorChange = colorOptions => { return (
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 = (
<ColorMapSelect <ColorMapSelect
colorMapOptions={COLOR_GRADIENTS} colorMapOptions={COLOR_GRADIENTS}
customOptionLabel={customOptionLabel} customOptionLabel={i18n.translate('xpack.maps.style.customColorRampLabel', {
onChange={options => onColorChange(options)} defaultMessage: 'Custom color ramp',
})}
onChange={onColorMapSelect}
colorMapType={COLOR_MAP_TYPE.ORDINAL} colorMapType={COLOR_MAP_TYPE.ORDINAL}
color={styleOptions.color} color={styleOptions.color}
customColorMap={styleOptions.customColorRamp} customColorMap={styleOptions.customColorRamp}
@ -101,52 +70,39 @@ export class DynamicColorForm extends React.Component {
compressed 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 ( return (
<Fragment> <ColorMapSelect
<EuiFlexGroup gutterSize="none" justifyContent="flexEnd"> colorMapOptions={COLOR_PALETTES}
<EuiFlexItem grow={false}>{staticDynamicSelect}</EuiFlexItem> customOptionLabel={i18n.translate('xpack.maps.style.customColorPaletteLabel', {
<EuiFlexItem> defaultMessage: 'Custom color palette',
<FieldSelect })}
fields={fields} onChange={onColorMapSelect}
selectedFieldName={_.get(styleOptions, 'field.name')} colorMapType={COLOR_MAP_TYPE.CATEGORICAL}
onChange={onFieldChange} color={styleOptions.colorCategory}
compressed customColorMap={styleOptions.customColorPalette}
/> useCustomColorMap={_.get(styleOptions, 'useCustomColorPalette', false)}
</EuiFlexItem> compressed
</EuiFlexGroup> />
<EuiSpacer size="s" />
{colorSelect}
</Fragment>
); );
} };
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>
);
} }

View file

@ -7,9 +7,67 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import { EuiComboBox } from '@elastic/eui'; import { EuiComboBox, EuiHighlight } from '@elastic/eui';
import { SOURCE_DATA_ID_ORIGIN } from '../../../../../common/constants'; import { FIELD_ORIGIN } from '../../../../../common/constants';
import { i18n } from '@kbn/i18n'; 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 />
&nbsp;
<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 }) { export function FieldSelect({ fields, selectedFieldName, onChange, ...rest }) {
const onFieldChange = selectedFields => { 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; let selectedOption;
if (selectedFieldName) { if (selectedFieldName) {
selectedOption = fields.find(field => { selectedOption = fields.find(field => {
@ -61,7 +86,7 @@ export function FieldSelect({ fields, selectedFieldName, onChange, ...rest }) {
return ( return (
<EuiComboBox <EuiComboBox
selectedOptions={selectedOption ? [selectedOption] : []} selectedOptions={selectedOption ? [selectedOption] : []}
options={groupFieldsByOrigin()} options={groupFieldsByOrigin(fields)}
onChange={onFieldChange} onChange={onFieldChange}
singleSelection={{ asPlainText: true }} singleSelection={{ asPlainText: true }}
isClearable={false} isClearable={false}
@ -69,6 +94,7 @@ export function FieldSelect({ fields, selectedFieldName, onChange, ...rest }) {
placeholder={i18n.translate('xpack.maps.styles.vector.selectFieldPlaceholder', { placeholder={i18n.translate('xpack.maps.styles.vector.selectFieldPlaceholder', {
defaultMessage: 'Select a field', defaultMessage: 'Select a field',
})} })}
renderOption={renderOption}
{...rest} {...rest}
/> />
); );
@ -76,7 +102,8 @@ export function FieldSelect({ fields, selectedFieldName, onChange, ...rest }) {
export const fieldShape = PropTypes.shape({ export const fieldShape = PropTypes.shape({
name: PropTypes.string.isRequired, 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 = { FieldSelect.propTypes = {

View file

@ -61,6 +61,7 @@ export class VectorStyleEditor extends Component {
label: await field.getLabel(), label: await field.getLabel(),
name: field.getName(), name: field.getName(),
origin: field.getOrigin(), origin: field.getOrigin(),
type: await field.getDataType(),
}; };
}; };