diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0ca3cf7..ec3beaa 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,6 +33,7 @@ jobs: bio company avatarUrl + websiteUrl socialAccounts(first:10) { edges { node { diff --git a/_quarto.yml b/_quarto.yml index f827c21..66149a9 100644 --- a/_quarto.yml +++ b/_quarto.yml @@ -11,10 +11,10 @@ website: text: Home - href: index.qmd#events text: Events + - href: whoWeAre.qmd + text: Who We Are - href: resbaz/resbazTucson2024.qmd text: ResBaz 2024 - # - href: whoWeAre.qmd - # text: Who We Are body-footer: | ::: {.footer} ![ResBaz Logo](/img/logos/ResBazAZrectanglelogo-small.png) \ diff --git a/components/nodeLinkDiagram.ojs b/components/nodeLinkDiagram.ojs new file mode 100644 index 0000000..b431117 --- /dev/null +++ b/components/nodeLinkDiagram.ojs @@ -0,0 +1,237 @@ +function nodeLinkDiagram(nodes, links) { + let selectNode, + selectedNode = null; + + const height = globalThis.screen.height; + + const personNodeRadius = 25; + const personNodePaddedRadius = personNodeRadius + 15; + + const highlightOutlineRadius = 5; + const highlightStrokeWeight = 5; + + const teamNodeRadius = 75; + + const strokeWeight = 3; + + const gravityMultiplier = 0.3; + const maxGravityAlpha = 0.0005; + const bounceStrength = 2; + const chargeStrength = -2000; // -10 * (personNodeRadius + teamNodeRadius); + + const teamColors = d3.scaleOrdinal( + ["WEEKLY", "FESTIVAL"], + ["#ea5a2a", "#1e58ac"] + ); + + const simulation = d3 + .forceSimulation(nodes) + .force( + "link", + d3.forceLink(links).id((d) => d.id) + ) + .force("charge", d3.forceManyBody().strength(chargeStrength)) + .force("centerAndBounds", (alpha) => { + nodes.forEach((d) => { + const radius = + d.type === "PERSON" ? personNodePaddedRadius : teamNodeRadius; + // Kinda weird, but has a nice effect: apply gravity more strongly + // (within a limit) at the beginning of a layout / while you're + // dragging, but taper it off toward the end + const gravityAlpha = Math.min( + (alpha * gravityMultiplier) ** 2, + maxGravityAlpha + ); + + if (d.x < radius) { + d.x = radius; + d.vx += alpha * bounceStrength * (radius - d.x); + } else if (d.x > width - radius) { + d.x = width - radius; + d.vx += -alpha * bounceStrength * (d.x - width - radius); + } + const dx = width / 2 - d.x; + d.vx += Math.sign(dx) * gravityAlpha * dx ** 2; + + if (d.y < radius) { + d.y = radius; + d.vy += alpha * bounceStrength * (radius - d.y); + } else if (d.y > height - radius) { + d.y = height - radius; + d.vy += -alpha * bounceStrength * (d.y - height - radius); + } + const dy = height / 2 - d.y; + d.vy += Math.sign(dy) * gravityAlpha * dy ** 2; + }); + }) + .force( + "collide", + d3.forceCollide((d) => + d.type === "PERSON" ? personNodePaddedRadius : teamNodeRadius + ) + ); + + const svg = d3 + .create("svg") + .attr("viewBox", [0, 0, width, height]) + .style("user-select", "none"); + svg.append("g").classed("links", true); + svg.append("g").classed("nodes", true); + + let draggedNode = null; + let dragOffset = null; + + function mousedown(event, node) { + if (draggedNode) { + return; + } + const bounds = svg.node().getBoundingClientRect(); + simulation.alphaTarget(0.025).restart(); + draggedNode = node; + selectNode(draggedNode); + const clickedPoint = { + x: event.x - bounds.left, + y: event.y - bounds.top, + }; + dragOffset = { + dx: clickedPoint.x - draggedNode.x, + dy: clickedPoint.y - draggedNode.y, + }; + draggedNode.fx = draggedNode.x; + draggedNode.fy = draggedNode.y; + } + + function mousemove(event) { + if (!draggedNode) { + return; + } + const bounds = svg.node().getBoundingClientRect(); + const clickedPoint = { + x: event.x - bounds.left, + y: event.y - bounds.top, + }; + draggedNode.fx = clickedPoint.x - dragOffset.dx; + draggedNode.fy = clickedPoint.y - dragOffset.dy; + } + + function mouseup(event) { + if (!draggedNode) { + return; + } + draggedNode.fx = null; + draggedNode.fy = null; + draggedNode = null; + dragOffset = null; + simulation.alphaTarget(0); + } + + function* render(_selectNode, _selectedNode) { + selectNode = _selectNode; + selectedNode = _selectedNode; + + let link = svg + .select(".links") + .selectAll("line") + .data(links, (d) => `${d.from?.id}_${d.to?.id}`); + const linkEnter = link + .enter() + .append("line") + .attr("stroke", "#999") + .attr("stroke-opacity", 0.6) + .attr("stroke-width", strokeWeight); + link.exit().remove(); + link = link.merge(linkEnter); + + let node = svg + .select(".nodes") + .selectAll("g.node") + .data(nodes, (d) => d.id); + const nodeEnter = node.enter().append("g").classed("node", true); + node.exit().remove(); + node = node + .merge(nodeEnter) + // d3.drag() does weird things with quarto's minified version of d3, and + // isn't very retina display-friendly... so we manage interactions ourselves + .on("mousedown", mousedown); + d3.select(document).on("mousemove", mousemove).on("mouseup", mouseup); + + nodeEnter + .append("circle") + .classed("outline", true) + .attr( + "r", + (d) => + highlightOutlineRadius + + (d.type === "PERSON" ? personNodeRadius : teamNodeRadius) + ) + .style("fill", "none") + .style("stroke", "#333") + .style("stroke-width", highlightStrokeWeight); + node + .select(".outline") + .style("display", (d) => (d.id === selectedNode?.id ? null : "none")); + + nodeEnter + .filter((d) => d.type !== "PERSON") + .append("circle") + .attr("r", teamNodeRadius) + .style("fill", (d) => teamColors(d.type)); + + nodeEnter + .filter((d) => d.type === "PERSON") + .append("clipPath") + .attr("id", (d) => d.id) + .append("circle") + .attr("id", (d) => d.id) + .attr("r", personNodeRadius); + + nodeEnter + .filter((d) => d.type === "PERSON") + .append("image") + .attr("href", (d) => d.avatarUrl) + .attr("x", (d) => -personNodeRadius) + .attr("y", (d) => -personNodeRadius) + .attr("width", personNodeRadius * 2) + .attr("height", personNodeRadius * 2) + .attr("clip-path", (d) => `url(#${d.id})`) + .attr("preserveAspectRatio", "xMidYMin slice"); + + nodeEnter + .append("text") + .attr("class", "node_label") + .style("fill", (d) => (d.type === "PERSON" ? "black" : "white")) + .style("dominant-baseline", (d) => + d.type === "PERSON" ? "hanging" : "bottom" + ) + .style("text-anchor", "middle") + .style("font-size", "10pt") + .text((d) => d.name || d.login); + node.select("text").attr("y", (d) => { + if (d.type !== "PERSON") { + return "0.5em"; + } + if (d.id === selectedNode?.id) { + return `${personNodeRadius + 2 * highlightOutlineRadius}px`; + } + return `${personNodeRadius}px`; + }); + + nodeEnter.append("title").text((d) => d.name || d.login); + + simulation.on("tick", () => { + node.attr("transform", (d) => "translate(" + d.x + "," + d.y + ")"); + + 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); + }); + + invalidation.then(() => simulation.stop()); + + yield svg.node(); + } + + return render; +} diff --git a/components/randomAvatars.ojs b/components/randomAvatars.ojs index 8a18210..7b407cb 100644 --- a/components/randomAvatars.ojs +++ b/components/randomAvatars.ojs @@ -1,34 +1,29 @@ -peopleFile = FileAttachment("data/people.json").json(); - /** * An observable.js widget that shows profile pictures for ResBaz GitHub Team * members; see index.qmd for examples. Additionally, some relevant styles are * in styles/index.css * + * @param peopleData The results of combineAndOverrideGithubData() * @param teamName A string that should correspond to the name of an entry in - * data/people.json, under data.organization.teams.nodes + * data/people.json under data.organization.teams.nodes, + * or a key under teams in data/overrides.json, * @returns A DOM element that can be embedded via an ojs cell */ -function randomAvatars(teamName) { - const team = peopleFile.data.organization.teams.nodes.find( - (team) => team.name === teamName - ); - const people = team.members.nodes - .map(({ id }) => - peopleFile.data.organization.membersWithRole.nodes.find( - (person) => person.id === id - ) - ) +function randomAvatars(peopleData, teamId) { + const team = peopleData.teamsById[teamId]; + const people = team.members + .map((id) => peopleData.peopleById[id]) .sort(() => 2 * Math.random() - 1) .slice(0, 5); const container = d3.create("div").classed("randomAvatars", true); container - .selectAll("img") + .selectAll("a") .data(people) .enter() + .append("a") + .attr("href", (person) => `./whoWeAre.html#${person.hash}`) .append("img") .attr("src", (person) => person.avatarUrl) .attr("title", (person) => person.name); - // TODO: link to the Who We Are page, when a profile picture is clicked return container.node(); } diff --git a/components/utilities.ojs b/components/utilities.ojs new file mode 100644 index 0000000..a26a3c0 --- /dev/null +++ b/components/utilities.ojs @@ -0,0 +1,81 @@ +people = FileAttachment("data/people.json").json(); +overrides = FileAttachment("data/overrides.json").json(); + +function combineAndOverrideGithubData() { + const peopleById = { + // Include people manually added in overrides.json, that may not have a + // github login (or their info hasn't been queried yet by + // .github/workflows/build.yml, e.g. during local development) + ...overrides.people, + ...Object.fromEntries( + people.data.organization.membersWithRole.nodes.map((person) => [ + person.login, + { + ...person, + // Github stores people by an illegible hash; we want to select + + // override data using Github usernames as ids (but still use + // hashes for url navigation) + hash: person.id, + id: person.login, + type: "PERSON", + // Override github profile information with details in overrides.json + ...(overrides.people[person?.login] || {}), + }, + ]) + ), + }; + + const peopleByHash = Object.fromEntries( + Object.values(peopleById).map((person) => [person.hash, person]) + ); + + const teamsById = { + // Include this separately for manual "teams" that aren't on Github + ...overrides.teams, + ...Object.fromEntries( + people.data.organization.teams.nodes.map((team) => { + const teamId = team.name.toLowerCase().replace(/\s+/g, "_"); + return [ + teamId, + { + id: teamId, + name: team.name, + type: "FESTIVAL", // For non-festival "teams," add an override! + members: team.members.nodes.map(({ id }) => peopleByHash[id].login), + // Override any github teams with details in overrides.json + ...(overrides.teams[teamId] || {}), + }, + ]; + }) + ), + }; + Object.entries(teamsById).forEach(([teamId, team]) => { + // Some extra fields that we don't want to have to hand-code + // in overrides.json + if (!team.hash) { + team.id = teamId; + team.hash = teamId; + } + if (!team.type) { + team.type = "FESTIVAL"; + } + }); + + const nodes = [...Object.values(peopleById), ...Object.values(teamsById)]; + const links = []; + Object.values(teamsById).forEach((team) => { + team.members.forEach((member) => { + links.push({ source: member, target: team.id }); + }); + }); + + return { + peopleById, + peopleByHash, + teamsById, + graph: { + nodes, + links, + }, + }; +} diff --git a/data/overrides.json b/data/overrides.json new file mode 100644 index 0000000..f0346e0 --- /dev/null +++ b/data/overrides.json @@ -0,0 +1,18 @@ +{ + "teams": { + "festival2024": { + "name": "2024 Festival Organizers" + }, + "coffee_and_code": { + "type": "WEEKLY" + }, + "hacky_hour": { + "type": "WEEKLY" + } + }, + "people": { + "alex-r-bigelow": { + "bio": "I research how (and build software for!) people as they think about, reshape, and visualize data" + } + } +} diff --git a/data/people.json b/data/people.json index 6464aaa..74a463b 100644 --- a/data/people.json +++ b/data/people.json @@ -7,15 +7,33 @@ "name": "Website Team", "members": { "nodes": [ - { "id": "MDQ6VXNlcjc0MjYzMw==" }, - { "id": "MDQ6VXNlcjEyMTU4NzM=" }, - { "id": "MDQ6VXNlcjE5Mzk4MTM=" }, - { "id": "MDQ6VXNlcjcwODIwMjU=" }, - { "id": "MDQ6VXNlcjExMDIzMzE3" }, - { "id": "MDQ6VXNlcjEyMDIyMDc2" }, - { "id": "MDQ6VXNlcjIxMTI5NjM5" }, - { "id": "MDQ6VXNlcjM4OTYyMjQz" }, - { "id": "U_kgDOBef8kg" } + { + "id": "MDQ6VXNlcjc0MjYzMw==" + }, + { + "id": "MDQ6VXNlcjEyMTU4NzM=" + }, + { + "id": "MDQ6VXNlcjE5Mzk4MTM=" + }, + { + "id": "MDQ6VXNlcjcwODIwMjU=" + }, + { + "id": "MDQ6VXNlcjExMDIzMzE3" + }, + { + "id": "MDQ6VXNlcjEyMDIyMDc2" + }, + { + "id": "MDQ6VXNlcjIxMTI5NjM5" + }, + { + "id": "MDQ6VXNlcjM4OTYyMjQz" + }, + { + "id": "U_kgDOBef8kg" + } ] } }, @@ -23,11 +41,21 @@ "name": "Governance Task Force", "members": { "nodes": [ - { "id": "MDQ6VXNlcjc0MjYzMw==" }, - { "id": "MDQ6VXNlcjEyMTU4NzM=" }, - { "id": "MDQ6VXNlcjIxMTI5NjM5" }, - { "id": "MDQ6VXNlcjU2NzAyNjAw" }, - { "id": "U_kgDOBef8kg" } + { + "id": "MDQ6VXNlcjc0MjYzMw==" + }, + { + "id": "MDQ6VXNlcjEyMTU4NzM=" + }, + { + "id": "MDQ6VXNlcjIxMTI5NjM5" + }, + { + "id": "MDQ6VXNlcjU2NzAyNjAw" + }, + { + "id": "U_kgDOBef8kg" + } ] } }, @@ -35,20 +63,48 @@ "name": "Festival2024", "members": { "nodes": [ - { "id": "MDQ6VXNlcjc0MjYzMw==" }, - { "id": "MDQ6VXNlcjEyMTU4NzM=" }, - { "id": "MDQ6VXNlcjE5Mzk4MTM=" }, - { "id": "MDQ6VXNlcjM4ODMyNDE=" }, - { "id": "MDQ6VXNlcjEyNjkxOTE4" }, - { "id": "MDQ6VXNlcjE5ODIxMTcx" }, - { "id": "MDQ6VXNlcjIxMTI5NjM5" }, - { "id": "MDQ6VXNlcjI1NDA0Nzgz" }, - { "id": "MDQ6VXNlcjM4OTYyMjQz" }, - { "id": "MDQ6VXNlcjQ2NjEyOTMz" }, - { "id": "MDQ6VXNlcjY4NDAzMzQw" }, - { "id": "U_kgDOBef8kg" }, - { "id": "U_kgDOBewVSw" }, - { "id": "U_kgDOCR6ywQ" } + { + "id": "MDQ6VXNlcjc0MjYzMw==" + }, + { + "id": "MDQ6VXNlcjEyMTU4NzM=" + }, + { + "id": "MDQ6VXNlcjE5Mzk4MTM=" + }, + { + "id": "MDQ6VXNlcjM4ODMyNDE=" + }, + { + "id": "MDQ6VXNlcjEyNjkxOTE4" + }, + { + "id": "MDQ6VXNlcjE5ODIxMTcx" + }, + { + "id": "MDQ6VXNlcjIxMTI5NjM5" + }, + { + "id": "MDQ6VXNlcjI1NDA0Nzgz" + }, + { + "id": "MDQ6VXNlcjM4OTYyMjQz" + }, + { + "id": "MDQ6VXNlcjQ2NjEyOTMz" + }, + { + "id": "MDQ6VXNlcjY4NDAzMzQw" + }, + { + "id": "U_kgDOBef8kg" + }, + { + "id": "U_kgDOBewVSw" + }, + { + "id": "U_kgDOCR6ywQ" + } ] } }, @@ -56,9 +112,15 @@ "name": "Hacky Hour", "members": { "nodes": [ - { "id": "MDQ6VXNlcjc0MjYzMw==" }, - { "id": "MDQ6VXNlcjY5NDcwODk=" }, - { "id": "MDQ6VXNlcjEyNjkxOTE4" } + { + "id": "MDQ6VXNlcjc0MjYzMw==" + }, + { + "id": "MDQ6VXNlcjY5NDcwODk=" + }, + { + "id": "MDQ6VXNlcjEyNjkxOTE4" + } ] } }, @@ -66,12 +128,37 @@ "name": "Coffee and Code", "members": { "nodes": [ - { "id": "MDQ6VXNlcjc0MjYzMw==" }, - { "id": "MDQ6VXNlcjEyMTU4NzM=" }, - { "id": "MDQ6VXNlcjEyNjkxOTE4" }, - { "id": "MDQ6VXNlcjIxMTI5NjM5" }, - { "id": "MDQ6VXNlcjI1NDA0Nzgz" }, - { "id": "U_kgDOBef8kg" } + { + "id": "MDQ6VXNlcjc0MjYzMw==" + }, + { + "id": "MDQ6VXNlcjEyMTU4NzM=" + }, + { + "id": "MDQ6VXNlcjEyNjkxOTE4" + }, + { + "id": "MDQ6VXNlcjIxMTI5NjM5" + }, + { + "id": "MDQ6VXNlcjI1NDA0Nzgz" + }, + { + "id": "U_kgDOBef8kg" + } + ] + } + }, + { + "name": "GitHub Admins", + "members": { + "nodes": [ + { + "id": "MDQ6VXNlcjc0MjYzMw==" + }, + { + "id": "U_kgDOBef8kg" + } ] } } @@ -86,7 +173,10 @@ "bio": "Organic Software Gardener 🍅", "company": "Exosphere Project", "avatarUrl": "https://avatars.githubusercontent.com/u/742633?u=204cc5138bfb07ef7332d3fa1d885bbbbe9e7281&v=4", - "socialAccounts": { "edges": [] } + "websiteUrl": "https://twitter.com/jpistorius", + "socialAccounts": { + "edges": [] + } }, { "id": "MDQ6VXNlcjEyMTU4NzM=", @@ -95,6 +185,7 @@ "bio": "I research how (and build software for!) people as they think about, reshape, and visualize data", "company": null, "avatarUrl": "https://avatars.githubusercontent.com/u/1215873?u=9a4a9336866c4a348a0bb220a6e0490963bf32b5&v=4", + "websiteUrl": "https://alex-r-bigelow.github.io", "socialAccounts": { "edges": [ { @@ -113,6 +204,7 @@ "bio": "", "company": "University of Arizona", "avatarUrl": "https://avatars.githubusercontent.com/u/1939813?u=d39a4cac18c94ef60a298c2ad5fa82bb73fc30c3&v=4", + "websiteUrl": null, "socialAccounts": { "edges": [ { @@ -131,6 +223,7 @@ "bio": "Computing Sciences Researcher II at The University of Arizona.", "company": "The University of Arizona", "avatarUrl": "https://avatars.githubusercontent.com/u/3883241?u=bb818aa9153eb92f57ae7b878a39faed4988f025&v=4", + "websiteUrl": "https://soham.dev", "socialAccounts": { "edges": [ { @@ -149,15 +242,19 @@ "bio": "", "company": "Natural History Museum, University of Oslo", "avatarUrl": "https://avatars.githubusercontent.com/u/6617332?u=3836c3ffa014b36b188f4f45297f26461fb39a67&v=4", - "socialAccounts": { "edges": [] } + "websiteUrl": "https://sites.google.com/site/meghanbalk/", + "socialAccounts": { + "edges": [] + } }, { "id": "MDQ6VXNlcjY5NDcwODk=", "name": "Ryan Bartelme", "login": "rbartelme", - "bio": "Staff Data Scientist in biotechnology research and development", - "company": "Accelerate Diagnostics", + "bio": "Bioinformatician and Data Scientist in biotechnology research and development", + "company": null, "avatarUrl": "https://avatars.githubusercontent.com/u/6947089?u=9cc0c86dc9db92eb4358b6b9643b79db33b279f2&v=4", + "websiteUrl": "rbartelme.github.io", "socialAccounts": { "edges": [ { @@ -165,6 +262,12 @@ "provider": "TWITTER", "url": "https://twitter.com/MicrobialBart" } + }, + { + "node": { + "provider": "GENERIC", + "url": "https://bsky.app/profile/microbialbart.bsky.social" + } } ] } @@ -176,6 +279,7 @@ "bio": "-_-", "company": null, "avatarUrl": "https://avatars.githubusercontent.com/u/7082025?v=4", + "websiteUrl": null, "socialAccounts": { "edges": [ { @@ -194,6 +298,7 @@ "bio": "", "company": "University of Arizona", "avatarUrl": "https://avatars.githubusercontent.com/u/10335479?u=b9414a8cb122c29d6c8544baea85a84c6baab939&v=4", + "websiteUrl": null, "socialAccounts": { "edges": [ { @@ -212,7 +317,10 @@ "bio": "I'm a code curious biologist.", "company": "University of Alabama at Birmingham", "avatarUrl": "https://avatars.githubusercontent.com/u/11023317?u=7200ca4bee6441f2ef582f70df5fe39bd236e3ee&v=4", - "socialAccounts": { "edges": [] } + "websiteUrl": null, + "socialAccounts": { + "edges": [] + } }, { "id": "MDQ6VXNlcjExNTI3MDQx", @@ -221,6 +329,7 @@ "bio": "Director of Open Science & Associate Professor at the University of Arizona, Institute for Computation and Data Enabled Insight ", "company": "University of Arizona, CyVerse", "avatarUrl": "https://avatars.githubusercontent.com/u/11527041?u=2cefefd17d2dc2ec8243b7a1f53a620f1fde2ff4&v=4", + "websiteUrl": "tysonswetnam.com", "socialAccounts": { "edges": [ { @@ -245,6 +354,7 @@ "bio": "", "company": "University of Arizona, CCT Data Science", "avatarUrl": "https://avatars.githubusercontent.com/u/12022076?u=377f60f40b83a81b1e57ef8518406bc38ce6748f&v=4", + "websiteUrl": null, "socialAccounts": { "edges": [ { @@ -263,7 +373,10 @@ "bio": null, "company": null, "avatarUrl": "https://avatars.githubusercontent.com/u/12691918?u=a1c7d45e5755e99746ae358919406cc7f163b192&v=4", - "socialAccounts": { "edges": [] } + "websiteUrl": null, + "socialAccounts": { + "edges": [] + } }, { "id": "MDQ6VXNlcjE5ODIxMTcx", @@ -272,7 +385,10 @@ "bio": "PhD Student at the University of Arizona's School of Information", "company": null, "avatarUrl": "https://avatars.githubusercontent.com/u/19821171?u=3cca330b757c209c3c1547844a3ad81508304b58&v=4", - "socialAccounts": { "edges": [] } + "websiteUrl": "https://lwdozal.github.io/", + "socialAccounts": { + "edges": [] + } }, { "id": "MDQ6VXNlcjIwMzA1NzM0", @@ -281,6 +397,7 @@ "bio": "Staring at the universe's data in the dark.", "company": "Princeton Plasma Physics Lab; Steward Obs", "avatarUrl": "https://avatars.githubusercontent.com/u/20305734?u=84a111e22eef413d58e1370acea7cb12d075fc05&v=4", + "websiteUrl": "https://astrochun.github.io", "socialAccounts": { "edges": [ { @@ -297,9 +414,12 @@ "name": "Heidi Steiner", "login": "hidyverse", "bio": "", - "company": "University of Arizona", + "company": "@Boehringer-Ingelheim", "avatarUrl": "https://avatars.githubusercontent.com/u/21129639?u=f02dbb9a9ecfad325fa0ead8378f8d77f9150012&v=4", - "socialAccounts": { "edges": [] } + "websiteUrl": null, + "socialAccounts": { + "edges": [] + } }, { "id": "MDQ6VXNlcjIyMzgxNTM2", @@ -308,7 +428,10 @@ "bio": "", "company": null, "avatarUrl": "https://avatars.githubusercontent.com/u/22381536?u=5925008755e77dd5f884bd288ed6c0ff6f29a25d&v=4", - "socialAccounts": { "edges": [] } + "websiteUrl": null, + "socialAccounts": { + "edges": [] + } }, { "id": "MDQ6VXNlcjI1MjM1MjI4", @@ -317,7 +440,10 @@ "bio": "Librarian @ University of Arizona\r\nDirector, Catalyst Studios\r\n@resbazaz organizer\r\n", "company": null, "avatarUrl": "https://avatars.githubusercontent.com/u/25235228?u=84becdeac90a0ca8727cfcbfac7988ba13baca46&v=4", - "socialAccounts": { "edges": [] } + "websiteUrl": null, + "socialAccounts": { + "edges": [] + } }, { "id": "MDQ6VXNlcjI1NDA0Nzgz", @@ -326,6 +452,7 @@ "bio": "Scientific Programmer & Educator at University of Arizona", "company": "University of Arizona, @cct-datascience", "avatarUrl": "https://avatars.githubusercontent.com/u/25404783?u=bf39b8163e91fb40423676c1806a9fc1ed665c0c&v=4", + "websiteUrl": "www.ericrscott.com", "socialAccounts": { "edges": [ { @@ -356,7 +483,10 @@ "bio": "", "company": "University of Arizona's iSchool", "avatarUrl": "https://avatars.githubusercontent.com/u/34466941?u=c00f55a3718179e6d8e73acf75d18f3373a59da9&v=4", - "socialAccounts": { "edges": [] } + "websiteUrl": null, + "socialAccounts": { + "edges": [] + } }, { "id": "MDQ6VXNlcjM0NDg2MTU0", @@ -365,7 +495,10 @@ "bio": "Turkeys make excellent companions.", "company": null, "avatarUrl": "https://avatars.githubusercontent.com/u/34486154?v=4", - "socialAccounts": { "edges": [] } + "websiteUrl": "MagicMilly.github.io", + "socialAccounts": { + "edges": [] + } }, { "id": "MDQ6VXNlcjM2MTczNDkw", @@ -374,6 +507,7 @@ "bio": "Computational geneticist", "company": "NIH", "avatarUrl": "https://avatars.githubusercontent.com/u/36173490?v=4", + "websiteUrl": "https://www.linkedin.com/in/joanneberghout/", "socialAccounts": { "edges": [ { @@ -392,7 +526,10 @@ "bio": "Data Scientist | PhD, University of Arizona | R/RStudio Evangelist", "company": "University of Arizona", "avatarUrl": "https://avatars.githubusercontent.com/u/38962243?u=7ec452e5b8489ddbd966d22fdd5984332225c2ad&v=4", - "socialAccounts": { "edges": [] } + "websiteUrl": "https://gregtchism.com/", + "socialAccounts": { + "edges": [] + } }, { "id": "MDQ6VXNlcjQxNzA2OTE1", @@ -401,7 +538,10 @@ "bio": "", "company": null, "avatarUrl": "https://avatars.githubusercontent.com/u/41706915?u=f94237b4dc3896de97d3fd66872ac172fb638337&v=4", - "socialAccounts": { "edges": [] } + "websiteUrl": null, + "socialAccounts": { + "edges": [] + } }, { "id": "MDQ6VXNlcjQ2NjEyOTMz", @@ -410,7 +550,10 @@ "bio": "Astronomer, Data Scientist, Space Science & EO Enthusiast", "company": null, "avatarUrl": "https://avatars.githubusercontent.com/u/46612933?v=4", - "socialAccounts": { "edges": [] } + "websiteUrl": null, + "socialAccounts": { + "edges": [] + } }, { "id": "MDQ6VXNlcjQ3NzY2MDc0", @@ -419,7 +562,10 @@ "bio": "", "company": "GSA", "avatarUrl": "https://avatars.githubusercontent.com/u/47766074?u=6e52d4344ed85cd5c8b5e873ec28f1d5e74907e9&v=4", - "socialAccounts": { "edges": [] } + "websiteUrl": null, + "socialAccounts": { + "edges": [] + } }, { "id": "MDQ6VXNlcjU2NzAyNjAw", @@ -428,6 +574,7 @@ "bio": "", "company": "University of Arizona", "avatarUrl": "https://avatars.githubusercontent.com/u/56702600?u=03ece3573c51cb41e21197f7c2071d122d5e25a8&v=4", + "websiteUrl": "https://liaossanna.wixsite.com/liaos", "socialAccounts": { "edges": [ { @@ -446,7 +593,10 @@ "bio": null, "company": null, "avatarUrl": "https://avatars.githubusercontent.com/u/61329916?v=4", - "socialAccounts": { "edges": [] } + "websiteUrl": null, + "socialAccounts": { + "edges": [] + } }, { "id": "MDQ6VXNlcjY4NDAzMzQw", @@ -455,6 +605,7 @@ "bio": "PhD Student in Ecological and Environmental Informatics,\r\nSchool of Informatics, Computing, and Cyber Systems\r\n \r\n\r\n", "company": "Northern Arizona University", "avatarUrl": "https://avatars.githubusercontent.com/u/68403340?u=d3e846be891006b37a7aae3b74c7ecde9d0ab6fd&v=4", + "websiteUrl": null, "socialAccounts": { "edges": [ { @@ -473,7 +624,10 @@ "bio": null, "company": null, "avatarUrl": "https://avatars.githubusercontent.com/u/72940286?u=c0d118c3c830aa038eb2993753b42979431b3dd2&v=4", - "socialAccounts": { "edges": [] } + "websiteUrl": null, + "socialAccounts": { + "edges": [] + } }, { "id": "U_kgDOBdICzA", @@ -482,7 +636,10 @@ "bio": "", "company": null, "avatarUrl": "https://avatars.githubusercontent.com/u/97649356?v=4", - "socialAccounts": { "edges": [] } + "websiteUrl": null, + "socialAccounts": { + "edges": [] + } }, { "id": "U_kgDOBef8kg", @@ -491,7 +648,10 @@ "bio": "", "company": "University of Arizona", "avatarUrl": "https://avatars.githubusercontent.com/u/99089554?u=1441d55ea7637555aca31523963c26a68df6f57f&v=4", - "socialAccounts": { "edges": [] } + "websiteUrl": null, + "socialAccounts": { + "edges": [] + } }, { "id": "U_kgDOBewVSw", @@ -500,7 +660,10 @@ "bio": null, "company": null, "avatarUrl": "https://avatars.githubusercontent.com/u/99358027?v=4", - "socialAccounts": { "edges": [] } + "websiteUrl": null, + "socialAccounts": { + "edges": [] + } }, { "id": "U_kgDOCR6ywQ", @@ -508,8 +671,11 @@ "login": "ejahnUA", "bio": "", "company": "University of Arizona", - "avatarUrl": "https://avatars.githubusercontent.com/u/153006785?u=d14316c1567ab29135738cc82d41d37607b9963f&v=4", - "socialAccounts": { "edges": [] } + "avatarUrl": "https://avatars.githubusercontent.com/u/153006785?u=bafb3ce0c2ce02e9812c805a17950ab0e4445c83&v=4", + "websiteUrl": null, + "socialAccounts": { + "edges": [] + } } ] } diff --git a/data/toyPeople.json b/data/toyPeople.json new file mode 100644 index 0000000..4ada482 --- /dev/null +++ b/data/toyPeople.json @@ -0,0 +1,66 @@ +{ + "data": { + "organization": { + "teams": { + "nodes": [ + { + "name": "Hacky Hour", + "members": { + "nodes": [ + { "id": "cat" }, + { "id": "mouse" }, + { "id": "squirrel" } + ] + } + }, + { + "name": "Coffee and Code", + "members": { + "nodes": [{ "id": "dog" }, { "id": "cat" }] + } + } + ] + }, + "membersWithRole": { + "nodes": [ + { + "id": "dog", + "name": "Dog", + "login": "dog", + "bio": "The best dog ever", + "company": "Pets, Inc.", + "avatarUrl": "https://picsum.photos/id/237/200", + "socialAccounts": { "edges": [] } + }, + { + "id": "cat", + "name": "Cat", + "login": "cat", + "bio": "wut", + "company": "Pets, Inc.", + "avatarUrl": "https://picsum.photos/id/238/200", + "socialAccounts": { "edges": [] } + }, + { + "id": "mouse", + "name": "Mouse", + "login": "mouse", + "bio": "i heart cheese", + "company": "roDENTS", + "avatarUrl": "https://picsum.photos/id/239/200", + "socialAccounts": { "edges": [] } + }, + { + "id": "squirrel", + "name": "Squirrel", + "login": "squirrel", + "bio": "you'll never find my acorns", + "company": "roDENTS", + "avatarUrl": "https://picsum.photos/id/240/200", + "socialAccounts": { "edges": [] } + } + ] + } + } + } +} diff --git a/img/logos/facebook.svg b/img/logos/facebook.svg new file mode 100644 index 0000000..2dc423e --- /dev/null +++ b/img/logos/facebook.svg @@ -0,0 +1,44 @@ + + diff --git a/img/logos/github.svg b/img/logos/github.svg new file mode 100644 index 0000000..14eab4d --- /dev/null +++ b/img/logos/github.svg @@ -0,0 +1,57 @@ + + diff --git a/img/logos/instagram.svg b/img/logos/instagram.svg new file mode 100644 index 0000000..e042935 --- /dev/null +++ b/img/logos/instagram.svg @@ -0,0 +1,101 @@ + + diff --git a/img/logos/link.svg b/img/logos/link.svg new file mode 100644 index 0000000..c15b509 --- /dev/null +++ b/img/logos/link.svg @@ -0,0 +1,70 @@ + + diff --git a/img/logos/linkedin.svg b/img/logos/linkedin.svg new file mode 100644 index 0000000..8e991c1 --- /dev/null +++ b/img/logos/linkedin.svg @@ -0,0 +1,54 @@ + + diff --git a/img/logos/mastodon.svg b/img/logos/mastodon.svg new file mode 100644 index 0000000..fdab6fc --- /dev/null +++ b/img/logos/mastodon.svg @@ -0,0 +1,47 @@ + + diff --git a/img/logos/pinterest.svg b/img/logos/pinterest.svg new file mode 100644 index 0000000..c52eefe --- /dev/null +++ b/img/logos/pinterest.svg @@ -0,0 +1,45 @@ + + diff --git a/img/logos/reddit.svg b/img/logos/reddit.svg new file mode 100644 index 0000000..0afe591 --- /dev/null +++ b/img/logos/reddit.svg @@ -0,0 +1,49 @@ + + diff --git a/img/logos/twitter.svg b/img/logos/twitter.svg new file mode 100644 index 0000000..802de75 --- /dev/null +++ b/img/logos/twitter.svg @@ -0,0 +1,58 @@ + + diff --git a/index.qmd b/index.qmd index 16d25ae..740f40c 100644 --- a/index.qmd +++ b/index.qmd @@ -10,7 +10,10 @@ title-block-banner-color: white --- ```{ojs} +import { combineAndOverrideGithubData } from './components/utilities.ojs'; import { randomAvatars } from './components/randomAvatars.ojs'; + +peopleData = combineAndOverrideGithubData(); ``` @@ -52,7 +55,7 @@ _(Optionally) RSVP for our events at Meetup: [Our MeetUp](https://www.meetup.com **A few people you're likely to see**: ```{ojs} -randomAvatars("Coffee and Code"); +randomAvatars(peopleData, "coffee_and_code"); ``` ::: @@ -74,7 +77,7 @@ randomAvatars("Coffee and Code"); **A few people you're likely to see**: ```{ojs} -randomAvatars("Hacky Hour"); +randomAvatars(peopleData, "hacky_hour"); ``` ::: @@ -102,7 +105,7 @@ randomAvatars("Hacky Hour"); **A few people you're likely to see**: ```{ojs} -randomAvatars("Festival2024"); +randomAvatars(peopleData, "festival2024"); ``` ::: diff --git a/styles/whoWeAre.css b/styles/whoWeAre.css new file mode 100644 index 0000000..5e68464 --- /dev/null +++ b/styles/whoWeAre.css @@ -0,0 +1,8 @@ +.socialIcons { + display: flex; + gap: 0.5em; + margin: 0.5em 0; +} +.socialIcons a { + text-decoration: none !important; +} diff --git a/theme.scss b/theme.scss index a6f5754..e9d9c42 100644 --- a/theme.scss +++ b/theme.scss @@ -18,7 +18,7 @@ $gray-800: #333 !default; $gray-900: #212529 !default; $black: #000 !default; -$blue: #007bff !default; +$blue: #1e58ac !default; $indigo: #6610f2 !default; $purple: #772953 !default; $pink: #e83e8c !default; diff --git a/whoWeAre.qmd b/whoWeAre.qmd index 328fc18..197f383 100644 --- a/whoWeAre.qmd +++ b/whoWeAre.qmd @@ -1,5 +1,98 @@ --- -title: "Who We Are" +layout: default +css: styles/whoWeAre.css +execute: + echo: false --- -TODO \ No newline at end of file +::: { .column-screen-left } + +```{ojs} +// A lot of setup here, mostly so we can have a common state +// between nodeLinkDiagram and the sidebar +import { combineAndOverrideGithubData } from './components/utilities.ojs'; +import { nodeLinkDiagram } from './components/nodeLinkDiagram.ojs'; + +peopleData = combineAndOverrideGithubData(); + +mutable selectedNode = null + +// On load, and when the back button is pressed, select the node indicated by the URL (if there is one) +function handleHashChange () { + mutable selectedNode = window.location.hash + ? peopleData.graph.nodes.find(node => node.hash === window.location.hash.slice(1)) || null + : null +} + +// More ojs weirdness: need to wrap event assignments in a cell +// or `undefined` will show up in the page +dummy = { + window.addEventListener('hashchange', handleHashChange); + window.addEventListener('load', handleHashChange); +} + +render = nodeLinkDiagram(peopleData.graph.nodes, peopleData.graph.links); +diagram = render((node) => { + mutable selectedNode = node; + window.location.hash = node.hash; +}, selectedNode); +``` +::: + +::: { .column-margin } +```{ojs} +{ + if (selectedNode === null) { + return md` +This is a diagram of attendees, current and past organizers, and +their relationships to various ResBaz Arizona events. + +*Click a node for more information* + `; + } else if (selectedNode.type === 'PERSON') { + const header = selectedNode.name + ? md`# ${selectedNode.name}` + : (selectedNode.login + ? md`# ${selectedNode.login}` + : ''); + + const profilePic = selectedNode.avatarUrl ? html`` : ''; + + const company = selectedNode.company ? md`*${selectedNode.company}*` : ''; + + console.log(selectedNode); + + const extraSocialAccounts = selectedNode?.socialAccounts?.edges + ?.filter(edge => edge.node?.provider && edge.node?.url) + ?.map(edge => edge.node) || []; + const socialIconList = [ + ...(selectedNode?.websiteUrl ? [{ url: `https://github.com/${selectedNode.websiteUrl}`, provider: 'link' }] : []), + ...(selectedNode ? [{ url: `https://github.com/${selectedNode.login}`, provider: 'github' }] : []), + ...extraSocialAccounts, + ].map(account => html` + + `) || ''; + const socialIcons = html`
`; + + // TODO: selectedNode.login for github link / icon, selectedNode.socialAccounts list for others + + const bio = selectedNode.bio ? md`${selectedNode.bio}` : ''; + + return md` +${header} +${profilePic} +${socialIcons} +${company} +${bio} +`; + } + // TODO: sidebar for selectedNode.type === 'WEEKLY' + // TODO: sidebar for selectedNode.type === 'FESTIVAL' + return html`Error! Unknown node:
${ + JSON.stringify(selectedNode, null, 2) + }`; +} +``` +::: \ No newline at end of file