-
Notifications
You must be signed in to change notification settings - Fork 0
Build a reusable research visualization template #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| # Python | ||
| __pycache__/ | ||
| *.py[cod] | ||
| *.pyc | ||
| *.pyo | ||
| *.pyd | ||
| *.so | ||
| *.egg-info/ | ||
| .pytest_cache/ | ||
| .mypy_cache/ | ||
| .tox/ | ||
| .cache/ | ||
| .coverage | ||
| build/ | ||
| dist/ | ||
| pip-wheel-metadata/ | ||
| .env | ||
| venv/ | ||
| .venv/ | ||
| env/ | ||
|
|
||
| # Node | ||
| node_modules/ | ||
| dist/ | ||
| .DS_Store | ||
|
drothermel marked this conversation as resolved.
|
||
| .env.* | ||
| *.log | ||
| npm-debug.log* | ||
| yarn-debug.log* | ||
| yarn-error.log* | ||
| pnpm-debug.log* | ||
|
|
||
| # Data (add your own data files here) | ||
| backend/data/** | ||
| !backend/data/.gitkeep | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| 3.12 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| # Repo Instructions | ||
|
|
||
| This repo is a transplant-oriented visualization template. | ||
| The primary goal is to help a coding agent copy the relevant pieces into an existing project with minimal churn. | ||
|
|
||
| ## Integration Priority | ||
|
|
||
| - Prefer adapting this template into the host project's existing architecture over copying the entire repo structure. | ||
| - Preserve the host project's package manager, app shell, router, styling system, and backend layout unless the user explicitly asks to replace them. | ||
| - Copy only the infrastructure that materially helps: | ||
| - Plot wrapper and Plotly-specific CSS fixes | ||
| - typed API client patterns | ||
| - example visualization pages as reference material | ||
| - simple backend response/model patterns | ||
|
|
||
| ## Replacement Priority | ||
|
|
||
| - Treat the experiment domain as demo code. | ||
| - Replace these first when integrating into a real project: | ||
| - `backend/models/experiment.py` | ||
| - `backend/routers/experiments.py` | ||
| - `frontend/src/types/experiment.ts` | ||
| - `frontend/src/api/experiments.ts` | ||
| - `frontend/src/pages/Overview.tsx` | ||
| - `frontend/src/pages/HeatmapExplorer.tsx` | ||
|
|
||
| ## Local Repo Constraints | ||
|
|
||
| - Python uses root `pyproject.toml` with `uv`. | ||
| - Start the backend from repo root as `backend.main:app`. | ||
| - Backend env vars are loaded from `backend/.env`. | ||
| - Frontend config files are ESM; do not introduce CommonJS `require(...)` into frontend config. | ||
| - Keep Plotly's modebar on hover by default unless the user explicitly wants persistent toolbar controls. | ||
| - Be careful with global SVG styling; Tailwind base styles can interfere with Plotly UI chrome. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| .PHONY: install dev backend frontend | ||
|
|
||
| install: | ||
| uv sync | ||
| cd frontend && npm ci | ||
|
|
||
| dev: | ||
| @$(MAKE) -j2 backend frontend | ||
|
|
||
| backend: | ||
| uv run uvicorn backend.main:app --reload --port 8000 | ||
|
|
||
| frontend: | ||
| cd frontend && npm run dev |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,104 @@ | ||
| # viz-template | ||
| # research-tool-template | ||
|
|
||
| A minimal full-stack template for research data exploration tools. | ||
|
|
||
| **Stack:** FastAPI + Pydantic · Vite + React + TypeScript · Tailwind + shadcn/ui · TanStack Query · Plotly | ||
|
|
||
| ## Start Here | ||
|
|
||
| If you want to copy this into an existing app instead of running this repo as-is: | ||
|
|
||
| - read [docs/integrating-into-existing-project.md](docs/integrating-into-existing-project.md) | ||
| - paste [docs/agent-prompt-template.md](docs/agent-prompt-template.md) into your coding agent | ||
| - run [docs/integration-smoke-checklist.md](docs/integration-smoke-checklist.md) after integration | ||
|
|
||
| ## Setup | ||
|
|
||
| ```bash | ||
| make install # uv sync + npm ci | ||
| make dev # starts backend (port 8000) + frontend (port 5173) | ||
| ``` | ||
|
|
||
| Open [http://localhost:5173](http://localhost:5173). | ||
|
|
||
| ### Prerequisites | ||
|
|
||
| - `uv` | ||
| - Node.js + npm | ||
| - `make` | ||
|
|
||
| ### Environment | ||
|
|
||
| If you need backend env vars, copy `backend/.env.example` to `backend/.env`. | ||
| The backend loads `backend/.env` even when started from the repo root. | ||
|
|
||
| ### Direct commands | ||
|
|
||
| ```bash | ||
| uv sync | ||
| cd frontend && npm ci | ||
|
|
||
| uv run uvicorn backend.main:app --reload --port 8000 | ||
| cd frontend && npm run dev | ||
| ``` | ||
|
|
||
| ## Structure | ||
|
|
||
| ``` | ||
| pyproject.toml # Python project definition for uv | ||
| uv.lock # locked Python dependencies | ||
|
|
||
| backend/ | ||
| main.py # FastAPI app, CORS, router registration | ||
| models/ | ||
| experiment.py # Pydantic models — replace with your own | ||
| routers/ | ||
| experiments.py # API endpoints — replace with your own | ||
| data/ # put data files here (gitignored) | ||
|
|
||
| frontend/src/ | ||
| api/ | ||
| client.ts # base fetch wrapper + TanStack Query client | ||
| experiments.ts # typed API functions — mirror your backend here | ||
| types/ | ||
| experiment.ts # TypeScript interfaces mirroring Pydantic models | ||
| components/ | ||
| ui/ # shadcn/ui components (copy-paste, no magic) | ||
| layout/ # Sidebar + Layout shell | ||
| charts/Plot.tsx # thin Plotly wrapper with sensible defaults | ||
| pages/ | ||
| Overview.tsx # scatter plot + filter controls (example) | ||
| HeatmapExplorer.tsx # heatmap with axis/metric selectors (example) | ||
| App.tsx # React Router routes | ||
| ``` | ||
|
|
||
| ## Adding a new page | ||
|
|
||
| 1. Create `frontend/src/pages/MyPage.tsx` (copy an existing page as a starting point) | ||
| 2. Add a route in `App.tsx` | ||
| 3. Add a nav link in `components/layout/Sidebar.tsx` | ||
| 4. Add a backend router in `backend/routers/` and register it in `main.py` | ||
| 5. Add typed API functions in `frontend/src/api/` and types in `frontend/src/types/` | ||
|
|
||
| ## Plotly chart types | ||
|
|
||
| The `<Plot>` wrapper accepts any Plotly trace type. Common ones: | ||
|
|
||
| ```tsx | ||
| // Scatter | ||
| data={[{ type: 'scatter', mode: 'markers', x: [...], y: [...] }]} | ||
|
|
||
| // Heatmap | ||
| data={[{ type: 'heatmap', z: [[...]], x: [...], y: [...], colorscale: 'Viridis' }]} | ||
|
|
||
| // Bump / line | ||
| data={[{ type: 'scatter', mode: 'lines+markers', x: [...], y: [...] }]} | ||
|
|
||
| // Faceted: use layout.grid | ||
| layout={{ grid: { rows: 2, columns: 3, pattern: 'independent' } }} | ||
| ``` | ||
|
|
||
| ## Deploying to Railway | ||
|
|
||
| The backend is standard FastAPI — Railway can install from `pyproject.toml` and run it with uvicorn. | ||
| Set the `ALLOWED_ORIGINS` env var to your frontend's Railway URL. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| ALLOWED_ORIGINS=http://localhost:5173 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| """Backend package for the research tool template.""" |
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| import os | ||
| from pathlib import Path | ||
|
|
||
| from fastapi import FastAPI | ||
| from fastapi.middleware.cors import CORSMiddleware | ||
| from dotenv import load_dotenv | ||
|
|
||
| from backend.routers import experiments | ||
|
|
||
| BACKEND_DIR = Path(__file__).resolve().parent | ||
|
|
||
| load_dotenv(BACKEND_DIR / ".env") | ||
|
|
||
| app = FastAPI(title="Research Tool API", version="0.1.0") | ||
|
|
||
| allowed_origins = [ | ||
| origin.strip() | ||
| for origin in os.getenv("ALLOWED_ORIGINS", "http://localhost:5173").split(",") | ||
| if origin.strip() | ||
| ] | ||
|
|
||
| app.add_middleware( | ||
| CORSMiddleware, | ||
| allow_origins=allowed_origins, | ||
| allow_credentials=True, | ||
| allow_methods=["*"], | ||
| allow_headers=["*"], | ||
| ) | ||
|
|
||
| # --- Register routers --- | ||
| # Add your own routers here following the same pattern | ||
| app.include_router(experiments.router, prefix="/api/experiments", tags=["experiments"]) | ||
|
|
||
|
|
||
| @app.get("/health") | ||
| def health(): | ||
| return {"status": "ok"} |
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| """ | ||
| Example Pydantic models for a hyperparameter sweep experiment. | ||
| Replace these with your own domain models. | ||
|
|
||
| Pattern to follow: | ||
| - One model per logical entity | ||
| - Use nested models liberally (Pydantic handles serialization) | ||
| - Response models wrap lists with a `total` field for easy pagination later | ||
| - Filter/query models as separate classes (can be used as query params or request bodies) | ||
| """ | ||
|
|
||
| from pydantic import BaseModel | ||
| from typing import Optional | ||
|
|
||
|
|
||
| # --- Domain models --- | ||
|
|
||
| class HyperparameterConfig(BaseModel): | ||
| learning_rate: float | ||
| batch_size: int | ||
| dropout: float | ||
| hidden_dim: int | ||
| num_layers: int | ||
|
|
||
|
|
||
| class Metrics(BaseModel): | ||
| accuracy: float | ||
| loss: float | ||
| val_accuracy: float | ||
| val_loss: float | ||
|
|
||
|
|
||
| class ExperimentResult(BaseModel): | ||
| id: str | ||
| name: str | ||
| config: HyperparameterConfig | ||
| metrics: Metrics | ||
| epoch: int | ||
| created_at: str | ||
|
|
||
|
|
||
| # --- Response models --- | ||
|
|
||
| class ExperimentsResponse(BaseModel): | ||
| experiments: list[ExperimentResult] | ||
| total: int | ||
|
|
||
|
|
||
| class HeatmapResponse(BaseModel): | ||
| x_param: str | ||
| y_param: str | ||
| metric: str | ||
| z: list[list[Optional[float]]] # 2D matrix, None = no data | ||
| x_labels: list[str] | ||
| y_labels: list[str] | ||
|
|
||
|
|
||
| # --- Filter/query param models --- | ||
|
|
||
| VALID_CONFIG_PARAMS = {"learning_rate", "batch_size", "dropout", "hidden_dim", "num_layers"} | ||
| VALID_METRICS = {"accuracy", "loss", "val_accuracy", "val_loss"} |
Empty file.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.