Last active
February 2, 2017 23:40
-
-
Save ngchwanlii/1819318543d6630cb522366ab48ed10b to your computer and use it in GitHub Desktop.
Lab 2 - D3 Basic Bar
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
var updateData = function() { | |
// get the textarea "value" (i.e. the entered text) | |
var text = d3.select("body").select("textarea").node().value; | |
// make sure we got the right text | |
console.log(text); | |
// get letter count | |
var count = countLetters(text); | |
// some browsers support console.table() | |
try { | |
console.table(count.entries()); | |
} | |
catch (e) { | |
console.log(count); | |
} | |
return count; | |
}; | |
/* | |
* our massive function to draw a bar chart. note some stuff in here | |
* is bonus material (for transitions and updating the text) | |
*/ | |
var drawBarChart = function() { | |
// get the data to visualize | |
var count = updateData(); | |
// get the svg to draw on | |
var svg = d3.select("body").select("svg"); | |
/* | |
* we will need to map our data domain to our svg range, which | |
* means we need to calculate the min and max of our data | |
*/ | |
var countMin = 0; // always include 0 in a bar chart! | |
/* d3.documents: | |
# map.values() = Returns an array of values for every entry in this map. | |
The order of the returned values is arbitrary. | |
*/ | |
var countMax = d3.max(count.values()); // count.values() = [value1, value2, etc..] (values from each entries[key] in d3.map) | |
console.log("count bounds:", [countMin, countMax]); | |
/* | |
* before we draw, we should decide what kind of margins we | |
* want. this will be the space around the core plot area, | |
* where the tick marks and axis labels will be placed | |
* http://bl.ocks.org/mbostock/3019563 | |
*/ | |
var margin = { | |
top: 15, | |
/* original code */ | |
// right: 35, // leave space for y-axis | |
/* P1_3 new code */ | |
right: 11, // since 10 -> 25 = (15 units), deduct 35 - 24 = 11 | |
bottom: 30, // leave space for x-axis | |
/* original code */ | |
// left: 10 | |
/* P1_3 new code */ | |
left: 25 | |
}; | |
// now we can calculate how much space we have to plot | |
var bounds = svg.node().getBoundingClientRect(); // get the svg's bounding (dimension info, width, height etc..) | |
console.log(bounds) | |
var plotWidth = bounds.width - margin.right - margin.left; | |
var plotHeight = bounds.height - margin.top - margin.bottom; | |
/* | |
* okay now somehow we have to figure out how to map a count value | |
* to a bar height, decide bar widths, and figure out how to space | |
* bars for each letter along the x-axis | |
* | |
* this is where the scales in d3 come in very handy | |
* https://github.com/d3/d3-scale#api-reference | |
*/ | |
/* | |
* the counts are easiest because they are numbers and we can use | |
* a simple linear scale, but the complicating matter is the | |
* coordinate system in svgs: | |
* https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Positions | |
* | |
* so we want to map our min count (0) to the max height of the plot area | |
*/ | |
// Y scale | |
var countScale = d3.scaleLinear() | |
.domain([countMin, countMax]) // domain([d1, d2]) | |
// d1 map to range r1 | |
// Note: computer graphics formed by matrix. | |
// Ex [(0,0), (0,1)] | |
// [(1,0), (1,1)] | |
// Y axis goes from 0 (top)-> increasing number (bottom)] | |
.range([plotHeight, 0]) // range([r1, r2]) | |
.nice(); // rounds the domain a bit for nicer output | |
/* | |
* the letters need an ordinal scale instead, which is used for | |
* categorical data. we want a bar space for all letters, not just | |
* the ones we found, and spaces between bars. | |
* https://github.com/d3/d3-scale#band-scales | |
*/ | |
// X scale | |
var letterScale = d3.scaleBand() // Explain: Constructs a new band scale with the empty domain, the unit range [0, 1], no padding, no rounding and center alignment. | |
.domain(letters) | |
.rangeRound([0, plotWidth]) // explain: https://github.com/d3/d3-interpolate#interpolateRound (makes a's bar, b's bar has interpolate space in between) | |
.paddingInner(0.1); // space between bars | |
// visualize the paddingInner in https://github.com/d3/d3-scale/blob/master/README.md#scaleImplicit under Band Scales section | |
// try using these scales in the console | |
console.log("count scale [0, 36]:", [countScale(0), countScale(36)]); // countScale(0) = Given a value in the input domain, returns the start of the corresponding band derived from the output range. If the given value is not in the scale’s domain, returns undefined. | |
// visualize in https://github.com/d3/d3-scale/blob/master/README.md#scaleImplicit under Band Scales section | |
// give the value to domain (ex: bandwidth 1, bandwidth 2 etc..), return the corresponding position and range of it | |
console.log("letter scale [a, z]:", [letterScale('a'), letterScale('z')]); | |
// we are actually going to draw on the "plot area" | |
var plot = svg.select("g#plot"); // the id=plot generated by svg in live web pages under #id = plot (can inspect with javascript developer tools on web to spot this) | |
// explain: plot.size() < 1 (to let svg generate #id = plot), first time execute these so plot.size() still < 1 | |
if (plot.size() < 1) { | |
// this is the first time we called this function | |
// we need to setup the plot area | |
plot = svg.append("g").attr("id", "plot"); | |
// notice in the "elements" view we now have a g element! | |
// shift the plot area over by our margins to leave room | |
// for the x- and y-axis | |
plot.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); | |
/* IMPORTANT NOTES */ | |
// explain: plot.attr() in http://jonathansoma.com/tutorials/d3/using-attr-and-style/ | |
// transform + translate explain: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform | |
// note: the translate has same properties as how computer graphics work (matrix) translate(the margin-left, the margin-top) | |
} | |
// now lets draw our x- and y-axis | |
// these require our x (letter) and y (count) scales | |
var xAxis = d3.axisBottom(letterScale); // bottom oriented axis = X-axis <<Example below:>> | |
// _|_|_| | |
/* orignal code */ | |
// var yAxis = d3.axisRight(countScale); // right oriented axis = Y-axis <<Example below:>> | |
// |_ | |
// |_ | |
// | | |
/* P1_3 new code */ | |
/* P1_3 new code */ | |
var yAxis = d3.axisLeft(countScale); | |
// check if we have already drawn our axes, else first time, draw it in these piece of code | |
// plot.select("g#y-axis"), here can be either g#y-axis or g#x-axis, doesn't matter since plot of graph shoul come along with x & y axis | |
// this is just for checking | |
if (plot.select("g#y-axis").size() < 1) { | |
// drawig x axis | |
var xGroup = plot.append("g").attr("id", "x-axis"); | |
// the drawing is triggered by call() | |
xGroup.call(xAxis); | |
// notice it is at the top of our svg | |
// we need to translate/shift it down to the bottom | |
xGroup.attr("transform", "translate(0," + plotHeight + ")"); | |
// do the same for our y axix | |
var yGroup = plot.append("g").attr("id", "y-axis"); | |
yGroup.call(yAxis); | |
/* original code */ | |
// yGroup.attr("transform", "translate(" + plotWidth + ",0)"); | |
yGroup.attr("transform", "translate(0,0)"); | |
} | |
else { | |
/* IMPORTANT NOTES */ | |
// we need to do this so our chart updates | |
// as we type new letters in our box | |
plot.select("g#y-axis").call(yAxis); | |
} | |
// now how about some bars! | |
/* | |
* time to bind each data element to a rectangle in our visualization | |
* hence the name data-driven documents (d3) | |
*/ | |
var bars = plot.selectAll("rect") | |
.data(count.entries(), function(d) { return d.key; }); | |
// setting the "key" is important... this is how d3 will tell | |
// what is existing data, new data, or old data | |
/* | |
* okay, this is where things get weird. d3 uses an enter, update, | |
* exit pattern for dealing with data. think of it as new data, | |
* existing data, and old data. for the first time, everything is new! | |
* http://bost.ocks.org/mike/join/ | |
*/ | |
// we use the enter() selection to add new bars for new data | |
bars.enter().append("rect") | |
// we will style using css | |
.attr("class", "bar") | |
// the width of our bar is determined by our band scale | |
.attr("width", letterScale.bandwidth()) | |
// we must now map our letter to an x pixel position | |
.attr("x", function(d) { | |
return letterScale(d.key); | |
}) | |
// and do something similar for our y pixel position | |
.attr("y", function(d) { | |
return countScale(d.value); | |
}) | |
// here it gets weird again, how do we set the bar height? | |
.attr("height", function(d) { | |
return plotHeight - countScale(d.value); | |
}); | |
// notice there will not be bars created for missing letters! | |
// so what happens when we change the text? | |
// well our data changed, and there will be a new enter selection! | |
// only new letters will get new bars | |
// but we have to bind this draw function to textarea events | |
// (see index.html) | |
// for bars that already existed, we must use the update selection | |
// and then update their height accordingly | |
// we use transitions for this to avoid change blindness | |
bars.transition() | |
.attr("y", function(d) { return countScale(d.value); }) | |
.attr("height", function(d) { return plotHeight - countScale(d.value); }); | |
// what about letters that disappeared? | |
// we use the exit selection for those to remove the bars | |
bars.exit().transition() | |
.attr("y", function(d) { return countScale(countMin); }) | |
.attr("height", function(d) { return plotHeight - countScale(countMin); }) | |
.remove(); | |
}; |
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
// this file includes code for the letter count | |
// array of all lowercase letters | |
var letters = "abcdefghijklmnopqrstuvwxyz".split(""); | |
/* | |
* try this out in the console! you can access any variable or function | |
* defined globally in the console | |
* | |
* and, you can right-click output in the console to make it global too! | |
*/ | |
/* | |
* removes any character (including spaces) that is not a letter | |
* and converts all remaining letters to lowercase | |
*/ | |
var onlyLetters = function(text) { | |
// there are multiple ways to define a function in javascript! | |
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions | |
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp | |
var notLetter = /[^a-z]/g; | |
return text.toLowerCase().replace(notLetter, ""); | |
}; | |
// in console try: onlyLetters("hello world!"); | |
/* | |
* counts all of the letters in the input text and stores the counts as | |
* a d3 map object | |
* https://github.com/d3/d3-collection/blob/master/README.md#map | |
*/ | |
var countLetters = function(input) { | |
var text = onlyLetters(input); | |
var count = d3.map(); | |
/* | |
* you can loop through strings as if they are arrays | |
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for | |
*/ | |
for (var i = 0; i < text.length; i++) { | |
var letter = text[i]; | |
// check if we have seen this letter before | |
if (count.has(letter)) { | |
count.set(letter, count.get(letter) + 1); | |
} | |
else { | |
count.set(letter, 1); | |
} | |
} | |
return count; | |
}; | |
// in console try: countLetters("hello world!"); | |
/* result | |
Fe | |
$d: 1 | |
$e: 1 | |
$h: 1 | |
$l: 3 | |
$o: 2 | |
$r: 1 | |
$w: 1 | |
*/ | |
// in console try: countLetters("hello world!").keys(); result = ["h", "e", "l", "o", "w", "r", "d"] | |
// in console try: countLetters("hello world!").entries(); | |
/* result => entries = {key: value} pair, here we got 7 object, each object inside contain key-value pair | |
console output = [Object, Object, Object, Object, Object, Object, Object] | |
Object | |
|_ key: "h" | |
|_ value: 1 | |
*/ |
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> | |
<!-- we are using html 5 --> | |
<head> | |
<meta charset="utf-8"> | |
<title>Letter Count Bar Chart</title> | |
<!-- this allows us to use the non-standard Roboto web font --> | |
<link href="https://fonts.googleapis.com/css?family=Roboto:300,300italic" rel="stylesheet" type="text/css"> | |
<!-- this is our custom css stylesheet --> | |
<link href="style.css" rel="stylesheet" type="text/css"> | |
</head> | |
<body> | |
<!-- we will place our visualization in this svg using d3.js --> | |
<svg></svg> | |
<!-- we will place the text to analyze here using javascript --> | |
<textarea></textarea> | |
<!-- include d3.js --> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<!-- include custom javascript --> | |
<script src="count.js"></script> | |
<script src="chart.js"></script> | |
<!-- here is our core javascript --> | |
<script type="text/javascript"> | |
// inside the script tag, // and /* */ are comments | |
// outside the script tag, <!-- --> are comments | |
// we need to load the text file into the textarea | |
// this will be done asynchronously! | |
// https://github.com/d3/d3-request/blob/master/README.md#text | |
d3.text("peter.txt", function(error, data) { | |
// we are creating a function within a method call here | |
if (error) throw error; | |
// we will use the console and developer tools extensively | |
console.log(data); | |
// now we select the textarea from the DOM and update | |
// https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model | |
// https://github.com/d3/d3-selection/blob/master/README.md#select | |
d3.select("body").select("textarea").text(data); | |
// updateData(); | |
drawBarChart(); | |
}); | |
// this message will appear BEFORE the text is logged! | |
console.log("after d3.text() call"); | |
// add an event listener to our text area and | |
// update our chart every time new data is entered | |
d3.select("body").select("textarea") | |
.on("keyup", drawBarChart); | |
</script> | |
</body> |
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
Peter Piper picked a peck of pickled peppers. | |
A peck of pickled peppers Peter Piper picked. | |
If Peter Piper picked a peck of pickled peppers, | |
Where's the peck of pickled peppers that Peter Piper picked? |
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
/* | |
* we use css to style our page and our svg elements | |
* the classes/ids defined here must match our d3 code | |
*/ | |
body, textarea { | |
font-family: 'Roboto', sans-serif; | |
font-weight: 300; | |
font-size: 11pt; | |
} | |
body { | |
margin: 5px; | |
padding: 0px; | |
/* see https://developer.mozilla.org/en-US/docs/Web/CSS/color_value */ | |
background-color: whitesmoke; | |
} | |
textarea { | |
/* position the textarea on top of the svg */ | |
position: fixed; | |
top: 5px; | |
/* P1_2: update textarea to right corner of svg */ | |
/*adjust pixel: 950(svg width) - 400(textarea's width) = 550px */ | |
left: 545px; | |
margin: 0px; | |
padding: 5px; | |
width: 400px; | |
height: 75px; | |
/* try changing this color in blockbuilder! */ | |
background-color: rgba(255, 255, 255, 0.8); | |
} | |
textarea, svg { | |
border: 1px solid gainsboro; | |
border-radius: 10px; | |
} | |
svg { | |
/* bl.ocks.org defaults to 960px by 500px */ | |
width: 950px; | |
height: 490px; | |
background-color: white; | |
} | |
/* svg elements are styled differently from html elements */ | |
rect.bar { | |
stroke: none; | |
/* P1_1 */ | |
fill: blue; | |
} | |
#x-axis text, | |
#y-axis text { | |
font-size: 10pt; | |
fill: #888888; | |
} | |
#x-axis line { | |
/* tick marks */ | |
fill: none; | |
stroke: none; | |
} | |
#x-axis path, | |
#y-axis path, | |
#y-axis line { | |
fill: none; | |
stroke: #bbbbbb; | |
stroke-width: 1px; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
updated left margin