@raruto/leaflet-elevation 1.9.4 → 2.0.0

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.
Files changed (45) hide show
  1. package/README.md +8 -7
  2. package/dist/leaflet-elevation.css +51 -161
  3. package/dist/leaflet-elevation.js +1622 -3767
  4. package/dist/leaflet-elevation.js.map +1 -1
  5. package/dist/leaflet-elevation.min.css +1 -1
  6. package/dist/leaflet-elevation.min.js +1 -3
  7. package/dist/leaflet-elevation.min.js.map +1 -1
  8. package/images/checkered.png +0 -0
  9. package/libs/leaflet-distance-marker.js +17 -18
  10. package/libs/leaflet-distance-marker.min.js +1 -1
  11. package/libs/leaflet-gpxgroup.js +2 -2
  12. package/libs/leaflet-ruler.css +42 -0
  13. package/libs/leaflet-ruler.js +183 -0
  14. package/libs/leaflet-ruler.min.css +1 -0
  15. package/libs/leaflet-ruler.min.js +1 -0
  16. package/package.json +9 -11
  17. package/src/chart.js +156 -143
  18. package/src/components.js +55 -76
  19. package/src/control.js +503 -345
  20. package/src/handlers/acceleration.js +64 -0
  21. package/src/handlers/acceleration.min.js +1 -0
  22. package/src/handlers/altitude.js +76 -0
  23. package/src/handlers/altitude.min.js +1 -0
  24. package/src/handlers/cadence.js +54 -0
  25. package/src/handlers/distance.js +45 -0
  26. package/src/handlers/distance.min.js +1 -0
  27. package/src/handlers/heart.js +53 -0
  28. package/src/handlers/slope.js +79 -0
  29. package/src/handlers/slope.min.js +1 -0
  30. package/src/handlers/speed.js +66 -0
  31. package/src/handlers/speed.min.js +1 -0
  32. package/src/handlers/time.js +103 -0
  33. package/src/handlers/time.min.js +1 -0
  34. package/src/index.css +51 -161
  35. package/src/index.js +0 -24
  36. package/src/marker.js +11 -14
  37. package/src/options.js +20 -28
  38. package/src/summary.js +14 -4
  39. package/src/utils.js +74 -102
  40. package/src/acceleration.js +0 -108
  41. package/src/altitude.js +0 -120
  42. package/src/distance.js +0 -78
  43. package/src/slope.js +0 -113
  44. package/src/speed.js +0 -110
  45. package/src/time.js +0 -124
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,19 +7,23 @@ 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.4/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
+ __modulesFolder: '../src/handlers/',
18
+ __LCHART: '../src/chart.js',
19
+ __LMARKER: '../src/marker.js',
20
+ __LSUMMARY: '../src/summary.js',
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
28
  if (typeof layer === "undefined" && d.on) {
29
29
  layer = d;
@@ -52,10 +52,11 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
52
52
  * Reset data and display
53
53
  */
54
54
  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();
55
+ if (this._marker) this._marker.remove();
56
+ if (this._chart) this._clearChart();
57
+ if (this._layers) this._clearLayers(this._layers);
58
+ if (this._markers) this._clearLayers(this._markers);
59
+ if (this._circleMarkers) this._clearLayers(this._circleMarkers);
59
60
 
60
61
  this._data = [];
61
62
  this.track_info = {};
@@ -67,7 +68,7 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
67
68
 
68
69
  _clearChart: function() {
69
70
  if (this._events && this._events.elechart_updated) {
70
- this._events.elechart_updated.forEach(e => this.off('elechart_updated', e.fn, e.ctx));
71
+ this._events.elechart_updated.forEach(({fn, ctx}) => this.off('elechart_updated', fn, ctx));
71
72
  }
72
73
  if (this._chart && this._chart._container) {
73
74
  this._chart._container.selectAll('g.point .point').remove();
@@ -75,19 +76,29 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
75
76
  }
76
77
  },
77
78
 
78
- _clearLayers: function() {
79
- if (this._layers && this._layers.eachLayer) {
80
- this._layers.eachLayer(l => l.remove())
81
- this._layers.clearLayers();
79
+ _clearLayers: function(l) {
80
+ l = l || this._layers;
81
+ if (l && l.eachLayer) {
82
+ l.eachLayer(f => f.remove())
83
+ l.clearLayers();
82
84
  }
83
85
  },
84
86
 
85
- _clearMarkers: function() {
86
- if (this._markers && this._markers.eachLayer) {
87
- this._markers.eachLayer(l => l.remove())
88
- this._markers.clearLayers();
89
- }
90
- },
87
+ /**
88
+ * TODO: Create a base class to handle custom data attributes (heart rate, cadence, temperature, ...)
89
+ *
90
+ * @link https://leafletjs.com/examples/extending/extending-3-controls.html#handlers
91
+ */
92
+ // addHandler: function (name, HandlerClass) {
93
+ // if (HandlerClass) {
94
+ // let handler = this[name] = new HandlerClass(this);
95
+ // this.handlers.push(handler);
96
+ // if (this.options[name]) {
97
+ // handler.enable();
98
+ // }
99
+ // }
100
+ // return this;
101
+ // },
91
102
 
92
103
  /**
93
104
  * Disable chart brushing.
@@ -128,12 +139,11 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
128
139
  },
129
140
 
130
141
  getBounds: function(data) {
131
- data = data || this._data;
132
- return L.latLngBounds(data.map((d) => d.latlng));
142
+ return L.latLngBounds((data || this._data).map((d) => d.latlng));
133
143
  },
134
144
 
135
145
  /**
136
- * Get default zoom level when "followMarker" is true.
146
+ * Get default zoom level (followMarker: true).
137
147
  */
138
148
  getZFollow: function() {
139
149
  return this.options.zFollow;
@@ -149,66 +159,58 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
149
159
  /**
150
160
  * Initialize chart control "options" and "container".
151
161
  */
152
- initialize: function(options) {
162
+ initialize: function(opts) {
163
+
164
+ opts = L.setOptions(this, opts);
165
+
153
166
  this._data = [];
154
167
  this._layers = L.featureGroup();
155
168
  this._markers = L.featureGroup();
169
+ this._circleMarkers = L.featureGroup();
156
170
  this._markedSegments = L.polyline([]);
157
- this._chartEnabled = true,
171
+ this._start = (opts.trkStart || Options.trkStart);
172
+ this._end = (opts.trkEnd || Options.trkEnd);
173
+ this._chartEnabled = true;
174
+ this._yCoordMax = -Infinity;
158
175
  this.track_info = {};
176
+ // this.handlers = [];
159
177
 
160
- L.setOptions(this, options);
178
+ if (opts.followMarker) this._setMapView = _.throttle(this._setMapView, 300, this);
179
+ if (opts.legend) opts.margins.bottom += 30;
180
+ if (opts.theme) opts.polylineSegments.className += ' ' + opts.theme;
181
+ if (opts.wptIcons === true) opts.wptIcons = Options.wptIcons;
182
+ if (opts.distanceMarkers === true) opts.distanceMarkers = Options.distanceMarkers;
183
+ if (opts.trkStart) this._start.addTo(this._circleMarkers);
184
+ if (opts.trkEnd) this._end.addTo(this._circleMarkers);
161
185
 
162
- if (this.options.followMarker) this._setMapView = L.Util.throttle(this._setMapView, 300, this);
163
- if (this.options.legend) this.options.margins.bottom += 30;
164
- if (this.options.theme) this.options.polylineSegments.className += ' ' + this.options.theme;
165
- if (this.options.wptIcons === true) this.options.wptIcons = Options.wptIcons;
166
- if (this.options.distanceMarkers === true) this.options.distanceMarkers = Options.distanceMarkers;
167
186
 
168
- this._markedSegments.setStyle(this.options.polylineSegments);
187
+ this._markedSegments.setStyle(opts.polylineSegments);
169
188
 
170
189
  // Leaflet canvas renderer colors
171
- L.extend(D3.Colors, this.options.colors || {});
190
+ L.extend(_.Colors, opts.colors || {});
172
191
 
173
192
  // Various stuff
174
193
  this._fixCanvasPaths();
175
194
  this._fixTooltipSize();
195
+
176
196
  },
177
197
 
178
198
  /**
179
199
  * Javascript scripts downloader (lazy loader)
180
200
  */
181
- lazyLoad: function(src, condition = false, loader = '') {
182
- if (!this.options.lazyLoadJS) {
183
- return Promise.resolve();
184
- }
201
+ import: function(src, condition) {
185
202
  switch(src) {
186
- case this.__D3:
187
- loader = '_d3LazyLoader';
188
- condition = typeof d3 !== 'object';
189
- break;
190
- case this.__TOGEOJSON:
191
- loader = '_togeojsonLazyLoader';
192
- condition = typeof toGeoJSON !== 'object';
193
- break;
194
- case this.__LGEOMUTIL:
195
- loader = '_geomutilLazyLoader';
196
- condition = typeof L.GeometryUtil !== 'object';
197
- break;
198
- case this.__LALMOSTOVER:
199
- loader = '_almostoverLazyLoader';
200
- condition = typeof L.Handler.AlmostOver !== 'function';
201
- break;
202
- case this.__LDISTANCEM:
203
- loader = '_distanceMarkersLazyLoader';
204
- condition = typeof L.DistanceMarkers !== 'function';
205
- break;
203
+ case this.__D3: condition = typeof d3 !== 'object'; break;
204
+ case this.__TOGEOJSON: condition = typeof toGeoJSON !== 'object'; break;
205
+ case this.__LGEOMUTIL: condition = typeof L.GeometryUtil !== 'object'; break;
206
+ case this.__LALMOSTOVER: condition = typeof L.Handler.AlmostOver !== 'function'; break;
207
+ case this.__LDISTANCEM: condition = typeof L.DistanceMarkers !== 'function'; break;
206
208
  }
207
- return L.Control.Elevation[loader] = _.lazyLoader(src, condition, L.Control.Elevation[loader]);
209
+ return condition !== false ? import(src) : Promise.resolve();
208
210
  },
209
211
 
210
212
  /**
211
- * Load elevation data (GPX, GeoJSON or KML).
213
+ * Load elevation data (GPX, GeoJSON, KML or TCX).
212
214
  */
213
215
  load: function(data) {
214
216
  this._parseFromString(data).then( geojson => geojson ? this._loadLayer(geojson) : this._loadFile(data));
@@ -222,33 +224,23 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
222
224
  this._map = map;
223
225
 
224
226
  let container = this._container = _.create("div", "elevation-control elevation " + this.options.theme + " " + (this.options.detached ? '' : 'leaflet-control'));
225
-
226
- this.lazyLoad(this.__D3)
227
- .then(() => {
228
- this._initButton(container);
229
- this._initChart(container);
230
- this._initSummary(container);
231
- this._initMarker(map);
232
- this._initLayer(map);
233
227
 
234
- map
235
- .on('zoom viewreset zoomanim', this._hideMarker, this)
236
- .on('resize', this._resetView, this)
237
- .on('resize', this._resizeChart, this)
238
- .on('rotate', this._rotateMarker, this)
239
- .on('mousedown', this._resetDrag, this);
240
-
241
- _.on(map.getContainer(), 'mousewheel', this._resetDrag, this);
242
- _.on(map.getContainer(), 'touchstart', this._resetDrag, this);
243
- _.on(document, 'keydown', this._keydownHandler, this);
244
-
245
- this
246
- .on('eledata_added eledata_loaded', this._updateChart, this)
247
- .on('eledata_added eledata_loaded', this._updateSummary, this);
248
-
249
- this._updateChart();
250
- this._updateSummary();
251
- });
228
+ let modules = [
229
+ "Distance",
230
+ "Time",
231
+ "Altitude",
232
+ "Slope",
233
+ "Speed",
234
+ "Acceleration",
235
+ ...this.options.handlers
236
+ ].filter(m => false !== this.options[typeof m == "string" && m.toLowerCase()]);
237
+
238
+ this._loadModules(modules); // Inject here required modules (data handlers)
239
+ this._initButton(container);
240
+ this._initChart(container);
241
+ this._initSummary(container);
242
+ this._initMarker(map);
243
+ this._initLayer(map);
252
244
 
253
245
  return container;
254
246
  },
@@ -261,18 +253,18 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
261
253
  this._container = null;
262
254
 
263
255
  map
264
- .off('zoom viewreset zoomanim', this._hideMarker, this)
265
- .off('resize', this._resetView, this)
266
- .off('resize', this._resizeChart, this)
267
- .off('mousedown', this._resetDrag, this);
256
+ .off('zoom viewreset zoomanim', this._hideMarker, this)
257
+ .off('resize', this._resetView, this)
258
+ .off('resize', this._resizeChart, this)
259
+ .off('mousedown', this._resetDrag, this);
268
260
 
269
- _.off(map.getContainer(), 'mousewheel', this._resetDrag, this);
270
- _.off(map.getContainer(), 'touchstart', this._resetDrag, this);
271
- _.off(document, 'keydown', this._keydownHandler, this);
261
+ _.off(map.getContainer(), 'mousewheel', this._resetDrag, this);
262
+ _.off(map.getContainer(), 'touchstart', this._resetDrag, this);
263
+ _.off(document, 'keydown', this._onKeyDown, this);
272
264
 
273
265
  this
274
- .off('eledata_added eledata_loaded', this._updateChart, this)
275
- .off('eledata_added eledata_loaded', this._updateSummary, this);
266
+ .off('eledata_added eledata_loaded', this._updateChart, this)
267
+ .off('eledata_added eledata_loaded', this._updateSummary, this);
276
268
  },
277
269
 
278
270
  /**
@@ -283,7 +275,7 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
283
275
  },
284
276
 
285
277
  /**
286
- * Set default zoom level when "followMarker" is true.
278
+ * Set default zoom level (followMarker: true).
287
279
  */
288
280
  setZFollow: function(zoom) {
289
281
  this.options.zFollow = zoom;
@@ -319,7 +311,7 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
319
311
  break;
320
312
 
321
313
  default:
322
- console.warn('Unsopperted GeoJSON feature geometry type:' + geom.type);
314
+ return console.warn('Unsopperted GeoJSON feature geometry type:' + geom.type);
323
315
  }
324
316
  }
325
317
  if (d.type === "FeatureCollection") {
@@ -340,23 +332,15 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
340
332
  _addGeoJSONData: function(coords, properties) {
341
333
  coords.forEach((point, i) => {
342
334
 
343
- // Inspired by L.GPX layer properties
344
- point.meta = point.meta ?? { time: null, ele: null, hr: null, cad: null, atemp: null };
335
+ // GARMIN_EXTENSIONS = ["hr", "cad", "atemp", "wtemp", "depth", "course", "bearing"];
336
+ point.meta = point.meta ?? { time: null, ele: null };
345
337
 
346
- // "alt" property is generated inside "leaflet"
347
- if ("alt" in point) point.meta.ele = point.alt;
348
-
349
338
  // "coordinateProperties" property is generated inside "@tmcw/toGeoJSON"
350
- let props = (properties && properties.coordinateProperties) || properties;
351
- if (props) {
352
- if("coordTimes" in props) point.meta.time = new Date(Date.parse(props.coordTimes[i]));
353
- else if("times" in props) point.meta.time = new Date(Date.parse(props.times[i]));
354
- else if("time" in props) point.meta.time = new Date(Date.parse((typeof props.time === 'object' ? props.time[i] : props.time)));
355
- if ("heartRates" in props) point.meta.hr = parseInt(props.heartRates[i]);
356
- else if("heartRate" in props) point.meta.hr = parseInt((typeof props.heartRate === 'object' ? props.heartRate[i] : props.heartRate));
357
- else if("heart" in props) point.meta.hr = parseInt((typeof props.heart === 'object' ? props.heart[i] : props.heart));
358
- // TODO: ask to "@tmcw/toGeoJSON" for "cadence" and "temperature" implementation
359
- }
339
+ let props = (properties && properties.coordinateProperties) || properties;
340
+
341
+ point.prev = (attr) => (attr ? this._data[i > 0 ? i - 1 : 0][attr] : this._data[i > 0 ? i - 1 : 0]);
342
+
343
+ this.fire("elepoint_init", { point: point, props: props, id: i });
360
344
 
361
345
  this._addPoint(
362
346
  point.lat ?? point[1],
@@ -364,10 +348,12 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
364
348
  point.alt ?? point.meta.ele ?? point[2]
365
349
  );
366
350
 
367
- this._fireEvt("elepoint_added", { point: point, index: this._data.length - 1 });
351
+ this.fire("elepoint_added", { point: point, index: this._data.length - 1 });
352
+
353
+ if (this._yCoordMax < this._data[this._data.length - 1][this.options.yAttr]) this._yCoordMax = this._data[this._data.length - 1][this.options.yAttr];
368
354
  });
369
355
 
370
- this._fireEvt("eletrack_added", { coords: coords, index: this._data.length - 1 });
356
+ this.fire("eletrack_added", { coords: coords, index: this._data.length - 1 });
371
357
  },
372
358
 
373
359
  /*
@@ -385,14 +371,14 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
385
371
  latlng: L.latLng(x, y, z)
386
372
  });
387
373
 
388
- this._fireEvt("eledata_updated", { index: this._data.length - 1 });
374
+ this.fire("eledata_updated", { index: this._data.length - 1 });
389
375
  },
390
376
 
391
377
  _addLayer: function(layer) {
392
378
  if (layer) this._layers.addLayer(layer)
393
379
  // Postpone adding the distance markers (lazy: true)
394
380
  if (layer && this.options.distanceMarkers && this.options.distanceMarkers.lazy) {
395
- layer.on('add remove', (e) => L.DistanceMarkers && e.target instanceof L.Polyline && e.target[e.type + 'DistanceMarkers']());
381
+ layer.on('add remove', ({target, type}) => L.DistanceMarkers && target instanceof L.Polyline && target[type + 'DistanceMarkers']());
396
382
  }
397
383
  },
398
384
 
@@ -405,17 +391,18 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
405
391
  */
406
392
  _initAlmostOverHandler: function(map, layer) {
407
393
  if(!map || !this.options.almostOver) return;
408
- this.lazyLoad(this.__LGEOMUTIL)
409
- .then(() => this.lazyLoad(this.__LALMOSTOVER))
410
- .then(() => {
411
- map.addHandler('almostOver', L.Handler.AlmostOver)
412
- if (L.GeometryUtil && map.almostOver && map.almostOver.enabled()) {
413
- map.almostOver.addLayer(layer);
414
- map
415
- .on('almost:move', (e) => this._mousemoveLayerHandler(e))
416
- .on('almost:out', (e) => this._mouseoutHandler(e));
417
- }
418
- });
394
+ Promise.all([
395
+ this.import(this.__LGEOMUTIL),
396
+ this.import(this.__LALMOSTOVER)
397
+ ]).then(() => {
398
+ map.addHandler('almostOver', L.Handler.AlmostOver)
399
+ if (L.GeometryUtil && map.almostOver && map.almostOver.enabled()) {
400
+ map.almostOver.addLayer(layer);
401
+ map
402
+ .on('almost:move', (e) => this._onMouseMoveLayer(e))
403
+ .on('almost:out', (e) => this._onMouseOut(e));
404
+ }
405
+ });
419
406
  },
420
407
 
421
408
  /**
@@ -424,9 +411,10 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
424
411
  _initDistanceMarkers: function(map, layer) {
425
412
  if (!map) return;
426
413
  if (this.options.distanceMarkers) {
427
- this.lazyLoad(this.__LGEOMUTIL)
428
- .then(() => this.lazyLoad(this.__LDISTANCEM))
429
- .then(() => this.options.polyline && layer.addTo(map));
414
+ Promise.all([
415
+ this.import(this.__LGEOMUTIL),
416
+ this.import(this.__LDISTANCEM)
417
+ ]).then(() => this.options.polyline && layer.addTo(map) && this._circleMarkers.bringToFront());
430
418
  } else if (this.options.polyline) {
431
419
  layer.addTo(map);
432
420
  }
@@ -452,7 +440,12 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
452
440
  */
453
441
  _initMapIntegrations: function(control, layer) {
454
442
  let map = control._map;
455
- if (control._map) {
443
+ if (control._data.length) {
444
+ control._start.setLatLng(control._data[0].latlng);
445
+ control._end.setLatLng(control._data[control._data.length -1].latlng);
446
+ }
447
+ if (map) {
448
+ control._circleMarkers.addTo(map);
456
449
  map.once('layeradd', (e) => control.options.autofitBounds && control.fitBounds(layer.getBounds()));
457
450
  if (!L.Browser.mobile) {
458
451
  control._initAlmostOverHandler(map, layer);
@@ -485,12 +478,14 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
485
478
  _fixCanvasPaths: function() {
486
479
  let oldProto = L.Canvas.prototype._fillStroke;
487
480
  let control = this;
481
+
482
+ let theme = this.options.theme.replace('-theme', '');
483
+ let color = _.Colors[theme] || {};
484
+
488
485
  L.Canvas.include({
489
486
  _fillStroke: function(ctx, layer) {
490
487
  if (control._layers.hasLayer(layer)) {
491
488
 
492
- let theme = control.options.theme.replace('-theme', '');
493
- let color = D3.Colors[theme] || {};
494
489
  let options = layer.options;
495
490
 
496
491
  options.color = color.line || color.area || theme;
@@ -521,12 +516,12 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
521
516
  */
522
517
  _fixTooltipSize: function() {
523
518
  this.on('elechart_init', () =>
524
- this.once('elechart_change elechart_hover', e => {
519
+ this.once('elechart_change elechart_hover', ({data, xCoord}) => {
525
520
  if (this._chartEnabled) {
526
- this._chart._showDiagramIndicator(e.data, e.xCoord);
527
- this._chart._showDiagramIndicator(e.data, e.xCoord);
521
+ this._chart._showDiagramIndicator(data, xCoord);
522
+ this._chart._showDiagramIndicator(data, xCoord);
528
523
  }
529
- this._updateMarker(e.data);
524
+ this._updateMarker(data);
530
525
  })
531
526
  );
532
527
  },
@@ -553,15 +548,6 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
553
548
  if (this._map) this._map.fire(type, data, propagate);
554
549
  },
555
550
 
556
- /**
557
- * Calculates chart height.
558
- */
559
- _height: function() {
560
- if (this._chart) return this._chart._height();
561
- let opts = this.options;
562
- return opts.height - opts.margins.top - opts.margins.bottom;
563
- },
564
-
565
551
  /*
566
552
  * Hides the position/height indicator marker drawn onto the map
567
553
  */
@@ -572,14 +558,11 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
572
558
  },
573
559
 
574
560
  /**
575
- * Generate "svg" chart DOM element.
561
+ * Generate "svg" chart (DOM element).
576
562
  */
577
563
  _initChart: function(container) {
578
564
  let opts = this.options;
579
565
 
580
- opts.xTicks = this._xTicks();
581
- opts.yTicks = this._yTicks();
582
-
583
566
  if (opts.detached) {
584
567
  let { offsetWidth, offsetHeight} = this.eleDiv;
585
568
  if (offsetWidth > 0) opts.width = offsetWidth;
@@ -591,41 +574,66 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
591
574
  if (opts._maxWidth > clientWidth) opts.width = clientWidth - 30;
592
575
  }
593
576
 
594
- let chart = this._chart = new Chart(opts, this);
595
-
596
- this._x = this._chart._x;
597
- this._y = this._chart._y;
598
-
599
- d3
600
- .select(container)
601
- .call(chart.render())
602
-
603
- chart
604
- .on('reset_drag', this._hideMarker, this)
605
- .on('mouse_enter', this._fireEvt.bind('elechart_enter'), this)
606
- .on('dragged', this._dragendHandler, this)
607
- .on('mouse_move', this._mousemoveHandler, this)
608
- .on('mouse_out', this._mouseoutHandler, this)
609
- .on('ruler_filter', this._rulerFilterHandler, this)
610
- .on('zoom', this._updateChart, this)
611
- .on('elepath_toggle', this._toggleChartHandler, this)
612
- .on('margins_updated',this._resizeChart, this);
613
-
614
-
615
- this._fireEvt("elechart_axis");
616
- if (this.options.legend) this._fireEvt("elechart_legend");
577
+ Promise.all([
578
+ this.import(this.__D3),
579
+ this.import(this.__LCHART)
580
+ ]).then((m) => {
581
+
582
+ let chart = this._chart = new (m[1] || Elevation).Chart(opts, this);
583
+
584
+ this._x = this._chart._x;
585
+ this._y = this._chart._y;
586
+
587
+ d3
588
+ .select(container)
589
+ .call(chart.render())
590
+
591
+ chart
592
+ .on('reset_drag', this._hideMarker, this)
593
+ .on('mouse_enter', this._onMouseEnter, this)
594
+ .on('dragged', this._onDragEnd, this)
595
+ .on('mouse_move', this._onMouseMove, this)
596
+ .on('mouse_out', this._onMouseOut, this)
597
+ .on('ruler_filter', this._onRulerFilter, this)
598
+ .on('zoom', this._updateChart, this)
599
+ .on('elepath_toggle', this._onToggleChart, this)
600
+ .on('margins_updated', this._resizeChart, this);
601
+
602
+
603
+ this.fire("elechart_axis");
604
+
605
+ if (this.options.legend) this.fire("elechart_legend");
606
+
607
+ this.fire("elechart_init");
608
+
609
+ map
610
+ .on('zoom viewreset zoomanim', this._hideMarker, this)
611
+ .on('resize', this._resetView, this)
612
+ .on('resize', this._resizeChart, this)
613
+ .on('rotate', this._rotateMarker, this)
614
+ .on('mousedown', this._resetDrag, this);
615
+
616
+ _.on(map.getContainer(), 'mousewheel', this._resetDrag, this);
617
+ _.on(map.getContainer(), 'touchstart', this._resetDrag, this);
618
+ _.on(document, 'keydown', this._onKeyDown, this);
619
+
620
+ this
621
+ .on('eledata_added eledata_loaded', this._updateChart, this)
622
+ .on('eledata_added eledata_loaded', this._updateSummary, this);
623
+
624
+ this._updateChart();
625
+ this._updateSummary();
626
+ });
617
627
 
618
- this._fireEvt("elechart_init");
619
628
  },
620
629
 
621
630
  _initLayer: function() {
622
631
  this._layers
623
- .on('layeradd layerremove', (e) => {
624
- let layer = e.layer
632
+ .on('layeradd layerremove', ({layer, type}) => {
625
633
  let node = layer.getElement && layer.getElement();
626
- _.toggleClass(node, this.options.polyline.className + ' ' + this.options.theme, e.type == 'layeradd');
627
- _.toggleEvent(layer, "mousemove", this._mousemoveLayerHandler.bind(this), e.type == 'layeradd')
628
- _.toggleEvent(layer, "mouseout", this._mouseoutHandler.bind(this), e.type == 'layeradd');
634
+ _.toggleClass(node, this.options.polyline.className + ' ' + this.options.theme, type == 'layeradd');
635
+ _.toggleEvent(layer, "mousemove", this._onMouseMoveLayer.bind(this), type == 'layeradd')
636
+ _.toggleEvent(layer, "mouseout", this._onMouseOut.bind(this), type == 'layeradd');
629
637
  });
630
638
  },
631
639
 
@@ -639,9 +647,14 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
639
647
 
640
648
  if (this._renderer) this._renderer.remove()
641
649
  this._renderer = L.svg({ pane: "elevationPane" }).addTo(this._map); // default leaflet svg renderer
642
- this._marker = new Marker(this.options);
643
650
 
644
- this._fireEvt("elechart_marker");
651
+ Promise.all([
652
+ this.import(this.__D3),
653
+ this.import(this.__LMARKER)
654
+ ]).then((m) => {
655
+ this._marker = new (m[1] || Elevation).Marker(this.options, this);
656
+ this.fire("elechart_marker");
657
+ });
645
658
  },
646
659
 
647
660
  /**
@@ -676,15 +689,13 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
676
689
  },
677
690
 
678
691
  _initSummary: function(container) {
679
- this._summary = new Summary({ summary: this.options.summary }, this);
680
-
681
- d3.select(container).call(this._summary.render());
682
- },
692
+ this.import(this.__LSUMMARY).then((m)=>{
693
+ this._summary = new (m || Elevation).Summary({ summary: this.options.summary }, this);
683
694
 
684
- _keydownHandler: function(e) {
685
- if(!this.options.detached && e.key === "Escape"){
686
- this._collapse()
687
- };
695
+ this.on('elechart_init', () => {
696
+ d3.select(container).call(this._summary.render());
697
+ });
698
+ });
688
699
  },
689
700
 
690
701
  /**
@@ -700,6 +711,31 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
700
711
  }).catch((err) => console.warn(err));
701
712
  },
702
713
 
714
+ /**
715
+ * Dynamically import only required javascript modules (code splitting)
716
+ */
717
+ _loadModules: function(handlers) {
718
+ // First map known classnames (eg. "Altitude" --> L.Control.Elevation.Altitude)
719
+ handlers = handlers.map((h) => typeof h === 'string' && typeof L.Control.Elevation[h] !== "undefined" ? L.Control.Elevation[h] : h);
720
+ // Then load optional classes and custom imports (eg. "Cadence" --> import('../src/handlers/cadence.js'))
721
+ Promise.
722
+ all(
723
+ handlers
724
+ .filter(h => typeof h === 'string' || h instanceof Promise)
725
+ .map(file => file instanceof Promise ? file : this.import(this.__modulesFolder + file.toLowerCase() + '.js'))
726
+ )
727
+ .then((m) => {
728
+ _.each(m, (exported) => {
729
+ let fn = Object.keys(exported)[0];
730
+ if (fn && typeof L.Control.Elevation[fn] === "undefined" ) {
731
+ handlers[fn] = L.Control.Elevation[fn] = exported[fn];
732
+
733
+ }
734
+ });
735
+ _.each(handlers, h => this._registerHandler(h));
736
+ });
737
+ },
738
+
703
739
  /**
704
740
  * Simple GeoJSON data loader (L.GeoJSON).
705
741
  */
@@ -760,71 +796,78 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
760
796
  },
761
797
  });
762
798
 
763
- control.lazyLoad(control.__D3).then(() => {
799
+ control.import(control.__D3).then(() => {
764
800
  control._initMapIntegrations(control, layer);
765
- control._fireEvt("eledata_loaded", { data: geojson, layer: layer, name: control.track_info.name, track_info: control.track_info })
801
+ control._fireEvt("eledata_loaded", { data: geojson, layer: layer, name: control.track_info.name, track_info: control.track_info });
766
802
  });
767
803
 
768
804
  return layer;
769
805
  },
770
806
 
771
- _dragendHandler: function(e) {
807
+ _onDragEnd: function({ dragstart, dragend}) {
772
808
  this._hideMarker();
773
- this.fitBounds(L.latLngBounds([e.dragstart.latlng, e.dragend.latlng]));
809
+ this.fitBounds(L.latLngBounds([dragstart.latlng, dragend.latlng]));
774
810
 
775
- this._fireEvt("elechart_dragged");
811
+ this.fire("elechart_dragged");
812
+ },
813
+
814
+ _onKeyDown: function({key}) {
815
+ if(!this.options.detached && key === "Escape"){
816
+ this._collapse()
817
+ };
818
+ },
819
+
820
+ /**
821
+ * Trigger mouseenter event.
822
+ */
823
+ _onMouseEnter: function() {
824
+ this.fire('elechart_enter');
776
825
  },
777
826
 
778
827
  /*
779
828
  * Handles the moueseover the chart and displays distance and altitude level.
780
829
  */
781
- _mousemoveHandler: function(e) {
782
- if (!this._data.length || !this._chartEnabled) {
783
- return;
784
- }
785
- let item = this._findItemForX(e.xCoord);
786
-
787
- if (item) {
788
- let xCoord = e.xCoord;
830
+ _onMouseMove: function({xCoord}) {
831
+ if (this._chartEnabled && this._data.length) {
832
+ let item = this._findItemForX(xCoord);
833
+ if (item) {
834
+ if (this._chartEnabled) this._chart._showDiagramIndicator(item, xCoord);
789
835
 
790
- if (this._chartEnabled) this._chart._showDiagramIndicator(item, xCoord);
836
+ this._updateMarker(item);
837
+ this._setMapView(item);
791
838
 
792
- this._updateMarker(item);
793
- this._setMapView(item);
839
+ if (this._map) {
840
+ _.addClass(this._map.getContainer(), 'elechart-hover');
841
+ }
794
842
 
795
- if (this._map) {
796
- _.addClass(this._map.getContainer(), 'elechart-hover');
843
+ this.fire("elechart_change", { data: item, xCoord: xCoord });
844
+ this.fire("elechart_hover", { data: item, xCoord: xCoord });
797
845
  }
798
-
799
- this._fireEvt("elechart_change", { data: item, xCoord: xCoord });
800
- this._fireEvt("elechart_hover", { data: item, xCoord: xCoord });
801
846
  }
802
847
  },
803
848
 
804
849
  /*
805
850
  * Handles mouseover events of the data layers on the map.
806
851
  */
807
- _mousemoveLayerHandler: function(e) {
808
- if (!this._data.length) {
809
- return;
810
- }
811
-
812
- let item = this._findItemForLatLng(e.latlng);
813
- if (item) {
814
- let xCoord = item.xDiagCoord;
815
-
816
- if (this._chartEnabled) this._chart._showDiagramIndicator(item, xCoord);
817
-
818
- this._updateMarker(item);
819
-
820
- this._fireEvt("elechart_change", { data: item, xCoord: xCoord });
852
+ _onMouseMoveLayer: function({latlng}) {
853
+ if (this._data.length) {
854
+ let item = this._findItemForLatLng(latlng);
855
+ if (item) {
856
+ let xCoord = item.xDiagCoord;
857
+
858
+ if (this._chartEnabled) this._chart._showDiagramIndicator(item, xCoord);
859
+
860
+ this._updateMarker(item);
861
+
862
+ this.fire("elechart_change", { data: item, xCoord: xCoord });
863
+ }
821
864
  }
822
865
  },
823
866
 
824
867
  /*
825
868
  * Handles the moueseout over the chart.
826
869
  */
827
- _mouseoutHandler: function() {
870
+ _onMouseOut: function() {
828
871
  if (!this.options.detached) {
829
872
  this._hideMarker();
830
873
  this._chart._hideDiagramIndicator();
@@ -834,7 +877,34 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
834
877
  _.removeClass(this._map.getContainer(), 'elechart-hover');
835
878
  }
836
879
 
837
- this._fireEvt("elechart_leave");
880
+ this.fire("elechart_leave");
881
+ },
882
+
883
+ /**
884
+ * Handles the drag event over the ruler filter.
885
+ */
886
+ _onRulerFilter: function({coords}) {
887
+ this._updateMapSegments(coords);
888
+ },
889
+
890
+ /**
891
+ * Toggle chart data on legend click
892
+ */
893
+ _onToggleChart: function({ name, enabled }) {
894
+
895
+ this._chartEnabled = this._chart._hasActiveLayers();
896
+
897
+ // toggle layer visibility on empty chart
898
+ this._layers.eachLayer(layer => _.toggleClass(layer.getElement && layer.getElement(), this.options.polyline.className + ' ' + this.options.theme, this._chartEnabled));
899
+
900
+ // toggle option value (eg. altitude = { 'disabled' || 'enabled' })
901
+ this.options[name] = !enabled && this.options[name] == 'disabled' ? 'enabled' : 'disabled';
902
+
903
+ // remove marker on empty chart
904
+ if (!this._chartEnabled) {
905
+ this._chart._hideDiagramIndicator();
906
+ this._marker.remove();
907
+ }
838
908
  },
839
909
 
840
910
  /**
@@ -851,7 +921,7 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
851
921
  */
852
922
  _parseFromString: function(data) {
853
923
  return new Promise(resolve =>
854
- this.lazyLoad(this.__TOGEOJSON).then(() => {
924
+ this.import(this.__TOGEOJSON).then(() => {
855
925
  let geojson;
856
926
  try {
857
927
  geojson = this._parseFromXMLString(data.trim());
@@ -919,13 +989,6 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
919
989
  return geojson.features.filter(feauture => feauture.geometry.type != "MultiLineString");
920
990
  },
921
991
 
922
- /**
923
- * Add a point of interest over the diagram
924
- */
925
- _registerCheckPoint: function(props) {
926
- this.on("elechart_updated", () => this._chart._registerCheckPoint(props));
927
- },
928
-
929
992
  /**
930
993
  * Add chart profile to diagram
931
994
  */
@@ -948,18 +1011,163 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
948
1011
  },
949
1012
 
950
1013
  /**
951
- * Add chart or marker tooltip info
1014
+ * Add a point of interest over the diagram
952
1015
  */
953
- _registerTooltip: function(props) {
954
- if (props.chart) {
955
- let label = L.extend({}, props, { value: props.chart });
956
- this.on("elechart_init", () => this._chart._registerTooltip(label));
1016
+ _registerCheckPoint: function(props) {
1017
+ this.on("elechart_updated", () => this._chart._registerCheckPoint(props));
1018
+ },
1019
+
1020
+
1021
+ /**
1022
+ * Base handler for iterative track statistics (dist, time, z, slope, speed, acceleration, ...)
1023
+ */
1024
+ _registerDataAttribute: function(props) {
1025
+
1026
+ // parse of "coordinateProperties" for later usage
1027
+ if (props.coordPropsToMeta) {
1028
+ this.on("elepoint_init", (e) => props.coordPropsToMeta.call(this, e));
1029
+ }
1030
+
1031
+ // prevent excessive variabile instanstations
1032
+ let i, curr, prev, attr;
1033
+
1034
+ // save here a reference to last used point
1035
+ let lastValid = null;
1036
+
1037
+ // iteration
1038
+ this.on("elepoint_added", ({index, point}) => {
1039
+ i = index;
1040
+
1041
+ prev = curr ?? this._data[i]; // same as: this._data[i > 0 ? i - 1 : i]
1042
+ curr = this._data[i];
1043
+ attr = props.attr || props.name;
1044
+
1045
+ // check and fix missing data on last added point
1046
+ if (props.skipNull === false) {
1047
+ let curr = this._data[i][attr];
1048
+ if(i > 0) {
1049
+ let prev = this._data[i - 1][attr];
1050
+ if (isNaN(prev)) {
1051
+ if (!isNaN(lastValid) && !isNaN(curr)) {
1052
+ prev = (lastValid + curr) / 2;
1053
+ } else if (!isNaN(lastValid)) {
1054
+ prev = lastValid;
1055
+ } else if (!isNaN(curr)) {
1056
+ prev = curr;
1057
+ }
1058
+ if (!isNaN(prev)) return this._data.splice(i - 1, 1);
1059
+ this._data[i - 1][attr] = prev;
1060
+ }
1061
+ }
1062
+ // update reference to last used point (ie. if it has data)
1063
+ if (!isNaN(curr)) {
1064
+ lastValid = curr;
1065
+ }
1066
+ }
1067
+
1068
+ // retrieve point value
1069
+ curr[attr] = props.pointToAttr.call(this, point, i);
1070
+
1071
+ // update "track_info" stats (min, max, avg, ...)
1072
+ if (props.stats) {
1073
+ for (const key in props.stats) {
1074
+ let sname = (props.statsName || attr) + (key != '' ? '_' : '');
1075
+ this.track_info[sname + key] = props.stats[key].call(this, curr[attr], this.track_info[sname + key]);
1076
+ }
1077
+ }
1078
+
1079
+ // Limit "crazy" delta values.
1080
+ if (props.deltaMax) {
1081
+ curr[attr] =_.wrapDelta(curr[attr], prev[attr], props.deltaMax);
1082
+ }
1083
+
1084
+ // Range of acceptable values.
1085
+ if (props.clampRange) {
1086
+ curr[attr] = _.clamp(curr[attr], props.clampRange);
1087
+ }
1088
+
1089
+ // Limit floating point precision.
1090
+ if (!isNaN(props.decimals)) {
1091
+ curr[attr] = _.round(curr[attr], props.decimals);
1092
+ }
1093
+
1094
+ // update here some mixins (eg. complex "track_info" stuff)
1095
+ if(props.onPointAdded) props.onPointAdded.call(this, curr[attr], i, point);
1096
+ });
1097
+ },
1098
+
1099
+ /**
1100
+ * Parse a module definition and attach related function listeners
1101
+ */
1102
+ _registerHandler: function(props) {
1103
+
1104
+ // eg. L.Control.Altitude
1105
+ if (typeof props === "function") {
1106
+ return this._registerHandler(props.call(this));
957
1107
  }
958
- if (props.marker) {
959
- let label = L.extend({}, props, { value: props.marker });
960
- this.on("elechart_marker", () => this._marker._registerTooltip(label));
1108
+
1109
+ // eg. "altitude" == true
1110
+ if (this.options[props.name] || props.required) {
1111
+
1112
+ this._registerDataAttribute({
1113
+ name: props.name,
1114
+ attr: props.attr,
1115
+ skipNull: props.skipNull,
1116
+ deltaMax: props.deltaMax,
1117
+ clampRange: props.clampRange,
1118
+ decimals: props.decimals,
1119
+ coordPropsToMeta: _.coordPropsToMeta(props.coordinateProperties, props.meta || props.name, props.coordPropsToMeta),
1120
+ pointToAttr: props.pointToAttr,
1121
+ onPointAdded: props.onPointAdded,
1122
+ stats: props.stats,
1123
+ statsName: props.statsName,
1124
+ });
1125
+
1126
+ if (props.grid) {
1127
+ props.grid.name = props.grid.name || props.name;
1128
+ this._registerAxisGrid(props.grid);
1129
+ }
1130
+
1131
+ if (this.options[props.name] !== "summary") {
1132
+ if (props.scale) {
1133
+ props.scale.name = props.scale.name || props.name;
1134
+ props.scale.label = props.scale.label || props.unit;
1135
+ this._registerAxisScale(props.scale);
1136
+ }
1137
+ if (props.path) {
1138
+ props.path.name = props.path.name || props.name;
1139
+ this._registerAreaPath(props.path);
1140
+ }
1141
+ }
1142
+
1143
+ if (props.tooltips) {
1144
+ _.each(props.tooltips, t => {
1145
+ if (t) {
1146
+ t.name = t.name || props.name;
1147
+ this._registerTooltip(t)
1148
+ }
1149
+ });
1150
+ } else if (props.tooltip) {
1151
+ props.tooltip.name = props.tooltip.name || props.name;
1152
+ this._registerTooltip(props.tooltip)
1153
+ }
1154
+
1155
+ if (props.summary) {
1156
+ for (const key in props.summary) {
1157
+ props.summary[key].unit = props.summary[key].unit || props.unit;
1158
+ }
1159
+ this._registerSummary(props.summary);
1160
+ }
961
1161
  }
962
1162
  },
1163
+
1164
+ /**
1165
+ * Add chart or marker tooltip info
1166
+ */
1167
+ _registerTooltip: function(props) {
1168
+ props.chart && this.on("elechart_init", () => this._chart._registerTooltip(L.extend({}, props, { value: props.chart })));
1169
+ props.marker && this.on("elechart_marker", () => this._marker._registerTooltip(L.extend({}, props, { value: props.marker })));
1170
+ },
963
1171
 
964
1172
  /**
965
1173
  * Add summary info to diagram
@@ -992,85 +1200,39 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
992
1200
  * Hacky way for handling chart resize. Deletes it and redraw chart.
993
1201
  */
994
1202
  _resizeChart: function() {
995
- if(!this._container) return;
996
-
997
- // prevent displaying chart on resize if hidden
998
- if (_.style(this._container, "display") == "none") return;
999
-
1000
- let opts = this.options;
1001
-
1002
- let newWidth;
1003
-
1004
- if (opts.detached) {
1005
- newWidth = (this.eleDiv || this._container).offsetWidth;
1006
- } else {
1007
- let { clientWidth } = this._map.getContainer();
1008
- newWidth = opts._maxWidth > clientWidth ? clientWidth - 30 : opts._maxWidth;
1009
- }
1010
-
1011
- if (newWidth) {
1012
- let chart = this._chart;
1013
- opts.width = newWidth;
1014
- if (chart && chart._chart) {
1015
- chart._chart._resize(opts);
1016
- opts.xTicks = this._xTicks();
1017
- opts.yTicks = this._yTicks();
1018
- this._updateChart();
1203
+ if (this._container && _.style(this._container, "display") != "none") {
1204
+ let opts = this.options;
1205
+ let newWidth = opts.detached ? (this.eleDiv || this._container).offsetWidth : _.clamp(opts._maxWidth, [0, this._map.getContainer().clientWidth - 30]);
1206
+ if (newWidth) {
1207
+ opts.width = newWidth;
1208
+ if (this._chart && this._chart._chart) {
1209
+ this._chart._chart._resize(opts);
1210
+ this._updateChart();
1211
+ }
1019
1212
  }
1213
+ this._updateMapSegments();
1020
1214
  }
1021
-
1022
- this._updateMapSegments();
1023
- },
1024
-
1025
- /**
1026
- * Handles the drag event over the ruler filter.
1027
- */
1028
- _rulerFilterHandler: function(e) {
1029
- this._updateMapSegments(e.coords);
1030
1215
  },
1031
1216
 
1032
1217
  /**
1033
- * Collapse or Expand current chart control.
1218
+ * Collapse or Expand chart control.
1034
1219
  */
1035
1220
  _toggle: function() {
1036
- if (_.hasClass(this._container, "elevation-expanded"))
1037
- this._collapse();
1038
- else
1039
- this._expand();
1221
+ _.hasClass(this._container, "elevation-expanded") ? this._collapse() : this._expand();
1040
1222
  },
1041
1223
 
1042
1224
  /**
1043
- * Sets the view of the map (center and zoom). Useful when "followMarker" is true.
1225
+ * Update map center and zoom (followMarker: true)
1044
1226
  */
1045
1227
  _setMapView: function(item) {
1046
- if (!this.options.followMarker || !this._map) return;
1047
- let zoom = this._map.getZoom();
1048
- let z = this.options.zFollow;
1049
- if (typeof z === "number") {
1050
- this._map.setView(item.latlng, (zoom < z ? z : zoom), { animate: true, duration: 0.25 });
1051
- } else if (!this._map.getBounds().contains(item.latlng)) {
1052
- this._map.setView(item.latlng, zoom, { animate: true, duration: 0.25 });
1053
- }
1054
- },
1055
-
1056
- /**
1057
- * Toggle chart data on legend click
1058
- */
1059
- _toggleChartHandler: function(e) {
1060
- let { name, enabled } = e;
1061
-
1062
- this._chartEnabled = this._chart._hasActiveLayers();
1063
-
1064
- // toggle layer visibility on empty chart
1065
- this._layers.eachLayer(layer => _.toggleClass(layer.getElement && layer.getElement(), this.options.polyline.className + ' ' + this.options.theme, this._chartEnabled));
1066
-
1067
- // toggle option value (eg. altitude = { 'disabled' || 'disabled' })
1068
- this.options[name] = !enabled && this.options[name] == 'disabled' ? 'enabled' : 'disabled';
1069
-
1070
- // remove marker on empty chart
1071
- if (!this._chartEnabled) {
1072
- this._chart._hideDiagramIndicator();
1073
- this._marker.remove();
1228
+ if (this._map && this.options.followMarker) {
1229
+ let zoom = this._map.getZoom();
1230
+ let z = this.options.zFollow;
1231
+ if (typeof z === "number") {
1232
+ this._map.setView(item.latlng, (zoom < z ? z : zoom), { animate: true, duration: 0.25 });
1233
+ } else if (!this._map.getBounds().contains(item.latlng)) {
1234
+ this._map.setView(item.latlng, zoom, { animate: true, duration: 0.25 });
1235
+ }
1074
1236
  }
1075
1237
  },
1076
1238
 
@@ -1078,17 +1240,17 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
1078
1240
  * Calculates [x, y] domain and then update chart.
1079
1241
  */
1080
1242
  _updateChart: function() {
1081
- if (/*!this._data.length ||*/ !this._chart || !this._container) return;
1082
-
1083
- this._fireEvt("elechart_axis");
1084
- this._fireEvt("elechart_area");
1085
-
1086
- this._chart.update({ data: this._data, options: this.options });
1087
-
1088
- this._x = this._chart._x;
1089
- this._y = this._chart._y;
1090
-
1091
- this._fireEvt('elechart_updated');
1243
+ if (this._chart && this._container) {
1244
+ this.fire("elechart_axis");
1245
+ this.fire("elechart_area");
1246
+
1247
+ this._chart.update({ data: this._data, options: this.options });
1248
+
1249
+ this._x = this._chart._x;
1250
+ this._y = this._chart._y;
1251
+
1252
+ this.fire('elechart_updated');
1253
+ }
1092
1254
  },
1093
1255
 
1094
1256
  /*
@@ -1099,7 +1261,7 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
1099
1261
  this._marker.update({
1100
1262
  map : this._map,
1101
1263
  item : item,
1102
- maxElevation: this.track_info.elevation_max || 0,
1264
+ yCoordMax : this._yCoordMax || 0,
1103
1265
  options : this.options
1104
1266
  });
1105
1267
  }
@@ -1128,22 +1290,25 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
1128
1290
  * Update chart summary.
1129
1291
  */
1130
1292
  _updateSummary: function() {
1293
+ if (!this._summary) return;
1294
+
1131
1295
  this._summary.reset();
1132
1296
 
1133
1297
  if (this.options.summary) {
1134
- this._fireEvt("elechart_summary");
1298
+ this.fire("elechart_summary");
1299
+ this._summary.update();
1135
1300
  }
1136
1301
  if (this.options.downloadLink && this._downloadURL) { // TODO: generate dynamically file content instead of using static file urls.
1137
1302
  this._summary._container.innerHTML += '<span class="download"><a href="#">' + L._('Download') + '</a></span>'
1138
- _.select('.download a', this._summary._container).onclick = (e) => {
1139
- e.preventDefault();
1303
+ _.select('.download a', this._summary._container).onclick = ({preventDefault}) => {
1304
+ preventDefault();
1140
1305
  let event = { downloadLink: this.options.downloadLink, confirm: _.saveFile.bind(this, this._downloadURL) };
1141
1306
  if (this.options.downloadLink == 'modal' && typeof CustomEvent === "function") {
1142
1307
  document.dispatchEvent(new CustomEvent("eletrack_download", { detail: event }));
1143
1308
  } else if (this.options.downloadLink == 'link' || this.options.downloadLink === true) {
1144
1309
  event.confirm();
1145
1310
  }
1146
- this._fireEvt('eletrack_download', event);
1311
+ this.fire('eletrack_download', event);
1147
1312
  };
1148
1313
  };
1149
1314
  },
@@ -1154,24 +1319,17 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
1154
1319
  */
1155
1320
  _width: function() {
1156
1321
  if (this._chart) return this._chart._width();
1157
- let opts = this.options;
1158
- return opts.width - opts.margins.left - opts.margins.right;
1322
+ const { width, margins } = this.options;
1323
+ return width - margins.left - margins.right;
1159
1324
  },
1160
1325
 
1161
1326
  /**
1162
- * Calculate chart xTicks
1327
+ * Calculates chart height.
1163
1328
  */
1164
- _xTicks: function() {
1165
- if (this.__xTicks) this.__xTicks = this.options.xTicks;
1166
- return this.__xTicks || Math.round(this._width() / 75);
1329
+ _height: function() {
1330
+ if (this._chart) return this._chart._height();
1331
+ const { height, margins } = this.options;
1332
+ return height - margins.top - margins.bottom;
1167
1333
  },
1168
1334
 
1169
- /**
1170
- * Calculate chart yTicks
1171
- */
1172
- _yTicks: function() {
1173
- if (this.__yTicks) this.__yTicks = this.options.yTicks;
1174
- return this.__yTicks || Math.round(this._height() / 30);
1175
- }
1176
-
1177
1335
  });