[Maps] Update style when metrics change (#83586)
This commit is contained in:
parent
918dbb17de
commit
d41fbf948e
9 changed files with 491 additions and 172 deletions
|
@ -254,3 +254,8 @@ export type DynamicStylePropertyOptions =
|
|||
| LabelDynamicOptions
|
||||
| OrientationDynamicOptions
|
||||
| SizeDynamicOptions;
|
||||
|
||||
export type DynamicStyleProperties = {
|
||||
type: STYLE_TYPE.DYNAMIC;
|
||||
options: DynamicStylePropertyOptions;
|
||||
};
|
||||
|
|
|
@ -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()));
|
||||
};
|
||||
}
|
||||
|
|
|
@ -97,4 +97,8 @@ export class CountAggField implements IESAggField {
|
|||
canReadFromGeoJson(): boolean {
|
||||
return this._canReadFromGeoJson;
|
||||
}
|
||||
|
||||
isEqual(field: IESAggField) {
|
||||
return field.getName() === this.getName();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -83,4 +83,8 @@ export class TopTermPercentageField implements IESAggField {
|
|||
canReadFromGeoJson(): boolean {
|
||||
return this._canReadFromGeoJson;
|
||||
}
|
||||
|
||||
isEqual(field: IESAggField) {
|
||||
return field.getName() === this.getName();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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:
|
||||
|
|
|
@ -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',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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(),
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue