From 413ba0b6ef67b1cc9b62d244a98b7e253ad42a7d Mon Sep 17 00:00:00 2001 From: porink0424 Date: Fri, 29 Mar 2024 10:10:23 +0900 Subject: [PATCH] Delete some components from standalone_app --- standalone_app/package-lock.json | 2 +- standalone_app/src/components/DataGrid.tsx | 383 ------------------ standalone_app/src/components/PlotHistory.tsx | 312 -------------- standalone_app/src/components/StudyDetail.tsx | 3 +- standalone_app/src/components/TrialTable.tsx | 155 ------- standalone_app/src/types/index.d.ts | 2 +- tslib/storybook/package-lock.json | 3 +- tslib/storybook/package.json | 2 +- 8 files changed, 5 insertions(+), 857 deletions(-) delete mode 100644 standalone_app/src/components/DataGrid.tsx delete mode 100644 standalone_app/src/components/PlotHistory.tsx delete mode 100644 standalone_app/src/components/TrialTable.tsx diff --git a/standalone_app/package-lock.json b/standalone_app/package-lock.json index eff8fb2d..2e92af0d 100644 --- a/standalone_app/package-lock.json +++ b/standalone_app/package-lock.json @@ -61,6 +61,7 @@ "@mui/icons-material": "^5.15.10", "@mui/material": "^5.15.10", "@mui/system": "^5.15.9", + "@optuna/storage": "../storage/", "plotly.js-dist-min": "^2.30.1", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -69,7 +70,6 @@ }, "devDependencies": { "@biomejs/biome": "1.5.3", - "@optuna/storage": "../storage/", "@optuna/types": "../types/", "@storybook/addon-essentials": "^8.0.4", "@storybook/addon-interactions": "^8.0.4", diff --git a/standalone_app/src/components/DataGrid.tsx b/standalone_app/src/components/DataGrid.tsx deleted file mode 100644 index 1d91883d..00000000 --- a/standalone_app/src/components/DataGrid.tsx +++ /dev/null @@ -1,383 +0,0 @@ -import { Clear } from "@mui/icons-material" -import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown" -import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp" -import { - Collapse, - IconButton, - Table, - TableBody, - TableCell, - TableContainer, - TableHead, - TablePagination, - TableRow, - TableSortLabel, - useTheme, -} from "@mui/material" -import { styled } from "@mui/system" -import React from "react" - -type Order = "asc" | "desc" - -// biome-ignore lint/suspicious/noExplicitAny: -type Value = any - -const defaultRowsPerPageOption = [10, 50, 100, { label: "All", value: -1 }] - -interface DataGridColumn { - field: keyof T - label: string - sortable?: boolean - less?: (a: T, b: T, ascending: boolean) => number - filterable?: boolean - toCellValue?: (rowIndex: number) => string | React.ReactNode - padding?: "normal" | "checkbox" | "none" -} - -interface RowFilter { - columnIdx: number - value: Value -} - -function DataGrid(props: { - columns: DataGridColumn[] - rows: T[] - keyField: keyof T - dense?: boolean - collapseBody?: (rowIndex: number) => React.ReactNode - initialRowsPerPage?: number - rowsPerPageOption?: Array - defaultFilter?: (row: T) => boolean -}): React.ReactElement { - const { columns, rows, keyField, dense, collapseBody, defaultFilter } = props - let { initialRowsPerPage, rowsPerPageOption } = props - const [order, setOrder] = React.useState("asc") - const [orderBy, setOrderBy] = React.useState(0) // index of columns - const [page, setPage] = React.useState(0) - const [filters, setFilters] = React.useState([]) - - const getRowIndex = (row: T): number => { - return rows.findIndex((row2) => row[keyField] === row2[keyField]) - } - - // Pagination - rowsPerPageOption = rowsPerPageOption || defaultRowsPerPageOption - initialRowsPerPage = initialRowsPerPage // use first element as default - ? initialRowsPerPage - : isNumber(rowsPerPageOption[0]) - ? rowsPerPageOption[0] - : rowsPerPageOption[0].value - const [rowsPerPage, setRowsPerPage] = React.useState(initialRowsPerPage) - - const handleChangePage = (event: unknown, newPage: number) => { - setPage(newPage) - } - - const handleChangeRowsPerPage = ( - event: React.ChangeEvent - ) => { - setRowsPerPage(parseInt(event.target.value, 10)) - setPage(0) - } - - // Filtering - const fieldAlreadyFiltered = (columnIdx: number): boolean => - filters.some((f) => f.columnIdx === columnIdx) - - const handleClickFilterCell = (columnIdx: number, value: Value) => { - if (fieldAlreadyFiltered(columnIdx)) { - return - } - const newFilters = [...filters, { columnIdx: columnIdx, value: value }] - setFilters(newFilters) - } - - const clearFilter = (columnIdx: number): void => { - setFilters(filters.filter((f) => f.columnIdx !== columnIdx)) - } - - const filteredRows = rows.filter((row, rowIdx) => { - if (defaultFilter?.(row)) { - return false - } - return filters.length === 0 - ? true - : filters.some((f) => { - if (columns.length <= f.columnIdx) { - console.log( - `columnIdx=${f.columnIdx} must be smaller than columns.length=${columns.length}` - ) - return true - } - const toCellValue = columns[f.columnIdx].toCellValue - if (toCellValue !== undefined) { - return toCellValue(rowIdx) === f.value - } - const field = columns[f.columnIdx].field - return row[field] === f.value - }) - }) - - // Sorting - const createSortHandler = (columnId: number) => () => { - const isAsc = orderBy === columnId && order === "asc" - setOrder(isAsc ? "desc" : "asc") - setOrderBy(columnId) - } - const sortedRows = stableSort(filteredRows, order, orderBy, columns) - const currentPageRows = - rowsPerPage > 0 - ? sortedRows.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) - : sortedRows - const emptyRows = - rowsPerPage - Math.min(rowsPerPage, sortedRows.length - page * rowsPerPage) - - const RootDiv = styled("div")({ - width: "100%", - }) - const HiddenSpan = styled("span")({ - border: 0, - clip: "rect(0 0 0 0)", - height: 1, - margin: -1, - overflow: "hidden", - padding: 0, - position: "absolute", - top: 20, - width: 1, - }) - const TableHeaderCellSpan = styled("span")({ - display: "inline-flex", - }) - return ( - - - - - - {collapseBody ? : null} - {columns.map((column, columnIdx) => ( - - key={columnIdx} - padding={column.padding || "normal"} - sortDirection={orderBy === column.field ? order : false} - > - - {column.sortable ? ( - - {column.label} - {orderBy === column.field ? ( - - {order === "desc" - ? "sorted descending" - : "sorted ascending"} - - ) : null} - - ) : ( - column.label - )} - {column.filterable ? ( - { - clearFilter(columnIdx) - }} - > - - - ) : null} - - - ))} - - - - {currentPageRows.map((row) => ( - - columns={columns} - rowIndex={getRowIndex(row)} - row={row} - keyField={keyField} - collapseBody={collapseBody} - key={`${row[keyField]}`} - handleClickFilterCell={handleClickFilterCell} - /> - ))} - {emptyRows > 0 && ( - - - - )} - -
-
- -
- ) -} - -function DataGridRow(props: { - columns: DataGridColumn[] - rowIndex: number - row: T - keyField: keyof T - collapseBody?: (rowIndex: number) => React.ReactNode - handleClickFilterCell: (columnIdx: number, value: Value) => void -}) { - const { - columns, - rowIndex, - row, - keyField, - collapseBody, - handleClickFilterCell, - } = props - const [open, setOpen] = React.useState(false) - const theme = useTheme() - - const FilterableDiv = styled("div")({ - color: theme.palette.primary.main, - textDecoration: "underline", - cursor: "pointer", - }) - return ( - - - {collapseBody ? ( - - setOpen(!open)} - > - {open ? : } - - - ) : null} - {columns.map((column, columnIndex) => { - const cellItem = column.toCellValue - ? column.toCellValue(rowIndex) - : // TODO(c-bata): Avoid this implicit type conversion. - (row[column.field] as number | string | null | undefined) - - return column.filterable ? ( - { - const value = - column.toCellValue !== undefined - ? column.toCellValue(rowIndex) - : row[column.field] - handleClickFilterCell(columnIndex, value) - }} - > - {cellItem} - - ) : ( - - {cellItem} - - ) - })} - - {collapseBody ? ( - - - - {collapseBody(rowIndex)} - - - - ) : null} - - ) -} - -function getComparator( - order: Order, - columns: DataGridColumn[], - orderBy: number -): (a: T, b: T) => number { - return order === "desc" - ? (a, b) => descendingComparator(a, b, columns, orderBy) - : (a, b) => -descendingComparator(a, b, columns, orderBy) -} - -function descendingComparator( - a: T, - b: T, - columns: DataGridColumn[], - orderBy: number -): number { - const field = columns[orderBy].field - if (b[field] < a[field]) { - return -1 - } - if (b[field] > a[field]) { - return 1 - } - return 0 -} - -function stableSort( - array: T[], - order: Order, - orderBy: number, - columns: DataGridColumn[] -) { - // TODO(c-bata): Refactor here by implementing as the same comparator interface. - const less = columns[orderBy].less - const comparator = getComparator(order, columns, orderBy) - const stabilizedThis = array.map((el, index) => [el, index] as [T, number]) - stabilizedThis.sort((a, b) => { - if (less) { - const ascending = order === "asc" - const result = ascending - ? -less(a[0], b[0], ascending) - : less(a[0], b[0], ascending) - if (result !== 0) return result - } else { - const result = comparator(a[0], b[0]) - if (result !== 0) return result - } - return a[1] - b[1] - }) - return stabilizedThis.map((el) => el[0]) -} - -const isNumber = ( - rowsPerPage: number | { value: number; label: string } -): rowsPerPage is number => { - return typeof rowsPerPage === "number" -} - -export { DataGrid } -export type { DataGridColumn } diff --git a/standalone_app/src/components/PlotHistory.tsx b/standalone_app/src/components/PlotHistory.tsx deleted file mode 100644 index 44a65e49..00000000 --- a/standalone_app/src/components/PlotHistory.tsx +++ /dev/null @@ -1,312 +0,0 @@ -import { - Checkbox, - FormControl, - FormControlLabel, - FormLabel, - Grid, - MenuItem, - Radio, - RadioGroup, - Select, - SelectChangeEvent, - Switch, - Typography, - useTheme, -} from "@mui/material" -import * as plotly from "plotly.js-dist-min" -import React, { ChangeEvent, FC, useEffect, useState } from "react" -import { plotlyDarkTemplate } from "../PlotlyDarkMode" - -const plotDomId = "plot-history" - -export const PlotHistory: FC<{ - study: Study | null -}> = ({ study = null }) => { - const theme = useTheme() - const [xAxis, setXAxis] = useState("number") - const [objectiveId, setObjectiveId] = useState(0) - const [logScale, setLogScale] = useState(false) - const [filterCompleteTrial, setFilterCompleteTrial] = useState(false) - const [filterPrunedTrial, setFilterPrunedTrial] = useState(false) - - const handleObjectiveChange = (event: SelectChangeEvent) => { - setObjectiveId(event.target.value as number) - } - - const handleXAxisChange = (e: ChangeEvent) => { - setXAxis(e.target.value) - } - - const handleLogScaleChange = () => { - setLogScale(!logScale) - } - - const handleFilterCompleteChange = () => { - setFilterCompleteTrial(!filterCompleteTrial) - } - - const handleFilterPrunedChange = () => { - setFilterPrunedTrial(!filterPrunedTrial) - } - - useEffect(() => { - if (study !== null) { - plotHistory( - study, - objectiveId, - xAxis, - logScale, - filterCompleteTrial, - filterPrunedTrial, - theme.palette.mode - ) - } - }, [ - study, - objectiveId, - logScale, - xAxis, - filterPrunedTrial, - filterCompleteTrial, - theme.palette.mode, - ]) - - return ( - - - - History - - {study !== null && study.directions.length !== 1 ? ( - - Objective ID: - - - ) : null} - - Log y scale: - - - - Filter state: - - } - label="Complete" - /> - - } - label="Pruned" - /> - - - X-axis: - - } - label="Number" - /> - } - label="Datetime start" - /> - } - label="Datetime complete" - /> - - - - -
- - - ) -} - -const filterFunc = (trial: Trial, objectiveId: number): boolean => { - if (trial.state !== "Complete" && trial.state !== "Pruned") { - return false - } - if (trial.values === undefined) { - return false - } - return ( - trial.values.length > objectiveId && - trial.values[objectiveId] !== Infinity && - trial.values[objectiveId] !== -Infinity - ) -} - -const plotHistory = ( - study: Study, - objectiveId: number, - xAxis: string, - logScale: boolean, - filterCompleteTrial: boolean, - filterPrunedTrial: boolean, - mode: string -) => { - if (document.getElementById(plotDomId) === null) { - return - } - - const layout: Partial = { - margin: { - l: 50, - t: 0, - r: 50, - b: 0, - }, - yaxis: { - title: "Objective Value", - type: logScale ? "log" : "linear", - }, - xaxis: { - title: xAxis === "number" ? "Trial" : "Time", - type: xAxis === "number" ? "linear" : "date", - }, - showlegend: true, - template: mode === "dark" ? plotlyDarkTemplate : {}, - } - - let filteredTrials = study.trials.filter((t) => filterFunc(t, objectiveId)) - if (filterCompleteTrial) { - filteredTrials = filteredTrials.filter((t) => t.state !== "Complete") - } - if (filterPrunedTrial) { - filteredTrials = filteredTrials.filter((t) => t.state !== "Pruned") - } - if (filteredTrials.length === 0) { - plotly.react(plotDomId, [], layout) - return - } - - 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 getValue = (trial: Trial, objectiveId: number): number | null => { - if ( - objectiveId === null || - trial.values === undefined || - trial.values.length <= objectiveId - ) { - return null - } - const value = trial.values[objectiveId] - if (value === Infinity || value === -Infinity) { - return null - } - return value - } - - const xForLinePlot: (number | Date)[] = [] - const yForLinePlot: number[] = [] - let currentBest: number | null = null - for (let i = 0; i < filteredTrials.length; i++) { - const t = filteredTrials[i] - const v = getValue(t, objectiveId) as number - if (currentBest === null) { - currentBest = v - xForLinePlot.push(getAxisX(t)) - yForLinePlot.push(v) - } else if ( - study.directions[objectiveId] === "maximize" && - v > currentBest - ) { - const p = filteredTrials[i - 1] - if (!xForLinePlot.includes(getAxisX(p))) { - xForLinePlot.push(getAxisX(p)) - yForLinePlot.push(currentBest) - } - currentBest = v - xForLinePlot.push(getAxisX(t)) - yForLinePlot.push(v) - } else if ( - study.directions[objectiveId] === "minimize" && - v < currentBest - ) { - const p = filteredTrials[i - 1] - if (!xForLinePlot.includes(getAxisX(p))) { - xForLinePlot.push(getAxisX(p)) - yForLinePlot.push(currentBest) - } - currentBest = v - xForLinePlot.push(getAxisX(t)) - yForLinePlot.push(v) - } - } - xForLinePlot.push(getAxisX(filteredTrials[filteredTrials.length - 1])) - yForLinePlot.push(yForLinePlot[yForLinePlot.length - 1]) - - const plotData: Partial[] = [ - { - x: filteredTrials.map(getAxisX), - y: filteredTrials.map( - (t: Trial): number => getValue(t, objectiveId) as number - ), - name: "Objective Value", - mode: "markers", - type: "scatter", - }, - { - x: xForLinePlot, - y: yForLinePlot, - name: "Best Value", - mode: "lines", - type: "scatter", - }, - ] - plotly.react(plotDomId, plotData, layout) -} diff --git a/standalone_app/src/components/StudyDetail.tsx b/standalone_app/src/components/StudyDetail.tsx index 2cb0baf6..392cf553 100644 --- a/standalone_app/src/components/StudyDetail.tsx +++ b/standalone_app/src/components/StudyDetail.tsx @@ -15,11 +15,10 @@ import { import Grid2 from "@mui/material/Unstable_Grid2" import React, { FC, useContext, useState, useEffect } from "react" import { Link, useParams } from "react-router-dom" -import { PlotHistory } from "./PlotHistory" import { PlotImportance } from "./PlotImportance" import { PlotIntermediateValues } from "./PlotIntermediateValues" import { StorageContext } from "./StorageProvider" -import { TrialTable } from "./TrialTable" +import { PlotHistory, TrialTable } from "@optuna/storybook" export const StudyDetail: FC<{ toggleColorMode: () => void diff --git a/standalone_app/src/components/TrialTable.tsx b/standalone_app/src/components/TrialTable.tsx deleted file mode 100644 index 6cea0228..00000000 --- a/standalone_app/src/components/TrialTable.tsx +++ /dev/null @@ -1,155 +0,0 @@ -import React, { FC } from "react" -import { DataGrid } from "@optuna/storybook" -import { DataGridColumn } from "@optuna/storybook/types" - -export const TrialTable: FC<{ - study: Study - initialRowsPerPage?: number -}> = ({ study, initialRowsPerPage }) => { - const trials: Trial[] = study.trials - - const columns: DataGridColumn[] = [ - { field: "number", label: "Number", sortable: true, padding: "none" }, - { - field: "state", - label: "State", - sortable: true, - filterable: true, - padding: "none", - toCellValue: (i) => trials[i].state.toString(), - }, - ] - - if (study === null || study.directions.length === 1) { - columns.push({ - field: "values", - label: "Value", - sortable: true, - less: (firstEl, secondEl, ascending): number => { - const firstVal = firstEl.values?.[0] - const secondVal = secondEl.values?.[0] - - if (firstVal === secondVal) { - return 0 - } - if (firstVal === undefined) { - return ascending ? -1 : 1 - } - if (secondVal === undefined) { - return ascending ? 1 : -1 - } - return firstVal < secondVal ? 1 : -1 - }, - toCellValue: (i) => { - if (trials[i].values === undefined) { - return null - } - return trials[i].values?.[0] - }, - }) - } else { - const objectiveColumns: DataGridColumn[] = study.directions.map( - (s, objectiveId) => ({ - field: "values", - label: `Objective ${objectiveId}`, - sortable: true, - less: (firstEl, secondEl, ascending): number => { - const firstVal = firstEl.values?.[objectiveId] - const secondVal = secondEl.values?.[objectiveId] - - if (firstVal === secondVal) { - return 0 - } - if (firstVal === undefined) { - return ascending ? -1 : 1 - } - if (secondVal === undefined) { - return ascending ? 1 : -1 - } - return firstVal < secondVal ? 1 : -1 - }, - toCellValue: (i) => { - if (trials[i].values === undefined) { - return null - } - return trials[i].values?.[objectiveId] - }, - }) - ) - columns.push(...objectiveColumns) - } - - // biome-ignore lint/complexity/noForEach: - study.union_search_space.forEach((s) => { - columns.push({ - field: "params", - label: `Param ${s.name}`, - toCellValue: (i) => - trials[i].params.find((p) => p.name === s.name)?.param_external_value ?? - null, - sortable: true, - filterable: false, - less: (firstEl, secondEl): number => { - const firstVal = firstEl.params.find( - (p) => p.name === s.name - )?.param_internal_value - const secondVal = secondEl.params.find( - (p) => p.name === s.name - )?.param_internal_value - - if (firstVal === secondVal) { - return 0 - } - if (firstVal && secondVal) { - return firstVal < secondVal ? 1 : -1 - } - if (firstVal) { - return -1 - } - return 1 - }, - }) - }) - - // biome-ignore lint/complexity/noForEach: - study.union_user_attrs.forEach((attr_spec) => { - columns.push({ - field: "user_attrs", - label: `UserAttribute ${attr_spec.key}`, - toCellValue: (i) => - trials[i].user_attrs.find((attr) => attr.key === attr_spec.key) - ?.value || null, - sortable: attr_spec.sortable, - filterable: false, - less: (firstEl, secondEl): number => { - const firstVal = firstEl.user_attrs.find( - (attr) => attr.key === attr_spec.key - )?.value - const secondVal = secondEl.user_attrs.find( - (attr) => attr.key === attr_spec.key - )?.value - - if (firstVal === secondVal) { - return 0 - } - if (firstVal && secondVal) { - return firstVal < secondVal ? 1 : -1 - } - if (firstVal) { - return -1 - } - return 1 - }, - }) - }) - - return ( - - columns={columns} - rows={trials} - keyField={"trial_id"} - dense={false} - initialRowsPerPage={initialRowsPerPage} - /> - ) -} diff --git a/standalone_app/src/types/index.d.ts b/standalone_app/src/types/index.d.ts index 1d258f36..eb1f7e59 100644 --- a/standalone_app/src/types/index.d.ts +++ b/standalone_app/src/types/index.d.ts @@ -2,7 +2,7 @@ declare const IS_VSCODE: boolean type TrialState = "Running" | "Complete" | "Pruned" | "Fail" | "Waiting" type TrialStateFinished = "Complete" | "Fail" | "Pruned" -type StudyDirection = "maximize" | "minimize" | "not_set" +type StudyDirection = "maximize" | "minimize" type OptunaStorage = { getStudies: () => Promise diff --git a/tslib/storybook/package-lock.json b/tslib/storybook/package-lock.json index 81483739..e58dfeee 100644 --- a/tslib/storybook/package-lock.json +++ b/tslib/storybook/package-lock.json @@ -14,6 +14,7 @@ "@mui/icons-material": "^5.15.10", "@mui/material": "^5.15.10", "@mui/system": "^5.15.9", + "@optuna/storage": "../storage/", "plotly.js-dist-min": "^2.30.1", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -22,7 +23,6 @@ }, "devDependencies": { "@biomejs/biome": "1.5.3", - "@optuna/storage": "../storage/", "@optuna/types": "../types/", "@storybook/addon-essentials": "^8.0.4", "@storybook/addon-interactions": "^8.0.4", @@ -43,7 +43,6 @@ "../storage": { "name": "@optuna/storage", "version": "0.0.1", - "dev": true, "license": "MIT", "dependencies": { "@sqlite.org/sqlite-wasm": "^3.45.1-build1" diff --git a/tslib/storybook/package.json b/tslib/storybook/package.json index da76622b..a1547eb4 100644 --- a/tslib/storybook/package.json +++ b/tslib/storybook/package.json @@ -33,6 +33,7 @@ "@mui/icons-material": "^5.15.10", "@mui/material": "^5.15.10", "@mui/system": "^5.15.9", + "@optuna/storage": "../storage/", "plotly.js-dist-min": "^2.30.1", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -41,7 +42,6 @@ }, "devDependencies": { "@biomejs/biome": "1.5.3", - "@optuna/storage": "../storage/", "@optuna/types": "../types/", "@storybook/addon-essentials": "^8.0.4", "@storybook/addon-interactions": "^8.0.4",