pretix-map 0.1.3__py3-none-any.whl → 0.1.4__py3-none-any.whl

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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pretix-map
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: An overview map of the catchment area of previous orders. Measured by postcode
5
5
  Author-email: MarkenJaden <jjsch1410@gmail.com>
6
6
  Maintainer-email: MarkenJaden <jjsch1410@gmail.com>
@@ -1,5 +1,5 @@
1
- pretix_map-0.1.3.dist-info/licenses/LICENSE,sha256=RhQ89ePNDClBzEROahhwjDrBSEb5Zpx6XewZfGlY4Ss,569
2
- pretix_mapplugin/__init__.py,sha256=LPNaKUFe5S0lxcbXP4IcviKz02tUsjRUDAJch2OV7IE,23
1
+ pretix_map-0.1.4.dist-info/licenses/LICENSE,sha256=RhQ89ePNDClBzEROahhwjDrBSEb5Zpx6XewZfGlY4Ss,569
2
+ pretix_mapplugin/__init__.py,sha256=wQL21SKqJmZ-NSboyC8tgIR3EmyN1XDjwrN7CAHZpGQ,23
3
3
  pretix_mapplugin/apps.py,sha256=AnThwyRw2AAz5f-kmXZ8hm85OmKnlDkRosVoQOBgPzE,830
4
4
  pretix_mapplugin/geocoding.py,sha256=lBmwMvmE_cPyOHxWE8H3Se2P-2Eq0UjDTCv9gUs97Fo,4018
5
5
  pretix_mapplugin/models.py,sha256=klKrgMu1bmiPBwucTdOAZGWtv4WAEKcnoeqPlZgFR1A,2091
@@ -14,13 +14,13 @@ pretix_mapplugin/locale/de_Informal/LC_MESSAGES/django.mo,sha256=6VVRAqa0ixL-lDA
14
14
  pretix_mapplugin/locale/de_Informal/LC_MESSAGES/django.po,sha256=tIFKw9KOdGTjGq8bHV6tquRZe_MOn8TT4MJjdTRhId8,323
15
15
  pretix_mapplugin/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  pretix_mapplugin/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
- pretix_mapplugin/management/commands/geocode_existing_orders.py,sha256=Go9XzBb-eI_ohGDJ4x74MWIQkq2f1rMDLFNo0hp29AM,14127
17
+ pretix_mapplugin/management/commands/geocode_existing_orders.py,sha256=zD8OD7c1ZXPCR1KNOc-gWY8ZlIk_IbMKtagFinG6qf0,14099
18
18
  pretix_mapplugin/migrations/0001_initial.py,sha256=KAl1Egxptv1bpregGbsh8wUbr4Yh5A_zazVSAQdmoHM,1020
19
19
  pretix_mapplugin/migrations/0002_remove_ordergeocodedata_geocoded_timestamp_and_more.py,sha256=dXmZRdqrND0pxiPRuitlDdg-Q2JBqYG-sRPJxr6Urpk,889
20
20
  pretix_mapplugin/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
21
  pretix_mapplugin/static/pretix_mapplugin/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
- pretix_mapplugin/static/pretix_mapplugin/css/salesmap.css,sha256=itRfmhct3UAz9rg0i8KYZDNQbN_sNgo8qoa1UPhvQW0,964
23
- pretix_mapplugin/static/pretix_mapplugin/js/salesmap.js,sha256=4SX0yr0VuJQro-A0Fnl6pTlAfk4X9XhjDxkGq95qUsQ,21272
22
+ pretix_mapplugin/static/pretix_mapplugin/css/salesmap.css,sha256=9t2grYB2nWk90Q8h7XjDrlMw9UvcwYS4lcXM1KFidqI,964
23
+ pretix_mapplugin/static/pretix_mapplugin/js/salesmap.js,sha256=ObXC0E0UOJMIiS5DobuGkTVjNzxDjODs8WgxbKaryWE,18591
24
24
  pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/MarkerCluster.Default.css,sha256=LWhzWaQGZRsWFrrJxg-6Zn8TT84k0_trtiHBc6qcGpY,1346
25
25
  pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/MarkerCluster.css,sha256=-bdWuWOXMFkX0v9Cvr3OWClPiYefDQz9GGZP_7xZxdc,886
26
26
  pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/leaflet-heat.js,sha256=aPb_2lnWKnXsUc1_-aT9-kbtr4CV3c85jH9xC1e5QDI,5168
@@ -39,9 +39,9 @@ pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/images/marker-ic
39
39
  pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/images/marker-icon.png,sha256=V0w6XMqF9BFAhbaEFZbWLwDXyJLHsD8oy_owHesdxDc,1466
40
40
  pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/images/marker-shadow.png,sha256=Jk9cZAM58ELdcpBiz8BMF_jqDymIK1OOOEjtjxDttNo,618
41
41
  pretix_mapplugin/templates/pretix_mapplugin/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
42
- pretix_mapplugin/templates/pretix_mapplugin/map_page.html,sha256=OC1CA5wAcDTC8BOejqO98u0vT0-pnfTgB1Frxk_3uWI,3391
43
- pretix_map-0.1.3.dist-info/METADATA,sha256=LslAvtHnkh0p5WdCL6vJi5ERPpFNuOXzkvy7ZcRA-jQ,9518
44
- pretix_map-0.1.3.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
45
- pretix_map-0.1.3.dist-info/entry_points.txt,sha256=C3NAjeZHoCekafkLMCJynPcABRTK8AUprtQv7sUNDZs,137
46
- pretix_map-0.1.3.dist-info/top_level.txt,sha256=CAtEnkgA73zE9Gadm5mjt1SpXHBPOS-QWP0dQVoNToE,17
47
- pretix_map-0.1.3.dist-info/RECORD,,
42
+ pretix_mapplugin/templates/pretix_mapplugin/map_page.html,sha256=wrUFxvtlmBhgADoRqB7sL3QsGETNC_DgYjWboHvDYFw,4827
43
+ pretix_map-0.1.4.dist-info/METADATA,sha256=BkU4P9Kyz6cXKpmquqrTwkVXTd3ng0ygN_kmbP3YBuU,9518
44
+ pretix_map-0.1.4.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
45
+ pretix_map-0.1.4.dist-info/entry_points.txt,sha256=C3NAjeZHoCekafkLMCJynPcABRTK8AUprtQv7sUNDZs,137
46
+ pretix_map-0.1.4.dist-info/top_level.txt,sha256=CAtEnkgA73zE9Gadm5mjt1SpXHBPOS-QWP0dQVoNToE,17
47
+ pretix_map-0.1.4.dist-info/RECORD,,
@@ -1 +1 @@
1
- __version__ = "0.1.3"
1
+ __version__ = "0.1.4"
@@ -131,7 +131,7 @@ class Command(BaseCommand):
131
131
  with scope(organizer=organizer):
132
132
  # --- Get orders ---
133
133
  orders_qs = Order.objects.filter(status=Order.STATUS_PAID).select_related(
134
- 'invoice_address', 'invoice_address__country', 'event'
134
+ 'invoice_address', 'event'
135
135
  )
136
136
 
137
137
  # --- Filter by event ---
@@ -1,7 +1,7 @@
1
1
  .plugin-map-content-wrapper {
2
2
  display: flex;
3
3
  flex-direction: column;
4
- height: calc(100vh - 200px);
4
+ height: calc(100vh - 100px);
5
5
  min-height: 450px;
6
6
  }
7
7
 
