@raruto/leaflet-elevation 1.9.6 → 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.
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.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
+ __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,17 +76,11 @@ 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();
82
- }
83
- },
84
-
85
- _clearMarkers: function() {
86
- if (this._markers && this._markers.eachLayer) {
87
- this._markers.eachLayer(l => l.remove())
88
- this._markers.clearLayers();
79
+ _clearLayers: function(l) {
80
+ l = l || this._layers;
81
+ if (l && l.eachLayer) {
82
+ l.eachLayer(f => f.remove())
83
+ l.clearLayers();
89
84
  }
90
85
  },
91
86
 
@@ -144,8 +139,7 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
144
139
  },
145
140
 
146
141
  getBounds: function(data) {
147
- data = data || this._data;
148
- return L.latLngBounds(data.map((d) => d.latlng));
142
+ return L.latLngBounds((data || this._data).map((d) => d.latlng));
149
143
  },
150
144
 
151
145
  /**
@@ -165,63 +159,54 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
165
159
  /**
166
160
  * Initialize chart control "options" and "container".
167
161
  */
168
- initialize: function(options) {
162
+ initialize: function(opts) {
163
+
164
+ opts = L.setOptions(this, opts);
165
+
169
166
  this._data = [];
170
167
  this._layers = L.featureGroup();
171
168
  this._markers = L.featureGroup();
169
+ this._circleMarkers = L.featureGroup();
172
170
  this._markedSegments = L.polyline([]);
173
- 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;
174
175
  this.track_info = {};
175
- this.handlers = [];
176
+ // this.handlers = [];
176
177
 
177
- 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);
178
185
 
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
186
 
185
- this._markedSegments.setStyle(this.options.polylineSegments);
187
+ this._markedSegments.setStyle(opts.polylineSegments);
186
188
 
187
189
  // Leaflet canvas renderer colors
188
- L.extend(D3.Colors, this.options.colors || {});
190
+ L.extend(_.Colors, opts.colors || {});
189
191
 
190
192
  // Various stuff
191
193
  this._fixCanvasPaths();
192
194
  this._fixTooltipSize();
195
+
193
196
  },
194
197
 
195
198
  /**
196
199
  * Javascript scripts downloader (lazy loader)
197
200
  */
198
- lazyLoad: function(src, condition = false, loader = '') {
199
- if (!this.options.lazyLoadJS) {
200
- return Promise.resolve();
201
- }
201
+ import: function(src, condition) {
202
202
  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;
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;
223
208
  }
224
- return L.Control.Elevation[loader] = _.lazyLoader(src, condition, L.Control.Elevation[loader]);
209
+ return condition !== false ? import(src) : Promise.resolve();
225
210
  },
226
211
 
227
212
  /**
@@ -239,33 +224,23 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
239
224
  this._map = map;
240
225
 
241
226
  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
227
 
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
- });
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);
269
244
 
270
245
  return container;
271
246
  },
@@ -278,18 +253,18 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
278
253
  this._container = null;
279
254
 
280
255
  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);
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);
285
260
 
286
- _.off(map.getContainer(), 'mousewheel', this._resetDrag, this);
287
- _.off(map.getContainer(), 'touchstart', this._resetDrag, this);
288
- _.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);
289
264
 
290
265
  this
291
- .off('eledata_added eledata_loaded', this._updateChart, this)
292
- .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);
293
268
  },
294
269
 
295
270
  /**
@@ -336,7 +311,7 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
336
311
  break;
337
312
 
338
313
  default:
339
- console.warn('Unsopperted GeoJSON feature geometry type:' + geom.type);
314
+ return console.warn('Unsopperted GeoJSON feature geometry type:' + geom.type);
340
315
  }
341
316
  }
342
317
  if (d.type === "FeatureCollection") {
@@ -357,11 +332,13 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
357
332
  _addGeoJSONData: function(coords, properties) {
358
333
  coords.forEach((point, i) => {
359
334
 
360
- // Inspired by L.GPX layer properties
361
- 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 };
362
337
 
363
338
  // "coordinateProperties" property is generated inside "@tmcw/toGeoJSON"
364
- let props = (properties && properties.coordinateProperties) || properties;
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]);
365
342
 
366
343
  this.fire("elepoint_init", { point: point, props: props, id: i });
367
344
 
@@ -372,6 +349,8 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
372
349
  );
373
350
 
374
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];
375
354
  });
376
355
 
377
356
  this.fire("eletrack_added", { coords: coords, index: this._data.length - 1 });
@@ -399,7 +378,7 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
399
378
  if (layer) this._layers.addLayer(layer)
400
379
  // Postpone adding the distance markers (lazy: true)
401
380
  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']());
381
+ layer.on('add remove', ({target, type}) => L.DistanceMarkers && target instanceof L.Polyline && target[type + 'DistanceMarkers']());
403
382
  }
404
383
  },
405
384
 
@@ -412,17 +391,18 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
412
391
  */
413
392
  _initAlmostOverHandler: function(map, layer) {
414
393
  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
- });
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
+ });
426
406
  },
