Skip to content

Instantly share code, notes, and snippets.

@micahstubbs
Last active July 19, 2016 21:10
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save micahstubbs/734ddd29b231b58ebf7cb87c055883d9 to your computer and use it in GitHub Desktop.
Save micahstubbs/734ddd29b231b58ebf7cb87c055883d9 to your computer and use it in GitHub Desktop.
Voronoi Scatterplot
border: none
license: CC0-1.0
height: 520

An ES2015 fork of Step 2 - Voronoi Scatterplot - Tooltip attached to Voronoi grid from @NadiehBremer

babel script tag that allows us to use ES2015 syntax from ES2015 Sequences Sunburst

<script src="https://npmcdn.com/babel-core@5.8.34/browser.min.js"></script>

the nice voronoi path highlight-on-mouseover effect is inspired by martgnz's bl.ock Chatty Map

in script.js:

voronoiGroup.selectAll('path')
  // ...
  .on('mouseover', showTooltip)
  .on('mouseout', removeTooltip);

then

function showTooltip(d) {
  // ...

  // highlight the border of the current voronoi cell
    d3.select(`path.${d.CountryCode}`)
      .style('stroke', 'black')
      .style('stroke-opacity', 0.5)
      .style('stroke-width', 2);
}

and

function removeTooltip(d) {
  // ...

  // restore the border of the cell to its original color
  d3.select(`path.${d.CountryCode}`)
    .style('stroke', '#2074A0')
    .style('stroke-opacity', 0.3)
    .style('stroke-width', 1);
}

Original README.md from @NadiehBremer

