교차 집계
같은 데이터로 지역 × 품목군, 지역 × 월 교차표를 만들고 행·열 합계를 붙입니다.
보고서 정의 (JSON)
{
"name": "지역별 교차 집계",
"page": {
"size": "A4",
"orientation": "landscape",
"margins": 14
},
"font": {
"family": "Nanum Gothic",
"size": 8.5
},
"bands": {
"title": {
"height": 150,
"elements": [
{
"type": "text",
"x": 0,
"y": 0,
"w": 269,
"h": 10,
"text": "2026년 3분기 지역별 교차 집계",
"style": {
"fontSize": 16,
"bold": true
}
},
{
"type": "text",
"x": 0,
"y": 10,
"w": 269,
"h": 6,
"text": "대상 {count()}건 · 매출 합계 {sum(amount):#,##0}원",
"style": {
"color": "#4a5568"
}
},
{
"type": "text",
"x": 0,
"y": 20,
"w": 200,
"h": 7,
"text": "1. 지역 × 품목군 (금액, 원)",
"style": {
"bold": true
}
},
{
"type": "crosstab",
"x": 0,
"y": 28,
"w": 269,
"h": 50,
"rowKey": "region",
"columnKey": "category",
"value": "amount",
"aggregate": "sum",
"format": "#,##0",
"corner": "지역 / 품목군",
"rowHeaderWidth": 28,
"cellHeight": 7,
"headerStyle": {
"bold": true,
"align": "center",
"background": "#eef1f6",
"border": "0.2 solid #8a94a6"
},
"style": {
"border": "0.15 solid #b9c1cd",
"align": "right",
"fontSize": 8
}
},
{
"type": "text",
"x": 0,
"y": 88,
"w": 200,
"h": 7,
"text": "2. 지역 × 월 (건수)",
"style": {
"bold": true
}
},
{
"type": "crosstab",
"x": 0,
"y": 96,
"w": 190,
"h": 50,
"rowKey": "region",
"columnKey": "month(date) + \"월\"",
"value": "no",
"aggregate": "count",
"format": "#,##0",
"corner": "지역 / 월",
"rowHeaderWidth": 28,
"cellHeight": 7,
"headerStyle": {
"bold": true,
"align": "center",
"background": "#eef1f6",
"border": "0.2 solid #8a94a6"
},
"style": {
"border": "0.15 solid #b9c1cd",
"align": "right",
"fontSize": 8
}
}
]
}
}
}데이터
긴 목록은 처음 3건만 보여 줍니다.
{
"rows": [
{
"no": 1,
"date": "2026-09-28",
"region": "대구",
"customer": "다온상사",
"manager": "정하준",
"product": "노트북 15형",
"category": "전자",
"qty": 1,
"price": 1290000,
"amount": 1290000
},
{
"no": 2,
"date": "2026-07-21",
"region": "서울",
"customer": "새봄디자인",
"manager": "강서윤",
"product": "무선 키보드",
"category": "주변기기",
"qty": 7,
"price": 59000,
"amount": 413000
},
{
"no": 3,
"date": "2026-08-09",
"region": "경기",
"customer": "온누리건설",
"manager": "박도윤",
"product": "27형 모니터",
"category": "전자",
"qty": 5,
"price": 349000,
"amount": 1745000
},
"… 600 items"
]
}이 데모의 코드
import type { ReportDefinition, Style } from '@freereportjs/core';
import { makeSales } from '../../samples/data';
import { mountViewer, select, tr, type DemoContext } from '../ui';
/** Cross tabulation of the sales rows: region × product group (amount) and region × month (count), with totals. */
const head: Style = { bold: true, align: 'center', background: '#eef1f6', border: '0.2 solid #8a94a6' };
const cell: Style = { border: '0.15 solid #b9c1cd', align: 'right', fontSize: 8 };
export const definition: ReportDefinition = {
name: '지역별 교차 집계',
page: { size: 'A4', orientation: 'landscape', margins: 14 },
font: { family: 'Nanum Gothic', size: 8.5 },
bands: {
title: {
height: 150,
elements: [
{ type: 'text', x: 0, y: 0, w: 269, h: 10, text: '2026년 3분기 지역별 교차 집계', style: { fontSize: 16, bold: true } },
{ type: 'text', x: 0, y: 10, w: 269, h: 6, text: '대상 {count()}건 · 매출 합계 {sum(amount):#,##0}원', style: { color: '#4a5568' } },
{ type: 'text', x: 0, y: 20, w: 200, h: 7, text: '1. 지역 × 품목군 (금액, 원)', style: { bold: true } },
{ type: 'crosstab', x: 0, y: 28, w: 269, h: 50, rowKey: 'region', columnKey: 'category', value: 'amount', aggregate: 'sum', format: '#,##0', corner: '지역 / 품목군', rowHeaderWidth: 28, cellHeight: 7, headerStyle: head, style: cell },
{ type: 'text', x: 0, y: 88, w: 200, h: 7, text: '2. 지역 × 월 (건수)', style: { bold: true } },
{ type: 'crosstab', x: 0, y: 96, w: 190, h: 50, rowKey: 'region', columnKey: 'month(date) + "월"', value: 'no', aggregate: 'count', format: '#,##0', corner: '지역 / 월', rowHeaderWidth: 28, cellHeight: 7, headerStyle: head, style: cell },
],
},
},
};
export const sampleData = { rows: makeSales(600) };
export default function mount(ctx: DemoContext) {
const viewer = mountViewer(ctx, { definition, data: sampleData, zoom: 'page', fileName: '교차 집계' });
select(ctx, tr(ctx.lang, { en: 'Rows', ko: '건수' }), [['200', '200'], ['600', '600'], ['5000', '5,000']], '600', n => {
void viewer.setData({ rows: makeSales(Number(n)) });
});
}