{
const d3 = await require("d3@7")
const species_nodes = [
{ id: "bd", label: "B. dorsalis", type: "species", colour: "#2E7D9E" },
{ id: "cc", label: "C. capitata", type: "species", colour: "#C05C28" }
]
const theme_nodes = [
{ id: "gut", label: "Gut Microbiota", type: "shared", colour: "#7B5EA7" },
{ id: "gene", label: "Gene Drives", type: "shared", colour: "#7B5EA7" },
{ id: "eco", label: "Ecology & Behaviour", type: "shared", colour: "#7B5EA7" },
{ id: "pest", label: "Pest Control", type: "bd", colour: "#2E7D9E" },
{ id: "trans", label: "Transcriptomics", type: "bd", colour: "#2E7D9E" },
{ id: "det", label: "Detection & Biosensors", type: "bd", colour: "#2E7D9E" },
{ id: "sit", label: "SIT", type: "cc", colour: "#C05C28" },
{ id: "res", label: "Insecticide Resistance", type: "cc", colour: "#C05C28" },
{ id: "meth", label: "Methods & Technology", type: "cc", colour: "#C05C28" },
{ id: "nutr", label: "Nutrition & Reproduction", type: "cc", colour: "#C05C28" },
{ id: "ento", label: "Entomophagy", type: "cc", colour: "#C05C28" }
]
const edges = [
{ source: "bd", target: "gut", weight: 7, papers: [
{ title: "B. dorsalis and its gut microbiota: an emerging insect model (2026)", url: "https://doi.org/10.3390/insects17070662" },
{ title: "Gut microbiota and adaptation to temperature and diet stressors (2026)", url: "https://hal.science/hal-05774781v1/file/619332.pdf" },
{ title: "Excess sucrose alters gut microbiota and reduces body size and longevity (2026)", url: "https://link.springer.com/article/10.1007/s13744-026-01404-8" },
{ title: "BdNOX5 mediates gut microbiota homeostasis via ROS regulation (2026)", url: "https://scijournals.onlinelibrary.wiley.com/doi/10.1002/ps.70506" },
{ title: "Cuticle-associated microbiota suppress fungal infection (2026)", url: "https://scijournals.onlinelibrary.wiley.com/doi/10.1002/ps.70704" },
{ title: "Gut-microbiota reshaping by oxytetracycline residues (2026)", url: "https://www.sciencedirect.com/science/article/abs/pii/S0304389426017681" },
{ title: "Bacterial-driven odorant receptor expression shifts alter olfactory behaviour (2026)", url: "https://resjournals.onlinelibrary.wiley.com/doi/full/10.1111/een.70045" }
]},
{ source: "bd", target: "pest", weight: 8, papers: [
{ title: "Fusarium solani KMZW-1 toxicity against adult B. dorsalis (2026)", url: "https://doi.org/10.3389/fmicb.2026.1880895" },
{ title: "RNAi-mediated immune suppression enhances Serratia marcescens pathogenicity (2026)", url: "https://doi.org/10.1007/s10340" },
{ title: "Novel defensin-like antimicrobial peptides as promising targets for control (2026)", url: "https://scijournals.onlinelibrary.wiley.com/doi/10.1002/ps.70805" },
{ title: "Chitosan nanoparticle-based nucleic acid pesticide system (2026)", url: "https://doi.org/10.1007/s10340-025-01984-1" },
{ title: "Improving fungal biocontrol through binary strain combinations (2026)", url: "https://doi.org/10.1002/ps.71247" },
{ title: "Integration of insecticides with methyl eugenol-based MAT trap device (2026)", url: "https://doi.org/10.1155/aia/5346922" },
{ title: "Attractancy potential of terpinen-4-ol and synergistic attractant effects (2026)", url: "https://doi.org/10.1021/acs.jafc.6c01314" },
{ title: "Lure-and-infect strategy integrating attractants with Beauveria bassiana (2026)", url: "https://doi.org/10.1007/s10340-026-02054-w" }
]},
{ source: "bd", target: "gene", weight: 3, papers: [
{ title: "CRISPR/Cas9-based white pupae mutant lines in Bactrocera spp. for SIT (2026)", url: "https://doi.org/10.1111/1744-7917.70190" },
{ title: "CRISPR/Cas9-mediated genome editing in the management of B. dorsalis (2026)", url: "https://link.springer.com/protocol/10.1007/978-1-0716-4746-2_18" },
{ title: "Next-generation genetic biocontrol technologies for tephritid pests (2026)", url: "https://doi.org/10.1080/19336934.2026.2682509" }
]},
{ source: "bd", target: "eco", weight: 10, papers: [
{ title: "Flight characteristics of B. dorsalis associated with long-distance migration (2026)", url: "https://doi.org/10.3390/insects17020203" },
{ title: "Cadmium exposure triggers adaptive physiological responses undermining development (2026)", url: "https://doi.org/10.1016/j.ecoenv.2026.120524" },
{ title: "Asymmetric evolution of antennal cell types underlies ammonia-sensing logic (2026)", url: "https://doi.org/10.1002/advs.77168" },
{ title: "N-(3-methylbutyl)acetamide mediates male-male courtship (2026)", url: "https://doi.org/10.1073/pnas.2605503123" },
{ title: "Chemosensory and detoxification mechanisms in B. dorsalis (2026)", url: "https://doi.org/10.3390/insects17040416" },
{ title: "Enhanced female preference to oviposition cues by mating-induced odorant-binding proteins (2026)", url: "https://doi.org/10.1021/acs.jafc.5c12985" },
{ title: "Resource competition between Z. cucurbitae and B. dorsalis (2026)", url: "https://doi.org/10.3390/insects17090921" },
{ title: "B. dorsalis dominance and first report of three Dacus species in Uganda (2026)", url: "https://doi.org/10.1016/j.sciaf.2026.e03286" },
{ title: "Survey parameters to support detection survey design for B. dorsalis (2026)", url: "https://doi.org/10.2903/sp.efsa.2026.EN-10407" }
]},
{ source: "bd", target: "trans", weight: 1, papers: [
{ title: "METTL3/METTL14-mediated RNA m6A modification in male reproductive development (2026)", url: "https://doi.org/10.1111/1744-7917.13510" }
]},
{ source: "bd", target: "det", weight: 2, papers: [
{ title: "MOF-enhanced colorimetric sensor array for early detection of citrus infestation by B. dorsalis (2026)", url: "https://doi.org/10.1016/j.postharvbio.2026.114242" },
{ title: "Hollow ZIF-8-enhanced composite colorimetric sensor array for nondestructive detection (2026)", url: "https://doi.org/10.1016/j.postharvbio.2026.114760" }
]},
{ source: "cc", target: "gut", weight: 6, papers: [
{ title: "Full-length 16S rRNA gene sequencing reveals variable Medfly gut microbiome features (2026)", url: "https://academic.oup.com/ismecommun/advance-article/doi/10.1093/ismeco/ycag292/8898642" },
{ title: "Dominance of Zygosaccharomyces and shifts in bacterial pathways (2026)", url: "https://doi.org/10.1371/journal.pone.0335811" },
{ title: "Enterobacter supplement and ambient conditions determine sterile male performance (2026)", url: "https://resjournals.onlinelibrary.wiley.com/doi/full/10.1111/phen.70054" },
{ title: "C. capitata microbiota and its effect on environmental stress tolerance (2026)", url: "https://doi.org/10.1111/1744-7917.70010" },
{ title: "Enterobacter-based dietary supplements affect thermal response of sterile males (2026)", url: "https://www.cambridge.org/core/journals/bulletin-of-entomological-research/article/enterobacterbased-dietary-supplements-in-both-larvae-and-adult-diet-affect-thermal-response-of-sterile-males/A1E682C2632276FCC5F8EE472B13DB0B" },
{ title: "Antimicrobial feeding of parental adults reduces offspring tolerance to chemical stress (2026)", url: "https://doi.org/10.1111/aen.70117" }
]},
{ source: "cc", target: "gene", weight: 3, papers: [
{ title: "A novel polymerase III promoter for gene editing in C. capitata (2026)", url: "https://doi.org/10.64898/2026.04.21.719894" },
{ title: "Generating cisgenic sexing strains in insect pests (2026)", url: "https://www.nature.com/articles/s42003-026-09624-9" },
{ title: "DsiRNA-mediated silencing of transformer leads to masculinisation of XX embryos (2026)", url: "https://doi.org/10.1111/1744-7917.70173" }
]},
{ source: "cc", target: "sit", weight: 3, papers: [
{ title: "Probiotic modulation of gut microbiota enhances immunity in SIT sterile males (2026)", url: "https://doi.org/10.1007/s00248-026-02754-x" },
{ title: "Holobiont integrity in C. capitata enhances larval development: implications for SIT (2026)", url: "https://doi.org/10.1111/aen.70063" },
{ title: "Functional validation of a white pupae minimal gene construct in C. capitata (2026)", url: "https://doi.org/10.1111/1744-7917.70058" }
]},
{ source: "cc", target: "eco", weight: 12, papers: [
{ title: "Thermal acclimation and environment drive field dispersal patterns (2026)", url: "https://doi.org/10.1111/een.70147" },
{ title: "Analyzing C. capitata dispersal from South to Northward in Turkiye (2026)", url: "https://link.springer.com/article/10.1007/s12600-025-01350-y" },
{ title: "Urban-agricultural interface and occurrence of Mediterranean fruit fly (2026)", url: "https://link.springer.com/article/10.1007/s10340-025-01968-1" },
{ title: "Seasonal reinfestation of C. capitata in continental climates (2026)", url: "https://link.springer.com/article/10.1007/s10341-026-01825-0" },
{ title: "Novel approaches for the detection of low-density Mediterranean fruit fly populations (2026)", url: "https://link.springer.com/article/10.1007/s10530-026-03917-0" },
{ title: "Chemical profile of C. capitata from different agricultural Egyptian systems (2026)", url: "https://doi.org/10.21608/jpces.2026.510311.1016" },
{ title: "Biological and biochemical effects of olive mill wastewater on C. capitata (2026)", url: "https://doi.org/10.1134/S1062359025612935" },
{ title: "Promising nutritional values of Anethum graveolens as a bioinsecticidal agent (2026)", url: "https://doi.org/10.1002/ffj.70062" },
{ title: "Horizontal and vertical transmission of Beauveria bassiana (2026)", url: "https://doi.org/10.1111/eea.70144" },
{ title: "Sleep bout length and nighttime activity predict longevity in medflies (2026)", url: "https://doi.org/10.1038/s41514-026-00481-8" },
{ title: "Egg-stage desiccation reduces developmental recovery and reveals Wolbachia-associated costs (2026)", url: "https://doi.org/10.64898/2026.04.21.719827" },
{ title: "Impact of hydric stress during pupal development on adult morphology (2026)", url: "https://doi.org/10.1016/j.jinsphys.2026.104989" }
]},
{ source: "cc", target: "res", weight: 1, papers: [
{ title: "Reduced fitness cost accounts for stability of malathion-resistant haplotype in field populations (2026)", url: "https://doi.org/10.1002/ps.71269" }
]},
{ source: "cc", target: "meth", weight: 2, papers: [
{ title: "Rapid and non-destructive method to discriminate irradiated C. capitata using near-infrared spectroscopy (2026)", url: "https://doi.org/10.1093/jee/toae290" },
{ title: "Deep learning-based classification of embryonic development stages in C. capitata (2026)", url: "https://doi.org/10.1002/jez.b.70042" }
]},
{ source: "cc", target: "nutr", weight: 2, papers: [
{ title: "Effects of essential amino acids on phagostimulation and reproduction in C. capitata adults (2026)", url: "https://doi.org/10.1111/phen.70063" },
{ title: "Early-life nutrition shapes adult responses to heat and cold in C. capitata (2026)", url: "https://doi.org/10.1017/S0007485326000367" }
]},
{ source: "cc", target: "ento", weight: 1, papers: [
{ title: "From pest to food ingredient: quality of C. capitata larvae submitted to different methods of killing and drying (2026)", url: "https://doi.org/10.1016/j.ifset.2026.104493" }
]}
]
const W = width
const H = 540
const wrap = document.createElement("div")
const bar = document.createElement("div")
bar.style.cssText = "font-size:12px;color:#666;margin-bottom:8px;padding:7px 12px;" +
"background:#f5f5f5;border-radius:4px;border:1px solid #e0e0e0"
bar.innerHTML = "<strong>How to use:</strong> Hover to highlight connections · " +
"<strong>Click</strong> a node to see papers below · Click background to reset · Drag to rearrange"
wrap.appendChild(bar)
const svgEl = d3.create("svg")
.attr("viewBox", `0 0 ${W} ${H}`)
.attr("width", W)
.attr("height", H)
.style("font-family", "sans-serif")
.style("background", "#fafaf8")
.style("border-radius", "8px")
.style("border", "1px solid #ddd")
.style("display", "block")
wrap.appendChild(svgEl.node())
const panel = document.createElement("div")
panel.style.cssText = "margin-top:12px;border:1px solid #ddd;border-radius:8px;" +
"padding:16px 20px;background:#fff;font-family:sans-serif;font-size:13px;display:none"
wrap.appendChild(panel)
const nodes = [
...species_nodes.map(d => ({...d})),
...theme_nodes.map(d => ({...d}))
]
const links = edges.map(e => ({
...e,
source: nodes.find(n => n.id === e.source),
target: nodes.find(n => n.id === e.target)
}))
const maxWeight = d3.max(links, d => d.weight)
const sim = d3.forceSimulation(nodes)
.force("link", d3.forceLink(links).id(d => d.id).distance(d => d.target.type === "shared" ? 170 : 140).strength(0.7))
.force("charge", d3.forceManyBody().strength(-450))
.force("center", d3.forceCenter(W / 2, H / 2))
.force("collision", d3.forceCollide().radius(d => d.type === "species" ? 52 : 42))
const defs = svgEl.append("defs")
const flt = defs.append("filter").attr("id","shadow").attr("x","-20%").attr("y","-20%").attr("width","140%").attr("height","140%")
flt.append("feDropShadow").attr("dx",0).attr("dy",2).attr("stdDeviation",3).attr("flood-opacity",0.15)
const link = svgEl.append("g")
.selectAll("line").data(links).join("line")
.attr("stroke", d => d.target.colour)
.attr("stroke-opacity", 0.4)
.attr("stroke-width", d => 1.5 + (d.weight / maxWeight) * 7)
.attr("stroke-linecap", "round")
const node = svgEl.append("g")
.selectAll("g").data(nodes).join("g")
.style("cursor", "pointer")
.call(d3.drag()
.on("start", (event, d) => { if (!event.active) sim.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y })
.on("drag", (event, d) => { d.fx = event.x; d.fy = event.y })
.on("end", (event, d) => { if (!event.active) sim.alphaTarget(0); d.fx = null; d.fy = null })
)
node.append("circle")
.attr("r", d => d.type === "species" ? 42 : 32)
.attr("fill", d => d.colour)
.attr("fill-opacity", d => d.type === "species" ? 1 : 0.85)
.attr("stroke", "#fff").attr("stroke-width", 3)
.attr("filter", "url(#shadow)")
node.append("text")
.attr("text-anchor", "middle")
.attr("font-size", d => d.type === "species" ? "13px" : "10.5px")
.attr("font-weight", d => d.type === "species" ? "700" : "600")
.attr("fill", "#fff").attr("pointer-events", "none")
.each(function(d) {
const words = d.label.split(" ")
const el = d3.select(this)
if (words.length <= 2 || d.type === "species") {
el.attr("dy","0.35em").text(d.label)
} else {
const mid = Math.ceil(words.length / 2)
el.append("tspan").attr("x",0).attr("dy","-0.6em").text(words.slice(0,mid).join(" "))
el.append("tspan").attr("x",0).attr("dy","1.25em").text(words.slice(mid).join(" "))
}
})
node.filter(d => d.type !== "species").append("text")
.attr("text-anchor","middle").attr("dy","2.8em")
.attr("font-size","9px").attr("fill","rgba(255,255,255,0.75)").attr("pointer-events","none")
.text(d => {
const count = links.filter(l => l.target.id === d.id || l.source.id === d.id).reduce((s,l) => s + l.weight, 0)
return `${count} paper${count!==1?"s":""}`
})
node.on("mouseenter", function(event, d) {
const connected = new Set([d.id])
links.forEach(l => { if (l.source.id===d.id) connected.add(l.target.id); if (l.target.id===d.id) connected.add(l.source.id) })
node.selectAll("circle").attr("fill-opacity", n => connected.has(n.id) ? (n.type==="species"?1:0.95) : 0.2)
link.attr("stroke-opacity", l => connected.has(l.source.id) && connected.has(l.target.id) ? 0.8 : 0.04)
})
node.on("mouseleave", function() {
node.selectAll("circle").attr("fill-opacity", n => n.type==="species"?1:0.85)
link.attr("stroke-opacity", 0.4)
})
node.on("click", function(event, d) {
event.stopPropagation()
const relatedEdges = links.filter(l => l.source.id===d.id || l.target.id===d.id)
let html = ""
if (d.type === "species") {
html = `<div style="font-weight:700;font-size:15px;color:${d.colour};margin-bottom:10px"><em>${d.label}</em> — research themes</div>`
relatedEdges.forEach(l => {
html += `<span style="display:inline-block;background:${l.target.colour};color:#fff;border-radius:4px;padding:3px 10px;margin:3px;font-size:12px">${l.target.label} (${l.weight} papers)</span>`
})
} else {
const pbs = {}
relatedEdges.forEach(l => {
const key = l.source.id==="bd" ? "B. dorsalis" : "C. capitata"
const col = l.source.id==="bd" ? "#2E7D9E" : "#C05C28"
if (!pbs[key]) pbs[key] = { papers: l.papers, colour: col }
})
html = `<div style="font-weight:700;font-size:15px;color:${d.colour};margin-bottom:10px">${d.label}</div>`
Object.entries(pbs).forEach(([sp, {papers, colour}]) => {
html += `<div style="font-weight:600;font-size:12px;color:${colour};margin:8px 0 4px"><em>${sp}</em></div><ol style="margin:0;padding-left:18px">`
papers.forEach(p => {
html += `<li style="margin-bottom:5px">`
html += p.url ? `<a href="${p.url}" target="_blank" style="color:#2E7D9E;text-decoration:none;font-size:12px">${p.title}</a>`
: `<span style="font-size:12px;color:#555">${p.title}</span>`
html += `</li>`
})
html += `</ol>`
})
}
panel.style.display = "block"
panel.innerHTML = html
})
svgEl.on("click", function() {
node.selectAll("circle").attr("fill-opacity", n => n.type==="species"?1:0.85)
link.attr("stroke-opacity", 0.4)
panel.style.display = "none"
})
sim.on("tick", () => {
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("transform", d => `translate(${Math.max(45,Math.min(W-45,d.x))},${Math.max(45,Math.min(H-45,d.y))})`)
})
const lg = svgEl.append("g").attr("transform","translate(16,16)")
const ld = [
{ colour: "#2E7D9E", label: "B. dorsalis only" },
{ colour: "#C05C28", label: "C. capitata only" },
{ colour: "#7B5EA7", label: "Both species" }
]
ld.forEach((l,i) => {
lg.append("circle").attr("cx",8).attr("cy",i*22).attr("r",7).attr("fill",l.colour).attr("fill-opacity",0.9)
lg.append("text").attr("x",20).attr("y",i*22+4).attr("font-size","11px").attr("fill","#444").text(l.label)
})
lg.append("text").attr("x",0).attr("y",78).attr("font-size","10px").attr("fill","#999").text("Edge width = paper count")
yield wrap
}