Let's learn more about our institution while learning more about javascript and interactive information visualization (a.k.a. "infovis" or "dataviz").
- Fork this repository
- Clone your fork
Note: this not about using 3p code in your own app (final project)
- come to have the code (e.g. clone repo, as in preceding section)
- open a terminal in that directory (i.e. the one that has the
package.jsonfile), you will know this worked because you will see thepackage.jsonfile when you runlsordir - run
npm i(ornpm installto install the dependencies listed in thepackage.jsonfile) - you're ready to roll, so in the case of this project which is built with vite, you could now do like
npm run dev
- Go to the URL listed in the output of the previous step (e.g. http://localhost:5173/ )
- What do you see?
- Review the
index.htmlandmain.jsfiles to see what they're doing- Notice that
main.js:- loads the
d3library: https://d3js.org/what-is-d3 - fetches some json data from
data/data_sankey.json- take a look at this file:
- what javascript data structures are used in this file?
- what general data structure (for those of you have taken Data Structures) might best represent the data that is in this file?
- take a look at this file:
- uses the
d3-sankeylibrary to create a sankey diagram
- loads the
- Notice that
We will make 4 Sankey diagrams (more details follow) using data from JMU which you can find in the data directory.
- Inspect the
data/jmu.jsonfile- What javascript structure(s) is(are) used in this file?
- how does the structure of the data in this file compare to the structure of the data in
data/data_sankey.json?
- Create the new sankey diagram per specs below.
- When working conducting data analysis, including infovis, it is often necessary to wrangle the data into a format that is more amenable to the task at hand.
- often this just means in to a format expected by our own existing code, our (or third-party, a.k.a. "3p") libraries, tools, apps, etc.
- In this case, we will need to wrangle the data in
data/jmu.jsoninto a format that is expected by thed3-sankeylibrary. - In
main.jsedit the code where the current json data is loaded to instead load the correct data file, and before proceeding to the next parts of the code, construct data in the format expected by thed3-sankeylibrary. This is outlined further in the Recommended Process below.
Working in groups of 4 or fewer members, each group member should work on at least 1 diagram from those that follow. **Note: it is ok if you cannot coordinate with 3 others to have 1 diagram/member, but you/your group will be responsible for all 3 regardless of how many members you have. The diagrams are as follows:
- leftmost node: JMU Student
- second-to-leftmost nodes: Fall, Spring
- rightmost nodes: the
student itemizedcosts from thestudent-costs
- leftmost node: Auxiliary Comprehensive Fee (or "Comprehensive Fee")
- rightmost nodes: the
Auxiliary Comprehensive Fee Componentcosts from thestudent-costs
- leftmost nodes: JMU (positive) Revenue items (all items with
categoryofincome) - second-to-leftmost nodes: JMU Revenue Categories (e.g. operating revenues, non-operating revenues, etc.), the set of unique
typevalues from the items in the preceding column - center node: JMU
- second-to-rightmost nodes: JMU Expense (negative revenue) Categories (e.g. operating expenses), the set of unique
typevalues from the items in the following column - rightmost nodes: JMU Expense items (e.g. Instruction, Research, etc.) (all items with the
categoryofexpense)
- leftmost nodes: football, men's basketball, women's basketball, other sports, non-program specific
- second-to-leftmost nodes: JMU Athletics (positive) Revenue items (e.g. Ticket sales, etc.)
- center node: JMU Athletics
- second-to-rightmost nodes: JMU Athletics Expense categories (negative revenue) (e.g. Athletic student aid, etc.)
- rightmost nodes: football, men's basketball, women's basketball, other sports, non-program specific
For each of the diagrams listed above, the goal is to end up with a Sankey diagram that shows visualizes of JMU's financial data, rather than the contrived data. While it can be interested to dive into d3 and the d3-sankey library, I recommend something simpler for now.
You could try (reading the d3-sankey docs, or source code or) experimenting with removing certain attributed from the starting data file to see how the different properties affect the diagram, but to save a little time, let me tell you the following:
- the data in the example file is a single object with 2 keys:
nodesandlinks - the
nodeskey is an array of objects, each with:- a
namekey that should be unique among all nodes - a
titlekey which is currently used in the diagram to label the node
- a
- the
linkskey is an array of objects, each with:- a
sourcekey that specifies that this link begins at thenodewithnameequal to the value of the thislink'ssourcekey - a
targetkey that specifies that this link begins at thenodewithnameequal to the value of the thislink'stargetkey - a
valuekey that specifies the "weight" (i.e. width, value) of the link
- a
On a whiteboard, paper (napkin?!), or a chalkboard if you must 😏, sketch what you understand from the specs about your diagram
- How many columns of nodes does the spec for your diagram define?
- draw a column of 2 nodes for each of the columns in the spec
- What does the spec say about what the nodes in each column will be?
- write a label for the title of each of your nodes in each of your columns. Base the label on the spec and the dataset.
- draw a link between at least 1 node int eh first column and at least 1 node in the second column
- based on the preceding steps and the spec, what do you think the value of this link should be? add that value to your sketch
- Find the place in
main.jswhere the data is loaded.- it reads
const data = await d3.json("data/data_sankey.json");
- it reads
- Duplicate this line, immediately after the original line, in the second (the duplicate) line, change the variable name (but none of the subsequent references in the file) to something else, e.g.
jmuDataand change the file it's loading to be the one with the JMU data. - Comment out the first one
- Immediately after the new line fetching your data, declare a variable names
dataand set it to the result of a function that you will write that will transform the data from the JMU data file into the format expected by thed3-sankeylibrary. You might name this functionforDiagram3(if you are working on diagram 3)- e.g.
const data = forDiagram3(jmuData);
- e.g.
- Declare the function
forDiagram3that will transform the data from the JMU data file into the format expected by thed3-sankeylibrary. This function should:- return an object with
nodesandlinkskeys, each of which should have an array of objects as their value. - The
nodesarray should have an object for each node in the diagram - The
linksarray should have an object for each link in the diagram. - The
nodesobjects should have at leastnameandtitlekeys - The
linksobjects should have at leastsource,target, andvaluekeys.
- return an object with
- since the tasks above are so many, decompose the problem further, and where necessary, do a little work to simplify the next functions' work. consider:
function forDiagram3(jmuData) { // this is the data for all the diagrams, but this funciton is only about some of that data... const relevantData = // FIXME: how can you assign only the values from the releveant key of jmuData here? const nodes = getNodes(jmuData); const links = getLinks(jmuData); return { nodes, links }; }
- Declare the functions
getNodesandgetLinksthat will transform the data from the JMU data file into the format expected by thed3-sankeylibrary. These functions should:- return an array of objects
- The objects should have at least
nameandtitlekeys forgetNodesandsource,target, andvaluekeys forgetLinks.
- as the specs say different ways about how to get the nodes for different columns, you might want to further decompose this problem by having a function for each column of nodes, e.g.
getNodesCol1(or instead ofColyou could use the semantic meaning of that column),getNodesCol2, etc.- this reduces the responsibilities of the
getNodesfunction to just calling these other functions and combining their results
- this reduces the responsibilities of the
- at this point of problem decomposition, you have reduced the responsibility of (e.g.) getNodesCol1 to returning a list of nodes for only the first column, all of the nodes for a column are defined as being created using the same pieces of the dataset
- depending on how many columns you have, there may be different rules for constructing the links in the diagram. the rules should be the same for links between the same two columns, but will likely differ for links between other columns.
- Note: since the links should use actual, existing node
names for theirsourceandtargetvalues, you may want to pass the list of nodes created in getNodes as a second parameter togetLinks
- Note: since the links should use actual, existing node
- as the specs say different ways about how to get the links for different column-pairs, you might want to further decompose this problem by having a function for each pair of columns of nodes (unless your diagram has only 2 columns), e.g.
getLinksCol1Col2(or instead ofColyou could use the semantic meaning of that column), etc.- this reduces the responsibilities of the
getLinksfunction to just calling these other functions and combining their results
- this reduces the responsibilities of the
- at this point of problem decomposition, you have reduced the responsibility of (e.g.)
getLinksCol1Col2to returning a list of links for only the links between the first and second columns, all of the links for a column are defined as being created using the same pieces of the dataset
- https://d3-graph-gallery.com/graph/sankey_basic.html
- https://github.com/d3/d3-sankey
- https://observablehq.com/@d3/sankey/2
- https://observablehq.com/@d3/sankey-component
