diff --git a/optuna_dashboard/ts/apiClient.ts b/optuna_dashboard/ts/apiClient.ts index a539c590..d7812a56 100644 --- a/optuna_dashboard/ts/apiClient.ts +++ b/optuna_dashboard/ts/apiClient.ts @@ -40,8 +40,8 @@ interface StudyDetailResponse { name: string datetime_start: string directions: StudyDirection[] - best_trial?: TrialResponse trials: TrialResponse[] + best_trials: TrialResponse[] intersection_search_space: SearchSpace[] union_search_space: SearchSpace[] union_user_attrs: AttributeSpec[] @@ -66,12 +66,16 @@ export const getStudyDetailAPI = ( const trials = res.data.trials.map((trial): Trial => { return convertTrialResponse(trial) }) + const best_trials = res.data.best_trials.map((trial): Trial => { + return convertTrialResponse(trial) + }) return { id: studyId, name: res.data.name, datetime_start: new Date(res.data.datetime_start), directions: res.data.directions, trials: trials, + best_trials: best_trials, union_search_space: res.data.union_search_space, intersection_search_space: res.data.intersection_search_space, union_user_attrs: res.data.union_user_attrs, diff --git a/optuna_dashboard/ts/components/App.tsx b/optuna_dashboard/ts/components/App.tsx index 756518ed..d149615b 100644 --- a/optuna_dashboard/ts/components/App.tsx +++ b/optuna_dashboard/ts/components/App.tsx @@ -2,6 +2,8 @@ import React, { FC, useMemo, useState, useEffect } from "react" import { RecoilRoot } from "recoil" import { BrowserRouter as Router, Switch, Route } from "react-router-dom" import { SnackbarProvider } from "notistack" +import blue from "@mui/material/colors/blue" +import pink from "@mui/material/colors/pink" import { createTheme, useMediaQuery, @@ -25,6 +27,8 @@ export const App: FC = () => { createTheme({ palette: { mode: colorMode, + primary: blue, + secondary: pink, }, }), [colorMode] @@ -53,7 +57,16 @@ export const App: FC = () => { children={ + } + /> + } /> diff --git a/optuna_dashboard/ts/components/Note.tsx b/optuna_dashboard/ts/components/Note.tsx index 51c9a4cf..16422395 100644 --- a/optuna_dashboard/ts/components/Note.tsx +++ b/optuna_dashboard/ts/components/Note.tsx @@ -16,7 +16,8 @@ import { actionCreator } from "../action" export const Note: FC<{ studyId: number latestNote: Note -}> = ({ studyId, latestNote }) => { + minRows: number +}> = ({ studyId, latestNote, minRows }) => { const theme = useTheme() const [saving, setSaving] = useState(false) const [edited, setEdited] = useState(false) @@ -72,7 +73,7 @@ export const Note: FC<{ {studyDetail !== null ? ( - + ) : null} diff --git a/optuna_dashboard/ts/components/StudyDetailBeta.tsx b/optuna_dashboard/ts/components/StudyDetailBeta.tsx index 83afdbd2..35823bfc 100644 --- a/optuna_dashboard/ts/components/StudyDetailBeta.tsx +++ b/optuna_dashboard/ts/components/StudyDetailBeta.tsx @@ -1,47 +1,50 @@ import React, { FC, useEffect } from "react" -import { useRecoilState, useRecoilValue } from "recoil" -import { Link, useParams } from "react-router-dom" -import MuiDrawer from "@mui/material/Drawer" -import IconButton from "@mui/material/IconButton" -import ChevronLeftIcon from "@mui/icons-material/ChevronLeft" -import Divider from "@mui/material/Divider" -import List from "@mui/material/List" -import ListItem from "@mui/material/ListItem" -import ListItemButton from "@mui/material/ListItemButton" -import ListItemIcon from "@mui/material/ListItemIcon" -import ListItemText from "@mui/material/ListItemText" -import ListSubheader from "@mui/material/ListSubheader" +import { useRecoilValue } from "recoil" +import { useParams } from "react-router-dom" import { Card, CardContent, Box, - useTheme, - Switch, - Theme, - CSSObject, - styled, Typography, - Toolbar, + useTheme, + ListItem, } from "@mui/material" +import Grid2 from "@mui/material/Unstable_Grid2" import { GraphHistory } from "./GraphHistory" import { Note } from "./Note" import { actionCreator } from "../action" -import { reloadIntervalState, studyDetailsState } from "../state" +import { + reloadIntervalState, + studyDetailsState, + studySummariesState, +} from "../state" import { TrialTable } from "./TrialTable" import { StudyDetailDrawer } from "./StudyDetailDrawer" +import { GraphParallelCoordinate } from "./GraphParallelCoordinate" +import { Contour } from "./GraphContour" +import { GraphHyperparameterImportances } from "./GraphHyperparameterImportances" +import { GraphSlice } from "./GraphSlice" +import { GraphParetoFront } from "./GraphParetoFront" +import { DataGrid, DataGridColumn } from "./DataGrid" +import List from "@mui/material/List" interface ParamTypes { studyId: string } -type PageId = "top" | "trials" | "note" +type PageId = "history" | "analytics" | "trials" | "note" const useStudyDetailValue = (studyId: number): StudyDetail | null => { const studyDetails = useRecoilValue(studyDetailsState) return studyDetails[studyId] || null } +const useStudySummaryValue = (studyId: number): StudySummary | null => { + const studySummaries = useRecoilValue(studySummariesState) + return studySummaries.find((s) => s.study_id == studyId) || null +} + export const StudyDetailBeta: FC<{ toggleColorMode: () => void page: PageId @@ -52,71 +55,169 @@ export const StudyDetailBeta: FC<{ const studyIdNumber = parseInt(studyId, 10) const studyDetail = useStudyDetailValue(studyIdNumber) const reloadInterval = useRecoilValue(reloadIntervalState) + const studySummary = useStudySummaryValue(studyIdNumber) + const directions = studyDetail?.directions || studySummary?.directions || null + const userAttrs = studySummary?.user_attrs || [] + + const title = + studyDetail !== null || studySummary !== null + ? `${studyDetail?.name || studySummary?.study_name} (id=${studyId})` + : `Study #${studyId}` useEffect(() => { action.updateStudyDetail(studyIdNumber) }, []) useEffect(() => { - if (reloadInterval < 0) { + if (reloadInterval < 0 || page === "trials") { return } const intervalId = setInterval(function () { action.updateStudyDetail(studyIdNumber) }, reloadInterval * 1000) return () => clearInterval(intervalId) - }, [reloadInterval, studyDetail]) + }, [reloadInterval, studyDetail, page]) - const trials: Trial[] = studyDetail !== null ? studyDetail.trials : [] - - const trialListWidth = 240 + const userAttrColumns: DataGridColumn[] = [ + { field: "key", label: "Key", sortable: true }, + { field: "value", label: "Value", sortable: true }, + ] let content = null - if (page === "top") { + if (page === "history") { content = ( - - - - {`Trials (${ - studyDetail?.trials.length || 0 - })`} - {trials.map((trial, i) => { - return ( - - - - - - ) - })} - - - - + - + + + + + {directions !== null && directions.length > 1 ? ( + - + - + ) : null} + + + + + {studyDetail !== null && + studyDetail.best_trials.length === 1 && ( + <> + + Best Trial + + + {studyDetail.best_trials[0].values} + + + {studyDetail.best_trials[0].params.map((param) => ( + + {param.name} {param.value} + + ))} + + + )} + {studyDetail !== null && studyDetail.best_trials.length > 1 && ( + <> + + Best Trials + + + )} + + + + + + + + Study User Attributes + + + columns={userAttrColumns} + rows={userAttrs} + keyField={"key"} + dense={true} + initialRowsPerPage={5} + rowsPerPageOption={[5, 10, { label: "All", value: -1 }]} + /> + + + + + + ) + } else if (page === "analytics") { + content = ( + + + Hyperparameter Importance + + + + + + + + Hyperparameter Relationships + + + + + + + + + + + + + + + + ) } else if (page === "trials") { - content = + content = ( + + + + + + ) } else { content = studyDetail !== null ? ( - + ) : null } @@ -126,6 +227,7 @@ export const StudyDetailBeta: FC<{ studyId={studyIdNumber} page={page} toggleColorMode={toggleColorMode} + title={title} > {content} diff --git a/optuna_dashboard/ts/components/StudyDetailDrawer.tsx b/optuna_dashboard/ts/components/StudyDetailDrawer.tsx index 7c8e67c7..be9e9d6e 100644 --- a/optuna_dashboard/ts/components/StudyDetailDrawer.tsx +++ b/optuna_dashboard/ts/components/StudyDetailDrawer.tsx @@ -27,8 +27,9 @@ import TableViewIcon from "@mui/icons-material/TableView" import RateReviewIcon from "@mui/icons-material/RateReview" import ClearIcon from "@mui/icons-material/Clear" import MenuIcon from "@mui/icons-material/Menu" -import GitHubIcon from '@mui/icons-material/GitHub'; -import OpenInNewIcon from '@mui/icons-material/OpenInNew'; +import GitHubIcon from "@mui/icons-material/GitHub" +import OpenInNewIcon from "@mui/icons-material/OpenInNew" +import QueryStatsIcon from "@mui/icons-material/QueryStats" import { Switch } from "@mui/material" const drawerWidth = 240 @@ -105,11 +106,12 @@ const Drawer = styled(MuiDrawer, { export const StudyDetailDrawer: FC<{ studyId: number toggleColorMode: () => void - page: "top" | "trials" | "note" + page: "history" | "analytics" | "trials" | "note" + title: string children?: React.ReactNode -}> = ({ studyId, toggleColorMode, page, children }) => { +}> = ({ studyId, toggleColorMode, page, title, children }) => { const theme = useTheme() - const [open, setOpen] = React.useState(false) + const [open, setOpen] = React.useState(true) const [reloadInterval, updateReloadInterval] = useRecoilState(reloadIntervalState) @@ -157,8 +159,8 @@ export const StudyDetailDrawer: FC<{ > - - Mini variant drawer + + {title} @@ -174,12 +176,12 @@ export const StudyDetailDrawer: FC<{ - + @@ -187,6 +189,19 @@ export const StudyDetailDrawer: FC<{ + + + + + + + + - + diff --git a/optuna_dashboard/ts/components/StudyList.tsx b/optuna_dashboard/ts/components/StudyList.tsx index d0c9218f..1a57d4da 100644 --- a/optuna_dashboard/ts/components/StudyList.tsx +++ b/optuna_dashboard/ts/components/StudyList.tsx @@ -376,6 +376,7 @@ export const StudyList: FC<{ }, }} /> + {sortBySelect} diff --git a/optuna_dashboard/ts/types/index.d.ts b/optuna_dashboard/ts/types/index.d.ts index 6541a93b..1cb85277 100644 --- a/optuna_dashboard/ts/types/index.d.ts +++ b/optuna_dashboard/ts/types/index.d.ts @@ -96,7 +96,7 @@ declare interface StudyDetail { name: string directions: StudyDirection[] datetime_start: Date - best_trial?: Trial + best_trials: Trial[] trials: Trial[] intersection_search_space: SearchSpace[] union_search_space: SearchSpace[]