UI Design — ChemLib¶
Overview¶
The UI is a server-rendered web application using FastAPI's Jinja2 templates with JavaScript for interactivity. The 3D molecular viewer uses 3Dmol.js (the JavaScript library that py3Dmol wraps). All data flows through the REST API — the templates are served by FastAPI but fetch data via fetch() calls to /api/* endpoints.
Technology Choices¶
| Component | Technology | Reason |
|---|---|---|
| Templates | Jinja2 (via FastAPI) | Simple, server-rendered, no build step |
| Styling | Bootstrap 5 or Tailwind CSS | Quick, responsive layouts |
| 3D Viewer | 3Dmol.js | WebGL, supports SDF/PDB, rotate/zoom |
| 2D Depiction | RDKit SVG (server-side) | Clean 2D molecule images |
| Data Fetching | Vanilla fetch() API |
No framework needed |
| Tables | DataTables.js (optional) | Sorting, filtering, pagination |
Pages¶
1. Dashboard (/)¶
- Summary statistics: total compounds, fragments, assembled molecules
- Quick actions: Import compounds, Browse fragments, New assembly
- Recent activity feed
2. Compound Browser (/compounds)¶
- Filterable table of all compounds
- Filters sidebar: MW range, LogP range, Lipinski pass, search by name
- Each row shows: 2D depiction (SVG thumbnail), name, SMILES, MW, LogP, QED
- Click row → Compound Detail page
- "Import" button → SDF upload or SMILES input
3. Compound Detail (/compounds/{id})¶
- Large 2D depiction (SVG)
- All computed properties in a card
- Drug-likeness scorecard (Lipinski, Veber, PAINS, QED, SA Score)
- "Decompose into Fragments" button
- "View in 3D" button → opens 3D viewer
- List of fragments derived from this compound
4. Fragment Browser (/fragments)¶
- Grid or table of all fragments
- Each card shows: 2D depiction, SMILES, attachment points, MW
- Filter by attachment point labels
- Filter by size (heavy atom count)
- Click → Fragment Detail with compatible fragments list
5. Assembly Workspace (/assembly/new and /assembly/{id})¶
This is the core interactive page where users build molecules.
Layout:
┌────────────────────────────────────────────────────────┐
│ Assembly Workspace │
├──────────────────────┬─────────────────────────────────┤
│ │ │
│ Current Molecule │ Available Fragments │
│ ┌──────────────┐ │ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │ │ │ │ F1 │ │ F2 │ │ F3 │ │
│ │ 2D Preview │ │ │[3*] │ │[4*] │ │[16*]│ │
│ │ │ │ └─────┘ └─────┘ └─────┘ │
│ └──────────────┘ │ (filtered by compatibility) │
│ │ │
│ Attachment Points: │ Click a fragment to add it │
│ • Point 3 (open) │ │
│ • Point 16 (used) │ │
│ │ │
├──────────────────────┴─────────────────────────────────┤
│ Assembly History │
│ Step 1: [3*]C(=O)O ✓ │
│ Step 2: + [4*]CCCN at 3↔4 ✓ │
│ Step 3: ... │
├────────────────────────────────────────────────────────┤
│ [Finalize & Score] [View in 3D] [Discard] │
└────────────────────────────────────────────────────────┘
Workflow:
1. Select starting fragment (from fragment browser or search)
2. Page shows current molecule with open attachment points highlighted
3. Right panel shows compatible fragments (fetched via /api/fragments/{id}/compatible)
4. Click a fragment → modal asks which attachment points to connect
5. API call to /api/assembly/{id}/add-fragment
6. Page updates with new molecule preview
7. Repeat until satisfied
8. Click "Finalize" → calls /api/assembly/{id}/finalize
9. Redirects to scored result page
6. 3D Viewer Page (/viewer/{parent_type}/{parent_id})¶
Full-page interactive 3D molecular viewer.
Layout:
┌────────────────────────────────────────────────────────┐
│ 3D Molecular Viewer │
├────────────────────────────────────────────┬───────────┤
│ │ Controls │
│ │ │
│ │ Style: │
│ 3Dmol.js Canvas │ ○ Stick │
│ (WebGL) │ ● Ball+S │
│ │ ○ Sphere │
│ Rotate: drag │ ○ Surface │
│ Zoom: scroll │ │
│ Pan: right-drag │ Color: │
│ │ ○ Element │
│ │ ○ Chain │
│ │ │
│ │ Conf: │
│ │ ◀ 5/50 ▶ │
│ │ E:-42.3 │
│ │ │
│ │ [Minimize]│
├────────────────────────────────────────────┴───────────┤
│ Molecule: O=C(O)CCCN | MW: 103.1 | Energy: -42.3 │
└────────────────────────────────────────────────────────┘
Features: - Interactive rotation, zoom, pan (built into 3Dmol.js) - Style switching (stick, ball-and-stick, sphere, surface) - Color scheme switching (element, chain, residue) - Conformer navigator: browse through conformers with energy display - "Generate 3D" button if no conformers exist - Molecule info bar at bottom
7. Scoring Report (/scoring/{parent_type}/{parent_id})¶
- Visual scorecard with pass/fail indicators
- Lipinski properties with color-coded thresholds
- Veber rules status
- PAINS alerts (if any, shown in red)
- QED gauge (0-1 scale, color gradient)
- SA Score gauge (1-10 scale, color gradient)
- Overall recommendation badge
3Dmol.js Integration¶
Loading 3Dmol.js¶
Viewer Initialization (static/js/viewer.js)¶
class MolViewer {
constructor(containerId) {
this.container = document.getElementById(containerId);
this.viewer = $3Dmol.createViewer(this.container, {
backgroundColor: 'white',
});
this.currentStyle = 'stick';
this.currentModel = null;
}
async loadMolecule(parentType, parentId, conformerId = null) {
let url = `/api/viz/${parentType}/${parentId}/3d`;
if (conformerId) url += `?conformer_id=${conformerId}`;
const response = await fetch(url);
const data = await response.json();
this.viewer.removeAllModels();
this.currentModel = this.viewer.addModel(data.mol_block, 'sdf');
this.setStyle(this.currentStyle);
this.viewer.zoomTo();
this.viewer.render();
return data;
}
setStyle(style) {
this.currentStyle = style;
this.viewer.setStyle({}, this.getStyleSpec(style));
this.viewer.render();
}
getStyleSpec(style) {
switch (style) {
case 'stick':
return { stick: { radius: 0.15, colorscheme: 'Jmol' } };
case 'ball-and-stick':
return {
stick: { radius: 0.1, colorscheme: 'Jmol' },
sphere: { scale: 0.25, colorscheme: 'Jmol' },
};
case 'sphere':
return { sphere: { colorscheme: 'Jmol' } };
case 'surface':
// Add surface to existing style
this.viewer.addSurface($3Dmol.SurfaceType.VDW, {
opacity: 0.85,
colorscheme: 'Jmol',
});
return { stick: { radius: 0.1, colorscheme: 'Jmol' } };
default:
return { stick: {} };
}
}
async loadConformerList(parentType, parentId) {
const response = await fetch(
`/api/viz/${parentType}/${parentId}/conformers`
);
return await response.json();
}
}
Conformer Browser Widget¶
class ConformerBrowser {
constructor(viewer, parentType, parentId) {
this.viewer = viewer;
this.parentType = parentType;
this.parentId = parentId;
this.conformers = [];
this.currentIndex = 0;
}
async init() {
const data = await this.viewer.loadConformerList(
this.parentType, this.parentId
);
this.conformers = data.conformers;
if (this.conformers.length > 0) {
await this.showConformer(0);
}
this.updateUI();
}
async showConformer(index) {
this.currentIndex = index;
const conf = this.conformers[index];
await this.viewer.loadMolecule(
this.parentType, this.parentId, conf.id
);
this.updateUI();
}
async next() {
if (this.currentIndex < this.conformers.length - 1) {
await this.showConformer(this.currentIndex + 1);
}
}
async prev() {
if (this.currentIndex > 0) {
await this.showConformer(this.currentIndex - 1);
}
}
updateUI() {
const conf = this.conformers[this.currentIndex];
document.getElementById('conf-counter').textContent =
`${this.currentIndex + 1} / ${this.conformers.length}`;
document.getElementById('conf-energy').textContent =
`Energy: ${conf.energy.toFixed(2)} kcal/mol`;
document.getElementById('conf-lowest').textContent =
conf.is_lowest ? '★ Lowest Energy' : '';
}
}
2D Depiction (Server-Side SVG)¶
RDKit generates SVG depictions server-side. These are served via the /api/viz/{type}/{id}/2d endpoint and embedded in HTML as inline SVG or <img> tags.
# In viz_service.py
from rdkit.Chem.Draw import rdMolDraw2D
def mol_to_svg(mol: Chem.Mol, width: int = 300, height: int = 200) -> str:
AllChem.Compute2DCoords(mol)
drawer = rdMolDraw2D.MolDraw2DSVG(width, height)
drawer.DrawMolecule(mol)
drawer.FinishDrawing()
return drawer.GetDrawingText()
Responsive Design Considerations¶
- 3D viewer should resize to fill available space
- Fragment grid uses CSS grid with responsive breakpoints
- Assembly workspace uses a split-pane layout (resizable on desktop, stacked on mobile)
- Tables switch to card view on small screens
No Frontend Build Step¶
The entire frontend is vanilla HTML/CSS/JS served by FastAPI. This keeps the project simple:
- No Node.js, webpack, or npm required
- 3Dmol.js loaded from CDN
- Bootstrap/Tailwind loaded from CDN
- Custom JS in chemlib/static/js/
- Custom CSS in chemlib/static/css/
For future iterations, could migrate to React/Vue if complexity demands it, but for this scope, templates + vanilla JS are sufficient.