427
407
 
428
408
  /**
@@ -431,9 +411,10 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
431
411
  _initDistanceMarkers: function(map, layer) {
432
412
  if (!map) return;
433
413
  if (this.options.distanceMarkers) {
434
- this.lazyLoad(this.__LGEOMUTIL)
435
- .then(() => this.lazyLoad(this.__LDISTANCEM))
436
- .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());
437
418
  } else if (this.options.polyline) {
438
419
  layer.addTo(map);
439
420
  }
@@ -459,7 +440,12 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
459
440
  */
460
441
  _initMapIntegrations: function(control, layer) {
461
442
  let map = control._map;
462
- 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);
463
449
  map.once('layeradd', (e) => control.options.autofitBounds && control.fitBounds(layer.getBounds()));
464
450
  if (!L.Browser.mobile) {
465
451
  control._initAlmostOverHandler(map, layer);
@@ -492,12 +478,14 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
492
478
  _fixCanvasPaths: function() {
493
479
  let oldProto = L.Canvas.prototype._fillStroke;
494
480
  let control = this;
481
+
482
+ let theme = this.options.theme.replace('-theme', '');
483
+ let color = _.Colors[theme] || {};
484
+
495
485
  L.Canvas.include({
496
486
  _fillStroke: function(ctx, layer) {
497
487
  if (control._layers.hasLayer(layer)) {
498
488
 
499
- let theme = control.options.theme.replace('-theme', '');
500
- let color = D3.Colors[theme] || {};
501
489
  let options = layer.options;
502
490
 
503
491
  options.color = color.line || color.area || theme;
@@ -528,12 +516,12 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
528
516
  */
529
517
  _fixTooltipSize: function() {
530
518
  this.on('elechart_init', () =>
531
- this.once('elechart_change elechart_hover', e => {
519
+ this.once('elechart_change elechart_hover', ({data, xCoord}) => {
532
520
  if (this._chartEnabled) {
533
- this._chart._showDiagramIndicator(e.data, e.xCoord);
534
- this._chart._showDiagramIndicator(e.data, e.xCoord);
521
+ this._chart._showDiagramIndicator(data, xCoord);
522
+ this._chart._showDiagramIndicator(data, xCoord);
535
523
  }
536
- this._updateMarker(e.data);
524
+ this._updateMarker(data);
537
525
  })
538
526
  );
539
527
  },
@@ -560,15 +548,6 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
560
548
  if (this._map) this._map.fire(type, data, propagate);
561
549
  },
562
550
 
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
551
  /*
573
552
  * Hides the position/height indicator marker drawn onto the map
574
553
  */
@@ -584,9 +563,6 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
584
563
  _initChart: function(container) {
585
564
  let opts = this.options;
586
565
 
587
- opts.xTicks = this._xTicks();
588
- opts.yTicks = this._yTicks();
589
-
590
566
  if (opts.detached) {
591
567
  let { offsetWidth, offsetHeight} = this.eleDiv;
592
568
  if (offsetWidth > 0) opts.width = offsetWidth;
@@ -598,42 +574,66 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
598
574
  if (opts._maxWidth > clientWidth) opts.width = clientWidth - 30;
599
575
  }
600
576
 
601
- let chart = this._chart = new Chart(opts, this);
577
+ Promise.all([
578
+ this.import(this.__D3),
579
+ this.import(this.__LCHART)
580
+ ]).then((m) => {
602
581
 
603
- this._x = this._chart._x;
604
- this._y = this._chart._y;
605
-
606
- d3
607
- .select(container)
608
- .call(chart.render())
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");
609
608
 
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);
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);
620
615
 
616
+ _.on(map.getContainer(), 'mousewheel', this._resetDrag, this);
617
+ _.on(map.getContainer(), 'touchstart', this._resetDrag, this);
618
+ _.on(document, 'keydown', this._onKeyDown, this);
621
619
 
622
- this.fire("elechart_axis");
620
+ this
621
+ .on('eledata_added eledata_loaded', this._updateChart, this)
622
+ .on('eledata_added eledata_loaded', this._updateSummary, this);
623
623
 
624
- if (this.options.legend) this.fire("elechart_legend");
624
+ this._updateChart();
625
+ this._updateSummary();
626
+ });
625
627
 
626
- this.fire("elechart_init");
627
628
  },
628
629
 
629
630
  _initLayer: function() {
630
631
  this._layers
631
- .on('layeradd layerremove', (e) => {
632
- let layer = e.layer
632
+ .on('layeradd layerremove', ({layer, type}) => {
633
633
  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');
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');
637
637
  });
638
638
  },
639
639
 
@@ -647,9 +647,14 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
647
647
 
648
648
  if (this._renderer) this._renderer.remove()
649
649
  this._renderer = L.svg({ pane: "elevationPane" }).addTo(this._map); // default leaflet svg renderer
650
- this._marker = new Marker(this.options);
651
650
 
652
- this.fire("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
+ });
653
658
  },
654
659
 
655
660
  /**
@@ -684,15 +689,13 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
684
689
  },
685
690
 
686
691
  _initSummary: function(container) {
687
- this._summary = new Summary({ summary: this.options.summary }, this);
688
-
689
- d3.select(container).call(this._summary.render());
690
- },
692
+ this.import(this.__LSUMMARY).then((m)=>{
693
+ this._summary = new (m || Elevation).Summary({ summary: this.options.summary }, this);
691
694
 
692
- _keydownHandler: function(e) {
693
- if(!this.options.detached && e.key === "Escape"){
694
- this._collapse()
695
- };
695
+ this.on('elechart_init', () => {
696
+ d3.select(container).call(this._summary.render());
697
+ });
698
+ });
696
699
  },
697
700
 
698
701
  /**
@@ -708,6 +711,31 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
708
711
  }).catch((err) => console.warn(err));
709
712
  },
710
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
+
711
739
  /**
712
740
  * Simple GeoJSON data loader (L.GeoJSON).
713
741
  */
@@ -768,78 +796,78 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
768
796
  },
769
797
  });
770
798
 
771
- control.lazyLoad(control.__D3).then(() => {
799
+ control.import(control.__D3).then(() => {
772
800
  control._initMapIntegrations(control, layer);
773
- 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 });
774
802
  });
775
803
 
776
804
  return layer;
777
805
  },
778
806
 
779
- _dragendHandler: function(e) {
807
+ _onDragEnd: function({ dragstart, dragend}) {
780
808
  this._hideMarker();
781
- this.fitBounds(L.latLngBounds([e.dragstart.latlng, e.dragend.latlng]));
809
+ this.fitBounds(L.latLngBounds([dragstart.latlng, dragend.latlng]));
782
810
 
783
811
  this.fire("elechart_dragged");
784
812
  },
785
813
 
814
+ _onKeyDown: function({key}) {
815
+ if(!this.options.detached && key === "Escape"){
816
+ this._collapse()
817
+ };
818
+ },
819
+
786
820
  /**
787
- * Trigger mouseenter event
821
+ * Trigger mouseenter event.
788
822
  */
789
- _mouseenterHandler: function(e) {
823
+ _onMouseEnter: function() {
790
824
  this.fire('elechart_enter');
791
825
  },
792
826
 
793
827
  /*
794
828
  * Handles the moueseover the chart and displays distance and altitude level.
795
829
  */
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;
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);
804
835
 
805
- if (this._chartEnabled) this._chart._showDiagramIndicator(item, xCoord);
836
+ this._updateMarker(item);
837
+ this._setMapView(item);
806
838
 
807
- this._updateMarker(item);
808
- this._setMapView(item);
839
+ if (this._map) {
840
+ _.addClass(this._map.getContainer(), 'elechart-hover');
841
+ }
809
842
 
810
- if (this._map) {
811
- _.addClass(this._map.getContainer(), 'elechart-hover');
843
+ this.fire("elechart_change", { data: item, xCoord: xCoord });
844
+ this.fire("elechart_hover", { data: item, xCoord: xCoord });
812
845
  }
813
-
814
- this.fire("elechart_change", { data: item, xCoord: xCoord });
815
- this.fire("elechart_hover", { data: item, xCoord: xCoord });
816
846
  }
817
847
  },
