|
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
const searchButton = document.getElementById('searchButton'); |
|
|
const searchQuery = document.getElementById('searchQuery'); |
|
|
const databaseCheckboxes = document.querySelectorAll('input[name="database"]'); |
|
|
let timeRange, sortBy; |
|
|
|
|
|
function getSelectedFilters() { |
|
|
timeRange = document.querySelector('input[name="timeRange"]:checked').value; |
|
|
sortBy = document.querySelector('input[name="sortBy"]:checked').value; |
|
|
} |
|
|
const loadingSpinner = document.getElementById('loadingSpinner'); |
|
|
const resultsContainer = document.getElementById('resultsContainer'); |
|
|
const noResults = document.getElementById('noResults'); |
|
|
const resultCount = document.getElementById('resultCount'); |
|
|
const articleList = document.getElementById('articleList'); |
|
|
const constellation = d3.select('#constellation'); |
|
|
const tooltip = document.getElementById('tooltip'); |
|
|
|
|
|
|
|
|
searchButton.addEventListener('click', performSearch); |
|
|
searchQuery.addEventListener('keypress', function(e) { |
|
|
if (e.key === 'Enter') performSearch(); |
|
|
}); |
|
|
function performSearch() { |
|
|
const query = searchQuery.value.trim(); |
|
|
if (!query) return; |
|
|
|
|
|
getSelectedFilters(); |
|
|
showLoading(true); |
|
|
simulateAPICall(query); |
|
|
} |
|
|
function showLoading(show) { |
|
|
loadingSpinner.classList.toggle('hidden', !show); |
|
|
resultsContainer.classList.add('hidden'); |
|
|
noResults.classList.add('hidden'); |
|
|
} |
|
|
function simulateAPICall(query) { |
|
|
|
|
|
setTimeout(() => { |
|
|
try { |
|
|
|
|
|
const mockData = generateMockData(query); |
|
|
console.log("Generated mock data:", mockData); |
|
|
|
|
|
|
|
|
if (mockData.length > 0) { |
|
|
displayResults(mockData); |
|
|
} else { |
|
|
|
|
|
const fallbackData = generateMockData("médecine"); |
|
|
displayResults(fallbackData); |
|
|
} |
|
|
} catch (error) { |
|
|
console.error("Error displaying results:", error); |
|
|
|
|
|
const fallbackData = generateMockData("médecine"); |
|
|
displayResults(fallbackData); |
|
|
} finally { |
|
|
showLoading(false); |
|
|
} |
|
|
}, 500); |
|
|
} |
|
|
function generateMockData(query) { |
|
|
const yearsFilter = timeRange; |
|
|
const count = 15 + Math.floor(Math.random() * 20); |
|
|
const currentYear = new Date().getFullYear(); |
|
|
|
|
|
const articles = []; |
|
|
const centralArticleCitations = 200 + Math.floor(Math.random() * 300); |
|
|
|
|
|
|
|
|
articles.push({ |
|
|
title: `L'étude la plus influente sur ${query} ces dernières années`, |
|
|
authors: "Dupont, J.; Martin, A.; Leroy, K. et al.", |
|
|
journal: "Nature Medicine", |
|
|
year: currentYear - Math.floor(Math.random() * (yearsFilter === 'all' ? 20 : parseInt(yearsFilter))), |
|
|
citations: centralArticleCitations, |
|
|
doi: "10.1038/nm.1234", |
|
|
abstract: `Cette étude révolutionnaire a transformé notre compréhension de ${query} en démontrant des améliorations significatives des résultats pour les patients. L'équipe de recherche a employé des méthodologies innovantes qui sont depuis devenues standard dans le domaine.` |
|
|
}); |
|
|
|
|
|
|
|
|
for (let i = 0; i < count - 1; i++) { |
|
|
const citationCount = Math.floor(Math.random() * centralArticleCitations * 0.8); |
|
|
|
|
|
articles.push({ |
|
|
title: `Étude ${i+1} sur ${query}: ${['Nouvelle', 'Complète', 'Systématique', 'Randomisée', 'Clinique'][i%5]} ${['découverte', 'analyse', 'étude', 'revue', 'évaluation'][i%5]}`, |
|
|
authors: `${['Dubois', 'Bernard', 'Petit', 'Moreau', 'Lefevre'][i%5]}, ${String.fromCharCode(65 + (i%26))}. et al.`, |
|
|
journal: ["La Presse Médicale", "NEJM", "The Lancet", "BMJ", "Annales"][i%5], |
|
|
year: currentYear - Math.floor(Math.random() * (yearsFilter === 'all' ? 20 : parseInt(yearsFilter))), |
|
|
citations: citationCount, |
|
|
doi: `10.1234/abcd.${1000 + i}`, |
|
|
abstract: `Cette étude ${['importante', 'fondamentale', 'innovante', 'détaillée', 'approfondie'][i%5]} a examiné ${query} à travers l'analyse ${['clinique', 'épidémiologique', 'moléculaire', 'biochimique', 'génomique'][i%5]}. Les résultats ont ${['confirmé', 'remis en question', 'élargi', 'affiné', 'redéfini'][i%5]} les connaissances précédentes dans ce domaine.` |
|
|
}); |
|
|
} |
|
|
|
|
|
return articles; |
|
|
} |
|
|
|
|
|
function displayResults(articles) { |
|
|
|
|
|
articles.sort((a, b) => b.citations - a.citations); |
|
|
|
|
|
|
|
|
resultCount.textContent = `${articles.length} articles de recherche trouvés`; |
|
|
|
|
|
createConstellation(articles); |
|
|
|
|
|
|
|
|
renderArticleList(articles); |
|
|
|
|
|
|
|
|
resultsContainer.classList.remove('hidden'); |
|
|
} |
|
|
|
|
|
function createConstellation(articles) { |
|
|
const width = constellation.node().clientWidth; |
|
|
const height = constellation.node().clientHeight; |
|
|
const center = { x: width / 2, y: height / 2 }; |
|
|
|
|
|
|
|
|
constellation.selectAll("*").remove(); |
|
|
|
|
|
|
|
|
const simulation = d3.forceSimulation() |
|
|
.force("link", d3.forceLink().id(d => d.id).distance(100)) |
|
|
.force("charge", d3.forceManyBody().strength(-100)) |
|
|
.force("x", d3.forceX(center.x).strength(0.1)) |
|
|
.force("y", d3.forceY(center.y).strength(0.1)); |
|
|
|
|
|
|
|
|
const nodes = articles.map((article, i) => ({ |
|
|
id: i, |
|
|
...article, |
|
|
r: Math.sqrt(article.citations) / 3, |
|
|
isCentral: i === 0 |
|
|
})); |
|
|
|
|
|
|
|
|
const links = nodes.slice(1).map(node => ({ |
|
|
source: 0, |
|
|
target: node.id, |
|
|
value: node.citations / nodes[0].citations |
|
|
})); |
|
|
|
|
|
|
|
|
const link = constellation.append("g") |
|
|
.selectAll("line") |
|
|
.data(links) |
|
|
.enter().append("line") |
|
|
.attr("class", "link") |
|
|
.attr("stroke-width", d => Math.sqrt(d.value) * 2); |
|
|
|
|
|
|
|
|
const node = constellation.append("g") |
|
|
.selectAll("circle") |
|
|
.data(nodes) |
|
|
.enter().append("circle") |
|
|
.attr("class", d => `node ${d.isCentral ? 'central-node' : ''}`) |
|
|
.attr("r", d => d.r) |
|
|
.attr("fill", d => d.isCentral ? "#7c3aed" : "#4f46e5") |
|
|
.on("mouseover", showTooltip) |
|
|
.on("mouseout", hideTooltip) |
|
|
.call(d3.drag() |
|
|
.on("start", dragstarted) |
|
|
.on("drag", dragged) |
|
|
.on("end", dragended)); |
|
|
|
|
|
|
|
|
constellation.append("g") |
|
|
.selectAll("text") |
|
|
.data(nodes.filter(d => d.isCentral)) |
|
|
.enter().append("text") |
|
|
.attr("dy", 4) |
|
|
.attr("text-anchor", "middle") |
|
|
.style("fill", "white") |
|
|
.style("font-size", "10px") |
|
|
.style("font-weight", "bold") |
|
|
.text("★"); |
|
|
|
|
|
|
|
|
simulation.nodes(nodes).on("tick", ticked); |
|
|
simulation.force("link").links(links); |
|
|
|
|
|
function ticked() { |
|
|
link |
|
|
.attr("x1", d => d.source.x) |
|
|
.attr("y1", d => d.source.y) |
|
|
.attr("x2", d => d.target.x) |
|
|
.attr("y2", d => d.target.y); |
|
|
|
|
|
node |
|
|
.attr("cx", d => d.x) |
|
|
.attr("cy", d => d.y); |
|
|
} |
|
|
|
|
|
function dragstarted(event, d) { |
|
|
if (!event.active) simulation.alphaTarget(0.3).restart(); |
|
|
d.fx = d.x; |
|
|
d.fy = d.y; |
|
|
} |
|
|
|
|
|
function dragged(event, d) { |
|
|
d.fx = event.x; |
|
|
d.fy = event.y; |
|
|
} |
|
|
|
|
|
function dragended(event, d) { |
|
|
if (!event.active) simulation.alphaTarget(0); |
|
|
d.fx = null; |
|
|
d.fy = null; |
|
|
} |
|
|
} |
|
|
|
|
|
function showTooltip(event, d) { |
|
|
tooltip.innerHTML = ` |
|
|
<h4 class="font-bold text-purple-700 mb-1">${d.title}</h4> |
|
|
<p class="text-sm text-gray-600 mb-1">${d.authors}</p> |
|
|
<p class="text-sm mb-2">${d.journal}, ${d.year} (Cited ${d.citations} times)</p> |
|
|
<p class="text-xs text-gray-700 line-clamp-3">${d.abstract}</p> |
|
|
`; |
|
|
tooltip.style.left = `${event.pageX + 10}px`; |
|
|
tooltip.style.top = `${event.pageY + 10}px`; |
|
|
tooltip.classList.remove('hidden'); |
|
|
} |
|
|
|
|
|
function hideTooltip() { |
|
|
tooltip.classList.add('hidden'); |
|
|
} |
|
|
|
|
|
function renderArticleList(articles) { |
|
|
articleList.innerHTML = ''; |
|
|
|
|
|
articles.forEach((article, index) => { |
|
|
const articleElement = document.createElement('div'); |
|
|
articleElement.className = 'article-card bg-white rounded-lg shadow p-6'; |
|
|
articleElement.innerHTML = ` |
|
|
<div class="flex flex-col sm:flex-row gap-4"> |
|
|
<div class="flex-shrink-0 w-12 h-12 rounded-full flex items-center justify-center ${index === 0 ? 'bg-purple-100 text-purple-700' : 'bg-blue-100 text-blue-700'}"> |
|
|
<span class="font-bold">${index + 1}</span> |
|
|
</div> |
|
|
<div class="flex-grow"> |
|
|
<h3 class="font-bold text-lg mb-1">${article.title}</h3> |
|
|
<p class="text-sm text-gray-600 mb-1">${article.authors}</p> |
|
|
<p class="text-sm text-gray-700 mb-2">${article.journal}, ${article.year} · Cited ${article.citations} times</p> |
|
|
<p class="text-gray-700 text-sm line-clamp-2 mb-3">${article.abstract}</p> |
|
|
<div class="flex flex-wrap gap-2"> |
|
|
<a href="https://doi.org/${article.doi}" target="_blank" class="text-xs px-3 py-1 bg-gray-100 text-gray-700 rounded-full hover:bg-gray-200 transition-colors"> |
|
|
Voir la publication |
|
|
</a> |
|
|
${index === 0 ? ` |
|
|
<span class="text-xs px-3 py-1 bg-purple-100 text-purple-700 rounded-full"> |
|
|
Plus cité |
|
|
</span> |
|
|
` : ''} |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
`; |
|
|
articleList.appendChild(articleElement); |
|
|
}); |
|
|
} |
|
|
function showNoResults() { |
|
|
showLoading(false); |
|
|
resultsContainer.classList.add('hidden'); |
|
|
noResults.classList.remove('hidden'); |
|
|
|
|
|
|
|
|
const suggestions = generateSearchSuggestions("médecine"); |
|
|
|
|
|
noResults.innerHTML = ` |
|
|
<div class="text-center py-16"> |
|
|
<i data-feather="alert-circle" class="mx-auto text-gray-400 w-16 h-16 mb-6"></i> |
|
|
<h3 class="text-2xl font-bold text-gray-900 mb-3">Aucun résultat trouvé</h3> |
|
|
<p class="text-gray-600 text-lg">Essayez ces recherches alternatives :</p> |
|
|
<div class="mt-6 flex flex-wrap justify-center gap-2"> |
|
|
${suggestions.map(s => ` |
|
|
<button onclick="document.getElementById('searchQuery').value='${s}'; performSearch();" |
|
|
class="text-sm px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors"> |
|
|
${s} |
|
|
</button> |
|
|
`).join('')} |
|
|
</div> |
|
|
<button onclick="performSearch()" class="mt-8 px-6 py-3 bg-white text-gray-700 border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors font-medium"> |
|
|
Réessayer |
|
|
</button> |
|
|
</div> |
|
|
`; |
|
|
feather.replace(); |
|
|
} |
|
|
function generateSearchSuggestions(query) { |
|
|
const commonTerms = { |
|
|
'cancer': ['cancer du sein', 'cancer du poumon', 'traitement du cancer', 'thérapie ciblée cancer'], |
|
|
'diabète': ['diabète type 1', 'diabète type 2', 'traitement diabète', 'prévention diabète'], |
|
|
'covid': ['covid-19', 'vaccin covid', 'variants covid', 'traitement covid'] |
|
|
}; |
|
|
|
|
|
|
|
|
for (const [term, suggestions] of Object.entries(commonTerms)) { |
|
|
if (query.toLowerCase().includes(term)) { |
|
|
return suggestions; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return [ |
|
|
query + ' traitement', |
|
|
query + ' étude clinique', |
|
|
'nouveautés ' + query, |
|
|
'méta-analyse ' + query |
|
|
]; |
|
|
} |
|
|
}); |