@raruto/leaflet-elevation 2.4.0 → 2.5.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.
@@ -1,96 +1,96 @@
1
- import * as D3 from './d3.js';
2
-
3
- const _ = L.Control.Elevation.Utils;
4
-
5
- export var Marker = L.Class.extend({
6
-
7
- initialize(options, control) {
8
- this.options = options;
9
- this.control = control
10
-
11
- switch(this.options.marker) {
12
- case 'elevation-line':
13
- // this._container = d3.create("g").attr("class", "height-focus-group");
14
- break;
15
- case 'position-marker':
16
- // this._marker = L.circleMarker([0, 0], { pane: 'overlayPane', radius: 6, fillColor: '#fff', fillOpacity:1, color: '#000', weight:1, interactive: false });
17
- this._marker = L.marker([0, 0], { icon: this.options.markerIcon, zIndexOffset: 1000000, interactive: false });
18
- break;
19
- }
20
-
21
- this._labels = {};
22
-
23
- return this;
24
- },
25
-
26
- addTo(map) {
27
- this._map = map;
28
- switch(this.options.marker) {
29
- case 'elevation-line': this._container = d3.select(map.getPane('elevationPane')).select("svg > g").call(D3.PositionMarker({})); break;
30
- case 'position-marker': this._marker.addTo(map, { pane: 'overlayPane' }); break;
31
- }
32
- return this;
33
- },
34
-
35
- /**
36
- * Update position marker ("leaflet-marker").
37
- */
38
- update(props) {
39
- if (props) this._props = props;
40
- else props = this._props;
41
-
42
- if(!props) return;
43
-
44
- if (props.options) this.options = props.options;
45
- if (!this._map) this.addTo(props.map);
46
-
47
- this._latlng = props.item.latlng;
48
-
49
- switch(this.options.marker) {
50
- case 'elevation-line':
51
- if (this._container) {
52
- let point = this._map.latLngToLayerPoint(this._latlng);
53
- point = L.extend({}, props.item, this._map._rotate ? this._map.rotatedPointToMapPanePoint(point) : point);
54
-
55
- let yMax = (this.control._height() / props.yCoordMax * point[this.options.yAttr]);
56
-
57
- if(!isFinite(yMax) || isNaN(yMax)) yMax = 0;
58
-
59
- this._container.classed("leaflet-hidden", false);
60
- this._container.call(D3.PositionMarker({
61
- theme : this.options.theme,
62
- xCoord: point.x,
63
- yCoord: point.y,
64
- length: point.y - yMax, // normalized Y
65
- labels: this._labels,
66
- item: point,
67
- }));
68
- }
69
- break;
70
- case 'position-marker':
71
- _.removeClass(this._marker.getElement(), 'leaflet-hidden');
72
- this._marker.setLatLng(this._latlng);
73
- break;
74
- }
75
- },
76
-
77
- /*
78
- * Hides the position/height indicator marker drawn onto the map
79
- */
80
- remove() {
81
- this._props = null;
82
- switch(this.options.marker) {
83
- case 'elevation-line': this._container && this._container.classed("leaflet-hidden", true); break;
84
- case 'position-marker': _.addClass(this._marker.getElement(), 'leaflet-hidden'); break;
85
- }
86
- },
87
-
88
- getLatLng() {
89
- return this._latlng;
90
- },
91
-
92
- _registerTooltip(props) {
93
- this._labels[props.name] = props;
94
- }
95
-
96
- });
1
+ import * as D3 from './d3.js';
2
+
3
+ const _ = L.Control.Elevation.Utils;
4
+
5
+ export var Marker = L.Class.extend({
6
+
7
+ initialize(options, control) {
8
+ this.options = options;
9
+ this.control = control
10
+
11
+ switch(this.options.marker) {
12
+ case 'elevation-line':
13
+ // this._container = d3.create("g").attr("class", "height-focus-group");
14
+ break;
15
+ case 'position-marker':
16
+ // this._marker = L.circleMarker([0, 0], { pane: 'overlayPane', radius: 6, fillColor: '#fff', fillOpacity:1, color: '#000', weight:1, interactive: false });
17
+ this._marker = L.marker([0, 0], { icon: this.options.markerIcon, zIndexOffset: 1000000, interactive: false });
18
+ break;
19
+ }
20
+
21
+ this._labels = {};
22
+
23
+ return this;
24
+ },
25
+
26
+ addTo(map) {
27
+ this._map = map;
28
+ switch(this.options.marker) {
29
+ case 'elevation-line': this._container = d3.select(map.getPane('elevationPane')).select("svg > g").call(D3.PositionMarker({})); break;
30
+ case 'position-marker': this._marker.addTo(map, { pane: 'overlayPane' }); break;
31
+ }
32
+ return this;
33
+ },
34
+
35
+ /**
36
+ * Update position marker ("leaflet-marker").
37
+ */
38
+ update(props) {
39
+ if (props) this._props = props;
40
+ else props = this._props;
41
+
42
+ if (!props) return;
43
+
44
+ if (props.options) this.options = props.options;
45
+ if (!this._map) this.addTo(props.map);
46
+
47
+ this._latlng = props.item.latlng;
48
+
49
+ switch(this.options.marker) {
50
+ case 'elevation-line':
51
+ if (this._container) {
52
+ let point = this._map.latLngToLayerPoint(this._latlng);
53
+ point = L.extend({}, props.item, this._map._rotate ? this._map.rotatedPointToMapPanePoint(point) : point);
54
+
55
+ let yMax = (this.control._height() / props.yCoordMax * point[this.options.yAttr]);
56
+
57
+ if (!isFinite(yMax) || isNaN(yMax)) yMax = 0;
58
+
59
+ this._container.classed("leaflet-hidden", false);
60
+ this._container.call(D3.PositionMarker({
61
+ theme : this.options.theme,
62
+ xCoord: point.x,
63
+ yCoord: point.y,
64
+ length: point.y - yMax, // normalized Y
65
+ labels: this._labels,
66
+ item: point,
67
+ }));
68
+ }
69
+ break;
70
+ case 'position-marker':
71
+ _.removeClass(this._marker.getElement(), 'leaflet-hidden');
72
+ this._marker.setLatLng(this._latlng);
73
+ break;
74
+ }
75
+ },
76
+
77
+ /*
78
+ * Hides the position/height indicator marker drawn onto the map
79
+ */
80
+ remove() {
81
+ this._props = null;
82
+ switch(this.options.marker) {
83
+ case 'elevation-line': this._container && this._container.classed("leaflet-hidden", true); break;
84
+ case 'position-marker': _.addClass(this._marker.getElement(), 'leaflet-hidden'); break;
85
+ }
86
+ },
87
+
88
+ getLatLng() {
89
+ return this._latlng;
90
+ },
91
+
92
+ _registerTooltip(props) {
93
+ this._labels[props.name] = props;
94
+ }
95
+
96
+ });
package/src/control.js CHANGED
@@ -14,7 +14,7 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
14
14
  __mileFactor: 0.621371, // 1 km = (0.621371 mi)
15
15
  __footFactor: 3.28084, // 1 m = (3.28084 ft)
16
16
  __D3: 'https://unpkg.com/d3@7.8.4/dist/d3.min.js',
17
- __TOGEOJSON: 'https://unpkg.com/@tmcw/togeojson@5.6.0/dist/togeojson.umd.js',
17
+ __TOGEOJSON: 'https://unpkg.com/@tmcw/togeojson@5.6.2/dist/togeojson.umd.js',
18
18
  __LGEOMUTIL: 'https://unpkg.com/leaflet-geometryutil@0.10.1/src/leaflet.geometryutil.js',
19
19
  __LALMOSTOVER: 'https://unpkg.com/leaflet-almostover@1.0.1/src/leaflet.almostover.js',
20
20
  __LHOTLINE: '../libs/leaflet-hotline.min.js',
@@ -214,6 +214,9 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
214
214
  * Javascript scripts downloader (lazy loader)
215
215
  */
216
216
  import(src, condition) {
217
+ if (Array.isArray(src)) {
218
+ return Promise.all(src.map(m => this.import(m)));
219
+ }
217
220
  switch(src) {
218
221
  case this.__D3: condition = typeof d3 !== 'object'; break;
219
222
  case this.__TOGEOJSON: condition = typeof toGeoJSON !== 'object'; break;
@@ -401,38 +404,36 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
401
404
  * Initialize "L.AlmostOver" integration
402
405
  */
403
406
  _initAlmostOverHandler(map, layer) {
404
- return (map && this.options.almostOver && !L.Browser.mobile) ? Promise.all([
405
- this.import(this.__LGEOMUTIL),
406
- this.import(this.__LALMOSTOVER)
407
- ]).then(() => {
408
- map.addHandler('almostOver', L.Handler.AlmostOver)
409
- if (L.GeometryUtil && map.almostOver && map.almostOver.enabled()) {
410
- map.almostOver.addLayer(layer);
411
- map
412
- .on('almost:move', this._onMouseMoveLayer, this)
413
- .on('almost:out', this._onMouseOut, this);
414
- this.once('eledata_clear', () => {
415
- map.almostOver.removeLayer(layer);
407
+ return (map && this.options.almostOver && !L.Browser.mobile) ? this.import([this.__LGEOMUTIL, this.__LALMOSTOVER])
408
+ .then(() => {
409
+ map.addHandler('almostOver', L.Handler.AlmostOver)
410
+ if (L.GeometryUtil && map.almostOver && map.almostOver.enabled()) {
411
+ map.almostOver.addLayer(layer);
416
412
  map
417
- .off('almost:move', this._onMouseMoveLayer, this)
418
- .off('almost:out', this._onMouseOut, this);
419
- })
420
- }
421
- }) : Promise.resolve();
413
+ .on('almost:move', this._onMouseMoveLayer, this)
414
+ .on('almost:out', this._onMouseOut, this);
415
+ this.once('eledata_clear', () => {
416
+ map.almostOver.removeLayer(layer);
417
+ map
418
+ .off('almost:move', this._onMouseMoveLayer, this)
419
+ .off('almost:out', this._onMouseOut, this);
420
+ })
421
+ }
422
+ }) : Promise.resolve();
422
423
  },
423
424
 
424
425
  /**
425
426
  * Initialize "L.DistanceMarkers" integration
426
427
  */
427
428
  _initDistanceMarkers() {
428
- return this.options.distanceMarkers ? Promise.all([this.import(this.__LGEOMUTIL), this.import(this.__LDISTANCEM)]) : Promise.resolve();
429
+ return this.options.distanceMarkers ? this.import([this.__LGEOMUTIL, this.__LDISTANCEM]) : Promise.resolve();
429
430
  },
430
431
 
431
432
  /**
432
433
  * Initialize "L.Control.EdgeScale" integration
433
434
  */
434
435
  _initEdgeScale(map) {
435
- return this.options.edgeScale ? Promise.all([this.import(this.__LEDGESCALE)])
436
+ return this.options.edgeScale ? this.import(this.__LEDGESCALE)
436
437
  .then(() => {
437
438
  map.edgeScaleControl = map.edgeScaleControl || L.control.edgeScale('boolean' !== typeof this.options.edgeScale ? this.options.edgeScale : {}).addTo(map);
438
439
  }) : Promise.resolve();
@@ -443,7 +444,7 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
443
444
  return this.options.hotline ? this.import(this.__LHOTLINE)
444
445
  .then(() => {
445
446
  layer.eachLayer((trkseg) => {
446
- if(trkseg.feature.geometry.type != "Point") {
447
+ if (trkseg.feature.geometry.type != "Point") {
447
448
  let geo = L.geoJson(trkseg.toGeoJSON(), { coordsToLatLng: (coords) => L.latLng(coords[0], coords[1], coords[2] * (this.options.altitudeFactor || 1))});
448
449
  let line = L.hotline(geo.toGeoJSON().features[0].geometry.coordinates, {
449
450
  renderer: L.Hotline.renderer(),
@@ -618,10 +619,9 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
618
619
  if (opts._maxWidth > clientWidth) opts.width = clientWidth - 30;
619
620
  }
620
621
 
621
- Promise.all([
622
- this.import(this.__D3),
623
- this.import(this.__LCHART)
624
- ]).then((m) => {
622
+ this
623
+ .import([this.__D3, this.__LCHART])
624
+ .then((m) => {
625
625
 
626
626
  let chart = this._chart = new (m[1] || Elevation).Chart(opts, this);
627
627
 
@@ -689,13 +689,11 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
689
689
  if (this._renderer) this._renderer.remove()
690
690
  this._renderer = L.svg({ pane: "elevationPane" }).addTo(this._map); // default leaflet svg renderer
691
691
 
692
- Promise.all([
693
- this.import(this.__D3),
694
- this.import(this.__LMARKER)
695
- ]).then((m) => {
696
- this._marker = new (m[1] || Elevation).Marker(this.options, this);
697
- this.fire("elechart_marker");
698
- });
692
+ this.import([this.__D3, this.__LMARKER])
693
+ .then((m) => {
694
+ this._marker = new (m[1] || Elevation).Marker(this.options, this);
695
+ this.fire("elechart_marker");
696
+ });
699
697
  },
700
698
 
701
699
  /**
@@ -829,7 +827,7 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
829
827
  },
830
828
 
831
829
  _onKeyDown({key}) {
832
- if(!this.options.detached && key === "Escape"){
830
+ if (!this.options.detached && key === "Escape"){
833
831
  this._collapse()
834
832
  };
835
833
  },
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @see https://github.com/Raruto/leaflet-elevation/issues/211
3
- *
3
+ *
4
4
  * @example
5
5
  * ```js
6
6
  * L.control.Elevation({ handlers: [ 'Labels' ], labelsRotation: 25, labelsAlign: 'start' })
@@ -8,7 +8,7 @@
8
8
  */
9
9
  export function Labels() {
10
10
 
11
- this.on('elechart_init elechart_axis', function(e) {
11
+ this.on('elechart_updated', function(e) {
12
12
 
13
13
  const pointG = this._chart._chart.pane('point');
14
14
 
@@ -29,7 +29,7 @@ export function Labels() {
29
29
  .attr("stroke", '#000')
30
30
  .attr("stroke-width", '1.1');
31
31
 
32
- } else if (!isNaN(textRotation)) {
32
+ } else if (!isNaN(textRotation)) {
33
33
 
34
34
  pointG.selectAll('text')
35
35
  .attr("dx", "4px")
@@ -0,0 +1,122 @@
1
+ /**
2
+ * @see https://github.com/Raruto/leaflet-elevation/issues/251
3
+ *
4
+ * @example
5
+ * ```js
6
+ * L.control.Elevation({
7
+ * altitude: true,
8
+ * distance: true,
9
+ * handlers: [ 'Altitude', 'Distance', 'LinearGradient', ],
10
+ * linearGradient: {
11
+ * attr: 'z',
12
+ * path: 'altitude',
13
+ * range: { 0.0: '#008800', 0.5: '#ffff00', 1.0: '#ff0000' },
14
+ * min: 'elevation_min',
15
+ * max: 'elevation_max',
16
+ * },
17
+ * })
18
+ * ```
19
+ */
20
+ export function LinearGradient() {
21
+
22
+ if (!this.options.linearGradient) {
23
+ return {};
24
+ }
25
+
26
+ const _ = L.Control.Elevation.Utils;
27
+
28
+ /**
29
+ * Initialize gradient color palette.
30
+ */
31
+ const get_palette = function ({range, min, max, depth = 256}) {
32
+ const canvas = document.createElement('canvas'),
33
+ ctx = canvas.getContext('2d'),
34
+ gradient = ctx.createLinearGradient(0, 0, 0, depth);
35
+
36
+ canvas.width = 1;
37
+ canvas.height = depth;
38
+
39
+ for (let i in range) {
40
+ gradient.addColorStop(i, range[i]);
41
+ }
42
+
43
+ ctx.fillStyle = gradient;
44
+ ctx.fillRect(0, 0, 1, depth);
45
+
46
+ const { data } = ctx.getImageData(0, 0, 1, depth);
47
+
48
+ return {
49
+ /**
50
+ * Gets the RGB values of a given z value of the current palette.
51
+ *
52
+ * @param {number} value - Value to get the color for, should be between min and max.
53
+ * @returns {string} The RGB values as `rgb(r, g, b)` string
54
+ */
55
+ getRGBColor(value) {
56
+ const idx = Math.floor(Math.min(Math.max((value - min) / (max - min), 0), 0.999) * depth) * 4;
57
+ return 'rgb(' + [data[idx], data[idx + 1], data[idx + 2]].join(',') + ')';
58
+ }
59
+ };
60
+ };
61
+
62
+ const { preferCanvas } = this.options;
63
+ const { attr, path: path_name, range, min, max } = L.extend({
64
+ attr: 'z',
65
+ path: 'altitude',
66
+ range: { 0.0: '#008800', 0.5: '#ffff00', 1.0: '#ff0000' },
67
+ min: 'elevation_min',
68
+ max: 'elevation_max',
69
+ }, (true === this.options.linearGradient) ? {} : this.options.linearGradient);
70
+
71
+ const gradient_id = path_name + '-gradient-' + _.randomId();
72
+ const legend_id = 'legend-' + gradient_id;
73
+
74
+ // Charte profile gradient
75
+ this.on('elechart_axis', () => {
76
+ if (!this._data.length) return;
77
+
78
+ const chart = this._chart;
79
+ const path = chart._paths[path_name];
80
+ const { defs } = chart._chart.utils;
81
+
82
+ const palette = get_palette({
83
+ min: isFinite(this.track_info[min]) ? this.track_info[min] : 0,
84
+ max: isFinite(this.track_info[max]) ? this.track_info[max] : 1,
85
+ range,
86
+ });
87
+
88
+ let gradient;
89
+
90
+ if (preferCanvas) {
91
+ /** ref: `path.__fillStyle` within L.Control.Elevation.Utils::drawCanvas(ctx, path) */
92
+ path.__fillStyle = gradient = chart._context.createLinearGradient(0, 0, chart._width(), 0);
93
+ } else {
94
+ defs.select('#' + gradient_id).remove();
95
+ gradient = defs.append('svg:linearGradient').attr('id', gradient_id);
96
+ gradient.addColorStop = function(offset, color) { gradient.append('svg:stop').attr('offset', offset).attr('stop-color', color) };
97
+ path.attr('fill', 'url(#' + gradient_id + ')').classed('area', false);
98
+ }
99
+
100
+ // Generate gradient for each segment picking colors from palette
101
+ for (let i = 0, data = this._data; i < data.length; i++) {
102
+ gradient.addColorStop((i) / data.length, palette.getRGBColor(data[i][attr]));
103
+ }
104
+
105
+ });
106
+
107
+ // Legend item gradient
108
+ this.on('elechart_updated', () => {
109
+ const chart = this._chart;
110
+ const { defs } = chart._chart.utils;
111
+ defs.select('#' + legend_id).remove();
112
+ const legendGradient = defs.append('svg:linearGradient').attr('id', legend_id);
113
+ Object.keys(range).sort().forEach(i => legendGradient.append('svg:stop').attr('offset', i).attr('stop-color', range[i]));
114
+
115
+ chart._container
116
+ .select('.legend-' + path_name + ' > rect')
117
+ .attr('fill', 'url(#' + legend_id + ')')
118
+ .classed('area', false);
119
+ });
120
+
121
+ return { };
122
+ }
@@ -1,103 +1,103 @@
1
- export function Time() {
2
-
3
- const _ = L.Control.Elevation.Utils;
4
-
5
- let opts = this.options;
6
- let time = {};
7
-
8
- time.label = opts.timeLabel || 't';
9
- opts.timeFactor = opts.timeFactor || 3600;
10
-
11
- /**
12
- * Common AVG speeds:
13
- * ----------------------
14
- * slow walk = 1.8 km/h
15
- * walking = 3.6 km/h <-- default: 3.6
16
- * running = 10.8 km/h
17
- * cycling = 18 km/h
18
- * driving = 72 km/h
19
- * ----------------------
20
- */
21
- this._timeAVGSpeed = (opts.timeAVGSpeed || 3.6) * (opts.speedFactor || 1);
22
-
23
- if (!opts.timeFormat) {
24
- opts.timeFormat = (time) => (new Date(time)).toLocaleString().replaceAll('/', '-').replaceAll(',', ' ');
25
- } else if (opts.timeFormat == 'time') {
26
- opts.timeFormat = (time) => (new Date(time)).toLocaleTimeString();
27
- } else if (opts.timeFormat == 'date') {
28
- opts.timeFormat = (time) => (new Date(time)).toLocaleDateString();
29
- }
30
-
31
- opts.xTimeFormat = opts.xTimeFormat || ((t) => _.formatTime(t).split("'")[0]);
32
-
33
- return {
34
- name: 'time',
35
- required: (this.options.speed || this.options.acceleration || this.options.timestamps),
36
- coordinateProperties: ["coordTimes", "times", "time"],
37
- coordPropsToMeta: _.parseDate,
38
- pointToAttr: function(point, i) {
39
- // Add missing timestamps (see: options.timeAVGSpeed)
40
- if (!point.meta || !point.meta.time) {
41
- point.meta = point.meta || {};
42
- if(i > 0) {
43
- let dx = (this._data[i].dist - this._data[i - 1].dist);
44
- let t0 = this._data[i - 1].time.getTime();
45
- point.meta.time = new Date(t0 + ( dx / this._timeAVGSpeed) * this.options.timeFactor * 1000);
46
- } else {
47
- point.meta.time = new Date(Date.now())
48
- }
49
- }
50
- // Handle timezone offset
51
- let time = (point.meta.time.getTime() - point.meta.time.getTimezoneOffset() * 60 * 1000 !== 0) ? point.meta.time : 0;
52
- // Update duration
53
- this._data[i].duration = i > 0 ? (this._data[i - 1].duration || 0) + Math.abs(time - this._data[i - 1].time) : 0;
54
- return time;
55
- },
56
- onPointAdded: (_, i) => this.track_info.time = this._data[i].duration,
57
- scale: (opts.time && opts.time != "summary" && !L.Browser.mobile) && {
58
- axis : "x",
59
- position : "top",
60
- scale : {
61
- attr : "duration",
62
- min : 0,
63
- },
64
- label : time.label,
65
- labelY : -10,
66
- labelX : () => this._width(),
67
- name : "time",
68
- ticks : () => _.clamp(this._chart._xTicks() / 2, [4, +Infinity]),
69
- tickFormat : (d) => (d == 0 ? '' : opts.xTimeFormat(d)),
70
- onAxisMount: axis => {
71
- axis.select(".domain")
72
- .remove();
73
- axis.selectAll("text")
74
- .attr('opacity', 0.65)
75
- .style('font-family', 'Monospace')
76
- .style('font-size', '110%');
77
- axis.selectAll(".tick line")
78
- .attr('y2', this._height())
79
- .attr('stroke-dasharray', 2)
80
- .attr('opacity', 0.75);
81
- }
82
- },
83
- tooltips: [
84
- (this.options.time) && {
85
- name: 'time',
86
- chart: (item) => L._("T: ") + _.formatTime(item.duration || 0),
87
- order: 20
88
- },
89
- (this.options.timestamps) && {
90
- name: 'date',
91
- chart: (item) => L._("t: ") + this.options.timeFormat(item.time),
92
- order: 21,
93
- }
94
- ],
95
- summary: (this.options.time) && {
96
- "tottime" : {
97
- label: "Total Time: ",
98
- value: (track) => _.formatTime(track.time || 0),
99
- order: 20
100
- }
101
- }
102
- };
1
+ export function Time() {
2
+
3
+ const _ = L.Control.Elevation.Utils;
4
+
5
+ let opts = this.options;
6
+ let time = {};
7
+
8
+ time.label = opts.timeLabel || 't';
9
+ opts.timeFactor = opts.timeFactor || 3600;
10
+
11
+ /**
12
+ * Common AVG speeds:
13
+ * ----------------------
14
+ * slow walk = 1.8 km/h
15
+ * walking = 3.6 km/h <-- default: 3.6
16
+ * running = 10.8 km/h
17
+ * cycling = 18 km/h
18
+ * driving = 72 km/h
19
+ * ----------------------
20
+ */
21
+ this._timeAVGSpeed = (opts.timeAVGSpeed || 3.6) * (opts.speedFactor || 1);
22
+
23
+ if (!opts.timeFormat) {
24
+ opts.timeFormat = (time) => (new Date(time)).toLocaleString().replaceAll('/', '-').replaceAll(',', ' ');
25
+ } else if (opts.timeFormat == 'time') {
26
+ opts.timeFormat = (time) => (new Date(time)).toLocaleTimeString();
27
+ } else if (opts.timeFormat == 'date') {
28
+ opts.timeFormat = (time) => (new Date(time)).toLocaleDateString();
29
+ }
30
+
31
+ opts.xTimeFormat = opts.xTimeFormat || ((t) => _.formatTime(t).split("'")[0]);
32
+
33
+ return {
34
+ name: 'time',
35
+ required: (this.options.speed || this.options.acceleration || this.options.timestamps),
36
+ coordinateProperties: ["coordTimes", "times", "time"],
37
+ coordPropsToMeta: _.parseDate,
38
+ pointToAttr: function(point, i) {
39
+ // Add missing timestamps (see: options.timeAVGSpeed)
40
+ if (!point.meta || !point.meta.time) {
41
+ point.meta = point.meta || {};
42
+ if (i > 0) {
43
+ let dx = (this._data[i].dist - this._data[i - 1].dist);
44
+ let t0 = this._data[i - 1].time.getTime();
45
+ point.meta.time = new Date(t0 + ( dx / this._timeAVGSpeed) * this.options.timeFactor * 1000);
46
+ } else {
47
+ point.meta.time = new Date(Date.now())
48
+ }
49
+ }
50
+ // Handle timezone offset
51
+ let time = (point.meta.time.getTime() - point.meta.time.getTimezoneOffset() * 60 * 1000 !== 0) ? point.meta.time : 0;
52
+ // Update duration
53
+ this._data[i].duration = i > 0 ? (this._data[i - 1].duration || 0) + Math.abs(time - this._data[i - 1].time) : 0;
54
+ return time;
55
+ },
56
+ onPointAdded: (_, i) => this.track_info.time = this._data[i].duration,
57
+ scale: (opts.time && opts.time != "summary" && !L.Browser.mobile) && {
58
+ axis : "x",
59
+ position : "top",
60
+ scale : {
61
+ attr : "duration",
62
+ min : 0,
63
+ },
64
+ label : time.label,
65
+ labelY : -10,
66
+ labelX : () => this._width(),
67
+ name : "time",
68
+ ticks : () => _.clamp(this._chart._xTicks() / 2, [4, +Infinity]),
69
+ tickFormat : (d) => (d == 0 ? '' : opts.xTimeFormat(d)),
70
+ onAxisMount: axis => {
71
+ axis.select(".domain")
72
+ .remove();
73
+ axis.selectAll("text")
74
+ .attr('opacity', 0.65)
75
+ .style('font-family', 'Monospace')
76
+ .style('font-size', '110%');
77
+ axis.selectAll(".tick line")
78
+ .attr('y2', this._height())
79
+ .attr('stroke-dasharray', 2)
80
+ .attr('opacity', 0.75);
81
+ }
82
+ },
83
+ tooltips: [
84
+ (this.options.time) && {
85
+ name: 'time',
86
+ chart: (item) => L._("T: ") + _.formatTime(item.duration || 0),
87
+ order: 20
88
+ },
89
+ (this.options.timestamps) && {
90
+ name: 'date',
91
+ chart: (item) => L._("t: ") + this.options.timeFormat(item.time),
92
+ order: 21,
93
+ }
94
+ ],
95
+ summary: (this.options.time) && {
96
+ "tottime" : {
97
+ label: "Total Time: ",
98
+ value: (track) => _.formatTime(track.time || 0),
99
+ order: 20
100
+ }
101
+ }
102
+ };
103
103
  }