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.
- {pretix_map-0.1.3.dist-info → pretix_map-0.1.4.dist-info}/METADATA +1 -1
- {pretix_map-0.1.3.dist-info → pretix_map-0.1.4.dist-info}/RECORD +11 -11
- pretix_mapplugin/__init__.py +1 -1
- pretix_mapplugin/management/commands/geocode_existing_orders.py +1 -1
- pretix_mapplugin/static/pretix_mapplugin/css/salesmap.css +1 -1
- pretix_mapplugin/static/pretix_mapplugin/js/salesmap.js +203 -193
- pretix_mapplugin/templates/pretix_mapplugin/map_page.html +66 -46
- {pretix_map-0.1.3.dist-info → pretix_map-0.1.4.dist-info}/WHEEL +0 -0
- {pretix_map-0.1.3.dist-info → pretix_map-0.1.4.dist-info}/entry_points.txt +0 -0
- {pretix_map-0.1.3.dist-info → pretix_map-0.1.4.dist-info}/licenses/LICENSE +0 -0
- {pretix_map-0.1.3.dist-info → pretix_map-0.1.4.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: pretix-map
|
3
|
-
Version: 0.1.
|
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.
|
2
|
-
pretix_mapplugin/__init__.py,sha256=
|
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=
|
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=
|
23
|
-
pretix_mapplugin/static/pretix_mapplugin/js/salesmap.js,sha256=
|
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=
|
43
|
-
pretix_map-0.1.
|
44
|
-
pretix_map-0.1.
|
45
|
-
pretix_map-0.1.
|
46
|
-
pretix_map-0.1.
|
47
|
-
pretix_map-0.1.
|
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,,
|
pretix_mapplugin/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "0.1.
|
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', '
|
134
|
+
'invoice_address', 'event'
|
135
135
|
)
|
136
136
|
|
137
137
|
# --- Filter by event ---
|
@@ -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',
|
10
|
-
iconUrl: '/static/leaflet/images/marker-icon.png',
|
11
|
-
shadowUrl: '/static/leaflet/images/marker-shadow.png',
|
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
|
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';
|
24
|
+
const viewToggleButtonId = 'view-toggle-btn';
|
28
25
|
const clusterToggleButtonId = 'cluster-toggle-btn';
|
29
26
|
const heatmapOptionsPanelId = 'heatmap-options-panel';
|
30
|
-
const initialZoom = 5;
|
31
|
-
const defaultMapView = 'pins';
|
27
|
+
const initialZoom = 5;
|
28
|
+
const defaultMapView = 'pins';
|
32
29
|
|
33
30
|
// --- Globals & State ---
|
34
31
|
let map = null;
|
35
|
-
let coordinateData = [];
|
36
|
-
let pinLayer = null;
|
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;
|
41
|
-
let heatmapOptions = {
|
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) {
|
67
|
-
|
68
|
-
|
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) {
|
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
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
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);
|
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
|
-
|
104
|
-
|
105
|
-
}).on('load', function() {
|
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();
|
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
|
-
|
129
|
-
if(
|
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
|
-
|
136
|
-
|
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
|
-
|
142
|
-
|
143
|
-
|
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
|
147
|
-
updateStatus("No
|
148
|
-
coordinateData = [];
|
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
|
-
|
155
|
-
if(
|
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
|
-
|
171
|
+
map.invalidateSize();
|
176
172
|
} else {
|
177
|
-
|
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
|
183
|
-
updateStatus(`Error loading map data: ${error.message}
|
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("
|
188
|
+
console.log("Layers created/updated.");
|
195
189
|
}
|
196
190
|
|
197
191
|
function createPinLayer() {
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
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
|
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) {
|
204
|
+
if (loc.order_url) {
|
205
|
+
marker.on('click', () => window.open(loc.order_url, '_blank'));
|
206
|
+
}
|
211
207
|
markers.push(marker);
|
212
|
-
} catch (e) {
|
208
|
+
} catch (e) {
|
209
|
+
console.error(`Error creating marker ${index}:`, e);
|
210
|
+
}
|
213
211
|
});
|
214
|
-
|
215
|
-
|
216
|
-
|
212
|
+
if (markers.length === 0) {
|
213
|
+
console.warn("No valid markers created.");
|
214
|
+
return;
|
215
|
+
}
|
217
216
|
if (isClusteringEnabled) {
|
218
|
-
pinLayer = L.markerClusterGroup();
|
217
|
+
pinLayer = L.markerClusterGroup();
|
219
218
|
pinLayer.addLayers(markers);
|
220
|
-
console.log("Marker cluster
|
219
|
+
console.log("Marker cluster populated.");
|
221
220
|
} else {
|
222
|
-
pinLayer = L.layerGroup(markers);
|
223
|
-
console.log("Simple layer group
|
221
|
+
pinLayer = L.layerGroup(markers);
|
222
|
+
console.log("Simple layer group populated.");
|
224
223
|
}
|
225
224
|
}
|
226
225
|
|
227
226
|
function createHeatmapLayer() {
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
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];
|
237
|
-
}
|
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);
|
242
|
-
console.log("Heatmap
|
243
|
-
} else {
|
244
|
-
|
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
|
-
|
255
|
-
|
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
|
-
|
261
|
-
|
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
|
267
|
+
console.warn("Cannot update heatmap.");
|
269
268
|
return;
|
270
269
|
}
|
271
|
-
console.log("Updating heatmap
|
270
|
+
console.log("Updating heatmap opts:", heatmapOptions);
|
272
271
|
try {
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
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
|
-
|
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
|
-
|
290
|
-
|
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
|
-
|
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...");
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
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();
|
329
|
+
updateViewToggleButtonText();
|
312
330
|
viewToggleButton.addEventListener('click', () => {
|
313
|
-
console.log("View toggle
|
331
|
+
console.log("View toggle clicked!");
|
314
332
|
currentView = (currentView === 'pins') ? 'heatmap' : 'pins';
|
315
|
-
showCurrentView();
|
316
|
-
updateViewToggleButtonText();
|
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
|
337
|
+
console.log("View toggle listener setup.");
|
321
338
|
}
|
322
339
|
|
323
340
|
function setupClusterToggleButton() {
|
324
|
-
updateClusterToggleButtonText();
|
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;
|
329
|
-
console.log("Cluster toggle
|
330
|
-
isClusteringEnabled = !isClusteringEnabled;
|
331
|
-
redrawPinLayer();
|
332
|
-
updateClusterToggleButtonText();
|
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
|
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("
|
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
|
355
|
-
heatmapOptions.radius =
|
356
|
-
radiusValueSpan.textContent =
|
357
|
-
updateHeatmap();
|
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
|
-
|
361
|
-
heatmapOptions.blur =
|
362
|
-
blurValueSpan.textContent =
|
363
|
-
updateHeatmap();
|
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
|
-
|
367
|
-
heatmapOptions.maxZoom =
|
368
|
-
maxZoomValueSpan.textContent =
|
369
|
-
updateHeatmap();
|
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
|
382
|
+
console.log("Heatmap control listeners setup.");
|
372
383
|
}
|
373
384
|
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
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) {
|
386
|
-
|
387
|
-
|
388
|
-
|
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)
|
399
|
-
|
400
|
-
|
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)
|
404
|
-
|
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
|
-
|
411
|
-
|
421
|
+
map.addLayer(layerToAdd);
|
422
|
+
console.log(`Added ${currentView} layer.`);
|
412
423
|
} catch (e) {
|
413
|
-
|
414
|
-
|
424
|
+
console.error(`Error adding ${currentView}:`, e);
|
425
|
+
updateStatus(`Error display ${currentView}.`, true);
|
415
426
|
}
|
416
427
|
} else {
|
417
|
-
|
418
|
-
|
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
|
428
|
-
viewToggleButton.textContent = `Switch to ${
|
429
|
-
console.log(`View
|
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
|
-
|
443
|
+
if (!clusterToggleButton) return;
|
434
444
|
clusterToggleButton.textContent = isClusteringEnabled ? 'Disable Clustering' : 'Enable Clustering';
|
435
|
-
console.log(`Cluster
|
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
|
-
|
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
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
51
|
-
|
52
|
-
<
|
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
|
-
|
59
|
-
|
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"
|
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
|
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|