PageSourceSearch

https://www.housingwire.com/wp-content/themes/housingwire/js/charts/initialize-charts.js?ver=1790846128

js housingwire.com collected 2026-10-09 22:41:10 UTC 10,893 bytes, 449 lines download raw bytes

1// Define the endpoint with multiple stats
2const ninetyDaysAgo = new Date();
3ninetyDaysAgo.setDate(ninetyDaysAgo.getDate() - 90);
4ninetyDaysAgo.setHours(0, 0, 0, 0); // Normalize to midnight
5const excludedLabels = ['10-year Yield', 'Mortgage Rates'];
6let charts = {};
7let dataWindowStart;
8let yieldLabels = [];
9
10//console.log("Confirm that initialize charts updated 10:33");
11
12// Listeners
13window.addEventListener('orchestratorPrefsDataProcessed', (event) => {
14	console.log('Orchestrator prefs data processed. Firing initCharts().');
15
16	if (event.detail.locationId) {
17		altosUrl =
18			'https://housingwire-api.housingwire.com/rates/altos/multi-stat?altosLocationId=' +
19			event.detail.locationId;
20	} else {
21		altosUrl = hwCharts.nationalAltosUrl;
22	}
23
24	//console.log('start' + dataWindowStart);
25	hwCharts.initCharts(altosUrl);
26	hwCharts.updateLocationName(event.detail.locationName);
27});
28
29// Mapping for the stat labels
30const statLabelMap = {
31	price_median: 'Median List Price',
32	count: 'Inventory',
33	dom_mean: 'Avg Days on Market',
34	price_decreased_percent: 'Price Reductions',
35	rental_price_median: 'Median Rent',
36	'30 YEAR CONFORMING': 'Mortgage Rates',
37	'Ten Year Yield': '10-year Yield',
38	price_increased_percent: 'Price Percentage Increased',
39	dom_median: 'Median Days on Market',
40	relisted_percent: 'Percentage of Listings Relisted',
41	rental_price_median: 'Median Rent',
42	mai: 'Market Action Index',
43	per_sqft_mean: 'Price per Square Foot',
44};
45
46const chartClassNames = [
47	'chart-flex-container flex flex-col h-full',
48	'chart-wrapper grow',
49	'label-data-container text-center pt-1 flex flex-row gap-2 justify-center',
50	'chart-label text-13 flex items-start leading-tight tw-hidden',
51	'chart-data font-bold text-16 lg:text-23',
52	'text-15 sm:text-16 text-center md:mb-2 leading-tight',
53	'flex items-center gap-1 text-13 lg:text-16',
54];
55
56const chartThemeConfig = {
57	tooltipBackgroundColor: '#ffffff',
58	tooltipTextColor: '#000000',
59	borderColor: '#ffffff',
60
61	//fillColor: 'rgba(255,255,255,.1)',
62	fillColor: '#313638',
63	gridLineColor: 'rgba(255,255,255,.2)',
64};
65
66// Function to create a chart for each stat
67function createChart(
68	statLabel,
69	labels,
70	data,
71	classNames,
72	containerID,
73	themeConfig,
74	statLabel2,
75	data2,
76) {
77	// Dynamically create a new subarray of charts
78	if (!charts[containerID]) {
79		charts[containerID] = [];
80	}
81
82	const [
83		chartContainerClass,
84		chartWrapperClass,
85		labelDataDivClass,
86		labelDivClass,
87		dataDivClass,
88		topLabelDivClass,
89		differenceDivClass,
90	] = classNames;
91
92	const chartContainer = document.createElement('div');
93	chartContainer.className = chartContainerClass;
94
95	const chartWrapper = document.createElement('div');
96	chartWrapper.className = chartWrapperClass;
97
98	let tickFontSize;
99	let tickPosition;
100	let tickPadding;
101	let tooltipDateSize;
102	let tooltipBodySize;
103	let tickColor = 'rgba(115, 123, 140, .75)';
104
105	//chartContainer.appendChild( topLabelDiv );
106
107	//chartContainer.appendChild( labelDataDiv );
108	tickFontSize = 10;
109	tickPosition = 'left';
110	tickPadding = 0;
111	tooltipDateSize = 12;
112	tooltipBodySize = 15;
113
114	chartContainer.appendChild(chartWrapper);
115
116	const canvas = document.createElement('canvas');
117	canvas.id = `${statLabel.replace(/\s+/g, '_')}_original`;
118
119	chartWrapper.appendChild(canvas);
120	document.getElementById(containerID).appendChild(chartContainer);
121
122	canvas.width = chartWrapper.clientWidth;
123	canvas.height = chartWrapper.clientHeight;
124
125	const ctx = canvas.getContext('2d');
126
127	const darkerColor = 'rgba(115, 123, 140, .75)';
128
129	const scalesConfig = {
130		x: {
131			display: 'auto',
132			type: 'category',
133			border: {
134				display: true,
135				dash: [3, 3],
136				dashOffset: 0,
137				color: darkerColor,
138				width: 1,
139			},
140			grid: {
141				color: 'hsl(220 10% 90%)',
142				drawOnChartArea: true,
143				tickColor: darkerColor,
144			},
145			ticks: {
146				maxTicksLimit: 6,
147				font: { size: tickFontSize },
148				callback: function (value) {
149					// Use the scale helper to get the original label (handles numeric index)
150					let label = this.getLabelForValue(value);
151					if (!label || 'string' !== typeof label) {
152						return label;
153					}
154					const parts = label.split('-').map(Number);
155					if (3 !== parts.length || parts.some(isNaN)) {
156						return label;
157					}
158					const [year, month, day] = parts;
159					const date = new Date(year, month - 1, day);
160					return date.toLocaleString('en-US', {
161						month: 'short',
162						day: 'numeric',
163						timeZone: 'UTC',
164					});
165				},
166			},
167		},
168		y: {
169			display: 'auto',
170			position: tickPosition,
171			border: {
172				display: true,
173				dash: [3, 3],
174				dashOffset: 0,
175				color: darkerColor,
176				width: 1,
177			},
178			title: {
179				display: true,
180				text: statLabel,
181				color: darkerColor,
182				font: { size: tickFontSize },
183			},
184			ticks: {
185				callback: function (value) {
186					return hwCharts.returnFormattedValue(
187						value,
188						value,
189						statLabel,
190						'y-axis',
191						containerID,
192					);
193				},
194				padding: tickPadding,
195				maxTicksLimit: 5,
196				align: 'left',
197				font: { size: tickFontSize },
198			},
199			grid: {
200				color: 'hsl(220 10% 90%)',
201				drawOnChartArea: true,
202				tickColor: darkerColor,
203			},
204		},
205	};
206
207	const baseYAxis = {
208		...scalesConfig.y,
209		ticks: { ...(scalesConfig.y.ticks || {}) },
210		title: { ...(scalesConfig.y.title || {}) },
211		grid: { ...(scalesConfig.y.grid || {}) },
212		border: { ...(scalesConfig.y.border || {}) },
213	};
214
215	let datasets = [
216		{
217			label: statLabel,
218			data: data,
219			borderColor: 'rgba(237, 172, 184, 1)',
220			backgroundColor: 'rgba(237, 172, 184, .1)',
221			borderWidth: 2,
222			fill: true,
223		},
224	];
225
226	if (statLabel2 && data2 && 0 < data2.length) {
227		datasets.push({
228			label: statLabel2,
229			data: data2,
230			borderColor: 'rgba(158, 196, 248, 1)',
231			backgroundColor: 'rgba(158, 196, 248, .1)',
232			borderWidth: 2,
233			fill: true,
234			order: 1,
235			pointRadius: 0,
236			yAxisID: 'y2',
237		});
238
239		scalesConfig.y2 = {
240			...baseYAxis,
241			display: 'auto',
242			position: 'right',
243			grid: {
244				...baseYAxis.grid,
245				display: false,
246				drawTicks: true,
247				tickColor: darkerColor,
248				tickLength: 6,
249			},
250			title: {
251				...baseYAxis.title,
252				display: true,
253				text: 'Inventory',
254				color: darkerColor,
255				font: { size: tickFontSize },
256			},
257			border: { ...baseYAxis.border, color: darkerColor },
258			ticks: {
259				...baseYAxis.ticks,
260				align: 'right',
261				color: darkerColor,
262				callback: function (value) {
263					return hwCharts.returnFormattedValue(
264						value,
265						value,
266						statLabel2,
267						'y-axis',
268						containerID,
269					);
270				},
271			},
272		};
273	}
274
275	const chartConfig = {
276		type: 'line',
277		data: {
278			labels: labels,
279			datasets: datasets,
280		},
281		options: {
282			responsive: true,
283			maintainAspectRatio: false,
284			spanGaps: true,
285			scales: scalesConfig,
286			plugins: {
287				legend: { display: false },
288				tooltip: {
289					enabled: true,
290					backgroundColor: themeConfig.tooltipBackgroundColor,
291					titleColor: '#7C7C7B',
292					bodyColor: themeConfig.tooltipTextColor,
293					borderColor: '#908E8D',
294					borderWidth: 0,
295					titleMarginBottom: 4,
296					titleFont: {
297						weight: 'normal',
298						size: tooltipDateSize,
299					},
300					bodyFont: { size: tooltipBodySize },
301					cornerRadius: 0,
302					displayColors: false,
303					callbacks: {
304						label: function (tooltipItem) {
305							return hwCharts.returnFormattedValue(
306								tooltipItem.raw,
307								tooltipItem.formattedValue,
308								statLabel,
309								'tooltip',
310								containerID,
311							);
312						},
313						title: function (tooltipItem) {
314							let formattedDate;
315
316							if ('Mortgage Rates' != statLabel) {
317								const originalDate = tooltipItem[0].label;
318								formattedDate = hwCharts.formatDate(originalDate);
319							} else {
320								const originalDate = tooltipItem[0].label;
321
322								//console.log('originalDate: ' + originalDate);
323								const date = new Date(originalDate);
324								const options = {
325									year: 'numeric',
326									month: 'long',
327									day: 'numeric',
328								};
329								formattedDate = date.toLocaleDateString('en-US', options);
330							}
331							return formattedDate;
332						},
333					},
334				},
335			},
336			elements: {
337				line: { tension: 0.4 },
338				point: {
339					pointStyle: false,
340					radius: 10,
341				},
342			},
343		},
344	};
345
346	const myChart = new Chart(ctx, chartConfig);
347
348	// Push myChart into the correct subarray
349	charts[containerID].push(myChart);
350}
351
352hwCharts.initCharts = async function (altosUrl) {
353	// Clear container
354	const containerID = 'chartsContainer';
355	const chartsContainer = document.getElementById(containerID);
356
357	if (chartsContainer) {
358		chartsContainer.innerHTML = '';
359	}
360
361	const chartsToShow = ['price_median', 'count'];
362	dataWindowStart = hwCharts.setWindowStart(90);
363
364	try {
365		const response1 = await fetch(altosUrl);
366		const data1 = await response1.json();
367
368		hwCharts.updateStatsFromData(data1);
369
370		let anyDatasetEmpty = false; // Track if any dataset is empty
371
372		// Process charts
373		for (let i = 0; i < chartsToShow.length; i += 2) {
374			const statKey1 = chartsToShow[i];
375			const statKey2 = chartsToShow[i + 1];
376			const chartsToIgnore = ['10-year Yield', 'Mortgage Rates'];
377
378			const label1 = statLabelMap[statKey1];
379			if (chartsToIgnore.includes(label1)) {
380				continue;
381			}
382
383			const stat1 = data1.stats.find((s) => s.stat === statKey1) || null;
384			const stat2 = statKey2
385				? data1.stats.find((s) => s.stat === statKey2) || null
386				: null;
387
388			if (!stat1 && !stat2) {
389				anyDatasetEmpty = true;
390				continue;
391			}
392
393			const proc = (stat) => {
394				if (!stat || !stat.data) {
395					return { labels: [], data: [] };
396				}
397				const entries = Object.entries(stat.data)
398					.filter(([date]) => new Date(date) >= ninetyDaysAgo)
399					.sort((a, b) => new Date(a[0]) - new Date(b[0]));
400				return {
401					labels: entries.map((e) => e[0]),
402					data: entries.map((e) => e[1]),
403				};
404			};
405
406			const ds1 = proc(stat1);
407			const ds2 = proc(stat2);
408
409			if (0 === ds1.data.length && 0 === ds2.data.length) {
410				anyDatasetEmpty = true;
411				continue;
412			}
413
414			// If primary is empty but secondary exists, promote secondary to primary
415			const primaryLabels = ds1.data.length ? ds1.labels : ds2.labels;
416			const primaryData = ds1.data.length ? ds1.data : ds2.data;
417			const secondaryLabels = ds1.data.length ? ds2.labels : [];
418			const secondaryData = ds1.data.length ? ds2.data : [];
419
420			const primaryLabelText =
421				statLabelMap[statKey1] || (stat1 ? stat1.stat : statKey1);
422			if (chartsContainer) {
423				createChart(
424					primaryLabelText,
425					primaryLabels,
426					primaryData,
427					chartClassNames,
428					containerID,
429					chartThemeConfig,
430					secondaryLabels,
431					secondaryData,
432				);
433			}
434		}
435
436		// Execute function with 'empty' status
437		hwCharts.handleEmptyDataset(anyDatasetEmpty);
438	} catch (error) {
439		console.error('Failed to fetch altosUrl data:', error);
440	}
441};
442
443window.addEventListener('resize', () => {
444	Object.values(charts).forEach((subArray) => {
445		subArray.forEach((chart) => {
446			chart.resize(); // Resize each chart
447		});
448	});
449});

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.