py2Dmol, embedded

One script tag and one call. Each section below is a complete program — the code shown is the code that made the viewer beside it, run once when the page loaded — and each introduces exactly one thing the one before it did not. Every viewer is live: drag to rotate, scroll to zoom.

1 · One call

Ubiquitin, 1UBQ. A C-alpha trace is enough — the backbone, the secondary structure and the ribbon are all worked out from it.


    

The div is yours, the canvas inside it is the library's. PDB or mmCIF, told apart by looking at the text, so you do not have to say which you have.

The frame around the canvas is on by default. box: false takes it off.

Getting the text. show takes a string, so it is yours to fetch, read from a file input, or write into the page. For the two public archives there is a helper that knows the URLs:

const text = await py2Dmol.fetch('1UBQ');    // four characters: the PDB
const text = await py2Dmol.fetch('P01308');  // anything else: AlphaFold
py2Dmol.show('mol', text);

It is separate from show and hands back text rather than a viewer, so a download that fails — offline, a wrong id, a slow server — is yours to catch, and show stays something that either works or throws immediately.

Biological assemblies are built by default. A crystal structure file holds the asymmetric unit, which for anything symmetric is a fraction of the real molecule — one protomer of a trimer, a sixtieth of a capsid — along with the operations that make the rest. Those are applied unless you say otherwise:

py2Dmol.show('mol', text, { biounit: false });   // the deposited coordinates

A file with no assembly records is drawn exactly as it is, which is most files, so the default costs a search that finds nothing.

2 · A style

Four looks, one list. tube is the backbone trace; the other three are cartoons.


    

Pass it to show as style, or change it later with setStyle. Both take the same four names.

3d comes up on a black page, since solid shaded geometry is meant to be seen on one. bg: 'white' overrides that.

3 · A colour mode

Haemoglobin, 4HHB — four chains, so the modes differ visibly. auto chooses for you: one chain gets rainbow, several get chain.


    

auto · chain · rainbow · plddt · deepmind · ss · hydrophobicity. A name it does not know throws, rather than leaving the picture unchanged and letting you wonder.

hydrophobicity is Kyte & Doolittle in five buckets rather than a ramp, orange for the core and blue for the surface, so you can point at a band and name it. py2Dmol.hydrophobicityBands is the same five, in order, for a legend.

plddt reads the B-factor column, which in a crystal structure is a real B-factor rather than a confidence.

4 · ...on part of it

A mode colours everything. Give the colour a selector and it colours part of it instead — and an explicit colour always beats the mode.


    

This is the selector, and everything else on the page takes the same one. A bare string is a chain, {range} is half open, and {residues} is the numbering in the file.

Colours accumulate: a range and then a chain leaves both in place. To start again, set a mode.

5 · Selecting

A selection is a highlight — it changes nothing about what is drawn. Two verbs, each relative to what is selected now.


    

select adds, unselect takes away, and unselect() with nothing to take away clears.

Clicking the canvas selects too: a click takes a residue, shift-click adds another, double-click takes the whole chain, and a click on the background clears. select: false turns that off for a viewer meant only to be looked at.

6 · Hiding

Different from selecting: this decides what is drawn at all. The same two shapes — add and take away, relative to what is on screen.


    

hide takes residues off, show puts them back, and resetVisibility() returns to everything. To draw one thing and nothing else, reset first and hide the rest: v.resetVisibility(); v.hide({not: sel});

What is hidden is hidden to everything else too: it cannot be clicked, and it does not pull the camera when you orient.

7 · A selector that computes

Trypsin with benzamidine in its pocket. Two selector keys that find residues rather than naming them, so nothing below mentions a residue number.


    

{type} is what a position is: 'L' ligand, 'P' protein, 'N' nucleic. You do not need to know what the ligand is called.

{near} is everything within 5 Å of another selector, measured atom to atom. That is a contact shell — a hydrogen bond is under 3.5 and a salt bridge under 4. {angstroms} changes it.

The ligand is part of site, since it is within 5 Å of itself. {not: {type: 'L'}} would leave it out.

8 · Side chains

The same site, with atoms. Side chains are drawn per residue, so you ask for the ones you want to see — and they can carry a colour of their own, apart from the ribbon they grow out of.


    

hideSidechains takes them away again.

setSidechainColor takes a colour or a whole colour mode, and the same selector as everything else. It is a second colour, not a replacement: a side chain nobody spoke for follows its residue, so recolouring the main chain carries it along, and one given a colour of its own stays put. That is what lets a backbone say plddt while its side chains say hydrophobicity — two questions on one picture. v.setSidechainColor(null) hands them back.

They arrive coloured by element — carbon in the residue's own colour, oxygen red, nitrogen blue — and so does a ligand. That is on by default, since those are the atoms with an element worth reading; v.hideElements(sel) turns it off for a flat colour, and v.showElements(sel) puts it back.

This needs a structure that carries side-chain atoms — a C-alpha trace has none, and asking for them throws rather than quietly doing nothing. The file here keeps every C-alpha, and all the atoms of the residues nearest the ligand.

9 · The camera

A viewer turns each structure to face you as it loads. orient is for afterwards, and it takes a selector, so it can frame a part.


    

With no selector it frames the selection, or everything visible if nothing is selected.

{animate: false} jumps instead of flying. On load it never animates, and orient: false keeps the file's own orientation for a structure that already sits the way you want it.

10 · Clipping in

Close up, the pocket is still behind the front of the protein. A slab cuts down to it.


    

The slab is as thick as the selection is deep, and refits as you turn, so it stays over the site rather than at a fixed depth. Drag it and watch.

To cut deeper, clip to less: v.clip({type: 'L'}) keeps a thinner slice than v.clip(site). There is no separate thickness setting — the selector is it.

v.clip() with no selector turns it off.

11 · More than one frame

An NMR ensemble, 1YNE — six models, and RNA, so this is the nucleic acid example too. Nothing below asks for either.


    

A file with more than one model gets a player; a single structure stays a bare canvas. play: false refuses it, play: true insists.

The player is the library's: play, a slider, a counter and a speed control. You can also step frames from code with v.setFrame(i).

Bases are drawn as plates on the backbone. Nothing asks for that either — a structure holding protein and nucleic is drawn as both.

12 · Contacts

Lines between residues. The weight sets the width, and the colour is optional.


    

Within one structure an end is either a position or a chain and residue number: [5, 60, 1.0] or ['A', 12, 'B', 34, 1.0].

An empty list clears them. Contacts belong to the structure, so loading over it replaces them.

13 · Two structures at once

load puts a second structure up under its own name, and showObjects draws both. Here the second is ubiquitin again, moved 26 Å clear so the two are easy to tell apart.


    

Names are yours to choose. They are deliberately not A and B here, which is what the chains inside a structure are called.

The camera widens to take in a structure it has not framed before. Hiding one and showing it again leaves the camera where it is.

A contact can join two different structures. Each end names its own, and the contact belongs to the viewer rather than to either one, so you pass no name.

14 · Two viewers on one page

Call show twice with different elements.


    

The canvas is looked up inside its own container, so the containers are what must be distinct. Everything the library creates inside them is scoped and may repeat.

The two viewers are independent: a selection, a style or a camera in one does not touch the other.

15 · The panel and the player

Ask for controls and you get the notebook's own panel — the same one, not an imitation.


    

Style, Capture and every slider, plus Orient and Rotate. Off by default: most embeds want a picture and nothing else.

And the selection panel, which arrives with it: click a residue and it slides in with the website's own controls — colour (a PyMOL palette or a scheme, on the main chain and the side chains separately), Show/Hide per part, element colours, base plates, a secondary-structure override, contacts between a pair, Find interactions, and Copy / Cut / Delete. Pass select: false to keep the chrome and turn picking and the panel off. Align is the one row that does not travel: it needs align/align.js, which can never be bundled, so the row hides itself here.

Its styling is scoped to your container, so it cannot reach your page's own dropdowns or buttons.

It stays inside the box you give it. On a fixed height the panel scrolls; in a container too narrow for the panel beside the canvas, it wraps underneath.

16 · A map beside it

Any residue×residue matrix — a PAE, a contact map, a distogram — with a drag on it that selects.


    

The bundle carries the panel; your page carries the box — a #heatmapContainer holding a #heatmapCanvas (the older #paeContainer / #paeCanvas still work). controls: true is not optional: it selects the shell that mounts the panels.

A map describes itself. vmin/vmax are the values the bytes are encoded against, colors is a list of stops, and xlabel/ylabel caption the axes — all of them beating the panel's built-in scale for that key. The tab is named by the key, so two maps in one frame are two tabs.

Numbered slots, the same tabs over each. The viewer's box is slot 1 and this one is slot 2; each shows the structure, a map or the scatter plot, each can be dragged to size, and picking in one what another shows swaps them. With no coordinates yet the map takes the first, and full screen is slot 1's. slots: ['contact'] in the options, or v.setSlots(['pae', 'structure']) later, chooses from code — one name per slot, in order, and a short list leaves the rest alone.

Dragging a box on the plot selects those residues in the structure, and a box is stored in residues — so it means the same thing on every map, whatever each one's resolution.

Which bundle

Two, differing only in which painter they carry. Everything on this page runs the same on either.

filesize
py2Dmol.embed.min.js632 KBWebGL2. What this page loads. Turning and zooming cost one draw call, which is what makes a large structure usable.
py2Dmol.embed.cpu.min.js540 KBThe 2D painter. 92 KB smaller, no WebGL2 needed, and it can export SVG — but a big structure repaints slowly.

Both carry the control panel, the capture UI and the heatmap panel — what controls: true gives you, plus 15 KB for a plot that draws nothing until you give it a container. Neither carries the sequence strip, the MSA, saved sessions or structural alignment; for those, use the full application.

Neither is a fallback for the other. Each carries one painter and nothing behind it, so the WebGL2 build on a browser without WebGL2 draws nothing and says so on the console rather than quietly changing how it works. There is no gpu option for the same reason: the file you load decides.

On a 313,000-atom capsid the difference is 26 ms a frame against 840. Below a few thousand residues you will not notice it, and the CPU build is the only one that can save an SVG.

bundles/py2Dmol.full.min.js is a different animal: the whole website — its panels, its file ingestion, its sessions — with this embed API on top. Set for set it is the site's own bundle and one module more, so it is 755 KB against 737 and 476: the embed half costs 18 KB, because the app had already paid for everything it needs.

The bundle is the code, not the layout — and here that means the app's layout, not just a panel's. Every panel finds its own markup and does nothing until it exists (the heatmap wants a #heatmapContainer holding a #heatmapCanvas — the older #paeContainer / #paeCanvas still work), and src/app/ looks up the site's controls the same way. A page loading this is hosting index.html's markup, or the parts of it whose features it wants. Panel markup goes beside the viewer, never inside — show replaces its container's children. Then ask for it:

py2Dmol.show('v', text, {controls: true, heatmap: {enabled: true}});

controls: true is not optional here: it selects the shell that mounts the panels. The sequence strip and the MSA look their elements up on document rather than inside the viewer, so two viewers on one page would find each other's — the PAE plot scopes to the container and is the one to reach for first.

Two doors lead into the app itself. py2dmolLoadFiles(files, loadAsFrames, groupName) is the ingestion path: it takes virtual files, {name, readAsync} rather than File objects, so a page hands over a structure it computed in memory instead of faking an upload — and the extension decides what each one is (.pdb/.cif a structure, .json a PAE matrix paired by basename, .a3m/.fasta an alignment, .cst contacts). py2dmolReadyMessage replaces the idle status line, which otherwise names two controls your page may not have.

This bundle needs two script tags. It carries the TM-align methods, and align/align.js can never be concatenated into any bundle: it starts its Worker by having the worker import itself, found through document.currentScript.src, so inlined it has no URL and the alignment silently runs on the main thread. Load it separately, exactly as index.html does, or every method it adds throws.

Reference

py2Dmol.show(target, text, options)

Creates the canvas inside target (an element or its id), parses text, and returns the viewer. The format is decided by looking at the text — mmCIF if it declares data_, PDB otherwise — so a caller who fetched a URL need not know which they got.

const v = py2Dmol.show('mol', text, {
    width: 300,          // defaults to the element's own width
    height: 300,
    style: 'richardson', // tube (default), richardson, ribbon, 3d
    color: 'chain',      // auto, chain, rainbow, plddt, deepmind, ss
    bg: 'white',         // 'black' is the default under style '3d'
    box: true,           // the frame around the canvas
    name: 'structure',   // what to call this object
    biounit: true,       // build the biological assembly, if the file says how
    orient: true,        // turn it to face the reader as it lands
    select: true,        // clicking the canvas picks a residue
    controls: false,     // the Style panel
    play: undefined,     // the frame strip; on by itself when frames > 1
});

Anything else is passed to the viewer as configuration: detail (2–8 segments per residue), shadow, shadow_strength, outline, ortho, colorblind, ss_palette, rotate, autoplay, cyclic. There is no gpu option — the bundle decides which painter draws, and passing one throws.

py2Dmol.fetch(id)

A structure by id, as text: four characters go to the PDB, anything else to AlphaFold. Returns a promise; a bad id or a lost network rejects, which is why it is not folded into show.

The selector

One grammar, taken by everything that names a set of residues.

'B'a chain, by id
[3, 4, 5]positions outright; a Set works too
{chain}one id or several
{positions}indices into what is drawn, from zero
{range: [a, b]}positions a..b-1, half open
{residues}the numbering in the file
{type}'L' ligand, 'P' protein, 'N' nucleic
{near}within 5 Å of another selector, atom to atom
{angstroms}...at another distance
{object}within one object of several
{not}the complement

Keys narrow each other, so {chain: 'B', residues: [12]} is one residue of one chain. A key it does not recognise throws — a selector that narrows nothing would mean everything, which is rarely what a typo was meant to say.

Positions are not residue numbers. A position counts from zero across everything drawn; a residue is the number in the file, which starts where the depositor started and can repeat between chains. Both are integers, so nothing will warn you if you pass one for the other.

Looking

v.setStyle(name)tube, richardson, ribbon, 3d
v.setColor(colour, sel?)a mode for everything, or a colour on part of it. Colours accumulate until you set a mode again
v.orient(sel?)frame that, or the selection, or everything visible. {animate: false} jumps
v.clip(sel)a slab as deep as the selection is, refitted as you turn. No argument turns it off
v.render(reason)redraw; everything above already does
{rendering: {ortho}}0 is a camera close in with strong perspective, 1 stands it far enough back that the divergence is invisible. 0.5 by default. Set at show; the focal length is worked out from the structure, so it means the same thing whatever size it is
py2Dmol.superpose(mobile, reference, opts?)Kabsch, as a function: the coordinates of mobile moved onto reference. {from, to} are index arrays naming the points the fit is computed from — the alpha carbons, say — and the transform still applies to every point, which is how two structures with different atom lists are superposed

Choosing what is drawn

v.select(sel) · v.unselect(sel)add to and take from the highlight, both relative to it. v.unselect() clears
v.hide(sel) · v.show(sel)subtract from and add to what is showing, both relative to it
v.resetVisibility()draw everything again. Reset and then hide({not: sel}) draws sel alone
v.showSidechains(sel) · v.hideSidechains(sel)side-chain atoms, per residue. Throws on a structure that carries none, such as a C-alpha trace
v.setSidechainColor(colour, sel?)a colour or a colour mode for the side chains alone. Unset follows the residue, and null puts it back
v.showElements(sel) · v.hideElements(sel)colour those atoms by element. On by default wherever there are atoms to read — ligands and drawn side chains — so this is mostly how you turn it off
v.setVisibility(patch) · v.getVisibility()the lower level underneath those, taking Sets of what to keep. Prefer hide and show

Structures and frames

v.load(text, name)another structure, under its own name. Loading over a name replaces what was there
v.showObjects(names)which structures are drawn. The camera widens for one it has not framed before
v.setFrame(i) · v.addFrame(frame, name)step to a frame, or add one. Frames come from py2Dmol.frameFromText, or framesFromText for every model in a file
v.addObject(name)an empty object to add frames to
v.resetAll()back to an empty viewer

Contacts

v.setContacts(list, name?) — lines between residues, an empty list clears. Within one object an end is a position or a chain-and-residue pair:

v.setContacts([[5, 60, 1.0],
               [10, 40, 0.4, { r: 255, g: 0, b: 0 }]]);
v.setContacts([['A', 12, 'B', 34, 1.0]]);

An end written as a selector naming one residue may sit in a different structure. Such a contact belongs to the viewer rather than to either object, so pass no name:

v.setContacts([[{ object: 'ubiquitin', residues: [10] },
                { object: 'copy', residues: [40] }, 1.0]]);