1var formatDecimal = d3.format(".2n"); 2var formatDecimal2 = d3.format(".2n"); 3var formatThousand = d3.format(",.0f"); 4var CURRENT_DISPLAY_YEAR = "2025"; 5var selectedYear = "Current"; 6var uniqueYearsArray = [] 7var dataset; 8var csvData; 9var workrole_top_skills; 10var master_ksa_task; // pair ksat_id + ksat description 11var workrole_kt_mapping; // pair workrole id + ksat_id 12var niceKeys; 13var testKeys 14var postingsArr = []; 15var theTreeMap, theMap; 16var rateByStateId, rateByMsaId; 17var clickedId = 0; 18var currentView = "states"; 19var quantile, selectedVar; 20var mapW; 21var storedCxStSd, storedCxMsaSd, storedCxStLq, storedCxMsaLq; 22var dataKind = "all"; 23var states_list, msas_list, name_lookup_map = new Map(); 24var us_map_data; 25var globalmode; 26var selectedState = ''; 27 28var heatMap = function (opts) { 29 this.geo = opts.geo; 30 this.element = opts.element; 31 this.view = opts.view; 32 33 this.draw(); 34 this.setView(); 35 this.update(); 36}; 37 38var holdersBarChart; 39var reqCertBarChart; 40 41 42 43heatMap.prototype.draw = function () { 44 this.setDimensions(); 45 // this.element.innerHTML = ""; 46 this.svg = d3.select(this.element).append("svg") 47 48 this.element.style.width = this.width + "px"; 49 this.svg.attr("width", this.width); 50 this.svg.attr("height", this.height); 51 52 this.centered = null; //Store path data if map is zoomed to path 53 this.isZoomed = clickedId ? true : false; //Store path data if map is zoomed to path 54 this.maxZoom = 2.5; //Level to zoom into when area or region is clicked. 55 this.lineStroke = 1; //Stroke width to maintain at various zoom levels. 56 57 this.homeButton(); //Add a "reset map" button to the target element 58 59 /* DRAW THE MAP FEATURES */ 60 var _this = this; 61 62 this.plot = this.svg 63 .append("g") 64 .attr( 65 "transform", 66 "translate(" + this.margin.left + "," + this.margin.top + ")" 67 ) 68 .attr("class", "map-g"); 69 70 //Append a background element 71 this.plot 72 .append("rect") 73 .attr("class", "background") 74 .attr("x", 0) 75 .attr("y", 0) 76 .attr("width", this.width) 77 .attr("height", this.height) 78 .on("click", function (d) { 79 var el = this; 80 81 if (_this.isZoomed) { 82 _this.clicked(d, el); 83 } 84 d3.selectAll(".smallTitle-barchart").html(`NATIONAL, ${selectedYear === 'Current' ? CURRENT_DISPLAY_YEAR : selectedYear}`) 85 86 }); 87 88 // change job openings and workforce popup text after changing selectedYear 89 90 if (selectedYear !== 'Current') { 91 d3.select('.job-openings-def').html(`Shows the number of online job listings for cybersecurity-related 92 positions for the year ${selectedYear}.`) 93 d3.select('.total-workforce-def').html(`Shows the estimated number of workers employed in cybersecurity-related 94 jobs for the year ${selectedYear}.`) 95 } else if (selectedYear === 'Current') { 96 d3.select('.job-openings-def').html(`Shows the number of online job listings for cybersecurity-related positions from September 2023 through August 2024.`) 97 d3.select('.total-workforce-def').html(`Shows the estimated number of workers employed in cybersecurity-related jobs from September 2023 through August 2024. 98 `) 99 } 100 101 //Append the tooltip 102 this.tooltipDiv = d3 103 .select(this.element) 104 .append("div") 105 .attr("class", "tooltip") 106 .style('border-radius', '5px') 107 .style('background-color', 'white') 108 .style('border', '1px solid rgba(0,0,0,0)') 109 110 this.resetProjection(); 111 112 /* ------------------- */ 113 /* STATES */ 114 /* ------------------- */ 115 116 var states = this.plot 117 .append("g") 118 .attr("class", "states-g") 119 .attr("transform", () => { 120 if (mapW > 600) return "translate(-" + this.width * 0.06 + ",0)"; 121 else return ""; 122 }); 123 124 var paths = states 125 .selectAll("path") 126 .data(topojson.feature(this.geo, this.geo.objects.states).features) 127 .enter() 128 .append("path") 129 .attr("d", _this.path) 130 .attr('stroke-width', 2.5) 131 .attr('stroke-linejoin', 'round') 132 .attr('stroke-linecap', 'round') 133 .attr("class", "state feature") 134 .attr("id", function (d) { 135 return "fips_" + d.id; 136 }); 137 138 //SORTING PATHS TOP TO BOTTOM FOR THE TRANSITION EFFECT 139 140 for (var i = 0; i < paths[0].length; i++) { 141 paths[0][i].__data__["centroidY"] = this.path.centroid( 142 paths[0][i].__data__ 143 )[1]; 144 } 145 paths.sort(function (a, b) { 146 return d3.ascending(a.centroidY, b.centroidY); 147 }); 148 149 150 /* ------------------- */ 151 /* MSAs */ 152 /* ------------------- */ 153 154 var msas = this.plot.append("g").attr("class", "msas-g"); 155 156 var stateBckg = msas 157 .selectAll("stateBckg") 158 .data(topojson.feature(this.geo, this.geo.objects.states).features) 159 .enter() 160 .append("path") 161 .attr("d", _this.path) 162 .attr("class", "stateBckg") 163 .attr("id", function (d) { 164 return "bgfips_" + d.id; 165 }) 166 .attr("fill", "#dddddd") 167 .attr("stroke", "#fff"); 168 169 170 171 172 var couPaths = msas 173 .selectAll("msa") 174 .data(topojson.feature(this.geo, this.geo.objects.msa).features) 175 .enter() 176 .append("path") 177 .attr("d", _this.path) 178 .attr("class", function (d) { 179 180 if (typeof dataset[d.properties.geoid] != "undefined") { 181 if (+dataset[d.properties.geoid][0].population < 250000) { 182 return "msa feature small";
183 } else if (+dataset[d.properties.geoid][0].population > 1000000) { 184 return "msa feature large"; 185 } else return "msa feature medium"; 186 } 187 }) 188 .attr("id", function (d) { 189 return "fips_" + d.properties.geoid; 190 }); 191 192 193 //SORTING PATHS TOP TO BOTTOM FOR THE TRANSITION EFFECT 194 for (var i = 0; i < couPaths[0].length; i++) { 195 couPaths[0][i].__data__["centroidY"] = this.path.centroid( 196 couPaths[0][i].__data__ 197 )[1]; 198 } 199 couPaths.sort(function (a, b) { 200 return d3.ascending(a.centroidY, b.centroidY); 201 }); 202 203 //Assign mouse events to all geographies 204 this.setListeners(); 205}; 206 207heatMap.prototype.setListeners = function () { 208 var _this = this; 209 var features = this.plot 210 .selectAll(".feature") 211 .on("mouseover", function (d) { 212 213 d3v6.select(this).raise() 214 if (d3.select(this).classed("active") != true) { 215 d3.select(this).classed("active", true); 216 d3.select("#fips_" + clickedId) 217 _this.tooltip(d); 218 _this.tooltipDiv.style("display", "block"); 219 } 220 }) 221 .on("mouseout", function (d) { 222 d3.select(this).classed("active", false); 223 _this.tooltipDiv.style("display", "none"); 224 }) 225 .on("mousemove", function () { 226 //Get page offset position of map container 227 //This tooltip positioning method should work across browsers 228 var bodyRect = document.body.getBoundingClientRect(), 229 elemRect = _this.element.getBoundingClientRect(), 230 offsetTop = elemRect.top - bodyRect.top, 231 offsetLeft = elemRect.left - bodyRect.left; 232 233 //Mouse positions 234 var xPos = d3.event.pageX - offsetLeft; 235 var yPos = d3.event.pageY - offsetTop; 236 237 //Tooltip dimensions 238 var ttWidth = parseInt( 239 _this.tooltipDiv.style("width").replace("px", ""), 240 10 241 ); 242 var ttHeight = parseInt( 243 _this.tooltipDiv.style("height").replace("px", ""), 244 10 245 ); 246 247 //Tooltip positions 248 var ttLeft = xPos - ttWidth / 2; 249 var ttTop = yPos - ttHeight - 30; 250 251 //Some spacing logic to ensure tooltip doesn't get cut off by parent container 252 var maxRight = _this.width - ttWidth / 2; 253 254 //If too far to the right 255 if (ttLeft + ttWidth / 2 >= maxRight) { 256 ttLeft = maxRight - ttWidth / 2; 257 } 258 259 //If too close to the top 260 if (ttTop < 0) { 261 ttTop = yPos + 30; 262 } 263 264 //If too far to the left 265 if (ttLeft < 0) { 266 ttLeft = 0; 267 } 268 269 _this.tooltipDiv.style({ 270 top: ttTop + "px", 271 left: ttLeft + "px", 272 }); 273 }) 274 .on("click", function (d) { 275 _this.tooltipDiv.style("display", "none"); 276 var el = this; 277 278 if (globalmode != "widget") updateTable(d); 279 _this.clicked(d, el); 280 }); 281}; 282 283heatMap.prototype.setColor = function (val) { 284 if (!val) { 285 return this.colors.blank; //Would add logic to here to color feature by result 286 } 287}; 288 289heatMap.prototype.resetProjection = function () { 290 if (mapW > 600) { 291 projectionRatio = 1; 292 } else projectionRatio = 1.35; 293 294 this.path = d3.geo.path(); 295 this.projection = d3.geo 296 .albersUsa() 297 .scale(this.width * projectionRatio) 298 .translate([this.width / 2, this.height / 2]); 299 300 this.path.projection(this.projection); 301}; 302 303heatMap.prototype.setDimensions = function () { 304 // define width, height and margin 305 this.width = this.element.offsetWidth; 306 if (mapW > 600) { 307 this.height = this.element.offsetWidth * 0.46; //0.46; 308 } else { 309 this.height = this.element.offsetWidth * 0.76; 310 } /// 0.76; 311 312 this.margin = { 313 top: 0, 314 right: 0, 315 bottom: 0, 316 left: 0, 317 }; 318}; 319 320//Set view as "states" or "msas" 321 322heatMap.prototype.setView = function () { 323 var states = this.plot.select(".states-g"); 324 var msas = this.plot.select(".msas-g"); 325 326 if (this.view === "states") { 327 states.style("display", "block"); 328 msas.style("display", "none"); 329 330 d3.selectAll(".coun").style("display", "none"); 331 d3.selectAll(".st").style("display", "block"); 332 333 d3.select("#location_quotientSvg") 334 .select(".avLine") 335 .attr("x1", function (d) { 336 return storedCxStLq(d.ratio); 337 }) 338 .attr("x2", function (d) { 339 return storedCxStLq(d.ratio); 340 }); 341 342 d3.select("#location_quotientSvg") 343 .select(".avTxt") 344 .attr("x", function (d) { 345 return storedCxStLq(d.ratio); 346 }); 347 348 d3.select("#location_quotientSvg") 349 .select(".avNum") 350 .attr("x", function (d) { 351 return storedCxStLq(d.ratio); 352 }); 353 354 d3.selectAll(".legendTxt").text(function (d, i) { 355 return setLegendLabel(d, i); 356 }); 357 358 d3.select("#search").attr("placeholder", "Search State"); 359 d3.select(".filterBtns").style("display", "none"); 360 d3.selectAll(".noMsaR").style("display", "none"); 361 d3.selectAll(".noMsaT").style("display", "none"); 362 363 currentView = "states"; 364 } else if (this.view === "msas") { 365 states.style("display", "none"); 366 msas.style("display", "block"); 367 368 d3.selectAll(".coun").style("display", "block"); 369 d3.selectAll(".st").style("display", "none"); 370 371 d3.select("#location_quotientSvg") 372 .select(".avLine") 373 .attr("x1", function (d) { 374 return storedCxMsaLq(d.ratio); 375 }) 376 .attr("x2", function (d) { 377 return storedCxMsaLq(d.ratio); 378 }); 379 d3.select("#location_quotientSvg") 380 .select(".avTxt") 381 .attr("x", function (d) { 382 return storedCxMsaLq(d.ratio); 383 }); 384 d3.select("#location_quotientSvg") 385 .select(".avNum") 386 .attr("x", function (d) { 387 return storedCxMsaLq(d.ratio); 388 }); 389 d3.selectAll(".legendTxt").text(function (d, i) { 390 return setLegendLabel(d, i); 391 }); 392 393 d3.select("#search").attr("placeholder", "Search Metro Area"); 394 395 d3.select(".filterBtns").style("display", "block"); 396 d3.selectAll(".noMsaR").style("display", "block"); 397 d3.selectAll(".noMsaT").style("display", "block"); 398 399 currentView = "msas"; 400 } 401 402 if (currentView === 'states') { 403 d3.selectAll(".text-label-group.county").style("opacity", 0); 404 d3.selectAll(".text-label-group.state").style("opacity", 1); 405 } else { 406 d3.selectAll(".text-label-group.county").style("opacity", 1); 407 d3.selectAll(".text-label-group.state").style("opacity", 0); 408 } 409 410 var nationalY = 0; 411 412 d3.selectAll('.supply_demand_ratio_nationalAvgGroup') 413 .attr('transform', function (d) { 414 const y = currentView === 'states' ? storedCxStSd(d.ratio) : storedCxMsaSd(d.ratio); 415 nationalY = y; 416 return `translate(0, ${y})` 417 }); 418 419 d3.select("#supply_demand_ratioSvg") 420 .selectAll("g.text-label-group") 421 .attr('transform', function (d) { 422 let y = currentView === 'states' ? storedCxStSd(d.ratio) : storedCxMsaSd(d.ratio); 423 d.yCoord = y; 424 return `translate(0, ${y})` 425 }).each(function (d) { 426 let y = 0; 427 let dy = Math.abs(d.yCoord - nationalY); 428 429 if (dy < 20) { 430 if (nationalY >= d.yCoord) { 431 y = dy - 22; 432 } else { 433 y = 22 - dy; 434 } 435 } 436 d3.select(this).select(".ratio-number").attr('y', y) 437 d3.select(this).select(".ratio-state").attr('y', y) 438 }); 439}; 440 441 442/* ---------------------------------------- */ 443/* FIRE THIS FUNCTION TO UPDATE MAP DATA */ 444/* ---------------------------------------- */ 445 446heatMap.prototype.update = function () { 447 this.setDimensions(); 448 449 //Update svg dimensions 450 this.svg.attr("width", this.width); 451 this.svg.attr("height", this.height); 452 this.resetProjection(); 453 d3.selectAll(".state").attr("d", this.path); 454 d3.selectAll(".msa").attr("d", this.path); 455 d3.selectAll(".stateBckg").attr("d", this.path); 456 d3.select(".leg").attr( 457 "transform", 458 "translate(" + this.width / 1.2 + "," + this.height *0.3 + ")" 459 ); 460 d3.select(".instr") 461 .attr("x", this.width / 1.75) 462 .attr("y", this.height * 0.08); 463}; 464 465heatMap.prototype.homeButton = function () { 466 var _this = this; 467 468 d3.select(this.element) 469 .append("button") 470 .attr("class", "home-btn") 471 .html("− ") 472 .on("click", function (d) { 473 console.log(d) 474 // selectedState = d.properties.state 475 //Recenter map 476 var el = this; 477 if (_this.isZoomed) { 478 _this.clicked(d, el); 479 } 480 d3.selectAll(".smallTitle-barchart").html(`NATIONAL, ${selectedYear === 'Current' ? CURRENT_DISPLAY_YEAR : selectedYear}`) 481 }); 482}; 483 484// Zoom to center of selected feature when clicked 485heatMap.prototype.clicked = function (d, el) { 486 // selectedState = d.properties.state 487 var x, y, k; //left, top, zoom 488 d3.selectAll(".feature").style("stroke-width", "1px"); 489 var _this = this; 490 if (typeof d != "undefined" && d.id) { 491 if (d && _this.centered !== d) { 492 _this.centered = d; 493 _this.isZoomed = true; 494 d3.selectAll(".feature").classed("clicked", false); 495 d3.selectAll(".feature").classed("active", false); 496 d3.select(el) 497 .moveToFront() 498 .classed("centered", true) 499 .classed("clicked", true) 500 .style("stroke-width", "3px"); 501 _this.isZoomed = true; 502 clickedId = d.id; 503 504 // DIRECT UPDATE to value-text with the state ratio value 505 const stateData = csvData.filter(d2 => d2.fips == d.id && d2.year === selectedYear); 506 if (stateData.length > 0 && stateData[0].supply_demand_ratio) { 507 let stateRatioValue = Math.round(stateData[0].supply_demand_ratio * 100); 508 if (isNaN(stateRatioValue)) { 509 stateRatioValue = 'N/A'; 510 } 511 512 // Use direct DOM manipulation to ensure value is updated 513 document.querySelector(".supply-demand-ratio-card .value-text").textContent = stateRatioValue + "%"; 514 515 // DIRECT UPDATE to the ratio-fill-bar height 516 const barHeight = 10 + (stateRatioValue * 0.9) + "%"; 517 document.querySelector("#ratio-fill-bar").style.height = barHeight; 518 519 520 } 521 } else { 522 _this.centered = null; 523 _this.isZoomed = false;
524 d3.select(el).classed("clicked", false); 525 d3.selectAll(".feature").classed("clicked", false); 526 _this.isZoomed = false; 527 clickedId = 0; 528 d3.select(".region").html("National Level"); 529 drawOpenings(0); 530 d3.selectAll("circle.ratioCirc") 531 .style("opacity", 0.5) 532 .attr("stroke", "none"); 533 d3.selectAll(".ratioNumber").style("display", "none"); 534 d3.selectAll(".ratioState").style("display", "none"); 535 d3.selectAll('.text-label-group').style("display", "none"); 536 d3.selectAll(".cirG").select("rect").style("display", "none"); 537 d3.select("#cf0") 538 .moveToFront() 539 .style("opacity", 1) 540 .attr("stroke", "#455A64") 541 .attr("stroke-width", "2px"); 542 d3.select("#tf0").style("display", "block"); 543 d3.select("#tsf0").style("display", "block"); 544 545 // RESET value-text to national average 546 const nationalData = (typeof csvData !== 'undefined' && csvData) 547 ? csvData.filter(d => d.fips == 0 && d.year === selectedYear) 548 : []; 549 if (nationalData.length > 0 && nationalData[0].supply_demand_ratio) { 550 let nationalRatioValue = Math.round(nationalData[0].supply_demand_ratio * 100); 551 if (isNaN(nationalRatioValue)) { 552 nationalRatioValue = 'N/A'; 553 } 554 555 // Use direct DOM manipulation to ensure value is updated 556 document.querySelector(".supply-demand-ratio-card .value-text").textContent = nationalRatioValue + "%"; 557 558 // DIRECT UPDATE to the ratio-fill-bar height 559 const nationalBarHeight = 10 + (nationalRatioValue * 0.9) + "%"; 560 document.querySelector("#ratio-fill-bar").style.height = nationalBarHeight; 561 562 // Set the appropriate bar color based on the ratio value 563 let barColor = "#2F80ED"; // Default blue for good ratio 564 if (nationalRatioValue < 75) { 565 barColor = "#F2994A"; // Orange for low ratio 566 } else if (nationalRatioValue > 110) { 567 barColor = "#27AE60"; // Green for high ratio 568 } 569 document.querySelector("#ratio-fill-bar").style.backgroundColor = barColor; 570 } 571 572 if (globalmode != "widget") { 573 theTreeMap.updateTreemap(dataset[0][0]); 574 holdersBarChart.updateBars(0); 575 reqCertBarChart.updateBars(0); 576 d3.selectAll(".qualText").text(""); 577 d3.selectAll(".qualifiers").style("opacity", 0.4); 578 d3.select('.smallTitle').html(''); 579 580 // Add a label to the national average line for comparison 581 const nationalAvgLabel = d3.select(".national-average-label"); 582 if (window.nationalAverageRatio) { 583 const nationalAvgDashedLine = d3.select(".national-average-dashed-line"); 584 if (nationalAvgDashedLine.node()) { 585 if (!nationalAvgLabel.node()) { 586 nationalAvgDashedLine 587 .append("div") 588 .attr("class", "national-average-label") 589 .style("position", "absolute") 590 .style("right", "0") 591 .style("transform", "translateY(-50%)") 592 .style("background-color", "rgba(0,0,0,0.6)") 593 .style("color", "white") 594 .style("padding", "2px 6px") 595 .style("border-radius", "4px") 596 .style("font-size", "10px") 597 .text(window.nationalAverageRatio + "% Natl. Avg"); 598 } else if (nationalAvgLabel.node()) { 599 nationalAvgLabel.text(window.nationalAverageRatio + "% Natl. Avg"); 600 } 601 } 602 } 603 } 604 } 605 } else { 606 if (d && _this.centered !== d) { 607 var centroid = _this.path.centroid(d); 608 x = centroid[0]; 609 y = centroid[1]; 610 k = _this.maxZoom; 611 _this.centered = d; 612 d3.selectAll(".feature").classed("clicked", false); 613 d3.select(el) 614 .moveToFront() 615 .classed("centered", true) 616 .classed("clicked", true) 617 .style("stroke-width", "3px"); 618 _this.isZoomed = true; 619 _this.view === "states" 620 ? (clickedId = d.id) 621 : (clickedId = d.properties.geoid); 622 623 // DIRECT UPDATE to value-text with the metro area ratio value 624 if (d.properties && d.properties.geoid) { 625 const msaData = csvData.filter(d2 => d2.fips == d.properties.geoid && d2.year === selectedYear); 626 if (msaData.length > 0 && msaData[0].supply_demand_ratio) { 627 let msaRatioValue = Math.round(msaData[0].supply_demand_ratio * 100); 628 if (isNaN(msaRatioValue)) { 629 msaRatioValue = 'N/A'; 630 } 631 632 // Use direct DOM manipulation to ensure value is updated 633 document.querySelector(".supply-demand-ratio-card .value-text").textContent = msaRatioValue + "%"; 634 635 // DIRECT UPDATE to the ratio-fill-bar height 636 const msaBarHeight = 10 + (msaRatioValue * 0.9) + "%"; 637 document.querySelector("#ratio-fill-bar").style.height = msaBarHeight; 638 639 // Update total job openings and employed workforce cards 640 if (msaData[0].total_postings) { 641 document.querySelector(".total-job-openings").textContent = formatThousand(Math.round(msaData[0].total_postings)); 642 } 643 644 if (msaData[0].employment_estimate) { 645 document.querySelector(".total-employed-workforce").textContent = formatThousand(Math.round(msaData[0].employment_estimate)); 646 } 647 648 // Update the date-level labels 649 const msaDateLabel = `${selectedState.toUpperCase()}, ${selectedYear === 'Current' ? CURRENT_DISPLAY_YEAR : selectedYear}`; 650 document.querySelectorAll('.date-level-label').forEach(el => { 651 el.textContent = msaDateLabel; 652 }); 653 654 // Update the label to indicate we're showing metro area data 655 const nationalLabelText = d3.select(".national-label-text"); 656 if (nationalLabelText.node()) { 657 nationalLabelText.text(selectedState); 658 // Ensure the styling is maintained when changing from national to state/metro 659 nationalLabelText 660 .style("background-color", "rgba(0,0,0,0.5)") 661 .style("color", "white") 662 .style("padding", "2px 8px") 663 .style("border-radius", "4px"); 664 } 665 666 // Add a label to the national average line for comparison 667 const nationalAvgLabel = d3.select(".national-average-label"); 668 if (window.nationalAverageRatio) { 669 const nationalAvgDashedLine = d3.select(".national-average-dashed-line"); 670 if (nationalAvgDashedLine.node()) { 671 if (!nationalAvgLabel.node()) { 672 nationalAvgDashedLine 673 .append("div") 674 .attr("class", "national-average-label") 675 .style("position", "absolute") 676 .style("right", "0") 677 .style("transform", "translateY(-50%)") 678 .style("background-color", "rgba(0,0,0,0.6)") 679 .style("color", "white") 680 .style("padding", "2px 6px") 681 .style("border-radius", "4px") 682 .style("font-size", "10px") 683 .text(window.nationalAverageRatio + "% Natl. Avg"); 684 } else if (nationalAvgLabel.node()) { 685 nationalAvgLabel.text(window.nationalAverageRatio + "% Natl. Avg"); 686 } 687 } 688 } 689 } 690 } 691 d3.select(".home-btn").style("display", "block"); 692 _this.plot 693 .selectAll(".feature:not(.clicked)") 694 .transition() 695 .duration(750) 696 .style("stroke-width", _this.lineStroke / 2 + "px"); 697 d3.select(".instruction").style("display", "none"); 698 _this.plot 699 .selectAll(".stateBckg") 700 .transition() 701 .duration(750) 702 .style("stroke-width", _this.lineStroke / 2 + "px"); 703 } 704 else { 705 x = _this.width / 2; 706 y = _this.height / 2 - _this.margin.top; 707 k = 1; 708 _this.centered = null; 709 d3.select(el).classed("centered", false); 710 d3.selectAll(".feature").classed("clicked", false); 711 _this.isZoomed = false;
712 clickedId = 0; 713 d3.select(".home-btn").style("display", "none"); 714 _this.setListeners(); 715 d3.select(".region").html("National Level"); 716 717 // Reset selectedState 718 selectedState = ''; 719 720 // Reset the national-label-text back to "National average" 721 const nationalLabelText = d3.select(".national-label-text"); 722 if (nationalLabelText.node()) { 723 nationalLabelText.text("National average"); 724 // Ensure styling is consistent 725 nationalLabelText 726 .style("background-color", "rgba(0,0,0,0.5)") 727 .style("color", "white") 728 .style("padding", "2px 8px") 729 .style("border-radius", "4px"); 730 } 731 732 // Remove the national average comparison label if it exists 733 const nationalAvgLabel = d3.select(".national-average-label"); 734 if (nationalAvgLabel.node()) { 735 nationalAvgLabel.remove(); 736 } 737 738 // Call drawOpenings to update the cards with national data 739 drawOpenings(0); 740 741 d3.selectAll("circle.ratioCirc") 742 .style("opacity", 0.5) 743 .attr("stroke", "none"); 744 745 if (globalmode != "widget") { 746 theTreeMap.updateTreemap(dataset[0][0]); 747 holdersBarChart.updateBars(0); 748 reqCertBarChart.updateBars(0); 749 d3.select(".instruction").style("display", "block"); 750 d3.selectAll(".qualText").text(""); 751 d3.selectAll(".qualifiers").style("opacity", 0.4); 752 _this.plot 753 .selectAll(".feature") 754 .transition() 755 .duration(750) 756 .style("stroke-width", _this.lineStroke + "px"); 757 _this.plot 758 .selectAll(".stateBckg") 759 .transition() 760 .duration(750) 761 .style("stroke-width", _this.lineStroke + "px"); 762 } 763 } 764 _this.plot.classed("zoomed", _this.isZoomed); 765 _this.zoomScale(k, x, y); 766 } 767}; 768 769//Set new scale and translate position and size strokes according to scale. 770heatMap.prototype.zoomScale = function (k, x, y) { 771 var _this = this; 772 _this.plot 773 .transition() 774 .duration(750) 775 .attr( 776 "transform", 777 "translate(" + 778 _this.width / 2 + 779 "," + 780 _this.height / 2 + 781 ")scale(" + 782 k + 783 ")translate(" + 784 -x + 785 "," + 786 -y + 787 ")" 788 ); 789}; 790 791heatMap.prototype.tooltip = function (d) { 792 var txt; 793 var elem = document.getElementById("drpdn"); 794 var selectedVarName = elem.options[elem.selectedIndex].text; 795 796 selectedVar = elem.options[elem.selectedIndex].value; 797 798 const id = d.id || d.properties.geoid 799 let datum 800 801 if (dataset[id] && dataset[id][0] && dataset[id][0].year) { 802 datum = dataset[id].find(d => d.year === selectedYear) 803 } else { 804 datum = dataset[d.id][0] 805 } 806 if (this.view === "states") { 807 var formatedValue; 808 if (selectedVar === "total_postings" || selectedVar === "employment_estimate") { 809 formatedValue = formatThousand(+datum[selectedVar]); 810 } else { 811 formatedValue = (formatDecimal(+datum[selectedVar]) * 100).toFixed() + '%'; 812 813 } 814 815 txt = '<div class="tipStateName"> ' + d.properties.state + "</div>"; 816 txt += '<div class="tipVarName"> ' + selectedVarName + "</div>"; 817 txt += '<div class="tipVar"> ' + formatedValue + "</div>"; 818 819 820 } else if (this.view === "msas") { 821 var formatedValue; 822 823 if (selectedVar == "total_postings" || selectedVar == "employment_estimate") { 824 formatedValue = formatThousand( 825 +datum[selectedVar] 826 ); 827 } else { 828 formatedValue = (formatDecimal( 829 +datum[selectedVar] 830 ) * 100).toFixed() + '%' 831 } 832 833 txt = '<div class="tipStateName"> ' + d.properties.NAME + "</div>"; 834 txt += '<div class="tipVarName"> ' + selectedVarName + "</div>"; 835 txt += '<div class="tipVar"> ' + formatedValue + "</div>"; 836 txt += "<div class='more'>ⓘ Click for more info</div>"; 837 } 838 839 // d3.select('.tooltip').html(txt) 840 this.tooltipDiv.html(txt); 841}; 842 843heatMap.prototype.reDraw = function () { 844 var _this = this; 845 846 // if (_this.isZoomed) { 847 // _this.clicked(d, el); 848 849 // } 850 switch (selectedVar) { 851 case "total_postings": 852 quantile.range(colorbrewer.PuBu["7"]); 853 break; 854 case "employment_estimate": 855 quantile.range([ 856 "#fef0d9", 857 "#fdd49e", 858 "#fdbb84", 859 "#fc8d59", 860 "#ef6548", 861 "#d7301f", 862 "#990000", 863 ]); 864 break; 865 case "supply_demand_ratio": 866 quantile.range(["#b30000", "#e34a33", "#fc8d59", "#fdcc8a", "#fef0d9",]); 867 868 break; 869 case "location_quotient": 870 quantile.range(colorbrewer.BuPu["5"]); 871 } 872 873 874 875 csvData.filter(d => d.year === selectedYear).forEach(function (d) { 876 if (d.fips.toString().length < 3 && d.fips != 0) { 877 rateByStateId.set(+d.fips, +d[selectedVar]); 878 } else if (d.fips.toString().length > 3 && d.fips != 0) { 879 rateByMsaId.set(+d.fips, +d[selectedVar]); 880 } 881 }); 882 883 884 885 d3.selectAll(".feature") 886 .transition() 887 .duration(500) 888 .delay(function (d, i) { 889 return _this.view == "msas" ? i * 1 : i * 5; 890 }) 891 .attr("fill", function (d) { 892 if (d.id) { 893 if (selectedVar == "supply_demand_ratio") { 894 var domainArr = [0, 0.70, 0.90, 1.15, 1.20, 56]; 895 quantile.domain(domainArr); 896 return quantile(rateByStateId.get(d.id)); 897 898 } else { 899 quantile.domain(rateByStateId.values()); 900 return quantile(rateByStateId.get(d.id)); 901 } 902 } else { 903 if (typeof d.properties != "undefined") { 904 if (selectedVar == "supply_demand_ratio") { 905 var domainArr = [0, 0.70, 0.90, 1.15, 1.20, 56]; 906 quantile.domain(domainArr); 907 return quantile(rateByMsaId.get(d.properties.geoid)); 908 } else { 909 quantile.domain(rateByMsaId.values()); 910 return quantile(rateByMsaId.get(d.properties.geoid)); 911 } 912 } else { 913 return "#fff"; 914 } 915 } 916 }); 917 //Redraw Map Legend 918 d3.select(".leg").remove(); 919 drawLegend(); 920 921 var elem = document.getElementById("drpdn"); 922 var selectedTitle = elem.options[elem.selectedIndex].text; 923 924 d3.select(".legendVar").text(selectedTitle); 925}; 926 927 928function updateTable(d) { 929 if (d3.event) d3.event.cancelBubble = true; 930 931 var fips = 0; //d.properties ? d.properties.geoid : d.id; 932 var name = "National Average"; 933 934 // Safety check for d and clickedId 935 if (d && clickedId) { 936 var state = d3.select("#fips_" + clickedId); 937 938 // Make sure the element exists before checking its class 939 if (state && state.node() && state.classed("active")) { 940 fips = clickedId; 941 } else { 942 fips = 0; 943 } 944 } else if (d && d.id) { 945 var state = d3.select("#fips_" + d.id); 946 947 if (state && state.node() && state.classed("active")) { 948 fips = d.id; 949 } else { 950 fips = 0; 951 } 952 } 953 954 if (d && d.properties) { 955 var msa = d3.select("#msafips_" + d.properties.geoid); 956 957 if (msa && msa.node() && msa.classed("active")) { 958 fips = d.properties.geoid; 959 } else { 960 fips = 0; 961 } 962 } 963 964 if (fips != 0 && currentView === "states") { 965 var selectedStateObj = states_list.find(d => d.fips == fips); 966 name = selectedStateObj ? selectedStateObj.state : ''; 967 selectedState = name; 968 969 // Update supply-demand-ratio card for selected state 970 const stateData = csvData.filter(d => d.fips == fips && d.year === selectedYear); 971 if (stateData.length > 0 && stateData[0].supply_demand_ratio) { 972 let stateRatioValue = Math.round(stateData[0].supply_demand_ratio * 100); 973 if (isNaN(stateRatioValue)) { 974 stateRatioValue = 'N/A'; 975 } 976 console.log("Updating state ratio value:", stateRatioValue, "for state:", selectedState); 977 978 // DIRECT UPDATE to the value-text element - ensure this happens without interference 979 document.querySelector(".supply-demand-ratio-card .value-text").textContent = stateRatioValue + "%"; 980 981 // DIRECT UPDATE to the ratio-fill-bar height 982 const barHeight = 10 + (stateRatioValue * 0.9) + "%"; 983 document.querySelector("#ratio-fill-bar").style.height = barHeight; 984 985 // Update total job openings and employed workforce cards 986 if (stateData[0].total_postings) { 987 document.querySelector(".total-job-openings").textContent = formatThousand(Math.round(stateData[0].total_postings)); 988 } 989 990 if (stateData[0].employment_estimate) { 991 document.querySelector(".total-employed-workforce").textContent = formatThousand(Math.round(stateData[0].employment_estimate)); 992 } 993 994 // Update the date-level labels 995 const stateDateLabel = `${selectedState.toUpperCase()}, ${selectedYear === 'Current' ? CURRENT_DISPLAY_YEAR : selectedYear}`; 996 document.querySelectorAll('.date-level-label').forEach(el => { 997 el.textContent = stateDateLabel; 998 }); 999 1000 // Update the label to indicate we're showing regional data 1001 const nationalLabelText = d3.select(".national-label-text"); 1002 if (nationalLabelText.node()) { 1003 nationalLabelText.text(selectedState); 1004 // Ensure the styling is maintained when changing from national to state/metro 1005 nationalLabelText 1006 .style("background-color", "rgba(0,0,0,0.5)") 1007 .style("color", "white") 1008 .style("padding", "2px 8px") 1009 .style("border-radius", "4px"); 1010 } 1011 1012 // Add a label to the national average line for comparison 1013 const nationalAvgLabel = d3.select(".national-average-label"); 1014 if (window.nationalAverageRatio) { 1015 const nationalAvgDashedLine = d3.select(".national-average-dashed-line"); 1016 if (nationalAvgDashedLine.node()) { 1017 if (!nationalAvgLabel.node()) { 1018 nationalAvgDashedLine 1019 .append("div") 1020 .attr("class", "national-average-label") 1021 .style("position", "absolute") 1022 .style("right", "0") 1023 .style("transform", "translateY(-50%)") 1024 .style("background-color", "rgba(0,0,0,0.6)") 1025 .style("color", "white") 1026 .style("padding", "2px 6px") 1027 .style("border-radius", "4px") 1028 .style("font-size", "10px") 1029 .text(window.nationalAverageRatio + "% Natl. Avg"); 1030 } else if (nationalAvgLabel.node()) { 1031 nationalAvgLabel.text(window.nationalAverageRatio + "% Natl. Avg"); 1032 } 1033 } 1034 } 1035 } 1036 } else if (fips != 0 && currentView === "msas") { 1037 var selectedMsaObj = msas_list.find(d => d.fips == fips); 1038 name = selectedMsaObj ? selectedMsaObj.msa : ''; 1039 selectedState = name; 1040 1041 // Update supply-demand-ratio card for metro area 1042 const msaData = csvData.filter(d => d.fips == fips && d.year === selectedYear); 1043 if (msaData.length > 0 && msaData[0].supply_demand_ratio) { 1044 let msaRatioValue = Math.round(msaData[0].supply_demand_ratio * 100); 1045 if (isNaN(msaRatioValue)) { 1046 msaRatioValue = 'N/A'; 1047 } 1048 1049 console.log("Updating metro area ratio value:", msaRatioValue, "for metro area:", selectedState); 1050 1051 // DIRECT UPDATE to the value-text element - ensure this happens without interference 1052 document.querySelector(".supply-demand-ratio-card .value-text").textContent = msaRatioValue + "%"; 1053 1054 // DIRECT UPDATE to the ratio-fill-bar height 1055 const msaBarHeight = 10 + (msaRatioValue * 0.9) + "%"; 1056 document.querySelector("#ratio-fill-bar").style.height = msaBarHeight; 1057 1058 1059 1060 // Update the label to indicate we're showing metro area data 1061 const nationalLabelText = d3.select(".national-label-text"); 1062 if (nationalLabelText.node()) { 1063 nationalLabelText.text(selectedState); 1064 } 1065 1066 // Add a label to the national average line for comparison 1067 const nationalAvgLabel = d3.select(".national-average-label"); 1068 if (window.nationalAverageRatio) { 1069 const nationalAvgDashedLine = d3.select(".national-average-dashed-line"); 1070 if (nationalAvgDashedLine.node()) { 1071 if (!nationalAvgLabel.node()) { 1072 nationalAvgDashedLine 1073 .append("div") 1074 .attr("class", "national-average-label") 1075 .style("position", "absolute") 1076 .style("right", "0") 1077 .style("transform", "translateY(-50%)") 1078 .style("background-color", "rgba(0,0,0,0.6)") 1079 .style("color", "white") 1080 .style("padding", "2px 6px") 1081 .style("border-radius", "4px") 1082 .style("font-size", "10px") 1083 .text(window.nationalAverageRatio + "% Natl. Avg"); 1084 } else if (nationalAvgLabel.node()) { 1085 nationalAvgLabel.text(window.nationalAverageRatio + "% Natl. Avg"); 1086 } 1087 } 1088 } 1089 1090 1091 } 1092 } else { 1093 selectedState = ''; 1094 1095 // Reset to national average when no area is selected 1096 const nationalData = (typeof csvData !== 'undefined' && csvData) 1097 ? csvData.filter(d => d.fips == 0 && d.year === selectedYear) 1098 : []; 1099 if (nationalData.length > 0 && nationalData[0].supply_demand_ratio) { 1100 let nationalRatioValue = Math.round(nationalData[0].supply_demand_ratio * 100); 1101 if (isNaN(nationalRatioValue)) { 1102 nationalRatioValue = 'N/A'; 1103 } 1104 console.log("Updating national ratio value:", nationalRatioValue, "for national average"); 1105 1106 // DIRECT UPDATE to the value-text element - ensure this happens without interference 1107 document.querySelector(".supply-demand-ratio-card .value-text").textContent = nationalRatioValue + "%"; 1108 1109 // DIRECT UPDATE to the ratio-fill-bar height 1110 const nationalBarHeight = 10 + (nationalRatioValue * 0.9) + "%"; 1111 document.querySelector("#ratio-fill-bar").style.height = nationalBarHeight; 1112 1113 1114 1115 // Reset the label to show "National average" 1116 const nationalLabelText = d3.select(".national-label-text"); 1117 if (nationalLabelText.node()) { 1118 nationalLabelText.text("National average"); 1119 // Ensure the styling is maintained when resetting to national average 1120 nationalLabelText 1121 .style("background-color", "rgba(0,0,0,0.5)") 1122 .style("color", "white") 1123 .style("padding", "2px 8px") 1124 .style("border-radius", "4px"); 1125 } 1126 1127 // Remove the national average label when showing national data 1128 const nationalAvgLabel = d3.select(".national-average-label"); 1129 if (nationalAvgLabel.node()) { 1130 nationalAvgLabel.remove(); 1131 } 1132 1133 1134 } 1135 } 1136 1137 // Add null checks for all D3 selections 1138 const circlesSelection = d3.selectAll("circle.ratioCirc"); 1139 if (circlesSelection.node()) { 1140 circlesSelection.style("opacity", 0.5).attr("stroke", "none"); 1141 } 1142 1143 const textLabelGroups = d3.selectAll("g.text-label-group"); 1144 if (textLabelGroups.node()) { 1145 textLabelGroups.style("display", "none"); 1146 } 1147 1148 const tsfGroups = d3.selectAll("g.tsf"); 1149 if (tsfGroups.node()) { 1150 tsfGroups.style("display", "none"); 1151 } 1152 1153 const bgGroups = d3.selectAll("g.bg"); 1154 if (bgGroups.node()) { 1155 bgGroups.style("display", "none"); 1156 } 1157 1158 if (d && d.id) { 1159 drawOpenings(d.id); 1160 drawLineChart("total_postings", d.id); 1161 drawLineChart("employment_estimate", d.id); 1162 drawLineChart("supply_demand_ratio", d.id); 1163 1164 // Add null check for region selection 1165 const regionElement = d3.select(".region"); 1166 if (regionElement.node()) { 1167 regionElement.text(name); 1168 } 1169 1170 // Add null checks for all element selections 1171 var cfElement = d3.select("#supply_demand_ratioSvg").select("#cf" + d.id); 1172 if (cfElement.node()) { 1173 cfElement 1174 .moveToFront() 1175 .style("opacity", 1) 1176 .attr("stroke", "#455A64") 1177 .attr("stroke-width", "2px"); 1178 } 1179 1180 var tfElement = d3.select("#supply_demand_ratioSvg").select("#tf" + d.id); 1181 if (tfElement.node()) { 1182 tfElement.style("display", "block"); 1183 } 1184 1185 var bgElement = d3.select("#supply_demand_ratioSvg").select("#bg" + d.id); 1186 if (bgElement.node()) { 1187 bgElement.style("display", "block"); 1188 } 1189 1190 var tsfElement = d3.select("#supply_demand_ratioSvg").select("#tsf" + d.id); 1191 if (tsfElement.node()) { 1192 tsfElement.style("display", "block"); 1193 } 1194 1195 // Location quotient SVG elements 1196 var lcfElement = d3.select("#location_quotientSvg").select("#cf" + d.id); 1197 if (lcfElement.node()) { 1198 lcfElement 1199 .moveToFront() 1200 .style("opacity", 1) 1201 .attr("stroke", "#455A64") 1202 .attr("stroke-width", "2px"); 1203 } 1204 1205 var ltfElement = d3.
1205select("#location_quotientSvg").select("#tf" + d.id); 1206 if (ltfElement.node()) { 1207 ltfElement.style("display", "block"); 1208 } 1209 1210 var lbgElement = d3.select("#location_quotientSvg").select("#bg" + d.id); 1211 if (lbgElement.node()) { 1212 lbgElement.style("display", "block"); 1213 } 1214 1215 var ltsfElement = d3.select("#location_quotientSvg").select("#tsf" + d.id); 1216 if (ltsfElement.node()) { 1217 ltsfElement.style("display", "block"); 1218 } 1219 } else if (d && d.properties) { 1220 drawOpenings(d.properties.geoid); 1221 drawLineChart("total_postings", d.properties.geoid); 1222 drawLineChart("employment_estimate", d.properties.geoid); 1223 drawLineChart("supply_demand_ratio", d.properties.geoid); 1224 1225 // Add null check for region selection 1226 const regionElement = d3.select(".region"); 1227 if (regionElement.node()) { 1228 regionElement.text(name); 1229 } 1230 1231 // Add null checks for all element selections 1232 var cfElement = d3.select("#supply_demand_ratioSvg").select("#cf" + d.properties.geoid); 1233 if (cfElement.node()) { 1234 cfElement 1235 .moveToFront() 1236 .style("opacity", 1) 1237 .attr("stroke", "#455A64") 1238 .attr("stroke-width", "2px"); 1239 } 1240 1241 var tfElement = d3.select("#supply_demand_ratioSvg").select("#tf" + d.properties.geoid); 1242 if (tfElement.node()) { 1243 tfElement.style("display", "block"); 1244 } 1245 1246 var bgElement = d3.select("#supply_demand_ratioSvg").select("#bg" + d.properties.geoid); 1247 if (bgElement.node()) { 1248 bgElement.style("display", "block"); 1249 } 1250 1251 var tsfElement = d3.select("#supply_demand_ratioSvg").select("#tsf" + d.properties.geoid); 1252 if (tsfElement.node()) { 1253 tsfElement.style("display", "block"); 1254 } 1255 1256 // Location quotient SVG elements 1257 var lcfElement = d3.select("#location_quotientSvg").select("#cf" + d.properties.geoid); 1258 if (lcfElement.node()) { 1259 lcfElement 1260 .moveToFront() 1261 .style("opacity", 1) 1262 .attr("stroke", "#455A64") 1263 .attr("stroke-width", "2px"); 1264 } 1265 1266 var ltfElement = d3.select("#location_quotientSvg").select("#tf" + d.properties.geoid); 1267 if (ltfElement.node()) { 1268 ltfElement.style("display", "block"); 1269 } 1270 1271 var lbgElement = d3.select("#location_quotientSvg").select("#bg" + d.properties.geoid); 1272 if (lbgElement.node()) { 1273 lbgElement.style("display", "block"); 1274 } 1275 1276 var ltsfElement = d3.select("#location_quotientSvg").select("#tsf" + d.properties.geoid); 1277 if (ltsfElement.node()) { 1278 ltsfElement.style("display", "block"); 1279 } 1280 } else { 1281 drawOpenings(0); 1282 drawLineChart("total_postings", 0); 1283 drawLineChart("employment_estimate", 0); 1284 drawLineChart("supply_demand_ratio", 0); 1285 1286 // Add null check for region selection 1287 const regionElement = d3.select(".region"); 1288 if (regionElement.node()) { 1289 regionElement.text("National Average"); 1290 } 1291 } 1292} 1293 1294function newTreeMap() { 1295 theTreeMap = new treeMap({ 1296 element: document.querySelector(".treemap"), 1297 fips: 0, 1298 flatData: dataset[0][0], 1299 workrolesKeys: niceKeys, 1300 // workrole_top_skills: workrole_top_skills, 1301 workrole_kt_mapping: workrole_kt_mapping, 1302 master_ksa_task: master_ksa_task, 1303 }); 1304} 1305 1306function init(kindOfData, cb = () => { }) { 1307 rateByStateId = d3.map(); 1308 rateByMsaId = d3.map(); 1309 quantile = d3.scale.quantile().range(["#214670", 1310 "#356FB2", 1311 "#428BDF", 1312 "#66A0E3", 1313 "#8AB5E8", 1314 "#B0D5FF", 1315 "#D2E5FA"].reverse()); 1316 let basefile = globalmode == "widget" ? "../" : ""; 1317 1318 queue() 1319 .defer(d3.json, basefile + "data/us_states_msa.json") 1320 .defer(d3.csv, basefile + "data/nice_key(2024 05 17).csv") 1321 .defer(d3.csv, basefile + "data/heatmap/" + kindOfData + ".csv") 1322 .defer(d3.csv, basefile + "data/workrole_top_skills.csv") 1323 .defer(d3.csv, basefile + "data/workid_fully.csv") 1324 .defer(d3.csv, basefile + "data/cyberseek - master_ksa_task.csv") 1325 .await(ready); 1326 1327 function ready( 1328 error, 1329 us, 1330 nice_key, 1331 csvd, 1332 _workrole_top_skills, 1333 _workrole_kt_mapping, 1334 _master_ksa_task, 1335 ) { 1336 if (error) throw new Error(error); 1337 1338 1339 const uniqueData = Array.from(new Set(_workrole_kt_mapping.map(item => JSON.stringify(item)))).map(item => JSON.parse(item)); 1340 1341 _workrole_kt_mapping = uniqueData 1342 1343 mapW = parseInt(d3.select(".map").style("width")); 1344 csvData = csvd; 1345 us_map_data = us; 1346 niceKeys = d3 1347 .nest() 1348 .key((d) => d["workroleid"].toLowerCase().replace(/-/g, "_")) 1349 .rollup((d) => { 1350 return { 1351 name: d[0]["workrolename"], 1352 description: d[0]["workroledescription"], 1353 }; 1354 }) 1355 .map(nice_key); 1356 1357 1358 1359 1360 1361 1362 //Parse data for choropleth 1363 csvData.filter(d => d.year === selectedYear).forEach(function (d) { 1364 if (d.fips.toString().length < 3 && d.fips != 0) { 1365 rateByStateId.set(+d.fips, +d.total_postings); 1366 } else if (d.fips.toString().length > 3 && d.fips != 0) { 1367 rateByMsaId.set(+d.fips, +d.total_postings); 1368 } 1369 }); 1370 1371 // years for selection option 1372 const yearsArray = csvData.map(d => d.year) 1373 const hasCurrentYear = yearsArray.includes("Current"); 1374 const normalizedYearsArray = Array.from( 1375 new Set(yearsArray.map(d => d === "Current" ? "2025" : d)) 1376 ); 1377 1378 uniqueYearsArray = normalizedYearsArray.sort((a, b) => b - a).map(d => { 1379 return { 1380 value: d, 1381 label: hasCurrentYear && d === "2025" ? `Current date 2025` : d 1382 } 1383 }) 1384 // (${new Date().getFullYear()}) 1385 1386 //Nest general dataset 1387 dataset = d3 1388 .nest() 1389 .key(function (d) { 1390 return d.fips; 1391 }) 1392 .map(csvData); 1393 1394 1395 csvData.forEach(function (d) { 1396 if (d.fips !== "0") { 1397 postingsArr.push(+d.total_postings); 1398 postingsArr.push(+d.employment_estimate); 1399 } 1400 }); 1401 1402 1403 workrole_top_skills = _workrole_top_skills; 1404
1405 workrole_top_skills.forEach((d) => { 1406 d.workrole_id = d.workrole_id.replace(/-/g, "_").toLowerCase(); 1407 }); 1408 1409 workrole_kt_mapping = _workrole_kt_mapping; 1410 _workrole_kt_mapping.forEach((d) => { 1411 d.workrole_id = d.workrole_id.replace(/-/g, "_").toLowerCase(); 1412 }); 1413 1414 master_ksa_task = _master_ksa_task; 1415 1416 // Now in function above 1417 1418 if (globalmode != "widget") newTreeMap(); 1419 1420 // create new Map using Map constructor 1421 theMap = new heatMap({ 1422 element: document.querySelector(".map"), 1423 geo: us, 1424 view: currentView, 1425 }); 1426 // Filter by year 1427 const selectYears = d3.select(`#select_years`) 1428 .html('') 1429 .on('change', function (d) { 1430 selectedYear = this.value === "2025" ? "Current" : this.value 1431 const currentView = theMap.view 1432 choices = currentView 1433 choices = d3.select(this).attr("id"); 1434 reInit(kindOfData.replace("_data", ""), () => { 1435 d3.selectAll(".smallTitle-barchart").html(`${selectedState === '' ? 'NATIONAL' : selectedState.toUpperCase()}, ${selectedYear === 'Current' ? CURRENT_DISPLAY_YEAR : selectedYear}`) 1436 d3.selectAll(".btn.geo").classed("checked", false); 1437 d3.selectAll('#' + currentView).classed("checked", true); 1438 if (currentView === 'msas') { 1439 } 1440 theMap.view = currentView 1441 theMap.setView(); 1442 changeSearch(choices); 1443 }) 1444 1445 d3.select("#supply_demand_ratioSvg") 1446 .selectAll("g.text-label-group") 1447 .attr('transform', function (d) { 1448 let y = currentView === 'states' ? storedCxStSd(d.ratio) : storedCxMsaSd(d.ratio); 1449 d.yCoord = y; 1450 return `translate(0, ${y})` 1451 }).each(function (d) { 1452 let y = 0; 1453 let dy = Math.abs(d.yCoord - nationalY); 1454 1455 if (dy < 20) { 1456 if (nationalY >= d.yCoord) { 1457 y = dy - 22; 1458 } else { 1459 y = 22 - dy; 1460 } 1461 } 1462 d3.select(this).select(".ratio-number").attr('y', y) 1463 d3.select(this).select(".ratio-state").attr('y', y) 1464 }); 1465 1466 }) 1467 1468 selectYears 1469 .selectAll('option') 1470 .data(uniqueYearsArray) 1471 .enter() 1472 .append('option') 1473 .attr('value', (d) => d.value) 1474 .property('selected', d => d.value === (selectedYear === "Current" ? "2025" : selectedYear)) 1475 .html(d => d.label) 1476 1477 1478 d3.selectAll(".feature").attr("fill", function (d) { 1479 if (d.id) { 1480 quantile.domain(rateByStateId.values()); 1481 return quantile(rateByStateId.get(d.id)); 1482 } else { 1483 if (typeof d.properties != "undefined") { 1484 quantile.domain(rateByMsaId.values()); 1485 return quantile(rateByMsaId.get(d.properties.geoid)); 1486 } else { 1487 return "#fff"; 1488 } 1489 } 1490 }); 1491 1492 theMap.svg 1493 .append("text") 1494 .attr("class", "instruction") 1495 .style('font-size', 16) 1496 .attr("x", 10) 1497 .attr("y", -40) 1498 .text(function () { 1499 return mapW > 600 ? "Click on a state or MSA to compare insights" : ""; 1500 }); 1501 1502 //MAP LEGEND 1503 drawLegend(); 1504 1505 //SEARCH FUNCTIONALITY 1506 states_list = []; 1507 msas_list = []; 1508 var fips = []; 1509 1510 csvData.forEach(function (d) { 1511 if (d.fips !== "0" && d.fips.toString().length < 3) { 1512 fips.push(d.fips); 1513 } 1514 if (d.fips !== "0" && d.msa != "") { 1515 msas_list.push(d.msa); 1516 name_lookup_map.set(String(d.fips), d.msa); 1517 } 1518 }); 1519 1520 for (var i = 0; i < fips.length; i++) { 1521 var stat = us.objects.states.geometries.filter(function (obj) { 1522 return obj.id == fips[i]; 1523 }); 1524 if (states_list.indexOf(stat[0].properties.state) == -1) { 1525 states_list.push(stat[0].properties.state); 1526 name_lookup_map.set(String(stat[0].id), stat[0].properties.state); 1527 } 1528 } 1529 1530 //INIT TABLE CHARTS 1531 if (globalmode != "widget") { 1532 drawOpenings(0); 1533 drawSupplyDemandBar("supply_demand_ratio"); 1534 drawLineChart("total_postings"); 1535 drawLineChart("employment_estimate"); 1536 drawLineChart('supply_demand_ratio') 1537 1538 holdersBarChart = BarChart({ 1539 target: "holders", 1540 chartRef: "#holders_bar", 1541 }); 1542 reqCertBarChart = BarChart({ 1543 target: "requesting-cert", 1544 chartRef: "#req_cert_bar", 1545 }); 1546 } 1547 1548 changeSearch("states");
1549 theMap.setView(); 1550 var el = document.getElementById("prelC"); 1551 el.style.display = "none"; 1552 var cont = document.getElementById("con"); 1553 cont.style.opacity = "1"; 1554 1555 new ShareButton({ 1556 networks: { 1557 email: { 1558 enabled: false, 1559 }, 1560 }, 1561 }); 1562 1563 1564 //DROPDOWN MENU 1565 var elem = document.getElementById("drpdn"); 1566 1567 elem.onchange = function () { 1568 var selectedString = elem.options[elem.selectedIndex].value; 1569 1570 selectedVar = selectedString; 1571 theMap.reDraw(); 1572 }; 1573 1574 if (clickedId) { 1575 heatMap.prototype.centered = null; 1576 1577 const el = d3.select("#fips_" + clickedId); 1578 1579 if (!el.empty()) { 1580 var d = el.datum(); 1581 if (globalmode != "widget") updateTable(d); 1582 heatMap.prototype.clicked(d, el.node()); 1583 } 1584 } 1585 1586 if (cb) { 1587 cb() 1588 } 1589 } 1590 1591 d3.select(".region").html("National Level"); 1592 1593 const btns = d3.selectAll('.toggle_btn'); 1594 1595 btns.on('click', function () { 1596 const el = d3.select(this); 1597 const target = el.attr("data-value"); 1598 1599 btns.classed('checked', false); 1600 el.classed('checked', true); 1601 1602 d3.selectAll('.bar_charts .barChart').classed('checked', false); 1603 d3.select(`.bar_charts .barChart[data-value=${target}]`).classed('checked', true); 1604 }) 1605} 1606 1607 1608 1609 1610 1611function changeSearch(selectedChoice) { 1612 if (typeof stateSearch != "undefined") { 1613 stateSearch.destroy(); 1614 } 1615 stateSearch = new autoComplete({ 1616 selector: "#search", 1617 minChars: 0, 1618 source: function (term, suggest) { 1619 term = term.toLowerCase(); 1620 var choices; 1621 selectedChoice == "states" 1622 ? (choices = states_list) 1623 : (choices = msas_list); 1624 var suggestions = []; 1625 for (i = 0; i < choices.length; i++) 1626 if (~choices[i].toLowerCase().indexOf(term)) 1627 suggestions.push(choices[i]); 1628 suggest(suggestions); 1629 }, 1630 onSelect: function (e, term, item) { 1631 var selectedFips, d, selEl; 1632 if (selectedChoice == "states") { 1633 selectedFips = us_map_data.objects.states.geometries.filter(function ( 1634 obj 1635 ) { 1636 return obj.properties.state == term; 1637 }); 1638 d = d3.select("#fips_" + selectedFips[0].id)[0][0].__data__; 1639 selEl = document.getElementById("fips_" + selectedFips[0].id); 1640 } else { 1641 selectedFips = csvData.filter(function (obj) { 1642 return obj.msa == term; 1643 }); 1644 selEl = document.getElementById("fips_" + selectedFips[0].fips); 1645 d = d3.select("#fips_" + selectedFips[0].fips)[0][0].__data__; 1646 } 1647 updateTable(d); 1648 theMap.clicked(d, selEl); 1649 }, 1650 }); 1651} 1652 1653$(window).on("load", function () { 1654 globalmode = d3.select("body").classed("widget") ? "widget" : "standard"; 1655 init("all_data"); 1656 initHandlers(); 1657 1658 // Set initial national average value 1659 const nationalData = (typeof csvData !== 'undefined' && csvData) 1660 ? csvData.filter(d => d.fips == 0 && d.year === selectedYear) 1661 : []; 1662 if (nationalData.length > 0 && nationalData[0].supply_demand_ratio) { 1663 let nationalRatioValue = Math.round(nationalData[0].supply_demand_ratio * 100); 1664 if (isNaN(nationalRatioValue)) { 1665 nationalRatioValue = 'N/A'; 1666 } 1667 1668 // Set value-text 1669 const valueTextElement = d3.select(".supply-demand-ratio-card .value-text"); 1670 if (valueTextElement.node()) { 1671 valueTextElement.text(nationalRatioValue + "%"); 1672 } 1673 1674 // Set ratio-fill-bar height 1675 const barHeight = 10 + (nationalRatioValue * 0.9) + "%"; 1676 const ratioFillBar = d3.select("#ratio-fill-bar"); 1677 if (ratioFillBar.node()) { 1678 ratioFillBar.style("height", barHeight); 1679 } 1680 1681 // Set ratio-fill-bar color 1682 let barColor = "#2F80ED"; // Default blue for good ratio 1683 if (nationalRatioValue < 75) { 1684 barColor = "#F2994A"; // Orange for low ratio 1685 } else if (nationalRatioValue > 110) { 1686 barColor = "#27AE60"; // Green for high ratio 1687 } 1688 if (ratioFillBar.node()) { 1689 ratioFillBar.style("background-color", barColor); 1690 } 1691 } 1692}); 1693 1694var resizeFunc = debounce(function () { 1695 // Safety check - if theMap is not initialized, don't proceed 1696 if (!theMap) { 1697 console.warn('Map not initialized'); 1698 return; 1699 } 1700 1701 // **** UPDATE MAP **** 1702 $(".map").css("width", ""); // reset computed and fixed element width 1703 theMap.update(); 1704 mapW = parseInt(d3.select(".map").style("width")); 1705 1706 if (mapW < 600) { 1707 d3.select(".leg").remove(); 1708 drawLegend(); 1709 d3.select(".instruction").remove(); 1710 } else if (mapW > 600) { 1711 d3.select(".leg").remove(); 1712 drawLegend(); 1713 } 1714 1715 // **** UPDATE TREEMAP **** 1716 1717 $(".treemap").empty(); 1718 1719 newTreeMap(); 1720 1721 theTreeMap.updateTreemap(dataset[clickedId][0]); 1722 1723 // **** UPDATE BAR CHART **** 1724 1725 $(".barChart").empty(); 1726 1727 holdersBarChart.createBars(); 1728 holdersBarChart.updateBars(clickedId); 1729 1730 reqCertBarChart.createBars(); 1731 reqCertBarChart.updateBars(clickedId); 1732 1733 // **** UPDATE BAR CHART **** 1734 // Don't call drawSupplyDemandBar directly as it overrides state-specific values 1735 // Comment out this call as it overrides state-specific values 1736 // drawSupplyDemandBar("supply_demand_ratio"); 1737 1738 if (currentView === "msas") { 1739 } else { 1740 $("#supply_demand_ratioSvg").find("circle.coun").hide(); 1741 $("#location_quotientSvg").find("circle.coun").hide(); 1742 } 1743 1744 d3.select("#supply_demand_ratioSvg") 1745 .select("#cf" + clickedId) 1746 .moveToFront() 1747 .style("opacity", 1) 1748 .attr("stroke", "#455A64") 1749 .attr("stroke-width", "2px"); 1750 d3.select("#supply_demand_ratioSvg") 1751 .select("#tf" + clickedId) 1752 .style("display", "block"); 1753 d3.select("#supply_demand_ratioSvg") 1754 .select("#bg" + clickedId) 1755 .style("display", "block"); 1756 d3.select("#supply_demand_ratioSvg") 1757 .select("#tsf" + clickedId) 1758 .style("display", "block"); 1759 d3.select("#location_quotientSvg") 1760 .select("#cf" + clickedId) 1761 .moveToFront() 1762 .style("opacity", 1) 1763 .attr("stroke", "#455A64") 1764 .attr("stroke-width", "2px"); 1765 d3.select("#location_quotientSvg") 1766 .select("#tf" + clickedId) 1767 .style("display", "block"); 1768 d3.select("#location_quotientSvg") 1769 .select("#bg" + clickedId) 1770 .style("display", "block"); 1771 d3.select("#location_quotientSvg") 1772 .select("#tsf" + clickedId) 1773 .style("display", "block"); 1774 1775 1776 drawLineChart("total_postings", clickedId); 1777 drawLineChart("employment_estimate", clickedId); 1778 drawLineChart('supply_demand_ratio', clickedId) 1779}, 100); 1780 1781function initHandlers() { 1782 d3.select(window).on("resize", resizeFunc); 1783 1784 //FILTER BUTTONS 1785 d3.selectAll(".filter").on("click", function (d) { 1786 d3.selectAll(".msa").style("display", "none"); 1787 const text = d3.select(this).text()?.trim(); 1788 const lowerText = text.toLowerCase(); 1789 d3.select("#all_filter_msa").classed("pressed") == true 1790 ? d3.selectAll(".msa").style("display", "none") 1791 : ""; 1792 if (text != "All") { 1793 if (d3.select(this).classed("pressed")) { 1794 d3.select(this).classed("pressed", false); 1795 1796 d3.selectAll("." + text).style( 1797 "display", 1798 "none" 1799 ); 1800 } else { 1801 d3.select("#all_filter_msa").classed("pressed", false); 1802 d3.select(this).classed("pressed", true); 1803 d3.selectAll("." + lowerText).style( 1804 "display", 1805 "block" 1806 ); 1807 } 1808 1809 const allPressed = Array.from(d3.selectAll(".filterBtns .pressed")[0]).map(d => d.innerText).map(d => d.trim().toLowerCase()) 1810 allPressed.forEach(d => { 1811 if (d != "all") { 1812 d3.selectAll("." + d).style("display", "block") 1813 } 1814 }) 1815 } else { 1816 d3.selectAll(".filter").classed("pressed", false); 1817 d3.select("#all_filter_msa").classed("pressed", true); 1818 d3.selectAll(".msa").style("display", "block"); 1819 } 1820 }); 1821 1822 1823 // all data button click 1824 $("#all").on("click", function () { 1825 dataKind = $(this).attr("id"); 1826 1827 d3.select(this).classed("checked", true); 1828 $("select#drpdn_dataK_industry").val(""); 1829 $("select#dropdown_public_sector").val(""); 1830 reInit(dataKind); 1831 1832 d3.select("#all").style('background-color', '#495963') 1833 d3.select("#all").style('color', '#ffffff') 1834 d3.select('#dropdown_public_sector').classed('selected', false).style('background-color', '#EDEFF1') 1835 d3.select('#dropdown_public_sector').style('color', '#495057') 1836 d3.select('#drpdn_dataK_industry').classed('selected', false).style('background-color', '#EDEFF1') 1837 d3.select('#drpdn_dataK_industry').style('color', '#495057') 1838 }); 1839 1840 // private sector dropdown change 1841 $("#drpdn_dataK_industry").change(function () { 1842 dataKind = $(this).val(); 1843 $("select#dropdown_public_sector").val(""); 1844 d3.select("#all").classed("checked", dataKind === "") 1845 reInit(dataKind || "all"); 1846 1847 d3.select("#all").style('background-color', '#EDEFF1') 1848 d3.select('#all').style('color', '#495057') 1849 d3.select('#dropdown_public_sector').classed('selected', false).style('background-color', '#EDEFF1') 1850 d3.select('#dropdown_public_sector').style('color', '#495057') 1851 if (dataKind) { 1852 d3.select('#drpdn_dataK_industry').classed('selected', true).style('background-color', '#495963') 1853 d3.select('#drpdn_dataK_industry').style('color', '#ffffff') 1854 } 1855 else { 1856 dataKind = 'all' 1857 d3.select('#drpdn_dataK_industry').classed('selected', false).style('background-color', '#EDEFF1') 1858 d3.select('#drpdn_dataK_industry').style('color', '#495057') 1859 d3.select("#all").style('background-color', '#495963') 1860 d3.select('#all').style('color', '#ffffff') 1861 } 1862 1863 }); 1864 1865 // public sector dropdown change 1866 $("select#dropdown_public_sector").change(function () { 1867 dataKind = $(this).val(); 1868 1869 $("select#drpdn_dataK_industry").val(""); 1870 1871 d3.select("#all").classed("checked", dataKind === "") 1872 1873 reInit(dataKind || "all"); 1874 1875 d3.select("#all").style('background-color', '#EDEFF1') 1876 d3.select("#all").style('color', '#495057') 1877 d3.select('#drpdn_dataK_industry').classed('selected', false).style('background-color', '#EDEFF1') 1878 d3.select('#drpdn_dataK_industry').style('color', '#495057') 1879 d3.select('#drpdn').style('background-color', '#EDEFF1') 1880 1881 if (dataKind) { 1882 1883 d3.select('#dropdown_public_sector').classed('selected', true).style('background-color', '#495963') 1884 d3.select('#dropdown_public_sector').style('color', '#ffffff') 1885 } else { 1886 dataKind = 'all' 1887 d3.select('#dropdown_public_sector').classed('selected', false).style('background-color', '#EDEFF1') 1888 d3.select('#dropdown_public_sector').style('color', '#495057') 1889 d3.select("#all").style('background-color', '#495963') 1890 d3.select('#all').style('color', '#ffffff') 1891 } 1892 }) 1893 1894 // Year dropdown change 1895 1896 1897 1898 1899 // reset window on "Reset" 1900 d3.select('#reset_window').on('click', function () { 1901 window.location.reload() 1902 }) 1903 1904 // set button toggle for view state 1905 d3.selectAll(".btn.geo").on("click", function () { 1906 1907 theMap.view = d3.select(this).attr("id"); 1908 d3.selectAll(".btn.geo").classed("checked", false); 1909 d3.select(this).classed("checked", true);
1910 theMap.setView(); 1911 choices = d3.select(this).attr("id"); 1912 changeSearch(choices); 1913 }); 1914 1915 d3.select(".map").on("click", function (event) { 1916 document.getElementById("search").value = ""; 1917 }); 1918 1919 d3.selectAll(".infIcon").on("click", function (d) { 1920 d3.select("#b" + d3.select(this).attr("rel")).style("display", "block"); 1921 }); 1922 1923 d3.selectAll(".closeBtn").on("click", function (d) { 1924 d3.select("#b" + d3.select(this).attr("rel")).style("display", "none"); 1925 }); 1926} 1927 1928function reInit(dataKind, cb = () => { }) { 1929 var el = document.getElementById("prelC"); 1930 el.style.display = "block"; 1931 var cont = document.getElementById("con"); 1932 cont.style.opacity = "0.1"; 1933 1934 d3.selectAll(".map svg").remove(); 1935 d3.selectAll(".map button").remove(); 1936 d3.selectAll(".map").selectAll(".tooltip").remove(); 1937 $("#drpdn").val("total_postings"); 1938 $("#supply_demand_ratioChart").empty(); 1939 $(".treemap").empty(); 1940 $(".barChart").empty(); 1941 init(dataKind + "_data", cb); 1942 dataKind == "all" 1943 ? $(".changeTitle").html( 1944 "CERTIFICATION HOLDERS / OPENINGS REQUESTING CERTIFICATION<img width='17px' height='15px' src='./images/i_03.png' class='infIcon' rel='8'>" 1945 ) 1946 : $(".changeTitle").html( 1947 "OPENINGS REQUESTING CERTIFICATION<img width='17px' height='15px' src='./images/i_03.png' class='infIcon' rel='8'>" 1948 ); 1949} 1950 1951function setLegendLabel(d, i) { 1952 if (theMap.view == "states") { 1953 var domainArr = [0, 72, 85, 115, 133, 150]; 1954 if (selectedVar == "supply_demand_ratio") { 1955 quantile.domain(domainArr) + "%" 1956 } else quantile.domain(rateByStateId.values()); 1957 var extent = quantile.invertExtent(d); 1958 var format = d3.format(",.0f"); 1959 var formatK = d3.format("s"); 1960 var format = d3.format(",.0f"); 1961 var formatK = d3.format("s"); 1962 if (selectedVar == "total_postings" || selectedVar == "employment_estimate") { 1963 1964 if (mapW > 600) { 1965 return i != 0 1966 ? format(+extent[0] + 1) + " - " + format(+extent[1]) 1967 : format(+extent[0]) + " - " + format(+extent[1]); 1968 } else return i != 0 ? formatK(Math.round(+extent[0] / 100) * 100) : ""; 1969 } else if (selectedVar == "supply_demand_ratio") { 1970 if (mapW > 600) { 1971 if (i != 4) { 1972 return i != 0 1973 ? format(+extent[0] + 1) + " - " + format(+extent[1]) + '%' 1974 : "<= " + format(+extent[1]) + '%'; 1975 } else return format(+extent[0]) + " + " + '%'; 1976 } else return i != 0 ? format(+extent[0] + 1) + '%' : ""; 1977 } 1978 } else { 1979 var domainArr = [0, 72, 85, 115, 133, 150]; 1980 if (selectedVar == "supply_demand_ratio") { 1981 quantile.domain(domainArr); 1982 } else quantile.domain(rateByMsaId.values()); 1983 1984 var extent = quantile.invertExtent(d); 1985 var format = d3.format(",.0f"); 1986 var formatK = d3.format("s"); 1987 1988 if (selectedVar == "total_postings" || selectedVar == "employment_estimate") { 1989 if (mapW > 600) { 1990 return i != 0 1991 ? format(+extent[0] + 1) + " - " + format(+extent[1]) 1992 : format(+extent[0]) + " - " + format(+extent[1]); 1993 } else return i != 0 ? formatK(Math.round(+extent[0])) : ""; 1994 } else if (selectedVar == "supply_demand_ratio") { 1995 if (mapW > 600) { 1996 if (i != 4) { 1997 return i != 0 1998 ? format(+extent[0] + 0.1) + " - " + format(+extent[1]) + '%' 1999 : "<= " + format(+extent[1]) + '%'; 2000 } else return format(+extent[0] + 0.05) + " + " + '%'; 2001 } else return i != 0 ? format(+extent[0] + 0.05) + '%' : ""; 2002 } 2003 } 2004} 2005 2006 2007 2008globalmode = d3.select("body").classed("widget") ? "widget" : "standard"; 2009if (globalmode != "widget") { 2010 document.getElementById("copyButton").addEventListener("click", function () { 2011 copyToClipboard(document.getElementById("copyTarget")); 2012 }); 2013 2014 //document.getElementById("year").innerHTML = new Date().getFullYear(); 2015} 2016 2017function copyToClipboard(elem) { 2018 // create hidden text element, if it doesn't already exist 2019 var targetId = "_hiddenCopyText_"; 2020 var isInput = elem.tagName === "INPUT" || elem.tagName === "TEXTAREA"; 2021 var origSelectionStart, origSelectionEnd; 2022 if (isInput) { 2023 // can just use the original source element for the selection and copy 2024 target = elem; 2025 origSelectionStart = elem.selectionStart; 2026 origSelectionEnd = elem.selectionEnd; 2027 } else { 2028 // must use a temporary form element for the selection and copy 2029 target = document.getElementById(targetId); 2030 if (!target) { 2031 var target = document.createElement("textarea"); 2032 target.style.position = "absolute"; 2033 target.style.left = "-9999px"; 2034 target.style.top = "0"; 2035 target.id = targetId; 2036 document.body.appendChild(target); 2037 } 2038 target.textContent = elem.textContent; 2039 } 2040 // select the content 2041 var currentFocus = document.activeElement;
2042 target.focus(); 2043 target.setSelectionRange(0, target.value.length); 2044 2045 // copy the selection 2046 var succeed; 2047 try { 2048 succeed = document.execCommand("copy"); 2049 } catch (e) { 2050 succeed = false; 2051 } 2052 // restore original focus 2053 if (currentFocus && typeof currentFocus.focus === "function") { 2054 currentFocus.focus(); 2055 } 2056 2057 if (isInput) { 2058 // restore prior selection 2059 elem.setSelectionRange(origSelectionStart, origSelectionEnd); 2060 } else { 2061 // clear temporary content 2062 target.textContent = ""; 2063 } 2064 return succeed; 2065} 2066 2067function isBigEnough(value) { 2068 return function (element, index, array) { 2069 return element >= value; 2070 }; 2071} 2072 2073//Move to front and back 2074d3.selection.prototype.moveToFront = function () { 2075 return this.each(function () { 2076 this.parentNode.appendChild(this); 2077 }); 2078}; 2079 2080d3.selection.prototype.moveToBack = function () { 2081 return this.each(function () { 2082 var firstChild = this.parentNode.firstChild; 2083 if (firstChild) { 2084 this.parentNode.insertBefore(this, firstChild); 2085 } 2086 }); 2087}; 2088 2089 2090
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.