An simple multi-line chart that pivots from raw values to percent change using separate CSVs.
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", "+%" );
});
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Scatterplot Pivot</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.2/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.2/css/bootstrap.css">
</head>
<body>
<div id="chart">
<h1>Stock History</h1>
<h4>News Corp. (NWS) & A. H. Belo Corp. (AHC)</h4>
<div class="btn-group" data-toggle="buttons">
<label id="priceBtn" class="btn btn-primary active">
<input type="radio" name="options" autocomplete="off" checked>Weekly Price
</label>
<label id="changeBtn" class="btn btn-primary">
<input type="radio" name="options" autocomplete="off">Weekly Change
</label>
</div>
</div>
<link rel="stylesheet" type="text/css" href="style.css">
<script src="script.js"></script>
</body>
</html>
body {
font: 10px arial, sans-serif;
}
.btn-group{
float:right;
}
#chart{
width:700px;
margin-top: 20px;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
}
.zeroAxis{
stroke:black;
shape-rendering: crispEdges;
}
.labels text{
font-size: 16px;
font-weight: bold;
fill:white;
}
date,AHC,NWS
2015-02-23,-0.0125,0.0089982004
2015-02-17,-0.0361445783,0.0158439976
2015-02-09,-0.0097613883,0.0405833862
2015-02-02,0.0313199105,0.0921052632
2015-01-26,-0.0407725322,-0.0150068213
2015-01-20,-0.0371900826,0.0082530949
2015-01-12,-0.0638297872,-0.0228494624
2015-01-05,0.0009680542,-0.0066755674
2014-12-29,-0.0254716981,-0.0039893617
2014-12-22,0.021194605,0.0308430432
2014-12-15,0.0318091451,-0.0040955631
2014-12-08,-0.0098425197,-0.0380827315
2014-12-01,0.0314720812,0.0119601329
2014-11-24,0.0478723404,0.0148347943
2014-11-17,-0.009483667,-0.0100133511
2014-11-10,0.0042328042,0.0094339623
2014-11-03,0.0074626866,-0.0139534884
2014-10-27,-0.0010649627,-0.0176240209
2014-10-20,0.0184381779,0.0743338008
2014-10-13,0.0720930233,-0.0474281897
2014-10-06,-0.0486725664,-0.0501269036
2014-09-29,0.0145903479,-0.0523150932
2014-09-22,-0.0011210762,-0.0124703088
2014-09-15,0.0194285714,0.0029779631
2014-09-08,-0.0621650589,-0.0345025877
2014-09-02,-0.0063897764,0.0075318656
2014-08-25,0.0184381779,0.0070011669
2014-08-18,0.0076502732,0.0196311719
2014-08-11,0.0021905805,0.0035820896
2014-08-04,0.0830367734,-0.0351382488
2014-07-28,-0.061247216,-0.0141964793
2014-07-21,-0.0228509249,-0.002266289
2014-07-14,-0.0086299892,0.0051252847
2014-07-07,-0.050204918,-0.0118176702
2014-06-30,0.0051493306,0.0125356125
2014-06-23,0.0440860215,0.0257159556
2014-06-16,-0.01378579,0.022713688
2014-06-09,-0.0298353909,-0.031268095
2014-06-02,0.0199370409,0.0409885473
2014-05-27,0.0449561404,-0.0089605735
2014-05-19,0.0961538462,0.0023952096
2014-05-12,0.0060459492,-0.0402298851
2014-05-05,0.046835443,0.0338680927
2014-04-28,-0.0050377834,0.0029797378
2014-04-21,0.0298313878,0.0188221008
2014-04-14,-0.006443299,0.0129151292
2014-04-07,-0.0064020487,-0.026929982
2014-03-31,-0.0533333333,0.0102781137
2014-03-24,-0.0873893805,-0.0276308054
2014-03-17,0.0261066969,-0.0023460411
2014-03-10,0.0704738761,-0.027936146
2014-03-03,0.0510855683,-0.0195640022
2014-02-24,0.0711354309,0.0389082462
2014-02-18,0.0781710914,0
2014-02-10,0.2372262774,0.0129411765
2014-02-03,-0.0231729055,0.0890454837
2014-01-27,0.0181488203,-0.0334365325
2014-01-21,-0.0247787611,-0.0511163337
2014-01-13,0.0721062619,-0.0184544406
2014-01-06,-0.0037807183,-0.0219966159
2014-01-02,0,0
date,AHC,NWS
2015-02-23,8.69,16.82
2015-02-17,8.8,16.67
2015-02-09,9.13,16.41
2015-02-02,9.22,15.77
2015-01-26,8.94,14.44
2015-01-20,9.32,14.66
2015-01-12,9.68,14.54
2015-01-05,10.34,14.88
2014-12-29,10.33,14.98
2014-12-22,10.6,15.04
2014-12-15,10.38,14.59
2014-12-08,10.06,14.65
2014-12-01,10.16,15.23
2014-11-24,9.85,15.05
2014-11-17,9.4,14.83
2014-11-10,9.49,14.98
2014-11-03,9.45,14.84
2014-10-27,9.38,15.05
2014-10-20,9.39,15.32
2014-10-13,9.22,14.26
2014-10-06,8.6,14.97
2014-09-29,9.04,15.76
2014-09-22,8.91,16.63
2014-09-15,8.92,16.84
2014-09-08,8.75,16.79
2014-09-02,9.33,17.39
2014-08-25,9.39,17.26
2014-08-18,9.22,17.14
2014-08-11,9.15,16.81
2014-08-04,9.13,16.75
2014-07-28,8.43,17.36
2014-07-21,8.98,17.61
2014-07-14,9.19,17.65
2014-07-07,9.27,17.56
2014-06-30,9.76,17.77
2014-06-23,9.71,17.55
2014-06-16,9.3,17.11
2014-06-09,9.43,16.73
2014-06-02,9.72,17.27
2014-05-27,9.53,16.59
2014-05-19,9.12,16.74
2014-05-12,8.32,16.7
2014-05-05,8.27,17.4
2014-04-28,7.9,16.83
2014-04-21,7.94,16.78
2014-04-14,7.71,16.47
2014-04-07,7.76,16.26
2014-03-31,7.81,16.71
2014-03-24,8.25,16.54
2014-03-17,9.04,17.01
2014-03-10,8.81,17.05
2014-03-03,8.23,17.54
2014-02-24,7.83,17.89
2014-02-18,7.31,17.22
2014-02-10,6.78,17.22
2014-02-03,5.48,17
2014-01-27,5.61,15.61
2014-01-21,5.51,16.15
2014-01-13,5.65,17.02
2014-01-06,5.27,17.34
2014-01-02,5.29,17.73