edit marker
This commit is contained in:
parent
4418783441
commit
3a889bd25f
|
@ -255,8 +255,6 @@
|
||||||
|
|
||||||
<uc-upload-ctx-provider
|
<uc-upload-ctx-provider
|
||||||
ctx-name="my-uploader"
|
ctx-name="my-uploader"
|
||||||
bind:this={uploaderCtxEl}
|
|
||||||
bind:ctx={uploaderCtx}
|
|
||||||
></uc-upload-ctx-provider>
|
></uc-upload-ctx-provider>
|
||||||
|
|
||||||
{#if showImageError}
|
{#if showImageError}
|
||||||
|
|
|
@ -15,40 +15,46 @@
|
||||||
lat: number;
|
lat: number;
|
||||||
lng: number;
|
lng: number;
|
||||||
};
|
};
|
||||||
|
startDate: string;
|
||||||
|
endDate: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getPastTripLocations(): Promise<TripLocation[]> {
|
function formatDate(dateStr: string): string {
|
||||||
|
const date = new Date(dateStr);
|
||||||
|
return `${date.getDate().toString().padStart(2, '0')}.${(date.getMonth() + 1)
|
||||||
|
.toString()
|
||||||
|
.padStart(2, '0')}.${date.getFullYear()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getPastTripLocations(): Promise<Record<string, TripLocation[]>> {
|
||||||
try {
|
try {
|
||||||
const tripsRef = ref(db, 'trips');
|
const tripsRef = ref(db, 'trips');
|
||||||
const snapshot = await get(tripsRef);
|
const snapshot = await get(tripsRef);
|
||||||
|
if (!snapshot.exists()) return {};
|
||||||
|
|
||||||
if (!snapshot.exists()) return [];
|
|
||||||
|
|
||||||
// Get today's date at midnight for comparison
|
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
today.setHours(0, 0, 0, 0);
|
today.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
// Create a Set to store unique locations
|
const locationMap: Record<string, TripLocation[]> = {};
|
||||||
const uniqueLocations = new Map<string, TripLocation>();
|
|
||||||
|
|
||||||
// Filter past trips and extract unique destinations
|
|
||||||
Object.values(snapshot.val()).forEach((trip: any) => {
|
Object.values(snapshot.val()).forEach((trip: any) => {
|
||||||
const endDate = new Date(trip.endDate);
|
const endDate = new Date(trip.endDate);
|
||||||
if (endDate < today && trip.destination?.location) {
|
if (endDate < today && trip.destination?.location) {
|
||||||
const locationKey = `${trip.destination.location.lat},${trip.destination.location.lng}`;
|
const key = `${trip.destination.location.lat},${trip.destination.location.lng}`;
|
||||||
if (!uniqueLocations.has(locationKey)) {
|
if (!locationMap[key]) locationMap[key] = [];
|
||||||
uniqueLocations.set(locationKey, {
|
locationMap[key].push({
|
||||||
name: trip.destination.name,
|
name: trip.destination.name,
|
||||||
location: trip.destination.location
|
location: trip.destination.location,
|
||||||
|
startDate: trip.startDate,
|
||||||
|
endDate: trip.endDate
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return Array.from(uniqueLocations.values());
|
return locationMap;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching past trips:', error);
|
console.error('Error fetching past trips:', error);
|
||||||
return [];
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +69,6 @@
|
||||||
const width = mapContainer.clientWidth;
|
const width = mapContainer.clientWidth;
|
||||||
const height = mapContainer.clientHeight;
|
const height = mapContainer.clientHeight;
|
||||||
|
|
||||||
// Create SVG
|
|
||||||
const svg = d3.select(mapContainer)
|
const svg = d3.select(mapContainer)
|
||||||
.append('svg')
|
.append('svg')
|
||||||
.attr('width', '100%')
|
.attr('width', '100%')
|
||||||
|
@ -71,30 +76,24 @@
|
||||||
.attr('viewBox', `0 0 ${width} ${height}`)
|
.attr('viewBox', `0 0 ${width} ${height}`)
|
||||||
.attr('preserveAspectRatio', 'xMidYMid meet') as d3.Selection<SVGSVGElement, unknown, null, undefined>;
|
.attr('preserveAspectRatio', 'xMidYMid meet') as d3.Selection<SVGSVGElement, unknown, null, undefined>;
|
||||||
|
|
||||||
// Add a group for all map elements that will be transformed
|
|
||||||
const g = svg.append('g');
|
const g = svg.append('g');
|
||||||
|
|
||||||
// Create projection
|
|
||||||
const projection = d3.geoMercator()
|
const projection = d3.geoMercator()
|
||||||
.scale(width / (2 * Math.PI))
|
.scale(width / (2 * Math.PI))
|
||||||
.translate([width / 2, height / 1.6]); // position the map, horizontally centered but is slighty upward
|
.translate([width / 2, height / 1.6]);
|
||||||
|
|
||||||
const path = d3.geoPath().projection(projection);
|
const path = d3.geoPath().projection(projection);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get past trip locations
|
const locationMap = await getPastTripLocations();
|
||||||
const pastLocations = await getPastTripLocations();
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
||||||
// Load world map data
|
|
||||||
const response = await fetch('https://unpkg.com/world-atlas@2/countries-110m.json');
|
const response = await fetch('https://unpkg.com/world-atlas@2/countries-110m.json');
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
const world = await response.json();
|
const world = await response.json();
|
||||||
|
|
||||||
// Convert TopoJSON to GeoJSON
|
|
||||||
const countries = feature(world, world.objects.countries) as any;
|
const countries = feature(world, world.objects.countries) as any;
|
||||||
|
|
||||||
// Draw the map
|
|
||||||
g.append('g')
|
g.append('g')
|
||||||
.selectAll('path')
|
.selectAll('path')
|
||||||
.data(countries.features)
|
.data(countries.features)
|
||||||
|
@ -106,7 +105,8 @@
|
||||||
.attr('stroke', Colors.gray.light50)
|
.attr('stroke', Colors.gray.light50)
|
||||||
.attr('stroke-width', '0.5');
|
.attr('stroke-width', '0.5');
|
||||||
|
|
||||||
// Add markers for past trip locations
|
const pastLocations = Object.values(locationMap).map(trips => trips[0]);
|
||||||
|
|
||||||
g.selectAll('circle')
|
g.selectAll('circle')
|
||||||
.data(pastLocations)
|
.data(pastLocations)
|
||||||
.enter()
|
.enter()
|
||||||
|
@ -116,8 +116,69 @@
|
||||||
.attr('r', 5)
|
.attr('r', 5)
|
||||||
.attr('class', 'marker')
|
.attr('class', 'marker')
|
||||||
.attr('fill', Colors.planner.med400)
|
.attr('fill', Colors.planner.med400)
|
||||||
|
.on('click', function (event, d) {
|
||||||
|
d3.selectAll('.trip-label').remove();
|
||||||
|
d3.selectAll('.trip-marker').remove();
|
||||||
|
d3.selectAll('.trip-line').remove();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
const key = `${d.location.lat},${d.location.lng}`;
|
||||||
|
const trips = locationMap[key];
|
||||||
|
|
||||||
|
if (trips && trips.length > 0) {
|
||||||
|
const baseX = projection([d.location.lng, d.location.lat])![0];
|
||||||
|
const baseY = projection([d.location.lng, d.location.lat])![1];
|
||||||
|
|
||||||
|
trips.sort((a, b) => new Date(a.endDate).getTime() - new Date(b.endDate).getTime());
|
||||||
|
|
||||||
|
if (trips.length > 1) {
|
||||||
|
g.append('line')
|
||||||
|
.attr('x1', baseX)
|
||||||
|
.attr('y1', baseY + 5)
|
||||||
|
.attr('x2', baseX)
|
||||||
|
.attr('y2', baseY - (trips.length - 1) * 24 + 6)
|
||||||
|
.attr('stroke', Colors.planner.med400)
|
||||||
|
.attr('stroke-width', 2)
|
||||||
|
.attr('class', 'trip-line');
|
||||||
|
}
|
||||||
|
|
||||||
|
trips.forEach((trip, idx) => {
|
||||||
|
const offsetY = idx * 24;
|
||||||
|
|
||||||
|
const markerGroup = g.append('g')
|
||||||
|
.attr('class', 'trip-group')
|
||||||
|
.on('mouseover', function () {
|
||||||
|
d3.select(this).select('circle').transition().duration(200).attr('r', 7);
|
||||||
|
d3.select(this).select('text').transition().duration(200).attr('fill', Colors.planner.med400);
|
||||||
|
})
|
||||||
|
.on('mouseout', function () {
|
||||||
|
d3.select(this).select('circle').transition().duration(200).attr('r', 5);
|
||||||
|
d3.select(this).select('text').transition().duration(200).attr('fill', Colors.black);
|
||||||
|
});
|
||||||
|
|
||||||
|
markerGroup.append('circle')
|
||||||
|
.attr('cx', baseX)
|
||||||
|
.attr('cy', baseY - offsetY)
|
||||||
|
.attr('r', 5)
|
||||||
|
.attr('fill', Colors.planner.med400)
|
||||||
|
.attr('class', 'trip-marker');
|
||||||
|
|
||||||
|
markerGroup.append('text')
|
||||||
|
.attr('x', baseX + 10)
|
||||||
|
.attr('y', baseY - offsetY + 4)
|
||||||
|
.attr('font-size', '12px')
|
||||||
|
.attr('class', 'trip-label')
|
||||||
|
.text(`${formatDate(trip.startDate)} - ${formatDate(trip.endDate)}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
svg.on('click', () => {
|
||||||
|
d3.selectAll('.trip-label').remove();
|
||||||
|
d3.selectAll('.trip-marker').remove();
|
||||||
|
d3.selectAll('.trip-line').remove();
|
||||||
|
});
|
||||||
|
|
||||||
// Add zoom behavior
|
|
||||||
const zoom = d3.zoom<SVGSVGElement, unknown>()
|
const zoom = d3.zoom<SVGSVGElement, unknown>()
|
||||||
.scaleExtent([1, 8])
|
.scaleExtent([1, 8])
|
||||||
.on('zoom', (event) => {
|
.on('zoom', (event) => {
|
||||||
|
@ -168,10 +229,17 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.marker:hover) {
|
:global(.marker:hover) {
|
||||||
r: 8;
|
r: 7;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:global(.trip-label),
|
||||||
|
:global(.trip-marker),
|
||||||
|
:global(.trip-line) {
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
|
||||||
:global(.dark .map-wrapper) {
|
:global(.dark .map-wrapper) {
|
||||||
background-color: var(--black);
|
background-color: var(--black);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user