From 856b3cb8985ee12e448847ed072c74d7cbed794a Mon Sep 17 00:00:00 2001 From: c-bata Date: Sat, 3 Dec 2022 18:42:00 +0900 Subject: [PATCH] Split ReloadIntervalSelect and TrialTable from StudyDetail.tsx --- .../ts/components/ReloadIntervalSelect.tsx | 85 ++++ .../ts/components/StudyDetail.tsx | 385 +----------------- optuna_dashboard/ts/components/TrialTable.tsx | 298 ++++++++++++++ 3 files changed, 387 insertions(+), 381 deletions(-) create mode 100644 optuna_dashboard/ts/components/ReloadIntervalSelect.tsx create mode 100644 optuna_dashboard/ts/components/TrialTable.tsx diff --git a/optuna_dashboard/ts/components/ReloadIntervalSelect.tsx b/optuna_dashboard/ts/components/ReloadIntervalSelect.tsx new file mode 100644 index 00000000..0693f214 --- /dev/null +++ b/optuna_dashboard/ts/components/ReloadIntervalSelect.tsx @@ -0,0 +1,85 @@ +import React, { FC } from "react" +import { styled } from "@mui/system" +import { useRecoilState } from "recoil" +import { reloadIntervalState } from "../state" +import { MenuItem, TextField, alpha } from "@mui/material" +import { Cached } from "@mui/icons-material" + +export const ReloadIntervalSelect: FC = () => { + const [reloadInterval, updateReloadInterval] = + useRecoilState(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 ( + + + + + + + ) +} diff --git a/optuna_dashboard/ts/components/StudyDetail.tsx b/optuna_dashboard/ts/components/StudyDetail.tsx index 3b3c700e..af57c556 100644 --- a/optuna_dashboard/ts/components/StudyDetail.tsx +++ b/optuna_dashboard/ts/components/StudyDetail.tsx @@ -1,5 +1,5 @@ import React, { FC, useEffect } from "react" -import { useRecoilState, useRecoilValue } from "recoil" +import { useRecoilValue } from "recoil" import { Link, useParams } from "react-router-dom" import { AppBar, @@ -7,21 +7,15 @@ import { Typography, CardContent, Container, - Grid, Toolbar, Box, IconButton, - MenuItem, useTheme, - TextField, - alpha, } from "@mui/material" -import { styled } from "@mui/system" -import { Cached, Home, Settings } from "@mui/icons-material" +import { Home, Settings } from "@mui/icons-material" import Brightness4Icon from "@mui/icons-material/Brightness4" import Brightness7Icon from "@mui/icons-material/Brightness7" -import { DataGridColumn, DataGrid } from "./DataGrid" import { GraphParallelCoordinate } from "./GraphParallelCoordinate" import { GraphHyperparameterImportances } from "./GraphHyperparameterImportances" import { Edf } from "./GraphEdf" @@ -39,6 +33,8 @@ import { studySummariesState, } from "../state" import { usePreferenceDialog } from "./PreferenceDialog" +import { ReloadIntervalSelect } from "./ReloadIntervalSelect" +import { TrialTable } from "./TrialTable" interface ParamTypes { studyId: string @@ -246,376 +242,3 @@ export const StudyDetail: FC<{ ) } - -const ReloadIntervalSelect: FC = () => { - const [reloadInterval, updateReloadInterval] = - useRecoilState(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 ( - - - - - - - ) -} - -export const TrialTable: FC<{ studyDetail: StudyDetail | null }> = ({ - studyDetail, -}) => { - const trials: Trial[] = studyDetail !== null ? studyDetail.trials : [] - - const columns: DataGridColumn[] = [ - { field: "number", label: "Number", sortable: true, padding: "none" }, - { - field: "state", - label: "State", - sortable: true, - filterable: true, - padding: "none", - toCellValue: (i) => trials[i].state.toString(), - }, - ] - if (studyDetail === null || studyDetail.directions.length == 1) { - columns.push({ - field: "values", - label: "Value", - sortable: true, - less: (firstEl, secondEl): number => { - const firstVal = firstEl.values?.[0] - const secondVal = secondEl.values?.[0] - - if (firstVal === secondVal) { - return 0 - } - if (firstVal === undefined) { - return -1 - } else if (secondVal === undefined) { - return 1 - } - if (firstVal === "-inf" || secondVal === "inf") { - return 1 - } else if (secondVal === "-inf" || firstVal === "inf") { - return -1 - } - return firstVal < secondVal ? 1 : -1 - }, - toCellValue: (i) => { - if (trials[i].values === undefined) { - return null - } - return trials[i].values?.[0] - }, - }) - } else { - const objectiveColumns: DataGridColumn[] = - studyDetail.directions.map((s, objectiveId) => ({ - field: "values", - label: `Objective ${objectiveId}`, - sortable: true, - less: (firstEl, secondEl): number => { - const firstVal = firstEl.values?.[objectiveId] - const secondVal = secondEl.values?.[objectiveId] - - if (firstVal === secondVal) { - return 0 - } - if (firstVal === undefined) { - return -1 - } else if (secondVal === undefined) { - return 1 - } - if (firstVal === "-inf" || secondVal === "inf") { - return 1 - } else if (secondVal === "-inf" || firstVal === "inf") { - return -1 - } - return firstVal < secondVal ? 1 : -1 - }, - toCellValue: (i) => { - if (trials[i].values === undefined) { - return null - } - return trials[i].values?.[objectiveId] - }, - })) - columns.push(...objectiveColumns) - } - 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 - ) { - studyDetail?.intersection_search_space.forEach((s) => { - const sortable = s.distribution !== "CategoricalDistribution" - const filterable = s.distribution === "CategoricalDistribution" - columns.push({ - field: "params", - label: `Param ${s.name}`, - toCellValue: (i) => - trials[i].params.find((p) => p.name === s.name)?.value || null, - sortable: sortable, - filterable: filterable, - less: (firstEl, secondEl): number => { - const firstVal = firstEl.params.find((p) => p.name === s.name)?.value - const secondVal = secondEl.params.find( - (p) => p.name === s.name - )?.value - - if (firstVal === secondVal) { - return 0 - } else if (firstVal && secondVal) { - return Number(firstVal) < Number(secondVal) ? 1 : -1 - } else if (firstVal) { - return -1 - } else { - return 1 - } - }, - }) - }) - } else { - columns.push({ - field: "params", - label: "Params", - toCellValue: (i) => - trials[i].params.map((p) => p.name + ": " + p.value).join(", "), - }) - } - - studyDetail?.union_user_attrs.forEach((attr_name) => { - columns.push({ - field: "user_attrs", - label: `User attribute ${attr_name}`, - toCellValue: (i) => - trials[i].user_attrs.find((attr) => attr.key === attr_name)?.value || - null, - sortable: true, - filterable: true, - less: (firstEl, secondEl): number => { - const firstVal = firstEl.user_attrs.find( - (attr) => attr.key === attr_name - )?.value - const secondVal = secondEl.user_attrs.find( - (attr) => attr.key === attr_name - )?.value - - if (firstVal === secondVal) { - return 0 - } else if (firstVal && secondVal) { - return firstVal < secondVal ? 1 : -1 - } else if (firstVal) { - return -1 - } else { - return 1 - } - }, - }) - }) - - const collapseParamColumns: DataGridColumn[] = [ - { field: "name", label: "Name", sortable: true }, - { field: "value", label: "Value", sortable: true }, - ] - const collapseIntermediateValueColumns: DataGridColumn[] = - [ - { 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[] = [ - { field: "key", label: "Key", sortable: true }, - { field: "value", label: "Value", sortable: true }, - ] - - const collapseBody = (index: number) => { - return ( - - - - - Parameters - - - columns={collapseParamColumns} - rows={trials[index].params} - keyField={"name"} - dense={true} - rowsPerPageOption={[5, 10, { label: "All", value: -1 }]} - /> - - Trial user attributes - - - columns={collapseAttrColumns} - rows={trials[index].user_attrs} - keyField={"key"} - dense={true} - rowsPerPageOption={[5, 10, { label: "All", value: -1 }]} - /> - - - - - - Intermediate values - - - columns={collapseIntermediateValueColumns} - rows={trials[index].intermediate_values} - keyField={"step"} - dense={true} - rowsPerPageOption={[5, 10, { label: "All", value: -1 }]} - /> - - Trial system attributes - - - columns={collapseAttrColumns} - rows={trials[index].system_attrs} - keyField={"key"} - dense={true} - rowsPerPageOption={[5, 10, { label: "All", value: -1 }]} - /> - - - - ) - } - - return ( - - columns={columns} - rows={trials} - keyField={"trial_id"} - dense={true} - collapseBody={collapseBody} - /> - ) -} diff --git a/optuna_dashboard/ts/components/TrialTable.tsx b/optuna_dashboard/ts/components/TrialTable.tsx new file mode 100644 index 00000000..6763323c --- /dev/null +++ b/optuna_dashboard/ts/components/TrialTable.tsx @@ -0,0 +1,298 @@ +import React, { FC } from "react" +import { Typography, Grid, Box } from "@mui/material" + +import { DataGridColumn, DataGrid } from "./DataGrid" + +export const TrialTable: FC<{ studyDetail: StudyDetail | null }> = ({ + studyDetail, +}) => { + const trials: Trial[] = studyDetail !== null ? studyDetail.trials : [] + + const columns: DataGridColumn[] = [ + { field: "number", label: "Number", sortable: true, padding: "none" }, + { + field: "state", + label: "State", + sortable: true, + filterable: true, + padding: "none", + toCellValue: (i) => trials[i].state.toString(), + }, + ] + if (studyDetail === null || studyDetail.directions.length == 1) { + columns.push({ + field: "values", + label: "Value", + sortable: true, + less: (firstEl, secondEl): number => { + const firstVal = firstEl.values?.[0] + const secondVal = secondEl.values?.[0] + + if (firstVal === secondVal) { + return 0 + } + if (firstVal === undefined) { + return -1 + } else if (secondVal === undefined) { + return 1 + } + if (firstVal === "-inf" || secondVal === "inf") { + return 1 + } else if (secondVal === "-inf" || firstVal === "inf") { + return -1 + } + return firstVal < secondVal ? 1 : -1 + }, + toCellValue: (i) => { + if (trials[i].values === undefined) { + return null + } + return trials[i].values?.[0] + }, + }) + } else { + const objectiveColumns: DataGridColumn[] = + studyDetail.directions.map((s, objectiveId) => ({ + field: "values", + label: `Objective ${objectiveId}`, + sortable: true, + less: (firstEl, secondEl): number => { + const firstVal = firstEl.values?.[objectiveId] + const secondVal = secondEl.values?.[objectiveId] + + if (firstVal === secondVal) { + return 0 + } + if (firstVal === undefined) { + return -1 + } else if (secondVal === undefined) { + return 1 + } + if (firstVal === "-inf" || secondVal === "inf") { + return 1 + } else if (secondVal === "-inf" || firstVal === "inf") { + return -1 + } + return firstVal < secondVal ? 1 : -1 + }, + toCellValue: (i) => { + if (trials[i].values === undefined) { + return null + } + return trials[i].values?.[objectiveId] + }, + })) + columns.push(...objectiveColumns) + } + 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 + ) { + studyDetail?.intersection_search_space.forEach((s) => { + const sortable = s.distribution !== "CategoricalDistribution" + const filterable = s.distribution === "CategoricalDistribution" + columns.push({ + field: "params", + label: `Param ${s.name}`, + toCellValue: (i) => + trials[i].params.find((p) => p.name === s.name)?.value || null, + sortable: sortable, + filterable: filterable, + less: (firstEl, secondEl): number => { + const firstVal = firstEl.params.find((p) => p.name === s.name)?.value + const secondVal = secondEl.params.find( + (p) => p.name === s.name + )?.value + + if (firstVal === secondVal) { + return 0 + } else if (firstVal && secondVal) { + return Number(firstVal) < Number(secondVal) ? 1 : -1 + } else if (firstVal) { + return -1 + } else { + return 1 + } + }, + }) + }) + } else { + columns.push({ + field: "params", + label: "Params", + toCellValue: (i) => + trials[i].params.map((p) => p.name + ": " + p.value).join(", "), + }) + } + + studyDetail?.union_user_attrs.forEach((attr_name) => { + columns.push({ + field: "user_attrs", + label: `User attribute ${attr_name}`, + toCellValue: (i) => + trials[i].user_attrs.find((attr) => attr.key === attr_name)?.value || + null, + sortable: true, + filterable: true, + less: (firstEl, secondEl): number => { + const firstVal = firstEl.user_attrs.find( + (attr) => attr.key === attr_name + )?.value + const secondVal = secondEl.user_attrs.find( + (attr) => attr.key === attr_name + )?.value + + if (firstVal === secondVal) { + return 0 + } else if (firstVal && secondVal) { + return firstVal < secondVal ? 1 : -1 + } else if (firstVal) { + return -1 + } else { + return 1 + } + }, + }) + }) + + const collapseParamColumns: DataGridColumn[] = [ + { field: "name", label: "Name", sortable: true }, + { field: "value", label: "Value", sortable: true }, + ] + const collapseIntermediateValueColumns: DataGridColumn[] = + [ + { 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[] = [ + { field: "key", label: "Key", sortable: true }, + { field: "value", label: "Value", sortable: true }, + ] + + const collapseBody = (index: number) => { + return ( + + + + + Parameters + + + columns={collapseParamColumns} + rows={trials[index].params} + keyField={"name"} + dense={true} + rowsPerPageOption={[5, 10, { label: "All", value: -1 }]} + /> + + Trial user attributes + + + columns={collapseAttrColumns} + rows={trials[index].user_attrs} + keyField={"key"} + dense={true} + rowsPerPageOption={[5, 10, { label: "All", value: -1 }]} + /> + + + + + + Intermediate values + + + columns={collapseIntermediateValueColumns} + rows={trials[index].intermediate_values} + keyField={"step"} + dense={true} + rowsPerPageOption={[5, 10, { label: "All", value: -1 }]} + /> + + Trial system attributes + + + columns={collapseAttrColumns} + rows={trials[index].system_attrs} + keyField={"key"} + dense={true} + rowsPerPageOption={[5, 10, { label: "All", value: -1 }]} + /> + + + + ) + } + + return ( + + columns={columns} + rows={trials} + keyField={"trial_id"} + dense={true} + collapseBody={collapseBody} + /> + ) +}