mirror of
https://github.com/wassname/optuna-dashboard.git
synced 2026-09-11 12:30:25 +08:00
Merge pull request #893 from porink0424/fix/move-graphSlice-to-tslib
Move `GraphSlice` from `optuna_dashboard/ts` to `tslib/react`
This commit is contained in:
@@ -1,9 +1,4 @@
|
||||
import {
|
||||
GraphContainer,
|
||||
PlotEdf,
|
||||
getPlotDomId,
|
||||
useGraphComponentState,
|
||||
} from "@optuna/react"
|
||||
import { GraphContainer, PlotEdf, useGraphComponentState } from "@optuna/react"
|
||||
import * as plotly from "plotly.js-dist-min"
|
||||
import React, { FC, useEffect } from "react"
|
||||
import { StudyDetail } from "ts/types/optuna"
|
||||
@@ -22,6 +17,8 @@ export const GraphEdf: FC<{
|
||||
}
|
||||
}
|
||||
|
||||
const domId = "graph-edf"
|
||||
|
||||
const GraphEdfBackend: FC<{
|
||||
studies: StudyDetail[]
|
||||
}> = ({ studies }) => {
|
||||
@@ -29,7 +26,6 @@ const GraphEdfBackend: FC<{
|
||||
const { graphComponentState, notifyGraphDidRender } = useGraphComponentState()
|
||||
|
||||
const studyIds = studies.map((s) => s.id)
|
||||
const domId = getPlotDomId(-1)
|
||||
const numCompletedTrials = studies.reduce(
|
||||
(acc, study) =>
|
||||
acc + study?.trials.filter((t) => t.state === "Complete").length,
|
||||
|
||||
@@ -1,41 +1,14 @@
|
||||
import {
|
||||
FormControl,
|
||||
FormLabel,
|
||||
Grid,
|
||||
MenuItem,
|
||||
Select,
|
||||
SelectChangeEvent,
|
||||
Switch,
|
||||
Typography,
|
||||
useTheme,
|
||||
} from "@mui/material"
|
||||
import {
|
||||
GraphContainer,
|
||||
PlotSlice,
|
||||
useGraphComponentState,
|
||||
useMergedUnionSearchSpace,
|
||||
} from "@optuna/react"
|
||||
import {
|
||||
Target,
|
||||
useFilteredTrials,
|
||||
useObjectiveAndUserAttrTargets,
|
||||
useParamTargets,
|
||||
} from "@optuna/react"
|
||||
import * as Optuna from "@optuna/types"
|
||||
import * as plotly from "plotly.js-dist-min"
|
||||
import React, { FC, useEffect, useState } from "react"
|
||||
import { SearchSpaceItem, StudyDetail } from "ts/types/optuna"
|
||||
import React, { FC, useEffect } from "react"
|
||||
import { StudyDetail } from "ts/types/optuna"
|
||||
import { PlotType } from "../apiClient"
|
||||
import { usePlot } from "../hooks/usePlot"
|
||||
import { useBackendRender, usePlotlyColorTheme } from "../state"
|
||||
|
||||
const plotDomId = "graph-slice"
|
||||
|
||||
const isLogScale = (s: SearchSpaceItem): boolean => {
|
||||
if (s.distribution.type === "CategoricalDistribution") {
|
||||
return false
|
||||
}
|
||||
return s.distribution.log
|
||||
}
|
||||
import { useBackendRender } from "../state"
|
||||
|
||||
export const GraphSlice: FC<{
|
||||
study: StudyDetail | null
|
||||
@@ -43,10 +16,12 @@ export const GraphSlice: FC<{
|
||||
if (useBackendRender()) {
|
||||
return <GraphSliceBackend study={study} />
|
||||
} else {
|
||||
return <GraphSliceFrontend study={study} />
|
||||
return <PlotSlice study={study} />
|
||||
}
|
||||
}
|
||||
|
||||
const domId = "graph-slice"
|
||||
|
||||
const GraphSliceBackend: FC<{
|
||||
study: StudyDetail | null
|
||||
}> = ({ study = null }) => {
|
||||
@@ -64,7 +39,7 @@ const GraphSliceBackend: FC<{
|
||||
|
||||
useEffect(() => {
|
||||
if (data && layout && graphComponentState !== "componentWillMount") {
|
||||
plotly.react(plotDomId, data, layout).then(notifyGraphDidRender)
|
||||
plotly.react(domId, data, layout).then(notifyGraphDidRender)
|
||||
}
|
||||
}, [data, layout, graphComponentState])
|
||||
useEffect(() => {
|
||||
@@ -75,253 +50,8 @@ const GraphSliceBackend: FC<{
|
||||
|
||||
return (
|
||||
<GraphContainer
|
||||
plotDomId={plotDomId}
|
||||
plotDomId={domId}
|
||||
graphComponentState={graphComponentState}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const GraphSliceFrontend: FC<{
|
||||
study: StudyDetail | null
|
||||
}> = ({ study = null }) => {
|
||||
const { graphComponentState, notifyGraphDidRender } = useGraphComponentState()
|
||||
|
||||
const theme = useTheme()
|
||||
const colorTheme = usePlotlyColorTheme(theme.palette.mode)
|
||||
|
||||
const [objectiveTargets, selectedObjective, setObjectiveTarget] =
|
||||
useObjectiveAndUserAttrTargets(study)
|
||||
const searchSpace = useMergedUnionSearchSpace(study?.union_search_space)
|
||||
const [paramTargets, selectedParamTarget, setParamTarget] =
|
||||
useParamTargets(searchSpace)
|
||||
const [logYScale, setLogYScale] = useState<boolean>(false)
|
||||
|
||||
const trials = useFilteredTrials(
|
||||
study,
|
||||
selectedParamTarget !== null
|
||||
? [selectedObjective, selectedParamTarget]
|
||||
: [selectedObjective],
|
||||
false
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (graphComponentState !== "componentWillMount") {
|
||||
plotSlice(
|
||||
trials,
|
||||
selectedObjective,
|
||||
selectedParamTarget,
|
||||
searchSpace.find((s) => s.name === selectedParamTarget?.key) || null,
|
||||
logYScale,
|
||||
colorTheme
|
||||
)?.then(notifyGraphDidRender)
|
||||
}
|
||||
}, [
|
||||
trials,
|
||||
selectedObjective,
|
||||
searchSpace,
|
||||
selectedParamTarget,
|
||||
logYScale,
|
||||
colorTheme,
|
||||
graphComponentState,
|
||||
])
|
||||
|
||||
const handleObjectiveChange = (event: SelectChangeEvent<string>) => {
|
||||
setObjectiveTarget(event.target.value)
|
||||
}
|
||||
|
||||
const handleSelectedParam = (e: SelectChangeEvent<string>) => {
|
||||
setParamTarget(e.target.value)
|
||||
}
|
||||
|
||||
const handleLogYScaleChange = () => {
|
||||
setLogYScale(!logYScale)
|
||||
}
|
||||
|
||||
return (
|
||||
<Grid container direction="row">
|
||||
<Grid
|
||||
item
|
||||
xs={3}
|
||||
container
|
||||
direction="column"
|
||||
sx={{ paddingRight: theme.spacing(2) }}
|
||||
>
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{ margin: "1em 0", fontWeight: theme.typography.fontWeightBold }}
|
||||
>
|
||||
Slice
|
||||
</Typography>
|
||||
{objectiveTargets.length !== 1 && (
|
||||
<FormControl component="fieldset">
|
||||
<FormLabel component="legend">Objective:</FormLabel>
|
||||
<Select
|
||||
value={selectedObjective.identifier()}
|
||||
onChange={handleObjectiveChange}
|
||||
>
|
||||
{objectiveTargets.map((t, i) => (
|
||||
<MenuItem value={t.identifier()} key={i}>
|
||||
{t.toLabel(study?.objective_names)}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
{paramTargets.length !== 0 && selectedParamTarget !== null && (
|
||||
<FormControl component="fieldset">
|
||||
<FormLabel component="legend">Parameter:</FormLabel>
|
||||
<Select
|
||||
value={selectedParamTarget.identifier()}
|
||||
onChange={handleSelectedParam}
|
||||
>
|
||||
{paramTargets.map((t, i) => (
|
||||
<MenuItem value={t.identifier()} key={i}>
|
||||
{t.toLabel()}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
<FormControl component="fieldset">
|
||||
<FormLabel component="legend">Log y scale:</FormLabel>
|
||||
<Switch
|
||||
checked={logYScale}
|
||||
onChange={handleLogYScaleChange}
|
||||
value="enable"
|
||||
/>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
<Grid item xs={9}>
|
||||
<GraphContainer
|
||||
plotDomId={plotDomId}
|
||||
graphComponentState={graphComponentState}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
const plotSlice = (
|
||||
trials: Optuna.Trial[],
|
||||
objectiveTarget: Target,
|
||||
selectedParamTarget: Target | null,
|
||||
selectedParamSpace: SearchSpaceItem | null,
|
||||
logYScale: boolean,
|
||||
colorTheme: Partial<Plotly.Template>
|
||||
) => {
|
||||
if (document.getElementById(plotDomId) === null) {
|
||||
return
|
||||
}
|
||||
|
||||
const layout: Partial<plotly.Layout> = {
|
||||
margin: {
|
||||
l: 50,
|
||||
t: 0,
|
||||
r: 50,
|
||||
b: 0,
|
||||
},
|
||||
xaxis: {
|
||||
title: selectedParamTarget?.toLabel() || "",
|
||||
type:
|
||||
selectedParamSpace !== null && isLogScale(selectedParamSpace)
|
||||
? "log"
|
||||
: "linear",
|
||||
gridwidth: 1,
|
||||
automargin: true,
|
||||
},
|
||||
yaxis: {
|
||||
title: "Objective Value",
|
||||
type: logYScale ? "log" : "linear",
|
||||
gridwidth: 1,
|
||||
automargin: true,
|
||||
},
|
||||
showlegend: false,
|
||||
uirevision: "true",
|
||||
template: colorTheme,
|
||||
}
|
||||
if (
|
||||
selectedParamSpace === null ||
|
||||
selectedParamTarget === null ||
|
||||
trials.length === 0
|
||||
) {
|
||||
return plotly.react(plotDomId, [], layout)
|
||||
}
|
||||
|
||||
const feasibleTrials: Optuna.Trial[] = []
|
||||
const infeasibleTrials: Optuna.Trial[] = []
|
||||
trials.forEach((t) => {
|
||||
if (t.constraints.every((c) => c <= 0)) {
|
||||
feasibleTrials.push(t)
|
||||
} else {
|
||||
infeasibleTrials.push(t)
|
||||
}
|
||||
})
|
||||
|
||||
const feasibleObjectiveValues: number[] = feasibleTrials.map(
|
||||
(t) => objectiveTarget.getTargetValue(t) as number
|
||||
)
|
||||
const infeasibleObjectiveValues: number[] = infeasibleTrials.map(
|
||||
(t) => objectiveTarget.getTargetValue(t) as number
|
||||
)
|
||||
|
||||
const feasibleValues = feasibleTrials.map(
|
||||
(t) => selectedParamTarget.getTargetValue(t) as number
|
||||
)
|
||||
const infeasibleValues = infeasibleTrials.map(
|
||||
(t) => selectedParamTarget.getTargetValue(t) as number
|
||||
)
|
||||
const trace: plotly.Data[] = [
|
||||
{
|
||||
type: "scatter",
|
||||
x: feasibleValues,
|
||||
y: feasibleObjectiveValues,
|
||||
mode: "markers",
|
||||
name: "Feasible Trial",
|
||||
marker: {
|
||||
color: feasibleTrials.map((t) => t.number),
|
||||
colorscale: "Blues",
|
||||
reversescale: true,
|
||||
colorbar: {
|
||||
title: "Trial",
|
||||
},
|
||||
line: {
|
||||
color: "Grey",
|
||||
width: 0.5,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "scatter",
|
||||
x: infeasibleValues,
|
||||
y: infeasibleObjectiveValues,
|
||||
mode: "markers",
|
||||
name: "Infeasible Trial",
|
||||
marker: {
|
||||
color: "#cccccc",
|
||||
reversescale: true,
|
||||
},
|
||||
},
|
||||
]
|
||||
if (selectedParamSpace.distribution.type !== "CategoricalDistribution") {
|
||||
layout["xaxis"] = {
|
||||
title: selectedParamTarget.toLabel(),
|
||||
type: isLogScale(selectedParamSpace) ? "log" : "linear",
|
||||
gridwidth: 1,
|
||||
automargin: true, // Otherwise the label is outside of the plot
|
||||
}
|
||||
} else {
|
||||
const vocabArr = selectedParamSpace.distribution.choices.map(
|
||||
(c) => c?.toString() ?? "null"
|
||||
)
|
||||
const tickvals: number[] = vocabArr.map((v, i) => i)
|
||||
layout["xaxis"] = {
|
||||
title: selectedParamTarget.toLabel(),
|
||||
type: "linear",
|
||||
gridwidth: 1,
|
||||
tickvals: tickvals,
|
||||
ticktext: vocabArr,
|
||||
automargin: true, // Otherwise the label is outside of the plot
|
||||
}
|
||||
}
|
||||
return plotly.react(plotDomId, trace, layout)
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ export type EdfPlotInfo = {
|
||||
trials: Optuna.Trial[]
|
||||
}
|
||||
|
||||
export const getPlotDomId = (objectiveId: number) => `graph-edf-${objectiveId}`
|
||||
const getPlotDomId = (objectiveId: number) => `plot-edf-${objectiveId}`
|
||||
|
||||
export const PlotEdf: FC<{
|
||||
studies: Optuna.Study[]
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { CssBaseline, ThemeProvider } from "@mui/material"
|
||||
import { Meta, StoryObj } from "@storybook/react"
|
||||
import React from "react"
|
||||
import { useMockStudy } from "../MockStudies"
|
||||
import { lightTheme } from "../styles/lightTheme"
|
||||
import { PlotSlice } from "./PlotSlice"
|
||||
|
||||
const meta: Meta<typeof PlotSlice> = {
|
||||
component: PlotSlice,
|
||||
title: "PlotSlice",
|
||||
tags: ["autodocs"],
|
||||
decorators: [
|
||||
(Story, storyContext) => {
|
||||
const { study } = useMockStudy(storyContext.parameters?.studyId)
|
||||
if (!study) return <p>loading...</p>
|
||||
return (
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<CssBaseline />
|
||||
<Story
|
||||
args={{
|
||||
study,
|
||||
}}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
)
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof PlotSlice>
|
||||
|
||||
export const MockStudyExample1: Story = {
|
||||
parameters: {
|
||||
studyId: 1,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
import {
|
||||
FormControl,
|
||||
FormLabel,
|
||||
Grid,
|
||||
MenuItem,
|
||||
Select,
|
||||
SelectChangeEvent,
|
||||
Switch,
|
||||
Typography,
|
||||
useTheme,
|
||||
} from "@mui/material"
|
||||
import * as Optuna from "@optuna/types"
|
||||
import * as plotly from "plotly.js-dist-min"
|
||||
import { FC, useEffect, useState } from "react"
|
||||
import { useGraphComponentState } from "../hooks/useGraphComponentState"
|
||||
import { useMergedUnionSearchSpace } from "../utils/searchSpace"
|
||||
import {
|
||||
Target,
|
||||
useFilteredTrials,
|
||||
useObjectiveAndUserAttrTargets,
|
||||
useParamTargets,
|
||||
} from "../utils/trialFilter"
|
||||
import { GraphContainer } from "./GraphContainer"
|
||||
import { plotlyDarkTemplate } from "./PlotlyDarkMode"
|
||||
|
||||
const isLogScale = (s: Optuna.SearchSpaceItem): boolean => {
|
||||
if (s.distribution.type === "CategoricalDistribution") {
|
||||
return false
|
||||
}
|
||||
return s.distribution.log
|
||||
}
|
||||
|
||||
const domId = "plot-slice"
|
||||
|
||||
export const PlotSlice: FC<{
|
||||
study: Optuna.Study | null
|
||||
}> = ({ study = null }) => {
|
||||
const { graphComponentState, notifyGraphDidRender } = useGraphComponentState()
|
||||
|
||||
const theme = useTheme()
|
||||
|
||||
const [objectiveTargets, selectedObjective, setObjectiveTarget] =
|
||||
useObjectiveAndUserAttrTargets(study)
|
||||
const searchSpace = useMergedUnionSearchSpace(study?.union_search_space)
|
||||
const [paramTargets, selectedParamTarget, setParamTarget] =
|
||||
useParamTargets(searchSpace)
|
||||
const [logYScale, setLogYScale] = useState(false)
|
||||
|
||||
const trials = useFilteredTrials(
|
||||
study,
|
||||
selectedParamTarget !== null
|
||||
? [selectedObjective, selectedParamTarget]
|
||||
: [selectedObjective],
|
||||
false
|
||||
)
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
|
||||
useEffect(() => {
|
||||
if (graphComponentState !== "componentWillMount") {
|
||||
plotSlice(
|
||||
trials,
|
||||
selectedObjective,
|
||||
selectedParamTarget,
|
||||
searchSpace.find((s) => s.name === selectedParamTarget?.key) || null,
|
||||
logYScale,
|
||||
theme.palette.mode
|
||||
)?.then(notifyGraphDidRender)
|
||||
}
|
||||
}, [
|
||||
trials,
|
||||
selectedObjective,
|
||||
searchSpace,
|
||||
selectedParamTarget,
|
||||
logYScale,
|
||||
theme.palette.mode,
|
||||
graphComponentState,
|
||||
])
|
||||
|
||||
const handleObjectiveChange = (event: SelectChangeEvent<string>) => {
|
||||
setObjectiveTarget(event.target.value)
|
||||
}
|
||||
|
||||
const handleSelectedParam = (e: SelectChangeEvent<string>) => {
|
||||
setParamTarget(e.target.value)
|
||||
}
|
||||
|
||||
const handleLogYScaleChange = () => {
|
||||
setLogYScale(!logYScale)
|
||||
}
|
||||
|
||||
return (
|
||||
<Grid container direction="row">
|
||||
<Grid
|
||||
item
|
||||
xs={3}
|
||||
container
|
||||
direction="column"
|
||||
sx={{ paddingRight: theme.spacing(2) }}
|
||||
>
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{ margin: "1em 0", fontWeight: theme.typography.fontWeightBold }}
|
||||
>
|
||||
Slice
|
||||
</Typography>
|
||||
{objectiveTargets.length !== 1 && (
|
||||
<FormControl component="fieldset">
|
||||
<FormLabel component="legend">Objective:</FormLabel>
|
||||
<Select
|
||||
value={selectedObjective.identifier()}
|
||||
onChange={handleObjectiveChange}
|
||||
>
|
||||
{objectiveTargets.map((t, i) => (
|
||||
// biome-ignore lint/suspicious/noArrayIndexKey: <explanation>
|
||||
<MenuItem value={t.identifier()} key={i}>
|
||||
{t.toLabel(study?.metric_names)}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
{paramTargets.length !== 0 && selectedParamTarget !== null && (
|
||||
<FormControl component="fieldset">
|
||||
<FormLabel component="legend">Parameter:</FormLabel>
|
||||
<Select
|
||||
value={selectedParamTarget.identifier()}
|
||||
onChange={handleSelectedParam}
|
||||
>
|
||||
{paramTargets.map((t, i) => (
|
||||
// biome-ignore lint/suspicious/noArrayIndexKey: <explanation>
|
||||
<MenuItem value={t.identifier()} key={i}>
|
||||
{t.toLabel()}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
<FormControl component="fieldset">
|
||||
<FormLabel component="legend">Log y scale:</FormLabel>
|
||||
<Switch
|
||||
checked={logYScale}
|
||||
onChange={handleLogYScaleChange}
|
||||
value="enable"
|
||||
/>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
<Grid item xs={9}>
|
||||
<GraphContainer
|
||||
plotDomId={domId}
|
||||
graphComponentState={graphComponentState}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
const plotSlice = (
|
||||
trials: Optuna.Trial[],
|
||||
objectiveTarget: Target,
|
||||
selectedParamTarget: Target | null,
|
||||
selectedParamSpace: Optuna.SearchSpaceItem | null,
|
||||
logYScale: boolean,
|
||||
mode: string
|
||||
) => {
|
||||
if (document.getElementById(domId) === null) {
|
||||
return
|
||||
}
|
||||
|
||||
const layout: Partial<plotly.Layout> = {
|
||||
margin: {
|
||||
l: 50,
|
||||
t: 0,
|
||||
r: 50,
|
||||
b: 0,
|
||||
},
|
||||
xaxis: {
|
||||
title: selectedParamTarget?.toLabel() || "",
|
||||
type:
|
||||
selectedParamSpace !== null && isLogScale(selectedParamSpace)
|
||||
? "log"
|
||||
: "linear",
|
||||
gridwidth: 1,
|
||||
automargin: true,
|
||||
},
|
||||
yaxis: {
|
||||
title: "Objective Value",
|
||||
type: logYScale ? "log" : "linear",
|
||||
gridwidth: 1,
|
||||
automargin: true,
|
||||
},
|
||||
showlegend: false,
|
||||
uirevision: "true",
|
||||
template: mode === "dark" ? plotlyDarkTemplate : {},
|
||||
}
|
||||
if (
|
||||
selectedParamSpace === null ||
|
||||
selectedParamTarget === null ||
|
||||
trials.length === 0
|
||||
) {
|
||||
return plotly.react(domId, [], layout)
|
||||
}
|
||||
|
||||
const feasibleTrials: Optuna.Trial[] = []
|
||||
const infeasibleTrials: Optuna.Trial[] = []
|
||||
// biome-ignore lint/complexity/noForEach: <explanation>
|
||||
trials.forEach((t) => {
|
||||
if (t.constraints.every((c) => c <= 0)) {
|
||||
feasibleTrials.push(t)
|
||||
} else {
|
||||
infeasibleTrials.push(t)
|
||||
}
|
||||
})
|
||||
|
||||
const feasibleObjectiveValues: number[] = feasibleTrials.map(
|
||||
(t) => objectiveTarget.getTargetValue(t) as number
|
||||
)
|
||||
const infeasibleObjectiveValues: number[] = infeasibleTrials.map(
|
||||
(t) => objectiveTarget.getTargetValue(t) as number
|
||||
)
|
||||
|
||||
const feasibleValues = feasibleTrials.map(
|
||||
(t) => selectedParamTarget.getTargetValue(t) as number
|
||||
)
|
||||
const infeasibleValues = infeasibleTrials.map(
|
||||
(t) => selectedParamTarget.getTargetValue(t) as number
|
||||
)
|
||||
const trace: plotly.Data[] = [
|
||||
{
|
||||
type: "scatter",
|
||||
x: feasibleValues,
|
||||
y: feasibleObjectiveValues,
|
||||
mode: "markers",
|
||||
name: "Feasible Trial",
|
||||
marker: {
|
||||
color: feasibleTrials.map((t) => t.number),
|
||||
colorscale: "Blues",
|
||||
reversescale: true,
|
||||
colorbar: {
|
||||
title: "Trial",
|
||||
},
|
||||
line: {
|
||||
color: "Grey",
|
||||
width: 0.5,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "scatter",
|
||||
x: infeasibleValues,
|
||||
y: infeasibleObjectiveValues,
|
||||
mode: "markers",
|
||||
name: "Infeasible Trial",
|
||||
marker: {
|
||||
color: "#cccccc",
|
||||
reversescale: true,
|
||||
},
|
||||
},
|
||||
]
|
||||
if (selectedParamSpace.distribution.type !== "CategoricalDistribution") {
|
||||
layout.xaxis = {
|
||||
title: selectedParamTarget.toLabel(),
|
||||
type: isLogScale(selectedParamSpace) ? "log" : "linear",
|
||||
gridwidth: 1,
|
||||
automargin: true, // Otherwise the label is outside of the plot
|
||||
}
|
||||
} else {
|
||||
const vocabArr = selectedParamSpace.distribution.choices.map(
|
||||
(c) => c?.toString() ?? "null"
|
||||
)
|
||||
const tickvals: number[] = vocabArr.map((_v, i) => i)
|
||||
layout.xaxis = {
|
||||
title: selectedParamTarget.toLabel(),
|
||||
type: "linear",
|
||||
gridwidth: 1,
|
||||
tickvals: tickvals,
|
||||
ticktext: vocabArr,
|
||||
automargin: true, // Otherwise the label is outside of the plot
|
||||
}
|
||||
}
|
||||
return plotly.react(domId, trace, layout)
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { CssBaseline, ThemeProvider } from "@mui/material"
|
||||
import { Meta, StoryObj } from "@storybook/react"
|
||||
import React from "react"
|
||||
import { useMockStudy } from "../MockStudies"
|
||||
import { darkTheme } from "../styles/darkTheme"
|
||||
import { PlotSlice } from "./PlotSlice"
|
||||
|
||||
const meta: Meta<typeof PlotSlice> = {
|
||||
component: PlotSlice,
|
||||
title: "PlotSliceDark",
|
||||
tags: ["autodocs"],
|
||||
decorators: [
|
||||
(Story, storyContext) => {
|
||||
const { study } = useMockStudy(storyContext.parameters?.studyId)
|
||||
if (!study) return <p>loading...</p>
|
||||
return (
|
||||
<ThemeProvider theme={darkTheme}>
|
||||
<CssBaseline />
|
||||
<Story
|
||||
args={{
|
||||
study,
|
||||
}}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
)
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
backgrounds: { default: "dark" },
|
||||
},
|
||||
}
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof PlotSlice>
|
||||
|
||||
export const MockStudyExample1: Story = {
|
||||
parameters: {
|
||||
studyId: 1,
|
||||
},
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
export { DataGrid } from "./components/DataGrid"
|
||||
export { plotlyDarkTemplate } from "./components/PlotlyDarkMode"
|
||||
export { PlotEdf, getPlotDomId } from "./components/PlotEdf"
|
||||
export { PlotEdf } from "./components/PlotEdf"
|
||||
export type { EdfPlotInfo } from "./components/PlotEdf"
|
||||
export { PlotHistory } from "./components/PlotHistory"
|
||||
export { PlotImportance } from "./components/PlotImportance"
|
||||
export { PlotIntermediateValues } from "./components/PlotIntermediateValues"
|
||||
export { PlotSlice } from "./components/PlotSlice"
|
||||
export { TrialTable } from "./components/TrialTable"
|
||||
export { GraphContainer } from "./components/GraphContainer"
|
||||
export { useGraphComponentState } from "./hooks/useGraphComponentState"
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import * as Optuna from "@optuna/types"
|
||||
import { render, screen } from "@testing-library/react"
|
||||
import React from "react"
|
||||
import { describe, expect, test } from "vitest"
|
||||
import { PlotSlice } from "../src/components/PlotSlice"
|
||||
|
||||
describe("PlotSlice Tests", async () => {
|
||||
const setup = ({
|
||||
study,
|
||||
dataTestId,
|
||||
}: { study: Optuna.Study; dataTestId: string }) => {
|
||||
const Wrapper = ({
|
||||
dataTestId,
|
||||
children,
|
||||
}: {
|
||||
dataTestId: string
|
||||
children: React.ReactNode
|
||||
}) => <div data-testid={dataTestId}>{children}</div>
|
||||
return render(
|
||||
<Wrapper dataTestId={dataTestId}>
|
||||
<PlotSlice study={study} />
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
|
||||
for (const study of window.mockStudies) {
|
||||
test(`PlotSlice (study name: ${study.name})`, () => {
|
||||
setup({ study, dataTestId: `plot-slice-${study.id}` })
|
||||
expect(screen.getByTestId(`plot-slice-${study.id}`)).toBeInTheDocument()
|
||||
})
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user