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*priceFunctions
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!.
| Name | Type | Description |
|---|---|---|
Math | SUM 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 | Numbers. SUM, AVG, MIN and MAX accept COL(field) and skip text in it. |
Text | CONCAT TEXTJOIN LEN LEFT RIGHT MID UPPER LOWER PROPER TRIM SUBSTITUTE REPLACE REPT FIND SEARCH VALUE TEXT | Text. TEXT(n, "#,##0.00") formats numbers, TEXT(d, "yyyy-mm-dd") dates. |
Logic | IF IFS IFERROR IFNA SWITCH CHOOSE AND OR XOR NOT ISBLANK ISNUMBER ISTEXT ISERROR | IF, IFS, IFERROR, SWITCH and CHOOSE only work out the branch they take. |
Dates | TODAY NOW DATE YEAR MONTH DAY WEEKDAY DAYS EDATE EOMONTH | Dates as "YYYY-MM-DD" text, like date columns. DAYS(end, start) counts days. |
Across rows | COL(field) PREV(field, n) NEXT(field, n) ROW() SUMIF COUNTIF AVERAGEIF LOOKUP XLOOKUP RANK | COL 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 textYour 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.