-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
DH Code review #2
base: empty
Are you sure you want to change the base?
Conversation
* implement link icons (Princeton-CDH#248) * Add phosphor icons and styles for link indicators * Clean up links to work better with external link icons * Correct link icon size to use rem instead of em * add as twitter summary card * add as card for linkedin, mastodon, and facebook * Add twitter card and opengraph metadata to page header * Update develop version to 1.1.0 * Set develop version to indicate dev status * Revise logo order in footer; center footer logos on mobile * Add titles and affiliations for core team members on credits page * Set version to 1.0 (no code/functionality changes) --------- Co-authored-by: gissoo <44615389+gissoo@users.noreply.github.com>
* Revise Mark Peters title from Chief to Historian per Keely: they only use the title chief when they are acting as chiefs * Fix figure caption/attribution * Fix mis-ordered words * Add missing comma for Stockbridge, NY * Fix problem with attribution link in figure shortcode
* Update advocacy.md * Update nassau-hall.md * Update white-eyes.md * Update white-eyes.md * Update col-morgan.md * Update cultural-survival.md * Update ontario-munsee.md * Update b-calvin.md * Update dedication.md * Update _index.md * Update _index.md * Update about.md * Update credits.md * Update lunaapahkiing.md * Update concession.md * Update brotherton-res.md * Update bethel-mission.md * Update delaware-valley.md * Update equality.md * Update lenapewihittuck.md * Update stockbridge.md * Update chiefs.md * Update sand-hill.md * Update b-calvin.md * Update b-calvin.md * Update b-calvin.md * Update departure.md * Update murder.md * Update resources.md * Update and rename otterskin.md to beaverskin.md * Update beaverskin.md * Update william-potter-ross.md * Update library-collections.md * Update language-alliance.md * Update recruitment.md * Update recruitment.md * Update and rename space.md to affinity space.md * Update greenville-treaty.md * Update gnadenhutten-massacre.md * Update fallen-timbers.md * Update cherokee-purchase.md * Update brotherton-collapse.md * Update pontiac.md * Update complaints.md * Update storytelling.md * Update munsee-three-sisters-farm.md * Update munsee-names.md * Update wooley.md * Update nassau-hall.md * Rename cultural-survival.md to cultural-salvage.md * Update occupation.md * Update native-scholars.md * Fix minor typos * Convert US to more standardized U.S. * Fix broken figure tags --------- Co-authored-by: rlskoeser <rebecca.s.koeser@princeton.edu>
0425042
to
3ea0523
Compare
@ColeDCrawford here are the more d3-specific parts of the code that I think you should focus on. All of these are If you want to run locally and inspect the network, set |
@raffazizzi I think it would make sense for you to use |
I took a look on the overall structure of the app, and focused more carefully on I think the overall structure is clear, and likely keeps the JS to a bare minimum (which can be desirable!). Though, and I think I suggested this during the initial call, if this application where to be generalized to be re-used with other data in other projects, it may be useful looking at a framework to facilitate certain operations. For example, to better handle events (e.g. in A less important comment: I find the navigation at the root of the tree hard to identify as... navigation. The terms seemed like nodes relevant to the visualization and it took me a while to realize they're clickable. I would make that more apparent, e.g. with a mouseover highlight, or just by making them more typically recognizable as a navbar. |
}; | ||
|
||
// branch style color sequence; set class name and control with css | ||
function getBranchStyle(branchName) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this function should only return a string (currently it returns either a string or void). It may be safer to throw an error when branchSlug
is undefined
. If throwing an error is inconvenient, I would return an empty string and report on the console with either console.log()
or console.error()
)
const trunkNodeIndex = 0; // first node is the trunk | ||
|
||
// array of links between our nodes | ||
let links = new Array(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Elsewhere you used the more concise []
(e.g. in line 171)
// let currentBranchIndex; // = null; | ||
let currentBranchNodeCount = 0; | ||
let currentCentury; | ||
let previousBranchIndex = trunkNodeIndex; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to reset previousBranchIndex
to 0
at every new branch
-- does it need trunkNodeIndex
at all?
currentBranchNodeCount > 5 || | ||
currentCentury != leaf.century | ||
) { | ||
let branchId = `${branch}-century${leaf.century}-${index}`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unused?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, duplicate - thanks for catching
@@ -0,0 +1,197 @@ | |||
import { select, selectAll } from "d3-selection"; | |||
|
|||
import { Leaf } from "./leaves"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unused?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It sure is. Thanks
); | ||
} | ||
|
||
bindHandlers() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
generally, it's good practice to remove event listeners when they are no longer needed to avoid memory leaks and a progressive slow down of the application. A lot of the addEventListener
s here are attached to the container (the panel, I believe) so they're not too risky: they are attached when the page loads and are needed active until the user navigates away or refreshes the page.
I wonder if others could be a bit more leaky, however.
// bind handler to current tag x button to deactivate tag | ||
this.activeTagClose = document.querySelector("#current-tag .close"); | ||
if (this.activeTagClose) { | ||
this.activeTagClose.addEventListener("click", (event) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This may be an event worth turning off for tags that are no longer active. If a user clicks on the same tag after removing it, a new eventListener will be created and the previous one will still be in memory!
I think it should be possible to do so here, but you won't be able to use an anonymous function. Here is a guess (not tested):
if (this.activeTagClose) {
const handleTagDeselect = (event) => {
this.currentTag = null;
this.container.dispatchEvent(TagDeselectEvent);
this.activeTagClose.removeEventListener("click", handleTagDeselect)
};
this.activeTagClose.addEventListener("click", handleTagDeselect)
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had to look back at the template code - the active tag close button is always there, it's just not visible when there is no active tag. So this shouldn't ever create any new event listeners, and it's only bound once when the page is loaded. But I did notice I have a delegated click handler just above that and wonder if it would make sense to consolidate them....
import { line, curveNatural } from "d3-shape"; | ||
|
||
// combine into d3 object for convenience | ||
const d3 = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that's already the case, but I would limit d3 DOM operations to SVG nodes in the visualization.
let midY2 = -this.height / 2 + this.height * 0.66 - randomNumBetween(7); | ||
|
||
// should we adjust the upper width? | ||
if (cointoss()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is fun :)
|
||
// run simulation for 30 ticks without animation to set positions | ||
// (30 is enough with alpha decay of 0.2; need 300 with default alpha decay) | ||
simulation.tick(30); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have done this in another project as well where I wanted to bypass the animation. So I think this is the way to go. I had many more nodes so the loading time was longer, however, and without the animation it's important to give the user some feedback. It seems like this isn't a problem for the current size of the tree, but this is a place to keep an eye on if the data will grow.
branchToBranch: 3, // between branch century nodes | ||
}; | ||
|
||
class TimeTree extends TimeTreeKeysMixin(BaseSVG) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this the only class that uses the TimeTreeKeysMixin
?
// load & parse leaf data from json embedded in the document | ||
const data = JSON.parse(document.querySelector(".leaf-data").value); | ||
// load and parse tag list for label / slug lookup label based on slug | ||
const tags = JSON.parse(document.querySelector(".tag-data").value); | ||
// determine if this is a production or development build | ||
const params = JSON.parse(document.querySelector(".env-data").value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More of a question - interested why you chose to inject the data this way rather than through an embedded script or reading a CSV / JSON blob with D3. Had a similar problem come up recently and went back and forth on different approaches. The way it's rendered isn't very readable so that doesn't seem to be an advantage.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for flagging; I don't remember why I did it this way! I agree that using a script tag with type="application/json"
is better. Just figured out how to generate that with Hugo (had to figure out how to tell it not to escape my json 😀 ) and updated the parse calls.
|
||
drawTimeTree() { | ||
let width = this.getSVGWidth(); // width depends on if mobile or not | ||
let height = 800; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you're trying to reuse this in different contexts, maybe pull this out into config.
.attr("d", (d) => { | ||
return d.type == "leaf" ? new LeafPath().path : emptyPath; | ||
}) | ||
// for accessibility purposes, leaves are buttons |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general the keyboard / voiceover nav works really well. It took a little bit for me to understand why tabbing through leaves was not in visual order (because the data is in chronological order) but I don't think it's a major issue. Kind of fits with the aesthetic and purpose of the vis which is not necessarily to find specific data but to browse. Much more accessible than many D3 visualizations.
This project was soft-launched in the spring; production site is accessible at https://lenapetimetree.indigenous.princeton.edu/
The readme includes instructions on setting it up to run locally, if desired or helpful for review. Functionality included in the 1.0 release is documented in the change log.
This PR includes all of the JavaScript code (and only that). It's probably more than should be reviewed. Unit tests were added to tests specific parts of the code where it was helpful for development; my goal in this project was not complete coverage.
The code uses a d3.js force layout with custom forces to generate a tree structure with data points grouped into five ordered branches and roughly ordered by date. Interaction logic for selecting leaves in the tree, fetching and displaying contents, selecting tags and highlighting leaves is implemented with d3 and JavaScript (no other framework). The main entry point for the code is
timetree.js
.This is written with d3.js v7.8.4; javascript dependencies are installed with npm and imported into local js code; javascript is compiled and embedded using Hugo pipelines.
The JSON data used for the timetree visualization is embedded in the page, but the structure and content is viewable at this url: https://lenapetimetree.indigenous.princeton.edu/index.json
If you have questions about the Hugo template logic used to generate the JSON, or any other parts of the application that aren't included in this review, please let me know and I can point you to the relevant parts of the code.
Feedback I'm interested in at this point: