FreeGridv1.6.0한국어

Formulas

A spreadsheet-style formula engine: computed columns, about 60 functions, whole-column totals with COL(), running totals with PREV(), formulas typed into cells and your own functions.

Live demoFormula engine

Computed columns, about 60 functions, column totals with COL(), running totals with PREV() and your own functions.

Code for this demo
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 has a spreadsheet-style formula engine. Each formula is parsed once and reused, so it stays fast on 100,000 rows, and when a value changes the computed columns, totals, sorting, filters and exports that use it follow. There is no eval: a formula in your data can never run code.

Computed columns

Give a column a formula and it is worked out for every row, even though the value is not in your data. Use field names of the same row (in brackets when they have spaces: [unit price]) and the ids of other computed columns. COL(field) is every value of a column and PREV(field) is the value one row up; running totals work across 100,000 rows.

createGrid(el, {
  columns: [
    { field: 'item' },
    { field: 'qty', type: 'number', editable: true },
    { field: 'price', type: 'number', editable: true },
    // computed columns: worked out for every row, no field needed (give them an id)
    { id: 'amount', header: 'Amount', type: 'number', formula: '=qty * price' },
    { id: 'share', header: 'Share', type: 'number', format: '0.0%', formula: '=amount / SUM(COL(amount))' },
    { id: 'running', header: 'Running total', type: 'number', formula: '=amount + PREV(running)' },
  ],
  data,
});

Formulas typed into cells

With formulas: true people can type a formula starting with = into a cell. The result is what shows, sorts, copies and exports to Excel; the editor shows the formula again.

// with formulas: true, a cell value starting with '=' is worked out
{ field: 'total', type: 'number', editable: true, formulas: true }

data = [{ qty: 2, price: 10, total: '=qty*price' }];   // shows 20; the editor shows =qty*price

Functions

Operators are + - * / % ^, & to join text and = <> < > <= >= to compare. Separate arguments with commas or semicolons. Errors show as spreadsheet codes: #DIV/0!, #REF!, #NAME?, #VALUE!, #CYCLE!.

NameTypeDescription
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 PINumbers. SUM, AVG, MIN and MAX accept COL(field) and skip text in it.
TextCONCAT TEXTJOIN LEN LEFT RIGHT MID UPPER LOWER PROPER TRIM SUBSTITUTE REPLACE REPT FIND SEARCH VALUE TEXTText. TEXT(n, "#,##0.00") formats numbers, TEXT(d, "yyyy-mm-dd") dates.
LogicIF IFS IFERROR IFNA SWITCH CHOOSE AND OR XOR NOT ISBLANK ISNUMBER ISTEXT ISERRORIF, IFS, IFERROR, SWITCH and CHOOSE only work out the branch they take.
DatesTODAY NOW DATE YEAR MONTH DAY WEEKDAY DAYS EDATE EOMONTHDates as "YYYY-MM-DD" text, like date columns. DAYS(end, start) counts days.
Across rowsCOL(field) PREV(field, n) NEXT(field, n) ROW() SUMIF COUNTIF AVERAGEIF LOOKUP XLOOKUP RANKCOL is a whole column; PREV / NEXT read n rows up or down; criteria like ">10", "<>x", "A*".
=ROUND(qty * price * (1 - discount), 0)       ' amount after discount
=IF(stock < 10, "low", "ok")                    ' a condition
=IFERROR(sales / visits, 0)                     ' no division by zero
=SUMIF(COL(region), "East", COL(amount))        ' total of matching rows
=COUNTIF(COL(status), "Done")                   ' count of matching rows
=LOOKUP(managerId, COL(id), COL(name))          ' a value from another row
=amount + PREV(running)                         ' running total
=RANK(score, COL(score))                        ' rank
=DAYS(dueDate, TODAY())                         ' days left
=TEXT(price, "#,##0") & " USD"                  ' formatted text

Your own functions

registerFormulaFunction adds a function every grid's formulas can call. A function that throws shows #VALUE!.

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

// your own rules as functions: =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 and ROW follow the order of your data, and COL sees every row whatever the filters, so values do not jump when people sort or filter.

Questions about this page

Can a FreeGrid column be calculated from other columns?

Yes. Give the column a formula such as =qty * price and an id; it is worked out for every row and used for display, sorting, filtering, summaries and exports.

How do I make a running total in a grid?

Use PREV: a computed column with formula =amount + PREV(running) adds each row to the total of the row above, following the order of the data.

Which spreadsheet functions does FreeGrid support?

About 60, including SUM, AVG, ROUND, IF, IFS, IFERROR, SUMIF, COUNTIF, LOOKUP, RANK, LEFT, MID, TEXT, YEAR, DAYS and EDATE, plus your own through registerFormulaFunction.