Browse Source

UI: Trip map settings.

pull/12807/head
Igor Kulikov 2 years ago
parent
commit
73a654d784
  1. 4
      ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/trips-data-layer.ts
  2. 35
      ui-ngx/src/app/modules/home/components/widget/lib/maps/models/map.models.ts
  3. 71
      ui-ngx/src/app/modules/home/components/widget/lib/maps/models/marker-shape.models.ts
  4. 11
      ui-ngx/src/app/modules/home/components/widget/lib/maps/panels/map-timeline-panel.component.html
  5. 8
      ui-ngx/src/app/modules/home/components/widget/lib/maps/panels/map-timeline-panel.component.scss
  6. 36
      ui-ngx/src/app/modules/home/components/widget/lib/maps/panels/map-timeline-panel.component.ts
  7. 2
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-pattern-settings.component.html
  8. 3
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-pattern-settings.component.ts
  9. 2
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-action-button-row.component.html
  10. 2
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-action-button-row.component.ts
  11. 2
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-action-buttons-settings.component.html
  12. 2
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-action-buttons-settings.component.ts
  13. 172
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.html
  14. 124
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.ts
  15. 12
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-row.component.html
  16. 30
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-row.component.ts
  17. 4
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layers.component.html
  18. 4
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layers.component.ts
  19. 9
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.html
  20. 4
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.models.ts
  21. 12
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.ts
  22. 10
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/marker-shape-settings.component.ts
  23. 13
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/marker-shapes.component.ts
  24. 81
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/trip-timeline-settings.component.html
  25. 172
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/trip-timeline-settings.component.ts
  26. 4
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts
  27. 1
      ui-ngx/src/app/modules/home/components/widget/lib/settings/map/map-widget-settings.component.html
  28. 12
      ui-ngx/src/app/modules/home/components/widget/lib/settings/map/map-widget-settings.component.ts
  29. 31
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  30. 4
      ui-ngx/src/assets/markers/tripShape1.svg
  31. 10
      ui-ngx/src/assets/markers/tripShape10.svg
  32. 15
      ui-ngx/src/assets/markers/tripShape2.svg
  33. 9
      ui-ngx/src/assets/markers/tripShape3.svg
  34. 9
      ui-ngx/src/assets/markers/tripShape4.svg
  35. 10
      ui-ngx/src/assets/markers/tripShape5.svg
  36. 10
      ui-ngx/src/assets/markers/tripShape6.svg
  37. 11
      ui-ngx/src/assets/markers/tripShape7.svg
  38. 9
      ui-ngx/src/assets/markers/tripShape8.svg
  39. 10
      ui-ngx/src/assets/markers/tripShape9.svg

4
ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/trips-data-layer.ts

