[Maps] Label style properties (#52957) (#53630)

* text styling

* label style editor UI

* wire up styles to mb

* allow string values

* remove console.log

* default getFields to provide ordinal fields for vector source

* fix vector_style jest test

* add label styles to docs

* fix prettier errors

* use index-pattern field formatter to format label

* rename LABEL to LABEL_TEXT

* review feedback

* fix problem with icons not displaying with labels

* fix functional tests

* fix canno read name of null error

* update jest expect

* fix eslint errors

* do not display label text in legend

* always show all label styling properties in editor

* review feedback
This commit is contained in:
Nathan Reese 2019-12-19 15:02:22 -07:00 committed by GitHub
parent c620d11d72
commit 2055347fc7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 554 additions and 105 deletions

View file

@ -8,32 +8,52 @@ Point, polygon, and line features support different styling properties.
[[point-style-properties]]
==== Point style properties
You can add text labels to your Point features by configuring label style properties.
[cols="2*"]
|===
|*Label*
|Specifies label content.
|*Label color*
|The text color.
|*Label size*
|The size of the text font, in pixels.
|===
You can symbolize Point features as *Circle markers* or *Icons*.
Use *Circle marker* to symbolize Points as circles.
*Fill color*:: The fill color of the point features.
*Border color*:: The border color of the point features.
*Border width*:: The border width of the point features.
*Symbol size*:: The radius of the symbol size, in pixels.
[cols="2*"]
|===
|*Border color*
|The border color of the point features.
|*Border width*
|The border width of the point features.
|*Fill color*
|The fill color of the point features.
|*Symbol size*
|The radius of the symbol size, in pixels.
|===
Use *Icon* to symbolize Points as icons.
*Fill color*:: The fill color of the point features.
[cols="2*"]
|===
|*Border color*
|The border color of the point features.
|*Border width*
|The border width of the point features.
|*Fill color*
|The fill color of the point features.
|*Symbol orientation*
|The symbol orientation rotating the icon clockwise.
|*Symbol size*
|The radius of the symbol size, in pixels.
|===
*Border color*:: The border color of the point features.
*Border width*:: The border width of the point features.
*Symbol orientation*:: The symbol orientation rotating the icon clockwise.
*Symbol size*:: The radius of the symbol size, in pixels.
+
Available icons
+
[role="screenshot"]
image::maps/images/maki-icons.png[]
@ -42,17 +62,25 @@ image::maps/images/maki-icons.png[]
[[polygon-style-properties]]
==== Polygon style properties
*Fill color*:: The fill color of the polygon features.
*Border color*:: The border color of the polygon features.
*Border width*:: The border width of the polygon features.
[cols="2*"]
|===
|*Border color*
|The border color of the polygon features.
|*Border width*
|The border width of the polygon features.
|*Fill color*
|The fill color of the polygon features.
|===
[float]
[[line-style-properties]]
==== Line style properties
*Border color*:: The color of the line features.
*Border width*:: The width of the line features.
[cols="2*"]
|===
|*Border color*
|The color of the line features.
|*Border width*
|The width of the line features.
|===

View file

@ -344,6 +344,10 @@ export class AbstractLayer {
return [];
}
async getFields() {
return [];
}
syncVisibilityWithMb(mbMap, mbLayerId) {
mbMap.setLayoutProperty(mbLayerId, 'visibility', this.isVisible() ? 'visible' : 'none');
}

View file

@ -124,6 +124,24 @@ export class ESSearchSource extends AbstractESSource {
}
}
async getFields() {
try {
const indexPattern = await this.getIndexPattern();
return indexPattern.fields
.filter(field => {
// Ensure fielddata is enabled for field.
// Search does not request _source
return field.aggregatable;
})
.map(field => {
return this.createField({ fieldName: field.name });
});
} catch (error) {
// failed index-pattern retrieval will show up as error-message in the layer-toc-entry
return [];
}
}
getFieldNames() {
return [this._descriptor.geoField];
}

View file

@ -103,6 +103,10 @@ export class AbstractVectorSource extends AbstractSource {
return [];
}
async getFields() {
return [...(await this.getDateFields()), ...(await this.getNumberFields())];
}
async getLeftJoinFields() {
return [];
}

View file

