Reference implementation : https://xkcd.com/1205/ (:
Last active
July 31, 2018 22:33
-
-
Save mthh/59c50f1776cc3383e547e2e99033c7bc to your computer and use it in GitHub Desktop.
XKCD-style grid chart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
license: mit |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<meta charset="utf-8"> | |
<head> | |
<style> | |
@font-face { | |
font-family: "xkcd"; | |
src: url('Humor-Sans-1.0.ttf'); | |
} | |
body { | |
font-family: "xkcd", sans-serif; | |
font-size: 16px; | |
color: #333; | |
} | |
.title, #grid { | |
text-align: center; | |
margin: auto; | |
} | |
.title { | |
margin-left: 80px; | |
margin-bottom: 30px; | |
} | |
select > * { | |
font-family: "xkcd", sans-serif; | |
} | |
</style> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
</head> | |
<body> | |
<div class="title"> | |
<h3>How long can you work on making a routine task more<br>efficient before you're spending more time than you save?</h3> | |
<p>Across <select id="select_years"></select> years</p> | |
</div> | |
<div id="grid"></div> | |
<script> | |
function convertSeconds(sec) { | |
let seconds = sec; | |
let day, hour, minute; | |
minute = Math.floor(seconds / 60); | |
seconds = seconds % 60; | |
hour = Math.floor(minute / 60); | |
minute = minute % 60; | |
day = Math.floor(hour / 24); | |
hour = hour % 24; | |
return { | |
day: day, | |
hour: hour, | |
minute: minute, | |
seconds: seconds | |
}; | |
} | |
function makeGridTime(years=5, width, height) { | |
const data = []; | |
let xpos = 1; | |
let ypos = 1; | |
let t; | |
let t1; | |
let rows; | |
for (let row = 0; row < 9; row++) { | |
rows = []; | |
if (row === 0) { | |
// 1 second | |
t = 1; | |
} else if (row === 1) { | |
// 5 seconds | |
t = 5; | |
} else if (row === 2) { | |
// 30 seconds | |
t = 30; | |
} else if (row === 3) { | |
// 1 minute | |
t = 60; | |
} else if (row === 4) { | |
// 5 minutes | |
t = 60 * 5; | |
} else if (row === 5) { | |
// 3O minutes | |
t = 60 * 30; | |
} else if (row === 6) { | |
// 1 hours | |
t = 3600; | |
} else if (row === 7) { | |
// 6 hours | |
t = 3600 * 6; | |
} else if (row === 8) { | |
// 1 day | |
t = 3600 * 24; | |
} | |
for (let column = 0; column < 6; column++) { | |
if (column === 0) { | |
// 50 times per day | |
t1 = t * 50 * 365 * years; | |
} else if (column === 1) { | |
// 5 times per day | |
t1 = t * 5 * 365 * years; | |
} else if (column === 2) { | |
// daily | |
t1 = t * 365 * years; | |
} else if (column === 3) { | |
// weekly | |
t1 = t * 52 * years; | |
} else if (column === 4) { | |
// monthly | |
t1 = t * 12 * years; | |
} else if (column === 5) { | |
// yearly | |
t1 = t * 1 * years; | |
} | |
let time = convertSeconds(t1); | |
let value; | |
if (time.minute === 0 && time.day === 0 && time.hour == 0){ | |
value = `${time.seconds} seconds` | |
} else if (time.hour === 0 && time.day === 0) { | |
value = `${time.minute} minutes` | |
} else if (time.hour > 21 && time.hour < 30 && time.day === 0) { | |
value = `1 day` | |
} else if (time.day === 0) { | |
value = `${time.hour} hours`; | |
} else if (time.day > 32) { | |
value = `${Math.round(time.day / 31)} months` | |
} else if (time.day > 5 && time.day % 7 <= 3) { | |
value = `${(time.day - (time.day % 7)) / 7} weeks` | |
} else { | |
value = `${time.day} days`; | |
} | |
value = value.split(' '); | |
rows.push({ | |
x: xpos, | |
y: ypos, | |
width: width, | |
height: height, | |
click: 0, | |
value: value[0], | |
unit: value[1], | |
value_second: t1, | |
}) | |
xpos += width; | |
} | |
data.push(rows); | |
xpos = 1; | |
ypos += height; | |
} | |
return data; | |
} | |
function makeChart(years) { | |
d3.select("#grid").selectAll('*').remove() | |
let width_cell = 85; | |
let height_cell = 45; | |
let gridData = makeGridTime(years, width_cell, height_cell); | |
let margin = { | |
left: 160, | |
right: 40, | |
top: 40, | |
bottom: 40, | |
}; | |
let width = (width_cell + 1) * 6 + margin.left + margin.right; | |
let height = (height_cell + 1) * 9 + margin.top + margin.bottom; | |
let svg = d3.select("#grid") | |
.append("svg") | |
.attr("width", width) | |
.attr("height", height); | |
let grid = svg.append('g') | |
.attr('transform', `translate(${margin.left}, ${margin.top})`); | |
let xaxis = svg.append('g') | |
.attr('transform', `translate(${margin.left}, 0)`); | |
xaxis.append('text') | |
.attr('text-anchor', 'middle') | |
.attr('x', (width - margin.left) / 2) | |
.attr('y', 10) | |
.text('HOW OFTEN YOU DO THE TASK'); | |
xaxis.selectAll('.axislabel') | |
.data(['50/DAY', '5/DAY', 'DAILY', 'WEEKLY', 'MONTHLY', 'YEARLY']) | |
.enter() | |
.append('text') | |
.attr('x', (_, i) => (width_cell / 2) + (i * width_cell)) | |
.attr('y', 30) | |
.attr('font-size', 11) | |
.attr('text-anchor', 'middle') | |
.text(d => d) | |
let yaxis = svg.append('g') | |
.attr('transform', `translate(0, ${margin.top})`); | |
yaxis.selectAll('.axislabel2') | |
.data('HOW MUCH TIME YOU SHAVE OFF'.split(' ')) | |
.enter() | |
.append('text') | |
.attr('x', 65) | |
.attr('y', (_, i) => 155 + i * 19) | |
.attr('text-anchor', 'end') | |
.text(d => d) | |
yaxis.selectAll('.axislabel') | |
.data(['1 SECOND', '5 SECONDS', '30 SECONDS', '1 MINUTE', '5 MINUTES', '30 MINUTES', '1 HOUR', '6 HOURS', '1 DAY']) | |
.enter() | |
.append('text') | |
.attr('x', margin.left - 5) | |
.attr('y', (_, i) => (height_cell / 2) + (i * height_cell)) | |
.attr('font-size', 11) | |
.attr('text-anchor', 'end') | |
.text(d => d); | |
let row = grid.selectAll(".row") | |
.data(gridData) | |
.enter().append("g") | |
.attr("class", "row"); | |
let column = row.selectAll(".square") | |
.data(d => d) | |
.enter().append("rect") | |
.attr("class","square") | |
.attr("x", function(d) { return d.x; }) | |
.attr("y", function(d) { return d.y; }) | |
.attr("width", function(d) { return d.width; }) | |
.attr("height", function(d) { return d.height; }) | |
.style("fill", d => +d.value_second >= 31536000 * 0.9 ? 'gray' : 'white' ) | |
.style("stroke", "#222") | |
.on('click', function(d) { | |
d.click++; | |
if ((d.click)%4 == 0 ) { d3.select(this).style("fill","#fff"); } | |
if ((d.click)%4 == 1 ) { d3.select(this).style("fill","#2C93E8"); } | |
if ((d.click)%4 == 2 ) { d3.select(this).style("fill","#F56C4E"); } | |
}); | |
grid.append('g') | |
.selectAll('.labelvalue') | |
.data(gridData.reduce((acc, val) => acc.concat(val), [])) | |
.enter() | |
.append('text') | |
.attr('class', 'labelvalue') | |
.attr('x', d => d.x + 15) | |
.attr('y', d => d.y + 15) | |
.html(d => +d.value_second < 31536000 * 0.9 ? d.value : null); | |
grid.append('g') | |
.selectAll('.labelunit') | |
.data(gridData.reduce((acc, val) => acc.concat(val), [])) | |
.enter() | |
.append('text') | |
.attr('class', 'labelunit') | |
.attr('x', d => d.x + 15) | |
.attr('y', d => d.y + 35) | |
.html(d => +d.value_second < 31536000 * 0.9 ? d.unit : null); | |
} | |
const select_years = d3.select('#select_years'); | |
select_years.append('option').attr('value', 2).text('two'); | |
select_years.append('option').attr('value', 3).text('three'); | |
select_years.append('option').attr('value', 4).text('four'); | |
select_years.append('option').attr('value', 5).text('five'); | |
select_years.on('change', function() { | |
makeChart(+this.value); | |
}); | |
select_years.node().value = 5; | |
select_years.node().dispatchEvent(new Event('change')); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment