US Map as Hexagons idea from NPR
A Pen by Jake Albaugh on CodePen.
<h1>Computer and Mathematical Occupations: May 2014 <a href="http://www.bls.gov/oes/current/oes150000.htm" target="blank">[source]</a></h1> |
US Map as Hexagons idea from NPR
A Pen by Jake Albaugh on CodePen.
// launchpad | |
function initializeMap() { | |
// creating base svg | |
var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); | |
// hexagon shape variables | |
var hex_di = 100, | |
// radius | |
hex_rad = hex_di / 2, | |
// apothem | |
hex_apo = hex_rad * Math.cos(Math.PI / 6), | |
// matrix defining state placement | |
states_grid = usStateMatrix(), | |
// data | |
states_data = usStateData(), | |
// rows we'll generate | |
rows = states_grid.length, | |
// columns we'll generate | |
cols = states_grid[0].length, | |
// stroke width around hexagon | |
stroke = 4, | |
// the hover state zoom scale | |
scale = 2, | |
// initial x | |
x = hex_rad * scale / 2 + stroke * scale, | |
// initial y | |
y = hex_rad * scale + stroke * scale, | |
// side length in pixels | |
side = Math.sin(Math.PI / 6) * hex_rad, | |
// height of map in pixels | |
height = (hex_di - side) * rows + side + hex_rad * scale + stroke * scale, | |
// width of map in pixels | |
width = (hex_apo * 2) * cols + hex_rad * scale + stroke * scale; | |
// svg attributes | |
svg.setAttribute("class", "us-states"); | |
svg.setAttribute("width", "100%"); | |
svg.setAttribute("height", "100%"); | |
svg.setAttribute("viewBox", "0 0 " + width + " " + height); | |
// loop variables | |
var offset = false, | |
// parsing state data | |
states = states_data.states, | |
data = states_data.data, | |
// initial state index | |
state_index = 0; | |
// getting range of data defaults | |
var hourly_mean_wage_max = 0, | |
hourly_mean_wage_min = 100, | |
annual_mean_wage_max = 0, | |
annual_mean_wage_min = 100000, | |
jobs_per_1000_max = 0, | |
jobs_per_1000_min = 10; | |
// for each data find max and min | |
for(var d = 0; d < data.length; d++) { | |
hourly_mean_wage_max = Math.max(hourly_mean_wage_max, data[d].hourly_mean_wage); | |
hourly_mean_wage_min = Math.min(hourly_mean_wage_min, data[d].hourly_mean_wage); | |
annual_mean_wage_max = Math.max(annual_mean_wage_max, data[d].annual_mean_wage); | |
annual_mean_wage_min = Math.min(annual_mean_wage_min, data[d].annual_mean_wage); | |
jobs_per_1000_max = Math.max(jobs_per_1000_max, data[d].jobs_per_1000); | |
jobs_per_1000_min = Math.min(jobs_per_1000_min, data[d].jobs_per_1000); | |
} | |
// getting differences in range | |
var hourly_mean_wage_dif = hourly_mean_wage_max - hourly_mean_wage_min, | |
annual_mean_wage_dif = annual_mean_wage_max - annual_mean_wage_min, | |
jobs_per_1000_dif = jobs_per_1000_max - jobs_per_1000_min; | |
// draw grid | |
for(var i = 0; i < states_grid.length; i++) { | |
var loop_x = offset ? hex_apo * 2 : hex_apo; | |
var loc_x = x; | |
for(var s = 0; s < states_grid[i].length; s++) { | |
// grid plot in 0 and 1 array | |
var grid_plot = states_grid[i][s]; | |
// if we have a plot in the grid | |
if (grid_plot != 0) { | |
// get the state | |
var state = states[state_index]; | |
// lookup data for state | |
for(var d = 0; d < data.length; d++) { | |
if (data[d].state == state.abbr) { | |
state.data = data[d]; | |
} | |
} | |
// ratio for fill on polygon | |
var ratio = (state.data.annual_mean_wage - annual_mean_wage_min) / annual_mean_wage_dif; | |
// create the hex group | |
var hexGroup = getHexGroup(svg, loc_x + loop_x , y, hex_rad, state, ratio, width, state.data); | |
// have to reappend element on hover for stacking | |
hexGroup.addEventListener("mouseenter", function () { | |
var self = this; | |
self.setAttribute("class", "hover"); | |
self.remove(); | |
svg.appendChild(self); | |
}); | |
// clear class | |
hexGroup.addEventListener("mouseleave", function () { | |
this.setAttribute("class", ""); | |
}); | |
// append the hex to our svg | |
svg.appendChild(hexGroup); | |
// increase the state index reference | |
state_index++; | |
} | |
// move our x plot to next hex position | |
loc_x += hex_apo * 2; | |
} | |
// move our y plot to next row position | |
y += hex_di * 0.75; | |
// toggle offset per row | |
offset = !offset; | |
} | |
// add svg to DOM | |
document.body.appendChild(svg); | |
} | |
// run the initialization script | |
initializeMap(); | |
// individual hex calculations | |
function getHexGroup(svg,x,y,r,state,ratio,width,data) { | |
var svgNS = svg.namespaceURI, // svgNS for creating svg elements | |
group = document.createElementNS(svgNS, "g"), | |
hex = document.createElementNS(svgNS, "polygon"), | |
abbr = document.createElementNS(svgNS, "text"), | |
name = document.createElementNS(svgNS, "text"), | |
pi_six = Math.PI/6, | |
cos_six = Math.cos(pi_six), | |
sin_six = Math.sin(pi_six); | |
// hexagon polygon points | |
var hex_points = [ | |
[x, y - r].join(","), | |
[x + cos_six * r, y - sin_six * r].join(","), | |
[x + cos_six * r, y + sin_six * r].join(","), | |
[x, y + r].join(","), | |
[x - cos_six * r, y + sin_six * r].join(","), | |
[x - cos_six * r, y - sin_six * r].join(",") | |
] | |
// hexagon fill based on ratio | |
var fill = "hsl(180,60%," + ((1 - ratio) * 60 + 20) + "%)"; | |
hex.setAttribute("points", hex_points.join(" ")); | |
hex.setAttribute("fill", fill); | |
abbr.setAttribute("class", "state-abbr"); | |
abbr.setAttribute("x", x); | |
abbr.setAttribute("y", y); | |
abbr.textContent = state.abbr; | |
name.setAttribute("class", "state-name"); | |
name.setAttribute("x", x); | |
name.setAttribute("y", y); | |
name.textContent = state.name; | |
// loop through data points | |
var index = 1, | |
// lineheight of data text | |
line_height = 20, | |
// starting y of data | |
start_y = 60; | |
for(var key in data) { | |
var text = document.createElementNS(svgNS, "text"); | |
text.setAttribute("x", width / 2); | |
text.setAttribute("y", index * line_height + start_y); | |
if(key != 'state') { | |
text.setAttribute("class", "data"); | |
text.textContent = keyToName(key) + ": " + data[key]; | |
} else { | |
text.setAttribute("class", "data title"); | |
text.textContent = state.name; | |
} | |
group.appendChild(text); | |
index++; | |
} | |
group.appendChild(hex); | |
group.appendChild(abbr); | |
group.appendChild(name); | |
return group; | |
} | |
function keyToName(str) { | |
return str.replace(/_/g,' ') | |
.replace(/\w\S*/g, function(txt) { | |
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); | |
}); | |
} | |
function usStateMatrix() { | |
return [ | |
[1,0,0,0,0,0,0,0,0,0,0,1], | |
[0,0,0,0,0,0,0,0,0,1,1,0], | |
[0,1,1,1,1,1,0,1,0,1,1,1], | |
[0,1,1,1,1,1,1,1,1,1,1,0], | |
[0,1,1,1,1,1,1,1,1,1,1,0], | |
[0,1,1,1,1,1,1,1,1,1,0,0], | |
[0,0,0,1,1,1,1,1,1,0,0,0], | |
[1,0,0,0,1,0,0,1,0,0,0,0] | |
] | |
} | |
function usStateData() { | |
return { | |
states: [ | |
{ abbr: "AK", name: "Alaska" }, | |
{ abbr: "ME", name: "Maine"}, | |
{ abbr: "VT", name: "Vermont" }, | |
{ abbr: "NH", name: "New Hampshire"}, | |
{ abbr: "WA", name: "Washington" }, | |
{ abbr: "MT", name: "Montana" }, | |
{ abbr: "ND", name: "North Dakota" }, | |
{ abbr: "MN", name: "Minnesota" }, | |
{ abbr: "WI", name: "Wisconsin" }, | |
{ abbr: "MI", name: "Michigan" }, | |
{ abbr: "NY", name: "New York" }, | |
{ abbr: "MA", name: "Massachusetts" }, | |
{ abbr: "RI", name: "Rhode Island"}, | |
{ abbr: "ID", name: "Idaho" }, | |
{ abbr: "WY", name: "Wyoming" }, | |
{ abbr: "SD", name: "South Dakota" }, | |
{ abbr: "IA", name: "Iowa" }, | |
{ abbr: "IL", name: "Illinois" }, | |
{ abbr: "IN", name: "Indiana" }, | |
{ abbr: "OH", name: "Ohio" }, | |
{ abbr: "PA", name: "Pennsylvania" }, | |
{ abbr: "NJ", name: "New Jersey" }, | |
{ abbr: "CT", name: "Connecticut"}, | |
{ abbr: "OR", name: "Oregon" }, | |
{ abbr: "NV", name: "Nevada" }, | |
{ abbr: "CO", name: "Colorado" }, | |
{ abbr: "NE", name: "Nebraska" }, | |
{ abbr: "MO", name: "Missouri" }, | |
{ abbr: "KY", name: "Kentucky" }, | |
{ abbr: "WV", name: "West Virgina" }, | |
{ abbr: "VA", name: "Virginia" }, | |
{ abbr: "MD", name: "Maryland" }, | |
{ abbr: "DE", name: "Delaware"}, | |
{ abbr: "CA", name: "California" }, | |
{ abbr: "UT", name: "Utah" }, | |
{ abbr: "NM", name: "New Mexico" }, | |
{ abbr: "KS", name: "Kansas" }, | |
{ abbr: "AR", name: "Arkansas" }, | |
{ abbr: "TN", name: "Tennessee" }, | |
{ abbr: "NC", name: "North Carolina" }, | |
{ abbr: "SC", name: "South Carolina" }, | |
{ abbr: "DC", name: "District of Columbia"}, | |
{ abbr: "AZ", name: "Arizona" }, | |
{ abbr: "OK", name: "Oklahoma" }, | |
{ abbr: "LA", name: "Louisiana" }, | |
{ abbr: "MS", name: "Mississippi" }, | |
{ abbr: "AL", name: "Alabama" }, | |
{ abbr: "GA", name: "Georgia"}, | |
{ abbr: "HI", name: "Hawaii" }, | |
{ abbr: "TX", name: "Texas" }, | |
{ abbr: "FL", name: "Florida" } | |
], | |
// Computer and Mathematical Occupations May 2014 | |
// http://www.bls.gov/oes/current/oes150000.htm | |
data: [ | |
{ state: "AL", hourly_mean_wage: 25.6, annual_mean_wage: 53250, jobs_per_1000: 0.348 }, | |
{ state: "AK", hourly_mean_wage: 28.78, annual_mean_wage: 59870, jobs_per_1000: 0.284 }, | |
{ state: "AZ", hourly_mean_wage: 30.93, annual_mean_wage: 64340, jobs_per_1000: 0.988 }, | |
{ state: "AR", hourly_mean_wage: 28.27, annual_mean_wage: 58810, jobs_per_1000: 0.432 }, | |
{ state: "CA", hourly_mean_wage: 38.23, annual_mean_wage: 79520, jobs_per_1000: 1.255 }, | |
{ state: "CO", hourly_mean_wage: 29.71, annual_mean_wage: 61800, jobs_per_1000: 1.231 }, | |
{ state: "CT", hourly_mean_wage: 33.03, annual_mean_wage: 68710, jobs_per_1000: 0.803 }, | |
{ state: "DE", hourly_mean_wage: 39.15, annual_mean_wage: 81440, jobs_per_1000: 0.917 }, | |
{ state: "DC", hourly_mean_wage: 37.84, annual_mean_wage: 78710, jobs_per_1000: 1.845 }, | |
{ state: "FL", hourly_mean_wage: 29.19, annual_mean_wage: 60720, jobs_per_1000: 0.989 }, | |
{ state: "GA", hourly_mean_wage: 35.96, annual_mean_wage: 74790, jobs_per_1000: 0.739 }, | |
{ state: "HI", hourly_mean_wage: 35.46, annual_mean_wage: 73760, jobs_per_1000: 0.557 }, | |
{ state: "ID", hourly_mean_wage: 24.18, annual_mean_wage: 50300, jobs_per_1000: 1.243 }, | |
{ state: "IL", hourly_mean_wage: 31.33, annual_mean_wage: 65160, jobs_per_1000: 0.714 }, | |
{ state: "IN", hourly_mean_wage: 25.96, annual_mean_wage: 53990, jobs_per_1000: 0.666 }, | |
{ state: "IA", hourly_mean_wage: 29.43, annual_mean_wage: 61200, jobs_per_1000: 0.569 }, | |
{ state: "KS", hourly_mean_wage: 27.83, annual_mean_wage: 57880, jobs_per_1000: 0.692 }, | |
{ state: "KY", hourly_mean_wage: 24.62, annual_mean_wage: 51220, jobs_per_1000: 0.494 }, | |
{ state: "LA", hourly_mean_wage: 24, annual_mean_wage: 49920, jobs_per_1000: 0.343 }, | |
{ state: "ME", hourly_mean_wage: 24.81, annual_mean_wage: 51600, jobs_per_1000: 0.639 }, | |
{ state: "MD", hourly_mean_wage: 36.28, annual_mean_wage: 75460, jobs_per_1000: 1.548 }, | |
{ state: "MA", hourly_mean_wage: 37.1, annual_mean_wage: 77170, jobs_per_1000: 1.192 }, | |
{ state: "MI", hourly_mean_wage: 29.21, annual_mean_wage: 60760, jobs_per_1000: 0.527 }, | |
{ state: "MN", hourly_mean_wage: 31.91, annual_mean_wage: 66380, jobs_per_1000: 1.151 }, | |
{ state: "MS", hourly_mean_wage: 26.51, annual_mean_wage: 55140, jobs_per_1000: 0.279 }, | |
{ state: "MO", hourly_mean_wage: 27.71, annual_mean_wage: 57630, jobs_per_1000: 0.571 }, | |
{ state: "MT", hourly_mean_wage: 23.44, annual_mean_wage: 48750, jobs_per_1000: 1.195 }, | |
{ state: "NE", hourly_mean_wage: 27.67, annual_mean_wage: 57550, jobs_per_1000: 1.017 }, | |
{ state: "NV", hourly_mean_wage: 28.26, annual_mean_wage: 58780, jobs_per_1000: 0.56 }, | |
{ state: "NH", hourly_mean_wage: 26.9, annual_mean_wage: 55950, jobs_per_1000: 1.254 }, | |
{ state: "NJ", hourly_mean_wage: 33.55, annual_mean_wage: 69780, jobs_per_1000: 0.609 }, | |
{ state: "NM", hourly_mean_wage: 28.98, annual_mean_wage: 60280, jobs_per_1000: 0.439 }, | |
{ state: "NY", hourly_mean_wage: 36.15, annual_mean_wage: 75180, jobs_per_1000: 1.017 }, | |
{ state: "NC", hourly_mean_wage: 30.95, annual_mean_wage: 64370, jobs_per_1000: 0.709 }, | |
{ state: "ND", hourly_mean_wage: 24.24, annual_mean_wage: 50410, jobs_per_1000: 0.488 }, | |
{ state: "OH", hourly_mean_wage: 29.44, annual_mean_wage: 61230, jobs_per_1000: 0.803 }, | |
{ state: "OK", hourly_mean_wage: 25.84, annual_mean_wage: 53740, jobs_per_1000: 0.443 }, | |
{ state: "OR", hourly_mean_wage: 33.29, annual_mean_wage: 69250, jobs_per_1000: 1.708 }, | |
{ state: "PA", hourly_mean_wage: 29.67, annual_mean_wage: 61710, jobs_per_1000: 0.694 }, | |
{ state: "RI", hourly_mean_wage: 33.3, annual_mean_wage: 69260, jobs_per_1000: 0.74 }, | |
{ state: "SC", hourly_mean_wage: 27.7, annual_mean_wage: 57620, jobs_per_1000: 0.451 }, | |
{ state: "SD", hourly_mean_wage: 29.82, annual_mean_wage: 62020, jobs_per_1000: 0.664 }, | |
{ state: "TN", hourly_mean_wage: 27.39, annual_mean_wage: 56980, jobs_per_1000: 0.503 }, | |
{ state: "TX", hourly_mean_wage: 32.21, annual_mean_wage: 66990, jobs_per_1000: 0.85 }, | |
{ state: "UT", hourly_mean_wage: 28.12, annual_mean_wage: 58490, jobs_per_1000: 1.559 }, | |
{ state: "VT", hourly_mean_wage: 31.53, annual_mean_wage: 65570, jobs_per_1000: 1.577 }, | |
{ state: "VA", hourly_mean_wage: 38.79, annual_mean_wage: 80690, jobs_per_1000: 1.233 }, | |
{ state: "WA", hourly_mean_wage: 39.62, annual_mean_wage: 82420, jobs_per_1000: 1.659 }, | |
{ state: "WV", hourly_mean_wage: 20.64, annual_mean_wage: 42940, jobs_per_1000: 0.371 }, | |
{ state: "WI", hourly_mean_wage: 27.14, annual_mean_wage: 56450, jobs_per_1000: 0.759 }, | |
{ state: "WY", hourly_mean_wage: 25.06, annual_mean_wage: 52130, jobs_per_1000: 0.444 } | |
] | |
} | |
} |
$stroke-width: 4; | |
$scale: 2; | |
svg { | |
position: absolute; | |
top: 50%; left: 50%; | |
transform: translate3d(-50%,-50%, 0); | |
g { | |
cursor: pointer; | |
polygon { | |
stroke: white; | |
stroke-width: $stroke-width; | |
transform-origin: 50% 50%; | |
transform: scale(1); | |
opacity: 0.5; | |
transition: opacity 200ms ease-in; | |
} | |
text { | |
text-anchor: middle; | |
alignment-baseline: middle; | |
transition: opacity 200ms ease-in; | |
} | |
text:not(.data) { | |
fill: white; | |
} | |
text.data { | |
fill: hsl(180,60%,60); | |
font-weight: 100; | |
&.title { | |
font-size: 20px; | |
fill: hsl(180,60%,30); | |
} | |
} | |
.state-name, .data { opacity: 0; } | |
.state-abbr { opacity: 1; } | |
&.hover { | |
.state-name, .data { animation: fade-in 300ms ease-in 200ms forwards; } | |
.state-abbr { animation: fade-out 300ms ease-in 200ms forwards; } | |
polygon { | |
opacity: 1; | |
transition: fill 200ms ease-in; | |
animation: scale-polygon 300ms ease-in forwards; | |
} | |
} | |
} | |
} | |
@keyframes scale-polygon { | |
from { transform: scale(1); stroke-width: $stroke-width; } | |
to { transform: scale($scale); stroke-width: $stroke-width / $scale; } | |
} | |
@keyframes fade-in { to { opacity: 1; } } | |
@keyframes fade-out { to { opacity: 0; } } | |
h1 { | |
color: #999; | |
font-weight: 100; | |
text-align: center; | |
font-size: 1em; | |
position: fixed; | |
top: 0; | |
left: 0; width: 100%; | |
z-index: 1; | |
background: rgba(255,255,255,0.8); | |
padding: 0.6em 0; | |
a { | |
color: hsl(180,60%,70); | |
text-decoration: none; | |
font-size: 0.7em; | |
} | |
} |
The mouseover events used to work well, but now the events generate awkward, wonky results.
Have browsers changed how they render some part of the css animations?
This is super cool. I'm trying to get it work on Firefox right now. Firefox doesn't render the zoom properly, and the mouseleave/mouseout event doesn't seem to be firing on firefox.