xxxxxxxxxx
<html lang="en">
<head>
<meta charset="utf-8">
<title>Expense Ratio vs Yield</title>
<script type="text/javascript" src="https://d3js.org/d3.v3.js"></script>
<style type="text/css">
body {
background-color: white;
font-family: Helvetica, Arial, sans-serif;
}
h1 {
font-size: 24px;
margin: 0;
}
p {
font-size: 14px;
margin: 10px 0 0 0;
}
button {
background-color: orange;
}
svg {
background-color: white;
}
circle:hover {
fill: orange;
}
.axis path,
.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
.axis text {
font-family: sans-serif;
font-size: 11px;
}
</style>
</head>
<body>
<h1>Expense Ratio vs Yield</h1>
<p>Expense Ratio vs Yield for a selection of mutual funds and ETFs.</p>
<p id='legend'>Showing one-year yield.</p>
<script type="text/javascript">
var w = 700;
var h = 600;
var padding = [ 20, 10, 50, 50 ]; //Top, right, bottom, left
var xScale = d3.scale.linear()
.range([ padding[3], w - padding[1] - padding[3] ]);
var yScale = d3.scale.linear()
.range([ padding[0], h - padding[2] ]);
var colorScale = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left");
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
d3.csv("ExpenseVsYield.csv", function(data) {
xScale.domain([
d3.min(data, function(d) {
return +d.ExpenseRatio;
}),
d3.max(data, function(d) {
return +d.ExpenseRatio;
})
]);
// The domain of yScale has to be the max of all the yields
yScale.domain([
d3.max(data, function(d) {
return d3.max([+d.Y1, +d.Y3, +d.Y5, +d.Y10]);
}),
d3.min(data, function(d) {
return d3.min([+d.Y1, +d.Y3, +d.Y5, +d.Y10]);
})
]);
colorScale.domain([0, data.length-1]);
// Make a group for each stock
var groups = svg.selectAll("g")
.data(data)
.enter()
.append("g");
// A function to create a translation string for a stock
var translation = function(d, year) {
return "translate(" + xScale(d.ExpenseRatio) + "," + yScale(d[year]) + ")";
}
// A function to format title text
var titleText = function(d, year, text)
{
return d.Name + "'s expense ratio is " + d.ExpenseRatio + ", and " + text + " yield is " + d[year] + "";
}
// Locate the group
groups.attr("transform", function(d) { return translation(d, 'Y1');})
.append("title")
.text(function(d) { return titleText(d, 'Y1', 'one-year'); });
// Add a circle and a text legend for each group
groups.append('circle')
.attr("cx", 0)
.attr("cy", 0)
.attr("r", 5)
.attr("fill", function(d, i) { return colorScale(i%10); });
groups.append('text')
.attr({
x: 8,
y: 4,
class: 'label',
'font-size': '10px',
fill: function(d, i) { return colorScale(i%10); }
})
.text(function (d) {
return d.Symbol;
});
// Add the axes
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (h - padding[2] + 10) + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + (padding[3] - 10) + ",0)")
.call(yAxis);
// Add event handlers for the controls
// This is the function that will be called
yield = function(year, text)
{
// Reset text label positions
svg.selectAll('.label').attr({x: 8, y: 4});
groups.select('title')
.text(function(d) { return titleText(d, year, text); });
groups.transition(1500)
.attr("transform", function(d) { return translation(d, year);})
.attr('visibility', function(d) { return d[year]=='NA' ? 'hidden' : 'visible';})
.each('end', arrangeLabels(year));
d3.select('p#legend')
.transition(750).style('color', 'white')
.transition(750).style('color', 'black')
.text('Showing ' + text + ' yield.');
}
d3.select('button#y1').on('click', function () { yield('Y1', 'one-year'); });
d3.select('button#y3').on('click', function () { yield('Y3', 'three-year'); });
d3.select('button#y5').on('click', function () { yield('Y5', 'five-year'); });
d3.select('button#y10').on('click', function () { yield('Y10', 'ten-year'); });
// Set the initial positions
arrangeLabels('Y1');
});
// Test for intersection of two rectangles
var intersectRect = function(r1, r2) {
return !(r2.left > r1.right ||
r2.right < r1.left ||
r2.top > r1.bottom ||
r2.bottom < r1.top);
};
// Arrange the labels so they don't overlap
// From https://bl.ocks.org/larskotthoff/11406992
// and https://blog.safaribooksonline.com/2014/03/11/solving-d3-label-placement-constraint-relaxing/
function arrangeLabels(year) {
var alpha = 0.5;
// First reset them all
var textLabels = svg.selectAll('.label');
//textLabels.attr({x: 8, y: 4});
var again = false;
textLabels.each(function() {
if (this.__data__[year] == 'NaN') return;
var that = this, thatRect = this.getBoundingClientRect();
textLabels.each(function() {
if (this == that || this.__data__[year]=='NaN') return;
var thisRect = this.getBoundingClientRect();
if (!intersectRect(thisRect, thatRect)) return;
var deltaY = thatRect.top - thisRect.top;
// If the labels collide, we'll push each
// of the two labels up and down a little bit.
again = true;
var sign = deltaY > 0 ? 1 : -1;
var adjust = sign * alpha;
var dthat = d3.select(that);
dthat.attr("y", +dthat.attr('y') + adjust);
var dthis = d3.select(this);
dthis.attr("y", +dthis.attr('y') - adjust);
});
});
if(again) {
setTimeout(function() { arrangeLabels(year); },20);
}
}
</script>
<p>Click the buttons to change the displayed yield.</p>
<button class='click' id='y1'>1-Year</button> <button class='click' id='y3'>3-Year</button>
<button class='click' id='y5'>5-Year</button> <button class='click' id='y10'>10-Year</button>
</body>
</html>
Modified http://d3js.org/d3.v3.js to a secure url
https://d3js.org/d3.v3.js