Commit 416d0458933548db86aed4cb5dcd2efbea13d54f
Committed by
GitHub
1 parent
7a87e6c1
Map/3.0 (#2738)
* WIP on trip-animation settings * trip-animation points & anchors * fixes Co-authored-by: Adsumus <artemtv42@gmail.com> Co-authored-by: Igor Kulikov <ikulikov@thingsboard.io>
Showing
11 changed files
with
439 additions
and
396 deletions
@@ -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 | ||
@@ -47,6 +47,8 @@ export default abstract class LeafletMap { | @@ -47,6 +47,8 @@ export default abstract class LeafletMap { | ||
47 | bounds: L.LatLngBounds; | 47 | bounds: L.LatLngBounds; |
48 | datasources: FormattedData[]; | 48 | datasources: FormattedData[]; |
49 | markersCluster; | 49 | markersCluster; |
50 | + points: FeatureGroup; | ||
51 | + markersData = []; | ||
50 | 52 | ||
51 | protected constructor(public $container: HTMLElement, options: UnitedMapSettings) { | 53 | protected constructor(public $container: HTMLElement, options: UnitedMapSettings) { |
52 | this.options = options; | 54 | this.options = options; |
@@ -157,9 +159,9 @@ export default abstract class LeafletMap { | @@ -157,9 +159,9 @@ export default abstract class LeafletMap { | ||
157 | this.map = map; | 159 | this.map = map; |
158 | if (this.options.useDefaultCenterPosition) { | 160 | if (this.options.useDefaultCenterPosition) { |
159 | this.map.panTo(this.options.defaultCenterPosition); | 161 | this.map.panTo(this.options.defaultCenterPosition); |
160 | - this.bounds = map.getBounds(); | 162 | + this.bounds = map.getBounds(); |
161 | } | 163 | } |
162 | - else this.bounds = new L.LatLngBounds(null, null); | 164 | + else this.bounds = new L.LatLngBounds(null, null); |
163 | if (this.options.draggableMarker) { | 165 | if (this.options.draggableMarker) { |
164 | this.addMarkerControl(); | 166 | this.addMarkerControl(); |
165 | } | 167 | } |
@@ -200,9 +202,9 @@ export default abstract class LeafletMap { | @@ -200,9 +202,9 @@ export default abstract class LeafletMap { | ||
200 | return this.map.getCenter(); | 202 | return this.map.getCenter(); |
201 | } | 203 | } |
202 | 204 | ||
203 | - fitBounds(bounds: LatLngBounds, useDefaultZoom = false, padding?: LatLngTuple) { | 205 | + fitBounds(bounds: LatLngBounds, padding?: LatLngTuple) { |
204 | if (bounds.isValid()) { | 206 | if (bounds.isValid()) { |
205 | - this.bounds = this.bounds.extend(bounds); | 207 | + this.bounds = !!this.bounds ? this.bounds.extend(bounds) : bounds; |
206 | if (!this.options.fitMapBounds && this.options.defaultZoomLevel) { | 208 | if (!this.options.fitMapBounds && this.options.defaultZoomLevel) { |
207 | this.map.setZoom(this.options.defaultZoomLevel, { animate: false }); | 209 | this.map.setZoom(this.options.defaultZoomLevel, { animate: false }); |
208 | if (this.options.useDefaultCenterPosition) { | 210 | if (this.options.useDefaultCenterPosition) { |
@@ -218,9 +220,9 @@ export default abstract class LeafletMap { | @@ -218,9 +220,9 @@ export default abstract class LeafletMap { | ||
218 | } | 220 | } |
219 | }); | 221 | }); |
220 | if (this.options.useDefaultCenterPosition) { | 222 | if (this.options.useDefaultCenterPosition) { |
221 | - bounds = bounds.extend(this.options.defaultCenterPosition); | 223 | + this.bounds = this.bounds.extend(this.options.defaultCenterPosition); |
222 | } | 224 | } |
223 | - this.map.fitBounds(bounds, { padding: padding || [50, 50], animate: false }); | 225 | + this.map.fitBounds(this.bounds, { padding: padding || [50, 50], animate: false }); |
224 | } | 226 | } |
225 | } | 227 | } |
226 | } | 228 | } |
@@ -252,11 +254,10 @@ export default abstract class LeafletMap { | @@ -252,11 +254,10 @@ export default abstract class LeafletMap { | ||
252 | const style = currentImage ? 'background-image: url(' + currentImage.url + ');' : ''; | 254 | const style = currentImage ? 'background-image: url(' + currentImage.url + ');' : ''; |
253 | this.options.icon = L.divIcon({ | 255 | this.options.icon = L.divIcon({ |
254 | html: `<div class="arrow" | 256 | html: `<div class="arrow" |
255 | - style="transform: translate(-10px, -10px); | ||
256 | - ${style} | ||
257 | - rotate(${data.rotationAngle}deg); | ||
258 | - "><div>` | ||
259 | - }) | 257 | + style="transform: translate(-10px, -10px) |
258 | + rotate(${data.rotationAngle}deg); | ||
259 | + ${style}"><div>` | ||
260 | + }); | ||
260 | } | 261 | } |
261 | else { | 262 | else { |
262 | this.options.icon = null; | 263 | this.options.icon = null; |
@@ -268,6 +269,7 @@ export default abstract class LeafletMap { | @@ -268,6 +269,7 @@ export default abstract class LeafletMap { | ||
268 | this.createMarker(data.entityName, data, markersData, this.options as MarkerSettings); | 269 | this.createMarker(data.entityName, data, markersData, this.options as MarkerSettings); |
269 | } | 270 | } |
270 | }); | 271 | }); |
272 | + this.markersData = markersData; | ||
271 | } | 273 | } |
272 | 274 | ||
273 | dragMarker = (e, data?) => { | 275 | dragMarker = (e, data?) => { |
@@ -278,7 +280,8 @@ export default abstract class LeafletMap { | @@ -278,7 +280,8 @@ export default abstract class LeafletMap { | ||
278 | private createMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings) { | 280 | private createMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings) { |
279 | this.ready$.subscribe(() => { | 281 | this.ready$.subscribe(() => { |
280 | const newMarker = new Marker(this.convertPosition(data), settings, data, dataSources, this.dragMarker); | 282 | const newMarker = new Marker(this.convertPosition(data), settings, data, dataSources, this.dragMarker); |
281 | - this.fitBounds(this.bounds.extend(newMarker.leafletMarker.getLatLng()), settings.draggableMarker && this.markers.size < 2); | 283 | + if (this.bounds) |
284 | + this.fitBounds(this.bounds.extend(newMarker.leafletMarker.getLatLng())); | ||
282 | this.markers.set(key, newMarker); | 285 | this.markers.set(key, newMarker); |
283 | if (this.options.useClusterMarkers) { | 286 | if (this.options.useClusterMarkers) { |
284 | this.markersCluster.addLayer(newMarker.leafletMarker); | 287 | this.markersCluster.addLayer(newMarker.leafletMarker); |
@@ -313,6 +316,29 @@ export default abstract class LeafletMap { | @@ -313,6 +316,29 @@ export default abstract class LeafletMap { | ||
313 | } | 316 | } |
314 | } | 317 | } |
315 | 318 | ||
319 | + updatePoints(pointsData: FormattedData[], getTooltip: (point: FormattedData, setTooltip?: boolean) => string) { | ||
320 | + this.map$.subscribe(map => { | ||
321 | + if (this.points) { | ||
322 | + map.removeLayer(this.points); | ||
323 | + } | ||
324 | + this.points = new FeatureGroup(); | ||
325 | + pointsData.filter(pdata => !!this.convertPosition(pdata)).forEach(data => { | ||
326 | + const point = L.circleMarker(this.convertPosition(data), { | ||
327 | + color: this.options.pointColor, | ||
328 | + radius: this.options.pointSize | ||
329 | + }); | ||
330 | + if (!this.options.pointTooltipOnRightPanel) { | ||
331 | + point.on('click', () => getTooltip(data)); | ||
332 | + } | ||
333 | + else { | ||
334 | + createTooltip(point, this.options, pointsData, getTooltip(data, false)); | ||
335 | + } | ||
336 | + this.points.addLayer(point); | ||
337 | + }); | ||
338 | + map.addLayer(this.points); | ||
339 | + }); | ||
340 | + } | ||
341 | + | ||
316 | setImageAlias(alias: Observable<any>) { | 342 | setImageAlias(alias: Observable<any>) { |
317 | } | 343 | } |
318 | 344 | ||
@@ -337,15 +363,17 @@ export default abstract class LeafletMap { | @@ -337,15 +363,17 @@ export default abstract class LeafletMap { | ||
337 | this.ready$.subscribe(() => { | 363 | this.ready$.subscribe(() => { |
338 | const poly = new Polyline(this.map, | 364 | const poly = new Polyline(this.map, |
339 | data.map(el => this.convertPosition(el)).filter(el => !!el), data, dataSources, settings); | 365 | data.map(el => this.convertPosition(el)).filter(el => !!el), data, dataSources, settings); |
340 | - const bounds = this.bounds.extend(poly.leafletPoly.getBounds()); | ||
341 | - this.fitBounds(bounds) | ||
342 | - this.polylines.set(data[0].entityName, poly) | 366 | + const bounds = poly.leafletPoly.getBounds(); |
367 | + this.fitBounds(bounds); | ||
368 | + this.polylines.set(data[0].entityName, poly); | ||
343 | }); | 369 | }); |
344 | } | 370 | } |
345 | 371 | ||
346 | updatePolyline(key: string, data: FormattedData[], dataSources: FormattedData[], settings: PolylineSettings) { | 372 | updatePolyline(key: string, data: FormattedData[], dataSources: FormattedData[], settings: PolylineSettings) { |
347 | this.ready$.subscribe(() => { | 373 | this.ready$.subscribe(() => { |
348 | - this.polylines.get(key).updatePolyline(settings, data.map(el => this.convertPosition(el)), dataSources); | 374 | + const poly = this.polylines.get(key); |
375 | + poly.updatePolyline(settings, data.map(el => this.convertPosition(el)), dataSources); | ||
376 | + const bounds = poly.leafletPoly.getBounds(); | ||
349 | }); | 377 | }); |
350 | } | 378 | } |
351 | 379 | ||
@@ -370,7 +398,7 @@ export default abstract class LeafletMap { | @@ -370,7 +398,7 @@ export default abstract class LeafletMap { | ||
370 | createPolygon(polyData: DatasourceData, dataSources: DatasourceData[], settings: PolygonSettings) { | 398 | createPolygon(polyData: DatasourceData, dataSources: DatasourceData[], settings: PolygonSettings) { |
371 | this.ready$.subscribe(() => { | 399 | this.ready$.subscribe(() => { |
372 | const polygon = new Polygon(this.map, polyData, dataSources, settings); | 400 | const polygon = new Polygon(this.map, polyData, dataSources, settings); |
373 | - const bounds = this.bounds.extend(polygon.leafletPoly.getBounds()); | 401 | + const bounds = polygon.leafletPoly.getBounds(); |
374 | this.fitBounds(bounds); | 402 | this.fitBounds(bounds); |
375 | this.polygons.set(polyData.datasource.entityName, polygon); | 403 | this.polygons.set(polyData.datasource.entityName, polygon); |
376 | }); | 404 | }); |
@@ -380,7 +408,6 @@ export default abstract class LeafletMap { | @@ -380,7 +408,6 @@ export default abstract class LeafletMap { | ||
380 | this.ready$.subscribe(() => { | 408 | this.ready$.subscribe(() => { |
381 | const poly = this.polygons.get(polyData.datasource.entityName); | 409 | const poly = this.polygons.get(polyData.datasource.entityName); |
382 | poly.updatePolygon(polyData.data, dataSources, settings); | 410 | poly.updatePolygon(polyData.data, dataSources, settings); |
383 | - this.fitBounds(poly.leafletPoly.getBounds()); | ||
384 | }); | 411 | }); |
385 | } | 412 | } |
386 | } | 413 | } |
@@ -15,13 +15,19 @@ | @@ -15,13 +15,19 @@ | ||
15 | /// | 15 | /// |
16 | 16 | ||
17 | import { LatLngTuple } from 'leaflet'; | 17 | import { LatLngTuple } from 'leaflet'; |
18 | -import { Datasource } from '@app/shared/models/widget.models'; | 18 | +import { Datasource, JsonSettingsSchema } from '@app/shared/models/widget.models'; |
19 | +import { Type } from '@angular/core'; | ||
20 | +import LeafletMap from './leaflet-map'; | ||
21 | +import { OpenStreetMap, TencentMap, GoogleMap, HEREMap, ImageMap } from './providers'; | ||
22 | +import { | ||
23 | + openstreetMapSettingsSchema, tencentMapSettingsSchema, | ||
24 | + googleMapSettingsSchema, hereMapSettingsSchema, imageMapSettingsSchema | ||
25 | +} from './schemes'; | ||
19 | 26 | ||
20 | export type GenericFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => string; | 27 | export type GenericFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => string; |
21 | export type MarkerImageFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => string; | 28 | export type MarkerImageFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => string; |
22 | 29 | ||
23 | export type MapSettings = { | 30 | export type MapSettings = { |
24 | - polygonKeyName: any; | ||
25 | draggableMarker: boolean; | 31 | draggableMarker: boolean; |
26 | initCallback?: () => any; | 32 | initCallback?: () => any; |
27 | posFunction: (origXPos, origYPos) => { x, y }; | 33 | posFunction: (origXPos, origYPos) => { x, y }; |
@@ -108,7 +114,8 @@ export interface FormattedData { | @@ -108,7 +114,8 @@ export interface FormattedData { | ||
108 | 114 | ||
109 | export type PolygonSettings = { | 115 | export type PolygonSettings = { |
110 | showPolygon: boolean; | 116 | showPolygon: boolean; |
111 | - showTooltip: any; | 117 | + polygonKeyName: string; |
118 | + polKeyName: string;// deprecated | ||
112 | polygonStrokeOpacity: number; | 119 | polygonStrokeOpacity: number; |
113 | polygonOpacity: number; | 120 | polygonOpacity: number; |
114 | polygonStrokeWeight: number; | 121 | polygonStrokeWeight: number; |
@@ -116,12 +123,13 @@ export type PolygonSettings = { | @@ -116,12 +123,13 @@ export type PolygonSettings = { | ||
116 | polygonColor: string; | 123 | polygonColor: string; |
117 | showPolygonTooltip: boolean; | 124 | showPolygonTooltip: boolean; |
118 | autocloseTooltip: boolean; | 125 | autocloseTooltip: boolean; |
119 | - tooltipFunction: GenericFunction; | ||
120 | showTooltipAction: string; | 126 | showTooltipAction: string; |
121 | tooltipAction: { [name: string]: actionsHandler }; | 127 | tooltipAction: { [name: string]: actionsHandler }; |
122 | - tooltipPattern: string; | ||
123 | - useTooltipFunction: boolean; | 128 | + polygonTooltipPattern: string; |
129 | + usePolygonTooltipFunction: boolean; | ||
124 | polygonClick: { [name: string]: actionsHandler }; | 130 | polygonClick: { [name: string]: actionsHandler }; |
131 | + usePolygonColorFunction: boolean; | ||
132 | + polygonTooltipFunction: GenericFunction; | ||
125 | polygonColorFunction?: GenericFunction; | 133 | polygonColorFunction?: GenericFunction; |
126 | } | 134 | } |
127 | 135 | ||
@@ -154,6 +162,88 @@ export interface HistorySelectSettings { | @@ -154,6 +162,88 @@ export interface HistorySelectSettings { | ||
154 | buttonColor: string; | 162 | buttonColor: string; |
155 | } | 163 | } |
156 | 164 | ||
165 | +export type TripAnimationSttings = { | ||
166 | + pointColor: string; | ||
167 | + pointSize: number; | ||
168 | + pointTooltipOnRightPanel: boolean; | ||
169 | +} | ||
170 | + | ||
157 | export type actionsHandler = ($event: Event, datasource: Datasource) => void; | 171 | export type actionsHandler = ($event: Event, datasource: Datasource) => void; |
158 | 172 | ||
159 | -export type UnitedMapSettings = MapSettings & PolygonSettings & MarkerSettings & PolylineSettings; | 173 | +export type UnitedMapSettings = MapSettings & PolygonSettings & MarkerSettings & PolylineSettings & TripAnimationSttings; |
174 | + | ||
175 | +interface IProvider { | ||
176 | + MapClass: Type<LeafletMap>, | ||
177 | + schema: JsonSettingsSchema, | ||
178 | + name: string | ||
179 | +} | ||
180 | + | ||
181 | +export const providerSets: { [key: string]: IProvider } = { | ||
182 | + 'openstreet-map': { | ||
183 | + MapClass: OpenStreetMap, | ||
184 | + schema: openstreetMapSettingsSchema, | ||
185 | + name: 'openstreet-map', | ||
186 | + }, | ||
187 | + 'tencent-map': { | ||
188 | + MapClass: TencentMap, | ||
189 | + schema: tencentMapSettingsSchema, | ||
190 | + name: 'tencent-map' | ||
191 | + }, | ||
192 | + 'google-map': { | ||
193 | + MapClass: GoogleMap, | ||
194 | + schema: googleMapSettingsSchema, | ||
195 | + name: 'google-map' | ||
196 | + }, | ||
197 | + here: { | ||
198 | + MapClass: HEREMap, | ||
199 | + schema: hereMapSettingsSchema, | ||
200 | + name: 'here' | ||
201 | + }, | ||
202 | + 'image-map': { | ||
203 | + MapClass: ImageMap, | ||
204 | + schema: imageMapSettingsSchema, | ||
205 | + name: 'image-map' | ||
206 | + } | ||
207 | +}; | ||
208 | + | ||
209 | +export const defaultSettings: any = { | ||
210 | + xPosKeyName: 'xPos', | ||
211 | + yPosKeyName: 'yPos', | ||
212 | + markerOffsetX: 0.5, | ||
213 | + markerOffsetY: 1, | ||
214 | + latKeyName: 'latitude', | ||
215 | + lngKeyName: 'longitude', | ||
216 | + polygonKeyName: 'coordinates', | ||
217 | + showLabel: false, | ||
218 | + label: '${entityName}', | ||
219 | + showTooltip: false, | ||
220 | + useDefaultCenterPosition: false, | ||
221 | + showTooltipAction: 'click', | ||
222 | + autocloseTooltip: false, | ||
223 | + showPolygon: false, | ||
224 | + labelColor: '#000000', | ||
225 | + color: '#FE7569', | ||
226 | + polygonColor: '#0000ff', | ||
227 | + polygonStrokeColor: '#fe0001', | ||
228 | + polygonOpacity: 0.5, | ||
229 | + polygonStrokeOpacity: 1, | ||
230 | + polygonStrokeWeight: 1, | ||
231 | + useLabelFunction: false, | ||
232 | + markerImages: [], | ||
233 | + strokeWeight: 2, | ||
234 | + strokeOpacity: 1.0, | ||
235 | + initCallback: () => { }, | ||
236 | + defaultZoomLevel: 8, | ||
237 | + disableScrollZooming: false, | ||
238 | + minZoomLevel: 16, | ||
239 | + credentials: '', | ||
240 | + markerClusteringSetting: null, | ||
241 | + draggableMarker: false, | ||
242 | + fitMapBounds: true | ||
243 | +}; | ||
244 | + | ||
245 | +export const hereProviders = [ | ||
246 | + 'HERE.normalDay', | ||
247 | + 'HERE.normalNight', | ||
248 | + 'HERE.hybridDay', | ||
249 | + 'HERE.terrainDay'] |
@@ -26,7 +26,7 @@ export interface MapWidgetInterface { | @@ -26,7 +26,7 @@ export interface MapWidgetInterface { | ||
26 | 26 | ||
27 | export interface MapWidgetStaticInterface { | 27 | export interface MapWidgetStaticInterface { |
28 | settingsSchema(mapProvider?: MapProviders, drawRoutes?: boolean): JsonSettingsSchema; | 28 | settingsSchema(mapProvider?: MapProviders, drawRoutes?: boolean): JsonSettingsSchema; |
29 | - getProvidersSchema(mapProvider?: MapProviders): JsonSettingsSchema | 29 | + getProvidersSchema(mapProvider?: MapProviders, ignoreImageMap?: boolean): JsonSettingsSchema |
30 | dataKeySettingsSchema(): object; | 30 | dataKeySettingsSchema(): object; |
31 | actionSources(): object; | 31 | actionSources(): object; |
32 | } | 32 | } |
@@ -14,23 +14,17 @@ | @@ -14,23 +14,17 @@ | ||
14 | /// limitations under the License. | 14 | /// limitations under the License. |
15 | /// | 15 | /// |
16 | 16 | ||
17 | -import { MapProviders, UnitedMapSettings } from './map-models'; | 17 | +import { MapProviders, UnitedMapSettings, providerSets, hereProviders, defaultSettings } from './map-models'; |
18 | import LeafletMap from './leaflet-map'; | 18 | import LeafletMap from './leaflet-map'; |
19 | import { | 19 | import { |
20 | - openstreetMapSettingsSchema, | ||
21 | - googleMapSettingsSchema, | ||
22 | - imageMapSettingsSchema, | ||
23 | - tencentMapSettingsSchema, | ||
24 | commonMapSettingsSchema, | 20 | commonMapSettingsSchema, |
25 | routeMapSettingsSchema, | 21 | routeMapSettingsSchema, |
26 | markerClusteringSettingsSchema, | 22 | markerClusteringSettingsSchema, |
27 | markerClusteringSettingsSchemaLeaflet, | 23 | markerClusteringSettingsSchemaLeaflet, |
28 | - hereMapSettingsSchema, | ||
29 | mapProviderSchema, | 24 | mapProviderSchema, |
30 | mapPolygonSchema | 25 | mapPolygonSchema |
31 | } from './schemes'; | 26 | } from './schemes'; |
32 | import { MapWidgetStaticInterface, MapWidgetInterface } from './map-widget.interface'; | 27 | import { MapWidgetStaticInterface, MapWidgetInterface } from './map-widget.interface'; |
33 | -import { OpenStreetMap, TencentMap, GoogleMap, HEREMap, ImageMap } from './providers'; | ||
34 | import { initSchema, addToSchema, mergeSchemes, addCondition, addGroupInfo } from '@core/schema-utils'; | 28 | import { initSchema, addToSchema, mergeSchemes, addCondition, addGroupInfo } from '@core/schema-utils'; |
35 | import { of, Subject } from 'rxjs'; | 29 | import { of, Subject } from 'rxjs'; |
36 | import { WidgetContext } from '@app/modules/home/models/widget-component.models'; | 30 | import { WidgetContext } from '@app/modules/home/models/widget-component.models'; |
@@ -39,7 +33,6 @@ import { JsonSettingsSchema, WidgetActionDescriptor, DatasourceType, widgetType, | @@ -39,7 +33,6 @@ import { JsonSettingsSchema, WidgetActionDescriptor, DatasourceType, widgetType, | ||
39 | import { EntityId } from '@shared/models/id/entity-id'; | 33 | import { EntityId } from '@shared/models/id/entity-id'; |
40 | import { AttributeScope, DataKeyType, LatestTelemetry } from '@shared/models/telemetry/telemetry.models'; | 34 | import { AttributeScope, DataKeyType, LatestTelemetry } from '@shared/models/telemetry/telemetry.models'; |
41 | import { AttributeService } from '@core/http/attribute.service'; | 35 | import { AttributeService } from '@core/http/attribute.service'; |
42 | -import { Type } from '@angular/core'; | ||
43 | import { TranslateService } from '@ngx-translate/core'; | 36 | import { TranslateService } from '@ngx-translate/core'; |
44 | import { UtilsService } from '@core/services/utils.service'; | 37 | import { UtilsService } from '@core/services/utils.service'; |
45 | 38 | ||
@@ -85,11 +78,19 @@ export class MapWidgetController implements MapWidgetInterface { | @@ -85,11 +78,19 @@ export class MapWidgetController implements MapWidgetInterface { | ||
85 | return {}; | 78 | return {}; |
86 | } | 79 | } |
87 | 80 | ||
88 | - public static getProvidersSchema(mapProvider: MapProviders) { | ||
89 | - mapProviderSchema.schema.properties.provider.default = mapProvider; | ||
90 | - return mergeSchemes([mapProviderSchema, | 81 | + public static getProvidersSchema(mapProvider: MapProviders, ignoreImageMap = false) { |
82 | + if (mapProvider) | ||
83 | + mapProviderSchema.schema.properties.provider.default = mapProvider; | ||
84 | + const providerSchema = mapProviderSchema; | ||
85 | + if (ignoreImageMap) { | ||
86 | + providerSchema.form[0].items = providerSchema.form[0]?.items.filter(item => item.value !== 'image-map'); | ||
87 | + } | ||
88 | + return mergeSchemes([providerSchema, | ||
91 | ...Object.keys(providerSets)?.map( | 89 | ...Object.keys(providerSets)?.map( |
92 | - (key: string) => { const setting = providerSets[key]; return addCondition(setting?.schema, `model.provider === '${setting.name}'`) })]); | 90 | + (key: string) => { |
91 | + const setting = providerSets[key]; | ||
92 | + return addCondition(setting?.schema, `model.provider === '${setting.name}'`); | ||
93 | + })]); | ||
93 | } | 94 | } |
94 | 95 | ||
95 | public static settingsSchema(mapProvider: MapProviders, drawRoutes: boolean): JsonSettingsSchema { | 96 | public static settingsSchema(mapProvider: MapProviders, drawRoutes: boolean): JsonSettingsSchema { |
@@ -218,6 +219,7 @@ export class MapWidgetController implements MapWidgetInterface { | @@ -218,6 +219,7 @@ export class MapWidgetController implements MapWidgetInterface { | ||
218 | polygonColorFunction: parseFunction(settings.polygonColorFunction, functionParams), | 219 | polygonColorFunction: parseFunction(settings.polygonColorFunction, functionParams), |
219 | markerImageFunction: parseFunction(settings.markerImageFunction, ['data', 'images', 'dsData', 'dsIndex']), | 220 | markerImageFunction: parseFunction(settings.markerImageFunction, ['data', 'images', 'dsData', 'dsIndex']), |
220 | labelColor: this.ctx.widgetConfig.color, | 221 | labelColor: this.ctx.widgetConfig.color, |
222 | + polygonKeyName: settings.polKeyName ? settings.polKeyName : settings.polygonKeyName, | ||
221 | tooltipPattern: settings.tooltipPattern || | 223 | tooltipPattern: settings.tooltipPattern || |
222 | '<b>${entityName}</b><br/><br/><b>Latitude:</b> ${' + | 224 | '<b>${entityName}</b><br/><br/><b>Latitude:</b> ${' + |
223 | settings.latKeyName + ':7}<br/><b>Longitude:</b> ${' + settings.lngKeyName + ':7}', | 225 | settings.latKeyName + ':7}<br/><b>Longitude:</b> ${' + settings.lngKeyName + ':7}', |
@@ -295,78 +297,4 @@ export class MapWidgetController implements MapWidgetInterface { | @@ -295,78 +297,4 @@ export class MapWidgetController implements MapWidgetInterface { | ||
295 | 297 | ||
296 | export let TbMapWidgetV2: MapWidgetStaticInterface = MapWidgetController; | 298 | export let TbMapWidgetV2: MapWidgetStaticInterface = MapWidgetController; |
297 | 299 | ||
298 | -interface IProvider { | ||
299 | - MapClass: Type<LeafletMap>, | ||
300 | - schema: JsonSettingsSchema, | ||
301 | - name: string | ||
302 | -} | ||
303 | - | ||
304 | -export const providerSets: { [key: string]: IProvider } = { | ||
305 | - 'openstreet-map': { | ||
306 | - MapClass: OpenStreetMap, | ||
307 | - schema: openstreetMapSettingsSchema, | ||
308 | - name: 'openstreet-map', | ||
309 | - }, | ||
310 | - 'tencent-map': { | ||
311 | - MapClass: TencentMap, | ||
312 | - schema: tencentMapSettingsSchema, | ||
313 | - name: 'tencent-map' | ||
314 | - }, | ||
315 | - 'google-map': { | ||
316 | - MapClass: GoogleMap, | ||
317 | - schema: googleMapSettingsSchema, | ||
318 | - name: 'google-map' | ||
319 | - }, | ||
320 | - here: { | ||
321 | - MapClass: HEREMap, | ||
322 | - schema: hereMapSettingsSchema, | ||
323 | - name: 'here' | ||
324 | - }, | ||
325 | - 'image-map': { | ||
326 | - MapClass: ImageMap, | ||
327 | - schema: imageMapSettingsSchema, | ||
328 | - name: 'image-map' | ||
329 | - } | ||
330 | -}; | ||
331 | - | ||
332 | -export const defaultSettings: any = { | ||
333 | - xPosKeyName: 'xPos', | ||
334 | - yPosKeyName: 'yPos', | ||
335 | - markerOffsetX: 0.5, | ||
336 | - markerOffsetY: 1, | ||
337 | - latKeyName: 'latitude', | ||
338 | - lngKeyName: 'longitude', | ||
339 | - polygonKeyName: 'coordinates', | ||
340 | - showLabel: false, | ||
341 | - label: '${entityName}', | ||
342 | - showTooltip: false, | ||
343 | - useDefaultCenterPosition: false, | ||
344 | - showTooltipAction: 'click', | ||
345 | - autocloseTooltip: false, | ||
346 | - showPolygon: false, | ||
347 | - labelColor: '#000000', | ||
348 | - color: '#FE7569', | ||
349 | - polygonColor: '#0000ff', | ||
350 | - polygonStrokeColor: '#fe0001', | ||
351 | - polygonOpacity: 0.5, | ||
352 | - polygonStrokeOpacity: 1, | ||
353 | - polygonStrokeWeight: 1, | ||
354 | - useLabelFunction: false, | ||
355 | - markerImages: [], | ||
356 | - strokeWeight: 2, | ||
357 | - strokeOpacity: 1.0, | ||
358 | - initCallback: () => { }, | ||
359 | - defaultZoomLevel: 8, | ||
360 | - disableScrollZooming: false, | ||
361 | - minZoomLevel: 16, | ||
362 | - credentials: '', | ||
363 | - markerClusteringSetting: null, | ||
364 | - draggableMarker: false, | ||
365 | - fitMapBounds: true | ||
366 | -}; | ||
367 | 300 | ||
368 | -export const hereProviders = [ | ||
369 | - 'HERE.normalDay', | ||
370 | - 'HERE.normalNight', | ||
371 | - 'HERE.hybridDay', | ||
372 | - 'HERE.terrainDay'] |
@@ -120,7 +120,6 @@ export class Marker { | @@ -120,7 +120,6 @@ export class Marker { | ||
120 | [this.data, this.settings.markerImages, this.dataSources, this.data.dsIndex]) : this.settings.currentImage; | 120 | [this.data, this.settings.markerImages, this.dataSources, this.data.dsIndex]) : this.settings.currentImage; |
121 | const currentColor = tinycolor(this.settings.useColorFunction ? safeExecute(this.settings.colorFunction, | 121 | const currentColor = tinycolor(this.settings.useColorFunction ? safeExecute(this.settings.colorFunction, |
122 | [this.data, this.dataSources, this.data.dsIndex]) : this.settings.color).toHex(); | 122 | [this.data, this.dataSources, this.data.dsIndex]) : this.settings.color).toHex(); |
123 | - | ||
124 | if (currentImage && currentImage.url) { | 123 | if (currentImage && currentImage.url) { |
125 | aspectCache(currentImage.url).subscribe( | 124 | aspectCache(currentImage.url).subscribe( |
126 | (aspect) => { | 125 | (aspect) => { |
@@ -53,8 +53,9 @@ export class Polygon { | @@ -53,8 +53,9 @@ export class Polygon { | ||
53 | } | 53 | } |
54 | 54 | ||
55 | updateTooltip(data: DatasourceData) { | 55 | updateTooltip(data: DatasourceData) { |
56 | - const pattern = this.settings.useTooltipFunction ? | ||
57 | - safeExecute(this.settings.tooltipFunction, [this.data, this.dataSources, this.data.dsIndex]) : this.settings.tooltipPattern; | 56 | + const pattern = this.settings.usePolygonTooltipFunction ? |
57 | + safeExecute(this.settings.polygonTooltipFunction, [this.data, this.dataSources, this.data.dsIndex]) : | ||
58 | + this.settings.polygonTooltipPattern; | ||
58 | this.tooltip.setContent(parseWithTranslation.parseTemplate(pattern, data, true)); | 59 | this.tooltip.setContent(parseWithTranslation.parseTemplate(pattern, data, true)); |
59 | } | 60 | } |
60 | 61 | ||
@@ -71,10 +72,12 @@ export class Polygon { | @@ -71,10 +72,12 @@ export class Polygon { | ||
71 | this.map.removeLayer(this.leafletPoly); | 72 | this.map.removeLayer(this.leafletPoly); |
72 | } | 73 | } |
73 | 74 | ||
74 | - updatePolygonColor(settings) { | 75 | + updatePolygonColor(settings: PolygonSettings) { |
76 | + const color = settings.usePolygonColorFunction ? | ||
77 | + safeExecute(settings.polygonColorFunction, [this.data, this.dataSources, this.data.dsIndex]) : settings.polygonColor; | ||
75 | const style: L.PathOptions = { | 78 | const style: L.PathOptions = { |
76 | fill: true, | 79 | fill: true, |
77 | - fillColor: settings.polygonColor, | 80 | + fillColor: color, |
78 | color: settings.polygonStrokeColor, | 81 | color: settings.polygonStrokeColor, |
79 | weight: settings.polygonStrokeWeight, | 82 | weight: settings.polygonStrokeWeight, |
80 | fillOpacity: settings.polygonOpacity, | 83 | fillOpacity: settings.polygonOpacity, |
@@ -109,13 +109,13 @@ export class ImageMap extends LeafletMap { | @@ -109,13 +109,13 @@ export class ImageMap extends LeafletMap { | ||
109 | lastCenterPos.y /= prevHeight; | 109 | lastCenterPos.y /= prevHeight; |
110 | this.updateBounds(updateImage, lastCenterPos); | 110 | this.updateBounds(updateImage, lastCenterPos); |
111 | this.map.invalidateSize(true); | 111 | this.map.invalidateSize(true); |
112 | - // TODO: need add update marker position | 112 | + this.updateMarkers(this.markersData); |
113 | } | 113 | } |
114 | } | 114 | } |
115 | } | 115 | } |
116 | } | 116 | } |
117 | 117 | ||
118 | - fitBounds(bounds: LatLngBounds, useDefaultZoom = false, padding?: LatLngTuple) { } | 118 | + fitBounds(bounds: LatLngBounds, padding?: LatLngTuple) { } |
119 | 119 | ||
120 | initMap(updateImage?) { | 120 | initMap(updateImage?) { |
121 | if (!this.map && this.aspect > 0) { | 121 | if (!this.map && this.aspect > 0) { |
@@ -546,6 +546,20 @@ export const mapPolygonSchema = | @@ -546,6 +546,20 @@ export const mapPolygonSchema = | ||
546 | type: 'boolean', | 546 | type: 'boolean', |
547 | default: false | 547 | default: false |
548 | }, | 548 | }, |
549 | + polygonTooltipPattern: { | ||
550 | + title: 'Tooltip (for ex. \'Text ${keyName} units.\' or <link-act name=\'my-action\'>Link text</link-act>\')', | ||
551 | + type: 'string', | ||
552 | + default: '<b>${entityName}</b><br/><br/><b>TimeStamp:</b> ${ts:7}' | ||
553 | + }, | ||
554 | + usePolygonTooltipFunction: { | ||
555 | + title: 'Use polygon tooltip function', | ||
556 | + type: 'boolean', | ||
557 | + default: false | ||
558 | + }, | ||
559 | + polygonTooltipFunction: { | ||
560 | + title: 'Polygon tooltip function: f(data, dsData, dsIndex)', | ||
561 | + type: 'string' | ||
562 | + }, | ||
549 | usePolygonColorFunction: { | 563 | usePolygonColorFunction: { |
550 | title: 'Use polygon color function', | 564 | title: 'Use polygon color function', |
551 | type: 'boolean', | 565 | type: 'boolean', |
@@ -570,7 +584,15 @@ export const mapPolygonSchema = | @@ -570,7 +584,15 @@ export const mapPolygonSchema = | ||
570 | key: 'polygonStrokeColor', | 584 | key: 'polygonStrokeColor', |
571 | type: 'color' | 585 | type: 'color' |
572 | }, | 586 | }, |
573 | - 'polygonStrokeOpacity', 'polygonStrokeWeight', 'usePolygonColorFunction', 'showPolygonTooltip', | 587 | + 'polygonStrokeOpacity', 'polygonStrokeWeight', 'showPolygonTooltip', |
588 | + { | ||
589 | + key: 'polygonTooltipPattern', | ||
590 | + type: 'textarea' | ||
591 | + }, 'usePolygonTooltipFunction', { | ||
592 | + key: 'polygonTooltipFunction', | ||
593 | + type: 'javascript' | ||
594 | + }, | ||
595 | + 'usePolygonColorFunction', | ||
574 | { | 596 | { |
575 | key: 'polygonColorFunction', | 597 | key: 'polygonColorFunction', |
576 | type: 'javascript' | 598 | type: 'javascript' |
@@ -710,6 +732,161 @@ export const imageMapSettingsSchema = | @@ -710,6 +732,161 @@ export const imageMapSettingsSchema = | ||
710 | ] | 732 | ] |
711 | }; | 733 | }; |
712 | 734 | ||
735 | +export const pathSchema = | ||
736 | +{ | ||
737 | + schema: { | ||
738 | + title: 'Trip Animation Path Configuration', | ||
739 | + type: 'object', | ||
740 | + properties: { | ||
741 | + color: { | ||
742 | + title: 'Path color', | ||
743 | + type: 'string' | ||
744 | + }, | ||
745 | + strokeWeight: { | ||
746 | + title: 'Stroke weight', | ||
747 | + type: 'number', | ||
748 | + default: 2 | ||
749 | + }, | ||
750 | + strokeOpacity: { | ||
751 | + title: 'Stroke opacity', | ||
752 | + type: 'number', | ||
753 | + default: 1 | ||
754 | + }, | ||
755 | + useColorFunction: { | ||
756 | + title: 'Use path color function', | ||
757 | + type: 'boolean', | ||
758 | + default: false | ||
759 | + }, | ||
760 | + colorFunction: { | ||
761 | + title: 'Path color function: f(data, dsData, dsIndex)', | ||
762 | + type: 'string' | ||
763 | + }, | ||
764 | + usePolylineDecorator: { | ||
765 | + title: 'Use path decorator', | ||
766 | + type: 'boolean', | ||
767 | + default: false | ||
768 | + }, | ||
769 | + decoratorSymbol: { | ||
770 | + title: 'Decorator symbol', | ||
771 | + type: 'string', | ||
772 | + default: 'arrowHead' | ||
773 | + }, | ||
774 | + decoratorSymbolSize: { | ||
775 | + title: 'Decorator symbol size (px)', | ||
776 | + type: 'number', | ||
777 | + default: 10 | ||
778 | + }, | ||
779 | + useDecoratorCustomColor: { | ||
780 | + title: 'Use path decorator custom color', | ||
781 | + type: 'boolean', | ||
782 | + default: false | ||
783 | + }, | ||
784 | + decoratorCustomColor: { | ||
785 | + title: 'Decorator custom color', | ||
786 | + type: 'string', | ||
787 | + default: '#000' | ||
788 | + }, | ||
789 | + decoratorOffset: { | ||
790 | + title: 'Decorator offset', | ||
791 | + type: 'string', | ||
792 | + default: '20px' | ||
793 | + }, | ||
794 | + endDecoratorOffset: { | ||
795 | + title: 'End decorator offset', | ||
796 | + type: 'string', | ||
797 | + default: '20px' | ||
798 | + }, | ||
799 | + decoratorRepeat: { | ||
800 | + title: 'Decorator repeat', | ||
801 | + type: 'string', | ||
802 | + default: '20px' | ||
803 | + } | ||
804 | + }, | ||
805 | + required: [] | ||
806 | + }, | ||
807 | + form: [ | ||
808 | + { | ||
809 | + key: 'color', | ||
810 | + type: 'color' | ||
811 | + }, 'useColorFunction', { | ||
812 | + key: 'colorFunction', | ||
813 | + type: 'javascript' | ||
814 | + }, 'strokeWeight', 'strokeOpacity', | ||
815 | + 'usePolylineDecorator', { | ||
816 | + key: 'decoratorSymbol', | ||
817 | + type: 'rc-select', | ||
818 | + multiple: false, | ||
819 | + items: [{ | ||
820 | + value: 'arrowHead', | ||
821 | + label: 'Arrow' | ||
822 | + }, { | ||
823 | + value: 'dash', | ||
824 | + label: 'Dash' | ||
825 | + }] | ||
826 | + }, 'decoratorSymbolSize', 'useDecoratorCustomColor', { | ||
827 | + key: 'decoratorCustomColor', | ||
828 | + type: 'color' | ||
829 | + }, { | ||
830 | + key: 'decoratorOffset', | ||
831 | + type: 'textarea' | ||
832 | + }, { | ||
833 | + key: 'endDecoratorOffset', | ||
834 | + type: 'textarea' | ||
835 | + }, { | ||
836 | + key: 'decoratorRepeat', | ||
837 | + type: 'textarea' | ||
838 | + } | ||
839 | + ] | ||
840 | +}; | ||
841 | + | ||
842 | +export const pointSchema = | ||
843 | +{ | ||
844 | + schema: { | ||
845 | + title: 'Trip Animation Path Configuration', | ||
846 | + type: 'object', | ||
847 | + properties: { | ||
848 | + showPoints: { | ||
849 | + title: 'Show points', | ||
850 | + type: 'boolean', | ||
851 | + default: false | ||
852 | + }, | ||
853 | + pointColor: { | ||
854 | + title: 'Point color', | ||
855 | + type: 'string' | ||
856 | + }, | ||
857 | + pointSize: { | ||
858 | + title: 'Point size (px)', | ||
859 | + type: 'number', | ||
860 | + default: 10 | ||
861 | + }, | ||
862 | + usePointAsAnchor: { | ||
863 | + title: 'Use point as anchor', | ||
864 | + type: 'boolean', | ||
865 | + default: false | ||
866 | + }, | ||
867 | + pointAsAnchorFunction: { | ||
868 | + title: 'Point as anchor function: f(data, dsData, dsIndex)', | ||
869 | + type: 'string' | ||
870 | + }, | ||
871 | + pointTooltipOnRightPanel: { | ||
872 | + title: 'Independant point tooltip', | ||
873 | + type: 'boolean', | ||
874 | + default: true | ||
875 | + }, | ||
876 | + }, | ||
877 | + required: [] | ||
878 | + }, | ||
879 | + form: [ | ||
880 | + 'showPoints', { | ||
881 | + key: 'pointColor', | ||
882 | + type: 'color' | ||
883 | + }, 'pointSize', 'usePointAsAnchor', { | ||
884 | + key: 'pointAsAnchorFunction', | ||
885 | + type: 'javascript' | ||
886 | + }, 'pointTooltipOnRightPanel', | ||
887 | + ] | ||
888 | +}; | ||
889 | + | ||
713 | export const mapProviderSchema = | 890 | export const mapProviderSchema = |
714 | { | 891 | { |
715 | schema: { | 892 | schema: { |
@@ -755,7 +932,6 @@ export const mapProviderSchema = | @@ -755,7 +932,6 @@ export const mapProviderSchema = | ||
755 | ] | 932 | ] |
756 | }; | 933 | }; |
757 | 934 | ||
758 | - | ||
759 | export const tripAnimationSchema = { | 935 | export const tripAnimationSchema = { |
760 | schema: { | 936 | schema: { |
761 | title: 'Openstreet Map Configuration', | 937 | title: 'Openstreet Map Configuration', |
@@ -776,11 +952,6 @@ export const tripAnimationSchema = { | @@ -776,11 +952,6 @@ export const tripAnimationSchema = { | ||
776 | type: 'string', | 952 | type: 'string', |
777 | default: 'longitude' | 953 | default: 'longitude' |
778 | }, | 954 | }, |
779 | - polKeyName: { | ||
780 | - title: 'Polygon key name', | ||
781 | - type: 'string', | ||
782 | - default: 'coordinates' | ||
783 | - }, | ||
784 | showLabel: { | 955 | showLabel: { |
785 | title: 'Show label', | 956 | title: 'Show label', |
786 | type: 'boolean', | 957 | type: 'boolean', |
@@ -834,148 +1005,6 @@ export const tripAnimationSchema = { | @@ -834,148 +1005,6 @@ export const tripAnimationSchema = { | ||
834 | title: 'Tooltip function: f(data, dsData, dsIndex)', | 1005 | title: 'Tooltip function: f(data, dsData, dsIndex)', |
835 | type: 'string' | 1006 | type: 'string' |
836 | }, | 1007 | }, |
837 | - color: { | ||
838 | - title: 'Path color', | ||
839 | - type: 'string' | ||
840 | - }, | ||
841 | - strokeWeight: { | ||
842 | - title: 'Stroke weight', | ||
843 | - type: 'number', | ||
844 | - default: 2 | ||
845 | - }, | ||
846 | - strokeOpacity: { | ||
847 | - title: 'Stroke opacity', | ||
848 | - type: 'number', | ||
849 | - default: 1 | ||
850 | - }, | ||
851 | - useColorFunction: { | ||
852 | - title: 'Use path color function', | ||
853 | - type: 'boolean', | ||
854 | - default: false | ||
855 | - }, | ||
856 | - colorFunction: { | ||
857 | - title: 'Path color function: f(data, dsData, dsIndex)', | ||
858 | - type: 'string' | ||
859 | - }, | ||
860 | - usePolylineDecorator: { | ||
861 | - title: 'Use path decorator', | ||
862 | - type: 'boolean', | ||
863 | - default: false | ||
864 | - }, | ||
865 | - decoratorSymbol: { | ||
866 | - title: 'Decorator symbol', | ||
867 | - type: 'string', | ||
868 | - default: 'arrowHead' | ||
869 | - }, | ||
870 | - decoratorSymbolSize: { | ||
871 | - title: 'Decorator symbol size (px)', | ||
872 | - type: 'number', | ||
873 | - default: 10 | ||
874 | - }, | ||
875 | - useDecoratorCustomColor: { | ||
876 | - title: 'Use path decorator custom color', | ||
877 | - type: 'boolean', | ||
878 | - default: false | ||
879 | - }, | ||
880 | - decoratorCustomColor: { | ||
881 | - title: 'Decorator custom color', | ||
882 | - type: 'string', | ||
883 | - default: '#000' | ||
884 | - }, | ||
885 | - decoratorOffset: { | ||
886 | - title: 'Decorator offset', | ||
887 | - type: 'string', | ||
888 | - default: '20px' | ||
889 | - }, | ||
890 | - endDecoratorOffset: { | ||
891 | - title: 'End decorator offset', | ||
892 | - type: 'string', | ||
893 | - default: '20px' | ||
894 | - }, | ||
895 | - decoratorRepeat: { | ||
896 | - title: 'Decorator repeat', | ||
897 | - type: 'string', | ||
898 | - default: '20px' | ||
899 | - }, | ||
900 | - showPolygon: { | ||
901 | - title: 'Show polygon', | ||
902 | - type: 'boolean', | ||
903 | - default: false | ||
904 | - }, | ||
905 | - polygonTooltipPattern: { | ||
906 | - title: 'Tooltip (for ex. \'Text ${keyName} units.\' or <link-act name=\'my-action\'>Link text</link-act>\')', | ||
907 | - type: 'string', | ||
908 | - default: '<b>${entityName}</b><br/><br/><b>TimeStamp:</b> ${ts:7}' | ||
909 | - }, | ||
910 | - usePolygonTooltipFunction: { | ||
911 | - title: 'Use polygon tooltip function', | ||
912 | - type: 'boolean', | ||
913 | - default: false | ||
914 | - }, | ||
915 | - polygonTooltipFunction: { | ||
916 | - title: 'Polygon tooltip function: f(data, dsData, dsIndex)', | ||
917 | - type: 'string' | ||
918 | - }, | ||
919 | - polygonColor: { | ||
920 | - title: 'Polygon color', | ||
921 | - type: 'string' | ||
922 | - }, | ||
923 | - polygonOpacity: { | ||
924 | - title: 'Polygon opacity', | ||
925 | - type: 'number', | ||
926 | - default: 0.5 | ||
927 | - }, | ||
928 | - polygonStrokeColor: { | ||
929 | - title: 'Polygon border color', | ||
930 | - type: 'string' | ||
931 | - }, | ||
932 | - polygonStrokeOpacity: { | ||
933 | - title: 'Polygon border opacity', | ||
934 | - type: 'number', | ||
935 | - default: 1 | ||
936 | - }, | ||
937 | - polygonStrokeWeight: { | ||
938 | - title: 'Polygon border weight', | ||
939 | - type: 'number', | ||
940 | - default: 1 | ||
941 | - }, | ||
942 | - usePolygonColorFunction: { | ||
943 | - title: 'Use polygon color function', | ||
944 | - type: 'boolean', | ||
945 | - default: false | ||
946 | - }, | ||
947 | - polygonColorFunction: { | ||
948 | - title: 'Polygon Color function: f(data, dsData, dsIndex)', | ||
949 | - type: 'string' | ||
950 | - }, | ||
951 | - showPoints: { | ||
952 | - title: 'Show points', | ||
953 | - type: 'boolean', | ||
954 | - default: false | ||
955 | - }, | ||
956 | - pointColor: { | ||
957 | - title: 'Point color', | ||
958 | - type: 'string' | ||
959 | - }, | ||
960 | - pointSize: { | ||
961 | - title: 'Point size (px)', | ||
962 | - type: 'number', | ||
963 | - default: 10 | ||
964 | - }, | ||
965 | - usePointAsAnchor: { | ||
966 | - title: 'Use point as anchor', | ||
967 | - type: 'boolean', | ||
968 | - default: false | ||
969 | - }, | ||
970 | - pointAsAnchorFunction: { | ||
971 | - title: 'Point as anchor function: f(data, dsData, dsIndex)', | ||
972 | - type: 'string' | ||
973 | - }, | ||
974 | - pointTooltipOnRightPanel: { | ||
975 | - title: 'Independant point tooltip', | ||
976 | - type: 'boolean', | ||
977 | - default: true | ||
978 | - }, | ||
979 | autocloseTooltip: { | 1008 | autocloseTooltip: { |
980 | title: 'Auto-close point popup', | 1009 | title: 'Auto-close point popup', |
981 | type: 'boolean', | 1010 | type: 'boolean', |
@@ -1015,111 +1044,35 @@ export const tripAnimationSchema = { | @@ -1015,111 +1044,35 @@ export const tripAnimationSchema = { | ||
1015 | }, | 1044 | }, |
1016 | required: [] | 1045 | required: [] |
1017 | }, | 1046 | }, |
1018 | - form: [{ | ||
1019 | - key: 'mapProvider', | ||
1020 | - type: 'rc-select', | ||
1021 | - multiple: false, | ||
1022 | - items: [{ | ||
1023 | - value: 'OpenStreetMap.Mapnik', | ||
1024 | - label: 'OpenStreetMap.Mapnik (Default)' | ||
1025 | - }, { | ||
1026 | - value: 'OpenStreetMap.BlackAndWhite', | ||
1027 | - label: 'OpenStreetMap.BlackAndWhite' | ||
1028 | - }, { | ||
1029 | - value: 'OpenStreetMap.HOT', | ||
1030 | - label: 'OpenStreetMap.HOT' | ||
1031 | - }, { | ||
1032 | - value: 'Esri.WorldStreetMap', | ||
1033 | - label: 'Esri.WorldStreetMap' | ||
1034 | - }, { | ||
1035 | - value: 'Esri.WorldTopoMap', | ||
1036 | - label: 'Esri.WorldTopoMap' | ||
1037 | - }, { | ||
1038 | - value: 'CartoDB.Positron', | ||
1039 | - label: 'CartoDB.Positron' | ||
1040 | - }, { | ||
1041 | - value: 'CartoDB.DarkMatter', | ||
1042 | - label: 'CartoDB.DarkMatter' | ||
1043 | - }] | ||
1044 | - }, 'normalizationStep', 'latKeyName', 'lngKeyName', 'polKeyName', 'showLabel', 'label', 'useLabelFunction', { | 1047 | + form: ['normalizationStep', 'latKeyName', 'lngKeyName', 'showLabel', 'label', 'useLabelFunction', { |
1045 | key: 'labelFunction', | 1048 | key: 'labelFunction', |
1046 | type: 'javascript' | 1049 | type: 'javascript' |
1047 | }, 'showTooltip', { | 1050 | }, 'showTooltip', { |
1048 | - key: 'tooltipColor', | ||
1049 | - type: 'color' | ||
1050 | - }, { | ||
1051 | - key: 'tooltipFontColor', | ||
1052 | - type: 'color' | ||
1053 | - }, 'tooltipOpacity', { | ||
1054 | - key: 'tooltipPattern', | ||
1055 | - type: 'textarea' | ||
1056 | - }, 'useTooltipFunction', { | ||
1057 | - key: 'tooltipFunction', | ||
1058 | - type: 'javascript' | ||
1059 | - }, { | ||
1060 | - key: 'color', | ||
1061 | - type: 'color' | ||
1062 | - }, 'useColorFunction', { | ||
1063 | - key: 'colorFunction', | ||
1064 | - type: 'javascript' | ||
1065 | - }, 'usePolylineDecorator', { | ||
1066 | - key: 'decoratorSymbol', | ||
1067 | - type: 'rc-select', | ||
1068 | - multiple: false, | ||
1069 | - items: [{ | ||
1070 | - value: 'arrowHead', | ||
1071 | - label: 'Arrow' | 1051 | + key: 'tooltipColor', |
1052 | + type: 'color' | ||
1053 | + }, { | ||
1054 | + key: 'tooltipFontColor', | ||
1055 | + type: 'color' | ||
1056 | + }, 'tooltipOpacity', { | ||
1057 | + key: 'tooltipPattern', | ||
1058 | + type: 'textarea' | ||
1059 | + }, 'useTooltipFunction', { | ||
1060 | + key: 'tooltipFunction', | ||
1061 | + type: 'javascript' | ||
1062 | + }, 'autocloseTooltip', { | ||
1063 | + key: 'markerImage', | ||
1064 | + type: 'image' | ||
1065 | + }, 'markerImageSize', 'rotationAngle', 'useMarkerImageFunction', | ||
1066 | + { | ||
1067 | + key: 'markerImageFunction', | ||
1068 | + type: 'javascript' | ||
1072 | }, { | 1069 | }, { |
1073 | - value: 'dash', | ||
1074 | - label: 'Dash' | 1070 | + key: 'markerImages', |
1071 | + items: [ | ||
1072 | + { | ||
1073 | + key: 'markerImages[]', | ||
1074 | + type: 'image' | ||
1075 | + } | ||
1076 | + ] | ||
1075 | }] | 1077 | }] |
1076 | - }, 'decoratorSymbolSize', 'useDecoratorCustomColor', { | ||
1077 | - key: 'decoratorCustomColor', | ||
1078 | - type: 'color' | ||
1079 | - }, { | ||
1080 | - key: 'decoratorOffset', | ||
1081 | - type: 'textarea' | ||
1082 | - }, { | ||
1083 | - key: 'endDecoratorOffset', | ||
1084 | - type: 'textarea' | ||
1085 | - }, { | ||
1086 | - key: 'decoratorRepeat', | ||
1087 | - type: 'textarea' | ||
1088 | - }, 'strokeWeight', 'strokeOpacity', 'showPolygon', { | ||
1089 | - key: 'polygonTooltipPattern', | ||
1090 | - type: 'textarea' | ||
1091 | - }, 'usePolygonTooltipFunction', { | ||
1092 | - key: 'polygonTooltipFunction', | ||
1093 | - type: 'javascript' | ||
1094 | - }, { | ||
1095 | - key: 'polygonColor', | ||
1096 | - type: 'color' | ||
1097 | - }, 'polygonOpacity', { | ||
1098 | - key: 'polygonStrokeColor', | ||
1099 | - type: 'color' | ||
1100 | - }, 'polygonStrokeOpacity', 'polygonStrokeWeight', 'usePolygonColorFunction', { | ||
1101 | - key: 'polygonColorFunction', | ||
1102 | - type: 'javascript' | ||
1103 | - }, 'showPoints', { | ||
1104 | - key: 'pointColor', | ||
1105 | - type: 'color' | ||
1106 | - }, 'pointSize', 'usePointAsAnchor', { | ||
1107 | - key: 'pointAsAnchorFunction', | ||
1108 | - type: 'javascript' | ||
1109 | - }, 'pointTooltipOnRightPanel', 'autocloseTooltip', { | ||
1110 | - key: 'markerImage', | ||
1111 | - type: 'image' | ||
1112 | - }, 'markerImageSize', 'rotationAngle', 'useMarkerImageFunction', | ||
1113 | - { | ||
1114 | - key: 'markerImageFunction', | ||
1115 | - type: 'javascript' | ||
1116 | - }, { | ||
1117 | - key: 'markerImages', | ||
1118 | - items: [ | ||
1119 | - { | ||
1120 | - key: 'markerImages[]', | ||
1121 | - type: 'image' | ||
1122 | - } | ||
1123 | - ] | ||
1124 | - }] | ||
1125 | } | 1078 | } |
@@ -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,9 +21,9 @@ import { interpolateOnPointSegment } from 'leaflet-geometryutil'; | @@ -21,9 +21,9 @@ 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'; | ||
25 | -import { initSchema, addToSchema, addGroupInfo } from '@app/core/schema-utils'; | ||
26 | -import { tripAnimationSchema } from '../lib/maps/schemes'; | 24 | +import { MapProviders, FormattedData } from '../lib/maps/map-models'; |
25 | +import { initSchema, addToSchema, addGroupInfo, addCondition } from '@app/core/schema-utils'; | ||
26 | +import { tripAnimationSchema, mapPolygonSchema, pathSchema, pointSchema } from '../lib/maps/schemes'; | ||
27 | import { DomSanitizer } from '@angular/platform-browser'; | 27 | import { DomSanitizer } from '@angular/platform-browser'; |
28 | import { WidgetContext } from '@app/modules/home/models/widget-component.models'; | 28 | import { WidgetContext } from '@app/modules/home/models/widget-component.models'; |
29 | import { findAngle, getRatio, parseArray, parseWithTranslation, safeExecute } from '../lib/maps/maps-utils'; | 29 | import { findAngle, getRatio, parseArray, parseWithTranslation, safeExecute } from '../lib/maps/maps-utils'; |
@@ -58,13 +58,21 @@ export class TripAnimationComponent implements OnInit, AfterViewInit { | @@ -58,13 +58,21 @@ 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(); |
64 | - addToSchema(schema, TbMapWidgetV2.getProvidersSchema()); | 66 | + addToSchema(schema, TbMapWidgetV2.getProvidersSchema(null, true)); |
65 | addGroupInfo(schema, 'Map Provider Settings'); | 67 | addGroupInfo(schema, 'Map Provider Settings'); |
66 | addToSchema(schema, tripAnimationSchema); | 68 | addToSchema(schema, tripAnimationSchema); |
67 | addGroupInfo(schema, 'Trip Animation Settings'); | 69 | addGroupInfo(schema, 'Trip Animation Settings'); |
70 | + addToSchema(schema, pathSchema); | ||
71 | + addGroupInfo(schema, 'Path Settings'); | ||
72 | + addToSchema(schema, addCondition(pointSchema, 'model.showPoints === true', ['showPoints'])); | ||
73 | + addGroupInfo(schema, 'Path Points Settings'); | ||
74 | + addToSchema(schema, addCondition(mapPolygonSchema, 'model.showPolygon === true', ['showPolygon'])); | ||
75 | + addGroupInfo(schema, 'Polygon Settings'); | ||
68 | return schema; | 76 | return schema; |
69 | } | 77 | } |
70 | 78 | ||
@@ -78,14 +86,15 @@ export class TripAnimationComponent implements OnInit, AfterViewInit { | @@ -78,14 +86,15 @@ export class TripAnimationComponent implements OnInit, AfterViewInit { | ||
78 | rotationAngle: 0 | 86 | rotationAngle: 0 |
79 | } | 87 | } |
80 | 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; | ||
81 | const subscription = this.ctx.subscriptions[Object.keys(this.ctx.subscriptions)[0]]; | 92 | const subscription = this.ctx.subscriptions[Object.keys(this.ctx.subscriptions)[0]]; |
82 | - if (subscription) subscription.callbacks.onDataUpdated = (updated) => { | 93 | + if (subscription) subscription.callbacks.onDataUpdated = () => { |
83 | this.historicalData = parseArray(this.ctx.data); | 94 | this.historicalData = parseArray(this.ctx.data); |
84 | this.activeTrip = this.historicalData[0][0]; | 95 | this.activeTrip = this.historicalData[0][0]; |
85 | this.calculateIntervals(); | 96 | this.calculateIntervals(); |
86 | this.timeUpdated(this.intervals[0]); | 97 | this.timeUpdated(this.intervals[0]); |
87 | - this.mapWidget.map.updatePolylines(this.interpolatedData.map(ds => _.values(ds))); | ||
88 | - | ||
89 | this.mapWidget.map.map?.invalidateSize(); | 98 | this.mapWidget.map.map?.invalidateSize(); |
90 | this.cd.detectChanges(); | 99 | this.cd.detectChanges(); |
91 | } | 100 | } |
@@ -104,9 +113,17 @@ export class TripAnimationComponent implements OnInit, AfterViewInit { | @@ -104,9 +113,17 @@ export class TripAnimationComponent implements OnInit, AfterViewInit { | ||
104 | this.calcLabel(); | 113 | this.calcLabel(); |
105 | this.calcTooltip(); | 114 | this.calcTooltip(); |
106 | if (this.mapWidget) { | 115 | if (this.mapWidget) { |
116 | + this.mapWidget.map.updatePolylines(this.interpolatedData.map(ds => _.values(ds))); | ||
107 | if (this.settings.showPolygon) { | 117 | if (this.settings.showPolygon) { |
108 | this.mapWidget.map.updatePolygons(this.interpolatedData); | 118 | this.mapWidget.map.updatePolygons(this.interpolatedData); |
109 | } | 119 | } |
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); | ||
126 | + } | ||
110 | this.mapWidget.map.updateMarkers(currentPosition); | 127 | this.mapWidget.map.updateMarkers(currentPosition); |
111 | } | 128 | } |
112 | } | 129 | } |
@@ -117,23 +134,29 @@ export class TripAnimationComponent implements OnInit, AfterViewInit { | @@ -117,23 +134,29 @@ export class TripAnimationComponent implements OnInit, AfterViewInit { | ||
117 | calculateIntervals() { | 134 | calculateIntervals() { |
118 | this.historicalData.forEach((dataSource, index) => { | 135 | this.historicalData.forEach((dataSource, index) => { |
119 | this.intervals = []; | 136 | this.intervals = []; |
120 | - | ||
121 | 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) { |
122 | this.intervals.push(time); | 138 | this.intervals.push(time); |
123 | } | 139 | } |
124 | - | ||
125 | this.intervals.push(dataSource[dataSource.length - 1]?.time); | 140 | this.intervals.push(dataSource[dataSource.length - 1]?.time); |
126 | this.interpolatedData[index] = this.interpolateArray(dataSource, this.intervals); | 141 | this.interpolatedData[index] = this.interpolateArray(dataSource, this.intervals); |
127 | }); | 142 | }); |
128 | 143 | ||
129 | } | 144 | } |
130 | 145 | ||
131 | - calcTooltip() { | ||
132 | - const data = { ...this.activeTrip, maxTime: this.maxTime, minTime: this.minTime } | ||
133 | - 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 ? | ||
134 | safeExecute(this.settings.tooolTipFunction, [data, this.historicalData, 0]) : this.settings.tooltipPattern; | 152 | safeExecute(this.settings.tooolTipFunction, [data, this.historicalData, 0]) : this.settings.tooltipPattern; |
135 | - this.mainTooltip = this.sanitizer.sanitize( | ||
136 | - 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; | ||
137 | } | 160 | } |
138 | 161 | ||
139 | 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 | } |