@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.
- package/CHANGELOG.md +46 -0
- package/README.md +160 -6
- package/SOURCES.md +34 -0
- package/dist/data/README.md +5 -1
- package/dist/data/candidates/2016-11-08.json +1 -0
- package/dist/data/candidates/2018-11-06.json +1 -0
- package/dist/data/candidates/2020-11-03.json +1 -0
- package/dist/data/candidates/2022-11-08.json +1 -0
- package/dist/data/candidates/2024-11-05.json +1 -0
- package/dist/data/candidates/2026-06-02.json +1 -0
- package/dist/data/candidates/README.md +43 -0
- package/dist/data/candidates/catalog.json +40 -0
- package/dist/data/types.d.ts +4 -1
- package/dist/src/api.d.ts +3 -0
- package/dist/src/api.js +3 -0
- package/dist/src/camera.d.ts +16 -0
- package/dist/src/camera.js +55 -0
- package/dist/src/clusters.d.ts +7 -0
- package/dist/src/clusters.js +33 -0
- package/dist/src/configuration.d.ts +5 -0
- package/dist/src/configuration.js +73 -0
- package/dist/src/controller-types.d.ts +84 -0
- package/dist/src/controller-types.js +1 -0
- package/dist/src/explorer-core.d.ts +1 -1
- package/dist/src/explorer-core.js +570 -145
- package/dist/src/features.d.ts +24 -0
- package/dist/src/features.js +99 -0
- package/dist/src/guide-data.js +3 -2
- package/dist/src/guide-detailed.js +3 -2
- package/dist/src/guide-map.d.ts +4 -0
- package/dist/src/guide-map.js +35 -10
- package/dist/src/guide-shell.d.ts +2 -0
- package/dist/src/guide-shell.js +16 -0
- package/dist/src/guide-static.d.ts +9 -0
- package/dist/src/guide-static.js +26 -0
- package/dist/src/guide.d.ts +2 -1
- package/dist/src/guide.js +1 -1
- package/dist/src/interactive-data.d.ts +1 -1
- package/dist/src/interactive-data.js +2 -0
- package/dist/src/interactive.d.ts +1 -1
- package/dist/src/interactive.js +2 -0
- package/dist/src/layers.js +3 -1
- package/dist/src/map-core.js +3 -8
- package/dist/src/map.d.ts +7 -0
- package/dist/src/map.js +106 -0
- package/dist/src/presets.d.ts +3 -0
- package/dist/src/presets.js +17 -0
- package/dist/src/static.d.ts +10 -0
- package/dist/src/static.js +5 -0
- package/dist/src/types.d.ts +73 -9
- package/dist/src/validation.d.ts +7 -0
- package/dist/src/validation.js +226 -0
- package/dist/src/viewport.js +9 -2
- package/docs/EXAMPLES.md +11 -1
- package/docs/api-audit.md +80 -0
- package/docs/consumer-integration.md +165 -0
- package/docs/guide-bundle-report.md +19 -0
- package/docs/migration-v2.md +238 -0
- 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) |
|
|
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
|
-
|
|
|
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
|
|
65
|
-
|
|
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
|
|
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):
|
package/dist/data/README.md
CHANGED
|
@@ -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]}}]}
|