From 4d6d1a1ef8b5f1b9f0523af9a952fce3cfd0ec2f Mon Sep 17 00:00:00 2001 From: c-bata Date: Sat, 7 Jan 2023 18:22:04 +0900 Subject: [PATCH] Add Checkboxes in ParallelCoordinate --- .../ts/components/GraphParallelCoordinate.tsx | 130 ++++++++++++------ 1 file changed, 89 insertions(+), 41 deletions(-) diff --git a/optuna_dashboard/ts/components/GraphParallelCoordinate.tsx b/optuna_dashboard/ts/components/GraphParallelCoordinate.tsx index 4b762efe..296becd1 100644 --- a/optuna_dashboard/ts/components/GraphParallelCoordinate.tsx +++ b/optuna_dashboard/ts/components/GraphParallelCoordinate.tsx @@ -1,6 +1,14 @@ import * as plotly from "plotly.js-dist-min" -import React, { FC, useEffect } from "react" -import { Typography, useTheme, Box, Grid } from "@mui/material" +import React, { FC, ReactNode, useEffect, useState } from "react" +import { + Typography, + useTheme, + Box, + Grid, + FormGroup, + FormControlLabel, + Checkbox, +} from "@mui/material" import { plotlyDarkTemplate } from "./PlotlyDarkMode" import { Target, @@ -11,19 +19,59 @@ import { const plotDomId = "graph-parallel-coordinate" -const useTargets = (study: StudyDetail | null): Target[] => { +const useTargets = (study: StudyDetail | null): [Target[], () => ReactNode] => { const [targets1, _target1, _setter1] = useObjectiveAndUserAttrTargets(study) const [targets2, _target2, _setter2] = useParamTargets( study?.intersection_search_space || [] ) - return [...targets1, ...targets2] + const [checked, setChecked] = useState([true]) + + const allTargets = [...targets1, ...targets2] + useEffect(() => { + if (allTargets.length !== checked.length) { + setChecked(allTargets.map((_) => true)) + } + }, [allTargets]) + + const handleOnChange = (event: React.ChangeEvent) => { + setChecked( + checked.map((c, i) => + i.toString() === event.target.name ? event.target.checked : c + ) + ) + } + + const renderCheckBoxes = (): ReactNode => ( + + {allTargets.map((t, i) => { + return ( + i ? checked[i] : true} + onChange={handleOnChange} + name={i.toString()} + /> + } + label={t.toLabel(study?.objective_names)} + /> + ) + })} + + ) + + const targets = allTargets.filter((t, i) => + checked.length > i ? checked[i] : true + ) + return [targets, renderCheckBoxes] } export const GraphParallelCoordinate: FC<{ study: StudyDetail | null }> = ({ study = null }) => { const theme = useTheme() - const targets = useTargets(study) + const [targets, renderCheckBoxes] = useTargets(study) const trials = useFilteredTrials(study, targets, false, false) useEffect(() => { @@ -42,12 +90,13 @@ export const GraphParallelCoordinate: FC<{ sx={{ paddingRight: theme.spacing(2), display: "flex", - flexDirection: "row", + flexDirection: "column", }} > Parallel Coordinate + {renderCheckBoxes()} @@ -75,7 +124,7 @@ const plotCoordinate = ( }, template: mode === "dark" ? plotlyDarkTemplate : {}, } - if (trials.length === 0) { + if (trials.length === 0 || targets.length === 0) { plotly.react(plotDomId, [], layout) return } @@ -98,46 +147,45 @@ const plotCoordinate = ( .join("") } - const dimensions = targets - .map((target) => { - if (target.kind === "objective" || target.kind === "user_attr") { - const values: number[] = trials.map( - (t) => target.getTargetValue(t) as number - ) + const dimensions = targets.map((target) => { + if (target.kind === "objective" || target.kind === "user_attr") { + const values: number[] = trials.map( + (t) => target.getTargetValue(t) as number + ) + return { + label: target.toLabel(study.objective_names), + values: values, + range: [Math.min(...values), Math.max(...values)], + } + } else { + const s = study.intersection_search_space.find( + (s) => s.name === target.key + ) as SearchSpaceItem // Must be already filtered. + + const values: number[] = trials.map( + (t) => target.getTargetValue(t) as number + ) + if (s.distribution.type !== "CategoricalDistribution") { return { - label: target.toLabel(study.objective_names), + label: breakLabelIfTooLong(s.name), values: values, - range: [Math.min(...values), Math.max(...values)], + range: [s.distribution.low, s.distribution.high], } } else { - const s = study.intersection_search_space.find( - (s) => s.name === target.key - ) as SearchSpaceItem // Must be already filtered. - - const values: number[] = trials.map( - (t) => target.getTargetValue(t) as number - ) - if (s.distribution.type !== "CategoricalDistribution") { - return { - label: breakLabelIfTooLong(s.name), - values: values, - range: [s.distribution.low, s.distribution.high], - } - } else { - // categorical - const vocabArr: string[] = s.distribution.choices.map((c) => c.value) - const tickvals: number[] = vocabArr.map((v, i) => i) - return { - label: breakLabelIfTooLong(s.name), - values: values, - range: [0, s.distribution.choices.length - 1], - // @ts-ignore - tickvals: tickvals, - ticktext: vocabArr, - } + // categorical + const vocabArr: string[] = s.distribution.choices.map((c) => c.value) + const tickvals: number[] = vocabArr.map((v, i) => i) + return { + label: breakLabelIfTooLong(s.name), + values: values, + range: [0, s.distribution.choices.length - 1], + // @ts-ignore + tickvals: tickvals, + ticktext: vocabArr, } } - }) + } + }) if (dimensions.length === 0) { console.log("Must not reach here.") plotly.react(plotDomId, [], layout)