Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ja-k-e/4da63c9fc39ab12714f8 to your computer and use it in GitHub Desktop.
Save ja-k-e/4da63c9fc39ab12714f8 to your computer and use it in GitHub Desktop.
Interactive SVG US States Hexagon Map
<h1>Computer and Mathematical Occupations: May 2014 <a href="http://www.bls.gov/oes/current/oes150000.htm" target="blank">[source]</a></h1>
// 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;
}
}
@kcpt-steven-kohlmeyer
Copy link

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.

@mg1075
Copy link

mg1075 commented Mar 27, 2018

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?

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