← back to Nationalrealestate
USRE Map: left-rail county data-point filters in the drawer — dim counties by metric ranges + live match count (v0.8.0)
27ca20752e23791fd596788898c01257c66dbf8f · 2026-07-26 20:11:15 -0700 · Steve
Files touched
M package-lock.jsonM package.jsonM public/index.html
Diff
commit 27ca20752e23791fd596788898c01257c66dbf8f
Author: Steve <steve@designerwallcoverings.com>
Date: Sun Jul 26 20:11:15 2026 -0700
USRE Map: left-rail county data-point filters in the drawer — dim counties by metric ranges + live match count (v0.8.0)
---
package-lock.json | 4 +--
package.json | 2 +-
public/index.html | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
3 files changed, 78 insertions(+), 4 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index a3b7d3b..a16f3ff 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "nationalrealestate",
- "version": "0.7.1",
+ "version": "0.8.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "nationalrealestate",
- "version": "0.7.1",
+ "version": "0.8.0",
"dependencies": {
"better-sqlite3": "^13.0.1",
"dotenv": "^16.4.5",
diff --git a/package.json b/package.json
index 23be7a3..aebddcc 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "nationalrealestate",
- "version": "0.7.1",
+ "version": "0.8.0",
"private": true,
"type": "module",
"description": "USRealEstate — national U.S. residential market explorer. Free-data v1 (Redfin Data Center, Zillow Research, Census ACS, FHFA). Internal analyst tool behind basic auth.",
diff --git a/public/index.html b/public/index.html
index 56f3db9..7ff7917 100644
--- a/public/index.html
+++ b/public/index.html
@@ -39,6 +39,20 @@
.county-tip .nm{font-weight:700;} .county-tip .vl{color:var(--gold);font-variant-numeric:tabular-nums;}
.leaflet-control-attribution{background:rgba(17,21,29,.8)!important;color:var(--muted)!important;}
.leaflet-control-attribution a{color:var(--muted)!important;}
+ /* map county-filter panel (lives inside the hamburger drawer) */
+ #mapfilters{border-top:1px solid var(--line);padding-top:14px;margin-top:6px;}
+ #mapfilters .mf-h{font-size:11px;text-transform:uppercase;letter-spacing:.6px;color:var(--gold);font-weight:700;
+ display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;}
+ #mapfilters #mf-reset{background:none;border:1px solid var(--line);color:var(--muted);border-radius:6px;
+ font-size:10px;padding:2px 8px;cursor:pointer;text-transform:none;letter-spacing:0;}
+ #mapfilters #mf-reset:hover{color:var(--gold);border-color:var(--gold);}
+ #mapfilters .mf-row{margin-bottom:9px;}
+ #mapfilters .mf-row label{display:block;font-size:11px;color:var(--muted);margin-bottom:3px;}
+ #mapfilters .mf-in{display:flex;gap:6px;}
+ #mapfilters .mf-in input{width:100%;min-width:0;background:var(--bg);border:1px solid var(--line);color:var(--fg);
+ border-radius:6px;padding:5px 7px;font-size:12px;}
+ #mapfilters #mf-count{font-size:12px;color:var(--fg);margin-top:8px;font-variant-numeric:tabular-nums;}
+ #mapfilters #mf-count b{color:var(--gold);}
</style>
<link rel="stylesheet" href="/nav-drawer.css">
</head>
@@ -115,11 +129,69 @@ function quantileBreaks(vals, n){
}
function styleFeature(f){
- return { weight: .35, color: '#222936', fillOpacity: .82, fillColor: classColor(values[f.id]) };
+ const pass = filterPass(f.id);
+ return { weight: .35, color: '#222936',
+ fillOpacity: pass ? .82 : .05,
+ fillColor: pass ? classColor(values[f.id]) : NO_DATA };
}
function metricDef(){ return METRICS.find(m => m.key === currentMetric); }
+// ---- county data-point filters (left panel, inside the hamburger drawer) ----
+const metricVals = {}; // metric -> { countyId: value }
+const fRanges = {}; // metric -> {min,max}
+METRICS.forEach(m => fRanges[m.key] = { min: null, max: null });
+let filtersReady = false;
+
+function anyFilterActive(){ return METRICS.some(m => { const r = fRanges[m.key]; return r.min != null || r.max != null; }); }
+function filterPass(id){
+ if (!filtersReady || !anyFilterActive()) return true;
+ for (const m of METRICS){
+ const r = fRanges[m.key];
+ if (r.min == null && r.max == null) continue;
+ const v = (metricVals[m.key] || {})[id];
+ if (v == null || !Number.isFinite(v)) return false; // no data can't satisfy an active range
+ if (r.min != null && !(v >= r.min)) return false;
+ if (r.max != null && !(v <= r.max)) return false;
+ }
+ return true;
+}
+async function loadAllMetricVals(){
+ const res = await Promise.all(METRICS.map(m =>
+ fetch('/api/choropleth?metric=' + m.key).then(r => r.json()).catch(() => ({}))));
+ METRICS.forEach((m, i) => metricVals[m.key] = res[i].values || {});
+ filtersReady = true;
+}
+function applyFilters(){
+ if (geoLayer) geoLayer.setStyle(styleFeature);
+ const el = document.getElementById('mf-count');
+ if (!el) return;
+ if (!anyFilterActive()){ el.textContent = ''; return; }
+ let n = 0, tot = 0;
+ (geojson && geojson.features || []).forEach(f => { tot++; if (filterPass(f.id)) n++; });
+ el.innerHTML = `<b>${n.toLocaleString()}</b> of ${tot.toLocaleString()} counties match`;
+}
+function buildMapFilters(){
+ const host = document.getElementById('navDrawerBody');
+ if (!host || document.getElementById('mapfilters')) return;
+ const wrap = document.createElement('div'); wrap.id = 'mapfilters';
+ wrap.innerHTML = `<div class="mf-h">Filter counties <button id="mf-reset" type="button">reset</button></div>` +
+ METRICS.map(m => `<div class="mf-row" data-k="${m.key}"><label>${m.label}</label>` +
+ `<div class="mf-in"><input type="number" class="mf-min" placeholder="min"><input type="number" class="mf-max" placeholder="max"></div></div>`).join('') +
+ `<div id="mf-count"></div>`;
+ host.appendChild(wrap);
+ wrap.querySelectorAll('.mf-row').forEach(row => {
+ const k = row.dataset.k;
+ row.querySelector('.mf-min').addEventListener('input', e => { fRanges[k].min = e.target.value ? +e.target.value : null; applyFilters(); });
+ row.querySelector('.mf-max').addEventListener('input', e => { fRanges[k].max = e.target.value ? +e.target.value : null; applyFilters(); });
+ });
+ wrap.querySelector('#mf-reset').addEventListener('click', () => {
+ METRICS.forEach(m => fRanges[m.key] = { min: null, max: null });
+ wrap.querySelectorAll('input').forEach(i => i.value = '');
+ applyFilters();
+ });
+}
+
function renderLegend(count){
const md = metricDef();
const el = document.getElementById('legend');
@@ -217,8 +289,10 @@ async function loadTopMarkets(){
]);
geojson = geo;
geoLayer = L.geoJSON(geo, { style: styleFeature, onEachFeature }).addTo(map);
+ buildMapFilters();
loadTopMarkets();
loadNationalStrip();
+ loadAllMetricVals().then(applyFilters); // enable county filtering once all metric values are in
})();
</script>
</body>
← 46b0b58 USRE grid: polished empty state — icon + message + one-click
·
back to Nationalrealestate
·
parcels: add Salt Lake County UT (49035) — 393k parcels, mar b761f11 →