mirror of
https://github.com/wassname/optuna-dashboard.git
synced 2026-09-10 12:23:22 +08:00
Merge pull request #453 from c-bata/replace-with-experimental-ui
Replace Stable UI with Experimental UI.
This commit is contained in:
@@ -15,8 +15,6 @@ import {
|
||||
import { CompareStudies } from "./CompareStudies"
|
||||
import { StudyDetail } from "./StudyDetail"
|
||||
import { StudyList } from "./StudyList"
|
||||
import { StudyDetailBeta } from "./StudyDetailBeta"
|
||||
import { StudyListBeta } from "./StudyListBeta"
|
||||
|
||||
export const App: FC = () => {
|
||||
const prefersDarkMode = useMediaQuery("(prefers-color-scheme: dark)")
|
||||
@@ -53,19 +51,10 @@ export const App: FC = () => {
|
||||
<SnackbarProvider maxSnack={3}>
|
||||
<Router>
|
||||
<Switch>
|
||||
<Route
|
||||
path={URL_PREFIX + "/studies/:studyId/beta"}
|
||||
children={
|
||||
<StudyDetailBeta
|
||||
toggleColorMode={toggleColorMode}
|
||||
page={"history"}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={URL_PREFIX + "/studies/:studyId/analytics"}
|
||||
children={
|
||||
<StudyDetailBeta
|
||||
<StudyDetail
|
||||
toggleColorMode={toggleColorMode}
|
||||
page={"analytics"}
|
||||
/>
|
||||
@@ -74,7 +63,7 @@ export const App: FC = () => {
|
||||
<Route
|
||||
path={URL_PREFIX + "/studies/:studyId/trials"}
|
||||
children={
|
||||
<StudyDetailBeta
|
||||
<StudyDetail
|
||||
toggleColorMode={toggleColorMode}
|
||||
page={"trialList"}
|
||||
/>
|
||||
@@ -83,7 +72,7 @@ export const App: FC = () => {
|
||||
<Route
|
||||
path={URL_PREFIX + "/studies/:studyId/trials"}
|
||||
children={
|
||||
<StudyDetailBeta
|
||||
<StudyDetail
|
||||
toggleColorMode={toggleColorMode}
|
||||
page={"trialList"}
|
||||
/>
|
||||
@@ -92,7 +81,7 @@ export const App: FC = () => {
|
||||
<Route
|
||||
path={URL_PREFIX + "/studies/:studyId/trialTable"}
|
||||
children={
|
||||
<StudyDetailBeta
|
||||
<StudyDetail
|
||||
toggleColorMode={toggleColorMode}
|
||||
page={"trialTable"}
|
||||
/>
|
||||
@@ -101,7 +90,7 @@ export const App: FC = () => {
|
||||
<Route
|
||||
path={URL_PREFIX + "/studies/:studyId/note"}
|
||||
children={
|
||||
<StudyDetailBeta
|
||||
<StudyDetail
|
||||
toggleColorMode={toggleColorMode}
|
||||
page={"note"}
|
||||
/>
|
||||
@@ -109,7 +98,12 @@ export const App: FC = () => {
|
||||
/>
|
||||
<Route
|
||||
path={URL_PREFIX + "/studies/:studyId"}
|
||||
children={<StudyDetail toggleColorMode={toggleColorMode} />}
|
||||
children={
|
||||
<StudyDetail
|
||||
toggleColorMode={toggleColorMode}
|
||||
page={"history"}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={URL_PREFIX + "/compare-studies"}
|
||||
@@ -117,10 +111,6 @@ export const App: FC = () => {
|
||||
<CompareStudies toggleColorMode={toggleColorMode} />
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={URL_PREFIX + "/beta"}
|
||||
children={<StudyListBeta toggleColorMode={toggleColorMode} />}
|
||||
/>
|
||||
<Route
|
||||
path={URL_PREFIX + "/"}
|
||||
children={<StudyList toggleColorMode={toggleColorMode} />}
|
||||
|
||||
@@ -24,7 +24,6 @@ import Brightness4Icon from "@mui/icons-material/Brightness4"
|
||||
import Brightness7Icon from "@mui/icons-material/Brightness7"
|
||||
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"
|
||||
@@ -185,7 +184,7 @@ export const AppDrawer: FC<{
|
||||
<ListItem key="History" disablePadding sx={styleListItem}>
|
||||
<ListItemButton
|
||||
component={Link}
|
||||
to={`${URL_PREFIX}/studies/${studyId}/beta`}
|
||||
to={`${URL_PREFIX}/studies/${studyId}`}
|
||||
sx={styleListItemButton}
|
||||
selected={page === "history"}
|
||||
>
|
||||
@@ -304,7 +303,7 @@ export const AppDrawer: FC<{
|
||||
<ListItem key="Feedback" disablePadding sx={styleListItem}>
|
||||
<ListItemButton
|
||||
target="_blank"
|
||||
href="https://github.com/optuna/optuna-dashboard/discussions/332"
|
||||
href="https://github.com/optuna/optuna-dashboard/discussions/new/choose"
|
||||
sx={styleListItemButton}
|
||||
>
|
||||
<ListItemIcon sx={styleListItemIcon}>
|
||||
@@ -314,21 +313,6 @@ export const AppDrawer: FC<{
|
||||
<OpenInNewIcon sx={styleSwitch} />
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
<ListItem key="BetaUI" disablePadding sx={styleListItem}>
|
||||
<ListItemButton
|
||||
component={Link}
|
||||
to={URL_PREFIX}
|
||||
sx={styleListItemButton}
|
||||
>
|
||||
<ListItemIcon sx={styleListItemIcon}>
|
||||
<ClearIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary="Switch to stable UI"
|
||||
sx={styleListItemText}
|
||||
/>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
</List>
|
||||
</Drawer>
|
||||
<Box component="main" sx={{ flexGrow: 1 }}>
|
||||
|
||||
@@ -89,7 +89,7 @@ export const CompareStudies: FC<{
|
||||
}, [studies, query])
|
||||
|
||||
const studyListWidth = 200
|
||||
const title = "Compare Studies"
|
||||
const title = "Compare Studies (Experimental)"
|
||||
|
||||
useEffect(() => {
|
||||
action.updateStudySummaries()
|
||||
@@ -99,7 +99,7 @@ export const CompareStudies: FC<{
|
||||
<>
|
||||
<IconButton
|
||||
component={Link}
|
||||
to={URL_PREFIX + "/beta"}
|
||||
to={URL_PREFIX + "/"}
|
||||
sx={{ marginRight: theme.spacing(1) }}
|
||||
color="inherit"
|
||||
title="Return to the top page"
|
||||
@@ -131,7 +131,7 @@ export const CompareStudies: FC<{
|
||||
<List>
|
||||
<ListSubheader sx={{ display: "flex", flexDirection: "row" }}>
|
||||
<Typography sx={{ p: theme.spacing(1, 0) }}>
|
||||
{studies.length} Studies
|
||||
Compare studies with Shift+Click
|
||||
</Typography>
|
||||
<Box sx={{ flexGrow: 1 }} />
|
||||
</ListSubheader>
|
||||
@@ -315,8 +315,8 @@ const StudiesGraph: FC<{ studies: StudySummary[] }> = ({ studies }) => {
|
||||
<CardContent>
|
||||
<GraphHistoryMultiStudies
|
||||
studies={showStudyDetails}
|
||||
betaIncludePruned={includePruned}
|
||||
betaLogScale={logScale}
|
||||
includePruned={includePruned}
|
||||
logScale={logScale}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -27,7 +27,7 @@ interface EdfPlotInfo {
|
||||
trials: Trial[]
|
||||
}
|
||||
|
||||
export const GraphEdfBeta: FC<{
|
||||
export const GraphEdf: FC<{
|
||||
study: StudyDetail | null
|
||||
objectiveId: number
|
||||
}> = ({ study, objectiveId }) => {
|
||||
@@ -37,7 +37,7 @@ export const GraphEdfBeta: FC<{
|
||||
() => new Target("objective", objectiveId),
|
||||
[objectiveId]
|
||||
)
|
||||
const trials = useFilteredTrials(study, [target], false, false)
|
||||
const trials = useFilteredTrials(study, [target], false)
|
||||
|
||||
useEffect(() => {
|
||||
if (study !== null) {
|
||||
@@ -57,60 +57,6 @@ export const GraphEdfBeta: FC<{
|
||||
)
|
||||
}
|
||||
|
||||
export const GraphEdf: FC<{
|
||||
study: StudyDetail | null
|
||||
}> = ({ study = null }) => {
|
||||
const theme = useTheme()
|
||||
const [targets, selected, setTarget] = useObjectiveTargets(study)
|
||||
const trials = useFilteredTrials(study, [selected], false, false)
|
||||
|
||||
const handleObjectiveChange = (event: SelectChangeEvent<string>) => {
|
||||
setTarget(event.target.value)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (study != null) {
|
||||
plotEdf(trials, selected, plotDomId, theme.palette.mode)
|
||||
}
|
||||
}, [trials, selected, theme.palette.mode])
|
||||
return (
|
||||
<Grid container direction="row">
|
||||
<Grid
|
||||
item
|
||||
xs={3}
|
||||
container
|
||||
direction="column"
|
||||
sx={{ paddingRight: theme.spacing(2) }}
|
||||
>
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{ margin: "1em 0", fontWeight: theme.typography.fontWeightBold }}
|
||||
>
|
||||
EDF
|
||||
</Typography>
|
||||
{study !== null && study.directions.length !== 1 ? (
|
||||
<FormControl component="fieldset">
|
||||
<FormLabel component="legend">Objective:</FormLabel>
|
||||
<Select
|
||||
value={selected.identifier()}
|
||||
onChange={handleObjectiveChange}
|
||||
>
|
||||
{targets.map((target, i) => (
|
||||
<MenuItem value={target.identifier()} key={i}>
|
||||
{target.toLabel(study?.objective_names)}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
) : null}
|
||||
</Grid>
|
||||
<Grid item xs={9}>
|
||||
<Box id={plotDomId} sx={{ height: "450px" }} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
export const GraphEdfMultiStudies: FC<{
|
||||
studies: StudyDetail[]
|
||||
}> = ({ studies }) => {
|
||||
@@ -119,7 +65,7 @@ export const GraphEdfMultiStudies: FC<{
|
||||
studies.length !== 0 ? studies[0] : null
|
||||
)
|
||||
|
||||
const trials = useFilteredTrialsFromStudies(studies, [selected], false, false)
|
||||
const trials = useFilteredTrialsFromStudies(studies, [selected], false)
|
||||
const edfPlotInfos = studies.map((study, index) => {
|
||||
const e: EdfPlotInfo = {
|
||||
study_name: study?.name,
|
||||
|
||||
@@ -5,9 +5,7 @@ import {
|
||||
FormControl,
|
||||
FormLabel,
|
||||
FormControlLabel,
|
||||
Checkbox,
|
||||
MenuItem,
|
||||
Switch,
|
||||
Select,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
@@ -34,24 +32,16 @@ interface HistoryPlotInfo {
|
||||
|
||||
export const GraphHistory: FC<{
|
||||
study: StudyDetail | null
|
||||
betaLogScale?: boolean
|
||||
betaIncludePruned?: boolean
|
||||
}> = ({ study, betaLogScale, betaIncludePruned }) => {
|
||||
logScale: boolean
|
||||
includePruned: boolean
|
||||
}> = ({ study, logScale, includePruned }) => {
|
||||
const theme = useTheme()
|
||||
const [xAxis, setXAxis] = useState<
|
||||
"number" | "datetime_start" | "datetime_complete"
|
||||
>("number")
|
||||
const [logScale, setLogScale] = useState<boolean>(false)
|
||||
const [filterCompleteTrial, setFilterCompleteTrial] = useState<boolean>(false)
|
||||
const [filterPrunedTrial, setFilterPrunedTrial] = useState<boolean>(false)
|
||||
|
||||
const [targets, selected, setTarget] = useObjectiveAndUserAttrTargets(study)
|
||||
const trials = useFilteredTrials(
|
||||
study,
|
||||
[selected],
|
||||
filterCompleteTrial,
|
||||
betaIncludePruned === undefined ? filterPrunedTrial : !betaIncludePruned
|
||||
)
|
||||
const trials = useFilteredTrials(study, [selected], includePruned)
|
||||
|
||||
useEffect(() => {
|
||||
if (study !== null) {
|
||||
@@ -60,7 +50,7 @@ export const GraphHistory: FC<{
|
||||
study.directions,
|
||||
selected,
|
||||
xAxis,
|
||||
betaLogScale === undefined ? logScale : betaLogScale,
|
||||
logScale,
|
||||
theme.palette.mode,
|
||||
study?.objective_names
|
||||
)
|
||||
@@ -70,7 +60,6 @@ export const GraphHistory: FC<{
|
||||
study?.directions,
|
||||
selected,
|
||||
logScale,
|
||||
betaLogScale,
|
||||
xAxis,
|
||||
theme.palette.mode,
|
||||
study?.objective_names,
|
||||
@@ -90,18 +79,6 @@ export const GraphHistory: FC<{
|
||||
}
|
||||
}
|
||||
|
||||
const handleLogScaleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setLogScale(!logScale)
|
||||
}
|
||||
|
||||
const handleFilterCompleteChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setFilterCompleteTrial(!filterCompleteTrial)
|
||||
}
|
||||
|
||||
const handleFilterPrunedChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setFilterPrunedTrial(!filterPrunedTrial)
|
||||
}
|
||||
|
||||
return (
|
||||
<Grid container direction="row">
|
||||
<Grid
|
||||
@@ -135,46 +112,6 @@ export const GraphHistory: FC<{
|
||||
</Select>
|
||||
</FormControl>
|
||||
) : null}
|
||||
{betaLogScale === undefined ? (
|
||||
<FormControl
|
||||
component="fieldset"
|
||||
sx={{ marginBottom: theme.spacing(2) }}
|
||||
>
|
||||
<FormLabel component="legend">Log y scale:</FormLabel>
|
||||
<Switch
|
||||
checked={logScale}
|
||||
onChange={handleLogScaleChange}
|
||||
value="enable"
|
||||
/>
|
||||
</FormControl>
|
||||
) : null}
|
||||
{betaIncludePruned === undefined ? (
|
||||
<FormControl
|
||||
component="fieldset"
|
||||
sx={{ marginBottom: theme.spacing(2) }}
|
||||
>
|
||||
<FormLabel component="legend">Filter state:</FormLabel>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={!filterCompleteTrial}
|
||||
onChange={handleFilterCompleteChange}
|
||||
/>
|
||||
}
|
||||
label="Complete"
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={!filterPrunedTrial}
|
||||
disabled={!study?.has_intermediate_values}
|
||||
onChange={handleFilterPrunedChange}
|
||||
/>
|
||||
}
|
||||
label="Pruned"
|
||||
/>
|
||||
</FormControl>
|
||||
) : null}
|
||||
<FormControl
|
||||
component="fieldset"
|
||||
sx={{ marginBottom: theme.spacing(2) }}
|
||||
@@ -213,16 +150,13 @@ export const GraphHistory: FC<{
|
||||
|
||||
export const GraphHistoryMultiStudies: FC<{
|
||||
studies: StudyDetail[]
|
||||
betaLogScale?: boolean
|
||||
betaIncludePruned?: boolean
|
||||
}> = ({ studies, betaLogScale, betaIncludePruned }) => {
|
||||
logScale: boolean
|
||||
includePruned: boolean
|
||||
}> = ({ studies, logScale, includePruned }) => {
|
||||
const theme = useTheme()
|
||||
const [xAxis, setXAxis] = useState<
|
||||
"number" | "datetime_start" | "datetime_complete"
|
||||
>("number")
|
||||
const [logScale, setLogScale] = useState<boolean>(false)
|
||||
const [filterCompleteTrial, setFilterCompleteTrial] = useState<boolean>(false)
|
||||
const [filterPrunedTrial, setFilterPrunedTrial] = useState<boolean>(false)
|
||||
|
||||
// TODO(umezawa): Prepare targets with all studies.
|
||||
const [targets, selected, setTarget] = useObjectiveAndUserAttrTargets(
|
||||
@@ -232,8 +166,7 @@ export const GraphHistoryMultiStudies: FC<{
|
||||
const trials = useFilteredTrialsFromStudies(
|
||||
studies,
|
||||
[selected],
|
||||
filterCompleteTrial,
|
||||
betaIncludePruned === undefined ? filterPrunedTrial : !betaIncludePruned
|
||||
!includePruned
|
||||
)
|
||||
const historyPlotInfos = studies.map((study, index) => {
|
||||
const h: HistoryPlotInfo = {
|
||||
@@ -250,10 +183,10 @@ export const GraphHistoryMultiStudies: FC<{
|
||||
historyPlotInfos,
|
||||
selected,
|
||||
xAxis,
|
||||
betaLogScale === undefined ? logScale : betaLogScale,
|
||||
logScale,
|
||||
theme.palette.mode
|
||||
)
|
||||
}, [studies, selected, logScale, betaLogScale, xAxis, theme.palette.mode])
|
||||
}, [studies, selected, logScale, xAxis, theme.palette.mode])
|
||||
|
||||
const handleObjectiveChange = (event: SelectChangeEvent<string>) => {
|
||||
setTarget(event.target.value)
|
||||
@@ -269,18 +202,6 @@ export const GraphHistoryMultiStudies: FC<{
|
||||
}
|
||||
}
|
||||
|
||||
const handleLogScaleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setLogScale(!logScale)
|
||||
}
|
||||
|
||||
const handleFilterCompleteChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setFilterCompleteTrial(!filterCompleteTrial)
|
||||
}
|
||||
|
||||
const handleFilterPrunedChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setFilterPrunedTrial(!filterPrunedTrial)
|
||||
}
|
||||
|
||||
return (
|
||||
<Grid container direction="row">
|
||||
<Grid
|
||||
@@ -314,46 +235,6 @@ export const GraphHistoryMultiStudies: FC<{
|
||||
</Select>
|
||||
</FormControl>
|
||||
) : null}
|
||||
{betaLogScale === undefined ? (
|
||||
<FormControl
|
||||
component="fieldset"
|
||||
sx={{ marginBottom: theme.spacing(2) }}
|
||||
>
|
||||
<FormLabel component="legend">Log y scale:</FormLabel>
|
||||
<Switch
|
||||
checked={logScale}
|
||||
onChange={handleLogScaleChange}
|
||||
value="enable"
|
||||
/>
|
||||
</FormControl>
|
||||
) : null}
|
||||
{betaIncludePruned === undefined ? (
|
||||
<FormControl
|
||||
component="fieldset"
|
||||
sx={{ marginBottom: theme.spacing(2) }}
|
||||
>
|
||||
<FormLabel component="legend">Filter state:</FormLabel>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={!filterCompleteTrial}
|
||||
onChange={handleFilterCompleteChange}
|
||||
/>
|
||||
}
|
||||
label="Complete"
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={!filterPrunedTrial}
|
||||
disabled={!studies[0]?.has_intermediate_values}
|
||||
onChange={handleFilterPrunedChange}
|
||||
/>
|
||||
}
|
||||
label="Pruned"
|
||||
/>
|
||||
</FormControl>
|
||||
) : null}
|
||||
<FormControl
|
||||
component="fieldset"
|
||||
sx={{ marginBottom: theme.spacing(2) }}
|
||||
|
||||
@@ -1,25 +1,13 @@
|
||||
import * as plotly from "plotly.js-dist-min"
|
||||
import React, { FC, useEffect, useState } from "react"
|
||||
import {
|
||||
Grid,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
MenuItem,
|
||||
Select,
|
||||
Typography,
|
||||
SelectChangeEvent,
|
||||
useTheme,
|
||||
Box,
|
||||
Card,
|
||||
CardContent,
|
||||
} from "@mui/material"
|
||||
import React, { FC, useEffect } from "react"
|
||||
import { Typography, useTheme, Box, Card, CardContent } from "@mui/material"
|
||||
|
||||
import { plotlyDarkTemplate } from "./PlotlyDarkMode"
|
||||
import { actionCreator } from "../action"
|
||||
import { useParamImportanceValue, useStudyDirections } from "../state"
|
||||
const plotDomId = "graph-hyperparameter-importances"
|
||||
|
||||
export const GraphHyperparameterImportanceBeta: FC<{
|
||||
export const GraphHyperparameterImportance: FC<{
|
||||
studyId: number
|
||||
study: StudyDetail | null
|
||||
graphHeight: string
|
||||
@@ -41,7 +29,7 @@ export const GraphHyperparameterImportanceBeta: FC<{
|
||||
|
||||
useEffect(() => {
|
||||
if (importances !== null && nObjectives === importances.length) {
|
||||
plotParamImportancesBeta(importances, objectiveNames, theme.palette.mode)
|
||||
plotParamImportance(importances, objectiveNames, theme.palette.mode)
|
||||
}
|
||||
}, [nObjectives, importances, theme.palette.mode])
|
||||
|
||||
@@ -60,7 +48,7 @@ export const GraphHyperparameterImportanceBeta: FC<{
|
||||
)
|
||||
}
|
||||
|
||||
const plotParamImportancesBeta = (
|
||||
const plotParamImportance = (
|
||||
importances: ParamImportance[][],
|
||||
objectiveNames: string[],
|
||||
mode: string
|
||||
@@ -111,110 +99,3 @@ const plotParamImportancesBeta = (
|
||||
)
|
||||
plotly.react(plotDomId, traces, layout)
|
||||
}
|
||||
|
||||
export const GraphHyperparameterImportances: FC<{
|
||||
study: StudyDetail | null
|
||||
studyId: number
|
||||
}> = ({ study = null, studyId }) => {
|
||||
const theme = useTheme()
|
||||
const action = actionCreator()
|
||||
const importances = useParamImportanceValue(studyId)
|
||||
const [objectiveId, setObjectiveId] = useState<number>(0)
|
||||
const numCompletedTrials =
|
||||
study?.trials.filter((t) => t.state === "Complete").length || 0
|
||||
|
||||
const handleObjectiveChange = (event: SelectChangeEvent<number>) => {
|
||||
setObjectiveId(event.target.value as number)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
action.updateParamImportance(studyId)
|
||||
}, [numCompletedTrials])
|
||||
|
||||
useEffect(() => {
|
||||
if (importances !== null && importances.length > objectiveId) {
|
||||
plotParamImportances(importances[objectiveId], theme.palette.mode)
|
||||
}
|
||||
}, [importances, objectiveId, theme.palette.mode])
|
||||
|
||||
return (
|
||||
<Grid container direction="row">
|
||||
<Grid
|
||||
item
|
||||
xs={3}
|
||||
container
|
||||
direction="column"
|
||||
sx={{ paddingRight: theme.spacing(2) }}
|
||||
>
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{ margin: "1em 0", fontWeight: theme.typography.fontWeightBold }}
|
||||
>
|
||||
Hyperparameter importance
|
||||
</Typography>
|
||||
{study !== null && study.directions.length !== 1 ? (
|
||||
<FormControl component="fieldset">
|
||||
<FormLabel component="legend">Objective ID:</FormLabel>
|
||||
<Select value={objectiveId} onChange={handleObjectiveChange}>
|
||||
{study.directions.map((d, i) => (
|
||||
<MenuItem value={i} key={i}>
|
||||
{i}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
) : null}
|
||||
</Grid>
|
||||
<Grid item xs={9}>
|
||||
<Box id={plotDomId} sx={{ height: "450px" }} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
const plotParamImportances = (importance: ParamImportance[], mode: string) => {
|
||||
if (document.getElementById(plotDomId) === null) {
|
||||
return
|
||||
}
|
||||
const reversed = [...importance].reverse()
|
||||
const importance_values = reversed.map((p) => p.importance)
|
||||
const param_names = reversed.map((p) => p.name)
|
||||
const param_hover_templates = reversed.map(
|
||||
(p) => `${p.name} (${p.distribution}): ${p.importance} <extra></extra>`
|
||||
)
|
||||
|
||||
const layout: Partial<plotly.Layout> = {
|
||||
xaxis: {
|
||||
title: `Importance for the Objective Value`,
|
||||
},
|
||||
yaxis: {
|
||||
title: "Hyperparameter",
|
||||
automargin: true,
|
||||
},
|
||||
margin: {
|
||||
l: 50,
|
||||
t: 0,
|
||||
r: 50,
|
||||
b: 50,
|
||||
},
|
||||
showlegend: false,
|
||||
template: mode === "dark" ? plotlyDarkTemplate : {},
|
||||
}
|
||||
|
||||
const plotData: Partial<plotly.PlotData>[] = [
|
||||
{
|
||||
type: "bar",
|
||||
orientation: "h",
|
||||
x: importance_values,
|
||||
y: param_names,
|
||||
text: importance_values.map((v) => String(v.toFixed(2))),
|
||||
textposition: "outside",
|
||||
hovertemplate: param_hover_templates,
|
||||
marker: {
|
||||
color: "rgb(66,146,198)",
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
plotly.react(plotDomId, plotData, layout)
|
||||
}
|
||||
|
||||
@@ -1,22 +1,11 @@
|
||||
import * as plotly from "plotly.js-dist-min"
|
||||
import React, { ChangeEvent, FC, useEffect, useState } from "react"
|
||||
import {
|
||||
Box,
|
||||
Checkbox,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
FormControlLabel,
|
||||
Grid,
|
||||
Typography,
|
||||
useTheme,
|
||||
CardContent,
|
||||
Card,
|
||||
} from "@mui/material"
|
||||
import React, { FC, useEffect } from "react"
|
||||
import { Box, Typography, useTheme, CardContent, Card } from "@mui/material"
|
||||
import { plotlyDarkTemplate } from "./PlotlyDarkMode"
|
||||
|
||||
const plotDomId = "graph-intermediate-values"
|
||||
|
||||
export const GraphIntermediateValuesBeta: FC<{
|
||||
export const GraphIntermediateValues: FC<{
|
||||
trials: Trial[]
|
||||
includePruned: boolean
|
||||
logScale: boolean
|
||||
@@ -48,78 +37,6 @@ export const GraphIntermediateValuesBeta: FC<{
|
||||
)
|
||||
}
|
||||
|
||||
export const GraphIntermediateValues: FC<{
|
||||
trials: Trial[]
|
||||
}> = ({ trials = [] }) => {
|
||||
const theme = useTheme()
|
||||
const [filterCompleteTrial, setFilterCompleteTrial] = useState<boolean>(false)
|
||||
const [filterPrunedTrial, setFilterPrunedTrial] = useState<boolean>(false)
|
||||
|
||||
useEffect(() => {
|
||||
plotIntermediateValue(
|
||||
trials,
|
||||
theme.palette.mode,
|
||||
filterCompleteTrial,
|
||||
filterPrunedTrial,
|
||||
false
|
||||
)
|
||||
}, [trials, theme.palette.mode, filterCompleteTrial, filterPrunedTrial])
|
||||
|
||||
const handleFilterCompleteChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
e.preventDefault()
|
||||
setFilterCompleteTrial(!filterCompleteTrial)
|
||||
}
|
||||
const handleFilterPrunedChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
e.preventDefault()
|
||||
setFilterPrunedTrial(!filterPrunedTrial)
|
||||
}
|
||||
return (
|
||||
<Grid container direction="row">
|
||||
<Grid
|
||||
item
|
||||
xs={3}
|
||||
container
|
||||
direction="column"
|
||||
sx={{ paddingRight: theme.spacing(2) }}
|
||||
>
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{ margin: "1em 0", fontWeight: theme.typography.fontWeightBold }}
|
||||
>
|
||||
Intermediate values
|
||||
</Typography>
|
||||
<FormControl
|
||||
component="fieldset"
|
||||
sx={{ marginBottom: theme.spacing(2) }}
|
||||
>
|
||||
<FormLabel component="legend">Filter state:</FormLabel>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={!filterCompleteTrial}
|
||||
onChange={handleFilterCompleteChange}
|
||||
/>
|
||||
}
|
||||
label="Complete"
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={!filterPrunedTrial}
|
||||
onChange={handleFilterPrunedChange}
|
||||
/>
|
||||
}
|
||||
label="Pruned"
|
||||
/>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
<Grid item xs={9}>
|
||||
<Box id={plotDomId} sx={{ height: "450px" }} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
const plotIntermediateValue = (
|
||||
trials: Trial[],
|
||||
mode: string,
|
||||
|
||||
@@ -90,7 +90,7 @@ export const GraphParallelCoordinate: FC<{
|
||||
const theme = useTheme()
|
||||
const [targets, searchSpace, renderCheckBoxes] = useTargets(study)
|
||||
|
||||
const trials = useFilteredTrials(study, targets, false, false)
|
||||
const trials = useFilteredTrials(study, targets, false)
|
||||
useEffect(() => {
|
||||
if (study !== null) {
|
||||
plotCoordinate(study, trials, targets, searchSpace, theme.palette.mode)
|
||||
|
||||
@@ -47,7 +47,6 @@ export const GraphSlice: FC<{
|
||||
selectedParamTarget !== null
|
||||
? [selectedObjective, selectedParamTarget]
|
||||
: [selectedObjective],
|
||||
false,
|
||||
false
|
||||
)
|
||||
|
||||
|
||||
@@ -1,172 +0,0 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import MuiDialogTitle from "@mui/material/DialogTitle"
|
||||
import CloseIcon from "@mui/icons-material/Close"
|
||||
import MuiDialogContent from "@mui/material/DialogContent"
|
||||
import FormControlLabel from "@mui/material/FormControlLabel"
|
||||
import {
|
||||
Dialog,
|
||||
Checkbox,
|
||||
Typography,
|
||||
IconButton,
|
||||
FormGroup,
|
||||
useTheme,
|
||||
FormLabel,
|
||||
} from "@mui/material"
|
||||
import { useRecoilValue } from "recoil"
|
||||
import { graphVisibilityState } from "../state"
|
||||
import { actionCreator } from "../action"
|
||||
|
||||
type UsePreferenceDialogReturn = [(open: boolean) => void, () => JSX.Element]
|
||||
|
||||
export const usePreferenceDialog = (
|
||||
studyDetail: StudyDetail | null
|
||||
): UsePreferenceDialogReturn => {
|
||||
const theme = useTheme()
|
||||
const action = actionCreator()
|
||||
const globalGraphVisibility =
|
||||
useRecoilValue<GraphVisibility>(graphVisibilityState)
|
||||
const [localGraphVisibility, setLocalGraphVisibility] =
|
||||
useState<GraphVisibility>(globalGraphVisibility)
|
||||
|
||||
useEffect(() => {
|
||||
action.getGraphVisibility()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
setLocalGraphVisibility(globalGraphVisibility)
|
||||
}, [globalGraphVisibility])
|
||||
|
||||
const [prefOpen, setPrefOpen] = useState(false)
|
||||
const handleClose = () => {
|
||||
setPrefOpen(false)
|
||||
action.saveGraphVisibility(localGraphVisibility)
|
||||
}
|
||||
const handlePreferenceOnChange = (
|
||||
event: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
setLocalGraphVisibility({
|
||||
...localGraphVisibility,
|
||||
[event.target.name]: event.target.checked,
|
||||
})
|
||||
}
|
||||
|
||||
const renderPreferenceDialog = () => {
|
||||
return (
|
||||
<Dialog onClose={handleClose} aria-labelledby="vis-pref" open={prefOpen}>
|
||||
<MuiDialogTitle
|
||||
sx={{
|
||||
margin: 0,
|
||||
padding: theme.spacing(2),
|
||||
minWidth: 300,
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6">Preferences</Typography>
|
||||
<IconButton
|
||||
aria-label="close"
|
||||
sx={{
|
||||
position: "absolute",
|
||||
right: theme.spacing(1),
|
||||
top: theme.spacing(1),
|
||||
color: theme.palette.grey[500],
|
||||
}}
|
||||
onClick={handleClose}
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</MuiDialogTitle>
|
||||
<MuiDialogContent dividers>
|
||||
<FormLabel component="legend">Charts</FormLabel>
|
||||
<FormGroup>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={localGraphVisibility.history}
|
||||
onChange={handlePreferenceOnChange}
|
||||
name="history"
|
||||
/>
|
||||
}
|
||||
label="History"
|
||||
/>
|
||||
<FormControlLabel
|
||||
disabled={studyDetail?.directions?.length === 1}
|
||||
control={
|
||||
<Checkbox
|
||||
checked={localGraphVisibility.paretoFront}
|
||||
onChange={handlePreferenceOnChange}
|
||||
name="paretoFront"
|
||||
/>
|
||||
}
|
||||
label="Pareto Front"
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={localGraphVisibility.parallelCoordinate}
|
||||
onChange={handlePreferenceOnChange}
|
||||
name="parallelCoordinate"
|
||||
/>
|
||||
}
|
||||
label="Parallel Coordinate"
|
||||
/>
|
||||
<FormControlLabel
|
||||
disabled={
|
||||
studyDetail !== null &&
|
||||
(studyDetail.directions.length > 1 ||
|
||||
!studyDetail.has_intermediate_values)
|
||||
}
|
||||
control={
|
||||
<Checkbox
|
||||
checked={localGraphVisibility.intermediateValues}
|
||||
onChange={handlePreferenceOnChange}
|
||||
name="intermediateValues"
|
||||
/>
|
||||
}
|
||||
label="Intermediate Values"
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={localGraphVisibility.edf}
|
||||
onChange={handlePreferenceOnChange}
|
||||
name="edf"
|
||||
/>
|
||||
}
|
||||
label="EDF"
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={localGraphVisibility.contour}
|
||||
onChange={handlePreferenceOnChange}
|
||||
name="contour"
|
||||
/>
|
||||
}
|
||||
label="Contour"
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={localGraphVisibility.importances}
|
||||
onChange={handlePreferenceOnChange}
|
||||
name="importances"
|
||||
/>
|
||||
}
|
||||
label="Hyperparameter Importances"
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={localGraphVisibility.slice}
|
||||
onChange={handlePreferenceOnChange}
|
||||
name="slice"
|
||||
/>
|
||||
}
|
||||
label="Slice"
|
||||
/>
|
||||
</FormGroup>
|
||||
</MuiDialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
return [setPrefOpen, renderPreferenceDialog]
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
import React, { FC } from "react"
|
||||
import { styled } from "@mui/system"
|
||||
import { useRecoilValue } from "recoil"
|
||||
import { reloadIntervalState } from "../state"
|
||||
import { MenuItem, TextField, alpha } from "@mui/material"
|
||||
import { Cached } from "@mui/icons-material"
|
||||
import { actionCreator } from "../action"
|
||||
|
||||
export const ReloadIntervalSelect: FC = () => {
|
||||
const action = actionCreator()
|
||||
const reloadInterval = useRecoilValue<number>(reloadIntervalState)
|
||||
|
||||
const Wrapper = styled("div")(({ theme }) => ({
|
||||
position: "relative",
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
backgroundColor: alpha(theme.palette.common.white, 0.15),
|
||||
"&:hover": {
|
||||
backgroundColor: alpha(theme.palette.common.white, 0.25),
|
||||
},
|
||||
marginLeft: 0,
|
||||
width: "100%",
|
||||
[theme.breakpoints.up("sm")]: {
|
||||
marginLeft: theme.spacing(1),
|
||||
width: "auto",
|
||||
},
|
||||
}))
|
||||
|
||||
const IconWrapper = styled("div")(({ theme }) => ({
|
||||
padding: theme.spacing(0, 2),
|
||||
height: "100%",
|
||||
position: "absolute",
|
||||
pointerEvents: "none",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}))
|
||||
|
||||
const Select = styled(TextField)(({ theme }) => ({
|
||||
color: "inherit",
|
||||
width: "14ch",
|
||||
"& .MuiInput-underline:after": {
|
||||
borderColor: "rgb(256,256,256,.1)",
|
||||
},
|
||||
"& .MuiOutlinedInput-root": {
|
||||
color: "inherit",
|
||||
"& fieldset": {
|
||||
borderColor: "rgb(256,256,256,.1)",
|
||||
},
|
||||
"& .MuiSelect-icon": {
|
||||
color: "white",
|
||||
},
|
||||
"&:hover fieldset": {
|
||||
borderColor: "rgb(256,256,256,.1)",
|
||||
},
|
||||
"&.Mui-focused fieldset": {
|
||||
borderColor: "rgb(256,256,256,.1)",
|
||||
},
|
||||
},
|
||||
"& .MuiInputBase-input": {
|
||||
// vertical padding + font size from searchIcon
|
||||
paddingLeft: `calc(1em + ${theme.spacing(4)})`,
|
||||
width: "100%",
|
||||
},
|
||||
}))
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<IconWrapper>
|
||||
<Cached />
|
||||
</IconWrapper>
|
||||
<Select
|
||||
select
|
||||
value={reloadInterval}
|
||||
onChange={(e) => {
|
||||
action.saveReloadInterval(e.target.value as unknown as number)
|
||||
}}
|
||||
>
|
||||
<MenuItem value={-1}>stop</MenuItem>
|
||||
<MenuItem value={5}>5s</MenuItem>
|
||||
<MenuItem value={10}>10s</MenuItem>
|
||||
<MenuItem value={30}>30s</MenuItem>
|
||||
<MenuItem value={60}>60s</MenuItem>
|
||||
</Select>
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
@@ -1,249 +1,197 @@
|
||||
import React, { FC, useEffect } from "react"
|
||||
import React, { FC, useEffect, useMemo } from "react"
|
||||
import { useRecoilValue } from "recoil"
|
||||
import { Link, useParams } from "react-router-dom"
|
||||
import {
|
||||
AppBar,
|
||||
Card,
|
||||
Typography,
|
||||
CardContent,
|
||||
Container,
|
||||
Toolbar,
|
||||
Box,
|
||||
IconButton,
|
||||
Card,
|
||||
CardContent,
|
||||
Typography,
|
||||
useTheme,
|
||||
IconButton,
|
||||
} from "@mui/material"
|
||||
import { Home, Settings } from "@mui/icons-material"
|
||||
import Brightness4Icon from "@mui/icons-material/Brightness4"
|
||||
import Brightness7Icon from "@mui/icons-material/Brightness7"
|
||||
import Grid2 from "@mui/material/Unstable_Grid2"
|
||||
import ChevronRightIcon from "@mui/icons-material/ChevronRight"
|
||||
import HomeIcon from "@mui/icons-material/Home"
|
||||
|
||||
import { GraphParallelCoordinate } from "./GraphParallelCoordinate"
|
||||
import { GraphHyperparameterImportances } from "./GraphHyperparameterImportances"
|
||||
import { GraphEdf } from "./GraphEdf"
|
||||
import { Contour } from "./GraphContour"
|
||||
import { GraphIntermediateValues } from "./GraphIntermediateValues"
|
||||
import { GraphSlice } from "./GraphSlice"
|
||||
import { GraphHistory } from "./GraphHistory"
|
||||
import { GraphParetoFront } from "./GraphParetoFront"
|
||||
import { StudyNote } from "./Note"
|
||||
import { actionCreator } from "../action"
|
||||
import {
|
||||
graphVisibilityState,
|
||||
reloadIntervalState,
|
||||
studyDetailsState,
|
||||
studySummariesState,
|
||||
useStudyDetailValue,
|
||||
useStudyName,
|
||||
} from "../state"
|
||||
import { usePreferenceDialog } from "./PreferenceDialog"
|
||||
import { ReloadIntervalSelect } from "./ReloadIntervalSelect"
|
||||
import { TrialTable } from "./TrialTable"
|
||||
import { AppDrawer, PageId } from "./AppDrawer"
|
||||
import { GraphParallelCoordinate } from "./GraphParallelCoordinate"
|
||||
import { Contour } from "./GraphContour"
|
||||
import { GraphSlice } from "./GraphSlice"
|
||||
import { GraphEdf } from "./GraphEdf"
|
||||
import { TrialList } from "./TrialList"
|
||||
import { StudyHistory } from "./StudyHistory"
|
||||
|
||||
interface ParamTypes {
|
||||
studyId: string
|
||||
}
|
||||
|
||||
const useStudyDetailValue = (studyId: number): StudyDetail | null => {
|
||||
const studyDetails = useRecoilValue<StudyDetails>(studyDetailsState)
|
||||
return studyDetails[studyId] || null
|
||||
}
|
||||
export const useURLVars = (): number => {
|
||||
const { studyId } = useParams<ParamTypes>()
|
||||
|
||||
const useStudySummaryValue = (studyId: number): StudySummary | null => {
|
||||
const studySummaries = useRecoilValue<StudySummary[]>(studySummariesState)
|
||||
return studySummaries.find((s) => s.study_id == studyId) || null
|
||||
return useMemo(() => parseInt(studyId, 10), [studyId])
|
||||
}
|
||||
|
||||
export const StudyDetail: FC<{
|
||||
toggleColorMode: () => void
|
||||
}> = ({ toggleColorMode }) => {
|
||||
page: PageId
|
||||
}> = ({ toggleColorMode, page }) => {
|
||||
const theme = useTheme()
|
||||
const action = actionCreator()
|
||||
const { studyId } = useParams<ParamTypes>()
|
||||
const studyIdNumber = parseInt(studyId, 10)
|
||||
const studyDetail = useStudyDetailValue(studyIdNumber)
|
||||
const studySummary = useStudySummaryValue(studyIdNumber)
|
||||
const directions = studyDetail?.directions || studySummary?.directions || null
|
||||
const graphVisibility = useRecoilValue<GraphVisibility>(graphVisibilityState)
|
||||
const studyId = useURLVars()
|
||||
const studyDetail = useStudyDetailValue(studyId)
|
||||
const reloadInterval = useRecoilValue<number>(reloadIntervalState)
|
||||
const [openPreferenceDialog, renderPreferenceDialog] =
|
||||
usePreferenceDialog(studyDetail)
|
||||
const studyName = useStudyName(studyId)
|
||||
|
||||
const title =
|
||||
studyName !== null ? `${studyName} (id=${studyId})` : `Study #${studyId}`
|
||||
|
||||
useEffect(() => {
|
||||
action.loadReloadInterval()
|
||||
action.updateStudyDetail(studyIdNumber)
|
||||
action.updateStudyDetail(studyId)
|
||||
action.updateAPIMeta()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (reloadInterval < 0) {
|
||||
return
|
||||
}
|
||||
const nTrials = studyDetail ? studyDetail.trials.length : 0
|
||||
let interval = reloadInterval * 1000
|
||||
|
||||
// For Human-in-the-loop Optimization, the interval is set to 2 seconds
|
||||
// when the number of trials is small and the page is "trialList".
|
||||
if (page === "trialList" && nTrials < 100) {
|
||||
interval = 2000
|
||||
} else if (page === "trialList" && nTrials < 500) {
|
||||
interval = 5000
|
||||
}
|
||||
|
||||
const intervalId = setInterval(function () {
|
||||
action.updateStudyDetail(studyIdNumber)
|
||||
}, reloadInterval * 1000)
|
||||
action.updateStudyDetail(studyId)
|
||||
}, interval)
|
||||
return () => clearInterval(intervalId)
|
||||
}, [reloadInterval, studyDetail])
|
||||
}, [reloadInterval, studyDetail, page])
|
||||
|
||||
// TODO(chenghuzi): Reduce the number of calls to setInterval and clearInterval.
|
||||
const title = studyDetail !== null ? studyDetail.name : `Study #${studyId}`
|
||||
const trials: Trial[] = studyDetail !== null ? studyDetail.trials : []
|
||||
|
||||
return (
|
||||
<div>
|
||||
{renderPreferenceDialog()}
|
||||
<AppBar position="static">
|
||||
<Container
|
||||
sx={{
|
||||
["@media (min-width: 1280px)"]: {
|
||||
maxWidth: "100%",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Toolbar>
|
||||
<Typography variant="h6">{APP_BAR_TITLE}</Typography>
|
||||
<Box sx={{ flexGrow: 1 }} />
|
||||
<ReloadIntervalSelect />
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
toggleColorMode()
|
||||
}}
|
||||
color="inherit"
|
||||
title={
|
||||
theme.palette.mode === "dark"
|
||||
? "Switch to light mode"
|
||||
: "Switch to dark mode"
|
||||
}
|
||||
>
|
||||
{theme.palette.mode === "dark" ? (
|
||||
<Brightness7Icon />
|
||||
) : (
|
||||
<Brightness4Icon />
|
||||
)}
|
||||
</IconButton>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
onClick={() => {
|
||||
openPreferenceDialog(true)
|
||||
}}
|
||||
title="Open preference panel"
|
||||
>
|
||||
<Settings />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
aria-controls="menu-appbar"
|
||||
aria-haspopup="true"
|
||||
component={Link}
|
||||
to={URL_PREFIX + "/"}
|
||||
color="inherit"
|
||||
title="Go to the top"
|
||||
>
|
||||
<Home />
|
||||
</IconButton>
|
||||
</Toolbar>
|
||||
</Container>
|
||||
</AppBar>
|
||||
<Container
|
||||
let content = null
|
||||
if (page === "history") {
|
||||
content = <StudyHistory studyId={studyId} />
|
||||
} else if (page === "analytics") {
|
||||
content = (
|
||||
<Box sx={{ display: "flex", width: "100%", flexDirection: "column" }}>
|
||||
<Typography variant="h5" sx={{ margin: theme.spacing(2) }}>
|
||||
Hyperparameter Relationships
|
||||
</Typography>
|
||||
<Card sx={{ margin: theme.spacing(2) }}>
|
||||
<CardContent>
|
||||
<GraphSlice study={studyDetail} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card sx={{ margin: theme.spacing(2) }}>
|
||||
<CardContent>
|
||||
<GraphParallelCoordinate study={studyDetail} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card sx={{ margin: theme.spacing(2) }}>
|
||||
<CardContent>
|
||||
<Contour study={studyDetail} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Typography variant="h5" sx={{ margin: theme.spacing(2) }}>
|
||||
Empirical Distribution of the Objective Value
|
||||
</Typography>
|
||||
<Grid2 container spacing={2} sx={{ padding: theme.spacing(0, 2) }}>
|
||||
{studyDetail !== null
|
||||
? studyDetail.directions.map((d, i) => (
|
||||
<Grid2 xs={6} key={i}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<GraphEdf study={studyDetail} objectiveId={i} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid2>
|
||||
))
|
||||
: null}
|
||||
</Grid2>
|
||||
</Box>
|
||||
)
|
||||
} else if (page === "trialTable") {
|
||||
content = (
|
||||
<Card sx={{ margin: theme.spacing(2) }}>
|
||||
<CardContent>
|
||||
<TrialTable studyDetail={studyDetail} initialRowsPerPage={50} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
} else if (page === "trialList") {
|
||||
content = <TrialList studyDetail={studyDetail} />
|
||||
} else if (page === "note" && studyDetail !== null) {
|
||||
content = (
|
||||
<Box
|
||||
sx={{
|
||||
["@media (min-width: 1280px)"]: {
|
||||
maxWidth: "100%",
|
||||
},
|
||||
height: `calc(100vh - ${theme.spacing(8)})`,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
padding: theme.spacing(2),
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{
|
||||
margin: `${theme.spacing(4)} ${theme.spacing(2)}`,
|
||||
fontWeight: theme.typography.fontWeightBold,
|
||||
fontSize: "1.8rem",
|
||||
...(theme.palette.mode === "dark" && {
|
||||
color: theme.palette.primary.light,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</Typography>
|
||||
{graphVisibility.history ? (
|
||||
<Card
|
||||
sx={{
|
||||
margin: theme.spacing(2),
|
||||
}}
|
||||
>
|
||||
<CardContent>
|
||||
<GraphHistory study={studyDetail} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : null}
|
||||
<Typography
|
||||
variant="h5"
|
||||
sx={{
|
||||
fontWeight: theme.typography.fontWeightBold,
|
||||
margin: theme.spacing(2, 0),
|
||||
}}
|
||||
>
|
||||
Note
|
||||
</Typography>
|
||||
<StudyNote
|
||||
studyId={studyId}
|
||||
latestNote={studyDetail.note}
|
||||
cardSx={{ flexGrow: 1 }}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
{directions !== null &&
|
||||
directions.length > 1 &&
|
||||
graphVisibility.paretoFront ? (
|
||||
<Card sx={{ margin: theme.spacing(2) }}>
|
||||
<CardContent>
|
||||
<GraphParetoFront study={studyDetail} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : null}
|
||||
{graphVisibility.parallelCoordinate ? (
|
||||
<Card sx={{ margin: theme.spacing(2) }}>
|
||||
<CardContent>
|
||||
<GraphParallelCoordinate study={studyDetail} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : null}
|
||||
const toolbar = (
|
||||
<>
|
||||
<IconButton
|
||||
component={Link}
|
||||
to={URL_PREFIX + "/"}
|
||||
sx={{ marginRight: theme.spacing(1) }}
|
||||
color="inherit"
|
||||
title="Return to the top page"
|
||||
>
|
||||
<HomeIcon />
|
||||
</IconButton>
|
||||
<ChevronRightIcon sx={{ marginRight: theme.spacing(1) }} />
|
||||
<Typography
|
||||
noWrap
|
||||
component="div"
|
||||
sx={{ fontWeight: theme.typography.fontWeightBold }}
|
||||
>
|
||||
{title}
|
||||
</Typography>
|
||||
</>
|
||||
)
|
||||
|
||||
{studyDetail !== null &&
|
||||
studyDetail.directions.length == 1 &&
|
||||
studyDetail.has_intermediate_values &&
|
||||
graphVisibility.intermediateValues ? (
|
||||
<Card sx={{ margin: theme.spacing(2) }}>
|
||||
<CardContent>
|
||||
<GraphIntermediateValues trials={trials} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : null}
|
||||
{graphVisibility.edf ? (
|
||||
<Card sx={{ margin: theme.spacing(2) }}>
|
||||
<CardContent>
|
||||
<GraphEdf study={studyDetail} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : null}
|
||||
|
||||
{graphVisibility.contour ? (
|
||||
<Card sx={{ margin: theme.spacing(2) }}>
|
||||
<CardContent>
|
||||
<Contour study={studyDetail} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : null}
|
||||
|
||||
{graphVisibility.importances ? (
|
||||
<Card sx={{ margin: theme.spacing(2) }}>
|
||||
<CardContent>
|
||||
<GraphHyperparameterImportances
|
||||
study={studyDetail}
|
||||
studyId={studyIdNumber}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : null}
|
||||
|
||||
{studyDetail !== null && graphVisibility.slice ? (
|
||||
<Card sx={{ margin: theme.spacing(2) }}>
|
||||
<CardContent>
|
||||
<GraphSlice study={studyDetail} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : null}
|
||||
<Card sx={{ margin: theme.spacing(2) }}>
|
||||
<TrialTable studyDetail={studyDetail} isBeta={false} />
|
||||
</Card>
|
||||
{studyDetail !== null ? (
|
||||
<StudyNote
|
||||
studyId={studyIdNumber}
|
||||
latestNote={studyDetail.note}
|
||||
cardSx={{ margin: theme.spacing(2) }}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
return (
|
||||
<Box sx={{ display: "flex" }}>
|
||||
<AppDrawer
|
||||
studyId={studyId}
|
||||
page={page}
|
||||
toggleColorMode={toggleColorMode}
|
||||
toolbar={toolbar}
|
||||
>
|
||||
{content}
|
||||
</AppDrawer>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,201 +0,0 @@
|
||||
import React, { FC, useEffect, useMemo } from "react"
|
||||
import { useRecoilValue } from "recoil"
|
||||
import { Link, useParams } from "react-router-dom"
|
||||
import {
|
||||
Box,
|
||||
Card,
|
||||
CardContent,
|
||||
Typography,
|
||||
useTheme,
|
||||
IconButton,
|
||||
} from "@mui/material"
|
||||
import Grid2 from "@mui/material/Unstable_Grid2"
|
||||
import ChevronRightIcon from "@mui/icons-material/ChevronRight"
|
||||
import HomeIcon from "@mui/icons-material/Home"
|
||||
|
||||
import { StudyNote } from "./Note"
|
||||
import { actionCreator } from "../action"
|
||||
import {
|
||||
reloadIntervalState,
|
||||
useStudyDetailValue,
|
||||
useStudyName,
|
||||
} from "../state"
|
||||
import { TrialTable } from "./TrialTable"
|
||||
import { AppDrawer, PageId } from "./AppDrawer"
|
||||
import { GraphParallelCoordinate } from "./GraphParallelCoordinate"
|
||||
import { Contour } from "./GraphContour"
|
||||
import { GraphSlice } from "./GraphSlice"
|
||||
import { GraphEdfBeta } from "./GraphEdf"
|
||||
import { TrialList } from "./TrialList"
|
||||
import { StudyHistory } from "./StudyHistory"
|
||||
|
||||
interface ParamTypes {
|
||||
studyId: string
|
||||
}
|
||||
|
||||
export const useURLVars = (): number => {
|
||||
const { studyId } = useParams<ParamTypes>()
|
||||
|
||||
return useMemo(() => parseInt(studyId, 10), [studyId])
|
||||
}
|
||||
|
||||
export const StudyDetailBeta: FC<{
|
||||
toggleColorMode: () => void
|
||||
page: PageId
|
||||
}> = ({ toggleColorMode, page }) => {
|
||||
const theme = useTheme()
|
||||
const action = actionCreator()
|
||||
const studyId = useURLVars()
|
||||
const studyDetail = useStudyDetailValue(studyId)
|
||||
const reloadInterval = useRecoilValue<number>(reloadIntervalState)
|
||||
const studyName = useStudyName(studyId)
|
||||
|
||||
const title =
|
||||
studyName !== null ? `${studyName} (id=${studyId})` : `Study #${studyId}`
|
||||
|
||||
useEffect(() => {
|
||||
action.loadReloadInterval()
|
||||
action.updateStudyDetail(studyId)
|
||||
action.updateAPIMeta()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (reloadInterval < 0) {
|
||||
return
|
||||
}
|
||||
const nTrials = studyDetail ? studyDetail.trials.length : 0
|
||||
let interval = reloadInterval * 1000
|
||||
|
||||
// For Human-in-the-loop Optimization, the interval is set to 2 seconds
|
||||
// when the number of trials is small and the page is "trialList".
|
||||
if (page === "trialList" && nTrials < 100) {
|
||||
interval = 2000
|
||||
} else if (page === "trialList" && nTrials < 500) {
|
||||
interval = 5000
|
||||
}
|
||||
|
||||
const intervalId = setInterval(function () {
|
||||
action.updateStudyDetail(studyId)
|
||||
}, interval)
|
||||
return () => clearInterval(intervalId)
|
||||
}, [reloadInterval, studyDetail, page])
|
||||
|
||||
let content = null
|
||||
if (page === "history") {
|
||||
content = <StudyHistory studyId={studyId} />
|
||||
} else if (page === "analytics") {
|
||||
content = (
|
||||
<Box sx={{ display: "flex", width: "100%", flexDirection: "column" }}>
|
||||
<Typography variant="h5" sx={{ margin: theme.spacing(2) }}>
|
||||
Hyperparameter Relationships
|
||||
</Typography>
|
||||
<Card sx={{ margin: theme.spacing(2) }}>
|
||||
<CardContent>
|
||||
<GraphSlice study={studyDetail} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card sx={{ margin: theme.spacing(2) }}>
|
||||
<CardContent>
|
||||
<GraphParallelCoordinate study={studyDetail} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card sx={{ margin: theme.spacing(2) }}>
|
||||
<CardContent>
|
||||
<Contour study={studyDetail} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Typography variant="h5" sx={{ margin: theme.spacing(2) }}>
|
||||
Empirical Distribution of the Objective Value
|
||||
</Typography>
|
||||
<Grid2 container spacing={2} sx={{ padding: theme.spacing(0, 2) }}>
|
||||
{studyDetail !== null
|
||||
? studyDetail.directions.map((d, i) => (
|
||||
<Grid2 xs={6} key={i}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<GraphEdfBeta study={studyDetail} objectiveId={i} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid2>
|
||||
))
|
||||
: null}
|
||||
</Grid2>
|
||||
</Box>
|
||||
)
|
||||
} else if (page === "trialTable") {
|
||||
content = (
|
||||
<Card sx={{ margin: theme.spacing(2) }}>
|
||||
<CardContent>
|
||||
<TrialTable
|
||||
studyDetail={studyDetail}
|
||||
isBeta={true}
|
||||
initialRowsPerPage={50}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
} else if (page === "trialList") {
|
||||
content = <TrialList studyDetail={studyDetail} />
|
||||
} else if (page === "note" && studyDetail !== null) {
|
||||
content = (
|
||||
<Box
|
||||
sx={{
|
||||
height: `calc(100vh - ${theme.spacing(8)})`,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
padding: theme.spacing(2),
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="h5"
|
||||
sx={{
|
||||
fontWeight: theme.typography.fontWeightBold,
|
||||
margin: theme.spacing(2, 0),
|
||||
}}
|
||||
>
|
||||
Note
|
||||
</Typography>
|
||||
<StudyNote
|
||||
studyId={studyId}
|
||||
latestNote={studyDetail.note}
|
||||
cardSx={{ flexGrow: 1 }}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
const toolbar = (
|
||||
<>
|
||||
<IconButton
|
||||
component={Link}
|
||||
to={URL_PREFIX + "/beta"}
|
||||
sx={{ marginRight: theme.spacing(1) }}
|
||||
color="inherit"
|
||||
title="Return to the top page"
|
||||
>
|
||||
<HomeIcon />
|
||||
</IconButton>
|
||||
<ChevronRightIcon sx={{ marginRight: theme.spacing(1) }} />
|
||||
<Typography
|
||||
noWrap
|
||||
component="div"
|
||||
sx={{ fontWeight: theme.typography.fontWeightBold }}
|
||||
>
|
||||
{title}
|
||||
</Typography>
|
||||
</>
|
||||
)
|
||||
|
||||
return (
|
||||
<Box sx={{ display: "flex" }}>
|
||||
<AppDrawer
|
||||
studyId={studyId}
|
||||
page={page}
|
||||
toggleColorMode={toggleColorMode}
|
||||
toolbar={toolbar}
|
||||
>
|
||||
{content}
|
||||
</AppDrawer>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
@@ -10,10 +10,10 @@ import {
|
||||
} from "@mui/material"
|
||||
import { GraphParetoFront } from "./GraphParetoFront"
|
||||
import { GraphHistory } from "./GraphHistory"
|
||||
import { GraphIntermediateValuesBeta } from "./GraphIntermediateValues"
|
||||
import { GraphIntermediateValues } from "./GraphIntermediateValues"
|
||||
import Grid2 from "@mui/material/Unstable_Grid2"
|
||||
import { DataGrid, DataGridColumn } from "./DataGrid"
|
||||
import { GraphHyperparameterImportanceBeta } from "./GraphHyperparameterImportances"
|
||||
import { GraphHyperparameterImportance } from "./GraphHyperparameterImportances"
|
||||
import { BestTrialsCard } from "./BestTrialsCard"
|
||||
import {
|
||||
useStudyDetailValue,
|
||||
@@ -96,8 +96,8 @@ export const StudyHistory: FC<{ studyId: number }> = ({ studyId }) => {
|
||||
<CardContent>
|
||||
<GraphHistory
|
||||
study={studyDetail}
|
||||
betaIncludePruned={includePruned}
|
||||
betaLogScale={logScale}
|
||||
includePruned={includePruned}
|
||||
logScale={logScale}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -106,7 +106,7 @@ export const StudyHistory: FC<{ studyId: number }> = ({ studyId }) => {
|
||||
studyDetail.directions.length == 1 &&
|
||||
studyDetail.has_intermediate_values ? (
|
||||
<Grid2 xs={6}>
|
||||
<GraphIntermediateValuesBeta
|
||||
<GraphIntermediateValues
|
||||
trials={trials}
|
||||
includePruned={includePruned}
|
||||
logScale={logScale}
|
||||
@@ -114,7 +114,7 @@ export const StudyHistory: FC<{ studyId: number }> = ({ studyId }) => {
|
||||
</Grid2>
|
||||
) : null}
|
||||
<Grid2 xs={6}>
|
||||
<GraphHyperparameterImportanceBeta
|
||||
<GraphHyperparameterImportance
|
||||
studyId={studyId}
|
||||
study={studyDetail}
|
||||
graphHeight="450px"
|
||||
|
||||
@@ -1,35 +1,43 @@
|
||||
import React, { FC, useEffect, useMemo } from "react"
|
||||
import React, { FC, useEffect, useState } from "react"
|
||||
import { useRecoilValue } from "recoil"
|
||||
import { Link } from "react-router-dom"
|
||||
import {
|
||||
AppBar,
|
||||
Toolbar,
|
||||
Typography,
|
||||
Container,
|
||||
Card,
|
||||
Grid,
|
||||
CardActionArea,
|
||||
Box,
|
||||
Button,
|
||||
IconButton,
|
||||
MenuItem,
|
||||
useTheme,
|
||||
InputAdornment,
|
||||
SvgIcon,
|
||||
CardContent,
|
||||
TextField,
|
||||
CardActions,
|
||||
} from "@mui/material"
|
||||
import { AddBox, Delete, Refresh, Search } from "@mui/icons-material"
|
||||
import { Delete, Refresh, Search } from "@mui/icons-material"
|
||||
import SortIcon from "@mui/icons-material/Sort"
|
||||
import HomeIcon from "@mui/icons-material/Home"
|
||||
import AddBoxIcon from "@mui/icons-material/AddBox"
|
||||
import CompareIcon from "@mui/icons-material/Compare"
|
||||
import DriveFileRenameOutlineIcon from "@mui/icons-material/DriveFileRenameOutline"
|
||||
|
||||
import { actionCreator } from "../action"
|
||||
import { DataGrid, DataGridColumn } from "./DataGrid"
|
||||
import { DebouncedInputTextField } from "./Debounce"
|
||||
import { studySummariesState } from "../state"
|
||||
import Brightness7Icon from "@mui/icons-material/Brightness7"
|
||||
import Brightness4Icon from "@mui/icons-material/Brightness4"
|
||||
import { useDeleteStudyDialog } from "./DeleteStudyDialog"
|
||||
import { styled } from "@mui/system"
|
||||
import { AppDrawer } from "./AppDrawer"
|
||||
import { useCreateStudyDialog } from "./CreateStudyDialog"
|
||||
import { useDeleteStudyDialog } from "./DeleteStudyDialog"
|
||||
import { useRenameStudyDialog } from "./RenameStudyDialog"
|
||||
|
||||
export const StudyList: FC<{
|
||||
toggleColorMode: () => void
|
||||
}> = ({ toggleColorMode }) => {
|
||||
const theme = useTheme()
|
||||
const action = actionCreator()
|
||||
|
||||
const [studyFilterText, setStudyFilterText] = React.useState<string>("")
|
||||
const studyFilter = (row: StudySummary) => {
|
||||
@@ -41,116 +49,69 @@ export const StudyList: FC<{
|
||||
return row.study_name.indexOf(k) >= 0
|
||||
})
|
||||
}
|
||||
const [openDeleteStudyDialog, renderDeleteStudyDialog] =
|
||||
useDeleteStudyDialog()
|
||||
const studies = useRecoilValue<StudySummary[]>(studySummariesState)
|
||||
const [openCreateStudyDialog, renderCreateStudyDialog] =
|
||||
useCreateStudyDialog()
|
||||
const [openDeleteStudyDialog, renderDeleteStudyDialog] =
|
||||
useDeleteStudyDialog()
|
||||
const [openRenameStudyDialog, renderRenameStudyDialog] =
|
||||
useRenameStudyDialog(studies)
|
||||
const [sortBy, setSortBy] = useState<"id-asc" | "id-desc">("id-asc")
|
||||
|
||||
const linkColor = useMemo(
|
||||
() =>
|
||||
theme.palette.mode === "dark"
|
||||
? theme.palette.primary.light
|
||||
: theme.palette.primary.dark,
|
||||
[theme.palette.mode]
|
||||
)
|
||||
|
||||
const action = actionCreator()
|
||||
const studies = useRecoilValue<StudySummary[]>(studySummariesState)
|
||||
let filteredStudies = studies.filter((s) => !studyFilter(s))
|
||||
if (sortBy === "id-desc") {
|
||||
filteredStudies = filteredStudies.reverse()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
action.updateStudySummaries()
|
||||
}, [])
|
||||
|
||||
const columns: DataGridColumn<StudySummary>[] = [
|
||||
{
|
||||
field: "study_id",
|
||||
label: "Study ID",
|
||||
sortable: true,
|
||||
const Select = styled(TextField)(({ theme }) => ({
|
||||
"& .MuiInputBase-input": {
|
||||
// vertical padding + font size from searchIcon
|
||||
paddingLeft: `calc(1em + ${theme.spacing(4)})`,
|
||||
},
|
||||
{
|
||||
field: "study_name",
|
||||
label: "Name",
|
||||
sortable: true,
|
||||
toCellValue: (i) => (
|
||||
<Link
|
||||
to={`${URL_PREFIX}/studies/${studies[i].study_id}`}
|
||||
style={{ color: linkColor }}
|
||||
>
|
||||
{studies[i].study_name}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
field: "directions",
|
||||
label: "Direction",
|
||||
sortable: false,
|
||||
toCellValue: (i) => studies[i].directions.join(),
|
||||
},
|
||||
{
|
||||
field: "study_name",
|
||||
label: "",
|
||||
sortable: false,
|
||||
padding: "none",
|
||||
toCellValue: (i) => (
|
||||
<IconButton
|
||||
aria-label="delete study"
|
||||
size="small"
|
||||
color="inherit"
|
||||
onClick={() => {
|
||||
openDeleteStudyDialog(studies[i].study_id)
|
||||
}}
|
||||
>
|
||||
<Delete />
|
||||
</IconButton>
|
||||
),
|
||||
},
|
||||
]
|
||||
}))
|
||||
const sortBySelect = (
|
||||
<Box
|
||||
sx={{
|
||||
position: "relative",
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
margin: theme.spacing(0, 2),
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
padding: theme.spacing(0, 2),
|
||||
height: "100%",
|
||||
position: "absolute",
|
||||
pointerEvents: "none",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<SortIcon />
|
||||
</Box>
|
||||
<Select
|
||||
select
|
||||
value={sortBy}
|
||||
onChange={(e) => {
|
||||
setSortBy(e.target.value as "id-asc" | "id-desc")
|
||||
}}
|
||||
>
|
||||
<MenuItem value={"id-asc"}>Sort ascending</MenuItem>
|
||||
<MenuItem value={"id-desc"}>Sort descending</MenuItem>
|
||||
</Select>
|
||||
</Box>
|
||||
)
|
||||
|
||||
const collapseAttrColumns: DataGridColumn<Attribute>[] = [
|
||||
{ field: "key", label: "Key", sortable: true },
|
||||
{ field: "value", label: "Value", sortable: true },
|
||||
]
|
||||
|
||||
const collapseBody = (index: number) => {
|
||||
return (
|
||||
<Grid container direction="row">
|
||||
<Grid item xs={6}>
|
||||
<Box margin={1}>
|
||||
<Typography variant="h6" gutterBottom component="div">
|
||||
Study user attributes
|
||||
</Typography>
|
||||
<DataGrid<Attribute>
|
||||
columns={collapseAttrColumns}
|
||||
rows={studies[index].user_attrs}
|
||||
keyField={"key"}
|
||||
dense={true}
|
||||
initialRowsPerPage={5}
|
||||
rowsPerPageOption={[5, 10, { label: "All", value: -1 }]}
|
||||
/>
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Box margin={1}>
|
||||
<Typography variant="h6" gutterBottom component="div">
|
||||
Study system attributes
|
||||
</Typography>
|
||||
<DataGrid<Attribute>
|
||||
columns={collapseAttrColumns}
|
||||
rows={studies[index].system_attrs}
|
||||
keyField={"key"}
|
||||
dense={true}
|
||||
initialRowsPerPage={5}
|
||||
rowsPerPageOption={[5, 10, { label: "All", value: -1 }]}
|
||||
/>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
const toolbar = <HomeIcon sx={{ margin: theme.spacing(0, 1) }} />
|
||||
|
||||
return (
|
||||
<>
|
||||
<AppBar position="static">
|
||||
<Box sx={{ display: "flex" }}>
|
||||
<AppDrawer toggleColorMode={toggleColorMode} toolbar={toolbar}>
|
||||
<Container
|
||||
sx={{
|
||||
["@media (min-width: 1280px)"]: {
|
||||
@@ -158,120 +119,122 @@ export const StudyList: FC<{
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Toolbar>
|
||||
<Typography variant="h6">{APP_BAR_TITLE}</Typography>
|
||||
<Box sx={{ flexGrow: 1 }} />
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
toggleColorMode()
|
||||
}}
|
||||
color="inherit"
|
||||
title={
|
||||
theme.palette.mode === "dark"
|
||||
? "Switch to light mode"
|
||||
: "Switch to dark mode"
|
||||
}
|
||||
>
|
||||
{theme.palette.mode === "dark" ? (
|
||||
<Brightness7Icon />
|
||||
) : (
|
||||
<Brightness4Icon />
|
||||
)}
|
||||
</IconButton>
|
||||
<IconButton
|
||||
aria-controls="menu-appbar"
|
||||
aria-haspopup="true"
|
||||
onClick={() => {
|
||||
action.updateStudySummaries("Success to reload")
|
||||
}}
|
||||
color="inherit"
|
||||
title="Reload studies"
|
||||
>
|
||||
<Refresh />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
aria-controls="menu-appbar"
|
||||
aria-haspopup="true"
|
||||
onClick={(e) => {
|
||||
openCreateStudyDialog()
|
||||
}}
|
||||
color="inherit"
|
||||
title="Create new study"
|
||||
>
|
||||
<AddBox />
|
||||
</IconButton>
|
||||
</Toolbar>
|
||||
<Card sx={{ margin: theme.spacing(2) }}>
|
||||
<CardContent>
|
||||
<Box sx={{ display: "flex" }}>
|
||||
<DebouncedInputTextField
|
||||
onChange={(s) => {
|
||||
setStudyFilterText(s)
|
||||
}}
|
||||
delay={500}
|
||||
textFieldProps={{
|
||||
fullWidth: true,
|
||||
id: "search-study",
|
||||
variant: "outlined",
|
||||
placeholder: "Search study",
|
||||
sx: { maxWidth: 500 },
|
||||
InputProps: {
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SvgIcon fontSize="small" color="action">
|
||||
<Search />
|
||||
</SvgIcon>
|
||||
</InputAdornment>
|
||||
),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
{sortBySelect}
|
||||
<Box sx={{ flexGrow: 1 }} />
|
||||
<Button
|
||||
variant="outlined"
|
||||
startIcon={<Refresh />}
|
||||
onClick={(e) => {
|
||||
action.updateStudySummaries("Success to reload")
|
||||
}}
|
||||
sx={{ marginRight: theme.spacing(2), minWidth: "120px" }}
|
||||
>
|
||||
Reload
|
||||
</Button>
|
||||
<Button
|
||||
variant="outlined"
|
||||
startIcon={<AddBoxIcon />}
|
||||
onClick={(e) => {
|
||||
openCreateStudyDialog()
|
||||
}}
|
||||
sx={{ marginRight: theme.spacing(2), minWidth: "120px" }}
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
<Button
|
||||
variant="outlined"
|
||||
startIcon={<CompareIcon />}
|
||||
component={Link}
|
||||
to={`${URL_PREFIX}/compare-studies`}
|
||||
sx={{ marginRight: theme.spacing(2), minWidth: "120px" }}
|
||||
>
|
||||
Compare
|
||||
</Button>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Box sx={{ display: "flex", flexWrap: "wrap" }}>
|
||||
{filteredStudies.map((study) => (
|
||||
<Card
|
||||
key={study.study_id}
|
||||
sx={{ margin: theme.spacing(2), width: "500px" }}
|
||||
>
|
||||
<CardActionArea
|
||||
component={Link}
|
||||
to={`${URL_PREFIX}/studies/${study.study_id}`}
|
||||
>
|
||||
<CardContent>
|
||||
<Typography variant="h5">
|
||||
{study.study_id}. {study.study_name}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="subtitle1"
|
||||
color="text.secondary"
|
||||
component="div"
|
||||
>
|
||||
{"Direction: " +
|
||||
study.directions
|
||||
.map((d) => d.toString().toUpperCase())
|
||||
.join(", ")}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</CardActionArea>
|
||||
<CardActions disableSpacing sx={{ paddingTop: 0 }}>
|
||||
<Box sx={{ flexGrow: 1 }} />
|
||||
<IconButton
|
||||
aria-label="rename study"
|
||||
size="small"
|
||||
color="inherit"
|
||||
onClick={() => {
|
||||
openRenameStudyDialog(study.study_id, study.study_name)
|
||||
}}
|
||||
>
|
||||
<DriveFileRenameOutlineIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
aria-label="delete study"
|
||||
size="small"
|
||||
color="inherit"
|
||||
onClick={() => {
|
||||
openDeleteStudyDialog(study.study_id)
|
||||
}}
|
||||
>
|
||||
<Delete />
|
||||
</IconButton>
|
||||
</CardActions>
|
||||
</Card>
|
||||
))}
|
||||
</Box>
|
||||
</Container>
|
||||
</AppBar>
|
||||
<Container
|
||||
sx={{
|
||||
["@media (min-width: 1280px)"]: {
|
||||
maxWidth: "100%",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Card sx={{ margin: theme.spacing(2) }}>
|
||||
<CardContent>
|
||||
<Typography
|
||||
variant="h5"
|
||||
style={{ paddingBottom: theme.spacing(1) }}
|
||||
>
|
||||
Announcement
|
||||
</Typography>
|
||||
<Typography>
|
||||
{`Please go to `}
|
||||
<Link to={`${URL_PREFIX}/beta`} style={{ color: linkColor }}>
|
||||
our experimental new UI page
|
||||
</Link>
|
||||
{" and share your thoughts with us via "}
|
||||
<Link to={`${URL_PREFIX}/beta`} style={{ color: linkColor }}>
|
||||
the GitHub Discussion's post
|
||||
</Link>
|
||||
{"."}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card sx={{ margin: theme.spacing(2) }}>
|
||||
<CardContent>
|
||||
<Box sx={{ maxWidth: 500 }}>
|
||||
<DebouncedInputTextField
|
||||
onChange={(s) => {
|
||||
setStudyFilterText(s)
|
||||
}}
|
||||
delay={500}
|
||||
textFieldProps={{
|
||||
fullWidth: true,
|
||||
id: "search-study",
|
||||
variant: "outlined",
|
||||
placeholder: "Search study",
|
||||
InputProps: {
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SvgIcon fontSize="small" color="action">
|
||||
<Search />
|
||||
</SvgIcon>
|
||||
</InputAdornment>
|
||||
),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card sx={{ margin: theme.spacing(2) }}>
|
||||
<DataGrid<StudySummary>
|
||||
columns={columns}
|
||||
rows={studies}
|
||||
keyField={"study_id"}
|
||||
collapseBody={collapseBody}
|
||||
initialRowsPerPage={10}
|
||||
rowsPerPageOption={[5, 10, { label: "All", value: -1 }]}
|
||||
defaultFilter={studyFilter}
|
||||
/>
|
||||
</Card>
|
||||
</Container>
|
||||
</AppDrawer>
|
||||
{renderCreateStudyDialog()}
|
||||
{renderDeleteStudyDialog()}
|
||||
</>
|
||||
{renderRenameStudyDialog()}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,255 +0,0 @@
|
||||
import React, { FC, useEffect, useState } from "react"
|
||||
import { useRecoilValue } from "recoil"
|
||||
import { Link } from "react-router-dom"
|
||||
import {
|
||||
Typography,
|
||||
Container,
|
||||
Card,
|
||||
CardActionArea,
|
||||
Box,
|
||||
Button,
|
||||
IconButton,
|
||||
MenuItem,
|
||||
useTheme,
|
||||
InputAdornment,
|
||||
SvgIcon,
|
||||
CardContent,
|
||||
TextField,
|
||||
CardActions,
|
||||
} from "@mui/material"
|
||||
import MuiLink from "@mui/material/Link"
|
||||
import { Delete, Refresh, Search } from "@mui/icons-material"
|
||||
import SortIcon from "@mui/icons-material/Sort"
|
||||
import HomeIcon from "@mui/icons-material/Home"
|
||||
import AddBoxIcon from "@mui/icons-material/AddBox"
|
||||
import CompareIcon from "@mui/icons-material/Compare"
|
||||
import DriveFileRenameOutlineIcon from "@mui/icons-material/DriveFileRenameOutline"
|
||||
|
||||
import { actionCreator } from "../action"
|
||||
import { DebouncedInputTextField } from "./Debounce"
|
||||
import { studySummariesState } from "../state"
|
||||
import { styled } from "@mui/system"
|
||||
import { AppDrawer } from "./AppDrawer"
|
||||
import { useCreateStudyDialog } from "./CreateStudyDialog"
|
||||
import { useDeleteStudyDialog } from "./DeleteStudyDialog"
|
||||
import { useRenameStudyDialog } from "./RenameStudyDialog"
|
||||
|
||||
export const StudyListBeta: FC<{
|
||||
toggleColorMode: () => void
|
||||
}> = ({ toggleColorMode }) => {
|
||||
const theme = useTheme()
|
||||
const action = actionCreator()
|
||||
|
||||
const [studyFilterText, setStudyFilterText] = React.useState<string>("")
|
||||
const studyFilter = (row: StudySummary) => {
|
||||
const keywords = studyFilterText.split(" ")
|
||||
return !keywords.every((k) => {
|
||||
if (k === "") {
|
||||
return true
|
||||
}
|
||||
return row.study_name.indexOf(k) >= 0
|
||||
})
|
||||
}
|
||||
const studies = useRecoilValue<StudySummary[]>(studySummariesState)
|
||||
const [openCreateStudyDialog, renderCreateStudyDialog] =
|
||||
useCreateStudyDialog()
|
||||
const [openDeleteStudyDialog, renderDeleteStudyDialog] =
|
||||
useDeleteStudyDialog()
|
||||
const [openRenameStudyDialog, renderRenameStudyDialog] =
|
||||
useRenameStudyDialog(studies)
|
||||
const [sortBy, setSortBy] = useState<"id-asc" | "id-desc">("id-asc")
|
||||
|
||||
let filteredStudies = studies.filter((s) => !studyFilter(s))
|
||||
if (sortBy === "id-desc") {
|
||||
filteredStudies = filteredStudies.reverse()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
action.updateStudySummaries()
|
||||
}, [])
|
||||
|
||||
const Select = styled(TextField)(({ theme }) => ({
|
||||
"& .MuiInputBase-input": {
|
||||
// vertical padding + font size from searchIcon
|
||||
paddingLeft: `calc(1em + ${theme.spacing(4)})`,
|
||||
},
|
||||
}))
|
||||
const sortBySelect = (
|
||||
<Box
|
||||
sx={{
|
||||
position: "relative",
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
margin: theme.spacing(0, 2),
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
padding: theme.spacing(0, 2),
|
||||
height: "100%",
|
||||
position: "absolute",
|
||||
pointerEvents: "none",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<SortIcon />
|
||||
</Box>
|
||||
<Select
|
||||
select
|
||||
value={sortBy}
|
||||
onChange={(e) => {
|
||||
setSortBy(e.target.value as "id-asc" | "id-desc")
|
||||
}}
|
||||
>
|
||||
<MenuItem value={"id-asc"}>Sort ascending</MenuItem>
|
||||
<MenuItem value={"id-desc"}>Sort descending</MenuItem>
|
||||
</Select>
|
||||
</Box>
|
||||
)
|
||||
|
||||
const toolbar = <HomeIcon sx={{ margin: theme.spacing(0, 1) }} />
|
||||
|
||||
return (
|
||||
<Box sx={{ display: "flex" }}>
|
||||
<AppDrawer toggleColorMode={toggleColorMode} toolbar={toolbar}>
|
||||
<Container
|
||||
sx={{
|
||||
["@media (min-width: 1280px)"]: {
|
||||
maxWidth: "100%",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Card sx={{ margin: theme.spacing(2) }}>
|
||||
<CardContent>
|
||||
<Typography>
|
||||
{`Thank you for testing the new UI! We would appreciate it if you could send us the feedback via `}
|
||||
<MuiLink
|
||||
target="_blank"
|
||||
href="https://github.com/optuna/optuna-dashboard/discussions/332"
|
||||
>
|
||||
this post
|
||||
</MuiLink>
|
||||
{" on GitHub Discussions."}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card sx={{ margin: theme.spacing(2) }}>
|
||||
<CardContent>
|
||||
<Box sx={{ display: "flex" }}>
|
||||
<DebouncedInputTextField
|
||||
onChange={(s) => {
|
||||
setStudyFilterText(s)
|
||||
}}
|
||||
delay={500}
|
||||
textFieldProps={{
|
||||
fullWidth: true,
|
||||
id: "search-study",
|
||||
variant: "outlined",
|
||||
placeholder: "Search study",
|
||||
sx: { maxWidth: 500 },
|
||||
InputProps: {
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SvgIcon fontSize="small" color="action">
|
||||
<Search />
|
||||
</SvgIcon>
|
||||
</InputAdornment>
|
||||
),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
{sortBySelect}
|
||||
<Box sx={{ flexGrow: 1 }} />
|
||||
<Button
|
||||
variant="outlined"
|
||||
startIcon={<Refresh />}
|
||||
onClick={(e) => {
|
||||
action.updateStudySummaries("Success to reload")
|
||||
}}
|
||||
sx={{ marginRight: theme.spacing(2), minWidth: "120px" }}
|
||||
>
|
||||
Reload
|
||||
</Button>
|
||||
<Button
|
||||
variant="outlined"
|
||||
startIcon={<AddBoxIcon />}
|
||||
onClick={(e) => {
|
||||
openCreateStudyDialog()
|
||||
}}
|
||||
sx={{ marginRight: theme.spacing(2), minWidth: "120px" }}
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
<Button
|
||||
variant="outlined"
|
||||
startIcon={<CompareIcon />}
|
||||
component={Link}
|
||||
to={`${URL_PREFIX}/compare-studies`}
|
||||
sx={{ marginRight: theme.spacing(2), minWidth: "120px" }}
|
||||
>
|
||||
Compare
|
||||
</Button>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Box sx={{ display: "flex", flexWrap: "wrap" }}>
|
||||
{filteredStudies.map((study) => (
|
||||
<Card
|
||||
key={study.study_id}
|
||||
sx={{ margin: theme.spacing(2), width: "500px" }}
|
||||
>
|
||||
<CardActionArea
|
||||
component={Link}
|
||||
to={`${URL_PREFIX}/studies/${study.study_id}/beta`}
|
||||
>
|
||||
<CardContent>
|
||||
<Typography variant="h5">
|
||||
{study.study_id}. {study.study_name}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="subtitle1"
|
||||
color="text.secondary"
|
||||
component="div"
|
||||
>
|
||||
{"Direction: " +
|
||||
study.directions
|
||||
.map((d) => d.toString().toUpperCase())
|
||||
.join(", ")}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</CardActionArea>
|
||||
<CardActions disableSpacing sx={{ paddingTop: 0 }}>
|
||||
<Box sx={{ flexGrow: 1 }} />
|
||||
<IconButton
|
||||
aria-label="rename study"
|
||||
size="small"
|
||||
color="inherit"
|
||||
onClick={() => {
|
||||
openRenameStudyDialog(study.study_id, study.study_name)
|
||||
}}
|
||||
>
|
||||
<DriveFileRenameOutlineIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
aria-label="delete study"
|
||||
size="small"
|
||||
color="inherit"
|
||||
onClick={() => {
|
||||
openDeleteStudyDialog(study.study_id)
|
||||
}}
|
||||
>
|
||||
<Delete />
|
||||
</IconButton>
|
||||
</CardActions>
|
||||
</Card>
|
||||
))}
|
||||
</Box>
|
||||
</Container>
|
||||
</AppDrawer>
|
||||
{renderCreateStudyDialog()}
|
||||
{renderDeleteStudyDialog()}
|
||||
{renderRenameStudyDialog()}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
@@ -1,28 +1,16 @@
|
||||
import React, { createRef, FC, FormEvent, MouseEvent } from "react"
|
||||
import {
|
||||
Typography,
|
||||
Grid,
|
||||
Box,
|
||||
Button,
|
||||
IconButton,
|
||||
Stack,
|
||||
TextField,
|
||||
} from "@mui/material"
|
||||
import React, { FC } from "react"
|
||||
import { IconButton } from "@mui/material"
|
||||
import LinkIcon from "@mui/icons-material/Link"
|
||||
|
||||
import { DataGridColumn, DataGrid } from "./DataGrid"
|
||||
import { Link } from "react-router-dom"
|
||||
|
||||
import { actionCreator } from "../action"
|
||||
|
||||
export const TrialTable: FC<{
|
||||
studyDetail: StudyDetail | null
|
||||
isBeta: boolean
|
||||
initialRowsPerPage?: number
|
||||
}> = ({ studyDetail, isBeta, initialRowsPerPage }) => {
|
||||
}> = ({ studyDetail, initialRowsPerPage }) => {
|
||||
const trials: Trial[] = studyDetail !== null ? studyDetail.trials : []
|
||||
const objectiveNames: string[] = studyDetail?.objective_names || []
|
||||
const action = actionCreator()
|
||||
|
||||
const columns: DataGridColumn<Trial>[] = [
|
||||
{ field: "number", label: "Number", sortable: true, padding: "none" },
|
||||
@@ -103,48 +91,6 @@ export const TrialTable: FC<{
|
||||
}))
|
||||
columns.push(...objectiveColumns)
|
||||
}
|
||||
if (!isBeta) {
|
||||
columns.push({
|
||||
field: "datetime_start",
|
||||
label: "Duration(ms)",
|
||||
toCellValue: (i) => {
|
||||
const startMs = trials[i].datetime_start?.getTime()
|
||||
const completeMs = trials[i].datetime_complete?.getTime()
|
||||
if (startMs !== undefined && completeMs !== undefined) {
|
||||
return (completeMs - startMs).toString()
|
||||
}
|
||||
return null
|
||||
},
|
||||
sortable: true,
|
||||
less: (firstEl, secondEl): number => {
|
||||
const firstStartMs = firstEl.datetime_start?.getTime()
|
||||
const firstCompleteMs = firstEl.datetime_complete?.getTime()
|
||||
const firstDurationMs =
|
||||
firstStartMs !== undefined && firstCompleteMs !== undefined
|
||||
? firstCompleteMs - firstStartMs
|
||||
: undefined
|
||||
const secondStartMs = secondEl.datetime_start?.getTime()
|
||||
const secondCompleteMs = secondEl.datetime_complete?.getTime()
|
||||
const secondDurationMs =
|
||||
secondStartMs !== undefined && secondCompleteMs !== undefined
|
||||
? secondCompleteMs - secondStartMs
|
||||
: undefined
|
||||
|
||||
if (firstDurationMs === secondDurationMs) {
|
||||
return 0
|
||||
} else if (
|
||||
firstDurationMs !== undefined &&
|
||||
secondDurationMs !== undefined
|
||||
) {
|
||||
return firstDurationMs < secondDurationMs ? 1 : -1
|
||||
} else if (firstDurationMs !== undefined) {
|
||||
return -1
|
||||
} else {
|
||||
return 1
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
if (
|
||||
studyDetail?.union_search_space.length ===
|
||||
studyDetail?.intersection_search_space.length
|
||||
@@ -220,175 +166,24 @@ export const TrialTable: FC<{
|
||||
},
|
||||
})
|
||||
})
|
||||
if (isBeta) {
|
||||
columns.push({
|
||||
field: "trial_id",
|
||||
label: "Detail",
|
||||
toCellValue: (i) => (
|
||||
<IconButton
|
||||
component={Link}
|
||||
to={
|
||||
URL_PREFIX +
|
||||
`/studies/${trials[i].study_id}/trials?numbers=${trials[i].number}`
|
||||
}
|
||||
color="inherit"
|
||||
title="Go to the trial's detail page"
|
||||
size="small"
|
||||
>
|
||||
<LinkIcon />
|
||||
</IconButton>
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
const collapseIntermediateValueColumns: DataGridColumn<TrialIntermediateValue>[] =
|
||||
[
|
||||
{ field: "step", label: "Step", sortable: true },
|
||||
{
|
||||
field: "value",
|
||||
label: "Value",
|
||||
sortable: true,
|
||||
less: (firstEl, secondEl): number => {
|
||||
const firstVal = firstEl.value
|
||||
const secondVal = secondEl.value
|
||||
if (firstVal === secondVal) {
|
||||
return 0
|
||||
}
|
||||
if (firstVal === "nan") {
|
||||
return -1
|
||||
} else if (secondVal === "nan") {
|
||||
return 1
|
||||
}
|
||||
if (firstVal === "-inf" || secondVal === "inf") {
|
||||
return 1
|
||||
} else if (secondVal === "-inf" || firstVal === "inf") {
|
||||
return -1
|
||||
}
|
||||
return firstVal < secondVal ? 1 : -1
|
||||
},
|
||||
},
|
||||
]
|
||||
const collapseAttrColumns: DataGridColumn<Attribute>[] = [
|
||||
{ field: "key", label: "Key", sortable: true },
|
||||
{ field: "value", label: "Value", sortable: true },
|
||||
]
|
||||
|
||||
const collapseBody = (index: number) => {
|
||||
const objectiveFormRefs = studyDetail?.directions.map((d) =>
|
||||
createRef<HTMLInputElement>()
|
||||
)
|
||||
const handleSubmit = (e: FormEvent<HTMLFormElement>): void => {
|
||||
if (objectiveFormRefs === undefined) {
|
||||
return
|
||||
}
|
||||
if (studyDetail === null) {
|
||||
return
|
||||
}
|
||||
|
||||
e.preventDefault()
|
||||
const studyId = studyDetail.id
|
||||
const trialId = trials[index].trial_id
|
||||
const objectiveValues = objectiveFormRefs.map((ref) =>
|
||||
ref.current ? Number(ref.current.value) : NaN
|
||||
)
|
||||
if (objectiveValues.includes(NaN)) {
|
||||
return
|
||||
}
|
||||
|
||||
action.makeTrialComplete(studyId, trialId, objectiveValues)
|
||||
}
|
||||
|
||||
const handleFailTrial = (e: MouseEvent<HTMLButtonElement>): void => {
|
||||
if (studyDetail === null) {
|
||||
return
|
||||
}
|
||||
const studyId = studyDetail.id
|
||||
const trialId = trials[index].trial_id
|
||||
action.makeTrialFail(studyId, trialId)
|
||||
}
|
||||
|
||||
return (
|
||||
<Grid container direction="row">
|
||||
<Grid item xs={6}>
|
||||
<Box margin={1}>
|
||||
<Typography variant="h6" gutterBottom component="div">
|
||||
Intermediate values
|
||||
</Typography>
|
||||
<DataGrid<TrialIntermediateValue>
|
||||
columns={collapseIntermediateValueColumns}
|
||||
rows={trials[index].intermediate_values}
|
||||
keyField={"step"}
|
||||
dense={true}
|
||||
rowsPerPageOption={[5, 10, { label: "All", value: -1 }]}
|
||||
/>
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Box margin={1}>
|
||||
<Typography variant="h6" gutterBottom component="div">
|
||||
Trial system attributes
|
||||
</Typography>
|
||||
<DataGrid<Attribute>
|
||||
columns={collapseAttrColumns}
|
||||
rows={trials[index].system_attrs}
|
||||
keyField={"key"}
|
||||
dense={true}
|
||||
rowsPerPageOption={[5, 10, { label: "All", value: -1 }]}
|
||||
/>
|
||||
</Box>
|
||||
</Grid>
|
||||
{trials[index].state === "Running" ? (
|
||||
<Grid item xs={12}>
|
||||
<Box margin={1}>
|
||||
<Typography variant="h6" gutterBottom component="div">
|
||||
Trial tell
|
||||
</Typography>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Box margin={1}>
|
||||
<Stack direction="row" spacing={1}>
|
||||
{objectiveFormRefs !== undefined &&
|
||||
objectiveFormRefs.map((ref, i) => (
|
||||
<TextField
|
||||
required
|
||||
id={`objective-${i}`}
|
||||
key={`objective-${i}`}
|
||||
label={
|
||||
objectiveNames.length ===
|
||||
studyDetail?.directions.length
|
||||
? objectiveNames[i]
|
||||
: `Objective ${i}`
|
||||
}
|
||||
inputProps={{
|
||||
inputMode: "numeric",
|
||||
pattern: "[-+]?[0-9]*.?[0-9]+([eE][-+]?[0-9]+)?",
|
||||
title: "Please input a float number",
|
||||
}}
|
||||
inputRef={ref}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</Box>
|
||||
<Box margin={1}>
|
||||
<Stack direction="row" spacing={1}>
|
||||
<Button variant="contained" type="submit">
|
||||
Submit
|
||||
</Button>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="error"
|
||||
onClick={handleFailTrial}
|
||||
>
|
||||
Fail Trial
|
||||
</Button>
|
||||
</Stack>
|
||||
</Box>
|
||||
</form>
|
||||
</Box>
|
||||
</Grid>
|
||||
) : null}
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
columns.push({
|
||||
field: "trial_id",
|
||||
label: "Detail",
|
||||
toCellValue: (i) => (
|
||||
<IconButton
|
||||
component={Link}
|
||||
to={
|
||||
URL_PREFIX +
|
||||
`/studies/${trials[i].study_id}/trials?numbers=${trials[i].number}`
|
||||
}
|
||||
color="inherit"
|
||||
title="Go to the trial's detail page"
|
||||
size="small"
|
||||
>
|
||||
<LinkIcon />
|
||||
</IconButton>
|
||||
),
|
||||
})
|
||||
|
||||
return (
|
||||
<DataGrid<Trial>
|
||||
@@ -396,7 +191,6 @@ export const TrialTable: FC<{
|
||||
rows={trials}
|
||||
keyField={"trial_id"}
|
||||
dense={true}
|
||||
collapseBody={isBeta ? undefined : collapseBody}
|
||||
initialRowsPerPage={initialRowsPerPage}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -95,7 +95,6 @@ export class Target {
|
||||
const filterTrials = (
|
||||
study: StudyDetail | null,
|
||||
targets: Target[],
|
||||
filterComplete: boolean,
|
||||
filterPruned: boolean
|
||||
): Trial[] => {
|
||||
if (study === null) {
|
||||
@@ -105,9 +104,6 @@ const filterTrials = (
|
||||
if (t.state !== "Complete" && t.state !== "Pruned") {
|
||||
return false
|
||||
}
|
||||
if (t.state === "Complete" && filterComplete) {
|
||||
return false
|
||||
}
|
||||
if (t.state === "Pruned" && filterPruned) {
|
||||
return false
|
||||
}
|
||||
@@ -118,24 +114,20 @@ const filterTrials = (
|
||||
export const useFilteredTrials = (
|
||||
study: StudyDetail | null,
|
||||
targets: Target[],
|
||||
filterComplete: boolean,
|
||||
filterPruned: boolean
|
||||
): Trial[] =>
|
||||
useMemo<Trial[]>(() => {
|
||||
return filterTrials(study, targets, filterComplete, filterPruned)
|
||||
}, [study?.trials, targets, filterComplete, filterPruned])
|
||||
return filterTrials(study, targets, filterPruned)
|
||||
}, [study?.trials, targets, filterPruned])
|
||||
|
||||
export const useFilteredTrialsFromStudies = (
|
||||
studies: StudyDetail[],
|
||||
targets: Target[],
|
||||
filterComplete: boolean,
|
||||
filterPruned: boolean
|
||||
): Trial[][] =>
|
||||
useMemo<Trial[][]>(() => {
|
||||
return studies.map((s) =>
|
||||
filterTrials(s, targets, filterComplete, filterPruned)
|
||||
)
|
||||
}, [studies, targets, filterComplete, filterPruned])
|
||||
return studies.map((s) => filterTrials(s, targets, filterPruned))
|
||||
}, [studies, targets, filterPruned])
|
||||
|
||||
export const useObjectiveTargets = (
|
||||
study: StudyDetail | null
|
||||
|
||||
@@ -1,222 +0,0 @@
|
||||
import React from "react"
|
||||
global.URL.createObjectURL = jest.fn()
|
||||
|
||||
import { SnackbarProvider } from "notistack"
|
||||
import { RecoilRoot } from "recoil"
|
||||
import { cleanup, render, within, fireEvent } from "@testing-library/react"
|
||||
import { TrialTable } from "../optuna_dashboard/ts/components/TrialTable"
|
||||
|
||||
afterEach(cleanup)
|
||||
|
||||
const dummyDistribution: FloatDistribution = {
|
||||
type: "FloatDistribution",
|
||||
low: 0,
|
||||
high: 10,
|
||||
step: 1,
|
||||
log: false,
|
||||
}
|
||||
const trials: Trial[] = [
|
||||
{
|
||||
trial_id: 1,
|
||||
study_id: 0,
|
||||
number: 0,
|
||||
state: "Complete" as TrialState,
|
||||
values: [-1],
|
||||
intermediate_values: [],
|
||||
datetime_start: new Date("2021-06-15T00:00:00"),
|
||||
datetime_complete: new Date("2021-06-15T00:00:01"),
|
||||
params: [
|
||||
{
|
||||
name: "x",
|
||||
param_internal_value: 1,
|
||||
param_external_value: "1",
|
||||
param_external_type: "float",
|
||||
distribution: dummyDistribution,
|
||||
},
|
||||
{
|
||||
name: "y",
|
||||
param_internal_value: 2,
|
||||
param_external_value: "2",
|
||||
param_external_type: "float",
|
||||
distribution: dummyDistribution,
|
||||
},
|
||||
],
|
||||
fixed_params: [],
|
||||
user_attrs: [],
|
||||
system_attrs: [],
|
||||
note: {
|
||||
body: "",
|
||||
version: 0,
|
||||
},
|
||||
artifacts: [],
|
||||
},
|
||||
{
|
||||
trial_id: 2,
|
||||
study_id: 0,
|
||||
number: 1,
|
||||
state: "Fail" as TrialState,
|
||||
values: [-2],
|
||||
intermediate_values: [],
|
||||
datetime_start: new Date("2021-06-15T00:00:01"),
|
||||
datetime_complete: new Date("2021-06-15T00:00:03"),
|
||||
params: [
|
||||
{
|
||||
name: "x",
|
||||
param_internal_value: 1,
|
||||
param_external_value: "1",
|
||||
param_external_type: "float",
|
||||
distribution: dummyDistribution,
|
||||
},
|
||||
{
|
||||
name: "y",
|
||||
param_internal_value: 2,
|
||||
param_external_value: "2",
|
||||
param_external_type: "float",
|
||||
distribution: dummyDistribution,
|
||||
},
|
||||
],
|
||||
fixed_params: [],
|
||||
user_attrs: [],
|
||||
system_attrs: [],
|
||||
note: {
|
||||
body: "",
|
||||
version: 0,
|
||||
},
|
||||
artifacts: [],
|
||||
},
|
||||
]
|
||||
|
||||
const study_direction: StudyDirection = "minimize" as StudyDirection
|
||||
|
||||
const studyDetail: StudyDetail = {
|
||||
id: 1,
|
||||
name: "study_0",
|
||||
directions: [study_direction],
|
||||
datetime_start: new Date("2021-06-15T00:00:00"),
|
||||
best_trials: [trials[1]],
|
||||
trials: trials,
|
||||
intersection_search_space: [
|
||||
{
|
||||
name: "x",
|
||||
distribution: dummyDistribution,
|
||||
},
|
||||
{
|
||||
name: "y",
|
||||
distribution: dummyDistribution,
|
||||
},
|
||||
],
|
||||
union_search_space: [
|
||||
{
|
||||
name: "x",
|
||||
distribution: dummyDistribution,
|
||||
},
|
||||
{
|
||||
name: "y",
|
||||
distribution: dummyDistribution,
|
||||
},
|
||||
],
|
||||
union_user_attrs: [
|
||||
{ key: "foo", sortable: false },
|
||||
{ key: "bar", sortable: false },
|
||||
],
|
||||
has_intermediate_values: false,
|
||||
note: {
|
||||
version: 0,
|
||||
body: "",
|
||||
},
|
||||
}
|
||||
|
||||
it("Sort TrialTable by trial number", () => {
|
||||
const { getAllByRole, getByText } = render(
|
||||
<RecoilRoot>
|
||||
<SnackbarProvider>
|
||||
<TrialTable studyDetail={studyDetail} isBeta={false} />
|
||||
</SnackbarProvider>
|
||||
</RecoilRoot>
|
||||
)
|
||||
const rows = getAllByRole("row")
|
||||
|
||||
expect(within(rows[1]).getByText("0")).toBeTruthy()
|
||||
expect(within(rows[3]).getAllByText("1")[0]).toBeTruthy()
|
||||
|
||||
fireEvent.click(getByText("Number"))
|
||||
|
||||
const rows_updated = getAllByRole("row")
|
||||
expect(within(rows_updated[1]).getAllByText("1")[0]).toBeTruthy()
|
||||
expect(within(rows_updated[3]).getByText("0")).toBeTruthy()
|
||||
})
|
||||
|
||||
it("Sort TrialTable by value", () => {
|
||||
const { getAllByRole, getByText } = render(
|
||||
<RecoilRoot>
|
||||
<SnackbarProvider>
|
||||
<TrialTable studyDetail={studyDetail} isBeta={false} />
|
||||
</SnackbarProvider>
|
||||
</RecoilRoot>
|
||||
)
|
||||
fireEvent.click(getByText("Value"))
|
||||
const rows = getAllByRole("row")
|
||||
expect(within(rows[1]).getByText("-2")).toBeTruthy()
|
||||
expect(within(rows[3]).getByText("-1")).toBeTruthy()
|
||||
|
||||
fireEvent.click(getByText("Value"))
|
||||
const rows_updated = getAllByRole("row")
|
||||
expect(within(rows_updated[1]).getByText("-1")).toBeTruthy()
|
||||
expect(within(rows_updated[3]).getByText("-2")).toBeTruthy()
|
||||
})
|
||||
|
||||
it("Sort TrialTable by duration", () => {
|
||||
const { getAllByRole, getByText } = render(
|
||||
<RecoilRoot>
|
||||
<SnackbarProvider>
|
||||
<TrialTable studyDetail={studyDetail} isBeta={false} />
|
||||
</SnackbarProvider>
|
||||
</RecoilRoot>
|
||||
)
|
||||
fireEvent.click(getByText("Duration(ms)"))
|
||||
const rows = getAllByRole("row")
|
||||
expect(within(rows[1]).getByText("1000")).toBeTruthy()
|
||||
expect(within(rows[3]).getByText("2000")).toBeTruthy()
|
||||
|
||||
fireEvent.click(getByText("Duration(ms)"))
|
||||
const rows_updated = getAllByRole("row")
|
||||
expect(within(rows_updated[1]).getByText("2000")).toBeTruthy()
|
||||
expect(within(rows_updated[3]).getByText("1000")).toBeTruthy()
|
||||
})
|
||||
|
||||
it("Sort TrialTable by state", () => {
|
||||
const { getAllByRole, getByText } = render(
|
||||
<RecoilRoot>
|
||||
<SnackbarProvider>
|
||||
<TrialTable studyDetail={studyDetail} isBeta={false} />
|
||||
</SnackbarProvider>
|
||||
</RecoilRoot>
|
||||
)
|
||||
fireEvent.click(getByText("State"))
|
||||
const rows = getAllByRole("row")
|
||||
expect(within(rows[1]).getByText("Complete")).toBeTruthy()
|
||||
expect(within(rows[3]).getByText("Fail")).toBeTruthy()
|
||||
|
||||
fireEvent.click(getByText("State"))
|
||||
const rows_updated = getAllByRole("row")
|
||||
expect(within(rows_updated[1]).getByText("Fail")).toBeTruthy()
|
||||
expect(within(rows_updated[3]).getByText("Complete")).toBeTruthy()
|
||||
})
|
||||
|
||||
it("Filter trials by state", () => {
|
||||
const { queryAllByText } = render(
|
||||
<RecoilRoot>
|
||||
<SnackbarProvider>
|
||||
<TrialTable studyDetail={studyDetail} isBeta={false} />
|
||||
</SnackbarProvider>
|
||||
</RecoilRoot>
|
||||
)
|
||||
expect(queryAllByText("Fail").length).toBe(1)
|
||||
|
||||
// Click 'Complete' state
|
||||
const completedRows = queryAllByText("Complete")
|
||||
expect(completedRows.length).toBe(1)
|
||||
fireEvent.click(completedRows[0])
|
||||
|
||||
expect(queryAllByText("Fail").length).toBe(0)
|
||||
})
|
||||
@@ -182,10 +182,10 @@ def create_dummy_storage() -> optuna.storages.InMemoryStorage:
|
||||
|
||||
|
||||
async def contains_study_name(page: Page, study_name: str) -> bool:
|
||||
h4_elements = await page.querySelectorAll("h4")
|
||||
for element in h4_elements:
|
||||
title = await page.evaluate("(element) => element.textContent", element)
|
||||
if title == study_name:
|
||||
typography_elements = await page.querySelectorAll("div.MuiTypography-root")
|
||||
for element in typography_elements:
|
||||
title = await page.evaluate("(element) => element.innerText", element)
|
||||
if study_name in title:
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -205,6 +205,7 @@ async def take_screenshots(storage: optuna.storages.BaseStorage) -> list[str]:
|
||||
summaries = get_all_study_summaries(storage)
|
||||
study_ids = {s._study_id: s.study_name for s in summaries}
|
||||
for study_id, study_name in study_ids.items():
|
||||
# TODO(c-bata): Check "Analysis" tab.
|
||||
await page.goto(f"http://{args.host}:{args.port}/dashboard/studies/{study_id}")
|
||||
time.sleep(args.sleep)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user