@ -12,7 +12,7 @@ import { FieldSelect, fieldShape } from '../field_select';
import { ColorRampSelect } from './color_ramp_select';
import { EuiSpacer } from '@elastic/eui';
export function DynamicColorSelection({ ordinalFields, onChange, styleOptions }) {
export function DynamicColorSelection({ fields, onChange, styleOptions }) {
const onFieldChange = ({ field }) => {
onChange({ ...styleOptions, field });
};
@ -32,7 +32,7 @@ export function DynamicColorSelection({ ordinalFields, onChange, styleOptions })
/>
<EuiSpacer size="s" />
<FieldSelect
fields={ordinalFields}
fields={fields}
selectedFieldName={_.get(styleOptions, 'field.name')}
onChange={onFieldChange}
compressed
@ -42,7 +42,7 @@ export function DynamicColorSelection({ ordinalFields, onChange, styleOptions })
}
DynamicColorSelection.propTypes = {
ordinalFields: PropTypes.arrayOf(fieldShape).isRequired,
fields: PropTypes.arrayOf(fieldShape).isRequired,
styleOptions: dynamicColorShape.isRequired,
onChange: PropTypes.func.isRequired,
};

View file

@ -13,7 +13,7 @@ import { StaticColorSelection } from './static_color_selection';
export function VectorStyleColorEditor(props) {
return (
<StaticDynamicStyleRow
ordinalFields={props.ordinalFields}
fields={props.fields}
styleProperty={props.styleProperty}
handlePropertyChange={props.handlePropertyChange}
swatches={props.swatches}

View file

@ -30,6 +30,18 @@ export function getVectorStyleLabel(styleName) {
return i18n.translate('xpack.maps.styles.vector.orientationLabel', {
defaultMessage: 'Symbol orientation',
});
case VECTOR_STYLES.LABEL_TEXT:
return i18n.translate('xpack.maps.styles.vector.labelLabel', {
defaultMessage: 'Label',
});
case VECTOR_STYLES.LABEL_COLOR:
return i18n.translate('xpack.maps.styles.vector.labelColorLabel', {
defaultMessage: 'Label color',
});
case VECTOR_STYLES.LABEL_SIZE:
return i18n.translate('xpack.maps.styles.vector.labelSizeLabel', {
defaultMessage: 'Label size',
});
default:
return styleName;
}

View file

@ -0,0 +1,24 @@
/*
* 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 _ from 'lodash';
import React from 'react';
import { FieldSelect } from '../field_select';
export function DynamicLabelSelector({ fields, styleOptions, onChange }) {
const onFieldChange = ({ field }) => {
onChange({ ...styleOptions, field });
};
return (
<FieldSelect
fields={fields}
selectedFieldName={_.get(styleOptions, 'field.name')}
onChange={onFieldChange}
compressed
/>
);
}

View file

@ -0,0 +1,28 @@
/*
* 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 { i18n } from '@kbn/i18n';
import { EuiFieldText } from '@elastic/eui';
export function StaticLabelSelector({ onChange, styleOptions }) {
const onValueChange = event => {
onChange({ value: event.target.value });
};
return (
<EuiFieldText
placeholder={i18n.translate('xpack.maps.styles.staticLabel.valuePlaceholder', {
defaultMessage: 'symbol label',
})}
value={styleOptions.value}
onChange={onValueChange}
aria-label={i18n.translate('xpack.maps.styles.staticLabel.valueAriaLabel', {
defaultMessage: 'symbol label',
})}
/>
);
}

View file

@ -0,0 +1,21 @@
/*
* 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 { StaticDynamicStyleRow } from '../static_dynamic_style_row';
import { DynamicLabelSelector } from './dynamic_label_selector';
import { StaticLabelSelector } from './static_label_selector';
export function VectorStyleLabelEditor(props) {
return (
<StaticDynamicStyleRow
{...props}
DynamicSelector={DynamicLabelSelector}
StaticSelector={StaticLabelSelector}
/>
);
}

View file

@ -10,14 +10,14 @@ import PropTypes from 'prop-types';
import { dynamicOrientationShape } from '../style_option_shapes';
import { FieldSelect, fieldShape } from '../field_select';
export function DynamicOrientationSelection({ ordinalFields, styleOptions, onChange }) {
export function DynamicOrientationSelection({ fields, styleOptions, onChange }) {
const onFieldChange = ({ field }) => {
onChange({ ...styleOptions, field });
};
return (
<FieldSelect
fields={ordinalFields}
fields={fields}
selectedFieldName={_.get(styleOptions, 'field.name')}
onChange={onFieldChange}
compressed
@ -26,7 +26,7 @@ export function DynamicOrientationSelection({ ordinalFields, styleOptions, onCha
}
DynamicOrientationSelection.propTypes = {
ordinalFields: PropTypes.arrayOf(fieldShape).isRequired,
fields: PropTypes.arrayOf(fieldShape).isRequired,
styleOptions: dynamicOrientationShape.isRequired,
onChange: PropTypes.func.isRequired,
};

View file

@ -13,7 +13,7 @@ import { StaticOrientationSelection } from './static_orientation_selection';
export function OrientationEditor(props) {
return (
<StaticDynamicStyleRow
ordinalFields={props.ordinalFields}
fields={props.fields}
styleProperty={props.styleProperty}
handlePropertyChange={props.handlePropertyChange}
DynamicSelector={DynamicOrientationSelection}

View file

@ -12,7 +12,7 @@ import { FieldSelect, fieldShape } from '../field_select';
import { SizeRangeSelector } from './size_range_selector';
import { EuiSpacer } from '@elastic/eui';
export function DynamicSizeSelection({ ordinalFields, styleOptions, onChange }) {
export function DynamicSizeSelection({ fields, styleOptions, onChange }) {
const onFieldChange = ({ field }) => {
onChange({ ...styleOptions, field });
};
@ -32,7 +32,7 @@ export function DynamicSizeSelection({ ordinalFields, styleOptions, onChange })
/>
<EuiSpacer size="s" />
<FieldSelect
fields={ordinalFields}
fields={fields}
selectedFieldName={_.get(styleOptions, 'field.name')}
onChange={onFieldChange}
compressed
@ -42,7 +42,7 @@ export function DynamicSizeSelection({ ordinalFields, styleOptions, onChange })
}
DynamicSizeSelection.propTypes = {
ordinalFields: PropTypes.arrayOf(fieldShape).isRequired,
fields: PropTypes.arrayOf(fieldShape).isRequired,
styleOptions: dynamicSizeShape.isRequired,
onChange: PropTypes.func.isRequired,
};

View file

@ -13,7 +13,7 @@ import { StaticSizeSelection } from './static_size_selection';
export function VectorStyleSizeEditor(props) {
return (
<StaticDynamicStyleRow
ordinalFields={props.ordinalFields}
fields={props.fields}
styleProperty={props.styleProperty}
handlePropertyChange={props.handlePropertyChange}
DynamicSelector={DynamicSizeSelection}

View file

@ -19,7 +19,7 @@ export class StaticDynamicStyleRow extends Component {
prevDynamicStyleOptions = this.props.defaultDynamicStyleOptions;
_canBeDynamic() {
return this.props.ordinalFields.length > 0;
return this.props.fields.length > 0;
}
_isDynamic() {
@ -78,7 +78,7 @@ export class StaticDynamicStyleRow extends Component {
return (
<Fragment>
<DynamicSelector
ordinalFields={this.props.ordinalFields}
fields={this.props.fields}
onChange={this._onDynamicStyleChange}
styleOptions={this._getStyleOptions()}
/>

View file

@ -11,6 +11,7 @@ import chrome from 'ui/chrome';
import { VectorStyleColorEditor } from './color/vector_style_color_editor';
import { VectorStyleSizeEditor } from './size/vector_style_size_editor';
import { VectorStyleSymbolEditor } from './vector_style_symbol_editor';
import { VectorStyleLabelEditor } from './label/vector_style_label_editor';
import { OrientationEditor } from './orientation/orientation_editor';
import { getDefaultDynamicProperties, getDefaultStaticProperties } from '../vector_style_defaults';
import { DEFAULT_FILL_COLORS, DEFAULT_LINE_COLORS } from '../../color_utils';
@ -25,6 +26,7 @@ export class VectorStyleEditor extends Component {
state = {
dateFields: [],
numberFields: [],
fields: [],
defaultDynamicProperties: getDefaultDynamicProperties(),
defaultStaticProperties: getDefaultStaticProperties(),
supportedFeatures: undefined,
@ -37,16 +39,16 @@ export class VectorStyleEditor extends Component {
componentDidMount() {
this._isMounted = true;
this._loadOrdinalFields();
this._loadFields();
this._loadSupportedFeatures();
}
componentDidUpdate() {
this._loadOrdinalFields();
this._loadFields();
this._loadSupportedFeatures();
}
async _loadOrdinalFields() {
async _loadFields() {
const getFieldMeta = async field => {
return {
label: await field.getLabel(),
@ -54,21 +56,27 @@ export class VectorStyleEditor extends Component {
origin: field.getOrigin(),
};
};
const dateFields = await this.props.layer.getDateFields();
const dateFieldPromises = dateFields.map(getFieldMeta);
const dateFieldsArray = await Promise.all(dateFieldPromises);
if (this._isMounted && !_.isEqual(dateFieldsArray, this.state.dateFields)) {
this.setState({ dateFields: dateFieldsArray });
}
const numberFields = await this.props.layer.getNumberFields();
const numberFieldPromises = numberFields.map(getFieldMeta);
const numberFieldsArray = await Promise.all(numberFieldPromises);
if (this._isMounted && !_.isEqual(numberFieldsArray, this.state.numberFields)) {
this.setState({ numberFields: numberFieldsArray });
}
const fields = await this.props.layer.getFields();
const fieldPromises = fields.map(getFieldMeta);
const fieldsArray = await Promise.all(fieldPromises);
if (this._isMounted && !_.isEqual(fieldsArray, this.state.fields)) {
this.setState({ fields: fieldsArray });
}
}
async _loadSupportedFeatures() {
@ -126,7 +134,7 @@ export class VectorStyleEditor extends Component {
swatches={DEFAULT_FILL_COLORS}
handlePropertyChange={this.props.handlePropertyChange}
styleProperty={this.props.styleProperties.fillColor}
ordinalFields={this._getOrdinalFields()}
fields={this._getOrdinalFields()}
defaultStaticStyleOptions={this.state.defaultStaticProperties.fillColor.options}
defaultDynamicStyleOptions={this.state.defaultDynamicProperties.fillColor.options}
/>
@ -139,7 +147,7 @@ export class VectorStyleEditor extends Component {
swatches={DEFAULT_LINE_COLORS}
handlePropertyChange={this.props.handlePropertyChange}
styleProperty={this.props.styleProperties.lineColor}
ordinalFields={this._getOrdinalFields()}
fields={this._getOrdinalFields()}
defaultStaticStyleOptions={this.state.defaultStaticProperties.lineColor.options}
defaultDynamicStyleOptions={this.state.defaultDynamicProperties.lineColor.options}
/>
@ -151,7 +159,7 @@ export class VectorStyleEditor extends Component {
<VectorStyleSizeEditor
handlePropertyChange={this.props.handlePropertyChange}
styleProperty={this.props.styleProperties.lineWidth}
ordinalFields={this._getOrdinalFields()}
fields={this._getOrdinalFields()}
defaultStaticStyleOptions={this.state.defaultStaticProperties.lineWidth.options}
defaultDynamicStyleOptions={this.state.defaultDynamicProperties.lineWidth.options}
/>
@ -163,27 +171,58 @@ export class VectorStyleEditor extends Component {
<VectorStyleSizeEditor
handlePropertyChange={this.props.handlePropertyChange}
styleProperty={this.props.styleProperties.iconSize}
ordinalFields={this._getOrdinalFields()}
fields={this._getOrdinalFields()}
defaultStaticStyleOptions={this.state.defaultStaticProperties.iconSize.options}
defaultDynamicStyleOptions={this.state.defaultDynamicProperties.iconSize.options}
/>
);
}
_renderLabelProperties() {
return (
<Fragment>
<VectorStyleLabelEditor
handlePropertyChange={this.props.handlePropertyChange}
styleProperty={this.props.styleProperties.labelText}
fields={this.state.fields}
defaultStaticStyleOptions={this.state.defaultStaticProperties.labelText.options}
defaultDynamicStyleOptions={this.state.defaultDynamicProperties.labelText.options}
/>
<EuiSpacer size="m" />
<VectorStyleColorEditor
swatches={DEFAULT_LINE_COLORS}
handlePropertyChange={this.props.handlePropertyChange}
styleProperty={this.props.styleProperties.labelColor}
fields={this._getOrdinalFields()}
defaultStaticStyleOptions={this.state.defaultStaticProperties.labelColor.options}
defaultDynamicStyleOptions={this.state.defaultDynamicProperties.labelColor.options}
/>
<EuiSpacer size="m" />
<VectorStyleSizeEditor
handlePropertyChange={this.props.handlePropertyChange}
styleProperty={this.props.styleProperties.labelSize}
fields={this._getOrdinalFields()}
defaultStaticStyleOptions={this.state.defaultStaticProperties.labelSize.options}
defaultDynamicStyleOptions={this.state.defaultDynamicProperties.labelSize.options}
/>
<EuiSpacer size="m" />
</Fragment>
);
}
_renderPointProperties() {
let iconOrientation;
if (this.props.symbolDescriptor.options.symbolizeAs === SYMBOLIZE_AS_ICON) {
iconOrientation = (
<Fragment>
<OrientationEditor
handlePropertyChange={this.props.handlePropertyChange}
styleProperty={this.props.styleProperties.iconOrientation}
ordinalFields={this.state.numberFields}
defaultStaticStyleOptions={this.state.defaultStaticProperties.iconOrientation.options}
defaultDynamicStyleOptions={this.state.defaultDynamicProperties.iconOrientation.options}
/>
<EuiSpacer size="m" />
</Fragment>
<OrientationEditor
handlePropertyChange={this.props.handlePropertyChange}
styleProperty={this.props.styleProperties.iconOrientation}
fields={this.state.numberFields}
defaultStaticStyleOptions={this.state.defaultStaticProperties.iconOrientation.options}
defaultDynamicStyleOptions={this.state.defaultDynamicProperties.iconOrientation.options}
/>
);
}
@ -207,8 +246,12 @@ export class VectorStyleEditor extends Component {
<EuiSpacer size="m" />
{iconOrientation}
<EuiSpacer size="m" />
{this._renderSymbolSize()}
<EuiSpacer size="m" />
{this._renderLabelProperties()}
</Fragment>
);
}

View file

@ -46,6 +46,12 @@ export class DynamicColorProperty extends DynamicStyleProperty {
mbMap.setPaintProperty(mbLayerId, 'line-opacity', alpha);
}
syncLabelColorWithMb(mbLayerId, mbMap, alpha) {
const color = this._getMbColor();
mbMap.setPaintProperty(mbLayerId, 'text-color', color);
mbMap.setPaintProperty(mbLayerId, 'text-opacity', alpha);
}
isCustomColorRamp() {
return this._options.useCustomColorRamp;
}

View file

@ -43,6 +43,10 @@ function getSymbolSizeIcons() {
}
export class DynamicSizeProperty extends DynamicStyleProperty {
supportsFeatureState() {
return this.getStyleName() !== VECTOR_STYLES.LABEL_SIZE;
}
syncHaloWidthWithMb(mbLayerId, mbMap) {
const haloWidth = this._getMbSize();
mbMap.setPaintProperty(mbLayerId, 'icon-halo-width', haloWidth);
@ -89,6 +93,11 @@ export class DynamicSizeProperty extends DynamicStyleProperty {
mbMap.setPaintProperty(mbLayerId, 'line-width', lineWidth);
}
syncLabelSizeWithMb(mbLayerId, mbMap) {
const lineWidth = this._getMbSize();
mbMap.setLayoutProperty(mbLayerId, 'text-size', lineWidth);
}
_getMbSize() {
if (this._isSizeDynamicConfigComplete(this._options)) {
return this._getMbDataDrivenSize({
@ -101,10 +110,11 @@ export class DynamicSizeProperty extends DynamicStyleProperty {
}
_getMbDataDrivenSize({ targetName, minSize, maxSize }) {
const lookup = this.supportsFeatureState() ? 'feature-state' : 'get';
return [
'interpolate',
['linear'],
['coalesce', ['feature-state', targetName], 0],
['coalesce', [lookup, targetName], 0],
0,
minSize,
1,

View file

@ -33,6 +33,10 @@ export class DynamicStyleProperty extends AbstractStyleProperty {
return true;
}
isOrdinal() {
return true;
}
isComplete() {
return !!this._field;
}

View file

@ -0,0 +1,39 @@
/*
* 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 { DynamicStyleProperty } from './dynamic_style_property';
import { getComputedFieldName } from '../style_util';
export class DynamicTextProperty extends DynamicStyleProperty {
syncTextFieldWithMb(mbLayerId, mbMap) {
if (this._field && this._field.isValid()) {
const targetName = getComputedFieldName(this._styleName, this._options.field.name);
mbMap.setLayoutProperty(mbLayerId, 'text-field', ['coalesce', ['get', targetName], '']);
} else {
mbMap.setLayoutProperty(mbLayerId, 'text-field', null);
}
}
isOrdinal() {
return false;
}
supportsFieldMeta() {
return false;
}
supportsFeatureState() {
return false;
}
isScaled() {
return false;
}
renderHeader() {
return null;
}
}

View file

@ -30,8 +30,13 @@ export class StaticColorProperty extends StaticStyleProperty {
mbMap.setPaintProperty(mbLayerId, 'line-opacity', alpha);
}
syncCircleStrokeWithMb(pointLayerId, mbMap, alpha) {
mbMap.setPaintProperty(pointLayerId, 'circle-stroke-color', this._options.color);
mbMap.setPaintProperty(pointLayerId, 'circle-stroke-opacity', alpha);
syncCircleStrokeWithMb(mbLayerId, mbMap, alpha) {
mbMap.setPaintProperty(mbLayerId, 'circle-stroke-color', this._options.color);
mbMap.setPaintProperty(mbLayerId, 'circle-stroke-opacity', alpha);
}
syncLabelColorWithMb(mbLayerId, mbMap, alpha) {
mbMap.setPaintProperty(mbLayerId, 'text-color', this._options.color);
mbMap.setPaintProperty(mbLayerId, 'text-opacity', alpha);
}
}

View file

@ -43,4 +43,8 @@ export class StaticSizeProperty extends StaticStyleProperty {
syncLineWidthWithMb(mbLayerId, mbMap) {
mbMap.setPaintProperty(mbLayerId, 'line-width', this._options.size);
}
syncLabelSizeWithMb(mbLayerId, mbMap) {
mbMap.setLayoutProperty(mbLayerId, 'text-size', this._options.size);
}
}

View file

@ -0,0 +1,21 @@
/*
* 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 { StaticStyleProperty } from './static_style_property';
export class StaticTextProperty extends StaticStyleProperty {
isComplete() {
return this.getOptions().value.length > 0;
}
syncTextFieldWithMb(mbLayerId, mbMap) {
if (this.getOptions().value.length) {
mbMap.setLayoutProperty(mbLayerId, 'text-field', this.getOptions().value);
} else {
mbMap.setLayoutProperty(mbLayerId, 'text-field', null);
}
}
}

View file

@ -36,6 +36,8 @@ import { StaticColorProperty } from './properties/static_color_property';
import { DynamicColorProperty } from './properties/dynamic_color_property';
import { StaticOrientationProperty } from './properties/static_orientation_property';
import { DynamicOrientationProperty } from './properties/dynamic_orientation_property';
import { StaticTextProperty } from './properties/static_text_property';
import { DynamicTextProperty } from './properties/dynamic_text_property';
const POINTS = [GEO_JSON_TYPE.POINT, GEO_JSON_TYPE.MULTI_POINT];
const LINES = [GEO_JSON_TYPE.LINE_STRING, GEO_JSON_TYPE.MULTI_LINE_STRING];
@ -85,6 +87,17 @@ export class VectorStyle extends AbstractStyle {
this._descriptor.properties[VECTOR_STYLES.ICON_ORIENTATION],
VECTOR_STYLES.ICON_ORIENTATION
);
this._labelStyleProperty = this._makeLabelProperty(
this._descriptor.properties[VECTOR_STYLES.LABEL_TEXT]
);
this._labelSizeStyleProperty = this._makeSizeProperty(
this._descriptor.properties[VECTOR_STYLES.LABEL_SIZE],
VECTOR_STYLES.LABEL_SIZE
);
this._labelColorStyleProperty = this._makeColorProperty(
this._descriptor.properties[VECTOR_STYLES.LABEL_COLOR],
VECTOR_STYLES.LABEL_COLOR
);
}
_getAllStyleProperties() {
@ -94,6 +107,9 @@ export class VectorStyle extends AbstractStyle {
this._lineWidthStyleProperty,
this._iconSizeStyleProperty,
this._iconOrientationProperty,
this._labelStyleProperty,
this._labelSizeStyleProperty,
this._labelColorStyleProperty,
];
}
@ -114,16 +130,15 @@ export class VectorStyle extends AbstractStyle {
return dynamicStyleProp.isFieldMetaEnabled();
});
const styleProperties = {};
this._getAllStyleProperties().forEach(styleProperty => {
styleProperties[styleProperty.getStyleName()] = styleProperty;
});
return (
<VectorStyleEditor
handlePropertyChange={handlePropertyChange}
styleProperties={{
lineColor: this._lineColorStyleProperty,
fillColor: this._fillColorStyleProperty,
lineWidth: this._lineWidthStyleProperty,
iconSize: this._iconSizeStyleProperty,
iconOrientation: this._iconOrientationProperty,
}}
styleProperties={styleProperties}
symbolDescriptor={this._descriptor.properties[VECTOR_STYLES.SYMBOL]}
layer={layer}
loadIsPointsOnly={this._getIsPointsOnly}
@ -391,12 +406,17 @@ export class VectorStyle extends AbstractStyle {
const isPolygonsOnly = await this._getIsPolygonsOnly();
return this.getDynamicPropertiesArray().filter(styleProperty => {
const styleName = styleProperty.getStyleName();
if ([VECTOR_STYLES.ICON_ORIENTATION, VECTOR_STYLES.LABEL_TEXT].includes(styleName)) {
return false;
}
if (isLinesOnly) {
return LINE_STYLES.includes(styleProperty.getStyleName());
return LINE_STYLES.includes(styleName);
}
if (isPolygonsOnly) {
return POLYGON_STYLES.includes(styleProperty.getStyleName());
return POLYGON_STYLES.includes(styleName);
}
return true;
@ -420,6 +440,7 @@ export class VectorStyle extends AbstractStyle {
// To work around this limitation, some styling values must fall back to geojson property values.
let supportsFeatureState;
let isScaled;
// TODO move first check into DynamicSizeProperty.supportsFeatureState
if (
styleProperty.getStyleName() === VECTOR_STYLES.ICON_SIZE &&
this._descriptor.properties.symbol.options.symbolizeAs === SYMBOLIZE_AS_ICON
@ -435,8 +456,10 @@ export class VectorStyle extends AbstractStyle {
return {
supportsFeatureState,
isScaled,
isOrdinal: styleProperty.isOrdinal(),
name: field.getName(),
meta: this._getFieldMeta(field.getName()),
formatter: this._getFieldFormatter(field.getName()),
computedName: getComputedFieldName(styleProperty.getStyleName(), field.getName()),
};
});
@ -455,6 +478,20 @@ export class VectorStyle extends AbstractStyle {
}
}
_getOrdinalValue(value, isScaled, range) {
const valueAsFloat = parseFloat(value);
if (isScaled) {
return scaleValue(valueAsFloat, range);
}
if (isNaN(valueAsFloat)) {
return 0;
}
return valueAsFloat;
}
setFeatureStateAndStyleProps(featureCollection, mbMap, mbSourceId) {
if (!featureCollection) {
return;
@ -479,20 +516,20 @@ export class VectorStyle extends AbstractStyle {
const {
supportsFeatureState,
isScaled,
isOrdinal,
name,
meta: range,
formatter,
computedName,
} = featureStateParams[j];
const value = parseFloat(feature.properties[name]);
let styleValue;
if (isScaled) {
styleValue = scaleValue(value, range);
if (isOrdinal) {
styleValue = this._getOrdinalValue(feature.properties[name], isScaled, range);
} else if (formatter) {
styleValue = formatter(feature.properties[name]);
} else {
if (isNaN(value)) {
styleValue = 0;
} else {
styleValue = value;
}
styleValue = feature.properties[name];
}
if (supportsFeatureState) {
@ -530,6 +567,14 @@ export class VectorStyle extends AbstractStyle {
this._iconSizeStyleProperty.syncCircleRadiusWithMb(pointLayerId, mbMap);
}
setMBPropertiesForLabelText({ alpha, mbMap, textLayerId }) {
mbMap.setLayoutProperty(textLayerId, 'icon-allow-overlap', true);
mbMap.setLayoutProperty(textLayerId, 'text-allow-overlap', true);
this._labelStyleProperty.syncTextFieldWithMb(textLayerId, mbMap);
this._labelColorStyleProperty.syncLabelColorWithMb(textLayerId, mbMap, alpha);
this._labelSizeStyleProperty.syncLabelSizeWithMb(textLayerId, mbMap);
}
setMBSymbolPropertiesForPoints({ mbMap, symbolLayerId, alpha }) {
const symbolId = this._descriptor.properties.symbol.options.symbolId;
mbMap.setLayoutProperty(symbolLayerId, 'icon-ignore-placement', true);
@ -619,4 +664,17 @@ export class VectorStyle extends AbstractStyle {
throw new Error(`${descriptor} not implemented`);
}
}
_makeLabelProperty(descriptor) {
if (!descriptor || !descriptor.options) {
return new StaticTextProperty({ value: '' }, VECTOR_STYLES.LABEL_TEXT);
} else if (descriptor.type === StaticStyleProperty.type) {
return new StaticTextProperty(descriptor.options, VECTOR_STYLES.LABEL_TEXT);
} else if (descriptor.type === DynamicStyleProperty.type) {
const field = this._makeField(descriptor.options.field);
return new DynamicTextProperty(descriptor.options, VECTOR_STYLES.LABEL_TEXT, field);
} else {
throw new Error(`${descriptor} not implemented`);
}
}
}

View file

@ -96,6 +96,24 @@ describe('getDescriptorWithMissingStylePropsRemoved', () => {
},
type: 'DYNAMIC',
},
labelText: {
options: {
value: '',
},
type: 'STATIC',
},
labelColor: {
options: {
color: '#000000',
},
type: 'STATIC',
},
labelSize: {
options: {
size: 14,
},
type: 'STATIC',
},
lineColor: {
options: {},
type: 'DYNAMIC',

View file

@ -7,11 +7,14 @@
import { VectorStyle } from './vector_style';
import { SYMBOLIZE_AS_CIRCLE, DEFAULT_ICON_SIZE } from './vector_constants';
import { COLOR_GRADIENTS, DEFAULT_FILL_COLORS, DEFAULT_LINE_COLORS } from '../color_utils';
import chrome from 'ui/chrome';
const DEFAULT_ICON = 'airfield';
export const MIN_SIZE = 1;
export const MAX_SIZE = 64;
export const DEFAULT_MIN_SIZE = 4;
export const DEFAULT_MAX_SIZE = 32;
export const DEFAULT_SIGMA = 3;
export const VECTOR_STYLES = {
@ -21,6 +24,9 @@ export const VECTOR_STYLES = {
LINE_WIDTH: 'lineWidth',
ICON_SIZE: 'iconSize',
ICON_ORIENTATION: 'iconOrientation',
LABEL_TEXT: 'labelText',
LABEL_COLOR: 'labelColor',
LABEL_SIZE: 'labelSize',
};
export const LINE_STYLES = [VECTOR_STYLES.LINE_COLOR, VECTOR_STYLES.LINE_WIDTH];
@ -49,6 +55,8 @@ export function getDefaultStaticProperties(mapColors = []) {
const nextFillColor = DEFAULT_FILL_COLORS[nextColorIndex];
const nextLineColor = DEFAULT_LINE_COLORS[nextColorIndex];
const isDarkMode = chrome.getUiSettingsClient().get('theme:darkMode', false);
return {
[VECTOR_STYLES.FILL_COLOR]: {
type: VectorStyle.STYLE_TYPE.STATIC,
@ -80,6 +88,24 @@ export function getDefaultStaticProperties(mapColors = []) {
orientation: 0,
},
},
[VECTOR_STYLES.LABEL_TEXT]: {
type: VectorStyle.STYLE_TYPE.STATIC,
options: {
value: '',
},
},
[VECTOR_STYLES.LABEL_COLOR]: {
type: VectorStyle.STYLE_TYPE.STATIC,
options: {
color: isDarkMode ? '#FFFFFF' : '#000000',
},
},
[VECTOR_STYLES.LABEL_SIZE]: {
type: VectorStyle.STYLE_TYPE.STATIC,
options: {
size: 14,
},
},
};
}
@ -122,8 +148,8 @@ export function getDefaultDynamicProperties() {
[VECTOR_STYLES.ICON_SIZE]: {
type: VectorStyle.STYLE_TYPE.DYNAMIC,
options: {
minSize: 4,
maxSize: 32,
minSize: DEFAULT_MIN_SIZE,
maxSize: DEFAULT_MAX_SIZE,
field: undefined,
fieldMetaOptions: {
isEnabled: true,
@ -141,5 +167,34 @@ export function getDefaultDynamicProperties() {
},
},
},
[VECTOR_STYLES.LABEL_TEXT]: {
type: VectorStyle.STYLE_TYPE.STATIC,
options: {
field: undefined,
},
},
[VECTOR_STYLES.LABEL_COLOR]: {
type: VectorStyle.STYLE_TYPE.STATIC,
options: {
color: COLOR_GRADIENTS[0].value,
field: undefined,
fieldMetaOptions: {
isEnabled: true,
sigma: DEFAULT_SIGMA,
},
},
},
[VECTOR_STYLES.LABEL_SIZE]: {
type: VectorStyle.STYLE_TYPE.STATIC,
options: {
minSize: DEFAULT_MIN_SIZE,
maxSize: DEFAULT_MAX_SIZE,
field: undefined,
fieldMetaOptions: {
isEnabled: true,
sigma: DEFAULT_SIGMA,
},
},
},
};
}

View file

@ -191,24 +191,33 @@ export class VectorLayer extends AbstractLayer {
return this._source.getDisplayName();
}
_getJoinFields() {
const joinFields = [];
this.getValidJoins().forEach(join => {
const fields = join.getJoinFields();
joinFields.push(...fields);
});
return joinFields;
}
async getDateFields() {
return await this._source.getDateFields();
}
async getNumberFields() {
const numberFieldOptions = await this._source.getNumberFields();
const joinFields = [];
this.getValidJoins().forEach(join => {
const fields = join.getJoinFields();
joinFields.push(...fields);
});
return [...numberFieldOptions, ...joinFields];
return [...numberFieldOptions, ...this._getJoinFields()];
}
async getOrdinalFields() {
return [...(await this.getDateFields()), ...(await this.getNumberFields())];
}
async getFields() {
const sourceFields = await this._source.getFields();
return [...sourceFields, ...this._getJoinFields()];
}
getIndexPatternIds() {
const indexPatternIds = this._source.getIndexPatternIds();
this.getValidJoins().forEach(join => {
@ -621,30 +630,40 @@ export class VectorLayer extends AbstractLayer {
const pointLayer = mbMap.getLayer(pointLayerId);
const symbolLayer = mbMap.getLayer(symbolLayerId);
let mbLayerId;
// Point layers symbolized as circles require 2 mapbox layers because
// "circle" layers do not support "text" style properties
// Point layers symbolized as icons only contain a single mapbox layer.
let markerLayerId;
let textLayerId;
if (this._style.arePointsSymbolizedAsCircles()) {
mbLayerId = pointLayerId;
markerLayerId = pointLayerId;
textLayerId = this._getMbTextLayerId();
if (symbolLayer) {
mbMap.setLayoutProperty(symbolLayerId, 'visibility', 'none');
}
this._setMbCircleProperties(mbMap);
} else {
mbLayerId = symbolLayerId;
markerLayerId = symbolLayerId;
textLayerId = symbolLayerId;
if (pointLayer) {
mbMap.setLayoutProperty(pointLayerId, 'visibility', 'none');
mbMap.setLayoutProperty(this._getMbTextLayerId(), 'visibility', 'none');
}
this._setMbSymbolProperties(mbMap);
}
this.syncVisibilityWithMb(mbMap, mbLayerId);
mbMap.setLayerZoomRange(mbLayerId, this._descriptor.minZoom, this._descriptor.maxZoom);
this.syncVisibilityWithMb(mbMap, markerLayerId);
mbMap.setLayerZoomRange(markerLayerId, this._descriptor.minZoom, this._descriptor.maxZoom);
if (markerLayerId !== textLayerId) {
this.syncVisibilityWithMb(mbMap, textLayerId);
mbMap.setLayerZoomRange(textLayerId, this._descriptor.minZoom, this._descriptor.maxZoom);
}
}
_setMbCircleProperties(mbMap) {
const sourceId = this.getId();
const pointLayerId = this._getMbPointLayerId();
const pointLayer = mbMap.getLayer(pointLayerId);
if (!pointLayer) {
mbMap.addLayer({
id: pointLayerId,
@ -654,15 +673,32 @@ export class VectorLayer extends AbstractLayer {
});
}
const textLayerId = this._getMbTextLayerId();
const textLayer = mbMap.getLayer(textLayerId);
if (!textLayer) {
mbMap.addLayer({
id: textLayerId,
type: 'symbol',
source: sourceId,
});
}
const filterExpr = getPointFilterExpression(this._hasJoins());
if (filterExpr !== mbMap.getFilter(pointLayerId)) {
mbMap.setFilter(pointLayerId, filterExpr);
mbMap.setFilter(textLayerId, filterExpr);
}
this._style.setMBPaintPropertiesForPoints({
alpha: this.getAlpha(),
mbMap,
pointLayerId: pointLayerId,
pointLayerId,
});
this._style.setMBPropertiesForLabelText({
alpha: this.getAlpha(),
mbMap,
textLayerId,
});
}
@ -687,7 +723,13 @@ export class VectorLayer extends AbstractLayer {
this._style.setMBSymbolPropertiesForPoints({
alpha: this.getAlpha(),
mbMap,
symbolLayerId: symbolLayerId,
symbolLayerId,
});
this._style.setMBPropertiesForLabelText({
alpha: this.getAlpha(),
mbMap,
textLayerId: symbolLayerId,
});
}
@ -759,6 +801,10 @@ export class VectorLayer extends AbstractLayer {
return this.makeMbLayerId('circle');
}
_getMbTextLayerId() {
return this.makeMbLayerId('text');
}
_getMbSymbolLayerId() {
return this.makeMbLayerId('symbol');
}
@ -774,6 +820,7 @@ export class VectorLayer extends AbstractLayer {
getMbLayerIds() {
return [
this._getMbPointLayerId(),
this._getMbTextLayerId(),
this._getMbSymbolLayerId(),
this._getMbLineLayerId(),
this._getMbPolygonLayerId(),
@ -781,12 +828,7 @@ export class VectorLayer extends AbstractLayer {
}
ownsMbLayerId(mbLayerId) {
return (
this._getMbPointLayerId() === mbLayerId ||
this._getMbLineLayerId() === mbLayerId ||
this._getMbPolygonLayerId() === mbLayerId ||
this._getMbSymbolLayerId() === mbLayerId
);
return this.getMbLayerIds().includes(mbLayerId);
}
ownsMbSourceId(mbSourceId) {

View file

@ -18,6 +18,9 @@ const EXPECTED_JOIN_VALUES = {
};
const VECTOR_SOURCE_ID = 'n1t6f';
const CIRCLE_STYLE_LAYER_INDEX = 0;
const FILL_STYLE_LAYER_INDEX = 2;
const LINE_STYLE_LAYER_INDEX = 3;
export default function({ getPageObjects, getService }) {
const PageObjects = getPageObjects(['maps']);
@ -82,19 +85,21 @@ export default function({ getPageObjects, getService }) {
const layersForVectorSource = mapboxStyle.layers.filter(mbLayer => {
return mbLayer.id.startsWith(VECTOR_SOURCE_ID);
});
// Color is dynamically obtained from eui source lib
const dynamicColor = layersForVectorSource[0].paint['circle-stroke-color'];
const dynamicColor =
layersForVectorSource[CIRCLE_STYLE_LAYER_INDEX].paint['circle-stroke-color'];
//circle layer for points
expect(layersForVectorSource[0]).to.eql(
expect(layersForVectorSource[CIRCLE_STYLE_LAYER_INDEX]).to.eql(
_.set(MAPBOX_STYLES.POINT_LAYER, 'paint.circle-stroke-color', dynamicColor)
);
//fill layer
expect(layersForVectorSource[1]).to.eql(MAPBOX_STYLES.FILL_LAYER);
expect(layersForVectorSource[FILL_STYLE_LAYER_INDEX]).to.eql(MAPBOX_STYLES.FILL_LAYER);
//line layer for borders
expect(layersForVectorSource[2]).to.eql(
expect(layersForVectorSource[LINE_STYLE_LAYER_INDEX]).to.eql(
_.set(MAPBOX_STYLES.LINE_LAYER, 'paint.line-color', dynamicColor)
);
});