@kahwee/sf-map-svg 1.5.2 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +160 -6
  3. package/SOURCES.md +34 -0
  4. package/dist/data/README.md +5 -1
  5. package/dist/data/candidates/2016-11-08.json +1 -0
  6. package/dist/data/candidates/2018-11-06.json +1 -0
  7. package/dist/data/candidates/2020-11-03.json +1 -0
  8. package/dist/data/candidates/2022-11-08.json +1 -0
  9. package/dist/data/candidates/2024-11-05.json +1 -0
  10. package/dist/data/candidates/2026-06-02.json +1 -0
  11. package/dist/data/candidates/README.md +43 -0
  12. package/dist/data/candidates/catalog.json +40 -0
  13. package/dist/data/types.d.ts +4 -1
  14. package/dist/src/api.d.ts +3 -0
  15. package/dist/src/api.js +3 -0
  16. package/dist/src/camera.d.ts +16 -0
  17. package/dist/src/camera.js +55 -0
  18. package/dist/src/clusters.d.ts +7 -0
  19. package/dist/src/clusters.js +33 -0
  20. package/dist/src/configuration.d.ts +5 -0
  21. package/dist/src/configuration.js +73 -0
  22. package/dist/src/controller-types.d.ts +84 -0
  23. package/dist/src/controller-types.js +1 -0
  24. package/dist/src/explorer-core.d.ts +1 -1
  25. package/dist/src/explorer-core.js +570 -145
  26. package/dist/src/features.d.ts +24 -0
  27. package/dist/src/features.js +99 -0
  28. package/dist/src/guide-data.js +3 -2
  29. package/dist/src/guide-detailed.js +3 -2
  30. package/dist/src/guide-map.d.ts +4 -0
  31. package/dist/src/guide-map.js +35 -10
  32. package/dist/src/guide-shell.d.ts +2 -0
  33. package/dist/src/guide-shell.js +16 -0
  34. package/dist/src/guide-static.d.ts +9 -0
  35. package/dist/src/guide-static.js +26 -0
  36. package/dist/src/guide.d.ts +2 -1
  37. package/dist/src/guide.js +1 -1
  38. package/dist/src/interactive-data.d.ts +1 -1
  39. package/dist/src/interactive-data.js +2 -0
  40. package/dist/src/interactive.d.ts +1 -1
  41. package/dist/src/interactive.js +2 -0
  42. package/dist/src/layers.js +3 -1
  43. package/dist/src/map-core.js +3 -8
  44. package/dist/src/map.d.ts +7 -0
  45. package/dist/src/map.js +106 -0
  46. package/dist/src/presets.d.ts +3 -0
  47. package/dist/src/presets.js +17 -0
  48. package/dist/src/static.d.ts +10 -0
  49. package/dist/src/static.js +5 -0
  50. package/dist/src/types.d.ts +73 -9
  51. package/dist/src/validation.d.ts +7 -0
  52. package/dist/src/validation.js +226 -0
  53. package/dist/src/viewport.js +9 -2
  54. package/docs/EXAMPLES.md +11 -1
  55. package/docs/api-audit.md +80 -0
  56. package/docs/consumer-integration.md +165 -0
  57. package/docs/guide-bundle-report.md +19 -0
  58. package/docs/migration-v2.md +238 -0
  59. package/package.json +40 -11
package/CHANGELOG.md CHANGED
@@ -4,6 +4,52 @@ User-visible changes are recorded here. Unreleased entries describe changes on `
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 2.1.0 — 2026-09-27
8
+
9
+ - Pages site: add motion and a dark theme. Cross-document View Transitions keep the header still and morph example titles into page titles; the home hero is an interactive dot map of the 2022 districts; entrances, count-ups, growing charts, and hover/focus micro-interactions respect reduced motion and print. The site adds no library.
10
+ - Pages site: load simplified display maps and thumbnails instead of full-precision SVGs and bundled GeoJSON (candidate and proposition explorers drop from about 391 KB to 12 KB of compressed JavaScript and CSS); add dark map thumbnails, a favicon, a 404 page, a sitemap, per-page social tags, theme-colored browser bars, and a browser smoke test with size budgets that gates deployment.
11
+ - Add six optional San Francisco candidate vote snapshots (2016–2026), a Pages explorer, official source records, and a dataset import script.
12
+
13
+ ## 2.0.0 — 2026-09-27
14
+
15
+ Version 2 makes geography an explicit dependency and separates the application API
16
+ from its DOM element. This keeps small consumers small, gives configuration one
17
+ consistent home, and makes animation and subscription ownership predictable.
18
+
19
+ **Migration:** [Complete v1 → v2 guide, with before/after examples](https://github.com/kahwee/sf-map-svg/blob/v2.0.0/docs/migration-v2.md).
20
+ **API:** [README](https://github.com/kahwee/sf-map-svg/blob/v2.0.0/README.md).
21
+ **Consumer performance:** [Integration guide](https://github.com/kahwee/sf-map-svg/blob/v2.0.0/docs/consumer-integration.md).
22
+
23
+ ### Breaking changes
24
+
25
+ - The root export now provides data-free `createMap(data, options)` and `renderMap(data, options)`. Move old `createSFMap`, `renderSFMap`, `neighborhoodNames`, `districtColors`, `districtYears`, and legacy static type imports to `/legacy` for an incremental migration. Existing named subpaths retain their compatibility APIs.
26
+ - `createMap` returns a controller: mount `map.element`. Features belong in `features`, styling in `appearance`; flat spellings are rejected. Use `configure({ features, layers, controls })`, `camera.*`, and typed `on()` subscriptions. Appearance is construction-only.
27
+ - The controller throws on operations after disposal; `destroy()` and unsubscribe remain idempotent. Configuration rejects unknown/malformed keys and invalid ranges. Marker/overlay IDs must be unique. Shared guide geography is immutable; clone before deriving custom data.
28
+
29
+ ### Added
30
+
31
+ - Data-free `/map` and `/static` entrypoints, configuration-only `/presets`, detached configuration snapshots, and all-groups validation before configuration updates. Camera pan, zoom, reset, fit and set accept consistent animation options.
32
+ - Palette, label font/weight/halo, selected/hover neighborhood styling, custom legend entries, compact expandable attribution, and independent visible/accessibility touch labels.
33
+ - Opt-in eased camera motion and staggered marker entrances, respecting reduced motion, interruption, preference changes, and disposal.
34
+ - Per-pin radii, selected rings, deterministic screen-space clustering and accessible marker choice, HTML overlay placement, screen projection, north arrow and approximate scale bar.
35
+ - Server-safe overview `createGuideSVG` / `createGuideShell` and compact `mountGuideMap`, using matching simplified geography and reserved layout rows.
36
+ - Coastline-only basemaps without neighborhood datasets. SFAR remains the default when supplied; alternative-only data selects the available source.
37
+ - Full migration documentation, public examples, adversarial API contract matrix, production import-graph/size budgets, and automatic verification that GitHub release notes contain the complete changelog. The npm archive now includes CHANGELOG.md.
38
+
39
+ ### Fixed and hardened
40
+
41
+ - Reentrant camera and selection callbacks cannot revive obsolete animation or overwrite a newer selection. Failed source, marker, overlay, feature, layer and control updates preserve existing state.
42
+ - Runtime controls remain independent, feature toggles preserve camera/selection, and unrelated configuration updates no longer interrupt motion. Hiding touch controls returns gestures to the page.
43
+ - Revoke obsolete cluster handlers immediately when markers, selection or clustering settings change; v2 overlay activation is wired to typed events for mouse and keyboard users.
44
+ - Keyboard focus remains reachable through filtering and clustering; removed overlays lose listeners. Construction failures and repeated destruction clean up observers, listeners and asynchronous work.
45
+ - Detached selection/event snapshots prevent accidental mutation of renderer state. Malformed/sparse viewports, coordinates, padding, overlay geometry and misspelled options are rejected. Corrected public `LineString` overlay typing.
46
+ - Root static imports tree-shake away browser code and all geographic JSON. Renderer, camera, clustering, configuration and validation have separate internal boundaries; rendering remains offline with zero runtime dependencies.
47
+
48
+ ### Validation and bundle guidance
49
+
50
+ - Covered by Node regression tests, Chromium Storybook interactions/coverage, declaration tests, clean packed-consumer installation, and desktop/390px browser inspection. Required checks include demo, Storybook and Pages builds.
51
+ - Representative production gzip measurements: v2 root **22.8 KiB without geography**; static-only root import **4.5 KiB**; compatibility guide including overview geography **110.2 KiB**. Consumer output varies. Runtime switches do not remove imported code or data; use narrow entrypoints, explicit datasets and lazy detail loading.
52
+
7
53
  ## 1.5.2 — 2026-09-26
8
54
 
9
55
  - Run every Storybook story as a Chromium/Vitest browser check and publish an LCOV and JSON coverage artifact for the renderer, with baseline regression thresholds.
package/README.md CHANGED
@@ -14,7 +14,7 @@ Self-contained SVG maps of San Francisco, with precise coastlines, soft district
14
14
  | --- | --- | --- |
15
15
  | Explore real election data | [California propositions by SF district](https://kahwee.github.io/sf-map-svg/propositions.html) or [local measures](https://kahwee.github.io/sf-map-svg/measures.html) | `custom-map` |
16
16
  | Make a small interactive city map | [Neighborhood guide](https://kahwee.github.io/sf-map-svg/#explore-more-title) | `/guide` |
17
- | Render a static or custom SVG | [Code recipes](docs/EXAMPLES.md) | Root or `/custom-map` |
17
+ | Render a static or custom SVG | [Code recipes](docs/EXAMPLES.md) | `/static` or `/custom-map` |
18
18
  | Animate a route | [BART journey](https://kahwee.github.io/sf-map-svg/transit.html) | `/transit` or overlays |
19
19
 
20
20
  ## Install
@@ -27,7 +27,9 @@ Use Node 22.12+ for server-side rendering. Browser components need a DOM and a b
27
27
 
28
28
  | Start with | Entry point | What you get |
29
29
  | --- | --- | --- |
30
- | Static SVG | `@kahwee/sf-map-svg` | SVG markup, projection helpers, optional layers |
30
+ | v2 controller | `@kahwee/sf-map-svg` or `/map` | Explicit data, grouped options, managed events and camera |
31
+ | v2 static SVG | `@kahwee/sf-map-svg/static` | Server-safe rendering with explicit data |
32
+ | Compatibility static SVG | `@kahwee/sf-map-svg/legacy` | Original renderer with bundled geography |
31
33
  | Data-injected SVG | `@kahwee/sf-map-svg/custom-map` | Tree-shakeable renderer core with only the geographic data you provide |
32
34
  | Neighborhood explorer | `@kahwee/sf-map-svg/explorer` | Search, source selection, map controls, GeoJSON downloads |
33
35
  | Interactive map | `@kahwee/sf-map-svg/interactive` | Embeddable map and controls without the explorer sidebar |
@@ -38,11 +40,44 @@ Use Node 22.12+ for server-side rendering. Browser components need a DOM and a b
38
40
  | Metadata search | `@kahwee/sf-map-svg/data/catalog` | Search names without polygon geometry |
39
41
  | SFAR lookup | `@kahwee/sf-map-svg/data/realtor` | Default neighborhoods without alternative sources |
40
42
 
43
+ ## Version 2: explicit data and a controller
44
+
45
+ ```js
46
+ import { createMap } from '@kahwee/sf-map-svg';
47
+ import { guideMapData } from '@kahwee/sf-map-svg/guide/data';
48
+ import { guideOptions } from '@kahwee/sf-map-svg/presets';
49
+
50
+ const map = createMap(guideMapData, {
51
+ ...guideOptions,
52
+ features: { motion: true, markerEntrance: true, clustering: true },
53
+ appearance: { colors: { water: '#e6f1f5' } },
54
+ });
55
+ document.querySelector('#map').append(map.element);
56
+ map.configure({ features: { motion: false }, controls: { pan: false } });
57
+ const unsubscribe = map.on('markerchange', ({ marker }) => console.log(marker?.id));
58
+ map.camera.reset({ animate: false });
59
+ // On component disposal: map.destroy();
60
+ ```
61
+
62
+ The root and `/map` include no geography; `/static` includes no interactive runtime.
63
+ `guide/data` supplies only overview geography; `/presets` supplies configuration only.
64
+ Camera methods share `{ animate, duration }`. `configure()` accepts the same feature,
65
+ layer and control groups as construction and validates the entire patch before applying
66
+ it. Appearance is construction-only. The controller's `on()` returns an unsubscribe
67
+ function and disposal removes all its subscriptions. Operations after disposal throw;
68
+ `destroy()` itself is idempotent.
69
+
70
+ `renderMap(data, options)` from `/static` returns `{ svg, project, ... }` and needs only
71
+ `StaticMapData` (for example, `guideMapData.map`). Static options retain their SVG-unit
72
+ semantics; camera/motion/control options belong exclusively to the browser controller.
73
+ See [v2 migration and architecture](docs/migration-v2.md) for breaking changes,
74
+ configuration resets, and bundle boundaries. Existing subpaths remain compatibility APIs.
75
+
41
76
  ## Render a static map
42
77
 
43
78
  ```js
