[Maps] typescript vector style phase 1 (#69994)

* [Maps] typescript vector style phase 1

* tslint

* unify Ordinal and Categorical field meta since they are mixed in real data

* field formatter type

* review feedback

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Nathan Reese 2020-07-16 13:31:43 -06:00 committed by GitHub
parent 5a755ddb01
commit 52597b203b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 228 additions and 178 deletions

View file

@ -64,6 +64,22 @@ export type DataMeta = Partial<VectorSourceRequestMeta> &
Partial<VectorStyleRequestMeta> &
Partial<ESSearchSourceResponseMeta>;
type NumericalStyleFieldData = {
avg: number;
max: number;
min: number;
std_deviation: number;
};
type CategoricalStyleFieldData = {
buckets: Array<{ key: string; doc_count: number }>;
};
export type StyleMetaData = {
// key is field name for field requiring style meta
[key: string]: NumericalStyleFieldData | CategoricalStyleFieldData;
};
export type DataRequestDescriptor = {
dataId: string;
dataMetaAtStart?: DataMeta | null;

View file

@ -15,4 +15,6 @@ export class InnerJoin implements IJoin {
getRightJoinSource(): IESTermSource;
toDescriptor(): JoinDescriptor;
getSourceMetaDataRequestId(): string;
}

View file

@ -11,4 +11,6 @@ export interface IJoin {
getRightJoinSource(): IESTermSource;
toDescriptor(): JoinDescriptor;
getSourceMetaDataRequestId(): string;
}

View file

@ -33,6 +33,7 @@ import {
VectorStyleDescriptor,
SizeDynamicOptions,
DynamicStylePropertyOptions,
StylePropertyOptions,
VectorLayerDescriptor,
} from '../../../../common/descriptor_types';
import { IStyle } from '../../styles/style';
@ -44,7 +45,7 @@ interface CountData {
isSyncClustered: boolean;
}
function getAggType(dynamicProperty: IDynamicStyleProperty): AGG_TYPE {
function getAggType(dynamicProperty: IDynamicStyleProperty<DynamicStylePropertyOptions>): AGG_TYPE {
return dynamicProperty.isOrdinal() ? AGG_TYPE.AVG : AGG_TYPE.TERMS;
}
@ -100,52 +101,57 @@ function getClusterStyleDescriptor(
},
},
};
documentStyle.getAllStyleProperties().forEach((styleProperty: IStyleProperty) => {
const styleName = styleProperty.getStyleName();
if (
[VECTOR_STYLES.LABEL_TEXT, VECTOR_STYLES.ICON_SIZE].includes(styleName) &&
(!styleProperty.isDynamic() || !styleProperty.isComplete())
) {
// Do not migrate static label and icon size properties to provide unique cluster styling out of the box
return;
}
documentStyle
.getAllStyleProperties()
.forEach((styleProperty: IStyleProperty<StylePropertyOptions>) => {
const styleName = styleProperty.getStyleName();
if (
[VECTOR_STYLES.LABEL_TEXT, VECTOR_STYLES.ICON_SIZE].includes(styleName) &&
(!styleProperty.isDynamic() || !styleProperty.isComplete())
) {
// Do not migrate static label and icon size properties to provide unique cluster styling out of the box
return;
}
if (styleName === VECTOR_STYLES.SYMBOLIZE_AS || styleName === VECTOR_STYLES.LABEL_BORDER_SIZE) {
// copy none static/dynamic styles to cluster style
clusterStyleDescriptor.properties[styleName] = {
if (
styleName === VECTOR_STYLES.SYMBOLIZE_AS ||
styleName === VECTOR_STYLES.LABEL_BORDER_SIZE
) {
// copy none static/dynamic styles to cluster style
clusterStyleDescriptor.properties[styleName] = {
// @ts-expect-error
options: { ...styleProperty.getOptions() },
};
} else if (styleProperty.isDynamic()) {
// copy dynamic styles to cluster style
const options = styleProperty.getOptions() as DynamicStylePropertyOptions;
const field =
options && options.field && options.field.name
? {
...options.field,
name: clusterSource.getAggKey(
getAggType(styleProperty as IDynamicStyleProperty<DynamicStylePropertyOptions>),
options.field.name
),
}
: undefined;
// @ts-expect-error
options: { ...styleProperty.getOptions() },
};
} else if (styleProperty.isDynamic()) {
// copy dynamic styles to cluster style
const options = styleProperty.getOptions() as DynamicStylePropertyOptions;
const field =
options && options.field && options.field.name
? {
...options.field,
name: clusterSource.getAggKey(
getAggType(styleProperty as IDynamicStyleProperty),
options.field.name
),
}
: undefined;
// @ts-expect-error
clusterStyleDescriptor.properties[styleName] = {
type: STYLE_TYPE.DYNAMIC,
options: {
...options,
field,
},
};
} else {
// copy static styles to cluster style
// @ts-expect-error
clusterStyleDescriptor.properties[styleName] = {
type: STYLE_TYPE.STATIC,
options: { ...styleProperty.getOptions() },
};
}
});
clusterStyleDescriptor.properties[styleName] = {
type: STYLE_TYPE.DYNAMIC,
options: {
...options,
field,
},
};
} else {
// copy static styles to cluster style
// @ts-expect-error
clusterStyleDescriptor.properties[styleName] = {
type: STYLE_TYPE.STATIC,
options: { ...styleProperty.getOptions() },
};
}
});
return clusterStyleDescriptor;
}

