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