import * as plotly from "plotly.js-dist" import React, { FC, useEffect, useState } from "react" import { Grid, FormControl, FormLabel, MenuItem, Select, Typography, SelectChangeEvent, useTheme, } from "@mui/material" import {plotlyDarkTemplate} from "./PlotlyDarkMode"; const plotDomId = "graph-parallel-coordinate" export const GraphParallelCoordinate: FC<{ study: StudyDetail | null }> = ({ study = null }) => { const theme = useTheme() const [objectiveId, setObjectiveId] = useState(0) const handleObjectiveChange = (event: SelectChangeEvent) => { setObjectiveId(event.target.value as number) } useEffect(() => { if (study !== null) { plotCoordinate(study, objectiveId) } }, [study, objectiveId]) return ( Parallel Coordinate {study !== null && study.directions.length !== 1 ? ( Objective ID: ) : null}
) } const plotCoordinate = (study: StudyDetail, objectiveId: number) => { if (document.getElementById(plotDomId) === null) { return } const layout: Partial = { margin: { l: 70, t: 100, r: 50, b: 0, }, template: plotlyDarkTemplate, } if (study.trials.length === 0) { plotly.react(plotDomId, [], layout) return } const filteredTrials = study.trials.filter( (t) => t.state === "Complete" || (t.state === "Pruned" && t.values && t.values.length > 0) ) const maxLabelLength = 40 const breakLength = maxLabelLength / 2 const ellipsis = "…" const truncateLabelIfTooLong = (originalLabel: string): string => { return originalLabel.length > maxLabelLength ? originalLabel.substring(0, maxLabelLength - ellipsis.length) + ellipsis : originalLabel } const breakLabelIfTooLong = (originalLabel: string): string => { const truncated = truncateLabelIfTooLong(originalLabel) return truncated .split("") .map((c, i) => { return (i + 1) % breakLength == 0 ? c + "
" : c }) .join("") } // Intersection param names const objectiveValues: number[] = filteredTrials.map( (t) => t.values![objectiveId] ) const dimensions = [ { label: "Objective value", values: objectiveValues, range: [Math.min(...objectiveValues), Math.max(...objectiveValues)], }, ] study.intersection_search_space.forEach((s) => { const valueStrings = filteredTrials.map((t) => { const param = t.params.find((p) => p.name === s.name) return param!.value }) const isnum = valueStrings.every((v) => { return !isNaN(parseFloat(v)) }) if (isnum) { const values: number[] = valueStrings.map((v) => parseFloat(v)) dimensions.push({ label: breakLabelIfTooLong(s.name), values: values, range: [Math.min(...values), Math.max(...values)], }) } else { // categorical const vocabSet = new Set(valueStrings) const vocabArr = Array.from(vocabSet) const values: number[] = valueStrings.map((v) => vocabArr.findIndex((vocab) => v === vocab) ) const tickvals: number[] = vocabArr.map((v, i) => i) dimensions.push({ label: breakLabelIfTooLong(s.name), values: values, range: [Math.min(...values), Math.max(...values)], // @ts-ignore tickvals: tickvals, ticktext: vocabArr, }) } }) const plotData: Partial[] = [ { type: "parcoords", // @ts-ignore dimensions: dimensions, labelangle: 30, }, ] plotly.react(plotDomId, plotData, layout) }