mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
@@ -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]) {
|
||||
|
||||
Reference in New Issue
Block a user