@gooddata/react-components
Version:
GoodData.UI - A powerful JavaScript library for building analytical applications
161 lines (148 loc) • 5.83 kB
text/typescript
// (C) 2019-2020 GoodData Corporation
import partial = require("lodash/partial");
import mapboxgl from "mapbox-gl";
import {
DEFAULT_CLUSTER_FILTER,
DEFAULT_CLUSTER_LABELS_CONFIG,
DEFAULT_CLUSTER_LAYER_NAME,
DEFAULT_CLUSTER_POINT_BORDERS,
DEFAULT_CLUSTER_POINT_COLORS,
DEFAULT_CLUSTER_POINT_SIZES,
DEFAULT_LAYER_NAME,
DEFAULT_PUSHPIN_BORDER_COLOR_VALUE,
DEFAULT_PUSHPIN_OPTIONS,
PUSHPIN_STYLE_CIRCLE,
PUSHPIN_STYLE_CIRCLE_COLOR,
PUSHPIN_STYLE_CIRCLE_SIZE,
PUSHPIN_STYLE_CIRCLE_STROKE_COLOR,
EMPTY_SEGMENT_VALUE,
PUSHPIN_SIZE_OPTIONS_MAP,
} from "../../../constants/geoChart";
import { IGeoData, IGeoPointsConfig, IGeoConfig } from "../../../interfaces/GeoChart";
import { SEGMENT } from "../../../constants/bucketNames";
import { getMinMax } from "../../../helpers/utils";
function createPushpinSizeOptions(
geoData: IGeoData,
geoPointsConfig: IGeoPointsConfig,
): mapboxgl.Expression | number {
const { size } = geoData;
const hasSize = size !== undefined;
const defaultRadius = PUSHPIN_SIZE_OPTIONS_MAP.min.default / 2;
if (!hasSize || size.data.length === 0) {
return defaultRadius;
}
const { min: minSizeFromData, max: maxSizeFromData } = getMinMax(size.data);
if (maxSizeFromData === minSizeFromData) {
return defaultRadius;
}
const { minSize: minSizeFromConfig = "default", maxSize: maxSizeFromConfig = "default" } =
geoPointsConfig || {};
const minSizeInPixel = PUSHPIN_SIZE_OPTIONS_MAP.min[minSizeFromConfig];
const maxSizeInPixel = PUSHPIN_SIZE_OPTIONS_MAP.max[maxSizeFromConfig];
const base = Math.pow(maxSizeInPixel / minSizeInPixel, 0.25);
const getStopPointSize = partial(getPointSize, minSizeInPixel, base);
// The mouseenter event uses queryRenderedFeatures to determine when the mouse is touching a feature
// And queryRenderedFeatures is not supporting nested object in expression
// https://github.com/mapbox/mapbox-gl-js/issues/7194
return [
"step",
["get", "pushpinSize"],
Math.round(minSizeInPixel / 2), // a
getStopPointSize(1),
Math.round(getStopPointSize(1) / 2), // ar^1
getStopPointSize(2),
Math.round(getStopPointSize(2) / 2), // ar^2
getStopPointSize(3),
Math.round(getStopPointSize(3) / 2), // ar^3
maxSizeInPixel,
Math.round(maxSizeInPixel / 2), // ar^4
];
}
function getPointSize(minSizeInPixel: number, base: number, exponent: number): number {
const stepValue = minSizeInPixel * Math.pow(base, exponent);
return parseFloat(stepValue.toFixed(2));
}
export function createPushpinFilter(selectedSegmentItems: string[]): mapboxgl.Expression {
return [
"match",
["get", "uri", ["object", ["get", SEGMENT]]],
selectedSegmentItems.length ? selectedSegmentItems : [EMPTY_SEGMENT_VALUE],
true,
false,
]; // true/false are the output values, from the https://docs.mapbox.com/mapbox-gl-js/style-spec/expressions/#match
}
function createPushpinColorOptions(): mapboxgl.Expression {
return ["string", ["get", "background", ["object", ["get", "color"]]]];
}
function createPushpinBorderOptions(): mapboxgl.Expression {
return ["string", ["get", "border", ["object", ["get", "color"]]], DEFAULT_PUSHPIN_BORDER_COLOR_VALUE];
}
export function createPushpinDataLayer(
dataSourceName: string,
geoData: IGeoData,
config: IGeoConfig,
): mapboxgl.Layer {
const { selectedSegmentItems = [], points: geoPointsConfig = {} } = config || {};
const layer: mapboxgl.Layer = {
id: DEFAULT_LAYER_NAME,
type: PUSHPIN_STYLE_CIRCLE,
source: dataSourceName,
paint: {
...DEFAULT_PUSHPIN_OPTIONS,
[PUSHPIN_STYLE_CIRCLE_COLOR]: createPushpinColorOptions(),
[PUSHPIN_STYLE_CIRCLE_STROKE_COLOR]: createPushpinBorderOptions(),
[PUSHPIN_STYLE_CIRCLE_SIZE]: createPushpinSizeOptions(geoData, geoPointsConfig),
},
};
if (selectedSegmentItems.length > 0) {
layer.filter = createPushpinFilter(selectedSegmentItems);
}
return layer;
}
/**
* Create layer for clustered points/pins which have 'properties.point_count' indicates number of same points is clustered together
* @param dataSourceName
*/
export function createClusterPoints(dataSourceName: string): mapboxgl.Layer {
return {
id: DEFAULT_CLUSTER_LAYER_NAME,
type: PUSHPIN_STYLE_CIRCLE,
source: dataSourceName,
filter: DEFAULT_CLUSTER_FILTER,
paint: {
...DEFAULT_CLUSTER_POINT_BORDERS,
[PUSHPIN_STYLE_CIRCLE_COLOR]: DEFAULT_CLUSTER_POINT_COLORS,
[PUSHPIN_STYLE_CIRCLE_SIZE]: DEFAULT_CLUSTER_POINT_SIZES,
},
};
}
/**
* Create layer for cluster labels which indicate number of points/pins is clustered
* @param dataSourceName
*/
export function createClusterLabels(dataSourceName: string): mapboxgl.Layer {
return {
...DEFAULT_CLUSTER_LABELS_CONFIG,
source: dataSourceName,
filter: DEFAULT_CLUSTER_FILTER,
};
}
/**
* Create layer for un-clustered points which are not close to others
* @param dataSourceName
* @param selectedSegmentItems
*/
export function createUnclusterPoints(dataSourceName: string): mapboxgl.Layer {
return {
id: DEFAULT_LAYER_NAME,
type: PUSHPIN_STYLE_CIRCLE,
source: dataSourceName,
filter: ["!", DEFAULT_CLUSTER_FILTER],
paint: {
...DEFAULT_PUSHPIN_OPTIONS,
[PUSHPIN_STYLE_CIRCLE_COLOR]: createPushpinColorOptions(),
[PUSHPIN_STYLE_CIRCLE_STROKE_COLOR]: DEFAULT_PUSHPIN_BORDER_COLOR_VALUE,
[PUSHPIN_STYLE_CIRCLE_SIZE]: PUSHPIN_SIZE_OPTIONS_MAP.min.default / 2,
},
};
}