Merge pull request #865 from porink0424/feat/lazy-loading-graphs

Impl lazy loading for graph components
This commit is contained in:
c-bata
2024-04-24 13:04:15 +09:00
committed by GitHub
8 changed files with 204 additions and 73 deletions
@@ -0,0 +1,32 @@
import { Box, Typography, useTheme } from "@mui/material"
import React from "react"
import { GraphComponentState } from "ts/types/optuna"
function GraphContainer({
plotDomId,
graphComponentState,
}: {
plotDomId: string
graphComponentState: GraphComponentState
}) {
const theme = useTheme()
return (
<Box component="div" id={plotDomId} sx={{ height: "450px" }}>
{graphComponentState !== "graphDidRender" && (
<Box
component="div"
sx={{
height: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<Typography color={theme.palette.grey[700]}>Loading...</Typography>
</Box>
)}
</Box>
)
}
export default GraphContainer
+28 -15
View File
@@ -17,10 +17,12 @@ import React, { FC, useEffect, useMemo, useState } from "react"
import { SearchSpaceItem, StudyDetail, Trial } from "ts/types/optuna"
import { PlotType } from "../apiClient"
import { getAxisInfo } from "../graphUtil"
import { useGraphComponentState } from "../hooks/useGraphComponentState"
import { usePlot } from "../hooks/usePlot"
import { useMergedUnionSearchSpace } from "../searchSpace"
import { usePlotlyColorTheme } from "../state"
import { useBackendRender } from "../state"
import GraphContainer from "./GraphContainer"
const plotDomId = "graph-contour"
const CONTOUR_DISABLED_THRESHOLD = 100
@@ -84,6 +86,8 @@ const DisabledContour: FC<{
const ContourBackend: FC<{
study: StudyDetail | null
}> = ({ study = null }) => {
const { graphComponentState, notifyGraphDidRender } = useGraphComponentState()
const studyId = study?.id
const numCompletedTrials =
study?.trials.filter((t) => t.state === "Complete").length || 0
@@ -94,22 +98,29 @@ const ContourBackend: FC<{
})
useEffect(() => {
if (data && layout) {
plotly.react(plotDomId, data, layout)
if (data && layout && graphComponentState !== "componentWillMount") {
plotly.react(plotDomId, data, layout).then(notifyGraphDidRender)
}
}, [data, layout])
}, [data, layout, graphComponentState])
useEffect(() => {
if (error) {
console.error(error)
}
}, [error])
return <Box component="div" id={plotDomId} sx={{ height: "450px" }} />
return (
<GraphContainer
plotDomId={plotDomId}
graphComponentState={graphComponentState}
/>
)
}
const ContourFrontend: FC<{
study: StudyDetail | null
}> = ({ study = null }) => {
const { graphComponentState, notifyGraphDidRender } = useGraphComponentState()
const theme = useTheme()
const colorTheme = usePlotlyColorTheme(theme.palette.mode)
@@ -139,10 +150,12 @@ const ContourFrontend: FC<{
}
useEffect(() => {
if (study != null) {
plotContour(study, objectiveId, xParam, yParam, colorTheme)
if (study != null && graphComponentState !== "componentWillMount") {
plotContour(study, objectiveId, xParam, yParam, colorTheme)?.then(
notifyGraphDidRender
)
}
}, [study, objectiveId, xParam, yParam, colorTheme])
}, [study, objectiveId, xParam, yParam, colorTheme, graphComponentState])
const space: SearchSpaceItem[] = study ? study.union_search_space : []
@@ -201,7 +214,10 @@ const ContourFrontend: FC<{
) : null}
</Grid>
<Grid item xs={9}>
<Box component="div" id={plotDomId} sx={{ height: "450px" }} />
<GraphContainer
plotDomId={plotDomId}
graphComponentState={graphComponentState}
/>
</Grid>
</Grid>
)
@@ -225,10 +241,9 @@ const plotContour = (
const trials: Trial[] = study ? study.trials : []
const filteredTrials = trials.filter((t) => filterFunc(t))
if (filteredTrials.length < 2 || xParam === null || yParam === null) {
plotly.react(plotDomId, [], {
return plotly.react(plotDomId, [], {
template: colorTheme,
})
return
}
const xAxis = getAxisInfo(trials, xParam)
@@ -257,8 +272,7 @@ const plotContour = (
// TODO(c-bata): Support parameters that only have the single value
if (xIndices.length <= 1 || yIndices.length <= 1) {
plotly.react(plotDomId, [], layout)
return
return plotly.react(plotDomId, [], layout)
}
const xValues: plotly.Datum[] = []
@@ -322,8 +336,7 @@ const plotContour = (
showlegend: false,
},
]
plotly.react(plotDomId, plotData, layout)
return
return plotly.react(plotDomId, plotData, layout)
}
layout.legend = {
@@ -351,5 +364,5 @@ const plotContour = (
mode: "markers",
},
]
plotly.react(plotDomId, plotData, layout)
return plotly.react(plotDomId, plotData, layout)
}
+34 -15
View File
@@ -3,8 +3,10 @@ import * as plotly from "plotly.js-dist-min"
import React, { FC, useEffect, useMemo } from "react"
import { StudyDetail, Trial } from "ts/types/optuna"
import { CompareStudiesPlotType, getCompareStudiesPlotAPI } from "../apiClient"
import { useGraphComponentState } from "../hooks/useGraphComponentState"
import { useBackendRender, usePlotlyColorTheme } from "../state"
import { Target, useFilteredTrialsFromStudies } from "../trialFilter"
import GraphContainer from "./GraphContainer"
const getPlotDomId = (objectiveId: number) => `graph-edf-${objectiveId}`
@@ -27,6 +29,8 @@ export const GraphEdf: FC<{
const GraphEdfBackend: FC<{
studies: StudyDetail[]
}> = ({ studies }) => {
const { graphComponentState, notifyGraphDidRender } = useGraphComponentState()
const studyIds = studies.map((s) => s.id)
const domId = getPlotDomId(-1)
const numCompletedTrials = studies.reduce(
@@ -38,21 +42,30 @@ const GraphEdfBackend: FC<{
if (studyIds.length === 0) {
return
}
getCompareStudiesPlotAPI(studyIds, CompareStudiesPlotType.EDF)
.then(({ data, layout }) => {
plotly.react(domId, data, layout)
})
.catch((err) => {
console.error(err)
})
}, [studyIds, numCompletedTrials])
return <Box component="div" id={domId} sx={{ height: "450px" }} />
if (graphComponentState !== "componentWillMount") {
getCompareStudiesPlotAPI(studyIds, CompareStudiesPlotType.EDF)
.then(({ data, layout }) => {
plotly.react(domId, data, layout).then(notifyGraphDidRender)
})
.catch((err) => {
console.error(err)
})
}
}, [studyIds, numCompletedTrials, graphComponentState])
return (
<GraphContainer
plotDomId={domId}
graphComponentState={graphComponentState}
/>
)
}
const GraphEdfFrontend: FC<{
studies: StudyDetail[]
objectiveId: number
}> = ({ studies, objectiveId }) => {
const { graphComponentState, notifyGraphDidRender } = useGraphComponentState()
const theme = useTheme()
const colorTheme = usePlotlyColorTheme(theme.palette.mode)
@@ -71,8 +84,12 @@ const GraphEdfFrontend: FC<{
})
useEffect(() => {
plotEdf(edfPlotInfos, target, domId, colorTheme)
}, [studies, target, colorTheme])
if (graphComponentState !== "componentWillMount") {
plotEdf(edfPlotInfos, target, domId, colorTheme)?.then(
notifyGraphDidRender
)
}
}, [studies, target, colorTheme, graphComponentState])
return (
<Box component="div">
@@ -82,7 +99,10 @@ const GraphEdfFrontend: FC<{
>
{`EDF for ${target.toLabel(studies[0].objective_names)}`}
</Typography>
<Box component="div" id={domId} sx={{ height: "450px" }} />
<GraphContainer
plotDomId={domId}
graphComponentState={graphComponentState}
/>
</Box>
)
}
@@ -97,10 +117,9 @@ const plotEdf = (
return
}
if (edfPlotInfos.length === 0) {
plotly.react(domId, [], {
return plotly.react(domId, [], {
template: colorTheme,
})
return
}
const target_name = "Objective Value"
@@ -147,5 +166,5 @@ const plotEdf = (
y: yValues,
}
})
plotly.react(domId, plotData, layout)
return plotly.react(domId, plotData, layout)
}
@@ -1,5 +1,4 @@
import {
Box,
Checkbox,
FormControlLabel,
FormGroup,
@@ -11,6 +10,7 @@ import * as plotly from "plotly.js-dist-min"
import React, { FC, ReactNode, useEffect, useState } from "react"
import { SearchSpaceItem, StudyDetail, Trial } from "ts/types/optuna"
import { PlotType } from "../apiClient"
import { useGraphComponentState } from "../hooks/useGraphComponentState"
import { usePlot } from "../hooks/usePlot"
import { useMergedUnionSearchSpace } from "../searchSpace"
import { usePlotlyColorTheme } from "../state"
@@ -21,6 +21,7 @@ import {
useObjectiveAndUserAttrTargets,
useParamTargets,
} from "../trialFilter"
import GraphContainer from "./GraphContainer"
const plotDomId = "graph-parallel-coordinate"
@@ -101,6 +102,8 @@ export const GraphParallelCoordinate: FC<{
const GraphParallelCoordinateBackend: FC<{
study: StudyDetail | null
}> = ({ study = null }) => {
const { graphComponentState, notifyGraphDidRender } = useGraphComponentState()
const studyId = study?.id
const numCompletedTrials =
study?.trials.filter((t) => t.state === "Complete").length || 0
@@ -112,22 +115,29 @@ const GraphParallelCoordinateBackend: FC<{
})
useEffect(() => {
if (data && layout) {
plotly.react(plotDomId, data, layout)
if (data && layout && graphComponentState !== "componentWillMount") {
plotly.react(plotDomId, data, layout).then(notifyGraphDidRender)
}
}, [data, layout])
}, [data, layout, graphComponentState])
useEffect(() => {
if (error) {
console.error(error)
}
}, [error])
return <Box component="div" id={plotDomId} sx={{ height: "450px" }} />
return (
<GraphContainer
plotDomId={plotDomId}
graphComponentState={graphComponentState}
/>
)
}
const GraphParallelCoordinateFrontend: FC<{
study: StudyDetail | null
}> = ({ study = null }) => {
const { graphComponentState, notifyGraphDidRender } = useGraphComponentState()
const theme = useTheme()
const colorTheme = usePlotlyColorTheme(theme.palette.mode)
@@ -135,10 +145,12 @@ const GraphParallelCoordinateFrontend: FC<{
const trials = useFilteredTrials(study, targets, false)
useEffect(() => {
if (study !== null) {
plotCoordinate(study, trials, targets, searchSpace, colorTheme)
if (study !== null && graphComponentState !== "componentWillMount") {
plotCoordinate(study, trials, targets, searchSpace, colorTheme)?.then(
notifyGraphDidRender
)
}
}, [study, trials, targets, searchSpace, colorTheme])
}, [study, trials, targets, searchSpace, colorTheme, graphComponentState])
return (
<Grid container direction="row">
@@ -162,7 +174,10 @@ const GraphParallelCoordinateFrontend: FC<{
{renderCheckBoxes()}
</Grid>
<Grid item xs={9}>
<Box component="div" id={plotDomId} sx={{ height: "450px" }} />
<GraphContainer
plotDomId={plotDomId}
graphComponentState={graphComponentState}
/>
</Grid>
</Grid>
)
@@ -190,8 +205,7 @@ const plotCoordinate = (
uirevision: "true",
}
if (trials.length === 0 || targets.length === 0) {
plotly.react(plotDomId, [], layout)
return
return plotly.react(plotDomId, [], layout)
}
const maxLabelLength = 40
@@ -282,8 +296,7 @@ const plotCoordinate = (
})
if (dimensions.length === 0) {
console.log("Must not reach here.")
plotly.react(plotDomId, [], layout)
return
return plotly.react(plotDomId, [], layout)
}
let reversescale = false
if (
@@ -312,5 +325,5 @@ const plotCoordinate = (
},
]
plotly.react(plotDomId, plotData, layout)
return plotly.react(plotDomId, plotData, layout)
}
+32 -12
View File
@@ -1,5 +1,4 @@
import {
Box,
FormControl,
FormLabel,
Grid,
@@ -14,9 +13,11 @@ import React, { FC, useEffect, useState } from "react"
import { SearchSpaceItem, StudyDetail, Trial } from "ts/types/optuna"
import { PlotType } from "../apiClient"
import { getAxisInfo, makeHovertext } from "../graphUtil"
import { useGraphComponentState } from "../hooks/useGraphComponentState"
import { usePlot } from "../hooks/usePlot"
import { useMergedUnionSearchSpace } from "../searchSpace"
import { useBackendRender, usePlotlyColorTheme } from "../state"
import GraphContainer from "./GraphContainer"
const plotDomId = "graph-rank"
@@ -45,6 +46,8 @@ export const GraphRank: FC<{
const GraphRankBackend: FC<{
study: StudyDetail | null
}> = ({ study = null }) => {
const { graphComponentState, notifyGraphDidRender } = useGraphComponentState()
const studyId = study?.id
const numCompletedTrials =
study?.trials.filter((t) => t.state === "Complete").length || 0
@@ -56,22 +59,29 @@ const GraphRankBackend: FC<{
})
useEffect(() => {
if (data && layout) {
plotly.react(plotDomId, data, layout)
if (data && layout && graphComponentState !== "componentWillMount") {
plotly.react(plotDomId, data, layout).then(notifyGraphDidRender)
}
}, [data, layout])
}, [data, layout, graphComponentState])
useEffect(() => {
if (error) {
console.error(error)
}
}, [error])
return <Box component="div" id={plotDomId} sx={{ height: "450px" }} />
return (
<GraphContainer
plotDomId={plotDomId}
graphComponentState={graphComponentState}
/>
)
}
const GraphRankFrontend: FC<{
study: StudyDetail | null
}> = ({ study = null }) => {
const { graphComponentState, notifyGraphDidRender } = useGraphComponentState()
const theme = useTheme()
const colorTheme = usePlotlyColorTheme(theme.palette.mode)
@@ -101,11 +111,19 @@ const GraphRankFrontend: FC<{
}
useEffect(() => {
if (study != null) {
if (study != null && graphComponentState !== "componentWillMount") {
const rankPlotInfo = getRankPlotInfo(study, objectiveId, xParam, yParam)
plotRank(rankPlotInfo, colorTheme)
plotRank(rankPlotInfo, colorTheme)?.then(notifyGraphDidRender)
}
}, [study, objectiveId, xParam, yParam, theme.palette.mode, colorTheme])
}, [
study,
objectiveId,
xParam,
yParam,
theme.palette.mode,
colorTheme,
graphComponentState,
])
const space: SearchSpaceItem[] = study ? study.union_search_space : []
@@ -164,7 +182,10 @@ const GraphRankFrontend: FC<{
) : null}
</Grid>
<Grid item xs={9}>
<Box component="div" id={plotDomId} sx={{ height: "450px" }} />
<GraphContainer
plotDomId={plotDomId}
graphComponentState={graphComponentState}
/>
</Grid>
</Grid>
)
@@ -301,10 +322,9 @@ const plotRank = (
}
if (rankPlotInfo === null) {
plotly.react(plotDomId, [], {
return plotly.react(plotDomId, [], {
template: colorTheme,
})
return
}
const layout: Partial<plotly.Layout> = {
@@ -375,5 +395,5 @@ const plotRank = (
),
},
]
plotly.react(plotDomId, plotData, layout)
return plotly.react(plotDomId, plotData, layout)
}
+32 -17
View File
@@ -1,5 +1,4 @@
import {
Box,
FormControl,
FormLabel,
Grid,
@@ -14,6 +13,7 @@ import * as plotly from "plotly.js-dist-min"
import React, { FC, useEffect, useState } from "react"
import { SearchSpaceItem, StudyDetail, Trial } from "ts/types/optuna"
import { PlotType } from "../apiClient"
import { useGraphComponentState } from "../hooks/useGraphComponentState"
import { usePlot } from "../hooks/usePlot"
import { useMergedUnionSearchSpace } from "../searchSpace"
import { useBackendRender, usePlotlyColorTheme } from "../state"
@@ -23,6 +23,7 @@ import {
useObjectiveAndUserAttrTargets,
useParamTargets,
} from "../trialFilter"
import GraphContainer from "./GraphContainer"
const plotDomId = "graph-slice"
@@ -46,6 +47,8 @@ export const GraphSlice: FC<{
const GraphSliceBackend: FC<{
study: StudyDetail | null
}> = ({ study = null }) => {
const { graphComponentState, notifyGraphDidRender } = useGraphComponentState()
const studyId = study?.id
const numCompletedTrials =
study?.trials.filter((t) => t.state === "Complete").length || 0
@@ -57,22 +60,29 @@ const GraphSliceBackend: FC<{
})
useEffect(() => {
if (data && layout) {
plotly.react(plotDomId, data, layout)
if (data && layout && graphComponentState !== "componentWillMount") {
plotly.react(plotDomId, data, layout).then(notifyGraphDidRender)
}
}, [data, layout])
}, [data, layout, graphComponentState])
useEffect(() => {
if (error) {
console.error(error)
}
}, [error])
return <Box component="div" id={plotDomId} sx={{ height: "450px" }} />
return (
<GraphContainer
plotDomId={plotDomId}
graphComponentState={graphComponentState}
/>
)
}
const GraphSliceFrontend: FC<{
study: StudyDetail | null
}> = ({ study = null }) => {
const { graphComponentState, notifyGraphDidRender } = useGraphComponentState()
const theme = useTheme()
const colorTheme = usePlotlyColorTheme(theme.palette.mode)
@@ -92,14 +102,16 @@ const GraphSliceFrontend: FC<{
)
useEffect(() => {
plotSlice(
trials,
selectedObjective,
selectedParamTarget,
searchSpace.find((s) => s.name === selectedParamTarget?.key) || null,
logYScale,
colorTheme
)
if (graphComponentState !== "componentWillMount") {
plotSlice(
trials,
selectedObjective,
selectedParamTarget,
searchSpace.find((s) => s.name === selectedParamTarget?.key) || null,
logYScale,
colorTheme
)?.then(notifyGraphDidRender)
}
}, [
trials,
selectedObjective,
@@ -107,6 +119,7 @@ const GraphSliceFrontend: FC<{
selectedParamTarget,
logYScale,
colorTheme,
graphComponentState,
])
const handleObjectiveChange = (event: SelectChangeEvent<string>) => {
@@ -176,7 +189,10 @@ const GraphSliceFrontend: FC<{
</FormControl>
</Grid>
<Grid item xs={9}>
<Box component="div" id={plotDomId} sx={{ height: "450px" }} />
<GraphContainer
plotDomId={plotDomId}
graphComponentState={graphComponentState}
/>
</Grid>
</Grid>
)
@@ -225,8 +241,7 @@ const plotSlice = (
selectedParamTarget === null ||
trials.length === 0
) {
plotly.react(plotDomId, [], layout)
return
return plotly.react(plotDomId, [], layout)
}
const feasibleTrials: Trial[] = []
@@ -305,5 +320,5 @@ const plotSlice = (
automargin: true, // Otherwise the label is outside of the plot
}
}
plotly.react(plotDomId, trace, layout)
return plotly.react(plotDomId, trace, layout)
}
@@ -0,0 +1,14 @@
import { useEffect, useState } from "react"
import { GraphComponentState } from "ts/types/optuna"
export const useGraphComponentState = () => {
const [graphComponentState, setGraphComponentState] =
useState<GraphComponentState>("componentWillMount")
useEffect(() => {
setGraphComponentState("componentDidMount")
}, [])
return {
graphComponentState,
notifyGraphDidRender: () => setGraphComponentState("graphDidRender"),
}
}
+5
View File
@@ -196,3 +196,8 @@ export type PlotlyColorTheme = {
dark: PlotlyColorThemeDark
light: PlotlyColorThemeLight
}
export type GraphComponentState =
| "componentWillMount"
| "componentDidMount"
| "graphDidRender"