A볼트AstroB볼트BusinessC볼트카페·디저트
md

차트 컴포넌트 사용법

  • meta
  • dev
  • chart

사업 계획 문서에서 쓸 시각화 컴포넌트 모음. React나 차트 라이브러리 없이 순수 .astro + 인라인 SVG로 만들었다(GraphView.astro와 같은 방식) — 색은 전부 var(--bv-*)를 그대로 써서 라이트/다크 테마가 자동으로 따라온다.

색 규칙: 카테고리(어느 항목인지)는 --bv-series-1~8(고정 순서 8색, 색각이상·명도대역·대비 전부 검증 완료 — src/styles/global.css 참고), 증감 같은 상태는 --bv-emerald-300(좋음)/--bv-rose-400(나쁨)을 아이콘+텍스트와 함께 쓰고 색만으로 의미를 전달하지 않는다.

LineChart — 추이/성장 그래프

{ label, value }[] 배열만 넘기면 된다. 단일 시리즈라 범례 없이 제목/문맥으로 식별한다.

1월: 820만원2월: 940만원3월: 1,100만원4월: 1,050만원5월: 1,280만원6월: 1,460만원7월: 1,390만원8월: 1,610만원9월: 1,780만원10월: 1,920만원11월: 2,050만원12월: 2,300만원1월3월5월7월9월11월12월
import LineChart from '.../components/charts/LineChart.astro';

<LineChart data={data} ariaLabel="월별 매출 추이" unit="만원" />

BarChart — 항목 비교 (경쟁사 등)

이름이 곧 식별자인 카테고리 비교라, 막대마다 고정 순서 색을 하나씩 배정하고 끝에 값을 직접 라벨링한다(범례 없이도 읽힘).

베소 프랑세베소 프랑세: 4,200만원4,200만원와플대학와플대학: 3,100만원3,100만원벨지안와플하우스벨지안와플하우스: 2,600만원2,600만원리에주웨이브리에주웨이브: 2,050만원2,050만원수퍼와플수퍼와플: 1,700만원1,700만원골든와플골든와플: 1,400만원1,400만원
import BarChart from '.../components/charts/BarChart.astro';

<BarChart data={data} ariaLabel="경쟁사 월 매출 비교" unit="만원" />

DonutChart — 부분-전체 구조 (비용/수익 등)

세그먼트 2개 이상이라 범례가 항상 따라온다(라벨+값+퍼센트) — 도넛 위 색만으로는 구분을 강제하지 않는다.

임대료: 350만원 (35%)재료비: 280만원 (28%)인건비: 220만원 (22%)공과금: 60만원 (6%)마케팅: 50만원 (5%)기타: 40만원 (4%)1,000만원
  • 임대료350만원 · 35%
  • 재료비280만원 · 28%
  • 인건비220만원 · 22%
  • 공과금60만원 · 6%
  • 마케팅50만원 · 5%
  • 기타40만원 · 4%
import DonutChart from '.../components/charts/DonutChart.astro';

<DonutChart data={data} ariaLabel="월 비용 구조" unit="만원" />

KpiCard — 숫자 강조

차트가 필요 없을 만큼 단순한 “숫자 하나”에 쓴다. 증감은 화살표 아이콘 + 텍스트로 같이 보여줘서 색약이어도 좋다/나쁘다가 전달된다.

이번 달 매출

2,300만원

+12.4%전월 대비

객단가

8,500원

-3.1%전월 대비

누적 방문객

18,400명

import KpiCard from '.../components/charts/KpiCard.astro';

<KpiCard label="이번 달 매출" value={2300} unit="만원" delta={12.4} deltaLabel="전월 대비" />

Timeline — 마일스톤/로드맵

완료 여부는 색이 아니라 “채워진 점 vs 빈 점”(모양)으로 인코딩한다 — 흑백 인쇄나 색각이상에서도 구분된다.

  1. 2026-01시장 조사 및 경쟁사 분석 완료완료
  2. 2026-03입지 후보 3곳 선정완료
  3. 2026-05사업자 등록 및 임대차 계약예정
  4. 2026-07인테리어 및 장비 세팅예정
  5. 2026-09정식 오픈예정
import Timeline from '.../components/charts/Timeline.astro';

<Timeline items={[{ date: '2026-01', label: '...', done: true }]} ariaLabel="..." />

Sparkline — 표/카드용 인라인 미니 추이

축·격자 없이 선 하나 + 마지막 점만 찍는 극소 버전. 표 셀이나 KpiCard 옆에 자연스럽게 얹으려고 만들었다. values: number[]만 넘기면 된다(라벨은 툴팁용, 선택).

최근 매출 추이 1월 820만원 → 12월 2,300만원

import Sparkline from '.../components/charts/Sparkline.astro';

<Sparkline values={[820, 940, 1100, ...]} unit="만원" ariaLabel="최근 매출 추이" />

GroupedBarChart — 항목 × 지표 비교

BarChart는 “항목”별 색이지만, 이건 “지표”(맛/가격/접근성 등)별로 색을 고정하고 항목마다 그 지표들을 나란히 놓는다 — 시리즈가 2개 이상이라 범례가 항상 따라온다.

베소 프랑세 · 맛: 8점베소 프랑세 · 가격경쟁력: 5점베소 프랑세 · 접근성: 9점베소 프랑세와플대학 · 맛: 7점와플대학 · 가격경쟁력: 7점와플대학 · 접근성: 6점와플대학수퍼와플 · 맛: 6점수퍼와플 · 가격경쟁력: 8점수퍼와플 · 접근성: 7점수퍼와플
  • 맛
  • 가격경쟁력
  • 접근성
import GroupedBarChart from '.../components/charts/GroupedBarChart.astro';

<GroupedBarChart
	categories={['경쟁사 A', '경쟁사 B']}
	series={[{ label: '맛', values: [8, 7] }, { label: '가격', values: [5, 8] }]}
	ariaLabel="경쟁사 항목별 점수 비교"
/>

ProgressGauge — 목표 달성률

DonutChart와 같은 원 스트로크 기법이지만 세그먼트가 하나뿐이다. 항목 정체성이 아니라 “크기(달성률)“를 나타내므로 --bv-series-* 대신 브랜드 액센트(violet) 하나만 쓴다.

이번 달 매출 목표: 1,780만원 / 2,000만원 (89%)1,780만원
이번 달 매출 목표1,780만원 / 2,000만원
TAM 대비 SAM 비율: 89% / 100% (89%)89%
TAM 대비 SAM 비율
import ProgressGauge from '.../components/charts/ProgressGauge.astro';

<ProgressGauge value={1780} max={2000} label="이번 달 매출 목표" unit="만원" />

ScatterPlot — 포지셔닝 맵 (입지/경쟁사)

x/y 두 지표로 항목을 흩뿌려 위치를 비교한다. 점의 색은 개별 항목이 아니라 “그룹”(후보지/경쟁사)을 나타낸다 — 산점도는 어느 두 점이든 이웃할 수 있어서(all-pairs) 그룹은 2~3개까지만 안전하다. size를 주면 반지름에 반영된다(예: 평수).

임대료 (솔/m²)일 유동인구 (명)후보지 A (미라플로레스 말레콘) (후보지) — 임대료: 42솔/m², 일 유동인구: 3,200명후보지 B (미라플로레스 중심가) (후보지) — 임대료: 38솔/m², 일 유동인구: 2,600명후보지 C (바랑코 예술거리) (후보지) — 임대료: 29솔/m², 일 유동인구: 2,100명후보지 D (바랑코 부둣가) (후보지) — 임대료: 33솔/m², 일 유동인구: 1,800명후보지 E (수르퀴요 경계) (후보지) — 임대료: 25솔/m², 일 유동인구: 1,400명베소 프랑세 (라르코마르점) (경쟁사) — 임대료: 45솔/m², 일 유동인구: 3,600명와플대학 (미라플로레스점) (경쟁사) — 임대료: 40솔/m², 일 유동인구: 2,900명벨지안와플하우스 (바랑코점) (경쟁사) — 임대료: 31솔/m², 일 유동인구: 2,300명리에주웨이브 (수르퀴요점) (경쟁사) — 임대료: 27솔/m², 일 유동인구: 1,600명
  • 후보지
  • 경쟁사
import ScatterPlot from '.../components/charts/ScatterPlot.astro';

<ScatterPlot
	data={[{ x: 42, y: 3200, label: '후보지 A', group: '후보지', size: 28 }]}
	xLabel="임대료" yLabel="일 유동인구" ariaLabel="포지셔닝 맵"
