@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,641 +1,649 @@
1
- export const Area = ({
2
- width,
3
- height,
4
- xAttr,
5
- yAttr,
6
- scaleX,
7
- scaleY,
8
- interpolation = "curveLinear"
9
- }) => {
10
- return d3.area()
11
- .curve(typeof interpolation === 'string' ? d3[interpolation] : interpolation)
12
- .x(d => (d.xDiagCoord = scaleX(d[xAttr])))
13
- .y0(height)
14
- .y1(d => scaleY(d[yAttr]));
15
- };
16
-
17
- export const Path = ({
18
- name,
19
- color,
20
- strokeColor,
21
- strokeOpacity,
22
- fillOpacity,
23
- className = ''
24
- }) => {
25
- let path = d3.create('svg:path')
26
-
27
- if (name) path.classed(name + ' ' + className, true);
28
-
29
- path.style("pointer-events", "none");
30
-
31
- path
32
- .attr("fill", color || '#3366CC')
33
- .attr("stroke", strokeColor || '#000')
34
- .attr("stroke-opacity", strokeOpacity || '1')
35
- .attr("fill-opacity", fillOpacity || '0.8');
36
-
37
- return path;
38
- };
39
-
40
- export const Axis = ({
41
- type = "axis",
42
- tickSize = 6,
43
- tickPadding = 3,
44
- position,
45
- height,
46
- width,
47
- axis,
48
- scale,
49
- ticks,
50
- tickFormat,
51
- label,
52
- labelX,
53
- labelY,
54
- name = "",
55
- onAxisMount,
56
- }) => {
57
- return g => {
58
- let [w, h] = [0, 0];
59
- if (position == "bottom") h = height;
60
- if (position == "right") w = width;
61
-
62
- if (axis == "x" && type == "grid") {
63
- tickSize = -height;
64
- } else if (axis == "y" && type == "grid") {
65
- tickSize = -width;
66
- }
67
-
68
- let axisScale = d3["axis" + position.replace(/\b\w/g, l => l.toUpperCase())]()
69
- .scale(scale)
70
- .ticks(typeof ticks === 'function' ? ticks() : ticks)
71
- .tickPadding(tickPadding)
72
- .tickSize(tickSize)
73
- .tickFormat(tickFormat);
74
-
75
- let axisGroup = g.append("g")
76
- .attr("class", [axis, type, position, name].join(" "))
77
- .attr("transform", "translate(" + w + "," + h + ")")
78
- .call(axisScale);
79
-
80
- if (label) {
81
- axisGroup.append("svg:text")
82
- .attr("x", labelX)
83
- .attr("y", labelY)
84
- .text(label);
85
- }
86
-
87
- if (onAxisMount) {
88
- axisGroup.call(onAxisMount);
89
- }
90
-
91
- return axisGroup;
92
- };
93
- };
94
-
95
- export const Grid = (props) => {
96
- props.type = "grid";
97
- return Axis(props);
98
- };
99
-
100
- export const PositionMarker = ({
101
- theme,
102
- xCoord = 0,
103
- yCoord = 0,
104
- labels = {},
105
- item = {},
106
- length = 0,
107
- }) => {
108
- return g => {
109
-
110
- g.attr("class", "height-focus-group");
111
-
112
- let line = g.select('.height-focus.line');
113
- let circle = g.select('.height-focus.circle-lower');
114
- let text = g.select('.height-focus-label');
115
-
116
- if (!line.node()) line = g.append('svg:line');
117
- if (!circle.node()) circle = g.append('svg:circle');
118
- if (!text.node()) text = g.append('svg:text');
119
-
120
- if (isNaN(xCoord) || isNaN(yCoord)) return g;
121
-
122
- circle
123
- .attr("class", theme + " height-focus circle-lower")
124
- .attr("transform", "translate(" + xCoord + "," + yCoord + ")")
125
- .attr("r", 6)
126
- .attr("cx", 0)
127
- .attr("cy", 0);
128
-
129
- line
130
- .attr("class", theme + " height-focus line")
131
- .attr("x1", xCoord)
132
- .attr("x2", xCoord)
133
- .attr("y1", yCoord)
134
- .attr("y2", length);
135
-
136
- text
137
- .attr("class", theme + " height-focus-label")
138
- .style("pointer-events", "none")
139
- .attr("x", xCoord + 5)
140
- .attr("y", length);
141
-
142
- let label;
143
-
144
- Object
145
- .keys(labels)
146
- .sort((a, b) => labels[a].order - labels[b].order) // TODO: any performance issues?
147
- .forEach((i)=> {
148
- label = text.select(".height-focus-" + labels[i].name);
149
-
150
- if (!label.size()) {
151
- label = text.append("svg:tspan")
152
- .attr("class", "height-focus-" + labels[i].name /*+ " " + "order-" + labels[i].order*/)
153
- .attr("dy", "1.5em");
154
- }
155
-
156
- label.text(typeof labels[i].value !== "function" ? labels[i].value : labels[i].value(item));
157
-
158
- });
159
-
160
- text.select('tspan').attr("dy", text.selectAll('tspan').size() > 1 ? "-1.5em" : "0em" );
161
- text.selectAll('tspan').attr("x", xCoord + 5);
162
-
163
- return g;
164
- }
165
- };
166
-
167
- export const LegendItem = ({
168
- name,
169
- label,
170
- width,
171
- height,
172
- margins = {},
173
- color,
174
- path,
175
- className = ''
176
- }) => {
177
- return g => {
178
- g
179
- .attr("class", "legend-item legend-" + name.toLowerCase())
180
- .attr("data-name", name);
181
-
182
- g.on('click.legend', () => d3.select(g.node().ownerSVGElement || g)
183
- .dispatch("legend_clicked", {
184
- detail: {
185
- path: path.node(),
186
- name: name,
187
- legend: g.node(),
188
- enabled: !path.classed('leaflet-hidden'),
189
- }
190
- })
191
- );
192
-
193
- g.append("svg:rect")
194
- .attr("x", (width / 2) - 50)
195
- .attr("y", height + margins.bottom / 2)
196
- .attr("width", 50)
197
- .attr("height", 10)
198
- .attr("fill", color)
199
- .attr("stroke", "#000")
200
- .attr("stroke-opacity", "0.5")
201
- .attr("fill-opacity", "0.25")
202
- .attr("class", className);
203
-
204
- g.append('svg:text')
205
- .text(L._(label || name))
206
- .attr("x", (width / 2) + 5)
207
- .attr("font-size", 10)
208
- .style("font-weight", "700")
209
- .attr('y', height + margins.bottom / 2)
210
- .attr('dy', "0.75em");
211
-
212
- return g;
213
- }
214
- };
215
-
216
- export const LegendSmall = ({
217
- width,
218
- height,
219
- items,
220
- onClick
221
- }) => {
222
- return g => {
223
- let idx = 0;
224
-
225
- g.data([{
226
- x: width,
227
- y: height + 40
228
- }]).attr("transform", d => "translate(" + d.x + "," + d.y + ")").classed('legend-switcher');
229
-
230
- // let label = g.selectAll(".legend-switcher-label") .data([{ idx: 0 }]);
231
- let label = g.append('svg:text')
232
- .attr("class", "legend-switcher-label")
233
- .attr("text-anchor", "end")
234
- .attr("x", -25)
235
- .attr("y", 5)
236
- .on("mousedown", (e, d) => setIdx(L.Util.wrapNum((idx + 1), [0, items.length])));
237
-
238
- let symbol = g.selectAll(".legend-switcher-symbol").data([
239
- { type: d3.symbolTriangle, x: 0, y: 3, angle: 0, size: 50, id: "down" },
240
- { type: d3.symbolTriangle, x: -13, y: 0, angle: 180, size: 50, id: "up" }
241
- ]);
242
-
243
- symbol.exit().remove();
244
- label.exit().remove();
245
-
246
- symbol.enter()
247
- .append("svg:path")
248
- .attr("class", "legend-switcher-symbol")
249
- .attr("cursor", 'pointer')
250
- .attr("fill", "#000")
251
- .merge(symbol)
252
- .attr("d",
253
- d3.symbol()
254
- .type(d => d.type)
255
- .size(d => d.size)
256
- )
257
- .attr("transform", d => "translate(" + d.x + "," + d.y + ") rotate(" + d.angle + ")")
258
- .on("mousedown", (e, d) => setIdx(L.Util.wrapNum((d.id === "up" ? idx + 1 : idx - 1), [0, items.length])));
259
-
260
- const setIdx = (id) => {
261
- idx = id;
262
- // label.enter()
263
- // .append('text')
264
- // .attr("class", "legend-switcher-label")
265
- // .attr("text-anchor", "end")
266
- // .attr("x", -25)
267
- // .attr("y", 5)
268
- // .merge(label.data([{ idx: id }]))
269
- // .text(d => items.length ? (items[idx][0].toUpperCase() + items[idx].slice(1)) : '')
270
- // .on("mousedown", (e, d) => setIdx(L.Util.wrapNum((idx + 1), [0, items.length])));
271
- label.text(items.length ? L._((items[idx][0].toUpperCase() + items[idx].slice(1))) : '');
272
- onClick(items[idx]);
273
- };
274
- setIdx(0);
275
-
276
- return g;
277
- };
278
- };
279
-
280
- export const Tooltip = ({
281
- xCoord,
282
- yCoord,
283
- width,
284
- height,
285
- labels = {},
286
- item = {},
287
- }) => {
288
- return g => {
289
-
290
- let line = g.select('.mouse-focus-line');
291
- let box = g.select('.mouse-focus-label');
292
-
293
- if (!line.node()) line = g.append('svg:line');
294
- if (!box.node()) box = g.append("g");
295
-
296
- let rect = box.select(".mouse-focus-label-rect");
297
- let text = box.select(".mouse-focus-label-text");
298
-
299
- if (!rect.node()) rect = box.append("svg:rect");
300
- if (!text.node()) text = box.append("svg:text");
301
-
302
- // Sets focus-label-text position to the left / right of the mouse-focus-line
303
- let xAlign = 0;
304
- let yAlign = 0;
305
- let bbox = { width: 0, height: 0 };
306
- try { bbox = text.node().getBBox(); } catch (e) { return g; }
307
-
308
- if (xCoord) xAlign = xCoord + (xCoord < width / 2 ? 10 : -bbox.width - 10);
309
- if (yCoord) yAlign = Math.max(yCoord - bbox.height, L.Browser.webkit ? 0 : -Infinity);
310
-
311
- line
312
- .attr('class', 'mouse-focus-line')
313
- .attr('x2', xCoord)
314
- .attr('y2', 0)
315
- .attr('x1', xCoord)
316
- .attr('y1', height);
317
-
318
- box
319
- .attr('class', 'mouse-focus-label');
320
-
321
- rect
322
- .attr("class", "mouse-focus-label-rect")
323
- .attr("x", xAlign - 5)
324
- .attr("y", yAlign - 5)
325
- .attr("width", bbox.width + 10)
326
- .attr("height", bbox.height + 10)
327
- .attr("rx", 3)
328
- .attr("ry", 3);
329
-
330
- text
331
- .attr("class", "mouse-focus-label-text")
332
- .style("font-weight", "700")
333
- .attr("y", yAlign);
334
-
335
- let label;
336
-
337
- Object
338
- .keys(labels)
339
- .sort((a, b) => labels[a].order - labels[b].order) // TODO: any performance issues?
340
- .forEach((i)=> {
341
- label = text.select(".mouse-focus-label-" + labels[i].name);
342
-
343
- if (!label.size()) {
344
- label = text.append("svg:tspan", ".mouse-focus-label-x")
345
- .attr("class", "mouse-focus-label-" + labels[i].name /*+ " " + "order-" + labels[i].order*/)
346
- .attr("dy", "1.5em");
347
- }
348
-
349
- label.text(typeof labels[i].value !== "function" ? labels[i].value : labels[i].value(item));
350
- });
351
-
352
- text.select('tspan').attr("dy", "1em");
353
- text.selectAll('tspan').attr("x", xAlign);
354
-
355
- return g;
356
- };
357
- };
358
-
359
- export const Ruler = ({
360
- height,
361
- width,
362
- }) => {
363
- return g => {
364
-
365
- g.data([{
366
- x: 0,
367
- y: height,
368
- }])
369
- .attr("transform", d => "translate(" + d.x + "," + d.y + ")");
370
-
371
- let rect = g.selectAll('.horizontal-drag-rect').data([{ w: width }]);
372
- let line = g.selectAll('.horizontal-drag-line').data([{ w: width }]);
373
- let label = g.selectAll('.horizontal-drag-label').data([{ w: width - 8 }]);
374
- let symbol = g.selectAll('.horizontal-drag-symbol')
375
- .data([{
376
- type: d3.symbolTriangle,
377
- x: width + 7,
378
- y: 0,
379
- angle: -90,
380
- size: 50
381
- }]);
382
-
383
- rect.exit().remove();
384
- line.exit().remove();
385
- label.exit().remove();
386
- symbol.exit().remove();
387
-
388
- rect.enter()
389
- .append("svg:rect")
390
- .attr("class", "horizontal-drag-rect")
391
- .attr("x", 0)
392
- .attr("y", -8)
393
- .attr("height", 8)
394
- .attr('fill', 'none')
395
- .attr('pointer-events', 'all')
396
- .merge(rect)
397
- .attr("width", d => d.w);
398
-
399
- line.enter()
400
- .append("svg:line")
401
- .attr("class", "horizontal-drag-line")
402
- .attr("x1", 0)
403
- .merge(line)
404
- .attr("x2", d => d.w);
405
-
406
- label.enter()
407
- .append("svg:text")
408
- .attr("class", "horizontal-drag-label")
409
- .attr("text-anchor", "end")
410
- .attr("y", -8)
411
- .merge(label)
412
- .attr("x", d => d.w)
413
-
414
- symbol
415
- .enter()
416
- .append("svg:path")
417
- .attr("class", "horizontal-drag-symbol")
418
- .merge(symbol)
419
- .attr("d",
420
- d3.symbol()
421
- .type(d => d.type)
422
- .size(d => d.size)
423
- )
424
- .attr("transform", d => "translate(" + d.x + "," + d.y + ") rotate(" + d.angle + ")");
425
-
426
- return g;
427
- }
428
- };
429
-
430
- export const CheckPoint = ({
431
- point,
432
- height,
433
- width,
434
- x,
435
- y
436
- }) => {
437
- return g => {
438
-
439
- if (isNaN(x) || isNaN(y)) return g;
440
-
441
- if (!point.item || !point.item.property('isConnected')) {
442
- point.position = point.position || "bottom";
443
-
444
- point.item = g.append('g');
445
-
446
- point.item.append("svg:line")
447
- .attr("y1", 0)
448
- .attr("x1", 0)
449
- .attr("style","stroke: rgb(51, 51, 51); stroke-width: 0.5; stroke-dasharray: 2, 2;");
450
-
451
- point.item
452
- .append("svg:circle")
453
- .attr("class", " height-focus circle-lower")
454
- .attr("r", 3);
455
-
456
- if (point.label) {
457
- point.item.append("svg:text")
458
- .attr("dx", "4px")
459
- .attr("dy", "-4px");
460
- }
461
- }
462
-
463
- point.item
464
- .datum({
465
- pos: point.position,
466
- x: x,
467
- y: y
468
- })
469
- .attr("class", d => "point " + d.pos)
470
- .attr("transform", d => "translate(" + d.x + "," + d.y + ")");
471
-
472
- point.item.select('line')
473
- .datum({
474
- y2: ({'top': -y, 'bottom': height - y})[point.position],
475
- x2: ({'left': -x, 'right': width - x})[point.position] || 0
476
- })
477
- .attr("y2", d => d.y2)
478
- .attr("x2", d => d.x2)
479
-
480
- if (point.label) {
481
- point.item.select('text')
482
- .text(point.label);
483
- }
484
-
485
- return g;
486
- }
487
- };
488
-
489
- export const Domain = ({
490
- min,
491
- max,
492
- attr,
493
- name,
494
- forceBounds,
495
- scale
496
- }) => function(data) {
497
- attr = (scale && scale.attr) || attr || name;
498
- let domain = data && data.length ? d3.extent(data, d => d[attr]) : [0, 1];
499
- if (typeof min !== "undefined" && (min < domain[0] || forceBounds)) {
500
- domain[0] = min;
501
- }
502
- if (typeof max !== "undefined" && (max > domain[1] || forceBounds)) {
503
- domain[1] = max;
504
- }
505
- return domain;
506
- };
507
-
508
- export const Range = ({
509
- axis
510
- }) => function(width, height) {
511
- if (axis == 'x') return [0, width];
512
- else if (axis == 'y') return [height, 0];
513
- };
514
-
515
- export const Scale = ({
516
- data,
517
- attr,
518
- min,
519
- max,
520
- forceBounds,
521
- range,
522
- }) => {
523
- return d3.scaleLinear()
524
- .range(range)
525
- .domain(Domain({min, max, attr, forceBounds})(data));
526
- };
527
-
528
- export const Bisect = ({
529
- data = [0, 1],
530
- scale,
531
- x,
532
- attr
533
- }) => {
534
- return d3
535
- .bisector(d => d[attr])
536
- .left(data, scale.invert(x));
537
- };
538
-
539
- export const Chart = ({
540
- width,
541
- height,
542
- margins = {},
543
- ruler,
544
- }) => {
545
-
546
- const w = width - margins.left - margins.right;
547
- const h = height - margins.top - margins.bottom;
548
-
549
- // SVG Container
550
- const svg = d3.create("svg:svg").attr("class", "background");
551
-
552
- // SVG Groups
553
- const g = svg.append("g");
554
- const panes = {
555
- grid : g.append("g").attr("class", "grid"),
556
- area : g.append('g').attr("class", "area"),
557
- axis : g.append('g').attr("class", "axis"),
558
- point : g.append('g').attr("class", "point"),
559
- brush : g.append("g").attr("class", "brush"),
560
- tooltip: g.append("g").attr("class", "tooltip").attr('display', 'none'),
561
- ruler : g.append('g').attr('class', 'ruler'),
562
- legend : g.append('g').attr("class", "legend"),
563
- };
564
-
565
- // SVG Paths
566
- const mask = panes.area .append("svg:mask") .attr("id", 'elevation-clipper-' + Math.random().toString(36).substr(2, 9)).attr('fill-opacity', 1);
567
- const maskRect = mask .append("svg:rect") .attr('class', 'zoom') .attr('fill', 'white'); // white = transparent
568
-
569
- // Canvas Paths
570
- const foreignObject = panes.area .append('svg:foreignObject').attr('mask', 'url(#' + mask.attr('id') + ')');
571
- const canvas = foreignObject.append('xhtml:canvas') .attr('class', 'canvas-plot');
572
- const context = canvas.node().getContext('2d');
573
-
574
- // Add tooltip
575
- panes.tooltip.call(Tooltip({ xCoord: 0, yCoord: 0, height: h, width : w, labels: {} }));
576
-
577
- // Add the brushing
578
- let brush = d3.brushX().on('start.cursor end.cursor brush.cursor', () => panes.brush.select(".overlay").attr('cursor', null));
579
-
580
- // Scales
581
- const scale = (opts) => ({ x: Scale(opts.x), y: Scale(opts.y)});
582
-
583
- let utils = {
584
- mask,
585
- canvas,
586
- context,
587
- brush,
588
- };
589
-
590
- let chart = {
591
- svg,
592
- g,
593
- panes,
594
- utils,
595
- scale,
596
- };
597
-
598
- // Resize
599
- chart._resize = ({
600
- width,
601
- height,
602
- margins = {},
603
- ruler,
604
- }) => {
605
-
606
- const w = width - margins.left - margins.right;
607
- const h = height - margins.top - margins.bottom;
608
-
609
- svg .attr("width", width).attr("height", height).attr("viewBox", `0 0 ${width} ${height}`);
610
- g .attr("transform", "translate(" + margins.left + "," + margins.top + ")");
611
-
612
- // Partially fix: https://github.com/Raruto/leaflet-elevation/issues/123
613
- if(/Mac|iPod|iPhone|iPad/.test(navigator.platform) && /AppleWebkit/i.test(navigator.userAgent)) {
614
- canvas .style("transform", "translate(" + margins.left + "px," + margins.top + "px)");
615
- }
616
-
617
- maskRect .attr("width", w).attr("height", h).attr("x", 0).attr("y", 0);
618
- foreignObject.attr('width', w).attr('height', h);
619
- canvas .attr('width', w).attr('height', h);
620
-
621
- if (ruler) {
622
- panes.ruler.call(Ruler({ height: h, width: w }));
623
- }
624
-
625
- panes.brush.call(brush.extent( [ [0,0], [w, h] ] ));
626
- panes.brush.select(".overlay").attr('cursor', null);
627
-
628
- chart._width = w;
629
- chart._height = h;
630
-
631
- chart.svg.dispatch('resize', { detail: { width: w, height: h } } );
632
-
633
- };
634
-
635
- chart.pane = (name) => (panes[name] || (panes[name] = g.append('g').attr("class", name)));
636
- chart.get = (name) => utils[name];
637
-
638
- chart._resize({ width, height, margins});
639
-
640
- return chart;
641
- };
1
+ export const Area = ({
2
+ width,
3
+ height,
4
+ xAttr,
5
+ yAttr,
6
+ scaleX,
7
+ scaleY,
8
+ interpolation = "curveLinear"
9
+ }) => {
10
+ return d3.area()
11
+ .curve(typeof interpolation === 'string' ? d3[interpolation] : interpolation)
12
+ .x(d => (d.xDiagCoord = scaleX(d[xAttr])))
13
+ .y0(height)
14
+ .y1(d => scaleY(d[yAttr]));
15
+ };
16
+
17
+ export const Path = ({
18
+ name,
19
+ color,
20
+ strokeColor,
21
+ strokeOpacity,
22
+ fillOpacity,
23
+ className = ''
24
+ }) => {
25
+ let path = d3.create('svg:path')
26
+
27
+ if (name) path.classed(name + ' ' + className, true);
28
+
29
+ path.style("pointer-events", "none");
30
+
31
+ path
32
+ .attr("fill", color || '#3366CC')
33
+ .attr("stroke", strokeColor || '#000')
34
+ .attr("stroke-opacity", strokeOpacity || '1')
35
+ .attr("fill-opacity", fillOpacity || '0.8');
36
+
37
+ return path;
38
+ };
39
+
40
+ export const Axis = ({
41
+ type = "axis",
42
+ tickSize = 6,
43
+ tickPadding = 3,
44
+ position,
45
+ height,
46
+ width,
47
+ axis,
48
+ scale,
49
+ ticks,
50
+ tickFormat,
51
+ label,
52
+ labelX,
53
+ labelY,
54
+ name = "",
55
+ onAxisMount,
56
+ }) => {
57
+ return g => {
58
+ let [w, h] = [0, 0];
59
+ if (position == "bottom") h = height;
60
+ if (position == "right") w = width;
61
+
62
+ if (axis == "x" && type == "grid") {
63
+ tickSize = -height;
64
+ } else if (axis == "y" && type == "grid") {
65
+ tickSize = -width;
66
+ }
67
+
68
+ let axisScale = d3["axis" + position.replace(/\b\w/g, l => l.toUpperCase())]()
69
+ .scale(scale)
70
+ .ticks(typeof ticks === 'function' ? ticks() : ticks)
71
+ .tickPadding(tickPadding)
72
+ .tickSize(tickSize)
73
+ .tickFormat(tickFormat);
74
+
75
+ let axisGroup = g.append("g")
76
+ .attr("class", [axis, type, position, name].join(" "))
77
+ .attr("transform", "translate(" + w + "," + h + ")")
78
+ .call(axisScale);
79
+
80
+ if (label) {
81
+ axisGroup.append("svg:text")
82
+ .attr("x", labelX)
83
+ .attr("y", labelY)
84
+ .text(label);
85
+ }
86
+
87
+ if (onAxisMount) {
88
+ axisGroup.call(onAxisMount);
89
+ }
90
+
91
+ return axisGroup;
92
+ };
93
+ };
94
+
95
+ export const Grid = (props) => {
96
+ props.type = "grid";
97
+ return Axis(props);
98
+ };
99
+
100
+ export const PositionMarker = ({
101
+ theme,
102
+ xCoord = 0,
103
+ yCoord = 0,
104
+ labels = {},
105
+ item = {},
106
+ length = 0,
107
+ }) => {
108
+ return g => {
109
+
110
+ g.attr("class", "height-focus-group");
111
+
112
+ let line = g.select('.height-focus.line');
113
+ let circle = g.select('.height-focus.circle-lower');
114
+ let text = g.select('.height-focus-label');
115
+
116
+ if (!line.node()) line = g.append('svg:line');
117
+ if (!circle.node()) circle = g.append('svg:circle');
118
+ if (!text.node()) text = g.append('svg:text');
119
+
120
+ if (isNaN(xCoord) || isNaN(yCoord)) return g;
121
+
122
+ circle
123
+ .attr("class", theme + " height-focus circle-lower")
124
+ .attr("transform", "translate(" + xCoord + "," + yCoord + ")")
125
+ .attr("r", 6)
126
+ .attr("cx", 0)
127
+ .attr("cy", 0);
128
+
129
+ line
130
+ .attr("class", theme + " height-focus line")
131
+ .attr("x1", xCoord)
132
+ .attr("x2", xCoord)
133
+ .attr("y1", yCoord)
134
+ .attr("y2", length);
135
+
136
+ text
137
+ .attr("class", theme + " height-focus-label")
138
+ .style("pointer-events", "none")
139
+ .attr("x", xCoord + 5)
140
+ .attr("y", length);
141
+
142
+ let label;
143
+
144
+ Object
145
+ .keys(labels)
146
+ .sort((a, b) => labels[a].order - labels[b].order) // TODO: any performance issues?
147
+ .forEach((i)=> {
148
+ label = text.select(".height-focus-" + labels[i].name);
149
+
150
+ if (!label.size()) {
151
+ label = text.append("svg:tspan")
152
+ .attr("class", "height-focus-" + labels[i].name /*+ " " + "order-" + labels[i].order*/)
153
+ .attr("dy", "1.5em");
154
+ }
155
+
156
+ label.text(typeof labels[i].value !== "function" ? labels[i].value : labels[i].value(item));
157
+
158
+ });
159
+
160
+ text.select('tspan').attr("dy", text.selectAll('tspan').size() > 1 ? "-1.5em" : "0em" );
161
+ text.selectAll('tspan').attr("x", xCoord + 5);
162
+
163
+ return g;
164
+ }
165
+ };
166
+
167
+ export const LegendItem = ({
168
+ name,
169
+ label,
170
+ width,
171
+ height,
172
+ margins = {},
173
+ color,
174
+ path,
175
+ className = ''
176
+ }) => {
177
+ return g => {
178
+ g
179
+ .attr("class", "legend-item legend-" + name.toLowerCase())
180
+ .attr("data-name", name);
181
+
182
+ g.on('click.legend', () => d3.select(g.node().ownerSVGElement || g)
183
+ .dispatch("legend_clicked", {
184
+ detail: {
185
+ path: path.node(),
186
+ name: name,
187
+ legend: g.node(),
188
+ enabled: !path.classed('leaflet-hidden'),
189
+ }
190
+ })
191
+ );
192
+
193
+ g.append("svg:rect")
194
+ .attr("x", (width / 2) - 50)
195
+ .attr("y", height + margins.bottom / 2)
196
+ .attr("width", 50)
197
+ .attr("height", 10)
198
+ .attr("fill", color)
199
+ .attr("stroke", "#000")
200
+ .attr("stroke-opacity", "0.5")
201
+ .attr("fill-opacity", "0.25")
202
+ .attr("class", className);
203
+
204
+ g.append('svg:text')
205
+ .text(L._(label || name))
206
+ .attr("x", (width / 2) + 5)
207
+ .attr("font-size", 10)
208
+ .style("font-weight", "700")
209
+ .attr('y', height + margins.bottom / 2)
210
+ .attr('dy', "0.75em");
211
+
212
+ return g;
213
+ }
214
+ };
215
+
216
+ export const LegendSmall = ({
217
+ width,
218
+ height,
219
+ items,
220
+ onClick
221
+ }) => {
222
+ return g => {
223
+ let idx = 0;
224
+
225
+ g.data([{
226
+ x: width,
227
+ y: height + 40
228
+ }]).attr("transform", d => "translate(" + d.x + "," + d.y + ")").classed('legend-switcher');
229
+
230
+ // let label = g.selectAll(".legend-switcher-label") .data([{ idx: 0 }]);
231
+ let label = g.append('svg:text')
232
+ .attr("class", "legend-switcher-label")
233
+ .attr("text-anchor", "end")
234
+ .attr("x", -25)
235
+ .attr("y", 5)
236
+ .on("mousedown", (e, d) => setIdx(L.Util.wrapNum((idx + 1), [0, items.length])));
237
+
238
+ let symbol = g.selectAll(".legend-switcher-symbol").data([
239
+ { type: d3.symbolTriangle, x: 0, y: 3, angle: 0, size: 50, id: "down" },
240
+ { type: d3.symbolTriangle, x: -13, y: 0, angle: 180, size: 50, id: "up" }
241
+ ]);
242
+
243
+ symbol.exit().remove();
244
+ label.exit().remove();
245
+
246
+ symbol.enter()
247
+ .append("svg:path")
248
+ .attr("class", "legend-switcher-symbol")
249
+ .attr("cursor", 'pointer')
250
+ .attr("fill", "#000")
251
+ .merge(symbol)
252
+ .attr("d",
253
+ d3.symbol()
254
+ .type(d => d.type)
255
+ .size(d => d.size)
256
+ )
257
+ .attr("transform", d => "translate(" + d.x + "," + d.y + ") rotate(" + d.angle + ")")
258
+ .on("mousedown", (e, d) => setIdx(L.Util.wrapNum((d.id === "up" ? idx + 1 : idx - 1), [0, items.length])));
259
+
260
+ const setIdx = (id) => {
261
+ idx = id;
262
+ // label.enter()
263
+ // .append('text')
264
+ // .attr("class", "legend-switcher-label")
265
+ // .attr("text-anchor", "end")
266
+ // .attr("x", -25)
267
+ // .attr("y", 5)
268
+ // .merge(label.data([{ idx: id }]))
269
+ // .text(d => items.length ? (items[idx][0].toUpperCase() + items[idx].slice(1)) : '')
270
+ // .on("mousedown", (e, d) => setIdx(L.Util.wrapNum((idx + 1), [0, items.length])));
271
+ label.text(items.length ? L._((items[idx][0].toUpperCase() + items[idx].slice(1))) : '');
272
+ onClick(items[idx]);
273
+ };
274
+ setIdx(0);
275
+
276
+ return g;
277
+ };
278
+ };
279
+
280
+ export const Tooltip = ({
281
+ xCoord,
282
+ yCoord,
283
+ width,
284
+ height,
285
+ labels = {},
286
+ item = {},
287
+ }) => {
288
+ return g => {
289
+
290
+ let line = g.select('.mouse-focus-line');
291
+ let box = g.select('.mouse-focus-label');
292
+
293
+ if (!line.node()) line = g.append('svg:line');
294
+ if (!box.node()) box = g.append("g");
295
+
296
+ let rect = box.select(".mouse-focus-label-rect");
297
+ let text = box.select(".mouse-focus-label-text");
298
+
299
+ if (!rect.node()) rect = box.append("svg:rect");
300
+ if (!text.node()) text = box.append("svg:text");
301
+
302
+ // Sets focus-label-text position to the left / right of the mouse-focus-line
303
+ let xAlign = 0;
304
+ let yAlign = 0;
305
+ let bbox = { width: 0, height: 0 };
306
+ try { bbox = text.node().getBBox(); } catch (e) { return g; }
307
+
308
+ if (xCoord) xAlign = xCoord + (xCoord < width / 2 ? 10 : -bbox.width - 10);
309
+ if (yCoord) yAlign = Math.max(yCoord - bbox.height, L.Browser.webkit ? 0 : -Infinity);
310
+
311
+ line
312
+ .attr('class', 'mouse-focus-line')
313
+ .attr('x2', xCoord)
314
+ .attr('y2', 0)
315
+ .attr('x1', xCoord)
316
+ .attr('y1', height);
317
+
318
+ box
319
+ .attr('class', 'mouse-focus-label');
320
+
321
+ rect
322
+ .attr("class", "mouse-focus-label-rect")
323
+ .attr("x", xAlign - 5)
324
+ .attr("y", yAlign - 5)
325
+ .attr("width", bbox.width + 10)
326
+ .attr("height", bbox.height + 10)
327
+ .attr("rx", 3)
328
+ .attr("ry", 3);
329
+
330
+ text
331
+ .attr("class", "mouse-focus-label-text")
332
+ .style("font-weight", "700")
333
+ .attr("y", yAlign);
334
+
335
+ let label;
336
+
337
+ Object
338
+ .keys(labels)
339
+ .sort((a, b) => labels[a].order - labels[b].order) // TODO: any performance issues?
340
+ .forEach((i)=> {
341
+ label = text.select(".mouse-focus-label-" + labels[i].name);
342
+
343
+ if (!label.size()) {
344
+ label = text.append("svg:tspan", ".mouse-focus-label-x")
345
+ .attr("class", "mouse-focus-label-" + labels[i].name /*+ " " + "order-" + labels[i].order*/)
346
+ .attr("dy", "1.5em");
347
+ }
348
+
349
+ label.text(typeof labels[i].value !== "function" ? labels[i].value : labels[i].value(item));
350
+ });
351
+
352
+ text.select('tspan').attr("dy", "1em");
353
+ text.selectAll('tspan').attr("x", xAlign);
354
+
355
+ return g;
356
+ };
357
+ };
358
+
359
+ export const Ruler = ({
360
+ height,
361
+ width,
362
+ }) => {
363
+ return g => {
364
+
365
+ g.data([{
366
+ x: 0,
367
+ y: height,
368
+ }])
369
+ .attr("transform", d => "translate(" + d.x + "," + d.y + ")");
370
+
371
+ let rect = g.selectAll('.horizontal-drag-rect').data([{ w: width }]);
372
+ let line = g.selectAll('.horizontal-drag-line').data([{ w: width }]);
373
+ let label = g.selectAll('.horizontal-drag-label').data([{ w: width - 8 }]);
374
+ let symbol = g.selectAll('.horizontal-drag-symbol')
375
+ .data([{
376
+ type: d3.symbolTriangle,
377
+ x: width + 7,
378
+ y: 0,
379
+ angle: -90,
380
+ size: 50
381
+ }]);
382
+
383
+ rect.exit().remove();
384
+ line.exit().remove();
385
+ label.exit().remove();
386
+ symbol.exit().remove();
387
+
388
+ rect.enter()
389
+ .append("svg:rect")
390
+ .attr("class", "horizontal-drag-rect")
391
+ .attr("x", 0)
392
+ .attr("y", -8)
393
+ .attr("height", 8)
394
+ .attr('fill', 'none')
395
+ .attr('pointer-events', 'all')
396
+ .merge(rect)
397
+ .attr("width", d => d.w);
398
+
399
+ line.enter()
400
+ .append("svg:line")
401
+ .attr("class", "horizontal-drag-line")
402
+ .attr("x1", 0)
403
+ .merge(line)
404
+ .attr("x2", d => d.w);
405
+
406
+ label.enter()
407
+ .append("svg:text")
408
+ .attr("class", "horizontal-drag-label")
409
+ .attr("text-anchor", "end")
410
+ .attr("y", -8)
411
+ .merge(label)
412
+ .attr("x", d => d.w)
413
+
414
+ symbol
415
+ .enter()
416
+ .append("svg:path")
417
+ .attr("class", "horizontal-drag-symbol")
418
+ .merge(symbol)
419
+ .attr("d",
420
+ d3.symbol()
421
+ .type(d => d.type)
422
+ .size(d => d.size)
423
+ )
424
+ .attr("transform", d => "translate(" + d.x + "," + d.y + ") rotate(" + d.angle + ")");
425
+
426
+ return g;
427
+ }
428
+ };
429
+
430
+ export const CheckPoint = ({
431
+ point,
432
+ height,
433
+ width,
434
+ x,
435
+ y
436
+ }) => {
437
+ return g => {
438
+
439
+ if (isNaN(x) || isNaN(y)) return g;
440
+
441
+ if (!point.item || !point.item.property('isConnected')) {
442
+ point.position = point.position || "bottom";
443
+
444
+ point.item = g.append('g');
445
+
446
+ point.item.append("svg:line")
447
+ .attr("y1", 0)
448
+ .attr("x1", 0)
449
+ .attr("style","stroke: rgb(51, 51, 51); stroke-width: 0.5; stroke-dasharray: 2, 2;");
450
+
451
+ point.item
452
+ .append("svg:circle")
453
+ .attr("class", " height-focus circle-lower")
454
+ .attr("r", 3);
455
+
456
+ if (point.label) {
457
+ point.item.append("svg:text")
458
+ .attr("dx", "4px")
459
+ .attr("dy", "-4px");
460
+ }
461
+ }
462
+
463
+ point.item
464
+ .datum({
465
+ pos: point.position,
466
+ x: x,
467
+ y: y
468
+ })
469
+ .attr("class", d => "point " + d.pos)
470
+ .attr("transform", d => "translate(" + d.x + "," + d.y + ")");
471
+
472
+ point.item.select('line')
473
+ .datum({
474
+ y2: ({'top': -y, 'bottom': height - y})[point.position],
475
+ x2: ({'left': -x, 'right': width - x})[point.position] || 0
476
+ })
477
+ .attr("y2", d => d.y2)
478
+ .attr("x2", d => d.x2)
479
+
480
+ if (point.label) {
481
+ point.item.select('text')
482
+ .text(point.label);
483
+ }
484
+
485
+ return g;
486
+ }
487
+ };
488
+
489
+ export const Domain = ({
490
+ min,
491
+ max,
492
+ attr,
493
+ name,
494
+ forceBounds,
495
+ scale
496
+ }) => function(data) {
497
+ attr = (scale && scale.attr) || attr || name;
498
+ let domain = data && data.length ? d3.extent(data, d => d[attr]) : [0, 1];
499
+ if (typeof min !== "undefined" && (min < domain[0] || forceBounds)) {
500
+ domain[0] = min;
501
+ }
502
+ if (typeof max !== "undefined" && (max > domain[1] || forceBounds)) {
503
+ domain[1] = max;
504
+ }
505
+ return domain;
506
+ };
507
+
508
+ export const Range = ({
509
+ axis
510
+ }) => function(width, height) {
511
+ if (axis == 'x') return [0, width];
512
+ else if (axis == 'y') return [height, 0];
513
+ };
514
+
515
+ export const Scale = ({
516
+ data,
517
+ attr,
518
+ min,
519
+ max,
520
+ forceBounds,
521
+ range,
522
+ }) => {
523
+ return d3.scaleLinear()
524
+ .range(range)
525
+ .domain(Domain({min, max, attr, forceBounds})(data));
526
+ };
527
+
528
+ export const Bisect = ({
529
+ data = [0, 1],
530
+ scale,
531
+ x,
532
+ attr
533
+ }) => {
534
+ return d3
535
+ .bisector(d => d[attr])
536
+ .left(data, scale.invert(x));
537
+ };
538
+
539
+ export const Chart = ({
540
+ width,
541
+ height,
542
+ margins = {},
543
+ ruler,
544
+ }) => {
545
+
546
+ const w = width - margins.left - margins.right;
547
+ const h = height - margins.top - margins.bottom;
548
+
549
+ // SVG Container
550
+ const svg = d3.create("svg:svg").attr("class", "background");
551
+
552
+ const defs = svg.append("svg:defs");
553
+
554
+ // SVG Groups
555
+ const g = svg.append("g");
556
+ const panes = {
557
+ grid : g.append("g").attr("class", "grid"),
558
+ area : g.append('g').attr("class", "area"),
559
+ axis : g.append('g').attr("class", "axis"),
560
+ point : g.append('g').attr("class", "point"),
561
+ brush : g.append("g").attr("class", "brush"),
562
+ tooltip: g.append("g").attr("class", "tooltip").attr('display', 'none'),
563
+ ruler : g.append('g').attr('class', 'ruler'),
564
+ legend : g.append('g').attr("class", "legend"),
565
+ };
566
+
567
+ // SVG Paths
568
+ const mask = panes.area .append("svg:mask") .attr("id", 'elevation-clipper-' + Math.random().toString(36).substr(2, 9)).attr('fill-opacity', 1);
569
+ const maskRect = mask .append("svg:rect") .attr('class', 'zoom') .attr('fill', 'white'); // white = transparent
570
+
571
+ // Canvas Paths
572
+ const foreignObject = panes.area .append('svg:foreignObject').attr('mask', 'url(#' + mask.attr('id') + ')');
573
+ const canvas = foreignObject.append('xhtml:canvas') .attr('class', 'canvas-plot');
574
+ const context = canvas.node().getContext('2d');
575
+
576
+ // Add tooltip
577
+ panes.tooltip.call(Tooltip({ xCoord: 0, yCoord: 0, height: h, width : w, labels: {} }));
578
+
579
+ // Add the brushing
580
+ let brush = d3.brushX().on('start.cursor end.cursor brush.cursor', () => panes.brush.select(".overlay").attr('cursor', null));
581
+
582
+ // Scales
583
+ const scale = (opts) => ({ x: Scale(opts.x), y: Scale(opts.y)});
584
+
585
+ let utils = {
586
+ defs,
587
+ mask,
588
+ canvas,
589
+ context,
590
+ brush,
591
+ };
592
+
593
+ let chart = {
594
+ svg,
595
+ g,
596
+ panes,
597
+ utils,
598
+ scale,
599
+ };
600
+
601
+ // Resize
602
+ chart._resize = ({
603
+ width,
604
+ height,
605
+ margins = {},
606
+ ruler,
607
+ }) => {
608
+
609
+ const w = width - margins.left - margins.right;
610
+ const h = height - margins.top - margins.bottom;
611
+
612
+ svg .attr("width", width).attr("height", height).attr("viewBox", `0 0 ${width} ${height}`);
613
+ g .attr("transform", "translate(" + margins.left + "," + margins.top + ")");
614
+
615
+ // Fix: https://github.com/Raruto/leaflet-elevation/issues/123
616
+ // Fix: https://github.com/Raruto/leaflet-elevation/issues/232
617
+ if (
618
+ /Mac|iPod|iPhone|iPad/.test(navigator.platform) &&
619
+ /AppleWebkit/i.test(navigator.userAgent) &&
620
+ !/Chrome/.test(navigator.userAgent)
621
+ ) {
622
+ canvas .style("transform", "translate(" + margins.left + "px," + margins.top + "px)");
623
+ }
624
+
625
+ maskRect .attr("width", w).attr("height", h).attr("x", 0).attr("y", 0);
626
+ foreignObject.attr('width', w).attr('height', h);
627
+ canvas .attr('width', w).attr('height', h);
628
+
629
+ if (ruler) {
630
+ panes.ruler.call(Ruler({ height: h, width: w }));
631
+ }
632
+
633
+ panes.brush.call(brush.extent( [ [0,0], [w, h] ] ));
634
+ panes.brush.select(".overlay").attr('cursor', null);
635
+
636
+ chart._width = w;
637
+ chart._height = h;
638
+
639
+ chart.svg.dispatch('resize', { detail: { width: w, height: h } } );
640
+
641
+ };
642
+
643
+ chart.pane = (name) => (panes[name] || (panes[name] = g.append('g').attr("class", name)));
644
+ chart.get = (name) => utils[name];
645
+
646
+ chart._resize({ width, height, margins});
647
+
648
+ return chart;
649
+ };