xref: /webtrees/resources/views/modules/pedigree-map/chart.phtml (revision 985793245869e051c3438a56b44f5662c8529182)
1<?php
2
3use Fisharebest\Webtrees\I18N;
4use Fisharebest\Webtrees\View;
5
6/**
7 * @var array $data
8 * @var array $provider
9 */
10?>
11
12<div class="py-4">
13    <div class="row gchart osm-wrapper">
14        <div id="osm-map" class="col-sm-9 wt-ajax-load osm-user-map" dir="ltr"></div>
15        <ul class="col-sm-3 osm-sidebar wt-page-options-value list-unstyled px-md-1"></ul>
16    </div>
17</div>
18
19<?php View::push('styles') ?>
20<style>
21    .osm-wrapper, .osm-user-map {
22        height: 70vh
23    }
24
25    .osm-sidebar {
26        height: 100%;
27        overflow-y: auto;
28        font-size: small;
29    }
30
31</style>
32<?php View::endpush() ?>
33
34<?php View::push('javascript') ?>
35<script type="application/javascript">
36"use strict";
37
38window.WT_OSM = (function() {
39    const minZoom = 2;
40
41    let map      = null;
42    let zoom     = null;
43    let sidebar  = $('.osm-sidebar');
44    let provider = <?= json_encode($provider) ?>;
45
46    // Map components
47    let markers = L.markerClusterGroup({
48        showCoverageOnHover: false
49    });
50
51    let resetControl = L.Control.extend({
52        options: {
53            position: 'topleft'
54        },
55        onAdd: function(map) {
56            let container = L.DomUtil.create('div', 'leaflet-bar leaflet-control leaflet-control-custom');
57            container.onclick = function() {
58                if (zoom) {
59                    map.flyTo(markers.getBounds().getCenter(), zoom);
60                } else {
61                    map.flyToBounds(markers.getBounds().pad(0.2));
62                }
63                sidebar.scrollTo(sidebar.children(":first"));
64
65                return false;
66            };
67            let reset    = <?= json_encode(I18N::translate('Reload map')) ?>;
68            let anchor   = L.DomUtil.create('a', 'leaflet-control-reset', container);
69            anchor.setAttribute('aria-label', reset);
70            anchor.href  = '#';
71            anchor.title = reset;
72            anchor.role  = 'button';
73            let image    = L.DomUtil.create('i', 'fas fa-redo', anchor);
74            image.alt    = reset;
75
76            return container;
77        }
78    });
79
80    // Zoom control with localised text
81    let newZoomControl = new L.control.zoom({
82        zoomInTitle : <?= json_encode(I18N::translate('Zoom in')) ?>,
83        zoomOutTitle: <?= json_encode(I18N::translate('Zoom out')) ?>,
84    });
85
86    /**
87     *
88     * @private
89     */
90    let _drawMap = function() {
91        map = L.map('osm-map', {
92            center     : [0, 0],
93            minZoom    : minZoom,   // maxZoom set by leaflet-providers.js
94            zoomControl: false,     // remove default
95        })
96        .addControl(new resetControl())
97        .addControl(newZoomControl)
98        .addLayer(L.tileLayer.provider(provider.name, provider.options))
99    };
100
101    /**
102     * @private
103     */
104    let _buildMapData = function() {
105        let sidebar_content = '';
106        let geoJson_data    = <?= json_encode($data) ?>;
107
108        if (geoJson_data.features.length === 0) {
109            map.fitWorld();
110            sidebar_content += '<div class="bg-info text-white text-center">' + <?= json_encode(I18N::translate('Nothing to show')) ?> + '</div>';
111        } else {
112            if (geoJson_data.features.length === 1) {
113                //fudge factor - maps zooms to maximum permitted otherwise
114                zoom = geoJson_data.features[0].properties.zoom;
115            }
116            let geoJsonLayer = L.geoJson(geoJson_data, {
117                pointToLayer: function(feature, latlng) {
118                    return new L.Marker(latlng, {
119                            icon: L.BeautifyIcon.icon({
120                                icon           : 'bullseye fas',
121                                borderColor    : 'transparent',
122                                backgroundColor: feature.properties.iconcolor,
123                                iconShape      : 'marker',
124                                textColor      : 'white',
125                            }),
126                            title: feature.properties.tooltip,
127                            alt  : feature.properties.tooltip,
128                            id   : feature.id
129                        })
130                        .on('popupopen', function(e) {
131                            let item = sidebar.children(".gchart[data-id=" + e.target.feature.id + "]");
132                            item.addClass('messagebox');
133                            sidebar.scrollTo(item);
134                        })
135                        .on('popupclose', function() {
136                            sidebar.children(".gchart").removeClass('messagebox');
137                        });
138                },
139                onEachFeature: function(feature, layer) {
140                    if (feature.properties.polyline) {
141                        let pline = L.polyline(feature.properties.polyline.points, feature.properties.polyline.options);
142                        markers.addLayer(pline);
143                    }
144                    layer.bindPopup(feature.properties.summary);
145                    sidebar_content += `<li class="gchart px-md-2" data-id=${feature.id}>${feature.properties.summary}</li>`;
146                }
147            });
148            markers.addLayer(geoJsonLayer);
149            map.addLayer(markers);
150            if (zoom) {
151                map.setView(markers.getBounds().getCenter(), zoom);
152            } else {
153                map.fitBounds(markers.getBounds().pad(0.2));
154            }
155        }
156        sidebar.append(sidebar_content);
157    };
158
159    /**
160     *
161     * @param elem
162     * @returns {$}
163     */
164
165    $.fn.scrollTo = function(elem) {
166        let _this = $(this);
167        _this.animate({
168            scrollTop: elem.offset().top - _this.offset().top + _this.scrollTop()
169        });
170        return this;
171    };
172
173    // Activate marker popup when sidebar entry clicked
174    $(function() {
175        sidebar
176            // open marker popup if sidebar event is clicked
177            .on('click', '.gchart', function(e) {
178                // first close any existing
179                map.closePopup();
180                let eventId = $(this).data('id');
181                //find the marker corresponding to the clicked event
182                let mkrLayer = markers.getLayers().filter(function(v) {
183                    return typeof(v.feature) !== 'undefined' && v.feature.id === eventId;
184                });
185                let mkr = mkrLayer.pop();
186                // Unfortunately zoomToShowLayer zooms to maxZoom
187                // when all marker in a cluster have exactly the
188                // same co-ordinates
189                markers.zoomToShowLayer(mkr, function(e) {
190                    mkr.openPopup();
191                });
192
193                return false;
194            })
195            .on('click', 'a', function(e) { // stop click on a person also opening the popup
196                e.stopPropagation();
197            });
198    });
199
200    _drawMap();
201    _buildMapData();
202
203    return "Leaflet map interface for webtrees-2";
204})();
205</script>
206<?php View::endpush() ?>
207