818
848
 
819
849
  /*
820
850
  * Handles mouseover events of the data layers on the map.
821
851
  */
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 });
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
+ }
836
864
  }
837
865
  },
838
866
 
839
867
  /*
840
868
  * Handles the moueseout over the chart.
841
869
  */
842
- _mouseoutHandler: function() {
870
+ _onMouseOut: function() {
843
871
  if (!this.options.detached) {
844
872
  this._hideMarker();
845
873
  this._chart._hideDiagramIndicator();
@@ -852,6 +880,33 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
852
880
  this.fire("elechart_leave");
853
881
  },
854
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
+ }
908
+ },
909
+
855
910
  /**
856
911
  * Simple GeoJSON Parser
857
912
  */
@@ -866,7 +921,7 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
866
921
  */
867
922
  _parseFromString: function(data) {
868
923
  return new Promise(resolve =>
869
- this.lazyLoad(this.__TOGEOJSON).then(() => {
924
+ this.import(this.__TOGEOJSON).then(() => {
870
925
  let geojson;
871
926
  try {
872
927
  geojson = this._parseFromXMLString(data.trim());
@@ -967,22 +1022,31 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
967
1022
  * Base handler for iterative track statistics (dist, time, z, slope, speed, acceleration, ...)
968
1023
  */
969
1024
  _registerDataAttribute: function(props) {
970
- // save here a reference to last used point
971
- let lastValid = null;
972
1025
 
973
1026
  // parse of "coordinateProperties" for later usage
974
- if (props.init) {
975
- this.on("elepoint_init", (e) => props.init.call(this, e));
1027
+ if (props.coordPropsToMeta) {
1028
+ this.on("elepoint_init", (e) => props.coordPropsToMeta.call(this, e));
976
1029
  }
977
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
+
978
1037
  // iteration
979
- this.on("elepoint_added", (e) => {
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;
980
1044
 
981
1045
  // check and fix missing data on last added point
982
1046
  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];
1047
+ let curr = this._data[i][attr];
1048
+ if(i > 0) {
1049
+ let prev = this._data[i - 1][attr];
986
1050
  if (isNaN(prev)) {
987
1051
  if (!isNaN(lastValid) && !isNaN(curr)) {
988
1052
  prev = (lastValid + curr) / 2;
@@ -991,8 +1055,8 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
991
1055
  } else if (!isNaN(curr)) {
992
1056
  prev = curr;
993
1057
  }
994
- if (!isNaN(prev)) return this._data.splice(e.index - 1, 1);
995
- this._data[e.index - 1][props.name] = prev;
1058
+ if (!isNaN(prev)) return this._data.splice(i - 1, 1);
1059
+ this._data[i - 1][attr] = prev;
996
1060
  }
997
1061
  }
998
1062
  // update reference to last used point (ie. if it has data)
@@ -1002,25 +1066,107 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
1002
1066
  }
1003
1067
 
1004
1068
  // retrieve point value
1005
- this._data[e.index][props.name] = props.fetch.call(this, e.index, e.point);
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
+ }
1006
1078
 
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)
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);
1009
1096
  });
1010
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));
1107
+ }
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
+ }
1161
+ }
1162
+ },
1011
1163
 
