Merge branch 'main' into fix/add-color-scale-setting-follow-up

This commit is contained in:
Daichi Kato
2024-02-14 11:12:40 +09:00
committed by GitHub
14 changed files with 326 additions and 195 deletions
-30
View File
@@ -3,7 +3,6 @@ import { useSnackbar } from "notistack"
import {
getStudyDetailAPI,
getStudySummariesAPI,
getParamImportances,
createNewStudyAPI,
deleteStudyAPI,
saveStudyNoteAPI,
@@ -25,7 +24,6 @@ import {
import {
studyDetailsState,
studySummariesState,
paramImportanceState,
isFileUploading,
artifactIsAvailable,
plotlypyIsAvailableState,
@@ -43,8 +41,6 @@ export const actionCreator = () => {
const [studyDetails, setStudyDetails] =
useRecoilState<StudyDetails>(studyDetailsState)
const setReloadInterval = useSetRecoilState<number>(reloadIntervalState)
const [paramImportance, setParamImportance] =
useRecoilState<StudyParamImportance>(paramImportanceState)
const setUploading = useSetRecoilState<boolean>(isFileUploading)
const setTrialsUpdating = useSetRecoilState(trialsUpdatingState)
const setArtifactIsAvailable = useSetRecoilState<boolean>(artifactIsAvailable)
@@ -207,15 +203,6 @@ export const actionCreator = () => {
setStudyDetailState(studyId, newStudy)
}
const setStudyParamImportanceState = (
studyId: number,
importance: ParamImportance[][]
) => {
const newVal = Object.assign({}, paramImportance)
newVal[studyId] = importance
setParamImportance(newVal)
}
const updateAPIMeta = () => {
getMetaInfoAPI().then((r) => {
setArtifactIsAvailable(r.artifact_is_available)
@@ -273,22 +260,6 @@ export const actionCreator = () => {
})
}
const updateParamImportance = (studyId: number) => {
getParamImportances(studyId)
.then((importance) => {
setStudyParamImportanceState(studyId, importance)
})
.catch((err) => {
const reason = err.response?.data.reason
enqueueSnackbar(
`Failed to load hyperparameter importance (reason=${reason})`,
{
variant: "error",
}
)
})
}
const createNewStudy = (studyName: string, directions: StudyDirection[]) => {
createNewStudyAPI(studyName, directions)
.then((study_summary) => {
@@ -714,7 +685,6 @@ export const actionCreator = () => {
updateAPIMeta,
updateStudyDetail,
updateStudySummaries,
updateParamImportance,
createNewStudy,
deleteStudy,
renameStudy,
+106 -90
View File
@@ -15,6 +15,18 @@ import {
import { CompareStudies } from "./CompareStudies"
import { StudyDetail } from "./StudyDetail"
import { StudyList } from "./StudyList"
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: false,
refetchOnMount: false,
refetchOnReconnect: false,
refetchOnWindowFocus: false,
},
},
})
export const App: FC = () => {
const prefersDarkMode = useMediaQuery("(prefers-color-scheme: dark)")
@@ -38,95 +50,99 @@ export const App: FC = () => {
}
return (
<RecoilRoot>
<ThemeProvider theme={theme}>
<CssBaseline />
<Box
sx={{
backgroundColor: colorMode === "dark" ? "#121212" : "#ffffff",
width: "100%",
minHeight: "100vh",
}}
>
<SnackbarProvider maxSnack={3}>
<Router>
<Routes>
<Route
path={URL_PREFIX + "/studies/:studyId/analytics"}
element={
<StudyDetail
toggleColorMode={toggleColorMode}
page={"analytics"}
/>
}
/>
<Route
path={URL_PREFIX + "/studies/:studyId/trials"}
element={
<StudyDetail
toggleColorMode={toggleColorMode}
page={"trialList"}
/>
}
/>
<Route
path={URL_PREFIX + "/studies/:studyId/trialTable"}
element={
<StudyDetail
toggleColorMode={toggleColorMode}
page={"trialTable"}
/>
}
/>
<Route
path={URL_PREFIX + "/studies/:studyId/note"}
element={
<StudyDetail
toggleColorMode={toggleColorMode}
page={"note"}
/>
}
/>
<Route
path={URL_PREFIX + "/studies/:studyId/graph"}
element={
<StudyDetail
toggleColorMode={toggleColorMode}
page={"graph"}
/>
}
/>
<Route
path={URL_PREFIX + "/studies/:studyId"}
element={
<StudyDetail
toggleColorMode={toggleColorMode}
page={"top"}
/>
}
/>
<Route
path={URL_PREFIX + "/studies/:studyId/preference-history"}
element={
<StudyDetail
toggleColorMode={toggleColorMode}
page={"preferenceHistory"}
/>
}
/>
<Route
path={URL_PREFIX + "/compare-studies"}
element={<CompareStudies toggleColorMode={toggleColorMode} />}
/>
<Route
path={URL_PREFIX + "/"}
element={<StudyList toggleColorMode={toggleColorMode} />}
/>
</Routes>
</Router>
</SnackbarProvider>
</Box>
</ThemeProvider>
</RecoilRoot>
<QueryClientProvider client={queryClient}>
<RecoilRoot>
<ThemeProvider theme={theme}>
<CssBaseline />
<Box
sx={{
backgroundColor: colorMode === "dark" ? "#121212" : "#ffffff",
width: "100%",
minHeight: "100vh",
}}
>
<SnackbarProvider maxSnack={3}>
<Router>
<Routes>
<Route
path={URL_PREFIX + "/studies/:studyId/analytics"}
element={
<StudyDetail
toggleColorMode={toggleColorMode}
page={"analytics"}
/>
}
/>
<Route
path={URL_PREFIX + "/studies/:studyId/trials"}
element={
<StudyDetail
toggleColorMode={toggleColorMode}
page={"trialList"}
/>
}
/>
<Route
path={URL_PREFIX + "/studies/:studyId/trialTable"}
element={
<StudyDetail
toggleColorMode={toggleColorMode}
page={"trialTable"}
/>
}
/>
<Route
path={URL_PREFIX + "/studies/:studyId/note"}
element={
<StudyDetail
toggleColorMode={toggleColorMode}
page={"note"}
/>
}
/>
<Route
path={URL_PREFIX + "/studies/:studyId/graph"}
element={
<StudyDetail
toggleColorMode={toggleColorMode}
page={"graph"}
/>
}
/>
<Route
path={URL_PREFIX + "/studies/:studyId"}
element={
<StudyDetail
toggleColorMode={toggleColorMode}
page={"top"}
/>
}
/>
<Route
path={URL_PREFIX + "/studies/:studyId/preference-history"}
element={
<StudyDetail
toggleColorMode={toggleColorMode}
page={"preferenceHistory"}
/>
}
/>
<Route
path={URL_PREFIX + "/compare-studies"}
element={
<CompareStudies toggleColorMode={toggleColorMode} />
}
/>
<Route
path={URL_PREFIX + "/"}
element={<StudyList toggleColorMode={toggleColorMode} />}
/>
</Routes>
</Router>
</SnackbarProvider>
</Box>
</ThemeProvider>
</RecoilRoot>
</QueryClientProvider>
)
}
+17 -11
View File
@@ -15,8 +15,9 @@ import blue from "@mui/material/colors/blue"
import { useMergedUnionSearchSpace } from "../searchSpace"
import { usePlotlyColorTheme } from "../state"
import { getAxisInfo } from "../graphUtil"
import { getPlotAPI, PlotType } from "../apiClient"
import { PlotType } from "../apiClient"
import { useBackendRender } from "../state"
import { usePlot } from "../hooks/usePlot"
const plotDomId = "graph-contour"
@@ -36,18 +37,23 @@ const ContourBackend: FC<{
const studyId = study?.id
const numCompletedTrials =
study?.trials.filter((t) => t.state === "Complete").length || 0
const { data, layout, error } = usePlot({
numCompletedTrials,
studyId,
plotType: PlotType.Contour,
})
useEffect(() => {
if (studyId === undefined) {
return
if (data && layout) {
plotly.react(plotDomId, data, layout)
}
getPlotAPI(studyId, PlotType.Contour)
.then(({ data, layout }) => {
plotly.react(plotDomId, data, layout)
})
.catch((err) => {
console.error(err)
})
}, [studyId, numCompletedTrials])
}, [data, layout])
useEffect(() => {
if (error) {
console.error(error)
}
}, [error])
return <Box id={plotDomId} sx={{ height: "450px" }} />
}
@@ -2,9 +2,8 @@ import * as plotly from "plotly.js-dist-min"
import React, { FC, useEffect } from "react"
import { Typography, useTheme, Box, Card, CardContent } from "@mui/material"
import { actionCreator } from "../action"
import { useParamImportanceValue, useStudyDirections } from "../state"
import { usePlotlyColorTheme } from "../state"
import { useParamImportance } from "../hooks/useParamImportance"
import { useStudyDirections, usePlotlyColorTheme } from "../state"
const plotDomId = "graph-hyperparameter-importances"
@@ -16,10 +15,12 @@ export const GraphHyperparameterImportance: FC<{
const theme = useTheme()
const colorTheme = usePlotlyColorTheme(theme.palette.mode)
const action = actionCreator()
const importances = useParamImportanceValue(studyId)
const numCompletedTrials =
study?.trials.filter((t) => t.state === "Complete").length || 0
const { importances } = useParamImportance({
numCompletedTrials,
studyId,
})
const nObjectives = useStudyDirections(studyId)?.length
const objectiveNames: string[] =
study?.objective_names ||
@@ -27,11 +28,7 @@ export const GraphHyperparameterImportance: FC<{
[]
useEffect(() => {
action.updateParamImportance(studyId)
}, [numCompletedTrials])
useEffect(() => {
if (importances !== null && nObjectives === importances.length) {
if (importances !== undefined && nObjectives === importances.length) {
plotParamImportance(importances, objectiveNames, colorTheme)
}
}, [nObjectives, importances, colorTheme])
@@ -17,8 +17,9 @@ import {
useParamTargets,
} from "../trialFilter"
import { useMergedUnionSearchSpace } from "../searchSpace"
import { getPlotAPI, PlotType } from "../apiClient"
import { PlotType } from "../apiClient"
import { useBackendRender } from "../state"
import { usePlot } from "../hooks/usePlot"
const plotDomId = "graph-parallel-coordinate"
@@ -102,18 +103,24 @@ const GraphParallelCoordinateBackend: FC<{
const studyId = study?.id
const numCompletedTrials =
study?.trials.filter((t) => t.state === "Complete").length || 0
const { data, layout, error } = usePlot({
numCompletedTrials,
studyId,
plotType: PlotType.ParallelCoordinate,
})
useEffect(() => {
if (studyId === undefined) {
return
if (data && layout) {
plotly.react(plotDomId, data, layout)
}
getPlotAPI(studyId, PlotType.ParallelCoordinate)
.then(({ data, layout }) => {
plotly.react(plotDomId, data, layout)
})
.catch((err) => {
console.error(err)
})
}, [studyId, numCompletedTrials])
}, [data, layout])
useEffect(() => {
if (error) {
console.error(error)
}
}, [error])
return <Box id={plotDomId} sx={{ height: "450px" }} />
}
+18 -11
View File
@@ -13,8 +13,9 @@ import {
} from "@mui/material"
import { getAxisInfo, makeHovertext } from "../graphUtil"
import { useMergedUnionSearchSpace } from "../searchSpace"
import { PlotType } from "../apiClient"
import { usePlotlyColorTheme, useBackendRender } from "../state"
import { getPlotAPI, PlotType } from "../apiClient"
import { usePlot } from "../hooks/usePlot"
const plotDomId = "graph-rank"
@@ -46,18 +47,24 @@ const GraphRankBackend: FC<{
const studyId = study?.id
const numCompletedTrials =
study?.trials.filter((t) => t.state === "Complete").length || 0
const { data, layout, error } = usePlot({
numCompletedTrials,
studyId,
plotType: PlotType.Rank,
})
useEffect(() => {
if (studyId === undefined) {
return
if (data && layout) {
plotly.react(plotDomId, data, layout)
}
getPlotAPI(studyId, PlotType.Rank)
.then(({ data, layout }) => {
plotly.react(plotDomId, data, layout)
})
.catch((err) => {
console.error(err)
})
}, [studyId, numCompletedTrials])
}, [data, layout])
useEffect(() => {
if (error) {
console.error(error)
}
}, [error])
return <Box id={plotDomId} sx={{ height: "450px" }} />
}
+18 -11
View File
@@ -19,8 +19,9 @@ import {
useParamTargets,
} from "../trialFilter"
import { useMergedUnionSearchSpace } from "../searchSpace"
import { PlotType } from "../apiClient"
import { usePlotlyColorTheme, useBackendRender } from "../state"
import { getPlotAPI, PlotType } from "../apiClient"
import { usePlot } from "../hooks/usePlot"
const plotDomId = "graph-slice"
@@ -47,18 +48,24 @@ const GraphSliceBackend: FC<{
const studyId = study?.id
const numCompletedTrials =
study?.trials.filter((t) => t.state === "Complete").length || 0
const { data, layout, error } = usePlot({
numCompletedTrials,
studyId,
plotType: PlotType.Slice,
})
useEffect(() => {
if (studyId === undefined) {
return
if (data && layout) {
plotly.react(plotDomId, data, layout)
}
getPlotAPI(studyId, PlotType.Slice)
.then(({ data, layout }) => {
plotly.react(plotDomId, data, layout)
})
.catch((err) => {
console.error(err)
})
}, [studyId, numCompletedTrials])
}, [data, layout])
useEffect(() => {
if (error) {
console.error(error)
}
}, [error])
return <Box id={plotDomId} sx={{ height: "450px" }} />
}
+17 -1
View File
@@ -2,6 +2,7 @@ import React from "react"
import {
Typography,
Select,
Switch,
MenuItem,
SelectChangeEvent,
Stack,
@@ -10,7 +11,7 @@ import {
} from "@mui/material"
import ClearIcon from "@mui/icons-material/Clear"
import { useRecoilState } from "recoil"
import { plotlyColorThemeState } from "../state"
import { plotlyColorThemeState, plotBackendRenderingState } from "../state"
interface SettingsProps {
handleClose: () => void
@@ -32,6 +33,12 @@ export const Settings = ({ handleClose }: SettingsProps) => {
setPlotlyColorTheme((prev) => ({ ...prev, light }))
}
const [plotBackendRendering, setPlotBackendRendering] =
useRecoilState<boolean>(plotBackendRenderingState)
const handleBackendRenderingChange = () => {
setPlotBackendRendering(!plotBackendRendering)
}
return (
<Stack
spacing={4}
@@ -117,6 +124,15 @@ export const Settings = ({ handleClose }: SettingsProps) => {
</Select>
</Stack>
)}
<Typography variant="h6" color="textSecondary">
Use Plotlypy
</Typography>
<Switch
checked={plotBackendRendering}
onChange={handleBackendRenderingChange}
value="enable"
/>
</Stack>
</Stack>
)
@@ -0,0 +1,40 @@
import { useEffect } from "react"
import { useSnackbar } from "notistack"
import { getParamImportances } from "../../ts/apiClient"
import { useQuery } from "@tanstack/react-query"
import { AxiosError } from "axios"
export const useParamImportance = ({
numCompletedTrials,
studyId,
}: { numCompletedTrials: number; studyId: number }) => {
const { enqueueSnackbar } = useSnackbar()
const { data, isLoading, error } = useQuery<
ParamImportance[][],
AxiosError<{ reason: string }>
>({
queryKey: ["paramImportance", studyId, numCompletedTrials],
queryFn: () => getParamImportances(studyId),
staleTime: Infinity,
gcTime: 30 * 60 * 1000, // 30 minutes
})
useEffect(() => {
if (error) {
const reason = error.response?.data.reason
enqueueSnackbar(
`Failed to load hyperparameter importance (reason=${reason})`,
{
variant: "error",
}
)
}
}, [error])
return {
importances: data,
isLoading,
error,
}
}
+37
View File
@@ -0,0 +1,37 @@
import * as plotly from "plotly.js-dist-min"
import { useQuery } from "@tanstack/react-query"
import { AxiosError } from "axios"
import { PlotType, getPlotAPI } from "../apiClient"
export const usePlot = ({
numCompletedTrials,
studyId,
plotType,
}: {
numCompletedTrials: number
studyId: number | undefined
plotType: PlotType
}) => {
const { data, isLoading, error } = useQuery<
{ data: plotly.Data[]; layout: plotly.Layout },
AxiosError
>({
enabled: studyId !== undefined,
queryKey: ["plot", studyId, numCompletedTrials, plotType],
queryFn: () => {
if (studyId === undefined) {
return Promise.reject(new Error("Invalid studyId"))
}
return getPlotAPI(studyId, plotType)
},
staleTime: Infinity,
gcTime: 30 * 60 * 1000, // 30 minutes
})
return {
data: data?.data,
layout: data?.layout,
isLoading,
error,
}
}
+9 -16
View File
@@ -3,7 +3,6 @@ import {
LightColorTemplates,
DarkColorTemplates,
} from "./components/PlotlyColorTemplates"
import { useQuery } from "./urlQuery"
export const studySummariesState = atom<StudySummary[]>({
key: "studySummaries",
@@ -22,11 +21,6 @@ export const trialsUpdatingState = atom<{
default: {},
})
export const paramImportanceState = atom<StudyParamImportance>({
key: "paramImportance",
default: {},
})
// TODO(c-bata): Consider representing the state as boolean.
export const reloadIntervalState = atom<number>({
key: "reloadInterval",
@@ -56,6 +50,11 @@ export const plotlyColorThemeState = atom<PlotlyColorTheme>({
},
})
export const plotBackendRenderingState = atom<boolean>({
key: "plotBackendRendering",
default: false,
})
export const plotlypyIsAvailableState = atom<boolean>({
key: "plotlypyIsAvailable",
default: true,
@@ -81,14 +80,6 @@ export const useTrialUpdatingValue = (trialId: number): boolean => {
return updating[trialId] || false
}
export const useParamImportanceValue = (
studyId: number
): ParamImportance[][] | null => {
const studyParamImportance =
useRecoilValue<StudyParamImportance>(paramImportanceState)
return studyParamImportance[studyId] || null
}
export const useStudyDirections = (
studyId: number
): StudyDirection[] | null => {
@@ -128,10 +119,12 @@ export const usePlotlyColorTheme = (mode: string): Partial<Plotly.Template> => {
}
export const useBackendRender = (): boolean => {
const query = useQuery()
const plotBackendRendering = useRecoilValue<boolean>(
plotBackendRenderingState
)
const plotlypyIsAvailable = useRecoilValue<boolean>(plotlypyIsAvailableState)
if (query.get("plotlypy_rendering") === "true") {
if (plotBackendRendering) {
if (plotlypyIsAvailable) {
return true
}
-4
View File
@@ -226,10 +226,6 @@ type StudyDetails = {
[study_id: string]: StudyDetail
}
type StudyParamImportance = {
[study_id: string]: ParamImportance[][]
}
type PreferenceHistory = {
id: string
candidates: number[]
+38
View File
@@ -16,6 +16,7 @@
"@mui/material": "^5.15.6",
"@react-three/drei": "^9.96.4",
"@react-three/fiber": "^8.15.15",
"@tanstack/react-query": "^5.18.1",
"@types/three": "^0.160.0",
"axios": "^1.6.7",
"elkjs": "^0.9.1",
@@ -4247,6 +4248,30 @@
"@sinonjs/commons": "^3.0.0"
}
},
"node_modules/@tanstack/query-core": {
"version": "5.18.1",
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.18.1.tgz",
"integrity": "sha512-fYhrG7bHgSNbnkIJF2R4VUXb4lF7EBiQjKkDc5wOlB7usdQOIN4LxxHpDxyE3qjqIst1WBGvDtL48T0sHJGKCw==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@tanstack/react-query": {
"version": "5.18.1",
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.18.1.tgz",
"integrity": "sha512-PdI07BbsahZ+04PxSuDQsQvBWe008eWFk/YYWzt8fvzt2sALUM0TpAJa/DFpqa7+SSo7j1EQR6Jx6znXNHyaXw==",
"dependencies": {
"@tanstack/query-core": "5.18.1"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
"react": "^18.0.0"
}
},
"node_modules/@testing-library/dom": {
"version": "9.3.4",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.4.tgz",
@@ -18499,6 +18524,19 @@
"@sinonjs/commons": "^3.0.0"
}
},
"@tanstack/query-core": {
"version": "5.18.1",
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.18.1.tgz",
"integrity": "sha512-fYhrG7bHgSNbnkIJF2R4VUXb4lF7EBiQjKkDc5wOlB7usdQOIN4LxxHpDxyE3qjqIst1WBGvDtL48T0sHJGKCw=="
},
"@tanstack/react-query": {
"version": "5.18.1",
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.18.1.tgz",
"integrity": "sha512-PdI07BbsahZ+04PxSuDQsQvBWe008eWFk/YYWzt8fvzt2sALUM0TpAJa/DFpqa7+SSo7j1EQR6Jx6znXNHyaXw==",
"requires": {
"@tanstack/query-core": "5.18.1"
}
},
"@testing-library/dom": {
"version": "9.3.4",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.4.tgz",
+1
View File
@@ -25,6 +25,7 @@
"@mui/material": "^5.15.6",
"@react-three/drei": "^9.96.4",
"@react-three/fiber": "^8.15.15",
"@tanstack/react-query": "^5.18.1",
"@types/three": "^0.160.0",
"axios": "^1.6.7",
"elkjs": "^0.9.1",