Geomap: Add color gradients to route layer (#59062)

* Geomap: Add color gradients to route layer

* Add support for all color schemes

* Address PR feedback: remove ! from verified object

* Add arrow support and simplify color functions

* Simplify and clean-up code

* Remove line width slider and drive width by size

* Drive arrow size based on size

* Allow arrows for fixed color

* Add gdev dashboard

* Use square line caps only when arrows are active

* Apply size to width for fixed color and size

* Handle arrows when size and color are fixed

* Add tags to gdev dashboard

* Fix null error in backend test for gdev dashboard

Co-authored-by: nmarrs <nathanielmarrs@gmail.com>
This commit is contained in:
Drew Slobodnjak
2022-12-15 09:54:08 -08:00
committed by GitHub
parent 3dc74bd74b
commit 5c7f77e402
3 changed files with 265 additions and 23 deletions

View File

@@ -15,8 +15,7 @@ import Map from 'ol/Map';
import { FeatureLike } from 'ol/Feature';
import { Subscription, throttleTime } from 'rxjs';
import { getGeometryField, getLocationMatchers } from 'app/features/geo/utils/location';
import { getColorDimension } from 'app/features/dimensions';
import { defaultStyleConfig, StyleConfig, StyleDimensions } from '../../style/types';
import { defaultStyleConfig, StyleConfig } from '../../style/types';
import { StyleEditor } from '../../editor/StyleEditor';
import { getStyleConfigState } from '../../style/utils';
import VectorLayer from 'ol/layer/Vector';
@@ -28,10 +27,15 @@ import VectorSource from 'ol/source/Vector';
import { Fill, Stroke, Style, Circle } from 'ol/style';
import Feature from 'ol/Feature';
import { alpha } from '@grafana/data/src/themes/colorManipulator';
import { LineString, SimpleGeometry } from 'ol/geom';
import FlowLine from 'ol-ext/style/FlowLine';
import tinycolor from 'tinycolor2';
import { getStyleDimension } from '../../utils/utils';
// Configuration options for Circle overlays
export interface RouteConfig {
style: StyleConfig;
arrow?: 0 | 1 | -1;
}
const defaultOptions: RouteConfig = {
@@ -40,6 +44,7 @@ const defaultOptions: RouteConfig = {
opacity: 1,
lineWidth: 2,
},
arrow: 0,
};
export const ROUTE_LAYER_ID = 'route';
@@ -81,10 +86,16 @@ export const routeLayer: MapLayerRegistryItem<RouteConfig> = {
const location = await getLocationMatchers(options.location);
const source = new FrameVectorSource(location);
const vectorLayer = new VectorLayer({ source });
const hasArrows = config.arrow == 1 || config.arrow == -1;
if (!style.fields) {
if (!style.fields && !hasArrows) {
// Set a global style
vectorLayer.setStyle(routeStyle(style.base));
const styleBase = routeStyle(style.base);
if (style.config.size && style.config.size.fixed) {
// Applies width to base style if specified
styleBase.getStroke().setWidth(style.config.size.fixed);
}
vectorLayer.setStyle(styleBase);
} else {
vectorLayer.setStyle((feature: FeatureLike) => {
const idx = feature.get('rowIndex') as number;
@@ -93,6 +104,53 @@ export const routeLayer: MapLayerRegistryItem<RouteConfig> = {
return routeStyle(style.base);
}
const styles = [];
const geom = feature.getGeometry();
const opacity = style.config.opacity ?? 1;
if (geom instanceof SimpleGeometry) {
const coordinates = geom.getCoordinates();
if (coordinates) {
for (let i = 0; i < coordinates.length - 1; i++) {
const color1 = tinycolor(
theme.visualization.getColorByName((dims.color && dims.color.get(i)) ?? style.base.color)
)
.setAlpha(opacity)
.toString();
const color2 = tinycolor(
theme.visualization.getColorByName((dims.color && dims.color.get(i + 1)) ?? style.base.color)
)
.setAlpha(opacity)
.toString();
const arrowSize1 = (dims.size && dims.size.get(i)) ?? style.base.size;
const arrowSize2 = (dims.size && dims.size.get(i + 1)) ?? style.base.size;
const flowStyle = new FlowLine({
visible: true,
lineCap: config.arrow == 0 ? 'round' : 'square',
color: color1,
color2: color2,
width: (dims.size && dims.size.get(i)) ?? style.base.size,
width2: (dims.size && dims.size.get(i + 1)) ?? style.base.size,
});
if (config.arrow) {
flowStyle.setArrow(config.arrow);
if (config.arrow > 0) {
flowStyle.setArrowColor(color2);
flowStyle.setArrowSize((arrowSize2 ?? 0) * 1.5);
} else {
flowStyle.setArrowColor(color1);
flowStyle.setArrowSize((arrowSize1 ?? 0) * 1.5);
}
}
const LS = new LineString([coordinates[i], coordinates[i + 1]]);
flowStyle.setGeometry(LS);
styles.push(flowStyle);
}
}
return styles;
}
const values = { ...style.base };
if (dims.color) {
@@ -110,10 +168,10 @@ export const routeLayer: MapLayerRegistryItem<RouteConfig> = {
radius: crosshairRadius,
stroke: new Stroke({
color: alpha(style.base.color, 0.4),
width: crosshairRadius + 2
width: crosshairRadius + 2,
}),
fill: new Fill({color: style.base.color}),
})
fill: new Fill({ color: style.base.color }),
}),
});
const crosshairLayer = new VectorLayer({
@@ -124,7 +182,7 @@ export const routeLayer: MapLayerRegistryItem<RouteConfig> = {
});
const layer = new LayerGroup({
layers: [vectorLayer, crosshairLayer]
layers: [vectorLayer, crosshairLayer],
});
// Crosshair sharing subscriptions
@@ -172,12 +230,8 @@ export const routeLayer: MapLayerRegistryItem<RouteConfig> = {
}
for (const frame of data.series) {
if (style.fields) {
const dims: StyleDimensions = {};
if (style.fields.color) {
dims.color = getColorDimension(frame, style.config.color ?? defaultStyleConfig.color, theme);
}
style.dims = dims;
if (style.fields || hasArrows) {
style.dims = getStyleDimension(frame, style, theme);
}
source.updateLineString(frame);
@@ -194,19 +248,21 @@ export const routeLayer: MapLayerRegistryItem<RouteConfig> = {
name: 'Style',
editor: StyleEditor,
settings: {
simpleFixedValues: true,
simpleFixedValues: false,
},
defaultValue: defaultOptions.style,
})
.addSliderInput({
path: 'config.style.lineWidth',
name: 'Line width',
defaultValue: defaultOptions.style.lineWidth,
.addRadio({
path: 'config.arrow',
name: 'Arrow',
settings: {
min: 1,
max: 10,
step: 1,
options: [
{ label: 'None', value: 0 },
{ label: 'Forward', value: 1 },
{ label: 'Reverse', value: -1 },
],
},
defaultValue: defaultOptions.arrow,
});
},
};
@@ -229,7 +285,7 @@ function findNearestTimeIndex(timestamps: number[], time: number): number | null
return lastIdx;
}
const probableIdx = Math.abs(Math.round(lastIdx * (time - timestamps[0]) / (timestamps[lastIdx] - timestamps[0])));
const probableIdx = Math.abs(Math.round((lastIdx * (time - timestamps[0])) / (timestamps[lastIdx] - timestamps[0])));
if (time < timestamps[probableIdx]) {
for (let i = probableIdx; i > 0; i--) {
if (time > timestamps[i]) {