Merge pull request #897 from keisuke-umezawa/feature/use-tslib-trialtable

Use tslib DataGrid in optuna-dashboard
This commit is contained in:
c-bata
2024-07-10 12:36:17 +09:00
committed by GitHub
8 changed files with 20 additions and 425 deletions
+3 -28
View File
@@ -19,7 +19,7 @@
"@react-three/drei": "^9.96.4",
"@react-three/fiber": "^8.15.15",
"@tanstack/react-query": "^5.18.1",
"@tanstack/react-table": "^8.16.0",
"@tanstack/react-table": "file:../tslib/react/node_modules/@tanstack/react-table",
"@tanstack/react-virtual": "^3.1.2",
"@types/papaparse": "^5.3.14",
"@types/three": "^0.160.0",
@@ -14278,22 +14278,8 @@
}
},
"node_modules/@tanstack/react-table": {
"version": "8.17.3",
"license": "MIT",
"dependencies": {
"@tanstack/table-core": "8.17.3"
},
"engines": {
"node": ">=12"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
"react": ">=16.8",
"react-dom": ">=16.8"
}
"resolved": "../tslib/react/node_modules/@tanstack/react-table",
"link": true
},
"node_modules/@tanstack/react-virtual": {
"version": "3.5.1",
@@ -14310,17 +14296,6 @@
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/@tanstack/table-core": {
"version": "8.17.3",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@tanstack/virtual-core": {
"version": "3.5.1",
"license": "MIT",
+1 -1
View File
@@ -26,7 +26,7 @@
"@react-three/drei": "^9.96.4",
"@react-three/fiber": "^8.15.15",
"@tanstack/react-query": "^5.18.1",
"@tanstack/react-table": "^8.16.0",
"@tanstack/react-table": "file:../tslib/react/node_modules/@tanstack/react-table",
"@tanstack/react-virtual": "^3.1.2",
"@types/papaparse": "^5.3.14",
"@types/three": "^0.160.0",
@@ -1,10 +1,10 @@
import ClearIcon from "@mui/icons-material/Clear"
import { Box, Modal, useTheme } from "@mui/material"
import IconButton from "@mui/material/IconButton"
import { DataGrid } from "@optuna/react"
import { useSnackbar } from "notistack"
import Papa from "papaparse"
import React, { useState, useEffect, ReactNode } from "react"
import { DataGrid } from "../DataGrid"
import { Artifact } from "ts/types/optuna"
-383
View File
@@ -1,383 +0,0 @@
import CheckBoxIcon from "@mui/icons-material/CheckBox"
import CheckBoxOutlineBlankIcon from "@mui/icons-material/CheckBoxOutlineBlank"
import FilterListIcon from "@mui/icons-material/FilterList"
import FirstPageIcon from "@mui/icons-material/FirstPage"
import KeyboardArrowLeft from "@mui/icons-material/KeyboardArrowLeft"
import KeyboardArrowRight from "@mui/icons-material/KeyboardArrowRight"
import LastPageIcon from "@mui/icons-material/LastPage"
import {
Box,
IconButton,
Menu,
MenuItem,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TablePagination,
TableRow,
TableSortLabel,
TextField,
useTheme,
} from "@mui/material"
import ListItemIcon from "@mui/material/ListItemIcon"
import Paper from "@mui/material/Paper"
import { TablePaginationActionsProps } from "@mui/material/TablePagination/TablePaginationActions"
import { styled } from "@mui/system"
import React from "react"
import {
ColumnDef,
ColumnFiltersState,
Header,
PaginationState,
SortingState,
flexRender,
getCoreRowModel,
getFacetedRowModel,
getFacetedUniqueValues,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table"
const TableHeaderCellSpan = styled("span")({
display: "inline-flex",
})
const HiddenSpan = styled("span")({
border: 0,
clip: "rect(0 0 0 0)",
height: 1,
margin: -1,
overflow: "hidden",
padding: 0,
position: "absolute",
top: 20,
width: 1,
})
function FilterMenu<T>({
header,
filterChoices,
}: {
header: Header<T, unknown>
filterChoices: string[]
}): React.ReactElement {
const [filterMenuAnchorEl, setFilterMenuAnchorEl] =
React.useState<null | HTMLElement>(null)
return (
<>
<IconButton
size="small"
onClick={(e) => {
setFilterMenuAnchorEl(e.currentTarget)
}}
>
<FilterListIcon fontSize="small" />
</IconButton>
<Menu
anchorEl={filterMenuAnchorEl}
open={filterMenuAnchorEl !== null}
onClose={() => {
setFilterMenuAnchorEl(null)
}}
>
{filterChoices.map((choice) => (
<MenuItem
key={choice}
onClick={() => {
const skippedValues = header.column.getFilterValue() as string[]
const isSkipped = skippedValues.includes(choice)
const newSkippedValues = isSkipped
? skippedValues.filter((v) => v !== choice)
: skippedValues.concat(choice)
header.column.setFilterValue(newSkippedValues)
}}
>
<ListItemIcon>
{header.column.getFilterValue() !== undefined ? (
(header.column.getFilterValue() as string[]).includes(
choice
) ? (
<CheckBoxOutlineBlankIcon color="primary" />
) : (
<CheckBoxIcon color="primary" />
)
) : null}
</ListItemIcon>
{choice ?? "(missing value)"}
</MenuItem>
))}
</Menu>
</>
)
}
function DataGrid<T>({
data,
columns,
initialRowsPerPage,
}: {
data: T[]
columns: ColumnDef<T>[]
initialRowsPerPage?: number
}): React.ReactElement {
const [sorting, setSorting] = React.useState<SortingState>([])
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[]
)
const rowsPerPageOptions = [10, 50, 100, { label: "All", value: data.length }]
const [pagination, setPagination] = React.useState<PaginationState>({
pageIndex: 0,
pageSize:
initialRowsPerPage && rowsPerPageOptions.includes(initialRowsPerPage)
? initialRowsPerPage
: 50,
})
const table = useReactTable({
data,
columns,
state: {
columnFilters,
sorting,
pagination,
},
onColumnFiltersChange: setColumnFilters,
onSortingChange: setSorting,
onPaginationChange: setPagination,
getCoreRowModel: getCoreRowModel(),
getFacetedRowModel: getFacetedRowModel(),
getFacetedUniqueValues: getFacetedUniqueValues(),
getSortedRowModel: getSortedRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getFilteredRowModel: getFilteredRowModel(),
autoResetPageIndex: false,
})
return (
<Box component="div" sx={{ width: "100%" }}>
<TableContainer component={Paper}>
<Table sx={{ minWidth: 650 }} aria-label="simple table">
<TableHead>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
if (
header.column.getCanFilter() &&
!header.column.getIsFiltered()
) {
header.column.setFilterValue([])
}
const order = header.column.getIsSorted()
const filterChoices = header.column.getCanFilter()
? Array.from(
header.column.getFacetedUniqueValues().keys()
).sort()
: null
return (
<TableCell key={header.id} colSpan={header.colSpan}>
{header.isPlaceholder ? null : (
<TableHeaderCellSpan>
{header.column.getCanSort() ? (
<TableSortLabel
active={order !== false}
direction={order || "asc"}
onClick={header.column.getToggleSortingHandler()}
>
{flexRender(
header.column.columnDef.header,
header.getContext()
)}
{order !== null ? (
<HiddenSpan>
{order === "desc"
? "sorted descending"
: "sorted ascending"}
</HiddenSpan>
) : null}
</TableSortLabel>
) : (
flexRender(
header.column.columnDef.header,
header.getContext()
)
)}
{filterChoices !== null ? (
<FilterMenu
header={header}
filterChoices={filterChoices}
/>
) : null}
</TableHeaderCellSpan>
)}
</TableCell>
)
})}
</TableRow>
))}
</TableHead>
<TableBody>
{table.getRowModel().rows.map((row) => {
return (
<TableRow key={row.id}>
{row.getVisibleCells().map((cell) => {
return (
<TableCell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</TableCell>
)
})}
</TableRow>
)
})}
</TableBody>
</Table>
</TableContainer>
<Box component="div" display="flex" alignItems="center">
<TablePagination
rowsPerPageOptions={rowsPerPageOptions}
component="div"
count={table.getFilteredRowModel().rows.length}
rowsPerPage={table.getState().pagination.pageSize}
page={table.getState().pagination.pageIndex}
slotProps={{
select: {
inputProps: { "aria-label": "rows per page" },
native: true,
},
}}
onPageChange={(_, page) => {
table.setPageIndex(page)
}}
onRowsPerPageChange={(e) => {
const size = e.target.value ? Number(e.target.value) : 10
table.setPageSize(size)
}}
ActionsComponent={TablePaginationActions}
/>
{table.getPageCount() > 2 ? (
<PaginationForm1
onPageNumberSubmit={(page) => table.setPageIndex(page)}
maxPageNumber={table.getPageCount()}
/>
) : null}
</Box>
</Box>
)
}
const TablePaginationActions = ({
count,
page,
rowsPerPage,
onPageChange,
}: TablePaginationActionsProps) => {
const theme = useTheme()
const handleFirstPageButtonClick = (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
) => {
onPageChange(event, 0)
}
const handleBackButtonClick = (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
) => {
onPageChange(event, page - 1)
}
const handleNextButtonClick = (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
) => {
onPageChange(event, page + 1)
}
const handleLastPageButtonClick = (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
) => {
onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1))
}
return (
<Box component="div" sx={{ flexShrink: 0, ml: 2.5 }}>
<IconButton
onClick={handleFirstPageButtonClick}
disabled={page === 0}
aria-label="first page"
>
{theme.direction === "rtl" ? <LastPageIcon /> : <FirstPageIcon />}
</IconButton>
<IconButton
onClick={handleBackButtonClick}
disabled={page === 0}
aria-label="previous page"
>
{theme.direction === "rtl" ? (
<KeyboardArrowRight />
) : (
<KeyboardArrowLeft />
)}
</IconButton>
<IconButton
onClick={handleNextButtonClick}
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
aria-label="next page"
>
{theme.direction === "rtl" ? (
<KeyboardArrowLeft />
) : (
<KeyboardArrowRight />
)}
</IconButton>
<IconButton
onClick={handleLastPageButtonClick}
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
aria-label="last page"
>
{theme.direction === "rtl" ? <FirstPageIcon /> : <LastPageIcon />}
</IconButton>
</Box>
)
}
const PaginationForm1: React.FC<{
onPageNumberSubmit: (value: number) => void
maxPageNumber: number
}> = ({ onPageNumberSubmit, maxPageNumber }) => {
// This component is separated from DataGrid to prevent `DataGrid` from re-rendering the page,
// every time any letters are input.
const [specifiedPageText, setSpecifiedPageText] = React.useState("")
const handleSubmitPageNumber = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault()
const newPageNumber = parseInt(specifiedPageText, 10)
// Page is 0-indexed in `TablePagination`.
onPageNumberSubmit(newPageNumber - 1)
setSpecifiedPageText("") // reset the input field
}
return (
<form onSubmit={handleSubmitPageNumber}>
<TextField
size="small"
label={`Go to Page: n / ${maxPageNumber}`}
value={specifiedPageText}
type="number"
style={{ width: 200 }}
inputProps={{ min: 1, max: maxPageNumber }}
onChange={(e) => {
setSpecifiedPageText(e.target.value)
}}
/>
</form>
)
}
export { DataGrid }
@@ -7,11 +7,11 @@ import {
useTheme,
} from "@mui/material"
import Grid from "@mui/material/Grid"
import { DataGrid } from "@optuna/react"
import * as Optuna from "@optuna/types"
import React, { FC } from "react"
import { useStudyDetailValue, useStudySummaryValue } from "../../state"
import { BestTrialsCard } from "../BestTrialsCard"
import { DataGrid } from "../DataGrid"
import { Contour } from "../GraphContour"
import { ColumnDef, createColumnHelper } from "@tanstack/react-table"
@@ -9,6 +9,7 @@ import {
} from "@mui/material"
import FormControlLabel from "@mui/material/FormControlLabel"
import Grid from "@mui/material/Grid"
import { DataGrid } from "@optuna/react"
import * as Optuna from "@optuna/types"
import React, { FC, useState } from "react"
import { useRecoilValue } from "recoil"
@@ -21,7 +22,6 @@ import {
import { artifactIsAvailable } from "../state"
import { StudyArtifactCards } from "./Artifact/StudyArtifactCards"
import { BestTrialsCard } from "./BestTrialsCard"
import { DataGrid } from "./DataGrid"
import { GraphHistory } from "./GraphHistory"
import { GraphHyperparameterImportance } from "./GraphHyperparameterImportances"
import { GraphIntermediateValues } from "./GraphIntermediateValues"
@@ -3,9 +3,10 @@ import LinkIcon from "@mui/icons-material/Link"
import { Button, IconButton, useTheme } from "@mui/material"
import React, { FC } from "react"
import { DataGrid } from "@optuna/react"
import { Link } from "react-router-dom"
import { StudyDetail, Trial } from "ts/types/optuna"
import { DataGrid } from "./DataGrid"
import {
ColumnDef,
+11 -9
View File
@@ -1,15 +1,19 @@
import CheckBoxIcon from "@mui/icons-material/CheckBox"
import CheckBoxOutlineBlankIcon from "@mui/icons-material/CheckBoxOutlineBlank"
import FilterListIcon from "@mui/icons-material/FilterList"
import FirstPageIcon from "@mui/icons-material/FirstPage"
import KeyboardArrowLeft from "@mui/icons-material/KeyboardArrowLeft"
import KeyboardArrowRight from "@mui/icons-material/KeyboardArrowRight"
import LastPageIcon from "@mui/icons-material/LastPage"
import {
CheckBox as CheckBoxIcon,
CheckBoxOutlineBlank as CheckBoxOutlineBlankIcon,
FilterList as FilterListIcon,
FirstPage as FirstPageIcon,
KeyboardArrowLeft,
KeyboardArrowRight,
LastPage as LastPageIcon,
} from "@mui/icons-material"
import {
Box,
IconButton,
ListItem as ListItemIcon,
Menu,
MenuItem,
Paper,
Table,
TableBody,
TableCell,
@@ -21,8 +25,6 @@ import {
TextField,
useTheme,
} from "@mui/material"
import ListItemIcon from "@mui/material/ListItemIcon"
import Paper from "@mui/material/Paper"
import { TablePaginationActionsProps } from "@mui/material/TablePagination/TablePaginationActions"
import { styled } from "@mui/system"
import React from "react"