Closed Jhsmit closed 2 years ago
Hi @Jhsmit
Thx. This looks amazing. It seems the molstar_webcomponent.html
file is not included in the PR. Could you please add?
My main comments are
PdbeMolStarWebComponent
to be named PdbeMolStar
. I would like to keep the WebComponent out of it such that we can change the implementation later if needed. Unless its relevant to the end user, just keep the WebComponent name out. Would that be ok?ReactiveHTML
or Viewable
. Inheriting from HTML
was never really intended by Philipp I believe and there can be unexpected consequences. Would that be possible?object
or any of the other parameters. I would expect that to be needed too.I took a look. And I would suggest converting it to a ReactiveHTML
implementation @Jhsmit . Then you would also be able to get events. Etc.
A start is below. It will probably be a bit rough as the web component does not seem that mature/ dynamic. An alternative is using the JS component.
"""A Panel Pane to wrap the PDBe implementation of the Mol* ('MolStar') viewer.
The PBBe viewer is available both as a webcomponent or interactive plugin.
Check out
- [PDBe Mol*](https://github.com/PDBeurope/pdbe-molstar)
- [Mol*](https://molstar.org/)
- [Mol* GitHub](https://github.com/molstar/molstar)
Cite Mol*:
David Sehnal, Sebastian Bittrich, Mandar Deshpande, Radka Svobodová, Karel Berka,
Václav Bazgier, Sameer Velankar, Stephen K Burley, Jaroslav Koča, Alexander S Rose:
Mol* Viewer: modern web app for 3D visualization and analysis of large biomolecular structures,
Nucleic Acids Research, 2021; https://doi.org/10.1093/nar/gkab314.
"""
import param
from panel.reactive import ReactiveHTML
from panel.pane import HTML
import panel as pn
from pathlib import Path
from string import Template
REPRESENTATIONS = [
"cartoon",
"ball-and-stick",
"carbohydrate",
"distance-restraint",
"ellipsoid",
"gaussian-surface",
"molecular-surface",
"point",
"putty",
"spacefill",
]
# See https://embed.plnkr.co/plunk/m3GxFYx9cBjIanBp for an example JS implementation
class PdbeMolStarWebComponent(ReactiveHTML):
"""PDBe MolStar structure viewer.
For more information:
- https://github.com/PDBeurope/pdbe-molstar/wiki
- https://molstar.org/
The implementation is based on the web component. See
- https://github.com/PDBeurope/pdbe-molstar/wiki/2.-PDBe-Molstar-as-Web-component
"""
molecule_id = param.String(
default=None,
doc="PDB id to load"
)
custom_data_url = param.String(
default=None,
doc="Data url for loading custom data. Incompatible with `molecule_id`"
)
custom_data_binary = param.Boolean(
default=None, doc="Optional parameter")
custom_data_format = param.String(
default=None,
doc="Format for custom data, for example 'cif'"
)
ligand_label_comp_id = param.String(
default=None,
doc=""
)
ligand_auth_asym_id = param.String(
default=None,
)
ligand_auth_seq_id = param.Number(
default=None,
)
ligand_hydrogens = param.Boolean(
default=None
)
alphafold_view = param.Boolean(
default=None,
doc="Applies AlphaFold confidence score colouring theme for alphafold model"
)
assembly_id = param.Number(
default=None,
doc="Specify assembly"
)
bg_color = param.String(
doc="Color of the background. If `None`, colors default is chosen depending on the color theme"
)
highlight_color = param.String(
doc='Color for mouseover highlighting'
)
select_color = param.String(
doc='Color for selections'
)
visual_style = param.Selector(
objects=REPRESENTATIONS,
doc="Visual styling"
)
theme = param.Selector(
default='dark',
objects=['light', 'dark'],
doc="CSS theme to use"
)
# hide_polymer = param.Boolean(
# default=None,
# doc="Hide polymer"
# )
# hide_water = param.Boolean(
# default=None,
# doc="Hide water"
# )
# hide_het = param.Boolean(
# default=None,
# doc="Hide het"
# )
# hide_carbs = param.Boolean(
# default=None,
# doc="Hide carbs"
# )
# hide_non_standard = param.Boolean(
# default=None,
# doc="Hide non standard"
# )
# hide_coarse = param.Boolean(
# default=None,
# doc="Hide coarse"
# )
# pdbe_url = param.String(
# default=None,
# doc="Url for PDB data. Mostly used for internal testing"
# )
# load_maps = param.Boolean(
# default=None,
# doc="Load electron density maps from the pdb volume server"
# )
# validation_annotation = param.Boolean(
# default=None,
# doc="Adds 'annotation' control in the menu"
# )
# domain_annotations = param.Boolean(
# default=None,
# doc="Adds 'annotation' control in the menu"
# )
# low_precision = param.Boolean(
# default=None,
# doc="Load low precision coordinates from the model server"
# )
# expanded = param.Boolean(
# default=None,
# doc="Display full-screen by default on load"
# )
# hide_controls = param.Boolean(
# default=True,
# doc="Hide the control menu"
# )
# landscape = param.Boolean(
# default=None
# )
# select_interaction = param.Boolean(
# default=None,
# doc="Switch on or off the default selection interaction behaviour"
# )
# lighting = param.Selector(
# default='matte',
# objects=['flat', 'matte', 'glossy', 'metallic', 'plastic'],
# doc="Set the lighting"
# )
# default_preset = param.Selector(
# default='default',
# objects=['default', 'unitcell', 'all-models', 'supercell'],
# doc="Set the preset view"
# )
# pdbe_link = param.Selector(
# default=None,
# doc="Show the PDBe entry link at in the top right corner"
# )
# hide_expand_icon = param.Boolean(
# default=None,
# doc="Hide the expand icon"
# )
# hide_selection_icon = param.Boolean(
# default=None, # Default False, set False/True for True
# doc="Hide the selection icon"
# )
# hide_animation_icon = param.Boolean(
# default=None,
# doc="Hide the animation icon"
# )
# component_spec = param.String()
_template = """
<link id="molstarTheme" rel="stylesheet" type="text/css" href="https://www.ebi.ac.uk/pdbe/pdb-component-library/css/pdbe-molstar-1.2.0.css"/>
<div id="pdbeMolStarContainer" style="width:100%; height: 100%;">
<pdbe-molstar id="pdbeMolstarComponent"
molecule-id=${molecule_id}
custom-data-url=${custom_data_url} custom-data-format=${custom_data_format} custom-data-binary=${custom_data_binary}
ligand-label-comp-id=${ligand_label_comp_id} ligand-auth-asym-Id=${ligand_auth_asym_id} ligand-auth-seq-id=${ligand_auth_seq_id} ligand-hydrogens=${ligand_hydrogens}
alphafold-view=${alphafold_view}
assembly-id=${assembly_id}
></pdbe-molstar>
</div>
"""
# highlight-color-r=${highlight_color_r} highlight-color-g=${highlight_color_g} highlight-color-b=${highlight_color_b}
__javascript__=[
# "https://cdn.jsdelivr.net/npm/babel-polyfill/dist/polyfill.min.js",
# "https://cdn.jsdelivr.net/npm/@webcomponents/webcomponentsjs/webcomponents-lite.js",
# "https://cdn.jsdelivr.net/npm/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js",
"https://www.ebi.ac.uk/pdbe/pdb-component-library/js/pdbe-molstar-component-1.2.1.js",
]
_scripts = {
"render": """
function standardize_color(str){
var ctx = document.createElement("canvas").getContext("2d");
ctx.fillStyle = str;
return ctx.fillStyle;
}
function toRgb(color) {
var hex = standardize_color(color)
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
}
state.toRgb = toRgb
self.bg_color()
self.highlight_color()
self.select_color()
""",
"bg_color": """
rgb = state.toRgb(data.bg_color)
pdbeMolstarComponent.viewerInstance.canvas.setBgColor(rgb)
""",
# does not work
"highlight_color": """
rgb = state.toRgb(data.highlight_color)
pdbeMolstarComponent.highlightColorR=rgb.r
pdbeMolstarComponent.highlightColorG=rgb.g
pdbeMolstarComponent.highlightColorB=rgb.b
""",
# does not work
"select_color": """
rgb = state.toRgb(data.select_color)
pdbeMolstarComponent.selectColorR=rgb.r
pdbeMolstarComponent.selectColorG=rgb.g
pdbeMolstarComponent.selectColorB=rgb.b
"""
}
def __init__(self, **params):
super().__init__(**params)
# skip = {
# 'theme',
# 'name',
# 'bg_color'
# }
# # Find class attributes defined only on this subclass
# attrs = self.__class__.__dict__.keys() - HTML.__class__.__dict__.keys() - skip
# attrs = [a for a in attrs if not a.startswith('_')]
# elements = []
# for attr in attrs:
# val = getattr(self, attr)
# if val is None:
# continue
# elif isinstance(val, bool):
# val = 'true' if val else 'false'
# else:
# val = f'"{val}"'
# name = attr.replace('_', '-')
# elem = f'{name}={val}'
# elements.append(elem)
# bg_default = 'F7F7F7' if self.theme == 'light' else '000000'
# self.bg_color = self.bg_color or bg_default
# # Split color options into rgb components
# color_options = ['bg_color', 'highlight_color', 'select_color']
# for option in color_options:
# hex_val = getattr(self, option)
# if hex_val is None:
# continue
# hex_val = hex_val.lstrip('#')
# name = option.replace('_', '-')
# for i, c in enumerate(['r', 'g', 'b']):
# val = int(hex_val[2*i:2*i+2], 16)
# elem = f'{name}-{c}="{val}"'
# elements.append(elem)
# component_spec = ' '.join(elements)
# theme = '-light' if self.theme == 'light' else ''
# html_string = (Path(__file__).parent / 'molstar_webcomponent.html').read_text()
# html_template = Template(html_string)
# self.component_spec = html_template.substitute(
# component_spec=component_spec,
# theme=theme
# )
# print(self.component_spec)
pn.extension(sizing_mode="stretch_width")
pdbe = PdbeMolStarWebComponent(
height=500,
# hide_water=True,
# theme='light',
# lighting='metallic',
# hide_expand_icon=True,
# highlight_color='#d1fa07',
# bg_color='#8ad6e6',
visual_style="distance-restraint",
highlight_color="blue",
molecule_id='1qyn',
)
pn.template.FastListTemplate(
site="Panel Chemistry", title="Pdbe Molstar Viewer",
sidebar=[pn.Param(pdbe)],
main=[pdbe],
).servable()
I've experimented with the JS plugin as well @Jhsmit . I would recommend using this one. It seems more robust.
I have implemented below. I will continue a little bit more with it now and the create a separate PR using this one. I will try to figure out how you can become a co-contributor and push directly to branches on this repo.
"""A Panel Pane to wrap the PDBe implementation of the Mol* ('MolStar') viewer.
The PBBe viewer is available both as a webcomponent or interactive plugin.
Check out
- [PDBe Mol*](https://github.com/PDBeurope/pdbe-molstar)
- [Mol*](https://molstar.org/)
- [Mol* GitHub](https://github.com/molstar/molstar)
Cite Mol*:
David Sehnal, Sebastian Bittrich, Mandar Deshpande, Radka Svobodová, Karel Berka,
Václav Bazgier, Sameer Velankar, Stephen K Burley, Jaroslav Koča, Alexander S Rose:
Mol* Viewer: modern web app for 3D visualization and analysis of large biomolecular structures,
Nucleic Acids Research, 2021; https://doi.org/10.1093/nar/gkab314.
"""
import param
from panel.reactive import ReactiveHTML
from panel.pane import HTML
import panel as pn
from pathlib import Path
from string import Template
REPRESENTATIONS = [
"cartoon",
"ball-and-stick",
"carbohydrate",
"distance-restraint",
"ellipsoid",
"gaussian-surface",
"molecular-surface",
"point",
"putty",
"spacefill",
]
# See https://embed.plnkr.co/plunk/m3GxFYx9cBjIanBp for an example JS implementation
class PdbeMolStarWebComponent(ReactiveHTML):
"""PDBe MolStar structure viewer.
Set one of `molecule_id`, `custom_data` and `ligand_view`.
For more information:
- https://github.com/PDBeurope/pdbe-molstar/wiki
- https://molstar.org/
The implementation is based on the js Plugin. See
- https://github.com/PDBeurope/pdbe-molstar/wiki/1.-PDBe-Molstar-as-JS-plugin
"""
molecule_id = param.String(
default=None,
doc="PDB id to load. Example: '1qyn' or '1cbs'"
)
custom_data = param.Dict(
doc="""Load data from a specific data source. Example:
{ "url": "https://www.ebi.ac.uk/pdbe/coordinates/1cbs/chains?entityId=1&asymId=A&encoding=bcif", "format": "cif", "binary": True }
"""
)
ligand_view = param.Dict(
doc="""This option can be used to display the PDBe ligand page 3D view like https://www.ebi.ac.uk/pdbe/entry/pdb/1cbs/bound/REA.
Example: {"label_comp_id": "REA"}
"""
)
alphafold_view = param.Boolean(
default=False,
doc="Applies AlphaFold confidence score colouring theme for alphafold model"
)
assembly_id = param.String(
doc="Specify assembly"
)
# bg_color = param.String(
# doc="Color of the background. If `None`, colors default is chosen depending on the color theme"
# )
# highlight_color = param.String(
# doc='Color for mouseover highlighting'
# )
# select_color = param.String(
# doc='Color for selections'
# )
visual_style = param.Selector(
objects=REPRESENTATIONS,
doc="Visual styling"
)
# theme = param.Selector(
# default='dark',
# objects=['light', 'dark'],
# doc="CSS theme to use"
# )
# hide_polymer = param.Boolean(
# default=None,
# doc="Hide polymer"
# )
# hide_water = param.Boolean(
# default=None,
# doc="Hide water"
# )
# hide_het = param.Boolean(
# default=None,
# doc="Hide het"
# )
# hide_carbs = param.Boolean(
# default=None,
# doc="Hide carbs"
# )
# hide_non_standard = param.Boolean(
# default=None,
# doc="Hide non standard"
# )
# hide_coarse = param.Boolean(
# default=None,
# doc="Hide coarse"
# )
# pdbe_url = param.String(
# default=None,
# doc="Url for PDB data. Mostly used for internal testing"
# )
# load_maps = param.Boolean(
# default=None,
# doc="Load electron density maps from the pdb volume server"
# )
# validation_annotation = param.Boolean(
# default=None,
# doc="Adds 'annotation' control in the menu"
# )
# domain_annotations = param.Boolean(
# default=None,
# doc="Adds 'annotation' control in the menu"
# )
# low_precision = param.Boolean(
# default=None,
# doc="Load low precision coordinates from the model server"
# )
# expanded = param.Boolean(
# default=None,
# doc="Display full-screen by default on load"
# )
# hide_controls = param.Boolean(
# default=True,
# doc="Hide the control menu"
# )
# landscape = param.Boolean(
# default=None
# )
# select_interaction = param.Boolean(
# default=None,
# doc="Switch on or off the default selection interaction behaviour"
# )
# lighting = param.Selector(
# default='matte',
# objects=['flat', 'matte', 'glossy', 'metallic', 'plastic'],
# doc="Set the lighting"
# )
# default_preset = param.Selector(
# default='default',
# objects=['default', 'unitcell', 'all-models', 'supercell'],
# doc="Set the preset view"
# )
# pdbe_link = param.Selector(
# default=None,
# doc="Show the PDBe entry link at in the top right corner"
# )
# hide_expand_icon = param.Boolean(
# default=None,
# doc="Hide the expand icon"
# )
# hide_selection_icon = param.Boolean(
# default=None, # Default False, set False/True for True
# doc="Hide the selection icon"
# )
# hide_animation_icon = param.Boolean(
# default=None,
# doc="Hide the animation icon"
# )
# component_spec = param.String()
_template = """
<link id="molstarTheme" rel="stylesheet" type="text/css" href="https://www.ebi.ac.uk/pdbe/pdb-component-library/css/pdbe-molstar-1.2.0.css"/>
<div id="container" style="width:100%; height: 100%;"><div id="pdbeViewer" style="width:100%; height: 100%;"></div></div>
"""
__javascript__=[
"https://www.ebi.ac.uk/pdbe/pdb-component-library/js/pdbe-molstar-plugin-1.2.0.js",
]
_scripts = {
"render": """
function standardize_color(str){
var ctx = document.createElement("canvas").getContext("2d");
ctx.fillStyle = str;
return ctx.fillStyle;
}
function toRgb(color) {
var hex = standardize_color(color)
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
}
state.toRgb = toRgb
function getOptions(){
return {
moleculeId: data.molecule_id,
customData: data.custom_data,
ligandView: data.ligand_view,
alphafoldView: data.alphafold_view,
assemblyId: data.assembly_id,
visualStyle: data.visual_style,
}
}
state.getOptions=getOptions
state.viewerInstance = new PDBeMolstarPlugin();
state.viewerInstance.render(pdbeViewer, state.getOptions());
""",
"rerender": """
state.viewerInstance.visual.update(state.getOptions(), fullLoad=true)
""",
"molecule_id": "state.viewerInstance.visual.update({moleculeId:data.molecule_id})",
"custom_data": "state.viewerInstance.visual.update({customData:data.custom_data})",
# Don't know if below works. To be tested
"ligand_view": "state.viewerInstance.visual.update({ligandView:data.ligand_view})",
"alphafold_view": "state.viewerInstance.visual.update({alphafoldView:data.alphafold_view})",
"assembly_id": "state.viewerInstance.visual.update({assembly_id:data.assembly_id})",
"visual_style": "self.rerender()",
}
def __init__(self, **params):
super().__init__(**params)
# skip = {
# 'theme',
# 'name',
# 'bg_color'
# }
# # Find class attributes defined only on this subclass
# attrs = self.__class__.__dict__.keys() - HTML.__class__.__dict__.keys() - skip
# attrs = [a for a in attrs if not a.startswith('_')]
# elements = []
# for attr in attrs:
# val = getattr(self, attr)
# if val is None:
# continue
# elif isinstance(val, bool):
# val = 'true' if val else 'false'
# else:
# val = f'"{val}"'
# name = attr.replace('_', '-')
# elem = f'{name}={val}'
# elements.append(elem)
# bg_default = 'F7F7F7' if self.theme == 'light' else '000000'
# self.bg_color = self.bg_color or bg_default
# # Split color options into rgb components
# color_options = ['bg_color', 'highlight_color', 'select_color']
# for option in color_options:
# hex_val = getattr(self, option)
# if hex_val is None:
# continue
# hex_val = hex_val.lstrip('#')
# name = option.replace('_', '-')
# for i, c in enumerate(['r', 'g', 'b']):
# val = int(hex_val[2*i:2*i+2], 16)
# elem = f'{name}-{c}="{val}"'
# elements.append(elem)
# component_spec = ' '.join(elements)
# theme = '-light' if self.theme == 'light' else ''
# html_string = (Path(__file__).parent / 'molstar_webcomponent.html').read_text()
# html_template = Template(html_string)
# self.component_spec = html_template.substitute(
# component_spec=component_spec,
# theme=theme
# )
# print(self.component_spec)
pn.extension(sizing_mode="stretch_width")
pdbe = PdbeMolStarWebComponent(
molecule_id='1qyn',
# custom_data= { "url": "https://www.ebi.ac.uk/pdbe/coordinates/1cbs/chains?entityId=1&asymId=A&encoding=bcif", "format": "cif", "binary": True },
# ligand_view={"label_comp_id": "REA"},
alphafold_view=False,
height=500,
# hide_water=True,
# theme='light',
# lighting='metallic',
# hide_expand_icon=True,
# highlight_color='#d1fa07',
# bg_color='#8ad6e6',
visual_style="cartoon" # , "ball-and-stick",
# highlight_color="blue",
)
pn.template.FastListTemplate(
site="Panel Chemistry", title="Pdbe Molstar Viewer",
sidebar=[pn.Param(pdbe)],
main=[pdbe],
).servable()
Ok sounds good. I've reached the same conclusion and started with the JS plugin. But I'm about at the same stage as you are. Thanks for letting me know, I was about to continue, but I'll hold off so we dont repeat the same effort.
@MarcSkovMadsen did you have any problems with param.Color
on a ReactiveHTML
?
This raises an error for me: https://github.com/holoviz/panel/issues/3058
I will close this one and create a new one based on the JS plugin
Current status of the PR adds the PBDe webcomponent implementation of the molstar viewer
The webcomponent works well although there are some bugs/confusion around setting of some boolean parameters: https://github.com/PDBeurope/pdbe-molstar/issues/44
I'm planning to further add the PDBe JS plugin as ReactiveHTML and perhaps also the Mol* viewer