From 5336a9f43f194bb95e8a8d314a98e322c75971b9 Mon Sep 17 00:00:00 2001 From: c-bata Date: Sat, 3 Dec 2022 18:17:53 +0900 Subject: [PATCH] Refactor PreferenceDialog --- optuna_dashboard/ts/action.ts | 30 +- .../ts/components/PreferenceDialog.tsx | 172 ++++++++++++ .../ts/components/StudyDetail.tsx | 258 +++--------------- optuna_dashboard/ts/state.ts | 19 ++ optuna_dashboard/ts/types/index.d.ts | 11 + 5 files changed, 268 insertions(+), 222 deletions(-) create mode 100644 optuna_dashboard/ts/components/PreferenceDialog.tsx diff --git a/optuna_dashboard/ts/action.ts b/optuna_dashboard/ts/action.ts index 1bc8eeff..da87dc3f 100644 --- a/optuna_dashboard/ts/action.ts +++ b/optuna_dashboard/ts/action.ts @@ -7,7 +7,13 @@ import { deleteStudyAPI, saveNoteAPI, } from "./apiClient" -import { studyDetailsState, studySummariesState } from "./state" +import { + graphVisibilityState, + studyDetailsState, + studySummariesState, +} from "./state" + +const localStorageGraphVisibility = "graphVisibility" export const actionCreator = () => { const { enqueueSnackbar } = useSnackbar() @@ -15,6 +21,8 @@ export const actionCreator = () => { useRecoilState(studySummariesState) const [studyDetails, setStudyDetails] = useRecoilState(studyDetailsState) + const [graphVisibility, setGraphVisibility] = + useRecoilState(graphVisibilityState) const setStudyDetailState = (studyId: number, study: StudyDetail) => { const newVal = Object.assign({}, studyDetails) @@ -102,6 +110,24 @@ export const actionCreator = () => { }) } + const getGraphVisibility = () => { + const localStoragePreferences = localStorage.getItem( + localStorageGraphVisibility + ) + if (localStoragePreferences !== null) { + const merged = { + ...graphVisibility, + ...JSON.parse(localStoragePreferences), + } + setGraphVisibility(merged) + } + } + + const saveGraphVisibility = (value: GraphVisibility) => { + setGraphVisibility(value) + localStorage.setItem(localStorageGraphVisibility, JSON.stringify(value)) + } + const saveNote = (studyId: number, note: Note): Promise => { return saveNoteAPI(studyId, note) .then(() => { @@ -133,6 +159,8 @@ export const actionCreator = () => { updateStudySummaries, createNewStudy, deleteStudy, + getGraphVisibility, + saveGraphVisibility, saveNote, } } diff --git a/optuna_dashboard/ts/components/PreferenceDialog.tsx b/optuna_dashboard/ts/components/PreferenceDialog.tsx new file mode 100644 index 00000000..79261f7f --- /dev/null +++ b/optuna_dashboard/ts/components/PreferenceDialog.tsx @@ -0,0 +1,172 @@ +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(graphVisibilityState) + const [localGraphVisibility, setLocalGraphVisibility] = + useState(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 + ) => { + setLocalGraphVisibility({ + ...localGraphVisibility, + [event.target.name]: event.target.checked, + }) + } + + const renderPreferenceDialog = () => { + return ( + + + Preferences + + + + + + Charts + + + } + label="History" + /> + + } + label="Pareto Front" + /> + + } + label="Parallel Coordinate" + /> + 1 || + !studyDetail.has_intermediate_values) + } + control={ + + } + label="Intermediate Values" + /> + + } + label="EDF" + /> + + } + label="Contour" + /> + + } + label="Hyperparameter Importances" + /> + + } + label="Slice" + /> + + + + ) + } + return [setPrefOpen, renderPreferenceDialog] +} diff --git a/optuna_dashboard/ts/components/StudyDetail.tsx b/optuna_dashboard/ts/components/StudyDetail.tsx index b1d4c368..3b3c700e 100644 --- a/optuna_dashboard/ts/components/StudyDetail.tsx +++ b/optuna_dashboard/ts/components/StudyDetail.tsx @@ -1,10 +1,8 @@ -import React, { FC, useEffect, useState } from "react" -import { useRecoilValue } from "recoil" +import React, { FC, useEffect } from "react" +import { useRecoilState, useRecoilValue } from "recoil" import { Link, useParams } from "react-router-dom" import { AppBar, - Dialog, - Checkbox, Card, Typography, CardContent, @@ -14,18 +12,12 @@ import { Box, IconButton, MenuItem, - FormGroup, useTheme, - FormLabel, TextField, alpha, } from "@mui/material" import { styled } from "@mui/system" import { Cached, Home, Settings } from "@mui/icons-material" -import FormControlLabel from "@mui/material/FormControlLabel" -import MuiDialogTitle from "@mui/material/DialogTitle" -import MuiDialogContent from "@mui/material/DialogContent" -import CloseIcon from "@mui/icons-material/Close" import Brightness4Icon from "@mui/icons-material/Brightness4" import Brightness7Icon from "@mui/icons-material/Brightness7" @@ -40,7 +32,13 @@ import { GraphHistory } from "./GraphHistory" import { GraphParetoFront } from "./GraphParetoFront" import { Note } from "./Note" import { actionCreator } from "../action" -import { studyDetailsState, studySummariesState } from "../state" +import { + graphVisibilityState, + reloadIntervalState, + studyDetailsState, + studySummariesState, +} from "../state" +import { usePreferenceDialog } from "./PreferenceDialog" interface ParamTypes { studyId: string @@ -56,19 +54,6 @@ const useStudySummaryValue = (studyId: number): StudySummary | null => { return studySummaries.find((s) => s.study_id == studyId) || null } -interface Preference { - graphHistoryChecked: boolean - graphParetoFrontChecked: boolean - graphParallelCoordinateChecked: boolean - graphIntermediateValuesChecked: boolean - graphEdfChecked: boolean - graphContourChecked: boolean - graphHyperparameterImportancesChecked: boolean - graphSliceChecked: boolean - noteEditorChecked: boolean - reloadInterval: number -} - export const StudyDetail: FC<{ toggleColorMode: () => void }> = ({ toggleColorMode }) => { @@ -79,197 +64,32 @@ export const StudyDetail: FC<{ const studyDetail = useStudyDetailValue(studyIdNumber) const studySummary = useStudySummaryValue(studyIdNumber) const directions = studyDetail?.directions || studySummary?.directions || null - - const [preferences, setPreferences] = useState({ - graphHistoryChecked: true, - graphParetoFrontChecked: true, - graphParallelCoordinateChecked: true, - graphIntermediateValuesChecked: true, - graphEdfChecked: true, - graphContourChecked: true, - graphHyperparameterImportancesChecked: true, - graphSliceChecked: true, - noteEditorChecked: true, - reloadInterval: 10, - }) - useEffect(() => { - const localStoragePreferences = localStorage.getItem("savedPref") - if (localStoragePreferences !== null) { - const merged = { ...preferences, ...JSON.parse(localStoragePreferences) } - setPreferences(merged) - } - }, []) - useEffect(() => { - localStorage.setItem("savedPref", JSON.stringify(preferences)) - }, [preferences]) - - const [prefOpen, setPrefOpen] = useState(false) - const handleClickOpen = () => { - setPrefOpen(true) - } - const handleClose = () => { - setPrefOpen(false) - } - const handlePreferenceOnChange = ( - event: React.ChangeEvent - ) => { - setPreferences({ - ...preferences, - [event.target.name]: event.target.checked, - }) - } + const graphVisibility = useRecoilValue(graphVisibilityState) + const reloadInterval = useRecoilValue(reloadIntervalState) + const [openPreferenceDialog, renderPreferenceDialog] = + usePreferenceDialog(studyDetail) useEffect(() => { action.updateStudyDetail(studyIdNumber) }, []) useEffect(() => { - if (preferences.reloadInterval < 0) { + if (reloadInterval < 0) { return } const intervalId = setInterval(function () { action.updateStudyDetail(studyIdNumber) - }, preferences.reloadInterval * 1000) + }, reloadInterval * 1000) return () => clearInterval(intervalId) - }, [preferences.reloadInterval, studyDetail]) - // TODO(chenghuzi): Reduce the number of calls to setInterval and clearInterval. + }, [reloadInterval, studyDetail]) + // 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 : [] - const PreferenceDialog = () => { - return ( - - - Preferences - - - - - - Charts - - - } - label="History" - /> - - } - label="Pareto Front" - /> - - } - label="Parallel Coordinate" - /> - 1 || - !studyDetail.has_intermediate_values) - } - control={ - - } - label="Intermediate Values" - /> - - } - label="EDF" - /> - - } - label="Contour" - /> - - } - label="Hyperparameter Importances" - /> - - } - label="Slice" - /> - Editor - - } - label="NoteEditor" - /> - - - - ) - } - return (
- + {renderPreferenceDialog()} {APP_BAR_TITLE} - + { toggleColorMode() @@ -304,7 +121,9 @@ export const StudyDetail: FC<{ { + openPreferenceDialog(true) + }} title="Open preference panel" > @@ -343,7 +162,7 @@ export const StudyDetail: FC<{ > {title} - {preferences.graphHistoryChecked ? ( + {graphVisibility.history ? ( 1 && - preferences.graphParetoFrontChecked ? ( + graphVisibility.paretoFront ? ( ) : null} - {preferences.graphParallelCoordinateChecked ? ( + {graphVisibility.parallelCoordinate ? ( @@ -375,14 +194,14 @@ export const StudyDetail: FC<{ {studyDetail !== null && studyDetail.directions.length == 1 && studyDetail.has_intermediate_values && - preferences.graphIntermediateValuesChecked ? ( + graphVisibility.intermediateValues ? ( ) : null} - {preferences.graphEdfChecked ? ( + {graphVisibility.edf ? ( @@ -390,7 +209,7 @@ export const StudyDetail: FC<{ ) : null} - {preferences.graphContourChecked ? ( + {graphVisibility.contour ? ( @@ -398,7 +217,7 @@ export const StudyDetail: FC<{ ) : null} - {preferences.graphHyperparameterImportancesChecked ? ( + {graphVisibility.importances ? ( ) : null} - {studyDetail !== null && preferences.graphSliceChecked ? ( + {studyDetail !== null && graphVisibility.slice ? ( @@ -419,7 +238,7 @@ export const StudyDetail: FC<{ - {studyDetail !== null && preferences.noteEditorChecked ? ( + {studyDetail !== null ? ( ) : null}
@@ -428,10 +247,10 @@ export const StudyDetail: FC<{ ) } -const ReloadIntervalSelect: FC<{ - preferences: Preference - setPreferences: (p: Preference) => void -}> = ({ preferences, setPreferences }) => { +const ReloadIntervalSelect: FC = () => { + const [reloadInterval, updateReloadInterval] = + useRecoilState(reloadIntervalState) + const Wrapper = styled("div")(({ theme }) => ({ position: "relative", borderRadius: theme.shape.borderRadius, @@ -492,12 +311,9 @@ const ReloadIntervalSelect: FC<{