diff --git a/src/js/nfdimap.js b/src/js/nfdimap.js index 8a19121..6475704 100644 --- a/src/js/nfdimap.js +++ b/src/js/nfdimap.js @@ -1,68 +1,87 @@ Promise.all([ - d3.json("/data/nfdimap/germany.json"), + d3.json("/data/nfdimap/germany.json"), d3.buffer("/data/nfdimap/participantsM.xlsx") -]).then( ([germany, buffer]) => { +]).then(([germany, buffer]) => { - const tmp = XLSX.read(buffer, { type: 'array'}) + const tmp = XLSX.read(buffer, { type: 'array'}); return { - people: XLSX.utils.sheet_to_json(tmp.Sheets.People), - places: XLSX.utils.sheet_to_json(tmp.Sheets.Places), + people: XLSX.utils.sheet_to_json(tmp.Sheets.Peoples), + projects: XLSX.utils.sheet_to_json(tmp.Sheets.project), + places: XLSX.utils.sheet_to_json(tmp.Sheets.Places), germany: germany } -}).then( data => { +}).then(data => { - // determine how to show a person's name + // Utility: format a person's name with their role formatName = function(d) { - return d.Name + (d.Role ? ` (${d.Role})` : '') - } + return d.Name + (d.Role ? ` (${d.Role})` : ''); + }; - // utility: make a HTML list with a heading + // Utility: make an HTML list with a heading (for People and Projects) makeList = function(heading, entries) { - return `

${heading}

` + - '' - } + if (entries.length === 0) return ''; // Do not show if no entries + return `

${heading}

` + + ''; + }; - // determine the class attributes for a given place from its persons - placeClass = function(entries) { - const allRoles = String(entries.map(d => d.Role).join('')).toLowerCase(); + // Combine data for each institution (people + projects) + function combineInstitutionData(institution, people, projects) { + const peopleList = people.map(formatName); + const projectList = projects.map(p => p.Project); - // simple logic for now - if( allRoles.includes('spokesperson') ) - return 'applicant' + // Show the institution name as a header (h4), and then lists of people and projects + const combinedEntries = [ + `

${institution}

`, // Display institution name as a header + makeList('People', peopleList), + makeList('Projects', projectList) // Only show Projects if they exist + ]; - return 'normal' + return combinedEntries.join(''); + } + + // Function to determine the class attributes for a given place based on people + function placeClass(entries) { + const allRoles = String(entries.map(d => d.Role).join('')).toLowerCase(); + return allRoles.includes('spokesperson') ? 'applicant' : 'normal'; } - // pre-process places + // Pre-process places const projection = d3.geoConicConformal().center([20.22, 49.3]).scale(2750); - const placeIndex = d3.index(data.places, d => d.Name) - + const placeIndex = d3.index(data.places, d => d.Name); + const places = d3.groups( - data.people, - p => p.City, - ).map( ([place, people]) => ({ - name: place, - info: d3.groups( people, p => p.Institution ) - .map( ([inst, people]) => { - return makeList( inst, people.map(formatName) ) - }) - .join(''), - coord: (d => projection([d.Longitude, d.Latitude]))(placeIndex.get(place)), - type: placeClass(people) - })) - + data.people, + p => p.City, + ).map(([place, people]) => { + // Group projects by institution as well + const projectsByInstitution = d3.group(data.projects, p => p.Institution); + + // Group people by institution + const peopleByInstitution = d3.group(people, p => p.Institution); + + return { + name: place, + info: [...peopleByInstitution.entries()].map(([inst, people]) => { + const projects = projectsByInstitution.get(inst) || []; // Get projects for this institution, or empty array if none + return combineInstitutionData(inst, people, projects); // Combine people and projects per institution + }).join(''), + coord: (d => projection([d.Longitude, d.Latitude]))(placeIndex.get(place)), + type: placeClass(people) // determine class based on people roles + }; + }); + const path = d3.geoPath().projection(projection); - - // begin map drawing + + // Begin map drawing const svg = d3 .select("#dataplant-map") .append("svg") .attr("viewBox", [0, 0, 300, 400]); - // draw base map + // Draw base map svg .append("g") .selectAll("path") @@ -71,6 +90,7 @@ Promise.all([ .attr("d", path) .classed("map", true); + // Draw places const placesnodes = svg .append("g") .selectAll("circle") @@ -80,15 +100,17 @@ Promise.all([ .attr("cx", d => d.coord[0]) .attr("cy", d => d.coord[1]) .attr("r", () => 4) - .attr('data-tippy-content', d=> d.info) - .attr('data-place-type', d => d.type) + .attr('data-tippy-content', d => d.info) + .attr('data-place-type', d => d.type); + // Add tooltips using tippy.js tippy(placesnodes.nodes(), { appendTo: (reference) => document.querySelector('#dataplant-map'), interactive: true, maxWidth: 'none', allowHTML: true, theme: 'custom', - delay: [100, 1500] - }) -}) + delay: [100, 100] + }); +}); +