PageSourceSearch

https://cyberseekus.herokuapp.com/js/heatmap.js

js cyberseekus.herokuapp.com collected 2026-10-03 14:47:46 UTC 66,649 bytes, 2,090 lines download raw bytes

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("&minus; ")
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'>&#9432; 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.