[Maps] Update style when metrics change (#83586)

This commit is contained in:
Thomas Neirynck 2020-12-04 17:24:21 -05:00 committed by GitHub
parent 918dbb17de
commit d41fbf948e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 491 additions and 172 deletions

View file

@ -254,3 +254,8 @@ export type DynamicStylePropertyOptions =
| LabelDynamicOptions
| OrientationDynamicOptions
| SizeDynamicOptions;
export type DynamicStyleProperties = {
type: STYLE_TYPE.DYNAMIC;
options: DynamicStylePropertyOptions;
};

View file

@ -45,6 +45,8 @@ import { IVectorLayer } from '../classes/layers/vector_layer/vector_layer';
import { LAYER_STYLE_TYPE, LAYER_TYPE } from '../../common/constants';
import { IVectorStyle } from '../classes/styles/vector/vector_style';
import { notifyLicensedFeatureUsage } from '../licensed_features';
import { IESAggField } from '../classes/fields/agg';
import { IField } from '../classes/fields/field';
export function trackCurrentLayerState(layerId: string) {
return {
@ -274,6 +276,24 @@ export function updateLayerOrder(newLayerOrder: number[]) {
};
}
function updateMetricsProp(layerId: string, value: unknown) {
return async (
dispatch: ThunkDispatch<MapStoreState, void, AnyAction>,
getState: () => MapStoreState
) => {
const layer = getLayerById(layerId, getState());
const previousFields = await (layer as IVectorLayer).getFields();
await dispatch({
type: UPDATE_SOURCE_PROP,
layerId,
propName: 'metrics',
value,
});
await dispatch(updateStyleProperties(layerId, previousFields as IESAggField[]));
dispatch(syncDataForLayerId(layerId));
};
}
export function updateSourceProp(
layerId: string,
propName: string,
@ -281,6 +301,12 @@ export function updateSourceProp(
newLayerType?: LAYER_TYPE
) {
return async (dispatch: ThunkDispatch<MapStoreState, void, AnyAction>) => {
if (propName === 'metrics') {
if (newLayerType) {
throw new Error('May not change layer-type when modifying metrics source-property');
}
return await dispatch(updateMetricsProp(layerId, value));
}
dispatch({
type: UPDATE_SOURCE_PROP,
layerId,
@ -290,7 +316,6 @@ export function updateSourceProp(
if (newLayerType) {
dispatch(updateLayerType(layerId, newLayerType));
}
await dispatch(clearMissingStyleProperties(layerId));
dispatch(syncDataForLayerId(layerId));
};
}
@ -422,7 +447,7 @@ function removeLayerFromLayerList(layerId: string) {
};
}
export function clearMissingStyleProperties(layerId: string) {
function updateStyleProperties(layerId: string, previousFields: IField[]) {
return async (
dispatch: ThunkDispatch<MapStoreState, void, AnyAction>,
getState: () => MapStoreState
@ -441,8 +466,9 @@ export function clearMissingStyleProperties(layerId: string) {
const {
hasChanges,
nextStyleDescriptor,
} = await (style as IVectorStyle).getDescriptorWithMissingStylePropsRemoved(
} = await (style as IVectorStyle).getDescriptorWithUpdatedStyleProps(
nextFields,
previousFields,
getMapColors(getState())
);
if (hasChanges && nextStyleDescriptor) {
@ -485,13 +511,13 @@ export function updateLayerStyleForSelectedLayer(styleDescriptor: StyleDescripto
export function setJoinsForLayer(layer: ILayer, joins: JoinDescriptor[]) {
return async (dispatch: ThunkDispatch<MapStoreState, void, AnyAction>) => {
const previousFields = await (layer as IVectorLayer).getFields();
await dispatch({
type: SET_JOINS,
layer,
joins,
});
await dispatch(clearMissingStyleProperties(layer.getId()));
await dispatch(updateStyleProperties(layer.getId(), previousFields));
dispatch(syncDataForLayerId(layer.getId()));
};
}

View file

@ -97,4 +97,8 @@ export class CountAggField implements IESAggField {
canReadFromGeoJson(): boolean {
return this._canReadFromGeoJson;
}
isEqual(field: IESAggField) {
return field.getName() === this.getName();
}
}

View file

@ -83,4 +83,8 @@ export class TopTermPercentageField implements IESAggField {
canReadFromGeoJson(): boolean {
return this._canReadFromGeoJson;
}
isEqual(field: IESAggField) {
return field.getName() === this.getName();
}
}

View file

@ -32,6 +32,7 @@ export interface IField {
supportsFieldMeta(): boolean;
canReadFromGeoJson(): boolean;
isEqual(field: IField): boolean;
}
export class AbstractField implements IField {
@ -99,4 +100,8 @@ export class AbstractField implements IField {
canReadFromGeoJson(): boolean {
return true;
}
isEqual(field: IField) {
return this._origin === field.getOrigin() && this._fieldName === field.getName();
}
}

View file

@ -0,0 +1,117 @@
/*
* 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 { FIELD_ORIGIN, VECTOR_STYLES } from '../../../../common/constants';
import { createStyleFieldsHelper, StyleFieldsHelper } from './style_fields_helper';
import { AbstractField, IField } from '../../fields/field';
class MockField extends AbstractField {
private readonly _dataType: string;
private readonly _supportsAutoDomain: boolean;
constructor({ dataType, supportsAutoDomain }: { dataType: string; supportsAutoDomain: boolean }) {
super({ fieldName: 'foobar_' + dataType, origin: FIELD_ORIGIN.SOURCE });
this._dataType = dataType;
this._supportsAutoDomain = supportsAutoDomain;
}
async getDataType() {
return this._dataType;
}
supportsAutoDomain(): boolean {
return this._supportsAutoDomain;
}
}
describe('StyleFieldHelper', () => {
describe('isFieldDataTypeCompatibleWithStyleType', () => {
async function createHelper(
supportsAutoDomain: boolean
): Promise<{
styleFieldHelper: StyleFieldsHelper;
stringField: IField;
numberField: IField;
dateField: IField;
}> {
const stringField = new MockField({
dataType: 'string',
supportsAutoDomain,
});
const numberField = new MockField({
dataType: 'number',
supportsAutoDomain,
});
const dateField = new MockField({
dataType: 'date',
supportsAutoDomain,
});
return {
styleFieldHelper: await createStyleFieldsHelper([stringField, numberField, dateField]),
stringField,
numberField,
dateField,
};
}
test('Should validate colors for all data types', async () => {
const { styleFieldHelper, stringField, numberField, dateField } = await createHelper(true);
[
VECTOR_STYLES.FILL_COLOR,
VECTOR_STYLES.LINE_COLOR,
VECTOR_STYLES.LABEL_COLOR,
VECTOR_STYLES.LABEL_BORDER_COLOR,
].forEach((styleType) => {
expect(styleFieldHelper.hasFieldForStyle(stringField, styleType)).toEqual(true);
expect(styleFieldHelper.hasFieldForStyle(numberField, styleType)).toEqual(true);
expect(styleFieldHelper.hasFieldForStyle(dateField, styleType)).toEqual(true);
});
});
test('Should validate sizes for all number types', async () => {
const { styleFieldHelper, stringField, numberField, dateField } = await createHelper(true);
[VECTOR_STYLES.LINE_WIDTH, VECTOR_STYLES.LABEL_SIZE, VECTOR_STYLES.ICON_SIZE].forEach(
(styleType) => {
expect(styleFieldHelper.hasFieldForStyle(stringField, styleType)).toEqual(false);
expect(styleFieldHelper.hasFieldForStyle(numberField, styleType)).toEqual(true);
expect(styleFieldHelper.hasFieldForStyle(dateField, styleType)).toEqual(true);
}
);
});
test('Should not validate sizes if autodomain is not enabled', async () => {
const { styleFieldHelper, stringField, numberField, dateField } = await createHelper(false);
[VECTOR_STYLES.LINE_WIDTH, VECTOR_STYLES.LABEL_SIZE, VECTOR_STYLES.ICON_SIZE].forEach(
(styleType) => {
expect(styleFieldHelper.hasFieldForStyle(stringField, styleType)).toEqual(false);
expect(styleFieldHelper.hasFieldForStyle(numberField, styleType)).toEqual(false);
expect(styleFieldHelper.hasFieldForStyle(dateField, styleType)).toEqual(false);
}
);
});
test('Should validate orientation only number types', async () => {
const { styleFieldHelper, stringField, numberField, dateField } = await createHelper(true);
[VECTOR_STYLES.ICON_ORIENTATION].forEach((styleType) => {
expect(styleFieldHelper.hasFieldForStyle(stringField, styleType)).toEqual(false);
expect(styleFieldHelper.hasFieldForStyle(numberField, styleType)).toEqual(true);
expect(styleFieldHelper.hasFieldForStyle(dateField, styleType)).toEqual(false);
});
});
test('Should not validate label_border_size', async () => {
const { styleFieldHelper, stringField, numberField, dateField } = await createHelper(true);
[VECTOR_STYLES.LABEL_BORDER_SIZE].forEach((styleType) => {
expect(styleFieldHelper.hasFieldForStyle(stringField, styleType)).toEqual(false);
expect(styleFieldHelper.hasFieldForStyle(numberField, styleType)).toEqual(false);
expect(styleFieldHelper.hasFieldForStyle(dateField, styleType)).toEqual(false);
});
});
});
});

View file

@ -69,6 +69,11 @@ export class StyleFieldsHelper {
this._ordinalFields = ordinalFields;
}
hasFieldForStyle(field: IField, styleName: VECTOR_STYLES): boolean {
const fieldList = this.getFieldsForStyle(styleName);
return fieldList.some((styleField) => field.getName() === styleField.name);
}
getFieldsForStyle(styleName: VECTOR_STYLES): StyleField[] {
switch (styleName) {
case VECTOR_STYLES.ICON_ORIENTATION:

View file

@ -31,8 +31,8 @@ class MockSource {
}
}
describe('getDescriptorWithMissingStylePropsRemoved', () => {
const fieldName = 'doIStillExist';
describe('getDescriptorWithUpdatedStyleProps', () => {
const previousFieldName = 'doIStillExist';
const mapColors = [];
const properties = {
fillColor: {
@ -43,7 +43,7 @@ describe('getDescriptorWithMissingStylePropsRemoved', () => {
type: STYLE_TYPE.DYNAMIC,
options: {
field: {
name: fieldName,
name: previousFieldName,
origin: FIELD_ORIGIN.SOURCE,
},
},
@ -53,89 +53,123 @@ describe('getDescriptorWithMissingStylePropsRemoved', () => {
options: {
minSize: 1,
maxSize: 10,
field: { name: fieldName, origin: FIELD_ORIGIN.SOURCE },
field: { name: previousFieldName, origin: FIELD_ORIGIN.SOURCE },
},
},
};
const previousFields = [new MockField({ fieldName: previousFieldName })];
beforeEach(() => {
require('../../../kibana_services').getUiSettings = () => ({
get: jest.fn(),
});
});
it('Should return no changes when next ordinal fields contain existing style property fields', async () => {
const vectorStyle = new VectorStyle({ properties }, new MockSource());
describe('When there is no mismatch in configuration', () => {
it('Should return no changes when next ordinal fields contain existing style property fields', async () => {
const vectorStyle = new VectorStyle({ properties }, new MockSource());
const nextFields = [new MockField({ fieldName, dataType: 'number' })];
const { hasChanges } = await vectorStyle.getDescriptorWithMissingStylePropsRemoved(
nextFields,
mapColors
);
expect(hasChanges).toBe(false);
});
it('Should clear missing fields when next ordinal fields do not contain existing style property fields', async () => {
const vectorStyle = new VectorStyle({ properties }, new MockSource());
const nextFields = [new MockField({ fieldName: 'someOtherField', dataType: 'number' })];
const {
hasChanges,
nextStyleDescriptor,
} = await vectorStyle.getDescriptorWithMissingStylePropsRemoved(nextFields, mapColors);
expect(hasChanges).toBe(true);
expect(nextStyleDescriptor.properties[VECTOR_STYLES.LINE_COLOR]).toEqual({
options: {},
type: 'DYNAMIC',
});
expect(nextStyleDescriptor.properties[VECTOR_STYLES.ICON_SIZE]).toEqual({
options: {
minSize: 1,
maxSize: 10,
},
type: 'DYNAMIC',
const nextFields = [new MockField({ fieldName: previousFieldName, dataType: 'number' })];
const { hasChanges } = await vectorStyle.getDescriptorWithUpdatedStyleProps(
nextFields,
previousFields,
mapColors
);
expect(hasChanges).toBe(false);
});
});
it('Should convert dynamic styles to static styles when there are no next fields', async () => {
const vectorStyle = new VectorStyle({ properties }, new MockSource());
describe('When styles should revert to static styling', () => {
it('Should convert dynamic styles to static styles when there are no next fields', async () => {
const vectorStyle = new VectorStyle({ properties }, new MockSource());
const nextFields = [];
const {
hasChanges,
nextStyleDescriptor,
} = await vectorStyle.getDescriptorWithMissingStylePropsRemoved(nextFields, mapColors);
expect(hasChanges).toBe(true);
expect(nextStyleDescriptor.properties[VECTOR_STYLES.LINE_COLOR]).toEqual({
options: {
color: '#41937c',
},
type: 'STATIC',
const nextFields = [];
const {
hasChanges,
nextStyleDescriptor,
} = await vectorStyle.getDescriptorWithUpdatedStyleProps(
nextFields,
previousFields,
mapColors
);
expect(hasChanges).toBe(true);
expect(nextStyleDescriptor.properties[VECTOR_STYLES.LINE_COLOR]).toEqual({
options: {
color: '#41937c',
},
type: 'STATIC',
});
expect(nextStyleDescriptor.properties[VECTOR_STYLES.ICON_SIZE]).toEqual({
options: {
size: 6,
},
type: 'STATIC',
});
});
expect(nextStyleDescriptor.properties[VECTOR_STYLES.ICON_SIZE]).toEqual({
options: {
size: 6,
},
type: 'STATIC',
it('Should convert dynamic ICON_SIZE static style when there are no next ordinal fields', async () => {
const vectorStyle = new VectorStyle({ properties }, new MockSource());
const nextFields = [
new MockField({
fieldName: previousFieldName,
dataType: 'number',
supportsAutoDomain: false,
}),
];
const {
hasChanges,
nextStyleDescriptor,
} = await vectorStyle.getDescriptorWithUpdatedStyleProps(
nextFields,
previousFields,
mapColors
);
expect(hasChanges).toBe(true);
expect(nextStyleDescriptor.properties[VECTOR_STYLES.ICON_SIZE]).toEqual({
options: {
size: 6,
},
type: 'STATIC',
});
});
});
it('Should convert dynamic ICON_SIZE static style when there are no next ordinal fields', async () => {
const vectorStyle = new VectorStyle({ properties }, new MockSource());
describe('When styles should not be cleared', () => {
it('Should update field in styles when the fields and style combination remains compatible', async () => {
const vectorStyle = new VectorStyle({ properties }, new MockSource());
const nextFields = [
new MockField({ fieldName, dataType: 'number', supportsAutoDomain: false }),
];
const {
hasChanges,
nextStyleDescriptor,
} = await vectorStyle.getDescriptorWithMissingStylePropsRemoved(nextFields, mapColors);
expect(hasChanges).toBe(true);
expect(nextStyleDescriptor.properties[VECTOR_STYLES.ICON_SIZE]).toEqual({
options: {
size: 6,
},
type: 'STATIC',
const nextFields = [new MockField({ fieldName: 'someOtherField', dataType: 'number' })];
const {
hasChanges,
nextStyleDescriptor,
} = await vectorStyle.getDescriptorWithUpdatedStyleProps(
nextFields,
previousFields,
mapColors
);
expect(hasChanges).toBe(true);
expect(nextStyleDescriptor.properties[VECTOR_STYLES.LINE_COLOR]).toEqual({
options: {
field: {
name: 'someOtherField',
origin: FIELD_ORIGIN.SOURCE,
},
},
type: 'DYNAMIC',
});
expect(nextStyleDescriptor.properties[VECTOR_STYLES.ICON_SIZE]).toEqual({
options: {
minSize: 1,
maxSize: 10,
field: {
name: 'someOtherField',
origin: FIELD_ORIGIN.SOURCE,
},
},
type: 'DYNAMIC',
});
});
});
});

View file

@ -6,17 +6,17 @@
import _ from 'lodash';
import React, { ReactElement } from 'react';
import { Map as MbMap, FeatureIdentifier } from 'mapbox-gl';
import { FeatureIdentifier, Map as MbMap } from 'mapbox-gl';
import { FeatureCollection } from 'geojson';
import { StyleProperties, VectorStyleEditor } from './components/vector_style_editor';
import { getDefaultStaticProperties, LINE_STYLES, POLYGON_STYLES } from './vector_style_defaults';
import {
GEO_JSON_TYPE,
FIELD_ORIGIN,
STYLE_TYPE,
SOURCE_FORMATTERS_DATA_REQUEST_ID,
LAYER_STYLE_TYPE,
DEFAULT_ICON,
FIELD_ORIGIN,
GEO_JSON_TYPE,
LAYER_STYLE_TYPE,
SOURCE_FORMATTERS_DATA_REQUEST_ID,
STYLE_TYPE,
VECTOR_SHAPE_TYPE,
VECTOR_STYLES,
} from '../../../../common/constants';
@ -25,7 +25,7 @@ import { VectorIcon } from './components/legend/vector_icon';
import { VectorStyleLegend } from './components/legend/vector_style_legend';
import { isOnlySingleFeatureType } from './style_util';
import { StaticStyleProperty } from './properties/static_style_property';
import { DynamicStyleProperty } from './properties/dynamic_style_property';
import { DynamicStyleProperty, IDynamicStyleProperty } from './properties/dynamic_style_property';
import { DynamicSizeProperty } from './properties/dynamic_size_property';
import { StaticSizeProperty } from './properties/static_size_property';
import { StaticColorProperty } from './properties/static_color_property';
@ -43,6 +43,7 @@ import {
ColorDynamicOptions,
ColorStaticOptions,
ColorStylePropertyDescriptor,
DynamicStyleProperties,
DynamicStylePropertyOptions,
IconDynamicOptions,
IconStaticOptions,
@ -66,11 +67,11 @@ import {
import { DataRequest } from '../../util/data_request';
import { IStyle } from '../style';
import { IStyleProperty } from './properties/style_property';
import { IDynamicStyleProperty } from './properties/dynamic_style_property';
import { IField } from '../../fields/field';
import { IVectorLayer } from '../../layers/vector_layer/vector_layer';
import { IVectorSource } from '../../sources/vector_source';
import { createStyleFieldsHelper } from './style_fields_helper';
import { createStyleFieldsHelper, StyleFieldsHelper } from './style_fields_helper';
import { IESAggField } from '../../fields/agg';
const POINTS = [GEO_JSON_TYPE.POINT, GEO_JSON_TYPE.MULTI_POINT];
const LINES = [GEO_JSON_TYPE.LINE_STRING, GEO_JSON_TYPE.MULTI_LINE_STRING];
@ -81,8 +82,9 @@ export interface IVectorStyle extends IStyle {
getDynamicPropertiesArray(): Array<IDynamicStyleProperty<DynamicStylePropertyOptions>>;
getSourceFieldNames(): string[];
getStyleMeta(): StyleMeta;
getDescriptorWithMissingStylePropsRemoved(
getDescriptorWithUpdatedStyleProps(
nextFields: IField[],
previousFields: IField[],
mapColors: string[]
): Promise<{ hasChanges: boolean; nextStyleDescriptor?: VectorStyleDescriptor }>;
pluckStyleMetaFromSourceDataRequest(sourceDataRequest: DataRequest): Promise<StyleMetaDescriptor>;
@ -239,11 +241,187 @@ export class VectorStyle implements IVectorStyle {
);
}
async _updateFieldsInDescriptor(
nextFields: IField[],
styleFieldsHelper: StyleFieldsHelper,
previousFields: IField[],
mapColors: string[]
) {
const originalProperties = this.getRawProperties();
const invalidStyleNames: VECTOR_STYLES[] = (Object.keys(
originalProperties
) as VECTOR_STYLES[]).filter((key) => {
const dynamicOptions = getDynamicOptions(originalProperties, key);
if (!dynamicOptions || !dynamicOptions.field || !dynamicOptions.field.name) {
return false;
}
const hasMatchingField = nextFields.some((field) => {
return (
dynamicOptions && dynamicOptions.field && dynamicOptions.field.name === field.getName()
);
});
return !hasMatchingField;
});
let hasChanges = false;
const updatedProperties: VectorStylePropertiesDescriptor = { ...originalProperties };
invalidStyleNames.forEach((invalidStyleName) => {
for (let i = 0; i < previousFields.length; i++) {
const previousField = previousFields[i];
const nextField = nextFields[i];
if (previousField.isEqual(nextField)) {
continue;
}
const isFieldDataTypeCompatible = styleFieldsHelper.hasFieldForStyle(
nextField,
invalidStyleName
);
if (!isFieldDataTypeCompatible) {
return;
}
hasChanges = true;
(updatedProperties[invalidStyleName] as DynamicStyleProperties) = {
type: STYLE_TYPE.DYNAMIC,
options: {
...originalProperties[invalidStyleName].options,
field: rectifyFieldDescriptor(nextField as IESAggField, {
origin: previousField.getOrigin(),
name: previousField.getName(),
}),
} as DynamicStylePropertyOptions,
};
}
});
return this._deleteFieldsFromDescriptorAndUpdateStyling(
nextFields,
updatedProperties,
hasChanges,
styleFieldsHelper,
mapColors
);
}
async _deleteFieldsFromDescriptorAndUpdateStyling(
nextFields: IField[],
originalProperties: VectorStylePropertiesDescriptor,
hasChanges: boolean,
styleFieldsHelper: StyleFieldsHelper,
mapColors: string[]
) {
// const originalProperties = this.getRawProperties();
const updatedProperties = {} as VectorStylePropertiesDescriptor;
const dynamicProperties = (Object.keys(originalProperties) as VECTOR_STYLES[]).filter((key) => {
const dynamicOptions = getDynamicOptions(originalProperties, key);
return dynamicOptions && dynamicOptions.field && dynamicOptions.field.name;
});
dynamicProperties.forEach((key: VECTOR_STYLES) => {
// Convert dynamic styling to static stying when there are no style fields
const styleFields = styleFieldsHelper.getFieldsForStyle(key);
if (styleFields.length === 0) {
const staticProperties = getDefaultStaticProperties(mapColors);
updatedProperties[key] = staticProperties[key] as any;
return;
}
const dynamicProperty = originalProperties[key];
if (!dynamicProperty || !dynamicProperty.options) {
return;
}
const fieldName = (dynamicProperty.options as DynamicStylePropertyOptions).field!.name;
if (!fieldName) {
return;
}
const matchingOrdinalField = nextFields.find((ordinalField) => {
return fieldName === ordinalField.getName();
});
if (matchingOrdinalField) {
return;
}
updatedProperties[key] = {
type: DynamicStyleProperty.type,
options: {
...originalProperties[key]!.options,
},
} as any;
if ('field' in updatedProperties[key].options) {
delete (updatedProperties[key].options as DynamicStylePropertyOptions).field;
}
});
if (Object.keys(updatedProperties).length !== 0) {
return {
hasChanges: true,
nextStyleDescriptor: VectorStyle.createDescriptor(
{
...originalProperties,
...updatedProperties,
},
this.isTimeAware()
),
};
} else {
return {
hasChanges,
nextStyleDescriptor: VectorStyle.createDescriptor(
{
...originalProperties,
},
this.isTimeAware()
),
};
}
}
/*
* Changes to source descriptor and join descriptor will impact style properties.
* For instance, a style property may be dynamically tied to the value of an ordinal field defined
* by a join or a metric aggregation. The metric aggregation or join may be edited or removed.
* When this happens, the style will be linked to a no-longer-existing ordinal field.
* This method provides a way for a style to clean itself and return a descriptor that unsets any dynamic
* properties that are tied to missing oridinal fields
*
* This method does not update its descriptor. It just returns a new descriptor that the caller
* can then use to update store state via dispatch.
*/
async getDescriptorWithUpdatedStyleProps(
nextFields: IField[],
previousFields: IField[],
mapColors: string[]
) {
const styleFieldsHelper = await createStyleFieldsHelper(nextFields);
return previousFields.length === nextFields.length
? // Field-config changed
await this._updateFieldsInDescriptor(
nextFields,
styleFieldsHelper,
previousFields,
mapColors
)
: // Deletions or additions
await this._deleteFieldsFromDescriptorAndUpdateStyling(
nextFields,
this.getRawProperties(),
false,
styleFieldsHelper,
mapColors
);
}
getType() {
return LAYER_STYLE_TYPE.VECTOR;
}
getAllStyleProperties() {
getAllStyleProperties(): Array<IStyleProperty<StylePropertyOptions>> {
return [
this._symbolizeAsStyleProperty,
this._iconStyleProperty,
@ -303,94 +481,6 @@ export class VectorStyle implements IVectorStyle {
);
}
/*
* Changes to source descriptor and join descriptor will impact style properties.
* For instance, a style property may be dynamically tied to the value of an ordinal field defined
* by a join or a metric aggregation. The metric aggregation or join may be edited or removed.
* When this happens, the style will be linked to a no-longer-existing ordinal field.
* This method provides a way for a style to clean itself and return a descriptor that unsets any dynamic
* properties that are tied to missing oridinal fields
*
* This method does not update its descriptor. It just returns a new descriptor that the caller
* can then use to update store state via dispatch.
*/
async getDescriptorWithMissingStylePropsRemoved(nextFields: IField[], mapColors: string[]) {
const styleFieldsHelper = await createStyleFieldsHelper(nextFields);
const originalProperties = this.getRawProperties();
const updatedProperties = {} as VectorStylePropertiesDescriptor;
const dynamicProperties = (Object.keys(originalProperties) as VECTOR_STYLES[]).filter((key) => {
if (!originalProperties[key]) {
return false;
}
const propertyDescriptor = originalProperties[key];
if (
!propertyDescriptor ||
!('type' in propertyDescriptor) ||
propertyDescriptor.type !== STYLE_TYPE.DYNAMIC ||
!propertyDescriptor.options
) {
return false;
}
const dynamicOptions = propertyDescriptor.options as DynamicStylePropertyOptions;
return dynamicOptions.field && dynamicOptions.field.name;
});
dynamicProperties.forEach((key: VECTOR_STYLES) => {
// Convert dynamic styling to static stying when there are no style fields
const styleFields = styleFieldsHelper.getFieldsForStyle(key);
if (styleFields.length === 0) {
const staticProperties = getDefaultStaticProperties(mapColors);
updatedProperties[key] = staticProperties[key] as any;
return;
}
const dynamicProperty = originalProperties[key];
if (!dynamicProperty || !dynamicProperty.options) {
return;
}
const fieldName = (dynamicProperty.options as DynamicStylePropertyOptions).field!.name;
if (!fieldName) {
return;
}
const matchingOrdinalField = nextFields.find((ordinalField) => {
return fieldName === ordinalField.getName();
});
if (matchingOrdinalField) {
return;
}
updatedProperties[key] = {
type: DynamicStyleProperty.type,
options: {
...originalProperties[key].options,
},
} as any;
// @ts-expect-error
delete updatedProperties[key].options.field;
});
if (Object.keys(updatedProperties).length === 0) {
return {
hasChanges: false,
nextStyleDescriptor: { ...this._descriptor },
};
}
return {
hasChanges: true,
nextStyleDescriptor: VectorStyle.createDescriptor(
{
...originalProperties,
...updatedProperties,
},
this.isTimeAware()
),
};
}
async pluckStyleMetaFromSourceDataRequest(sourceDataRequest: DataRequest) {
const features = _.get(sourceDataRequest.getData(), 'features', []);
@ -478,11 +568,11 @@ export class VectorStyle implements IVectorStyle {
return this._descriptor.isTimeAware;
}
getRawProperties() {
getRawProperties(): VectorStylePropertiesDescriptor {
return this._descriptor.properties || {};
}
getDynamicPropertiesArray() {
getDynamicPropertiesArray(): Array<IDynamicStyleProperty<DynamicStylePropertyOptions>> {
const styleProperties = this.getAllStyleProperties();
return styleProperties.filter(
(styleProperty) => styleProperty.isDynamic() && styleProperty.isComplete()
@ -882,3 +972,32 @@ export class VectorStyle implements IVectorStyle {
}
}
}
function getDynamicOptions(
originalProperties: VectorStylePropertiesDescriptor,
key: VECTOR_STYLES
): DynamicStylePropertyOptions | null {
if (!originalProperties[key]) {
return null;
}
const propertyDescriptor = originalProperties[key];
if (
!propertyDescriptor ||
!('type' in propertyDescriptor) ||
propertyDescriptor.type !== STYLE_TYPE.DYNAMIC ||
!propertyDescriptor.options
) {
return null;
}
return propertyDescriptor.options as DynamicStylePropertyOptions;
}
function rectifyFieldDescriptor(
currentField: IESAggField,
previousFieldDescriptor: StylePropertyField
): StylePropertyField {
return {
origin: previousFieldDescriptor.origin,
name: currentField.getName(),
};
}