Published
Edited
Apr 30, 2019
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// Declaring it here so that it can be used in multiple cells
defaultTippyConfig = ({
arrow: false,
flip: true,
followCursor: true,
placement: 'right',
offset: "60, 10",
animation: 'fade',
duration: [100, 100],
hideOnClick: false,
interactive: true
})
Insert cell
Insert cell
// Various event handers used for mouse events and displaying
eventHandlers = ({
hideOnClick: function(clickedElement) {
d3.select(clickedElement).transition("makeElementTransparent").duration(300).attr("opacity", 0);
d3.select(clickedElement).transition("hideElement").delay(300).attr("visibility", "hidden");
},
highlight: function(element, elementName) {
d3.select(element).transition("highlight").duration(100).attr("fill", colors[elementName][2]);
},
dehighlight: function(element, elementName) {
d3.select(element).transition("dehighlight").duration(100).attr("fill", colors[elementName][0]);
},
emphasize: function(element, data) {
d3.select(element).transition("emphasize").duration(100).attr("fill", colors[data.key][1]);
},
deemphasize: function(element, data) {
d3.select(element).transition("deemphasize").duration(100).attr("fill", colors[data.key][2]);
},
restore: function(element, data) {
d3.select(element).transition("restore").duration(100).attr("fill", colors[data.key][0]);
},
setStrokeOpacity: function(element, val) {
d3.select(element).attr("stroke-opacity", 1.0);
},
displayText: function(text, force) {
$("#narrative").fadeOut(function() {
$(this).text(text).fadeIn();
});
}
})
Insert cell
Insert cell
reality = {
// This section updates when the reality toggle button is hit
realityToggleButton
console.log("haha!!")
// Check the current value of reality
if (typeof this === 'undefined') {
// It's undefined at page load, so if it's undefined, initilize as false
// because we're starting with the planned schedule
// Display the beginning of the story
eventHandlers.displayText("up at 7:00 and in bed at 10:30 🙂");
return false;
}
else {
if (this === false) {
// Switching to reality
eventHandlers.displayText("lol just kidding 😂 ");
setTimeout(function(){ eventHandlers.displayText("Hover over graph to explore 🕵️‍♂️"); }, 3000);
// Make all the overlay areas (that might be hidden) immediately visible
d3.select("#transparentOverlay")
.attr("visibility", "hidden");
d3.select("#workingOnTheApp")
.attr("opacity", 1)
.attr("visibility", "visible");
d3.selectAll(".mainCategory")
.attr("opacity", 1)
.attr("visibility", "visible");
// Toggle button name
d3.select('#realityToggleButton')
.text("← Show Ideal Schedule");
// Config tippy
tippy(d3.selectAll(".mainCategory").nodes(),defaultTippyConfig);
tippy(d3.selectAll(".subCategory").nodes(),defaultTippyConfig);
}
else{
// Switching to plan
eventHandlers.displayText("up at 7:00 and in bed at 10:30 🙂");
// Toggle button name
d3.select('#realityToggleButton')
.text(" Show Reality →");
d3.select("#transparentOverlay")
.attr("visibility", "visible");
}
d3.select("#awake")
.transition("expandAwake").duration(1500)
.attr("d", timeAwakeAreaGenerator(!this));
d3.select("#workingOnTheApp")
.transition("expandWorkingOnTheApp").duration(1500)
.attr("d", workingOnTheAppAreaGenerator(!this));
d3.selectAll(".categories")
.transition("expandCategories").duration(1500)
.attr("d", stackedAreaGenerator(!this));
return !this;
}
}
Insert cell
Insert cell
unhideButtonClicked = {
// This section updates when the unhide button is hit
unhideOverlaysButton
// First make overlay areas visible
d3.selectAll(".overlay")
.attr("visibility", "visible");
// Then opacity transition from 0 to 1
d3.selectAll(".overlay")
.transition("makeElementOpaque")
.duration(300)
.attr("opacity", 1.0);
}
Insert cell
Insert cell
modifyChart = {
// This is to tell the notebook that this chunk depends on chart
// so that every time chart update, this block should update too
chart
graphGridlines
// These are the things we want to modify
let dayAxis = d3.select("#xAxis");
let hourAxis = d3.select("#yAxis")
let dayAxisTicks = dayAxis.selectAll(".tick");
let hourAxisTicks = hourAxis.selectAll(".tick");
// Hide both axes
dayAxis.select(".domain")
.attr("stroke", "hsla(0,0%,0%,0.0)");
hourAxis.select(".domain")
.attr("stroke", "hsla(0,0%,0%,0.0)");
// Change color of all day axis ticks
dayAxisTicks
.selectAll("line")
.attr("stroke", graphGridlines === "Yes"? "hsla(212, 8%, 52%, 0.5)" : "hsla(212, 8%, 52%, 0)");
// Set the tick mark at 2/13 to be clear
dayAxisTicks
.filter(function(){ return ["2/7 Thu","2/13 Wed"].includes(d3.select(this).select("text").text()) })
.select("line")
.attr("stroke", "hsla(0,0%,0%,0.0)");
// For all axis labels: set color, font size, and transform to lowercase
dayAxisTicks
.selectAll("text")
.attr("fill","hsl(0, 0%, 40%)")
.style("font-size","1.2rem")
// .style("text-transform","lowercase");
hourAxisTicks
.selectAll("text")
.attr("fill","hsl(0, 0%, 50%)")
.style("font-size","1rem")
.style("text-transform","lowercase");
// Hide the blue selection border for everythin in class .noSelect
d3.selectAll(".noSelect")
.style("outline","none");
// Change cursor to pointer for everything in the class .clickable
d3.selectAll(".clickable")
.style("cursor","pointer");
}
Insert cell
Insert cell
// Graph technically starts at 3 AM, but using 2:40 here to shrink the axis labels vertically so that they are vertically contained within the top and bottom boundary of the graph
startOfDay = new Date('2/7/19 2:40:00 AM')
Insert cell
// Same as above
endOfDay = startOfDay.addDays(1).addMinutes(40)
Insert cell
xAxisTicks = [new Date('2/7/19'), new Date('2/8/19'), new Date('2/9/19'), new Date('2/10/19'), new Date('2/11/19'), new Date('2/12/19'), new Date('2/13/19')]
Insert cell
yAxisTicks = startOfDay.generateTicks(9, 3, 20)
Insert cell
myDataCSV = `Index,Date,Wake up Time,Go to Sleep Time,Google,YouTube,Stack Overflow,Apple Developer Doc,Other Websites,Write Code,Create Graphics,Make Video,Update Portfolio,Search for Information,Consume Information,Produce Information,Working on the App
0,2/7/19,2/7/19 7:34:00 AM,2/7/19 10:28:00 PM,139,5193,420,848,0,3840,0,0,0,139,6461,3840,10440
1,2/8/19,2/8/19 7:05:00 AM,2/9/19 2:45:00 AM,1074,3360,3720,2182,2354,35280,1809,0,0,1074,11616,37089,49779
2,2/9/19,2/9/19 8:43:00 AM,2/9/19 11:41:00 PM,290,138,1860,1832,325,19320,1119,0,0,290,4155,20439,24884
3,2/10/19,2/10/19 8:04:00 AM,2/11/19 1:15:00 AM,1034,338,4980,648,530,27420,3723,0,0,1034,6496,31143,38673
4,2/11/19,2/11/19 8:08:00 AM,2/12/19 12:21:00 AM,368,109,900,591,342,23820,4879,0,0,368,1942,28699,31009
5,2/12/19,2/12/19 8:06:00 AM,2/12/19 10:53:00 PM,16,0,720,428,0,360,116,0,0,16,1148,476,1640
6,2/13/19,2/13/19 7:08:00 AM,2/13/19 10:34:00 PM,1094,0,300,60,569,2160,4555,4186,11863,1094,929,22764,24787`
Insert cell
// Convert csv data into an array of objects, where each object represents a single day (a row in the csv)
myData = d3.csvParse(myDataCSV, row => ({
"Index": +row["Index"],
"Date": new Date(row["Date"]),
"Wake up Time": new Date(row["Wake up Time"]),
"Go to Sleep Time": new Date(row["Go to Sleep Time"]),
"Baseline": new Date(row["Baseline"]),
"Google": +row["Google"],
"YouTube": +row["YouTube"],
"Stack Overflow": +row["Stack Overflow"],
"Apple Developer Doc": +row["Apple Developer Doc"],
"Other Websites": +row["Other Websites"],
"Write Code": +row["Write Code"],
"Create Graphics": +row["Create Graphics"],
"Make Video": +row["Make Video"],
"Update Portfolio": +row["Update Portfolio"],
"Search for Information": +row["Search for Information"],
"Consume Information": +row["Consume Information"],
"Produce Information": +row["Produce Information"],
"Working on the App": +row["Working on the App"]
}))
Insert cell
// Verify all the columns in my data
myData.columns
Insert cell
// An arbitrary array of offsets for the streamgraph that makes visual sense
appAreaOffsets = [281, 192, 230, 192, 211, 246, 258]
Insert cell
// Colors for all the area paths: [original, emphasize, deemphasize]
// emphasize: some bright colors are darkened (to increase contrast) while the rest are the same as the original
// deemphasize: lightened versions of the original colors
colors = ({
"Google": ["#DA4251", "#DA4251", "#FFD6DC"],
"YouTube": ["#ED6F48", "#ED6F48", "#FFE4DB"],
"Stack Overflow": ["#F7A65B", "#F7A65B", "#FFEEDE"],
"Apple Developer Doc": ["#FAD785","#FAD785","#FFF8E6"],
"Other Websites": ["#F6F3A7", "#E8E18B", "#FEFDEE"],
"Write Code": ["#D6EA92", "#BDD289", "#F8FCEA"],
"Create Graphics": ["#A2D69A","#A2D69A","#ECF9EB"],
"Make Video": ["#64B8A4","#64B8A4","#DFF5F0"],
"Update Portfolio": ["#4288B5","#4288B5","#D9EFFF"],
"Search for Information": ["#666666", "#666666", "#bbbbbb"],
"Consume Information": ["#888888", "#777777", "#cccccc"],
"Produce Information": ["#aaaaaa", "#888888", "#dddddd"],
"Working on the App": ["#555555", "#555555", "#888888"]
})
Insert cell
// Statistics to be interactively displayed below the graph
statistics = ({
"Google": "1h (2%)",
"YouTube": "2.5h (5%)",
"Stack Overflow": "3.5h (7%)",
"Apple Developer Doc": "2h (4%)",
"Other Websites": "1h (2%)",
"Write Code": "31h (62%)",
"Create Graphics": "4.5h (9%)",
"Make Video": "1h (2%)",
"Update Portfolio": "3.5h (7%)",
"Search for Information": "1h (2%)",
"Consume Information": "9h (18%)",
"Produce Information": "40h (80%)",
})
Insert cell
// Information to be displayed inside the Tippy tooltips upon hover
tooltips = ({
"Google": '<div><a href="https://google.com" target="_blank"><img src="https://github.com/Jianan-Li/icons/raw/master/Google.png" height="50" width="50"/></a></div>Google',
"YouTube": '<div><a href="https://youtube.com" target="_blank"><img src="https://github.com/Jianan-Li/icons/raw/master/Youtube.png" height="50" width="50"/></a></div>YouTube',
"Stack Overflow": '<div><a href="https://stackoverflow.com" target="_blank"><img src="https://github.com/Jianan-Li/icons/raw/master/StackOverflow.png" height="50" width="50"/></a></div>Stack Overflow',
"Apple Developer Doc": '<div><a href="https://developer.apple.com/documentation/" target="_blank"><img src="https://github.com/Jianan-Li/icons/raw/master/Apple.png" height="50" width="50" /></a><a href="https://swift.org/documentation/" target="_blank"><img src="https://github.com/Jianan-Li/icons/raw/master/Swift.png" height="50" width="50" /></a></div>Apple Developer Doc',
"Other Websites": '<div><a href="https://medium.com" target="_blank"><img src="https://github.com/Jianan-Li/icons/raw/master/Medium.png" height="50" width="50"/></a><a href="https://www.raywenderlich.com/" target="_blank"><img src="https://github.com/Jianan-Li/icons/raw/master/Raywenderlich.png" height="50" width="50"/></a></div>Other Websites',
"Write Code": '<div><a href="https://developer.apple.com/xcode/" target="_blank"><img src="https://github.com/Jianan-Li/icons/raw/master/Xcode_1.png" height="50" width="50"/></a></div>Write Code',
"Create Graphics": '<div><a href="https://figma.com" target="_blank"><img src="https://github.com/Jianan-Li/icons/raw/master/Figma.png" height="50" width="50" /></a><a href="https://geticonjar.com/" target="_blank"><img src="https://github.com/Jianan-Li/icons/raw/master/Iconjar.png" height="50" width="50" /></a></div><div><a href="https://getpixelsnap.com/" target="_blank"><img src="https://github.com/Jianan-Li/icons/raw/master/PixelSnap.png" height="50" width="50" /></a><a href="https://sipapp.io/" target="_blank"><img src="https://github.com/Jianan-Li/icons/raw/master/Sip.png" height="50" width="50" /></a></div>Create Graphics',
"Make Video": '<div><a href="https://rotato.xyz/" target="_blank"><img src="https://github.com/Jianan-Li/icons/raw/master/Rotato.png" height="50" width="50" /></a><a href="https://www.adobe.com/creativecloud.html" target="_blank"><img src="https://github.com/Jianan-Li/icons/raw/master/Premiere.png" height="50" width="50" /></a></div>Make Video',
"Update Portfolio": '<div><a href="https://atom.io/" target="_blank"><img src="https://github.com/Jianan-Li/icons/raw/master/Atom.png" height="50" width="50" /></a><a href="https://hyper.is/" target="_blank"><img src="https://github.com/Jianan-Li/icons/raw/master/Hyper.png" height="50" width="50" /></a></div><div><a href="https://firebase.google.com/" target="_blank"><img src="https://github.com/Jianan-Li/icons/raw/master/Firebase.png" height="50" width="50" /></a><a href="https://github.com" target="_blank"><img src="https://github.com/Jianan-Li/icons/raw/master/Github.png" height="50" width="50" /></a></div>Update Portfolio',
"Search for Information": '<div class="tippy-emoji">🔍</div>Search for Information',
"Consume Information": '<div class="tippy-emoji">🍽</div>Consume Information',
"Produce Information": '<div class="tippy-emoji">✍🏻</div>Produce Information',
"Working on the App": '<div class="tippy-emoji">👨🏻‍💻</div>Working on app',
"Awake": '<div class="tippy-emoji">🤹‍♂️</div>Not working on app',
"Asleep": '<div class="tippy-emoji">😴</div>Sleeping'
})
Insert cell
Insert cell
Insert cell
width
Insert cell
height = width * 0.55
Insert cell
margin = ({
top: 50,
bottom: 0,
left: 80,
right: 80
})
Insert cell
graphWidth = width - margin.left - margin.right
Insert cell
graphHeight = height - margin.top - margin.bottom
Insert cell
Insert cell
x = d3.scaleTime()
.domain([new Date('2/7/19'), new Date('2/13/19')])
.range([margin.left, graphWidth+margin.left]);
Insert cell
y = d3.scaleLinear()
.domain([startOfDay, endOfDay])
.range([margin.top, graphHeight+margin.top]);
Insert cell
// .tickSize(-graphHeight) is used to produce gridlines
xAxis = d3.axisTop(x).tickSize(-graphHeight).tickPadding(20).tickValues(xAxisTicks).tickFormat(d3.timeFormat("%-m/%-d %a"));
Insert cell
// .tickSize(0) is used to completely hide the ticks
yAxis = d3.axisLeft(y).tickSize(0).tickPadding(10).tickValues(yAxisTicks).tickFormat(d3.timeFormat("%-I %p"));
Insert cell
Insert cell
Insert cell
Insert cell
timeAwakeAreaGenerator = function(reality) {
return d3.area()
.curve(graphStyle == 'curveMonotoneX'? d3.curveMonotoneX : d3.curveLinear)
.x(d => x(d.Date))
.y0(d => reality ? y(d["Wake up Time"].subtractDays(d.Index)) : y(new Date("2/7/19 7:00:00 AM")) )
.y1(d => reality ? y(d["Go to Sleep Time"].subtractDays(d.Index)) : y(new Date("2/7/19 10:30:00 PM")));
}
Insert cell
Insert cell
workingOnTheAppAreaGenerator = function(reality) {
return d3.area()
.curve(graphStyle == 'curveMonotoneX'? d3.curveMonotoneX : d3.curveLinear)
.x(d => x(d.Date))
.y0(d => reality ?
y(d["Wake up Time"]
.subtractDays(d.Index).addMinutes(appAreaOffsets[d.Index]))
: y(new Date("2/7/19 12:00:00 PM")))
.y1(d => reality ?
y(d["Wake up Time"]
.subtractDays(d.Index).addMinutes(appAreaOffsets[d.Index]).addSeconds(d["Working on the App"]))
: y(new Date("2/7/19 12:00:00 PM")));
}
Insert cell
Insert cell
stackedAreaGenerator = function(reality) {
return d3.area()
.curve(graphStyle == 'curveMonotoneX'? d3.curveMonotoneX : d3.curveLinear)
.x(d => x(d.data.Date))
.y0(d => reality ?
y(d.data["Wake up Time"]
.subtractDays(d.data.Index).addMinutes(appAreaOffsets[d.data.Index]).addSeconds(d[0]))
: y(new Date("2/7/19 12:00:00 PM")))
.y1(d => reality ?
y(d.data["Wake up Time"]
.subtractDays(d.data.Index).addMinutes(appAreaOffsets[d.data.Index]).addSeconds(d[1]))
: y(new Date("2/7/19 12:00:00 PM")));
}
Insert cell
Insert cell
mainCategoriesStackedData = {
const keys = myData.columns.slice(-4, -1);
const stack = d3.stack()
.keys(keys)
.order(d3.stackOrderAescending);
return stack(myData);
}
Insert cell
Insert cell
subCategoriesStackedData = {
const keys = myData.columns.slice(4, -4);
const stack = d3.stack()
.keys(keys)
.order(d3.stackOrderAescending);
return stack(myData);
}
Insert cell
Insert cell
Date.prototype.generateTicks = function(numberOfTicks, gapInHours, offsetMinutes) {
let startTick = new Date(this.valueOf());
startTick = startTick.addMinutes(offsetMinutes)
let ticks = []
for (let i = 0; i < numberOfTicks; i++) {
let nextTick = startTick.addHours(i*gapInHours)
ticks.push(nextTick)
}
return ticks;
}
Insert cell
Date.prototype.subtractDays = function(days) {
var date = new Date(this.valueOf());
date.setDate(date.getDate() - days);
return date;
}
Insert cell
Date.prototype.addDays = function(days) {
var date = new Date(this.valueOf());
date.setDate(date.getDate() + days);
return date;
}
Insert cell
Date.prototype.addHours = function(hours) {
var date = new Date(this.valueOf());
date.setHours(date.getHours() + hours);
return date;
}
Insert cell
Date.prototype.addMinutes= function(minutes) {
var date = new Date(this.valueOf());
date.setMinutes(date.getMinutes() + minutes);
return date;
}
Insert cell
Date.prototype.addSeconds= function(seconds) {
var date = new Date(this.valueOf());
date.setSeconds(date.getSeconds() + seconds);
return date;
}
Insert cell
Insert cell
Insert cell
// Use jQuery to select all element on the page and set font family
$( "*" ).css( "font-family", "-apple-system,system-ui,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif" )
Insert cell
Insert cell
$("#realityToggleButton").wrap('<div class="flex-container"></div>');
Insert cell
Insert cell
html `
<style>
#realityToggleButton {
transition: background-color 0.2s;
font-size: 1rem;
width: 12rem;
height: 2.2rem;
text-decoration: none;
border-radius: 0.5rem;
outline: none;
cursor: pointer;
border: none;
background-color: #eee;
margin-top: 1rem;
}
#realityToggleButton:hover {
background-color: #ddd;
}
#realityToggleButton:active {
background-color: #ccc;
}
.tooltipImage {
width = 6rem;
height = 6rem;
}
.tippy-tooltip {
font-size: 1.1rem;
padding: 0.3rem 0.6rem;
}
.tippy-emoji {
font-size: 3rem;
}
</style>
`
Insert cell
Insert cell
Insert cell
Insert cell
// Import jQuery for both selection and easy text fade in and fade out transitions
$ = require("https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js")
Insert cell
// A tooltip library written in vanilla JS
tippy = require("https://unpkg.com/tippy.js@4.0.0")
Insert cell
// Tippy depends on popper
popper = require("https://unpkg.com/popper.js@1/dist/umd/popper.min.js")
Insert cell
// Import D3
d3 = require("d3@5")
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more