diff --git a/optuna_dashboard/_app.py b/optuna_dashboard/_app.py index 34ac83d3..dfa7338c 100644 --- a/optuna_dashboard/_app.py +++ b/optuna_dashboard/_app.py @@ -271,6 +271,18 @@ def create_app( ) if plot_type == "contour": fig = optuna.visualization.plot_contour(study) + elif plot_type == "slice": + fig = optuna.visualization.plot_slice(study) + # Note: Optuna's implementation forces a minimum width. + # We override it to prevent the figure from going beyond the screen width. + # https://github.com/optuna/optuna/blob/2abd0ae81eaf3683ce1dd580429904c8a705300d/optuna/visualization/_slice.py#L237-L239 + fig.update_layout(width=None) + elif plot_type == "parallel_coordinate": + fig = optuna.visualization.plot_parallel_coordinate(study) + elif plot_type == "rank": + fig = optuna.visualization.plot_rank(study) + elif plot_type == "edf": + fig = optuna.visualization.plot_edf(study) else: response.status = 404 # Not found return {"reason": f"plot_type={plot_type} is not supported."} diff --git a/optuna_dashboard/ts/apiClient.ts b/optuna_dashboard/ts/apiClient.ts index 3798f602..46fe3d0e 100644 --- a/optuna_dashboard/ts/apiClient.ts +++ b/optuna_dashboard/ts/apiClient.ts @@ -448,6 +448,10 @@ type PlotResponse = { } export enum PlotType { Contour = "contour", + Slice = "slice", + ParallelCoordinate = "parallel_coordinate", + Rank = "rank", + EDF = "edf", } export const getPlotAPI = ( studyId: number, diff --git a/optuna_dashboard/ts/components/GraphContour.tsx b/optuna_dashboard/ts/components/GraphContour.tsx index 2c660a89..7f59e718 100644 --- a/optuna_dashboard/ts/components/GraphContour.tsx +++ b/optuna_dashboard/ts/components/GraphContour.tsx @@ -15,27 +15,16 @@ import blue from "@mui/material/colors/blue" import { plotlyDarkTemplate } from "./PlotlyDarkMode" import { useMergedUnionSearchSpace } from "../searchSpace" import { getAxisInfo } from "../graphUtil" -import { useQuery } from "../urlQuery" import { getPlotAPI, PlotType } from "../apiClient" -import { useRecoilValue } from "recoil" -import { plotlypyIsAvailableState } from "../state" +import { useBackendRender } from "../state" const plotDomId = "graph-contour" export const Contour: FC<{ study: StudyDetail | null }> = ({ study = null }) => { - const query = useQuery() - const plotlypyIsAvailable = useRecoilValue(plotlypyIsAvailableState) - if (query.get("plotlypy_rendering") === "true") { - if (plotlypyIsAvailable) { - return - } else { - console.warn( - "Use frontend rendering because plotlypy is specified but not available." - ) - return - } + if (useBackendRender()) { + return } else { return } diff --git a/optuna_dashboard/ts/components/GraphParallelCoordinate.tsx b/optuna_dashboard/ts/components/GraphParallelCoordinate.tsx index b991ef94..3a227237 100644 --- a/optuna_dashboard/ts/components/GraphParallelCoordinate.tsx +++ b/optuna_dashboard/ts/components/GraphParallelCoordinate.tsx @@ -17,6 +17,8 @@ import { useParamTargets, } from "../trialFilter" import { useMergedUnionSearchSpace } from "../searchSpace" +import { getPlotAPI, PlotType } from "../apiClient" +import { useBackendRender } from "../state" const plotDomId = "graph-parallel-coordinate" @@ -86,6 +88,35 @@ const useTargets = ( export const GraphParallelCoordinate: FC<{ study: StudyDetail | null +}> = ({ study = null }) => { + if (useBackendRender()) { + return + } else { + return + } +} + +const GraphParallelCoordinateBackend: FC<{ + study: StudyDetail | null +}> = ({ study = null }) => { + const studyId = study?.id + useEffect(() => { + if (studyId === undefined) { + return + } + getPlotAPI(studyId, PlotType.ParallelCoordinate) + .then(({ data, layout }) => { + plotly.react(plotDomId, data, layout) + }) + .catch((err) => { + console.error(err) + }) + }, [studyId]) + return +} + +const GraphParallelCoordinateFrontend: FC<{ + study: StudyDetail | null }> = ({ study = null }) => { const theme = useTheme() const [targets, searchSpace, renderCheckBoxes] = useTargets(study) diff --git a/optuna_dashboard/ts/components/GraphRank.tsx b/optuna_dashboard/ts/components/GraphRank.tsx index 6daaf10a..3ee4f9df 100644 --- a/optuna_dashboard/ts/components/GraphRank.tsx +++ b/optuna_dashboard/ts/components/GraphRank.tsx @@ -14,6 +14,8 @@ import { import { plotlyDarkTemplate } from "./PlotlyDarkMode" import { getAxisInfo, makeHovertext } from "../graphUtil" import { useMergedUnionSearchSpace } from "../searchSpace" +import { getPlotAPI, PlotType } from "../apiClient" +import { useBackendRender } from "../state" const plotDomId = "graph-rank" @@ -31,6 +33,35 @@ interface RankPlotInfo { export const GraphRank: FC<{ study: StudyDetail | null +}> = ({ study = null }) => { + if (useBackendRender()) { + return + } else { + return + } +} + +const GraphRankBackend: FC<{ + study: StudyDetail | null +}> = ({ study = null }) => { + const studyId = study?.id + useEffect(() => { + if (studyId === undefined) { + return + } + getPlotAPI(studyId, PlotType.Rank) + .then(({ data, layout }) => { + plotly.react(plotDomId, data, layout) + }) + .catch((err) => { + console.error(err) + }) + }, [studyId]) + return +} + +const GraphRankFrontend: FC<{ + study: StudyDetail | null }> = ({ study = null }) => { const theme = useTheme() const [objectiveId, setobjectiveId] = useState(0) diff --git a/optuna_dashboard/ts/components/GraphSlice.tsx b/optuna_dashboard/ts/components/GraphSlice.tsx index 2a807cb7..c668423c 100644 --- a/optuna_dashboard/ts/components/GraphSlice.tsx +++ b/optuna_dashboard/ts/components/GraphSlice.tsx @@ -20,6 +20,8 @@ import { useParamTargets, } from "../trialFilter" import { useMergedUnionSearchSpace } from "../searchSpace" +import { getPlotAPI, PlotType } from "../apiClient" +import { useBackendRender } from "../state" const plotDomId = "graph-slice" @@ -32,6 +34,35 @@ const isLogScale = (s: SearchSpaceItem): boolean => { export const GraphSlice: FC<{ study: StudyDetail | null +}> = ({ study = null }) => { + if (useBackendRender()) { + return + } else { + return + } +} + +const GraphSliceBackend: FC<{ + study: StudyDetail | null +}> = ({ study = null }) => { + const studyId = study?.id + useEffect(() => { + if (studyId === undefined) { + return + } + getPlotAPI(studyId, PlotType.Slice) + .then(({ data, layout }) => { + plotly.react(plotDomId, data, layout) + }) + .catch((err) => { + console.error(err) + }) + }, [studyId]) + return +} + +const GraphSliceFrontend: FC<{ + study: StudyDetail | null }> = ({ study = null }) => { const theme = useTheme() diff --git a/optuna_dashboard/ts/state.ts b/optuna_dashboard/ts/state.ts index 49307401..20f75c2d 100644 --- a/optuna_dashboard/ts/state.ts +++ b/optuna_dashboard/ts/state.ts @@ -1,4 +1,5 @@ import { atom, useRecoilValue } from "recoil" +import { useQuery } from "./urlQuery" export const studySummariesState = atom({ key: "studySummaries", @@ -104,3 +105,18 @@ export const useArtifacts = (studyId: number, trialId: number): Artifact[] => { } return trial.artifacts } + +export const useBackendRender = (): boolean => { + const query = useQuery() + const plotlypyIsAvailable = useRecoilValue(plotlypyIsAvailableState) + + if (query.get("plotlypy_rendering") === "true") { + if (plotlypyIsAvailable) { + return true + } + console.warn( + "Use frontend rendering because plotlypy is specified but not available." + ) + } + return false +}