Merge pull request #172 from optuna/dark-theme

Dark Mode Support
This commit is contained in:
Masashi Shibata
2022-03-05 17:22:55 +09:00
committed by GitHub
12 changed files with 156 additions and 71 deletions
+1
View File
@@ -0,0 +1 @@
optuna_dashboard/static/components/PlotlyDarkMode.ts
+45 -12
View File
@@ -1,25 +1,58 @@
import React, { FC } from "react"
import React, { FC, useMemo, useState, useEffect } from "react"
import { RecoilRoot } from "recoil"
import { BrowserRouter as Router, Switch, Route } from "react-router-dom"
import { SnackbarProvider } from "notistack"
import { createTheme, useMediaQuery, ThemeProvider, Box } from "@mui/material"
import { StudyDetail } from "./StudyDetail"
import { StudyList } from "./StudyList"
export const App: FC = () => {
const prefersDarkMode = useMediaQuery("(prefers-color-scheme: dark)")
const [colorMode, setColorMode] = useState<"light" | "dark">("light")
useEffect(() => {
setColorMode(prefersDarkMode ? "dark" : "light")
}, [prefersDarkMode])
const theme = useMemo(
() =>
createTheme({
palette: {
mode: colorMode,
},
}),
[colorMode]
)
const toggleColorMode = () => {
setColorMode(colorMode === "dark" ? "light" : "dark")
}
return (
<RecoilRoot>
<SnackbarProvider maxSnack={3}>
<Router>
<Switch>
<Route
path={URL_PREFIX + "/studies/:studyId"}
children={<StudyDetail />}
/>
<Route path={URL_PREFIX + "/"} children={<StudyList />} />
</Switch>
</Router>
</SnackbarProvider>
<ThemeProvider theme={theme}>
<Box
sx={{
backgroundColor: colorMode === "dark" ? "#121212" : "#ffffff",
width: "100%",
minHeight: "100vh",
paddingBottom: theme.spacing(2),
}}
>
<SnackbarProvider maxSnack={3}>
<Router>
<Switch>
<Route
path={URL_PREFIX + "/studies/:studyId"}
children={<StudyDetail toggleColorMode={toggleColorMode} />}
/>
<Route
path={URL_PREFIX + "/"}
children={<StudyList toggleColorMode={toggleColorMode} />}
/>
</Switch>
</Router>
</SnackbarProvider>
</Box>
</ThemeProvider>
</RecoilRoot>
)
}
@@ -10,6 +10,7 @@ import {
SelectChangeEvent,
useTheme,
} from "@mui/material"
import { plotlyDarkTemplate } from "./PlotlyDarkMode"
const plotDomId = "graph-edf"
@@ -25,9 +26,9 @@ export const Edf: FC<{
useEffect(() => {
if (study != null) {
plotEdf(study, objectiveId)
plotEdf(study, objectiveId, theme.palette.mode)
}
}, [study, objectiveId])
}, [study, objectiveId, theme.palette.mode])
return (
<Grid container direction="row">
<Grid item xs={3}>
@@ -63,7 +64,7 @@ export const Edf: FC<{
)
}
const plotEdf = (study: StudyDetail, objectiveId: number) => {
const plotEdf = (study: StudyDetail, objectiveId: number, mode: string) => {
if (document.getElementById(plotDomId) === null) {
return
}
@@ -95,6 +96,7 @@ const plotEdf = (study: StudyDetail, objectiveId: number) => {
r: 50,
b: 50,
},
template: mode === "dark" ? plotlyDarkTemplate : {},
}
const values = completedTrials.map((t) => target(t))
@@ -15,6 +15,7 @@ import {
SelectChangeEvent,
useTheme,
} from "@mui/material"
import { plotlyDarkTemplate } from "./PlotlyDarkMode"
const plotDomId = "graph-history"
@@ -59,7 +60,8 @@ export const GraphHistory: FC<{
xAxis,
logScale,
filterCompleteTrial,
filterPrunedTrial
filterPrunedTrial,
theme.palette.mode
)
}
}, [
@@ -69,6 +71,7 @@ export const GraphHistory: FC<{
xAxis,
filterPrunedTrial,
filterCompleteTrial,
theme.palette.mode,
])
return (
@@ -171,7 +174,8 @@ const plotHistory = (
xAxis: string,
logScale: boolean,
filterCompleteTrial: boolean,
filterPrunedTrial: boolean
filterPrunedTrial: boolean,
mode: string
) => {
if (document.getElementById(plotDomId) === null) {
return
@@ -191,6 +195,7 @@ const plotHistory = (
type: xAxis === "number" ? "linear" : "date",
},
showlegend: false,
template: mode === "dark" ? plotlyDarkTemplate : {},
}
let filteredTrials = study.trials.filter(
@@ -12,6 +12,7 @@ import {
} from "@mui/material"
import { getParamImportances } from "../apiClient"
import { plotlyDarkTemplate } from "./PlotlyDarkMode"
const plotDomId = "graph-hyperparameter-importances"
// To match colors used by plot_param_importances in optuna.
@@ -59,13 +60,13 @@ export const GraphHyperparameterImportances: FC<{
studyId,
objectiveId
)
plotParamImportances(paramsImportanceData)
plotParamImportances(paramsImportanceData, theme.palette.mode)
}
if (numOfTrials > 0) {
fetchAndPlotParamImportances(studyId, objectiveId)
}
}, [numOfTrials, objectiveId])
}, [numOfTrials, objectiveId, theme.palette.mode])
return (
<Grid container direction="row">
@@ -102,7 +103,10 @@ export const GraphHyperparameterImportances: FC<{
)
}
const plotParamImportances = (paramsImportanceData: ParamImportances) => {
const plotParamImportances = (
paramsImportanceData: ParamImportances,
mode: string
) => {
if (document.getElementById(plotDomId) === null) {
return
}
@@ -131,6 +135,7 @@ const plotParamImportances = (paramsImportanceData: ParamImportances) => {
b: 50,
},
showlegend: false,
template: mode === "dark" ? plotlyDarkTemplate : {},
}
const plotData: Partial<plotly.PlotData>[] = [
@@ -1,15 +1,17 @@
import * as plotly from "plotly.js-dist"
import React, { FC, useEffect } from "react"
import { Grid, Typography } from "@mui/material"
import { Grid, Typography, useTheme } from "@mui/material"
import { plotlyDarkTemplate } from "./PlotlyDarkMode"
const plotDomId = "graph-intermediate-values"
export const GraphIntermediateValues: FC<{
trials: Trial[]
}> = ({ trials = [] }) => {
const theme = useTheme()
useEffect(() => {
plotIntermediateValue(trials)
}, [trials])
plotIntermediateValue(trials, theme.palette.mode)
}, [trials, theme.palette.mode])
return (
<Grid container direction="row">
<Grid item xs={3}>
@@ -27,7 +29,7 @@ export const GraphIntermediateValues: FC<{
)
}
const plotIntermediateValue = (trials: Trial[]) => {
const plotIntermediateValue = (trials: Trial[], mode: string) => {
if (document.getElementById(plotDomId) === null) {
return
}
@@ -39,6 +41,7 @@ const plotIntermediateValue = (trials: Trial[]) => {
r: 50,
b: 0,
},
template: mode === "dark" ? plotlyDarkTemplate : {},
}
if (trials.length === 0) {
plotly.react(plotDomId, [], layout)
@@ -10,6 +10,7 @@ import {
SelectChangeEvent,
useTheme,
} from "@mui/material"
import { plotlyDarkTemplate } from "./PlotlyDarkMode"
const plotDomId = "graph-parallel-coordinate"
@@ -25,16 +26,16 @@ export const GraphParallelCoordinate: FC<{
useEffect(() => {
if (study !== null) {
plotCoordinate(study, objectiveId)
plotCoordinate(study, objectiveId, theme.palette.mode)
}
}, [study, objectiveId])
}, [study, objectiveId, theme.palette.mode])
return (
<Grid container direction="row">
<Grid item xs={3}>
<Grid container direction="column">
<Typography variant="h6" sx={{ margin: "1em 0" }}>
Parallel coordinate
Parallel Coordinate
</Typography>
{study !== null && study.directions.length !== 1 ? (
<FormControl
@@ -64,7 +65,11 @@ export const GraphParallelCoordinate: FC<{
)
}
const plotCoordinate = (study: StudyDetail, objectiveId: number) => {
const plotCoordinate = (
study: StudyDetail,
objectiveId: number,
mode: string
) => {
if (document.getElementById(plotDomId) === null) {
return
}
@@ -76,6 +81,7 @@ const plotCoordinate = (study: StudyDetail, objectiveId: number) => {
r: 50,
b: 0,
},
template: mode === "dark" ? plotlyDarkTemplate : {},
}
if (study.trials.length === 0) {
@@ -10,6 +10,7 @@ import {
SelectChangeEvent,
useTheme,
} from "@mui/material"
import { plotlyDarkTemplate } from "./PlotlyDarkMode"
const plotDomId = "graph-pareto-front"
@@ -30,9 +31,9 @@ export const GraphParetoFront: FC<{
useEffect(() => {
if (study != null) {
plotParetoFront(study, objectiveXId, objectiveYId)
plotParetoFront(study, objectiveXId, objectiveYId, theme.palette.mode)
}
}, [study, objectiveXId, objectiveYId])
}, [study, objectiveXId, objectiveYId, theme.palette.mode])
return (
<Grid container direction="row">
@@ -87,7 +88,8 @@ export const GraphParetoFront: FC<{
const plotParetoFront = (
study: StudyDetail,
objectiveXId: number,
objectiveYId: number
objectiveYId: number,
mode: string
) => {
if (document.getElementById(plotDomId) === null) {
return
@@ -100,6 +102,7 @@ const plotParetoFront = (
r: 50,
b: 0,
},
template: mode === "dark" ? plotlyDarkTemplate : {},
}
const trials: Trial[] = study ? study.trials : []
@@ -12,6 +12,7 @@ import {
SelectChangeEvent,
useTheme,
} from "@mui/material"
import { plotlyDarkTemplate } from "./PlotlyDarkMode"
const plotDomId = "graph-slice"
@@ -38,8 +39,15 @@ export const GraphSlice: FC<{
}
useEffect(() => {
plotSlice(trials, objectiveId, selected, logXScale, logYScale)
}, [trials, objectiveId, selected, logXScale, logYScale])
plotSlice(
trials,
objectiveId,
selected,
logXScale,
logYScale,
theme.palette.mode
)
}, [trials, objectiveId, selected, logXScale, logYScale, theme.palette.mode])
const handleObjectiveChange = (event: SelectChangeEvent<number>) => {
setObjectiveId(event.target.value as number)
@@ -126,7 +134,8 @@ const plotSlice = (
objectiveId: number,
selected: string | null,
logXScale: boolean,
logYScale: boolean
logYScale: boolean,
mode: string
) => {
if (document.getElementById(plotDomId) === null) {
return
@@ -142,27 +151,17 @@ const plotSlice = (
xaxis: {
title: selected || "",
type: logXScale ? "log" : "linear",
zerolinecolor: "#f2f5fa",
zerolinewidth: 1.5,
linecolor: "#f2f5fa",
linewidth: 5,
gridcolor: "#f2f5fa",
gridwidth: 1,
automargin: true,
},
yaxis: {
title: "Objective Values",
type: logYScale ? "log" : "linear",
zerolinecolor: "#f2f5fa",
zerolinewidth: 2,
linecolor: "#f2f5fa",
linewidth: 5,
gridcolor: "#f2f5fa",
gridwidth: 1,
automargin: true,
},
plot_bgcolor: "#E5ecf6",
showlegend: false,
template: mode === "dark" ? plotlyDarkTemplate : {},
}
const filteredTrials = trials.filter(
@@ -195,23 +194,12 @@ const plotSlice = (
x: valuesNum,
y: objectiveValues,
mode: "markers",
marker: {
color: "#185799",
},
},
]
layout["xaxis"] = {
title: selected,
type: logXScale ? "log" : "linear",
zerolinecolor: "#f2f5fa",
zerolinewidth: 1.5,
linecolor: "#f2f5fa",
linewidth: 5,
gridcolor: "#f2f5fa",
gridwidth: 1,
tickfont: {
color: "#000000",
},
automargin: true, // Otherwise the label is outside of the plot
}
plotly.react(plotDomId, trace, layout)
@@ -228,23 +216,12 @@ const plotSlice = (
x: valuesCategorical,
y: objectiveValues,
mode: "markers",
marker: {
color: "#185799",
},
},
]
layout["xaxis"] = {
title: selected,
type: logXScale ? "log" : "linear",
zerolinecolor: "#f2f5fa",
zerolinewidth: 1.5,
linecolor: "#f2f5fa",
linewidth: 5,
gridcolor: "#f2f5fa",
gridwidth: 1,
tickfont: {
color: "#000000",
},
tickvals: tickvals,
ticktext: vocabArr,
automargin: true, // Otherwise the label is outside of the plot
File diff suppressed because one or more lines are too long
@@ -26,6 +26,8 @@ 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"
import { DataGridColumn, DataGrid } from "./DataGrid"
import { GraphParallelCoordinate } from "./GraphParallelCoordinate"
@@ -51,7 +53,9 @@ export const useStudyDetailValue = (studyId: number): StudyDetail | null => {
return studyDetails[studyId] || null
}
export const StudyDetail: FC = () => {
export const StudyDetail: FC<{
toggleColorMode: () => void
}> = ({ toggleColorMode }) => {
const theme = useTheme()
const action = actionCreator()
const { studyId } = useParams<ParamTypes>()
@@ -251,6 +255,18 @@ export const StudyDetail: FC = () => {
<Toolbar>
<Typography variant="h6">{APP_BAR_TITLE}</Typography>
<Box sx={{ flexGrow: 1 }} />
<IconButton
onClick={() => {
toggleColorMode()
}}
color="inherit"
>
{theme.palette.mode === "dark" ? (
<Brightness7Icon />
) : (
<Brightness4Icon />
)}
</IconButton>
<IconButton color="inherit" onClick={handleClickOpen}>
<Settings />
</IconButton>
@@ -1,4 +1,4 @@
import React, { FC, useEffect } from "react"
import React, { FC, useEffect, useMemo } from "react"
import { useRecoilValue } from "recoil"
import { Link } from "react-router-dom"
import {
@@ -31,8 +31,12 @@ import { Add, AddBox, Delete, Refresh, Remove } from "@mui/icons-material"
import { actionCreator } from "../action"
import { DataGrid, DataGridColumn } from "./DataGrid"
import { studySummariesState } from "../state"
import Brightness7Icon from "@mui/icons-material/Brightness7"
import Brightness4Icon from "@mui/icons-material/Brightness4"
export const StudyList: FC = () => {
export const StudyList: FC<{
toggleColorMode: () => void
}> = ({ toggleColorMode }) => {
const theme = useTheme()
const [newStudySelectionAnchorEl, setNewStudySelectionAnchorEl] =
@@ -55,6 +59,13 @@ export const StudyList: FC = () => {
const [directions, setDirections] = React.useState<StudyDirection[]>([
"minimize",
])
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)
@@ -78,7 +89,10 @@ export const StudyList: FC = () => {
label: "Name",
sortable: true,
toCellValue: (i) => (
<Link to={`${URL_PREFIX}/studies/${studies[i].study_id}`}>
<Link
to={`${URL_PREFIX}/studies/${studies[i].study_id}`}
style={{ color: linkColor }}
>
{studies[i].study_name}
</Link>
),
@@ -212,6 +226,18 @@ export const StudyList: FC = () => {
<Toolbar>
<Typography variant="h6">{APP_BAR_TITLE}</Typography>
<Box sx={{ flexGrow: 1 }} />
<IconButton
onClick={() => {
toggleColorMode()
}}
color="inherit"
>
{theme.palette.mode === "dark" ? (
<Brightness7Icon />
) : (
<Brightness4Icon />
)}
</IconButton>
<IconButton
aria-controls="menu-appbar"
aria-haspopup="true"