1012
1164
  /**
1013
1165
  * Add chart or marker tooltip info
1014
1166
  */
1015
1167
  _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
- }
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 })));
1024
1170
  },
1025
1171
 
1026
1172
  /**
@@ -1054,85 +1200,39 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
1054
1200
  * Hacky way for handling chart resize. Deletes it and redraw chart.
1055
1201
  */
1056
1202
  _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();
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
+ }
1081
1212
  }
1213
+ this._updateMapSegments();
1082
1214
  }
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
1215
  },
1093
1216
 
1094
1217
  /**
1095
1218
  * Collapse or Expand chart control.
1096
1219
  */
1097
1220
  _toggle: function() {
1098
- if (_.hasClass(this._container, "elevation-expanded"))
1099
- this._collapse();
1100
- else
1101
- this._expand();
1221
+ _.hasClass(this._container, "elevation-expanded") ? this._collapse() : this._expand();
1102
1222
  },
1103
1223
 
1104
1224
  /**
1105
1225
  * Update map center and zoom (followMarker: true)
1106
1226
  */
1107
1227
  _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();
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
+ }
1136
1236
  }
1137
1237
  },
1138
1238
 
@@ -1140,17 +1240,17 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
1140
1240
  * Calculates [x, y] domain and then update chart.
1141
1241
  */