This scatterplot is part of my blog on Using a D3 Voronoi grid to improve a chart's interactive experience in which the tooltip and the event are both triggered by an invisible Voronoi grid that lies over the scatterplot. This is used to illustrate the fact that you still need one more step, and that is to attach the tooltip to the circle that represents the seed of each Voronoi cell, which you can see in the next example

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<title>Scatterplot with Voronoi</title>
<!-- D3.js -->
<script src="https://d3js.org/d3.v3.js"></script>
<!-- babel - transpile ES2015 to ES5 javascript -->
<script src="https://npmcdn.com/babel-core@5.8.34/browser.min.js"></script>
<!-- Pym.js - iframe height handler for the Blog -->
<script src="pym.min.js"></script>
<!-- jQuery -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
<!-- Latest compiled and minified JavaScript -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
<!-- Open Sans & CSS -->
<link href='https://fonts.googleapis.com/css?family=Open+Sans:700,400,300' rel='stylesheet' type='text/css'>
<style>
body {
font-family: 'Open Sans', sans-serif;
font-size: 12px;
font-weight: 400;
color: #525252;
text-align: center;
}
html, body {
width:auto;
height:auto;
}
.axis path,
.axis line {
fill: none;
stroke: #B3B3B3;
shape-rendering: crispEdges;
}
.axis text {
font-size: 10px;
fill: #6B6B6B;
}
.popover {
pointer-events: none;
}
.legendTitle {
fill: #1A1A1A;
color: #1A1A1A;
text-anchor: middle;
font-size: 10px;
}
.legendText {
fill: #949494;
text-anchor: start;
font-size: 9px;
}
@media (min-width: 500px) {
.col-sm-3, .col-sm-9 {
float: left;
}
.col-sm-9 {
width: 75%;
}
.col-sm-3 {
width: 25%;
}
}
</style>
</head>
<body>
<div id="cont" class="container-fluid text-center">
<div class="row scatter">
<h5 style="color: #3B3B3B;">Life expectancy versus GDP per Capita</h5>
<h6 style="color: #A6A6A6;">Voronoi - Tooltip attached to Voronoi</h6>
<div class="col-sm-9">
<div id="chart"></div>
</div>
<div id = "legend" class="col-sm-3">
<div class="legendTitle" style="font-size: 12px;">REGION</div>
<div id="legend"></div>
</div>
</div>
</div>
<script src="worldbank.js"></script>
<script src="script.js" lang='babel' type='text/babel'></script>
</body>
</html>
5.1,3.5,1.4,0.2,Iris-setosa
5.1,3.5,1.4,0.2,Iris-setosa
4.9,3.0,1.4,0.2,Iris-setosa
4.7,3.2,1.3,0.2,Iris-setosa
4.6,3.1,1.5,0.2,Iris-setosa
5.0,3.6,1.4,0.2,Iris-setosa
5.4,3.9,1.7,0.4,Iris-setosa
4.6,3.4,1.4,0.3,Iris-setosa
5.0,3.4,1.5,0.2,Iris-setosa
4.4,2.9,1.4,0.2,Iris-setosa
4.9,3.1,1.5,0.1,Iris-setosa
5.4,3.7,1.5,0.2,Iris-setosa
4.8,3.4,1.6,0.2,Iris-setosa
4.8,3.0,1.4,0.1,Iris-setosa
4.3,3.0,1.1,0.1,Iris-setosa
5.8,4.0,1.2,0.2,Iris-setosa
5.7,4.4,1.5,0.4,Iris-setosa
5.4,3.9,1.3,0.4,Iris-setosa
5.1,3.5,1.4,0.3,Iris-setosa
5.7,3.8,1.7,0.3,Iris-setosa
5.1,3.8,1.5,0.3,Iris-setosa
5.4,3.4,1.7,0.2,Iris-setosa
5.1,3.7,1.5,0.4,Iris-setosa
4.6,3.6,1.0,0.2,Iris-setosa
5.1,3.3,1.7,0.5,Iris-setosa
4.8,3.4,1.9,0.2,Iris-setosa
5.0,3.0,1.6,0.2,Iris-setosa
5.0,3.4,1.6,0.4,Iris-setosa
5.2,3.5,1.5,0.2,Iris-setosa
5.2,3.4,1.4,0.2,Iris-setosa
4.7,3.2,1.6,0.2,Iris-setosa
4.8,3.1,1.6,0.2,Iris-setosa
5.4,3.4,1.5,0.4,Iris-setosa
5.2,4.1,1.5,0.1,Iris-setosa
5.5,4.2,1.4,0.2,Iris-setosa
4.9,3.1,1.5,0.1,Iris-setosa
5.0,3.2,1.2,0.2,Iris-setosa
5.5,3.5,1.3,0.2,Iris-setosa
4.9,3.1,1.5,0.1,Iris-setosa
4.4,3.0,1.3,0.2,Iris-setosa
5.1,3.4,1.5,0.2,Iris-setosa
5.0,3.5,1.3,0.3,Iris-setosa
4.5,2.3,1.3,0.3,Iris-setosa
4.4,3.2,1.3,0.2,Iris-setosa
5.0,3.5,1.6,0.6,Iris-setosa
5.1,3.8,1.9,0.4,Iris-setosa
4.8,3.0,1.4,0.3,Iris-setosa
5.1,3.8,1.6,0.2,Iris-setosa
4.6,3.2,1.4,0.2,Iris-setosa
5.3,3.7,1.5,0.2,Iris-setosa
5.0,3.3,1.4,0.2,Iris-setosa
7.0,3.2,4.7,1.4,Iris-versicolor
6.4,3.2,4.5,1.5,Iris-versicolor
6.9,3.1,4.9,1.5,Iris-versicolor
5.5,2.3,4.0,1.3,Iris-versicolor
6.5,2.8,4.6,1.5,Iris-versicolor
5.7,2.8,4.5,1.3,Iris-versicolor
6.3,3.3,4.7,1.6,Iris-versicolor
4.9,2.4,3.3,1.0,Iris-versicolor
6.6,2.9,4.6,1.3,Iris-versicolor
5.2,2.7,3.9,1.4,Iris-versicolor
5.0,2.0,3.5,1.0,Iris-versicolor
5.9,3.0,4.2,1.5,Iris-versicolor
6.0,2.2,4.0,1.0,Iris-versicolor
6.1,2.9,4.7,1.4,Iris-versicolor
5.6,2.9,3.6,1.3,Iris-versicolor
6.7,3.1,4.4,1.4,Iris-versicolor
5.6,3.0,4.5,1.5,Iris-versicolor
5.8,2.7,4.1,1.0,Iris-versicolor
6.2,2.2,4.5,1.5,Iris-versicolor
5.6,2.5,3.9,1.1,Iris-versicolor
5.9,3.2,4.8,1.8,Iris-versicolor
6.1,2.8,4.0,1.3,Iris-versicolor
6.3,2.5,4.9,1.5,Iris-versicolor
6.1,2.8,4.7,1.2,Iris-versicolor
6.4,2.9,4.3,1.3,Iris-versicolor
6.6,3.0,4.4,1.4,Iris-versicolor
6.8,2.8,4.8,1.4,Iris-versicolor
6.7,3.0,5.0,1.7,Iris-versicolor
6.0,2.9,4.5,1.5,Iris-versicolor
5.7,2.6,3.5,1.0,Iris-versicolor
5.5,2.4,3.8,1.1,Iris-versicolor
5.5,2.4,3.7,1.0,Iris-versicolor
5.8,2.7,3.9,1.2,Iris-versicolor
6.0,2.7,5.1,1.6,Iris-versicolor
5.4,3.0,4.5,1.5,Iris-versicolor
6.0,3.4,4.5,1.6,Iris-versicolor
6.7,3.1,4.7,1.5,Iris-versicolor
6.3,2.3,4.4,1.3,Iris-versicolor
5.6,3.0,4.1,1.3,Iris-versicolor
5.5,2.5,4.0,1.3,Iris-versicolor
5.5,2.6,4.4,1.2,Iris-versicolor
6.1,3.0,4.6,1.4,Iris-versicolor
5.8,2.6,4.0,1.2,Iris-versicolor
5.0,2.3,3.3,1.0,Iris-versicolor
5.6,2.7,4.2,1.3,Iris-versicolor
5.7,3.0,4.2,1.2,Iris-versicolor
5.7,2.9,4.2,1.3,Iris-versicolor
6.2,2.9,4.3,1.3,Iris-versicolor
5.1,2.5,3.0,1.1,Iris-versicolor
5.7,2.8,4.1,1.3,Iris-versicolor
6.3,3.3,6.0,2.5,Iris-virginica
5.8,2.7,5.1,1.9,Iris-virginica
7.1,3.0,5.9,2.1,Iris-virginica
6.3,2.9,5.6,1.8,Iris-virginica
6.5,3.0,5.8,2.2,Iris-virginica
7.6,3.0,6.6,2.1,Iris-virginica
4.9,2.5,4.5,1.7,Iris-virginica
7.3,2.9,6.3,1.8,Iris-virginica
6.7,2.5,5.8,1.8,Iris-virginica
7.2,3.6,6.1,2.5,Iris-virginica
6.5,3.2,5.1,2.0,Iris-virginica
6.4,2.7,5.3,1.9,Iris-virginica
6.8,3.0,5.5,2.1,Iris-virginica
5.7,2.5,5.0,2.0,Iris-virginica
5.8,2.8,5.1,2.4,Iris-virginica
6.4,3.2,5.3,2.3,Iris-virginica
6.5,3.0,5.5,1.8,Iris-virginica
7.7,3.8,6.7,2.2,Iris-virginica
7.7,2.6,6.9,2.3,Iris-virginica
6.0,2.2,5.0,1.5,Iris-virginica
6.9,3.2,5.7,2.3,Iris-virginica
5.6,2.8,4.9,2.0,Iris-virginica
7.7,2.8,6.7,2.0,Iris-virginica
6.3,2.7,4.9,1.8,Iris-virginica
6.7,3.3,5.7,2.1,Iris-virginica
7.2,3.2,6.0,1.8,Iris-virginica
6.2,2.8,4.8,1.8,Iris-virginica
6.1,3.0,4.9,1.8,Iris-virginica
6.4,2.8,5.6,2.1,Iris-virginica
7.2,3.0,5.8,1.6,Iris-virginica
7.4,2.8,6.1,1.9,Iris-virginica
7.9,3.8,6.4,2.0,Iris-virginica
6.4,2.8,5.6,2.2,Iris-virginica
6.3,2.8,5.1,1.5,Iris-virginica
6.1,2.6,5.6,1.4,Iris-virginica
7.7,3.0,6.1,2.3,Iris-virginica
6.3,3.4,5.6,2.4,Iris-virginica
6.4,3.1,5.5,1.8,Iris-virginica
6.0,3.0,4.8,1.8,Iris-virginica
6.9,3.1,5.4,2.1,Iris-virginica
6.7,3.1,5.6,2.4,Iris-virginica
6.9,3.1,5.1,2.3,Iris-virginica
5.8,2.7,5.1,1.9,Iris-virginica
6.8,3.2,5.9,2.3,Iris-virginica
6.7,3.3,5.7,2.5,Iris-virginica
6.7,3.0,5.2,2.3,Iris-virginica
6.3,2.5,5.0,1.9,Iris-virginica
6.5,3.0,5.2,2.0,Iris-virginica
6.2,3.4,5.4,2.3,Iris-virginica
5.9,3.0,5.1,1.8,Iris-virginica
/*! pym.js - v0.4.4 - 2015-07-16 */
!function(a){"function"==typeof define&&define.amd?define(a):"undefined"!=typeof module&&module.exports?module.exports=a():window.pym=a.call(this)}(function(){var a="xPYMx",b={},c=function(a){var b=new RegExp("[\\?&]"+a.replace(/[\[]/,"\\[").replace(/[\]]/,"\\]")+"=([^&#]*)"),c=b.exec(location.search);return null===c?"":decodeURIComponent(c[1].replace(/\+/g," "))},d=function(a,b){return"*"===b.xdomain||a.origin.match(new RegExp(b.xdomain+"$"))?!0:void 0},e=function(b,c,d){var e=["pym",b,c,d];return e.join(a)},f=function(b){var c=["pym",b,"(\\S+)","(.+)"];return new RegExp("^"+c.join(a)+"$")},g=function(){for(var a=document.querySelectorAll("[data-pym-src]:not([data-pym-auto-initialized])"),c=a.length,d=0;c>d;++d){var e=a[d];e.setAttribute("data-pym-auto-initialized",""),""===e.id&&(e.id="pym-"+d);var f=e.getAttribute("data-pym-src"),g=e.getAttribute("data-pym-xdomain"),h={};g&&(h.xdomain=g),new b.Parent(e.id,f,h)}};return b.Parent=function(a,b,c){this.id=a,this.url=b,this.el=document.getElementById(a),this.iframe=null,this.settings={xdomain:"*"},this.messageRegex=f(this.id),this.messageHandlers={},c=c||{},this._constructIframe=function(){var a=this.el.offsetWidth.toString();this.iframe=document.createElement("iframe");var b="",c=this.url.indexOf("#");c>-1&&(b=this.url.substring(c,this.url.length),this.url=this.url.substring(0,c)),this.url.indexOf("?")<0?this.url+="?":this.url+="&",this.iframe.src=this.url+"initialWidth="+a+"&childId="+this.id+"&parentUrl="+encodeURIComponent(window.location.href)+b,this.iframe.setAttribute("width","100%"),this.iframe.setAttribute("scrolling","no"),this.iframe.setAttribute("marginheight","0"),this.iframe.setAttribute("frameborder","0"),this.el.appendChild(this.iframe),window.addEventListener("resize",this._onResize)},this._onResize=function(){this.sendWidth()}.bind(this),this._fire=function(a,b){if(a in this.messageHandlers)for(var c=0;c<this.messageHandlers[a].length;c++)this.messageHandlers[a][c].call(this,b)},this.remove=function(){window.removeEventListener("message",this._processMessage),window.removeEventListener("resize",this._onResize),this.el.removeChild(this.iframe)},this._processMessage=function(a){if(d(a,this.settings)&&"string"==typeof a.data){var b=a.data.match(this.messageRegex);if(!b||3!==b.length)return!1;var c=b[1],e=b[2];this._fire(c,e)}}.bind(this),this._onHeightMessage=function(a){var b=parseInt(a);this.iframe.setAttribute("height",b+"px")},this._onNavigateToMessage=function(a){document.location.href=a},this.onMessage=function(a,b){a in this.messageHandlers||(this.messageHandlers[a]=[]),this.messageHandlers[a].push(b)},this.sendMessage=function(a,b){this.el.getElementsByTagName("iframe")[0].contentWindow.postMessage(e(this.id,a,b),"*")},this.sendWidth=function(){var a=this.el.offsetWidth.toString();this.sendMessage("width",a)};for(var g in c)this.settings[g]=c[g];return this.onMessage("height",this._onHeightMessage),this.onMessage("navigateTo",this._onNavigateToMessage),window.addEventListener("message",this._processMessage,!1),this._constructIframe(),this},b.Child=function(b){this.parentWidth=null,this.id=null,this.parentUrl=null,this.settings={renderCallback:null,xdomain:"*",polling:0},this.messageRegex=null,this.messageHandlers={},b=b||{},this.onMessage=function(a,b){a in this.messageHandlers||(this.messageHandlers[a]=[]),this.messageHandlers[a].push(b)},this._fire=function(a,b){if(a in this.messageHandlers)for(var c=0;c<this.messageHandlers[a].length;c++)this.messageHandlers[a][c].call(this,b)},this._processMessage=function(a){if(d(a,this.settings)&&"string"==typeof a.data){var b=a.data.match(this.messageRegex);if(b&&3===b.length){var c=b[1],e=b[2];this._fire(c,e)}}}.bind(this),this._onWidthMessage=function(a){var b=parseInt(a);b!==this.parentWidth&&(this.parentWidth=b,this.settings.renderCallback&&this.settings.renderCallback(b),this.sendHeight())},this.sendMessage=function(a,b){window.parent.postMessage(e(this.id,a,b),"*")},this.sendHeight=function(){var a=document.getElementsByTagName("body")[0].offsetHeight.toString();this.sendMessage("height",a)}.bind(this),this.scrollParentTo=function(a){this.sendMessage("navigateTo","#"+a)},this.navigateParentTo=function(a){this.sendMessage("navigateTo",a)},this.id=c("childId")||b.id,this.messageRegex=new RegExp("^pym"+a+this.id+a+"(\\S+)"+a+"(.+)$");var f=parseInt(c("initialWidth"));this.parentUrl=c("parentUrl"),this.onMessage("width",this._onWidthMessage);for(var g in b)this.settings[g]=b[g];return window.addEventListener("message",this._processMessage,!1),this.settings.renderCallback&&this.settings.renderCallback(f),this.sendHeight(),this.settings.polling&&window.setInterval(this.sendHeight,this.settings.polling),this},g(),b});
////////////////////////////////////////////////////////////
//////////////////////// Set-up ////////////////////////////
////////////////////////////////////////////////////////////
// Quick fix for resizing some things for mobile-ish viewers
const mobileScreen = ($(window).innerWidth() < 500);
// Scatterplot
const margin = {
left: 30,
top: 20,
right: 20,
bottom: 20
};
const width = Math.min($('#chart').width(), 800) - margin.left - margin.right;
const height = width * 2 / 3;
const svg = d3.select('#chart')
.append('svg')
.attr('width', (width + margin.left + margin.right))
.attr('height', (height + margin.top + margin.bottom));
const wrapper = svg.append('g')
.attr('class', 'chordWrapper')
.attr('transform', `translate(${margin.left}, ${margin.top})`);
//////////////////////////////////////////////////////
///////////// Initialize Axes & Scales ///////////////
//////////////////////////////////////////////////////
const opacityCircles = 0.7;
// Set the color for each region
const color = d3.scale.ordinal()
.range([
'#EFB605',
'#E58903',
'#E01A25',
'#C20049',
'#991C71',
'#66489F',
'#2074A0',
'#10A66E',
'#7EB852'
])
.domain([
'Africa | North & East',
'Africa | South & West',
'America | North & Central',
'America | South',
'Asia | East & Central',
'Asia | South & West',
'Europe | North & West',
'Europe | South & East',
'Oceania'
]);
// Set the new x axis range
const xScale = d3.scale.log()
.range([0, width])
.domain([100, 2e5]); // I prefer this exact scale over the true range and then using 'nice'
// .domain(d3.extent(countries, function(d) { return d.GDP_perCapita; }))
// .nice();
// Set new x-axis
const xAxis = d3.svg.axis()
.orient('bottom')
.ticks(2)
.tickFormat(d => { // Difficult function to create better ticks
return xScale.tickFormat((mobileScreen ? 4 : 8), d => {
const prefix = d3.formatPrefix(d);
return `$${prefix.scale(d)}${prefix.symbol}`;
})(d);
})
.scale(xScale);
// Append the x-axis
wrapper.append('g')
.attr('class', 'x axis')
.attr('transform', `translate(0, ${height})`)
.call(xAxis);
// Set the new y axis range
const yScale = d3.scale.linear()
.range([height, 0])
.domain(d3.extent(countries, d => d.lifeExpectancy))
.nice();
const yAxis = d3.svg.axis()
.orient('left')
.ticks(6) // Set rough # of ticks
.scale(yScale);
// Append the y-axis
wrapper.append('g')
.attr('class', 'y axis')
.attr('transform', 'translate(0, 0)')
.call(yAxis);
// Scale for the bubble size
const rScale = d3.scale.sqrt()
.range([mobileScreen ? 1 : 2, mobileScreen ? 10 : 16])
.domain(d3.extent(countries, d => d.GDP));
////////////////////////////////////////////////////////////
/////////////////// Scatterplot Circles ////////////////////
////////////////////////////////////////////////////////////
// Initiate a group element for the circles
const circleGroup = wrapper.append('g')
.attr('class', 'circleWrapper');
// Place the country circles
circleGroup.selectAll('countries')
.data(countries.sort((a, b) => b.GDP > a.GDP)) //Sort so the biggest circles are below
.enter().append('circle')
.attr('class', d => `countries ${d.CountryCode}`)
.style('opacity', opacityCircles)
.style('fill', d => color(d.Region))
.attr('cx', d => xScale(d.GDP_perCapita))
.attr('cy', d => yScale(d.lifeExpectancy))
.attr('r', 2); // d => rScale(d.GDP)
//////////////////////////////////////////////////////////////
//////////////////////// Voronoi /////////////////////////////
//////////////////////////////////////////////////////////////
// Initiate the voronoi function
// Use the same variables of the data in the .x and .y as used in the cx and cy of the circle call
// The clip extent will make the boundaries end nicely along the chart area instead of splitting up the entire SVG
// (if you do not do this it would mean that you already see a tooltip when your mouse is still in the axis area, which is confusing)
const voronoi = d3.geom.voronoi()
.x(d => xScale(d.GDP_perCapita))
.y(d => yScale(d.lifeExpectancy))
.clipExtent([[0, 0], [width, height]]);
// Initiate a group element to place the voronoi diagram in
const voronoiGroup = wrapper.append('g')
.attr('class', 'voronoiWrapper');
// Create the Voronoi diagram
voronoiGroup.selectAll('path')
.data(voronoi(countries)) // Use vononoi() with your dataset inside
.enter().append('path')
.attr('d', d => `M${d.join('L')}Z`)
.datum(d => d.point)
// Give each cell a unique class where the unique part corresponds to the circle classes
.attr('class', d => `voronoi ${d.CountryCode}`)
.style('stroke', '#2074A0') // I use this to look at how the cells are dispersed as a check
.style('stroke-opacity', 0.3)
.style('fill', 'none')
.style('pointer-events', 'all')
.on('mouseover', showTooltip)
.on('mouseout', removeTooltip);
//////////////////////////////////////////////////////
///////////////// Initialize Labels //////////////////
//////////////////////////////////////////////////////
const labelPadding = 5;
// Set up X axis label
wrapper.append('g')
.append('text')
.attr('class', 'x title')
.attr('text-anchor', 'end')
.style('font-size', `${(mobileScreen ? 8 : 12)}px`)
.style('opacity', 0.7)
.attr('transform', `translate(${width - labelPadding}, ${(height - 10)})`)
.text('GDP per capita [US $] - Note the logarithmic scale');
// Set up y axis label
wrapper.append('g')
.append('text')
.attr('class', 'y title')
.attr('text-anchor', 'end')
.style('font-size', `${(mobileScreen ? 8 : 12)}px`)
.style('opacity', 0.7)
.attr('transform', `translate(18, ${labelPadding}) rotate(-90)`)
.text('Life expectancy');
///////////////////////////////////////////////////////////////////////////
///////////////////////// Create the Legend////////////////////////////////
///////////////////////////////////////////////////////////////////////////
if (!mobileScreen) {
// Legend
const legendMargin = {
left: 5,
top: 10,
right: 5,
bottom: 10
};
const legendWidth = 160;
const legendHeight = 270;
const svgLegend = d3.select('#legend').append('svg')
.attr('width', (legendWidth + legendMargin.left + legendMargin.right))
.attr('height', (legendHeight + legendMargin.top + legendMargin.bottom));
const legendWrapper = svgLegend.append('g')
.attr('class', 'legendWrapper')
.attr('transform', `translate(${legendMargin.left}, ${legendMargin.top})`);
const rectSize = 16; // dimensions of the colored square
const rowHeight = 22; // height of a row in the legend
// const maxWidth = 125; // width of each row
// Create container per rect/text pair
const legend = legendWrapper.selectAll('.legendSquare')
.data(color.range())
.enter().append('g')
.attr('class', 'legendSquare')
.attr('transform', (d, i) => `translate(0, ${(i * rowHeight)})`);
// Append small squares to Legend
legend.append('rect')
.attr('width', rectSize)
.attr('height', rectSize)
.style('fill', d => d);
// Append text to Legend
legend.append('text')
.attr('transform', `translate(25, ${(rectSize / 2)})`)
.attr('class', 'legendText')
.style('font-size', '11px')
.attr('dy', '.35em')
.text((d, i) => color.domain()[i]);
} else { // if !mobileScreen
d3.select('#legend')
.style('display', 'none');
}
///////////////////////////////////////////////////////////////////////////
/////////////////// Hover functions of the circles ////////////////////////
///////////////////////////////////////////////////////////////////////////
// Show the tooltip on the hovered over circle
function showTooltip(d) {
$(this).popover({
placement: 'auto top', // place the tooltip above the item
container: '#chart', // the name (class or id) of the container
trigger: 'manual',
html: true,
// the html content to show inside the tooltip
content: () => `<span style=font-size: 11px; text-align: center;>${d.Country}</span>`
});
$(this).popover('show');
// highlight the border of the current voronoi cell
d3.select(`path.${d.CountryCode}`)
.style('stroke', 'black')
.style('stroke-opacity', 0.5)
.style('stroke-width', 2);
}// function showTooltip
// Hide the tooltip when the mouse moves away
function removeTooltip(d) {
// Hide the tooltip
$('.popover').each(function () { return $(this).remove()});
// restore the border of the cell to its original color
d3.select(`path.${d.CountryCode}`)
.style('stroke', '#2074A0')
.style('stroke-opacity', 0.3)
.style('stroke-width', 1);
}// function removeTooltip
// iFrame handler
const pymChild = new pym.Child();
pymChild.sendHeight();
setTimeout(() => pymChild.sendHeight(), 5000);
View raw

(Sorry about that, but we can’t show files that are this big right now.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment