mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Maps] heatmap color ramp select and legend details (#37187)
* [Maps] heatmap color ramp select and legend details * heatmap style editor * add tests for color_utils * add snapshot test for HeatmapStyleEditor * change label to Color range * update jest snapshot for label text change
This commit is contained in:
parent
6276a9b4af
commit
c8177e07cb
24 changed files with 590 additions and 166 deletions
|
@ -1,3 +1,2 @@
|
|||
@import './components/index';
|
||||
@import './icons/color_gradient';
|
||||
@import './layers/index';
|
||||
|
|
|
@ -1,38 +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 { vislibColorMaps } from 'ui/vislib/components/color/colormaps';
|
||||
import { getLegendColors } from 'ui/vis/map/color_util';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const GRADIENT_INTERVALS = 7;
|
||||
const COLOR_KEYS = Object.keys(vislibColorMaps);
|
||||
|
||||
export const ColorGradient = ({ color, className }) => {
|
||||
if (!color || !COLOR_KEYS.includes(color)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const classes = classNames('mapColorGradient', className);
|
||||
const rgbColorStrings = getLegendColors(vislibColorMaps[color].value, GRADIENT_INTERVALS);
|
||||
const background = getLinearGradient(rgbColorStrings, GRADIENT_INTERVALS);
|
||||
return (
|
||||
<div
|
||||
className={classes}
|
||||
style={{ background }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
function getLinearGradient(colorStrings, intervals) {
|
||||
let linearGradient = `linear-gradient(to right, ${colorStrings[0]} 0%,`;
|
||||
for (let i = 1; i < intervals - 1; i++) {
|
||||
linearGradient = `${linearGradient} ${colorStrings[i]} \
|
||||
${Math.floor(100 * i / (intervals - 1))}%,`;
|
||||
}
|
||||
return `${linearGradient} ${colorStrings.pop()} 100%)`;
|
||||
}
|
|
@ -62,7 +62,6 @@ export class HeatmapLayer extends AbstractLayer {
|
|||
data: { 'type': 'FeatureCollection', 'features': [] }
|
||||
});
|
||||
|
||||
|
||||
mbMap.addLayer({
|
||||
id: mbLayerId,
|
||||
type: 'heatmap',
|
||||
|
@ -210,4 +209,13 @@ export class HeatmapLayer extends AbstractLayer {
|
|||
return super.getCustomIconAndTooltipContent();
|
||||
}
|
||||
|
||||
hasLegendDetails() {
|
||||
return true;
|
||||
}
|
||||
|
||||
getLegendDetails() {
|
||||
const label = _.get(this._source.getMetricFields(), '[0].propertyLabel', '');
|
||||
return this._style.getLegendDetails(label);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
@import './components/color_gradient';
|
||||
@import './components/static_dynamic_style_row';
|
||||
@import './components/vector/color/static_color_selection';
|
||||
|
|
|
@ -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 React from 'react';
|
||||
|
||||
import { vislibColorMaps } from 'ui/vislib/components/color/colormaps';
|
||||
import { getLegendColors, getColor } from 'ui/vis/map/color_util';
|
||||
import { ColorGradient } from './components/color_gradient';
|
||||
import chroma from 'chroma-js';
|
||||
|
||||
const GRADIENT_INTERVALS = 8;
|
||||
|
||||
function getColorRamp(colorRampName) {
|
||||
const colorRamp = vislibColorMaps[colorRampName];
|
||||
if (!colorRamp) {
|
||||
throw new Error(`${colorRampName} not found. Expected one of following values: ${Object.keys(vislibColorMaps)}`);
|
||||
}
|
||||
return colorRamp;
|
||||
}
|
||||
|
||||
export function getRGBColorRangeStrings(colorRampName, numberColors = GRADIENT_INTERVALS) {
|
||||
const colorRamp = getColorRamp(colorRampName);
|
||||
return getLegendColors(colorRamp.value, numberColors);
|
||||
}
|
||||
|
||||
export function getHexColorRangeStrings(colorRampName, numberColors = GRADIENT_INTERVALS) {
|
||||
return getRGBColorRangeStrings(colorRampName, numberColors)
|
||||
.map(rgbColor => chroma(rgbColor).hex());
|
||||
}
|
||||
|
||||
export function getColorRampCenterColor(colorRampName) {
|
||||
const colorRamp = getColorRamp(colorRampName);
|
||||
const centerIndex = Math.floor(colorRamp.value.length / 2);
|
||||
return getColor(colorRamp.value, centerIndex);
|
||||
}
|
||||
|
||||
// Returns an array of color stops
|
||||
// [ stop_input_1: number, stop_output_1: color, stop_input_n: number, stop_output_n: color ]
|
||||
export function getColorRampStops(colorRampName, numberColors = GRADIENT_INTERVALS) {
|
||||
return getHexColorRangeStrings(colorRampName, numberColors)
|
||||
.reduce((accu, stopColor, idx, srcArr) => {
|
||||
const stopNumber = idx / srcArr.length; // number between 0 and 1, increasing as index increases
|
||||
return [ ...accu, stopNumber, stopColor ];
|
||||
}, []);
|
||||
}
|
||||
|
||||
export const COLOR_GRADIENTS = Object.keys(vislibColorMaps).map(colorRampName => ({
|
||||
value: colorRampName,
|
||||
text: colorRampName,
|
||||
inputDisplay: <ColorGradient colorRampName={colorRampName}/>
|
||||
}));
|
||||
|
||||
export const COLOR_RAMP_NAMES = Object.keys(vislibColorMaps);
|
||||
|
||||
export function getLinearGradient(colorStrings) {
|
||||
const intervals = colorStrings.length;
|
||||
let linearGradient = `linear-gradient(to right, ${colorStrings[0]} 0%,`;
|
||||
for (let i = 1; i < intervals - 1; i++) {
|
||||
linearGradient = `${linearGradient} ${colorStrings[i]} \
|
||||
${Math.floor(100 * i / (intervals - 1))}%,`;
|
||||
}
|
||||
return `${linearGradient} ${colorStrings[colorStrings.length - 1]} 100%)`;
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* 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 {
|
||||
COLOR_GRADIENTS,
|
||||
getColorRampCenterColor,
|
||||
getColorRampStops,
|
||||
getHexColorRangeStrings,
|
||||
getLinearGradient,
|
||||
getRGBColorRangeStrings,
|
||||
} from './color_utils';
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRGBColorRangeStrings', () => {
|
||||
it('Should create RGB color ramp', () => {
|
||||
expect(getRGBColorRangeStrings('Blues')).toEqual([
|
||||
'rgb(247,250,255)',
|
||||
'rgb(221,234,247)',
|
||||
'rgb(197,218,238)',
|
||||
'rgb(157,201,224)',
|
||||
'rgb(106,173,213)',
|
||||
'rgb(65,145,197)',
|
||||
'rgb(32,112,180)',
|
||||
'rgb(7,47,107)'
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getHexColorRangeStrings', () => {
|
||||
it('Should create HEX color ramp', () => {
|
||||
expect(getHexColorRangeStrings('Blues')).toEqual([
|
||||
'#f7faff',
|
||||
'#ddeaf7',
|
||||
'#c5daee',
|
||||
'#9dc9e0',
|
||||
'#6aadd5',
|
||||
'#4191c5',
|
||||
'#2070b4',
|
||||
'#072f6b'
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getColorRampCenterColor', () => {
|
||||
it('Should get center color from color ramp', () => {
|
||||
expect(getColorRampCenterColor('Blues')).toBe('rgb(106,173,213)');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getColorRampStops', () => {
|
||||
it('Should create color stops for color ramp', () => {
|
||||
expect(getColorRampStops('Blues')).toEqual([
|
||||
0, '#f7faff',
|
||||
0.125, '#ddeaf7',
|
||||
0.25, '#c5daee',
|
||||
0.375, '#9dc9e0',
|
||||
0.5, '#6aadd5',
|
||||
0.625, '#4191c5',
|
||||
0.75, '#2070b4',
|
||||
0.875, '#072f6b'
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLinearGradient', () => {
|
||||
it('Should create linear gradient from color ramp', () => {
|
||||
const colorRamp = [
|
||||
'rgb(247,250,255)',
|
||||
'rgb(221,234,247)',
|
||||
'rgb(197,218,238)',
|
||||
'rgb(157,201,224)',
|
||||
'rgb(106,173,213)',
|
||||
'rgb(65,145,197)',
|
||||
'rgb(32,112,180)',
|
||||
'rgb(7,47,107)'
|
||||
];
|
||||
// eslint-disable-next-line max-len
|
||||
expect(getLinearGradient(colorRamp)).toBe('linear-gradient(to right, rgb(247,250,255) 0%, rgb(221,234,247) 14%, rgb(197,218,238) 28%, rgb(157,201,224) 42%, rgb(106,173,213) 57%, rgb(65,145,197) 71%, rgb(32,112,180) 85%, rgb(7,47,107) 100%)');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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 { COLOR_RAMP_NAMES, getRGBColorRangeStrings, getLinearGradient } from '../color_utils';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export const ColorGradient = ({ colorRamp, colorRampName, className }) => {
|
||||
if (!colorRamp && (!colorRampName || !COLOR_RAMP_NAMES.includes(colorRampName))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const classes = classNames('mapColorGradient', className);
|
||||
const rgbColorStrings = colorRampName
|
||||
? getRGBColorRangeStrings(colorRampName)
|
||||
: colorRamp;
|
||||
const background = getLinearGradient(rgbColorStrings);
|
||||
return (
|
||||
<div
|
||||
className={classes}
|
||||
style={{ background }}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,81 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`HeatmapStyleEditor is rendered 1`] = `
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
fullWidth={false}
|
||||
hasEmptyLabelSpace={false}
|
||||
label="Color range"
|
||||
labelType="label"
|
||||
>
|
||||
<EuiSuperSelect
|
||||
compressed={false}
|
||||
fullWidth={false}
|
||||
hasDividers={true}
|
||||
isInvalid={false}
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"inputDisplay": <ColorGradient
|
||||
colorRamp={
|
||||
Array [
|
||||
"rgb(65, 105, 225)",
|
||||
"rgb(0, 256, 256)",
|
||||
"rgb(0, 256, 0)",
|
||||
"rgb(256, 256, 0)",
|
||||
"rgb(256, 0, 0)",
|
||||
]
|
||||
}
|
||||
/>,
|
||||
"text": "theclassic",
|
||||
"value": "theclassic",
|
||||
},
|
||||
Object {
|
||||
"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",
|
||||
},
|
||||
]
|
||||
}
|
||||
valueOfSelected="Blues"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
`;
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
|
||||
// Color stops from default Mapbox heatmap-color
|
||||
export const DEFAULT_RGB_HEATMAP_COLOR_RAMP = [
|
||||
'rgb(65, 105, 225)', // royalblue
|
||||
'rgb(0, 256, 256)', // cyan
|
||||
'rgb(0, 256, 0)', // lime
|
||||
'rgb(256, 256, 0)', // yellow
|
||||
'rgb(256, 0, 0)', // red
|
||||
];
|
||||
|
||||
export const DEFAULT_HEATMAP_COLOR_RAMP_NAME = 'theclassic';
|
||||
|
||||
export const HEATMAP_COLOR_RAMP_LABEL = i18n.translate('xpack.maps.heatmap.colorRampLabel', {
|
||||
defaultMessage: 'Color range'
|
||||
});
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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 { EuiFormRow, EuiSuperSelect } from '@elastic/eui';
|
||||
import { COLOR_GRADIENTS } from '../../color_utils';
|
||||
import { ColorGradient } from '../color_gradient';
|
||||
import {
|
||||
DEFAULT_RGB_HEATMAP_COLOR_RAMP,
|
||||
DEFAULT_HEATMAP_COLOR_RAMP_NAME,
|
||||
HEATMAP_COLOR_RAMP_LABEL
|
||||
} from './heatmap_constants';
|
||||
|
||||
export function HeatmapStyleEditor({ colorRampName, onHeatmapColorChange }) {
|
||||
|
||||
const onColorRampChange = (selectedColorRampName) => {
|
||||
onHeatmapColorChange({
|
||||
colorRampName: selectedColorRampName
|
||||
});
|
||||
};
|
||||
|
||||
const colorRampOptions = [
|
||||
{
|
||||
value: DEFAULT_HEATMAP_COLOR_RAMP_NAME,
|
||||
text: DEFAULT_HEATMAP_COLOR_RAMP_NAME,
|
||||
inputDisplay: <ColorGradient colorRamp={DEFAULT_RGB_HEATMAP_COLOR_RAMP}/>
|
||||
},
|
||||
...COLOR_GRADIENTS
|
||||
];
|
||||
|
||||
return (
|
||||
<EuiFormRow label={HEATMAP_COLOR_RAMP_LABEL}>
|
||||
<EuiSuperSelect
|
||||
options={colorRampOptions}
|
||||
onChange={onColorRampChange}
|
||||
valueOfSelected={colorRampName}
|
||||
hasDividers={true}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
}
|
|
@ -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 React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { HeatmapStyleEditor } from './heatmap_style_editor';
|
||||
|
||||
describe('HeatmapStyleEditor', () => {
|
||||
test('is rendered', () => {
|
||||
const component = shallow(
|
||||
<HeatmapStyleEditor
|
||||
colorRampName="Blues"
|
||||
onHeatmapColorChange={() => {}}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(component)
|
||||
.toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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 { ColorGradient } from '../../color_gradient';
|
||||
import { StyleLegendRow } from '../../style_legend_row';
|
||||
import {
|
||||
DEFAULT_RGB_HEATMAP_COLOR_RAMP,
|
||||
DEFAULT_HEATMAP_COLOR_RAMP_NAME,
|
||||
HEATMAP_COLOR_RAMP_LABEL
|
||||
} from '../heatmap_constants';
|
||||
|
||||
export function HeatmapLegend({ colorRampName, label }) {
|
||||
const header = colorRampName === DEFAULT_HEATMAP_COLOR_RAMP_NAME
|
||||
? <ColorGradient colorRamp={DEFAULT_RGB_HEATMAP_COLOR_RAMP}/>
|
||||
: <ColorGradient colorRampName={colorRampName}/>;
|
||||
|
||||
return (
|
||||
<StyleLegendRow
|
||||
header={header}
|
||||
minLabel={
|
||||
i18n.translate('xpack.maps.heatmapLegend.coldLabel', {
|
||||
defaultMessage: 'cold'
|
||||
})
|
||||
}
|
||||
maxLabel={
|
||||
i18n.translate('xpack.maps.heatmapLegend.hotLabel', {
|
||||
defaultMessage: 'hot'
|
||||
})
|
||||
}
|
||||
propertyLabel={HEATMAP_COLOR_RAMP_LABEL}
|
||||
fieldLabel={label}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -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 React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiText,
|
||||
EuiSpacer,
|
||||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
|
||||
export function StyleLegendRow({ header, minLabel, maxLabel, propertyLabel, fieldLabel }) {
|
||||
return (
|
||||
<div>
|
||||
<EuiSpacer size="xs"/>
|
||||
{header}
|
||||
<EuiFlexGroup gutterSize="xs" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={true}>
|
||||
<EuiText size="xs">
|
||||
<small>{minLabel}</small>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip
|
||||
position="top"
|
||||
title={propertyLabel}
|
||||
content={fieldLabel}
|
||||
>
|
||||
<EuiText
|
||||
className="eui-textTruncate"
|
||||
size="xs"
|
||||
style={{ maxWidth: '180px' }}
|
||||
>
|
||||
<small><strong>{fieldLabel}</strong></small>
|
||||
</EuiText>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={true}>
|
||||
<EuiText textAlign="right" size="xs">
|
||||
<small>{maxLabel}</small>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
StyleLegendRow.propTypes = {
|
||||
header: PropTypes.node.isRequired,
|
||||
minLabel: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number,
|
||||
]).isRequired,
|
||||
maxLabel: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number,
|
||||
]).isRequired,
|
||||
propertyLabel: PropTypes.string.isRequired,
|
||||
fieldLabel: PropTypes.string.isRequired,
|
||||
};
|
|
@ -8,14 +8,7 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
|
||||
import { EuiSuperSelect } from '@elastic/eui';
|
||||
import { vislibColorMaps } from 'ui/vislib/components/color/colormaps';
|
||||
import { ColorGradient } from '../../../../../icons/color_gradient';
|
||||
|
||||
export const COLOR_GRADIENTS = Object.keys(vislibColorMaps).map(colorKey => ({
|
||||
value: colorKey,
|
||||
text: colorKey,
|
||||
inputDisplay: <ColorGradient color={colorKey}/>
|
||||
}));
|
||||
import { COLOR_GRADIENTS } from '../../../color_utils';
|
||||
|
||||
export function ColorRampSelect({ color, onChange }) {
|
||||
const onColorRampChange = (selectedColorRampString) => {
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* 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';
|
||||
|
||||
export const LineIcon = ({ style }) => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<line x1="0" y1="6" x2="16" y2="6" style={style} />
|
||||
</svg>
|
||||
);
|
|
@ -4,10 +4,9 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export const FillableCircle = ({ style }) => (
|
||||
export const PointIcon = ({ style }) => (
|
||||
<svg
|
||||
className="euiIcon euiIcon--medium mapFillableCircle"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
@ -43,20 +42,3 @@ export const FillableCircle = ({ style }) => (
|
|||
</g>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const FillableRectangle = ({ style }) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<rect width="15" height="15" x=".5" y=".5" style={style} rx="4"/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const ColorableLine = ({ style }) => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<line x1="0" y1="6" x2="16" y2="6" style={style} />
|
||||
</svg>
|
||||
);
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* 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';
|
||||
|
||||
export const PolygonIcon = ({ style }) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<rect width="15" height="15" x=".5" y=".5" style={style} rx="4"/>
|
||||
</svg>
|
||||
);
|
|
@ -10,17 +10,11 @@ import PropTypes from 'prop-types';
|
|||
|
||||
import { styleOptionShapes, rangeShape } from '../style_option_shapes';
|
||||
import { VectorStyle } from '../../../vector_style';
|
||||
import { ColorGradient } from '../../../../../icons/color_gradient';
|
||||
import { FillableCircle } from '../../../../../icons/additional_layer_icons';
|
||||
import { ColorGradient } from '../../color_gradient';
|
||||
import { PointIcon } from './point_icon';
|
||||
import { getVectorStyleLabel } from '../get_vector_style_label';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiText,
|
||||
EuiSpacer,
|
||||
EuiToolTip,
|
||||
EuiHorizontalRule,
|
||||
} from '@elastic/eui';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule } from '@elastic/eui';
|
||||
import { StyleLegendRow } from '../../style_legend_row';
|
||||
|
||||
function getLineWidthIcons() {
|
||||
const defaultStyle = {
|
||||
|
@ -29,9 +23,9 @@ function getLineWidthIcons() {
|
|||
width: '12px',
|
||||
};
|
||||
return [
|
||||
<FillableCircle style={{ ...defaultStyle, strokeWidth: '1px' }}/>,
|
||||
<FillableCircle style={{ ...defaultStyle, strokeWidth: '2px' }}/>,
|
||||
<FillableCircle style={{ ...defaultStyle, strokeWidth: '3px' }}/>,
|
||||
<PointIcon style={{ ...defaultStyle, strokeWidth: '1px' }}/>,
|
||||
<PointIcon style={{ ...defaultStyle, strokeWidth: '2px' }}/>,
|
||||
<PointIcon style={{ ...defaultStyle, strokeWidth: '3px' }}/>,
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -42,9 +36,9 @@ function getSymbolSizeIcons() {
|
|||
fill: 'grey',
|
||||
};
|
||||
return [
|
||||
<FillableCircle style={{ ...defaultStyle, width: '4px' }}/>,
|
||||
<FillableCircle style={{ ...defaultStyle, width: '8px' }}/>,
|
||||
<FillableCircle style={{ ...defaultStyle, width: '12px' }}/>,
|
||||
<PointIcon style={{ ...defaultStyle, width: '4px' }}/>,
|
||||
<PointIcon style={{ ...defaultStyle, width: '8px' }}/>,
|
||||
<PointIcon style={{ ...defaultStyle, width: '12px' }}/>,
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -84,7 +78,7 @@ export function StylePropertyLegendRow({ name, type, options, range }) {
|
|||
|
||||
let header;
|
||||
if (options.color) {
|
||||
header = <ColorGradient color={options.color}/>;
|
||||
header = <ColorGradient colorRampName={options.color}/>;
|
||||
} else if (name === 'lineWidth') {
|
||||
header = renderHeaderWithIcons(getLineWidthIcons());
|
||||
} else if (name === 'iconSize') {
|
||||
|
@ -92,37 +86,13 @@ export function StylePropertyLegendRow({ name, type, options, range }) {
|
|||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<EuiSpacer size="xs"/>
|
||||
{header}
|
||||
<EuiFlexGroup gutterSize="xs" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={true}>
|
||||
<EuiText size="xs">
|
||||
<small>{_.get(range, 'min', '')}</small>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip
|
||||
position="top"
|
||||
title={getVectorStyleLabel(name)}
|
||||
content={options.field.label}
|
||||
>
|
||||
<EuiText
|
||||
className="eui-textTruncate"
|
||||
size="xs"
|
||||
style={{ maxWidth: '180px' }}
|
||||
>
|
||||
<small><strong>{options.field.label}</strong></small>
|
||||
</EuiText>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={true}>
|
||||
<EuiText textAlign="right" size="xs">
|
||||
<small>{_.get(range, 'max', '')}</small>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
<StyleLegendRow
|
||||
header={header}
|
||||
minLabel={_.get(range, 'min', '')}
|
||||
maxLabel={_.get(range, 'max', '')}
|
||||
propertyLabel={getVectorStyleLabel(name)}
|
||||
fieldLabel={options.field.label}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -8,9 +8,11 @@ import React, { Component } from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
|
||||
import { dynamicColorShape, staticColorShape } from '../style_option_shapes';
|
||||
import { ColorableLine, FillableCircle, FillableRectangle } from '../../../../../icons/additional_layer_icons';
|
||||
import { PointIcon } from './point_icon';
|
||||
import { LineIcon } from './line_icon';
|
||||
import { PolygonIcon } from './polygon_icon';
|
||||
import { VectorStyle } from '../../../vector_style';
|
||||
import { getColorRampCenterColor } from '../../../../../utils/color_utils';
|
||||
import { getColorRampCenterColor } from '../../../color_utils';
|
||||
|
||||
export class VectorIcon extends Component {
|
||||
|
||||
|
@ -50,7 +52,7 @@ export class VectorIcon extends Component {
|
|||
strokeWidth: '4px',
|
||||
};
|
||||
return (
|
||||
<ColorableLine style={style}/>
|
||||
<LineIcon style={style}/>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -61,8 +63,8 @@ export class VectorIcon extends Component {
|
|||
};
|
||||
|
||||
return this.state.isPointsOnly
|
||||
? <FillableCircle style={style}/>
|
||||
: <FillableRectangle style={style}/>;
|
||||
? <PointIcon style={style}/>
|
||||
: <PolygonIcon style={style}/>;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,22 +4,28 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { GRID_RESOLUTION } from '../grid_resolution';
|
||||
import { AbstractStyle } from './abstract_style';
|
||||
import { HeatmapStyleEditor } from './components/heatmap/heatmap_style_editor';
|
||||
import { HeatmapLegend } from './components/heatmap/legend/heatmap_legend';
|
||||
import { DEFAULT_HEATMAP_COLOR_RAMP_NAME } from './components/heatmap/heatmap_constants';
|
||||
import { getColorRampStops } from './color_utils';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export class HeatmapStyle extends AbstractStyle {
|
||||
|
||||
static type = 'HEATMAP';
|
||||
|
||||
constructor() {
|
||||
constructor(descriptor = {}) {
|
||||
super();
|
||||
this._descriptor = HeatmapStyle.createDescriptor();
|
||||
this._descriptor = HeatmapStyle.createDescriptor(descriptor.colorRampName);
|
||||
}
|
||||
|
||||
static createDescriptor() {
|
||||
static createDescriptor(colorRampName) {
|
||||
return {
|
||||
type: HeatmapStyle.type,
|
||||
colorRampName: colorRampName ? colorRampName : DEFAULT_HEATMAP_COLOR_RAMP_NAME,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -29,6 +35,29 @@ export class HeatmapStyle extends AbstractStyle {
|
|||
});
|
||||
}
|
||||
|
||||
renderEditor({ onStyleDescriptorChange }) {
|
||||
const onHeatmapColorChange = ({ colorRampName }) => {
|
||||
const styleDescriptor = HeatmapStyle.createDescriptor(colorRampName);
|
||||
onStyleDescriptorChange(styleDescriptor);
|
||||
};
|
||||
|
||||
return (
|
||||
<HeatmapStyleEditor
|
||||
colorRampName={this._descriptor.colorRampName}
|
||||
onHeatmapColorChange={onHeatmapColorChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
getLegendDetails(label) {
|
||||
return (
|
||||
<HeatmapLegend
|
||||
colorRampName={this._descriptor.colorRampName}
|
||||
label={label}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
setMBPaintProperties({ mbMap, layerId, propertyName, resolution }) {
|
||||
let radius;
|
||||
if (resolution === GRID_RESOLUTION.COARSE) {
|
||||
|
@ -49,7 +78,29 @@ export class HeatmapStyle extends AbstractStyle {
|
|||
type: 'identity',
|
||||
property: propertyName
|
||||
});
|
||||
|
||||
const { colorRampName } = this._descriptor;
|
||||
if (colorRampName && colorRampName !== DEFAULT_HEATMAP_COLOR_RAMP_NAME) {
|
||||
const colorStops = getColorRampStops(colorRampName);
|
||||
mbMap.setPaintProperty(layerId, 'heatmap-color', [
|
||||
'interpolate',
|
||||
['linear'],
|
||||
['heatmap-density'],
|
||||
0, 'rgba(0, 0, 255, 0)',
|
||||
...colorStops.slice(2) // remove first stop from colorStops to avoid conflict with transparent stop at zero
|
||||
]);
|
||||
} else {
|
||||
mbMap.setPaintProperty(layerId, 'heatmap-color', [
|
||||
'interpolate',
|
||||
['linear'],
|
||||
['heatmap-density'],
|
||||
0, 'rgba(0, 0, 255, 0)',
|
||||
0.1, 'royalblue',
|
||||
0.3, 'cyan',
|
||||
0.5, 'lime',
|
||||
0.7, 'yellow',
|
||||
1, 'red'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import _ from 'lodash';
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { getHexColorRangeStrings } from '../../utils/color_utils';
|
||||
import { getColorRampStops } from './color_utils';
|
||||
import { VectorStyleEditor } from './components/vector/vector_style_editor';
|
||||
import { getDefaultStaticProperties } from './vector_style_defaults';
|
||||
import { AbstractStyle } from './abstract_style';
|
||||
|
@ -372,18 +372,14 @@ export class VectorStyle extends AbstractStyle {
|
|||
}
|
||||
|
||||
_getMBDataDrivenColor({ fieldName, color }) {
|
||||
const colorRange = getHexColorRangeStrings(color, 8)
|
||||
.reduce((accu, curColor, idx, srcArr) => {
|
||||
accu = [ ...accu, idx / srcArr.length, curColor ];
|
||||
return accu;
|
||||
}, []);
|
||||
const colorStops = getColorRampStops(color);
|
||||
const targetName = VectorStyle.getComputedFieldName(fieldName);
|
||||
return [
|
||||
'interpolate',
|
||||
['linear'],
|
||||
['coalesce', ['feature-state', targetName], -1],
|
||||
-1, 'rgba(0,0,0,0)',
|
||||
...colorRange
|
||||
...colorStops
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import { VectorStyle } from './vector_style';
|
||||
import { COLOR_GRADIENTS } from './components/vector/color/color_ramp_select';
|
||||
import { COLOR_GRADIENTS } from './color_utils';
|
||||
|
||||
const DEFAULT_COLORS = ['#e6194b', '#3cb44b', '#ffe119', '#f58231', '#911eb4'];
|
||||
|
||||
|
|
|
@ -1,33 +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 { vislibColorMaps } from 'ui/vislib/components/color/colormaps';
|
||||
import { getLegendColors, getColor } from 'ui/vis/map/color_util';
|
||||
import chroma from 'chroma-js';
|
||||
|
||||
function getColorRamp(colorRampName) {
|
||||
const colorRamp = vislibColorMaps[colorRampName];
|
||||
if (!colorRamp) {
|
||||
throw new Error(`${colorRampName} not found. Expected one of following values: ${Object.keys(vislibColorMaps)}`);
|
||||
}
|
||||
return colorRamp;
|
||||
}
|
||||
|
||||
export function getRGBColorRangeStrings(colorRampName, numberColors) {
|
||||
const colorRamp = getColorRamp(colorRampName);
|
||||
return getLegendColors(colorRamp.value, numberColors);
|
||||
}
|
||||
|
||||
export function getHexColorRangeStrings(colorRampName, numberColors) {
|
||||
return getRGBColorRangeStrings(colorRampName, numberColors)
|
||||
.map(rgbColor => chroma(rgbColor).hex());
|
||||
}
|
||||
|
||||
export function getColorRampCenterColor(colorRampName) {
|
||||
const colorRamp = getColorRamp(colorRampName);
|
||||
const centerIndex = Math.floor(colorRamp.value.length / 2);
|
||||
return getColor(colorRamp.value, centerIndex);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue