Skip to content

Instantly share code, notes, and snippets.

@ngchwanlii
Last active February 2, 2017 23:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ngchwanlii/1819318543d6630cb522366ab48ed10b to your computer and use it in GitHub Desktop.
Save ngchwanlii/1819318543d6630cb522366ab48ed10b to your computer and use it in GitHub Desktop.
Lab 2 - D3 Basic Bar

Project Description: Letter Count Bar Chart

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 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
*/
<!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>
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?
/*
* 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;
}
@ngchwanlii
Copy link
Author

updated left margin

@ngchwanlii
Copy link
Author

updated title

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment