diff --git a/.gitignore b/.gitignore index 991361f..244ee38 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ .quarto _site /.quarto/ +.Rproj.user +**.Rhistory +**.Rproj \ No newline at end of file diff --git a/README.md b/README.md index fdc6afc..17bd0c6 100644 --- a/README.md +++ b/README.md @@ -10,21 +10,13 @@ ### 2\. Clone this Repository -(TODO: need to update this if/when the repo moves!) - ``` -git clone https://github.com/resbazaz/website-draft -# TODO: switch to this once the website is ready: -# git clone https://github.com/resbazaz/website.git +git clone https://github.com/resbazaz/website.git ``` -### 3\. TODO - -(probably need python, r, etc setup... might be optional?) - -### 4. Previewing changes +### 3. Previewing changes -This should build the website on your machine, serve it locally, and open a browser window to see any changes as you make them: +This should build the website on your machine, serve it locally. This should automatically open a browser window, showing any changes as you make them: ``` cd website @@ -39,11 +31,15 @@ TODO: basic git fork + commit + PR flow ### Editing page contents -TODO: where to find markdown +TODO: document qmd files, `_quarto.yml` + +### Updating page components + +TODO: link to quarto docs -### Updating page elements +We have a few custom elements, in the `components` directory (documentation about each should be in the corresponding file): -TODO: where to find HTML, how includes and \_data work +- [randomAvatars](https://github.com/resbazaz/website/blob/main/components/randomAvatars.ojs) ### Customizing styles @@ -53,7 +49,17 @@ TODO: Alpine, Tailwind stuff TODO: document `.github/workflows/build.yml` -Be advised that `localData/people.json` in your local respository is just an example file, that will be overwritten by the latest ResBazAZ teams on Github—so to update who is shown, and what teams are shown, you should edit the Github teams, not the JSON file. +### Data about people + +`data/people.json` is a collection of public GitHub profiles, for people in our organization. It is used to create various visual elements, such as the `randomAvatars` widget, or the `nodeLinkDiagram` visualization on the "Who We Are" page. + +This file is automatically generated whenever a branch is merged to `main`. For details on how this works, see [Changing how Github builds and deploys the site](#changing-how-github-builds-and-deploys-the-site). + +A few things to be careful about: + +1. To update who is shown, what teams are shown, etc., you should edit the `resbazaz` GitHub organization Teams and encourage members to updaite their GitHub profiles, instead of editing `data/people.json` directly. +2. The information in your local repository may not always be up to date. For the latest information, especially when you're developing locally, navigate to `https://researchbazaar.arizona.edu/data/people.json`, and download it to replace `data/people.json` +3. Although this is currently just a copy of publicly-available information, please keep in mind that it's still data about people! Especially if we adapt this process to include more than public GitHub profiles, where this data is stored / how it is collected may need to be revised. ### Reconfiguring the site's infrastructure diff --git a/_quarto.yml b/_quarto.yml index 8d2da68..66149a9 100644 --- a/_quarto.yml +++ b/_quarto.yml @@ -2,7 +2,7 @@ project: type: website website: - title: 'ResBaz Arizona' + title: "ResBaz Arizona" navbar: background: dark foreground: primary @@ -13,6 +13,8 @@ website: text: Events - href: whoWeAre.qmd text: Who We Are + - href: resbaz/resbazTucson2024.qmd + text: ResBaz 2024 body-footer: | ::: {.footer} ![ResBaz Logo](/img/logos/ResBazAZrectanglelogo-small.png) \ diff --git a/components/nodeLinkDiagram.ojs b/components/nodeLinkDiagram.ojs index d546d7e..5fdcd64 100644 --- a/components/nodeLinkDiagram.ojs +++ b/components/nodeLinkDiagram.ojs @@ -1,7 +1,8 @@ people = FileAttachment("data/people.json").json(); -nodeLinkDiagram = { - let selectNode, selectedNode = null; +function nodeLinkDiagram() { + let selectNode, + selectedNode = null; const height = globalThis.screen.height; @@ -20,27 +21,31 @@ nodeLinkDiagram = { const bounceStrength = 2; const chargeStrength = -2000; // -10 * (personNodeRadius + teamNodeRadius); - const weeklyTeams = new Set([ - 'coffee_and_code', - 'hacky_hour' - ]); + const weeklyTeams = new Set(["coffee_and_code", "hacky_hour"]); - const teamColors = d3.scaleOrdinal(['WEEKLY', 'FESTIVAL'], ['#ea5a2a', '#1e58ac']); + const teamColors = d3.scaleOrdinal( + ["WEEKLY", "FESTIVAL"], + ["#ea5a2a", "#1e58ac"] + ); const peopleById = Object.fromEntries( people.data.organization.membersWithRole.nodes.map((person) => [ person.id, { ...person, - type: "PERSON" - } + type: "PERSON", + }, ]) ); const nodes = Object.values(peopleById); const links = []; people.data.organization.teams.nodes.forEach((team) => { const teamId = team.name.toLowerCase().replace(/\s+/g, "_"); - nodes.push({ id: teamId, name: team.name, type: weeklyTeams.has(teamId) ? "WEEKLY" : "FESTIVAL" }); + nodes.push({ + id: teamId, + name: team.name, + type: weeklyTeams.has(teamId) ? "WEEKLY" : "FESTIVAL", + }); team.members.nodes.forEach((member) => { links.push({ source: member.id, target: teamId }); }); @@ -52,17 +57,18 @@ nodeLinkDiagram = { "link", d3.forceLink(links).id((d) => d.id) ) - .force( - "charge", - d3.forceManyBody().strength(chargeStrength) - ) + .force("charge", d3.forceManyBody().strength(chargeStrength)) .force("centerAndBounds", (alpha) => { - nodes.forEach(d => { - const radius = d.type === 'PERSON' ? personNodePaddedRadius : teamNodeRadius; + 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); + const gravityAlpha = Math.min( + (alpha * gravityMultiplier) ** 2, + maxGravityAlpha + ); if (d.x < radius) { d.x = radius; @@ -73,7 +79,7 @@ nodeLinkDiagram = { } 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); @@ -85,15 +91,19 @@ nodeLinkDiagram = { d.vy += Math.sign(dy) * gravityAlpha * dy ** 2; }); }) - .force('collide', d3.forceCollide((d) => d.type === 'PERSON' ? personNodePaddedRadius : teamNodeRadius)); + .force( + "collide", + d3.forceCollide((d) => + d.type === "PERSON" ? personNodePaddedRadius : teamNodeRadius + ) + ); - const svg = d3.create("svg") + 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); + svg.append("g").classed("links", true); + svg.append("g").classed("nodes", true); let draggedNode = null; let dragOffset = null; @@ -108,11 +118,11 @@ nodeLinkDiagram = { selectNode(draggedNode); const clickedPoint = { x: event.x - bounds.left, - y: event.y - bounds.top + y: event.y - bounds.top, }; dragOffset = { dx: clickedPoint.x - draggedNode.x, - dy: clickedPoint.y - draggedNode.y + dy: clickedPoint.y - draggedNode.y, }; // console.log('down', event, bounds, draggedNode, clickedPoint, dragOffset); draggedNode.fx = draggedNode.x; @@ -126,7 +136,7 @@ nodeLinkDiagram = { const bounds = svg.node().getBoundingClientRect(); const clickedPoint = { x: event.x - bounds.left, - y: event.y - bounds.top + y: event.y - bounds.top, }; // console.log('move', event, bounds, clickedPoint, dragOffset); draggedNode.fx = clickedPoint.x - dragOffset.dx; @@ -145,7 +155,7 @@ nodeLinkDiagram = { simulation.alphaTarget(0); } - function * render (_selectNode, _selectedNode) { + function* render(_selectNode, _selectedNode) { selectNode = _selectNode; selectedNode = _selectedNode; @@ -153,7 +163,8 @@ nodeLinkDiagram = { .select(".links") .selectAll("line") .data(links, (d) => `${d.from?.id}_${d.to?.id}`); - const linkEnter = link.enter() + const linkEnter = link + .enter() .append("line") .attr("stroke", "#999") .attr("stroke-opacity", 0.6) @@ -165,33 +176,36 @@ nodeLinkDiagram = { .select(".nodes") .selectAll("g.node") .data(nodes, (d) => d.id); - const nodeEnter = node.enter() - .append('g') - .classed('node', true); + const nodeEnter = node.enter().append("g").classed("node", true); node.exit().remove(); - node = node.merge(nodeEnter) + 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); - + .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'); + .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)); + .style("fill", (d) => teamColors(d.type)); nodeEnter .filter((d) => d.type === "PERSON") @@ -222,24 +236,20 @@ nodeLinkDiagram = { .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`; - }); + 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); + nodeEnter.append("title").text((d) => d.name || d.login); simulation.on("tick", () => { - node - .attr("transform", (d) => "translate(" + d.x + "," + d.y + ")"); + node.attr("transform", (d) => "translate(" + d.x + "," + d.y + ")"); link .attr("x1", (d) => d.source.x) @@ -254,4 +264,4 @@ nodeLinkDiagram = { } return render; -} \ No newline at end of file +} diff --git a/components/randomAvatars.ojs b/components/randomAvatars.ojs index c38b78f..e40344d 100644 --- a/components/randomAvatars.ojs +++ b/components/randomAvatars.ojs @@ -1,5 +1,14 @@ -peopleFile = FileAttachment('data/people.json').json(); +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 teamName A string that should correspond to the name of an entry in + * data/people.json, under data.organization.teams.nodes + * @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 @@ -11,13 +20,14 @@ function randomAvatars(teamName) { ) ) .sort(() => 2 * Math.random() - 1); - const container = d3.create('div') - .classed('randomAvatars', true); - container.selectAll('img') + const container = d3.create("div").classed("randomAvatars", true); + container + .selectAll("img") .data(people) .enter() - .append('img') - .attr('src', person => person.avatarUrl) - .attr('title', person => person.name); + .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/data/people.json b/data/people.json index 1023052..6464aaa 100644 --- a/data/people.json +++ b/data/people.json @@ -67,6 +67,10 @@ "members": { "nodes": [ { "id": "MDQ6VXNlcjc0MjYzMw==" }, + { "id": "MDQ6VXNlcjEyMTU4NzM=" }, + { "id": "MDQ6VXNlcjEyNjkxOTE4" }, + { "id": "MDQ6VXNlcjIxMTI5NjM5" }, + { "id": "MDQ6VXNlcjI1NDA0Nzgz" }, { "id": "U_kgDOBef8kg" } ] } @@ -89,7 +93,7 @@ "name": "Alex Bigelow", "login": "alex-r-bigelow", "bio": "I research how (and build software for!) people as they think about, reshape, and visualize data", - "company": "Stardog", + "company": null, "avatarUrl": "https://avatars.githubusercontent.com/u/1215873?u=9a4a9336866c4a348a0bb220a6e0490963bf32b5&v=4", "socialAccounts": { "edges": [ @@ -320,7 +324,7 @@ "name": "Eric R. Scott", "login": "Aariq", "bio": "Scientific Programmer & Educator at University of Arizona", - "company": "University of Arizona", + "company": "University of Arizona, @cct-datascience", "avatarUrl": "https://avatars.githubusercontent.com/u/25404783?u=bf39b8163e91fb40423676c1806a9fc1ed665c0c&v=4", "socialAccounts": { "edges": [ diff --git a/index.qmd b/index.qmd index e41f0b3..abe590f 100644 --- a/index.qmd +++ b/index.qmd @@ -83,6 +83,29 @@ randomAvatars("Hacky Hour"); ::::: +### ResBaz Festival + +::::: {.split-section} + +![ResBaz Festival](img/resBazAZ2024.png){.side-image .lightbox} \ + +::: {.split-chunk} +**Where**: Arizona + +**When**: Annually, for a few days in the spring (Apr/May) + +**What**: Learn, Network, Teach, Help, and Discuss are just a few of the availble things to do at ResBaz. Use the festival to add organizing events or presenting technical workshops to your resume, or simply attend and pick up a new data science trick, the choice is all yours! + +**Who you're likely to see**: + +```{ojs} +randomAvatars("Festival2024"); +``` + +::: + +::::: + ### Related Community Events #### Code Commons diff --git a/resbaz/resbazTucson2024.qmd b/resbaz/resbazTucson2024.qmd new file mode 100644 index 0000000..8526887 --- /dev/null +++ b/resbaz/resbazTucson2024.qmd @@ -0,0 +1,46 @@ +--- +layout: default +title: ResBaz Arizona +aliases: + - resbazArizona2024 +--- + +The Research Bazaar is a worldwide festival promoting the digital literacy emerging at the center of modern research. Throughout 2024, events are happening at university campuses around the globe. + +ResBaz Arizona is a free, multi-day intensive conference where researchers come together to up-skill in "next generation digital research tools and skills." In the spirit of a marketplace or bazaar, ResBaz is a highly participatory event where researchers from many different disciplines can learn, share knowledge and skills, and have fun! + +Who should attend? Researchers at ALL levels from ALL disciplines. PhD and research master's students are especially encouraged to attend. Research technologists who support researchers to do their job should also come along! + +[Register here!](https://forms.gle/u5naq46pn3YoKERd8) + +### How do I participate in ResBaz? + +At ResBaz, you choose your level of participation. + +All in? On the [event registration link](https://forms.gle/u5naq46pn3YoKERd8), you will find a choice to volunteer to be contacted to speak, help, or facilitate a session. + +Just starting out? Join us for one session, one day, or the entire conference, depending on your interests and availability. + +### What can I do at ResBaz? + +Learn, Network, Teach, Help, and Discuss are just a few of the availble things to do at ResBaz. Use the conference to add organizing events to your resume or simply attend and pick up a new data science trick, the choice is all yours! + +## Schedule + +*Calendar coming soon!* + +## Where to participate + +*Map(s) and Zoom links coming soon!* + +#### Accessibility + +We are committed to making this workshop accessible to everybody. For any accommodation requests, please contact us at [TODO: email]. We will do our best to make the event inclusive to all. + +## Previous years + +- [ResBaz Arizona 2023](/resbaz/Arizona2023.qmd) +- [ResBaz Tucson 2022](/resbaz/resbazTucson2022.qmd) +- [ResBaz Tucson 2021](/resbaz/resbazTucson2021.qmd) +- [ResBaz Tucson 2020](/resbaz/resbazTucson2020.qmd) +- [ResBaz Tucson 2019](/resbaz/resbazTucson2019.qmd) \ No newline at end of file diff --git a/styles/index.css b/styles/index.css index 2e190f5..7ddf0b1 100644 --- a/styles/index.css +++ b/styles/index.css @@ -29,6 +29,7 @@ } .randomAvatars { display: flex; + flex-wrap: wrap; gap: 1em; } .randomAvatars img { diff --git a/whoWeAre.qmd b/whoWeAre.qmd index d35283c..a88c31a 100644 --- a/whoWeAre.qmd +++ b/whoWeAre.qmd @@ -12,7 +12,8 @@ mutable selectedNode = null ::: { .column-screen-left } ```{ojs} -graph = nodeLinkDiagram((node) => { +render = nodeLinkDiagram(); +graph = render((node) => { mutable selectedNode = node; }, selectedNode); ```