Skip to content

Commit

Permalink
[Maps] Add categorical styling (#54408) (#54860)
Browse files Browse the repository at this point in the history
This allows users to style fields by category. Users can either uses one of default color palettes or specify a custom ramp.

Co-authored-by: Elastic Machine <[email protected]>
  • Loading branch information
2 people authored and nickpeihl committed Jan 15, 2020
1 parent 8ff26e3 commit 1cf8480
Show file tree
Hide file tree
Showing 32 changed files with 1,456 additions and 375 deletions.
9 changes: 9 additions & 0 deletions x-pack/legacy/plugins/maps/common/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,12 @@ export const LAYER_STYLE_TYPE = {
VECTOR: 'VECTOR',
HEATMAP: 'HEATMAP',
};

export const COLOR_MAP_TYPE = {
CATEGORICAL: 'CATEGORICAL',
ORDINAL: 'ORDINAL',
};

export const COLOR_PALETTE_MAX_SIZE = 10;

export const CATEGORICAL_DATA_TYPES = ['string', 'ip', 'boolean'];
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export class ESAggMetricField extends AbstractField {
return !isMetricCountable(this.getAggType());
}

async getFieldMetaRequest(config) {
return this._esDocField.getFieldMetaRequest(config);
async getOrdinalFieldMetaRequest(config) {
return this._esDocField.getOrdinalFieldMetaRequest(config);
}
}
28 changes: 27 additions & 1 deletion x-pack/legacy/plugins/maps/public/layers/fields/es_doc_field.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import { AbstractField } from './field';
import { ESTooltipProperty } from '../tooltips/es_tooltip_property';
import { COLOR_PALETTE_MAX_SIZE } from '../../../common/constants';

export class ESDocField extends AbstractField {
static type = 'ES_DOC';
Expand All @@ -29,7 +30,7 @@ export class ESDocField extends AbstractField {
return true;
}

async getFieldMetaRequest(/* config */) {
async getOrdinalFieldMetaRequest() {
const field = await this._getField();

if (field.type !== 'number' && field.type !== 'date') {
Expand All @@ -51,4 +52,29 @@ export class ESDocField extends AbstractField {
},
};
}

async getCategoricalFieldMetaRequest() {
const field = await this._getField();
if (field.type !== 'string') {
//UX does not support categorical styling for number/date fields
return null;
}

const topTerms = {
size: COLOR_PALETTE_MAX_SIZE - 1, //need additional color for the "other"-value
};
if (field.scripted) {
topTerms.script = {
source: field.script,
lang: field.lang,
};
} else {
topTerms.field = this._fieldName;
}
return {
[this._fieldName]: {
terms: topTerms,
},
};
}
}
6 changes: 5 additions & 1 deletion x-pack/legacy/plugins/maps/public/layers/fields/field.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@ export class AbstractField {
return false;
}

async getFieldMetaRequest(/* config */) {
async getOrdinalFieldMetaRequest(/* config */) {
return null;
}

async getCategoricalFieldMetaRequest() {
return null;
}
}
4 changes: 4 additions & 0 deletions x-pack/legacy/plugins/maps/public/layers/layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,10 @@ export class AbstractLayer {
return [];
}

async getCategoricalFields() {
return [];
}

async getFields() {
return [];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
getDefaultDynamicProperties,
VECTOR_STYLES,
} from '../../styles/vector/vector_style_defaults';
import { COLOR_GRADIENTS } from '../../styles/color_utils';
import { RENDER_AS } from './render_as';
import { CreateSourceEditor } from './create_source_editor';
import { UpdateSourceEditor } from './update_source_editor';
Expand Down Expand Up @@ -249,7 +250,7 @@ export class ESGeoGridSource extends AbstractESAggSource {
name: COUNT_PROP_NAME,
origin: SOURCE_DATA_ID_ORIGIN,
},
color: 'Blues',
color: COLOR_GRADIENTS[0].value,
},
},
[VECTOR_STYLES.LINE_COLOR]: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { Schemas } from 'ui/vis/editors/default/schemas';
import { AggConfigs } from 'ui/agg_types';
import { AbstractESAggSource } from '../es_agg_source';
import { DynamicStyleProperty } from '../../styles/vector/properties/dynamic_style_property';
import { COLOR_GRADIENTS } from '../../styles/color_utils';

const MAX_GEOTILE_LEVEL = 29;

Expand Down Expand Up @@ -136,7 +137,7 @@ export class ESPewPewSource extends AbstractESAggSource {
name: COUNT_PROP_NAME,
origin: SOURCE_DATA_ID_ORIGIN,
},
color: 'Blues',
color: COLOR_GRADIENTS[0].value,
},
},
[VECTOR_STYLES.LINE_WIDTH]: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
ES_GEO_FIELD_TYPE,
DEFAULT_MAX_BUCKETS_LIMIT,
SORT_ORDER,
CATEGORICAL_DATA_TYPES,
} from '../../../../common/constants';
import { i18n } from '@kbn/i18n';
import { getDataSourceLabel } from '../../../../common/i18n_getters';
Expand Down Expand Up @@ -125,6 +126,27 @@ export class ESSearchSource extends AbstractESSource {
}
}

