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?