@@ -2,21 +2,18 @@ document.addEventListener('DOMContentLoaded', function () {
2
2
  console.log("Sales Map JS Loaded (Cluster Toggle & Heatmap Opts)");
3
3
 
4
4
  // --- L.Icon.Default setup ---
5
- // Explicitly set the path to Leaflet's default icon images
6
5
  try {
7
6
  delete L.Icon.Default.prototype._getIconUrl;
8
7
  L.Icon.Default.mergeOptions({
9
- iconRetinaUrl: '/static/leaflet/images/marker-icon-2x.png', // Path within your collected static files
10
- iconUrl: '/static/leaflet/images/marker-icon.png', // Path within your collected static files
11
- shadowUrl: '/static/leaflet/images/marker-shadow.png', // Path within your collected static files
8
+ iconRetinaUrl: '/static/leaflet/images/marker-icon-2x.png',
9
+ iconUrl: '/static/leaflet/images/marker-icon.png',
10
+ shadowUrl: '/static/leaflet/images/marker-shadow.png',
12
11
  iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34],
13
12
  tooltipAnchor: [16, -28], shadowSize: [41, 41]
14
13
  });
15
14
  console.log("Set Leaflet default icon image paths explicitly.");
16
15
  } catch (e) {
17
- console.error("Error setting Leaflet default icon path:", e);
18
- // Fallback might be needed for very old Leaflet versions
19
- // L.Icon.Default.imagePath = '/static/leaflet/images/';
16
+ console.error("Error setting icon path:", e);
20
17
  }
21
18
  // --- End L.Icon.Default setup ---
22
19
 
@@ -24,25 +21,22 @@ document.addEventListener('DOMContentLoaded', function () {
24
21
  // --- Configuration ---
25
22
  const mapContainerId = 'sales-map-container';
26
23
  const statusOverlayId = 'map-status-overlay';
27
- const viewToggleButtonId = 'view-toggle-btn'; // Changed from toggleButtonId for clarity
24
+ const viewToggleButtonId = 'view-toggle-btn';
28
25
  const clusterToggleButtonId = 'cluster-toggle-btn';
29
26
  const heatmapOptionsPanelId = 'heatmap-options-panel';
30
- const initialZoom = 5; // Start more zoomed out
31
- const defaultMapView = 'pins'; // Start with pins
27
+ const initialZoom = 5;
28
+ const defaultMapView = 'pins';
32
29
 
33
30
  // --- Globals & State ---
34
31
  let map = null;
35
- let coordinateData = []; // Stores {lat, lon, tooltip, order_url}
36
- let pinLayer = null; // Holds L.markerClusterGroup OR L.layerGroup
32
+ let coordinateData = [];
33
+ let pinLayer = null;
37
34
  let heatmapLayer = null;
38
35
  let currentView = defaultMapView;
39
36
  let dataUrl = null;
40
- let isClusteringEnabled = true; // Default Cluster State: ON
41
- let heatmapOptions = { // Default Heatmap Options (state variable)
42
- radius: 25,
43
- blur: 15,
44
- maxZoom: 18,
45
- minOpacity: 0.2 // Example optional parameter
37
+ let isClusteringEnabled = true;
38
+ let heatmapOptions = {
39
+ radius: 25, blur: 15, maxZoom: 18, minOpacity: 0.2
46
40
  };
47
41
 
48
42
  // --- DOM Elements ---
@@ -51,7 +45,6 @@ document.addEventListener('DOMContentLoaded', function () {
51
45
  const viewToggleButton = document.getElementById(viewToggleButtonId);
52
46
  const clusterToggleButton = document.getElementById(clusterToggleButtonId);
53
47
  const heatmapOptionsPanel = document.getElementById(heatmapOptionsPanelId);
54
- // Heatmap control elements
55
48
  const heatmapRadiusInput = document.getElementById('heatmap-radius');
56
49
  const heatmapBlurInput = document.getElementById('heatmap-blur');
57
50
  const heatmapMaxZoomInput = document.getElementById('heatmap-maxZoom');
@@ -63,28 +56,39 @@ document.addEventListener('DOMContentLoaded', function () {
63
56
  function updateStatus(message, isError = false) {
64
57
  if (statusOverlay) {
65
58
  const p = statusOverlay.querySelector('p');
66
- if (p) { p.textContent = message; p.className = isError ? 'text-danger' : ''; }
67
- statusOverlay.style.display = 'flex'; // Show overlay when status updates
68
- } else { console.warn("Status overlay element not found."); }
59
+ if (p) {
60
+ p.textContent = message;
61
+ p.className = isError ? 'text-danger' : '';
62
+ }
63
+ statusOverlay.style.display = 'flex';
64
+ } else {
65
+ console.warn("Status overlay element not found.");
66
+ }
69
67
  }
68
+
70
69
  function hideStatus() {
71
- if (statusOverlay) { statusOverlay.style.display = 'none'; } // Hide overlay
70
+ if (statusOverlay) {
71
+ statusOverlay.style.display = 'none';
72
+ }
72
73
  }
74
+
73
75
  // --- End Status Helpers ---
74
76
 
75
77
 
76
78
  // --- Initialization ---
77
79
  function initializeMap() {
78
- // Check required elements
79
- if (!mapElement) { console.error(`Map container #${mapContainerId} not found.`); return; }
80
- if (!statusOverlay) { console.warn("Status overlay element not found."); }
81
- // Check control elements (warn but continue if missing)
80
+ if (!mapElement) {
81
+ console.error(`Map container #${mapContainerId} not found.`);
82
+ return;
83
+ }
84
+ if (!statusOverlay) {
85
+ console.warn("Status overlay element not found.");
86
+ }
82
87
  if (!viewToggleButton) console.warn("View toggle button not found.");
83
88
  if (!clusterToggleButton) console.warn("Cluster toggle button not found.");
84
89
  if (!heatmapOptionsPanel) console.warn("Heatmap options panel not found.");
85
90
  if (!heatmapRadiusInput || !heatmapBlurInput || !heatmapMaxZoomInput) console.warn("Heatmap input elements missing.");
86
91
 
87
- // Get Data URL
88
92
  dataUrl = mapElement.dataset.dataUrl;
89
93
  if (!dataUrl) {
90
94
  updateStatus("Configuration Error: Missing data source URL.", true);
@@ -93,24 +97,21 @@ document.addEventListener('DOMContentLoaded', function () {
93
97
  console.log(`Data URL found: ${dataUrl}`);
94
98
  updateStatus("Initializing map...");
95
99
 
96
- // Create Leaflet Map
97
100
  try {
98
- map = L.map(mapContainerId).setView([48.85, 2.35], initialZoom); // Centered somewhat on Europe
101
+ map = L.map(mapContainerId).setView([48.85, 2.35], initialZoom);
99
102
  console.log("L.map() called successfully.");
100
103
 
101
- // Add Base Tile Layer
102
104
  L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
103
- attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
104
- maxZoom: 18, // Standard max zoom
105
- }).on('load', function() { console.log('Base tiles loaded.'); }).addTo(map);
105
+ attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
106
+ maxZoom: 18,
107
+ }).on('load', function () {
108
+ console.log('Base tiles loaded.');
109
+ }).addTo(map);
106
110
  console.log("Tile layer added successfully.");
107
111
 
108
- // Setup Controls (Event Listeners)
109
112
  if (viewToggleButton) setupViewToggleButton();
110
113
  if (clusterToggleButton) setupClusterToggleButton();
111
- setupHeatmapControls(); // Setup listeners even if panel hidden
112
-
113
- // Fetch data to populate layers
114
+ setupHeatmapControls();
114
115
  fetchDataAndDraw();
115
116
 
116
117
  } catch (error) {
@@ -125,298 +126,307 @@ document.addEventListener('DOMContentLoaded', function () {
125
126
  if (!dataUrl) return;
126
127
  console.log("Fetching coordinates from:", dataUrl);
127
128
  updateStatus("Loading ticket locations...");
128
- // Disable controls while loading
129
- if(viewToggleButton) viewToggleButton.disabled = true;
130
- if(clusterToggleButton) clusterToggleButton.disabled = true;
129
+ if (viewToggleButton) viewToggleButton.disabled = true;
130
+ if (clusterToggleButton) clusterToggleButton.disabled = true;
131
131
  disableHeatmapControls(true);
132
132
 
133
133
  fetch(dataUrl)
134
134
  .then(response => {
135
- if (!response.ok) throw new Error(`HTTP error! Status: ${response.status} ${response.statusText}`);
136
- return response.json();
137
- })
135
+ if (!response.ok) throw new Error(`HTTP error! Status: ${response.status} ${response.statusText}`);
136
+ return response.json();
137
+ })
138
138
  .then(data => {
139
139
  if (data.error) throw new Error(`API Error: ${data.error}`);
140
140
  if (!data || !data.locations || !Array.isArray(data.locations)) {
141
- console.warn("Invalid or empty data format received:", data);
142
- updateStatus("No valid geocoded ticket locations found.", false);
143
- coordinateData = []; hideStatus(); return;
141
+ console.warn("Invalid data format:", data);
142
+ updateStatus("No valid locations found.", false);
143
+ coordinateData = [];
144
+ hideStatus();
145
+ return;
144
146
  }
145
147
  if (data.locations.length === 0) {
146
- console.log("No coordinate data received (empty list).");
147
- updateStatus("No geocoded ticket locations found for this event.", false);
148
- coordinateData = []; hideStatus(); return;
148
+ console.log("No locations received.");
149
+ updateStatus("No locations found for event.", false);
150
+ coordinateData = [];
151
+ hideStatus();
152
+ return;
149
153
  }
150
154
 
151
155
  coordinateData = data.locations;
152
156
  console.log(`Received ${coordinateData.length} coordinates.`);
153
157
 
154
- // Enable controls now that we have data
155
- if(viewToggleButton) viewToggleButton.disabled = false;
156
- // Only enable cluster button if in pins view initially and data exists
157
- if(clusterToggleButton) clusterToggleButton.disabled = (currentView !== 'pins');
158
- // Only enable heatmap controls if data exists
158
+ if (viewToggleButton) viewToggleButton.disabled = false;
159
+ if (clusterToggleButton) clusterToggleButton.disabled = (currentView !== 'pins');
159
160
  disableHeatmapControls(false);
160
161
 
161
- // Create both layers based on fetched data
162
162
  createAllLayers();
163
- // Display the default layer
164
163
  showCurrentView();
165
- // Adjust bounds to fit the data
166
164
  adjustMapBounds();
165
+ hideStatus();
167
166
 
168
- hideStatus(); // Hide loading overlay
169
-
170
- // Force redraw after a short delay
171
167
  setTimeout(function () {
172
168
  console.log("Forcing map.invalidateSize() after data load...");
173
169
  const container = document.getElementById(mapContainerId);
174
170
  if (map && container && container.offsetWidth > 0) {
175
- map.invalidateSize();
171
+ map.invalidateSize();
176
172
  } else {
177
- console.warn(`Skipping invalidateSize. Map: ${!!map}, Container: ${!!container}, OffsetWidth: ${container ? container.offsetWidth : 'N/A'}`);
173
+ console.warn(`Skipping invalidateSize. Map: ${!!map}, Container: ${!!container}, OffsetWidth: ${container ? container.offsetWidth : 'N/A'}`);
178
174
  }
179
175
  }, 100);
180
176
  })
181
177
  .catch(error => {
182
- console.error('Error fetching or processing coordinate data:', error);
183
- updateStatus(`Error loading map data: ${error.message}. Please try again later.`, true);
184
- // Keep controls disabled on error
178
+ console.error('Error fetching/processing data:', error);
179
+ updateStatus(`Error loading map data: ${error.message}.`, true);
185
180
  });
186
181
  }
187
182
 
188
183
 
189
184
  // --- Layer Creation Functions ---
190
185
  function createAllLayers() {
191
- // Orchestrates creation of both layer types
192
186
  createPinLayer();
193
187
  createHeatmapLayer();
194
- console.log("Map layer instances created/updated.");
188
+ console.log("Layers created/updated.");
195
189
  }
196
190
 
197
191
  function createPinLayer() {
198
- // Creates/recreates the pin layer based on isClusteringEnabled state
199
- console.log(`Creating pin layer instance (Clustering: ${isClusteringEnabled})...`);
200
- pinLayer = null; // Clear previous instance
201
- if (coordinateData.length === 0) { console.warn("No coordinate data for pin layer."); return; }
202
-
192
+ console.log(`Creating pin layer (Clustering: ${isClusteringEnabled})...`);
193
+ pinLayer = null;
194
+ if (coordinateData.length === 0) {
195
+ console.warn("No data for pin layer.");
196
+ return;
197
+ }
203
198
  const markers = [];
204
199
  coordinateData.forEach((loc, index) => {
205
200
  try {
206
201
  if (loc.lat == null || loc.lon == null || isNaN(loc.lat) || isNaN(loc.lon)) return;
207
- const latLng = L.latLng(loc.lat, loc.lon);
208
- const marker = L.marker(latLng);
202
+ const marker = L.marker(L.latLng(loc.lat, loc.lon));
209
203
  if (loc.tooltip) marker.bindTooltip(loc.tooltip);
210
- if (loc.order_url) { marker.on('click', () => window.open(loc.order_url, '_blank')); }
204
+ if (loc.order_url) {
205
+ marker.on('click', () => window.open(loc.order_url, '_blank'));
206
+ }
211
207
  markers.push(marker);
212
- } catch (e) { console.error(`Error creating marker ${index}:`, e); }
208
+ } catch (e) {
209
+ console.error(`Error creating marker ${index}:`, e);
210
+ }
213
211
  });
214
-
215
- if (markers.length === 0) { console.warn("No valid markers created for pin layer."); return; }
216
-
212
+ if (markers.length === 0) {
213
+ console.warn("No valid markers created.");
214
+ return;
215
+ }
217
216
  if (isClusteringEnabled) {
218
- pinLayer = L.markerClusterGroup(); // Use clustering
217
+ pinLayer = L.markerClusterGroup();
219
218
  pinLayer.addLayers(markers);
220
- console.log("Marker cluster group created and populated.");
219
+ console.log("Marker cluster populated.");
221
220
  } else {
222
- pinLayer = L.layerGroup(markers); // Use simple layer group
223
- console.log("Simple layer group created and populated.");
221
+ pinLayer = L.layerGroup(markers);
222
+ console.log("Simple layer group populated.");
224
223
  }
225
224
  }
226
225
 
227
226
  function createHeatmapLayer() {
228
- // Creates/recreates the heatmap layer using current heatmapOptions
229
- console.log("Creating heatmap layer instance...");
230
- heatmapLayer = null; // Clear previous instance
231
- if (coordinateData.length === 0) { console.warn("No coordinate data for heatmap layer."); return; }
232
-
227
+ console.log("Creating heatmap layer...");
228
+ heatmapLayer = null;
229
+ if (coordinateData.length === 0) {
230
+ console.warn("No data for heatmap.");
231
+ return;
232
+ }
233
233
  try {
234
234
  const heatPoints = coordinateData.map(loc => {
235
235
  if (loc.lat != null && loc.lon != null && !isNaN(loc.lat) && !isNaN(loc.lon)) {
236
- return [loc.lat, loc.lon, 1.0]; // Intensity 1.0
237
- } return null;
236
+ return [loc.lat, loc.lon, 1.0];
237
+ }
238
+ return null;
238
239
  }).filter(p => p !== null);
239
-
240
240
  if (heatPoints.length > 0) {
241
- heatmapLayer = L.heatLayer(heatPoints, heatmapOptions); // Use state variable
242
- console.log("Heatmap layer instance created with options:", heatmapOptions);
243
- } else { console.warn("No valid points for heatmap layer."); }
244
- } catch (e) { console.error("Error creating heatmap layer instance:", e); }
241
+ heatmapLayer = L.heatLayer(heatPoints, heatmapOptions);
242
+ console.log("Heatmap created:", heatmapOptions);
243
+ } else {
244
+ console.warn("No valid points for heatmap.");
245
+ }
246
+ } catch (e) {
247
+ console.error("Error creating heatmap:", e);
248
+ }
245
249
  }
246
250
 
247
251
 
248
252
  // --- Layer Update Functions ---
249
253
  function redrawPinLayer() {
250
- // Removes existing pin layer, calls createPinLayer, adds new one if current view is 'pins'
251
254
  if (!map) return;
252
255
  console.log("Redrawing pin layer...");
253
- if (pinLayer && map.hasLayer(pinLayer)) {
254
- map.removeLayer(pinLayer);
255
- console.log("Removed existing pin layer before redraw.");
256
- }
257
- pinLayer = null; // Ensure it's cleared
258
- createPinLayer(); // Recreate based on current clustering state
256
+ if (pinLayer && map.hasLayer(pinLayer)) map.removeLayer(pinLayer);
257
+ pinLayer = null;
258
+ createPinLayer();
259
259
  if (currentView === 'pins' && pinLayer) {
260
- console.log("Adding newly created pin layer to map.");
261
- map.addLayer(pinLayer);
260
+ console.log("Adding new pin layer.");
261
+ map.addLayer(pinLayer);
262
262
  }
263
263
  }
264
264
 
265
265
  function updateHeatmap() {
266
- // Updates options on the existing heatmap layer if it's present
267
266
  if (!map || !heatmapLayer) {
268
- console.warn("Cannot update heatmap: map or heatmap layer missing.");
267
+ console.warn("Cannot update heatmap.");
269
268
  return;
270
269
  }
271
- console.log("Updating heatmap options:", heatmapOptions);
270
+ console.log("Updating heatmap opts:", heatmapOptions);
272
271
  try {
273
- // Use Leaflet.heat's setOptions method
274
- heatmapLayer.setOptions(heatmapOptions);
275
- console.log("Heatmap options updated.");
276
- } catch(e) {
277
- console.error("Error setting heatmap options:", e);
272
+ heatmapLayer.setOptions(heatmapOptions);
273
+ console.log("Heatmap opts updated.");
274
+ } catch (e) {
275
+ console.error("Error setting heatmap opts:", e);
278
276
  }
279
277
  }
280
278
 
281
279
 
282
- // --- Adjust Map Bounds ---
280
+ // --- Adjust Map Bounds (Corrected) ---
283
281
  function adjustMapBounds() {
284
- // (Keep the working version from previous steps)
285
282
  if (!map || coordinateData.length === 0) return;
286
283
  try {
287
284
  let bounds = null;
288
285
  if (currentView === 'pins' && pinLayer && typeof pinLayer.getBounds === 'function') {
289
- bounds = pinLayer.getBounds();
290
- console.log("Attempting bounds from pin layer.");
286
+ bounds = pinLayer.getBounds();
287
+ console.log("Attempting bounds from pin layer.");
291
288
  }
289
+ // Calculate from raw if pin layer bounds unavailable/invalid or if in heatmap view
292
290
  if (!bounds || !bounds.isValid()) {
293
291
  console.log("Calculating bounds from raw coordinates.");
294
- const latLngs = coordinateData.map(loc => { /* ... filter valid ... */ return [loc.lat, loc.lon]; }).filter(p => p !== null);
292
+ // Filter valid lat/lon pairs
293
+ const latLngs = coordinateData
294
+ .map(loc => {
295
+ if (loc.lat != null && loc.lon != null && !isNaN(loc.lat) && !isNaN(loc.lon)) {
296
+ return [loc.lat, loc.lon];
297
+ }
298
+ return null;
299
+ })
300
+ .filter(p => p !== null);
295
301
  if (latLngs.length > 0) bounds = L.latLngBounds(latLngs);
296
302
  }
303
+
304
+ // Apply bounds if valid
297
305
  if (bounds && bounds.isValid()) {
298
- console.log("Fitting map to bounds..."); map.fitBounds(bounds, { padding: [50, 50] }); console.log("Bounds fitted.");
299
- } else if (coordinateData.length === 1) {
300
- console.log("Setting view for single coordinate.");
301
- const singleCoord = coordinateData.find(loc => /* ... valid ... */);
302
- if (singleCoord) map.setView([singleCoord.lat, singleCoord.lon], 13);
303
- else console.warn("Could not find single valid coordinate.");
304
- } else { console.warn("Could not determine valid bounds."); }
305
- } catch (e) { console.error("Error fitting map bounds:", e); }
306
- }
306
+ console.log("Fitting map to bounds...");
307
+ map.fitBounds(bounds, {padding: [50, 50]});
308
+ console.log("Bounds fitted.");
309
+ // Handle single point case
310
+ } else if (coordinateData.filter(loc => loc.lat != null && loc.lon != null && !isNaN(loc.lat) && !isNaN(loc.lon)).length === 1) {
311
+ console.log("Setting view for single coordinate.");
312
+ // --- Corrected logic to find the single valid coordinate ---
313
+ const singleCoord = coordinateData.find(loc => loc.lat != null && loc.lon != null && !isNaN(loc.lat) && !isNaN(loc.lon));
314
+ // --- End Correction ---
315
+ if (singleCoord) map.setView([singleCoord.lat, singleCoord.lon], 13);
316
+ else console.warn("Could not find single valid coordinate to set view.");
317
+ // Removed extra ')' here
318
+ } else {
319
+ console.warn("Could not determine valid bounds.");
320
+ }
321
+ } catch (e) {
322
+ console.error("Error fitting map bounds:", e);
323
+ }
324
+ } // End adjustMapBounds function
307
325
 
308
326
 
309
327
  // --- Control Setup Functions ---
310
328
  function setupViewToggleButton() {
311
- updateViewToggleButtonText(); // Initial text
329
+ updateViewToggleButtonText();
312
330
  viewToggleButton.addEventListener('click', () => {
313
- console.log("View toggle button clicked!");
331
+ console.log("View toggle clicked!");
314
332
  currentView = (currentView === 'pins') ? 'heatmap' : 'pins';
315
- showCurrentView(); // Update map display
316
- updateViewToggleButtonText(); // Update button text
317
- // Enable/disable cluster button based on view
333
+ showCurrentView();
334
+ updateViewToggleButtonText();
318
335
  if (clusterToggleButton) clusterToggleButton.disabled = (currentView !== 'pins');
319
336
  });
320
- console.log("View toggle button listener setup complete.");
337
+ console.log("View toggle listener setup.");
321
338
  }
322
339
 
323
340
  function setupClusterToggleButton() {
324
- updateClusterToggleButtonText(); // Initial text
325
- // Start disabled if not in pins view
341
+ updateClusterToggleButtonText();
326
342
  clusterToggleButton.disabled = (currentView !== 'pins');
327
343
  clusterToggleButton.addEventListener('click', () => {
328
- if (currentView !== 'pins') return; // Safety check
329
- console.log("Cluster toggle button clicked!");
330
- isClusteringEnabled = !isClusteringEnabled; // Toggle state
331
- redrawPinLayer(); // Recreate and potentially re-add the pin layer
332
- updateClusterToggleButtonText(); // Update button text
344
+ if (currentView !== 'pins') return;
345
+ console.log("Cluster toggle clicked!");
346
+ isClusteringEnabled = !isClusteringEnabled;
347
+ redrawPinLayer();
348
+ updateClusterToggleButtonText();
333
349
  });
334
- console.log("Cluster toggle button listener setup complete.");
350
+ console.log("Cluster toggle listener setup.");
335
351
  }