44
79
  import { writeFile } from 'node:fs/promises';
45
- import { renderSFMap } from '@kahwee/sf-map-svg';
80
+ import { renderSFMap } from '@kahwee/sf-map-svg/legacy';
46
81
 
47
82
  const svg = renderSFMap({
48
83
  landmarks: true,
@@ -61,8 +96,8 @@ That entry point does not import the package's built-in JSON collections. Its `S
61
96
  requires the coast and accepts selected district vintages plus optional neighborhood,
62
97
  road, park, and station arrays. Use the canonical JSON subpaths documented in
63
98
  [`data/README.md`](data/README.md) as source; map each feature collection to the
64
- corresponding `SFMapData` records. The root entry remains convenient and includes the
65
- built-in datasets for backward compatibility.
99
+ corresponding `SFMapData` records. The `/legacy` entry includes the built-in datasets for migration. The v2 root
100
+ imports no geographic JSON.
66
101
 
67
102
  In tree-shaking bundlers, importing a single symbol from `/data` retains only the
68
103
  modules that symbol uses.
@@ -387,7 +422,7 @@ pnpm test:stories:coverage # Chromium checks plus renderer coverage report
387
422
  pnpm test:package # install and check the packed package
388
423
  ```
389
424
 
390
- Use `pnpm format` to apply Biome formatting and safe lint fixes. Install Chromium once with `pnpm exec playwright install chromium` before local Storybook tests. Run `pnpm storybook` for interactive component examples at http://127.0.0.1:6006. The coverage command writes `coverage/storybook/coverage-summary.json` and `lcov.info` for `src/` TypeScript only. CI runs package checks on Node 22, 24, and 26 plus Chromium Storybook checks on Node 24, and uploads the coverage report. See [CONTRIBUTING.md](CONTRIBUTING.md) for source structure and release instructions.
425
+ Use `pnpm format` to apply Biome formatting and safe lint fixes. Install Chromium once with `pnpm exec playwright install chromium` before local Storybook tests. Run `pnpm storybook` for interactive component examples at http://127.0.0.1:6006. The coverage command writes `coverage/storybook/coverage-summary.json` and `lcov.info` for `src/` TypeScript only. CI runs package and Chromium Storybook checks on Node 26 and uploads the coverage report. See [CONTRIBUTING.md](CONTRIBUTING.md) for source structure and release instructions.
391
426
 
392
427
  ### Browser verification
393
428
 
@@ -418,6 +453,21 @@ synthetic pointer tests do not establish physical-device compatibility.
418
453
 
419
454
  The [civic atlas](https://kahwee.github.io/sf-map-svg/) leads with certified June 2026 ballot measure results. Visitors can select a measure and district, switch Yes/No shading, compare the official 2002, 2012, and 2022 district maps, and play a schematic BART journey. The boundary animation morphs matched district outlines between dated SVGs. Intermediate shapes illustrate the change; each completed year uses its exact published geometry. Playback is user initiated, pauses when the page is hidden, and switches instantly when reduced motion is requested. The lightweight neighborhood guide loads on demand. The full [ballot measures explorer](https://kahwee.github.io/sf-map-svg/measures.html) spans all three district map years with four separately sourced election snapshots.
420
455
 
456
+ The [candidate vote explorer](https://kahwee.github.io/sf-map-svg/candidates.html) maps
457
+ 56 certified federal, statewide, and state legislative contests across six
458
+ San Francisco elections from 2016 to 2026. It loads one election JSON on
459
+ demand, marks districts only partly eligible for House or legislative races,
460
+ and keeps the 2012 and 2022 supervisorial map vintages distinct. Applications
461
+ can import a snapshot explicitly from npm without adding it to the default
462
+ renderer:
463
+
464
+ ```js
465
+ import election from '@kahwee/sf-map-svg/data/candidates/2024-11-05.json' with { type: 'json' };
466
+ ```
467
+
468
+ See the [candidate schema](data/candidates/README.md), [source records](SOURCES.md),
469
+ and [next dataset ideas](docs/data-opportunities.md).
470
+
421
471
  ```sh
422
472
  pnpm build:pages # local preview
423
473
  pnpm build:pages --released # use the current npm release
@@ -449,3 +499,107 @@ Embed the Pages demo with `<iframe src="https://kahwee.github.io/sf-map-svg/tran
449
499
  ## Ballot measures explorer
450
500
 
451
501
  [Explore local ballot measures](https://kahwee.github.io/sf-map-svg/measures.html): 44 measures from the November 2002, November 2012, November 2022, and June 2026 elections, across all three supported district map vintages. Pick an election, search its measures, inspect a district, compare two measures, and download an SVG, CSV, or the sourced JSON. The page includes touch pan/zoom, keyboard district selection, a sortable district table, and shareable year-specific views. Each election's results and district map load on demand. This is an archive of four elections, not every intervening election or a live results service. See [the election data schema](data/elections/README.md) and [SOURCES.md](SOURCES.md) for methods and source links.
502
+
503
+ ### Motion, styling, and progressive embeds
504
+
505
+ The guide now exposes the same `colors` palette as the static renderer. Existing
506
+ appearance and immediate camera movement remain the defaults. See
507
+ [consumer integration recommendations](docs/consumer-integration.md) for the
508
+ complete example, bundle choices, progressive shell, and testing contract.
509
+
510
+ ```js
511
+ import { createGuideMap } from '@kahwee/sf-map-svg/guide/map';
512
+
513
+ const map = createGuideMap({
514
+ colors: { water: '#202d38', land: '#34434a', park: '#42624d',
515
+ road: '#728080', neighborhood: '#64767e', label: '#f1f3ee' },
516
+ labelStyle: { fontFamily: 'DM Sans, system-ui, sans-serif', fontWeight: 550,
517
+ haloColor: '#34434a' },
518
+ motion: { duration: 400 },
519
+ markerEntrance: { duration: 450, stagger: 35 },
520
+ selectedMarkerRing: { color: '#f1f3ee', width: 2, gap: 3 },
521
+ clustering: { radius: 32 },
522
+ attribution: 'compact',
523
+ legend: { items: [{ label: 'Places', color: '#cf8757' }] },
524
+ northArrow: true,
525
+ scaleBar: true,
526
+ strings: { touchNavigation: 'Touch pan', touchNavigationLabel: 'Enable touch pan',
527
+ touchNavigationDone: 'Done', touchNavigationExitLabel: 'Restore page scrolling' },
528
+ });
529
+ document.querySelector('#map').append(map);
530
+ ```
531
+
532
+ | Option | Contract |
533
+ | --- | --- |
534
+ | `colors` | Static palette keys: water, land, district, neighborhood, highway, road, park, landmark, BART (`bart`), label, marker, selected |
535
+ | `labelStyle` | `fontFamily`, numeric `fontWeight`, `haloColor`; the application loads any custom font |
536
+ | `areaStyle` | `selectedFill`, `selectedStroke`, `hoverFill`, `hoverStroke`; applies to selectable neighborhoods |
537
+ | `motion` | `false` by default; `true` uses 320ms, or `{ duration }` in milliseconds |
538
+ | `markerEntrance` | `false` by default; `true` uses 420ms and 35ms stagger, or `{ duration, stagger }`; only newly introduced IDs animate, delay capped at 1s |
539
+ | `MapMarker.radius` | Per-marker visible radius; interactive units are CSS pixels, static units are SVG units |
540
+ | `selectedMarkerRing` | Optional `{ color, width, gap }` in CSS pixels; preserves the hit target |
541
+ | `clustering` | `false` by default; `true` or `{ radius }` groups nearby screen positions; selected pin remains independent |
542
+ | `legend` | `{ builtins: false, items: [{ label, color }] }` replaces built-ins; omit `builtins` to append custom entries; `hidden` hides selected built-ins (`bart`, `park`, `highway`, `road`) |
543
+ | `attribution` | `'full'` (default) or `'compact'`; compact keeps full provenance in a native disclosure |
544
+ | `northArrow`, `scaleBar` | Optional canvas furniture; scale is approximate at central SF latitude, in metric units |
545
+
546
+ `setViewport(view, { animate, duration })`, `fitGeometry(geometry, padding,
547
+ { animate, duration })`, `selectMarker(id, { fit, animate, duration })`, and
548
+ `selectNeighborhood(name, { fit, animate, duration })` accept per-call motion
549
+ controls. `animate: true` opts in even when global motion is disabled.
550
+ `stopAnimation()` freezes the camera at its current viewport. A new camera
551
+ operation replaces the previous transition; pointer gestures interrupt it.
552
+ Reduced-motion preference overrides all animation requests. `destroy()` cancels
553
+ camera frames, marker animations, listeners, and resize observation.
554
+
555
+ `map.overlayElement` is a public HTML overlay slot. `map.projectToScreen(lng, lat)`
556
+ returns `{ x, y, visible }` in CSS pixels relative to that slot. Call it after
557
+ mounting; reposition your callout on `viewportchange` and `mapresize`.
558
+ The slot ignores pointer events; interactive children can set `pointer-events:auto`.
559
+ `clusteractivate` emits `{ markers }` and fits their geographic extent. Coincident
560
+ pins cannot separate through zoom; retain the full native marker chooser or an
561
+ accessible external list. `--sf-marker-index` on each marker is a stable entrance
562
+ index hook, but the built-in animation avoids the need to style internal SVG.
563
+
564
+ For server rendering, `createGuideSVG(options)` from `@kahwee/sf-map-svg/guide/static`
565
+ uses the same simplified geography as the browser guide with one import.
566
+ `createGuideShell(options)` also reserves compact toolbar, legend, and attribution
567
+ rows; pass its `.sf-guide-shell` element to `mountGuideMap(shell, options)` from
568
+ `@kahwee/sf-map-svg/guide/map`. By default, this compact layout hides the native
569
+ pickers, pan buttons, label switch, help, and visible status; explicit control overrides are honored. Provide an accessible
570
+ external place list. The toolbar and legend scroll horizontally if needed.
571
+
572
+ ### Reconfigure without rebuilding
573
+
574
+ Construction options remain backward-compatible. Use these atomic patches for
575
+ runtime switches; they preserve the map element, viewport, markers, and selection:
576
+
577
+ ```js
578
+ map.setFeatures({ motion: false, clustering: true, selectedMarkerRing: true });
579
+ map.setLayers({ landmarks: false, bartStations: true, roadLabels: false });
580
+ map.setControls({ zoom: false, reset: true, touch: true });
581
+ ```
582
+
583
+ `setFeatures` accepts the exported `MapFeatures` interface: `motion`,
584
+ `markerEntrance`, `clustering`, `selectedMarkerRing`, `northArrow`, and `scaleBar`.
585
+ Every feature accepts `false` to disable; the first four also accept `true` for
586
+ built-in settings or an options object. Omitted patch keys retain their settings;
587
+ `undefined` resets that key to the default. Options objects **replace** the previous
588
+ object for that feature; they do not deep-merge. `getFeatures()` returns a detached,
589
+ normalized snapshot. Changing motion cancels the current transition; changing
590
+ entrance settings cancels active entrances and applies to future new marker IDs.
591
+
592
+ `setLayers` accepts `InteractiveLayers`; `undefined` restores the mode default.
593
+ Geometry, associated labels, built-in legend entries, and attribution update
594
+ together. Roads and road labels remain independent switches. Toggling does not
595
+ remove geography already imported into a client bundle.
596
+
597
+ `setControls` accepts the same keys as `controls`. Hiding zoom no longer hides
598
+ Reset, Labels, or Touch. Hiding the touch button disengages touch navigation so
599
+ page scrolling stays recoverable. Unknown switch names and invalid values throw
600
+ before modifying state. After `destroy()`, setters are no-ops and disposal can be
601
+ repeated safely. Getters return the last state; screen projection still requires
602
+ a mounted, visible canvas.
603
+
604
+ For configuration precedence, malformed inputs, callback reentrancy, and lifecycle
605
+ ownership, see the [maintainer API audit](docs/api-audit.md).
package/SOURCES.md CHANGED
@@ -1,5 +1,39 @@
1
1
  # Geometry and provenance
2
2
 
3
+ ## Candidate votes in San Francisco, 2016–2026
4
+
5
+ Downloaded September 26, 2026 from the San Francisco Department of Elections'
6
+ final district statements of vote. The files under `data/candidates/`
7
+ contain 56 candidate contests across six elections, with candidate vote counts
8
+ for San Francisco citywide and supervisorial districts. Original workbooks are
9
+ ingestion inputs and are not distributed.
10
+
11
+ | Election | Final official workbook | Supervisorial map vintage |
12
+ | --- | --- | --- |
13
+ | November 8, 2016 | https://www.sfelections.org/results/20161108/data/20161206/20161206_sov.xlsx | 2012 |
14
+ | November 6, 2018 | https://www.sfelections.org/results/20181106/data/20181127/20181127_sov.xlsx | 2012 |
15
+ | November 3, 2020 | https://www.sfelections.org/results/20201103/data/20201201/20201201_dsov.xlsx | 2012 |
16
+ | November 8, 2022 | https://www.sfelections.org/results/20221108/data/20221201/dsov.xlsx | 2022 |
17
+ | November 5, 2024 | https://www.sfelections.org/results/20241105/data/20241203/dsov.xlsx | 2022 |
18
+ | June 2, 2026 | https://sfelections.org/results/20260602/data/20260625/dsov.xlsx | 2022 |
19
+
20
+ The [Department's election data catalog](https://sfelections.org/tools/election_data/datasets.php)
21
+ indexes related election materials. `scripts/import-candidate-results.py` records
22
+ each input SHA-256 in the corresponding JSON and reconciles candidate totals,
23
+ under/overvotes, and all eleven supervisorial areas against the official SF
24
+ total. The 2016 and 2018 workbooks contain explicit district and neighborhood
25
+ summary sections. Later workbooks supply dedicated district statements. A
26
+ missing supervisorial row in a congressional or legislative contest means that
27
+ district was outside the eligible contest area; it is not a zero vote share.
28
+ The 2024 Treasurer workbook reports one additional cumulative vote and two
29
+ undervotes outside the supervisorial rows, retained in `unassigned` and the
30
+ citywide total.
31
+ All pages use candidate / total valid contest votes, not a two-party share.
32
+ The archive excludes ranked-choice local races, primary contests before 2026,
33
+ and other election dates; it does not claim to represent all SF elections.
34
+ Historical neighborhood labels in election workbooks are not substituted for
35
+ the independent neighborhood boundary datasets in this repository.
36
+
3
37
  ## California propositions in San Francisco
4
38
 
5
39
  Downloaded September 26, 2026 from the San Francisco Department of Elections' [final November 5, 2024 results](https://sfelections.org/results/20241105w/detail.html):
@@ -4,6 +4,10 @@ The JSON files here are the geographic source of truth, not generated copies of
4
4
 
5
5
  The renderer, `neighborhoods` convenience export, and `getNeighborhood` default to the **92 SFAR realtor areas**. Import `neighborhoods-realtor.json` directly for that same geometry. The filename `neighborhoods.json` continues to identify the separate SF Find collection.
6
6
 
7
+ Certified candidate vote snapshots are separate, optional JSON imports under
8
+ [`candidates/`](candidates/README.md). They do not change the geographic
9
+ renderer or its default bundle.
10
+
7
11
  ## Available files
8
12
 
9
13
  | File | Contents |
@@ -95,7 +99,7 @@ Lookup is exact after case/punctuation normalization, returns `undefined` for an
95
99
  ## Draw a neighborhood with the existing projection
96
100
 
97
101
  ```js
98
- import { createSFMap } from '@kahwee/sf-map-svg';
102
+ import { createSFMap } from '@kahwee/sf-map-svg/legacy';
99
103
  import { getNeighborhood } from '@kahwee/sf-map-svg/data';
100
104
  import { geometryPath } from '@kahwee/sf-map-svg/geometry';
101
105
 
@@ -0,0 +1 @@
1
+ {"electionDate":"2016-11-08","electionName":"2016-11-08 San Francisco candidate results","districtYear":2012,"certifiedDate":"2016-12-06","downloadedDate":"2026-09-26","source":"https://www.sfelections.org/results/20161108/data/20161206/20161206_sov.xlsx","sourceSha256":"b5a0ca135b54fe36c1cf22684d8f3ec237e7cf37ab3a304682c066238e7c6675","geography":"Votes cast in San Francisco, reported by supervisorial district. Congressional and state legislative contests include only eligible portions of each district.","method":"Candidate vote share divides a candidate count by all valid votes in that contest and area. Write-ins are included when reported. District eligibility uses registered-voter counts in the official workbook compared with the election’s citywide presidential or gubernatorial contest. Missing supervisorial rows indicate no eligible voters, not zero support. County cumulative votes, when present, are recorded separately as unassigned.","contests":[{"id":"president-and-vice-president","title":"President and Vice President","sourceSheet":"100 - President and Vice Presid","candidates":[{"name":"JILL STEIN / AJAMU BARAKA","party":null,"sourceColumn":8},{"name":"HILLARY CLINTON / TIM KAINE","party":null,"sourceColumn":9},{"name":"GLORIA ESTELA LA RIVA / DENNIS J. BANK","party":null,"sourceColumn":10},{"name":"DONALD J. TRUMP / MICHAEL R. PENCE","party":null,"sourceColumn":11},{"name":"GARY JOHNSON / BILL WELD","party":null,"sourceColumn":12},{"name":"WRITE-IN","party":null,"sourceColumn":13},{"name":"WRITE-IN LAURENCE KOTLIKOFF / EDWARD L","party":null,"sourceColumn":14},{"name":"WRITE-IN MIKE MATUREN / JUAN MUNOZ","party":null,"sourceColumn":15},{"name":"WRITE-IN EVAN MCMULLIN / NATHAN JOHNSO","party":null,"sourceColumn":16},{"name":"WRITE-IN BERNARD 'BERNIE' SANDERS / TU","party":null,"sourceColumn":17},{"name":"WRITE-IN JERRY WHITE / NILES NIEMUTH","party":null,"sourceColumn":18}],"citywide":{"votes":[9904,345084,1799,37688,8883,2749,13,22,373,2021,5],"totalVotes":408541,"registeredVoters":513573,"undervotes":5218,"overvotes":769,"sourceRow":1227},"districts":[{"district":1,"eligible":true,"votes":[968,28937,162,4166,775,232,0,5,29,213,2],"totalVotes":35489,"registeredVoters":43818,"undervotes":507,"overvotes":37,"sourceRow":1216,"partiallyEligible":false},{"district":2,"eligible":true,"votes":[591,36678,72,4597,1759,552,2,1,88,139,0],"totalVotes":44479,"registeredVoters":51388,"undervotes":648,"overvotes":28,"sourceRow":1217,"partiallyEligible":false},{"district":3,"eligible":true,"votes":[746,26892,129,3121,886,235,1,2,36,149,0],"totalVotes":32197,"registeredVoters":41720,"undervotes":423,"overvotes":62,"sourceRow":1218,"partiallyEligible":false},{"district":4,"eligible":true,"votes":[929,26567,164,5202,786,223,0,0,39,224,0],"totalVotes":34134,"registeredVoters":43829,"undervotes":705,"overvotes":38,"sourceRow":1219,"partiallyEligible":false},{"district":5,"eligible":true,"votes":[1214,41514,166,2123,861,227,1,6,31,244,1],"totalVotes":46388,"registeredVoters":56261,"undervotes":354,"overvotes":72,"sourceRow":1220,"partiallyEligible":false},{"district":6,"eligible":true,"votes":[778,26665,156,2531,781,241,3,0,26,190,1],"totalVotes":31372,"registeredVoters":44187,"undervotes":386,"overvotes":108,"sourceRow":1221,"partiallyEligible":false},{"district":7,"eligible":true,"votes":[929,31845,136,5540,965,310,0,0,56,190,0],"totalVotes":39971,"registeredVoters":48429,"undervotes":646,"overvotes":42,"sourceRow":1222,"partiallyEligible":false},{"district":8,"eligible":true,"votes":[1155,47382,158,2163,747,241,4,3,34,202,1],"totalVotes":52090,"registeredVoters":59392,"undervotes":371,"overvotes":40,"sourceRow":1223,"partiallyEligible":false},{"district":9,"eligible":true,"votes":[1252,30909,232,2167,443,156,1,3,9,192,0],"totalVotes":35364,"registeredVoters":44492,"undervotes":335,"overvotes":94,"sourceRow":1224,"partiallyEligible":false},{"district":10,"eligible":true,"votes":[650,24861,205,2455,420,143,0,0,14,139,0],"totalVotes":28887,"registeredVoters":41815,"undervotes":381,"overvotes":129,"sourceRow":1225,"partiallyEligible":false},{"district":11,"eligible":true,"votes":[692,22834,219,3623,460,189,1,2,11,139,0],"totalVotes":28170,"registeredVoters":38242,"undervotes":462,"overvotes":119,"sourceRow":1226,"partiallyEligible":false}],"unassigned":{"totalVotes":0,"undervotes":0,"overvotes":0,"votes":[0,0,0,0,0,0,0,0,0,0,0]}},{"id":"u-s-senator","title":"U.S. Senator","sourceSheet":"105 - U.S. Senator","candidates":[{"name":"LORETTA L. SANCHEZ","party":null,"sourceColumn":8},{"name":"KAMALA D. HARRIS","party":null,"sourceColumn":9}],"citywide":{"votes":[80985,286723],"totalVotes":367708,"registeredVoters":513573,"undervotes":46255,"overvotes":565,"sourceRow":1227},"districts":[{"district":1,"eligible":true,"votes":[7093,24604],"totalVotes":31697,"registeredVoters":43818,"undervotes":4308,"overvotes":28,"sourceRow":1216,"partiallyEligible":false},{"district":2,"eligible":true,"votes":[8179,30807],"totalVotes":38986,"registeredVoters":51388,"undervotes":6122,"overvotes":47,"sourceRow":1217,"partiallyEligible":false},{"district":3,"eligible":true,"votes":[7197,20949],"totalVotes":28146,"registeredVoters":41720,"undervotes":4507,"overvotes":29,"sourceRow":1218,"partiallyEligible":false},{"district":4,"eligible":true,"votes":[7741,22024],"totalVotes":29765,"registeredVoters":43829,"undervotes":5077,"overvotes":35,"sourceRow":1219,"partiallyEligible":false},{"district":5,"eligible":true,"votes":[7003,35352],"totalVotes":42355,"registeredVoters":56261,"undervotes":4401,"overvotes":58,"sourceRow":1220,"partiallyEligible":false},{"district":6,"eligible":true,"votes":[6858,21073],"totalVotes":27931,"registeredVoters":44187,"undervotes":3846,"overvotes":89,"sourceRow":1221,"partiallyEligible":false},{"district":7,"eligible":true,"votes":[8745,27244],"totalVotes":35989,"registeredVoters":48429,"undervotes":4634,"overvotes":36,"sourceRow":1222,"partiallyEligible":false},{"district":8,"eligible":true,"votes":[7306,41201],"totalVotes":48507,"registeredVoters":59392,"undervotes":3954,"overvotes":40,"sourceRow":1223,"partiallyEligible":false},{"district":9,"eligible":true,"votes":[7095,25571],"totalVotes":32666,"registeredVoters":44492,"undervotes":3056,"overvotes":71,"sourceRow":1224,"partiallyEligible":false},{"district":10,"eligible":true,"votes":[6253,20030],"totalVotes":26283,"registeredVoters":41815,"undervotes":3049,"overvotes":65,"sourceRow":1225,"partiallyEligible":false},{"district":11,"eligible":true,"votes":[7515,17868],"totalVotes":25383,"registeredVoters":38242,"undervotes":3301,"overvotes":67,"sourceRow":1226,"partiallyEligible":false}],"unassigned":{"totalVotes":0,"undervotes":0,"overvotes":0,"votes":[0,0]}},{"id":"u-s-representative-district-12","title":"U.S. Representative, District 12","sourceSheet":"110 - U.S. Representative, Dist","candidates":[{"name":"NANCY PELOSI","party":null,"sourceColumn":8},{"name":"PRESTON PICUS","party":null,"sourceColumn":9}],"citywide":{"votes":[274035,64810],"totalVotes":338845,"registeredVoters":455043,"undervotes":29471,"overvotes":130,"sourceRow":1074},"districts":[{"district":1,"eligible":true,"votes":[25689,7260],"totalVotes":32949,"registeredVoters":43818,"undervotes":3076,"overvotes":8,"sourceRow":1063,"partiallyEligible":false},{"district":2,"eligible":true,"votes":[32027,9144],"totalVotes":41171,"registeredVoters":51388,"undervotes":3974,"overvotes":10,"sourceRow":1064,"partiallyEligible":false},{"district":3,"eligible":true,"votes":[23553,5987],"totalVotes":29540,"registeredVoters":41720,"undervotes":3131,"overvotes":11,"sourceRow":1065,"partiallyEligible":false},{"district":4,"eligible":true,"votes":[23510,7391],"totalVotes":30901,"registeredVoters":43327,"undervotes":3551,"overvotes":10,"sourceRow":1066,"partiallyEligible":true},{"district":5,"eligible":true,"votes":[35866,7689],"totalVotes":43555,"registeredVoters":56261,"undervotes":3246,"overvotes":13,"sourceRow":1067,"partiallyEligible":false},{"district":6,"eligible":true,"votes":[23616,5568],"totalVotes":29184,"registeredVoters":44187,"undervotes":2665,"overvotes":17,"sourceRow":1068,"partiallyEligible":false},{"district":7,"eligible":true,"votes":[11933,2894],"totalVotes":14827,"registeredVoters":18762,"undervotes":1282,"overvotes":5,"sourceRow":1069,"partiallyEligible":true},{"district":8,"eligible":true,"votes":[42239,7028],"totalVotes":49267,"registeredVoters":58958,"undervotes":2878,"overvotes":17,"sourceRow":1070,"partiallyEligible":true},{"district":9,"eligible":true,"votes":[27102,6156],"totalVotes":33258,"registeredVoters":44492,"undervotes":2517,"overvotes":18,"sourceRow":1071,"partiallyEligible":false},{"district":10,"eligible":true,"votes":[22494,4236],"totalVotes":26730,"registeredVoters":41546,"undervotes":2455,"overvotes":15,"sourceRow":1072,"partiallyEligible":true},{"district":11,"eligible":true,"votes":[6006,1457],"totalVotes":7463,"registeredVoters":10584,"undervotes":696,"overvotes":6,"sourceRow":1073,"partiallyEligible":true}],"unassigned":{"totalVotes":0,"undervotes":0,"overvotes":0,"votes":[0,0]}},{"id":"u-s-representative-district-14","title":"U.S. Representative, District 14","sourceSheet":"120 - U.S. Representative, Dist","candidates":[{"name":"JACKIE SPEIER","party":null,"sourceColumn":8},{"name":"ANGEL CARDENAS","party":null,"sourceColumn":9}],"citywide":{"votes":[34294,6702],"totalVotes":40996,"registeredVoters":58530,"undervotes":5074,"overvotes":12,"sourceRow":171},"districts":[{"district":1,"eligible":false,"votes":null,"totalVotes":null,"undervotes":null,"overvotes":null,"registeredVoters":null,"sourceRow":null,"partiallyEligible":false},{"district":2,"eligible":false,"votes":null,"totalVotes":null,"undervotes":null,"overvotes":null,"registeredVoters":null,"sourceRow":null,"partiallyEligible":false},{"district":3,"eligible":false,"votes":null,"totalVotes":null,"undervotes":null,"overvotes":null,"registeredVoters":null,"sourceRow":null,"partiallyEligible":false},{"district":4,"eligible":true,"votes":[309,59],"totalVotes":368,"registeredVoters":502,"undervotes":47,"overvotes":0,"sourceRow":166,"partiallyEligible":true},{"district":5,"eligible":false,"votes":null,"totalVotes":null,"undervotes":null,"overvotes":null,"registeredVoters":null,"sourceRow":null,"partiallyEligible":false},{"district":6,"eligible":false,"votes":null,"totalVotes":null,"undervotes":null,"overvotes":null,"registeredVoters":null,"sourceRow":null,"partiallyEligible":false},{"district":7,"eligible":true,"votes":[18411,3765],"totalVotes":22176,"registeredVoters":29667,"undervotes":2366,"overvotes":3,"sourceRow":167,"partiallyEligible":true},{"district":8,"eligible":true,"votes":[249,56],"totalVotes":305,"registeredVoters":434,"undervotes":34,"overvotes":0,"sourceRow":168,"partiallyEligible":true},{"district":9,"eligible":false,"votes":null,"totalVotes":null,"undervotes":null,"overvotes":null,"registeredVoters":null,"sourceRow":null,"partiallyEligible":false},{"district":10,"eligible":true,"votes":[153,29],"totalVotes":182,"registeredVoters":269,"undervotes":15,"overvotes":0,"sourceRow":169,"partiallyEligible":true},{"district":11,"eligible":true,"votes":[15172,2793],"totalVotes":17965,"registeredVoters":27658,"undervotes":2612,"overvotes":9,"sourceRow":170,"partiallyEligible":true}],"unassigned":{"totalVotes":0,"undervotes":0,"overvotes":0,"votes":[0,0]}}]}
@@ -0,0 +1 @@
1
+ {"electionDate":"2018-11-06","electionName":"2018-11-06 San Francisco candidate results","districtYear":2012,"certifiedDate":"2018-11-27","downloadedDate":"2026-09-26","source":"https://www.sfelections.org/results/20181106/data/20181127/20181127_sov.xlsx","sourceSha256":"71ad5e4a8abbec7a2f27c341744207953289a6b52899afaa615aa008580683c7","geography":"Votes cast in San Francisco, reported by supervisorial district. Congressional and state legislative contests include only eligible portions of each district.","method":"Candidate vote share divides a candidate count by all valid votes in that contest and area. Write-ins are included when reported. District eligibility uses registered-voter counts in the official workbook compared with the election’s citywide presidential or gubernatorial contest. Missing supervisorial rows indicate no eligible voters, not zero support. County cumulative votes, when present, are recorded separately as unassigned.","contests":[{"id":"governor","title":"Governor","sourceSheet":"100 - Governor","candidates":[{"name":"JOHN H. COX","party":null,"sourceColumn":8},{"name":"GAVIN NEWSOM","party":null,"sourceColumn":9}],"citywide":{"votes":[49181,312181],"totalVotes":361362,"registeredVoters":500516,"undervotes":11236,"overvotes":144,"sourceRow":1241},"districts":[{"district":1,"eligible":true,"votes":[5025,26726],"totalVotes":31751,"registeredVoters":42767,"undervotes":981,"overvotes":4,"sourceRow":1230,"partiallyEligible":false},{"district":2,"eligible":true,"votes":[6904,32646],"totalVotes":39550,"registeredVoters":50394,"undervotes":839,"overvotes":12,"sourceRow":1231,"partiallyEligible":false},{"district":3,"eligible":true,"votes":[4092,23498],"totalVotes":27590,"registeredVoters":39799,"undervotes":967,"overvotes":12,"sourceRow":1232,"partiallyEligible":false},{"district":4,"eligible":true,"votes":[6349,24281],"totalVotes":30630,"registeredVoters":42953,"undervotes":1047,"overvotes":14,"sourceRow":1233,"partiallyEligible":false},{"district":5,"eligible":true,"votes":[3007,37718],"totalVotes":40725,"registeredVoters":53744,"undervotes":1283,"overvotes":23,"sourceRow":1234,"partiallyEligible":false},{"district":6,"eligible":true,"votes":[3450,24766],"totalVotes":28216,"registeredVoters":43321,"undervotes":922,"overvotes":18,"sourceRow":1235,"partiallyEligible":false},{"district":7,"eligible":true,"votes":[6753,28819],"totalVotes":35572,"registeredVoters":47076,"undervotes":972,"overvotes":13,"sourceRow":1236,"partiallyEligible":false},{"district":8,"eligible":true,"votes":[3219,44050],"totalVotes":47269,"registeredVoters":58122,"undervotes":1320,"overvotes":12,"sourceRow":1237,"partiallyEligible":false},{"district":9,"eligible":true,"votes":[2782,27696],"totalVotes":30478,"registeredVoters":42988,"undervotes":1268,"overvotes":13,"sourceRow":1238,"partiallyEligible":false},{"district":10,"eligible":true,"votes":[3239,22362],"totalVotes":25601,"registeredVoters":41862,"undervotes":843,"overvotes":6,"sourceRow":1239,"partiallyEligible":false},{"district":11,"eligible":true,"votes":[4361,19619],"totalVotes":23980,"registeredVoters":37490,"undervotes":794,"overvotes":17,"sourceRow":1240,"partiallyEligible":false}],"unassigned":{"totalVotes":0,"undervotes":0,"overvotes":0,"votes":[0,0]}},{"id":"lieutenant-governor","title":"Lieutenant Governor","sourceSheet":"105 - Lieutenant Governor","candidates":[{"name":"ED HERNANDEZ","party":null,"sourceColumn":8},{"name":"ELENI KOUNALAKIS","party":null,"sourceColumn":9}],"citywide":{"votes":[153733,172741],"totalVotes":326474,"registeredVoters":500516,"undervotes":45920,"overvotes":348,"sourceRow":1241},"districts":[{"district":1,"eligible":true,"votes":[13262,15090],"totalVotes":28352,"registeredVoters":42767,"undervotes":4370,"overvotes":14,"sourceRow":1230,"partiallyEligible":false},{"district":2,"eligible":true,"votes":[13996,21086],"totalVotes":35082,"registeredVoters":50394,"undervotes":5294,"overvotes":25,"sourceRow":1231,"partiallyEligible":false},{"district":3,"eligible":true,"votes":[10990,13723],"totalVotes":24713,"registeredVoters":39799,"undervotes":3838,"overvotes":18,"sourceRow":1232,"partiallyEligible":false},{"district":4,"eligible":true,"votes":[12747,13802],"totalVotes":26549,"registeredVoters":42953,"undervotes":5122,"overvotes":20,"sourceRow":1233,"partiallyEligible":false},{"district":5,"eligible":true,"votes":[18310,19466],"totalVotes":37776,"registeredVoters":53744,"undervotes":4215,"overvotes":40,"sourceRow":1234,"partiallyEligible":false},{"district":6,"eligible":true,"votes":[11502,14057],"totalVotes":25559,"registeredVoters":43321,"undervotes":3546,"overvotes":51,"sourceRow":1235,"partiallyEligible":false},{"district":7,"eligible":true,"votes":[14598,16980],"totalVotes":31578,"registeredVoters":47076,"undervotes":4956,"overvotes":23,"sourceRow":1236,"partiallyEligible":false},{"district":8,"eligible":true,"votes":[19949,24009],"totalVotes":43958,"registeredVoters":58122,"undervotes":4610,"overvotes":33,"sourceRow":1237,"partiallyEligible":false},{"district":9,"eligible":true,"votes":[15784,12490],"totalVotes":28274,"registeredVoters":42988,"undervotes":3439,"overvotes":46,"sourceRow":1238,"partiallyEligible":false},{"district":10,"eligible":true,"votes":[10983,12176],"totalVotes":23159,"registeredVoters":41862,"undervotes":3257,"overvotes":34,"sourceRow":1239,"partiallyEligible":false},{"district":11,"eligible":true,"votes":[11612,9862],"totalVotes":21474,"registeredVoters":37490,"undervotes":3273,"overvotes":44,"sourceRow":1240,"partiallyEligible":false}],"unassigned":{"totalVotes":0,"undervotes":0,"overvotes":0,"votes":[0,0]}},{"id":"secretary-of-state","title":"Secretary of State","sourceSheet":"110 - Secretary of State","candidates":[{"name":"ALEX PADILLA","party":null,"sourceColumn":8},{"name":"MARK P. MEUSER","party":null,"sourceColumn":9}],"citywide":{"votes":[309367,43538],"totalVotes":352905,"registeredVoters":500516,"undervotes":19728,"overvotes":109,"sourceRow":1241},"districts":[{"district":1,"eligible":true,"votes":[26564,4372],"totalVotes":30936,"registeredVoters":42767,"undervotes":1791,"overvotes":9,"sourceRow":1230,"partiallyEligible":false},{"district":2,"eligible":true,"votes":[31815,6500],"totalVotes":38315,"registeredVoters":50394,"undervotes":2079,"overvotes":7,"sourceRow":1231,"partiallyEligible":false},{"district":3,"eligible":true,"votes":[23002,3686],"totalVotes":26688,"registeredVoters":39799,"undervotes":1873,"overvotes":8,"sourceRow":1232,"partiallyEligible":false},{"district":4,"eligible":true,"votes":[23910,5360],"totalVotes":29270,"registeredVoters":42953,"undervotes":2415,"overvotes":6,"sourceRow":1233,"partiallyEligible":false},{"district":5,"eligible":true,"votes":[37553,2794],"totalVotes":40347,"registeredVoters":53744,"undervotes":1666,"overvotes":18,"sourceRow":1234,"partiallyEligible":false},{"district":6,"eligible":true,"votes":[24105,3289],"totalVotes":27394,"registeredVoters":43321,"undervotes":1747,"overvotes":15,"sourceRow":1235,"partiallyEligible":false},{"district":7,"eligible":true,"votes":[28853,5804],"totalVotes":34657,"registeredVoters":47076,"undervotes":1894,"overvotes":6,"sourceRow":1236,"partiallyEligible":false},{"district":8,"eligible":true,"votes":[44056,2919],"totalVotes":46975,"registeredVoters":58122,"undervotes":1621,"overvotes":5,"sourceRow":1237,"partiallyEligible":false},{"district":9,"eligible":true,"votes":[27996,2344],"totalVotes":30340,"registeredVoters":42988,"undervotes":1405,"overvotes":14,"sourceRow":1238,"partiallyEligible":false},{"district":10,"eligible":true,"votes":[21939,2819],"totalVotes":24758,"registeredVoters":41862,"undervotes":1685,"overvotes":7,"sourceRow":1239,"partiallyEligible":false},{"district":11,"eligible":true,"votes":[19574,3651],"totalVotes":23225,"registeredVoters":37490,"undervotes":1552,"overvotes":14,"sourceRow":1240,"partiallyEligible":false}],"unassigned":{"totalVotes":0,"undervotes":0,"overvotes":0,"votes":[0,0]}},{"id":"state-controller","title":"State Controller","sourceSheet":"115 - State Controller","candidates":[{"name":"KONSTANTINOS RODITIS","party":null,"sourceColumn":8},{"name":"BETTY T. YEE","party":null,"sourceColumn":9}],"citywide":{"votes":[43021,311559],"totalVotes":354580,"registeredVoters":500516,"undervotes":18077,"overvotes":85,"sourceRow":1241},"districts":[{"district":1,"eligible":true,"votes":[4025,27231],"totalVotes":31256,"registeredVoters":42767,"undervotes":1473,"overvotes":7,"sourceRow":1230,"partiallyEligible":false},{"district":2,"eligible":true,"votes":[7221,30980],"totalVotes":38201,"registeredVoters":50394,"undervotes":2195,"overvotes":5,"sourceRow":1231,"partiallyEligible":false},{"district":3,"eligible":true,"votes":[3794,23074],"totalVotes":26868,"registeredVoters":39799,"undervotes":1699,"overvotes":2,"sourceRow":1232,"partiallyEligible":false},{"district":4,"eligible":true,"votes":[4674,25192],"totalVotes":29866,"registeredVoters":42953,"undervotes":1817,"overvotes":8,"sourceRow":1233,"partiallyEligible":false},{"district":5,"eligible":true,"votes":[2979,37280],"totalVotes":40259,"registeredVoters":53744,"undervotes":1764,"overvotes":8,"sourceRow":1234,"partiallyEligible":false},{"district":6,"eligible":true,"votes":[3436,23952],"totalVotes":27388,"registeredVoters":43321,"undervotes":1753,"overvotes":15,"sourceRow":1235,"partiallyEligible":false},{"district":7,"eligible":true,"votes":[5535,29496],"totalVotes":35031,"registeredVoters":47076,"undervotes":1519,"overvotes":7,"sourceRow":1236,"partiallyEligible":false},{"district":8,"eligible":true,"votes":[3155,43720],"totalVotes":46875,"registeredVoters":58122,"undervotes":1723,"overvotes":3,"sourceRow":1237,"partiallyEligible":false},{"district":9,"eligible":true,"votes":[2350,28006],"totalVotes":30356,"registeredVoters":42988,"undervotes":1389,"overvotes":14,"sourceRow":1238,"partiallyEligible":false},{"district":10,"eligible":true,"votes":[2661,22343],"totalVotes":25004,"registeredVoters":41862,"undervotes":1440,"overvotes":6,"sourceRow":1239,"partiallyEligible":false},{"district":11,"eligible":true,"votes":[3191,20285],"totalVotes":23476,"registeredVoters":37490,"undervotes":1305,"overvotes":10,"sourceRow":1240,"partiallyEligible":false}],"unassigned":{"totalVotes":0,"undervotes":0,"overvotes":0,"votes":[0,0]}},{"id":"state-treasurer","title":"State Treasurer","sourceSheet":"120 - State Treasurer","candidates":[{"name":"GREG CONLON","party":null,"sourceColumn":8},{"name":"FIONA MA","party":null,"sourceColumn":9}],"citywide":{"votes":[45699,306998],"totalVotes":352697,"registeredVoters":500516,"undervotes":19947,"overvotes":98,"sourceRow":1241},"districts":[{"district":1,"eligible":true,"votes":[4232,26867],"totalVotes":31099,"registeredVoters":42767,"undervotes":1630,"overvotes":7,"sourceRow":1230,"partiallyEligible":false},{"district":2,"eligible":true,"votes":[7508,30503],"totalVotes":38011,"registeredVoters":50394,"undervotes":2381,"overvotes":9,"sourceRow":1231,"partiallyEligible":false},{"district":3,"eligible":true,"votes":[4005,22761],"totalVotes":26766,"registeredVoters":39799,"undervotes":1797,"overvotes":6,"sourceRow":1232,"partiallyEligible":false},{"district":4,"eligible":true,"votes":[5065,24872],"totalVotes":29937,"registeredVoters":42953,"undervotes":1746,"overvotes":8,"sourceRow":1233,"partiallyEligible":false},{"district":5,"eligible":true,"votes":[3266,36588],"totalVotes":39854,"registeredVoters":53744,"undervotes":2161,"overvotes":16,"sourceRow":1234,"partiallyEligible":false},{"district":6,"eligible":true,"votes":[3602,23758],"totalVotes":27360,"registeredVoters":43321,"undervotes":1786,"overvotes":10,"sourceRow":1235,"partiallyEligible":false},{"district":7,"eligible":true,"votes":[5739,29209],"totalVotes":34948,"registeredVoters":47076,"undervotes":1603,"overvotes":6,"sourceRow":1236,"partiallyEligible":false},{"district":8,"eligible":true,"votes":[3434,42903],"totalVotes":46337,"registeredVoters":58122,"undervotes":2258,"overvotes":6,"sourceRow":1237,"partiallyEligible":false},{"district":9,"eligible":true,"votes":[2593,27391],"totalVotes":29984,"registeredVoters":42988,"undervotes":1766,"overvotes":9,"sourceRow":1238,"partiallyEligible":false},{"district":10,"eligible":true,"votes":[2861,22082],"totalVotes":24943,"registeredVoters":41862,"undervotes":1499,"overvotes":8,"sourceRow":1239,"partiallyEligible":false},{"district":11,"eligible":true,"votes":[3394,20064],"totalVotes":23458,"registeredVoters":37490,"undervotes":1320,"overvotes":13,"sourceRow":1240,"partiallyEligible":false}],"unassigned":{"totalVotes":0,"undervotes":0,"overvotes":0,"votes":[0,0]}},{"id":"attorney-general","title":"Attorney General","sourceSheet":"125 - Attorney General","candidates":[{"name":"STEVEN C BAILEY","party":null,"sourceColumn":8},{"name":"XAVIER BECERRA","party":null,"sourceColumn":9}],"citywide":{"votes":[47070,305501],"totalVotes":352571,"registeredVoters":500516,"undervotes":20077,"overvotes":94,"sourceRow":1241},"districts":[{"district":1,"eligible":true,"votes":[4675,26222],"totalVotes":30897,"registeredVoters":42767,"undervotes":1834,"overvotes":5,"sourceRow":1230,"partiallyEligible":false},{"district":2,"eligible":true,"votes":[6910,31355],"totalVotes":38265,"registeredVoters":50394,"undervotes":2131,"overvotes":5,"sourceRow":1231,"partiallyEligible":false},{"district":3,"eligible":true,"votes":[3930,22744],"totalVotes":26674,"registeredVoters":39799,"undervotes":1889,"overvotes":6,"sourceRow":1232,"partiallyEligible":false},{"district":4,"eligible":true,"votes":[5882,23392],"totalVotes":29274,"registeredVoters":42953,"undervotes":2413,"overvotes":4,"sourceRow":1233,"partiallyEligible":false},{"district":5,"eligible":true,"votes":[2984,37321],"totalVotes":40305,"registeredVoters":53744,"undervotes":1711,"overvotes":15,"sourceRow":1234,"partiallyEligible":false},{"district":6,"eligible":true,"votes":[3593,23724],"totalVotes":27317,"registeredVoters":43321,"undervotes":1827,"overvotes":12,"sourceRow":1235,"partiallyEligible":false},{"district":7,"eligible":true,"votes":[6330,28339],"totalVotes":34669,"registeredVoters":47076,"undervotes":1884,"overvotes":4,"sourceRow":1236,"partiallyEligible":false},{"district":8,"eligible":true,"votes":[3124,43784],"totalVotes":46908,"registeredVoters":58122,"undervotes":1688,"overvotes":5,"sourceRow":1237,"partiallyEligible":false},{"district":9,"eligible":true,"votes":[2523,27819],"totalVotes":30342,"registeredVoters":42988,"undervotes":1407,"overvotes":10,"sourceRow":1238,"partiallyEligible":false},{"district":10,"eligible":true,"votes":[3145,21604],"totalVotes":24749,"registeredVoters":41862,"undervotes":1688,"overvotes":13,"sourceRow":1239,"partiallyEligible":false},{"district":11,"eligible":true,"votes":[3974,19197],"totalVotes":23171,"registeredVoters":37490,"undervotes":1605,"overvotes":15,"sourceRow":1240,"partiallyEligible":false}],"unassigned":{"totalVotes":0,"undervotes":0,"overvotes":0,"votes":[0,0]}},{"id":"insurance-commissioner","title":"Insurance Commissioner","sourceSheet":"130 - Insurance Commissioner","candidates":[{"name":"RICARDO LARA","party":null,"sourceColumn":8},{"name":"STEVE POIZNER","party":null,"sourceColumn":9}],"citywide":{"votes":[235299,102364],"totalVotes":337663,"registeredVoters":500516,"undervotes":34922,"overvotes":157,"sourceRow":1241},"districts":[{"district":1,"eligible":true,"votes":[20160,9461],"totalVotes":29621,"registeredVoters":42767,"undervotes":3106,"overvotes":9,"sourceRow":1230,"partiallyEligible":false},{"district":2,"eligible":true,"votes":[21150,14962],"totalVotes":36112,"registeredVoters":50394,"undervotes":4276,"overvotes":13,"sourceRow":1231,"partiallyEligible":false},{"district":3,"eligible":true,"votes":[16819,8537],"totalVotes":25356,"registeredVoters":39799,"undervotes":3200,"overvotes":13,"sourceRow":1232,"partiallyEligible":false},{"district":4,"eligible":true,"votes":[18401,9477],"totalVotes":27878,"registeredVoters":42953,"undervotes":3805,"overvotes":8,"sourceRow":1233,"partiallyEligible":false},{"district":5,"eligible":true,"votes":[28844,9828],"totalVotes":38672,"registeredVoters":53744,"undervotes":3334,"overvotes":25,"sourceRow":1234,"partiallyEligible":false},{"district":6,"eligible":true,"votes":[18301,7924],"totalVotes":26225,"registeredVoters":43321,"undervotes":2914,"overvotes":17,"sourceRow":1235,"partiallyEligible":false},{"district":7,"eligible":true,"votes":[20987,12212],"totalVotes":33199,"registeredVoters":47076,"undervotes":3352,"overvotes":6,"sourceRow":1236,"partiallyEligible":false},{"district":8,"eligible":true,"votes":[33598,11535],"totalVotes":45133,"registeredVoters":58122,"undervotes":3446,"overvotes":22,"sourceRow":1237,"partiallyEligible":false},{"district":9,"eligible":true,"votes":[23122,6169],"totalVotes":29291,"registeredVoters":42988,"undervotes":2453,"overvotes":15,"sourceRow":1238,"partiallyEligible":false},{"district":10,"eligible":true,"votes":[17514,6305],"totalVotes":23819,"registeredVoters":41862,"undervotes":2617,"overvotes":14,"sourceRow":1239,"partiallyEligible":false},{"district":11,"eligible":true,"votes":[16403,5954],"totalVotes":22357,"registeredVoters":37490,"undervotes":2419,"overvotes":15,"sourceRow":1240,"partiallyEligible":false}],"unassigned":{"totalVotes":0,"undervotes":0,"overvotes":0,"votes":[0,0]}},{"id":"board-of-equalization-district-2","title":"Board of Equalization, District 2","sourceSheet":"135 - Board of Equalization, Di","candidates":[{"name":"MALIA COHEN","party":null,"sourceColumn":8},{"name":"MARK BURNS","party":null,"sourceColumn":9}],"citywide":{"votes":[292973,47753],"totalVotes":340726,"registeredVoters":500516,"undervotes":31941,"overvotes":75,"sourceRow":1241},"districts":[{"district":1,"eligible":true,"votes":[25236,4637],"totalVotes":29873,"registeredVoters":42767,"undervotes":2863,"overvotes":0,"sourceRow":1230,"partiallyEligible":false},{"district":2,"eligible":true,"votes":[29554,7094],"totalVotes":36648,"registeredVoters":50394,"undervotes":3747,"overvotes":6,"sourceRow":1231,"partiallyEligible":false},{"district":3,"eligible":true,"votes":[21660,3986],"totalVotes":25646,"registeredVoters":39799,"undervotes":2920,"overvotes":3,"sourceRow":1232,"partiallyEligible":false},{"district":4,"eligible":true,"votes":[22758,5699],"totalVotes":28457,"registeredVoters":42953,"undervotes":3229,"overvotes":5,"sourceRow":1233,"partiallyEligible":false},{"district":5,"eligible":true,"votes":[35683,3101],"totalVotes":38784,"registeredVoters":53744,"undervotes":3233,"overvotes":14,"sourceRow":1234,"partiallyEligible":false},{"district":6,"eligible":true,"votes":[22903,3558],"totalVotes":26461,"registeredVoters":43321,"undervotes":2683,"overvotes":12,"sourceRow":1235,"partiallyEligible":false},{"district":7,"eligible":true,"votes":[27307,6316],"totalVotes":33623,"registeredVoters":47076,"undervotes":2926,"overvotes":8,"sourceRow":1236,"partiallyEligible":false},{"district":8,"eligible":true,"votes":[41597,3378],"totalVotes":44975,"registeredVoters":58122,"undervotes":3622,"overvotes":4,"sourceRow":1237,"partiallyEligible":false},{"district":9,"eligible":true,"votes":[26597,2713],"totalVotes":29310,"registeredVoters":42988,"undervotes":2441,"overvotes":8,"sourceRow":1238,"partiallyEligible":false},{"district":10,"eligible":true,"votes":[20826,3387],"totalVotes":24213,"registeredVoters":41862,"undervotes":2234,"overvotes":3,"sourceRow":1239,"partiallyEligible":false},{"district":11,"eligible":true,"votes":[18852,3884],"totalVotes":22736,"registeredVoters":37490,"undervotes":2043,"overvotes":12,"sourceRow":1240,"partiallyEligible":false}],"unassigned":{"totalVotes":0,"undervotes":0,"overvotes":0,"votes":[0,0]}},{"id":"u-s-senator","title":"U.S. Senator","sourceSheet":"140 - U.S. Senator","candidates":[{"name":"DIANNE FEINSTEIN","party":null,"sourceColumn":8},{"name":"KEVIN DE LEON","party":null,"sourceColumn":9}],"citywide":{"votes":[226167,125954],"totalVotes":352121,"registeredVoters":500516,"undervotes":20247,"overvotes":374,"sourceRow":1241},"districts":[{"district":1,"eligible":true,"votes":[19414,11396],"totalVotes":30810,"registeredVoters":42767,"undervotes":1907,"overvotes":19,"sourceRow":1230,"partiallyEligible":false},{"district":2,"eligible":true,"votes":[26668,11447],"totalVotes":38115,"registeredVoters":50394,"undervotes":2260,"overvotes":26,"sourceRow":1231,"partiallyEligible":false},{"district":3,"eligible":true,"votes":[17800,8998],"totalVotes":26798,"registeredVoters":39799,"undervotes":1746,"overvotes":25,"sourceRow":1232,"partiallyEligible":false},{"district":4,"eligible":true,"votes":[19072,10235],"totalVotes":29307,"registeredVoters":42953,"undervotes":2367,"overvotes":17,"sourceRow":1233,"partiallyEligible":false},{"district":5,"eligible":true,"votes":[24793,15594],"totalVotes":40387,"registeredVoters":53744,"undervotes":1601,"overvotes":43,"sourceRow":1234,"partiallyEligible":false},{"district":6,"eligible":true,"votes":[17449,9939],"totalVotes":27388,"registeredVoters":43321,"undervotes":1711,"overvotes":57,"sourceRow":1235,"partiallyEligible":false},{"district":7,"eligible":true,"votes":[22946,11251],"totalVotes":34197,"registeredVoters":47076,"undervotes":2333,"overvotes":27,"sourceRow":1236,"partiallyEligible":false},{"district":8,"eligible":true,"votes":[29815,17173],"totalVotes":46988,"registeredVoters":58122,"undervotes":1589,"overvotes":24,"sourceRow":1237,"partiallyEligible":false},{"district":9,"eligible":true,"votes":[16901,13412],"totalVotes":30313,"registeredVoters":42988,"undervotes":1398,"overvotes":48,"sourceRow":1238,"partiallyEligible":false},{"district":10,"eligible":true,"votes":[16561,8257],"totalVotes":24818,"registeredVoters":41862,"undervotes":1602,"overvotes":30,"sourceRow":1239,"partiallyEligible":false},{"district":11,"eligible":true,"votes":[14748,8252],"totalVotes":23000,"registeredVoters":37490,"undervotes":1733,"overvotes":58,"sourceRow":1240,"partiallyEligible":false}],"unassigned":{"totalVotes":0,"undervotes":0,"overvotes":0,"votes":[0,0]}},{"id":"u-s-representative-district-12","title":"U.S. Representative, District 12","sourceSheet":"145 - U.S. Representative, Dist","candidates":[{"name":"LISA REMMER","party":null,"sourceColumn":8},{"name":"NANCY PELOSI","party":null,"sourceColumn":9}],"citywide":{"votes":[41780,275292],"totalVotes":317072,"registeredVoters":443570,"undervotes":15225,"overvotes":107,"sourceRow":1088},"districts":[{"district":1,"eligible":true,"votes":[4939,26328],"totalVotes":31267,"registeredVoters":42767,"undervotes":1463,"overvotes":6,"sourceRow":1077,"partiallyEligible":false},{"district":2,"eligible":true,"votes":[7354,31793],"totalVotes":39147,"registeredVoters":50394,"undervotes":1244,"overvotes":10,"sourceRow":1078,"partiallyEligible":false},{"district":3,"eligible":true,"votes":[4192,22833],"totalVotes":27025,"registeredVoters":39799,"undervotes":1534,"overvotes":10,"sourceRow":1079,"partiallyEligible":false},{"district":4,"eligible":true,"votes":[5717,23866],"totalVotes":29583,"registeredVoters":42453,"undervotes":1719,"overvotes":10,"sourceRow":1080,"partiallyEligible":true},{"district":5,"eligible":true,"votes":[3318,36993],"totalVotes":40311,"registeredVoters":53744,"undervotes":1707,"overvotes":13,"sourceRow":1081,"partiallyEligible":false},{"district":6,"eligible":true,"votes":[3625,23906],"totalVotes":27531,"registeredVoters":43321,"undervotes":1611,"overvotes":14,"sourceRow":1082,"partiallyEligible":false},{"district":7,"eligible":true,"votes":[2212,12123],"totalVotes":14335,"registeredVoters":18498,"undervotes":581,"overvotes":4,"sourceRow":1083,"partiallyEligible":true},{"district":8,"eligible":true,"votes":[3488,43028],"totalVotes":46516,"registeredVoters":57714,"undervotes":1786,"overvotes":14,"sourceRow":1084,"partiallyEligible":true},{"district":9,"eligible":true,"votes":[2767,27194],"totalVotes":29961,"registeredVoters":42988,"undervotes":1787,"overvotes":11,"sourceRow":1085,"partiallyEligible":false},{"district":10,"eligible":true,"votes":[3096,21796],"totalVotes":24892,"registeredVoters":41589,"undervotes":1403,"overvotes":9,"sourceRow":1086,"partiallyEligible":true},{"district":11,"eligible":true,"votes":[1072,5432],"totalVotes":6504,"registeredVoters":10303,"undervotes":390,"overvotes":6,"sourceRow":1087,"partiallyEligible":true}],"unassigned":{"totalVotes":0,"undervotes":0,"overvotes":0,"votes":[0,0]}},{"id":"u-s-representative-district-14","title":"U.S. Representative, District 14","sourceSheet":"155 - U.S. Representative, Dist","candidates":[{"name":"CRISTINA OSMEÑA","party":null,"sourceColumn":8},{"name":"JACKIE SPEIER","party":null,"sourceColumn":9}],"citywide":{"votes":[6487,31632],"totalVotes":38119,"registeredVoters":56946,"undervotes":2211,"overvotes":8,"sourceRow":171},"districts":[{"district":1,"eligible":false,"votes":null,"totalVotes":null,"undervotes":null,"overvotes":null,"registeredVoters":null,"sourceRow":null,"partiallyEligible":false},{"district":2,"eligible":false,"votes":null,"totalVotes":null,"undervotes":null,"overvotes":null,"registeredVoters":null,"sourceRow":null,"partiallyEligible":false},{"district":3,"eligible":false,"votes":null,"totalVotes":null,"undervotes":null,"overvotes":null,"registeredVoters":null,"sourceRow":null,"partiallyEligible":false},{"district":4,"eligible":true,"votes":[76,282],"totalVotes":358,"registeredVoters":500,"undervotes":21,"overvotes":0,"sourceRow":166,"partiallyEligible":true},{"district":5,"eligible":false,"votes":null,"totalVotes":null,"undervotes":null,"overvotes":null,"registeredVoters":null,"sourceRow":null,"partiallyEligible":false},{"district":6,"eligible":false,"votes":null,"totalVotes":null,"undervotes":null,"overvotes":null,"registeredVoters":null,"sourceRow":null,"partiallyEligible":false},{"district":7,"eligible":true,"votes":[3612,17071],"totalVotes":20683,"registeredVoters":28578,"undervotes":949,"overvotes":5,"sourceRow":167,"partiallyEligible":true},{"district":8,"eligible":true,"votes":[38,236],"totalVotes":274,"registeredVoters":408,"undervotes":11,"overvotes":0,"sourceRow":168,"partiallyEligible":true},{"district":9,"eligible":false,"votes":null,"totalVotes":null,"undervotes":null,"overvotes":null,"registeredVoters":null,"sourceRow":null,"partiallyEligible":false},{"district":10,"eligible":true,"votes":[18,120],"totalVotes":138,"registeredVoters":273,"undervotes":8,"overvotes":0,"sourceRow":169,"partiallyEligible":true},{"district":11,"eligible":true,"votes":[2743,13923],"totalVotes":16666,"registeredVoters":27187,"undervotes":1222,"overvotes":3,"sourceRow":170,"partiallyEligible":true}],"unassigned":{"totalVotes":0,"undervotes":0,"overvotes":0,"votes":[0,0]}}]}
@@ -0,0 +1 @@
1
+ {"electionDate":"2020-11-03","electionName":"2020-11-03 San Francisco candidate results","districtYear":2012,"certifiedDate":"2020-12-01","downloadedDate":"2026-09-26","source":"https://www.sfelections.org/results/20201103/data/20201201/20201201_dsov.xlsx","sourceSha256":"539db60fee6ec633e132b1aa64665a1e1f5ddbc3e5fc56d474bedbcc972d5131","geography":"Votes cast in San Francisco, reported by supervisorial district. Congressional and state legislative contests include only eligible portions of each district.","method":"Candidate vote share divides a candidate count by all valid votes in that contest and area. Write-ins are included when reported. District eligibility uses registered-voter counts in the official workbook compared with the election’s citywide presidential or gubernatorial contest. Missing supervisorial rows indicate no eligible voters, not zero support. County cumulative votes, when present, are recorded separately as unassigned.","contests":[{"id":"president-and-vice-president","title":"PRESIDENT AND VICE PRESIDENT","sourceSheet":"Sheet2","candidates":[{"name":"JOSEPH R. BIDEN AND KAMALA D. HARRIS","party":null,"sourceColumn":7},{"name":"DONALD J. TRUMP AND MICHAEL R. PENCE","party":null,"sourceColumn":9},{"name":"GLORIA LA RIVA AND SUNIL FREEMAN","party":null,"sourceColumn":11},{"name":"ROQUE \"ROCKY\" DE LA FUENTE GUERRA AND KANYE OMARI WEST","party":null,"sourceColumn":14},{"name":"HOWIE HAWKINS AND ANGELA NICOLE WALKER","party":null,"sourceColumn":16},{"name":"JO JORGENSEN AND JEREMY \"SPIKE\" COHEN","party":null,"sourceColumn":19},{"name":"Write-in","party":null,"sourceColumn":21}],"citywide":{"votes":[378156,56417,1543,1262,2781,3299,95],"totalVotes":443553,"registeredVoters":521099,"undervotes":5658,"overvotes":609,"sourceRow":14},"districts":[{"district":1,"eligible":true,"votes":[32986,5832,124,104,259,326,5],"totalVotes":39636,"registeredVoters":45097,"undervotes":541,"overvotes":43,"sourceRow":51,"partiallyEligible":false},{"district":2,"eligible":true,"votes":[39507,5306,49,115,155,536,6],"totalVotes":45674,"registeredVoters":51074,"undervotes":706,"overvotes":24,"sourceRow":55,"partiallyEligible":false},{"district":3,"eligible":true,"votes":[27656,4384,101,108,200,312,8],"totalVotes":32769,"registeredVoters":39734,"undervotes":437,"overvotes":47,"sourceRow":59,"partiallyEligible":false},{"district":4,"eligible":true,"votes":[31691,7876,175,136,257,282,13],"totalVotes":40430,"registeredVoters":46857,"undervotes":742,"overvotes":29,"sourceRow":63,"partiallyEligible":false},{"district":5,"eligible":true,"votes":[42210,3242,138,102,349,287,13],"totalVotes":46341,"registeredVoters":53491,"undervotes":480,"overvotes":40,"sourceRow":67,"partiallyEligible":false},{"district":6,"eligible":true,"votes":[29918,4485,170,132,254,319,3],"totalVotes":35281,"registeredVoters":45820,"undervotes":437,"overvotes":94,"sourceRow":71,"partiallyEligible":false},{"district":7,"eligible":true,"votes":[35632,6893,108,117,208,277,15],"totalVotes":43250,"registeredVoters":48874,"undervotes":584,"overvotes":48,"sourceRow":75,"partiallyEligible":false},{"district":8,"eligible":true,"votes":[49284,2793,137,85,313,313,12],"totalVotes":52937,"registeredVoters":58419,"undervotes":390,"overvotes":33,"sourceRow":79,"partiallyEligible":false},{"district":9,"eligible":true,"votes":[32697,3745,179,87,289,197,9],"totalVotes":37203,"registeredVoters":44282,"undervotes":418,"overvotes":62,"sourceRow":83,"partiallyEligible":false},{"district":10,"eligible":true,"votes":[29934,5145,182,142,284,249,4],"totalVotes":35940,"registeredVoters":46114,"undervotes":456,"overvotes":77,"sourceRow":87,"partiallyEligible":false},{"district":11,"eligible":true,"votes":[26641,6716,180,134,213,201,7],"totalVotes":34092,"registeredVoters":41337,"undervotes":467,"overvotes":112,"sourceRow":91,"partiallyEligible":false}],"unassigned":{"totalVotes":0,"undervotes":0,"overvotes":0,"votes":[0,0,0,0,0,0,0]}},{"id":"us-house-of-rep-district-12","title":"US House of Rep District 12","sourceSheet":"Sheet3","candidates":[{"name":"SHAHID BUTTAR","party":"DEM","sourceColumn":7},{"name":"NANCY PELOSI","party":"DEM","sourceColumn":9}],"citywide":{"votes":[81174,281776],"totalVotes":362950,"registeredVoters":460309,"undervotes":34157,"overvotes":409,"sourceRow":14},"districts":[{"district":1,"eligible":true,"votes":[8994,27186],"totalVotes":36180,"registeredVoters":45097,"undervotes":4003,"overvotes":37,"sourceRow":43,"partiallyEligible":false},{"district":2,"eligible":true,"votes":[8579,33982],"totalVotes":42561,"registeredVoters":51074,"undervotes":3827,"overvotes":16,"sourceRow":47,"partiallyEligible":false},{"district":3,"eligible":true,"votes":[7175,22847],"totalVotes":30022,"registeredVoters":39734,"undervotes":3205,"overvotes":26,"sourceRow":51,"partiallyEligible":false},{"district":4,"eligible":true,"votes":[10075,25871],"totalVotes":35946,"registeredVoters":46299,"undervotes":4715,"overvotes":40,"sourceRow":55,"partiallyEligible":true},{"district":5,"eligible":true,"votes":[8808,34545],"totalVotes":43353,"registeredVoters":53491,"undervotes":3458,"overvotes":50,"sourceRow":59,"partiallyEligible":false},{"district":6,"eligible":true,"votes":[7684,24927],"totalVotes":32611,"registeredVoters":45820,"undervotes":3123,"overvotes":78,"sourceRow":63,"partiallyEligible":false},{"district":7,"eligible":true,"votes":[3379,12868],"totalVotes":16247,"registeredVoters":19388,"undervotes":1538,"overvotes":6,"sourceRow":67,"partiallyEligible":true},{"district":8,"eligible":true,"votes":[8162,41702],"totalVotes":49864,"registeredVoters":57979,"undervotes":3068,"overvotes":33,"sourceRow":71,"partiallyEligible":true},{"district":9,"eligible":true,"votes":[8376,26141],"totalVotes":34517,"registeredVoters":44282,"undervotes":3122,"overvotes":44,"sourceRow":75,"partiallyEligible":false},{"district":10,"eligible":true,"votes":[7583,25438],"totalVotes":33021,"registeredVoters":45804,"undervotes":3147,"overvotes":68,"sourceRow":79,"partiallyEligible":true},{"district":11,"eligible":true,"votes":[2359,6269],"totalVotes":8628,"registeredVoters":11341,"undervotes":951,"overvotes":11,"sourceRow":83,"partiallyEligible":true}],"unassigned":{"totalVotes":0,"undervotes":0,"overvotes":0,"votes":[0,0]}},{"id":"us-house-of-rep-district-14","title":"US House of Rep District 14","sourceSheet":"Sheet5","candidates":[{"name":"RAN S. PETEL","party":"REP","sourceColumn":7},{"name":"JACKIE SPEIER","party":"DEM","sourceColumn":9}],"citywide":{"votes":[9179,39670],"totalVotes":48849,"registeredVoters":60790,"undervotes":3434,"overvotes":21,"sourceRow":14},"districts":[{"district":1,"eligible":false,"votes":null,"totalVotes":null,"undervotes":null,"overvotes":null,"registeredVoters":null,"sourceRow":null,"partiallyEligible":false},{"district":2,"eligible":false,"votes":null,"totalVotes":null,"undervotes":null,"overvotes":null,"registeredVoters":null,"sourceRow":null,"partiallyEligible":false},{"district":3,"eligible":false,"votes":null,"totalVotes":null,"undervotes":null,"overvotes":null,"registeredVoters":null,"sourceRow":null,"partiallyEligible":false},{"district":4,"eligible":true,"votes":[105,355],"totalVotes":460,"registeredVoters":558,"undervotes":40,"overvotes":0,"sourceRow":43,"partiallyEligible":true},{"district":5,"eligible":false,"votes":null,"totalVotes":null,"undervotes":null,"overvotes":null,"registeredVoters":null,"sourceRow":null,"partiallyEligible":false},{"district":6,"eligible":false,"votes":null,"totalVotes":null,"undervotes":null,"overvotes":null,"registeredVoters":null,"sourceRow":null,"partiallyEligible":false},{"district":7,"eligible":true,"votes":[4560,20062],"totalVotes":24622,"registeredVoters":29486,"undervotes":1462,"overvotes":7,"sourceRow":47,"partiallyEligible":true},{"district":8,"eligible":true,"votes":[69,299],"totalVotes":368,"registeredVoters":440,"undervotes":27,"overvotes":0,"sourceRow":51,"partiallyEligible":true},{"district":9,"eligible":false,"votes":null,"totalVotes":null,"undervotes":null,"overvotes":null,"registeredVoters":null,"sourceRow":null,"partiallyEligible":false},{"district":10,"eligible":true,"votes":[35,185],"totalVotes":220,"registeredVoters":310,"undervotes":17,"overvotes":0,"sourceRow":55,"partiallyEligible":true},{"district":11,"eligible":true,"votes":[4410,18769],"totalVotes":23179,"registeredVoters":29996,"undervotes":1888,"overvotes":14,"sourceRow":59,"partiallyEligible":true}],"unassigned":{"totalVotes":0,"undervotes":0,"overvotes":0,"votes":[0,0]}},{"id":"state-senator-district-11","title":"State Senator District 11","sourceSheet":"Sheet6","candidates":[{"name":"SCOTT WIENER","party":"DEM","sourceColumn":7},{"name":"JACKIE FIELDER","party":"DEM","sourceColumn":9}],"citywide":{"votes":[229160,168348],"totalVotes":397508,"registeredVoters":521099,"undervotes":51645,"overvotes":667,"sourceRow":14},"districts":[{"district":1,"eligible":true,"votes":[18785,16307],"totalVotes":35092,"registeredVoters":45097,"undervotes":5087,"overvotes":41,"sourceRow":51,"partiallyEligible":false},{"district":2,"eligible":true,"votes":[27274,13471],"totalVotes":40745,"registeredVoters":51074,"undervotes":5629,"overvotes":30,"sourceRow":55,"partiallyEligible":false},{"district":3,"eligible":true,"votes":[17647,11462],"totalVotes":29109,"registeredVoters":39734,"undervotes":4106,"overvotes":38,"sourceRow":59,"partiallyEligible":false},{"district":4,"eligible":true,"votes":[18309,16430],"totalVotes":34739,"registeredVoters":46857,"undervotes":6416,"overvotes":46,"sourceRow":63,"partiallyEligible":false},{"district":5,"eligible":true,"votes":[24116,18611],"totalVotes":42727,"registeredVoters":53491,"undervotes":4067,"overvotes":67,"sourceRow":67,"partiallyEligible":false},{"district":6,"eligible":true,"votes":[19238,12112],"totalVotes":31350,"registeredVoters":45820,"undervotes":4372,"overvotes":90,"sourceRow":71,"partiallyEligible":false},{"district":7,"eligible":true,"votes":[22651,15511],"totalVotes":38162,"registeredVoters":48874,"undervotes":5667,"overvotes":53,"sourceRow":75,"partiallyEligible":false},{"district":8,"eligible":true,"votes":[30724,19180],"totalVotes":49904,"registeredVoters":58419,"undervotes":3413,"overvotes":43,"sourceRow":79,"partiallyEligible":false},{"district":9,"eligible":true,"votes":[16371,17528],"totalVotes":33899,"registeredVoters":44282,"undervotes":3721,"overvotes":63,"sourceRow":83,"partiallyEligible":false},{"district":10,"eligible":true,"votes":[18314,13616],"totalVotes":31930,"registeredVoters":46114,"undervotes":4460,"overvotes":83,"sourceRow":87,"partiallyEligible":false},{"district":11,"eligible":true,"votes":[15731,14120],"totalVotes":29851,"registeredVoters":41337,"undervotes":4707,"overvotes":113,"sourceRow":91,"partiallyEligible":false}],"unassigned":{"totalVotes":0,"undervotes":0,"overvotes":0,"votes":[0,0]}},{"id":"state-assembly-member-district-17","title":"STATE ASSEMBLY MEMBER District 17","sourceSheet":"Sheet7","candidates":[{"name":"STARCHILD","party":"LIB","sourceColumn":7},{"name":"DAVID CHIU","party":"DEM","sourceColumn":9}],"citywide":{"votes":[23834,190731],"totalVotes":214565,"registeredVoters":300579,"undervotes":40112,"overvotes":119,"sourceRow":14},"districts":[{"district":1,"eligible":true,"votes":[10,126],"totalVotes":136,"registeredVoters":175,"undervotes":23,"overvotes":1,"sourceRow":47,"partiallyEligible":true},{"district":2,"eligible":true,"votes":[566,4768],"totalVotes":5334,"registeredVoters":6971,"undervotes":934,"overvotes":0,"sourceRow":51,"partiallyEligible":true},{"district":3,"eligible":true,"votes":[3479,24497],"totalVotes":27976,"registeredVoters":39734,"undervotes":5261,"overvotes":16,"sourceRow":55,"partiallyEligible":false},{"district":4,"eligible":false,"votes":null,"totalVotes":null,"undervotes":null,"overvotes":null,"registeredVoters":null,"sourceRow":null,"partiallyEligible":false},{"district":5,"eligible":true,"votes":[2606,26847],"totalVotes":29453,"registeredVoters":40227,"undervotes":5706,"overvotes":14,"sourceRow":59,"partiallyEligible":true},{"district":6,"eligible":true,"votes":[3900,26210],"totalVotes":30110,"registeredVoters":45820,"undervotes":5669,"overvotes":33,"sourceRow":63,"partiallyEligible":false},{"district":7,"eligible":true,"votes":[633,5871],"totalVotes":6504,"registeredVoters":8246,"undervotes":1150,"overvotes":2,"sourceRow":67,"partiallyEligible":true},{"district":8,"eligible":true,"votes":[3449,42387],"totalVotes":45836,"registeredVoters":57979,"undervotes":7119,"overvotes":10,"sourceRow":71,"partiallyEligible":true},{"district":9,"eligible":true,"votes":[3564,27615],"totalVotes":31179,"registeredVoters":44282,"undervotes":6486,"overvotes":18,"sourceRow":75,"partiallyEligible":false},{"district":10,"eligible":true,"votes":[4266,25903],"totalVotes":30169,"registeredVoters":45804,"undervotes":6047,"overvotes":20,"sourceRow":79,"partiallyEligible":true},{"district":11,"eligible":true,"votes":[1361,6507],"totalVotes":7868,"registeredVoters":11341,"undervotes":1717,"overvotes":5,"sourceRow":83,"partiallyEligible":true}],"unassigned":{"totalVotes":0,"undervotes":0,"overvotes":0,"votes":[0,0]}},{"id":"state-assembly-member-district-19","title":"STATE ASSEMBLY MEMBER District 19","sourceSheet":"Sheet8","candidates":[{"name":"JOHN P. MCDONNELL","party":"REP","sourceColumn":7},{"name":"PHIL TING","party":"DEM","sourceColumn":9}],"citywide":{"votes":[39283,134175],"totalVotes":173458,"registeredVoters":220520,"undervotes":21504,"overvotes":62,"sourceRow":14},"districts":[{"district":1,"eligible":true,"votes":[7162,28336],"totalVotes":35498,"registeredVoters":44922,"undervotes":4548,"overvotes":14,"sourceRow":43,"partiallyEligible":true},{"district":2,"eligible":true,"votes":[9439,26376],"totalVotes":35815,"registeredVoters":44103,"undervotes":4314,"overvotes":7,"sourceRow":47,"partiallyEligible":true},{"district":3,"eligible":false,"votes":null,"totalVotes":null,"undervotes":null,"overvotes":null,"registeredVoters":null,"sourceRow":null,"partiallyEligible":false},{"district":4,"eligible":true,"votes":[8706,27842],"totalVotes":36548,"registeredVoters":46857,"undervotes":4634,"overvotes":19,"sourceRow":51,"partiallyEligible":false},{"district":5,"eligible":true,"votes":[1301,9041],"totalVotes":10342,"registeredVoters":13264,"undervotes":1344,"overvotes":2,"sourceRow":55,"partiallyEligible":true},{"district":6,"eligible":false,"votes":null,"totalVotes":null,"undervotes":null,"overvotes":null,"registeredVoters":null,"sourceRow":null,"partiallyEligible":false},{"district":7,"eligible":true,"votes":[7577,25084],"totalVotes":32661,"registeredVoters":40628,"undervotes":3556,"overvotes":9,"sourceRow":59,"partiallyEligible":true},{"district":8,"eligible":true,"votes":[89,266],"totalVotes":355,"registeredVoters":440,"undervotes":40,"overvotes":0,"sourceRow":63,"partiallyEligible":true},{"district":9,"eligible":false,"votes":null,"totalVotes":null,"undervotes":null,"overvotes":null,"registeredVoters":null,"sourceRow":null,"partiallyEligible":false},{"district":10,"eligible":true,"votes":[37,166],"totalVotes":203,"registeredVoters":310,"undervotes":34,"overvotes":0,"sourceRow":67,"partiallyEligible":true},{"district":11,"eligible":true,"votes":[4972,17064],"totalVotes":22036,"registeredVoters":29996,"undervotes":3034,"overvotes":11,"sourceRow":71,"partiallyEligible":true}],"unassigned":{"totalVotes":0,"undervotes":0,"overvotes":0,"votes":[0,0]}}]}