@ -331,7 +331,7 @@ export class TbTripsDataLayer extends TbMapDataLayer<TripsDataLayerSettings, TbT
let minTime = Infinity; let minTime = Infinity;
let maxTime = -Infinity; let maxTime = -Infinity;
this.rawTripsData = this.rawTripsData =
tripsData.filter(d => d[0].$datasource.mapDataIds.includes(this.mapDataId)).map( tripsData.filter(d => !!d.length && d[0].$datasource.mapDataIds.includes(this.mapDataId)).map(
item => this.clearIncorrectFirsLastDatapoint(item)).filter(arr => arr.length); item => this.clearIncorrectFirsLastDatapoint(item)).filter(arr => arr.length);
this.latestTripsData = tripsLatestData.filter(d => d.$datasource.mapDataIds.includes(this.mapDataId)); this.latestTripsData = tripsLatestData.filter(d => d.$datasource.mapDataIds.includes(this.mapDataId));
this.rawTripsData.forEach((dataSource) => { this.rawTripsData.forEach((dataSource) => {
@ -396,7 +396,7 @@ export class TbTripsDataLayer extends TbMapDataLayer<TripsDataLayerSettings, TbT
} }
public createColoredMarkerIcon(icon: string, color: tinycolor.Instance, rotationAngle = 0, size = 34): Observable<MarkerIconInfo> { public createColoredMarkerIcon(icon: string, color: tinycolor.Instance, rotationAngle = 0, size = 34): Observable<MarkerIconInfo> {
return createColorMarkerIconElement(this.getCtx().$injector.get(MatIconRegistry), this.getCtx().$injector.get(DomSanitizer), icon, color).pipe( return createColorMarkerIconElement(this.getCtx().$injector.get(MatIconRegistry), this.getCtx().$injector.get(DomSanitizer), icon, color, true).pipe(
map((element) => { map((element) => {
element.style.transform = `rotate(${rotationAngle}deg)`; element.style.transform = `rotate(${rotationAngle}deg)`;
return { return {

35
ui-ngx/src/app/modules/home/components/widget/lib/maps/models/map.models.ts

@ -157,7 +157,7 @@ export const defaultBaseDataLayerSettings = (mapType: MapType): Partial<MapDataL
} }
}) })
export type MapDataLayerType = 'markers' | 'polygons' | 'circles'; export type MapDataLayerType = 'trips' | 'markers' | 'polygons' | 'circles';
export const mapDataLayerValid = (dataLayer: MapDataLayerSettings, type: MapDataLayerType): boolean => { export const mapDataLayerValid = (dataLayer: MapDataLayerSettings, type: MapDataLayerType): boolean => {
if (!dataLayer.dsType || ![DatasourceType.function, DatasourceType.device, DatasourceType.entity].includes(dataLayer.dsType)) { if (!dataLayer.dsType || ![DatasourceType.function, DatasourceType.device, DatasourceType.entity].includes(dataLayer.dsType)) {
@ -307,13 +307,13 @@ const defaultMarkerYPosFunction = 'var value = prevValue || 0.3;\n' +
'}\n' + '}\n' +
'return value;'; 'return value;';
export const defaultMarkersDataLayerSettings = (mapType: MapType, functionsOnly = false): MarkersDataLayerSettings => mergeDeep({ const defaultMarkersDataSourceSettings = (mapType: MapType, timeSeries = false, functionsOnly = false): Partial<MarkersDataLayerSettings> => ({
dsType: functionsOnly ? DatasourceType.function : DatasourceType.entity, dsType: functionsOnly ? DatasourceType.function : DatasourceType.entity,
dsLabel: functionsOnly ? 'First point' : '', dsLabel: functionsOnly ? 'First point' : '',
xKey: { xKey: {
name: functionsOnly ? 'f(x)' : (MapType.geoMap === mapType ? 'latitude' : 'xPos'), name: functionsOnly ? 'f(x)' : (MapType.geoMap === mapType ? 'latitude' : 'xPos'),
label: MapType.geoMap === mapType ? 'latitude' : 'xPos', label: MapType.geoMap === mapType ? 'latitude' : 'xPos',
type: functionsOnly ? DataKeyType.function : DataKeyType.attribute, type: functionsOnly ? DataKeyType.function : (timeSeries ? DataKeyType.timeseries : DataKeyType.attribute),
funcBody: functionsOnly ? (MapType.geoMap === mapType ? defaultMarkerLatitudeFunction : defaultMarkerXPosFunction) : undefined, funcBody: functionsOnly ? (MapType.geoMap === mapType ? defaultMarkerLatitudeFunction : defaultMarkerXPosFunction) : undefined,
settings: {}, settings: {},
color: materialColors[0].value color: materialColors[0].value
@ -321,12 +321,16 @@ export const defaultMarkersDataLayerSettings = (mapType: MapType, functionsOnly
yKey: { yKey: {
name: functionsOnly ? 'f(x)' : (MapType.geoMap === mapType ? 'longitude' : 'yPos'), name: functionsOnly ? 'f(x)' : (MapType.geoMap === mapType ? 'longitude' : 'yPos'),
label: MapType.geoMap === mapType ? 'longitude' : 'yPos', label: MapType.geoMap === mapType ? 'longitude' : 'yPos',
type: functionsOnly ? DataKeyType.function : DataKeyType.attribute, type: functionsOnly ? DataKeyType.function : (timeSeries ? DataKeyType.timeseries : DataKeyType.attribute),
funcBody: functionsOnly ? (MapType.geoMap === mapType ? defaultMarkerLongitudeFunction : defaultMarkerYPosFunction) : undefined, funcBody: functionsOnly ? (MapType.geoMap === mapType ? defaultMarkerLongitudeFunction : defaultMarkerYPosFunction) : undefined,
settings: {}, settings: {},
color: materialColors[0].value color: materialColors[0].value
} }
} as MarkersDataLayerSettings, defaultBaseMarkersDataLayerSettings(mapType) as MarkersDataLayerSettings); });
export const defaultMarkersDataLayerSettings = (mapType: MapType, functionsOnly = false): MarkersDataLayerSettings => mergeDeep(
defaultMarkersDataSourceSettings(mapType, false, functionsOnly) as MarkersDataLayerSettings,
defaultBaseMarkersDataLayerSettings(mapType) as MarkersDataLayerSettings);
export const defaultBaseMarkersDataLayerSettings = (mapType: MapType): Partial<MarkersDataLayerSettings> => mergeDeep({ export const defaultBaseMarkersDataLayerSettings = (mapType: MapType): Partial<MarkersDataLayerSettings> => mergeDeep({
markerType: MarkerType.shape, markerType: MarkerType.shape,
@ -378,8 +382,8 @@ export const pathDecoratorSymbols = Object.keys(PathDecoratorSymbol) as PathDeco
export const pathDecoratorSymbolTranslationMap = new Map<PathDecoratorSymbol, string>( export const pathDecoratorSymbolTranslationMap = new Map<PathDecoratorSymbol, string>(
[ [
[PathDecoratorSymbol.arrowHead, 'widgets.maps.data-layer.decorator-symbol-arrow-head'], [PathDecoratorSymbol.arrowHead, 'widgets.maps.data-layer.path.decorator-symbol-arrow-head'],
[PathDecoratorSymbol.dash, 'widgets.maps.data-layer.decorator-symbol-dash'] [PathDecoratorSymbol.dash, 'widgets.maps.data-layer.path.decorator-symbol-dash']
] ]
); );
@ -402,6 +406,10 @@ export interface TripsDataLayerSettings extends MarkersDataLayerSettings {
pointTooltip?: DataLayerTooltipSettings; pointTooltip?: DataLayerTooltipSettings;
} }
export const defaultTripsDataLayerSettings = (mapType: MapType, functionsOnly = false): TripsDataLayerSettings => mergeDeep(
defaultMarkersDataSourceSettings(mapType, true, functionsOnly) as TripsDataLayerSettings,
defaultBaseTripsDataLayerSettings(mapType) as TripsDataLayerSettings);
export const defaultBaseTripsDataLayerSettings = (mapType: MapType): Partial<TripsDataLayerSettings> => mergeDeep( export const defaultBaseTripsDataLayerSettings = (mapType: MapType): Partial<TripsDataLayerSettings> => mergeDeep(
defaultBaseMarkersDataLayerSettings(mapType), defaultBaseMarkersDataLayerSettings(mapType),
{ {
@ -412,6 +420,15 @@ export const defaultBaseTripsDataLayerSettings = (mapType: MapType): Partial<Tri
}, },
rotateMarker: true, rotateMarker: true,
offsetAngle: 0, offsetAngle: 0,
markerShape: {
shape: MarkerShape.tripMarkerShape1
},
markerIcon: {
icon: 'arrow_forward'
},
markerImage: {
image: '/assets/markers/tripShape1.svg'
},
markerOffsetX: 0.5, markerOffsetX: 0.5,
markerOffsetY: 0.5, markerOffsetY: 0.5,
showPath: true, showPath: true,
@ -512,6 +529,8 @@ export const defaultBaseCirclesDataLayerSettings = (mapType: MapType): Partial<C
export const defaultMapDataLayerSettings = (mapType: MapType, dataLayerType: MapDataLayerType, functionsOnly = false): MapDataLayerSettings => { export const defaultMapDataLayerSettings = (mapType: MapType, dataLayerType: MapDataLayerType, functionsOnly = false): MapDataLayerSettings => {
switch (dataLayerType) { switch (dataLayerType) {
case 'trips':
return defaultTripsDataLayerSettings(mapType, functionsOnly);
case 'markers': case 'markers':
return defaultMarkersDataLayerSettings(mapType, functionsOnly); return defaultMarkersDataLayerSettings(mapType, functionsOnly);
case 'polygons': case 'polygons':
@ -523,6 +542,8 @@ export const defaultMapDataLayerSettings = (mapType: MapType, dataLayerType: Map
export const defaultBaseMapDataLayerSettings = <T extends MapDataLayerSettings>(mapType: MapType, dataLayerType: MapDataLayerType): T => { export const defaultBaseMapDataLayerSettings = <T extends MapDataLayerSettings>(mapType: MapType, dataLayerType: MapDataLayerType): T => {
switch (dataLayerType) { switch (dataLayerType) {
case 'trips':
return defaultBaseTripsDataLayerSettings(mapType) as T;
case 'markers': case 'markers':
return defaultBaseMarkersDataLayerSettings(mapType) as T; return defaultBaseMarkersDataLayerSettings(mapType) as T;
case 'polygons': case 'polygons':

71
ui-ngx/src/app/modules/home/components/widget/lib/maps/models/marker-shape.models.ts

@ -32,7 +32,17 @@ export enum MarkerShape {
markerShape7 = 'markerShape7', markerShape7 = 'markerShape7',
markerShape8 = 'markerShape8', markerShape8 = 'markerShape8',
markerShape9 = 'markerShape9', markerShape9 = 'markerShape9',
markerShape10 = 'markerShape10' markerShape10 = 'markerShape10',
tripMarkerShape1 = 'tripMarkerShape1',
tripMarkerShape2 = 'tripMarkerShape2',
tripMarkerShape3 = 'tripMarkerShape3',
tripMarkerShape4 = 'tripMarkerShape4',
tripMarkerShape5 = 'tripMarkerShape5',
tripMarkerShape6 = 'tripMarkerShape6',
tripMarkerShape7 = 'tripMarkerShape7',
tripMarkerShape8 = 'tripMarkerShape8',
tripMarkerShape9 = 'tripMarkerShape9',
tripMarkerShape10 = 'tripMarkerShape10'
} }
export const markerShapeMap = new Map<MarkerShape, string>( export const markerShapeMap = new Map<MarkerShape, string>(
@ -46,21 +56,63 @@ export const markerShapeMap = new Map<MarkerShape, string>(
[MarkerShape.markerShape7, '/assets/markers/shape7.svg'], [MarkerShape.markerShape7, '/assets/markers/shape7.svg'],
[MarkerShape.markerShape8, '/assets/markers/shape8.svg'], [MarkerShape.markerShape8, '/assets/markers/shape8.svg'],
[MarkerShape.markerShape9, '/assets/markers/shape9.svg'], [MarkerShape.markerShape9, '/assets/markers/shape9.svg'],
[MarkerShape.markerShape10, '/assets/markers/shape10.svg'] [MarkerShape.markerShape10, '/assets/markers/shape10.svg'],
[MarkerShape.tripMarkerShape1, '/assets/markers/tripShape1.svg'],
[MarkerShape.tripMarkerShape2, '/assets/markers/tripShape2.svg'],
[MarkerShape.tripMarkerShape3, '/assets/markers/tripShape3.svg'],
[MarkerShape.tripMarkerShape4, '/assets/markers/tripShape4.svg'],
[MarkerShape.tripMarkerShape5, '/assets/markers/tripShape5.svg'],
[MarkerShape.tripMarkerShape6, '/assets/markers/tripShape6.svg'],
[MarkerShape.tripMarkerShape7, '/assets/markers/tripShape7.svg'],
[MarkerShape.tripMarkerShape8, '/assets/markers/tripShape8.svg'],
[MarkerShape.tripMarkerShape9, '/assets/markers/tripShape9.svg'],
[MarkerShape.tripMarkerShape10, '/assets/markers/tripShape10.svg']
] ]
); );
export const markerShapes = [
MarkerShape.markerShape1,
MarkerShape.markerShape2,
MarkerShape.markerShape3,
MarkerShape.markerShape4,
MarkerShape.markerShape5,
MarkerShape.markerShape6,
MarkerShape.markerShape7,
MarkerShape.markerShape8,
MarkerShape.markerShape9,
MarkerShape.markerShape10
];
export const tripMarkerShapes = [
MarkerShape.tripMarkerShape1,
MarkerShape.tripMarkerShape2,
MarkerShape.tripMarkerShape3,
MarkerShape.tripMarkerShape4,
MarkerShape.tripMarkerShape5,
MarkerShape.tripMarkerShape6,
MarkerShape.tripMarkerShape7,
MarkerShape.tripMarkerShape8,
MarkerShape.tripMarkerShape9,
MarkerShape.tripMarkerShape10
];
const createColorMarkerShape = (iconRegistry: MatIconRegistry, domSanitizer: DomSanitizer, shape: MarkerShape, color: tinycolor.Instance): Observable<SVGElement> => { const createColorMarkerShape = (iconRegistry: MatIconRegistry, domSanitizer: DomSanitizer, shape: MarkerShape, color: tinycolor.Instance): Observable<SVGElement> => {
const markerAssetUrl = markerShapeMap.get(shape); const markerAssetUrl = markerShapeMap.get(shape);
const safeUrl = domSanitizer.bypassSecurityTrustResourceUrl(markerAssetUrl); const safeUrl = domSanitizer.bypassSecurityTrustResourceUrl(markerAssetUrl);
return iconRegistry.getSvgIconFromUrl(safeUrl).pipe( return iconRegistry.getSvgIconFromUrl(safeUrl).pipe(
map((svgElement) => { map((svgElement) => {
const colorElements = Array.from(svgElement.getElementsByClassName('marker-color')); const colorElements = Array.from(svgElement.getElementsByClassName('marker-color'));
if (svgElement.classList.contains('marker-color')) {
colorElements.push(svgElement);
}
colorElements.forEach(el => { colorElements.forEach(el => {
el.setAttribute('fill', '#'+color.toHex()); el.setAttribute('fill', '#'+color.toHex());
el.setAttribute('fill-opacity', `${color.getAlpha()}`); el.setAttribute('fill-opacity', `${color.getAlpha()}`);
}); });
const strokeElements = Array.from(svgElement.getElementsByClassName('marker-stroke')); const strokeElements = Array.from(svgElement.getElementsByClassName('marker-stroke'));
if (svgElement.classList.contains('marker-stroke')) {
strokeElements.push(svgElement);
}
strokeElements.forEach(el => { strokeElements.forEach(el => {
el.setAttribute('stroke', '#'+color.toHex()); el.setAttribute('stroke', '#'+color.toHex());
el.setAttribute('stroke-opacity', `${color.getAlpha()}`); el.setAttribute('stroke-opacity', `${color.getAlpha()}`);
@ -80,10 +132,10 @@ export const createColorMarkerShapeURI = (iconRegistry: MatIconRegistry, domSani
); );
} }
const createIconElement = (iconRegistry: MatIconRegistry, icon: string, size: number, color: tinycolor.Instance): Observable<Element> => { const createIconElement = (iconRegistry: MatIconRegistry, icon: string, size: number, color: tinycolor.Instance, trip = false): Observable<Element> => {
const isSvg = isSvgIcon(icon); const isSvg = isSvgIcon(icon);
const iconAlpha = color.getAlpha(); const iconAlpha = color.getAlpha();
const iconColor = tinycolor.mix(color.clone().setAlpha(1), tinycolor('rgba(0,0,0,0.38)')); const iconColor = trip ? color : tinycolor.mix(color.clone().setAlpha(1), tinycolor('rgba(0,0,0,0.38)'));
if (isSvg) { if (isSvg) {
const [namespace, iconName] = splitIconName(icon); const [namespace, iconName] = splitIconName(icon);
return iconRegistry return iconRegistry
@ -122,14 +174,19 @@ const createIconElement = (iconRegistry: MatIconRegistry, icon: string, size: nu
} }
} }
export const createColorMarkerIconElement = (iconRegistry: MatIconRegistry, domSanitizer: DomSanitizer, icon: string, color: tinycolor.Instance): Observable<SVGElement> => { const markerIconShape = MarkerShape.markerShape6;
return createColorMarkerShape(iconRegistry, domSanitizer, MarkerShape.markerShape6, color).pipe( const tripMarkerIconShape = MarkerShape.tripMarkerShape2;
export const createColorMarkerIconElement = (iconRegistry: MatIconRegistry, domSanitizer: DomSanitizer, icon: string, color: tinycolor.Instance,
trip = false): Observable<SVGElement> => {
return createColorMarkerShape(iconRegistry, domSanitizer, trip ? tripMarkerIconShape : markerIconShape, color).pipe(
switchMap((svgElement) => { switchMap((svgElement) => {
return createIconElement(iconRegistry, icon, 12, color).pipe( return createIconElement(iconRegistry, icon, trip ? 24 : 12, trip ? tinycolor('#fff') : color, trip).pipe(
map((iconElement) => { map((iconElement) => {
let elements = svgElement.getElementsByClassName('marker-icon-container'); let elements = svgElement.getElementsByClassName('marker-icon-container');
if (iconElement && elements.length) { if (iconElement && elements.length) {
const iconContainer = new G(elements[0] as SVGGElement); const iconContainer = new G(elements[0] as SVGGElement);
iconContainer.clear();
iconContainer.add(iconElement); iconContainer.add(iconElement);
const box = iconElement.bbox(); const box = iconElement.bbox();
iconElement.translate(-box.cx, -box.cy); iconElement.translate(-box.cx, -box.cy);

11
ui-ngx/src/app/modules/home/components/widget/lib/maps/panels/map-timeline-panel.component.html

@ -23,4 +23,15 @@
[step]="settings.timeStep"> [step]="settings.timeStep">
<input matSliderThumb [(ngModel)]="currentTime" (valueChange)="onTimeChange()"> <input matSliderThumb [(ngModel)]="currentTime" (valueChange)="onTimeChange()">
</mat-slider> </mat-slider>
<div class="tb-timeline-controls">
<div class="flex flex-1 flex-row items-center justify-start">
<div *ngIf="settings.showTimestamp" [innerHTML]="timestampFormat.formatted"></div>
</div>
<div class="tb-timeline-buttons flex flex-1 flex-row items-center justify-center gap-3">2</div>
<div class="flex flex-1 flex-row items-center justify-end">
<mat-select class="w-auto" [(ngModel)]="speed" (selectionChange)="speedUpdated()" aria-label="Speed selector">
<mat-option [value]="speedValue" *ngFor="let speedValue of settings.speedOptions">{{speedValue}}x</mat-option>
</mat-select>
</div>
</div>
</div> </div>

8
ui-ngx/src/app/modules/home/components/widget/lib/maps/panels/map-timeline-panel.component.scss

@ -14,6 +14,14 @@
* limitations under the License. * limitations under the License.
*/ */
.tb-map-timeline-panel { .tb-map-timeline-panel {
padding-top: 4px;
padding-left: 16px;
padding-right: 16px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
.tb-timeline-controls {
display: flex;
flex-direction: row;
align-items: center;
}
} }

36
ui-ngx/src/app/modules/home/components/widget/lib/maps/panels/map-timeline-panel.component.ts

@ -15,16 +15,18 @@
/// ///
import { import {
ChangeDetectorRef,
Component, Component,
ElementRef, ElementRef,
EventEmitter, EventEmitter, Injector,
Input, Input, OnChanges,
OnDestroy, OnDestroy,
OnInit, OnInit,
Output, Output,
ViewEncapsulation ViewEncapsulation
} from '@angular/core'; } from '@angular/core';
import { TripTimelineSettings } from '@home/components/widget/lib/maps/models/map.models'; import { TripTimelineSettings } from '@home/components/widget/lib/maps/models/map.models';
import { DateFormatProcessor } from '@shared/models/widget-settings.models';
@Component({ @Component({
selector: 'tb-map-timeline-panel', selector: 'tb-map-timeline-panel',
@ -32,7 +34,7 @@ import { TripTimelineSettings } from '@home/components/widget/lib/maps/models/ma
styleUrls: ['./map-timeline-panel.component.scss'], styleUrls: ['./map-timeline-panel.component.scss'],
encapsulation: ViewEncapsulation.None encapsulation: ViewEncapsulation.None
}) })
export class MapTimelinePanelComponent implements OnInit, OnDestroy { export class MapTimelinePanelComponent implements OnInit, OnChanges, OnDestroy {
@Input() @Input()
settings: TripTimelineSettings; settings: TripTimelineSettings;
@ -51,18 +53,42 @@ export class MapTimelinePanelComponent implements OnInit, OnDestroy {
currentTime = 0; currentTime = 0;
constructor(public element: ElementRef<HTMLElement>) { timestampFormat: DateFormatProcessor;
speed: number;
constructor(public element: ElementRef<HTMLElement>,
private cd: ChangeDetectorRef,
private injector: Injector) {
} }
ngOnInit() { ngOnInit() {
if (this.settings.showTimestamp) {
this.timestampFormat = DateFormatProcessor.fromSettings(this.injector, this.settings.timestampFormat);
this.timestampFormat.update(this.currentTime);
}
this.speed = this.settings.speedOptions[0];
}
ngOnChanges() {
this.currentTime = this.min === Infinity ? 0 : this.min;
if (this.settings.showTimestamp) {
this.timestampFormat.update(this.currentTime);
this.cd.markForCheck();
}
} }
ngOnDestroy() { ngOnDestroy() {
} }
public onTimeChange() { public onTimeChange() {
//this.updateValueText(); if (this.settings.showTimestamp) {
this.timestampFormat.update(this.currentTime);
this.cd.markForCheck();
}
this.timeChanged.next(this.currentTime); this.timeChanged.next(this.currentTime);
} }
public speedUpdated() {}
} }

2
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-pattern-settings.component.html

@ -23,7 +23,7 @@
<mat-panel-title> <mat-panel-title>
<div class="flex flex-1 flex-row items-center justify-between"> <div class="flex flex-1 flex-row items-center justify-between">
<mat-slide-toggle class="mat-slide flex items-stretch justify-center" formControlName="show" (click)="$event.stopPropagation()"> <mat-slide-toggle class="mat-slide flex items-stretch justify-center" formControlName="show" (click)="$event.stopPropagation()">
{{ (patternType === 'label' ? 'widgets.maps.data-layer.label' : 'widgets.maps.data-layer.tooltip') | translate }} {{ patternTitle ? patternTitle : ((patternType === 'label' ? 'widgets.maps.data-layer.label' : 'widgets.maps.data-layer.tooltip') | translate) }}
</mat-slide-toggle> </mat-slide-toggle>
<tb-toggle-select [class.!hidden]="!expansionPanel.expanded" formControlName="type" (click)="$event.stopPropagation()"> <tb-toggle-select [class.!hidden]="!expansionPanel.expanded" formControlName="type" (click)="$event.stopPropagation()">
<tb-toggle-option [value]="DataLayerPatternType.pattern">{{ 'widgets.maps.data-layer.pattern-type-pattern' | translate }}</tb-toggle-option> <tb-toggle-option [value]="DataLayerPatternType.pattern">{{ 'widgets.maps.data-layer.pattern-type-pattern' | translate }}</tb-toggle-option>

3
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-pattern-settings.component.ts

@ -71,6 +71,9 @@ export class DataLayerPatternSettingsComponent implements OnInit, ControlValueAc
@Input() @Input()
patternType: 'label' | 'tooltip' = 'label'; patternType: 'label' | 'tooltip' = 'label';
@Input()
patternTitle: string;
@Input() @Input()
@coerceBoolean() @coerceBoolean()
hasTooltipOffset = false; hasTooltipOffset = false;

2
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-action-button-row.component.html

@ -1,6 +1,6 @@
<!-- <!--
Copyright © 2016-2024 The Thingsboard Authors Copyright © 2016-2025 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

2
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-action-button-row.component.ts

@ -1,5 +1,5 @@
/// ///
/// Copyright © 2016-2024 The Thingsboard Authors /// Copyright © 2016-2025 The Thingsboard Authors
/// ///
/// Licensed under the Apache License, Version 2.0 (the "License"); /// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License. /// you may not use this file except in compliance with the License.

2
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-action-buttons-settings.component.html

@ -1,6 +1,6 @@
<!-- <!--
Copyright © 2016-2024 The Thingsboard Authors Copyright © 2016-2025 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

2
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-action-buttons-settings.component.ts

@ -1,5 +1,5 @@
/// ///
/// Copyright © 2016-2024 The Thingsboard Authors /// Copyright © 2016-2025 The Thingsboard Authors
/// ///
/// Licensed under the Apache License, Version 2.0 (the "License"); /// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License. /// you may not use this file except in compliance with the License.

172
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.html

@ -69,7 +69,7 @@
<div class="tb-form-panel"> <div class="tb-form-panel">
<div class="tb-form-panel-title">{{ 'datakey.keys' | translate }}</div> <div class="tb-form-panel-title">{{ 'datakey.keys' | translate }}</div>
<div class="flex flex-col"> <div class="flex flex-col">
<div *ngIf="dataLayerType === 'markers'" class="flex flex-row gap-3"> <div *ngIf="['trips', 'markers'].includes(dataLayerType)" class="flex flex-row gap-3">
<tb-data-key-input <tb-data-key-input
class="flex-1" class="flex-1"
inlineField="false" inlineField="false"
@ -82,9 +82,9 @@
[entityAliasId]="dataLayerFormGroup.get('dsEntityAliasId').value" [entityAliasId]="dataLayerFormGroup.get('dsEntityAliasId').value"
[deviceId]="dataLayerFormGroup.get('dsDeviceId').value" [deviceId]="dataLayerFormGroup.get('dsDeviceId').value"
[aliasController]="context.aliasController" [aliasController]="context.aliasController"
[widgetType]="widgetType.latest" [widgetType]="dataLayerType === 'trips' ? widgetType.timeseries : widgetType.latest"
[dataKeyType]="context.functionsOnly ? DataKeyType.function : null" [dataKeyType]="context.functionsOnly ? DataKeyType.function : null"
[dataKeyTypes]="[DataKeyType.attribute, DataKeyType.timeseries]" [dataKeyTypes]="dataLayerType === 'trips' ? [DataKeyType.timeseries] : [DataKeyType.attribute, DataKeyType.timeseries]"
[callbacks]="context.callbacks" [callbacks]="context.callbacks"
[generateKey]="context.generateDataKey" [generateKey]="context.generateDataKey"
(keyEdit)="editKey('xKey')" (keyEdit)="editKey('xKey')"
@ -96,16 +96,15 @@
appearance="outline" appearance="outline"
subscriptSizing="fixed" subscriptSizing="fixed"
label="{{ (mapType === MapType.geoMap ? 'widgets.maps.data-layer.marker.longitude-key' : 'widgets.maps.data-layer.marker.y-pos-key') | translate }}" label="{{ (mapType === MapType.geoMap ? 'widgets.maps.data-layer.marker.longitude-key' : 'widgets.maps.data-layer.marker.y-pos-key') | translate }}"
*ngIf="dataLayerType === 'markers'"
required required
requiredText="{{ mapType === MapType.geoMap ? 'widgets.maps.data-layer.marker.longitude-key-required' : 'widgets.maps.data-layer.marker.y-pos-key-required' }}" requiredText="{{ mapType === MapType.geoMap ? 'widgets.maps.data-layer.marker.longitude-key-required' : 'widgets.maps.data-layer.marker.y-pos-key-required' }}"
[datasourceType]="dataLayerFormGroup.get('dsType').value" [datasourceType]="dataLayerFormGroup.get('dsType').value"
[entityAliasId]="dataLayerFormGroup.get('dsEntityAliasId').value" [entityAliasId]="dataLayerFormGroup.get('dsEntityAliasId').value"
[deviceId]="dataLayerFormGroup.get('dsDeviceId').value" [deviceId]="dataLayerFormGroup.get('dsDeviceId').value"
[aliasController]="context.aliasController" [aliasController]="context.aliasController"
[widgetType]="widgetType.latest" [widgetType]="dataLayerType === 'trips' ? widgetType.timeseries : widgetType.latest"
[dataKeyType]="context.functionsOnly ? DataKeyType.function : null" [dataKeyType]="context.functionsOnly ? DataKeyType.function : null"
[dataKeyTypes]="[DataKeyType.attribute, DataKeyType.timeseries]" [dataKeyTypes]="dataLayerType === 'trips' ? [DataKeyType.timeseries] : [DataKeyType.attribute, DataKeyType.timeseries]"
[callbacks]="context.callbacks" [callbacks]="context.callbacks"
[generateKey]="context.generateDataKey" [generateKey]="context.generateDataKey"
(keyEdit)="editKey('yKey')" (keyEdit)="editKey('yKey')"
@ -176,10 +175,10 @@
</div> </div>
<div class="tb-form-panel"> <div class="tb-form-panel">
<div class="tb-form-panel-title">{{ 'widget-config.appearance' | translate }}</div> <div class="tb-form-panel-title">{{ 'widget-config.appearance' | translate }}</div>
<ng-container *ngIf="dataLayerType === 'markers'"> <ng-container *ngIf="['trips', 'markers'].includes(dataLayerType)">
<div class="tb-form-panel stroked"> <div class="tb-form-panel stroked">
<div class="flex flex-row items-center justify-between"> <div class="flex flex-row items-center justify-between">
<div class="tb-form-panel-title">{{ 'widgets.maps.data-layer.marker.marker-type' | translate }}</div> <div class="tb-form-panel-title">{{ 'widgets.maps.data-layer.marker.marker' | translate }}</div>
<tb-toggle-select formControlName="markerType"> <tb-toggle-select formControlName="markerType">
<tb-toggle-option [value]="MarkerType.shape">{{ 'widgets.maps.data-layer.marker.marker-type-shape' | translate }}</tb-toggle-option> <tb-toggle-option [value]="MarkerType.shape">{{ 'widgets.maps.data-layer.marker.marker-type-shape' | translate }}</tb-toggle-option>
<tb-toggle-option [value]="MarkerType.icon">{{ 'widgets.maps.data-layer.marker.marker-type-icon' | translate }}</tb-toggle-option> <tb-toggle-option [value]="MarkerType.icon">{{ 'widgets.maps.data-layer.marker.marker-type-icon' | translate }}</tb-toggle-option>
@ -188,30 +187,42 @@
</div> </div>
<div *ngIf="dataLayerFormGroup.get('markerType').value === MarkerType.shape" class="tb-form-row space-between"> <div *ngIf="dataLayerFormGroup.get('markerType').value === MarkerType.shape" class="tb-form-row space-between">
<div translate>widgets.maps.data-layer.marker.shape</div> <div translate>widgets.maps.data-layer.marker.shape</div>
<tb-marker-shape-settings formControlName="markerShape" [markerType]="MarkerType.shape"></tb-marker-shape-settings> <tb-marker-shape-settings formControlName="markerShape" [trip]="dataLayerType === 'trips'" [markerType]="MarkerType.shape"></tb-marker-shape-settings>
</div> </div>
<div *ngIf="dataLayerFormGroup.get('markerType').value === MarkerType.icon" class="tb-form-row space-between"> <div *ngIf="dataLayerFormGroup.get('markerType').value === MarkerType.icon" class="tb-form-row space-between">
<div translate>widgets.maps.data-layer.marker.icon</div> <div translate>widgets.maps.data-layer.marker.icon</div>
<tb-marker-shape-settings formControlName="markerIcon" [markerType]="MarkerType.icon"></tb-marker-shape-settings> <tb-marker-shape-settings formControlName="markerIcon" [trip]="dataLayerType === 'trips'" [markerType]="MarkerType.icon"></tb-marker-shape-settings>
</div> </div>
<div *ngIf="dataLayerFormGroup.get('markerType').value === MarkerType.image" class="tb-form-row space-between"> <div *ngIf="dataLayerFormGroup.get('markerType').value === MarkerType.image" class="tb-form-row space-between">
<div translate>widgets.maps.data-layer.marker.image</div> <div translate>widgets.maps.data-layer.marker.image</div>
<tb-marker-image-settings formControlName="markerImage"></tb-marker-image-settings> <tb-marker-image-settings formControlName="markerImage"></tb-marker-image-settings>
</div> </div>
</div> <div class="tb-form-row space-between column-xs">
<div class="tb-form-row space-between column-xs"> <div translate>widgets.maps.data-layer.marker.marker-offset</div>
<div translate>widgets.maps.data-layer.marker.marker-offset</div> <div class="flex flex-row items-center justify-start gap-2">
<div class="flex flex-row items-center justify-start gap-2"> <div class="tb-small-label" translate>widgets.maps.data-layer.marker.offset-horizontal</div>
<div class="tb-small-label" translate>widgets.maps.data-layer.marker.offset-horizontal</div> <mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic"> <input matInput formControlName="markerOffsetX"
<input matInput formControlName="markerOffsetX" type="number" placeholder="{{ 'widget-config.set' | translate }}">
type="number" placeholder="{{ 'widget-config.set' | translate }}"> </mat-form-field>
</mat-form-field> <div class="tb-small-label" translate>widgets.maps.data-layer.marker.offset-vertical</div>
<div class="tb-small-label" translate>widgets.maps.data-layer.marker.offset-vertical</div> <mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic"> <input matInput formControlName="markerOffsetY"
<input matInput formControlName="markerOffsetY" type="number" placeholder="{{ 'widget-config.set' | translate }}">
type="number" placeholder="{{ 'widget-config.set' | translate }}"> </mat-form-field>
</mat-form-field> </div>
</div>
<div *ngIf="dataLayerType === 'trips'" class="tb-form-row space-between">
<mat-slide-toggle class="mat-slide" formControlName="rotateMarker">
{{ 'widgets.maps.data-layer.marker.rotate-marker' | translate }}
</mat-slide-toggle>
<div class="flex flex-row items-center justify-start gap-2">
<div class="tb-small-label" translate>widgets.maps.data-layer.marker.offset-angle</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="offsetAngle" type="number" min="0" max="360" step="1" placeholder="{{ 'widget-config.set' | translate }}">
<div matSuffix>deg</div>
</mat-form-field>
</div>
</div> </div>
</div> </div>
<div *ngIf="mapType === MapType.image" class="tb-form-panel stroked"> <div *ngIf="mapType === MapType.image" class="tb-form-panel stroked">
@ -226,6 +237,115 @@
</tb-js-func> </tb-js-func>
</div> </div>
</ng-container> </ng-container>
<ng-container *ngIf="dataLayerType === 'trips'">
<div class="tb-form-panel tb-slide-toggle stroked">
<mat-expansion-panel class="tb-settings" [expanded]="dataLayerFormGroup.get('showPath').value"
[disabled]="!dataLayerFormGroup.get('showPath').value">
<mat-expansion-panel-header class="flex flex-row flex-wrap">
<mat-panel-title>
<mat-slide-toggle class="mat-slide flex items-stretch justify-center" formControlName="showPath" (click)="$event.stopPropagation()">
{{ 'widgets.maps.data-layer.path.path' | translate }}
</mat-slide-toggle>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<div class="tb-form-row space-between">
<div translate>widgets.maps.data-layer.path.path</div>
<div class="flex flex-row items-center gap-2">
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput type="number" min="0" formControlName="pathStrokeWeight" placeholder="{{ 'widget-config.set' | translate }}">
<span matSuffix>px</span>
</mat-form-field>
<tb-data-layer-color-settings formControlName="pathStrokeColor"></tb-data-layer-color-settings>
</div>
</div>
<div class="tb-form-panel tb-slide-toggle stroked">
<mat-expansion-panel class="tb-settings" [expanded]="dataLayerFormGroup.get('usePathDecorator').value"
[disabled]="!dataLayerFormGroup.get('usePathDecorator').value">
<mat-expansion-panel-header class="flex flex-row flex-wrap">
<mat-panel-title>
<mat-slide-toggle class="mat-slide flex items-stretch justify-center" formControlName="usePathDecorator" (click)="$event.stopPropagation()">
{{ 'widgets.maps.data-layer.path.path-decorator' | translate }}
</mat-slide-toggle>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<div class="tb-form-row">
<div class="fixed-title-width" translate>widgets.maps.data-layer.path.decorator-symbol</div>
<div class="flex flex-1 flex-row items-center justify-start gap-2">
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="pathDecoratorSymbol">
<mat-option *ngFor="let symbol of pathDecoratorSymbols" [value]="symbol">
{{ pathDecoratorSymbolTranslationMap.get(symbol) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput type="number" min="0" formControlName="pathDecoratorSymbolSize" placeholder="{{ 'widget-config.set' | translate }}">
<span matSuffix>px</span>
</mat-form-field>
<tb-color-input asBoxInput
formControlName="pathDecoratorSymbolColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-row space-between">
<div translate>widgets.maps.data-layer.path.decorator-arrangement</div>
<div class="flex flex-row items-center justify-start gap-2">
<div class="tb-small-label" translate>widgets.maps.data-layer.path.decorator-offset</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="pathDecoratorOffset" type="number" min="0" placeholder="{{ 'widget-config.set' | translate }}">
<div matSuffix>px</div>
</mat-form-field>
<div class="tb-small-label" translate>widgets.maps.data-layer.path.decorator-end-offset</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="pathEndDecoratorOffset" type="number" min="0" placeholder="{{ 'widget-config.set' | translate }}">
<div matSuffix>px</div>
</mat-form-field>
<div class="tb-small-label" translate>widgets.maps.data-layer.path.decorator-repeat</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="pathDecoratorRepeat" type="number" min="0" placeholder="{{ 'widget-config.set' | translate }}">
<div matSuffix>px</div>
</mat-form-field>
</div>
</div>
</ng-template>
</mat-expansion-panel>
</div>
</ng-template>
</mat-expansion-panel>
</div>
<div class="tb-form-panel tb-slide-toggle stroked">
<mat-expansion-panel class="tb-settings" [expanded]="dataLayerFormGroup.get('showPoints').value"
[disabled]="!dataLayerFormGroup.get('showPoints').value">
<mat-expansion-panel-header class="flex flex-row flex-wrap">
<mat-panel-title>
<mat-slide-toggle class="mat-slide flex items-stretch justify-center" formControlName="showPoints" (click)="$event.stopPropagation()">
{{ 'widgets.maps.data-layer.points.points' | translate }}
</mat-slide-toggle>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<div class="tb-form-row space-between">
<div translate>widgets.maps.data-layer.points.points</div>
<div class="flex flex-row items-center gap-2">
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput type="number" min="0" formControlName="pointSize" placeholder="{{ 'widget-config.set' | translate }}">
<span matSuffix>px</span>
</mat-form-field>
<tb-data-layer-color-settings formControlName="pointColor"></tb-data-layer-color-settings>
</div>
</div>
<tb-data-layer-pattern-settings
patternType="tooltip"
patternTitle="{{ 'widgets.maps.data-layer.points.point-tooltip' | translate }}"
[context]="context"
formControlName="pointTooltip">
</tb-data-layer-pattern-settings>
</ng-template>
</mat-expansion-panel>
</div>
</ng-container>
<ng-container *ngIf="['polygons', 'circles'].includes(dataLayerType)"> <ng-container *ngIf="['polygons', 'circles'].includes(dataLayerType)">
<div class="tb-form-row space-between"> <div class="tb-form-row space-between">
<div translate>widgets.maps.data-layer.fill-color</div> <div translate>widgets.maps.data-layer.fill-color</div>
@ -250,7 +370,7 @@
<tb-data-layer-pattern-settings <tb-data-layer-pattern-settings
patternType="tooltip" patternType="tooltip"
[context]="context" [context]="context"
[hasTooltipOffset]="dataLayerType === 'markers'" [hasTooltipOffset]="['trips', 'markers'].includes(dataLayerType)"
formControlName="tooltip"> formControlName="tooltip">
</tb-data-layer-pattern-settings> </tb-data-layer-pattern-settings>
</div> </div>
@ -276,7 +396,7 @@
formControlName="groups"> formControlName="groups">
</tb-string-items-list> </tb-string-items-list>
</div> </div>
<div class="tb-form-panel" formGroupName="edit"> <div *ngIf="dataLayerType !== 'trips'" class="tb-form-panel" formGroupName="edit">
<div class="tb-form-panel-title">{{ dataLayerEditTitle | translate }}</div> <div class="tb-form-panel-title">{{ dataLayerEditTitle | translate }}</div>
<div class="tb-form-row space-between column-lt-md"> <div class="tb-form-row space-between column-lt-md">
<div translate>widgets.maps.data-layer.edit-instruments</div> <div translate>widgets.maps.data-layer.edit-instruments</div>

124
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.ts

@ -26,9 +26,9 @@ import {
MapDataLayerType, MapDataLayerType,
MapType, MapType,
MarkersDataLayerSettings, MarkersDataLayerSettings,
MarkerType, MarkerType, pathDecoratorSymbols, pathDecoratorSymbolTranslationMap,
PolygonsDataLayerSettings, PolygonsDataLayerSettings,
ShapeDataLayerSettings ShapeDataLayerSettings, TripsDataLayerSettings
} from '@home/components/widget/lib/maps/models/map.models'; } from '@home/components/widget/lib/maps/models/map.models';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
@ -42,6 +42,7 @@ import { EntityType } from '@shared/models/entity-type.models';
import { MapSettingsContext } from '@home/components/widget/lib/settings/common/map/map-settings.component.models'; import { MapSettingsContext } from '@home/components/widget/lib/settings/common/map/map-settings.component.models';
import { genNextLabelForDataKeys, mergeDeepIgnoreArray } from '@core/utils'; import { genNextLabelForDataKeys, mergeDeepIgnoreArray } from '@core/utils';
import { WidgetService } from '@core/http/widget.service'; import { WidgetService } from '@core/http/widget.service';
import { merge } from 'rxjs';
export interface MapDataLayerDialogData { export interface MapDataLayerDialogData {
settings: MapDataLayerSettings; settings: MapDataLayerSettings;
@ -77,6 +78,9 @@ export class MapDataLayerDialogComponent extends DialogComponent<MapDataLayerDia
dataLayerEditActions: Array<DataLayerEditAction> = []; dataLayerEditActions: Array<DataLayerEditAction> = [];
dataLayerEditActionTranslationMap = dataLayerEditActionTranslationMap; dataLayerEditActionTranslationMap = dataLayerEditActionTranslationMap;
pathDecoratorSymbols = pathDecoratorSymbols;
pathDecoratorSymbolTranslationMap = pathDecoratorSymbolTranslationMap;
dataLayerFormGroup: UntypedFormGroup; dataLayerFormGroup: UntypedFormGroup;
settings = this.data.settings; settings = this.data.settings;
@ -118,14 +122,57 @@ export class MapDataLayerDialogComponent extends DialogComponent<MapDataLayerDia
label: [this.settings.label, []], label: [this.settings.label, []],
tooltip: [this.settings.tooltip, []], tooltip: [this.settings.tooltip, []],
click: [this.settings.click, []], click: [this.settings.click, []],
groups: [this.settings.groups, []], groups: [this.settings.groups, []]
edit: this.fb.group({ });
if (this.dataLayerType !== 'trips') {
this.dataLayerFormGroup.addControl('edit', this.fb.group({
enabledActions: [this.settings.edit?.enabledActions, []], enabledActions: [this.settings.edit?.enabledActions, []],
snappable: [this.settings.edit?.snappable, []] snappable: [this.settings.edit?.snappable, []]
}) }));
}); }
switch (this.dataLayerType) { switch (this.dataLayerType) {
case 'trips':
this.dialogTitle = 'widgets.maps.data-layer.trip.trip-configuration';
const tripsDataLayer = this.settings as TripsDataLayerSettings;
this.dataLayerFormGroup.addControl('xKey', this.fb.control(tripsDataLayer.xKey, Validators.required));
this.dataLayerFormGroup.addControl('yKey', this.fb.control(tripsDataLayer.yKey, Validators.required));
this.dataLayerFormGroup.addControl('markerType', this.fb.control(tripsDataLayer.markerType, Validators.required));
this.dataLayerFormGroup.addControl('markerShape', this.fb.control(tripsDataLayer.markerShape, Validators.required));
this.dataLayerFormGroup.addControl('markerIcon', this.fb.control(tripsDataLayer.markerIcon, Validators.required));
this.dataLayerFormGroup.addControl('markerImage', this.fb.control(tripsDataLayer.markerImage, Validators.required));
this.dataLayerFormGroup.addControl('markerOffsetX', this.fb.control(tripsDataLayer.markerOffsetX));
this.dataLayerFormGroup.addControl('markerOffsetY', this.fb.control(tripsDataLayer.markerOffsetY));
this.dataLayerFormGroup.addControl('rotateMarker', this.fb.control(tripsDataLayer.rotateMarker));
this.dataLayerFormGroup.addControl('offsetAngle', this.fb.control(tripsDataLayer.offsetAngle, [Validators.min(0), Validators.max(360)]));
if (this.mapType === MapType.image) {
this.dataLayerFormGroup.addControl('positionFunction', this.fb.control(tripsDataLayer.positionFunction));
}
this.dataLayerFormGroup.addControl('showPath', this.fb.control(tripsDataLayer.showPath));
this.dataLayerFormGroup.addControl('pathStrokeWeight', this.fb.control(tripsDataLayer.pathStrokeWeight, [Validators.required, Validators.min(0)]));
this.dataLayerFormGroup.addControl('pathStrokeColor', this.fb.control(tripsDataLayer.pathStrokeColor, Validators.required));
this.dataLayerFormGroup.addControl('usePathDecorator', this.fb.control(tripsDataLayer.usePathDecorator));
this.dataLayerFormGroup.addControl('pathDecoratorSymbol', this.fb.control(tripsDataLayer.pathDecoratorSymbol, Validators.required));
this.dataLayerFormGroup.addControl('pathDecoratorSymbolSize', this.fb.control(tripsDataLayer.pathDecoratorSymbolSize, [Validators.required, Validators.min(0)]));
this.dataLayerFormGroup.addControl('pathDecoratorSymbolColor', this.fb.control(tripsDataLayer.pathDecoratorSymbolColor, Validators.required));
this.dataLayerFormGroup.addControl('pathDecoratorOffset', this.fb.control(tripsDataLayer.pathDecoratorOffset, [Validators.required, Validators.min(0)]));
this.dataLayerFormGroup.addControl('pathEndDecoratorOffset', this.fb.control(tripsDataLayer.pathEndDecoratorOffset, [Validators.required, Validators.min(0)]));
this.dataLayerFormGroup.addControl('pathDecoratorRepeat', this.fb.control(tripsDataLayer.pathDecoratorRepeat, [Validators.required, Validators.min(0)]));
this.dataLayerFormGroup.addControl('showPoints', this.fb.control(tripsDataLayer.showPoints));
this.dataLayerFormGroup.addControl('pointSize', this.fb.control(tripsDataLayer.pointSize, [Validators.required, Validators.min(0)]));
this.dataLayerFormGroup.addControl('pointColor', this.fb.control(tripsDataLayer.pointColor, Validators.required));
this.dataLayerFormGroup.addControl('pointTooltip', this.fb.control(tripsDataLayer.pointTooltip));
merge(this.dataLayerFormGroup.get('markerType').valueChanges,
this.dataLayerFormGroup.get('rotateMarker').valueChanges,
this.dataLayerFormGroup.get('showPath').valueChanges,
this.dataLayerFormGroup.get('usePathDecorator').valueChanges,
this.dataLayerFormGroup.get('showPoints').valueChanges).pipe(
takeUntilDestroyed(this.destroyRef)
).subscribe(() =>
this.updateValidators()
);
break;
case 'markers': case 'markers':
this.dialogTitle = 'widgets.maps.data-layer.marker.marker-configuration'; this.dialogTitle = 'widgets.maps.data-layer.marker.marker-configuration';
this.dataLayerEditTitle = 'widgets.maps.data-layer.marker.edit'; this.dataLayerEditTitle = 'widgets.maps.data-layer.marker.edit';
@ -179,13 +226,14 @@ export class MapDataLayerDialogComponent extends DialogComponent<MapDataLayerDia
private onDsTypeChanged(newDsType: DatasourceType) { private onDsTypeChanged(newDsType: DatasourceType) {
switch (this.dataLayerType) { switch (this.dataLayerType) {
case 'trips':
case 'markers': case 'markers':
const xKey: DataKey = this.dataLayerFormGroup.get('xKey').value; const xKey: DataKey = this.dataLayerFormGroup.get('xKey').value;
if (this.updateDataKeyToNewDsType(xKey, newDsType)) { if (this.updateDataKeyToNewDsType(xKey, newDsType, this.dataLayerType === 'trips')) {
this.dataLayerFormGroup.get('xKey').patchValue(xKey, {emitEvent: false}); this.dataLayerFormGroup.get('xKey').patchValue(xKey, {emitEvent: false});
} }
const yKey: DataKey = this.dataLayerFormGroup.get('yKey').value; const yKey: DataKey = this.dataLayerFormGroup.get('yKey').value;
if (this.updateDataKeyToNewDsType(yKey, newDsType)) { if (this.updateDataKeyToNewDsType(yKey, newDsType, this.dataLayerType === 'trips')) {
this.dataLayerFormGroup.get('yKey').patchValue(yKey, {emitEvent: false}); this.dataLayerFormGroup.get('yKey').patchValue(yKey, {emitEvent: false});
} }
break; break;
@ -215,7 +263,7 @@ export class MapDataLayerDialogComponent extends DialogComponent<MapDataLayerDia
this.updateValidators(); this.updateValidators();
} }
private updateDataKeyToNewDsType(dataKey: DataKey, newDsType: DatasourceType): boolean { private updateDataKeyToNewDsType(dataKey: DataKey, newDsType: DatasourceType, timeSeries = false): boolean {
if (newDsType === DatasourceType.function) { if (newDsType === DatasourceType.function) {
if (dataKey.type !== DataKeyType.function) { if (dataKey.type !== DataKeyType.function) {
dataKey.type = DataKeyType.function; dataKey.type = DataKeyType.function;
@ -223,7 +271,7 @@ export class MapDataLayerDialogComponent extends DialogComponent<MapDataLayerDia
} }
} else { } else {
if (dataKey.type === DataKeyType.function) { if (dataKey.type === DataKeyType.function) {
dataKey.type = DataKeyType.attribute; dataKey.type = timeSeries ? DataKeyType.timeseries : DataKeyType.attribute;
return true; return true;
} }
} }
@ -245,7 +293,7 @@ export class MapDataLayerDialogComponent extends DialogComponent<MapDataLayerDia
this.dataLayerFormGroup.get('dsDeviceId').disable({emitEvent: false}); this.dataLayerFormGroup.get('dsDeviceId').disable({emitEvent: false});
this.dataLayerFormGroup.get('dsEntityAliasId').enable({emitEvent: false}); this.dataLayerFormGroup.get('dsEntityAliasId').enable({emitEvent: false});
} }
if (this.dataLayerType === 'markers') { if (['trips', 'markers'].includes(this.dataLayerType)) {
const markerType: MarkerType = this.dataLayerFormGroup.get('markerType').value; const markerType: MarkerType = this.dataLayerFormGroup.get('markerType').value;
if (markerType === MarkerType.shape) { if (markerType === MarkerType.shape) {
this.dataLayerFormGroup.get('markerShape').enable({emitEvent: false}); this.dataLayerFormGroup.get('markerShape').enable({emitEvent: false});
@ -260,13 +308,64 @@ export class MapDataLayerDialogComponent extends DialogComponent<MapDataLayerDia
this.dataLayerFormGroup.get('markerIcon').disable({emitEvent: false}); this.dataLayerFormGroup.get('markerIcon').disable({emitEvent: false});
this.dataLayerFormGroup.get('markerImage').enable({emitEvent: false}); this.dataLayerFormGroup.get('markerImage').enable({emitEvent: false});
} }
if (this.dataLayerType === 'trips') {
const rotateMarker: boolean = this.dataLayerFormGroup.get('rotateMarker').value;
const showPath: boolean = this.dataLayerFormGroup.get('showPath').value;
const usePathDecorator: boolean = this.dataLayerFormGroup.get('usePathDecorator').value;
const showPoints: boolean = this.dataLayerFormGroup.get('showPoints').value;
if (rotateMarker) {
this.dataLayerFormGroup.get('offsetAngle').enable({emitEvent: false});
} else {
this.dataLayerFormGroup.get('offsetAngle').disable({emitEvent: false});
}
if (showPath) {
this.dataLayerFormGroup.get('pathStrokeWeight').enable({emitEvent: false});
this.dataLayerFormGroup.get('pathStrokeColor').enable({emitEvent: false});
this.dataLayerFormGroup.get('usePathDecorator').enable({emitEvent: false});
if (usePathDecorator) {
this.dataLayerFormGroup.get('pathDecoratorSymbol').enable({emitEvent: false});
this.dataLayerFormGroup.get('pathDecoratorSymbolSize').enable({emitEvent: false});
this.dataLayerFormGroup.get('pathDecoratorSymbolColor').enable({emitEvent: false});
this.dataLayerFormGroup.get('pathDecoratorOffset').enable({emitEvent: false});
this.dataLayerFormGroup.get('pathEndDecoratorOffset').enable({emitEvent: false});
this.dataLayerFormGroup.get('pathDecoratorRepeat').enable({emitEvent: false});
} else {
this.dataLayerFormGroup.get('pathDecoratorSymbol').disable({emitEvent: false});
this.dataLayerFormGroup.get('pathDecoratorSymbolSize').disable({emitEvent: false});
this.dataLayerFormGroup.get('pathDecoratorSymbolColor').disable({emitEvent: false});
this.dataLayerFormGroup.get('pathDecoratorOffset').disable({emitEvent: false});
this.dataLayerFormGroup.get('pathEndDecoratorOffset').disable({emitEvent: false});
this.dataLayerFormGroup.get('pathDecoratorRepeat').disable({emitEvent: false});
}
} else {
this.dataLayerFormGroup.get('pathStrokeWeight').disable({emitEvent: false});
this.dataLayerFormGroup.get('pathStrokeColor').disable({emitEvent: false});
this.dataLayerFormGroup.get('usePathDecorator').disable({emitEvent: false});
this.dataLayerFormGroup.get('pathDecoratorSymbol').disable({emitEvent: false});
this.dataLayerFormGroup.get('pathDecoratorSymbolSize').disable({emitEvent: false});
this.dataLayerFormGroup.get('pathDecoratorSymbolColor').disable({emitEvent: false});
this.dataLayerFormGroup.get('pathDecoratorOffset').disable({emitEvent: false});
this.dataLayerFormGroup.get('pathEndDecoratorOffset').disable({emitEvent: false});
this.dataLayerFormGroup.get('pathDecoratorRepeat').disable({emitEvent: false});
}
if (showPoints) {
this.dataLayerFormGroup.get('pointSize').enable({emitEvent: false});
this.dataLayerFormGroup.get('pointColor').enable({emitEvent: false});
this.dataLayerFormGroup.get('pointTooltip').enable({emitEvent: false});
} else {
this.dataLayerFormGroup.get('pointSize').disable({emitEvent: false});
this.dataLayerFormGroup.get('pointColor').disable({emitEvent: false});
this.dataLayerFormGroup.get('pointTooltip').disable({emitEvent: false});
}
}
} }
} }
editKey(keyType: 'xKey' | 'yKey' | 'polygonKey' | 'circleKey') { editKey(keyType: 'xKey' | 'yKey' | 'polygonKey' | 'circleKey') {
const targetDataKey: DataKey = this.dataLayerFormGroup.get(keyType).value; const targetDataKey: DataKey = this.dataLayerFormGroup.get(keyType).value;
this.context.editKey(targetDataKey, this.context.editKey(targetDataKey,
this.dataLayerFormGroup.get('dsDeviceId').value, this.dataLayerFormGroup.get('dsEntityAliasId').value).subscribe( this.dataLayerFormGroup.get('dsDeviceId').value, this.dataLayerFormGroup.get('dsEntityAliasId').value,
this.dataLayerType === 'trips' ? widgetType.timeseries : widgetType.latest).subscribe(
(updatedDataKey) => { (updatedDataKey) => {
if (updatedDataKey) { if (updatedDataKey) {
this.dataLayerFormGroup.get(keyType).patchValue(updatedDataKey); this.dataLayerFormGroup.get(keyType).patchValue(updatedDataKey);
@ -279,6 +378,7 @@ export class MapDataLayerDialogComponent extends DialogComponent<MapDataLayerDia
const dataKey = this.context.callbacks.generateDataKey(key.name, key.type, null, false, null); const dataKey = this.context.callbacks.generateDataKey(key.name, key.type, null, false, null);
const dataKeys: DataKey[] = []; const dataKeys: DataKey[] = [];
switch (this.dataLayerType) { switch (this.dataLayerType) {
case 'trips':
case 'markers': case 'markers':
const xKey: DataKey = this.dataLayerFormGroup.get('xKey').value; const xKey: DataKey = this.dataLayerFormGroup.get('xKey').value;
if (xKey) { if (xKey) {

12
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-row.component.html

@ -50,16 +50,16 @@
</div> </div>
<tb-data-key-input <tb-data-key-input
class="tb-x-pos-field" class="tb-x-pos-field"
*ngIf="dataLayerType === 'markers'" *ngIf="['trips', 'markers'].includes(dataLayerType)"
required required
requiredText="{{ mapType === MapType.geoMap ? 'widgets.maps.data-layer.marker.latitude-key-required' : 'widgets.maps.data-layer.marker.x-pos-key-required' }}" requiredText="{{ mapType === MapType.geoMap ? 'widgets.maps.data-layer.marker.latitude-key-required' : 'widgets.maps.data-layer.marker.x-pos-key-required' }}"
[datasourceType]="dataLayerFormGroup.get('dsType').value" [datasourceType]="dataLayerFormGroup.get('dsType').value"
[entityAliasId]="dataLayerFormGroup.get('dsEntityAliasId').value" [entityAliasId]="dataLayerFormGroup.get('dsEntityAliasId').value"
[deviceId]="dataLayerFormGroup.get('dsDeviceId').value" [deviceId]="dataLayerFormGroup.get('dsDeviceId').value"
[aliasController]="context.aliasController" [aliasController]="context.aliasController"
[widgetType]="widgetType.latest" [widgetType]="dataLayerType === 'trips' ? widgetType.timeseries : widgetType.latest"
[dataKeyType]="context.functionsOnly ? DataKeyType.function : null" [dataKeyType]="context.functionsOnly ? DataKeyType.function : null"
[dataKeyTypes]="[DataKeyType.attribute, DataKeyType.timeseries]" [dataKeyTypes]="dataLayerType === 'trips' ? [DataKeyType.timeseries] : [DataKeyType.attribute, DataKeyType.timeseries]"
[callbacks]="context.callbacks" [callbacks]="context.callbacks"
[generateKey]="context.generateDataKey" [generateKey]="context.generateDataKey"
(keyEdit)="editKey('xKey')" (keyEdit)="editKey('xKey')"
@ -67,16 +67,16 @@
</tb-data-key-input> </tb-data-key-input>
<tb-data-key-input <tb-data-key-input
class="tb-y-pos-field" class="tb-y-pos-field"
*ngIf="dataLayerType === 'markers'" *ngIf="['trips', 'markers'].includes(dataLayerType)"
required required
requiredText="{{ mapType === MapType.geoMap ? 'widgets.maps.data-layer.marker.longitude-key-required' : 'widgets.maps.data-layer.marker.y-pos-key-required' }}" requiredText="{{ mapType === MapType.geoMap ? 'widgets.maps.data-layer.marker.longitude-key-required' : 'widgets.maps.data-layer.marker.y-pos-key-required' }}"
[datasourceType]="dataLayerFormGroup.get('dsType').value" [datasourceType]="dataLayerFormGroup.get('dsType').value"
[entityAliasId]="dataLayerFormGroup.get('dsEntityAliasId').value" [entityAliasId]="dataLayerFormGroup.get('dsEntityAliasId').value"
[deviceId]="dataLayerFormGroup.get('dsDeviceId').value" [deviceId]="dataLayerFormGroup.get('dsDeviceId').value"
[aliasController]="context.aliasController" [aliasController]="context.aliasController"
[widgetType]="widgetType.latest" [widgetType]="dataLayerType === 'trips' ? widgetType.timeseries : widgetType.latest"
[dataKeyType]="context.functionsOnly ? DataKeyType.function : null" [dataKeyType]="context.functionsOnly ? DataKeyType.function : null"
[dataKeyTypes]="[DataKeyType.attribute, DataKeyType.timeseries]" [dataKeyTypes]="dataLayerType === 'trips' ? [DataKeyType.timeseries] : [DataKeyType.attribute, DataKeyType.timeseries]"
[callbacks]="context.callbacks" [callbacks]="context.callbacks"
[generateKey]="context.generateDataKey" [generateKey]="context.generateDataKey"
(keyEdit)="editKey('yKey')" (keyEdit)="editKey('yKey')"

30
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-row.component.ts

@ -40,7 +40,8 @@ import {
MapDataLayerType, MapDataLayerType,
MapType, MapType,
MarkersDataLayerSettings, MarkersDataLayerSettings,
PolygonsDataLayerSettings PolygonsDataLayerSettings,
TripsDataLayerSettings
} from '@home/components/widget/lib/maps/models/map.models'; } from '@home/components/widget/lib/maps/models/map.models';
import { DataKey, DatasourceType, datasourceTypeTranslationMap, widgetType } from '@shared/models/widget.models'; import { DataKey, DatasourceType, datasourceTypeTranslationMap, widgetType } from '@shared/models/widget.models';
import { EntityType } from '@shared/models/entity-type.models'; import { EntityType } from '@shared/models/entity-type.models';
@ -126,6 +127,12 @@ export class MapDataLayerRowComponent implements ControlValueAccessor, OnInit {
dsEntityAliasId: [null, [Validators.required]] dsEntityAliasId: [null, [Validators.required]]
}); });
switch (this.dataLayerType) { switch (this.dataLayerType) {
case 'trips':
this.editDataLayerText = 'widgets.maps.data-layer.trip.trip-configuration';
this.removeDataLayerText = 'widgets.maps.data-layer.trip.remove-trip';
this.dataLayerFormGroup.addControl('xKey', this.fb.control(null, Validators.required));
this.dataLayerFormGroup.addControl('yKey', this.fb.control(null, Validators.required));
break;
case 'markers': case 'markers':
this.editDataLayerText = 'widgets.maps.data-layer.marker.marker-configuration'; this.editDataLayerText = 'widgets.maps.data-layer.marker.marker-configuration';
this.removeDataLayerText = 'widgets.maps.data-layer.marker.remove-marker'; this.removeDataLayerText = 'widgets.maps.data-layer.marker.remove-marker';
@ -183,6 +190,15 @@ export class MapDataLayerRowComponent implements ControlValueAccessor, OnInit {
}, {emitEvent: false} }, {emitEvent: false}
); );
switch (this.dataLayerType) { switch (this.dataLayerType) {
case 'trips':
const tripsDataLayer = value as TripsDataLayerSettings;
this.dataLayerFormGroup.patchValue(
{
xKey: tripsDataLayer?.xKey,
yKey: tripsDataLayer?.yKey
}, {emitEvent: false}
);
break;
case 'markers': case 'markers':
const markersDataLayer = value as MarkersDataLayerSettings; const markersDataLayer = value as MarkersDataLayerSettings;
this.dataLayerFormGroup.patchValue( this.dataLayerFormGroup.patchValue(
@ -216,7 +232,8 @@ export class MapDataLayerRowComponent implements ControlValueAccessor, OnInit {
editKey(keyType: 'xKey' | 'yKey' | 'polygonKey' | 'circleKey') { editKey(keyType: 'xKey' | 'yKey' | 'polygonKey' | 'circleKey') {
const targetDataKey: DataKey = this.dataLayerFormGroup.get(keyType).value; const targetDataKey: DataKey = this.dataLayerFormGroup.get(keyType).value;
this.context.editKey(targetDataKey, this.context.editKey(targetDataKey,
this.dataLayerFormGroup.get('dsDeviceId').value, this.dataLayerFormGroup.get('dsEntityAliasId').value).subscribe( this.dataLayerFormGroup.get('dsDeviceId').value, this.dataLayerFormGroup.get('dsEntityAliasId').value,
this.dataLayerType === 'trips' ? widgetType.timeseries : widgetType.latest).subscribe(
(updatedDataKey) => { (updatedDataKey) => {
if (updatedDataKey) { if (updatedDataKey) {
this.dataLayerFormGroup.get(keyType).patchValue(updatedDataKey); this.dataLayerFormGroup.get(keyType).patchValue(updatedDataKey);
@ -251,14 +268,15 @@ export class MapDataLayerRowComponent implements ControlValueAccessor, OnInit {
private onDsTypeChanged(newDsType: DatasourceType) { private onDsTypeChanged(newDsType: DatasourceType) {
let updateModel = false; let updateModel = false;
switch (this.dataLayerType) { switch (this.dataLayerType) {
case 'trips':
case 'markers': case 'markers':
const xKey: DataKey = this.dataLayerFormGroup.get('xKey').value; const xKey: DataKey = this.dataLayerFormGroup.get('xKey').value;
if (this.updateDataKeyToNewDsType(xKey, newDsType)) { if (this.updateDataKeyToNewDsType(xKey, newDsType, this.dataLayerType === 'trips')) {
this.dataLayerFormGroup.get('xKey').patchValue(xKey, {emitEvent: false}); this.dataLayerFormGroup.get('xKey').patchValue(xKey, {emitEvent: false});
updateModel = true; updateModel = true;
} }
const yKey: DataKey = this.dataLayerFormGroup.get('yKey').value; const yKey: DataKey = this.dataLayerFormGroup.get('yKey').value;
if (this.updateDataKeyToNewDsType(yKey, newDsType)) { if (this.updateDataKeyToNewDsType(yKey, newDsType, this.dataLayerType === 'trips')) {
this.dataLayerFormGroup.get('yKey').patchValue(yKey, {emitEvent: false}); this.dataLayerFormGroup.get('yKey').patchValue(yKey, {emitEvent: false});
updateModel = true; updateModel = true;
} }
@ -284,7 +302,7 @@ export class MapDataLayerRowComponent implements ControlValueAccessor, OnInit {
} }
} }
private updateDataKeyToNewDsType(dataKey: DataKey, newDsType: DatasourceType): boolean { private updateDataKeyToNewDsType(dataKey: DataKey, newDsType: DatasourceType, timeSeries = false): boolean {
if (newDsType === DatasourceType.function) { if (newDsType === DatasourceType.function) {
if (dataKey.type !== DataKeyType.function) { if (dataKey.type !== DataKeyType.function) {
dataKey.type = DataKeyType.function; dataKey.type = DataKeyType.function;
@ -292,7 +310,7 @@ export class MapDataLayerRowComponent implements ControlValueAccessor, OnInit {
} }
} else { } else {
if (dataKey.type === DataKeyType.function) { if (dataKey.type === DataKeyType.function) {
dataKey.type = DataKeyType.attribute; dataKey.type = timeSeries ? DataKeyType.timeseries : DataKeyType.attribute;
return true; return true;
} }
} }

4
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layers.component.html

@ -19,10 +19,10 @@
<div class="tb-form-table tb-map-data-layers"> <div class="tb-form-table tb-map-data-layers">
<div class="tb-form-table-header no-padding-right"> <div class="tb-form-table-header no-padding-right">
<div class="tb-form-table-header-cell tb-source-header" translate>widgets.maps.data-layer.source</div> <div class="tb-form-table-header-cell tb-source-header" translate>widgets.maps.data-layer.source</div>
<div *ngIf="dataLayerType === 'markers'" class="tb-form-table-header-cell tb-x-pos-header"> <div *ngIf="['trips', 'markers'].includes(dataLayerType)" class="tb-form-table-header-cell tb-x-pos-header">
{{ (mapType === MapType.geoMap ? 'widgets.maps.data-layer.marker.latitude-key' : 'widgets.maps.data-layer.marker.x-pos-key') | translate }} {{ (mapType === MapType.geoMap ? 'widgets.maps.data-layer.marker.latitude-key' : 'widgets.maps.data-layer.marker.x-pos-key') | translate }}
</div> </div>
<div *ngIf="dataLayerType === 'markers'" class="tb-form-table-header-cell tb-y-pos-header"> <div *ngIf="['trips', 'markers'].includes(dataLayerType)" class="tb-form-table-header-cell tb-y-pos-header">
{{ (mapType === MapType.geoMap ? 'widgets.maps.data-layer.marker.longitude-key' : 'widgets.maps.data-layer.marker.y-pos-key') | translate }} {{ (mapType === MapType.geoMap ? 'widgets.maps.data-layer.marker.longitude-key' : 'widgets.maps.data-layer.marker.y-pos-key') | translate }}
</div> </div>
<div *ngIf="dataLayerType === 'polygons'" class="tb-form-table-header-cell tb-key-header" translate>widgets.maps.data-layer.polygon.polygon-key</div> <div *ngIf="dataLayerType === 'polygons'" class="tb-form-table-header-cell tb-key-header" translate>widgets.maps.data-layer.polygon.polygon-key</div>

4
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layers.component.ts

@ -88,6 +88,10 @@ export class MapDataLayersComponent implements ControlValueAccessor, OnInit, Val
ngOnInit() { ngOnInit() {
switch (this.dataLayerType) { switch (this.dataLayerType) {
case 'trips':
this.addDataLayerText = 'widgets.maps.data-layer.trip.add-trip';
this.noDataLayersText = 'widgets.maps.data-layer.trip.no-trips';
break;
case 'markers': case 'markers':
this.addDataLayerText = 'widgets.maps.data-layer.marker.add-marker'; this.addDataLayerText = 'widgets.maps.data-layer.marker.add-marker';
this.noDataLayersText = 'widgets.maps.data-layer.marker.no-markers'; this.noDataLayersText = 'widgets.maps.data-layer.marker.no-markers';

9
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.html

@ -41,11 +41,18 @@
</div> </div>
<tb-toggle-select [(ngModel)]="dataLayerMode" <tb-toggle-select [(ngModel)]="dataLayerMode"
[ngModelOptions]="{ standalone: true }"> [ngModelOptions]="{ standalone: true }">
<tb-toggle-option *ngIf="trip" value="trips">{{ 'widgets.maps.overlays.trips' | translate }}</tb-toggle-option>
<tb-toggle-option value="markers">{{ 'widgets.maps.overlays.markers' | translate }}</tb-toggle-option> <tb-toggle-option value="markers">{{ 'widgets.maps.overlays.markers' | translate }}</tb-toggle-option>
<tb-toggle-option value="polygons">{{ 'widgets.maps.overlays.polygons' | translate }}</tb-toggle-option> <tb-toggle-option value="polygons">{{ 'widgets.maps.overlays.polygons' | translate }}</tb-toggle-option>
<tb-toggle-option value="circles">{{ 'widgets.maps.overlays.circles' | translate }}</tb-toggle-option> <tb-toggle-option value="circles">{{ 'widgets.maps.overlays.circles' | translate }}</tb-toggle-option>
</tb-toggle-select> </tb-toggle-select>
</div> </div>
<tb-map-data-layers *ngIf="trip"
[class.!hidden]="dataLayerMode !== 'trips'"
formControlName="trips"
dataLayerType="trips"
[context]="context"
[mapType]="mapSettingsFormGroup.get('mapType').value"></tb-map-data-layers>
<tb-map-data-layers [class.!hidden]="dataLayerMode !== 'markers'" <tb-map-data-layers [class.!hidden]="dataLayerMode !== 'markers'"
formControlName="markers" formControlName="markers"
dataLayerType="markers" dataLayerType="markers"
@ -92,6 +99,8 @@
</mat-chip-option> </mat-chip-option>
</mat-chip-listbox> </mat-chip-listbox>
</div> </div>
<tb-trip-timeline-settings *ngIf="trip"
formControlName="tripTimeline"></tb-trip-timeline-settings>
</div> </div>
<tb-map-action-button-settings formControlName="mapActionButtons"> <tb-map-action-button-settings formControlName="mapActionButtons">
</tb-map-action-button-settings> </tb-map-action-button-settings>

4
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.models.ts

@ -16,7 +16,7 @@
import { IAliasController } from '@core/api/widget-api.models'; import { IAliasController } from '@core/api/widget-api.models';
import { WidgetConfigCallbacks } from '@home/components/widget/config/widget-config.component.models'; import { WidgetConfigCallbacks } from '@home/components/widget/config/widget-config.component.models';
import { DataKey, Widget } from '@shared/models/widget.models'; import { DataKey, Widget, widgetType } from '@shared/models/widget.models';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
export interface MapSettingsContext { export interface MapSettingsContext {
@ -24,6 +24,6 @@ export interface MapSettingsContext {
aliasController: IAliasController; aliasController: IAliasController;
callbacks: WidgetConfigCallbacks; callbacks: WidgetConfigCallbacks;
widget: Widget; widget: Widget;
editKey: (key: DataKey, deviceId: string, entityAliasId: string) => Observable<DataKey>; editKey: (key: DataKey, deviceId: string, entityAliasId: string, WidgetType?: widgetType) => Observable<DataKey>;
generateDataKey: (key: DataKey) => DataKey; generateDataKey: (key: DataKey) => DataKey;
} }

12
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.ts

@ -116,6 +116,10 @@ export class MapSettingsComponent implements OnInit, ControlValueAccessor, Valid
ngOnInit(): void { ngOnInit(): void {
if (this.trip) {
this.dataLayerMode = 'trips';
}
this.context = { this.context = {
functionsOnly: this.functionsOnly, functionsOnly: this.functionsOnly,
aliasController: this.aliasController, aliasController: this.aliasController,
@ -142,6 +146,10 @@ export class MapSettingsComponent implements OnInit, ControlValueAccessor, Valid
mapPageSize: [null, [Validators.min(1), Validators.required]], mapPageSize: [null, [Validators.min(1), Validators.required]],
mapActionButtons: [null] mapActionButtons: [null]
}); });
if (this.trip) {
this.mapSettingsFormGroup.addControl('trips', this.fb.control(null));
this.mapSettingsFormGroup.addControl('tripTimeline', this.fb.control(null));
}
this.mapSettingsFormGroup.valueChanges.pipe( this.mapSettingsFormGroup.valueChanges.pipe(
takeUntilDestroyed(this.destroyRef) takeUntilDestroyed(this.destroyRef)
).subscribe(() => { ).subscribe(() => {
@ -234,7 +242,7 @@ export class MapSettingsComponent implements OnInit, ControlValueAccessor, Valid
this.propagateChange(this.modelValue); this.propagateChange(this.modelValue);
} }
private editKey(key: DataKey, deviceId: string, entityAliasId: string): Observable<DataKey> { private editKey(key: DataKey, deviceId: string, entityAliasId: string, _widgetType = widgetType.latest): Observable<DataKey> {
return this.dialog.open<DataKeyConfigDialogComponent, DataKeyConfigDialogData, DataKey>(DataKeyConfigDialogComponent, return this.dialog.open<DataKeyConfigDialogComponent, DataKeyConfigDialogData, DataKey>(DataKeyConfigDialogComponent,
{ {
disableClose: true, disableClose: true,
@ -243,7 +251,7 @@ export class MapSettingsComponent implements OnInit, ControlValueAccessor, Valid
dataKey: deepClone(key), dataKey: deepClone(key),
dataKeyConfigMode: DataKeyConfigMode.general, dataKeyConfigMode: DataKeyConfigMode.general,
aliasController: this.aliasController, aliasController: this.aliasController,
widgetType: widgetType.latest, widgetType: _widgetType,
deviceId, deviceId,
entityAliasId, entityAliasId,
showPostProcessing: true, showPostProcessing: true,

10
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/marker-shape-settings.component.ts

@ -46,6 +46,7 @@ import tinycolor from 'tinycolor2';
import { map, share } from 'rxjs/operators'; import { map, share } from 'rxjs/operators';
import { MarkerShapesComponent } from '@home/components/widget/lib/settings/common/map/marker-shapes.component'; import { MarkerShapesComponent } from '@home/components/widget/lib/settings/common/map/marker-shapes.component';
import { MaterialIconsComponent } from '@shared/components/material-icons.component'; import { MaterialIconsComponent } from '@shared/components/material-icons.component';
import { coerceBoolean } from '@shared/decorators/coercion';
@Component({ @Component({
selector: 'tb-marker-shape-settings', selector: 'tb-marker-shape-settings',
@ -69,6 +70,10 @@ export class MarkerShapeSettingsComponent implements ControlValueAccessor, OnIni
@Input() @Input()
markerType: MarkerType; markerType: MarkerType;
@Input()
@coerceBoolean()
trip = false;
modelValue: MarkerShapeSettings | MarkerIconSettings; modelValue: MarkerShapeSettings | MarkerIconSettings;
public shapeSettingsFormGroup: UntypedFormGroup; public shapeSettingsFormGroup: UntypedFormGroup;
@ -140,7 +145,8 @@ export class MarkerShapeSettingsComponent implements ControlValueAccessor, OnIni
if (this.markerType === MarkerType.shape) { if (this.markerType === MarkerType.shape) {
const ctx: any = { const ctx: any = {
shape: (this.modelValue as MarkerShapeSettings).shape, shape: (this.modelValue as MarkerShapeSettings).shape,
color: this.modelValue.color.color color: this.modelValue.color.color,
trip: this.trip
}; };
const markerShapesPopover = this.popoverService.displayPopover(trigger, this.renderer, const markerShapesPopover = this.popoverService.displayPopover(trigger, this.renderer,
this.viewContainerRef, MarkerShapesComponent, 'left', true, null, this.viewContainerRef, MarkerShapesComponent, 'left', true, null,
@ -193,7 +199,7 @@ export class MarkerShapeSettingsComponent implements ControlValueAccessor, OnIni
); );
} else if (this.markerType === MarkerType.icon) { } else if (this.markerType === MarkerType.icon) {
const icon = (this.modelValue as MarkerIconSettings).icon; const icon = (this.modelValue as MarkerIconSettings).icon;
this.iconPreview$ = createColorMarkerIconElement(this.iconRegistry, this.domSanitizer, icon, tinycolor(color)).pipe( this.iconPreview$ = createColorMarkerIconElement(this.iconRegistry, this.domSanitizer, icon, tinycolor(color), this.trip).pipe(
map((element) => { map((element) => {
return this.domSanitizer.bypassSecurityTrustHtml(element.outerHTML); return this.domSanitizer.bypassSecurityTrustHtml(element.outerHTML);
}), }),

13
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/marker-shapes.component.ts

@ -19,12 +19,17 @@ import { PageComponent } from '@shared/components/page.component';
import { TbPopoverComponent } from '@shared/components/popover.component'; import { TbPopoverComponent } from '@shared/components/popover.component';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { createColorMarkerShapeURI, MarkerShape } from '@home/components/widget/lib/maps/models/marker-shape.models'; import {
createColorMarkerShapeURI,
MarkerShape, markerShapes,
tripMarkerShapes
} from '@home/components/widget/lib/maps/models/marker-shape.models';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
import { MatIconRegistry } from '@angular/material/icon'; import { MatIconRegistry } from '@angular/material/icon';
import tinycolor from 'tinycolor2'; import tinycolor from 'tinycolor2';
import { map, share } from 'rxjs/operators'; import { map, share } from 'rxjs/operators';
import { coerceBoolean } from '@shared/decorators/coercion';
interface MarkerShapeInfo { interface MarkerShapeInfo {
shape: MarkerShape; shape: MarkerShape;
@ -46,6 +51,10 @@ export class MarkerShapesComponent extends PageComponent implements OnInit {
@Input() @Input()
color: string; color: string;
@Input()
@coerceBoolean()
trip = false;
@Input() @Input()
popover: TbPopoverComponent<MarkerShapesComponent>; popover: TbPopoverComponent<MarkerShapesComponent>;
@ -61,7 +70,7 @@ export class MarkerShapesComponent extends PageComponent implements OnInit {
} }
ngOnInit(): void { ngOnInit(): void {
this.shapes = (Object.keys(MarkerShape) as MarkerShape[]).map((shape) => { this.shapes = (this.trip ? tripMarkerShapes : markerShapes).map((shape) => {
return { return {
shape, shape,
url$: createColorMarkerShapeURI(this.iconRegistry, this.domSanitizer, shape, tinycolor(this.color)).pipe( url$: createColorMarkerShapeURI(this.iconRegistry, this.domSanitizer, shape, tinycolor(this.color)).pipe(

81
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/trip-timeline-settings.component.html

@ -0,0 +1,81 @@
<!--
Copyright © 2016-2025 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<ng-container [formGroup]="tripTimelineSettingsFormGroup">
<div class="tb-form-panel stroked tb-slide-toggle">
<mat-expansion-panel class="tb-settings" [expanded]="settingsExpanded"
[disabled]="!tripTimelineSettingsFormGroup.get('showTimelineControl').value">
<mat-expansion-panel-header class="flex flex-row flex-wrap">
<mat-panel-title>
<mat-slide-toggle class="mat-slide flex items-stretch justify-center" formControlName="showTimelineControl" (click)="$event.stopPropagation()">
{{ 'widgets.maps.timeline.control-panel' | translate }}
</mat-slide-toggle>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<div class="tb-form-row space-between">
<div translate>widgets.maps.timeline.time-step</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput type="number" min="1" step="1" formControlName="timeStep" placeholder="{{ 'widget-config.set' | translate }}">
<span matSuffix>ms</span>
</mat-form-field>
</div>
<div class="tb-form-row space-between">
<div class="fixed-title-width" translate>widgets.maps.timeline.speed-options</div>
<mat-form-field class="flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="speedOptions" multiple>
<mat-option *ngFor="let speed of [1,5,10,15,25]" [value]="speed">
{{ speed }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showTimestamp">
{{ 'widgets.maps.timeline.timestamp' | translate }}
</mat-slide-toggle>
<tb-date-format-select class="flex-1"
excludeLastUpdateAgo
formControlName="timestampFormat"></tb-date-format-select>
</div>
<div class="tb-form-panel tb-slide-toggle stroked">
<mat-expansion-panel class="tb-settings" [expanded]="tripTimelineSettingsFormGroup.get('snapToRealLocation').value"
[disabled]="!tripTimelineSettingsFormGroup.get('snapToRealLocation').value">
<mat-expansion-panel-header class="flex flex-row flex-wrap">
<mat-panel-title>
<mat-slide-toggle class="mat-slide flex items-stretch justify-center" formControlName="snapToRealLocation" (click)="$event.stopPropagation()">
{{ 'widgets.maps.timeline.snap-to-real-location' | translate }}
</mat-slide-toggle>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<tb-js-func formControlName="locationSnapFilter"
required
withModules
[globalVariables]="functionScopeVariables"
[functionArgs]="['data', 'dsData']"
functionTitle="{{ 'widgets.maps.timeline.location-snap-filter-function' | translate }}"
helpId="widget/lib/map/trip_point_as_anchor_fn">
</tb-js-func>
</ng-template>
</mat-expansion-panel>
</div>
</ng-template>
</mat-expansion-panel>
</div>
</ng-container>

172
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/trip-timeline-settings.component.ts

@ -0,0 +1,172 @@
///
/// Copyright © 2016-2025 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, DestroyRef, forwardRef, Input, OnInit } from '@angular/core';
import {
ControlValueAccessor,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
UntypedFormBuilder,
UntypedFormControl,
UntypedFormGroup,
Validator,
Validators
} from '@angular/forms';
import { merge } from 'rxjs';
import { WidgetService } from '@core/http/widget.service';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import {
DataLayerPatternSettings,
DataLayerPatternType,
DataLayerTooltipSettings,
dataLayerTooltipTriggers,
dataLayerTooltipTriggerTranslationMap,
pathDecoratorSymbols, pathDecoratorSymbolTranslationMap,
TripTimelineSettings
} from '@home/components/widget/lib/maps/models/map.models';
import { coerceBoolean } from '@shared/decorators/coercion';
import { MapSettingsContext } from '@home/components/widget/lib/settings/common/map/map-settings.component.models';
@Component({
selector: 'tb-trip-timeline-settings',
templateUrl: './trip-timeline-settings.component.html',
styleUrls: ['./../../widget-settings.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => TripTimelineSettingsComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => TripTimelineSettingsComponent),
multi: true
}
]
})
export class TripTimelineSettingsComponent implements OnInit, ControlValueAccessor, Validator {
settingsExpanded = false;
functionScopeVariables = this.widgetService.getWidgetScopeVariables();
@Input()
disabled: boolean;
private modelValue: TripTimelineSettings;
private propagateChange = null;
public tripTimelineSettingsFormGroup: UntypedFormGroup;
constructor(private fb: UntypedFormBuilder,
private widgetService: WidgetService,
private destroyRef: DestroyRef) {
}
ngOnInit(): void {
this.tripTimelineSettingsFormGroup = this.fb.group({
showTimelineControl: [null],
timeStep: [null, [Validators.required, Validators.min(1)]],
speedOptions: [null, [Validators.required]],
showTimestamp: [null],
timestampFormat: [null, [Validators.required]],
snapToRealLocation: [null],
locationSnapFilter: [null, [Validators.required]]
});
this.tripTimelineSettingsFormGroup.valueChanges.pipe(
takeUntilDestroyed(this.destroyRef)
).subscribe(() => {
this.updateModel();
});
merge(this.tripTimelineSettingsFormGroup.get('showTimelineControl').valueChanges,
this.tripTimelineSettingsFormGroup.get('showTimestamp').valueChanges,
this.tripTimelineSettingsFormGroup.get('snapToRealLocation').valueChanges
).pipe(
takeUntilDestroyed(this.destroyRef)
).subscribe(() => {
this.updateValidators();
});
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(_fn: any): void {
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (isDisabled) {
this.tripTimelineSettingsFormGroup.disable({emitEvent: false});
} else {
this.tripTimelineSettingsFormGroup.enable({emitEvent: false});
this.updateValidators();
}
}
writeValue(value: TripTimelineSettings): void {
this.modelValue = value;
this.tripTimelineSettingsFormGroup.patchValue(
value, {emitEvent: false}
);
this.updateValidators();
this.settingsExpanded = this.tripTimelineSettingsFormGroup.get('showTimelineControl').value;
this.tripTimelineSettingsFormGroup.get('showTimelineControl').valueChanges.pipe(
takeUntilDestroyed(this.destroyRef)
).subscribe((show) => {
this.settingsExpanded = show;
});
}
public validate(c: UntypedFormControl) {
const valid = this.tripTimelineSettingsFormGroup.valid;
return valid ? null : {
tripTimelineSettings: {
valid: false,
},
};
}
private updateValidators() {
const showTimelineControl: boolean = this.tripTimelineSettingsFormGroup.get('showTimelineControl').value;
const showTimestamp: boolean = this.tripTimelineSettingsFormGroup.get('showTimestamp').value;
const snapToRealLocation: boolean = this.tripTimelineSettingsFormGroup.get('snapToRealLocation').value;
if (showTimelineControl) {
this.tripTimelineSettingsFormGroup.enable({emitEvent: false});
if (!showTimestamp) {
this.tripTimelineSettingsFormGroup.get('timestampFormat').disable({emitEvent: false});
}
if (!snapToRealLocation) {
this.tripTimelineSettingsFormGroup.get('locationSnapFilter').disable({emitEvent: false});
}
} else {
this.tripTimelineSettingsFormGroup.disable({emitEvent: false});
this.tripTimelineSettingsFormGroup.get('showTimelineControl').enable({emitEvent: false});
}
}
private updateModel() {
this.modelValue = this.tripTimelineSettingsFormGroup.getRawValue();
this.propagateChange(this.modelValue);
}
protected readonly pathDecoratorSymbols = pathDecoratorSymbols;
protected readonly pathDecoratorSymbolTranslationMap = pathDecoratorSymbolTranslationMap;
}

4
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts

@ -242,6 +242,9 @@ import {
import { import {
MapActionButtonRowComponent MapActionButtonRowComponent
} from '@home/components/widget/lib/settings/common/map/map-action-button-row.component'; } from '@home/components/widget/lib/settings/common/map/map-action-button-row.component';
import {
TripTimelineSettingsComponent
} from '@home/components/widget/lib/settings/common/map/trip-timeline-settings.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -332,6 +335,7 @@ import {
MapDataLayersComponent, MapDataLayersComponent,
MapDataSourceRowComponent, MapDataSourceRowComponent,
MapDataSourcesComponent, MapDataSourcesComponent,
TripTimelineSettingsComponent,
MapSettingsComponent, MapSettingsComponent,
EntityAliasSelectComponent, EntityAliasSelectComponent,
FilterSelectComponent, FilterSelectComponent,

1
ui-ngx/src/app/modules/home/components/widget/lib/settings/map/map-widget-settings.component.html

@ -17,6 +17,7 @@
--> -->
<ng-container [formGroup]="mapWidgetSettingsForm"> <ng-container [formGroup]="mapWidgetSettingsForm">
<tb-map-settings <tb-map-settings
[trip]="trip"
[aliasController]="aliasController" [aliasController]="aliasController"
[callbacks]="callbacks" [callbacks]="callbacks"
[functionsOnly]="functionsOnly" [functionsOnly]="functionsOnly"

12
ui-ngx/src/app/modules/home/components/widget/lib/settings/map/map-widget-settings.component.ts

@ -19,8 +19,9 @@ import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.m
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { mergeDeep, mergeDeepIgnoreArray } from '@core/utils'; import { isDefinedAndNotNull, mergeDeep, mergeDeepIgnoreArray } from '@core/utils';
import { mapWidgetDefaultSettings, MapWidgetSettings } from '@home/components/widget/lib/maps/map-widget.models'; import { mapWidgetDefaultSettings, MapWidgetSettings } from '@home/components/widget/lib/maps/map-widget.models';
import { WidgetConfigComponentData } from '@home/models/widget-component.models';
@Component({ @Component({
selector: 'tb-map-widget-settings', selector: 'tb-map-widget-settings',
@ -31,6 +32,8 @@ export class MapWidgetSettingsComponent extends WidgetSettingsComponent {
mapWidgetSettingsForm: UntypedFormGroup; mapWidgetSettingsForm: UntypedFormGroup;
trip = false;
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
private fb: UntypedFormBuilder) { private fb: UntypedFormBuilder) {
super(store); super(store);
@ -40,6 +43,13 @@ export class MapWidgetSettingsComponent extends WidgetSettingsComponent {
return this.mapWidgetSettingsForm; return this.mapWidgetSettingsForm;
} }
protected onWidgetConfigSet(widgetConfig: WidgetConfigComponentData) {
const params = widgetConfig.typeParameters as any;
if (isDefinedAndNotNull(params.trip)) {
this.trip = params.trip === true;
}
}
protected defaultSettings(): WidgetSettings { protected defaultSettings(): WidgetSettings {
return mergeDeepIgnoreArray<MapWidgetSettings>({} as MapWidgetSettings, mapWidgetDefaultSettings); return mergeDeepIgnoreArray<MapWidgetSettings>({} as MapWidgetSettings, mapWidgetDefaultSettings);
} }

31
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -7706,6 +7706,14 @@
"zoom-double-click": "Double click", "zoom-double-click": "Double click",
"zoom-control-buttons": "Control buttons" "zoom-control-buttons": "Control buttons"
}, },
"timeline": {
"control-panel": "Timeline control panel",
"time-step": "Time step",
"speed-options": "Speed options",
"timestamp": "Timestamp",
"snap-to-real-location": "Snap to real location",
"location-snap-filter-function": "Location snap filter function"
},
"map-action": { "map-action": {
"map-action-buttons": "Map action buttons", "map-action-buttons": "Map action buttons",
"label": "Label", "label": "Label",
@ -7779,6 +7787,7 @@
}, },
"overlays": { "overlays": {
"overlays": "Overlays", "overlays": "Overlays",
"trips": "Trips",
"markers": "Markers", "markers": "Markers",
"polygons": "Polygons", "polygons": "Polygons",
"circles": "Circles" "circles": "Circles"
@ -7827,7 +7836,14 @@
"action-remove": "Remove", "action-remove": "Remove",
"edit-instruments": "Instruments", "edit-instruments": "Instruments",
"enable-snapping": "Enable snapping to other vertices for precision drawing", "enable-snapping": "Enable snapping to other vertices for precision drawing",
"trip": {
"no-trips": "No trips configured",
"add-trip": "Add trip",
"trip-configuration": "Trip configuration",
"remove-trip": "Remove trip"
},
"marker": { "marker": {
"marker": "Marker",
"latitude-key": "Latitude key", "latitude-key": "Latitude key",
"longitude-key": "Longitude key", "longitude-key": "Longitude key",
"x-pos-key": "X position key", "x-pos-key": "X position key",
@ -7857,6 +7873,8 @@
"marker-offset": "Marker offset", "marker-offset": "Marker offset",
"offset-horizontal": "Horizontal", "offset-horizontal": "Horizontal",
"offset-vertical": "Vertical", "offset-vertical": "Vertical",
"rotate-marker": "Rotate marker",
"offset-angle": "Offset angle",
"position-conversion": "Position conversion", "position-conversion": "Position conversion",
"position-conversion-function": "Position conversion function, should return x,y coordinates as double from 0 to 1 each", "position-conversion-function": "Position conversion function, should return x,y coordinates as double from 0 to 1 each",
"clustering": { "clustering": {
@ -7880,8 +7898,19 @@
"place-marker-hint-with-entity": "Click to place '{{entityName}}' entity" "place-marker-hint-with-entity": "Click to place '{{entityName}}' entity"
}, },
"path": { "path": {
"path": "Path",
"path-decorator": "Path decorator",
"decorator-symbol": "Decorator symbol",
"decorator-symbol-arrow-head": "Arrow", "decorator-symbol-arrow-head": "Arrow",
"decorator-symbol-dash": "Dash" "decorator-symbol-dash": "Dash",
"decorator-arrangement": "Decorator arrangement",
"decorator-offset": "Start",
"decorator-end-offset": "End",
"decorator-repeat": "Repeat"
},
"points": {
"points": "Points",
"point-tooltip": "Point tooltip"
}, },
"polygon": { "polygon": {
"polygon-key": "Polygon key", "polygon-key": "Polygon key",

4
ui-ngx/src/assets/markers/tripShape1.svg

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="34" height="34" viewBox="0 0 34 34" class="marker-color" fill="#307FE5">
<circle cx="17" cy="17" r="17" fill="#ffffff" />
<path d="M 7.63578,10.2032 C 7.4184,9.79185 7.83153,9.33261 8.26356,9.50542 L 25.8394,16.5358 c 0.4191,0.1676 0.4191,0.7608 0,0.9284 L 8.26356,24.4946 C 7.83154,24.6674 7.4184,24.2082 7.63578,23.7968 L 11.2271,17 Z" />
</svg>

After

Width:  |  Height:  |  Size: 399 B

10
ui-ngx/src/assets/markers/tripShape10.svg

@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="34" height="34" viewBox="0 0 34 34" class="marker-color" fill="#307FE5">
<path fill-rule="evenodd" clip-rule="evenodd" d="M 34,20 V 14 C 34,11.2695 33,10.0174 31.5632,10.0174 31.5632,9.5 31,9 30.2511,9 v 1.0174 H 1 C 1,10.0174 0,10 0,11 c 0,4.5 0,7.5 0,12 0,1 0,1 1,1 3.72696,-2e-4 26.602,-0.0013 29.2511,0 v 1 C 31,25 31.5632,24.5 31.5632,24 33,24 34,22.7617 34,20 Z m -1.3219,-6.6831 c -0.173,-0.6832 -0.3749,-0.9593 -0.5479,-1.0611 -0.0989,-0.0581 -0.0467,-0.0581 -0.2884,-0.0581 -0.2417,0 -1.5331,1.2209 -1.5331,1.2209 0.7329,0.8554 0.709,6.603 0,7.468 l 1.5331,1.221 c 0.1026,0.0344 0.1977,0.0021 0.2884,-0.0436 0.173,-0.1018 0.3749,-0.378 0.5479,-1.0611 C 32.8223,20.3924 32.9712,19.468 33,18 v -2 c -0.0144,-1.468 -0.1633,-2.0581 -0.3219,-2.6831 z m -7.1709,8.2864 v 1.0029 h 4.1383 c 0,-0.5524 -0.4759,-1.0029 -1.067,-1.0029 z m 0.0144,-0.9202 h 0.3749 v -7.3808 h -0.3749 z m 4.1239,-8.2238 c 0,-0.5523 -0.4759,-1.0029 -1.067,-1.0029 h -3.0713 v 1.0029 z" />
<path d="m 34.0002,14 v 6 c 0,2.7617 -1,4.0145 -2.4368,4.0145 C 31.4481,24.407 30.8731,25 30.2513,25 V 24.0145 H 24.6577 V 24 C 24.4125,23.9419 24.2251,23.7238 24.2251,23.4622 V 10.5698 c 0,-0.2617 0.1874,-0.5262 0.4326,-0.58434 l 5.5936,0.03194 V 9 c 0.7489,0 1.3121,0.5 1.3121,1.0174 C 33.0002,10 34.0002,11.2695 34.0002,14 Z" fill="#ffffff" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M 31.5632,24 C 33,24 34,22.7617 34,20 V 14 C 34,11.2695 33,10.0174 31.5632,10.0174 31.5632,9.5 31,9 30.2511,9 v 1.0174 H 3.60475 L 1,10 C 0.497025,10.0088 0.247034,10.0131 0.122782,10.1403 0,10.266 0,10.5116 0,11 v 12 c 0,0.5 0,0.75 0.125,0.875 C 0.25,24 0.5,24 1,24 h 29.2511 v 1 C 31,25 31.5632,24.5 31.5632,24 Z m 0.567,-11.7442 c 0.173,0.1018 0.3749,0.3779 0.5479,1.0611 C 32.8367,13.9419 32.9856,14.532 33,16 v 2 c -0.0288,1.468 -0.1777,2.3924 -0.3219,3.0029 -0.173,0.6831 -0.3749,0.9593 -0.5479,1.0611 -0.0907,0.0457 -0.1858,0.078 -0.2884,0.0436 l -1.5331,-1.221 c 0.709,-0.865 0.7329,-6.6126 0,-7.468 0,0 1.2914,-1.2209 1.5331,-1.2209 0.1505,0 0.1871,0 0.2171,0.014 0.0096,0.0045 0.0186,0.0105 0.0304,0.0183 0.0105,0.007 0.0233,0.0155 0.0409,0.0258 z m -2.4847,10.34 h -4.1383 v -1.0029 h 3.0713 c 0.5911,0 1.067,0.4506 1.067,1.0029 z m -3.749,-1.9127 h -0.3749 v -7.3808 h 0.3749 z m 2.682,-9.2267 c 0.5911,0 1.067,0.4506 1.067,1.0029 H 25.5072 V 11.4564 Z M 2,23 C 1.42324,23 1.22562,22.9651 1.22562,22.4273 V 11.907 C 1.22562,11.3692 1.42324,11 2,11 h 3.00022 v 0.0144 c 0,0.4612 0.37471,0.8359 0.83588,0.8359 h 2.1041 c 0.46117,0 0.85028,-0.3747 0.85028,-0.8359 V 11 H 16 c 0.0432,0.4323 0.4035,0.7782 0.8359,0.7782 H 18.94 c 0.4467,0 0.807,-0.3459 0.8358,-0.7782 H 22 c 1.0526,0 1.3876,0.3692 1.3876,0.907 V 22.1076 C 23.3876,22.6453 23.0526,23 22,23 h -2.2096 v -0.1007 c 0,-0.4611 -0.3747,-0.8503 -0.8503,-0.8503 H 16.836 c -0.4611,0 -0.8502,0.3747 -0.8502,0.8503 v 0 c 0,0.0288 0,0.0719 0.0143,0.1007 H 8.77878 c 0.02607,-9e-4 0.02607,-0.0144 0.02607,-0.0144 0,-0.4611 -0.3747,-0.8503 -0.85029,-0.8503 h -2.1041 c -0.46117,0 -0.85028,0.3747 -0.85028,0.8503 V 23 Z" style="filter:url(#filter4)" />
<defs>
<filter id="filter4" color-interpolation-filters="sRGB" x="0" y="0" width="1" height="1">
<feColorMatrix id="feColorMatrix2" values="0.60000000 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 0.60000000 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 0.60000000 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 1.0000000 0.0000000 " />
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

15
ui-ngx/src/assets/markers/tripShape2.svg

@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" width="34" height="34" viewBox="0 0 34 34" class="marker-color" fill="#307FE5">
<circle cx="17" cy="17" r="17"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M 17,33 C 25.8366,33 33,25.8366 33,17 33,8.16344 25.8366,1 17,1 8.16344,1 1,8.16344 1,17 1,25.8366 8.16344,33 17,33 Z m 0,1 C 26.3888,34 34,26.3888 34,17 34,7.61116 26.3888,0 17,0 7.61116,0 0,7.61116 0,17 0,26.3888 7.61116,34 17,34 Z"
style="filter:url(#filter2)" />
<g class="marker-icon-container" transform="translate(17,17)" fill="#ffffff">
<g transform="translate(-17,-17)">
<path d="M 7.63578,10.2032 C 7.4184,9.79185 7.83153,9.33261 8.26356,9.50542 L 25.8394,16.5358 c 0.4191,0.1676 0.4191,0.7608 0,0.9284 L 8.26356,24.4946 C 7.83154,24.6674 7.4184,24.2082 7.63578,23.7968 L 11.2271,17 Z"/>
</g>
</g>
<defs>
<filter id="filter2" color-interpolation-filters="sRGB" x="0" y="0" width="1" height="1">
<feColorMatrix id="feColorMatrix2" values="0.60000000 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 0.60000000 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 0.60000000 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 1.0000000 0.0000000 " />
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

9
ui-ngx/src/assets/markers/tripShape3.svg

@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" width="34" height="34" viewBox="0 0 34 34" class="marker-color" fill="#307FE5">
<path d="M 17,0.0858536 C 16.0112,0.279907 15.4367,1.00295 15.2798,1.98651 15.2639,2.09816 15.2533,3.49641 15.2533,5.96593 v 3.8093 L 8.57642,9.78055 1.89691,9.78852 1.67098,9.86295 C 0.793849,10.1554 0.185171,10.8439 0.0363239,11.7078 0.00177019,11.9151 -0.00354577,12.577 0.00177019,17.112 l 0.00797394,5.1704 0.05847557,0.1993 c 0.2604823,0.8932 0.9356103,1.5285 1.8180603,1.7146 0.16214,0.0346 1.05256,0.0399 6.77785,0.0399 h 6.58917 v 3.8066 c 0,2.4722 0.0106,3.8705 0.0265,3.9821 0.1436,0.9065 0.7841,1.6269 1.6932,1.906 C 17.183,33.9947 17.2494,34 17.6056,34 17.6836,34 17.79,33.9947 18,33.9309 18.3641,33.8192 19.2342,33.2339 19.5,33 19.5664,32.9389 24.3845,27.7274 27,25 28.9555,22.9588 33.5,18 34,17.0057 33.5,16 29.7561,11.8752 27,9 25.5992,7.53795 19.1037,0.590381 19,0.5 18.7661,0.297972 18.2694,0.178893 17.9531,0.0858536 17.6581,-0.00186917 17.2897,0.03003 17,0.0858536 Z" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M 0.00974413,22.2588 0.00177019,17.102 c -0.00531596,-4.523 0,-5.1832 0.03455371,-5.39 C 0.210358,10.7045 1,10.0087 1.89691,9.79777 L 15.2533,9.78452 c 0,-2.5257 0,-5.26136 0.0265,-7.76824 C 15.2905,1.01395 16.0112,0.228478 17,0.0349352 17.6746,-0.0946972 18.6108,0.146463 19.1286,0.592552 22.3114,3.84368 34,15.9927 34,17.102 34,18.1435 23.8254,28.5757 20,32.5015 19.5,33.001 18.759,34 17.5,34 c -1.259,0 -2.2202,-1.2932 -2.2202,-2.0243 -0.0265,-2.5183 -0.0265,-5.2425 -0.0265,-7.7683 -4.2262,0 -9.07008,0 -13.36702,-0.0397 C 1,24.1677 0,22.9745 0.00974413,22.2588 Z M 16.2789,31.8814 c 0.0981,0.467 0.4594,0.8714 1.0131,1.0415 0.708,0.2175 1.3331,-0.4318 1.8148,-0.9262 1.7887,-1.8356 4.9957,-5.1269 7.9052,-8.2189 1.6588,-1.7629 3.212,-3.4516 4.3474,-4.7643 0.5694,-0.6583 1.0215,-1.208 1.3267,-1.6184 0.0857,-0.1153 0.1554,-0.2137 0.2106,-0.2959 -0.0556,-0.0842 -0.1259,-0.185 -0.2126,-0.3033 -0.3043,-0.4154 -0.755,-0.972 -1.3225,-1.6388 C 30.2299,13.8276 28.6807,12.1156 27.0235,10.3275 23.7364,6.78063 20.0561,2.96839 18.4493,1.32633 18.1247,1.072 17.5966,0.937045 17.191,1.01395 16.7497,1.101 16.4027,1.4472 16.279,2.10661 16.2533,4.58546 16.2533,7.28455 16.2533,9.78452 V 10.7809 L 2.02778,10.795 c -0.51455,0.1558 -0.91043,0.557 -1.00424,1.0765 -6.4e-4,0.0073 -0.00469,0.0504 -0.00847,0.1663 -0.00482,0.1479 -0.00851,0.3802 -0.011,0.7572 -0.004948,0.7515 -0.00496,2.0428 -0.0023,4.3055 l 0.00797,5.1567 v 0.0027 c 0,0 0.0079,0.051 0.05941,0.1541 0.05987,0.1197 0.15509,0.2589 0.27701,0.3904 0.12224,0.1318 0.25063,0.2336 0.36376,0.2974 0.05521,0.0312 0.09971,0.0492 0.13189,0.059 0.02872,0.0087 0.04447,0.0095 0.04447,0.0095 H 1.89556 C 6.187,23.2101 11.026,23.2101 15.2533,23.2101 h 1 v 0.9973 c 0,2.4988 0,5.1853 0.0256,7.674 z" style="filter:url(#filter2)" />
<defs>
<filter id="filter2" color-interpolation-filters="sRGB" x="0" y="0" width="1" height="1">
<feColorMatrix id="feColorMatrix2" values="0.60000000 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 0.60000000 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 0.60000000 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 1.0000000 0.0000000 " />
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

9
ui-ngx/src/assets/markers/tripShape4.svg

@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" width="34" height="34" viewBox="0 0 34 34" class="marker-color" fill="#307FE5">
<path d="M 4.47621,17.6524 4.52498,17.5 4.47621,17.3476 0.788173,5.82249 32.551,17.5 0.788173,29.1775 Z" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M 5.04976,17.4998 1.57615,28.3548 31.1018,17.4998 1.57615,6.64476 Z m -1.04996,0 -3.688034,11.5251 c -0.126878,0.3965 0.258016,0.7653 0.648745,0.6217 L 32.7233,17.9691 c 0.4367,-0.1606 0.4367,-0.7781 0,-0.9386 L 0.960511,5.35298 C 0.569782,5.20933 0.184888,5.57816 0.311766,5.97465 Z" style="filter:url(#filter2)" />
<defs>
<filter id="filter2" color-interpolation-filters="sRGB" x="0" y="0" width="1" height="1">
<feColorMatrix id="feColorMatrix2" values="0.60000000 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 0.60000000 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 0.60000000 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 1.0000000 0.0000000 " />
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 989 B

10
ui-ngx/src/assets/markers/tripShape5.svg

@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="34" height="34" viewBox="0 0 34 34" class="marker-color" fill="#307FE5">
<circle cx="17" cy="17" r="17" />
<path d="m 16.5942,12.0051 c -0.4157,-0.4527 -0.6417,-1.054 -0.6296,-1.6751 0.0121,-0.62116 0.2613,-1.21277 0.6942,-1.64824 0.433,-0.43547 1.0153,-0.68018 1.6223,-0.68175 0.6071,-0.00158 1.1906,0.24011 1.6257,0.67333 l 6.4281,6.69026 C 26.7612,15.8083 27,16.4065 27,17.0295 c 0,0.623 -0.2388,1.2211 -0.6651,1.6659 l -6.2979,6.5553 c -0.2119,0.2307 -0.4667,0.4159 -0.7495,0.5444 -0.2827,0.1286 -0.5877,0.1981 -0.8971,0.2044 -0.3093,0.0063 -0.6168,-0.0507 -0.9043,-0.1677 -0.2876,-0.117 -0.5494,-0.2916 -0.7701,-0.5136 -0.2206,-0.2219 -0.3958,-0.4868 -0.5151,-0.779 -0.1192,-0.2922 -0.1803,-0.6058 -0.1795,-0.9225 8e-4,-0.3166 0.0634,-0.6299 0.1841,-0.9215 0.1207,-0.2916 0.2972,-0.5555 0.519,-0.7763 L 19.1792,19.3567 9.3034,19.3084 C 8.68734,19.3031 8.09857,19.0476 7.66662,18.598 7.23466,18.1484 6.9949,17.5417 7.00008,16.9112 7.00526,16.2807 7.25496,15.6782 7.69424,15.2361 8.13352,14.794 8.72641,14.5486 9.34246,14.554 l 9.74224,0.0466 z" fill="#ffffff" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M 17,33 C 25.8366,33 33,25.8366 33,17 33,8.16344 25.8366,1 17,1 8.16344,1 1,8.16344 1,17 1,25.8366 8.16344,33 17,33 Z m 0,1 C 26.3888,34 34,26.3888 34,17 34,7.61116 26.3888,0 17,0 7.61116,0 0,7.61116 0,17 0,26.3888 7.61116,34 17,34 Z" style="filter:url(#filter2)" />
<defs>
<filter id="filter2" color-interpolation-filters="sRGB" x="0" y="0" width="1" height="1">
<feColorMatrix id="feColorMatrix2" values="0.60000000 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 0.60000000 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 0.60000000 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 1.0000000 0.0000000 " />
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

10
ui-ngx/src/assets/markers/tripShape6.svg

@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="34" height="34" viewBox="0 0 34 34" class="marker-color" fill="#307FE5">
<circle cx="17" cy="17" r="17" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M 17,33 C 25.8366,33 33,25.8366 33,17 33,8.16344 25.8366,1 17,1 8.16344,1 1,8.16344 1,17 1,25.8366 8.16344,33 17,33 Z m 0,1 C 26.3888,34 34,26.3888 34,17 34,7.61116 26.3888,0 17,0 7.61116,0 0,7.61116 0,17 0,26.3888 7.61116,34 17,34 Z" style="filter:url(#filter2)" />
<path d="m 12.6301,12.0051 c -0.4157,-0.4527 -0.6417,-1.054 -0.6296,-1.6751 0.012,-0.62116 0.2612,-1.21277 0.6942,-1.64824 0.4329,-0.43547 1.0152,-0.68018 1.6223,-0.68175 0.607,-0.00158 1.1905,0.24011 1.6256,0.67333 l 6.4281,6.69026 c 0.4263,0.4447 0.6651,1.0429 0.6651,1.6659 0,0.623 -0.2388,1.2211 -0.6651,1.6659 l -6.2979,6.5553 c -0.2118,0.2307 -0.4666,0.4158 -0.7494,0.5444 -0.2828,0.1286 -0.5878,0.1981 -0.8971,0.2044 -0.3094,0.0063 -0.6169,-0.0507 -0.9044,-0.1677 -0.2875,-0.117 -0.5493,-0.2916 -0.77,-0.5136 -0.2207,-0.2219 -0.3958,-0.4868 -0.5151,-0.779 -0.1193,-0.2922 -0.1803,-0.6058 -0.1795,-0.9225 7e-4,-0.3166 0.0633,-0.6299 0.1841,-0.9215 0.1207,-0.2916 0.2971,-0.5555 0.5189,-0.7763 l 2.4547,-2.5622 c 2.3206,-2.3272 2.3206,-2.3272 2.3206,-2.3272 l -2.415,-2.4289 z" fill="#ffffff" />
<defs>
<filter id="filter2" color-interpolation-filters="sRGB" x="0" y="0" width="1" height="1">
<feColorMatrix id="feColorMatrix2" values="0.60000000 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 0.60000000 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 0.60000000 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 1.0000000 0.0000000 " />
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

11
ui-ngx/src/assets/markers/tripShape7.svg

@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" width="34" height="34" viewBox="0 0 34 34" class="marker-color" fill="#307FE5">
<path fill-rule="evenodd" clip-rule="evenodd" d="M 3.03511,11.1037 0.975008,11.3 c -0.565732,0.0362 -0.8856091,0.3379 -0.8993192,1.0023 -0.06708141,3.163 -0.06704708,6.3758 0,9.5389 0.0137101,0.6644 0.3335872,0.9661 0.8993192,1.0022 H 3.03511 c 0.68089,0.0434 1.18081,0.0837 1.5144,0.1416 8.32419,1.4476 22.42079,2.4591 29.26439,-5.6302 0.2148,-0.2541 0.2148,-0.5062 0,-0.7604 C 26.9694,8.5051 12.8737,9.51665 4.54951,10.9642 4.21684,11.0221 3.716,11.0624 3.03511,11.1058 Z" fill="#ffffff" />
<path fill-rule="evenodd" clip-rule="evenodd" d="m 3.03511,11.1042 -2.060102,0.1963 c -0.565732,0.0362 -0.8856091,0.3379 -0.8993192,1.0023 -0.06708141,3.163 -0.06704708,6.3758 0,9.5389 0.0137101,0.6644 0.3335872,0.9661 0.8993192,1.0022 H 3.03511 c 0.68089,0.0434 1.18081,0.0837 1.5144,0.1416 8.32419,1.4476 22.42079,2.4591 29.26439,-5.6302 0.2148,-0.2541 0.2148,-0.5063 0,-0.7604 C 26.9694,8.50559 12.8737,9.51714 4.54951,10.9647 4.21684,11.0226 3.716,11.0629 3.03511,11.1063 Z m 9.89889,0.8709 h 3.6631 c 3.1074,0.0909 4.5615,0.5849 4.5615,4.999 0,4.414 -1.4541,4.9101 -4.5615,5.001 H 12.934 c 0.0073,0.2407 0.4113,0.3007 0.6032,0.3234 0.6407,0.0744 2.6925,0.3316 4.6684,0.1766 3.8834,-0.3048 5.279,-1.3877 5.279,-5.501 0,-4.1134 -1.3956,-5.1942 -5.279,-5.499 -1.9759,-0.155 -4.0286,0.1022 -4.6684,0.1766 -0.1928,0.0227 -0.5959,0.0827 -0.6032,0.3234 z" />
<path fill-rule="evenodd" clip-rule="evenodd" d="m 9.55575,16.0347 1.99985,-0.0279 c 0.2761,-0.0039 0.4968,-0.2308 0.4929,-0.5069 l -0.0349,-2.4998 c -0.0039,-0.2761 -0.2308,-0.4968 -0.507,-0.493 L 9.61037,12.5336 C 9.54408,12.5346 9.48013,12.5091 9.43259,12.4629 9.27291,12.3076 9.00521,12.4229 9.00833,12.6456 l 0.04048,2.8962 c 0.00386,0.2761 0.23082,0.4968 0.50694,0.4929 z m 0.02784,1.9999 1.99981,-0.028 c 0.2761,-0.0039 0.5031,0.2168 0.5069,0.493 l 0.035,2.4997 c 0.0038,0.2761 -0.2169,0.5031 -0.493,0.507 l -1.89625,0.0265 c -0.0663,9e-4 -0.12951,0.0281 -0.17574,0.0757 -0.15527,0.1596 -0.42609,0.0519 -0.4292,-0.1708 L 9.09062,18.5415 C 9.08676,18.2654 9.30747,18.0384 9.58359,18.0346 Z m 8.76411,-2.1225 -2.2927,0.0321 c -0.2761,0.0038 -0.5031,-0.2169 -0.5069,-0.493 l -0.028,-1.9998 c -0.0038,-0.2761 0.2169,-0.5031 0.493,-0.5069 l 2.2926,-0.0321 c 0.261,-0.0036 0.396,0.31 0.2141,0.4971 0,0 0.5078,0.5563 0.5139,0.9929 0.0062,0.4366 -0.4859,1.0069 -0.4859,1.0069 0.187,0.1819 0.0608,0.4992 -0.2001,0.5028 z m -2.2224,5.0316 2.2927,-0.0321 c 0.2609,-0.0036 0.3872,-0.3209 0.2001,-0.5028 0,0 0.4921,-0.5703 0.486,-1.0069 -0.0061,-0.4366 -0.514,-0.9929 -0.514,-0.9929 0.1819,-0.1871 0.0469,-0.5007 -0.214,-0.4971 l -2.2927,0.0321 c -0.2761,0.0038 -0.4968,0.2308 -0.493,0.5069 l 0.028,1.9998 c 0.0038,0.2761 0.2308,0.4968 0.5069,0.493 z" style="filter:url(#filter4)" />
<path fill-rule="evenodd" clip-rule="evenodd" d="m 3.04579,23.005 c 0.69564,0 1.63358,0.1207 2.85604,0.2781 C 8.18884,23.5774 11.4716,24 16.0266,24 c 6.6603,0 13.6073,-1.6373 17.8126,-6.6195 0.2144,-0.2543 0.2144,-0.5067 0,-0.7611 C 29.6334,11.6373 22.6858,10.1045 16.0266,10 11.4716,10 8.18884,10.4225 5.90183,10.7169 4.67937,10.8742 3.74143,10.995 3.04579,10.995 H 1.04875 c -0.564898,0.0362 -0.9848352,0.3359 -0.9985262,1.0008 -0.0669822,3.1657 -0.0669479,6.8426 0,10.0084 0.013691,0.6649 0.4336282,0.975 0.9985262,1.0112 z M 1.08578,22.0144 3.04579,22.0041 c 0.76299,0 1.80277,0.1338 3.06781,0.2967 0.51876,0.0667 1.0754,0.1383 1.66636,0.2076 2.09575,0.2457 4.80134,0.4908 8.24664,0.4908 6.4258,0 12.8746,-1.557 16.82,-5.9991 -3.9405,-4.4322 -10.3798,-5.897 -16.8278,-5.9993 -3.4416,4e-4 -6.14468,0.2452 -8.23884,0.4907 -0.59097,0.0693 -1.1476,0.141 -1.66636,0.2077 -1.26504,0.1628 -2.30482,0.2966 -3.06781,0.2966 H 1.08737 c -0.01454,0.0015 -0.02728,0.0032 -0.0383,0.0051 -2.4e-4,0.0048 -4.2e-4,0.01 -5.4e-4,0.0156 -0.066677,3.1515 -0.066658,6.8148 -1e-5,9.9665 2e-4,0.0095 6.2e-4,0.0186 0.00115,0.0263 0.0103,0.0018 0.0223,0.0036 0.03611,0.0051 z" style="filter:url(#filter4)" />
<defs>
<filter id="filter4" color-interpolation-filters="sRGB" x="0" y="0" width="1" height="1">
<feColorMatrix id="feColorMatrix4" values="0.59999999999999998 0 0 0 0 0 0.59999999999999998 0 0 0 0 0 0.59999999999999998 0 0 0 0 0 1 0" />
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

9
ui-ngx/src/assets/markers/tripShape8.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.8 KiB

10
ui-ngx/src/assets/markers/tripShape9.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.7 KiB

Loading…
Cancel
Save