Built with blockbuilder.org
forked from 35degrees's block: gatestestnew
license: mit |
Built with blockbuilder.org
forked from 35degrees's block: gatestestnew
start_year,grant_title,total_amount | |
2019,SAPInsideTrackBrisbane,10 | |
2019,SAPInsideTrackGuadalajara,10 | |
2019,SAPTechNightMelbourne,10 | |
2019,SAPInsideTrackMaidenhead,10 | |
2019,SAPInsideTrackSao,10 | |
2019,SAPInsideTrackFrankfurt,10 | |
2019,SAPInsideTrackTimisoara,10 | |
2019,SAPInsideTrackIskenderun,10 | |
2019,SAPInsideTrackMadrid,10 | |
2019,SAPInsideTrackIstanbul,10 | |
2019,SAPInsideTrackCopenhagen,10 | |
2019,SAPInsideTrackBelo,10 | |
2019,SAPInsideTrackHannover,10 | |
2019,SAPInsideTrackWrocław,10 | |
2019,SAPInsideTrackBelgium,10 | |
2019,SAPInsideTrackRuhrpott,10 | |
2019,SAPInsideTrackOslo,10 | |
2019,SAPInsideTrackBerlin,10 | |
2019,SAPInsideTrackBursa,10 | |
2019,SAPInsideTrackMunich,10 | |
2019,SAPInsideTrackTokyo,10 | |
2019,SAPInsideTrackthe,10 | |
2019,SAPInsideTrackHamburg,10 | |
2019,SAPInsideTrackVienna,10 | |
2019,SAPInsideTrackIstanbul,10 | |
2019,SAPInsideTrackCaribbean,10 | |
2019,SAPInsideTrackAtlanta,10 | |
2019,SAPInsideTrackRiga,10 | |
2019,SAPInsideTrackMontréal,10 | |
2019,SAPInsideTrackToronto,10 | |
2019,SAPInsideTrackRecife,10 | |
2019,SAPInsideTrackWalldorf,10 | |
2019,SAPInsideTrackCampinas,10 | |
2018,SAPTechNightSydney,10 | |
2018,SAPInsideTrackIstanbul,10 | |
2018,SAPInsideTrackWien,10 | |
2018,SAPInsideTrackBrisbane,10 | |
2018,SAPInsideTrackCopenhagen,10 | |
2018,SAPInsideTrackNetherlands,10 | |
2018,SAPInsideTrackTokyo,10 | |
2018,SAPInsideTrackBarcelona,10 | |
2018,SAPInsideTrackMunich,10 | |
2018,SAPInsideTrackSan,10 | |
2018,SAPInsideTrackSão,10 | |
2018,SAPInsideTrackfor,10 | |
2018,SAPInsideTrackBern,10 | |
2018,SAPInsideTrackBerlin,10 | |
2018,SAPTechNightSydney,10 | |
2018,SAPInsideTrackSao,10 | |
2018,SAPInsideTrackRome,10 | |
2018,SAPInsideTrackBogota,10 | |
2018,SAPInsideTrackHamburg,10 | |
2018,SAPInsideTrackBrisbane,10 | |
2018,SAPInsideTrackWrocław,10 | |
2018,SAPInsideTrackVancouver,10 | |
2018,SAPInsideTrackKids,10 | |
2018,SAPTechNightMelbourne,10 | |
2018,SAPInsideTrackCopenhagen,10 | |
2018,SAPInsideTrackAnkara,10 | |
2018,SAPInsideTrackNewtown,10 | |
2018,SAPInsideTrackAnkara,10 | |
2018,SAPInsideTrackBelgium,10 | |
2018,SAPInsideTrackIstanbul,10 | |
2018,SAPTechNightSydney,10 | |
2018,SAPInsideTrackTimisoara,10 | |
2018,SAPInsideTrackBelo,10 | |
2018,SAPInsideTrackRibeirao,10 | |
2018,SAPInsideTrackbefore,10 | |
2018,SAPInsideTrackFrankfurt,10 | |
2018,SAPInsideTrackWalldorf,10 | |
2018,SAPInsideTrackCampinas,10 | |
2018,SAPInsideTrackBrisbane,10 | |
2018,SAPInsideTrackHannover,10 | |
2018,SAPInsideTrackSalvador,10 | |
2017,SAPInsideTrackIstanbul,10 | |
2017,SAPInsideTrackSydney,10 | |
2017,SAPInsideTrackParis,10 | |
2017,SAPInsideTrackSão,10 | |
2017,SAPInsideTrackNetherlands,10 | |
2017,SAPInsideTrackCopenhagen,10 | |
2017,SAPInsideTrackBogota,10 | |
2017,SAPInsideTrackTokyo,10 | |
2017,SAPInsideTrackMelbourne,10 | |
2017,5thITConferenceon,10 | |
2017,SAPInsideTrackMonterrey,10 | |
2017,SAPInsideTrackMunich,10 | |
2017,SAPInsideTrackSilicon,10 | |
2017,SAPInsideTrackRecife,10 | |
2017,SAPInsideTrackNovo,10 | |
2017,SAPInsideTrackBern,10 | |
2017,SAPInsideTrackfor,10 | |
2017,SAPInsideTrackBerlin,10 | |
2017,SAPInsideTrackVancouver,10 | |
2017,SAPInsideTrackBuenos,10 | |
2017,SAPInsideTrackMumbai,10 | |
2017,SAPInsideTrackMelbourne,10 | |
2017,SAPInsideTrackBangalore,10 | |
2017,SAPInsideTrackSao,10 | |
2017,SAPInsideTrackBarcelona,10 | |
2017,SAPInsideTrackBrussels,10 | |
2017,SAPInsideTrackTech,10 | |
2017,SAPInsideTrackHamburg,10 | |
2017,SAPInsideTrackWrocław,10 | |
2017,SAPInsideTrackBallerup,10 | |
2017,SAPInsideTrackBrisbane,10 | |
2017,SAPInsideTrackIstanbul,10 | |
2017,SAPInsideTrackFrankfurt,10 | |
2017,SAPInsideTrackSydney,10 | |
2017,SAPInsideTrackWalldorf,10 | |
2017,SAPInsideTrackTokyo,10 | |
2017,SAPInsideTrackCampinas,10 | |
2017,SAPInsideTrackHannover,10 | |
2016,SAPInsideTrackIstanbul,10 | |
2016,SAPInsideTrackChennai,10 | |
2016,SAPInsideTrackSão,10 | |
2016,SAPInsideTrackRecife,10 | |
2016,SAPInsideTrackNetherlands,10 | |
2016,SAPInsideTrackCaribbean,10 | |
2016,SAPInsideTrackSydney,10 | |
2016,SAPInsideTrackTokyo,10 | |
2016,SAPInsideTrackNovo,10 | |
2016,SAPInsideTrackMexico,10 | |
2016,SAPInsideTrackMunich,10 | |
2016,SAPInsideTrackPeru,10 | |
2016,SAPInsideTrackJoinville,10 | |
2016,SAPInsideTrackBrisbane,10 | |
2016,SAPInsideTrackManchester,10 | |
2016,SAPInsideTrackSilicon,10 | |
2016,SAPInsideTrackBern,10 | |
2016,SAPInsideTrackKolkata,10 | |
2016,SAPInsideTrackChicago,10 | |
2016,SAPInsideTrackMexico,10 | |
2016,SAPInsideTrackSao,10 | |
2016,SAPInsideTrackMississauga,10 | |
2016,SAPInsideTrackHamburg,10 | |
2016,SAPInsideTrackGurgaon,10 | |
2016,SAPInsideTrackBelgium,10 | |
2016,SAPInsideTrackWrocław,10 | |
2016,SAPInsideTrackIstanbul,10 | |
2016,SAPInsideTrackFrankfurt,10 | |
2016,SAPInsideTrackSydney,10 | |
2016,SAPInsideTrackSao,10 | |
2015,SAPInsideTrackWalldorf,10 | |
2015,SAPInsideTrackIstanbul,10 | |
2015,SAPInsideTrackHyderabad,10 | |
2015,SAPInsideTrackSao,10 | |
2015,SAPInsideTrackCaribbean,10 | |
2015,SAPInsideTrackTokyo,10 | |
2015,SAPInsideTrackNetherlands,10 | |
2015,SAPInsideTrackBangalore,10 | |
2015,SAPInsideTrackMunich,10 | |
2015,SAPInsideTrackSheffield,10 | |
2015,SAPInsideTrackWrocław,10 | |
2015,SAPInsideTrackPalo,10 | |
2015,SAPInsideTrackMexico,10 | |
2015,SAPInsideTrackKuala,10 | |
2015,SAPInsideTrackJoinville,10 | |
2015,SAPInsideTrackHamburg,10 | |
2015,SAPInsideTrackWashington,10 | |
2015,SAPInsideTrackMelbourne,10 | |
2015,SAPInsideTrackSao,10 | |
2015,SAPInsideTrackFrankfurt,10 | |
2015,SAPInsideTrackRome,10 | |
2015,SAPMeetupWrocław-,10 | |
2014,SAPInsideTrackIstanbul,10 | |
2014,SAPInsideTrackSao,10 | |
2014,SAPInsideTrackNetherlands,10 | |
2014,SAPInsideTrackBengaluru,10 | |
2014,SAPInsideTrackMunich,10 | |
2014,SAPInsideTrackKochi,10 | |
2014,SAPInsideTrackSheffield,10 | |
2014,SAPInsideTrackHyderabad,10 | |
2014,SAPInsideTrackSão,10 | |
2014,SAPInsideTrackNew,10 | |
2014,SAPInsideTrackOslo,10 | |
2014,SAPInsideTrackNoida,10 | |
2014,SAPInsideTrackChicago,10 | |
2014,SAPInsideTrackChennai,10 | |
2014,SAPInsideTrackHamburg,10 | |
2014,SAPInsideTrackMelbourne,10 | |
2014,SAPInsideTrackVancouver,10 | |
2014,SAPInsideTrackHyderabad,10 | |
2014,SAPInsideTrackSingapore,10 | |
2013,Manchester-SAPInside,10 | |
2013,SAPInsideTrack2013,10 | |
2013,SAPInsideTrackCalgary,10 | |
2013,SAPInsideTrackCalgary,10 | |
2013,SAPInsideTrackChicago,10 | |
2013,SAPInsideTrackHamburg,10 | |
2013,SAPInsideTrackIndia,10 | |
2013,SAPInsideTrackIsrael,10 | |
2013,SAPInsideTrackIstanbul,10 | |
2013,SAPInsideTrackManchester,10 | |
2013,SAPInsideTrackMelbourne,10 | |
2013,SAPInsideTrackMunich,10 | |
2013,SAPInsideTrackNewtown,10 | |
2013,SAPInsideTrackNew,10 | |
2013,SAPInsideTrackPrague,10 | |
2013,SAPInsideTrackSingapore,10 | |
2013,SAPInsideTrackSt.,10 | |
2013,SAPInsideTrackToronto,10 | |
2013,SAPInsideTrackWashington,10 | |
2012,SAPInsideTrack2012,10 | |
2012,SAPInsideTrackAnkara,10 | |
2012,SAPInsideTrackBelgium,10 | |
2012,SAPInsideTrackCalgary,10 | |
2012,SAPInsideTrackCalgary,10 | |
2012,SAPInsideTrackCalgary,10 | |
2012,SAPInsideTrackChicago,10 | |
2012,SAPInsideTrackHamburg,10 | |
2012,SAPInsideTrackIndia,10 | |
2012,SAPInsideTrackMilan,10 | |
2012,SAPInsideTrackMilan,10 | |
2012,SAPInsideTrackNew,10 | |
2012,SAPInsideTrackPalo,10 | |
2012,SAPInsideTrackPalo,10 | |
2012,SAPInsideTrackSao,10 | |
2012,SAPInsideTrackSingapore,10 | |
2012,SAPInsideTrackSydney,10 | |
2012,SAPInsideTrackToronto,10 | |
2012,SAPInsideTrackVancouver,10 | |
2012,SAPInsideTrackWashington,10 | |
2012,SAPNetworkHamburg,Germany,10 | |
2011,SAPInsideTrackNetherlands,10:00,19:00,10 | |
2011,SAPINSIDETRACK-,10 | |
2011,SAPINSIDETRACK-,10 | |
2011,SAPInsideTrackAnkara,10 | |
2011,SAPInsideTrackChicago,10 | |
2011,SAPInsideTrackIndia,10 | |
2011,SAPInsideTrackIstanbul,10 | |
2011,SAPInsideTrackMilan,10 | |
2011,SAPInsideTrackNewtown,10 | |
2011,SAPInsideTrackRio,10 | |
2011,SAPInsideTrackSao,10 | |
2011,SAPInsideTrackSt.,10 | |
2011,SAPInsideTrackSydney,10 | |
2011,SAPInsideTrackVancouver,10 | |
2011,SAPInsideTrackWashington,10 | |
2010,SAPInsideTrackBangalore,10 | |
2010,SAPInsideTrackBrussels,10 | |
2010,SAPInsideTrackLondon,10 | |
2010,SAPInsideTrackNetherlands,10 | |
2010,SAPInsideTrackBonn,10 | |
2010,SAPInsideTrackIsrael,10 | |
2010,SAPInsideTrackIstanbul,10 | |
2010,SAPInsideTrackNetherlands,10 | |
2010,SAPInsideTrackNewtown,10 | |
2010,SAPInsideTrackPalo,10 | |
2010,SAPInsideTrackSao,10 | |
2010,SAPInsideTrackSt.,10 | |
2010,SAPInsideTrackSydney,10 | |
2010,SAPInsideTrackVancouver,10 | |
2010,SAPInsideTrackWalldorf,10 | |
2010,SAPInsideTrackWarwick,10 | |
2009,SAPInsideTrackCommunity,10 | |
2009,SAPInsideTrackEindhoven,10 | |
2009,SAPInsideTrackPalo,10 | |
2009,SAPInsideTrackSao,10 | |
2009,SAPInsideTrackLondon,10 |
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<meta http-equiv="X-UA-Compatible" content="ie=edge"> | |
<title>Bubble Chart Experiment</title> | |
</head> | |
<style> | |
a, a:visited, a:active { | |
color: #444; | |
} | |
.container { | |
max-width: 900px; | |
margin: auto; | |
} | |
.button { | |
min-width: 130px; | |
padding: 4px 5px; | |
cursor: pointer; | |
text-align: center; | |
font-size: 13px; | |
border: 1px solid #e0e0e0; | |
text-decoration: none; | |
} | |
.button.active { | |
background: #000; | |
color: #fff; | |
} | |
#vis { | |
width: 940px; | |
height: 600px; | |
clear: both; | |
margin-bottom: 10px; | |
} | |
#toolbar { | |
margin-top: 10px; | |
} | |
.year { | |
font-size: 21px; | |
fill: #aaa; | |
cursor: default; | |
} | |
.tooltip { | |
position: absolute; | |
top: 100px; | |
left: 100px; | |
-moz-border-radius:5px; | |
border-radius: 5px; | |
border: 2px solid #000; | |
background: #fff; | |
opacity: .9; | |
color: black; | |
padding: 10px; | |
width: 300px; | |
font-size: 12px; | |
z-index: 10; | |
} | |
.tooltip .title { | |
font-size: 13px; | |
} | |
.tooltip .name { | |
font-weight:bold; | |
} | |
.footer { | |
text-align: center; | |
} | |
</style> | |
<body> | |
<div class="container"> | |
<h1>Gates Foundation Educational Spending</h1> | |
<div id="toolbar"> | |
<a href="#" id="all" class="button active">All Grants</a> | |
<a href="#" id="year" class="button">Grants By Year</a> | |
</div> | |
<div id="vis"></div> | |
</body> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<script> | |
function bubbleChart() { | |
// Constants for sizing | |
var width = 840; | |
var height = 600; | |
// tooltip for mouseover functionality | |
var tooltip = floatingTooltip('gates_tooltip', 240); | |
// Locations to move bubbles towards, depending | |
// on which view mode is selected. | |
var center = { x: width / 2, y: height / 2 }; | |
var yearCenters = { | |
2008: { x: 9 * width / 9, y: height / 2 }, | |
2009: { x: 8 * width / 9, y: height / 2 }, | |
2010: { x: 7 * width / 9, y: height / 2 }, | |
2011: { x: 6 * width / 9, y: height / 2 }, | |
2012: { x: 5 * width / 9, y: height / 2 }, | |
2013: { x: 4 * width / 9, y: height / 2 }, | |
2014: { x: 3 * width / 9, y: height / 2 }, | |
2015: { x: 2 * width / 9, y: height / 2 }, | |
2016: { x: width / 9, y: height / 2 } | |
}; | |
// X locations of the year titles. | |
var yearsTitleX = { | |
2008: 9 * width / 9, | |
2009: 8 * width / 9, | |
2010: 7 * width / 9, | |
2011: 6 * width / 9, | |
2012: 5 * width / 9, | |
2013: 4 * width / 9, | |
2014: 3 * width / 9, | |
2015: 2 * width / 9, | |
2016: 1 * width / 9 | |
}; | |
// @v4 strength to apply to the position forces | |
var forceStrength = 0.03; | |
// These will be set in create_nodes and create_vis | |
var svg = null; | |
var bubbles = null; | |
var nodes = []; | |
// Charge function that is called for each node. | |
// As part of the ManyBody force. | |
// This is what creates the repulsion between nodes. | |
// | |
// Charge is proportional to the diameter of the | |
// circle (which is stored in the radius attribute | |
// of the circle's associated data. | |
// | |
// This is done to allow for accurate collision | |
// detection with nodes of different sizes. | |
// | |
// Charge is negative because we want nodes to repel. | |
// @v4 Before the charge was a stand-alone attribute | |
// of the force layout. Now we can use it as a separate force! | |
function charge(d) { | |
return -Math.pow(d.radius, 2.0) * forceStrength; | |
} | |
// Here we create a force layout and | |
// @v4 We create a force simulation now and | |
// add forces to it. | |
var simulation = d3.forceSimulation() | |
.velocityDecay(0.2) | |
.force('x', d3.forceX().strength(forceStrength).x(center.x)) | |
.force('y', d3.forceY().strength(forceStrength).y(center.y)) | |
.force('charge', d3.forceManyBody().strength(charge)) | |
.on('tick', ticked); | |
// @v4 Force starts up automatically, | |
// which we don't want as there aren't any nodes yet. | |
simulation.stop(); | |
// Nice looking colors - no reason to buck the trend | |
// @v4 scales now have a flattened naming scheme | |
var fillColor = d3.scaleLinear() | |
.domain([2008, 2019]) | |
.range(['#4286f4', '#0a9850']) | |
.interpolate(d3.interpolateHcl); | |
/* | |
* This data manipulation function takes the raw data from | |
* the CSV file and converts it into an array of node objects. | |
* Each node will store data and visualization values to visualize | |
* a bubble. | |
* | |
* rawData is expected to be an array of data objects, read in from | |
* one of d3's loading functions like d3.csv. | |
* | |
* This function returns the new node array, with a node in that | |
* array for each element in the rawData input. | |
*/ | |
function createNodes(rawData) { | |
// Use the max total_amount in the data as the max in the scale's domain | |
// note we have to ensure the total_amount is a number. | |
var maxAmount = d3.max(rawData, function (d) { return +d.total_amount; }); | |
// Sizes bubbles based on area. | |
// @v4: new flattened scale names. | |
var radiusScale = d3.scalePow() | |
.exponent(0.5) | |
.range([2, 15]) | |
.domain([0, maxAmount]); | |
// Use map() to convert raw data into node data. | |
// Checkout http://learnjsdata.com/ for more on | |
// working with data. | |
var myNodes = rawData.map(function (d) { | |
return { | |
id: d.id, | |
// radius: radiusScale(+d.total_amount), | |
radius: radiusScale(+d.total_amount), | |
value: +d.total_amount, | |
name: d.grant_title, | |
// org: d.organization, | |
// group: d.group, | |
year: d.start_year, | |
x: Math.random() * 900, | |
y: Math.random() * 800 | |
}; | |
}); | |
// sort them to prevent occlusion of smaller nodes. | |
myNodes.sort(function (a, b) { return b.value - a.value; }); | |
return myNodes; | |
} | |
/* | |
* Main entry point to the bubble chart. This function is returned | |
* by the parent closure. It prepares the rawData for visualization | |
* and adds an svg element to the provided selector and starts the | |
* visualization creation process. | |
* | |
* selector is expected to be a DOM element or CSS selector that | |
* points to the parent element of the bubble chart. Inside this | |
* element, the code will add the SVG continer for the visualization. | |
* | |
* rawData is expected to be an array of data objects as provided by | |
* a d3 loading function like d3.csv. | |
*/ | |
var chart = function chart(selector, rawData) { | |
// convert raw data into nodes data | |
nodes = createNodes(rawData); | |
// Create a SVG element inside the provided selector | |
// with desired size. | |
svg = d3.select(selector) | |
.append('svg') | |
.attr('width', width) | |
.attr('height', height); | |
// Bind nodes data to what will become DOM elements to represent them. | |
bubbles = svg.selectAll('.bubble') | |
.data(nodes, function (d) { return d.id; }); | |
// Create new circle elements each with class `bubble`. | |
// There will be one circle.bubble for each object in the nodes array. | |
// Initially, their radius (r attribute) will be 0. | |
// @v4 Selections are immutable, so lets capture the | |
// enter selection to apply our transtition to below. | |
var bubblesE = bubbles.enter().append('circle') | |
.classed('bubble', true) | |
.attr('r', 0) | |
.attr('fill', function (d) { return fillColor(+d.start_year); }) | |
.attr('stroke', function (d) { return d3.rgb(fillColor(d.group)).darker(); }) | |
.attr('stroke-width', 2) | |
.on('mouseover', showDetail) | |
.on('mouseout', hideDetail); | |
// @v4 Merge the original empty selection and the enter selection | |
bubbles = bubbles.merge(bubblesE); | |
// Fancy transition to make bubbles appear, ending with the | |
// correct radius | |
bubbles.transition() | |
.duration(2000) | |
.attr('r', function (d) { return d.radius; }); | |
// Set the simulation's nodes to our newly created nodes array. | |
// @v4 Once we set the nodes, the simulation will start running automatically! | |
simulation.nodes(nodes); | |
// Set initial layout to single group. | |
groupBubbles(); | |
}; | |
/* | |
* Callback function that is called after every tick of the | |
* force simulation. | |
* Here we do the acutal repositioning of the SVG circles | |
* based on the current x and y values of their bound node data. | |
* These x and y values are modified by the force simulation. | |
*/ | |
function ticked() { | |
bubbles | |
.attr('cx', function (d) { return d.x; }) | |
.attr('cy', function (d) { return d.y; }); | |
} | |
/* | |
* Provides a x value for each node to be used with the split by year | |
* x force. | |
*/ | |
function nodeYearPos(d) { | |
console.log(yearCenters[d.year].x); | |
return yearCenters[d.year].x; | |
} | |
/* | |
* Sets visualization in "single group mode". | |
* The year labels are hidden and the force layout | |
* tick function is set to move all nodes to the | |
* center of the visualization. | |
*/ | |
function groupBubbles() { | |
hideYearTitles(); | |
// @v4 Reset the 'x' force to draw the bubbles to the center. | |
simulation.force('x', d3.forceX().strength(forceStrength).x(center.x)); | |
// @v4 We can reset the alpha value and restart the simulation | |
simulation.alpha(1).restart(); | |
} | |
/* | |
* Sets visualization in "split by year mode". | |
* The year labels are shown and the force layout | |
* tick function is set to move nodes to the | |
* yearCenter of their data's year. | |
*/ | |
function splitBubbles() { | |
showYearTitles(); | |
// @v4 Reset the 'x' force to draw the bubbles to their year centers | |
simulation.force('x', d3.forceX().strength(forceStrength).x(nodeYearPos)); | |
// @v4 We can reset the alpha value and restart the simulation | |
simulation.alpha(1).restart(); | |
} | |
/* | |
* Hides Year title displays. | |
*/ | |
function hideYearTitles() { | |
svg.selectAll('.year').remove(); | |
} | |
/* | |
* Shows Year title displays. | |
*/ | |
function showYearTitles() { | |
// Another way to do this would be to create | |
// the year texts once and then just hide them. | |
var yearsData = d3.keys(yearsTitleX); | |
var years = svg.selectAll('.year') | |
.data(yearsData); | |
years.enter().append('text') | |
.attr('class', 'year') | |
.attr('x', function (d) { return yearsTitleX[d]; }) | |
.attr('y', 40) | |
.attr('text-anchor', 'middle') | |
.text(function (d) { return d; }); | |
} | |
/* | |
* Function called on mouseover to display the | |
* details of a bubble in the tooltip. | |
*/ | |
function showDetail(d) { | |
// change outline to indicate hover state. | |
d3.select(this).attr('stroke', 'black'); | |
var content = '<span class="name">Title: </span><span class="value">' + | |
d.name + | |
'</span><br/>' + | |
'<span class="name">Amount: </span><span class="value">$' + | |
addCommas(d.value) + | |
'</span><br/>' + | |
'<span class="name">Year: </span><span class="value">' + | |
d.year + | |
'</span>'; | |
tooltip.showTooltip(content, d3.event); | |
} | |
/* | |
* Hides tooltip | |
*/ | |
function hideDetail(d) { | |
// reset outline | |
d3.select(this) | |
.attr('stroke', d3.rgb(fillColor(d.group)).darker()); | |
tooltip.hideTooltip(); | |
} | |
/* | |
* Externally accessible function (this is attached to the | |
* returned chart function). Allows the visualization to toggle | |
* between "single group" and "split by year" modes. | |
* | |
* displayName is expected to be a string and either 'year' or 'all'. | |
*/ | |
chart.toggleDisplay = function (displayName) { | |
if (displayName === 'year') { | |
splitBubbles(); | |
} else { | |
groupBubbles(); | |
} | |
}; | |
// return the chart function from closure. | |
return chart; | |
} | |
/* | |
* Below is the initialization code as well as some helper functions | |
* to create a new bubble chart instance, load the data, and display it. | |
*/ | |
var myBubbleChart = bubbleChart(); | |
/* | |
* Function called once data is loaded from CSV. | |
* Calls bubble chart function to display inside #vis div. | |
*/ | |
function display(error, data) { | |
if (error) { | |
console.log(error); | |
} | |
myBubbleChart('#vis', data); | |
} | |
/* | |
* Sets up the layout buttons to allow for toggling between view modes. | |
*/ | |
function setupButtons() { | |
d3.select('#toolbar') | |
.selectAll('.button') | |
.on('click', function () { | |
// Remove active class from all buttons | |
d3.selectAll('.button').classed('active', false); | |
// Find the button just clicked | |
var button = d3.select(this); | |
// Set it as the active button | |
button.classed('active', true); | |
// Get the id of the button | |
var buttonId = button.attr('id'); | |
// Toggle the bubble chart based on | |
// the currently clicked button. | |
myBubbleChart.toggleDisplay(buttonId); | |
}); | |
} | |
/* | |
* Helper function to convert a number into a string | |
* and add commas to it to improve presentation. | |
*/ | |
function addCommas(nStr) { | |
nStr += ''; | |
var x = nStr.split('.'); | |
var x1 = x[0]; | |
var x2 = x.length > 1 ? '.' + x[1] : ''; | |
var rgx = /(\d+)(\d{3})/; | |
while (rgx.test(x1)) { | |
x1 = x1.replace(rgx, '$1' + ',' + '$2'); | |
} | |
return x1 + x2; | |
} | |
// Load the data. | |
d3.csv('gates_money.csv', display); | |
// setup the buttons. | |
setupButtons(); | |
function floatingTooltip(tooltipId, width) { | |
// Local variable to hold tooltip div for | |
// manipulation in other functions. | |
var tt = d3.select('body') | |
.append('div') | |
.attr('class', 'tooltip') | |
.attr('id', tooltipId) | |
.style('pointer-events', 'none'); | |
// Set a width if it is provided. | |
if (width) { | |
tt.style('width', width); | |
} | |
// Initially it is hidden. | |
hideTooltip(); | |
/* | |
* Display tooltip with provided content. | |
* | |
* content is expected to be HTML string. | |
* | |
* event is d3.event for positioning. | |
*/ | |
function showTooltip(content, event) { | |
tt.style('opacity', 1.0) | |
.html(content); | |
updatePosition(event); | |
} | |
/* | |
* Hide the tooltip div. | |
*/ | |
function hideTooltip() { | |
tt.style('opacity', 0.0); | |
} | |
/* | |
* Figure out where to place the tooltip | |
* based on d3 mouse event. | |
*/ | |
function updatePosition(event) { | |
var xOffset = 20; | |
var yOffset = 10; | |
var ttw = tt.style('width'); | |
var tth = tt.style('height'); | |
var wscrY = window.scrollY; | |
var wscrX = window.scrollX; | |
var curX = (document.all) ? event.clientX + wscrX : event.pageX; | |
var curY = (document.all) ? event.clientY + wscrY : event.pageY; | |
var ttleft = ((curX - wscrX + xOffset * 2 + ttw) > window.innerWidth) ? | |
curX - ttw - xOffset * 2 : curX + xOffset; | |
if (ttleft < wscrX + xOffset) { | |
ttleft = wscrX + xOffset; | |
} | |
var tttop = ((curY - wscrY + yOffset * 2 + tth) > window.innerHeight) ? | |
curY - tth - yOffset * 2 : curY + yOffset; | |
if (tttop < wscrY + yOffset) { | |
tttop = curY + yOffset; | |
} | |
tt | |
.style('top', tttop + 'px') | |
.style('left', ttleft + 'px'); | |
} | |
return { | |
showTooltip: showTooltip, | |
hideTooltip: hideTooltip, | |
updatePosition: updatePosition | |
}; | |
} | |
</script> | |
</html> |