From 0cb20e8301f90e46613b86e6e62279f7744a7170 Mon Sep 17 00:00:00 2001 From: hrntsm Date: Sun, 12 Jan 2025 11:47:56 +0900 Subject: [PATCH 1/4] Add show artifact toggle in selection --- .../Artifact/SelectedTrialArtifactCards.tsx | 164 ++++++++++++++++++ .../ts/components/TrialSelection.tsx | 28 +++ 2 files changed, 192 insertions(+) create mode 100644 optuna_dashboard/ts/components/Artifact/SelectedTrialArtifactCards.tsx diff --git a/optuna_dashboard/ts/components/Artifact/SelectedTrialArtifactCards.tsx b/optuna_dashboard/ts/components/Artifact/SelectedTrialArtifactCards.tsx new file mode 100644 index 000000000..c663a7b87 --- /dev/null +++ b/optuna_dashboard/ts/components/Artifact/SelectedTrialArtifactCards.tsx @@ -0,0 +1,164 @@ +import DeleteIcon from "@mui/icons-material/Delete" +import DownloadIcon from "@mui/icons-material/Download" +import FullscreenIcon from "@mui/icons-material/Fullscreen" +import { + Box, + Card, + CardContent, + IconButton, + Typography, + useTheme, +} from "@mui/material" +import React, { FC } from "react" + +import { StudyDetail, Trial } from "ts/types/optuna" +import { ArtifactCardMedia } from "./ArtifactCardMedia" +import { useDeleteTrialArtifactDialog } from "./DeleteArtifactDialog" +import { isTableArtifact, useTableArtifactModal } from "./TableArtifactViewer" +import { + isThreejsArtifact, + useThreejsArtifactModal, +} from "./ThreejsArtifactViewer" + +export const SelectedTrialArtifactCards: FC<{ + study: StudyDetail + selectedTrials: number[] +}> = ({ study, selectedTrials }) => { + const theme = useTheme() + const [openDeleteArtifactDialog, renderDeleteArtifactDialog] = + useDeleteTrialArtifactDialog() + const [openThreejsArtifactModal, renderThreejsArtifactModal] = + useThreejsArtifactModal() + const [openTableArtifactModal, renderTableArtifactModal] = + useTableArtifactModal() + const isArtifactModifiable = (trial: Trial) => { + return trial.state === "Running" || trial.state === "Waiting" + } + + if (selectedTrials.length === 0) { + selectedTrials = study.trials.map((trial) => trial.number) + } + + const trials = study.trials.filter((trial) => + selectedTrials.includes(trial.number) + ) + const width = "200px" + const height = "150px" + + return ( + <> + + Artifacts + + + {trials.map((trial) => { + const artifact = trial.artifacts[0] + const urlPath = `/artifacts/${trial.study_id}/${trial.trial_id}/${artifact.artifact_id}` + return ( + + + + + {"Trial id: " + trial.number} + + {isThreejsArtifact(artifact) ? ( + { + openThreejsArtifactModal(urlPath, artifact) + }} + > + + + ) : null} + {isTableArtifact(artifact) ? ( + { + openTableArtifactModal(urlPath, artifact) + }} + > + + + ) : null} + {isArtifactModifiable(trial) ? ( + { + openDeleteArtifactDialog( + trial.study_id, + trial.trial_id, + artifact + ) + }} + > + + + ) : null} + + + + + + ) + })} + + {renderDeleteArtifactDialog()} + {renderThreejsArtifactModal()} + {renderTableArtifactModal()} + + ) +} diff --git a/optuna_dashboard/ts/components/TrialSelection.tsx b/optuna_dashboard/ts/components/TrialSelection.tsx index 74c8cbefa..6af318d3f 100644 --- a/optuna_dashboard/ts/components/TrialSelection.tsx +++ b/optuna_dashboard/ts/components/TrialSelection.tsx @@ -16,6 +16,7 @@ import { Link } from "react-router-dom" import { StudyDetail } from "ts/types/optuna" import { useConstants } from "../constantsProvider" import { studyDetailToStudy } from "../graphUtil" +import { SelectedTrialArtifactCards } from "./Artifact/SelectedTrialArtifactCards" import { GraphHistory } from "./GraphHistory" import { GraphParetoFront } from "./GraphParetoFront" @@ -29,6 +30,7 @@ export const TrialSelection: FC<{ studyDetail: StudyDetail | null }> = ({ useState(true) const [includeDominatedTrials, setIncludeDominatedTrials] = useState(true) + const [showArtifacts, setShowArtifacts] = useState(false) const handleSelectionChange = (selectedTrials: number[]) => { setSelectedTrials(selectedTrials) @@ -36,6 +38,9 @@ export const TrialSelection: FC<{ studyDetail: StudyDetail | null }> = ({ const handleIncludeInfeasibleTrialsChange = () => { setIncludeInfeasibleTrials(!includeInfeasibleTrials) } + const handleShowArtifactsChange = () => { + setShowArtifacts(!showArtifacts) + } const handleIncludeDominatedTrialsChange = () => { if (includeDominatedTrials) { setIncludeInfeasibleTrials(false) @@ -100,6 +105,19 @@ export const TrialSelection: FC<{ studyDetail: StudyDetail | null }> = ({ label="Include dominated trials" /> ) : null} + {studyDetail ? ( + 1)} + value="enable" + /> + } + label="Show Artifacts" + /> + ) : null} = ({ )} + {studyDetail != null && showArtifacts ? ( + + + + + + ) : null} {study ? ( From 885f92523ab0eccb7dd6a9a489e5b887a32dcb94 Mon Sep 17 00:00:00 2001 From: hrntsm Date: Sun, 12 Jan 2025 12:48:57 +0900 Subject: [PATCH 2/4] Add min-max calculation and border color logic for artifact cards --- .../Artifact/SelectedTrialArtifactCards.tsx | 85 ++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-) diff --git a/optuna_dashboard/ts/components/Artifact/SelectedTrialArtifactCards.tsx b/optuna_dashboard/ts/components/Artifact/SelectedTrialArtifactCards.tsx index c663a7b87..501ed7e76 100644 --- a/optuna_dashboard/ts/components/Artifact/SelectedTrialArtifactCards.tsx +++ b/optuna_dashboard/ts/components/Artifact/SelectedTrialArtifactCards.tsx @@ -45,6 +45,15 @@ export const SelectedTrialArtifactCards: FC<{ const width = "200px" const height = "150px" + const targetValueIndex = 0 + const targetArtifactIndex = 0 + + const valueRanges = calculateMinMax(trials.map((trial) => trial.values)) + const direction = + study.directions[targetValueIndex] === "maximize" + ? "maximization" + : "minimization" + return ( <> {trials.map((trial) => { - const artifact = trial.artifacts[0] + const artifact = trial.artifacts[targetArtifactIndex] const urlPath = `/artifacts/${trial.study_id}/${trial.trial_id}/${artifact.artifact_id}` + + const value = trial.values + ? trial.values[targetValueIndex] + : valueRanges.min[targetValueIndex] + const borderValue = calculateBorderColor( + value, + valueRanges.min[0], + valueRanges.max[0], + direction + ) + const border = `5px solid ${borderValue}` return ( ) } + +type MinMaxResult = { + min: number[] + max: number[] +} + +function calculateMinMax(values: (number[] | undefined)[]): MinMaxResult { + if (values.length === 0) { + return { min: [], max: [] } + } + + const firstValidArray = values.find((arr) => arr !== undefined) + if (!firstValidArray) { + return { min: [], max: [] } + } + + const length = firstValidArray.length + const mins = new Array(length).fill(Infinity) + const maxs = new Array(length).fill(-Infinity) + + values.forEach((arr) => { + if (arr === undefined) return + + arr.forEach((value, index) => { + if (index < length) { + mins[index] = Math.min(mins[index], value) + maxs[index] = Math.max(maxs[index], value) + } + }) + }) + + const result: MinMaxResult = { + min: mins.map((val) => (val === Infinity ? 0 : val)), + max: maxs.map((val) => (val === -Infinity ? 0 : val)), + } + + return result +} + +type Direction = "minimization" | "maximization" + +function calculateBorderColor( + value: number, + minValue: number, + maxValue: number, + direction: Direction = "minimization" +): string { + if (minValue === maxValue) { + return "rgb(255, 255, 255)" + } + + let normalizedValue = (value - minValue) / (maxValue - minValue) + if (direction === "maximization") { + normalizedValue = 1 - normalizedValue + } + + const red = Math.round(255 * normalizedValue) + const green = Math.round(255 * normalizedValue) + const blue = 255 + + return `rgb(${red}, ${green}, ${blue})` +} From 6d148477a1e0f7f5cfcfb39ded5caeb888828609 Mon Sep 17 00:00:00 2001 From: hrntsm Date: Sun, 12 Jan 2025 13:43:03 +0900 Subject: [PATCH 3/4] Add target artifact and value selection for artifact cards --- .../Artifact/SelectedTrialArtifactCards.tsx | 71 +++++++++++++++---- 1 file changed, 58 insertions(+), 13 deletions(-) diff --git a/optuna_dashboard/ts/components/Artifact/SelectedTrialArtifactCards.tsx b/optuna_dashboard/ts/components/Artifact/SelectedTrialArtifactCards.tsx index 501ed7e76..108cd6d9b 100644 --- a/optuna_dashboard/ts/components/Artifact/SelectedTrialArtifactCards.tsx +++ b/optuna_dashboard/ts/components/Artifact/SelectedTrialArtifactCards.tsx @@ -5,11 +5,17 @@ import { Box, Card, CardContent, + FormControl, + FormLabel, IconButton, + MenuItem, + Select, + SelectChangeEvent, + Stack, Typography, useTheme, } from "@mui/material" -import React, { FC } from "react" +import React, { FC, useState } from "react" import { StudyDetail, Trial } from "ts/types/optuna" import { ArtifactCardMedia } from "./ArtifactCardMedia" @@ -34,6 +40,14 @@ export const SelectedTrialArtifactCards: FC<{ const isArtifactModifiable = (trial: Trial) => { return trial.state === "Running" || trial.state === "Waiting" } + const [targetArtifactId, setTargetArtifactId] = useState(0) + const [targetValueId, setTargetValueId] = useState(0) + const handleTargetArtifactChange = (event: SelectChangeEvent) => { + setTargetArtifactId(event.target.value as number) + } + const handleTargetValueChange = (event: SelectChangeEvent) => { + setTargetValueId(event.target.value as number) + } if (selectedTrials.length === 0) { selectedTrials = study.trials.map((trial) => trial.number) @@ -44,35 +58,66 @@ export const SelectedTrialArtifactCards: FC<{ ) const width = "200px" const height = "150px" - - const targetValueIndex = 0 - const targetArtifactIndex = 0 + const metricNames: string[] = study?.metric_names || [] const valueRanges = calculateMinMax(trials.map((trial) => trial.values)) const direction = - study.directions[targetValueIndex] === "maximize" + study.directions[targetValueId] === "maximize" ? "maximization" : "minimization" return ( <> - - Artifacts - + + Artifacts + + + Target Artifact Index: + + + + + Border Color Objective Value: + + + + {trials.map((trial) => { - const artifact = trial.artifacts[targetArtifactIndex] + const artifact = trial.artifacts[targetArtifactId] const urlPath = `/artifacts/${trial.study_id}/${trial.trial_id}/${artifact.artifact_id}` const value = trial.values - ? trial.values[targetValueIndex] - : valueRanges.min[targetValueIndex] + ? trial.values[targetValueId] + : valueRanges.min[targetValueId] const borderValue = calculateBorderColor( value, valueRanges.min[0], From 51caf0347c22941ce7401242888b187ccfad5fac Mon Sep 17 00:00:00 2001 From: hrntsm Date: Sun, 12 Jan 2025 13:53:13 +0900 Subject: [PATCH 4/4] Update artifact toggle to disable when no artifacts are available --- optuna_dashboard/ts/components/TrialSelection.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/optuna_dashboard/ts/components/TrialSelection.tsx b/optuna_dashboard/ts/components/TrialSelection.tsx index 6af318d3f..e5f9cc849 100644 --- a/optuna_dashboard/ts/components/TrialSelection.tsx +++ b/optuna_dashboard/ts/components/TrialSelection.tsx @@ -111,7 +111,7 @@ export const TrialSelection: FC<{ studyDetail: StudyDetail | null }> = ({ 1)} + disabled={studyDetail.trials[0].artifacts.length === 0} value="enable" /> }