1414 cursor : pointer; background : # 2563eb ; color : white; }
1515 button : hover { background : # 1d4ed8 ; }
1616 # theme-toggle { background : # 555 ; }
17- .grid { display : flex; flex-wrap : wrap; gap : 12px ; }
18- .mol-card { background : white; border-radius : 6px ; padding : 8px ; box-shadow : 0 1px 3px rgba (0 , 0 , 0 , 0.1 ); }
17+ .section-title { font-size : 14px ; font-weight : 600 ; color : # 374151 ; margin : 20px 0 8px 0 ;
18+ border-bottom : 1px solid # ccc ; padding-bottom : 4px ; width : 100% ; }
19+ .section-title : first-child { margin-top : 0 ; }
20+ .grid { display : flex; flex-wrap : wrap; gap : 10px ; }
21+ .mol-card { background : white; border-radius : 6px ; padding : 6px ; box-shadow : 0 1px 3px rgba (0 , 0 , 0 , 0.1 );
22+ cursor : pointer; transition : box-shadow 0.15s ; }
23+ .mol-card : hover { box-shadow : 0 2px 8px rgba (0 , 0 , 0 , 0.2 ); }
1924 .mol-card svg { display : block; }
20- .mol-label { font-size : 11 px ; color : # 666 ; font-family : monospace; margin-top : 4 px ;
21- max-width : 300 px ; overflow : hidden; text-overflow : ellipsis; white-space : nowrap; }
25+ .mol-label { font-size : 10 px ; color : # 666 ; font-family : monospace; margin-top : 3 px ;
26+ max-width : 250 px ; overflow : hidden; text-overflow : ellipsis; white-space : nowrap; }
2227 .error { color : # dc2626 ; font-size : 12px ; margin-top : 4px ; }
23- # presets { margin-bottom : 16px ; display : flex; flex-wrap : wrap; gap : 6px ; }
24- .preset { padding : 4px 10px ; font-size : 12px ; background : # e5e7eb ; border : none;
25- border-radius : 12px ; cursor : pointer; }
26- .preset : hover { background : # d1d5db ; }
28+ # detail-overlay { display : none; position : fixed; top : 0 ; left : 0 ; right : 0 ; bottom : 0 ;
29+ background : rgba (0 , 0 , 0 , 0.5 ); z-index : 100 ; justify-content : center; align-items : center; }
30+ # detail-overlay .visible { display : flex; }
31+ # detail-card { background : white; border-radius : 8px ; padding : 20px ; max-width : 90vw ; max-height : 90vh ; }
32+ # detail-card svg { display : block; margin : 0 auto; }
33+ # detail-smiles { font-family : monospace; font-size : 13px ; color : # 555 ; margin-top : 10px ;
34+ word-break : break-all; max-width : 600px ; text-align : center; }
2735 </ style >
2836</ head >
2937< body >
3038 < h1 > SmilesDrawer Playground</ h1 >
3139 < div class ="controls ">
32- < input id ="smiles-input " type ="text " placeholder ="Enter SMILES string... " value =" CC1C(=O)NCCN1C(=O)C1(CNC(=O)[C@@H]2[C@H]3CCOC[C@H]32)CC=CC1 " >
33- < button onclick ="drawSmiles () "> Draw</ button >
40+ < input id ="smiles-input " type ="text " placeholder ="Enter SMILES string... ">
41+ < button onclick ="drawFromInput () "> Draw</ button >
3442 < button id ="theme-toggle " onclick ="toggleTheme() "> Theme: light</ button >
35- < button onclick ="drawAll() "> Draw All Presets</ button >
3643 </ div >
37- < div id ="presets "> </ div >
3844 < div id ="output " class ="grid "> </ div >
3945
46+ < div id ="detail-overlay " onclick ="closeDetail(event) ">
47+ < div id ="detail-card ">
48+ < div id ="detail-svg "> </ div >
49+ < div id ="detail-smiles "> </ div >
50+ </ div >
51+ </ div >
52+
4053 < script src ="../dist/smiles-drawer.js "> </ script >
4154 < script >
4255 let currentTheme = 'light' ;
4356
4457 const presets = {
45- // The molecule from the bug report
46- 'oxanorbornane (bug)' : 'CC1C(=O)NCCN1C(=O)C1(CNC(=O)[C@@H]2[C@H]3CCOC[C@H]32)CC=CC1' ,
47- // Simple molecules
48- 'methanol' : 'CO' ,
49- 'ethane' : 'CC' ,
50- 'acetic acid' : 'CC(=O)O' ,
51- // Rings
52- 'benzene' : 'c1ccccc1' ,
53- 'naphthalene' : 'c1ccc2ccccc2c1' ,
54- 'cyclohexane' : 'C1CCCCC1' ,
55- // Bridged rings
56- 'norbornane' : 'C1CC2CC1CC2' ,
57- 'camphor' : 'CC1(C)C2CCC1(C)C(=O)C2' ,
58- // Fused rings
59- 'indole' : 'c1ccc2[nH]ccc2c1' ,
60- 'anthracene' : 'c1ccc2cc3ccccc3cc2c1' ,
61- // Drugs
62- 'aspirin' : 'CC(=O)Oc1ccccc1C(=O)O' ,
63- 'caffeine' : 'Cn1cnc2c1c(=O)n(c(=O)n2C)C' ,
64- 'ibuprofen' : 'CC(C)Cc1ccc(cc1)C(C)C(=O)O' ,
65- 'nicotine' : 'CN1CCC[C@H]1c1cccnc1' ,
66- 'penicillin G' : 'CC1([C@@H](N2[C@H](S1)[C@@H](C2=O)NC(=O)Cc1ccccc1)C(=O)O)C' ,
67- // Issue molecules
68- '#162 polyphenyl' : 'C(C1=CC=CC=C1)1=CC=C(C2C=CC(C3C=CC(C4C=CC(C5C=CC=CC=5)=CC=4)=CC=3)=CC=2)C=C1' ,
69- '#209 pseudo charge' : 'CCS(=O)(=O)[O-]' ,
70- '#217 stereo' : 'C/C(=C\\C(=O)OC)/C1=CC=C(C=C1)C(C)(F)F' ,
71- '#188 fused rings' : 'O(C(c1cccc2c1cccc2)c3cccc4ccccc34)C(C(C)Sc5ccc(cc5)Br)=O' ,
72- // Charged / salts
73- 'NaCl' : '[Na+].[Cl-]' ,
74- '[NH4]+' : '[NH4+]' ,
75- // Stereo
76- 'L-alanine' : 'N[C@@H](C)C(=O)O' ,
77- 'cholesterol' : 'C([C@@H]1CC2=CC(O)CC[C@@]2(C)[C@H]1[C@H]1CC[C@@]2([C@@H]1CC1=C)[C@H](C)CC[C@H]2C)C' ,
78- // Nucleotides / complex stereo
79- 'NAD+' : 'O=C(N)c1ccc[n+](c1)[C@H]2[C@H](O)[C@H](O)[C@H](O2)COP([O-])(=O)OP(=O)(O)OC[C@H]3O[C@@H](n4cnc5c4ncnc5N)[C@@H]([C@@H]3O)OP(=O)(O)O' ,
80- 'adenosine' : 'n2c1c(ncnc1n(c2)[C@@H]3O[C@@H]([C@@H](O)[C@H]3O)CO)N' ,
81- 'AZT-like' : 'O=C1NC(C(C)=CN1[C@@H]2O[C@H](CO)[C@@H](N=[N+]=[N-])C2)=O' ,
82- // Complex
83- 'morphine' : 'CN1CC[C@]23[C@@H]4[C@H]1CC5=C2C(=C(C=C5)O)O[C@H]3[C@H](C=C4)O' ,
84- 'glucose' : 'OC[C@H]1OC(O)[C@H](O)[C@@H](O)[C@@H]1O' ,
85- 'vancomycin partial' : 'CC(O)C(NC(=O)C1CC(=O)N1)C(=O)NCC(=O)O' ,
58+ 'Simple' : {
59+ 'methanol' : 'CO' ,
60+ 'ethane' : 'CC' ,
61+ 'acetic acid' : 'CC(=O)O' ,
62+ 'benzene' : 'c1ccccc1' ,
63+ 'cyclohexane' : 'C1CCCCC1' ,
64+ 'cyclopropane' : 'C1CC1' ,
65+ 'ethene' : 'C=C' ,
66+ 'ethyne' : 'C#C' ,
67+ } ,
68+ 'E/Z double bonds' : {
69+ 'trans-difluoroethene (E)' : 'F/C=C/F' ,
70+ 'cis-difluoroethene (Z)' : 'F/C=C\\F' ,
71+ 'trans-dichloroethene (E)' : 'Cl/C=C/Cl' ,
72+ 'cis-dichloroethene (Z)' : 'Cl/C=C\\Cl' ,
73+ 'trans-2-butene (E)' : 'C/C=C/C' ,
74+ 'cis-2-butene (Z)' : 'C/C=C\\C' ,
75+ '#217 branched E/Z' : 'C/C(=C\\C(=O)OC)/C1=CC=C(C=C1)C(C)(F)F' ,
76+ 'ring-connected E/Z' : 'C/C=C/1\\CCC[C@@]2(C1CC2)C' ,
77+ 'CID 153456993' : 'C1=CC(=CC=C1CS)/C=C\\2/C(=O)N(C(=O)S2)CC(=O)O' ,
78+ 'CID 102485436' : 'CN(C)CCCN/C(=C\\C(=O)C1=CC=CC=C1)/C2=CC=CC=C2' ,
79+ 'CID 173497121' : 'CC(/C(=C(/C(C)OC1=CC=C(C=C1)O)\\O)/C(O)OC)O' ,
80+ 'tamoxifen' : 'CCC(=C(C1=CC=CC=C1)C2=CC=C(C=C2)OCCN(C)C)C3=CC=CC=C3' ,
81+ } ,
82+ 'Stereo R/S' : {
83+ 'L-alanine' : 'N[C@@H](C)C(=O)O' ,
84+ 'D-alanine' : 'N[C@H](C)C(=O)O' ,
85+ 'nicotine' : 'CN1CCC[C@H]1c1cccnc1' ,
86+ 'penicillin G' : 'CC1([C@@H](N2[C@H](S1)[C@@H](C2=O)NC(=O)Cc1ccccc1)C(=O)O)C' ,
87+ 'glucose' : 'OC[C@H]1OC(O)[C@H](O)[C@@H](O)[C@@H]1O' ,
88+ 'cholesterol' : 'C([C@@H]1CC2=CC(O)CC[C@@]2(C)[C@H]1[C@H]1CC[C@@]2([C@@H]1CC1=C)[C@H](C)CC[C@H]2C)C' ,
89+ } ,
90+ 'Bridged rings' : {
91+ 'norbornane' : 'C1CC2CC1CC2' ,
92+ 'norbornane variant' : 'C1CC2CCC1C2' ,
93+ 'camphor' : 'CC1(C)C2CCC1(C)C(=O)C2' ,
94+ 'adamantane' : 'C1C2CC3CC1CC(C2)C3' ,
95+ 'alpha-pinene' : 'CC1=CCC2CC1C2(C)C' ,
96+ } ,
97+ 'Fused rings' : {
98+ 'naphthalene' : 'c1ccc2ccccc2c1' ,
99+ 'anthracene' : 'c1ccc2cc3ccccc3cc2c1' ,
100+ 'indole' : 'c1ccc2[nH]ccc2c1' ,
101+ 'quinoline' : 'c1ccc2ncccc2c1' ,
102+ 'phenanthrene' : 'c1ccc2c(c1)ccc1ccccc12' ,
103+ 'fluorene' : 'c1ccc2c(c1)Cc1ccccc1-2' ,
104+ '#188 polycyclic' : 'O(C(c1cccc2c1cccc2)c3cccc4ccccc34)C(C(C)Sc5ccc(cc5)Br)=O' ,
105+ } ,
106+ 'Drugs' : {
107+ 'aspirin' : 'CC(=O)Oc1ccccc1C(=O)O' ,
108+ 'caffeine' : 'Cn1cnc2c1c(=O)n(c(=O)n2C)C' ,
109+ 'ibuprofen' : 'CC(C)Cc1ccc(cc1)C(C)C(=O)O' ,
110+ 'morphine' : 'CN1CC[C@]23[C@@H]4[C@H]1CC5=C2C(=C(C=C5)O)O[C@H]3[C@H](C=C4)O' ,
111+ 'codeine' : 'COC1=CC2=C(C=C1)[C@@H]3CC=C[C@H]4[C@@H]5N(CC[C@]34[C@@H]2O5)C' ,
112+ 'taxol' : 'CC1=C2[C@@]([C@]([C@H]([C@@H]3[C@]4([C@H](OC4)C[C@@H]([C@]3(C(=O)[C@@H]2OC(=O)C)C)O)OC(=O)C)OC(=O)c5ccccc5)(C[C@@H]1OC(=O)[C@@H](O)[C@@H](NC(=O)c6ccccc6)c7ccccc7)O)(C)C' ,
113+ 'simvastatin' : 'CCC(C)(C)C(=O)O[C@H]1C[C@@H](O)C=C2C=C[C@H](C)[C@H](CC[C@@H](O)CC(=O)O)[C@@H]12' ,
114+ 'lovastatin' : 'CC[C@H](C)C(=O)O[C@H]1C[C@@H](O)C=C2C=C[C@H](C)[C@H](CC[C@@H](O)CC(=O)O)[C@@H]12' ,
115+ 'warfarin' : 'CC(=O)CC(C1=CC=CC=C1)C2=C(O)C3=CC=CC=C3OC2=O' ,
116+ 'metformin' : 'CN(C)C(=N)NC(=N)N' ,
117+ 'sildenafil' : 'CCCC1=NN(C)C2=C1NC(=NC2=O)C1=CC(=CC=C1OCC)S(=O)(=O)N1CCN(C)CC1' ,
118+ 'omeprazole' : 'CC1=CN=C(C(=C1OC)C)CS(=O)C2=NC3=CC=CC=C3N2' ,
119+ } ,
120+ 'Charged / salts' : {
121+ 'NaCl' : '[Na+].[Cl-]' ,
122+ '[NH4]+' : '[NH4+]' ,
123+ '#209 pseudo charge' : 'CCS(=O)(=O)[O-]' ,
124+ 'glycine zwitterion' : '[NH3+]CC([O-])=O' ,
125+ 'calcium acetate' : '[Ca+2].[O-]C(=O)C.[O-]C(=O)C' ,
126+ } ,
127+ 'Heterocycles' : {
128+ 'pyridine' : 'c1ccncc1' ,
129+ 'pyrimidine' : 'c1ccnc(n1)' ,
130+ 'thiophene' : 'c1ccsc1' ,
131+ 'furan' : 'c1ccoc1' ,
132+ 'imidazole' : 'c1cnc[nH]1' ,
133+ 'purine' : 'c1ncc2[nH]cnc2n1' ,
134+ } ,
135+ 'Drug-like (from test set)' : {
136+ 'thiazoline-A' : 'CC1=CC(C)=CC(N=C2SC=C(C3=CC=C(Br)O3)N2CC2CN(S(C)(=O)=O)C2)=C1' ,
137+ 'thiazoline-B' : 'COC(=O)C1=CC(C2=CSC(=NC3=C(C)C=CC=C3C)N2C[C@H](C)OC)=C(C)O1' ,
138+ 'thiazoline-C' : 'CC1=CC=CC(C)=C1N=C1SC=C(C2=CC=NN2C)N1CCC2COC(C)(C)O2' ,
139+ 'thiazoline-D' : 'O=C1NC[C@H](CN2C(C3=CC=C([N+](=O)[O-])O3)=CSC2=NC2=CC=CC=C2F)O1' ,
140+ 'thiazoline-E' : 'COC1=CC=C(C2=CSC(=NC3=CC=CC=C3F)N2C[C@H]2CCC(=O)N2)C=C1O' ,
141+ 'thiazoline-F' : 'CN1C=C(C2=CSC(=NC3=CC=C(F)C(Cl)=C3)N2CC[C@H]2C[C@H]3C=C[C@@H]2C3)N=N1' ,
142+ } ,
143+ 'Complex / stress test' : {
144+ '#162 polyphenyl' : 'C(C1=CC=CC=C1)1=CC=C(C2C=CC(C3C=CC(C4C=CC(C5C=CC=CC=5)=CC=4)=CC=3)=CC=2)C=C1' ,
145+ 'NAD+' : 'O=C(N)c1ccc[n+](c1)[C@H]2[C@H](O)[C@H](O)[C@H](O2)COP([O-])(=O)OP(=O)(O)OC[C@H]3O[C@@H](n4cnc5c4ncnc5N)[C@@H]([C@@H]3O)OP(=O)(O)O' ,
146+ 'adenosine' : 'n2c1c(ncnc1n(c2)[C@@H]3O[C@@H]([C@@H](O)[C@H]3O)CO)N' ,
147+ 'vancomycin partial' : 'CC(O)C(NC(=O)C1CC(=O)N1)C(=O)NCC(=O)O' ,
148+ 'oxanorbornane (bug)' : 'CC1C(=O)NCCN1C(=O)C1(CNC(=O)[C@@H]2[C@H]3CCOC[C@H]32)CC=CC1' ,
149+ 'AZT-like' : 'O=C1NC(C(C)=CN1[C@@H]2O[C@H](CO)[C@@H](N=[N+]=[N-])C2)=O' ,
150+ } ,
86151 } ;
87152
88- // Build preset buttons
89- const presetsDiv = document . getElementById ( 'presets' ) ;
90- for ( const [ name , smiles ] of Object . entries ( presets ) ) {
91- const btn = document . createElement ( 'button' ) ;
92- btn . className = 'preset' ;
93- btn . textContent = name ;
94- btn . onclick = ( ) => {
95- document . getElementById ( 'smiles-input' ) . value = smiles ;
96- drawSmiles ( ) ;
97- } ;
98- presetsDiv . appendChild ( btn ) ;
99- }
100-
101153 function toggleTheme ( ) {
102154 currentTheme = currentTheme === 'light' ? 'dark' : 'light' ;
103155 document . getElementById ( 'theme-toggle' ) . textContent = 'Theme: ' + currentTheme ;
104- if ( currentTheme === 'dark' ) {
105- document . body . style . background = '#1a1a1a' ;
106- document . body . style . color = '#eee' ;
107- } else {
108- document . body . style . background = '#f5f5f5' ;
109- document . body . style . color = '#000' ;
110- }
156+ document . body . style . background = currentTheme === 'dark' ? '#1a1a1a' : '#f5f5f5' ;
157+ document . body . style . color = currentTheme === 'dark' ? '#eee' : '#000' ;
158+ drawAll ( ) ;
111159 }
112160
113- function drawSingle ( smiles , label ) {
161+ function drawMolecule ( smiles , label , size ) {
114162 const card = document . createElement ( 'div' ) ;
115163 card . className = 'mol-card' ;
116164 if ( currentTheme === 'dark' ) card . style . background = '#2a2a2a' ;
117165
118166 try {
119- const drawer = new SmilesDrawer . SmiDrawer ( { width : 300 , height : 300 } ) ;
167+ const drawer = new SmilesDrawer . SmiDrawer ( { width : size , height : size } ) ;
120168 const svgEl = document . createElementNS ( 'http://www.w3.org/2000/svg' , 'svg' ) ;
121- svgEl . setAttribute ( 'width' , '300' ) ;
122- svgEl . setAttribute ( 'height' , '300' ) ;
169+ svgEl . setAttribute ( 'width' , size ) ;
170+ svgEl . setAttribute ( 'height' , size ) ;
123171 card . appendChild ( svgEl ) ;
124172 drawer . draw ( smiles , svgEl , currentTheme ) ;
125173 } catch ( e ) {
@@ -135,27 +183,75 @@ <h1>SmilesDrawer Playground</h1>
135183 lbl . title = smiles ;
136184 card . appendChild ( lbl ) ;
137185
186+ card . onclick = ( ) => showDetail ( smiles , label ) ;
138187 return card ;
139188 }
140189
141- function drawSmiles ( ) {
190+ function showDetail ( smiles , label ) {
191+ const container = document . getElementById ( 'detail-svg' ) ;
192+ const smilesLabel = document . getElementById ( 'detail-smiles' ) ;
193+ const card = document . getElementById ( 'detail-card' ) ;
194+ container . innerHTML = '' ;
195+
196+ if ( currentTheme === 'dark' ) {
197+ card . style . background = '#2a2a2a' ;
198+ card . style . color = '#eee' ;
199+ } else {
200+ card . style . background = 'white' ;
201+ card . style . color = '#000' ;
202+ }
203+
204+ try {
205+ const drawer = new SmilesDrawer . SmiDrawer ( { width : 600 , height : 600 } ) ;
206+ const svgEl = document . createElementNS ( 'http://www.w3.org/2000/svg' , 'svg' ) ;
207+ svgEl . setAttribute ( 'width' , '600' ) ;
208+ svgEl . setAttribute ( 'height' , '600' ) ;
209+ container . appendChild ( svgEl ) ;
210+ drawer . draw ( smiles , svgEl , currentTheme ) ;
211+ } catch ( e ) {
212+ container . innerHTML = '<div class="error">Error: ' + e . message + '</div>' ;
213+ }
214+
215+ smilesLabel . textContent = ( label ? label + ': ' : '' ) + smiles ;
216+ document . getElementById ( 'detail-overlay' ) . classList . add ( 'visible' ) ;
217+ }
218+
219+ function closeDetail ( event ) {
220+ if ( event . target === document . getElementById ( 'detail-overlay' ) ) {
221+ document . getElementById ( 'detail-overlay' ) . classList . remove ( 'visible' ) ;
222+ }
223+ }
224+
225+ document . addEventListener ( 'keydown' , ( e ) => {
226+ if ( e . key === 'Escape' ) {
227+ document . getElementById ( 'detail-overlay' ) . classList . remove ( 'visible' ) ;
228+ }
229+ } ) ;
230+
231+ function drawFromInput ( ) {
142232 const smiles = document . getElementById ( 'smiles-input' ) . value . trim ( ) ;
143233 if ( ! smiles ) return ;
144234 const output = document . getElementById ( 'output' ) ;
145235 output . innerHTML = '' ;
146- output . appendChild ( drawSingle ( smiles , smiles ) ) ;
236+ output . appendChild ( drawMolecule ( smiles , smiles , 350 ) ) ;
147237 }
148238
149239 function drawAll ( ) {
150240 const output = document . getElementById ( 'output' ) ;
151241 output . innerHTML = '' ;
152- for ( const [ name , smiles ] of Object . entries ( presets ) ) {
153- output . appendChild ( drawSingle ( smiles , name + ': ' + smiles ) ) ;
242+ for ( const [ category , molecules ] of Object . entries ( presets ) ) {
243+ const title = document . createElement ( 'div' ) ;
244+ title . className = 'section-title' ;
245+ title . textContent = category ;
246+ output . appendChild ( title ) ;
247+ for ( const [ name , smiles ] of Object . entries ( molecules ) ) {
248+ output . appendChild ( drawMolecule ( smiles , name , 250 ) ) ;
249+ }
154250 }
155251 }
156252
157- // Draw initial molecule
158- drawSmiles ( ) ;
253+ // Draw everything on load
254+ drawAll ( ) ;
159255 </ script >
160256</ body >
161257</ html >
0 commit comments