import * as plotly from "plotly.js-dist-min" import React, { ChangeEvent, FC, useEffect, useState } from "react" import { Box, Grid, FormControl, FormLabel, FormControlLabel, MenuItem, Select, Radio, RadioGroup, Typography, SelectChangeEvent, useTheme, Slider, } from "@mui/material" import { plotlyDarkTemplate } from "./PlotlyDarkMode" import { useFilteredTrialsFromStudies, Target, useObjectiveAndUserAttrTargetsFromStudies, } from "../trialFilter" import { useNavigate } from "react-router-dom" const plotDomId = "graph-history" interface HistoryPlotInfo { study_name: string trials: Trial[] directions: StudyDirection[] objective_names?: string[] } export const GraphHistory: FC<{ studies: StudyDetail[] logScale: boolean includePruned: boolean }> = ({ studies, logScale, includePruned }) => { const theme = useTheme() 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, objective_names: study?.objective_names, } return h }) useEffect(() => { plotHistory( historyPlotInfos, selected, xAxis, logScale, theme.palette.mode, 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, 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, markerSize: number ) => { if (document.getElementById(plotDomId) === null) { return } if (historyPlotInfos.length === 0) { plotly.react(plotDomId, [], { template: mode === "dark" ? plotlyDarkTemplate : {}, }) return } const layout: Partial = { margin: { l: 50, t: 0, r: 50, b: 0, }, yaxis: { title: target.toLabel(historyPlotInfos[0].objective_names), type: logScale ? "log" : "linear", }, xaxis: { title: xAxis === "number" ? "Trial" : "Time", type: xAxis === "number" ? "linear" : "date", }, showlegend: historyPlotInfos.length === 1 ? false : true, template: mode === "dark" ? plotlyDarkTemplate : {}, } const getAxisX = (trial: 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: Trial[] = [] const infeasibleTrials: 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: Trial): number => target.getTargetValue(t) as number ), name: `${target.toLabel(h.objective_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 (value === null) { 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: 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) }