Skip to content

Commit

Permalink
Merge branch 'add-events-festival' into who-we-are
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-r-bigelow committed Mar 31, 2024
2 parents f667a4f + afca6cb commit 7e229b4
Show file tree
Hide file tree
Showing 10 changed files with 194 additions and 88 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
.quarto
_site
/.quarto/
.Rproj.user
**.Rhistory
**.Rproj
36 changes: 21 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand All @@ -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

Expand Down
4 changes: 3 additions & 1 deletion _quarto.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ project:
type: website

website:
title: 'ResBaz Arizona'
title: "ResBaz Arizona"
navbar:
background: dark
foreground: primary
Expand All @@ -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) \
Expand Down
134 changes: 72 additions & 62 deletions components/nodeLinkDiagram.ojs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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 });
});
Expand All @@ -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;
Expand All @@ -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);
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -145,15 +155,16 @@ nodeLinkDiagram = {
simulation.alphaTarget(0);
}

function * render (_selectNode, _selectedNode) {
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()
const linkEnter = link
.enter()
.append("line")
.attr("stroke", "#999")
.attr("stroke-opacity", 0.6)
Expand All @@ -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")
Expand Down Expand Up @@ -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)
Expand All @@ -254,4 +264,4 @@ nodeLinkDiagram = {
}

return render;
}
}
24 changes: 17 additions & 7 deletions components/randomAvatars.ojs
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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();
}
Loading

0 comments on commit 7e229b4

Please sign in to comment.