@raruto/leaflet-elevation 1.7.6 → 1.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raruto/leaflet-elevation",
3
- "version": "1.7.6",
3
+ "version": "1.8.2",
4
4
  "description": "A Leaflet plugin that allows to add elevation profiles using d3js",
5
5
  "main": "dist/leaflet-elevation.min.js",
6
6
  "module": "src/index.js",
@@ -10,8 +10,8 @@ Elevation.addInitHook(function() {
10
10
  let opts = this.options;
11
11
  let acceleration = {};
12
12
 
13
- acceleration.label = opts.accelerationLabel || L._(this.options.imperial ? 'ft/s²' : 'm/s²');
14
- this._accelerationFactor = opts.accelerationFactor || 1;
13
+ acceleration.label = opts.accelerationLabel || L._(opts.imperial ? 'ft/s²' : 'm/s²');
14
+ opts.accelerationFactor = opts.accelerationFactor || 1;
15
15
 
16
16
  if (this.options.acceleration != "summary") {
17
17
 
@@ -61,8 +61,8 @@ Elevation.addInitHook(function() {
61
61
  if (deltaT > 0) {
62
62
  let curr = data[i].speed;
63
63
  let prev = i > 0 ? data[i - 1].speed : curr;
64
- let delta = (curr - prev) * (1000 / this._timeFactor);
65
- acceleration = Math.abs((delta / deltaT)) * this._accelerationFactor;
64
+ let delta = (curr - prev) * (1000 / opts.timeFactor);
65
+ acceleration = Math.abs((delta / deltaT)) * opts.accelerationFactor;
66
66
  }
67
67
 
68
68
  // Try to smooth "crazy" acceleration values.
@@ -75,26 +75,16 @@ Elevation.addInitHook(function() {
75
75
  }
76
76
 
77
77
  // Range of acceptable acceleration values.
78
- if (this.options.accelerationRange) {
79
- let range = this.options.accelerationRange;
80
- if (acceleration < range[0]) acceleration = range[0];
81
- else if (acceleration > range[1]) acceleration = range[1];
82
- }
83
-
84
- acceleration = L.Util.formatNum(acceleration, 2);
85
-
86
- if (acceleration > sMax) sMax = acceleration;
87
- if (acceleration < sMin) sMin = acceleration;
88
- sAvg = (acceleration + sAvg) / 2.0;
78
+ acceleration = _.clamp(acceleration, this.options.accelerationRange);
89
79
 
90
- data[i].acceleration = acceleration;
80
+ track.acceleration_max = acceleration > sMax ? acceleration : sMax;
81
+ track.acceleration_min = acceleration < sMin ? acceleration : sMin;
82
+ track.acceleration_avg = (acceleration + sAvg) / 2.0;
91
83
 
92
- track.acceleration_max = sMax;
93
- track.acceleration_min = sMin;
94
- track.acceleration_avg = sAvg;
84
+ data[i].acceleration = L.Util.formatNum(acceleration, 2);
95
85
  });
96
86
 
97
- this._registerFocusLabel({
87
+ this._registerTooltip({
98
88
  name : 'acceleration',
99
89
  chart: (item) => L._("a: ") + item.acceleration + " " + acceleration.label,
100
90
  marker: (item) => Math.round(item.acceleration) + " " + acceleration.label,
package/src/altitude.js CHANGED
@@ -9,21 +9,21 @@ Elevation.addInitHook(function() {
9
9
  let altitude = {};
10
10
 
11
11
  if (opts.imperial) {
12
- this._heightFactor = this.__footFactor;
13
- this._yLabel = "ft";
12
+ opts.altitudeFactor = this.__footFactor;
13
+ altitude.label = "ft";
14
14
  } else {
15
- this._heightFactor = opts.heightFactor;
16
- this._yLabel = opts.yLabel;
15
+ opts.altitudeFactor = opts.heightFactor || opts.altitudeFactor || 1;
16
+ altitude.label = opts.yLabel;
17
17
  }
18
18
 
19
19
  this.on("eledata_updated", function(e) {
20
20
  let data = this._data;
21
21
  let i = e.index;
22
- let z = data[i].z * this._heightFactor;
22
+ let z = data[i].z * opts.altitudeFactor;
23
23
 
24
24
  let track = this.track_info;
25
- let eleMax = track.elevation_max || -Infinity;
26
- let eleMin = track.elevation_min || +Infinity;
25
+ let zMax = track.elevation_max || -Infinity;
26
+ let zMin = track.elevation_min || +Infinity;
27
27
 
28
28
  // check and fix missing elevation data on last added point
29
29
  if (!this.options.skipNullZCoords && i > 0) {
@@ -44,15 +44,13 @@ Elevation.addInitHook(function() {
44
44
  }
45
45
  // skip point if it has not elevation
46
46
  if (!isNaN(z)) {
47
- eleMax = eleMax < z ? z : eleMax;
48
- eleMin = eleMin > z ? z : eleMin;
49
47
  this._lastValidZ = z;
50
48
  }
51
49
 
52
- data[i].z = z;
50
+ track.elevation_max = z > zMax ? z : zMax;
51
+ track.elevation_min = z < zMin ? z : zMin;
53
52
 
54
- track.elevation_max = eleMax;
55
- track.elevation_min = eleMin;
53
+ data[i].z = z;
56
54
  });
57
55
 
58
56
  this.on("elechart_axis", function() {
@@ -69,13 +67,10 @@ Elevation.addInitHook(function() {
69
67
 
70
68
  this.on("elechart_axis", function() {
71
69
 
72
- altitude.y = this._chart._y;
73
- altitude.label = this._yLabel;
74
-
75
70
  this._chart._registerAxisScale({
76
71
  axis : "y",
77
72
  position: "left",
78
- scale : altitude.y,
73
+ scale : this._chart._y,
79
74
  label : altitude.label,
80
75
  labelX : -3,
81
76
  labelY : -8,
@@ -88,8 +83,6 @@ Elevation.addInitHook(function() {
88
83
 
89
84
  let theme = this.options.theme.replace('-theme', '');
90
85
  let color = D3.Colors[theme] || {};
91
- let alpha = (this.options.detached ? (color.alpha || '0.8') : 1);
92
- let stroke = (this.options.detached ? color.stroke : false);
93
86
 
94
87
  this._chart._registerAreaPath({
95
88
  name : 'altitude',
@@ -97,9 +90,9 @@ Elevation.addInitHook(function() {
97
90
  scaleX : 'distance',
98
91
  scaleY : 'altitude',
99
92
  color : color.area || theme,
100
- strokeColor : stroke || '#000',
93
+ strokeColor : this.options.detached ? color.stroke : '#000',
101
94
  strokeOpacity: "1",
102
- fillOpacity : alpha,
95
+ fillOpacity : this.options.detached ? (color.alpha || '0.8') : 1,
103
96
  preferCanvas : this.options.preferCanvas,
104
97
  });
105
98
 
@@ -107,20 +100,20 @@ Elevation.addInitHook(function() {
107
100
 
108
101
  }
109
102
 
110
- this._registerFocusLabel({
103
+ this._registerTooltip({
111
104
  name: 'y',
112
- chart: (item) => L._("y: ") + d3.format("." + opts.decimalsY + "f")(item[opts.yAttr]) + " " + this._yLabel,
113
- marker: (item) => d3.format("." + opts.decimalsY + "f")(item[opts.yAttr]) + " " + this._yLabel,
105
+ chart: (item) => L._("y: ") + d3.format("." + opts.decimalsY + "f")(item[opts.yAttr]) + " " + altitude.label,
106
+ marker: (item) => d3.format("." + opts.decimalsY + "f")(item[opts.yAttr]) + " " + altitude.label,
114
107
  });
115
108
 
116
109
  this._registerSummary({
117
110
  "maxele" : {
118
111
  label: "Max Elevation: ",
119
- value: (track) => (track.elevation_max || 0).toFixed(2) + '&nbsp;' + this._yLabel
112
+ value: (track) => (track.elevation_max || 0).toFixed(2) + '&nbsp;' + altitude.label
120
113
  },
121
114
  "minele" : {
122
115
  label: "Min Elevation: ",
123
- value: (track) => (track.elevation_min || 0).toFixed(2) + '&nbsp;' + this._yLabel
116
+ value: (track) => (track.elevation_min || 0).toFixed(2) + '&nbsp;' + altitude.label
124
117
  },
125
118
  });
126
119
 
package/src/chart.js CHANGED
@@ -14,13 +14,13 @@ export var Chart = L.Class.extend({
14
14
 
15
15
  // cache registered components
16
16
  this._props = {
17
- scales : {},
18
- paths : {},
19
- areas : {},
20
- grids : {},
21
- axes : {},
22
- legendItems: {},
23
- focusLabels: {},
17
+ scales : {},
18
+ paths : {},
19
+ areas : {},
20
+ grids : {},
21
+ axes : {},
22
+ legendItems : {},
23
+ tooltipItems: {},
24
24
  };
25
25
 
26
26
  this._scales = {};
@@ -31,14 +31,6 @@ export var Chart = L.Class.extend({
31
31
  this._brushEnabled = opts.dragging;
32
32
  this._zoomEnabled = opts.zooming;
33
33
 
34
- if (opts.imperial) {
35
- this._xLabel = "mi";
36
- this._yLabel = "ft";
37
- } else {
38
- this._xLabel = opts.xLabel;
39
- this._yLabel = opts.yLabel;
40
- }
41
-
42
34
  let chart = this._chart = D3.Chart(opts);
43
35
 
44
36
  // SVG Container
@@ -50,22 +42,17 @@ export var Chart = L.Class.extend({
50
42
  this._point = chart.pane('point');
51
43
  this._axis = chart.pane('axis');
52
44
  this._legend = chart.pane('legend');
45
+ this._tooltip = chart.pane('tooltip');
46
+ this._ruler = chart.pane('ruler');
53
47
 
54
48
  // Scales
55
49
  this._initScale();
56
50
 
57
51
  // Helpers
58
- this._clipPath = chart.get('clipPath');
59
- this._canvas = chart.get('canvas');
52
+ this._mask = chart.get('mask');
60
53
  this._context = chart.get('context');
61
- this._dragG = chart.get('dragG');
62
- this._focusG = chart.get('focusG');
63
54
  this._brush = chart.get('brush');
64
55
 
65
- // Tooltip
66
- this._focusline = this._focusG.select('.mouse-focus-line');
67
- this._focuslabel = this._focusG.select('.mouse-focus-label');
68
-
69
56
  this._zoom = d3.zoom();
70
57
  this._drag = d3.drag();
71
58
 
@@ -74,6 +61,13 @@ export var Chart = L.Class.extend({
74
61
 
75
62
  // svg.on('resize', (e)=>console.log(e.detail));
76
63
 
64
+ // Handle multi-track segments (BETA)
65
+ this._maskGaps = [];
66
+ control.on('eletrack_added', (e) => {
67
+ this._maskGaps.push(e.index);
68
+ control.once('eledata_updated', (e) => this._maskGaps.push(e.index));
69
+ });
70
+
77
71
  },
78
72
 
79
73
  update: function(props) {
@@ -101,62 +95,36 @@ export var Chart = L.Class.extend({
101
95
  this._area.selectAll('path').attr("d", "M0 0");
102
96
  this._context.clearRect(0, 0, this._width(), this._height());
103
97
 
104
- if (this._path) {
98
+ // if (this._path) {
105
99
  // this._x.domain([0, 1]);
106
100
  // this._y.domain([0, 1]);
107
- }
101
+ // }
102
+
103
+ this._maskGaps = [];
104
+ this._mask.selectAll(".gap").remove();
108
105
  },
109
106
 
110
107
  _drawPath: function(name) {
111
- let opts = this.options;
112
- let ctx = this._context;
113
-
114
108
  let path = this._paths[name];
115
109
  let area = this._props.areas[name];
116
- let node = path.node();
117
- let scaleX = this._scales[area.scaleX];
118
- let scaleY = this._scales[area.scaleY];
119
-
120
- area = L.extend({}, area, {
121
- width : this._width(),
122
- height : this._height(),
123
- scaleX : scaleX,
124
- scaleY : scaleY
125
- });
126
-
127
- if (!scaleY || !scaleY) {
128
- return console.warn('Unable to render path:' + name);
129
- }
130
110
 
131
- path.datum(this._data).attr("d", D3.Area(area));
111
+ path.datum(this._data).attr("d",
112
+ D3.Area(
113
+ L.extend({}, area, {
114
+ width : this._width(),
115
+ height : this._height(),
116
+ scaleX : this._scales[area.scaleX],
117
+ scaleY : this._scales[area.scaleY]
118
+ })
119
+ )
120
+ );
132
121
 
133
122
  if (path.classed('leaflet-hidden')) return;
134
123
 
135
- if (opts.preferCanvas) {
136
- path.classed('canvas-path', true);
137
-
138
- ctx.beginPath();
139
- ctx.moveTo(0, 0);
140
- let p = new Path2D(path.attr('d'));
141
-
142
- ctx.strokeStyle = path.attr('stroke');
143
- ctx.fillStyle = path.attr('fill');
144
- ctx.lineWidth = 1.25;
145
- ctx.globalCompositeOperation = 'source-over';
146
-
147
- // stroke opacity
148
- ctx.globalAlpha = path.attr('stroke-opacity') || 0.3;
149
- ctx.stroke(p);
150
-
151
- // fill opacity
152
- ctx.globalAlpha = path.attr('fill-opacity') || 0.45;
153
- ctx.fill(p);
154
-
155
- ctx.globalAlpha = 1;
156
-
157
- ctx.closePath();
124
+ if (this.options.preferCanvas) {
125
+ _.drawCanvas(this._context, path);
158
126
  } else {
159
- this._area.append(() => node);
127
+ this._area.append(() => path.node());
160
128
  }
161
129
  },
162
130
 
@@ -220,6 +188,10 @@ export var Chart = L.Class.extend({
220
188
  if (e.sourceEvent && e.sourceEvent.type == "mousedown") svg.style('cursor', 'grabbing');
221
189
  if (e.transform.k == 1 && e.transform.x == 0) {
222
190
  this._container.classed('zoomed', true);
191
+ // Apply d3-zoom (bind <clipPath> mask)
192
+ if (this._mask) {
193
+ this._point.attr('mask', 'url(#' + this._mask.attr('id') + ')');
194
+ }
223
195
  }
224
196
  this.zooming = true;
225
197
  };
@@ -227,6 +199,10 @@ export var Chart = L.Class.extend({
227
199
  const onEnd = (e) => {
228
200
  if (e.transform.k ==1 && e.transform.x == 0){
229
201
  this._container.classed('zoomed', false);
202
+ // Apply d3-zoom (bind <clipPath> mask)
203
+ if (this._mask) {
204
+ this._point.attr('mask', null);
205
+ }
230
206
  }
231
207
  this.zooming = false;
232
208
  svg.style('cursor', '');
@@ -307,8 +283,8 @@ export var Chart = L.Class.extend({
307
283
 
308
284
  const label = (e, d) => {
309
285
  let yMax = this._height();
310
- let y = this._dragG.data()[0].y;
311
- if (y >= yMax || y <= 0) this._dragG.select(".horizontal-drag-label").text('');
286
+ let y = this._ruler.data()[0].y;
287
+ if (y >= yMax || y <= 0) this._ruler.select(".horizontal-drag-label").text('');
312
288
  this._hideDiagramIndicator();
313
289
  };
314
290
 
@@ -317,15 +293,15 @@ export var Chart = L.Class.extend({
317
293
  let yCoord = d3.pointers(e, this._area.node())[0][1];
318
294
  let y = yCoord > 0 ? (yCoord < yMax ? yCoord : yMax) : 0;
319
295
  let z = this._y.invert(y);
320
- let data = L.extend(this._dragG.data()[0], { y: y });
296
+ let data = L.extend(this._ruler.data()[0], { y: y });
321
297
 
322
- this._dragG
298
+ this._ruler
323
299
  .data([data])
324
300
  .attr("transform", d => "translate(" + d.x + "," + d.y + ")")
325
301
  .classed('active', y < yMax);
326
302
 
327
303
  this._container.select(".horizontal-drag-label")
328
- .text(formatNum(z) + " " + this._yLabel);
304
+ .text(formatNum(z) + " " + (this.options.imperial ? 'ft' : 'm'));
329
305
 
330
306
  this.fire('ruler_filter', { coords: yCoord < yMax && yCoord > 0 ? this._findCoordsForY(yCoord) : [] });
331
307
  }
@@ -334,7 +310,7 @@ export var Chart = L.Class.extend({
334
310
  .on("start end", label)
335
311
  .on("drag", position);
336
312
 
337
- this._dragG.call(drag);
313
+ this._ruler.call(drag);
338
314
 
339
315
  },
340
316
 
@@ -427,7 +403,7 @@ export var Chart = L.Class.extend({
427
403
  },
428
404
 
429
405
  /**
430
- * Add a waypoint of interest over the chart
406
+ * Add a point of interest over the chart
431
407
  */
432
408
  _registerCheckPoint: function(point) {
433
409
  if (!this._data.length) return;
@@ -442,58 +418,19 @@ export var Chart = L.Class.extend({
442
418
  x = this._x(point.dist);
443
419
  item = this._data[this._findIndexForXCoord(x)]
444
420
  y = this._y(item.z);
445
- } else
446
-
447
- if (isNaN(x) || isNaN(y)) return;
448
-
449
- if (!point.item || !point.item.property('isConnected')) {
450
- point.position = point.position || "bottom";
451
-
452
- point.item = this._point.append('g');
453
-
454
- point.item.append("svg:line")
455
- .attr("y1", 0)
456
- .attr("x1", 0)
457
- .attr("style","stroke: rgb(51, 51, 51); stroke-width: 0.5; stroke-dasharray: 2, 2;");
458
-
459
- point.item
460
- .append("svg:circle")
461
- .attr("class", " height-focus circle-lower")
462
- .attr("r", 3);
463
-
464
- if (point.label) {
465
- point.item.append("svg:text")
466
- .attr("dx", "4px")
467
- .attr("dy", "-4px");
468
- }
469
- }
470
-
471
- point.item
472
- .datum({
473
- pos: point.position,
474
- x: x,
475
- y: y
476
- })
477
- .attr("class", d => "point " + d.pos)
478
- .attr("transform", d => "translate(" + d.x + "," + d.y + ")");
479
-
480
- point.item.select('line')
481
- .datum({
482
- y2: ({'top': -y, 'bottom': this._height() - y})[point.position],
483
- x2: ({'left': -x, 'right': this._width() - x})[point.position] || 0
484
- })
485
- .attr("y2", d => d.y2)
486
- .attr("x2", d => d.x2)
487
-
488
- if (point.label) {
489
- point.item.select('text')
490
- .text(point.label);
491
- }
492
-
421
+ }
422
+
423
+ this._point.call(D3.CheckPoint({
424
+ point: point,
425
+ width: this._width(),
426
+ height: this._height(),
427
+ x: x,
428
+ y: y,
429
+ }));
493
430
  },
494
431
 
495
- _registerFocusLabel: function(props) {
496
- this._props.focusLabels[props.name] = props;
432
+ _registerTooltip: function(props) {
433
+ this._props.tooltipItems[props.name] = props;
497
434
  },
498
435
 
499
436
  _updateArea: function() {
@@ -578,11 +515,29 @@ export var Chart = L.Class.extend({
578
515
  [this._width() - margin.right, Infinity]
579
516
  ]);
580
517
 
518
+ // Apply svg mask on multi-track segments (BETA)
519
+ this._mask.selectAll(".gap").remove()
520
+ this._maskGaps.forEach((d, i) => {
521
+ if(i >= this._maskGaps.length - 2) return;
522
+ let d1 = this._data[this._maskGaps[i]];
523
+ let d2 = this._data[this._maskGaps[i + 1]];
524
+ let x1 = this._x(this._data[this._findIndexForLatLng(d1.latlng)].dist);
525
+ let x2 = this._x(this._data[this._findIndexForLatLng(d2.latlng)].dist);
526
+ this._mask
527
+ .append("rect")
528
+ .attr("x", x1)
529
+ .attr("y", 0)
530
+ .attr("width", x2 - x1 )
531
+ .attr("height", this._height())
532
+ .attr('class', 'gap')
533
+ .attr('fill-opacity', '0.8')
534
+ .attr("fill", 'black'); // black = hide
535
+ });
581
536
  },
582
537
 
583
538
  _updateLegend: function () {
584
539
 
585
- if (this.options.legend == false) return;
540
+ if (this.options.legend === false) return;
586
541
 
587
542
  let legends = this._props.legendItems;
588
543
  let legend;
@@ -654,8 +609,8 @@ export var Chart = L.Class.extend({
654
609
  }
655
610
 
656
611
  // Apply d3-zoom (bind <clipPath> mask)
657
- if (this._clipPath) {
658
- path.attr('clip-path', 'url(#' + this._clipPath.attr('id') + ')');
612
+ if (this._mask) {
613
+ path.attr('mask', 'url(#' + this._mask.attr('id') + ')');
659
614
  }
660
615
 
661
616
  });
@@ -664,16 +619,10 @@ export var Chart = L.Class.extend({
664
619
  _updateScale: function() {
665
620
  if (this.zooming) return { x: this._x, y: this._y };
666
621
 
667
- let opts = this.options;
668
-
669
- let scales = this._scales;
670
- let d = this._domains;
671
- let r = this._ranges;
672
-
673
- for (let i in scales) {
674
- scales[i]
675
- .domain(d[i](this._data))
676
- .range(r[i](this._width(), this._height()))
622
+ for (let i in this._scales) {
623
+ this._scales[i]
624
+ .domain(this._domains[i](this._data))
625
+ .range(this._ranges[i](this._width(), this._height()))
677
626
  }
678
627
 
679
628
  return { x: this._x, y: this._y };
@@ -772,31 +721,19 @@ export var Chart = L.Class.extend({
772
721
  * Display distance and altitude level ("focus-rect").
773
722
  */
774
723
  _showDiagramIndicator: function(item, xCoordinate) {
775
- let opts = this.options;
776
- let yCoordinate = this._y(item[opts.yAttr]);
777
-
778
- this._focusG.attr("display", null);
779
-
780
- this._focusline.call(
781
- D3.MouseFocusLine({
724
+ this._tooltip
725
+ .attr("display", null)
726
+ .call(D3.Tooltip({
782
727
  xCoord: xCoordinate,
783
- height: this._height()
784
- })
785
- );
786
-
787
- this._focuslabel.call(
788
- D3.MouseFocusLabel({
789
- xCoord: xCoordinate,
790
- yCoord: yCoordinate,
728
+ yCoord: this._y(item[this.options.yAttr]),
791
729
  height: this._height(),
792
730
  width : this._width(),
793
- labels: this._props.focusLabels,
731
+ labels: this._props.tooltipItems,
794
732
  item: item
795
- })
796
- );
733
+ }));
797
734
  },
798
735
 
799
736
  _hideDiagramIndicator: function() {
800
- this._focusG.attr("display", 'none');
737
+ this._tooltip.attr("display", 'none');
801
738
  },
802
739
  });