Skip to content

Instantly share code, notes, and snippets.

@TommyCoin80
Last active May 30, 2018 16:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save TommyCoin80/cd4c124bb0d8faa52247e9f2797df607 to your computer and use it in GitHub Desktop.
Save TommyCoin80/cd4c124bb0d8faa52247e9f2797df607 to your computer and use it in GitHub Desktop.
WDW Hotel Cost Versus Quality
license: mit

Data from the 2017 Unofficial Guide to Walt Disney World. This scatter plot compares a hotel's cost with its quality rating, in stars, from the Unofficial Guide. Mousing over any of the points will display a readout with detail on the hotel. A regression line (average quality units per cost unit) is plotted. Hotels with better than average value are in the green. Hotels with worse than average value are in the red.

Built with blockbuilder.org

var data = [
{name:"Bay Lake Tower at Disney's Contemporary Resort",quality:4.5,room:95,cost:13,group:"Contemporary"},
{name:"Disney's Animal Kingdom Villas (Kidani Village)",quality:4.5,room:95,cost:11.25, group:"Animal Kingdom"},
{name:"Disney's Contemporary Resort",quality:4.5,room:93,cost:10.25, group:"Contemporary"},
{name:"Disney's Grand Floridian Resort & Spa",quality:4.5,room:93,cost:15,group:"Grand Floridian"},
{name:"The Villas at Disney's Grand Floridian Resort & Spa",quality:4.5,room:93,cost:20, group:"Grand Floridian"},
{name:"Disney's Polynesian Village Resort",quality:4.5,room:92,cost:12.25, group:"Polynesian"},
{name:"Disney's Polynesian Village, Villas & Bungalows",quality:4.5,room:92,cost:13.75, group:"Polynesian"},
{name:"Disney's Animal Kingdom Villas (Jambo House)",quality:4.5,room:91,cost:11.25, group:"Animal Kingdom"},
{name:"Boulder Ridge Villas at Disney's Wilderness Lodge",quality:4.5,room:90,cost:10.75, group:"Wilderness Lodge"},
{name:"Disney's Beach Club Resort",quality:4.5,room:90,cost:10.75, group:"Yacht and Beach"},
{name:"Disney's Beach Club Villas",quality:4.5,room:90,cost:12, group:"Yacht and Beach"},
{name:"Disney's BoardWalk Villas",quality:4.5,room:90,cost:12, group:"Boardwalk"},
{name:"Disney's Old Key West Resort",quality:4.5,room:90,cost:9.25, group:"Old Key West"},
{name:"Disney's Saratoga Springs Resort & Spa",quality:4.5,room:90,cost:9.25, group:"Saratoga Springs"},
{name:"Treehouse Villas at Disney's Saratoga Springs Resort & Spa",quality:4.5,room:90,cost:23, group:"Saratoga Springs"},
{name:"Walt Disney World Dolphin",quality:4.5,room:90,cost:4.75, group:"Swan and Dolphin"},
{name:"Walt Disney World Swan",quality:4.5,room:90,cost:5, group:"Swan and Dolphin"},
{name:"Disney's Animal Kingdom Lodge",quality:4,room:89,cost:9, group:"Animal Kingdom"},
{name:"Disney's BoardWalk Inn",quality:4,room:89,cost:11, group:"Boardwalk"},
{name:"Disney's Yacht Club Resort",quality:4,room:89,cost:10.75, group:"Yacht and Beach"},
{name:"Disney's Fort Wilderness Resort (cabins)",quality:4,room:86,cost:8.75, group:"Fort Wilderness"},
{name:"Disney's Wilderness Lodge",quality:4,room:86,cost:8.75, group:"Wilderness Lodge"},
{name:"Disney's Port Orleans Resort-French Quarter",quality:4,room:84,cost:5, group:"Port Orleans" },
{name:"Disney's Coronado Springs Resort",quality:4,room:83,cost:4.75, group:"Coronado Springs"},
{name:"Disney's Port Orleans Resort-Riverside",quality:4,room:83,cost:5, group:"Port Orleans"},
{name:"Disney's Art of Animation Resort",quality:3.5,room:80,cost:3.75, group:"Art of Animation"},
{name:"Disney's Caribbean Beach Resort",quality:3.5,room:80,cost:4.75, group:"Caribbean Beach"},
{name:"Disney's All-Star Movies Resort",quality:3,room:73,cost:3, group:"All-Star and Pop Century"},
{name:"Disney's All-Star Music Resort",quality:3,room:73,cost:3,group:"All-Star and Pop Century"},
{name:"Disney's All-Star Sports Resort",quality:3,room:73,cost:3,group:"All-Star and Pop Century"},
{name:"Disney's Pop Century Resort",quality:3,room:71,cost:3.25,group:"All-Star and Pop Century"}
];
<!DOCTYPE html>
<meta charset="utf-8">
<head>
<link href='https://fonts.googleapis.com/css?family=Josefin+Sans' rel='stylesheet' type='text/css'>
<style>
body {
margin:auto;
font-family: 'Josefin Sans', sans-serif;
font-size:100%;
}
text {
font-family: 'Josefin Sans', sans-serif;
}
</style>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
<script src="ValueChart.js"></script>
<script src="data.js"></script>
<div id="chart"><div>
<script>
var vc = new ValueChart(data,"chart");
</script>
var ValueChart = function(data,elementId,width,height,margin) {
//Set Defaults
margin = margin || {top:10,left:80,bottom:40,right:30};
width = width || 960 - margin.left - margin.right;
height = height || 500 - margin.top - margin.bottom;
//Object to store selection
var s = {};
//Draw Plot Area
s.svg = d3.select("#" + elementId)
.append("svg")
.attr("height", height + margin.top + margin.bottom)
.attr("width", width + margin.left + margin.right)
.style("-webkit-user-select","none") // Disable Highlighting
.style("cursor","default"); // Disable cursor style changes
s.chart = s.svg.append("g")
.attr("transform","translate(" + margin.left + "," + margin.top + ")");
s.chart.append("clipPath")
.attr("id","regLineClip")
.append("rect")
.attr("width", width)
.attr("height", height);
// Object to store scales
var scale = {};
var ytv = [2.5,3,3.5,4,4.5,5];
scale.x = d3.scaleLinear()
.domain(d3.extent(data.map(function(d) { return d.cost;})))
.range([0,width])
.nice();
scale.y = d3.scaleLinear()
.domain(d3.extent(ytv))
.range([height,0])
scale.color = d3.scaleOrdinal()
.domain(d3.set(data.map(function(d) { return d.name;})).values());
scale.color.range(scale.color.domain().map(function(d,i) { return d3.interpolateSpectral(i/scale.color.domain().length)}));
//Object to store axes
var axis = {};
axis.x = d3.axisBottom(scale.x).tickFormat(function(d) {
if(d<=4) {
return Array(d+1).join('$')
} else {
return ('$') + "×" + (d);
}
})
axis.y = d3.axisLeft(scale.y).tickValues(ytv).tickFormat(function(d) {
return Array(Math.floor(d) + 1).join('★') + Array(Math.ceil(d)-Math.floor(d) + 1).join('½') })
//Object to store grids
var grid = {};
grid.x = d3.axisBottom(scale.x)
.tickSizeInner(-height)
.tickFormat("");
grid.y = d3.axisLeft(scale.y)
.tickSizeInner(-width)
.tickFormat("")
.tickValues(ytv);
// Calculate reg line and draw line, polygons and labels
var regCoefs = lsReg(
data.map(function(d) { return scale.x(d.cost)}),
data.map(function(d) { return scale.y(d.quality)})
);
s.chart.append("polygon")
.attr("clip-path", "url(#regLineClip)")
.style("fill","#5cb85c")
.style("fill-opacity",.25)
.attr("points", "0,0 0," + regCoefs[1] + " " + width + "," + (width*regCoefs[0] + regCoefs[1]) )
s.chart.append("polygon")
.attr("clip-path", "url(#regLineClip)")
.style("fill","#d9534f")
.style("fill-opacity",.25)
.attr("points", "0," + regCoefs[1] + " 0," + height + " " + width + "," + height + " " + width + "," + (width*regCoefs[0] + regCoefs[1]) )
s.line = s.chart.append("line")
.attr("clip-path", "url(#regLineClip)")
.attr("x1", 0)
.attr("y1", regCoefs[1] )
.attr("x2", width)
.attr("y2", width*regCoefs[0] + regCoefs[1])
.style("stroke","white")
.attr("stroke-dasharray","5 5")
// Draw reg line label
s.chart.append("g")
.attr("transform","translate(" +scale.x(5) + "," + scale.y(3.75) + ")")
.append("text")
.attr("dy", -15)
.attr("dx",26)
.text("More ★★ per $")
.attr("transform","rotate(-19.0)")
.style("fill","white")
.style("font-weight","bold")
.style("font-size","1.5em");
s.chart.append("g")
.attr("transform","translate(" +scale.x(10.0) + "," + scale.y(4.2) + ")")
.append("text")
.attr("dy",30)
.attr("dx",-26)
.text("More $$ per ★")
.attr("transform","rotate(-19.0)")
.style("fill","white")
.style("font-weight","bold")
.style("font-size","1.5em");
// Draw Grids
// Object to store grid selections
s.grid = {};
s.grid.x = s.chart.append("g")
.attr("transform","translate(0," + height + ")")
.call(grid.x)
.each(function(d) {
d3.select(this)
.style("opacity", .15)
.select(".domain")
.style("display","none")
});
s.grid.y = s.chart.append("g")
.call(grid.y)
.each(function(d) {
d3.select(this)
.style("opacity", .15)
.select(".domain")
.style("display","none")
});
// Draw Axes
// Object to store axis selection
s.axis = {};
s.axis.x = s.chart.append("g")
.attr("transform","translate(0," + (height + 16) + ")")
.call(axis.x);
s.axis.x.append("text")
.text("Cost")
.style("fill","black")
.attr("dx", width)
.attr("dy", -3)
.attr("text-anchor","end")
s.axis.y = s.chart.append("g")
.attr("transform","translate(-16,0)")
.call(axis.y)
s.axis.y.append("text")
.attr("transform","rotate(90)")
.text("Quality")
.style("fill","black")
.attr("dy", -5)
.attr("text-anchor","start");
//Simulate Forces to jitter points
var force = d3.forceSimulation(data)
.force("x", d3.forceX(function(d) { return scale.x(d.cost); }).strength(1))
.force("y", d3.forceY(function(d) { return scale.y(d.quality); }).strength(1))
.force("collide", d3.forceCollide(6))
.stop();
for (var i = 0; i < 120; ++i) force.tick();
// Draw Points
s.points = s.chart.selectAll(".point")
.data(data)
.enter()
.append("g")
.attr("transform",function(d) { return "translate(" + d.x + "," + d.y+ ")"})
.on("mouseenter", mouseenterPoint)
.on("mouseleave", mouseleavePoint);
s.points.append("circle")
.attr("r",6)
.style("fill", function(d) { return scale.color(d.name)})
.style("stroke","gray");
//Setup the readout
// Object to store readout components
s.readout = {};
s.readout.g = s.chart.append("g")
.attr("transform","translate(" + (width*2/3) + "," + height*(4/5) + ")")
.style("display","none");
s.readout.underlay = s.readout.g.append("rect")
s.readout.rect = s.readout.g.append("rect")
s.readout.name = s.readout.g.append("text")
.attr("y",6)
.attr("text-anchor","middle");
s.readout.scores = s.readout.g.append("text")
.attr("dy",24)
.attr("text-anchor","middle")
// Functions to update the readout
function mouseenterPoint(d) {
var rx,ry;
var rw = 0;
s.readout.g.style("display",null)
s.readout.name.text(d.name)
.style("fill","black")
.each(function(d) {
var bb = this.getBBox();
bb.width > rw ? rw = bb.width : null;
ry = bb.y;
});
// Quality in stars, cost in $, +/- for cost
var quality, cost, costSign;
// Calculate how many stars to display
quality = Array(Math.floor(d.quality) + 1).join('★') + Array(Math.ceil(d.quality)-Math.floor(d.quality) + 1).join('½');
// Calculate whether to include a +/- sign
if(d.cost - Math.floor(d.cost) >= .75) {
costSign = "+";
} else if(d.cost - Math.floor(d.cost) >= .25) {
costSign = "-";
} else {
costSign = "";
}
// Calculate how many $ to display
if(d.cost<6) {
cost = Array(Math.floor(d.cost)+1).join('$')
} else {
cost = ('$') + "×" + (Math.floor(d.cost));
}
s.readout.scores.text("Quality: " + quality + " Cost: " + cost+costSign + " Room: " + d.room)
.attr("xml:space", "preserve")
.style("fill","black")
.each(function(d) {
var bb = this.getBBox();
bb.width > rw ? rw = bb.width : null;
});
s.readout.underlay.attr("x", -(rw+8)/2)
.attr("y", ry)
.attr("width", rw+8)
.attr("height",38)
.attr("rx",4)
.style("fill", "white")
.style("fill-opacity",.5)
.style("stroke-width",2);
s.readout.rect.attr("x", -(rw+8)/2)
.attr("y", ry)
.attr("width", rw+8)
.attr("height",38)
.attr("rx",4)
.style("fill", scale.color(d.name))
.style("fill-opacity",.5)
.style("stroke", scale.color(d.name))
.style("stroke-width",2);
}
function mouseleavePoint() {
s.readout.g.style("display","none")
}
function lsReg(X,Y) {
var meanX = d3.mean(X),
meanY = d3.mean(Y);
var ssXX = d3.sum(X.map(function(d) { return Math.pow(d - meanX, 2); })),
ssYY = d3.sum(Y.map(function(d) { return Math.pow(d - meanY, 2); }));
var ssXY = d3.sum(X.map(function(d, i) { return (d - meanX) * (Y[i] - meanY);}))
var slope = ssXY / ssXX;
var intercept = meanY - (meanX * slope);
return [slope, intercept];
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment