Data-Driven Disappointment Part 2: Getting set up and getting started
In part 2 of this series on using the D3 Javascript library (d3.js), we will look at one way to setup data to be consumed by D3 via a tab-separated values data string copied directly out of Google Sheets. We will also be introduced to the dataset that we’ll be using throughout, a list of lay off casualties derived from the publicly available team information on the website of my former employer, Canada Learning Code.
Note: if you’re just coming to this series or need a refresher on the story so far, why not check out the first post?
What is D3 (and D3.js)?
D3 (https://d3js.org), short for Data-Driven Documents, is a JavaScript library that is used for data visualisation. The “Documents” of D3 refer to the document object model (DOM) of a webpage, with which this library interacts. From D3’s own website page (“What is D3?”, https://d3js.org/what-is-d3:
D3 is not a charting library in the traditional sense. It has no concept of “charts”. When you visualize data with D3, you compose a variety of primitives. … D3 makes things possible, not necessarily easy; even simple things that should be easy are often not. To paraphrase Amanda Cox: “Use D3 if you think it’s perfectly normal to write a hundred lines of code for a bar chart.”
Sounds perfect.
What data will we (can we) use?
The main constraint for data is that it has to be publicly available. Being a registered Canadian not-for-profit and charity, there is actually a lot of information that can be accessed by way of the organisation’s annual statutory filing, the T3010 Registered Charity Information Return. I don’t need to go that far to find what I want: the information that I want can be found on Canada Learning Code’s own website at the bottom of the Our Team page (https://www.canadalearningcode.ca/our-team/).
The plan
The plan is simple and easily reproducible:
- Grab a historic version of the Our Team page from the Internet Archive’s WayBack Machine (https://web.archive.org/) from some time around Judgment Day but before the webpage was updated.
- Grab an up-to-date or updated version of the Our Team page.
- Tabulate the roles of the people listed for the HQ Team.
- Cross-reference the people listed for the HQ Team between page versions: If they aren’t present in the updated version, they’re likely (enough) to have been laid off. Flag these people.
There are few enough folks that I will do this manually, though it would probably be quite simple to scrape this information and set up a few rules to automatically cross-reference it as well.
Additional data notes
- Since there exists no publicly-accessible organisational chart (AFAIK), I won’t be -grouping people into their organisational units.
- People can still be functionally grouped through reasonable inference based on their role title and a rough hierarchy will be coded, from 0 to 4, where:
- 1 = “manager”
- 2 = “director”
- 3 = “chief” (non-executive)
- 4 = “chief executive”
- 0 = everyone else (“non-manager”)
- The hierarchy coding doesn’t distinguish between the different types of managers, (e.g., people managers versus product or program managers), nor does it include within-level seniority (e.g., managers versus senior managers).
- I won’t be using names, though they are publicly listed alongside the rest of the information being used.
- I won’t be differentiating between full-time and part-time people because that information doesn’t seem outwardly apparent or available.
Getting D3 Set Up
There are lots of ways to load D3, however I will be using an import
statement to grab it as an ECMAScript module (ESM) bundle from the JavaScript CDN jsdelivr.net
1:
<!DOCTYPE html>
<script type="module">
import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";
// your code goes here
</script>
<div id="d3-container">
<!-- this will hold whatever D3 generates -->
</div>
- Note how the
<script>
tag has atype
attribute ofmodule
2: this is necessary to be able to make use of theimport
statement. Without it, you’ll get an error message along the lines of:SyntaxError: Cannot use import statement outside a module
. After theimport
statement, we can start using the different D3 components on our page throught thed3
object/namespace. - I’ve also created a
<div>
withid="d3-container"
, which will be the target for the elements created with D3.
Loading the data
Did you know that if you drag-select and copy cells from Google Sheets, they will be pasted as a tab-separated values (tsv) string? I’ve gathered the data that I’ll be working with as detailed above and I’ve staged it in a Google Sheets document structured as follows:
Role | LaidOff | Functional | Hierarchy |
---|---|---|---|
string , role title/name as written on website |
bool -ish 3, "TRUE" if the role disappeared between updates otherwise "FALSE" |
string , The role’s functional unit that’s been inferred from the role title, e.g. MARCOM , HR , FUND , etc. |
int , value from 0 to 4 codifying the role’s relative position in the organisation’s control structure. |
Copying and pasting the data into an IDE yields the following string, which I’m assigning to a variable, rawData
.
Show data string
const rawData = `Role LaidOff Functional Hierarchy
Chief Executive Officer FALSE 4
Chief Strategy & People Officer FALSE 3
Director, Fund Development FALSE FUND 2
Director, Partnerships & Program Facilitation FALSE 2
Director of Marketing and Communications FALSE MARCOM 2
Director of Finances & Accounting TRUE FIN 2
Director, Programs FALSE 2
K-12 Program Manager TRUE 1
Adult Program Manager FALSE 1
Manager, Chapters FALSE 1
Senior Manager, Partnerships FALSE 1
Senior Manager, Program Facilitation FALSE 1
Senior Project Manager TRUE 1
Sr Manager Evaluation & Impact Measurement TRUE EVAL 1
Senior People and Culture Manager FALSE HR 1
Senior Marketing Manager FALSE MARCOM 1
Instructional Training Manager FALSE 1
Senior Fund Development Specialist TRUE 0
Sr. Learning Experience Designer TRUE 0
Senior Partner Development Lead TRUE 0
Senior Learning Facilitator TRUE 0
Senior Learning Facilitator TRUE 0
Senior Learning Facilitator TRUE 0
Senior Fund Development Lead FALSE FUND 0
Senior Fund Development Lead FALSE FUND 0
Partnership Development TRUE 0
Lead, Teen Ambassador Program (TAP) TRUE 0
Senior Partnership Development Lead TRUE 0
Senior Bilingual Learning Facilitator TRUE 0
Senior Bilingual Learning Facilitator TRUE 0
Bilingual learning Facilitator TRUE 0
Learning Experience Designer, Adult Programs TRUE 0
Learning Facilitator TRUE 0
Learning Facilitator TRUE 0
Learning Facilitator TRUE 0
Learning Facilitator TRUE 0
Bilingual, Partnership Development TRUE 0
Accountant FALSE FIN 0
People and Culture Coordinator TRUE HR 0
Data Analyst TRUE EVAL 0
Partnerships Coordinator TRUE 0
Marketing Coordinator FALSE MARCOM 0`
With a well formed tsv string, I can use the d3.tsvParse()
method to–you guessed it–parse the tsv string into an array of objects, using each column name as a property.
const data = d3.tsvParse(rawData);
//console.log(data);
// [
// { "Role": "Chief Executive Officer", "LaidOff": "FALSE", "Functional": "", "Hierarchy": "4" },
// { "Role": "Chief Strategy & People Officer", "LaidOff": "FALSE", "Functional": "", "Hierarchy": "3" },
// // and 40 other entries ...
// ]
Our first (pie) chart
See the code:
<script type="module" defer>
import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";
const data = [1, 2, 3, 4, 5];
const height = 300;
const width = 300;
const margin = 30;
const outerRadius = Math.min(width,height) / 2 - margin;
const arcGenerator = d3.arc()
.innerRadius(outerRadius * .57)
.outerRadius(outerRadius)
;
const pieGenerator = d3.pie()
// .value(d => d)
;
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [-width /2, -height /2, width, height])
.style("margin-inline", "auto")
.style("width", "100%")
;
svg.append("g")
.selectAll()
.data(pieGenerator(data))
.join("path")
.attr("fill", d => d3.interpolateGnBu(d.value/d3.max(data)))
.attr("stroke", "none")
.attr("d", arcGenerator) // SVG attribute for drawn path
;
d3.select("#d3-container").node()
.append(svg.node());
</script>
<div id="d3-container"></div>
From here, we can begin to use (some of) the rest of D3’s functionality to explore the data. The “100 lines of code” is, in this case, a bit of an exaggeration, but it’s true that there’s more than a little bit of setup and preparation necessary to make even a simple chart. Let’s take a pie chart4, for example:
- A pie chart is made of slices. In D3, a pie slice (or circle fraction) is called an
arc
. - An
arc
is defined by aninnerRadius
, anouterRadius
, astartAngle
, and astopAngle
. - It would be a hassle to have to determine these ourselves, but D3 has a pie generator,
d3.pie()
which can calculate the appropriate values forstartAngle
andstopAngle
based on data that we provide.
Even with those tools to help us, we’re still responsible for creating the scalable vector graphics (SVG) elements and inserting them into the DOM. SVGs are images that are made of paths defined in code. Behind the scenes, each pie segment in the example above looks a little something like:
<path
fill="rgb(211, 238, 206)"
stroke="none"
d="M-48.808,-109.625A120,120,0,0,1,0,-120L0,-68.4A68.4,68.4,0,0,0,-27.821,-62.487Z">
</path>
The fill
and stroke
attributes are probably self-explanatory, but the d
attribute is where the magic happens: this defines what shape the path will take when drawn–in our case, how the pie segment will appear. Thankfully, we’re not the ones coming up with these numbers: we can provide data to d3.arc()
and have it compute the appropriate paths.
Planning the next move
✅import
D3 ECMAScript module inside a<script>
element withtype="module"
Create a container✅<div>
for our future chart.Format data as tsv string and load with✅d3.tsvParse()
.- Create a root
<svg>
element to contain the chart graphics, defining awidth
andheight
. - Create a
<g>
(group) element to contain the<path>
elements that will make up the pie chart. - Use
d3.pie()
andd3.arc()
to generate the code for the pie chart slices, based on some data that we’ll provide. - Put the generated pie chart into the container
<div>
. - Admire our handiwork! 💪
Are you having fun yet? We’ll pick this up in the next post, Data-Driven Disappointment Part 3: Dealing with data
-
D3 also plays nice with other popular frameworks such as React and Svelte. Check out their Getting Started page for more details on the different ways you can use D3. ↩
-
Learn more about the differences between JavaScript modules and standard scripts here: MDN: JavaScript Modules # Other differences between modules and standard scripts ↩
-
bool
-ish because it’s a string that has the vale of"TRUE"
or"FALSE"
but not a truebool
as of writing. ↩ -
A pie chart is a way of representing fractions or proportions, with each group carving out a certain amount of “chart angle” (pie slice) based on its size relative to other groups. ↩