1142
1242
  _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');
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
+ }
1154
1254
  },
1155
1255
 
1156
1256
  /*
@@ -1161,7 +1261,7 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
1161
1261
  this._marker.update({
1162
1262
  map : this._map,
1163
1263
  item : item,
1164
- maxElevation: this.track_info.elevation_max || 0,
1264
+ yCoordMax : this._yCoordMax || 0,
1165
1265
  options : this.options
1166
1266
  });
1167
1267
  }
@@ -1190,6 +1290,8 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
1190
1290
  * Update chart summary.
1191
1291
  */
1192
1292
  _updateSummary: function() {
1293
+ if (!this._summary) return;
1294
+
1193
1295
  this._summary.reset();
1194
1296
 
1195
1297
  if (this.options.summary) {
@@ -1198,8 +1300,8 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
1198
1300
  }
1199
1301
  if (this.options.downloadLink && this._downloadURL) { // TODO: generate dynamically file content instead of using static file urls.
1200
1302
  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();
1303
+ _.select('.download a', this._summary._container).onclick = ({preventDefault}) => {
1304
+ preventDefault();
1203
1305
  let event = { downloadLink: this.options.downloadLink, confirm: _.saveFile.bind(this, this._downloadURL) };
1204
1306
  if (this.options.downloadLink == 'modal' && typeof CustomEvent === "function") {
1205
1307
  document.dispatchEvent(new CustomEvent("eletrack_download", { detail: event }));
@@ -1217,24 +1319,17 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
1217
1319
  */
1218
1320
  _width: function() {
1219
1321
  if (this._chart) return this._chart._width();
1220
- let opts = this.options;
1221
- return opts.width - opts.margins.left - opts.margins.right;
1322
+ const { width, margins } = this.options;
1323
+ return width - margins.left - margins.right;
1222
1324
  },
1223
1325
 
1224
1326
  /**
1225
- * Calculate chart xTicks
1327
+ * Calculates chart height.
1226
1328
  */
1227
- _xTicks: function() {
1228
- if (this.__xTicks) this.__xTicks = this.options.xTicks;
1229
- 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;
1230
1333
  },
1231
1334
 
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
1335
  });