[Maps] Support categorical styling for numbers and dates (#57908)

This commit is contained in:
Thomas Neirynck 2020-03-09 13:22:30 -04:00 committed by GitHub
parent 4bd7b36431
commit 6e5e8c815e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 320 additions and 183 deletions

View file

@ -165,6 +165,7 @@ export const COLOR_MAP_TYPE = {
export const COLOR_PALETTE_MAX_SIZE = 10;
export const CATEGORICAL_DATA_TYPES = ['string', 'ip', 'boolean'];
export const ORDINAL_DATA_TYPES = ['number', 'date'];
export const SYMBOLIZE_AS_TYPES = {
CIRCLE: 'circle',

View file

@ -35,11 +35,11 @@ export type AggDescriptor = {
type: AGG_TYPE;
};
export type AbstractESAggDescriptor = AbstractESSourceDescriptor & {
export type AbstractESAggSourceDescriptor = AbstractESSourceDescriptor & {
metrics: AggDescriptor[];
};
export type ESGeoGridSourceDescriptor = AbstractESAggDescriptor & {
export type ESGeoGridSourceDescriptor = AbstractESAggSourceDescriptor & {
requestType?: RENDER_AS;
resolution?: GRID_RESOLUTION;
};
@ -54,12 +54,12 @@ export type ESSearchSourceDescriptor = AbstractESSourceDescriptor & {
topHitsSize?: number;
};
export type ESPewPewSourceDescriptor = AbstractESAggDescriptor & {
export type ESPewPewSourceDescriptor = AbstractESAggSourceDescriptor & {
sourceGeoField: string;
destGeoField: string;
};
export type ESTermSourceDescriptor = AbstractESAggDescriptor & {
export type ESTermSourceDescriptor = AbstractESAggSourceDescriptor & {
indexPatternTitle: string;
term: string; // term field name
};

View file

@ -57,11 +57,6 @@ export class ESDocField extends AbstractField {
async getCategoricalFieldMetaRequest() {
const field = await this._getField();
if (field.type !== 'string') {
//UX does not support categorical styling for number/date fields
return null;
}
const topTerms = {
size: COLOR_PALETTE_MAX_SIZE - 1, //need additional color for the "other"-value
};

View file

@ -332,18 +332,6 @@ export class AbstractLayer {
return [];
}
async getDateFields() {
return [];
}
async getNumberFields() {
return [];
}
async getCategoricalFields() {
return [];
}
async getFields() {
return [];
}

View file

@ -7,13 +7,19 @@
import { IESSource } from './es_source';
import { AbstractESSource } from './es_source';
import { AGG_TYPE } from '../../../common/constants';
import { IESAggField } from '../fields/es_agg_field';
import { AbstractESAggSourceDescriptor } from '../../../common/descriptor_types';
export interface IESAggSource extends IESSource {
getAggKey(aggType: AGG_TYPE, fieldName: string): string;
getAggLabel(aggType: AGG_TYPE, fieldName: string): string;
getMetricFields(): IESAggField[];
}
export class AbstractESAggSource extends AbstractESSource implements IESAggSource {
constructor(sourceDescriptor: AbstractESAggSourceDescriptor, inspectorAdapters: object);
getAggKey(aggType: AGG_TYPE, fieldName: string): string;
getAggLabel(aggType: AGG_TYPE, fieldName: string): string;
getMetricFields(): IESAggField[];
}

View file

@ -78,6 +78,10 @@ export class AbstractESAggSource extends AbstractESSource {
}
}
async getFields() {
return this.getMetricFields();
}
getValueAggsDsl(indexPattern) {
const valueAggsDsl = {};
this.getMetricFields().forEach(esAggMetric => {
@ -89,10 +93,6 @@ export class AbstractESAggSource extends AbstractESSource {
return valueAggsDsl;
}
async getNumberFields() {
return this.getMetricFields();
}
async filterAndFormatPropertiesToHtmlForMetricFields(properties) {
const metricFields = this.getMetricFields();
const tooltipPropertiesPromises = [];

View file

@ -0,0 +1,82 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { AbstractESAggSource } from './es_agg_source';
import { IField } from '../fields/field';
import { IESAggField } from '../fields/es_agg_field';
import _ from 'lodash';
import { AGG_TYPE } from '../../../common/constants';
import { AggDescriptor } from '../../../common/descriptor_types';
jest.mock('ui/new_platform');
const sumFieldName = 'myFieldGettingSummed';
const metricExamples = [
{
type: AGG_TYPE.SUM,
field: sumFieldName,
label: 'my custom label',
},
{
// metric config is invalid beause field is missing
type: AGG_TYPE.MAX,
},
{
// metric config is valid because "count" metric does not need to provide field
type: AGG_TYPE.COUNT,
label: '', // should ignore empty label fields
},
];
class TestESAggSource extends AbstractESAggSource {
constructor(metrics: AggDescriptor[]) {
super({ type: 'test', id: 'foobar', indexPatternId: 'foobarid', metrics }, []);
}
}
describe('getMetricFields', () => {
it('should add default "count" metric when no metrics are provided', async () => {
const source = new TestESAggSource([]);
const metrics = source.getMetricFields();
expect(metrics.length).toBe(1);
expect(metrics[0].getName()).toEqual('doc_count');
expect(await metrics[0].getLabel()).toEqual('count');
});
it('should remove incomplete metric configurations', async () => {
const source = new TestESAggSource(metricExamples);
const metrics = source.getMetricFields();
expect(metrics.length).toBe(2);
expect(metrics[0].getRootName()).toEqual(sumFieldName);
expect(metrics[0].getName()).toEqual('sum_of_myFieldGettingSummed');
expect(await metrics[0].getLabel()).toEqual('my custom label');
expect(metrics[1].getName()).toEqual('doc_count');
expect(await metrics[1].getLabel()).toEqual('count');
});
it('getMetrics should be identical to getFields', async () => {
const source = new TestESAggSource(metricExamples);
const metrics = source.getMetricFields();
const fields = await source.getFields();
const getFieldMeta = async (field: IField) => {
const esAggField = field as IESAggField; // this ensures we can downcast correctly.
return {
name: esAggField.getName(),
label: await esAggField.getLabel(),
esDoc: esAggField.getRootName(),
};
};
const metricsFieldMeta = await Promise.all(metrics.map(getFieldMeta));
const fieldsFieldMeta = await Promise.all(fields.map(getFieldMeta));
expect(_.isEqual(metricsFieldMeta, fieldsFieldMeta)).toEqual(true);
});
});

View file

@ -6,7 +6,10 @@
import { AbstractESAggSource } from '../es_agg_source';
import { ESGeoGridSourceDescriptor } from '../../../../common/descriptor_types';
import { GRID_RESOLUTION } from '../../../../common/constants';
export class ESGeoGridSource extends AbstractESAggSource {
constructor(sourceDescriptor: ESGeoGridSourceDescriptor, inspectorAdapters: unknown);
getGridResolution(): GRID_RESOLUTION;
getGeoGridPrecision(zoom: number): number;
}

View file

@ -35,7 +35,7 @@ import { DynamicStyleProperty } from '../../styles/vector/properties/dynamic_sty
import { StaticStyleProperty } from '../../styles/vector/properties/static_style_property';
import { DataRequestAbortError } from '../../util/data_request';
const MAX_GEOTILE_LEVEL = 29;
export const MAX_GEOTILE_LEVEL = 29;
export class ESGeoGridSource extends AbstractESAggSource {
static type = ES_GEO_GRID;

View file

@ -0,0 +1,41 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
jest.mock('../../../kibana_services', () => {});
jest.mock('ui/new_platform');
import { ESGeoGridSource } from './es_geo_grid_source';
import { ES_GEO_GRID, GRID_RESOLUTION, RENDER_AS } from '../../../../common/constants';
describe('ESGeoGridSource', () => {
const geogridSource = new ESGeoGridSource(
{
id: 'foobar',
indexPatternId: 'fooIp',
geoField: 'bar',
metrics: [],
resolution: GRID_RESOLUTION.COARSE,
type: ES_GEO_GRID,
requestType: RENDER_AS.HEATMAP,
},
{}
);
describe('getGridResolution', () => {
it('should echo gridResoltuion', () => {
expect(geogridSource.getGridResolution()).toBe(GRID_RESOLUTION.COARSE);
});
});
describe('getGeoGridPrecision', () => {
it('should clamp geo-grid derived zoom to max geotile level supported by ES', () => {
expect(geogridSource.getGeoGridPrecision(29)).toBe(29);
});
it('should use heuristic to derive precision', () => {
expect(geogridSource.getGeoGridPrecision(10)).toBe(12);
});
});
});

View file

@ -19,7 +19,6 @@ import {
ES_GEO_FIELD_TYPE,
DEFAULT_MAX_BUCKETS_LIMIT,
SORT_ORDER,
CATEGORICAL_DATA_TYPES,
} from '../../../../common/constants';
import { i18n } from '@kbn/i18n';
import { getDataSourceLabel } from '../../../../common/i18n_getters';
@ -135,49 +134,6 @@ export class ESSearchSource extends AbstractESSource {
);
}
async getNumberFields() {
try {
const indexPattern = await this.getIndexPattern();
return indexPattern.fields.getByType('number').map(field => {
return this.createField({ fieldName: field.name });
});
} catch (error) {
return [];
}
}
async getDateFields() {
try {
const indexPattern = await this.getIndexPattern();
return indexPattern.fields.getByType('date').map(field => {
return this.createField({ fieldName: field.name });
});
} catch (error) {
return [];
}
}
async getCategoricalFields() {
try {
const indexPattern = await this.getIndexPattern();
const aggFields = [];
CATEGORICAL_DATA_TYPES.forEach(dataType => {
indexPattern.fields.getByType(dataType).forEach(field => {
if (field.aggregatable) {
aggFields.push(field);
}
});
});
return aggFields.map(field => {
return this.createField({ fieldName: field.name });
});
} catch (error) {
//error surfaces in the LayerTOC UI
return [];
}
}
async getFields() {
try {
const indexPattern = await this.getIndexPattern();

View file

@ -31,36 +31,27 @@ const metricExamples = [
];
describe('getMetricFields', () => {
it('should add default "count" metric when no metrics are provided', async () => {
it('should override name and label of count metric', async () => {
const source = new ESTermSource({
indexPatternTitle: indexPatternTitle,
term: termFieldName,
});
const metrics = source.getMetricFields();
expect(metrics.length).toBe(1);
expect(metrics[0].getAggType()).toEqual('count');
expect(metrics[0].getName()).toEqual('__kbnjoin__count_groupby_myIndex.myTermField');
expect(await metrics[0].getLabel()).toEqual('Count of myIndex');
});
it('should remove incomplete metric configurations', async () => {
it('should override name and label of sum metric', async () => {
const source = new ESTermSource({
indexPatternTitle: indexPatternTitle,
term: termFieldName,
metrics: metricExamples,
});
const metrics = source.getMetricFields();
expect(metrics.length).toBe(2);
expect(metrics[0].getAggType()).toEqual('sum');
expect(metrics[0].getRootName()).toEqual(sumFieldName);
expect(metrics[0].getName()).toEqual(
'__kbnjoin__sum_of_myFieldGettingSummed_groupby_myIndex.myTermField'
);
expect(await metrics[0].getLabel()).toEqual('my custom label');
expect(metrics[1].getAggType()).toEqual('count');
expect(metrics[1].getName()).toEqual('__kbnjoin__count_groupby_myIndex.myTermField');
expect(await metrics[1].getLabel()).toEqual('Count of myIndex');
});

View file

@ -111,19 +111,7 @@ export class AbstractVectorSource extends AbstractSource {
return null;
}
async getDateFields() {
return [];
}
async getNumberFields() {
return [];
}
async getFields() {
return [...(await this.getDateFields()), ...(await this.getNumberFields())];
}
async getCategoricalFields() {
return [];
}

View file

@ -6,10 +6,11 @@
import React, { Component, Fragment } from 'react';
import { EuiSuperSelect, EuiSpacer } from '@elastic/eui';
import { EuiSpacer, EuiSelect, EuiSuperSelect, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { ColorStopsOrdinal } from './color_stops_ordinal';
import { COLOR_MAP_TYPE } from '../../../../../../common/constants';
import { ColorStopsCategorical } from './color_stops_categorical';
import { i18n } from '@kbn/i18n';
const CUSTOM_COLOR_MAP = 'CUSTOM_COLOR_MAP';
@ -27,6 +28,43 @@ export class ColorMapSelect extends Component {
};
}
_renderColorMapToggle() {
const options = [
{
value: COLOR_MAP_TYPE.ORDINAL,
text: i18n.translate('xpack.maps.styles.dynamicColorSelect.quantitativeLabel', {
defaultMessage: 'As number',
}),
},
{
value: COLOR_MAP_TYPE.CATEGORICAL,
text: i18n.translate('xpack.maps.styles.dynamicColorSelect.qualitativeLabel', {
defaultMessage: 'As category',
}),
},
];
const selectedValue = this.props.styleProperty.isOrdinal()
? COLOR_MAP_TYPE.ORDINAL
: COLOR_MAP_TYPE.CATEGORICAL;
return (
<EuiSelect
options={options}
value={selectedValue}
onChange={this.props.onColorMapTypeChange}
aria-label={i18n.translate(
'xpack.maps.styles.dynamicColorSelect.qualitativeOrQuantitativeAriaLabel',
{
defaultMessage:
'Choose `As number` to map by number in a color range, or `As category`to categorize by color palette.',
}
)}
compressed
/>
);
}
_onColorMapSelect = selectedValue => {
const useCustomColorMap = selectedValue === CUSTOM_COLOR_MAP;
this.props.onChange({
@ -55,32 +93,32 @@ export class ColorMapSelect extends Component {
return null;
}
let colorStopEditor;
if (this.props.colorMapType === COLOR_MAP_TYPE.ORDINAL) {
return (
<Fragment>
<EuiSpacer size="s" />
<ColorStopsOrdinal
colorStops={this.state.customColorMap}
onChange={this._onCustomColorMapChange}
/>
</Fragment>
colorStopEditor = (
<ColorStopsOrdinal
colorStops={this.state.customColorMap}
onChange={this._onCustomColorMapChange}
/>
);
}
return (
<Fragment>
<EuiSpacer size="s" />
} else
colorStopEditor = (
<ColorStopsCategorical
colorStops={this.state.customColorMap}
field={this.props.styleProperty.getField()}
getValueSuggestions={this.props.styleProperty.getValueSuggestions}
onChange={this._onCustomColorMapChange}
/>
</Fragment>
);
return (
<EuiFlexGroup>
<EuiFlexItem>{colorStopEditor}</EuiFlexItem>
</EuiFlexGroup>
);
}
render() {
_renderColorMapSelections() {
const colorMapOptionsWithCustom = [
{
value: CUSTOM_COLOR_MAP,
@ -98,15 +136,31 @@ export class ColorMapSelect extends Component {
: '';
}
const toggle = this.props.showColorMapTypeToggle ? (
<EuiFlexItem grow={false}>{this._renderColorMapToggle()}</EuiFlexItem>
) : null;
return (
<EuiFlexGroup gutterSize={'none'}>
{toggle}
<EuiFlexItem>
<EuiSuperSelect
compressed
options={colorMapOptionsWithCustom}
onChange={this._onColorMapSelect}
valueOfSelected={valueOfSelected}
hasDividers={true}
/>
</EuiFlexItem>
</EuiFlexGroup>
);
}
render() {
return (
<Fragment>
<EuiSuperSelect
options={colorMapOptionsWithCustom}
onChange={this._onColorMapSelect}
valueOfSelected={valueOfSelected}
hasDividers={true}
compressed
/>
{this._renderColorMapSelections()}
<EuiSpacer size="s" />
{this._renderColorStopsInput()}
</Fragment>
);

View file

@ -40,21 +40,44 @@ export function DynamicColorForm({
};
const onFieldChange = async ({ field }) => {
const { name, origin, type } = field;
const { name, origin, type: fieldType } = field;
const defaultColorMapType = CATEGORICAL_DATA_TYPES.includes(fieldType)
? COLOR_MAP_TYPE.CATEGORICAL
: COLOR_MAP_TYPE.ORDINAL;
onDynamicStyleChange(styleProperty.getStyleName(), {
...styleOptions,
field: { name, origin },
type: CATEGORICAL_DATA_TYPES.includes(type)
? COLOR_MAP_TYPE.CATEGORICAL
: COLOR_MAP_TYPE.ORDINAL,
type: defaultColorMapType,
});
};
const onColorMapTypeChange = async e => {
const colorMapType = e.target.value;
onDynamicStyleChange(styleProperty.getStyleName(), {
...styleOptions,
type: colorMapType,
});
};
const getField = () => {
const fieldName = styleProperty.getFieldName();
if (!fieldName) {
return null;
}
return fields.find(field => {
return field.name === fieldName;
});
};
const renderColorMapSelect = () => {
if (!styleOptions.field || !styleOptions.field.name) {
const field = getField();
if (!field) {
return null;
}
const showColorMapTypeToggle = !CATEGORICAL_DATA_TYPES.includes(field.type);
if (styleProperty.isOrdinal()) {
return (
<ColorMapSelect
@ -63,29 +86,33 @@ export function DynamicColorForm({
defaultMessage: 'Custom color ramp',
})}
onChange={onColorMapSelect}
onColorMapTypeChange={onColorMapTypeChange}
colorMapType={COLOR_MAP_TYPE.ORDINAL}
color={styleOptions.color}
customColorMap={styleOptions.customColorRamp}
useCustomColorMap={_.get(styleOptions, 'useCustomColorRamp', false)}
styleProperty={styleProperty}
showColorMapTypeToggle={showColorMapTypeToggle}
/>
);
} else if (styleProperty.isCategorical()) {
return (
<ColorMapSelect
colorMapOptions={COLOR_PALETTES}
customOptionLabel={i18n.translate('xpack.maps.style.customColorPaletteLabel', {
defaultMessage: 'Custom color palette',
})}
onColorMapTypeChange={onColorMapTypeChange}
onChange={onColorMapSelect}
colorMapType={COLOR_MAP_TYPE.CATEGORICAL}
color={styleOptions.colorCategory}
customColorMap={styleOptions.customColorPalette}
useCustomColorMap={_.get(styleOptions, 'useCustomColorPalette', false)}
styleProperty={styleProperty}
showColorMapTypeToggle={showColorMapTypeToggle}
/>
);
}
return (
<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)}
styleProperty={styleProperty}
/>
);
};
return (

View file

@ -26,13 +26,14 @@ import { VECTOR_SHAPE_TYPES } from '../../../sources/vector_feature_types';
import { i18n } from '@kbn/i18n';
import { EuiSpacer, EuiButtonGroup, EuiFormRow, EuiSwitch } from '@elastic/eui';
import { CATEGORICAL_DATA_TYPES, ORDINAL_DATA_TYPES } from '../../../../../common/constants';
export class VectorStyleEditor extends Component {
state = {
dateFields: [],
numberFields: [],
categoricalFields: [],
fields: [],
ordinalAndCategoricalFields: [],
defaultDynamicProperties: getDefaultDynamicProperties(),
defaultStaticProperties: getDefaultStaticProperties(),
supportedFeatures: undefined,
@ -64,33 +65,24 @@ export class VectorStyleEditor extends Component {
};
};
const dateFields = await this.props.layer.getDateFields();
const dateFieldPromises = dateFields.map(getFieldMeta);
const dateFieldsArray = await Promise.all(dateFieldPromises);
if (this._isMounted && !_.isEqual(dateFieldsArray, this.state.dateFields)) {
this.setState({ dateFields: dateFieldsArray });
}
const numberFields = await this.props.layer.getNumberFields();
const numberFieldPromises = numberFields.map(getFieldMeta);
const numberFieldsArray = await Promise.all(numberFieldPromises);
if (this._isMounted && !_.isEqual(numberFieldsArray, this.state.numberFields)) {
this.setState({ numberFields: numberFieldsArray });
}
const categoricalFields = await this.props.layer.getCategoricalFields();
const categoricalFieldMeta = categoricalFields.map(getFieldMeta);
const categoricalFieldsArray = await Promise.all(categoricalFieldMeta);
if (this._isMounted && !_.isEqual(categoricalFieldsArray, this.state.categoricalFields)) {
this.setState({ categoricalFields: categoricalFieldsArray });
}
//These are all fields (only used for text labeling)
const fields = await this.props.layer.getFields();
const fieldPromises = fields.map(getFieldMeta);
const fieldsArray = await Promise.all(fieldPromises);
if (this._isMounted && !_.isEqual(fieldsArray, this.state.fields)) {
this.setState({ fields: fieldsArray });
const fieldsArrayAll = await Promise.all(fieldPromises);
if (!this._isMounted || _.isEqual(fieldsArrayAll, this.state.fields)) {
return;
}
this.setState({
fields: fieldsArrayAll,
ordinalAndCategoricalFields: fieldsArrayAll.filter(field => {
return (
CATEGORICAL_DATA_TYPES.includes(field.type) || ORDINAL_DATA_TYPES.includes(field.type)
);
}),
dateFields: fieldsArrayAll.filter(field => field.type === 'date'),
numberFields: fieldsArrayAll.filter(field => field.type === 'number'),
});
}
async _loadSupportedFeatures() {
@ -118,10 +110,6 @@ export class VectorStyleEditor extends Component {
return [...this.state.dateFields, ...this.state.numberFields];
}
_getOrdinalAndCategoricalFields() {
return [...this.state.dateFields, ...this.state.numberFields, ...this.state.categoricalFields];
}
_handleSelectedFeatureChange = selectedFeature => {
this.setState({ selectedFeature });
};
@ -172,7 +160,7 @@ export class VectorStyleEditor extends Component {
onStaticStyleChange={this._onStaticStyleChange}
onDynamicStyleChange={this._onDynamicStyleChange}
styleProperty={this.props.styleProperties[VECTOR_STYLES.FILL_COLOR]}
fields={this._getOrdinalAndCategoricalFields()}
fields={this.state.ordinalAndCategoricalFields}
defaultStaticStyleOptions={
this.state.defaultStaticProperties[VECTOR_STYLES.FILL_COLOR].options
}
@ -193,7 +181,7 @@ export class VectorStyleEditor extends Component {
onStaticStyleChange={this._onStaticStyleChange}
onDynamicStyleChange={this._onDynamicStyleChange}
styleProperty={this.props.styleProperties[VECTOR_STYLES.LINE_COLOR]}
fields={this._getOrdinalAndCategoricalFields()}
fields={this.state.ordinalAndCategoricalFields}
defaultStaticStyleOptions={
this.state.defaultStaticProperties[VECTOR_STYLES.LINE_COLOR].options
}
@ -249,7 +237,7 @@ export class VectorStyleEditor extends Component {
onStaticStyleChange={this._onStaticStyleChange}
onDynamicStyleChange={this._onDynamicStyleChange}
styleProperty={this.props.styleProperties[VECTOR_STYLES.LABEL_COLOR]}
fields={this._getOrdinalAndCategoricalFields()}
fields={this.state.ordinalAndCategoricalFields}
defaultStaticStyleOptions={
this.state.defaultStaticProperties[VECTOR_STYLES.LABEL_COLOR].options
}
@ -282,7 +270,7 @@ export class VectorStyleEditor extends Component {
onStaticStyleChange={this._onStaticStyleChange}
onDynamicStyleChange={this._onDynamicStyleChange}
styleProperty={this.props.styleProperties[VECTOR_STYLES.LABEL_BORDER_COLOR]}
fields={this._getOrdinalAndCategoricalFields()}
fields={this.state.ordinalAndCategoricalFields}
defaultStaticStyleOptions={
this.state.defaultStaticProperties[VECTOR_STYLES.LABEL_BORDER_COLOR].options
}
@ -335,7 +323,7 @@ export class VectorStyleEditor extends Component {
onStaticStyleChange={this._onStaticStyleChange}
onDynamicStyleChange={this._onDynamicStyleChange}
styleProperty={this.props.styleProperties[VECTOR_STYLES.ICON]}
fields={this.state.categoricalFields}
fields={this.state.ordinalAndCategoricalFields}
defaultStaticStyleOptions={
this.state.defaultStaticProperties[VECTOR_STYLES.ICON].options
}

View file

@ -236,3 +236,33 @@ test('Should pluck the categorical style-meta from fieldmeta', async () => {
],
});
});
test('isCategorical should return true when type is categorical', async () => {
const categoricalColorStyle = makeProperty({
type: COLOR_MAP_TYPE.CATEGORICAL,
colorCategory: 'palette_0',
});
expect(categoricalColorStyle.isOrdinal()).toEqual(false);
expect(categoricalColorStyle.isCategorical()).toEqual(true);
});
test('isOrdinal should return true when type is ordinal', async () => {
const ordinalColorStyle = makeProperty({
type: undefined,
color: 'Blues',
});
expect(ordinalColorStyle.isOrdinal()).toEqual(true);
expect(ordinalColorStyle.isCategorical()).toEqual(false);
});
test('Should read out ordinal type correctly', async () => {
const ordinalColorStyle2 = makeProperty({
type: COLOR_MAP_TYPE.ORDINAL,
colorCategory: 'palette_0',
});
expect(ordinalColorStyle2.isOrdinal()).toEqual(true);
expect(ordinalColorStyle2.isCategorical()).toEqual(false);
});

View file

@ -197,19 +197,6 @@ export class VectorLayer extends AbstractLayer {
return joinFields;
}
async getDateFields() {
return await this._source.getDateFields();
}
async getNumberFields() {
const numberFieldOptions = await this._source.getNumberFields();
return [...numberFieldOptions, ...this._getJoinFields()];
}
async getCategoricalFields() {
return await this._source.getCategoricalFields();
}
async getFields() {
const sourceFields = await this._source.getFields();
return [...sourceFields, ...this._getJoinFields()];