Skip to content
This repository was archived by the owner on Jan 28, 2026. It is now read-only.

Commit 8c2b753

Browse files
authored
Merge pull request #159 from DigitalProductInnovationAndDevelopment/fix/WorldMapZoom&TrendGraphAPICall
Worldmap zoom and data consistent now trendgraph api updated
2 parents 67f7ebb + 43a314c commit 8c2b753

3 files changed

Lines changed: 264 additions & 78 deletions

File tree

frontend/src/components/shared/WorldMapPapers/WorldMapPapers.jsx

Lines changed: 196 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -213,20 +213,65 @@ const countryCentroids = {
213213
LI: [9.5554, 47.1660], // Liechtenstein
214214
};
215215

216-
const getCountryCentroid = (countryCode) => countryCentroids[countryCode] || [0, 0];
216+
const getCountryCentroid = (countryCode) => {
217+
if (!countryCode) return null;
218+
219+
// Normalize country code to uppercase
220+
const normalizedCode = countryCode.toUpperCase();
221+
222+
// Direct lookup
223+
if (countryCentroids[normalizedCode]) {
224+
return countryCentroids[normalizedCode];
225+
}
226+
227+
// Handle common variations
228+
const codeVariations = {
229+
'GERMANY': 'DE',
230+
'DEUTSCHLAND': 'DE',
231+
'UNITED STATES': 'US',
232+
'USA': 'US',
233+
'UNITED KINGDOM': 'GB',
234+
'UK': 'GB',
235+
'CHINA': 'CN',
236+
'JAPAN': 'JP',
237+
'FRANCE': 'FR',
238+
'ITALY': 'IT',
239+
'SPAIN': 'ES',
240+
'NETHERLANDS': 'NL',
241+
'SWEDEN': 'SE',
242+
'SWITZERLAND': 'CH',
243+
'CANADA': 'CA',
244+
'AUSTRALIA': 'AU'
245+
};
246+
247+
// Try variations
248+
if (codeVariations[normalizedCode]) {
249+
return countryCentroids[codeVariations[normalizedCode]];
250+
}
251+
252+
// Try partial matches
253+
for (const [variation, code] of Object.entries(codeVariations)) {
254+
if (normalizedCode.includes(variation) || variation.includes(normalizedCode)) {
255+
return countryCentroids[code];
256+
}
257+
}
258+
259+
return null;
260+
};
217261

218262
const OPENALEX_API_BASE = 'https://api.openalex.org';
219263

