var margin = {top: 20, right: 20, bottom: 35, left: 50}, width = 700 - margin.left - margin.right, height = 300 - margin.top - margin.bottom; var parseDate = d3.time.format("%Y-%m-%d").parse; var x = d3.time.scale() .range([0, width]); var y = d3.scale.linear() .range([height, 0]); // Our style for this chart is to display 4-digit year for January and then 3-letter // abbreviation for every other month. We can use d3's multi formatter to give us mixed // formatting. Check out the docs for this one: // https://github.com/mbostock/d3/wiki/Time-Formatting#format_multi var xAxisFormat = d3.time.format.multi([ ["%b.", function(d) { return d.getMonth(); }], ["%Y", function() { return true; }] ]); var xAxis = d3.svg.axis() .scale(x) .tickFormat( xAxisFormat ) .orient("bottom"); var yAxis = d3.svg.axis() .scale(y) .orient("left"); // Color scales work just like other d3 scales in that they map // a domain of data values to a range of discrete colors. // https://github.com/mbostock/d3/wiki/Ordinal-Scales#category10 var color = d3.scale.category10(); var line = d3.svg.line() // We're going to "interpolate"/draw a "spline"/curve which will smooth the line a bit. // A "cardinal" spline gives us a little smoothing without diminishing // our peaks and troughs. // https://github.com/mbostock/d3/wiki/SVG-Shapes#line_interpolate .interpolate("cardinal") .x(function(d) { return x(d.date); }) .y(function(d) { return y(d.price); }); var svg = d3.select("#chart").append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height + ")"); svg.append("g") .attr("class", "y axis"); // An axis line at zero on the Y axis. svg.append("line") .attr("class","zeroAxis"); svg.append("text") .text("SOURCE: Yahoo! Finance") .attr({ class: "source", x:0, y:height + 30 }); // Whenever we want to make a d3 chart update with new data, we need to put the // elements of our chart that are going to change with the new data into a function. // That way we can simply call it to redraw these elements. // In our case, those elements will be the axes, lines and labels, all of which depend // our data. function draw(dataFile, axisFormat){ // Because our data is changeing type (from a share price in dollars to a percentage) // we need to reformat our yAxis. In our draw function we're passing a d3 format string: // https://github.com/mbostock/d3/wiki/Formatting yAxis.tickFormat(d3.format(axisFormat)); // d3 has special methods for dealing with data in comma-delimited files, CSVs. // In this case, we have two CSVs with different data but which we want to share // the same chart, so we're passing the filename as an argument to the draw func. // https://github.com/mbostock/d3/wiki/CSV d3.csv(dataFile, function(error, data) { // Our color scale domain is going to be the values in the header row of our CSV, // excluding the "date" column. color.domain( d3.keys( data[0] ).filter( function(key) { return key !== "date"; }) ); data.forEach(function(d) { d.date = parseDate(d.date); }); // Since we'll have multiple companies in our data, we need to create a data array // that has multiple objects, one for AHC and one for NWS. We'll use javascript's map // function to relate all the price and date data to their respective companies. var companies = color.domain().map(function(name) { return { name: name, values: data.map(function(d) { return {date: d.date, price: +d[name]}; }) }; }); /* After we're done the companies array looks like this: companies = [ {name: "AHC" values: [ {date: Mon Feb 23 2015 00:00:00 GMT-0600 (CST) price: 8.69 }, etc... ] }, {name: "NWS" values: [ {date: Mon Feb 23 2015 00:00:00 GMT-0600 (CST) price: 16.82 }, etc... ] }, ] */ // You can print the companies data to the console and take a look. console.log(companies); x.domain(d3.extent(data, function(d) { return d.date; })); // To get our Y domain, we'll take the min/max of each price for each company object in the companies array // and then take the final min/max of all those mins/maxs. y.domain([ d3.min(companies, function(company) { return d3.min(company.values, function(value) { return value.price; }); }), d3.max(companies, function(company) { return d3.max(company.values, function(value) { return value.price; }); }) ]); // Update our zero axis. svg.select(".zeroAxis") .transition().duration(1000) .attr({ x1:0, x2:width, y1:y(0), y2:y(0) }); // Company lines // JOIN var company = svg.selectAll(".company") .data(companies); // ENTER company.enter().append("path") .attr("class", "company line") .style("stroke", function(d) { return color(d.name); }); // UPDATE company .transition().duration(1000) .attr("d", function(d) { return line(d.values); }); // EXIT ??? Nope, won't need it. We'll always be dealing with the same lines. No need // to remove anything. // D3 makes updating our axes REALLY easy. All we do is select the axis and call them again. svg.select(".x.axis") .transition().duration(1000) .call(xAxis); svg.select(".y.axis") .transition().duration(1000) .call(yAxis); //Technically we don't need to follow the update pattern for our labels in this chart // since we know what two companies are in our data. But we'll do it anyway, that way // we can easily reuse this chart for any two other companies! var labels = svg.selectAll(".labels") // Data is the array of headers in our CSV, excluding the date column. // Helpfully, we already have that array. It's our color domain! .data(color.domain()); labels.enter() .append("g") .attr("class", "labels"); labels.append("rect") .attr({ fill: function(d){return color(d);}, height: 20, width: 42, // A little math to automatically place our labeling. // Remember, "i" is the index number of the data element "d". // We can use it to space our labels! x: function(d, i){return width - 23 - (42 * i) ;}, y: -10 }); labels.append("text") .text(function(d){return d;}) .attr({ x: function(d, i){return width - 20 - (42 * i) ;}, y: 5, }); }); } // Draw the chart when the page loads. draw("wk_prices.csv","$"); // Bind the draw function to our two buttons with the correct arguments. $("#priceBtn").click(function(){ draw("wk_prices.csv", "$" ); }); $("#changeBtn").click(function(){ draw("wk_changes.csv", "+%" ); });