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?