PageSourceSearch

https://statesnewsroom.com/wp-content/themes/genesis-child/js/newsroomMap.js?ver=20250304

js statesnewsroom.com collected 2026-09-25 19:12:04 UTC 10,755 bytes, 291 lines download raw bytes

1// Sets up event handlers for states in the map
2function setupStateInteractions(selection) {
3  
4  // Highlight states in red upon hover and make
5  // outlet labels (if used on this map) appear
6  selection.on('mouseover', function(event, data) {
7    if (!STATE_OUTLETS[data.id]) {
8      return;
9    }
10    d3.select(this)
11      .transition()
12      .duration(200)
13      .attr('fill', 'rgba(235, 76, 4, 1)');
14    d3.select('#outletLabel' + data.id).style('visibility', 'visible');
15  })
16
17  // Remove highlight when not hovering and make outlet labels
18  // (if present) disappear
19  .on('mouseleave', function(event, data) {
20    if (!STATE_OUTLETS[data.id]) {
21      return;
22    }   
23    d3.select(this)
24      .transition()
25      .duration(200)
26      .attr('fill', 'rgba(56, 78, 119, 1)');
27    d3.select('#outletLabel' + data.id).style('visibility', 'hidden');
28  })
29
30  // Display outlet info upon click - turns off currently
31  // displayed containers and turns on container corresponding
32  // to the clicked state's "FIPS" ID
33  .on('click', function(event, data) {  
34    d3.selectAll('.mapNonDefault').style('display', 'none');
35    d3.select('#mapInformationDefaultContainer').style('display', !STATE_OUTLETS[data.id] ? 'flex' : 'none');
36    d3.select('#mapInformationContainer' + data.id).style('display', 'flex');
37  });
38}
39
40// Retrieves outlet name from provided D3 data
41function getOutletName(d) {
42  const outlet = STATE_OUTLETS[d.id];
43  return outlet ? outlet.outletName.toUpperCase() : '';
44}
45
46// Generates newsroom map from given data
47function generateNewsroomMap(mapdata) {
48
49  // Define how high above the state labels should float
50  const floatFactor = 35;
51
52  // Define some constants to properly adapt label boxes to
53  // the size of the label text
54  const labelBoxVerticalPadding = 30;
55  const labelBoxHorizontalPadding = 25;
56  const labelBoxVerticalOffset = 18;
57
58  // Define the width of the label box's highlight bar
59  const labelBoxHighLightWidth = 4.5;
60
61  // Define the amount of padding between lines of the label
62  // (i.e. between outlet name and partner tag)
63  const labelItemPadding = 5;
64
65  // Append SVG with dimensions corresponding to U.S. Atlas TopoJSON
66  // specifications (i.e. 975x610 viewport)
67  const svg = d3.select('#interactiveMapContainerNewsroomPageV3, #interactiveMapContainerImpactReport').append('svg')
68      .attr('viewBox', [0 ,0, 975, 610])
69
70  // Define a drop-shadow effect for use with state labels
71  const shadows = svg
72    .append('defs')
73    .append('filter')
74    .attr('id', 'dropShadow')
75    .append('feDropShadow')
76    .attr('dx', '1')
77    .attr('dy', '1')
78    .attr('stdDeviation', '5.0')
79    .attr('flood-color', 'rgba(0, 0, 0, 0.30)'); 
80  
81  // Add state shapes from the U.S. Atlas TopoJSON dataset
82  const stateMap = svg.append('g')
83    
84    // Set up some basic configuration for the overall set of states
85    .attr('class', 'newsroomStateGroup')
86    .attr('stroke', 'rgba(255, 255, 255, 1)')
87    .attr('stroke-linejoin', 'round')
88    .attr('stroke-linecap', 'round')
89    
90    // Apply data and distinguish between newsroom states and non-newsroom
91    .selectAll('g')
92    .data(topojson.feature(mapdata, mapdata.objects.states).features)
93    .join('g')
94    .attr('class', function(d) {
95      return STATE_OUTLETS[d.id] ? 'newsroomState' : 'otherState';
96    })
97
98    // Add a URL to the attributes so we can use it for clicking
99    .attr('data-url', function(d) {
100      return STATE_OUTLETS[d.id] ? STATE_OUTLETS[d.id].url : '';
101    })
102
103    // Add the SVG path element for creating the actual shape,
104    // and color it according to whether outlet exists
105    .append('path')
106    .attr('fill', function(d) {
107      return STATE_OUTLETS[d.id] ? 'rgba(56, 78, 119, 1)' : '#929AAA';
108    })
109    
110    // Define state SVG path properties
111    .attr('class', 'newsroomStateShape')
112    .attr('stroke-width', '0.75')
113    
114    // Render the paths 
115    .attr('d', d3.geoPath())
116    
117    // Set up interaction events
118    .call(setupStateInteractions);
119
120  // Tweak viewBox and add labels to the map when appearing on impact report
121  const impactLabelGroups = d3.select('#interactiveMapContainerImpactReport svg')
122
123    // Allow labels to extend outside the viewBox
124    // to prevent clipping
125    .attr('overflow', 'visible')
126    
127    // Add a group to contain the labels
128    .append('g')
129    .attr('class', 'outletLabelSet')
130    
131    // Add individual groups for each label, and apply the U.S.
132    // Atlas TopJSON dataset
133    .selectAll('g')
134    .data(topojson.feature(mapdata, mapdata.objects.states).features)
135    .join('g')
136
137    // Add some atributes to the individual label groups
138    .style('visibility', 'hidden') // Should start out hidden (using "visibility" rather than "display" for Firefox compatibility)
139    .attr('class', 'outletLabel')
140    .attr('id', function(d) {
141      return 'outletLabel' + d.id; // ID is used for displaying/hiding on hover
142    })
143    .style('pointer-events', 'none') // Prevents interfering with hover effects
144
145    // Calculate the starting coordinates for labels (e.g.
146    // floating a little above the upper edge of the shape's
147    // bounding pox, and positioned at the horizontal midpoint
148    .each(function(d) {
149      d.labelAnchorX = d3.geoPath().centroid(d)[0];
150      d.labelAnchorY = d3.geoPath().bounds(d)[0][1] - floatFactor;
151    })
152
153  // Add "CONTENT PARTNER" tag to partner state labels
154  const partnerTexts = impactLabelGroups.append('text')
155    
156    // Populate the text tag only if this is a partner state
157    .text(function(d) {
158      const outlet = STATE_OUTLETS[d.id];
159      return ( outlet && outlet.partner) ? 'CONTENT PARTNER' : '';
160    })
161
162    // Format the partner tag
163    .attr('font-family', 'sans-serif')
164    .attr('font-size', '20')
165    .attr('font-weight', '400')
166    .attr('letter-spacing', '2')
167    .attr('fill', 'rgba(134, 147, 170, 1)')
168    
169    // Determine the size of the formatted text
170    .each(function(d) {
171      d.partnerFormattedNameWidth = this.getBBox().width;
172      d.partnerFormattedNameHeight = this.getBBox().height ? this.getBBox().height + labelItemPadding : 0;
173    });
174
175  // Add a little triangle/pointer for label boxes
176  // (Rendered first so boxes layer on top of it.)
177  impactLabelGroups.append('polygon')
178    .attr('points', function(d) {
179      const baseY = d.labelAnchorY + 25;
180      const heightY = baseY + 17;
181      const centerX = d.labelAnchorX;
182      const baseX1 = d.labelAnchorX - 15;
183      const baseX2 = d.labelAnchorX + 15;
184      return baseX1 + ',' + baseY + ' ' + baseX2 + ',' + baseY + ' ' + centerX + ',' + heightY;
185    })
186    .attr('fill', 'black');
187  
188  // Add outlet names to the label groups
189  const labelTexts = impactLabelGroups.append('text')
190    
191    // Retrieve and assign the outlet names
192    .text(getOutletName)
193
194    // Format the outlet names
195    .attr('font-family', '"Barlow Condensed", sans-serif')
196    .attr('font-size', '35')
197    .attr('font-weight', '700')
198    .attr('letter-spacing', '4')
199    .attr('fill', 'rgba(56, 78, 119, 1)')
200
201    // Determine the width of the newly formatted outlet names
202    // and calculate their expected starting coordinates
203    .each(function(d) {
204
205      // Width and height are equivalent to the text's bounding box
206      d.formattedNameWidth = this.getBBox().width;
207      d.formattedNameHeight = this.getBBox().height;
208
209      // Starting coordinates are always going to be
210      // shifted to the left by half the width of the
211      // text (so that the label is centered with the anchor point).
212      // If this position would cause the label to extend past
213      // the right or left edge of the graphic, then adjust the
214      // position so that it does not! (Even though we've enabled
215      // overflow, this can be an issue at very small screen sizes.)
216      d.labelStartingPointX = Math.max(0, d.labelAnchorX - d.formattedNameWidth / 2);
217      const labelApproxEndPoint = d.labelStartingPointX + Math.max(d.formattedNameWidth, d.partnerFormattedNameWidth)
218      if (labelApproxEndPoint > 975) {
219        d.labelStartingPointX = d.labelStartingPointX - (labelApproxEndPoint - 975);
220      }
221    })
222  
223  // Position the label text(s) so that they sit on
224  // and are centered with the anchor point
225  labelTexts.attr('x', function(d) {
226    return d.labelStartingPointX;
227  })
228  .attr('y', function(d) {
229    // Main label y-coordinate must account for presence
230    // of partner tag
231    return d.labelAnchorY - d.partnerFormattedNameHeight;
232  })
233  partnerTexts.attr('x', function(d) {
234    return d.labelStartingPointX;
235  })
236  .attr('y', function(d) {
237    return d.labelAnchorY;
238  })
239
240  // Position a white box behind the label text
241  // using the same coordinates/dimensions, but
242  // adding a little padding on all sidees
243  // (account for size of main label AND tag if needed)
244  impactLabelGroups.insert('rect', 'text')
245    .attr('x', function(d) {
246      return d.labelStartingPointX - labelBoxHorizontalPadding;
247    })
248    .attr('y', function(d) {
249      return d.labelAnchorY - d.partnerFormattedNameHeight - labelBoxVerticalPadding - labelBoxVerticalOffset;
250    })
251    .attr('width', function(d) {
252      return Math.max(d.formattedNameWidth, d.partnerFormattedNameWidth) + ( labelBoxHorizontalPadding * 2 ) - labelBoxHighLightWidth;
253    })
254    .attr('height', function(d) {
255      return d.formattedNameHeight + d.partnerFormattedNameHeight + labelBoxVerticalPadding;
256    })
257    .attr('fill', 'rgba(255, 255, 255, 1)')
258    .attr('filter', 'url(#dropShadow)');
259
260  // Add a small red highlight bar at the left of the white box
261  // using the same starting points and sizes as above
262  impactLabelGroups.append('rect')
263    .attr('x', function(d) {
264      return d.labelStartingPointX - labelBoxHorizontalPadding;
265    })
266    .attr('y', function(d) {
267      return d.labelAnchorY - d.partnerFormattedNameHeight - labelBoxVerticalPadding - labelBoxVerticalOffset;
268    })
269    .attr('width', labelBoxHighLightWidth)
270    .attr('height', function(d) {
271      return d.formattedNameHeight + d.partnerFormattedNameHeight + labelBoxVerticalPadding;
272    })
273    .attr('fill', 'var(--fiery_red)');
274
275  // On impact map only, allow cursor to change to "pointer"
276  // when the state has a newsroom, and open that page on click
277  d3.selectAll('#interactiveMapContainerImpactReport .newsroomState')
278    .attr('cursor', 'pointer')
279    .on('click', function() {
280      window.open(this.getAttribute('data-url'), '_blank');
281    });
282}
283
284// Fetch U.S. Atlas TopoJSON data for map of the states,
285// and use this data to generate the newsroom map upon
286// successful retrieval
287window.addEventListener('DOMContentLoaded', async (event) => {
288  const res = await fetch('https://cdn.jsdelivr.net/npm/us-atlas@3/states-albers-10m.json');
289  const mapJson = await res.json();
290  generateNewsroomMap(mapJson);
291});

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.