mirror of
https://github.com/wassname/optuna-dashboard.git
synced 2026-09-11 12:30:25 +08:00
Copy type script files from Goptuna
This commit is contained in:
@@ -0,0 +1 @@
|
||||
14.14.0
|
||||
@@ -0,0 +1,4 @@
|
||||
trailingComma: "es5"
|
||||
tabWidth: 2
|
||||
semi: false
|
||||
singleQuote: false
|
||||
@@ -0,0 +1,75 @@
|
||||
import { useRecoilState } from "recoil"
|
||||
import { useSnackbar } from "notistack"
|
||||
import {
|
||||
getStudyDetailAPI,
|
||||
getStudySummariesAPI,
|
||||
createNewStudyAPI,
|
||||
} from "./apiClient"
|
||||
import { studyDetailsState, studySummariesState } from "./state"
|
||||
|
||||
export const actionCreator = () => {
|
||||
const { enqueueSnackbar } = useSnackbar()
|
||||
const [studySummaries, setStudySummaries] = useRecoilState<StudySummary[]>(
|
||||
studySummariesState
|
||||
)
|
||||
const [studyDetails, setStudyDetails] = useRecoilState<StudyDetails>(
|
||||
studyDetailsState
|
||||
)
|
||||
|
||||
const updateStudySummaries = (successMsg?: string) => {
|
||||
getStudySummariesAPI()
|
||||
.then((studySummaries: StudySummary[]) => {
|
||||
setStudySummaries(studySummaries)
|
||||
|
||||
if (successMsg) {
|
||||
enqueueSnackbar(successMsg, { variant: "success" })
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
enqueueSnackbar(`Failed to fetch study list.`, {
|
||||
variant: "error",
|
||||
})
|
||||
console.log(err)
|
||||
})
|
||||
}
|
||||
|
||||
const updateStudyDetail = (studyId: number) => {
|
||||
getStudyDetailAPI(studyId)
|
||||
.then((study) => {
|
||||
let newVal = Object.assign({}, studyDetails)
|
||||
newVal[studyId] = study
|
||||
setStudyDetails(newVal)
|
||||
})
|
||||
.catch((err) => {
|
||||
enqueueSnackbar(`Failed to fetch study (id=${studyId})`, {
|
||||
variant: "error",
|
||||
})
|
||||
console.log(err)
|
||||
})
|
||||
}
|
||||
|
||||
const createNewStudy = (studyName: string, direction: StudyDirection) => {
|
||||
createNewStudyAPI(studyName, direction)
|
||||
.then((study_summary) => {
|
||||
const newVal = [...studySummaries, study_summary]
|
||||
setStudySummaries(newVal)
|
||||
enqueueSnackbar(`Success to create a study (study_name=${studyName})`, {
|
||||
variant: "success",
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
enqueueSnackbar(`Failed to create a study (study_name=${studyName})`, {
|
||||
variant: "error",
|
||||
})
|
||||
console.log(err)
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
updateStudyDetail,
|
||||
updateStudySummaries,
|
||||
createNewStudy,
|
||||
}
|
||||
}
|
||||
|
||||
export type Action = ReturnType<typeof actionCreator>
|
||||
@@ -0,0 +1,138 @@
|
||||
import axios from "axios"
|
||||
|
||||
const axiosInstance = axios.create({ baseURL: API_ENDPOINT })
|
||||
|
||||
interface TrialResponse {
|
||||
trial_id: number
|
||||
study_id: number
|
||||
number: number
|
||||
state: TrialState
|
||||
value?: number
|
||||
intermediate_values: TrialIntermediateValue[]
|
||||
datetime_start: string
|
||||
datetime_complete?: string
|
||||
params: TrialParam[]
|
||||
user_attrs: Attribute[]
|
||||
system_attrs: Attribute[]
|
||||
}
|
||||
|
||||
const convertTrialResponse = (res: TrialResponse): Trial => {
|
||||
return {
|
||||
trial_id: res.trial_id,
|
||||
study_id: res.study_id,
|
||||
number: res.number,
|
||||
state: res.state,
|
||||
value: res.value,
|
||||
intermediate_values: res.intermediate_values,
|
||||
datetime_start: new Date(res.datetime_start),
|
||||
datetime_complete: res.datetime_complete
|
||||
? new Date(res.datetime_complete)
|
||||
: undefined,
|
||||
params: res.params,
|
||||
user_attrs: res.user_attrs,
|
||||
system_attrs: res.system_attrs,
|
||||
}
|
||||
}
|
||||
|
||||
interface StudyDetailResponse {
|
||||
name: string
|
||||
datetime_start: string
|
||||
direction: StudyDirection
|
||||
best_trial?: TrialResponse
|
||||
trials: TrialResponse[]
|
||||
}
|
||||
|
||||
export const getStudyDetailAPI = (studyId: number): Promise<StudyDetail> => {
|
||||
return axiosInstance
|
||||
.get<StudyDetailResponse>(`/api/studies/${studyId}`, {})
|
||||
.then((res) => {
|
||||
const trials = res.data.trials.map(
|
||||
(trial): Trial => {
|
||||
return convertTrialResponse(trial)
|
||||
}
|
||||
)
|
||||
return {
|
||||
name: res.data.name,
|
||||
datetime_start: new Date(res.data.datetime_start),
|
||||
direction: res.data.direction,
|
||||
best_trial: res.data.best_trial
|
||||
? convertTrialResponse(res.data.best_trial)
|
||||
: undefined,
|
||||
trials: trials,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
interface StudySummariesResponse {
|
||||
study_summaries: {
|
||||
study_id: number
|
||||
study_name: string
|
||||
direction: StudyDirection
|
||||
best_trial?: {
|
||||
trial_id: number
|
||||
study_id: number
|
||||
number: number
|
||||
state: TrialState
|
||||
value?: number
|
||||
intermediate_values: TrialIntermediateValue[]
|
||||
datetime_start: string
|
||||
datetime_complete?: string
|
||||
params: TrialParam[]
|
||||
user_attrs: Attribute[]
|
||||
system_attrs: Attribute[]
|
||||
}
|
||||
user_attrs: Attribute[]
|
||||
system_attrs: Attribute[]
|
||||
datetime_start: string
|
||||
}[]
|
||||
}
|
||||
|
||||
export const getStudySummariesAPI = (): Promise<StudySummary[]> => {
|
||||
return axiosInstance
|
||||
.get<StudySummariesResponse>(`/api/studies`, {})
|
||||
.then((res) => {
|
||||
return res.data.study_summaries.map(
|
||||
(study): StudySummary => {
|
||||
const best_trial = study.best_trial
|
||||
? convertTrialResponse(study.best_trial)
|
||||
: undefined
|
||||
return {
|
||||
study_id: study.study_id,
|
||||
study_name: study.study_name,
|
||||
direction: study.direction,
|
||||
best_trial: best_trial,
|
||||
user_attrs: study.user_attrs,
|
||||
system_attrs: study.system_attrs,
|
||||
datetime_start: new Date(study.datetime_start),
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
interface CreateNewStudyResponse {
|
||||
study_summary: StudySummary
|
||||
}
|
||||
|
||||
export const createNewStudyAPI = (
|
||||
studyName: string,
|
||||
direction: StudyDirection
|
||||
): Promise<StudySummary> => {
|
||||
return axiosInstance
|
||||
.post<CreateNewStudyResponse>(`/api/studies`, {
|
||||
study_name: studyName,
|
||||
direction,
|
||||
})
|
||||
.then((res) => {
|
||||
const study_summary = res.data.study_summary
|
||||
return {
|
||||
study_id: study_summary.study_id,
|
||||
study_name: study_summary.study_name,
|
||||
direction: study_summary.direction,
|
||||
// best_trial: undefined,
|
||||
user_attrs: study_summary.user_attrs,
|
||||
system_attrs: study_summary.system_attrs,
|
||||
datetime_start: new Date(study_summary.datetime_start),
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
import React from "react"
|
||||
import { createStyles, makeStyles, Theme } from "@material-ui/core/styles"
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TablePagination,
|
||||
TableRow,
|
||||
TableSortLabel,
|
||||
Collapse,
|
||||
IconButton,
|
||||
} from "@material-ui/core"
|
||||
import KeyboardArrowDownIcon from "@material-ui/icons/KeyboardArrowDown"
|
||||
import KeyboardArrowUpIcon from "@material-ui/icons/KeyboardArrowUp"
|
||||
|
||||
type Order = "asc" | "desc"
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
width: "100%",
|
||||
},
|
||||
table: {
|
||||
minWidth: 750,
|
||||
},
|
||||
visuallyHidden: {
|
||||
border: 0,
|
||||
clip: "rect(0 0 0 0)",
|
||||
height: 1,
|
||||
margin: -1,
|
||||
overflow: "hidden",
|
||||
padding: 0,
|
||||
position: "absolute",
|
||||
top: 20,
|
||||
width: 1,
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
const defaultInitialRowsPerPage = 10
|
||||
const defaultRowsPerPageOption = [10, 50, 100, { label: "All", value: -1 }]
|
||||
|
||||
function descendingComparator<T>(a: T, b: T, orderBy: keyof T) {
|
||||
if (b[orderBy] < a[orderBy]) {
|
||||
return -1
|
||||
}
|
||||
if (b[orderBy] > a[orderBy]) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
interface DataGridColumn<T> {
|
||||
field: keyof T
|
||||
label: string
|
||||
sortable: boolean
|
||||
toCellValue?: (dataIndex: number) => string | React.ReactNode
|
||||
}
|
||||
|
||||
function DataGrid<T>(props: {
|
||||
columns: DataGridColumn<T>[]
|
||||
rows: T[]
|
||||
keyField: keyof T
|
||||
dense?: boolean
|
||||
collapseBody?: (dataIndex: number) => React.ReactNode
|
||||
initialRowsPerPage?: number
|
||||
rowsPerPageOption?: Array<number | { value: number; label: string }>
|
||||
}) {
|
||||
const classes = useStyles()
|
||||
const {
|
||||
columns,
|
||||
rows,
|
||||
keyField,
|
||||
dense,
|
||||
collapseBody,
|
||||
initialRowsPerPage,
|
||||
rowsPerPageOption,
|
||||
} = props
|
||||
const [order, setOrder] = React.useState<Order>("asc")
|
||||
const [orderBy, setOrderBy] = React.useState<keyof T>(keyField)
|
||||
const [page, setPage] = React.useState(0)
|
||||
const [rowsPerPage, setRowsPerPage] = React.useState(
|
||||
initialRowsPerPage || defaultInitialRowsPerPage
|
||||
)
|
||||
|
||||
const handleRequestSort = (
|
||||
event: React.MouseEvent<unknown>,
|
||||
property: keyof T
|
||||
) => {
|
||||
const isAsc = orderBy === property && order === "asc"
|
||||
setOrder(isAsc ? "desc" : "asc")
|
||||
setOrderBy(property)
|
||||
}
|
||||
const createSortHandler = (property: keyof T) => (
|
||||
event: React.MouseEvent<unknown>
|
||||
) => {
|
||||
handleRequestSort(event, property)
|
||||
}
|
||||
|
||||
const handleChangePage = (event: unknown, newPage: number) => {
|
||||
setPage(newPage)
|
||||
}
|
||||
|
||||
const handleChangeRowsPerPage = (
|
||||
event: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
setRowsPerPage(parseInt(event.target.value, 10))
|
||||
setPage(0)
|
||||
}
|
||||
|
||||
const emptyRows =
|
||||
rowsPerPage - Math.min(rowsPerPage, rows.length - page * rowsPerPage)
|
||||
|
||||
const sortedRows = stableSort<T>(rows, getComparator(order, orderBy))
|
||||
const paginateRows =
|
||||
rowsPerPage > 0
|
||||
? sortedRows.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
|
||||
: sortedRows
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<TableContainer>
|
||||
<Table
|
||||
className={classes.table}
|
||||
aria-labelledby="tableTitle"
|
||||
size={dense ? "small" : "medium"}
|
||||
aria-label="enhanced table"
|
||||
>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{collapseBody ? <TableCell /> : null}
|
||||
{columns.map((column, index) => (
|
||||
<TableCell
|
||||
key={index}
|
||||
sortDirection={orderBy === column.field ? order : false}
|
||||
>
|
||||
{column.sortable ? (
|
||||
<TableSortLabel
|
||||
active={orderBy === column.field}
|
||||
direction={orderBy === column.field ? order : "asc"}
|
||||
onClick={createSortHandler(column.field)}
|
||||
>
|
||||
{column.label}
|
||||
{orderBy === column.field ? (
|
||||
<span className={classes.visuallyHidden}>
|
||||
{order === "desc"
|
||||
? "sorted descending"
|
||||
: "sorted ascending"}
|
||||
</span>
|
||||
) : null}
|
||||
</TableSortLabel>
|
||||
) : (
|
||||
column.label
|
||||
)}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{paginateRows.map((row, index) => (
|
||||
<DataGridRow<T>
|
||||
columns={columns}
|
||||
rowIndex={page * rowsPerPage + index}
|
||||
row={row}
|
||||
keyField={keyField}
|
||||
collapseBody={collapseBody}
|
||||
key={`data-grid-row-${row[keyField]}`}
|
||||
/>
|
||||
))}
|
||||
{emptyRows > 0 && (
|
||||
<TableRow style={{ height: (dense ? 33 : 53) * emptyRows }}>
|
||||
<TableCell colSpan={6} />
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<TablePagination
|
||||
rowsPerPageOptions={rowsPerPageOption || defaultRowsPerPageOption}
|
||||
component="div"
|
||||
count={rows.length}
|
||||
rowsPerPage={rowsPerPage}
|
||||
page={page}
|
||||
onChangePage={handleChangePage}
|
||||
onChangeRowsPerPage={handleChangeRowsPerPage}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function DataGridRow<T>(props: {
|
||||
columns: DataGridColumn<T>[]
|
||||
rowIndex: number
|
||||
row: T
|
||||
keyField: keyof T
|
||||
collapseBody?: (dataIndex: number) => React.ReactNode
|
||||
}) {
|
||||
const { columns, rowIndex, row, keyField, collapseBody } = props
|
||||
const [open, setOpen] = React.useState(false)
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<TableRow hover role="checkbox" tabIndex={-1}>
|
||||
{collapseBody ? (
|
||||
<TableCell>
|
||||
<IconButton
|
||||
aria-label="expand row"
|
||||
size="small"
|
||||
onClick={() => setOpen(!open)}
|
||||
>
|
||||
{open ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />}
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
) : null}
|
||||
{columns.map((column) => (
|
||||
<TableCell key={`${row[keyField]}:${column.field}`}>
|
||||
{column.toCellValue
|
||||
? column.toCellValue(rowIndex)
|
||||
: row[column.field]}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
{collapseBody ? (
|
||||
<TableRow>
|
||||
<TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={6}>
|
||||
<Collapse in={open} timeout="auto" unmountOnExit>
|
||||
{collapseBody(rowIndex)}
|
||||
</Collapse>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : null}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
function getComparator<T>(
|
||||
order: Order,
|
||||
orderBy: keyof T
|
||||
): (a: T, b: T) => number {
|
||||
return order === "desc"
|
||||
? (a, b) => descendingComparator<T>(a, b, orderBy)
|
||||
: (a, b) => -descendingComparator<T>(a, b, orderBy)
|
||||
}
|
||||
|
||||
function stableSort<T>(array: T[], comparator: (a: T, b: T) => number) {
|
||||
const stabilizedThis = array.map((el, index) => [el, index] as [T, number])
|
||||
stabilizedThis.sort((a, b) => {
|
||||
const order = comparator(a[0], b[0])
|
||||
if (order !== 0) return order
|
||||
return a[1] - b[1]
|
||||
})
|
||||
return stabilizedThis.map((el) => el[0])
|
||||
}
|
||||
|
||||
export { DataGrid, DataGridColumn }
|
||||
@@ -0,0 +1,205 @@
|
||||
import * as plotly from "plotly.js-dist"
|
||||
import React, { ChangeEvent, FC, useEffect, useState } from "react"
|
||||
import {
|
||||
Grid,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
FormControlLabel,
|
||||
Checkbox,
|
||||
Switch,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
} from "@material-ui/core"
|
||||
|
||||
const plotDomId = "graph-history"
|
||||
|
||||
export const GraphHistory: FC<{
|
||||
study: StudyDetail | null
|
||||
}> = ({ study = null }) => {
|
||||
const [xAxis, setXAxis] = useState<string>("number")
|
||||
const [logScale, setLogScale] = useState<boolean>(false)
|
||||
const [filterCompleteTrial, setFilterCompleteTrial] = useState<boolean>(false)
|
||||
const [filterPrunedTrial, setFilterPrunedTrial] = useState<boolean>(false)
|
||||
|
||||
const handleXAxisChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setXAxis(e.target.value)
|
||||
}
|
||||
|
||||
const handleLogScaleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
e.preventDefault()
|
||||
setLogScale(!logScale)
|
||||
}
|
||||
|
||||
const handleFilterCompleteChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
e.preventDefault()
|
||||
setFilterCompleteTrial(!filterCompleteTrial)
|
||||
}
|
||||
|
||||
const handleFilterPrunedChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
e.preventDefault()
|
||||
setFilterPrunedTrial(!filterPrunedTrial)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (study !== null) {
|
||||
plotHistory(
|
||||
study,
|
||||
xAxis,
|
||||
logScale,
|
||||
filterCompleteTrial,
|
||||
filterPrunedTrial
|
||||
)
|
||||
}
|
||||
}, [study, logScale, xAxis, filterPrunedTrial, filterCompleteTrial])
|
||||
|
||||
return (
|
||||
<Grid container direction="row">
|
||||
<Grid item xs={3}>
|
||||
<Grid container direction="column">
|
||||
<FormControl component="fieldset">
|
||||
<FormLabel component="legend">Log scale:</FormLabel>
|
||||
<Switch
|
||||
checked={logScale}
|
||||
onChange={handleLogScaleChange}
|
||||
value="enable"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl component="fieldset">
|
||||
<FormLabel component="legend">Filter state:</FormLabel>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={!filterCompleteTrial}
|
||||
onChange={handleFilterCompleteChange}
|
||||
/>
|
||||
}
|
||||
label="Complete"
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={!filterPrunedTrial}
|
||||
onChange={handleFilterPrunedChange}
|
||||
/>
|
||||
}
|
||||
label="Pruned"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl component="fieldset">
|
||||
<FormLabel component="legend">X-axis:</FormLabel>
|
||||
<RadioGroup
|
||||
aria-label="gender"
|
||||
name="gender1"
|
||||
value={xAxis}
|
||||
onChange={handleXAxisChange}
|
||||
>
|
||||
<FormControlLabel
|
||||
value="number"
|
||||
control={<Radio />}
|
||||
label="Number"
|
||||
/>
|
||||
<FormControlLabel
|
||||
value="datetime_start"
|
||||
control={<Radio />}
|
||||
label="Datetime start"
|
||||
/>
|
||||
<FormControlLabel
|
||||
value="datetime_complete"
|
||||
control={<Radio />}
|
||||
label="Datetime complete"
|
||||
/>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={9}>
|
||||
<div id={plotDomId} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
const plotHistory = (
|
||||
study: StudyDetail,
|
||||
xAxis: string,
|
||||
logScale: boolean,
|
||||
filterCompleteTrial: boolean,
|
||||
filterPrunedTrial: boolean
|
||||
) => {
|
||||
if (document.getElementById(plotDomId) === null) {
|
||||
return
|
||||
}
|
||||
|
||||
const layout: Partial<plotly.Layout> = {
|
||||
margin: {
|
||||
l: 50,
|
||||
t: 0,
|
||||
r: 50,
|
||||
b: 0,
|
||||
},
|
||||
yaxis: {
|
||||
type: logScale ? "log" : "linear",
|
||||
},
|
||||
xaxis: {
|
||||
type: xAxis === "number" ? "linear" : "date",
|
||||
},
|
||||
showlegend: false,
|
||||
}
|
||||
|
||||
let filteredTrials = study.trials.filter(
|
||||
(t) => t.state === "Complete" || t.state === "Pruned"
|
||||
)
|
||||
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, [])
|
||||
return
|
||||
}
|
||||
let trialsForLinePlot: Trial[] = []
|
||||
let currentBest: number | null = null
|
||||
filteredTrials.forEach((item) => {
|
||||
if (currentBest === null) {
|
||||
currentBest = item.value!
|
||||
trialsForLinePlot.push(item)
|
||||
} else if (study.direction === "maximize" && item.value! > currentBest) {
|
||||
currentBest = item.value!
|
||||
trialsForLinePlot.push(item)
|
||||
} else if (study.direction === "minimize" && item.value! < currentBest) {
|
||||
currentBest = item.value!
|
||||
trialsForLinePlot.push(item)
|
||||
}
|
||||
})
|
||||
|
||||
const getAxisX = (trial: Trial): number | Date => {
|
||||
return xAxis === "number"
|
||||
? trial.number
|
||||
: xAxis === "datetime_start"
|
||||
? trial.datetime_start
|
||||
: trial.datetime_complete!
|
||||
}
|
||||
|
||||
let xForLinePlot = trialsForLinePlot.map(getAxisX)
|
||||
xForLinePlot.push(getAxisX(filteredTrials[filteredTrials.length - 1]))
|
||||
let yForLinePlot = trialsForLinePlot.map((t: Trial): number => t.value!)
|
||||
yForLinePlot.push(yForLinePlot[yForLinePlot.length - 1])
|
||||
|
||||
const plotData: Partial<plotly.PlotData>[] = [
|
||||
{
|
||||
x: filteredTrials.map(getAxisX),
|
||||
y: filteredTrials.map((t: Trial): number => t.value!),
|
||||
mode: "markers",
|
||||
type: "scatter",
|
||||
},
|
||||
{
|
||||
x: xForLinePlot,
|
||||
y: yForLinePlot,
|
||||
mode: "lines",
|
||||
type: "scatter",
|
||||
},
|
||||
]
|
||||
plotly.react(plotDomId, plotData, layout)
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import * as plotly from "plotly.js-dist"
|
||||
import React, { FC, useEffect } from "react"
|
||||
|
||||
const plotDomId = "graph-intermediate-values"
|
||||
|
||||
export const GraphIntermediateValues: FC<{
|
||||
trials: Trial[]
|
||||
}> = ({ trials = [] }) => {
|
||||
useEffect(() => {
|
||||
plotIntermediateValue(trials)
|
||||
}, [trials])
|
||||
return <div id={plotDomId} />
|
||||
}
|
||||
|
||||
const plotIntermediateValue = (trials: Trial[]) => {
|
||||
if (document.getElementById(plotDomId) === null) {
|
||||
return
|
||||
}
|
||||
|
||||
const layout: Partial<plotly.Layout> = {
|
||||
title: "Intermediate values",
|
||||
margin: {
|
||||
l: 50,
|
||||
r: 50,
|
||||
b: 0,
|
||||
},
|
||||
}
|
||||
if (trials.length === 0) {
|
||||
plotly.react(plotDomId, [], layout)
|
||||
return
|
||||
}
|
||||
|
||||
let filteredTrials = trials.filter(
|
||||
(t) => t.state === "Complete" || t.state === "Pruned"
|
||||
)
|
||||
const plotData: Partial<plotly.PlotData>[] = filteredTrials.map((trial) => {
|
||||
return {
|
||||
x: trial.intermediate_values.map((iv) => iv.step),
|
||||
y: trial.intermediate_values.map((iv) => iv.value),
|
||||
mode: "lines+markers",
|
||||
type: "scatter",
|
||||
name: `trial #${trial.number}`,
|
||||
}
|
||||
})
|
||||
plotly.react(plotDomId, plotData, layout)
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
import * as plotly from "plotly.js-dist"
|
||||
import React, { FC, useEffect } from "react"
|
||||
|
||||
const plotDomId = "graph-parallel-coordinate"
|
||||
|
||||
export const GraphParallelCoordinate: FC<{
|
||||
trials: Trial[]
|
||||
}> = ({ trials = [] }) => {
|
||||
useEffect(() => {
|
||||
plotCoordinate(trials)
|
||||
}, [trials])
|
||||
return <div id={plotDomId} />
|
||||
}
|
||||
|
||||
const plotCoordinate = (trials: Trial[]) => {
|
||||
if (document.getElementById(plotDomId) === null) {
|
||||
return
|
||||
}
|
||||
|
||||
const layout: Partial<plotly.Layout> = {
|
||||
title: "Parallel coordinate",
|
||||
margin: {
|
||||
l: 50,
|
||||
r: 50,
|
||||
b: 0,
|
||||
},
|
||||
}
|
||||
|
||||
if (trials.length === 0) {
|
||||
plotly.react(plotDomId, [])
|
||||
return
|
||||
}
|
||||
let filteredTrials = trials.filter(
|
||||
(t) => t.state === "Complete" || t.state === "Pruned"
|
||||
)
|
||||
|
||||
// Intersection param names
|
||||
let paramNames = new Set<string>(trials[0].params.map((p) => p.name))
|
||||
filteredTrials.forEach((t) => {
|
||||
paramNames = new Set<string>(
|
||||
t.params.filter((p) => paramNames.has(p.name)).map((p) => p.name)
|
||||
)
|
||||
})
|
||||
|
||||
if (paramNames.size === 0) {
|
||||
plotly.react(plotDomId, [])
|
||||
return
|
||||
}
|
||||
|
||||
const objectiveValues: number[] = filteredTrials.map((t) => t.value!)
|
||||
let dimensions = [
|
||||
{
|
||||
label: "Objective value",
|
||||
values: objectiveValues,
|
||||
range: [Math.min(...objectiveValues), Math.max(...objectiveValues)],
|
||||
},
|
||||
]
|
||||
paramNames.forEach((paramName) => {
|
||||
const valueStrings = filteredTrials.map((t) => {
|
||||
const param = t.params.find((p) => p.name == paramName)
|
||||
return param!.value
|
||||
})
|
||||
const isnum = valueStrings.every((v) => {
|
||||
return /^-?\d+\.\d+$/.test(v)
|
||||
})
|
||||
if (isnum) {
|
||||
const values: number[] = valueStrings.map((v) => parseFloat(v))
|
||||
dimensions.push({
|
||||
label: paramName,
|
||||
values: values,
|
||||
range: [Math.min(...values), Math.max(...values)],
|
||||
})
|
||||
} else {
|
||||
// categorical
|
||||
const vocabSet = new Set<string>(valueStrings)
|
||||
const vocabArr = Array.from<string>(vocabSet)
|
||||
const values: number[] = valueStrings.map((v) =>
|
||||
vocabArr.findIndex((vocab) => v === vocab)
|
||||
)
|
||||
const tickvals: number[] = vocabArr.map((v, i) => i)
|
||||
dimensions.push({
|
||||
label: paramName,
|
||||
values: values,
|
||||
range: [Math.min(...values), Math.max(...values)],
|
||||
// @ts-ignore
|
||||
tickvals: tickvals,
|
||||
ticktext: vocabArr,
|
||||
})
|
||||
}
|
||||
})
|
||||
const plotData: Partial<plotly.PlotData>[] = [
|
||||
{
|
||||
type: "parcoords",
|
||||
// @ts-ignore
|
||||
dimensions: dimensions,
|
||||
},
|
||||
]
|
||||
|
||||
plotly.react(plotDomId, plotData, layout)
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
import React, { FC, useEffect } from "react"
|
||||
import { useRecoilValue } from "recoil"
|
||||
import { Link, useParams } from "react-router-dom"
|
||||
import { createStyles, makeStyles, Theme } from "@material-ui/core/styles"
|
||||
import {
|
||||
AppBar,
|
||||
Card,
|
||||
Typography,
|
||||
CardContent,
|
||||
Container,
|
||||
Grid,
|
||||
Toolbar,
|
||||
Paper,
|
||||
Box,
|
||||
IconButton,
|
||||
} from "@material-ui/core"
|
||||
import { Home } from "@material-ui/icons"
|
||||
|
||||
import { DataGridColumn, DataGrid } from "./dataGrid"
|
||||
import { GraphParallelCoordinate } from "./graphParallelCoordinate"
|
||||
import { GraphIntermediateValues } from "./graphIntermediateValues"
|
||||
import { GraphHistory } from "./graphHistory"
|
||||
import { Action, actionCreator } from "../action"
|
||||
import { studyDetailsState } from "../state"
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
paper: {
|
||||
margin: theme.spacing(2),
|
||||
padding: theme.spacing(2),
|
||||
},
|
||||
card: {
|
||||
margin: theme.spacing(2),
|
||||
},
|
||||
grow: {
|
||||
flexGrow: 1,
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
interface ParamTypes {
|
||||
studyId: string
|
||||
}
|
||||
|
||||
export const useStudyDetail = (
|
||||
action: Action,
|
||||
studyId: number
|
||||
): StudyDetail | null => {
|
||||
const studyDetails = useRecoilValue<StudyDetails>(studyDetailsState)
|
||||
|
||||
useEffect(() => {
|
||||
action.updateStudyDetail(studyId)
|
||||
const intervalId = setInterval(function () {
|
||||
action.updateStudyDetail(studyId)
|
||||
}, 10 * 1000)
|
||||
return () => clearInterval(intervalId)
|
||||
}, [])
|
||||
|
||||
return studyDetails[studyId] || null
|
||||
}
|
||||
|
||||
export const StudyDetail: FC<{}> = () => {
|
||||
const classes = useStyles()
|
||||
const action = actionCreator()
|
||||
const { studyId } = useParams<ParamTypes>()
|
||||
const studyIdNumber = parseInt(studyId, 10)
|
||||
const studyDetail = useStudyDetail(action, studyIdNumber)
|
||||
|
||||
const title = studyDetail !== null ? studyDetail.name : `Study #${studyId}`
|
||||
const trials: Trial[] = studyDetail !== null ? studyDetail.trials : []
|
||||
|
||||
return (
|
||||
<div>
|
||||
<AppBar position="static">
|
||||
<Container>
|
||||
<Toolbar>
|
||||
<Typography variant="h6">{APP_BAR_TITLE}</Typography>
|
||||
<div className={classes.grow} />
|
||||
<IconButton
|
||||
aria-controls="menu-appbar"
|
||||
aria-haspopup="true"
|
||||
component={Link}
|
||||
to={URL_PREFIX + "/"}
|
||||
color="inherit"
|
||||
>
|
||||
<Home />
|
||||
</IconButton>
|
||||
</Toolbar>
|
||||
</Container>
|
||||
</AppBar>
|
||||
<Container>
|
||||
<div>
|
||||
<Paper className={classes.paper}>
|
||||
<Typography variant="h6">{title}</Typography>
|
||||
</Paper>
|
||||
<Card className={classes.card}>
|
||||
<CardContent>
|
||||
<GraphHistory study={studyDetail} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Grid container direction="row">
|
||||
<Grid item xs={6}>
|
||||
<Card className={classes.card}>
|
||||
<CardContent>
|
||||
<GraphParallelCoordinate trials={trials} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Card className={classes.card}>
|
||||
<CardContent>
|
||||
<GraphIntermediateValues trials={trials} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Card className={classes.card}>
|
||||
<TrialTable trials={trials} />
|
||||
</Card>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const TrialTable: FC<{ trials: Trial[] }> = ({ trials = [] }) => {
|
||||
const columns: DataGridColumn<Trial>[] = [
|
||||
{ field: "number", label: "Number", sortable: true },
|
||||
{
|
||||
field: "state",
|
||||
label: "State",
|
||||
sortable: false,
|
||||
toCellValue: (i) => trials[i].state.toString(),
|
||||
},
|
||||
{ field: "value", label: "Value", sortable: true },
|
||||
{
|
||||
field: "params",
|
||||
label: "Params",
|
||||
sortable: false,
|
||||
toCellValue: (i) =>
|
||||
trials[i].params.map((p) => p.name + ": " + p.value).join(", "),
|
||||
},
|
||||
]
|
||||
const collapseIntermediateValueColumns: DataGridColumn<
|
||||
TrialIntermediateValue
|
||||
>[] = [
|
||||
{ field: "step", label: "Step", sortable: true },
|
||||
{ field: "value", label: "Value", sortable: true },
|
||||
]
|
||||
const collapseAttrColumns: DataGridColumn<Attribute>[] = [
|
||||
{ field: "key", label: "Key", sortable: true },
|
||||
{ field: "value", label: "Value", sortable: true },
|
||||
]
|
||||
|
||||
const collapseBody = (index: number) => {
|
||||
return (
|
||||
<Grid container direction="row">
|
||||
<Grid item xs={6}>
|
||||
<Box margin={1}>
|
||||
<Typography variant="h6" gutterBottom component="div">
|
||||
Intermediate values
|
||||
</Typography>
|
||||
<DataGrid<TrialIntermediateValue>
|
||||
columns={collapseIntermediateValueColumns}
|
||||
rows={trials[index].intermediate_values}
|
||||
keyField={"step"}
|
||||
dense={true}
|
||||
initialRowsPerPage={5}
|
||||
rowsPerPageOption={[5, 10, { label: "All", value: -1 }]}
|
||||
/>
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Box margin={1}>
|
||||
<Typography variant="h6" gutterBottom component="div">
|
||||
Trial user attributes
|
||||
</Typography>
|
||||
<DataGrid<Attribute>
|
||||
columns={collapseAttrColumns}
|
||||
rows={trials[index].user_attrs}
|
||||
keyField={"key"}
|
||||
dense={true}
|
||||
initialRowsPerPage={5}
|
||||
rowsPerPageOption={[5, 10, { label: "All", value: -1 }]}
|
||||
/>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<DataGrid<Trial>
|
||||
columns={columns}
|
||||
rows={trials}
|
||||
keyField={"trial_id"}
|
||||
dense={true}
|
||||
collapseBody={collapseBody}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
import React, { FC, useEffect } from "react"
|
||||
import { useRecoilValue } from "recoil"
|
||||
import { Link } from "react-router-dom"
|
||||
import { createStyles, makeStyles, Theme } from "@material-ui/core/styles"
|
||||
import {
|
||||
AppBar,
|
||||
Toolbar,
|
||||
Typography,
|
||||
Container,
|
||||
Card,
|
||||
Grid,
|
||||
Box,
|
||||
Button,
|
||||
IconButton,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
TextField,
|
||||
DialogActions,
|
||||
FormControlLabel,
|
||||
Checkbox,
|
||||
} from "@material-ui/core"
|
||||
import { AddBox, Refresh } from "@material-ui/icons"
|
||||
|
||||
import { actionCreator } from "../action"
|
||||
import { DataGrid, DataGridColumn } from "./dataGrid"
|
||||
import { studySummariesState } from "../state"
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
card: {
|
||||
margin: theme.spacing(2),
|
||||
},
|
||||
grow: {
|
||||
flexGrow: 1,
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
export const StudyList: FC<{}> = () => {
|
||||
const classes = useStyles()
|
||||
const [openDialog, setOpenDialog] = React.useState(false)
|
||||
const [newStudyName, setNewStudyName] = React.useState("")
|
||||
const [maximize, setMaximize] = React.useState<boolean>(false)
|
||||
|
||||
const action = actionCreator()
|
||||
const studies = useRecoilValue<StudySummary[]>(studySummariesState)
|
||||
|
||||
const newStudyNameAlreadyUsed = studies.some(
|
||||
(v) => v.study_name === newStudyName
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
action.updateStudySummaries()
|
||||
}, [])
|
||||
|
||||
const columns: DataGridColumn<StudySummary>[] = [
|
||||
{
|
||||
field: "study_id",
|
||||
label: "Study ID",
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
field: "study_name",
|
||||
label: "Name",
|
||||
sortable: true,
|
||||
toCellValue: (i) => (
|
||||
<Link to={`${URL_PREFIX}/studies/${studies[i].study_id}`}>
|
||||
{studies[i].study_name}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
field: "direction",
|
||||
label: "Direction",
|
||||
sortable: false,
|
||||
toCellValue: (i) => studies[i].direction.toString(),
|
||||
},
|
||||
{
|
||||
field: "best_trial",
|
||||
label: "Best value",
|
||||
sortable: false,
|
||||
toCellValue: (i) => studies[i].best_trial?.value || null,
|
||||
},
|
||||
]
|
||||
|
||||
const collapseAttrColumns: DataGridColumn<Attribute>[] = [
|
||||
{ field: "key", label: "Key", sortable: true },
|
||||
{ field: "value", label: "Value", sortable: true },
|
||||
]
|
||||
|
||||
const handleCloseNewStudyDialog = () => {
|
||||
setNewStudyName("")
|
||||
setOpenDialog(false)
|
||||
}
|
||||
|
||||
const handleCreateNewStudy = () => {
|
||||
const direction = maximize ? "maximize" : "minimize"
|
||||
action.createNewStudy(newStudyName, direction)
|
||||
setOpenDialog(false)
|
||||
setNewStudyName("")
|
||||
}
|
||||
|
||||
const collapseBody = (index: number) => {
|
||||
return (
|
||||
<Grid container direction="row">
|
||||
<Grid item xs={6}>
|
||||
<Box margin={1}>
|
||||
<Typography variant="h6" gutterBottom component="div">
|
||||
Study user attributes
|
||||
</Typography>
|
||||
<DataGrid<Attribute>
|
||||
columns={collapseAttrColumns}
|
||||
rows={studies[index].user_attrs}
|
||||
keyField={"key"}
|
||||
dense={true}
|
||||
initialRowsPerPage={5}
|
||||
rowsPerPageOption={[5, 10, { label: "All", value: -1 }]}
|
||||
/>
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Box margin={1}>
|
||||
<Typography variant="h6" gutterBottom component="div">
|
||||
Study system attributes
|
||||
</Typography>
|
||||
<DataGrid<Attribute>
|
||||
columns={collapseAttrColumns}
|
||||
rows={studies[index].system_attrs}
|
||||
keyField={"key"}
|
||||
dense={true}
|
||||
initialRowsPerPage={5}
|
||||
rowsPerPageOption={[5, 10, { label: "All", value: -1 }]}
|
||||
/>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<AppBar position="static">
|
||||
<Container>
|
||||
<Toolbar>
|
||||
<Typography variant="h6">{APP_BAR_TITLE}</Typography>
|
||||
<div className={classes.grow} />
|
||||
<IconButton
|
||||
aria-controls="menu-appbar"
|
||||
aria-haspopup="true"
|
||||
onClick={(e) => {
|
||||
action.updateStudySummaries("Success to reload")
|
||||
}}
|
||||
color="inherit"
|
||||
>
|
||||
<Refresh />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
aria-controls="menu-appbar"
|
||||
aria-haspopup="true"
|
||||
onClick={(e) => {
|
||||
setOpenDialog(true)
|
||||
}}
|
||||
color="inherit"
|
||||
>
|
||||
<AddBox />
|
||||
</IconButton>
|
||||
</Toolbar>
|
||||
</Container>
|
||||
</AppBar>
|
||||
<Container>
|
||||
<Card className={classes.card}>
|
||||
<DataGrid<StudySummary>
|
||||
columns={columns}
|
||||
rows={studies}
|
||||
keyField={"study_id"}
|
||||
collapseBody={collapseBody}
|
||||
initialRowsPerPage={-1}
|
||||
rowsPerPageOption={[5, 10, { label: "All", value: -1 }]}
|
||||
/>
|
||||
</Card>
|
||||
</Container>
|
||||
<Dialog
|
||||
open={openDialog}
|
||||
onClose={(e) => handleCloseNewStudyDialog}
|
||||
aria-labelledby="form-dialog-title"
|
||||
>
|
||||
<DialogTitle id="form-dialog-title">New study</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
To create a new study, please enter the study name here.
|
||||
</DialogContentText>
|
||||
<TextField
|
||||
autoFocus
|
||||
error={newStudyNameAlreadyUsed}
|
||||
helperText={
|
||||
newStudyNameAlreadyUsed ? `"${newStudyName}" is already used` : ""
|
||||
}
|
||||
label="Study name"
|
||||
type="text"
|
||||
onChange={(e) => {
|
||||
setNewStudyName(e.target.value)
|
||||
}}
|
||||
fullWidth
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={maximize}
|
||||
onChange={(e) => {
|
||||
setMaximize(!maximize)
|
||||
}}
|
||||
color="primary"
|
||||
/>
|
||||
}
|
||||
label="Set maximize direction"
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleCloseNewStudyDialog} color="primary">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleCreateNewStudy}
|
||||
color="primary"
|
||||
disabled={newStudyName === "" || newStudyNameAlreadyUsed}
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
export const formatDate = (date: Date): string => {
|
||||
const options = {
|
||||
year: "numeric",
|
||||
month: "numeric",
|
||||
day: "numeric",
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
second: "numeric",
|
||||
}
|
||||
return new Intl.DateTimeFormat("ja-JP", options).format(date)
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import React, { FC } from "react"
|
||||
import { render } from "react-dom"
|
||||
import { RecoilRoot } from "recoil"
|
||||
import { BrowserRouter as Router, Switch, Route } from "react-router-dom"
|
||||
import { SnackbarProvider } from "notistack"
|
||||
|
||||
import { StudyDetail } from "./components/studyDetail"
|
||||
import { StudyList } from "./components/studyList"
|
||||
|
||||
const DashboardApp: FC<{}> = () => {
|
||||
return (
|
||||
<RecoilRoot>
|
||||
<SnackbarProvider maxSnack={3}>
|
||||
<Router>
|
||||
<Switch>
|
||||
<Route
|
||||
path={URL_PREFIX + "/studies/:studyId"}
|
||||
children={<StudyDetail />}
|
||||
/>
|
||||
<Route path={URL_PREFIX + "/"} children={<StudyList />} />
|
||||
</Switch>
|
||||
</Router>
|
||||
</SnackbarProvider>
|
||||
</RecoilRoot>
|
||||
)
|
||||
}
|
||||
|
||||
render(
|
||||
<React.StrictMode>
|
||||
<DashboardApp />
|
||||
</React.StrictMode>,
|
||||
document.getElementById("dashboard")
|
||||
)
|
||||
@@ -0,0 +1,11 @@
|
||||
import { atom } from "recoil"
|
||||
|
||||
export const studySummariesState = atom<StudySummary[]>({
|
||||
key: "studySummaries",
|
||||
default: [],
|
||||
})
|
||||
|
||||
export const studyDetailsState = atom<StudyDetails>({
|
||||
key: "studyDetails",
|
||||
default: {},
|
||||
})
|
||||
Vendored
+62
@@ -0,0 +1,62 @@
|
||||
declare module "*.css"
|
||||
declare module "*.png"
|
||||
declare module "*.jpg"
|
||||
declare module "*.svg"
|
||||
|
||||
declare const APP_BAR_TITLE: string
|
||||
declare const API_ENDPOINT: string
|
||||
declare const URL_PREFIX: string
|
||||
|
||||
type TrialState = "Running" | "Complete" | "Pruned" | "Fail" | "Waiting"
|
||||
type StudyDirection = "maximize" | "minimize"
|
||||
|
||||
declare interface TrialIntermediateValue {
|
||||
step: number
|
||||
value: number
|
||||
}
|
||||
|
||||
declare interface TrialParam {
|
||||
name: string
|
||||
value: string
|
||||
}
|
||||
|
||||
declare interface Attribute {
|
||||
key: string
|
||||
value: string
|
||||
}
|
||||
|
||||
declare interface Trial {
|
||||
trial_id: number
|
||||
study_id: number
|
||||
number: number
|
||||
state: TrialState
|
||||
value?: number
|
||||
intermediate_values: TrialIntermediateValue[]
|
||||
datetime_start: Date
|
||||
datetime_complete?: Date
|
||||
params: TrialParam[]
|
||||
user_attrs: Attribute[]
|
||||
system_attrs: Attribute[]
|
||||
}
|
||||
|
||||
declare interface StudySummary {
|
||||
study_id: number
|
||||
study_name: string
|
||||
direction: StudyDirection
|
||||
best_trial?: Trial
|
||||
user_attrs: Attribute[]
|
||||
system_attrs: Attribute[]
|
||||
datetime_start: Date
|
||||
}
|
||||
|
||||
declare interface StudyDetail {
|
||||
name: string
|
||||
direction: StudyDirection
|
||||
datetime_start: Date
|
||||
best_trial?: Trial
|
||||
trials: Trial[]
|
||||
}
|
||||
|
||||
declare interface StudyDetails {
|
||||
[study_id: string]: StudyDetail
|
||||
}
|
||||
Generated
+2424
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "goptuna-dashboard",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"description": "Dashboard for Goptuna",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"fmt": "prettier --write \"dashboard/static/**/*.{ts,tsx}\"",
|
||||
"watch": "webpack --watch",
|
||||
"build": "webpack",
|
||||
"build:dev": "NODE_ENV=development webpack",
|
||||
"build:prd": "NODE_ENV=production webpack"
|
||||
},
|
||||
"author": "Masashi Shibata",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"axios": "^0.20.0",
|
||||
"dayjs": "^1.9.3",
|
||||
"notistack": "^1.0.1",
|
||||
"plotly.js-dist": "^1.57.0",
|
||||
"react": "^16.14.0",
|
||||
"react-dom": "^16.14.0",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"recoil": "0.0.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.3",
|
||||
"@types/plotly.js": "^1.50.22",
|
||||
"@types/react": "^16.9.53",
|
||||
"@types/react-dom": "^16.9.8",
|
||||
"@types/react-router-dom": "^5.1.6",
|
||||
"autoprefixer": "^10.0.1",
|
||||
"css-loader": "^5.0.0",
|
||||
"file-loader": "^6.1.1",
|
||||
"prettier": "^2.1.2",
|
||||
"style-loader": "^2.0.0",
|
||||
"ts-loader": "^8.0.6",
|
||||
"typescript": "^4.0.3",
|
||||
"url-loader": "^4.1.1",
|
||||
"webpack": "^5.1.3",
|
||||
"webpack-cli": "^4.1.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"strictNullChecks": true,
|
||||
"moduleResolution": "node",
|
||||
"noUnusedLocals": true,
|
||||
"noImplicitThis": true,
|
||||
"alwaysStrict": true,
|
||||
"outDir": "./dashboard/public/",
|
||||
"paths": {
|
||||
"plotly.js-dist": ["node_modules/@types/plotly.js"]
|
||||
},
|
||||
"noImplicitAny": true,
|
||||
"lib": ["dom", "esnext"],
|
||||
"module": "esnext",
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"target": "es5",
|
||||
"jsx": "react",
|
||||
"sourceMap": true,
|
||||
"strict": true
|
||||
},
|
||||
"files": [
|
||||
"./dashboard/static/index.tsx"
|
||||
],
|
||||
"include": [
|
||||
"./dashboard/static/types/**/*"
|
||||
],
|
||||
"types": ["node"]
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
const webpack = require('webpack');
|
||||
|
||||
const externals = [];
|
||||
var mode = 'development';
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
mode = 'production';
|
||||
}
|
||||
const isDev = mode === 'development';
|
||||
|
||||
if (isDev) {
|
||||
const _externals = {};
|
||||
externals.push(_externals);
|
||||
}
|
||||
|
||||
var config = {
|
||||
mode,
|
||||
entry: [__dirname + '/dashboard/static/index.tsx'],
|
||||
output: {
|
||||
path: __dirname + '/dashboard/public/',
|
||||
filename: 'bundle.js',
|
||||
publicPath: '/public/'
|
||||
},
|
||||
module: {
|
||||
rules: [{
|
||||
oneOf: [
|
||||
{
|
||||
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
|
||||
loader: require.resolve('url-loader'),
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: 'media/[name].[hash:8].[ext]',
|
||||
publicPath: '/public/'
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
exclude: [/node_modules/],
|
||||
loader: 'ts-loader',
|
||||
options: {
|
||||
configFile: __dirname + '/tsconfig.json'
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.p?css$/,
|
||||
use: [
|
||||
'style-loader',
|
||||
{
|
||||
loader: require.resolve('css-loader'),
|
||||
options: {
|
||||
importLoaders: 1,
|
||||
sourceMap: isDev
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
exclude: [/\.(ts|tsx|js)$/, /\.html$/, /\.json$/],
|
||||
loader: require.resolve('file-loader'),
|
||||
options: {
|
||||
name: 'media/[name].[hash:8].[ext]',
|
||||
publicPath: '/public/'
|
||||
}
|
||||
}
|
||||
]}]
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.ts', '.tsx', '.js']
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
|
||||
'APP_BAR_TITLE': JSON.stringify(process.env.APP_BAR_TITLE || "Optuna Dashboard"),
|
||||
'API_ENDPOINT': JSON.stringify(process.env.API_ENDPOINT),
|
||||
'URL_PREFIX': JSON.stringify(process.env.API_ENDPOINT || "/dashboard")
|
||||
})
|
||||
],
|
||||
externals
|
||||
};
|
||||
|
||||
if (isDev) {
|
||||
config.devtool = 'source-map';
|
||||
console.log('= = = = = = = = = = = = = = = = = = =');
|
||||
console.log('DEVELOPMENT BUILD');
|
||||
console.log('= = = = = = = = = = = = = = = = = = =');
|
||||
}
|
||||
|
||||
module.exports = config;
|
||||
Reference in New Issue
Block a user