A visual representation of how FastPass+ availablity changes for Magic Kingdom attractions based on crowd level. Data from pg 495 of the 2017 Walt Disney World Unoffical Guide. The graph will toggle through crowd levels until the user clicks on the crowd level toggle.
xxxxxxxxxx
<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="dataIn.js"></script>
<script>
var FpChart = function(opts) {
this.elementId = opts.elementId;
this.height = opts.height || 500;
this.width = opts.width || 960;
this.margin = opts.margin || {top:10,left:50,bottom:30,right:10};
this.s = {};
this.prepData(opts.data);
this.setScales();
this.setAxes();
this.setGrids()
this.draw()
this.drawGrids();
this.drawAxes();
this.drawBars();
this.drawLabels(opts.data);
this.drawToggle();
this.drawLegend();
var _this = this;
var counter = 0;
this.timer = d3.interval(function(elapsed) {
if(counter == 0) _this.toggleCrowdLevel({key:"med"});
if(counter == 1) _this.toggleCrowdLevel({key:"high"});
if(counter == 2) _this.toggleCrowdLevel({key:"low"});
counter++;
if(counter == 3) counter = 0;
},2000)
}
FpChart.prototype.setGrids = function() {
this.grid = {};
this.grid.y = d3.axisTop(this.scale.time)
.tickSizeInner(-this.height)
.tickFormat("");
}
FpChart.prototype.drawGrids = function() {
this.s.chart.append("g")
.call(this.grid.y)
.each(function(d) {
d3.select(this)
.style("opacity", .15)
.select(".domain")
.style("display","none")
})
}
FpChart.prototype.toggleCrowdLevel = function(d) {
var cl = d.key,
_this = this;
this.s.bars.transition()
.duration(1000)
.attr("x", function(d) { return _this.scale.time(d.crowdLevel[cl].start)})
.attr("width",function(d) { return _this.scale.time(d.crowdLevel[cl].end) - _this.scale.time(d.crowdLevel[cl].start) });
this.s.slider.transition()
.duration(1000)
.attr("transform","translate(" + (this.width + 10) + "," + _this.scale.toggle(cl) +")");
}
FpChart.prototype.drawLegend = function() {
var _this = this;
var legend = this.s.chart.append("g").attr("transform","translate(" + (this.width + 10) + ",0)")
.selectAll(".legend")
.data(this.legendData)
.enter()
.append("g")
.attr("transform",function(d) { return "translate(0," + _this.scale.legend(d.key) + ")" })
legend.append("rect")
.attr("height", this.scale.legend.bandwidth())
.attr("width", 100)
.style("fill", function(d) { return _this.scale.color(d.key)})
.style("fill-opacity",.65)
.style("stroke", function(d) { return _this.scale.color(d.key)})
.style("stroke-width",2)
legend.append("text")
.style("text-anchor","middle")
.text(function(d) { return d.value;})
.attr("x", (legend.select("rect").attr("width"))/2)
.attr("y", this.scale.legend.bandwidth()/2 + 1.5)
.attr("dy",".3em")
.style("fill","black")
this.s.chart.append("text")
.attr("transform","translate(" + (this.width + 10 + (+legend.select("rect").attr("width"))/2) + "," +
this.height*(2.8/10) + ")")
.style("text-anchor","middle")
.style("font-weight","bold")
.text("Availability")
this.s.chart.append("text")
.attr("x",this.width + 10 + (legend.select("rect").attr("width"))/2)
.attr("y", this.height*(2.4/10) )
.style("text-anchor","middle")
.style("font-weight","bold")
.text("FastPass")
}
FpChart.prototype.drawToggle = function() {
var _this = this;
this.s.toggles = this.s.chart.append("g").attr("transform","translate(" + (this.width + 10) + ",0)")
.selectAll(".toggle")
.data(this.toggleData)
.enter()
.append("g")
.attr("transform",function(d) { return "translate(0," + _this.scale.toggle(d.key) + ")" })
.style("cursor","pointer")
.on("click", function(d) {
_this.timer.stop();
_this.toggleCrowdLevel(d);})
this.s.toggles.append("rect")
.attr("height", this.scale.toggle.bandwidth())
.attr("width", 100)
.style("fill", "white")
.style("stroke","gray");
this.s.toggles.append("text")
.style("text-anchor","middle")
.text(function(d) { return d.value;})
.attr("x", (+this.s.toggles.select("rect").attr("width"))/2)
.attr("y", this.scale.toggle.bandwidth()/2 + 1.5)
.attr("dy",".3em")
.style("fill","black")
this.s.slider = this.s.chart.append("g")
.attr("transform","translate(" + (this.width + 10) + "," + _this.scale.toggle("low") +")");
this.s.slider.append("rect")
.attr("width", +this.s.toggles.select("rect").attr("width"))
.attr("height", +this.s.toggles.select("rect").attr("height"))
.style("stroke","black")
.style("stroke-width", 4)
.style("fill","none")
this.s.chart.append("text")
.attr("transform","translate(" + (this.width + 10 + (+this.s.toggles.select("rect").attr("width"))/2) + "," +
this.height*(6.8/10) + ")")
.style("text-anchor","middle")
.style("font-weight","bold")
.text("Level")
this.s.chart.append("text")
.attr("x",this.width + 10 + (+this.s.toggles.select("rect").attr("width"))/2)
.attr("y", this.height*(6.4/10) )
.style("text-anchor","middle")
.style("font-weight","bold")
.text("Crowd")
}
FpChart.prototype.drawLabels = function(data) {
var _this = this;
this.s.chart.selectAll(".label")
.data(data)
.enter()
.append("text")
.attr("y", function(d) { return _this.scale.attraction(d.attraction) + _this.scale.attraction.bandwidth()/2})
.attr("x",10)
.text(function(d) { return d.attraction})
.style("text-anchor","start")
.attr("dy", ".37em")
.style("fill", "black")
.style("font-size",".78em")
}
FpChart.prototype.drawBars = function() {
var _this = this;
this.s.bars = this.s.chart.selectAll(".attraction")
.data(this.data)
.enter()
.append("rect")
.attr("x", function(d) { return _this.scale.time(d.crowdLevel.low.start)})
.attr("y", function(d) { return _this.scale.attraction(d.attraction)})
.attr("height", _this.scale.attraction.bandwidth())
.attr("width",function(d) { return _this.scale.time(d.crowdLevel.low.end) - _this.scale.time(d.crowdLevel.low.start) })
.style("fill", function(d) { return _this.scale.color(d.status)})
.style("stroke",function(d) { return _this.scale.color(d.status)})
.style("stroke-width",2)
.style("fill-opacity",.6)
}
FpChart.prototype.draw = function() {
this.s.svg = d3.select("#" + this.elementId)
.append("svg")
.attr("height", this.height + this.margin.top + this.margin.bottom)
.attr("width", this.width + this.margin.left + this.margin.right)
.style("-webkit-user-select","none")
.style("cursor","default");
this.s.chart = this.s.svg.append("g")
.attr("transform","translate(" + this.margin.left + "," + this.margin.top + ")");
}
FpChart.prototype.drawAxes = function() {
this.s.axis = {};
this.s.axis.bottom = this.s.chart.append("g").attr("transform","translate(0," + (this.height+2) + ")")
.call(this.axis.bottom);
this.s.axis.top = this.s.chart.append("g").attr("transform","translate(0,-2)").call(this.axis.top);
}
FpChart.prototype.setAxes = function() {
this.axis = {};
this.axis.bottom = d3.axisBottom(this.scale.time).tickFormat(d3.timeFormat('%I:%M %p'));
this.axis.top = d3.axisTop(this.scale.time).tickFormat(d3.timeFormat('%I:%M %p'));
}
FpChart.prototype.setScales = function() {
this.scale = {};
this.scale.color = d3.scaleOrdinal()
.domain(["available","runningOut","gone"])
.range(["#5cb85c","#f0ad4e","#d9534f"]);
this.scale.attraction = d3.scaleBand()
.domain(this.data.map(function(d) { return d.attraction}))
.range([0, this.height])
.padding(.23);
this.scale.time = d3.scaleTime()
.domain([new Date(2017, 7, 4, 9, 0, 0, 0),new Date(2017, 7, 4, 22, 0, 0, 0)])
.range([0,this.width]);
this.scale.toggle = d3.scaleBand()
.domain(this.toggleData.map(function(d) { return d.key}))
.range([this.height*(7/10), this.height*(9/10)])
this.scale.legend = d3.scaleBand()
.domain(this.scale.color.domain())
.range([this.height*(3/10), this.height*(5/10)])
.padding(.1)
}
FpChart.prototype.prepData = function(data) {
var _this = this;
this.data = [];
data.forEach(function(d){
["available","runningOut","gone"].forEach(function(e) {
var o = {attraction:d.attraction, status:e, crowdLevel: {low:{},med:{},high:{}}};
if(e == "available") {
o.crowdLevel.low.start = new Date(2017, 7, 4, 9, 0, 0, 0);
o.crowdLevel.med.start = new Date(2017, 7, 4, 9, 0, 0, 0);
o.crowdLevel.high.start = new Date(2017, 7, 4, 9, 0, 0, 0);
o.crowdLevel.low.end = !d.lowStart ? new Date(2017, 7, 4, 9, 0, 0, 0) : new Date(2017, 7, 4, 12 + d.lowStart, 0, 0, 0);
o.crowdLevel.med.end = !d.medStart ? new Date(2017, 7, 4, 9, 0, 0, 0) : new Date(2017, 7, 4, 12 + d.medStart, 0, 0, 0);
o.crowdLevel.high.end = !d.highStart ? new Date(2017, 7, 4, 9, 0, 0, 0) : new Date(2017, 7, 4, 12 + d.highStart, 0, 0, 0);
} else if (e == "runningOut") {
o.crowdLevel.low.start = !d.lowStart ? new Date(2017, 7, 4, 9, 0, 0, 0) : new Date(2017, 7, 4, 12 + d.lowStart, 0, 0, 0);
o.crowdLevel.med.start = !d.medStart ? new Date(2017, 7, 4, 9, 0, 0, 0) : new Date(2017, 7, 4, 12 + d.medStart, 0, 0, 0);
o.crowdLevel.high.start = !d.highStart ? new Date(2017, 7, 4, 9, 0, 0, 0) : new Date(2017, 7, 4, 12 + d.highStart, 0, 0, 0);
o.crowdLevel.low.end = !d.lowEnd ? new Date(2017, 7, 4, 9, 0, 0, 0) : new Date(2017, 7, 4, 12 + d.lowEnd, 0, 0, 0);
o.crowdLevel.med.end = !d.medEnd ? new Date(2017, 7, 4, 9, 0, 0, 0) : new Date(2017, 7, 4, 12 + d.medEnd, 0, 0, 0);
o.crowdLevel.high.end = !d.highEnd ? new Date(2017, 7, 4, 9, 0, 0, 0) : new Date(2017, 7, 4, 12 + d.highEnd, 0, 0, 0);
} else {
o.crowdLevel.low.start = !d.lowEnd ? new Date(2017, 7, 4, 9, 0, 0, 0): new Date(2017, 7, 4, 12 + d.lowEnd, 0, 0, 0);
o.crowdLevel.med.start = !d.medEnd ? new Date(2017, 7, 4, 9, 0, 0, 0): new Date(2017, 7, 4, 12 + d.medEnd, 0, 0, 0);
o.crowdLevel.high.start = !d.highEnd ? new Date(2017, 7, 4, 9, 0, 0, 0): new Date(2017, 7, 4, 12 + d.highEnd, 0, 0, 0);
o.crowdLevel.low.end = new Date(2017,7,4,22,0,0,0);
o.crowdLevel.med.end = new Date(2017,7,4,22,0,0,0);
o.crowdLevel.high.end = new Date(2017,7,4,22,0,0,0);
}
_this.data.push(o)
})
})
this.toggleData = [
{key:"low", value:"Low"},
{key:"med", value:"Medium"},
{key:"high", value:"High"}
];
this.legendData = [
{key:'available', value:'Available'},
{key:'runningOut', value:'Running Out'},
{key:'gone',value:'Gone'}
];
}
</script>
<body>
<div id="chartDiv"></div>
<script>
var fpChart = new FpChart({
elementId:"chartDiv",
data:dataIn,
height:450,
width:800,
margin: {top:25,left:30,bottom:25,right:130}
})
</script>
https://d3js.org/d3.v4.min.js