그래픽 차트
데이터 시각화 차트를 PNG로 생성합니다. headless Chromium에서 Apache ECharts v6을 사용하여 HTML을 렌더링하고 Playwright를 통해 2배 레티나 품질로 스크린샷을 캡처합니다.
CDN: https://cdn.jsdelivr.net/npm/[email protected]/dist/echarts.min.js
중요 규칙 (생성할 때마다 반드시 읽을 것)
area유형 →type: 'line'+areaStyle: {}— ECharts에는type: 'area'가 없습니다.doughnut→type: 'pie'+radius: ['40%', '70%']— ECharts에는type: 'doughnut'가 없습니다.- 준비 신호:
chart.on('finished', fn)을chart.setOption()이전에 등록 — ECharts 버그 #14101/#17500: setOption 후에 리스너를 등록하면 조용히 절대 실행되지 않습니다. 항상finished와rendered이벤트를 setOption 전에 등록하세요. xAxis.type: 'category'는 반드시 명시적으로 설정 — ECharts는 데이터로부터 이를 추론하지 않습니다. 이를 잊으면 빈 차트가 생성됩니다.- 범주 레이블은
xAxis.data에 넣고,data.labels배열에 넣지 않음 — ECharts 구조는 평평합니다:{ xAxis, yAxis, series, grid, title, legend }—data나options아래에 중첩되지 않습니다. - 데이터 레이블은 완전히 내장됨 — 모든 시리즈에서
label: { show: true }를 사용하세요. 플러그인이 필요 없습니다. - 특정 막대/포인트 강조는 항목별
itemStyle로 처리 —데이터배열에{ value: N, itemStyle: { color: '#...' } }를 직접 넣으세요. Chart.js 스타일의backgroundColor배열을 사용하지 마세요. - ECharts 초기화는
<canvas>가 아닌<div>컨테이너를 사용 —echarts.init(document.getElementById('chart'))입니다. 컨테이너 div는 명시적인 크기가 필요합니다. - 옵션에
animation: false— 즉시 렌더링을 위해 애니메이션을 비활성화합니다. 그래도 준비 신호를 위해 setOption 전에finished+rendered이벤트를 등록하세요. - HTML을 채팅에 절대 덤프하지 마세요. 파일에 저장하고 요약만 표시하세요.
- 제목은 주제가 아닌 인사이트를 명시합니다. "월별 수익"이 아닌 "수익이 12개월 만에 3배 성장"
- 파이/도넛: body에
padding: 64px 80px사용 및.chart-container { max-height: 860px }— 제목이 없을 때 가장자리부터 채워지는 것을 방지합니다.
1단계: 입력 수집
필수: chart_type, data
선택적 매개변수 및 기본값:
| 매개변수 | 기본값 | 설명 |
|---|---|---|
| chart_type | — | bar / line / area / pie / doughnut / scatter / radar / treemap |
| data | — | JSON 배열 또는 CSV — 필수 |
| title | — | 인사이트를 명시하며, 10단어 이하 |
| subtitle | — | 1문장 컨텍스트 줄 |
| style | clean-slate | clean-slate / midnight-editorial / matt-gray / electric-burst / brutalist |
| dimensions | 1080x1080 | WxH 픽셀 (출력 PNG는 deviceScaleFactor로 2배) |
| x_label | — | X축 레이블 텍스트 |
| y_label | — | Y축 레이블 텍스트 |
| source | — | 바닥글에 표시되는 데이터 출처 |
| highlight | — | 강조할 데이터 레이블 (예: "Q4", "12월", 인덱스 3) |
chart_type 또는 data가 없으면 정확히 다음과 같이 물어보세요:
"차트를 만들려면 다음이 필요합니다:
- 차트 유형 — bar / line / area / pie / doughnut / scatter / radar / treemap
- 데이터 — JSON 배열 또는 CSV로 제공 (예: 레이블
['Q1','Q2','Q3','Q4','Q5']와 함께[12, 18, 22, 25, 31])선택 사항: title, style (기본값: clean-slate), dimensions (기본값: 1080×1080), 특정 데이터 포인트 강조"
둘 다 있으면 → 2단계로 이동합니다.
2단계: 내부 아키텍처 (사용자에게 절대 표시하지 않음)
1. 차트 유형 정규화:
area→ 시리즈에line+areaStyle: {}doughnut→ 시리즈에pie+radius: ['40%', '70%']horizontal bar→bar+ xAxis/yAxis 교체 (범주 축이 y에)- 그 외: 그대로 사용
2. references/chart-library.md 읽기 — 이 차트 유형의 전체 설정 사양 로드
3. references/style-presets.md 읽기 — 선택된 스타일의 CSS 토큰 + 데이터 팔레트 로드
4. 디자인 방향 결정:
| 결정 | 출처 |
|---|---|
| 톤 | 전문 / 에디토리얼 / 대담 / 기술 — 데이터의 대상 독자에 맞춤 |
| 데이터 스토리 | 이 차트가 증명하는 단일 인사이트 (제목이 됨) |
| 강조 전략 | 어떤 데이터 포인트에 시각적 강조가 필요한가? 왜? |
| 배경 | 밝음 (clean-slate, matt-gray) 또는 어두움 (midnight-editorial, electric-burst, brutalist) |
5. 데이터 파싱:
- 단순 배열
[12, 18, 22]→ series.data, 레이블은 별도 제공 - 객체 배열
[{x: '1월', y: 12}]→ xAxis.data는 x 키에서, series.data는 y 값에서 - CSV: 헤더 행을 xAxis.data로 파싱, 값 행을 series.data로 파싱
- 다중 시리즈: 각각
type,name,data가 있는 여러series항목 - 산점도:
series.data: [[x1,y1], [x2,y2], ...]형식
6. 크기 파싱: "1080x1080" → W=1080, H=1080. Body = WxH. 출력 PNG = 2W × 2H.
3단계: HTML 생성
생성하기 전에 모두 읽기:
- 이 차트 유형의 전체 ECharts 설정 사양을 위한
references/chart-library.md - 선택된 스타일의 CSS 토큰 + 팔레트를 위한
references/style-presets.md
필수 HTML 구조:
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
[스타일 프리셋의 폰트 CDN 링크]
<style>
:root {
[스타일 프리셋의 모든 CSS 토큰]
}
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
html, body {
width: [W]px; height: [H]px;
overflow: hidden;
background: var(--bg);
font-family: var(--font-body);
}
body {
display: flex;
flex-direction: column;
padding: 40px 48px 32px; /* 파이/도넛: 64px 80px 사용 */
}
/* ECharts 컨테이너는 명시적 크기가 있어야 함 */
.chart-container {
flex: 1;
min-height: 0;
/* 파이/도넛 전용: max-height: 860px; */
}
.chart-header { margin-bottom: 24px; }
.chart-title {
font-family: var(--font-display);
font-size: clamp(1.1rem, 2.5vw, 1.6rem);
font-weight: 700;
color: var(--text);
line-height: 1.2;
}
.chart-subtitle {
font-family: var(--font-body);
font-size: clamp(0.75rem, 1.2vw, 0.9rem);
color: var(--text-muted);
margin-top: 6px;
line-height: 1.5;
}
.chart-footer {
margin-top: 14px;
font-family: var(--font-body);
font-size: 10px;
color: var(--text-muted);
opacity: 0.65;
}
</style>
</head>
<body>
[제목 또는 부제목이 있으면: <div class="chart-header"><div class="chart-title">...</div>...</div>]
<div id="chart" class="chart-container"></div>
[출처가 있으면: <div class="chart-footer">출처: [source]</div>]
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/echarts.min.js"></script>
<script>
window.__chartReady = false;
document.fonts.ready.then(() => {
const container = document.getElementById('chart');
const chart = echarts.init(container, null, { renderer: 'canvas' });
// 중요: setOption 전에 이벤트 등록 — ECharts 버그 #14101/#17500
// 'finished'가 animation:false로 setOption 후에 등록되면 조용히 실행되지 않을 수 있음
chart.on('finished', () => {
window.__chartReady = true;
});
// 안전장치: 'rendered'를 통한 폴백
chart.on('rendered', () => {
clearTimeout(window.__renderDebounce);
window.__renderDebounce = setTimeout(() => { window.__chartReady = true; }, 100);
});
const palette = [스타일 프리셋의 팔레트];
const option = {
animation: false, // 스크린샷을 위한 즉시 렌더링
backgroundColor: 'transparent', // body CSS가 배경색 처리
textStyle: {
fontFamily: '[--font-body 값]',
color: '[--text-muted 값]',
},
[title 매개변수가 제공된 경우 title 설정],
[차트 유형별 legend 설정],
[차트 유형별 grid 설정],
[차트 유형별 xAxis 설정],
[차트 유형별 yAxis 설정],
series: [{
[이 유형에 대한 chart-library.md의 전체 시리즈 설정]
[차트 유형별로 적용된 팔레트 색상]
[강조가 있는 경우: 강조된 데이터 포인트에 항목별 itemStyle]
}]
};
chart.setOption(option); // setOption은 항상 이벤트 등록 후에 옴
});
</script>
</body>
</html>
디자인 품질 규칙:
- 제목 폰트:
fontWeight: 'bold',fontSize: 20–24— 얇은 제목 금지 - 그리드 선: 낮은 불투명도 (0.06–0.10) — 데이터에 종속
- 막대:
barMaxWidth: 60,itemStyle.borderRadius: [4,4,0,0]로 둥글게 - 선: 자연스러운 곡선을 위해
smooth: true, 포인트를 위해symbolSize: 8 - 파이/도넛: 내장 슬라이스 레이블을 위해
label.formatter: '{b}\n{d}%' - 어두운 프리셋: 그리드
rgba(255,255,255,0.07), 축 선/눈금 색상rgba(255,255,255,0.15) - 툴팁:
show: false— 정적 PNG, 호버 상호작용 없음 - 제목이 제공되지 않을 때:
.chart-header생략, ECharts 옵션에서 title 생략
4단계: 자체 QA (5단계 전에 모든 실패 수정)
구조:
- 컨테이너는
<div>이며<canvas>가 아님 -
window.__chartReady = false가document.fonts.ready전에 선언됨 -
chart.on('finished', ...)가chart.setOption()전에 등록됨 -
chart.on('rendered', ...)디바운스 폴백이chart.setOption()전에 등록됨 - 옵션 객체에
animation: false가 있음 -
chart.setOption(option)이 init 블록의 마지막 호출임
유형별:
- 영역:
type: 'line'+areaStyle: {}—type: 'area'없음 - 도넛:
type: 'pie'+radius: ['40%', '70%']—type: 'doughnut'없음 - 막대:
xAxis.type: 'category'가 명시적으로 설정됨 - 범주 데이터가
xAxis.data에 있음 (data.labels아님) - 파이/도넛: body
padding: 64px 80px+.chart-container { max-height: 860px } - 강조: 데이터 배열에 항목별
{ value: N, itemStyle: { color } }
디자인:
- 모든 팔레트 색상이
references/style-presets.md에서 왔음 - 제목이 인사이트를 명시함 (단순 주제가 아님)
-
tooltip: { show: false }또는 생략 (정적 PNG에 호버 없음) - 어두운 프리셋: 그리드/축 색상이
rgba(255,255,255,...)사용 -
source매개변수가 제공된 경우 바닥글에 출처 있음 - 데이터 레이블이 보임 (시리즈에 내장
label: { show: true })
5단계: 내보내기
제목 또는 차트 유형 + 데이터 컨텍스트에서 슬러그 결정 (케밥 케이스, 30자 이하):
mkdir -p chart/[slug]
HTML 저장: chart/[slug]/chart.html
빠른 브라우저 확인:
open chart/[slug]/chart.html
내보내기 실행 ([skill-root]를 이 스킬 디렉터리의 실제 경로로 바꾸기):
bash [skill-root]/scripts/export-chart.sh \
chart/[slug]/chart.html \
chart/[slug]/chart.png \
--width [W] \
--height [H]
스크립트는 첫 실행 시 Playwright를 설치하고 (~200MB Chromium 다운로드), deviceScaleFactor: 2로 차트를 캡처합니다.
6단계: 출력 요약
## 차트: [title]
날짜: [YYYY-MM-DD] | 유형: [chart_type] | 스타일: [style]
크기: [W×H]px → PNG: [2W×2H]px @2배 레티나
파일
소스: chart/[slug]/chart.html
출력: chart/[slug]/chart.png
크기: [X] KB
체크리스트
- [ ] 제목이 인사이트를 명확히 명시함
- [ ] 데이터 레이블이 표시 크기에서 읽기 쉬움
- [ ] 강조가 올바른 데이터 포인트에 표시됨
- [ ] 바닥글에 출처 표시가 있음
프롬프트 팁 (사용자가 안내를 요청할 때 표시)
"구조화된 데이터를 제공하세요 — JSON 또는 CSV, 설명문이 아닙니다."
"차트 유형을 명시적으로 지정하세요. '분기를 보여주는 차트'가 아닌 'Q1–Q4를 비교하는 막대 차트'"
"데이터 스토리를 지정하세요. '다른 모든 분기보다 뛰어난 Q4 강조'가 주석 컨텍스트를 제공합니다."
✅ 좋은 예: "선 차트를 만들어 주세요. 제목: '12개월 만에 $12k에서 $95k ARR로'. 데이터: [12, 18, 22, 25, 31, 38, 44, 52, 61, 68, 78, 95] (2024년 1월~12월). 12월 강조. 출처: 내부 CRM. 스타일: electric-burst."
❌ 나쁜 예: "우리 회사 성장에 대한 차트 만들어 줘"