220264
const WorldMapPapers = ({ searchQuery, onPaperSelect, onApiCallsUpdate, triggerSearch = false, searchResults = null }) => {
221265
const [papers, setPapers] = useState([]);
222266
const [loading, setLoading] = useState(false);
223-
const [tooltipContent, setTooltipContent] = useState('');
224-
const [mapError, setMapError] = useState(false);
225267
const [fetchError, setFetchError] = useState(null);
268+
const [mapError, setMapError] = useState(false);
269+
const [tooltipContent, setTooltipContent] = useState(null);
270+
const [zoom, setZoom] = useState(1);
226271

227272
useEffect(() => {
228-
// If search results are provided from parent, use those
229273
if (searchResults && searchResults.length > 0) {
274+
// Process searchResults directly
230275
const mapped = searchResults.map((work, idx) => {
231276
// Try to get first author institution country and coordinates
232277
let country = null;
@@ -264,24 +309,84 @@ const WorldMapPapers = ({ searchQuery, onPaperSelect, onApiCallsUpdate, triggerS
264309
institution: institution || null
265310
};
266311
}).filter(Boolean);
267-
setPapers(mapped);
268-
setLoading(false);
269-
setFetchError(null);
270-
} else if (triggerSearch && searchQuery && searchQuery.trim().length > 0) {
271-
// Fallback to own API call if no results provided
272-
fetchPapersByQuery(searchQuery.trim());
273-
} else if (!triggerSearch) {
274-
// Reset to empty state when not searching
312+
313+
// Ensure all countries from leadership analysis have markers
314+
const countriesWithMarkers = new Set(mapped.map(p => p.country));
315+
const additionalMarkers = [];
316+
317+
// Get all unique countries from the search results
318+
const allCountriesInData = new Set();
319+
searchResults.forEach((work) => {
320+
let country = null;
321+
if (work.authorships && work.authorships.length > 0) {
322+
const firstAuth = work.authorships[0];
323+
if (firstAuth.institutions && firstAuth.institutions.length > 0) {
324+
const inst = firstAuth.institutions[0];
325+
country = inst.country_code || inst.country || null;
326+
}
327+
}
328+
if (!country && work.country_code) {
329+
country = work.country_code;
330+
}
331+
if (country) {
332+
allCountriesInData.add(country);
333+
}
334+
});
335+
336+
// Create markers for ALL countries that appear in the data
337+
allCountriesInData.forEach((country) => {
338+
const coordinates = getCountryCentroid(country);
339+
if (coordinates) {
340+
// If country doesn't have any markers yet, create one
341+
if (!countriesWithMarkers.has(country)) {
342+
additionalMarkers.push({
343+
id: `additional-${country}`,
344+
title: `Research papers from ${country}`,
345+
authors: [],
346+
citations: 0,
347+
country,
348+
coordinates,
349+
year: null,
350+
institution: null,
351+
isAdditional: true
352+
});
353+
countriesWithMarkers.add(country);
354+
}
355+
}
356+
});
357+
const allMarkers = [...mapped, ...additionalMarkers];
358+
setPapers(allMarkers);
359+
} else if (triggerSearch && searchQuery) {
360+
fetchPapersByQuery(searchQuery);
361+
} else {
275362
setPapers([]);
276-
setFetchError(null);
277-
setLoading(false);
278-
// Update API calls for disclaimer
279-
if (onApiCallsUpdate) {
280-
onApiCallsUpdate([]);
281-
}
282363
}
283-
// eslint-disable-next-line
284-
}, [triggerSearch, searchQuery, searchResults]);
364+
}, [searchResults, triggerSearch, searchQuery]);
365+
366+
// Add global event listeners to prevent zoom
367+
useEffect(() => {
368+
const handleWheel = (e) => {
369+
if (e.ctrlKey) {
370+
e.preventDefault(); // Prevent zoom on ctrl + wheel
371+
}
372+
};
373+
374+
const handleGesture = (e) => {
375+
e.preventDefault(); // Prevent pinch zoom on Mac trackpad
376+
};
377+
378+
window.addEventListener('wheel', handleWheel, { passive: false });
379+
window.addEventListener('gesturestart', handleGesture, { passive: false });
380+
window.addEventListener('gesturechange', handleGesture, { passive: false });
381+
window.addEventListener('gestureend', handleGesture, { passive: false });
382+
383+
return () => {
384+
window.removeEventListener('wheel', handleWheel);
385+
window.removeEventListener('gesturestart', handleGesture);
386+
window.removeEventListener('gesturechange', handleGesture);
387+
window.removeEventListener('gestureend', handleGesture);
388+
};
389+
}, []);
285390

