Skip to content

Commit

Permalink
[Maps] Remove client-side scaling of ordinal values (#58528) (#61344)
Browse files Browse the repository at this point in the history
This removes the rescaling of ordinal values to the [0,1] domain, and modifies the creation of the mapbox-style rules to use the actual RangeStyleMeta-data. This is an important prerequisite for Maps handling tile vector sources. For these sources, Maps does not have access to the raw underlying GeoJson and needs to use the stylemeta directly.
  • Loading branch information
thomasneirynck authored Mar 25, 2020
1 parent bd1cd18 commit f91fb98
Show file tree
Hide file tree
Showing 17 changed files with 335 additions and 252 deletions.
2 changes: 1 addition & 1 deletion x-pack/legacy/plugins/maps/public/layers/heatmap_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export class HeatmapLayer extends VectorLayer {
const propertyKey = this._getPropKeyOfSelectedMetric();
const dataBoundToMap = AbstractLayer.getBoundDataForSource(mbMap, this.getId());
if (featureCollection !== dataBoundToMap) {
let max = 0;
let max = 1; //max will be at least one, since counts or sums will be at least one.
for (let i = 0; i < featureCollection.features.length; i++) {
max = Math.max(featureCollection.features[i].properties[propertyKey], max);
}
Expand Down
25 changes: 17 additions & 8 deletions x-pack/legacy/plugins/maps/public/layers/styles/color_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,17 +78,26 @@ export function getColorRampCenterColor(colorRampName) {

// Returns an array of color stops
// [ stop_input_1: number, stop_output_1: color, stop_input_n: number, stop_output_n: color ]
export function getOrdinalColorRampStops(colorRampName, numberColors = GRADIENT_INTERVALS) {
export function getOrdinalColorRampStops(colorRampName, min, max) {
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
return [...accu, stopNumber, stopColor];
},
[]
);

if (min > max) {
return null;
}

const hexColors = getHexColorRangeStrings(colorRampName, GRADIENT_INTERVALS);
if (max === min) {
//just return single stop value
return [max, hexColors[hexColors.length - 1]];
}

const delta = max - min;
return hexColors.reduce((accu, stopColor, idx, srcArr) => {
const stopNumber = min + (delta * idx) / srcArr.length;
return [...accu, stopNumber, stopColor];
}, []);
}

export const COLOR_GRADIENTS = Object.keys(vislibColorMaps).map(colorRampName => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,26 +60,30 @@ describe('getColorRampCenterColor', () => {
});

describe('getColorRampStops', () => {
it('Should create color stops for color ramp', () => {
expect(getOrdinalColorRampStops('Blues')).toEqual([
it('Should create color stops for custom range', () => {
expect(getOrdinalColorRampStops('Blues', 0, 1000)).toEqual([
0,
'#f7faff',
0.125,
125,
'#ddeaf7',
0.25,
250,
'#c5daee',
0.375,
375,
'#9dc9e0',
0.5,
500,
'#6aadd5',
0.625,
625,
'#4191c5',
0.75,
750,
'#2070b4',
0.875,
875,
'#072f6b',
]);
});

it('Should snap to end of color stops for identical range', () => {
expect(getOrdinalColorRampStops('Blues', 23, 23)).toEqual([23, '#072f6b']);
});
});

describe('getLinearGradient', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ import { getOrdinalColorRampStops } from '../color_utils';
import { i18n } from '@kbn/i18n';
import { EuiIcon } from '@elastic/eui';

//The heatmap range chosen hear runs from 0 to 1. It is arbitrary.
//Weighting is on the raw count/sum values.
const MIN_RANGE = 0;
const MAX_RANGE = 1;

export class HeatmapStyle extends AbstractStyle {
static type = LAYER_STYLE_TYPE.HEATMAP;

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

const { colorRampName } = this._descriptor;
if (colorRampName && colorRampName !== DEFAULT_HEATMAP_COLOR_RAMP_NAME) {
const colorStops = getOrdinalColorRampStops(colorRampName);
const colorStops = getOrdinalColorRampStops(colorRampName, MIN_RANGE, MAX_RANGE);
mbMap.setPaintProperty(layerId, 'heatmap-color', [
'interpolate',
['linear'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
*/

import { DynamicStyleProperty } from './dynamic_style_property';
import _ from 'lodash';
import { getComputedFieldName, getOtherCategoryLabel } from '../style_util';
import { getOtherCategoryLabel, makeMbClampedNumberExpression } from '../style_util';
import { getOrdinalColorRampStops, getColorPalette } from '../../color_utils';
import { ColorGradient } from '../../components/color_gradient';
import React from 'react';
Expand All @@ -23,6 +22,7 @@ import { COLOR_MAP_TYPE } from '../../../../../common/constants';
import { isCategoricalStopsInvalid } from '../components/color/color_stops_utils';

const EMPTY_STOPS = { stops: [], defaultColor: null };
const RGBA_0000 = 'rgba(0,0,0,0)';

export class DynamicColorProperty extends DynamicStyleProperty {
syncCircleColorWithMb(mbLayerId, mbMap, alpha) {
Expand Down Expand Up @@ -70,6 +70,17 @@ export class DynamicColorProperty extends DynamicStyleProperty {
mbMap.setPaintProperty(mbLayerId, 'text-halo-color', color);
}

supportsFieldMeta() {
if (!this.isComplete() || !this._field.supportsFieldMeta()) {
return false;
}

return (
(this.isCategorical() && !this._options.useCustomColorPalette) ||
(this.isOrdinal() && !this._options.useCustomColorRamp)
);
}

isOrdinal() {
return (
typeof this._options.type === 'undefined' || this._options.type === COLOR_MAP_TYPE.ORDINAL
Expand All @@ -80,28 +91,20 @@ export class DynamicColorProperty extends DynamicStyleProperty {
return this._options.type === COLOR_MAP_TYPE.CATEGORICAL;
}

isCustomOrdinalColorRamp() {
return this._options.useCustomColorRamp;
}

supportsMbFeatureState() {
return true;
}

isOrdinalScaled() {
return this.isOrdinal() && !this.isCustomOrdinalColorRamp();
}

isOrdinalRanged() {
return this.isOrdinal() && !this.isCustomOrdinalColorRamp();
return this.isOrdinal() && !this._options.useCustomColorRamp;
}

hasOrdinalBreaks() {
return (this.isOrdinal() && this.isCustomOrdinalColorRamp()) || this.isCategorical();
return (this.isOrdinal() && this._options.useCustomColorRamp) || this.isCategorical();
}

_getMbColor() {
if (!_.get(this._options, 'field.name')) {
if (!this._field || !this._field.getName()) {
return null;
}

Expand All @@ -111,7 +114,7 @@ export class DynamicColorProperty extends DynamicStyleProperty {
}

_getOrdinalColorMbExpression() {
const targetName = getComputedFieldName(this._styleName, this._options.field.name);
const targetName = this._field.getName();
if (this._options.useCustomColorRamp) {
if (!this._options.customColorRamp || !this._options.customColorRamp.length) {
// custom color ramp config is not complete
Expand All @@ -122,27 +125,44 @@ export class DynamicColorProperty extends DynamicStyleProperty {
return [...accumulatedStops, nextStop.stop, nextStop.color];
}, []);
const firstStopValue = colorStops[0];
const lessThenFirstStopValue = firstStopValue - 1;
const lessThanFirstStopValue = 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
['coalesce', ['feature-state', targetName], lessThanFirstStopValue],
RGBA_0000, // MB will assign the base value to any features that is below the first stop value
...colorStops,
];
}
} else {
const rangeFieldMeta = this.getRangeFieldMeta();
if (!rangeFieldMeta) {
return null;
}

const colorStops = getOrdinalColorRampStops(this._options.color);
if (!colorStops) {
return null;
const colorStops = getOrdinalColorRampStops(
this._options.color,
rangeFieldMeta.min,
rangeFieldMeta.max
);
if (!colorStops) {
return null;
}

const lessThanFirstStopValue = rangeFieldMeta.min - 1;
return [
'interpolate',
['linear'],
makeMbClampedNumberExpression({
minValue: rangeFieldMeta.min,
maxValue: rangeFieldMeta.max,
lookupFunction: 'feature-state',
fallback: lessThanFirstStopValue,
fieldName: targetName,
}),
lessThanFirstStopValue,
RGBA_0000,
...colorStops,
];
}
return [
'interpolate',
['linear'],
['coalesce', ['feature-state', targetName], -1],
-1,
'rgba(0,0,0,0)',
...colorStops,
];
}

_getColorPaletteStops() {
Expand Down Expand Up @@ -220,7 +240,7 @@ export class DynamicColorProperty extends DynamicStyleProperty {
}

mbStops.push(defaultColor); //last color is default color
return ['match', ['to-string', ['get', this._options.field.name]], ...mbStops];
return ['match', ['to-string', ['get', this._field.getName()]], ...mbStops];
}

renderRangeLegendHeader() {
Expand Down
Loading

0 comments on commit f91fb98

Please sign in to comment.