diff --git a/optuna_dashboard/ts/components/DataGrid.tsx b/optuna_dashboard/ts/components/DataGrid.tsx index 3df8b414..7f858ee8 100644 --- a/optuna_dashboard/ts/components/DataGrid.tsx +++ b/optuna_dashboard/ts/components/DataGrid.tsx @@ -18,10 +18,36 @@ import { TableRow, TableSortLabel, TextField, + Collapse, + IconButton, + Menu, + MenuItem, + Box, + useTheme, } from "@mui/material" import ListItemIcon from "@mui/material/ListItemIcon" import { styled } from "@mui/system" import React from "react" +import Paper from "@mui/material/Paper" +import { TablePaginationActionsProps } from "@mui/material/TablePagination/TablePaginationActions" +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 { + ColumnDef, + flexRender, + getCoreRowModel, + getSortedRowModel, + getPaginationRowModel, + getFilteredRowModel, + getFacetedUniqueValues, + getFacetedRowModel, + SortingState, + ColumnFiltersState, + useReactTable, +} from "@tanstack/react-table" type Order = "asc" | "desc" @@ -455,4 +481,268 @@ const isNumber = ( return typeof rowsPerPage === "number" } -export { DataGrid, DataGridColumn } +function DataGrid2(props: { + data: Trial[] + columns: ColumnDef[] +}): React.ReactElement { + const { data, columns } = props + const [sorting, setSorting] = React.useState([]) + const [columnFilters, setColumnFilters] = React.useState( + [] + ) + const [filterMenuAnchorEl, setFilterMenuAnchorEl] = + React.useState(null) + + const table = useReactTable({ + data, + columns, + state: { + columnFilters, + sorting, + }, + onColumnFiltersChange: setColumnFilters, + onSortingChange: setSorting, + getCoreRowModel: getCoreRowModel(), + getFacetedRowModel: getFacetedRowModel(), + getFacetedUniqueValues: getFacetedUniqueValues(), + getSortedRowModel: getSortedRowModel(), + getPaginationRowModel: getPaginationRowModel(), + getFilteredRowModel: getFilteredRowModel(), + // + // debugTable: true, + }) + + const { pageSize, pageIndex } = table.getState().pagination + + React.useEffect(() => { + return () => { + table.resetColumnFilters() + } + }, []) + + return ( + + + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + const order = header.column.getIsSorted() + const filterChoices = header.column.getCanFilter() + ? Array.from( + header.column.getFacetedUniqueValues().keys() + ).sort() + : [] + if ( + header.column.getCanFilter() && + !header.column.getIsFiltered() + ) { + header.column.setFilterValue([]) + console.log(header.column.getFilterValue()) + } + return ( + + {header.isPlaceholder ? null : ( + + {header.column.getCanSort() ? ( + + {flexRender( + header.column.columnDef.header, + header.getContext() + )} + {order !== null ? ( + + {order === "desc" + ? "sorted descending" + : "sorted ascending"} + + ) : null} + + ) : ( + flexRender( + header.column.columnDef.header, + header.getContext() + ) + )} + {header.column.getCanFilter() ? ( + <> + { + setFilterMenuAnchorEl(e.currentTarget) + }} + > + + + { + setFilterMenuAnchorEl(null) + }} + > + {filterChoices.map((choice) => ( + { + 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 + ) + }} + > + + {header.column.getFilterValue() !== + undefined ? ( + ( + header.column.getFilterValue() as string[] + ).includes(choice) ? ( + + ) : ( + + ) + ) : null} + + {choice ?? "(missing value)"} + + ))} + + + ) : null} + + )} + + ) + })} + + ))} + + + {table.getRowModel().rows.map((row) => { + return ( + + {row.getVisibleCells().map((cell) => { + return ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} + + ) + })} + + ) + })} + +
+
+ { + table.setPageIndex(page) + }} + onRowsPerPageChange={(e) => { + const size = e.target.value ? Number(e.target.value) : 10 + table.setPageSize(size) + }} + ActionsComponent={TablePaginationActions} + /> +
+ ) +} + +const TablePaginationActions = (props: TablePaginationActionsProps) => { + const theme = useTheme() + const { count, page, rowsPerPage, onPageChange } = props + + const handleFirstPageButtonClick = ( + event: React.MouseEvent + ) => { + onPageChange(event, 0) + } + + const handleBackButtonClick = ( + event: React.MouseEvent + ) => { + onPageChange(event, page - 1) + } + + const handleNextButtonClick = ( + event: React.MouseEvent + ) => { + onPageChange(event, page + 1) + } + + const handleLastPageButtonClick = ( + event: React.MouseEvent + ) => { + onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1)) + } + + return ( + + + {theme.direction === "rtl" ? : } + + + {theme.direction === "rtl" ? ( + + ) : ( + + )} + + = Math.ceil(count / rowsPerPage) - 1} + aria-label="next page" + > + {theme.direction === "rtl" ? ( + + ) : ( + + )} + + = Math.ceil(count / rowsPerPage) - 1} + aria-label="last page" + > + {theme.direction === "rtl" ? : } + + + ) +} + +export { DataGrid, DataGrid2, DataGridColumn } diff --git a/optuna_dashboard/ts/components/TrialTable.tsx b/optuna_dashboard/ts/components/TrialTable.tsx index 9d575017..7d78e967 100644 --- a/optuna_dashboard/ts/components/TrialTable.tsx +++ b/optuna_dashboard/ts/components/TrialTable.tsx @@ -1,75 +1,24 @@ import React, { FC } from "react" -import { - IconButton, - Button, - useTheme, - TableSortLabel, - Menu, - MenuItem, -} from "@mui/material" +import { IconButton, Button, useTheme } from "@mui/material" import LinkIcon from "@mui/icons-material/Link" import DownloadIcon from "@mui/icons-material/Download" import LinkIcon from "@mui/icons-material/Link" import { Button, IconButton, useTheme } from "@mui/material" import React, { FC } from "react" +import { DataGridColumn, DataGrid, DataGrid2 } from "./DataGrid" import { Link } from "react-router-dom" import { StudyDetail, Trial } from "ts/types/optuna" import { DataGrid, DataGridColumn } from "./DataGrid" -import Box from "@mui/material/Box" -import Table from "@mui/material/Table" -import TableBody from "@mui/material/TableBody" -import TableCell from "@mui/material/TableCell" -import TableContainer from "@mui/material/TableContainer" -import TableHead from "@mui/material/TableHead" -import TableRow from "@mui/material/TableRow" -import TablePagination from "@mui/material/TablePagination" -import Paper from "@mui/material/Paper" -import { TablePaginationActionsProps } from "@mui/material/TablePagination/TablePaginationActions" -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 { styled } from "@mui/system" -import CheckBoxOutlineBlankIcon from "@mui/icons-material/CheckBoxOutlineBlank" -import CheckBoxIcon from "@mui/icons-material/CheckBox" -import FilterListIcon from "@mui/icons-material/FilterList" -import ListItemIcon from "@mui/material/ListItemIcon" - import { ColumnDef, createColumnHelper, - flexRender, - getCoreRowModel, - getSortedRowModel, - getPaginationRowModel, - getFilteredRowModel, - getFacetedUniqueValues, - SortingState, - ColumnFiltersState, - useReactTable, Row, IdType, FilterFn, } 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, -}) - const multiValueFilter: FilterFn = ( row: Row, columnId: IdType, @@ -79,266 +28,6 @@ const multiValueFilter: FilterFn = ( return !filterValue.includes(rowValue) } -const TablePaginationActions = (props: TablePaginationActionsProps) => { - const theme = useTheme() - const { count, page, rowsPerPage, onPageChange } = props - - const handleFirstPageButtonClick = ( - event: React.MouseEvent - ) => { - onPageChange(event, 0) - } - - const handleBackButtonClick = ( - event: React.MouseEvent - ) => { - onPageChange(event, page - 1) - } - - const handleNextButtonClick = ( - event: React.MouseEvent - ) => { - onPageChange(event, page + 1) - } - - const handleLastPageButtonClick = ( - event: React.MouseEvent - ) => { - onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1)) - } - - return ( - - - {theme.direction === "rtl" ? : } - - - {theme.direction === "rtl" ? ( - - ) : ( - - )} - - = Math.ceil(count / rowsPerPage) - 1} - aria-label="next page" - > - {theme.direction === "rtl" ? ( - - ) : ( - - )} - - = Math.ceil(count / rowsPerPage) - 1} - aria-label="last page" - > - {theme.direction === "rtl" ? : } - - - ) -} - -function BasicTable(props: { - data: Trial[] - columns: ColumnDef[] -}): React.ReactElement { - const { data, columns } = props - const [sorting, setSorting] = React.useState([]) - const [columnFilters, setColumnFilters] = React.useState( - [] - ) - const [filterMenuAnchorEl, setFilterMenuAnchorEl] = - React.useState(null) - - const table = useReactTable({ - data, - columns, - state: { - columnFilters, - sorting, - }, - onColumnFiltersChange: setColumnFilters, - onSortingChange: setSorting, - getCoreRowModel: getCoreRowModel(), - getFacetedUniqueValues: getFacetedUniqueValues(), - getSortedRowModel: getSortedRowModel(), - getPaginationRowModel: getPaginationRowModel(), - getFilteredRowModel: getFilteredRowModel(), - // - // debugTable: true, - }) - - const { pageSize, pageIndex } = table.getState().pagination - - React.useEffect(() => { - return () => { - table.resetColumnFilters() - } - }, []) - - return ( - - - - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => { - const order = header.column.getIsSorted() - const filterChoices = header.column.getCanFilter() - ? Array.from( - header.column.getFacetedUniqueValues().keys() - ).sort() - : [] - if ( - header.column.getCanFilter() && - !header.column.getIsFiltered() - ) { - header.column.setFilterValue([]) - console.log(header.column.getFilterValue()) - } - return ( - - {header.isPlaceholder ? null : ( - - {header.column.getCanSort() ? ( - - {flexRender( - header.column.columnDef.header, - header.getContext() - )} - {order !== null ? ( - - {order === "desc" - ? "sorted descending" - : "sorted ascending"} - - ) : null} - - ) : ( - flexRender( - header.column.columnDef.header, - header.getContext() - ) - )} - {header.column.getCanFilter() ? ( - <> - { - setFilterMenuAnchorEl(e.currentTarget) - }} - > - - - { - setFilterMenuAnchorEl(null) - }} - > - {filterChoices.map((choice) => ( - { - 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 - ) - }} - > - - {( - header.column.getFilterValue() as string[] - ).includes(choice) ? ( - - ) : ( - - )} - - {choice ?? "(missing value)"} - - ))} - - - ) : null} - - )} - - ) - })} - - ))} - - - {table.getRowModel().rows.map((row) => { - return ( - - {row.getVisibleCells().map((cell) => { - return ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} - - ) - })} - - ) - })} - -
-
- { - table.setPageIndex(page) - }} - onRowsPerPageChange={(e) => { - const size = e.target.value ? Number(e.target.value) : 10 - table.setPageSize(size) - }} - ActionsComponent={TablePaginationActions} - /> -
- ) -} - export const TrialTable: FC<{ studyDetail: StudyDetail | null initialRowsPerPage?: number @@ -370,8 +59,7 @@ export const TrialTable: FC<{ header: "State", footer: (info) => info.column.id, enableSorting: false, - // enableColumnFilter: true, - enableColumnFilter: false, + enableColumnFilter: true, filterFn: multiValueFilter, }), ] @@ -495,8 +183,7 @@ export const TrialTable: FC<{ header: `Param ${s.name}`, footer: (info) => info.column.id, enableSorting: sortable, - // enableColumnFilter: filterChoices !== undefined, - enableColumnFilter: false, + enableColumnFilter: filterChoices !== undefined, filterFn: multiValueFilter, } ) @@ -600,7 +287,7 @@ export const TrialTable: FC<{ > Download CSV File - + ) }