336
352
 
337
353
  function setupHeatmapControls() {
338
- // Check if all elements exist before adding listeners
339
354
  if (!heatmapRadiusInput || !heatmapBlurInput || !heatmapMaxZoomInput || !radiusValueSpan || !blurValueSpan || !maxZoomValueSpan) {
340
- console.error("One or more heatmap control elements not found. Cannot setup listeners.");
355
+ console.error("Heatmap controls missing.");
341
356
  return;
342
357
  }
343
-
344
- // Set initial display values from defaults
345
358
  radiusValueSpan.textContent = heatmapOptions.radius;
346
359
  heatmapRadiusInput.value = heatmapOptions.radius;
347
360
  blurValueSpan.textContent = heatmapOptions.blur;
348
361
  heatmapBlurInput.value = heatmapOptions.blur;
349
362
  maxZoomValueSpan.textContent = heatmapOptions.maxZoom;
350
363
  heatmapMaxZoomInput.value = heatmapOptions.maxZoom;
351
-
352
- // Add 'input' listeners for real-time updates
353
364
  heatmapRadiusInput.addEventListener('input', (e) => {
354
- const value = parseFloat(e.target.value);
355
- heatmapOptions.radius = value; // Update state
356
- radiusValueSpan.textContent = value; // Update display
357
- updateHeatmap(); // Apply change to map
365
+ const v = parseFloat(e.target.value);
366
+ heatmapOptions.radius = v;
367
+ radiusValueSpan.textContent = v;
368
+ updateHeatmap();
358
369
  });
359
370
  heatmapBlurInput.addEventListener('input', (e) => {
360
- const value = parseFloat(e.target.value);
361
- heatmapOptions.blur = value; // Update state
362
- blurValueSpan.textContent = value; // Update display
363
- updateHeatmap(); // Apply change to map
371
+ const v = parseFloat(e.target.value);
372
+ heatmapOptions.blur = v;
373
+ blurValueSpan.textContent = v;
374
+ updateHeatmap();
364
375
  });
365
376
  heatmapMaxZoomInput.addEventListener('input', (e) => {
366
- const value = parseInt(e.target.value, 10);
367
- heatmapOptions.maxZoom = value; // Update state
368
- maxZoomValueSpan.textContent = value; // Update display
369
- updateHeatmap(); // Apply change to map
377
+ const v = parseInt(e.target.value, 10);
378
+ heatmapOptions.maxZoom = v;
379
+ maxZoomValueSpan.textContent = v;
380
+ updateHeatmap();
370
381
  });
371
- console.log("Heatmap control listeners setup complete.");
382
+ console.log("Heatmap control listeners setup.");
372
383
  }
