|  | <!DOCTYPE html> | 
        
          |  | <head> | 
        
          |  | <meta charset="utf-8"> | 
        
          |  | <script src="https://d3js.org/d3.v4.min.js"></script> | 
        
          |  | <script src="paygaps.js"></script> | 
        
          |  | <style> | 
        
          |  | body { margin: 0; position: fixed; top: 0; right: 0; bottom: 0; left: 0; } | 
        
          |  |  | 
        
          |  | .enclosing-circle { | 
        
          |  | fill-opacity: 0.1; | 
        
          |  | } | 
        
          |  |  | 
        
          |  | .uni-label { | 
        
          |  | font-family: monospace; | 
        
          |  | font-size: 12px; | 
        
          |  | } | 
        
          |  |  | 
        
          |  | .count-label { | 
        
          |  | pointer-events: none; | 
        
          |  | font-family: sans-serif; | 
        
          |  | font-weight: bold; | 
        
          |  | fill: white; | 
        
          |  | } | 
        
          |  | </style> | 
        
          |  | </head> | 
        
          |  |  | 
        
          |  | <body> | 
        
          |  | <script> | 
        
          |  | var margin = { top: 50, right: 50, bottom: 50, left: 50 }; | 
        
          |  |  | 
        
          |  | var width = 960 - margin.left - margin.right, | 
        
          |  | height = 500 - margin.top - margin.bottom; | 
        
          |  |  | 
        
          |  | var svg = d3.select("body").append("svg") | 
        
          |  | .attr("width", width + margin.left + margin.right) | 
        
          |  | .attr("height", height + margin.top + margin.bottom) | 
        
          |  | .append("g") | 
        
          |  | .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); | 
        
          |  |  | 
        
          |  | var colour = d3.scaleLinear() | 
        
          |  | .range(["cyan", "magenta"]); | 
        
          |  |  | 
        
          |  | var radius = 5; | 
        
          |  |  | 
        
          |  | var initPos = { x: 50, y: 50 }, | 
        
          |  | paddingX = 110, | 
        
          |  | paddingY = 150; | 
        
          |  |  | 
        
          |  | var rowLength = 8; | 
        
          |  |  | 
        
          |  | function sortBy(attribute, order) { | 
        
          |  | paygaps.sort(function(a, b) { | 
        
          |  | if(a[attribute] < b[attribute]) return -1 * order; | 
        
          |  | if(a[attribute] > b[attribute]) return 1 * order; | 
        
          |  | return 0; | 
        
          |  | }); | 
        
          |  | } | 
        
          |  |  | 
        
          |  | // calculate ratios | 
        
          |  | paygaps.map(function(d) { | 
        
          |  | d.ratio = Math.round(d.max / d.min); | 
        
          |  | return d; | 
        
          |  | }); | 
        
          |  |  | 
        
          |  | colour.domain(d3.extent(paygaps, function(d) { | 
        
          |  | return d.ratio; | 
        
          |  | })) | 
        
          |  |  | 
        
          |  | sortBy("ratio", -1); | 
        
          |  |  | 
        
          |  | paygaps.forEach(function(p, i) { | 
        
          |  | var dots = d3.range(p.ratio).map(function(d) { | 
        
          |  | return { | 
        
          |  | circleId: i, | 
        
          |  | id: d | 
        
          |  | } | 
        
          |  | }); | 
        
          |  |  | 
        
          |  | var simulation = d3.forceSimulation(dots) | 
        
          |  | .force("x", d3.forceX(initPos.x + paddingX * (i % rowLength)).strength(1)) | 
        
          |  | .force("y", d3.forceY(initPos.y + paddingY * (Math.floor(i / rowLength))).strength(1)) | 
        
          |  | .force("collide", d3.forceCollide(radius + 1).iterations(6)) | 
        
          |  | .stop(); | 
        
          |  |  | 
        
          |  | for (var x = 0; x < 120; ++x) simulation.tick(); | 
        
          |  |  | 
        
          |  | addCircles(dots, i); | 
        
          |  | }) | 
        
          |  |  | 
        
          |  | function addCircles(data, i) { | 
        
          |  |  | 
        
          |  | var circles = svg.append("g") | 
        
          |  | .attr("class", "circles") | 
        
          |  | .selectAll("g").data(data) | 
        
          |  | .enter().append("g") | 
        
          |  | .each(function(d) { | 
        
          |  | d.r = radius; | 
        
          |  | }) | 
        
          |  | .style("fill", function(d) { | 
        
          |  | return colour(paygaps[d.circleId].ratio); | 
        
          |  | }); | 
        
          |  |  | 
        
          |  | circles.append("circle") | 
        
          |  | .attr("r", radius) | 
        
          |  | .attr("cx", function(d) { | 
        
          |  | return d.x; | 
        
          |  | }) | 
        
          |  | .attr("cy", function(d) { | 
        
          |  | return d.y; | 
        
          |  | }); | 
        
          |  |  | 
        
          |  | var enclosingCircleAttr = d3.packEnclose(circles.data()); | 
        
          |  |  | 
        
          |  | var enclosingCircle = svg.append("g") | 
        
          |  | .append("circle") | 
        
          |  | .datum(enclosingCircleAttr) | 
        
          |  | .attr("class", "enclosing-circle") | 
        
          |  | .attr("r", function(d) { | 
        
          |  | return d.r + radius; | 
        
          |  | }) | 
        
          |  | .attr("cx", function(d) { | 
        
          |  | return d.x; | 
        
          |  | }) | 
        
          |  | .attr("cy", function(d) { | 
        
          |  | return d.y; | 
        
          |  | }) | 
        
          |  | .on("mouseover", function() { | 
        
          |  | count | 
        
          |  | .style("opacity", 1) | 
        
          |  | enclosingCircle | 
        
          |  | .style("fill-opacity", 0.5) | 
        
          |  | }) | 
        
          |  | .on("mouseout", function() { | 
        
          |  | count | 
        
          |  | .style("opacity", 0) | 
        
          |  | enclosingCircle | 
        
          |  | .style("fill-opacity", 0.1) | 
        
          |  | }) | 
        
          |  |  | 
        
          |  | var count = svg.append("g") | 
        
          |  | .append("text") | 
        
          |  | .attr("class", "count-label") | 
        
          |  | .datum(enclosingCircleAttr) | 
        
          |  | .attr("text-anchor", "middle") | 
        
          |  | .attr("x", function(d) { | 
        
          |  | return d.x; | 
        
          |  | }) | 
        
          |  | .attr("y", function(d) { | 
        
          |  | return d.y; | 
        
          |  | }) | 
        
          |  | .attr("dy", function(d) { | 
        
          |  | return (d.r / 2) - (d.r / 8); | 
        
          |  | }) | 
        
          |  | .text(paygaps[i].ratio + ":1") | 
        
          |  | .style("font-size", function(d) { | 
        
          |  | return Math.min(1.5 * d.r, (1.5 * d.r - 8) / this.getComputedTextLength() * 24) + "px"; | 
        
          |  | }) | 
        
          |  | .style("opacity", 0); | 
        
          |  |  | 
        
          |  | var label = svg.append("text") | 
        
          |  | .attr("class", "uni-label") | 
        
          |  | .attr("text-anchor", "middle") | 
        
          |  | .attr("x", enclosingCircleAttr.x) | 
        
          |  | .attr("y", enclosingCircleAttr.y + enclosingCircleAttr.r + 25) | 
        
          |  | .text(paygaps[i].name); | 
        
          |  | } | 
        
          |  |  | 
        
          |  | </script> | 
        
          |  | </body> |