FreeGridv1.6.0English

수식

엑셀 같은 수식 엔진: 계산 컬럼, 함수 약 60개, COL()로 컬럼 전체 합계, PREV()로 누계, 셀에 직접 입력하는 수식, 직접 만든 함수.

실행 예제수식 엔진

계산 컬럼, 함수 약 60개, COL()로 컬럼 전체 합계, PREV()로 누계, 직접 만든 함수까지.

이 데모의 코드
import { createGrid, registerFormulaFunction, type Row } from '@freegridjs/core';
import { button, separator, text, tr, type DemoContext } from '../ui';
import '../data'; // registers the grid's language packs

export default function mount(ctx: DemoContext) {
  const { lang } = ctx;
  const h = (en: string, ko: string) => tr(lang, { en, ko });
  const items = [
    [h('Laptop', '노트북'), 2, 1290], [h('Monitor', '모니터'), 4, 320], [h('Keyboard', '키보드'), 10, 59], [h('Mouse', '마우스'), 12, 25],
    [h('Desk', '책상'), 3, 410], [h('Chair', '의자'), 6, 230], [h('Headset', '헤드셋'), 5, 89], [h('Webcam', '웹캠'), 7, 75],
  ] as const;
  const data: Row[] = items.map(([item, qty, price], i) => ({ id: i + 1, item, qty, price, discount: i % 3 === 0 ? 0.1 : 0, note: '' }));
  // a function of your own: formulas can call it like any other
  registerFormulaFunction('VAT', ([amount]) => Math.round(Number(amount) * 0.1));

  const grid = createGrid(ctx.host, {
    columns: [
      { field: 'item', header: h('Item', '품목'), width: 120 },
      { field: 'qty', header: h('Qty', '수량'), type: 'number', width: 80, editable: true },
      { field: 'price', header: h('Price', '단가'), type: 'number', format: '#,##0', width: 100, editable: true },
      { field: 'discount', header: h('Discount', '할인'), type: 'number', format: '0%', width: 90, editable: true },
      // computed columns: a formula on the column, worked out for every row
      { id: 'amount', header: h('Amount', '금액'), type: 'number', format: '#,##0', width: 110, formula: '=ROUND(qty * price * (1 - discount), 0)' },
      { id: 'vat', header: h('VAT', '부가세'), type: 'number', format: '#,##0', width: 90, formula: '=VAT(amount)' },
      { id: 'share', header: h('Share', '비중'), type: 'number', format: '0.0%', width: 90, formula: '=amount / SUM(COL(amount))' },
      { id: 'running', header: h('Running total', '누계'), type: 'number', format: '#,##0', width: 120, formula: '=amount + PREV(running)' },
      { id: 'rank', header: h('Rank', '순위'), type: 'number', width: 70, formula: '=RANK(amount, COL(amount))' },
      { id: 'grade', header: h('Grade', '등급'), width: 90, formula: h('=IFS(share >= 0.25, "A", share >= 0.1, "B", TRUE, "C")', '=IFS(share >= 0.25, "A", share >= 0.1, "B", TRUE, "C")') },
      // a cell formula typed into the data (columns with formulas: true)
      { field: 'note', header: h('Your formula', '직접 입력'), width: 200, editable: true, formulas: true },
    ],
    data, locale: lang, getRowId: r => String(r.id),
    summary: { rows: [{ label: h('Total', '합계'), columns: { amount: 'sum', vat: 'sum' } }] },
  });
  grid.on('cellValueChanged', e => ctx.log(`cellValueChanged → ${e.colId}: ${String(e.oldValue)} → ${String(e.newValue)}`));

  text(ctx, h('Amount, VAT, share, running total, rank and grade are formulas. Edit Qty, Price or Discount and they follow; type =qty*2 or =UPPER(item) into the last column.',
    '금액·부가세·비중·누계·순위·등급은 수식입니다. 수량·단가·할인을 고치면 따라 바뀌고, 마지막 컬럼에 =qty*2나 =UPPER(item)을 입력해 보세요.'));
  separator(ctx);
  button(ctx, h('Sort by amount', '금액순 정렬'), () => grid.setSort([{ colId: 'amount', dir: 'desc' }]));
  button(ctx, h('Price +10%', '단가 10% 인상'), () => {
    grid.applyTransaction({ update: grid.getData().map(r => ({ id: r.id, price: Math.round(Number(r.price) * 1.1) })) });
  });
  button(ctx, h('Fill example formulas', '수식 예시 채우기'), () => {
    const examples = ['=qty * 2', '=UPPER(item)', '=IF(amount > 2000, "big", "small")', '=TEXT(price, "#,##0") & " won"', '=LEN(item)', '=ROUND(amount / qty, 1)', '=YEAR(TODAY())', '=COUNTIF(COL(discount), ">0")'];
    grid.applyTransaction({ update: grid.getData().map((r, i) => ({ id: r.id, note: examples[i % examples.length] })) });
  });
  return grid;
}

