xref: /webtrees/resources/views/modules/pedigree-map/chart.phtml (revision 43f2f523bcb6d4090564d23802872c0679ede6bc)
1<?php
2
3use Fisharebest\Webtrees\I18N;
4use Fisharebest\Webtrees\View;
5
6/**
7 * @var array<mixed> $data
8 * @var object       $leaflet_config
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>
36  'use strict';
37
38  window.WT_OSM = (function () {
39    const config = <?= json_encode($leaflet_config, JSON_THROW_ON_ERROR) ?>;
40    const minZoom = 2;
41
42    let map = null;
43    let sidebar = $('.osm-sidebar');
44
45    // Map components
46    let markers = L.markerClusterGroup({
47      showCoverageOnHover: false,
48    });
49
50    let resetControl = L.Control.extend({
51      options: {
52        position: 'topleft',
53      },
54      onAdd: function (map) {
55        let container = L.DomUtil.create('div', 'leaflet-bar leaflet-control leaflet-control-custom');
56        container.onclick = function () {
57          map.flyToBounds(markers.getBounds(), { padding: [50, 30], maxZoom: 15 });
58          sidebar.scrollTo(sidebar.children(':first'));
59
60          return false;
61        };
62        let reset = config.i18n.reset;
63        let anchor = L.DomUtil.create('a', 'leaflet-control-reset', container);
64        anchor.setAttribute('aria-label', reset);
65        anchor.href = '#';
66        anchor.title = reset;
67        anchor.role = 'button';
68        let image = L.DomUtil.create('i', 'fas fa-redo', anchor);
69        image.alt = reset;
70
71        return container;
72      },
73    });
74
75    /**
76     * @private
77     */
78    let _drawMap = function () {
79      map = webtrees.buildLeafletJsMap('osm-map', config)
80        .addControl(new resetControl());
81    };
82
83    /**
84     * @private
85     */
86    let _buildMapData = function () {
87      let sidebar_content = '';
88      let geoJson_data = <?= json_encode($data, JSON_THROW_ON_ERROR) ?>;
89
90      if (geoJson_data.features.length === 0) {
91        map.fitWorld();
92        sidebar_content += '<div class="bg-info text-white text-center">' + <?= json_encode(I18N::translate('Nothing to show'), JSON_THROW_ON_ERROR) ?> + '</div>';
93      } else {
94        let geoJsonLayer = L.geoJson(geoJson_data, {
95          pointToLayer: function (feature, latlng) {
96            return new L.Marker(latlng, {
97              icon: L.BeautifyIcon.icon({
98                icon: 'bullseye fas',
99                borderColor: 'transparent',
100                backgroundColor: feature.properties.iconcolor,
101                iconShape: 'marker',
102                textColor: 'white',
103              }),
104              title: feature.properties.tooltip,
105              alt: feature.properties.tooltip,
106              id: feature.id,
107            })
108              .on('popupopen', function (e) {
109                let item = sidebar.children('.gchart[data-wt-feature-id=' + e.target.feature.id + ']');
110                item.addClass('messagebox');
111                sidebar.scrollTo(item);
112              })
113              .on('popupclose', function () {
114                sidebar.children('.gchart').removeClass('messagebox');
115              });
116          },
117          onEachFeature: function (feature, layer) {
118            if (feature.properties.polyline) {
119              let pline = L.polyline(feature.properties.polyline.points, feature.properties.polyline.options);
120              markers.addLayer(pline);
121            }
122            layer.bindPopup(feature.properties.summary);
123            sidebar_content += `<li class="gchart px-md-2" data-wt-feature-id=${feature.id}>${feature.properties.summary}</li>`;
124          },
125        });
126        markers.addLayer(geoJsonLayer);
127        map.addLayer(markers);
128        map.fitBounds(markers.getBounds(), { padding: [50, 30], maxZoom: 15 });
129      }
130      sidebar.append(sidebar_content);
131    };
132
133    /**
134     * @param   elem
135     * @returns {$}
136     */
137    $.fn.scrollTo = function (elem) {
138      let _this = $(this);
139      _this.animate({
140        scrollTop: elem.offset().top - _this.offset().top + _this.scrollTop(),
141      });
142      return this;
143    };
144
145    // Activate marker popup when sidebar entry clicked
146    $(function () {
147      sidebar
148        // open marker popup if sidebar event is clicked
149        .on('click', '.gchart', function (e) {
150          // first close any existing
151          map.closePopup();
152          let eventId = $(this).data('id');
153          //find the marker corresponding to the clicked event
154          let mkrLayer = markers.getLayers().filter(function (v) {
155            return typeof (v.feature) !== 'undefined' && v.feature.id === eventId;
156          });
157          let mkr = mkrLayer.pop();
158          // Unfortunately zoomToShowLayer zooms to maxZoom
159          // when all marker in a cluster have exactly the
160          // same co-ordinates
161          markers.zoomToShowLayer(mkr, function (e) {
162            mkr.openPopup();
163          });
164
165          return false;
166        })
167        .on('click', 'a', function (e) { // stop click on a person also opening the popup
168          e.stopPropagation();
169        });
170    });
171
172    _drawMap();
173    _buildMapData();
174
175    return 'Leaflet map interface for webtrees-2';
176  })();
177</script>
178<?php View::endpush() ?>
179