[Maps] custom color ramp (#41603)
* [Maps] custom color ramp * round value down to find center color * do not update redux state with invalide color stops * rename EuiColorStop to ColorStop * remove untracked file * fix jest tests * review feedback * use steps instead of interpolate * add percy functional test to verify rendering of interpolate and step color expressions * add padding to color stop row so add/remove icons do not overlap color select
This commit is contained in:
parent
df72f91878
commit
c6335656d1
|
@ -1,2 +1,3 @@
|
|||
@import './components/color_gradient';
|
||||
@import './components/static_dynamic_style_row';
|
||||
@import './components/vector/color/color_stops';
|
||||
|
|
|
@ -49,7 +49,6 @@ export function getColorRampStops(colorRampName, numberColors = GRADIENT_INTERVA
|
|||
|
||||
export const COLOR_GRADIENTS = Object.keys(vislibColorMaps).map(colorRampName => ({
|
||||
value: colorRampName,
|
||||
text: colorRampName,
|
||||
inputDisplay: <ColorGradient colorRampName={colorRampName}/>
|
||||
}));
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@ describe('COLOR_GRADIENTS', () => {
|
|||
it('Should contain EuiSuperSelect options list of color ramps', () => {
|
||||
expect(COLOR_GRADIENTS.length).toBe(6);
|
||||
const colorGradientOption = COLOR_GRADIENTS[0];
|
||||
expect(colorGradientOption.text).toBe('Blues');
|
||||
expect(colorGradientOption.value).toBe('Blues');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -35,42 +35,36 @@ exports[`HeatmapStyleEditor is rendered 1`] = `
|
|||
"inputDisplay": <ColorGradient
|
||||
colorRampName="Blues"
|
||||
/>,
|
||||
"text": "Blues",
|
||||
"value": "Blues",
|
||||
},
|
||||
Object {
|
||||
"inputDisplay": <ColorGradient
|
||||
colorRampName="Greens"
|
||||
/>,
|
||||
"text": "Greens",
|
||||
"value": "Greens",
|
||||
},
|
||||
Object {
|
||||
"inputDisplay": <ColorGradient
|
||||
colorRampName="Greys"
|
||||
/>,
|
||||
"text": "Greys",
|
||||
"value": "Greys",
|
||||
},
|
||||
Object {
|
||||
"inputDisplay": <ColorGradient
|
||||
colorRampName="Reds"
|
||||
/>,
|
||||
"text": "Reds",
|
||||
"value": "Reds",
|
||||
},
|
||||
Object {
|
||||
"inputDisplay": <ColorGradient
|
||||
colorRampName="Yellow to Red"
|
||||
/>,
|
||||
"text": "Yellow to Red",
|
||||
"value": "Yellow to Red",
|
||||
},
|
||||
Object {
|
||||
"inputDisplay": <ColorGradient
|
||||
colorRampName="Green to Red"
|
||||
/>,
|
||||
"text": "Green to Red",
|
||||
"value": "Green to Red",
|
||||
},
|
||||
]
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
.mapColorStop {
|
||||
position: relative;
|
||||
padding-right: $euiSizeXL + $euiSizeXS;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
.mapColorStop__icons {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
display: block;
|
||||
animation: mapColorStopBecomeVisible $euiAnimSpeedFast $euiAnimSlightResistance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mapColorStop__icons {
|
||||
flex-shrink: 0;
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
margin-right: -$euiSizeS;
|
||||
margin-top: -$euiSizeM;
|
||||
}
|
||||
|
||||
@keyframes mapColorStopBecomeVisible {
|
||||
0% { opacity: 0; }
|
||||
100% { opacity: 1; }
|
||||
}
|
|
@ -4,29 +4,95 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { EuiSuperSelect } from '@elastic/eui';
|
||||
import { EuiSuperSelect, EuiSpacer } from '@elastic/eui';
|
||||
import { COLOR_GRADIENTS } from '../../../color_utils';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { ColorStops } from './color_stops';
|
||||
|
||||
export function ColorRampSelect({ color, onChange }) {
|
||||
const onColorRampChange = (selectedColorRampString) => {
|
||||
onChange({
|
||||
color: selectedColorRampString
|
||||
const CUSTOM_COLOR_RAMP = 'CUSTOM_COLOR_RAMP';
|
||||
|
||||
export class ColorRampSelect extends Component {
|
||||
|
||||
state = {};
|
||||
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
if (nextProps.customColorRamp !== prevState.prevPropsCustomColorRamp) {
|
||||
return {
|
||||
prevPropsCustomColorRamp: nextProps.customColorRamp, // reset tracker to latest value
|
||||
customColorRamp: nextProps.customColorRamp, // reset customColorRamp to latest value
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
_onColorRampSelect = (selectedValue) => {
|
||||
const useCustomColorRamp = selectedValue === CUSTOM_COLOR_RAMP;
|
||||
this.props.onChange({
|
||||
color: useCustomColorRamp ? null : selectedValue,
|
||||
useCustomColorRamp,
|
||||
});
|
||||
};
|
||||
return (
|
||||
<EuiSuperSelect
|
||||
options={COLOR_GRADIENTS}
|
||||
onChange={onColorRampChange}
|
||||
valueOfSelected={color}
|
||||
hasDividers={true}
|
||||
/>
|
||||
);
|
||||
|
||||
_onCustomColorRampChange = ({ colorStops, isInvalid }) => {
|
||||
// Manage invalid custom color ramp in local state
|
||||
if (isInvalid) {
|
||||
this.setState({ customColorRamp: colorStops });
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.onChange({
|
||||
customColorRamp: colorStops,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
let colorStopsInput;
|
||||
if (this.props.useCustomColorRamp) {
|
||||
colorStopsInput = (
|
||||
<Fragment>
|
||||
<EuiSpacer size="m" />
|
||||
<ColorStops
|
||||
colorStops={this.state.customColorRamp}
|
||||
onChange={this._onCustomColorRampChange}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
const colorRampOptions = [
|
||||
{
|
||||
value: CUSTOM_COLOR_RAMP,
|
||||
inputDisplay: (
|
||||
<FormattedMessage
|
||||
id="xpack.maps.style.customColorRampLabel"
|
||||
defaultMessage="Custom color ramp"
|
||||
/>
|
||||
)
|
||||
},
|
||||
...COLOR_GRADIENTS
|
||||
];
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiSuperSelect
|
||||
options={colorRampOptions}
|
||||
onChange={this._onColorRampSelect}
|
||||
valueOfSelected={this.props.useCustomColorRamp ? CUSTOM_COLOR_RAMP : this.props.color}
|
||||
hasDividers={true}
|
||||
/>
|
||||
{colorStopsInput}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ColorRampSelect.propTypes = {
|
||||
color: PropTypes.string.isRequired,
|
||||
color: PropTypes.string,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
useCustomColorRamp: PropTypes.bool,
|
||||
customColorRamp: PropTypes.array,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
* 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 PropTypes from 'prop-types';
|
||||
|
||||
import {
|
||||
EuiColorPicker,
|
||||
EuiFormRow,
|
||||
EuiFieldNumber,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiButtonIcon
|
||||
} from '@elastic/eui';
|
||||
import {
|
||||
addRow,
|
||||
removeRow,
|
||||
isColorInvalid,
|
||||
isStopInvalid,
|
||||
isInvalid,
|
||||
} from './color_stops_utils';
|
||||
|
||||
const DEFAULT_COLOR = '#FF0000';
|
||||
|
||||
export const ColorStops = ({
|
||||
colorStops = [{ stop: 0, color: DEFAULT_COLOR }],
|
||||
onChange,
|
||||
}) => {
|
||||
function getStopInput(stop, index) {
|
||||
const onStopChange = e => {
|
||||
const newColorStops = _.cloneDeep(colorStops);
|
||||
const sanitizedValue = parseFloat(e.target.value);
|
||||
newColorStops[index].stop = isNaN(sanitizedValue) ? '' : sanitizedValue;
|
||||
onChange({
|
||||
colorStops: newColorStops,
|
||||
isInvalid: isInvalid(newColorStops),
|
||||
});
|
||||
};
|
||||
|
||||
let error;
|
||||
if (isStopInvalid(stop)) {
|
||||
error = 'Stop must be a number';
|
||||
} else if (index !== 0 && colorStops[index - 1].stop >= stop) {
|
||||
error = 'Stop must be greater than previous stop value';
|
||||
}
|
||||
|
||||
return {
|
||||
stopError: error,
|
||||
stopInput: (
|
||||
<EuiFieldNumber
|
||||
aria-label="Stop"
|
||||
value={stop}
|
||||
onChange={onStopChange}
|
||||
/>
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
function getColorInput(color, index) {
|
||||
const onColorChange = color => {
|
||||
const newColorStops = _.cloneDeep(colorStops);
|
||||
newColorStops[index].color = color;
|
||||
onChange({
|
||||
colorStops: newColorStops,
|
||||
isInvalid: isInvalid(newColorStops),
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
colorError: isColorInvalid(color)
|
||||
? 'Color must provide a valid hex value'
|
||||
: undefined,
|
||||
colorInput: <EuiColorPicker onChange={onColorChange} color={color} />,
|
||||
};
|
||||
}
|
||||
|
||||
const rows = colorStops.map((colorStop, index) => {
|
||||
const { stopError, stopInput } = getStopInput(colorStop.stop, index);
|
||||
const { colorError, colorInput } = getColorInput(colorStop.color, index);
|
||||
const errors = [];
|
||||
if (stopError) {
|
||||
errors.push(stopError);
|
||||
}
|
||||
if (colorError) {
|
||||
errors.push(colorError);
|
||||
}
|
||||
|
||||
const onRemove = () => {
|
||||
const newColorStops = removeRow(colorStops, index);
|
||||
onChange({
|
||||
colorStops: newColorStops,
|
||||
isInvalid: isInvalid(newColorStops),
|
||||
});
|
||||
};
|
||||
|
||||
const onAdd = () => {
|
||||
const newColorStops = addRow(colorStops, index);
|
||||
|
||||
onChange({
|
||||
colorStops: newColorStops,
|
||||
isInvalid: isInvalid(newColorStops),
|
||||
});
|
||||
};
|
||||
|
||||
let deleteButton;
|
||||
if (colorStops.length > 1) {
|
||||
deleteButton = (
|
||||
<EuiButtonIcon
|
||||
iconType="trash"
|
||||
color="danger"
|
||||
aria-label="Delete"
|
||||
title="Delete"
|
||||
onClick={onRemove}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiFormRow
|
||||
key={index}
|
||||
className="mapColorStop"
|
||||
isInvalid={errors.length !== 0}
|
||||
error={errors}
|
||||
>
|
||||
<div>
|
||||
<EuiFlexGroup responsive={false} alignItems="center" gutterSize="s">
|
||||
<EuiFlexItem>{stopInput}</EuiFlexItem>
|
||||
<EuiFlexItem>{colorInput}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<div className="mapColorStop__icons">
|
||||
{deleteButton}
|
||||
<EuiButtonIcon
|
||||
iconType="plusInCircle"
|
||||
color="primary"
|
||||
aria-label="Add"
|
||||
title="Add"
|
||||
onClick={onAdd}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</EuiFormRow>
|
||||
);
|
||||
});
|
||||
|
||||
return <div>{rows}</div>;
|
||||
};
|
||||
|
||||
ColorStops.propTypes = {
|
||||
/**
|
||||
* Array of { stop, color }.
|
||||
* Stops are numbers in strictly ascending order.
|
||||
* The range is from the given stop number (inclusive) to the next stop number (exclusive).
|
||||
* Colors are color hex strings (3 or 6 character).
|
||||
*/
|
||||
colorStops: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
stopKey: PropTypes.number,
|
||||
color: PropTypes.string,
|
||||
})
|
||||
),
|
||||
/**
|
||||
* Callback for when the color stops changes. Called with { colorStops, isInvalid }
|
||||
*/
|
||||
onChange: PropTypes.func.isRequired,
|
||||
};
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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 { isValidHex } from '@elastic/eui';
|
||||
|
||||
export function removeRow(colorStops, index) {
|
||||
if (colorStops.length === 1) {
|
||||
return colorStops;
|
||||
}
|
||||
|
||||
return [...colorStops.slice(0, index), ...colorStops.slice(index + 1)];
|
||||
}
|
||||
|
||||
export function addRow(colorStops, index) {
|
||||
const currentStop = colorStops[index].stop;
|
||||
let delta = 1;
|
||||
if (index === colorStops.length - 1) {
|
||||
// Adding row to end of list.
|
||||
if (index !== 0) {
|
||||
const prevStop = colorStops[index - 1].stop;
|
||||
delta = currentStop - prevStop;
|
||||
}
|
||||
} else {
|
||||
// Adding row in middle of list.
|
||||
const nextStop = colorStops[index + 1].stop;
|
||||
delta = (nextStop - currentStop) / 2;
|
||||
}
|
||||
|
||||
const newRow = {
|
||||
stop: currentStop + delta,
|
||||
color: '#FF0000',
|
||||
};
|
||||
return [
|
||||
...colorStops.slice(0, index + 1),
|
||||
newRow,
|
||||
...colorStops.slice(index + 1),
|
||||
];
|
||||
}
|
||||
|
||||
export function isColorInvalid(color) {
|
||||
return !isValidHex(color) || color === '';
|
||||
}
|
||||
|
||||
export function isStopInvalid(stop) {
|
||||
return stop === '' || isNaN(stop);
|
||||
}
|
||||
|
||||
export function isInvalid(colorStops) {
|
||||
return colorStops.some((colorStop, index) => {
|
||||
// expect stops to be in ascending order
|
||||
let isDescending = false;
|
||||
if (index !== 0) {
|
||||
const prevStop = colorStops[index - 1].stop;
|
||||
isDescending = prevStop >= colorStop.stop;
|
||||
}
|
||||
|
||||
return (
|
||||
isColorInvalid(colorStop.color) ||
|
||||
isStopInvalid(colorStop.stop) ||
|
||||
isDescending
|
||||
);
|
||||
});
|
||||
}
|
|
@ -17,8 +17,8 @@ export function DynamicColorSelection({ ordinalFields, onChange, styleOptions })
|
|||
onChange({ ...styleOptions, field });
|
||||
};
|
||||
|
||||
const onColorChange = ({ color }) => {
|
||||
onChange({ ...styleOptions, color });
|
||||
const onColorChange = colorOptions => {
|
||||
onChange({ ...styleOptions, ...colorOptions });
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -26,6 +26,8 @@ export function DynamicColorSelection({ ordinalFields, onChange, styleOptions })
|
|||
<ColorRampSelect
|
||||
onChange={onColorChange}
|
||||
color={styleOptions.color}
|
||||
customColorRamp={styleOptions.customColorRamp}
|
||||
useCustomColorRamp={_.get(styleOptions, 'useCustomColorRamp', false)}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
<FieldSelect
|
||||
|
|
|
@ -97,6 +97,17 @@ function extractColorFromStyleProperty(colorStyleProperty, defaultColor) {
|
|||
}
|
||||
|
||||
// return middle of gradient for dynamic style property
|
||||
|
||||
if (colorStyleProperty.options.useCustomColorRamp) {
|
||||
if (!colorStyleProperty.options.customColorRamp ||
|
||||
!colorStyleProperty.options.customColorRamp.length) {
|
||||
return defaultColor;
|
||||
}
|
||||
// favor the lowest color in even arrays
|
||||
const middleIndex = Math.floor((colorStyleProperty.options.customColorRamp.length - 1) / 2);
|
||||
return colorStyleProperty.options.customColorRamp[middleIndex].color;
|
||||
}
|
||||
|
||||
return getColorRampCenterColor(colorStyleProperty.options.color);
|
||||
}
|
||||
|
||||
|
|
|
@ -12,8 +12,10 @@ export const staticColorShape = PropTypes.shape({
|
|||
});
|
||||
|
||||
export const dynamicColorShape = PropTypes.shape({
|
||||
color: PropTypes.string.isRequired,
|
||||
color: PropTypes.string,
|
||||
field: fieldShape,
|
||||
customColorRamp: PropTypes.array,
|
||||
useCustomColorRamp: PropTypes.bool,
|
||||
});
|
||||
|
||||
export const staticOrientationShape = PropTypes.shape({
|
||||
|
|
|
@ -326,14 +326,22 @@ export class VectorStyle extends AbstractStyle {
|
|||
|
||||
// "feature-state" data expressions are not supported with layout properties.
|
||||
// To work around this limitation, some styling values must fall back to geojson property values.
|
||||
let supportsFeatureState = true;
|
||||
let isScaled = true;
|
||||
let supportsFeatureState;
|
||||
let isScaled;
|
||||
if (styleName === 'iconSize'
|
||||
&& this._descriptor.properties.symbol.options.symbolizeAs === SYMBOLIZE_AS_ICON) {
|
||||
supportsFeatureState = false;
|
||||
isScaled = true;
|
||||
} else if (styleName === 'iconOrientation') {
|
||||
supportsFeatureState = false;
|
||||
isScaled = false;
|
||||
} else if ((styleName === 'fillColor' || styleName === 'lineColor')
|
||||
&& options.useCustomColorRamp) {
|
||||
supportsFeatureState = true;
|
||||
isScaled = false;
|
||||
} else {
|
||||
supportsFeatureState = true;
|
||||
isScaled = true;
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -417,9 +425,20 @@ export class VectorStyle extends AbstractStyle {
|
|||
return hasGeoJsonProperties;
|
||||
}
|
||||
|
||||
_getMBDataDrivenColor({ fieldName, color }) {
|
||||
const colorStops = getColorRampStops(color);
|
||||
_getMBDataDrivenColor({ fieldName, colorStops, isSteps }) {
|
||||
const targetName = VectorStyle.getComputedFieldName(fieldName);
|
||||
|
||||
if (isSteps) {
|
||||
const firstStopValue = colorStops[0];
|
||||
const lessThenFirstStopValue = firstStopValue - 1;
|
||||
return [
|
||||
'step',
|
||||
['coalesce', ['feature-state', targetName], lessThenFirstStopValue],
|
||||
'rgba(0,0,0,0)', // MB will assign the base value to any features that is below the first stop value
|
||||
...colorStops
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'interpolate',
|
||||
['linear'],
|
||||
|
@ -448,14 +467,31 @@ export class VectorStyle extends AbstractStyle {
|
|||
|
||||
const isDynamicConfigComplete = _.has(styleDescriptor, 'options.field.name')
|
||||
&& _.has(styleDescriptor, 'options.color');
|
||||
if (isDynamicConfigComplete) {
|
||||
return this._getMBDataDrivenColor({
|
||||
fieldName: styleDescriptor.options.field.name,
|
||||
color: styleDescriptor.options.color,
|
||||
});
|
||||
if (!isDynamicConfigComplete) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
if (styleDescriptor.options.useCustomColorRamp &&
|
||||
(!styleDescriptor.options.customColorRamp ||
|
||||
!styleDescriptor.options.customColorRamp.length)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this._getMBDataDrivenColor({
|
||||
fieldName: styleDescriptor.options.field.name,
|
||||
colorStops: this._getMBColorStops(styleDescriptor),
|
||||
isSteps: styleDescriptor.options.useCustomColorRamp,
|
||||
});
|
||||
}
|
||||
|
||||
_getMBColorStops(styleDescriptor) {
|
||||
if (styleDescriptor.options.useCustomColorRamp) {
|
||||
return styleDescriptor.options.customColorRamp.reduce((accumulatedStops, nextStop) => {
|
||||
return [...accumulatedStops, nextStop.stop, nextStop.color];
|
||||
}, []);
|
||||
}
|
||||
|
||||
return getColorRampStops(styleDescriptor.options.color);
|
||||
}
|
||||
|
||||
_isSizeDynamicConfigComplete(styleDescriptor) {
|
||||
|
|
|
@ -27,7 +27,8 @@
|
|||
],
|
||||
"type": "polygon"
|
||||
},
|
||||
"name": "alpha"
|
||||
"name": "alpha",
|
||||
"prop1": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -61,7 +62,8 @@
|
|||
],
|
||||
"type": "polygon"
|
||||
},
|
||||
"name": "bravo"
|
||||
"name": "bravo",
|
||||
"prop1": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -95,7 +97,8 @@
|
|||
],
|
||||
"type": "polygon"
|
||||
},
|
||||
"name": "charlie"
|
||||
"name": "charlie",
|
||||
"prop1": 3
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -127,7 +130,8 @@
|
|||
],
|
||||
"type": "linestring"
|
||||
},
|
||||
"name": "tango"
|
||||
"name": "tango",
|
||||
"prop1": 4
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,9 @@
|
|||
},
|
||||
"name": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"prop1": {
|
||||
"type": "byte"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
"index": ".kibana",
|
||||
"source": {
|
||||
"index-pattern": {
|
||||
"fields": "[{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"geometry\",\"type\":\"geo_shape\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]",
|
||||
"fields" : "[{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"esTypes\":[\"_type\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"geometry\",\"type\":\"geo_shape\",\"esTypes\":[\"geo_shape\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"prop1\",\"type\":\"number\",\"esTypes\":[\"byte\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]",
|
||||
"title": "geo_shapes*"
|
||||
},
|
||||
"type": "index-pattern"
|
||||
|
@ -556,6 +556,67 @@
|
|||
}
|
||||
}
|
||||
|
||||
{
|
||||
"type": "doc",
|
||||
"value": {
|
||||
"id": "map:4cd3e220-bf64-11e9-bbcc-7db09a1519e9",
|
||||
"index": ".kibana",
|
||||
"source": {
|
||||
"map": {
|
||||
"title" : "join and dynamic coloring demo",
|
||||
"description" : "",
|
||||
"mapStateJSON" : "{\"zoom\":3.42,\"center\":{\"lon\":81.67747,\"lat\":1.80586},\"timeFilters\":{\"from\":\"now-17m\",\"to\":\"now\",\"mode\":\"quick\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000},\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filters\":[]}",
|
||||
"layerListJSON" : "[{\"id\":\"n1t6f\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"62eca1fc-fe42-11e8-8eb2-f2801f1b9fd1\",\"type\":\"ES_SEARCH\",\"geoField\":\"geometry\",\"limit\":2048,\"filterByMapBounds\":false,\"showTooltip\":true,\"tooltipProperties\":[\"name\"],\"indexPatternRefName\":\"layer_0_source_index_pattern\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"max(prop1) group by meta_for_geo_shapes*.shape_name\",\"name\":\"__kbnjoin__max_of_prop1_groupby_meta_for_geo_shapes*.shape_name\",\"origin\":\"join\"},\"color\":null,\"useCustomColorRamp\":true,\"customColorRamp\":[{\"stop\":0,\"color\":\"#E6C220\"},{\"stop\":5,\"color\":\"#F98510\"},{\"stop\":11,\"color\":\"#DB1374\"}]}},\"lineColor\":{\"type\":\"DYNAMIC\",\"options\":{\"color\":\"Blues\",\"field\":{\"label\":\"prop1\",\"name\":\"prop1\",\"origin\":\"source\"},\"useCustomColorRamp\":false}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":10}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}},\"iconOrientation\":{\"type\":\"STATIC\",\"options\":{\"orientation\":0}},\"symbol\":{\"options\":{\"symbolizeAs\":\"circle\",\"symbolId\":\"airfield\"}}}},\"type\":\"VECTOR\",\"joins\":[{\"leftField\":\"name\",\"right\":{\"id\":\"855ccb86-fe42-11e8-8eb2-f2801f1b9fd1\",\"indexPatternTitle\":\"meta_for_geo_shapes*\",\"term\":\"shape_name\",\"metrics\":[{\"type\":\"max\",\"field\":\"prop1\"}],\"indexPatternRefName\":\"layer_0_join_0_index_pattern\"}}]}]",
|
||||
"uiStateJSON" : "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}",
|
||||
"bounds" : {
|
||||
"type" : "Polygon",
|
||||
"coordinates" : [
|
||||
[
|
||||
[
|
||||
29.45263,
|
||||
23.75812
|
||||
],
|
||||
[
|
||||
29.45263,
|
||||
-20.41148
|
||||
],
|
||||
[
|
||||
133.90231,
|
||||
-20.41148
|
||||
],
|
||||
[
|
||||
133.90231,
|
||||
23.75812
|
||||
],
|
||||
[
|
||||
29.45263,
|
||||
23.75812
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "map",
|
||||
"references" : [
|
||||
{
|
||||
"name" : "layer_0_source_index_pattern",
|
||||
"type" : "index-pattern",
|
||||
"id" : "561253e0-f731-11e8-8487-11b9dd924f96"
|
||||
},
|
||||
{
|
||||
"name" : "layer_0_join_0_index_pattern",
|
||||
"type" : "index-pattern",
|
||||
"id" : "e20b2a30-f735-11e8-8ce0-9723965e01e3"
|
||||
}
|
||||
],
|
||||
"migrationVersion" : {
|
||||
"map" : "7.2.0"
|
||||
},
|
||||
"updated_at" : "2019-08-15T13:56:15.793Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
"type": "doc",
|
||||
"value": {
|
||||
|
|
|
@ -21,5 +21,19 @@ export default function ({ getPageObjects, getService }) {
|
|||
});
|
||||
|
||||
});
|
||||
|
||||
describe('dynamic coloring', () => {
|
||||
before(async () => {
|
||||
await PageObjects.maps.loadSavedMap('join and dynamic coloring demo');
|
||||
await PageObjects.maps.enterFullScreen();
|
||||
await PageObjects.maps.closeLegend();
|
||||
});
|
||||
|
||||
// eslint-disable-next-line max-len
|
||||
it('should symbolize fill color with custom steps from join value and border color with dynamic color ramp from prop value', async () => {
|
||||
await visualTesting.snapshot();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue