Svelte Example: Aggregation

<script lang="ts">
  import { FlexRender, aggregationFeature, aggregationFn_count, aggregationFn_extent, aggregationFn_mean, aggregationFn_sum, columnFilteringFeature, createColumnHelper, createFilteredRowModel, createPaginatedRowModel, createTable, filterFn_includesString, metaHelper, rowPaginationFeature, rowSelectionFeature, tableFeatures } from '@tanstack/svelte-table'
  import { makeData } from './makeData'
  import type { Sale } from './makeData'
  import type { Table } from '@tanstack/svelte-table'
  import './index.css'
  type RowSource = 'all' | 'custom' | 'filtered' | 'page' | 'selected'
  type AggregationTableMeta = { rowSource: RowSource }
  const features = tableFeatures({
    aggregationFeature, columnFilteringFeature, rowPaginationFeature, rowSelectionFeature,
    filteredRowModel: createFilteredRowModel(), paginatedRowModel: createPaginatedRowModel(),
    filterFns: { includesString: filterFn_includesString },
    aggregationFns: { count: aggregationFn_count, extent: aggregationFn_extent, mean: aggregationFn_mean, sum: aggregationFn_sum },
    tableMeta: metaHelper<AggregationTableMeta>(),
  })
  const columnHelper = createColumnHelper<typeof features, Sale>()
  function getAggregationRows(table: Table<typeof features, Sale>) {
    const source = table.options.meta?.rowSource
    if (source === 'all') return table.getCoreRowModel().rows
    if (source === 'page') return table.getRowModel().rows
    if (source === 'selected') return table.getFilteredSelectedRowModel().rows
    if (source === 'custom') return table.getCoreRowModel().rows.slice(0, 3)
    return undefined
  }
  function formatValue(value: unknown): string {
    if (Array.isArray(value)) return value.map(formatValue).join(' – ')
    if (value && typeof value === 'object') return Object.entries(value).map(([key, entry]) => `${key}: ${formatValue(entry)}`).join(', ')
    if (typeof value === 'number') return value.toLocaleString(undefined, { maximumFractionDigits: 2 })
    return String(value ?? '—')
  }
  let data = $state(makeData(10_000))
  let rowSource = $state<RowSource>('filtered')
  const columns = columnHelper.columns([
    columnHelper.display({ id: 'select' }),
    columnHelper.accessor('category', { header: 'Category', filterFn: 'includesString' }),
    columnHelper.accessor('item', { header: 'Item', footer: ({ table }) => `${table.options.meta?.rowSource} total` }),
    columnHelper.accessor('amount', { header: 'Amount', aggregationFn: 'sum', cell: ({ getValue }) => getValue<number>().toLocaleString(), footer: ({ column, table }) => formatValue(column.getAggregationValue(getAggregationRows(table))) }),
    columnHelper.accessor('score', { header: 'Score', aggregationFn: ['count', 'mean', { id: 'range', aggregationFn: 'extent' }], footer: ({ column, table }) => formatValue(column.getAggregationValue(getAggregationRows(table))) }),
  ])
  const table = createTable({
    features, columns,
    get data() { return data },
    get meta() { return { rowSource } },
    initialState: { pagination: { pageIndex: 0, pageSize: 10 } }, debugTable: true, debugColumns: true,
  }, state => state)
  function changeRowSource(value: RowSource) {
    rowSource = value
    table.setOptions((previous) => ({ ...previous, meta: { rowSource: value } }))
  }
</script>

<div class="demo-root">
  <h1>Aggregation without grouping</h1>
  <p>Amount uses a scalar <code>sum</code>. Score runs count, mean, and range together and returns a keyed object.</p>
  <div><button onclick={() => data = makeData(10_000)}>Regenerate Data</button><button onclick={() => data = makeData(200_000)}>Stress Test (200k rows)</button></div>
  <div class="spacer-sm"></div>
  <div class="controls">
    <label>Category filter: <input value={(table.getColumn('category')?.getFilterValue() ?? '') as string} oninput={(event) => table.getColumn('category')?.setFilterValue(event.currentTarget.value)} /></label>
    <label>Total rows: <select value={rowSource} onchange={(event) => changeRowSource(event.currentTarget.value as RowSource)}><option value="filtered">Filtered rows</option><option value="all">All rows</option><option value="page">Visible page</option><option value="selected">Filtered selected rows</option><option value="custom">First three core rows</option></select></label>
  </div>
  <div class="spacer-sm"></div>
  <table>
    <thead>{#each table.getHeaderGroups() as group (group.id)}<tr>{#each group.headers as header (header.id)}<th>{#if header.column.id === 'select'}<input type="checkbox" checked={table.getIsAllPageRowsSelected()} onchange={() => table.toggleAllPageRowsSelected()} />{:else if !header.isPlaceholder}<FlexRender {header} />{/if}</th>{/each}</tr>{/each}</thead>
    <tbody>{#each table.getRowModel().rows as row (row.id)}<tr>{#each row.getAllCells() as cell (cell.id)}<td class:numeric={cell.column.id === 'amount'}>{#if cell.column.id === 'select'}<input type="checkbox" checked={row.getIsSelected()} onchange={() => row.toggleSelected()} />{:else}<FlexRender {cell} />{/if}</td>{/each}</tr>{/each}</tbody>
    <tfoot>{#each table.getFooterGroups() as group (group.id)}<tr>{#each group.headers as header (header.id)}<th colspan={header.colSpan}>{#if !header.isPlaceholder}<FlexRender footer={header} />{/if}</th>{/each}</tr>{/each}</tfoot>
  </table>
  <div class="spacer-sm"></div>
  <div class="controls"><button class="demo-button demo-button-sm" onclick={() => table.firstPage()} disabled={!table.getCanPreviousPage()}>{'<<'}</button><button class="demo-button demo-button-sm" onclick={() => table.previousPage()} disabled={!table.getCanPreviousPage()}>{'<'}</button><button class="demo-button demo-button-sm" onclick={() => table.nextPage()} disabled={!table.getCanNextPage()}>{'>'}</button><button class="demo-button demo-button-sm" onclick={() => table.lastPage()} disabled={!table.getCanNextPage()}>{'>>'}</button>
    <span class="inline-controls"><div>Page</div><strong>{(table.state.pagination.pageIndex + 1).toLocaleString()} of {table.getPageCount().toLocaleString()}</strong></span>
    <span class="inline-controls">| Go to page:<input type="number" min="1" max={table.getPageCount()} value={table.state.pagination.pageIndex + 1} oninput={(event) => table.setPageIndex(event.currentTarget.value ? Number(event.currentTarget.value) - 1 : 0)} class="page-size-input" /></span>
    <select value={table.state.pagination.pageSize} onchange={(event) => table.setPageSize(Number(event.currentTarget.value))}>{#each [10,20,30,40,50] as size}<option value={size}>Show {size}</option>{/each}</select>
  </div>
  <div>Showing {table.getRowModel().rows.length.toLocaleString()} of {table.getRowCount().toLocaleString()} Rows</div><pre>{JSON.stringify(table.state, null, 2)}</pre>
</div>