FreeGrid에는 엑셀과 비슷한 수식 엔진이 들어 있습니다. 수식은 처음 한 번만 해석해 두고 다시 쓰기 때문에 10만 행에서도 빠르고, 값이 바뀌면 그 값을 쓰는 계산 컬럼, 합계, 정렬, 필터, 내보내기가 모두 따라 바뀝니다. eval을 쓰지 않아 데이터에 들어 있는 수식이 코드를 실행할 수 없습니다.

계산 컬럼

컬럼에 formula를 주면 데이터에 없는 값도 행마다 계산해 보여 줍니다. 같은 행의 필드 이름(공백이 있으면 [단가 합계]처럼 대괄호)과 다른 계산 컬럼의 id를 그대로 쓸 수 있습니다. COL(필드)는 그 컬럼 전체 값, PREV(필드)는 바로 위 행의 값입니다. 누계처럼 위 행을 참조하는 수식도 10만 행까지 문제없이 계산됩니다.

createGrid(el, {
  columns: [
    { field: 'item' },
    { field: 'qty', type: 'number', editable: true },
    { field: 'price', type: 'number', editable: true },
    // 계산 컬럼: 데이터에 없어도 행마다 계산됩니다. id가 필요합니다.
    { id: 'amount', header: '금액', type: 'number', formula: '=qty * price' },
    { id: 'share', header: '비중', type: 'number', format: '0.0%', formula: '=amount / SUM(COL(amount))' },
    { id: 'running', header: '누계', type: 'number', formula: '=amount + PREV(running)' },
  ],
  data,
});

셀에 입력하는 수식

컬럼에 formulas: true를 주면 사용자가 셀에 =로 시작하는 수식을 직접 입력할 수 있습니다. 화면, 정렬, 복사, 엑셀 내보내기에는 결과가 쓰이고, 편집기를 열면 수식이 다시 보입니다.

// formulas: true인 컬럼은 셀 값이 '='로 시작하면 수식으로 계산합니다
{ field: 'total', type: 'number', editable: true, formulas: true }

data = [{ qty: 2, price: 10, total: '=qty*price' }];   // 화면에는 20, 편집기에는 =qty*price

함수

연산자는 + - * / % ^, 글자 잇기 &, 비교 = <> < > <= >=입니다. 인수 구분은 쉼표나 세미콜론을 씁니다. 오류는 #DIV/0!, #REF!, #NAME?, #VALUE!, #CYCLE!처럼 엑셀식 코드로 표시됩니다.

이름타입설명
MathSUM AVG AVERAGE MIN MAX MEDIAN COUNT COUNTA COUNTBLANK PRODUCT ROUND ROUNDUP ROUNDDOWN INT TRUNC ABS SIGN SQRT POWER EXP LN LOG10 MOD CEILING FLOOR PI숫자 계산. SUM·AVG·MIN·MAX는 COL(필드)를 받고, 그 안의 글자는 건너뜁니다.
TextCONCAT TEXTJOIN LEN LEFT RIGHT MID UPPER LOWER PROPER TRIM SUBSTITUTE REPLACE REPT FIND SEARCH VALUE TEXT글자 처리. TEXT(n, "#,##0.00")은 숫자, TEXT(d, "yyyy-mm-dd")는 날짜를 형식에 맞춰 바꿉니다.
LogicIF IFS IFERROR IFNA SWITCH CHOOSE AND OR XOR NOT ISBLANK ISNUMBER ISTEXT ISERRORIF·IFS·IFERROR·SWITCH·CHOOSE는 선택된 쪽만 계산합니다.
DatesTODAY NOW DATE YEAR MONTH DAY WEEKDAY DAYS EDATE EOMONTH날짜는 날짜 컬럼처럼 "YYYY-MM-DD" 글자로 다룹니다. DAYS(끝, 시작)은 일수를 셉니다.
Across rowsCOL(field) PREV(field, n) NEXT(field, n) ROW() SUMIF COUNTIF AVERAGEIF LOOKUP XLOOKUP RANKCOL은 컬럼 전체, PREV/NEXT는 n행 위·아래 값. 조건은 ">10", "<>x", "A*"처럼 씁니다.
=ROUND(qty * price * (1 - discount), 0)       ' 할인 적용 금액
=IF(stock < 10, "부족", "충분")                  ' 조건
=IFERROR(sales / visits, 0)                     ' 0으로 나누기 방지
=SUMIF(COL(region), "서울", COL(amount))        ' 조건부 합계
=COUNTIF(COL(status), "완료")                    ' 조건에 맞는 개수
=LOOKUP(managerId, COL(id), COL(name))          ' 다른 행에서 찾기
=amount + PREV(running)                         ' 누계
=RANK(score, COL(score))                        ' 순위
=DAYS(dueDate, TODAY())                         ' 남은 일수
=TEXT(price, "#,##0") & "원"                     ' 서식 있는 글자

직접 만든 함수

registerFormulaFunction으로 회사마다 다른 계산 규칙을 함수로 등록하세요. 등록한 함수는 모든 그리드의 수식에서 바로 쓸 수 있고, 함수 안에서 오류가 나면 #VALUE!로 표시됩니다.

import { registerFormulaFunction } from '@freegridjs/core';

// 우리 회사 규칙도 함수로: =VAT(amount), =GRADE(score)
registerFormulaFunction('VAT', ([amount]) => Math.round(Number(amount) * 0.1));
registerFormulaFunction('GRADE', ([score]) => (Number(score) >= 90 ? 'A' : Number(score) >= 70 ? 'B' : 'C'));

수식은 원본 데이터 순서를 기준으로 PREV·NEXT·ROW를 계산하고, COL은 필터와 관계없이 모든 행을 봅니다. 그래서 정렬이나 필터를 바꿔도 값이 흔들리지 않습니다.

이 페이지에 대한 질문

그리드 컬럼을 다른 컬럼으로 계산할 수 있나요?

네. 컬럼에 =qty * price 같은 formula와 id를 주면 행마다 계산되고, 화면·정렬·필터·요약·내보내기에 모두 쓰입니다.

그리드에서 누계는 어떻게 만드나요?

PREV를 쓰세요. =amount + PREV(running) 수식을 가진 계산 컬럼은 바로 위 행의 누계에 이번 행 값을 더합니다. 순서는 원본 데이터 순서를 따릅니다.

어떤 엑셀 함수를 쓸 수 있나요?

SUM, AVG, ROUND, IF, IFS, IFERROR, SUMIF, COUNTIF, LOOKUP, RANK, LEFT, MID, TEXT, YEAR, DAYS, EDATE 등 약 60개이고, registerFormulaFunction으로 직접 만든 함수도 추가할 수 있습니다.