async getCategoricalFields() {
try {
const indexPattern = await this.getIndexPattern();

const aggFields = [];
CATEGORICAL_DATA_TYPES.forEach(dataType => {
indexPattern.fields.getByType(dataType).forEach(field => {
if (field.aggregatable) {
aggFields.push(field);
}
});
});
return aggFields.map(field => {
return this.createField({ fieldName: field.name });
});
} catch (error) {
//error surfaces in the LayerTOC UI
return [];
}
}

async getFields() {
try {
const indexPattern = await this.getIndexPattern();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ export class AbstractVectorSource extends AbstractSource {
return [...(await this.getDateFields()), ...(await this.getNumberFields())];
}

async getCategoricalFields() {
return [];
}

async getLeftJoinFields() {
return [];
}
Expand Down
68 changes: 67 additions & 1 deletion x-pack/legacy/plugins/maps/public/layers/styles/color_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { ColorGradient } from './components/color_gradient';
import { euiPaletteColorBlind } from '@elastic/eui/lib/services';
import tinycolor from 'tinycolor2';
import chroma from 'chroma-js';
import { COLOR_PALETTE_MAX_SIZE } from '../../../common/constants';

const GRADIENT_INTERVALS = 8;

Expand Down Expand Up @@ -51,14 +52,20 @@ export function getHexColorRangeStrings(colorRampName, numberColors = GRADIENT_I
}

export function getColorRampCenterColor(colorRampName) {
if (!colorRampName) {
return null;
}
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) {
export function getOrdinalColorRampStops(colorRampName, numberColors = GRADIENT_INTERVALS) {
if (!colorRampName) {
return null;
}
return getHexColorRangeStrings(colorRampName, numberColors).reduce(
(accu, stopColor, idx, srcArr) => {
const stopNumber = idx / srcArr.length; // number between 0 and 1, increasing as index increases
Expand All @@ -84,3 +91,62 @@ export function getLinearGradient(colorStrings) {
}
return `${linearGradient} ${colorStrings[colorStrings.length - 1]} 100%)`;
}

const COLOR_PALETTES_CONFIGS = [
{
id: 'palette_0',
colors: DEFAULT_FILL_COLORS.slice(0, COLOR_PALETTE_MAX_SIZE),
},
{
id: 'palette_1',
colors: [
'#a6cee3',
'#1f78b4',
'#b2df8a',
'#33a02c',
'#fb9a99',
'#e31a1c',
'#fdbf6f',
'#ff7f00',
'#cab2d6',
'#6a3d9a',
],
},
{
id: 'palette_2',
colors: [
'#8dd3c7',
'#ffffb3',
'#bebada',
'#fb8072',
'#80b1d3',
'#fdb462',
'#b3de69',
'#fccde5',
'#d9d9d9',
'#bc80bd',
],
},
];

export function getColorPalette(paletteId) {
const palette = COLOR_PALETTES_CONFIGS.find(palette => palette.id === paletteId);
return palette ? palette.colors : null;
}

export const COLOR_PALETTES = COLOR_PALETTES_CONFIGS.map(palette => {
const paletteDisplay = palette.colors.map(color => {
const style = {
backgroundColor: color,
width: '10%',
position: 'relative',
height: '100%',
display: 'inline-block',
};
return <div style={style}>&nbsp;</div>;
});
return {
value: palette.id,
inputDisplay: <div className={'mapColorGradient'}>{paletteDisplay}</div>,
};
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import {
COLOR_GRADIENTS,
getColorRampCenterColor,
getColorRampStops,
getOrdinalColorRampStops,
getHexColorRangeStrings,
getLinearGradient,
getRGBColorRangeStrings,
Expand Down Expand Up @@ -59,7 +59,7 @@ describe('getColorRampCenterColor', () => {

describe('getColorRampStops', () => {
it('Should create color stops for color ramp', () => {
expect(getColorRampStops('Blues')).toEqual([
expect(getOrdinalColorRampStops('Blues')).toEqual([
0,
'#f7faff',
0.125,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { HeatmapStyleEditor } from './components/heatmap_style_editor';
import { HeatmapLegend } from './components/legend/heatmap_legend';
import { DEFAULT_HEATMAP_COLOR_RAMP_NAME } from './components/heatmap_constants';
import { LAYER_STYLE_TYPE } from '../../../../common/constants';
import { getColorRampStops } from '../color_utils';
import { getOrdinalColorRampStops } from '../color_utils';
import { i18n } from '@kbn/i18n';
import { EuiIcon } from '@elastic/eui';

Expand Down Expand Up @@ -81,7 +81,7 @@ export class HeatmapStyle extends AbstractStyle {

const { colorRampName } = this._descriptor;
if (colorRampName && colorRampName !== DEFAULT_HEATMAP_COLOR_RAMP_NAME) {
const colorStops = getColorRampStops(colorRampName);
const colorStops = getOrdinalColorRampStops(colorRampName);
mbMap.setPaintProperty(layerId, 'heatmap-color', [
'interpolate',
['linear'],
Expand Down
Loading

0 comments on commit 1cf8480

Please sign in to comment.