From bcf570227e1ab1e323e63eef70ebc2c9ff33f96e Mon Sep 17 00:00:00 2001 From: keisuke-umezawa Date: Tue, 20 Aug 2024 23:01:37 +0900 Subject: [PATCH] Apply PlotHistory to optuna-dashboard --- .../ts/components/GraphHistory.tsx | 368 +----------------- 1 file changed, 5 insertions(+), 363 deletions(-) diff --git a/optuna_dashboard/ts/components/GraphHistory.tsx b/optuna_dashboard/ts/components/GraphHistory.tsx index 8a7fe3a9..4eb7a2cc 100644 --- a/optuna_dashboard/ts/components/GraphHistory.tsx +++ b/optuna_dashboard/ts/components/GraphHistory.tsx @@ -1,373 +1,15 @@ -import { - Box, - FormControl, - FormControlLabel, - FormLabel, - Grid, - MenuItem, - Radio, - RadioGroup, - Select, - SelectChangeEvent, - Slider, - Typography, - useTheme, -} from "@mui/material" -import { - Target, - useFilteredTrialsFromStudies, - useObjectiveAndUserAttrTargetsFromStudies, -} from "@optuna/react" -import * as Optuna from "@optuna/types" -import * as plotly from "plotly.js-dist-min" -import React, { ChangeEvent, FC, useEffect, useState } from "react" -import { useNavigate } from "react-router-dom" +import { useTheme } from "@mui/material" +import { PlotHistory } from "@optuna/react" +import React, { FC } from "react" import { StudyDetail } from "ts/types/optuna" -import { useConstants } from "../constantsProvider" import { usePlotlyColorTheme } from "../state" -const plotDomId = "graph-history" - -interface HistoryPlotInfo { - study_name: string - trials: Optuna.Trial[] - directions: Optuna.StudyDirection[] - metric_names?: string[] -} - export const GraphHistory: FC<{ studies: StudyDetail[] logScale: boolean includePruned: boolean -}> = ({ studies, logScale, includePruned }) => { - const { url_prefix } = useConstants() - +}> = ({ studies }) => { const theme = useTheme() const colorTheme = usePlotlyColorTheme(theme.palette.mode) - const navigate = useNavigate() - const [xAxis, setXAxis] = useState< - "number" | "datetime_start" | "datetime_complete" - >("number") - const [markerSize, setMarkerSize] = useState(5) - - const [targets, selected, setTarget] = - useObjectiveAndUserAttrTargetsFromStudies(studies) - - const trials = useFilteredTrialsFromStudies( - studies, - [selected], - !includePruned - ) - const historyPlotInfos = studies.map((study, index) => { - const h: HistoryPlotInfo = { - study_name: study?.name, - trials: trials[index], - directions: study?.directions, - metric_names: study?.metric_names, - } - return h - }) - - useEffect(() => { - plotHistory( - historyPlotInfos, - selected, - xAxis, - logScale, - theme.palette.mode, - colorTheme, - markerSize - ) - const element = document.getElementById(plotDomId) - if (element !== null && studies.length >= 1) { - // @ts-ignore - element.on("plotly_click", (data) => { - if (data.points[0].data.mode !== "lines") { - let studyId = 1 - if (data.points[0].data.name.includes("Infeasible Trial of")) { - const studyInfo: { id: number; name: string }[] = [] - studies.forEach((study) => { - studyInfo.push({ id: study.id, name: study.name }) - }) - const dataPointStudyName = data.points[0].data.name.replace( - "Infeasible Trial of ", - "" - ) - const targetId = studyInfo.find( - (s) => s.name === dataPointStudyName - )?.id - if (targetId !== undefined) { - studyId = targetId - } - } else { - studyId = studies[Math.floor(data.points[0].curveNumber / 2)].id - } - navigate( - url_prefix + - `/studies/${studyId}/trials?numbers=${data.points[0].x}` - ) - } - }) - return () => { - // @ts-ignore - element.removeAllListeners("plotly_click") - } - } - }, [ - studies, - selected, - logScale, - xAxis, - theme.palette.mode, - colorTheme, - markerSize, - ]) - - const handleObjectiveChange = (event: SelectChangeEvent) => { - setTarget(event.target.value) - } - - const handleXAxisChange = (e: ChangeEvent) => { - if (e.target.value === "number") { - setXAxis("number") - } else if (e.target.value === "datetime_start") { - setXAxis("datetime_start") - } else if (e.target.value === "datetime_complete") { - setXAxis("datetime_complete") - } - } - - return ( - - - - History - - {studies[0] !== null && targets.length >= 2 ? ( - - y Axis - - - ) : null} - - X-axis: - - } - label="Number" - /> - } - label="Datetime start" - /> - } - label="Datetime complete" - /> - - - - Marker size: - { - // @ts-ignore - setMarkerSize(e.target.value as number) - }} - /> - - - - - - - ) -} - -const plotHistory = ( - historyPlotInfos: HistoryPlotInfo[], - target: Target, - xAxis: "number" | "datetime_start" | "datetime_complete", - logScale: boolean, - mode: string, - colorTheme: Partial, - markerSize: number -) => { - if (document.getElementById(plotDomId) === null) { - return - } - if (historyPlotInfos.length === 0) { - plotly.react(plotDomId, [], { - template: colorTheme, - }) - return - } - - const layout: Partial = { - margin: { - l: 50, - t: 0, - r: 50, - b: 0, - }, - yaxis: { - title: target.toLabel(historyPlotInfos[0].metric_names), - type: logScale ? "log" : "linear", - }, - xaxis: { - title: xAxis === "number" ? "Trial" : "Time", - type: xAxis === "number" ? "linear" : "date", - }, - showlegend: historyPlotInfos.length === 1 ? false : true, - template: colorTheme, - legend: { - x: 1.0, - y: 0.95, - }, - } - - const getAxisX = (trial: Optuna.Trial): number | Date => { - return xAxis === "number" - ? trial.number - : xAxis === "datetime_start" - ? trial.datetime_start ?? new Date() - : trial.datetime_complete ?? new Date() - } - - const plotData: Partial[] = [] - const infeasiblePlotData: Partial[] = [] - historyPlotInfos.forEach((h) => { - const feasibleTrials: Optuna.Trial[] = [] - const infeasibleTrials: Optuna.Trial[] = [] - h.trials.forEach((t) => { - if (t.constraints.every((c) => c <= 0)) { - feasibleTrials.push(t) - } else { - infeasibleTrials.push(t) - } - }) - plotData.push({ - x: feasibleTrials.map(getAxisX), - y: feasibleTrials.map( - (t: Optuna.Trial): number => target.getTargetValue(t) as number - ), - name: `${target.toLabel(h.metric_names)} of ${h.study_name}`, - marker: { - size: markerSize, - }, - mode: "markers", - type: "scatter", - }) - - const objectiveId = target.getObjectiveId() - if (objectiveId !== null) { - const xForLinePlot: (number | Date)[] = [] - const yForLinePlot: number[] = [] - let currentBest: number | null = null - for (let i = 0; i < feasibleTrials.length; i++) { - const t = feasibleTrials[i] - const value = target.getTargetValue(t) as number - if (t.state !== "Complete") { - continue - } else if (currentBest === null) { - currentBest = value - xForLinePlot.push(getAxisX(t)) - yForLinePlot.push(value) - } else if ( - h.directions[objectiveId] === "maximize" && - value > currentBest - ) { - const p = feasibleTrials[i - 1] - if (!xForLinePlot.includes(getAxisX(p))) { - xForLinePlot.push(getAxisX(p)) - yForLinePlot.push(currentBest) - } - currentBest = value - xForLinePlot.push(getAxisX(t)) - yForLinePlot.push(value) - } else if ( - h.directions[objectiveId] === "minimize" && - value < currentBest - ) { - const p = feasibleTrials[i - 1] - if (!xForLinePlot.includes(getAxisX(p))) { - xForLinePlot.push(getAxisX(p)) - yForLinePlot.push(currentBest) - } - currentBest = value - xForLinePlot.push(getAxisX(t)) - yForLinePlot.push(value) - } - } - if (h.trials.length !== 0) { - xForLinePlot.push(getAxisX(h.trials[h.trials.length - 1])) - yForLinePlot.push(yForLinePlot[yForLinePlot.length - 1]) - } - plotData.push({ - x: xForLinePlot, - y: yForLinePlot, - name: `Best Value of ${h.study_name}`, - mode: "lines", - type: "scatter", - }) - } - infeasiblePlotData.push({ - x: infeasibleTrials.map(getAxisX), - y: infeasibleTrials.map( - (t: Optuna.Trial): number => target.getTargetValue(t) as number - ), - name: `Infeasible Trial of ${h.study_name}`, - marker: { - size: markerSize, - color: mode === "dark" ? "#666666" : "#cccccc", - }, - mode: "markers", - type: "scatter", - showlegend: false, - }) - }) - plotData.push(...infeasiblePlotData) - plotly.react(plotDomId, plotData, layout) + return }