Commit 4890125423912e4f86104c294a38bef610ed8ba4

Authored by Artem Halushko
1 parent af0cd505

trip-animation points & anchors

@@ -14,18 +14,18 @@ @@ -14,18 +14,18 @@
14 /// limitations under the License. 14 /// limitations under the License.
15 /// 15 ///
16 16
17 -import L, { LatLngBounds, LatLngTuple, markerClusterGroup, MarkerClusterGroupOptions } from 'leaflet'; 17 +import L, { LatLngBounds, LatLngTuple, markerClusterGroup, MarkerClusterGroupOptions, FeatureGroup, LayerGroup } from 'leaflet';
18 18
19 import 'leaflet-providers'; 19 import 'leaflet-providers';
20 import 'leaflet.markercluster/dist/leaflet.markercluster'; 20 import 'leaflet.markercluster/dist/leaflet.markercluster';
21 21
22 import { 22 import {
23 - FormattedData,  
24 - MapSettings,  
25 - MarkerSettings,  
26 - PolygonSettings,  
27 - PolylineSettings,  
28 - UnitedMapSettings 23 + FormattedData,
  24 + MapSettings,
  25 + MarkerSettings,
  26 + PolygonSettings,
  27 + PolylineSettings,
  28 + UnitedMapSettings
29 } from './map-models'; 29 } from './map-models';
30 import { Marker } from './markers'; 30 import { Marker } from './markers';
31 import { BehaviorSubject, Observable } from 'rxjs'; 31 import { BehaviorSubject, Observable } from 'rxjs';
@@ -33,7 +33,7 @@ import { filter } from 'rxjs/operators'; @@ -33,7 +33,7 @@ import { filter } from 'rxjs/operators';
33 import { Polyline } from './polyline'; 33 import { Polyline } from './polyline';
34 import { Polygon } from './polygon'; 34 import { Polygon } from './polygon';
35 import { DatasourceData } from '@app/shared/models/widget.models'; 35 import { DatasourceData } from '@app/shared/models/widget.models';
36 -import { safeExecute } from '@home/components/widget/lib/maps/maps-utils'; 36 +import { safeExecute, createTooltip } from '@home/components/widget/lib/maps/maps-utils';
37 37
38 export default abstract class LeafletMap { 38 export default abstract class LeafletMap {
39 39
@@ -48,6 +48,7 @@ export default abstract class LeafletMap { @@ -48,6 +48,7 @@ export default abstract class LeafletMap {
48 bounds: L.LatLngBounds; 48 bounds: L.LatLngBounds;
49 datasources: FormattedData[]; 49 datasources: FormattedData[];
50 markersCluster; 50 markersCluster;
  51 + points: FeatureGroup;
51 52
52 protected constructor(public $container: HTMLElement, options: UnitedMapSettings) { 53 protected constructor(public $container: HTMLElement, options: UnitedMapSettings) {
53 this.options = options; 54 this.options = options;
@@ -158,9 +159,9 @@ export default abstract class LeafletMap { @@ -158,9 +159,9 @@ export default abstract class LeafletMap {
158 this.map = map; 159 this.map = map;
159 if (this.options.useDefaultCenterPosition) { 160 if (this.options.useDefaultCenterPosition) {
160 this.map.panTo(this.options.defaultCenterPosition); 161 this.map.panTo(this.options.defaultCenterPosition);
161 - this.bounds = map.getBounds(); 162 + // this.bounds = map.getBounds();
162 } 163 }
163 - else this.bounds = new L.LatLngBounds(null, null); 164 + // else this.bounds = new L.LatLngBounds(null, null);
164 if (this.options.draggableMarker) { 165 if (this.options.draggableMarker) {
165 this.addMarkerControl(); 166 this.addMarkerControl();
166 } 167 }
@@ -201,9 +202,9 @@ export default abstract class LeafletMap { @@ -201,9 +202,9 @@ export default abstract class LeafletMap {
201 return this.map.getCenter(); 202 return this.map.getCenter();
202 } 203 }
203 204
204 - fitBounds(bounds: LatLngBounds, useDefaultZoom = false, padding?: LatLngTuple) { 205 + fitBounds(bounds: LatLngBounds, padding?: LatLngTuple) {
205 if (bounds.isValid()) { 206 if (bounds.isValid()) {
206 - this.bounds = this.bounds.extend(bounds); 207 + this.bounds = !!this.bounds ? this.bounds.extend(bounds) : bounds;
207 if (!this.options.fitMapBounds && this.options.defaultZoomLevel) { 208 if (!this.options.fitMapBounds && this.options.defaultZoomLevel) {
208 this.map.setZoom(this.options.defaultZoomLevel, { animate: false }); 209 this.map.setZoom(this.options.defaultZoomLevel, { animate: false });
209 if (this.options.useDefaultCenterPosition) { 210 if (this.options.useDefaultCenterPosition) {
@@ -219,9 +220,9 @@ export default abstract class LeafletMap { @@ -219,9 +220,9 @@ export default abstract class LeafletMap {
219 } 220 }
220 }); 221 });
221 if (this.options.useDefaultCenterPosition) { 222 if (this.options.useDefaultCenterPosition) {
222 - bounds = bounds.extend(this.options.defaultCenterPosition); 223 + this.bounds = this.bounds.extend(this.options.defaultCenterPosition);
223 } 224 }
224 - this.map.fitBounds(bounds, { padding: padding || [50, 50], animate: false }); 225 + this.map.fitBounds(this.bounds, { padding: padding || [50, 50], animate: false });
225 } 226 }
226 } 227 }
227 } 228 }
@@ -253,11 +254,10 @@ export default abstract class LeafletMap { @@ -253,11 +254,10 @@ export default abstract class LeafletMap {
253 const style = currentImage ? 'background-image: url(' + currentImage.url + ');' : ''; 254 const style = currentImage ? 'background-image: url(' + currentImage.url + ');' : '';
254 this.options.icon = L.divIcon({ 255 this.options.icon = L.divIcon({
255 html: `<div class="arrow" 256 html: `<div class="arrow"
256 - style="transform: translate(-10px, -10px);  
257 - ${style}  
258 - rotate(${data.rotationAngle}deg);  
259 - "><div>`  
260 - }) 257 + style="transform: translate(-10px, -10px)
  258 + rotate(${data.rotationAngle}deg);
  259 + ${style}"><div>`
  260 + });
261 } 261 }
262 else { 262 else {
263 this.options.icon = null; 263 this.options.icon = null;
@@ -279,7 +279,8 @@ export default abstract class LeafletMap { @@ -279,7 +279,8 @@ export default abstract class LeafletMap {
279 private createMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings) { 279 private createMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings) {
280 this.ready$.subscribe(() => { 280 this.ready$.subscribe(() => {
281 const newMarker = new Marker(this.convertPosition(data), settings, data, dataSources, this.dragMarker); 281 const newMarker = new Marker(this.convertPosition(data), settings, data, dataSources, this.dragMarker);
282 - this.fitBounds(this.bounds.extend(newMarker.leafletMarker.getLatLng()), settings.draggableMarker && this.markers.size < 2); 282 + if (this.bounds)
  283 + this.fitBounds(this.bounds.extend(newMarker.leafletMarker.getLatLng()));
283 this.markers.set(key, newMarker); 284 this.markers.set(key, newMarker);
284 if (this.options.useClusterMarkers) { 285 if (this.options.useClusterMarkers) {
285 this.markersCluster.addLayer(newMarker.leafletMarker); 286 this.markersCluster.addLayer(newMarker.leafletMarker);
@@ -314,6 +315,29 @@ export default abstract class LeafletMap { @@ -314,6 +315,29 @@ export default abstract class LeafletMap {
314 } 315 }
315 } 316 }
316 317
  318 + updatePoints(pointsData: FormattedData[], getTooltip: (point: FormattedData, setTooltip?: boolean) => string) {
  319 + this.map$.subscribe(map => {
  320 + if (this.points) {
  321 + map.removeLayer(this.points);
  322 + }
  323 + this.points = new FeatureGroup();
  324 + pointsData.filter(pdata => !!this.convertPosition(pdata)).forEach(data => {
  325 + const point = L.circleMarker(this.convertPosition(data), {
  326 + color: this.options.pointColor,
  327 + radius: this.options.pointSize
  328 + });
  329 + if (!this.options.pointTooltipOnRightPanel) {
  330 + point.on('click', () => getTooltip(data));
  331 + }
  332 + else {
  333 + createTooltip(point, this.options, pointsData, getTooltip(data, false));
  334 + }
  335 + this.points.addLayer(point);
  336 + });
  337 + map.addLayer(this.points);
  338 + });
  339 + }
  340 +
317 setImageAlias(alias: Observable<any>) { 341 setImageAlias(alias: Observable<any>) {
318 } 342 }
319 343
@@ -338,15 +362,17 @@ export default abstract class LeafletMap { @@ -338,15 +362,17 @@ export default abstract class LeafletMap {
338 this.ready$.subscribe(() => { 362 this.ready$.subscribe(() => {
339 const poly = new Polyline(this.map, 363 const poly = new Polyline(this.map,
340 data.map(el => this.convertPosition(el)).filter(el => !!el), data, dataSources, settings); 364 data.map(el => this.convertPosition(el)).filter(el => !!el), data, dataSources, settings);
341 - const bounds = this.bounds.extend(poly.leafletPoly.getBounds());  
342 - this.fitBounds(bounds)  
343 - this.polylines.set(data[0].entityName, poly) 365 + const bounds = poly.leafletPoly.getBounds();
  366 + this.fitBounds(bounds);
  367 + this.polylines.set(data[0].entityName, poly);
344 }); 368 });
345 } 369 }
346 370
347 updatePolyline(key: string, data: FormattedData[], dataSources: FormattedData[], settings: PolylineSettings) { 371 updatePolyline(key: string, data: FormattedData[], dataSources: FormattedData[], settings: PolylineSettings) {
348 this.ready$.subscribe(() => { 372 this.ready$.subscribe(() => {
349 - this.polylines.get(key).updatePolyline(settings, data.map(el => this.convertPosition(el)), dataSources); 373 + const poly = this.polylines.get(key);
  374 + poly.updatePolyline(settings, data.map(el => this.convertPosition(el)), dataSources);
  375 + const bounds = poly.leafletPoly.getBounds();
350 }); 376 });
351 } 377 }
352 378
@@ -371,7 +397,7 @@ export default abstract class LeafletMap { @@ -371,7 +397,7 @@ export default abstract class LeafletMap {
371 createPolygon(polyData: DatasourceData, dataSources: DatasourceData[], settings: PolygonSettings) { 397 createPolygon(polyData: DatasourceData, dataSources: DatasourceData[], settings: PolygonSettings) {
372 this.ready$.subscribe(() => { 398 this.ready$.subscribe(() => {
373 const polygon = new Polygon(this.map, polyData, dataSources, settings); 399 const polygon = new Polygon(this.map, polyData, dataSources, settings);
374 - const bounds = this.bounds.extend(polygon.leafletPoly.getBounds()); 400 + const bounds = polygon.leafletPoly.getBounds();
375 this.fitBounds(bounds); 401 this.fitBounds(bounds);
376 this.polygons.set(polyData.datasource.entityName, polygon); 402 this.polygons.set(polyData.datasource.entityName, polygon);
377 }); 403 });
@@ -381,7 +407,6 @@ export default abstract class LeafletMap { @@ -381,7 +407,6 @@ export default abstract class LeafletMap {
381 this.ready$.subscribe(() => { 407 this.ready$.subscribe(() => {
382 const poly = this.polygons.get(polyData.datasource.entityName); 408 const poly = this.polygons.get(polyData.datasource.entityName);
383 poly.updatePolygon(polyData.data, dataSources, settings); 409 poly.updatePolygon(polyData.data, dataSources, settings);
384 - this.fitBounds(poly.leafletPoly.getBounds());  
385 }); 410 });
386 } 411 }
387 } 412 }
@@ -159,9 +159,15 @@ export interface HistorySelectSettings { @@ -159,9 +159,15 @@ export interface HistorySelectSettings {
159 buttonColor: string; 159 buttonColor: string;
160 } 160 }
161 161
  162 +export type TripAnimationSttings = {
  163 + pointColor: string;
  164 + pointSize: number;
  165 + pointTooltipOnRightPanel: boolean;
  166 +}
  167 +
162 export type actionsHandler = ($event: Event, datasource: Datasource) => void; 168 export type actionsHandler = ($event: Event, datasource: Datasource) => void;
163 169
164 -export type UnitedMapSettings = MapSettings & PolygonSettings & MarkerSettings & PolylineSettings; 170 +export type UnitedMapSettings = MapSettings & PolygonSettings & MarkerSettings & PolylineSettings & TripAnimationSttings;
165 171
166 interface IProvider { 172 interface IProvider {
167 MapClass: Type<LeafletMap>, 173 MapClass: Type<LeafletMap>,
@@ -113,7 +113,6 @@ export class Marker { @@ -113,7 +113,6 @@ export class Marker {
113 [this.data, this.settings.markerImages, this.dataSources, this.data.dsIndex]) : this.settings.currentImage; 113 [this.data, this.settings.markerImages, this.dataSources, this.data.dsIndex]) : this.settings.currentImage;
114 const currentColor = tinycolor(this.settings.useColorFunction ? safeExecute(this.settings.colorFunction, 114 const currentColor = tinycolor(this.settings.useColorFunction ? safeExecute(this.settings.colorFunction,
115 [this.data, this.dataSources, this.data.dsIndex]) : this.settings.color).toHex(); 115 [this.data, this.dataSources, this.data.dsIndex]) : this.settings.color).toHex();
116 -  
117 if (currentImage && currentImage.url) { 116 if (currentImage && currentImage.url) {
118 aspectCache(currentImage.url).subscribe( 117 aspectCache(currentImage.url).subscribe(
119 (aspect) => { 118 (aspect) => {
@@ -112,7 +112,7 @@ export class ImageMap extends LeafletMap { @@ -112,7 +112,7 @@ export class ImageMap extends LeafletMap {
112 } 112 }
113 } 113 }
114 114
115 - fitBounds(bounds: LatLngBounds, useDefaultZoom = false, padding?: LatLngTuple) { } 115 + fitBounds(bounds: LatLngBounds, padding?: LatLngTuple) { }
116 116
117 initMap(updateImage?) { 117 initMap(updateImage?) {
118 if (!this.map && this.aspect > 0) { 118 if (!this.map && this.aspect > 0) {
@@ -923,35 +923,7 @@ export const pathSchema = @@ -923,35 +923,7 @@ export const pathSchema =
923 title: 'Decorator repeat', 923 title: 'Decorator repeat',
924 type: 'string', 924 type: 'string',
925 default: '20px' 925 default: '20px'
926 - },  
927 - showPoints: {  
928 - title: 'Show points',  
929 - type: 'boolean',  
930 - default: false  
931 - },  
932 - pointColor: {  
933 - title: 'Point color',  
934 - type: 'string'  
935 - },  
936 - pointSize: {  
937 - title: 'Point size (px)',  
938 - type: 'number',  
939 - default: 10  
940 - },  
941 - usePointAsAnchor: {  
942 - title: 'Use point as anchor',  
943 - type: 'boolean',  
944 - default: false  
945 - },  
946 - pointAsAnchorFunction: {  
947 - title: 'Point as anchor function: f(data, dsData, dsIndex)',  
948 - type: 'string'  
949 - },  
950 - pointTooltipOnRightPanel: {  
951 - title: 'Independant point tooltip',  
952 - type: 'boolean',  
953 - default: true  
954 - }, 926 + }
955 }, 927 },
956 required: [] 928 required: []
957 }, 929 },
@@ -986,13 +958,7 @@ export const pathSchema = @@ -986,13 +958,7 @@ export const pathSchema =
986 }, { 958 }, {
987 key: 'decoratorRepeat', 959 key: 'decoratorRepeat',
988 type: 'textarea' 960 type: 'textarea'
989 - }, 'showPoints', {  
990 - key: 'pointColor',  
991 - type: 'color'  
992 - }, 'pointSize', 'usePointAsAnchor', {  
993 - key: 'pointAsAnchorFunction',  
994 - type: 'javascript'  
995 - }, 'pointTooltipOnRightPanel', 961 + }
996 ] 962 ]
997 }; 963 };
998 964
@@ -32,6 +32,6 @@ @@ -32,6 +32,6 @@
32 [ngStyle]="{'background-color': settings.tooltipColor, 'opacity': settings.tooltipOpacity, 'color': settings.tooltipFontColor}"> 32 [ngStyle]="{'background-color': settings.tooltipColor, 'opacity': settings.tooltipOpacity, 'color': settings.tooltipFontColor}">
33 </div> 33 </div>
34 </div> 34 </div>
35 - <tb-history-selector *ngIf="historicalData" [settings]="settings" [intervals]="intervals" 35 + <tb-history-selector *ngIf="historicalData" [settings]="settings" [intervals]="intervals" [anchors]="anchors" [useAnchors]="useAnchors"
36 (timeUpdated)="timeUpdated($event)"></tb-history-selector> 36 (timeUpdated)="timeUpdated($event)"></tb-history-selector>
37 </div> 37 </div>
@@ -21,7 +21,7 @@ import { interpolateOnPointSegment } from 'leaflet-geometryutil'; @@ -21,7 +21,7 @@ import { interpolateOnPointSegment } from 'leaflet-geometryutil';
21 21
22 import { AfterViewInit, ChangeDetectorRef, Component, Input, OnInit, SecurityContext, ViewChild } from '@angular/core'; 22 import { AfterViewInit, ChangeDetectorRef, Component, Input, OnInit, SecurityContext, ViewChild } from '@angular/core';
23 import { MapWidgetController, TbMapWidgetV2 } from '../lib/maps/map-widget2'; 23 import { MapWidgetController, TbMapWidgetV2 } from '../lib/maps/map-widget2';
24 -import { MapProviders } from '../lib/maps/map-models'; 24 +import { MapProviders, FormattedData } from '../lib/maps/map-models';
25 import { initSchema, addToSchema, addGroupInfo, addCondition } from '@app/core/schema-utils'; 25 import { initSchema, addToSchema, addGroupInfo, addCondition } from '@app/core/schema-utils';
26 import { tripAnimationSchema, mapPolygonSchema, pathSchema, pointSchema } from '../lib/maps/schemes'; 26 import { tripAnimationSchema, mapPolygonSchema, pathSchema, pointSchema } from '../lib/maps/schemes';
27 import { DomSanitizer } from '@angular/platform-browser'; 27 import { DomSanitizer } from '@angular/platform-browser';
@@ -58,6 +58,8 @@ export class TripAnimationComponent implements OnInit, AfterViewInit { @@ -58,6 +58,8 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
58 label; 58 label;
59 minTime; 59 minTime;
60 maxTime; 60 maxTime;
  61 + anchors = [];
  62 + useAnchors = false;
61 63
62 static getSettingsSchema(): JsonSettingsSchema { 64 static getSettingsSchema(): JsonSettingsSchema {
63 const schema = initSchema(); 65 const schema = initSchema();
@@ -67,8 +69,8 @@ export class TripAnimationComponent implements OnInit, AfterViewInit { @@ -67,8 +69,8 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
67 addGroupInfo(schema, 'Trip Animation Settings'); 69 addGroupInfo(schema, 'Trip Animation Settings');
68 addToSchema(schema, pathSchema); 70 addToSchema(schema, pathSchema);
69 addGroupInfo(schema, 'Path Settings'); 71 addGroupInfo(schema, 'Path Settings');
70 - addToSchema(schema, addCondition(pointSchema, 'model.showPoint === true', ['showPoint']));  
71 - addGroupInfo(schema, 'Polygon Settings'); 72 + addToSchema(schema, addCondition(pointSchema, 'model.showPoints === true', ['showPoints']));
  73 + addGroupInfo(schema, 'Path Points Settings');
72 addToSchema(schema, addCondition(mapPolygonSchema, 'model.showPolygon === true', ['showPolygon'])); 74 addToSchema(schema, addCondition(mapPolygonSchema, 'model.showPolygon === true', ['showPolygon']));
73 addGroupInfo(schema, 'Polygon Settings'); 75 addGroupInfo(schema, 'Polygon Settings');
74 return schema; 76 return schema;
@@ -84,14 +86,15 @@ export class TripAnimationComponent implements OnInit, AfterViewInit { @@ -84,14 +86,15 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
84 rotationAngle: 0 86 rotationAngle: 0
85 } 87 }
86 this.settings = { ...settings, ...this.ctx.settings }; 88 this.settings = { ...settings, ...this.ctx.settings };
  89 + this.useAnchors = this.settings.usePointAsAnchor && this.settings.showPoints;
  90 + this.settings.fitMapBounds = true;
  91 + this.normalizationStep = this.settings.normalizationStep;
87 const subscription = this.ctx.subscriptions[Object.keys(this.ctx.subscriptions)[0]]; 92 const subscription = this.ctx.subscriptions[Object.keys(this.ctx.subscriptions)[0]];
88 if (subscription) subscription.callbacks.onDataUpdated = () => { 93 if (subscription) subscription.callbacks.onDataUpdated = () => {
89 this.historicalData = parseArray(this.ctx.data); 94 this.historicalData = parseArray(this.ctx.data);
90 this.activeTrip = this.historicalData[0][0]; 95 this.activeTrip = this.historicalData[0][0];
91 this.calculateIntervals(); 96 this.calculateIntervals();
92 this.timeUpdated(this.intervals[0]); 97 this.timeUpdated(this.intervals[0]);
93 - this.mapWidget.map.updatePolylines(this.interpolatedData.map(ds => _.values(ds)));  
94 -  
95 this.mapWidget.map.map?.invalidateSize(); 98 this.mapWidget.map.map?.invalidateSize();
96 this.cd.detectChanges(); 99 this.cd.detectChanges();
97 } 100 }
@@ -110,11 +113,16 @@ export class TripAnimationComponent implements OnInit, AfterViewInit { @@ -110,11 +113,16 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
110 this.calcLabel(); 113 this.calcLabel();
111 this.calcTooltip(); 114 this.calcTooltip();
112 if (this.mapWidget) { 115 if (this.mapWidget) {
  116 + this.mapWidget.map.updatePolylines(this.interpolatedData.map(ds => _.values(ds)));
113 if (this.settings.showPolygon) { 117 if (this.settings.showPolygon) {
114 this.mapWidget.map.updatePolygons(this.interpolatedData); 118 this.mapWidget.map.updatePolygons(this.interpolatedData);
115 } 119 }
116 - if(this.settings.showPoint){  
117 - this.mapWidget.map.updateMarkers(this.interpolatedData) 120 + if (this.settings.showPoints) {
  121 + this.mapWidget.map.updatePoints(this.historicalData[0], this.calcTooltip);
  122 + this.anchors = this.historicalData[0]
  123 + .filter(data =>
  124 + this.settings.usePointAsAnchor ||
  125 + safeExecute(this.settings.pointAsAnchorFunction, [this.historicalData, data, data.dsIndex])).map(data => data.time);
118 } 126 }
119 this.mapWidget.map.updateMarkers(currentPosition); 127 this.mapWidget.map.updateMarkers(currentPosition);
120 } 128 }
@@ -126,23 +134,29 @@ export class TripAnimationComponent implements OnInit, AfterViewInit { @@ -126,23 +134,29 @@ export class TripAnimationComponent implements OnInit, AfterViewInit {
126 calculateIntervals() { 134 calculateIntervals() {
127 this.historicalData.forEach((dataSource, index) => { 135 this.historicalData.forEach((dataSource, index) => {
128 this.intervals = []; 136 this.intervals = [];
129 -  
130 for (let time = dataSource[0]?.time; time < dataSource[dataSource.length - 1]?.time; time += this.normalizationStep) { 137 for (let time = dataSource[0]?.time; time < dataSource[dataSource.length - 1]?.time; time += this.normalizationStep) {
131 this.intervals.push(time); 138 this.intervals.push(time);
132 } 139 }
133 -  
134 this.intervals.push(dataSource[dataSource.length - 1]?.time); 140 this.intervals.push(dataSource[dataSource.length - 1]?.time);
135 this.interpolatedData[index] = this.interpolateArray(dataSource, this.intervals); 141 this.interpolatedData[index] = this.interpolateArray(dataSource, this.intervals);
136 }); 142 });
137 143
138 } 144 }
139 145
140 - calcTooltip() {  
141 - const data = { ...this.activeTrip, maxTime: this.maxTime, minTime: this.minTime }  
142 - const tooltipText: string = this.settings.useTooltipFunction ? 146 + calcTooltip = (point?: FormattedData, setTooltip = true) => {
  147 + if (!point) {
  148 + point = this.activeTrip;
  149 + }
  150 + const data = { ...point, maxTime: this.maxTime, minTime: this.minTime }
  151 + const tooltipPattern: string = this.settings.useTooltipFunction ?
143 safeExecute(this.settings.tooolTipFunction, [data, this.historicalData, 0]) : this.settings.tooltipPattern; 152 safeExecute(this.settings.tooolTipFunction, [data, this.historicalData, 0]) : this.settings.tooltipPattern;
144 - this.mainTooltip = this.sanitizer.sanitize(  
145 - SecurityContext.HTML, (parseWithTranslation.parseTemplate(tooltipText, data, true))); 153 + const tooltipText = parseWithTranslation.parseTemplate(tooltipPattern, data, true);
  154 + if (setTooltip) {
  155 + this.mainTooltip = this.sanitizer.sanitize(
  156 + SecurityContext.HTML, tooltipText);
  157 + this.cd.detectChanges();
  158 + }
  159 + return tooltipText;
146 } 160 }
147 161
148 calcLabel() { 162 calcLabel() {
@@ -28,6 +28,8 @@ export class HistorySelectorComponent implements OnInit, OnChanges { @@ -28,6 +28,8 @@ export class HistorySelectorComponent implements OnInit, OnChanges {
28 28
29 @Input() settings: HistorySelectSettings 29 @Input() settings: HistorySelectSettings
30 @Input() intervals = []; 30 @Input() intervals = [];
  31 + @Input() anchors = [];
  32 + @Input() useAnchors = false;
31 33
32 @Output() timeUpdated: EventEmitter<number> = new EventEmitter(); 34 @Output() timeUpdated: EventEmitter<number> = new EventEmitter();
33 35
@@ -56,7 +58,7 @@ export class HistorySelectorComponent implements OnInit, OnChanges { @@ -56,7 +58,7 @@ export class HistorySelectorComponent implements OnInit, OnChanges {
56 this.interval = interval(1000 / this.speed) 58 this.interval = interval(1000 / this.speed)
57 .pipe( 59 .pipe(
58 filter(() => this.playing)).subscribe(() => { 60 filter(() => this.playing)).subscribe(() => {
59 - this.index++; 61 + this.index++;
60 if (this.index < this.maxTimeIndex) { 62 if (this.index < this.maxTimeIndex) {
61 this.cd.detectChanges(); 63 this.cd.detectChanges();
62 this.timeUpdated.emit(this.intervals[this.index]); 64 this.timeUpdated.emit(this.intervals[this.index]);
@@ -91,14 +93,24 @@ export class HistorySelectorComponent implements OnInit, OnChanges { @@ -91,14 +93,24 @@ export class HistorySelectorComponent implements OnInit, OnChanges {
91 93
92 moveNext() { 94 moveNext() {
93 if (this.index < this.maxTimeIndex) { 95 if (this.index < this.maxTimeIndex) {
94 - this.index++; 96 + if (this.useAnchors) {
  97 + const anchorIndex = this.findIndex(this.intervals[this.index], this.anchors)+1;
  98 + this.index = this.findIndex(this.anchors[anchorIndex], this.intervals);
  99 + }
  100 + else
  101 + this.index++;
95 } 102 }
96 this.pause(); 103 this.pause();
97 } 104 }
98 105
99 movePrev() { 106 movePrev() {
100 if (this.index > this.minTimeIndex) { 107 if (this.index > this.minTimeIndex) {
101 - this.index++; 108 + if (this.useAnchors) {
  109 + const anchorIndex = this.findIndex(this.intervals[this.index], this.anchors) - 1;
  110 + this.index = this.findIndex(this.anchors[anchorIndex], this.intervals);
  111 + }
  112 + else
  113 + this.index--;
102 } 114 }
103 this.pause(); 115 this.pause();
104 } 116 }
@@ -113,6 +125,14 @@ export class HistorySelectorComponent implements OnInit, OnChanges { @@ -113,6 +125,14 @@ export class HistorySelectorComponent implements OnInit, OnChanges {
113 this.pause(); 125 this.pause();
114 } 126 }
115 127
  128 + findIndex(value, array: any[]) {
  129 + let i = 0;
  130 + while (array[i] < value) {
  131 + i++;
  132 + };
  133 + return i;
  134 + }
  135 +
116 changeIndex() { 136 changeIndex() {
117 this.timeUpdated.emit(this.intervals[this.index]); 137 this.timeUpdated.emit(this.intervals[this.index]);
118 } 138 }