Appearance
Olympic medals
The classic data set of olympic medal winners (8.618 rows) — loaded from a json file.
How it works
- Loading data:
rowsisundefineduntil the file is loaded, the grid shows its loading indicator meanwhile. The dates (dd/mm/yyyy) are converted to ISO dates, so the date filter works. - Grouping:
v-model:columnGroupsswitches between grouping by country, sport and country or no grouping. Users can also drag columns into the group area. - Aggregation: the medal columns use the custom column type
medalwithaggFunc: "sum", the athlete column counts rows, the age column shows the average. The total row sums up everything that matches the filters. - Custom column type:
medalextends the built-innumbertype (right alignment, number filter) and adds the aggregation and width. - Set filters: country, sport and year show a list of all values.
- Initial sort:
v-model:sortedColumnssorts by gold medals. Groups are sorted by their aggregated gold medals. - Styling:
cellClasscolors medal counts, zero values are dimmed.
vue
<template>
<div class="demo">
<div class="demo-toolbar">
<input v-model="search" type="search" placeholder="Search athlete, country, sport…" />
<button @click="groups = ['country']">By country</button>
<button @click="groups = ['sport', 'country']">Sport › Country</button>
<button @click="groups = []">Flat</button>
<span>{{ rows?.length.toLocaleString() ?? "…" }} results</span>
</div>
<Datagrid
:columns="columns"
:rows="rows"
:options="options"
v-model:globalFilter="search"
v-model:columnGroups="groups"
v-model:sortedColumns="sort"
/>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref, shallowRef } from "vue";
import { withBase } from "vitepress";
import { Datagrid, type ColumnConfig, type GridOptions } from "@datagrid/vue-ui";
type Winner = {
athlete: string;
age: number;
country: string;
year: number;
date: string;
sport: string;
gold: number;
silver: number;
bronze: number;
total: number;
};
const rows = shallowRef<Array<Winner>>();
const search = ref("");
const groups = ref(["country"]);
const sort = ref([{ column: "gold", asc: false }]);
onMounted(async () => {
const response = await fetch(withBase("/data/olympic-winners.json"));
const data: Array<Winner> = await response.json();
// dates are delivered as dd/mm/yyyy, convert them to ISO dates
rows.value = data.map((row) => ({
...row,
date: row.date.split("/").reverse().join("-"),
}));
});
// medal columns share their configuration with a custom column type
const medal = (field: "gold" | "silver" | "bronze", icon: string): ColumnConfig<Winner> => ({
field,
text: icon,
type: "medal",
cellClass: ({ value }) => (value > 0 ? `medal-${field}` : "medal-none"),
});
const columns: Array<ColumnConfig<Winner>> = [
{ field: "athlete", text: "Athlete", width: 200, pinned: "left", aggFunc: "count" },
{ field: "country", text: "Country", width: 160, groupable: true, filterType: "set" },
{ field: "sport", text: "Sport", width: 150, groupable: true, filterType: "set" },
{ field: "year", text: "Year", type: "number", width: 90, groupable: true, filterType: "set" },
{ field: "date", text: "Date", type: "date", width: 130 },
{ field: "age", text: "Age", type: "number", width: 80, aggFunc: "avg", valueFormatter: ({ value }) => (value == null ? "" : String(Math.round(value))) },
{
id: "medals",
text: "Medals",
children: [medal("gold", "🥇"), medal("silver", "🥈"), medal("bronze", "🥉"), { field: "total", text: "Total", type: "medal" }],
},
];
const options: GridOptions<Winner> = {
columnMenu: true,
totalRow: "bottom",
groupDefaultExpanded: 0,
columnTypes: {
medal: { type: "number", align: "right", headerAlign: "right", filterType: "number", aggFunc: "sum", width: 80 },
},
defaultColumn: { sortable: true, filterable: true, resizeable: true, moveable: true },
};
</script>
<style>
.medal-gold {
color: #b08600;
font-weight: 700;
}
.medal-silver {
color: #6e7781;
font-weight: 700;
}
.medal-bronze {
color: #a0522d;
font-weight: 700;
}
.medal-none {
color: var(--ut-border-color);
}
</style>tsx
import { useEffect, useState } from "react";
import { withBase } from "vitepress";
import { Datagrid, type ColumnConfig, type GridOptions, type SortModel } from "@datagrid/react-ui";
type Winner = {
athlete: string;
age: number;
country: string;
year: number;
date: string;
sport: string;
gold: number;
silver: number;
bronze: number;
total: number;
};
// medal columns share their configuration with a custom column type
const medal = (field: "gold" | "silver" | "bronze", icon: string): ColumnConfig<Winner> => ({
field,
text: icon,
type: "medal",
cellClass: ({ value }) => (value > 0 ? `medal-${field}` : "medal-none"),
});
const columns: Array<ColumnConfig<Winner>> = [
{ field: "athlete", text: "Athlete", width: 200, pinned: "left", aggFunc: "count" },
{ field: "country", text: "Country", width: 160, groupable: true, filterType: "set" },
{ field: "sport", text: "Sport", width: 150, groupable: true, filterType: "set" },
{ field: "year", text: "Year", type: "number", width: 90, groupable: true, filterType: "set" },
{ field: "date", text: "Date", type: "date", width: 130 },
{ field: "age", text: "Age", type: "number", width: 80, aggFunc: "avg", valueFormatter: ({ value }) => (value == null ? "" : String(Math.round(value))) },
{
id: "medals",
text: "Medals",
children: [medal("gold", "🥇"), medal("silver", "🥈"), medal("bronze", "🥉"), { field: "total", text: "Total", type: "medal" }],
},
];
const options: GridOptions<Winner> = {
columnMenu: true,
totalRow: "bottom",
groupDefaultExpanded: 0,
columnTypes: {
medal: { type: "number", align: "right", headerAlign: "right", filterType: "number", aggFunc: "sum", width: 80 },
},
defaultColumn: { sortable: true, filterable: true, resizeable: true, moveable: true },
};
export default function ShowcaseOlympics() {
// undefined shows the loading indicator
const [rows, setRows] = useState<Array<Winner>>();
const [search, setSearch] = useState<unknown>("");
const [groups, setGroups] = useState(["country"]);
const [sort, setSort] = useState<SortModel>([{ column: "gold", asc: false }]);
useEffect(() => {
let cancelled = false;
(async () => {
const response = await fetch(withBase("/data/olympic-winners.json"));
const data: Array<Winner> = await response.json();
// dates are delivered as dd/mm/yyyy, convert them to ISO dates
if (!cancelled) setRows(data.map((row) => ({ ...row, date: row.date.split("/").reverse().join("-") })));
})();
return () => {
cancelled = true;
};
}, []);
return (
<div className="demo">
<div className="demo-toolbar">
<input
type="search"
placeholder="Search athlete, country, sport…"
value={String(search ?? "")}
onChange={(e) => setSearch(e.target.value)}
/>
<button onClick={() => setGroups(["country"])}>By country</button>
<button onClick={() => setGroups(["sport", "country"])}>Sport › Country</button>
<button onClick={() => setGroups([])}>Flat</button>
<span>{rows?.length.toLocaleString() ?? "…"} results</span>
</div>
<Datagrid
columns={columns}
rows={rows}
options={options}
globalFilter={search}
onGlobalFilterChange={setSearch}
columnGroups={groups}
onColumnGroupsChange={setGroups}
sortedColumns={sort}
onSortedColumnsChange={setSort}
/>
<style>{`
.medal-gold {
color: #b08600;
font-weight: 700;
}
.medal-silver {
color: #6e7781;
font-weight: 700;
}
.medal-bronze {
color: #a0522d;
font-weight: 700;
}
.medal-none {
color: var(--ut-border-color);
}
`}</style>
</div>
);
}