/>

RadarChart — 다축 비교 (경쟁사 5각 평가 등)

여러 축을 한 폴리곤으로 겹쳐서 강점/약점을 한눈에 보여준다. 폴리곤도 산점도처럼 all-pairs 상황이라 시리즈는 3~4개까지만 권장(그 이상은 겹쳐서 어차피 안 읽힘).

맛가격경쟁력접근성브랜딩회전율베소 프랑세 · 맛: 8베소 프랑세 · 가격경쟁력: 5베소 프랑세 · 접근성: 9베소 프랑세 · 브랜딩: 9베소 프랑세 · 회전율: 6와플대학 · 맛: 7와플대학 · 가격경쟁력: 7와플대학 · 접근성: 6와플대학 · 브랜딩: 5와플대학 · 회전율: 8(우리) 후보지 A · 맛: 8(우리) 후보지 A · 가격경쟁력: 8(우리) 후보지 A · 접근성: 8(우리) 후보지 A · 브랜딩: 6(우리) 후보지 A · 회전율: 7
  • 베소 프랑세
  • 와플대학
  • (우리) 후보지 A
import RadarChart from '.../components/charts/RadarChart.astro';

<RadarChart
	axes={['맛', '가격경쟁력', '접근성']}
	series={[{ label: '경쟁사 A', values: [8, 5, 9] }]}
	ariaLabel="경쟁사 다축 비교"
/>

QuadrantMatrix — 2x2 사분면 (우선순위 매트릭스 등)

ScatterPlot과 계산은 같지만 중앙에 십자선이 있고 항목 수가 적어서(보통 10개 미만) 점마다 이름을 직접 라벨링한다 — 여기서는 항목 정체성보다 “어느 사분면에 있는가”가 중요해서 점 색은 그룹 구분 없이 액센트 하나만 쓴다.

바로 실행계획 필요여유 있을 때보류투입 노력기대 효과SNS 마케팅 강화 — 투입 노력: 3, 기대 효과: 8SNS 마케팅 강화시그니처 메뉴 개발 — 투입 노력: 2, 기대 효과: 6시그니처 메뉴 개발배달앱 입점 — 투입 노력: 7, 기대 효과: 8배달앱 입점인테리어 리뉴얼 — 투입 노력: 8, 기대 효과: 4인테리어 리뉴얼직원 교육 강화 — 투입 노력: 4, 기대 효과: 3직원 교육 강화회원제 도입 — 투입 노력: 6, 기대 효과: 3회원제 도입
import QuadrantMatrix from '.../components/charts/QuadrantMatrix.astro';

<QuadrantMatrix
	data={[{ x: 3, y: 8, label: 'SNS 마케팅 강화' }]}
	xLabel="투입 노력" yLabel="기대 효과" midX={5} midY={5}
	quadrantLabels={['바로 실행', '계획 필요', '여유 있을 때', '보류']}
	ariaLabel="실행 우선순위 매트릭스"
/>

SWOT처럼 좌표가 아니라 텍스트 블록 4개가 필요하면 차트 컴포넌트가 아니라 그냥 .bv-card 4개를 2x2 CSS 그리드로 배치하면 된다(사실 이건 차트가 아니라 레이아웃이라 전용 컴포넌트를 안 만들었다):

<div style="display:grid; grid-template-columns:1fr 1fr; gap:0.75rem;">
	<div class="bv-card"><strong>강점(S)</strong><ul><li>...</li></ul></div>
	<div class="bv-card"><strong>약점(W)</strong><ul><li>...</li></ul></div>
	<div class="bv-card"><strong>기회(O)</strong><ul><li>...</li></ul></div>
	<div class="bv-card"><strong>위협(T)</strong><ul><li>...</li></ul></div>
</div>

StackedBarChart — 구성 비율의 시간에 따른 변화

DonutChart가 한 시점의 스냅샷이라면, 이건 그 구성이 여러 카테고리(월 등)에 걸쳐 어떻게 바뀌는지 보여준다. 구성 항목이 시리즈 정체성이라 항상 같은 색 + 범례.

1월 · 임대료: 350만원1월 · 재료비: 180만원1월 · 인건비: 150만원1월 · 기타: 60만원1월2월 · 임대료: 350만원2월 · 재료비: 190만원2월 · 인건비: 150만원2월 · 기타: 55만원2월3월 · 임대료: 350만원3월 · 재료비: 210만원3월 · 인건비: 180만원3월 · 기타: 60만원3월4월 · 임대료: 350만원4월 · 재료비: 230만원4월 · 인건비: 200만원4월 · 기타: 65만원4월5월 · 임대료: 350만원5월 · 재료비: 260만원5월 · 인건비: 220만원5월 · 기타: 70만원5월6월 · 임대료: 350만원6월 · 재료비: 280만원6월 · 인건비: 220만원6월 · 기타: 75만원6월
  • 임대료
  • 재료비
  • 인건비
  • 기타
import StackedBarChart from '.../components/charts/StackedBarChart.astro';

<StackedBarChart
	data={[{ label: '1월', stacks: [{ label: '임대료', value: 350 }, { label: '재료비', value: 180 }] }]}
	ariaLabel="월별 비용 구조 변화" unit="만원"
/>

FunnelChart — 고객 전환 퍼널

순서를 바꾸면 의미가 달라지는 ordinal 데이터라 카테고리 8색이 아니라 한 색조의 명도(불투명도) 단계를 쓴다. 전환율은 이전 단계 값 대비 자동 계산 — 따로 안 넣어도 된다.

광고 노출: 12,000광고 노출 · 12,000프로필/링크 클릭: 2,400 (이전 단계 대비 20%)프로필/링크 클릭 · 2,400↓ 20%매장 방문: 640 (이전 단계 대비 26.7%)매장 방문 · 640↓ 26.7%구매 전환: 410 (이전 단계 대비 64.1%)구매 전환 · 410↓ 64.1%
import FunnelChart from '.../components/charts/FunnelChart.astro';

<FunnelChart data={[{ label: '광고 노출', value: 12000 }, { label: '매장 방문', value: 640 }]} ariaLabel="고객 전환 퍼널" />

BreakEvenChart — 손익분기점

비용/매출 두 선을 겹쳐 그리고 교차점을 자동으로 계산해 점선으로 표시한다(“비용” vs “매출”은 상태가 아니라 서로 다른 두 측정값이라 --bv-series-1/2를 쓴다).

손익분기점 (약 4월~5월 사이)손익분기점1월2월3월4월5월6월
  • 비용 (만원)
  • 매출 (만원)
import BreakEvenChart from '.../components/charts/BreakEvenChart.astro';

<BreakEvenChart data={[{ label: '1월', cost: 740, revenue: 400 }]} ariaLabel="손익분기점" unit="만원" />

HeatmapGrid — 요일×시간대 등 행렬 강도 (viz/)

값이 “크기”라 카테고리 8색이 아니라 한 액센트 색의 명도(불투명도) 단계로 표시한다. 리마 상권은 시간대 장사가 중요해서 요일×시간대 매출 밀도를 한눈에 보기 좋다.

10시12시14시16시18시20시22시월월 · 10시: 10월 · 12시: 35월 · 14시: 20월 · 16시: 18월 · 18시: 30월 · 20시: 40월 · 22시: 15화화 · 10시: 8화 · 12시: 32화 · 14시: 18화 · 16시: 16화 · 18시: 28화 · 20시: 38화 · 22시: 14수수 · 10시: 9수 · 12시: 34수 · 14시: 19수 · 16시: 17수 · 18시: 29수 · 20시: 39수 · 22시: 15목목 · 10시: 10목 · 12시: 36목 · 14시: 22목 · 16시: 20목 · 18시: 34목 · 20시: 45목 · 22시: 20금금 · 10시: 15금 · 12시: 42금 · 14시: 28금 · 16시: 26금 · 18시: 48금 · 20시: 68금 · 22시: 40토토 · 10시: 25토 · 12시: 55토 · 14시: 45토 · 16시: 40토 · 18시: 60토 · 20시: 85토 · 22시: 58일일 · 10시: 22일 · 12시: 50일 · 14시: 40일 · 16시: 35일 · 18시: 45일 · 20시: 55일 · 22시: 32
085
import HeatmapGrid from '.../components/viz/HeatmapGrid.astro';

<HeatmapGrid rows={['월', '화']} cols={['10시', '12시']} values={[[10, 35], [8, 32]]} ariaLabel="요일별 시간대 매출 히트맵" />

Table — 정렬 + 합계 행 (viz/, 기존 컴포넌트 확장)

기존 Table에 sortable(헤더 클릭 정렬, 최소 클라이언트 JS), showTotal(숫자 컬럼 합계 행), caption(스크린리더용 표 설명)을 추가했다.

경쟁사 비교표 더미 데이터
매장월매출_만원객단가_원좌석수
베소 프랑세4200980032
와플대학3100850024
벨지안와플하우스2600920018
리에주웨이브2050780020
합계11,95035,30094
import Table from '.../components/viz/Table.astro';

<Table rows={data} sortable showTotal caption="경쟁사 비교표" />

CanvasGrid — 린 캔버스 9블록 (viz/)

사실 차트가 아니라 CSS 그리드 레이아웃이라 색 규칙이 필요 없다 — 9개 블록 다 같은 카드 톤이고 위치와 제목으로 구분한다.

import CanvasGrid from '.../components/viz/CanvasGrid.astro';

<CanvasGrid problem={['...']} solution={['...']} uvp={['...']} customerSegments={['...']} costStructure={['...']} revenueStreams={['...']} />

PersonaCard — 고객 페르소나 (viz/)

사진 없이도 바로 쓸 수 있게 이니셜 원으로 아바타를 대신한다. Pain/Gain은 좋다/나쁘다 상태가 아니라 서로 다른 두 목록이라 중립 톤으로 나란히 놓는다.

마리아(29세)

미라플로레스 거주 마케팅 직장인

"퇴근길에 줄 안 서고 빨리 달달한 걸 먹고 싶어요."

Pain

  • 기존 와플집은 웨이팅이 김
  • 테이크아웃 포장이 부실해서 식으면 눅눅해짐

Gain

  • SNS에 올리기 좋은 비주얼
  • 주문 후 5분 내 픽업
import PersonaCard from '.../components/viz/PersonaCard.astro';

<PersonaCard name="마리아" role="직장인" age={29} pains={['...']} gains={['...']} />

PricingTable — 메뉴/요금제 가격·원가·마진 비교 (viz/)

마진율은 판매가/원가에서 자동 계산한다(따로 안 넣음 — 입력받으면 원본과 어긋날 위험만 생김). 마진율이 낮으면(기본 30% 미만) 색만이 아니라 경고 아이콘과 함께 표시한다.

와플 메뉴 가격·원가·마진 더미 데이터
메뉴판매가원가마진액마진율
클래식 리에주 와플18솔6솔12솔66.7%
누텔라 바나나 와플22솔9솔13솔59.1%
프리미엄 베리 치즈 와플28솔21솔7솔25%
import PricingTable from '.../components/viz/PricingTable.astro';

<PricingTable items={[{ name: '클래식 리에주 와플', price: 18, cost: 6 }]} unit="솔" />

WaterfallChart — 원가/손익 누적 빌드업

시작값에서 증감 단계를 거쳐 최종값까지 계단식으로 쌓는다. “증가=나쁨”인 원가 누적처럼 방향의 좋고 나쁨이 맥락마다 달라서 positiveIsGood으로 뒤집을 수 있다(기본은 증가=emerald/감소=rose, 아래 예시는 비용 누적이라 뒤집었다).

재료비: +1.5솔 (누계 1.5솔)+1.5재료비인건비 배분: +1.8솔 (누계 3.3솔)+1.8인건비 배분임대료 배분: +1.1솔 (누계 4.4솔)+1.1임대료 배분원가 합계: 4.4솔 (누계 4.4솔)4.4원가 합계
import WaterfallChart from '.../components/charts/WaterfallChart.astro';

<WaterfallChart
	steps={[{ label: '재료비', value: 1.5, type: 'increase' }, { label: '원가 합계', value: 4.4, type: 'total' }]}
	positiveIsGood={false} unit="솔" ariaLabel="원가 누적"
/>

WeightedScorecard — 후보지 가중치 평가표 (viz/)

LocationMap으로 지도에서 추린 후보지를 숫자로 최종 결판낼 때 쓴다. 열(후보지)은 카테고리 색, 셀 점수는 그 색의 명도로 “크기”까지 겹쳐 보여준다(숫자는 항상 텍스트로 병기).

입지 후보지 가중치 평가 더미 데이터
평가 기준후보지 A (말레콘)후보지 B (미라플로레스 중심가)후보지 C (바랑코)
임대료 부담 (30%)3 / 52 / 54 / 5
유동인구 (30%)5 / 54 / 53 / 5
접근성 (20%)4 / 55 / 53 / 5
경쟁 밀도(낮을수록 좋음) (20%)3 / 52 / 54 / 5
가중 합계3.83.23.5
import WeightedScorecard from '.../components/viz/WeightedScorecard.astro';

<WeightedScorecard
	criteria={[{ label: '임대료 부담', weight: 30 }, { label: '유동인구', weight: 30 }]}
	sites={[{ label: '후보지 A', scores: [3, 5] }, { label: '후보지 B', scores: [2, 4] }]}
/>

BulletChart — KPI 목표 대비 실적 한 줄 요약

배경의 3단계 구간(미달/양호/우수)은 순서가 있는 데이터라 카테고리색이 아니라 중립색 하나의 명도 단계를 쓰고, 실제값 막대만 액센트로 강조한다. 여러 KPI를 세로로 나열하기 좋다.

일 매출: 실제 78만원 / 목표 90만원일 매출
78만원/ 목표 90만원
테이블 회전율: 실제 3.2회 / 목표 3회테이블 회전율
3.2회/ 목표 3회
객단가: 실제 8,500원 / 목표 9,500원객단가
8,500원/ 목표 9,500원
import BulletChart from '.../components/charts/BulletChart.astro';

<BulletChart label="일 매출" actual={78} target={90} unit="만원" ariaLabel="일 매출 목표 대비" />

TornadoChart — 민감도 분석

가정(임대료, 객단가 등)이 ±% 변할 때 결과가 얼마나 흔들리는지 보여준다. 여기서는 방향이 실제로 좋다/나쁘다를 의미해서(기준보다 낮아지면 나쁜 시나리오) status 색(emerald/rose)을 쓴다. 영향이 큰 요인이 자동으로 위로 정렬된다.

객단가 ±10%객단가 ±10% 하한: 300만원객단가 ±10% 상한: 620만원300620방문객 수 ±10%방문객 수 ±10% 하한: 340만원방문객 수 ±10% 상한: 580만원340580임대료 ±10%임대료 ±10% 하한: 380만원임대료 ±10% 상한: 540만원380540재료비 ±10%재료비 ±10% 하한: 400만원재료비 ±10% 상한: 520만원400520인건비 ±10%인건비 ±10% 하한: 420만원인건비 ±10% 상한: 500만원420500

기준값: 460만원

import TornadoChart from '.../components/charts/TornadoChart.astro';

<TornadoChart baseValue={460} factors={[{ label: '임대료 ±10%', low: 380, high: 540 }]} unit="만원" ariaLabel="손익 민감도" />

SlopeChart — 두 시점 간 순위/값 변화

항목 식별은 카테고리 색, 상승/하락 방향은 색이 아니라 오른쪽 끝 삼각형으로 표시한다(색으로 방향까지 이중 인코딩하면 항목 색과 헷갈릴 수 있어서 분리).

2025 Q42026 Q2베소 프랑세: 8,200 → 9,500베소 프랑세 · 8,2009,500와플대학: 6,100 → 5,400와플대학 · 6,1005,400벨지안와플하우스: 4,200 → 4,800벨지안와플하우스 · 4,2004,800리에주웨이브: 2,100 → 3,600리에주웨이브 · 2,1003,600
import SlopeChart from '.../components/charts/SlopeChart.astro';

<SlopeChart leftLabel="2025 Q4" rightLabel="2026 Q2" items={[{ id: 'a', label: '경쟁사 A', left: 8200, right: 9500 }]} ariaLabel="변화 비교" />

GanttChart — 기간이 있는 작업 일정

Timeline은 점(마일스톤)이지만 이건 시작~종료 기간이 있는 막대다. “YYYY-MM” 문자열을 연속 월 인덱스로 바꿔 선형 축에 놓는다. 완료 여부는 색이 아니라 진하기(불투명도) + 점선 테두리로 구분한다.

2026-012026-032026-052026-072026-09✓ 상권 조사상권 조사: 2026-01 ~ 2026-02 (조사) — 완료✓ 후보지 방문후보지 방문: 2026-02 ~ 2026-03 (조사) — 완료사업자 등록사업자 등록: 2026-03 ~ 2026-03 (허가)임대차 계약임대차 계약: 2026-03 ~ 2026-04 (허가)인테리어 공사인테리어 공사: 2026-04 ~ 2026-06 (공사)SNS 사전 마케팅SNS 사전 마케팅: 2026-05 ~ 2026-07 (마케팅)정식 오픈정식 오픈: 2026-07 ~ 2026-07 (마케팅)
  • 조사
  • 허가
  • 공사
  • 마케팅
import GanttChart from '.../components/charts/GanttChart.astro';

<GanttChart items={[{ id: 'a', label: '상권 조사', start: '2026-01', end: '2026-02', group: '조사', done: true }]} ariaLabel="오픈 로드맵" />

HistogramChart — 연속값의 분포

BarChart는 이름 있는 카테고리 비교지만, 이건 순서가 있는 “구간”의 분포라 카테고리 8색 대신 한 hue의 명도 단계로 구간 순서만 은은하게 표시한다(막대 높이=빈도는 그대로 크기 인코딩).

6-8솔: 4곳6-8솔8-10솔: 11곳8-10솔10-12솔: 16곳10-12솔12-14솔: 9곳12-14솔14-16솔: 5곳14-16솔16솔~: 2곳16솔~
import HistogramChart from '.../components/charts/HistogramChart.astro';

<HistogramChart bins={[{ rangeLabel: '6-8솔', count: 4 }, { rangeLabel: '8-10솔', count: 11 }]} unit="곳" ariaLabel="가격대 분포" />

RangeBarChart — 구간(영업시간 등) 비교

GanttChart와 계산은 같지만 날짜가 아니라 숫자 축(0~24시 등)이다. 경쟁사들의 영업시간을 겹쳐서 “22시 이후 여는 곳이 우리밖에 없다” 같은 걸 바로 보여준다.

8시10.5시13시15.5시18시20.5시23시(우리) 후보지 A(우리) 후보지 A: 8시 ~ 23시베소 프랑세베소 프랑세: 9시 ~ 21시와플대학와플대학: 10시 ~ 20시벨지안와플하우스벨지안와플하우스: 9시 ~ 22시리에주웨이브리에주웨이브: 11시 ~ 19시
  • 우리
  • 경쟁사
import RangeBarChart from '.../components/charts/RangeBarChart.astro';

<RangeBarChart items={[{ label: '경쟁사 A', start: 9, end: 21, group: '경쟁사' }]} axisUnit="시" ariaLabel="영업시간 비교" />

PopulationPyramidChart — 배후 인구 피라미드

성별은 고정 카테고리 2개라 분홍/파랑 같은 고정관념 색 대신 --bv-series-1(남)/--bv-series-2(여)를 그대로 쓴다. CityMap의 POI 밀집도와 같이 쓰면 상권 분석 설득력이 올라간다.

60+ 남성: 3.2천명60+ 여성: 4.1천명60+50-59 남성: 4.5천명50-59 여성: 5천명50-5940-49 남성: 5.8천명40-49 여성: 6.2천명40-4930-39 남성: 7.5천명30-39 여성: 8.4천명30-3920-29 남성: 8.9천명20-29 여성: 10.6천명20-2910-19 남성: 5.1천명10-19 여성: 5.3천명10-19
  • 남성
  • 여성
import PopulationPyramidChart from '.../components/charts/PopulationPyramidChart.astro';

<PopulationPyramidChart groups={[{ ageLabel: '20-29', male: 8.9, female: 10.6 }]} unit="천명" ariaLabel="배후 인구 피라미드" />

실제 데이터로 바꾸는 법

전부 Props로 데이터를 받는 구조라 실제 데이터가 생기면 src/data/에 JSON으로 넣고 getCollection('data')로 읽어서 각 컴포넌트가 요구하는 배열 모양({ label, value }[] 또는 { date, label, done }[])으로만 맞춰 넘기면 된다. 지금 쓰는 src/data/mock/*.ts는 스타일 확인용이라, 실제 데이터로 바꾸고 나면 지워도 된다.

GRAPH VIEW · 연결 지도
dashboardmdcsvjson