csat-chart.js 1.2.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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/core/canvas/renderer.ts","../src/core/canvas/labels.ts","../src/core/canvas/fit.ts","../src/core/canvas/axes.ts","../src/core/canvas/legend.ts","../src/core/canvas/patterns.ts","../src/core/graphs/AbsBarGraph.ts","../src/core/types/common.ts","../src/core/types/climate.ts","../src/core/types/pyramid.ts","../src/core/types/ternary.ts","../src/core/types/stacked.ts","../src/core/types/absbar.ts","../src/core/types/scatter.ts","../src/core/types/hythergraph.ts","../src/core/types/cube.ts","../src/core/types/radar.ts","../src/core/types/categorydot.ts","../src/core/types/line.ts","../src/core/types/matrix.ts","../src/core/types/datatable.ts","../src/core/types/treemap.ts","../src/core/graphs/CategoryDotGraph.ts","../src/core/graphs/LineGraph.ts","../src/core/graphs/MatrixTable.ts","../src/core/graphs/DataTable.ts","../src/core/graphs/TreemapGraph.ts","../src/core/graphs/ClimateGraph.ts","../src/core/graphs/CubeGraph.ts","../src/core/graphs/DeviationAGraph.ts","../src/core/graphs/DeviationBGraph.ts","../src/core/graphs/Hythergraph.ts","../src/core/graphs/PopulationPyramid.ts","../src/core/graphs/RadarChart.ts","../src/core/graphs/ScatterBubble.ts","../src/core/graphs/StackedBarPie.ts","../src/core/graphs/TernaryDiagram.ts","../src/registry.ts","../src/validate.ts","../src/fonts.ts","../src/roundrect.ts","../src/chart.ts"],"sourcesContent":["// © 2026 김용현\n// csat-chart.js 공개 표면.\n//\n// `export *` 를 쓰지 않는다 — core 에는 GeoGrapher UI 의 관심사(지도 4종을 담은\n// GraphType, 내보내기 대화상자의 ExportSettings)가 섞여 있어서, 몰아 내보내면\n// 이 패키지에 없는 기능이 딸려 나간다. 이름을 하나씩 적는다.\n\n// ── 파사드 ─────────────────────────────────────────────\nexport { CsatChart } from './chart';\nexport type { CanvasLike } from './chart';\n\n// ── 종류와 설정 ────────────────────────────────────────\nexport { CHART_TYPES, isCsatChartType } from './registry';\nexport type {\n ChartDataMap,\n ConfigFor,\n CsatChartConfig,\n CsatChartType,\n PartialGraphOptions,\n UpdateFor,\n} from './types';\n\n// ── 오류 ───────────────────────────────────────────────\nexport { CsatChartError } from './validate';\n\n// ── 글꼴 ───────────────────────────────────────────────\nexport { ensureFonts } from './fonts';\nexport type { EnsureFontsOptions } from './fonts';\n\n// ── roundRect 폴리필 ───────────────────────────────────\n// `CsatChart` 생성자가 첫 렌더 전에 자동으로 부른다. 파사드를 거치지 않고\n// 저수준 렌더러를 직접 부르는 사람만 이 함수를 스스로 불러야 한다 — 자세한\n// 사정은 `./roundrect` 참고.\nexport { installRoundRectPolyfill } from './roundrect';\n\n// ── 저수준 렌더러 16종 ─────────────────────────────────\nexport {\n renderAbsBarGraph,\n renderCategoryDotGraph,\n renderClimateGraph,\n renderCubeGraph,\n renderDataTable,\n renderDeviationAGraph,\n renderDeviationBGraph,\n renderHythergraph,\n renderLineGraph,\n renderMatrixTable,\n renderPyramidGraph,\n renderRadarChart,\n renderScatterGraph,\n renderStackedGraph,\n renderTernaryGraph,\n renderTreemapGraph,\n} from './core/index';\n\n// ── 축 계산 유틸 ───────────────────────────────────────\nexport { niceStep, autoRange } from './core/index';\n\n// ── 기본값 생성기 ──────────────────────────────────────\nexport {\n createDefaultGraphOptions,\n createDefaultAbsBarData,\n createDefaultCategoryDotData,\n createDefaultClimateData,\n createDefaultCubeData,\n createDefaultDataTableData,\n createDefaultDeviationAData,\n createDefaultDeviationBData,\n createDefaultHythergraphData,\n createDefaultLineData,\n createDefaultMatrixTableData,\n createDefaultPyramidData,\n createDefaultRadarData,\n createDefaultScatterData,\n createDefaultStackedData,\n createDefaultTernaryData,\n createDefaultTreemapData,\n} from './core/index';\n\n// ── 상수 ───────────────────────────────────────────────\nimport {\n AGE_GROUPS as coreAgeGroups,\n DOT_MARKER_ORDER as coreDotMarkerOrder,\n LINE_DASH as coreLineDash,\n LINE_MARKER_ORDER as coreLineMarkerOrder,\n LINE_STYLE_ORDER as coreLineStyleOrder,\n MONTH_LABELS_EN as coreMonthLabelsEn,\n MONTH_LABELS_NUM as coreMonthLabelsNum,\n} from './core/index';\n\n/**\n * 상수는 얼려서 내보낸다.\n *\n * 여기서 내보내는 것은 렌더러가 기본값으로 읽는 **바로 그 객체**다. 얼리지 않으면\n * `CsatChart.DOT_MARKER_ORDER.reverse()` 한 번에 이후 모든 그림의 기호 배정이\n * 조용히 어긋난다. 타입 검사를 받지 않는 CDN 사용자를 겨냥한 패키지라 특히 그렇다.\n * `CHART_TYPES` 를 얼린 것과 같은 이유다.\n *\n * 각 `Object.freeze` 앞의 PURE 주석을 지우지 말 것. 함수 호출은 번들러가\n * 부작용이 있을 수 있다고 보수적으로 가정하는 대상이라, 표시가 없으면 이\n * 상수들을 아무도 안 써도 문장 자체가 안 지워진다 — 여기서는 값이 각각\n * `core` 배열 하나씩만 가리키므로 결과가 이 파일 안에서 끝나 심각하지 않지만,\n * `registry.ts` 의 `CHART_TYPES` 는 같은 문제가 16종 렌더러 전체를 붙드는\n * 문제로 번진다. 그 사례를 따라 여기도 표시해 둔다.\n */\nexport const AGE_GROUPS = /* @__PURE__ */ Object.freeze(coreAgeGroups);\nexport const DOT_MARKER_ORDER = /* @__PURE__ */ Object.freeze(coreDotMarkerOrder);\nexport const LINE_MARKER_ORDER = /* @__PURE__ */ Object.freeze(coreLineMarkerOrder);\nexport const LINE_STYLE_ORDER = /* @__PURE__ */ Object.freeze(coreLineStyleOrder);\nexport const MONTH_LABELS_EN = /* @__PURE__ */ Object.freeze(coreMonthLabelsEn);\nexport const MONTH_LABELS_NUM = /* @__PURE__ */ Object.freeze(coreMonthLabelsNum);\n\n// LINE_DASH 는 Record<LineStyle, number[]> 다. 얕게 얼리면 LINE_DASH.dashed 를\n// 갈아 끼우는 것만 막고, LINE_DASH.dashed.push(1) 은 그대로 통한다.\nexport const LINE_DASH = /* @__PURE__ */ Object.freeze({\n solid: /* @__PURE__ */ Object.freeze(coreLineDash.solid),\n dashed: /* @__PURE__ */ Object.freeze(coreLineDash.dashed),\n dotted: /* @__PURE__ */ Object.freeze(coreLineDash.dotted),\n dashdot: /* @__PURE__ */ Object.freeze(coreLineDash.dashdot),\n});\n\n// ── 타입 ───────────────────────────────────────────────\nexport type {\n GraphOptions,\n LegendPosition,\n InsideLegendCorner,\n AbsBarCategory,\n AbsBarDirection,\n AbsBarGraphData,\n CategoryDotCategory,\n CategoryDotGraphData,\n DotMarker,\n ClimateGraphData,\n ClimateMode,\n ClimateMonthData,\n MonthInterval,\n DeviationAData,\n DeviationBData,\n DeviationBRegion,\n CubeAxisConfig,\n CubeGraphData,\n CubePoint,\n LabelOffset,\n DataTableData,\n DataTableRow,\n HythergraphData,\n HythergraphMode,\n HythergraphSeries,\n MonthLabelStyle,\n LineGraphData,\n LineLabelPlacement,\n LineMarker,\n LineSeries,\n LineStyle,\n MatrixTableData,\n AgeLabelSide,\n PyramidAgeData,\n PyramidGraphData,\n PyramidUnit,\n RadarGraphData,\n RadarSeries,\n BubbleLegendPosition,\n ScatterGraphData,\n ScatterMode,\n ScatterPoint,\n StackedBarDirection,\n StackedCategory,\n StackedDisplayMode,\n StackedGraphData,\n TernaryGraphData,\n TernaryGridInterval,\n TernaryPoint,\n TreemapCell,\n TreemapGraphData,\n} from './core/index';\n","// © 2026 김용현\n// Canvas 공통 렌더링 유틸리티\n\nexport interface Padding {\n top: number;\n right: number;\n bottom: number;\n left: number;\n}\n\nexport interface CanvasSize {\n width: number;\n height: number;\n}\n\nexport function getFont(\n size: number,\n family: 'serif' | 'sans' | 'custom' = 'serif',\n customFont?: string,\n weight: string = 'normal'\n): string {\n const familyMap: Record<string, string> = {\n serif: \"'Noto Serif KR', 'NanumMyeongjo', serif\",\n sans: \"'Noto Sans KR', sans-serif\",\n custom: customFont || \"'Noto Serif KR', serif\",\n };\n return `${weight} ${size}px ${familyMap[family]}`;\n}\n\nexport function clearCanvas(ctx: CanvasRenderingContext2D, w: number, h: number) {\n ctx.fillStyle = '#fff';\n ctx.fillRect(0, 0, w, h);\n}\n\n/** 줄바꿈(\\n) 지원 텍스트 렌더링 */\nexport function fillTextMultiline(\n ctx: CanvasRenderingContext2D,\n text: string,\n x: number,\n y: number,\n lineHeight: number\n) {\n const lines = text.split('\\\\n');\n if (lines.length <= 1) {\n ctx.fillText(text, x, y);\n return;\n }\n // 세로 중앙 정렬: baseline이 middle인 경우 전체 블록 중앙에 맞춤\n const totalH = (lines.length - 1) * lineHeight;\n const startY = y - totalH / 2;\n for (let i = 0; i < lines.length; i++) {\n ctx.fillText(lines[i], x, startY + i * lineHeight);\n }\n}\n\n/** 적절한 눈금 간격을 자동 계산 */\nexport function niceStep(range: number, maxTicks: number = 8): number {\n // 간격 0을 돌려주면 안 된다. 호출부는 `for (v = step; v <= max; v += step)`\n // 꼴로 눈금을 그리므로 step 이 0 이면 영원히 돈다(동기 루프라 탭이 멎는다).\n // 범위가 0·음수·NaN 인 경우가 여기 해당한다.\n if (!Number.isFinite(range) || range <= 0) return 1;\n const rough = range / maxTicks;\n const pow = Math.pow(10, Math.floor(Math.log10(rough)));\n const norm = rough / pow;\n let step: number;\n if (norm <= 1.5) step = 1;\n else if (norm <= 3) step = 2;\n else if (norm <= 7) step = 5;\n else step = 10;\n return step * pow;\n}\n\n/** 자동 축 범위 계산 (min, max, step) */\nexport function autoRange(\n values: number[],\n maxTicks: number = 8\n): { min: number; max: number; step: number } {\n if (values.length === 0) return { min: 0, max: 100, step: 20 };\n let min = Math.min(...values);\n let max = Math.max(...values);\n // 값이 사실상 하나뿐이면 범위를 벌린다.\n //\n // `min === max` 로만 판정하면 부동소수점 오차로 미세하게 갈린 값들이\n // 빠져나간다. 예를 들어 편차 그래프에서 모든 달에 같은 값을 더하면\n // 3.0000000000000004 와 2.9999999999999996 이 섞여 나온다. 그러면\n // step 이 1e-16 수준이 되어 눈금 루프가 6e16 번 돌고 화면이 멎는다.\n // 그래서 절대 크기에 비례한 허용 오차로 판정한다.\n if (!(max - min > Math.max(Math.abs(min), Math.abs(max)) * 1e-9)) {\n min -= 10;\n max += 10;\n }\n const margin = (max - min) * 0.1;\n min = min - margin;\n max = max + margin;\n const step = niceStep(max - min, maxTicks);\n min = Math.floor(min / step) * step;\n max = Math.ceil(max / step) * step;\n return { min, max, step };\n}\n","// © 2026 김용현\nconst LABEL_FONT = \"'Noto Sans KR', sans-serif\";\n\ninterface TitleParams {\n ctx: CanvasRenderingContext2D;\n plotX: number;\n plotW: number;\n title: string;\n fontSize: number;\n /** 캔버스 전체 너비. 주면 제목이 넘칠 때 글자를 줄여 맞춘다. */\n canvasWidth?: number;\n}\n\n/** 최소 이만큼까지는 줄인다. 더 줄이면 읽기 어렵다. */\nconst MIN_SHRINK = 0.6;\n\n/**\n * 주어진 폭에 들어가도록 글꼴 크기를 줄인다.\n * 그래도 안 들어가면 마지막 수단으로 fillText 의 maxWidth 로 눌러 담는다.\n */\nfunction fitFontSize(\n ctx: CanvasRenderingContext2D,\n text: string,\n fontSize: number,\n available: number,\n makeFont: (size: number) => string\n): number {\n if (available <= 0) return fontSize;\n ctx.font = makeFont(fontSize);\n const w = ctx.measureText(text).width;\n if (w <= available) return fontSize;\n const scaled = Math.max(fontSize * MIN_SHRINK, fontSize * (available / w));\n return Math.floor(scaled);\n}\n\nexport function drawTitle({ ctx, plotX, plotW, title, fontSize, canvasWidth }: TitleParams) {\n if (!title) return;\n ctx.save();\n ctx.fillStyle = '#000';\n\n const makeFont = (size: number) => `bold ${size}px ${LABEL_FONT}`;\n // 제목은 그래프 가운데에 놓이므로 캔버스 양쪽으로 넘칠 수 있다.\n // 가운데를 기준으로 양쪽에서 좁은 쪽 × 2 가 실제로 쓸 수 있는 폭이다.\n const centerX = plotX + plotW / 2;\n const available = canvasWidth != null\n ? Math.max(0, Math.min(centerX, canvasWidth - centerX) * 2 - 12)\n : plotW;\n\n const size = fitFontSize(ctx, title, fontSize, available, makeFont);\n ctx.font = makeFont(size);\n ctx.textAlign = 'center';\n ctx.textBaseline = 'top';\n ctx.fillText(title, centerX, 10, available > 0 ? available : undefined);\n ctx.restore();\n}\n\ninterface SourceFootnoteParams {\n ctx: CanvasRenderingContext2D;\n plotX: number;\n plotW: number;\n height: number;\n source: string;\n /** 출처 줄 왼쪽에 함께 적는 글 (예: 자료 연도). 주면 출처 줄이 각주 아래로 내려간다. */\n sourceLeft?: string;\n /** 출처를 마지막 각주와 같은 줄 오른쪽 끝에 둔다 (시험지 관습) */\n sourceInline?: boolean;\n footnotes: string[];\n fontSize: number;\n canvasWidth?: number;\n}\n\n/** 출처·각주를 캔버스 좌우 끝에서 얼마나 띄울지 */\nconst EDGE_MARGIN = 10;\n\nexport function drawSourceAndFootnote({\n ctx, plotX, plotW, height, source, sourceLeft, sourceInline, footnotes, fontSize, canvasWidth,\n}: SourceFootnoteParams) {\n ctx.save();\n\n // 출처·연도는 오른쪽 **끝**, 각주는 왼쪽 **끝**에 붙인다 (2026-08-04 사용자 결정).\n // 예전에는 플롯 영역에 맞춰 안쪽으로 들여써서 그림 가운데에 뜬 것처럼 보였다.\n let leftX = plotX;\n let rightX = plotX + plotW;\n if (canvasWidth != null) {\n leftX = EDGE_MARGIN;\n rightX = canvasWidth - EDGE_MARGIN;\n }\n\n // 위에서 아래로: 출처 → 각주들.\n // 단 sourceLeft 를 주면 순서가 뒤집힌다 — 각주 아래에 \"(연도) ... (출처)\" 한 줄.\n const filtered = footnotes.filter((f) => f.trim());\n const totalFootnoteH = filtered.length * (fontSize * 0.9 + 4);\n const sourceBelow = !!sourceLeft;\n // 시험지 관습 — 출처를 마지막 각주와 **같은 줄** 오른쪽 끝에 둔다.\n // 각주가 없으면 놓을 줄이 없으므로 기존 배치를 그대로 쓴다.\n const inlineSource = !!sourceInline && !!source && !sourceBelow && filtered.length > 0;\n const sourceH = (source || sourceLeft) && !inlineSource ? fontSize + 4 : 0;\n let y = height - 6 - totalFootnoteH - (sourceBelow ? sourceH : 0);\n\n // 각주는 왼쪽 끝에서 시작해 오른쪽으로 흐른다. 캔버스를 넘으면 잘리므로\n // 쓸 수 있는 폭을 미리 재 두고, 넘치는 글은 글꼴을 줄여 맞춘다.\n const rightEdge = canvasWidth != null ? rightX : plotX + plotW;\n const available = Math.max(0, rightEdge - leftX);\n\n const sourceFont = (size: number) => `bold ${size}px ${LABEL_FONT}`;\n\n if (source && !sourceBelow && !inlineSource) {\n ctx.fillStyle = '#555';\n const size = fitFontSize(ctx, source, fontSize, available, sourceFont);\n ctx.font = sourceFont(size);\n ctx.textAlign = 'right';\n ctx.textBaseline = 'bottom';\n ctx.fillText(source, rightX, y, available > 0 ? available : undefined);\n y += sourceH;\n }\n\n // 같은 줄에 출처가 들어오면 각주가 쓸 수 있는 폭이 그만큼 줄어든다\n ctx.font = sourceFont(fontSize);\n const inlineSourceW = inlineSource ? ctx.measureText(source).width + 16 : 0;\n const footnoteAvailable = Math.max(0, available - inlineSourceW);\n\n let lastFootnoteY = y;\n for (let i = 0; i < filtered.length; i++) {\n const text = '* ' + filtered[i];\n ctx.fillStyle = '#555';\n const makeFont = (size: number) => `${size}px ${LABEL_FONT}`;\n const size = fitFontSize(ctx, text, fontSize * 0.9, footnoteAvailable, makeFont);\n ctx.font = makeFont(size);\n ctx.textAlign = 'left';\n ctx.textBaseline = 'bottom';\n ctx.fillText(text, leftX, y, footnoteAvailable > 0 ? footnoteAvailable : undefined);\n lastFootnoteY = y;\n y += fontSize * 0.9 + 4;\n }\n\n // 마지막 각주와 같은 줄, 오른쪽 끝\n if (inlineSource) {\n ctx.fillStyle = '#555';\n const size = fitFontSize(ctx, source, fontSize, inlineSourceW, sourceFont);\n ctx.font = sourceFont(size);\n ctx.textAlign = 'right';\n ctx.textBaseline = 'bottom';\n ctx.fillText(source, rightX, lastFootnoteY, inlineSourceW > 0 ? inlineSourceW : undefined);\n }\n\n // 각주 아래 출처 줄 — 왼쪽에 자료 연도, 오른쪽에 출처 기관\n if (sourceBelow) {\n const makeFont = (size: number) => `bold ${size}px ${LABEL_FONT}`;\n const half = available > 0 ? available / 2 : 0;\n ctx.fillStyle = '#555';\n ctx.textBaseline = 'bottom';\n\n ctx.font = makeFont(fitFontSize(ctx, sourceLeft!, fontSize, half, makeFont));\n ctx.textAlign = 'left';\n ctx.fillText(sourceLeft!, leftX, height - 6, half > 0 ? half : undefined);\n\n if (source) {\n ctx.font = makeFont(fitFontSize(ctx, source, fontSize, half, makeFont));\n ctx.textAlign = 'right';\n ctx.fillText(source, rightX, height - 6, half > 0 ? half : undefined);\n }\n }\n\n ctx.restore();\n}\n\n// ── 겹침 회피 라벨 배치 ─────────────────────────────────────\n//\n// 점이 붙어 있으면 라벨끼리, 또는 라벨과 점이 겹쳐 읽을 수 없게 된다.\n// 후보 위치를 순서대로 시도해 빈 자리를 찾고, 어디에도 안 들어가면\n// 가장 멀리 밀어낸 뒤 유도선을 그어 어느 점의 라벨인지 알 수 있게 한다.\n\nexport interface LabelBox {\n left: number;\n right: number;\n top: number;\n bottom: number;\n}\n\n/** 이미 자리를 차지한 영역들을 모아 두고 충돌을 판정한다 */\nexport class LabelPlacer {\n private used: LabelBox[] = [];\n\n /** 점 자체도 자리를 차지한다 — 라벨이 점 위에 얹히지 않게 미리 등록한다 */\n reserve(box: LabelBox) {\n this.used.push(box);\n }\n\n reserveCircle(cx: number, cy: number, r: number) {\n this.used.push({ left: cx - r, right: cx + r, top: cy - r, bottom: cy + r });\n }\n\n private overlaps(box: LabelBox): boolean {\n return this.used.some(\n (u) => !(box.right < u.left || box.left > u.right || box.bottom < u.top || box.top > u.bottom)\n );\n }\n\n /**\n * 라벨을 **정해진 x 열에** 놓고, 겹치면 위아래로만 민다.\n *\n * 선 끝 이름처럼 여러 라벨이 한 줄로 나란해야 하는 경우에 쓴다.\n * 일반 `place()` 는 8방향으로 움직여 가로 위치가 흐트러진다.\n *\n * @param x 라벨 왼쪽 끝 x (모든 라벨이 공유하는 열)\n * @param preferredY 원래 놓고 싶은 y (라벨 상단)\n */\n placeInColumn(\n ctx: CanvasRenderingContext2D,\n label: string,\n x: number,\n preferredY: number,\n opts: { lineHeight: number; bounds: LabelBox }\n ) {\n const w = ctx.measureText(label).width;\n const h = opts.lineHeight;\n const { bounds } = opts;\n const stepY = h + 2;\n\n // 원래 자리에서 시작해 위·아래로 번갈아 가며 빈 자리를 찾는다\n for (let k = 0; k < 40; k++) {\n const dy = Math.ceil(k / 2) * stepY * (k % 2 === 0 ? 1 : -1);\n const y = preferredY + dy;\n if (y < bounds.top || y + h > bounds.bottom) continue;\n\n const box = { left: x, right: x + w, top: y, bottom: y + h };\n if (!this.overlaps(box)) {\n this.used.push(box);\n ctx.textAlign = 'left';\n ctx.textBaseline = 'top';\n ctx.fillText(label, x, y);\n return;\n }\n }\n\n // 빈 자리를 못 찾으면 원래 자리에 그대로 둔다 (열은 유지)\n this.used.push({ left: x, right: x + w, top: preferredY, bottom: preferredY + h });\n ctx.textAlign = 'left';\n ctx.textBaseline = 'top';\n ctx.fillText(label, x, preferredY);\n }\n\n /**\n * (cx, cy) 근처에 라벨을 놓는다.\n *\n * 가까운 후보부터 8방향으로 시도하고, 다 막히면 거리를 늘려 다시 돈다.\n * 그래도 안 되면 마지막 후보에 놓고 유도선을 긋는다.\n *\n * @param bounds 라벨이 벗어나면 안 되는 영역 (플롯 영역)\n */\n place(\n ctx: CanvasRenderingContext2D,\n label: string,\n cx: number,\n cy: number,\n opts: { gap: number; lineHeight: number; bounds: LabelBox }\n ) {\n const w = ctx.measureText(label).width;\n const h = opts.lineHeight;\n const { gap, bounds } = opts;\n\n // 가까운 곳부터 — 오른쪽 위를 먼저 본다(기존 기본 위치)\n const dirs: [number, number][] = [\n [1, -1], [1, 0], [0, -1], [-1, -1],\n [-1, 0], [0, 1], [1, 1], [-1, 1],\n ];\n\n let fallback: { x: number; y: number } | null = null;\n\n for (const dist of [gap, gap * 2, gap * 3.5]) {\n for (const [dx, dy] of dirs) {\n const x = cx + dx * dist - (dx < 0 ? w : dx === 0 ? w / 2 : 0);\n const y = cy + dy * dist - (dy > 0 ? 0 : dy === 0 ? h / 2 : h);\n const box = { left: x, right: x + w, top: y, bottom: y + h };\n\n if (box.left < bounds.left || box.right > bounds.right) continue;\n if (box.top < bounds.top || box.bottom > bounds.bottom) continue;\n\n if (!this.overlaps(box)) {\n this.used.push(box);\n ctx.textAlign = 'left';\n ctx.textBaseline = 'top';\n ctx.fillText(label, x, y);\n return;\n }\n if (!fallback) fallback = { x, y };\n }\n }\n\n // 다 막혔다 — 가장 먼 자리에 놓고 유도선을 긋는다\n const far = fallback ?? { x: cx + gap, y: cy - h };\n const box = { left: far.x, right: far.x + w, top: far.y, bottom: far.y + h };\n this.used.push(box);\n\n ctx.save();\n ctx.strokeStyle = '#666';\n ctx.lineWidth = 0.8;\n ctx.beginPath();\n ctx.moveTo(cx, cy);\n ctx.lineTo(far.x + w / 2, far.y + h / 2);\n ctx.stroke();\n ctx.restore();\n\n ctx.textAlign = 'left';\n ctx.textBaseline = 'top';\n ctx.fillText(label, far.x, far.y);\n }\n}\n\n// ── 눈금 라벨 겹침 방지 ─────────────────────────────────────\n\n/**\n * 눈금 라벨을 몇 개 걸러 그릴지 정한다.\n *\n * 축 범위가 좁거나 칸이 많으면 숫자가 서로 붙어 읽을 수 없게 된다.\n * 라벨 크기와 눈금 간격을 재서, 겹치지 않는 최소 간격(stride)을 돌려준다.\n * 1이면 전부 그려도 된다는 뜻이다.\n *\n * 눈금 표시(작은 선)는 그대로 두고 숫자만 솎아낸다 — 축의 눈금 간격 자체는\n * 그래프가 읽히는 근거이므로 없애면 안 된다.\n *\n * @param spacing 이웃한 눈금 사이의 픽셀 거리\n * @param extent 라벨이 축 방향으로 차지하는 크기 (가로축이면 글자 너비, 세로축이면 줄 높이)\n */\nexport function labelStride(spacing: number, extent: number, minGap = 6): number {\n if (!Number.isFinite(spacing) || spacing <= 0) return 1;\n return Math.max(1, Math.ceil((extent + minGap) / spacing));\n}\n\n/** 눈금 라벨들 중 가장 넓은 글자 너비 */\nexport function widestLabel(ctx: CanvasRenderingContext2D, labels: string[]): number {\n if (labels.length === 0) return 0;\n return Math.max(...labels.map((l) => ctx.measureText(l).width));\n}\n","// © 2026 김용현\n// 글자를 캔버스 안에 넣는 공통 계산.\n//\n// 시험지 그림에서 **이름을 잘라 「…」로 끝내는 것은 답이 아니다.** 「서울특별시」와\n// 「서울특별시 강남구」가 같은 「서울특별…」로 보이면 문항 자체가 틀린다. 그래서\n// 자리가 모자랄 때 쓸 수 있는 수는 넷뿐이고, 순서가 있다.\n//\n// 1. **여백을 넓힌다** — 글자가 놓일 자리를 그만큼 비우고 그림을 줄인다.\n// 2. **줄을 늘린다** — 여백을 더 못 넓힐 때 이름을 여러 줄로 접는다.\n// 3. **글꼴을 줄인다** — 그 글자에 한해, 바닥(`MIN_SCALE`)까지만.\n// 4. **밀어 넣는다** — 여백에 떠 있는 글자(축 위 단위·유도선 붙은 이름)는\n// 옆으로 밀어도 자료를 가리지 않는다. 이때도 글자는 통째로 남는다.\n//\n// 이 파일은 그 넷을 재는 자다. 어느 수를 쓸지는 그래프마다 다르므로 각 렌더러가\n// 정한다.\n\n/** 글꼴은 원래 크기의 이만큼까지만 줄인다. 더 줄이면 시험지에서 읽기 어렵다. */\nexport const MIN_SCALE = 0.7;\n\n/** 캔버스 가장자리에서 이만큼은 띄운다 (글자 가장자리 안티에일리어싱 여유) */\nexport const EDGE = 1;\n\nexport interface TextExtent {\n /** 기준점에서 왼쪽으로 뻗은 길이 */\n left: number;\n /** 기준점에서 오른쪽으로 뻗은 길이 */\n right: number;\n /** 기준점에서 위로 뻗은 길이 */\n up: number;\n /** 기준점에서 아래로 뻗은 길이 */\n down: number;\n}\n\n/**\n * 지금 `ctx.font`·`textAlign`·`textBaseline` 으로 그렸을 때 글자가 기준점\n * 둘레로 뻗는 길이.\n *\n * **가로는 `actualBoundingBoxLeft/Right` 를 쓰지 않고 보내는 폭(`width`)으로\n * 잰다.** 글꼴 대체가 일어나면 그 두 값이 «첫 글꼴 조각만» 재는 구현이 있다 —\n * @napi-rs/canvas 가 그렇다. 「평년 대비 강수량 차이(mm)」의 `width` 는 338.1\n * 인데 `actualBoundingBoxRight` 는 한글 부분만 센 275.2 를 돌려준다. 그 값을\n * 믿으면 「(mm)」 이 캔버스 밖으로 나가는데도 들어갔다고 판단한다. `width` 는\n * 조각 수와 무관하게 늘 옳고, 잉크보다 조금 넉넉하니 안전한 쪽으로 틀린다.\n *\n * 세로는 **글자마다 따로 재서** 가장 높은·낮은 것을 취한다. 한 글자는 언제나\n * 한 조각이므로 같은 함정에 빠지지 않는다.\n */\nexport function textExtent(ctx: CanvasRenderingContext2D, text: string): TextExtent {\n const m = ctx.measureText(text);\n const w = m.width;\n const left = ctx.textAlign === 'right' || ctx.textAlign === 'end' ? w\n : ctx.textAlign === 'center' ? w / 2 : 0;\n\n const size = fontSizeOf(ctx.font);\n let up = ctx.textBaseline === 'top' ? 0 : ctx.textBaseline === 'middle' ? size * 0.5 : size * 0.8;\n let down = size - up;\n if (m.actualBoundingBoxAscent != null) {\n up = 0;\n down = 0;\n for (const ch of text) {\n if (!ch.trim()) continue;\n const cm = ctx.measureText(ch);\n up = Math.max(up, cm.actualBoundingBoxAscent);\n down = Math.max(down, cm.actualBoundingBoxDescent);\n }\n }\n return { left, right: w - left, up, down };\n}\n\n/** `'bold 28px ...'` 에서 28 을 뽑는다. 못 읽으면 16. */\nfunction fontSizeOf(font: string): number {\n const m = /(\\d+(?:\\.\\d+)?)px/.exec(font);\n return m ? parseFloat(m[1]) : 16;\n}\n\n/**\n * 캔버스 안으로 **밀어 넣은** 기준점.\n *\n * 이미 안에 들어 있으면 받은 좌표를 **그대로** 돌려준다 — 멀쩡한 그림이\n * 움직이지 않게 하는 성질이 여기 걸려 있다.\n *\n * 글자가 캔버스보다 넓거나 높으면 한쪽만 맞춘다. 그런 글자는 밀어서 될 일이\n * 아니므로 부르는 쪽이 먼저 `shrinkToWidth` 로 줄여야 한다.\n */\nexport function nudgeInside(\n ctx: CanvasRenderingContext2D,\n text: string,\n x: number,\n y: number,\n canvasW: number,\n canvasH: number,\n margin: number = EDGE,\n): { x: number; y: number } {\n const e = textExtent(ctx, text);\n let nx = x;\n let ny = y;\n if (x - e.left < margin) nx = margin + e.left;\n else if (x + e.right > canvasW - margin) nx = canvasW - margin - e.right;\n if (y - e.up < margin) ny = margin + e.up;\n else if (y + e.down > canvasH - margin) ny = canvasH - margin - e.down;\n return { x: nx, y: ny };\n}\n\n/**\n * 주어진 폭에 들어가도록 줄인 글꼴 크기. 바닥은 `size * MIN_SCALE`.\n *\n * 바닥에서도 안 들어가면 바닥 값을 돌려준다 — 자르지는 않는다.\n */\nexport function shrinkToWidth(\n ctx: CanvasRenderingContext2D,\n texts: string[],\n size: number,\n available: number,\n makeFont: (size: number) => string,\n minScale: number = MIN_SCALE,\n): number {\n if (available <= 0 || texts.length === 0) return size;\n const saved = ctx.font;\n ctx.font = makeFont(size);\n const w = Math.max(...texts.map((t) => ctx.measureText(t).width));\n ctx.font = saved;\n if (w <= available) return size;\n return Math.max(size * minScale, size * (available / w));\n}\n\n/**\n * 이름을 주어진 폭 안에 들어가도록 여러 줄로 접는다.\n *\n * 공백에서 먼저 접고, 한 낱말이 통째로 넘치면 글자 단위로 접는다. 한글은\n * 낱말 사이에만 공백이 있어 글자 단위 접기가 실제로 필요하다\n * (「지역내총생산」 한 낱말이 벌써 여섯 글자다).\n *\n * 폭이 0 이하면 접지 않는다 — 부르는 쪽이 재기 전이라는 뜻이다.\n */\nexport function wrapToWidth(\n ctx: CanvasRenderingContext2D,\n text: string,\n maxWidth: number,\n): string[] {\n if (maxWidth <= 0 || !text) return [text];\n if (ctx.measureText(text).width <= maxWidth) return [text];\n\n const lines: string[] = [];\n let line = '';\n const flush = () => { if (line) { lines.push(line); line = ''; } };\n\n for (const word of text.split(/(\\s+)/)) {\n if (/^\\s+$/.test(word)) {\n if (line) line += word;\n continue;\n }\n const trial = line ? line + word : word;\n if (ctx.measureText(trial).width <= maxWidth) { line = trial; continue; }\n flush();\n if (ctx.measureText(word).width <= maxWidth) { line = word; continue; }\n // 한 낱말이 통째로 넘친다 — 글자 단위로 자른다\n for (const ch of word) {\n const t2 = line + ch;\n if (line && ctx.measureText(t2).width > maxWidth) { flush(); line = ch; }\n else line = t2;\n }\n }\n flush();\n return lines.length > 0 ? lines : [text];\n}\n\n/** 여러 줄 중 가장 넓은 줄의 폭 */\nexport function widestLine(ctx: CanvasRenderingContext2D, lines: string[]): number {\n if (lines.length === 0) return 0;\n return Math.max(...lines.map((l) => ctx.measureText(l).width));\n}\n\n/**\n * 여러 줄을 (x, y)를 **세로 가운데**로 삼아 그린다.\n * 한 줄이면 `fillText(text, x, y)` 와 좌표가 정확히 같다.\n */\nexport function fillLines(\n ctx: CanvasRenderingContext2D,\n lines: string[],\n x: number,\n y: number,\n lineHeight: number,\n) {\n const top = y - ((lines.length - 1) * lineHeight) / 2;\n for (let i = 0; i < lines.length; i++) ctx.fillText(lines[i], x, top + i * lineHeight);\n}\n\n/**\n * 여백에 떠 있는 글자를 그린다.\n *\n * 캔버스보다 넓으면 글꼴을 줄이고, 가장자리를 넘으면 안쪽으로 민다. **옆으로\n * 밀어도 자료를 가리지 않는 자리**에만 쓴다 — 축 위 단위, 축 아래 이름처럼\n * 플롯 바깥 여백에 놓이는 글자다. 이미 안에 들어 있으면 좌표가 그대로다.\n */\nexport function drawFloatingLabel(\n ctx: CanvasRenderingContext2D,\n text: string,\n x: number,\n y: number,\n canvasW: number,\n canvasH: number,\n fontSize: number,\n makeFont: (size: number) => string,\n) {\n if (!text) return;\n ctx.font = makeFont(shrinkToWidth(ctx, [text], fontSize, canvasW - EDGE * 2, makeFont));\n const at = nudgeInside(ctx, text, x, y, canvasW, canvasH);\n ctx.fillText(text, at.x, at.y);\n}\n\n/**\n * 여러 줄 묶음(가운데가 `x, y`)을 캔버스 안으로 밀어 넣은 기준점.\n * `ctx.font`·`textAlign`·`textBaseline` 이 그릴 때와 같아야 한다.\n */\nexport function nudgeLinesInside(\n ctx: CanvasRenderingContext2D,\n lines: string[],\n x: number,\n y: number,\n lineHeight: number,\n canvasW: number,\n canvasH: number,\n margin: number = EDGE,\n): { x: number; y: number } {\n if (lines.length === 0) return { x, y };\n const es = lines.map((l) => textExtent(ctx, l));\n const half = ((lines.length - 1) * lineHeight) / 2;\n const left = Math.max(...es.map((e) => e.left));\n const right = Math.max(...es.map((e) => e.right));\n const up = half + Math.max(...es.map((e) => e.up));\n const down = half + Math.max(...es.map((e) => e.down));\n let nx = x;\n let ny = y;\n if (x - left < margin) nx = margin + left;\n else if (x + right > canvasW - margin) nx = canvasW - margin - right;\n if (y - up < margin) ny = margin + up;\n else if (y + down > canvasH - margin) ny = canvasH - margin - down;\n return { x: nx, y: ny };\n}\n\n/**\n * 여러 줄 묶음의 **세로 가운데**를 캔버스 안으로 민 값.\n * `ctx.font`·`textBaseline` 이 그릴 때와 같아야 한다.\n */\nexport function clampLinesMiddle(\n ctx: CanvasRenderingContext2D,\n lines: string[],\n mid: number,\n lineHeight: number,\n canvasH: number,\n margin: number = EDGE,\n): number {\n if (lines.length === 0) return mid;\n const half = ((lines.length - 1) * lineHeight) / 2;\n const up = Math.max(...lines.map((l) => textExtent(ctx, l).up));\n const down = Math.max(...lines.map((l) => textExtent(ctx, l).down));\n if (mid - half - up < margin) return margin + half + up;\n if (mid + half + down > canvasH - margin) return canvasH - margin - half - down;\n return mid;\n}\n\n/**\n * `fits(size)` 가 참이 되는 가장 큰 크기를 찾는다 (`lo`~`hi` 사이, 이분 탐색).\n *\n * 방사형의 반지름·정육면체의 배율처럼 **키우면 글자가 바깥으로 나가는** 값에\n * 쓴다. `hi` 에서 이미 들어가면 `hi` 를 그대로 돌려주므로, 멀쩡한 그림은\n * 한 픽셀도 움직이지 않는다.\n */\nexport function largestFitting(\n lo: number,\n hi: number,\n fits: (v: number) => boolean,\n steps: number = 24,\n): number {\n if (fits(hi)) return hi;\n let a = lo;\n let b = hi;\n for (let i = 0; i < steps; i++) {\n const mid = (a + b) / 2;\n if (fits(mid)) a = mid;\n else b = mid;\n }\n return a;\n}\n","// © 2026 김용현\nimport { type Padding, getFont } from './renderer';\nimport { labelStride, widestLabel } from './labels';\nimport { EDGE, nudgeInside, shrinkToWidth } from './fit';\n\ninterface AxisOptions {\n ctx: CanvasRenderingContext2D;\n padding: Padding;\n width: number;\n height: number;\n fontFamily: 'serif' | 'sans' | 'custom';\n customFont?: string;\n tickFontSize: number;\n labelFontSize: number;\n}\n\ninterface YAxisParams extends AxisOptions {\n min: number;\n max: number;\n step: number;\n label: string;\n side: 'left' | 'right';\n drawGrid?: boolean;\n}\n\ninterface XAxisParams extends AxisOptions {\n labels: string[];\n indices?: number[];\n}\n\nconst plotArea = (p: Padding, w: number, h: number) => ({\n x: p.left,\n y: p.top,\n w: w - p.left - p.right,\n h: h - p.top - p.bottom,\n});\n\nexport function drawYAxis({\n ctx, padding, width, height,\n min, max, step, label, side,\n fontFamily, customFont, tickFontSize, labelFontSize,\n drawGrid = false,\n}: YAxisParams) {\n const plot = plotArea(padding, width, height);\n const x = side === 'left' ? plot.x : plot.x + plot.w;\n\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 2;\n ctx.beginPath();\n ctx.moveTo(x, plot.y);\n ctx.lineTo(x, plot.y + plot.h);\n ctx.stroke();\n\n // 눈금 — 모두 bold\n ctx.fillStyle = '#000';\n ctx.font = getFont(tickFontSize, fontFamily, customFont, 'bold');\n ctx.textBaseline = 'middle';\n ctx.textAlign = side === 'left' ? 'right' : 'left';\n\n // 눈금 값 배열 생성: min부터 step 간격, max는 항상 포함\n const ticks: number[] = [];\n for (let val = min; val < max - step * 1e-9; val += step) {\n ticks.push(Math.abs(val) < 1e-9 ? 0 : val);\n }\n if (ticks.length === 0 || Math.abs(ticks[ticks.length - 1] - max) > 1e-9) {\n ticks.push(max);\n }\n\n const stride = labelStride(plot.h / Math.max(1, ticks.length - 1), tickFontSize * 1.1);\n\n for (let i = 0; i < ticks.length; i++) {\n const val = ticks[i];\n const y = plot.y + plot.h - ((val - min) / (max - min)) * plot.h;\n\n // 눈금 선\n ctx.lineWidth = 1.5;\n ctx.beginPath();\n if (side === 'left') {\n ctx.moveTo(x - 6, y);\n ctx.lineTo(x, y);\n } else {\n ctx.moveTo(x, y);\n ctx.lineTo(x + 6, y);\n }\n ctx.stroke();\n\n // 격자선\n if (drawGrid && i > 0 && i < ticks.length - 1) {\n ctx.save();\n ctx.strokeStyle = '#ccc';\n ctx.lineWidth = 0.5;\n ctx.setLineDash([4, 4]);\n ctx.beginPath();\n ctx.moveTo(plot.x, y);\n ctx.lineTo(plot.x + plot.w, y);\n ctx.stroke();\n ctx.restore();\n }\n\n // 숫자 — 서로 붙으면 몇 개 걸러 그린다 (눈금선은 그대로)\n if (i % stride === 0) {\n const tx = side === 'left' ? x - 12 : x + 12;\n ctx.fillText(formatTick(val), tx, y);\n }\n }\n\n // 축 라벨 (축 상단, 숫자 열에 맞춤)\n //\n // 이 글자는 플롯 **위 여백**에 떠 있다 — 옆으로 밀거나 조금 내려도 자료를\n // 가리지 않는다. 그래서 캔버스를 벗어날 때는 플롯을 줄이는 대신 안으로 민다.\n // (「(°C)」의 여는 괄호가 위로 3.2px, 「월평균 기온(°C)」 같은 긴 이름이\n // 왼쪽으로 72.9px 넘던 자리다.)\n if (label) {\n ctx.save();\n const makeFont = (size: number) => getFont(size, fontFamily, customFont, 'bold');\n ctx.fillStyle = '#000';\n ctx.textBaseline = 'bottom';\n ctx.textAlign = side === 'left' ? 'right' : 'left';\n // 캔버스보다 넓은 이름은 밀어서 될 일이 아니다 — 글꼴부터 줄인다\n ctx.font = makeFont(shrinkToWidth(ctx, [label], labelFontSize, width - EDGE * 2, makeFont));\n const labelX = side === 'left' ? x - 12 : x + 12;\n const at = nudgeInside(ctx, label, labelX, plot.y - tickFontSize * 0.5 - 8, width, height);\n ctx.fillText(label, at.x, at.y);\n ctx.restore();\n }\n}\n\nexport function drawXAxis({\n ctx, padding, width, height,\n labels, indices,\n fontFamily, customFont, tickFontSize,\n}: XAxisParams) {\n const plot = plotArea(padding, width, height);\n const y = plot.y + plot.h;\n\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 2;\n ctx.beginPath();\n ctx.moveTo(plot.x, y);\n ctx.lineTo(plot.x + plot.w, y);\n ctx.stroke();\n\n ctx.fillStyle = '#000';\n ctx.font = getFont(tickFontSize, fontFamily, customFont, 'bold');\n ctx.textAlign = 'center';\n ctx.textBaseline = 'top';\n\n const showIndices = indices ?? labels.map((_, i) => i);\n const slotWidth = plot.w / labels.length;\n // 범주 이름이 서로 붙으면 몇 개 걸러 그린다 (눈금선은 그대로)\n const stride = labelStride(slotWidth, widestLabel(ctx, labels));\n let shown = 0;\n\n for (const i of showIndices) {\n if (i < 0 || i >= labels.length) continue;\n const cx = plot.x + slotWidth * i + slotWidth / 2;\n\n // 눈금선\n ctx.lineWidth = 1.5;\n ctx.beginPath();\n ctx.moveTo(cx, y);\n ctx.lineTo(cx, y + 6);\n ctx.stroke();\n\n // 라벨\n if (shown % stride === 0) ctx.fillText(labels[i], cx, y + 12);\n shown++;\n }\n}\n\nfunction formatTick(val: number): string {\n if (Number.isInteger(val)) return val.toString();\n return val.toFixed(1);\n}\n","// © 2026 김용현\n// 공통 범례 렌더링\nimport { type LegendPosition, type InsideLegendCorner } from '../types/index';\n\nexport interface LegendItem {\n type: 'rect' | 'circle' | 'line';\n fillStyle: string | CanvasPattern;\n strokeStyle?: string;\n label: string;\n /** 밝은 채움일 때 아이콘에 검정 테두리 표시 */\n bordered?: boolean;\n /** 선 아이콘(line)의 대시 패턴 — 미지정 시 실선 */\n dash?: number[];\n /** 선 아이콘(line)의 굵기 — 미지정 시 2.5 */\n lineWidth?: number;\n /** 선 아이콘 가운데 점의 모양 — 미지정 시 원 */\n marker?: 'circle' | 'square';\n}\n\nexport interface InsideLegendParams {\n ctx: CanvasRenderingContext2D;\n items: LegendItem[];\n /** 1순위 모서리. 여기가 막히면 나머지 셋을 차례로 본다. */\n corner: InsideLegendCorner;\n plotX: number;\n plotY: number;\n plotW: number;\n plotH: number;\n /** 캔버스 너비 — 상자를 여기 안에 가둔다 */\n canvasW: number;\n /** 캔버스 높이 — 상자를 여기 안에 가둔다 */\n canvasH: number;\n fontSize: number;\n /** 글꼴 문자열 — 그래프 글꼴을 그대로 쓰라고 호출부가 넘긴다 */\n font: string;\n /** 이 사각형들과 겹치는 모서리는 피한다 (막대·점 등이 가려지면 안 된다) */\n avoid?: { x0: number; y0: number; x1: number; y1: number }[];\n}\n\n/**\n * 범례 글꼴을 줄일 수 있는 바닥.\n *\n * 이름을 줄임표로 자르는 선택지는 **없다**. 시험지 그림에서 「서울특별시」와\n * 「서울특별시 강남구」가 같은 「서울특별…」로 보이면 문항 자체가 틀린다.\n * 그래서 자리가 모자라면 (1) 줄을 늘리고 (2) 글꼴을 이 값까지 줄인다.\n */\nconst MIN_LEGEND_FONT = 11;\n\n/** 글꼴 문자열의 px 크기만 바꾼다 (`bold 20px \"명조\"` → `bold 16px \"명조\"`) */\nfunction withFontSize(font: string, size: number): string {\n return font.replace(/(\\d*\\.?\\d+)px/, `${Math.round(size * 100) / 100}px`);\n}\n\n/**\n * 범례를 플롯 **안쪽** 모서리에 작은 상자로 그린다.\n *\n * 시험지 그래프는 범례를 그림 바깥이 아니라 빈 구석에 넣는 경우가 많다.\n * `avoid`를 주면 자료를 덮지 않는 모서리를 골라 쓴다 — 어떤 값이 들어와도\n * 막대나 점이 가려지면 안 되기 때문이다.\n */\nexport function drawInsideLegend({\n ctx, items, corner, plotX, plotY, plotW, plotH, canvasW, canvasH, fontSize, font, avoid = [],\n}: InsideLegendParams): void {\n if (items.length === 0) return;\n\n ctx.save();\n ctx.font = font;\n\n const padX = 8;\n const margin = 8;\n // 상자가 플롯보다 넓어지면 그 자리에서 시작점이 플롯 밖으로 밀려난다.\n // 이름을 자르는 대신 글꼴을 줄여 폭을 맞춘다.\n const sizeOf = (fs: number) => {\n // 줄이지 않은 경우엔 받은 글꼴 문자열을 그대로 쓴다 (같은 값을 다시 조립하지 않는다)\n ctx.font = fs === fontSize ? font : withFontSize(font, fs);\n const swatch = fs * 0.95;\n const maxIconW = Math.max(...items.map((i) => (i.type === 'line' ? swatch * 2 : swatch)));\n const maxLabelW = Math.max(...items.map((i) => ctx.measureText(i.label).width));\n return { swatch, maxIconW, boxW: padX * 2 + maxIconW + 8 + maxLabelW, rowH: fs * 1.5 };\n };\n let fs = fontSize;\n let size = sizeOf(fs);\n const avail = plotW - margin * 2;\n while (fs > MIN_LEGEND_FONT && size.boxW > avail) {\n fs = Math.max(MIN_LEGEND_FONT, fs - 0.5);\n size = sizeOf(fs);\n }\n const { swatch, maxIconW, boxW, rowH } = size;\n const boxH = rowH * items.length + 8;\n\n const spotAt = (c: InsideLegendCorner) => ({\n x: c.endsWith('right') ? plotX + plotW - boxW - margin : plotX + margin,\n y: c.startsWith('top') ? plotY + margin : plotY + plotH - boxH - margin,\n });\n const others: InsideLegendCorner[] = ['top-right', 'top-left', 'bottom-right', 'bottom-left'];\n const order = [corner, ...others.filter((c) => c !== corner)];\n const spots = order.map(spotAt);\n const clear = (s: { x: number; y: number }) => !avoid.some(\n (b) => b.x0 < s.x + boxW && b.x1 > s.x && b.y0 < s.y + boxH && b.y1 > s.y\n );\n const picked = spots.find(clear) ?? spots[0];\n // 글꼴을 바닥까지 줄여도 안 들어가는 경우가 남는다 — 그때도 캔버스 밖으로는 안 내보낸다\n const spot = {\n x: Math.max(1, Math.min(picked.x, canvasW - boxW - 1)),\n y: Math.max(1, Math.min(picked.y, canvasH - boxH - 1)),\n };\n\n ctx.fillStyle = '#fff';\n ctx.fillRect(spot.x, spot.y, boxW, boxH);\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 1;\n ctx.setLineDash([]);\n ctx.strokeRect(spot.x, spot.y, boxW, boxH);\n\n for (let i = 0; i < items.length; i++) {\n const item = items[i];\n const cy = spot.y + 4 + rowH * (i + 0.5);\n drawInsideIcon(ctx, item, spot.x + padX, cy, swatch);\n ctx.fillStyle = '#000';\n ctx.textAlign = 'left';\n ctx.textBaseline = 'middle';\n ctx.fillText(item.label, spot.x + padX + maxIconW + 8, cy);\n }\n ctx.restore();\n}\n\n/** 플롯 안 범례의 아이콘 — 상자 크기에 맞춰 그린다 (바깥 범례의 고정 크기와 다르다) */\nfunction drawInsideIcon(\n ctx: CanvasRenderingContext2D,\n item: LegendItem,\n x: number,\n cy: number,\n size: number\n) {\n if (item.type === 'line') {\n const w = size * 2;\n ctx.strokeStyle = typeof item.fillStyle === 'string' ? item.fillStyle : '#000';\n ctx.lineWidth = item.lineWidth ?? 2;\n ctx.setLineDash(item.dash && item.dash.length > 0 ? item.dash : []);\n ctx.beginPath();\n ctx.moveTo(x, cy);\n ctx.lineTo(x + w, cy);\n ctx.stroke();\n ctx.setLineDash([]);\n\n ctx.fillStyle = item.fillStyle;\n const r = size * 0.3;\n if (item.marker === 'square') {\n ctx.fillRect(x + w / 2 - r, cy - r, r * 2, r * 2);\n } else {\n ctx.beginPath();\n ctx.arc(x + w / 2, cy, r, 0, Math.PI * 2);\n ctx.fill();\n }\n return;\n }\n\n ctx.fillStyle = item.fillStyle;\n ctx.fillRect(x, cy - size / 2, size, size);\n ctx.strokeStyle = item.strokeStyle ?? '#000';\n ctx.lineWidth = 1;\n ctx.strokeRect(x, cy - size / 2, size, size);\n}\n\nconst LINE_ICON_SIZE = 36;\n\ninterface LegendParams {\n ctx: CanvasRenderingContext2D;\n items: LegendItem[];\n position: LegendPosition;\n plotX: number;\n plotY: number;\n plotW: number;\n plotH: number;\n /** 캔버스 너비 — 상자를 여기 안에 가둔다 */\n canvasW: number;\n /** 캔버스 높이 — 상자를 여기 안에 가둔다 */\n canvasH: number;\n fontSize: number;\n /** 하단 범례의 plotH 아래 오프셋 (기본 50) */\n bottomOffset?: number;\n /** 우측 범례의 plotW 오른쪽 간격 (기본 20) */\n rightGap?: number;\n}\n\nconst LEGEND_FONT = \"'Noto Sans KR', sans-serif\";\nconst ICON_GAP = 10;\nconst BOX_PADDING = 12;\nconst ITEM_SPACING = 30;\n/** 두 줄 이상일 때 줄 사이 간격 */\nconst ROW_GAP = 6;\n\n/** 우측 범례일 때 필요한 추가 너비를 계산 */\nexport function measureLegendWidth(\n ctx: CanvasRenderingContext2D,\n labels: string[],\n fontSize: number,\n iconType: 'rect' | 'circle' | 'line' = 'rect'\n): number {\n ctx.save();\n ctx.font = `bold ${fontSize}px ${LEGEND_FONT}`;\n const iconSize = iconType === 'line' ? LINE_ICON_SIZE : 16;\n const iconGap = 10;\n const padding = 12;\n const maxW = Math.max(...labels.map((l) => iconSize + iconGap + ctx.measureText(l).width));\n ctx.restore();\n return maxW + padding * 2 + 30; // 박스 + 간격\n}\n\nexport interface BottomLegendRow {\n /** items 안에서의 자리 */\n index: number;\n width: number;\n}\n\nexport interface BottomLegendLayout {\n /** 실제로 쓸 글꼴 크기 — 항목 하나가 한 줄보다 넓을 때만 요청값보다 작아진다 */\n fontSize: number;\n lineHeight: number;\n /** 줄 사이 간격 */\n rowGap: number;\n boxH: number;\n rows: BottomLegendRow[][];\n}\n\n/**\n * 하단 범례를 상자 너비에 맞춰 **여러 줄로 접는다.**\n *\n * 예전에는 무조건 한 줄에 늘어놓고 가운데로 모았다. 항목이 상자보다 넓으면\n * 그 줄이 좌우로 똑같이 삐져나가 캔버스 밖에서 잘렸다 — 지명 두 개짜리\n * 방사형 그래프에서 실제로 그랬다. 이름을 줄임표로 자르는 선택지는 없으므로\n * (시험지에서 「서울특별시」와 「서울특별시 강남구」가 같아 보이면 안 된다)\n * 줄을 늘리고, 항목 하나가 통째로 한 줄보다 넓을 때만 글꼴을 줄인다.\n *\n * 한 줄로 끝나는 경우의 결과값은 예전 배치와 **정확히 같다** — 줄 간격과\n * 상자 높이가 줄 수에만 붙어 있기 때문이다.\n */\nexport function layoutBottomLegend(\n ctx: CanvasRenderingContext2D,\n labels: string[],\n iconWidths: number[],\n fontSize: number,\n boxW: number,\n opts: { iconGap?: number; padding?: number; spacing?: number; font?: string } = {},\n): BottomLegendLayout {\n const iconGap = opts.iconGap ?? ICON_GAP;\n const padding = opts.padding ?? BOX_PADDING;\n const spacing = opts.spacing ?? ITEM_SPACING;\n const fontOf = (fs: number) => (opts.font ? withFontSize(opts.font, fs) : `bold ${fs}px ${LEGEND_FONT}`);\n const inner = Math.max(1, boxW - padding * 2);\n\n ctx.save();\n const widthsAt = (fs: number) => {\n ctx.font = fontOf(fs);\n return labels.map((l, i) => iconWidths[i] + iconGap + ctx.measureText(l).width);\n };\n let fs = fontSize;\n let widths = widthsAt(fs);\n while (fs > MIN_LEGEND_FONT && Math.max(...widths) > inner) {\n fs = Math.max(MIN_LEGEND_FONT, fs - 0.5);\n widths = widthsAt(fs);\n }\n ctx.restore();\n\n const rows: BottomLegendRow[][] = [];\n let row: BottomLegendRow[] = [];\n let rowW = 0;\n for (let i = 0; i < labels.length; i++) {\n const add = row.length === 0 ? widths[i] : spacing + widths[i];\n if (row.length > 0 && rowW + add > inner) {\n rows.push(row);\n row = [];\n rowW = 0;\n }\n row.push({ index: i, width: widths[i] });\n rowW += row.length === 1 ? widths[i] : spacing + widths[i];\n }\n if (row.length > 0) rows.push(row);\n\n const lineHeight = fs + 8;\n return {\n fontSize: fs,\n lineHeight,\n rowGap: ROW_GAP,\n rows,\n boxH: rows.length * lineHeight + (rows.length - 1) * ROW_GAP + padding * 2,\n };\n}\n\n/**\n * 하단 범례가 플롯 아래로 실제로 차지하는 높이 (오프셋 포함).\n *\n * 여백을 잡는 쪽이 이 값을 **미리** 물어봐야 한다. 범례 상자의 높이는 줄\n * 수에 따라 달라지는데, 여백을 상수로 잡아 두면 줄이 늘어난 만큼 상자가\n * 캔버스 밖으로 밀려나기 때문이다.\n */\nexport function measureBottomLegend(\n ctx: CanvasRenderingContext2D,\n labels: string[],\n fontSize: number,\n plotW: number,\n iconType: 'rect' | 'circle' | 'line' | ('rect' | 'circle' | 'line')[] = 'rect',\n bottomOffset = 50,\n): number {\n if (labels.length === 0) return 0;\n const sizeOf = (t: 'rect' | 'circle' | 'line') => (t === 'line' ? LINE_ICON_SIZE : 16);\n const iconWidths = labels.map((_, i) =>\n sizeOf(Array.isArray(iconType) ? (iconType[i] ?? 'rect') : iconType));\n const { boxH } = layoutBottomLegend(ctx, labels, iconWidths, fontSize, plotW);\n return bottomOffset + boxH + 2;\n}\n\nexport function drawLegend({\n ctx, items, position,\n plotX, plotY, plotW, plotH,\n canvasW, canvasH,\n fontSize,\n bottomOffset = 50,\n rightGap = 20,\n}: LegendParams): number {\n if (items.length === 0) return 0;\n\n ctx.save();\n ctx.font = `bold ${fontSize}px ${LEGEND_FONT}`;\n\n const iconGap = ICON_GAP;\n const padding = BOX_PADDING;\n const lineHeight = fontSize + 8;\n const iconWidthOf = (item: LegendItem) => (item.type === 'line' ? LINE_ICON_SIZE : 16);\n\n // 각 아이템 텍스트 너비 측정\n const itemWidths = items.map((item) => iconWidthOf(item) + iconGap + ctx.measureText(item.label).width);\n\n if (position === 'bottom') {\n // 하단: 그래프 너비 박스, 아이템 가운데 모아서 배치.\n // 한 줄에 다 못 넣으면 줄을 늘린다 (이름은 자르지 않는다).\n const boxW = plotW;\n const layout = layoutBottomLegend(\n ctx, items.map((i) => i.label), items.map(iconWidthOf), fontSize, boxW);\n const boxH = layout.boxH;\n const boxX = plotX;\n // 호출부가 잡아 둔 아래 여백이 모자라도 캔버스 밖으로는 내보내지 않는다\n const boxY = Math.max(0, Math.min(plotY + plotH + bottomOffset, canvasH - boxH - 1));\n\n // 박스\n ctx.strokeStyle = '#888';\n ctx.lineWidth = 1.5;\n ctx.fillStyle = '#fff';\n ctx.beginPath();\n ctx.roundRect(boxX, boxY, boxW, boxH, 0);\n ctx.fill();\n ctx.stroke();\n\n // 아이템들 — 줄마다 가운데 정렬\n for (let r = 0; r < layout.rows.length; r++) {\n const row = layout.rows[r];\n const rowW = row.reduce((a, it) => a + it.width, 0) + ITEM_SPACING * (row.length - 1);\n let cx = boxX + (boxW - rowW) / 2;\n const cy = boxY + padding + r * (layout.lineHeight + layout.rowGap) + layout.lineHeight / 2;\n for (const { index, width } of row) {\n const item = items[index];\n const iSize = iconWidthOf(item);\n drawIcon(ctx, item, cx, cy, iSize);\n ctx.font = `bold ${layout.fontSize}px ${LEGEND_FONT}`;\n ctx.fillStyle = '#000';\n ctx.textAlign = 'left';\n ctx.textBaseline = 'middle';\n ctx.fillText(item.label, cx + iSize + iconGap, cy);\n cx += width + ITEM_SPACING;\n }\n }\n ctx.restore();\n return boxY + boxH;\n } else {\n // 우측: 그래프 바로 옆, 하단 정렬\n const itemGap = 10;\n const maxItemW = Math.max(...itemWidths);\n const boxW = maxItemW + padding * 2;\n const boxH = lineHeight * items.length + itemGap * (items.length - 1) + padding * 2;\n // 호출부가 잡아 둔 오른쪽 여백이 모자라도 캔버스 밖으로는 내보내지 않는다\n const boxX = Math.max(0, Math.min(plotX + plotW + rightGap, canvasW - boxW - 1));\n const boxY = Math.max(0, Math.min(plotY + plotH - boxH, canvasH - boxH - 1));\n\n // 박스\n ctx.strokeStyle = '#888';\n ctx.lineWidth = 1.5;\n ctx.fillStyle = '#fff';\n ctx.beginPath();\n ctx.roundRect(boxX, boxY, boxW, boxH, 0);\n ctx.fill();\n ctx.stroke();\n\n // 아이템들\n let cy = boxY + padding + lineHeight / 2;\n for (const item of items) {\n const ix = boxX + padding;\n const iSize = iconWidthOf(item);\n drawIcon(ctx, item, ix, cy, iSize);\n ctx.font = `bold ${fontSize}px ${LEGEND_FONT}`;\n ctx.fillStyle = '#000';\n ctx.textAlign = 'left';\n ctx.textBaseline = 'middle';\n ctx.fillText(item.label, ix + iSize + iconGap, cy);\n cy += lineHeight + itemGap;\n }\n ctx.restore();\n return 0;\n }\n}\n\nfunction drawIcon(\n ctx: CanvasRenderingContext2D,\n item: LegendItem,\n x: number,\n cy: number,\n size: number\n) {\n if (item.type === 'rect') {\n ctx.fillStyle = item.fillStyle;\n ctx.fillRect(x, cy - size / 2, size, size);\n if (item.bordered) {\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 1;\n ctx.strokeRect(x, cy - size / 2, size, size);\n } else if (item.strokeStyle) {\n ctx.strokeStyle = item.strokeStyle;\n ctx.lineWidth = 1;\n ctx.strokeRect(x, cy - size / 2, size, size);\n }\n } else if (item.type === 'circle') {\n ctx.fillStyle = item.fillStyle;\n ctx.beginPath();\n ctx.arc(x + size / 2, cy, size / 2.5, 0, Math.PI * 2);\n ctx.fill();\n } else if (item.type === 'line') {\n ctx.save();\n ctx.strokeStyle = item.fillStyle;\n ctx.lineWidth = item.lineWidth ?? 2.5;\n if (item.dash && item.dash.length > 0) ctx.setLineDash(item.dash);\n ctx.beginPath();\n ctx.moveTo(x, cy);\n ctx.lineTo(x + size, cy);\n ctx.stroke();\n ctx.restore();\n ctx.fillStyle = item.fillStyle;\n ctx.beginPath();\n ctx.arc(x + size / 2, cy, 3.5, 0, Math.PI * 2);\n ctx.fill();\n }\n}\n","// © 2026 김용현\n// Canvas 패턴 생성 유틸리티\n\nexport type PatternType =\n | 'diagonal'\n | 'grid'\n | 'diagonalGrid'\n | 'dot'\n | 'dotReverse'\n | 'vertical'\n | 'horizontal';\n\nconst TILE = 10;\n\n/**\n * 패턴 타일용 작은 캔버스를 만든다.\n *\n * 브라우저면 `document`로 만들고, 없으면(서버 렌더·테스트) 지금 그리고 있는\n * 캔버스와 **같은 종류**를 하나 더 만든다. `ctx.createPattern`은 같은 백엔드의\n * 캔버스만 받으므로 이렇게 해야 어느 쪽에서든 패턴이 나온다.\n */\nfunction createTileCanvas(ctx: CanvasRenderingContext2D, size: number): HTMLCanvasElement {\n if (typeof document !== 'undefined') {\n const c = document.createElement('canvas');\n c.width = size;\n c.height = size;\n return c;\n }\n const Ctor = (ctx.canvas as unknown as {\n constructor: new (w: number, h: number) => HTMLCanvasElement;\n }).constructor;\n return new Ctor(size, size);\n}\n\nfunction makeTile(\n ctx: CanvasRenderingContext2D,\n draw: (pctx: CanvasRenderingContext2D, s: number) => void\n): HTMLCanvasElement {\n const c = createTileCanvas(ctx, TILE);\n const p = c.getContext('2d')!;\n // 기본 흰색 배경\n p.fillStyle = '#fff';\n p.fillRect(0, 0, TILE, TILE);\n draw(p, TILE);\n return c;\n}\n\nfunction createPatternCanvas(ctx: CanvasRenderingContext2D, type: PatternType): HTMLCanvasElement {\n switch (type) {\n case 'diagonal':\n return makeTile(ctx, (p, s) => {\n p.strokeStyle = '#000';\n p.lineWidth = 1.5;\n p.beginPath();\n // 사선 (/) 패턴 — 타일 이음새 처리\n p.moveTo(0, s);\n p.lineTo(s, 0);\n p.moveTo(-s * 0.5, s * 0.5);\n p.lineTo(s * 0.5, -s * 0.5);\n p.moveTo(s * 0.5, s * 1.5);\n p.lineTo(s * 1.5, s * 0.5);\n p.stroke();\n });\n\n case 'grid':\n return makeTile(ctx, (p, s) => {\n p.strokeStyle = '#000';\n p.lineWidth = 1.2;\n p.beginPath();\n p.moveTo(s / 2, 0);\n p.lineTo(s / 2, s);\n p.moveTo(0, s / 2);\n p.lineTo(s, s / 2);\n p.stroke();\n });\n\n case 'diagonalGrid':\n return makeTile(ctx, (p, s) => {\n p.strokeStyle = '#000';\n p.lineWidth = 1.2;\n p.beginPath();\n p.moveTo(0, 0);\n p.lineTo(s, s);\n p.moveTo(s, 0);\n p.lineTo(0, s);\n p.stroke();\n });\n\n case 'dot':\n return makeTile(ctx, (p, s) => {\n p.fillStyle = '#000';\n p.beginPath();\n p.arc(s / 2, s / 2, 1.8, 0, Math.PI * 2);\n p.fill();\n });\n\n case 'dotReverse':\n return makeTile(ctx, (p, s) => {\n p.fillStyle = '#333';\n p.fillRect(0, 0, s, s);\n p.fillStyle = '#fff';\n p.beginPath();\n p.arc(s / 2, s / 2, 1.8, 0, Math.PI * 2);\n p.fill();\n });\n\n case 'vertical':\n return makeTile(ctx, (p, s) => {\n p.strokeStyle = '#000';\n p.lineWidth = 1.5;\n p.beginPath();\n p.moveTo(s / 2, 0);\n p.lineTo(s / 2, s);\n p.stroke();\n });\n\n case 'horizontal':\n return makeTile(ctx, (p, s) => {\n p.strokeStyle = '#000';\n p.lineWidth = 1.5;\n p.beginPath();\n p.moveTo(0, s / 2);\n p.lineTo(s, s / 2);\n p.stroke();\n });\n }\n}\n\n// ── 누적 차트 채움 시스템 ─────────────────────────────\n\n// 항목 1~3: 단색 그레이스케일, 항목 4: 흰색, 항목 5+: 패턴\nconst SOLID_FILLS = ['#333', '#999', '#666', '#fff'];\n\nconst PATTERN_ORDER: PatternType[] = [\n 'diagonal',\n 'grid',\n 'diagonalGrid',\n 'dot',\n 'dotReverse',\n 'vertical',\n 'horizontal',\n];\n\n// 컨텍스트별 패턴 캐시\nconst cache = new WeakMap<CanvasRenderingContext2D, Map<PatternType, CanvasPattern>>();\n\nfunction getCached(ctx: CanvasRenderingContext2D, type: PatternType): CanvasPattern {\n let m = cache.get(ctx);\n if (!m) {\n m = new Map();\n cache.set(ctx, m);\n }\n let pat = m.get(type);\n if (!pat) {\n pat = ctx.createPattern(createPatternCanvas(ctx, type), 'repeat')!;\n m.set(type, pat);\n }\n return pat;\n}\n\n/** 누적 차트의 index번째 항목 채움값 반환 */\nexport function getStackedFill(\n ctx: CanvasRenderingContext2D,\n index: number\n): string | CanvasPattern {\n if (index < SOLID_FILLS.length) return SOLID_FILLS[index];\n const pi = (index - SOLID_FILLS.length) % PATTERN_ORDER.length;\n return getCached(ctx, PATTERN_ORDER[pi]);\n}\n\n/** 채움 지정값에서 패턴을 가리키는 접두사 — 예: `'pattern:diagonal'` */\nexport const PATTERN_FILL_PREFIX = 'pattern:';\n\n/**\n * 채움 지정값(문자열)을 실제 채움으로 바꾼다.\n *\n * `'pattern:diagonal'` 이면 사선 빗금 패턴을, 그 밖에는 색 문자열을 그대로 쓴다.\n * 색과 패턴을 한 문자열 타입으로 다루는 이유는 프로젝트 저장(JSON)에 그대로\n * 실려야 하기 때문이다 — CanvasPattern 은 직렬화할 수 없다.\n */\nexport function resolveFill(\n ctx: CanvasRenderingContext2D,\n value: string\n): string | CanvasPattern {\n if (!value.startsWith(PATTERN_FILL_PREFIX)) return value;\n const type = value.slice(PATTERN_FILL_PREFIX.length) as PatternType;\n return PATTERN_ORDER.includes(type) ? getCached(ctx, type) : value;\n}\n\n/** 그 채움이 밝아서 테두리를 그려야 하는가 (흰색·빗금 등) */\nexport function isLightFillValue(value: string): boolean {\n if (value.startsWith(PATTERN_FILL_PREFIX)) {\n return value.slice(PATTERN_FILL_PREFIX.length) !== 'dotReverse';\n }\n const hex = value.replace('#', '');\n if (hex.length !== 3 && hex.length !== 6) return false;\n const full = hex.length === 3 ? hex.split('').map((c) => c + c).join('') : hex;\n const r = parseInt(full.slice(0, 2), 16);\n const g = parseInt(full.slice(2, 4), 16);\n const b = parseInt(full.slice(4, 6), 16);\n if ([r, g, b].some(Number.isNaN)) return false;\n return (r * 299 + g * 587 + b * 114) / 1000 > 180;\n}\n\n/** 밝은 채움인지 (흰색/패턴) — 테두리·라벨색 결정용 */\nexport function isLightFill(index: number): boolean {\n return index >= 3 && !isDarkPattern(index);\n}\n\n/** 어두운 패턴인지 (dotReverse 등) */\nfunction isDarkPattern(index: number): boolean {\n if (index < SOLID_FILLS.length) return false;\n const pi = (index - SOLID_FILLS.length) % PATTERN_ORDER.length;\n return PATTERN_ORDER[pi] === 'dotReverse';\n}\n","// © 2026 김용현\nimport { type AbsBarGraphData, type GraphOptions } from '../types/index';\nimport { type Padding, clearCanvas, autoRange, getFont } from '../canvas/renderer';\nimport { drawYAxis } from '../canvas/axes';\nimport { drawTitle, drawSourceAndFootnote, labelStride, widestLabel } from '../canvas/labels';\nimport { drawLegend, drawInsideLegend, measureLegendWidth, measureBottomLegend } from '../canvas/legend';\nimport { getStackedFill, isLightFill, resolveFill, isLightFillValue } from '../canvas/patterns';\n\nexport function renderAbsBarGraph(\n ctx: CanvasRenderingContext2D,\n w: number,\n h: number,\n data: AbsBarGraphData,\n options: GraphOptions\n) {\n clearCanvas(ctx, w, h);\n\n const showLegend = options.showLegend;\n const legendPos = options.legendPosition;\n const legendW = (showLegend && legendPos === 'right' && !data.insideLegend)\n ? measureLegendWidth(ctx, data.seriesLabels, options.fontSize.dataLabel * 0.85 + 5)\n : 0;\n\n const isVertical = data.barDirection === 'vertical';\n const font = options.fontFamily;\n const customFont = options.customFont;\n const n = data.categories.length;\n const sCount = data.seriesLabels.length;\n\n // 2단 라벨(가로 전용) — 바깥 라벨이 들어갈 만큼 왼쪽 여백을 넓힌다\n const hasGroups = !isVertical && !!data.groups && data.groups.length > 0;\n let groupLabelW = 0;\n let catLabelW = 0;\n let unitW = 0;\n if (hasGroups || data.unitAdjacent || data.insideLegend) {\n ctx.save();\n ctx.font = getFont(options.fontSize.tick, font, customFont, 'bold');\n if (hasGroups) {\n groupLabelW = widestLabel(ctx, data.groups!.map((g) => g.label));\n catLabelW = widestLabel(ctx, data.categories.map((c) => c.label));\n }\n ctx.font = getFont(options.fontSize.axisLabel, font, customFont, 'bold');\n unitW = ctx.measureText(data.unit).width;\n ctx.restore();\n }\n\n const padRight = isVertical\n ? 60 + legendW\n : (data.unitAdjacent ? 40 + unitW : 160 + legendW);\n const padLeft = isVertical\n ? 130\n : (hasGroups ? 20 + groupLabelW + 14 + catLabelW + 12 : 100);\n // 범례가 몇 줄이 될지 먼저 재야 그만큼 아래 여백을 잡을 수 있다\n const legendReserve = (showLegend && legendPos === 'bottom' && !data.insideLegend)\n ? measureBottomLegend(ctx, data.seriesLabels, options.fontSize.dataLabel * 0.85 + 5, w - padLeft - padRight)\n : 0;\n\n const padding: Padding = {\n top: options.title ? 100 : 50,\n right: padRight,\n bottom: (() => {\n let b = isVertical ? 70 : 60;\n // 플롯 안에 범례를 그릴 때는 아래에 자리를 비워 둘 이유가 없다\n if (showLegend && legendPos === 'bottom' && !data.insideLegend) b += 60;\n b = Math.max(b, legendReserve);\n if (options.source) b += 30;\n b += options.footnotes.filter(f => f.trim()).length * 22;\n return b;\n })(),\n left: padLeft,\n };\n\n const plotX = padding.left;\n const plotY = padding.top;\n const plotW = w - padding.left - padding.right;\n const plotH = h - padding.top - padding.bottom;\n\n // 축 범위 계산\n const allValues: number[] = [0]; // 0을 항상 포함 (막대 기준선)\n for (const cat of data.categories) {\n if (data.stacked) {\n allValues.push(cat.values.reduce((a, b) => a + b, 0));\n } else {\n allValues.push(...cat.values);\n }\n }\n if (allValues.length <= 1) allValues.push(100);\n\n let axis = data.yRange.auto\n ? autoRange(allValues, 6)\n : {\n min: data.yRange.min,\n max: data.yRange.max,\n step: data.yRange.step || Math.max(1, Math.round((data.yRange.max - data.yRange.min) / 6)),\n };\n\n // 최댓값에 맞추기: 축 최댓값을 데이터 최댓값으로 설정\n if (data.yRange.auto && data.fitMax) {\n const dataMax = Math.max(...allValues);\n axis = { ...axis, max: dataMax };\n }\n\n /** 계열 채움 — seriesFills 를 줬으면 그 값(색 또는 패턴), 아니면 기본 팔레트 */\n const fillOf = (i: number): string | CanvasPattern => {\n const v = data.seriesFills?.[i];\n return v === undefined || v === null ? getStackedFill(ctx, i) : resolveFill(ctx, v);\n };\n\n // 플롯 안 범례가 막대를 덮지 않도록, 그린 막대 자리를 모아 둔다\n const barRects: { x0: number; y0: number; x1: number; y1: number }[] = [];\n const noteBar = (x: number, y: number, bw: number, bh: number) => {\n if (data.insideLegend) barRects.push({ x0: x, y0: y, x1: x + bw, y1: y + bh });\n };\n\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 2;\n\n if (isVertical) {\n // 사각 테두리\n ctx.beginPath();\n ctx.moveTo(plotX, plotY);\n ctx.lineTo(plotX, plotY + plotH);\n ctx.lineTo(plotX + plotW, plotY + plotH);\n ctx.lineTo(plotX + plotW, plotY);\n ctx.lineTo(plotX, plotY);\n ctx.stroke();\n\n // Y축 눈금\n drawYAxis({\n ctx, padding, width: w, height: h,\n min: axis.min, max: axis.max, step: axis.step,\n label: data.unit,\n side: 'left',\n fontFamily: font, customFont,\n tickFontSize: options.fontSize.tick,\n labelFontSize: options.fontSize.axisLabel,\n drawGrid: true,\n });\n\n // 막대 (플롯 영역 클리핑)\n ctx.save();\n ctx.beginPath();\n ctx.rect(plotX, plotY, plotW, plotH);\n ctx.clip();\n\n const catArea = plotW / n;\n const valToY = (v: number) => plotY + plotH - ((v - axis.min) / (axis.max - axis.min)) * plotH;\n // 막대가 어디서 시작하는가 — 기본은 축 바닥, zeroBaseline 이면 0\n const baseValue = data.zeroBaseline ? 0 : axis.min;\n const baseY = valToY(baseValue);\n if (data.stacked) {\n const barW = Math.min(catArea * 0.5, 80);\n for (let c = 0; c < n; c++) {\n const cx = plotX + catArea * c + catArea / 2;\n // 누적 막대는 원래부터 0에서 쌓는다 — zeroBaseline 과 무관하다\n let cumVal = 0;\n for (let s = 0; s < sCount; s++) {\n const val = data.categories[c].values[s] || 0;\n const y = Math.min(valToY(cumVal + val), valToY(cumVal));\n const barH = Math.abs(valToY(cumVal) - valToY(cumVal + val));\n\n ctx.fillStyle = fillOf(s);\n ctx.fillRect(cx - barW / 2, y, barW, barH);\n noteBar(cx - barW / 2, y, barW, barH);\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 0.8;\n ctx.strokeRect(cx - barW / 2, y, barW, barH);\n\n if (options.showDataLabels && barH > options.fontSize.dataLabel) {\n ctx.fillStyle = isLightFill(s) ? '#000' : '#fff';\n ctx.font = getFont(options.fontSize.dataLabel * 0.8, font, customFont, 'bold');\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(String(val), cx, y + barH / 2);\n }\n cumVal += val;\n }\n }\n } else {\n // 그룹 막대\n const groupW = Math.min(catArea * 0.7, sCount * 60);\n const barW = groupW / sCount;\n for (let c = 0; c < n; c++) {\n const groupStart = plotX + catArea * c + (catArea - groupW) / 2;\n for (let s = 0; s < sCount; s++) {\n const val = data.categories[c].values[s] || 0;\n const by = Math.min(valToY(val), baseY);\n const barH = Math.abs(valToY(val) - baseY);\n const bx = groupStart + barW * s;\n\n ctx.fillStyle = fillOf(s);\n ctx.fillRect(bx, by, barW, barH);\n noteBar(bx, by, barW, barH);\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 0.8;\n ctx.strokeRect(bx, by, barW, barH);\n\n if (options.showDataLabels && barH > options.fontSize.dataLabel) {\n ctx.fillStyle = isLightFill(s) ? '#000' : '#fff';\n ctx.font = getFont(options.fontSize.dataLabel * 0.8, font, customFont, 'bold');\n ctx.textAlign = 'center';\n ctx.textBaseline = 'bottom';\n ctx.fillText(String(val), bx + barW / 2, by - 4);\n }\n }\n }\n }\n ctx.restore(); // 클리핑 해제\n\n // 0 기준선 — 막대 위에 그어야 가려지지 않는다\n if (data.zeroBaseline && baseY > plotY && baseY < plotY + plotH) {\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 1.5;\n ctx.setLineDash([]);\n ctx.beginPath();\n ctx.moveTo(plotX, baseY);\n ctx.lineTo(plotX + plotW, baseY);\n ctx.stroke();\n }\n\n // X축 라벨 (클리핑 밖에서).\n // 편차 그래프는 라벨이 플롯 아래가 아니라 0선 바로 아래에 붙는다.\n const labelY = data.categoryLabelAtBaseline ? baseY + 6 : plotY + plotH + 12;\n ctx.fillStyle = '#000';\n ctx.font = getFont(options.fontSize.tick, font, customFont, 'bold');\n ctx.textAlign = 'center';\n ctx.textBaseline = 'top';\n // 범주 이름이 서로 붙으면 몇 개 걸러 그린다\n const catStride = labelStride(catArea, widestLabel(ctx, data.categories.map((c) => c.label)));\n for (let c = 0; c < n; c++) {\n if (c % catStride !== 0) continue;\n const cx = plotX + catArea * c + catArea / 2;\n ctx.fillText(data.categories[c].label, cx, labelY);\n }\n } else {\n // 가로 — 사각 테두리\n ctx.beginPath();\n ctx.moveTo(plotX, plotY);\n ctx.lineTo(plotX, plotY + plotH);\n ctx.lineTo(plotX + plotW, plotY + plotH);\n ctx.lineTo(plotX + plotW, plotY);\n ctx.lineTo(plotX, plotY);\n ctx.stroke();\n\n // X축 눈금 (하단)\n const ticks: number[] = [];\n for (let v = axis.min; v < axis.max - axis.step * 1e-9; v += axis.step) {\n ticks.push(Math.abs(v) < 1e-9 ? 0 : v);\n }\n if (ticks.length === 0 || Math.abs(ticks[ticks.length - 1] - axis.max) > 1e-9) {\n ticks.push(axis.max);\n }\n\n ctx.font = getFont(options.fontSize.tick, font, customFont, 'bold');\n ctx.fillStyle = '#000';\n ctx.textAlign = 'center';\n ctx.textBaseline = 'top';\n let lastTickHalfW = 0;\n for (let i = 0; i < ticks.length; i++) {\n const v = ticks[i];\n const x = plotX + ((v - axis.min) / (axis.max - axis.min)) * plotW;\n ctx.lineWidth = 1;\n ctx.strokeStyle = '#000';\n ctx.beginPath();\n ctx.moveTo(x, plotY + plotH);\n ctx.lineTo(x, plotY + plotH + 5);\n ctx.stroke();\n ctx.fillText(formatTick(v), x, plotY + plotH + 10);\n if (i === ticks.length - 1) lastTickHalfW = ctx.measureText(formatTick(v)).width / 2;\n\n if (i > 0 && i < ticks.length - 1) {\n ctx.save();\n ctx.strokeStyle = '#ddd';\n ctx.lineWidth = 0.5;\n ctx.setLineDash([3, 3]);\n ctx.beginPath();\n ctx.moveTo(x, plotY);\n ctx.lineTo(x, plotY + plotH);\n ctx.stroke();\n ctx.restore();\n }\n }\n\n // 단위 — 기본은 축 오른쪽에 떨어뜨리고, unitAdjacent 면 마지막 눈금 숫자에 바로 붙인다\n ctx.font = getFont(options.fontSize.axisLabel, font, customFont, 'bold');\n ctx.fillStyle = '#000';\n ctx.textAlign = 'left';\n ctx.textBaseline = 'top';\n const unitX = data.unitAdjacent\n ? plotX + plotW + lastTickHalfW + 2\n : plotX + plotW + 30;\n ctx.fillText(data.unit, unitX, plotY + plotH + 10);\n\n // 막대 (플롯 영역 클리핑)\n ctx.save();\n ctx.beginPath();\n ctx.rect(plotX, plotY, plotW, plotH);\n ctx.clip();\n\n const catArea = plotH / n;\n // 그룹이 있으면 그룹 안 막대는 붙이고 그룹 사이는 벌린다.\n // 그룹이 없으면 아래 식이 예전과 같은 값(칸 가운데)을 준다.\n const barPitch = hasGroups ? catArea * 0.73 : catArea;\n const centerY: number[] = [];\n if (hasGroups) {\n let first = 0;\n for (const g of data.groups!) {\n const blockTop = plotY + catArea * first + (catArea - barPitch) * g.span / 2;\n for (let i = 0; i < g.span; i++) centerY.push(blockTop + barPitch * (i + 0.5));\n first += g.span;\n }\n }\n for (let c = centerY.length; c < n; c++) centerY.push(plotY + catArea * c + catArea / 2);\n\n const valToX = (v: number) => plotX + ((v - axis.min) / (axis.max - axis.min)) * plotW;\n if (data.stacked) {\n const barH = hasGroups ? Math.min(barPitch * 0.74, 80) : Math.min(catArea * 0.5, 80);\n for (let c = 0; c < n; c++) {\n const cy = centerY[c];\n let cumVal = 0;\n for (let s = 0; s < sCount; s++) {\n const val = data.categories[c].values[s] || 0;\n const bx = valToX(cumVal);\n const bw = valToX(cumVal + val) - bx;\n\n ctx.fillStyle = fillOf(s);\n ctx.fillRect(bx, cy - barH / 2, bw, barH);\n noteBar(bx, cy - barH / 2, bw, barH);\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 0.8;\n ctx.strokeRect(bx, cy - barH / 2, bw, barH);\n\n if (options.showDataLabels && bw > options.fontSize.dataLabel * 2) {\n ctx.fillStyle = isLightFill(s) ? '#000' : '#fff';\n ctx.font = getFont(options.fontSize.dataLabel * 0.8, font, customFont, 'bold');\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(String(val), bx + bw / 2, cy);\n }\n cumVal += val;\n }\n }\n } else {\n // 그룹 막대 (가로)\n const groupH = Math.min(catArea * 0.7, sCount * 60);\n const barH = groupH / sCount;\n for (let c = 0; c < n; c++) {\n const groupStart = centerY[c] - groupH / 2;\n for (let s = 0; s < sCount; s++) {\n const val = data.categories[c].values[s] || 0;\n const bx = valToX(axis.min);\n const bw = valToX(val) - bx;\n const by = groupStart + barH * s;\n\n ctx.fillStyle = fillOf(s);\n ctx.fillRect(bx, by, bw, barH);\n noteBar(bx, by, bw, barH);\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 0.8;\n ctx.strokeRect(bx, by, bw, barH);\n\n if (options.showDataLabels && bw > options.fontSize.dataLabel * 2) {\n ctx.fillStyle = isLightFill(s) ? '#000' : '#fff';\n ctx.font = getFont(options.fontSize.dataLabel * 0.8, font, customFont, 'bold');\n ctx.textAlign = 'left';\n ctx.textBaseline = 'middle';\n ctx.fillText(String(val), bx + bw + 4, by + barH / 2);\n }\n }\n }\n }\n ctx.restore(); // 클리핑 해제\n\n // Y축 라벨 (클리핑 밖에서)\n const catLabelX = hasGroups ? plotX - 12 : plotX - 10;\n for (let c = 0; c < n; c++) {\n ctx.fillStyle = '#000';\n ctx.font = getFont(options.fontSize.tick, font, customFont, 'bold');\n ctx.textAlign = 'right';\n ctx.textBaseline = 'middle';\n ctx.fillText(data.categories[c].label, catLabelX, centerY[c]);\n }\n\n // 바깥(2단) 라벨 — 그룹이 차지한 칸의 한가운데에 놓는다\n if (hasGroups) {\n let first = 0;\n for (const g of data.groups!) {\n const cy = plotY + catArea * first + (catArea * g.span) / 2;\n ctx.fillText(g.label, catLabelX - catLabelW - 14, cy);\n first += g.span;\n }\n }\n }\n\n // 제목\n drawTitle({ ctx, plotX, plotW, title: options.title, fontSize: options.fontSize.title, canvasWidth: w });\n\n // 범례\n if (showLegend && data.insideLegend) {\n // 시험지는 범례를 플롯 안쪽 모서리에 작은 상자로 둔다.\n // 막대와 겹치는 모서리는 피한다 — 어느 나라를 고르든 가려지면 안 된다.\n drawInsideLegend({\n ctx,\n items: data.seriesLabels.map((label, i) => ({\n type: 'rect' as const,\n fillStyle: fillOf(i),\n label,\n })),\n corner: data.insideLegend,\n plotX, plotY, plotW, plotH,\n canvasW: w, canvasH: h,\n fontSize: options.fontSize.dataLabel * 0.9,\n font: getFont(options.fontSize.dataLabel * 0.9, font, customFont, 'bold'),\n avoid: barRects,\n });\n } else if (showLegend) {\n const items = data.seriesLabels.map((label, i) => ({\n type: 'rect' as const,\n // 막대와 같은 채움을 쓴다 — seriesFills 를 준 경우 범례도 그 색이어야 한다\n fillStyle: fillOf(i),\n bordered: data.seriesFills?.[i] ? isLightFillValue(data.seriesFills[i]) : isLightFill(i),\n label,\n }));\n drawLegend({\n ctx, items, position: legendPos,\n plotX, plotY, plotW, plotH,\n canvasW: w, canvasH: h,\n fontSize: options.fontSize.dataLabel * 0.85 + 5,\n });\n }\n\n // 출처 + 각주\n drawSourceAndFootnote({ ctx, plotX, plotW, height: h, source: options.source, footnotes: options.footnotes, fontSize: options.fontSize.dataLabel, canvasWidth: w });\n}\n\nfunction formatTick(val: number): string {\n if (Number.isInteger(val)) return val.toString();\n return val.toFixed(1);\n}\n","// © 2026 김용현\n// 그래프 유형\nexport type GraphType =\n | 'guide'\n | 'climate'\n | 'pyramid'\n | 'ternary'\n | 'stacked'\n | 'absbar'\n | 'scatter'\n | 'hythergraph'\n | 'cube'\n | 'radar'\n | 'choropleth'\n | 'symbolmap'\n | 'isoline'\n | 'flowmap';\n\nexport const GRAPH_LABELS: Record<GraphType, string> = {\n guide: '사용법 보기',\n climate: '기후 그래프',\n pyramid: '인구 피라미드',\n ternary: '삼각 그래프',\n stacked: '100% 막대/원 그래프',\n absbar: '절댓값 막대 그래프',\n scatter: '산점도/버블 차트',\n hythergraph: '하이서그래프',\n cube: '정육면체 그래프',\n radar: '방사형 그래프',\n choropleth: '단계구분도',\n symbolmap: '도형표현도',\n isoline: '등치선도',\n flowmap: '유선도',\n};\n\nexport const MAP_GRAPH_TYPES: ReadonlyArray<GraphType> = ['choropleth', 'symbolmap', 'isoline', 'flowmap'];\nexport function isMapGraphType(t: GraphType): boolean {\n return (MAP_GRAPH_TYPES as ReadonlyArray<GraphType>).includes(t);\n}\n\nexport type LegendPosition = 'bottom' | 'right';\n\n/** 플롯 안쪽 범례를 놓을 모서리 */\nexport type InsideLegendCorner = 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left';\n\n// 공통 그래프 옵션\nexport interface GraphOptions {\n title: string;\n source: string;\n /**\n * 출처 줄 **왼쪽**에 함께 적는 글 (예: 자료 연도 `(2024)`).\n *\n * 주면 출처 줄이 각주 **아래**로 내려가고 왼쪽·오른쪽에 나눠 적힌다.\n * 미지정이면 기존대로 출처가 각주 위에 오른쪽 정렬로만 놓인다.\n */\n sourceLeft?: string;\n /**\n * 출처를 **마지막 각주와 같은 줄** 오른쪽 끝에 적는다 (각주는 왼쪽 끝).\n *\n * 시험지 관습이다 — 원본은 `* 1991~2020년의 평년값임.` 과 `(기상청)` 이\n * 한 줄에 좌우로 놓인다. 미지정이면 기존대로 출처가 각주 **위** 줄에 놓인다.\n * 각주가 없으면 아무 차이가 없다.\n */\n sourceInline?: boolean;\n footnotes: string[];\n fontFamily: 'serif' | 'sans' | 'custom';\n customFont: string;\n fontSize: {\n title: number;\n axisLabel: number;\n tick: number;\n dataLabel: number;\n };\n showDataLabels: boolean;\n showLegend: boolean;\n legendPosition: LegendPosition;\n legendLabel1: string;\n legendLabel2: string;\n}\n\n// PNG 내보내기 설정\nexport interface ExportSettings {\n mode: 'exam' | 'custom';\n width: number;\n height: number;\n scale: 1 | 2 | 3;\n}\n\nexport function createDefaultGraphOptions(): GraphOptions {\n return {\n title: '',\n source: '',\n footnotes: [''],\n fontFamily: 'serif',\n customFont: '',\n fontSize: {\n title: 36,\n axisLabel: 28,\n tick: 26,\n dataLabel: 22,\n },\n showDataLabels: false,\n showLegend: true,\n legendPosition: 'bottom',\n legendLabel1: '',\n legendLabel2: '',\n };\n}\n\nexport function createDefaultExportSettings(): ExportSettings {\n return {\n mode: 'exam',\n width: 800,\n height: 600,\n scale: 2,\n };\n}\n","// © 2026 김용현\n// 기후 그래프 데이터\nimport type { InsideLegendCorner } from './common';\n\nexport interface ClimateMonthData {\n temp: number;\n precip: number;\n}\n\nexport type MonthInterval = 12 | 4 | 2;\n\nexport interface ClimateGraphData {\n months: ClimateMonthData[];\n monthInterval: MonthInterval;\n tempLabel: string;\n precipLabel: string;\n tempRange: { min: number; max: number; auto: boolean; step?: number };\n precipRange: { min: number; max: number; auto: boolean; step?: number };\n}\n\n// 편차 그래프 모드\nexport type ClimateMode = 'normal' | 'deviationA' | 'deviationB';\n\n// 모드 A — 월별 편차 (시계열)\nexport interface DeviationAData {\n baseMonths: ClimateMonthData[]; // 월별 기준값\n months: ClimateMonthData[]; // 실제값, 편차는 렌더링 시 계산\n monthInterval: MonthInterval;\n tempLabel: string;\n precipLabel: string;\n /** step 을 주면 눈금 간격을 그 값으로 고정한다. 미지정이면 기존대로 자동 계산 */\n tempRange: { min: number; max: number; auto: boolean; step?: number };\n precipRange: { min: number; max: number; auto: boolean; step?: number };\n /**\n * 막대 채움을 하나로 고정한다. 미지정이면 기존대로 양수는 진회색,\n * 음수는 연회색으로 나뉜다. 시험지는 부호와 무관하게 한 색이다.\n */\n barFill?: string;\n /** 막대 테두리색 — 미지정이면 `#444` */\n barStroke?: string;\n /** 꺾은선의 점 모양 — 미지정이면 원. 시험지는 검은 정사각형을 쓴다. */\n markerShape?: 'circle' | 'square';\n /**\n * 좌·우 축 이름. 주면 눈금 숫자 바깥에 **한 글자씩 세로로** 쌓아 그린다.\n * 미지정이면 단위 표기(tempLabel·precipLabel)만 축 위에 놓는다.\n */\n tempAxisName?: string;\n precipAxisName?: string;\n /** 범례를 플롯 안쪽 모서리 상자에 둔다. 자료를 덮는 모서리는 피한다. */\n insideLegend?: InsideLegendCorner;\n /** 위·오른쪽까지 이어 사각 테두리로 감싼다. 미지정이면 ㄷ자 축선만 그린다. */\n showFrame?: boolean;\n}\n\n// 모드 B — 지역별 편차 (비교형)\nexport interface DeviationBRegion {\n label: string;\n precip: number;\n temp: number;\n}\n\nexport interface DeviationBData {\n baseTemp: number;\n basePrecip: number;\n regions: DeviationBRegion[];\n precipDiffLabel: string;\n tempDiffLabel: string;\n precipUnit: string;\n tempUnit: string;\n precipRange: { min: number; max: number; auto: boolean };\n tempRange: { min: number; max: number; auto: boolean };\n}\n\nexport function createDefaultDeviationAData(): DeviationAData {\n return {\n baseMonths: Array.from({ length: 12 }, () => ({ temp: 0, precip: 0 })),\n months: Array.from({ length: 12 }, () => ({ temp: 0, precip: 0 })),\n monthInterval: 12,\n tempLabel: '(°C)',\n precipLabel: '(mm)',\n tempRange: { min: -10, max: 10, auto: true },\n precipRange: { min: -200, max: 200, auto: true },\n };\n}\n\nexport function createDefaultDeviationBData(): DeviationBData {\n return {\n baseTemp: 0,\n basePrecip: 0,\n regions: [\n { label: 'A', precip: 0, temp: 0 },\n { label: 'B', precip: 0, temp: 0 },\n { label: 'C', precip: 0, temp: 0 },\n ],\n precipDiffLabel: '강수량 차이',\n tempDiffLabel: '기온 차이',\n precipUnit: '(mm)',\n tempUnit: '(°C)',\n precipRange: { min: -200, max: 200, auto: true },\n tempRange: { min: -10, max: 10, auto: true },\n };\n}\n\n// 기본 기후 데이터\nexport function createDefaultClimateData(): ClimateGraphData {\n return {\n months: Array.from({ length: 12 }, () => ({ temp: 0, precip: 0 })),\n monthInterval: 12,\n tempLabel: '(°C)',\n precipLabel: '(mm)',\n tempRange: { min: -10, max: 40, auto: true },\n precipRange: { min: 0, max: 400, auto: true },\n };\n}\n","// © 2026 김용현\n// 인구 피라미드 데이터\nexport const AGE_GROUPS = [\n '0-4', '5-9', '10-14', '15-19', '20-24', '25-29', '30-34',\n '35-39', '40-44', '45-49', '50-54', '55-59', '60-64',\n '65-69', '70-74', '75-79', '80+',\n];\n\nexport interface PyramidAgeData {\n male: number;\n female: number;\n}\n\nexport type PyramidUnit = 'count' | 'percent';\nexport type AgeLabelSide = 'left' | 'right' | 'center';\n\nexport interface PyramidGraphData {\n ages: PyramidAgeData[];\n unit: PyramidUnit;\n maleLabel: string;\n femaleLabel: string;\n axisLabel: string;\n range: { max: number; auto: boolean };\n ageLabelSide: AgeLabelSide;\n /**\n * 세로축을 연령대 이름(0-4, 5-9…) 대신 **나이 수치 눈금**으로 그린다.\n * 시험지 피라미드가 이 방식이다 — 왼쪽에 (세) 0/20/40/60/80.\n * 미지정이면 기존대로 연령대 이름을 쓴다.\n */\n numericAgeAxis?: boolean;\n /** 나이 축 단위 표기 (numericAgeAxis 일 때만). 예: '(세)' */\n ageUnit?: string;\n /**\n * 가로축 단위 표기를 눈금 **숫자와 같은 줄** 오른쪽 끝에 둔다.\n * 시험지 피라미드가 이 배치다. 미지정이면 기존대로 숫자 아랫줄에 적는다.\n */\n axisLabelInline?: boolean;\n /**\n * 남·여 막대 채움색 [남, 여]. 미지정이면 기존 색(진회색/연회색).\n * 시험지는 둘 다 같은 연회색에 검정 테두리다.\n */\n sexFills?: [string, string];\n}\n\nexport function createDefaultPyramidData(): PyramidGraphData {\n return {\n ages: Array.from({ length: 17 }, () => ({ male: 0, female: 0 })),\n unit: 'percent',\n maleLabel: '남',\n femaleLabel: '여',\n axisLabel: '(%)',\n range: { max: 10, auto: true },\n ageLabelSide: 'center',\n };\n}\n","// © 2026 김용현\n// 삼각 그래프 데이터\nexport interface TernaryPoint {\n a: number;\n b: number;\n c: number;\n label: string;\n}\n\nexport type TernaryGridInterval = 10 | 20 | 25;\n\nexport interface TernaryGraphData {\n points: TernaryPoint[];\n axisLabels: [string, string, string];\n gridInterval: TernaryGridInterval;\n}\n\nexport function createDefaultTernaryData(): TernaryGraphData {\n return {\n points: [\n { a: 33, b: 33, c: 34, label: '(가)' },\n ],\n axisLabels: ['A', 'B', 'C'],\n gridInterval: 20,\n };\n}\n","// © 2026 김용현\n// 누적 막대/원 그래프 데이터\nexport type StackedDisplayMode = 'bar' | 'pie';\nexport type StackedBarDirection = 'vertical' | 'horizontal';\n\nexport interface StackedCategory {\n label: string;\n values: number[];\n /**\n * 이 막대에서 세그먼트를 쌓을 순서 (아래→위). 계열 번호를 늘어놓는다.\n *\n * 시험지 누적 막대는 막대마다 **자기 값 순서로** 쌓는다(큰 값이 아래,\n * '기타'가 맨 위). 계열 순서는 모든 막대가 공유하므로 그것만으로는\n * 재현할 수 없다. 미지정이면 기존대로 계열 번호 순으로 쌓는다.\n */\n segmentOrder?: number[];\n}\n\nexport interface StackedGraphData {\n displayMode: StackedDisplayMode;\n barDirection: StackedBarDirection;\n categories: StackedCategory[];\n seriesLabels: string[];\n unit: string;\n /**\n * 계열별 채움 직접 지정. `'pattern:diagonal'` 처럼 적으면 패턴을 쓴다.\n * 미지정이면 기존 팔레트(getStackedFill).\n */\n seriesFills?: string[];\n /**\n * 값 대신 **계열 이름**을 세그먼트 안에 적는다.\n *\n * 시험지 누적 막대는 범례 없이 칸마다 이름을 적는 경우가 많다.\n * 패턴 위에서도 읽히도록 글자에 흰 테두리를 두르고, 어두운 채움 위에서는\n * 흰 글자로 쓴다. 미지정이면 기존대로 `showDataLabels` 에 따라 값을 적는다.\n */\n labelInSegment?: boolean;\n /**\n * 계열 이름이 기호(A·B)인지 실제 이름인지. 시험지는 기호를 명조,\n * 지명을 고딕으로 쓴다. 미지정이면 전부 본문 글꼴.\n */\n seriesIsSymbol?: boolean[];\n /** 가로 격자선 색 — 미지정이면 `#ddd`. 시험지는 더 진하다. */\n gridColor?: string;\n pieRotation?: number; // 0~360도\n pieScale?: number; // 퍼센트 (기본 100)\n axisStep?: number; // 축 눈금 간격 (기본 20)\n}\n\n// 채움 색상/패턴은 canvas/patterns.ts 의 getStackedFill() 사용\n\nexport function createDefaultStackedData(): StackedGraphData {\n return {\n displayMode: 'bar',\n barDirection: 'vertical',\n categories: [\n { label: '(가)', values: [40, 30, 30] },\n { label: '(나)', values: [50, 25, 25] },\n { label: '(다)', values: [20, 40, 40] },\n ],\n seriesLabels: ['항목1', '항목2', '항목3'],\n unit: '(%)',\n };\n}\n","// © 2026 김용현\n// 절댓값 막대 그래프 데이터\nexport type AbsBarDirection = 'vertical' | 'horizontal';\n\nexport interface AbsBarCategory {\n label: string;\n values: number[];\n}\n\nexport interface AbsBarGraphData {\n barDirection: AbsBarDirection;\n stacked: boolean;\n categories: AbsBarCategory[];\n seriesLabels: string[];\n unit: string;\n yRange: { min: number; max: number; auto: boolean; step?: number };\n fitMax?: boolean; // 최댓값에 맞추기\n /**\n * 계열별 채움색 직접 지정. 미지정이면 기존 팔레트(getStackedFill)를 쓴다.\n * 원본 시험지를 재현할 때 기본 팔레트와 색이 다른 경우에 쓴다.\n */\n seriesFills?: string[];\n /**\n * 0을 기준으로 막대를 그린다. 음수는 0에서 아래로 내려가고 0 위치에 실선을 긋는다.\n *\n * 미지정이면 기존대로 축 바닥에서 막대가 자란다. 축이 음수까지 내려가는\n * 편차 그래프에서는 그 방식이 막대를 허공에 띄운 것처럼 보이게 한다.\n */\n zeroBaseline?: boolean;\n /**\n * 범주 라벨을 0선 바로 아래에 놓는다. 미지정이면 플롯 아래(축 바깥)에 놓는다.\n *\n * 시험지마다 다르다 — 2026 수능 한국지리 6번은 0선 아래, 2025 수능\n * 세계지리 19번은 플롯 아래다. 그래서 zeroBaseline 과 따로 둔다.\n */\n categoryLabelAtBaseline?: boolean;\n /**\n * 범주를 묶어 2단 라벨로 만든다 (가로 막대 전용).\n *\n * 시험지에서 흔한 형태다 — 바깥 라벨이 A·B·C, 안쪽 라벨이 1990년·2020년.\n * `span` 은 그 그룹에 속한 범주 개수이고, 합이 categories 길이와 같아야 한다.\n * 그룹 안 막대는 서로 붙고 그룹 사이는 벌어진다.\n *\n * 미지정이면 기존대로 범주마다 라벨 하나를 고르게 배치한다.\n */\n groups?: { label: string; span: number }[];\n /**\n * 범례를 플롯 안쪽 모서리에 작은 상자로 둔다.\n *\n * 미지정이면 기존대로 options.legendPosition 을 따라 플롯 바깥에 그린다.\n */\n insideLegend?: 'top-right' | 'top-left';\n /**\n * 단위를 마지막 눈금 숫자 바로 옆에 붙인다 (가로 막대 전용).\n *\n * 원본 시험지는 축 끝이 `100(EJ)` 처럼 붙어 있다.\n * 미지정이면 기존대로 축 오른쪽에 조금 떨어뜨려 그린다.\n */\n unitAdjacent?: boolean;\n}\n\nexport function createDefaultAbsBarData(): AbsBarGraphData {\n return {\n barDirection: 'vertical',\n stacked: false,\n categories: [\n { label: '(가)', values: [30, 50] },\n { label: '(나)', values: [45, 35] },\n { label: '(다)', values: [60, 20] },\n ],\n seriesLabels: ['항목1', '항목2'],\n unit: '',\n yRange: { min: 0, max: 100, auto: true },\n };\n}\n","// © 2026 김용현\n// 산점도/버블 차트 데이터\nexport type ScatterMode = 'normal' | 'deviation';\n\nexport interface ScatterPoint {\n x: number;\n y: number;\n size: number; // 버블 크기 (0이면 기본 점)\n label: string;\n /**\n * 이 점(버블)의 채움색. 미지정이면 기존 회색을 쓴다.\n * 크기와 별개인 두 번째 값(예: 1인당 GDP 구간)을 색으로 나타낼 때 쓴다.\n */\n fill?: string;\n}\n\n/**\n * 버블 범례 자리.\n *\n * `outside-right` 는 플롯 **바깥** 오른쪽 여백에 세로로 쌓는다. 자료를 아예\n * 덮지 않으므로 점이 많을 때 가장 안전하다. 나머지는 플롯 안쪽 모서리이고,\n * 버블을 덜 덮는 쪽으로 알아서 옮겨 간다.\n */\nexport type BubbleLegendPosition =\n | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'\n | 'outside-right';\n\nexport interface ScatterGraphData {\n mode: ScatterMode;\n points: ScatterPoint[];\n bubbleLegendPosition: BubbleLegendPosition;\n xLabel: string;\n yLabel: string;\n xUnit: string;\n yUnit: string;\n /** step 을 주면 눈금 간격을 그 값으로 고정한다. 미지정이면 기존대로 자동 계산 */\n xRange: { min: number; max: number; auto: boolean; step?: number };\n yRange: { min: number; max: number; auto: boolean; step?: number };\n showBubble: boolean;\n bubbleScale: number; // 버블 최대 반지름 (px)\n /**\n * 바깥 사각 테두리를 그린다. 미지정이면 그린다(기존 동작).\n * 시험지 편차 그래프는 테두리 없이 교차하는 두 축만 둔다.\n *\n * **편차 모드 전용이다.** 일반 모드의 틀은 `examFrame` 이 정한다.\n */\n showFrame?: boolean;\n /**\n * 일반 모드의 틀을 시험지 모양으로 바꾼다. 세 가지가 함께 간다 —\n * 시험지 산점도에서 늘 같이 나타나므로 하나로 묶었다.\n *\n * 1. 플롯을 사각 테두리로 감싼다 (기본은 왼쪽·아래 축선만)\n * 2. 격자를 진한 점선으로 (기본은 아주 연한 회색)\n * 3. x축 단위를 **마지막 눈금 옆**에 둔다 — 원본은 `4(℃)` 꼴이다\n * (기본은 축 이름과 같은 줄 오른쪽 끝)\n *\n * 미지정이면 기존 동작 그대로다.\n */\n examFrame?: boolean;\n /**\n * 축 이름을 상자에 넣어 y는 그래프 위, x는 그래프 오른쪽에 세운다.\n * 시험지 편차 그래프 관습이다. 미지정이면 기존대로 축 옆에 쓴다.\n * 편차 모드에서만 쓴다.\n */\n boxedAxisLabels?: boolean;\n /**\n * 눈금과 눈금 숫자를 가운데 십자선 축에 붙인다. 미지정이면 플롯 가장자리에 붙인다.\n * 테두리를 끄면(showFrame: false) 가장자리에 붙은 눈금이 허공에 뜨므로 함께 쓴다.\n * 편차 모드에서만 쓴다.\n */\n ticksOnAxis?: boolean;\n /**\n * 채움색이 무엇을 뜻하는지 알려주는 범례. 버블 크기 범례 아래에 놓인다.\n * 미지정이면 그리지 않는다.\n */\n fillLegend?: {\n title: string;\n items: { label: string; fill: string }[];\n };\n /**\n * 버블 크기 범례에 쓸 항목을 직접 지정한다.\n * 미지정이면 최댓값에서 자동으로 세 단계를 잡고 숫자를 그대로 쓴다 —\n * 인구처럼 자릿수가 큰 값은 그대로 두면 읽을 수 없으므로 여기서 정해 준다.\n */\n bubbleLegendItems?: { size: number; label: string }[];\n // 편차 모드 전용\n quadrantLabels: [string, string, string, string]; // 1사분면(우상), 2(좌상), 3(좌하), 4(우하)\n}\n\nexport function createDefaultScatterData(): ScatterGraphData {\n return {\n mode: 'normal',\n bubbleLegendPosition: 'bottom-right',\n points: [\n { x: 10, y: 25, size: 50, label: 'A' },\n { x: 25, y: 40, size: 80, label: 'B' },\n { x: 40, y: 15, size: 30, label: 'C' },\n { x: 55, y: 60, size: 100, label: 'D' },\n ],\n xLabel: 'X축',\n yLabel: 'Y축',\n xUnit: '',\n yUnit: '',\n xRange: { min: 0, max: 100, auto: true },\n yRange: { min: 0, max: 100, auto: true },\n showBubble: true,\n bubbleScale: 30,\n quadrantLabels: ['(가)', '(나)', '(다)', '(라)'],\n };\n}\n","// © 2026 김용현\n// 하이서그래프 데이터\nimport { type ClimateMonthData } from './climate';\n\nexport type HythergraphMode = 'loop' | 'points' | 'both';\nexport type MonthLabelStyle = 'number' | 'english';\n\nexport const MONTH_LABELS_NUM = ['1','2','3','4','5','6','7','8','9','10','11','12'];\nexport const MONTH_LABELS_EN = ['J','F','M','A','M','J','J','A','S','O','N','D'];\n\nexport interface HythergraphSeries {\n label: string;\n months: ClimateMonthData[];\n}\n\nexport interface HythergraphData {\n series: HythergraphSeries[];\n mode: HythergraphMode;\n monthLabelStyle: MonthLabelStyle;\n xUnit: string;\n yUnit: string;\n xRange: { min: number; max: number; auto: boolean };\n yRange: { min: number; max: number; auto: boolean };\n}\n\nexport function createDefaultHythergraphData(): HythergraphData {\n return {\n series: [\n { label: '(가)', months: Array.from({ length: 12 }, () => ({ temp: 0, precip: 0 })) },\n ],\n mode: 'both',\n monthLabelStyle: 'number',\n xUnit: '(°C)',\n yUnit: '(mm)',\n xRange: { min: -10, max: 40, auto: true },\n yRange: { min: 0, max: 400, auto: true },\n };\n}\n","// © 2026 김용현\n// 직육면체 그래프 데이터\nexport interface CubePoint {\n x: number; // 0~1\n y: number; // 0~1\n z: number; // 0~1\n label: string;\n labelDx: number; // 유도선 X 오프셋\n labelDy: number; // 유도선 Y 오프셋\n}\n\nexport interface LabelOffset {\n x: number;\n y: number;\n}\n\nexport interface CubeAxisConfig {\n name: string;\n lowLabel: string;\n highLabel: string;\n lowOffset: LabelOffset;\n highOffset: LabelOffset;\n}\n\nexport interface CubeGraphData {\n points: CubePoint[];\n xAxis: CubeAxisConfig;\n yAxis: CubeAxisConfig;\n zAxis: CubeAxisConfig;\n}\n\nexport function createDefaultCubeData(): CubeGraphData {\n return {\n points: [\n { x: 0, y: 1, z: 1, label: 'ㄱ', labelDx: 0, labelDy: 0 },\n { x: 0, y: 0, z: 0, label: 'ㄴ', labelDx: 0, labelDy: 0 },\n { x: 1, y: 0, z: 1, label: 'ㄷ', labelDx: 0, labelDy: 0 },\n ],\n xAxis: { name: 'X축', lowLabel: '낮음', highLabel: '높음', lowOffset: { x: 0, y: 0 }, highOffset: { x: 0, y: 0 } },\n yAxis: { name: 'Y축', lowLabel: '낮음', highLabel: '높음', lowOffset: { x: 0, y: 0 }, highOffset: { x: 0, y: 0 } },\n zAxis: { name: 'Z축', lowLabel: '낮음', highLabel: '높음', lowOffset: { x: 0, y: 0 }, highOffset: { x: 0, y: 0 } },\n };\n}\n","// © 2026 김용현\n// 방사형 그래프 데이터\nexport interface RadarSeries {\n label: string;\n values: number[];\n}\n\nexport interface RadarGraphData {\n axisLabels: string[];\n series: RadarSeries[];\n maxValue: number;\n autoMax: boolean;\n gridSteps: number;\n showFill: boolean;\n}\n\nexport function createDefaultRadarData(): RadarGraphData {\n return {\n axisLabels: ['축1', '축2', '축3', '축4', '축5'],\n series: [\n { label: '(가)', values: [0, 0, 0, 0, 0] },\n ],\n maxValue: 100,\n autoMax: true,\n gridSteps: 5,\n showFill: false,\n };\n}\n","// © 2026 김용현\n// 범주형 점 그래프 데이터\n//\n// 시험지에서 흔한 형태 — x축은 A·B·C·D 같은 범주, y축은 수치, 값은 점으로 찍는다.\n// 막대 그래프(AbsBar)와 축·틀 구조는 같고 표시만 점이다.\n\n/** 계열 구분은 색이 아니라 기호 모양으로 한다 (흑백 인쇄 관습) */\nexport type DotMarker = 'circle' | 'square' | 'triangle' | 'diamond';\n\nexport const DOT_MARKER_ORDER: DotMarker[] = ['circle', 'square', 'triangle', 'diamond'];\n\nexport interface CategoryDotCategory {\n label: string;\n /** 계열별 값. 계열이 하나면 길이 1. */\n values: number[];\n}\n\nexport interface CategoryDotGraphData {\n categories: CategoryDotCategory[];\n seriesLabels: string[];\n /** 계열별 기호. 미지정이면 DOT_MARKER_ORDER 순서를 쓴다. */\n seriesMarkers?: DotMarker[];\n /** y축 단위 — 축 위쪽에 붙는다 (예: '(°C)') */\n unit: string;\n /** 점 반지름(px) */\n dotRadius: number;\n yRange: { min: number; max: number; auto: boolean; step?: number };\n /**\n * 0 위치에 실선을 긋는다. 편차 그래프에서 기준선을 보이게 하려고 쓴다.\n * 점 자체는 값 위치에 찍히므로 막대와 달리 기준선이 그리기에 영향을 주지 않는다.\n */\n zeroBaseline?: boolean;\n}\n\nexport function createDefaultCategoryDotData(): CategoryDotGraphData {\n return {\n categories: [\n { label: 'A', values: [17] },\n { label: 'B', values: [11] },\n { label: 'C', values: [9] },\n { label: 'D', values: [25] },\n ],\n seriesLabels: ['항목1'],\n unit: '',\n dotRadius: 6,\n yRange: { min: 0, max: 100, auto: true },\n };\n}\n","// © 2026 김용현\n// 꺾은선 그래프 데이터\n//\n// 시험지 관습 — 계열은 색이 아니라 **선 종류와 기호 모양**으로 구분하고,\n// 계열 이름은 범례 대신 **선 끝**에 붙이는 경우가 많다.\n\n/** 선 종류 — 흑백 인쇄에서 계열을 구분하는 수단 */\nexport type LineStyle = 'solid' | 'dashed' | 'dotted' | 'dashdot';\n\n/** 선 종류별 대시 패턴 (Canvas setLineDash 인자) */\nexport const LINE_DASH: Record<LineStyle, number[]> = {\n solid: [],\n dashed: [10, 6],\n dotted: [2, 5],\n dashdot: [12, 5, 3, 5],\n};\n\nexport const LINE_STYLE_ORDER: LineStyle[] = ['solid', 'dashed', 'dotted', 'dashdot'];\n\n/** 꼭짓점 기호 — 선 종류만으로 부족할 때 함께 쓴다 */\nexport type LineMarker = 'circle' | 'square' | 'triangle' | 'diamond';\n\nexport const LINE_MARKER_ORDER: LineMarker[] = ['circle', 'square', 'triangle', 'diamond'];\n\nexport interface LineSeries {\n label: string;\n /** xLabels 와 같은 길이. 빠진 값은 null 로 둔다 (선이 끊긴다). */\n values: (number | null)[];\n lineStyle?: LineStyle;\n marker?: LineMarker;\n /** 기호 속을 비운다 (흰색 채움 + 검정 테두리) */\n hollowMarker?: boolean;\n /**\n * 면적 채움색. 주면 이 계열의 선 아래를 채운다.\n * `stacked` 와 함께 쓰면 아래 계열의 경계까지만 채워 누적 면적이 된다.\n */\n areaFill?: string;\n}\n\n/** 계열 이름을 어디에 쓸지 */\nexport type LineLabelPlacement = 'lineEnd' | 'legend';\n\nexport interface LineGraphData {\n series: LineSeries[];\n /** x축 눈금 이름 (예: 1~12월) */\n xLabels: string[];\n /** x축 끝에 붙는 단위 (예: '(월)') */\n xUnit: string;\n /** y축 위에 붙는 단위 (예: '(°C)') */\n yUnit: string;\n yRange: { min: number; max: number; auto: boolean; step?: number };\n /** 기본은 시험지 관습대로 선 끝 */\n labelPlacement: LineLabelPlacement;\n showMarkers: boolean;\n /** 0 위치에 점선 기준선을 긋는다 (편차 그래프) */\n zeroBaseline?: boolean;\n /**\n * 계열을 아래에서부터 쌓아 올린다 (누적 면적 그래프).\n * 각 계열의 선은 \"그때까지의 합\"을 나타낸다.\n */\n stacked?: boolean;\n /**\n * 범례를 플롯 안쪽 왼쪽 위에 작은 상자로 둔다.\n * 시험지 누적 면적 그래프가 이 방식이다. `labelPlacement:'legend'` 와 함께 쓴다.\n */\n insideLegend?: boolean;\n}\n\nexport function createDefaultLineData(): LineGraphData {\n return {\n series: [\n { label: '(가)', values: Array.from({ length: 12 }, (_, i) => i * 2) },\n ],\n xLabels: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'],\n xUnit: '(월)',\n yUnit: '',\n yRange: { min: 0, max: 100, auto: true },\n labelPlacement: 'lineEnd',\n showMarkers: true,\n };\n}\n","// © 2026 김용현\n// 계단식 행렬표 데이터\n//\n// 시험지에서 흔한 형태 — 도시 간 거리처럼 **짝마다 값이 하나뿐인** 자료를\n// 아래 삼각형만 남긴 계단 모양 표로 낸다. 대각선 칸에는 이름이 들어간다.\n// 그래프는 아니지만 시험지에 붙일 그림이므로 같은 파이프라인에 둔다.\n\nexport interface MatrixTableData {\n /**\n * 대각선 칸에 들어갈 이름. 문제에서 가릴 이름은 이미 (가)·(나)로\n * 바꿔서 넘긴다 — 렌더러는 무엇을 가렸는지 모른다.\n */\n names: string[];\n /**\n * 짝별 값. `values[i][j]`(j < i)만 쓴다 — 위 삼각형과 대각선은 무시한다.\n * 정사각 행렬을 그대로 넘겨도 된다.\n */\n values: number[][];\n /**\n * 이름이 기호인지((가)·A) 실제 지명인지.\n *\n * 시험지는 **기호를 명조, 지명을 고딕**으로 쓴다. 미지정이면 전부\n * 본문 글꼴(`options.fontFamily`)을 쓴다.\n */\n nameIsSymbol?: boolean[];\n /** 오른쪽 위에 붙는 단위 표기. 예: `'(단위 : km)'` */\n unit: string;\n /** 이름 칸 채움색 — 미지정이면 연회색 */\n nameFill?: string;\n /** 천 단위 쉼표 — 미지정이면 붙인다 (원본이 `2,648` 꼴이다) */\n groupThousands?: boolean;\n}\n\nexport function createDefaultMatrixTableData(): MatrixTableData {\n return {\n names: ['(가)', '(나)', '(다)'],\n values: [\n [0, 0, 0],\n [559, 0, 0],\n [2648, 2210, 0],\n ],\n unit: '(단위 : km)',\n };\n}\n","// © 2026 김용현\n// 항목 × 지역 데이터 표\n//\n// 시험지에서 가장 흔한 표다 — 머리글 행에 지역 기호((가)~(라)), 왼쪽 열에 항목\n// 이름, 칸마다 값이 하나. 계단식 행렬표(MatrixTable)와 달리 칸이 서로 붙어 있고\n// 값이 소수다. 그래프는 아니지만 시험지에 붙일 그림이므로 같은 파이프라인에 둔다.\n\nexport interface DataTableRow {\n /** 항목 이름. 예: `'기온의 연교차'` */\n label: string;\n /** 이름 뒤에 붙는 괄호 단위. 원본은 이름보다 조금 작게 쓴다. 예: `'(℃)'` */\n unit?: string;\n /** 머리글 순서대로 놓이는 값 */\n values: number[];\n /** 소수점 자릿수. 미지정이면 1자리 — 시험지 표는 대개 소수 한 자리다. */\n decimals?: number;\n}\n\nexport interface DataTableData {\n /** 왼쪽 위 모서리 칸. 원본은 `'구분'` */\n cornerLabel: string;\n /**\n * 머리글. 시험지는 지역을 (가)~(라) 처럼 기호로 가린다.\n * 문제에서 가릴 이름은 이미 바꿔서 넘긴다 — 렌더러는 무엇을 가렸는지 모른다.\n */\n columns: string[];\n rows: DataTableRow[];\n /**\n * 머리글이 기호인지((가)·A) 실제 지명인지.\n *\n * 시험지는 **기호를 명조, 지명을 고딕**으로 쓴다. 미지정이면 전부\n * 본문 글꼴(`options.fontFamily`)을 쓴다.\n */\n columnIsSymbol?: boolean[];\n /** 머리글 행 채움색 — 미지정이면 연회색 */\n headerFill?: string;\n /** 천 단위 쉼표 — 미지정이면 붙인다 */\n groupThousands?: boolean;\n}\n\nexport function createDefaultDataTableData(): DataTableData {\n return {\n cornerLabel: '구분',\n columns: ['(가)', '(나)', '(다)', '(라)'],\n rows: [\n { label: '기온의 연교차', unit: '(℃)', values: [40.5, 29.9, 3.8, 3.8] },\n { label: '최한월 평균 기온', unit: '(℃)', values: [-21.4, -24.4, 24.6, 19.5] },\n { label: '1월 강수량', unit: '(mm)', values: [1.8, 4.6, 28.8, 206.8] },\n ],\n };\n}\n","// © 2026 김용현\n// 트리맵(사각형 분할) 그래프 데이터\nexport interface TreemapCell {\n label: string;\n /** 넓이의 근거가 되는 값. 렌더러가 합으로 나눠 비율을 낸다. */\n value: number;\n}\n\nexport interface TreemapGraphData {\n /**\n * 순서는 배치 결과에만 영향을 준다 — 넓이는 값 비율대로 나뉘므로 어느\n * 순서든 정확하다. 다만 내림차순이라야 칸이 정사각형에 가깝게 나온다.\n * '기타'는 값이 더 크더라도 시험지 관행대로 맨 뒤에 둔다.\n */\n cells: TreemapCell[];\n /** 칸 라벨 글자 크기. 미지정이면 options.fontSize.tick 을 쓴다. */\n labelFontSize?: number;\n /**\n * 라벨을 적을 최소 칸 너비(px). 이보다 좁으면 글자를 생략한다.\n * 좁은 칸에 억지로 넣으면 글자가 테두리를 넘어 옆 칸을 밟는다.\n */\n minLabelWidth?: number;\n /**\n * 출처가 없어도 출처 한 줄만큼 아래를 비워 둔다.\n *\n * 패널을 여러 장 나란히 놓을 때 쓴다 — 출처를 마지막 패널에만 적으면\n * 그 패널만 상자가 짧아져 줄이 안 맞는다. 모두 같은 자리를 비우면\n * 상자 높이가 같아지고 출처는 상자 **아래**에 놓인다.\n */\n reserveSourceSpace?: boolean;\n}\n\nexport function createDefaultTreemapData(): TreemapGraphData {\n return {\n cells: [\n { label: '중국', value: 50 },\n { label: '미국', value: 20 },\n { label: '인도', value: 12 },\n { label: '기타', value: 18 },\n ],\n };\n}\n","// © 2026 김용현\n// 범주형 점 그래프 — x축은 범주(A·B·C·D), y축은 수치, 값은 점.\n//\n// ⚠️ GeoGrapher 원본에 없는 신규 렌더러다. CHANGES.md 참조.\n// 축·틀·격자는 AbsBarGraph 와 같은 구조를 쓴다. 한 문항에서 둘을 나란히\n// 놓는 경우(2024 수능 세계지리 19번)가 있어 모양이 어긋나면 안 된다.\nimport {\n type CategoryDotGraphData,\n type DotMarker,\n type GraphOptions,\n DOT_MARKER_ORDER,\n} from '../types/index';\nimport { type Padding, clearCanvas, autoRange, getFont } from '../canvas/renderer';\nimport { drawYAxis } from '../canvas/axes';\nimport { drawTitle, drawSourceAndFootnote } from '../canvas/labels';\nimport { drawLegend, measureLegendWidth, measureBottomLegend, type LegendItem } from '../canvas/legend';\n\n/** 기호 하나를 (cx, cy)에 그린다 */\nfunction drawMarker(\n ctx: CanvasRenderingContext2D,\n marker: DotMarker,\n cx: number,\n cy: number,\n r: number\n) {\n ctx.beginPath();\n switch (marker) {\n case 'circle':\n ctx.arc(cx, cy, r, 0, Math.PI * 2);\n break;\n case 'square':\n ctx.rect(cx - r, cy - r, r * 2, r * 2);\n break;\n case 'triangle':\n ctx.moveTo(cx, cy - r);\n ctx.lineTo(cx + r, cy + r);\n ctx.lineTo(cx - r, cy + r);\n ctx.closePath();\n break;\n case 'diamond':\n ctx.moveTo(cx, cy - r);\n ctx.lineTo(cx + r, cy);\n ctx.lineTo(cx, cy + r);\n ctx.lineTo(cx - r, cy);\n ctx.closePath();\n break;\n }\n ctx.fill();\n}\n\nexport function renderCategoryDotGraph(\n ctx: CanvasRenderingContext2D,\n w: number,\n h: number,\n data: CategoryDotGraphData,\n options: GraphOptions\n) {\n clearCanvas(ctx, w, h);\n\n const font = options.fontFamily;\n const customFont = options.customFont;\n const n = data.categories.length;\n const sCount = data.seriesLabels.length;\n const markers = data.seriesMarkers ?? DOT_MARKER_ORDER;\n\n // 계열이 하나뿐이면 범례가 의미 없다 (시험지도 안 넣는다)\n const showLegend = options.showLegend && sCount > 1;\n const legendPos = options.legendPosition;\n const legendW = (showLegend && legendPos === 'right')\n ? measureLegendWidth(ctx, data.seriesLabels, options.fontSize.dataLabel * 0.85 + 5, 'circle')\n : 0;\n\n // 범례가 몇 줄이 될지 먼저 재야 그만큼 아래 여백을 잡을 수 있다\n const legendReserve = (showLegend && legendPos === 'bottom')\n ? measureBottomLegend(ctx, data.seriesLabels, options.fontSize.dataLabel * 0.85, w - 130 - (60 + legendW), 'circle')\n : 0;\n\n const padding: Padding = {\n top: options.title ? 100 : 50,\n right: 60 + legendW,\n bottom: (() => {\n let b = 70;\n if (showLegend && legendPos === 'bottom') b += 60;\n b = Math.max(b, legendReserve);\n if (options.source) b += 30;\n b += options.footnotes.filter(f => f.trim()).length * 22;\n return b;\n })(),\n left: 130,\n };\n\n const plotX = padding.left;\n const plotY = padding.top;\n const plotW = w - padding.left - padding.right;\n const plotH = h - padding.top - padding.bottom;\n\n // 축 범위 — 0을 항상 포함한다 (시험지 관습: 값 축이 0에서 시작)\n const allValues: number[] = [0];\n for (const cat of data.categories) allValues.push(...cat.values);\n if (allValues.length <= 1) allValues.push(100);\n\n const axis = data.yRange.auto\n ? autoRange(allValues, 6)\n : {\n min: data.yRange.min,\n max: data.yRange.max,\n step: data.yRange.step || Math.max(1, Math.round((data.yRange.max - data.yRange.min) / 6)),\n };\n\n // 사각 테두리\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 2;\n ctx.beginPath();\n ctx.moveTo(plotX, plotY);\n ctx.lineTo(plotX, plotY + plotH);\n ctx.lineTo(plotX + plotW, plotY + plotH);\n ctx.lineTo(plotX + plotW, plotY);\n ctx.lineTo(plotX, plotY);\n ctx.stroke();\n\n // Y축 눈금 + 격자\n drawYAxis({\n ctx, padding, width: w, height: h,\n min: axis.min, max: axis.max, step: axis.step,\n label: data.unit,\n side: 'left',\n fontFamily: font, customFont,\n tickFontSize: options.fontSize.tick,\n labelFontSize: options.fontSize.axisLabel,\n drawGrid: true,\n });\n\n // 점 (플롯 영역 클리핑)\n //\n // 클리핑 범위를 점 반지름만큼 넓힌다. 막대는 바닥에서 자라니 딱 맞게 잘라도\n // 되지만, 점은 값 위치가 중심이라 최댓값 점이 위쪽 테두리에 놓이면 절반이\n // 잘려 나간다. 축 최댓값이 데이터 최댓값과 정확히 같을 때 항상 그렇다.\n const clipPad = data.dotRadius + 1;\n ctx.save();\n ctx.beginPath();\n ctx.rect(plotX - clipPad, plotY - clipPad, plotW + clipPad * 2, plotH + clipPad * 2);\n ctx.clip();\n\n const catArea = plotW / n;\n const valToY = (v: number) => plotY + plotH - ((v - axis.min) / (axis.max - axis.min)) * plotH;\n // 계열이 여럿이면 범주 안에서 좌우로 벌려 겹치지 않게 한다\n const seriesGap = sCount > 1 ? Math.min(catArea * 0.6, sCount * 24) / sCount : 0;\n\n for (let c = 0; c < n; c++) {\n const center = plotX + catArea * c + catArea / 2;\n for (let s = 0; s < sCount; s++) {\n const val = data.categories[c].values[s];\n if (val === undefined || Number.isNaN(val)) continue;\n const cx = center + (s - (sCount - 1) / 2) * seriesGap;\n const cy = valToY(val);\n\n ctx.fillStyle = '#000';\n drawMarker(ctx, markers[s % markers.length], cx, cy, data.dotRadius);\n\n if (options.showDataLabels) {\n ctx.font = getFont(options.fontSize.dataLabel * 0.8, font, customFont, 'bold');\n ctx.textAlign = 'center';\n ctx.textBaseline = 'bottom';\n ctx.fillText(String(val), cx, cy - data.dotRadius - 4);\n }\n }\n }\n ctx.restore();\n\n // 0 기준선 — 점 위에 그어야 가려지지 않는다\n if (data.zeroBaseline) {\n const zeroY = valToY(0);\n if (zeroY > plotY && zeroY < plotY + plotH) {\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 1.5;\n ctx.setLineDash([]);\n ctx.beginPath();\n ctx.moveTo(plotX, zeroY);\n ctx.lineTo(plotX + plotW, zeroY);\n ctx.stroke();\n }\n }\n\n // X축 범주 라벨 (클리핑 밖에서)\n ctx.fillStyle = '#000';\n ctx.font = getFont(options.fontSize.tick, font, customFont, 'bold');\n ctx.textAlign = 'center';\n ctx.textBaseline = 'top';\n for (let c = 0; c < n; c++) {\n const cx = plotX + catArea * c + catArea / 2;\n ctx.fillText(data.categories[c].label, cx, plotY + plotH + 12);\n }\n\n if (options.title) {\n drawTitle({ ctx, plotX, plotW, title: options.title, fontSize: options.fontSize.title });\n }\n\n if (showLegend) {\n // 알려진 한계: 범례 아이콘은 모두 원이다. LegendItem 에 기호 모양이 없어서인데,\n // 그걸 넣으려면 canvas/legend.ts 를 고쳐야 하고 그건 원본과의 텍스트 동일성\n // (verify:port)을 깨뜨린다. 필요해지면 원본에서 먼저 고칠 것.\n const items: LegendItem[] = data.seriesLabels.map((label) => ({\n type: 'circle',\n fillStyle: '#000',\n label,\n }));\n drawLegend({\n ctx, items, position: legendPos,\n plotX, plotY, plotW, plotH,\n canvasW: w, canvasH: h,\n fontSize: options.fontSize.dataLabel * 0.85,\n });\n }\n\n drawSourceAndFootnote({\n ctx, plotX, plotW, height: h, canvasWidth: w,\n source: options.source,\n footnotes: options.footnotes,\n fontSize: options.fontSize.dataLabel * 0.85,\n });\n}\n","// © 2026 김용현\n// 꺾은선 그래프 — 연도·월별 추이. 계열은 선 종류와 기호로 구분한다.\n//\n// ⚠️ GeoGrapher 원본에 없는 신규 렌더러다. CHANGES.md 참조.\n// 축·틀·격자는 AbsBarGraph·CategoryDotGraph 와 같은 구조를 쓴다.\n// 한 문항에서 나란히 놓이는 경우가 있어 모양이 어긋나면 안 된다.\nimport {\n type LineGraphData,\n type LineMarker,\n type GraphOptions,\n LINE_DASH,\n LINE_STYLE_ORDER,\n LINE_MARKER_ORDER,\n} from '../types/index';\nimport { type Padding, clearCanvas, autoRange, getFont } from '../canvas/renderer';\nimport { drawYAxis } from '../canvas/axes';\nimport {\n drawTitle,\n drawSourceAndFootnote,\n LabelPlacer,\n labelStride,\n widestLabel,\n} from '../canvas/labels';\nimport { drawLegend, measureLegendWidth, measureBottomLegend, type LegendItem } from '../canvas/legend';\n\n/** 꼭짓점 기호 하나 */\nfunction drawMarker(\n ctx: CanvasRenderingContext2D,\n marker: LineMarker,\n cx: number,\n cy: number,\n r: number,\n hollow: boolean\n) {\n ctx.beginPath();\n switch (marker) {\n case 'circle':\n ctx.arc(cx, cy, r, 0, Math.PI * 2);\n break;\n case 'square':\n ctx.rect(cx - r, cy - r, r * 2, r * 2);\n break;\n case 'triangle':\n ctx.moveTo(cx, cy - r);\n ctx.lineTo(cx + r, cy + r);\n ctx.lineTo(cx - r, cy + r);\n ctx.closePath();\n break;\n case 'diamond':\n ctx.moveTo(cx, cy - r);\n ctx.lineTo(cx + r, cy);\n ctx.lineTo(cx, cy + r);\n ctx.lineTo(cx - r, cy);\n ctx.closePath();\n break;\n }\n ctx.fillStyle = hollow ? '#fff' : '#000';\n ctx.fill();\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 1.5;\n ctx.setLineDash([]);\n ctx.stroke();\n}\n\nexport function renderLineGraph(\n ctx: CanvasRenderingContext2D,\n w: number,\n h: number,\n data: LineGraphData,\n options: GraphOptions\n) {\n clearCanvas(ctx, w, h);\n\n const font = options.fontFamily;\n const customFont = options.customFont;\n const n = data.xLabels.length;\n const useLegend = data.labelPlacement === 'legend' && options.showLegend;\n const legendPos = options.legendPosition;\n const legendW = (useLegend && legendPos === 'right')\n ? measureLegendWidth(ctx, data.series.map((s) => s.label), options.fontSize.dataLabel * 0.85 + 5, 'line')\n : 0;\n\n // 선 끝에 이름을 붙이면 오른쪽에 자리가 필요하다\n ctx.font = getFont(options.fontSize.dataLabel, font, customFont, 'bold');\n const endLabelW = data.labelPlacement === 'lineEnd'\n ? widestLabel(ctx, data.series.map((s) => s.label)) + 16\n : 0;\n\n // x축 단위((년) 등)도 마지막 눈금 오른쪽에 놓이므로 그만큼 자리를 비워 둔다.\n // 안 그러면 좁은 패널에서 잘린다.\n ctx.font = getFont(options.fontSize.tick, font, customFont, 'bold');\n const xUnitW = data.xUnit\n ? ctx.measureText(data.xUnit).width + ctx.measureText(data.xLabels[n - 1] ?? '').width / 2 + 12\n : 0;\n\n const padRight = 24 + legendW + Math.max(endLabelW, xUnitW);\n // 범례가 몇 줄이 될지 먼저 재야 그만큼 아래 여백을 잡을 수 있다\n const legendReserve = (useLegend && legendPos === 'bottom' && !data.insideLegend)\n ? measureBottomLegend(ctx, data.series.map((s) => s.label),\n options.fontSize.dataLabel * 0.85, w - 130 - padRight, 'line')\n : 0;\n\n const padding: Padding = {\n top: options.title ? 100 : 50,\n right: padRight,\n bottom: (() => {\n let b = 70;\n // 범례를 플롯 **안쪽**에 두면 아래에 자리를 비울 필요가 없다.\n // 예전에는 그래도 60을 비워, 연도 눈금 밑에 빈 띠가 남고 플롯만 눌렸다.\n if (useLegend && legendPos === 'bottom' && !data.insideLegend) b += 60;\n b = Math.max(b, legendReserve);\n if (options.source) b += 30;\n b += options.footnotes.filter((f) => f.trim()).length * 22;\n return b;\n })(),\n left: 130,\n };\n\n const plotX = padding.left;\n const plotY = padding.top;\n const plotW = w - padding.left - padding.right;\n const plotH = h - padding.top - padding.bottom;\n\n // 축 범위.\n // 누적이면 각 x 위치의 합이 최댓값이므로 개별 값이 아니라 합을 봐야 한다.\n const allValues = data.stacked\n ? data.xLabels.map((_, i) =>\n data.series.reduce((sum, s) => {\n const v = s.values[i];\n return sum + (v !== null && Number.isFinite(v) ? v : 0);\n }, 0))\n : data.series\n .flatMap((s) => s.values)\n .filter((v): v is number => v !== null && Number.isFinite(v));\n const axis = data.yRange.auto\n ? autoRange(allValues.length > 0 ? allValues : [0, 100], 6)\n : {\n min: data.yRange.min,\n max: data.yRange.max,\n step: data.yRange.step || Math.max(1, Math.round((data.yRange.max - data.yRange.min) / 6)),\n };\n\n // 사각 테두리\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 2;\n ctx.setLineDash([]);\n ctx.beginPath();\n ctx.moveTo(plotX, plotY);\n ctx.lineTo(plotX, plotY + plotH);\n ctx.lineTo(plotX + plotW, plotY + plotH);\n ctx.lineTo(plotX + plotW, plotY);\n ctx.lineTo(plotX, plotY);\n ctx.stroke();\n\n drawYAxis({\n ctx, padding, width: w, height: h,\n min: axis.min, max: axis.max, step: axis.step,\n label: data.yUnit,\n side: 'left',\n fontFamily: font, customFont,\n tickFontSize: options.fontSize.tick,\n labelFontSize: options.fontSize.axisLabel,\n drawGrid: true,\n });\n\n // x 위치 — 첫 점과 마지막 점이 좌우 끝에 오도록 나눈다\n const stepX = n > 1 ? plotW / (n - 1) : 0;\n const toX = (i: number) => plotX + stepX * i;\n const toY = (v: number) => plotY + plotH - ((v - axis.min) / (axis.max - axis.min)) * plotH;\n\n // 0 기준선 (편차 그래프)\n if (data.zeroBaseline && axis.min < 0 && axis.max > 0) {\n ctx.save();\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 1;\n ctx.setLineDash([4, 4]);\n ctx.beginPath();\n ctx.moveTo(plotX, toY(0));\n ctx.lineTo(plotX + plotW, toY(0));\n ctx.stroke();\n ctx.restore();\n }\n\n /**\n * 계열별로 실제로 그릴 y값.\n * 누적이면 아래 계열들의 합을 얹은 \"그때까지의 합\"이 된다.\n * 아래 경계(baseline)는 면적을 채울 때 쓴다.\n */\n const tops: (number | null)[][] = [];\n const bases: number[][] = [];\n {\n const running = data.xLabels.map(() => 0);\n for (const s of data.series) {\n const base = [...running];\n const top = s.values.map((v, i) => {\n if (v === null || !Number.isFinite(v)) return null;\n if (!data.stacked) return v;\n running[i] += v;\n return running[i];\n });\n tops.push(top);\n bases.push(base);\n }\n }\n\n // 면적 채움 — 선보다 먼저 그려야 선이 위에 남는다\n data.series.forEach((s, si) => {\n if (!s.areaFill) return;\n const top = tops[si];\n const base = bases[si];\n ctx.save();\n ctx.fillStyle = s.areaFill;\n // 값이 끊기면 구간을 나눠 채운다\n let i = 0;\n while (i < top.length) {\n if (top[i] === null) { i++; continue; }\n let j = i;\n while (j + 1 < top.length && top[j + 1] !== null) j++;\n\n ctx.beginPath();\n for (let k = i; k <= j; k++) ctx.lineTo(toX(k), toY(top[k] as number));\n for (let k = j; k >= i; k--) ctx.lineTo(toX(k), toY(data.stacked ? base[k] : axis.min));\n ctx.closePath();\n ctx.fill();\n i = j + 1;\n }\n ctx.restore();\n });\n\n // 선\n data.series.forEach((s, si) => {\n const style = s.lineStyle ?? LINE_STYLE_ORDER[si % LINE_STYLE_ORDER.length];\n ctx.save();\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 2;\n ctx.setLineDash(LINE_DASH[style]);\n ctx.beginPath();\n let started = false;\n tops[si].forEach((v, i) => {\n if (v === null) { started = false; return; }\n const x = toX(i);\n const y = toY(v);\n if (!started) { ctx.moveTo(x, y); started = true; }\n else ctx.lineTo(x, y);\n });\n ctx.stroke();\n ctx.restore();\n });\n\n // 꼭짓점 기호 — 선 위에 얹는다\n if (data.showMarkers) {\n data.series.forEach((s, si) => {\n const marker = s.marker ?? LINE_MARKER_ORDER[si % LINE_MARKER_ORDER.length];\n tops[si].forEach((v, i) => {\n if (v === null) return;\n drawMarker(ctx, marker, toX(i), toY(v), 4.5, s.hollowMarker ?? false);\n });\n });\n }\n\n // 계열 이름 — 선 끝에 붙인다.\n // 이름들이 세로 한 줄로 나란해야 하므로 x 는 고정하고 위아래로만 민다\n // (8방향 배치는 가로 위치가 흐트러져 어긋나 보인다).\n if (data.labelPlacement === 'lineEnd') {\n ctx.fillStyle = '#000';\n ctx.font = getFont(options.fontSize.dataLabel, font, customFont, 'bold');\n const placer = new LabelPlacer();\n const lineHeight = options.fontSize.dataLabel * 1.1;\n const columnX = plotX + plotW + 10;\n const bounds = {\n left: columnX,\n right: w - 4,\n top: 4,\n bottom: h - 4,\n };\n data.series.forEach((s, si) => {\n // 값이 있는 마지막 점을 찾는다\n const top = tops[si];\n let last = -1;\n for (let i = top.length - 1; i >= 0; i--) {\n if (top[i] !== null) { last = i; break; }\n }\n if (last < 0) return;\n placer.placeInColumn(\n ctx,\n s.label,\n columnX,\n toY(top[last] as number) - lineHeight / 2,\n { lineHeight, bounds },\n );\n });\n }\n\n // X축 눈금 이름\n ctx.fillStyle = '#000';\n ctx.font = getFont(options.fontSize.tick, font, customFont, 'bold');\n ctx.textAlign = 'center';\n ctx.textBaseline = 'top';\n const xStride = labelStride(stepX, widestLabel(ctx, data.xLabels));\n data.xLabels.forEach((label, i) => {\n if (i % xStride !== 0) return;\n ctx.fillText(label, toX(i), plotY + plotH + 10);\n });\n\n // X축 단위 — 마지막 눈금 이름 오른쪽.\n // 마지막 이름은 가운데 정렬이라 절반이 축 밖으로 나온다. 그만큼 더 띄운다.\n if (data.xUnit) {\n const lastLabelHalf = ctx.measureText(data.xLabels[n - 1] ?? '').width / 2;\n ctx.textAlign = 'left';\n ctx.fillText(data.xUnit, plotX + plotW + lastLabelHalf + 6, plotY + plotH + 10);\n }\n\n if (options.title) {\n drawTitle({ ctx, plotX, plotW, title: options.title, fontSize: options.fontSize.title, canvasWidth: w });\n }\n\n if (useLegend && data.insideLegend) {\n // 시험지 누적 면적 그래프는 범례를 플롯 안쪽 왼쪽 위에 작은 상자로 둔다\n ctx.save();\n ctx.font = getFont(options.fontSize.dataLabel * 0.8, font, customFont, 'bold');\n const rowH = options.fontSize.dataLabel * 1.15;\n const swatch = rowH * 0.75;\n const boxW = swatch + 6 + widestLabel(ctx, data.series.map((s) => s.label)) + 10;\n let ly = plotY + 6;\n for (const s of data.series) {\n ctx.fillStyle = '#fff';\n ctx.fillRect(plotX + 6, ly, boxW, rowH);\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 1;\n ctx.setLineDash([]);\n ctx.strokeRect(plotX + 6, ly, boxW, rowH);\n\n ctx.fillStyle = s.areaFill ?? '#000';\n ctx.fillRect(plotX + 10, ly + (rowH - swatch) / 2, swatch, swatch);\n ctx.strokeRect(plotX + 10, ly + (rowH - swatch) / 2, swatch, swatch);\n\n ctx.fillStyle = '#000';\n ctx.textAlign = 'left';\n ctx.textBaseline = 'middle';\n ctx.fillText(s.label, plotX + 10 + swatch + 5, ly + rowH / 2);\n ly += rowH;\n }\n ctx.restore();\n } else if (useLegend) {\n const items: LegendItem[] = data.series.map((s, si) => ({\n type: 'line',\n fillStyle: '#000',\n strokeStyle: '#000',\n label: s.label,\n dash: LINE_DASH[s.lineStyle ?? LINE_STYLE_ORDER[si % LINE_STYLE_ORDER.length]],\n }));\n drawLegend({\n ctx, items, position: legendPos,\n plotX, plotY, plotW, plotH,\n canvasW: w, canvasH: h,\n fontSize: options.fontSize.dataLabel * 0.85,\n });\n }\n\n drawSourceAndFootnote({\n ctx, plotX, plotW, height: h, canvasWidth: w,\n source: options.source,\n footnotes: options.footnotes,\n fontSize: options.fontSize.dataLabel * 0.85,\n });\n}\n","// © 2026 김용현\n// 계단식 행렬표 — 아래 삼각형만 남기고 대각선에 이름을 넣는다.\n//\n// 이 렌더러는 GeoGrapher 원본에 없다. GeoTester 에서 새로 만든 것이라\n// `verify:port` 대조 대상이 아니다. (CHANGES.md 참조)\nimport { type MatrixTableData, type GraphOptions } from '../types/index';\nimport { clearCanvas, getFont } from '../canvas/renderer';\nimport { drawTitle, drawSourceAndFootnote } from '../canvas/labels';\n\nconst NAME_FILL = '#d9d9d9';\n/** 칸은 서로 붙지 않는다 — 원본은 칸마다 떨어진 상자다 (글자 크기의 0.2배) */\nconst GAP_RATIO = 0.2;\nconst CELL_H_RATIO = 1.7;\n/** 칸 안쪽 좌우 여백 (글자 크기 기준) */\nconst PAD_RATIO = 0.75;\n\nexport function renderMatrixTable(\n ctx: CanvasRenderingContext2D,\n w: number,\n h: number,\n data: MatrixTableData,\n options: GraphOptions\n) {\n clearCanvas(ctx, w, h);\n\n const n = data.names.length;\n if (n === 0) return;\n\n const font = options.fontFamily;\n const customFont = options.customFont;\n\n // 칸 크기는 가장 긴 글자에 맞춘다 — 모든 칸이 같아야 계단이 반듯하다\n const base = options.fontSize.tick;\n ctx.font = getFont(base, font, customFont, 'bold');\n const widest = Math.max(\n ...data.names.map((t) => ctx.measureText(t).width),\n ...cellValues(data).map((v) => ctx.measureText(formatValue(v, data.groupThousands)).width),\n );\n\n const naturalW = widest + base * PAD_RATIO * 2;\n const unitH = data.unit ? options.fontSize.dataLabel * 1.6 : 0;\n // 각주 블록은 마지막 줄의 글자 높이만큼 위로 더 올라간다 (drawSourceAndFootnote).\n // 그 몫을 안 빼면 표 마지막 줄과 겹친다.\n const footCount = options.footnotes.filter((f) => f.trim()).length;\n const footH =\n (footCount > 0 ? footCount * 22 + options.fontSize.dataLabel + 12 : 0) +\n (options.source ? options.fontSize.dataLabel + 4 : 0);\n const top = options.title ? 100 : 30;\n\n // 이름을 가리지 않으면(실제 지명) 칸이 넓어져 캔버스를 넘는다.\n // 그럴 때는 표 전체를 줄여 넣는다 — 잘리는 것보다 작아지는 편이 낫다.\n const spanOf = (cell: number, gap: number) => cell * n + gap * (n - 1);\n const fit = Math.min(\n 1,\n (w - 20) / spanOf(naturalW, naturalW * GAP_RATIO),\n (h - top - unitH - footH - 10) / spanOf(base * CELL_H_RATIO, base * GAP_RATIO),\n );\n\n const cellFontSize = base * fit;\n const cellW = naturalW * fit;\n const cellH = base * CELL_H_RATIO * fit;\n const gap = base * GAP_RATIO * fit;\n const pad = cellFontSize * PAD_RATIO;\n\n const tableW = spanOf(cellW, gap);\n const tableH = spanOf(cellH, gap);\n\n const tableX = Math.max(10, (w - tableW) / 2);\n const centered = top + unitH + Math.max(0, (h - top - unitH - footH - tableH) / 2);\n // 캔버스가 모자라도 각주 위로는 넘지 않는다\n const tableY = Math.max(top + unitH, Math.min(centered, h - footH - tableH));\n\n // 단위 — 표 오른쪽 끝에 맞춘다\n if (data.unit) {\n ctx.font = getFont(options.fontSize.dataLabel, font, customFont, 'bold');\n ctx.fillStyle = '#000';\n ctx.textAlign = 'right';\n ctx.textBaseline = 'bottom';\n ctx.fillText(data.unit, tableX + tableW, tableY - 10);\n }\n\n // 칸 — i번째 줄은 값 i개 다음에 이름 한 개다 (그래서 계단이 된다)\n ctx.textBaseline = 'middle';\n for (let i = 0; i < n; i++) {\n const y = tableY + (cellH + gap) * i;\n for (let j = 0; j <= i; j++) {\n const x = tableX + (cellW + gap) * j;\n const isName = j === i;\n\n ctx.fillStyle = isName ? (data.nameFill ?? NAME_FILL) : '#fff';\n ctx.fillRect(x, y, cellW, cellH);\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 1.2;\n ctx.strokeRect(x, y, cellW, cellH);\n\n ctx.fillStyle = '#000';\n if (isName) {\n // 시험지 관습 — 기호((가)·A)는 명조, 실제 지명은 고딕이다\n const family = data.nameIsSymbol && !data.nameIsSymbol[i] ? 'sans' : font;\n ctx.font = getFont(cellFontSize, family, customFont, 'bold');\n ctx.textAlign = 'center';\n ctx.fillText(data.names[i], x + cellW / 2, y + cellH / 2);\n } else {\n // 값은 오른쪽 정렬 — 자릿수가 달라도 끝이 맞아야 읽힌다\n ctx.font = getFont(cellFontSize, font, customFont, 'bold');\n ctx.textAlign = 'right';\n ctx.fillText(\n formatValue(data.values[i]?.[j] ?? 0, data.groupThousands),\n x + cellW - pad,\n y + cellH / 2,\n );\n }\n }\n }\n\n drawTitle({ ctx, plotX: tableX, plotW: tableW, title: options.title, fontSize: options.fontSize.title, canvasWidth: w });\n drawSourceAndFootnote({\n ctx, plotX: tableX, plotW: tableW, height: h,\n source: options.source, footnotes: options.footnotes,\n fontSize: options.fontSize.dataLabel, canvasWidth: w,\n });\n}\n\n/** 실제로 그려지는 값들 (아래 삼각형) — 칸 너비를 재는 데 쓴다 */\nfunction cellValues(data: MatrixTableData): number[] {\n const out: number[] = [];\n for (let i = 0; i < data.names.length; i++) {\n for (let j = 0; j < i; j++) out.push(data.values[i]?.[j] ?? 0);\n }\n return out;\n}\n\nfunction formatValue(v: number, groupThousands = true): string {\n const rounded = Number.isInteger(v) ? v : Math.round(v);\n if (!groupThousands) return String(rounded);\n // toLocaleString 은 실행 환경의 로캘을 타므로 직접 끊는다 (서버·브라우저 동일해야 한다)\n return String(rounded).replace(/\\B(?=(\\d{3})+(?!\\d))/g, ',');\n}\n","// © 2026 김용현\n// 항목 × 지역 데이터 표 — 머리글 행에 지역 기호, 왼쪽 열에 항목 이름.\n//\n// 이 렌더러는 GeoGrapher 원본에 없다. GeoTester 에서 새로 만든 것이라\n// `verify:port` 대조 대상이 아니다. (CHANGES.md 참조)\n//\n// 계단식 행렬표(MatrixTable)와 달리 칸이 서로 붙어 있다. 원본 시험지의 표는\n// 머리글 행만 회색이고, 항목 이름은 가운데, 값은 오른쪽으로 정렬한다.\nimport { type DataTableData, type DataTableRow, type GraphOptions } from '../types/index';\nimport { clearCanvas, getFont } from '../canvas/renderer';\nimport { drawTitle, drawSourceAndFootnote } from '../canvas/labels';\n\nconst HEADER_FILL = '#d9d9d9';\n/** 칸 높이 (글자 크기 기준) */\nconst CELL_H_RATIO = 1.7;\n/** 칸 안쪽 좌우 여백 (글자 크기 기준) */\nconst PAD_RATIO = 0.75;\n/** 항목 이름 뒤 괄호 단위는 이름보다 작게 쓴다 */\nconst UNIT_RATIO = 0.8;\n\nexport function renderDataTable(\n ctx: CanvasRenderingContext2D,\n w: number,\n h: number,\n data: DataTableData,\n options: GraphOptions\n) {\n clearCanvas(ctx, w, h);\n\n const cols = data.columns.length;\n const rows = data.rows.length;\n if (cols === 0 || rows === 0) return;\n\n const font = options.fontFamily;\n const customFont = options.customFont;\n const base = options.fontSize.tick;\n\n // ── 자연스러운 칸 크기부터 잰다 ──────────────────────────\n ctx.font = getFont(base, font, customFont, 'bold');\n const labelTextW = Math.max(\n ctx.measureText(data.cornerLabel).width,\n ...data.rows.map((r) => rowLabelWidth(ctx, r, base, font, customFont))\n );\n // 값 열은 서로 폭이 같아야 표가 반듯하다 — 가장 넓은 글자에 맞춘다\n const valueTextW = Math.max(\n ...data.columns.map((c) => ctx.measureText(c).width),\n ...data.rows.flatMap((r) =>\n r.values.map((v) => ctx.measureText(formatValue(v, r.decimals, data.groupThousands)).width)\n )\n );\n\n const naturalLabelW = labelTextW + base * PAD_RATIO * 2;\n const naturalValueW = valueTextW + base * PAD_RATIO * 2;\n const naturalCellH = base * CELL_H_RATIO;\n\n const top = options.title ? 100 : 30;\n // 각주 블록은 마지막 줄의 글자 높이만큼 위로 더 올라간다 (drawSourceAndFootnote).\n // 그 몫을 안 빼면 표 마지막 줄과 겹친다.\n const footCount = options.footnotes.filter((f) => f.trim()).length;\n const footH =\n (footCount > 0 ? footCount * 22 + options.fontSize.dataLabel + 12 : 0) +\n (options.source ? options.fontSize.dataLabel + 4 : 0);\n\n // 항목 이름이 길면 표가 캔버스를 넘는다. 그럴 때는 표 전체를 줄여 넣는다 —\n // 잘리는 것보다 작아지는 편이 낫다. (MatrixTable 과 같은 규칙)\n const fit = Math.min(\n 1,\n (w - 20) / (naturalLabelW + naturalValueW * cols),\n (h - top - footH - 10) / (naturalCellH * (rows + 1))\n );\n\n const cellFontSize = base * fit;\n const labelW = naturalLabelW * fit;\n const valueW = naturalValueW * fit;\n const cellH = naturalCellH * fit;\n const pad = cellFontSize * PAD_RATIO;\n\n const tableW = labelW + valueW * cols;\n const tableH = cellH * (rows + 1);\n\n const tableX = Math.max(10, (w - tableW) / 2);\n const centered = top + Math.max(0, (h - top - footH - tableH) / 2);\n // 캔버스가 모자라도 각주 위로는 넘지 않는다\n const tableY = Math.max(top, Math.min(centered, h - footH - tableH));\n\n const columnX = (j: number) => tableX + labelW + valueW * j;\n\n // ── 머리글 행 ────────────────────────────────────────\n ctx.fillStyle = data.headerFill ?? HEADER_FILL;\n ctx.fillRect(tableX, tableY, tableW, cellH);\n\n ctx.fillStyle = '#000';\n ctx.textBaseline = 'middle';\n ctx.textAlign = 'center';\n ctx.font = getFont(cellFontSize, font, customFont, 'bold');\n ctx.fillText(data.cornerLabel, tableX + labelW / 2, tableY + cellH / 2, labelW - pad * 2);\n\n data.columns.forEach((name, j) => {\n // 시험지 관습 — 기호((가)·A)는 명조, 실제 지명은 고딕이다\n const family = data.columnIsSymbol && !data.columnIsSymbol[j] ? 'sans' : font;\n ctx.font = getFont(cellFontSize, family, customFont, 'bold');\n ctx.fillText(name, columnX(j) + valueW / 2, tableY + cellH / 2, valueW - pad * 2);\n });\n\n // ── 값 행 ────────────────────────────────────────────\n data.rows.forEach((row, i) => {\n const y = tableY + cellH * (i + 1);\n\n drawRowLabel(ctx, row, tableX + labelW / 2, y + cellH / 2, cellFontSize, font, customFont);\n\n ctx.font = getFont(cellFontSize, font, customFont, 'bold');\n ctx.textAlign = 'right';\n for (let j = 0; j < cols; j++) {\n const v = row.values[j];\n if (v === undefined) continue;\n // 값은 오른쪽 정렬 — 자릿수가 달라도 끝이 맞아야 읽힌다\n ctx.fillText(\n formatValue(v, row.decimals, data.groupThousands),\n columnX(j) + valueW - pad,\n y + cellH / 2,\n valueW - pad * 2\n );\n }\n ctx.textAlign = 'center';\n });\n\n // ── 선 ───────────────────────────────────────────────\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 1;\n for (let i = 1; i <= rows; i++) {\n const y = tableY + cellH * i;\n line(ctx, tableX, y, tableX + tableW, y);\n }\n line(ctx, tableX + labelW, tableY, tableX + labelW, tableY + tableH);\n for (let j = 1; j < cols; j++) {\n line(ctx, columnX(j), tableY, columnX(j), tableY + tableH);\n }\n\n // 바깥 테두리는 굵게 — 원본 시험지가 그렇다\n ctx.lineWidth = 2;\n ctx.strokeRect(tableX, tableY, tableW, tableH);\n\n drawTitle({ ctx, plotX: tableX, plotW: tableW, title: options.title, fontSize: options.fontSize.title, canvasWidth: w });\n drawSourceAndFootnote({\n ctx, plotX: tableX, plotW: tableW, height: h,\n source: options.source, footnotes: options.footnotes,\n fontSize: options.fontSize.dataLabel, canvasWidth: w,\n });\n}\n\nfunction line(ctx: CanvasRenderingContext2D, x1: number, y1: number, x2: number, y2: number) {\n ctx.beginPath();\n ctx.moveTo(x1, y1);\n ctx.lineTo(x2, y2);\n ctx.stroke();\n}\n\n/** 이름 + 단위를 한 덩어리로 보고 잰 폭 */\nfunction rowLabelWidth(\n ctx: CanvasRenderingContext2D,\n row: DataTableRow,\n size: number,\n font: GraphOptions['fontFamily'],\n customFont: string | undefined\n): number {\n ctx.font = getFont(size, font, customFont, 'bold');\n const labelW = ctx.measureText(row.label).width;\n if (!row.unit) return labelW;\n ctx.font = getFont(size * UNIT_RATIO, font, customFont, 'bold');\n return labelW + ctx.measureText(row.unit).width;\n}\n\n/** 이름과 단위를 한 덩어리로 가운데 정렬해 그린다 — 단위만 조금 작다 */\nfunction drawRowLabel(\n ctx: CanvasRenderingContext2D,\n row: DataTableRow,\n cx: number,\n cy: number,\n size: number,\n font: GraphOptions['fontFamily'],\n customFont: string | undefined\n) {\n ctx.fillStyle = '#000';\n ctx.textBaseline = 'middle';\n ctx.font = getFont(size, font, customFont, 'bold');\n const labelW = ctx.measureText(row.label).width;\n\n if (!row.unit) {\n ctx.textAlign = 'center';\n ctx.fillText(row.label, cx, cy);\n return;\n }\n\n ctx.font = getFont(size * UNIT_RATIO, font, customFont, 'bold');\n const unitW = ctx.measureText(row.unit).width;\n const startX = cx - (labelW + unitW) / 2;\n\n ctx.textAlign = 'left';\n ctx.font = getFont(size, font, customFont, 'bold');\n ctx.fillText(row.label, startX, cy);\n ctx.font = getFont(size * UNIT_RATIO, font, customFont, 'bold');\n ctx.fillText(row.unit, startX + labelW, cy);\n ctx.textAlign = 'center';\n}\n\n/**\n * 값 하나를 표에 쓸 글자로 바꾼다.\n *\n * 시험지 표는 소수 한 자리가 기본이다(40.5·-21.4). 자릿수를 맞춰야 칸마다\n * 끝자리가 나란히 선다 — `3.8` 과 `4` 가 섞이면 표가 어수선해진다.\n */\nfunction formatValue(v: number, decimals = 1, groupThousands = true): string {\n if (!Number.isFinite(v)) return '-';\n const fixed = v.toFixed(decimals);\n if (!groupThousands) return fixed;\n // toLocaleString 은 실행 환경의 로캘을 타므로 직접 끊는다 (서버·브라우저 동일해야 한다)\n const [int, frac] = fixed.split('.');\n const grouped = int.replace(/\\B(?=(\\d{3})+(?!\\d))/g, ',');\n return frac ? `${grouped}.${frac}` : grouped;\n}\n","// © 2026 김용현\n// 트리맵 — 값의 비율만큼 넓이를 나눈 사각형들.\n//\n// 이 렌더러는 GeoGrapher 원본에 없다. GeoTester 에서 새로 만든 것이라\n// `verify:port` 대조 대상이 아니다. (CHANGES.md 참조)\n//\n// 배치는 squarified treemap 이다 — 칸이 정사각형에 가깝게 나와야 라벨이 들어가고,\n// 원본 시험지(2024학년도 수능 세계지리 18번)의 모양과도 맞는다.\nimport { type TreemapGraphData, type GraphOptions } from '../types/index';\nimport { clearCanvas, getFont } from '../canvas/renderer';\nimport { drawTitle, drawSourceAndFootnote } from '../canvas/labels';\n\nexport interface TreemapRect {\n x: number;\n y: number;\n w: number;\n h: number;\n}\n\n/** 칸 안쪽 라벨 여백 (글자 크기 기준) */\nconst PAD_RATIO = 0.4;\n/** 라벨을 적을 최소 칸 너비 기본값(px) */\nconst MIN_LABEL_W = 34;\n/**\n * 라벨을 그릴지 말지 가르는 최소 배율(= 최소로 읽을 수 있는 글자 크기 / 기준 크기).\n * `pickLabelLayout`이 고른 자연스러운 배율(`layout.scale`)이 이보다 낮으면\n * 그 칸은 라벨을 아예 생략한다 — 억지로 하한까지 올려 그리지 않는다\n * (2026-08-18, 아래 「하한으로 올려 잡지 않는다」 참고).\n *\n * **값 0.4의 근거** (실측, `world_energy_consumption.csv` 2024년 `topN:6`,\n * 340×400 패널, `@napi-rs/canvas`로 실제 글꼴 측정):\n * 천연가스 패널의 `사우디아라비아`(r.w=95.9, r.h=25.5) 칸이 사용자가 다시\n * 보이게 해 달라고 지목한 칸인데, 그 칸의 자연스러운 배율이 **0.4729**로\n * 나왔다(과거 하한이던 0.5보다 낮다 — 그래서 예전엔 0.5로 밀어 올려져 눌렸다).\n * 0.4는 그보다 약간 낮게 잡아, 뜬 부동소수 오차로 이 칸이 실수로 생략되지\n * 않을 여유를 두면서도(약 15% 여유), 이미 검증된 하한(0.5 = 24px 기준\n * 12px, 예전 주석 \"여전히 읽을 수 있다\")보다 그리 멀지 않은 값이다(24px\n * 기준 9.6px). 실제 세 패널을 통틀어 이보다 낮은 자연 배율은 나타나지\n * 않았다(다음으로 낮은 값은 석유 패널 `일본`의 0.4955 — 이건 폭이 아니라\n * 칸 높이가 좁아 나온 값이고, 아래에서 보듯 이 값 자체가 이번 변경으로\n * 새로 보이게 된다).\n */\nconst MIN_LEGIBLE_SCALE = 0.4;\n/** `pickLabelLayout`이 시도해 보는 줄 수 후보의 상한. 라벨 글자 수보다 많이 시도하지 않는다. */\nconst MAX_LABEL_LINES = 4;\n\n/**\n * 한 줄의 가장 나쁜 가로세로비. 작을수록 정사각형에 가깝다.\n * (Bruls·Huizing·van Wijk, \"Squarified Treemaps\")\n */\nfunction worstRatio(row: number[], sum: number, side: number): number {\n if (row.length === 0 || sum <= 0 || side <= 0) return Infinity;\n const max = Math.max(...row);\n const min = Math.min(...row);\n if (min <= 0) return Infinity;\n const s2 = sum * sum;\n const w2 = side * side;\n return Math.max((w2 * max) / s2, s2 / (w2 * min));\n}\n\n/**\n * 값 하나를 쓸 수 있는 넓이로 바꾼다. 유한한 양수가 아니면 0 이다.\n *\n * ⚠️ `Math.max(0, NaN)` 은 `NaN` 이라 이 걸음이 없으면 값 하나가 NaN·Infinity 인\n * 것만으로 합계가 NaN 이 되고, `NaN <= 0` 은 `false` 라 아래 조기 반환이\n * 잡아내지 못한다. 그러면 **모든** 칸의 너비가 NaN 으로 나오는데, 캔버스는\n * 유한하지 않은 인자를 조용히 무시하므로 아무 것도 안 그려진 채 넘어간다.\n */\nconst clean = (v: number) => (Number.isFinite(v) && v > 0 ? v : 0);\n\n/**\n * 값들을 넓이 비율대로 사각형으로 나눈다. 돌려주는 순서는 넣은 순서와 같다.\n *\n * ⚠️ 동기 루프다. 0 나눗셈으로 두께가 Infinity 가 되면 배치가 무너지고\n * 탭이 멎을 수 있다 — 짧은 변이 0 이거나 합이 0 이면 **일찍 빠져나간다.**\n * `__tests__/treemap-layout.test.ts` 가 지킨다.\n */\nexport function squarify(values: number[], rect: TreemapRect): TreemapRect[] {\n const out: TreemapRect[] = values.map(() => ({ x: rect.x, y: rect.y, w: 0, h: 0 }));\n const total = values.reduce((a, b) => a + clean(b), 0);\n if (values.length === 0 || total <= 0 || rect.w <= 0 || rect.h <= 0) return out;\n\n // 값을 넓이로 바꿔 두면 아래 계산이 전부 넓이 단위로 통일된다\n const areas = values.map((v) => (clean(v) / total) * rect.w * rect.h);\n\n let free = { ...rect };\n let i = 0;\n\n while (i < areas.length) {\n const side = Math.min(free.w, free.h);\n // 남은 틀이 납작해지면 더 나눌 수 없다. 남은 칸은 0 넓이로 둔다.\n if (side <= 0) break;\n\n // 가로세로비가 나빠지기 직전까지 한 줄에 담는다.\n // ⚠️ 첫 바퀴(count === 0)는 절대 break 하지 않으므로 count 는 반드시 1 이상이다\n // — 그래야 아래 `i += count` 가 늘 나아가고 바깥 루프가 멎는다.\n let count = 0;\n let sum = 0;\n while (i + count < areas.length) {\n const next = areas[i + count];\n const cur = areas.slice(i, i + count);\n // ⚠️ `sum <= 0` 을 빼면 안 된다. 0 인 값이 줄의 **첫** 칸이 되면\n // (첫 바퀴는 break 하지 않으므로 막을 수 없다) 이후 비교가 늘\n // `Infinity > Infinity` = false 가 되어 **남은 값이 전부 한 줄로\n // 쓸려 들어간다.** 0 칸 자신은 넓이가 없어 안 보이지만 형제 칸의\n // 가로세로비가 15:1 까지 망가진다. 넓이가 없는 줄은 바로 닫는다.\n if (count > 0 && (sum <= 0 || worstRatio([...cur, next], sum + next, side) > worstRatio(cur, sum, side))) break;\n sum += next;\n count++;\n }\n\n const row = areas.slice(i, i + count);\n const thickness = sum / side;\n // 틀의 긴 변이 가로면 줄은 세로로 선 '열'이 된다\n const vertical = free.w >= free.h;\n\n let offset = 0;\n for (let k = 0; k < row.length; k++) {\n const len = thickness > 0 ? row[k] / thickness : 0;\n out[i + k] = vertical\n ? { x: free.x, y: free.y + offset, w: thickness, h: len }\n : { x: free.x + offset, y: free.y, w: len, h: thickness };\n offset += len;\n }\n\n // 남은 틀은 음수가 될 수 없다. 부동소수 오차로 -5.7e-14 같은 값이 남으면\n // 위의 `side <= 0` 이 한 줄 일찍 터진다 — 0 으로 눌러 둔다.\n // (`side <= 0` 쪽에 오차 여유를 주는 건 오히려 나쁘다. 정말 작지만\n // 멀쩡한 나머지까지 함께 버리게 된다.)\n free = vertical\n ? { x: free.x + thickness, y: free.y, w: Math.max(0, free.w - thickness), h: free.h }\n : { x: free.x, y: free.y + thickness, w: free.w, h: Math.max(0, free.h - thickness) };\n i += count;\n }\n\n return out;\n}\n\n/**\n * 라벨을 글자 수로 정확히 `n`줄(글자 수가 모자라면 그보다 적게)로 고르게 나눈다.\n * 한글 지명은 띄어쓰기가 없는 편이라 단어 단위로 자를 수 없어 글자 수로 나눈다.\n *\n * `wrapLabel`과 `pickLabelLayout`이 공유하는 가장 낮은 층의 도우미다 —\n * 실제로 몇 조각이 나오는지는 `text.length`와 `n`의 나눗셈에 달려 있어\n * (예: 5자를 4줄 요청해도 `ceil(5/4)=2`자씩 잘라 3조각만 나온다) 두 함수가\n * 서로 다르게 자르면 같은 라벨이 호출부에 따라 다르게 보이게 된다.\n */\nfunction splitIntoLines(text: string, n: number): string[] {\n const lineCount = Math.min(Math.max(n, 1), text.length);\n if (lineCount <= 1) return [text];\n const perLine = Math.ceil(text.length / lineCount);\n const lines: string[] = [];\n for (let i = 0; i < text.length; i += perLine) lines.push(text.slice(i, i + perLine));\n return lines;\n}\n\n/**\n * 칸에 들어가게 라벨을 필요한 만큼(최대 `maxLines`줄) 나눈다.\n *\n * `renderTreemapGraph`는 이제 이 함수를 직접 부르지 않는다 — 아래\n * `pickLabelLayout`이 줄 수와 글꼴 크기를 함께 고르기 때문이다(이유는\n * 그 함수의 설명 참고). 이 함수는 \"칸 높이가 감당하는 줄 수(`maxLines`)가\n * 이미 정해져 있고, 폭 기준으로 필요한 줄 수만 고르면 되는\" 경우를 위해\n * 공개 유틸로 남겨 둔다(패키지 추출 후 외부에서 같은 문제를 풀 수도 있다).\n * 예전 동작(글자 수로 필요한 줄 수를 어림잡고 자름)은 그대로다.\n */\nexport function wrapLabel(ctx: CanvasRenderingContext2D, text: string, maxW: number, maxLines: number): string[] {\n if (ctx.measureText(text).width <= maxW || text.length < 2 || maxLines <= 1) return [text];\n // 전체 폭이 칸 폭의 몇 배인지로 필요한 줄 수를 어림잡고, 칸 높이가 감당할\n // 수 있는 줄 수(maxLines)와 글자 수를 넘지 않게 자른다.\n const fullWidth = ctx.measureText(text).width;\n const needed = Math.ceil(fullWidth / Math.max(maxW, 1));\n const lineCount = Math.min(Math.max(needed, 2), maxLines, text.length);\n return splitIntoLines(text, lineCount);\n}\n\nexport interface LabelLayout {\n /** 실제로 그릴 줄들 */\n lines: string[];\n /**\n * 기준 글자 크기(`labelSize`)에 곱할 배율. 1을 넘지 않는다.\n *\n * ⚠️ 호출부는 이 값을 하한으로 올려 잡지 않는다(2026-08-18) — 그리로 밀어\n * 올리면 `byWidth`·`byHeight`가 세워 둔 \"칸에 맞는다\"는 보장이 깨진다.\n * 대신 `shouldOmitLayout`으로 너무 작은 경우(`MIN_LEGIBLE_SCALE` 미만)만\n * 걸러 아예 그리지 않는다 — 자세한 이유는 `pickLabelLayout` 설명 참고.\n */\n scale: number;\n}\n\n/**\n * 줄 수와 글꼴 크기를 **함께** 고른다.\n *\n * 예전 방식은 순서로 매겼다 — 칸 높이만 보고 줄 수(`maxLines`)를 먼저 정한\n * 뒤(그것도 하한 크기를 가정해서), 그 줄 수 안에서만 `wrapLabel`이 폭을\n * 맞췄다. 둘은 사실 서로 의존하는 값인데 순서를 매기면 좁고 낮은 칸에서\n * 못 푼다: 340×400 패널의 `인도네시아`·`러시아`(행 높이 30px 안팎)는\n * `r.h / (24 * 0.5 * 1.2) ≈ 1.7~2`로 내림해 1줄로 굳어 버리는데, 2줄이면\n * 하한(0.5)보다 살짝 낮은 배율로도 칸에 들어갈 수 있는데도 시도조차 못 했다.\n *\n * 그래서 줄 수 후보 `n`(1~`maxN`, 라벨 글자 수도 넘지 않는다)마다 실제로\n * 몇 조각이 되는지 나눠 보고, 그 조각들이 칸 너비·높이 각각에 맞으려면\n * 기준 크기(`labelSize`)에서 몇 배(`byWidth`·`byHeight`)로 줄여야 하는지\n * 재서 **더 작은 쪽**(`Math.min(1, byWidth, byHeight)`)을 그 줄 수의 배율로\n * 삼는다. 배율이 가장 큰(=글자가 가장 큰) 줄 수를 고른다 — 같으면 줄이\n * 적은 쪽이 읽기 편하므로 먼저 본 `n`(더 작은 쪽)을 그대로 쓴다(`>`로만\n * 교체하므로 자동으로 그렇게 된다).\n *\n * `byWidth`·`byHeight`를 각각 재서 더 작은 쪽을 쓰기 때문에, 이 함수가\n * 돌려주는 `scale`로 그리면 **폭·높이 둘 다 저절로 칸에 맞는다** — 증명:\n * `scale <= byWidth = avail / widest` 이므로 `widest * scale <= avail`\n * (그린 줄의 폭이 여유 폭을 넘지 않는다), `scale <= byHeight = cellH /\n * (n * labelSize * 1.2)` 이므로 `n * labelSize * scale * 1.2 <= cellH`\n * (그린 블록의 세로 높이가 칸을 넘지 않는다). 호출부는 이제 이 `scale`을\n * **그대로** 쓴다(2026-08-18 이전에는 하한(`MIN_LABEL_SHRINK`, 0.5)보다\n * 작으면 그 하한으로 밀어 올렸는데, 그러면 방금 세운 두 부등식이 깨져\n * 가로로는 `maxWidth`가 글자를 눌러 찍고 세로로는 그대로 넘쳤다 — 아래\n * `shouldOmitLayout` 참고). `fillText`의 `maxWidth` 인자는 그래도 남겨\n * 둔다 — 부동소수 오차에 대한 최후의 안전장치일 뿐, 정상 경로에서는\n * 이제 실제로 걸리지 않는다.\n */\nexport function pickLabelLayout(\n ctx: CanvasRenderingContext2D,\n text: string,\n avail: number,\n cellH: number,\n labelSize: number,\n maxN: number = MAX_LABEL_LINES,\n): LabelLayout {\n const cap = Math.max(1, Math.min(maxN, text.length));\n const layoutFor = (n: number): LabelLayout => {\n const lines = splitIntoLines(text, n);\n const widest = Math.max(...lines.map((line) => ctx.measureText(line).width));\n // 글자가 없거나 너비가 0이면 폭은 제약이 아니다 — Infinity 로 두면 아래\n // Math.min 에서 자연히 빠진다.\n const byWidth = widest > 0 ? avail / widest : Infinity;\n const byHeight = cellH / (lines.length * labelSize * 1.2);\n return { lines, scale: Math.min(1, byWidth, byHeight) };\n };\n\n let best = layoutFor(1);\n for (let n = 2; n <= cap; n++) {\n const candidate = layoutFor(n);\n if (candidate.scale > best.scale) best = candidate;\n }\n return best;\n}\n\n/**\n * 칸이 라벨을 그리기에 너무 작은지, 텍스트를 보기도 전에 **칸 크기만으로**\n * 값싸게 미리 거르는 판정이다(`ctx.measureText`도, 라벨 글자도 필요 없다).\n *\n * 너비 쪽(`minLabelW`)은 실제 하한이다 — `renderTreemapGraph`가 그대로 쓰는\n * `data.minLabelWidth` 값이고, 이보다 좁으면 글자가 옆 칸을 밟는다.\n *\n * 높이 쪽은 \"가장 작게 그릴 수 있는 크기(`MIN_LEGIBLE_SCALE`)로 한 줄만\n * 그려도 칸에 안 들어가면, 몇 줄로 나누든(더 나누면 오히려 세로로 더\n * 필요해진다) 어차피 못 그린다\"는 최선의 경우로 잰다 — 그래서\n * `pickLabelLayout`을 실제로 부르기 전에 이 칸을 걸러도 안전하다. **다만\n * 이 판정을 통과했다고 반드시 그려지는 건 아니다** — 라벨이 길어 폭 쪽이\n * 배율을 더 깎으면 실제 배율이 `MIN_LEGIBLE_SCALE` 밑으로 떨어질 수 있고,\n * 그건 텍스트를 알아야(`pickLabelLayout`을 불러야) 잴 수 있다. 그 마무리\n * 판정은 `shouldOmitLayout`이 한다.\n *\n * (예전 기록: 이 높이 기준은 처음엔 축소 전 `labelSize`를 그대로 썼다가\n * (`r.h < labelSize * 1.2`), 그다음엔 옛 클램프 하한이던 `MIN_LABEL_SHRINK`\n * (0.5)로 낮췄다 — 그때마다 340×400 패널의 짧은 칸(천연가스의 `캐나다`·\n * `사우디아라비아` 등)이 부당하게 생략되고 있던 것을 하나씩 고쳤다. 이제는\n * 클램프 자체가 없어져 그 하한이 곧 \"이 배율 밑이면 아예 안 그린다\"는\n * 진짜 기준(`MIN_LEGIBLE_SCALE`)과 같아졌으므로, 두 상수를 하나로 합쳤다.)\n */\nexport function shouldOmitLabel(r: TreemapRect, minLabelW: number, labelSize: number): boolean {\n return r.w < minLabelW || r.h < labelSize * MIN_LEGIBLE_SCALE * 1.2;\n}\n\n/**\n * `pickLabelLayout`이 고른 배율이 너무 작아 그려도 못 읽을 지경인지 판정한다.\n *\n * `shouldOmitLabel`과 짝을 이룬다 — 저쪽은 칸 크기만으로(텍스트를 보기 전에)\n * 미리 거르는 값싼 관문이고, 이쪽은 실제 라벨 텍스트로 `pickLabelLayout`을\n * 부른 **뒤** 그 결과(`layout.scale`)로 최종 판정한다. 라벨이 길어 폭이\n * 배율을 깎는 경우는 텍스트를 몰라선 알 수 없어 `shouldOmitLabel` 혼자로는\n * 못 잡는다 — 예: 칸은 넉넉해 보여도(`minLabelW`·높이 기준을 둘 다 통과)\n * 라벨이 아주 길면 `pickLabelLayout`이 고르는 최선의 배율이 그래도 낮을 수\n * 있다.\n *\n * 2026-08-18 이전에는 이런 칸도 그냥 하한(`MIN_LABEL_SHRINK`)까지 밀어\n * 올려 그렸다(→ 가로 눌림·세로 넘침, `TreemapGraph.ts` 파일 머리말 참고).\n * 이제는 밀어 올리는 대신 아예 생략한다 — 아주 좁은 칸에서 라벨을 생략하는\n * 것과 같은 종류의 판단이다(사용자 결정, 2026-08-18).\n */\nexport function shouldOmitLayout(layout: LabelLayout, minScale: number = MIN_LEGIBLE_SCALE): boolean {\n return layout.scale < minScale;\n}\n\n/**\n * 실제로 그릴 글자 크기를 정한다.\n *\n * `pickLabelLayout`이 고른 배율(`layout.scale`)을 **그대로** 곱할 뿐, 하한으로\n * 밀어 올리지 않는다(2026-08-18) — 밀어 올리면 `pickLabelLayout`이 세워 둔\n * \"칸에 맞는다\"는 보장이 깨져 가로 눌림·세로 넘침이 함께 생긴다(파일\n * 머리말·`pickLabelLayout`·`LabelLayout.scale` 설명 참고). 너무 작아 아예\n * 그리지 말아야 하는 경우는 이 함수가 아니라 `shouldOmitLayout`이 미리\n * 걸러낸다는 전제다 — 이 함수는 \"그릴 값이 이미 정해졌을 때 크기가 얼마냐\"만\n * 답한다.\n *\n * 별도 함수로 뽑아 둔 이유: 이전에는 이 한 줄(`Math.max(하한, …)`)이\n * `renderTreemapGraph` 안에 인라인으로 있어 캔버스 없이는 검증할 수 없었다\n * (골든 이미지는 800×600 한 장이라 이 결함이 걸리는 실제 340px 패널 크기의\n * 좁은 칸을 못 지난다). 함수로 뽑으면 `pickLabelLayout`이 이미 검증한\n * `scale`을 받아 결과만 재는 단위 테스트가 가능해, 클램프가 다시 들어와도\n * (`Math.max`로 되돌려도) 실패로 잡아낸다.\n */\nexport function resolveDrawSize(labelSize: number, layout: LabelLayout): number {\n return labelSize * layout.scale;\n}\n\nexport function renderTreemapGraph(\n ctx: CanvasRenderingContext2D,\n w: number,\n h: number,\n data: TreemapGraphData,\n options: GraphOptions\n) {\n clearCanvas(ctx, w, h);\n if (data.cells.length === 0) return;\n\n const top = options.title ? 100 : 30;\n // 각주 블록은 마지막 줄의 글자 높이만큼 위로 더 올라간다 (drawSourceAndFootnote).\n // 그 몫을 안 빼면 마지막 칸과 겹친다. (DataTable 과 같은 계산)\n const footCount = options.footnotes.filter((f) => f.trim()).length;\n // reserveSourceSpace 이면 출처 글이 없어도 한 줄만큼 비워 둔다 — 패널을\n // 나란히 놓을 때 출처가 있는 패널만 상자가 짧아지는 것을 막는다.\n const hasSourceLine = !!options.source || !!data.reserveSourceSpace;\n const footH =\n (footCount > 0 ? footCount * 22 + options.fontSize.dataLabel + 12 : 0) +\n (hasSourceLine ? options.fontSize.dataLabel + 4 : 0);\n\n const frame: TreemapRect = {\n x: 20,\n y: top,\n w: Math.max(0, w - 40),\n h: Math.max(0, h - top - footH - 10),\n };\n\n const rects = squarify(data.cells.map((c) => c.value), frame);\n\n const labelSize = data.labelFontSize ?? options.fontSize.tick;\n const minLabelW = data.minLabelWidth ?? MIN_LABEL_W;\n const pad = labelSize * PAD_RATIO;\n\n // ── 칸 ──────────────────────────────────────────────\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 1;\n rects.forEach((r) => {\n if (r.w <= 0 || r.h <= 0) return;\n // 원본 시험지는 칸을 칠하지 않는다 — 흰 바탕에 선만 있다\n ctx.strokeRect(r.x, r.y, r.w, r.h);\n });\n\n // ── 라벨 ────────────────────────────────────────────\n ctx.fillStyle = '#000';\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.font = getFont(labelSize, options.fontFamily, options.customFont);\n\n rects.forEach((r, i) => {\n if (shouldOmitLabel(r, minLabelW, labelSize)) return;\n const avail = r.w - pad * 2;\n ctx.font = getFont(labelSize, options.fontFamily, options.customFont);\n // 줄 수와 글꼴 크기를 함께 고른다 — 칸 너비·높이를 둘 다 만족하는 가장 큰\n // 배율을 주는 줄 수를 찾는다(순서로 매기면 좁고 낮은 칸을 못 푼다,\n // `pickLabelLayout` 설명 참고).\n const layout = pickLabelLayout(ctx, data.cells[i].label, avail, r.h, labelSize);\n // 배율이 너무 작으면(=글자가 너무 작아 못 읽을 지경이면) 아예 그리지\n // 않는다 — 하한으로 밀어 올리지 않는다(2026-08-18). 밀어 올리면\n // `pickLabelLayout`이 세워 둔 \"칸에 맞는다\"는 보장이 깨진다(파일\n // 머리말·`pickLabelLayout` 설명 참고).\n if (shouldOmitLayout(layout)) return;\n const drawSize = resolveDrawSize(labelSize, layout);\n if (drawSize !== labelSize) {\n ctx.font = getFont(drawSize, options.fontFamily, options.customFont);\n }\n\n const startY = r.y + r.h / 2 - ((layout.lines.length - 1) * drawSize * 1.2) / 2;\n layout.lines.forEach((line, k) => {\n // maxWidth는 순수한 안전장치다 — drawSize가 그 위 pickLabelLayout의\n // scale을 그대로 쓰므로(더는 하한으로 밀어 올리지 않으므로) 정상\n // 경로에서는 이제 실제로 걸리지 않는다. 그래도 남겨 두는 이유는\n // 부동소수 오차 같은 극단적인 경우의 마지막 방어선이기 때문이다.\n ctx.fillText(line, r.x + r.w / 2, startY + k * drawSize * 1.2, avail);\n });\n\n // 다음 칸의 pickLabelLayout 측정이 기준 크기를 쓰도록 되돌린다\n if (drawSize !== labelSize) {\n ctx.font = getFont(labelSize, options.fontFamily, options.customFont);\n }\n });\n\n // 바깥 테두리는 굵게 — 원본 시험지가 그렇다\n ctx.lineWidth = 2;\n ctx.strokeRect(frame.x, frame.y, frame.w, frame.h);\n\n drawTitle({\n ctx, plotX: frame.x, plotW: frame.w, title: options.title,\n fontSize: options.fontSize.title, canvasWidth: w,\n });\n drawSourceAndFootnote({\n ctx, plotX: frame.x, plotW: frame.w, height: h,\n source: options.source, footnotes: options.footnotes,\n fontSize: options.fontSize.dataLabel, canvasWidth: w,\n });\n}\n","// © 2026 김용현\nimport { type ClimateGraphData, type GraphOptions } from '../types/index';\nimport { type Padding, clearCanvas, autoRange, getFont } from '../canvas/renderer';\nimport { drawYAxis, drawXAxis } from '../canvas/axes';\nimport { drawTitle, drawSourceAndFootnote } from '../canvas/labels';\nimport { drawLegend, measureLegendWidth, measureBottomLegend } from '../canvas/legend';\n\nconst MONTH_LABELS = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'];\n\nconst INTERVAL_INDICES: Record<number, number[]> = {\n 12: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],\n 4: [2, 5, 8, 11],\n 2: [0, 6],\n};\n\nexport function renderClimateGraph(\n ctx: CanvasRenderingContext2D,\n w: number,\n h: number,\n data: ClimateGraphData,\n options: GraphOptions\n) {\n clearCanvas(ctx, w, h);\n\n const showLegend = options.showLegend;\n const legendPos = options.legendPosition;\n const legendLabels = [\n options.legendLabel1 || data.precipLabel,\n options.legendLabel2 || data.tempLabel,\n ];\n const legendW = (showLegend && legendPos === 'right')\n ? measureLegendWidth(ctx, legendLabels, options.fontSize.dataLabel * 0.85 + 5)\n : 0;\n\n // 범례가 몇 줄이 될지 먼저 재야 그만큼 아래 여백을 잡을 수 있다\n const legendReserve = (showLegend && legendPos === 'bottom')\n ? measureBottomLegend(ctx, legendLabels, options.fontSize.dataLabel * 0.85 + 5,\n w - 130 - (130 + legendW), ['rect', data.monthInterval === 12 ? 'line' : 'circle'])\n : 0;\n\n const padding: Padding = {\n top: options.title ? 100 : 50,\n right: 130 + legendW,\n bottom: (() => {\n let b = 60;\n if (showLegend && legendPos === 'bottom') b += 70;\n b = Math.max(b, legendReserve);\n if (options.source) b += 30;\n b += options.footnotes.filter(f => f.trim()).length * 22;\n return b;\n })(),\n left: 130,\n };\n\n const plotX = padding.left;\n const plotY = padding.top;\n const plotW = w - padding.left - padding.right;\n const plotH = h - padding.top - padding.bottom;\n\n const temps = data.months.map((m) => m.temp);\n const precips = data.months.map((m) => m.precip);\n\n // 축 범위 계산\n const tempAxis = data.tempRange.auto\n ? autoRange(temps, 6)\n : {\n min: data.tempRange.min,\n max: data.tempRange.max,\n step: data.tempRange.step || Math.max(1, Math.round((data.tempRange.max - data.tempRange.min) / 6)),\n };\n\n const precipAxis = data.precipRange.auto\n ? autoRange(precips.filter((v) => v > 0).length > 0 ? precips : [0, 100], 6)\n : {\n min: data.precipRange.min,\n max: data.precipRange.max,\n step: data.precipRange.step || Math.max(1, Math.round((data.precipRange.max - data.precipRange.min) / 6)),\n };\n if (precipAxis.min < 0) precipAxis.min = 0;\n\n const font = options.fontFamily;\n const customFont = options.customFont;\n const indices = INTERVAL_INDICES[data.monthInterval] ?? INTERVAL_INDICES[12];\n\n // 격자선 + Y축 (좌: 기온)\n drawYAxis({\n ctx, padding, width: w, height: h,\n min: tempAxis.min, max: tempAxis.max, step: tempAxis.step,\n label: data.tempLabel,\n side: 'left',\n fontFamily: font, customFont,\n tickFontSize: options.fontSize.tick,\n labelFontSize: options.fontSize.axisLabel,\n drawGrid: true,\n });\n\n // Y축 (우: 강수량)\n drawYAxis({\n ctx, padding, width: w, height: h,\n min: precipAxis.min, max: precipAxis.max, step: precipAxis.step,\n label: data.precipLabel,\n side: 'right',\n fontFamily: font, customFont,\n tickFontSize: options.fontSize.tick,\n labelFontSize: options.fontSize.axisLabel,\n });\n\n // X축 (4개월/2개월은 균등 배치)\n if (data.monthInterval === 12) {\n drawXAxis({\n ctx, padding, width: w, height: h,\n labels: MONTH_LABELS,\n indices,\n fontFamily: font, customFont,\n tickFontSize: options.fontSize.tick,\n labelFontSize: options.fontSize.axisLabel,\n });\n } else {\n const filteredLabels = indices.map((i) => MONTH_LABELS[i]);\n drawXAxis({\n ctx, padding, width: w, height: h,\n labels: filteredLabels,\n fontFamily: font, customFont,\n tickFontSize: options.fontSize.tick,\n labelFontSize: options.fontSize.axisLabel,\n });\n }\n\n // 강수량 막대 (표시 월만)\n const numSlots = data.monthInterval === 12 ? 12 : indices.length;\n const slotW = plotW / numSlots;\n const barWidth = slotW * 0.55;\n\n // 슬롯 인덱스→X좌표 헬퍼\n const slotX = (slotIdx: number) => plotX + slotW * slotIdx + slotW / 2;\n\n ctx.fillStyle = '#AAAAAA';\n for (let s = 0; s < indices.length; s++) {\n const i = indices[s];\n const val = data.months[i].precip;\n if (val <= 0) continue;\n const barH = ((val - precipAxis.min) / (precipAxis.max - precipAxis.min)) * plotH;\n const cx = data.monthInterval === 12 ? slotX(i) : slotX(s);\n const bx = cx - barWidth / 2;\n const by = plotY + plotH - barH;\n ctx.fillRect(bx, by, barWidth, barH);\n\n ctx.strokeStyle = '#444';\n ctx.lineWidth = 1;\n ctx.strokeRect(bx, by, barWidth, barH);\n }\n\n // 데이터 라벨 (강수량)\n if (options.showDataLabels) {\n ctx.fillStyle = '#000';\n ctx.font = getFont(options.fontSize.dataLabel, font, customFont, 'bold');\n ctx.textAlign = 'center';\n ctx.textBaseline = 'bottom';\n for (let s = 0; s < indices.length; s++) {\n const i = indices[s];\n const val = data.months[i].precip;\n if (val <= 0) continue;\n const barH = ((val - precipAxis.min) / (precipAxis.max - precipAxis.min)) * plotH;\n const cx = data.monthInterval === 12 ? slotX(i) : slotX(s);\n const by = plotY + plotH - barH;\n ctx.fillText(String(val), cx, by - 4);\n }\n }\n\n // 기온 꺾은선 (12개월일 때만)\n if (data.monthInterval === 12) {\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 2.5;\n ctx.beginPath();\n for (let i = 0; i < 12; i++) {\n const cx = slotX(i);\n const y =\n plotY + plotH - ((data.months[i].temp - tempAxis.min) / (tempAxis.max - tempAxis.min)) * plotH;\n if (i === 0) ctx.moveTo(cx, y);\n else ctx.lineTo(cx, y);\n }\n ctx.stroke();\n }\n\n // 기온 점 (표시 월만)\n for (let s = 0; s < indices.length; s++) {\n const i = indices[s];\n const cx = data.monthInterval === 12 ? slotX(i) : slotX(s);\n const y =\n plotY + plotH - ((data.months[i].temp - tempAxis.min) / (tempAxis.max - tempAxis.min)) * plotH;\n ctx.fillStyle = '#000';\n ctx.beginPath();\n ctx.arc(cx, y, 5, 0, Math.PI * 2);\n ctx.fill();\n\n if (options.showDataLabels) {\n ctx.fillStyle = '#000';\n ctx.font = getFont(options.fontSize.dataLabel, font, customFont, 'bold');\n ctx.textAlign = 'center';\n ctx.textBaseline = 'bottom';\n ctx.fillText(String(data.months[i].temp), cx, y - 8);\n }\n }\n\n // 제목\n drawTitle({ ctx, plotX, plotW, title: options.title, fontSize: options.fontSize.title, canvasWidth: w });\n\n // (월) 라벨\n ctx.fillStyle = '#000';\n ctx.font = getFont(options.fontSize.tick, font, customFont, 'bold');\n ctx.textAlign = 'center';\n ctx.textBaseline = 'top';\n ctx.fillText('(월)', plotX + plotW + 30, plotY + plotH + 12);\n\n // 범례\n\n if (showLegend) {\n drawLegend({\n ctx,\n items: [\n { type: 'rect', fillStyle: '#AAA', strokeStyle: '#666', label: legendLabels[0] },\n { type: data.monthInterval === 12 ? 'line' : 'circle', fillStyle: '#000', label: legendLabels[1] },\n ],\n position: legendPos,\n plotX, plotY, plotW, plotH,\n canvasW: w, canvasH: h,\n fontSize: options.fontSize.dataLabel * 0.85 + 5,\n rightGap: 80,\n });\n }\n\n // 출처 + 각주\n drawSourceAndFootnote({ ctx, plotX, plotW, height: h, source: options.source, footnotes: options.footnotes, fontSize: options.fontSize.dataLabel });\n}\n","// © 2026 김용현\nimport { type CubeGraphData, type GraphOptions } from '../types/index';\nimport { clearCanvas, getFont } from '../canvas/renderer';\nimport { drawSourceAndFootnote } from '../canvas/labels';\nimport { EDGE, MIN_SCALE, drawFloatingLabel, fillLines, largestFitting, nudgeInside, nudgeLinesInside, textExtent, wrapToWidth } from '../canvas/fit';\n\n// 사각 투영 (oblique / cabinet)\n// 앞면: Z→오른쪽, Y→위 (직사각형)\n// 깊이(X): 좌하 대각선\nconst DEPTH_ANGLE = 35 * Math.PI / 180;\nconst DEPTH_RATIO = 0.5;\nconst DEPTH_COS = Math.cos(DEPTH_ANGLE) * DEPTH_RATIO;\nconst DEPTH_SIN = Math.sin(DEPTH_ANGLE) * DEPTH_RATIO;\n\nfunction project(\n x: number, y: number, z: number,\n cx: number, cy: number, scale: number\n): [number, number] {\n // x=깊이(좌하), y=위, z=오른쪽\n const px = cx + z * scale - x * DEPTH_COS * scale;\n const py = cy - y * scale + x * DEPTH_SIN * scale;\n return [px, py];\n}\n\n// 꼭짓점\nconst V: [number, number, number][] = [\n [0, 0, 0], // 0: 앞-하-좌 (원점)\n [0, 0, 1], // 1: 앞-하-우\n [0, 1, 0], // 2: 앞-상-좌\n [0, 1, 1], // 3: 앞-상-우\n [1, 0, 0], // 4: 뒤-하-좌 (숨김 꼭짓점)\n [1, 0, 1], // 5: 뒤-하-우\n [1, 1, 0], // 6: 뒤-상-좌\n [1, 1, 1], // 7: 뒤-상-우\n];\n\n// 모서리: [시작, 끝, 숨김여부]\n// vertex 4 (1,0,0) 에 연결된 3개가 뒤쪽(점선)\nconst EDGES: [number, number, boolean][] = [\n [4, 0, false],\n [4, 5, false],\n // 나머지 전부 실선\n [4, 6, false], // 뒤하좌 → 뒤상좌 (Y축 방향)\n // 앞면\n [0, 1, false],\n [0, 2, false],\n [1, 3, false],\n [2, 3, false],\n // 깊이 방향\n [1, 5, false],\n [2, 6, false],\n [3, 7, false],\n // 뒷면\n [5, 7, false],\n [6, 7, false],\n];\n\nexport function renderCubeGraph(\n ctx: CanvasRenderingContext2D,\n w: number,\n h: number,\n data: CubeGraphData,\n options: GraphOptions\n) {\n clearCanvas(ctx, w, h);\n\n const font = options.fontFamily;\n const cf = options.customFont;\n const fs = options.fontSize;\n\n const topPad = options.title ? 100 : 60;\n let bottomPad = 40;\n if (options.source) bottomPad += 30;\n bottomPad += options.footnotes.filter(f => f.trim()).length * 22;\n\n const availW = w - 240;\n const availH = h - topPad - bottomPad;\n\n // 240 은 축 이름 「X축」 몫으로 잡은 상수였다. 「1인당 지역내총생산」 같은\n // 이름은 오른쪽으로 58.1px 이 캔버스 밖이었다. 이름이 놓일 자리는 큐브\n // 배율에 딸려 있으므로, 여백을 넓히는 것이 곧 **배율을 줄이는 것**이다.\n const fit = fitCubeScale(ctx, data, w, h, topPad, availH,\n Math.min(availW * 0.5, availH * 0.55), fs, font, cf);\n const scale = fit.scale;\n\n // 큐브 중심을 화면 중심에 맞추기\n const cubeCenter = project(0.5, 0.5, 0.5, 0, 0, scale);\n const cx = w / 2 - cubeCenter[0];\n const cy = topPad + availH / 2 - cubeCenter[1];\n\n // 꼭짓점 2D 좌표\n const v2 = V.map(([vx, vy, vz]) => project(vx, vy, vz, cx, cy, scale));\n\n // 뒤쪽 모서리 (점선)\n ctx.save();\n ctx.strokeStyle = '#999';\n ctx.lineWidth = 1.5;\n ctx.setLineDash([6, 5]);\n for (const [a, b, back] of EDGES) {\n if (!back) continue;\n ctx.beginPath();\n ctx.moveTo(v2[a][0], v2[a][1]);\n ctx.lineTo(v2[b][0], v2[b][1]);\n ctx.stroke();\n }\n ctx.restore();\n\n // 앞쪽 모서리 (실선)\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 2;\n for (const [a, b, back] of EDGES) {\n if (back) continue;\n ctx.beginPath();\n ctx.moveTo(v2[a][0], v2[a][1]);\n ctx.lineTo(v2[b][0], v2[b][1]);\n ctx.stroke();\n }\n\n // 축 화살표 + 라벨\n drawAxes(ctx, data, cx, cy, scale, w, h, font, cf, fs, fit.names, fit.nameSize);\n\n // 데이터 포인트\n // 큐브 중심 (2D)\n const cubeCenter2D = project(0.5, 0.5, 0.5, cx, cy, scale);\n\n for (const pt of data.points) {\n // 라벨 매핑: 사용자 X=오른쪽(project의 z), Z=깊이(project의 x)\n const [px, py] = project(pt.z, pt.y, pt.x, cx, cy, scale);\n\n // 포인트 (채운 원, 이전 크기)\n ctx.fillStyle = '#000';\n ctx.beginPath();\n ctx.arc(px, py, 14, 0, Math.PI * 2);\n ctx.fill();\n\n // 유도선 방향: 자동(큐브 중심에서 바깥으로) + 수동 오프셋\n let autoDx = px - cubeCenter2D[0];\n let autoDy = py - cubeCenter2D[1];\n const len = Math.sqrt(autoDx * autoDx + autoDy * autoDy) || 1;\n autoDx = (autoDx / len) * 40;\n autoDy = (autoDy / len) * 40;\n\n const dx = autoDx + pt.labelDx;\n const dy = autoDy + pt.labelDy;\n\n // 점 이름은 유도선 끝에 붙는다 — 캔버스를 넘으면 이름과 유도선 끝을\n // **함께** 안으로 민다. 유도선이 그대로 점을 가리키므로 어느 점의\n // 이름인지가 흐려지지 않는다. (「서울특별시 강남구」가 왼쪽으로 53.4px\n // 넘던 자리다. 들어가 있으면 좌표가 한 픽셀도 안 움직인다.)\n ctx.font = getFont(fs.dataLabel + 10, font, cf, 'bold');\n ctx.textAlign = dx >= 0 ? 'left' : 'right';\n ctx.textBaseline = 'middle';\n const anchor = nudgeInside(ctx, pt.label, px + dx + (dx >= 0 ? 4 : -4), py + dy, w, h);\n const lx = anchor.x - (dx >= 0 ? 4 : -4);\n const ly = anchor.y;\n\n // 유도선\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 1.2;\n ctx.beginPath();\n ctx.moveTo(px, py);\n ctx.lineTo(lx, ly);\n ctx.stroke();\n\n // 라벨\n ctx.fillStyle = '#000';\n ctx.fillText(pt.label, anchor.x, anchor.y);\n }\n\n // 큐브 좌우 범위 기준으로 정렬\n const allX = v2.map(([vx]) => vx);\n const cubeLeft = Math.min(...allX) - 30;\n const cubeRight = Math.max(...allX) + 30;\n const plotX = cubeLeft;\n const plotW = cubeRight - cubeLeft;\n\n if (options.title) {\n const yAxisTop = project(0, 1.25, 0, cx, cy, scale);\n ctx.fillStyle = '#000';\n ctx.font = `bold ${fs.title}px 'Noto Sans KR', sans-serif`;\n ctx.textAlign = 'center';\n ctx.textBaseline = 'bottom';\n // 제목은 캔버스 가운데에 놓이므로 길면 양쪽으로 넘친다 — 줄여 담고 민다\n drawFloatingLabel(ctx, options.title, w / 2, yAxisTop[1] - 50, w, h, fs.title,\n (size) => `bold ${size}px 'Noto Sans KR', sans-serif`);\n }\n\n // Z축 이름/높음 라벨 아래 기준\n const zAxisEnd = project(1.25, 0, 0, cx, cy, scale);\n const zHighRef = project(1, 0, 0, cx, cy, scale);\n const sourceY = Math.max(zAxisEnd[1] + 20 + fs.axisLabel, zHighRef[1] + 10 + fs.axisLabel * 0.9) + 47;\n drawSourceAndFootnote({ ctx, plotX, plotW, height: sourceY, source: options.source, footnotes: options.footnotes, fontSize: fs.dataLabel });\n}\n\nfunction drawArrow(ctx: CanvasRenderingContext2D, x1: number, y1: number, x2: number, y2: number) {\n const headLen = 10;\n const angle = Math.atan2(y2 - y1, x2 - x1);\n ctx.beginPath();\n ctx.moveTo(x1, y1);\n ctx.lineTo(x2, y2);\n ctx.stroke();\n ctx.beginPath();\n ctx.moveTo(x2, y2);\n ctx.lineTo(x2 - headLen * Math.cos(angle - 0.4), y2 - headLen * Math.sin(angle - 0.4));\n ctx.lineTo(x2 - headLen * Math.cos(angle + 0.4), y2 - headLen * Math.sin(angle + 0.4));\n ctx.closePath();\n ctx.fill();\n}\n\n/** 축 이름 세 개를 접은 결과 (접을 일이 없으면 한 줄짜리 그대로) */\ninterface AxisNameLines { x: string[]; y: string[]; z: string[] }\n\n/** 축 둘레 글자가 놓이는 자리 — 재는 쪽과 그리는 쪽이 이 하나를 함께 쓴다 */\ninterface AxisText {\n lines: string[];\n x: number;\n y: number;\n align: CanvasTextAlign;\n baseline: CanvasTextBaseline;\n /** 축 이름인지(`name`) 낮음·높음 표시인지(`dir`) — 글꼴이 다르다 */\n kind: 'name' | 'dir';\n}\n\n/** 축 이름의 줄 간격 (글꼴 크기 대비) */\nconst NAME_LINE_RATIO = 1.2;\n\n/** 이름을 접느니 큐브를 줄이겠다고 보는 한계 — 이보다 작아지면 접는다 */\nconst SHRINK_LIMIT = 0.8;\n\nfunction axisTexts(\n data: CubeGraphData,\n cx: number, cy: number, scale: number,\n names: AxisNameLines,\n): AxisText[] {\n const ext = 1.25;\n const xEnd = project(ext, 0, 0, cx, cy, scale);\n const yEnd = project(0, ext, 0, cx, cy, scale);\n const zEnd = project(0, 0, ext, cx, cy, scale);\n const zHighPos = project(1, 0, 0, cx, cy, scale);\n const yHigh = project(0, 1, 0, cx, cy, scale);\n const zHigh = project(0, 0, 1, cx, cy, scale);\n const origin = project(0, 0, 0, cx, cy, scale);\n\n return [\n // 좌하 깊이 → Z축 이름\n { lines: names.z, x: xEnd[0] - 6, y: xEnd[1] + 20, align: 'right', baseline: 'middle', kind: 'name' },\n { lines: [data.zAxis.highLabel], x: zHighPos[0] + 3 + data.zAxis.highOffset.x, y: zHighPos[1] + 10 + data.zAxis.highOffset.y, align: 'center', baseline: 'top', kind: 'dir' },\n // 위 → Y축 이름\n { lines: names.y, x: yEnd[0], y: yEnd[1] - 10, align: 'center', baseline: 'bottom', kind: 'name' },\n { lines: [data.yAxis.highLabel], x: yHigh[0] + 6 + data.yAxis.highOffset.x, y: yHigh[1] - 20 + data.yAxis.highOffset.y, align: 'left', baseline: 'middle', kind: 'dir' },\n // 오른쪽 → X축 이름\n { lines: names.x, x: zEnd[0] + 6, y: zEnd[1], align: 'left', baseline: 'middle', kind: 'name' },\n { lines: [data.xAxis.highLabel], x: zHigh[0] + data.xAxis.highOffset.x, y: zHigh[1] + 14 + data.xAxis.highOffset.y, align: 'center', baseline: 'top', kind: 'dir' },\n // 각 축 낮음 라벨 — 원점(0,0,0) 주변\n { lines: [data.xAxis.lowLabel], x: origin[0] + data.xAxis.lowOffset.x, y: origin[1] + 12 + data.xAxis.lowOffset.y, align: 'center', baseline: 'top', kind: 'dir' },\n { lines: [data.yAxis.lowLabel], x: origin[0] - 10 + data.yAxis.lowOffset.x, y: origin[1] - 4 + data.yAxis.lowOffset.y, align: 'right', baseline: 'bottom', kind: 'dir' },\n { lines: [data.zAxis.lowLabel], x: origin[0] + 10 + data.zAxis.lowOffset.x, y: origin[1] + 12 + data.zAxis.lowOffset.y, align: 'left', baseline: 'top', kind: 'dir' },\n ];\n}\n\n/** 배율 s 에서의 큐브 중심 좌표 */\nfunction centerAt(w: number, topPad: number, availH: number, s: number): [number, number] {\n const c = project(0.5, 0.5, 0.5, 0, 0, s);\n return [w / 2 - c[0], topPad + availH / 2 - c[1]];\n}\n\n/**\n * 축 둘레 글자가 캔버스 안에 들어가는 가장 큰 배율을 찾는다.\n *\n * 배율을 키우면 축 끝이 바깥으로 밀리므로 «들어가는가» 는 배율에 대해\n * 단조롭다 — 이분 탐색이 맞는다. 예전 배율에서 이미 들어가면 **그 값을\n * 그대로** 돌려주므로 멀쩡한 그림은 한 픽셀도 움직이지 않는다.\n *\n * 순서는 (1) 배율을 줄여 자리를 낸다, (2) 큐브가 5분의 1 넘게 줄어들 판이면\n * 축 이름을 여러 줄로 접고 다시 잰다, (3) 그래도 모자라면 이름 글꼴을 줄인다.\n * 어느 단계에서도 이름을 잘라 내지 않는다.\n */\nfunction fitCubeScale(\n ctx: CanvasRenderingContext2D,\n data: CubeGraphData,\n w: number, h: number,\n topPad: number, availH: number,\n maxScale: number,\n fs: GraphOptions['fontSize'],\n font: GraphOptions['fontFamily'],\n cf: string,\n): { scale: number; names: AxisNameLines; nameSize: number } {\n ctx.save();\n\n let nameSize = fs.axisLabel;\n const makeNameFont = (size: number) => getFont(size, font, cf, 'bold');\n const dirFont = getFont(fs.axisLabel * 0.9, font, cf, 'normal');\n let names: AxisNameLines = { x: [data.xAxis.name], y: [data.yAxis.name], z: [data.zAxis.name] };\n\n const fits = (s: number) => {\n const [cx, cy] = centerAt(w, topPad, availH, s);\n return axisTexts(data, cx, cy, s, names).every((t) => {\n ctx.font = t.kind === 'name' ? makeNameFont(nameSize) : dirFont;\n ctx.textAlign = t.align;\n ctx.textBaseline = t.baseline;\n const lineH = nameSize * NAME_LINE_RATIO;\n const es = t.lines.map((l) => textExtent(ctx, l));\n const half = ((t.lines.length - 1) * lineH) / 2;\n return t.x - Math.max(...es.map((e) => e.left)) >= EDGE\n && t.x + Math.max(...es.map((e) => e.right)) <= w - EDGE\n && t.y - half - Math.max(...es.map((e) => e.up)) >= EDGE\n && t.y + half + Math.max(...es.map((e) => e.down)) <= h - EDGE;\n });\n };\n\n let scale = largestFitting(20, maxScale, fits);\n\n if (scale < maxScale * SHRINK_LIMIT) {\n // 예전 배율에서 각 이름이 가로로 쓸 수 있는 몫만큼 접는다\n const [cx, cy] = centerAt(w, topPad, availH, maxScale);\n ctx.font = makeNameFont(nameSize);\n const room = (t: AxisText) => (t.align === 'left' ? w - EDGE - t.x\n : t.align === 'right' ? t.x - EDGE\n : 2 * Math.min(t.x - EDGE, w - EDGE - t.x));\n const at = axisTexts(data, cx, cy, maxScale, names);\n names = {\n z: wrapToWidth(ctx, data.zAxis.name, Math.max(30, room(at[0]))),\n y: wrapToWidth(ctx, data.yAxis.name, Math.max(30, room(at[2]))),\n x: wrapToWidth(ctx, data.xAxis.name, Math.max(30, room(at[4]))),\n };\n scale = largestFitting(20, maxScale, fits);\n }\n\n if (scale <= 20) {\n // 배율을 바닥까지 줄여도 안 들어간다 — 이름 글꼴을 줄여 본다\n nameSize = fs.axisLabel * MIN_SCALE;\n ctx.font = makeNameFont(nameSize);\n names = {\n x: names.x.flatMap((l) => wrapToWidth(ctx, l, Math.max(30, w / 3))),\n y: names.y.flatMap((l) => wrapToWidth(ctx, l, Math.max(30, w / 3))),\n z: names.z.flatMap((l) => wrapToWidth(ctx, l, Math.max(30, w / 3))),\n };\n scale = largestFitting(20, maxScale, fits);\n }\n\n ctx.restore();\n return { scale, names, nameSize };\n}\n\nfunction drawAxes(\n ctx: CanvasRenderingContext2D,\n data: CubeGraphData,\n cx: number, cy: number, scale: number,\n w: number, h: number,\n font: GraphOptions['fontFamily'],\n cf: string,\n fs: GraphOptions['fontSize'],\n names: AxisNameLines,\n nameSize: number,\n) {\n const ext = 1.25;\n\n ctx.strokeStyle = '#000';\n ctx.fillStyle = '#000';\n ctx.lineWidth = 1.5;\n\n // X축 화살표 (깊이, 좌하): 꼭짓점 (1,0,0)에서 바깥으로\n const xStart = project(1, 0, 0, cx, cy, scale);\n const xEnd = project(ext, 0, 0, cx, cy, scale);\n drawArrow(ctx, xStart[0], xStart[1], xEnd[0], xEnd[1]);\n\n // Y축 화살표 (위): 꼭짓점 (0,1,0)에서 바깥으로\n const yStart = project(0, 1, 0, cx, cy, scale);\n const yEnd = project(0, ext, 0, cx, cy, scale);\n drawArrow(ctx, yStart[0], yStart[1], yEnd[0], yEnd[1]);\n\n // Z축 화살표 (오른쪽): 꼭짓점 (0,0,1)에서 바깥으로\n const zStart = project(0, 0, 1, cx, cy, scale);\n const zEnd = project(0, 0, ext, cx, cy, scale);\n drawArrow(ctx, zStart[0], zStart[1], zEnd[0], zEnd[1]);\n\n const nameFont = getFont(nameSize, font, cf, 'bold');\n const dirFont = getFont(fs.axisLabel * 0.9, font, cf, 'normal');\n\n // 배율을 이미 맞췄으므로 여기서 미는 일은 거의 없다. 사용자가 준 오프셋이\n // 캔버스 밖을 가리키는 경우를 위한 마지막 안전장치다.\n ctx.fillStyle = '#000';\n for (const t of axisTexts(data, cx, cy, scale, names)) {\n if (t.lines.every((l) => !l)) continue;\n ctx.font = t.kind === 'name' ? nameFont : dirFont;\n ctx.textAlign = t.align;\n ctx.textBaseline = t.baseline;\n const lineH = nameSize * NAME_LINE_RATIO;\n const at = nudgeLinesInside(ctx, t.lines, t.x, t.y, lineH, w, h);\n fillLines(ctx, t.lines, at.x, at.y, lineH);\n }\n}\n","// © 2026 김용현\n// 모드 A — 월별 편차 (시계열)\nimport { type DeviationAData, type GraphOptions } from '../types/index';\nimport { type Padding, clearCanvas, autoRange, getFont } from '../canvas/renderer';\nimport { drawTitle, drawSourceAndFootnote } from '../canvas/labels';\nimport { drawLegend, drawInsideLegend, measureLegendWidth, measureBottomLegend } from '../canvas/legend';\nimport { EDGE, MIN_SCALE, nudgeInside, shrinkToWidth } from '../canvas/fit';\n\nconst MONTH_LABELS = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'];\n\nconst INTERVAL_INDICES: Record<number, number[]> = {\n 12: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],\n 4: [2, 5, 8, 11],\n 2: [0, 6],\n};\n\nexport function renderDeviationAGraph(\n ctx: CanvasRenderingContext2D,\n w: number,\n h: number,\n data: DeviationAData,\n options: GraphOptions\n) {\n clearCanvas(ctx, w, h);\n\n const showLegend = options.showLegend;\n const legendPos = options.legendPosition;\n const legendLabels = [\n options.legendLabel1 || data.precipLabel,\n options.legendLabel2 || data.tempLabel,\n ];\n const legendW = (showLegend && legendPos === 'right' && !data.insideLegend)\n ? measureLegendWidth(ctx, legendLabels, options.fontSize.dataLabel * 0.85 + 5)\n : 0;\n\n // 축 이름을 세로로 쌓으면 눈금 숫자 바깥에 한 글자 폭이 더 필요하다\n let nameW = 0;\n if (data.tempAxisName || data.precipAxisName) {\n ctx.save();\n ctx.font = getFont(options.fontSize.axisLabel, options.fontFamily, options.customFont, 'bold');\n nameW = ctx.measureText('가').width + 12;\n ctx.restore();\n }\n\n const padRight = 130 + legendW + (data.precipAxisName ? nameW : 0);\n const padLeft = 130 + (data.tempAxisName ? nameW : 0);\n // 범례가 몇 줄이 될지 먼저 재야 그만큼 아래 여백을 잡을 수 있다\n const legendReserve = (showLegend && legendPos === 'bottom' && !data.insideLegend)\n ? measureBottomLegend(ctx, legendLabels, options.fontSize.dataLabel * 0.85 + 5,\n w - padLeft - padRight, ['rect', data.monthInterval === 12 ? 'line' : 'circle'])\n : 0;\n\n const padding: Padding = {\n top: options.title ? 100 : 50,\n right: padRight,\n bottom: (() => {\n let b = 60;\n // 플롯 안에 범례를 그릴 때는 아래에 자리를 비워 둘 이유가 없다\n if (showLegend && legendPos === 'bottom' && !data.insideLegend) b += 60;\n b = Math.max(b, legendReserve);\n if (options.source) b += 30;\n b += options.footnotes.filter(f => f.trim()).length * 22;\n return b;\n })(),\n left: padLeft,\n };\n\n const plotX = padding.left;\n const plotY = padding.top;\n const plotW = w - padding.left - padding.right;\n const plotH = h - padding.top - padding.bottom;\n\n const font = options.fontFamily;\n const customFont = options.customFont;\n const indices = INTERVAL_INDICES[data.monthInterval] ?? INTERVAL_INDICES[12];\n\n // 편차 계산 (월별 기준값)\n const tempDevs = data.months.map((m, i) => m.temp - data.baseMonths[i].temp);\n const precipDevs = data.months.map((m, i) => m.precip - data.baseMonths[i].precip);\n\n const visibleTempDevs = indices.map((i) => tempDevs[i]);\n const visiblePrecipDevs = indices.map((i) => precipDevs[i]);\n\n // 좌축: 강수량 편차\n const precipAxis = data.precipRange.auto\n ? autoRange(visiblePrecipDevs.length > 0 ? visiblePrecipDevs : [0], 6)\n : { min: data.precipRange.min, max: data.precipRange.max, step: data.precipRange.step ?? 0 };\n if (precipAxis.step === 0) {\n precipAxis.step = Math.max(1, Math.round((precipAxis.max - precipAxis.min) / 6));\n }\n if (data.precipRange.auto) {\n const precipAbs = Math.max(Math.abs(precipAxis.min), Math.abs(precipAxis.max));\n precipAxis.min = -precipAbs;\n precipAxis.max = precipAbs;\n }\n\n // 우축: 기온 편차\n const tempAxis = data.tempRange.auto\n ? (() => {\n const r = autoRange(visibleTempDevs.length > 0 ? visibleTempDevs : [0], 6);\n const abs = Math.max(Math.abs(r.min), Math.abs(r.max));\n return { min: -abs, max: abs, step: r.step };\n })()\n : { min: data.tempRange.min, max: data.tempRange.max, step: data.tempRange.step ?? 0 };\n if (tempAxis.step === 0) {\n tempAxis.step = Math.max(0.5, Math.round(((tempAxis.max - tempAxis.min) / 6) * 10) / 10);\n }\n\n // Y축 (좌: 기온, 우: 강수량)\n const tempTickW = drawDeviationYAxis(ctx, padding, w, h, tempAxis, 'left', data.tempLabel, font, customFont, options.fontSize);\n const precipTickW = drawDeviationYAxis(ctx, padding, w, h, precipAxis, 'right', data.precipLabel, font, customFont, options.fontSize);\n\n // X축\n const totalSlots = indices.length;\n const slotW = plotW / totalSlots;\n\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 2;\n ctx.beginPath();\n ctx.moveTo(plotX, plotY + plotH);\n ctx.lineTo(plotX + plotW, plotY + plotH);\n ctx.stroke();\n\n // 위쪽까지 이어 사각 테두리로 감싼다 (좌·우·아래 선은 이미 그려졌다)\n if (data.showFrame) {\n ctx.beginPath();\n ctx.moveTo(plotX, plotY);\n ctx.lineTo(plotX + plotW, plotY);\n ctx.stroke();\n }\n\n ctx.fillStyle = '#000';\n ctx.font = getFont(options.fontSize.tick, font, customFont, 'bold');\n ctx.textAlign = 'center';\n ctx.textBaseline = 'top';\n for (let s = 0; s < totalSlots; s++) {\n const cx = plotX + slotW * s + slotW / 2;\n ctx.fillText(MONTH_LABELS[indices[s]], cx, plotY + plotH + 12);\n }\n\n // 기준선 (0선)\n const zeroYPrecip = plotY + plotH - ((0 - precipAxis.min) / (precipAxis.max - precipAxis.min)) * plotH;\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 1.5;\n ctx.setLineDash([]);\n ctx.beginPath();\n ctx.moveTo(plotX, zeroYPrecip);\n ctx.lineTo(plotX + plotW, zeroYPrecip);\n ctx.stroke();\n\n // 강수량 편차 막대 — +/- 동일 색상\n // monthInterval 이 2면 칸이 둘뿐이라 55%가 148px 까지 벌어진다 — 같은 80px 상한을 둔다.\n // 열두 달(24.75px)·넉 달(74.25px)에서는 걸리지 않아 그림이 그대로다.\n const barWidth = Math.min(slotW * 0.55, 80);\n // 플롯 안 범례가 자료를 덮지 않도록 막대·점이 차지한 자리를 모아 둔다\n const inkRects: { x0: number; y0: number; x1: number; y1: number }[] = [];\n for (let s = 0; s < totalSlots; s++) {\n const i = indices[s];\n const val = precipDevs[i];\n const cx = plotX + slotW * s + slotW / 2;\n const bx = cx - barWidth / 2;\n const valY = plotY + plotH - ((val - precipAxis.min) / (precipAxis.max - precipAxis.min)) * plotH;\n\n ctx.fillStyle = data.barFill ?? (val >= 0 ? '#666' : '#CCC');\n const barTop = Math.min(zeroYPrecip, valY);\n const barH = Math.abs(valY - zeroYPrecip);\n ctx.fillRect(bx, barTop, barWidth, barH);\n ctx.strokeStyle = data.barStroke ?? '#444';\n ctx.lineWidth = 1;\n ctx.strokeRect(bx, barTop, barWidth, barH);\n inkRects.push({ x0: bx, y0: barTop, x1: bx + barWidth, y1: barTop + barH });\n }\n\n // 기온 편차 꺾은선 (12개월일 때만)\n if (data.monthInterval === 12) {\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 2.5;\n ctx.beginPath();\n let prev: { x: number; y: number } | null = null;\n for (let s = 0; s < totalSlots; s++) {\n const i = indices[s];\n const cx = plotX + slotW * s + slotW / 2;\n const y = plotY + plotH - ((tempDevs[i] - tempAxis.min) / (tempAxis.max - tempAxis.min)) * plotH;\n if (s === 0) ctx.moveTo(cx, y);\n else ctx.lineTo(cx, y);\n if (prev) {\n inkRects.push({\n x0: prev.x, y0: Math.min(prev.y, y),\n x1: cx, y1: Math.max(prev.y, y),\n });\n }\n prev = { x: cx, y };\n }\n ctx.stroke();\n }\n\n // 기온 점\n for (let s = 0; s < totalSlots; s++) {\n const i = indices[s];\n const cx = plotX + slotW * s + slotW / 2;\n const y = plotY + plotH - ((tempDevs[i] - tempAxis.min) / (tempAxis.max - tempAxis.min)) * plotH;\n ctx.fillStyle = '#000';\n if (data.markerShape === 'square') {\n ctx.fillRect(cx - 5, y - 5, 10, 10);\n } else {\n ctx.beginPath();\n ctx.arc(cx, y, 5, 0, Math.PI * 2);\n ctx.fill();\n }\n inkRects.push({ x0: cx - 6, y0: y - 6, x1: cx + 6, y1: y + 6 });\n }\n\n // (월) 라벨\n ctx.fillStyle = '#000';\n ctx.font = getFont(options.fontSize.tick, font, customFont, 'bold');\n ctx.textAlign = 'center';\n ctx.textBaseline = 'top';\n ctx.fillText('(월)', plotX + plotW + 30, plotY + plotH + 12);\n\n // 범례\n\n if (showLegend && data.insideLegend) {\n // 시험지는 기온(선)을 위, 강수량(막대)을 아래로 적는다 — 바깥 범례와 순서가 반대다\n drawInsideLegend({\n ctx,\n items: [\n {\n type: data.monthInterval === 12 ? 'line' : 'circle',\n fillStyle: '#000',\n marker: data.markerShape ?? 'circle',\n label: legendLabels[1],\n },\n {\n type: 'rect',\n fillStyle: data.barFill ?? '#888',\n strokeStyle: data.barStroke ?? '#444',\n label: legendLabels[0],\n },\n ],\n corner: data.insideLegend,\n plotX, plotY, plotW, plotH,\n canvasW: w, canvasH: h,\n fontSize: options.fontSize.dataLabel * 0.9,\n font: getFont(options.fontSize.dataLabel * 0.9, font, customFont, 'bold'),\n avoid: inkRects,\n });\n } else if (showLegend) {\n drawLegend({\n ctx,\n items: [\n { type: 'rect', fillStyle: '#888', strokeStyle: '#444', label: legendLabels[0] },\n { type: data.monthInterval === 12 ? 'line' : 'circle', fillStyle: '#000', label: legendLabels[1] },\n ],\n position: legendPos,\n plotX, plotY, plotW, plotH,\n canvasW: w, canvasH: h,\n fontSize: options.fontSize.dataLabel * 0.85 + 5,\n rightGap: 80,\n });\n }\n\n // 축 이름 — 눈금 숫자 바깥에 한 글자씩 세로로 쌓는다\n if (data.tempAxisName) {\n drawVerticalAxisName(ctx, data.tempAxisName, plotX - 22 - tempTickW - nameW / 2, plotY, plotH, h,\n options.fontSize.axisLabel, font, customFont);\n }\n if (data.precipAxisName) {\n drawVerticalAxisName(ctx, data.precipAxisName, plotX + plotW + 22 + precipTickW + nameW / 2, plotY, plotH, h,\n options.fontSize.axisLabel, font, customFont);\n }\n\n drawTitle({ ctx, plotX, plotW, title: options.title, fontSize: options.fontSize.title, canvasWidth: w });\n drawSourceAndFootnote({ ctx, plotX, plotW, height: h, source: options.source, footnotes: options.footnotes, fontSize: options.fontSize.dataLabel, canvasWidth: w });\n}\n\nfunction drawDeviationYAxis(\n ctx: CanvasRenderingContext2D,\n padding: Padding,\n width: number,\n height: number,\n axis: { min: number; max: number; step: number },\n side: 'left' | 'right',\n label: string,\n fontFamily: 'serif' | 'sans' | 'custom',\n customFont: string | undefined,\n fontSize: { tick: number; axisLabel: number }\n): number {\n const plotX = padding.left;\n const plotY = padding.top;\n const plotW = width - padding.left - padding.right;\n const plotH = height - padding.top - padding.bottom;\n const x = side === 'left' ? plotX : plotX + plotW;\n\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 2;\n ctx.beginPath();\n ctx.moveTo(x, plotY);\n ctx.lineTo(x, plotY + plotH);\n ctx.stroke();\n\n ctx.fillStyle = '#000';\n ctx.font = getFont(fontSize.tick, fontFamily, customFont, 'bold');\n ctx.textBaseline = 'middle';\n ctx.textAlign = side === 'left' ? 'right' : 'left';\n\n let maxTickW = 0;\n const tickCount = Math.round((axis.max - axis.min) / axis.step);\n for (let i = 0; i <= tickCount; i++) {\n const val = axis.min + i * axis.step;\n const y = plotY + plotH - ((val - axis.min) / (axis.max - axis.min)) * plotH;\n\n ctx.lineWidth = 1.5;\n ctx.beginPath();\n if (side === 'left') {\n ctx.moveTo(x - 6, y);\n ctx.lineTo(x, y);\n } else {\n ctx.moveTo(x, y);\n ctx.lineTo(x + 6, y);\n }\n ctx.stroke();\n\n const tx = side === 'left' ? x - 12 : x + 12;\n const valStr = Number.isInteger(val) ? val.toString() : val.toFixed(1);\n ctx.fillText(valStr, tx, y);\n maxTickW = Math.max(maxTickW, ctx.measureText(valStr).width);\n }\n\n // 단위 표기는 플롯 **위 여백**에 떠 있다 — 캔버스를 벗어나면 플롯을 줄이는\n // 대신 안으로 민다 (「평년 대비 기온 차이(°C)」가 좌우로 129.5px 넘던 자리).\n if (label) {\n ctx.save();\n const makeFont = (size: number) => getFont(size, fontFamily, customFont, 'bold');\n ctx.fillStyle = '#000';\n ctx.textBaseline = 'bottom';\n ctx.textAlign = side === 'left' ? 'right' : 'left';\n ctx.font = makeFont(shrinkToWidth(ctx, [label], fontSize.axisLabel, width - EDGE * 2, makeFont));\n const labelX = side === 'left' ? x - 12 : x + 12;\n const at = nudgeInside(ctx, label, labelX, plotY - 16, width, height);\n ctx.fillText(label, at.x, at.y);\n ctx.restore();\n }\n\n return maxTickW;\n}\n\n/**\n * 축 이름을 한 글자씩 세로로 쌓아 그린다 (플롯 세로 가운데 정렬).\n *\n * **세로로 세운 이름은 플롯의 «높이»에 갇힌다.** 좌우 여백을 아무리 넓혀도\n * 자리가 늘지 않으므로 다른 글자에 쓰는 «여백을 넓힌다» 가 여기서는 무효다.\n * 줄을 늘리는 수(두 칸으로 세우기)도 세로 이름의 뜻을 흐린다 — 어느 칸부터\n * 읽어야 하는지가 그림에 안 적혀 있다. 그래서 남는 수는 글꼴을 줄이는 것이고,\n * 바닥(`MIN_SCALE`)까지 줄여도 넘치면 플롯 위아래 여백까지 빌린다.\n * 캔버스보다도 긴 이름이라면 그때만 바닥을 깨고 더 줄인다 — 잘라 내는 것보다\n * 작게 쓰는 편이 시험지에서 낫다.\n */\nfunction drawVerticalAxisName(\n ctx: CanvasRenderingContext2D,\n name: string,\n cx: number,\n plotY: number,\n plotH: number,\n canvasH: number,\n fontSize: number,\n fontFamily: 'serif' | 'sans' | 'custom',\n customFont: string | undefined\n) {\n const chars = [...name].filter((c) => c.trim() !== '');\n if (chars.length === 0) return;\n\n const stack = (size: number) => (chars.length - 1) * size * 1.15 + size;\n let size = fontSize;\n if (stack(size) > plotH) {\n size = Math.max(fontSize * MIN_SCALE, plotH / stack(1));\n }\n if (stack(size) > canvasH - EDGE * 2) size = (canvasH - EDGE * 2) / stack(1);\n\n ctx.save();\n ctx.font = getFont(size, fontFamily, customFont, 'bold');\n ctx.fillStyle = '#000';\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n const lineH = size * 1.15;\n // 플롯 가운데에 맞추되, 캔버스를 넘으면 위아래 여백 쪽으로 민다\n const half = ((chars.length - 1) * lineH) / 2;\n let mid = plotY + plotH / 2;\n const top = mid - half - size / 2;\n const bottom = mid + half + size / 2;\n if (top < EDGE) mid += EDGE - top;\n else if (bottom > canvasH - EDGE) mid -= bottom - (canvasH - EDGE);\n let y = mid - half;\n for (const c of chars) {\n ctx.fillText(c, cx, y);\n y += lineH;\n }\n ctx.restore();\n}\n","// © 2026 김용현\n// 모드 B — 지역별 편차 (비교형)\nimport { type DeviationBData, type GraphOptions } from '../types/index';\nimport { type Padding, clearCanvas, autoRange, getFont } from '../canvas/renderer';\nimport { drawTitle, drawSourceAndFootnote } from '../canvas/labels';\nimport { drawFloatingLabel } from '../canvas/fit';\nimport { drawLegend, measureLegendWidth, measureBottomLegend } from '../canvas/legend';\n\nexport function renderDeviationBGraph(\n ctx: CanvasRenderingContext2D,\n w: number,\n h: number,\n data: DeviationBData,\n options: GraphOptions\n) {\n clearCanvas(ctx, w, h);\n\n const showLegend = options.showLegend;\n const legendPos = options.legendPosition;\n const legendLabels = [\n options.legendLabel1 || data.precipDiffLabel,\n options.legendLabel2 || data.tempDiffLabel,\n ];\n const legendW = (showLegend && legendPos === 'right')\n ? measureLegendWidth(ctx, legendLabels, options.fontSize.dataLabel * 0.85 + 5)\n : 0;\n\n // 범례가 몇 줄이 될지 먼저 재야 그만큼 아래 여백을 잡을 수 있다\n const legendReserve = (showLegend && legendPos === 'bottom')\n ? measureBottomLegend(ctx, legendLabels, options.fontSize.dataLabel * 0.85 + 5,\n w - 130 - (130 + legendW), ['rect', 'circle'])\n : 0;\n\n const padding: Padding = {\n top: options.title ? 100 : 50,\n right: 130 + legendW,\n bottom: (() => {\n let b = 60;\n if (showLegend && legendPos === 'bottom') b += 60;\n b = Math.max(b, legendReserve);\n if (options.source) b += 30;\n b += options.footnotes.filter(f => f.trim()).length * 22;\n return b;\n })(),\n left: 130,\n };\n\n const plotX = padding.left;\n const plotY = padding.top;\n const plotW = w - padding.left - padding.right;\n const plotH = h - padding.top - padding.bottom;\n\n const font = options.fontFamily;\n const customFont = options.customFont;\n const regions = data.regions;\n const n = regions.length;\n\n // 기준값에서 자동 편차 계산\n const precipDiffs = regions.map((r) => r.precip - data.basePrecip);\n const tempDiffs = regions.map((r) => r.temp - data.baseTemp);\n\n // 축 범위 (대칭)\n const precipVals = precipDiffs;\n const tempVals = tempDiffs;\n\n const precipAxis = data.precipRange.auto\n ? autoRange(precipVals, 6)\n : { min: data.precipRange.min, max: data.precipRange.max, step: 0 };\n if (precipAxis.step === 0) precipAxis.step = Math.max(1, Math.round((precipAxis.max - precipAxis.min) / 6));\n if (data.precipRange.auto) {\n const precipAbs = Math.max(Math.abs(precipAxis.min), Math.abs(precipAxis.max));\n precipAxis.min = -precipAbs;\n precipAxis.max = precipAbs;\n }\n\n const tempAxis = data.tempRange.auto\n ? autoRange(tempVals, 6)\n : { min: data.tempRange.min, max: data.tempRange.max, step: 0 };\n if (tempAxis.step === 0) tempAxis.step = Math.max(1, Math.round((tempAxis.max - tempAxis.min) / 6));\n if (data.tempRange.auto) {\n const tempAbs = Math.max(Math.abs(tempAxis.min), Math.abs(tempAxis.max));\n tempAxis.min = -tempAbs;\n tempAxis.max = tempAbs;\n }\n\n // Y축 좌 (기온 차이), 우 (강수량 차이)\n drawDevBYAxis(ctx, padding, w, h, tempAxis, 'left', data.tempUnit, font, customFont, options.fontSize);\n drawDevBYAxis(ctx, padding, w, h, precipAxis, 'right', data.precipUnit, font, customFont, options.fontSize);\n\n // X축\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 2;\n ctx.beginPath();\n ctx.moveTo(plotX, plotY + plotH);\n ctx.lineTo(plotX + plotW, plotY + plotH);\n ctx.stroke();\n\n // 기준선 (0선)\n const zeroYPrecip = plotY + plotH - ((0 - precipAxis.min) / (precipAxis.max - precipAxis.min)) * plotH;\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 1.5;\n ctx.beginPath();\n ctx.moveTo(plotX, zeroYPrecip);\n ctx.lineTo(plotX + plotW, zeroYPrecip);\n ctx.stroke();\n\n const slotW = plotW / n;\n // 지역이 둘·셋뿐이면 칸의 절반도 90px 을 넘는다 — AbsBarGraph 와 같은 80px 상한을 둔다\n const barWidth = Math.min(slotW * 0.5, 80);\n\n // X축 라벨 (크게)\n ctx.fillStyle = '#000';\n ctx.font = getFont(options.fontSize.tick * 1.2, font, customFont, 'bold');\n ctx.textAlign = 'center';\n ctx.textBaseline = 'top';\n // 지역 이름은 칸 가운데에 놓이므로 양 끝 칸의 이름이 캔버스를 넘을 수 있다.\n // 아래 여백에 떠 있는 글자라 안으로 밀어도 어느 막대의 이름인지 안 흐려진다.\n const regionFont = (size: number) => getFont(size, font, customFont, 'bold');\n for (let i = 0; i < n; i++) {\n const cx = plotX + slotW * i + slotW / 2;\n drawFloatingLabel(ctx, regions[i].label, cx, plotY + plotH + 12, w, h,\n options.fontSize.tick * 1.2, regionFont);\n }\n\n // 강수량 차이 막대\n for (let i = 0; i < n; i++) {\n const val = precipDiffs[i];\n const cx = plotX + slotW * i + slotW / 2;\n const bx = cx - barWidth / 2;\n const valY = plotY + plotH - ((val - precipAxis.min) / (precipAxis.max - precipAxis.min)) * plotH;\n\n ctx.fillStyle = val >= 0 ? '#888' : '#CCC';\n const barTop = Math.min(zeroYPrecip, valY);\n const barH = Math.abs(valY - zeroYPrecip);\n ctx.fillRect(bx, barTop, barWidth, barH);\n ctx.strokeStyle = '#444';\n ctx.lineWidth = 1;\n ctx.strokeRect(bx, barTop, barWidth, barH);\n }\n\n // 기온 차이 점 (●)\n for (let i = 0; i < n; i++) {\n const cx = plotX + slotW * i + slotW / 2;\n const y = plotY + plotH - ((tempDiffs[i] - tempAxis.min) / (tempAxis.max - tempAxis.min)) * plotH;\n ctx.fillStyle = '#000';\n ctx.beginPath();\n ctx.arc(cx, y, 7, 0, Math.PI * 2);\n ctx.fill();\n }\n\n // 데이터 라벨\n if (options.showDataLabels) {\n ctx.font = getFont(options.fontSize.dataLabel, font, customFont, 'bold');\n ctx.textAlign = 'center';\n for (let i = 0; i < n; i++) {\n const cx = plotX + slotW * i + slotW / 2;\n const pVal = precipDiffs[i];\n const pY = plotY + plotH - ((pVal - precipAxis.min) / (precipAxis.max - precipAxis.min)) * plotH;\n ctx.fillStyle = '#000';\n ctx.textBaseline = pVal >= 0 ? 'bottom' : 'top';\n ctx.fillText(String(pVal), cx, pVal >= 0 ? pY - barWidth / 2 - 4 : pY + 4);\n const tVal = tempDiffs[i];\n const tY = plotY + plotH - ((tVal - tempAxis.min) / (tempAxis.max - tempAxis.min)) * plotH;\n ctx.textBaseline = 'bottom';\n ctx.fillText(String(tVal), cx, tY - 10);\n }\n }\n\n // 범례\n\n if (showLegend) {\n drawLegend({\n ctx,\n items: [\n { type: 'rect', fillStyle: '#888', strokeStyle: '#444', label: legendLabels[0] },\n { type: 'circle', fillStyle: '#000', label: legendLabels[1] },\n ],\n position: legendPos,\n plotX, plotY, plotW, plotH,\n canvasW: w, canvasH: h,\n fontSize: options.fontSize.dataLabel * 0.85 + 5,\n rightGap: 80,\n });\n }\n\n drawTitle({ ctx, plotX, plotW, title: options.title, fontSize: options.fontSize.title, canvasWidth: w });\n drawSourceAndFootnote({ ctx, plotX, plotW, height: h, source: options.source, footnotes: options.footnotes, fontSize: options.fontSize.dataLabel });\n}\n\nfunction drawDevBYAxis(\n ctx: CanvasRenderingContext2D,\n padding: Padding,\n width: number,\n height: number,\n axis: { min: number; max: number; step: number },\n side: 'left' | 'right',\n label: string,\n fontFamily: 'serif' | 'sans' | 'custom',\n customFont: string | undefined,\n fontSize: { tick: number; axisLabel: number }\n) {\n const plotX = padding.left;\n const plotY = padding.top;\n const plotW = width - padding.left - padding.right;\n const plotH = height - padding.top - padding.bottom;\n const x = side === 'left' ? plotX : plotX + plotW;\n\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 2;\n ctx.beginPath();\n ctx.moveTo(x, plotY);\n ctx.lineTo(x, plotY + plotH);\n ctx.stroke();\n\n ctx.fillStyle = '#000';\n ctx.font = getFont(fontSize.tick, fontFamily, customFont, 'bold');\n ctx.textBaseline = 'middle';\n ctx.textAlign = side === 'left' ? 'right' : 'left';\n\n const tickCount = Math.round((axis.max - axis.min) / axis.step);\n for (let i = 0; i <= tickCount; i++) {\n const val = axis.min + i * axis.step;\n const y = plotY + plotH - ((val - axis.min) / (axis.max - axis.min)) * plotH;\n\n ctx.lineWidth = 1.5;\n ctx.beginPath();\n if (side === 'left') {\n ctx.moveTo(x - 6, y);\n ctx.lineTo(x, y);\n } else {\n ctx.moveTo(x, y);\n ctx.lineTo(x + 6, y);\n }\n ctx.stroke();\n\n const tx = side === 'left' ? x - 12 : x + 12;\n const valStr = Number.isInteger(val) ? val.toString() : val.toFixed(1);\n ctx.fillText(valStr, tx, y);\n }\n\n ctx.save();\n ctx.font = getFont(fontSize.axisLabel, fontFamily, customFont, 'bold');\n ctx.fillStyle = '#000';\n ctx.textBaseline = 'bottom';\n const labelX = side === 'left' ? x - 12 : x + 12;\n ctx.textAlign = side === 'left' ? 'right' : 'left';\n ctx.fillText(label, labelX, plotY - 16);\n ctx.restore();\n}\n","// © 2026 김용현\nimport {\n type HythergraphData,\n type GraphOptions,\n MONTH_LABELS_NUM,\n MONTH_LABELS_EN,\n} from '../types/index';\nimport { type Padding, clearCanvas, getFont, autoRange } from '../canvas/renderer';\nimport { drawTitle, drawSourceAndFootnote } from '../canvas/labels';\nimport { drawFloatingLabel } from '../canvas/fit';\nimport { measureLegendWidth, layoutBottomLegend } from '../canvas/legend';\n\n// 계열별 선 스타일\nconst LINE_STYLES: { dash: number[]; width: number }[] = [\n { dash: [], width: 2 }, // 실선\n { dash: [8, 4], width: 2 }, // 파선\n { dash: [2, 3], width: 2 }, // 점선\n { dash: [8, 3, 2, 3], width: 2 }, // 일점쇄선\n { dash: [12, 4, 2, 4, 2, 4], width: 2 }, // 이점쇄선\n];\n\n// 기호: ● ■ ▲ ○ □\ntype MarkerType = 'filledCircle' | 'filledSquare' | 'filledTriangle' | 'openCircle' | 'openSquare';\nconst MARKERS: MarkerType[] = ['filledCircle', 'filledSquare', 'filledTriangle', 'openCircle', 'openSquare'];\n\nfunction drawMarker(ctx: CanvasRenderingContext2D, type: MarkerType, cx: number, cy: number, size: number) {\n const s = size;\n ctx.lineWidth = 1.5;\n ctx.strokeStyle = '#000';\n\n switch (type) {\n case 'filledCircle':\n ctx.fillStyle = '#000';\n ctx.beginPath();\n ctx.arc(cx, cy, s, 0, Math.PI * 2);\n ctx.fill();\n break;\n case 'filledSquare':\n ctx.fillStyle = '#000';\n ctx.fillRect(cx - s, cy - s, s * 2, s * 2);\n break;\n case 'filledTriangle':\n ctx.fillStyle = '#000';\n ctx.beginPath();\n ctx.moveTo(cx, cy - s * 1.1);\n ctx.lineTo(cx - s, cy + s * 0.7);\n ctx.lineTo(cx + s, cy + s * 0.7);\n ctx.closePath();\n ctx.fill();\n break;\n case 'openCircle':\n ctx.fillStyle = '#fff';\n ctx.beginPath();\n ctx.arc(cx, cy, s, 0, Math.PI * 2);\n ctx.fill();\n ctx.stroke();\n break;\n case 'openSquare':\n ctx.fillStyle = '#fff';\n ctx.fillRect(cx - s, cy - s, s * 2, s * 2);\n ctx.strokeRect(cx - s, cy - s, s * 2, s * 2);\n break;\n }\n}\n\nfunction drawMarkerLegendIcon(ctx: CanvasRenderingContext2D, type: MarkerType, cx: number, cy: number) {\n drawMarker(ctx, type, cx, cy, 5);\n}\n\nexport function renderHythergraph(\n ctx: CanvasRenderingContext2D,\n w: number,\n h: number,\n data: HythergraphData,\n options: GraphOptions\n) {\n clearCanvas(ctx, w, h);\n\n const font = options.fontFamily;\n const cf = options.customFont;\n const fs = options.fontSize;\n const showLegend = options.showLegend && data.series.length > 0;\n const legendPos = options.legendPosition;\n const legendLabels = data.series.map((s) => s.label);\n const legendW = (showLegend && legendPos === 'right')\n ? measureLegendWidth(ctx, legendLabels, fs.dataLabel * 0.85 + 5)\n : 0;\n\n // 범례가 몇 줄이 될지 먼저 재야 그만큼 아래 여백을 잡을 수 있다.\n // 아이콘 너비·간격은 아래 범례 그리기와 같은 값을 써야 한다.\n const legendReserve = (showLegend && legendPos === 'bottom')\n ? 65 + layoutBottomLegend(ctx, legendLabels, data.series.map(() => 36),\n fs.dataLabel * 0.85 + 5, w - 80 - (80 + legendW), { iconGap: 8 }).boxH + 2\n : 0;\n\n const padding: Padding = {\n top: options.title ? 110 : 60,\n right: 80 + legendW,\n bottom: (() => {\n let b = 90;\n if (showLegend && legendPos === 'bottom') b += 80;\n b = Math.max(b, legendReserve);\n if (options.source) b += 30;\n b += options.footnotes.filter(f => f.trim()).length * 22;\n return b;\n })(),\n left: 80,\n };\n\n const plotX = padding.left;\n const plotY = padding.top;\n const plotW = w - padding.left - padding.right;\n const plotH = h - padding.top - padding.bottom;\n\n // 모든 계열의 데이터로 축 범위 계산\n const allTemps = data.series.flatMap((s) => s.months.map((m) => m.temp));\n const allPrecips = data.series.flatMap((s) => s.months.map((m) => m.precip));\n const xAuto = autoRange(allTemps);\n const yAuto = autoRange(allPrecips);\n const xMin = data.xRange.auto ? xAuto.min : data.xRange.min;\n const xMax = data.xRange.auto ? xAuto.max : data.xRange.max;\n const yMin = data.yRange.auto ? yAuto.min : data.yRange.min;\n const yMax = data.yRange.auto ? yAuto.max : data.yRange.max;\n const xStep = data.xRange.auto ? xAuto.step : (xMax - xMin) / 5;\n const yStep = data.yRange.auto ? yAuto.step : (yMax - yMin) / 5;\n\n const toX = (v: number) => plotX + ((v - xMin) / (xMax - xMin)) * plotW;\n const toY = (v: number) => plotY + plotH - ((v - yMin) / (yMax - yMin)) * plotH;\n\n // 격자선\n ctx.save();\n ctx.strokeStyle = '#ddd';\n ctx.lineWidth = 0.5;\n ctx.setLineDash([3, 3]);\n for (let v = xMin + xStep; v < xMax; v += xStep) {\n const x = toX(v);\n ctx.beginPath();\n ctx.moveTo(x, plotY);\n ctx.lineTo(x, plotY + plotH);\n ctx.stroke();\n }\n for (let v = yMin + yStep; v < yMax; v += yStep) {\n const y = toY(v);\n ctx.beginPath();\n ctx.moveTo(plotX, y);\n ctx.lineTo(plotX + plotW, y);\n ctx.stroke();\n }\n ctx.restore();\n\n // 축선\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 2;\n ctx.beginPath();\n ctx.moveTo(plotX, plotY);\n ctx.lineTo(plotX, plotY + plotH);\n ctx.lineTo(plotX + plotW, plotY + plotH);\n ctx.stroke();\n\n // X축 눈금 — 마지막 눈금은 단위와 겹치므로 스킵\n ctx.fillStyle = '#000';\n ctx.font = getFont(fs.tick, font, cf, 'bold');\n ctx.textAlign = 'center';\n ctx.textBaseline = 'top';\n for (let v = xMin; v <= xMax + xStep * 0.01; v += xStep) {\n const x = toX(v);\n ctx.lineWidth = 1;\n ctx.beginPath();\n ctx.moveTo(x, plotY + plotH);\n ctx.lineTo(x, plotY + plotH + 6);\n ctx.stroke();\n const isLast = v + xStep > xMax + xStep * 0.01;\n if (!(isLast && data.xUnit)) {\n ctx.fillText(formatTick(v), x, plotY + plotH + 10);\n }\n }\n\n // Y축 눈금 — 최상단 눈금은 단위와 겹치므로 스킵\n ctx.textAlign = 'right';\n ctx.textBaseline = 'middle';\n for (let v = yMin; v <= yMax + yStep * 0.01; v += yStep) {\n const y = toY(v);\n ctx.lineWidth = 1;\n ctx.beginPath();\n ctx.moveTo(plotX - 6, y);\n ctx.lineTo(plotX, y);\n ctx.stroke();\n const isLast = v + yStep > yMax + yStep * 0.01;\n if (!(isLast && data.yUnit)) {\n ctx.fillText(formatTick(v), plotX - 10, y);\n }\n }\n\n // 축 단위 — X축 우측 끝, Y축 상단 끝\n ctx.font = getFont(fs.axisLabel, font, cf, 'bold');\n ctx.fillStyle = '#000';\n // 단위는 플롯 **바깥 여백**에 떠 있다 — 캔버스를 벗어나면 안으로 민다.\n // 왼쪽 여백이 좁은 편이라 대체 글꼴이 조금만 넓어도 「(mm)」 이 밖으로 나갔다.\n const unitFont = (size: number) => getFont(size, font, cf, 'bold');\n if (data.xUnit) {\n ctx.textAlign = 'right';\n ctx.textBaseline = 'top';\n drawFloatingLabel(ctx, data.xUnit, plotX + plotW + 10, plotY + plotH + 35, w, h, fs.axisLabel, unitFont);\n }\n if (data.yUnit) {\n ctx.textAlign = 'right';\n ctx.textBaseline = 'bottom';\n drawFloatingLabel(ctx, data.yUnit, plotX, plotY - 16, w, h, fs.axisLabel, unitFont);\n }\n\n // 계열별 렌더링\n const mLabels = data.monthLabelStyle === 'english' ? MONTH_LABELS_EN : MONTH_LABELS_NUM;\n const showLoop = data.mode === 'loop' || data.mode === 'both';\n const showPts = data.mode === 'points' || data.mode === 'both';\n\n for (let si = 0; si < data.series.length; si++) {\n const series = data.series[si];\n const style = LINE_STYLES[si % LINE_STYLES.length];\n const pts = series.months.map((m) => ({ cx: toX(m.temp), cy: toY(m.precip) }));\n\n // 루프 선\n if (showLoop && pts.length === 12) {\n ctx.strokeStyle = '#000';\n ctx.lineWidth = style.width;\n ctx.setLineDash(style.dash);\n ctx.beginPath();\n ctx.moveTo(pts[0].cx, pts[0].cy);\n for (let i = 1; i < 12; i++) {\n ctx.lineTo(pts[i].cx, pts[i].cy);\n }\n ctx.closePath();\n ctx.stroke();\n ctx.setLineDash([]);\n }\n\n // 포인트 + 월 라벨\n if (showPts) {\n const marker = MARKERS[si % MARKERS.length];\n for (let i = 0; i < 12; i++) {\n const { cx, cy } = pts[i];\n\n // 흰색 배경으로 선 가리기\n ctx.fillStyle = '#fff';\n ctx.beginPath();\n ctx.arc(cx, cy, 7, 0, Math.PI * 2);\n ctx.fill();\n\n // 기호\n drawMarker(ctx, marker, cx, cy, 5);\n\n // 월 라벨 (모든 계열에 표시)\n {\n ctx.fillStyle = '#000';\n ctx.font = getFont(fs.dataLabel, font, cf, 'bold');\n ctx.textAlign = 'left';\n ctx.textBaseline = 'bottom';\n ctx.fillText(mLabels[i], cx + 9, cy - 5);\n }\n }\n }\n }\n\n // 범례 (기호 + 선 스타일)\n if (showLegend) {\n const lfSize = fs.dataLabel * 0.85 + 5;\n const LEGEND_FONT = \"'Noto Sans KR', sans-serif\";\n ctx.save();\n ctx.font = `bold ${lfSize}px ${LEGEND_FONT}`;\n\n const iconW = 36; // 선 + 기호 공간\n const iconGap = 8;\n const pad = 12;\n const lineH = lfSize + 8;\n const n = data.series.length;\n\n const itemWidths = data.series.map((s) => iconW + iconGap + ctx.measureText(s.label).width);\n\n if (legendPos === 'bottom') {\n // 한 줄에 다 못 넣으면 줄을 늘린다 (이름은 자르지 않는다) — 바깥 범례와 같은 규칙\n const spacing = 30;\n const boxW = plotW;\n const layout = layoutBottomLegend(ctx, data.series.map((s) => s.label),\n data.series.map(() => iconW), lfSize, boxW, { iconGap, padding: pad, spacing });\n const boxH = layout.boxH;\n const boxX = plotX;\n const boxY = Math.max(0, Math.min(plotY + plotH + 65, h - boxH - 1));\n\n ctx.strokeStyle = '#888';\n ctx.lineWidth = 1.5;\n ctx.fillStyle = '#fff';\n ctx.beginPath();\n ctx.roundRect(boxX, boxY, boxW, boxH, 0);\n ctx.fill();\n ctx.stroke();\n\n for (let r = 0; r < layout.rows.length; r++) {\n const row = layout.rows[r];\n const rowW = row.reduce((a, it) => a + it.width, 0) + spacing * (row.length - 1);\n let cx = boxX + (boxW - rowW) / 2;\n const cy = boxY + pad + r * (layout.lineHeight + layout.rowGap) + layout.lineHeight / 2;\n for (const { index: i, width } of row) {\n const style = LINE_STYLES[i % LINE_STYLES.length];\n // 선\n ctx.strokeStyle = '#000';\n ctx.lineWidth = style.width;\n ctx.setLineDash(style.dash);\n ctx.beginPath();\n ctx.moveTo(cx, cy);\n ctx.lineTo(cx + iconW, cy);\n ctx.stroke();\n ctx.setLineDash([]);\n // 기호\n drawMarkerLegendIcon(ctx, MARKERS[i % MARKERS.length], cx + iconW / 2, cy);\n // 라벨\n ctx.font = `bold ${layout.fontSize}px ${LEGEND_FONT}`;\n ctx.fillStyle = '#000';\n ctx.textAlign = 'left';\n ctx.textBaseline = 'middle';\n ctx.fillText(data.series[i].label, cx + iconW + iconGap, cy);\n cx += width + spacing;\n }\n }\n } else {\n const itemGap = 10;\n const maxItemW = Math.max(...itemWidths);\n const boxW = maxItemW + pad * 2;\n const boxH = lineH * n + itemGap * (n - 1) + pad * 2;\n const boxX = Math.max(0, Math.min(plotX + plotW + 20, w - boxW - 1));\n const boxY = Math.max(0, Math.min(plotY + plotH - boxH, h - boxH - 1));\n\n ctx.strokeStyle = '#888';\n ctx.lineWidth = 1.5;\n ctx.fillStyle = '#fff';\n ctx.beginPath();\n ctx.roundRect(boxX, boxY, boxW, boxH, 0);\n ctx.fill();\n ctx.stroke();\n\n let cy = boxY + pad + lineH / 2;\n for (let i = 0; i < n; i++) {\n const ix = boxX + pad;\n const style = LINE_STYLES[i % LINE_STYLES.length];\n // 선\n ctx.strokeStyle = '#000';\n ctx.lineWidth = style.width;\n ctx.setLineDash(style.dash);\n ctx.beginPath();\n ctx.moveTo(ix, cy);\n ctx.lineTo(ix + iconW, cy);\n ctx.stroke();\n ctx.setLineDash([]);\n // 기호\n drawMarkerLegendIcon(ctx, MARKERS[i % MARKERS.length], ix + iconW / 2, cy);\n // 라벨\n ctx.font = `bold ${lfSize}px ${LEGEND_FONT}`;\n ctx.fillStyle = '#000';\n ctx.textAlign = 'left';\n ctx.textBaseline = 'middle';\n ctx.fillText(data.series[i].label, ix + iconW + iconGap, cy);\n cy += lineH + itemGap;\n }\n }\n ctx.restore();\n }\n\n drawTitle({ ctx, plotX, plotW, title: options.title, fontSize: fs.title, canvasWidth: w });\n drawSourceAndFootnote({ ctx, plotX, plotW, height: h, source: options.source, footnotes: options.footnotes, fontSize: fs.dataLabel, canvasWidth: w });\n}\n\nfunction formatTick(v: number): string {\n return Math.abs(v) < 0.0001 ? '0' : Number(v.toFixed(2)).toString();\n}\n","// © 2026 김용현\nimport { type PyramidGraphData, type GraphOptions, AGE_GROUPS } from '../types/index';\nimport { type Padding, clearCanvas, getFont, niceStep } from '../canvas/renderer';\nimport { drawTitle, drawSourceAndFootnote } from '../canvas/labels';\nimport { drawFloatingLabel } from '../canvas/fit';\nimport { drawLegend, measureLegendWidth, measureBottomLegend } from '../canvas/legend';\nimport { labelStride, widestLabel } from '../canvas/labels';\n\n/** 눈금 숫자 서식 — 정수는 그대로, 아니면 소수 한 자리 */\nfunction fmtTick(v: number): string {\n const r = Math.round(v * 1e10) / 1e10;\n return Number.isInteger(r) ? r.toString() : r.toFixed(1);\n}\n\n/** 이웃한 눈금 숫자 사이에 최소로 두는 빈틈 */\nconst TICK_LABEL_GAP = 6;\n\n/** 아무리 넓어도 이보다 잘게 나누지는 않는다 */\nconst MAX_TICK_DIVISIONS = 12;\n\n/**\n * 가로축 눈금 간격.\n *\n * **축 끝에 눈금이 놓이고 그 숫자가 적히도록** 고른다. 끝 숫자가 없으면 축이\n * 몇 %까지인지 읽을 수 없다 — `niceStep` 만 쓰면 최댓값이 9 일 때 눈금이\n * 0·2·4·6·8 에서 끊기고, 10 일 때는 끝 눈금이 숫자 솎기에 걸려 사라졌다\n * (2026-08-27 사용자 지적).\n *\n * 그래서 최댓값을 **정확히 나누는** 칸 수 중 숫자가 겹치지 않는 **가장 촘촘한**\n * 것을 쓴다. 간격 값은 정수를 먼저 보고, 없으면 0.5·0.1 단위까지 내려간다.\n * 12% 를 좁은 칸에 그리면 0·4·8·12 가 나오는데, 이게 원본 시험지 눈금이다.\n */\nexport function pickTickStep(maxVal: number, halfW: number, labelWidth: number): number {\n // ⚠️ 0 을 돌려주면 안 된다. 눈금은 `for (v = step; v <= max; v += step)` 꼴로\n // 그리므로 step 이 0 이면 동기 루프가 영원히 돈다 (vitest 타임아웃도 못 끊는다).\n // `niceStep` 이 갖고 있던 가드다 — 이 함수가 그 자리를 대신하니 함께 옮겨 왔다.\n // `auto-range.test.ts` 의 「수동 범위 최댓값이 0이어도 끝난다」 가 지킨다.\n if (!Number.isFinite(maxVal) || maxVal <= 0) return 1;\n\n const fits = Math.max(1, Math.floor(halfW / (labelWidth + TICK_LABEL_GAP)));\n const isMultipleOf = (v: number, unit: number) =>\n Math.abs(v / unit - Math.round(v / unit)) < 1e-9;\n\n for (const unit of [1, 0.5, 0.1]) {\n for (let d = Math.min(fits, MAX_TICK_DIVISIONS); d >= 2; d -= 1) {\n const step = maxVal / d;\n if (step > 0 && isMultipleOf(step, unit)) return step;\n }\n }\n // 어떤 칸 수로도 깔끔하게 안 나뉜다 — 0 과 끝만 남긴다\n return maxVal;\n}\n\nexport function renderPyramidGraph(\n ctx: CanvasRenderingContext2D,\n w: number,\n h: number,\n data: PyramidGraphData,\n options: GraphOptions\n) {\n clearCanvas(ctx, w, h);\n\n const showLegend = options.showLegend;\n const legendPos = options.legendPosition;\n const legendLabels = [\n options.legendLabel1 || data.maleLabel,\n options.legendLabel2 || data.femaleLabel,\n ];\n const legendW = (showLegend && legendPos === 'right')\n ? measureLegendWidth(ctx, legendLabels, options.fontSize.dataLabel * 0.85 + 5)\n : 0;\n\n // 범례가 몇 줄이 될지 먼저 재야 그만큼 아래 여백을 잡을 수 있다\n const legendReserve = (showLegend && legendPos === 'bottom')\n ? measureBottomLegend(ctx, legendLabels, options.fontSize.dataLabel * 0.85 + 5,\n w - 60 - (80 + legendW))\n : 0;\n\n const padding: Padding = {\n top: options.title ? 100 : 50,\n right: 80 + legendW,\n bottom: (() => {\n let b = 90;\n if (showLegend && legendPos === 'bottom') b += 60;\n b = Math.max(b, legendReserve);\n if (options.source) b += 30;\n b += options.footnotes.filter(f => f.trim()).length * 22;\n return b;\n })(),\n left: 60,\n };\n\n const plotX = padding.left;\n const plotY = padding.top;\n const plotW = w - padding.left - padding.right;\n const plotH = h - padding.top - padding.bottom;\n\n const font = options.fontFamily;\n const customFont = options.customFont;\n\n // 입력값 그대로 사용\n const displayAges = data.ages;\n\n /**\n * 그릴 구간 수.\n *\n * 연령대 **이름**을 축에 쓰는 기본 모드는 이름표(`AGE_GROUPS`)가 17개뿐이라\n * 그 수에 묶인다. 나이 **수치** 눈금 모드는 이름표를 안 쓰므로 자료가 준\n * 만큼 그린다 — 시험지가 마지막 구간을 `85세 이상`처럼 합쳐 18구간을 주는\n * 경우가 있다. 자료가 비면 예전대로 이름표 수를 쓴다.\n */\n const n = data.numericAgeAxis && displayAges.length > 0\n ? displayAges.length\n : AGE_GROUPS.length;\n const isPercent = data.unit === 'percent';\n\n // 축 범위 계산\n const allVals = displayAges.flatMap((a) => [a.male, a.female]);\n let maxVal = data.range.auto\n ? Math.max(...allVals, 1)\n : data.range.max;\n // 올림 처리\n if (data.range.auto) {\n const step = niceStep(maxVal, 5);\n maxVal = Math.ceil(maxVal / step) * step;\n }\n\n const centerX = plotX + plotW / 2;\n const halfW = plotW / 2;\n const barH = plotH / n;\n const barGap = barH * 0.1;\n const actualBarH = barH - barGap - 2;\n\n // 외곽선 + 연령 라벨 위치 세로선\n const side = data.ageLabelSide;\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 2;\n\n // 좌측 세로선\n if (side === 'left') {\n ctx.beginPath();\n ctx.moveTo(plotX, plotY);\n ctx.lineTo(plotX, plotY + plotH);\n ctx.stroke();\n }\n // 우측 세로선\n if (side === 'right') {\n ctx.beginPath();\n ctx.moveTo(plotX + plotW, plotY);\n ctx.lineTo(plotX + plotW, plotY + plotH);\n ctx.stroke();\n }\n // 중앙 세로선\n if (side === 'center') {\n ctx.beginPath();\n ctx.moveTo(centerX, plotY);\n ctx.lineTo(centerX, plotY + plotH);\n ctx.stroke();\n }\n\n // 상단 가로선\n ctx.beginPath();\n ctx.moveTo(plotX, plotY);\n ctx.lineTo(plotX + plotW, plotY);\n ctx.stroke();\n\n // 하단 가로선\n ctx.beginPath();\n ctx.moveTo(plotX, plotY + plotH);\n ctx.lineTo(plotX + plotW, plotY + plotH);\n ctx.stroke();\n\n // 눈금 간격 — 격자선·눈금·숫자가 모두 이 값을 쓴다.\n // 숫자 폭을 재야 하므로 눈금 글꼴을 잠깐 걸어 둔다.\n ctx.save();\n ctx.font = getFont(options.fontSize.tick, font, customFont, 'bold');\n const tickStep = pickTickStep(maxVal, halfW, ctx.measureText(fmtTick(maxVal)).width);\n ctx.restore();\n\n // 격자선 (막대 아래에 그리기 위해 먼저)\n for (let v = tickStep; v <= maxVal; v += tickStep) {\n const offset = (v / maxVal) * halfW;\n ctx.save();\n ctx.strokeStyle = '#aaa';\n ctx.lineWidth = 0.5;\n ctx.setLineDash([3, 3]);\n // 좌측\n ctx.beginPath();\n ctx.moveTo(centerX - offset, plotY);\n ctx.lineTo(centerX - offset, plotY + plotH);\n ctx.stroke();\n // 우측\n ctx.beginPath();\n ctx.moveTo(centerX + offset, plotY);\n ctx.lineTo(centerX + offset, plotY + plotH);\n ctx.stroke();\n ctx.restore();\n }\n\n // 막대 그리기\n for (let i = 0; i < n; i++) {\n const y = plotY + plotH - (i + 1) * barH + barGap / 2;\n const maleW = (displayAges[i].male / maxVal) * halfW;\n const femaleW = (displayAges[i].female / maxVal) * halfW;\n\n // 남성 (좌측) — 기본은 진한 회색, sexFills 를 주면 그 색\n ctx.fillStyle = data.sexFills?.[0] ?? '#666';\n ctx.fillRect(centerX - maleW, y, maleW, actualBarH);\n ctx.strokeStyle = data.sexFills ? '#000' : '#444';\n ctx.lineWidth = 0.5;\n ctx.strokeRect(centerX - maleW, y, maleW, actualBarH);\n\n // 여성 (우측) — 기본은 연한 회색\n ctx.fillStyle = data.sexFills?.[1] ?? '#BBB';\n ctx.fillRect(centerX, y, femaleW, actualBarH);\n ctx.strokeStyle = data.sexFills ? '#000' : '#888';\n ctx.lineWidth = 0.5;\n ctx.strokeRect(centerX, y, femaleW, actualBarH);\n }\n\n // 연령 축\n ctx.fillStyle = '#000';\n ctx.textBaseline = 'middle';\n\n if (data.numericAgeAxis) {\n // 시험지 방식 — 연령대 이름 대신 나이 수치 눈금을 왼쪽에 둔다.\n // 구간이 5세 단위라 i 번째 막대의 아래 경계가 i*5 세다.\n ctx.font = getFont(options.fontSize.tick, font, customFont, 'bold');\n ctx.textAlign = 'right';\n const ageStep = 20;\n const topAge = n * 5; // 구간 × 5세 (17구간이면 85, 18구간이면 90)\n for (let age = 0; age <= topAge; age += ageStep) {\n const y = plotY + plotH - (age / topAge) * plotH;\n ctx.lineWidth = 1;\n ctx.strokeStyle = '#000';\n ctx.beginPath();\n ctx.moveTo(plotX - 6, y);\n ctx.lineTo(plotX, y);\n ctx.stroke();\n ctx.fillText(String(age), plotX - 10, y);\n }\n if (data.ageUnit) {\n // 나이 단위는 축 위 여백에 떠 있다 — 캔버스를 벗어나면 안으로 민다\n ctx.textBaseline = 'bottom';\n drawFloatingLabel(ctx, data.ageUnit, plotX + 4, plotY - 8, w, h,\n options.fontSize.tick, (size) => getFont(size, font, customFont, 'bold'));\n ctx.font = getFont(options.fontSize.tick, font, customFont, 'bold');\n ctx.textBaseline = 'middle';\n }\n } else {\n ctx.font = getFont(options.fontSize.tick * 0.75, font, customFont, 'bold');\n for (let i = 0; i < n; i++) {\n const y = plotY + plotH - (i + 1) * barH + barH / 2;\n if (side === 'center') {\n ctx.textAlign = 'center';\n ctx.fillText(AGE_GROUPS[i], centerX, y);\n } else if (side === 'left') {\n ctx.textAlign = 'right';\n ctx.fillText(AGE_GROUPS[i], plotX - 8, y);\n } else {\n ctx.textAlign = 'left';\n ctx.fillText(AGE_GROUPS[i], plotX + plotW + 8, y);\n }\n }\n }\n\n // X축 눈금 (좌우 대칭)\n ctx.font = getFont(options.fontSize.tick, font, customFont, 'bold');\n ctx.textBaseline = 'top';\n ctx.fillStyle = '#000';\n\n // 좌우 대칭이라 숫자가 두 배로 들어간다 — 붙으면 몇 개 걸러 그린다\n const xTickValues: number[] = [];\n for (let v = 0; v <= maxVal + tickStep * 0.001; v += tickStep) {\n const rv = Math.round(v * 1e10) / 1e10;\n if (rv > maxVal) break;\n xTickValues.push(rv);\n }\n const divisions = Math.max(1, xTickValues.length - 1);\n let xStride = labelStride(\n halfW / divisions,\n widestLabel(ctx, xTickValues.map(fmtTick)),\n );\n // `pickTickStep` 이 이미 들어갈 만큼만 칸을 나눠서 보통 1 이 나온다.\n // 그래도 걸러야 한다면 **칸 수의 약수**로 올린다 — 그래야 0(한가운데)과\n // 축 끝이 둘 다 적히고, 남은 숫자도 고르게 놓인다.\n while (xStride < divisions && divisions % xStride !== 0) xStride += 1;\n\n for (let ti = 0; ti < xTickValues.length; ti++) {\n const rv = xTickValues[ti];\n const showNumber = ti % xStride === 0;\n const offset = (rv / maxVal) * halfW;\n\n // 좌측\n const lx = centerX - offset;\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 1;\n ctx.beginPath();\n ctx.moveTo(lx, plotY + plotH);\n ctx.lineTo(lx, plotY + plotH + 5);\n ctx.stroke();\n if (showNumber) {\n ctx.textAlign = 'center';\n ctx.fillText(fmtTick(rv), lx, plotY + plotH + 10);\n }\n\n // 우측\n const rx = centerX + offset;\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 1;\n ctx.beginPath();\n ctx.moveTo(rx, plotY + plotH);\n ctx.lineTo(rx, plotY + plotH + 5);\n ctx.stroke();\n if (rv > 0 && showNumber) {\n ctx.textAlign = 'center';\n ctx.fillText(fmtTick(rv), rx, plotY + plotH + 10);\n }\n }\n\n // 축 라벨 (좌: 남, 우: 여)\n ctx.font = getFont(options.fontSize.axisLabel, font, customFont, 'bold');\n ctx.textBaseline = 'bottom';\n ctx.fillStyle = '#000';\n ctx.textAlign = 'center';\n ctx.fillText(data.maleLabel, plotX + halfW / 2, plotY - 16);\n ctx.fillText(data.femaleLabel, centerX + halfW / 2, plotY - 16);\n\n // 단위 라벨 — 축 오른쪽 바깥.\n // 기본은 X축 숫자 **아랫줄**, `axisLabelInline` 이면 숫자와 **같은 줄**(시험지 배치).\n ctx.save();\n if (data.axisLabelInline) {\n // 마지막 눈금 숫자는 축 끝에 가운데 정렬이라 절반이 플롯 밖으로 나온다.\n // 그만큼 더 밀어야 숫자와 붙지 않는다. 글자 크기도 숫자와 같게 맞춘다 —\n // 크기가 다르면 같은 줄에 놓아도 글줄이 어긋나 보인다.\n ctx.font = getFont(options.fontSize.tick, font, customFont, 'bold');\n const lastTickHalfW = ctx.measureText(fmtTick(maxVal)).width / 2;\n ctx.textAlign = 'left';\n ctx.textBaseline = 'top';\n drawFloatingLabel(ctx, data.axisLabel, plotX + plotW + lastTickHalfW + 8, plotY + plotH + 10,\n w, h, options.fontSize.tick, (size) => getFont(size, font, customFont, 'bold'));\n } else {\n ctx.font = getFont(options.fontSize.axisLabel * 0.85, font, customFont, 'bold');\n const unitY = plotY + plotH + 10 + options.fontSize.tick + 22;\n ctx.textAlign = 'left';\n drawFloatingLabel(ctx, data.axisLabel, plotX + plotW + 4, unitY, w, h,\n options.fontSize.axisLabel * 0.85, (size) => getFont(size, font, customFont, 'bold'));\n }\n ctx.restore();\n\n // 데이터 라벨\n if (options.showDataLabels) {\n ctx.font = getFont(options.fontSize.dataLabel * 0.8, font, customFont, 'bold');\n ctx.fillStyle = '#000';\n for (let i = 0; i < n; i++) {\n const y = plotY + plotH - (i + 1) * barH + barH / 2;\n const mW = (displayAges[i].male / maxVal) * halfW;\n const fW = (displayAges[i].female / maxVal) * halfW;\n const mLabel = isPercent ? displayAges[i].male.toFixed(1) : String(displayAges[i].male);\n const fLabel = isPercent ? displayAges[i].female.toFixed(1) : String(displayAges[i].female);\n\n if (displayAges[i].male > 0) {\n ctx.textAlign = 'right';\n ctx.fillText(mLabel, centerX - mW - 4, y);\n }\n if (displayAges[i].female > 0) {\n ctx.textAlign = 'left';\n ctx.fillText(fLabel, centerX + fW + 4, y);\n }\n }\n }\n\n // 제목\n drawTitle({ ctx, plotX, plotW, title: options.title, fontSize: options.fontSize.title, canvasWidth: w });\n\n // 범례\n\n if (showLegend) {\n drawLegend({\n ctx,\n items: [\n { type: 'rect', fillStyle: '#666', strokeStyle: '#444', label: legendLabels[0] },\n { type: 'rect', fillStyle: '#BBB', strokeStyle: '#888', label: legendLabels[1] },\n ],\n position: legendPos,\n plotX, plotY, plotW, plotH,\n canvasW: w, canvasH: h,\n fontSize: options.fontSize.dataLabel * 0.85 + 5,\n });\n }\n\n // 출처 + 각주\n drawSourceAndFootnote({ ctx, plotX, plotW, height: h, source: options.source, footnotes: options.footnotes, fontSize: options.fontSize.dataLabel, canvasWidth: w });\n}\n","// © 2026 김용현\nimport { type RadarGraphData, type GraphOptions } from '../types/index';\nimport { clearCanvas, getFont } from '../canvas/renderer';\nimport { drawTitle, drawSourceAndFootnote } from '../canvas/labels';\nimport { drawLegend, measureLegendWidth, measureBottomLegend } from '../canvas/legend';\nimport { EDGE, MIN_SCALE, fillLines, largestFitting, textExtent, wrapToWidth } from '../canvas/fit';\n\n// 계열별 선 스타일\nconst LINE_STYLES: { dash: number[]; width: number }[] = [\n { dash: [], width: 2 },\n { dash: [8, 4], width: 2 },\n { dash: [2, 3], width: 2 },\n { dash: [8, 3, 2, 3], width: 2 },\n { dash: [12, 4, 2, 4, 2, 4], width: 2 },\n];\n\nconst GRAY_SHADES = ['#000', '#444', '#777', '#AAA', '#CCC'];\n\nexport function renderRadarChart(\n ctx: CanvasRenderingContext2D,\n w: number,\n h: number,\n data: RadarGraphData,\n options: GraphOptions\n) {\n clearCanvas(ctx, w, h);\n\n const font = options.fontFamily;\n const cf = options.customFont;\n const fs = options.fontSize;\n const n = data.axisLabels.length;\n if (n < 3) return;\n\n const showLegend = options.showLegend && data.series.length > 1;\n const legendPos = options.legendPosition;\n const legendLabels = data.series.map((s) => s.label);\n const legendW = (showLegend && legendPos === 'right')\n ? measureLegendWidth(ctx, legendLabels, fs.dataLabel * 0.85 + 5, 'line')\n : 0;\n\n const topPad = options.title ? 60 : 10;\n const rightPad = 60 + legendW;\n const leftPad = 60;\n let bottomPad = 10;\n if (showLegend && legendPos === 'bottom') {\n bottomPad += 70;\n // 상수 70 은 한 줄짜리 상자(높이 55.7)에도 모자랐다 — 실제 높이를 재서 잡는다\n bottomPad = Math.max(bottomPad, measureBottomLegend(\n ctx, legendLabels, fs.dataLabel * 0.85 + 5, w - leftPad - rightPad, 'line', 30));\n }\n if (options.source) bottomPad += 25;\n bottomPad += options.footnotes.filter(f => f.trim()).length * 22;\n\n const availW = w - leftPad - rightPad;\n const availH = h - topPad - bottomPad;\n const cx = leftPad + availW / 2;\n const cy = topPad + availH / 2;\n\n // 각 축의 각도 (12시 방향부터 시계 방향)\n const angles = Array.from({ length: n }, (_, i) => -Math.PI / 2 + (2 * Math.PI * i) / n);\n\n // 축 이름은 꼭짓점 **바깥**(반지름 + 20)에 놓인다. 상수 40 은 「축1」 정도만\n // 감당해서, 「주간 인구 지수」 같은 이름은 왼쪽으로 59.7px 이 캔버스 밖이었다.\n // 세워 놓은 이름이 아니므로 여백을 넓히면 되지만 여기서 여백을 넓히는 것은\n // 곧 **반지름을 줄이는 것**이다 (범례 때 플롯이 줄어든 것과 같은 손해다).\n // 다만 그림이 5분의 1 넘게 줄어들 판이면 줄이기 전에 이름을 접는다.\n const labelFontSize = fs.axisLabel * 0.85;\n const makeLabelFont = (size: number) => getFont(size, font, cf, 'bold');\n const { radius, labelLines, labelSize } = fitAxisLabels(\n ctx, data.axisLabels, angles, cx, cy, w, h,\n Math.min(availW, availH) / 2 - 40, labelFontSize, makeLabelFont,\n );\n\n // 최대값\n const allValues = data.series.flatMap((s) => s.values);\n const dataMax = allValues.length > 0 ? Math.max(...allValues, 1) : 100;\n const maxVal = data.autoMax ? Math.ceil(dataMax / data.gridSteps) * data.gridSteps : data.maxValue;\n\n const toXY = (angle: number, r: number): [number, number] => [\n cx + Math.cos(angle) * r,\n cy + Math.sin(angle) * r,\n ];\n\n // 동심 다각형 격자\n ctx.save();\n ctx.strokeStyle = '#ccc';\n ctx.lineWidth = 0.8;\n for (let step = 1; step <= data.gridSteps; step++) {\n const r = (step / data.gridSteps) * radius;\n ctx.beginPath();\n for (let i = 0; i <= n; i++) {\n const [px, py] = toXY(angles[i % n], r);\n if (i === 0) ctx.moveTo(px, py);\n else ctx.lineTo(px, py);\n }\n ctx.stroke();\n }\n ctx.restore();\n\n // 축 선\n ctx.strokeStyle = '#999';\n ctx.lineWidth = 1;\n for (let i = 0; i < n; i++) {\n const [px, py] = toXY(angles[i], radius);\n ctx.beginPath();\n ctx.moveTo(cx, cy);\n ctx.lineTo(px, py);\n ctx.stroke();\n }\n\n // 눈금값\n ctx.fillStyle = '#888';\n ctx.font = getFont(fs.tick * 0.8, font, cf, 'normal');\n ctx.textAlign = 'left';\n ctx.textBaseline = 'middle';\n for (let step = 1; step <= data.gridSteps; step++) {\n const val = (maxVal / data.gridSteps) * step;\n const r = (step / data.gridSteps) * radius;\n ctx.fillText(formatTick(val), cx + 4, cy - r - 2);\n }\n\n // 축 라벨\n ctx.fillStyle = '#000';\n ctx.font = makeLabelFont(labelSize);\n for (let i = 0; i < n; i++) {\n const [px, py] = toXY(angles[i], radius + 20);\n setLabelAlign(ctx, angles[i]);\n fillLines(ctx, labelLines[i], px, py, labelSize * LABEL_LINE_RATIO);\n }\n\n // 계열별 포인트 좌표 사전 계산\n const allPts = data.series.map((series) => {\n const pts: [number, number][] = [];\n for (let i = 0; i < n; i++) {\n const val = series.values[i] || 0;\n const r = (val / maxVal) * radius;\n pts.push(toXY(angles[i], r));\n }\n return pts;\n });\n\n // 1단계: 채움 먼저 전부 그리기\n if (data.showFill) {\n for (let si = 0; si < data.series.length; si++) {\n const color = GRAY_SHADES[si % GRAY_SHADES.length];\n const pts = allPts[si];\n const r = parseInt(color.slice(1, 2), 16) * 17;\n const g = parseInt(color.slice(2, 3), 16) * 17;\n const b = parseInt(color.slice(3, 4), 16) * 17;\n ctx.fillStyle = `rgba(${r},${g},${b},0.12)`;\n ctx.beginPath();\n for (let i = 0; i <= n; i++) {\n const [px, py] = pts[i % n];\n if (i === 0) ctx.moveTo(px, py);\n else ctx.lineTo(px, py);\n }\n ctx.fill();\n }\n }\n\n // 2단계: 선과 포인트를 채움 위에 그리기\n for (let si = 0; si < data.series.length; si++) {\n const style = LINE_STYLES[si % LINE_STYLES.length];\n const color = GRAY_SHADES[si % GRAY_SHADES.length];\n const pts = allPts[si];\n\n // 다각형 선\n ctx.strokeStyle = color;\n ctx.lineWidth = style.width;\n ctx.setLineDash(style.dash);\n ctx.beginPath();\n for (let i = 0; i <= n; i++) {\n const [px, py] = pts[i % n];\n if (i === 0) ctx.moveTo(px, py);\n else ctx.lineTo(px, py);\n }\n ctx.stroke();\n ctx.setLineDash([]);\n\n // 데이터 포인트\n for (let i = 0; i < n; i++) {\n const [px, py] = pts[i];\n ctx.fillStyle = '#fff';\n ctx.beginPath();\n ctx.arc(px, py, 5, 0, Math.PI * 2);\n ctx.fill();\n ctx.fillStyle = color;\n ctx.beginPath();\n ctx.arc(px, py, 3.5, 0, Math.PI * 2);\n ctx.fill();\n }\n }\n\n // 범례\n if (showLegend) {\n const plotX = leftPad;\n const plotY = topPad;\n const plotW = availW;\n const plotH = availH;\n\n const items = data.series.map((s, i) => {\n const style = LINE_STYLES[i % LINE_STYLES.length];\n return {\n type: 'line' as const,\n fillStyle: GRAY_SHADES[i % GRAY_SHADES.length],\n label: s.label,\n dash: style.dash,\n lineWidth: style.width,\n };\n });\n drawLegend({\n ctx, items, position: legendPos,\n plotX, plotY, plotW, plotH,\n canvasW: w, canvasH: h,\n fontSize: fs.dataLabel * 0.85 + 5,\n bottomOffset: 30,\n });\n }\n\n const plotX = leftPad;\n const plotW = availW;\n drawTitle({ ctx, plotX, plotW, title: options.title, fontSize: fs.title, canvasWidth: w });\n drawSourceAndFootnote({ ctx, plotX, plotW, height: h, source: options.source, footnotes: options.footnotes, fontSize: fs.dataLabel, canvasWidth: w });\n}\n\nfunction formatTick(v: number): string {\n if (Number.isInteger(v)) return v.toString();\n return v.toFixed(1);\n}\n\n/** 접힌 축 이름의 줄 간격 (글꼴 크기 대비) */\nconst LABEL_LINE_RATIO = 1.25;\n\n/** 이름을 접느니 반지름을 줄이겠다고 보는 한계 — 이보다 작아지면 접는다 */\nconst SHRINK_LIMIT = 0.8;\n\n/** 축 이름은 바깥쪽을 보게 정렬한다 — 재는 쪽과 그리는 쪽이 같아야 한다 */\nfunction setLabelAlign(ctx: CanvasRenderingContext2D, angle: number) {\n if (Math.abs(Math.cos(angle)) < 0.1) ctx.textAlign = 'center';\n else if (Math.cos(angle) > 0) ctx.textAlign = 'left';\n else ctx.textAlign = 'right';\n\n if (Math.abs(Math.sin(angle)) < 0.1) ctx.textBaseline = 'middle';\n else if (Math.sin(angle) > 0) ctx.textBaseline = 'top';\n else ctx.textBaseline = 'bottom';\n}\n\n/**\n * 축 이름이 캔버스 안에 들어가는 가장 큰 반지름을 찾는다.\n *\n * 반지름을 키우면 이름이 바깥으로 나가므로 «들어가는가» 는 반지름에 대해\n * 단조롭다 — 이분 탐색이 맞는다. 예전 반지름에서 이미 들어가면 **그 값을\n * 그대로** 돌려주므로 멀쩡한 그림은 한 픽셀도 움직이지 않는다.\n *\n * 순서는 (1) 반지름을 줄여 자리를 낸다, (2) 그림이 5분의 1 넘게 줄어들 판이면\n * 이름을 여러 줄로 접고 다시 잰다, (3) 그래도 모자라면 이름 글꼴을 줄인다.\n * 어느 단계에서도 이름을 잘라 내지 않는다.\n */\nfunction fitAxisLabels(\n ctx: CanvasRenderingContext2D,\n labels: string[],\n angles: number[],\n cx: number,\n cy: number,\n w: number,\n h: number,\n maxRadius: number,\n fontSize: number,\n makeFont: (size: number) => string,\n): { radius: number; labelLines: string[][]; labelSize: number } {\n ctx.save();\n\n let size = fontSize;\n let lines = labels.map((l) => [l]);\n\n /** 반지름 r 에서 i 번째 이름이 놓이는 자리 */\n const boxOf = (r: number, i: number) => {\n const px = cx + Math.cos(angles[i]) * (r + 20);\n const py = cy + Math.sin(angles[i]) * (r + 20);\n setLabelAlign(ctx, angles[i]);\n const es = lines[i].map((l) => textExtent(ctx, l));\n const half = ((lines[i].length - 1) * size * LABEL_LINE_RATIO) / 2;\n return {\n x0: px - Math.max(...es.map((e) => e.left)),\n x1: px + Math.max(...es.map((e) => e.right)),\n y0: py - half - Math.max(...es.map((e) => e.up)),\n y1: py + half + Math.max(...es.map((e) => e.down)),\n };\n };\n\n const fits = (r: number) => labels.every((_, i) => {\n const b = boxOf(r, i);\n return b.x0 >= EDGE && b.x1 <= w - EDGE && b.y0 >= EDGE && b.y1 <= h - EDGE;\n });\n\n ctx.font = makeFont(size);\n let radius = largestFitting(10, maxRadius, fits);\n\n if (radius < maxRadius * SHRINK_LIMIT) {\n // 예전 반지름에서 각 이름이 가로로 쓸 수 있는 몫만큼 접는다\n lines = labels.map((label, i) => {\n const px = cx + Math.cos(angles[i]) * (maxRadius + 20);\n setLabelAlign(ctx, angles[i]);\n const room = ctx.textAlign === 'left' ? w - EDGE - px\n : ctx.textAlign === 'right' ? px - EDGE\n : 2 * Math.min(px - EDGE, w - EDGE - px);\n return wrapToWidth(ctx, label, Math.max(20, room));\n });\n radius = largestFitting(10, maxRadius, fits);\n }\n\n if (radius <= 10) {\n // 반지름을 바닥까지 줄여도 안 들어간다 — 이름 글꼴을 줄여 본다\n size = fontSize * MIN_SCALE;\n ctx.font = makeFont(size);\n lines = lines.map((ls) => ls.flatMap((l) => wrapToWidth(ctx, l, Math.max(20, w / 3))));\n radius = largestFitting(10, maxRadius, fits);\n }\n\n ctx.restore();\n return { radius, labelLines: lines, labelSize: size };\n}\n","// © 2026 김용현\nimport { type ScatterGraphData, type GraphOptions } from '../types/index';\nimport { type Padding, clearCanvas, getFont, autoRange, fillTextMultiline } from '../canvas/renderer';\nimport { drawTitle, drawSourceAndFootnote, LabelPlacer, labelStride, widestLabel, type LabelBox } from '../canvas/labels';\nimport { clampLinesMiddle, drawFloatingLabel, fillLines, nudgeInside, shrinkToWidth, widestLine, wrapToWidth } from '../canvas/fit';\n\nexport function renderScatterGraph(\n ctx: CanvasRenderingContext2D,\n w: number,\n h: number,\n data: ScatterGraphData,\n options: GraphOptions\n) {\n clearCanvas(ctx, w, h);\n\n if (data.mode === 'deviation') {\n renderDeviation(ctx, w, h, data, options);\n } else {\n renderNormal(ctx, w, h, data, options);\n }\n}\n\n// ── 일반 산점도/버블 ──────────────────────────────────\n\nfunction renderNormal(\n ctx: CanvasRenderingContext2D,\n w: number,\n h: number,\n data: ScatterGraphData,\n options: GraphOptions\n) {\n const font = options.fontFamily;\n const cf = options.customFont;\n const fs = options.fontSize;\n\n // 범례를 플롯 바깥에 둘 참이면 먼저 크기를 재서 오른쪽 여백을 확보한다.\n // 그려 놓고 자리를 잡으면 이미 늦다.\n const outsideLegend = data.bubbleLegendPosition === 'outside-right'\n && data.showBubble && data.points.length > 0;\n const bubbleM = outsideLegend ? bubbleLegendMetrics(ctx, data, fs, font, cf) : null;\n const fillM = outsideLegend ? measureFillLegend(ctx, data, fs, font, cf) : { boxW: 0, boxH: 0 };\n const legendW = Math.max(bubbleM?.boxW ?? 0, fillM.boxW);\n\n // 시험지 틀은 x축 단위를 마지막 눈금 **옆**에 두므로 그만큼 오른쪽이 더 필요하다.\n // 60px 고정으로 두면 `(℃)` 가 캔버스 밖으로 밀린다.\n const examUnitW = (() => {\n if (data.examFrame !== true || !data.xUnit) return 0;\n ctx.save();\n ctx.font = getFont(fs.tick, font, cf, 'bold');\n const width = ctx.measureText(data.xUnit).width;\n ctx.restore();\n return width;\n })();\n\n // 축 범위 — 여백보다 **먼저** 잡는다. 눈금 숫자의 폭과 y축 이름의 폭이\n // 왼쪽 여백을 정하는데, 범위는 자료만으로 정해지므로 순환이 없다.\n const xs = data.points.map((p) => p.x);\n const ys = data.points.map((p) => p.y);\n const xAuto = autoRange(xs);\n const yAuto = autoRange(ys);\n const xMin = data.xRange.auto ? xAuto.min : data.xRange.min;\n const xMax = data.xRange.auto ? xAuto.max : data.xRange.max;\n const yMin = data.yRange.auto ? yAuto.min : data.yRange.min;\n const yMax = data.yRange.auto ? yAuto.max : data.yRange.max;\n const xStep = data.xRange.step ?? (data.xRange.auto ? xAuto.step : (xMax - xMin) / 5);\n const yStep = data.yRange.step ?? (data.yRange.auto ? yAuto.step : (yMax - yMin) / 5);\n\n const yName = measureYAxisName(ctx, data, w, yMin, yMax, yStep, fs, font, cf);\n\n const padding: Padding = {\n top: options.title ? 100 : 50,\n right: legendW > 0 ? legendW + 40 : Math.max(60, examUnitW + 34),\n bottom: (() => {\n let b = 90;\n const notes = options.footnotes.filter(f => f.trim()).length;\n // 출처를 각주와 같은 줄에 두면(sourceInline) 줄이 하나 줄어든다\n if (options.source && !(options.sourceInline && notes > 0)) b += 30;\n b += notes * 22;\n return b;\n })(),\n // 130 은 「Y축」 정도를 담을 만큼이다. 이름이 길면 그만큼 더 비운다 —\n // 「1인당 지역내총생산」이 왼쪽으로 77.6px 넘던 자리다. 이미 130 으로\n // 충분하면 130 이 이겨 예전 그림이 한 픽셀도 안 움직인다.\n left: Math.max(130, yName.reserve),\n };\n\n const plotX = padding.left;\n const plotY = padding.top;\n const plotW = w - padding.left - padding.right;\n const plotH = h - padding.top - padding.bottom;\n\n const toCanvasX = (v: number) => plotX + ((v - xMin) / (xMax - xMin)) * plotW;\n const toCanvasY = (v: number) => plotY + plotH - ((v - yMin) / (yMax - yMin)) * plotH;\n\n // 격자선 — 시험지 틀이면 진한 점선이다\n const exam = data.examFrame === true;\n ctx.save();\n ctx.strokeStyle = exam ? '#333' : '#ddd';\n ctx.lineWidth = exam ? 1 : 0.5;\n ctx.setLineDash(exam ? [5, 4] : [3, 3]);\n for (let v = xMin + xStep; v < xMax; v += xStep) {\n const x = toCanvasX(v);\n ctx.beginPath();\n ctx.moveTo(x, plotY);\n ctx.lineTo(x, plotY + plotH);\n ctx.stroke();\n }\n for (let v = yMin + yStep; v < yMax; v += yStep) {\n const y = toCanvasY(v);\n ctx.beginPath();\n ctx.moveTo(plotX, y);\n ctx.lineTo(plotX + plotW, y);\n ctx.stroke();\n }\n ctx.restore();\n\n // 축선 — 시험지 틀이면 사각 테두리로 감싼다\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 2;\n if (exam) {\n ctx.strokeRect(plotX, plotY, plotW, plotH);\n } else {\n ctx.beginPath();\n ctx.moveTo(plotX, plotY);\n ctx.lineTo(plotX, plotY + plotH);\n ctx.lineTo(plotX + plotW, plotY + plotH);\n ctx.stroke();\n }\n\n // X축 눈금\n ctx.fillStyle = '#000';\n ctx.font = getFont(fs.tick, font, cf, 'bold');\n ctx.textAlign = 'center';\n ctx.textBaseline = 'top';\n // 숫자가 서로 붙으면 몇 개 걸러 그린다 (눈금 표시는 그대로 둔다)\n const xTicks: number[] = [];\n for (let v = xMin; v <= xMax + xStep * 0.01; v += xStep) xTicks.push(v);\n const xStride = labelStride(\n plotW / Math.max(1, xTicks.length - 1),\n widestLabel(ctx, xTicks.map(formatTick)),\n );\n // 눈금 숫자는 가운데 정렬이라 마지막 것이 플롯 오른쪽 밖으로 절반 삐져나온다.\n // 시험지 틀의 x축 단위를 그 옆에 놓으려면 **실제로 그려진** 마지막 숫자의\n // 오른쪽 끝을 알아야 한다 (`30` 과 `(℃)` 가 겹치던 원인).\n let lastXLabelRight = plotX + plotW;\n xTicks.forEach((v, i) => {\n const x = toCanvasX(v);\n ctx.lineWidth = 1;\n ctx.beginPath();\n ctx.moveTo(x, plotY + plotH);\n ctx.lineTo(x, plotY + plotH + 6);\n ctx.stroke();\n if (i % xStride === 0) {\n const text = formatTick(v);\n ctx.fillText(text, x, plotY + plotH + 10);\n lastXLabelRight = Math.max(lastXLabelRight, x + ctx.measureText(text).width / 2);\n }\n });\n\n // Y축 눈금\n ctx.textAlign = 'right';\n ctx.textBaseline = 'middle';\n const yTicks: number[] = [];\n for (let v = yMin; v <= yMax + yStep * 0.01; v += yStep) yTicks.push(v);\n const yStride = labelStride(plotH / Math.max(1, yTicks.length - 1), fs.tick * 1.1);\n // 축 이름이 눈금 숫자를 밟지 않도록, 가장 넓은 숫자만큼 밀어낼 거리를 재 둔다\n // (여백을 정할 때 이미 잰 값이다 — 같은 것을 두 번 세지 않는다)\n const yTickTextW = yName.tickW;\n yTicks.forEach((v, i) => {\n const y = toCanvasY(v);\n ctx.lineWidth = 1;\n ctx.beginPath();\n ctx.moveTo(plotX - 6, y);\n ctx.lineTo(plotX, y);\n ctx.stroke();\n if (i % yStride === 0) ctx.fillText(formatTick(v), plotX - 10, y);\n });\n\n // 축 라벨\n ctx.font = getFont(fs.axisLabel, font, cf, 'bold');\n\n // X축 라벨 (하단 중앙) — 플롯 가운데에 놓이므로 캔버스 양쪽으로 넘칠 수 있다\n ctx.textAlign = 'center';\n ctx.textBaseline = 'top';\n drawFloatingLabel(ctx, data.xLabel, plotX + plotW / 2, plotY + plotH + 40, w, h,\n fs.axisLabel, (size) => getFont(size, font, cf, 'bold'));\n ctx.font = getFont(fs.axisLabel, font, cf, 'bold');\n\n // X축 단위 — 시험지 틀이면 마지막 눈금 옆(`4(℃)` 꼴),\n // 아니면 기존대로 축 이름과 같은 줄 오른쪽 끝\n if (data.xUnit) {\n if (exam) {\n ctx.font = getFont(fs.tick, font, cf, 'bold');\n ctx.textAlign = 'left';\n ctx.textBaseline = 'top';\n // 마지막 눈금 숫자 오른쪽 끝에서 한 칸 띄운다\n ctx.fillText(data.xUnit, lastXLabelRight + 6, plotY + plotH + 10);\n ctx.font = getFont(fs.axisLabel, font, cf, 'bold');\n } else {\n ctx.textAlign = 'right';\n ctx.textBaseline = 'top';\n ctx.fillText(data.xUnit, plotX + plotW + 10, plotY + plotH + 40);\n }\n }\n\n // Y축 단위 (상단 끝) — 플롯 위 여백에 떠 있다\n if (data.yUnit) {\n ctx.textAlign = 'right';\n ctx.textBaseline = 'bottom';\n drawFloatingLabel(ctx, data.yUnit, plotX - 10, plotY - 16, w, h,\n fs.axisLabel, (size) => getFont(size, font, cf, 'bold'));\n }\n\n // Y축 라벨 (Y축 중간) — 왼쪽 여백은 이미 이 이름 몫만큼 비워 두었다\n ctx.font = getFont(yName.size, font, cf, 'bold');\n ctx.textAlign = 'right';\n ctx.textBaseline = 'middle';\n const yNameLineH = yName.size * 1.3;\n fillLines(ctx, yName.lines, plotX - 18 - yTickTextW,\n clampLinesMiddle(ctx, yName.lines, plotY + plotH / 2, yNameLineH, h), yNameLineH);\n ctx.font = getFont(fs.axisLabel, font, cf, 'bold');\n\n // 데이터 포인트\n drawPoints(ctx, data, toCanvasX, toCanvasY, fs, font, cf, options.showDataLabels,\n { left: plotX, right: plotX + plotW, top: plotY, bottom: plotY + plotH }, w, h);\n\n // 버블 크기 범례\n if (outsideLegend) {\n // 플롯 바깥 오른쪽 — 위에서부터 차례로 쌓는다. 자료를 덮을 일이 없다.\n const x = plotX + plotW + 20;\n let y = plotY;\n if (bubbleM) {\n drawBubbleLegendAt(ctx, bubbleM, x, y, font, cf);\n y += bubbleM.boxH + 8;\n }\n if (fillM.boxH > 0) drawFillLegendAt(ctx, data, x, y, fillM, fs, font, cf);\n } else if (data.showBubble && data.points.length > 0) {\n const avoid = bubbleRects(data, toCanvasX, toCanvasY);\n const fill = measureFillLegend(ctx, data, fs, font, cf);\n const used = drawBubbleLegend(ctx, data, plotX, plotY, plotW, plotH, fs, font, cf, avoid, fill.boxH);\n drawFillLegend(ctx, data, plotX, plotY, plotW, plotH, used.bottom, fs, font, cf, used.corner);\n }\n\n drawTitle({ ctx, plotX, plotW, title: options.title, fontSize: fs.title, canvasWidth: w });\n drawSourceAndFootnote({ ctx, plotX, plotW, height: h, source: options.source, sourceInline: options.sourceInline, footnotes: options.footnotes, fontSize: fs.dataLabel, canvasWidth: w });\n}\n\n// ── 편차 산점도 ───────────────────────────────────────\n\nfunction renderDeviation(\n ctx: CanvasRenderingContext2D,\n w: number,\n h: number,\n data: ScatterGraphData,\n options: GraphOptions\n) {\n const font = options.fontFamily;\n const cf = options.customFont;\n const fs = options.fontSize;\n\n const padding: Padding = {\n // 상자 축이름은 그래프 위에 놓이고 그 아래에 y 단위가 들어가므로 자리를 더 준다\n top: options.title ? 100 : (data.boxedAxisLabels ? 120 : 50),\n // 상자 축이름은 플롯 오른쪽 바깥에 놓이므로 그때만 자리를 더 준다\n right: data.boxedAxisLabels ? 170 : 80,\n bottom: (() => {\n let b = 90;\n const notes = options.footnotes.filter(f => f.trim()).length;\n // 출처를 각주와 같은 줄에 두면(sourceInline) 줄이 하나 줄어든다\n if (options.source && !(options.sourceInline && notes > 0)) b += 30;\n b += notes * 22;\n return b;\n })(),\n left: 130,\n };\n\n const plotX = padding.left;\n const plotY = padding.top;\n const plotW = w - padding.left - padding.right;\n const plotH = h - padding.top - padding.bottom;\n\n // 대칭 범위 계산\n const xs = data.points.map((p) => Math.abs(p.x));\n const ys = data.points.map((p) => Math.abs(p.y));\n const xAbsMax = xs.length > 0 ? Math.max(...xs) : 10;\n const yAbsMax = ys.length > 0 ? Math.max(...ys) : 10;\n\n const xSymAuto = symmetricRange(xAbsMax);\n const ySymAuto = symmetricRange(yAbsMax);\n const xLimit = data.xRange.auto ? xSymAuto.limit : Math.max(Math.abs(data.xRange.min), Math.abs(data.xRange.max));\n const yLimit = data.yRange.auto ? ySymAuto.limit : Math.max(Math.abs(data.yRange.min), Math.abs(data.yRange.max));\n const xStep = data.xRange.step ?? (data.xRange.auto ? xSymAuto.step : xLimit / 4);\n const yStep = data.yRange.step ?? (data.yRange.auto ? ySymAuto.step : yLimit / 4);\n\n const toCanvasX = (v: number) => plotX + ((v + xLimit) / (2 * xLimit)) * plotW;\n const toCanvasY = (v: number) => plotY + plotH - ((v + yLimit) / (2 * yLimit)) * plotH;\n\n const originX = toCanvasX(0);\n const originY = toCanvasY(0);\n\n // 격자선\n ctx.save();\n ctx.strokeStyle = '#ddd';\n ctx.lineWidth = 0.5;\n ctx.setLineDash([3, 3]);\n for (let v = -xLimit + xStep; v < xLimit; v += xStep) {\n if (Math.abs(v) < xStep * 0.01) continue;\n const x = toCanvasX(v);\n ctx.beginPath();\n ctx.moveTo(x, plotY);\n ctx.lineTo(x, plotY + plotH);\n ctx.stroke();\n }\n for (let v = -yLimit + yStep; v < yLimit; v += yStep) {\n if (Math.abs(v) < yStep * 0.01) continue;\n const y = toCanvasY(v);\n ctx.beginPath();\n ctx.moveTo(plotX, y);\n ctx.lineTo(plotX + plotW, y);\n ctx.stroke();\n }\n ctx.restore();\n\n // 외곽 축선 — 시험지 편차 그래프는 테두리 없이 교차하는 두 축만 둔다\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 2;\n if (data.showFrame !== false) {\n ctx.beginPath();\n ctx.moveTo(plotX, plotY);\n ctx.lineTo(plotX, plotY + plotH);\n ctx.lineTo(plotX + plotW, plotY + plotH);\n ctx.lineTo(plotX + plotW, plotY);\n ctx.closePath();\n ctx.stroke();\n }\n\n // 십자 기준선 (0,0)\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 1.5;\n ctx.beginPath();\n ctx.moveTo(originX, plotY);\n ctx.lineTo(originX, plotY + plotH);\n ctx.stroke();\n ctx.beginPath();\n ctx.moveTo(plotX, originY);\n ctx.lineTo(plotX + plotW, originY);\n ctx.stroke();\n\n // 0 표시\n ctx.fillStyle = '#000';\n ctx.font = getFont(fs.tick, font, cf, 'bold');\n ctx.textAlign = 'right';\n ctx.textBaseline = 'top';\n ctx.fillText('0', originX - 6, originY + 4);\n\n // 눈금을 어디에 붙일지 — 기본은 플롯 가장자리, ticksOnAxis 면 가운데 십자선\n const xTickBase = data.ticksOnAxis ? originY : plotY + plotH;\n const yTickBase = data.ticksOnAxis ? originX : plotX;\n\n // X축 눈금\n ctx.textAlign = 'center';\n ctx.textBaseline = 'top';\n const devXTicks: number[] = [];\n for (let v = -xLimit; v <= xLimit + xStep * 0.01; v += xStep) devXTicks.push(v);\n const devXStride = labelStride(\n plotW / Math.max(1, devXTicks.length - 1),\n widestLabel(ctx, devXTicks.map(formatTick)),\n );\n devXTicks.forEach((v, i) => {\n if (Math.abs(v) < xStep * 0.01) return;\n const x = toCanvasX(v);\n ctx.lineWidth = 1;\n ctx.beginPath();\n ctx.moveTo(x, xTickBase);\n ctx.lineTo(x, xTickBase + 6);\n ctx.stroke();\n if (i % devXStride === 0) ctx.fillText(formatTick(v), x, xTickBase + 10);\n });\n\n // Y축 눈금\n ctx.textAlign = 'right';\n ctx.textBaseline = 'middle';\n const devYTicks: number[] = [];\n for (let v = -yLimit; v <= yLimit + yStep * 0.01; v += yStep) devYTicks.push(v);\n const devYStride = labelStride(plotH / Math.max(1, devYTicks.length - 1), fs.tick * 1.1);\n const yTickTextW = widestLabel(ctx, devYTicks.map(formatTick));\n devYTicks.forEach((v, i) => {\n if (Math.abs(v) < yStep * 0.01) return;\n const y = toCanvasY(v);\n ctx.lineWidth = 1;\n ctx.beginPath();\n ctx.moveTo(yTickBase - 6, y);\n ctx.lineTo(yTickBase, y);\n ctx.stroke();\n if (i % devYStride === 0) ctx.fillText(formatTick(v), yTickBase - 10, y);\n });\n\n // 축 라벨\n ctx.font = getFont(fs.axisLabel, font, cf, 'bold');\n\n // X축 라벨 (하단 중앙). 상자 방식이면 아래에서 따로 그린다.\n if (!data.boxedAxisLabels) {\n ctx.textAlign = 'center';\n ctx.textBaseline = 'top';\n ctx.fillText(data.xLabel, plotX + plotW / 2, plotY + plotH + 40);\n }\n\n // 축 단위.\n // 눈금이 십자축에 붙어 있으면 단위도 그 축 끝에, 눈금 숫자와 같은 줄·열에 둔다.\n // (축선 위가 아니라 숫자와 나란히 놓여야 읽힌다.)\n if (data.xUnit) {\n ctx.textAlign = data.ticksOnAxis ? 'left' : 'right';\n ctx.textBaseline = 'top';\n ctx.fillText(\n data.xUnit,\n plotX + plotW + (data.ticksOnAxis ? 20 : 10),\n data.ticksOnAxis ? originY + 10 : plotY + plotH + 40,\n );\n }\n\n if (data.yUnit) {\n ctx.textAlign = 'right';\n ctx.textBaseline = 'bottom';\n ctx.fillText(\n data.yUnit,\n data.ticksOnAxis ? originX - 10 : plotX - 10,\n data.ticksOnAxis ? plotY - 6 : plotY - 16,\n );\n }\n\n if (data.boxedAxisLabels) {\n // 시험지 관습 — y 이름은 그래프 위, x 이름은 그래프 오른쪽에 상자로 둔다\n // y 이름은 맨 위에 둔다 — 그 아래 자리는 y 단위가 쓴다.\n // y 이름은 세로축(0선)에 가운데를 맞춘다\n drawBoxedLabel(ctx, data.yLabel, originX, 10, fs.axisLabel, 'below');\n // x 이름은 x 단위 오른쪽에 둔다 — 둘 다 축 오른쪽 끝, 같은 줄이라 겹칠 수 있다.\n const xUnitW = data.xUnit && data.ticksOnAxis ? ctx.measureText(data.xUnit).width + 14 : 0;\n drawBoxedLabel(ctx, data.xLabel, plotX + plotW + 16 + xUnitW, plotY + plotH / 2, fs.axisLabel, 'right');\n } else {\n // Y축 라벨 (Y축 중간, 줄바꿈 지원)\n ctx.textAlign = 'right';\n ctx.textBaseline = 'middle';\n fillTextMultiline(ctx, data.yLabel, yTickBase - 18 - yTickTextW, plotY + plotH / 2, fs.axisLabel * 1.3);\n }\n\n // 데이터 포인트\n drawPoints(ctx, data, toCanvasX, toCanvasY, fs, font, cf, options.showDataLabels,\n { left: plotX, right: plotX + plotW, top: plotY, bottom: plotY + plotH }, w, h);\n\n // 버블 크기 범례\n if (data.showBubble && data.points.length > 0) {\n drawBubbleLegend(ctx, data, plotX, plotY, plotW, plotH, fs, font, cf,\n bubbleRects(data, toCanvasX, toCanvasY));\n }\n\n drawTitle({ ctx, plotX, plotW, title: options.title, fontSize: fs.title, canvasWidth: w });\n drawSourceAndFootnote({ ctx, plotX, plotW, height: h, source: options.source, sourceInline: options.sourceInline, footnotes: options.footnotes, fontSize: fs.dataLabel, canvasWidth: w });\n}\n\n// ── 공통 유틸 ─────────────────────────────────────────\n\nfunction drawPoints(\n ctx: CanvasRenderingContext2D,\n data: ScatterGraphData,\n toX: (v: number) => number,\n toY: (v: number) => number,\n fs: GraphOptions['fontSize'],\n font: GraphOptions['fontFamily'],\n cf: string,\n showLabels: boolean,\n bounds: LabelBox | undefined,\n canvasW: number,\n canvasH: number,\n) {\n const maxSize = data.points.length > 0 ? Math.max(...data.points.map((p) => p.size), 1) : 1;\n\n // 라벨이 서로/점과 겹치지 않게 자리를 잡는다.\n // 점을 먼저 전부 등록해야 라벨이 다른 점 위에 얹히지 않는다.\n const placer = new LabelPlacer();\n if (bounds) {\n for (const pt of data.points) {\n const r = data.showBubble && pt.size > 0 ? (pt.size / maxSize) * data.bubbleScale : 4;\n placer.reserveCircle(toX(pt.x), toY(pt.y), r);\n }\n }\n\n // 큰 원부터 그린다 — 겹칠 때 작은 원이 위로 올라와 가려지지 않는다.\n // (원은 흰색으로 바탕을 지우고 칠하므로, 나중에 그린 쪽이 위에 온다.)\n const drawOrder = [...data.points].sort((a, b) => b.size - a.size);\n for (const pt of drawOrder) {\n const cx = toX(pt.x);\n const cy = toY(pt.y);\n\n if (data.showBubble && pt.size > 0) {\n const r = (pt.size / maxSize) * data.bubbleScale;\n // 흰색 배경으로 그래프 선 가리기\n ctx.fillStyle = '#fff';\n ctx.beginPath();\n ctx.arc(cx, cy, r, 0, Math.PI * 2);\n ctx.fill();\n // 버블 원 — 점별 채움색을 주면 그 색으로 (크기와 다른 값을 색으로 나타낼 때)\n ctx.fillStyle = pt.fill ?? 'rgba(80,80,80,0.3)';\n ctx.beginPath();\n ctx.arc(cx, cy, r, 0, Math.PI * 2);\n ctx.fill();\n ctx.strokeStyle = '#333';\n ctx.lineWidth = 1.5;\n ctx.stroke();\n } else {\n // 버블 없을 때만 중심 점 표시\n ctx.fillStyle = '#000';\n ctx.beginPath();\n ctx.arc(cx, cy, 4, 0, Math.PI * 2);\n ctx.fill();\n }\n\n // 라벨\n if (pt.label) {\n ctx.fillStyle = '#000';\n ctx.font = getFont(fs.dataLabel, font, cf, 'bold');\n const offset = data.showBubble && pt.size > 0\n ? (pt.size / maxSize) * data.bubbleScale + 4\n : 8;\n\n if (bounds) {\n placer.place(ctx, pt.label, cx, cy, {\n gap: offset,\n lineHeight: fs.dataLabel * 1.1,\n bounds,\n });\n } else {\n // 경계를 모르면 겹침 회피를 할 수 없다 — 기존처럼 오른쪽 위에 둔다\n ctx.textAlign = 'left';\n ctx.textBaseline = 'bottom';\n ctx.fillText(pt.label, cx + offset, cy - 2);\n }\n }\n\n // 데이터 라벨 (좌표값)\n if (showLabels) {\n ctx.fillStyle = '#555';\n ctx.font = getFont(fs.dataLabel * 0.8, font, cf, 'normal');\n ctx.textAlign = 'left';\n ctx.textBaseline = 'top';\n const offset = data.showBubble && pt.size > 0\n ? (pt.size / maxSize) * data.bubbleScale + 4\n : 8;\n // 오른쪽 끝 점의 값 라벨은 플롯 밖으로 흘러 캔버스를 넘을 수 있다\n const valueLabel = `(${pt.x}, ${pt.y})`;\n const at = nudgeInside(ctx, valueLabel, cx + offset, cy + 2, canvasW, canvasH);\n ctx.fillText(valueLabel, at.x, at.y);\n }\n }\n}\n\nfunction symmetricRange(absMax: number): { limit: number; step: number } {\n if (absMax === 0) return { limit: 10, step: 2 };\n const margin = absMax * 0.2;\n const raw = absMax + margin;\n const pow = Math.pow(10, Math.floor(Math.log10(raw)));\n const norm = raw / pow;\n let nice: number;\n if (norm <= 1.5) nice = 1.5;\n else if (norm <= 2) nice = 2;\n else if (norm <= 3) nice = 3;\n else if (norm <= 5) nice = 5;\n else nice = 10;\n const limit = nice * pow;\n const step = limit / 4;\n return { limit, step };\n}\n\nfunction formatTick(v: number): string {\n return Math.abs(v) < 0.0001 ? '0' : Number(v.toFixed(2)).toString();\n}\n\n/**\n * y축 이름이 **왼쪽에 얼마나 자리를 요구하는지** 재고, 필요하면 접거나 줄인다.\n *\n * 이름은 눈금 숫자 열 바깥에 가로로 놓인다. 그래서 왼쪽 여백은\n * `18 + 눈금숫자폭 + 이름폭 + 12` 이상이라야 하고, 예전 상수 `130` 은\n * 「Y축」 정도만 감당했다 — 「1인당 지역내총생산」은 77.6px 이 캔버스 밖이었다.\n *\n * 여백만 넓히면 이름 하나가 그림을 다 먹으므로, 이름 몫을 캔버스 너비의 30%\n * 로 묶고 그보다 길면 **줄을 늘린다**. 한 글자가 이미 그 몫보다 넓은\n * 극단에서만 그 이름의 글꼴을 줄인다. 어느 경우에도 이름을 자르지 않는다.\n */\nfunction measureYAxisName(\n ctx: CanvasRenderingContext2D,\n data: ScatterGraphData,\n w: number,\n yMin: number,\n yMax: number,\n yStep: number,\n fs: GraphOptions['fontSize'],\n font: GraphOptions['fontFamily'],\n cf: string,\n): { lines: string[]; size: number; tickW: number; reserve: number } {\n ctx.save();\n\n ctx.font = getFont(fs.tick, font, cf, 'bold');\n const ticks: number[] = [];\n for (let v = yMin; v <= yMax + yStep * 0.01; v += yStep) ticks.push(v);\n const tickW = widestLabel(ctx, ticks.map(formatTick));\n\n const makeFont = (size: number) => getFont(size, font, cf, 'bold');\n ctx.font = makeFont(fs.axisLabel);\n // 사용자가 손으로 나눈 줄(리터럴 \\n)은 그대로 지킨다\n const given = (data.yLabel || '').split('\\\\n');\n const budget = Math.max(40, w * 0.3 - 18 - tickW - 12);\n let lines = given.flatMap((l) => wrapToWidth(ctx, l, budget));\n let size = fs.axisLabel;\n if (widestLine(ctx, lines) > budget) {\n size = shrinkToWidth(ctx, lines, fs.axisLabel, budget, makeFont);\n ctx.font = makeFont(size);\n lines = given.flatMap((l) => wrapToWidth(ctx, l, budget));\n }\n const nameW = widestLine(ctx, lines);\n\n ctx.restore();\n return { lines, size, tickW, reserve: 18 + tickW + nameW + 12 };\n}\n\ninterface Rect { x0: number; y0: number; x1: number; y1: number }\n\n/** 그려진 버블(과 중심점)이 차지한 자리 — 범례가 이걸 덮지 않아야 한다 */\nfunction bubbleRects(\n data: ScatterGraphData,\n toX: (v: number) => number,\n toY: (v: number) => number,\n): Rect[] {\n const maxSize = data.points.length > 0 ? Math.max(...data.points.map((p) => p.size), 1) : 1;\n return data.points.map((pt) => {\n const r = data.showBubble && pt.size > 0 ? (pt.size / maxSize) * data.bubbleScale : 4;\n const cx = toX(pt.x);\n const cy = toY(pt.y);\n return { x0: cx - r, y0: cy - r, x1: cx + r, y1: cy + r };\n });\n}\n\nconst CORNERS = ['top-right', 'top-left', 'bottom-right', 'bottom-left'] as const;\ntype Corner = (typeof CORNERS)[number];\n\n/** 플롯 안쪽에 그릴 때 쓸 모서리. `outside-right` 는 위-오른쪽으로 본다. */\nfunction insideCorner(pos: ScatterGraphData['bubbleLegendPosition']): Corner {\n return pos === 'outside-right' || !pos ? 'top-right' : pos;\n}\n\n/**\n * 범례 상자를 놓을 모서리를 고른다.\n *\n * 버블을 **가장 적게 덮는** 모서리를 쓴다. 빈 모서리를 찾는 방식은 상자가\n * 클 때(두 범례를 합치면 플롯 높이의 절반을 넘는다) 네 곳이 모두 걸려\n * 그대로 원위치에 놓이는 일이 잦았다. 겹친 넓이로 견주면 늘 가장 나은\n * 자리가 나온다. 넓이가 같으면 원하는 모서리를 먼저 쓴다.\n *\n * @param extraH 아래에 함께 쌓일 상자의 높이 — 두 범례가 같이 들어갈 자리를 본다\n */\nfunction pickLegendCorner(\n preferred: Corner,\n boxW: number,\n boxH: number,\n plotX: number,\n plotY: number,\n plotW: number,\n plotH: number,\n avoid: Rect[],\n extraH = 0,\n): { corner: Corner; x: number; y: number } {\n const total = boxH + (extraH > 0 ? extraH + 6 : 0);\n const spotAt = (c: Corner) => ({\n x: c.endsWith('right') ? plotX + plotW - boxW - 10 : plotX + 10,\n y: c.startsWith('top') ? plotY + 10 : plotY + plotH - total - 10,\n });\n /** 상자가 버블을 덮은 넓이 */\n const overlap = (x: number, y: number) => avoid.reduce((sum, b) => {\n const w = Math.min(b.x1, x + boxW) - Math.max(b.x0, x);\n const h = Math.min(b.y1, y + total) - Math.max(b.y0, y);\n return sum + (w > 0 && h > 0 ? w * h : 0);\n }, 0);\n\n const order: Corner[] = [preferred, ...CORNERS.filter((c) => c !== preferred)];\n let best = { corner: preferred, ...spotAt(preferred), score: Infinity };\n for (const corner of order) {\n const { x, y } = spotAt(corner);\n const score = overlap(x, y);\n if (score < best.score) best = { corner, x, y, score };\n if (score === 0) break;\n }\n return { corner: best.corner, x: best.x, y: best.y };\n}\n\n/** 채움 범례가 차지할 크기 — 모서리를 고를 때 두 상자를 함께 본다 */\nfunction measureFillLegend(\n ctx: CanvasRenderingContext2D,\n data: ScatterGraphData,\n fs: GraphOptions['fontSize'],\n font: GraphOptions['fontFamily'],\n cf: string,\n): { boxW: number; boxH: number } {\n const legend = data.fillLegend;\n if (!legend || legend.items.length === 0) return { boxW: 0, boxH: 0 };\n\n const fontSize = fs.dataLabel * 0.8;\n ctx.save();\n ctx.font = getFont(fontSize, font, cf, 'bold');\n const rowH = fontSize * 1.5;\n const swatch = fontSize * 0.95;\n const pad = 8;\n const titleH = legend.title ? rowH : 0;\n const textW = Math.max(\n legend.title ? ctx.measureText(legend.title).width : 0,\n ...legend.items.map((it) => swatch + 6 + ctx.measureText(it.label).width),\n );\n ctx.restore();\n return { boxW: textW + pad * 2, boxH: titleH + legend.items.length * rowH + pad * 2 };\n}\n\n/** 버블 크기 범례: 겹친 원 + 박스 */\n/** 그린 범례 상자의 높이를 돌려준다 (아래에 다른 범례를 붙일 때 쓴다) */\ninterface BubbleLegendMetrics {\n uniqueSteps: number[];\n labelOf: (size: number) => string;\n radiusOf: (size: number) => number;\n maxR: number;\n boxW: number;\n boxH: number;\n labelOffsets: number[];\n lowest: number;\n labelFontSize: number;\n pad: number;\n}\n\n/** 버블 범례의 크기·배치를 잰다. 그리기 전에 여백을 잡아야 할 때도 쓴다. */\nfunction bubbleLegendMetrics(\n ctx: CanvasRenderingContext2D,\n data: ScatterGraphData,\n fs: GraphOptions['fontSize'],\n font: GraphOptions['fontFamily'],\n cf: string,\n): BubbleLegendMetrics | null {\n const sizes = data.points.map((p) => p.size).filter((s) => s > 0);\n if (sizes.length === 0) return null;\n const maxSize = Math.max(...sizes);\n if (maxSize === 0) return null;\n\n // 항목을 직접 준 경우 그대로 쓴다 (자릿수가 큰 값은 자동 표기로는 읽을 수 없다).\n // 단 **데이터에서 가장 큰 원보다 큰 항목은 뺀다** — 원 반지름이\n // (크기 / 데이터최대) 비율이라 배율이 1을 넘으면 상자와 캔버스를 뚫고 나간다.\n const given = data.bubbleLegendItems?.filter((it) => it.size > 0 && it.size <= maxSize) ?? [];\n const steps = given.length > 0\n ? given.map((it) => it.size)\n : [maxSize, Math.round(maxSize * 0.5), Math.round(maxSize * 0.2)].filter((v) => v > 0);\n const uniqueSteps = [...new Set(steps)].sort((a, b) => b - a);\n const labelOf = (size: number) =>\n given.find((it) => it.size === size)?.label ?? String(size);\n\n // ⚠️ 범례 원은 **데이터 원과 반지름 식이 완전히 같아야 한다**.\n // 범례는 \"이만한 원이 이만한 값\"을 알려 주는 자이므로, 배율이 조금이라도\n // 다르면 자로서 못 쓴다. (예전에는 1.2배로 그려 따로 놀았다.)\n const radiusOf = (size: number) => (size / maxSize) * data.bubbleScale;\n const maxR = radiusOf(uniqueSteps[0]);\n\n const labelFontSize = fs.dataLabel * 0.85;\n ctx.save();\n ctx.font = getFont(labelFontSize, font, cf, 'bold');\n const labelW = Math.max(50, ...uniqueSteps.map((v) => ctx.measureText(labelOf(v)).width));\n ctx.restore();\n const pad = 14;\n const boxW = maxR * 2 + labelW + pad * 2 + 20;\n\n // 라벨 세로 위치를 먼저 잡는다.\n // 원 크기 차이가 크면 작은 원들의 지시선 높이가 거의 같아 라벨이 겹치므로\n // 아래로 밀어 간격을 확보하는데, 그만큼 상자도 커져야 라벨이 안 잘린다.\n // (원 바닥을 0 으로 놓은 상대 좌표. 음수가 위쪽이다.)\n const labelGap = labelFontSize * 1.2;\n const labelOffsets: number[] = [];\n let cursor = -Infinity;\n for (const size of uniqueSteps) {\n const r = radiusOf(size);\n cursor = Math.max(-2 * r, cursor + labelGap);\n labelOffsets.push(cursor);\n }\n const lowest = Math.max(0, ...labelOffsets) + labelFontSize / 2;\n const boxH = maxR * 2 + lowest + pad * 2 + 10;\n\n return { uniqueSteps, labelOf, radiusOf, maxR, boxW, boxH, labelOffsets, lowest, labelFontSize, pad };\n}\n\n/** 잰 값을 받아 지정한 자리에 그린다 */\nfunction drawBubbleLegendAt(\n ctx: CanvasRenderingContext2D,\n m: BubbleLegendMetrics,\n boxX: number,\n boxY: number,\n font: GraphOptions['fontFamily'],\n cf: string,\n) {\n ctx.fillStyle = '#fff';\n ctx.strokeStyle = '#666';\n ctx.lineWidth = 1.5;\n ctx.beginPath();\n ctx.roundRect(boxX, boxY, m.boxW, m.boxH, 4);\n ctx.fill();\n ctx.stroke();\n\n const circleX = boxX + m.pad + m.maxR;\n const bottomCircleY = boxY + m.boxH - m.pad - m.lowest;\n\n m.uniqueSteps.forEach((size, si) => {\n const r = m.radiusOf(size);\n const cy = bottomCircleY - r;\n\n ctx.strokeStyle = '#333';\n ctx.lineWidth = 1.5;\n ctx.beginPath();\n ctx.arc(circleX, cy, r, 0, Math.PI * 2);\n ctx.stroke();\n\n ctx.save();\n ctx.strokeStyle = '#666';\n ctx.lineWidth = 1;\n ctx.setLineDash([3, 2]);\n const lineY = cy - r;\n ctx.beginPath();\n ctx.moveTo(circleX, lineY);\n ctx.lineTo(circleX + m.maxR + 12, lineY);\n ctx.stroke();\n ctx.restore();\n\n ctx.fillStyle = '#000';\n ctx.font = getFont(m.labelFontSize, font, cf, 'bold');\n ctx.textAlign = 'left';\n ctx.textBaseline = 'middle';\n ctx.fillText(m.labelOf(size), circleX + m.maxR + 16, bottomCircleY + m.labelOffsets[si]);\n });\n}\n\n/** 플롯 **안쪽** 모서리에 그린다 — 버블을 가장 적게 덮는 자리를 고른다 */\nfunction drawBubbleLegend(\n ctx: CanvasRenderingContext2D,\n data: ScatterGraphData,\n plotX: number,\n plotY: number,\n plotW: number,\n plotH: number,\n fs: GraphOptions['fontSize'],\n font: GraphOptions['fontFamily'],\n cf: string,\n avoid: Rect[] = [],\n extraH = 0,\n): { height: number; corner: Corner; bottom: number } {\n const preferred = insideCorner(data.bubbleLegendPosition);\n const m = bubbleLegendMetrics(ctx, data, fs, font, cf);\n if (!m) return { height: 0, corner: preferred, bottom: 0 };\n\n const spot = pickLegendCorner(preferred, m.boxW, m.boxH, plotX, plotY, plotW, plotH, avoid, extraH);\n drawBubbleLegendAt(ctx, m, spot.x, spot.y, font, cf);\n return { height: m.boxH, corner: spot.corner, bottom: spot.y + m.boxH };\n}\n\n/**\n * 축 이름을 얇은 상자에 넣어 그린다 (시험지 편차 그래프 관습).\n *\n * place 가 'below' 면 (x, y)가 상자의 **위쪽 가운데**,\n * 'right' 면 (x, y)가 상자의 왼쪽 세로 중앙이다.\n * 줄바꿈 구분자는 fillTextMultiline 과 같은 리터럴 \n 이다.\n */\nfunction drawBoxedLabel(\n ctx: CanvasRenderingContext2D,\n text: string,\n x: number,\n y: number,\n fontSize: number,\n place: 'below' | 'right'\n) {\n if (!text) return;\n // fillTextMultiline 과 같은 규약 — 실제 줄바꿈이 아니라 리터럴 역슬래시+n 으로 나눈다\n const lines = text.split('\\\\n');\n const lineH = fontSize * 1.25;\n const padX = 8;\n const padY = 6;\n\n ctx.save();\n const textW = Math.max(...lines.map((l) => ctx.measureText(l).width));\n const boxW = textW + padX * 2;\n const boxH = lines.length * lineH + padY * 2;\n // 'below' 는 x 를 가로 중앙으로 본다 — 축에 맞춰 가운데 정렬하기 위해서다\n const boxX = place === 'below' ? x - boxW / 2 : x;\n const boxY = place === 'below' ? y : y - boxH / 2;\n\n ctx.fillStyle = '#fff';\n ctx.fillRect(boxX, boxY, boxW, boxH);\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 1.2;\n ctx.strokeRect(boxX, boxY, boxW, boxH);\n\n ctx.fillStyle = '#000';\n ctx.textAlign = 'center';\n ctx.textBaseline = 'top';\n for (let i = 0; i < lines.length; i++) {\n ctx.fillText(lines[i], boxX + boxW / 2, boxY + padY + i * lineH);\n }\n ctx.restore();\n}\n\n/**\n * 채움색이 무엇을 뜻하는지 알려주는 범례.\n * 버블 크기 범례와 같은 모서리에, 그 아래에 붙인다.\n *\n * @param offsetH 위에 이미 그려진 범례의 높이 (겹치지 않게 그만큼 비켜 놓는다)\n */\n/** 잰 값을 받아 지정한 자리에 그린다 */\nfunction drawFillLegendAt(\n ctx: CanvasRenderingContext2D,\n data: ScatterGraphData,\n boxX: number,\n boxY: number,\n m: { boxW: number; boxH: number },\n fs: GraphOptions['fontSize'],\n font: GraphOptions['fontFamily'],\n cf: string,\n) {\n const legend = data.fillLegend;\n if (!legend || legend.items.length === 0) return;\n\n const fontSize = fs.dataLabel * 0.8;\n ctx.save();\n ctx.font = getFont(fontSize, font, cf, 'bold');\n const rowH = fontSize * 1.5;\n const swatch = fontSize * 0.95;\n const pad = 8;\n\n ctx.fillStyle = '#fff';\n ctx.fillRect(boxX, boxY, m.boxW, m.boxH);\n ctx.strokeStyle = '#666';\n ctx.lineWidth = 1.5;\n ctx.setLineDash([]);\n ctx.strokeRect(boxX, boxY, m.boxW, m.boxH);\n\n let y = boxY + pad;\n if (legend.title) {\n ctx.fillStyle = '#000';\n ctx.textAlign = 'center';\n ctx.textBaseline = 'top';\n ctx.fillText(legend.title, boxX + m.boxW / 2, y);\n y += rowH;\n }\n\n for (const it of legend.items) {\n ctx.fillStyle = it.fill;\n ctx.fillRect(boxX + pad, y + (rowH - swatch) / 2, swatch, swatch);\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 0.8;\n ctx.strokeRect(boxX + pad, y + (rowH - swatch) / 2, swatch, swatch);\n\n ctx.fillStyle = '#000';\n ctx.textAlign = 'left';\n ctx.textBaseline = 'middle';\n ctx.fillText(it.label, boxX + pad + swatch + 6, y + rowH / 2);\n y += rowH;\n }\n ctx.restore();\n}\n\n/** 플롯 안쪽 — 버블 범례가 고른 모서리를 따라가 그 바로 아래에 붙는다 */\nfunction drawFillLegend(\n ctx: CanvasRenderingContext2D,\n data: ScatterGraphData,\n plotX: number,\n plotY: number,\n plotW: number,\n plotH: number,\n /** 버블 범례의 아래끝. 0 이면 버블 범례가 없다. */\n stackBelow: number,\n fs: GraphOptions['fontSize'],\n font: GraphOptions['fontFamily'],\n cf: string,\n corner: Corner = 'bottom-right',\n) {\n const legend = data.fillLegend;\n if (!legend || legend.items.length === 0) return;\n\n const m = measureFillLegend(ctx, data, fs, font, cf);\n const boxX = corner.endsWith('right') ? plotX + plotW - m.boxW - 10 : plotX + 10;\n const boxY = stackBelow > 0\n ? stackBelow + 6\n : (corner.startsWith('top') ? plotY + 10 : plotY + plotH - m.boxH - 10);\n\n drawFillLegendAt(ctx, data, boxX, boxY, m, fs, font, cf);\n}\n","// © 2026 김용현\nimport { type StackedGraphData, type StackedCategory, type GraphOptions } from '../types/index';\nimport { type Padding, clearCanvas, getFont } from '../canvas/renderer';\nimport { drawTitle, drawSourceAndFootnote } from '../canvas/labels';\nimport { drawLegend, measureLegendWidth, measureBottomLegend } from '../canvas/legend';\nimport { getStackedFill, isLightFill, resolveFill, isLightFillValue } from '../canvas/patterns';\n\nexport function renderStackedGraph(\n ctx: CanvasRenderingContext2D,\n w: number,\n h: number,\n data: StackedGraphData,\n options: GraphOptions\n) {\n clearCanvas(ctx, w, h);\n\n if (data.displayMode === 'bar') {\n renderStackedBar(ctx, w, h, data, options);\n } else {\n renderPieChart(ctx, w, h, data, options);\n }\n}\n\nfunction renderStackedBar(\n ctx: CanvasRenderingContext2D,\n w: number,\n h: number,\n data: StackedGraphData,\n options: GraphOptions\n) {\n const showLegend = options.showLegend;\n const legendPos = options.legendPosition;\n const legendW = (showLegend && legendPos === 'right')\n ? measureLegendWidth(ctx, data.seriesLabels, options.fontSize.dataLabel * 0.85 + 5)\n : 0;\n\n const isVertical = data.barDirection === 'vertical';\n\n // 범례가 몇 줄이 될지 먼저 재야 그만큼 아래 여백을 잡을 수 있다\n const legendReserve = (showLegend && legendPos === 'bottom')\n ? measureBottomLegend(ctx, data.seriesLabels, options.fontSize.dataLabel * 0.85 + 5,\n w - (isVertical ? 80 : 100) - (isVertical ? 60 + legendW : 160 + legendW))\n : 0;\n\n const padding: Padding = {\n top: options.title ? 100 : 50,\n right: isVertical ? 60 + legendW : 160 + legendW,\n bottom: (() => {\n let b = isVertical ? 70 : 60;\n if (showLegend && legendPos === 'bottom') b += 60;\n b = Math.max(b, legendReserve);\n if (options.source) b += 30;\n b += options.footnotes.filter(f => f.trim()).length * 22;\n return b;\n })(),\n left: isVertical ? 80 : 100,\n };\n\n const plotX = padding.left;\n const plotY = padding.top;\n const plotW = w - padding.left - padding.right;\n const plotH = h - padding.top - padding.bottom;\n const font = options.fontFamily;\n const customFont = options.customFont;\n const n = data.categories.length;\n const sCount = data.seriesLabels.length;\n\n // 축선\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 2;\n\n if (isVertical) {\n // 세로 누적 막대 — 사각 테두리\n ctx.beginPath();\n ctx.moveTo(plotX, plotY);\n ctx.lineTo(plotX, plotY + plotH);\n ctx.lineTo(plotX + plotW, plotY + plotH);\n ctx.lineTo(plotX + plotW, plotY);\n ctx.lineTo(plotX, plotY);\n ctx.stroke();\n\n // Y축 눈금 (0~100)\n const stepV = data.axisStep ?? 20;\n ctx.font = getFont(options.fontSize.tick, font, customFont, 'bold');\n ctx.fillStyle = '#000';\n ctx.textAlign = 'right';\n ctx.textBaseline = 'middle';\n for (let v = 0; v <= 100; v += stepV) {\n const y = plotY + plotH - (v / 100) * plotH;\n ctx.lineWidth = 1;\n ctx.beginPath();\n ctx.moveTo(plotX - 5, y);\n ctx.lineTo(plotX, y);\n ctx.stroke();\n ctx.fillText(String(v), plotX - 10, y);\n\n if (v > 0 && v < 100) {\n ctx.save();\n ctx.strokeStyle = data.gridColor ?? '#ddd';\n ctx.lineWidth = 0.5;\n ctx.setLineDash([3, 3]);\n ctx.beginPath();\n ctx.moveTo(plotX, y);\n ctx.lineTo(plotX + plotW, y);\n ctx.stroke();\n ctx.restore();\n }\n }\n\n // 단위\n ctx.font = getFont(options.fontSize.axisLabel, font, customFont, 'bold');\n ctx.textAlign = 'right';\n ctx.textBaseline = 'bottom';\n ctx.fillText(data.unit, plotX - 10, plotY - 16);\n\n // 막대 — 칸의 30%, 최대 84px. 상한은 AbsBarGraph 의 80px 과 같은 꼴이되\n // 조금 넓다: 이 막대는 칸 안에 계열 이름을 적으므로(drawSegmentLabel),\n // 네 글자 이름(19.8px 굵은 명조로 76.5px)이 좌우 여백 6px 과 함께\n // 눌리지 않고 들어가는 폭이 82.5px 이다.\n const barArea = plotW / n;\n const barW = Math.min(barArea * 0.3, 84);\n for (let c = 0; c < n; c++) {\n const cx = plotX + barArea * c + barArea / 2;\n const total = data.categories[c].values.reduce((a, b) => a + b, 0);\n let cumY = 0;\n for (const s of stackOrder(data.categories[c], sCount)) {\n const val = data.categories[c].values[s] || 0;\n const ratio = total > 0 ? val / total : 0;\n const barH = ratio * plotH;\n const y = plotY + plotH - cumY - barH;\n\n ctx.fillStyle = fillOf(ctx, data, s);\n ctx.fillRect(cx - barW / 2, y, barW, barH);\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 0.8;\n ctx.strokeRect(cx - barW / 2, y, barW, barH);\n\n if (data.labelInSegment) {\n drawSegmentLabel(ctx, data, options, s, cx, y + barH / 2, barW, barH, lightAt(data, s), font, customFont);\n } else if (options.showDataLabels && barH > options.fontSize.dataLabel) {\n ctx.fillStyle = lightAt(data, s) ? '#000' : '#fff';\n ctx.font = getFont(options.fontSize.dataLabel * 0.8, font, customFont, 'bold');\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(String(val), cx, y + barH / 2);\n }\n cumY += barH;\n }\n\n // X축 라벨\n ctx.fillStyle = '#000';\n ctx.font = getFont(options.fontSize.tick, font, customFont, 'bold');\n ctx.textAlign = 'center';\n ctx.textBaseline = 'top';\n ctx.fillText(data.categories[c].label, cx, plotY + plotH + 12);\n }\n } else {\n // 가로 누적 막대 — 사각 테두리\n ctx.beginPath();\n ctx.moveTo(plotX, plotY);\n ctx.lineTo(plotX, plotY + plotH);\n ctx.lineTo(plotX + plotW, plotY + plotH);\n ctx.lineTo(plotX + plotW, plotY);\n ctx.lineTo(plotX, plotY);\n ctx.stroke();\n\n // X축 눈금 (0~100)\n const stepH = data.axisStep ?? 20;\n ctx.font = getFont(options.fontSize.tick, font, customFont, 'bold');\n ctx.fillStyle = '#000';\n ctx.textAlign = 'center';\n ctx.textBaseline = 'top';\n for (let v = 0; v <= 100; v += stepH) {\n const x = plotX + (v / 100) * plotW;\n ctx.lineWidth = 1;\n ctx.beginPath();\n ctx.moveTo(x, plotY + plotH);\n ctx.lineTo(x, plotY + plotH + 5);\n ctx.stroke();\n ctx.fillText(String(v), x, plotY + plotH + 10);\n\n if (v > 0 && v < 100) {\n ctx.save();\n ctx.strokeStyle = '#ddd';\n ctx.lineWidth = 0.5;\n ctx.setLineDash([3, 3]);\n ctx.beginPath();\n ctx.moveTo(x, plotY);\n ctx.lineTo(x, plotY + plotH);\n ctx.stroke();\n ctx.restore();\n }\n }\n\n // 단위 (축 맨 오른쪽)\n ctx.font = getFont(options.fontSize.axisLabel, font, customFont, 'bold');\n ctx.textAlign = 'left';\n ctx.textBaseline = 'top';\n ctx.fillText(data.unit, plotX + plotW + 30, plotY + plotH + 10);\n\n // 막대 — 세로와 같은 규칙(칸의 30%, 최대 84px)\n const barArea = plotH / n;\n const barH = Math.min(barArea * 0.3, 84);\n for (let c = 0; c < n; c++) {\n const cy = plotY + barArea * c + barArea / 2;\n const total = data.categories[c].values.reduce((a, b) => a + b, 0);\n let cumX = 0;\n for (const s of stackOrder(data.categories[c], sCount)) {\n const val = data.categories[c].values[s] || 0;\n const ratio = total > 0 ? val / total : 0;\n const bw = ratio * plotW;\n const x = plotX + cumX;\n\n ctx.fillStyle = fillOf(ctx, data, s);\n ctx.fillRect(x, cy - barH / 2, bw, barH);\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 0.8;\n ctx.strokeRect(x, cy - barH / 2, bw, barH);\n\n if (options.showDataLabels && bw > options.fontSize.dataLabel * 2) {\n ctx.fillStyle = lightAt(data, s) ? '#000' : '#fff';\n ctx.font = getFont(options.fontSize.dataLabel * 0.8, font, customFont, 'bold');\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(String(val), x + bw / 2, cy);\n }\n cumX += bw;\n }\n\n // Y축 라벨\n ctx.fillStyle = '#000';\n ctx.font = getFont(options.fontSize.tick, font, customFont, 'bold');\n ctx.textAlign = 'right';\n ctx.textBaseline = 'middle';\n ctx.fillText(data.categories[c].label, plotX - 10, cy);\n }\n }\n\n // 제목\n drawTitle({ ctx, plotX, plotW, title: options.title, fontSize: options.fontSize.title, canvasWidth: w });\n\n // 범례\n if (showLegend) {\n const items = data.seriesLabels.map((label, i) => ({\n type: 'rect' as const,\n fillStyle: fillOf(ctx, data, i),\n bordered: lightAt(data, i),\n label,\n }));\n drawLegend({\n ctx, items, position: legendPos,\n plotX, plotY, plotW, plotH,\n canvasW: w, canvasH: h,\n fontSize: options.fontSize.dataLabel * 0.85 + 5,\n });\n }\n\n // 출처 + 각주\n drawSourceAndFootnote({ ctx, plotX, plotW, height: h, source: options.source, sourceLeft: options.sourceLeft, footnotes: options.footnotes, fontSize: options.fontSize.dataLabel, canvasWidth: w });\n}\n\nfunction renderPieChart(\n ctx: CanvasRenderingContext2D,\n w: number,\n h: number,\n data: StackedGraphData,\n options: GraphOptions\n) {\n const showLegend = options.showLegend;\n const legendPos = options.legendPosition;\n const legendW = (showLegend && legendPos === 'right')\n ? measureLegendWidth(ctx, data.seriesLabels, options.fontSize.dataLabel * 0.85 + 5)\n : 0;\n\n // 범례가 몇 줄이 될지 먼저 재야 그만큼 아래 여백을 잡을 수 있다\n const legendReserve = (showLegend && legendPos === 'bottom')\n ? measureBottomLegend(ctx, data.seriesLabels, options.fontSize.dataLabel * 0.85 + 5,\n w - 60 - (60 + legendW), 'rect', 16)\n : 0;\n\n const padding: Padding = {\n top: options.title ? 100 : 50,\n right: 60 + legendW,\n bottom: (() => {\n let b = 50;\n if (showLegend && legendPos === 'bottom') b += 60;\n b = Math.max(b, legendReserve);\n if (options.source) b += 30;\n b += options.footnotes.filter(f => f.trim()).length * 22;\n return b;\n })(),\n left: 60,\n };\n\n const plotX = padding.left;\n const plotY = padding.top;\n const plotW = w - padding.left - padding.right;\n const plotH = h - padding.top - padding.bottom;\n const font = options.fontFamily;\n const customFont = options.customFont;\n const n = data.categories.length;\n const sCount = data.seriesLabels.length;\n\n // 원 그래프: 카테고리별 원 그리드 배치 (겹침 방지)\n const pieScale = (data.pieScale ?? 100) / 100;\n const rotationRad = ((data.pieRotation ?? 0) * Math.PI) / 180;\n const minGap = 24;\n const labelSpace = options.fontSize.tick + 16;\n\n // 기준 반지름 (scale=100%, 한 줄 배치)\n const singleColW = plotW / n;\n const refR = Math.min(singleColW * 0.4, (plotH - labelSpace) * 0.4);\n const desiredR = refR * pieScale;\n\n // 그리드: 카테고리 수 기반 결정 (캔버스 크기와 독립 — 화면/PNG 일관성 보장)\n // n <= 5: 한 줄, n >= 6: ceil(sqrt(n)) 으로 그리드 구성\n const cols = n <= 5 ? n : Math.ceil(Math.sqrt(n));\n const rows = Math.ceil(n / cols);\n const colW = plotW / cols;\n const rowH = plotH / rows;\n\n // 셀에 맞게 반지름 제한\n const maxR = Math.min(desiredR, (colW - minGap) / 2, (rowH - labelSpace - minGap) / 2);\n\n for (let c = 0; c < n; c++) {\n const col = c % cols;\n const row = Math.floor(c / cols);\n const cx = plotX + colW * col + colW / 2;\n const cy = plotY + rowH * row + (rowH - labelSpace) / 2;\n const total = data.categories[c].values.reduce((a, b) => a + b, 0);\n let startAngle = -Math.PI / 2 + rotationRad;\n\n for (let s = 0; s < sCount; s++) {\n const val = data.categories[c].values[s] || 0;\n const ratio = total > 0 ? val / total : 0;\n const endAngle = startAngle + ratio * Math.PI * 2;\n\n ctx.fillStyle = fillOf(ctx, data, s);\n ctx.beginPath();\n ctx.moveTo(cx, cy);\n ctx.arc(cx, cy, maxR, startAngle, endAngle);\n ctx.closePath();\n ctx.fill();\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 1;\n ctx.stroke();\n\n // 데이터 라벨\n if (options.showDataLabels && ratio > 0.05) {\n const midAngle = (startAngle + endAngle) / 2;\n const lx = cx + Math.cos(midAngle) * maxR * 0.65;\n const ly = cy + Math.sin(midAngle) * maxR * 0.65;\n ctx.fillStyle = lightAt(data, s) ? '#000' : '#fff';\n ctx.font = getFont(options.fontSize.dataLabel * 0.8, font, customFont, 'bold');\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(String(val), lx, ly);\n }\n\n startAngle = endAngle;\n }\n\n // 원 외곽선\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 1.5;\n ctx.beginPath();\n ctx.arc(cx, cy, maxR, 0, Math.PI * 2);\n ctx.stroke();\n\n // 카테고리 라벨\n ctx.fillStyle = '#000';\n ctx.font = getFont(options.fontSize.tick, font, customFont, 'bold');\n ctx.textAlign = 'center';\n ctx.textBaseline = 'top';\n ctx.fillText(data.categories[c].label, cx, cy + maxR + 12);\n }\n\n // 제목\n drawTitle({ ctx, plotX, plotW, title: options.title, fontSize: options.fontSize.title, canvasWidth: w });\n\n // 범례 — 원 그래프 실제 영역(우측 끝 / 하단 끝) 기준으로 위치\n if (showLegend) {\n const items = data.seriesLabels.map((label, i) => ({\n type: 'rect' as const,\n fillStyle: fillOf(ctx, data, i),\n bordered: lightAt(data, i),\n label,\n }));\n if (legendPos === 'right') {\n const rightmostCol = Math.min(cols - 1, n - 1);\n const rightmostCx = plotX + colW * rightmostCol + colW / 2;\n const effectivePlotW = rightmostCx + maxR - plotX;\n // 마지막 행 원의 하단까지를 유효 plotH로 사용 (범례를 원 옆에 정렬)\n const lastRow = rows - 1;\n const lastCy = plotY + rowH * lastRow + (rowH - labelSpace) / 2;\n const pieBottom = lastCy + maxR + 12 + options.fontSize.tick;\n const effectivePlotH = pieBottom - plotY;\n drawLegend({\n ctx, items, position: legendPos,\n plotX, plotY, plotW: effectivePlotW, plotH: effectivePlotH,\n canvasW: w, canvasH: h,\n fontSize: options.fontSize.dataLabel * 0.85 + 5,\n });\n } else {\n // 하단: 가장 아래 행의 원+카테고리 라벨 하단까지를 기준으로\n const lastRow = rows - 1;\n const lastCy = plotY + rowH * lastRow + (rowH - labelSpace) / 2;\n const pieBottom = lastCy + maxR + 12 + options.fontSize.tick;\n const effectivePlotH = pieBottom - plotY;\n drawLegend({\n ctx, items, position: legendPos,\n plotX, plotY, plotW, plotH: effectivePlotH,\n canvasW: w, canvasH: h,\n fontSize: options.fontSize.dataLabel * 0.85 + 5,\n bottomOffset: 16,\n });\n }\n }\n\n // 출처 + 각주\n drawSourceAndFootnote({ ctx, plotX, plotW, height: h, source: options.source, sourceLeft: options.sourceLeft, footnotes: options.footnotes, fontSize: options.fontSize.dataLabel, canvasWidth: w });\n}\n\n/** 이 막대의 쌓기 순서 (아래→위). 미지정이면 계열 번호 순. */\nfunction stackOrder(cat: StackedCategory, sCount: number): number[] {\n const given = cat.segmentOrder;\n if (!given || given.length === 0) {\n return Array.from({ length: sCount }, (_, i) => i);\n }\n // 빠뜨린 계열이 있으면 뒤에 붙인다 — 값이 있는데 안 그려지면 안 된다\n const rest = Array.from({ length: sCount }, (_, i) => i).filter((i) => !given.includes(i));\n return [...given.filter((i) => i >= 0 && i < sCount), ...rest];\n}\n\n/** 계열 채움 — seriesFills 를 줬으면 그 값(색 또는 패턴), 아니면 기본 팔레트 */\nfunction fillOf(\n ctx: CanvasRenderingContext2D,\n data: StackedGraphData,\n i: number\n): string | CanvasPattern {\n const v = data.seriesFills?.[i];\n return v === undefined || v === null ? getStackedFill(ctx, i) : resolveFill(ctx, v);\n}\n\n/** 그 채움 위에 검은 글자를 써도 읽히는가 */\nfunction lightAt(data: StackedGraphData, i: number): boolean {\n const v = data.seriesFills?.[i];\n return v === undefined || v === null ? isLightFill(i) : isLightFillValue(v);\n}\n\n/**\n * 세그먼트 안에 계열 이름을 적는다.\n *\n * 패턴 위에서도 읽히도록 글자에 흰 테두리를 두르고, 어두운 채움 위에서는\n * 흰 글자로 쓴다. 칸이 이름보다 좁으면 폭에 맞춰 눌러 넣는다.\n */\nfunction drawSegmentLabel(\n ctx: CanvasRenderingContext2D,\n data: StackedGraphData,\n options: GraphOptions,\n s: number,\n cx: number,\n cy: number,\n barW: number,\n barH: number,\n light: boolean,\n font: 'serif' | 'sans' | 'custom',\n customFont: string | undefined\n) {\n const text = data.seriesLabels[s] ?? '';\n const size = options.fontSize.dataLabel * 0.9;\n // 칸이 글자보다 얇으면 적지 않는다 — 넘쳐서 옆 칸을 밟는 것보다 낫다\n if (!text || barH < size * 1.1) return;\n\n // 시험지 관습 — 기호(A·B)는 명조, 지명은 고딕\n const family = data.seriesIsSymbol && !data.seriesIsSymbol[s] ? 'sans' : font;\n ctx.font = getFont(size, family, customFont, 'bold');\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n const maxW = Math.max(1, barW - 6);\n\n if (light) {\n // 패턴·연한 채움 위 — 흰 테두리를 둘러 글자를 띄운다\n ctx.save();\n ctx.lineWidth = 3;\n ctx.strokeStyle = '#fff';\n ctx.lineJoin = 'round';\n ctx.strokeText(text, cx, cy, maxW);\n ctx.restore();\n ctx.fillStyle = '#000';\n } else {\n ctx.fillStyle = '#fff';\n }\n ctx.fillText(text, cx, cy, maxW);\n}\n","// © 2026 김용현\nimport { type TernaryGraphData, type GraphOptions } from '../types/index';\nimport { type Padding, clearCanvas, getFont } from '../canvas/renderer';\nimport { drawTitle, drawSourceAndFootnote } from '../canvas/labels';\nimport { EDGE, MIN_SCALE, fillLines, largestFitting, nudgeInside, textExtent, wrapToWidth } from '../canvas/fit';\n\n// 삼각좌표 → 캔버스 좌표 변환\n// a = 하단좌, b = 하단우, c = 상단\nfunction ternaryToXY(\n a: number, b: number, c: number,\n cx: number, cy: number, size: number\n): { x: number; y: number } {\n const total = a + b + c;\n if (total === 0) return { x: cx, y: cy };\n const na = a / total;\n const nb = b / total;\n const nc = c / total;\n // 꼭짓점: 상단(cx, cy - h), 좌하(cx - size/2, cy + h/3*?), 우하\n const h = size * Math.sqrt(3) / 2;\n // 상단 = C, 좌하 = A, 우하 = B\n const topX = cx, topY = cy - h * 2 / 3;\n const leftX = cx - size / 2, leftY = cy + h / 3;\n const rightX = cx + size / 2, rightY = cy + h / 3;\n\n const x = nc * topX + na * leftX + nb * rightX;\n const y = nc * topY + na * leftY + nb * rightY;\n return { x, y };\n}\n\nexport function renderTernaryGraph(\n ctx: CanvasRenderingContext2D,\n w: number,\n h: number,\n data: TernaryGraphData,\n options: GraphOptions\n) {\n clearCanvas(ctx, w, h);\n\n // 하단 여백: tick + 숫자 + 축라벨 + 출처/각주\n const tickSpace = 12 + options.fontSize.tick + 10;\n const axisLabelSpace = options.fontSize.axisLabel * 1.3 + 30;\n let bottomExtra = tickSpace + axisLabelSpace;\n if (options.source) bottomExtra += options.fontSize.dataLabel + 10;\n bottomExtra += options.footnotes.filter(f => f.trim()).length * 22;\n\n const padding: Padding = {\n top: options.title ? 80 : 50,\n right: 120,\n bottom: bottomExtra,\n left: 120,\n };\n\n const plotX = padding.left;\n const plotW = w - padding.left - padding.right;\n const plotH = h - padding.top - padding.bottom;\n // 삼각형 크기: plot 영역에 맞춤\n const triSizeByW = plotW * 0.9;\n const triSizeByH = plotH * 2 / Math.sqrt(3);\n\n // 축 이름은 삼각형 **바깥** 좌·우·아래에 52px 떨어져 붙는다. 이름이 길면\n // 삼각형을 줄여 자리를 낸다 — 방사형·정육면체와 같은 손해다. 다만 그림이\n // 5분의 1 넘게 줄어들 판이면 줄이기 전에 이름을 접는다.\n const nameFit = fitAxisNames(\n ctx, data.axisLabels, options, w, h, plotX + plotW / 2, padding.top,\n Math.min(triSizeByW, triSizeByH),\n );\n const triSize = nameFit.size;\n const triH = triSize * Math.sqrt(3) / 2;\n\n const cx = plotX + plotW / 2;\n // 상단 꼭짓점을 padding.top + 11에 배치\n const cy = padding.top + 11 + triH * 2 / 3;\n\n const font = options.fontFamily;\n const customFont = options.customFont;\n\n // 삼각형 꼭짓점\n const topPt = { x: cx, y: cy - triH * 2 / 3 };\n const leftPt = { x: cx - triSize / 2, y: cy + triH / 3 };\n const rightPt = { x: cx + triSize / 2, y: cy + triH / 3 };\n\n // 격자선\n const interval = data.gridInterval;\n const steps = 100 / interval;\n\n ctx.save();\n ctx.strokeStyle = '#ccc';\n ctx.lineWidth = 0.5;\n for (let i = 1; i < steps; i++) {\n const v = i * interval;\n // A축 평행선 (하변과 평행, 상수 c = v)\n const a1 = ternaryToXY(100 - v, 0, v, cx, cy, triSize);\n const a2 = ternaryToXY(0, 100 - v, v, cx, cy, triSize);\n ctx.beginPath();\n ctx.moveTo(a1.x, a1.y);\n ctx.lineTo(a2.x, a2.y);\n ctx.stroke();\n\n // B축 평행선 (우변과 평행, 상수 a = v)\n const b1 = ternaryToXY(v, 100 - v, 0, cx, cy, triSize);\n const b2 = ternaryToXY(v, 0, 100 - v, cx, cy, triSize);\n ctx.beginPath();\n ctx.moveTo(b1.x, b1.y);\n ctx.lineTo(b2.x, b2.y);\n ctx.stroke();\n\n // C축 평행선 (좌변과 평행, 상수 b = v)\n const c1 = ternaryToXY(0, v, 100 - v, cx, cy, triSize);\n const c2 = ternaryToXY(100 - v, v, 0, cx, cy, triSize);\n ctx.beginPath();\n ctx.moveTo(c1.x, c1.y);\n ctx.lineTo(c2.x, c2.y);\n ctx.stroke();\n }\n ctx.restore();\n\n // 삼각형 외곽선\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 2;\n ctx.beginPath();\n ctx.moveTo(topPt.x, topPt.y);\n ctx.lineTo(leftPt.x, leftPt.y);\n ctx.lineTo(rightPt.x, rightPt.y);\n ctx.closePath();\n ctx.stroke();\n\n // 축 눈금 tick mark + 숫자\n // tick 방향 = 격자선 방향의 연장 (삼각형 바깥으로)\n // A축(좌변) 격자선은 하변과 평행 → 수평 좌측으로 (180°, dx=-1, dy=0)\n // B축(하변) 격자선은 우변과 평행 → 60° 방향 아래로 (dx=cos(-60°), dy=sin(-60°)) = (0.5, 0.866)\n // C축(우변) 격자선은 좌변과 평행 → 수평 우측으로 (0°, dx=1, dy=0)\n // 아니... 정확히 생각하면:\n // 좌변의 tick은 좌변에서 바깥쪽, 격자선(하변 평행=수평) 방향 → 좌측 수평\n // 하변의 tick은 하변에서 바깥쪽, 격자선(좌변 평행=120°) 방향 → 60° 아래 좌측\n // 우변의 tick은 우변에서 바깥쪽, 격자선(하변 평행=수평) 방향 → 우측 수평\n const tickLen = 12;\n\n // A축(좌변) tick 방향: 수평 좌측 (-1, 0)\n const aTick = { x: -1, y: 0 };\n // B축(하변) tick 방향: 120° = (cos60°, sin60°) → 우하 방향\n const bTick = { x: Math.cos(Math.PI / 3), y: Math.sin(Math.PI / 3) };\n // C축(우변) tick 방향: 60° = (cos60°, -sin60°)\n const cTick = { x: Math.cos(Math.PI / 3), y: -Math.sin(Math.PI / 3) };\n\n ctx.fillStyle = '#000';\n ctx.font = getFont(options.fontSize.tick, font, customFont, 'bold');\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 1.5;\n\n for (let i = 0; i <= steps; i++) {\n const val = i * interval;\n\n // A축 (좌변: bottom → top, 시계방향) — tick + 숫자\n const aPos = ternaryToXY(100 - val, 0, val, cx, cy, triSize);\n ctx.beginPath();\n ctx.moveTo(aPos.x, aPos.y);\n ctx.lineTo(aPos.x + aTick.x * tickLen, aPos.y + aTick.y * tickLen);\n ctx.stroke();\n ctx.textAlign = 'right';\n ctx.textBaseline = 'middle';\n ctx.fillText(String(val), aPos.x + aTick.x * tickLen - 4, aPos.y + aTick.y * tickLen);\n\n // B축 (하변: right → left, 시계방향) — tick + 숫자\n const bPos = ternaryToXY(val, 100 - val, 0, cx, cy, triSize);\n ctx.beginPath();\n ctx.moveTo(bPos.x, bPos.y);\n ctx.lineTo(bPos.x + bTick.x * tickLen, bPos.y + bTick.y * tickLen);\n ctx.stroke();\n ctx.textAlign = 'center';\n ctx.textBaseline = 'top';\n ctx.fillText(String(val), bPos.x + bTick.x * tickLen, bPos.y + bTick.y * tickLen + 4);\n\n // C축 (우변: top → bottom, 시계방향) — tick + 숫자\n const cPos = ternaryToXY(0, val, 100 - val, cx, cy, triSize);\n ctx.beginPath();\n ctx.moveTo(cPos.x, cPos.y);\n ctx.lineTo(cPos.x + cTick.x * tickLen, cPos.y + cTick.y * tickLen);\n ctx.stroke();\n ctx.textAlign = 'left';\n ctx.textBaseline = 'middle';\n // 「100 (%)」는 우하 꼭짓점 바깥이라 삼각형이 크면 캔버스를 넘는다.\n // 눈금 숫자는 여백에 떠 있으므로 안으로 민다.\n const cLabel = val === 100 ? '100 (%)' : String(val);\n const cAt = nudgeInside(ctx, cLabel, cPos.x + cTick.x * tickLen + 4, cPos.y + cTick.y * tickLen, w, h);\n ctx.fillText(cLabel, cAt.x, cAt.y);\n }\n\n // 축 라벨 — 자리는 재는 쪽(fitAxisNames)과 같은 계산을 쓴다\n ctx.font = getFont(nameFit.fontSize, font, customFont, 'bold');\n ctx.fillStyle = '#000';\n const labelLineH = nameFit.fontSize * 1.3;\n const spots = namePlaces(w, plotX + plotW / 2, padding.top, triSize, nameFit.lines, labelLineH);\n for (let i = 0; i < 3; i++) {\n ctx.textAlign = spots[i].align;\n ctx.textBaseline = spots[i].baseline;\n fillLines(ctx, nameFit.lines[i], spots[i].x, spots[i].y, labelLineH);\n }\n\n // 데이터 포인트\n for (let i = 0; i < data.points.length; i++) {\n const p = data.points[i];\n // 시계방향 좌표: user(a,b,c) → internal(b, c, a)\n const { x, y } = ternaryToXY(p.b, p.c, p.a, cx, cy, triSize);\n\n // 점\n ctx.fillStyle = '#000';\n ctx.beginPath();\n ctx.arc(x, y, 6, 0, Math.PI * 2);\n ctx.fill();\n\n // 라벨\n if (p.label) {\n ctx.fillStyle = '#000';\n ctx.font = getFont(options.fontSize.dataLabel, font, customFont, 'bold');\n ctx.textAlign = 'left';\n ctx.textBaseline = 'bottom';\n ctx.fillText(p.label, x + 10, y - 4);\n }\n\n // 데이터 라벨 (값 표시)\n if (options.showDataLabels) {\n ctx.fillStyle = '#555';\n ctx.font = getFont(options.fontSize.dataLabel * 0.8, font, customFont);\n ctx.textAlign = 'left';\n ctx.textBaseline = 'top';\n ctx.fillText(`(${p.a}, ${p.b}, ${p.c})`, x + 10, y + 4);\n }\n }\n\n // 제목\n drawTitle({ ctx, plotX, plotW, title: options.title, fontSize: options.fontSize.title, canvasWidth: w });\n\n\n // 출처 + 각주 — 삼각형 좌우 범위 기준\n drawSourceAndFootnote({ ctx, plotX: leftPt.x, plotW: rightPt.x - leftPt.x, height: h, source: options.source, footnotes: options.footnotes, fontSize: options.fontSize.dataLabel, canvasWidth: w });\n}\n\n// ── 축 이름 자리 잡기 ────────────────────────────────────────\n//\n// 세 이름은 삼각형 바깥에 붙으므로, 삼각형을 키우면 이름이 캔버스 밖으로\n// 밀린다 — «들어가는가» 가 크기에 대해 단조로우니 이분 탐색이 맞는다.\n\n/** 이름을 접느니 삼각형을 줄이겠다고 보는 한계 — 이보다 작아지면 접는다 */\nconst SHRINK_LIMIT = 0.8;\n\ninterface NameSpot {\n x: number;\n y: number;\n align: CanvasTextAlign;\n baseline: CanvasTextBaseline;\n}\n\n/**\n * 삼각형 크기가 정해졌을 때 세 축 이름이 놓이는 자리.\n * `y` 는 **줄 묶음의 세로 가운데**다 (한 줄이면 예전 좌표와 정확히 같다).\n */\nfunction namePlaces(\n _w: number, cx: number, padTop: number, triSize: number,\n lines: string[][], lineH: number,\n): NameSpot[] {\n const triH = triSize * Math.sqrt(3) / 2;\n const cy = padTop + 11 + triH * 2 / 3;\n const topY = cy - triH * 2 / 3;\n const sideY = cy + triH / 3;\n const aMidX = cx - triSize / 4;\n const aMidY = (topY + sideY) / 2;\n const cMidX = cx + triSize / 4;\n // B 는 기준선이 top 이라 아래로 자란다 — 첫 줄이 예전 자리에 오도록 가운데를 내린다\n const bTop = sideY + 52;\n return [\n { x: aMidX - 52, y: aMidY, align: 'right', baseline: 'middle' },\n { x: cx, y: bTop + ((lines[1].length - 1) * lineH) / 2, align: 'center', baseline: 'top' },\n { x: cMidX + 52, y: aMidY, align: 'left', baseline: 'middle' },\n ];\n}\n\nfunction fitAxisNames(\n ctx: CanvasRenderingContext2D,\n labels: [string, string, string],\n options: GraphOptions,\n w: number, h: number,\n cx: number, padTop: number,\n maxSize: number,\n): { size: number; lines: string[][]; fontSize: number } {\n ctx.save();\n const makeFont = (size: number) => getFont(size, options.fontFamily, options.customFont, 'bold');\n let fontSize = options.fontSize.axisLabel * 1.3;\n // 사용자가 손으로 나눈 줄(리터럴 \\n)은 그대로 지킨다\n const given = labels.map((l) => (l || '').split('\\\\n'));\n let lines = given.map((g) => g.slice());\n\n const fits = (size: number) => {\n ctx.font = makeFont(fontSize);\n const lineH = fontSize * 1.3;\n const spots = namePlaces(w, cx, padTop, size, lines, lineH);\n return spots.every((sp, i) => {\n ctx.textAlign = sp.align;\n ctx.textBaseline = sp.baseline;\n const es = lines[i].map((l) => textExtent(ctx, l));\n const half = ((lines[i].length - 1) * lineH) / 2;\n return sp.x - Math.max(...es.map((e) => e.left)) >= EDGE\n && sp.x + Math.max(...es.map((e) => e.right)) <= w - EDGE\n && sp.y - half - Math.max(...es.map((e) => e.up)) >= EDGE\n && sp.y + half + Math.max(...es.map((e) => e.down)) <= h - EDGE;\n });\n };\n\n let size = largestFitting(40, maxSize, fits);\n\n if (size < maxSize * SHRINK_LIMIT) {\n ctx.font = makeFont(fontSize);\n const lineH = fontSize * 1.3;\n const spots = namePlaces(w, cx, padTop, maxSize, lines, lineH);\n lines = given.map((g, i) => {\n const sp = spots[i];\n const room = sp.align === 'left' ? w - EDGE - sp.x\n : sp.align === 'right' ? sp.x - EDGE\n : 2 * Math.min(sp.x - EDGE, w - EDGE - sp.x);\n return g.flatMap((l) => wrapToWidth(ctx, l, Math.max(30, room)));\n });\n size = largestFitting(40, maxSize, fits);\n }\n\n if (size <= 40) {\n // 삼각형을 바닥까지 줄여도 안 들어간다 — 이름 글꼴을 줄여 본다\n fontSize = options.fontSize.axisLabel * 1.3 * MIN_SCALE;\n ctx.font = makeFont(fontSize);\n lines = lines.map((ls) => ls.flatMap((l) => wrapToWidth(ctx, l, Math.max(30, w / 3))));\n size = largestFitting(40, maxSize, fits);\n }\n\n ctx.restore();\n return { size, lines, fontSize };\n}\n","// © 2026 김용현\n// 문자열 키 하나로 16종을 고르게 한다. 렌더러와 기본 데이터를 짝지어 두는 곳.\nimport {\n renderAbsBarGraph,\n renderCategoryDotGraph,\n renderClimateGraph,\n renderCubeGraph,\n renderDataTable,\n renderDeviationAGraph,\n renderDeviationBGraph,\n renderHythergraph,\n renderLineGraph,\n renderMatrixTable,\n renderPyramidGraph,\n renderRadarChart,\n renderScatterGraph,\n renderStackedGraph,\n renderTernaryGraph,\n renderTreemapGraph,\n createDefaultAbsBarData,\n createDefaultCategoryDotData,\n createDefaultClimateData,\n createDefaultCubeData,\n createDefaultDataTableData,\n createDefaultDeviationAData,\n createDefaultDeviationBData,\n createDefaultHythergraphData,\n createDefaultLineData,\n createDefaultMatrixTableData,\n createDefaultPyramidData,\n createDefaultRadarData,\n createDefaultScatterData,\n createDefaultStackedData,\n createDefaultTernaryData,\n createDefaultTreemapData,\n type GraphOptions,\n} from './core/index';\nimport type { ChartDataMap, CsatChartType } from './types';\n\n/**\n * 한 종류의 렌더러와 기본 데이터.\n *\n * **키마다 제네릭을 따로 두는 이유가 있다.** 하나로 뭉뚱그리면(`data: never` 같은)\n * 항목을 엉뚱하게 이어도 컴파일과 테스트가 모두 통과한다 — `radar` 를 산점도\n * 렌더러에 연결해도 그림은 나오고 픽셀도 찍히기 때문이다. 이렇게 두면 잘못\n * 이을 수 있는 480가지 중 478가지가 컴파일 오류가 되고, 항목마다 붙던\n * `as` 캐스팅 16개가 사라진다.\n */\nexport interface RegistryEntry<T extends CsatChartType> {\n render: (\n ctx: CanvasRenderingContext2D,\n width: number,\n height: number,\n data: ChartDataMap[T],\n options: GraphOptions,\n ) => void;\n createDefaultData: () => ChartDataMap[T];\n}\n\nexport const REGISTRY: { [K in CsatChartType]: RegistryEntry<K> } = {\n absbar: { render: renderAbsBarGraph, createDefaultData: createDefaultAbsBarData },\n 'category-dot': { render: renderCategoryDotGraph, createDefaultData: createDefaultCategoryDotData },\n // 컴파일러가 갈라주지 못하는 **유일한** 짝이 climate 와 deviation-a 다.\n // DeviationAData 가 ClimateGraphData 의 구조적 상위집합이기 때문이다\n // (같은 6필드 + baseMonths + 선택 필드). 한쪽 칸만 바꾼 오배선 두 가지가 통과한다:\n // · climate 의 기본값만 createDefaultDeviationAData 로 바꾼 경우\n // · deviation-a 의 렌더러만 renderClimateGraph 로 바꾼 경우\n // 렌더러와 기본값을 둘 다 바꿔 끼우면 어느 쪽이든 잡힌다.\n climate: { render: renderClimateGraph, createDefaultData: createDefaultClimateData },\n cube: { render: renderCubeGraph, createDefaultData: createDefaultCubeData },\n 'data-table': { render: renderDataTable, createDefaultData: createDefaultDataTableData },\n // 위 climate 주석 참고 — 이 칸의 렌더러만 바꾼 오배선은 컴파일러가 잡지 못한다.\n 'deviation-a': { render: renderDeviationAGraph, createDefaultData: createDefaultDeviationAData },\n 'deviation-b': { render: renderDeviationBGraph, createDefaultData: createDefaultDeviationBData },\n hythergraph: { render: renderHythergraph, createDefaultData: createDefaultHythergraphData },\n line: { render: renderLineGraph, createDefaultData: createDefaultLineData },\n 'matrix-table': { render: renderMatrixTable, createDefaultData: createDefaultMatrixTableData },\n pyramid: { render: renderPyramidGraph, createDefaultData: createDefaultPyramidData },\n radar: { render: renderRadarChart, createDefaultData: createDefaultRadarData },\n scatter: { render: renderScatterGraph, createDefaultData: createDefaultScatterData },\n stacked: { render: renderStackedGraph, createDefaultData: createDefaultStackedData },\n ternary: { render: renderTernaryGraph, createDefaultData: createDefaultTernaryData },\n treemap: { render: renderTreemapGraph, createDefaultData: createDefaultTreemapData },\n};\n\n/**\n * 사전순으로 정렬된 전체 종류 목록. 오류 메시지와 데모가 이 순서를 쓴다.\n *\n * `Object.freeze` 를 쓰는 까닭: `readonly` 는 타입에만 있고 런타임에는 없다.\n * 이 패키지는 타입 검사를 받지 않는 CDN 사용자를 겨냥하므로, 얼려 두지 않으면\n * `CsatChart.CHART_TYPES.reverse()` 한 번에 오류 메시지가 조용히 망가진다.\n *\n * 인자 없는 `sort()` 는 로캘을 보지 않고 UTF-16 코드 단위로 비교한다(명세).\n * 리눅스 CI 의 small-icu 빌드에서도 같은 순서가 나온다.\n *\n * 아래 세 PURE 주석을 지우지 말 것. `Object.freeze`·`.sort()`·`Object.keys`\n * 모두 이 모듈 맨 위에서 실행되는 함수 호출이다 — 번들러는 함수 호출에\n * 부작용이 있을 수 있다고 보수적으로 가정하므로, 표시가 없으면 `CHART_TYPES`\n * 를 아무도 안 써도 이 문장을 지우지 못하고, 그 문장이 붙들고 있는\n * `REGISTRY`(=16종 렌더러 전부)까지 함께 남는다. **셋 중 하나라도 빠지면**\n * 나머지 호출이 여전히 `REGISTRY` 를 읽으므로 소용이 없다 — 실제로 겪은\n * 문제다(`Object.freeze` 하나만 표시했을 때는 번들이 전혀 줄지 않았다).\n * 표시를 지우면 저수준 렌더러 하나만 가져와도 번들이 줄지 않는 문제가\n * 조용히 되돌아온다.\n */\nexport const CHART_TYPES: readonly CsatChartType[] = /* @__PURE__ */ Object.freeze(\n /* @__PURE__ */ (/* @__PURE__ */ Object.keys(REGISTRY) as CsatChartType[]).sort(),\n);\n\n/**\n * 아는 키인지 본다.\n *\n * **`v in REGISTRY` 로 바꾸지 말 것.** REGISTRY 는 객체 리터럴이라 프로토타입을\n * 물고 있어서 `'constructor'`·`'__proto__'`·`'toString'` 이 전부 통과하고,\n * 곧이어 `REGISTRY['constructor'].render` 에서 터진다.\n */\nexport function isCsatChartType(v: unknown): v is CsatChartType {\n return typeof v === 'string' && Object.prototype.hasOwnProperty.call(REGISTRY, v);\n}\n","// © 2026 김용현\n// 타입이 없는 자바스크립트 사용자를 위한 얕은 모양 검사.\n// 값의 타당성은 보지 않는다 — 모양만 본다.\nimport { CHART_TYPES, REGISTRY, isCsatChartType } from './registry';\nimport type { CsatChartType } from './types';\n\nexport class CsatChartError extends Error {\n constructor(message: string) {\n super(`csat-chart: ${message}`);\n this.name = 'CsatChartError';\n }\n}\n\n/**\n * 길이가 고정된 배열 필드. 기본 데이터에서 «몇 개여야 하는가»를 도출할 수 없어\n * (산점도의 점 개수처럼 자유로운 배열과 구별되지 않는다) 확인된 것만 여기 적는다.\n *\n * `scatter.quadrantLabels` 는 타입에 `[string, string, string, string]` 튜플로\n * 박혀 있어 ternary.axisLabels 와 같은 근거로 추가했다 — 렌더러가 아직 이\n * 필드를 읽지 않지만(포팅 원본을 그대로 둔 필드), 타입 계약은 4개를 요구한다.\n */\nconst FIXED_LENGTHS: Partial<Record<CsatChartType, Record<string, number>>> = {\n climate: { months: 12 },\n 'deviation-a': { baseMonths: 12, months: 12 },\n ternary: { axisLabels: 3 },\n // ⚠️ quadrantLabels 는 지금 어느 렌더러도 읽지 않는다 — 이식해 온 죽은 필드다.\n // 그래도 검사한다. 타입이 4-튜플이라 TypeScript 사용자는 이미 4개를 강요받고,\n // 기본 데이터에 있으니 필수 키 검사도 이미 걸린다. 여기서만 빼면\n // «기본 데이터가 곧 정답 모양» 이라는 규칙에 예외가 하나 생길 뿐이다.\n scatter: { quadrantLabels: 4 },\n};\n\nconst KIND_NAMES: Record<string, string> = {\n object: '객체',\n number: '숫자',\n string: '문자열',\n boolean: '불리언',\n undefined: 'undefined',\n function: '함수',\n};\n\nfunction kindOf(v: unknown): string {\n if (Array.isArray(v)) return '배열';\n if (v === null) return 'null';\n return KIND_NAMES[typeof v] ?? typeof v;\n}\n\n/**\n * «…이어야 합니다» / «…여야 합니다» 를 받침에 따라 고른다.\n *\n * 하나로 고정하면 «배열여야»·«문자열여야» 같은 문장이 나온다. 한글 음절은\n * (코드 − 0xAC00) % 28 로 받침 유무를 알 수 있고, 한글이 아니면(`null` 처럼)\n * 받침이 있는 것으로 친다 — 개발 문서에서 흔히 쓰는 «null이어야» 쪽이다.\n */\nfunction shouldBe(word: string): string {\n const last = word.charCodeAt(word.length - 1);\n const isHangul = last >= 0xac00 && last <= 0xd7a3;\n const hasFinal = isHangul ? (last - 0xac00) % 28 !== 0 : true;\n return `${word}${hasFinal ? '이어야' : '여야'} 합니다`;\n}\n\n/** 두 문자열의 편집 거리 (Levenshtein) */\nfunction distance(a: string, b: string): number {\n let prev = Array.from({ length: b.length + 1 }, (_, j) => j);\n for (let i = 1; i <= a.length; i++) {\n const cur = [i];\n for (let j = 1; j <= b.length; j++) {\n cur[j] = Math.min(\n prev[j] + 1,\n cur[j - 1] + 1,\n prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1),\n );\n }\n prev = cur;\n }\n return prev[b.length];\n}\n\nfunction nearestType(input: string): CsatChartType | null {\n let best: CsatChartType | null = null;\n let bestD = Infinity;\n for (const t of CHART_TYPES) {\n const d = distance(input.toLowerCase(), t);\n if (d < bestD) {\n bestD = d;\n best = t;\n }\n }\n return bestD <= 3 ? best : null;\n}\n\nexport function assertChartType(type: unknown): asserts type is CsatChartType {\n if (isCsatChartType(type)) return;\n const near = typeof type === 'string' ? nearestType(type) : null;\n throw new CsatChartError(\n `알 수 없는 type ${JSON.stringify(type)}` +\n (near ? ` — 혹시 \"${near}\"?` : ` — 쓸 수 있는 값: ${CHART_TYPES.join(', ')}`),\n );\n}\n\n/**\n * `new CsatChart(target, config)` 의 `config` 자체가 객체인지, 있다면\n * `config.options` 도 객체인지를 본다. `assertChartType`·`assertChartData` 보다\n * 먼저 불러야 한다 — 그 둘은 `config.type`·`config.data` 를 읽는데, `config` 를\n * 통째로 빠뜨리면(`new CsatChart(canvas)`) 거기 닿기도 전에 영문\n * `TypeError: Cannot read properties of undefined (reading 'type')` 로 죽는다.\n * 이 라이브러리의 다른 «잘못된 인자» 경로는 전부 한국어 `CsatChartError` 인데\n * 이 경로만 예외였다.\n *\n * `options` 은 흔히 실수로 문자열·배열·숫자를 통째로 넘기는 자리라 여기서도\n * 같이 본다 — 그냥 두면 `mergeOptions` 의 얕은 스프레드가 조용히 무시하고\n * 넘어가 «옵션을 줬는데 하나도 안 먹힌» 것처럼 보인다.\n */\nexport function assertConfigShape(config: unknown): void {\n if (typeof config !== 'object' || config === null || Array.isArray(config)) {\n throw new CsatChartError(`config 는 ${shouldBe('객체')} (지금 ${kindOf(config)})`);\n }\n\n const options = (config as { options?: unknown }).options;\n if (options !== undefined && (typeof options !== 'object' || options === null || Array.isArray(options))) {\n throw new CsatChartError(`config.options 는 ${shouldBe('객체')} (지금 ${kindOf(options)})`);\n }\n}\n\nexport function assertChartData(type: CsatChartType, data: unknown): void {\n if (typeof data !== 'object' || data === null || Array.isArray(data)) {\n throw new CsatChartError(\n `type \"${type}\" 의 data 는 ${shouldBe('객체')} (지금 ${kindOf(data)})`,\n );\n }\n\n const given = data as Record<string, unknown>;\n // 매핑 타입이라 반환값이 16종의 유니온이다. Record 로 바로 못 좁히므로\n // unknown 을 거친다. 키와 종류만 훑을 것이므로 안전하다.\n const shape = REGISTRY[type].createDefaultData() as unknown as Record<string, unknown>;\n const lengths = FIXED_LENGTHS[type] ?? {};\n\n for (const key of Object.keys(shape)) {\n if (!(key in given)) {\n // 키 이름 뒤에 «이/가» 를 붙이면 영문 식별자마다 조사가 어긋난다.\n // «항목이» 를 세우면 어떤 키가 와도 문장이 성립한다.\n throw new CsatChartError(`type \"${type}\" 의 data 에 ${key} 항목이 없습니다`);\n }\n\n const want = kindOf(shape[key]);\n const got = kindOf(given[key]);\n if (want !== got) {\n // 키 이름 뒤는 «는/은» 대신 콜론을 쓴다 — 영문 식별자에 조사를 붙이지 않는다.\n throw new CsatChartError(\n `type \"${type}\" 의 data.${key}: ${shouldBe(want)} (지금 ${got})`,\n );\n }\n\n const n = lengths[key];\n if (n !== undefined) {\n const len = (given[key] as unknown[]).length;\n if (len !== n) {\n throw new CsatChartError(\n `type \"${type}\" 의 data.${key}: ${n}개여야 합니다 (지금 ${len}개)`,\n );\n }\n }\n\n // 배열이면 원소의 종류까지 한 겹 더 본다.\n //\n // 여기까지만 검사하면 열두 달 자료를 «숫자 12개» 로 납작하게 붙여넣은 실수가\n // 그대로 통과한다 — 배열도 맞고 길이도 12이기 때문이다. 그런데 그 상태로\n // 그리면 브라우저에서는 좌표가 NaN 이 되어 **조용히 빈 그림**이 나오고\n // (Canvas2D 명세상 비유한 좌표는 무시된다), Node 캔버스에서는 네이티브\n // 프로세스가 통째로 죽는다. 이 검증 계층이 막으려던 바로 그 실패다.\n //\n // 기본 데이터의 첫 원소를 본보기로 삼는다. 한 겹만 본다 — 원소의 속속까지\n // 파고들지 않는 것이 이 파일의 «얕은 검사» 규칙이다.\n const sample = shape[key];\n if (want === '배열' && Array.isArray(sample) && sample.length > 0) {\n const sampleKind = kindOf(sample[0]);\n const arr = given[key] as unknown[];\n for (let i = 0; i < arr.length; i++) {\n const elemKind = kindOf(arr[i]);\n if (elemKind !== sampleKind) {\n throw new CsatChartError(\n `type \"${type}\" 의 data.${key}[${i}]: ${shouldBe(sampleKind)} (지금 ${elemKind})`,\n );\n }\n }\n }\n }\n}\n","// © 2026 김용현\n// 렌더러는 Noto Serif KR(기본)·Noto Sans KR 을 쓴다. 글꼴이 없으면 대체 글꼴로\n// 그려져 시험지 양식이 재현되지 않는다. 번들에 글꼴을 넣지 않는 대신, 부르는\n// 사람만 내려받도록 이 헬퍼를 둔다.\n\nexport interface EnsureFontsOptions {\n /**\n * 글꼴 CSS 주소. 기본은 Google Fonts. 사내망·오프라인이면 바꾼다.\n *\n * `families` 와 짝이다. 한쪽만 바꾸면 어긋난다 — 예컨대 `families` 만\n * 바꾸면 기본 구글 스타일시트가 그 글꼴을 제공하지 않는데도 `true` 가\n * 나온다(아래 반환값 설명 참고).\n */\n href?: string;\n /** 확인할 글꼴 이름. 기본은 Noto Serif KR, Noto Sans KR. `href` 와 함께 바꾼다. */\n families?: string[];\n /** 이 시간(ms) 안에 준비되지 않으면 false 를 돌려주고 넘어간다. 기본 5000 */\n timeoutMs?: number;\n}\n\nconst DEFAULT_HREF =\n 'https://fonts.googleapis.com/css2' +\n '?family=Noto+Sans+KR:wght@400;700' +\n '&family=Noto+Serif+KR:wght@400;700' +\n '&display=swap';\n\nconst DEFAULT_FAMILIES = ['Noto Serif KR', 'Noto Sans KR'];\nconst LINK_ID = 'csat-chart-fonts';\n\nlet pending: Promise<boolean> | null = null;\n\n/**\n * 시험지 양식에 쓰이는 글꼴을 확보한다.\n *\n * 브라우저에서만 일한다. 준비되면 `true`, 시간 안에 못 받거나 Node 이거나\n * 브라우저가 `document.fonts` 를 모르면 `false` 를 돌려준다. **던지지 않는다** —\n * 글꼴이 없어도 그림은 대체 글꼴로 그려져야 하기 때문이다.\n *\n * ⚠️ `true` 가 «그 글꼴로 그려진다» 를 보장하지는 않는다. `document.fonts.load`\n * 는 페이지에 `@font-face` 가 없는 이름에 대해 거부하지 않고 **빈 배열로\n * 이행한다**. 그래서 `href` 없이 `families` 만 바꾸면 없는 글꼴에도 `true` 가\n * 나온다. 둘은 함께 바꾼다.\n *\n * ⚠️ 여러 번 불러도 실제 작업은 한 번만 하는데, **나중 호출의 옵션은 조용히\n * 버려진다.** 앞선 호출이 이미 시작했으면 그 약속을 그대로 돌려준다. 사내망\n * 주소를 쓸 것이라면 **가장 먼저** 그 옵션으로 부른다.\n *\n * ⚠️ «한 번만» 은 이 모듈 한 벌 기준이다. 한 페이지에 ESM 판과 CDN 판이 함께\n * 올라오면 각자 한 번씩 한다. `<link>` 는 id 로 걸러지므로 두 번 들어가지는\n * 않는다.\n */\nexport function ensureFonts(options: EnsureFontsOptions = {}): Promise<boolean> {\n if (typeof document === 'undefined') return Promise.resolve(false);\n pending ??= load(options);\n return pending;\n}\n\n/** 테스트 전용 — 다음 호출이 다시 일하도록 되돌린다. 공개 표면에 넣지 않는다. */\nexport function resetFontsForTest(): void {\n pending = null;\n}\n\ninterface FontFaceSetLike {\n load(spec: string): Promise<unknown>;\n}\n\nasync function load(o: EnsureFontsOptions): Promise<boolean> {\n const href = o.href ?? DEFAULT_HREF;\n const families = o.families ?? DEFAULT_FAMILIES;\n const timeoutMs = o.timeoutMs ?? 5000;\n\n // 이 구간까지 감싸야 «던지지 않는다» 가 완성된다. document 는 있는데 head 가\n // 아직 없거나(문서 파싱 도중), createElement 가 막힌 환경(엄격한 CSP·샌드박스)\n // 에서 여기서 던진다. load() 가 async 라 그 예외는 거부된 약속이 되어\n // ensureFonts() 호출부로 그대로 새어 나간다.\n try {\n if (!document.getElementById(LINK_ID)) {\n const link = document.createElement('link');\n link.id = LINK_ID;\n link.rel = 'stylesheet';\n link.href = href;\n document.head.appendChild(link);\n }\n } catch {\n return false;\n }\n\n const fonts = (document as unknown as { fonts?: FontFaceSetLike }).fonts;\n if (!fonts) return false;\n\n const work = (async () => {\n for (const family of families) {\n await fonts.load(`400 16px \"${family}\"`);\n await fonts.load(`700 16px \"${family}\"`);\n }\n return true;\n })();\n\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<boolean>((resolve) => {\n timer = setTimeout(() => resolve(false), timeoutMs);\n });\n\n try {\n // `work.catch` 가 있어야 «던지지 않는다» 는 약속이 지켜진다. 네트워크가\n // 막히면 fonts.load 가 reject 하는데, 그게 새어 나가면 글꼴 하나 때문에\n // 그림 그리기 자체가 멈춘다.\n return await Promise.race([work.catch(() => false), timeout]);\n } finally {\n // 타이머를 지우지 않으면 일이 끝난 뒤에도 살아남아, Node 에서는\n // 프로세스가 timeoutMs 만큼 더 붙들린다.\n clearTimeout(timer);\n }\n}\n","// © 2026 김용현\n// `CanvasRenderingContext2D.prototype.roundRect` 폴리필.\n//\n// core 안에서 이 메서드를 부르는 곳은 딱 셋이다 — `src/core/canvas/legend.ts`\n// (두 계열 범례 박스: `climate`·`deviation-a`·`deviation-b`·`pyramid`·`absbar`·\n// `stacked` 여섯 종류가 이 공용 범례를 거친다), `src/core/graphs/Hythergraph.ts`\n// (하이서그래프 자체 범례 박스), `src/core/graphs/ScatterBubble.ts`(산점도·버블\n// 범례 박스). 이 **여덟 종류만** `roundRect` 를 실제로 부른다 — 나머지 여덟\n// (`category-dot`·`cube`·`data-table`·`line`·`matrix-table`·`radar`·`ternary`·\n// `treemap`)는 부르지 않는다. 세 파일 모두 반지름을 **숫자 하나**로만 준다\n// (`ctx.roundRect(x, y, w, h, 0)` 아니면 `ctx.roundRect(x, y, w, h, 4)`) — 배열도\n// `DOMPointInit` 도 어디서도 안 쓴다. 그래도 표준 `number | number[]` 두 형태는\n// 함께 지원해 둔다 — 저수준 렌더러를 직접 부르는 사람이 다른 반지름 모양을\n// 줘도 이 폴리필이 새 실패를 더하지 않게.\n//\n// `roundRect` 는 core 안의 유일한 최신 Canvas 2D API 다. 나머지는 전부 오래된\n// 메서드라 README 가 약속하는 지원 환경(Chrome 80·Firefox 74·Safari 13.1)에서\n// 이미 문제없이 그려진다. 그런데 `roundRect` 만은 **Chrome 99·Firefox 112·\n// Safari 16.4(iOS 16.4)** 부터 있다 — 그 아래에서는 `ctx.roundRect is not a\n// function` 이 던져지고, 위 여덟 종류는 흰 캔버스만 남는다.\n//\n// 이 라이브러리의 실제 관객이 바로 그 아래 버전을 쓴다. 학교 전산실 Chrome 은\n// 몇 해씩 갱신되지 않는 경우가 흔하고, iPad 5세대·Air 2 는 iOS 15 에서 멈춘\n// 기기라 iOS 16.4 를 영영 받을 수 없다. 콘솔을 열지 않는 교사·학생에게 이\n// 실패는 «그냥 안 그려짐» 으로만 보인다 — README 가 Chrome 80 을 약속해 놓고.\n\n/** 이 폴리필이 흉내 내는 반지름 표기. core 가 실제로 쓰는 건 숫자 하나뿐이다. */\ntype RoundRectRadii = number | number[];\n\ninterface Corners {\n tl: number;\n tr: number;\n br: number;\n bl: number;\n}\n\nfunction normalizeRadii(radii: RoundRectRadii | undefined): Corners {\n if (radii === undefined) return { tl: 0, tr: 0, br: 0, bl: 0 };\n\n if (typeof radii === 'number') {\n if (!(radii >= 0)) throw new RangeError('roundRect: 반지름은 0 이상이어야 합니다');\n return { tl: radii, tr: radii, br: radii, bl: radii };\n }\n\n if (radii.some((r) => !(r >= 0))) {\n throw new RangeError('roundRect: 반지름은 0 이상이어야 합니다');\n }\n switch (radii.length) {\n case 1:\n return { tl: radii[0], tr: radii[0], br: radii[0], bl: radii[0] };\n case 2:\n return { tl: radii[0], br: radii[0], tr: radii[1], bl: radii[1] };\n case 3:\n return { tl: radii[0], tr: radii[1], bl: radii[1], br: radii[2] };\n case 4:\n return { tl: radii[0], tr: radii[1], br: radii[2], bl: radii[3] };\n default:\n throw new RangeError(`roundRect: 반지름 배열은 1~4개여야 합니다 (지금 ${radii.length}개)`);\n }\n}\n\n/**\n * 네 모서리 반지름의 합이 변의 길이를 넘지 않도록 축척을 맞춘다 — CSS\n * `border-radius`·Canvas 명세와 같은 보정이다. core 가 실제로 쓰는 4px 안팎의\n * 반지름에서는 사실상 아무 효과가 없지만, 저수준 렌더러를 직접 부르는 사람이\n * 박스보다 큰 반지름을 줘도 모양이 찌그러지지 않게 막아 둔다.\n */\nfunction clampCorners(r: Corners, w: number, h: number): Corners {\n const scale = Math.min(\n 1,\n w / (r.tl + r.tr) || Infinity,\n h / (r.tr + r.br) || Infinity,\n w / (r.bl + r.br) || Infinity,\n h / (r.tl + r.bl) || Infinity,\n );\n if (scale >= 1) return r;\n return { tl: r.tl * scale, tr: r.tr * scale, br: r.br * scale, bl: r.bl * scale };\n}\n\n/**\n * 네이티브 구현과 같은 방식으로 경로만 그린다 — `moveTo`/`lineTo`/`arcTo` 넷을\n * 돌아가며 잇는다. `beginPath()`·`fill()`·`stroke()` 는 호출자(core 렌더러)의\n * 몫이다. `roundRect` 자체가 그렇다 — 새 하위 경로를 여는 것 말고는 현재 경로에\n * 얹기만 한다.\n */\nfunction drawRoundRectPath(\n ctx: CanvasRenderingContext2D,\n x: number,\n y: number,\n w: number,\n h: number,\n radii: RoundRectRadii | undefined,\n): void {\n const r = clampCorners(normalizeRadii(radii), Math.abs(w), Math.abs(h));\n\n ctx.moveTo(x + r.tl, y);\n ctx.lineTo(x + w - r.tr, y);\n if (r.tr > 0) ctx.arcTo(x + w, y, x + w, y + r.tr, r.tr);\n ctx.lineTo(x + w, y + h - r.br);\n if (r.br > 0) ctx.arcTo(x + w, y + h, x + w - r.br, y + h, r.br);\n ctx.lineTo(x + r.bl, y + h);\n if (r.bl > 0) ctx.arcTo(x, y + h, x, y + h - r.bl, r.bl);\n ctx.lineTo(x, y + r.tl);\n if (r.tl > 0) ctx.arcTo(x, y, x + r.tl, y, r.tl);\n ctx.closePath();\n}\n\n/**\n * `CanvasRenderingContext2D.prototype.roundRect` 가 없으면 심는다.\n *\n * - 브라우저가 아닌 곳(Node 등)에서는 아무 일도 하지 않는다 —\n * `typeof CanvasRenderingContext2D === 'undefined'` 로 가려낸다.\n * - 이미 있으면(Chrome 99·Firefox 112·Safari 16.4 이상, 또는 이미 폴리필이\n * 설치된 두 번째 호출) 아무 일도 하지 않는다 — 네이티브 구현을 밀어내지 않는다.\n * - 그 아래에서만 심는다.\n *\n * `CsatChart` 생성자가 첫 렌더 전에 이 함수를 부르므로 파사드 사용자는 따로\n * 부를 필요가 없다. 저수준 렌더러(`renderClimateGraph` 등)를 파사드 없이 직접\n * 부르는 사람은 그리기 전에 `installRoundRectPolyfill()` 을 한 번 불러야\n * 한다 — 파사드를 거치지 않으므로 이 라이브러리가 대신 불러 줄 자리가 없다.\n *\n * @returns 실제로 심었으면 `true`. 심을 필요가 없었으면(Node 이거나 이미\n * 있음) `false`.\n */\nexport function installRoundRectPolyfill(): boolean {\n if (typeof CanvasRenderingContext2D === 'undefined') return false;\n\n const proto = CanvasRenderingContext2D.prototype;\n if (typeof proto.roundRect === 'function') return false;\n\n proto.roundRect = function (\n this: CanvasRenderingContext2D,\n x: number,\n y: number,\n w: number,\n h: number,\n radii?: RoundRectRadii,\n ): void {\n drawRoundRectPath(this, x, y, w, h, radii);\n } as CanvasRenderingContext2D['roundRect'];\n\n return true;\n}\n","// © 2026 김용현\n// 캔버스 획득·다시 그리기·PNG 추출의 수명주기를 관리하는 파사드.\nimport { REGISTRY } from './registry';\nimport { CsatChartError, assertChartData, assertChartType, assertConfigShape } from './validate';\nimport { ensureFonts, type EnsureFontsOptions } from './fonts';\nimport { installRoundRectPolyfill } from './roundrect';\nimport { clearCanvas, createDefaultGraphOptions, type GraphOptions } from './core/index';\nimport type { ChartDataMap, ConfigFor, CsatChartType, PartialGraphOptions, UpdateFor } from './types';\n\n/**\n * 브라우저의 `HTMLCanvasElement` 와 Node 캔버스 구현체가 함께 만족하는 최소 모양.\n * 라이브러리는 이 이상을 요구하지 않는다.\n */\nexport interface CanvasLike {\n width: number;\n height: number;\n getContext(id: '2d'): unknown;\n toDataURL?(type?: string): string;\n}\n\nconst DEFAULT_WIDTH = 800;\nconst DEFAULT_HEIGHT = 600;\n\n/**\n * 속성을 적지 않은 `<canvas>` 가 갖는 크기 (HTML 명세). **0 이 아니라 300×150 이다.**\n * 그래서 `if (!canvas.width)` 로는 «작성자가 크기를 적지 않았다» 를 알아낼 수 없다.\n */\nconst HTML_DEFAULT_WIDTH = 300;\nconst HTML_DEFAULT_HEIGHT = 150;\n\n/**\n * 이 축의 크기를 작성자가 «정하지 않았는가».\n *\n * 렌더러의 여백과 글자 크기는 800×600 에 맞춰 절대 픽셀로 박혀 있다. 300×150\n * 에서는 플롯 영역의 높이가 음수가 되어 제목·눈금·각주가 한 덩어리로 겹친\n * 읽을 수 없는 그림이 나온다. `<canvas id=\"c\"></canvas>` 라고만 쓰는 실수는\n * 아주 흔한데, 그 결과가 조용한 오작동이어서는 안 된다.\n *\n * 세 가지를 구별한다.\n * · 크기가 0·없음 → 정하지 않았다 (평범한 객체·OffscreenCanvas 흉내)\n * · 크기가 300/150 이고 그 속성이 없다 → 정하지 않았다 (`<canvas>` 만 쓴 경우)\n * · 그 밖의 모든 경우 → 작성자가 고른 크기다. 건드리지 않는다\n *\n * 두 번째 판정에만 `getAttribute` 가 필요한데 `CanvasLike` 에는 없다. 있으면\n * 쓰고 없으면 «작성자가 골랐다» 로 본다 — 덕타이핑이므로 이것이 안전한 쪽이다.\n */\nfunction isUnsized(canvas: CanvasLike, dim: 'width' | 'height', htmlDefault: number): boolean {\n const current = canvas[dim];\n if (!current) return true;\n if (current !== htmlDefault) return false;\n const el = canvas as { getAttribute?: (name: string) => string | null };\n return typeof el.getAttribute === 'function' && el.getAttribute(dim) === null;\n}\n\n/**\n * `data`·`fontSize`·`footnotes` 를 안전하게 덮는다.\n *\n * `{ ...base, ...patch }` 로 얕게만 덮으면 두 가지가 새어 나간다.\n * · `fontSize` 를 하나만 준 순간(CDN 사용자가 흔히 그런다: `{ title: 44 }`)\n * 나머지 세 값이 `undefined` 가 되어, Node 에서는 `ctx.font` 대입이\n * 던지고 브라우저에서는 명세상 조용히 무시된다 — 어느 쪽이든 사고다.\n * · `footnotes` 는 배열이다. 호출자가 쥔 배열을 그대로 붙들면, 나중에 그\n * 배열에 `push` 한 것이 다음 그리기에 몰래 새어 들어온다.\n */\nfunction mergeOptions(base: GraphOptions, patch?: PartialGraphOptions): GraphOptions {\n return {\n ...base,\n ...patch,\n fontSize: { ...base.fontSize, ...patch?.fontSize },\n footnotes: [...(patch?.footnotes ?? base.footnotes)],\n };\n}\n\n/**\n * 종류 하나를 붙들고 사는 차트.\n *\n * `T` 는 생성자의 `config.type` 에서 추론된다. 그래서 `update()` 의 `data` 도\n * 같은 종류로 좁혀지고, 다른 종류의 데이터를 넣으면 컴파일 시점에 걸린다.\n */\nexport class CsatChart<T extends CsatChartType = CsatChartType> {\n /**\n * 시험지 글꼴을 확보한다. 자세한 것은 `ensureFonts` 참고.\n *\n * 필드가 아니라 **메서드**로 둔다. `static readonly ensureFonts = ensureFonts`\n * 로 적으면 ES2020 로 낮출 때 클래스 «뒤» 의 대입문이 되는데, 그것은 지울 수\n * 없는 부수효과라 이 클래스와 레지스트리와 렌더러 16종이 모든 번들에 박힌다.\n * 메서드는 클래스 본문의 일부라 어느 목표에서도 그런 일이 없다 — 실제로\n * 겪은 문제이고, `test/bundle.test.ts` 가 이걸 회귀로 잡지는 않으니 여기\n * 적어 둔다.\n */\n static ensureFonts(options?: EnsureFontsOptions): Promise<boolean> {\n return ensureFonts(options);\n }\n\n readonly canvas: CanvasLike;\n\n private readonly ctx: CanvasRenderingContext2D;\n private readonly type: T;\n private data: ChartDataMap[T];\n private options: GraphOptions;\n private destroyed = false;\n\n constructor(target: CanvasLike | string, config: ConfigFor<T>) {\n this.canvas = resolveCanvas(target);\n\n const ctx = this.canvas.getContext('2d');\n if (!ctx) throw new CsatChartError('캔버스에서 2d 컨텍스트를 얻지 못했습니다');\n this.ctx = ctx as CanvasRenderingContext2D;\n\n assertConfigShape(config);\n assertChartType(config.type);\n assertChartData(config.type, config.data);\n\n this.type = config.type;\n this.data = config.data;\n this.options = mergeOptions(createDefaultGraphOptions(), config.options);\n\n if (isUnsized(this.canvas, 'width', HTML_DEFAULT_WIDTH)) this.canvas.width = DEFAULT_WIDTH;\n if (isUnsized(this.canvas, 'height', HTML_DEFAULT_HEIGHT)) this.canvas.height = DEFAULT_HEIGHT;\n\n // 여덟 종류(범례 박스가 있는 climate·deviation-a/b·pyramid·absbar·stacked·\n // hythergraph·scatter)는 그리는 도중 `ctx.roundRect()` 를 부른다. Chrome 99·\n // Firefox 112·Safari 16.4 아래에서는 그 메서드가 없어 `draw()` 가 던진다 —\n // 아래 `draw()` 보다 먼저 심어 둔다. 자세한 사정은 `./roundrect` 참고.\n installRoundRectPolyfill();\n\n this.draw();\n this.redrawWhenFontsArrive();\n }\n\n /** `data`·`options` 중 준 것만 덮고 다시 그린다. 어긋나면 던지고 이전 상태를 지킨다. */\n update(next: UpdateFor<T>): this {\n this.assertAlive();\n if (next.data !== undefined) {\n assertChartData(this.type, next.data);\n this.data = next.data;\n }\n if (next.options !== undefined) {\n this.options = mergeOptions(this.options, next.options);\n }\n this.draw();\n return this;\n }\n\n resize(width: number, height: number): this {\n this.assertAlive();\n if (!(width > 0) || !(height > 0)) {\n throw new CsatChartError(`크기는 0보다 커야 합니다 (지금 ${width}×${height})`);\n }\n this.canvas.width = width;\n this.canvas.height = height;\n this.draw();\n return this;\n }\n\n /**\n * PNG data URL 을 돌려준다.\n *\n * `scale` 은 **글자·선까지 함께 키우는** 배율이다. 캔버스만 키우는\n * `resize(1600, 1200)` 과 다르다 — 이 라이브러리의 글꼴 크기와 여백은 절대\n * 픽셀이라, 캔버스를 두 배로 하면 «두 배로 선명한 같은 그림» 이 아니라\n * «글자가 절반으로 작아진 다른 그림» 이 나온다. 인쇄용으로 뽑으려면 이쪽을 쓴다.\n */\n toDataURL(options: { scale?: number } = {}): string {\n this.assertAlive();\n if (typeof this.canvas.toDataURL !== 'function') {\n throw new CsatChartError('이 캔버스는 toDataURL 을 지원하지 않습니다');\n }\n const scale = options.scale ?? 1;\n if (!(Number.isFinite(scale) && scale > 0)) {\n throw new CsatChartError(`scale 은 0보다 큰 유한한 수여야 합니다 (지금 ${scale})`);\n }\n if (scale === 1) return this.canvas.toDataURL('image/png');\n\n const w = this.canvas.width;\n const h = this.canvas.height;\n try {\n this.canvas.width = Math.round(w * scale);\n this.canvas.height = Math.round(h * scale);\n this.drawScaled(scale, w, h);\n return this.canvas.toDataURL('image/png');\n } finally {\n // 화면에 붙어 있는 캔버스다. 원래 크기로 돌리고 다시 그려 둔다.\n this.canvas.width = w;\n this.canvas.height = h;\n this.draw();\n }\n }\n\n /** 브라우저 전용. Node 에서는 `toDataURL()` 이나 캔버스의 버퍼를 쓴다. */\n download(filename = 'csat-chart.png', options: { scale?: number } = {}): void {\n this.assertAlive();\n if (typeof document === 'undefined') {\n throw new CsatChartError(\n 'download() 는 브라우저에서만 씁니다. Node 에서는 toDataURL() 이나 캔버스의 버퍼를 쓰세요',\n );\n }\n const a = document.createElement('a');\n a.href = this.toDataURL(options);\n a.download = filename;\n a.click();\n }\n\n destroy(): void {\n if (this.destroyed) return;\n // 불투명한 흰 바탕으로 지운다 — 시험지 원고를 다루는 라이브러리라, 반투명\n // «지워진 상태» 보다 «빈 답안지» 처럼 보이는 쪽이 낫다. 생성자나 resize()\n // 가 바꿔 둔 캔버스 크기는 되돌리지 않는다 — destroy() 는 그리기만 멈춘다.\n clearCanvas(this.ctx, this.canvas.width, this.canvas.height);\n this.destroyed = true;\n }\n\n /**\n * 글꼴이 늦게 도착하면 한 번 다시 그린다.\n *\n * `await CsatChart.ensureFonts()` 를 빠뜨리는 실수가 이 라이브러리에서 가장\n * 흔할 실패다. 그런데 증상이 조용하고 영구적이다 — 대체 글꼴로 «멀쩡히»\n * 그려지고, 잠시 뒤 글꼴이 도착해도 아무도 다시 그리지 않는다. 시험지 서체를\n * 모르는 사람은 무엇이 잘못됐는지조차 알 수 없다. 빌드 도구도 타입 검사도\n * 없는 사용자에게 그 책임을 문서로만 지울 수는 없다.\n *\n * ⚠️ 만능이 아니다. `document.fonts.ready` 는 **부르는 시점에** 로딩 중인\n * 것이 끝나면 이행한다. 그래서 차트를 먼저 만들고 `ensureFonts()` 를 나중에\n * 부르면 이 약속은 이미 이행된 뒤라 도움이 안 된다. 순서를 지켜 부르거나,\n * 그냥 `await` 하는 것이 여전히 옳다.\n */\n private redrawWhenFontsArrive(): void {\n if (typeof document === 'undefined') return;\n const fonts = (document as unknown as { fonts?: { ready?: Promise<unknown> } }).fonts;\n if (!fonts?.ready) return;\n void Promise.resolve(fonts.ready)\n .then(() => {\n if (!this.destroyed) this.draw();\n })\n .catch(() => {\n // 글꼴 때문에 그림이 멈추면 안 된다.\n });\n }\n\n private draw(): void {\n const { width, height } = this.canvas;\n clearCanvas(this.ctx, width, height);\n // 캐스팅이 없다. RegistryEntry<T> 의 data 가 곧 ChartDataMap[T] 이므로\n // 엉뚱한 값을 넘기면 여기서 컴파일이 막힌다.\n REGISTRY[this.type].render(this.ctx, width, height, this.data, this.options);\n }\n\n /**\n * 논리 크기는 `width`×`height` 로 두고 픽셀만 `scale` 배로 그린다.\n *\n * 렌더러들은 변환 행렬을 전혀 건드리지 않으므로(`grep setTransform src/core` →\n * 없음) 여기서 한 번 걸어 두면 그림 전체가 그대로 확대된다.\n */\n private drawScaled(scale: number, width: number, height: number): void {\n this.ctx.setTransform(scale, 0, 0, scale, 0, 0);\n try {\n clearCanvas(this.ctx, width, height);\n REGISTRY[this.type].render(this.ctx, width, height, this.data, this.options);\n } finally {\n this.ctx.setTransform(1, 0, 0, 1, 0, 0);\n }\n }\n\n private assertAlive(): void {\n if (this.destroyed) throw new CsatChartError('이미 destroy() 된 차트입니다');\n }\n}\n\nfunction resolveCanvas(target: CanvasLike | string): CanvasLike {\n if (typeof target === 'string') {\n if (typeof document === 'undefined') {\n throw new CsatChartError(`id 문자열은 브라우저에서만 씁니다 (\"${target}\")`);\n }\n const el = document.getElementById(target);\n if (!el) throw new CsatChartError(`id \"${target}\" 인 요소를 찾지 못했습니다`);\n // XHTML(application/xhtml+xml) 문서에서는 tagName 이 대문자로 바뀌지 않는다.\n // 대소문자를 맞춰 보지 않으면 멀쩡한 <canvas> 를 «캔버스가 아니다» 라고 막는다.\n if (el.tagName.toUpperCase() !== 'CANVAS') {\n throw new CsatChartError(`id \"${target}\" 인 요소는 <canvas> 가 아닙니다`);\n }\n return el as unknown as CanvasLike;\n }\n\n // 가장 흔한 사용법은 `new CsatChart(document.getElementById('c'), …)` 다.\n // id 를 잘못 적으면 여기로 null 이 들어오는데, 그냥 두면 아래에서\n // «Cannot read properties of null (reading 'getContext')» 라는 영문 TypeError 로\n // 끝난다. 개발자 도구를 열지 않는 사용자를 겨냥한 라이브러리에서, 문자열\n // 경로에는 친절한 안내가 있는데 요소 경로만 그러면 앞뒤가 맞지 않는다.\n if (target === null || typeof target !== 'object') {\n throw new CsatChartError(\n `첫 인자는 <canvas> 요소이거나 id 문자열이어야 합니다 (지금 ${target === null ? 'null' : typeof target})` +\n (target === null ? ' — getElementById 가 그 id 를 찾지 못한 것은 아닌지 보세요' : ''),\n );\n }\n if (typeof (target as { getContext?: unknown }).getContext !== 'function') {\n throw new CsatChartError('첫 인자에 getContext 가 없습니다 — <canvas> 요소가 맞는지 보세요');\n }\n return target;\n}\n"],"mappings":"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","names":["AGE_GROUPS","DOT_MARKER_ORDER","LINE_DASH","LINE_MARKER_ORDER","LINE_STYLE_ORDER","MONTH_LABELS_EN","MONTH_LABELS_NUM","size","box","line","fs","swatch","maxIconW","fillOf","formatTick","drawMarker","gap","CELL_H_RATIO","PAD_RATIO","formatValue","PAD_RATIO","line","PAD_RATIO","MONTH_LABELS","INTERVAL_INDICES","size","drawMarker","formatTick","LEGEND_FONT","LINE_STYLES","formatTick","plotX","plotW","SHRINK_LIMIT","formatTick","size","SHRINK_LIMIT","size","AGE_GROUPS","DOT_MARKER_ORDER","LINE_MARKER_ORDER","LINE_STYLE_ORDER","MONTH_LABELS_EN","MONTH_LABELS_NUM","LINE_DASH"]}