mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Geomap: Add rotation option for markers layer (#41992)
* add rotation option for markers layer * add ScalerDimensionEditor
This commit is contained in:
parent
0921037f32
commit
92e1883845
@ -68,7 +68,8 @@ export function useFieldDisplayNames(data: DataFrame[], filter?: (field: Field)
|
||||
export function useSelectOptions(
|
||||
displayNames: FrameFieldsDisplayNames,
|
||||
currentName?: string,
|
||||
firstItem?: SelectableValue<string>
|
||||
firstItem?: SelectableValue<string>,
|
||||
fieldType?: string
|
||||
): Array<SelectableValue<string>> {
|
||||
return useMemo(() => {
|
||||
let found = false;
|
||||
@ -81,12 +82,14 @@ export function useSelectOptions(
|
||||
found = true;
|
||||
}
|
||||
const field = displayNames.fields.get(name);
|
||||
if (!fieldType || fieldType === field?.type) {
|
||||
options.push({
|
||||
value: name,
|
||||
label: name,
|
||||
icon: field ? getFieldTypeIcon(field) : undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
for (const name of displayNames.raw) {
|
||||
if (!displayNames.display.has(name)) {
|
||||
if (!found && name === currentName) {
|
||||
@ -106,5 +109,5 @@ export function useSelectOptions(
|
||||
});
|
||||
}
|
||||
return options;
|
||||
}, [displayNames, currentName, firstItem]);
|
||||
}, [displayNames, currentName, firstItem, fieldType]);
|
||||
}
|
||||
|
110
public/app/features/dimensions/editors/ScalarDimensionEditor.tsx
Normal file
110
public/app/features/dimensions/editors/ScalarDimensionEditor.tsx
Normal file
@ -0,0 +1,110 @@
|
||||
import React, { FC, useCallback } from 'react';
|
||||
import { FieldType, GrafanaTheme2, SelectableValue, StandardEditorProps } from '@grafana/data';
|
||||
import { ScalarDimensionConfig, ScalarDimensionMode, ScalarDimensionOptions } from '../types';
|
||||
import { InlineField, InlineFieldRow, RadioButtonGroup, Select, useStyles2 } from '@grafana/ui';
|
||||
import { useFieldDisplayNames, useSelectOptions } from '@grafana/ui/src/components/MatchersUI/utils';
|
||||
import { NumberInput } from './NumberInput';
|
||||
import { css } from '@emotion/css';
|
||||
|
||||
const fixedValueOption: SelectableValue<string> = {
|
||||
label: 'Fixed value',
|
||||
value: '_____fixed_____',
|
||||
};
|
||||
|
||||
const scalarOptions = [
|
||||
{ label: 'Mod', value: ScalarDimensionMode.Mod, description: 'Use field values, mod from max' },
|
||||
{ label: 'Clamped', value: ScalarDimensionMode.Clamped, description: 'Use field values, clamped to max and min' },
|
||||
];
|
||||
|
||||
export const ScalarDimensionEditor: FC<StandardEditorProps<ScalarDimensionConfig, ScalarDimensionOptions, any>> = (
|
||||
props
|
||||
) => {
|
||||
const { value, context, onChange } = props;
|
||||
|
||||
const DEFAULT_VALUE = 0;
|
||||
|
||||
const fieldName = value?.field;
|
||||
const isFixed = Boolean(!fieldName);
|
||||
const names = useFieldDisplayNames(context.data);
|
||||
const selectOptions = useSelectOptions(names, fieldName, fixedValueOption, FieldType.number);
|
||||
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const onSelectChange = useCallback(
|
||||
(selection: SelectableValue<string>) => {
|
||||
const field = selection.value;
|
||||
if (field && field !== fixedValueOption.value) {
|
||||
onChange({
|
||||
...value,
|
||||
field,
|
||||
});
|
||||
} else {
|
||||
const fixed = value.fixed ?? DEFAULT_VALUE;
|
||||
onChange({
|
||||
...value,
|
||||
field: undefined,
|
||||
fixed,
|
||||
});
|
||||
}
|
||||
},
|
||||
[onChange, value]
|
||||
);
|
||||
|
||||
const onModeChange = useCallback(
|
||||
(mode) => {
|
||||
onChange({
|
||||
...value,
|
||||
mode,
|
||||
});
|
||||
},
|
||||
[onChange, value]
|
||||
);
|
||||
|
||||
const onValueChange = useCallback(
|
||||
(v: number | undefined) => {
|
||||
onChange({
|
||||
...value,
|
||||
field: undefined,
|
||||
fixed: v ?? DEFAULT_VALUE,
|
||||
});
|
||||
},
|
||||
[onChange, value]
|
||||
);
|
||||
|
||||
const val = value ?? {};
|
||||
const mode = value?.mode ?? ScalarDimensionMode.Mod;
|
||||
const selectedOption = isFixed ? fixedValueOption : selectOptions.find((v) => v.value === fieldName);
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<InlineFieldRow>
|
||||
<InlineField label="Limit" labelWidth={8} grow={true}>
|
||||
<RadioButtonGroup value={mode} options={scalarOptions} onChange={onModeChange} fullWidth />
|
||||
</InlineField>
|
||||
</InlineFieldRow>
|
||||
<Select
|
||||
menuShouldPortal
|
||||
value={selectedOption}
|
||||
options={selectOptions}
|
||||
onChange={onSelectChange}
|
||||
noOptionsMessage="No fields found"
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.range}>
|
||||
{isFixed && (
|
||||
<InlineFieldRow>
|
||||
<InlineField label="Value" labelWidth={8} grow={true}>
|
||||
<NumberInput value={val?.fixed ?? DEFAULT_VALUE} onChange={onValueChange} />
|
||||
</InlineField>
|
||||
</InlineFieldRow>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
range: css`
|
||||
padding-top: 8px;
|
||||
`,
|
||||
});
|
@ -2,4 +2,5 @@ export * from './ColorDimensionEditor';
|
||||
export * from './IconSelector';
|
||||
export * from './ResourceDimensionEditor';
|
||||
export * from './ScaleDimensionEditor';
|
||||
export * from './ScalarDimensionEditor';
|
||||
export * from './TextDimensionEditor';
|
||||
|
@ -6,3 +6,4 @@ export * from './text';
|
||||
export * from './utils';
|
||||
export * from './resource';
|
||||
export * from './context';
|
||||
export * from './scalar';
|
||||
|
94
public/app/features/dimensions/scalar.test.ts
Normal file
94
public/app/features/dimensions/scalar.test.ts
Normal file
@ -0,0 +1,94 @@
|
||||
import { ArrayVector, DataFrame, FieldType } from '@grafana/data';
|
||||
import { ScalarDimensionMode } from '.';
|
||||
import { getScalarDimension } from './scalar';
|
||||
|
||||
describe('scalar dimensions', () => {
|
||||
it('handles string field', () => {
|
||||
const values = ['-720', '10', '540', '90', '-210'];
|
||||
const frame: DataFrame = {
|
||||
name: 'a',
|
||||
length: values.length,
|
||||
fields: [
|
||||
{
|
||||
name: 'test',
|
||||
type: FieldType.number,
|
||||
values: new ArrayVector(values),
|
||||
config: {
|
||||
min: -720,
|
||||
max: 540,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const supplier = getScalarDimension(frame, {
|
||||
min: -360,
|
||||
max: 360,
|
||||
field: 'test',
|
||||
fixed: 0,
|
||||
mode: ScalarDimensionMode.Clamped,
|
||||
});
|
||||
|
||||
const clamped = frame.fields[0].values.toArray().map((k, i) => supplier.get(i));
|
||||
expect(clamped).toEqual([0, 0, 0, 0, 0]);
|
||||
});
|
||||
it('clamps out of range values', () => {
|
||||
const values = [-720, 10, 540, 90, -210];
|
||||
const frame: DataFrame = {
|
||||
name: 'a',
|
||||
length: values.length,
|
||||
fields: [
|
||||
{
|
||||
name: 'test',
|
||||
type: FieldType.number,
|
||||
values: new ArrayVector(values),
|
||||
config: {
|
||||
min: -720,
|
||||
max: 540,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const supplier = getScalarDimension(frame, {
|
||||
min: -360,
|
||||
max: 360,
|
||||
field: 'test',
|
||||
fixed: 0,
|
||||
mode: ScalarDimensionMode.Clamped,
|
||||
});
|
||||
|
||||
const clamped = frame.fields[0].values.toArray().map((k, i) => supplier.get(i));
|
||||
expect(clamped).toEqual([-360, 10, 360, 90, -210]);
|
||||
});
|
||||
|
||||
it('keeps remainder after divisible by max', () => {
|
||||
const values = [-721, 10, 540, 390, -210];
|
||||
const frame: DataFrame = {
|
||||
name: 'a',
|
||||
length: values.length,
|
||||
fields: [
|
||||
{
|
||||
name: 'test',
|
||||
type: FieldType.number,
|
||||
values: new ArrayVector(values),
|
||||
config: {
|
||||
min: -721,
|
||||
max: 540,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const supplier = getScalarDimension(frame, {
|
||||
min: -360,
|
||||
max: 360,
|
||||
field: 'test',
|
||||
fixed: 0,
|
||||
mode: ScalarDimensionMode.Mod,
|
||||
});
|
||||
|
||||
const remainder = frame.fields[0].values.toArray().map((k, i) => supplier.get(i));
|
||||
expect(remainder).toEqual([-1, 10, 180, 30, -210]);
|
||||
});
|
||||
});
|
59
public/app/features/dimensions/scalar.ts
Normal file
59
public/app/features/dimensions/scalar.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { DataFrame, Field } from '@grafana/data';
|
||||
import { DimensionSupplier, ScalarDimensionConfig, ScalarDimensionMode } from './types';
|
||||
import { findField, getLastNotNullFieldValue } from './utils';
|
||||
|
||||
//---------------------------------------------------------
|
||||
// Scalar dimension
|
||||
//---------------------------------------------------------
|
||||
export function getScalarDimension(
|
||||
frame: DataFrame | undefined,
|
||||
config: ScalarDimensionConfig
|
||||
): DimensionSupplier<number> {
|
||||
return getScalarDimensionForField(findField(frame, config?.field), config);
|
||||
}
|
||||
export function getScalarDimensionForField(
|
||||
field: Field | undefined,
|
||||
cfg: ScalarDimensionConfig
|
||||
): DimensionSupplier<number> {
|
||||
if (!field) {
|
||||
const v = cfg.fixed ?? 0;
|
||||
return {
|
||||
isAssumed: Boolean(cfg.field?.length) || !cfg.fixed,
|
||||
fixed: v,
|
||||
value: () => v,
|
||||
get: () => v,
|
||||
};
|
||||
}
|
||||
|
||||
//mod mode as default
|
||||
let validated = (value: number) => {
|
||||
return value % cfg.max;
|
||||
};
|
||||
|
||||
//capped mode
|
||||
if (cfg.mode === ScalarDimensionMode.Clamped) {
|
||||
validated = (value: number) => {
|
||||
if (value < cfg.min) {
|
||||
return cfg.min;
|
||||
}
|
||||
if (value > cfg.max) {
|
||||
return cfg.max;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
||||
const get = (i: number) => {
|
||||
const v = field.values.get(i);
|
||||
if (v === null || typeof v !== 'number') {
|
||||
return 0;
|
||||
}
|
||||
return validated(v);
|
||||
};
|
||||
|
||||
return {
|
||||
field,
|
||||
get,
|
||||
value: () => getLastNotNullFieldValue(field),
|
||||
};
|
||||
}
|
@ -51,6 +51,21 @@ export interface ScaleDimensionOptions {
|
||||
hideRange?: boolean; // false
|
||||
}
|
||||
|
||||
export enum ScalarDimensionMode {
|
||||
Mod = 'mod',
|
||||
Clamped = 'clamped',
|
||||
}
|
||||
export interface ScalarDimensionConfig extends BaseDimensionConfig<number> {
|
||||
mode: ScalarDimensionMode;
|
||||
min: number;
|
||||
max: number;
|
||||
}
|
||||
|
||||
export interface ScalarDimensionOptions {
|
||||
min: number;
|
||||
max: number;
|
||||
}
|
||||
|
||||
export interface TextDimensionOptions {
|
||||
// anything?
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import {
|
||||
ColorDimensionEditor,
|
||||
ResourceDimensionEditor,
|
||||
ScaleDimensionEditor,
|
||||
ScalarDimensionEditor,
|
||||
TextDimensionEditor,
|
||||
} from 'app/features/dimensions/editors';
|
||||
import {
|
||||
@ -27,14 +28,16 @@ import {
|
||||
ResourceFolderName,
|
||||
TextDimensionConfig,
|
||||
defaultTextConfig,
|
||||
ScalarDimensionConfig,
|
||||
} from 'app/features/dimensions/types';
|
||||
import { defaultStyleConfig, StyleConfig, TextAlignment, TextBaseline } from '../../style/types';
|
||||
import { defaultStyleConfig, GeometryTypeId, StyleConfig, TextAlignment, TextBaseline } from '../../style/types';
|
||||
import { styleUsesText } from '../../style/utils';
|
||||
import { LayerContentInfo } from '../../utils/getFeatures';
|
||||
|
||||
export interface StyleEditorOptions {
|
||||
layerInfo?: Observable<LayerContentInfo>;
|
||||
simpleFixedValues?: boolean;
|
||||
displayRotation?: boolean;
|
||||
}
|
||||
|
||||
export const StyleEditor: FC<StandardEditorProps<StyleConfig, StyleEditorOptions, any>> = ({
|
||||
@ -43,6 +46,8 @@ export const StyleEditor: FC<StandardEditorProps<StyleConfig, StyleEditorOptions
|
||||
onChange,
|
||||
item,
|
||||
}) => {
|
||||
const settings = item.settings;
|
||||
|
||||
const onSizeChange = (sizeValue: ScaleDimensionConfig | undefined) => {
|
||||
onChange({ ...value, size: sizeValue });
|
||||
};
|
||||
@ -59,6 +64,10 @@ export const StyleEditor: FC<StandardEditorProps<StyleConfig, StyleEditorOptions
|
||||
onChange({ ...value, opacity: opacityValue });
|
||||
};
|
||||
|
||||
const onRotationChange = (rotationValue: ScalarDimensionConfig | undefined) => {
|
||||
onChange({ ...value, rotation: rotationValue });
|
||||
};
|
||||
|
||||
const onTextChange = (textValue: TextDimensionConfig | undefined) => {
|
||||
onChange({ ...value, text: textValue });
|
||||
};
|
||||
@ -84,18 +93,18 @@ export const StyleEditor: FC<StandardEditorProps<StyleConfig, StyleEditorOptions
|
||||
};
|
||||
|
||||
let featuresHavePoints = false;
|
||||
if (item.settings?.layerInfo) {
|
||||
const propertyOptions = useObservable(item.settings?.layerInfo);
|
||||
featuresHavePoints = propertyOptions?.geometryType === 'point';
|
||||
if (settings?.layerInfo) {
|
||||
const propertyOptions = useObservable(settings?.layerInfo);
|
||||
featuresHavePoints = propertyOptions?.geometryType === GeometryTypeId.Point;
|
||||
}
|
||||
|
||||
const hasTextLabel = styleUsesText(value);
|
||||
|
||||
// Simple fixed value display
|
||||
if (item.settings?.simpleFixedValues) {
|
||||
if (settings?.simpleFixedValues) {
|
||||
return (
|
||||
<>
|
||||
{featuresHavePoints && (
|
||||
<>
|
||||
<InlineFieldRow>
|
||||
<InlineField label={'Symbol'}>
|
||||
<ResourceDimensionEditor
|
||||
@ -116,6 +125,22 @@ export const StyleEditor: FC<StandardEditorProps<StyleConfig, StyleEditorOptions
|
||||
/>
|
||||
</InlineField>
|
||||
</InlineFieldRow>
|
||||
<Field label={'Rotation angle'}>
|
||||
<ScalarDimensionEditor
|
||||
value={value.rotation ?? defaultStyleConfig.rotation}
|
||||
context={context}
|
||||
onChange={onRotationChange}
|
||||
item={
|
||||
{
|
||||
settings: {
|
||||
min: defaultStyleConfig.rotation.min,
|
||||
max: defaultStyleConfig.rotation.max,
|
||||
},
|
||||
} as any
|
||||
}
|
||||
/>
|
||||
</Field>
|
||||
</>
|
||||
)}
|
||||
<InlineFieldRow>
|
||||
<InlineField label="Color" labelWidth={10}>
|
||||
@ -210,6 +235,23 @@ export const StyleEditor: FC<StandardEditorProps<StyleConfig, StyleEditorOptions
|
||||
}
|
||||
/>
|
||||
</Field>
|
||||
{settings?.displayRotation && (
|
||||
<Field label={'Rotation angle'}>
|
||||
<ScalarDimensionEditor
|
||||
value={value.rotation ?? defaultStyleConfig.rotation}
|
||||
context={context}
|
||||
onChange={onRotationChange}
|
||||
item={
|
||||
{
|
||||
settings: {
|
||||
min: defaultStyleConfig.rotation.min,
|
||||
max: defaultStyleConfig.rotation.max,
|
||||
},
|
||||
} as any
|
||||
}
|
||||
/>
|
||||
</Field>
|
||||
)}
|
||||
<Field label={'Text label'}>
|
||||
<TextDimensionEditor
|
||||
value={value.text ?? defaultTextConfig}
|
||||
|
@ -12,7 +12,7 @@ import { Point } from 'ol/geom';
|
||||
import * as layer from 'ol/layer';
|
||||
import * as source from 'ol/source';
|
||||
import { dataFrameToPoints, getLocationMatchers } from '../../utils/location';
|
||||
import { getScaledDimension, getColorDimension, getTextDimension } from 'app/features/dimensions';
|
||||
import { getScaledDimension, getColorDimension, getTextDimension, getScalarDimension } from 'app/features/dimensions';
|
||||
import { ObservablePropsWrapper } from '../../components/ObservablePropsWrapper';
|
||||
import { MarkersLegend, MarkersLegendProps } from './MarkersLegend';
|
||||
import { ReplaySubject } from 'rxjs';
|
||||
@ -107,6 +107,9 @@ export const markersLayer: MapLayerRegistryItem<MarkersConfig> = {
|
||||
if (style.fields.text) {
|
||||
dims.text = getTextDimension(frame, style.config.text!);
|
||||
}
|
||||
if (style.fields.rotation) {
|
||||
dims.rotation = getScalarDimension(frame, style.config.rotation ?? defaultStyleConfig.rotation);
|
||||
}
|
||||
style.dims = dims;
|
||||
}
|
||||
|
||||
@ -139,7 +142,9 @@ export const markersLayer: MapLayerRegistryItem<MarkersConfig> = {
|
||||
path: 'config.style',
|
||||
name: 'Styles',
|
||||
editor: StyleEditor,
|
||||
settings: {},
|
||||
settings: {
|
||||
displayRotation: true,
|
||||
},
|
||||
defaultValue: defaultOptions.style,
|
||||
})
|
||||
.addBooleanSwitch({
|
||||
|
@ -150,6 +150,12 @@ describe('geomap migrations', () => {
|
||||
"fixed": "dark-green",
|
||||
},
|
||||
"opacity": 0.4,
|
||||
"rotation": Object {
|
||||
"fixed": 0,
|
||||
"max": 360,
|
||||
"min": -360,
|
||||
"mode": "mod",
|
||||
},
|
||||
"size": Object {
|
||||
"field": "Count",
|
||||
"fixed": 5,
|
||||
|
@ -121,13 +121,14 @@ const makers: SymbolMaker[] = [
|
||||
aliasIds: [MarkerShapePath.square],
|
||||
make: (cfg: StyleConfigValues) => {
|
||||
const radius = cfg.size ?? DEFAULT_SIZE;
|
||||
const rotation = cfg.rotation ?? 0;
|
||||
return new Style({
|
||||
image: new RegularShape({
|
||||
stroke: new Stroke({ color: cfg.color, width: cfg.lineWidth ?? 1 }),
|
||||
fill: getFillColor(cfg),
|
||||
points: 4,
|
||||
radius,
|
||||
angle: Math.PI / 4,
|
||||
rotation: (rotation * Math.PI) / 180 + Math.PI / 4,
|
||||
}),
|
||||
text: textLabel(cfg),
|
||||
});
|
||||
@ -139,13 +140,14 @@ const makers: SymbolMaker[] = [
|
||||
aliasIds: [MarkerShapePath.triangle],
|
||||
make: (cfg: StyleConfigValues) => {
|
||||
const radius = cfg.size ?? DEFAULT_SIZE;
|
||||
const rotation = cfg.rotation ?? 0;
|
||||
return new Style({
|
||||
image: new RegularShape({
|
||||
stroke: new Stroke({ color: cfg.color, width: cfg.lineWidth ?? 1 }),
|
||||
fill: getFillColor(cfg),
|
||||
points: 3,
|
||||
radius,
|
||||
rotation: Math.PI / 4,
|
||||
rotation: (rotation * Math.PI) / 180,
|
||||
angle: 0,
|
||||
}),
|
||||
text: textLabel(cfg),
|
||||
@ -158,6 +160,7 @@ const makers: SymbolMaker[] = [
|
||||
aliasIds: [MarkerShapePath.star],
|
||||
make: (cfg: StyleConfigValues) => {
|
||||
const radius = cfg.size ?? DEFAULT_SIZE;
|
||||
const rotation = cfg.rotation ?? 0;
|
||||
return new Style({
|
||||
image: new RegularShape({
|
||||
stroke: new Stroke({ color: cfg.color, width: cfg.lineWidth ?? 1 }),
|
||||
@ -166,6 +169,7 @@ const makers: SymbolMaker[] = [
|
||||
radius,
|
||||
radius2: radius * 0.4,
|
||||
angle: 0,
|
||||
rotation: (rotation * Math.PI) / 180,
|
||||
}),
|
||||
text: textLabel(cfg),
|
||||
});
|
||||
@ -177,6 +181,7 @@ const makers: SymbolMaker[] = [
|
||||
aliasIds: [MarkerShapePath.cross],
|
||||
make: (cfg: StyleConfigValues) => {
|
||||
const radius = cfg.size ?? DEFAULT_SIZE;
|
||||
const rotation = cfg.rotation ?? 0;
|
||||
return new Style({
|
||||
image: new RegularShape({
|
||||
stroke: new Stroke({ color: cfg.color, width: cfg.lineWidth ?? 1 }),
|
||||
@ -184,6 +189,7 @@ const makers: SymbolMaker[] = [
|
||||
radius,
|
||||
radius2: 0,
|
||||
angle: 0,
|
||||
rotation: (rotation * Math.PI) / 180,
|
||||
}),
|
||||
text: textLabel(cfg),
|
||||
});
|
||||
@ -195,13 +201,14 @@ const makers: SymbolMaker[] = [
|
||||
aliasIds: [MarkerShapePath.x],
|
||||
make: (cfg: StyleConfigValues) => {
|
||||
const radius = cfg.size ?? DEFAULT_SIZE;
|
||||
const rotation = cfg.rotation ?? 0;
|
||||
return new Style({
|
||||
image: new RegularShape({
|
||||
stroke: new Stroke({ color: cfg.color, width: cfg.lineWidth ?? 1 }),
|
||||
points: 4,
|
||||
radius,
|
||||
radius2: 0,
|
||||
angle: Math.PI / 4,
|
||||
rotation: (rotation * Math.PI) / 180 + Math.PI / 4,
|
||||
}),
|
||||
text: textLabel(cfg),
|
||||
});
|
||||
@ -265,6 +272,7 @@ export async function getMarkerMaker(symbol?: string, hasTextLabel?: boolean): P
|
||||
make: src
|
||||
? (cfg: StyleConfigValues) => {
|
||||
const radius = cfg.size ?? DEFAULT_SIZE;
|
||||
const rotation = cfg.rotation ?? 0;
|
||||
return [
|
||||
new Style({
|
||||
image: new Icon({
|
||||
@ -272,6 +280,7 @@ export async function getMarkerMaker(symbol?: string, hasTextLabel?: boolean): P
|
||||
color: cfg.color,
|
||||
opacity: cfg.opacity ?? 1,
|
||||
scale: (DEFAULT_SIZE + radius) / 100,
|
||||
rotation: (rotation * Math.PI) / 180,
|
||||
}),
|
||||
text: !cfg?.text ? undefined : textLabel(cfg),
|
||||
}),
|
||||
@ -281,7 +290,7 @@ export async function getMarkerMaker(symbol?: string, hasTextLabel?: boolean): P
|
||||
fill: new Fill({ color: 'rgba(0,0,0,0)' }),
|
||||
points: 4,
|
||||
radius: cfg.size,
|
||||
angle: Math.PI / 4,
|
||||
rotation: (rotation * Math.PI) / 180 + Math.PI / 4,
|
||||
}),
|
||||
}),
|
||||
];
|
||||
|
@ -4,6 +4,8 @@ import {
|
||||
ResourceDimensionConfig,
|
||||
ResourceDimensionMode,
|
||||
ScaleDimensionConfig,
|
||||
ScalarDimensionConfig,
|
||||
ScalarDimensionMode,
|
||||
TextDimensionConfig,
|
||||
} from 'app/features/dimensions';
|
||||
import { Style } from 'ol/style';
|
||||
@ -30,6 +32,9 @@ export interface StyleConfig {
|
||||
// Can show markers and text together!
|
||||
text?: TextDimensionConfig;
|
||||
textConfig?: TextStyleConfig;
|
||||
|
||||
// Allow for rotation of markers
|
||||
rotation?: ScalarDimensionConfig;
|
||||
}
|
||||
|
||||
export const DEFAULT_SIZE = 5;
|
||||
@ -66,6 +71,12 @@ export const defaultStyleConfig = Object.freeze({
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
},
|
||||
rotation: {
|
||||
fixed: 0,
|
||||
mode: ScalarDimensionMode.Mod,
|
||||
min: -360,
|
||||
max: 360,
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
@ -99,12 +110,14 @@ export interface StyleConfigFields {
|
||||
color?: string;
|
||||
size?: string;
|
||||
text?: string;
|
||||
rotation?: string;
|
||||
}
|
||||
|
||||
export interface StyleDimensions {
|
||||
color?: DimensionSupplier<string>;
|
||||
size?: DimensionSupplier<number>;
|
||||
text?: DimensionSupplier<string>;
|
||||
rotation?: DimensionSupplier<number>;
|
||||
}
|
||||
|
||||
export interface StyleConfigState {
|
||||
|
@ -35,7 +35,7 @@ export async function getStyleConfigState(cfg?: StyleConfig): Promise<StyleConfi
|
||||
opacity: cfg.opacity ?? defaultStyleConfig.opacity,
|
||||
lineWidth: cfg.lineWidth ?? 1,
|
||||
size: cfg.size?.fixed ?? defaultStyleConfig.size.fixed,
|
||||
rotation: 0, // dynamic will follow path
|
||||
rotation: cfg.rotation?.fixed ?? defaultStyleConfig.rotation.fixed, // add ability follow path later
|
||||
},
|
||||
maker,
|
||||
};
|
||||
@ -46,6 +46,9 @@ export async function getStyleConfigState(cfg?: StyleConfig): Promise<StyleConfi
|
||||
if (cfg.size?.field?.length) {
|
||||
fields.size = cfg.size.field;
|
||||
}
|
||||
if (cfg.rotation?.field?.length) {
|
||||
fields.rotation = cfg.rotation.field;
|
||||
}
|
||||
|
||||
if (hasText) {
|
||||
state.base.text = cfg.text?.fixed;
|
||||
|
@ -31,6 +31,9 @@ export const getFeatures = (
|
||||
if (dims.size) {
|
||||
values.size = dims.size.get(i);
|
||||
}
|
||||
if (dims.rotation) {
|
||||
values.rotation = dims.rotation.get(i);
|
||||
}
|
||||
if (dims.text) {
|
||||
values.text = dims.text.get(i);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user