Skip to content

Instantly share code, notes, and snippets.

@puzzler10
Last active May 30, 2016 10:36
Show Gist options
  • Save puzzler10/8b334714b484c61703c1eae3e674ded4 to your computer and use it in GitHub Desktop.
Save puzzler10/8b334714b484c61703c1eae3e674ded4 to your computer and use it in GitHub Desktop.
Visualising Net Promoter Score (NPS)

Let's say you're a restaurant owner, and you want to see exactly how important good table service is to your business. If you use the NPS system, you will have a number of reviews (possibly many if you own multiple restaruants) with ratings between 0 and 10 and comments.

You categorise each review into a number of predefined categories - good/bad table service, good/bad food, good/bad atmosphere , etc. If you categorise enough of these reviews, then you could fit a multiple linear regression model to quantify exactly just how much good table service affects your NPS score.

This is one way you could visualise these results. You can interpret the above pretty easily - good service adds 30.8 points to your NPS, while a meal that isn't good will take away 45.2 NPS points. You can quickly see how important each factor is.

Note that the values and the categories should not be taken literally - they were simply made up for illustration purposes.

See http://www.puzzlr.org/ for more examples.

<!DOCTYPE html>
<meta charset="utf-8">
<html>
<head>
<script src="//d3js.org/d3.v3.min.js"></script>
<style>
line {
stroke-width: 5px;
}
.label {
font-family: sans-serif;
font-style: normal;
font-weight: normal;
font-size: 1em;
}
.axis{
font-weight: bold;
}
</style>
</head>
<body>
<script>
var svgHeight = 700
var svgWidth = 950
var offset = 13
var svg = d3.select("body").append("svg")
.attr("height", svgHeight)
.attr("width", svgWidth);
var data = [
{'y': 40 , 'color': '#FF0000', 'pos': 30.8 , "neg": -51.3, "label": "table_service", "leftlabel": "poor service", "rightlabel": "good service" },
{'y': 140 , 'color': '#0000CC', 'pos': 50.1 , "neg": -45.1, "label": "speed", "leftlabel": "slow", "rightlabel": "quick" }, //blue
{'y': 240 , 'color': '#006600', 'pos': 30.3 , "neg": -45.2, "label": "taste", "leftlabel": "not tasty", "rightlabel":"tasty"}, //green
{'y': 340 , 'color': '#FF6600', 'pos': 28.6 , "neg": 0, "label": "simple", "leftlabel":"","rightlabel": "good atmosphere"}, //orange
{'y': 440 , 'color': '#FF44FF', 'pos': 22.3 , "neg": 0, "label": "personal", "leftlabel": "", "rightlabel": "personal service"}, //pink
{'y': 540 , 'color': '#9900FF', 'pos': 0 , "neg": -24.9, "label": "updates", "leftlabel": "too many updates", "rightlabel":""} //purple
]
var min = Math.min.apply(Math,data.map(function(d){return d.neg;}))
var max = Math.max.apply(Math,data.map(function(d){return d.pos;}))
var xScale = d3.scale.linear()
.domain([min, max])
.range([100,svgWidth-100]);
;
var line = svg.selectAll("line")
.data(data)
.enter()
.append("line")
.each(function (d) {
d3.select(this).attr({
x1: xScale(d.neg),
x2: xScale(d.pos),
y1: d.y,
y2: d.y,
stroke: d.color,
label : d.label
})
});
//axis
svg.append("line")
.attr("x1", xScale(0))
.attr("x2", xScale(0))
.attr("y1", 0)
.attr("y2", 600)
.style("stroke", "grey")
.style("stroke-width", "3px");
//axis label
svg.append("text")
.attr("class","axis label")
.attr("x", xScale(0) + 10)
.attr("y", 20)
.style("stroke", "9900FF")
.text("0")
var pts = svg.selectAll("circle")
.data(data)
.enter()
//Left points
pts.append("circle")
.attr("class", "leftcircle")
.each(function(d){
temp = d.neg
if (temp != 0)
{
d3.select(this).attr(
{
cx: xScale(d.neg),
cy: d.y,
fill: d.color,
r: 7
})
}
});
//Right Points
pts.append("circle")
.attr("class", "rightcircle")
.each(function(d){
if(d.pos != 0)
{
d3.select(this).attr(
{
cx: xScale(d.pos),
cy: d.y,
fill: d.color,
r: 7
})
}
}
);
//Left Labels
pts.append("text")
.attr("class", "left label")
.each(function(d){
if(d.neg != 0)
{
d3.select(this).attr (
{
x: xScale(d.neg),
y: d.y - offset,
fill: d.color
}
)
.text(d.leftlabel)
.style("text-anchor", "middle");
}
}
);
//Right Labels
pts.append("text")
.attr("class", "right label")
.each(function(d){
if(d.pos != 0)
{
d3.select(this).attr (
{
x: xScale(d.pos),
y: d.y - offset,
fill: d.color
}
)
.text(d.rightlabel)
//.style("stroke",d.color);
.attr("text-anchor", "middle")
}
}
);
//Left value labels
pts.append("text")
.attr("class", "left label value")
.each(function(d){
if(d.neg != 0)
{
d3.select(this).text(d.neg)
d3.select(this).attr (
{
x: xScale(d.neg),
y: d.y + 2* offset,
fill: d.color
}
)
d3.select(this).style("text-anchor", "middle")
}
}
);
//Right value labels
pts.append("text")
.attr("class", "right label value")
.each(function(d){
if(d.pos != 0)
{
d3.select(this).text(d.pos)
d3.select(this).attr (
{
x: xScale(d.pos),
y: d.y + 2* offset,
fill: d.color
}
)
d3.select(this).style("text-anchor", "middle")
}
}
);
//Style rectangles
pts.append("rect")
.attr("class", "background")
.each(function(d) {
d3.select(this).attr(
{
x: 0,
y: d.y - 40,
height: 100,
width: svgWidth,
fill: d.color
}
)
d3.select(this).style("opacity", 0.25)
}
);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment