From deab3ca31807fad7a3c6d59d6b169b338bd4e80c Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Wed, 27 May 2020 13:24:52 -0400 Subject: [PATCH] [Maps] display ranged-data with bands (#60570) (#67497) --- .../layers/vector_layer/vector_layer.d.ts | 2 + .../maps/public/classes/styles/_index.scss | 2 + .../maps/public/classes/styles/color_utils.js | 6 +- .../public/classes/styles/color_utils.test.js | 9 +- .../styles/components/color_gradient.js | 11 +- .../components/ranged_style_legend_row.js | 17 +- .../classes/styles/heatmap/heatmap_style.js | 9 +- .../vector/components/legend/_category.scss | 3 + .../components/legend/_vector_legend.scss | 5 + .../components/legend/breaked_legend.js | 82 +++++ .../vector/components/legend/category.js | 16 +- .../legend}/ordinal_legend.js | 71 +++- .../components/legend/vector_style_legend.js | 28 +- .../dynamic_color_property.test.js.snap | 312 +++++++++++++----- .../dynamic_icon_property.test.tsx.snap | 72 ++-- .../dynamic_size_property.test.tsx.snap | 73 ++++ .../vector/properties/__tests__/test_util.ts | 24 +- .../components/categorical_legend.js | 48 --- .../properties/dynamic_color_property.js | 155 ++++----- .../properties/dynamic_color_property.test.js | 196 ++++++----- .../properties/dynamic_icon_property.js | 101 +++--- .../properties/dynamic_size_property.js | 73 +--- .../properties/dynamic_size_property.test.tsx | 102 ++++++ .../properties/dynamic_style_property.js | 47 +-- .../vector/properties/style_property.ts | 5 - .../classes/styles/vector/style_util.js | 15 + .../classes/styles/vector/style_util.test.js | 14 +- .../classes/styles/vector/vector_style.d.ts | 3 + x-pack/test/functional/apps/maps/joins.js | 21 +- 29 files changed, 960 insertions(+), 562 deletions(-) create mode 100644 x-pack/plugins/maps/public/classes/styles/vector/components/legend/_category.scss create mode 100644 x-pack/plugins/maps/public/classes/styles/vector/components/legend/_vector_legend.scss create mode 100644 x-pack/plugins/maps/public/classes/styles/vector/components/legend/breaked_legend.js rename x-pack/plugins/maps/public/classes/styles/vector/{properties/components => components/legend}/ordinal_legend.js (50%) create mode 100644 x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_size_property.test.tsx.snap delete mode 100644 x-pack/plugins/maps/public/classes/styles/vector/properties/components/categorical_legend.js create mode 100644 x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.test.tsx diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.d.ts b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.d.ts index 5e1ea68533f1..e420087628bc 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.d.ts +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.d.ts @@ -30,6 +30,7 @@ export interface IVectorLayer extends ILayer { getJoins(): IJoin[]; getValidJoins(): IJoin[]; getSource(): IVectorSource; + getStyle(): IVectorStyle; } export class VectorLayer extends AbstractLayer implements IVectorLayer { @@ -73,4 +74,5 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { _setMbPointsProperties(mbMap: unknown, mvtSourceLayer?: string): void; _setMbLinePolygonProperties(mbMap: unknown, mvtSourceLayer?: string): void; getSource(): IVectorSource; + getStyle(): IVectorStyle; } diff --git a/x-pack/plugins/maps/public/classes/styles/_index.scss b/x-pack/plugins/maps/public/classes/styles/_index.scss index a1c4c297a3ac..3ee713ffc1a0 100644 --- a/x-pack/plugins/maps/public/classes/styles/_index.scss +++ b/x-pack/plugins/maps/public/classes/styles/_index.scss @@ -2,3 +2,5 @@ @import 'vector/components/style_prop_editor'; @import 'vector/components/color/color_stops'; @import 'vector/components/symbol/icon_select'; +@import 'vector/components/legend/category'; +@import 'vector/components/legend/vector_legend'; diff --git a/x-pack/plugins/maps/public/classes/styles/color_utils.js b/x-pack/plugins/maps/public/classes/styles/color_utils.js index 2df743375a53..9dc79c006dff 100644 --- a/x-pack/plugins/maps/public/classes/styles/color_utils.js +++ b/x-pack/plugins/maps/public/classes/styles/color_utils.js @@ -11,7 +11,7 @@ import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; import { ColorGradient } from './components/color_gradient'; import { vislibColorMaps } from '../../../../../../src/plugins/charts/public'; -const GRADIENT_INTERVALS = 8; +export const GRADIENT_INTERVALS = 8; export const DEFAULT_FILL_COLORS = euiPaletteColorBlind(); export const DEFAULT_LINE_COLORS = [ @@ -73,7 +73,7 @@ export function getColorRampCenterColor(colorRampName) { // Returns an array of color stops // [ stop_input_1: number, stop_output_1: color, stop_input_n: number, stop_output_n: color ] -export function getOrdinalColorRampStops(colorRampName, min, max) { +export function getOrdinalMbColorRampStops(colorRampName, min, max, numberColors) { if (!colorRampName) { return null; } @@ -82,7 +82,7 @@ export function getOrdinalColorRampStops(colorRampName, min, max) { return null; } - const hexColors = getHexColorRangeStrings(colorRampName, GRADIENT_INTERVALS); + const hexColors = getHexColorRangeStrings(colorRampName, numberColors); if (max === min) { //just return single stop value return [max, hexColors[hexColors.length - 1]]; diff --git a/x-pack/plugins/maps/public/classes/styles/color_utils.test.js b/x-pack/plugins/maps/public/classes/styles/color_utils.test.js index 9a5ece01d520..ed7cafd53a6f 100644 --- a/x-pack/plugins/maps/public/classes/styles/color_utils.test.js +++ b/x-pack/plugins/maps/public/classes/styles/color_utils.test.js @@ -3,11 +3,10 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - import { COLOR_GRADIENTS, getColorRampCenterColor, - getOrdinalColorRampStops, + getOrdinalMbColorRampStops, getHexColorRangeStrings, getLinearGradient, getRGBColorRangeStrings, @@ -25,7 +24,7 @@ describe('COLOR_GRADIENTS', () => { describe('getRGBColorRangeStrings', () => { it('Should create RGB color ramp', () => { - expect(getRGBColorRangeStrings('Blues')).toEqual([ + expect(getRGBColorRangeStrings('Blues', 8)).toEqual([ 'rgb(247,250,255)', 'rgb(221,234,247)', 'rgb(197,218,238)', @@ -61,7 +60,7 @@ describe('getColorRampCenterColor', () => { describe('getColorRampStops', () => { it('Should create color stops for custom range', () => { - expect(getOrdinalColorRampStops('Blues', 0, 1000)).toEqual([ + expect(getOrdinalMbColorRampStops('Blues', 0, 1000, 8)).toEqual([ 0, '#f7faff', 125, @@ -82,7 +81,7 @@ describe('getColorRampStops', () => { }); it('Should snap to end of color stops for identical range', () => { - expect(getOrdinalColorRampStops('Blues', 23, 23)).toEqual([23, '#072f6b']); + expect(getOrdinalMbColorRampStops('Blues', 23, 23, 8)).toEqual([23, '#072f6b']); }); }); diff --git a/x-pack/plugins/maps/public/classes/styles/components/color_gradient.js b/x-pack/plugins/maps/public/classes/styles/components/color_gradient.js index 8772f33b76fd..bf7e88df3a69 100644 --- a/x-pack/plugins/maps/public/classes/styles/components/color_gradient.js +++ b/x-pack/plugins/maps/public/classes/styles/components/color_gradient.js @@ -5,7 +5,12 @@ */ import React from 'react'; -import { COLOR_RAMP_NAMES, getRGBColorRangeStrings, getLinearGradient } from '../color_utils'; +import { + COLOR_RAMP_NAMES, + GRADIENT_INTERVALS, + getRGBColorRangeStrings, + getLinearGradient, +} from '../color_utils'; import classNames from 'classnames'; export const ColorGradient = ({ colorRamp, colorRampName, className }) => { @@ -14,7 +19,9 @@ export const ColorGradient = ({ colorRamp, colorRampName, className }) => { } const classes = classNames('mapColorGradient', className); - const rgbColorStrings = colorRampName ? getRGBColorRangeStrings(colorRampName) : colorRamp; + const rgbColorStrings = colorRampName + ? getRGBColorRangeStrings(colorRampName, GRADIENT_INTERVALS) + : colorRamp; const background = getLinearGradient(rgbColorStrings); return
; }; diff --git a/x-pack/plugins/maps/public/classes/styles/components/ranged_style_legend_row.js b/x-pack/plugins/maps/public/classes/styles/components/ranged_style_legend_row.js index 3eb34ec1406d..4a43cc24e2c0 100644 --- a/x-pack/plugins/maps/public/classes/styles/components/ranged_style_legend_row.js +++ b/x-pack/plugins/maps/public/classes/styles/components/ranged_style_legend_row.js @@ -7,19 +7,12 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { EuiFlexGroup, EuiFlexItem, EuiText, EuiSpacer, EuiToolTip } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiText, EuiToolTip } from '@elastic/eui'; export function RangedStyleLegendRow({ header, minLabel, maxLabel, propertyLabel, fieldLabel }) { return (
- - {header} - - - {minLabel} - - @@ -29,6 +22,14 @@ export function RangedStyleLegendRow({ header, minLabel, maxLabel, propertyLabel + + {header} + + + + {minLabel} + + {maxLabel} diff --git a/x-pack/plugins/maps/public/classes/styles/heatmap/heatmap_style.js b/x-pack/plugins/maps/public/classes/styles/heatmap/heatmap_style.js index 1fa24943c5e5..3b5bcf591c2a 100644 --- a/x-pack/plugins/maps/public/classes/styles/heatmap/heatmap_style.js +++ b/x-pack/plugins/maps/public/classes/styles/heatmap/heatmap_style.js @@ -10,7 +10,7 @@ import { HeatmapStyleEditor } from './components/heatmap_style_editor'; import { HeatmapLegend } from './components/legend/heatmap_legend'; import { DEFAULT_HEATMAP_COLOR_RAMP_NAME } from './components/heatmap_constants'; import { LAYER_STYLE_TYPE, GRID_RESOLUTION } from '../../../../common/constants'; -import { getOrdinalColorRampStops } from '../color_utils'; +import { getOrdinalMbColorRampStops, GRADIENT_INTERVALS } from '../color_utils'; import { i18n } from '@kbn/i18n'; import { EuiIcon } from '@elastic/eui'; @@ -85,7 +85,12 @@ export class HeatmapStyle extends AbstractStyle { const { colorRampName } = this._descriptor; if (colorRampName && colorRampName !== DEFAULT_HEATMAP_COLOR_RAMP_NAME) { - const colorStops = getOrdinalColorRampStops(colorRampName, MIN_RANGE, MAX_RANGE); + const colorStops = getOrdinalMbColorRampStops( + colorRampName, + MIN_RANGE, + MAX_RANGE, + GRADIENT_INTERVALS + ); mbMap.setPaintProperty(layerId, 'heatmap-color', [ 'interpolate', ['linear'], diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/legend/_category.scss b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/_category.scss new file mode 100644 index 000000000000..aff843c3ed12 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/_category.scss @@ -0,0 +1,3 @@ +.mapLegendIconPreview { + width: $euiSizeL; +} diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/legend/_vector_legend.scss b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/_vector_legend.scss new file mode 100644 index 000000000000..d260f6effb2c --- /dev/null +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/_vector_legend.scss @@ -0,0 +1,5 @@ +.vectorStyleLegendSpacer { + &:not(:last-child) { + margin-bottom: $euiSizeS; + } +} diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/legend/breaked_legend.js b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/breaked_legend.js new file mode 100644 index 000000000000..7e8e6896ef9c --- /dev/null +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/breaked_legend.js @@ -0,0 +1,82 @@ +/* + * 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 React from 'react'; +import _ from 'lodash'; +import { EuiFlexGroup, EuiFlexItem, EuiText, EuiToolTip } from '@elastic/eui'; +import { Category } from './category'; +const EMPTY_VALUE = ''; + +export class BreakedLegend extends React.Component { + state = { + label: EMPTY_VALUE, + }; + + componentDidMount() { + this._isMounted = true; + this._loadParams(); + } + + componentDidUpdate() { + this._loadParams(); + } + + componentWillUnmount() { + this._isMounted = false; + } + + async _loadParams() { + const label = await this.props.style.getField().getLabel(); + const newState = { label }; + if (this._isMounted && !_.isEqual(this.state, newState)) { + this.setState(newState); + } + } + + render() { + if (this.state.label === EMPTY_VALUE) { + return null; + } + + const categories = this.props.breaks.map((brk, index) => { + return ( + + + + ); + }); + + return ( +
+ + + + + + {this.state.label} + + + + + + + {categories} + +
+ ); + } +} diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/legend/category.js b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/category.js index b0f397b6375a..cfdbd728c221 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/legend/category.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/category.js @@ -31,13 +31,13 @@ export function Category({ styleName, label, color, isLinesOnly, isPointsOnly, s } return ( - - - - {label} - - {renderIcon()} - - + + + {renderIcon()} + + + {label} + + ); } diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/components/ordinal_legend.js b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/ordinal_legend.js similarity index 50% rename from x-pack/plugins/maps/public/classes/styles/vector/properties/components/ordinal_legend.js rename to x-pack/plugins/maps/public/classes/styles/vector/components/legend/ordinal_legend.js index 1ebd04211848..478d96962e47 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/components/ordinal_legend.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/ordinal_legend.js @@ -4,9 +4,37 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { Fragment } from 'react'; import _ from 'lodash'; import { RangedStyleLegendRow } from '../../../components/ranged_style_legend_row'; +import { VECTOR_STYLES } from '../../../../../../common/constants'; +import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule } from '@elastic/eui'; +import { CircleIcon } from './circle_icon'; + +function getLineWidthIcons() { + const defaultStyle = { + stroke: 'grey', + fill: 'none', + width: '12px', + }; + return [ + , + , + , + ]; +} + +function getSymbolSizeIcons() { + const defaultStyle = { + stroke: 'grey', + fill: 'grey', + }; + return [ + , + , + , + ]; +} const EMPTY_VALUE = ''; export class OrdinalLegend extends React.Component { @@ -45,7 +73,46 @@ export class OrdinalLegend extends React.Component { this._isMounted = true; this._loadParams(); } + + _renderRangeLegendHeader() { + let icons; + if (this.props.style.getStyleName() === VECTOR_STYLES.LINE_WIDTH) { + icons = getLineWidthIcons(); + } else if (this.props.style.getStyleName() === VECTOR_STYLES.ICON_SIZE) { + icons = getSymbolSizeIcons(); + } else { + return null; + } + + return ( + + {icons.map((icon, index) => { + const isLast = index === icons.length - 1; + let spacer; + if (!isLast) { + spacer = ( + + + + ); + } + return ( + + {icon} + {spacer} + + ); + })} + + ); + } + render() { + const header = this._renderRangeLegendHeader(); + if (!header) { + return null; + } + const fieldMeta = this.props.style.getRangeFieldMeta(); let minLabel = EMPTY_VALUE; @@ -67,7 +134,7 @@ export class OrdinalLegend extends React.Component { return ( { - return ( - - {style.renderLegendDetailRow({ - isLinesOnly, - isPointsOnly, - symbolId, - })} - + const legendRows = []; + + for (let i = 0; i < styles.length; i++) { + const row = styles[i].renderLegendDetailRow({ + isLinesOnly, + isPointsOnly, + symbolId, + }); + + legendRows.push( +
+ {row} +
); - }); + } + + return legendRows; } diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_color_property.test.js.snap b/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_color_property.test.js.snap index ab47e88bb314..29eb52897a50 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_color_property.test.js.snap +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_color_property.test.js.snap @@ -1,50 +1,12 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Should render categorical legend with breaks from custom 1`] = `""`; +exports[`categorical Should render categorical legend with breaks from custom 1`] = `""`; -exports[`Should render categorical legend with breaks from default 1`] = ` +exports[`categorical Should render categorical legend with breaks from default 1`] = `
- - - - - - Other - - } - styleName="lineColor" - /> - -
-`; - -exports[`Should render ordinal legend 1`] = ` - - } - maxLabel="100_format" - minLabel="0_format" - propertyLabel="Border color" -/> -`; - -exports[`Should render ordinal legend with breaks 1`] = ` -
- - - + > + + + + + + + + Other + + } + styleName="lineColor" + /> + +
+`; + +exports[`ordinal Should render custom ordinal legend with breaks 1`] = ` +
+ + + + + +
+`; + +exports[`ordinal Should render only single band of last color when delta is 0 1`] = ` +
+ + + + + + + foobar_label + + + + + + + + + + + +
+`; + +exports[`ordinal Should render ordinal legend as bands 1`] = ` +
+ + + + + + + foobar_label + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
`; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_icon_property.test.tsx.snap b/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_icon_property.test.tsx.snap index 057907353d68..b4843324a0de 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_icon_property.test.tsx.snap +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_icon_property.test.tsx.snap @@ -2,50 +2,9 @@ exports[`Should render categorical legend with breaks 1`] = `
- - - - - - Other - - } - styleName="icon" - symbolId="square" - /> - + + + + + + + +
`; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_size_property.test.tsx.snap b/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_size_property.test.tsx.snap new file mode 100644 index 000000000000..11138bf33704 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_size_property.test.tsx.snap @@ -0,0 +1,73 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renderLegendDetailRow Should render as range 1`] = ` + + + + + + + + + + + + + + + + + + + + + + + + } + maxLabel="100_format" + minLabel="0_format" + propertyLabel="Symbol size" +/> +`; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/__tests__/test_util.ts b/x-pack/plugins/maps/public/classes/styles/vector/properties/__tests__/test_util.ts index 1c478bb85ccc..a8fba834d65a 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/__tests__/test_util.ts +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/__tests__/test_util.ts @@ -14,6 +14,7 @@ import { StyleMetaDescriptor, } from '../../../../../../common/descriptor_types'; import { AbstractField, IField } from '../../../../fields/field'; +import { IStyle, AbstractStyle } from '../../../style'; class MockField extends AbstractField { async getLabel(): Promise { @@ -29,14 +30,27 @@ export const mockField: IField = new MockField({ origin: FIELD_ORIGIN.SOURCE, }); -class MockStyle { +export class MockStyle extends AbstractStyle implements IStyle { + private readonly _min: number; + private readonly _max: number; + + constructor({ min = 0, max = 100 } = {}) { + super(null); + this._min = min; + this._max = max; + } + getStyleMeta(): StyleMeta { const geomTypes: GeometryTypes = { isPointsOnly: false, isLinesOnly: false, isPolygonsOnly: false, }; - const rangeFieldMeta: RangeFieldMeta = { min: 0, max: 100, delta: 100 }; + const rangeFieldMeta: RangeFieldMeta = { + min: this._min, + max: this._max, + delta: this._max - this._min, + }; const catFieldMeta: CategoryFieldMeta = { categories: [ { @@ -65,8 +79,12 @@ class MockStyle { } export class MockLayer { + private readonly _style: IStyle; + constructor(style = new MockStyle()) { + this._style = style; + } getStyle() { - return new MockStyle(); + return this._style; } getDataRequest() { diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/components/categorical_legend.js b/x-pack/plugins/maps/public/classes/styles/vector/properties/components/categorical_legend.js deleted file mode 100644 index a46492b6034a..000000000000 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/components/categorical_legend.js +++ /dev/null @@ -1,48 +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. - */ - -import React from 'react'; -import _ from 'lodash'; -const EMPTY_VALUE = ''; - -export class CategoricalLegend extends React.Component { - state = { - label: EMPTY_VALUE, - }; - - componentDidMount() { - this._isMounted = true; - this._loadParams(); - } - - componentDidUpdate() { - this._loadParams(); - } - - componentWillUnmount() { - this._isMounted = false; - } - - async _loadParams() { - const label = await this.props.style.getField().getLabel(); - const newState = { label }; - if (this._isMounted && !_.isEqual(this.state, newState)) { - this.setState(newState); - } - } - - render() { - if (this.state.label === EMPTY_VALUE) { - return null; - } - return this.props.style.renderBreakedLegend({ - fieldLabel: this.state.label, - isLinesOnly: this.props.isLinesOnly, - isPointsOnly: this.props.isPointsOnly, - symbolId: this.props.symbolId, - }); - } -} diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.js b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.js index 0afc784c482c..4c02dee762e9 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.js @@ -5,23 +5,24 @@ */ import { DynamicStyleProperty } from './dynamic_style_property'; -import { getOtherCategoryLabel, makeMbClampedNumberExpression } from '../style_util'; -import { getOrdinalColorRampStops, getColorPalette } from '../../color_utils'; -import { ColorGradient } from '../../components/color_gradient'; -import React from 'react'; +import { makeMbClampedNumberExpression, dynamicRound } from '../style_util'; import { - EuiFlexGroup, - EuiFlexItem, - EuiSpacer, - EuiText, - EuiToolTip, - EuiTextColor, -} from '@elastic/eui'; -import { Category } from '../components/legend/category'; -import { COLOR_MAP_TYPE, RGBA_0000 } from '../../../../../common/constants'; -import { isCategoricalStopsInvalid } from '../components/color/color_stops_utils'; + getOrdinalMbColorRampStops, + getColorPalette, + getHexColorRangeStrings, + GRADIENT_INTERVALS, +} from '../../color_utils'; +import React from 'react'; +import { COLOR_MAP_TYPE } from '../../../../../common/constants'; +import { + isCategoricalStopsInvalid, + getOtherCategoryLabel, +} from '../components/color/color_stops_utils'; +import { BreakedLegend } from '../components/legend/breaked_legend'; +import { EuiTextColor } from '@elastic/eui'; const EMPTY_STOPS = { stops: [], defaultColor: null }; +const RGBA_0000 = 'rgba(0,0,0,0)'; export class DynamicColorProperty extends DynamicStyleProperty { syncCircleColorWithMb(mbLayerId, mbMap, alpha) { @@ -99,14 +100,6 @@ export class DynamicColorProperty extends DynamicStyleProperty { return true; } - isOrdinalRanged() { - return this.isOrdinal() && !this._options.useCustomColorRamp; - } - - hasOrdinalBreaks() { - return (this.isOrdinal() && this._options.useCustomColorRamp) || this.isCategorical(); - } - _getMbColor() { if (!this._field || !this._field.getName()) { return null; @@ -142,10 +135,11 @@ export class DynamicColorProperty extends DynamicStyleProperty { return null; } - const colorStops = getOrdinalColorRampStops( + const colorStops = getOrdinalMbColorRampStops( this._options.color, rangeFieldMeta.min, - rangeFieldMeta.max + rangeFieldMeta.max, + GRADIENT_INTERVALS ); if (!colorStops) { return null; @@ -237,28 +231,47 @@ export class DynamicColorProperty extends DynamicStyleProperty { for (let i = 0; i < stops.length; i++) { const stop = stops[i]; const branch = `${stop.stop}`; - if (typeof branch === 'string') { - mbStops.push(branch); - mbStops.push(stop.color); - } + mbStops.push(branch); + mbStops.push(stop.color); } mbStops.push(defaultColor); //last color is default color return ['match', ['to-string', ['get', this._field.getName()]], ...mbStops]; } - renderRangeLegendHeader() { - if (this._options.color) { - return ; - } else { - return null; - } - } - _getColorRampStops() { - return this._options.useCustomColorRamp && this._options.customColorRamp - ? this._options.customColorRamp - : []; + if (this._options.useCustomColorRamp && this._options.customColorRamp) { + return this._options.customColorRamp; + } + + if (!this._options.color) { + return []; + } + + const rangeFieldMeta = this.getRangeFieldMeta(); + if (!rangeFieldMeta) { + return []; + } + + const colors = getHexColorRangeStrings(this._options.color, GRADIENT_INTERVALS); + + if (rangeFieldMeta.delta === 0) { + //map to last color. + return [ + { + color: colors[colors.length - 1], + stop: dynamicRound(rangeFieldMeta.max), + }, + ]; + } + + return colors.map((color, index) => { + const rawStopValue = rangeFieldMeta.min + rangeFieldMeta.delta * (index / GRADIENT_INTERVALS); + return { + color, + stop: dynamicRound(rawStopValue), + }; + }); } _getColorStops() { @@ -274,55 +287,33 @@ export class DynamicColorProperty extends DynamicStyleProperty { } } - renderBreakedLegend({ fieldLabel, isPointsOnly, isLinesOnly, symbolId }) { - const categories = []; + renderLegendDetailRow({ isPointsOnly, isLinesOnly, symbolId }) { const { stops, defaultColor } = this._getColorStops(); - stops.map(({ stop, color }) => { - categories.push( - - ); + const breaks = []; + stops.forEach(({ stop, color }) => { + if (stop) { + breaks.push({ + color, + symbolId, + label: this.formatField(stop), + }); + } }); - if (defaultColor) { - categories.push( - {getOtherCategoryLabel()}} - color={defaultColor} - isLinesOnly={isLinesOnly} - isPointsOnly={isPointsOnly} - symbolId={symbolId} - /> - ); + breaks.push({ + color: defaultColor, + label: {getOtherCategoryLabel()}, + symbolId, + }); } return ( -
- - - {categories} - - - - - - - {fieldLabel} - - - - - -
+ ); } } diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.test.js b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.test.js index afcdf1e3cfc5..1879b260da2e 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.test.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.test.js @@ -16,12 +16,18 @@ import { shallow } from 'enzyme'; import { DynamicColorProperty } from './dynamic_color_property'; import { COLOR_MAP_TYPE, VECTOR_STYLES } from '../../../../../common/constants'; -import { mockField, MockLayer } from './__tests__/test_util'; +import { mockField, MockLayer, MockStyle } from './__tests__/test_util'; -const makeProperty = (options, field = mockField) => { - return new DynamicColorProperty(options, VECTOR_STYLES.LINE_COLOR, field, new MockLayer(), () => { - return (x) => x + '_format'; - }); +const makeProperty = (options, mockStyle, field = mockField) => { + return new DynamicColorProperty( + options, + VECTOR_STYLES.LINE_COLOR, + field, + new MockLayer(mockStyle), + () => { + return (x) => x + '_format'; + } + ); }; const defaultLegendParams = { @@ -29,91 +35,121 @@ const defaultLegendParams = { isLinesOnly: false, }; -test('Should render ordinal legend', async () => { - const colorStyle = makeProperty({ - color: 'Blues', - type: undefined, +describe('ordinal', () => { + test('Should render ordinal legend as bands', async () => { + const colorStyle = makeProperty({ + color: 'Blues', + type: undefined, + }); + + const legendRow = colorStyle.renderLegendDetailRow(defaultLegendParams); + + 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(); }); - const legendRow = colorStyle.renderLegendDetailRow(defaultLegendParams); + test('Should render only single band of last color when delta is 0', async () => { + const colorStyle = makeProperty( + { + color: 'Blues', + type: undefined, + }, + new MockStyle({ min: 100, max: 100 }) + ); - const component = shallow(legendRow); + const legendRow = colorStyle.renderLegendDetailRow(defaultLegendParams); - expect(component).toMatchSnapshot(); + 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(); + }); + + test('Should render custom ordinal legend with breaks', async () => { + const colorStyle = makeProperty({ + type: COLOR_MAP_TYPE.ORDINAL, + useCustomColorRamp: true, + customColorRamp: [ + { + stop: 0, + color: '#FF0000', + }, + { + stop: 10, + color: '#00FF00', + }, + ], + }); + + const legendRow = colorStyle.renderLegendDetailRow(defaultLegendParams); + + 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(); + }); }); -test('Should render ordinal legend with breaks', async () => { - const colorStyle = makeProperty({ - type: COLOR_MAP_TYPE.ORDINAL, - useCustomColorRamp: true, - customColorRamp: [ - { - stop: 0, - color: '#FF0000', - }, - { - stop: 10, - color: '#00FF00', - }, - ], +describe('categorical', () => { + test('Should render categorical legend with breaks from default', async () => { + const colorStyle = makeProperty({ + type: COLOR_MAP_TYPE.CATEGORICAL, + useCustomColorPalette: false, + colorCategory: 'palette_0', + }); + + const legendRow = colorStyle.renderLegendDetailRow(defaultLegendParams); + + 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(); }); - const legendRow = colorStyle.renderLegendDetailRow(defaultLegendParams); + test('Should render categorical legend with breaks from custom', async () => { + const colorStyle = makeProperty({ + type: COLOR_MAP_TYPE.CATEGORICAL, + useCustomColorPalette: true, + customColorPalette: [ + { + stop: null, //should include the default stop + color: '#FFFF00', + }, + { + stop: 'US_STOP', + color: '#FF0000', + }, + { + stop: 'CN_STOP', + color: '#00FF00', + }, + ], + }); - const component = shallow(legendRow); + const legendRow = colorStyle.renderLegendDetailRow(defaultLegendParams); - // Ensure all promises resolve - await new Promise((resolve) => process.nextTick(resolve)); - // Ensure the state changes are reflected - component.update(); + const component = shallow(legendRow); - expect(component).toMatchSnapshot(); -}); - -test('Should render categorical legend with breaks from default', async () => { - const colorStyle = makeProperty({ - type: COLOR_MAP_TYPE.CATEGORICAL, - useCustomColorPalette: false, - colorCategory: 'palette_0', + expect(component).toMatchSnapshot(); }); - - const legendRow = colorStyle.renderLegendDetailRow(defaultLegendParams); - - 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(); -}); - -test('Should render categorical legend with breaks from custom', async () => { - const colorStyle = makeProperty({ - type: COLOR_MAP_TYPE.CATEGORICAL, - useCustomColorPalette: true, - customColorPalette: [ - { - stop: null, //should include the default stop - color: '#FFFF00', - }, - { - stop: 'US_STOP', - color: '#FF0000', - }, - { - stop: 'CN_STOP', - color: '#00FF00', - }, - ], - }); - - const legendRow = colorStyle.renderLegendDetailRow(defaultLegendParams); - - const component = shallow(legendRow); - - expect(component).toMatchSnapshot(); }); function makeFeatures(foobarPropValues) { @@ -201,7 +237,7 @@ describe('supportsFieldMeta', () => { const dynamicStyleOptions = { type: COLOR_MAP_TYPE.ORDINAL, }; - const styleProp = makeProperty(dynamicStyleOptions, field); + const styleProp = makeProperty(dynamicStyleOptions, undefined, field); expect(styleProp.supportsFieldMeta()).toEqual(false); }); @@ -210,7 +246,7 @@ describe('supportsFieldMeta', () => { const dynamicStyleOptions = { type: COLOR_MAP_TYPE.ORDINAL, }; - const styleProp = makeProperty(dynamicStyleOptions, null); + const styleProp = makeProperty(dynamicStyleOptions, undefined, null); expect(styleProp.supportsFieldMeta()).toEqual(false); }); diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.js b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.js index 27c4fca7d701..c7620512710d 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.js @@ -6,19 +6,11 @@ import _ from 'lodash'; import React from 'react'; -import { getOtherCategoryLabel, assignCategoriesToPalette } from '../style_util'; import { DynamicStyleProperty } from './dynamic_style_property'; import { getIconPalette, getMakiIconId, getMakiSymbolAnchor } from '../symbol_utils'; - -import { - EuiFlexGroup, - EuiFlexItem, - EuiSpacer, - EuiText, - EuiToolTip, - EuiTextColor, -} from '@elastic/eui'; -import { Category } from '../components/legend/category'; +import { BreakedLegend } from '../components/legend/breaked_legend'; +import { getOtherCategoryLabel, assignCategoriesToPalette } from '../style_util'; +import { EuiTextColor } from '@elastic/eui'; export class DynamicIconProperty extends DynamicStyleProperty { isOrdinal() { @@ -60,7 +52,7 @@ export class DynamicIconProperty extends DynamicStyleProperty { } return { - fallback: + fallbackSymbolId: this._options.customIconStops.length > 0 ? this._options.customIconStops[0].icon : null, stops, }; @@ -73,9 +65,9 @@ export class DynamicIconProperty extends DynamicStyleProperty { } _getMbIconImageExpression(iconPixelSize) { - const { stops, fallback } = this._getPaletteStops(); + const { stops, fallbackSymbolId } = this._getPaletteStops(); - if (stops.length < 1 || !fallback) { + if (stops.length < 1 || !fallbackSymbolId) { //occurs when no data return null; } @@ -85,14 +77,17 @@ export class DynamicIconProperty extends DynamicStyleProperty { mbStops.push(`${stop}`); mbStops.push(getMakiIconId(style, iconPixelSize)); }); - mbStops.push(getMakiIconId(fallback, iconPixelSize)); //last item is fallback style for anything that does not match provided stops + + if (fallbackSymbolId) { + mbStops.push(getMakiIconId(fallbackSymbolId, iconPixelSize)); //last item is fallback style for anything that does not match provided stops + } return ['match', ['to-string', ['get', this._field.getName()]], ...mbStops]; } _getMbIconAnchorExpression() { - const { stops, fallback } = this._getPaletteStops(); + const { stops, fallbackSymbolId } = this._getPaletteStops(); - if (stops.length < 1 || !fallback) { + if (stops.length < 1 || !fallbackSymbolId) { //occurs when no data return null; } @@ -102,7 +97,10 @@ export class DynamicIconProperty extends DynamicStyleProperty { mbStops.push(`${stop}`); mbStops.push(getMakiSymbolAnchor(style)); }); - mbStops.push(getMakiSymbolAnchor(fallback)); //last item is fallback style for anything that does not match provided stops + + if (fallbackSymbolId) { + mbStops.push(getMakiSymbolAnchor(fallbackSymbolId)); //last item is fallback style for anything that does not match provided stops + } return ['match', ['to-string', ['get', this._field.getName()]], ...mbStops]; } @@ -110,55 +108,34 @@ export class DynamicIconProperty extends DynamicStyleProperty { return this._field && this._field.isValid(); } - renderBreakedLegend({ fieldLabel, isPointsOnly, isLinesOnly }) { - const categories = []; - const { stops, fallback } = this._getPaletteStops(); - stops.map(({ stop, style }) => { - categories.push( - - ); + renderLegendDetailRow({ isPointsOnly, isLinesOnly }) { + const { stops, fallbackSymbolId } = this._getPaletteStops(); + const breaks = []; + stops.forEach(({ stop, style }) => { + if (stop) { + breaks.push({ + color: 'grey', + label: this.formatField(stop), + symbolId: style, + }); + } }); - if (fallback) { - categories.push( - {getOtherCategoryLabel()}} - color="grey" - isLinesOnly={isLinesOnly} - isPointsOnly={isPointsOnly} - symbolId={fallback} - /> - ); + if (fallbackSymbolId) { + breaks.push({ + color: 'grey', + label: {getOtherCategoryLabel()}, + symbolId: fallbackSymbolId, + }); } return ( -
- - - {categories} - - - - - - - {fieldLabel} - - - - - -
+ ); } } diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.js b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.js index 71ac25f0c6e6..898da439c44a 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.js @@ -5,6 +5,7 @@ */ import { DynamicStyleProperty } from './dynamic_style_property'; +import { OrdinalLegend } from '../components/legend/ordinal_legend'; import { makeMbClampedNumberExpression } from '../style_util'; import { HALF_LARGE_MAKI_ICON_SIZE, @@ -13,34 +14,7 @@ import { } from '../symbol_utils'; import { VECTOR_STYLES } from '../../../../../common/constants'; import _ from 'lodash'; -import { CircleIcon } from '../components/legend/circle_icon'; -import React, { Fragment } from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule } from '@elastic/eui'; - -function getLineWidthIcons() { - const defaultStyle = { - stroke: 'grey', - fill: 'none', - width: '12px', - }; - return [ - , - , - , - ]; -} - -function getSymbolSizeIcons() { - const defaultStyle = { - stroke: 'grey', - fill: 'grey', - }; - return [ - , - , - , - ]; -} +import React from 'react'; export class DynamicSizeProperty extends DynamicStyleProperty { constructor(options, styleName, field, vectorLayer, getFieldFormatter, isSymbolizedAsIcon) { @@ -99,13 +73,9 @@ export class DynamicSizeProperty extends DynamicStyleProperty { } } - syncCircleStrokeWidthWithMb(mbLayerId, mbMap, hasNoRadius) { - if (hasNoRadius) { - mbMap.setPaintProperty(mbLayerId, 'circle-stroke-width', 0); - } else { - const lineWidth = this.getMbSizeExpression(); - mbMap.setPaintProperty(mbLayerId, 'circle-stroke-width', lineWidth); - } + syncCircleStrokeWidthWithMb(mbLayerId, mbMap) { + const lineWidth = this.getMbSizeExpression(); + mbMap.setPaintProperty(mbLayerId, 'circle-stroke-width', lineWidth); } syncCircleRadiusWithMb(mbLayerId, mbMap) { @@ -166,36 +136,7 @@ export class DynamicSizeProperty extends DynamicStyleProperty { ); } - renderRangeLegendHeader() { - let icons; - if (this.getStyleName() === VECTOR_STYLES.LINE_WIDTH) { - icons = getLineWidthIcons(); - } else if (this.getStyleName() === VECTOR_STYLES.ICON_SIZE) { - icons = getSymbolSizeIcons(); - } else { - return null; - } - - return ( - - {icons.map((icon, index) => { - const isLast = index === icons.length - 1; - let spacer; - if (!isLast) { - spacer = ( - - - - ); - } - return ( - - {icon} - {spacer} - - ); - })} - - ); + renderLegendDetailRow() { + return ; } } diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.test.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.test.tsx new file mode 100644 index 000000000000..34f3e796f409 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.test.tsx @@ -0,0 +1,102 @@ +/* + * 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 { IVectorStyle } from '../vector_style'; + +jest.mock('ui/new_platform'); +jest.mock('../components/vector_style_editor', () => ({ + VectorStyleEditor: () => { + return
mockVectorStyleEditor
; + }, +})); + +import React from 'react'; +import { shallow } from 'enzyme'; + +// @ts-ignore +import { DynamicSizeProperty } from './dynamic_size_property'; +import { StyleMeta } from '../style_meta'; +import { FIELD_ORIGIN, VECTOR_STYLES } from '../../../../../common/constants'; +import { DataRequest } from '../../../util/data_request'; +import { IVectorLayer } from '../../../layers/vector_layer/vector_layer'; +import { IField } from '../../../fields/field'; + +// @ts-ignore +const mockField: IField = { + async getLabel() { + return 'foobar_label'; + }, + getName() { + return 'foobar'; + }, + getRootName() { + return 'foobar'; + }, + getOrigin() { + return FIELD_ORIGIN.SOURCE; + }, + supportsFieldMeta() { + return true; + }, + canValueBeFormatted() { + return true; + }, + async getDataType() { + return 'number'; + }, +}; + +// @ts-ignore +const mockLayer: IVectorLayer = { + getDataRequest(): DataRequest | undefined { + return undefined; + }, + getStyle(): IVectorStyle { + // @ts-ignore + return { + getStyleMeta(): StyleMeta { + return new StyleMeta({ + geometryTypes: { + isPointsOnly: true, + isLinesOnly: false, + isPolygonsOnly: false, + }, + fieldMeta: { + foobar: { + range: { min: 0, max: 100, delta: 100 }, + categories: { categories: [] }, + }, + }, + }); + }, + }; + }, +}; + +const makeProperty: DynamicSizeProperty = (options: object) => { + return new DynamicSizeProperty(options, VECTOR_STYLES.ICON_SIZE, mockField, mockLayer, () => { + return (x: string) => x + '_format'; + }); +}; + +const defaultLegendParams = { + isPointsOnly: true, + isLinesOnly: false, +}; + +describe('renderLegendDetailRow', () => { + test('Should render as range', async () => { + const sizeProp = makeProperty(); + const legendRow = sizeProp.renderLegendDetailRow(defaultLegendParams); + 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(); + }); +}); diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.js b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.js index 82645b3a2931..98d5d3feb60e 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.js @@ -9,8 +9,6 @@ import { AbstractStyleProperty } from './style_property'; import { DEFAULT_SIGMA } from '../vector_style_defaults'; import { STYLE_TYPE, SOURCE_META_ID_ORIGIN, FIELD_ORIGIN } from '../../../../../common/constants'; import React from 'react'; -import { OrdinalLegend } from './components/ordinal_legend'; -import { CategoricalLegend } from './components/categorical_legend'; import { OrdinalFieldMetaPopover } from '../components/field_meta/ordinal_field_meta_popover'; import { CategoricalFieldMetaPopover } from '../components/field_meta/categorical_field_meta_popover'; @@ -119,14 +117,6 @@ export class DynamicStyleProperty extends AbstractStyleProperty { return 0; } - hasOrdinalBreaks() { - return false; - } - - isOrdinalRanged() { - return true; - } - isComplete() { return !!this._field; } @@ -280,49 +270,14 @@ export class DynamicStyleProperty extends AbstractStyleProperty { } getNumericalMbFeatureStateValue(value) { - if (typeof value === 'number') { - return value; - } - const valueAsFloat = parseFloat(value); return isNaN(valueAsFloat) ? null : valueAsFloat; } - renderBreakedLegend() { + renderLegendDetailRow() { return null; } - _renderCategoricalLegend({ isPointsOnly, isLinesOnly, symbolId }) { - return ( - - ); - } - - _renderRangeLegend() { - return ; - } - - renderLegendDetailRow({ isPointsOnly, isLinesOnly, symbolId }) { - if (this.isOrdinal()) { - if (this.isOrdinalRanged()) { - return this._renderRangeLegend(); - } else if (this.hasOrdinalBreaks()) { - return this._renderCategoricalLegend({ isPointsOnly, isLinesOnly, symbolId }); - } else { - return null; - } - } else if (this.isCategorical()) { - return this._renderCategoricalLegend({ isPointsOnly, isLinesOnly, symbolId }); - } else { - return null; - } - } - renderFieldMetaPopover(onFieldMetaOptionsChange) { if (!this.supportsFieldMeta()) { return null; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/style_property.ts b/x-pack/plugins/maps/public/classes/styles/vector/properties/style_property.ts index af04a95e3c3b..b704e4bd5697 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/style_property.ts +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/style_property.ts @@ -23,7 +23,6 @@ export interface IStyleProperty { formatField(value: string | undefined): string; getStyleName(): VECTOR_STYLES; getOptions(): StylePropertyOptions; - renderRangeLegendHeader(): ReactElement | null; renderLegendDetailRow(legendProps: LegendProps): ReactElement | null; renderFieldMetaPopover( onFieldMetaOptionsChange: (fieldMetaOptions: FieldMetaOptions) => void @@ -67,10 +66,6 @@ export class AbstractStyleProperty implements IStyleProperty { return this._options || {}; } - renderRangeLegendHeader() { - return null; - } - renderLegendDetailRow() { return null; } diff --git a/x-pack/plugins/maps/public/classes/styles/vector/style_util.js b/x-pack/plugins/maps/public/classes/styles/vector/style_util.js index 082056846843..3b62dcb27dce 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/style_util.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/style_util.js @@ -34,6 +34,21 @@ export function isOnlySingleFeatureType(featureType, supportedFeatures, hasFeatu }, true); } +export function dynamicRound(value) { + if (typeof value !== 'number') { + return value; + } + + let precision = 0; + let threshold = 10; + while (value < threshold && precision < 8) { + precision++; + threshold = threshold / 10; + } + + return precision === 0 ? Math.round(value) : parseFloat(value.toFixed(precision + 1)); +} + export function assignCategoriesToPalette({ categories, paletteValues }) { const stops = []; let fallback = null; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/style_util.test.js b/x-pack/plugins/maps/public/classes/styles/vector/style_util.test.js index 76bbfc84e389..eb4c6708fb2d 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/style_util.test.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/style_util.test.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { isOnlySingleFeatureType, assignCategoriesToPalette } from './style_util'; +import { isOnlySingleFeatureType, assignCategoriesToPalette, dynamicRound } from './style_util'; import { VECTOR_SHAPE_TYPES } from '../../sources/vector_feature_types'; describe('isOnlySingleFeatureType', () => { @@ -100,3 +100,15 @@ describe('assignCategoriesToPalette', () => { }); }); }); + +describe('dynamicRound', () => { + test('Should truncate based on magnitude of number', () => { + expect(dynamicRound(1000.1234)).toBe(1000); + expect(dynamicRound(1.1234)).toBe(1.12); + expect(dynamicRound(0.0012345678)).toBe(0.00123); + }); + + test('Should return argument when not a number', () => { + expect(dynamicRound('foobar')).toBe('foobar'); + }); +}); diff --git a/x-pack/plugins/maps/public/classes/styles/vector/vector_style.d.ts b/x-pack/plugins/maps/public/classes/styles/vector/vector_style.d.ts index beea94394399..ea0736c4837d 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/vector_style.d.ts +++ b/x-pack/plugins/maps/public/classes/styles/vector/vector_style.d.ts @@ -12,11 +12,13 @@ import { VectorStyleDescriptor, VectorStylePropertiesDescriptor, } from '../../../../common/descriptor_types'; +import { StyleMeta } from './style_meta'; export interface IVectorStyle extends IStyle { getAllStyleProperties(): IStyleProperty[]; getDynamicPropertiesArray(): IDynamicStyleProperty[]; getSourceFieldNames(): string[]; + getStyleMeta(): StyleMeta; } export class VectorStyle extends AbstractStyle implements IVectorStyle { @@ -26,4 +28,5 @@ export class VectorStyle extends AbstractStyle implements IVectorStyle { getSourceFieldNames(): string[]; getAllStyleProperties(): IStyleProperty[]; getDynamicPropertiesArray(): IDynamicStyleProperty[]; + getStyleMeta(): StyleMeta; } diff --git a/x-pack/test/functional/apps/maps/joins.js b/x-pack/test/functional/apps/maps/joins.js index 2bf52153b7a3..7534a1b09cc2 100644 --- a/x-pack/test/functional/apps/maps/joins.js +++ b/x-pack/test/functional/apps/maps/joins.js @@ -58,11 +58,18 @@ export default function ({ getPageObjects, getService }) { const layerTOCDetails = await PageObjects.maps.getLayerTOCDetails('geo_shapes*'); const split = layerTOCDetails.trim().split('\n'); - const min = split[0]; - expect(min).to.equal('3'); + //field display name + expect(split[0]).to.equal('max prop1'); - const max = split[2]; - expect(max).to.equal('12'); + //bands 1-8 + expect(split[1]).to.equal('3'); + expect(split[2]).to.equal('4.13'); + expect(split[3]).to.equal('5.25'); + expect(split[4]).to.equal('6.38'); + expect(split[5]).to.equal('7.5'); + expect(split[6]).to.equal('8.63'); + expect(split[7]).to.equal('9.75'); + expect(split[8]).to.equal('11'); }); it('should decorate feature properties with join property', async () => { @@ -164,10 +171,10 @@ export default function ({ getPageObjects, getService }) { const split = layerTOCDetails.trim().split('\n'); const min = split[0]; - expect(min).to.equal('12'); + expect(min).to.equal('max prop1'); - const max = split[2]; - expect(max).to.equal('12'); + const max = split[1]; + expect(max).to.equal('12'); // just single band because single value }); it('should flag only the joined features as visible', async () => {