373
384
 
374
- function disableHeatmapControls(disabled) {
375
- // Helper to enable/disable all heatmap inputs
376
- if (heatmapRadiusInput) heatmapRadiusInput.disabled = disabled;
377
- if (heatmapBlurInput) heatmapBlurInput.disabled = disabled;
378
- if (heatmapMaxZoomInput) heatmapMaxZoomInput.disabled = disabled;
379
- }
385
+ function disableHeatmapControls(disabled) {
386
+ if (heatmapRadiusInput) heatmapRadiusInput.disabled = disabled;
387
+ if (heatmapBlurInput) heatmapBlurInput.disabled = disabled;
388
+ if (heatmapMaxZoomInput) heatmapMaxZoomInput.disabled = disabled;
389
+ }
380
390
 
381
391
 
382
392
  // --- View Switching Logic ---
383
393
  function showCurrentView() {
384
394
  console.log(`Showing view: ${currentView}`);
385
- if (!map) { console.warn("Map not initialized."); return; }
386
-
387
- // Remove existing layers from map
388
- console.log("Removing existing layers (if present)...");
395
+ if (!map) {
396
+ console.warn("Map not init.");
397
+ return;
398
+ }
399
+ console.log("Removing layers...");
389
400
  if (pinLayer && map.hasLayer(pinLayer)) map.removeLayer(pinLayer);
390
401
  if (heatmapLayer && map.hasLayer(heatmapLayer)) map.removeLayer(heatmapLayer);
391
-
392
- // Add the selected layer and show/hide controls
393
402
  console.log(`Adding ${currentView} layer...`);
394
403
  let layerToAdd = null;
395
404
  if (currentView === 'pins') {
396
405
  layerToAdd = pinLayer;
397
- if(heatmapOptionsPanel) heatmapOptionsPanel.style.display = 'none';
398
- if(clusterToggleButton) clusterToggleButton.style.display = 'inline-block'; // Show cluster btn
399
- if(clusterToggleButton) clusterToggleButton.disabled = false; // Ensure enabled
400
- } else { // Heatmap view
406
+ if (heatmapOptionsPanel) heatmapOptionsPanel.style.display = 'none';
407
+ if (clusterToggleButton) {
408
+ clusterToggleButton.style.display = 'inline-block';
409
+ clusterToggleButton.disabled = false;
410
+ }
411
+ } else {
401
412
  layerToAdd = heatmapLayer;
402
- if(heatmapOptionsPanel) heatmapOptionsPanel.style.display = 'block';
403
- if(clusterToggleButton) clusterToggleButton.style.display = 'none'; // Hide cluster btn
404
- if(clusterToggleButton) clusterToggleButton.disabled = true; // Ensure disabled
413
+ if (heatmapOptionsPanel) heatmapOptionsPanel.style.display = 'block';
414
+ if (clusterToggleButton) {
415
+ clusterToggleButton.style.display = 'none';
416
+ clusterToggleButton.disabled = true;
417
+ }
405
418
  }
406
-
407
- // Add the layer to the map if it exists
408
419
  if (layerToAdd) {
409
420
  try {
410
- map.addLayer(layerToAdd);
411
- console.log(`Added ${currentView} layer to map.`);
421
+ map.addLayer(layerToAdd);
422
+ console.log(`Added ${currentView} layer.`);
412
423
  } catch (e) {
413
- console.error(`Error adding ${currentView} layer:`, e);
414
- updateStatus(`Error displaying ${currentView} layer.`, true);
424
+ console.error(`Error adding ${currentView}:`, e);
425
+ updateStatus(`Error display ${currentView}.`, true);
415
426
  }
416
427
  } else {
417
- // This happens if createLayer failed (e.g., no valid data)
418
- console.warn(`Cannot add layer for view "${currentView}": Layer instance missing.`);
419
- updateStatus(`No data available for ${currentView} view.`, false);
428
+ console.warn(`Layer instance missing for ${currentView}.`);
429
+ updateStatus(`No data for ${currentView}.`, false);
420
430
  }
421
431
  }
