# Shape Map 남한 행정구역을 SVG 도형으로 그리고 지역별 상태를 색으로 보여주는 지도입니다. 시·도에서 시·군·구로 들어갑니다. > 원본: `03_ASSET_GUIDE/catalog/shape-map.js` · CSS: `03_ASSET_GUIDE/styles/elements/shape-map.css` > kind `component` · category `display` · status `stable` · specSource `measured` > 함께 쓰는 CSS: `chip.css` (지도/표 전환 칩) --- ## 1. 구성 **Canvas** 안에 **Region**(시·도) 또는 **Area**(시·군·구)가 놓이고, **Legend** 가 색이 무엇을 뜻하는지 표시합니다. **Tooltip** 은 한 지역의 내역을 보여주고 다음 레벨로 들어가는 진입점이며, **Table** 은 같은 상태를 행으로 갖습니다. ``` ┌──────────────────────────────────┐ │ 전국 · 시·도 / ‹ 전국으로 [지도][표] │ ① ⑦ │ □ 정상 ■ 주의 ■ 장애 │ ⑤ │ ┌──────────────────────────────┐ │ │ │ ▓▓ 서울 ┌────────────┐ │ │ ② ③ ④ │ │ ░░░░░ │● 서울 │ │ │ ⑥ │ │ ░░░░░░░ │ 장애 12 │ │ │ │ │ ░░░░ └────────────┘ │ │ │ └──────────────────────────────┘ │ │ 지역 │ 상태 │ 장애 건수 │ ⑧ └──────────────────────────────────┘ ``` | # | 요소 | 필수 | 클래스 | 설명 | |---|---|---|---|---| | ① | Header | 선택 | `atom-shape-map__header` · `__toggle` | 레벨 이름 또는 Back, 그리고 지도/표 전환 칩([Chip](./chip.md)). | | ② | Canvas | 필수 | `atom-shape-map__canvas` | 도형이 앉는 판입니다. 배경색을 소유합니다. | | ③ | Region | sido 레벨 | `atom-shape-map__region` (``) | 시·도 하나입니다. `` 이고 그 안에 자기 구들의 `` 가 들어 있습니다. 시·도 레벨에서는 이것이 상태와 hover 대상을 가집니다. | | ④ | Area | gu 레벨 | `atom-shape-map__area` (``) | 시·군·구 하나입니다. 구 레벨에서 이것이 상태를 가집니다. | | ⑤ | Legend | 필수 | `atom-shape-map__legend` · `__legend-item` · `__legend-swatch` | 항상 둡니다. 범례가 없으면 흐린 채움 세 가지를 구분할 수 없습니다. | | ⑥ | Tooltip | 선택 | `atom-shape-map__tooltip` · `__tooltip-title` · `__tooltip-row` · `__tooltip-dot` · `__tooltip-label` · `__tooltip-value` | 그 지역의 상태·건수를 보여주고, 누르면 구 레벨로 들어갑니다. | | ⑦ | Back | gu 레벨 | `atom-shape-map__back` | 구 레벨에서 전국으로 돌아옵니다. | | ⑧ | Table | 선택 | `atom-shape-map__table` | 같은 상태를 행으로 보여줍니다. 툴팁 외에도 값을 확인할 수 있게 합니다. | | — | Label | 선택 | `atom-shape-map__label` | 지도 위 지역명 (`showLabels`). | | — | Empty | 감시 대상 없을 때 | `atom-shape-map__empty` | 빈 상태 문구입니다. | > [!NOTE] > **지오메트리는 하나입니다** > 시·도 레벨이라고 17개짜리 병합 도형을 따로 두지 않았습니다. **251개 시·군·구 경로를 > 그대로 쓰고**, 시·도 `` 의 stroke 를 자기 fill 과 같은 색으로 두어 내부 경계를 > 닫습니다. 레벨을 바꿀 때 `d` 속성은 그대로 두고 stroke 만 바뀝니다. 병합 폴리곤을 따로 > 관리하면 두 지오메트리가 어긋날 수 있습니다. --- ## 2. 종류 / 크기 ### 레벨 | data-level | 그리는 것 | 경계 | |---|---|---| | `sido` | 전국 17개 시·도 | 구 경계가 닫혀 시·도가 한 덩어리 | | `gu` | 한 시·도의 시·군·구 | 구마다 자기 경계 | 실제 사용 흐름은 **전국 → 툴팁 → 구별 지도** 입니다. 툴팁에서 다음 레벨로 들어가므로, 툴팁에는 그 지역의 요약과 함께 **들어갈 수 있다는 것**을 표시합니다. ### 상세 형식 같은 지오메트리로 세 가지 형식이 나옵니다. | 형식 | 구 경계 | 쓰는 곳 | |---|---|---| | `sido` (기본) | 없음 | 시·도 단위로만 판단할 때 | | `--detail_district` | 헤어라인 0.6px | 전국 화면에서 구 분할까지 함께 보여줄 때 | | `gu` | 1px, 상태 색 테두리 | 구별 상태를 읽어야 할 때 | 중간 형식은 전국 화면에서 시·도의 구 분할을 함께 보여줘야 할 때 씁니다. ### 배경과 선 배경색과 선 색은 **로컬 커스텀 프로퍼티**입니다. 같은 컴포넌트에 두 값만 바꿔 감시 월과 보고서 페이지를 함께 씁니다. | 프로퍼티 | 기본 | 무엇 | |---|---|---| | `--atom-shape-map-surface` | `--atom-bg-layer` | 도형이 앉는 판 | | `--atom-shape-map-line` | `--atom-border-default` | 경계를 읽어야 하는 자리의 선 색 | | `--atom-shape-map-fill` | `--atom-bg-neutral-weak` | 보고할 것이 없는 지역 | ```html
``` > [!NOTE] > **`--atom-shape-map-*` 는 토큰이 아닙니다** > `tokens.css` 에 없습니다. 이 엘리먼트 안에서만 쓰이는 로컬 커스텀 프로퍼티이고, > [Tree](./tree.md) 의 `--atom-tree-indent` · [Grid](./grid.md) 의 > `--atom-grid-tree-indent` 와 같은 성격입니다. 값은 역할 토큰으로 넘기세요. > 리터럴 색을 쓰면 다크 모드에서 테마가 적용되지 않습니다. ### 상태 상태는 시리즈 색이 아닌 **상태 토큰**으로 칠합니다. 이 지도는 상태의 정상·이상 여부를 표시합니다. | 상태 | 채움 | 테두리 (구 레벨) | 뜻 | |---|---|---|---| | 정상 | `--atom-bg-neutral-weak` | `--atom-shape-map-line` | 보고할 것이 없음 | | 주의 | `--atom-bg-warning-weak` | `--atom-fg-warning` | 임계치 접근 | | 장애 | `--atom-bg-critical-weak` | `--atom-fg-critical` | 장애 발생 | **정상은 초록이 아닌 중립색으로 칠합니다.** 색은 예외 지역에만 써서 주의·장애 지역이 눈에 띄게 합니다. 채움은 **약한 단계**입니다. 시·도는 면적이 크기 때문입니다. [Color](../foundation/color.md) 와 [Chart](./chart.md) 도 같은 규칙으로, 채도 높은 채움은 작은 마크와 강조에만 씁니다. 긴급함은 테두리로 표시합니다. ### 툴팁 툴팁은 **보조 수단이면서 다음 레벨의 진입점**입니다. 툴팁 안에서도 라벨은 텍스트 색이고 상태 색은 점에만 씁니다. ### 표 뷰 표에도 **상태 점이 함께** 들어갑니다. 지도와 표가 같은 정보를 같은 표시로 보여주게 합니다. ### 빈 상태 감시 대상이 없으면 회색 지도를 그리지 않습니다. 전부 정상인 것과 감시 대상이 없는 것은 다른 상태입니다. ### 클래스 계약 `styles/elements/shape-map.css` 헤더와 원본 `registry.json` `css.blocks` 기준입니다. 클래스가 없는 값(—)은 기본 스타일 그대로입니다. | Prop | 값 | 클래스 / 속성 | |---|---|---| | `level` | `sido` · `gu` | `[data-level="sido"]` · `[data-level="gu"]` | | `detail` | `none` · `district` | — · `--detail_district` | | `view` | `map` · `table` | `[data-view="map"]` · `[data-view="table"]` | | `states[code]` | `normal` · `warning` · `critical` | — · `[data-status="warning"]` · `[data-status="critical"]` (region · area · legend-swatch · tooltip-dot) | 상태 선택자: `[data-level]` · `[data-view]` · `[data-status]` · `atom-shape-map__tooltip[data-state="closed"]` · `:hover` · `:focus-visible` 로컬 커스텀 프로퍼티: `--atom-shape-map-surface` · `--atom-shape-map-line` · `--atom-shape-map-fill` · `--atom-shape-map-ink` (내부: 도형의 fill 과 stroke 가 함께 읽는 값) > [!WARNING] > **원본 `registry.json` 의 `detail` 기본값 경고 (`props-default-not-in-css`)** > registry `problems` 는 "detail: Props says the default is `district`" 라고 보고하지만, > `catalog/shape-map.js` Props 표 원문의 기본값은 `"none"` 입니다 (CSS 헤더도 `--detail_district` 만 선언하고 기본값은 없음). > registry `api.props` 가 Type 열의 `\|` 를 열 구분자로 잘라 읽은 결과입니다. --- ## 3. 상태 | 상태 | 표현 | 선택자 | |---|---|---| | 정상 | `--atom-shape-map-fill` 채움 | — | | 주의 | `--atom-bg-warning-weak` 채움 · 구 레벨 `--atom-fg-warning` 테두리 | `[data-status="warning"]` | | 장애 | `--atom-bg-critical-weak` 채움 · 구 레벨 `--atom-fg-critical` 테두리 | `[data-status="critical"]` | | hover | `filter: brightness(0.96)` — 상태 색은 바뀌지 않음 | `:hover` | | focus | `--atom-fg-brand` 2px outline, offset 1px | `:focus-visible` | | 툴팁 열림 / 닫힘 | 닫히면 `display: none` | `atom-shape-map__tooltip[data-state="open"]` · `[data-state="closed"]` | | map 뷰 / table 뷰 | table 이면 Canvas · Legend 숨김, map 이면 Table 숨김 | `[data-view="map"]` · `[data-view="table"]` | | 강제 색상 모드 | 도형에 `CanvasText` 1px 외곽선 | `@media (forced-colors: active)` | | 모션 줄이기 | fill 전환 없음 | `@media (prefers-reduced-motion: reduce)` | --- ## 4. Props | Prop | Type | Default | 필수 | 설명 | |---|---|---|---|---| | `level` | `"sido" \| "gu"` | `"sido"` | | 어느 레벨을 그릴지입니다. | | `regionCode` | `string` | | | `level: "gu"` 일 때 어느 시·도의 구를 그릴지입니다. | | `states` | `Record` | `{}` | ● | 지역 코드별 상태입니다. | | `counts` | `Record` | `{}` | | 툴팁과 표에 쓰는 건수입니다. | | `detail` | `"none" \| "district"` | `"none"` | | 시·도 레벨에서 구 경계를 함께 그릴지입니다. | | `showLabels` | `boolean` | `false` | | 지도 위에 지역명을 얹습니다. | | `view` | `"map" \| "table"` | `"map"` | | 어느 쌍둥이를 보일지입니다. | | `emptyText` | `string` | `"감시 대상이 없습니다"` | | 감시 대상이 없을 때 문구입니다. | | `onRegionSelect` | `(code: string) => void` | | | 지역을 누르거나 `Enter` 했을 때. 드릴다운을 여기서 처리합니다. | | `onBack` | `() => void` | | | 전국으로 돌아갈 때. | ### Parts (compound) | Part | 요소 | 설명 | |---|---|---| | `ShapeMap` | `
` | 데이터 기반 단일 컴포넌트. 위 Props 로 Header · Legend · Canvas(SVG) · Tooltip · Table · Empty 를 모두 그립니다. 하위 파트는 없습니다. | --- ## 5. Component tokens | 대상 | 속성 | Token | Value | |---|---|---|---| | Canvas | background | `--atom-shape-map-surface` → `{color.bg.layer}` | 로컬 프로퍼티 | | Canvas | radius | `{radius.r2}` | 8px | | Canvas | 경계 | `{color.border.subtle}` | `inset 0 0 0 1px` | | Region · Area | 기본 채움 | `--atom-shape-map-fill` → `{color.bg.neutral-weak}` | | | Region (sido) | stroke | 자기 fill | 1.6px — 내부 경계를 닫는 값 | | Area (gu) | stroke | `--atom-shape-map-line` | 1px | | detail_district | stroke | `--atom-shape-map-line` | 0.6px | | 주의 | 채움 · 테두리 | `{color.bg.warning-weak}` · `{color.fg.warning}` | | | 장애 | 채움 · 테두리 | `{color.bg.critical-weak}` · `{color.fg.critical}` | | | Label | fill | `{color.fg.neutral-muted}` | 10px | | Tooltip | 배경 · 글자 | `{color.bg.tooltip}` · `{color.fg.tooltip}` | [Tooltip](./tooltip.md) 과 같은 표면 | | Legend swatch | — | — | 12px · radius 3px · 1px 테두리 | ```json { "shape-map": { "surface": { "$type": "color", "$value": "{color.bg.layer}" }, "line": { "$type": "color", "$value": "{color.border.default}" }, "fill": { "$type": "color", "$value": "{color.bg.neutral-weak}" }, "canvas": { "radius": { "$type": "dimension", "$value": "{radius.r2}" }, "border": { "$type": "color", "$value": "{color.border.subtle}" } }, "stroke-width": { "$type": "dimension", "sido": { "$value": "1.6px" }, "gu": { "$value": "1px" }, "district": { "$value": "0.6px" } }, "warning": { "$type": "color", "fill": { "$value": "{color.bg.warning-weak}" }, "border": { "$value": "{color.fg.warning}" } }, "critical": { "$type": "color", "fill": { "$value": "{color.bg.critical-weak}" }, "border": { "$value": "{color.fg.critical}" } }, "label": { "fill": { "$type": "color", "$value": "{color.fg.neutral-muted}" }, "font-size": { "$type": "dimension", "$value": "10px" } }, "tooltip": { "bg": { "$type": "color", "$value": "{color.bg.tooltip}" }, "fg": { "$type": "color", "$value": "{color.fg.tooltip}" } }, "legend-swatch": { "size": { "$type": "dimension", "$value": "12px" }, "radius": { "$type": "dimension", "$value": "3px" } } } } ``` ### 지오메트리 > [!NOTE] > **출처와 가공** > `tango-oi/pm/tgo-pm-opm-web/.../data/geojson/skorea_geo.json` 의 251개 > 시·군·구 폴리곤(2013 기준)입니다. 메르카토르로 한 번 투영하고 > Douglas–Peucker 로 단순화한 뒤 좌표를 소수 1자리로 끊었습니다. > 전국 지도는 tol 2.4 로 28KB, 서울 25구는 tol 0.3 으로 2.8KB 입니다. > 지도를 갱신할 때는 원본 geojson 을 바꾸고 같은 절차를 다시 돌립니다 — > `d` 속성을 손으로 고치지 마세요. 각 feature 의 `properties` 는 `code` · `city` · `gu` 를 가지므로, `city` 로 묶으면 시·도 레벨이고 `code` 가 상태를 붙일 키입니다. > 구현(`implement/src/components/shape-map-data.ts`)은 원본 카탈로그 데모 마크업의 `d` 값을 스크립트로 옮긴 것입니다. > 전국 17개 시·도(viewBox `0 0 520 551`, path 250개)와 서울 25구(viewBox `0 0 520 433`)만 들어 있습니다 — > 원본에 구 지오메트리가 있는 시·도는 서울뿐입니다. --- ## 6. 접근성 - **역할** — 각 `` 에 `role="img"` + `aria-label` 로 무슨 지도인지 한 문장을 답니다. 개별 도형은 `role="button"` 이고, 이름에 **지역명과 상태를 함께** 넣습니다(`aria-label="서울, 장애"`). 색을 구분하지 못해도 초점을 옮기면 상태를 들을 수 있습니다. - **키보드** — 모든 지역이 `tabindex="0"` 으로 탭 정거장입니다. `Enter` 가 다음 레벨로 들어가고, `Esc` 가 전국으로 돌아옵니다. **드릴다운이 클릭 전용이면 안 됩니다.** - **17개 탭 정거장** — 전국 화면에서 지역만 17번을 지나야 합니다. 표 뷰로 이를 건너뛸 수 있으므로 표는 필수입니다. - **툴팁 외 경로** — 같은 값이 표에 있습니다. 초점으로도 hover 와 같은 툴팁이 열려야 합니다. - **대비** — 상태 채움은 약한 단계라 배경과 3:1 이 안 됩니다. 의도된 값이며, 대신 **테두리(구 레벨) · 범례 · 툴팁 · 표** 로 상태를 함께 전달합니다. 채움만 남기고 나머지를 빼지 마세요. - **강제 색상 모드** — 색이 사라지면 도형에 시스템 외곽선이 들어가 경계가 남고, 상태는 범례와 표가 전달합니다. --- ## 7. 사용 가이드 ### 색만으로 상태를 말하지 않습니다 흐린 노랑과 흐린 빨강은 색각 이상이나 흑백 인쇄에서 구분되지 않습니다. 범례·표·툴팁의 글자로 상태를 함께 전달합니다. - ✅ **Do** 범례를 항상 두고, 툴팁과 표에 상태를 **글자로** 적습니다. - ❌ **Don't** 범례를 빼고 색만 남기지 않습니다. ### 지도는 위치를 보여줄 때 씁니다 지도는 "어디" 를 보여줄 때 씁니다. 건수 비교에는 맞지 않습니다. 시·도는 면적 차이가 커서 **면적이 값처럼 읽히는 착시**가 생깁니다. 경기는 서울보다 열 배 넓지만 장애 건수와는 무관합니다. - ✅ **Do** "어느 지역에 문제가 있나" 에 답하는 자리에 씁니다. - ❌ **Don't** 지역별 건수를 비교하는 용도로 지도만 두지 않습니다. 건수 비교는 [Chart](./chart.md) 의 가로 막대를 씁니다. - ⚠️ **Caution** 대개 지도와 순위 표를 나란히 둡니다. 위치는 지도로, 크기는 표로 보여줍니다. ### 라벨은 읽는 레벨의 것만 전국 화면에 시·도 이름과 구 이름을 함께 얹으면 읽기 어렵습니다. 시·도 레벨에서는 시·도만, 구 레벨에서는 구만 답니다. 좁은 시·도(서울·대전 같은 특별·광역시)는 지도 위 라벨을 생략하고 툴팁과 표에 맡깁니다. ### 관련 컴포넌트 - [Chart](./chart.md) — 지역별 **크기**를 비교해야 하면 가로 막대입니다. 지도는 위치만 답합니다. - [Grid](./grid.md) — 장애 목록 그 자체. 지도 아래에 나란히 두는 짝입니다. - [Tooltip](./tooltip.md) — 지도 툴팁이 쓰는 표면과 같은 것입니다. - [Badge](./badge.md) — 표 안에서 상태를 말할 때. - [Color](../foundation/color.md) — 상태 토큰의 정의와 대비 값. --- ## 8. 예시 ```tsx const [region, setRegion] = useState(null); region ? ( setRegion(null)} /> ) : ( ); ``` ```html
전국 · 시·도
  • 장애

서울

장애 12
지역상태장애 건수
서울 장애 12
``` 구 레벨은 `` 없이 `__area` path 가 직접 상태를 가집니다. ```html
```