286391
const fetchPapersByQuery = async (query) => {
287392
const trimmed = (query || '').trim();
@@ -301,7 +406,7 @@ const WorldMapPapers = ({ searchQuery, onPaperSelect, onApiCallsUpdate, triggerS
301406
const filter = `title_and_abstract.search:"${trimmed.replace(/"/g, '\\"')}"`;
302407
const params = new URLSearchParams({
303408
filter,
304-
per_page: 20
409+
per_page: 100
305410
});
306411
const apiUrl = `${OPENALEX_API_BASE}/works?${params.toString()}`;
307412

@@ -372,6 +477,7 @@ const WorldMapPapers = ({ searchQuery, onPaperSelect, onApiCallsUpdate, triggerS
372477

373478
// Helper to offset markers with the same coordinates
374479
function offsetMarkers(papers) {
480+
375481
// Group by coordinates as string
376482
const groups = {};
377483
papers.forEach((paper) => {
@@ -381,6 +487,7 @@ const WorldMapPapers = ({ searchQuery, onPaperSelect, onApiCallsUpdate, triggerS
381487
groups[key].push(paper);
382488
}
383489
});
490+
384491
// Offset each group
385492
const R = 2.5; // increased degrees offset radius
386493
const result = [];
@@ -399,6 +506,7 @@ const WorldMapPapers = ({ searchQuery, onPaperSelect, onApiCallsUpdate, triggerS
399506
});
400507
}
401508
});
509+
402510
return result;
403511
}
404512

@@ -447,6 +555,19 @@ const WorldMapPapers = ({ searchQuery, onPaperSelect, onApiCallsUpdate, triggerS
447555
setMapError(true);
448556
};
449557

558+
// Zoom control functions
559+
const handleZoomIn = () => {
560+
setZoom(prevZoom => Math.min(prevZoom + 0.5, 4));
561+
};
562+
563+
const handleZoomOut = () => {
564+
setZoom(prevZoom => Math.max(prevZoom - 0.5, 0.8));
565+
};
566+
567+
const handleZoomReset = () => {
568+
setZoom(1);
569+
};
570+
450571
return (
451572
<div className={styles.worldMapContainer}>
452573
<div className={styles.header}>
@@ -520,7 +641,34 @@ const WorldMapPapers = ({ searchQuery, onPaperSelect, onApiCallsUpdate, triggerS
520641
</button>
521642
</div>
522643
) : papers.length > 0 && (
523-
<div className={styles.mapContainer}>
644+
<div
645+
className={styles.mapContainer}
646+
>
647+
{/* Zoom Controls */}
648+
<div className={styles.zoomControls}>
649+
<button
650+
className={styles.zoomButton}
651+
onClick={handleZoomIn}
652+
title="Zoom In"
653+
>
654+
+
655+
</button>
656+
<button
657+
className={styles.zoomButton}
658+
onClick={handleZoomOut}
659+
title="Zoom Out"
660+
>
661+
662+
</button>
663+
<button
664+
className={styles.zoomButton}
665+
onClick={handleZoomReset}
666+
title="Reset Zoom"
667+
>
668+
669+
</button>
670+
</div>
671+
524672
<ComposableMap
525673
projection="geoEqualEarth"
526674
projectionConfig={{
@@ -534,9 +682,11 @@ const WorldMapPapers = ({ searchQuery, onPaperSelect, onApiCallsUpdate, triggerS
534682
>
535683
<ZoomableGroup
536684
center={[0, 0]}
537-
zoom={1}
685+
zoom={zoom}
538686
maxZoom={4}
539687
minZoom={0.8}
688+
disablePanning={false}
689+
disableZooming={true}
540690
>
541691
<Geographies
542692
geography="https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/world.geojson"
@@ -559,23 +709,29 @@ const WorldMapPapers = ({ searchQuery, onPaperSelect, onApiCallsUpdate, triggerS
559709
));
560710
}}
561711
</Geographies>
562-
{offsetMarkers(papers.slice(0, 20)).map((paper) => (
563-
<Marker
564-
key={paper.id}
565-
coordinates={paper.coordinates}
566-
onClick={() => handleMarkerClick(paper)}
567-
onMouseEnter={e => handleMarkerMouseEnter(paper, e)}
568-
onMouseLeave={handleMarkerMouseLeave}
569-
>
570-
<circle
571-
r={getMarkerSize(paper.citations)}
572-
fill={getMarkerColor(paper.citations)}
573-
stroke="#fff"
574-
strokeWidth={2}
575-
className={styles.marker}
576-
/>
577-
</Marker>
578-
))}
712+
{(() => {
713+
const markersToRender = offsetMarkers(papers.slice(0, 100));
714+
715+
return markersToRender.map((paper) => {
716+
return (
717+
<Marker
718+
key={paper.id}
719+
coordinates={paper.coordinates}
720+
onClick={() => handleMarkerClick(paper)}
721+
onMouseEnter={e => handleMarkerMouseEnter(paper, e)}
722+
onMouseLeave={handleMarkerMouseLeave}
723+
>
724+
<circle
725+
r={getMarkerSize(paper.citations)}
726+
fill={getMarkerColor(paper.citations)}
727+
stroke="#fff"
728+
strokeWidth={2}
729+
className={styles.marker}
730+
/>
731+
</Marker>
732+
);
733+
});
734+
})()}
579735
</ZoomableGroup>
580736
</ComposableMap>
581737
</div>

0 commit comments

Comments
 (0)