mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Maps] scale marker size by area (#131911)
* [Maps] scale marker size by area * icon size * clean up * more clean-up * add tests Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
63f2d66471
commit
71c17a4631
8 changed files with 470 additions and 277 deletions
|
@ -1,162 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import type { Map as MbMap } from '@kbn/mapbox-gl';
|
||||
import { DynamicStyleProperty } from './dynamic_style_property';
|
||||
import { OrdinalLegend } from '../components/legend/ordinal_legend';
|
||||
import { makeMbClampedNumberExpression } from '../style_util';
|
||||
import {
|
||||
FieldFormatter,
|
||||
HALF_MAKI_ICON_SIZE,
|
||||
MB_LOOKUP_FUNCTION,
|
||||
VECTOR_STYLES,
|
||||
} from '../../../../../common/constants';
|
||||
import { SizeDynamicOptions } from '../../../../../common/descriptor_types';
|
||||
import { IField } from '../../../fields/field';
|
||||
import { IVectorLayer } from '../../../layers/vector_layer';
|
||||
|
||||
export class DynamicSizeProperty extends DynamicStyleProperty<SizeDynamicOptions> {
|
||||
private readonly _isSymbolizedAsIcon: boolean;
|
||||
|
||||
constructor(
|
||||
options: SizeDynamicOptions,
|
||||
styleName: VECTOR_STYLES,
|
||||
field: IField | null,
|
||||
vectorLayer: IVectorLayer,
|
||||
getFieldFormatter: (fieldName: string) => null | FieldFormatter,
|
||||
isSymbolizedAsIcon: boolean
|
||||
) {
|
||||
super(options, styleName, field, vectorLayer, getFieldFormatter);
|
||||
this._isSymbolizedAsIcon = isSymbolizedAsIcon;
|
||||
}
|
||||
|
||||
supportsFeatureState() {
|
||||
// mb style "icon-size" does not support feature state
|
||||
if (this.getStyleName() === VECTOR_STYLES.ICON_SIZE && this._isSymbolizedAsIcon) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// mb style "text-size" does not support feature state
|
||||
if (this.getStyleName() === VECTOR_STYLES.LABEL_SIZE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
syncHaloWidthWithMb(mbLayerId: string, mbMap: MbMap) {
|
||||
const haloWidth = this.getMbSizeExpression();
|
||||
mbMap.setPaintProperty(mbLayerId, 'icon-halo-width', haloWidth);
|
||||
}
|
||||
|
||||
syncIconSizeWithMb(symbolLayerId: string, mbMap: MbMap) {
|
||||
const rangeFieldMeta = this.getRangeFieldMeta();
|
||||
if (this._isSizeDynamicConfigComplete() && rangeFieldMeta) {
|
||||
const targetName = this.getMbFieldName();
|
||||
// Using property state instead of feature-state because layout properties do not support feature-state
|
||||
mbMap.setLayoutProperty(symbolLayerId, 'icon-size', [
|
||||
'interpolate',
|
||||
['linear'],
|
||||
makeMbClampedNumberExpression({
|
||||
minValue: rangeFieldMeta.min,
|
||||
maxValue: rangeFieldMeta.max,
|
||||
fallback: 0,
|
||||
lookupFunction: MB_LOOKUP_FUNCTION.GET,
|
||||
fieldName: targetName,
|
||||
}),
|
||||
rangeFieldMeta.min,
|
||||
this._options.minSize / HALF_MAKI_ICON_SIZE,
|
||||
rangeFieldMeta.max,
|
||||
this._options.maxSize / HALF_MAKI_ICON_SIZE,
|
||||
]);
|
||||
} else {
|
||||
mbMap.setLayoutProperty(symbolLayerId, 'icon-size', null);
|
||||
}
|
||||
}
|
||||
|
||||
syncCircleStrokeWidthWithMb(mbLayerId: string, mbMap: MbMap) {
|
||||
const lineWidth = this.getMbSizeExpression();
|
||||
mbMap.setPaintProperty(mbLayerId, 'circle-stroke-width', lineWidth);
|
||||
}
|
||||
|
||||
syncCircleRadiusWithMb(mbLayerId: string, mbMap: MbMap) {
|
||||
const circleRadius = this.getMbSizeExpression();
|
||||
mbMap.setPaintProperty(mbLayerId, 'circle-radius', circleRadius);
|
||||
}
|
||||
|
||||
syncLineWidthWithMb(mbLayerId: string, mbMap: MbMap) {
|
||||
const lineWidth = this.getMbSizeExpression();
|
||||
mbMap.setPaintProperty(mbLayerId, 'line-width', lineWidth);
|
||||
}
|
||||
|
||||
syncLabelSizeWithMb(mbLayerId: string, mbMap: MbMap) {
|
||||
const lineWidth = this.getMbSizeExpression();
|
||||
mbMap.setLayoutProperty(mbLayerId, 'text-size', lineWidth);
|
||||
}
|
||||
|
||||
getMbSizeExpression() {
|
||||
const rangeFieldMeta = this.getRangeFieldMeta();
|
||||
if (!this._isSizeDynamicConfigComplete() || !rangeFieldMeta) {
|
||||
// return min of size to avoid flashing
|
||||
// returning minimum allows "growing" of the symbols when the meta comes in
|
||||
// A grow effect us less visually jarring as shrinking.
|
||||
// especially relevant when displaying fine-grained grids using mvt
|
||||
return this._options.minSize >= 0 ? this._options.minSize : null;
|
||||
}
|
||||
|
||||
return this._getMbDataDrivenSize({
|
||||
targetName: this.getMbFieldName(),
|
||||
minSize: this._options.minSize,
|
||||
maxSize: this._options.maxSize,
|
||||
minValue: rangeFieldMeta.min,
|
||||
maxValue: rangeFieldMeta.max,
|
||||
});
|
||||
}
|
||||
|
||||
_getMbDataDrivenSize({
|
||||
targetName,
|
||||
minSize,
|
||||
maxSize,
|
||||
minValue,
|
||||
maxValue,
|
||||
}: {
|
||||
targetName: string;
|
||||
minSize: number;
|
||||
maxSize: number;
|
||||
minValue: number;
|
||||
maxValue: number;
|
||||
}) {
|
||||
const stops =
|
||||
minValue === maxValue ? [maxValue, maxSize] : [minValue, minSize, maxValue, maxSize];
|
||||
return [
|
||||
'interpolate',
|
||||
['linear'],
|
||||
makeMbClampedNumberExpression({
|
||||
lookupFunction: this.getMbLookupFunction(),
|
||||
maxValue,
|
||||
minValue,
|
||||
fieldName: targetName,
|
||||
fallback: 0,
|
||||
}),
|
||||
...stops,
|
||||
];
|
||||
}
|
||||
|
||||
_isSizeDynamicConfigComplete() {
|
||||
return (
|
||||
this._field &&
|
||||
this._field.isValid() &&
|
||||
this._options.minSize >= 0 &&
|
||||
this._options.maxSize >= 0
|
||||
);
|
||||
}
|
||||
|
||||
renderLegendDetailRow() {
|
||||
return <OrdinalLegend style={this} />;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
jest.mock('../../components/vector_style_editor', () => ({
|
||||
VectorStyleEditor: () => {
|
||||
return <div>mockVectorStyleEditor</div>;
|
||||
},
|
||||
}));
|
||||
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { DynamicSizeProperty } from './dynamic_size_property';
|
||||
import { FIELD_ORIGIN, RawValue, VECTOR_STYLES } from '../../../../../../common/constants';
|
||||
import { IField } from '../../../../fields/field';
|
||||
import { IVectorLayer } from '../../../../layers/vector_layer';
|
||||
|
||||
describe('renderLegendDetailRow', () => {
|
||||
test('Should render as range', async () => {
|
||||
const field = {
|
||||
getLabel: async () => {
|
||||
return 'foobar_label';
|
||||
},
|
||||
getName: () => {
|
||||
return 'foodbar';
|
||||
},
|
||||
getOrigin: () => {
|
||||
return FIELD_ORIGIN.SOURCE;
|
||||
},
|
||||
supportsFieldMetaFromEs: () => {
|
||||
return true;
|
||||
},
|
||||
supportsFieldMetaFromLocalData: () => {
|
||||
return true;
|
||||
},
|
||||
} as unknown as IField;
|
||||
const sizeProp = new DynamicSizeProperty(
|
||||
{ minSize: 0, maxSize: 10, fieldMetaOptions: { isEnabled: true } },
|
||||
VECTOR_STYLES.ICON_SIZE,
|
||||
field,
|
||||
{} as unknown as IVectorLayer,
|
||||
() => {
|
||||
return (value: RawValue) => value + '_format';
|
||||
},
|
||||
false
|
||||
);
|
||||
sizeProp.getRangeFieldMeta = () => {
|
||||
return {
|
||||
min: 0,
|
||||
max: 100,
|
||||
delta: 100,
|
||||
};
|
||||
};
|
||||
|
||||
const legendRow = sizeProp.renderLegendDetailRow();
|
||||
const component = shallow(legendRow);
|
||||
|
||||
// Ensure all promises resolve
|
||||
await new Promise((resolve) => process.nextTick(resolve));
|
||||
// Ensure the state changes are reflected
|
||||
component.update();
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMbSizeExpression', () => {
|
||||
test('Should return interpolation expression with single stop when range.delta is 0', async () => {
|
||||
const field = {
|
||||
isValid: () => {
|
||||
return true;
|
||||
},
|
||||
getName: () => {
|
||||
return 'foobar';
|
||||
},
|
||||
getMbFieldName: () => {
|
||||
return 'foobar';
|
||||
},
|
||||
getOrigin: () => {
|
||||
return FIELD_ORIGIN.SOURCE;
|
||||
},
|
||||
getSource: () => {
|
||||
return {
|
||||
isMvt: () => {
|
||||
return false;
|
||||
},
|
||||
};
|
||||
},
|
||||
supportsFieldMetaFromEs: () => {
|
||||
return true;
|
||||
},
|
||||
} as unknown as IField;
|
||||
const sizeProp = new DynamicSizeProperty(
|
||||
{ minSize: 8, maxSize: 32, fieldMetaOptions: { isEnabled: true } },
|
||||
VECTOR_STYLES.ICON_SIZE,
|
||||
field,
|
||||
{} as unknown as IVectorLayer,
|
||||
() => {
|
||||
return (value: RawValue) => value + '_format';
|
||||
},
|
||||
false
|
||||
);
|
||||
sizeProp.getRangeFieldMeta = () => {
|
||||
return {
|
||||
min: 100,
|
||||
max: 100,
|
||||
delta: 0,
|
||||
};
|
||||
};
|
||||
|
||||
expect(sizeProp.getMbSizeExpression()).toEqual([
|
||||
'interpolate',
|
||||
['linear'],
|
||||
[
|
||||
'sqrt',
|
||||
[
|
||||
'coalesce',
|
||||
[
|
||||
'case',
|
||||
['==', ['feature-state', 'foobar'], null],
|
||||
100,
|
||||
['max', ['min', ['to-number', ['feature-state', 'foobar']], 100], 100],
|
||||
],
|
||||
100,
|
||||
],
|
||||
],
|
||||
10,
|
||||
32,
|
||||
]);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import type { Map as MbMap } from '@kbn/mapbox-gl';
|
||||
import { DynamicStyleProperty } from '../dynamic_style_property';
|
||||
import { OrdinalLegend } from '../../components/legend/ordinal_legend';
|
||||
import { makeMbClampedNumberExpression } from '../../style_util';
|
||||
import {
|
||||
FieldFormatter,
|
||||
HALF_MAKI_ICON_SIZE,
|
||||
VECTOR_STYLES,
|
||||
} from '../../../../../../common/constants';
|
||||
import type { SizeDynamicOptions } from '../../../../../../common/descriptor_types';
|
||||
import type { IField } from '../../../../fields/field';
|
||||
import type { IVectorLayer } from '../../../../layers/vector_layer';
|
||||
|
||||
export class DynamicSizeProperty extends DynamicStyleProperty<SizeDynamicOptions> {
|
||||
private readonly _isSymbolizedAsIcon: boolean;
|
||||
|
||||
constructor(
|
||||
options: SizeDynamicOptions,
|
||||
styleName: VECTOR_STYLES,
|
||||
field: IField | null,
|
||||
vectorLayer: IVectorLayer,
|
||||
getFieldFormatter: (fieldName: string) => null | FieldFormatter,
|
||||
isSymbolizedAsIcon: boolean
|
||||
) {
|
||||
super(options, styleName, field, vectorLayer, getFieldFormatter);
|
||||
this._isSymbolizedAsIcon = isSymbolizedAsIcon;
|
||||
}
|
||||
|
||||
supportsFeatureState() {
|
||||
// mb style "icon-size" does not support feature state
|
||||
if (this.getStyleName() === VECTOR_STYLES.ICON_SIZE && this._isSymbolizedAsIcon) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// mb style "text-size" does not support feature state
|
||||
if (this.getStyleName() === VECTOR_STYLES.LABEL_SIZE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
syncHaloWidthWithMb(mbLayerId: string, mbMap: MbMap) {
|
||||
mbMap.setPaintProperty(mbLayerId, 'icon-halo-width', this.getMbSizeExpression());
|
||||
}
|
||||
|
||||
syncIconSizeWithMb(symbolLayerId: string, mbMap: MbMap) {
|
||||
mbMap.setLayoutProperty(symbolLayerId, 'icon-size', this.getMbSizeExpression());
|
||||
}
|
||||
|
||||
syncCircleStrokeWidthWithMb(mbLayerId: string, mbMap: MbMap) {
|
||||
mbMap.setPaintProperty(mbLayerId, 'circle-stroke-width', this.getMbSizeExpression());
|
||||
}
|
||||
|
||||
syncCircleRadiusWithMb(mbLayerId: string, mbMap: MbMap) {
|
||||
mbMap.setPaintProperty(mbLayerId, 'circle-radius', this.getMbSizeExpression());
|
||||
}
|
||||
|
||||
syncLineWidthWithMb(mbLayerId: string, mbMap: MbMap) {
|
||||
mbMap.setPaintProperty(mbLayerId, 'line-width', this.getMbSizeExpression());
|
||||
}
|
||||
|
||||
syncLabelSizeWithMb(mbLayerId: string, mbMap: MbMap) {
|
||||
mbMap.setLayoutProperty(mbLayerId, 'text-size', this.getMbSizeExpression());
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns interpolation expression linearly translating domain values [minValue, maxValue] to display range [minSize, maxSize]
|
||||
*/
|
||||
getMbSizeExpression() {
|
||||
const rangeFieldMeta = this.getRangeFieldMeta();
|
||||
if (!this._isSizeDynamicConfigComplete() || !rangeFieldMeta) {
|
||||
// return min of size to avoid flashing
|
||||
// returning minimum allows "growing" of the symbols when the meta comes in
|
||||
// A grow effect us less visually jarring as shrinking.
|
||||
// especially relevant when displaying fine-grained grids using mvt
|
||||
return this._options.minSize >= 0 ? this._options.minSize : null;
|
||||
}
|
||||
|
||||
const isArea = this.getStyleName() === VECTOR_STYLES.ICON_SIZE;
|
||||
// isArea === true
|
||||
// It's a mistake to linearly map a data value to an area dimension (i.e. cirle radius).
|
||||
// Area squares area dimension ("pie * r * r" or "x * x"), visually distorting proportions.
|
||||
// Since it is the quadratic function that is causing this,
|
||||
// we need to counteract its effects by applying its inverse function — the square-root function.
|
||||
// https://bl.ocks.org/guilhermesimoes/e6356aa90a16163a6f917f53600a2b4a
|
||||
|
||||
// can not take square root of 0 or negative number
|
||||
// shift values to be positive integers >= 1
|
||||
const valueShift = rangeFieldMeta.min < 1 ? Math.abs(rangeFieldMeta.min) + 1 : 0;
|
||||
|
||||
const maxValueStopInput = isArea
|
||||
? Math.sqrt(rangeFieldMeta.max + valueShift)
|
||||
: rangeFieldMeta.max;
|
||||
const minValueStopInput = isArea
|
||||
? Math.sqrt(rangeFieldMeta.min + valueShift)
|
||||
: rangeFieldMeta.min;
|
||||
const maxRangeStopOutput =
|
||||
this.getStyleName() === VECTOR_STYLES.ICON_SIZE && this._isSymbolizedAsIcon
|
||||
? this._options.maxSize / HALF_MAKI_ICON_SIZE
|
||||
: this._options.maxSize;
|
||||
const minRangeStopOutput =
|
||||
this.getStyleName() === VECTOR_STYLES.ICON_SIZE && this._isSymbolizedAsIcon
|
||||
? this._options.minSize / HALF_MAKI_ICON_SIZE
|
||||
: this._options.minSize;
|
||||
const stops =
|
||||
rangeFieldMeta.min === rangeFieldMeta.max
|
||||
? [maxValueStopInput, maxRangeStopOutput]
|
||||
: [minValueStopInput, minRangeStopOutput, maxValueStopInput, maxRangeStopOutput];
|
||||
|
||||
const valueExpression = makeMbClampedNumberExpression({
|
||||
lookupFunction: this.getMbLookupFunction(),
|
||||
maxValue: rangeFieldMeta.max,
|
||||
minValue: rangeFieldMeta.min,
|
||||
fieldName: this.getMbFieldName(),
|
||||
fallback: rangeFieldMeta.min,
|
||||
});
|
||||
const valueShiftExpression =
|
||||
rangeFieldMeta.min < 1 ? ['+', valueExpression, valueShift] : valueExpression;
|
||||
const sqrtValueExpression = ['sqrt', valueShiftExpression];
|
||||
const inputExpression = isArea ? sqrtValueExpression : valueExpression;
|
||||
|
||||
return ['interpolate', ['linear'], inputExpression, ...stops];
|
||||
}
|
||||
|
||||
_isSizeDynamicConfigComplete() {
|
||||
return (
|
||||
this._field &&
|
||||
this._field.isValid() &&
|
||||
this._options.minSize >= 0 &&
|
||||
this._options.maxSize >= 0
|
||||
);
|
||||
}
|
||||
|
||||
renderLegendDetailRow() {
|
||||
return <OrdinalLegend style={this} />;
|
||||
}
|
||||
}
|
|
@ -5,86 +5,13 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
jest.mock('../components/vector_style_editor', () => ({
|
||||
VectorStyleEditor: () => {
|
||||
return <div>mockVectorStyleEditor</div>;
|
||||
},
|
||||
}));
|
||||
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { DynamicSizeProperty } from './dynamic_size_property';
|
||||
import { FIELD_ORIGIN, RawValue, VECTOR_STYLES } from '../../../../../common/constants';
|
||||
import { IField } from '../../../fields/field';
|
||||
import type { Map as MbMap } from '@kbn/mapbox-gl';
|
||||
import { IVectorLayer } from '../../../layers/vector_layer';
|
||||
import { FIELD_ORIGIN, RawValue, VECTOR_STYLES } from '../../../../../../common/constants';
|
||||
import { IField } from '../../../../fields/field';
|
||||
import { IVectorLayer } from '../../../../layers/vector_layer';
|
||||
|
||||
export class MockMbMap {
|
||||
_paintPropertyCalls: unknown[];
|
||||
|
||||
constructor() {
|
||||
this._paintPropertyCalls = [];
|
||||
}
|
||||
setPaintProperty(...args: unknown[]) {
|
||||
this._paintPropertyCalls.push([...args]);
|
||||
}
|
||||
|
||||
getPaintPropertyCalls(): unknown[] {
|
||||
return this._paintPropertyCalls;
|
||||
}
|
||||
}
|
||||
|
||||
describe('renderLegendDetailRow', () => {
|
||||
test('Should render as range', async () => {
|
||||
const field = {
|
||||
getLabel: async () => {
|
||||
return 'foobar_label';
|
||||
},
|
||||
getName: () => {
|
||||
return 'foodbar';
|
||||
},
|
||||
getOrigin: () => {
|
||||
return FIELD_ORIGIN.SOURCE;
|
||||
},
|
||||
supportsFieldMetaFromEs: () => {
|
||||
return true;
|
||||
},
|
||||
supportsFieldMetaFromLocalData: () => {
|
||||
return true;
|
||||
},
|
||||
} as unknown as IField;
|
||||
const sizeProp = new DynamicSizeProperty(
|
||||
{ minSize: 0, maxSize: 10, fieldMetaOptions: { isEnabled: true } },
|
||||
VECTOR_STYLES.ICON_SIZE,
|
||||
field,
|
||||
{} as unknown as IVectorLayer,
|
||||
() => {
|
||||
return (value: RawValue) => value + '_format';
|
||||
},
|
||||
false
|
||||
);
|
||||
sizeProp.getRangeFieldMeta = () => {
|
||||
return {
|
||||
min: 0,
|
||||
max: 100,
|
||||
delta: 100,
|
||||
};
|
||||
};
|
||||
|
||||
const legendRow = sizeProp.renderLegendDetailRow();
|
||||
const component = shallow(legendRow);
|
||||
|
||||
// Ensure all promises resolve
|
||||
await new Promise((resolve) => process.nextTick(resolve));
|
||||
// Ensure the state changes are reflected
|
||||
component.update();
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('syncSize', () => {
|
||||
test('Should sync with circle-radius prop', async () => {
|
||||
describe('getMbSizeExpression - circle', () => {
|
||||
test('Should return interpolation expression with square-root function', async () => {
|
||||
const field = {
|
||||
isValid: () => {
|
||||
return true;
|
||||
|
@ -109,7 +36,7 @@ describe('syncSize', () => {
|
|||
return true;
|
||||
},
|
||||
} as unknown as IField;
|
||||
const sizeProp = new DynamicSizeProperty(
|
||||
const iconSize = new DynamicSizeProperty(
|
||||
{ minSize: 8, maxSize: 32, fieldMetaOptions: { isEnabled: true } },
|
||||
VECTOR_STYLES.ICON_SIZE,
|
||||
field,
|
||||
|
@ -119,51 +46,48 @@ describe('syncSize', () => {
|
|||
},
|
||||
false
|
||||
);
|
||||
sizeProp.getRangeFieldMeta = () => {
|
||||
iconSize.getRangeFieldMeta = () => {
|
||||
return {
|
||||
min: 0,
|
||||
max: 100,
|
||||
delta: 100,
|
||||
};
|
||||
};
|
||||
const mockMbMap = new MockMbMap() as unknown as MbMap;
|
||||
|
||||
sizeProp.syncCircleRadiusWithMb('foobar', mockMbMap);
|
||||
|
||||
// @ts-expect-error
|
||||
expect(mockMbMap.getPaintPropertyCalls()).toEqual([
|
||||
expect(iconSize.getMbSizeExpression()).toEqual([
|
||||
'interpolate',
|
||||
['linear'],
|
||||
[
|
||||
'foobar',
|
||||
'circle-radius',
|
||||
'sqrt',
|
||||
[
|
||||
'interpolate',
|
||||
['linear'],
|
||||
'+',
|
||||
[
|
||||
'coalesce',
|
||||
[
|
||||
'case',
|
||||
['==', ['feature-state', 'foobar'], null],
|
||||
-1,
|
||||
0,
|
||||
['max', ['min', ['to-number', ['feature-state', 'foobar']], 100], 0],
|
||||
],
|
||||
0,
|
||||
],
|
||||
0,
|
||||
8,
|
||||
100,
|
||||
32,
|
||||
1,
|
||||
],
|
||||
],
|
||||
1,
|
||||
8,
|
||||
10.04987562112089,
|
||||
32,
|
||||
]);
|
||||
});
|
||||
|
||||
test('Should truncate interpolate expression to max when no delta', async () => {
|
||||
test('Should return interpolation expression without value shift when range.min is > 1', async () => {
|
||||
const field = {
|
||||
isValid: () => {
|
||||
return true;
|
||||
},
|
||||
getName: () => {
|
||||
return 'foobar';
|
||||
return 'foodbar';
|
||||
},
|
||||
getMbFieldName: () => {
|
||||
return 'foobar';
|
||||
|
@ -182,7 +106,7 @@ describe('syncSize', () => {
|
|||
return true;
|
||||
},
|
||||
} as unknown as IField;
|
||||
const sizeProp = new DynamicSizeProperty(
|
||||
const iconSize = new DynamicSizeProperty(
|
||||
{ minSize: 8, maxSize: 32, fieldMetaOptions: { isEnabled: true } },
|
||||
VECTOR_STYLES.ICON_SIZE,
|
||||
field,
|
||||
|
@ -192,39 +116,106 @@ describe('syncSize', () => {
|
|||
},
|
||||
false
|
||||
);
|
||||
sizeProp.getRangeFieldMeta = () => {
|
||||
iconSize.getRangeFieldMeta = () => {
|
||||
return {
|
||||
min: 100,
|
||||
min: 1,
|
||||
max: 100,
|
||||
delta: 0,
|
||||
delta: 100,
|
||||
};
|
||||
};
|
||||
const mockMbMap = new MockMbMap() as unknown as MbMap;
|
||||
|
||||
sizeProp.syncCircleRadiusWithMb('foobar', mockMbMap);
|
||||
|
||||
// @ts-expect-error
|
||||
expect(mockMbMap.getPaintPropertyCalls()).toEqual([
|
||||
expect(iconSize.getMbSizeExpression()).toEqual([
|
||||
'interpolate',
|
||||
['linear'],
|
||||
[
|
||||
'foobar',
|
||||
'circle-radius',
|
||||
'sqrt',
|
||||
[
|
||||
'interpolate',
|
||||
['linear'],
|
||||
'coalesce',
|
||||
[
|
||||
'case',
|
||||
['==', ['feature-state', 'foobar'], null],
|
||||
1,
|
||||
['max', ['min', ['to-number', ['feature-state', 'foobar']], 100], 1],
|
||||
],
|
||||
1,
|
||||
],
|
||||
],
|
||||
1,
|
||||
8,
|
||||
10,
|
||||
32,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMbSizeExpression - icon', () => {
|
||||
test('Should return interpolation expression with square-root function', async () => {
|
||||
const field = {
|
||||
isValid: () => {
|
||||
return true;
|
||||
},
|
||||
getName: () => {
|
||||
return 'foodbar';
|
||||
},
|
||||
getMbFieldName: () => {
|
||||
return 'foobar';
|
||||
},
|
||||
getOrigin: () => {
|
||||
return FIELD_ORIGIN.SOURCE;
|
||||
},
|
||||
getSource: () => {
|
||||
return {
|
||||
isMvt: () => {
|
||||
return false;
|
||||
},
|
||||
};
|
||||
},
|
||||
supportsFieldMetaFromEs: () => {
|
||||
return true;
|
||||
},
|
||||
} as unknown as IField;
|
||||
const iconSize = new DynamicSizeProperty(
|
||||
{ minSize: 8, maxSize: 32, fieldMetaOptions: { isEnabled: true } },
|
||||
VECTOR_STYLES.ICON_SIZE,
|
||||
field,
|
||||
{} as unknown as IVectorLayer,
|
||||
() => {
|
||||
return (value: RawValue) => value + '_format';
|
||||
},
|
||||
true
|
||||
);
|
||||
iconSize.getRangeFieldMeta = () => {
|
||||
return {
|
||||
min: 0,
|
||||
max: 100,
|
||||
delta: 100,
|
||||
};
|
||||
};
|
||||
|
||||
expect(iconSize.getMbSizeExpression()).toEqual([
|
||||
'interpolate',
|
||||
['linear'],
|
||||
[
|
||||
'sqrt',
|
||||
[
|
||||
'+',
|
||||
[
|
||||
'coalesce',
|
||||
[
|
||||
'case',
|
||||
['==', ['feature-state', 'foobar'], null],
|
||||
99,
|
||||
['max', ['min', ['to-number', ['feature-state', 'foobar']], 100], 100],
|
||||
['==', ['get', 'foobar'], null],
|
||||
0,
|
||||
['max', ['min', ['to-number', ['get', 'foobar']], 100], 0],
|
||||
],
|
||||
0,
|
||||
],
|
||||
100,
|
||||
32,
|
||||
1,
|
||||
],
|
||||
],
|
||||
1,
|
||||
1,
|
||||
10.04987562112089,
|
||||
4,
|
||||
]);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export { DynamicSizeProperty } from './dynamic_size_property';
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { DynamicSizeProperty } from './dynamic_size_property';
|
||||
import { FIELD_ORIGIN, RawValue, VECTOR_STYLES } from '../../../../../../common/constants';
|
||||
import { IField } from '../../../../fields/field';
|
||||
import { IVectorLayer } from '../../../../layers/vector_layer';
|
||||
|
||||
describe('getMbSizeExpression', () => {
|
||||
test('Should return interpolation expression', async () => {
|
||||
const field = {
|
||||
isValid: () => {
|
||||
return true;
|
||||
},
|
||||
getName: () => {
|
||||
return 'foodbar';
|
||||
},
|
||||
getMbFieldName: () => {
|
||||
return 'foobar';
|
||||
},
|
||||
getOrigin: () => {
|
||||
return FIELD_ORIGIN.SOURCE;
|
||||
},
|
||||
getSource: () => {
|
||||
return {
|
||||
isMvt: () => {
|
||||
return false;
|
||||
},
|
||||
};
|
||||
},
|
||||
supportsFieldMetaFromEs: () => {
|
||||
return true;
|
||||
},
|
||||
} as unknown as IField;
|
||||
const lineWidth = new DynamicSizeProperty(
|
||||
{ minSize: 8, maxSize: 32, fieldMetaOptions: { isEnabled: true } },
|
||||
VECTOR_STYLES.LINE_WIDTH,
|
||||
field,
|
||||
{} as unknown as IVectorLayer,
|
||||
() => {
|
||||
return (value: RawValue) => value + '_format';
|
||||
},
|
||||
false
|
||||
);
|
||||
lineWidth.getRangeFieldMeta = () => {
|
||||
return {
|
||||
min: 0,
|
||||
max: 100,
|
||||
delta: 100,
|
||||
};
|
||||
};
|
||||
|
||||
expect(lineWidth.getMbSizeExpression()).toEqual([
|
||||
'interpolate',
|
||||
['linear'],
|
||||
[
|
||||
'coalesce',
|
||||
[
|
||||
'case',
|
||||
['==', ['feature-state', 'foobar'], null],
|
||||
0,
|
||||
['max', ['min', ['to-number', ['feature-state', 'foobar']], 100], 0],
|
||||
],
|
||||
0,
|
||||
],
|
||||
0,
|
||||
8,
|
||||
100,
|
||||
32,
|
||||
]);
|
||||
});
|
||||
});
|
|
@ -87,7 +87,7 @@ export function makeMbClampedNumberExpression({
|
|||
[
|
||||
'case',
|
||||
['==', [lookupFunction, fieldName], null],
|
||||
minValue - 1, // == does a JS-y like check where returns true for null and undefined
|
||||
fallback, // == does a JS-y like check where returns true for null and undefined
|
||||
clamp,
|
||||
],
|
||||
fallback,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue