Skip to content

Commit

Permalink
Merge pull request #877 from keisuke-umezawa/feature/table-viewer
Browse files Browse the repository at this point in the history
Add TableArtifactViewer for csv file
  • Loading branch information
c-bata authored Jun 7, 2024
2 parents a29ac8a + ef17f75 commit a6f08cd
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 10 deletions.
19 changes: 16 additions & 3 deletions optuna_dashboard/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions optuna_dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@
"@tanstack/react-query": "^5.18.1",
"@tanstack/react-table": "^8.16.0",
"@tanstack/react-virtual": "^3.1.2",
"@types/papaparse": "^5.3.14",
"@types/three": "^0.160.0",
"axios": "^1.6.7",
"elkjs": "^0.9.1",
"notistack": "^3.0.1",
"papaparse": "^5.4.1",
"plotly.js-dist-min": "^2.28.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand Down
17 changes: 17 additions & 0 deletions optuna_dashboard/ts/components/Artifact/StudyArtifactCards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { StudyDetail } from "ts/types/optuna"
import { actionCreator } from "../../action"
import { ArtifactCardMedia } from "./ArtifactCardMedia"
import { useDeleteStudyArtifactDialog } from "./DeleteArtifactDialog"
import { isTableArtifact, useTableArtifactModal } from "./TableArtifactViewer"
import {
isThreejsArtifact,
useThreejsArtifactModal,
Expand All @@ -35,6 +36,8 @@ export const StudyArtifactCards: FC<{ study: StudyDetail }> = ({ study }) => {
useDeleteStudyArtifactDialog()
const [openThreejsArtifactModal, renderThreejsArtifactModal] =
useThreejsArtifactModal()
const [openTableArtifactModal, renderTableArtifactModal] =
useTableArtifactModal()

const width = "200px"
const height = "150px"
Expand Down Expand Up @@ -96,6 +99,19 @@ export const StudyArtifactCards: FC<{ study: StudyDetail }> = ({ study }) => {
<FullscreenIcon />
</IconButton>
) : null}
{isTableArtifact(artifact) ? (
<IconButton
aria-label="show artifact table"
size="small"
color="inherit"
sx={{ margin: "auto 0" }}
onClick={() => {
openTableArtifactModal(urlPath, artifact)
}}
>
<FullscreenIcon />
</IconButton>
) : null}
<IconButton
aria-label="delete artifact"
size="small"
Expand Down Expand Up @@ -125,6 +141,7 @@ export const StudyArtifactCards: FC<{ study: StudyDetail }> = ({ study }) => {
</Box>
{renderDeleteArtifactDialog()}
{renderThreejsArtifactModal()}
{renderTableArtifactModal()}
</>
)
}
Expand Down
131 changes: 131 additions & 0 deletions optuna_dashboard/ts/components/Artifact/TableArtifactViewer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import ClearIcon from "@mui/icons-material/Clear"
import { Box, Modal, useTheme } from "@mui/material"
import IconButton from "@mui/material/IconButton"
import { useSnackbar } from "notistack"
import Papa from "papaparse"
import React, { useState, useEffect, ReactNode } from "react"
import { DataGrid } from "../DataGrid"

import { Artifact } from "ts/types/optuna"

export const isTableArtifact = (artifact: Artifact): boolean => {
return artifact.filename.endsWith(".csv")
}

interface TableArtifactViewerProps {
src: string
filetype: string | undefined
}

type Data = {
[key: string]: string | number
}

export const TableArtifactViewer: React.FC<TableArtifactViewerProps> = (
props
) => {
const [data, setData] = useState<Data[]>([])
const { enqueueSnackbar } = useSnackbar()

useEffect(() => {
const handleFileChange = async () => {
try {
const loadedData = await loadCSV(props)
setData(loadedData)
} catch (error: unknown) {
enqueueSnackbar("Failed to load the csv file.", {
variant: "error",
})
}
}
handleFileChange()
}, [props])

const columns = React.useMemo(() => {
const keys = data[0] ? Object.keys(data[0]) : []
return keys.map((key) => ({
header: key,
accessorKey: key,
enableSorting: true,
enableColumnFilter: false,
}))
}, [data])

return <DataGrid data={data} columns={columns} initialRowsPerPage={10} />
}

export const useTableArtifactModal = (): [
(path: string, artifact: Artifact) => void,
() => ReactNode,
] => {
const [open, setOpen] = useState(false)
const [target, setTarget] = useState<[string, Artifact | null]>(["", null])
const theme = useTheme()

const openModal = (artifactUrlPath: string, artifact: Artifact) => {
setTarget([artifactUrlPath, artifact])
setOpen(true)
}

const renderDeleteStudyDialog = () => {
return (
<Modal
open={open}
onClose={() => {
setOpen(false)
setTarget(["", null])
}}
>
<Box
component="div"
sx={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
bgcolor: "background.paper",
borderRadius: "15px",
width: "80%",
maxHeight: "80%",
overflowY: "auto",
p: 2,
}}
>
<IconButton
sx={{
position: "absolute",
top: theme.spacing(2),
right: theme.spacing(2),
}}
onClick={() => {
setOpen(false)
setTarget(["", null])
}}
>
<ClearIcon />
</IconButton>
<TableArtifactViewer
src={target[0]}
filetype={target[1]?.filename.split(".").pop()}
/>
</Box>
</Modal>
)
}
return [openModal, renderDeleteStudyDialog]
}

const loadCSV = (props: TableArtifactViewerProps): Promise<Data[]> => {
return new Promise((resolve, reject) => {
Papa.parse(props.src, {
header: true,
download: true,
complete: (results: Papa.ParseResult<Data>) => {
resolve(results?.data)
},
error: () => {
reject(new Error("csv parse err"))
},
})
})
}
16 changes: 9 additions & 7 deletions optuna_dashboard/ts/components/DataGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,17 +119,24 @@ function FilterMenu<T>({
function DataGrid<T>({
data,
columns,
initialRowsPerPage,
}: {
data: T[]
columns: ColumnDef<T>[]
initialRowsPerPage?: number
}): React.ReactElement {
const [sorting, setSorting] = React.useState<SortingState>([])
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[]
)
const rowsPerPageOptions = [10, 50, 100, { label: "All", value: data.length }]

const [pagination, setPagination] = React.useState<PaginationState>({
pageIndex: 0,
pageSize: 50,
pageSize:
initialRowsPerPage && rowsPerPageOptions.includes(initialRowsPerPage)
? initialRowsPerPage
: 50,
})

const table = useReactTable({
Expand Down Expand Up @@ -236,12 +243,7 @@ function DataGrid<T>({
</TableContainer>
<Box component="div" display="flex" alignItems="center">
<TablePagination
rowsPerPageOptions={[
10,
50,
100,
{ label: "All", value: data.length },
]}
rowsPerPageOptions={rowsPerPageOptions}
component="div"
count={table.getFilteredRowModel().rows.length}
rowsPerPage={table.getState().pagination.pageSize}
Expand Down

0 comments on commit a6f08cd

Please sign in to comment.