import * as plotly from "plotly.js-dist-min"
import React, { FC, useEffect, useState } from "react"
import {
Grid,
FormControl,
FormLabel,
MenuItem,
Switch,
Select,
Typography,
SelectChangeEvent,
useTheme,
Box,
} from "@mui/material"
import { plotlyDarkTemplate } from "./PlotlyDarkMode"
import {
Target,
useFilteredTrials,
useObjectiveAndUserAttrTargets,
useParamTargets,
} from "../trialFilter"
import { useMergedUnionSearchSpace } from "../searchSpace"
import { getPlotAPI, PlotType } from "../apiClient"
import { useBackendRender } from "../state"
const plotDomId = "graph-slice"
const isLogScale = (s: SearchSpaceItem): boolean => {
if (s.distribution.type === "CategoricalDistribution") {
return false
}
return s.distribution.log
}
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
const numCompletedTrials =
study?.trials.filter((t) => t.state === "Complete").length || 0
useEffect(() => {
if (studyId === undefined) {
return
}
getPlotAPI(studyId, PlotType.Slice)
.then(({ data, layout }) => {
plotly.react(plotDomId, data, layout)
})
.catch((err) => {
console.error(err)
})
}, [studyId, numCompletedTrials])
return
}
const GraphSliceFrontend: FC<{
study: StudyDetail | null
}> = ({ study = null }) => {
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
)
useEffect(() => {
plotSlice(
trials,
selectedObjective,
selectedParamTarget,
searchSpace.find((s) => s.name === selectedParamTarget?.key) || null,
logYScale,
theme.palette.mode
)
}, [
trials,
selectedObjective,
searchSpace,
selectedParamTarget,
logYScale,
theme.palette.mode,
])
const handleObjectiveChange = (event: SelectChangeEvent) => {
setObjectiveTarget(event.target.value)
}
const handleSelectedParam = (e: SelectChangeEvent) => {
setParamTarget(e.target.value)
}
const handleLogYScaleChange = () => {
setLogYScale(!logYScale)
}
return (
Slice
{objectiveTargets.length !== 1 && (
Objective:
)}
{paramTargets.length !== 0 && selectedParamTarget !== null && (
Parameter:
)}
Log y scale:
)
}
const plotSlice = (
trials: Trial[],
objectiveTarget: Target,
selectedParamTarget: Target | null,
selectedParamSpace: SearchSpaceItem | null,
logYScale: boolean,
mode: string
) => {
if (document.getElementById(plotDomId) === null) {
return
}
const layout: Partial = {
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
) {
plotly.react(plotDomId, [], layout)
return
}
const feasibleTrials: Trial[] = []
const infeasibleTrials: 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.value)
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
}
}
plotly.react(plotDomId, trace, layout)
}