Skip to content

Instantly share code, notes, and snippets.

@hobbes7878
Last active February 12, 2021 16:31
Show Gist options
  • Save hobbes7878/d315122a1759cba1f41b to your computer and use it in GitHub Desktop.
Save hobbes7878/d315122a1759cba1f41b to your computer and use it in GitHub Desktop.
#NICAR15: D3 stock chart

An simple multi-line chart that pivots from raw values to percent change using separate CSVs.

<!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>
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", "+%" );
});
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment