Built with blockbuilder.org
xxxxxxxxxx
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-annotation/1.12.1/d3-annotation.min.js"></script>
<style>
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
.overlay {
fill: none;
pointer-events: all;
}
.focus circle {
fill: none;
stroke: steelblue;
}
.hover-line {
stroke: #6F257F;
stroke-width: 2px;
stroke-dasharray: 3,3;
}
.annotation path {
stroke: #E8336D;
fill: none;
}
.annotation path.connector-arrow,
.annotation path.connector-dot,
.title text, .annotation text {
fill: #E8336D;
}
.annotation-note-bg {
fill: rgba(0, 0, 0, 0);
}
.annotation-note-title, text.title {
font-weight: bold;
}
text.title {
font-size: 1.2em
}
.badge path {
stroke: #00cc66;
stroke-width: 3px;
stroke-linecap: round;
}
.annotation.badge path.subject-pointer, .annotation.badge path.subject, .annotation.badge path.subject-ring {
fill: #00cc66;
stroke-width: 3px;
stroke-linecap: round;
}
.annotation.badge path.subject-ring {
fill: white;
stroke-width: 3px;
}
.annotation.badge .badge-text {
fill: white;
font-size: .7em;
}
text.hover {
font-size: 1em;
fill: black;
font-weight: bold;
stroke: green;
}
</style>
</head>
<body>
<script>
// Source: https://www.eia.gov/outlooks/steo/realprices/
var margin = {top: 20, right: 50, bottom: 30, left: 50},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom
var parseTime = d3.timeParse("%B %Y"),
formatTime = d3.timeFormat("%b %Y"),
bisectDate = d3.bisector(function(d) {return d.Month;}).left,
formatValue = d3.format(",.2f"),
formatCurrency = function(d){ return "$" + formatValue(d)};
var svg = d3.select("body").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 + ")");
var x = d3.scaleTime()
.range([0, width]);
var y = d3.scaleLinear()
.range([height, 0]);
var line = d3.line()
.x(function(d) { return x(d.Month); })
.y(function(d) { return y(d.Real_Imported_Crude_Oil_Price); });
d3.csv("real_oil_prices.csv", function(d){
d.Month = parseTime(d.Month);
d.Real_Imported_Crude_Oil_Price = parseInt(d.Real_Imported_Crude_Oil_Price);
d.Nominal_Imported_Crude_Oil_Price = parseInt(d.Nominal_Imported_Crude_Oil_Price);
return d;
}, function(error, data) {
console.log(data);
x.domain(d3.extent(data, function(d) { return d.Month; }));
y.domain(d3.extent(data, function(d) { return d.Real_Imported_Crude_Oil_Price; }));
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
.select(".domain")
.remove();
svg.append("g")
.call(d3.axisLeft(y))
.append("text")
.attr("fill", "#000")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", "0.71em")
.attr("text-anchor", "end")
.text("Real Imported Crude Oil Price ($)");
svg.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke-width", 1.5)
.attr("d", line);
var focus = svg.append("g")
.attr("class", "focus")
.style("display", "none");
focus.append("line")
.attr("class", "x-hover-line hover-line")
.attr("y1", 0)
.attr("y2", height);
focus.append("line")
.attr("class", "y-hover-line hover-line")
.attr("x1", width)
.attr("x2", width);
focus.append("circle")
.attr("r", 4.5);
focus.append("text")
.attr("x", 9)
.attr("dy", ".31em");
svg.append("rect")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height)
.on("mouseover", function() { focus.style("display", null); })
.on("mouseout", function() { focus.style("display", "none"); })
.on("mousemove", mousemove);
function mousemove() {
var x0 = x.invert(d3.mouse(this)[0]),
i = bisectDate(data, x0, 1),
d0 = data[i - 1],
d1 = data[i],
d = x0 - d0.date > d1.date - x0 ? d1 : d0;
focus.attr("transform", "translate(" + x(d.Month) + "," + y(d.Real_Imported_Crude_Oil_Price) + ")");
focus.select("text").text(formatCurrency(d.Real_Imported_Crude_Oil_Price) + " " + formatTime(d.Month));
focus.select(".x-hover-line").attr("y2", height - y(d.Real_Imported_Crude_Oil_Price));
focus.select(".y-hover-line").attr("x2", width + width)
}
const annotations = [
{
type: d3.annotationLabel,
note: {
label: "Iraq invades Kuwait. Kuwaiti exports cut until 1994",
title: "1991 Gulf War",
wrap: 140
},
x: 372,
y: 302,
dy: -20,
dx: 20
},{
//below in makeAnnotations has type set to d3.annotationLabel
//you can add this type value below to override that default
type: d3.annotationCalloutCircle,
note: {
label: "Exports from region slow; U.S. flags oil tankers to protect flow of oil in Persian Gulf.",
title: "1980-88 Iran-Iraq War",
wrap: 150
},
//settings for the subject, in this case the circle radius
subject: {
radius: 110
},
x: 208,
y: 227,
dy: -90,
dx: 150
},
{
type: d3.annotationLabel,
note: {
title: "2001 - 2003",
label: "9/11 and the invasion of Iraq. Venezuelan oil workers strike",
wrap: 140
},
x: 550,
y: 280
},
{
type: d3.annotationLabel,
note: {
title: "2007-2008 Global Financial Crisis",
wrap: 150
},
x: 720,
y: 381,
dy: 20,
dx: 0
},
{
type: d3.annotationLabel,
note: {
title: "Mid-2000s",
label: "Asia drives rising demand as production stagnates and Saudi spare capacity declines",
wrap: 140
},
x: 635,
y: 40,
},
{
type: d3.annotationLabel,
note: {
title: "2011 Arab Spring",
label: "Civil war disrupts Libyan output",
wrap: 150
},
x: 820,
y: 20,
},
{
type: d3.annotationBadge,
//can use x, y directly instead of data
// data: { Month: parseTime("June 1980"), Real_Imported_Crude_Oil_Price: 102.5125925 },
note: {
title: "1980 Energy Security Act",
label: "",
wrap: 150
},
x: 170,
y: 160,
dy: 137,
dx: 162,
subject: {
text: "A",
radius: 14
}
},
{
type: d3.annotationBadge,
//can use x, y directly instead of data
// data: { Month: parseTime("June 1980"), Real_Imported_Crude_Oil_Price: 102.5125925 },
note: {
title: "1992 Energy Security Act",
label: "",
wrap: 150
},
x: 407,
y: 397,
dy: 400,
dx: 400,
subject: {
text: "B",
radius: 14,
y: "top",
x: "right"
}
}, {
type: d3.annotationBadge,
//can use x, y directly instead of data
// data: { Month: parseTime("June 1980"), Real_Imported_Crude_Oil_Price: 102.5125925 },
note: {
title: "2005 Energy Security Act",
label: "",
wrap: 150
},
x: 650,
y: 295,
dy: 400,
dx: 400,
subject: {
text: "C",
radius: 14
}
},
{
type: d3.annotationBadge,
//can use x, y directly instead of data
// data: { Month: parseTime("June 1980"), Real_Imported_Crude_Oil_Price: 102.5125925 },
note: {
title: "2007 Energy Independence and Security Act",
label: "",
wrap: 150
},
x: 695,
y: 180,
dy: 400,
dx: 400,
subject: {
text: "D",
radius: 14,
y: "bottom"
}
}, {
type: d3.annotationBadge,
//can use x, y directly instead of data
// data: { Month: parseTime("June 1980"), Real_Imported_Crude_Oil_Price: 102.5125925 },
note: {
title: "2008 Energy and Tax Extenders Act",
label: "",
wrap: 150
},
x: 715,
y: 260,
dy: 400,
dx: 400,
subject: {
text: "E",
radius: 14,
y: "bottom"
}
},
{
type: d3.annotationBadge,
note: {
title: "2009 American Recovery and Reinvestment Act",
label: "",
wrap: 150
},
x: 725,
y: 355,
dy: 400,
dx: 400,
subject: {
text: "F",
radius: 14,
y: "bottom",
x: "right"
}
}
]
// const type = d3.annotationBadge;
// const makeAnnotations = d3.annotation()
// .type(type)
// //accessors & accessorsInverse not needed
// //if using x, y in annotations JSON
// .accessors({
// x: d => x(d.Month),
// y: d => y(d.Real_Imported_Crude_Oil_Price)
// })
// .accessorsInverse({
// Month: d => formatTime(x.invert(d.x)),
// Real_Imported_Crude_Oil_Price: d => y.invert(d.y)
// })
// .annotations(annotations)
const makeAnnotations = d3.annotation()
.annotations(annotations)
.on('subjectover', function (annotation) {
//cannot reference this if you are using es6 function syntax
this.append('text')
.attr('class', 'hover')
.text(annotation.note.title)
.attr('text-anchor', 'middle')
.attr('y', annotation.subject.y && annotation.subject.y == "bottom" ? 50 : -40)
.attr('x', -15);
this.append('text')
.attr('class', 'hover')
.text(annotation.note.label)
.attr('text-anchor', 'middle')
.attr('y', annotation.subject.y && annotation.subject.y == "bottom" ? 70 : -60)
.attr('x', -15);
}).on('subjectout', function (annotation) {
this.selectAll('text.hover').remove();
});
d3.select("svg")
.append("g")
.attr("class", "annotation-group")
.call(makeAnnotations)
});
</script>
</body>
<!-- https://en.wikipedia.org/wiki/List_of_United_States_energy_acts -->
<!-- https://www.zerohedge.com/sites/default/files/images/user3303/imageroot/2014/07/20140722_oilprice1.jpg -->
https://d3js.org/d3.v4.min.js
https://cdnjs.cloudflare.com/ajax/libs/d3-annotation/1.12.1/d3-annotation.min.js