Commit 3d52420d17b4fbc9ff7e24416ed9e13167756272

Authored by Igor Kulikov
1 parent 3bc534ec

Fixed map functions. Update map helps.

@@ -25,7 +25,7 @@ import { @@ -25,7 +25,7 @@ import {
25 SubscriptionTimewindow 25 SubscriptionTimewindow
26 } from '@shared/models/time/time.models'; 26 } from '@shared/models/time/time.models';
27 import { UtilsService } from '@core/services/utils.service'; 27 import { UtilsService } from '@core/services/utils.service';
28 -import { deepClone, isNumeric } from '@core/utils'; 28 +import { deepClone, isNumber, isNumeric } from '@core/utils';
29 import Timeout = NodeJS.Timeout; 29 import Timeout = NodeJS.Timeout;
30 30
31 export declare type onAggregatedData = (data: SubscriptionData, detectChanges: boolean) => void; 31 export declare type onAggregatedData = (data: SubscriptionData, detectChanges: boolean) => void;
@@ -92,20 +92,36 @@ declare type AggFunction = (aggData: AggData, value?: any) => void; @@ -92,20 +92,36 @@ declare type AggFunction = (aggData: AggData, value?: any) => void;
92 92
93 const avg: AggFunction = (aggData: AggData, value?: any) => { 93 const avg: AggFunction = (aggData: AggData, value?: any) => {
94 aggData.count++; 94 aggData.count++;
95 - aggData.sum += value;  
96 - aggData.aggValue = aggData.sum / aggData.count; 95 + if (isNumber(value)) {
  96 + aggData.sum += value;
  97 + aggData.aggValue = aggData.sum / aggData.count;
  98 + } else {
  99 + aggData.aggValue = value;
  100 + }
97 }; 101 };
98 102
99 const min: AggFunction = (aggData: AggData, value?: any) => { 103 const min: AggFunction = (aggData: AggData, value?: any) => {
100 - aggData.aggValue = Math.min(aggData.aggValue, value); 104 + if (isNumber(value)) {
  105 + aggData.aggValue = Math.min(aggData.aggValue, value);
  106 + } else {
  107 + aggData.aggValue = value;
  108 + }
101 }; 109 };
102 110
103 const max: AggFunction = (aggData: AggData, value?: any) => { 111 const max: AggFunction = (aggData: AggData, value?: any) => {
104 - aggData.aggValue = Math.max(aggData.aggValue, value); 112 + if (isNumber(value)) {
  113 + aggData.aggValue = Math.max(aggData.aggValue, value);
  114 + } else {
  115 + aggData.aggValue = value;
  116 + }
105 }; 117 };
106 118
107 const sum: AggFunction = (aggData: AggData, value?: any) => { 119 const sum: AggFunction = (aggData: AggData, value?: any) => {
108 - aggData.aggValue = aggData.aggValue + value; 120 + if (isNumber(value)) {
  121 + aggData.aggValue = aggData.aggValue + value;
  122 + } else {
  123 + aggData.aggValue = value;
  124 + }
109 }; 125 };
110 126
111 const count: AggFunction = (aggData: AggData) => { 127 const count: AggFunction = (aggData: AggData) => {
@@ -409,7 +425,7 @@ export class DataAggregator { @@ -409,7 +425,7 @@ export class DataAggregator {
409 } 425 }
410 426
411 private convertValue(val: string): any { 427 private convertValue(val: string): any {
412 - if (!this.noAggregation || val && isNumeric(val) && Number(val).toString() === val) { 428 + if (val && isNumeric(val) && (!this.noAggregation || this.noAggregation && Number(val).toString() === val)) {
413 return Number(val); 429 return Number(val);
414 } 430 }
415 return val; 431 return val;
@@ -16,7 +16,7 @@ @@ -16,7 +16,7 @@
16 16
17 import { FormattedData, MapProviders, ReplaceInfo } from '@home/components/widget/lib/maps/map-models'; 17 import { FormattedData, MapProviders, ReplaceInfo } from '@home/components/widget/lib/maps/map-models';
18 import { 18 import {
19 - createLabelFromDatasource, deepClone, 19 + createLabelFromDatasource,
20 hashCode, 20 hashCode,
21 isDefined, 21 isDefined,
22 isDefinedAndNotNull, 22 isDefinedAndNotNull,
@@ -127,7 +127,6 @@ export function aspectCache(imageUrl: string): Observable<number> { @@ -127,7 +127,6 @@ export function aspectCache(imageUrl: string): Observable<number> {
127 127
128 export type TranslateFunc = (key: string, defaultTranslation?: string) => string; 128 export type TranslateFunc = (key: string, defaultTranslation?: string) => string;
129 129
130 -const varsRegex = /\${([^}]*)}/g;  
131 const linkActionRegex = /<link-act name=['"]([^['"]*)['"]>([^<]*)<\/link-act>/g; 130 const linkActionRegex = /<link-act name=['"]([^['"]*)['"]>([^<]*)<\/link-act>/g;
132 const buttonActionRegex = /<button-act name=['"]([^['"]*)['"]>([^<]*)<\/button-act>/g; 131 const buttonActionRegex = /<button-act name=['"]([^['"]*)['"]>([^<]*)<\/button-act>/g;
133 132
@@ -304,7 +303,8 @@ export const parseWithTranslation = { @@ -304,7 +303,8 @@ export const parseWithTranslation = {
304 if (this.translateFn) { 303 if (this.translateFn) {
305 return this.translateFn(key, defaultTranslation); 304 return this.translateFn(key, defaultTranslation);
306 } else { 305 } else {
307 - throw console.error('Translate not assigned'); 306 + console.error('Translate not assigned');
  307 + throw Error('Translate not assigned');
308 } 308 }
309 }, 309 },
310 parseTemplate(template: string, data: object, forceTranslate = false): string { 310 parseTemplate(template: string, data: object, forceTranslate = false): string {
@@ -334,7 +334,7 @@ export function parseData(input: DatasourceData[], dataIndex?: number): Formatte @@ -334,7 +334,7 @@ export function parseData(input: DatasourceData[], dataIndex?: number): Formatte
334 if (!obj.hasOwnProperty(el.dataKey.label) || el.data[dataIndex][1] !== '') { 334 if (!obj.hasOwnProperty(el.dataKey.label) || el.data[dataIndex][1] !== '') {
335 obj[el.dataKey.label] = el.data[dataIndex][1]; 335 obj[el.dataKey.label] = el.data[dataIndex][1];
336 obj[el.dataKey.label + '|ts'] = el.data[dataIndex][0]; 336 obj[el.dataKey.label + '|ts'] = el.data[dataIndex][0];
337 - if (el.dataKey.label === 'type') { 337 + if (el.dataKey.label.toLowerCase() === 'type') {
338 obj.deviceType = el.data[dataIndex][1]; 338 obj.deviceType = el.data[dataIndex][1];
339 } 339 }
340 } 340 }
@@ -360,28 +360,35 @@ export function flatData(input: FormattedData[]): FormattedData { @@ -360,28 +360,35 @@ export function flatData(input: FormattedData[]): FormattedData {
360 } 360 }
361 361
362 export function parseArray(input: DatasourceData[]): FormattedData[][] { 362 export function parseArray(input: DatasourceData[]): FormattedData[][] {
363 - return _(input).groupBy(el => el?.datasource?.entityName)  
364 - .values().value().map((entityArray) =>  
365 - entityArray[0].data.map((el, i) => {  
366 - const obj: FormattedData = {  
367 - entityName: entityArray[0]?.datasource?.entityName,  
368 - entityId: entityArray[0]?.datasource?.entityId,  
369 - entityType: entityArray[0]?.datasource?.entityType,  
370 - $datasource: entityArray[0]?.datasource,  
371 - dsIndex: i,  
372 - time: el[0],  
373 - deviceType: null  
374 - };  
375 - entityArray.filter(e => e.data.length && e.data[i]).forEach(entity => {  
376 - obj[entity?.dataKey?.label] = entity?.data[i][1];  
377 - obj[entity?.dataKey?.label + '|ts'] = entity?.data[0][0];  
378 - if (entity?.dataKey?.label === 'type') {  
379 - obj.deviceType = entity?.data[0][1]; 363 + return _(input).groupBy(el => el.datasource.entityName)
  364 + .values().value().map((entityArray, dsIndex) => {
  365 + const timeDataMap: {[time: number]: FormattedData} = {};
  366 + entityArray.filter(e => e.data.length).forEach(entity => {
  367 + entity.data.forEach(tsData => {
  368 + const time = tsData[0];
  369 + const value = tsData[1];
  370 + let data = timeDataMap[time];
  371 + if (!data) {
  372 + data = {
  373 + entityName: entity.datasource.entityName,
  374 + entityId: entity.datasource.entityId,
  375 + entityType: entity.datasource.entityType,
  376 + $datasource: entity.datasource,
  377 + dsIndex,
  378 + time,
  379 + deviceType: null
  380 + };
  381 + timeDataMap[time] = data;
  382 + }
  383 + data[entity.dataKey.label] = value;
  384 + data[entity.dataKey.label + '|ts'] = time;
  385 + if (entity.dataKey.label.toLowerCase() === 'type') {
  386 + data.deviceType = value;
380 } 387 }
381 }); 388 });
382 - return obj;  
383 - })  
384 - ); 389 + });
  390 + return _.values(timeDataMap);
  391 + });
385 } 392 }
386 393
387 export function parseFunction(source: any, params: string[] = ['def']): (...args: any[]) => any { 394 export function parseFunction(source: any, params: string[] = ['def']): (...args: any[]) => any {
@@ -474,9 +474,10 @@ export default abstract class LeafletMap { @@ -474,9 +474,10 @@ export default abstract class LeafletMap {
474 this.showPolygon = showPolygon; 474 this.showPolygon = showPolygon;
475 if (this.map) { 475 if (this.map) {
476 const data = this.ctx.data; 476 const data = this.ctx.data;
477 - const formattedData = parseData(this.ctx.data); 477 + const formattedData = parseData(data);
478 if (drawRoutes) { 478 if (drawRoutes) {
479 - this.updatePolylines(parseArray(data), false); 479 + const polyData = parseArray(data);
  480 + this.updatePolylines(polyData, formattedData, false);
480 } 481 }
481 if (showPolygon) { 482 if (showPolygon) {
482 this.updatePolygons(formattedData, false); 483 this.updatePolygons(formattedData, false);
@@ -633,7 +634,8 @@ export default abstract class LeafletMap { @@ -633,7 +634,8 @@ export default abstract class LeafletMap {
633 return polygon; 634 return polygon;
634 } 635 }
635 636
636 - updatePoints(pointsData: FormattedData[][], getTooltip: (point: FormattedData) => string) { 637 + updatePoints(pointsData: FormattedData[][],
  638 + getTooltip: (point: FormattedData, points: FormattedData[]) => string) {
637 if (pointsData.length) { 639 if (pointsData.length) {
638 if (this.points) { 640 if (this.points) {
639 this.map.removeLayer(this.points); 641 this.map.removeLayer(this.points);
@@ -642,21 +644,25 @@ export default abstract class LeafletMap { @@ -642,21 +644,25 @@ export default abstract class LeafletMap {
642 } 644 }
643 let pointColor = this.options.pointColor; 645 let pointColor = this.options.pointColor;
644 for (const pointsList of pointsData) { 646 for (const pointsList of pointsData) {
645 - pointsList.filter(pdata => !!this.convertPosition(pdata)).forEach(data => {  
646 - if (this.options.useColorPointFunction) {  
647 - pointColor = safeExecute(this.options.colorPointFunction, [data, pointsData, data.dsIndex]);  
648 - }  
649 - const point = L.circleMarker(this.convertPosition(data), {  
650 - color: pointColor,  
651 - radius: this.options.pointSize  
652 - });  
653 - if (!this.options.pointTooltipOnRightPanel) {  
654 - point.on('click', () => getTooltip(data));  
655 - } else {  
656 - createTooltip(point, this.options, data.$datasource, getTooltip(data)); 647 + for (let tsIndex = 0; tsIndex < pointsList.length; tsIndex++) {
  648 + const pdata = pointsList[tsIndex];
  649 + if (!!this.convertPosition(pdata)) {
  650 + const dsData = pointsData.map(ds => ds[tsIndex]);
  651 + if (this.options.useColorPointFunction) {
  652 + pointColor = safeExecute(this.options.colorPointFunction, [pdata, dsData, pdata.dsIndex]);
  653 + }
  654 + const point = L.circleMarker(this.convertPosition(pdata), {
  655 + color: pointColor,
  656 + radius: this.options.pointSize
  657 + });
  658 + if (!this.options.pointTooltipOnRightPanel) {
  659 + point.on('click', () => getTooltip(pdata, dsData));
  660 + } else {
  661 + createTooltip(point, this.options, pdata.$datasource, getTooltip(pdata, dsData));
  662 + }
  663 + this.points.addLayer(point);
657 } 664 }
658 - this.points.addLayer(point);  
659 - }); 665 + }
660 } 666 }
661 if (pointsData.length) { 667 if (pointsData.length) {
662 this.map.addLayer(this.points); 668 this.map.addLayer(this.points);
@@ -665,15 +671,15 @@ export default abstract class LeafletMap { @@ -665,15 +671,15 @@ export default abstract class LeafletMap {
665 671
666 // Polyline 672 // Polyline
667 673
668 - updatePolylines(polyData: FormattedData[][], updateBounds = true, activePolyline?: FormattedData) { 674 + updatePolylines(polyData: FormattedData[][], dsData: FormattedData[], updateBounds = true) {
669 const keys: string[] = []; 675 const keys: string[] = [];
670 - polyData.forEach((dataSource: FormattedData[]) => {  
671 - const data = activePolyline || dataSource[0];  
672 - if (dataSource.length && data.entityName === dataSource[0].entityName) { 676 + polyData.forEach((tsData: FormattedData[], index) => {
  677 + const data = dsData[index];
  678 + if (tsData.length && data.entityName === tsData[0].entityName) {
673 if (this.polylines.get(data.entityName)) { 679 if (this.polylines.get(data.entityName)) {
674 - this.updatePolyline(data, dataSource, this.options, updateBounds); 680 + this.updatePolyline(data, tsData, dsData, this.options, updateBounds);
675 } else { 681 } else {
676 - this.createPolyline(data, dataSource, this.options, updateBounds); 682 + this.createPolyline(data, tsData, dsData, this.options, updateBounds);
677 } 683 }
678 keys.push(data.entityName); 684 keys.push(data.entityName);
679 } 685 }
@@ -689,9 +695,9 @@ export default abstract class LeafletMap { @@ -689,9 +695,9 @@ export default abstract class LeafletMap {
689 }); 695 });
690 } 696 }
691 697
692 - createPolyline(data: FormattedData, dataSources: FormattedData[], settings: PolylineSettings, updateBounds = true) { 698 + createPolyline(data: FormattedData, tsData: FormattedData[], dsData: FormattedData[], settings: PolylineSettings, updateBounds = true) {
693 const poly = new Polyline(this.map, 699 const poly = new Polyline(this.map,
694 - dataSources.map(el => this.convertPosition(el)).filter(el => !!el), data, dataSources, settings); 700 + tsData.map(el => this.convertPosition(el)).filter(el => !!el), data, dsData, settings);
695 if (updateBounds) { 701 if (updateBounds) {
696 const bounds = poly.leafletPoly.getBounds(); 702 const bounds = poly.leafletPoly.getBounds();
697 this.fitBounds(bounds); 703 this.fitBounds(bounds);
@@ -699,10 +705,10 @@ export default abstract class LeafletMap { @@ -699,10 +705,10 @@ export default abstract class LeafletMap {
699 this.polylines.set(data.entityName, poly); 705 this.polylines.set(data.entityName, poly);
700 } 706 }
701 707
702 - updatePolyline(data: FormattedData, dataSources: FormattedData[], settings: PolylineSettings, updateBounds = true) { 708 + updatePolyline(data: FormattedData, tsData: FormattedData[], dsData: FormattedData[], settings: PolylineSettings, updateBounds = true) {
703 const poly = this.polylines.get(data.entityName); 709 const poly = this.polylines.get(data.entityName);
704 const oldBounds = poly.leafletPoly.getBounds(); 710 const oldBounds = poly.leafletPoly.getBounds();
705 - poly.updatePolyline(dataSources.map(el => this.convertPosition(el)).filter(el => !!el), data, dataSources, settings); 711 + poly.updatePolyline(tsData.map(el => this.convertPosition(el)).filter(el => !!el), data, dsData, settings);
706 const newBounds = poly.leafletPoly.getBounds(); 712 const newBounds = poly.leafletPoly.getBounds();
707 if (updateBounds && oldBounds.toBBoxString() !== newBounds.toBBoxString()) { 713 if (updateBounds && oldBounds.toBBoxString() !== newBounds.toBBoxString()) {
708 this.fitBounds(newBounds); 714 this.fitBounds(newBounds);
@@ -866,7 +866,7 @@ export const pathSchema = @@ -866,7 +866,7 @@ export const pathSchema =
866 'useColorFunction', 866 'useColorFunction',
867 { 867 {
868 key: 'colorFunction', 868 key: 'colorFunction',
869 - helpId: 'widget/lib/map/trip_path_color_fn', 869 + helpId: 'widget/lib/map/path_color_fn',
870 type: 'javascript', 870 type: 'javascript',
871 condition: 'model.useColorFunction === true' 871 condition: 'model.useColorFunction === true'
872 }, 872 },
@@ -961,7 +961,7 @@ export const pointSchema = @@ -961,7 +961,7 @@ export const pointSchema =
961 'useColorPointFunction', 961 'useColorPointFunction',
962 { 962 {
963 key: 'colorPointFunction', 963 key: 'colorPointFunction',
964 - helpId: 'widget/lib/map/trip_path_point_color_fn', 964 + helpId: 'widget/lib/map/path_point_color_fn',
965 type: 'javascript', 965 type: 'javascript',
966 condition: 'model.useColorPointFunction === true' 966 condition: 'model.useColorPointFunction === true'
967 }, 967 },
@@ -1150,7 +1150,7 @@ export const tripAnimationSchema = { @@ -1150,7 +1150,7 @@ export const tripAnimationSchema = {
1150 { 1150 {
1151 key: 'labelFunction', 1151 key: 'labelFunction',
1152 type: 'javascript', 1152 type: 'javascript',
1153 - helpId: 'widget/lib/map/trip_label_fn', 1153 + helpId: 'widget/lib/map/label_fn',
1154 condition: 'model.showLabel === true && model.useLabelFunction === true' 1154 condition: 'model.showLabel === true && model.useLabelFunction === true'
1155 }, 1155 },
1156 'showTooltip', 1156 'showTooltip',
@@ -1184,7 +1184,7 @@ export const tripAnimationSchema = { @@ -1184,7 +1184,7 @@ export const tripAnimationSchema = {
1184 { 1184 {
1185 key: 'tooltipFunction', 1185 key: 'tooltipFunction',
1186 type: 'javascript', 1186 type: 'javascript',
1187 - helpId: 'widget/lib/map/trip_tooltip_fn', 1187 + helpId: 'widget/lib/map/tooltip_fn',
1188 condition: 'model.showTooltip === true && model.useTooltipFunction === true' 1188 condition: 'model.showTooltip === true && model.useTooltipFunction === true'
1189 }, 1189 },
1190 'rotationAngle', 1190 'rotationAngle',
@@ -1201,7 +1201,7 @@ export const tripAnimationSchema = { @@ -1201,7 +1201,7 @@ export const tripAnimationSchema = {
1201 { 1201 {
1202 key: 'markerImageFunction', 1202 key: 'markerImageFunction',
1203 type: 'javascript', 1203 type: 'javascript',
1204 - helpId: 'widget/lib/map/trip_marker_image_fn', 1204 + helpId: 'widget/lib/map/marker_image_fn',
1205 condition: 'model.useMarkerImageFunction === true' 1205 condition: 'model.useMarkerImageFunction === true'
1206 }, 1206 },
1207 { 1207 {
@@ -16,9 +16,7 @@ @@ -16,9 +16,7 @@
16 16
17 --> 17 -->
18 <div class="trip-animation-widget"> 18 <div class="trip-animation-widget">
19 - <div class="trip-animation-label-container" *ngIf="settings.showLabel">  
20 - {{label}}  
21 - </div> 19 + <div class="trip-animation-label-container" *ngIf="settings.showLabel" [innerHTML]="label"></div>
22 <div class="trip-animation-container" fxLayout="column"> 20 <div class="trip-animation-container" fxLayout="column">
23 <div class="map" #map></div> 21 <div class="map" #map></div>
24 <div class="trip-animation-info-panel" fxLayout="row"> 22 <div class="trip-animation-info-panel" fxLayout="row">
@@ -30,7 +30,7 @@ import { @@ -30,7 +30,7 @@ import {
30 import { FormattedData, MapProviders, TripAnimationSettings } from '@home/components/widget/lib/maps/map-models'; 30 import { FormattedData, MapProviders, TripAnimationSettings } from '@home/components/widget/lib/maps/map-models';
31 import { addCondition, addGroupInfo, addToSchema, initSchema } from '@app/core/schema-utils'; 31 import { addCondition, addGroupInfo, addToSchema, initSchema } from '@app/core/schema-utils';
32 import { mapPolygonSchema, pathSchema, pointSchema, tripAnimationSchema } from '@home/components/widget/lib/maps/schemes'; 32 import { mapPolygonSchema, pathSchema, pointSchema, tripAnimationSchema } from '@home/components/widget/lib/maps/schemes';
33 -import { DomSanitizer } from '@angular/platform-browser'; 33 +import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
34 import { WidgetContext } from '@app/modules/home/models/widget-component.models'; 34 import { WidgetContext } from '@app/modules/home/models/widget-component.models';
35 import { 35 import {
36 findAngle, getProviderSchema, 36 findAngle, getProviderSchema,
@@ -70,13 +70,14 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy @@ -70,13 +70,14 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
70 mapWidget: MapWidgetInterface; 70 mapWidget: MapWidgetInterface;
71 historicalData: FormattedData[][]; 71 historicalData: FormattedData[][];
72 normalizationStep: number; 72 normalizationStep: number;
73 - interpolatedTimeData = []; 73 + interpolatedTimeData: {[time: number]: FormattedData}[] = [];
  74 + formattedInterpolatedTimeData: FormattedData[][] = [];
74 widgetConfig: WidgetConfig; 75 widgetConfig: WidgetConfig;
75 settings: TripAnimationSettings; 76 settings: TripAnimationSettings;
76 mainTooltips = []; 77 mainTooltips = [];
77 visibleTooltip = false; 78 visibleTooltip = false;
78 activeTrip: FormattedData; 79 activeTrip: FormattedData;
79 - label: string; 80 + label: SafeHtml;
80 minTime: number; 81 minTime: number;
81 maxTime: number; 82 maxTime: number;
82 anchors: number[] = []; 83 anchors: number[] = [];
@@ -117,6 +118,8 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy @@ -117,6 +118,8 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
117 const subscription = this.ctx.defaultSubscription; 118 const subscription = this.ctx.defaultSubscription;
118 subscription.callbacks.onDataUpdated = () => { 119 subscription.callbacks.onDataUpdated = () => {
119 this.historicalData = parseArray(this.ctx.data).filter(arr => arr.length); 120 this.historicalData = parseArray(this.ctx.data).filter(arr => arr.length);
  121 + this.interpolatedTimeData.length = 0;
  122 + this.formattedInterpolatedTimeData.length = 0;
120 if (this.historicalData.length) { 123 if (this.historicalData.length) {
121 this.calculateIntervals(); 124 this.calculateIntervals();
122 this.timeUpdated(this.minTime); 125 this.timeUpdated(this.minTime);
@@ -146,6 +149,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy @@ -146,6 +149,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
146 149
147 timeUpdated(time: number) { 150 timeUpdated(time: number) {
148 this.currentTime = time; 151 this.currentTime = time;
  152 + // get point for each datasource associated with time
149 const currentPosition = this.interpolatedTimeData 153 const currentPosition = this.interpolatedTimeData
150 .map(dataSource => dataSource[time]); 154 .map(dataSource => dataSource[time]);
151 for (let j = 0; j < this.interpolatedTimeData.length; j++) { 155 for (let j = 0; j < this.interpolatedTimeData.length; j++) {
@@ -171,20 +175,20 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy @@ -171,20 +175,20 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
171 currentPosition[j] = this.calculateLastPoints(this.interpolatedTimeData[j], time); 175 currentPosition[j] = this.calculateLastPoints(this.interpolatedTimeData[j], time);
172 } 176 }
173 } 177 }
174 - this.calcLabel(); 178 + this.calcLabel(currentPosition);
175 this.calcMainTooltip(currentPosition); 179 this.calcMainTooltip(currentPosition);
176 if (this.mapWidget && this.mapWidget.map && this.mapWidget.map.map) { 180 if (this.mapWidget && this.mapWidget.map && this.mapWidget.map.map) {
177 - const formattedInterpolatedTimeData = this.interpolatedTimeData.map(ds => _.values(ds));  
178 - this.mapWidget.map.updatePolylines(formattedInterpolatedTimeData, true); 181 + this.mapWidget.map.updatePolylines(this.formattedInterpolatedTimeData, currentPosition, true);
179 if (this.settings.showPolygon) { 182 if (this.settings.showPolygon) {
180 - this.mapWidget.map.updatePolygons(this.interpolatedTimeData); 183 + this.mapWidget.map.updatePolygons(currentPosition);
181 } 184 }
182 if (this.settings.showPoints) { 185 if (this.settings.showPoints) {
183 - this.mapWidget.map.updatePoints(formattedInterpolatedTimeData.map(ds => _.union(ds)), this.calcTooltip); 186 + this.mapWidget.map.updatePoints(this.formattedInterpolatedTimeData, this.calcTooltip);
184 } 187 }
185 this.mapWidget.map.updateMarkers(currentPosition, true, (trip) => { 188 this.mapWidget.map.updateMarkers(currentPosition, true, (trip) => {
186 this.activeTrip = trip; 189 this.activeTrip = trip;
187 this.timeUpdated(this.currentTime); 190 this.timeUpdated(this.currentTime);
  191 + this.cd.markForCheck();
188 }); 192 });
189 } 193 }
190 } 194 }
@@ -215,41 +219,44 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy @@ -215,41 +219,44 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
215 this.maxTime = dataSource[dataSource.length - 1]?.time || -Infinity; 219 this.maxTime = dataSource[dataSource.length - 1]?.time || -Infinity;
216 this.interpolatedTimeData[index] = this.interpolateArray(dataSource); 220 this.interpolatedTimeData[index] = this.interpolateArray(dataSource);
217 }); 221 });
  222 + this.formattedInterpolatedTimeData = this.interpolatedTimeData.map(ds => _.values(ds));
218 if (!this.activeTrip) { 223 if (!this.activeTrip) {
219 this.activeTrip = this.interpolatedTimeData.map(dataSource => dataSource[this.minTime]).filter(ds => ds)[0]; 224 this.activeTrip = this.interpolatedTimeData.map(dataSource => dataSource[this.minTime]).filter(ds => ds)[0];
220 } 225 }
221 if (this.useAnchors) { 226 if (this.useAnchors) {
222 const anchorDate = Object.entries(_.union(this.interpolatedTimeData)[0]); 227 const anchorDate = Object.entries(_.union(this.interpolatedTimeData)[0]);
223 this.anchors = anchorDate 228 this.anchors = anchorDate
224 - .filter((data: [string, FormattedData]) => safeExecute(this.settings.pointAsAnchorFunction, [data[1], anchorDate, data[1].dsIndex])) 229 + .filter((data: [string, FormattedData], tsIndex) => safeExecute(this.settings.pointAsAnchorFunction, [data[1],
  230 + this.formattedInterpolatedTimeData.map(ds => ds[tsIndex]), data[1].dsIndex]))
225 .map(data => parseInt(data[0], 10)); 231 .map(data => parseInt(data[0], 10));
226 } 232 }
227 } 233 }
228 234
229 - calcTooltip = (point: FormattedData): string => { 235 + calcTooltip = (point: FormattedData, points: FormattedData[]): string => {
230 const data = point ? point : this.activeTrip; 236 const data = point ? point : this.activeTrip;
231 const tooltipPattern: string = this.settings.useTooltipFunction ? 237 const tooltipPattern: string = this.settings.useTooltipFunction ?
232 - safeExecute(this.settings.tooltipFunction, [data, this.historicalData, point.dsIndex]) : this.settings.tooltipPattern; 238 + safeExecute(this.settings.tooltipFunction,
  239 + [data, points, point.dsIndex]) : this.settings.tooltipPattern;
233 return parseWithTranslation.parseTemplate(tooltipPattern, data, true); 240 return parseWithTranslation.parseTemplate(tooltipPattern, data, true);
234 } 241 }
235 242
236 private calcMainTooltip(points: FormattedData[]): void { 243 private calcMainTooltip(points: FormattedData[]): void {
237 const tooltips = []; 244 const tooltips = [];
238 for (const point of points) { 245 for (const point of points) {
239 - tooltips.push(this.sanitizer.sanitize(SecurityContext.HTML, this.calcTooltip(point))); 246 + tooltips.push(this.sanitizer.sanitize(SecurityContext.HTML, this.calcTooltip(point, points)));
240 } 247 }
241 this.mainTooltips = tooltips; 248 this.mainTooltips = tooltips;
242 } 249 }
243 250
244 - calcLabel() {  
245 - const data = this.activeTrip; 251 + calcLabel(points: FormattedData[]) {
  252 + const data = points[this.activeTrip.dsIndex];
246 const labelText: string = this.settings.useLabelFunction ? 253 const labelText: string = this.settings.useLabelFunction ?
247 - safeExecute(this.settings.labelFunction, [data, this.historicalData, data.dsIndex]) : this.settings.label;  
248 - this.label = (parseWithTranslation.parseTemplate(labelText, data, true)); 254 + safeExecute(this.settings.labelFunction, [data, points, data.dsIndex]) : this.settings.label;
  255 + this.label = this.sanitizer.bypassSecurityTrustHtml(parseWithTranslation.parseTemplate(labelText, data, true));
249 } 256 }
250 257
251 - interpolateArray(originData: FormattedData[]) {  
252 - const result = {}; 258 + interpolateArray(originData: FormattedData[]): {[time: number]: FormattedData} {
  259 + const result: {[time: number]: FormattedData} = {};
253 const latKeyName = this.settings.latKeyName; 260 const latKeyName = this.settings.latKeyName;
254 const lngKeyName = this.settings.lngKeyName; 261 const lngKeyName = this.settings.lngKeyName;
255 for (const data of originData) { 262 for (const data of originData) {
1 - <li><b>data:</b> <code><a href="https://github.com/thingsboard/thingsboard/blob/5bb6403407aa4898084832d6698aa9ea6d484889/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts#L108" target="_blank">FormattedData</a></code> - A <a href="https://github.com/thingsboard/thingsboard/blob/5bb6403407aa4898084832d6698aa9ea6d484889/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts#L108" target="_blank">FormattedData</a> object associated with marker.<br/> 1 + <li><b>data:</b> <code><a href="https://github.com/thingsboard/thingsboard/blob/5bb6403407aa4898084832d6698aa9ea6d484889/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts#L108" target="_blank">FormattedData</a></code> - A <a href="https://github.com/thingsboard/thingsboard/blob/5bb6403407aa4898084832d6698aa9ea6d484889/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts#L108" target="_blank">FormattedData</a> object associated with marker or data point of the route.<br/>
2 Represents basic entity properties (ex. <code>entityId</code>, <code>entityName</code>)<br/>and provides access to other entity attributes/timeseries declared in widget datasource configuration. 2 Represents basic entity properties (ex. <code>entityId</code>, <code>entityName</code>)<br/>and provides access to other entity attributes/timeseries declared in widget datasource configuration.
3 </li> 3 </li>
4 - <li><b>dsData:</b> <code><a href="https://github.com/thingsboard/thingsboard/blob/5bb6403407aa4898084832d6698aa9ea6d484889/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts#L108" target="_blank">FormattedData[]</a></code> - All available widget data (entities) as array of <a href="https://github.com/thingsboard/thingsboard/blob/5bb6403407aa4898084832d6698aa9ea6d484889/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts#L108" target="_blank">FormattedData</a> objects<br/> 4 + <li><b>dsData:</b> <code><a href="https://github.com/thingsboard/thingsboard/blob/5bb6403407aa4898084832d6698aa9ea6d484889/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts#L108" target="_blank">FormattedData[]</a></code> - All available data associated with markers or routes data points as array of <a href="https://github.com/thingsboard/thingsboard/blob/5bb6403407aa4898084832d6698aa9ea6d484889/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts#L108" target="_blank">FormattedData</a> objects<br/>
5 resolved from configured datasources. Each object represents basic entity properties (ex. <code>entityId</code>, <code>entityName</code>)<br/> 5 resolved from configured datasources. Each object represents basic entity properties (ex. <code>entityId</code>, <code>entityName</code>)<br/>
6 and provides access to other entity attributes/timeseries declared in widget datasource configuration. 6 and provides access to other entity attributes/timeseries declared in widget datasource configuration.
7 </li> 7 </li>
8 - <li><b>dsIndex</b> <code>number</code> - index of the current marker data in <code>dsData</code> array.<br> 8 + <li><b>dsIndex</b> <code>number</code> - index of the current marker data or route data point in <code>dsData</code> array.<br>
9 <strong>Note: </strong> The <code>data</code> argument is equivalent to <code>dsData[dsIndex]</code> expression. 9 <strong>Note: </strong> The <code>data</code> argument is equivalent to <code>dsData[dsIndex]</code> expression.
10 </li> 10 </li>
ui-ngx/src/assets/help/en_US/widget/lib/map/path_color_fn.md renamed from ui-ngx/src/assets/help/en_US/widget/lib/map/trip_path_color_fn.md
@@ -10,7 +10,7 @@ A JavaScript function used to compute color of the trip path. @@ -10,7 +10,7 @@ A JavaScript function used to compute color of the trip path.
10 **Parameters:** 10 **Parameters:**
11 11
12 <ul> 12 <ul>
13 - {% include widget/lib/map/trip_fn_args %} 13 + {% include widget/lib/map/map_fn_args %}
14 </ul> 14 </ul>
15 15
16 **Returns:** 16 **Returns:**
ui-ngx/src/assets/help/en_US/widget/lib/map/path_point_color_fn.md renamed from ui-ngx/src/assets/help/en_US/widget/lib/map/trip_path_point_color_fn.md
@@ -10,7 +10,7 @@ A JavaScript function used to compute color of the trip path point. @@ -10,7 +10,7 @@ A JavaScript function used to compute color of the trip path point.
10 **Parameters:** 10 **Parameters:**
11 11
12 <ul> 12 <ul>
13 - {% include widget/lib/map/trip_fn_args %} 13 + {% include widget/lib/map/map_fn_args %}
14 </ul> 14 </ul>
15 15
16 **Returns:** 16 **Returns:**
@@ -5,7 +5,7 @@ @@ -5,7 +5,7 @@
5 5
6 *function (data, dsData, dsIndex): string* 6 *function (data, dsData, dsIndex): string*
7 7
8 -A JavaScript function used to compute text or HTML code to be displayed in the marker tooltip. 8 +A JavaScript function used to compute text or HTML code to be displayed in the marker, point or polygon tooltip.
9 9
10 **Parameters:** 10 **Parameters:**
11 11
@@ -15,7 +15,7 @@ A JavaScript function used to compute text or HTML code to be displayed in the m @@ -15,7 +15,7 @@ A JavaScript function used to compute text or HTML code to be displayed in the m
15 15
16 **Returns:** 16 **Returns:**
17 17
18 -Should return string value presenting text or HTML for the marker tooltip. 18 +Should return string value presenting text or HTML for the tooltip.
19 19
20 <div class="divider"></div> 20 <div class="divider"></div>
21 21
1 - <li><b>data:</b> <code><a href="https://github.com/thingsboard/thingsboard/blob/5bb6403407aa4898084832d6698aa9ea6d484889/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts#L108" target="_blank">FormattedData</a></code> - A <a href="https://github.com/thingsboard/thingsboard/blob/5bb6403407aa4898084832d6698aa9ea6d484889/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts#L108" target="_blank">FormattedData</a> object associated with the point of the trip.<br/>  
2 - Represents basic entity properties (ex. <code>entityId</code>, <code>entityName</code>)<br/>and provides access to other entity attributes/timeseries declared in widget datasource configuration.  
3 - </li>  
4 - <li><b>dsData:</b> <code><a href="https://github.com/thingsboard/thingsboard/blob/5bb6403407aa4898084832d6698aa9ea6d484889/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts#L108" target="_blank">FormattedData[][]</a></code> - two-dimensional array of <a href="https://github.com/thingsboard/thingsboard/blob/5bb6403407aa4898084832d6698aa9ea6d484889/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts#L108" target="_blank">FormattedData</a> objects presenting<br/>  
5 - array of trips (entities with timeseries data) resolved from configured datasources.<br/>  
6 - Each element of array is particular entity trip data presented as array of <b>FormattedData</b> object (trip point).<br/>  
7 - Each object represents basic entity properties (ex. <code>entityId</code>, <code>entityName</code>)<br/>  
8 - and provides access to other entity attributes/timeseries declared in widget datasource configuration.  
9 - </li>  
10 - <li><b>dsIndex</b> <code>number</code> - index of the current trip data in <code>dsData</code> array.  
11 - </li>  
1 -#### Trip animation widget label function  
2 -  
3 -<div class="divider"></div>  
4 -<br/>  
5 -  
6 -*function (data, dsData, dsIndex): string*  
7 -  
8 -A JavaScript function used to compute text or HTML code of the marker label.  
9 -  
10 -**Parameters:**  
11 -  
12 -<ul>  
13 - {% include widget/lib/map/trip_fn_args %}  
14 -</ul>  
15 -  
16 -**Returns:**  
17 -  
18 -Should return string value presenting text or HTML of the marker label.  
19 -  
20 -<div class="divider"></div>  
21 -  
22 -##### Examples  
23 -  
24 -* Display styled label with corresponding latest telemetry data for `energy meter` or `thermometer` device types:  
25 -  
26 -```javascript  
27 -var deviceType = data['Type'];  
28 -if (typeof deviceType !== undefined) {  
29 - if (deviceType == "energy meter") {  
30 - return '<span style="color:orange;">${entityName}, ${energy:2} kWt</span>';  
31 - } else if (deviceType == "thermometer") {  
32 - return '<span style="color:blue;">${entityName}, ${temperature:2} °C</span>';  
33 - }  
34 -}  
35 -return data.entityName;  
36 -{:copy-code}  
37 -```  
38 -  
39 -<br>  
40 -<br>  
1 -#### Marker image function  
2 -  
3 -<div class="divider"></div>  
4 -<br/>  
5 -  
6 -*function (data, images, dsData, dsIndex): {url: string, size: number}*  
7 -  
8 -A JavaScript function used to compute marker image.  
9 -  
10 -**Parameters:**  
11 -  
12 -<ul>  
13 - {% include widget/lib/map/trip_fn_args %}  
14 -</ul>  
15 -  
16 -**Returns:**  
17 -  
18 -Should return marker image data having the following structure:  
19 -  
20 -```typescript  
21 -{  
22 - url: string,  
23 - size: number  
24 -}  
25 -```  
26 -  
27 -- *url* - marker image url;  
28 -- *size* - marker image size;  
29 -  
30 -In case no data is returned, default marker image will be used.  
31 -  
32 -<div class="divider"></div>  
33 -  
34 -##### Examples  
35 -  
36 -<ul>  
37 -<li>  
38 -Calculate image url depending on <code>temperature</code> telemetry value for <code>thermometer</code> device type.<br>  
39 -Let's assume 4 images are defined in <b>Marker images</b> section. Each image corresponds to particular temperature level:  
40 -</li>  
41 -</ul>  
42 -  
43 -```javascript  
44 -var type = data['Type'];  
45 -if (type == 'thermometer') {  
46 - var res = {  
47 - url: images[0],  
48 - size: 40  
49 - }  
50 - var temperature = data['temperature'];  
51 - if (typeof temperature !== undefined) {  
52 - var percent = (temperature + 60)/120;  
53 - var index = Math.min(3, Math.floor(4 * percent));  
54 - res.url = images[index];  
55 - }  
56 - return res;  
57 -}  
58 -{:copy-code}  
59 -```  
60 -  
61 -<br>  
62 -<br>  
@@ -10,7 +10,7 @@ A JavaScript function evaluating whether to use trip point as time anchor used i @@ -10,7 +10,7 @@ A JavaScript function evaluating whether to use trip point as time anchor used i
10 **Parameters:** 10 **Parameters:**
11 11
12 <ul> 12 <ul>
13 - {% include widget/lib/map/trip_fn_args %} 13 + {% include widget/lib/map/map_fn_args %}
14 </ul> 14 </ul>
15 15
16 **Returns:** 16 **Returns:**
1 -#### Trip animation widget tooltip function  
2 -  
3 -<div class="divider"></div>  
4 -<br/>  
5 -  
6 -*function (data, dsData, dsIndex): string*  
7 -  
8 -A JavaScript function used to compute text or HTML code to be displayed in the marker tooltip.  
9 -  
10 -**Parameters:**  
11 -  
12 -<ul>  
13 - {% include widget/lib/map/trip_fn_args %}  
14 -</ul>  
15 -  
16 -**Returns:**  
17 -  
18 -Should return string value presenting text or HTML for the marker tooltip.  
19 -  
20 -<div class="divider"></div>  
21 -  
22 -##### Examples  
23 -  
24 -* Display details with corresponding telemetry data for `thermostat` device type:  
25 -  
26 -```javascript  
27 -var deviceType = data['Type'];  
28 -if (typeof deviceType !== undefined) {  
29 - if (deviceType == "energy meter") {  
30 - return '<b>${entityName}</b><br/><b>Energy:</b> ${energy:2} kWt<br/>';  
31 - } else if (deviceType == "thermometer") {  
32 - return '<b>${entityName}</b><br/><b>Temperature:</b> ${temperature:2} °C<br/>';  
33 - }  
34 -}  
35 -return data.entityName;  
36 -{:copy-code}  
37 -```  
38 -  
39 -<br>  
40 -<br>