index.html
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Projects</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js" charset="utf-8"></script>
<link rel="stylesheet" href="jquery-ui.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jqueryui/1.10.4/jquery-ui.min.js"></script>
<script src="d3.tip.v0.6.3.js"></script>
<style>
#d1 {
display: table;
}
.divvy{
display: inline-block;
padding: 2px 6px;
width: 116px;
height: 40px;
background-color: #eee;
white-space: nowrap;
border: 1px solid #686881;
text-align:center;
font-family:calibri;
vertical-align:middle;
white-space: pre-wrap;
white-space: -moz-pre-wrap;
white-space: -pre-wrap;
white-space: -o-pre-wrap;
word-wrap: break-word;
}
#amount, label, text, p{
font-family:calibri;
}
#slider {
width : 1230px;
}
.d3-tip {
line-height: 1;
font-weight: bold;
padding: 12px;
background: rgba(0, 0, 0, 0.8);
color: #fff;
border-radius: 2px;
font-family:calibri;
}
.d3-tip:after {
box-sizing: border-box;
display: inline;
font-size: 10px;
width: 100%;
line-height: 1;
color: rgba(0, 0, 0, 0.8);
content: "\25BC";
position: absolute;
text-align: center;
}
.d3-tip.n:after {
margin: -1px 0 0 0;
top: 100%;
left: 0;
}
#amount {
font-size: 20px;
font-family:calibri;
}
</style>
</head>
<body>
<p>
Tool to show how progress has been made on various projects over the last 13 months. Size of bubble indicates project budget. Vertical axis indicates progression gate (0-8).<hr>
<label for="amount">Current Month:</label>
<input type="text" id="amount" readonly style="border:0; color:#f6931f; font-weight:bold;">
</p>
<div id="slider"></div>
<div id="viz"></div>
<div id="d1">
<div class="divvy">Team A</div>
<div class="divvy">Team B</div>
<div class="divvy">Team C</div>
<div class="divvy">Team D</div>
<div class="divvy">Team E</div>
<div class="divvy">Team F</div>
<div class="divvy">Team G</div>
<div class="divvy">Team H</div>
<div class="divvy">Team I</div>
<div class="divvy">Team J</div>
</div>
<script type="text/javascript">
var monthNames = ["Apr-13","May-13", "Jun-13","Jul-13","Aug-13", "Sep-13","Oct-13","Nov-13", "Dec-13","Jan-14","Feb-14", "Mar-14", "Apr-14"];
$(function() {
$( "#slider" ).slider({
value:1,
min: 1,
max: 13,
step: 1,
slide: function( event, ui ) {
$( "#amount" ).val( monthNames[(ui.value)-1] );
chart(monthNames[(ui.value)-1] )
}
});
$( "#amount" ).val( "Apr-13" );
});
var col = d3.scale.category10();
var mydata , start = [];
var w = 1300;
var h = 460;
var margin = {top:100,right:100,bottom:100,left:100};
var x = d3.scale.linear().domain([1,10]).range([0,w-100]);
var b = d3.scale.linear().domain([0,25000]).range([0,50]);
var y = d3.scale.linear().domain([0,8]).range([h-margin.top,100]);
var svg = d3.select("#viz")
.append("svg")
.attr("width", w)
.attr("height", h);
function numberWithCommas(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function(d) {
return "<strong>Project:</strong> <span style='color:Turquoise'>" + d.project
+ "</span><br><strong>Budget:</strong><span style='color:CornflowerBlue'>£"+ numberWithCommas(d.budget)
+ "</span><br><strong>Tower:</strong> <span style='color:LawnGreen'>" + d.towerName
+ "</span><br><strong>Gate:</strong> <span style='color:DeepSkyBlue'>" + d.gate
+ "</span><br><strong>Project Manager:</strong> <span style='color:Gainsboro'>" + d.manager
+ "</span><br><strong>Sponsor:</strong> <span style='color:OrangeRed'>" + d.sponsor
;
})
svg.call(tip);
d3.csv("projects.csv",function(input) {
input.forEach(function(d) {
d.gate = +d.gate;
d.budget = +d.budget;
d.tower = +d.tower;
})
mydata = input;
start = mydata.filter(function(d) {
return d.month == "Apr-13";
})
svg.selectAll(".project")
.data(start)
.enter()
.append("circle")
.attr("class","project")
.style("fill",function(d) {return col(d.tower);})
.style("opacity",0.6)
.style("stroke","white")
.attr("r",function(d) {return b(d.budget);})
.attr("cx",function(d) { return 50 + x(d.tower);})
.attr("cy",function(d) {return y(d.gate);})
.on('mouseover', tip.show)
.on('mouseout', tip.hide)
}
)
function chart(selectedMonth) {
start = mydata.filter(function(d) {
return d.month == selectedMonth;
})
var svg = d3.select("svg");
var circle = svg.selectAll("circle")
.data(start);
var circleEnter = circle.enter().append("circle");
circleEnter.attr("r",function(d) {return b(d.budget);})
.attr("cx",function(d) { return 50 + x(d.tower);})
.attr("cy",function(d) {return y(d.gate);})
.style("fill",function(d) {return col(d.tower);})
.style("opacity",0.6)
.style("stroke","white");
circle.transition().duration(500).attr("r",function(d) {return b(d.budget);})
.attr("cx",function(d) { return 50 + x(d.tower);})
.attr("cy",function(d) {return y(d.gate);})
.style("fill",function(d) {return col(d.tower);})
}
var gates = [0,1,2,3,4,5,6,7,8];
var g = svg.selectAll(".gates")
.data(gates)
.enter()
.append("g")
.attr("class","gates");
g.append("rect")
.attr("width",w)
.attr("height",(h-margin.top)/9-10)
.attr("x",0)
.attr("y",function(d) {return -15+ y(d);})
.style("fill","#F0F0F2")
g.append("text")
.text(function(d) {return d;})
.attr("x",w/2)
.attr("y",function(d) {return y(d)+10;})
.style("fill","#909091")
.style("font-size","30px")
var gMonth = svg.selectAll(".months")
.data(monthNames)
.enter()
.append("g")
.attr("class","months");
gMonth.append("text")
.text(function(d) {return d;})
.attr("x",function(d,i) {return i*100 ;})
.attr("y",20)
.style("fill","#909091")
.style("font-size","10px")
setTimeout(c1, 1000);
setTimeout(c2, 1500);
setTimeout(c3, 2000);
setTimeout(c4, 2500);
setTimeout(c5, 3000);
setTimeout(c6, 3500);
setTimeout(c7, 4000);
setTimeout(c8, 4500);
setTimeout(c9, 5000);
setTimeout(c10, 5500);
setTimeout(c11, 6000);
setTimeout(c12, 6500);
function c1() {
$( "#slider" ).slider({
value:2})
chart(monthNames[1])
$( "#amount" ).val( "May-13" );
}
function c2() {
$( "#slider" ).slider({
value:3})
chart(monthNames[2])
$( "#amount" ).val( "Jun-13" );
}
function c3() {
$( "#slider" ).slider({
value:4})
chart(monthNames[3])
$( "#amount" ).val( "Jul-13" );
}
function c4() {
$( "#slider" ).slider({
value:5})
chart(monthNames[4])
$( "#amount" ).val( "Aug-13" );
}
function c5() {
$( "#slider" ).slider({
value:6})
chart(monthNames[5])
$( "#amount" ).val( "Sep-13" );
}
function c6() {
$( "#slider" ).slider({
value:7})
chart(monthNames[6])
$( "#amount" ).val( "Oct-13" );
}
function c7() {
$( "#slider" ).slider({
value:8})
chart(monthNames[7])
$( "#amount" ).val( "Nov-13" );
}
function c8() {
$( "#slider" ).slider({
value:9})
chart(monthNames[8])
$( "#amount" ).val( "Dec-13" );
}
function c9() {
$( "#slider" ).slider({
value:10})
chart(monthNames[9])
$( "#amount" ).val( "Jan-14" );
}
function c10() {
$( "#slider" ).slider({
value:11})
chart(monthNames[10])
$( "#amount" ).val( "Feb-14" );
}
function c11() {
$( "#slider" ).slider({
value:12})
chart(monthNames[11])
$( "#amount" ).val( "Mar-14" );
}
function c12() {
$( "#slider" ).slider({
value:13})
chart(monthNames[12])
$( "#amount" ).val( "Apr-14" );
}
</script>
</body>
</html>
d3.tip.v0.6.3.js
d3.tip = function() {
var direction = d3_tip_direction,
offset = d3_tip_offset,
html = d3_tip_html,
node = initNode(),
svg = null,
point = null,
target = null
function tip(vis) {
svg = getSVGNode(vis)
point = svg.createSVGPoint()
document.body.appendChild(node)
}
tip.show = function() {
var args = Array.prototype.slice.call(arguments)
if(args[args.length - 1] instanceof SVGElement) target = args.pop()
var content = html.apply(this, args),
poffset = offset.apply(this, args),
dir = direction.apply(this, args),
nodel = d3.select(node), i = 0,
coords
nodel.html(content)
.style({ opacity: 1, 'pointer-events': 'all' })
while(i--) nodel.classed(directions[i], false)
coords = direction_callbacks.get(dir).apply(this)
nodel.classed(dir, true).style({
top: (coords.top + poffset[0]) + 'px',
left: (coords.left + poffset[1]) + 'px'
})
return tip
}
tip.hide = function() {
nodel = d3.select(node)
nodel.style({ opacity: 0, 'pointer-events': 'none' })
return tip
}
tip.attr = function(n, v) {
if (arguments.length < 2 && typeof n === 'string') {
return d3.select(node).attr(n)
} else {
var args = Array.prototype.slice.call(arguments)
d3.selection.prototype.attr.apply(d3.select(node), args)
}
return tip
}
tip.style = function(n, v) {
if (arguments.length < 2 && typeof n === 'string') {
return d3.select(node).style(n)
} else {
var args = Array.prototype.slice.call(arguments)
d3.selection.prototype.style.apply(d3.select(node), args)
}
return tip
}
tip.direction = function(v) {
if (!arguments.length) return direction
direction = v == null ? v : d3.functor(v)
return tip
}
tip.offset = function(v) {
if (!arguments.length) return offset
offset = v == null ? v : d3.functor(v)
return tip
}
tip.html = function(v) {
if (!arguments.length) return html
html = v == null ? v : d3.functor(v)
return tip
}
function d3_tip_direction() { return 'n' }
function d3_tip_offset() { return [0, 0] }
function d3_tip_html() { return ' ' }
var direction_callbacks = d3.map({
n: direction_n,
s: direction_s,
e: direction_e,
w: direction_w,
nw: direction_nw,
ne: direction_ne,
sw: direction_sw,
se: direction_se
}),
directions = direction_callbacks.keys()
function direction_n() {
var bbox = getScreenBBox()
return {
top: bbox.n.y - node.offsetHeight,
left: bbox.n.x - node.offsetWidth / 2
}
}
function direction_s() {
var bbox = getScreenBBox()
return {
top: bbox.s.y,
left: bbox.s.x - node.offsetWidth / 2
}
}
function direction_e() {
var bbox = getScreenBBox()
return {
top: bbox.e.y - node.offsetHeight / 2,
left: bbox.e.x
}
}
function direction_w() {
var bbox = getScreenBBox()
return {
top: bbox.w.y - node.offsetHeight / 2,
left: bbox.w.x - node.offsetWidth
}
}
function direction_nw() {
var bbox = getScreenBBox()
return {
top: bbox.nw.y - node.offsetHeight,
left: bbox.nw.x - node.offsetWidth
}
}
function direction_ne() {
var bbox = getScreenBBox()
return {
top: bbox.ne.y - node.offsetHeight,
left: bbox.ne.x
}
}
function direction_sw() {
var bbox = getScreenBBox()
return {
top: bbox.sw.y,
left: bbox.sw.x - node.offsetWidth
}
}
function direction_se() {
var bbox = getScreenBBox()
return {
top: bbox.se.y,
left: bbox.e.x
}
}
function initNode() {
var node = d3.select(document.createElement('div'))
node.style({
position: 'absolute',
opacity: 0,
pointerEvents: 'none',
boxSizing: 'border-box'
})
return node.node()
}
function getSVGNode(el) {
el = el.node()
if(el.tagName.toLowerCase() == 'svg')
return el
return el.ownerSVGElement
}
function getScreenBBox() {
var targetel = target || d3.event.target,
bbox = {},
matrix = targetel.getScreenCTM(),
tbbox = targetel.getBBox(),
width = tbbox.width,
height = tbbox.height,
x = tbbox.x,
y = tbbox.y,
scrollTop = document.documentElement.scrollTop || document.body.scrollTop,
scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft
point.x = x + scrollLeft
point.y = y + scrollTop
bbox.nw = point.matrixTransform(matrix)
point.x += width
bbox.ne = point.matrixTransform(matrix)
point.y += height
bbox.se = point.matrixTransform(matrix)
point.x -= width
bbox.sw = point.matrixTransform(matrix)
point.y -= height / 2
bbox.w = point.matrixTransform(matrix)
point.x += width
bbox.e = point.matrixTransform(matrix)
point.x -= width / 2
point.y -= height / 2
bbox.n = point.matrixTransform(matrix)
point.y += height
bbox.s = point.matrixTransform(matrix)
return bbox
}
return tip
};