422
432
 
@@ -424,15 +434,15 @@ document.addEventListener('DOMContentLoaded', function () {
424
434
  // --- Button Text Update Functions ---
425
435
  function updateViewToggleButtonText() {
426
436
  if (!viewToggleButton) return;
427
- const nextViewText = (currentView === 'pins') ? 'Heatmap' : 'Pin';
428
- viewToggleButton.textContent = `Switch to ${nextViewText} View`;
429
- console.log(`View Toggle Button text updated to: ${viewToggleButton.textContent}`);
437
+ const next = (currentView === 'pins') ? 'Heatmap' : 'Pin';
438
+ viewToggleButton.textContent = `Switch to ${next} View`;
439
+ console.log(`View Btn text: ${viewToggleButton.textContent}`);
430
440
  }
431
441
 
432
442
  function updateClusterToggleButtonText() {
433
- if (!clusterToggleButton) return;
443
+ if (!clusterToggleButton) return;
434
444
  clusterToggleButton.textContent = isClusteringEnabled ? 'Disable Clustering' : 'Enable Clustering';
435
- console.log(`Cluster Toggle Button text updated to: ${clusterToggleButton.textContent}`);
445
+ console.log(`Cluster Btn text: ${clusterToggleButton.textContent}`);
436
446
  }
437
447
 
438
448
 
@@ -6,62 +6,82 @@
6
6
  {% block title %}{% trans "Ticket Sales Map" %}{% endblock %}
7
7
 
8
8
  {% block inside %}
9
- <h1>{% trans "Ticket Sales Map" %}</h1>
10
9
 
11
- {# -- Controls Row -- #}
12
- <div class="form-inline" style="margin-bottom: 1em;">
13
- <div class="form-group" style="margin-right: 10px;">
14
- <button id="view-toggle-btn" class="btn btn-default">Switch to Heatmap View</button>
15
- </div>
16
- {# -- NEW Cluster Toggle Button -- #}
17
- <div class="form-group" style="margin-right: 10px;">
18
- <button id="cluster-toggle-btn" class="btn btn-default" disabled>Disable Clustering</button>
19
- </div>
20
- {# -- End Cluster Toggle Button -- #}
21
- </div>
22
- {# -- End Controls Row -- #}
23
-
24
- {# -- NEW Heatmap Options Panel (Initially hidden for pins view) -- #}
25
- <div id="heatmap-options-panel" class="panel panel-default"
26
- style="display: none; padding: 15px; margin-bottom: 1em; max-width: 400px;">
27
- <h4>{% trans "Heatmap Options" %}</h4>
28
- <div class="form-group">
29
- <label for="heatmap-radius">Radius: <span id="radius-value">25</span></label>
30
- <input type="range" id="heatmap-radius" class="form-control" min="1" max="100" value="25" step="1" disabled>
31
- </div>
32
- <div class="form-group">
33
- <label for="heatmap-blur">Blur: <span id="blur-value">15</span></label>
34
- <input type="range" id="heatmap-blur" class="form-control" min="1" max="50" value="15" step="1" disabled>
35
- </div>
36
- <div class="form-group">
37
- <label for="heatmap-maxZoom">Max Zoom: <span id="maxzoom-value">18</span></label>
38
- <input type="range" id="heatmap-maxZoom" class="form-control" min="1" max="18" value="18" step="1" disabled>
39
- </div>
40
- {# Add more options like gradient if desired later #}
41
- </div>
42
- {# -- End Heatmap Options Panel -- #}
10
+ <div class="plugin-map-content-wrapper">
43
11
 
12
+ <h1>{% trans "Ticket Sales Map" %}</h1>
13
+
14
+ <div class="form-inline map-controls-row"
15
+ style="margin-bottom: 1em; display: flex; flex-wrap: wrap; align-items: flex-start; gap: 15px;">
44
16
 
45
- {# -- Map Area -- #}
46
- <div style="position: relative; border: 1px solid #ccc;">
47
- <div id="sales-map-container"
48
- data-data-url="{% url 'plugins:pretix_mapplugin:event.settings.salesmap.data' organizer=request.organizer.slug event=request.event.slug %}">
17
+ <div class="map-buttons-group" style="display: flex; flex-wrap: wrap; gap: 10px; align-items: center;">
18
+ <div class="form-group">
19
+ <button id="view-toggle-btn" class="btn btn-default" disabled>Switch to Heatmap View</button>
20
+ </div>
21
+ <div class="form-group">
22
+ <button id="cluster-toggle-btn" class="btn btn-default" disabled style="display: inline-block;">
23
+ Disable Clustering
24
+ </button>
25
+ </div>
26
+ </div>
27
+ <div id="heatmap-options-panel" class="panel panel-default"
28
+ style="display: none; padding: 10px 15px; border-radius: 4px; min-width: 350px;">
29
+ <h5 style="margin-top: 0; margin-bottom: 10px;">{% trans "Heatmap Options" %}</h5>
30
+ <div class="form-horizontal">
31
+ <div class="form-group form-group-sm" style="margin-bottom: 5px;">
32
+ <label for="heatmap-radius" class="col-sm-3 control-label"
33
+ style="padding-top: 5px;">Radius</label>
34
+ <div class="col-sm-7">
35
+ <input type="range" id="heatmap-radius" class="form-control" min="1" max="100" value="25"
36
+ step="1" disabled>
37
+ </div>
38
+ <div class="col-sm-2">
39
+ <span id="radius-value" class="form-control-static">25</span>
40
+ </div>
41
+ </div>
42
+ <div class="form-group form-group-sm" style="margin-bottom: 5px;">
43
+ <label for="heatmap-blur" class="col-sm-3 control-label" style="padding-top: 5px;">Blur</label>
44
+ <div class="col-sm-7">
45
+ <input type="range" id="heatmap-blur" class="form-control" min="1" max="50" value="15"
46
+ step="1" disabled>
47
+ </div>
48
+ <div class="col-sm-2">
49
+ <span id="blur-value" class="form-control-static">15</span>
50
+ </div>
51
+ </div>
52
+ <div class="form-group form-group-sm" style="margin-bottom: 5px;">
53
+ <label for="heatmap-maxZoom" class="col-sm-3 control-label" style="padding-top: 5px;">Max
54
+ Zoom</label>
55
+ <div class="col-sm-7">
56
+ <input type="range" id="heatmap-maxZoom" class="form-control" min="1" max="18" value="18"
57
+ step="1" disabled>
58
+ </div>
59
+ <div class="col-sm-2">
60
+ <span id="maxzoom-value" class="form-control-static">18</span>
61
+ </div>
62
+ </div>
63
+ </div>
64
+ </div>
49
65
  </div>
50
- <div id="map-status-overlay"
51
- style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(255, 255, 255, 0.8); z-index: 1000; display: flex; justify-content: center; align-items: center; text-align: center;">
52
- <p>Loading map data...</p>
66
+
67
+ <div class="map-wrapper" style="position: relative; border: 1px solid #ccc; flex-grow: 1; min-height: 0;">
68
+ <div id="sales-map-container"
69
+ data-data-url="{% url 'plugins:pretix_mapplugin:event.settings.salesmap.data' organizer=request.organizer.slug event=request.event.slug %}">
70
+ </div>
71
+ <div id="map-status-overlay"
72
+ style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(255, 255, 255, 0.8); z-index: 1000; display: flex; justify-content: center; align-items: center; text-align: center;">
73
+ <p>Loading map data...</p>
74
+ </div>
53
75
  </div>
54
76
  </div>
55
- {# -- End Map Area -- #}
56
-
57
77
 
58
- {# === Leaflet & Plugins (Keep as is) === #}
59
- <script src="{% static 'pretix_mapplugin/libs/leaflet-sales-map/leaflet-heat.js' %}"></script>
78
+ <script src="
79
+ {% static 'pretix_mapplugin/libs/leaflet-sales-map/leaflet-heat.js' %}"></script>
60
80
  <link rel="stylesheet" href="{% static 'pretix_mapplugin/libs/leaflet-sales-map/MarkerCluster.css' %}"/>
61
- <link rel="stylesheet" href="{% static 'pretix_mapplugin/libs/leaflet-sales-map/MarkerCluster.Default.css' %}"/>
81
+ <link rel="stylesheet"
82
+ href="{% static 'pretix_mapplugin/libs/leaflet-sales-map/MarkerCluster.Default.css' %}"/>
62
83
  <script src="{% static 'pretix_mapplugin/libs/leaflet-sales-map/leaflet.markercluster.js' %}"></script>
63
84
 
64
- {# === Your JS/CSS (Keep as is) === #}
65
85
  <script src="{% static 'pretix_mapplugin/js/salesmap.js' %}"></script>
66
86
  <link rel="stylesheet" href="{% static 'pretix_mapplugin/css/salesmap.css' %}"/>
67
87