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 <> // _|_|_| /* orignal code */ // var yAxis = d3.axisRight(countScale); // right oriented axis = Y-axis <> // |_ // |_ // | /* 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(); };