Skip to content

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

<!-- In base.html -->
<script src="https://3dmol.org/build/3Dmol-min.js"></script>

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.