@raruto/leaflet-elevation 1.9.6 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/control.js CHANGED
@@ -1,9 +1,5 @@
1
1
  import 'leaflet-i18n';
2
2
  import * as _ from './utils';
3
- import * as D3 from './components';
4
- import { Chart } from './chart';
5
- import { Marker } from './marker';
6
- import { Summary } from './summary';
7
3
  import { Options } from './options';
8
4
 
9
5
  export const Elevation = L.Control.Elevation = L.Control.extend({
@@ -11,23 +7,25 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
11
7
  includes: L.Evented ? L.Evented.prototype : L.Mixin.Events,
12
8
 
13
9
  options: Options,
14
- __mileFactor: 0.621371,
15
- __footFactor: 3.28084,
16
- __D3: 'https://unpkg.com/d3@6.5.0/dist/d3.min.js',
17
- __TOGEOJSON: 'https://unpkg.com/@tmcw/togeojson@4.5.0/dist/togeojson.umd.js',
18
- __LGEOMUTIL: 'https://unpkg.com/leaflet-geometryutil@0.9.3/src/leaflet.geometryutil.js',
19
- __LALMOSTOVER: 'https://unpkg.com/leaflet-almostover@1.0.1/src/leaflet.almostover.js',
20
- __LDISTANCEM: 'https://unpkg.com/@raruto/leaflet-elevation@1.9.6/libs/leaflet-distance-marker.min.js',
10
+ __mileFactor: 0.621371, // 1 km = (0.621371 mi)
11
+ __footFactor: 3.28084, // 1 m = (3.28084 ft)
12
+ __D3: 'https://unpkg.com/d3@6.5.0/dist/d3.min.js',
13
+ __TOGEOJSON: 'https://unpkg.com/@tmcw/togeojson@4.6.0/dist/togeojson.umd.js',
14
+ __LGEOMUTIL: 'https://unpkg.com/leaflet-geometryutil@0.9.3/src/leaflet.geometryutil.js',
15
+ __LALMOSTOVER: 'https://unpkg.com/leaflet-almostover@1.0.1/src/leaflet.almostover.js',
16
+ __LDISTANCEM: '../libs/leaflet-distance-marker.min.js',
17
+ __LCHART: '../src/components/chart.js',
18
+ __LMARKER: '../src/components/marker.js',
19
+ __LSUMMARY: '../src/components/summary.js',
20
+ __modulesFolder: '../src/handlers/',
21
21
 
22
22
  /*
23
23
  * Add data to the diagram either from GPX or GeoJSON and update the axis domain and data
24
24
  */
25
25
  addData: function(d, layer) {
26
- this.lazyLoad(this.__D3)
26
+ this.import(this.__D3)
27
27
  .then(() => {
28
- if (typeof layer === "undefined" && d.on) {
29
- layer = d;
30
- }
28
+ layer = layer ?? (d.on && d);
31
29
  this._addData(d);
32
30
  this._addLayer(layer);
33
31
  this._fireEvt("eledata_added", { data: d, layer: layer, track_info: this.track_info });
@@ -52,10 +50,11 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
52
50
  * Reset data and display
53
51
  */
54
52
  clear: function() {
55
- if (this._marker) this._marker.remove();
56
- if (this._chart) this._clearChart();
57
- if (this._layers) this._clearLayers();
58
- if (this._markers) this._clearMarkers();
53
+ if (this._marker) this._marker.remove();
54
+ if (this._chart) this._clearChart();
55
+ if (this._layers) this._clearLayers(this._layers);
56
+ if (this._markers) this._clearLayers(this._markers);
57
+ if (this._circleMarkers) this._clearLayers(this._circleMarkers);
59
58
 
60
59
  this._data = [];
61
60
  this.track_info = {};
@@ -67,7 +66,7 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
67
66
 
68
67
  _clearChart: function() {
69
68
  if (this._events && this._events.elechart_updated) {
70
- this._events.elechart_updated.forEach(e => this.off('elechart_updated', e.fn, e.ctx));
69
+ this._events.elechart_updated.forEach(({fn, ctx}) => this.off('elechart_updated', fn, ctx));
71
70
  }
72
71
  if (this._chart && this._chart._container) {
73
72
  this._chart._container.selectAll('g.point .point').remove();
@@ -75,17 +74,11 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
75
74
  }
76
75
  },
77
76
 
78
- _clearLayers: function() {
79
- if (this._layers && this._layers.eachLayer) {
80
- this._layers.eachLayer(l => l.remove())
81
- this._layers.clearLayers();
82
- }
83
- },
84
-
85
- _clearMarkers: function() {
86
- if (this._markers && this._markers.eachLayer) {
87
- this._markers.eachLayer(l => l.remove())
88
- this._markers.clearLayers();
77
+ _clearLayers: function(l) {
78
+ l = l || this._layers;
79
+ if (l && l.eachLayer) {
80
+ l.eachLayer(f => f.remove())
81
+ l.clearLayers();
89
82
  }
90
83
  },
91
84
 
@@ -144,8 +137,7 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
144
137
  },
145
138
 
146
139
  getBounds: function(data) {
147
- data = data || this._data;
148
- return L.latLngBounds(data.map((d) => d.latlng));
140
+ return L.latLngBounds((data || this._data).map((d) => d.latlng));
149
141
  },
150
142
 
151
143
  /**
@@ -165,63 +157,54 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
165
157
  /**
166
158
  * Initialize chart control "options" and "container".
167
159
  */
168
- initialize: function(options) {
160
+ initialize: function(opts) {
161
+
162
+ opts = L.setOptions(this, opts);
163
+
169
164
  this._data = [];
170
165
  this._layers = L.featureGroup();
171
166
  this._markers = L.featureGroup();
167
+ this._circleMarkers = L.featureGroup();
172
168
  this._markedSegments = L.polyline([]);
173
- this._chartEnabled = true,
169
+ this._start = (opts.trkStart || Options.trkStart);
170
+ this._end = (opts.trkEnd || Options.trkEnd);
171
+ this._chartEnabled = true;
172
+ this._yCoordMax = -Infinity;
174
173
  this.track_info = {};
175
- this.handlers = [];
174
+ // this.handlers = [];
176
175
 
177
- L.setOptions(this, options);
176
+ if (opts.followMarker) this._setMapView = _.throttle(this._setMapView, 300, this);
177
+ if (opts.legend) opts.margins.bottom += 30;
178
+ if (opts.theme) opts.polylineSegments.className += ' ' + opts.theme;
179
+ if (opts.wptIcons === true) opts.wptIcons = Options.wptIcons;
180
+ if (opts.distanceMarkers === true) opts.distanceMarkers = Options.distanceMarkers;
181
+ if (opts.trkStart) this._start.addTo(this._circleMarkers);
182
+ if (opts.trkEnd) this._end.addTo(this._circleMarkers);
178
183
 
179
- if (this.options.followMarker) this._setMapView = L.Util.throttle(this._setMapView, 300, this);
180
- if (this.options.legend) this.options.margins.bottom += 30;
181
- if (this.options.theme) this.options.polylineSegments.className += ' ' + this.options.theme;
182
- if (this.options.wptIcons === true) this.options.wptIcons = Options.wptIcons;
183
- if (this.options.distanceMarkers === true) this.options.distanceMarkers = Options.distanceMarkers;
184
184
 
185
- this._markedSegments.setStyle(this.options.polylineSegments);
185
+ this._markedSegments.setStyle(opts.polylineSegments);
186
186
 
187
187
  // Leaflet canvas renderer colors
188
- L.extend(D3.Colors, this.options.colors || {});
188
+ L.extend(_.Colors, opts.colors || {});
189
189
 
190
190
  // Various stuff
191
191
  this._fixCanvasPaths();
192
192
  this._fixTooltipSize();
193
+
193
194
  },
194
195
 
195
196
  /**
196
197
  * Javascript scripts downloader (lazy loader)
197
198
  */
198
- lazyLoad: function(src, condition = false, loader = '') {
199
- if (!this.options.lazyLoadJS) {
200
- return Promise.resolve();
201
- }
199
+ import: function(src, condition) {
202
200
  switch(src) {
203
- case this.__D3:
204
- loader = '_d3LazyLoader';
205
- condition = typeof d3 !== 'object';
206
- break;
207
- case this.__TOGEOJSON:
208
- loader = '_togeojsonLazyLoader';
209
- condition = typeof toGeoJSON !== 'object';
210
- break;
211
- case this.__LGEOMUTIL:
212
- loader = '_geomutilLazyLoader';
213
- condition = typeof L.GeometryUtil !== 'object';
214
- break;
215
- case this.__LALMOSTOVER:
216
- loader = '_almostoverLazyLoader';
217
- condition = typeof L.Handler.AlmostOver !== 'function';
218
- break;
219
- case this.__LDISTANCEM:
220
- loader = '_distanceMarkersLazyLoader';
221
- condition = typeof L.DistanceMarkers !== 'function';
222
- break;
201
+ case this.__D3: condition = typeof d3 !== 'object'; break;
202
+ case this.__TOGEOJSON: condition = typeof toGeoJSON !== 'object'; break;
203
+ case this.__LGEOMUTIL: condition = typeof L.GeometryUtil !== 'object'; break;
204
+ case this.__LALMOSTOVER: condition = typeof L.Handler.AlmostOver !== 'function'; break;
205
+ case this.__LDISTANCEM: condition = typeof L.DistanceMarkers !== 'function'; break;
223
206
  }
224
- return L.Control.Elevation[loader] = _.lazyLoader(src, condition, L.Control.Elevation[loader]);
207
+ return condition !== false ? import(src) : Promise.resolve();
225
208
  },
226
209
 
227
210
  /**
@@ -239,33 +222,23 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
239
222
  this._map = map;
240
223
 
241
224
  let container = this._container = _.create("div", "elevation-control elevation " + this.options.theme + " " + (this.options.detached ? '' : 'leaflet-control'));
242
-
243
- this.lazyLoad(this.__D3)
244
- .then(() => {
245
- this._initButton(container);
246
- this._initChart(container);
247
- this._initSummary(container);
248
- this._initMarker(map);
249
- this._initLayer(map);
250
225
 
251
- map
252
- .on('zoom viewreset zoomanim', this._hideMarker, this)
253
- .on('resize', this._resetView, this)
254
- .on('resize', this._resizeChart, this)
255
- .on('rotate', this._rotateMarker, this)
256
- .on('mousedown', this._resetDrag, this);
257
-
258
- _.on(map.getContainer(), 'mousewheel', this._resetDrag, this);
259
- _.on(map.getContainer(), 'touchstart', this._resetDrag, this);
260
- _.on(document, 'keydown', this._keydownHandler, this);
261
-
262
- this
263
- .on('eledata_added eledata_loaded', this._updateChart, this)
264
- .on('eledata_added eledata_loaded', this._updateSummary, this);
265
-
266
- this._updateChart();
267
- this._updateSummary();
268
- });
226
+ let modules = [
227
+ "Distance",
228
+ "Time",
229
+ "Altitude",
230
+ "Slope",
231
+ "Speed",
232
+ "Acceleration",
233
+ ...this.options.handlers
234
+ ].filter(m => false !== this.options[typeof m == "string" && m.toLowerCase()]);
235
+
236
+ this._loadModules(modules); // Inject here required modules (data handlers)
237
+ this._initButton(container);
238
+ this._initChart(container);
239
+ this._initSummary(container);
240
+ this._initMarker(map);
241
+ this._initLayer(map);
269
242
 
270
243
  return container;
271
244
  },
@@ -278,18 +251,18 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
278
251
  this._container = null;
279
252
 
280
253
  map
281
- .off('zoom viewreset zoomanim', this._hideMarker, this)
282
- .off('resize', this._resetView, this)
283
- .off('resize', this._resizeChart, this)
284
- .off('mousedown', this._resetDrag, this);
254
+ .off('zoom viewreset zoomanim', this._hideMarker, this)
255
+ .off('resize', this._resetView, this)
256
+ .off('resize', this._resizeChart, this)
257
+ .off('mousedown', this._resetDrag, this);
285
258
 
286
- _.off(map.getContainer(), 'mousewheel', this._resetDrag, this);
287
- _.off(map.getContainer(), 'touchstart', this._resetDrag, this);
288
- _.off(document, 'keydown', this._keydownHandler, this);
259
+ _.off(map.getContainer(), 'mousewheel', this._resetDrag, this);
260
+ _.off(map.getContainer(), 'touchstart', this._resetDrag, this);
261
+ _.off(document, 'keydown', this._onKeyDown, this);
289
262
 
290
263
  this
291
- .off('eledata_added eledata_loaded', this._updateChart, this)
292
- .off('eledata_added eledata_loaded', this._updateSummary, this);
264
+ .off('eledata_added eledata_loaded', this._updateChart, this)
265
+ .off('eledata_added eledata_loaded', this._updateSummary, this);
293
266
  },
294
267
 
295
268
  /**
@@ -321,49 +294,44 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
321
294
  return;
322
295
  }
323
296
 
324
- /**
325
- * Standard GeoJSON --> doesn't handle "time", "heart", …
326
- */
327
- let geom = d.geometry;
328
- if (geom) {
329
- switch (geom.type) {
330
- case 'LineString':
331
- this._addGeoJSONData(geom.coordinates);
332
- break;
333
-
334
- case 'MultiLineString':
335
- _.each(geom.coordinates, coords => this._addGeoJSONData(coords));
336
- break;
337
-
338
- default:
339
- console.warn('Unsopperted GeoJSON feature geometry type:' + geom.type);
297
+ // Standard GeoJSON
298
+ if (d.type === "FeatureCollection" ) {
299
+ return _.each(d.features, feature => this._addData(feature));
300
+ } else if (d.type === "Feature") {
301
+ let geom = d.geometry;
302
+ if (geom) {
303
+ switch (geom.type) {
304
+ case 'LineString': return this._addGeoJSONData(geom.coordinates, d.properties);
305
+ case 'MultiLineString': return _.each(geom.coordinates, (coords, i) => this._addGeoJSONData(coords, d.properties, i));
306
+ case 'Point':
307
+ default: return console.warn('Unsopperted GeoJSON feature geometry type:' + geom.type);
308
+ }
340
309
  }
341
310
  }
342
- if (d.type === "FeatureCollection") {
343
- _.each(d.features, feature => this._addData(feature));
344
- }
345
311
 
346
- /**
347
- * Extended GeoJSON --> rely on leaflet implementation
348
- */
312
+ // Fallback for leaflet layers (eg. L.Gpx)
349
313
  if (d._latlngs) {
350
- this._addGeoJSONData(d._latlngs, d.feature && d.feature.properties);
314
+ return this._addGeoJSONData(d._latlngs, d.feature && d.feature.properties);
351
315
  }
316
+
352
317
  },
353
318
 
354
319
  /*
355
320
  * Parsing of GeoJSON data lines and their elevation in z-coordinate
356
321
  */
357
- _addGeoJSONData: function(coords, properties) {
322
+ _addGeoJSONData: function(coords, properties, nestingLevel) {
323
+
324
+ // "coordinateProperties" property is generated inside "@tmcw/toGeoJSON"
325
+ let props = (properties && properties.coordinateProperties) || properties;
326
+
358
327
  coords.forEach((point, i) => {
359
328
 
360
- // Inspired by L.GPX layer properties
361
- point.meta = point.meta ?? { time: null, ele: null, hr: null, cad: null, atemp: null };
329
+ // GARMIN_EXTENSIONS = ["hr", "cad", "atemp", "wtemp", "depth", "course", "bearing"];
330
+ point.meta = point.meta ?? { time: null, ele: null };
362
331
 
363
- // "coordinateProperties" property is generated inside "@tmcw/toGeoJSON"
364
- let props = (properties && properties.coordinateProperties) || properties;
332
+ point.prev = (attr) => (attr ? this._data[i > 0 ? i - 1 : 0][attr] : this._data[i > 0 ? i - 1 : 0]);
365
333
 
366
- this.fire("elepoint_init", { point: point, props: props, id: i });
334
+ this.fire("elepoint_init", { point: point, props: props, id: i, isMulti: nestingLevel });
367
335
 
368
336
  this._addPoint(
369
337
  point.lat ?? point[1],
@@ -372,6 +340,8 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
372
340
  );
373
341
 
374
342
  this.fire("elepoint_added", { point: point, index: this._data.length - 1 });
343
+
344
+ if (this._yCoordMax < this._data[this._data.length - 1][this.options.yAttr]) this._yCoordMax = this._data[this._data.length - 1][this.options.yAttr];
375
345
  });
376
346
 
377
347
  this.fire("eletrack_added", { coords: coords, index: this._data.length - 1 });
@@ -399,12 +369,14 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
399
369
  if (layer) this._layers.addLayer(layer)
400
370
  // Postpone adding the distance markers (lazy: true)
401
371
  if (layer && this.options.distanceMarkers && this.options.distanceMarkers.lazy) {
402
- layer.on('add remove', (e) => L.DistanceMarkers && e.target instanceof L.Polyline && e.target[e.type + 'DistanceMarkers']());
372
+ layer.on('add remove', ({target, type}) => L.DistanceMarkers && target instanceof L.Polyline && target[type + 'DistanceMarkers']());
403
373
  }
374
+ return layer;
404
375
  },
405
376
 
406
377
  _addMarker: function(marker) {
407
378
  if (marker) this._markers.addLayer(marker)
379
+ return marker;
408
380
  },
409
381
 
410
382
  /**
@@ -412,17 +384,18 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
412
384
  */
413
385
  _initAlmostOverHandler: function(map, layer) {
414
386
  if(!map || !this.options.almostOver) return;
415
- this.lazyLoad(this.__LGEOMUTIL)
416
- .then(() => this.lazyLoad(this.__LALMOSTOVER))
417
- .then(() => {
418
- map.addHandler('almostOver', L.Handler.AlmostOver)
419
- if (L.GeometryUtil && map.almostOver && map.almostOver.enabled()) {
420
- map.almostOver.addLayer(layer);
421
- map
422
- .on('almost:move', (e) => this._mousemoveLayerHandler(e))
423
- .on('almost:out', (e) => this._mouseoutHandler(e));
424
- }
425
- });
387
+ Promise.all([
388
+ this.import(this.__LGEOMUTIL),
389
+ this.import(this.__LALMOSTOVER)
390
+ ]).then(() => {
391
+ map.addHandler('almostOver', L.Handler.AlmostOver)
392
+ if (L.GeometryUtil && map.almostOver && map.almostOver.enabled()) {
393
+ map.almostOver.addLayer(layer);
394
+ map
395
+ .on('almost:move', (e) => this._onMouseMoveLayer(e))
396
+ .on('almost:out', (e) => this._onMouseOut(e));
397
+ }
398
+ });
426
399
  },
427
400
 
428
401
  /**
@@ -431,9 +404,10 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
431
404
  _initDistanceMarkers: function(map, layer) {
432
405
  if (!map) return;
433
406
  if (this.options.distanceMarkers) {
434
- this.lazyLoad(this.__LGEOMUTIL)
435
- .then(() => this.lazyLoad(this.__LDISTANCEM))
436
- .then(() => this.options.polyline && layer.addTo(map));
407
+ Promise.all([
408
+ this.import(this.__LGEOMUTIL),
409
+ this.import(this.__LDISTANCEM)
410
+ ]).then(() => this.options.polyline && layer.addTo(map) && this._circleMarkers.bringToFront());
437
411
  } else if (this.options.polyline) {
438
412
  layer.addTo(map);
439
413
  }
@@ -459,7 +433,12 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
459
433
  */
460
434
  _initMapIntegrations: function(control, layer) {
461
435
  let map = control._map;
462
- if (control._map) {
436
+ if (control._data.length) {
437
+ control._start.setLatLng(control._data[0].latlng);
438
+ control._end.setLatLng(control._data[control._data.length -1].latlng);
439
+ }
440
+ if (map) {
441
+ control._circleMarkers.addTo(map);
463
442
  map.once('layeradd', (e) => control.options.autofitBounds && control.fitBounds(layer.getBounds()));
464
443
  if (!L.Browser.mobile) {
465
444
  control._initAlmostOverHandler(map, layer);
@@ -492,12 +471,14 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
492
471
  _fixCanvasPaths: function() {
493
472
  let oldProto = L.Canvas.prototype._fillStroke;
494
473
  let control = this;
474
+
475
+ let theme = this.options.theme.replace('-theme', '');
476
+ let color = _.Colors[theme] || {};
477
+
495
478
  L.Canvas.include({
496
479
  _fillStroke: function(ctx, layer) {
497
480
  if (control._layers.hasLayer(layer)) {
498
481
 
499
- let theme = control.options.theme.replace('-theme', '');
500
- let color = D3.Colors[theme] || {};
501
482
  let options = layer.options;
502
483
 
503
484
  options.color = color.line || color.area || theme;
@@ -528,12 +509,12 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
528
509
  */
529
510
  _fixTooltipSize: function() {
530
511
  this.on('elechart_init', () =>
531
- this.once('elechart_change elechart_hover', e => {
512
+ this.once('elechart_change elechart_hover', ({data, xCoord}) => {
532
513
  if (this._chartEnabled) {
533
- this._chart._showDiagramIndicator(e.data, e.xCoord);
534
- this._chart._showDiagramIndicator(e.data, e.xCoord);
514
+ this._chart._showDiagramIndicator(data, xCoord);
515
+ this._chart._showDiagramIndicator(data, xCoord);
535
516
  }
536
- this._updateMarker(e.data);
517
+ this._updateMarker(data);
537
518
  })
538
519
  );
539
520
  },
@@ -560,15 +541,6 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
560
541
  if (this._map) this._map.fire(type, data, propagate);
561
542
  },
562
543
 
563
- /**
564
- * Calculates chart height.
565
- */
566
- _height: function() {
567
- if (this._chart) return this._chart._height();
568
- let opts = this.options;
569
- return opts.height - opts.margins.top - opts.margins.bottom;
570
- },
571
-
572
544
  /*
573
545
  * Hides the position/height indicator marker drawn onto the map
574
546
  */
@@ -584,9 +556,6 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
584
556
  _initChart: function(container) {
585
557
  let opts = this.options;
586
558
 
587
- opts.xTicks = this._xTicks();
588
- opts.yTicks = this._yTicks();
589
-
590
559
  if (opts.detached) {
591
560
  let { offsetWidth, offsetHeight} = this.eleDiv;
592
561
  if (offsetWidth > 0) opts.width = offsetWidth;
@@ -598,42 +567,66 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
598
567
  if (opts._maxWidth > clientWidth) opts.width = clientWidth - 30;
599
568
  }
600
569
 
601
- let chart = this._chart = new Chart(opts, this);
602
-
603
- this._x = this._chart._x;
604
- this._y = this._chart._y;
570
+ Promise.all([
571
+ this.import(this.__D3),
572
+ this.import(this.__LCHART)
573
+ ]).then((m) => {
605
574
 
606
- d3
607
- .select(container)
608
- .call(chart.render())
575
+ let chart = this._chart = new (m[1] || Elevation).Chart(opts, this);
576
+
577
+ this._x = this._chart._x;
578
+ this._y = this._chart._y;
579
+
580
+ d3
581
+ .select(container)
582
+ .call(chart.render())
583
+
584
+ chart
585
+ .on('reset_drag', this._hideMarker, this)
586
+ .on('mouse_enter', this._onMouseEnter, this)
587
+ .on('dragged', this._onDragEnd, this)
588
+ .on('mouse_move', this._onMouseMove, this)
589
+ .on('mouse_out', this._onMouseOut, this)
590
+ .on('ruler_filter', this._onRulerFilter, this)
591
+ .on('zoom', this._updateChart, this)
592
+ .on('elepath_toggle', this._onToggleChart, this)
593
+ .on('margins_updated', this._resizeChart, this);
594
+
595
+
596
+ this.fire("elechart_axis");
597
+
598
+ if (this.options.legend) this.fire("elechart_legend");
599
+
600
+ this.fire("elechart_init");
609
601
 
610
- chart
611
- .on('reset_drag', this._hideMarker, this)
612
- .on('mouse_enter', this._mouseenterHandler, this)
613
- .on('dragged', this._dragendHandler, this)
614
- .on('mouse_move', this._mousemoveHandler, this)
615
- .on('mouse_out', this._mouseoutHandler, this)
616
- .on('ruler_filter', this._rulerFilterHandler, this)
617
- .on('zoom', this._updateChart, this)
618
- .on('elepath_toggle', this._toggleChartHandler, this)
619
- .on('margins_updated',this._resizeChart, this);
602
+ map
603
+ .on('zoom viewreset zoomanim', this._hideMarker, this)
604
+ .on('resize', this._resetView, this)
605
+ .on('resize', this._resizeChart, this)
606
+ .on('rotate', this._rotateMarker, this)
607
+ .on('mousedown', this._resetDrag, this);
620
608
 
609
+ _.on(map.getContainer(), 'mousewheel', this._resetDrag, this);
610
+ _.on(map.getContainer(), 'touchstart', this._resetDrag, this);
611
+ _.on(document, 'keydown', this._onKeyDown, this);
621
612
 
622
- this.fire("elechart_axis");
613
+ this
614
+ .on('eledata_added eledata_loaded', this._updateChart, this)
615
+ .on('eledata_added eledata_loaded', this._updateSummary, this);
623
616
 
624
- if (this.options.legend) this.fire("elechart_legend");
617
+ this._updateChart();
618
+ this._updateSummary();
619
+ });
625
620
 
626
- this.fire("elechart_init");
627
621
  },
628
622
 
629
623
  _initLayer: function() {
630
624
  this._layers
631
- .on('layeradd layerremove', (e) => {
632
- let layer = e.layer
625
+ .on('layeradd layerremove', ({layer, type}) => {
633
626
  let node = layer.getElement && layer.getElement();
634
- _.toggleClass(node, this.options.polyline.className + ' ' + this.options.theme, e.type == 'layeradd');
635
- _.toggleEvent(layer, "mousemove", this._mousemoveLayerHandler.bind(this), e.type == 'layeradd')
636
- _.toggleEvent(layer, "mouseout", this._mouseoutHandler.bind(this), e.type == 'layeradd');
627
+ _.toggleClass(node, this.options.polyline.className + ' ' + this.options.theme, type == 'layeradd');
628
+ _.toggleEvent(layer, "mousemove", this._onMouseMoveLayer.bind(this), type == 'layeradd')
629
+ _.toggleEvent(layer, "mouseout", this._onMouseOut.bind(this), type == 'layeradd');
637
630
  });
638
631
  },
639
632
 
@@ -647,9 +640,14 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
647
640
 
648
641
  if (this._renderer) this._renderer.remove()
649
642
  this._renderer = L.svg({ pane: "elevationPane" }).addTo(this._map); // default leaflet svg renderer
650
- this._marker = new Marker(this.options);
651
643
 
652
- this.fire("elechart_marker");
644
+ Promise.all([
645
+ this.import(this.__D3),
646
+ this.import(this.__LMARKER)
647
+ ]).then((m) => {
648
+ this._marker = new (m[1] || Elevation).Marker(this.options, this);
649
+ this.fire("elechart_marker");
650
+ });
653
651
  },
654
652
 
655
653
  /**
@@ -684,15 +682,13 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
684
682
  },
685
683
 
686
684
  _initSummary: function(container) {
687
- this._summary = new Summary({ summary: this.options.summary }, this);
685
+ this.import(this.__LSUMMARY).then((m)=>{
686
+ this._summary = new (m || Elevation).Summary({ summary: this.options.summary }, this);
688
687
 
689
- d3.select(container).call(this._summary.render());
690
- },
691
-
692
- _keydownHandler: function(e) {
693
- if(!this.options.detached && e.key === "Escape"){
694
- this._collapse()
695
- };
688
+ this.on('elechart_init', () => {
689
+ d3.select(container).call(this._summary.render());
690
+ });
691
+ });
696
692
  },
697
693
 
698
694
  /**
@@ -708,138 +704,146 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
708
704
  }).catch((err) => console.warn(err));
709
705
  },
710
706
 
707
+ /**
708
+ * Dynamically import only required javascript modules (code splitting)
709
+ */
710
+ _loadModules: function(handlers) {
711
+ // First map known classnames (eg. "Altitude" --> L.Control.Elevation.Altitude)
712
+ handlers = handlers.map((h) => typeof h === 'string' && typeof Elevation[h] !== "undefined" ? Elevation[h] : h);
713
+ // Then load optional classes and custom imports (eg. "Cadence" --> import('../src/handlers/cadence.js'))
714
+ Promise.
715
+ all(
716
+ handlers
717
+ .filter(h => typeof h === 'string' || h instanceof Promise)
718
+ .map(file => file instanceof Promise ? file : this.import(this.__modulesFolder + file.toLowerCase() + '.js'))
719
+ )
720
+ .then((m) => {
721
+ _.each(m, (exported) => {
722
+ let fn = Object.keys(exported)[0];
723
+ if (fn && typeof Elevation[fn] === "undefined" ) {
724
+ handlers[fn] = Elevation[fn] = exported[fn];
725
+
726
+ }
727
+ });
728
+ _.each(handlers, h => this._registerHandler(h));
729
+ });
730
+ },
731
+
711
732
  /**
712
733
  * Simple GeoJSON data loader (L.GeoJSON).
713
734
  */
714
735
  _loadLayer: function(geojson) {
715
- let control = this;
736
+ let { polyline, theme, waypoints, wptIcons, wptLabels, distanceMarkers } = this.options;
737
+ let style = L.extend({}, polyline);
716
738
 
717
- let { wptIcons, wptLabels } = control.options;
739
+ if (theme) {
740
+ style.className += ' ' + theme;
741
+ }
742
+
743
+ if (geojson.name) {
744
+ this.track_info.name = geojson.name;
745
+ }
718
746
 
719
747
  let layer = L.geoJson(geojson, {
720
- distanceMarkers: control.options.distanceMarkers,
721
- style: (feature) => {
722
- let style = L.extend({}, control.options.polyline);
723
- if (control.options.theme) {
724
- style.className += ' ' + control.options.theme;
725
- }
726
- return style;
727
- },
748
+ distanceMarkers: distanceMarkers,
749
+ style: style,
728
750
  pointToLayer: (feature, latlng) => {
729
- if (!control.options.waypoints) return;
730
-
731
- let prop = feature.properties;
732
- let desc = prop.desc ?? '';
733
- let name = prop.name ?? '';
734
- let sym = (prop.sym ?? name).replace(' ', '-').replace('"', '').replace("'", '').toLowerCase();
735
-
736
- // Handle chart waypoints (dots)
737
- if ([true, 'dots'].includes(control.options.waypoints)) {
738
- control._registerCheckPoint({
739
- latlng: latlng,
740
- label: ([true, 'dots'].includes(wptLabels) ? name : '')
741
- });
742
- }
743
-
744
- // Handle map waypoints (markers)
745
- if ([true, 'markers'].includes(control.options.waypoints) && wptIcons != false) {
746
- // generate and cache appropriate icon symbol
747
- if (!wptIcons.hasOwnProperty(sym)) {
748
- wptIcons[sym] = L.divIcon(
749
- L.extend({}, wptIcons[""].options, { html: '<i class="elevation-waypoint-icon ' + sym + '"></i>' } )
750
- );
751
+ if (waypoints) {
752
+ let { desc, name, sym } = feature.properties;
753
+ desc = desc || '';
754
+ name = name || '';
755
+ // Handle chart waypoints (dots)
756
+ if ([true, 'dots'].includes(waypoints)) {
757
+ this._registerCheckPoint({
758
+ latlng: latlng,
759
+ label : ([true, 'dots'].includes(wptLabels) ? name : '')
760
+ });
751
761
  }
752
- let marker = L.marker(latlng, { icon: wptIcons[sym] });
753
- if ([true, 'markers'].includes(wptLabels) && (name || desc)) {
754
- let content = decodeURI("<b>" + name + "</b>" + (desc.length > 0 ? '<br>' + desc : ''));
755
- marker.bindPopup(content, { className: 'elevation-popup', keepInView: true }).openPopup();
756
- marker.bindTooltip(content, { className: 'elevation-tooltip', direction: 'auto', sticky: true, opacity: 1 }).openTooltip();
762
+ // Handle map waypoints (markers)
763
+ if ([true, 'markers'].includes(waypoints) && wptIcons != false) {
764
+ return this._registerMarker({
765
+ latlng : latlng,
766
+ sym : (sym ?? name).replace(' ', '-').replace('"', '').replace("'", '').toLowerCase(),
767
+ content: [true, 'markers'].includes(wptLabels) && (name || desc) && decodeURI("<b>" + name + "</b>" + (desc.length > 0 ? '<br>' + desc : ''))
768
+ });
757
769
  }
758
- control._addMarker(marker);
759
- control.fire('waypoint_added', { point: marker, element: latlng, properties: prop });
760
- return marker;
761
770
  }
762
771
  },
763
- onEachFeature: (feature, layer) => {
764
- if (feature.geometry && feature.geometry.type != 'Point') {
765
- control.addData(layer);
766
- control.track_info = L.extend({}, control.track_info, { name: geojson.name });
767
- }
768
- },
772
+ onEachFeature: (feature, layer) => feature.geometry && feature.geometry.type != 'Point' && this.addData(feature, layer),
769
773
  });
770
774
 
771
- control.lazyLoad(control.__D3).then(() => {
772
- control._initMapIntegrations(control, layer);
773
- control._fireEvt("eledata_loaded", { data: geojson, layer: layer, name: control.track_info.name, track_info: control.track_info })
775
+ this.import(this.__D3).then(() => {
776
+ this._initMapIntegrations(this, layer);
777
+ this._fireEvt("eledata_loaded", { data: geojson, layer: layer, name: this.track_info.name, track_info: this.track_info });
774
778
  });
775
779
 
776
780
  return layer;
777
781
  },
778
782
 
779
- _dragendHandler: function(e) {
783
+ _onDragEnd: function({ dragstart, dragend}) {
780
784
  this._hideMarker();
781
- this.fitBounds(L.latLngBounds([e.dragstart.latlng, e.dragend.latlng]));
785
+ this.fitBounds(L.latLngBounds([dragstart.latlng, dragend.latlng]));
782
786
 
783
787
  this.fire("elechart_dragged");
784
788
  },
785
789
 
790
+ _onKeyDown: function({key}) {
791
+ if(!this.options.detached && key === "Escape"){
792
+ this._collapse()
793
+ };
794
+ },
795
+
786
796
  /**
787
- * Trigger mouseenter event
797
+ * Trigger mouseenter event.
788
798
  */
789
- _mouseenterHandler: function(e) {
799
+ _onMouseEnter: function() {
790
800
  this.fire('elechart_enter');
791
801
  },
792
802
 
793
803
  /*
794
804
  * Handles the moueseover the chart and displays distance and altitude level.
795
805
  */
796
- _mousemoveHandler: function(e) {
797
- if (!this._data.length || !this._chartEnabled) {
798
- return;
799
- }
800
- let item = this._findItemForX(e.xCoord);
801
-
802
- if (item) {
803
- let xCoord = e.xCoord;
806
+ _onMouseMove: function({xCoord}) {
807
+ if (this._chartEnabled && this._data.length) {
808
+ let item = this._findItemForX(xCoord);
809
+ if (item) {
810
+ if (this._chartEnabled) this._chart._showDiagramIndicator(item, xCoord);
804
811
 
805
- if (this._chartEnabled) this._chart._showDiagramIndicator(item, xCoord);
812
+ this._updateMarker(item);
813
+ this._setMapView(item);
806
814
 
807
- this._updateMarker(item);
808
- this._setMapView(item);
815
+ if (this._map) {
816
+ _.addClass(this._map.getContainer(), 'elechart-hover');
817
+ }
809
818
 
810
- if (this._map) {
811
- _.addClass(this._map.getContainer(), 'elechart-hover');
819
+ this.fire("elechart_change", { data: item, xCoord: xCoord });
820
+ this.fire("elechart_hover", { data: item, xCoord: xCoord });
812
821
  }
813
-
814
- this.fire("elechart_change", { data: item, xCoord: xCoord });
815
- this.fire("elechart_hover", { data: item, xCoord: xCoord });
816
822
  }
817
823
  },
818
824
 
819
825
  /*
820
826
  * Handles mouseover events of the data layers on the map.
821
827
  */
822
- _mousemoveLayerHandler: function(e) {
823
- if (!this._data.length) {
824
- return;
825
- }
826
-
827
- let item = this._findItemForLatLng(e.latlng);
828
- if (item) {
829
- let xCoord = item.xDiagCoord;
830
-
831
- if (this._chartEnabled) this._chart._showDiagramIndicator(item, xCoord);
832
-
833
- this._updateMarker(item);
834
-
835
- this.fire("elechart_change", { data: item, xCoord: xCoord });
828
+ _onMouseMoveLayer: function({latlng}) {
829
+ if (this._data.length) {
830
+ let item = this._findItemForLatLng(latlng);
831
+ if (item) {
832
+ let xCoord = item.xDiagCoord;
833
+
834
+ if (this._chartEnabled) this._chart._showDiagramIndicator(item, xCoord);
835
+
836
+ this._updateMarker(item);
837
+
838
+ this.fire("elechart_change", { data: item, xCoord: xCoord });
839
+ }
836
840
  }
837
841
  },
838
842
 
839
843
  /*
840
844
  * Handles the moueseout over the chart.
841
845
  */
842
- _mouseoutHandler: function() {
846
+ _onMouseOut: function() {
843
847
  if (!this.options.detached) {
844
848
  this._hideMarker();
845
849
  this._chart._hideDiagramIndicator();
@@ -852,6 +856,33 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
852
856
  this.fire("elechart_leave");
853
857
  },
854
858
 
859
+ /**
860
+ * Handles the drag event over the ruler filter.
861
+ */
862
+ _onRulerFilter: function({coords}) {
863
+ this._updateMapSegments(coords);
864
+ },
865
+
866
+ /**
867
+ * Toggle chart data on legend click
868
+ */
869
+ _onToggleChart: function({ name, enabled }) {
870
+
871
+ this._chartEnabled = this._chart._hasActiveLayers();
872
+
873
+ // toggle layer visibility on empty chart
874
+ this._layers.eachLayer(layer => _.toggleClass(layer.getElement && layer.getElement(), this.options.polyline.className + ' ' + this.options.theme, this._chartEnabled));
875
+
876
+ // toggle option value (eg. altitude = { 'disabled' || 'enabled' })
877
+ this.options[name] = !enabled && this.options[name] == 'disabled' ? 'enabled' : 'disabled';
878
+
879
+ // remove marker on empty chart
880
+ if (!this._chartEnabled) {
881
+ this._chart._hideDiagramIndicator();
882
+ this._marker.remove();
883
+ }
884
+ },
885
+
855
886
  /**
856
887
  * Simple GeoJSON Parser
857
888
  */
@@ -866,7 +897,7 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
866
897
  */
867
898
  _parseFromString: function(data) {
868
899
  return new Promise(resolve =>
869
- this.lazyLoad(this.__TOGEOJSON).then(() => {
900
+ this.import(this.__TOGEOJSON).then(() => {
870
901
  let geojson;
871
902
  try {
872
903
  geojson = this._parseFromXMLString(data.trim());
@@ -874,8 +905,7 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
874
905
  geojson = this._parseFromGeoJSONString(data.toString());
875
906
  }
876
907
  if (geojson) {
877
- geojson = this._prepareMultiLineStrings(geojson);
878
- geojson = this._prepareAdditionalProperties(geojson);
908
+ geojson.name = geojson.name || (this._downloadURL || '').split('/').pop().split('#')[0].split('?')[0];
879
909
  }
880
910
  resolve(geojson);
881
911
  })
@@ -903,37 +933,6 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
903
933
  return geojson;
904
934
  },
905
935
 
906
- /**
907
- * Extend GeoJSON properties (name, time, ...)
908
- */
909
- _prepareAdditionalProperties: function(geojson) {
910
- if (!geojson.name && this._downloadURL) {
911
- geojson.name = this._downloadURL.split('/').pop().split('#')[0].split('?')[0];
912
- }
913
- return geojson;
914
- },
915
-
916
- /**
917
- * Just a temporary fix for MultiLineString data (trk > trkseg + trkseg),
918
- * just split them into seperate LineStrings (trk > trkseg, trk > trkseg)
919
- *
920
- * @link https://github.com/Raruto/leaflet-elevation/issues/56
921
- */
922
- _prepareMultiLineStrings: function(geojson) {
923
- geojson.features.forEach(feauture => {
924
- if (feauture.geometry.type == "MultiLineString") {
925
- feauture.geometry.coordinates.forEach(coords => {
926
- geojson.features.push({
927
- geometry: { type: 'LineString', coordinates: coords },
928
- properties: feauture.properties,
929
- type: 'Feature'
930
- });
931
- });
932
- }
933
- });
934
- return geojson.features.filter(feauture => feauture.geometry.type != "MultiLineString");
935
- },
936
-
937
936
  /**
938
937
  * Add chart profile to diagram
939
938
  */
@@ -967,22 +966,31 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
967
966
  * Base handler for iterative track statistics (dist, time, z, slope, speed, acceleration, ...)
968
967
  */
969
968
  _registerDataAttribute: function(props) {
970
- // save here a reference to last used point
971
- let lastValid = null;
972
969
 
973
970
  // parse of "coordinateProperties" for later usage
974
- if (props.init) {
975
- this.on("elepoint_init", (e) => props.init.call(this, e));
971
+ if (props.coordPropsToMeta) {
972
+ this.on("elepoint_init", (e) => props.coordPropsToMeta.call(this, e));
976
973
  }
977
974
 
975
+ // prevent excessive variabile instanstations
976
+ let i, curr, prev, attr;
977
+
978
+ // save here a reference to last used point
979
+ let lastValid = null;
980
+
978
981
  // iteration
979
- this.on("elepoint_added", (e) => {
982
+ this.on("elepoint_added", ({index, point}) => {
983
+ i = index;
984
+
985
+ prev = curr ?? this._data[i]; // same as: this._data[i > 0 ? i - 1 : i]
986
+ curr = this._data[i];
987
+ attr = props.attr || props.name;
980
988
 
981
989
  // check and fix missing data on last added point
982
990
  if (props.skipNull === false) {
983
- let curr = this._data[e.index][props.name];
984
- if(e.index > 0) {
985
- let prev = this._data[e.index - 1][props.name];
991
+ let curr = this._data[i][attr];
992
+ if(i > 0) {
993
+ let prev = this._data[i - 1][attr];
986
994
  if (isNaN(prev)) {
987
995
  if (!isNaN(lastValid) && !isNaN(curr)) {
988
996
  prev = (lastValid + curr) / 2;
@@ -991,8 +999,8 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
991
999
  } else if (!isNaN(curr)) {
992
1000
  prev = curr;
993
1001
  }
994
- if (!isNaN(prev)) return this._data.splice(e.index - 1, 1);
995
- this._data[e.index - 1][props.name] = prev;
1002
+ if (!isNaN(prev)) return this._data.splice(i - 1, 1);
1003
+ this._data[i - 1][attr] = prev;
996
1004
  }
997
1005
  }
998
1006
  // update reference to last used point (ie. if it has data)
@@ -1002,25 +1010,126 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
1002
1010
  }
1003
1011
 
1004
1012
  // retrieve point value
1005
- this._data[e.index][props.name] = props.fetch.call(this, e.index, e.point);
1013
+ curr[attr] = props.pointToAttr.call(this, point, i);
1014
+
1015
+ // update "track_info" stats (min, max, avg, ...)
1016
+ if (props.stats) {
1017
+ for (const key in props.stats) {
1018
+ let sname = (props.statsName || attr) + (key != '' ? '_' : '');
1019
+ this.track_info[sname + key] = props.stats[key].call(this, curr[attr], this.track_info[sname + key]);
1020
+ }
1021
+ }
1006
1022
 
1007
- // update here some mixins (eg. "track_info", value validators, ...)
1008
- if(props.update) this._data[e.index][props.name] = props.update.call(this, this._data[e.index][props.name], e.index, e.point)
1023
+ // Limit "crazy" delta values.
1024
+ if (props.deltaMax) {
1025
+ curr[attr] =_.wrapDelta(curr[attr], prev[attr], props.deltaMax);
1026
+ }
1027
+
1028
+ // Range of acceptable values.
1029
+ if (props.clampRange) {
1030
+ curr[attr] = _.clamp(curr[attr], props.clampRange);
1031
+ }
1032
+
1033
+ // Limit floating point precision.
1034
+ if (!isNaN(props.decimals)) {
1035
+ curr[attr] = _.round(curr[attr], props.decimals);
1036
+ }
1037
+
1038
+ // update here some mixins (eg. complex "track_info" stuff)
1039
+ if(props.onPointAdded) props.onPointAdded.call(this, curr[attr], i, point);
1009
1040
  });
1010
1041
  },
1042
+
1043
+ /**
1044
+ * Parse a module definition and attach related function listeners
1045
+ */
1046
+ _registerHandler: function(props) {
1047
+
1048
+ // eg. L.Control.Altitude
1049
+ if (typeof props === "function") {
1050
+ return this._registerHandler(props.call(this));
1051
+ }
1052
+
1053
+ let {
1054
+ name,
1055
+ attr,
1056
+ required,
1057
+ skipNull,
1058
+ deltaMax,
1059
+ clampRange,
1060
+ decimals,
1061
+ meta,
1062
+ unit,
1063
+ coordinateProperties,
1064
+ coordPropsToMeta,
1065
+ pointToAttr,
1066
+ onPointAdded,
1067
+ stats,
1068
+ statsName,
1069
+ grid,
1070
+ scale,
1071
+ path,
1072
+ tooltip,
1073
+ summary
1074
+ } = props;
1075
+
1076
+ // eg. "altitude" == true
1077
+ if (this.options[name] || required) {
1078
+
1079
+ this._registerDataAttribute({
1080
+ name,
1081
+ attr,
1082
+ skipNull,
1083
+ deltaMax,
1084
+ clampRange,
1085
+ decimals,
1086
+ coordPropsToMeta: _.coordPropsToMeta(coordinateProperties, meta || name, coordPropsToMeta),
1087
+ pointToAttr,
1088
+ onPointAdded,
1089
+ stats,
1090
+ statsName,
1091
+ });
1092
+
1093
+ if (grid) {
1094
+ this._registerAxisGrid(L.extend({ name }, grid));
1095
+ }
1096
+
1097
+ if (this.options[name] !== "summary") {
1098
+ if (scale) this._registerAxisScale(L.extend({ name, label: unit }, scale));
1099
+ if (path) this._registerAreaPath(L.extend({ name }, path));
1100
+ }
1101
+
1102
+ if (tooltip || props.tooltips) {
1103
+ _.each([tooltip, ...(props.tooltips || [])], t => t && this._registerTooltip(L.extend({ name }, t)));
1104
+ }
1105
+
1106
+ if (summary) {
1107
+ _.each(summary, (s, k) => summary[k] = L.extend({ unit }, s));
1108
+ this._registerSummary(summary);
1109
+ }
1110
+ }
1111
+ },
1112
+
1113
+ _registerMarker({latlng, sym, content}) {
1114
+ let { wptIcons } = this.options;
1115
+ // generate and cache appropriate icon symbol
1116
+ if (!wptIcons.hasOwnProperty(sym)) {
1117
+ wptIcons[sym] = L.divIcon(L.extend({}, wptIcons[""].options, { html: '<i class="elevation-waypoint-icon ' + sym + '"></i>' } ));
1118
+ }
1119
+ let marker = L.marker(latlng, { icon: wptIcons[sym] });
1120
+ if (content) {
1121
+ marker.bindPopup(content, { className: 'elevation-popup', keepInView: true }).openPopup();
1122
+ marker.bindTooltip(content, { className: 'elevation-tooltip', direction: 'auto', sticky: true, opacity: 1 }).openTooltip();
1123
+ }
1124
+ return this._addMarker(marker)
1125
+ },
1011
1126
 
1012
1127
  /**
1013
1128
  * Add chart or marker tooltip info
1014
1129
  */
1015
1130
  _registerTooltip: function(props) {
1016
- if (props.chart) {
1017
- let label = L.extend({}, props, { value: props.chart });
1018
- this.on("elechart_init", () => this._chart._registerTooltip(label));
1019
- }
1020
- if (props.marker) {
1021
- let label = L.extend({}, props, { value: props.marker });
1022
- this.on("elechart_marker", () => this._marker._registerTooltip(label));
1023
- }
1131
+ props.chart && this.on("elechart_init", () => this._chart._registerTooltip(L.extend({}, props, { value: props.chart })));
1132
+ props.marker && this.on("elechart_marker", () => this._marker._registerTooltip(L.extend({}, props, { value: props.marker })));
1024
1133
  },
1025
1134
 
1026
1135
  /**
@@ -1054,85 +1163,39 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
1054
1163
  * Hacky way for handling chart resize. Deletes it and redraw chart.
1055
1164
  */
1056
1165
  _resizeChart: function() {
1057
- if(!this._container) return;
1058
-
1059
- // prevent displaying chart on resize if hidden
1060
- if (_.style(this._container, "display") == "none") return;
1061
-
1062
- let opts = this.options;
1063
-
1064
- let newWidth;
1065
-
1066
- if (opts.detached) {
1067
- newWidth = (this.eleDiv || this._container).offsetWidth;
1068
- } else {
1069
- let { clientWidth } = this._map.getContainer();
1070
- newWidth = opts._maxWidth > clientWidth ? clientWidth - 30 : opts._maxWidth;
1071
- }
1072
-
1073
- if (newWidth) {
1074
- let chart = this._chart;
1075
- opts.width = newWidth;
1076
- if (chart && chart._chart) {
1077
- chart._chart._resize(opts);
1078
- opts.xTicks = this._xTicks();
1079
- opts.yTicks = this._yTicks();
1080
- this._updateChart();
1166
+ if (this._container && _.style(this._container, "display") != "none") {
1167
+ let opts = this.options;
1168
+ let newWidth = opts.detached ? (this.eleDiv || this._container).offsetWidth : _.clamp(opts._maxWidth, [0, this._map.getContainer().clientWidth - 30]);
1169
+ if (newWidth) {
1170
+ opts.width = newWidth;
1171
+ if (this._chart && this._chart._chart) {
1172
+ this._chart._chart._resize(opts);
1173
+ this._updateChart();
1174
+ }
1081
1175
  }
1176
+ this._updateMapSegments();
1082
1177
  }
1083
-
1084
- this._updateMapSegments();
1085
- },
1086
-
1087
- /**
1088
- * Handles the drag event over the ruler filter.
1089
- */
1090
- _rulerFilterHandler: function(e) {
1091
- this._updateMapSegments(e.coords);
1092
1178
  },
1093
1179
 
1094
1180
  /**
1095
1181
  * Collapse or Expand chart control.
1096
1182
  */
1097
1183
  _toggle: function() {
1098
- if (_.hasClass(this._container, "elevation-expanded"))
1099
- this._collapse();
1100
- else
1101
- this._expand();
1184
+ _.hasClass(this._container, "elevation-expanded") ? this._collapse() : this._expand();
1102
1185
  },
1103
1186
 
1104
1187
  /**
1105
1188
  * Update map center and zoom (followMarker: true)
1106
1189
  */
1107
1190
  _setMapView: function(item) {
1108
- if (!this.options.followMarker || !this._map) return;
1109
- let zoom = this._map.getZoom();
1110
- let z = this.options.zFollow;
1111
- if (typeof z === "number") {
1112
- this._map.setView(item.latlng, (zoom < z ? z : zoom), { animate: true, duration: 0.25 });
1113
- } else if (!this._map.getBounds().contains(item.latlng)) {
1114
- this._map.setView(item.latlng, zoom, { animate: true, duration: 0.25 });
1115
- }
1116
- },
1117
-
1118
- /**
1119
- * Toggle chart data on legend click
1120
- */
1121
- _toggleChartHandler: function(e) {
1122
- let { name, enabled } = e;
1123
-
1124
- this._chartEnabled = this._chart._hasActiveLayers();
1125
-
1126
- // toggle layer visibility on empty chart
1127
- this._layers.eachLayer(layer => _.toggleClass(layer.getElement && layer.getElement(), this.options.polyline.className + ' ' + this.options.theme, this._chartEnabled));
1128
-
1129
- // toggle option value (eg. altitude = { 'disabled' || 'disabled' })
1130
- this.options[name] = !enabled && this.options[name] == 'disabled' ? 'enabled' : 'disabled';
1131
-
1132
- // remove marker on empty chart
1133
- if (!this._chartEnabled) {
1134
- this._chart._hideDiagramIndicator();
1135
- this._marker.remove();
1191
+ if (this._map && this.options.followMarker) {
1192
+ let zoom = this._map.getZoom();
1193
+ let z = this.options.zFollow;
1194
+ if (typeof z === "number") {
1195
+ this._map.setView(item.latlng, (zoom < z ? z : zoom), { animate: true, duration: 0.25 });
1196
+ } else if (!this._map.getBounds().contains(item.latlng)) {
1197
+ this._map.setView(item.latlng, zoom, { animate: true, duration: 0.25 });
1198
+ }
1136
1199
  }
1137
1200
  },
1138
1201
 
@@ -1140,17 +1203,17 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
1140
1203
  * Calculates [x, y] domain and then update chart.
1141
1204
  */
1142
1205
  _updateChart: function() {
1143
- if (/*!this._data.length ||*/ !this._chart || !this._container) return;
1144
-
1145
- this.fire("elechart_axis");
1146
- this.fire("elechart_area");
1147
-
1148
- this._chart.update({ data: this._data, options: this.options });
1149
-
1150
- this._x = this._chart._x;
1151
- this._y = this._chart._y;
1152
-
1153
- this.fire('elechart_updated');
1206
+ if (this._chart && this._container) {
1207
+ this.fire("elechart_axis");
1208
+ this.fire("elechart_area");
1209
+
1210
+ this._chart.update({ data: this._data, options: this.options });
1211
+
1212
+ this._x = this._chart._x;
1213
+ this._y = this._chart._y;
1214
+
1215
+ this.fire('elechart_updated');
1216
+ }
1154
1217
  },
1155
1218
 
1156
1219
  /*
@@ -1161,7 +1224,7 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
1161
1224
  this._marker.update({
1162
1225
  map : this._map,
1163
1226
  item : item,
1164
- maxElevation: this.track_info.elevation_max || 0,
1227
+ yCoordMax : this._yCoordMax || 0,
1165
1228
  options : this.options
1166
1229
  });
1167
1230
  }
@@ -1190,6 +1253,8 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
1190
1253
  * Update chart summary.
1191
1254
  */
1192
1255
  _updateSummary: function() {
1256
+ if (!this._summary) return;
1257
+
1193
1258
  this._summary.reset();
1194
1259
 
1195
1260
  if (this.options.summary) {
@@ -1198,8 +1263,8 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
1198
1263
  }
1199
1264
  if (this.options.downloadLink && this._downloadURL) { // TODO: generate dynamically file content instead of using static file urls.
1200
1265
  this._summary._container.innerHTML += '<span class="download"><a href="#">' + L._('Download') + '</a></span>'
1201
- _.select('.download a', this._summary._container).onclick = (e) => {
1202
- e.preventDefault();
1266
+ _.select('.download a', this._summary._container).onclick = ({preventDefault}) => {
1267
+ preventDefault();
1203
1268
  let event = { downloadLink: this.options.downloadLink, confirm: _.saveFile.bind(this, this._downloadURL) };
1204
1269
  if (this.options.downloadLink == 'modal' && typeof CustomEvent === "function") {
1205
1270
  document.dispatchEvent(new CustomEvent("eletrack_download", { detail: event }));
@@ -1217,24 +1282,17 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
1217
1282
  */
1218
1283
  _width: function() {
1219
1284
  if (this._chart) return this._chart._width();
1220
- let opts = this.options;
1221
- return opts.width - opts.margins.left - opts.margins.right;
1285
+ const { width, margins } = this.options;
1286
+ return width - margins.left - margins.right;
1222
1287
  },
1223
1288
 
1224
1289
  /**
1225
- * Calculate chart xTicks
1290
+ * Calculates chart height.
1226
1291
  */
1227
- _xTicks: function() {
1228
- if (this.__xTicks) this.__xTicks = this.options.xTicks;
1229
- return this.__xTicks || Math.round(this._width() / 75);
1292
+ _height: function() {
1293
+ if (this._chart) return this._chart._height();
1294
+ const { height, margins } = this.options;
1295
+ return height - margins.top - margins.bottom;
1230
1296
  },
1231
1297
 
1232
- /**
1233
- * Calculate chart yTicks
1234
- */
1235
- _yTicks: function() {
1236
- if (this.__yTicks) this.__yTicks = this.options.yTicks;
1237
- return this.__yTicks || Math.round(this._height() / 30);
1238
- }
1239
-
1240
1298
  });