[object Object]

← back to La Socrata Ingester

Viewer: CD/ZIP quick-jump control on the Map view

e6f035643f8cf1c0da499ac739bd3f24a84648a6 · 2026-08-12 11:57:41 -0700 · Steve Abrams

- On-map 'Jump to' control (top-left, clear of the zoom control) with CD + ZIP
  selects. Picking one sets the filter and the existing fitBounds flies the map
  to that area; clearing removes it. Options carry live counts and are drawn from
  a stable unfiltered facet list (allFacets, fetched once at boot) so every CD/ZIP
  is always offered even when a filter is active. Composes with deep-links + saved
  views via the shared state.filters.

Author: Steve Abrams
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit e6f035643f8cf1c0da499ac739bd3f24a84648a6
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Aug 12 11:57:41 2026 -0700

    Viewer: CD/ZIP quick-jump control on the Map view
    
    - On-map 'Jump to' control (top-left, clear of the zoom control) with CD + ZIP
      selects. Picking one sets the filter and the existing fitBounds flies the map
      to that area; clearing removes it. Options carry live counts and are drawn from
      a stable unfiltered facet list (allFacets, fetched once at boot) so every CD/ZIP
      is always offered even when a filter is active. Composes with deep-links + saved
      views via the shared state.filters.
    
    Author: Steve Abrams
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 viewer/public/index.html | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/viewer/public/index.html b/viewer/public/index.html
index 936b821..17f0328 100644
--- a/viewer/public/index.html
+++ b/viewer/public/index.html
@@ -88,6 +88,9 @@
   /* map */
   #map{height:100%;width:100%}
   .mapwrap{position:absolute;inset:0}
+  .mapjump{position:absolute;top:10px;left:52px;z-index:500;background:#fff;border:1px solid var(--line);border-radius:8px;
+    box-shadow:0 2px 8px rgba(0,0,0,.12);padding:5px 8px;display:flex;align-items:center;gap:6px;font-size:11px;color:var(--muted)}
+  .mapjump select{font-size:11px;padding:3px 6px;border:1px solid var(--line);border-radius:6px;background:#fff;color:var(--ink);max-width:150px}
   .legend{position:absolute;bottom:14px;right:14px;z-index:500;background:#fff;border:1px solid var(--line);border-radius:8px;padding:8px 11px;font-size:11px;box-shadow:0 2px 8px rgba(0,0,0,.12)}
   .legend b{display:block;margin-bottom:5px;font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--muted)}
   .legend .row{display:flex;align-items:center;gap:6px;margin:2px 0}
@@ -179,6 +182,11 @@
     </div>
     <div id="mapview" class="mapwrap hidden">
       <div id="map"></div>
+      <div class="mapjump" id="mapjump">
+        <span>Jump to</span>
+        <select id="jumpcd"><option value="">CD…</option></select>
+        <select id="jumpzip"><option value="">ZIP…</option></select>
+      </div>
       <div class="legend" id="legend"></div>
     </div>
   </main>
@@ -441,8 +449,21 @@ function renderMap(){
   if(pts.length) map.fitBounds(pts,{padding:[30,30],maxZoom:14});
   $('#legend').innerHTML = `<b>${$('#colorby').selectedOptions[0].text} · ${state.rows.filter(r=>r.lat).length} shown</b>`+
     legendItems().map(([l,c])=>`<div class="row"><span class="dot" style="background:${c}"></span>${escapeHtml(l)}</div>`).join('');
+  renderMapJump();
   setTimeout(()=>map.invalidateSize(),50);
 }
+// ---- CD / ZIP quick-jump on the map (filters + the existing fitBounds flies there) ----
+let allFacets = {}; // stable full CD/ZIP lists (unfiltered), so the jump menu always offers every value
+function renderMapJump(){
+  const cd=$('#jumpcd'), zip=$('#jumpzip'); if(!cd||!zip) return;
+  const jf = k => (allFacets[k]&&allFacets[k].length)?allFacets[k]:(facetData[k]||[]);
+  const fill=(sel,items,label,cur)=>{ sel.innerHTML=`<option value="">${label}</option>`+
+    (items||[]).map(o=>`<option value="${escapeAttr(o.v)}" ${String(cur||'')===String(o.v)?'selected':''}>${escapeHtml(o.v)} (${Number(o.c).toLocaleString()})</option>`).join(''); };
+  fill(cd, jf('cd'), 'CD…', state.filters.cd);
+  fill(zip, jf('zip'), 'ZIP…', state.filters.zip);
+}
+$('#jumpcd').onchange=e=>{ const v=e.target.value; if(v) state.filters.cd=v; else delete state.filters.cd; reload(); };
+$('#jumpzip').onchange=e=>{ const v=e.target.value; if(v) state.filters.zip=v; else delete state.filters.zip; reload(); };
 
 function syncURL(){ // mirror filters+sort+view+color into the URL so any view is shareable/bookmarkable
   const p=new URLSearchParams();
@@ -617,6 +638,7 @@ document.addEventListener('click',e=>{ if(!e.target.closest('#savedwrap')) $('#s
   hydrateFromQuery(location.search, false); // boot: deep-link params win, else persisted prefs
   syncControls();
   loadFacets(); loadRows(false);
+  fetch(api('/api/facets')).then(r=>r.json()).then(d=>{ allFacets=d; renderMapJump(); }).catch(()=>{}); // stable full CD/ZIP lists for the map jump
 })();
 </script>
 </body>

← 48e81f3 Viewer: per-column filter inputs in the List header  ·  back to La Socrata Ingester  ·  auto-data-snapshot: 2026-08-18T08:10:11 (1 data files) — dat b16e3ff →