xref: /webtrees/resources/views/modules/pedigree-map/chart.phtml (revision c9c6f2ec6e88594e58f14a6418e0de5aaa1484bd)
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
38window.WT_OSM = (function () {
39  const config = <?= json_encode($leaflet_config) ?>;
40  const minZoom = 2;
41
42  let map      = null;
43  let zoom     = null;
44  let sidebar  = $('.osm-sidebar');
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         = config.i18n.reset;
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   /**
81   * @private
82   */
83   let _drawMap = function() {
84    map = webtrees.buildLeafletJsMap('osm-map', config)
85      .addControl(new resetControl());
86  };
87
88  /**
89   * @private
90   */
91  let _buildMapData = function () {
92    let sidebar_content = '';
93    let geoJson_data    = <?= json_encode($data) ?>;
94
95    if (geoJson_data.features.length === 0) {
96      map.fitWorld();
97      sidebar_content += '<div class="bg-info text-white text-center">' + <?= json_encode(I18N::translate('Nothing to show')) ?> +'</div>';
98    } else {
99      if (geoJson_data.features.length === 1) {
100        //fudge factor - maps zooms to maximum permitted otherwise
101        zoom = geoJson_data.features[0].properties.zoom;
102      }
103      let geoJsonLayer = L.geoJson(geoJson_data, {
104        pointToLayer:  function (feature, latlng) {
105          return new L.Marker(latlng, {
106            icon:  L.BeautifyIcon.icon({
107              icon:            'bullseye fas',
108              borderColor:     'transparent',
109              backgroundColor: feature.properties.iconcolor,
110              iconShape:       'marker',
111              textColor:       'white',
112            }),
113            title: feature.properties.tooltip,
114            alt:   feature.properties.tooltip,
115            id:    feature.id,
116          })
117          .on('popupopen', function (e) {
118            let item = sidebar.children('.gchart[data-id=' + e.target.feature.id + ']');
119            item.addClass('messagebox');
120            sidebar.scrollTo(item);
121          })
122          .on('popupclose', function () {
123            sidebar.children('.gchart').removeClass('messagebox');
124          });
125        },
126        onEachFeature: function (feature, layer) {
127          if (feature.properties.polyline) {
128            let pline = L.polyline(feature.properties.polyline.points, feature.properties.polyline.options);
129            markers.addLayer(pline);
130          }
131          layer.bindPopup(feature.properties.summary);
132          sidebar_content += `<li class="gchart px-md-2" data-id=${feature.id}>${feature.properties.summary}</li>`;
133        },
134      });
135      markers.addLayer(geoJsonLayer);
136      map.addLayer(markers);
137      if (zoom) {
138        map.setView(markers.getBounds().getCenter(), zoom);
139      } else {
140        map.fitBounds(markers.getBounds(), {padding: [50, 30]});
141      }
142    }
143    sidebar.append(sidebar_content);
144  };
145
146  /**
147   * @param   elem
148   * @returns {$}
149   */
150  $.fn.scrollTo = function (elem) {
151    let _this = $(this);
152    _this.animate({
153      scrollTop: elem.offset().top - _this.offset().top + _this.scrollTop(),
154    });
155    return this;
156  };
157
158// Activate marker popup when sidebar entry clicked
159  $(function () {
160    sidebar
161    // open marker popup if sidebar event is clicked
162    .on('click', '.gchart', function (e) {
163      // first close any existing
164      map.closePopup();
165      let eventId  = $(this).data('id');
166      //find the marker corresponding to the clicked event
167      let mkrLayer = markers.getLayers().filter(function (v) {
168        return typeof (v.feature) !== 'undefined' && v.feature.id === eventId;
169      });
170      let mkr      = mkrLayer.pop();
171      // Unfortunately zoomToShowLayer zooms to maxZoom
172      // when all marker in a cluster have exactly the
173      // same co-ordinates
174      markers.zoomToShowLayer(mkr, function (e) {
175        mkr.openPopup();
176      });
177
178      return false;
179    })
180    .on('click', 'a', function (e) { // stop click on a person also opening the popup
181      e.stopPropagation();
182    });
183  });
184
185    _drawMap();
186    _buildMapData();
187
188  return 'Leaflet map interface for webtrees-2';
189})();
190</script>
191<?php View::endpush() ?>
192