Module:CineMol
| This module is rated as beta. It is considered ready for widespread use, but as it is still relatively new, it should be applied with some caution to ensure results are as expected. |
| This module uses TemplateStyles: |
| This module depends on the following other modules: |
Attempt to make a template that can render molecule descriptions. Adapted from CineMol.
It can use Wikidata's P3636 or P683 to decide what to render.
Example
[edit]As an example, the following produces a picture of folic acid using its Wikidata number.
{{CineMol|wikidata=Q127060|style=tube|ry=1.3}}
In most cases you probably want to use {{Image frame}}
{{Image frame|
|align=none
|style=width:250px
|caption=Caffine (wireframe mode)
|content=
{{CineMol
|style=wireframe
|look=cartoon
|pdb-ligand=CFF
|rx=1.57
|rz=1.9625
}}
}}
Usage
[edit]You can specify the molecule either by its pdb-ligand id, its wikidata Q number or the contents of a mol file. If unspecified will default to the Q number of the current page.
{{CineMol|wikidata=<Q id here>|...}}{{CineMol|pdb-ligand=<ligand id here>|...}}{{CineMol|mol=<SDF FILE HERE>|...}}
You can use the rx, ry, and rz parameters to adjust the viewpoint.
The style parameter determines the type of drawing. The choices are:
- Space filling
- Ball and stick
- Tube
- Wireframe
The look parameter determines the look of the drawing. There are two choices: Glossy and Cartoon.
By default hyrdogen atoms are not shown. To include them specify hydrogen=yes.
See template data for additional parameters.
Template data
[edit]Display diagrams of molecules based on a mol file or ligand id
| Parameter | Description | Type | Status | |
|---|---|---|---|---|
| style | style | Style of molecule drawing to do
| String | suggested |
| look | look | Look of rendering (Cartoon or glossy)
| String | suggested |
| resolution | resolution | Resolution of drawing (Todo: Clarify this)
| Number | optional |
| scale | scale | What scale to draw image at
| Number | optional |
| focal_length | focal_length | focal length of camera (This might not work properly) | Number | optional |
| rotation over x axis | rx | How much to rotate the x axis
| Number | optional |
| rotation over y axis | ry | How much to rotate the y axis
| Number | optional |
| rotation over z axis | rz | How much to rotate over the z axis
| Number | optional |
| hydrogen | hydrogen | Show hydrogen molecules
| Boolean | optional |
| lightbox | lightbox | Allow clicking to show a zoomed version of image.
| Boolean | optional |
| width | width | Width of images in pixels
| Number | suggested |
| height | height | Height in pixels. (Note: supplying both height and width will adjust the aspect ratio) | Number | optional |
| mol | mol | Contents of mol/sdf file to render | String | optional |
| pdb-ligand | pdb-ligand | The id of a compound in PDB's chemical component dictionary | String | optional |
| ChEBI | ChEBI | ChEBI id of molecule to render | String | optional |
| wikidata | wikidata | A Wikidata Q-ID to use the value of P3636 to render a molecule. Will default to the current page | String | optional |
| css | css | Extra CSS to apply to the image | String | optional |
| alt | alt | Alternate text for screen readers that can't view images | String | optional |
| class | class | CSS class to apply to image | String | optional |
| title | title | Tooltip to display on hover | String | optional |
| link | link | Either url or pagename to link image to | String | optional |
| lightbox_caption | lightbox_caption | Caption for when user clicks to zoom. If unspecified and using a wikidata or pdb-ligend source, a caption will be automatically generated. | Content | optional |
local p = {}
local compress = require( 'Module:libDeflate' )
require( 'strict' )
local m = require( 'Module:CineMol/api' )
local parse_sdf = require( 'Module:CineMol/parsers' ).parse_sdf
local getArgs = require( 'Module:Arguments' ).getArgs
local yesno = require( 'Module:Yesno' )
-- Stolen from Module:Cite_taxon
local b64decode = function(data)
local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
data = string.gsub(data, '[^'..b..'=]', '')
return (data:gsub('.', function(x)
if (x == '=') then return '' end
local r,f='',(b:find(x)-1)
for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end
return r;
end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x)
if (#x ~= 8) then return '' end
local c=0
for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end
return string.char(c)
end))
end
-- Keep in sync with Category:Tabular_data_of_the_Chemical_Component_Dictionary
local function getKey( id )
if string.sub( id, 1, 1 ) == 'A' then
local letter2 = #id <= 1 and 0 or string.byte( id, 2 )
local letter3 = #id <= 2 and 0 or string.byte( id, 3 )
if
letter2 < string.byte( '1' ) or
( string.sub( id, 1, 2 ) == 'A1' and letter3 < string.byte( 'B' ) )
then
return 'A'
elseif string.sub( id, 1, 2 ) == 'A1' and letter3 < string.byte( 'I' ) then
return 'A1B';
elseif string.sub( id, 1, 2 ) == 'A1' then
return 'A1I'
else
return 'A2'
end
else
return string.sub( id, 1, 1)
end
end
local function is2D( mol )
return string.match( mol, "^[^\n]*\n[^\n]*(2D)[\n ]" ) == '2D'
end
local function getMolFromCCD( id )
local data = mw.ext.data.get( string.format( "Chemical Component Dictionary/%s.tab", getKey(id) ) )
assert( data ~= false, 'Could not fetch CCD file from commons')
local ccd = data['data']
local molEncoded = ''
for i = 1,#ccd do
-- This assumes entries are in order.
-- if this becomes an efficiency issue, we could binary search it instead.
if ccd[i][1] == id then
molEncoded = molEncoded .. ccd[i][3]
end
end
assert( molEncoded ~= '', 'Could not find PDB-lignad id: ' .. id )
return compress:DecompressDeflate( b64decode( molEncoded ) )
end
local function getMolFromChEBI( id )
if string.upper(string.sub( id, 1, 6 )) == 'CHEBI:' then
id = string.sub( id, 7 )
end
local idNumb = tonumber( id )
local key
if idNumb < 160000 then
key = math.floor( idNumb / 2000 ) * 2000
elseif idNumb < 200000 then
key = math.floor( idNumb / 20000 ) * 20000
else
key = 200000
end
local data = mw.ext.data.get( string.format( "ChEBI/%d.tab", key ) )
assert( data ~= false, 'Could not fetch ChEBI data file from commons')
local dataset = data['data']
local molEncoded = ''
for i = 1,#dataset do
-- This assumes entries are in order.
-- if this becomes an efficiency issue, we could binary search it instead.
if dataset[i][1] == id then
molEncoded = molEncoded .. dataset[i][4]
end
end
assert( molEncoded ~= '', 'Could not find ChEBI: ' .. id )
return compress:DecompressDeflate( b64decode( molEncoded ) )
end
p.styleMap = {
spacefilling = m.Style.SPACEFILLING,
space_filling = m.Style.SPACEFILLING,
["space filling"] = m.Style.SPACEFILLING,
ballandstick = m.Style.BALL_AND_STICK,
ball_and_stick = m.Style.BALL_AND_STICK,
["ball and stick"] = m.Style.BALL_AND_STICK,
tube = m.Style.TUBE,
wireframe = m.Style.WIREFRAME
}
p.lookMap = {
cartoon = m.Look.CARTOON,
glossy = m.Look.GLOSSY
}
function p.draw_mol(frame)
local args = getArgs( frame )
return frame:extensionTag( "TemplateStyles", "", {src="Module:CineMol/styles.css"} )
.. p.draw_mol_internal(args)
end
-- Get automatic caption if using a ligand id
local function getAutoCaptionPdb(ligand)
local license = '<div class="cinemol-license-img" title="Public domain">[[File:Cc.logo.circle.svg|28px|class=skin-invert notpageimage|link=|Creative Commons]] [[File:Cc-zero.svg|28px|class=skin-invert notpageimage|link=https://creativecommons.org/publicdomain/zero/1.0/deed.en|CC-Zero]]</div>'
return license .. 'Rendered based on the entry in the [https://www.wwpdb.org/data/ccd Chemical Component Dictionary] for the molecule with id "[https://www.rcsb.org/ligand/' .. ligand .. ' ' .. ligand .. ']".'
end
local function getAutoCaptionChEBI(id)
local license = '<div class="cinemol-license-img" title="Creative Commons Attribution 4.0">[[File:Cc.logo.circle.svg|28px|class=skin-invert notpageimage|link=https://creativecommons.org/licenses/by/4.0/deed.en|Creative Commons]] [[File:Cc-by_new_white.svg|28px|class=skin-invert notpageimage|link=https://creativecommons.org/licenses/by/4.0/deed.en|CC-BY 4.0]]</div>'
return license .. 'Rendered based on the entry in the [[w:ChEBI|Chemical Entities of Biological Interest]] for the molecule with id "[https://www.ebi.ac.uk/chebi/searchId.do?chebiId=CHEBI:' .. id .. ' CHEBI:' .. id .. ']".'
end
function p.draw_mol_internal(args)
local style = args.style == nil and m.Style.SPACEFILLING or p.styleMap[string.lower(args.style)]
assert( style ~= nil, 'Unrecognized style' )
local look = args.look == nil and m.Look.GLOSSY or p.lookMap[string.lower(args.look)]
assert( look ~= nil, 'Unrecognized look' )
local resolution = args.resolution == nil and 30 or tonumber(args.resolution)
assert( type(resolution) == 'number', 'Resolution must be number' )
-- We can never zoom in so far that the molecule is out of frame, so best this default
-- a high number.
local scale = args.scale == nil and 5 or tonumber(args.scale)
assert( type(scale) == 'number', 'Scale must be number' )
local focal_length = args.focal_length ~= nil and tonumber(args.focal_length) or nil
local rx = args.rx == nil and 0 or tonumber(args.rx)
local ry = args.ry == nil and 0 or tonumber(args.ry)
local rz = args.rz == nil and 0 or tonumber(args.rz)
local useLightbox = yesno( args.lightbox, true ) or true
local hydrogen = yesno( args.include_hydrogen, false ) or false
local exclude = hydrogen and {} or {'H'}
local width = args.width
if args.height == nil and args.width == nil then
width = "250"
end
local internalLink = nil
local externalLink = nil
if args.link ~= nil then
if string.match( args.link, '^https?://' ) or string.sub( args.link, 1, 2 ) == '//' then
externalLink = args.link
useLightbox = false
else
internalLink = args.link
useLightbox = false
end
end
local lightboxCaption = args.lightbox_caption
local mol
if args.mol ~= nil and args.mol ~= '' then
mol = args.mol
elseif args['pdb-ligand'] ~= nil and args['pdb-ligand'] ~= '' then
mol = getMolFromCCD( args['pdb-ligand'] )
lightboxCaption = lightboxCaption == nil and getAutoCaptionPdb( args['pdb-ligand'] ) or lightboxCaption
elseif args.ChEBI ~= nil and args.ChEBI ~= '' then
mol = getMolFromChEBI( args.ChEBI )
lightboxCaption = lightboxCaption == nil and getAutoCaptionChEBI( args.ChEBI ) or lightboxCaption
else
local wd = args.wikidata == nil and mw.wikibase.getEntityIdForCurrentPage() or args.wikidata
if wd ~= nil then
local stmt = mw.wikibase.getBestStatements( wd, 'P3636' )
if stmt and stmt[1] and stmt[1].mainsnak then
local ccd = stmt[1].mainsnak.datavalue.value
mol = getMolFromCCD( ccd )
lightboxCaption = lightboxCaption == nil and getAutoCaptionPdb( ccd ) or lightboxCaption
else
stmt = mw.wikibase.getBestStatements( wd, 'P683' )
if stmt and stmt[1] and stmt[1].mainsnak then
local ChEBI = stmt[1].mainsnak.datavalue.value
mol = getMolFromChEBI( ChEBI )
lightboxCaption = lightboxCaption == nil and getAutoCaptionChEBI( ChEBI ) or lightboxCaption
end
end
end
end
assert( mol ~= nil and mol ~= '', 'Either mol, pdf-ligand, ChEBI or wikidata argument is required or the current page has to be connected to a wikidata item with P3636' )
local style = args.style == nil and ( is2D(mol) and m.Style.WIREFRAME or m.Style.SPACEFILLING ) or p.styleMap[string.lower(args.style)]
assert( style ~= nil, 'Unrecognized style' )
-- Actual template
local atoms, bonds = parse_sdf( mol )
local molSvg = m.draw_molecule( atoms, bonds, style, look, resolution, nil, nil, rx, ry, rz, scale, focal_length, exclude )
local mw_svg = molSvg
:to_mw_svg()
if ( width ) then
mw_svg:setImgAttribute( 'width', tostring( width ) )
end
if ( args.css ) then
mw_svg:setImgAttribute( 'style', tostring( args.css ) )
end
if ( args.alt ) then
mw_svg:setImgAttribute( 'alt', tostring( args.alt ) )
end
if ( args.class ) then
end
if ( args.height ) then
mw_svg:setImgAttribute( 'height', tostring( args.height ) )
end
if ( args.title ) then
mw_svg:setImgAttribute( 'title', tostring( args.title ) )
end
local class = 'cinemol-img'
if args.class then
class = class .. ' ' .. args.class
end
mw_svg:setImgAttribute( 'class', class )
local start = '<div class="plainlinks cinemol-container calculator-container">'
local tail = '</div>'
if useLightbox then
start = start .. '<div class="calculator-field cinemol-inner-container" data-calculator-type="passthru" id="calculator-field-lightbox">'
tail = '</div>' .. tail
end
if lightboxCaption and useLightbox then
-- potentially should have an aria-described-by pointing to it.
tail = '<div class="cinemol-lightbox-caption">' .. lightboxCaption .. '</div>' .. tail
end
if useLightbox then
start = start .. '<div class="cinemol-lightbox-controls">' .. mw.getCurrentFrame():preprocess('{{calculator button|contents=×|style=appearance:none|alt=Exit molecule lightbox view|title=Exit molecule lightbox view|for=lightbox|formula=0|type=plain}}') .. '</div>'
-- For now you can only open by click not by tab. It is unclear if there is any value for a screen reader
-- to zoom in on an image.
start = start .. '<div class="cinemol-inner calculator-field-buttonraw" data-calculator-for="lightbox" data-calculator-formula="1">'
tail = '</div>' .. tail
end
if internalLink then
start = start .. '[[' .. internalLink .. '|'
tail = ']]' .. tail
elseif externalLink then
start = start .. '[' .. externalLink .. ' '
tail = ']' .. tail
end
return start .. mw_svg:toImage() .. tail
end
-- Demo directly using the api
function p.spacefilling(frame)
local width = frame.args.width == nil and '250' or frame.args.width
local svg = m.draw_molecule(
{
m.Atom(0, "C", {0, 0, 0}),
m.Atom(1, "H", {1, 0, 0}),
m.Atom(2, "H", {0, 1, 0}),
m.Atom(3, "H", {0, 0, 1})
},
{
m.Bond(0, 1, 1),
m.Bond(0, 2, 1),
m.Bond(0, 3, 1),
},
m.Style.SPACEFILLING,
m.Look.GLOSSY,
50
)
return svg:to_mw_svg():setImgAttribute( 'width', tostring(width) ):toImage()
end
return p