mirror of
https://github.com/wassname/optuna-dashboard.git
synced 2026-09-09 11:28:14 +08:00
Merge branch 'main' of github.com:porink0424/optuna-dashboard into followup/feature/tslib-trialtable
This commit is contained in:
@@ -6,8 +6,12 @@ on:
|
||||
paths:
|
||||
- '.github/workflows/e2e-dashboard-tests.yml'
|
||||
- '**.py'
|
||||
- '**.ts'
|
||||
- '**.tsx'
|
||||
- 'tslib/**.ts'
|
||||
- 'tslib/**.tsx'
|
||||
- 'tslib/**/package.json'
|
||||
- 'tslib/**/package-lock.json'
|
||||
- 'optuna_dashboard/**.ts'
|
||||
- 'optuna_dashboard/**.tsx'
|
||||
- 'optuna_dashboard/package.json'
|
||||
- 'optuna_dashboard/package-lock.json'
|
||||
- 'optuna_dashboard/tsconfig.json'
|
||||
@@ -37,7 +41,7 @@ jobs:
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.10'
|
||||
python-version: '3.11'
|
||||
architecture: x64
|
||||
|
||||
- name: Setup Optuna ${{ matrix.optuna-version }}
|
||||
|
||||
@@ -5,6 +5,10 @@ on:
|
||||
- main
|
||||
paths:
|
||||
- '.github/workflows/e2e-standalone-tests.yml'
|
||||
- 'tslib/**.ts'
|
||||
- 'tslib/**.tsx'
|
||||
- 'tslib/**/package.json'
|
||||
- 'tslib/**/package-lock.json'
|
||||
- 'standalone_app/**.ts'
|
||||
- 'standalone_app/**.tsx'
|
||||
- 'standalone_app/package.json'
|
||||
@@ -34,7 +38,7 @@ jobs:
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.10'
|
||||
python-version: '3.11'
|
||||
architecture: x64
|
||||
|
||||
- name: Setup Optuna ${{ matrix.optuna-version }}
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '18'
|
||||
node-version: '20'
|
||||
- name: Build rustlib
|
||||
working-directory: rustlib
|
||||
run: wasm-pack build --target web
|
||||
|
||||
@@ -16,22 +16,16 @@ jobs:
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Build bundle.js
|
||||
working-directory: optuna_dashboard
|
||||
run: |
|
||||
npm install
|
||||
npm run build:prd
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10'
|
||||
python-version: '3.11'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip setuptools
|
||||
pip install --progress-bar off wheel twine build
|
||||
- run: python -m build --sdist --wheel
|
||||
|
||||
- run: make python-package
|
||||
- run: twine check dist/*
|
||||
|
||||
- name: Create GitHub release
|
||||
|
||||
@@ -13,27 +13,20 @@ jobs:
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Build bundle.js
|
||||
working-directory: optuna_dashboard
|
||||
run: |
|
||||
npm install
|
||||
npm run build:prd
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10'
|
||||
python-version: '3.11'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip setuptools
|
||||
pip install --progress-bar off wheel twine build
|
||||
- run: python -m build --sdist --wheel
|
||||
|
||||
- run: make python-package
|
||||
|
||||
- name: Publish distribution to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10'
|
||||
python-version: '3.11'
|
||||
architecture: x64
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
@@ -26,6 +26,23 @@ jobs:
|
||||
- run: black --check --diff .
|
||||
- run: isort --check --diff .
|
||||
- run: mypy optuna_dashboard python_tests
|
||||
build-python-package:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '20'
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip setuptools
|
||||
pip install --progress-bar off wheel twine build
|
||||
- run: make python-package
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
|
||||
+21
-15
@@ -9,18 +9,21 @@ The repository is organized as follows:
|
||||
|
||||
```
|
||||
.
|
||||
├── optuna_dashboard/ # The Python package.
|
||||
│ └── ts/ # TypeScript code for the Python package.
|
||||
├── standalone_app/ # Standalone application that can be run in browser or within the WebView of the VS Code extension.
|
||||
│ ├── browser_app_entry.tsx # Entry point for browser app, hosted on GitHub pages.
|
||||
│ └── vscode_entry.tsx # Entry point for VS Code app, output placed under `vscode/assets`.
|
||||
├── vscode/ # The VS Code extension.
|
||||
├── rustlib/ # Rust library exporting Wasm functions.
|
||||
│ └── pkg/ # Output directory for rustlib, installed from package.json via `"./rustlib/pkg"`.
|
||||
└── tslib/ # TypeScript library shared for common use.
|
||||
├── react/ # Common React components.
|
||||
├── storage/ # Common code for handling storage.
|
||||
└── types/ # Common TypeScript types.
|
||||
├── optuna_dashboard/ # The Python package.
|
||||
│ └── ts/ # TypeScript code for the Python package.
|
||||
│ ├── index.tsx # Entry point for the Python package.
|
||||
│ └── pkg_index.tsx # Entry point for Jupyter Lab extension, output placed under `optuna_dashboard/pkg/`.
|
||||
├── standalone_app/ # Standalone application that can be run in browser or within the WebView of the VS Code extension.
|
||||
│ ├── browser_app_entry.tsx # Entry point for browser app, hosted on GitHub pages.
|
||||
│ └── vscode_entry.tsx # Entry point for VS Code extension, output placed under `vscode/assets`.
|
||||
├── vscode/ # The VS Code extension.
|
||||
├── jupyterlab/ # The Jupyter Lab extension.
|
||||
├── rustlib/ # Rust library exporting Wasm functions.
|
||||
│ └── pkg/ # Output directory for rustlib, installed from package.json via `"./rustlib/pkg"`.
|
||||
└── tslib/ # TypeScript library shared for common use.
|
||||
├── react/ # Common React components.
|
||||
├── storage/ # Common code for handling storage.
|
||||
└── types/ # Common TypeScript types.
|
||||
```
|
||||
|
||||
## Python package
|
||||
@@ -145,8 +148,6 @@ The release process(compiling TypeScript files, packaging Python distributions a
|
||||
|
||||
## Standalone Single-page Application
|
||||
|
||||
### Compiling Rust library and TypeScript files
|
||||
|
||||
Please install [wasm-pack](https://rustwasm.github.io/wasm-pack/installer/) and execute the following command.
|
||||
|
||||
```
|
||||
@@ -155,10 +156,15 @@ $ make serve-browser-app
|
||||
|
||||
Open http://localhost:5173/
|
||||
|
||||
|
||||
## VS Code Extension
|
||||
|
||||
```
|
||||
$ npm i -g vsce
|
||||
$ make vscode-extension
|
||||
```
|
||||
|
||||
## Jupyter Lab Extension
|
||||
|
||||
```
|
||||
$ make jupyterlab-extension
|
||||
```
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.DEFAULT_GOAL := sdist
|
||||
.DEFAULT_GOAL := python-package
|
||||
|
||||
PYTHON ?= python3
|
||||
MODE ?= dev
|
||||
@@ -15,7 +15,7 @@ $(RUSTLIB_OUT): rustlib/src/*.rs rustlib/Cargo.toml
|
||||
vscode/assets/bundle.js: $(RUSTLIB_OUT) $(STANDALONE_SRC) tslib
|
||||
cd standalone_app && npm install && npm run build:vscode
|
||||
|
||||
$(DASHBOARD_TS_OUT): $(DASHBOARD_TS_SRC)
|
||||
$(DASHBOARD_TS_OUT): $(DASHBOARD_TS_SRC) tslib
|
||||
cd optuna_dashboard && npm install && npm run build:$(MODE)
|
||||
|
||||
.PHONY: tslib
|
||||
@@ -31,24 +31,21 @@ tslib-test: tslib
|
||||
|
||||
.PHONY: serve-browser-app
|
||||
serve-browser-app: tslib $(RUSTLIB_OUT)
|
||||
cd standalone_app && npm install && npm run watch
|
||||
cd standalone_app && npm i && npm run watch
|
||||
|
||||
.PHONY: vscode-extension
|
||||
vscode-extension: vscode/assets/bundle.js
|
||||
cd vscode && npm install && npm run vscode:prepublish && vsce package
|
||||
cd vscode && npm i && npm run vscode:prepublish && vsce package
|
||||
|
||||
.PHONY: jupyterlab-extension
|
||||
jupyterlab-extension: tslib
|
||||
cd optuna_dashboard && npm install && npm run build:pkg
|
||||
cd jupyterlab && python -m build --sdist
|
||||
|
||||
.PHONY: sdist
|
||||
sdist: pyproject.toml $(DASHBOARD_TS_OUT)
|
||||
python -m build --sdist
|
||||
|
||||
.PHONY: wheel
|
||||
wheel: pyproject.toml $(DASHBOARD_TS_OUT)
|
||||
python -m build --wheel
|
||||
.PHONY: python-package
|
||||
python-package: pyproject.toml tslib
|
||||
cd optuna_dashboard && npm i && npm run build:prd
|
||||
python -m build --sdist --wheel
|
||||
|
||||
.PHONY: docs
|
||||
docs: docs/conf.py $(RST_FILES)
|
||||
@@ -64,4 +61,4 @@ fmt:
|
||||
clean:
|
||||
rm -rf tslib/types/pkg tslib/storage/pkg tslib/react/pkg tslib/react/types
|
||||
rm -rf optuna_dashboard/public/ doc/_build/
|
||||
rm -rf rustlib/pkg standalone_app/public/ vscode/assets/ vscode/*.vsix
|
||||
rm -rf rustlib/pkg vscode/assets/ vscode/*.vsix
|
||||
|
||||
@@ -27,7 +27,7 @@ def run_single_objective_study(storage: optuna.storages.InMemoryStorage) -> optu
|
||||
x2 = trial.suggest_float("x2", 0, 10)
|
||||
return (x1 - 2) ** 2 + (x2 - 5) ** 2
|
||||
|
||||
study.optimize(objective, n_trials=50)
|
||||
study.optimize(objective, n_trials=20)
|
||||
return study
|
||||
|
||||
|
||||
@@ -46,20 +46,6 @@ def run_single_trial_objective_study(storage: optuna.storages.InMemoryStorage) -
|
||||
return study
|
||||
|
||||
|
||||
def run_single_1param_objective_study(storage: optuna.storages.InMemoryStorage) -> optuna.Study:
|
||||
sampler = optuna.samplers.RandomSampler(seed=0)
|
||||
study = optuna.create_study(
|
||||
study_name="single-1-param", storage=storage, direction="maximize", sampler=sampler
|
||||
)
|
||||
|
||||
def objective(trial: optuna.Trial) -> float:
|
||||
x1 = trial.suggest_float("x1", 0, 10)
|
||||
return -((x1 - 2) ** 2)
|
||||
|
||||
study.optimize(objective, n_trials=50)
|
||||
return study
|
||||
|
||||
|
||||
def run_single_dynamic_objective_study(storage: optuna.storages.InMemoryStorage) -> optuna.Study:
|
||||
# Single-objective study with dynamic search space
|
||||
sampler = optuna.samplers.RandomSampler(seed=0)
|
||||
@@ -78,24 +64,6 @@ def run_single_dynamic_objective_study(storage: optuna.storages.InMemoryStorage)
|
||||
return study
|
||||
|
||||
|
||||
def run_single_inf_objective_study(storage: optuna.storages.InMemoryStorage) -> optuna.Study:
|
||||
# Single objective study with 'inf', '-inf', or 'nan' value
|
||||
sampler = optuna.samplers.RandomSampler(seed=0)
|
||||
study = optuna.create_study(study_name="single-inf", storage=storage, sampler=sampler)
|
||||
|
||||
def objective(trial: optuna.Trial) -> float:
|
||||
x = trial.suggest_float("x", -10, 10)
|
||||
if trial.number % 3 == 0:
|
||||
return float("inf")
|
||||
elif trial.number % 3 == 1:
|
||||
return float("-inf")
|
||||
else:
|
||||
return x**2
|
||||
|
||||
study.optimize(objective, n_trials=50)
|
||||
return study
|
||||
|
||||
|
||||
def run_multi_objective_study(storage: optuna.storages.InMemoryStorage) -> optuna.Study:
|
||||
# Multi-objective study
|
||||
sampler = optuna.samplers.RandomSampler(seed=0)
|
||||
@@ -113,7 +81,7 @@ def run_multi_objective_study(storage: optuna.storages.InMemoryStorage) -> optun
|
||||
v1 = (x - 5) ** 2 + (y - 5) ** 2
|
||||
return v0, v1
|
||||
|
||||
study.optimize(objective, n_trials=50)
|
||||
study.optimize(objective, n_trials=20)
|
||||
return study
|
||||
|
||||
|
||||
@@ -142,7 +110,7 @@ def run_multi_dynamic_objective_study(storage: optuna.storages.InMemoryStorage)
|
||||
v1 = (x - 2) ** 2 + (y - 3) ** 2
|
||||
return v0, v1
|
||||
|
||||
study.optimize(objective, n_trials=50)
|
||||
study.optimize(objective, n_trials=20)
|
||||
return study
|
||||
|
||||
|
||||
@@ -167,66 +135,6 @@ def run_single_pruned_without_report_objective_study(
|
||||
return study
|
||||
|
||||
|
||||
def run_single_inf_report_objective_study(
|
||||
storage: optuna.storages.InMemoryStorage,
|
||||
) -> optuna.Study:
|
||||
# Single objective pruned after reported 'inf', '-inf', or 'nan'
|
||||
sampler = optuna.samplers.RandomSampler(seed=0)
|
||||
study = optuna.create_study(study_name="single-inf-report", storage=storage, sampler=sampler)
|
||||
|
||||
def objective(trial: optuna.Trial) -> float:
|
||||
x = trial.suggest_float("x", -10, 10)
|
||||
if trial.number % 3 == 0:
|
||||
trial.report(float("inf"), 1)
|
||||
elif trial.number % 3 == 1:
|
||||
trial.report(float("-inf"), 1)
|
||||
else:
|
||||
trial.report(float("nan"), 1)
|
||||
|
||||
if x > 0:
|
||||
raise optuna.TrialPruned()
|
||||
else:
|
||||
return x**2
|
||||
|
||||
study.optimize(objective, n_trials=50)
|
||||
return study
|
||||
|
||||
|
||||
def run_issue_410_objective_study(storage: optuna.storages.InMemoryStorage) -> optuna.Study:
|
||||
# Issue 410
|
||||
sampler = optuna.samplers.RandomSampler(seed=0)
|
||||
study = optuna.create_study(study_name="issue-410", storage=storage, sampler=sampler)
|
||||
|
||||
def objective(trial: optuna.Trial) -> float:
|
||||
trial.suggest_categorical("resample_rate", ["50ms"])
|
||||
trial.suggest_categorical("channels", ["all"])
|
||||
trial.suggest_categorical("window_size", [256])
|
||||
if trial.number > 15:
|
||||
raise Exception("Unexpected error")
|
||||
trial.suggest_categorical("cbow", [True])
|
||||
trial.suggest_categorical("model", ["m1"])
|
||||
|
||||
trial.set_user_attr("epochs", 0)
|
||||
trial.set_user_attr("deterministic", True)
|
||||
if trial.number > 10:
|
||||
raise Exception("unexpeccted error")
|
||||
trial.set_user_attr("folder", "/path/to/folder")
|
||||
trial.set_user_attr("resample_type", "foo")
|
||||
trial.set_user_attr("run_id", "0001")
|
||||
return 1.0
|
||||
|
||||
study.optimize(objective, n_trials=20, catch=(Exception,))
|
||||
return study
|
||||
|
||||
|
||||
def run_single_no_trials_objective_study(storage: optuna.storages.InMemoryStorage) -> optuna.Study:
|
||||
# No trials single-objective study
|
||||
sampler = optuna.samplers.RandomSampler(seed=0)
|
||||
study = optuna.create_study(study_name="single-no-trials", storage=storage, sampler=sampler)
|
||||
|
||||
return study
|
||||
|
||||
|
||||
def run_multi_no_trials_objective_study(storage: optuna.storages.InMemoryStorage) -> optuna.Study:
|
||||
# No trials multi-objective study
|
||||
sampler = optuna.samplers.RandomSampler(seed=0)
|
||||
@@ -244,15 +152,10 @@ parameterize_studies = pytest.mark.parametrize(
|
||||
[
|
||||
run_single_objective_study,
|
||||
run_single_trial_objective_study,
|
||||
run_single_1param_objective_study,
|
||||
run_single_dynamic_objective_study,
|
||||
run_single_inf_objective_study,
|
||||
run_multi_objective_study,
|
||||
run_multi_dynamic_objective_study,
|
||||
run_single_pruned_without_report_objective_study,
|
||||
run_single_inf_report_objective_study,
|
||||
run_issue_410_objective_study,
|
||||
run_single_no_trials_objective_study,
|
||||
run_multi_no_trials_objective_study,
|
||||
],
|
||||
)
|
||||
@@ -273,6 +176,7 @@ def test_study_list(
|
||||
page.goto(server_url)
|
||||
page.click(f"a[href='/dashboard/studies/{study_id}']")
|
||||
|
||||
page.wait_for_selector(".MuiTypography-body1")
|
||||
element = page.query_selector(".MuiTypography-body1")
|
||||
assert element is not None
|
||||
|
||||
@@ -289,14 +193,15 @@ def test_study_analytics(
|
||||
run_study: Callable[[optuna.storages.InMemoryStorage], optuna.Study],
|
||||
) -> None:
|
||||
study = run_study(storage)
|
||||
|
||||
study_id = study._study_id
|
||||
study_name = study.study_name
|
||||
url = f"{server_url}/studies/{study_id}"
|
||||
|
||||
page.on("console", lambda msg: print(f"error: {msg.text}") if msg.type == "error" else None)
|
||||
page.goto(url)
|
||||
page.click(f"a[href='/dashboard/studies/{study_id}/analytics']")
|
||||
|
||||
page.wait_for_selector(".MuiTypography-body1", timeout=60 * 1000)
|
||||
element = page.query_selector(".MuiTypography-body1")
|
||||
assert element is not None
|
||||
|
||||
@@ -321,6 +226,7 @@ def test_trial_list(
|
||||
page.goto(url)
|
||||
page.click(f"a[href='/dashboard/studies/{study_id}/trials']")
|
||||
|
||||
page.wait_for_selector(".MuiTypography-body1")
|
||||
element = page.query_selector(".MuiTypography-body1")
|
||||
assert element is not None
|
||||
|
||||
@@ -345,6 +251,7 @@ def test_trial_table(
|
||||
page.goto(url)
|
||||
page.click(f"a[href='/dashboard/studies/{study_id}/trialTable']")
|
||||
|
||||
page.wait_for_selector(".MuiTypography-body1")
|
||||
element = page.query_selector(".MuiTypography-body1")
|
||||
assert element is not None
|
||||
|
||||
@@ -367,6 +274,7 @@ def test_trial_note(
|
||||
url = f"{server_url}/studies/{study_id}"
|
||||
|
||||
page.goto(url)
|
||||
page.wait_for_selector(".MuiTypography-body1")
|
||||
page.click(f"a[href='/dashboard/studies/{study_id}/note']")
|
||||
|
||||
element = page.query_selector(".MuiTypography-body1")
|
||||
|
||||
@@ -17,4 +17,4 @@ from ._note import save_note # noqa
|
||||
from ._preference_setting import register_preference_feedback_component # noqa
|
||||
|
||||
|
||||
__version__ = "0.15.1"
|
||||
__version__ = "0.16.0"
|
||||
|
||||
Generated
+13
-11
@@ -23,7 +23,7 @@
|
||||
"@tanstack/react-virtual": "^3.1.2",
|
||||
"@types/papaparse": "^5.3.14",
|
||||
"@types/three": "^0.160.0",
|
||||
"axios": "^1.6.7",
|
||||
"axios": "^1.7.4",
|
||||
"elkjs": "^0.9.1",
|
||||
"notistack": "^3.0.1",
|
||||
"papaparse": "^5.4.1",
|
||||
@@ -79,15 +79,16 @@
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@chromatic-com/storybook": "^1.6.1",
|
||||
"@optuna/storage": "file:../storage",
|
||||
"@optuna/types": "file:../types",
|
||||
"@storybook/addon-essentials": "^8.0.4",
|
||||
"@storybook/addon-interactions": "^8.0.4",
|
||||
"@storybook/addon-links": "^8.0.4",
|
||||
"@storybook/blocks": "^8.0.4",
|
||||
"@storybook/react": "^8.0.4",
|
||||
"@storybook/react-vite": "^8.0.4",
|
||||
"@storybook/test": "^8.0.4",
|
||||
"@storybook/addon-essentials": "^8.2.9",
|
||||
"@storybook/addon-interactions": "^8.2.9",
|
||||
"@storybook/addon-links": "^8.2.9",
|
||||
"@storybook/blocks": "^8.2.9",
|
||||
"@storybook/react": "^8.2.9",
|
||||
"@storybook/react-vite": "^8.2.9",
|
||||
"@storybook/test": "^8.2.9",
|
||||
"@testing-library/jest-dom": "^6.4.2",
|
||||
"@testing-library/react": "^14.2.2",
|
||||
"@types/plotly.js-dist-min": "^2.3.4",
|
||||
@@ -95,7 +96,7 @@
|
||||
"@types/react-dom": "^18.2.19",
|
||||
"@vitejs/plugin-react-swc": "^3.5.0",
|
||||
"jsdom": "^24.0.0",
|
||||
"storybook": "^8.0.4",
|
||||
"storybook": "^8.2.9",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^5.1.0",
|
||||
"vitest": "^1.4.0"
|
||||
@@ -14913,8 +14914,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.7.2",
|
||||
"license": "MIT",
|
||||
"version": "1.7.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz",
|
||||
"integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
"@tanstack/react-virtual": "^3.1.2",
|
||||
"@types/papaparse": "^5.3.14",
|
||||
"@types/three": "^0.160.0",
|
||||
"axios": "^1.6.7",
|
||||
"axios": "^1.7.4",
|
||||
"elkjs": "^0.9.1",
|
||||
"notistack": "^3.0.1",
|
||||
"papaparse": "^5.4.1",
|
||||
|
||||
@@ -65,6 +65,7 @@ export interface StudyDetailResponse {
|
||||
has_intermediate_values: boolean
|
||||
note: Note
|
||||
is_preferential: boolean
|
||||
// TODO(c-bata): Rename this to metric_names after releasing the new Jupyter Lab extension.
|
||||
objective_names?: string[]
|
||||
form_widgets?: FormWidgets
|
||||
preferences?: [number, number][]
|
||||
|
||||
@@ -62,7 +62,7 @@ export class AxiosClient extends APIClient {
|
||||
union_user_attrs: res.data.union_user_attrs,
|
||||
has_intermediate_values: res.data.has_intermediate_values,
|
||||
note: res.data.note,
|
||||
objective_names: res.data.objective_names,
|
||||
metric_names: res.data.objective_names,
|
||||
form_widgets: res.data.form_widgets,
|
||||
is_preferential: res.data.is_preferential,
|
||||
feedback_component_type: res.data.feedback_component_type,
|
||||
|
||||
@@ -130,7 +130,7 @@ const ContourFrontend: FC<{
|
||||
const searchSpace = useMergedUnionSearchSpace(study?.union_search_space)
|
||||
const [xParam, setXParam] = useState<SearchSpaceItem | null>(null)
|
||||
const [yParam, setYParam] = useState<SearchSpaceItem | null>(null)
|
||||
const objectiveNames: string[] = study?.objective_names || []
|
||||
const metricNames: string[] = study?.metric_names || []
|
||||
|
||||
if (xParam === null && searchSpace.length > 0) {
|
||||
setXParam(searchSpace[0])
|
||||
@@ -182,8 +182,8 @@ const ContourFrontend: FC<{
|
||||
<Select value={objectiveId} onChange={handleObjectiveChange}>
|
||||
{study.directions.map((d, i) => (
|
||||
<MenuItem value={i} key={i}>
|
||||
{objectiveNames.length === study?.directions.length
|
||||
? objectiveNames[i]
|
||||
{metricNames.length === study?.directions.length
|
||||
? metricNames[i]
|
||||
: `${i}`}
|
||||
</MenuItem>
|
||||
))}
|
||||
|
||||
@@ -32,7 +32,7 @@ interface HistoryPlotInfo {
|
||||
study_name: string
|
||||
trials: Optuna.Trial[]
|
||||
directions: Optuna.StudyDirection[]
|
||||
objective_names?: string[]
|
||||
metric_names?: string[]
|
||||
}
|
||||
|
||||
export const GraphHistory: FC<{
|
||||
@@ -63,7 +63,7 @@ export const GraphHistory: FC<{
|
||||
study_name: study?.name,
|
||||
trials: trials[index],
|
||||
directions: study?.directions,
|
||||
objective_names: study?.objective_names,
|
||||
metric_names: study?.metric_names,
|
||||
}
|
||||
return h
|
||||
})
|
||||
@@ -164,7 +164,7 @@ export const GraphHistory: FC<{
|
||||
>
|
||||
{targets.map((t, i) => (
|
||||
<MenuItem value={t.identifier()} key={i}>
|
||||
{t.toLabel(studies[0].objective_names)}
|
||||
{t.toLabel(studies[0].metric_names)}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
@@ -253,7 +253,7 @@ const plotHistory = (
|
||||
b: 0,
|
||||
},
|
||||
yaxis: {
|
||||
title: target.toLabel(historyPlotInfos[0].objective_names),
|
||||
title: target.toLabel(historyPlotInfos[0].metric_names),
|
||||
type: logScale ? "log" : "linear",
|
||||
},
|
||||
xaxis: {
|
||||
@@ -293,7 +293,7 @@ const plotHistory = (
|
||||
y: feasibleTrials.map(
|
||||
(t: Optuna.Trial): number => target.getTargetValue(t) as number
|
||||
),
|
||||
name: `${target.toLabel(h.objective_names)} of ${h.study_name}`,
|
||||
name: `${target.toLabel(h.metric_names)} of ${h.study_name}`,
|
||||
marker: {
|
||||
size: markerSize,
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Box, Card, CardContent } from "@mui/material"
|
||||
import { Box, Card, CardContent, useTheme } from "@mui/material"
|
||||
import * as plotly from "plotly.js-dist-min"
|
||||
import React, { FC, useEffect } from "react"
|
||||
|
||||
@@ -7,7 +7,7 @@ import { StudyDetail } from "ts/types/optuna"
|
||||
import { PlotType } from "../apiClient"
|
||||
import { useParamImportance } from "../hooks/useParamImportance"
|
||||
import { usePlot } from "../hooks/usePlot"
|
||||
import { useBackendRender } from "../state"
|
||||
import { useBackendRender, usePlotlyColorTheme } from "../state"
|
||||
|
||||
const plotDomId = "graph-hyperparameter-importances"
|
||||
|
||||
@@ -32,6 +32,8 @@ export const GraphHyperparameterImportance: FC<{
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
const theme = useTheme()
|
||||
const colorTheme = usePlotlyColorTheme(theme.palette.mode)
|
||||
return (
|
||||
<Card>
|
||||
<CardContent>
|
||||
@@ -39,6 +41,7 @@ export const GraphHyperparameterImportance: FC<{
|
||||
study={study}
|
||||
importance={importances}
|
||||
graphHeight={graphHeight}
|
||||
colorTheme={colorTheme}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { Card, CardContent } from "@mui/material"
|
||||
import { Card, CardContent, useTheme } from "@mui/material"
|
||||
import { PlotIntermediateValues } from "@optuna/react"
|
||||
import React, { FC } from "react"
|
||||
import { Trial } from "ts/types/optuna"
|
||||
import { usePlotlyColorTheme } from "../state"
|
||||
|
||||
export const GraphIntermediateValues: FC<{
|
||||
trials: Trial[]
|
||||
includePruned: boolean
|
||||
logScale: boolean
|
||||
}> = ({ trials, includePruned, logScale }) => {
|
||||
const theme = useTheme()
|
||||
const colorTheme = usePlotlyColorTheme(theme.palette.mode)
|
||||
return (
|
||||
<Card>
|
||||
<CardContent>
|
||||
@@ -15,6 +18,7 @@ export const GraphIntermediateValues: FC<{
|
||||
trials={trials}
|
||||
includePruned={includePruned}
|
||||
logScale={logScale}
|
||||
colorTheme={colorTheme}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -1,104 +1,24 @@
|
||||
import {
|
||||
Checkbox,
|
||||
FormControlLabel,
|
||||
FormGroup,
|
||||
Grid,
|
||||
Typography,
|
||||
useTheme,
|
||||
} from "@mui/material"
|
||||
import {
|
||||
GraphContainer,
|
||||
PlotParallelCoordinate,
|
||||
useGraphComponentState,
|
||||
useMergedUnionSearchSpace,
|
||||
} from "@optuna/react"
|
||||
import {
|
||||
Target,
|
||||
useFilteredTrials,
|
||||
useObjectiveAndUserAttrTargets,
|
||||
useParamTargets,
|
||||
} from "@optuna/react"
|
||||
import * as Optuna from "@optuna/types"
|
||||
import * as plotly from "plotly.js-dist-min"
|
||||
import React, { FC, ReactNode, useEffect, useState } from "react"
|
||||
import { SearchSpaceItem, StudyDetail } from "ts/types/optuna"
|
||||
import React, { FC, useEffect } from "react"
|
||||
import { StudyDetail } from "ts/types/optuna"
|
||||
import { PlotType } from "../apiClient"
|
||||
import { usePlot } from "../hooks/usePlot"
|
||||
import { usePlotlyColorTheme } from "../state"
|
||||
import { useBackendRender } from "../state"
|
||||
|
||||
const plotDomId = "graph-parallel-coordinate"
|
||||
|
||||
const useTargets = (
|
||||
study: StudyDetail | null
|
||||
): [Target[], SearchSpaceItem[], () => ReactNode] => {
|
||||
const [targets1] = useObjectiveAndUserAttrTargets(study)
|
||||
const searchSpace = useMergedUnionSearchSpace(study?.union_search_space)
|
||||
const [targets2] = useParamTargets(searchSpace)
|
||||
const [checked, setChecked] = useState<boolean[]>([true])
|
||||
|
||||
const allTargets = [...targets1, ...targets2]
|
||||
useEffect(() => {
|
||||
if (allTargets.length !== checked.length) {
|
||||
setChecked(
|
||||
allTargets.map((t) => {
|
||||
if (t.kind === "user_attr") {
|
||||
return false
|
||||
}
|
||||
if (t.kind !== "params" || study === null) {
|
||||
return true
|
||||
}
|
||||
// By default, params that is not included in intersection search space should be disabled,
|
||||
// otherwise all trials are filtered.
|
||||
return (
|
||||
study.intersection_search_space.find((s) => s.name === t.key) !==
|
||||
undefined
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
}, [allTargets])
|
||||
|
||||
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setChecked(
|
||||
checked.map((c, i) =>
|
||||
i.toString() === event.target.name ? event.target.checked : c
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
const renderCheckBoxes = (): ReactNode => (
|
||||
<FormGroup>
|
||||
{allTargets.map((t, i) => {
|
||||
return (
|
||||
<FormControlLabel
|
||||
key={i}
|
||||
control={
|
||||
<Checkbox
|
||||
checked={checked.length > i ? checked[i] : true}
|
||||
onChange={handleOnChange}
|
||||
name={i.toString()}
|
||||
/>
|
||||
}
|
||||
label={t.toLabel(study?.objective_names)}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</FormGroup>
|
||||
)
|
||||
|
||||
const targets = allTargets.filter((t, i) =>
|
||||
checked.length > i ? checked[i] : true
|
||||
)
|
||||
return [targets, searchSpace, renderCheckBoxes]
|
||||
}
|
||||
|
||||
export const GraphParallelCoordinate: FC<{
|
||||
study: StudyDetail | null
|
||||
}> = ({ study = null }) => {
|
||||
if (useBackendRender()) {
|
||||
return <GraphParallelCoordinateBackend study={study} />
|
||||
} else {
|
||||
return <GraphParallelCoordinateFrontend study={study} />
|
||||
return <PlotParallelCoordinate study={study} />
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,198 +55,3 @@ const GraphParallelCoordinateBackend: FC<{
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const GraphParallelCoordinateFrontend: FC<{
|
||||
study: StudyDetail | null
|
||||
}> = ({ study = null }) => {
|
||||
const { graphComponentState, notifyGraphDidRender } = useGraphComponentState()
|
||||
|
||||
const theme = useTheme()
|
||||
const colorTheme = usePlotlyColorTheme(theme.palette.mode)
|
||||
|
||||
const [targets, searchSpace, renderCheckBoxes] = useTargets(study)
|
||||
|
||||
const trials = useFilteredTrials(study, targets, false)
|
||||
useEffect(() => {
|
||||
if (study !== null && graphComponentState !== "componentWillMount") {
|
||||
plotCoordinate(study, trials, targets, searchSpace, colorTheme)?.then(
|
||||
notifyGraphDidRender
|
||||
)
|
||||
}
|
||||
}, [study, trials, targets, searchSpace, colorTheme, graphComponentState])
|
||||
|
||||
return (
|
||||
<Grid container direction="row">
|
||||
<Grid
|
||||
item
|
||||
xs={3}
|
||||
container
|
||||
direction="column"
|
||||
sx={{
|
||||
paddingRight: theme.spacing(2),
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{ margin: "1em 0", fontWeight: theme.typography.fontWeightBold }}
|
||||
>
|
||||
Parallel Coordinate
|
||||
</Typography>
|
||||
{renderCheckBoxes()}
|
||||
</Grid>
|
||||
<Grid item xs={9}>
|
||||
<GraphContainer
|
||||
plotDomId={plotDomId}
|
||||
graphComponentState={graphComponentState}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
const plotCoordinate = (
|
||||
study: StudyDetail,
|
||||
trials: Optuna.Trial[],
|
||||
targets: Target[],
|
||||
searchSpace: SearchSpaceItem[],
|
||||
colorTheme: Partial<Plotly.Template>
|
||||
) => {
|
||||
if (document.getElementById(plotDomId) === null) {
|
||||
return
|
||||
}
|
||||
|
||||
const layout: Partial<plotly.Layout> = {
|
||||
margin: {
|
||||
l: 70,
|
||||
t: 50,
|
||||
r: 50,
|
||||
b: 100,
|
||||
},
|
||||
template: colorTheme,
|
||||
uirevision: "true",
|
||||
}
|
||||
if (trials.length === 0 || targets.length === 0) {
|
||||
return plotly.react(plotDomId, [], layout)
|
||||
}
|
||||
|
||||
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 + "<br>" : c
|
||||
})
|
||||
.join("")
|
||||
}
|
||||
|
||||
const calculateLogScale = (values: number[]) => {
|
||||
const logValues = values.map((v) => {
|
||||
return Math.log10(v)
|
||||
})
|
||||
const minValue = Math.min(...logValues)
|
||||
const maxValue = Math.max(...logValues)
|
||||
const range = [Math.floor(minValue), Math.ceil(maxValue)]
|
||||
const tickvals = Array.from(
|
||||
{ length: Math.ceil(maxValue) - Math.floor(minValue) + 1 },
|
||||
(_, i) => i + Math.floor(minValue)
|
||||
)
|
||||
const ticktext = tickvals.map((x) => `${Math.pow(10, x).toPrecision(3)}`)
|
||||
return { logValues, range, tickvals, ticktext }
|
||||
}
|
||||
|
||||
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 = searchSpace.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") {
|
||||
// categorical
|
||||
const vocabArr: string[] = s.distribution.choices.map(
|
||||
(c) => c?.toString() ?? "null"
|
||||
)
|
||||
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,
|
||||
}
|
||||
} else if (s.distribution.log) {
|
||||
// numerical and log
|
||||
const { logValues, range, tickvals, ticktext } =
|
||||
calculateLogScale(values)
|
||||
return {
|
||||
label: breakLabelIfTooLong(s.name),
|
||||
values: logValues,
|
||||
range,
|
||||
tickvals,
|
||||
ticktext,
|
||||
}
|
||||
} else {
|
||||
// numerical and linear
|
||||
return {
|
||||
label: breakLabelIfTooLong(s.name),
|
||||
values: values,
|
||||
range: [s.distribution.low, s.distribution.high],
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
if (dimensions.length === 0) {
|
||||
console.log("Must not reach here.")
|
||||
return plotly.react(plotDomId, [], layout)
|
||||
}
|
||||
let reversescale = false
|
||||
if (
|
||||
targets[0].kind === "objective" &&
|
||||
(targets[0].getObjectiveId() as number) < study.directions.length &&
|
||||
study.directions[targets[0].getObjectiveId() as number] === "maximize"
|
||||
) {
|
||||
reversescale = true
|
||||
}
|
||||
const plotData: Partial<plotly.PlotData>[] = [
|
||||
{
|
||||
type: "parcoords",
|
||||
dimensions: dimensions,
|
||||
labelangle: 30,
|
||||
labelside: "bottom",
|
||||
line: {
|
||||
color: dimensions[0]["values"],
|
||||
// @ts-ignore
|
||||
colorscale: "Blues",
|
||||
colorbar: {
|
||||
title: targets[0].toLabel(study.objective_names),
|
||||
},
|
||||
showscale: true,
|
||||
reversescale: reversescale,
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
return plotly.react(plotDomId, plotData, layout)
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ const GraphParetoFrontFrontend: FC<{
|
||||
const navigate = useNavigate()
|
||||
const [objectiveXId, setObjectiveXId] = useState<number>(0)
|
||||
const [objectiveYId, setObjectiveYId] = useState<number>(1)
|
||||
const objectiveNames: string[] = study?.objective_names || []
|
||||
const metricNames: string[] = study?.metric_names || []
|
||||
|
||||
const handleObjectiveXChange = (event: SelectChangeEvent<number>) => {
|
||||
setObjectiveXId(event.target.value as number)
|
||||
@@ -130,8 +130,8 @@ const GraphParetoFrontFrontend: FC<{
|
||||
<Select value={objectiveXId} onChange={handleObjectiveXChange}>
|
||||
{study.directions.map((d, i) => (
|
||||
<MenuItem value={i} key={i}>
|
||||
{objectiveNames.length === study?.directions.length
|
||||
? objectiveNames[i]
|
||||
{metricNames.length === study?.directions.length
|
||||
? metricNames[i]
|
||||
: `${i}`}
|
||||
</MenuItem>
|
||||
))}
|
||||
@@ -142,8 +142,8 @@ const GraphParetoFrontFrontend: FC<{
|
||||
<Select value={objectiveYId} onChange={handleObjectiveYChange}>
|
||||
{study.directions.map((d, i) => (
|
||||
<MenuItem value={i} key={i}>
|
||||
{objectiveNames.length === study?.directions.length
|
||||
? objectiveNames[i]
|
||||
{metricNames.length === study?.directions.length
|
||||
? metricNames[i]
|
||||
: `${i}`}
|
||||
</MenuItem>
|
||||
))}
|
||||
|
||||
@@ -91,7 +91,7 @@ const GraphRankFrontend: FC<{
|
||||
const searchSpace = useMergedUnionSearchSpace(study?.union_search_space)
|
||||
const [xParam, setXParam] = useState<SearchSpaceItem | null>(null)
|
||||
const [yParam, setYParam] = useState<SearchSpaceItem | null>(null)
|
||||
const objectiveNames: string[] = study?.objective_names || []
|
||||
const metricNames: string[] = study?.metric_names || []
|
||||
|
||||
if (xParam === null && searchSpace.length > 0) {
|
||||
setXParam(searchSpace[0])
|
||||
@@ -150,8 +150,8 @@ const GraphRankFrontend: FC<{
|
||||
<Select value={objectiveId} onChange={handleObjectiveChange}>
|
||||
{study.directions.map((d, i) => (
|
||||
<MenuItem value={i} key={i}>
|
||||
{objectiveNames.length === study?.directions.length
|
||||
? objectiveNames[i]
|
||||
{metricNames.length === study?.directions.length
|
||||
? metricNames[i]
|
||||
: `${i}`}
|
||||
</MenuItem>
|
||||
))}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useTheme } from "@mui/material"
|
||||
import {
|
||||
GraphContainer,
|
||||
PlotSlice,
|
||||
@@ -8,7 +9,7 @@ import React, { FC, useEffect } from "react"
|
||||
import { StudyDetail } from "ts/types/optuna"
|
||||
import { PlotType } from "../apiClient"
|
||||
import { usePlot } from "../hooks/usePlot"
|
||||
import { useBackendRender } from "../state"
|
||||
import { useBackendRender, usePlotlyColorTheme } from "../state"
|
||||
|
||||
export const GraphSlice: FC<{
|
||||
study: StudyDetail | null
|
||||
@@ -16,7 +17,9 @@ export const GraphSlice: FC<{
|
||||
if (useBackendRender()) {
|
||||
return <GraphSliceBackend study={study} />
|
||||
} else {
|
||||
return <PlotSlice study={study} />
|
||||
const theme = useTheme()
|
||||
const colorTheme = usePlotlyColorTheme(theme.palette.mode)
|
||||
return <PlotSlice study={study} colorTheme={colorTheme} />
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -165,7 +165,7 @@ const CandidateTrial: FC<{
|
||||
trial={trial}
|
||||
isBestTrial={() => false}
|
||||
directions={[]}
|
||||
objectiveNames={[]}
|
||||
metricNames={[]}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -629,7 +629,7 @@ export const PreferentialTrials: FC<{ studyDetail: StudyDetail | null }> = ({
|
||||
?.state === "Complete" ?? false
|
||||
}
|
||||
directions={[]}
|
||||
objectiveNames={[]}
|
||||
metricNames={[]}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -35,10 +35,10 @@ type WidgetState = {
|
||||
|
||||
export const TrialFormWidgets: FC<{
|
||||
trial: Trial
|
||||
objectiveNames: string[]
|
||||
metricNames: string[]
|
||||
directions: Optuna.StudyDirection[]
|
||||
formWidgets?: FormWidgets
|
||||
}> = ({ trial, objectiveNames, directions, formWidgets }) => {
|
||||
}> = ({ trial, metricNames, directions, formWidgets }) => {
|
||||
if (
|
||||
formWidgets === undefined ||
|
||||
trial.state === "Pruned" ||
|
||||
@@ -56,8 +56,8 @@ export const TrialFormWidgets: FC<{
|
||||
: "Set Objective Value Form"
|
||||
const widgetNames = formWidgets.widgets.map((widget, i) => {
|
||||
if (formWidgets.output_type === "objective") {
|
||||
if (objectiveNames.at(i) !== undefined) {
|
||||
return objectiveNames[i]
|
||||
if (metricNames.at(i) !== undefined) {
|
||||
return metricNames[i]
|
||||
}
|
||||
return directions.length === 1 ? "Objective" : `Objective ${i}`
|
||||
} else if (formWidgets.output_type === "user_attr") {
|
||||
|
||||
@@ -126,9 +126,9 @@ export const TrialListDetail: FC<{
|
||||
trial: Trial
|
||||
isBestTrial: (trialId: number) => boolean
|
||||
directions: Optuna.StudyDirection[]
|
||||
objectiveNames: string[]
|
||||
metricNames: string[]
|
||||
formWidgets?: FormWidgets
|
||||
}> = ({ trial, isBestTrial, directions, objectiveNames, formWidgets }) => {
|
||||
}> = ({ trial, isBestTrial, directions, metricNames, formWidgets }) => {
|
||||
const theme = useTheme()
|
||||
const action = actionCreator()
|
||||
const artifactEnabled = useRecoilValue<boolean>(artifactIsAvailable)
|
||||
@@ -290,7 +290,7 @@ export const TrialListDetail: FC<{
|
||||
<TrialFormWidgets
|
||||
trial={trial}
|
||||
directions={directions}
|
||||
objectiveNames={objectiveNames}
|
||||
metricNames={metricNames}
|
||||
formWidgets={formWidgets}
|
||||
/>
|
||||
<Box
|
||||
@@ -561,7 +561,7 @@ export const TrialList: FC<{ studyDetail: StudyDetail | null }> = ({
|
||||
trial={t}
|
||||
isBestTrial={isBestTrial}
|
||||
directions={studyDetail?.directions || []}
|
||||
objectiveNames={studyDetail?.objective_names || []}
|
||||
metricNames={studyDetail?.metric_names || []}
|
||||
formWidgets={studyDetail?.form_widgets}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
import DownloadIcon from "@mui/icons-material/Download"
|
||||
import LinkIcon from "@mui/icons-material/Link"
|
||||
import { Button, IconButton, useTheme } from "@mui/material"
|
||||
import React, { FC } from "react"
|
||||
|
||||
import { DataGrid } from "@optuna/react"
|
||||
|
||||
import { Link } from "react-router-dom"
|
||||
import { StudyDetail, Trial } from "ts/types/optuna"
|
||||
|
||||
import {
|
||||
ColumnDef,
|
||||
FilterFn,
|
||||
Row,
|
||||
createColumnHelper,
|
||||
} from "@tanstack/react-table"
|
||||
import { useConstants } from "../constantsProvider"
|
||||
|
||||
const multiValueFilter: FilterFn<Trial> = <D extends object>(
|
||||
row: Row<D>,
|
||||
columnId: string,
|
||||
filterValue: string[]
|
||||
) => {
|
||||
const rowValue = row.getValue(columnId) as string
|
||||
return !filterValue.includes(rowValue)
|
||||
}
|
||||
|
||||
export const TrialTable: FC<{
|
||||
studyDetail: StudyDetail | null
|
||||
}> = ({ studyDetail }) => {
|
||||
const { url_prefix } = useConstants()
|
||||
|
||||
const theme = useTheme()
|
||||
const trials: Trial[] = studyDetail !== null ? studyDetail.trials : []
|
||||
const metricNames: string[] = studyDetail?.metric_names || []
|
||||
|
||||
const columnHelper = createColumnHelper<Trial>()
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const columns: ColumnDef<Trial, any>[] = [
|
||||
columnHelper.accessor("number", {
|
||||
header: "Number",
|
||||
enableColumnFilter: false,
|
||||
}),
|
||||
columnHelper.accessor("state", {
|
||||
header: "State",
|
||||
enableSorting: false,
|
||||
enableColumnFilter: true,
|
||||
filterFn: multiValueFilter,
|
||||
}),
|
||||
]
|
||||
if (studyDetail === null || studyDetail.directions.length === 1) {
|
||||
columns.push(
|
||||
columnHelper.accessor("values", {
|
||||
header: "Value",
|
||||
enableSorting: true,
|
||||
enableColumnFilter: false,
|
||||
sortUndefined: "last",
|
||||
})
|
||||
)
|
||||
} else {
|
||||
columns.push(
|
||||
...studyDetail.directions.map((s, objectiveId) =>
|
||||
columnHelper.accessor((row) => row["values"]?.[objectiveId], {
|
||||
id: `values_${objectiveId}`,
|
||||
header:
|
||||
metricNames.length === studyDetail?.directions.length
|
||||
? metricNames[objectiveId]
|
||||
: `Objective ${objectiveId}`,
|
||||
enableSorting: true,
|
||||
enableColumnFilter: false,
|
||||
sortUndefined: "last",
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
const isDynamicSpace =
|
||||
studyDetail?.union_search_space.length !==
|
||||
studyDetail?.intersection_search_space.length
|
||||
studyDetail?.union_search_space.forEach((s) => {
|
||||
const sortable = s.distribution.type !== "CategoricalDistribution"
|
||||
const filterChoices: (string | null)[] | undefined =
|
||||
s.distribution.type === "CategoricalDistribution"
|
||||
? s.distribution.choices.map((c) => c?.toString() ?? "null")
|
||||
: undefined
|
||||
const hasMissingValue = trials.some(
|
||||
(t) => !t.params.some((p) => p.name === s.name)
|
||||
)
|
||||
if (filterChoices !== undefined && isDynamicSpace && hasMissingValue) {
|
||||
filterChoices.push(null)
|
||||
}
|
||||
columns.push(
|
||||
columnHelper.accessor(
|
||||
(row) =>
|
||||
row["params"].find((p) => p.name === s.name)?.param_external_value ||
|
||||
null,
|
||||
{
|
||||
id: `params_${s.name}`,
|
||||
header: `Param ${s.name}`,
|
||||
enableSorting: sortable,
|
||||
sortUndefined: "last",
|
||||
enableColumnFilter: filterChoices !== undefined,
|
||||
filterFn: multiValueFilter,
|
||||
}
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
studyDetail?.union_user_attrs.forEach((attr_spec) => {
|
||||
columns.push(
|
||||
columnHelper.accessor(
|
||||
(row) =>
|
||||
row["user_attrs"].find((a) => a.key === attr_spec.key)?.value || null,
|
||||
{
|
||||
id: `user_attrs_${attr_spec.key}`,
|
||||
header: `UserAttribute ${attr_spec.key}`,
|
||||
enableSorting: attr_spec.sortable,
|
||||
enableColumnFilter: false,
|
||||
sortUndefined: "last",
|
||||
}
|
||||
)
|
||||
)
|
||||
})
|
||||
columns.push(
|
||||
columnHelper.accessor((row) => row, {
|
||||
header: "Detail",
|
||||
cell: (info) => (
|
||||
<IconButton
|
||||
component={Link}
|
||||
to={
|
||||
url_prefix +
|
||||
`/studies/${info.getValue().study_id}/trials?numbers=${
|
||||
info.getValue().number
|
||||
}`
|
||||
}
|
||||
color="inherit"
|
||||
title="Go to the trial's detail page"
|
||||
size="small"
|
||||
>
|
||||
<LinkIcon />
|
||||
</IconButton>
|
||||
),
|
||||
enableSorting: false,
|
||||
enableColumnFilter: false,
|
||||
})
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<DataGrid data={trials} columns={columns} />
|
||||
<Button
|
||||
variant="outlined"
|
||||
startIcon={<DownloadIcon />}
|
||||
download
|
||||
href={`/csv/${studyDetail?.id}`}
|
||||
sx={{ marginRight: theme.spacing(2), minWidth: "120px" }}
|
||||
>
|
||||
Download CSV File
|
||||
</Button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -155,7 +155,7 @@ export type StudyDetail = {
|
||||
has_intermediate_values: boolean
|
||||
note: Note
|
||||
is_preferential: boolean
|
||||
objective_names?: string[]
|
||||
metric_names?: string[]
|
||||
form_widgets?: FormWidgets
|
||||
feedback_component_type: FeedbackComponentType
|
||||
preferences?: [number, number][]
|
||||
|
||||
Generated
+9
-8
@@ -62,15 +62,16 @@
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@chromatic-com/storybook": "^1.6.1",
|
||||
"@optuna/storage": "file:../storage",
|
||||
"@optuna/types": "file:../types",
|
||||
"@storybook/addon-essentials": "^8.0.4",
|
||||
"@storybook/addon-interactions": "^8.0.4",
|
||||
"@storybook/addon-links": "^8.0.4",
|
||||
"@storybook/blocks": "^8.0.4",
|
||||
"@storybook/react": "^8.0.4",
|
||||
"@storybook/react-vite": "^8.0.4",
|
||||
"@storybook/test": "^8.0.4",
|
||||
"@storybook/addon-essentials": "^8.2.9",
|
||||
"@storybook/addon-interactions": "^8.2.9",
|
||||
"@storybook/addon-links": "^8.2.9",
|
||||
"@storybook/blocks": "^8.2.9",
|
||||
"@storybook/react": "^8.2.9",
|
||||
"@storybook/react-vite": "^8.2.9",
|
||||
"@storybook/test": "^8.2.9",
|
||||
"@testing-library/jest-dom": "^6.4.2",
|
||||
"@testing-library/react": "^14.2.2",
|
||||
"@types/plotly.js-dist-min": "^2.3.4",
|
||||
@@ -78,7 +79,7 @@
|
||||
"@types/react-dom": "^18.2.19",
|
||||
"@vitejs/plugin-react-swc": "^3.5.0",
|
||||
"jsdom": "^24.0.0",
|
||||
"storybook": "^8.0.4",
|
||||
"storybook": "^8.2.9",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^5.1.0",
|
||||
"vitest": "^1.4.0"
|
||||
|
||||
@@ -2,18 +2,19 @@ import type { StorybookConfig } from "@storybook/react-vite"
|
||||
|
||||
const config: StorybookConfig = {
|
||||
stories: ["../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
|
||||
|
||||
addons: [
|
||||
"@storybook/addon-links",
|
||||
"@storybook/addon-essentials",
|
||||
"@storybook/addon-interactions",
|
||||
"@chromatic-com/storybook",
|
||||
],
|
||||
|
||||
framework: {
|
||||
name: "@storybook/react-vite",
|
||||
options: {},
|
||||
},
|
||||
docs: {
|
||||
autodocs: "tag",
|
||||
},
|
||||
|
||||
async viteFinal(config) {
|
||||
const { mergeConfig } = await import("vite")
|
||||
return mergeConfig(config, {
|
||||
@@ -25,5 +26,9 @@ const config: StorybookConfig = {
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
typescript: {
|
||||
reactDocgen: "react-docgen-typescript",
|
||||
},
|
||||
}
|
||||
export default config
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import type { Preview } from "@storybook/react"
|
||||
|
||||
const preview: Preview = {
|
||||
parameters: {
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/i,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default preview
|
||||
@@ -0,0 +1,32 @@
|
||||
import {
|
||||
Controls,
|
||||
Description,
|
||||
Primary,
|
||||
Subtitle,
|
||||
Title,
|
||||
} from "@storybook/blocks"
|
||||
import type { Preview } from "@storybook/react"
|
||||
|
||||
const preview: Preview = {
|
||||
parameters: {
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/i,
|
||||
},
|
||||
},
|
||||
docs: {
|
||||
page: () => (
|
||||
<>
|
||||
<Title />
|
||||
<Subtitle />
|
||||
<Description />
|
||||
<Primary />
|
||||
<Controls />
|
||||
</>
|
||||
),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default preview
|
||||
Generated
+945
-3945
File diff suppressed because it is too large
Load Diff
@@ -38,15 +38,16 @@
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@optuna/types": "file:../types",
|
||||
"@chromatic-com/storybook": "^1.6.1",
|
||||
"@optuna/storage": "file:../storage",
|
||||
"@storybook/addon-essentials": "^8.0.4",
|
||||
"@storybook/addon-interactions": "^8.0.4",
|
||||
"@storybook/addon-links": "^8.0.4",
|
||||
"@storybook/blocks": "^8.0.4",
|
||||
"@storybook/react": "^8.0.4",
|
||||
"@storybook/react-vite": "^8.0.4",
|
||||
"@storybook/test": "^8.0.4",
|
||||
"@optuna/types": "file:../types",
|
||||
"@storybook/addon-essentials": "^8.2.9",
|
||||
"@storybook/addon-interactions": "^8.2.9",
|
||||
"@storybook/addon-links": "^8.2.9",
|
||||
"@storybook/blocks": "^8.2.9",
|
||||
"@storybook/react": "^8.2.9",
|
||||
"@storybook/react-vite": "^8.2.9",
|
||||
"@storybook/test": "^8.2.9",
|
||||
"@testing-library/jest-dom": "^6.4.2",
|
||||
"@testing-library/react": "^14.2.2",
|
||||
"@types/plotly.js-dist-min": "^2.3.4",
|
||||
@@ -54,7 +55,7 @@
|
||||
"@types/react-dom": "^18.2.19",
|
||||
"@vitejs/plugin-react-swc": "^3.5.0",
|
||||
"jsdom": "^24.0.0",
|
||||
"storybook": "^8.0.4",
|
||||
"storybook": "^8.2.9",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^5.1.0",
|
||||
"vitest": "^1.4.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as Optuna from "@optuna/types"
|
||||
import { useEffect, useState } from "react"
|
||||
import { loadStorageFromFile } from "./utils/loadStorageFromFile"
|
||||
import { loadStorageFromFile } from "../test/loadStorageFromFile"
|
||||
|
||||
const fetchMockStudies = async () => {
|
||||
const filePath = "sample_db.sqlite3"
|
||||
|
||||
@@ -2,19 +2,22 @@ import { CssBaseline, ThemeProvider } from "@mui/material"
|
||||
import { Meta, StoryObj } from "@storybook/react"
|
||||
import React from "react"
|
||||
import { useMockStudy } from "../MockStudies"
|
||||
import { darkTheme } from "../styles/darkTheme"
|
||||
import { lightTheme } from "../styles/lightTheme"
|
||||
import { PlotEdf } from "./PlotEdf"
|
||||
|
||||
const meta: Meta<typeof PlotEdf> = {
|
||||
component: PlotEdf,
|
||||
title: "PlotEdf",
|
||||
title: "Plot/EDF",
|
||||
tags: ["autodocs"],
|
||||
decorators: [
|
||||
(Story, storyContext) => {
|
||||
const { study } = useMockStudy(storyContext.parameters?.studyId)
|
||||
const studyId = storyContext.parameters?.studyId
|
||||
const { study } = useMockStudy(studyId)
|
||||
if (!study) return <p>loading...</p>
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<ThemeProvider theme={storyContext.parameters?.theme}>
|
||||
<CssBaseline />
|
||||
<Story
|
||||
args={{
|
||||
@@ -29,10 +32,33 @@ const meta: Meta<typeof PlotEdf> = {
|
||||
}
|
||||
|
||||
export default meta
|
||||
|
||||
type Story = StoryObj<typeof PlotEdf>
|
||||
|
||||
export const MockStudyExample1: Story = {
|
||||
export const LightTheme: Story = {
|
||||
parameters: {
|
||||
studyId: 1,
|
||||
theme: lightTheme,
|
||||
},
|
||||
}
|
||||
|
||||
export const DarkTheme: Story = {
|
||||
parameters: {
|
||||
studyId: 1,
|
||||
theme: darkTheme,
|
||||
},
|
||||
}
|
||||
|
||||
// TODO(c-bata): Add a story for the case where two studies are compared.
|
||||
// export const CompareStudy: Story = {
|
||||
// parameters: {
|
||||
// ...
|
||||
// },
|
||||
// }
|
||||
|
||||
// TODO(c-bata): Add a story for multi objective study.
|
||||
// export const MultiObjective: Story = {
|
||||
// parameters: {
|
||||
// ...
|
||||
// },
|
||||
// }
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
import { CssBaseline, ThemeProvider } from "@mui/material"
|
||||
import { Meta, StoryObj } from "@storybook/react"
|
||||
import React from "react"
|
||||
import { useMockStudy } from "../MockStudies"
|
||||
import { darkTheme } from "../styles/darkTheme"
|
||||
import { PlotEdf } from "./PlotEdf"
|
||||
|
||||
const meta: Meta<typeof PlotEdf> = {
|
||||
component: PlotEdf,
|
||||
title: "PlotEdfDark",
|
||||
tags: ["autodocs"],
|
||||
decorators: [
|
||||
(Story, storyContext) => {
|
||||
const { study } = useMockStudy(storyContext.parameters?.studyId)
|
||||
if (!study) return <p>loading...</p>
|
||||
return (
|
||||
<ThemeProvider theme={darkTheme}>
|
||||
<CssBaseline />
|
||||
<Story
|
||||
args={{
|
||||
studies: [study],
|
||||
objectiveId: 0,
|
||||
}}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
)
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
backgrounds: { default: "dark" },
|
||||
},
|
||||
}
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof PlotEdf>
|
||||
|
||||
export const MockStudyExample1: Story = {
|
||||
parameters: {
|
||||
studyId: 1,
|
||||
},
|
||||
}
|
||||
@@ -2,19 +2,20 @@ import { CssBaseline, ThemeProvider } from "@mui/material"
|
||||
import { Meta, StoryObj } from "@storybook/react"
|
||||
import React from "react"
|
||||
import { useMockStudy } from "../MockStudies"
|
||||
import { darkTheme } from "../styles/darkTheme"
|
||||
import { lightTheme } from "../styles/lightTheme"
|
||||
import { PlotHistory } from "./PlotHistory"
|
||||
|
||||
const meta: Meta<typeof PlotHistory> = {
|
||||
component: PlotHistory,
|
||||
title: "PlotHistory",
|
||||
title: "Plot/History",
|
||||
tags: ["autodocs"],
|
||||
decorators: [
|
||||
(Story, storyContext) => {
|
||||
const { study } = useMockStudy(storyContext.parameters?.studyId)
|
||||
if (!study) return <p>loading...</p>
|
||||
return (
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<ThemeProvider theme={storyContext.parameters?.theme}>
|
||||
<CssBaseline />
|
||||
<Story
|
||||
args={{
|
||||
@@ -30,8 +31,30 @@ const meta: Meta<typeof PlotHistory> = {
|
||||
export default meta
|
||||
type Story = StoryObj<typeof PlotHistory>
|
||||
|
||||
export const MockStudyExample1: Story = {
|
||||
export const LightTheme: Story = {
|
||||
parameters: {
|
||||
studyId: 1,
|
||||
theme: lightTheme,
|
||||
},
|
||||
}
|
||||
|
||||
export const DarkTheme: Story = {
|
||||
parameters: {
|
||||
studyId: 1,
|
||||
theme: darkTheme,
|
||||
},
|
||||
}
|
||||
|
||||
// TODO(c-bata): Add a story for the case where two studies are compared.
|
||||
// export const CompareStudy: Story = {
|
||||
// parameters: {
|
||||
// ...
|
||||
// },
|
||||
// }
|
||||
|
||||
// TODO(c-bata): Add a story for multi objective study.
|
||||
// export const MultiObjective: Story = {
|
||||
// parameters: {
|
||||
// ...
|
||||
// },
|
||||
// }
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
import { CssBaseline, ThemeProvider } from "@mui/material"
|
||||
import { Meta, StoryObj } from "@storybook/react"
|
||||
import React from "react"
|
||||
import { useMockStudy } from "../MockStudies"
|
||||
import { darkTheme } from "../styles/darkTheme"
|
||||
import { PlotHistory } from "./PlotHistory"
|
||||
|
||||
const meta: Meta<typeof PlotHistory> = {
|
||||
component: PlotHistory,
|
||||
title: "PlotHistoryDark",
|
||||
tags: ["autodocs"],
|
||||
decorators: [
|
||||
(Story, storyContext) => {
|
||||
const { study } = useMockStudy(storyContext.parameters?.studyId)
|
||||
if (!study) return <p>loading...</p>
|
||||
return (
|
||||
<ThemeProvider theme={darkTheme}>
|
||||
<CssBaseline />
|
||||
<Story
|
||||
args={{
|
||||
study,
|
||||
}}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
)
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
backgrounds: { default: "dark" },
|
||||
},
|
||||
}
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof PlotHistory>
|
||||
|
||||
export const MockStudyExample1: Story = {
|
||||
parameters: {
|
||||
studyId: 1,
|
||||
},
|
||||
}
|
||||
@@ -2,12 +2,13 @@ import { CssBaseline, ThemeProvider } from "@mui/material"
|
||||
import { Meta, StoryObj } from "@storybook/react"
|
||||
import React from "react"
|
||||
import { useMockStudy } from "../MockStudies"
|
||||
import { darkTheme } from "../styles/darkTheme"
|
||||
import { lightTheme } from "../styles/lightTheme"
|
||||
import { PlotImportance } from "./PlotImportance"
|
||||
|
||||
const meta: Meta<typeof PlotImportance> = {
|
||||
component: PlotImportance,
|
||||
title: "PlotImportance",
|
||||
title: "Plot/Importance",
|
||||
tags: ["autodocs"],
|
||||
decorators: [
|
||||
(Story, storyContext) => {
|
||||
@@ -16,7 +17,7 @@ const meta: Meta<typeof PlotImportance> = {
|
||||
)
|
||||
if (!study) return <p>loading...</p>
|
||||
return (
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<ThemeProvider theme={storyContext.parameters?.theme}>
|
||||
<CssBaseline />
|
||||
<Story
|
||||
args={{
|
||||
@@ -33,8 +34,23 @@ const meta: Meta<typeof PlotImportance> = {
|
||||
export default meta
|
||||
type Story = StoryObj<typeof PlotImportance>
|
||||
|
||||
export const MockStudyExample1: Story = {
|
||||
export const LightTheme: Story = {
|
||||
parameters: {
|
||||
studyId: 1,
|
||||
theme: lightTheme,
|
||||
},
|
||||
}
|
||||
|
||||
export const DarkTheme: Story = {
|
||||
parameters: {
|
||||
studyId: 1,
|
||||
theme: darkTheme,
|
||||
},
|
||||
}
|
||||
|
||||
// TODO(c-bata): Add a story for multi objective study.
|
||||
// export const MultiObjective: Story = {
|
||||
// parameters: {
|
||||
// ...
|
||||
// },
|
||||
// }
|
||||
|
||||
@@ -10,17 +10,19 @@ export const PlotImportance: FC<{
|
||||
study: Optuna.Study | null
|
||||
importance?: Optuna.ParamImportance[][]
|
||||
graphHeight?: string
|
||||
}> = ({ study = null, importance, graphHeight = "450px" }) => {
|
||||
colorTheme?: Partial<Plotly.Template>
|
||||
}> = ({ study = null, importance, graphHeight = "450px", colorTheme }) => {
|
||||
const theme = useTheme()
|
||||
const colorThemeUsed =
|
||||
colorTheme ?? (theme.palette.mode === "dark" ? plotlyDarkTemplate : {})
|
||||
const objectiveNames: string[] = study
|
||||
? study.directions.map((_d, i) => `Objective ${i}`)
|
||||
: []
|
||||
|
||||
useEffect(() => {
|
||||
if (study !== null && importance !== undefined && importance.length > 0) {
|
||||
plotParamImportancesBeta(importance, objectiveNames, theme.palette.mode)
|
||||
plotParamImportancesBeta(importance, objectiveNames, colorThemeUsed)
|
||||
}
|
||||
}, [study, objectiveNames, importance, theme.palette.mode])
|
||||
}, [study, objectiveNames, importance, colorThemeUsed])
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -38,7 +40,7 @@ export const PlotImportance: FC<{
|
||||
const plotParamImportancesBeta = (
|
||||
importances: Optuna.ParamImportance[][],
|
||||
objectiveNames: string[],
|
||||
mode: string
|
||||
colorTheme: Partial<Plotly.Template>
|
||||
) => {
|
||||
const layout: Partial<plotly.Layout> = {
|
||||
xaxis: {
|
||||
@@ -58,7 +60,7 @@ const plotParamImportancesBeta = (
|
||||
bargap: 0.15,
|
||||
bargroupgap: 0.1,
|
||||
uirevision: "true",
|
||||
template: mode === "dark" ? plotlyDarkTemplate : {},
|
||||
template: colorTheme,
|
||||
legend: {
|
||||
x: 1.0,
|
||||
y: 0.95,
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import { CssBaseline, ThemeProvider } from "@mui/material"
|
||||
import { Meta, StoryObj } from "@storybook/react"
|
||||
import React from "react"
|
||||
import { useMockStudy } from "../MockStudies"
|
||||
import { darkTheme } from "../styles/darkTheme"
|
||||
import { PlotImportance } from "./PlotImportance"
|
||||
|
||||
const meta: Meta<typeof PlotImportance> = {
|
||||
component: PlotImportance,
|
||||
title: "PlotImportanceDark",
|
||||
tags: ["autodocs"],
|
||||
decorators: [
|
||||
(Story, storyContext) => {
|
||||
const { study, importance } = useMockStudy(
|
||||
storyContext.parameters?.studyId
|
||||
)
|
||||
if (!study) return <p>loading...</p>
|
||||
return (
|
||||
<ThemeProvider theme={darkTheme}>
|
||||
<CssBaseline />
|
||||
<Story
|
||||
args={{
|
||||
study,
|
||||
importance,
|
||||
}}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
)
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
backgrounds: { default: "dark" },
|
||||
},
|
||||
}
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof PlotImportance>
|
||||
|
||||
export const MockStudyExample1: Story = {
|
||||
parameters: {
|
||||
studyId: 1,
|
||||
},
|
||||
}
|
||||
@@ -2,19 +2,20 @@ import { CssBaseline, ThemeProvider } from "@mui/material"
|
||||
import { Meta, StoryObj } from "@storybook/react"
|
||||
import React from "react"
|
||||
import { useMockStudy } from "../MockStudies"
|
||||
import { darkTheme } from "../styles/darkTheme"
|
||||
import { lightTheme } from "../styles/lightTheme"
|
||||
import { PlotIntermediateValues } from "./PlotIntermediateValues"
|
||||
|
||||
const meta: Meta<typeof PlotIntermediateValues> = {
|
||||
component: PlotIntermediateValues,
|
||||
title: "PlotIntermediateValues",
|
||||
title: "Plot/IntermediateValues",
|
||||
tags: ["autodocs"],
|
||||
decorators: [
|
||||
(Story, storyContext) => {
|
||||
const { study } = useMockStudy(storyContext.parameters?.studyId)
|
||||
if (!study) return <p>loading...</p>
|
||||
return (
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<ThemeProvider theme={storyContext.parameters?.theme}>
|
||||
<CssBaseline />
|
||||
<Story
|
||||
args={{
|
||||
@@ -32,8 +33,16 @@ const meta: Meta<typeof PlotIntermediateValues> = {
|
||||
export default meta
|
||||
type Story = StoryObj<typeof PlotIntermediateValues>
|
||||
|
||||
export const MockStudyExample1: Story = {
|
||||
export const LightTheme: Story = {
|
||||
parameters: {
|
||||
studyId: 1,
|
||||
theme: lightTheme,
|
||||
},
|
||||
}
|
||||
|
||||
export const DarkTheme: Story = {
|
||||
parameters: {
|
||||
studyId: 1,
|
||||
theme: darkTheme,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -10,18 +10,21 @@ export const PlotIntermediateValues: FC<{
|
||||
trials: Optuna.Trial[]
|
||||
includePruned: boolean
|
||||
logScale: boolean
|
||||
}> = ({ trials, includePruned, logScale }) => {
|
||||
colorTheme?: Partial<Plotly.Template>
|
||||
}> = ({ trials, includePruned, logScale, colorTheme }) => {
|
||||
const theme = useTheme()
|
||||
const colorThemeUsed =
|
||||
colorTheme ?? (theme.palette.mode === "dark" ? plotlyDarkTemplate : {})
|
||||
|
||||
useEffect(() => {
|
||||
plotIntermediateValue(
|
||||
trials,
|
||||
theme.palette.mode,
|
||||
colorThemeUsed,
|
||||
false,
|
||||
!includePruned,
|
||||
logScale
|
||||
)
|
||||
}, [trials, theme.palette.mode, includePruned, logScale])
|
||||
}, [trials, colorThemeUsed, includePruned, logScale])
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -38,7 +41,7 @@ export const PlotIntermediateValues: FC<{
|
||||
|
||||
const plotIntermediateValue = (
|
||||
trials: Optuna.Trial[],
|
||||
mode: string,
|
||||
colorTheme: Partial<Plotly.Template>,
|
||||
filterCompleteTrial: boolean,
|
||||
filterPrunedTrial: boolean,
|
||||
logScale: boolean
|
||||
@@ -63,7 +66,7 @@ const plotIntermediateValue = (
|
||||
type: "linear",
|
||||
},
|
||||
uirevision: "true",
|
||||
template: mode === "dark" ? plotlyDarkTemplate : {},
|
||||
template: colorTheme,
|
||||
legend: {
|
||||
x: 1.0,
|
||||
y: 0.95,
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
import { CssBaseline, ThemeProvider } from "@mui/material"
|
||||
import { Meta, StoryObj } from "@storybook/react"
|
||||
import React from "react"
|
||||
import { useMockStudy } from "../MockStudies"
|
||||
import { darkTheme } from "../styles/darkTheme"
|
||||
import { PlotIntermediateValues } from "./PlotIntermediateValues"
|
||||
|
||||
const meta: Meta<typeof PlotIntermediateValues> = {
|
||||
component: PlotIntermediateValues,
|
||||
title: "PlotIntermediateValuesDark",
|
||||
tags: ["autodocs"],
|
||||
decorators: [
|
||||
(Story, storyContext) => {
|
||||
const { study } = useMockStudy(storyContext.parameters?.studyId)
|
||||
if (!study) return <p>loading...</p>
|
||||
return (
|
||||
<ThemeProvider theme={darkTheme}>
|
||||
<CssBaseline />
|
||||
<Story
|
||||
args={{
|
||||
trials: study.trials,
|
||||
includePruned: false,
|
||||
logScale: false,
|
||||
}}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
)
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
backgrounds: { default: "dark" },
|
||||
},
|
||||
}
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof PlotIntermediateValues>
|
||||
|
||||
export const MockStudyExample1: Story = {
|
||||
parameters: {
|
||||
studyId: 1,
|
||||
},
|
||||
}
|
||||
+23
-10
@@ -3,18 +3,19 @@ import { Meta, StoryObj } from "@storybook/react"
|
||||
import React from "react"
|
||||
import { useMockStudy } from "../MockStudies"
|
||||
import { darkTheme } from "../styles/darkTheme"
|
||||
import { TrialTable } from "./TrialTable"
|
||||
import { lightTheme } from "../styles/lightTheme"
|
||||
import { PlotParallelCoordinate } from "./PlotParallelCoordinate"
|
||||
|
||||
const meta: Meta<typeof TrialTable> = {
|
||||
component: TrialTable,
|
||||
title: "TrialTableDark",
|
||||
const meta: Meta<typeof PlotParallelCoordinate> = {
|
||||
component: PlotParallelCoordinate,
|
||||
title: "Plot/ParallelCoordinate",
|
||||
tags: ["autodocs"],
|
||||
decorators: [
|
||||
(Story, storyContext) => {
|
||||
const { study } = useMockStudy(storyContext.parameters?.studyId)
|
||||
if (!study) return <p>loading...</p>
|
||||
return (
|
||||
<ThemeProvider theme={darkTheme}>
|
||||
<ThemeProvider theme={storyContext.parameters?.theme}>
|
||||
<CssBaseline />
|
||||
<Story
|
||||
args={{
|
||||
@@ -25,16 +26,28 @@ const meta: Meta<typeof TrialTable> = {
|
||||
)
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
backgrounds: { default: "dark" },
|
||||
},
|
||||
}
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof TrialTable>
|
||||
type Story = StoryObj<typeof PlotParallelCoordinate>
|
||||
|
||||
export const MockStudyExample1: Story = {
|
||||
export const LightTheme: Story = {
|
||||
parameters: {
|
||||
studyId: 1,
|
||||
theme: lightTheme,
|
||||
},
|
||||
}
|
||||
|
||||
export const DarkTheme: Story = {
|
||||
parameters: {
|
||||
studyId: 1,
|
||||
theme: darkTheme,
|
||||
},
|
||||
}
|
||||
|
||||
// TODO(c-bata): Add a story for multi objective study.
|
||||
// export const MultiObjective: Story = {
|
||||
// parameters: {
|
||||
// ...
|
||||
// },
|
||||
// }
|
||||
@@ -0,0 +1,302 @@
|
||||
import {
|
||||
Checkbox,
|
||||
FormControlLabel,
|
||||
FormGroup,
|
||||
Grid,
|
||||
Typography,
|
||||
useTheme,
|
||||
} from "@mui/material"
|
||||
import * as Optuna from "@optuna/types"
|
||||
import * as plotly from "plotly.js-dist-min"
|
||||
import React, { FC, ReactNode, useEffect, useState } from "react"
|
||||
import {
|
||||
GraphContainer,
|
||||
Target,
|
||||
useFilteredTrials,
|
||||
useGraphComponentState,
|
||||
useMergedUnionSearchSpace,
|
||||
useObjectiveAndUserAttrTargets,
|
||||
useParamTargets,
|
||||
} from ".."
|
||||
import { plotlyDarkTemplate } from "./PlotlyDarkMode"
|
||||
|
||||
const plotDomId = "plot-parallel-coordinate"
|
||||
|
||||
export const PlotParallelCoordinate: FC<{
|
||||
study: Optuna.Study | null
|
||||
colorTheme?: Partial<Plotly.Template>
|
||||
}> = ({ study = null, colorTheme }) => {
|
||||
const { graphComponentState, notifyGraphDidRender } = useGraphComponentState()
|
||||
|
||||
const theme = useTheme()
|
||||
const colorThemeUsed =
|
||||
colorTheme ?? (theme.palette.mode === "dark" ? plotlyDarkTemplate : {})
|
||||
|
||||
const [targets, searchSpace, renderCheckBoxes] = useTargets(study)
|
||||
|
||||
const trials = useFilteredTrials(study, targets, false)
|
||||
useEffect(() => {
|
||||
if (study !== null && graphComponentState !== "componentWillMount") {
|
||||
// TODO(c-bata): Fix the broken E2E tests.
|
||||
// https://github.com/optuna/optuna-dashboard/pull/929#issuecomment-2296632106
|
||||
// https://github.com/optuna/optuna-dashboard/actions/runs/10451985071/job/28939493755?pr=937
|
||||
try {
|
||||
plotCoordinate(
|
||||
study,
|
||||
trials,
|
||||
targets,
|
||||
searchSpace,
|
||||
colorThemeUsed
|
||||
)?.then(notifyGraphDidRender)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
}, [
|
||||
study,
|
||||
trials,
|
||||
targets,
|
||||
searchSpace,
|
||||
colorThemeUsed,
|
||||
graphComponentState,
|
||||
notifyGraphDidRender,
|
||||
])
|
||||
|
||||
return (
|
||||
<Grid container direction="row">
|
||||
<Grid
|
||||
item
|
||||
xs={3}
|
||||
container
|
||||
direction="column"
|
||||
sx={{
|
||||
paddingRight: theme.spacing(2),
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{ margin: "1em 0", fontWeight: theme.typography.fontWeightBold }}
|
||||
>
|
||||
Parallel Coordinate
|
||||
</Typography>
|
||||
{renderCheckBoxes()}
|
||||
</Grid>
|
||||
<Grid item xs={9}>
|
||||
<GraphContainer
|
||||
plotDomId={plotDomId}
|
||||
graphComponentState={graphComponentState}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
const plotCoordinate = (
|
||||
study: Optuna.Study,
|
||||
trials: Optuna.Trial[],
|
||||
targets: Target[],
|
||||
searchSpace: Optuna.SearchSpaceItem[],
|
||||
colorTheme: Partial<Plotly.Template>
|
||||
) => {
|
||||
if (document.getElementById(plotDomId) === null) {
|
||||
return
|
||||
}
|
||||
|
||||
const layout: Partial<plotly.Layout> = {
|
||||
margin: {
|
||||
l: 70,
|
||||
t: 50,
|
||||
r: 50,
|
||||
b: 100,
|
||||
},
|
||||
template: colorTheme,
|
||||
uirevision: "true",
|
||||
}
|
||||
if (trials.length === 0 || targets.length === 0) {
|
||||
return plotly.react(plotDomId, [], layout)
|
||||
}
|
||||
|
||||
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}<br>` : c
|
||||
})
|
||||
.join("")
|
||||
}
|
||||
|
||||
const calculateLogScale = (values: number[]) => {
|
||||
const logValues = values.map((v) => {
|
||||
return Math.log10(v)
|
||||
})
|
||||
const minValue = Math.min(...logValues)
|
||||
const maxValue = Math.max(...logValues)
|
||||
const range = [Math.floor(minValue), Math.ceil(maxValue)]
|
||||
const tickvals = Array.from(
|
||||
{ length: Math.ceil(maxValue) - Math.floor(minValue) + 1 },
|
||||
(_, i) => i + Math.floor(minValue)
|
||||
)
|
||||
const ticktext = tickvals.map((x) => `${(10 ** x).toPrecision(3)}`)
|
||||
return { logValues, range, tickvals, ticktext }
|
||||
}
|
||||
|
||||
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.metric_names),
|
||||
values: values,
|
||||
range: [Math.min(...values), Math.max(...values)],
|
||||
}
|
||||
}
|
||||
const s = searchSpace.find(
|
||||
(s) => s.name === target.key
|
||||
) as Optuna.SearchSpaceItem // Must be already filtered.
|
||||
|
||||
const values: number[] = trials.map(
|
||||
(t) => target.getTargetValue(t) as number
|
||||
)
|
||||
if (s.distribution.type === "CategoricalDistribution") {
|
||||
// categorical
|
||||
const vocabArr: string[] = s.distribution.choices.map(
|
||||
(c) => c?.toString() ?? "null"
|
||||
)
|
||||
const tickvals: number[] = vocabArr.map((_, i) => i)
|
||||
return {
|
||||
label: breakLabelIfTooLong(s.name),
|
||||
values: values,
|
||||
range: [0, s.distribution.choices.length - 1],
|
||||
// @ts-ignore
|
||||
tickvals: tickvals,
|
||||
ticktext: vocabArr,
|
||||
}
|
||||
}
|
||||
if (s.distribution.log) {
|
||||
// numerical and log
|
||||
const { logValues, range, tickvals, ticktext } = calculateLogScale(values)
|
||||
return {
|
||||
label: breakLabelIfTooLong(s.name),
|
||||
values: logValues,
|
||||
range,
|
||||
tickvals,
|
||||
ticktext,
|
||||
}
|
||||
}
|
||||
// numerical and linear
|
||||
return {
|
||||
label: breakLabelIfTooLong(s.name),
|
||||
values: values,
|
||||
range: [s.distribution.low, s.distribution.high],
|
||||
}
|
||||
})
|
||||
if (dimensions.length === 0) {
|
||||
console.log("Must not reach here.")
|
||||
return plotly.react(plotDomId, [], layout)
|
||||
}
|
||||
let reversescale = false
|
||||
if (
|
||||
targets[0].kind === "objective" &&
|
||||
(targets[0].getObjectiveId() as number) < study.directions.length &&
|
||||
study.directions[targets[0].getObjectiveId() as number] === "maximize"
|
||||
) {
|
||||
reversescale = true
|
||||
}
|
||||
const plotData: Partial<plotly.PlotData>[] = [
|
||||
{
|
||||
type: "parcoords",
|
||||
dimensions: dimensions,
|
||||
labelangle: 30,
|
||||
labelside: "bottom",
|
||||
line: {
|
||||
color: dimensions[0].values,
|
||||
// @ts-ignore
|
||||
colorscale: "Blues",
|
||||
colorbar: {
|
||||
title: targets[0].toLabel(study.metric_names),
|
||||
},
|
||||
showscale: true,
|
||||
reversescale: reversescale,
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
return plotly.react(plotDomId, plotData, layout)
|
||||
}
|
||||
|
||||
const useTargets = (
|
||||
study: Optuna.Study | null
|
||||
): [Target[], Optuna.SearchSpaceItem[], () => ReactNode] => {
|
||||
const [targets1] = useObjectiveAndUserAttrTargets(study)
|
||||
const searchSpace = useMergedUnionSearchSpace(study?.union_search_space)
|
||||
const [targets2] = useParamTargets(searchSpace)
|
||||
const [checked, setChecked] = useState<boolean[]>([true])
|
||||
|
||||
const allTargets = [...targets1, ...targets2]
|
||||
useEffect(() => {
|
||||
if (allTargets.length !== checked.length) {
|
||||
setChecked(
|
||||
allTargets.map((t) => {
|
||||
if (t.kind === "user_attr") {
|
||||
return false
|
||||
}
|
||||
if (t.kind !== "params" || study === null) {
|
||||
return true
|
||||
}
|
||||
// By default, params that is not included in intersection search space should be disabled,
|
||||
// otherwise all trials are filtered.
|
||||
return (
|
||||
study.intersection_search_space.find((s) => s.name === t.key) !==
|
||||
undefined
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
}, [allTargets, study, checked.length])
|
||||
|
||||
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setChecked(
|
||||
checked.map((c, i) =>
|
||||
i.toString() === event.target.name ? event.target.checked : c
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
const renderCheckBoxes = (): ReactNode => (
|
||||
<FormGroup>
|
||||
{allTargets.map((t, i) => {
|
||||
const key = t.toLabel(study?.metric_names)
|
||||
return (
|
||||
<FormControlLabel
|
||||
key={key}
|
||||
control={
|
||||
<Checkbox
|
||||
checked={checked.length > i ? checked[i] : true}
|
||||
onChange={handleOnChange}
|
||||
name={i.toString()}
|
||||
/>
|
||||
}
|
||||
label={t.toLabel(study?.metric_names)}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</FormGroup>
|
||||
)
|
||||
|
||||
const targets = allTargets.filter((_, i) =>
|
||||
checked.length > i ? checked[i] : true
|
||||
)
|
||||
return [targets, searchSpace, renderCheckBoxes]
|
||||
}
|
||||
@@ -2,19 +2,20 @@ import { CssBaseline, ThemeProvider } from "@mui/material"
|
||||
import { Meta, StoryObj } from "@storybook/react"
|
||||
import React from "react"
|
||||
import { useMockStudy } from "../MockStudies"
|
||||
import { darkTheme } from "../styles/darkTheme"
|
||||
import { lightTheme } from "../styles/lightTheme"
|
||||
import { PlotSlice } from "./PlotSlice"
|
||||
|
||||
const meta: Meta<typeof PlotSlice> = {
|
||||
component: PlotSlice,
|
||||
title: "PlotSlice",
|
||||
title: "Plot/Slice",
|
||||
tags: ["autodocs"],
|
||||
decorators: [
|
||||
(Story, storyContext) => {
|
||||
const { study } = useMockStudy(storyContext.parameters?.studyId)
|
||||
if (!study) return <p>loading...</p>
|
||||
return (
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<ThemeProvider theme={storyContext.parameters?.theme}>
|
||||
<CssBaseline />
|
||||
<Story
|
||||
args={{
|
||||
@@ -30,8 +31,23 @@ const meta: Meta<typeof PlotSlice> = {
|
||||
export default meta
|
||||
type Story = StoryObj<typeof PlotSlice>
|
||||
|
||||
export const MockStudyExample1: Story = {
|
||||
export const LightTheme: Story = {
|
||||
parameters: {
|
||||
studyId: 1,
|
||||
theme: lightTheme,
|
||||
},
|
||||
}
|
||||
|
||||
export const DarkTheme: Story = {
|
||||
parameters: {
|
||||
studyId: 1,
|
||||
theme: darkTheme,
|
||||
},
|
||||
}
|
||||
|
||||
// TODO(c-bata): Add a story for multi objective study.
|
||||
// export const MultiObjective: Story = {
|
||||
// parameters: {
|
||||
// ...
|
||||
// },
|
||||
// }
|
||||
|
||||
@@ -34,10 +34,13 @@ const domId = "plot-slice"
|
||||
|
||||
export const PlotSlice: FC<{
|
||||
study: Optuna.Study | null
|
||||
}> = ({ study = null }) => {
|
||||
colorTheme?: Partial<Plotly.Template>
|
||||
}> = ({ study = null, colorTheme }) => {
|
||||
const { graphComponentState, notifyGraphDidRender } = useGraphComponentState()
|
||||
|
||||
const theme = useTheme()
|
||||
const colorThemeUsed =
|
||||
colorTheme ?? (theme.palette.mode === "dark" ? plotlyDarkTemplate : {})
|
||||
|
||||
const [objectiveTargets, selectedObjective, setObjectiveTarget] =
|
||||
useObjectiveAndUserAttrTargets(study)
|
||||
@@ -63,7 +66,7 @@ export const PlotSlice: FC<{
|
||||
selectedParamTarget,
|
||||
searchSpace.find((s) => s.name === selectedParamTarget?.key) || null,
|
||||
logYScale,
|
||||
theme.palette.mode
|
||||
colorThemeUsed
|
||||
)?.then(notifyGraphDidRender)
|
||||
}
|
||||
}, [
|
||||
@@ -72,7 +75,7 @@ export const PlotSlice: FC<{
|
||||
searchSpace,
|
||||
selectedParamTarget,
|
||||
logYScale,
|
||||
theme.palette.mode,
|
||||
colorThemeUsed,
|
||||
graphComponentState,
|
||||
])
|
||||
|
||||
@@ -160,7 +163,7 @@ const plotSlice = (
|
||||
selectedParamTarget: Target | null,
|
||||
selectedParamSpace: Optuna.SearchSpaceItem | null,
|
||||
logYScale: boolean,
|
||||
mode: string
|
||||
colorTheme: Partial<Plotly.Template>
|
||||
) => {
|
||||
if (document.getElementById(domId) === null) {
|
||||
return
|
||||
@@ -190,7 +193,7 @@ const plotSlice = (
|
||||
},
|
||||
showlegend: false,
|
||||
uirevision: "true",
|
||||
template: mode === "dark" ? plotlyDarkTemplate : {},
|
||||
template: colorTheme,
|
||||
}
|
||||
if (
|
||||
selectedParamSpace === null ||
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
import { CssBaseline, ThemeProvider } from "@mui/material"
|
||||
import { Meta, StoryObj } from "@storybook/react"
|
||||
import React from "react"
|
||||
import { useMockStudy } from "../MockStudies"
|
||||
import { darkTheme } from "../styles/darkTheme"
|
||||
import { PlotSlice } from "./PlotSlice"
|
||||
|
||||
const meta: Meta<typeof PlotSlice> = {
|
||||
component: PlotSlice,
|
||||
title: "PlotSliceDark",
|
||||
tags: ["autodocs"],
|
||||
decorators: [
|
||||
(Story, storyContext) => {
|
||||
const { study } = useMockStudy(storyContext.parameters?.studyId)
|
||||
if (!study) return <p>loading...</p>
|
||||
return (
|
||||
<ThemeProvider theme={darkTheme}>
|
||||
<CssBaseline />
|
||||
<Story
|
||||
args={{
|
||||
study,
|
||||
}}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
)
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
backgrounds: { default: "dark" },
|
||||
},
|
||||
}
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof PlotSlice>
|
||||
|
||||
export const MockStudyExample1: Story = {
|
||||
parameters: {
|
||||
studyId: 1,
|
||||
},
|
||||
}
|
||||
@@ -2,19 +2,20 @@ import { CssBaseline, ThemeProvider } from "@mui/material"
|
||||
import { Meta, StoryObj } from "@storybook/react"
|
||||
import React from "react"
|
||||
import { useMockStudy } from "../MockStudies"
|
||||
import { darkTheme } from "../styles/darkTheme"
|
||||
import { lightTheme } from "../styles/lightTheme"
|
||||
import { TrialTable } from "./TrialTable"
|
||||
|
||||
const meta: Meta<typeof TrialTable> = {
|
||||
component: TrialTable,
|
||||
title: "TrialTable",
|
||||
title: "Table/TrialTable",
|
||||
tags: ["autodocs"],
|
||||
decorators: [
|
||||
(Story, storyContext) => {
|
||||
const { study } = useMockStudy(storyContext.parameters?.studyId)
|
||||
if (!study) return <p>loading...</p>
|
||||
return (
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<ThemeProvider theme={storyContext.parameters?.theme}>
|
||||
<CssBaseline />
|
||||
<Story
|
||||
args={{
|
||||
@@ -30,8 +31,16 @@ const meta: Meta<typeof TrialTable> = {
|
||||
export default meta
|
||||
type Story = StoryObj<typeof TrialTable>
|
||||
|
||||
export const MockStudyExample1: Story = {
|
||||
export const LightTheme: Story = {
|
||||
parameters: {
|
||||
studyId: 1,
|
||||
theme: lightTheme,
|
||||
},
|
||||
}
|
||||
|
||||
export const DarkTheme: Story = {
|
||||
parameters: {
|
||||
studyId: 1,
|
||||
theme: darkTheme,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ export { PlotHistory } from "./components/PlotHistory"
|
||||
export { PlotImportance } from "./components/PlotImportance"
|
||||
export { PlotIntermediateValues } from "./components/PlotIntermediateValues"
|
||||
export { PlotSlice } from "./components/PlotSlice"
|
||||
export { PlotParallelCoordinate } from "./components/PlotParallelCoordinate"
|
||||
export { TrialTable } from "./components/TrialTable"
|
||||
export { GraphContainer } from "./components/GraphContainer"
|
||||
export { useGraphComponentState } from "./hooks/useGraphComponentState"
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import * as Optuna from "@optuna/types"
|
||||
import { render, screen } from "@testing-library/react"
|
||||
import React from "react"
|
||||
import { describe, expect, test } from "vitest"
|
||||
import { PlotParallelCoordinate } from "../src/components/PlotParallelCoordinate"
|
||||
|
||||
describe("PlotParallelCoordinate Tests", async () => {
|
||||
const setup = ({
|
||||
study,
|
||||
dataTestId,
|
||||
}: { study: Optuna.Study; dataTestId: string }) => {
|
||||
const Wrapper = ({
|
||||
dataTestId,
|
||||
children,
|
||||
}: {
|
||||
dataTestId: string
|
||||
children: React.ReactNode
|
||||
}) => <div data-testid={dataTestId}>{children}</div>
|
||||
return render(
|
||||
<Wrapper dataTestId={dataTestId}>
|
||||
<PlotParallelCoordinate study={study} />
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
|
||||
for (const study of window.mockStudies) {
|
||||
test(`PlotParallelCoordinate (study name: ${study.name})`, () => {
|
||||
setup({ study, dataTestId: `plot-parallel-coordinate-${study.id}` })
|
||||
expect(
|
||||
screen.getByTestId(`plot-parallel-coordinate-${study.id}`)
|
||||
).toBeInTheDocument()
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -447,6 +447,34 @@ def create_optuna_storage(
|
||||
trial.report(trial.number, step=0)
|
||||
trial.report(trial.number + 1, step=1)
|
||||
|
||||
# optuna-dashboard issue 410
|
||||
# https://github.com/optuna/optuna-dashboard/issues/410
|
||||
study = optuna.create_study(
|
||||
study_name="optuna-dashboard-issue-410",
|
||||
storage=storage,
|
||||
sampler=optuna.samplers.RandomSampler(),
|
||||
)
|
||||
|
||||
def objective_issue_410(trial: optuna.Trial) -> float:
|
||||
trial.suggest_categorical("resample_rate", ["50ms"])
|
||||
trial.suggest_categorical("channels", ["all"])
|
||||
trial.suggest_categorical("window_size", [256])
|
||||
if trial.number > 15:
|
||||
raise Exception("Unexpected error")
|
||||
trial.suggest_categorical("cbow", [True])
|
||||
trial.suggest_categorical("model", ["m1"])
|
||||
|
||||
trial.set_user_attr("epochs", 0)
|
||||
trial.set_user_attr("deterministic", True)
|
||||
if trial.number > 10:
|
||||
raise Exception("unexpeccted error")
|
||||
trial.set_user_attr("folder", "/path/to/folder")
|
||||
trial.set_user_attr("resample_type", "foo")
|
||||
trial.set_user_attr("run_id", "0001")
|
||||
return 1.0
|
||||
|
||||
study.optimize(objective_issue_410, n_trials=20, catch=(Exception,))
|
||||
|
||||
|
||||
def main() -> None:
|
||||
remove_assets()
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
"src/**/*.stories.tsx",
|
||||
"src/**/*.stories.ts",
|
||||
"src/MockStudies.ts",
|
||||
"src/utils/loadStorageFromFile.ts"
|
||||
],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user