[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:
parent
5a755ddb01
commit
52597b203b
16 changed files with 228 additions and 178 deletions
|
@ -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;
|
||||
|
|
|
@ -15,4 +15,6 @@ export class InnerJoin implements IJoin {
|
|||
getRightJoinSource(): IESTermSource;
|
||||
|
||||
toDescriptor(): JoinDescriptor;
|
||||
|
||||
getSourceMetaDataRequestId(): string;
|
||||
}
|
||||
|
|
|
@ -11,4 +11,6 @@ export interface IJoin {
|
|||
getRightJoinSource(): IESTermSource;
|
||||
|
||||
toDescriptor(): JoinDescriptor;
|
||||
|
||||
getSourceMetaDataRequestId(): string;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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
|
||||
/>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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: [
|
||||
{
|
||||
|
|
|
@ -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[];
|
||||
}
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue