[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';
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>
);
}

View file

@ -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>
);
}

View file

@ -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 />
&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 }) {
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 = {

View file

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