categoryGroup = (cat) => {
if (cat === "Teacher") return "Teacher";
if (cat === "Substitute Teacher") return "Substitute";
if (cat === "Paraeducator (SPED)" || cat === "Paraeducator (Other)") return "Paras";
if (cat === "Other Administrator" || cat === "Instructional Support" || cat === "Principal/Asst. Principal") return "Admin/Support";
if (cat === "Athletic Coach") return "Athletic Coach";
if (cat === "Support Services") return "Support Services";
return "Other";
}categoryData = d3.rollups(filtered, v => v.length, d => categoryGroup(d.category))
.map(([category, count]) => ({category, count}))subjectGroup = (subj) => {
if (subj === "Special Education") return "Special Education";
if (subj === "Multi-Lingual Learners") return "Multi-Lingual Learners";
if (subj === "Science") return "Science";
if (subj === "Mathematics") return "Mathematics";
if (subj === "English Language Arts") return "ELA";
if (subj === "Social Studies") return "Social Studies";
if (subj === "Elementary Education") return "Elementary";
return "Other";
}
subjectData = d3.rollups(
filtered.filter(d => d.subject !== "(none)"),
v => v.length,
d => subjectGroup(d.subject)
).map(([subject, count]) => ({subject, count})){
const viewW = window.innerWidth;
const isMobile = viewW <= 768;
const mainW = isMobile ? viewW - 32 : viewW - 280 - 48;
const gap = 12;
const catW = Math.max(200, isMobile ? mainW : Math.floor((mainW - gap) / 2));
const subjW = catW;
const catH = Math.round(catW * 0.55);
const subjH = Math.round(subjW * 0.55);
return html`<div>
<div class="date-range">Posts scraped between ${date_start} and ${date_end}</div>
<div class="charts-row">
<div class="chart-card">
<div class="chart-title">Posts by Category</div>
${Plot.plot({
marginLeft: Math.min(140, Math.round(catW * 0.3)),
marginTop: 8,
marginBottom: 28,
width: catW,
height: catH,
style: {fontSize: "12px", fontFamily: "Source Sans 3, Source Sans Pro, sans-serif", background: "transparent"},
x: {label: "Posts"},
y: {label: null},
color: {
range: ["#1e40af", "#2563eb", "#3b82f6", "#6366f1", "#818cf8", "#a78bfa", "#c4b5fd"]
},
marks: [
Plot.gridX({stroke: "#e2e8f0", strokeOpacity: 0.5}),
Plot.barX(categoryData, {
x: "count", y: "category", fill: "category",
sort: {y: "-x"}, rx: 3
}),
Plot.tip(categoryData, Plot.pointerY({
x: "count", y: "category",
title: d => `Category: ${d.category}\nCount: ${d.count.toLocaleString()}`
}))
]
})}
</div>
<div class="chart-card">
<div class="chart-title">Teacher Posts by Subject</div>
${Plot.plot({
marginLeft: Math.min(160, Math.round(subjW * 0.35)),
marginTop: 8,
marginBottom: 28,
width: subjW,
height: subjH,
style: {fontSize: "12px", fontFamily: "Source Sans 3, Source Sans Pro, sans-serif", background: "transparent"},
x: {label: "Posts"},
y: {label: null},
marks: [
Plot.gridX({stroke: "#e2e8f0", strokeOpacity: 0.5}),
Plot.barX(subjectData, {
x: "count", y: "subject", fill: "#6366f1",
sort: {y: "-x"}, rx: 3
}),
Plot.tip(subjectData, Plot.pointerY({
x: "count", y: "subject",
title: d => `Subject: ${d.subject}\nCount: ${d.count.toLocaleString()}`
}))
]
})}
</div>
</div>
</div>`;
}districtData = d3.rollups(filtered, v => {
const teacher = ["Teacher"];
const substitute = ["Substitute Teacher"];
const paras = ["Paraeducator (SPED)", "Paraeducator (Other)"];
const admin = ["Other Administrator", "Instructional Support", "Principal/Asst. Principal"];
const athletic = ["Athletic Coach"];
const support = ["Support Services"];
const allSpecific = [...teacher, ...substitute, ...paras, ...admin, ...athletic, ...support];
return {
All: v.length,
Teacher: v.filter(d => teacher.includes(d.category)).length,
Substitute: v.filter(d => substitute.includes(d.category)).length,
Paras: v.filter(d => paras.includes(d.category)).length,
"Admin/Support": v.filter(d => admin.includes(d.category)).length,
"Athletic Coach": v.filter(d => athletic.includes(d.category)).length,
"Support Services": v.filter(d => support.includes(d.category)).length,
Other: v.filter(d => !allSpecific.includes(d.category)).length
};
}, d => d.lea_name)
.map(([name, counts]) => ({District: name, ...counts}))
.sort((a, b) => b.All - a.All)html`<div class="table-scroll"><table class="district-table">
<thead>
<tr>
<th>District</th>
<th class="num">All</th>
<th class="num">Teacher</th>
<th class="num">Substitute</th>
<th class="num">Paras</th>
<th class="num">Admin/Support</th>
<th class="num">Athletic Coach</th>
<th class="num">Support Services</th>
<th class="num">Other</th>
</tr>
</thead>
<tbody>
${districtData.map(d => html`<tr>
<td class="district-name" title="${d.District}">${d.District}</td>
<td class="num">${d.All.toLocaleString()}</td>
<td class="num">${d.Teacher.toLocaleString()}</td>
<td class="num">${d.Substitute.toLocaleString()}</td>
<td class="num">${d.Paras.toLocaleString()}</td>
<td class="num">${d["Admin/Support"].toLocaleString()}</td>
<td class="num">${d["Athletic Coach"].toLocaleString()}</td>
<td class="num">${d["Support Services"].toLocaleString()}</td>
<td class="num">${d.Other.toLocaleString()}</td>
</tr>`)}
</tbody>
</table></div>`