v5.0.0 · on npm
SF MapSVG

Plate I · An atlas of San Francisco

San Francisco, line by line.

The coast, the neighborhoods, the lines that connect them. Beautiful offline SVG maps, ready for your stories, guides and data.

pnpm add @kahwee/sf-map-svg
San Francisco37°46′ N · 122°25′ W
Plate I. San Francisco in 2022: coast, districts, SFAR neighborhoods, parks, highways and BART, drawn by renderMap(data, { animation: true }). Every line is SVG; nothing loads from a tile server.
11
supervisorial districts
3
district map years
250
neighborhood areas, three definitions
0
runtime dependencies

Chapter II · How the map is made

Six layers, one controller.

Scroll through the same interactive map the library gives you. Each step is a single call on the controller from createMap; the code beside it is exactly what runs.

Loading the map…

  1. I

    Begin with the coast.

    Every map starts as land and water: a precise coastline with the Bay, the Presidio and Treasure Island. Geography is passed in explicitly, so small pages stay small.

    import { createMap } from '@kahwee/sf-map-svg';
    import { fullMapData } from '@kahwee/sf-map-svg/data/full';
    
    const map = createMap(fullMapData, { mode: 'basemap' });
    document.querySelector('#map').append(map.element);
  2. II

    Draw eleven districts.

    Supervisorial districts carry the soft palette the library is known for. They are selectable, keyboard-reachable, and announce themselves to screen readers.

    map.setMode('districts');
  3. III

    Watch the lines move.

    San Francisco redrew its districts in 2002, 2012 and 2022. With districtMorph the outlines travel from one map to the next, then settle into the exact published geometry.

    createMap(fullMapData, { features: { districtMorph: true } });
    map.setDistrictYear(2012);
  4. IV

    Three ways to draw a neighborhood.

    There is no single neighborhood map. SFAR realtor areas are the default; SF Find and the city’s analysis areas are kept as their own definitions. layerTransitions crossfades between them.

    map.setMode('neighborhoods');
    map.setSource('sf-find'); // 'realtor' | 'sf-find' | 'analysis'
  5. V

    Add parks, roads and rails.

    Parks, highways, key roads and BART stations are independent, optional layers. Switch any of them at runtime without losing the camera or selection.

    map.configure({
      layers: { landmarks: true, highways: true, keyRoads: true, bartStations: true },
    });
  6. VI

    Put your data on top.

    Markers keep their identity across updates, enter with a gentle stagger, and the camera eases to a selection. Motion always yields to the reader’s reduced-motion setting.

    map.setMarkers([
      { id: 'ferry', label: 'Ferry Building', position: [-122.3937, 37.7955 ]},
      { id: 'dolores', label: 'Dolores Park', position: [-122.4269, 37.7596 ]},
    ]);
    map.selectMarker('ferry');

Chapter III · Examples

The city votes, district by district.

Three worked examples built on certified results. Each is a complete page you can read, share, and download from — and a pattern you can copy.

Also in the atlasOne spot, three San FranciscosThe layers studioA schematic BART journeyEvery example

Chapter IV · Build with it

Static when you can, interactive when you need.

Render SVG on a server or at build time with no DOM at all, or mount a keyboard-accessible controller in the browser. Both take the same explicit geography.

Static · Node, edge or build step
import { renderMap } from '@kahwee/sf-map-svg/static';
import { staticMapData } from '@kahwee/sf-map-svg/data/static';

const { svg, project } = renderMap(staticMapData, {
  year: 2022,
  layers: { neighborhoodLines: true, bartStations: true },
  animation: true, // draws itself, even inside <img>
});
Interactive · browser
import { createMap } from '@kahwee/sf-map-svg';
import { fullMapData } from '@kahwee/sf-map-svg/data/full';

const map = createMap(fullMapData, {
  mode: 'districts',
  features: { layerTransitions: true, districtMorph: true },
});
document.querySelector('#map').append(map.element);
map.on('districtchange', ({ id }) => console.log(id));