Skip to content

Instantly share code, notes, and snippets.

@coppeliaMLA
Last active October 5, 2017 17:22
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save coppeliaMLA/8bea372483ab8878acea to your computer and use it in GitHub Desktop.
Save coppeliaMLA/8bea372483ab8878acea to your computer and use it in GitHub Desktop.
ROI Fan

Return on investment is often measured as revenue divided by costs, sometimes expressed as a percentage. For example if a marketing campaign cost £10K but brought in £20K of additional revenue then the ROI is 200%. Now if you are just given the ROI you'll find you are missing any of idea of scale. The same ROI could be achieved with a revenue of £200 and with one of £200 million. So it would be nice to see cost, revenue and ROI visualised all in one go. There are a few ways to do this but after playing around I came up with the following representation which personally I like the best. It's a simple scatterplot of cost against revenue but since all points on straight lines radiating from the origin have the same ROI it's easy to overlay that information. If r is the ROI the the angle of the corresponding spoke is arctan(r).

Note you can drag about the labels. That's my preferred solution for messy scatterplot labelling.

<!DOCTYPE html>
<meta charset="utf-8">
<div class="fan"></div>
<style>
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
width: 960px;
height: 500px;
position: relative;
}
.label {
font: 9px sans-serif;
fill: black;
}
text {
font: 10px sans-serif;
}
.spoke {
fill: none;
stroke: #999;
shape-rendering: crispEdges;
stroke-width: 1;
stroke-dasharray: 4, 2;
}
.axis path, .arc, .line {
fill: none;
stroke: #999;
shape-rendering: crispEdges;
stroke-width: 1;
}
circle {
fill: #1ABC9C;
}
</style>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
//Add data
var dataset = [
[100, 235, "Microtech"],
[345, 120, "Datalab"],
[65, 420, "Nanomesh"],
[110, 112, "Squilch"],
[701, 77, "Red Mango"],
[203, 303, "Blue Gerbil"]
];
//Set constants
var w = 500,
padding = 50;
//Create adaptable scales
var maxDist = d3.max(dataset, function (d) {
return Math.sqrt(Math.pow(d[0], 2) + Math.pow(d[1], 2));
})
var xScale = d3.scale.linear()
.domain([0, maxDist + 100])
.range([padding, w - padding]);
var yScale = d3.scale.linear()
.domain([0, maxDist + 50])
.range([w - padding, padding]);
//Set up the svg
var svg = d3.select(".fan")
.append("svg")
.attr("width", w)
.attr("height", w);
//Set up dragging
var drag = d3.behavior.drag().on("drag", dragmove);
function dragmove(d) {
d3.select(this)
.attr("x", d.x = d3.event.x)
.attr("y", d.y = d3.event.y)
.attr("dx", 0).attr("dy", 0);
}
//Draw an arc
var radius = w - 2 * padding,
radians = Math.PI / 2
m = w - padding;
var points = 50;
var angle = d3.scale.linear()
.domain([0, points - 1])
.range([0, radians]);
var arc = d3.svg.line.radial()
.interpolate("basis")
.tension(0)
.radius(radius)
.angle(function (d, i) {
return angle(i);
});
svg.append("path").datum(d3.range(points)).attr("class", "arc").attr("d", arc).attr("transform", "translate(" + padding + "," + m + ")");
//Calculate the angles for each ROI
roi = [0.1, 0.25, 0.5, 0.75, 1, 1.5, 2, 3, 4, 5, 10, 50];
thetas = roi.map(function (x) {
return 180 / Math.PI * Math.atan(x)
});
//Add in the spokes
var ga = svg.selectAll(".spokegroup")
.data(thetas)
.enter().append("g")
.attr('class', 'spokegroup')
.attr("transform", function (d) {
return "translate(" + padding + "," + m + ") rotate(" + -d + ")";
});
ga.append("line")
.attr("class", "spoke")
.attr("x2", radius);
//Add in the labels
ga.append("text")
.attr("x", radius + 6)
.attr("dy", ".35em")
.style("text-anchor", function (d) {
return "rotate(180 " + (radius + 6) + ",0)";
})
.text(function (d) {
return Math.round(Math.tan(Math.PI / 180 * d) * 100) + "%";
});
//Add in circles for each data point
svg.selectAll(".node")
.data(dataset)
.enter()
.append("g")
.attr("class", "node")
.append("circle")
.attr("cx", function (d) {
return xScale(d[0]);
})
.attr("cy", function (d) {
return yScale(d[1]);
})
.attr("r", 5);
//Add in the labels
svg.selectAll(".node").append("text").attr("class", "label")
.attr("x", function (d) {
return xScale(d[0]);
})
.attr("y", function (d) {
return yScale(d[1]);
})
.attr("dx", 10).attr("dy", 10)
.text(function (d) {
return d[2]
});
d3.selectAll(".label").call(drag);
//Set up the axes
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left");
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (w - padding) + ")")
.call(xAxis);
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + padding + ",0)")
.call(yAxis);
//Finally some axis labels
svg.append("text")
.attr("class", "xlabel")
.attr("text-anchor", "end")
.attr("x", w / 2)
.attr("y", w - 10)
.text("Cost");
svg.append("text")
.attr("class", "ylabel")
.attr("text-anchor", "middle")
.attr("x", -w / 2)
.attr("y", 0)
.attr("dy", ".75em")
.attr("transform", "rotate(-90)")
.text("Revenue");
svg.append("text")
.attr("class", "zlabel")
.attr("text-anchor", "middle")
.attr("x", radius + 100)
.attr("y", 0)
.attr("dy", ".75em")
.attr("transform", "translate(30, 470) rotate(-45 0 0) ")
.text("ROI");
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment