1 //TODO: write up mock tests
  2 
  3 /**
  4  * This is the main function
  5  * @module NaView
  6  * @namespace
  7  * @param {string} svg_id id of selected/created svg
  8  * @param {string} container_id id of selected/created svg container
  9  * @param {number} svg_width width of selected/created svg (default:1200px)
 10  * @param {number} svg_height height of selected/created svg (default:500px)
 11  * @param {Object} style_obj_input styling object for all drawing options. See <b><i>style_obj</i></b> in Docs
 12  * @param {string} protein_input UniProt raw text to be processed See <b><i>parsed_protein_data</i></b> and also <b><i>processRawUniProt</i></b> in Docs
 13  * @also
 14  * @param {Object} protein_input processed data input See <b><i>parsed_protein_data</i></b> and also <b><i>processRawUniProt</i></b> in Docs
 15  * @param {Object} properties residue properties for color mapping and Text drawing by residue selection. See <b><i>current_resid_properties</i></b> in Docs
 16  * @param {Array} color_rules Array of strings containing color rules in the color selection syntax. See <b><i>fillRules</i></b> and also <b><i>createFillRules</i></b> in Docs
 17  * @param {Array} text_to_draw Array of strings containing text drawing rules in the text selection syntax. See <b><i>draw_symbols</i></b> in Docs
 18  * @param {Array} relationships Array of Objects describing residue relationships to be drawn in plot See <b><i>draw_residue_relations</i></b> in Docs
 19  */
 20 function NaView({
 21     svg_id="naview_svg",
 22     container_id="naview_container",
 23     svg_width=1200,
 24     svg_height=500,
 25     //TODO: validate functions for all inputs
 26     style_obj_input,
 27     protein_input,
 28     //TODO: convert properties function 
 29     properties={},
 30     color_rules=[],
 31     text_to_draw=[],
 32     relationships=[],
 33     }={}) {
 34 
 35     /**
 36      * Function to generate the default style object.<br>
 37      * 1. Helices are shown as cartoon<br>
 38      * 2. Membrane is shown as lipid bilayer<br>
 39      * 3. All loops have fixed lengths<br>
 40      * @yields default style object
 41      * @exports NaView
 42      * @name generateDefaultStyleObject
 43      * @namespace
 44      */
 45     function generateDefaultStyleObject() {
 46         let defaultStyleObject = {
 47             "membrane": {
 48                 "membrane_mode": "lipid",
 49                 "membrane_draw_opts": {
 50                     "hfill": "white",
 51                     "hstroke": "black",
 52                     "hstroke_s": 2, //px
 53                     "hopacity": 0.6,
 54                     "tfill": "black",
 55                     "tstroke": "black",
 56                     "tstroke_s": 2, //px
 57                     "topacity": 0.6,
 58                     "lipid_head_radius_width": 0.0075, //% of viewbox width
 59                     "lipid_head_radius_height": 0.1, //% of viewbox height
 60                     "lipid_tail_number": 2,
 61                     "lipid_tail_breaks": 1,
 62                     "lipid_tail_spacing": 0.001, //% of viewbox height
 63                 },
 64                 "membrane_region_height": 0.4, //% of viewbox
 65             },
 66             "protein": {
 67                 "helix_mode": "cartoon",
 68                 "helix_draw_opts": {
 69                     "draw": { //example cartoon
 70                         "x_to_end_prop": 1/20,
 71                         "y_to_mid_prop": 1/7,
 72                         "aa_area_perc_displacement": 0.1,
 73                         "thickness": 0.55,
 74                         "front_helix_stroke":'black',
 75                         "back_helix_stroke": 'black',
 76                         "stroke_size": 1.5, //px
 77                     },
 78                     "type": "dicts",
 79                     "fill": {
 80                         "type":"domain_and_name",
 81                         "I": { 1:"red",2:"red",3:"red",4:"blue",5:"red",6:"red"},
 82                         "II": { 1:"red",2:"red",3:"red",4:"blue",5:"red",6:"red"},
 83                         "III": { 1:"red",2:"red",3:"red",4:"blue",5:"red",6:"red"},
 84                         "IV": { 1:"red",2:"red",3:"red",4:"blue",5:"red",6:"red"},
 85                     },
 86                     "back_fill": {
 87                         "type":"domain_and_name",
 88                         "I": { 1:"salmon",2:"salmon",3:"salmon",4:"lightblue",5:"salmon",6:"salmon"},
 89                         "II": { 1:"salmon",2:"salmon",3:"salmon",4:"lightblue",5:"salmon",6:"salmon"},
 90                         "III": { 1:"salmon",2:"salmon",3:"salmon",4:"lightblue",5:"salmon",6:"salmon"},
 91                         "IV": { 1:"salmon",2:"salmon",3:"salmon",4:"lightblue",5:"salmon",6:"salmon"},
 92                     },
 93                     "stroke": {
 94                         "type":"domain_and_name",
 95                         "I": { 1:"red",2:"red",3:"red",4:"blue",5:"red",6:"red"},
 96                         "II": { 1:"red",2:"red",3:"red",4:"blue",5:"red",6:"red"},
 97                         "III": { 1:"red",2:"red",3:"red",4:"blue",5:"red",6:"red"},
 98                         "IV": { 1:"red",2:"red",3:"red",4:"blue",5:"red",6:"red"},
 99                     },
100                     "stroke_size": {
101                         "type":"domain_and_name",
102                         "I": { 1:2, 2:2, 3:2, 4:3, 5:2, 6:2},
103                         "II": { 1:2, 2:2, 3:2, 4:3, 5:2, 6:2},
104                         "III": { 1:2, 2:2, 3:2, 4:3, 5:2, 6:2},
105                         "IV": { 1:2, 2:2, 3:2, 4:3, 5:2, 6:2},
106                     },
107                     "opacity": {
108                         "type":"domain_and_name",
109                         "I": { 1:1, 2:1, 3:1, 4:1, 5:1, 6:1},
110                         "II": { 1:1, 2:1, 3:1, 4:1, 5:1, 6:1},
111                         "III": { 1:1, 2:1, 3:1, 4:1, 5:1, 6:1},
112                         "IV": { 1:1, 2:1, 3:1, 4:1, 5:1, 6:1},
113                     },
114                 },
115                 "helix_region_width" : {
116                     "type": "domain_and_name", //pre defined widths
117                     "I": [0.02,0.02,0.02,0.02,0.02,0.02], //% of viewbox, example cartoon
118                     "II": [0.02,0.02,0.02,0.02,0.02,0.02], //% of viewbox
119                     "III": [0.02,0.02,0.02,0.02,0.02,0.02], //% of viewbox
120                     "IV": [0.02,0.02,0.02,0.02,0.02,0.02], //% of viewbox
121                 },
122                 "helix_region_height" : {
123                     "I": [0.4,0.4,0.4,0.4,0.4,0.4], //% of viewbox
124                     "II": [0.4,0.4,0.4,0.4,0.4,0.4], //% of viewbox
125                     "III": [0.4,0.4,0.4,0.4,0.4,0.4], //% of viewbox
126                     "IV": [0.4,0.4,0.4,0.4,0.4,0.4], //% of viewbox
127                 },
128                 "helix_spacing_width" : {
129                     "I": [5,5,5,5,5,5], // px
130                     "II": [5,5,5,5,5,5], // px
131                     "III": [5,5,5,5,5,5], // px
132                     "IV": [5,5,5,5,5,5], // px
133                 },
134                 "short_loops_draw_opts": {
135                     "type": "single",
136                     "stroke": "black",
137                     "fill": "none",
138                     "opacity": 1,
139                     "stroke_size": "3px",
140                     "calc_len": {
141                         "type": "reslen", // fixed, custom, scaled, reslen
142                         "calc": {
143                             "length": 2 //in pixels
144                         },
145                     },
146                     "shape": {
147                         "type": "simple", // simple, bulb, mushroom, mushroom_skew
148                         "calc": {
149                             "y_step": 0.5, //step used in path searching option in px, ignored in fixed, custom
150                         },
151                     }
152                 },
153                 "pore_loops_draw_opts": {
154                     "type": "single",
155                     "stroke": "black",
156                     "fill": "none",
157                     "opacity": 1,
158                     // "stroke_size": "1.5px",
159                     "stroke_size": "3px",
160                     "calc_len": {
161                         "type": "reslen", // fixed, scaled, reslen
162                         "calc": {
163                             "length": 2 //in pixels
164                         },
165                     },
166                     "shape": {
167                         "type": "simple_skewed", // simple_skewed only
168                         "calc": {
169                             "perc_center_x": 0, //% of each distance from x1 to center. modifies curve shape, ignored if break_type is not none 
170                             "y_step": 0.5, //step used in path searching option in px, ignored in fixed, custom
171                         }
172                     }
173                 },
174                 "pore_region_width" : {
175                     "I":0.01, //% of viewbox
176                     "II":0.01, //% of viewbox
177                     "III":0.01, //% of viewbox
178                     "IV":0.01, //% of viewbox
179                 },
180                 "pore_region_height": {
181                     "I":0.4, //% of viewbox
182                     "II":0.4, //% of viewbox
183                     "III":0.4, //% of viewbox
184                     "IV":0.4, //% of viewbox
185                 },
186                 "long_loops_draw_opts": {
187                     "type": "single",
188                     "stroke": "black",
189                     "fill": "none",
190                     "opacity": 1,
191                     "stroke_size": "3px",
192                     "width": {
193                         "type": "scaled", // fixed, custom, scaled
194                     },
195                     "calc_len": {
196                         "type": "reslen", // fixed, custom, scaled, reslen
197                         "calc": {
198                             "length": 2 //in pixels
199                         },
200                     },
201                     "shape": {
202                         "type": "mushroom", // simple, bulb, mushroom, mushroom_skew
203                         "calc": {
204                             "x_step":0.5, //step used in path searching option in px, ignored in fixed, custom
205                             "y_step":1,
206                             "perc_center_x": 0.9, //% of each distance from x1 to center. modifies mushroom shape
207                             "perc_step_y1":0.1 , //% of each y step for p1 and p5 of path. modifies mushroom shape
208                             "perc_step_y2": 0.5, //% of each y step for p2 and p4 of path. modifies mushroom shape
209                         }
210                     }
211                 },
212                 "nter_loop_width": 0.05, //% of viewbox
213                 "nter_loop_draw_opts": {
214                     "type": "single",
215                     "stroke": "black",
216                     "fill": "none",
217                     "opacity": 1,
218                     "stroke_size": "3px",
219                     "calc_len": {
220                         "type": "reslen", // fixed, reslen
221                         "calc": {
222                             "length": 2, //in pixels
223                             "start_loop": "edge",
224                         },
225                     },
226                     "shape": {
227                         "type": "n_curves", // n_curves
228                         "calc": {
229                             "y_step": 0.5, //step used in path searching option in px, ignored in fixed, custom
230                             "n_centers": 2, //number of waves to draw in curve
231                             "perc_centers_height":[1.0, 0.5],// height percentage of each wave
232                             "loop_rotation": 30 //rotation of loop around starting point
233                         }
234                     }
235                 },
236                 "cter_loop_width": 0.05, //% of viewbox
237                 "cter_loop_draw_opts": {
238                     "type": "single",
239                     "stroke": "black",
240                     "fill": "none",
241                     "opacity": 1,
242                     "stroke_size": "3px",
243                     "calc_len": {
244                         "type": "fixed", // fixed, reslen
245                         "calc":{
246                             "height": 0.4, //% of membrane to end height
247                             "start_loop": "edge",
248                         },
249                     },
250                     "shape": {
251                         "type": "n_curves", // n_curves
252                         "calc": {
253                             "y_step": 0.5, //step used in path searching option in px, ignored in fixed, custom
254                             "n_centers": 4, //number of waves to draw in curve
255                             "perc_centers_height":[1.0, 0.8, 0.5, 0.4],// height percentage of each wave
256                             "loop_rotation": -30 //rotation of loop around starting point
257                         }
258                     }
259                 },
260                 "residue_centroid_draw_opts": {
261                     "type": "single",
262                     "stroke": "black",
263                     "fill": "red",
264                     "opacity": 1,
265                     "stroke_size": "0px",
266                     "circle_radius": "1.5px",
267                 },
268                 "residue_relations_draw_opts" : {
269                     "type": "single",
270                     "opacity": 1,
271                     "stroke_size": "1.5px",
272                     "centroid_pos": {
273                         "type": "custom",
274                         "perc_dict": {
275                             "intramembrane":{
276                                 "intramembrane":{"perc_y":"between", "perc_x":"between"},
277                                 "intracellular":{"perc_y":0.65, "perc_x":"between"},
278                                 "extracellular":{"perc_y":0.35, "perc_x":"between"},
279                             },
280                             "intracellular":{
281                                 "intracellular":{"perc_y":"between", "perc_x":"between"},
282                                 "intramembrane":{"perc_y":0.65, "perc_x":"between"},
283                                 "extracellular":{"perc_y":0.85, "perc_x":0.95}
284                             },
285                             "extracellular":{
286                                 "extracellular":{"perc_y":"between", "perc_x":"between"},
287                                 "intracellular":{"perc_y":0.15, "perc_x":0.05},
288                                 "intramembrane":{"perc_y":0.35, "perc_x":"between"}
289                             },
290                         }
291                     },
292                     "weight_scaling": "absolute",
293                     "path_width_scaling": {
294                         "type":"calc",
295                         "domain": ["min", "max"],
296                         "range": [2, 5],
297                         "group_by_type": true
298                     },
299                     "color_scaling": {
300                         "type":"calc",
301                         "property": "weight",
302                         "domain": ["min", "max"],
303                         "range": ["green", "blue"],
304                         "lighter_fill": true
305                     },
306                     "element_centroid_scaling": {
307                         "type":"fixed",
308                         "radius": 5,
309                     },
310                     "stroke": "green",
311                     "fill": "lightgreen",
312                 },
313             },
314             "canvas" : {
315                 "border" : {
316                     "right": 0.02, //% of viewbox
317                     "left":0.02, //% of viewbox
318                     "top": 0.02, //% of viewbox
319                     "bottom": 0.02, //% of viewbox
320                 }
321             },
322             "text_defs": {
323                 "font_family": "sans-serif",
324                 "font_style": "italic",
325                 "font_size": "30px",
326                 "fill": "black",
327                 "center_xy": true,
328             }
329         }
330         return defaultStyleObject;
331     }
332 
333     /**
334      * Object which controls every drawing aspect of plot. Contains four main attributes with lots of possible subdivisions and options.<br>
335      * These four attributes are:<br>
336      * <br>
337      * 1. "membrane"<br>
338      * 2. "canvas"<br>
339      * 3. "textdefs"<br>
340      * 4. "protein"<br>
341      * <br>
342      * "membrane":<br>
343      * <br>
344      * ----Sub-attributes: "membrane_region_height", "membrane_mode", "membrane_draw_opts"<br>
345      * ----"membrane_region_height": controls % of total svg height occupied by membrane
346      * ----"membrane_draw_opts" sub-attributes depends on the "membrane_mode".<br>
347      * <br>
348      * ----"membrane_mode" has two possible values:<br>
349      * <br>
350      * -----> "box" for drawing membranes as a colored rectangle<br>
351      * -----> "lipid" for drawing membranes as a lipid bilayer<br>
352      * <br>
353      * --------"membrane_draw_opts" used sub-attributes for "membrane_mode":"box" are:<br>
354      * --------"fill": membrane rectangle color<br>
355      * --------"opacity": membrane rectangle opacity<br>
356      * <br>
357      * --------"membrane_draw_opts" used sub-attributes for "membrane_mode":"lipid" are:<br>
358      * --------"hfill": membrane lipid head group color<br>
359      * --------"hstroke": membrane lipid head group stroke color<br>
360      * --------"hstroke_s": membrane lipid head group stroke width in pixels<br>
361      * --------"hopacity": membrane lipid head group opacity<br>
362      * --------"tfill": membrane lipid tail group color<br>
363      * --------"tstroke": membrane lipid tail group stroke color<br>
364      * --------"tstroke_s": membrane lipid tail group stroke width in pixels<br>
365      * --------"topacity": membrane lipid tail group opacity<br>
366      * --------"lipid_head_radius_width": lipid head group width in % of total svg width<br>
367      * --------"lipid_head_radius_height": lipid head group height in % of total svg height<br>
368      * --------"lipid_tail_number": number of lipid tails per lipid group<br>
369      * --------"lipid_tail_breaks": lipid tail breaks per lipid group (currently not used)<br>
370      * --------"lipid_tail_spacing": lipid upper membrane to lower membrane tails spacing in % of total svg height<br>
371      * <br>
372      * "canvas":<br>
373      * <br>
374      * ----Sub-attribute: "border" with sub-attributes "right", "left", "top", "bottom"<br>
375      * ----"right": controls svg right border in % of total svg width<br>
376      * ----"left": controls svg left border in % of total svg width<br>
377      * ----"top": controls svg top border in % of total svg height<br>
378      * ----"bottom": controls svg bottom border in % of total svg height<br>
379      * <br>
380      * "textdefs":<br>
381      * ----Sub-attributes: "font_family", "font_style", "font_size", "fill", "center_xy"<br>
382      * ----"font_family: controls text default font family<br>
383      * ----"font_style": controls text default font style<br>
384      * ----"font_size": controls text default font size<br>
385      * ----"fill": controls text default color<br>
386      * ----"center_xy": controls text automatic centering around "absolute" coordinate<br>
387      * <br>
388      * "protein":<br>
389      * <br>
390      * ----Has many sub-attributes related to the drawing of different elements<br>
391      * ----Sub-attributes for helices under protein: "helix_mode", "helix_draw_opts", "helix_region_width", "helix_region_height", "helix_spacing_width"<br>
392      * <br>
393      * ----"helix_mode": determines type of helix to be drawn: "box", "cylinder" or "cartoon" are possible values.<br>
394      * ----"helix_draw_opts": determines helix drawing characteristics according to helix type.<br>
395      * --------"helix_draw_opts" used sub-attributes for "helix_mode":"box" are: NONE<br>
396      * --------"helix_draw_opts" used sub-attributes for "helix_mode":"cylinder" are:<br>
397      * --------"top_cylinder_stroke": stroke color of top cylinder circle<br>
398      * --------"top_cylinder_stroke_size": stroke size of top cylinder circle<br>
399      * --------"bottom_cylinder_stroke": stroke size of bottom cylinder cirle<br>
400      * --------"bottom_cylinder_stroke_size": stroke color of bottom cylinder circle<br>
401      * <br>
402      * --------"helix_draw_opts" used sub-attributes for "helix_mode":"cartoon" are:<br>
403      * --------"x_to_end_prop": proportion that influences shape of drawn half helices<br>
404      * --------"y_to_mid_prop": proportion that influences shape of drawn half helices<br>
405      * --------"aa_area_perc_displacement": proportion that influences shape of drawn half helices<br>
406      * --------"thickness": proportion that influences half turn height<br>
407      * --------"front_helix_stroke": stroke color of front half helices<br>
408      * --------"back_helix_stroke": stroke color of back half helices<br>
409      * --------"stroke_size": stroke size of half helices<br>
410      * <br>
411      * ----"helix_draw_opts" also has properties that can be applied for any helix element which<br>
412      * ----have a specific formatting as a dictionary of domain keys mapping to arrays of six elements<br>
413      * ----corresponding to each helix(H1-H6) of each domain (I-IV)<br>
414      * --------"fill": color of front turns for each helix of each domain<br>
415      * --------"back_fill": color of back turns for each helix of each domain<br>
416      * --------"stroke": color of stroke for each helix of each domain<br>
417      * --------"stroke_size": size in pixels of stroke for each helix of each domain<br>
418      * --------"opacity": opacity of each helix in each domain<br>
419      * --------example: "I": { 1:"red",2:"red",3:"red",4:"blue",5:"red",6:"red"} for colors<br>
420      * <br>
421      * ----The helix related attributes: "helix_region_width", "helix_region_height", "helix_spacing_width",<br>
422      * ----have a specific formatting as a dictionary of domain keys mapping to arrays of six elements<br>
423      * ----corresponding to each helix(H1-H6) of each domain (I-IV)<br>
424      * ----"helix_region_width": controls helix plot width in % of total svg width<br>
425      * ----"helix_region_height": controls helix plot height in % of total svg height<br>
426      * ----"helix_spacing_width": controls spacing between helices in % of total svg width<br>
427      * <br>
428      * ----Sub-attributes for loops include different loops categories such as:<br><br>
429      * ----"short_loops_draw_opts" for short, intra domain loops<br>
430      * ----"long_loops_draw_opts" for long, between domain loops<br>
431      * ----"pore_loops_draw_opts", "pore_region_width", "pore_region_height" for pore loops<br>
432      * ---- "nter_loop_width", "nter_loop_draw_opts" for the N-terminal loop<br>
433      * ---- "cter_loop_width", "cter_loop_draw_opts" for the C-terminal loop<br>
434      * ---- the sub-attributes of the differnt loops "_draw_opts" objects are mostly equal:<br>
435      * --------"type": value is "single". has no importance for now but is essential<br>
436      * --------"stroke": color of loop path stroke<br>
437      * --------"fill": color of loop path ("none" should be used for non-polygon paths)<br>
438      * --------"opacity": opacity of loop path<br>
439      * --------"stroke_size": stroke width of loop path<br>
440      * <br>
441      * --------"calc_len": describes how loop length should be calculated. has multiple options of additional attributes<br>
442      * ------------"type": indicates loop length calculus type. "fixed", "scaled", "reslen" and "custom" are possible options<br>
443      * ------------"calc": contains additional attributes for each loop length calculus "type"<br>
444      * <br>
445      * ------------ "fixed" indicates an equal, similar loop drawn for fixed box with a given height, width (when applicable for loop shape)<br>
446      * ------------ "fixed" has required attributes inside "calc": "height" "width" indicating % of total svg width, height<br>
447      * <br>
448      * ------------ "scaled" indicates each loop should be drawn from boxes with height, widths proportional to their amino acid numbering<br>
449      * ------------ "scaled" has required attributes inside "calc": "height" "width" indicating maximum % of total svg width, height for biggest loop<br>
450      * ------------ "scale" which indicates type of aa number loop scaling: "linear", "power" or "log". <br>
451      * ------------ logarithimic "log" scale can have its base set by a "base" additional attribute inside "calc"<br>
452      * ------------ power "power" scale can have its exponent set by a "exponent" additional attribute inside "calc"<br>
453      * ------------ "scale" types are unavailable for "nter_loop_draw_opts" and "cter_loop_draw_opts"<br>
454      * <br>
455      * ------------ "reslen" indicates each loops aminoacid should have a given length in pixels<br>
456      * ------------ "reslen" requires "length" attribute inside "calc" indicating aminoacid length in pixels.<br>
457      * ------------ please note that loop minimum width is automatically set when bigger than "length" attribute<br>
458      * ------------ this is calculates by a straight line between helices anchoring points divided by the loop aa number<br>
459      * <br>
460      * ------------ "custom" indicates an specific loop length for each loop defined by a dictionary indicating loop position<br>
461      * ------------ "custom" requires dictionaries for "width" and "height" inside "calc" options.<br>
462      * ------------ for "short_loops_draw_opts" this dictionary is formatted as the example below:<br>
463      * ------------ "I": [0.01,0.01,0.01,0.01,0.005,0.01,0.01]<br>
464      * ------------ for "long_loops_draw_opts" this dictionary is formatted as the example below:<br>
465      * ------------ {2: 0.01, 3: 0.01, 4: 0.01},<br>
466      * ------------ for "pore_loops_draw_opts", "nter_loop_draw_opts" and "cter_loop_draw_opts", the "custom" option is not available<br>
467      * <br>
468      * --------"shape": describes how loop shape should be drawn<br>
469      * ------------"type": indicates how loop shape should be drawn in plot<br>
470      * ------------"calc": contains additional attributes for drawing specific loop shapes<br>
471      * <br>
472      * ------------ loop types "simple", "bulb" and "mushroom" are available for <b><i>both short and long loops</b></i><br>
473      * ------------ in loop type "simple", loop points are defined by the two helices anchoring points plus<br>
474      * ------------ their centroid point, which is scaled in the vertical direction according to "calc_len"<br>
475      * ------------ "height" definitions and to a "y_step" calc attribute <br>
476      * ------------ an optional "calc" attribute "perc_center_x" can regulate the x positioning of the centroid<br>
477      * ------------ point in relation to the anchoring first and second points.<br>
478      * <br>
479      * ------------ in loop type "bulb", besides the above points for loop "simple", two additional points are added:<br>
480      * ------------ one before the first anchoring point and another after the first anchoring point<br>
481      * ------------ the y position of these points are defined as a percentage of the scaled centroid point height<br>
482      * ------------ by the "perc_step_y" attribute. their x positions are defined by the "calc_len"<br>
483      * ------------ "width" definitions and to a "x_step" calc attribute <br>
484      * <br>
485      * ------------ in loop type "mushroom", besides the above points for loop "bulb", two additional points are added:<br>
486      * ------------ one between the first anchoring point and the centroid point<br>
487      * ------------ and another between the centroid point and the second anchoring point<br>
488      * ------------ the y position of these points are defined as a percentage of the scaled centroid point height<br>
489      * ------------ by the "perc_step_y2" attribute. ("perc_step_y1" is used for the "bulb" additional points)<br>
490      * ------------ their x positions are defined by the "perc_center_x" "calc" attribute which regulates their distance<br>
491      * ------------ between the anchoring points and the centroid as a frequency (0.0-1.0)<br>
492      * <br>
493      * ------------ loop type "swirl" is available only for both <b><i>short loops</b></i><br>
494      * ------------ this is mostly equal to the simple loop but here a "swirl_x" calc attribute is<br>
495      * ------------ used to calculate the x positioning of two new points relative to the anchoring points centroid<br>
496      * ------------ these two points are added between the first and centroid and centroid and second points<br>
497      * ------------ their y positioning is controled by a "perc_step_y" attribute. the x positioning delta of the<br>
498      * ------------ two new points is also added to the centroid point<br>
499      * <br>
500      * ------------ loop type "simple_skewed" is the only available <b><i>only for pore loops</b></i><br>
501      * ------------ this is equal to the "simple" loop definition but with the obligatory attribute<br>
502      * ------------ "perc_center_x" for controlling the x positioning of the centroid point<br>
503      * <br>
504      * ------------ loop type "n_curves" is the only available <b><i>only for N and C terminal loops</b></i><br>
505      * ------------ in loop type "n_curves", loops are initially drawn as a straight line <br>
506      * ------------ from the "canvas" bottom left corner to Helix 1 of Domain 1 and a number of "waves" are added<br>
507      * ------------ to these loops. the height of each wave is calculated as percentages of a maximum wave step (attribute "y_step")<br>
508      * ------------ in the calc attribute "perc_centers_height". the number of waves is defined in the "n_centers" "calc" attribute<br>
509      * <br>
510      * --------- Note 1:<br>
511      * --------- long_loops_draw_opts has an additional "width" attribute that regulates their initial draw areas "width" calculation<br>
512      * --------- this is done by it's subattribute "type" whose possible values are "fixed" (equal inter domain loops width)<br>
513      * --------- or "scaled" (inter domain loops width proportional to loop aa number)<br>
514      * <br>
515      * ---- Note 2:<br>
516      * ---- pore loops have two attributes under "protein": <br>
517      * ---- "pore_region_width" and "pore_region_height" for controlling their draw areas width and height<br>
518      * ---- as percentages of the svg total width and height. the values of these attributes are dictionaries<br>
519      * ---- containing keys for the pore loops of each domain as the example below:<br>
520      * ---- {"I":0.01,"II":0.01,"III":0.01,"IV":0.01}<br>
521      * <br>
522      * ---- Note 3:<br>
523      * ---- N ter and C ter loops have each a single attribute under "protein": <br>
524      * ---- "nter_loop_width" and "cter_loop_width" controlling their draw areas respective width<br>
525      * ---- as percentages of the svg total width.<br>
526      * @namespace
527      * @type {{}}
528      * @exports NaView
529      * @name style_obj
530      */
531     var style_obj;
532 
533     /**
534      * Dictionary for residue properties.<br>
535      * Properties are defined as: residue_index:{"property":property_value}<br>
536      * Example: {<br>
537      *     1: {//residue 1 of protein sequence<br>
538      *         "Conservation": 0.1 //Value of Conservation Property<br>
539      *     },<br>
540      *     2: {//residue 2 of protein sequence<br>
541      *         "Conservation": 0.3 //Value of Conservation Property<br>
542      *     },<br>
543      *     3: {//residue 3 of protein sequence<br>
544      *         "Conservation": 0.6 //Value of Conservation Property<br>
545      *     },<br>
546      *     4: {//residue 4 of protein sequence<br>
547      *         "Conservation": 0.7 //Value of Conservation Property<br>
548      *     },<br>
549      *     ...<br>
550      *     2005: {//residue 2005 of protein sequence<br>
551      *         "Conservation": 0.9 //Value of Conservation Property<br>
552      *     }<br>
553      * }<br>
554      * @namespace
555      * @type {{}}
556      * @exports NaView
557      * @name current_resid_properties
558      */
559     var current_resid_properties;
560 
561     /**
562      * Global object storing fill rules for residue/elements
563      * @see createFillRules
564      * @namespace
565      * @type {{}}
566      * @exports NaView
567      * @name fillRules
568      */
569     var fillRules = [];
570 
571     /**
572      * Global object storing drawing area boundaries for SVG element
573      * @namespace
574      * @type {{}}
575      * @exports NaView
576      * @name svg_drawarea
577      */
578     var svg_drawarea;
579 
580     /**
581      * Dictionary for converting one letter amino acid syntax to three letter
582      * @namespace
583      * @type {{}}
584      * @exports NaView
585      * @name one_to_three
586      */
587     var one_to_three = {
588         "A":"ALA",
589         "C":"CYS",
590         "D":"ASP",
591         "E":"GLU",
592         "F":"PHE",
593         "G":"GLY",
594         "H":"HIS",
595         "I":"ILE",
596         "K":"LYS",
597         "L":"LEU",
598         "M":"MET",
599         "N":"ASN",
600         "P":"PRO",
601         "Q":"GLN",
602         "R":"ARG",
603         "S":"SER",
604         "T":"THR",
605         "V":"VAL",
606         "W":"TRP",
607         "Y":"TYR",
608     };
609     
610     /**
611      * Dictionary for converting three letter amino acid syntax to one letter
612      * @namespace
613      * @type {{}}
614      * @exports NaView
615      * @name three_to_one
616      */
617     var three_to_one = {
618         "ALA":"A",
619         "CYS":"C",
620         "ASP":"D",
621         "GLU":"E",
622         "PHE":"F",
623         "GLY":"G",
624         "HIS":"H",
625         "ILE":"I",
626         "LYS":"K",
627         "LEU":"L",
628         "MET":"M",
629         "ASN":"N",
630         "PRO":"P",
631         "GLN":"Q",
632         "ARG":"R",
633         "SER":"S",
634         "THR":"T",
635         "VAL":"V",
636         "TRP":"W",
637         "TYR":"Y",
638     };
639     
640     /**
641      * List of allowed names for drawn elements.<br>
642      * Allowed elements are named according to:<br><br>
643      * 1. their Domain (I,II,III,IV) or InterDomain (1,2,3,4,5) references<br>
644      * 2. A semicolon (;) Domain to Element separator.<br>
645      * 3. their element types (Helix, Pore, Loop) references<br>
646      * 4. (Optional) for intradomain Helices and Loops, their element type indexes (1,2,3,4,5,6)<br>
647      * @namespace
648      * @type {Array}
649      * @exports NaView
650      * @name allowed_element_names
651      */
652     var allowed_element_names = [
653         "DomainI;Helix1",
654         "DomainI;Helix2",
655         "DomainI;Helix3",
656         "DomainI;Helix4",
657         "DomainI;Helix5",
658         "DomainI;Helix6",
659         "DomainII;Helix1",
660         "DomainII;Helix2",
661         "DomainII;Helix3",
662         "DomainII;Helix4",
663         "DomainII;Helix5",
664         "DomainII;Helix6",
665         "DomainIII;Helix1",
666         "DomainIII;Helix2",
667         "DomainIII;Helix3",
668         "DomainIII;Helix4",
669         "DomainIII;Helix5",
670         "DomainIII;Helix6",
671         "DomainIV;Helix1",
672         "DomainIV;Helix2",
673         "DomainIV;Helix3",
674         "DomainIV;Helix4",
675         "DomainIV;Helix5",
676         "DomainIV;Helix6",
677         "DomainI;Loop1",
678         "DomainI;Loop2",
679         "DomainI;Loop3",
680         "DomainI;Loop4",
681         "DomainI;Loop5",
682         "DomainI;Loop6",
683         "DomainII;Loop1",
684         "DomainII;Loop2",
685         "DomainII;Loop3",
686         "DomainII;Loop4",
687         "DomainII;Loop5",
688         "DomainII;Loop6",
689         "DomainIII;Loop1",
690         "DomainIII;Loop2",
691         "DomainIII;Loop3",
692         "DomainIII;Loop4",
693         "DomainIII;Loop5",
694         "DomainIII;Loop6",
695         "DomainIV;Loop1",
696         "DomainIV;Loop2",
697         "DomainIV;Loop3",
698         "DomainIV;Loop4",
699         "DomainIV;Loop5",
700         "DomainIV;Loop6",
701         "DomainI;Pore",
702         "DomainII;Pore",
703         "DomainIII;Pore",
704         "DomainIV;Pore",
705         "InterDomain1;Loop",
706         "InterDomain2;Loop",
707         "InterDomain3;Loop",
708         "InterDomain4;Loop",
709         "InterDomain5;Loop"
710     ];
711 
712 
713 
714     /**
715      * Main function for running library. Uses detected parameters to automatically create NaV plot.
716      * @exports NaView
717      * @name initLib
718      * @namespace
719      */
720     function initLib() {
721         initMainSvg(svg_id, container_id, svg_width, svg_height);
722         if (style_obj_input) {
723             style_obj = style_obj_input;
724         }
725         if (!style_obj) {
726             console.warn('style obj not set. creating a new one...');
727             style_obj = generateDefaultStyleObject();
728         }
729         if (protein_input) {
730             initData();
731         } else {
732             throw "Error: no protein input data";
733         }
734         initViz();
735 
736     }
737     initLib();
738 
739     /**
740      * Function to generate the SVG Dom element.<br>
741      * 1. Detects if current svg container exists, and should be created if not<br>
742      * 2. Detects if current svg exists and creates it if not<br>
743      * 3. Sets svg width and height<br>
744      * @param {string} svg_id id of svg to edit/create
745      * @param {string} container_id  id of svg to select/create
746      * @param {number} svg_width width to set svg
747      * @param {number} svg_height height to set svg
748      * @exports NaView
749      * @name initMainSvg
750      * @namespace
751      */
752     function initMainSvg(svg_id, container_id, svg_width, svg_height) {
753         let select_svg_container;
754         if (container_id){
755             select_svg_container = d3.select("#"+container_id);
756             if (select_svg_container.size() === 0) {
757                 select_svg_container = d3.select("body").append("div").attr("id", container_id);
758             }
759         } else {
760             select_svg_container = d3.select("body");
761         }
762         let select_svg = d3.select("#"+svg_id);
763         if (select_svg.size() === 0) {
764             select_svg = select_svg_container.append("svg").attr("id", svg_id);
765         }
766         select_svg
767             .attr("width", svg_width)
768             .attr("height", svg_height);
769     }
770 
771     /**
772      * Function to verify and parse the user protein data.
773      * @exports NaView
774      * @name initData
775      * @namespace
776      */
777     function initData() {
778         //check if input is already processed or not
779         let processed = true;
780         let key_list = ["fullseq","data","count_data","resid_properties"];
781         for (let ikl = 0; ikl < key_list.length; ikl++) {
782             let e_key = key_list[ikl];
783             if (protein_input.hasOwnProperty(e_key) === false) {
784                 // throw "Error: input data is missing required data keys"; 
785                 console.warn("input data is missing required data keys... trying to parse raw data"); 
786                 processed = false;
787                 break;
788             }
789         }
790         if (!processed) {
791             parsed_protein_data = processRawUniProt(protein_input);
792         } else {
793             parsed_protein_data = protein_input;
794         }
795     }
796 
797     /**
798      * Function to generate the Membrane Plot.<br>
799      * @exports NaView
800      * @name initViz
801      * @namespace
802      */
803     function initViz() {
804         let parsed_protein_data_drawareas = define_draw_areas(parsed_protein_data.data);
805         parsed_protein_data.data = parsed_protein_data_drawareas[0];
806         parsed_protein_data.membrane = parsed_protein_data_drawareas[1];
807         parsed_protein_data.draw_area = parsed_protein_data_drawareas[2];
808         parsed_protein_data.resid_properties = properties;
809         parsed_protein_data.color_rules = color_rules;
810         parsed_protein_data.draw_symbols = text_to_draw;
811         parsed_protein_data.residue_relations = relationships;
812         drawEverything(parsed_protein_data);
813         console.log("generated plot!");
814     }
815 
816     /**
817      * Helper function for rounding a number up to a given decimal place
818      * @param {number} num number to be rounded
819      * @param {number} after_comma number of decimal places to round to
820      * @namespace
821      * @exports NaView
822      * @name roundDecimals
823      * @yields {number} rounded number up to given decimal places
824      */
825     function roundDecimals(num, after_comma) {
826         let ten_basis = Math.pow(10, after_comma)
827         return Math.round( num * ten_basis + Number.EPSILON ) / ten_basis;
828     }
829 
830     /**
831      * Helper function for generating random integer number between two numbers, including those numbers
832      * @param {number} min  minimum number to generate integer from
833      * @param {number} max maximum number to generate integer to
834      * @namespace
835      * @exports NaView
836      * @name getRandomIntInclusive
837      * @yields {number} integer number between min and max
838      */
839     function getRandomIntInclusive(min, max) {
840         min = Math.ceil(min);
841         max = Math.floor(max);
842         return Math.floor(Math.random() * (max - min + 1)) + min;
843     }
844 
845     /**
846      * Helper function for generating random float number between two numbers, including those numbers
847      * @param {number} min minimum number to generate float from
848      * @param {number} max maximum number to generate float to
849      * @namespace
850      * @exports NaView
851      * @name getRandomFloatInclusive
852      * @yields {number} float number between min and max
853      */
854     function getRandomFloatInclusive(min, max) {
855         return Math.random() * (max - min) + min;
856     }
857 
858     /**
859      * Helper function for creating an Array of substrings of length n from a string
860      * @param {string} str string to generate substrings from
861      * @param {number} n length of desired substrings
862      * @namespace
863      * @exports NaView
864      * @name stringToChunks
865      * @yields Array of substrings
866      */
867     function stringToChunks(str, n) {
868         var ret = [];
869         var i;
870         var len;
871     
872         for(i = 0, len = str.length; i < len; i += n) {
873            ret.push(str.substr(i, n))
874         }
875     
876         return ret
877     };
878 
879     /**
880      * Helper function for creating an Array of substrings of the alternating lengths n and s from a string
881      * @param {string} str string to generate substrings from
882      * @param {number} n alternating length 1 of desired substrings
883      * @param {number} s alternating length 2 of desired substrings
884      * @namespace
885      * @exports NaView
886      * @name stringToChunksSkip
887      * @see stringToChunks for original function this was based on
888      * @yields Array of substrings
889      */
890     function stringToChunksSkip(str, n, s) {
891         var ret = [];
892         var i;
893         var len;
894     
895         // for(i = 0, len = str.length; i < len; i += n) {
896         //    ret.push(str.substr(i, n))
897         // }
898         let to_skip = false;
899         let done_n = 0;
900         let temp = "";
901         for (let i = 0, len = str.length; i < len; i++) {
902             temp += str[i];
903             done_n += 1;
904             if (to_skip === false) {
905                 if (done_n === n) {
906                     ret.push(temp);
907                     temp = "";
908                     done_n = 0;
909                     to_skip = true;
910                 }
911             } else {
912                 if (done_n === s) {
913                     ret.push(temp);
914                     temp = "";
915                     done_n = 0;
916                     to_skip = false;
917                 }
918             }
919         }
920         if (temp.length > 0) {
921             ret.push(temp);
922         }
923     
924         return ret
925     };
926 
927     /**
928      * Helper function for generating an Array composed of ascending integers
929      * @param {number} size number of integers in final Array
930      * @param {number} startAt starting number to ascend from
931      * @namespace
932      * @exports NaView
933      * @name rangeArray
934      * @yields Array of ascending numbers
935      */
936     function rangeArray(size, startAt = 0) {
937         return [...Array(size).keys()].map(i => i + startAt);
938     }
939 
940     /**
941      * Helper function for generating a deep copy of an Object
942      * @param {Object} obj obj to generate deep copy
943      * @namespace
944      * @exports NaView
945      * @name deepCopy
946      * @yields {Object} deep copy of obj input 
947      */
948     function deepCopy(obj) {
949         return JSON.parse(JSON.stringify(obj));
950     }
951 
952     /**
953      * Helper function for returning any color as an Array of [r, g, b, a]
954      * @param {string} color color string. Must be a valid canvas fillStyle
955      * @namespace
956      * @exports NaView
957      * @name colorToRGBA
958      * @yields {Array} [r, g, b, a] color Array generated from a ghost canvas
959      */
960     function colorToRGBA(color) {
961         var cvs, ctx;
962         cvs = document.createElement('canvas');
963         cvs.height = 1;
964         cvs.width = 1;
965         ctx = cvs.getContext('2d');
966         ctx.fillStyle = color;
967         ctx.fillRect(0, 0, 1, 1);
968         return ctx.getImageData(0, 0, 1, 1).data;
969     }
970 
971     /**
972      * Helper library to lighten/darken colors
973      * @namespace
974      * @exports NaView
975      * @name pSBC
976      * @see  {@link https://github.com/PimpTrizkit/PJs/wiki/12.-Shade,-Blend-and-Convert-a-Web-Color-(pSBC.js)} for further information.
977      */
978     function pSBC(p,c0,c1,l) {
979         let r,g,b,P,f,t,h,i=parseInt,m=Math.round,a=typeof(c1)=="string";
980         if(typeof(p)!="number"||p<-1||p>1||typeof(c0)!="string"||(c0[0]!='r'&&c0[0]!='#')||(c1&&!a))return null;
981         if(!this.pSBCr)this.pSBCr=(d)=>{
982             let n=d.length,x={};
983             if(n>9){
984                 [r,g,b,a]=d=d.split(","),n=d.length;
985                 if(n<3||n>4)return null;
986                 x.r=i(r[3]=="a"?r.slice(5):r.slice(4)),x.g=i(g),x.b=i(b),x.a=a?parseFloat(a):-1
987             }else{
988                 if(n==8||n==6||n<4)return null;
989                 if(n<6)d="#"+d[1]+d[1]+d[2]+d[2]+d[3]+d[3]+(n>4?d[4]+d[4]:"");
990                 d=i(d.slice(1),16);
991                 if(n==9||n==5)x.r=d>>24&255,x.g=d>>16&255,x.b=d>>8&255,x.a=m((d&255)/0.255)/1000;
992                 else x.r=d>>16,x.g=d>>8&255,x.b=d&255,x.a=-1
993             }return x};
994         h=c0.length>9,h=a?c1.length>9?true:c1=="c"?!h:false:h,f=this.pSBCr(c0),P=p<0,t=c1&&c1!="c"?this.pSBCr(c1):P?{r:0,g:0,b:0,a:-1}:{r:255,g:255,b:255,a:-1},p=P?p*-1:p,P=1-p;
995         if(!f||!t)return null;
996         if(l)r=m(P*f.r+p*t.r),g=m(P*f.g+p*t.g),b=m(P*f.b+p*t.b);
997         else r=m((P*f.r**2+p*t.r**2)**0.5),g=m((P*f.g**2+p*t.g**2)**0.5),b=m((P*f.b**2+p*t.b**2)**0.5);
998         a=f.a,t=t.a,f=a>=0||t>=0,a=f?a<0?t:t<0?a:a*P+t*p:0;
999         if(h)return"rgb"+(f?"a(":"(")+r+","+g+","+b+(f?","+m(a*1000)/1000:"")+")";
1000         else return"#"+(4294967296+r*16777216+g*65536+b*256+(f?m(a*255):0)).toString(16).slice(1,f?undefined:-2)
1001     }
1002 
1003     /**
1004      * Helper function for returning unique elements when passed to the Array.prototype.filter() method.
1005      * @param {number} value value of current Array Object
1006      * @param {number} index index of current Array Object
1007      * @param {Array} self any possible Array
1008      * @namespace
1009      * @exports NaView
1010      * @name onlyUnique
1011      * @example [1,2,2,3,3,4].filter(onlyUnique) returns [1,2,3,4]
1012      * @yields {Boolean} true when an Object is in Array, false when not
1013      */
1014     function onlyUnique(value, index, self) {
1015         return self.indexOf(value) === index;
1016     }
1017 
1018     /**
1019      * Parses a UniProt formatted text string
1020      * to the required data format for plotting a NaV channel.<br><br>
1021      * Required line types include:<br>
1022      * 1. The FT (Feature Table) lines for:<br>
1023      *      a) TOPO_DOM - Topological domain<br>
1024      *      b) TRANSMEM - Extent of a transmembrane region<br>
1025      *      c) INTRAMEM - Extent of a region located in a membrane without crossing it<br>
1026      *      d) CHAIN - Extent of a polypeptide chain in the mature protein.<br>
1027      *      e) REPEAT - Extent of an internal sequence repetition.<br>
1028      * 2. The SQ (SeQuence header) lines
1029      * @param {string} test_raw_protein UniProt text formatted object with needed FT and SQ lines
1030      * @namespace
1031      * @exports NaView
1032      * @name processRawUniProt
1033      * @yields {Object} Object containing:<br>
1034      * 1.fullseq: full protein sequence<br>
1035      * 2.data: generated data Array containing information of each to be drawn protein element<br>
1036      * 3.count_data: counts per each element type and domain in protein<br>
1037      * 4.resid_properties: Object with an index for each residue in protein (1,2,3,4... corresponding to each amino acid)
1038      * @see {@link https://www.uniprot.org/docs/userman.htm} for information on the UniProt text format
1039      */
1040     function processRawUniProt(test_raw_protein) {
1041         let test_raw_protein_lines = test_raw_protein.split("\n");
1042         
1043         let start_chain = false;
1044         let end_chain = false;
1045         let start_chain_aa,end_chain_aa;
1046         let prev_test_raw_protein_line = "";
1047         let doms_to_resnums = {};
1048         let line_of_interest_queries = {
1049             "FT   TOPO_DOM": "loop",
1050             "FT   INTRAMEM": "pore",
1051             "FT   TRANSMEM": "helix"
1052         };
1053         let lines_of_interest = [];
1054         let dom_counts = {};
1055         let dom_type_counts = {};
1056         for (let irpl = 0; irpl < test_raw_protein_lines.length; irpl++) {
1057             let test_raw_protein_line = test_raw_protein_lines[irpl];
1058             if (test_raw_protein_line.length > 13) {
1059                 let header_str = test_raw_protein_line.substr(0, 13);
1060                 if (start_chain === false && end_chain === false) {
1061                     if (header_str === "FT   CHAIN   ") {
1062                         start_chain_aa = parseInt(test_raw_protein_line.split("FT   CHAIN   ")[1].split("..")[0].replace(/\s+/, ""));
1063                         end_chain_aa = parseInt(test_raw_protein_line.split("FT   CHAIN   ")[1].split("..")[1]);
1064                         start_chain = true;
1065                         continue;
1066                     }
1067                 } else if (start_chain === true && end_chain === false)  {
1068                     if (Object.keys(line_of_interest_queries).indexOf(header_str) > -1) {
1069                         let array_lines = [header_str, test_raw_protein_line];
1070                         let note_line = test_raw_protein_lines[irpl+1];
1071                         if (note_line.includes("/note=") === true) {
1072                             array_lines.push(note_line);
1073                         }
1074                         lines_of_interest.push(array_lines);
1075                     }
1076                     if (prev_test_raw_protein_line.includes(".."+end_chain_aa)) {
1077                         end_chain = true;
1078                         continue;
1079                     }
1080                 }
1081                 if (header_str === "FT   REPEAT  ") {
1082                     let note_line = test_raw_protein_lines[irpl+1];
1083                     let dom_name = note_line.split('/note="')[1].replace(/\"/, "");
1084 
1085                     let dom_start_aa = parseInt(test_raw_protein_line.split("FT   REPEAT  ")[1].split("..")[0].replace(/\s+/, ""));
1086                     let dom_end_aa = parseInt(test_raw_protein_line.split("FT   REPEAT  ")[1].split("..")[1]);
1087 
1088                     doms_to_resnums[dom_name] = [dom_start_aa, dom_end_aa];
1089                     dom_counts[dom_name] = 0;
1090                     dom_type_counts[dom_name] = {};
1091                     dom_type_counts[dom_name]["loop"] = 0;
1092                     dom_type_counts[dom_name]["helix"] = 0;
1093                     dom_type_counts[dom_name]["pore"] = 0;
1094                 }
1095                 if (header_str === "SQ   SEQUENCE") {
1096                     let aa_num = parseInt(test_raw_protein_line.split("SQ   SEQUENCE")[1].split(" AA;")[0].replace(/\s+/, ""));
1097                     if (aa_num !== end_chain_aa) {
1098                         return false;
1099                     }
1100                     sequence = "";
1101                     for (let irpl1 = irpl+1; irpl1 < test_raw_protein_lines.length; irpl1++) {
1102                         if (test_raw_protein_lines[irpl1].includes("//") === false) {
1103                             let aminoacids_in_line = test_raw_protein_lines[irpl1].replace(/\s+/g, "");
1104                             sequence = sequence + aminoacids_in_line;
1105                         }
1106                     }
1107                 }
1108             }
1109             prev_test_raw_protein_line = test_raw_protein_line;
1110         }
1111         let parsed_data = [];
1112         let previous_domain = false;
1113         let type_aacounts = {
1114             "loop":0,
1115             "helix":0,
1116             "pore":0,
1117         };
1118         let border_count = 0;
1119         let type_full_counts = {
1120             "loop":0,
1121             "helix":0,
1122             "pore":0,
1123             "internal_loop":0
1124         }
1125         let aacounts_per_type = {
1126             "loop":[],
1127             "helix":[],
1128             "pore":[],
1129             "internal_loop":[]
1130         }
1131         for (let ilip = 0; ilip < lines_of_interest.length; ilip++) {
1132             let array_lines = lines_of_interest[ilip];
1133             let resid_start_aa = parseInt(array_lines[1].split(array_lines[0])[1].split("..")[0].replace(/\s+/, ""));
1134             let resid_end_aa = parseInt(array_lines[1].split(array_lines[0])[1].split("..")[1]);
1135             let line_type = line_of_interest_queries[array_lines[0]];
1136 
1137             let aa_count = (resid_end_aa-resid_start_aa+1);
1138             type_aacounts[line_type] += aa_count;
1139             type_full_counts[line_type] += 1;
1140             aacounts_per_type[line_type].push(aa_count);
1141             if (ilip === 0 || ilip === lines_of_interest.length-1) {
1142                 border_count += aa_count;
1143             } else if (line_type === "loop") {
1144                 type_full_counts["internal_loop"] += 1;
1145                 aacounts_per_type["internal_loop"].push(aa_count);
1146             }
1147         }
1148         end_chain_aa_alldomains = end_chain_aa - border_count;
1149 
1150         type_aacounts["internal_helix"] = type_aacounts["helix"];// - border_count;
1151         type_aacounts["internal_loop"] = type_aacounts["loop"] - border_count;
1152         type_aacounts["internal_pore"] = type_aacounts["pore"];// - border_count;
1153 
1154         let type_index = {
1155             "loop":0,
1156             "helix":0,
1157             "pore":0,
1158             "internal_loop":0
1159         };
1160         let next_domain = {
1161             "I": "II",
1162             "II": "III",
1163             "III": "IV",
1164         }
1165         let count_inter_domain = 1;
1166         for (let ili = 0; ili < lines_of_interest.length; ili++) {
1167             let array_lines = lines_of_interest[ili];
1168             
1169             let line_type = line_of_interest_queries[array_lines[0]];
1170             type_index[line_type] += 1;
1171             if (ili > 0 && ili < lines_of_interest.length-1 && line_type === "loop") {
1172                 type_index["internal_loop"] += 1;
1173             }
1174 
1175             let noteworthy = array_lines["2"].split('/note="')[1].split('"')[0];
1176 
1177             let resid_start_aa = parseInt(array_lines[1].split(array_lines[0])[1].split("..")[0].replace(/\s+/, ""));
1178             let resid_end_aa = parseInt(array_lines[1].split(array_lines[0])[1].split("..")[1]);
1179 
1180             let aminoacids_of_interest = sequence.split("").slice(resid_start_aa-1, resid_end_aa);
1181 
1182             let current_dom = false;
1183 
1184             for (const dom_name in doms_to_resnums) {
1185                 if (doms_to_resnums.hasOwnProperty(dom_name)) {
1186                     let dom_bounds = doms_to_resnums[dom_name];
1187                     if ((resid_start_aa >= dom_bounds[0] && resid_start_aa <= dom_bounds[1]) && (resid_end_aa >= dom_bounds[0] && resid_end_aa <= dom_bounds[1])) {
1188                         current_dom = dom_name;
1189                     }
1190                 }
1191             }
1192 
1193             if (current_dom) {
1194                 dom_counts[current_dom] += 1;
1195                 dom_type_counts[current_dom][line_type] += 1;
1196             }
1197 
1198             let export_obj = {
1199                 "type": line_type,
1200                 "start": resid_start_aa,
1201                 "end": resid_end_aa,
1202                 "aanum": resid_end_aa-resid_start_aa+1,
1203                 //frequencies by various parsing modes
1204                 "aaperc_full": (resid_end_aa-resid_start_aa+1)/end_chain_aa,
1205                 "aaperc_bytype_full": (resid_end_aa-resid_start_aa+1)/type_aacounts[line_type],
1206                 "aas": aminoacids_of_interest.join(""),
1207                 "type_i_full": type_index[line_type],
1208                 "id": (ili+1),
1209                 "prev_dom_name": previous_domain,
1210                 "next_dom_name": next_domain[previous_domain],
1211                 "note": noteworthy,
1212             }
1213             if (current_dom) {
1214                 export_obj["dom_name"] = current_dom;
1215                 export_obj["dom_i"] = dom_counts[current_dom];
1216                 export_obj["dom_itype"] = dom_type_counts[current_dom][line_type];
1217                 // export_obj["type_i_domains"]: type_index["internal_loop"],
1218                 export_obj["aaperc_alldomains"] = (resid_end_aa-resid_start_aa+1)/end_chain_aa_alldomains;
1219                 export_obj["aaperc_bytype_alldomains"] = (resid_end_aa-resid_start_aa+1)/type_aacounts["internal_"+line_type];
1220             } else {
1221                 export_obj["dom_name"] = false;
1222                 export_obj["dom_i"] = false;
1223                 export_obj["dom_itype"] = false;
1224                 export_obj["dom_iname"] = count_inter_domain;
1225                 count_inter_domain += 1;
1226             }
1227             parsed_data.push(export_obj);
1228 
1229             previous_domain = current_dom;
1230         }
1231         let count_data = {
1232             "type": type_full_counts,
1233             "aacounts_per_type": aacounts_per_type,
1234         }
1235         let sequence_properties = {};
1236         for (let is = 0; is < sequence.length; is++) {
1237             if (sequence_properties.hasOwnProperty(is+1) === false) {
1238                 sequence_properties[is+1] = {};
1239             }
1240         }
1241         return {"fullseq": sequence,"data": parsed_data, "count_data": count_data, "resid_properties": sequence_properties};
1242     }
1243 
1244     /**
1245      * Calculates draw areas for each element to be drawn on plot
1246      * @param {Array} array_data data generated from processRawUniProt
1247      * @namespace
1248      * @exports NaView
1249      * @name define_draw_areas
1250      * @namespace
1251      * @yields {Array} Array of three draw areas: protein data from processRawUniProt alongside draw areas, draw area for membrane, SVG draw area with borders and membrane main coords
1252      */
1253     function define_draw_areas(array_data) {
1254         var valorInicial = 0.0;
1255         var perc_total = array_data.reduce(function(acumulador, valorAtual, index, array) {
1256             return acumulador + valorAtual.aaperc;
1257         }, valorInicial);
1258         let svg_drawing_area = createDrawingArea();
1259         let parsed_lines_data = drawDataParsing(array_data);
1260         let dom_idom_data = calcDomIDomWidths(svg_drawing_area, parsed_lines_data["domain_names"], parsed_lines_data["helices_and_pores_by_domain"]); // to_print
1261         let longLoopWidthF = distributeLongLoopsWidth(dom_idom_data["inter_domain_width"], parsed_lines_data["long_loops"]);
1262         let indexed_drawareas = calcElementsDrawAreas(array_data, svg_drawing_area, parsed_lines_data["domain_names"], parsed_lines_data["helices_and_pores_by_domain"],parsed_lines_data["internal_short_loops_by_domain"],parsed_lines_data["long_loops_by_prevdomain"],longLoopWidthF, dom_idom_data["total_domain_width"],dom_idom_data["inter_domain_width"]);
1263         for (let id = 0; id < array_data.length; id++) {
1264             let nid = array_data[id].id;
1265             array_data[id]["draw_area"] = indexed_drawareas.protein[nid];
1266         }
1267         return [array_data, indexed_drawareas.membrane, svg_drawing_area];
1268     }
1269 
1270     /**
1271      * Calculates main plot draw areas for borders and membrane site
1272      * @exports NaView
1273      * @name createDrawingArea
1274      * @namespace
1275      * @yields {Object} SVG draw area with borders and membrane main coords
1276      */
1277     function createDrawingArea() {
1278         let da_stx = svg_width * style_obj["canvas"]["border"]["left"];
1279         let da_width = svg_width - (da_stx+(svg_width * style_obj["canvas"]["border"]["right"]));
1280         let da_etx = da_stx + da_width;
1281         let da_sty = svg_height * style_obj["canvas"]["border"]["top"];
1282         let da_height = svg_height - (da_sty+(svg_height*style_obj["canvas"]["border"]["bottom"]));
1283         let da_ety = da_sty + da_height;
1284         let da_ms = (da_height/2) - (svg_height*style_obj["membrane"]["membrane_region_height"])/2;
1285         let da_me = (da_height/2) + (svg_height*style_obj["membrane"]["membrane_region_height"])/2;
1286         let da_mh = svg_height*style_obj["membrane"]["membrane_region_height"];
1287         svg_drawarea = {
1288             "start_x": da_stx,
1289             "end_x": da_etx,
1290             "width": da_width,
1291             "start_y": da_sty,
1292             "end_y": da_ety,
1293             "height": da_height,
1294             "membrane_start": da_ms,
1295             "membrane_height": da_mh,
1296             "membrane_end": da_me,
1297         }
1298         return svg_drawarea;
1299     }
1300 
1301     /**
1302      * Separates parsed protein data array into sub groups according to protein element types (helix, pore, internal and external loops)
1303      * @param {Array} array_data data generated from processRawUniProt
1304      * @exports NaView
1305      * @name drawDataParsing
1306      * @namespace
1307      * @yields {Object} JSON containing array_data separated by protein element type (helix, pore, internal and external loops)
1308      */
1309     function drawDataParsing(array_data) {
1310         let helices_and_pores = array_data.filter(function(a) {
1311             return a.type === "helix" || a.type === "pore";
1312         });
1313     
1314         let helices_and_pores_by_domain = helices_and_pores.reduce( (reduce_dict, a) => {
1315             reduce_dict[a.dom_name] = [...reduce_dict[a.dom_name] || [], a];
1316             return reduce_dict;
1317         }, {});
1318     
1319         let internal_short_loops =  array_data.filter(function(a) {
1320             return a.type === "loop" && a.dom_name !== false;
1321         });
1322     
1323         let internal_short_loops_by_domain = internal_short_loops.reduce( (reduce_dict, a) => {
1324             reduce_dict[a.dom_name] = [...reduce_dict[a.dom_name] || [], a];
1325             return reduce_dict;
1326         }, {});
1327         
1328         let long_loops = array_data.filter(function(a) {
1329             return a.type === "loop" && a.prev_dom_name !== false && a.dom_name === false && a.next_dom_name;
1330         });
1331     
1332         let long_loops_by_prevdomain = long_loops.reduce( (reduce_dict, a) => {
1333             // reduce_dict[a.prev_dom_name] = [...reduce_dict[a.prev_dom_name] || [], a];
1334             reduce_dict[a.prev_dom_name] = a;
1335             return reduce_dict;
1336         }, {});
1337     
1338         let domain_names = Object.keys(helices_and_pores_by_domain);
1339         
1340         let result_obj = {
1341             "helices_and_pores": helices_and_pores,
1342             "helices_and_pores_by_domain": helices_and_pores_by_domain,
1343             "internal_short_loops": internal_short_loops,
1344             "internal_short_loops_by_domain": internal_short_loops_by_domain,
1345             "long_loops": long_loops,
1346             "long_loops_by_prevdomain": long_loops_by_prevdomain,
1347             "domain_names": domain_names,
1348         }
1349         return result_obj;
1350     }
1351 
1352     /**
1353      * Calculates each domain width from style_obj, determines full interdomain width
1354      * @param {Object} svg_drawing_area SVG draw area with borders and membrane main coords
1355      * @param {Array} domain_names list of domain names
1356      * @param {Array} helices_and_pores_by_domain Array of Objects from processRawUniProt for helices and pores of each domain
1357      * @exports NaView
1358      * @name calcDomIDomWidths
1359      * @namespace
1360      * @yields {Object} JSON for total domain width, interdomain width and dictionary for each domain's width
1361      */
1362     function calcDomIDomWidths(svg_drawing_area, domain_names, helices_and_pores_by_domain) {
1363         let total_domain_width = 0;
1364         let domain_to_width = {};
1365         for (let idn = 0; idn < domain_names.length; idn++) {
1366             let domain_name = domain_names[idn];
1367             let domain_width = 0;
1368             // let domain_spacing_width = (helices_and_pores_by_domain[domain_name].length-1)* style_obj["protein"]["helix_spacing_width"];
1369             let domain_spacing_width = style_obj["protein"]["helix_spacing_width"][domain_name].reduce(function(r, a) {
1370                 return r + a;
1371             }, 0);
1372             domain_width += domain_spacing_width;
1373             let total_perc_width = style_obj["protein"]["helix_region_width"][domain_name].reduce(function(r, a) {
1374                 return r + a;
1375             }, 0);
1376             let domain_helices_width = svg_width * total_perc_width;
1377             domain_width += domain_helices_width;
1378             let domain_pore_width = svg_width * style_obj["protein"]["pore_region_width"][domain_name];
1379             domain_width += domain_pore_width;
1380             domain_to_width[domain_name] = domain_width;
1381             total_domain_width += domain_width;
1382         }
1383         let n_ter_width = svg_width * style_obj["protein"]["nter_loop_width"];
1384         let c_ter_width = svg_width * style_obj["protein"]["cter_loop_width"];
1385     
1386         let inter_domain_width = svg_drawing_area["width"] - (total_domain_width+n_ter_width+c_ter_width);
1387     
1388         let return_obj = {
1389             "total_domain_width":total_domain_width,
1390             "inter_domain_width":inter_domain_width,
1391             "domain_to_width":domain_to_width
1392         }
1393         return return_obj;
1394     }
1395 
1396     /**
1397      * Creates scale functions for calculating the interdomain loops widths.<br>
1398      * Three setups are possible as defined in the style_obj:<br>
1399      * 1. Custom: user defined inside the style_obj <br>
1400      * 2. Scaled: according to amino acid number of each loop<br>
1401      * 3. Fixed: equally divided between the different loops <br>
1402      * @param {number} total_width the total interdomain width to be distributed
1403      * @param {Array} long_loops_array Array containing the interdomain loop objects from processRawUniProt
1404      * @exports NaView
1405      * @name distributeLongLoopsWidth
1406      * @namespace
1407      * @yields {Function} scale function for calculating the interdomain loops width
1408      */
1409     function distributeLongLoopsWidth(total_width, long_loops_array) {
1410         let loops_print_vars = style_obj["protein"]["long_loops_draw_opts"];
1411         let w_dist = loops_print_vars["width"];
1412         // let calc_len = loops_print_vars["calc_len"];
1413         let length_function;
1414         switch (w_dist.type) {
1415             case "custom":
1416                 length_function = function(context) {
1417                     return svg_width * calc_len[context];
1418                 }
1419                 break;
1420             case "scaled":
1421                 let total_aanum = long_loops_array.reduce(function(r, a){
1422                     return r + a.aanum;
1423                 }, 0);
1424                 length_function = d3.scaleLinear().range([0, total_width]).domain([0, total_aanum]);
1425                 break;
1426             case "reslen":
1427                 //this one requires pre-rendering which has not been implemented
1428                 break;
1429             default: //fixed
1430                 length_function = function(context) {
1431                     return total_width / long_loops_array.length;
1432                 }
1433                 break;
1434         }
1435         return length_function;
1436     }
1437 
1438     /**
1439      * Calculates main plot area for drawing the membrane
1440      * @param {Object} drawing_area SVG draw area with borders and membrane main coords
1441      * @exports NaView
1442      * @name calculateDrawAreaMembrane
1443      * @namespace
1444      * @yields {Object} draw area object for drawing the plasmatic membrane
1445      */
1446     function calculateDrawAreaMembrane(drawing_area) {
1447         let el_stx = drawing_area["start_x"];
1448         let el_etx = drawing_area["end_x"];
1449         let el_width = el_etx-el_stx;
1450     
1451         let el_sty = drawing_area["membrane_start"];
1452         let el_ety = drawing_area["membrane_end"];
1453         let el_height = el_ety-el_sty;
1454     
1455         let positioned_element = {
1456             "start_x": el_stx,
1457             "end_x": el_etx,
1458             "width": el_width,
1459             "start_y": el_sty,
1460             "end_y": el_ety,
1461             "height": el_height,
1462         };
1463         return positioned_element;
1464     }
1465 
1466     /**
1467      * Calculates main plot area for drawing N or C terminus loops by using style_obj definitions
1468      * @param {String} terminus_type N or C for the respective terminus type
1469      * @param {Object} drawing_area  SVG draw area with borders and membrane main coords
1470      * @param {Object} terminus_data  Object containing the terminus object from processRawUniProt
1471      * @param {number} total_domain_width total plot width for domain elements
1472      * @param {number} inter_domain_width total plot width for interdomain loops
1473      * @exports NaView
1474      * @name calculateDrawAreaTermini
1475      * @namespace
1476      * @yields {Object} draw area object for drawing N or C terminus loops
1477      */
1478     function calculateDrawAreaTermini(terminus_type, drawing_area, terminus_data, total_domain_width,inter_domain_width) {
1479         let return_obj = {
1480             "objs": [],
1481             "end_x": 0,
1482         };
1483         let el_stx = drawing_area["start_x"];
1484         let el_width = svg_width * style_obj["protein"]["nter_loop_width"];
1485         
1486         if (terminus_type === "C") {
1487             el_stx = drawing_area["start_x"]+el_width+(total_domain_width+inter_domain_width);
1488             el_width = svg_width * style_obj["protein"]["cter_loop_width"];
1489         }
1490         let el_etx = el_stx + el_width;
1491     
1492         let el_sty = drawing_area["start_y"];
1493         let el_ety = drawing_area["membrane_start"];
1494         if (terminus_data.note === "Cytoplasmic") {
1495             el_sty = drawing_area["membrane_end"];
1496             el_ety = drawing_area["end_y"];
1497         }
1498         let el_height = el_ety-el_sty;
1499     
1500         let positioned_element = {
1501             "start_x": el_stx,
1502             "end_x": el_etx,
1503             "width": el_width,
1504             "start_y": el_sty,
1505             "end_y": el_ety,
1506             "height": el_height,
1507         };
1508         terminus_data["draw_area"] = positioned_element;
1509         return_obj["objs"].push(terminus_data);
1510         return return_obj;
1511     }
1512 
1513     /**
1514      * Calculates main plot area for drawing domain intramembranar helices or pores by using style_obj definitions
1515      * @param {String} domain_name Name of current domain (I, II, III, IV)
1516      * @param {Array} helices_and_pores_array Array of Objects from processRawUniProt for helices and pores of each domain
1517      * @param {Object} drawing_area SVG draw area with borders and membrane main coords
1518      * @param {number} cumulative_x cumulative width of previously determined plot elements (left to right)
1519      * @exports NaView
1520      * @name calculateDrawAreaHelicesPoresByDomain
1521      * @namespace
1522      * @yields {Object} draw area object for drawing domain intramembranar helices or pores
1523      */
1524     function calculateDrawAreaHelicesPoresByDomain(domain_name, helices_and_pores_array, drawing_area, cumulative_x) {
1525         let return_obj = {
1526             "objs": [],
1527             "end_y": 0,
1528             "end_x": 0,
1529         };
1530         let accumulated_x = cumulative_x;
1531         let hcount = 0;
1532 
1533         let prev_dom_name;
1534         let dom_index = -1;
1535         for (let iel = 0; iel < helices_and_pores_array.length; iel++) {
1536             let helix_or_pore = helices_and_pores_array[iel];
1537             let dom_name = helix_or_pore["dom_name"];
1538             let el_stx = accumulated_x;
1539             let el_sty = drawing_area["membrane_start"];
1540             let el_width = 0; //style_obj["protein"]["helix_width"];
1541             let el_height = 0; //style_obj["protein"]["helix_height"];
1542             if (dom_name !== prev_dom_name) {
1543                 dom_index = -1;
1544                 prev_dom_name = dom_name + "";
1545             }
1546             dom_index += 1;
1547             if (helix_or_pore.type === "pore") {
1548                 el_width = svg_width * style_obj["protein"]["pore_region_width"][domain_name];
1549                 el_height = svg_height * style_obj["protein"]["pore_region_height"][domain_name];
1550             } else {
1551                 el_width = svg_width * style_obj["protein"]["helix_region_width"][domain_name][hcount];
1552                 el_height = svg_height * style_obj["protein"]["helix_region_height"][domain_name][hcount];
1553                 hcount += 1;
1554             }
1555             let positioned_element = {
1556                 "start_x": el_stx,
1557                 "end_x": (el_stx+el_width),
1558                 "width": el_width,
1559                 "start_y": el_sty,
1560                 "end_y": (el_sty+el_height),
1561                 "height": el_height,
1562             };
1563             helices_and_pores_array[iel]["draw_area"] = positioned_element;
1564             return_obj["objs"].push(helices_and_pores_array[iel]);
1565             return_obj["end_y"] = el_sty+el_height;
1566 
1567             accumulated_x += el_width;
1568             // let helixpore_spacing = style_obj["protein"]["helix_spacing_width"];
1569             let helixpore_spacing = style_obj["protein"]["helix_spacing_width"][dom_name][dom_index];
1570             if (iel < helices_and_pores_array.length-1) {
1571                 // helixpore_spacing = (iel+1) * style_obj["protein"]["helix_spacing_width"];
1572                 accumulated_x += helixpore_spacing;
1573             }
1574         }
1575         return_obj["end_x"] = accumulated_x;
1576         return return_obj;
1577     }
1578 
1579     /**
1580      * Inserts processRawUniProt objects containing draw areas in a dictionary according to their N to C termini indexa and style_obj definitions
1581      * @param {Object} protein_dict Object containing each processRawUniProt object according to their N to C termini index
1582      * @param {Array} array_to_dict Array of processRawUniProt objects containing draw areas
1583      * @exports NaView
1584      * @name indexDrawAreas
1585      * @namespace
1586      * @yields {Object} protein_dict Object containing each processRawUniProt object according to their N to C termini index
1587      */
1588     function indexDrawAreas(protein_dict, array_to_dict){
1589         for (let iatd = 0; iatd < array_to_dict.length; iatd++) {
1590             let obj = array_to_dict[iatd];
1591             protein_dict[obj.id] = obj.draw_area;
1592         }
1593         return protein_dict;
1594     }
1595 
1596     /**
1597      * Calculates main plot area for drawing domain loops connecting helices or pores and style_obj definitions
1598      * @param {Array} internal_short_loops_array Array of Objects from processRawUniProt for loops connecting helices and pores of each domain
1599      * @param {Array} helixpore_objs Array of Objects from processRawUniProt for helices and pores of each domain
1600      * @param {Object} drawing_area SVG draw area with borders and membrane main coords
1601      * @param {number} cumulative_x cumulative width of previously determined plot elements (left to right)
1602      * @exports NaView
1603      * @name calculateDrawAreaShortLoopsByDomain
1604      * @yields {Object} draw area object for drawing domain loops connecting helices or pores
1605      * @namespace 
1606      */
1607     function calculateDrawAreaShortLoopsByDomain(internal_short_loops_array, helixpore_objs, drawing_area, cumulative_x) {
1608         let return_obj = {
1609             "objs": [],
1610             "width": 0,
1611             "end_y": 0,
1612         };
1613     
1614         let accumulated_x = cumulative_x;
1615         for (let isl = 0; isl < internal_short_loops_array.length; isl++) {
1616             let current_internal_short_loop = internal_short_loops_array[isl];
1617             let previous_helixpore_el = helixpore_objs[isl];
1618             let next_helixpore_el = helixpore_objs[isl+1];
1619             // let el_stx = accumulated_x; //end of previous element
1620             let el_stx = previous_helixpore_el["draw_area"]["end_x"] - previous_helixpore_el["draw_area"]["width"]/2;
1621             let el_etx = next_helixpore_el["draw_area"]["start_x"] + next_helixpore_el["draw_area"]["width"]/2;
1622             let el_width = el_etx-el_stx;
1623     
1624             let el_sty = drawing_area["start_y"];
1625             let el_ety = drawing_area["membrane_start"];
1626             if (current_internal_short_loop.note === "Cytoplasmic") {
1627                 el_sty = drawing_area["membrane_end"];
1628                 el_ety = drawing_area["end_y"];
1629             }
1630             let el_height = el_ety-el_sty;
1631     
1632             let positioned_element = {
1633                 "start_x": el_stx,
1634                 "end_x": el_etx,
1635                 "width": el_width,
1636                 "start_y": el_sty,
1637                 "end_y": el_ety,
1638                 "height": el_height,
1639             };
1640             internal_short_loops_array[isl]["draw_area"] = positioned_element;
1641             return_obj["objs"].push(internal_short_loops_array[isl]);
1642         }
1643         return return_obj;
1644     }
1645 
1646     /**
1647      * Calculates main plot area for drawing interdomain loops by using style_obj definitions
1648      * @param {Object} long_loop_obj Object containing each processRawUniProt interdomain loop object
1649      * @param {number} cumulative_x cumulative width of previously determined plot elements (left to right)
1650      * @param {number} last_y plot y positioning of the last intradomain helix (Helix:6)
1651      * @param {Function} longLoopWidthF scale function for calculating the interdomain loops width from <i><b>distributeLongLoopsWidth</b></i>
1652      * @param {Object} drawing_area  SVG draw area with borders and membrane main coords
1653      * @exports NaView
1654      * @name calculateDrawAreaLongLoops
1655      * @yields {Object} draw area object for drawing interdomain loops
1656      * @namespace
1657      */
1658     function calculateDrawAreaLongLoops(long_loop_obj, cumulative_x, last_y, longLoopWidthF, drawing_area) {
1659         let return_obj = {
1660             "objs": [],
1661             "end_x": 0,
1662         };
1663         let accumulated_x = cumulative_x;
1664         
1665         let prev_dom_helix_data = style_obj["protein"]["helix_region_width"][long_loop_obj.prev_dom_name];
1666         let prev_dom_helix_data_sub = (svg_width * (prev_dom_helix_data[prev_dom_helix_data.length-1])/2)
1667         let next_dom_helix_data = style_obj["protein"]["helix_region_width"][long_loop_obj.next_dom_name];
1668         let next_dom_helix_data_add = (svg_width * (next_dom_helix_data[0])/2)
1669     
1670         let el_stx = accumulated_x - prev_dom_helix_data_sub;
1671     
1672         let context_data = long_loop_obj;
1673         if (style_obj["protein"]["long_loops_draw_opts"]["width"]["type"] !== "custom") {
1674             context_data = long_loop_obj.aanum;
1675         }
1676     
1677         // let calc_width = longLoopWidthF(long_loop_obj.aanum);
1678         let calc_width = longLoopWidthF(context_data);
1679         let el_width = calc_width + prev_dom_helix_data_sub + next_dom_helix_data_add;
1680         // let el_etx = accumulated_x + el_width;
1681     
1682         let el_sty = drawing_area["start_y"];
1683         let el_ety = drawing_area["membrane_start"];
1684         if (long_loop_obj.note === "Cytoplasmic") {
1685             el_sty = drawing_area["membrane_end"];
1686             el_ety = drawing_area["end_y"];
1687         }
1688         let el_height = el_ety-el_sty;
1689         let positioned_element = {
1690             "start_x": el_stx,
1691             "end_x": (el_stx+el_width),
1692             "width": el_width,
1693             "start_y": el_sty,
1694             "end_y": (el_sty+el_height),
1695             "height": el_height,
1696         };
1697         long_loop_obj["draw_area"] = positioned_element;
1698         return_obj["objs"].push(long_loop_obj);
1699         // accumulated_x += longLoopWidthF(long_loop_obj.aanum);
1700         accumulated_x += longLoopWidthF(context_data);
1701         return_obj["end_x"] = accumulated_x;
1702         return return_obj;
1703     }
1704 
1705     /**
1706      * Function organizes protein element order for generating draw areas for every protein and membrane elements.<br>
1707      * @see calculateDrawAreaMembrane
1708      * @see calculateDrawAreaTermini
1709      * @see calculateDrawAreaHelicesPoresByDomain
1710      * @see indexDrawAreas
1711      * @see calculateDrawAreaLongLoops
1712      * @param {Array} array_data data generated from processRawUniProt
1713      * @param {Object} svg_drawing_area SVG draw area with borders and membrane main coords
1714      * @param {Array} domain_names list of names for each domain(I,II,III,IV)
1715      * @param {Object} helices_and_pores_by_domain dictionary for helices and pore elements according to their domain names (I,II,III,IV)
1716      * @param {Object} internal_short_loops_by_domain dictionary for loops connecting helices and pore elements according to their domain names (I,II,III,IV)
1717      * @param {Object} long_loops_by_prevdomain dictionary for interdomain loop elements according to their previous domain names (I,II,III)
1718      * @param {Function} longLoopWidthF scale function for calculating the interdomain loops width from <i><b>distributeLongLoopsWidth</b></i>
1719      * @param {number} total_domain_width total plot width for domain objects
1720      * @param {number} inter_domain_width total plot width for interdomain objects
1721      * @exports NaView
1722      * @name calcElementsDrawAreas
1723      * @namespace
1724      * @yields {Object} Object containing draw areas for every protein and membrane elements
1725      */
1726     function calcElementsDrawAreas(array_data, svg_drawing_area, domain_names, helices_and_pores_by_domain, internal_short_loops_by_domain, long_loops_by_prevdomain, longLoopWidthF, total_domain_width, inter_domain_width) {
1727         let indexed_drawareas = {
1728             "protein": {},
1729             "membrane": undefined
1730         };
1731         let plines = [];
1732         
1733         let membrane_drawarea = calculateDrawAreaMembrane(svg_drawing_area);
1734         indexed_drawareas.membrane = membrane_drawarea;
1735         
1736         let cumulative_x = svg_drawing_area["start_x"] + (svg_width*style_obj["protein"]["nter_loop_width"]); //cter_loop_width
1737     
1738         let n_terminus_data = array_data[0];
1739         let n_terminus_drawarea = calculateDrawAreaTermini("N", svg_drawing_area, n_terminus_data,total_domain_width, inter_domain_width);
1740         // indexed_drawareas.protein[0] = n_terminus_drawarea.objs[0].draw_area;
1741         indexed_drawareas.protein[1] = n_terminus_drawarea.objs[0].draw_area;
1742     
1743         let c_terminus_data = array_data[array_data.length-1];
1744         let c_terminus_drawarea = calculateDrawAreaTermini("C", svg_drawing_area, c_terminus_data,total_domain_width, inter_domain_width);
1745         // indexed_drawareas.protein[array_data.length-1] = c_terminus_drawarea.objs[c_terminus_drawarea.objs.length-1].draw_area;
1746         indexed_drawareas.protein[array_data.length] = c_terminus_drawarea.objs[c_terminus_drawarea.objs.length-1].draw_area;
1747     
1748         for (let idn = 0; idn < domain_names.length; idn++) {
1749             let domain_name = domain_names[idn];
1750     
1751             let domain_assignment_results_helixpore = calculateDrawAreaHelicesPoresByDomain(domain_name, helices_and_pores_by_domain[domain_name], svg_drawing_area, cumulative_x);
1752             indexed_drawareas.protein = indexDrawAreas(indexed_drawareas.protein, domain_assignment_results_helixpore.objs);
1753     
1754             let domain_assignment_results_shortloops = calculateDrawAreaShortLoopsByDomain(internal_short_loops_by_domain[domain_name], domain_assignment_results_helixpore.objs, svg_drawing_area, cumulative_x);
1755             indexed_drawareas.protein = indexDrawAreas(indexed_drawareas.protein, domain_assignment_results_shortloops.objs);
1756     
1757             // cumulative_x += domain_assignment_results_helixpore.width;
1758             // cumulative_x += domain_to_width[domain_name];
1759             cumulative_x = domain_assignment_results_helixpore.end_x;
1760             
1761             let last_y = domain_assignment_results_helixpore.end_y;
1762             let long_loop_obj = long_loops_by_prevdomain[domain_name];
1763             if (long_loop_obj) {
1764                 let inter_domain_assignment_results = calculateDrawAreaLongLoops(long_loop_obj, cumulative_x, last_y, longLoopWidthF, svg_drawing_area);
1765                 indexed_drawareas.protein = indexDrawAreas(indexed_drawareas.protein, inter_domain_assignment_results.objs);
1766                 cumulative_x = inter_domain_assignment_results.end_x;
1767             }
1768         }
1769         return indexed_drawareas;
1770     }
1771 
1772     /**
1773      * Draws whole plot using various accessory functions
1774      * @param {Array} parsed_protein_data data generated from processRawUniProt containing draw areas
1775      * @exports NaView
1776      * @name drawEverything
1777      * @namespace
1778      * @see createFillRules
1779      * @see draw_membrane
1780      * @see mergeDrawData
1781      * @see createResidData
1782      * @see calculateResidOrientation
1783      * @see draw_helices
1784      * @see gen_shortloop_anchordata
1785      * @see draw_shortLoops
1786      * @see gen_poreloop_anchordata
1787      * @see draw_poreLoops
1788      * @see gen_longloop_anchordata
1789      * @see draw_longLoops
1790      * @see gen_termini_anchordata
1791      * @see draw_termini
1792      * @see createResidElementToCentroidData
1793      * @see draw_residue_relations
1794      * @see draw_symbols
1795      */
1796     function drawEverything(parsed_protein_data) {
1797         let membrane_data = parsed_protein_data.membrane;
1798         for (const membrane_draw_key in style_obj.membrane.membrane_draw_opts) {
1799             if (style_obj.membrane.membrane_draw_opts.hasOwnProperty(membrane_draw_key)) {
1800                 membrane_data[membrane_draw_key] = style_obj.membrane.membrane_draw_opts[membrane_draw_key];
1801             }
1802         }
1803         current_resid_properties = deepCopy(parsed_protein_data.resid_properties);
1804         if (parsed_protein_data.hasOwnProperty("color_rules")) {
1805             createFillRules(parsed_protein_data.color_rules);
1806         }
1807         draw_membrane(membrane_data);
1808         let helices_data = parsed_protein_data.data.filter(function(a) {
1809             return a.type === "helix";
1810         });
1811         let helices_pores_data = parsed_protein_data.data.filter(function(a) {
1812             return a.type === "helix" || a.type === "pore";
1813         });
1814         let short_loop_data = parsed_protein_data.data.filter(function(a) {
1815             return a.type === "loop" && a.dom_name !== false;
1816         });
1817         let longloop_data = parsed_protein_data.data.filter(function(a) {
1818             return a.type === "loop" && a.dom_name === false && a.prev_dom_name && a.next_dom_name;
1819         });
1820         let nter_loop_data = parsed_protein_data.data.filter(function(a) {
1821             return a.type === "loop" && a.dom_name === false && a.prev_dom_name === false && !a.next_dom_name;
1822         });
1823         let cter_loop_data = parsed_protein_data.data.filter(function(a) {
1824             return a.type === "loop" && a.dom_name === false && a.prev_dom_name && !a.next_dom_name;
1825         });
1826         let all_loop_data = parsed_protein_data.data.filter(function(a) {
1827             return a.type === "loop";
1828         });
1829         let pores_data = parsed_protein_data.data.filter(function(a) {
1830             return a.type === "pore";
1831         });
1832 
1833         // let n_terminus_note = parsed_protein_data.data[0].note;
1834 
1835         helices_data = mergeDrawData("helix", helices_data);
1836         helices_data = createResidData(helices_data);
1837         helices_data = calculateResidOrientation(helices_data, all_loop_data);
1838         draw_helices(helices_data);
1839 
1840         short_loop_data = mergeDrawData("short_loops", short_loop_data);
1841         short_loop_data = createResidData(short_loop_data);
1842         short_loop_data = gen_shortloop_anchordata(short_loop_data, helices_pores_data);
1843         draw_shortLoops(short_loop_data);
1844 
1845         pores_data = mergeDrawData("pore_loops", pores_data);
1846         pores_data = createResidData(pores_data);
1847         pores_data = gen_poreloop_anchordata(pores_data, short_loop_data);
1848         draw_poreLoops(pores_data);
1849 
1850         longloop_data = mergeDrawData("long_loops", longloop_data);
1851         longloop_data = createResidData(longloop_data);
1852         longloop_data = gen_longloop_anchordata(longloop_data, helices_data);
1853         draw_longLoops(longloop_data);
1854 
1855         let sorted_helices = deepCopy(helices_data).sort(function(a, b) {
1856             return a.start - b.start;
1857         });
1858 
1859         nter_loop_data = mergeDrawData("nter_loop", nter_loop_data);
1860         nter_loop_data = createResidData(nter_loop_data);
1861         nter_loop_data = gen_termini_anchordata(nter_loop_data[0],sorted_helices[0], "N");
1862         draw_termini(nter_loop_data);
1863 
1864         cter_loop_data = mergeDrawData("cter_loop", cter_loop_data);
1865         cter_loop_data = createResidData(cter_loop_data);
1866         cter_loop_data = gen_termini_anchordata(cter_loop_data[0],sorted_helices[sorted_helices.length-1], "C");
1867         draw_termini(cter_loop_data);
1868         parsed_protein_data.residue_element_centroids = createResidElementToCentroidData();
1869 
1870         if (parsed_protein_data.hasOwnProperty("residue_relations")) {
1871             draw_residue_relations(parsed_protein_data.residue_relations, parsed_protein_data.residue_element_centroids,parsed_protein_data.data);
1872         }
1873         if (parsed_protein_data.hasOwnProperty("draw_symbols")) {
1874             draw_symbols(parsed_protein_data.draw_symbols, parsed_protein_data.residue_element_centroids,parsed_protein_data.resid_properties,parsed_protein_data.data);
1875         }
1876         if (parsed_protein_data.hasOwnProperty("color_rules")) {
1877             if (parsed_protein_data.color_rules.length > 0) {
1878                 d3.selectAll(".element_path").style("visibility", "hidden");
1879                 d3.selectAll(".residue_path").style("visibility", "");
1880             }
1881         }
1882     }
1883 
1884     /**
1885      * Resets global fillRules object and generates a new color filling object for each user defined color rule.<br>
1886      * When multiple user defined color rules are applied to the same residue/element, the last rule takes precedence.<br>
1887      * Color rules allow the selection of residues or elements (helices, pore, loops) by the "selection,color" syntax.<br>
1888      * Multiple "selection" possibilities are allowed including: <br>
1889      * "W" -> Selects all tryptophan residues <br>
1890      * "Trp" -> Select all tryptophan residues <br>
1891      * "1" -> Selects first residue of protein sequence <br>
1892      * "W1" -> Selects first residue of protein sequence <br>
1893      * "Trp1" -> Selects first residue of protein sequence <br>
1894      * "Domain:I" -> Selects first domain <br>
1895      * "Domain:I&Helix:1" -> Selects first domain <br>
1896      * "Domain:I&Loop:1" -> Selects first domain <br>
1897      * "Helix:1" -> Selects first helix of all domains <br>
1898      * "Loop:1" -> Selects first loop of all domains <br>
1899      * "Id:1" -> Selects first residue of protein sequence <br>
1900      * "ALL" -> Selects all protein residues <br>
1901      * The "color" possibilities include both named and hexadecimal colors: <br> <br>
1902      * "white" -> colors selection white <br>
1903      * "#FFFFFF" -> colors selection white <br>
1904      * an advanced color mapping to user inputted residue properties is also possible using the following syntax after a "selection" string: <br>
1905      * "by:Residue_Property_Name,color_range,color_property_domain" <br>
1906      * Residue_Property_Name -> refers to the user inputted residue property <br>
1907      * color_range -> a color range from each to map this property. valid examples: "white;green" or "red;white;blue" <br>
1908      * color_property_domain -> the color domain to which the color range refers to. valid examples: "min;max" (from minimum to maximum value), "0.0;0.5;1.0" <br>
1909      * @param {Array} data_color list of Strings inputted from user to generate per element/residue color filling rules
1910      * @namespace
1911      * @exports NaView
1912      * @name createFillRules
1913      */
1914     function createFillRules(data_color) {
1915         fillRules = [];
1916         for (let idc = 0; idc < data_color.length; idc++) {
1917             let fillObj = {
1918                 "check_keys": [],
1919                 "check_values":[],
1920                 "color":undefined,
1921                 "range":undefined,
1922                 "domain":undefined,
1923                 "get": undefined,
1924             };
1925             let selection_text = data_color[idc];
1926             let color_rule = selection_text.split(",")[0];
1927             let color_rule_keys = color_rule.split("&");
1928             for (let crk = 0; crk < color_rule_keys.length; crk++) {
1929                 let color_rule = color_rule_keys[crk];
1930                 if (/\d+/.test(color_rule)) {
1931                     let color_number = color_rule.match(/\d+/)[0];
1932                     let color_rule_type = color_rule.split(color_number+"")[0];
1933                     if (color_rule_type === "Helix:") {
1934                         fillObj.check_keys.push("type");
1935                         fillObj.check_values.push(["helix"]);
1936                         fillObj.check_keys.push("dom_itype");
1937                         fillObj.check_values.push([parseInt(color_number)]);
1938                     } else if (color_rule_type === "Loop:") {
1939                         fillObj.check_keys.push("type");
1940                         fillObj.check_values.push(["loop"]);
1941                         fillObj.check_keys.push("dom_itype");
1942                         fillObj.check_values.push([parseInt(color_number)]);
1943                     } else if (color_rule_type === "Id:") {
1944                         fillObj.check_keys.push("id");
1945                         fillObj.check_values.push([parseInt(color_number)]);
1946                     } else if (Object.keys(one_to_three).indexOf(color_rule_type) > -1) {
1947                         fillObj.check_keys.push("res_ind");
1948                         fillObj.check_values.push([parseInt(color_number)]);
1949                         fillObj.check_keys.push("res_1");
1950                         fillObj.check_values.push([color_rule_type]);
1951                     } else if (Object.keys(three_to_one).indexOf(color_rule_type) > -1) {
1952                         fillObj.check_keys.push("res_ind");
1953                         fillObj.check_values.push([parseInt(color_number)]);
1954                         fillObj.check_keys.push("res_3");
1955                         fillObj.check_values.push([color_rule_type]);
1956                     } else if (color_rule_type === "") {
1957                         fillObj.check_keys.push("res_ind");
1958                         fillObj.check_values.push([parseInt(color_number)]);
1959                     }
1960                 } else if (color_rule.includes("Domain:")) {
1961                     fillObj.check_keys.push("dom_name");
1962                     fillObj.check_values.push([color_rule.split("Domain:")[1]]);
1963                 } else if (Object.keys(one_to_three).indexOf(color_rule) > -1)  {
1964                     fillObj.check_keys.push("res_1");
1965                     fillObj.check_values.push([color_rule]);
1966                 } else if (Object.keys(three_to_one).indexOf(color_rule) > -1) {
1967                     fillObj.check_keys.push("res_3");
1968                     fillObj.check_values.push([color_rule]);
1969                 } else if (color_rule === "ALL") {
1970                     fillObj.check_keys.push("res_3");
1971                     fillObj.check_values.push(deepCopy(Object.keys(three_to_one)));
1972                 }
1973             }
1974             let color_to_map = selection_text.split(",")[1];
1975             if (color_to_map.includes("by:")) {
1976                 fillObj.get = color_to_map.split("by:")[1];
1977                 fillObj.range = selection_text.split(",")[2].split(";");
1978                 fillObj.domain = selection_text.split(",")[3].split(";");
1979             } else {
1980                 fillObj.color = color_to_map;
1981             }
1982             fillRules.push(fillObj);
1983         }
1984     }
1985 
1986     /**
1987      * Plots membrane as a rectangle
1988      * @param {Object} data draw area Object merged to style_obj for drawing membrane
1989      * @param {String} dataId membrane main g element id attribute 
1990      * @namespace
1991      * @exports NaView
1992      * @name draw_membrane_box
1993      */
1994     function draw_membrane_box(data, dataId) {
1995         let svg_element = d3.select("#"+svg_id)
1996             .append("g")
1997             .attr("class", "membrane_group")
1998             .append("rect")
1999             .attr("x", data["start_x"])
2000             .attr("y", data["start_y"])
2001             .attr("width", data["width"])
2002             .attr("height", data["height"])
2003             .attr("fill", data["fill"])
2004             .attr("opacity", data["opacity"])
2005             ;
2006         if (dataId) {
2007             svg_element.attr("id", dataId);
2008         }
2009     }
2010 
2011     /**
2012      * Generates lipid head related data for drawing ellipses representing the membrane bilayer lipids head grouÅ›
2013      * @param {number} number_of_lipids total number of lipids to be drawn on a single membrane layer 
2014      * @param {number} shead_x starting x coordinate of a lipid
2015      * @param {number} head_y1  starting y coordinate of a lipid in the top bilayer
2016      * @param {number} head_y2  starting y coordinate of a lipid in the bottom bilayer
2017      * @param {String} head_fill color of each lipid head
2018      * @param {number} head_opacity opacity of each lipid head
2019      * @param {String} head_stroke  stroke color of each lipid head
2020      * @param {String} head_stroke_s stroke width (px) of each lipid head
2021      * @param {number} lip_rad lipid head ellipse radii 1
2022      * @param {number} lip_rad_y  lipid head ellipse radii 1
2023      * @namespace
2024      * @exports NaView
2025      * @name gen_lipid_head_data
2026      * @yields {Array} list of plot related data for generating lipid head ellipse drawings
2027      */
2028     function gen_lipid_head_data(number_of_lipids, shead_x, head_y1, head_y2, head_fill, head_opacity,head_stroke,head_stroke_s,lip_rad,lip_rad_y) {
2029         let head_array = [];
2030         for (let lip = 0; lip < number_of_lipids; lip++) {
2031             let head_x = (lip*lip_rad*2) + shead_x;
2032             let head_obj1 = {
2033                 "cx": head_x,
2034                 "cy": head_y1,//+lip_rad_y,
2035                 "r": lip_rad,
2036                 "rx": lip_rad,
2037                 "ry": lip_rad_y,
2038                 // "ry": lip_rad,
2039                 // "ry": lip_rad*(svg_height/svg_width),
2040                 "fill": head_fill,
2041                 "opacity": head_opacity,
2042                 "stroke": head_stroke,
2043                 "stroke_size": head_stroke_s,
2044                 "type": "upper"
2045             };
2046             let head_obj2 = {
2047                 "cx": head_x,
2048                 "cy": head_y2,//-lip_rad_y,
2049                 "r": lip_rad,
2050                 "rx": lip_rad,
2051                 "ry": lip_rad_y,
2052                 // "ry": lip_rad,
2053                 // "ry": lip_rad*(svg_height/svg_width),
2054                 "fill": head_fill,
2055                 "opacity": head_opacity,
2056                 "stroke": head_stroke,
2057                 "stroke_size": head_stroke_s,
2058                 "type": "lower"
2059             };
2060             head_array.push(head_obj1);
2061             head_array.push(head_obj2);
2062         }
2063         return head_array;
2064     }
2065 
2066     /**
2067      * Calculates the y coordinate of a point in the surface of a sphere.
2068      * This is done given:<br>
2069      * the x coordinates<br>
2070      * the ellipse center coordinates <br>
2071      * the ellipse radii <br>
2072      * @param {number} gx x coordinate of point at ellipse surface
2073      * @param {number} cx x coordinate of ellipse center
2074      * @param {number} cy y coordinate of ellipse center
2075      * @param {number} rx ellipse radii 1
2076      * @param {number} ry ellipse radii 2
2077      * @namespace
2078      * @exports NaView
2079      * @name ellipsis_equation_y
2080      * @yields {number} y coordinate of a point in the surface of a sphere.
2081      */
2082     function ellipsis_equation_y(gx, cx, cy, rx, ry) {
2083         return (Math.sqrt((1 - Math.pow((gx-cx),2)/Math.pow(rx,2)) * Math.pow(ry,2)))+cy;
2084     }
2085 
2086     /**
2087      * Generate lipid tail paths data by calculating tail start and end points.<br>
2088      * For double lipid tails, it is necessary to find the y coordinates of a point in the surface
2089      * of the lipid heads ellipse elements.
2090      * @see ellipsis_equation_y
2091      * @param {Array} head_data list of plot related data for generating lipid head ellipse drawings
2092      * @param {number} lipid_tail_number  total number of lipids to be drawn on a single membrane layer 
2093      * @param {number} lipid_break_num  total number of lipid breaks (not implemented)
2094      * @param {number} tail_max_length lipid tail total length (px)
2095      * @param {number} tail_head_dist (deprecated)
2096      * @param {String} tail_fill color of lipid tail fill
2097      * @param {number} tail_opacity opacity of lipid tail
2098      * @param {String} tail_stroke stroke color of lipid tail
2099      * @param {String} tail_stroke_s stroke size (px) of lipid tail
2100      * @namespace
2101      * @exports NaView
2102      * @name gen_lipid_tail_data
2103      * @yields {Array} list of plot related data for generating lipid tail path drawings
2104      */
2105     function gen_lipid_tail_data(head_data, lipid_tail_number, lipid_break_num, tail_max_length, tail_head_dist,tail_fill,tail_opacity,tail_stroke,tail_stroke_s) {
2106         let lipid_data = [];
2107         for (let ihd = 0; ihd < head_data.length; ihd++) {
2108             let h_data = head_data[ihd];
2109             let lipid_objs = [];
2110             if (lipid_tail_number === 1) {
2111                 //add one lipid tail
2112                 let lipid_line = {
2113                     "fill": tail_fill,
2114                     "opacity": tail_opacity,
2115                     "stroke": tail_stroke,
2116                     "stroke_size": tail_stroke_s,
2117                     "coords": [
2118                         [h_data.cx,h_data.cy+h_data.ry]
2119                     ],
2120                 }
2121                 lipid_objs.push(lipid_line);
2122             } else {
2123                 let one_third_x = h_data.cx-h_data.rx + (h_data.rx/1.8);
2124                 let two_thirds_x = h_data.cx+h_data.rx-(h_data.rx/1.8);
2125 
2126                 let other_y = ellipsis_equation_y(one_third_x, h_data.cx, h_data.cy, h_data.rx, h_data.ry);
2127                 // let other_y = h_data.cy;
2128                 // other_y += (tail_stroke_s)/2;
2129                 // other_y += (h_data.stroke_size);
2130                 // other_y += Math.sqrt(h_data.stroke_size);
2131 
2132                 //add two lipid tails
2133                 let lipid_line1 = {
2134                     "fill": tail_fill,
2135                     "opacity": tail_opacity,
2136                     "stroke": tail_stroke,
2137                     "stroke_size": tail_stroke_s,
2138                     "coords": [
2139                         [one_third_x,other_y]
2140                     ],
2141                 }
2142                 lipid_objs.push(lipid_line1);
2143                 let lipid_line2 = {
2144                     "fill": tail_fill,
2145                     "opacity": tail_opacity,
2146                     "stroke": tail_stroke,
2147                     "stroke_size": tail_stroke_s,
2148                     "coords": [
2149                         [two_thirds_x,other_y]
2150                     ],
2151                 }
2152                 lipid_objs.push(lipid_line2);
2153             }
2154             if (lipid_break_num > 1) {
2155                 let prevy = 0;
2156                 let defy = tail_max_length/lipid_break_num;
2157                 for (let ilo = 0; ilo < lipid_objs.length; ilo++) {
2158                     //TODO
2159                     //calculate breaks for each lipid tail
2160                     //for each break, move forward xperc then backward x perc
2161                     // lipid_objs[ilo];
2162                     return;
2163                 }
2164             } else {
2165                 let prevy = 0;
2166                 let defy = tail_max_length;
2167                 if (h_data.type === "lower") {
2168                     prevy = ((h_data.ry*2)-h_data.stroke_size) * -1;
2169                     defy = tail_max_length * -1;
2170                 }
2171                 for (let ilo = 0; ilo < lipid_objs.length; ilo++) {
2172                     let prev_coord = lipid_objs[ilo].coords[lipid_objs[ilo].coords.length-1];
2173                     prev_coord[1] += prevy;
2174                     lipid_objs[ilo].coords.push(
2175                         [prev_coord[0],prev_coord[1]+defy]
2176                     );
2177                 }
2178             }
2179             //extend lipid_data with each lipid tail line obj;
2180             lipid_data.push(...lipid_objs);
2181         }
2182         return lipid_data;
2183     }
2184 
2185     /**
2186      * Plots membrane as a lipid bilayer
2187      * @see gen_lipid_head_data
2188      * @see gen_lipid_tail_data
2189      * @param {Object} data data object for membrane draw area object merged to style_obj definitions
2190      * @param {String} dataId membrane main g element id attribute 
2191      * @param {number} tail_head_dist deprecated: height ratio between head and tail groups
2192      * @namespace
2193      * @exports NaView
2194      * @name draw_membrane_lipid
2195      */
2196     function draw_membrane_lipid(data, dataId, tail_head_dist) {
2197 
2198         let lipid_head_radius_width = data["lipid_head_radius_width"] * svg_width;
2199         let ratio_wh = svg_height/svg_width;
2200         let lipid_head_radius_height = lipid_head_radius_width * ratio_wh * 2;
2201 
2202         let number_of_lipids = data["width"] / (lipid_head_radius_width*2);
2203         
2204         let shead_x = data["start_x"] + (lipid_head_radius_width/2);
2205         let head_y = data["start_y"] + (lipid_head_radius_height);
2206         let head_y2 = data["end_y"] - (lipid_head_radius_height);
2207 
2208         let head_fill = data["hfill"];
2209         let head_stroke = data["hstroke"];
2210         let head_stroke_s = data["hstroke_s"];
2211         let head_opacity = data["hopacity"];
2212 
2213         let head_data = gen_lipid_head_data(number_of_lipids, shead_x, head_y, head_y2, head_fill, head_opacity,head_stroke,head_stroke_s, lipid_head_radius_width, lipid_head_radius_height);
2214 
2215         let membrane_mid_point = svg_drawarea["height"];
2216         let tail_max_length = svg_drawarea["membrane_height"]/2 - ((lipid_head_radius_height*2) + (svg_height * data["lipid_tail_spacing"]) );
2217         if (!tail_head_dist) {
2218             tail_head_dist = 0.1;
2219         }
2220         let tail_fill = data["tfill"];
2221         let tail_opacity = data["topacity"];
2222         let tail_stroke = data["tstroke"];
2223         let tail_stroke_s = data["tstroke_s"];
2224         let tail_data = gen_lipid_tail_data(head_data, data["lipid_tail_number"], data["lipid_tail_breaks"], tail_max_length, tail_head_dist, tail_fill, tail_opacity,tail_stroke,tail_stroke_s);
2225 
2226         //draw membrane heads
2227         let membrane_group = d3.select("#"+svg_id)
2228         .append("g")
2229         .attr("class", "membrane_group");
2230 
2231         membrane_group.append("g").attr("class","membrane_heads")
2232         .selectAll(".lipid_heads")
2233         .data(head_data)
2234         .join(
2235             function(enter) {
2236                 return enter
2237                     .append("ellipse")
2238                     .attr("class", "lipid_heads")
2239                     .attr("cx",function(d){
2240                         return d.cx;
2241                     })
2242                     .attr("cy", function(d){
2243                         return d.cy;
2244                     })
2245                     .attr("rx", function(d) { return d.rx;})
2246                     .attr("ry", function(d) { return d.ry;})
2247                     .attr("fill", function(d) { return d.fill;})
2248                     .attr("stroke", function(d) { return d.stroke;})
2249                     .attr("stroke-width", function(d) { return d.stroke_size;})
2250                     .attr("opacity", function(d) { return d.opacity;})
2251                     ;
2252             },
2253             function(exit) {
2254                 return exit.remove();
2255             }
2256         );
2257 
2258         let tail_lines = d3.line();
2259 
2260         membrane_group.append("g").attr("class","membrane_tails")
2261         .selectAll(".lipid_tails")
2262         .data(tail_data)
2263         .join(
2264             function(enter) {
2265                 return enter
2266                     .append("path")
2267                     .attr("class", "lipid_tails")
2268                     .attr("d", function(d){
2269                         return tail_lines(d.coords);
2270                     })
2271                     .attr("fill", function(d) { return d.fill;})
2272                     .attr("stroke", function(d) { return d.stroke;})
2273                     .attr("stroke-width", function(d) { return d.stroke_size;})
2274                     .attr("opacity", function(d) { return d.opacity;})
2275                     ;
2276             },
2277             function(exit) {
2278                 return exit.remove();
2279             }
2280         );
2281     }
2282 
2283     /**
2284      * Draws membrane object as either a single rectangle (mode box) or as a lipid bilayer
2285      * @see draw_membrane_box
2286      * @see draw_membrane_lipid
2287      * @param {Object} data data object for membrane draw area object merged to style_obj definitions
2288      * @namespace
2289      * @exports NaView
2290      * @name draw_membrane
2291      */
2292     function draw_membrane(data) {
2293         //membrane can be box or lipid
2294         if (style_obj.membrane.membrane_mode === "box") {
2295             draw_membrane_box(data, "membrane");
2296             // draw_membrane_box(data, "membrane", "membrane");
2297         } else if (style_obj.membrane.membrane_mode === "lipid") {
2298             // draw_membrane_lipid(data, "membrane", "membrane");
2299             draw_membrane_lipid(data, "membrane");
2300         }
2301     }
2302 
2303     /**
2304      * Merges data from the user defined style_obj draw_opts to the current residue/element selection
2305      * @param {String} mergeType element name in the style_obj ("element_name"+draw_opts)
2306      * @param {Array} data data array for a given element (helices, pores, intra or inter domain loops)
2307      * @yields {Array} data array for a given element with user defined style_obj draw_opts definitions
2308      * @namespace
2309      * @exports NaView
2310      * @name mergeDrawData
2311      */
2312     function mergeDrawData(mergeType, data) {
2313         let drawKey = mergeType+"_draw_opts";
2314         let drawDict = style_obj["protein"][drawKey];
2315         let properties = Object.keys(drawDict);
2316         if (drawDict.type === "dicts") {
2317             for (let id = 0; id < data.length; id++) {
2318                 for (let ip = 0; ip < properties.length; ip++) {
2319                     let prop = properties[ip];
2320                     let to_draw = drawDict[prop];
2321                     if (to_draw.type === "domain_and_name") {
2322                         let dom = data[id].dom_name;
2323                         let itypen = data[id].dom_itype;
2324                         data[id][prop] = to_draw[dom][itypen];
2325                     } else if (to_draw.type === "domain") {
2326                         let dom = data[id].dom_name;
2327                         data[id][prop] = to_draw[dom];
2328                     } else if (to_draw.type === "name") {
2329                         let itypen = data[id].dom_itype;
2330                         data[id][prop] = to_draw[itypen];
2331                     }
2332                 }
2333             }
2334         } else if (drawDict.type === "single") {
2335             for (let id = 0; id < data.length; id++) {
2336                 for (let ip = 0; ip < properties.length; ip++) {
2337                     let prop = properties[ip];
2338                     if (prop !== "type") {
2339                         data[id][prop] = drawDict[prop];
2340                     }
2341                 }
2342             }
2343         }
2344         return data;
2345     }
2346     
2347     /**
2348      * Builds data for each residue for a given element specific (helices, pores, intra or inter domain loops) data array
2349      * @param {Array} data data array for a given element (helices, pores, intra or inter domain loops)
2350      * @yields {Array} element specific data array with per residue information now appended
2351      * @namespace
2352      * @exports NaView
2353      * @name createResidData
2354      */
2355     function createResidData(data) {
2356         for (let id = 0; id < data.length; id++) {
2357             data[id].resids = [];
2358             let resids = data[id].aas.split("");
2359             let resid_index = data[id].start;
2360             for (let ir = 0; ir < resids.length; ir++) {
2361                 resid_obj = {
2362                     "res_1": resids[ir],
2363                     "res_3": one_to_three[resids[ir]],
2364                     "res_ind": resid_index,
2365                 }
2366                 data[id].resids.push(resid_obj);
2367                 resid_index += 1;
2368             }
2369         }
2370         return data;
2371     }    
2372 
2373     /**
2374      * Calculates helix residue ordering based on loops intracellular or extracellular positioning
2375      * @param {Array} helices_data helices data generated from processRawUniProt
2376      * @param {Array} all_loop_data all loop data generated from processRawUniProt
2377      * @namespace
2378      * @exports NaView
2379      * @name calculateResidOrientation 
2380      * @yields {Array} helices data generated from processRawUniProt with additional residue orientation variable
2381      */
2382     function calculateResidOrientation(helices_data, all_loop_data) {
2383         let all_loop_data_by_domain_position = all_loop_data.reduce( (reduce_dict, a) => {
2384             reduce_dict[a.id] = a;
2385             return reduce_dict;
2386         }, {});
2387         for (let ihd = 0; ihd < helices_data.length; ihd++) {
2388             let previous_loop = all_loop_data_by_domain_position[helices_data[ihd].id-1];
2389             if (previous_loop.note === "Cytoplasmic") {
2390                 helices_data[ihd].inverted = true;
2391             } else {
2392                 helices_data[ihd].inverted = false;
2393             }
2394         }
2395         return helices_data;
2396     }
2397 
2398     /**
2399      * Calculates centroid points of each residue and loop/helix elements in plot and stores it in global variable
2400      * @namespace
2401      * @exports NaView
2402      * @name createResidElementToCentroidData 
2403      */
2404     function createResidElementToCentroidData() {
2405         let residueCentroidDict = {};
2406         let all_residue_paths = d3.selectAll(".single_residue_path");
2407         let done_resind = [];
2408         all_residue_paths.each(function(d) {
2409             let resname = d3.select(this).attr("resname");
2410             let res_ind = d.res_ind;
2411             if (done_resind.indexOf(d.res_ind) === -1) {
2412                 done_resind.push(d.res_ind);
2413             }
2414             if (residueCentroidDict.hasOwnProperty(res_ind)) {
2415                 residueCentroidDict[res_ind]["path_data"].push(deepCopy(d));
2416                 let old_centroid = deepCopy(residueCentroidDict[res_ind]["point"]);
2417                 residueCentroidDict[res_ind]["point"] = [
2418                     (old_centroid[0]+d.centroid[0])/2,
2419                     (old_centroid[1]+d.centroid[1])/2,
2420                 ];
2421             } else {
2422                 let to_dict_obj = {
2423                     "res_ind": res_ind,
2424                     "resname": resname,
2425                     "path_data": [deepCopy(d)],
2426                     "point": d.centroid,
2427                 }
2428                 to_dict_obj = mergeDrawData("residue_centroid", [to_dict_obj])[0];
2429                 for (const ky in d) {
2430                     if (d.hasOwnProperty(ky) && to_dict_obj.hasOwnProperty(ky) === false) {
2431                         to_dict_obj[ky] = d[ky];
2432                     }
2433                 }
2434                 residueCentroidDict[res_ind] = to_dict_obj;
2435             }
2436         });
2437         let all_element_paths = d3.selectAll(".element_path_group");
2438         all_element_paths.each(function(d) {
2439             let elname;
2440             let element_centroid;
2441             if (d.type === "helix") {
2442                 elname = "Domain" +d.dom_name+";Helix"+d.dom_itype;
2443                 let helix_bbox = this.getBBox();
2444                 element_centroid = [helix_bbox.x + (helix_bbox.width)/2, helix_bbox.y + (helix_bbox.height)/2];
2445             } else if (d.type === "pore") {
2446                 elname = "Domain" +d.dom_name+";Pore";
2447                 let pathg_bbox = this.getBBox();
2448                 let path_half_length = d3.select(this).select("path").node().getTotalLength() * 0.5;
2449                 let center_of_path = d3.select(this).select("path").node().getPointAtLength(path_half_length);
2450                 element_centroid = [center_of_path.x, center_of_path.y];
2451                 let y_deviation = pathg_bbox.height * 0.4;
2452                 // let y_deviation = viewport_height * 0.05;
2453                 if (pathg_bbox.y > (svg_height/2)) {
2454                     y_deviation *= -1.0;
2455                 }
2456                 element_centroid[1] += y_deviation;
2457             } else if (d.type === "loop" && d.dom_name) {
2458                 elname = "Domain" +d.dom_name+";Loop"+d.dom_itype;
2459                 let pathg_bbox = this.getBBox();
2460                 let path_half_length = d3.select(this).select("path").node().getTotalLength() * 0.5;
2461                 let center_of_path = d3.select(this).select("path").node().getPointAtLength(path_half_length);
2462                 element_centroid = [center_of_path.x, center_of_path.y];
2463                 let y_deviation = pathg_bbox.height * 0.4;
2464                 // let y_deviation = viewport_height * 0.05;
2465                 if (pathg_bbox.y > (svg_height/2)) {
2466                     y_deviation *= -1.0;
2467                 }
2468                 element_centroid[1] += y_deviation;
2469             } else {
2470                 elname = "InterDomain" +d.dom_iname+";Loop";
2471                 let pathg_bbox = this.getBBox();
2472                 let path_half_length = d3.select(this).select("path").node().getTotalLength() * 0.5;
2473                 let center_of_path = d3.select(this).select("path").node().getPointAtLength(path_half_length);
2474                 element_centroid = [center_of_path.x, center_of_path.y];
2475                 let y_deviation = pathg_bbox.height * 0.4;
2476                 // let y_deviation = viewport_height * 0.05;
2477                 if (pathg_bbox.y > (svg_height/2)) {
2478                     y_deviation *= -1.0;
2479                 }
2480                 element_centroid[1] += y_deviation;
2481             }
2482             // console.log(elname);
2483             let to_dict_obj = {
2484                 "el_ind": d.id,
2485                 "elname": elname,
2486                 "path_data": [deepCopy(d)],
2487                 "point": element_centroid,
2488             }
2489             let new_to_dict_obj = mergeDrawData("residue_centroid", [to_dict_obj])[0];
2490             new_to_dict_obj["circle_radius"] = "5px";
2491             residueCentroidDict[elname] = new_to_dict_obj;
2492         });
2493         // DONE: calculate each path element (helix_group, loop path, etc) centroid and anchor pts
2494         // for helices it is center of membrane
2495         // for loop it is point at 0.5 length of loop -/+ perc of height for y (right below)
2496         // console.log("residueCentroidDict");
2497         // console.log(residueCentroidDict);
2498         // plotDotsCircles(Object.values(residueCentroidDict), "residue_centroids", "residue_centroids_circles");
2499         return residueCentroidDict;
2500     }
2501 
2502     /**
2503      * Calculate short loop x,y anchors of element based on previously drawn elements
2504      * @namespace
2505      * @exports NaView
2506      * @name gen_shortloop_anchordata 
2507      * @param {Array} short_loop_data intradomain short loop data generated from processRawUniProt 
2508      * @param {Array} helices_pores_data helices and pore loop data generated from processRawUniProt 
2509      * @yields {Array} intradomain short loop data generated from processRawUniProt with additional x,y helix anchors
2510      */
2511     function gen_shortloop_anchordata(short_loop_data, helices_pores_data) {
2512         let helices_and_pores_by_domain_position = helices_pores_data.reduce( (reduce_dict, a) => {
2513             reduce_dict[a.dom_name+"_"+a.dom_i] = a;
2514             return reduce_dict;
2515         }, {});
2516 
2517         for (let isld = 0; isld < short_loop_data.length; isld++) {
2518             // const element = array[isld];
2519             let short_loop_dom_name = short_loop_data[isld].dom_name;
2520             let short_loop_dom_i = short_loop_data[isld].dom_i;
2521             let short_loop_side = short_loop_data[isld].note;
2522             short_loop_data[isld].anchorage = {
2523                 "p1": undefined,
2524                 "p2": undefined
2525             }
2526             let previous_element = helices_and_pores_by_domain_position[short_loop_dom_name+"_"+(short_loop_dom_i-1)];
2527             let next_element = helices_and_pores_by_domain_position[short_loop_dom_name+"_"+(short_loop_dom_i+1)];
2528             if (previous_element.type === "pore") {
2529                 short_loop_data[isld].anchorage.p1 = [previous_element.draw_area.end_x, previous_element.draw_area.start_y];
2530             } else {
2531                 if (short_loop_side === "Extracellular") {
2532                     short_loop_data[isld].anchorage.p1 = previous_element.anchor.top;
2533                 } else  {
2534                     short_loop_data[isld].anchorage.p1 = previous_element.anchor.bottom;
2535                 }
2536             }
2537             if (next_element.type === "pore") {
2538                 short_loop_data[isld].anchorage.p2 = [next_element.draw_area.start_x, previous_element.draw_area.start_y];
2539             } else {
2540                 if (short_loop_side === "Extracellular") {
2541                     short_loop_data[isld].anchorage.p2 = next_element.anchor.top;
2542                 } else  {
2543                     short_loop_data[isld].anchorage.p2 = next_element.anchor.bottom;
2544                 }
2545             }
2546             short_loop_data[isld].anchorage.dist = euclideanDistance(short_loop_data[isld].anchorage.p1, short_loop_data[isld].anchorage.p2);
2547         }
2548     //     calc_len
2549     // shape
2550         return short_loop_data;
2551     }
2552 
2553     /**
2554      * Calculate pore loop x,y anchors of element based on previously drawn elements
2555      * @namespace
2556      * @exports NaView
2557      * @name gen_poreloop_anchordata 
2558      * @param {Array} pores_data pore loop data generated from processRawUniProt 
2559      * @param {Array} short_loop_data intradomain short loop data generated from processRawUniProt 
2560      * @yields {Array} intradomain pore loop data generated from processRawUniProt with additional x,y short loop anchors
2561      */
2562     function gen_poreloop_anchordata(pores_data, short_loop_data) {
2563         let short_loop_by_domain_position = short_loop_data.reduce( (reduce_dict, a) => {
2564             reduce_dict[a.dom_name+"_"+a.dom_i] = a;
2565             return reduce_dict;
2566         }, {});
2567 
2568         for (let ipd = 0; ipd < pores_data.length; ipd++) {
2569             let pore_dom_name = pores_data[ipd].dom_name;
2570             let pore_dom_i = pores_data[ipd].dom_i;
2571             let pore_side = pores_data[ipd].note;
2572             pores_data[ipd].anchorage = {
2573                 "p1": undefined,
2574                 "p2": undefined
2575             }
2576             let previous_element = short_loop_by_domain_position[pore_dom_name+"_"+(pore_dom_i-1)];
2577             let next_element = short_loop_by_domain_position[pore_dom_name+"_"+(pore_dom_i+1)];
2578             pores_data[ipd].anchorage.p1 = deepCopy(previous_element.anchorage.p2);
2579             pores_data[ipd].anchorage.p2 = deepCopy(next_element.anchorage.p1);
2580             pores_data[ipd].anchorage.dist = euclideanDistance(pores_data[ipd].anchorage.p1, pores_data[ipd].anchorage.p2);
2581         }
2582         return pores_data;
2583     }
2584 
2585     /**
2586      * Calculate interdomain loop x,y anchors of element based on previously drawn elements
2587      * @namespace
2588      * @exports NaView
2589      * @name gen_longloop_anchordata 
2590      * @param {Array} longloop_data interdomain loop data generated from processRawUniProt 
2591      * @param {Array} helices_data helices data generated from processRawUniProt 
2592      * @yields {Array} interdomain long loop data generated from processRawUniProt with additional x,y helix anchors
2593      */
2594     function gen_longloop_anchordata(longloop_data, helices_data) {
2595         let helices_by_domain_position = helices_data.reduce( (reduce_dict, a) => {
2596             reduce_dict[a.dom_name+"_"+a.dom_i] = a;
2597             return reduce_dict;
2598         }, {});
2599 
2600         let last_dom_index_by_name = {};
2601         for (let ihd = 0; ihd < helices_data.length; ihd++) {
2602             if (last_dom_index_by_name.hasOwnProperty(helices_data[ihd].dom_name) === false) {
2603                 last_dom_index_by_name[helices_data[ihd].dom_name] = helices_data[ihd].dom_i;
2604             } else if (helices_data[ihd].dom_i > last_dom_index_by_name[helices_data[ihd].dom_name]) {
2605                 last_dom_index_by_name[helices_data[ihd].dom_name] = helices_data[ihd].dom_i;
2606             }
2607         }
2608 
2609         for (let illd = 0; illd < longloop_data.length; illd++) {
2610             let prev_dom_name = longloop_data[illd].prev_dom_name;
2611             let next_dom_name = longloop_data[illd].next_dom_name;
2612             longloop_data[illd].anchorage = {
2613                 "p1": undefined,
2614                 "p2": undefined
2615             }
2616             // let previous_element = helices_by_domain_position[prev_dom_name+"_"+1];
2617             let previous_element = helices_by_domain_position[prev_dom_name+"_"+last_dom_index_by_name[prev_dom_name]];
2618             // let next_element = helices_by_domain_position[next_dom_name+"_"+last_dom_index_by_name[next_dom_name]];
2619             let next_element = helices_by_domain_position[next_dom_name+"_"+1];
2620             longloop_data[illd].anchorage.p1 = previous_element.anchor.bottom;
2621             longloop_data[illd].anchorage.p2 = next_element.anchor.bottom;
2622             longloop_data[illd].anchorage.dist = euclideanDistance(longloop_data[illd].anchorage.p1, longloop_data[illd].anchorage.p2);
2623         }
2624         return longloop_data;
2625     }
2626     
2627     /**
2628      * Calculate termini x,y anchors of element based on previously drawn elements
2629      * @namespace
2630      * @exports NaView
2631      * @name gen_termini_anchordata 
2632      * @param {Object} termini_data termini loop data generated from processRawUniProt 
2633      * @param {Object} neighboring_helix neighbouring termini helix data generated from processRawUniProt
2634      * @param {String} termini_type "N" or "C" for N-terminal or C-terminal loop
2635      * @yields {Object} termini loop data generated from processRawUniProt with additional x,y helix anchors
2636      */
2637     function gen_termini_anchordata(termini_data, neighboring_helix, termini_type) {
2638         let current_point;
2639         termini_data.terminus_type = termini_type;
2640         if (termini_type === "N") {
2641             if (style_obj.protein.nter_loop_draw_opts.calc_len.calc.start_loop === "membrane") {
2642                 current_point = [termini_data.draw_area.start_x,termini_data.draw_area.start_y];
2643             } else if (style_obj.protein.nter_loop_draw_opts.calc_len.calc.start_loop === "edge") {
2644                 current_point = [termini_data.draw_area.start_x,termini_data.draw_area.end_y];
2645             }
2646             termini_data.anchorage = {
2647                 "p1": current_point,
2648                 "p2": neighboring_helix.anchor.bottom,
2649                 "dist": euclideanDistance(current_point, neighboring_helix.anchor.bottom)
2650             }
2651         } else {
2652             // current_point = [termini_data.draw_area.end_x,termini_data.draw_area.end_y];
2653             if (style_obj.protein.cter_loop_draw_opts.calc_len.calc.start_loop === "membrane") {
2654                 current_point = [termini_data.draw_area.end_x,termini_data.draw_area.start_y];
2655             } else if (style_obj.protein.cter_loop_draw_opts.calc_len.calc.start_loop === "edge") {
2656                 current_point = [termini_data.draw_area.end_x,termini_data.draw_area.end_y];
2657             }
2658             termini_data.anchorage = {
2659                 "p1": neighboring_helix.anchor.bottom,
2660                 "p2": current_point,
2661                 "dist": euclideanDistance(current_point, neighboring_helix.anchor.bottom)
2662             }
2663         }
2664         return termini_data;
2665     }
2666 
2667 
2668     /**
2669      * Generates a d3.scaleLinear color scale from user inputted color filling rules that use a
2670      * ",by:property,domain,range" syntax.
2671      * @see createFillRules
2672      * @param {String} fillproperty residue property name to map color scale to
2673      * @param {Array} fillrange fill scale linear color range
2674      * @param {Array} filldomain fill scale linear color domain. "min" and "max" strings are properly parsed
2675      * @yields {Function} d3.scaleLinear for a color scale of the desired range and domain
2676      * @namespace
2677      * @exports NaView
2678      * @name createFillScale 
2679      */
2680     function createFillScale(fillproperty, fillrange, filldomain) {
2681         let prop_values = [];
2682         let scale_min = filldomain[0];
2683         if ((scale_min+"").includes("min")) {
2684             let min_res = d3.min(Object.keys(current_resid_properties));
2685             let max_res = d3.min(Object.keys(current_resid_properties));
2686             for (let i_res = min_res; i_res < max_res+1; i_res++) {
2687                 let prop_value = current_resid_properties[i_res][fillproperty];
2688                 prop_values.push(prop_value);
2689             }
2690             scale_min = d3.min(prop_values);
2691         }
2692         filldomain[0] = scale_min;
2693         let scale_max = filldomain[filldomain.length-1];
2694         if ((scale_max+"").includes("max")) {
2695             if (prop_values.length === 0) {
2696                 let min_res = d3.min(Object.keys(current_resid_properties));
2697                 let max_res = d3.min(Object.keys(current_resid_properties));
2698                 for (let i_res = min_res; i_res < max_res+1; i_res++) {
2699                     let prop_value = current_resid_properties[i_res][fillproperty];
2700                     prop_values.push(prop_value);
2701                 }
2702             }
2703             scale_max = d3.max(prop_values);
2704         }
2705         filldomain[filldomain.length-1] = scale_max;
2706         return d3.scaleLinear()
2707         .domain(deepCopy(filldomain))
2708         .range(deepCopy(fillrange));
2709     }
2710     
2711     /**
2712      * Parses fillRules global object checking if object meets any rule criteria.<br>
2713      * If so, the appropriate color/color scale is then returned.
2714      * @param {String} data_obj Helix/pore/loop element current datum
2715      * @yields false or a rgb/hex/named color. opacity check currently deprecated
2716      * @namespace
2717      * @exports NaView
2718      * @name checkFillResidue 
2719      */
2720     function checkFillResidue(data_obj) {
2721         let this_residue_color = false;
2722         let get_value = false;
2723         for (let each_rule_index = 0; each_rule_index < fillRules.length; each_rule_index++) {
2724             let current_rule = fillRules[each_rule_index];
2725             let fillproperty = current_rule.get;
2726             let fillrange = current_rule.range;
2727             let filldomain = current_rule.domain;
2728             let colorFunction;
2729             if (current_rule.get) {
2730                 colorFunction = createFillScale(fillproperty, fillrange, filldomain);
2731                 get_value = current_resid_properties[data_obj.res_ind][fillproperty];
2732             } else {
2733                 colorFunction = function(etc) {
2734                     return current_rule.color;
2735                 };
2736             }
2737             let current_rule_keys_to_check = current_rule.check_keys;
2738             let color_this_residue = false;
2739             for (let check_key_index = 0; check_key_index < current_rule_keys_to_check.length; check_key_index++) {
2740                 let check_key = current_rule_keys_to_check[check_key_index];
2741                 let check_values = current_rule.check_values[check_key_index];
2742                 if ( check_values.indexOf(data_obj[check_key]) > -1) {
2743                     color_this_residue = true;
2744                 } else  {
2745                     color_this_residue = false;
2746                     break;
2747                 }
2748             }
2749             if (color_this_residue) {
2750                 this_residue_color = colorFunction(get_value);
2751             }
2752         }
2753         return [this_residue_color, false]; //color, opacity
2754     }
2755 
2756     /**
2757      * Creates single rectangle SVG elements for each residue of a rectangle helix.
2758      * @param {D3 Selection} enter_element d3 selection element containing drawn plot helices
2759      * @namespace
2760      * @exports NaView
2761      * @name draw_helices_resids_box 
2762      */
2763     function draw_helices_resids_box(enter_element) {
2764         let g_helix_resids = enter_element.append("g")
2765         .attr("id", function(d){
2766             return "g_helix_resids_" + d.dom_name + "_" + d.dom_itype
2767         })
2768         .attr("class", "g_helix_resids residue_path")
2769         // .style("display", "none")
2770         .style("visibility", "hidden");
2771     
2772         g_helix_resids.selectAll(+function(pd) {
2773             return ".helices_rect_" + pd.dom_name + "_" + pd.dom_itype;
2774         })
2775         .data(function(pd) {
2776             let aa_array = pd.aas.split('');
2777             let aa_indarray = rangeArray(pd.aas.length, pd.start);
2778             if (pd.inverted) {
2779                 aa_array.reverse();
2780                 aa_indarray.reverse();
2781             }
2782             let height_per_aa = pd.draw_area.height / aa_array.length;
2783             let d_data = [];
2784             for (let iaaa = 0; iaaa < aa_array.length; iaaa++) {
2785                 let aa1 = aa_array[iaaa];
2786                 let aa3 = one_to_three[aa_array[iaaa]];
2787                 let aai = aa_indarray[iaaa];
2788                 let start_x1 = pd.draw_area.start_x;
2789                 let start_y1 = pd.draw_area.start_y + (height_per_aa*iaaa);
2790                 let width1 = pd.draw_area.width;
2791                 let height1 = height_per_aa;
2792                 d_data.push({
2793                     "id":pd.id,
2794                     "type":pd.type,
2795                     "dom_name":pd.dom_name,
2796                     "dom_itype":pd.dom_itype,
2797                     "start_x": start_x1,
2798                     "start_y": start_y1,
2799                     "width": width1,
2800                     "height": height1,
2801                     "fill":pd.fill,
2802                     "stroke":pd.fill,
2803                     "stroke_width":pd.stroke_width,
2804                     "opacity":pd.opacity,
2805                     "res_1":aa1,
2806                     "res_3":aa3,
2807                     "res_ind":aai,
2808                     "centroid":[
2809                         (start_x1+width1)/2,
2810                         (start_y1+height1)/2,
2811                     ],
2812                 })
2813             }
2814             return d_data;
2815         })
2816         .join(
2817             function(enter) {
2818                 return enter.append("rect")
2819                 .attr("class", function(d) { return "helices_rect_" + d.dom_name + "_" + d.dom_itype + " single_residue_path"; })
2820                 .attr("resname", function(d){ return d.res_3+d.res_ind; })
2821                 .attr("x", function(d) { return d["start_x"];} )
2822                 .attr("y", function(d) { return d["start_y"];} )
2823                 .attr("width", function(d) { return d["width"];} )
2824                 .attr("height", function(d) { return d["height"];} )
2825                 .attr("fill", function(d) {
2826                     let f_color = checkFillResidue(d)[0];
2827                     if (f_color) {
2828                         return f_color;
2829                     }
2830                     return d["fill"];
2831                 } )
2832                 .attr("opacity", function(d) {
2833                     let o_color = checkFillResidue(d)[1];
2834                     if (o_color) {
2835                         return o_color;;
2836                     }
2837                     return d["opacity"];
2838                 })
2839                 .attr("stroke", function(d) {
2840                     let f_color = checkFillResidue(d)[0];
2841                     if (f_color) {
2842                         return f_color;
2843                     }
2844                     return d.stroke;
2845                 })
2846                 .attr("stroke-width", function(d) { return d.stroke_size;})
2847                 ;
2848             },
2849             function(update) {
2850                 return update
2851                 .transition()
2852                 .attr("fill", function(d) {
2853                     let f_color = checkFillResidue(d)[0];
2854                     if (f_color) {
2855                         return f_color;
2856                     }
2857                     return d["fill"];
2858                 } )
2859                 .attr("opacity", function(d) {
2860                     let o_color = checkFillResidue(d)[1];
2861                     if (o_color) {
2862                         return o_color;;
2863                     }
2864                     return d["opacity"];
2865                 })
2866                 .attr("stroke", function(d) {
2867                     let f_color = checkFillResidue(d)[0];
2868                     if (f_color) {
2869                         return f_color;
2870                     }
2871                     return d.stroke;
2872                 });
2873             },
2874             function(exit) {
2875                 return exit.remove();
2876             }
2877         );
2878     }
2879     
2880     /**
2881      * Plots Helices in Box mode (rectangles).
2882      * @param {Array} data  helices data generated from processRawUniProt function
2883      * @param {String} dataId helices main g element id attribute 
2884      * @namespace
2885      * @exports NaView
2886      * @name draw_helices_box 
2887      */
2888     function draw_helices_box(data, dataId) {
2889         let svg_element = d3.select("#"+svg_id)
2890             .append("g")
2891             .attr("class", "helices_group");
2892         
2893         svg_element.selectAll(".helices_rect")
2894             .data(data)
2895             .join(
2896                 function(enter) {
2897                     let e = enter.append("g")
2898                         .attr("id", function(d){ return "g_helix_" + d.dom_name + "_" + d.dom_itype})
2899                         .attr("class", "element_path_group");
2900     
2901                     e.append("rect")
2902                         .attr("class", "helices_rect element_path")
2903                         .attr("x", function(d) { return d["draw_area"]["start_x"];} )
2904                         .attr("y", function(d) { return d["draw_area"]["start_y"];} )
2905                         .attr("width", function(d) { return d["draw_area"]["width"];} )
2906                         .attr("height", function(d) { return d["draw_area"]["height"];} )
2907                         .attr("fill", function(d) { return d["fill"];} )
2908                         .attr("opacity", function(d) { return d["opacity"];} )
2909                         .attr("stroke", function(d) { return d.stroke;})
2910                         .attr("stroke-width", function(d) { return d.stroke_size;})
2911                         ;
2912     
2913                     draw_helices_resids_box(e);
2914     
2915                     return e;
2916                 },
2917                 function(exit) {
2918                     return exit.remove();
2919                 }
2920             );
2921         if (dataId) {
2922             svg_element.attr("id", dataId);
2923         }
2924     }
2925 
2926     /**
2927      * Creates single rectangle SVG elements for each residue of cylinder helix.
2928      * @param {D3 Selection} enter_element d3 selection element containing drawn plot helices
2929      * @namespace
2930      * @exports NaView
2931      * @name draw_helices_resids_cylinder 
2932      */
2933     function draw_helices_resids_cylinder(enter_element) {
2934         let g_helix_resids = enter_element.append("g")
2935         .attr("id", function(d){ return "g_helix_resids_" + d.dom_name + "_" + d.dom_itype})
2936         .attr("class", "g_helix_resids residue_path")
2937         // .style("display", "none")
2938         .style("visibility", "hidden");
2939         
2940         g_helix_resids.selectAll(+function(d) {
2941             return ".helices_cylinders_" + d.dom_name + "_" + d.dom_itype;
2942         })
2943         .data(function(d) {
2944             let aa_array = d.aas.split('');
2945             // let aa_array = data.aas.split('');
2946             let aa_indarray = rangeArray(d.aas.length, d.start);
2947             if (d.inverted) {
2948                 aa_array.reverse();
2949                 aa_indarray.reverse();
2950             }
2951     
2952             let height_per_aa = d.draw_area.height / aa_array.length;
2953             let d_data = [];
2954             for (let iaaa = 0; iaaa < aa_array.length; iaaa++) {
2955                 let aa1 = aa_array[iaaa];
2956                 let aa3 = one_to_three[aa_array[iaaa]];
2957                 let aai = aa_indarray[iaaa];
2958                 let start_x1 = d.draw_area.start_x;
2959                 let start_y1 = d.draw_area.start_y + (height_per_aa*iaaa);
2960                 let width1 = d.draw_area.width;
2961                 let height1 = height_per_aa;
2962                 d_data.push({
2963                     "id":d.id,
2964                     "type":d.type,
2965                     "dom_name":d.dom_name,
2966                     "dom_itype":d.dom_itype,
2967                     "start_x": start_x1,
2968                     "start_y": start_y1,
2969                     "width": width1,
2970                     "height": height1,
2971                     "fill":d.fill,
2972                     "stroke":d.fill,
2973                     "stroke_width":d.stroke_width,
2974                     "opacity":d.opacity,
2975                     "res_1":aa1,
2976                     "res_3":aa3,
2977                     "res_ind":aai,
2978                     "centroid":[
2979                         (start_x1+width1)/2,
2980                         (start_y1+height1)/2,
2981                     ],
2982                 })
2983             }
2984             return d_data;
2985         })
2986         .join(
2987             function(enter) {
2988                 return enter.append("rect")
2989                 .attr("class", function(d) { return "helices_rect_" + d.dom_name + "_" + d.dom_itype + " single_residue_path"; })
2990                 .attr("resname", function(d){ return d.res_3+d.res_ind; })
2991                 .attr("x", function(d) { return d["start_x"];} )
2992                 .attr("y", function(d) { return d["start_y"];} )
2993                 .attr("width", function(d) { return d["width"];} )
2994                 .attr("height", function(d) { return d["height"];} )
2995                 .attr("stroke-width", function(d) { return d.stroke_size;})
2996                 .attr("fill", function(d) {
2997                     let f_color = checkFillResidue(d)[0];
2998                     if (f_color) {
2999                         return f_color;
3000                     }
3001                     return d["fill"];
3002                 } )
3003                 .attr("opacity", function(d) {
3004                     let o_color = checkFillResidue(d)[1];
3005                     if (o_color) {
3006                         return o_color;;
3007                     }
3008                     return d["opacity"];
3009                 })
3010                 .attr("stroke", function(d) {
3011                     let f_color = checkFillResidue(d)[0];
3012                     if (f_color) {
3013                         return f_color;
3014                     }
3015                     return d.stroke;
3016                 })
3017                 .attr("stroke-dasharray", function(d,i) {
3018                     let max_i = g_helix_resids.data().length-1;
3019                     if (i === max_i) {
3020                         return (d["width"]+d["height"]) + "," + d["width"];
3021                     }
3022                     return;
3023                 })
3024                 .on("mouseover", function(d) {
3025                     console.log("");
3026                     console.log("#######");
3027                     console.log("MOUSE OVER PATH");
3028                     console.log("data:");
3029                     console.log(d);
3030                     console.log("#######");
3031                     console.log("");
3032                 })
3033                 ;
3034             },
3035             function(update) {
3036                 return update
3037                 .transition()
3038                 .attr("fill", function(d) {
3039                     let f_color = checkFillResidue(d)[0];
3040                     if (f_color) {
3041                         return f_color;
3042                     }
3043                     return d["fill"];
3044                 } )
3045                 .attr("opacity", function(d) {
3046                     let o_color = checkFillResidue(d)[1];
3047                     if (o_color) {
3048                         return o_color;;
3049                     }
3050                     return d["opacity"];
3051                 })
3052                 .attr("stroke", function(d) {
3053                     let f_color = checkFillResidue(d)[0];
3054                     if (f_color) {
3055                         return f_color;
3056                     }
3057                     return d.stroke;
3058                 });
3059             },
3060         );
3061     }
3062     
3063     /**
3064      * Plots Helices in Cylinder mode.<br>
3065      * Cylinder is composed of circle-rectangle-circle
3066      * @see draw_helices_resids_cylinder
3067      * @param {Array} data helices data generated from processRawUniProt function
3068      * @param {String} dataId helices main g element id attribute
3069      * @namespace
3070      * @exports NaView
3071      * @name draw_helices_cylinder 
3072      */
3073     function draw_helices_cylinder(data, dataId) {
3074         let ratio_wh = svg_height/svg_width;
3075         let cylinder_radius_x = data[0].draw_area.width/2;
3076         let cylinder_radius_y = cylinder_radius_x * ratio_wh;
3077         let top_cylinder_stroke = style_obj.protein.helix_draw_opts.draw.top_cylinder_stroke; 
3078         let top_cylinder_stroke_size = style_obj.protein.helix_draw_opts.draw.top_cylinder_stroke_size; 
3079         // let top_cylinder_fill = style_obj.protein.helix_draw_opts.draw.top_cylinder_fill;
3080         let bottom_cylinder_stroke =  style_obj.protein.helix_draw_opts.draw.bottom_cylinder_stroke; 
3081         let bottom_cylinder_stroke_size = style_obj.protein.helix_draw_opts.draw.bottom_cylinder_stroke_size; 
3082         // let bottom_cylinder_fill = style_obj.protein.helix_draw_opts.draw.bottom_cylinder_fill;
3083     
3084         // let lipid_head_radius_height = lipid_head_radius_width * ratio_wh * 2;
3085     
3086         let svg_element = d3.select("#"+svg_id)
3087             .append("g")
3088             .attr("class", "helices_group");
3089         
3090         svg_element.selectAll(".helices_cylinder")
3091             .data(data)
3092             .join(
3093                 function(enter) {
3094                     let e = enter.append("g")
3095                         .attr("id", function(d){ return "g_helix_" + d.dom_name + "_" + d.dom_itype})
3096                         .attr("class", "helices_cylinder element_path_group");
3097     
3098                     e.append("ellipse")
3099                         .attr("class", "helices_cyl_bottom_circle")
3100                         .attr("cx", function(d) { return d["draw_area"]["start_x"]+cylinder_radius_x;} )
3101                         .attr("cy", function(d) { return d["draw_area"]["end_y"];} )
3102                         .attr("rx", cylinder_radius_x )
3103                         .attr("ry", cylinder_radius_y )
3104                         .attr("fill", function(d) {
3105                             let f_color = checkFillResidue(d.resids[d.resids.length-1])[0];
3106                             if (f_color) {
3107                                 return f_color;
3108                             }
3109                             return d["fill"];
3110                         } )
3111                         .attr("opacity", function(d) {
3112                             let o_color = checkFillResidue(d.resids[d.resids.length-1])[1];
3113                             if (o_color) {
3114                                 return o_color;;
3115                             }
3116                             return d["opacity"];
3117                         })
3118                         // .attr("fill", function(d) {
3119                         //     return d["fill"];
3120                         // } )
3121                         // .attr("opacity", function(d) { return d["opacity"];} )
3122                         .attr("stroke", bottom_cylinder_stroke)
3123                         .attr("stroke-width", bottom_cylinder_stroke_size)
3124                         ;
3125                     e.append("rect")
3126                         .attr("class", "helices_cyl_rect element_path")
3127                         .attr("x", function(d) { return d["draw_area"]["start_x"];} )
3128                         .attr("y", function(d) { return d["draw_area"]["start_y"];} )
3129                         .attr("width", function(d) { return d["draw_area"]["width"];} )
3130                         .attr("height", function(d) { return d["draw_area"]["height"];} )
3131                         .attr("fill", function(d) { return d["fill"];} )
3132                         .attr("opacity", function(d) { return d["opacity"];} )
3133                         .attr("stroke", function(d) { return d.stroke;})
3134                         .attr("stroke-width", function(d) { return d.stroke_size;})
3135                         .attr("stroke-dasharray", function(d) { return d["draw_area"]["width"]+d["draw_area"]["height"]+","+d["draw_area"]["width"];})
3136                         ;
3137                     draw_helices_resids_cylinder(e);
3138                     e.append("ellipse")
3139                         .attr("class", "helices_cyl_top_circle")
3140                         .attr("cx", function(d) { return d["draw_area"]["start_x"]+cylinder_radius_x;} )
3141                         .attr("cy", function(d) { return d["draw_area"]["start_y"];} )
3142                         .attr("rx", cylinder_radius_x)
3143                         .attr("ry", cylinder_radius_y )
3144                         .attr("fill", function(d) {
3145                             let f_color = checkFillResidue(d.resids[0])[0];
3146                             if (f_color) {
3147                                 return f_color;
3148                             }
3149                             return d["fill"];
3150                         } )
3151                         .attr("opacity", function(d) {
3152                             let o_color = checkFillResidue(d.resids[0])[1];
3153                             if (o_color) {
3154                                 return o_color;;
3155                             }
3156                             return d["opacity"];
3157                         })
3158                         // .attr("fill", function(d) {
3159                             // return d["fill"];
3160                         // } )
3161                         // .attr("opacity", function(d) { return d["opacity"];} )
3162                         .attr("stroke", top_cylinder_stroke)
3163                         .attr("stroke-width", top_cylinder_stroke_size)
3164                         ;
3165                     return e;
3166                 },
3167                 function(update) {
3168                     let bc = update.select(".helices_cyl_bottom_circle")
3169                     .transition()
3170                     .attr("fill", function(d) {
3171                         let f_color = checkFillResidue(d.resids[d.resids.length-1])[0];
3172                         if (f_color) {
3173                             return f_color;
3174                         }
3175                         return d["fill"];
3176                     } )
3177                     .attr("opacity", function(d) {
3178                         let o_color = checkFillResidue(d.resids[d.resids.length-1])[1];
3179                         if (o_color) {
3180                             return o_color;;
3181                         }
3182                         return d["opacity"];
3183                     })
3184                     ;
3185                     let tc = update.select(".helices_cyl_top_circle")
3186                     .transition()
3187                     .attr("fill", function(d) {
3188                         let f_color = checkFillResidue(d.resids[0])[0];
3189                         if (f_color) {
3190                             return f_color;
3191                         }
3192                         return d["fill"];
3193                     } )
3194                     .attr("opacity", function(d) {
3195                         let o_color = checkFillResidue(d.resids[0])[1];
3196                         if (o_color) {
3197                             return o_color;;
3198                         }
3199                         return d["opacity"];
3200                     });
3201                     return update;
3202                 },
3203                 function(exit) {
3204                     return exit.remove();
3205                 }
3206             );
3207         if (dataId) {
3208             svg_element.attr("id", dataId);
3209         }
3210     }
3211 
3212     /**
3213      * Constructs Path "d" attribute from a datum element containing a "points" attribute
3214      * @param {Object} d datum element containing a "points" attribute
3215      * @namespace
3216      * @exports NaView
3217      * @name buildPathStringFromData 
3218      * @yields {String} Path "d" attribute
3219      */
3220     function buildPathStringFromData(d) {
3221         let cmdarray = ["M"];
3222         for (let i_todraw = 0; i_todraw < d.points.length; i_todraw++) {
3223             let points_element = d.points[i_todraw];
3224             let points_element_type = points_element.type;
3225             switch (points_element_type) {
3226                 case "curvB":
3227                     cmdarray.push("C"); //start downward curves
3228                     for (let icrvD = 0; icrvD < points_element.p.length; icrvD++) {
3229                         let crvP = points_element.p[icrvD];
3230                         cmdarray.push(crvP); //downward curves movement
3231                     }
3232                     break;
3233                 case "lineP":
3234                     cmdarray.push("L"); //start downward curves
3235                     cmdarray.push(points_element.p); //start downward curves
3236                     break;
3237                 default:
3238                     cmdarray.push(points_element.p); //start movement
3239                     break;
3240             }
3241         }
3242         if (d.pathclose === true) {
3243             cmdarray.push("z"); //finish movement
3244         }
3245         var string_d = "";
3246         for (let icmd = 0; icmd < cmdarray.length-1; icmd++) {
3247             string_d += cmdarray[icmd] + " ";
3248         }
3249         string_d += cmdarray[cmdarray.length-1];
3250         return string_d;
3251     }
3252     
3253     /**
3254      * Retrieves points from a D3 Path selection
3255      * @param {D3 Path Selection} d3path D3 Path selection to get desired points
3256      * @param {String} searchchar optional path selection. searches for a points refering to a given curve or line
3257      * @namespace
3258      * @exports NaView
3259      * @name pathStringToStringPoints 
3260      * @yields {Array} Coordinate Array of points for given path
3261      */
3262     function pathStringToStringPoints(d3path, searchchar) {
3263         // var re = new RegExp("a|b");
3264         let path_string = d3path.attr("d");
3265         let arrayOfStrings = path_string.split(/(?=[MCL])/g);
3266         if (searchchar) {
3267             let results = [];
3268             for (let i_strarr = 0; i_strarr < arrayOfStrings.length; i_strarr++) {
3269                 let string_el = arrayOfStrings[i_strarr];
3270                 if (string_el.indexOf(searchchar) > -1) {
3271                     results.push(string_el);
3272                 }
3273             }
3274             if (results.length === 1) {
3275                 return results[0];
3276             }
3277             return results;
3278         }
3279         return arrayOfStrings;
3280     }
3281 
3282     /**
3283      * Converts SVG point object to Array of coordinates
3284      * @param {Object} dot1 point to be converted to Array of coordinates
3285      * @namespace
3286      * @exports NaView
3287      * @name dotToCoords 
3288      * @yields {Array} Coordinate Array of given point
3289      */
3290     function dotToCoords(dot1) {
3291         let ndot = [];
3292         if (dot1.x) {
3293             ndot.push(dot1.x);
3294         } else {
3295             ndot.push(dot1[0]);
3296         }
3297         if (dot1.y) {
3298             ndot.push(dot1.y);
3299         } else {
3300             ndot.push(dot1[1]);
3301         }
3302         return ndot;
3303     }
3304     
3305     /**
3306      * Generate mid point between two points [x,y]
3307      * @param {Array} dot1 point 1 to generate mid point
3308      * @param {Array} dot2 point 2 to generate mid point
3309      * @namespace
3310      * @exports NaView
3311      * @name calculateDotArrayMiddlePoint 
3312      * @yields {Array} Coordinate Array of Mid Points
3313      */
3314     function calculateDotArrayMiddlePoint(dot1, dot2) {
3315         return [(dot1[0] + dot2[0])/2,(dot1[1] + dot2[1])/2];
3316     }
3317     
3318     /**
3319      * Generate mid point between two SVG Path points {"x":x,"y":y}
3320      * @param {Object} dot1 point 1 to generate mid point
3321      * @param {Object} dot2 point 2 to generate mid point
3322      * @namespace
3323      * @exports NaView
3324      * @name calculateDotMiddlePoint 
3325      * @yields {Array} Coordinate Array of Mid Points
3326      */
3327     function calculateDotMiddlePoint(dot1, dot2) {
3328         return [(dot1.x + dot2.x)/2,(dot1.y + dot2.y)/2];
3329     }
3330     
3331     /**
3332      * Generate X mid points equally spaced between two points
3333      * @param {Object} dot1 point 1 to generate mid points
3334      * @param {Object} dot2 point 2 to generate mid points
3335      * @param {number} xmids number of equally spaced mid points
3336      * @yields {Array} Array of equally spaced X mid points
3337      * @namespace
3338      * @exports NaView
3339      * @name generateXMidPts 
3340      */
3341     function generateXMidPts(dot1, dot2, xmids) {
3342         let dots = [];
3343         dots.push([dot1.x, dot1.y]);
3344         let deltaX = (dot2.x - dot1.x)/xmids;
3345         let deltaY = (dot2.y - dot1.y)/xmids;
3346         for (let idot = 1; idot <= xmids; idot++) {
3347             dots.push([(dot1.x+(deltaX*idot)),(dot1.y+(deltaY*idot))]);
3348         }
3349         dots.push([dot2.x, dot2.y]);
3350         return dots;
3351     }
3352     
3353     /**
3354      * Sample points from a SVG path from a start length to end length
3355      * @param {HTMLElement} dompath half turn parent path element as DOM Object
3356      * @param {number} segmentstart start length of sampling
3357      * @param {number} segmentend end length of sampling
3358      * @param {number} sampling_step step of sampling
3359      * @param {number} sampling_stepend final step of sampling
3360      * @yields {Array} Array of sampled path points
3361      * @namespace
3362      * @exports NaView
3363      * @name pathSegmentToXYPoints 
3364      */
3365     function pathSegmentToXYPoints(dompath, segmentstart, segmentend, sampling_step, sampling_stepend) {
3366         if (!sampling_step) {
3367             sampling_step = 0.25;
3368         }
3369         if (!sampling_stepend) {
3370             sampling_stepend = sampling_step;
3371         }
3372         let segs = [];
3373         for (var i=segmentstart; i<=segmentend+sampling_stepend; i+=sampling_step) {
3374         // for (var i=segmentstart; i<segmentend; i+=sampling_step) {
3375             //length to generate point is translations from i and accumulated previous path points to pieceSize
3376             let sampled_point = dompath.getPointAtLength(i);
3377             segs.push([sampled_point.x, sampled_point.y]);
3378         }
3379         return segs;
3380     }
3381     
3382     /**
3383      * Generates drawing information for bezier curves that define each half turn object.<br>
3384      * This is done according to a helix number of turns (division by three).<br>
3385      * And also to style_obj helix width and height definitions, as well as to the number of residues in each half turn.<br>
3386      * @namespace
3387      * @exports NaView
3388      * @name gen_helix_cartoon_halfturn_data 
3389      * @param {Array} data helix data generated by processRawUniProt
3390      * @yields {Object} Object containing:<br>
3391      * 1. back and front half turn points to generate half turn paths:<br>
3392      * 2. Total turn number for a helix<br>
3393      * 3. Parity of half helix front turn number (deprecated) <br>
3394      */
3395     function gen_helix_cartoon_halfturn_data(data) {
3396         let aanum = data.aanum;
3397         let helix_drawarea = data.draw_area;
3398         let helix_hstart = helix_drawarea.start_y;
3399         let helix_wstart = helix_drawarea.start_x;
3400         let helixwidth = helix_drawarea.width;
3401         let helixheight = helix_drawarea.height;
3402         let resid_inversion = data.inverted;
3403     
3404         let aa_array = data.aas.split('');
3405         let aa_indarray = rangeArray(data.aas.length, data.start);
3406         if (resid_inversion) {
3407             aa_array.reverse();
3408             aa_indarray.reverse();
3409         }
3410     
3411         var end_bck = false;
3412         var end_frt = false;
3413         var ends_at = "";
3414         var number_of_turns_integer = 0;
3415         var half_turns_number = 0;
3416         let counter = 0;
3417         for (let iaa = 0; iaa < aanum; iaa++) {
3418             if (counter === 0) { //fourth is skip
3419                 number_of_turns_integer += 1;
3420                 half_turns_number += 1;
3421                 ends_at = "back";
3422                 end_bck = true;
3423                 end_frt = false;
3424                 counter += 1;
3425             } else if (counter === 3) {
3426                 half_turns_number += 1;
3427                 ends_at = "front";
3428                 end_frt = true;
3429                 end_bck = false;
3430                 counter = 0;
3431             } else {
3432                 counter += 1;
3433             }
3434         }
3435         
3436         let half_turn_resids = stringToChunksSkip(data.aas, 3, 1);
3437         let half_turn_resids2 = stringToChunksSkip(data.aas.slice(2), 3, 1);
3438         // half_turns_number = half_turn_resids.length;
3439     
3440         let back_half_turn_resids = half_turn_resids.filter(function(a, i) {
3441             // return a.length === 3;
3442             let parity = i % 2 === 0;
3443             if (end_frt && i === half_turn_resids.length-1) {
3444                 return false;
3445             }
3446             return parity;
3447         });
3448         let front_half_turn_resids = half_turn_resids2.filter(function(a, i) {
3449             // return a.length === 3;
3450             if (end_bck && i === half_turn_resids.length-1) {
3451                 return false;
3452             }
3453             return i % 2 === 0;
3454         });
3455         half_turns_number = back_half_turn_resids.length + front_half_turn_resids.length;
3456     
3457         var height_step_per_full_turn = helixheight / (half_turns_number/2);
3458         // var height_step_per_full_turn = helixheight / number_of_turns_integer;
3459         var height_step_per_half_turn = height_step_per_full_turn / 2;
3460     
3461         let helix_draw_thickness = style_obj["protein"]["helix_draw_opts"]["draw"]["thickness"];
3462         var thickness_step_per_half_turn = height_step_per_half_turn * helix_draw_thickness;
3463     
3464         var width_step_per_half_turn = helixwidth;
3465     
3466         
3467         let w = width_step_per_half_turn;
3468         let h = height_step_per_half_turn;
3469     
3470         let helix_draw_x_to_end_proportion = style_obj["protein"]["helix_draw_opts"]["draw"]["x_to_end_prop"];
3471         //let helix_draw_thickness = helix_draw_thickness;
3472         let helix_draw_y_to_mid_proportion = style_obj["protein"]["helix_draw_opts"]["draw"]["y_to_mid_prop"];
3473     
3474         let tx = (w * helix_draw_x_to_end_proportion);
3475         let ty = (h * helix_draw_thickness); // helix half turn height
3476         let py = helix_draw_y_to_mid_proportion;
3477     
3478         let back_helix_color = data.back_fill;
3479         let front_helix_color = data.fill;
3480     
3481         let helix_stroke_size = style_obj["protein"]["helix_draw_opts"]["draw"]["stroke_size"];
3482         let back_helix_stroke = style_obj["protein"]["helix_draw_opts"]["draw"]["back_helix_stroke"];
3483         let front_helix_stroke = style_obj["protein"]["helix_draw_opts"]["draw"]["front_helix_stroke"];
3484     
3485         let parsed_resnum = 0;
3486     
3487         let helix_bck_resnum = 0;
3488         var helix_bck_draw_array = [];
3489     
3490         let helix_frt_resnum = 0;
3491         var helix_frt_draw_array = [];
3492     
3493         for (let half_turn_i = 0; half_turn_i < half_turns_number; half_turn_i++) {
3494             var half_turn_oddeven = half_turn_i % 2;
3495             var x = helix_wstart + (width_step_per_half_turn * half_turn_oddeven);
3496             var y = helix_hstart + (half_turn_i * height_step_per_half_turn);
3497             let parts_division;
3498             if (half_turn_i === 0) {
3499                 parts_division = [2,2,1];
3500             } else if (half_turn_i === half_turns_number-1) {
3501                 parts_division = [1,2,2];
3502             } else {
3503                 parts_division = [2,3,2];
3504             }
3505             if (half_turn_oddeven == 0) {
3506                 let bckresids = [];
3507                 let bck_resid_data = back_half_turn_resids[helix_bck_resnum].split("");
3508                 let bck_respos_index = (half_turn_i*2);
3509                 for (let ibrd = 0; ibrd < bck_resid_data.length; ibrd++) {
3510                     let res_1 = bck_resid_data[ibrd];
3511                     bckresids.push({
3512                         "id":data.id,
3513                         "type":data.type,
3514                         "dom_name":data.dom_name,
3515                         "dom_itype":data.dom_itype,
3516                         "res_1":aa_array[bck_respos_index],
3517                         "res_3":one_to_three[aa_array[bck_respos_index]],
3518                         // "res_ind": data.start+(bck_respos_index),
3519                         "res_ind": aa_indarray[bck_respos_index],
3520                         "opacity": data.opacity,
3521                         "fill": back_helix_color,
3522                         "stroke_color": back_helix_stroke,
3523                         "stroke_size": helix_stroke_size,
3524                     });
3525                     // if (aa_array[bck_respos_index] !== res_1) {
3526                     //     console.log("##############");
3527                     //     console.log("!!! FATAL ERROR: WRONG RESIDUE");
3528                     //     console.log("##############");
3529                     //     return;
3530                     // }
3531                     bck_respos_index += 1;
3532                 }
3533     
3534                 let xend = x+w;
3535                 let y_add = 0;
3536                 if (bckresids.length === 2) {
3537                     xend = x+((w/3)*2);
3538                     parts_division = parts_division.slice(0,2);
3539                 } else if (bckresids.length === 1) {
3540                     xend = x+(w/3);
3541                     parts_division = parts_division.slice(0,1);
3542                 }
3543                 if (half_turn_i === half_turns_number-1) {
3544                     y_add = 0;
3545                 }
3546     
3547                 let yend = y+(h+y_add);
3548     
3549                 let d = ((h+y_add)-ty) / 2;
3550     
3551                 let bx1 = x + tx;
3552                 let bx2 = xend  - tx;
3553                 
3554                 let by1 = (y+ty) + ( ((h+y_add)-ty)/2  + ( ((h+y_add)-ty) * py) );
3555                 let by2 = (y+ty) + ( ((h+y_add)-ty)/2  - ( ((h+y_add)-ty) * py) );
3556     
3557                 let by3 = (yend-ty) - ( ((h+y_add)-ty)/2  + ( ((h+y_add)-ty) * py) );
3558                 let by4 = (yend-ty) - ( ((h+y_add)-ty)/2  - ( ((h+y_add)-ty) * py) );
3559     
3560                 helix_bck_resnum += 1;
3561     
3562                 helix_bck_draw_array.push({
3563                     "id":data.id,
3564                     "type":data.type,
3565                     "dom_name":data.dom_name,
3566                     "dom_itype":data.dom_itype,
3567                     "resids": bckresids,
3568                     "pathclose": true,
3569                     "fill": back_helix_color,
3570                     "stroke_color": back_helix_stroke,
3571                     "stroke_size": helix_stroke_size,
3572                     "parts_division": parts_division,
3573                     "points":[
3574                         {"turn_index": half_turn_i,"pathtype": "bck","type": "addP", "p": x + "," + y},
3575                         {"turn_index": half_turn_i,"pathtype": "bck","type": "addP", "p": x + "," + (y+ty)},
3576                         {"turn_index": half_turn_i,"pathtype": "bck","type": "curvB", "p":[ bx1+","+by1 ,   bx2+","+by2,   xend+","+yend]},
3577                         {"turn_index": half_turn_i,"pathtype": "bck","type": "lineP", "p":xend+","+(yend-ty)},
3578                         {"turn_index": half_turn_i,"pathtype": "bck","type": "curvB", "p":[ bx2+","+by3 ,   bx1+","+by4,   x+","+y]},
3579                     ]
3580                 });
3581             }
3582             else if (half_turn_oddeven == 1) {
3583                 y = helix_hstart + ( (half_turn_i-0.5) * height_step_per_half_turn);
3584                 var y2 = helix_hstart + ( (half_turn_i) * height_step_per_half_turn);
3585                 var y3 = helix_hstart + ( (half_turn_i+0.5) * height_step_per_half_turn);
3586     
3587                 let frtresids = [];
3588                 let frt_resid_data = front_half_turn_resids[helix_frt_resnum].split("");
3589                 let frt_respos_index = (half_turn_i*2);
3590                 for (let ifrd = 0; ifrd < frt_resid_data.length; ifrd++) {
3591                     let res_1 = frt_resid_data[ifrd];
3592                     frtresids.push({
3593                         "id":data.id,
3594                         "type":data.type,
3595                         "dom_name":data.dom_name,
3596                         "dom_itype":data.dom_itype,
3597                         "res_1":aa_array[frt_respos_index],
3598                         "res_3":one_to_three[aa_array[frt_respos_index]],
3599                         // "res_ind": data.start+(frt_respos_index),
3600                         "res_ind": aa_indarray[frt_respos_index],
3601                         "opacity": data.opacity,
3602                         "fill": front_helix_color,
3603                         "stroke_color": front_helix_stroke,
3604                         "stroke_size": helix_stroke_size,
3605                     });
3606                     // if (aa_array[frt_respos_index] !== res_1) {
3607                     //     console.log("##############");
3608                     //     console.log("!!! FATAL ERROR: WRONG RESIDUE");
3609                     //     console.log("##############");
3610                     //     return;
3611                     // }
3612                     frt_respos_index += 1;
3613                 }
3614                 let xend = x-w;
3615                 if (frtresids.length === 2) {
3616                     xend = x-(w/3);
3617                     parts_division = parts_division.slice(0,2);
3618                 } else if (frtresids.length === 1) {
3619                     xend = x-((w/3)*2);
3620                     parts_division = parts_division.slice(0,1);
3621                 }
3622                 if (half_turn_i === half_turns_number-1) {
3623                     y3 = y2;
3624                 }
3625     
3626                 let yend = y3+h;
3627                 let yend2 = y2+h;
3628     
3629                 let d = (h-ty) / 2;
3630     
3631                 let bx1 = x - tx;
3632                 let bx2 = xend  + tx;
3633                 
3634                 let by1 = (y2+ty) + ( (h-ty)/2  + ( (h-ty) * py) );
3635                 let by2 = (y2+ty) + ( (h-ty)/2  - ( (h-ty) * py) );
3636     
3637                 let by3 = (yend2-ty) - ( (h-ty)/2  + ( (h-ty) * py) );
3638                 let by4 = (yend2-ty) - ( (h-ty)/2  - ( (h-ty) * py) );
3639     
3640                 helix_frt_resnum += 1;
3641                 helix_frt_draw_array.push({
3642                     "id":data.id,
3643                     "type":data.type,
3644                     "dom_name":data.dom_name,
3645                     "dom_itype":data.dom_itype,
3646                     "resids": frtresids,
3647                     "pathclose": true,
3648                     "fill": front_helix_color,
3649                     "stroke_color": front_helix_stroke,
3650                     "stroke_size": helix_stroke_size,
3651                     "parts_division": parts_division,
3652                     "points":[
3653                         {"turn_index": half_turn_i, "pathtype":"frt","type": "addP", "p": x + "," + y},
3654                         {"turn_index": half_turn_i, "pathtype":"frt","type": "addP", "p": x + "," + (y+ty)},
3655                         {"turn_index": half_turn_i, "pathtype":"frt","type": "curvB", "p":[ bx1+","+by1 ,   bx2+","+by2,   xend+","+yend]},
3656                         {"turn_index": half_turn_i, "pathtype":"frt","type": "lineP", "p":xend+","+(yend-ty)},
3657                         {"turn_index": half_turn_i, "pathtype":"frt","type": "curvB", "p":[ bx2+","+by3 ,   bx1+","+by4,   x+","+y]},
3658                     ]
3659                 });
3660             }
3661         }
3662         let helix_to_draw_data = {
3663             "back": helix_bck_draw_array,
3664             "front": helix_frt_draw_array,
3665             "half_turns_number":half_turns_number,
3666             "aanum_parity": aanum % 2 === 0,
3667         }
3668         return helix_to_draw_data;
3669     }
3670 
3671     /**
3672      * Defines boundaries of each residue in parent path half helix polygon for residue partitioning function
3673      * @namespace
3674      * @exports NaView
3675      * @name getHalfHelixBoundaries 
3676      * @param {HTMLElement} dom_created_path half turn parent path element as DOM Object
3677      * @param {number} dom_created_path_length total length of half turn parent path element
3678      * @param {number} height_sline pixel height of each half turn symetrical left and right vertical lines
3679      * @param {Array} division number of residues and residue proportion in each half turn element
3680      * @yields {Array} Array of three important objects:<br>
3681      * 1.bottom "left" and "right" coordinates of half turn parent path element<br>
3682      * 2.top "left" and "right" coordinates of half turn parent path element<br>
3683      * 3.proportion of each residue in parent path half helix polygon
3684      */
3685     function getHalfHelixBoundaries(dom_created_path, dom_created_path_length, height_sline, division) {
3686         //and subtract 2 times line height and divide by 2
3687         let curve_length = (dom_created_path_length - (2*height_sline)) / 2;
3688         
3689         let curve_step =  curve_length / 3;
3690     
3691         let curve_div_size = curve_length / division.size;
3692     
3693         //division.curve_steps indicates proportion of polygons
3694         let curve_step_size_f = curve_length;
3695         let curve_step_size_1 = division.curve_steps[0].size * curve_div_size;
3696         division.curve_steps[0].csize = curve_step_size_1;
3697         
3698         curve_step_size_f -= curve_step_size_1;
3699     
3700         let curve_step_size_2 = false;
3701     
3702         if (division.curve_steps.length === 2) {
3703             curve_step_size_2 = division.curve_steps[1].size * curve_div_size;
3704             division.curve_steps[1].csize = curve_step_size_2;
3705             curve_step_size_f -= curve_step_size_2;
3706         }
3707     
3708         let displacement_ps = curve_step*style_obj["protein"]["helix_draw_opts"]["draw"]["aa_area_perc_displacement"];
3709         
3710         let displacement_ps1 = curve_step_size_1*style_obj["protein"]["helix_draw_opts"]["draw"]["aa_area_perc_displacement"];
3711         let displacement_psf = curve_step_size_f*style_obj["protein"]["helix_draw_opts"]["draw"]["aa_area_perc_displacement"];
3712         let displacement_ps2 = false;
3713         if (division.curve_steps.length === 2) {
3714             displacement_ps2 = curve_step_size_2*style_obj["protein"]["helix_draw_opts"]["draw"]["aa_area_perc_displacement"];
3715         }
3716     
3717         //the use getpointatlength for getting the desired points at each third of path
3718     
3719         //division.curve_steps indicates proportion of polygons.
3720         //first curve start for both back and front half-helices is after going down (left/right) lines
3721         //first curve "third" should be summed first step, this is clear
3722     
3723         let f_curve_start = height_sline;
3724         let f_curve_fthird = f_curve_start+curve_step_size_1;
3725         let f_curve_sthird = false;
3726         if (division.curve_steps.length === 2) {
3727             //first curve second "third" should be summed second step, this is also clear
3728             f_curve_sthird = f_curve_start+(curve_step_size_1+curve_step_size_2);
3729         }
3730     
3731         let s_curve_start = ((height_sline*2)+curve_length);
3732         //now it is a bit unclear. second curve shoudl also be summed first step
3733         let s_curve_fthird = s_curve_start+curve_step_size_f;
3734         let s_curve_sthird = false;
3735         if (division.curve_steps.length === 2) {
3736             s_curve_sthird = s_curve_start+(curve_step_size_2+curve_step_size_f);
3737         }
3738     
3739         let fp_btm_curve = dom_created_path.getPointAtLength(f_curve_fthird+displacement_ps1);
3740         let sp_btm_curve = false;
3741         let fp_top_curve = dom_created_path.getPointAtLength(s_curve_fthird+displacement_psf);
3742         let sp_top_curve = false;
3743         if (division.curve_steps.length === 2) {
3744             sp_btm_curve = dom_created_path.getPointAtLength(f_curve_sthird+displacement_psf);
3745             sp_top_curve = dom_created_path.getPointAtLength(s_curve_sthird+displacement_ps1);
3746         }
3747         
3748         let default_curve_point = {
3749             "x":0,
3750             "y":0,
3751             "path_len_to_pt":0,
3752         };
3753     
3754         let bottom_curve_points = {
3755             "left": deepCopy(default_curve_point),
3756         };
3757         let top_curve_points = {
3758             "left": deepCopy(default_curve_point),
3759         };
3760         if (division.curve_steps.length === 2) {
3761             bottom_curve_points["right"] = deepCopy(default_curve_point);
3762             top_curve_points["right"] = deepCopy(default_curve_point);
3763         }
3764         
3765         bottom_curve_points["left"]["x"] = fp_btm_curve.x;
3766         bottom_curve_points["left"]["y"] = fp_btm_curve.y;
3767         bottom_curve_points["left"]["path_len_to_pt"] = f_curve_fthird+displacement_ps;
3768     
3769         if (division.curve_steps.length === 2) {
3770             bottom_curve_points["right"]["x"] = sp_btm_curve.x;
3771             bottom_curve_points["right"]["y"] = sp_btm_curve.y;
3772             bottom_curve_points["right"]["path_len_to_pt"] = f_curve_sthird+displacement_ps;
3773         }
3774     
3775         if (division.curve_steps.length === 2) {
3776             top_curve_points["right"]["x"] = fp_top_curve.x;
3777             top_curve_points["right"]["y"] = fp_top_curve.y;
3778             top_curve_points["right"]["path_len_to_pt"] = s_curve_fthird+displacement_ps;
3779         }
3780     
3781         top_curve_points["left"]["x"] = sp_top_curve.x;
3782         top_curve_points["left"]["y"] = sp_top_curve.y;
3783         top_curve_points["left"]["path_len_to_pt"] = s_curve_sthird+displacement_ps;
3784     
3785         if (division.curve_steps.length === 1) {
3786             top_curve_points["left"]["x"] = fp_top_curve.x;
3787             top_curve_points["left"]["y"] = fp_top_curve.y;
3788             top_curve_points["left"]["path_len_to_pt"] = s_curve_fthird+displacement_ps;
3789         }
3790         
3791         return [bottom_curve_points, top_curve_points, curve_step_size_1, curve_step_size_2];
3792     }
3793 
3794     /**
3795      * Calculate Parent Path division points and Plots residue specific path elements for half turns representing a single residue
3796      * @namespace
3797      * @exports NaView
3798      * @name drawSingleHalfHelixResidue 
3799      * @param {Object} data data object for each half helix turn representing a single residue
3800      * @param {String} path_class name to generate group element class of new sub paths
3801      * @param {number} data_index index of half turn parent element
3802      * @param {HTMLElement} dom_created_path half turn parent path element as DOM Object
3803      */
3804     function drawSingleHalfHelixResidue(data, path_class, data_index, dom_created_path) {
3805         let d3_created_path_parent = d3.select(dom_created_path);
3806         let g_res_polygons = d3.select("#"+path_class+"_resids").append("g");
3807         
3808         data.resids[0]["path_d"] = d3_created_path_parent.attr("d");
3809         g_res_polygons
3810         .selectAll("."+path_class+"_resids_"+data_index)
3811         .data(data.resids)
3812         .join(
3813             function(enter) {
3814                 enter.append("path")
3815                 .attr("class", path_class+"_resids_"+data_index + " single_residue_path")
3816                 .attr("resname", function(d){
3817                     // if (!d.res3 || !d.res_ind) {
3818                         // console.log("d");
3819                         // console.log(d);
3820                     // }
3821                     return d.res_3 + d.res_ind;
3822                 })
3823                 .attr("d", function(d) {
3824                     return d.path_d;
3825                 })
3826                 .attr('stroke', function(d) {
3827                     let f_color = checkFillResidue(d)[0];
3828                     if (f_color) {
3829                         return f_color;
3830                     }
3831                     // return d["fill"];
3832                     return d["stroke"];
3833                     // return "black";
3834                 })
3835                 .attr("fill", function(d) {
3836                     let f_color = checkFillResidue(d)[0];
3837                     if (f_color) {
3838                         return f_color;
3839                     }
3840                     return d["fill"];
3841                 } )
3842                 .attr("opacity", function(d) {
3843                     let o_color = checkFillResidue(d)[1];
3844                     if (o_color) {
3845                         return o_color;;
3846                     }
3847                     return d["opacity"];
3848                 })
3849                 .attr('stroke-width', function(d) {
3850                     return d.stroke_size;
3851                 })
3852                 .on("mouseover", function(d) {
3853                     console.log("");
3854                     console.log("#######");
3855                     console.log("MOUSE OVER PATH");
3856                     console.log("data:");
3857                     console.log(d);
3858                     console.log("#######");
3859                     console.log("");
3860                 })
3861                 ;
3862             },
3863             function(update) {
3864                 return update.transition()
3865                 .attr('stroke', function(d) {
3866                     let f_color = checkFillResidue(d)[0];
3867                     if (f_color) {
3868                         return f_color;
3869                     }
3870                     return d["fill"];
3871                 })
3872                 .attr("fill", function(d) {
3873                     let f_color = checkFillResidue(d)[0];
3874                     if (f_color) {
3875                         return f_color;
3876                     }
3877                     return d["fill"];
3878                 } )
3879                 .attr("opacity", function(d) {
3880                     let o_color = checkFillResidue(d)[1];
3881                     if (o_color) {
3882                         return o_color;;
3883                     }
3884                     return d["opacity"];
3885                 });
3886             },
3887             function(exit) {
3888                 return exit.remove();
3889             }
3890         );
3891         let created_paths = document.getElementsByClassName(path_class+"_resids_"+data_index);
3892         for (let icp = 0; icp < created_paths.length; icp++) {
3893             const element = created_paths[icp];
3894             // let path_box = element.getBoundingClientRect();
3895             let path_box = element.getBBox();
3896             let xCenter = path_box.x + (path_box.width / 2) ;
3897             let yCenter = path_box.y + (path_box.height / 2) ;
3898             let new_datum = d3.select(element).datum();
3899             new_datum["centroid"] = [xCenter,yCenter];
3900             d3.select(element).datum(new_datum);
3901             if (new_datum.res_ind === "NaN") {
3902                 d3.select(element).remove();
3903             }
3904         }
3905     }
3906     
3907     /**
3908      * Calculate Parent Path division points and Plots residue specific path elements for half turns representing 2 or 3 possible residues.
3909      * d3.curveCardinal is used for drawing new Paths from old points
3910      * @namespace
3911      * @exports NaView
3912      * @name drawHalfHelixResidPolygons 
3913      * @param {Object} data data object for each half helix turn representing 2 or 3 possible residues
3914      * @param {String} path_class name to generate group element class of new sub paths
3915      * @param {number} data_index index of half turn parent element
3916      * @param {HTMLElement} dom_created_path half turn parent path element as DOM Object
3917      * @param {number} dom_created_path_length total length of half turn parent path element
3918      * @param {Object} bottom_curve_points bottom "left" and "right" coordinates of half turn parent path element
3919      * @param {Object} top_curve_points top "left" and "right" coordinates of half turn parent path element
3920      * @param {number} height_sline pixel height of each half turn symetrical left and right vertical lines
3921      * @param {number} curve_length pixel length of each half turn symetrical top and bottom bezier curves
3922      * @param {Array} division_curve_lengths proportion of each residue in parent path half helix polygon
3923      */
3924     function drawHalfHelixResidPolygons(data, path_class, data_index, dom_created_path, dom_created_path_length, bottom_curve_points, top_curve_points, height_sline, curve_length, division_curve_lengths) {
3925         // var svg = d3.select("#"+svg_id);
3926         // let gresareas = svg.append("g").attr("class", "helix_aa_areas");
3927         // console.log("data2");
3928         // console.log(data);
3929         // let g_res_polygons = d3.select("#g_helix_resids_" + data.dom_name + "_" + data.dom_itype);
3930         let g_res_polygons = d3.select("#"+path_class+"_resids").append("g");
3931         
3932         var cardinalDrawing = d3.line().curve(d3.curveCardinal); //https://github.com/d3/d3-shape#curveCardinalClosed
3933     
3934         let left_mid_pt = calculateDotMiddlePoint(bottom_curve_points["left"], top_curve_points["left"]);
3935     
3936         division_curve_lengths
3937         let right_mid_pt = false;
3938         if (division_curve_lengths[1]) {
3939             right_mid_pt = calculateDotMiddlePoint(bottom_curve_points["right"], top_curve_points["right"]);
3940         }
3941     
3942         let smleft_bottom_to_top = [
3943             dotToCoords(bottom_curve_points["left"]),
3944             dotToCoords(top_curve_points["left"]),
3945         ];
3946     
3947         let left_bottom_to_top = generateXMidPts(bottom_curve_points["left"], top_curve_points["left"], 10);
3948         let left_top_to_bottom = generateXMidPts(top_curve_points["left"], bottom_curve_points["left"], 10);
3949     
3950         let right_bottom_to_top = false;
3951         let right_top_to_bottom = false;
3952         if (division_curve_lengths[1]) {
3953             right_bottom_to_top = generateXMidPts(bottom_curve_points["right"], top_curve_points["right"], 10);
3954             right_top_to_bottom = generateXMidPts(top_curve_points["right"], bottom_curve_points["right"], 10);
3955         }
3956     
3957         let array_of_pathpts = [];
3958         
3959         if (division_curve_lengths[1]) {
3960             // each polygon class should be index of half turn residue for easy coloring
3961             array_of_pathpts = [
3962                 [], // first polygon (left in bck, right in frt)
3963                 [], // center polygon
3964                 [] //right polygon (right in bck, left in frt)
3965             ];
3966     
3967             //first polygon downward segment
3968             array_of_pathpts[0].push(...pathSegmentToXYPoints(dom_created_path, 0, height_sline, 5));
3969             //first polygon horizontal segment 1
3970             array_of_pathpts[0].push(...pathSegmentToXYPoints(dom_created_path, height_sline, bottom_curve_points["left"]["path_len_to_pt"], 5));
3971             // first polygon upward segment
3972             array_of_pathpts[0].push(...left_bottom_to_top);
3973             // array_of_pathpts[0].push(...pathSegmentToXYPoints(dom_created_path, height_sline, bottom_curve_points["left"]["path_len_to_pt"], 5));
3974             //first polygon horizontal segment 2
3975             array_of_pathpts[0].push(...pathSegmentToXYPoints(dom_created_path, top_curve_points["left"]["path_len_to_pt"], dom_created_path_length,2));
3976     
3977             //second polygon downward segment
3978             array_of_pathpts[1].push(...left_top_to_bottom);
3979             //second polygon horizontal segment 1
3980             array_of_pathpts[1].push(...pathSegmentToXYPoints(dom_created_path, bottom_curve_points["left"]["path_len_to_pt"], bottom_curve_points["right"]["path_len_to_pt"], 2));
3981             // second polygon upward segment
3982             array_of_pathpts[1].push(...right_bottom_to_top);
3983             //second polygon horizontal segment 2
3984             array_of_pathpts[1].push(...pathSegmentToXYPoints(dom_created_path, top_curve_points["right"]["path_len_to_pt"], top_curve_points["left"]["path_len_to_pt"],2));
3985     
3986             //third polygon downward segment
3987             array_of_pathpts[2].push(...right_top_to_bottom);
3988             //third polygon horizontal segment 1
3989             // array_of_pathpts[2].push(...pathSegmentToXYPoints(dom_created_path, bottom_curve_points["right"]["path_len_to_pt"], (height_sline+curve_length), 5));
3990             array_of_pathpts[2].push(...pathSegmentToXYPoints(dom_created_path, bottom_curve_points["right"]["path_len_to_pt"], (height_sline+division_curve_lengths[1]), 5));
3991             // third polygon upward segment
3992             // array_of_pathpts[2].push(...pathSegmentToXYPoints(dom_created_path, (height_sline+curve_length), ((height_sline*2)+curve_length), 5));
3993             array_of_pathpts[2].push(...pathSegmentToXYPoints(dom_created_path, (height_sline+division_curve_lengths[1]), ((height_sline*2)+division_curve_lengths[1]), 5));
3994             //third polygon horizontal segment 2
3995             // array_of_pathpts[2].push(...pathSegmentToXYPoints(dom_created_path, ((height_sline*2)+curve_length), top_curve_points["right"]["path_len_to_pt"],2));
3996             array_of_pathpts[2].push(...pathSegmentToXYPoints(dom_created_path, ((height_sline*2)+division_curve_lengths[1]), top_curve_points["right"]["path_len_to_pt"],2));
3997         } else {
3998             array_of_pathpts = [
3999                 [], // first polygon (left in bck, right in frt)
4000                 [], // center polygon
4001             ];
4002     
4003             //first polygon downward segment
4004             array_of_pathpts[0].push(...pathSegmentToXYPoints(dom_created_path, 0, height_sline, 5));
4005             //first polygon horizontal segment 1
4006             array_of_pathpts[0].push(...pathSegmentToXYPoints(dom_created_path, height_sline, bottom_curve_points["left"]["path_len_to_pt"], 5));
4007             // first polygon upward segment
4008             array_of_pathpts[0].push(...left_bottom_to_top);
4009             // array_of_pathpts[0].push(...pathSegmentToXYPoints(dom_created_path, height_sline, bottom_curve_points["left"]["path_len_to_pt"], 5));
4010             //first polygon horizontal segment 2
4011             array_of_pathpts[0].push(...pathSegmentToXYPoints(dom_created_path, top_curve_points["left"]["path_len_to_pt"], dom_created_path_length,2));
4012             
4013             // second polygon downward segment
4014             array_of_pathpts[1].push(...left_top_to_bottom);
4015             // second polygon horizontal segment 1
4016             array_of_pathpts[1].push(...pathSegmentToXYPoints(dom_created_path, (height_sline+division_curve_lengths[0]), (height_sline+(curve_length-division_curve_lengths[0])), 2));
4017             // second polygon upward segment
4018             array_of_pathpts[1].push(...pathSegmentToXYPoints(dom_created_path, (height_sline+(curve_length-division_curve_lengths[0])), ((height_sline*2)+curve_length), 2));
4019             // second polygon horizontal segment 2
4020             array_of_pathpts[1].push(...pathSegmentToXYPoints(dom_created_path, ((height_sline*2)+curve_length), ((height_sline*2)+((curve_length-division_curve_lengths[0])+curve_length)), 2));
4021         }
4022     
4023     
4024         
4025         for (let iapp = 0; iapp < array_of_pathpts.length; iapp++) {
4026             let polygon_pathpts = array_of_pathpts[iapp];
4027             data.resids[iapp]["pathpts"] = polygon_pathpts;
4028         }
4029     
4030         g_res_polygons
4031         .selectAll("."+path_class+"_resids_"+data_index)
4032         .data(data.resids)
4033         .join(
4034             function(enter) {
4035                 enter.append("path")
4036                 .attr("class", path_class+"_resids_"+data_index + " single_residue_path")
4037                 .attr("resname", function(d){
4038                     // if (!d.res3 || !d.res_ind) {
4039                         // console.log("d");
4040                         // console.log(d);
4041                     // }
4042                     return d.res_3 + d.res_ind;
4043                 })
4044                 .attr("d", function(d) {
4045                     return cardinalDrawing(d.pathpts);
4046                 })
4047                 .attr('stroke', function(d) {
4048                     let f_color = checkFillResidue(d)[0];
4049                     if (f_color) {
4050                         return f_color;
4051                     }
4052                     // return d["fill"];
4053                     return d["stroke"];
4054                     // return "black";
4055                 })
4056                 .attr("fill", function(d) {
4057                     let f_color = checkFillResidue(d)[0];
4058                     if (f_color) {
4059                         return f_color;
4060                     }
4061                     return d["fill"];
4062                 } )
4063                 .attr("opacity", function(d) {
4064                     let o_color = checkFillResidue(d)[1];
4065                     if (o_color) {
4066                         return o_color;;
4067                     }
4068                     return d["opacity"];
4069                 })
4070                 .attr('stroke-width', function(d) {
4071                     return d.stroke_size;
4072                 })
4073                 .on("mouseover", function(d) {
4074                     console.log("");
4075                     console.log("#######");
4076                     console.log("MOUSE OVER PATH");
4077                     console.log("data:");
4078                     console.log(d);
4079                     console.log("#######");
4080                     console.log("");
4081                 })
4082                 ;
4083             },
4084             function(update) {
4085                 return update.transition()
4086     
4087                 .attr('stroke', function(d) {
4088                     let f_color = checkFillResidue(d)[0];
4089                     if (f_color) {
4090                         return f_color;
4091                     }
4092                     return d["fill"];
4093                 })
4094                 .attr("fill", function(d) {
4095                     let f_color = checkFillResidue(d)[0];
4096                     if (f_color) {
4097                         return f_color;
4098                     }
4099                     return d["fill"];
4100                 } )
4101                 .attr("opacity", function(d) {
4102                     let o_color = checkFillResidue(d)[1];
4103                     if (o_color) {
4104                         return o_color;;
4105                     }
4106                     return d["opacity"];
4107                 });
4108             },
4109             function(exit) {
4110                 return exit.remove();
4111             }
4112         );
4113         let created_paths = document.getElementsByClassName(path_class+"_resids_"+data_index);
4114         for (let icp = 0; icp < created_paths.length; icp++) {
4115             const element = created_paths[icp];
4116             // let path_box = element.getBoundingClientRect();
4117             let path_box = element.getBBox();
4118             // let xCenter = (path_box.left + path_box.right) / 2;
4119             // let yCenter = (path_box.top + path_box.bottom) / 2;
4120             let xCenter = path_box.x + (path_box.width / 2) ;
4121             let yCenter = path_box.y + (path_box.height / 2) ;
4122             let new_datum = d3.select(element).datum();
4123             new_datum["centroid"] = [xCenter,yCenter];
4124             d3.select(element).datum(new_datum);
4125             if (new_datum.res_ind === "NaN") {
4126                 d3.select(element).remove();
4127             }
4128         }
4129         // for (let icp = 0; icp < created_paths.length; icp++) {
4130         //     const element = created_paths[icp];
4131         //     console.log(d3.select(element).datum());
4132         // }
4133     }
4134     
4135     /**
4136      * Partitions half-helix paths into new path elements, each corresponding to a single residue
4137      * @namespace
4138      * @exports NaView
4139      * @name draw_helices_resids_cartoon 
4140      * @see pathStringToStringPoints
4141      * @see getHalfHelixBoundaries
4142      * @see drawHalfHelixResidPolygons
4143      * @see drawSingleHalfHelixResidue
4144      * @param {Array} data back half helices or front half helices specific data array
4145      * @param {String} path_class name to generate group element class of new sub paths
4146      */
4147     function draw_helices_resids_cartoon(data, path_class) {
4148         // function draw_helices_resids_cartoon(data, path_class, half_turns_number, aanum_parity) {
4149         let g_helix_resids = d3.select("#g_helix_" + data[0].dom_name + "_" + data[0].dom_itype)
4150         .append("g")
4151         .attr("id", function() {
4152             return path_class + "_resids"
4153         })
4154         .attr("class", "g_helix_resids residue_path")
4155         // .style("display", "none")
4156         .style("visibility", "hidden")
4157         ;
4158     
4159     
4160         let created_paths = document.getElementsByClassName(path_class);
4161         for (let i_path = 0; i_path < created_paths.length; i_path++) {
4162             let start_a, mid_a, end_a;
4163             
4164             let dom_created_path = created_paths[i_path];
4165             let dom_cpath_bbox = dom_created_path.getBBox();
4166             let dom_created_path_centroid = [dom_cpath_bbox.x + dom_cpath_bbox.width/2, dom_cpath_bbox.y + dom_cpath_bbox.height/2];
4167     
4168             // mid_a = [dom_created_path_centroid[0], dom_created_path_centroid[1]];
4169     
4170             let d3_created_path = d3.select(dom_created_path);
4171             let path_data = d3_created_path.datum();
4172             let ptype = path_data.points[0].pathtype;
4173             let pind = path_data.points[0].turn_index;
4174     
4175             // let creation_string ="Domain: "+ path_data.dom_name +" Helix: "+ path_data.dom_itype +" Half Type: "+ ptype +" Half Index: "+ pind;
4176             // console.log(creation_string);
4177     
4178             let parts_division = path_data.parts_division;
4179     
4180             let path_sline_str = pathStringToStringPoints(d3_created_path, "M");
4181             let path_sline_split = path_sline_str.split(" ");
4182             let height_sline = Math.abs(parseFloat(path_sline_split[1].split(",")[1]) - parseFloat(path_sline_split[2].split(",")[1]));
4183             
4184             //for this use getTotalLength of path for curves
4185             let dom_created_path_length = dom_created_path.getTotalLength();
4186     
4187             //and subtract 2 times line height and divide by 2
4188             let curve_length = (dom_created_path_length - (2*height_sline)) / 2;
4189             
4190             let curve_step =  curve_length / 3;
4191             // let displacement_ps = curve_step*style_obj["protein"]["helix_draw_opts"]["draw"]["aa_area_perc_displacement"];
4192             // //the use getpointatlength for getting the desired points at each third of path
4193             
4194             let current_division = {
4195                 "size": d3.sum(parts_division),
4196                 "curve_steps": []
4197             }
4198             for (let ipd = 0; ipd < parts_division.length-1; ipd++) {
4199                 current_division["curve_steps"].push({"size":parts_division[ipd], "csize": undefined});
4200             }
4201     
4202             if (parts_division.length > 1) {
4203                 let results_pts = false;
4204                 results_pts = getHalfHelixBoundaries(dom_created_path, dom_created_path_length, height_sline, current_division);
4205                 drawHalfHelixResidPolygons(path_data, path_class,i_path, dom_created_path, dom_created_path_length, results_pts[0], results_pts[1], height_sline, curve_length, [results_pts[2], results_pts[3]]);
4206             } else {
4207                 drawSingleHalfHelixResidue(path_data, path_class,i_path, dom_created_path)
4208             }
4209         }
4210     }
4211     
4212     /**
4213      * Plots helices in Cartoon Mode.
4214      * 1. Generates data for each Helix half turns
4215      * 2. Builds bezier curves according to helix properties set in style_obj
4216      * 3. Partition Path Helix Half Turns SVG Object into sub-objects for each residue.
4217      * @namespace
4218      * @exports NaView
4219      * @name draw_helices_cartoon 
4220      * @see gen_helix_cartoon_halfturn_data
4221      * @see buildPathStringFromData
4222      * @see draw_helices_resids_cartoon
4223      * @param {Array} data helix data generated from processRawUniProt
4224      */
4225     function draw_helices_cartoon(data) {
4226     // function draw_helices_cartoon(data, dataId, dataClass) {
4227     
4228         let svg_element = d3.select("#"+svg_id)
4229         .append("g")
4230         .attr("class", "helices_group");
4231     
4232         for (let id = 0; id < data.length; id++) {
4233             let obj = data[id];
4234             let helix_to_draw_data = gen_helix_cartoon_halfturn_data(obj);
4235             
4236             //create helix g and draw it
4237             let cartoon_helix_g = svg_element.append("g")
4238             .attr("id", function(){
4239                 return "g_helix_" + obj.dom_name + "_" + obj.dom_itype
4240             })
4241             .attr("class", "element_path_group")
4242             .datum(obj)
4243             ;
4244     
4245             //draw back helix path
4246             let bck_path_class = "bck_h_hel_path_" + obj.dom_name + "_" + obj.dom_itype;
4247             cartoon_helix_g.append("g")
4248                 .attr("class", "back_half_helix element_path")
4249                 .selectAll("."+bck_path_class)
4250                 .data(helix_to_draw_data["back"])
4251                 .enter()
4252                 .append('path')
4253                 .attr("class", bck_path_class)
4254                 .attr('d', function(d) {
4255                     return buildPathStringFromData(d);
4256                 })
4257                 .attr('stroke', function(d) {return d.stroke_color;})
4258                 .attr('stroke-width', function(d) {return d.stroke_size;})
4259                 .attr('fill', function(d){return d.fill;})
4260                 ;
4261     
4262             // draw_helices_resids_cartoon(helix_to_draw_data["back"], bck_path_class,  helix_to_draw_data["half_turns_number"], helix_to_draw_data["aanum_parity"]);
4263             draw_helices_resids_cartoon(helix_to_draw_data["back"], bck_path_class);
4264             
4265             //draw front helix path
4266             let frt_path_class = "frt_h_hel_path_" + obj.dom_name + "_" + obj.dom_itype;
4267             cartoon_helix_g.append("g")
4268                 .attr("class", "front_half_helix element_path")
4269                 .selectAll("."+frt_path_class)
4270                 .data(helix_to_draw_data["front"])
4271                 .enter()
4272                 .append('path')
4273                 .attr("class", frt_path_class)
4274                 .attr('d', function(d) {
4275                     return buildPathStringFromData(d);
4276                 })
4277                 .attr('stroke', function(d) {return d.stroke_color;})
4278                 .attr('stroke-width', function(d) {return d.stroke_size;})
4279                 .attr('fill', function(d){return d.fill;})
4280                 ;
4281                 // draw_helices_resids_cartoon(helix_to_draw_data["front"], frt_path_class,  helix_to_draw_data["half_turns_number"], helix_to_draw_data["aanum_parity"]);
4282                 draw_helices_resids_cartoon(helix_to_draw_data["front"], frt_path_class);
4283         }
4284     }
4285     
4286     /**
4287      * Calculates helices anchoring points for loop elements
4288      * @namespace
4289      * @exports NaView
4290      * @name get_helices_endpoints 
4291      * @param {String} helix_mode helix drawing mode defined in stye_obj
4292      * @param {Array} helices_data already plotted helix data generated from processRawUniProt with draw areas
4293      * @yields {Array} already plotted helix data now also including anchoring points
4294      */
4295     function get_helices_endpoints(helix_mode, helices_data) {
4296         //iterate over helices, get their elements, calculate centroid or box points, push to data
4297         for (let ihg = 0; ihg < helices_data.length; ihg++) {
4298             let helix_data = helices_data[ihg];
4299             let helix_group = document.getElementById("g_helix_"+helix_data.dom_name+"_"+helix_data.dom_itype);
4300             let residue_groups = helix_group.getElementsByClassName("g_helix_resids");
4301             let helix_group_bcr = helix_group.getBBox()
4302             let maximum_y = 0;
4303             let maximum_bbox;
4304             let element_of_maximum_y;
4305             for (let irg = 0; irg < residue_groups.length; irg++) {
4306                 let residue_group = residue_groups[irg];
4307                 let residue_elements = residue_group.childNodes;
4308                 for (let ire = 0; ire < residue_elements.length; ire++) {
4309                     let residue_element = residue_elements[ire];
4310                     let residue_element_box = residue_element.getBoundingClientRect();
4311                     if (residue_element_box.y > maximum_y) {
4312                         element_of_maximum_y = residue_element;
4313                         maximum_y = residue_element_box.y;
4314                         maximum_bbox = residue_element.getBBox();
4315                     }
4316                 }
4317             }
4318             let x_anchor_start = helix_group_bcr.x + (helix_group_bcr.width/2);
4319             let x_anchor_end = maximum_bbox.x + (maximum_bbox.width/2);
4320             if (helix_mode === "cartoon") {
4321                 x_anchor_start = helix_group_bcr.x;
4322                 x_anchor_end = maximum_bbox.x + maximum_bbox.width;
4323                 let element_to_data = element_of_maximum_y.querySelector("path");
4324                 let element_to_data_class = d3.select(element_to_data).attr("class");
4325                 if (element_to_data_class.includes("frt")) {
4326                     x_anchor_end = maximum_bbox.x;
4327                 }
4328             }
4329             let y_anchor_start = helix_group_bcr.y;
4330             let y_anchor_end = maximum_bbox.y + maximum_bbox.height;
4331             helices_data[ihg].anchor = {
4332                 "top":[x_anchor_start,y_anchor_start],
4333                 "bottom":[x_anchor_end,y_anchor_end]
4334             }
4335         }
4336         return helices_data;
4337     }
4338     
4339     /**
4340      * Plots helices in mode defined by style_obj.
4341      * @namespace
4342      * @exports NaView
4343      * @name draw_helices 
4344      * @see draw_helices_box
4345      * @see draw_helices_cylinder
4346      * @see draw_helices_cartoon
4347      * @see get_helices_endpoints
4348      * @param {Array} helices_data helix data generated from processRawUniProt with draw area
4349      * @yields {Array}  helix data generated from processRawUniProt with additional information generated from plot
4350      */
4351     function draw_helices(helices_data) {
4352         //helix can be box, cylinder or cartoon helix
4353         //membrane can be box or lipid
4354         if (style_obj.protein.helix_mode === "box") {
4355             // draw_helices_box(helices_data, "helices", "helices");
4356             draw_helices_box(helices_data, "helices");
4357         } else if (style_obj.protein.helix_mode === "cylinder") {
4358             // draw_helices_cylinder(helices_data, "helices", "helices");
4359             draw_helices_cylinder(helices_data, "helices");
4360         } else if (style_obj.protein.helix_mode === "cartoon") {
4361             // draw_helices_cartoon(helices_data, "helices", "helices");
4362             draw_helices_cartoon(helices_data);
4363         }
4364         helices_data = get_helices_endpoints(style_obj.protein.helix_mode, helices_data);
4365         return helices_data;
4366     }
4367     
4368     /**
4369      * Creates a vector from the difference of two 2d-vectors
4370      * @namespace
4371      * @exports NaView
4372      * @name createVector 
4373      * @param {Array} p1 array of coordinates 1
4374      * @param {Array} p2 array of coordinates 2
4375      * @yields {Array} array of coordinates from the difference between two vectors
4376      */
4377     function createVector(p1, p2) {
4378         return [p1[0]-p2[0], p1[1] - p2[1]];
4379     }
4380 
4381     /**
4382      * Normalizes a 2d-vector
4383      * @namespace
4384      * @exports NaView
4385      * @name normalizeVector 
4386      * @param {Array} v1 array of coordinates 1
4387      * @yields {Array} normalized array of coordinates
4388      */
4389     function normalizeVector(v1) {
4390         let dist = Math.sqrt( Math.pow(v1[0], 2) + Math.pow(v1[1], 2));
4391         return [v1[0]/dist, v1[1]/dist];
4392     }
4393     
4394     /**
4395      * Scales a 2d-vector to a given length
4396      * @namespace
4397      * @exports NaView
4398      * @name scaleVector 
4399      * @param {Array} v1 array of coordinates 1
4400      * @param {number} val length to scale vector to
4401      * @yields {Array} scaled array of coordinates
4402      */
4403     function scaleVector(v1, val) {
4404         return [v1[0] * val, v1[1] * val];
4405     }
4406 
4407     /**
4408      * Rotates a 2d-vector 90 degrees in the clockwise or anticlockwise directions
4409      * @namespace
4410      * @exports NaView
4411      * @name rotate90 
4412      * @param {Array} v1 array of coordinates 1
4413      * @param {String} direction String that defines clockwise or anticlockwise 90 rotation of vector
4414      * @param {Array} centerp array of coordinates to center rotated vector
4415      * @yields {Array} rotated array of coordinates
4416      */
4417     function rotate90(v1, direction, centerp) {
4418         if (!direction) {
4419             direction = "clockwise";
4420         }
4421         if (!centerp) {
4422             centerp = [0, 0];
4423         }
4424         if (direction === "clockwise") {
4425             return [
4426                 // (v1[1] - centerp[1]) + centerp[0],
4427                 // (-1*(v1[0]-centerp[0]))+centerp[1]
4428                 v1[1]+centerp[0],
4429                 (-1*v1[0])+centerp[1]
4430             ];
4431         } else {
4432             return [
4433                 // (-1*(v1[1] -centerp[1])) + centerp[0],
4434                 // (v1[0]-centerp[0])+centerp[1]
4435                 (-1*v1[1])+centerp[0],
4436                 v1[0]+centerp[1]
4437             ];
4438         }
4439     }
4440 
4441     /**
4442      * Rotates a 2d-vector by a given angle
4443      * @namespace
4444      * @exports NaView
4445      * @name rotateByAng 
4446      * @param {Array} v1 array of coordinates 1
4447      * @param {Array} centerp array of coordinates to center rotated vector
4448      * @param {number} angle angle in degrees to rotate a vector to
4449      * @yields {Array} rotated array of coordinates
4450      */
4451     function rotateByAng(v1, centerp, angle) {
4452         let x = v1[0];
4453         let y = v1[1];
4454         let cx = centerp[0];
4455         let cy = centerp[1];
4456         var radians = (Math.PI / 180) * angle,
4457             cos = Math.cos(radians),
4458             sin = Math.sin(radians),
4459             // nx = (cos * (x - cx)) + (sin * (y - cy)) + cx,
4460             // ny = (cos * (y - cy)) - (sin * (x - cx)) + cy;
4461             nx = (cos * x) + (sin * y) + cx,
4462             ny = (cos * y) - (sin * x) + cy;
4463         return [nx, ny];
4464     }
4465 
4466     /**
4467      * Calculates coordinates at a given angle and distance from another point
4468      * @namespace
4469      * @exports NaView
4470      * @name getPointAtAngleDistance 
4471      * @param {number} x coordinate x of initial point
4472      * @param {number} y coordinate y of initial point
4473      * @param {number} angle desired angle to find a point
4474      * @param {number} distance desired distance to find a point
4475      * @yields {Array} coordinates at a given angle and distance
4476      */
4477     function getPointAtAngleDistance(x, y, angle, distance) {
4478         var result = [];
4479         result.push(Math.round(Math.cos(angle * Math.PI / 180) * distance + x));
4480         result.push(Math.round(Math.sin(angle * Math.PI / 180) * distance + y));
4481         return result;
4482     }
4483     /**
4484      * Calculates angle between three points. Center point is B
4485      * @namespace
4486      * @exports NaView
4487      * @name find_angle 
4488      * @param {Array} A array of coordinates A
4489      * @param {Array} B array of coordinates B (center)
4490      * @param {Array} C array of coordinates C
4491      * @yields {number} coordinates at a given angle and distance
4492      */
4493     function find_angle(A,B,C) {
4494         var AB = Math.sqrt(Math.pow(B[0]-A[0],2)+ Math.pow(B[1]-A[1],2));    
4495         var BC = Math.sqrt(Math.pow(B[0]-C[0],2)+ Math.pow(B[1]-C[1],2)); 
4496         var AC = Math.sqrt(Math.pow(C[0]-A[0],2)+ Math.pow(C[1]-A[1],2));
4497         let radians_angle = Math.acos((BC*BC+AB*AB-AC*AC)/(2*BC*AB));
4498         return radians_angle * (180/Math.PI);
4499     }
4500 
4501     /**
4502      * Calculates the length of a SVG path without displaying it on a screen
4503      * @namespace
4504      * @exports NaView
4505      * @name getGhostPathLength 
4506      * @param {Array} pts Array of coordinates to draw a path using d3.curveNatural
4507      * @param {String} path_id_keep id of generated "ghost" path. if set, vector is not automatically removed from SVG
4508      * @yields {number} calculated length of a vector
4509      */
4510     function getGhostPathLength(pts, path_id_keep) {
4511         let curve = d3.line().curve(d3.curveNatural);
4512         let pg = d3.select("#"+svg_id)
4513         .append("g")
4514         .attr("id", function() {
4515             if (path_id_keep) {
4516                 return "par"+path_id_keep;
4517             }
4518             return;
4519         })
4520         .attr("visibility", "hidden");
4521 
4522         let p = pg.append("path")
4523         .attr("id", function() {
4524             if (path_id_keep) {
4525                 return path_id_keep;
4526             }
4527             return;
4528         })
4529         .attr("d", function() {
4530             return curve(pts);
4531         });
4532 
4533         let val = p.node().getTotalLength();
4534         val = roundDecimals(val, 2);
4535         if (!path_id_keep) {
4536             pg.remove();
4537         }
4538         return val;
4539     }
4540     /**
4541      * Calculates length of a vector between two points using the pythagorean theorem
4542      * @namespace
4543      * @exports NaView
4544      * @name euclideanDistance 
4545      * @param {Array} p1 array of coordinates 1
4546      * @param {Array} p2 array of coordinates 2
4547      * @yields {number} length of vector between two coordinates
4548      */
4549     function euclideanDistance(p1, p2) {
4550         return Math.sqrt(Math.pow(p1[0] - p2[0], 2) + Math.pow(p1[1] - p2[1], 2));
4551     }
4552 
4553     /**
4554      * Simple loop curve generating function.<br>
4555      * 1. A centroid is calculated between two points<br>
4556      * 2. This centroid y position is raised by a given step in a given direction (scaled_centroid point)<br>
4557      *    2a. A skew value CAN added to the x positioning of this centroid point<br>
4558      * 3. Original and scaled centroid are returned.<br>
4559      * @namespace
4560      * @exports NaView
4561      * @name generateSimpleLoopPoints 
4562      * @param {Array} p1 point 1 (start)
4563      * @param {Array} p2 point 2 (end)
4564      * @param {number} step_y number of pixels to scale centroid generating y scaled curve center
4565      * @param {number} y_direction (1.0 or -1.0), y direction to scale step
4566      * @param {number} x_skew_perc Optional percentual between centroid x and point 1 x to move the y scaled curve center
4567      * @param {Object} add_wavefold_obj Optional: addition of a folding object. See <i><b></b></i>
4568      * @yields {Array} list of three points composing curve to be generated by d3.curveNatural left to right: (p1, scaled_centroid, p2)
4569      */
4570     function generateSimpleLoopPoints(p1, p2, step_y, y_direction, x_skew_perc, add_wavefold_obj) {
4571         if (!y_direction) {
4572             y_direction = 1.0;
4573         }
4574         if (!x_skew_perc) {
4575             x_skew_perc = 0.0;
4576         }
4577         let x1 = p1[0];
4578         let y1 = p1[1];
4579 
4580         let x2 = p2[0];
4581         let y2 = p2[1];
4582 
4583         let xc = (x1+x2)/2;
4584         let yc = (y1+y2)/2;
4585 
4586         let x_p1 = xc + (x_skew_perc*(xc-x1));
4587         let y_p1 = yc + ((y_direction*-1)* step_y);
4588 
4589         let resulting_points = [
4590             [x1,y1],
4591             [x_p1,y_p1],
4592             [x2,y2]
4593         ];
4594         if (add_wavefold_obj) {
4595             if (add_wavefold_obj.type === "fold") {
4596                 resulting_points = foldAddition(resulting_points, add_wavefold_obj);
4597             }
4598             // } else if (add_wavefold_obj.type === "wave")  {
4599             //     resulting_points = waveAddition(resulting_points, add_wavefold_obj);
4600             // }
4601         }
4602         return resulting_points;
4603     }
4604 
4605     /**
4606      * Swirled loop curve generating function.<br>
4607      * 1. A centroid is calculated between two points<br>
4608      * 2. This centroid y position is raised by a given step in a given direction (scaled_centroid point)<br>
4609      *    2a. A skew value CAN added to the x positioning of this centroid point<br>
4610      *    2b. A swirl value is added to the x positioning of this centroid point<br>
4611      * 3. Original and scaled centroid are returned.<br>
4612      * 4. Two new points (swirl points) are generated.<br>
4613      *    4a. Their x coordinates are guided by the opposite swirl value (-1*) of the centroid swirl<br>
4614      *    4b. Their y coordinates are guided by a percentage of the total y step<br>
4615      * @namespace
4616      * @exports NaView
4617      * @name generateSwirlLoopPoints 
4618      * @param {Array} p1 point 1 (start)
4619      * @param {Array} p2 point 2 (end)
4620      * @param {number} swirl_x swirling value to generate wavy curve
4621      * @param {number} step_y number of pixels to scale centroid generating y scaled curve center
4622      * @param {number} perc_step_y  (0 to 1), percentage of y step to generate swirl points
4623      * @param {number} y_direction (1.0 or -1.0), y direction to scale step
4624      * @param {Object} add_wavefold_obj Optional: addition of a folding object. See <i><b></b></i>
4625      * @yields {Array} list of five points composing curve to be generated by d3.curveNatural left to right: (p1, pswirl1, scaled_centroid, pswirl2, p2)
4626      */
4627     function generateSwirlLoopPoints(p1, p2, swirl_x, step_y, perc_step_y, y_direction, add_wavefold_obj) {
4628         if (!y_direction) {
4629             y_direction = 1.0;
4630         }
4631         let x1 = p1[0];
4632         let y1 = p1[1];
4633 
4634         let x2 = p2[0];
4635         let y2 = p2[1];
4636 
4637         let xc = (x1+x2)/2;
4638         let yc = (y1+y2)/2;
4639 
4640         let swirl_value = (swirl_x*(xc-x1));
4641         let x_p1 = x1 - swirl_value;
4642         let y_p1 = yc + ((y_direction*-1)* (step_y*perc_step_y));
4643         
4644         // let x_p2 = xc + ((x_direction*-1) * swirl_value);
4645         let x_p2 = xc - swirl_value;
4646         let y_p2 = yc + ((y_direction*-1)* step_y);
4647 
4648         let x_p3 = x2 - swirl_value;
4649         let y_p3 = yc + ((y_direction*-1)* (step_y*perc_step_y));
4650 
4651         let resulting_points = [
4652             [x1,y1],
4653             [x_p1,y_p1],
4654             [x_p2,y_p2],
4655             [x_p3,y_p3],
4656             [x2,y2]
4657         ];
4658         return resulting_points;
4659     }
4660 
4661     /**
4662      * Bulb loop curve generating function.<br>
4663      * 1. A centroid is calculated between two points<br>
4664      * 2. This centroid y position is raised by a given step in a given direction (scaled_centroid point)<br>
4665      *    2a. A skew value CAN added to the x positioning of this centroid point<br>
4666      * 3. Original and scaled centroid are returned.<br>
4667      * 4. Two new points (bulb points) are generated:<br>
4668      *    4a. They are created before the first point and after the second point (horizontally) by a given x_step<br>
4669      *    4b. Their vertical position is arranged at a percentage of the y step<br>
4670      * @namespace
4671      * @exports NaView
4672      * @name generateBulbLoopPoints 
4673      * @param {Array} p1 point 1 (start)
4674      * @param {Array} p2 point 2 (end)
4675      * @param {number} step_x number of pixels to scale bulb points
4676      * @param {number} step_y number of pixels to scale centroid generating y scaled curve center
4677      * @param {number} perc_step_y  (0 to 1), percentage of y step to generate bulb points
4678      * @param {number} y_direction (1.0 or -1.0), y direction to scale bulb points
4679      * @param {number} x_skew_perc Optional percentual between centroid x and point 1 x to move the y scaled curve center
4680      * @param {Object} add_wavefold_obj Optional: addition of a folding object. See <i><b></b></i>
4681      * @yields {Array} list of five points composing curve to be generated by d3.curveNatural. left to right: (pbulb1, p1, scaled_centroid, p3, pbulb2)
4682      */
4683     function generateBulbLoopPoints(p1, p2, step_x, step_y, perc_step_y, y_direction, x_skew_perc, add_wavefold_obj) {
4684         if (!y_direction) {
4685             y_direction = 1.0;
4686         }
4687         if (!x_skew_perc) {
4688             x_skew_perc = 0.0;
4689         }
4690         let x1 = p1[0];
4691         let y1 = p1[1];
4692 
4693         let x2 = p2[0];
4694         let y2 = p2[1];
4695 
4696         let xc = (x1+x2)/2;
4697         let yc = (y1+y2)/2;
4698 
4699         let x_p1 = x1-step_x;
4700         let y_p1 = yc + ((y_direction*-1)* (step_y*perc_step_y));
4701 
4702 
4703         // let x_p2 = xc;
4704         let x_p2 = xc + (x_skew_perc*(xc-x1));
4705         let y_p2 = yc + ((y_direction*-1)* step_y);
4706 
4707         let x_p3 = x2+step_x;
4708         let y_p3 = yc + ((y_direction*-1)* (step_y*perc_step_y));
4709 
4710         let resulting_points = [
4711             [x1,y1],
4712             [x_p1,y_p1],
4713             [x_p2,y_p2],
4714             [x_p3,y_p3],
4715             [x2,y2]
4716         ];
4717         if (add_wavefold_obj) {
4718             if (add_wavefold_obj.type === "fold") {
4719                 resulting_points = foldAddition(resulting_points, add_wavefold_obj);
4720             }
4721             // } else if (add_wavefold_obj.type === "wave")  {
4722             //     resulting_points = waveAddition(resulting_points, add_wavefold_obj);
4723             // }
4724         }
4725         return resulting_points;
4726     }
4727 
4728     /**
4729      * Mushroom loop curve generating function.<br>
4730      * 1. A centroid is calculated between two points<br>
4731      * 2. This centroid y position is raised by a given step in a given direction (scaled_centroid point)<br>
4732     *    2a. A skew value CAN added to the x positioning of this centroid point<br>
4733      * 3. Original and scaled centroid are returned.<br>
4734      * 4. Two new points (bulb points) are generated:<br>
4735      *    4a. They are created before the first point and after the second point (horizontally) by a given x_step<br>
4736      *    4b. Their vertical position is arranged at a percentage of the y step<br>
4737      * 5. Two new points (mushroom points) are generated:<br>
4738      *    5a. Their x coordinate is defined by a percentage of the centroid to p1 and p2<br>
4739      *    5b. Their y coordinate is arranged at a percentage of the y step<br>
4740      * @namespace
4741      * @exports NaView
4742      * @name generateMushRoomLoopPoints 
4743      * @param {Array} p1 point 1 (start)
4744      * @param {Array} p2 point 2 (end)
4745      * @param {number} step_x number of pixels to scale bulb points
4746      * @param {number} step_y number of pixels to scale centroid generating y scaled curve center
4747      * @param {number} perc_center_x (0 to 1), percentage of p1-center and p2-center for mushroom points
4748      * @param {number} perc_step_y1  (0 to 1), percentage of y step to generate bulb points
4749      * @param {number} perc_step_y2  (0 to 1), percentage of y step to generate mushroom points
4750      * @param {number} y_direction (1.0 or -1.0), y direction to scale step
4751      * @param {number} x_skew_perc Optional percentual between centroid x and point 1 x to move the y scaled curve center
4752      * @param {Object} add_wavefold_obj Optional: addition of a folding object. See <i><b></b></i>
4753      * @yields {Array} list of seven points composing curve to be generated by d3.curveNatural. left to right: (pbulb1, p1, pmush1, scaled_centroid, pmush2, p3, pbulb2)
4754      */
4755     function generateMushRoomLoopPoints(p1,p2, step_x, step_y, perc_center_x, perc_step_y1, perc_step_y2, y_direction, x_skew_perc, add_wavefold_obj) {
4756         if (!y_direction) {
4757             y_direction = 1.0;
4758         }
4759         if (!x_skew_perc) {
4760             x_skew_perc = 0.0;
4761         }
4762         let x1 = p1[0];
4763         let y1 = p1[1];
4764 
4765         let x2 = p2[0];
4766         let y2 = p2[1];
4767 
4768         let xc = (x1+x2)/2;
4769         let yc = (y1+y2)/2;
4770 
4771         // let x_p3 = xc;
4772         let x_p3 = xc + (x_skew_perc*(xc-x1));
4773         let y_p3 = yc + ((y_direction*-1)* step_y);
4774 
4775         let x_p1 = xc - (perc_center_x*(xc-x1));
4776         let y_p1 = yc + (((y_direction*-1)* step_y) * perc_step_y1);
4777         
4778         let x_p2 = x1 - step_x;
4779         let y_p2 = yc + (((y_direction*-1)* step_y) * perc_step_y2);
4780         
4781         let x_p4 = x2 + step_x;
4782         let y_p4 = yc + (((y_direction*-1)* step_y) * perc_step_y2);
4783 
4784         let x_p5 = xc + (perc_center_x*(xc-x1));
4785         let y_p5 = yc + (((y_direction*-1)* step_y) * perc_step_y1);
4786 
4787         let resulting_points = [
4788             [x1,y1],
4789             [x_p1,y_p1],
4790             [x_p2,y_p2],
4791             [x_p3,y_p3],
4792             [x_p4,y_p4],
4793             [x_p5,y_p5],
4794             [x2,y2]
4795         ];
4796         if (add_wavefold_obj) {
4797             if (add_wavefold_obj.type === "fold") {
4798                 resulting_points = foldAddition(resulting_points, add_wavefold_obj);
4799             }
4800             // } else if (add_wavefold_obj.type === "wave")  {
4801             //     resulting_points = waveAddition(resulting_points, add_wavefold_obj);
4802             // }
4803         }
4804         return resulting_points;
4805     }
4806 
4807     /**
4808      * Skewed loop curve generating function.<br>
4809      * 1. A centroid is calculated between two points<br>
4810      * 2. This centroid y position is raised by a given step in a given direction (skewed_centroid point)<br>
4811      *    2a. A skew value is added to the x positioning of this centroid point<br>
4812      * @namespace
4813      * @exports NaView
4814      * @name generateSkewedSimpleLoopPoints 
4815      * @param {Array} p1 point 1 (start)
4816      * @param {Array} p2 point 2 (end)
4817      * @param {number} perc_center_x (0 to 1), percentage of p1-center for skewed point
4818      * @param {number} step_y number of pixels to scale centroid generating y scaled curve center
4819      * @param {number} y_direction (1.0 or -1.0), y direction to scale step
4820      * @yields {Array} list of three points composing curve to be generated by d3.curveNatural left to right: (p1, skewed_centroid, p2)
4821      */
4822     function generateSkewedSimpleLoopPoints(p1, p2, perc_center_x, step_y, y_direction) {
4823         if (!y_direction) {
4824             y_direction = 1.0;
4825         }
4826         let x1 = p1[0];
4827         let y1 = p1[1];
4828 
4829         let x2 = p2[0];
4830         let y2 = p2[1];
4831 
4832         let xc = (x1+x2)/2;
4833         let yc = (y1+y2)/2;
4834 
4835         let x_p1 = xc + (perc_center_x*(xc-x1));
4836         let y_p1 = yc + ((y_direction*-1)* step_y);
4837 
4838         let resulting_points = [
4839             [x1,y1],
4840             [x_p1,y_p1],
4841             [x2,y2]
4842         ];
4843         return resulting_points;
4844     }
4845 
4846     /**
4847      * Wavy loop curve generating function.<br>
4848      * 1. N points are created between the start and end points<br>
4849      * 2. Each point is a created from a Normal from the vector between the start and end points<br>
4850      * 3. Height scales of N curves is defined as a percentage of a given maximum step<br>
4851      * @namespace
4852      * @exports NaView
4853      * @name generateNWaveCurves 
4854      * @param {Array} p1 point 1 (start)
4855      * @param {Array} p2 point 2 (end)
4856      * @param {number} n_of_centers number of wave peaks to generate
4857      * @param {number} step_height number of pixels to generate wave peaks centers
4858      * @param {Array} perc_centers_height array of percentages to generate wave peaks from step height
4859      * @param {Boolean} clockwise starting orientation of generated wave peaks clockwise (true) or counterclockwise (false)
4860      * @param {number} rotate_loop angle to rotate final path in relation to the its anchored point
4861      * @yields {Array} list of N points composing curve to be generated by d3.curveNatural left to right: (p1, ... , p2)
4862      */
4863     function generateNWaveCurves(p1, p2, n_of_centers, step_height,perc_centers_height, clockwise, rotate_loop) {
4864         if (!clockwise) {
4865             clockwise = true;
4866         }
4867         let resulting_points = [];
4868         
4869         let x1 = p1[0];
4870         let y1 = p1[1];
4871 
4872         let x2 = p2[0];
4873         let y2 = p2[1];
4874         if (rotate_loop) {
4875             if (y2 > y1) {
4876                 let vecp1p2 = createVector(p2, p1);
4877                 let p2rotation = rotateByAng(vecp1p2, [x1, y1], rotate_loop);
4878                 x2 = p2rotation[0];
4879                 y2 = p2rotation[1];
4880             } else {
4881                 let vecp1p2_2 = createVector(p1, p2);
4882                 let p1rotation = rotateByAng(vecp1p2_2, [x2, y2], rotate_loop);
4883 
4884                 x1 = p1rotation[0];
4885                 y1 = p1rotation[1];
4886             }
4887         }
4888         resulting_points.push([x1,y1]);
4889 
4890         // let n_of_centers = perc_x_centers_array.length;
4891         let x_step_size = (x2-x1)/(n_of_centers+1);
4892         let y_step_size = (y2-y1)/(n_of_centers+1);
4893         let prev_xpc = x1;
4894         let prev_ypc = y1;
4895         for (let i = 1; i <= n_of_centers; i++) {
4896 
4897             let x_pc = x1+(x_step_size * i);
4898             let y_pc = y1+(y_step_size * i);
4899 
4900             let current_step = step_height * perc_centers_height[i-1];
4901 
4902             let rotation_direction;
4903             if (clockwise === true) {
4904                 rotation_direction = "clockwise";
4905             } else {
4906                 rotation_direction = "anticlockwise";
4907             }
4908             clockwise = !clockwise;
4909 
4910             let v_pc = createVector([x_pc+0, y_pc+0], [prev_xpc, prev_ypc]);
4911             let v_pc_norm = normalizeVector(v_pc);
4912             let v_pc_scale = scaleVector(v_pc_norm, current_step);
4913             let v_pc_rot = rotate90(v_pc_scale, rotation_direction, [x_pc, y_pc]);
4914             resulting_points.push(v_pc_rot);
4915             prev_xpc = x_pc;
4916             prev_ypc = y_pc;
4917         }
4918         resulting_points.push([x2,y2]);
4919         return resulting_points;
4920     }
4921 
4922     /**
4923      * Function to add horizontal folds on Simple, Bulb and Mushroom curves.<br>
4924      * Works by finding x centroid of current curve and y point connected to closest membrane (anchored centroid) <br>
4925      * Folds are then created by creating copies of the curve points with scaled distances to this anchored centroid
4926      * with the first and last points excluded.<br>
4927      * @namespace
4928      * @exports NaView
4929      * @name foldAddition 
4930      * @param {Array} resulting_points curve points to draw d3.curveNatural curve
4931      * @param {Object} add_fold_obj Object part of style_obj describing shape of this horizontal fold (number of folds, relative heights)
4932      * @yields {Array} list of N points composing curve to be generated by d3.curveNatural left to right: (p1, ... , p2)
4933      */
4934     function foldAddition(resulting_points, add_fold_obj) {
4935         let new_resulting_points = [];
4936         let half_index = Math.ceil(resulting_points.length / 2);
4937         let ghost_length = getGhostPathLength(resulting_points, "ghostParent");
4938         let ghost_path_doc = document.getElementById("ghostParent");
4939         
4940         let start_abs = ghost_length * add_fold_obj.start;
4941 
4942         let first_point = ghost_path_doc.getPointAtLength(start_abs);
4943         let first_point_xy = [first_point.x, first_point.y];
4944 
4945         new_resulting_points = deepCopy(resulting_points).slice(0, half_index);
4946         let new_points = [];
4947         let anchored_centroid = calculateDotArrayMiddlePoint(resulting_points[0],resulting_points[resulting_points.length-1]);
4948         if (add_fold_obj.x_scaling !== 0) {
4949             anchored_centroid[0] = anchored_centroid[0]+(euclideanDistance(anchored_centroid, resulting_points[resulting_points.length-1])* add_fold_obj.x_scaling);
4950         }
4951 
4952         let resulting_points_no_fl = deepCopy(resulting_points);
4953         resulting_points_no_fl = resulting_points_no_fl.slice(1,resulting_points.length-1);
4954         
4955         for (let ih = 0; ih < add_fold_obj.heights.length; ih++) {
4956             let scale_factor = add_fold_obj.heights[ih];
4957             let to_new_points = [];
4958             // let starting_loop_point;
4959             if (ih % 2 === 0 ) {
4960                 for (let iep = resulting_points_no_fl.length-1; iep >= 0; iep--) {
4961                     let point = resulting_points_no_fl[iep];
4962                     let dist_to_centroid = euclideanDistance(point, anchored_centroid);
4963                     let scale_value = dist_to_centroid * scale_factor;
4964                     let vector_point_to_centroid = createVector(point, anchored_centroid);
4965                     let vector_point_to_centroid_norm = normalizeVector(vector_point_to_centroid);
4966                     let vector_point_to_centroid_norm_scaled = scaleVector(vector_point_to_centroid_norm, scale_value);
4967                     let point_to_centroid_norm_scaled = createVector(point, vector_point_to_centroid_norm_scaled);
4968 
4969                     to_new_points.push(point_to_centroid_norm_scaled);
4970                 }
4971             } else {
4972                 for (let iep = 0; iep < resulting_points_no_fl.length; iep++) {
4973                     let point = resulting_points_no_fl[iep];
4974                     let dist_to_centroid = euclideanDistance(point, anchored_centroid);
4975                     let scale_value = dist_to_centroid * scale_factor;
4976                     let vector_point_to_centroid = createVector(point, anchored_centroid);
4977                     let vector_point_to_centroid_norm = normalizeVector(vector_point_to_centroid);
4978                     let vector_point_to_centroid_norm_scaled = scaleVector(vector_point_to_centroid_norm, scale_value);
4979                     let point_to_centroid_norm_scaled = createVector(point, vector_point_to_centroid_norm_scaled);
4980 
4981                     to_new_points.push(point_to_centroid_norm_scaled);
4982                 }
4983             }
4984             new_points.push(...to_new_points);
4985         }
4986         new_resulting_points.push(first_point_xy);
4987         new_resulting_points.push(...deepCopy(new_points));
4988         new_resulting_points.push(resulting_points[resulting_points.length-1]);
4989         
4990         // plotPoints(new_points);
4991         // plotPoints([anchored_centroid]);
4992         d3.select("#parghostParent").remove();
4993         // return resulting_points;
4994         return new_resulting_points;
4995     }
4996 
4997     /**
4998      * For scaled loops, generate a linear, power or log scale
4999      * @namespace
5000      * @exports NaView
5001      * @name makeLoopScaling 
5002      * @param {Object} scale_var variable containing "domain" and "range" of loop scale
5003      * @param {Object} loop_length_calculation variable part of style_obj containing additional information about scale such as exponent and base
5004      * @yields d3.js scale function for loop length calculations
5005      */
5006     function makeLoopScaling(scale_var, loop_length_calculation) {
5007         let loop_scaling;
5008         if (scale_var.scale === "linear") {
5009             loop_scaling = d3.scaleLinear()
5010             .domain(scale_var.domain)
5011             .range(scale_var.range);
5012         } else if (scale_var.scale === "power") {
5013             loop_scaling = d3.scalePow()
5014             .exponent(loop_length_calculation.calc.exponent)
5015             .domain(scale_var.domain)
5016             .range(scale_var.range);
5017         } else if (scale_var.scale === "log") {
5018             loop_scaling = d3.scaleLog()
5019             .base(loop_length_calculation.calc.base)
5020             .domain(scale_var.domain)
5021             .range(scale_var.range);
5022         }
5023         return loop_scaling;
5024     }
5025     
5026     /**
5027      * Verifies if a fold object should be added to this given loop
5028      * @namespace
5029      * @exports NaView
5030      * @name checkWaveFold 
5031      * @param {Array} loop_data loop data generated from processRawUniProt function
5032      * @param {Object} add_wave_fold_dict_array object part of style_obj describing loops in which folds should be added
5033      * @yield null or Object part of style_obj describing how loop should be folded
5034      */
5035     function checkWaveFold(loop_data, add_wave_fold_dict_array) {
5036         let wave_fold_obj;
5037         let dom_name;
5038         let dom_itype;
5039         if (loop_data.hasOwnProperty("dom_name") && loop_data.hasOwnProperty("dom_itype") ) {
5040             dom_name = loop_data.dom_name;
5041             dom_itype = loop_data.dom_itype;
5042         }
5043         if (dom_name && dom_itype) {
5044             wave_fold_obj = add_wave_fold_dict_array[dom_name][dom_itype];
5045         } else if (loop_data.hasOwnProperty("dom_iname")) {
5046             let dom_iname = loop_data.dom_iname;
5047             if (dom_iname) {
5048                 wave_fold_obj = add_wave_fold_dict_array[dom_iname];
5049             }
5050         }
5051         return wave_fold_obj;
5052     }
5053     
5054     /**
5055      * Checks for an array of coordinate points if any coordinate is NaN
5056      * @namespace
5057      * @exports NaView
5058      * @name checkForNaN 
5059      * @param {Array} points array of coordinate points
5060      * @yields {Boolean} true if NaN is present or false if NaN is absent
5061      */
5062     function checkForNaN(points) {
5063         for (let ip = 0; ip < points.length; ip++) {
5064             // const element = points[ip];
5065             if (isNaN(points[ip][0]) || isNaN(points[ip][1])) {
5066                 return true;
5067             }
5068         }
5069         return false;
5070     }
5071 
5072     /**
5073      * Generate loop points for short and long loops of fixed height and width
5074      * @namespace
5075      * @exports NaView
5076      * @name calculateFixedPointData 
5077      * @param {Array} short_loop_data array of short or long loop data generated by processRawUniProt
5078      * @param {Object} loop_length_calculation Object part of style_obj describing how loop length should be calculated
5079      * @param {Object} loop_drawing_shape Object part of style_obj describing which loop shape should be drawn
5080      * @yields {Array} array of short or long loop data generated by processRawUniProt with points describing loop added
5081      */
5082     function calculateFixedPointData(short_loop_data, loop_length_calculation, loop_drawing_shape) {
5083         let curve_height;
5084         let curve_width;
5085         if (loop_drawing_shape.type === "simple") {
5086             curve_height = svg_height * loop_length_calculation.calc.height;
5087         } else if (loop_drawing_shape.type === "swirl") {
5088             curve_height = svg_height * loop_length_calculation.calc.height;
5089         } else if (loop_drawing_shape.type === "bulb") {
5090             curve_height = svg_height * loop_length_calculation.calc.height;
5091             curve_width = svg_width * loop_length_calculation.calc.width;
5092         } else if (loop_drawing_shape.type === "mushroom") {
5093             curve_height = svg_height * loop_length_calculation.calc.height;
5094             curve_width = svg_width * loop_length_calculation.calc.width;
5095         }
5096         for (let isld = 0; isld < short_loop_data.length; isld++) {
5097             let p1 = short_loop_data[isld].anchorage.p1;
5098             let p2 = short_loop_data[isld].anchorage.p2;
5099             let y_direction = 1;
5100             if (short_loop_data[isld].note === "Cytoplasmic") {
5101                 y_direction = -1;
5102             }
5103             let points;
5104             if (loop_drawing_shape.type === "simple") {
5105                 let x_skew_perc = 0.0;
5106                 if (loop_drawing_shape.calc.hasOwnProperty("x_skew_perc")) {
5107                     x_skew_perc = loop_drawing_shape.calc.x_skew_perc;
5108                 }
5109                 let add_wavefold_obj;
5110                 if (loop_drawing_shape.calc.hasOwnProperty("add_wave_fold")) {
5111                     add_wavefold_obj = checkWaveFold(short_loop_data[isld], loop_drawing_shape.calc.add_wave_fold);
5112                 }
5113                 points = generateSimpleLoopPoints(p1, p2, curve_height, y_direction, x_skew_perc, add_wavefold_obj);
5114             } else if (loop_drawing_shape.type === "swirl") {
5115                 let swirl_x = loop_drawing_shape.calc.swirl_x;
5116                 let perc_step_y = loop_drawing_shape.calc.perc_step_y;
5117                 points = generateSwirlLoopPoints(p1, p2, swirl_x, curve_height, perc_step_y, y_direction);
5118             } else if (loop_drawing_shape.type === "bulb") {
5119                 let perc_step_y = loop_drawing_shape.calc.perc_step_y;
5120                 let add_wavefold_obj;
5121                 if (loop_drawing_shape.calc.hasOwnProperty("add_wave_fold")) {
5122                     add_wavefold_obj = checkWaveFold(short_loop_data[isld], loop_drawing_shape.calc.add_wave_fold);
5123                 }
5124                 let x_skew_perc = 0.0;
5125                 if (loop_drawing_shape.calc.hasOwnProperty("x_skew_perc")) {
5126                     x_skew_perc = loop_drawing_shape.calc.x_skew_perc;
5127                 }
5128                 points = generateBulbLoopPoints(p1, p2, curve_width, curve_height, perc_step_y, y_direction,x_skew_perc, add_wavefold_obj);
5129             } else if (loop_drawing_shape.type === "mushroom") {
5130                 let perc_step_y1 = loop_drawing_shape.calc.perc_step_y1;
5131                 let perc_step_y2 = loop_drawing_shape.calc.perc_step_y2;
5132                 let perc_center_x = loop_drawing_shape.calc.perc_center_x;
5133                 let add_wavefold_obj;
5134                 if (loop_drawing_shape.calc.hasOwnProperty("add_wave_fold")) {
5135                     add_wavefold_obj = checkWaveFold(short_loop_data[isld], loop_drawing_shape.calc.add_wave_fold,x_skew_perc, add_wavefold_obj);
5136                 }
5137                 let x_skew_perc = 0.0;
5138                 if (loop_drawing_shape.calc.hasOwnProperty("x_skew_perc")) {
5139                     x_skew_perc = loop_drawing_shape.calc.x_skew_perc;
5140                 }
5141                 points = generateMushRoomLoopPoints(p1,p2, curve_width, curve_height, perc_center_x, perc_step_y1, perc_step_y2, y_direction,x_skew_perc, add_wavefold_obj);
5142             }
5143             short_loop_data[isld].points = points;
5144         }
5145         return short_loop_data;
5146     }
5147 
5148     /**
5149      * Generate loop points for short and long loops of scaled y growth "step" sizes according to their aanumber
5150      * @namespace
5151      * @exports NaView
5152      * @name calculateScaledPointData 
5153      * @param {Array} short_loop_data array of short or long loop data generated by processRawUniProt
5154      * @param {Object} loop_length_calculation Object part of style_obj describing how loop length should be calculated
5155      * @param {Object} loop_drawing_shape Object part of style_obj describing which loop shape should be drawn
5156      * @yields {Array} array of short or long loop data generated by processRawUniProt with points describing loop added
5157      */
5158     function calculateScaledPointData(short_loop_data, loop_length_calculation, loop_drawing_shape) {
5159         let aa_num_sld_array = short_loop_data.map(function(a, i){
5160             return [a.aanum, i];
5161         });
5162         let sorted_aa_num_sld_array = aa_num_sld_array.sort(function(a,b){
5163             return b[0] - a[0];
5164         });
5165         //get: maximum residue number
5166         let max_aanum = sorted_aa_num_sld_array[0][0];
5167         let max_aanum_index = sorted_aa_num_sld_array[0][1];
5168 
5169         //get: neeeded variables for step scales to be generated
5170         let scale_vars = {};
5171         if (loop_drawing_shape.type === "simple") {
5172             scale_vars.y_step = {};
5173         } else if (loop_drawing_shape.type === "swirl") {
5174             scale_vars.y_step = {};
5175         } else if (loop_drawing_shape.type === "bulb") {
5176             scale_vars.y_step = {};
5177             scale_vars.x_step = {};
5178         } else if (loop_drawing_shape.type === "mushroom") {
5179             scale_vars.y_step = {};
5180             scale_vars.x_step = {};
5181         }
5182 
5183         //get: maximum possible height and width for a curve
5184         //get: corresponding maximum step for generating these dimensions
5185         let curve_maxheight;
5186         let curve_maxwidth;
5187         if (loop_drawing_shape.type === "simple") {
5188             curve_maxheight = svg_height * loop_length_calculation.calc.height;
5189             scale_vars.y_step.max_step = curve_maxheight / loop_drawing_shape.calc.y_step;
5190         } else if (loop_drawing_shape.type === "swirl") {
5191                 curve_maxheight = svg_height * loop_length_calculation.calc.height;
5192                 scale_vars.y_step.max_step = curve_maxheight / loop_drawing_shape.calc.y_step;
5193         } else if (loop_drawing_shape.type === "bulb") {
5194             curve_maxheight = svg_height * loop_length_calculation.calc.height;
5195             curve_maxwidth = svg_width * loop_length_calculation.calc.width;
5196             scale_vars.y_step.max_step = curve_maxheight / loop_drawing_shape.calc.y_step;
5197             scale_vars.x_step.max_step = curve_maxwidth / loop_drawing_shape.calc.x_step;
5198         } else if (loop_drawing_shape.type === "mushroom") {
5199             curve_maxheight = svg_height * loop_length_calculation.calc.height;
5200             curve_maxwidth = svg_width * loop_length_calculation.calc.width;
5201             scale_vars.y_step.max_step = curve_maxheight / loop_drawing_shape.calc.y_step;
5202             scale_vars.x_step.max_step = curve_maxwidth / loop_drawing_shape.calc.x_step;
5203         }
5204         //create: step scaling for each loop, based on their aanumber
5205         for (const scale_var in scale_vars) {
5206             if (scale_vars.hasOwnProperty(scale_var)) {
5207                 scale_vars[scale_var]["scale"] = loop_length_calculation.calc.scale;
5208                 scale_vars[scale_var]["domain"] = [1,max_aanum];
5209                 scale_vars[scale_var]["range"] = [1, scale_vars[scale_var]["max_step"]];
5210             }
5211         }
5212 
5213         //for each loop: generate path with given scaling
5214         for (let isld = 0; isld < short_loop_data.length; isld++) {
5215             let p1 = short_loop_data[isld].anchorage.p1;
5216             let p2 = short_loop_data[isld].anchorage.p2;
5217             let y_direction = 1;
5218             if (short_loop_data[isld].note === "Cytoplasmic") {
5219                 y_direction = -1;
5220             }
5221             let points;
5222             if (loop_drawing_shape.type === "simple") {
5223                 let loop_scaling = makeLoopScaling(scale_vars.y_step, loop_length_calculation);
5224                 let step_number = loop_scaling(short_loop_data[isld].aanum);
5225                 let curve_height = (step_number * loop_drawing_shape.calc.y_step);
5226                 let x_skew_perc = 0.0;
5227                 if (loop_drawing_shape.calc.hasOwnProperty("x_skew_perc")) {
5228                     x_skew_perc = loop_drawing_shape.calc.x_skew_perc;
5229                 }
5230                 let add_wavefold_obj;
5231                 if (loop_drawing_shape.calc.hasOwnProperty("add_wave_fold")) {
5232                     add_wavefold_obj = checkWaveFold(short_loop_data[isld], loop_drawing_shape.calc.add_wave_fold);
5233                 }
5234                 points = generateSimpleLoopPoints(p1, p2, curve_height, y_direction, x_skew_perc, add_wavefold_obj);
5235             } else if (loop_drawing_shape.type === "swirl") {
5236                 let loop_scaling = makeLoopScaling(scale_vars.y_step, loop_length_calculation);
5237                 let step_number = loop_scaling(short_loop_data[isld].aanum);
5238                 let curve_height = (step_number * loop_drawing_shape.calc.y_step);
5239                 let swirl_x = loop_drawing_shape.calc.swirl_x;
5240                 let perc_step_y = loop_drawing_shape.calc.perc_step_y;
5241                 points = generateSwirlLoopPoints(p1, p2, swirl_x, curve_height, perc_step_y, y_direction);
5242             } else if (loop_drawing_shape.type === "bulb") {
5243                 let loop_scaling_y = makeLoopScaling(scale_vars.y_step, loop_length_calculation);
5244                 let step_number_y = loop_scaling_y(short_loop_data[isld].aanum);
5245                 let curve_height = (step_number_y * loop_drawing_shape.calc.y_step);
5246 
5247                 let loop_scaling_x = makeLoopScaling(scale_vars.x_step, loop_length_calculation);
5248                 let step_number_x = loop_scaling_x(short_loop_data[isld].aanum);
5249                 let curve_width = (step_number_x * loop_drawing_shape.calc.x_step);// + short_loop_data[isld].anchorage.dist;
5250 
5251                 let perc_step_y = loop_drawing_shape.calc.perc_step_y;
5252 
5253                 let add_wavefold_obj;
5254                 if (loop_drawing_shape.calc.hasOwnProperty("add_wave_fold")) {
5255                     add_wavefold_obj = checkWaveFold(short_loop_data[isld], loop_drawing_shape.calc.add_wave_fold);
5256                 }
5257                 let x_skew_perc = 0.0;
5258                 if (loop_drawing_shape.calc.hasOwnProperty("x_skew_perc")) {
5259                     x_skew_perc = loop_drawing_shape.calc.x_skew_perc;
5260                 }
5261                 points = generateBulbLoopPoints(p1, p2, curve_width, curve_height, perc_step_y, y_direction,x_skew_perc, add_wavefold_obj);
5262             } else if (loop_drawing_shape.type === "mushroom") {
5263                 let loop_scaling_y = makeLoopScaling(scale_vars.y_step, loop_length_calculation);
5264                 let step_number_y = loop_scaling_y(short_loop_data[isld].aanum);
5265                 let curve_height = (step_number_y * loop_drawing_shape.calc.y_step);
5266 
5267                 let loop_scaling_x = makeLoopScaling(scale_vars.x_step, loop_length_calculation);
5268                 let step_number_x = loop_scaling_x(short_loop_data[isld].aanum);
5269                 let curve_width = (step_number_x * loop_drawing_shape.calc.x_step);// + short_loop_data[isld].anchorage.dist;
5270 
5271                 let perc_step_y1 = loop_drawing_shape.calc.perc_step_y1;
5272                 let perc_step_y2 = loop_drawing_shape.calc.perc_step_y2;
5273                 let perc_center_x = loop_drawing_shape.calc.perc_center_x;
5274 
5275                 let add_wavefold_obj;
5276                 if (loop_drawing_shape.calc.hasOwnProperty("add_wave_fold")) {
5277                     add_wavefold_obj = checkWaveFold(short_loop_data[isld], loop_drawing_shape.calc.add_wave_fold);
5278                 }
5279                 let x_skew_perc = 0.0;
5280                 if (loop_drawing_shape.calc.hasOwnProperty("x_skew_perc")) {
5281                     x_skew_perc = loop_drawing_shape.calc.x_skew_perc;
5282                 }
5283                 points = generateMushRoomLoopPoints(p1,p2, curve_width, curve_height, perc_center_x, perc_step_y1, perc_step_y2, y_direction,x_skew_perc, add_wavefold_obj);
5284             }
5285             short_loop_data[isld].points = points;
5286         }
5287         return short_loop_data;
5288     }
5289 
5290     /**
5291      * Iterates over multiple step sizes until loop of an expected length is generated for a given curve.
5292      * @namespace
5293      * @exports NaView
5294      * @name generatePathWithLength 
5295      * @param {Object} p1 starting point of curve
5296      * @param {Object} p2 end point of curve
5297      * @param {number} loop_length expected length of curve
5298      * @param {number} y_direction 1.0 or -1.0 indicating orientation of curve
5299      * @param {Object} loop_drawing_shape Object part of style_obj describing which loop shape should be drawn
5300      * @param {String} loop_drawing_shape_type String describing curve type according to style_obj
5301      * @param {Object} extra_variables dictionary containing extra style_obj parameters not included in shape
5302      * @param {Object} loop_data original loop data generated by processRawUniProt
5303      * @yields {Array} array of points describing curve of loop
5304      */
5305     function generatePathWithLength(p1,p2,loop_length,y_direction,loop_drawing_shape,loop_drawing_shape_type,extra_variables,loop_data) {
5306         let step_number = 1;
5307         let current_length = 0;
5308         let current_points;
5309         let x_skew_perc;
5310         if (extra_variables.hasOwnProperty("x_skew_perc")) {
5311             x_skew_perc = deepCopy(extra_variables["x_skew_perc"]);
5312         }
5313         while (loop_length > current_length) {
5314             if (loop_drawing_shape_type === "simple") {
5315                 let curve_height = (step_number * extra_variables.y_step);
5316                 let x_skew_perc = 0.0;
5317                 if (loop_drawing_shape.calc.hasOwnProperty("x_skew_perc")) {
5318                     x_skew_perc = loop_drawing_shape.calc.x_skew_perc;
5319                 }
5320                 let add_wavefold_obj;
5321                 if (loop_drawing_shape.calc.hasOwnProperty("add_wave_fold")) {
5322                     add_wavefold_obj = checkWaveFold(loop_data, loop_drawing_shape.calc.add_wave_fold);
5323                 }
5324                 current_points = generateSimpleLoopPoints(p1, p2, curve_height, y_direction, x_skew_perc, add_wavefold_obj);
5325             } else if (loop_drawing_shape_type === "swirl") {
5326                 let curve_height = (step_number * extra_variables.y_step);
5327                 let swirl_x = extra_variables.swirl_x;
5328                 let perc_step_y = extra_variables.perc_step_y;
5329                 current_points = generateSwirlLoopPoints(p1, p2, swirl_x, curve_height, perc_step_y, y_direction);
5330             } else if (loop_drawing_shape_type === "simple_skewed") {
5331                 let curve_height = (step_number * extra_variables.y_step); 
5332                 let perc_center_x = extra_variables.perc_center_x;
5333                 current_points = generateSkewedSimpleLoopPoints(p1, p2, perc_center_x, curve_height, y_direction);
5334             } else if (loop_drawing_shape_type === "bulb") {
5335                 let curve_height = (step_number * extra_variables.y_step); 
5336                 let curve_width = (step_number * extra_variables.x_step);
5337                 let perc_step_y = extra_variables.perc_step_y;
5338                 let add_wavefold_obj;
5339                 if (loop_drawing_shape.calc.hasOwnProperty("add_wave_fold")) {
5340                     add_wavefold_obj = checkWaveFold(loop_data, loop_drawing_shape.calc.add_wave_fold);
5341                 }
5342                 current_points = generateBulbLoopPoints(p1, p2, curve_width, curve_height, perc_step_y, y_direction,x_skew_perc, add_wavefold_obj);
5343             } else if (loop_drawing_shape_type === "mushroom") {
5344                 let curve_height = (step_number * extra_variables.y_step); 
5345                 let curve_width = (step_number * extra_variables.x_step);
5346                 let perc_center_x = extra_variables.perc_center_x;
5347                 let perc_step_y1 = extra_variables.perc_step_y1;
5348                 let perc_step_y2 = extra_variables.perc_step_y2;
5349                 let add_wavefold_obj;
5350                 if (loop_drawing_shape.calc.hasOwnProperty("add_wave_fold")) {
5351                     add_wavefold_obj = checkWaveFold(loop_data, loop_drawing_shape.calc.add_wave_fold);
5352                 }
5353                 current_points = generateMushRoomLoopPoints(p1,p2, curve_width, curve_height, perc_center_x, perc_step_y1, perc_step_y2, y_direction,x_skew_perc, add_wavefold_obj);
5354             } else if (loop_drawing_shape_type === "n_curves") {
5355                 let curve_height = (step_number * extra_variables.y_step);
5356                 let n_centers = loop_drawing_shape.calc.n_centers;
5357                 let perc_centers_height = loop_drawing_shape.calc.perc_centers_height;
5358                 let loop_rotation = loop_drawing_shape.calc.loop_rotation;
5359                 current_points = generateNWaveCurves(p1, p2, n_centers, curve_height, perc_centers_height, y_direction, loop_rotation);
5360             }
5361             let hasNaN = checkForNaN(current_points);
5362             if (hasNaN || current_points.length === 0) {
5363                 return;
5364             }
5365             current_length = getGhostPathLength(current_points);
5366             step_number += 1;
5367         }
5368         // gname += 1;
5369         return current_points;
5370     }
5371 
5372     /**
5373      * Generate loop points for short and long loops of length according to the expected length of each aminoacid in style_obj.
5374      * @namespace
5375      * @exports NaView
5376      * @name calculateResLengthPointData 
5377      * @param {Array} short_loop_data array of short or long loop data generated by processRawUniProt
5378      * @param {Object} loop_length_calculation Object part of style_obj describing how loop length should be calculated
5379      * @param {Object} loop_drawing_shape Object part of style_obj describing which loop shape should be drawn
5380      * @yields {Array} array of short or long loop data generated by processRawUniProt with points describing loop added
5381      */
5382     function calculateResLengthPointData(short_loop_data, loop_length_calculation, loop_drawing_shape) {
5383         //if smaller than, refactor length
5384         let aa_dist_sld_aanum_array = short_loop_data.map(function(a, i){
5385             return [a.anchorage.dist, i, a.aanum];
5386         });
5387         let sorted_aa_dist_sld_array = aa_dist_sld_aanum_array.sort(function(a,b){
5388             return a[0] - b[0];
5389         });
5390         //get: minimum anchorage euclidean distance
5391         let min_dist = sorted_aa_dist_sld_array[0][0];
5392         let min_dist_index = sorted_aa_dist_sld_array[0][1];
5393         let min_dist_aanum = sorted_aa_dist_sld_array[0][2];
5394 
5395         let minimum_possible_reslen = min_dist/min_dist_aanum;
5396         let current_reslen = loop_length_calculation.calc.length;
5397         if (current_reslen < minimum_possible_reslen) {
5398             console.log("WARNING: Reslength is smaller than what is possible. Refactoring...")
5399             current_reslen = minimum_possible_reslen;
5400             loop_length_calculation.calc.length = minimum_possible_reslen;
5401         }
5402 
5403         let extra_variables = {};
5404         for (const k in loop_drawing_shape["calc"]) {
5405             if (loop_drawing_shape["calc"].hasOwnProperty(k)) {
5406                 extra_variables[k] = loop_drawing_shape["calc"][k];
5407             }
5408         }
5409         for (let isld = 0; isld < short_loop_data.length; isld++) {
5410             let p1 = short_loop_data[isld].anchorage.p1;
5411             let p2 = short_loop_data[isld].anchorage.p2;
5412             let y_direction = 1;
5413             if (short_loop_data[isld].note === "Cytoplasmic") {
5414                 y_direction = -1;
5415             }
5416             if (loop_drawing_shape.type === "n_curves") {
5417                 y_direction = true;
5418             }
5419             let loop_length = short_loop_data[isld].aanum * current_reslen;
5420             let points = generatePathWithLength(p1,p2,loop_length,y_direction,loop_drawing_shape,loop_drawing_shape.type,extra_variables,short_loop_data[isld]);
5421             short_loop_data[isld].points = points;
5422         }
5423         return short_loop_data;
5424     }
5425 
5426     /**
5427      * Generate loop points for short and long loops of custom height and width according to style_obj
5428      * @namespace
5429      * @exports NaView
5430      * @name calculateCustomPointData 
5431      * @param {Array} short_loop_data array of short or long loop data generated by processRawUniProt
5432      * @param {Object} loop_length_calculation Object part of style_obj describing how loop length should be calculated
5433      * @param {Object} loop_drawing_shape Object part of style_obj describing which loop shape should be drawn
5434      * @yields {Array} array of short or long loop data generated by processRawUniProt with points describing loop added
5435      */
5436     function calculateCustomPointData(short_loop_data, loop_length_calculation, loop_drawing_shape) {
5437         for (let isld = 0; isld < short_loop_data.length; isld++) {
5438             let p1 = short_loop_data[isld].anchorage.p1;
5439             let p2 = short_loop_data[isld].anchorage.p2;
5440             let y_direction = 1;
5441             if (short_loop_data[isld].note === "Cytoplasmic") {
5442                 y_direction = -1;
5443             }
5444             let curve_width;
5445             let curve_height;
5446             if (short_loop_data[isld].hasOwnProperty("dom_name")) {
5447                 let dom_name = short_loop_data[isld].dom_name;
5448                 let dom_itype = short_loop_data[isld].dom_itype;
5449                 curve_width = loop_length_calculation["calc"]["width"][dom_name][dom_itype] *  svg_width;
5450                 curve_height = loop_length_calculation["calc"]["height"][dom_name][dom_itype] * svg_height;
5451             } else {
5452                 let dom_iname = short_loop_data[isld].dom_iname;
5453                 curve_width = loop_length_calculation["calc"]["width"][dom_iname] *  svg_width;
5454                 curve_height = loop_length_calculation["calc"]["height"][dom_iname] * svg_height;
5455             }
5456             let points;
5457             if (loop_drawing_shape.type === "simple") {
5458                 let x_skew_perc = 0.0;
5459                 if (loop_drawing_shape.calc.hasOwnProperty("x_skew_perc")) {
5460                     x_skew_perc = loop_drawing_shape.calc.x_skew_perc;
5461                 }
5462                 let add_wavefold_obj;
5463                 if (loop_drawing_shape.calc.hasOwnProperty("add_wave_fold")) {
5464                     add_wavefold_obj = checkWaveFold(short_loop_data[isld], loop_drawing_shape.calc.add_wave_fold);
5465                 }
5466                 points = generateSimpleLoopPoints(p1, p2, curve_height, y_direction, x_skew_perc, add_wavefold_obj);
5467             } else if (loop_drawing_shape.type === "swirl") {
5468                 let swirl_x = loop_drawing_shape.calc.swirl_x;
5469                 let perc_step_y = loop_drawing_shape.calc.perc_step_y;
5470                 points = generateSwirlLoopPoints(p1, p2, swirl_x, curve_height, perc_step_y, y_direction);
5471             } else if (loop_drawing_shape.type === "bulb") {
5472                 let perc_step_y = loop_drawing_shape.calc.perc_step_y;
5473                 let add_wavefold_obj;
5474                 if (loop_drawing_shape.calc.hasOwnProperty("add_wave_fold")) {
5475                     add_wavefold_obj = checkWaveFold(short_loop_data[isld], loop_drawing_shape.calc.add_wave_fold);
5476                 }
5477                 let x_skew_perc = 0.0;
5478                 if (loop_drawing_shape.calc.hasOwnProperty("x_skew_perc")) {
5479                     x_skew_perc = loop_drawing_shape.calc.x_skew_perc;
5480                 }
5481                 points = generateBulbLoopPoints(p1, p2, curve_width, curve_height, perc_step_y, y_direction, x_skew_perc, add_wavefold_obj);
5482             } else if (loop_drawing_shape.type === "mushroom") {
5483                 let perc_step_y1 = loop_drawing_shape.calc.perc_step_y1;
5484                 let perc_step_y2 = loop_drawing_shape.calc.perc_step_y2;
5485                 let perc_center_x = loop_drawing_shape.calc.perc_center_x;
5486                 let add_wavefold_obj;
5487                 if (loop_drawing_shape.calc.hasOwnProperty("add_wave_fold")) {
5488                     add_wavefold_obj = checkWaveFold(short_loop_data[isld], loop_drawing_shape.calc.add_wave_fold);
5489                 }
5490                 let x_skew_perc = 0.0;
5491                 if (loop_drawing_shape.calc.hasOwnProperty("x_skew_perc")) {
5492                     x_skew_perc = loop_drawing_shape.calc.x_skew_perc;
5493                 }
5494                 points = generateMushRoomLoopPoints(p1,p2, curve_width, curve_height, perc_center_x, perc_step_y1, perc_step_y2, y_direction, x_skew_perc, add_wavefold_obj);
5495             }
5496             short_loop_data[isld].points = points;
5497         }
5498         return short_loop_data;
5499     }
5500 
5501     /**
5502      * After loop parent elements are drawn and partitioned to individual aminoacid, appends centroid data for each aminoacid in loops.
5503      * @namespace
5504      * @exports NaView
5505      * @name gen_centroids_loop_paths_resids 
5506      * @param {String} given_class class which loop group elements were named
5507      */
5508     function gen_centroids_loop_paths_resids(given_class) {
5509         let created_paths = document.getElementsByClassName(given_class);
5510         for (let icp = 0; icp < created_paths.length; icp++) {
5511             const element = created_paths[icp];
5512             // let path_box = element.getBoundingClientRect();
5513             let path_box = element.getBBox();
5514             // let xCenter = (path_box.left + path_box.right) / 2;
5515             // let yCenter = (path_box.top + path_box.bottom) / 2;
5516             let xCenter = path_box.x + (path_box.width / 2) ;
5517             let yCenter = path_box.y + (path_box.height / 2) ;
5518             let new_datum = d3.select(element).datum();
5519             new_datum["centroid"] = [xCenter, yCenter];
5520             d3.select(element).datum(new_datum);
5521             // if (new_datum.res_ind === "NaN") {
5522             //     d3.select(element).remove();
5523             // }
5524         }
5525     }
5526 
5527     /**
5528      * Function to create loop path elements to each individual amino acid in loop by partitioning the parent loop element
5529      * @namespace
5530      * @exports NaView
5531      * @name draw_loop_paths_resids 
5532      * @param {D3 Selection} enter_element parent loop element d3.js selection
5533      * @param {String} class_naming class to name loop group elements
5534      */
5535     function draw_loop_paths_resids(enter_element, class_naming) {
5536         let curve = d3.line().curve(d3.curveNatural);
5537         let data = enter_element.datum();
5538 
5539         let identifier_1 = "dom_name";
5540         let identifier_2 = "dom_itype";
5541         if (data.hasOwnProperty(identifier_1) === false || !data[identifier_1]) {
5542             identifier_1 = "id";
5543             identifier_2 = "id";
5544         }
5545 
5546         let data_resids = data.resids;
5547         let g_short_loop_resids = enter_element.append("g")
5548         .attr("id", function(d){
5549             // return "g_"+class_naming+"_resids_" + d.dom_name + "_" + d.dom_itype;
5550             return "g_"+class_naming+"_resids_" + d[identifier_1] + "_" + d[identifier_2];
5551         })
5552         .attr("class", "g_"+class_naming+"_resids residue_path")
5553         // .attr("class", "g_"+class_naming+"_resids")
5554         // .style("display", "none")
5555         .style("visibility", "hidden")
5556         ;
5557         
5558         g_short_loop_resids.selectAll(+function(d) {
5559             // return "."+class_naming+"_resid_" + d.dom_name + "_" + d.dom_itype;
5560             return "."+class_naming+"_resid_" + d[identifier_1] + "_" + d[identifier_2];
5561         })
5562         .data(function(d) {
5563             let aa_array = d.aas.split('');
5564             let height_per_aa = d.draw_area.height / aa_array.length;
5565             let d_data = [];
5566             
5567             // let p_element = d3.select("#g_"+class_naming+"_" + d.dom_name + "_" + d.dom_itype);
5568             // let p_element = d3.select("#g_"+class_naming+"_" + d[identifier_1] + "_" + d[identifier_2]);
5569             // let p_length = p_element.node().getTotalLength();
5570             // let p_length_per_resid = p_length / data_resids.length;
5571             // let p_length_per_resid = document.getElementById("g_"+class_naming+"_" + d.dom_name + "_" + d.dom_itype).getTotalLength() / data_resids.length;
5572             // let p_length_per_resid = document.getElementById("g_"+class_naming+"_" + d.dom_name + "_" + d.dom_itype).getTotalLength() / d.resids.length;
5573             let path_resids_id_name = "g_"+class_naming+"_" + d[identifier_1] + "_" + d[identifier_2];
5574             let p_length_per_resid = document.getElementById(path_resids_id_name).getTotalLength() / d.resids.length;
5575             let segmentstart = 0;
5576             for (let iaaa = 0; iaaa < aa_array.length; iaaa++) {
5577                 let aa = aa_array[iaaa];
5578                 // segmentstart = segmentend + 0;
5579                 segmentstart = p_length_per_resid*iaaa;
5580                 let segmentend = segmentstart + p_length_per_resid;
5581                 // let path_piece_pts = pathSegmentToXYPoints(document.getElementById("g_"+class_naming+"_" + d.dom_name + "_" + d.dom_itype), segmentstart, segmentend, 1);
5582                 let path_piece_pts = pathSegmentToXYPoints(document.getElementById("g_"+class_naming+"_" + d[identifier_1] + "_" + d[identifier_2]), segmentstart, segmentend, 1);
5583                 d_data.push({
5584                     "id": d.id,
5585                     "type":d.type,
5586                     "dom_name":d.dom_name,
5587                     "dom_itype":d.dom_itype,
5588                     "path_piece_pts":path_piece_pts,
5589                     "fill":data.fill,
5590                     "stroke":data.stroke,
5591                     "stroke_width":data.stroke_size,
5592                     "opacity":data.opacity,
5593                     "res_1":d.resids[iaaa].res_1,
5594                     "res_3":d.resids[iaaa].res_3,
5595                     "res_ind":d.resids[iaaa].res_ind,
5596                 })
5597             }
5598             return d_data;
5599         })
5600         .join(
5601             function(enter) {
5602                 let e2 = enter.append("path")
5603                 // .attr("class", function(d) { return class_naming+"_resid_" + d.dom_name + "_" + d.dom_itype + " short_loops_residues"; })
5604                 .attr("class", function(d) { return class_naming+"_resid_" + d[identifier_1] + "_" + d[identifier_2] + " "+class_naming+"_residues single_residue_path"; })
5605                 .attr("resname", function(d){ return d.res_3+d.res_ind; })
5606                 .attr("d", function(d) {
5607                     return curve(d.path_piece_pts);
5608                 })
5609                 .attr("stroke-width", function(d) { return d.stroke_width;})
5610                 .attr("fill", function(d) {
5611                     // let f_color = checkFillResidue(d)[0];
5612                     // if (f_color) {
5613                     //     return f_color;
5614                     // }
5615                     return d["fill"];
5616                 } )
5617                 .attr("opacity", function(d) {
5618                     let o_color = checkFillResidue(d)[1];
5619                     if (o_color) {
5620                         return o_color;;
5621                     }
5622                     return d["opacity"];
5623                 })
5624                 .attr("stroke", function(d) {
5625                     let f_color = checkFillResidue(d)[0];
5626                     if (f_color) {
5627                         return f_color;
5628                     }
5629                     return d.stroke;
5630                 })
5631                 // .style("vector-effect", "non-scaling-stroke")
5632                 .on("mouseover", function(d) {
5633                     console.log("");
5634                     console.log("#######");
5635                     console.log("MOUSE OVER PATH");
5636                     console.log("data:");
5637                     console.log(d);
5638                     console.log("#######");
5639                     console.log("");
5640                 })
5641                 ;
5642                 gen_centroids_loop_paths_resids(class_naming+"_residues");
5643                 return e2;
5644             },
5645             function(update) {
5646                 return update
5647                 .transition()
5648                 .attr("fill", function(d) {
5649                     // let f_color = checkFillResidue(d)[0];
5650                     // if (f_color) {
5651                     //     return f_color;
5652                     // }
5653                     return d["fill"];
5654                 } )
5655                 .attr("opacity", function(d) {
5656                     let o_color = checkFillResidue(d)[1];
5657                     if (o_color) {
5658                         return o_color;
5659                     }
5660                     return d["opacity"];
5661                 })
5662                 .attr("stroke", function(d) {
5663                     let f_color = checkFillResidue(d)[0];
5664                     if (f_color) {
5665                         return f_color;
5666                     }
5667                     return d.stroke;
5668                 });
5669             },
5670         );
5671     }
5672 
5673     /**
5674      * Function responsible for drawing loop curves according to d3.curveNatural and points of each curve
5675      * @namespace
5676      * @exports NaView
5677      * @name draw_loop_paths 
5678      * @param {Array} short_loop_data  Array of loop data to draw
5679      * @param {String} class_naming class to name loop group elements
5680      */
5681     function draw_loop_paths(short_loop_data, class_naming) {
5682         let curve = d3.line().curve(d3.curveNatural);
5683         let svg_element = d3.select("#"+svg_id)
5684         .append("g")
5685         .attr("class", class_naming+"_group");
5686 
5687         svg_element.selectAll("."+class_naming)
5688         .data(short_loop_data)
5689         .join(
5690             function(enter) {
5691                 let e = enter.append("g")
5692                 .attr("class", class_naming + " element_path_group");
5693 
5694                 let p = e.append("path")
5695                 .attr("id", function(d){
5696                     let identifier_1 = "dom_name";
5697                     let identifier_2 = "dom_itype";
5698                     if (d.hasOwnProperty("dom_name") === false || !d["dom_name"]) {
5699                         identifier_1 = "id";
5700                         identifier_2 = "id";
5701                     }
5702                     // return "g_"+class_naming+"_" + d.dom_name + "_" + d.dom_itype;
5703                     return "g_"+class_naming+"_" + d[identifier_1] + "_" + d[identifier_2];
5704                 })
5705                 .attr("class", "element_path")
5706                 .attr("d", function(d) {
5707                     return curve(d.points);
5708                 })
5709                 .attr('stroke', function(d) {
5710                     return d["stroke"];
5711                 })
5712                 .attr("fill", function(d) {
5713                     return d["fill"];
5714                 } )
5715                 .attr("opacity", function(d) {
5716                     return d["opacity"];
5717                 })
5718                 .attr('stroke-width', function(d) {
5719                     return d["stroke_size"];
5720                 })
5721                 // .style("vector-effect", "non-scaling-stroke")
5722                 ;
5723                 draw_loop_paths_resids(e, class_naming);
5724                 return e;
5725             },
5726             function(update) {
5727                 return update;
5728             },
5729             function(exit) {
5730                 return exit.remove();
5731             },
5732         )
5733         //here d3 is used for append data to each path of loop to be generated with curveNatural
5734 
5735         // for each loop:
5736         // call point generation function according to parameters
5737         // retrieve points
5738         // draw path according to configs
5739         // split path into sub paths of residues
5740     }
5741 
5742     /**
5743      * Function responsbile for controlling addition of points to termini data and drawing of termini curves
5744      * @namespace
5745      * @exports NaView
5746      * @name draw_shortLoops 
5747      * @param {Array} short_loop_data array of short or long loop data generated by processRawUniProt
5748      */
5749     function draw_shortLoops(short_loop_data) {
5750         let loop_length_calculation = style_obj.protein.short_loops_draw_opts.calc_len;
5751         let loop_drawing_shape = style_obj.protein.short_loops_draw_opts.shape;
5752         if (loop_length_calculation.type === "fixed") {
5753             short_loop_data = calculateFixedPointData(short_loop_data, loop_length_calculation, loop_drawing_shape);
5754         } else if (loop_length_calculation.type === "scaled") {
5755             short_loop_data = calculateScaledPointData(short_loop_data, loop_length_calculation, loop_drawing_shape);
5756         } else if (loop_length_calculation.type === "reslen") {
5757             short_loop_data = calculateResLengthPointData(short_loop_data, loop_length_calculation, loop_drawing_shape);
5758         } else if (loop_length_calculation.type === "custom") {
5759             short_loop_data = calculateCustomPointData(short_loop_data, loop_length_calculation, loop_drawing_shape);
5760         }
5761         draw_loop_paths(short_loop_data, "short_loops");
5762     }
5763 
5764     /**
5765      * Generate loop points for pore loops of fixed or custom (per style_obj) height and width
5766      * @namespace
5767      * @exports NaView
5768      * @name calculateFixedCustomPointDataPore 
5769      * @param {Array} pores_data array of pore loop data generated by processRawUniProt
5770      * @param {Object} loop_length_calculation Object part of style_obj describing how loop length should be calculated
5771      * @param {Object} loop_drawing_shape Object part of style_obj describing which loop shape should be drawn
5772      * @yields {Array} array of pore loop data generated by processRawUniProt with points describing loop added
5773      */
5774     function calculateFixedCustomPointDataPore(pores_data, loop_length_calculation, loop_drawing_shape) {
5775         for (let ipd = 0; ipd < pores_data.length; ipd++) {
5776             let dom_name = pores_data[ipd].dom_name;
5777             let dom_itype = pores_data[ipd].dom_itype;
5778             let p1 = pores_data[ipd].anchorage.p1;
5779             let p2 = pores_data[ipd].anchorage.p2;
5780             let y_direction = -1;
5781             
5782             let box_height = pores_data[ipd].draw_area.height;
5783             let curve_height;
5784             if (loop_length_calculation["calc"]["height"].constructor == Object) {
5785                 curve_height = loop_length_calculation["calc"]["height"][dom_name][dom_itype] * box_height;
5786             } else {
5787                 curve_height = loop_length_calculation["calc"]["height"] * box_height;
5788             }
5789             let points;
5790             let perc_center_x = loop_drawing_shape.calc.perc_center_x;
5791             points = generateSkewedSimpleLoopPoints(p1, p2, perc_center_x, curve_height, y_direction);
5792             pores_data[ipd].points = points;
5793         }
5794         return pores_data;
5795     }
5796 
5797     /**
5798      * Generate loop points for pore loops of scaled y growth "step" sizes according to their aanumber
5799      * @namespace
5800      * @exports NaView
5801      * @name calculateScaledPointDataPore 
5802      * @param {Array} pores_data array of pore loop data generated by processRawUniProt
5803      * @param {Object} loop_length_calculation Object part of style_obj describing how loop length should be calculated
5804      * @param {Object} loop_drawing_shape Object part of style_obj describing which loop shape should be drawn
5805      * @yields {Array} array of pore loop data generated by processRawUniProt with points describing loop added
5806      */
5807     function calculateScaledPointDataPore(pores_data, loop_length_calculation, loop_drawing_shape) {
5808         let aa_num_pore_array = pores_data.map(function(a, i){
5809             return [a.aanum, i, a.draw_area.height];
5810         });
5811         let sorted_aa_num_pore_array = aa_num_pore_array.sort(function(a,b){
5812             return b[0] - a[0];
5813         });
5814 
5815         //get: maximum residue number
5816         let max_aanum = sorted_aa_num_pore_array[0][0];
5817         let max_aanum_height = sorted_aa_num_pore_array[0][2];
5818 
5819         let curve_maxheight = max_aanum_height * loop_length_calculation.calc.height; 
5820 
5821         let scale_vars = {};
5822         scale_vars["y_step"] = {};
5823         scale_vars["y_step"]["max_step"] =  curve_maxheight / loop_drawing_shape.calc.y_step;
5824         scale_vars["y_step"]["scale"] = loop_length_calculation.calc.scale;
5825         scale_vars["y_step"]["domain"] = [1,max_aanum];
5826         scale_vars["y_step"]["range"] = [1,  curve_maxheight / loop_drawing_shape.calc.y_step];
5827 
5828         let loop_scaling = makeLoopScaling(scale_vars.y_step, loop_length_calculation);
5829 
5830         for (let ipd = 0; ipd < pores_data.length; ipd++) {
5831             let p1 = pores_data[ipd].anchorage.p1;
5832             let p2 = pores_data[ipd].anchorage.p2;
5833             let y_direction = -1;
5834             let points;
5835             let step_number = loop_scaling(pores_data[ipd].aanum);
5836             let curve_height = (step_number * loop_drawing_shape.calc.y_step);
5837             let perc_center_x = loop_drawing_shape.calc.perc_center_x;
5838             points = generateSkewedSimpleLoopPoints(p1, p2, perc_center_x, curve_height, y_direction);
5839             pores_data[ipd].points = points;
5840         }
5841         return pores_data;
5842     }
5843 
5844     /**
5845      * Generate loop points for pore loops of length according to the expected length of each aminoacid in style_obj.
5846      * @namespace
5847      * @exports NaView
5848      * @name calculateResLengthPointDataPore 
5849      * @param {Array} pores_data array of pore loop data generated by processRawUniProt
5850      * @param {Object} loop_length_calculation Object part of style_obj describing how loop length should be calculated
5851      * @param {Object} loop_drawing_shape Object part of style_obj describing which loop shape should be drawn
5852      * @yields {Array} array of pore loop data generated by processRawUniProt with points describing loop added
5853      */
5854     function calculateResLengthPointDataPore(pores_data, loop_length_calculation, loop_drawing_shape) {
5855         let aa_dist_sld_aanum_array = pores_data.map(function(a, i){
5856             return [a.anchorage.dist, i, a.aanum];
5857         });
5858         let sorted_aa_dist_sld_array = aa_dist_sld_aanum_array.sort(function(a,b){
5859             return a[0] - b[0];
5860         });
5861         //get: minimum anchorage euclidean distance
5862         let min_dist = sorted_aa_dist_sld_array[0][0];
5863         let min_dist_index = sorted_aa_dist_sld_array[0][1];
5864         let min_dist_aanum = sorted_aa_dist_sld_array[0][2];
5865 
5866         let minimum_possible_reslen = min_dist/min_dist_aanum;
5867         let current_reslen = loop_length_calculation.calc.length;
5868         if (current_reslen < minimum_possible_reslen) {
5869             console.log("WARNING: Reslength is smaller than what is possible. Refactoring...")
5870             current_reslen = minimum_possible_reslen;
5871             loop_length_calculation.calc.length = minimum_possible_reslen;
5872         }
5873 
5874         let extra_variables = {};
5875         for (const k in loop_drawing_shape["calc"]) {
5876             if (loop_drawing_shape["calc"].hasOwnProperty(k)) {
5877                 extra_variables[k] = loop_drawing_shape["calc"][k];
5878             }
5879         }
5880         for (let ipd = 0; ipd < pores_data.length; ipd++) {
5881             let p1 = pores_data[ipd].anchorage.p1;
5882             let p2 = pores_data[ipd].anchorage.p2;
5883             let y_direction = -1;
5884             let loop_length = pores_data[ipd].aanum * current_reslen;
5885             let points = generatePathWithLength(p1,p2,loop_length,y_direction,loop_drawing_shape,loop_drawing_shape.type,extra_variables,pores_data[ipd]);
5886             pores_data[ipd].points = points;
5887         }
5888         return pores_data;
5889     }
5890 
5891     /**
5892      * Function responsbile for controlling addition of points to pore data and drawing of pore curves
5893      * @namespace
5894      * @exports NaView
5895      * @name draw_poreLoops 
5896      * @param {Array} pores_data array of pore loop data generated by processRawUniProt
5897      */
5898     function draw_poreLoops(pores_data) {
5899         // pore drawing. pain in the arse. basically can be scaled alongside helix height
5900         // can have a custom breakpoint residue
5901         // and can be scaled according to a custom residue length
5902 
5903         let loop_length_calculation = style_obj.protein.pore_loops_draw_opts.calc_len;
5904         let loop_drawing_shape = style_obj.protein.pore_loops_draw_opts.shape;
5905         if (loop_length_calculation.type === "fixed" || loop_length_calculation.type === "custom") {
5906             pores_data = calculateFixedCustomPointDataPore(pores_data, loop_length_calculation, loop_drawing_shape)
5907         } else if (loop_length_calculation.type === "scaled") {
5908             pores_data = calculateScaledPointDataPore(pores_data, loop_length_calculation, loop_drawing_shape);
5909         } else if (loop_length_calculation.type === "reslen") {
5910             pores_data = calculateResLengthPointDataPore(pores_data, loop_length_calculation, loop_drawing_shape);
5911         }
5912         draw_loop_paths(pores_data, "pore_loops");
5913     }
5914 
5915     /**
5916      * Function responsbile for controlling addition of points to long loops data and drawing of long loops curves
5917      * @namespace
5918      * @exports NaView
5919      * @name draw_longLoops 
5920      * @param {Array} longloop_data  array of long loop data generated by processRawUniProt
5921      */
5922     function draw_longLoops(longloop_data) {
5923         let loop_length_calculation = style_obj.protein.long_loops_draw_opts.calc_len;
5924         let loop_drawing_shape = style_obj.protein.long_loops_draw_opts.shape;
5925         if (loop_length_calculation.type === "fixed") {
5926             longloop_data = calculateFixedPointData(longloop_data, loop_length_calculation, loop_drawing_shape);
5927         } else if (loop_length_calculation.type === "scaled") {
5928             longloop_data = calculateScaledPointData(longloop_data, loop_length_calculation, loop_drawing_shape);
5929         } else if (loop_length_calculation.type === "reslen") {
5930             longloop_data = calculateResLengthPointData(longloop_data, loop_length_calculation, loop_drawing_shape);
5931         } else if (loop_length_calculation.type === "custom") {
5932             longloop_data = calculateCustomPointData(longloop_data, loop_length_calculation, loop_drawing_shape);
5933         }
5934         draw_loop_paths(longloop_data, "long_loops");
5935     }
5936 
5937     /**
5938      * Generate loop points for termini loops of fixed or custom (per style_obj) height and width
5939      * @namespace
5940      * @exports NaView
5941      * @name calculateFixedPointTermini 
5942      * @param {Object} termini_data termini loop data Object generated by processRawUniProt
5943      * @param {Object} loop_length_calculation Object part of style_obj describing how loop length should be calculated
5944      * @param {Object} loop_drawing_shape Object part of style_obj describing which loop shape should be drawn
5945      * @yields {Object} termini loop data Object generated by processRawUniProt with points describing loop added
5946      */
5947     function calculateFixedPointTermini(termini_data, loop_length_calculation, loop_drawing_shape) {
5948         let termini_height;
5949         if (termini_data.terminus_type === "N") {
5950             termini_height = svg_drawarea.end_y - svg_drawarea.membrane_end;
5951         } else {
5952             termini_height = svg_drawarea.end_y - svg_drawarea.membrane_end;
5953         }
5954         let curve_height = termini_height * loop_length_calculation.calc.height;
5955         let p1 = termini_data.anchorage.p1;
5956         let p2 = termini_data.anchorage.p2;
5957         let starting_y_direction = true;
5958         let points;
5959         if (loop_drawing_shape.type === "n_curves") {
5960             let n_centers = loop_drawing_shape.calc.n_centers;
5961             let perc_centers_height = loop_drawing_shape.calc.perc_centers_height;
5962             let loop_rotation = loop_drawing_shape.calc.loop_rotation;
5963             points = generateNWaveCurves(p1, p2, n_centers, curve_height, perc_centers_height, starting_y_direction, loop_rotation);
5964         }
5965         termini_data.points = points;
5966         draw_loop_paths([termini_data], termini_data.terminus_type+"ter_loops");
5967         return termini_data;
5968     }
5969 
5970     /**
5971      * Function responsbile for controlling addition of points to termini data and drawing of termini curves
5972      * @namespace
5973      * @exports NaView
5974      * @name draw_termini 
5975      * @param {Object} termini_data termini loop data Object generated by processRawUniProt
5976      */
5977     function draw_termini(termini_data) {
5978         let loop_length_calculation;// = style_obj.protein.pore_loops_draw_opts.calc_len;
5979         let loop_drawing_shape;// = style_obj.protein.pore_loops_draw_opts.shape;
5980         if (termini_data.terminus_type === "N") {
5981             loop_length_calculation = style_obj.protein.nter_loop_draw_opts.calc_len;
5982             loop_drawing_shape = style_obj.protein.nter_loop_draw_opts.shape;
5983         } else {
5984             loop_length_calculation = style_obj.protein.cter_loop_draw_opts.calc_len;
5985             loop_drawing_shape = style_obj.protein.cter_loop_draw_opts.shape;
5986         }
5987         if (loop_length_calculation.type === "fixed") {
5988             termini_data = calculateFixedPointTermini(termini_data, loop_length_calculation, loop_drawing_shape);
5989         } else if (loop_length_calculation.type === "reslen") {
5990             termini_data = calculateResLengthPointData([termini_data], loop_length_calculation, loop_drawing_shape);
5991             draw_loop_paths(termini_data, termini_data[0].terminus_type+"ter_loops");
5992         }
5993     }
5994     
5995     /**
5996      * Function to parse residue relations description array and style_obj, generating points, width and color scales to each relation object.<br>
5997      * Relations between any residues or drawn elements follows the below syntax:<br>
5998      * {<br>
5999      *     "source":"random_residue_source", //index OR residue_name+index of residue in relationship or valid element name such as: "DomainI;Helix1"<br>
6000      *     "target":"random_residue_target", //index OR residue_name+index of residue in relationship or valid element name such as: "DomainI;Helix1"<br>
6001      *     "raw_weight":possibly_random_residue_weight //number indicating strength of each residue or element relationship<br>
6002      *     "type": "type" //optional type object for describing multiple types of relationships in plor<br>
6003      * }<br>
6004      * @see allowed_element_names
6005      * @see draw_relation_paths
6006      * @see whereIsResIdElName
6007      * @namespace
6008      * @exports NaView
6009      * @name draw_residue_relations 
6010      * @param {Array} residue_relation_data array of residue relation Objects
6011      * @param {Object} centroid_data Dictionary containing centroids of elements and residues
6012      * @param {Object} full_protein_data full data generated by processRawUniProt
6013      */
6014     function draw_residue_relations(residue_relation_data, centroid_data, full_protein_data) {
6015         let draw_opts = style_obj.protein.residue_relations_draw_opts;
6016         let group_weights_by_type;
6017         if (draw_opts.path_width_scaling.type === "calc") {
6018             group_weights_by_type = draw_opts.path_width_scaling.group_by_type;
6019         }
6020     
6021         residue_relation_data = mergeDrawData("residue_relations", deepCopy(residue_relation_data));
6022     
6023         let list_of_types = [];
6024         let total_weights_by_type = {};
6025         if (group_weights_by_type) {
6026             list_of_types = residue_relation_data.map(function(a) {
6027                 total_weights_by_type[a.type] = 0;
6028                 return a.type;
6029             });
6030             list_of_types = deepCopy(list_of_types).filter(onlyUnique);
6031         }
6032     
6033         let total_weights = residue_relation_data.reduce(function(r, a) {
6034             return r + a.raw_weight;
6035         }, 0);
6036         if (group_weights_by_type) {
6037             for (let irrd = 0; irrd < residue_relation_data.length; irrd++) {
6038                 total_weights_by_type[residue_relation_data[irrd]["type"]] += residue_relation_data[irrd]["raw_weight"];
6039             }
6040         }
6041     
6042     
6043         let all_relative_weights = [];
6044         let all_absolute_weights = [];
6045         let all_relative_weights_by_type = {};
6046         let all_absolute_weights_by_type = {};
6047         for (let irrd = 0; irrd < residue_relation_data.length; irrd++) {
6048             residue_relation_data[irrd]["absolute_weight"] = residue_relation_data[irrd]["raw_weight"];
6049             if (group_weights_by_type) {
6050                 residue_relation_data[irrd]["relative_weight"] = residue_relation_data[irrd]["raw_weight"] / total_weights_by_type[residue_relation_data[irrd]["type"]];
6051                 if (all_relative_weights_by_type.hasOwnProperty(residue_relation_data[irrd]["type"]) === false) {
6052                     all_relative_weights_by_type[residue_relation_data[irrd]["type"]] = [];
6053                 }
6054                 all_relative_weights_by_type[residue_relation_data[irrd]["type"]].push(residue_relation_data[irrd]["relative_weight"]);
6055                 if (all_absolute_weights_by_type.hasOwnProperty(residue_relation_data[irrd]["type"]) === false) {
6056                     all_absolute_weights_by_type[residue_relation_data[irrd]["type"]] = [];
6057                 }
6058                 all_absolute_weights_by_type[residue_relation_data[irrd]["type"]].push(residue_relation_data[irrd]["absolute_weight"]);
6059             } else {
6060                 residue_relation_data[irrd]["relative_weight"] = residue_relation_data[irrd]["raw_weight"] / total_weights;        
6061             }
6062             all_absolute_weights.push(residue_relation_data[irrd]["absolute_weight"]);
6063             all_relative_weights.push(residue_relation_data[irrd]["relative_weight"]);
6064         }
6065         // let all_relative_weights = residue_relation_data.map(function(a) {
6066         //     return a.relative_weight;
6067         // });
6068         // let all_absolute_weights = residue_relation_data.map(function(a) {
6069         //     return a.raw_weight;
6070         // });
6071     
6072         let to_use_weights = deepCopy(all_relative_weights);
6073         let to_use_weights_by_type = deepCopy(all_relative_weights_by_type);
6074         if (draw_opts.weight_scaling === "absolute") {
6075             to_use_weights = all_absolute_weights;
6076             to_use_weights_by_type = deepCopy(all_absolute_weights_by_type);
6077         }
6078     
6079         let current_width_scale;
6080         if (draw_opts.path_width_scaling.type === "calc") {
6081             let wdomain = draw_opts.path_width_scaling.domain;
6082             let wrange = draw_opts.path_width_scaling.range;
6083     
6084             if (group_weights_by_type) {
6085                 current_width_scale = function(etc, etctype) {
6086                     let cwdomain = deepCopy(wdomain);
6087                     if (cwdomain[0] === "min") {
6088                         cwdomain[0] = d3.min(to_use_weights_by_type[etctype]);
6089                     }
6090                     if (cwdomain[1] === "max") {
6091                         cwdomain[1] = d3.max(to_use_weights_by_type[etctype]);
6092                     }
6093                     let cwrange = deepCopy(wrange);
6094                     let sc = d3.scaleLinear()
6095                     .domain(cwdomain)
6096                     .range(cwrange);
6097                     return sc(etc);
6098                 }
6099             } else {
6100                 if (wdomain[0] === "min") {
6101                     wdomain[0] = d3.min(to_use_weights);
6102                 }
6103                 if (wdomain[1] === "max") {
6104                     wdomain[1] = d3.max(to_use_weights);
6105                 }
6106     
6107                 current_width_scale = d3.scaleLinear()
6108                 .domain(wdomain)
6109                 .range(wrange);
6110             }
6111             // console.log(current_width_scale)
6112         } else if (draw_opts.path_width_scaling.type === "fixed") {
6113             current_width_scale = function(etc) {
6114                 return svg_width * draw_opts.path_width_scaling.perc_x;
6115             };
6116         } else {
6117             current_width_scale = function(etc) {
6118                 return 0;
6119             };
6120         }
6121         let current_color_scale;
6122         let current_color_scale_prop;
6123         let lighter_fill_scale = draw_opts.color_scaling.lighter_fill;
6124         if (draw_opts.color_scaling.type === "calc") {
6125             if (draw_opts.color_scaling.property === "weight") {
6126                 current_color_scale_prop = draw_opts.weight_scaling + "_" + draw_opts.color_scaling.property;
6127                 let cdomain = draw_opts.color_scaling.domain;
6128                 if (cdomain[0] === "min") {
6129                     cdomain[0] = d3.min(to_use_weights);
6130                 }
6131                 if (cdomain[1] === "max") {
6132                     cdomain[1] = d3.max(to_use_weights);
6133                 }
6134                 let crange = draw_opts.color_scaling.range;
6135                 current_color_scale= d3.scaleLinear()
6136                 .domain(cdomain)
6137                 .range(crange);
6138             } else if (draw_opts.color_scaling.property === "type") {
6139                 current_color_scale_prop = draw_opts.color_scaling.property;
6140                 let cdomain = draw_opts.color_scaling.domain;
6141                 let crange = draw_opts.color_scaling.range;
6142                 // current_color_scale= d3.scaleLinear()
6143                 // .domain(cdomain)
6144                 // .range(crange);
6145                 current_color_scale = function(etc) {
6146                     return crange[cdomain.indexOf(etc)];
6147                 }
6148             }
6149         }
6150         // color_scaling
6151         // property
6152         
6153         let current_centroid_radius;
6154         if (draw_opts.element_centroid_scaling.type === "fixed") {
6155             current_centroid_radius = draw_opts.element_centroid_scaling.radius;
6156         }
6157     
6158         let test_relation_circle_data = [];
6159         for (let irrd = 0; irrd < residue_relation_data.length; irrd++) {
6160             let raw_relation = residue_relation_data[irrd];
6161     
6162             //TODO: test check for elements
6163             //TODO: check for stretches of residues
6164             let source_txt = raw_relation["source"];
6165             let target_txt = raw_relation["target"];
6166     
6167             let source_idx = source_txt;
6168             let target_idx = target_txt;
6169     
6170             let source_resel = whereIsResIdElName(source_idx,full_protein_data);
6171             let target_resel = whereIsResIdElName(target_idx,full_protein_data);
6172     
6173             let source_centroid = centroid_data[source_idx].point;
6174             let target_centroid = centroid_data[target_idx].point;
6175     
6176             let before_source = [];
6177             let after_target = [];
6178             // let source_idx_str = source_idx+"";
6179             // let target_idx_str = target_idx+"";
6180             // if (source_idx_str.includes(";") && current_centroid_radius) {
6181             //     console.log("source is element");
6182             //     let before_centroids_vector = createVector(source_centroid, target_centroid);
6183             //     let before_centroids_vector_normalized = normalizeVector(before_centroids_vector);
6184             //     let before_centroids_vector_scaled = scaleVector(before_centroids_vector_normalized, current_centroid_radius);
6185             //     let before_centroids_vector_rotated_1 = rotateByAng(before_centroids_vector_scaled, source_centroid, 90);
6186             //     let before_centroids_vector_rotated_2 = rotateByAng(before_centroids_vector_scaled, source_centroid, -90);
6187             //     before_source.push(before_centroids_vector_rotated_1);
6188             //     before_source.push(before_centroids_vector_rotated_2);
6189             // }
6190             // if (target_idx_str.includes(";") && current_centroid_radius) {
6191             //     console.log("target is element");
6192             //     let after_centroids_vector = createVector(source_centroid, target_centroid);
6193             //     let after_centroids_vector_normalized = normalizeVector(after_centroids_vector);
6194             //     let after_centroids_vector_scaled = scaleVector(after_centroids_vector_normalized, current_centroid_radius);
6195             //     let after_centroids_vector_rotated_1 = rotateByAng(after_centroids_vector_scaled, target_centroid, 90);
6196             //     let after_centroids_vector_rotated_2 = rotateByAng(after_centroids_vector_scaled, target_centroid, -90);
6197             //     after_target.push(after_centroids_vector_rotated_1);
6198             //     after_target.push(after_centroids_vector_rotated_2);
6199             // }
6200     
6201             let between_centroid = [];
6202             let after_centroid = [];
6203     
6204             let value_height = residue_relation_data[irrd]["relative_weight"];
6205             if (draw_opts.weight_scaling === "absolute") {
6206                 value_height = residue_relation_data[irrd]["absolute_weight"];
6207             }
6208             let centroid_height;
6209             if (group_weights_by_type) {
6210                 centroid_height = current_width_scale(value_height, residue_relation_data[irrd]["type"]);
6211             } else {
6212                 centroid_height = current_width_scale(value_height);
6213             }
6214     
6215             let mid_point = calculateDotArrayMiddlePoint(source_centroid, target_centroid);
6216     
6217             if (draw_opts.centroid_pos.type === "fixed") {
6218                 if (draw_opts.centroid_pos.perc_y !== "between") {
6219                     let between_centroid_y = svg_height * draw_opts.centroid_pos.perc_y;
6220                     mid_point[1] = between_centroid_y;
6221                     // console.log("between_centroid_y");
6222                     // console.log(between_centroid_y);
6223                 }
6224                 if (draw_opts.centroid_pos.perc_x !== "between") {
6225                     let between_centroid_x = svg_width * draw_opts.centroid_pos.perc_x;
6226                     mid_point[0] = between_centroid_x;
6227                 }
6228             } else {
6229                 let source_resel_type = "extracellular";
6230                 if (source_resel.type === "helix" || source_resel.type === "pore")  {
6231                     source_resel_type = "intramembrane";
6232                 } else if (source_resel.type === "loop" && source_resel.draw_area.start_y > (svg_height/2)) {
6233                     source_resel_type = "intracellular";
6234                 }
6235                 let target_resel_type = "extracellular";
6236                 if (target_resel.type === "helix" || target_resel.type === "pore")  {
6237                     target_resel_type = "intramembrane";
6238                 } else if (target_resel.type === "loop" && target_resel.draw_area.start_y > (svg_height/2)) {
6239                     target_resel_type = "intracellular";
6240                 }
6241                 let perc_dict = draw_opts["centroid_pos"]["perc_dict"][source_resel_type][target_resel_type];
6242                 if (perc_dict["perc_y"] !== "between") {
6243                     let between_centroid_y = svg_height * perc_dict["perc_y"];
6244                     mid_point[1] = between_centroid_y;
6245                 }
6246                 if (perc_dict["perc_x"] !== "between") {
6247                     let between_centroid_x = svg_width * perc_dict["perc_x"]
6248                     mid_point[0] = between_centroid_x;
6249                 }
6250                 between_centroid = [mid_point];
6251             }
6252             // let between_centroids_vector = createVector(source_centroid, target_centroid);
6253             // console.log("centroid_height");
6254             // console.log(centroid_height);
6255             if (centroid_height > 0) {
6256                 let between_centroids_vector = createVector(source_centroid, target_centroid);
6257                 let between_centroids_vector_normalized = normalizeVector(between_centroids_vector);
6258                 let between_centroids_vector_scaled = scaleVector(between_centroids_vector_normalized, centroid_height);
6259                 let between_centroids_vector_rotated_1 = rotateByAng(between_centroids_vector_scaled, mid_point, 90);
6260                 let between_centroids_vector_rotated_2 = rotateByAng(between_centroids_vector_scaled, mid_point, -90);
6261                 between_centroid = [between_centroids_vector_rotated_1];
6262                 after_centroid = [between_centroids_vector_rotated_2];
6263             }
6264     
6265             let points = [];
6266             // if (before_source.length > 0) {
6267             //     points.push(...before_source);
6268             // }
6269             points.push(source_centroid);
6270             points.push(...between_centroid);
6271             points.push(target_centroid);
6272             // if (after_target.length > 0) {
6273             //     points.push(...after_target);
6274             //     points.push(target_centroid);
6275             // }
6276             if (after_centroid.length > 0) {
6277                 points.push(...after_centroid);
6278                 points.push(source_centroid);
6279             }
6280             // if (before_source.length > 0) {
6281             //     points.push(before_source[0]);
6282             // }
6283             for (let ip = 0; ip < points.length; ip++) {
6284                 let f = "blue";
6285                 test_relation_circle_data.push({
6286                     "point": points[ip],
6287                     "fill": f,
6288                     "stroke": "yellow",
6289                     "stroke_size": "1px",
6290                     "opacity": "1",
6291                     "circle_radius": "4px",
6292                 });
6293                 
6294             }
6295     
6296             residue_relation_data[irrd]["source_resid"] = raw_relation["source"];
6297             residue_relation_data[irrd]["target_resid"] = raw_relation["target"];
6298             residue_relation_data[irrd]["points"] = deepCopy(points);
6299             if (current_color_scale) {
6300                 let to_fill = current_color_scale(residue_relation_data[irrd][current_color_scale_prop]);
6301                 if (lighter_fill_scale) {
6302                     to_fill = "rgba(" + colorToRGBA(to_fill) + ")";
6303                     to_fill = pSBC(0.20,to_fill);
6304                 }
6305                 residue_relation_data[irrd]["fill"] = to_fill;
6306                 residue_relation_data[irrd]["stroke"] = current_color_scale(residue_relation_data[irrd][current_color_scale_prop]);
6307             }
6308     
6309         }
6310         // plotDotsCircles(test_relation_circle_data, "test_relation_circle_g", "test_relation_circle")
6311         draw_relation_paths(residue_relation_data);
6312     }
6313 
6314     /**
6315      * Searches for residue/element data in processRawUniProt generated data
6316      * @namespace
6317      * @exports NaView
6318      * @name whereIsResIdElName 
6319      * @param {String} resid_or_elname String containing name of a given residue/element
6320      * @param {Object} full_protein_data full data generated by processRawUniProt
6321      * @yields {Object} fresidue/element data of String containing name of a given residue/element
6322      */
6323     function whereIsResIdElName(resid_or_elname, full_protein_data) {
6324         // parsed_protein_data
6325         let resid_or_elname_str = resid_or_elname + "";
6326         let keywords = ["Domain", "InterDomain"];
6327         let filtered;
6328         if (resid_or_elname_str.includes(keywords[0]) === false && resid_or_elname_str.includes(keywords[1]) === false ) {
6329             filtered = deepCopy(full_protein_data).filter(function(a) {
6330                 return a.start <= resid_or_elname && a.end >= resid_or_elname;
6331             });
6332         } else if (resid_or_elname_str.includes(keywords[1]) === true) {
6333             let interdomainindex = parseInt(resid_or_elname.split("InterDomain")[1].split(";")[0]);
6334             filtered = deepCopy(full_protein_data).filter(function(a) {
6335                 return a.dom_iname === interdomainindex;
6336             });
6337         } else if (resid_or_elname_str.includes(keywords[0]) === true) {
6338             let domain_name = resid_or_elname.split("Domain")[1].split(";")[0];
6339             let el_type = resid_or_elname.split(";")[1];
6340             if (el_type === "Pore") {
6341                 el_type = el_type.toLowerCase();
6342                 filtered = deepCopy(full_protein_data).filter(function(a) {
6343                     return a.dom_name === domain_name && a.type === el_type; 
6344                 });
6345             } else {
6346                 let index_search = parseInt(el_type.split("")[el_type.length-1]);
6347                 el_type = el_type.slice(0, el_type.length-1);
6348                 el_type = el_type.toLowerCase();
6349                 filtered = deepCopy(full_protein_data).filter(function(a) {
6350                     return a.dom_name === domain_name && a.type === el_type && a.dom_itype === index_search; 
6351                 });
6352             }
6353         }
6354         return filtered[0];
6355     }
6356 
6357     /**
6358      * Plots paths indicating residue/element relationships
6359      * @namespace
6360      * @exports NaView
6361      * @name draw_relation_paths 
6362      * @param {Array} residue_relation_data array of parsed residue relation Objects
6363      */
6364     function draw_relation_paths(residue_relation_data) {
6365         // let curveN = d3.line().curve(d3.curveNatural);
6366         let curveN = d3.line().curve(d3.curveCatmullRom);
6367         // let curveN = d3.line().curve(d3.curveBasis);
6368         let svg_element = d3.select("#"+svg_id)
6369         .append("g")
6370         .attr("class", "relations_group");
6371     
6372         svg_element.selectAll(".relation_paths_g")
6373         .data(residue_relation_data)
6374         .join(
6375             function(enter) {
6376                 let e = enter.append("g")
6377                 .attr("class", "relation_paths_g");
6378     
6379                 let p = e.append("path")
6380                 .attr("id", function(d){
6381                     return d.source_resid + "_" + d.target_resid;
6382                 })
6383                 .attr("class", "relation_path")
6384                 .attr("d", function(d) {
6385                     return curveN(d.points);
6386                 })
6387                 .attr('stroke', function(d) {
6388                     return d["stroke"];
6389                 })
6390                 .attr("fill", function(d) {
6391                     return d["fill"];
6392                 } )
6393                 .attr("opacity", function(d) {
6394                     return d["opacity"];
6395                 })
6396                 .attr('stroke-width', function(d) {
6397                     return d["stroke_size"];
6398                 });
6399                 
6400                 return e;
6401             },
6402             function(update) {
6403                 return update;
6404             },
6405             function(exit) {
6406                 return exit.remove();
6407             },
6408         )
6409     }
6410 
6411     /**
6412      * Generates text data based on text plotting rules syntax.<br>
6413      * Text data object syntax examples:<br>
6414      * {<br>
6415      *  "text": "text to be drawn" //text to be drawn<br>
6416      *  "positioning": {<br>
6417      *     "type": "absolute" //for specific text drawing coordinates<br>
6418      *     "x": number //x positioning on svg<br>
6419      *     "y": number //y positioning on svg<br>
6420      *     },<br>
6421      *  "fill": "text color" //text color. when absent, defaults froms style object are used<br>
6422      *  "font_style": "normal" //normal, italic or bold. when absent, defaults froms style object are used<br>
6423      *  "font_size": number //size of font. when absent, defaults froms style object are used<br>
6424      *  "font_family": Arial //font family. when absent, defaults froms style object are used<br>
6425      * }<br>
6426      * <br>
6427      * positioning anchored to a given residue/protein element is also possible:<br>
6428      * <br>
6429      * {<br>
6430      *  "text": "text to be drawn" //text to be drawn<br>
6431      *  "positioning": {<br>
6432      *     "type": "residue_or_element" //for text drawing coordinates centered to an element selection<br>
6433      *     "reference": 5 // examples include: 5, W5, DomainI;Helix4, and all possible objects with a centroid<br>
6434      *     "dx": number // optional: number of pixels to move from reference centroid in the horizontal axis<br>
6435      *     "dy": number // optional: number of pixels to move from reference centroid in the vertical axis<br>
6436      *      }<br>
6437      *  ...<br>
6438      * }<br>
6439      * drawing a residue name, id and properties is also possible when reference indicates a residue:<br>
6440      * {<br>
6441      *  "props": ["residue_attribute", "random_string", "residue_property"]<br>
6442      *  "positioning": {<br><br>
6443      *     "type": "residue_or_element" //for text drawing coordinates centered to an element selection<br>
6444      *     "reference": 5<br>
6445      *      ...<br>
6446      *   }<br>
6447      *  ...<br>
6448      * }<br>
6449      * residue properties include one and three letter names ("res_1" and "res_3"), residue index in protein ("res_ind")<br>
6450      * random strings such as ",", "  ", ": " are summed to attributes and properties<br>
6451      * @see convertToResInd
6452      * @see whereIsResIdElName
6453      * @see draw_text_symbols
6454      * @namespace
6455      * @exports NaView
6456      * @name draw_symbols 
6457      * @param {Array} symbols_data array of objects including text data to be drawn
6458      * @param {Object} centroid_data dictionary of valid residue indexes and protein element names
6459      * @param {Object} property_data dictionary of properties per each valid residue indexes
6460      * @param {Ibject} full_protein_data parsed protein data generated from processRawUniProt
6461      */
6462     function draw_symbols(symbols_data, centroid_data,property_data, full_protein_data) {
6463         for (let isd = 0; isd < symbols_data.length; isd++) {
6464             if (symbols_data[isd].hasOwnProperty("font_family") === false) {
6465                 symbols_data[isd]["font_family"] = style_obj.text_defs.font_family;
6466             }
6467             if (symbols_data[isd].hasOwnProperty("font_style") === false) {
6468                 symbols_data[isd]["font_style"] = style_obj.text_defs.font_style;
6469             }
6470             if (symbols_data[isd].hasOwnProperty("font_size") === false) {
6471                 symbols_data[isd]["font_size"] = style_obj.text_defs.font_size;
6472             }
6473             if (symbols_data[isd].hasOwnProperty("fill") === false) {
6474                 symbols_data[isd]["fill"] = style_obj.text_defs.fill;
6475             }
6476             //pre calc element width and height for collision checking
6477             let symbol_bbox = getBBoxGhostText(symbols_data[isd]);
6478             if (symbols_data[isd].positioning.type === "absolute") {
6479                 symbols_data[isd]["tdx"] = symbols_data[isd].positioning.x;
6480                 symbols_data[isd]["tdy"] = symbols_data[isd].positioning.y;
6481                 symbols_data[isd]["x"] = symbols_data[isd].positioning.x;
6482                 symbols_data[isd]["y"] = symbols_data[isd].positioning.y;
6483             } else if (symbols_data[isd].positioning.type === "residue_or_element") {
6484             // } else {
6485                 //get anchoring element centroid and append it
6486                 let obj_index = convertToResInd(symbols_data[isd].positioning.reference);
6487                 // let target_resel = whereIsResIdElName(obj_index,full_protein_data);
6488                 obj_index = obj_index[0];
6489                 let target_centroid = centroid_data[obj_index].point;
6490                 symbols_data[isd]["tdx"] = target_centroid[0];
6491                 symbols_data[isd]["tdy"] = target_centroid[1];
6492                 symbols_data[isd]["x"] = target_centroid[0];
6493                 symbols_data[isd]["y"] = target_centroid[1];
6494             // }
6495             } else if (symbols_data[isd].positioning.type === "residue_relation") {
6496                 //TODO: Allow anchoring on residue relation?
6497             }
6498             //centering element
6499             if (style_obj.text_defs.center_xy) {
6500                 symbols_data[isd]["tdx"] -= symbol_bbox.width/2;
6501                 symbols_data[isd]["tdy"] -= symbol_bbox.height/2;
6502                 symbols_data[isd]["x"] -= symbol_bbox.width/2;
6503                 symbols_data[isd]["y"] -= symbol_bbox.height/2;
6504             }
6505             if (symbols_data[isd].positioning.hasOwnProperty("dx") === true) {
6506                 symbols_data[isd]["tdx"] += symbols_data[isd].positioning.dx;
6507                 symbols_data[isd]["x"] += symbols_data[isd].positioning.dx;
6508             }
6509             if (symbols_data[isd].positioning.hasOwnProperty("dy") === true) {
6510                 symbols_data[isd]["tdy"] += symbols_data[isd].positioning.dy;
6511                 symbols_data[isd]["y"] += symbols_data[isd].positioning.dy;
6512             }
6513             let current_draw_area = {
6514                 "start_x":symbols_data[isd]["x"],
6515                 "start_y":symbols_data[isd]["y"],
6516                 "centroid": [symbols_data[isd]["x"]+(symbol_bbox.width/2),symbols_data[isd]["y"]+(symbol_bbox.height/2)],
6517                 "end_x":symbols_data[isd]["x"]+symbol_bbox.width,
6518                 "end_y":symbols_data[isd]["y"]+symbol_bbox.height,
6519                 "width":symbol_bbox.width,
6520                 "height":symbol_bbox.height,
6521             };
6522             if (symbols_data[isd].hasOwnProperty("text") === false && symbols_data[isd].hasOwnProperty("props") === true && symbols_data[isd].positioning.type !== "residue_relation") {
6523                 let to_text = "";
6524     
6525                 let obj_index = convertToResInd(symbols_data[isd].positioning.reference);
6526                 obj_index = obj_index[0];
6527                 let target_data = whereIsResIdElName(symbols_data[isd].positioning.reference,full_protein_data);
6528                 
6529                 let target_data_keys = Object.keys(target_data);
6530                 let centroid_data_keys = Object.keys(centroid_data[obj_index]);
6531                 let property_data_keys = Object.keys(property_data[obj_index]);
6532     
6533                 for (let iprop = 0; iprop < symbols_data[isd]["props"].length; iprop++) {
6534                     let prop = symbols_data[isd]["props"][iprop];
6535                     if (target_data_keys.indexOf(prop) > -1) {
6536                         to_text += target_data[prop];
6537                     } else if (centroid_data_keys.indexOf(prop) > -1) {
6538                         to_text += centroid_data[obj_index][prop];
6539                     } else if (property_data_keys.indexOf(prop) > -1) {
6540                         to_text += property_data[obj_index][prop];
6541                     } else {
6542                         to_text += prop;
6543                     }
6544                 }
6545                 symbols_data[isd]["text"] = to_text;
6546             }
6547             // else if (symbols_data[isd].hasOwnProperty("text") === false && symbols_data[isd].hasOwnProperty("props") === true && symbols_data[isd].positioning.type === "residue_relation") {
6548             // }            
6549             symbols_data[isd]["draw_area"] = current_draw_area;
6550         }
6551         draw_text_symbols(symbols_data);
6552     }
6553 
6554     /**
6555      * Generates text elements with attributes embedded in data array
6556      * @namespace
6557      * @exports NaView
6558      * @name draw_text_symbols 
6559      * @param {Array} symbols_data array of objects including text data to be drawn
6560      */
6561     function draw_text_symbols(symbols_data) {
6562         let svg_element = d3.select("#"+svg_id)
6563         .append("g")
6564         .attr("class", "text_symbols_group");
6565 
6566         svg_element.selectAll(".text_symbols_g")
6567         .data(symbols_data)
6568         .join(
6569             function(enter) {
6570                 let e = enter.append("g")
6571                 .attr("class", "text_symbols_g");
6572 
6573                 let p = e.append("text")
6574                 .attr("id", function(d, i){
6575                     return "text_symbol_" + i;
6576                 })
6577                 .attr("class", "text_symbols_el")
6578                 .attr("fill", function(d) {
6579                     return d["fill"];
6580                 } )
6581                 // .attr("font", function(d) {
6582                 //     return d["font"];
6583                 // })
6584                 .attr("font-style", function(d) {
6585                     return d["font_style"];
6586                 })
6587                 .attr("font-family", function(d) {
6588                     return d["font_family"];
6589                 })
6590                 .attr("font-size", function(d) {
6591                     return d["font_size"];
6592                 })
6593                 .text(function(d) {
6594                     return d["text"];
6595                 })
6596                 .attr("x", function(d) {
6597                     return d["draw_area"]["start_x"];
6598                 })
6599                 .attr("y", function(d) {
6600                     return d["draw_area"]["start_y"];
6601                 });
6602                 // drawTextCenterArrows(e);
6603                 return e;
6604             },
6605             function(update) {
6606                 return update;
6607             },
6608             function(exit) {
6609                 return exit.remove();
6610             },
6611         )
6612     }
6613 
6614     /**
6615      * Calculates the BBox of a SVG text element without displaying it on a screen
6616      * @namespace
6617      * @exports NaView
6618      * @name getBBoxGhostText 
6619      * @param {Object} data data object for the text object including font and text to be drawn
6620      * @param {String} path_id_keep optional id for keeping element after generation
6621      * @yields {Object} getBBox results of text
6622      */
6623     function getBBoxGhostText(data, path_id_keep) {
6624         let pg = d3.select("#"+svg_id)
6625         .append("g")
6626         .attr("id", function() {
6627             if (path_id_keep) {
6628                 return "par"+path_id_keep;
6629             }
6630             return;
6631         })
6632         .attr("visibility", "hidden");
6633 
6634         let p = pg.append("text")
6635         .attr("id", function() {
6636             if (path_id_keep) {
6637                 return path_id_keep;
6638             }
6639             return;
6640         })
6641         .attr("fill", function() {
6642             return data["fill"];
6643         } )
6644         .attr("font-style", function() {
6645             return data["font_style"];
6646         })
6647         .attr("font-family", function() {
6648             return data["font_family"];
6649         })
6650         .attr("font-size", function() {
6651             return data["font_size"];
6652         })
6653         .text(function() {
6654             return data.text;
6655         })
6656         ;
6657 
6658         let val = p.node().getBBox();
6659         // val = roundDecimals(val, 2);
6660         if (!path_id_keep) {
6661             pg.remove();
6662         }
6663         return val;
6664     }
6665 
6666     /**
6667      * Verifies if current selection has a number corresponding to a residue Id
6668      * @namespace
6669      * @exports NaView
6670      * @name convertToResInd 
6671      * @param {String} resobj selection string to be checked
6672      * @yields {Array} if selection has index returns index of current selection, true<br>
6673      * else returns current selection, false
6674      */
6675     function convertToResInd(resobj) {
6676         let resobj_str = resobj + "";
6677         if (/\d+/.test(resobj) && resobj_str.includes(";") === false) {
6678             let objind = resobj_str.match(/\d+/)[0];
6679             return [parseInt(objind), true];
6680         }
6681         return [resobj, false];
6682     }
6683     
6684 }
6685 
6686