View file

@ -14,6 +14,7 @@ export interface IESAggSource extends IESSource {
getAggKey(aggType: AGG_TYPE, fieldName: string): string;
getAggLabel(aggType: AGG_TYPE, fieldName: string): string;
getMetricFields(): IESAggField[];
hasMatchingMetricField(fieldName: string): boolean;
}
export class AbstractESAggSource extends AbstractESSource implements IESAggSource {
@ -22,4 +23,5 @@ export class AbstractESAggSource extends AbstractESSource implements IESAggSourc
getAggKey(aggType: AGG_TYPE, fieldName: string): string;
getAggLabel(aggType: AGG_TYPE, fieldName: string): string;
getMetricFields(): IESAggField[];
hasMatchingMetricField(fieldName: string): boolean;
}

View file

@ -7,7 +7,10 @@
import { AbstractVectorSource } from '../vector_source';
import { IVectorSource } from '../vector_source';
import { IndexPattern, ISearchSource } from '../../../../../../../src/plugins/data/public';
import { VectorSourceRequestMeta } from '../../../../common/descriptor_types';
import {
DynamicStylePropertyOptions,
VectorSourceRequestMeta,
} from '../../../../common/descriptor_types';
import { VectorStyle } from '../../styles/vector/vector_style';
import { IDynamicStyleProperty } from '../../styles/vector/properties/dynamic_style_property';
@ -25,7 +28,7 @@ export interface IESSource extends IVectorSource {
loadStylePropsMeta(
layerName: string,
style: VectorStyle,
dynamicStyleProps: IDynamicStyleProperty[],
dynamicStyleProps: Array<IDynamicStyleProperty<DynamicStylePropertyOptions>>,
registerCancelCallback: (requestToken: symbol, callback: () => void) => void,
searchFilters: VectorSourceRequestMeta
): Promise<unknown>;
@ -45,7 +48,7 @@ export class AbstractESSource extends AbstractVectorSource implements IESSource
loadStylePropsMeta(
layerName: string,
style: VectorStyle,
dynamicStyleProps: IDynamicStyleProperty[],
dynamicStyleProps: Array<IDynamicStyleProperty<DynamicStylePropertyOptions>>,
registerCancelCallback: (requestToken: symbol, callback: () => void) => void,
searchFilters: VectorSourceRequestMeta
): Promise<unknown>;

View file

@ -9,18 +9,17 @@ import React from 'react';
import { EuiFormRow, EuiSwitch, EuiSwitchEvent } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FieldMetaPopover } from './field_meta_popover';
import { IDynamicStyleProperty } from '../../properties/dynamic_style_property';
import { FieldMetaOptions } from '../../../../../../common/descriptor_types';
type Props = {
styleProperty: IDynamicStyleProperty;
fieldMetaOptions: FieldMetaOptions;
onChange: (fieldMetaOptions: FieldMetaOptions) => void;
};
export function CategoricalFieldMetaPopover(props: Props) {
const onIsEnabledChange = (event: EuiSwitchEvent) => {
props.onChange({
...props.styleProperty.getFieldMetaOptions(),
...props.fieldMetaOptions,
isEnabled: event.target.checked,
});
};
@ -32,7 +31,7 @@ export function CategoricalFieldMetaPopover(props: Props) {
label={i18n.translate('xpack.maps.styles.fieldMetaOptions.isEnabled.categoricalLabel', {
defaultMessage: 'Get categories from indices',
})}
checked={props.styleProperty.getFieldMetaOptions().isEnabled}
checked={props.fieldMetaOptions.isEnabled}
onChange={onIsEnabledChange}
compressed
/>

View file

@ -11,7 +11,6 @@ import { EuiFormRow, EuiRange, EuiSwitch, EuiSwitchEvent } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { DEFAULT_SIGMA } from '../../vector_style_defaults';
import { FieldMetaPopover } from './field_meta_popover';
import { IDynamicStyleProperty } from '../../properties/dynamic_style_property';
import { FieldMetaOptions } from '../../../../../../common/descriptor_types';
import { VECTOR_STYLES } from '../../../../../../common/constants';
@ -38,21 +37,22 @@ function getIsEnableToggleLabel(styleName: string) {
}
type Props = {
styleProperty: IDynamicStyleProperty;
fieldMetaOptions: FieldMetaOptions;
styleName: VECTOR_STYLES;
onChange: (fieldMetaOptions: FieldMetaOptions) => void;
};
export function OrdinalFieldMetaPopover(props: Props) {
const onIsEnabledChange = (event: EuiSwitchEvent) => {
props.onChange({
...props.styleProperty.getFieldMetaOptions(),
...props.fieldMetaOptions,
isEnabled: event.target.checked,
});
};
const onSigmaChange = (event: ChangeEvent<HTMLInputElement> | MouseEvent<HTMLButtonElement>) => {
props.onChange({
...props.styleProperty.getFieldMetaOptions(),
...props.fieldMetaOptions,
sigma: parseInt(event.currentTarget.value, 10),
});
};
@ -62,8 +62,8 @@ export function OrdinalFieldMetaPopover(props: Props) {
<Fragment>
<EuiFormRow display="columnCompressedSwitch">
<EuiSwitch
label={getIsEnableToggleLabel(props.styleProperty.getStyleName())}
checked={props.styleProperty.getFieldMetaOptions().isEnabled}
label={getIsEnableToggleLabel(props.styleName)}
checked={props.fieldMetaOptions.isEnabled}
onChange={onIsEnabledChange}
compressed
/>
@ -79,9 +79,9 @@ export function OrdinalFieldMetaPopover(props: Props) {
min={1}
max={5}
step={0.25}
value={_.get(props.styleProperty.getFieldMetaOptions(), 'sigma', DEFAULT_SIGMA)}
value={_.get(props.fieldMetaOptions, 'sigma', DEFAULT_SIGMA)}
onChange={onSigmaChange}
disabled={!props.styleProperty.getFieldMetaOptions().isEnabled}
disabled={!props.fieldMetaOptions.isEnabled}
showTicks
tickInterval={1}
compressed

View file

@ -29,6 +29,7 @@ import { shallow } from 'enzyme';
import { FIELD_ORIGIN } from '../../../../../../common/constants';
import { AbstractField } from '../../../../fields/field';
import { IDynamicStyleProperty } from '../../properties/dynamic_style_property';
import { IconDynamicOptions } from '../../../../../../common/descriptor_types';
import { IconMapSelect } from './icon_map_select';
class MockField extends AbstractField {}
@ -46,7 +47,9 @@ class MockDynamicStyleProperty {
const defaultProps = {
iconPaletteId: 'filledShapes',
onChange: () => {},
styleProperty: (new MockDynamicStyleProperty() as unknown) as IDynamicStyleProperty,
styleProperty: (new MockDynamicStyleProperty() as unknown) as IDynamicStyleProperty<
IconDynamicOptions
>,
isCustomOnly: false,
};

View file

@ -12,7 +12,7 @@ import { i18n } from '@kbn/i18n';
import { IconStops } from './icon_stops';
// @ts-expect-error
import { getIconPaletteOptions, PREFERRED_ICONS } from '../../symbol_utils';
import { IconStop } from '../../../../../../common/descriptor_types';
import { IconDynamicOptions, IconStop } from '../../../../../../common/descriptor_types';
import { IDynamicStyleProperty } from '../../properties/dynamic_style_property';
const CUSTOM_MAP_ID = 'CUSTOM_MAP_ID';
@ -32,7 +32,7 @@ interface Props {
customIconStops?: IconStop[];
iconPaletteId: string | null;
onChange: ({ customIconStops, iconPaletteId, useCustomIconMap }: StyleOptionChanges) => void;
styleProperty: IDynamicStyleProperty;
styleProperty: IDynamicStyleProperty<IconDynamicOptions>;
useCustomIconMap?: boolean;
isCustomOnly: boolean;
}

View file

@ -187,7 +187,7 @@ test('Should pluck the categorical style-meta from fieldmeta', async () => {
colorCategory: 'palette_0',
});
const meta = colorStyle.pluckCategoricalStyleMetaFromFieldMetaData({
const meta = colorStyle._pluckCategoricalStyleMetaFromFieldMetaData({
foobar: {
buckets: [
{

View file

@ -1,37 +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;
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable @typescript-eslint/consistent-type-definitions */
import { IStyleProperty } from './style_property';
import { FIELD_ORIGIN } from '../../../../../common/constants';
import {
CategoryFieldMeta,
DynamicStylePropertyOptions,
FieldMetaOptions,
RangeFieldMeta,
} from '../../../../../common/descriptor_types';
import { IField } from '../../../fields/field';
export interface IDynamicStyleProperty extends IStyleProperty {
getOptions(): DynamicStylePropertyOptions;
getFieldMetaOptions(): FieldMetaOptions;
getField(): IField | undefined;
getFieldName(): string;
getFieldOrigin(): FIELD_ORIGIN | undefined;
getRangeFieldMeta(): RangeFieldMeta;
getCategoryFieldMeta(): CategoryFieldMeta;
getNumberOfCategories(): number;
isFieldMetaEnabled(): boolean;
isOrdinal(): boolean;
supportsFieldMeta(): boolean;
getFieldMetaRequest(): Promise<unknown>;
supportsMbFeatureState(): boolean;
pluckOrdinalStyleMetaFromFeatures(features: unknown[]): RangeFieldMeta;
pluckCategoricalStyleMetaFromFeatures(features: unknown[]): CategoryFieldMeta;
pluckOrdinalStyleMetaFromFieldMetaData(fieldMetaData: unknown): RangeFieldMeta;
pluckCategoricalStyleMetaFromFieldMetaData(fieldMetaData: unknown): CategoryFieldMeta;
getValueSuggestions(query: string): string[];
}

View file

@ -3,42 +3,87 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable @typescript-eslint/consistent-type-definitions */
import _ from 'lodash';
import { AbstractStyleProperty } from './style_property';
import React from 'react';
import { Feature } from 'geojson';
import { AbstractStyleProperty, IStyleProperty } from './style_property';
import { DEFAULT_SIGMA } from '../vector_style_defaults';
import {
STYLE_TYPE,
SOURCE_META_DATA_REQUEST_ID,
FIELD_ORIGIN,
VECTOR_STYLES,
} from '../../../../../common/constants';
import React from 'react';
import { OrdinalFieldMetaPopover } from '../components/field_meta/ordinal_field_meta_popover';
import { CategoricalFieldMetaPopover } from '../components/field_meta/categorical_field_meta_popover';
import {
CategoryFieldMeta,
FieldMetaOptions,
StyleMetaData,
RangeFieldMeta,
} from '../../../../../common/descriptor_types';
import { IField } from '../../../fields/field';
import { IVectorLayer } from '../../../layers/vector_layer/vector_layer';
import { IJoin } from '../../../joins/join';
export class DynamicStyleProperty extends AbstractStyleProperty {
export interface IDynamicStyleProperty<T> extends IStyleProperty<T> {
getFieldMetaOptions(): FieldMetaOptions;
getField(): IField | null;
getFieldName(): string;
getFieldOrigin(): FIELD_ORIGIN | null;
getRangeFieldMeta(): RangeFieldMeta | null;
getCategoryFieldMeta(): CategoryFieldMeta | null;
getNumberOfCategories(): number;
isFieldMetaEnabled(): boolean;
isOrdinal(): boolean;
supportsFieldMeta(): boolean;
getFieldMetaRequest(): Promise<unknown>;
supportsMbFeatureState(): boolean;
pluckOrdinalStyleMetaFromFeatures(features: Feature[]): RangeFieldMeta | null;
pluckCategoricalStyleMetaFromFeatures(features: Feature[]): CategoryFieldMeta | null;
getValueSuggestions(query: string): Promise<string[]>;
}
type fieldFormatter = (value: string | undefined) => string;
export class DynamicStyleProperty<T> extends AbstractStyleProperty<T>
implements IDynamicStyleProperty<T> {
static type = STYLE_TYPE.DYNAMIC;
constructor(options, styleName, field, vectorLayer, getFieldFormatter) {
protected readonly _field: IField | null;
protected readonly _layer: IVectorLayer;
protected readonly _getFieldFormatter: (fieldName: string) => null | fieldFormatter;
constructor(
options: T,
styleName: VECTOR_STYLES,
field: IField | null,
vectorLayer: IVectorLayer,
getFieldFormatter: (fieldName: string) => null | fieldFormatter
) {
super(options, styleName);
this._field = field;
this._layer = vectorLayer;
this._getFieldFormatter = getFieldFormatter;
}
getValueSuggestions = (query) => {
const field = this.getField();
const fieldSource = this._getFieldSource();
return fieldSource && field ? fieldSource.getValueSuggestions(field, query) : [];
// ignore TS error about "Type '(query: string) => Promise<string[]> | never[]' is not assignable to type '(query: string) => Promise<string[]>'."
// @ts-expect-error
getValueSuggestions = (query: string) => {
return this._field === null
? []
: this._field.getSource().getValueSuggestions(this._field, query);
};
_getStyleMetaDataRequestId(fieldName) {
_getStyleMetaDataRequestId(fieldName: string) {
if (this.getFieldOrigin() === FIELD_ORIGIN.SOURCE) {
return SOURCE_META_DATA_REQUEST_ID;
}
const join = this._layer.getValidJoins().find((join) => {
return join.getRightJoinSource().hasMatchingMetricField(fieldName);
const join = this._layer.getValidJoins().find((validJoin: IJoin) => {
return validJoin.getRightJoinSource().hasMatchingMetricField(fieldName);
});
return join ? join.getSourceMetaDataRequestId() : null;
}
@ -63,8 +108,8 @@ export class DynamicStyleProperty extends AbstractStyleProperty {
return rangeFieldMetaFromLocalFeatures;
}
const data = styleMetaDataRequest.getData();
const rangeFieldMeta = this.pluckOrdinalStyleMetaFromFieldMetaData(data);
const data = styleMetaDataRequest.getData() as StyleMetaData;
const rangeFieldMeta = this._pluckOrdinalStyleMetaFromFieldMetaData(data);
return rangeFieldMeta ? rangeFieldMeta : rangeFieldMetaFromLocalFeatures;
}
@ -88,8 +133,8 @@ export class DynamicStyleProperty extends AbstractStyleProperty {
return categoryFieldMetaFromLocalFeatures;
}
const data = styleMetaDataRequest.getData();
const rangeFieldMeta = this.pluckCategoricalStyleMetaFromFieldMetaData(data);
const data = styleMetaDataRequest.getData() as StyleMetaData;
const rangeFieldMeta = this._pluckCategoricalStyleMetaFromFieldMetaData(data);
return rangeFieldMeta ? rangeFieldMeta : categoryFieldMetaFromLocalFeatures;
}
@ -97,10 +142,6 @@ export class DynamicStyleProperty extends AbstractStyleProperty {
return this._field;
}
_getFieldSource() {
return this._field ? this._field.getSource() : null;
}
getFieldName() {
return this._field ? this._field.getName() : '';
}
@ -126,7 +167,7 @@ export class DynamicStyleProperty extends AbstractStyleProperty {
}
getFieldOrigin() {
return this._field.getOrigin();
return this._field ? this._field.getOrigin() : null;
}
isFieldMetaEnabled() {
@ -135,10 +176,14 @@ export class DynamicStyleProperty extends AbstractStyleProperty {
}
supportsFieldMeta() {
return this.isComplete() && this._field.supportsFieldMeta();
return this.isComplete() && !!this._field && this._field.supportsFieldMeta();
}
async getFieldMetaRequest() {
if (!this._field) {
return null;
}
if (this.isOrdinal()) {
return this._field.getOrdinalFieldMetaRequest();
} else if (this.isCategorical()) {
@ -154,20 +199,20 @@ export class DynamicStyleProperty extends AbstractStyleProperty {
}
getFieldMetaOptions() {
return _.get(this.getOptions(), 'fieldMetaOptions', {});
return _.get(this.getOptions(), 'fieldMetaOptions', { isEnabled: true });
}
pluckOrdinalStyleMetaFromFeatures(features) {
pluckOrdinalStyleMetaFromFeatures(features: Feature[]) {
if (!this.isOrdinal()) {
return null;
}
const name = this.getField().getName();
const name = this.getFieldName();
let min = Infinity;
let max = -Infinity;
for (let i = 0; i < features.length; i++) {
const feature = features[i];
const newValue = parseFloat(feature.properties[name]);
const newValue = parseFloat(feature.properties ? feature.properties[name] : null);
if (!isNaN(newValue)) {
min = Math.min(min, newValue);
max = Math.max(max, newValue);
@ -176,25 +221,24 @@ export class DynamicStyleProperty extends AbstractStyleProperty {
return min === Infinity || max === -Infinity
? null
: {
min: min,
max: max,
: ({
min,
max,
delta: max - min,
};
} as RangeFieldMeta);
}
pluckCategoricalStyleMetaFromFeatures(features) {
pluckCategoricalStyleMetaFromFeatures(features: Feature[]) {
const size = this.getNumberOfCategories();
if (!this.isCategorical() || size <= 0) {
return null;
}
const fieldName = this.getField().getName();
const counts = new Map();
for (let i = 0; i < features.length; i++) {
const feature = features[i];
const term = feature.properties[fieldName];
//properties object may be sparse, so need to check if the field is effectively present
const term = feature.properties ? feature.properties[this.getFieldName()] : undefined;
// properties object may be sparse, so need to check if the field is effectively present
if (typeof term !== undefined) {
if (counts.has(term)) {
counts.set(term, counts.get(term) + 1);
@ -215,16 +259,16 @@ export class DynamicStyleProperty extends AbstractStyleProperty {
const truncated = ordered.slice(0, size);
return {
categories: truncated,
};
} as CategoryFieldMeta;
}
pluckOrdinalStyleMetaFromFieldMetaData(fieldMetaData) {
if (!this.isOrdinal()) {
_pluckOrdinalStyleMetaFromFieldMetaData(styleMetaData: StyleMetaData) {
if (!this.isOrdinal() || !this._field) {
return null;
}
const stats = fieldMetaData[this._field.getRootName()];
if (!stats) {
const stats = styleMetaData[this._field.getRootName()];
if (!stats || !('avg' in stats)) {
return null;
}
@ -242,55 +286,56 @@ export class DynamicStyleProperty extends AbstractStyleProperty {
};
}
pluckCategoricalStyleMetaFromFieldMetaData(fieldMetaData) {
if (!this.isCategorical()) {
_pluckCategoricalStyleMetaFromFieldMetaData(styleMetaData: StyleMetaData) {
if (!this.isCategorical() || !this._field) {
return null;
}
const rootFieldName = this._field.getRootName();
if (!fieldMetaData[rootFieldName] || !fieldMetaData[rootFieldName].buckets) {
const fieldMeta = styleMetaData[this._field.getRootName()];
if (!fieldMeta || !('buckets' in fieldMeta)) {
return null;
}
const ordered = fieldMetaData[rootFieldName].buckets.map((bucket) => {
return {
key: bucket.key,
count: bucket.doc_count,
};
});
return {
categories: ordered,
categories: fieldMeta.buckets.map((bucket) => {
return {
key: bucket.key,
count: bucket.doc_count,
};
}),
};
}
formatField(value) {
formatField(value: string | undefined): string {
if (this.getField()) {
const fieldName = this.getField().getName();
const fieldName = this.getFieldName();
const fieldFormatter = this._getFieldFormatter(fieldName);
return fieldFormatter ? fieldFormatter(value) : value;
return fieldFormatter ? fieldFormatter(value) : super.formatField(value);
} else {
return value;
return super.formatField(value);
}
}
getNumericalMbFeatureStateValue(value) {
const valueAsFloat = parseFloat(value);
return isNaN(valueAsFloat) ? null : valueAsFloat;
}
renderLegendDetailRow() {
return null;
}
renderFieldMetaPopover(onFieldMetaOptionsChange) {
renderFieldMetaPopover(onFieldMetaOptionsChange: (fieldMetaOptions: FieldMetaOptions) => void) {
if (!this.supportsFieldMeta()) {
return null;
}
return this.isCategorical() ? (
<CategoricalFieldMetaPopover styleProperty={this} onChange={onFieldMetaOptionsChange} />
<CategoricalFieldMetaPopover
fieldMetaOptions={this.getFieldMetaOptions()}
onChange={onFieldMetaOptionsChange}
/>
) : (
<OrdinalFieldMetaPopover styleProperty={this} onChange={onFieldMetaOptionsChange} />
<OrdinalFieldMetaPopover
fieldMetaOptions={this.getFieldMetaOptions()}
styleName={this.getStyleName()}
onChange={onFieldMetaOptionsChange}
/>
);
}
}

View file

@ -8,7 +8,7 @@
import { ReactElement } from 'react';
// @ts-ignore
import { getVectorStyleLabel } from '../components/get_vector_style_label';
import { FieldMetaOptions, StylePropertyOptions } from '../../../../../common/descriptor_types';
import { FieldMetaOptions } from '../../../../../common/descriptor_types';
import { VECTOR_STYLES } from '../../../../../common/constants';
type LegendProps = {
@ -17,12 +17,12 @@ type LegendProps = {
symbolId?: string;
};
export interface IStyleProperty {
export interface IStyleProperty<T> {
isDynamic(): boolean;
isComplete(): boolean;
formatField(value: string | undefined): string;
getStyleName(): VECTOR_STYLES;
getOptions(): StylePropertyOptions;
getOptions(): T;
renderLegendDetailRow(legendProps: LegendProps): ReactElement<any> | null;
renderFieldMetaPopover(
onFieldMetaOptionsChange: (fieldMetaOptions: FieldMetaOptions) => void
@ -30,11 +30,11 @@ export interface IStyleProperty {
getDisplayStyleName(): string;
}
export class AbstractStyleProperty implements IStyleProperty {
private readonly _options: StylePropertyOptions;
private readonly _styleName: VECTOR_STYLES;
export class AbstractStyleProperty<T> implements IStyleProperty<T> {
protected readonly _options: T;
protected readonly _styleName: VECTOR_STYLES;
constructor(options: StylePropertyOptions, styleName: VECTOR_STYLES) {
constructor(options: T, styleName: VECTOR_STYLES) {
this._options = options;
this._styleName = styleName;
}
@ -62,15 +62,17 @@ export class AbstractStyleProperty implements IStyleProperty {
return this._styleName;
}
getOptions(): StylePropertyOptions {
return this._options || {};
getOptions(): T {
return this._options;
}
renderLegendDetailRow() {
return null;
}
renderFieldMetaPopover() {
renderFieldMetaPopover(
onFieldMetaOptionsChange: (fieldMetaOptions: FieldMetaOptions) => void
): ReactElement<any> | null {
return null;
}

View file

@ -9,14 +9,16 @@ import { IVectorLayer } from '../../layers/vector_layer/vector_layer';
import { IVectorSource } from '../../sources/vector_source';
import { AbstractStyle, IStyle } from '../style';
import {
DynamicStylePropertyOptions,
StylePropertyOptions,
VectorStyleDescriptor,
VectorStylePropertiesDescriptor,
} from '../../../../common/descriptor_types';
import { StyleMeta } from './style_meta';
export interface IVectorStyle extends IStyle {
getAllStyleProperties(): IStyleProperty[];
getDynamicPropertiesArray(): IDynamicStyleProperty[];
getAllStyleProperties(): Array<IStyleProperty<StylePropertyOptions>>;
getDynamicPropertiesArray(): Array<IDynamicStyleProperty<DynamicStylePropertyOptions>>;
getSourceFieldNames(): string[];
getStyleMeta(): StyleMeta;
}
@ -26,7 +28,7 @@ export class VectorStyle extends AbstractStyle implements IVectorStyle {
static createDefaultStyleProperties(mapColors: string[]): VectorStylePropertiesDescriptor;
constructor(descriptor: VectorStyleDescriptor, source: IVectorSource, layer: IVectorLayer);
getSourceFieldNames(): string[];
getAllStyleProperties(): IStyleProperty[];
getDynamicPropertiesArray(): IDynamicStyleProperty[];
getAllStyleProperties(): Array<IStyleProperty<StylePropertyOptions>>;
getDynamicPropertiesArray(): Array<IDynamicStyleProperty<DynamicStylePropertyOptions>>;
getStyleMeta(): StyleMeta;
}

View file

@ -48,6 +48,11 @@ const POINTS = [GEO_JSON_TYPE.POINT, GEO_JSON_TYPE.MULTI_POINT];
const LINES = [GEO_JSON_TYPE.LINE_STRING, GEO_JSON_TYPE.MULTI_LINE_STRING];
const POLYGONS = [GEO_JSON_TYPE.POLYGON, GEO_JSON_TYPE.MULTI_POLYGON];
function getNumericalMbFeatureStateValue(value) {
const valueAsFloat = parseFloat(value);
return isNaN(valueAsFloat) ? null : valueAsFloat;
}
export class VectorStyle extends AbstractStyle {
static type = LAYER_STYLE_TYPE.VECTOR;
@ -518,14 +523,14 @@ export class VectorStyle extends AbstractStyle {
const computedName = getComputedFieldName(dynamicStyleProp.getStyleName(), name);
const rawValue = feature.properties[name];
if (dynamicStyleProp.supportsMbFeatureState()) {
tmpFeatureState[name] = dynamicStyleProp.getNumericalMbFeatureStateValue(rawValue); //the same value will be potentially overridden multiple times, if the name remains identical
tmpFeatureState[name] = getNumericalMbFeatureStateValue(rawValue); //the same value will be potentially overridden multiple times, if the name remains identical
} else {
//in practice, a new system property will only be created for:
// - label text: this requires the value to be formatted first.
// - icon orientation: this is a lay-out property which do not support feature-state (but we're still coercing to a number)
const formattedValue = dynamicStyleProp.isOrdinal()
? dynamicStyleProp.getNumericalMbFeatureStateValue(rawValue)
? getNumericalMbFeatureStateValue(rawValue)
: dynamicStyleProp.formatField(rawValue);
feature.properties[computedName] = formattedValue;