├── .gitignore
├── .npmignore
├── .nvmrc
├── LICENSE
├── README.md
├── changelog
├── dist
├── assets
│ ├── .gitkeep
│ └── proseqviewer.css
├── index.d.ts
├── index.js
└── sqv-bundle.js
├── figure.png
├── package.json
├── src
├── assets
│ ├── .gitkeep
│ └── proseqviewer.css
├── index.ts
└── lib
│ ├── colors.model.ts
│ ├── consensus.model.ts
│ ├── events.model.ts
│ ├── icons.model.ts
│ ├── icons.ts
│ ├── interface.ts
│ ├── options.model.ts
│ ├── palettes.ts
│ ├── patterns.model.ts
│ ├── proseqviewer.ts
│ ├── rows.model.ts
│ ├── selection.model.ts
│ └── sequenceInfoModel.ts
├── tsconfig.json
└── webpack.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | node_modules/
3 | /dist/lib
4 | package-lock.json
5 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | src
2 | tsconfig.json
3 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | lts/erbium
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 |
5 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
6 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
7 | Everyone is permitted to copy and distribute verbatim copies
8 | of this license document, but changing it is not allowed.
9 |
10 | Preamble
11 |
12 | The licenses for most software are designed to take away your
13 | freedom to share and change it. By contrast, the GNU General Public
14 | License is intended to guarantee your freedom to share and change free
15 | software--to make sure the software is free for all its users. This
16 | General Public License applies to most of the Free Software
17 | Foundation's software and to any other program whose authors commit to
18 | using it. (Some other Free Software Foundation software is covered by
19 | the GNU Lesser General Public License instead.) You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | this service if you wish), that you receive source code or can get it
26 | if you want it, that you can change the software or use pieces of it
27 | in new free programs; and that you know you can do these things.
28 |
29 | To protect your rights, we need to make restrictions that forbid
30 | anyone to deny you these rights or to ask you to surrender the rights.
31 | These restrictions translate to certain responsibilities for you if you
32 | distribute copies of the software, or if you modify it.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must give the recipients all the rights that
36 | you have. You must make sure that they, too, receive or can get the
37 | source code. And you must show them these terms so they know their
38 | rights.
39 |
40 | We protect your rights with two steps: (1) copyright the software, and
41 | (2) offer you this license which gives you legal permission to copy,
42 | distribute and/or modify the software.
43 |
44 | Also, for each author's protection and ours, we want to make certain
45 | that everyone understands that there is no warranty for this free
46 | software. If the software is modified by someone else and passed on, we
47 | want its recipients to know that what they have is not the original, so
48 | that any problems introduced by others will not reflect on the original
49 | authors' reputations.
50 |
51 | Finally, any free program is threatened constantly by software
52 | patents. We wish to avoid the danger that redistributors of a free
53 | program will individually obtain patent licenses, in effect making the
54 | program proprietary. To prevent this, we have made it clear that any
55 | patent must be licensed for everyone's free use or not licensed at all.
56 |
57 | The precise terms and conditions for copying, distribution and
58 | modification follow.
59 |
60 | GNU GENERAL PUBLIC LICENSE
61 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
62 |
63 | 0. This License applies to any program or other work which contains
64 | a notice placed by the copyright holder saying it may be distributed
65 | under the terms of this General Public License. The "Program", below,
66 | refers to any such program or work, and a "work based on the Program"
67 | means either the Program or any derivative work under copyright law:
68 | that is to say, a work containing the Program or a portion of it,
69 | either verbatim or with modifications and/or translated into another
70 | language. (Hereinafter, translation is included without limitation in
71 | the term "modification".) Each licensee is addressed as "you".
72 |
73 | Activities other than copying, distribution and modification are not
74 | covered by this License; they are outside its scope. The act of
75 | running the Program is not restricted, and the output from the Program
76 | is covered only if its contents constitute a work based on the
77 | Program (independent of having been made by running the Program).
78 | Whether that is true depends on what the Program does.
79 |
80 | 1. You may copy and distribute verbatim copies of the Program's
81 | source code as you receive it, in any medium, provided that you
82 | conspicuously and appropriately publish on each copy an appropriate
83 | copyright notice and disclaimer of warranty; keep intact all the
84 | notices that refer to this License and to the absence of any warranty;
85 | and give any other recipients of the Program a copy of this License
86 | along with the Program.
87 |
88 | You may charge a fee for the physical act of transferring a copy, and
89 | you may at your option offer warranty protection in exchange for a fee.
90 |
91 | 2. You may modify your copy or copies of the Program or any portion
92 | of it, thus forming a work based on the Program, and copy and
93 | distribute such modifications or work under the terms of Section 1
94 | above, provided that you also meet all of these conditions:
95 |
96 | a) You must cause the modified files to carry prominent notices
97 | stating that you changed the files and the date of any change.
98 |
99 | b) You must cause any work that you distribute or publish, that in
100 | whole or in part contains or is derived from the Program or any
101 | part thereof, to be licensed as a whole at no charge to all third
102 | parties under the terms of this License.
103 |
104 | c) If the modified program normally reads commands interactively
105 | when run, you must cause it, when started running for such
106 | interactive use in the most ordinary way, to print or display an
107 | announcement including an appropriate copyright notice and a
108 | notice that there is no warranty (or else, saying that you provide
109 | a warranty) and that users may redistribute the program under
110 | these conditions, and telling the user how to view a copy of this
111 | License. (Exception: if the Program itself is interactive but
112 | does not normally print such an announcement, your work based on
113 | the Program is not required to print an announcement.)
114 |
115 | These requirements apply to the modified work as a whole. If
116 | identifiable sections of that work are not derived from the Program,
117 | and can be reasonably considered independent and separate works in
118 | themselves, then this License, and its terms, do not apply to those
119 | sections when you distribute them as separate works. But when you
120 | distribute the same sections as part of a whole which is a work based
121 | on the Program, the distribution of the whole must be on the terms of
122 | this License, whose permissions for other licensees extend to the
123 | entire whole, and thus to each and every part regardless of who wrote it.
124 |
125 | Thus, it is not the intent of this section to claim rights or contest
126 | your rights to work written entirely by you; rather, the intent is to
127 | exercise the right to control the distribution of derivative or
128 | collective works based on the Program.
129 |
130 | In addition, mere aggregation of another work not based on the Program
131 | with the Program (or with a work based on the Program) on a volume of
132 | a storage or distribution medium does not bring the other work under
133 | the scope of this License.
134 |
135 | 3. You may copy and distribute the Program (or a work based on it,
136 | under Section 2) in object code or executable form under the terms of
137 | Sections 1 and 2 above provided that you also do one of the following:
138 |
139 | a) Accompany it with the complete corresponding machine-readable
140 | source code, which must be distributed under the terms of Sections
141 | 1 and 2 above on a medium customarily used for software interchange; or,
142 |
143 | b) Accompany it with a written offer, valid for at least three
144 | years, to give any third party, for a charge no more than your
145 | cost of physically performing source distribution, a complete
146 | machine-readable copy of the corresponding source code, to be
147 | distributed under the terms of Sections 1 and 2 above on a medium
148 | customarily used for software interchange; or,
149 |
150 | c) Accompany it with the information you received as to the offer
151 | to distribute corresponding source code. (This alternative is
152 | allowed only for noncommercial distribution and only if you
153 | received the program in object code or executable form with such
154 | an offer, in accord with Subsection b above.)
155 |
156 | The source code for a work means the preferred form of the work for
157 | making modifications to it. For an executable work, complete source
158 | code means all the source code for all modules it contains, plus any
159 | associated interface definition files, plus the scripts used to
160 | control compilation and installation of the executable. However, as a
161 | special exception, the source code distributed need not include
162 | anything that is normally distributed (in either source or binary
163 | form) with the major components (compiler, kernel, and so on) of the
164 | operating system on which the executable runs, unless that component
165 | itself accompanies the executable.
166 |
167 | If distribution of executable or object code is made by offering
168 | access to copy from a designated place, then offering equivalent
169 | access to copy the source code from the same place counts as
170 | distribution of the source code, even though third parties are not
171 | compelled to copy the source along with the object code.
172 |
173 | 4. You may not copy, modify, sublicense, or distribute the Program
174 | except as expressly provided under this License. Any attempt
175 | otherwise to copy, modify, sublicense or distribute the Program is
176 | void, and will automatically terminate your rights under this License.
177 | However, parties who have received copies, or rights, from you under
178 | this License will not have their licenses terminated so long as such
179 | parties remain in full compliance.
180 |
181 | 5. You are not required to accept this License, since you have not
182 | signed it. However, nothing else grants you permission to modify or
183 | distribute the Program or its derivative works. These actions are
184 | prohibited by law if you do not accept this License. Therefore, by
185 | modifying or distributing the Program (or any work based on the
186 | Program), you indicate your acceptance of this License to do so, and
187 | all its terms and conditions for copying, distributing or modifying
188 | the Program or works based on it.
189 |
190 | 6. Each time you redistribute the Program (or any work based on the
191 | Program), the recipient automatically receives a license from the
192 | original licensor to copy, distribute or modify the Program subject to
193 | these terms and conditions. You may not impose any further
194 | restrictions on the recipients' exercise of the rights granted herein.
195 | You are not responsible for enforcing compliance by third parties to
196 | this License.
197 |
198 | 7. If, as a consequence of a court judgment or allegation of patent
199 | infringement or for any other reason (not limited to patent issues),
200 | conditions are imposed on you (whether by court order, agreement or
201 | otherwise) that contradict the conditions of this License, they do not
202 | excuse you from the conditions of this License. If you cannot
203 | distribute so as to satisfy simultaneously your obligations under this
204 | License and any other pertinent obligations, then as a consequence you
205 | may not distribute the Program at all. For example, if a patent
206 | license would not permit royalty-free redistribution of the Program by
207 | all those who receive copies directly or indirectly through you, then
208 | the only way you could satisfy both it and this License would be to
209 | refrain entirely from distribution of the Program.
210 |
211 | If any portion of this section is held invalid or unenforceable under
212 | any particular circumstance, the balance of the section is intended to
213 | apply and the section as a whole is intended to apply in other
214 | circumstances.
215 |
216 | It is not the purpose of this section to induce you to infringe any
217 | patents or other property right claims or to contest validity of any
218 | such claims; this section has the sole purpose of protecting the
219 | integrity of the free software distribution system, which is
220 | implemented by public license practices. Many people have made
221 | generous contributions to the wide range of software distributed
222 | through that system in reliance on consistent application of that
223 | system; it is up to the author/donor to decide if he or she is willing
224 | to distribute software through any other system and a licensee cannot
225 | impose that choice.
226 |
227 | This section is intended to make thoroughly clear what is believed to
228 | be a consequence of the rest of this License.
229 |
230 | 8. If the distribution and/or use of the Program is restricted in
231 | certain countries either by patents or by copyrighted interfaces, the
232 | original copyright holder who places the Program under this License
233 | may add an explicit geographical distribution limitation excluding
234 | those countries, so that distribution is permitted only in or among
235 | countries not thus excluded. In such case, this License incorporates
236 | the limitation as if written in the body of this License.
237 |
238 | 9. The Free Software Foundation may publish revised and/or new versions
239 | of the General Public License from time to time. Such new versions will
240 | be similar in spirit to the present version, but may differ in detail to
241 | address new problems or concerns.
242 |
243 | Each version is given a distinguishing version number. If the Program
244 | specifies a version number of this License which applies to it and "any
245 | later version", you have the option of following the terms and conditions
246 | either of that version or of any later version published by the Free
247 | Software Foundation. If the Program does not specify a version number of
248 | this License, you may choose any version ever published by the Free Software
249 | Foundation.
250 |
251 | 10. If you wish to incorporate parts of the Program into other free
252 | programs whose distribution conditions are different, write to the author
253 | to ask for permission. For software which is copyrighted by the Free
254 | Software Foundation, write to the Free Software Foundation; we sometimes
255 | make exceptions for this. Our decision will be guided by the two goals
256 | of preserving the free status of all derivatives of our free software and
257 | of promoting the sharing and reuse of software generally.
258 |
259 | NO WARRANTY
260 |
261 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
262 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
263 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
264 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
265 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
266 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
267 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
268 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
269 | REPAIR OR CORRECTION.
270 |
271 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
272 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
273 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
274 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
275 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
276 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
277 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
278 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
279 | POSSIBILITY OF SUCH DAMAGES.
280 |
281 | END OF TERMS AND CONDITIONS
282 |
283 | How to Apply These Terms to Your New Programs
284 |
285 | If you develop a new program, and you want it to be of the greatest
286 | possible use to the public, the best way to achieve this is to make it
287 | free software which everyone can redistribute and change under these terms.
288 |
289 | To do so, attach the following notices to the program. It is safest
290 | to attach them to the start of each source file to most effectively
291 | convey the exclusion of warranty; and each file should have at least
292 | the "copyright" line and a pointer to where the full notice is found.
293 |
294 | {description}
295 | Copyright (C) {year} {fullname}
296 |
297 | This program is free software; you can redistribute it and/or modify
298 | it under the terms of the GNU General Public License as published by
299 | the Free Software Foundation; either version 2 of the License, or
300 | (at your option) any later version.
301 |
302 | This program is distributed in the hope that it will be useful,
303 | but WITHOUT ANY WARRANTY; without even the implied warranty of
304 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
305 | GNU General Public License for more details.
306 |
307 | You should have received a copy of the GNU General Public License along
308 | with this program; if not, write to the Free Software Foundation, Inc.,
309 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
310 |
311 | Also add information on how to contact you by electronic and paper mail.
312 |
313 | If the program is interactive, make it output a short notice like this
314 | when it starts in an interactive mode:
315 |
316 | Gnomovision version 69, Copyright (C) year name of author
317 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
318 | This is free software, and you are welcome to redistribute it
319 | under certain conditions; type `show c' for details.
320 |
321 | The hypothetical commands `show w' and `show c' should show the appropriate
322 | parts of the General Public License. Of course, the commands you use may
323 | be called something other than `show w' and `show c'; they could even be
324 | mouse-clicks or menu items--whatever suits your program.
325 |
326 | You should also get your employer (if you work as a programmer) or your
327 | school, if any, to sign a "copyright disclaimer" for the program, if
328 | necessary. Here is a sample; alter the names:
329 |
330 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
331 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
332 |
333 | {signature of Ty Coon}, 1 April 1989
334 | Ty Coon, President of Vice
335 |
336 | This General Public License does not permit incorporating your program into
337 | proprietary programs. If your program is a subroutine library, you may
338 | consider it more useful to permit linking proprietary applications with the
339 | library. If this is what you want to do, use the GNU Lesser General
340 | Public License instead of this License.
341 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # ProSeqViewer
3 |
4 | ProSeqViewer is a [TypeScript](https://www.typescriptlang.org/) library to visualize annotation
5 | on single sequences and multiple sequence alignments.
6 |
7 | 
8 |
9 |
10 | ProSeqViewer can be integrated in both modern and dynamic frameworks like [Angular](https://angular.io/)
11 | as well as in static HTML websites. It is used by [MobiDB](http://mobidb.bio.unipd.it/),
12 | [DisProt](http://www.disprot.org/), [RepeatsDB](http://repeatsdb.bio.unipd.it/)
13 |
14 | ### ProSeqViewer features
15 |
16 | * Generates pure HTML, compatible with any browser and operating system
17 | * Easy to install
18 | * Lightweight
19 | * Zero dependencies
20 | * Fast, able to render large alignments
21 | * Interactive, capture mouse selections and clicks
22 | * Responsive, dynamically adapt to window changes
23 |
24 | ## Links
25 | * [ProSeqViewer library GitHub repository](https://github.com/BioComputingUP/ProSeqViewer)
26 | * [Documentation website](https://biocomputingup.github.io/ProSeqViewer-documentation/)
27 | * [NPM package](https://www.npmjs.com/package/proseqviewer)
28 |
29 | ## Getting started
30 |
31 | ### JavaScript installation
32 |
33 | Import the JavaScript bundle and CSS from local files
34 | ```html
35 |
36 |
37 |
38 |
39 | ```
40 |
41 | Alternatively, import from GitHub
42 | ```html
43 |
44 |
45 |
46 |
47 | ```
48 |
49 | Add component
50 | ```html
51 |
52 |
53 |
54 | ```
55 |
56 | Create an instance
57 | ```html
58 |
59 |
60 |
86 |
87 | ```
88 |
89 | ### Angular installation
90 |
91 | Install ProSeqViewer from npm
92 | ```
93 | npm install proseqviewer
94 | ```
95 |
96 | Add ProSeqViewer CSS to your angular.json file
97 | ```json
98 | {
99 | styles: ["./node_modules/proseqviewer/dist/assets/proseqviewer.css"]
100 | }
101 | ```
102 |
103 | Import in your component
104 | ```typescript
105 | import {ProSeqViewer} from 'proseqviewer/dist';
106 | ```
107 |
108 | Add component to your page
109 | ```html
110 |
111 | ```
112 |
113 | Create an instance in your component
114 | ```typescript
115 | // Input sequences
116 | const sequences = [
117 | {sequence: 'TLRAIENFYISNNKISDIPEFVR', id: 1, label: 'ASPA_ECOLI/13-156'},
118 | {sequence: 'TLRASENFPITGYKIHEE..MIN', id: 2, label: 'ASPA_BACSU/16-156'},
119 | {sequence: 'GTKFPRRIIWS............', id: 3, label: 'FUMC_SACS2/1-124'}
120 | ]
121 |
122 | // Input icons
123 | const icons = [
124 | {sequenceId: 1, start: 2, end: 2, icon: 'lollipop'},
125 | {sequenceId: 1, start: 13, end: 13, icon: 'lollipop'}
126 | ]
127 |
128 | // Options and configuration
129 | const options = {
130 | chunkSize: 0,
131 | sequenceColor: 'clustal',
132 | lateralIndexes: false
133 | };
134 |
135 | // Initialize the viewer
136 | const psv = new ProSeqViewer('psv');
137 |
138 | // Generate the HTML
139 | psv.draw({sequences, options, icons});
140 |
141 | ```
142 |
143 | ## Developers
144 | If you are a developer you can update the GitHub and NPM repo with these commands
145 | ```bash
146 | nvm use
147 | npm install
148 | npm run buildall
149 | npm publish
150 | ```
151 |
152 |
153 | ## License
154 |
155 | This program is free software; you can redistribute it and/or modify it under the terms of the CC-BY
156 | License as published by the Creative Commons.
157 |
--------------------------------------------------------------------------------
/changelog:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | 1.1.0 | 15/10/2021
5 | Update input parameters: consensus and sequenceColor.
6 |
7 |
8 | 1.1.7 | 3/11/2021
9 | Fix lateralIndexes bug.
10 |
--------------------------------------------------------------------------------
/dist/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BioComputingUP/ProSeqViewer/3bcadd92532207260ff30bc992c4b36676d53910/dist/assets/.gitkeep
--------------------------------------------------------------------------------
/dist/assets/proseqviewer.css:
--------------------------------------------------------------------------------
1 | .root{
2 | font-family: monospace, monospace;
3 | }
4 |
5 | .container {
6 | margin-left: 0;
7 | }
8 |
9 | .cnk {
10 | position: relative;
11 | white-space: nowrap;
12 | display: inline-block;
13 | vertical-align: top;
14 | }
15 |
16 | .idx {
17 | position: relative;
18 | display: inline-block;
19 | vertical-align: top;
20 | -moz-user-select: none;
21 | -ms-user-select: none;
22 | -webkit-user-select: none;
23 | -webkit-touch-callout: none;
24 | }
25 |
26 | .hidden {
27 | display: none;
28 | }
29 |
30 | .lblContainer {
31 | direction: rtl;
32 | }
33 |
34 | .lbl-hidden {
35 | padding-left: 2px;
36 | padding-right: 5px;
37 | -webkit-user-select: none;
38 | display:block;
39 | height:1em;
40 | line-height:1em;
41 | margin-bottom: 1.5em;
42 | font-style: italic;
43 | justify-content: flex-end;
44 | flex-direction: column;
45 | }
46 |
47 | .lbl {
48 | font-size: 1em;
49 | }
50 |
51 | .cell{
52 | margin-bottom: 1.5em;
53 | height: 1em;
54 | display: inline-block;
55 | }
56 |
57 | .highlight {
58 | background-color: #EBD270!important;;
59 | }
60 |
61 | /* 'card' name is not available because used by bootstrap */
62 | .crd {
63 | position: relative;
64 | display: inline-block;
65 | }
66 |
67 | .crds {
68 | display: inline-block;
69 | -webkit-user-select: none;
70 | }
71 |
--------------------------------------------------------------------------------
/dist/index.d.ts:
--------------------------------------------------------------------------------
1 | export { ProSeqViewer } from './lib/proseqviewer';
2 |
--------------------------------------------------------------------------------
/dist/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | exports.ProSeqViewer = void 0;
4 | var proseqviewer_1 = require("./lib/proseqviewer");
5 | Object.defineProperty(exports, "ProSeqViewer", { enumerable: true, get: function () { return proseqviewer_1.ProSeqViewer; } });
6 |
--------------------------------------------------------------------------------
/dist/sqv-bundle.js:
--------------------------------------------------------------------------------
1 | /******/ (() => { // webpackBootstrap
2 | /******/ "use strict";
3 | /******/ var __webpack_modules__ = ({
4 |
5 | /***/ 333:
6 | /***/ ((__unused_webpack_module, exports) => {
7 |
8 |
9 | Object.defineProperty(exports, "__esModule", ({ value: true }));
10 | exports.ColorsModel = void 0;
11 | class ColorsModel {
12 | static getRowsList(coloring) {
13 | const outCol = this.palette[coloring];
14 | if (!outCol) {
15 | return [];
16 | }
17 | return Object.keys(outCol);
18 | }
19 | static getPositions(coloring, rowNum) {
20 | let outCol;
21 | outCol = this.palette[coloring];
22 | if (!outCol) {
23 | return [];
24 | }
25 | outCol = outCol[rowNum];
26 | if (!outCol) {
27 | return [];
28 | }
29 | outCol = outCol.positions;
30 | if (!outCol) {
31 | return [];
32 | }
33 | return outCol;
34 | }
35 | process(allInputs) {
36 | if (!allInputs.regions) {
37 | allInputs.regions = [];
38 | }
39 | if (allInputs.options && !allInputs.options.sequenceColor) {
40 | const sequenceColorRegions = [];
41 | for (const sequence of allInputs.sequences) {
42 | if (sequence.sequenceColor) {
43 | // @ts-ignore
44 | sequenceColorRegions.push({ sequenceId: sequence.id, start: 1, end: sequence.sequence.length, sequenceColor: sequence.sequenceColor });
45 | }
46 | }
47 | for (const reg of allInputs.regions) {
48 | if (!reg.backgroundColor && reg.sequenceId !== -99999999999998) {
49 | sequenceColorRegions.push(reg);
50 | }
51 | }
52 | if (sequenceColorRegions.length > 0) {
53 | allInputs.regions = sequenceColorRegions;
54 | }
55 | }
56 | const allRegions = Array.prototype.concat(allInputs.icons, allInputs.regions, allInputs.patterns); // ordering
57 | let newRegions = this.fixMissingIds(allRegions, allInputs.sequences);
58 | newRegions = this.transformInput(allRegions, newRegions, allInputs.sequences, allInputs.options);
59 | this.transformColors(allInputs.options);
60 | return newRegions;
61 | }
62 | // transform input structure
63 | transformInput(regions, newRegions, sequences, globalColor) {
64 | // if don't receive new colors, keep old colors
65 | if (!regions) {
66 | return;
67 | }
68 | // if receive new colors, change them
69 | ColorsModel.palette = {};
70 | let info;
71 | if (!globalColor) {
72 | for (const seq of sequences) {
73 | let reg = { sequenceId: seq.id, backgroundColor: '', start: 1, end: seq.sequence.length, sequenceColor: '' };
74 | if (seq.sequenceColor) {
75 | reg.backgroundColor = seq.sequenceColor;
76 | reg.sequenceColor = seq.sequenceColor;
77 | info = this.setSequenceColor(reg, seq);
78 | }
79 | }
80 | }
81 | // overwrite region color if sequenceColor is set
82 | // @ts-ignore
83 | for (const reg of newRegions) {
84 | let sequenceColor;
85 | if (reg.icon) {
86 | continue;
87 | }
88 | if (sequences.find(x => x.id === reg.sequenceId)) {
89 | sequenceColor = sequences.find(x => x.id === reg.sequenceId).sequenceColor;
90 | if (sequenceColor && !globalColor) {
91 | // sequenceColor is set. Cannot set backgroundColor
92 | reg.sequenceColor = sequenceColor;
93 | }
94 | }
95 | info = this.processColor(reg);
96 | if (info === -1) {
97 | continue;
98 | }
99 | ColorsModel.palette[info.type][info.sequenceId].positions
100 | .push({ start: reg.start, end: reg.end, target: info.letterStyle });
101 | if (sequenceColor && sequenceColor.includes('binary')) {
102 | // @ts-ignore
103 | ColorsModel.palette[info.type].binaryColors = this.getBinaryColors(sequenceColor);
104 | }
105 | }
106 | return newRegions;
107 | }
108 | setSequenceColor(reg, seq) {
109 | let info;
110 | info = this.processColor(reg);
111 | ColorsModel.palette[info.type][info.sequenceId].positions
112 | .push({ start: reg.start, end: reg.end, target: info.letterStyle });
113 | if (seq.sequenceColor.includes('binary')) {
114 | // @ts-ignore
115 | ColorsModel.palette[info.type].binaryColors = this.getBinaryColors(seq.sequenceColor);
116 | }
117 | return info;
118 | }
119 | fixMissingIds(regions, sequences) {
120 | const newRegions = [];
121 | for (const reg of regions) {
122 | if (!reg) {
123 | continue;
124 | }
125 | if (sequences.find(x => x.id === reg.sequenceId)) {
126 | newRegions.push(reg);
127 | }
128 | else {
129 | for (const seq of sequences) {
130 | const newReg = {};
131 | // tslint:disable-next-line:forin
132 | for (const key in reg) {
133 | if (reg[key] !== 'sequenceId') {
134 | newReg[key] = reg[key];
135 | }
136 | newReg['sequenceId'] = seq.id;
137 | }
138 | newRegions.push(newReg);
139 | }
140 | }
141 | }
142 | return newRegions;
143 | }
144 | transformColors(opt) {
145 | const sequenceColor = opt.sequenceColor;
146 | let arrColors;
147 | let n;
148 | let c;
149 | for (const type in ColorsModel.palette) {
150 | switch (type) {
151 | case 'gradient': {
152 | // tslint:disable-next-line:forin
153 | for (const row in ColorsModel.palette[type]) {
154 | c = ColorsModel.palette[type][row];
155 | n = c.positions.length + c.chars.length;
156 | arrColors = this.gradient(n);
157 | c.positions.sort((a, b) => (a.start > b.start) ? 1 : -1);
158 | for (const e of c.positions) {
159 | e.backgroundColor = arrColors.pop();
160 | }
161 | }
162 | break;
163 | }
164 | case 'binary': {
165 | // tslint:disable-next-line:forin
166 | for (const row in ColorsModel.palette[type]) {
167 | if (row === 'binaryColors') {
168 | continue;
169 | }
170 | c = ColorsModel.palette[type][row];
171 | n = c.positions.length + c.chars.length;
172 | arrColors = this.binary(n, ColorsModel.palette[type].binaryColors);
173 | c.positions.sort((a, b) => (a.start > b.start) ? 1 : -1);
174 | for (const e of c.positions) {
175 | e.backgroundColor = arrColors.pop();
176 | }
177 | }
178 | break;
179 | }
180 | case sequenceColor: {
181 | // tslint:disable-next-line:forin
182 | // ColorsModel.palette[type]: an obj with regions and color associated es. positions: 1-200, zappo
183 | for (const row in ColorsModel.palette[type]) {
184 | c = ColorsModel.palette[type][row];
185 | if (c.positions.length > 0) {
186 | for (const pos of c.positions) {
187 | pos.backgroundColor = sequenceColor;
188 | }
189 | }
190 | }
191 | break;
192 | }
193 | }
194 | }
195 | }
196 | processColor(e) {
197 | const result = { type: 'custom', sequenceId: -1, letterStyle: '' };
198 | // check if row key is a number
199 | if (e.sequenceId === undefined || isNaN(+e.sequenceId)) {
200 | // wrong entity row key
201 | return -1;
202 | }
203 | result.sequenceId = +e.sequenceId;
204 | // transform target in CSS property
205 | if (e.color) {
206 | result.letterStyle = `color:${e.color};`;
207 | }
208 | if (e.backgroundColor) {
209 | result.letterStyle += `background-color:${e.backgroundColor};`;
210 | }
211 | if (e.backgroundImage) {
212 | result.letterStyle += `background-image: ${e.backgroundImage};`;
213 | }
214 | // define color or palette
215 | if (e.sequenceColor) {
216 | result.type = e.sequenceColor;
217 | }
218 | if (result.type.includes('binary')) {
219 | result.type = 'binary';
220 | }
221 | // reserving space for the transformed object (this.palette)
222 | // if color type not inserted yet
223 | if (!(result.type in ColorsModel.palette)) {
224 | ColorsModel.palette[result.type] = {};
225 | }
226 | // if row not inserted yet
227 | if (!(result.sequenceId in ColorsModel.palette[result.type])) {
228 | ColorsModel.palette[result.type][result.sequenceId] = { positions: [], chars: [] };
229 | }
230 | return result;
231 | }
232 | gradient(n) {
233 | return this.evenlySpacedColors(n);
234 | }
235 | getBinaryColors() {
236 | const color1 = '#93E1D8';
237 | const color2 = '#FFA69E';
238 | return [color1, color2];
239 | }
240 | binary(n, binaryColors) {
241 | let reg = 0;
242 | let flag;
243 | const arrColors = [];
244 | while (reg < n) {
245 | if (flag) {
246 | arrColors.push(binaryColors[0]);
247 | flag = !flag;
248 | }
249 | else {
250 | arrColors.push(binaryColors[1]);
251 | flag = !flag;
252 | }
253 | reg += 1;
254 | }
255 | return arrColors;
256 | }
257 | evenlySpacedColors(n) {
258 | /** how to go around the rgb wheel */
259 | /** add to next rgb component, subtract to previous */
260 | /** ex.: 255,0,0 -(add)-> 255,255,0 -(subtract)-> 0,255,0 */
261 | // starting color: red
262 | const rgb = [255, 0, 0];
263 | // 1536 colors in the rgb wheel
264 | const delta = Math.floor(1536 / n);
265 | let remainder;
266 | let add = true;
267 | let value = 0;
268 | let tmp;
269 | const colors = [];
270 | for (let i = 0; i < n; i++) {
271 | remainder = delta;
272 | while (remainder > 0) {
273 | if (add) {
274 | tmp = (((value + 1) % 3) + 3) % 3;
275 | if (rgb[tmp] + remainder > 255) {
276 | remainder -= (255 - rgb[tmp]);
277 | rgb[tmp] = 255;
278 | add = false;
279 | value = tmp;
280 | }
281 | else {
282 | rgb[tmp] += remainder;
283 | remainder = 0;
284 | }
285 | }
286 | else {
287 | tmp = (((value - 1) % 3) + 3) % 3;
288 | if (rgb[tmp] - remainder < 0) {
289 | remainder -= rgb[tmp];
290 | rgb[tmp] = 0;
291 | add = true;
292 | }
293 | else {
294 | rgb[tmp] -= remainder;
295 | remainder = 0;
296 | }
297 | }
298 | }
299 | colors.push('rgba(' + rgb[0] + ',' + rgb[1] + ',' + rgb[2] + ', 0.4)');
300 | }
301 | return colors;
302 | }
303 | }
304 | exports.ColorsModel = ColorsModel;
305 |
306 |
307 | /***/ }),
308 |
309 | /***/ 588:
310 | /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
311 |
312 |
313 | Object.defineProperty(exports, "__esModule", ({ value: true }));
314 | exports.ConsensusModel = void 0;
315 | const palettes_1 = __webpack_require__(548);
316 | class ConsensusModel {
317 | static setConsensusInfo(type, sequences) {
318 | const idIdentity = -99999999999999;
319 | const idPhysical = -99999999999998;
320 | const consensusInfo = [];
321 | for (let i = 0; i < sequences[0].sequence.length; i++) {
322 | const consensusColumn = {};
323 | for (const sequence of sequences) {
324 | let letter = sequence.sequence[i];
325 | if (type === 'physical') {
326 | if (sequence.id === idIdentity) {
327 | continue;
328 | }
329 | if (letter in palettes_1.Palettes.consensusAaLesk) {
330 | letter = palettes_1.Palettes.consensusAaLesk[letter][0];
331 | }
332 | }
333 | else {
334 | if (sequence.id === idPhysical) {
335 | continue;
336 | }
337 | }
338 | if (letter === '-' || !letter) {
339 | continue;
340 | }
341 | if (consensusColumn[letter]) {
342 | consensusColumn[letter] += 1;
343 | }
344 | else {
345 | consensusColumn[letter] = 1;
346 | }
347 | }
348 | consensusInfo.push(consensusColumn);
349 | }
350 | return consensusInfo;
351 | }
352 | static createConsensus(type, consensus, consensus2, sequences, regions, threshold, palette) {
353 | if (threshold < 50) {
354 | threshold = 100 - threshold;
355 | }
356 | let id = -99999999999999;
357 | let label;
358 | if (type === 'physical') {
359 | label = 'Consensus physical ' + threshold + '%';
360 | id = -99999999999998;
361 | }
362 | else {
363 | label = 'Consensus identity ' + threshold + '%';
364 | }
365 | let consensusSequence = '';
366 | // tslint:disable-next-line:forin
367 | for (const column in consensus) {
368 | let maxLetter;
369 | let maxIndex;
370 | if (Object.keys(consensus[column]).length === 0) {
371 | maxLetter = '.';
372 | }
373 | else {
374 | maxLetter = Object.keys(consensus[column]).reduce((a, b) => consensus[column][a] > consensus[column][b] ? a : b);
375 | maxIndex = consensus[column][maxLetter];
376 | }
377 | let backgroundColor;
378 | let color;
379 | const frequency = (maxIndex / sequences.length) * 100;
380 | if (type === 'physical') {
381 | // consensus id to see if I have all letters equals
382 | // equals letters have precedence over properties
383 | let maxLetterId;
384 | let maxIndexId;
385 | if (Object.keys(consensus[column]).length === 0) {
386 | maxLetterId = '.';
387 | }
388 | else {
389 | maxLetterId = Object.keys(consensus2[column]).reduce((a, b) => consensus2[column][a] > consensus2[column][b] ? a : b);
390 | maxIndexId = consensus2[column][maxLetterId];
391 | }
392 | const frequencyId = (maxIndexId / sequences.length) * 100;
393 | if (frequencyId >= threshold) {
394 | maxLetter = maxLetterId;
395 | [backgroundColor, color] = ConsensusModel.setColorsIdentity(frequencyId, palette, 'physical');
396 | }
397 | else {
398 | if (frequency >= threshold) {
399 | [backgroundColor, color] = ConsensusModel.setColorsPhysical(maxLetter, palette);
400 | }
401 | }
402 | }
403 | else {
404 | [backgroundColor, color] = ConsensusModel.setColorsIdentity(frequency, palette, 'identity');
405 | }
406 | if (frequency < threshold) {
407 | maxLetter = '.';
408 | }
409 | // + 1 because residues start from 1 and not 0
410 | regions.push({ start: +column + 1, end: +column + 1, sequenceId: id, backgroundColor, color });
411 | consensusSequence += maxLetter;
412 | }
413 | sequences.push({ id, sequence: consensusSequence, label });
414 | return [sequences, regions];
415 | }
416 | static setColorsIdentity(frequency, palette, flag) {
417 | let backgroundColor;
418 | let color;
419 | let finalPalette;
420 | if (palette && typeof palette !== 'string' && flag == 'identity') {
421 | finalPalette = palette;
422 | }
423 | else {
424 | finalPalette = palettes_1.Palettes.consensusLevelsIdentity;
425 | }
426 | let steps = [];
427 | for (let key in finalPalette) {
428 | steps.push(+key); // 42
429 | }
430 | steps = steps.sort((a, b) => a < b ? 1 : a > b ? -1 : 0);
431 | for (const step of steps) {
432 | if (frequency >= step) {
433 | backgroundColor = finalPalette[step][0];
434 | color = finalPalette[step][1];
435 | break;
436 | }
437 | }
438 | return [backgroundColor, color];
439 | }
440 | static setColorsPhysical(letter, palette) {
441 | let finalPalette;
442 | let backgroundColor;
443 | let color;
444 | if (palette && typeof palette !== 'string') {
445 | finalPalette = palette;
446 | }
447 | else {
448 | finalPalette = palettes_1.Palettes.consensusAaLesk;
449 | }
450 | for (const el in finalPalette) {
451 | if (finalPalette[el][0] == letter) {
452 | backgroundColor = finalPalette[el][1];
453 | color = finalPalette[el][2];
454 | break;
455 | }
456 | }
457 | return [backgroundColor, color];
458 | }
459 | process(sequences, regions, options) {
460 | if (!regions) {
461 | regions = [];
462 | }
463 | let maxIdx = 0;
464 | for (const row of sequences) {
465 | if (maxIdx < row.sequence.length) {
466 | maxIdx = row.sequence.length;
467 | }
468 | }
469 | for (const row of sequences) {
470 | const diff = maxIdx - row.sequence.length;
471 | if (diff > 0 && row.id !== -99999999999999 && row.id !== -99999999999998) {
472 | for (let i = 0; i < diff; i++) {
473 | row.sequence += '-';
474 | }
475 | }
476 | }
477 | if (options.sequenceColorMatrix) {
478 | regions = [];
479 | sequences.sort((a, b) => a.id - b.id);
480 | const min = sequences[0];
481 | let palette = palettes_1.Palettes.substitutionMatrixBlosum;
482 | // console.log(palette)
483 | if (options.sequenceColorMatrixPalette) {
484 | palette = options.sequenceColorMatrixPalette;
485 | }
486 | let key;
487 | // tslint:disable-next-line:prefer-for-of
488 | for (let i = 0; i < min.sequence.length; i++) {
489 | for (const sequence of sequences) {
490 | if (sequence.id === min.id) {
491 | key = sequence.sequence[i] + sequence.sequence[i];
492 | if (key in palette) {
493 | regions.push({ sequenceId: sequence.id, start: i + 1, end: i + 1,
494 | backgroundColor: palette[key][0], color: palette[key][1] });
495 | }
496 | }
497 | else {
498 | // score with first sequence
499 | key = sequence.sequence[i] + min.sequence[i];
500 | if (key in palette) {
501 | regions.push({ sequenceId: sequence.id, start: i + 1, end: i + 1,
502 | backgroundColor: palette[key][0] });
503 | }
504 | else if (palette[min.sequence[i] + sequence.sequence[i]]) {
505 | key = min.sequence[i] + sequence.sequence[i];
506 | regions.push({ sequenceId: sequence.id, start: i + 1, end: i + 1,
507 | backgroundColor: palette[key][0], color: palette[key][1] });
508 | }
509 | }
510 | }
511 | }
512 | }
513 | else if (options.sequenceColor) {
514 | regions = [];
515 | for (const sequence of sequences) {
516 | sequence.sequenceColor = options.sequenceColor;
517 | regions.push({ sequenceId: sequence.id, start: 1, end: sequence.sequence.length, sequenceColor: options.sequenceColor });
518 | }
519 | }
520 | let consensusInfoIdentity;
521 | let consensusInfoPhysical;
522 | if (options.consensusColorIdentity) {
523 | consensusInfoIdentity = ConsensusModel.setConsensusInfo('identity', sequences);
524 | [sequences, regions] = ConsensusModel.createConsensus('identity', consensusInfoIdentity, false, sequences, regions, options.dotThreshold, options.consensusColorIdentity);
525 | }
526 | else if (options.consensusColorMapping) {
527 | consensusInfoPhysical = ConsensusModel.setConsensusInfo('physical', sequences);
528 | if (!consensusInfoIdentity) {
529 | consensusInfoIdentity = ConsensusModel.setConsensusInfo('identity', sequences);
530 | }
531 | [sequences, regions] = ConsensusModel.createConsensus('physical', consensusInfoPhysical, consensusInfoIdentity, sequences, regions, options.dotThreshold, options.consensusColorMapping);
532 | }
533 | return [sequences, regions];
534 | }
535 | }
536 | exports.ConsensusModel = ConsensusModel;
537 |
538 |
539 | /***/ }),
540 |
541 | /***/ 252:
542 | /***/ ((__unused_webpack_module, exports) => {
543 |
544 |
545 | Object.defineProperty(exports, "__esModule", ({ value: true }));
546 | exports.EventsModel = void 0;
547 | class EventsModel {
548 | onRegionSelected() {
549 | const sequenceViewers = document.getElementsByClassName('cell');
550 | // @ts-ignore
551 | for (const sqv of sequenceViewers) {
552 | sqv.addEventListener('dblclick', r => {
553 | const evt = new CustomEvent('onRegionSelected', { detail: { char: r.srcElement.innerHTML, x: r.srcElement.dataset.resX, y: r.srcElement.dataset.resY } });
554 | window.dispatchEvent(evt);
555 | });
556 | }
557 | }
558 | }
559 | exports.EventsModel = EventsModel;
560 |
561 |
562 | /***/ }),
563 |
564 | /***/ 312:
565 | /***/ ((__unused_webpack_module, exports) => {
566 |
567 |
568 | Object.defineProperty(exports, "__esModule", ({ value: true }));
569 | exports.Icons = void 0;
570 | class Icons {
571 | }
572 | exports.Icons = Icons;
573 | Icons.lollipop = ' ';
574 | Icons.arrowLeft = ' ';
575 | Icons.arrowRight = ' ';
576 | Icons.strand = ' ';
577 | Icons.noSecondary = ' ';
578 | Icons.helix = ' ';
579 | Icons.turn = ' ';
580 |
581 |
582 | /***/ }),
583 |
584 | /***/ 473:
585 | /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
586 |
587 |
588 | Object.defineProperty(exports, "__esModule", ({ value: true }));
589 | exports.IconsModel = void 0;
590 | const icons_1 = __webpack_require__(312);
591 | class IconsModel {
592 | process(regions, sequences, iconsPaths) {
593 | const rows = {};
594 | if (regions && sequences) {
595 | for (const seq of sequences) {
596 | for (const reg of regions) {
597 | if (+seq.id === reg.sequenceId) {
598 | if (!rows[seq.id]) {
599 | rows[seq.id] = {};
600 | }
601 | // tslint:disable-next-line:forin
602 | for (let key in sequences.find(x => x.id === seq.id).sequence) {
603 | key = (+key + 1).toString();
604 | // chars with icon
605 | if (+key >= reg.start && +key <= reg.end && reg.icon) {
606 | if (reg.icon) {
607 | const region = reg.end - (reg.start - 1);
608 | const center = Math.floor(region / 2);
609 | let icon;
610 | if (reg.color && reg.color[0] === '(') {
611 | reg.color = 'rgb' + reg.color;
612 | }
613 | // default icons
614 | switch (reg.icon) {
615 | case 'lollipop': {
616 | icon = icons_1.Icons.lollipop;
617 | break;
618 | }
619 | case 'arrowRight': {
620 | icon = icons_1.Icons.arrowRight;
621 | break;
622 | }
623 | case 'arrowLeft': {
624 | icon = icons_1.Icons.arrowLeft;
625 | break;
626 | }
627 | case 'strand': {
628 | icon = icons_1.Icons.strand;
629 | break;
630 | }
631 | case 'noSecondary': {
632 | icon = icons_1.Icons.noSecondary;
633 | break;
634 | }
635 | case 'helix': {
636 | icon = icons_1.Icons.helix;
637 | break;
638 | }
639 | case 'turn': {
640 | icon = icons_1.Icons.turn;
641 | break;
642 | }
643 | default: {
644 | // customizable icons (svg)
645 | icon = reg.icon;
646 | break;
647 | }
648 | }
649 | if (reg.display === 'center' && +key === reg.start + center) {
650 | rows[seq.id][key] = { char: icon };
651 | }
652 | else if (!reg.display) {
653 | rows[seq.id][key] = { char: icon };
654 | }
655 | }
656 | }
657 | // chars without icon
658 | if (!rows[seq.id][key]) {
659 | rows[seq.id][key] = { char: '' };
660 | }
661 | }
662 | }
663 | }
664 | }
665 | }
666 | const filteredRows = {};
667 | // tslint:disable-next-line:forin
668 | for (const row in rows) {
669 | let flag;
670 | const chars = rows[row];
671 | for (const char in rows[row]) {
672 | if (rows[row][char].char !== '') {
673 | flag = true;
674 | }
675 | }
676 | if (flag) {
677 | filteredRows[row] = chars;
678 | }
679 | }
680 | return filteredRows;
681 | }
682 | }
683 | exports.IconsModel = IconsModel;
684 |
685 |
686 | /***/ }),
687 |
688 | /***/ 175:
689 | /***/ ((__unused_webpack_module, exports) => {
690 |
691 |
692 | Object.defineProperty(exports, "__esModule", ({ value: true }));
693 | exports.OptionsModel = void 0;
694 | class OptionsModel {
695 | constructor() {
696 | this.options = {
697 | fontSize: '14px',
698 | chunkSize: 10,
699 | chunkSeparation: 1,
700 | emptyFiller: ' ',
701 | indexesLocation: null,
702 | wrapLine: true,
703 | viewerWidth: '',
704 | dotThreshold: 90,
705 | lineSeparation: '5px',
706 | sequenceColor: undefined,
707 | customPalette: undefined,
708 | sequenceColorMatrix: undefined,
709 | sequenceColorMatrixPalette: undefined,
710 | consensusColorIdentity: undefined,
711 | consensusColorMapping: undefined,
712 | selection: undefined
713 | };
714 | }
715 | process(opt, consensus) {
716 | /** check input fontSize */
717 | if (opt && opt.fontSize) {
718 | const fSize = opt.fontSize;
719 | const fNum = +fSize.substr(0, fSize.length - 2);
720 | const fUnit = fSize.substr(fSize.length - 2, 2);
721 | if (isNaN(fNum) || (fUnit !== 'px' && fUnit !== 'vw' && fUnit !== 'em')) {
722 | // wrong fontSize format
723 | }
724 | else {
725 | this.options.fontSize = fSize;
726 | }
727 | }
728 | else {
729 | // fontSize not set
730 | this.options.fontSize = '14px'; // default reset
731 | }
732 | /** check input chunkSize */
733 | if (opt && opt.chunkSize) {
734 | const cSize = +opt.chunkSize;
735 | if (isNaN(cSize) || cSize < 0) {
736 | // wrong chunkSize format
737 | }
738 | else {
739 | this.options.chunkSize = cSize;
740 | }
741 | }
742 | /** check input spaceSize */
743 | if (opt && opt.chunkSeparation) {
744 | const chunkSeparation = +opt.chunkSeparation;
745 | if (chunkSeparation >= 0) {
746 | this.options.chunkSeparation = chunkSeparation;
747 | }
748 | }
749 | if (opt && opt.chunkSize == 0) {
750 | this.options.chunkSize = 1;
751 | this.options.chunkSeparation = 0;
752 | }
753 | /** check indexesLocation value */
754 | if (opt && opt.indexesLocation) {
755 | if (opt.indexesLocation == "top" || opt.indexesLocation == "lateral") {
756 | this.options.indexesLocation = opt.indexesLocation;
757 | }
758 | }
759 | /** check selection value */
760 | if (opt && opt.selection) {
761 | if (opt.selection == "columnselection" || opt.selection == "areaselection") {
762 | this.options.selection = opt.selection;
763 | }
764 | }
765 | /** check sequenceColor value */
766 | if (opt && opt.sequenceColor) {
767 | if (typeof opt.sequenceColor !== 'string') {
768 | const keys = Object.keys(opt.sequenceColor);
769 | if (keys[0].length === 1) {
770 | this.options.sequenceColor = 'custom';
771 | this.options.customPalette = opt.sequenceColor;
772 | }
773 | else {
774 | this.options.sequenceColorMatrix = 'custom';
775 | this.options.sequenceColorMatrixPalette = opt.sequenceColor;
776 | }
777 | }
778 | else {
779 | if (opt.sequenceColor === "blosum62") {
780 | this.options.sequenceColorMatrix = opt.sequenceColor;
781 | }
782 | else if (opt.sequenceColor === "clustal") {
783 | this.options.sequenceColor = opt.sequenceColor;
784 | }
785 | }
786 | }
787 | /** check consensusType value */
788 | if (consensus && consensus.color) {
789 | if (typeof consensus.color !== 'string') {
790 | const keys = Object.keys(consensus.color);
791 | if (typeof (keys[0]) === 'string') {
792 | this.options.consensusColorIdentity = consensus.color;
793 | }
794 | else {
795 | this.options.consensusColorMapping = consensus.color;
796 | }
797 | }
798 | else {
799 | if (consensus.color === "identity") {
800 | this.options.consensusColorIdentity = consensus.color;
801 | }
802 | else if (consensus.color === "physical") {
803 | this.options.consensusColorMapping = consensus.color;
804 | }
805 | }
806 | }
807 | /** check consensusThreshold value */
808 | if (consensus && consensus.dotThreshold) {
809 | if (typeof consensus.dotThreshold == 'number') {
810 | this.options.dotThreshold = consensus.dotThreshold;
811 | }
812 | }
813 | /** check rowMarginBottom value */
814 | if (opt && opt.lineSeparation !== undefined) {
815 | const rSize = opt.lineSeparation;
816 | const rNum = +rSize.substr(0, rSize.length - 2);
817 | const rUnit = rSize.substr(rSize.length - 2, 2);
818 | if (isNaN(rNum) || (rUnit !== 'px' && rUnit !== 'vw' && rUnit !== 'em')) {
819 | // wrong lineSeparation format
820 | }
821 | else {
822 | this.options.lineSeparation = rSize;
823 | }
824 | }
825 | else {
826 | // lineSeparation not set
827 | this.options.lineSeparation = '5px'; // default reset
828 | }
829 | /** check wrapline value */
830 | if (opt && typeof opt.wrapLine == 'boolean') {
831 | this.options.wrapLine = opt.wrapLine;
832 | }
833 | /** check oneLineWidth */
834 | if (opt && opt.viewerWidth) {
835 | const viewerWidth = opt.viewerWidth;
836 | const olNum = +viewerWidth.substr(0, viewerWidth.length - 2);
837 | const olUnit = viewerWidth.substr(viewerWidth.length - 2, 2);
838 | if (isNaN(olNum) || (olUnit !== 'px' && olUnit !== 'vw' && olUnit !== 'em')) {
839 | // wrong oneLineWidth format
840 | }
841 | else {
842 | this.options.viewerWidth = viewerWidth;
843 | }
844 | }
845 | return this.options;
846 | }
847 | }
848 | exports.OptionsModel = OptionsModel;
849 |
850 |
851 | /***/ }),
852 |
853 | /***/ 548:
854 | /***/ ((__unused_webpack_module, exports) => {
855 |
856 |
857 | Object.defineProperty(exports, "__esModule", ({ value: true }));
858 | exports.Palettes = void 0;
859 | class Palettes {
860 | }
861 | exports.Palettes = Palettes;
862 | // AA propensities
863 | Palettes.clustal = {
864 | A: '#80a0f0', I: '#80a0f0', L: '#80a0f0', M: '#80a0f0', F: '#80a0f0', W: '#80a0f0', V: '#80a0f0',
865 | K: '#f01505', R: '#f01505', E: '#c048c0', D: '#c048c0', C: '#f08080', G: '#f09048',
866 | N: '#15c015', Q: '#15c015', S: '#15c015', T: '#15c015', P: '#c0c000', H: '#15a4a4', Y: '#15a4a4'
867 | };
868 | Palettes.zappo = {
869 | A: '#ffafaf', R: '#6464ff', N: '#00ff00', D: '#ff0000', C: '#ffff00', Q: '#00ff00', E: '#ff0000',
870 | G: '#ff00ff', H: '#6464ff', I: '#ffafaf', L: '#ffafaf', K: '#ffafaf', M: '#ffc800', F: '#ff00ff',
871 | P: '#00ff00', S: '#00ff00', T: '#15c015', W: '#ffc800', V: '#ffc800', Y: '#ffafaf'
872 | };
873 | Palettes.taylor = {
874 | A: '#ccff00', R: '#0000ff', N: '#cc00ff', D: '#ff0000', C: '#ffff00', Q: '#ff00cc', E: '#ff0066',
875 | G: '#ff9900', H: '#0066ff', I: '#66ff00', L: '#33ff00', K: '#6600ff', M: '#00ff00', F: '#00ff66',
876 | P: '#ffcc00', S: '#ff3300', T: '#ff6600', W: '#00ccff', V: '#00ffcc', Y: '#99ff00'
877 | };
878 | Palettes.hydrophobicity = {
879 | A: '#ad0052', R: '#0000ff', N: '#0c00f3', D: '#0c00f3', C: '#c2003d', Q: '#0c00f3', E: '#0c00f3',
880 | G: '#6a0095', H: '#1500ea', I: '#ff0000', L: '#ea0015', K: '#0000ff', M: '#b0004f', F: '#cb0034',
881 | P: '#4600b9', S: '#5e00a1', T: '#61009e', W: '#5b00a4', V: '#4f00b0', Y: '#f60009',
882 | B: '#0c00f3', X: '#680097', Z: '#0c00f3'
883 | };
884 | Palettes.helixpropensity = {
885 | A: '#e718e7', R: '#6f906f', N: '#1be41b', D: '#778877', C: '#23dc23', Q: '#926d92', E: '#ff00ff',
886 | G: '#00ff00', H: '#758a75', I: '#8a758a', L: '#ae51ae', K: '#a05fa0', M: '#ef10ef', F: '#986798',
887 | P: '#00ff00', S: '#36c936', T: '#47b847', W: '#8a758a', V: '#21de21', Y: '#857a85',
888 | B: '#49b649', X: '#758a75', Z: '#c936c9'
889 | };
890 | Palettes.strandpropensity = {
891 | A: '#5858a7', R: '#6b6b94', N: '#64649b', D: '#2121de', C: '#9d9d62', Q: '#8c8c73', E: '#0000ff',
892 | G: '#4949b6', H: '#60609f', I: '#ecec13', L: '#b2b24d', K: '#4747b8', M: '#82827d', F: '#c2c23d',
893 | P: '#2323dc', S: '#4949b6', T: '#9d9d62', W: '#c0c03f', V: '#d3d32c', Y: '#ffff00',
894 | B: '#4343bc', X: '#797986', Z: '#4747b8'
895 | };
896 | Palettes.turnpropensity = {
897 | A: '#2cd3d3', R: '#708f8f', N: '#ff0000', D: '#e81717', C: '#a85757', Q: '#3fc0c0', E: '#778888',
898 | G: '#ff0000', H: '#708f8f', I: '#00ffff', L: '#1ce3e3', K: '#7e8181', M: '#1ee1e1', F: '#1ee1e1',
899 | P: '#f60909', S: '#e11e1e', T: '#738c8c', W: '#738c8c', V: '#9d6262', Y: '#07f8f8',
900 | B: '#f30c0c', X: '#7c8383', Z: '#5ba4a4'
901 | };
902 | Palettes.buriedindex = {
903 | A: '#00a35c', R: '#00fc03', N: '#00eb14', D: '#00eb14', C: '#0000ff', Q: '#00f10e', E: '#00f10e',
904 | G: '#009d62', H: '#00d52a', I: '#0054ab', L: '#007b84', K: '#00ff00', M: '#009768', F: '#008778',
905 | P: '#00e01f', S: '#00d52a', T: '#00db24', W: '#00a857', V: '#00e619', Y: '#005fa0',
906 | B: '#00eb14', X: '#00b649', Z: '#00f10e'
907 | };
908 | Palettes.nucleotide = {
909 | A: '#64F73F', C: '#FFB340', G: '#EB413C', T: '#3C88EE', U: '#3C88EE'
910 | };
911 | Palettes.purinepyrimidine = {
912 | A: '#FF83FA', C: '#40E0D0', G: '#FF83FA', T: '#40E0D0', U: '#40E0D0', R: '#FF83FA', Y: '#40E0D0'
913 | };
914 | Palettes.consensusLevelsIdentity = {
915 | 100: ['#0A0A0A', '#FFFFFF'],
916 | 70: ['#333333', '#FFFFFF'],
917 | 40: ['#707070', '#FFFFFF'],
918 | 10: ['#A3A3A3', '#FFFFFF'],
919 | 0: ['#FFFFFF', '#FFFFFF']
920 | };
921 | // colour scheme in Lesk, Introduction to Bioinformatics
922 | Palettes.consensusAaLesk = {
923 | A: ['n', '#f09048', '#FFFFFF'],
924 | G: ['n', '#f09048', '#FFFFFF'],
925 | S: ['n', '#f09048', '#FFFFFF'],
926 | T: ['n', '#f09048', '#FFFFFF'],
927 | C: ['h', '#558B6E', '#FFFFFF'],
928 | V: ['h', '#558B6E', '#FFFFFF'],
929 | I: ['h', '#558B6E', '#FFFFFF'],
930 | L: ['h', '#558B6E', '#FFFFFF'],
931 | P: ['h', '#558B6E', '#FFFFFF'],
932 | F: ['h', '#558B6E', '#FFFFFF'],
933 | Y: ['h', '#558B6E', '#FFFFFF'],
934 | M: ['h', '#558B6E', '#FFFFFF'],
935 | W: ['h', '#558B6E', '#FFFFFF'],
936 | N: ['p', '#D4358D', '#FFFFFF'],
937 | Q: ['p', '#D4358D', '#FFFFFF'],
938 | H: ['p', '#D4358D', '#FFFFFF'],
939 | D: ['~', '#A10702', '#FFFFFF'],
940 | E: ['~', '#A10702', '#FFFFFF'],
941 | K: ['+', '#3626A7', '#FFFFFF'],
942 | R: ['+', '#3626A7', '#FFFFFF'] // +: positively charged
943 | };
944 | Palettes.substitutionMatrixBlosum = { WF: ['#CFDBF2', '#000000'], QQ: ['#A1B8E3', '#000000'],
945 | HH: ['#7294D5', '#000000'], YY: ['#81A0D9', '#000000'], ZZ: ['#A1B8E3', '#000000'],
946 | CC: ['#6288D0', '#000000'], PP: ['#81A0D9', '#000000'], VI: ['#B0C4E8', '#000000'],
947 | VM: ['#CFDBF2', '#000000'], KK: ['#A1B8E3', '#000000'], ZK: ['#CFDBF2', '#000000'],
948 | DN: ['#CFDBF2', '#000000'], SS: ['#A1B8E3', '#000000'], QR: ['#CFDBF2', '#000000'],
949 | NN: ['#91ACDE', '#000000'], YF: ['#B0C4E8', '#000000'], VL: ['#CFDBF2', '#000000'],
950 | KR: ['#C0CFED', '#000000'], ED: ['#C0CFED', '#000000'], VV: ['#A1B8E3', '#000000'],
951 | MI: ['#CFDBF2', '#000000'], MM: ['#A1B8E3', '#000000'], ZD: ['#CFDBF2', '#000000'],
952 | FF: ['#91ACDE', '#000000'], BD: ['#A1B8E3', '#000000'], HN: ['#CFDBF2', '#000000'],
953 | TT: ['#A1B8E3', '#000000'], SN: ['#CFDBF2', '#000000'], LL: ['#A1B8E3', '#000000'],
954 | EQ: ['#C0CFED', '#000000'], YW: ['#C0CFED', '#000000'], EE: ['#A1B8E3', '#000000'],
955 | KQ: ['#CFDBF2', '#000000'], WW: ['#3867BC', '#000000'], ML: ['#C0CFED', '#000000'],
956 | KE: ['#CFDBF2', '#000000'], ZE: ['#A1B8E3', '#000000'], ZQ: ['#B0C4E8', '#000000'],
957 | BE: ['#CFDBF2', '#000000'], DD: ['#91ACDE', '#000000'], SA: ['#CFDBF2', '#000000'],
958 | YH: ['#C0CFED', '#000000'], GG: ['#91ACDE', '#000000'], AA: ['#A1B8E3', '#000000'],
959 | II: ['#A1B8E3', '#000000'], TS: ['#CFDBF2', '#000000'], RR: ['#A1B8E3', '#000000'],
960 | LI: ['#C0CFED', '#000000'], ZB: ['#CFDBF2', '#000000'], BN: ['#B0C4E8', '#000000'],
961 | BB: ['#A1B8E3', '#000000']
962 | };
963 |
964 |
965 | /***/ }),
966 |
967 | /***/ 227:
968 | /***/ ((__unused_webpack_module, exports) => {
969 |
970 |
971 | Object.defineProperty(exports, "__esModule", ({ value: true }));
972 | exports.PatternsModel = void 0;
973 | class PatternsModel {
974 | // find index of matched regex positions and create array of regions with color
975 | process(patterns, sequences) {
976 | if (!patterns) {
977 | return;
978 | }
979 | const regions = []; // OutPatterns
980 | // @ts-ignore
981 | for (const element of patterns) {
982 | // tslint:disable-next-line:no-conditional-assignment
983 | const pattern = element.pattern;
984 | let str;
985 | if (sequences.find(x => x.id === element.sequenceId)) {
986 | str = sequences.find(x => x.id === element.sequenceId).sequence;
987 | if (element.start && element.end) {
988 | str = str.substr(element.start - 1, element.end - (element.start - 1));
989 | }
990 | this.regexMatch(str, pattern, regions, element);
991 | }
992 | else {
993 | for (const seq of sequences) {
994 | // regex
995 | if (element.start && element.end) {
996 | str = seq.sequence.substr(element.start - 1, element.end - (element.start - 1));
997 | }
998 | this.regexMatch(str, pattern, regions, element);
999 | }
1000 | }
1001 | }
1002 | return regions;
1003 | }
1004 | regexMatch(str, pattern, regions, element) {
1005 | const re = new RegExp(pattern, "g");
1006 | let match;
1007 | // tslint:disable-next-line:no-conditional-assignment
1008 | while ((match = re.exec(str)) != null) {
1009 | regions.push({ start: +match.index + 1, end: +match.index + +match[0].length,
1010 | backgroundColor: element.backgroundColor, color: element.color, backgroundImage: element.backgroundImage,
1011 | borderColor: element.borderColor, borderStyle: element.borderStyle, sequenceId: element.sequenceId });
1012 | }
1013 | }
1014 | }
1015 | exports.PatternsModel = PatternsModel;
1016 |
1017 |
1018 | /***/ }),
1019 |
1020 | /***/ 505:
1021 | /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
1022 |
1023 |
1024 | Object.defineProperty(exports, "__esModule", ({ value: true }));
1025 | exports.ProSeqViewer = void 0;
1026 | const options_model_1 = __webpack_require__(175);
1027 | const rows_model_1 = __webpack_require__(912);
1028 | const colors_model_1 = __webpack_require__(333);
1029 | const selection_model_1 = __webpack_require__(788);
1030 | const icons_model_1 = __webpack_require__(473);
1031 | const sequenceInfoModel_1 = __webpack_require__(695);
1032 | const events_model_1 = __webpack_require__(252);
1033 | const patterns_model_1 = __webpack_require__(227);
1034 | const consensus_model_1 = __webpack_require__(588);
1035 | class ProSeqViewer {
1036 | constructor(divId) {
1037 | this.divId = divId;
1038 | this.init = false;
1039 | this.params = new options_model_1.OptionsModel();
1040 | this.rows = new rows_model_1.RowsModel();
1041 | this.consensus = new consensus_model_1.ConsensusModel();
1042 | this.regions = new colors_model_1.ColorsModel();
1043 | this.patterns = new patterns_model_1.PatternsModel();
1044 | this.icons = new icons_model_1.IconsModel();
1045 | this.labels = new sequenceInfoModel_1.SequenceInfoModel();
1046 | this.selection = new selection_model_1.SelectionModel();
1047 | this.events = new events_model_1.EventsModel();
1048 | window.onresize = () => {
1049 | this.calculateIdxs(false);
1050 | };
1051 | window.onclick = () => {
1052 | this.calculateIdxs(true);
1053 | }; // had to add this to cover mobidb toggle event
1054 | }
1055 | calculateIdxs(flag) {
1056 | for (const id of ProSeqViewer.sqvList) {
1057 | if (document.getElementById(id) != null) {
1058 | const sqvBody = document.getElementById(id);
1059 | const chunks = sqvBody.getElementsByClassName('cnk');
1060 | let oldTop = 0;
1061 | let newTop = 1;
1062 | for (let i = 0; i < chunks.length; i++) {
1063 | // erase old indexes before recalculating them
1064 | chunks[i].firstElementChild.className = 'idx hidden';
1065 | if (flag) {
1066 | // avoid calculating if idx already set
1067 | if (chunks[i].firstElementChild.className === 'idx') {
1068 | return;
1069 | }
1070 | }
1071 | newTop = chunks[i].getBoundingClientRect().top + window.scrollY;
1072 | if (newTop > oldTop) {
1073 | chunks[i].firstElementChild.className = 'idx';
1074 | oldTop = newTop;
1075 | }
1076 | }
1077 | }
1078 | }
1079 | }
1080 | draw(inputs) {
1081 | const sqvBody = document.getElementById(this.divId);
1082 | if (sqvBody) {
1083 | sqvBody.innerHTML = ``;
1084 | }
1085 | ProSeqViewer.sqvList.push(this.divId);
1086 | let labels;
1087 | let labelsFlag;
1088 | let startIndexes;
1089 | let tooltips;
1090 | let data;
1091 | /** check and process parameters input */
1092 | inputs.options = this.params.process(inputs.options, inputs.consensus);
1093 | /** check and consensus input and global colorScheme */
1094 | if (inputs.options) {
1095 | [inputs.sequences, inputs.regions] = this.consensus.process(inputs.sequences, inputs.regions, inputs.options);
1096 | }
1097 | /** check and process patterns input */
1098 | inputs.patterns = this.patterns.process(inputs.patterns, inputs.sequences);
1099 | /** check and process colors input */
1100 | inputs.regions = this.regions.process(inputs);
1101 | /** check and process icons input */
1102 | let icons = this.icons.process(inputs.regions, inputs.sequences, inputs.icons);
1103 | /** check and process sequences input */
1104 | data = this.rows.process(inputs.sequences, icons, inputs.regions, inputs.options);
1105 | /** check and process labels input */
1106 | [labels, startIndexes, tooltips, labelsFlag] = this.labels.process(inputs.regions, inputs.sequences);
1107 | /** create/update sqv-body html */
1108 | this.createGUI(data, labels, startIndexes, tooltips, inputs.options, labelsFlag);
1109 | /** listen copy paste events */
1110 | this.selection.process();
1111 | /** listen selection events */
1112 | this.events.onRegionSelected();
1113 | }
1114 | generateLabels(idx, labels, startIndexes, indexesLocation, chunkSize, fontSize, tooltips, data, lineSeparation) {
1115 | let labelshtml = '';
1116 | let labelsContainer = '';
1117 | const noGapsLabels = [];
1118 | if (labels.length > 0) {
1119 | if (indexesLocation != 'lateral') {
1120 | labelshtml += ` `;
1121 | }
1122 | let flag;
1123 | let count = -1;
1124 | let seqN = -1;
1125 | for (const seqNum of data) {
1126 | if (noGapsLabels.length < data.length) {
1127 | noGapsLabels.push(0);
1128 | }
1129 | seqN += 1;
1130 | for (const res in seqNum) {
1131 | if (seqNum[res].char && seqNum[res].char.includes('svg')) {
1132 | flag = true;
1133 | break;
1134 | }
1135 | }
1136 | if (flag) {
1137 | noGapsLabels[seqN] = '';
1138 | if (idx) {
1139 | // line with only icons, no need for index
1140 | labelshtml += ` ${noGapsLabels[seqN]} `;
1141 | }
1142 | else {
1143 | labelshtml += ` `;
1144 | }
1145 | }
1146 | else {
1147 | count += 1;
1148 | if (idx) {
1149 | if (!chunkSize) {
1150 | // lateral index regular
1151 | labelshtml += `
1152 | ${(startIndexes[count] - 1) + idx} `;
1153 | }
1154 | else {
1155 | let noGaps = 0;
1156 | for (const res in seqNum) {
1157 | if (+res <= (idx) && seqNum[res].char !== '-') {
1158 | noGaps += 1;
1159 | }
1160 | }
1161 | // lateral index gap
1162 | noGapsLabels[seqN] = noGaps;
1163 | labelshtml += `
1164 | ${(startIndexes[count] - 1) + noGapsLabels[seqN]} `;
1165 | }
1166 | }
1167 | else {
1168 | labelshtml += `${labels[count]}${tooltips[count]} `;
1169 | }
1170 | }
1171 | flag = false;
1172 | }
1173 | if (indexesLocation == 'lateral' || 'both') {
1174 | labelsContainer = `${labelshtml} `;
1175 | }
1176 | else {
1177 | // add margin in case we only have labels and no indexes
1178 | labelsContainer = `${labelshtml} `;
1179 | }
1180 | }
1181 | return labelsContainer;
1182 | }
1183 | addTopIndexes(chunkSize, x, maxTop, rowMarginBottom) {
1184 | let cells = '';
1185 | // adding top indexes
1186 | let chunkTopIndex;
1187 | if (x % chunkSize === 0 && x <= maxTop) {
1188 | chunkTopIndex = `${x} `;
1189 | }
1190 | else {
1191 | chunkTopIndex = `0 `;
1192 | }
1193 | cells += chunkTopIndex;
1194 | return cells;
1195 | }
1196 | createGUI(data, labels, startIndexes, tooltips, options, labelsFlag) {
1197 | const sqvBody = document.getElementById(this.divId);
1198 | // convert to nodes to improve rendering (idea to try):
1199 | // Create new element
1200 | // const root = document.createElement('div');
1201 | // // Add class to element
1202 | // root.className = 'my-new-element';
1203 | // // Add color
1204 | // root.style.color = 'red';
1205 | // // Fill element with html
1206 | // root.innerHTML = ``;
1207 | // // Add element node to DOM graph
1208 | // sqvBody.appendChild(root);
1209 | // // Exit
1210 | // return;
1211 | if (!sqvBody) {
1212 | // Cannot find sqv-body element
1213 | return;
1214 | }
1215 | const chunkSize = options.chunkSize;
1216 | const fontSize = options.fontSize;
1217 | const chunkSeparation = options.chunkSeparation;
1218 | const indexesLocation = options.indexesLocation;
1219 | const wrapLine = options.wrapLine;
1220 | const viewerWidth = options.viewerWidth;
1221 | const lineSeparation = options.lineSeparation + ';';
1222 | const fNum = +fontSize.substr(0, fontSize.length - 2);
1223 | const fUnit = fontSize.substr(fontSize.length - 2, 2);
1224 | // maxIdx = length of the longest sequence
1225 | let maxIdx = 0;
1226 | let maxTop = 0;
1227 | for (const row of data) {
1228 | if (maxIdx < Object.keys(row).length) {
1229 | maxIdx = Object.keys(row).length;
1230 | }
1231 | if (maxTop < Object.keys(row).length) {
1232 | maxTop = Object.keys(row).length;
1233 | }
1234 | }
1235 | const lenghtIndex = maxIdx.toString().length;
1236 | const indexWidth = (fNum * lenghtIndex).toString() + fUnit;
1237 | // consider the last chunk even if is not long enough
1238 | if (chunkSize > 0) {
1239 | maxIdx += (chunkSize - (maxIdx % chunkSize)) % chunkSize;
1240 | }
1241 | // generate labels
1242 | const labelsContainer = this.generateLabels(false, labels, startIndexes, indexesLocation, false, indexWidth, tooltips, data, lineSeparation);
1243 | let index = '';
1244 | let cards = '';
1245 | let cell;
1246 | let entity;
1247 | let style;
1248 | let html = '';
1249 | let idxNum = 0;
1250 | let idx;
1251 | let cells = '';
1252 | for (let x = 1; x <= maxIdx; x++) {
1253 | if (indexesLocation != 'lateral') {
1254 | cells = this.addTopIndexes(chunkSize, x, maxTop, lineSeparation);
1255 | }
1256 | ;
1257 | for (let y = 0; y < data.length; y++) {
1258 | entity = data[y][x];
1259 | style = 'font-size: 1em;display:block;height:1em;line-height:1em;margin-bottom:' + lineSeparation;
1260 | if (y === data.length - 1) {
1261 | style = 'font-size: 1em;display:block;line-height:1em;margin-bottom:' + lineSeparation;
1262 | }
1263 | if (!entity) {
1264 | // emptyfiller
1265 | style = 'font-size: 1em;display:block;color: rgba(0, 0, 0, 0);height:1em;line-height:1em;margin-bottom:' + lineSeparation;
1266 | cell = `A `; // mock char, this has to be done to have chunks all of the same length (last chunk can't be shorter)
1267 | }
1268 | else {
1269 | if (entity.target) {
1270 | style += `${entity.target}`;
1271 | }
1272 | if (entity.char && !entity.char.includes('svg')) {
1273 | // y is the row, x is the column
1274 | cell = `${entity.char} `;
1276 | }
1277 | else {
1278 | style += '-webkit-user-select: none;';
1279 | cell = `${entity.char} `;
1280 | }
1281 | }
1282 | cells += cell;
1283 | }
1284 | cards += `${cells}
`; // width 3/5em to reduce white space around letters
1285 | cells = '';
1286 | if (chunkSize > 0 && x % chunkSize === 0) {
1287 | // considering the row of top indexes
1288 | if (indexesLocation != 'top') {
1289 | idxNum += chunkSize; // lateral index (set only if top indexes missing)
1290 | idx = idxNum - (chunkSize - 1);
1291 | // adding labels
1292 | const gapsContainer = this.generateLabels(idx, labels, startIndexes, indexesLocation, chunkSize, indexWidth, false, data, lineSeparation);
1293 | if (labels[0] === '') {
1294 | index = gapsContainer; // lateral number indexes
1295 | }
1296 | else {
1297 | index = labelsContainer + gapsContainer; // lateral number indexes + labels
1298 | }
1299 | if (!labelsFlag) {
1300 | index = gapsContainer; // lateral number indexes
1301 | }
1302 | else {
1303 | // if(indexesLocation == 'both'){
1304 | index = labelsContainer + gapsContainer; // lateral number indexes + labels
1305 | // }
1306 | }
1307 | }
1308 | else {
1309 | index = labelsContainer; // top
1310 | }
1311 | index = `${index}
`;
1312 | style = `font-size: ${fontSize};`;
1313 | if (x !== maxIdx) {
1314 | style += 'padding-right: ' + chunkSeparation + 'em;';
1315 | }
1316 | else {
1317 | style += 'margin-right: ' + chunkSeparation + 'em;';
1318 | }
1319 | let chunk = '';
1320 | if (labelsFlag || options.consensusType || indexesLocation == 'both' || indexesLocation == 'lateral') { // both
1321 | chunk = ``;
1322 | }
1323 | else {
1324 | chunk = ``; // top
1325 | }
1326 | cards = '';
1327 | index = '';
1328 | html += chunk;
1329 | }
1330 | }
1331 | let innerHTML;
1332 | if (wrapLine) {
1333 | innerHTML = ` ${html}
`;
1334 | }
1335 | else {
1336 | innerHTML = ``;
1339 | }
1340 | sqvBody.innerHTML = innerHTML;
1341 | window.dispatchEvent(new Event('resize'));
1342 | }
1343 | }
1344 | window.ProSeqViewer = ProSeqViewer;
1345 | ProSeqViewer.sqvList = [];
1346 | // VERY IMPORTANT AND USEFUL TO BE ABLE TO HAVE A WORKING BUNDLE.JS!! NEVER DELETE THIS LINE
1347 | window.ProSeqViewer = ProSeqViewer;
1348 |
1349 |
1350 | /***/ }),
1351 |
1352 | /***/ 912:
1353 | /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
1354 |
1355 |
1356 | Object.defineProperty(exports, "__esModule", ({ value: true }));
1357 | exports.RowsModel = void 0;
1358 | const palettes_1 = __webpack_require__(548);
1359 | const colors_model_1 = __webpack_require__(333);
1360 | class RowsModel {
1361 | constructor() {
1362 | this.substitutiveId = 99999999999999;
1363 | }
1364 | processRows(rows, icons, regions, opt) {
1365 | const allData = [];
1366 | // decide which color is more important in case of overwriting
1367 | const coloringOrder = ['custom', 'clustal', 'zappo', 'gradient', 'binary'];
1368 | // order row Numbers
1369 | const rowNumsOrdered = Object.keys(rows).map(Number).sort((n1, n2) => n1 - n2);
1370 | // order keys of Row object
1371 | const ordered = {};
1372 | for (const rowNum of rowNumsOrdered) {
1373 | ordered[rowNum] = Object.keys(rows[+rowNum]).map(Number).sort((n1, n2) => n1 - n2);
1374 | }
1375 | let data;
1376 | let coloringRowNums;
1377 | let tmp;
1378 | // loop through data rows
1379 | for (const rowNum of rowNumsOrdered) {
1380 | tmp = ordered[rowNum];
1381 | // data key: indexes, value: chars
1382 | data = rows[rowNum];
1383 | // data[rowNum].label = this.rows.getLabel(rowNum, this.sequences);
1384 | // console.log(data)
1385 | if (regions) {
1386 | for (const coloring of coloringOrder.reverse()) {
1387 | coloringRowNums = colors_model_1.ColorsModel.getRowsList(coloring).map(Number);
1388 | // if there is coloring for the data row
1389 | if (coloringRowNums.indexOf(rowNum) < 0) {
1390 | // go to next coloring
1391 | continue;
1392 | }
1393 | const positions = colors_model_1.ColorsModel.getPositions(coloring, rowNum);
1394 | // positions = start, end, target (bgcolor || fgcolor)
1395 | if (positions.length > 0) {
1396 | for (const e of positions) {
1397 | for (let i = e.start; i <= e.end; i++) {
1398 | if (!data[i]) {
1399 | continue;
1400 | }
1401 | if (e.backgroundColor && !e.backgroundColor.startsWith('#')) { // is a palette
1402 | if (e.backgroundColor == 'custom') {
1403 | data[i].backgroundColor = opt.customPalette[data[i].char];
1404 | }
1405 | else {
1406 | data[i].backgroundColor = palettes_1.Palettes[e.backgroundColor][data[i].char]; // e.backgroundcolor = zappo, clustal..
1407 | }
1408 | }
1409 | else {
1410 | data[i].backgroundColor = e.backgroundColor; // is a region or pattern
1411 | }
1412 | data[i].target = e.target + 'background-color:' + data[i].backgroundColor;
1413 | }
1414 | }
1415 | }
1416 | }
1417 | if (icons !== {}) {
1418 | const iconsData = icons[rowNum];
1419 | if (iconsData) {
1420 | allData.push(iconsData);
1421 | }
1422 | }
1423 | }
1424 | allData.push(data);
1425 | }
1426 | return allData;
1427 | }
1428 | process(sequences, icons, regions, opt) {
1429 | // check and set global sequenceColor
1430 | if (opt && opt.sequenceColor) {
1431 | // @ts-ignore
1432 | for (const sequence of sequences) {
1433 | if (!sequence.sequenceColor) {
1434 | sequence.sequenceColor = opt.sequenceColor;
1435 | }
1436 | }
1437 | }
1438 | // keep previous data
1439 | if (!sequences) {
1440 | return;
1441 | }
1442 | // reset data
1443 | const rows = {};
1444 | // check if there are undefined or duplicate ids and prepare to reset them
1445 | const values = [];
1446 | let undefinedValues = 0;
1447 | for (const r of Object.keys(sequences)) {
1448 | if (isNaN(+sequences[r].id)) {
1449 | // missing id
1450 | undefinedValues += 1;
1451 | sequences[r].id = this.substitutiveId;
1452 | this.substitutiveId -= 1;
1453 | // otherwise just reset missing ids and log the resetted id
1454 | }
1455 | else {
1456 | if (values.includes(+sequences[r].id)) {
1457 | // Duplicate sequence id
1458 | delete sequences[r];
1459 | }
1460 | else {
1461 | values.push(+sequences[r].id);
1462 | }
1463 | }
1464 | }
1465 | for (const row of Object.keys(sequences)) {
1466 | /** check sequences id type */
1467 | let id;
1468 | if (isNaN(+sequences[row].id)) {
1469 | id = values.sort()[values.length - 1] + 1;
1470 | }
1471 | else {
1472 | id = sequences[row].id;
1473 | }
1474 | /** set row chars */
1475 | rows[id] = {};
1476 | for (const idx of Object.keys(sequences[row].sequence)) {
1477 | const idxKey = (+idx + 1).toString();
1478 | const char = sequences[row].sequence[idx];
1479 | rows[id][idxKey] = { char };
1480 | }
1481 | }
1482 | return this.processRows(rows, icons, regions, opt);
1483 | }
1484 | }
1485 | exports.RowsModel = RowsModel;
1486 |
1487 |
1488 | /***/ }),
1489 |
1490 | /***/ 788:
1491 | /***/ ((__unused_webpack_module, exports) => {
1492 |
1493 |
1494 | Object.defineProperty(exports, "__esModule", ({ value: true }));
1495 | exports.SelectionModel = void 0;
1496 | class SelectionModel {
1497 | constructor() {
1498 | this.event_sequence = [];
1499 | }
1500 | set_start(e) {
1501 | let id;
1502 | let element;
1503 | if (e.path) {
1504 | // chrome support
1505 | element = e.path[0];
1506 | id = document.getElementById(element.dataset.resId);
1507 | }
1508 | else {
1509 | // firefox support
1510 | element = e.originalTarget;
1511 | id = document.getElementById(element.dataset.resId);
1512 | }
1513 | this.lastId = element.dataset.resId;
1514 | this.lastSqv = id;
1515 | this.start = { y: element.dataset.resY, x: element.dataset.resX, sqvId: element.dataset.resId };
1516 | this.lastOver = { y: element.dataset.resY, x: element.dataset.resX, sqvId: element.dataset.resId };
1517 | const elements = document.querySelectorAll('[data-res-id=' + element.dataset.resId + ']');
1518 | this.selectionhighlight(elements);
1519 | this.firstOver = false;
1520 | }
1521 | selectionhighlight(elements) {
1522 | for (const selection of elements) {
1523 | const x = +selection.getAttribute('data-res-x');
1524 | const y = +selection.getAttribute('data-res-y');
1525 | let firstX = Math.min(+this.start.x, +this.lastOver.x);
1526 | let lastX = Math.max(+this.start.x, +this.lastOver.x);
1527 | let firstY = Math.min(+this.start.y, +this.lastOver.y);
1528 | let lastY = Math.max(+this.start.y, +this.lastOver.y);
1529 | // on every drag reselect the whole area ...
1530 | if (x >= +firstX && x <= +lastX &&
1531 | y >= +firstY && y <= +lastY &&
1532 | selection.getAttribute('data-res-id') === this.lastOver.sqvId) {
1533 | selection.classList.add('highlight');
1534 | }
1535 | else {
1536 | selection.classList.remove('highlight');
1537 | }
1538 | }
1539 | }
1540 | process() {
1541 | const sequenceViewers = document.getElementsByClassName('cell');
1542 | // remove selection on new click
1543 | window.onmousedown = (event) => {
1544 | this.event_sequence.push(0);
1545 | // @ts-ignore
1546 | for (const sqv of sequenceViewers) {
1547 | sqv.onmousedown = (e) => {
1548 | this.set_start(e);
1549 | };
1550 | }
1551 | if (this.event_sequence[0] == 0 && this.event_sequence[1] == 1 && this.event_sequence[2] == 2 && this.event_sequence[0] == 0) {
1552 | // left click
1553 | const elements = document.querySelectorAll('[data-res-id=' + this.lastId + ']');
1554 | // @ts-ignore
1555 | for (const selection of elements) {
1556 | selection.classList.remove('highlight');
1557 | }
1558 | }
1559 | // if first click outside sqvDiv (first if is valid in Chrome, second in firefox)
1560 | if (!event.target.dataset.resX) {
1561 | this.firstOver = true;
1562 | }
1563 | if (event.explicitOriginalTarget && event.explicitOriginalTarget.dataset) {
1564 | this.firstOver = true;
1565 | }
1566 | this.event_sequence = [0];
1567 | };
1568 | // @ts-ignore
1569 | for (const sqv of sequenceViewers) {
1570 | sqv.onmouseover = (e) => {
1571 | if (!(1 in this.event_sequence)) {
1572 | this.event_sequence.push(1);
1573 | }
1574 | if (this.firstOver) {
1575 | this.set_start(e);
1576 | }
1577 | let element;
1578 | if (e.path) {
1579 | element = e.path[0];
1580 | }
1581 | else {
1582 | element = e.originalTarget;
1583 | }
1584 | if (this.start) {
1585 | this.lastOver = { y: element.dataset.resY, x: element.dataset.resX, sqvId: element.dataset.resId };
1586 | const elements = document.querySelectorAll('[data-res-id=' + element.dataset.resId + ']');
1587 | if (this.lastId == element.dataset.resId) {
1588 | this.selectionhighlight(elements);
1589 | }
1590 | }
1591 | };
1592 | }
1593 | document.body.onmouseup = () => {
1594 | this.event_sequence.push(2);
1595 | this.firstOver = false;
1596 | if (this.start) {
1597 | this.start = undefined;
1598 | }
1599 | if (this.event_sequence[0] == 0 && this.event_sequence[1] == 2) {
1600 | const elements = document.querySelectorAll('[data-res-id=' + this.lastId + ']');
1601 | // @ts-ignore
1602 | for (const selection of elements) {
1603 | selection.classList.remove('highlight');
1604 | }
1605 | }
1606 | };
1607 | document.body.addEventListener('keydown', (e) => {
1608 | const elements = document.querySelectorAll('[data-res-id=' + this.lastId + ']');
1609 | // @ts-ignore
1610 | e = e || window.event;
1611 | const key = e.which || e.keyCode; // keyCode detection
1612 | const ctrl = e.ctrlKey ? e.ctrlKey : ((key === 17)); // ctrl detection
1613 | if (key === 67 && ctrl) {
1614 | let textToPaste = '';
1615 | const textDict = {};
1616 | let row = '';
1617 | // tslint:disable-next-line:forin
1618 | // @ts-ignore
1619 | for (const selection of elements) {
1620 | if (selection.classList.contains('highlight')) {
1621 | if (!textDict[selection.getAttribute('data-res-y')]) {
1622 | textDict[selection.getAttribute('data-res-y')] = '';
1623 | }
1624 | // new line when new row
1625 | if (selection.getAttribute('data-res-y') !== row && row !== '') {
1626 | textDict[selection.getAttribute('data-res-y')] += selection.innerText;
1627 | }
1628 | else {
1629 | textDict[selection.getAttribute('data-res-y')] += selection.innerText;
1630 | }
1631 | row = selection.getAttribute('data-res-y');
1632 | }
1633 | }
1634 | let flag;
1635 | for (const textRow in textDict) {
1636 | if (flag) {
1637 | textToPaste += '\n' + textDict[textRow];
1638 | }
1639 | else {
1640 | textToPaste += textDict[textRow];
1641 | flag = true;
1642 | }
1643 | }
1644 | if (textToPaste !== '') {
1645 | // copy to clipboard for the paste event
1646 | const dummy = document.createElement('textarea');
1647 | document.body.appendChild(dummy);
1648 | dummy.value = textToPaste;
1649 | dummy.select();
1650 | document.execCommand('copy');
1651 | document.body.removeChild(dummy);
1652 | const evt = new CustomEvent('onHighlightSelection', { detail: { text: textToPaste, eventType: 'highlight selection' } });
1653 | window.dispatchEvent(evt);
1654 | }
1655 | }
1656 | }, false);
1657 | }
1658 | }
1659 | exports.SelectionModel = SelectionModel;
1660 |
1661 |
1662 | /***/ }),
1663 |
1664 | /***/ 695:
1665 | /***/ ((__unused_webpack_module, exports) => {
1666 |
1667 |
1668 | Object.defineProperty(exports, "__esModule", ({ value: true }));
1669 | exports.SequenceInfoModel = void 0;
1670 | class SequenceInfoModel {
1671 | constructor() {
1672 | this.isHTML = (str) => {
1673 | const fragment = document.createRange().createContextualFragment(str);
1674 | // remove all non text nodes from fragment
1675 | fragment.querySelectorAll('*').forEach(el => el.parentNode.removeChild(el));
1676 | // if there is textContent, then not a pure HTML
1677 | return !(fragment.textContent || '').trim();
1678 | };
1679 | }
1680 | process(regions, sequences) {
1681 | const labels = [];
1682 | const startIndexes = [];
1683 | const tooltips = [];
1684 | let flag;
1685 | sequences.sort((a, b) => a.id - b.id);
1686 | for (const seq of sequences) {
1687 | if (!seq) {
1688 | continue;
1689 | }
1690 | if (seq.startIndex) {
1691 | startIndexes.push(seq.startIndex);
1692 | }
1693 | else {
1694 | startIndexes.push(1);
1695 | }
1696 | if (seq.labelTooltip) {
1697 | tooltips.push(seq.labelTooltip);
1698 | }
1699 | else {
1700 | tooltips.push(' ');
1701 | }
1702 | if (seq.label && !this.isHTML(seq.label)) {
1703 | labels.push(seq.label);
1704 | flag = true; // to check if I have at least one label
1705 | }
1706 | else {
1707 | labels.push('');
1708 | }
1709 | }
1710 | return [labels, startIndexes, tooltips, flag];
1711 | }
1712 | }
1713 | exports.SequenceInfoModel = SequenceInfoModel;
1714 |
1715 |
1716 | /***/ })
1717 |
1718 | /******/ });
1719 | /************************************************************************/
1720 | /******/ // The module cache
1721 | /******/ var __webpack_module_cache__ = {};
1722 | /******/
1723 | /******/ // The require function
1724 | /******/ function __webpack_require__(moduleId) {
1725 | /******/ // Check if module is in cache
1726 | /******/ var cachedModule = __webpack_module_cache__[moduleId];
1727 | /******/ if (cachedModule !== undefined) {
1728 | /******/ return cachedModule.exports;
1729 | /******/ }
1730 | /******/ // Create a new module (and put it into the cache)
1731 | /******/ var module = __webpack_module_cache__[moduleId] = {
1732 | /******/ // no module.id needed
1733 | /******/ // no module.loaded needed
1734 | /******/ exports: {}
1735 | /******/ };
1736 | /******/
1737 | /******/ // Execute the module function
1738 | /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
1739 | /******/
1740 | /******/ // Return the exports of the module
1741 | /******/ return module.exports;
1742 | /******/ }
1743 | /******/
1744 | /************************************************************************/
1745 | var __webpack_exports__ = {};
1746 | // This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk.
1747 | (() => {
1748 | var exports = __webpack_exports__;
1749 | var __webpack_unused_export__;
1750 |
1751 | __webpack_unused_export__ = ({ value: true });
1752 | __webpack_unused_export__ = void 0;
1753 | var proseqviewer_1 = __webpack_require__(505);
1754 | __webpack_unused_export__ = ({ enumerable: true, get: function () { return proseqviewer_1.ProSeqViewer; } });
1755 |
1756 | })();
1757 |
1758 | /******/ })()
1759 | ;
--------------------------------------------------------------------------------
/figure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BioComputingUP/ProSeqViewer/3bcadd92532207260ff30bc992c4b36676d53910/figure.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "proseqviewer",
3 | "version": "1.1.9",
4 | "main": "index.js",
5 | "types": "index.d.ts",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/BioComputingUP/ProSeqViewer"
9 | },
10 | "homepage": "https://biocomputingup.github.io/ProSeqViewer-documentation/",
11 | "scripts": {
12 | "test": "echo \"Error: no test specified\" && exit 1",
13 | "build": "rm -rfv dist && tsc",
14 | "postbuild": "cp -r src/assets dist/",
15 | "wp": "npx webpack",
16 | "postwp": "sed -i 's/^exports\\.\\(ProSeqViewer = ProSeqViewer;\\)$/window\\.\\1/' dist/sqv-bundle.js",
17 | "buildall": "rm -rfv dist && npm run build && npm run postbuild && npm run wp && npm run postwp",
18 | "start": "npm run build -- -w"
19 | },
20 | "files": [
21 | "dist/**"
22 | ],
23 | "author": "Martina Bevilacqua",
24 | "license": "ISC",
25 | "description": "TypeScript library to visualize annotation on single sequences and multiple sequence alignments",
26 | "devDependencies": {
27 | "@types/node": "14.14.13",
28 | "typescript": "4.2.4",
29 | "webpack": "5.32.0",
30 | "webpack-cli": "4.6.0"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BioComputingUP/ProSeqViewer/3bcadd92532207260ff30bc992c4b36676d53910/src/assets/.gitkeep
--------------------------------------------------------------------------------
/src/assets/proseqviewer.css:
--------------------------------------------------------------------------------
1 | .root{
2 | font-family: monospace, monospace;
3 | }
4 |
5 | .container {
6 | margin-left: 0;
7 | }
8 |
9 | .cnk {
10 | position: relative;
11 | white-space: nowrap;
12 | display: inline-block;
13 | vertical-align: top;
14 | }
15 |
16 | .idx {
17 | position: relative;
18 | display: inline-block;
19 | vertical-align: top;
20 | -moz-user-select: none;
21 | -ms-user-select: none;
22 | -webkit-user-select: none;
23 | -webkit-touch-callout: none;
24 | }
25 |
26 | .hidden {
27 | display: none;
28 | }
29 |
30 | .lblContainer {
31 | direction: rtl;
32 | }
33 |
34 | .lbl-hidden {
35 | padding-left: 2px;
36 | padding-right: 5px;
37 | -webkit-user-select: none;
38 | display:block;
39 | height:1em;
40 | line-height:1em;
41 | margin-bottom: 1.5em;
42 | font-style: italic;
43 | justify-content: flex-end;
44 | flex-direction: column;
45 | }
46 |
47 | .lbl {
48 | font-size: 1em;
49 | }
50 |
51 | .cell{
52 | margin-bottom: 1.5em;
53 | height: 1em;
54 | display: inline-block;
55 | }
56 |
57 | .highlight {
58 | background-color: #EBD270!important;;
59 | }
60 |
61 | /* 'card' name is not available because used by bootstrap */
62 | .crd {
63 | position: relative;
64 | display: inline-block;
65 | }
66 |
67 | .crds {
68 | display: inline-block;
69 | -webkit-user-select: none;
70 | }
71 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export {ProSeqViewer} from './lib/proseqviewer'
2 |
--------------------------------------------------------------------------------
/src/lib/colors.model.ts:
--------------------------------------------------------------------------------
1 | interface InpColor {
2 | backgroundColor: string;
3 | backgroundImage?: string;
4 | sequenceId: string;
5 | color?: string;
6 | start: number;
7 | end: number;
8 | sequenceColor?: string;
9 | }
10 |
11 | /** Output Colors */
12 | // E.g.: 'coloring': {rowNum: { positions:[{start, end, color, target}] , chars: [{entity, color, target}]}}
13 | interface OutColors {
14 | [coloring: string]: OutColor;
15 | }
16 |
17 | interface OutColor {
18 | [rowNum: string]: {positions: Array, chars: Array};
19 | }
20 |
21 | interface PositionColor {
22 | start: number;
23 | end: number;
24 | backgroundColor?: string;
25 | target: string;
26 | }
27 |
28 | interface CharColor {
29 | entity: string;
30 | backgroundColor?: string;
31 | target: string;
32 | }
33 |
34 | export class ColorsModel {
35 |
36 | static palette: OutColors;
37 |
38 |
39 | static getRowsList(coloring: string) {
40 | const outCol = this.palette[coloring];
41 | if (!outCol) {
42 | return [];
43 | }
44 | return Object.keys(outCol);
45 | }
46 |
47 | static getPositions(coloring: string, rowNum: number) {
48 | let outCol: any;
49 | outCol = this.palette[coloring];
50 | if (!outCol) {
51 | return [];
52 | }
53 | outCol = outCol[rowNum];
54 | if (!outCol) {
55 | return [];
56 | }
57 | outCol = outCol.positions;
58 | if (!outCol) {
59 | return [];
60 | }
61 | return outCol;
62 | }
63 |
64 | process(allInputs) {
65 | if (!allInputs.regions) {
66 | allInputs.regions = [];
67 | }
68 |
69 | if (allInputs.options && !allInputs.options.sequenceColor) {
70 | const sequenceColorRegions = [];
71 | for (const sequence of allInputs.sequences) {
72 | if (sequence.sequenceColor) {
73 | // @ts-ignore
74 | sequenceColorRegions.push({sequenceId: sequence.id, start: 1, end: sequence.sequence.length, sequenceColor: sequence.sequenceColor});
75 | }
76 | }
77 | for (const reg of allInputs.regions) {
78 | if (!reg.backgroundColor && reg.sequenceId !== -99999999999998 ) {
79 | sequenceColorRegions.push(reg);
80 | }
81 | }
82 |
83 | if (sequenceColorRegions.length > 0) {
84 | allInputs.regions = sequenceColorRegions;
85 | }
86 | }
87 |
88 | const allRegions = Array.prototype.concat(allInputs.icons, allInputs.regions, allInputs.patterns); // ordering
89 | let newRegions = this.fixMissingIds(allRegions, allInputs.sequences);
90 | newRegions = this.transformInput(allRegions, newRegions, allInputs.sequences, allInputs.options);
91 | this.transformColors(allInputs.options);
92 |
93 | return newRegions;
94 | }
95 |
96 | // transform input structure
97 | private transformInput(regions, newRegions, sequences, globalColor) {
98 |
99 | // if don't receive new colors, keep old colors
100 | if (!regions) { return; }
101 |
102 |
103 | // if receive new colors, change them
104 | ColorsModel.palette = {};
105 | let info;
106 | if (!globalColor) {
107 | for (const seq of sequences) {
108 |
109 | let reg = {sequenceId: seq.id, backgroundColor: '', start: 1, end: seq.sequence.length, sequenceColor: ''};
110 | if (seq.sequenceColor) {
111 |
112 | reg.backgroundColor = seq.sequenceColor;
113 | reg.sequenceColor = seq.sequenceColor;
114 | info = this.setSequenceColor(reg, seq);
115 | }
116 | }
117 | }
118 |
119 | // overwrite region color if sequenceColor is set
120 | // @ts-ignore
121 | for (const reg of newRegions) {
122 |
123 | let sequenceColor;
124 | if (reg.icon) { continue; }
125 | if (sequences.find(x => x.id === reg.sequenceId)) {
126 |
127 | sequenceColor = sequences.find(x => x.id === reg.sequenceId).sequenceColor;
128 | if (sequenceColor && !globalColor) {
129 | // sequenceColor is set. Cannot set backgroundColor
130 | reg.sequenceColor = sequenceColor; }
131 | }
132 |
133 |
134 |
135 | info = this.processColor(reg);
136 | if (info === -1) { continue; }
137 |
138 |
139 | ColorsModel.palette[info.type][info.sequenceId].positions
140 | .push({start: reg.start, end: reg.end, target: info.letterStyle});
141 | if (sequenceColor && sequenceColor.includes('binary')) {
142 |
143 | // @ts-ignore
144 | ColorsModel.palette[info.type].binaryColors = this.getBinaryColors(sequenceColor);
145 | }
146 | }
147 | return newRegions;
148 | }
149 |
150 | private setSequenceColor(reg, seq) {
151 | let info;
152 |
153 | info = this.processColor(reg);
154 |
155 | ColorsModel.palette[info.type][info.sequenceId].positions
156 | .push({start: reg.start, end: reg.end, target: info.letterStyle});
157 |
158 | if (seq.sequenceColor.includes('binary')) {
159 | // @ts-ignore
160 | ColorsModel.palette[info.type].binaryColors = this.getBinaryColors(seq.sequenceColor);
161 | }
162 | return info;
163 | }
164 |
165 | private fixMissingIds(regions, sequences) {
166 | const newRegions = [];
167 | for (const reg of regions) {
168 | if (!reg) { continue; }
169 | if (sequences.find(x => x.id === reg.sequenceId)) {
170 | newRegions.push(reg);
171 | } else {
172 | for (const seq of sequences) {
173 | const newReg = {};
174 | // tslint:disable-next-line:forin
175 | for (const key in reg) {
176 | if (reg[key] !== 'sequenceId') {
177 | newReg[key] = reg[key];
178 | }
179 | newReg['sequenceId'] = seq.id;
180 | }
181 | newRegions.push(newReg);
182 | }
183 | }
184 | }
185 | return newRegions;
186 | }
187 |
188 | private transformColors(opt) {
189 | const sequenceColor = opt.sequenceColor
190 | let arrColors;
191 | let n;
192 | let c;
193 |
194 |
195 | for (const type in ColorsModel.palette) {
196 | switch (type) {
197 | case 'gradient': {
198 | // tslint:disable-next-line:forin
199 | for (const row in ColorsModel.palette[type]) {
200 | c = ColorsModel.palette[type][row];
201 | n = c.positions.length + c.chars.length;
202 | arrColors = this.gradient(n);
203 | c.positions.sort((a, b) => (a.start > b.start) ? 1 : -1);
204 | for (const e of c.positions) {
205 | e.backgroundColor = arrColors.pop();
206 | }
207 | }
208 | break;
209 | }
210 | case 'binary': {
211 | // tslint:disable-next-line:forin
212 | for (const row in ColorsModel.palette[type]) {
213 | if (row === 'binaryColors') {
214 | continue;
215 | }
216 | c = ColorsModel.palette[type][row];
217 | n = c.positions.length + c.chars.length;
218 | arrColors = this.binary(n, ColorsModel.palette[type].binaryColors);
219 | c.positions.sort((a, b) => (a.start > b.start) ? 1 : -1);
220 | for (const e of c.positions) {
221 | e.backgroundColor = arrColors.pop();
222 | }
223 | }
224 | break;
225 | }
226 | case sequenceColor: {
227 | // tslint:disable-next-line:forin
228 | // ColorsModel.palette[type]: an obj with regions and color associated es. positions: 1-200, zappo
229 | for (const row in ColorsModel.palette[type]) {
230 |
231 | c = ColorsModel.palette[type][row];
232 | if (c.positions.length > 0) {
233 |
234 | for (const pos of c.positions) {
235 | pos.backgroundColor = sequenceColor;
236 | }
237 | }
238 | }
239 | break;
240 | }
241 | }
242 | }
243 | }
244 |
245 | private processColor(e: InpColor) {
246 |
247 |
248 | const result = {type: 'custom', sequenceId: -1, letterStyle: ''};
249 |
250 | // check if row key is a number
251 | if (e.sequenceId === undefined || isNaN(+e.sequenceId)) {
252 | // wrong entity row key
253 | return -1;
254 | }
255 | result.sequenceId = +e.sequenceId;
256 |
257 | // transform target in CSS property
258 | if (e.color) {result.letterStyle = `color:${e.color};`;
259 | }
260 | if (e.backgroundColor) {
261 | result.letterStyle += `background-color:${e.backgroundColor};`; }
262 | if (e.backgroundImage) {
263 | result.letterStyle += `background-image: ${e.backgroundImage};`;
264 | }
265 |
266 | // define color or palette
267 | if (e.sequenceColor) { result.type = e.sequenceColor; }
268 |
269 |
270 | if (result.type.includes('binary')) { result.type = 'binary'; }
271 |
272 | // reserving space for the transformed object (this.palette)
273 | // if color type not inserted yet
274 | if (!(result.type in ColorsModel.palette)) {
275 | ColorsModel.palette[result.type] = {};
276 | }
277 | // if row not inserted yet
278 | if (!(result.sequenceId in ColorsModel.palette[result.type])) {
279 | ColorsModel.palette[result.type][result.sequenceId] = {positions: [], chars: []};
280 | }
281 | return result;
282 | }
283 |
284 |
285 | private gradient(n: number) {
286 | return this.evenlySpacedColors(n);
287 | }
288 |
289 | private getBinaryColors() {
290 | const color1 = '#93E1D8';
291 | const color2 = '#FFA69E';
292 | return [color1, color2];
293 | }
294 |
295 | private binary(n: number, binaryColors) {
296 | let reg = 0;
297 | let flag;
298 |
299 | const arrColors = [];
300 | while (reg < n) {
301 | if (flag) {
302 | arrColors.push(binaryColors[0]);
303 | flag = !flag;
304 | } else {
305 | arrColors.push(binaryColors[1]);
306 | flag = !flag;
307 | }
308 | reg += 1;
309 | }
310 | return arrColors;
311 | }
312 |
313 | private evenlySpacedColors( n: number ) {
314 | /** how to go around the rgb wheel */
315 | /** add to next rgb component, subtract to previous */
316 | /** ex.: 255,0,0 -(add)-> 255,255,0 -(subtract)-> 0,255,0 */
317 |
318 | // starting color: red
319 | const rgb = [255, 0, 0];
320 | // 1536 colors in the rgb wheel
321 | const delta = Math.floor(1536 / n);
322 |
323 | let remainder;
324 | let add = true;
325 | let value = 0;
326 | let tmp;
327 |
328 | const colors = [];
329 | for (let i = 0; i < n; i++) {
330 | remainder = delta;
331 | while (remainder > 0) {
332 | if (add) {
333 | tmp = (((value + 1) % 3) + 3) % 3;
334 | if (rgb[tmp] + remainder > 255) {
335 | remainder -= (255 - rgb[tmp]);
336 | rgb[tmp] = 255;
337 | add = false;
338 | value = tmp;
339 | } else {
340 | rgb[tmp] += remainder;
341 | remainder = 0;
342 | }
343 | } else {
344 | tmp = (((value - 1) % 3) + 3) % 3;
345 | if (rgb[tmp] - remainder < 0) {
346 | remainder -= rgb[tmp];
347 | rgb[tmp] = 0;
348 | add = true;
349 | } else {
350 | rgb[tmp] -= remainder;
351 | remainder = 0;
352 | }
353 | }
354 | }
355 | colors.push('rgba(' + rgb[0] + ',' + rgb[1] + ',' + rgb[2] + ', 0.4)');
356 | }
357 | return colors;
358 | }
359 | }
360 |
--------------------------------------------------------------------------------
/src/lib/consensus.model.ts:
--------------------------------------------------------------------------------
1 | import {Palettes} from './palettes';
2 |
3 | export class ConsensusModel {
4 |
5 |
6 |
7 | static setConsensusInfo(type, sequences) {
8 | const idIdentity = -99999999999999;
9 | const idPhysical = -99999999999998;
10 | const consensusInfo = [];
11 |
12 | for (let i = 0; i < sequences[0].sequence.length; i++) {
13 | const consensusColumn = {};
14 | for (const sequence of sequences) {
15 | let letter = sequence.sequence[i];
16 | if (type === 'physical') {
17 | if (sequence.id === idIdentity) { continue; }
18 | if (letter in Palettes.consensusAaLesk) {
19 | letter = Palettes.consensusAaLesk[letter][0];
20 | }
21 | } else {
22 | if (sequence.id === idPhysical) { continue; }
23 | }
24 | if (letter === '-' || !letter) { continue; }
25 | if (consensusColumn[letter]) {
26 | consensusColumn[letter] += 1;
27 | } else {
28 | consensusColumn[letter] = 1;
29 | }
30 | }
31 | consensusInfo.push(consensusColumn);
32 | }
33 | return consensusInfo;
34 | }
35 |
36 | static createConsensus(type, consensus, consensus2, sequences, regions, threshold, palette) {
37 |
38 |
39 | if (threshold < 50) {
40 | threshold = 100 - threshold;
41 | }
42 |
43 | let id = -99999999999999;
44 | let label;
45 | if (type === 'physical') {
46 | label = 'Consensus physical ' + threshold + '%';
47 | id = -99999999999998;
48 | } else {
49 | label = 'Consensus identity ' + threshold + '%';
50 | }
51 |
52 | let consensusSequence = '';
53 |
54 | // tslint:disable-next-line:forin
55 | for (const column in consensus) {
56 |
57 | let maxLetter;
58 | let maxIndex;
59 | if (Object.keys(consensus[column]).length === 0) {
60 | maxLetter = '.';
61 | } else {
62 | maxLetter = Object.keys(consensus[column]).reduce((a, b) =>
63 | consensus[column][a] > consensus[column][b] ? a : b);
64 | maxIndex = consensus[column][maxLetter];
65 | }
66 |
67 | let backgroundColor;
68 | let color;
69 | const frequency = (maxIndex / sequences.length) * 100;
70 | if (type === 'physical') {
71 | // consensus id to see if I have all letters equals
72 | // equals letters have precedence over properties
73 | let maxLetterId;
74 | let maxIndexId;
75 | if (Object.keys(consensus[column]).length === 0) {
76 | maxLetterId = '.';
77 | } else {
78 | maxLetterId = Object.keys(consensus2[column]).reduce((a, b) =>
79 | consensus2[column][a] > consensus2[column][b] ? a : b);
80 | maxIndexId = consensus2[column][maxLetterId];
81 | }
82 | const frequencyId = (maxIndexId / sequences.length) * 100;
83 | if (frequencyId >= threshold) {
84 | maxLetter = maxLetterId;
85 | [backgroundColor, color] = ConsensusModel.setColorsIdentity(frequencyId, palette, 'physical');
86 | } else {
87 |
88 | if (frequency >= threshold) {
89 | [backgroundColor, color] = ConsensusModel.setColorsPhysical(maxLetter, palette);
90 | }
91 | }
92 |
93 | } else {
94 | [backgroundColor, color] = ConsensusModel.setColorsIdentity(frequency, palette, 'identity');
95 | }
96 | if (frequency < threshold) {
97 | maxLetter = '.';
98 | }
99 | // + 1 because residues start from 1 and not 0
100 | regions.push({start: +column + 1, end: +column + 1, sequenceId: id, backgroundColor, color });
101 | consensusSequence += maxLetter;
102 | }
103 |
104 | sequences.push({id, sequence: consensusSequence, label});
105 |
106 | return [sequences, regions];
107 | }
108 |
109 | static setColorsIdentity(frequency, palette, flag) {
110 | let backgroundColor;
111 | let color;
112 | let finalPalette;
113 |
114 | if (palette && typeof palette !== 'string' && flag == 'identity') {
115 | finalPalette = palette;
116 | } else {
117 | finalPalette = Palettes.consensusLevelsIdentity;
118 | }
119 | let steps = [];
120 | for (let key in finalPalette) {
121 | steps.push(+key); // 42
122 | }
123 | steps = steps.sort((a,b) => a < b ? 1 : a > b ? -1 : 0)
124 |
125 | for (const step of steps) {
126 |
127 | if (frequency >= step) {
128 | backgroundColor = finalPalette[step][0];
129 | color = finalPalette[step][1];
130 | break;
131 | }
132 | }
133 | return [backgroundColor, color];
134 | }
135 |
136 | static setColorsPhysical(letter, palette) {
137 | let finalPalette;
138 | let backgroundColor;
139 | let color;
140 |
141 | if (palette && typeof palette !== 'string') {
142 | finalPalette = palette;
143 | } else {
144 | finalPalette = Palettes.consensusAaLesk;
145 | }
146 |
147 | for (const el in finalPalette) {
148 |
149 | if (finalPalette[el][0] == letter) {
150 | backgroundColor = finalPalette[el][1];
151 | color = finalPalette[el][2];
152 | break;
153 | }
154 | }
155 | return [backgroundColor, color];
156 | }
157 |
158 | process(sequences, regions, options) {
159 | if (!regions) { regions = []}
160 | let maxIdx = 0;
161 |
162 | for (const row of sequences) {
163 | if (maxIdx < row.sequence.length) { maxIdx = row.sequence.length; }
164 | }
165 |
166 | for (const row of sequences) {
167 | const diff = maxIdx - row.sequence.length;
168 | if (diff > 0 && row.id !== -99999999999999 && row.id !== -99999999999998) {
169 | for (let i = 0; i < diff; i++) {
170 | row.sequence += '-';
171 | }
172 | }
173 | }
174 |
175 | if (options.sequenceColorMatrix) {
176 | regions = [];
177 | sequences.sort( (a, b) => a.id - b.id);
178 | const min = sequences[0];
179 |
180 | let palette = Palettes.substitutionMatrixBlosum;
181 | // console.log(palette)
182 | if (options.sequenceColorMatrixPalette) {
183 | palette = options.sequenceColorMatrixPalette
184 | }
185 |
186 | let key;
187 | // tslint:disable-next-line:prefer-for-of
188 | for (let i = 0; i < min.sequence.length; i++) {
189 | for (const sequence of sequences) {
190 |
191 | if (sequence.id === min.id) {
192 | key = sequence.sequence[i] + sequence.sequence[i]
193 | if (key in palette) {
194 |
195 | regions.push({sequenceId: sequence.id, start: i + 1, end: i + 1,
196 | backgroundColor: palette[key][0], color: palette[key][1]});
197 | }
198 |
199 | } else {
200 |
201 | // score with first sequence
202 | key = sequence.sequence[i] + min.sequence[i]
203 | if (key in palette) {
204 | regions.push({sequenceId: sequence.id, start: i + 1, end: i + 1,
205 | backgroundColor: palette[key][0]});
206 | } else if (palette[min.sequence[i] + sequence.sequence[i]]) {
207 | key = min.sequence[i] + sequence.sequence[i]
208 | regions.push({sequenceId: sequence.id, start: i + 1, end: i + 1,
209 | backgroundColor: palette[key][0], color: palette[key][1]});
210 | }
211 |
212 | }
213 | }
214 |
215 | }
216 |
217 | } else if (options.sequenceColor) {
218 | regions = [];
219 | for (const sequence of sequences) {
220 | sequence.sequenceColor = options.sequenceColor;
221 | regions.push({sequenceId: sequence.id, start: 1, end: sequence.sequence.length, sequenceColor: options.sequenceColor});
222 | }
223 | }
224 |
225 | let consensusInfoIdentity;
226 | let consensusInfoPhysical;
227 |
228 | if (options.consensusColorIdentity) {
229 | consensusInfoIdentity = ConsensusModel.setConsensusInfo('identity', sequences);
230 | [sequences, regions] = ConsensusModel.createConsensus('identity', consensusInfoIdentity, false, sequences, regions, options.dotThreshold, options.consensusColorIdentity);
231 | } else if (options.consensusColorMapping) {
232 | consensusInfoPhysical = ConsensusModel.setConsensusInfo('physical', sequences);
233 | if (!consensusInfoIdentity) { consensusInfoIdentity = ConsensusModel.setConsensusInfo('identity', sequences); }
234 | [sequences, regions] = ConsensusModel.createConsensus('physical', consensusInfoPhysical, consensusInfoIdentity, sequences, regions, options.dotThreshold, options.consensusColorMapping);
235 | }
236 |
237 | return [sequences, regions];
238 | }
239 |
240 | }
241 |
--------------------------------------------------------------------------------
/src/lib/events.model.ts:
--------------------------------------------------------------------------------
1 | export class EventsModel {
2 |
3 | public onRegionSelected() {
4 |
5 | const sequenceViewers = document.getElementsByClassName('cell');
6 |
7 | // @ts-ignore
8 | for (const sqv of sequenceViewers) {
9 |
10 | sqv.addEventListener('dblclick', r => {
11 |
12 | const evt = new CustomEvent('onRegionSelected', {detail: {char: r.srcElement.innerHTML, x: r.srcElement.dataset.resX, y: r.srcElement.dataset.resY}} );
13 | window.dispatchEvent(evt);
14 |
15 | });
16 | }
17 |
18 | }
19 |
20 | }
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/lib/icons.model.ts:
--------------------------------------------------------------------------------
1 | import {Icons} from './icons';
2 |
3 | export class IconsModel {
4 |
5 | process(regions, sequences, iconsPaths) {
6 | const rows = {};
7 | if (regions && sequences) {
8 | for (const seq of sequences) {
9 | for (const reg of regions) {
10 | if (+seq.id === reg.sequenceId) {
11 | if (!rows[seq.id]) {
12 | rows[seq.id] = {};
13 | }
14 | // tslint:disable-next-line:forin
15 | for (let key in sequences.find(x => x.id === seq.id).sequence) {
16 | key = (+key + 1).toString();
17 | // chars with icon
18 | if (+key >= reg.start && +key <= reg.end && reg.icon) {
19 | if (reg.icon) {
20 | const region = reg.end - (reg.start - 1);
21 | const center = Math.floor(region / 2);
22 | let icon;
23 | if (reg.color && reg.color[0] === '(') {
24 | reg.color = 'rgb' + reg.color;
25 | }
26 |
27 | // default icons
28 |
29 | switch (reg.icon) {
30 | case 'lollipop': {
31 | icon = Icons.lollipop;
32 | break;
33 | }
34 | case 'arrowRight': {
35 | icon = Icons.arrowRight;
36 | break;
37 | }
38 | case 'arrowLeft': {
39 | icon = Icons.arrowLeft;
40 | break;
41 | }
42 | case 'strand': {
43 | icon = Icons.strand;
44 | break;
45 | }
46 | case 'noSecondary': {
47 | icon = Icons.noSecondary;
48 | break;
49 | }
50 | case 'helix': {
51 | icon = Icons.helix;
52 | break;
53 | }
54 | case 'turn': {
55 | icon = Icons.turn;
56 | break;
57 | }
58 | default: {
59 | // customizable icons (svg)
60 | icon = reg.icon;
61 | break;
62 | }
63 | }
64 |
65 | if (reg.display === 'center' && +key === reg.start + center) {
66 | rows[seq.id][key] = {char: icon};
67 | } else if (!reg.display) {
68 | rows[seq.id][key] = {char: icon};
69 | }
70 |
71 | }
72 | }
73 | // chars without icon
74 | if (!rows[seq.id][key]) {
75 | rows[seq.id][key] = {char: ''};
76 | }
77 | }
78 | }
79 | }
80 | }
81 |
82 | }
83 | const filteredRows = {};
84 | // tslint:disable-next-line:forin
85 | for (const row in rows) {
86 | let flag;
87 | const chars = rows[row];
88 | for (const char in rows[row]) {
89 | if (rows[row][char].char !== '') {
90 | flag = true;
91 | }
92 | }
93 |
94 | if (flag) {
95 | filteredRows[row] = chars;
96 | }
97 | }
98 | return filteredRows;
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/lib/icons.ts:
--------------------------------------------------------------------------------
1 | export class Icons {
2 | static lollipop = ' ';
3 | static arrowLeft = ' ';
4 | static arrowRight = ' ';
5 | static strand = ' ';
6 | static noSecondary = ' ';
7 | static helix = ' ';
8 | static turn = ' ';
9 | }
10 |
--------------------------------------------------------------------------------
/src/lib/interface.ts:
--------------------------------------------------------------------------------
1 | export interface Sequences {
2 | sequence: string,
3 | id?: number,
4 | label?: string,
5 | sequenceColor?: string,
6 | labelTooltip?: string,
7 | startIndex?: number
8 | }
9 |
10 | export interface Regions {
11 | sequenceId: number,
12 | start: number,
13 | end: number,
14 | backgroundColor?: string,
15 | color?: string,
16 | backgroundImage?: string
17 | }
18 |
19 |
20 | export interface Patterns {
21 | sequenceId: number,
22 | pattern: string,
23 | start?: number,
24 | end?: number,
25 | backgroundColor?: string,
26 | color?: string,
27 | backgroundImage?: string
28 | }
29 |
30 | export interface Icons {
31 | sequenceId: number,
32 | start: number,
33 | end: number,
34 | icon: string, // TODO check if placeholder or block (maybe to be implemented)
35 | display?: string, // center
36 | }
37 |
38 | export interface Options {
39 | fontSize?: string,
40 | chunkSize?: number, // number of chunk letters
41 | chunkSeparation?: number, // space between chunks
42 | viewerWidth?: string,
43 | indexesLocation?: string, // "top" / "lateral"
44 | wrapLine?: boolean,
45 | lineSeparation?: string, // margin bottom of horizontal rows
46 |
47 | sequenceColor?: string | {}, // colorscheme, introduce option for custom input
48 | selection?: string
49 | }
50 |
51 | export interface Consensus {
52 | color: string | {},
53 | dotThreshold?: number
54 | }
55 |
56 |
57 | export interface Input {
58 | consensus?: Consensus;
59 | sequences: Array,
60 | regions?: Array,
61 | patterns?: Array,
62 | icons?: Array,
63 | options?: Options,
64 | }
65 |
--------------------------------------------------------------------------------
/src/lib/options.model.ts:
--------------------------------------------------------------------------------
1 | export class OptionsModel {
2 |
3 | options = {
4 | fontSize: '14px',
5 | chunkSize: 10,
6 | chunkSeparation: 1, // relative to fontSize
7 | emptyFiller: ' ', // fills gap at the end of the MSA sequences
8 | indexesLocation: null,
9 | wrapLine: true,
10 | viewerWidth: '',
11 | dotThreshold: 90,
12 | lineSeparation: '5px',
13 | sequenceColor: undefined,
14 | customPalette: undefined,
15 | sequenceColorMatrix: undefined, // blosum
16 | sequenceColorMatrixPalette: undefined,
17 | consensusColorIdentity: undefined,
18 | consensusColorMapping: undefined,
19 | selection: undefined
20 | };
21 |
22 | process(opt, consensus) {
23 |
24 | /** check input fontSize */
25 | if (opt && opt.fontSize) {
26 | const fSize = opt.fontSize;
27 | const fNum = +fSize.substr(0, fSize.length - 2);
28 | const fUnit = fSize.substr(fSize.length - 2, 2);
29 |
30 | if (isNaN(fNum) || (fUnit !== 'px' && fUnit !== 'vw' && fUnit !== 'em')) {
31 | // wrong fontSize format
32 | } else {
33 | this.options.fontSize = fSize;
34 | }
35 | } else {
36 | // fontSize not set
37 | this.options.fontSize = '14px'; // default reset
38 | }
39 |
40 | /** check input chunkSize */
41 | if (opt && opt.chunkSize) {
42 |
43 | const cSize = +opt.chunkSize;
44 | if (isNaN(cSize) || cSize < 0) {
45 | // wrong chunkSize format
46 | } else {
47 | this.options.chunkSize = cSize;
48 | }
49 | }
50 |
51 | /** check input spaceSize */
52 | if (opt && opt.chunkSeparation) {
53 |
54 | const chunkSeparation = +opt.chunkSeparation;
55 | if (chunkSeparation >= 0) {
56 | this.options.chunkSeparation = chunkSeparation;
57 | }
58 | }
59 |
60 | if (opt && opt.chunkSize == 0) {
61 | this.options.chunkSize = 1;
62 | this.options.chunkSeparation = 0;
63 | }
64 |
65 | /** check indexesLocation value */
66 | if (opt && opt.indexesLocation) {
67 | if (opt.indexesLocation == "top" || opt.indexesLocation == "lateral") {
68 | this.options.indexesLocation = opt.indexesLocation;
69 | }
70 | }
71 |
72 | /** check selection value */
73 |
74 | if (opt && opt.selection) {
75 | if (opt.selection == "columnselection" || opt.selection == "areaselection") {
76 | this.options.selection = opt.selection;
77 | }
78 | }
79 |
80 | /** check sequenceColor value */
81 | if (opt && opt.sequenceColor) {
82 | if (typeof opt.sequenceColor !== 'string' ) {
83 | const keys = Object.keys(opt.sequenceColor);
84 |
85 | if(keys[0].length === 1) {
86 | this.options.sequenceColor = 'custom';
87 | this.options.customPalette = opt.sequenceColor;
88 | } else {
89 | this.options.sequenceColorMatrix = 'custom';
90 | this.options.sequenceColorMatrixPalette = opt.sequenceColor;
91 | }
92 | } else {
93 | if(opt.sequenceColor === "blosum62") {
94 | this.options.sequenceColorMatrix = opt.sequenceColor;
95 | } else if (opt.sequenceColor === "clustal") {
96 | this.options.sequenceColor = opt.sequenceColor;
97 | }
98 | }
99 | }
100 |
101 |
102 | /** check consensusType value */
103 | if (consensus && consensus.color) {
104 |
105 | if (typeof consensus.color !== 'string' ) {
106 | const keys = Object.keys(consensus.color);
107 | if(typeof (keys[0]) === 'string') {
108 | this.options.consensusColorIdentity = consensus.color
109 | } else {
110 | this.options.consensusColorMapping = consensus.color
111 | }
112 | } else {
113 | if(consensus.color === "identity") {
114 | this.options.consensusColorIdentity = consensus.color
115 | } else if (consensus.color === "physical") {
116 | this.options.consensusColorMapping = consensus.color
117 | }
118 | }
119 | }
120 |
121 |
122 | /** check consensusThreshold value */
123 | if (consensus && consensus.dotThreshold) {
124 | if (typeof consensus.dotThreshold == 'number') {
125 | this.options.dotThreshold = consensus.dotThreshold;
126 | }
127 | }
128 |
129 | /** check rowMarginBottom value */
130 | if (opt && opt.lineSeparation !== undefined) {
131 | const rSize = opt.lineSeparation;
132 | const rNum = +rSize.substr(0, rSize.length - 2);
133 | const rUnit = rSize.substr(rSize.length - 2, 2);
134 |
135 | if (isNaN(rNum) || (rUnit !== 'px' && rUnit !== 'vw' && rUnit !== 'em')) {
136 | // wrong lineSeparation format
137 | } else {
138 | this.options.lineSeparation = rSize;
139 | }
140 | } else {
141 | // lineSeparation not set
142 | this.options.lineSeparation = '5px'; // default reset
143 | }
144 |
145 | /** check wrapline value */
146 |
147 | if (opt && typeof opt.wrapLine == 'boolean') {
148 | this.options.wrapLine = opt.wrapLine;
149 | }
150 |
151 | /** check oneLineWidth */
152 | if (opt && opt.viewerWidth) {
153 | const viewerWidth = opt.viewerWidth;
154 | const olNum = +viewerWidth.substr(0, viewerWidth.length - 2);
155 | const olUnit = viewerWidth.substr(viewerWidth.length - 2, 2);
156 | if (isNaN(olNum) || (olUnit !== 'px' && olUnit !== 'vw' && olUnit !== 'em')) {
157 | // wrong oneLineWidth format
158 | } else {
159 | this.options.viewerWidth = viewerWidth;
160 | }
161 | }
162 |
163 | return this.options;
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/src/lib/palettes.ts:
--------------------------------------------------------------------------------
1 | export class Palettes {
2 |
3 |
4 | // AA propensities
5 | static clustal = {
6 | A: '#80a0f0', I: '#80a0f0', L: '#80a0f0', M: '#80a0f0', F: '#80a0f0', W: '#80a0f0', V: '#80a0f0',
7 | K: '#f01505', R: '#f01505', E: '#c048c0', D: '#c048c0', C: '#f08080', G: '#f09048',
8 | N: '#15c015', Q: '#15c015', S: '#15c015', T: '#15c015', P: '#c0c000', H: '#15a4a4', Y: '#15a4a4'
9 | };
10 |
11 | static zappo = {
12 | A: '#ffafaf', R: '#6464ff', N: '#00ff00', D: '#ff0000', C: '#ffff00', Q: '#00ff00',E: '#ff0000',
13 | G: '#ff00ff', H: '#6464ff', I: '#ffafaf', L: '#ffafaf',K: '#ffafaf', M: '#ffc800', F: '#ff00ff',
14 | P: '#00ff00', S: '#00ff00', T: '#15c015', W: '#ffc800', V: '#ffc800', Y: '#ffafaf'
15 | };
16 |
17 | static taylor = {
18 | A: '#ccff00', R: '#0000ff', N: '#cc00ff', D: '#ff0000', C: '#ffff00', Q: '#ff00cc',E: '#ff0066',
19 | G: '#ff9900', H: '#0066ff', I: '#66ff00', L: '#33ff00',K: '#6600ff', M: '#00ff00', F: '#00ff66',
20 | P: '#ffcc00', S: '#ff3300', T: '#ff6600', W: '#00ccff', V: '#00ffcc', Y: '#99ff00'
21 | };
22 |
23 | static hydrophobicity = {
24 | A: '#ad0052', R: '#0000ff', N: '#0c00f3', D: '#0c00f3', C: '#c2003d', Q: '#0c00f3',E: '#0c00f3',
25 | G: '#6a0095', H: '#1500ea', I: '#ff0000', L: '#ea0015',K: '#0000ff', M: '#b0004f', F: '#cb0034',
26 | P: '#4600b9', S: '#5e00a1', T: '#61009e', W: '#5b00a4', V: '#4f00b0', Y: '#f60009',
27 | B: '#0c00f3', X: '#680097', Z: '#0c00f3'
28 | };
29 | static helixpropensity = {
30 | A: '#e718e7', R: '#6f906f', N: '#1be41b', D: '#778877', C: '#23dc23', Q: '#926d92',E: '#ff00ff',
31 | G: '#00ff00', H: '#758a75', I: '#8a758a', L: '#ae51ae',K: '#a05fa0', M: '#ef10ef', F: '#986798',
32 | P: '#00ff00', S: '#36c936', T: '#47b847', W: '#8a758a', V: '#21de21', Y: '#857a85',
33 | B: '#49b649', X: '#758a75', Z: '#c936c9'
34 | };
35 | static strandpropensity = {
36 | A: '#5858a7', R: '#6b6b94', N: '#64649b', D: '#2121de', C: '#9d9d62', Q: '#8c8c73',E: '#0000ff',
37 | G: '#4949b6', H: '#60609f', I: '#ecec13', L: '#b2b24d',K: '#4747b8', M: '#82827d', F: '#c2c23d',
38 | P: '#2323dc', S: '#4949b6', T: '#9d9d62', W: '#c0c03f', V: '#d3d32c', Y: '#ffff00',
39 | B: '#4343bc', X: '#797986', Z: '#4747b8'
40 | };
41 | static turnpropensity = {
42 | A: '#2cd3d3', R: '#708f8f', N: '#ff0000', D: '#e81717', C: '#a85757', Q: '#3fc0c0',E: '#778888',
43 | G: '#ff0000', H: '#708f8f', I: '#00ffff', L: '#1ce3e3', K: '#7e8181', M: '#1ee1e1', F: '#1ee1e1',
44 | P: '#f60909', S: '#e11e1e', T: '#738c8c', W: '#738c8c', V: '#9d6262', Y: '#07f8f8',
45 | B: '#f30c0c', X: '#7c8383', Z: '#5ba4a4'
46 | };
47 |
48 | static buriedindex = {
49 | A: '#00a35c', R: '#00fc03', N: '#00eb14', D: '#00eb14', C: '#0000ff', Q: '#00f10e',E: '#00f10e',
50 | G: '#009d62', H: '#00d52a', I: '#0054ab', L: '#007b84', K: '#00ff00', M: '#009768', F: '#008778',
51 | P: '#00e01f', S: '#00d52a', T: '#00db24', W: '#00a857', V: '#00e619', Y: '#005fa0',
52 | B: '#00eb14', X: '#00b649', Z: '#00f10e'
53 | };
54 |
55 | static nucleotide = {
56 | A: '#64F73F', C: '#FFB340', G: '#EB413C', T: '#3C88EE', U: '#3C88EE'
57 | };
58 | static purinepyrimidine = {
59 | A: '#FF83FA', C: '#40E0D0', G: '#FF83FA', T: '#40E0D0', U: '#40E0D0', R: '#FF83FA', Y: '#40E0D0'
60 | };
61 |
62 |
63 | static consensusLevelsIdentity = {
64 | 100: ['#0A0A0A', '#FFFFFF'],
65 | 70: ['#333333', '#FFFFFF'],
66 | 40: ['#707070', '#FFFFFF'],
67 | 10: ['#A3A3A3', '#FFFFFF'],
68 | 0: ['#FFFFFF', '#FFFFFF']
69 | };
70 |
71 | // colour scheme in Lesk, Introduction to Bioinformatics
72 | static consensusAaLesk = {
73 | A: ['n', '#f09048', '#FFFFFF'], // n: small nonpolar
74 | G: ['n', '#f09048', '#FFFFFF'],
75 | S: ['n', '#f09048', '#FFFFFF'],
76 | T: ['n', '#f09048', '#FFFFFF'],
77 | C: ['h', '#558B6E', '#FFFFFF'], // h, hydrophobic
78 | V: ['h', '#558B6E', '#FFFFFF'],
79 | I: ['h', '#558B6E', '#FFFFFF'],
80 | L: ['h', '#558B6E', '#FFFFFF'],
81 | P: ['h', '#558B6E', '#FFFFFF'],
82 | F: ['h', '#558B6E', '#FFFFFF'],
83 | Y: ['h', '#558B6E', '#FFFFFF'],
84 | M: ['h', '#558B6E', '#FFFFFF'],
85 | W: ['h', '#558B6E', '#FFFFFF'],
86 | N: ['p', '#D4358D', '#FFFFFF'], // p: polar
87 | Q: ['p', '#D4358D', '#FFFFFF'],
88 | H: ['p', '#D4358D', '#FFFFFF'],
89 | D: ['~', '#A10702', '#FFFFFF'], // ~: negatively charged
90 | E: ['~', '#A10702', '#FFFFFF'],
91 | K: ['+', '#3626A7', '#FFFFFF'],
92 | R: ['+', '#3626A7', '#FFFFFF'] // +: positively charged
93 | }
94 |
95 | static substitutionMatrixBlosum = { WF: [ '#CFDBF2', '#000000'], QQ: [ '#A1B8E3', '#000000'],
96 | HH: [ '#7294D5', '#000000'], YY: [ '#81A0D9', '#000000'], ZZ: [ '#A1B8E3', '#000000'],
97 | CC: [ '#6288D0', '#000000'], PP: [ '#81A0D9', '#000000'], VI: [ '#B0C4E8', '#000000'],
98 | VM: [ '#CFDBF2', '#000000'], KK: [ '#A1B8E3', '#000000'], ZK: [ '#CFDBF2', '#000000'],
99 | DN: [ '#CFDBF2', '#000000'], SS: [ '#A1B8E3', '#000000'], QR: [ '#CFDBF2', '#000000'],
100 | NN: [ '#91ACDE', '#000000'], YF: [ '#B0C4E8', '#000000'], VL: [ '#CFDBF2', '#000000'],
101 | KR: [ '#C0CFED', '#000000'], ED: [ '#C0CFED', '#000000'], VV: [ '#A1B8E3', '#000000'],
102 | MI: [ '#CFDBF2', '#000000'], MM: [ '#A1B8E3', '#000000'], ZD: [ '#CFDBF2', '#000000'],
103 | FF: [ '#91ACDE', '#000000'], BD: [ '#A1B8E3', '#000000'], HN: [ '#CFDBF2', '#000000'],
104 | TT: [ '#A1B8E3', '#000000'], SN: [ '#CFDBF2', '#000000'], LL: [ '#A1B8E3', '#000000'],
105 | EQ: [ '#C0CFED', '#000000'], YW: [ '#C0CFED', '#000000'], EE: [ '#A1B8E3', '#000000'],
106 | KQ: [ '#CFDBF2', '#000000'], WW: [ '#3867BC', '#000000'], ML: [ '#C0CFED', '#000000'],
107 | KE: [ '#CFDBF2', '#000000'], ZE: [ '#A1B8E3', '#000000'], ZQ: [ '#B0C4E8', '#000000'],
108 | BE: [ '#CFDBF2', '#000000'], DD: [ '#91ACDE', '#000000'], SA: [ '#CFDBF2', '#000000'],
109 | YH: [ '#C0CFED', '#000000'], GG: [ '#91ACDE', '#000000'], AA: [ '#A1B8E3', '#000000'],
110 | II: [ '#A1B8E3', '#000000'], TS: [ '#CFDBF2', '#000000'], RR: [ '#A1B8E3', '#000000'],
111 | LI: [ '#C0CFED', '#000000'], ZB: [ '#CFDBF2', '#000000'], BN: [ '#B0C4E8', '#000000'],
112 | BB: [ '#A1B8E3', '#000000']
113 | };
114 | }
115 |
--------------------------------------------------------------------------------
/src/lib/patterns.model.ts:
--------------------------------------------------------------------------------
1 |
2 | export class PatternsModel {
3 |
4 | // find index of matched regex positions and create array of regions with color
5 | process(patterns, sequences) {
6 | if (!patterns) { return }
7 | const regions = []; // OutPatterns
8 | // @ts-ignore
9 | for (const element of patterns) {
10 | // tslint:disable-next-line:no-conditional-assignment
11 | const pattern = element.pattern;
12 | let str;
13 | if (sequences.find(x => x.id === element.sequenceId)) {
14 | str = sequences.find(x => x.id === element.sequenceId).sequence;
15 | if (element.start && element.end) {
16 | str = str.substr(element.start - 1, element.end - (element.start - 1));
17 | }
18 | this.regexMatch(str, pattern, regions, element);
19 | } else {
20 | for (const seq of sequences) {
21 | // regex
22 | if (element.start && element.end) {
23 | str = seq.sequence.substr(element.start - 1, element.end - (element.start - 1));
24 | }
25 | this.regexMatch(str, pattern, regions, element);
26 | }
27 | }
28 | }
29 | return regions;
30 |
31 | }
32 |
33 | regexMatch(str, pattern, regions, element) {
34 | const re = new RegExp(pattern, "g");
35 | let match;
36 | // tslint:disable-next-line:no-conditional-assignment
37 | while ((match = re.exec(str)) != null) {
38 | regions.push({start: +match.index + 1, end: +match.index + +match[0].length,
39 | backgroundColor: element.backgroundColor, color: element.color, backgroundImage: element.backgroundImage,
40 | borderColor: element.borderColor, borderStyle: element.borderStyle, sequenceId: element.sequenceId});
41 | }
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/src/lib/proseqviewer.ts:
--------------------------------------------------------------------------------
1 | import {OptionsModel} from './options.model';
2 | import {RowsModel} from './rows.model';
3 | import {ColorsModel} from './colors.model';
4 | import {SelectionModel} from './selection.model';
5 | import {IconsModel} from './icons.model';
6 | import {SequenceInfoModel} from './sequenceInfoModel';
7 | import {EventsModel} from './events.model';
8 | import {PatternsModel} from './patterns.model';
9 | import {ConsensusModel} from './consensus.model';
10 | import {Input} from './interface';
11 |
12 | export class ProSeqViewer {
13 | static sqvList = [];
14 | divId: string;
15 | init: boolean;
16 | params: OptionsModel;
17 | rows: RowsModel;
18 | consensus: ConsensusModel;
19 | regions: ColorsModel;
20 | patterns: PatternsModel;
21 | icons: IconsModel;
22 | labels: SequenceInfoModel;
23 | selection: SelectionModel;
24 | events: EventsModel;
25 |
26 | constructor(divId?: string ) {
27 | this.divId = divId;
28 | this.init = false;
29 | this.params = new OptionsModel();
30 | this.rows = new RowsModel();
31 | this.consensus = new ConsensusModel();
32 | this.regions = new ColorsModel();
33 | this.patterns = new PatternsModel();
34 | this.icons = new IconsModel();
35 | this.labels = new SequenceInfoModel();
36 | this.selection = new SelectionModel();
37 | this.events = new EventsModel();
38 |
39 |
40 |
41 | window.onresize = () => {
42 | this.calculateIdxs(false);
43 | };
44 |
45 |
46 |
47 | window.onclick = () => {
48 | this.calculateIdxs(true);
49 | }; // had to add this to cover mobidb toggle event
50 | }
51 |
52 |
53 | private calculateIdxs(flag) {
54 |
55 |
56 |
57 | for (const id of ProSeqViewer.sqvList) {
58 |
59 | if (document.getElementById(id) != null) {
60 | const sqvBody = document.getElementById(id);
61 | const chunks = sqvBody.getElementsByClassName('cnk');
62 |
63 | let oldTop = 0;
64 | let newTop = 1;
65 |
66 | for (let i = 0; i < chunks.length; i++) {
67 | // erase old indexes before recalculating them
68 | chunks[i].firstElementChild.className = 'idx hidden';
69 |
70 | if (flag) {
71 | // avoid calculating if idx already set
72 | if (chunks[i].firstElementChild.className === 'idx') {
73 | return;
74 | }
75 | }
76 |
77 | newTop = chunks[i].getBoundingClientRect().top + window.scrollY;
78 |
79 | if (newTop > oldTop) {
80 | chunks[i].firstElementChild.className = 'idx';
81 | oldTop = newTop;
82 | }
83 |
84 | }
85 | }
86 | }
87 |
88 | }
89 |
90 |
91 | public draw(inputs: Input) {
92 | const sqvBody = document.getElementById(this.divId);
93 | if (sqvBody) {
94 | sqvBody.innerHTML = ``;
95 | }
96 | ProSeqViewer.sqvList.push(this.divId);
97 |
98 | let labels;
99 | let labelsFlag;
100 | let startIndexes;
101 | let tooltips;
102 | let data;
103 |
104 | /** check and process parameters input */
105 | inputs.options = this.params.process(inputs.options, inputs.consensus);
106 |
107 | /** check and consensus input and global colorScheme */
108 | if (inputs.options){ [inputs.sequences, inputs.regions ] = this.consensus.process(inputs.sequences, inputs.regions, inputs.options); }
109 |
110 |
111 |
112 | /** check and process patterns input */
113 | inputs.patterns = this.patterns.process(inputs.patterns, inputs.sequences);
114 |
115 |
116 | /** check and process colors input */
117 | inputs.regions = this.regions.process(inputs);
118 |
119 |
120 | /** check and process icons input */
121 | let icons = this.icons.process(inputs.regions, inputs.sequences, inputs.icons);
122 |
123 | /** check and process sequences input */
124 | data = this.rows.process(inputs.sequences, icons, inputs.regions, inputs.options);
125 |
126 | /** check and process labels input */
127 | [ labels, startIndexes, tooltips, labelsFlag ] = this.labels.process(inputs.regions, inputs.sequences);
128 |
129 | /** create/update sqv-body html */
130 | this.createGUI(data, labels, startIndexes, tooltips, inputs.options, labelsFlag);
131 |
132 | /** listen copy paste events */
133 | this.selection.process();
134 |
135 | /** listen selection events */
136 | this.events.onRegionSelected();
137 | }
138 |
139 | private generateLabels(idx, labels, startIndexes, indexesLocation, chunkSize, fontSize, tooltips, data, lineSeparation) {
140 | let labelshtml = '';
141 | let labelsContainer = '';
142 | const noGapsLabels = [];
143 |
144 | if (labels.length > 0) {
145 | if (indexesLocation != 'lateral') {
146 | labelshtml += ` `;
147 | }
148 | let flag;
149 | let count = -1;
150 | let seqN = -1;
151 | for (const seqNum of data) {
152 | if (noGapsLabels.length < data.length) {
153 | noGapsLabels.push(0);
154 | }
155 | seqN += 1;
156 | for (const res in seqNum) {
157 | if (seqNum[res].char && seqNum[res].char.includes('svg')) {
158 | flag = true;
159 | break;
160 | }
161 | }
162 |
163 | if (flag) {
164 | noGapsLabels[seqN] = '';
165 | if (idx) {
166 | // line with only icons, no need for index
167 | labelshtml += ` ${noGapsLabels[seqN]} `;
168 | } else {
169 | labelshtml += ` `;
170 | }
171 |
172 | } else {
173 | count += 1;
174 | if (idx) {
175 | if (!chunkSize) {
176 | // lateral index regular
177 | labelshtml += `
178 | ${(startIndexes[count] - 1) + idx} `;
179 | } else {
180 | let noGaps = 0;
181 | for (const res in seqNum) {
182 | if (+res <= (idx) && seqNum[res].char !== '-') {
183 | noGaps += 1;
184 | }
185 | }
186 | // lateral index gap
187 | noGapsLabels[seqN] = noGaps;
188 | labelshtml += `
189 | ${(startIndexes[count] - 1) + noGapsLabels[seqN]} `;
190 | }
191 |
192 | } else {
193 | labelshtml += `${labels[count]}${tooltips[count]} `;
194 | }
195 | }
196 | flag = false;
197 | }
198 |
199 | if (indexesLocation == 'lateral' || 'both') {
200 | labelsContainer = `${labelshtml} `;
201 |
202 | } else {
203 | // add margin in case we only have labels and no indexes
204 | labelsContainer = `${labelshtml} `;
205 |
206 | }
207 |
208 | }
209 | return labelsContainer;
210 | }
211 |
212 | private addTopIndexes(chunkSize, x, maxTop, rowMarginBottom) {
213 | let cells = '';
214 | // adding top indexes
215 |
216 | let chunkTopIndex;
217 | if (x % chunkSize === 0 && x <= maxTop) {
218 | chunkTopIndex = `${x} `;
219 |
220 | } else {
221 | chunkTopIndex = `0 `;
222 | }
223 | cells += chunkTopIndex;
224 | return cells;
225 | }
226 |
227 | private createGUI(data, labels, startIndexes, tooltips, options, labelsFlag) {
228 |
229 | const sqvBody = document.getElementById(this.divId);
230 |
231 | // convert to nodes to improve rendering (idea to try):
232 | // Create new element
233 | // const root = document.createElement('div');
234 | // // Add class to element
235 | // root.className = 'my-new-element';
236 | // // Add color
237 | // root.style.color = 'red';
238 | // // Fill element with html
239 | // root.innerHTML = ``;
240 | // // Add element node to DOM graph
241 | // sqvBody.appendChild(root);
242 | // // Exit
243 | // return;
244 |
245 | if (!sqvBody) {
246 | // Cannot find sqv-body element
247 | return;
248 | }
249 |
250 | const chunkSize = options.chunkSize;
251 | const fontSize = options.fontSize;
252 | const chunkSeparation = options.chunkSeparation;
253 | const indexesLocation = options.indexesLocation;
254 | const wrapLine = options.wrapLine;
255 | const viewerWidth = options.viewerWidth;
256 | const lineSeparation = options.lineSeparation + ';';
257 | const fNum = +fontSize.substr(0, fontSize.length - 2);
258 | const fUnit = fontSize.substr(fontSize.length - 2, 2);
259 |
260 |
261 | // maxIdx = length of the longest sequence
262 | let maxIdx = 0;
263 | let maxTop = 0;
264 | for (const row of data) {
265 | if (maxIdx < Object.keys(row).length) { maxIdx = Object.keys(row).length; }
266 | if (maxTop < Object.keys(row).length) { maxTop = Object.keys(row).length; }
267 | }
268 |
269 | const lenghtIndex = maxIdx.toString().length;
270 | const indexWidth = (fNum * lenghtIndex).toString() + fUnit;
271 |
272 | // consider the last chunk even if is not long enough
273 | if (chunkSize > 0) { maxIdx += (chunkSize - (maxIdx % chunkSize)) % chunkSize; }
274 |
275 | // generate labels
276 | const labelsContainer = this.generateLabels(false, labels, startIndexes, indexesLocation, false, indexWidth, tooltips, data, lineSeparation);
277 |
278 | let index = '';
279 | let cards = '';
280 | let cell;
281 | let entity;
282 | let style;
283 | let html = '';
284 | let idxNum = 0;
285 | let idx;
286 | let cells = '';
287 | for (let x = 1; x <= maxIdx; x++) {
288 | if (indexesLocation != 'lateral') {cells = this.addTopIndexes(chunkSize, x, maxTop, lineSeparation)};
289 |
290 | for (let y = 0; y < data.length; y++) {
291 | entity = data[y][x];
292 | style = 'font-size: 1em;display:block;height:1em;line-height:1em;margin-bottom:' + lineSeparation;
293 | if (y === data.length - 1) { style = 'font-size: 1em;display:block;line-height:1em;margin-bottom:' + lineSeparation; }
294 | if (!entity) {
295 | // emptyfiller
296 | style = 'font-size: 1em;display:block;color: rgba(0, 0, 0, 0);height:1em;line-height:1em;margin-bottom:' + lineSeparation;
297 | cell = `A `; // mock char, this has to be done to have chunks all of the same length (last chunk can't be shorter)
298 | } else {
299 |
300 | if (entity.target) { style += `${entity.target}`; }
301 | if (entity.char && !entity.char.includes('svg')) {
302 | // y is the row, x is the column
303 | cell = `${entity.char} `;
305 | } else {
306 | style += '-webkit-user-select: none;';
307 | cell = `${entity.char} `;
308 | }
309 | }
310 | cells += cell;
311 | }
312 |
313 | cards += `${cells}
`; // width 3/5em to reduce white space around letters
314 | cells = '';
315 |
316 |
317 | if (chunkSize > 0 && x % chunkSize === 0) {
318 | // considering the row of top indexes
319 | if (indexesLocation != 'top') {
320 | idxNum += chunkSize; // lateral index (set only if top indexes missing)
321 | idx = idxNum - (chunkSize - 1);
322 | // adding labels
323 | const gapsContainer = this.generateLabels(idx, labels, startIndexes, indexesLocation, chunkSize, indexWidth, false, data, lineSeparation);
324 |
325 | if (labels[0] === '') {
326 | index = gapsContainer; // lateral number indexes
327 | } else {
328 | index = labelsContainer + gapsContainer; // lateral number indexes + labels
329 | }
330 |
331 |
332 | if (!labelsFlag) {
333 | index = gapsContainer; // lateral number indexes
334 | } else {
335 | index = labelsContainer + gapsContainer; // lateral number indexes + labels
336 | }
337 | } else {
338 | index = labelsContainer; // top
339 | }
340 |
341 | index = `${index}
`;
342 | style = `font-size: ${fontSize};`;
343 |
344 | if (x !== maxIdx) { style += 'padding-right: ' + chunkSeparation + 'em;'; } else { style += 'margin-right: ' + chunkSeparation + 'em;'; }
345 |
346 | let chunk = '';
347 |
348 | if (labelsFlag || options.consensusType || indexesLocation == 'both' || indexesLocation == 'lateral') { // both
349 | chunk = ``;
350 | } else {
351 | chunk = ``; // top
352 | }
353 | cards = '';
354 | index = '';
355 | html += chunk;
356 | }
357 |
358 | }
359 | let innerHTML;
360 |
361 |
362 | if (wrapLine) {
363 | innerHTML = ` ${html}
`;
364 |
365 | } else {
366 |
367 | innerHTML = ``;
370 |
371 | }
372 |
373 | sqvBody.innerHTML = innerHTML;
374 | window.dispatchEvent(new Event('resize'));
375 | }
376 |
377 |
378 | }
379 | // VERY IMPORTANT AND USEFUL TO BE ABLE TO HAVE A WORKING BUNDLE.JS!! NEVER DELETE THIS LINE
380 | (window as any).ProSeqViewer = ProSeqViewer;
381 |
--------------------------------------------------------------------------------
/src/lib/rows.model.ts:
--------------------------------------------------------------------------------
1 | import {Palettes} from './palettes';
2 | import {ColorsModel} from './colors.model';
3 |
4 | export class RowsModel {
5 | substitutiveId = 99999999999999;
6 |
7 | private processRows(rows, icons, regions, opt) {
8 |
9 | const allData = [];
10 |
11 | // decide which color is more important in case of overwriting
12 | const coloringOrder = ['custom', 'clustal', 'zappo', 'gradient', 'binary'];
13 |
14 | // order row Numbers
15 | const rowNumsOrdered = Object.keys(rows).map(Number).sort((n1, n2) => n1 - n2);
16 |
17 | // order keys of Row object
18 | const ordered = {};
19 | for (const rowNum of rowNumsOrdered) {
20 | ordered[rowNum] = Object.keys(rows[+rowNum]).map(Number).sort((n1, n2) => n1 - n2);
21 | }
22 |
23 | let data;
24 | let coloringRowNums;
25 | let tmp;
26 | // loop through data rows
27 | for (const rowNum of rowNumsOrdered) {
28 | tmp = ordered[rowNum];
29 | // data key: indexes, value: chars
30 | data = rows[rowNum];
31 | // data[rowNum].label = this.rows.getLabel(rowNum, this.sequences);
32 | // console.log(data)
33 | if (regions) {
34 | for (const coloring of coloringOrder.reverse()) {
35 | coloringRowNums = ColorsModel.getRowsList(coloring).map(Number);
36 | // if there is coloring for the data row
37 | if (coloringRowNums.indexOf(rowNum) < 0) {
38 | // go to next coloring
39 | continue;
40 | }
41 |
42 | const positions = ColorsModel.getPositions(coloring, rowNum);
43 | // positions = start, end, target (bgcolor || fgcolor)
44 | if (positions.length > 0) {
45 | for (const e of positions) {
46 | for (let i = e.start; i <= e.end; i++) {
47 | if (!data[i]) {
48 | continue;
49 | }
50 |
51 | if (e.backgroundColor && !e.backgroundColor.startsWith('#')) { // is a palette
52 | if (e.backgroundColor == 'custom') {
53 | data[i].backgroundColor = opt.customPalette[data[i].char];
54 | } else {
55 | data[i].backgroundColor = Palettes[e.backgroundColor][data[i].char]; // e.backgroundcolor = zappo, clustal..
56 | }
57 | } else {
58 | data[i].backgroundColor = e.backgroundColor; // is a region or pattern
59 | }
60 | data[i].target = e.target + 'background-color:' + data[i].backgroundColor;
61 | }
62 | }
63 | }
64 | }
65 | if (icons !== {}) {
66 | const iconsData = icons[rowNum];
67 | if (iconsData) { allData.push(iconsData); }
68 | }
69 | }
70 | allData.push(data);
71 | }
72 | return allData;
73 | }
74 |
75 | process(sequences, icons, regions, opt) {
76 |
77 | // check and set global sequenceColor
78 | if (opt && opt.sequenceColor) {
79 | // @ts-ignore
80 | for (const sequence of sequences) {
81 | if (!sequence.sequenceColor) {
82 | sequence.sequenceColor = opt.sequenceColor;
83 | }
84 | }
85 | }
86 |
87 | // keep previous data
88 | if (!sequences) { return; }
89 |
90 | // reset data
91 | const rows = {};
92 |
93 | // check if there are undefined or duplicate ids and prepare to reset them
94 | const values = [];
95 | let undefinedValues = 0;
96 |
97 | for (const r of Object.keys(sequences)) {
98 |
99 | if (isNaN(+sequences[r].id)) {
100 | // missing id
101 | undefinedValues += 1;
102 | sequences[r].id = this.substitutiveId;
103 | this.substitutiveId -= 1;
104 | // otherwise just reset missing ids and log the resetted id
105 | } else {
106 | if (values.includes(+sequences[r].id)) {
107 | // Duplicate sequence id
108 | delete sequences[r];
109 | } else {
110 | values.push(+sequences[r].id);
111 | }
112 | }
113 | }
114 |
115 | for (const row of Object.keys(sequences)) {
116 |
117 | /** check sequences id type */
118 | let id;
119 | if (isNaN(+sequences[row].id)) {
120 | id = values.sort()[values.length - 1] + 1;
121 | } else {
122 | id = sequences[row].id;
123 | }
124 |
125 | /** set row chars */
126 | rows[id] = {};
127 | for (const idx of Object.keys(sequences[row].sequence)) {
128 | const idxKey = (+idx + 1).toString();
129 | const char = sequences[row].sequence[idx];
130 | rows[id][idxKey] = {char};
131 | }
132 | }
133 | return this.processRows(rows, icons, regions, opt);
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/lib/selection.model.ts:
--------------------------------------------------------------------------------
1 | import {start} from "repl";
2 |
3 | interface Cell {
4 | x: string;
5 | y: string;
6 | char?: string;
7 | sequenceId?: number;
8 | sqvId: string;
9 | }
10 |
11 | export class SelectionModel {
12 |
13 | start: Cell;
14 | lastOver: Cell;
15 | lastSqv;
16 | lastId;
17 | firstOver;
18 | event_sequence = [];
19 |
20 |
21 | private set_start(e) {
22 | let id;
23 | let element;
24 | if (e.path) {
25 | // chrome support
26 | element = e.path[0];
27 | id = document.getElementById(element.dataset.resId);
28 | } else {
29 | // firefox support
30 | element = e.originalTarget;
31 | id = document.getElementById(element.dataset.resId);
32 | }
33 | this.lastId = element.dataset.resId;
34 | this.lastSqv = id;
35 |
36 |
37 | this.start = {y: element.dataset.resY, x: element.dataset.resX, sqvId: element.dataset.resId};
38 | this.lastOver = {y: element.dataset.resY, x: element.dataset.resX, sqvId: element.dataset.resId};
39 |
40 | const elements = document.querySelectorAll('[data-res-id=' + element.dataset.resId + ']');
41 | this.selectionhighlight(elements);
42 | this.firstOver = false;
43 |
44 | }
45 |
46 | private selectionhighlight(elements) {
47 |
48 | for (const selection of elements) {
49 | const x = +selection.getAttribute('data-res-x');
50 | const y = +selection.getAttribute('data-res-y');
51 |
52 |
53 | let firstX = Math.min(+this.start.x, +this.lastOver.x);
54 | let lastX = Math.max(+this.start.x, +this.lastOver.x);
55 | let firstY = Math.min(+this.start.y, +this.lastOver.y);
56 | let lastY = Math.max(+this.start.y, +this.lastOver.y);
57 |
58 |
59 | // on every drag reselect the whole area ...
60 | if (x >= +firstX && x <= +lastX &&
61 | y >= +firstY && y <= +lastY &&
62 | selection.getAttribute('data-res-id') === this.lastOver.sqvId ) {
63 | selection.classList.add('highlight');
64 | } else {
65 | selection.classList.remove('highlight');
66 | }
67 | }
68 | }
69 |
70 | public process() {
71 | const sequenceViewers = document.getElementsByClassName('cell');
72 |
73 |
74 |
75 |
76 | // remove selection on new click
77 | window.onmousedown = (event) => {
78 |
79 | this.event_sequence.push(0);
80 |
81 |
82 | // @ts-ignore
83 | for (const sqv of sequenceViewers) {
84 | sqv.onmousedown = (e) => {
85 | this.set_start(e);
86 | }
87 | }
88 |
89 | if (this.event_sequence[0] == 0 && this.event_sequence[1] == 1 && this.event_sequence[2] == 2 && this.event_sequence[0]== 0) {
90 | // left click
91 |
92 | const elements = document.querySelectorAll('[data-res-id=' + this.lastId + ']');
93 | // @ts-ignore
94 | for (const selection of elements) {
95 | selection.classList.remove('highlight');
96 | }
97 |
98 | }
99 |
100 | // if first click outside sqvDiv (first if is valid in Chrome, second in firefox)
101 | if (!event.target.dataset.resX) {
102 | this.firstOver = true;
103 | }
104 | if (event.explicitOriginalTarget && event.explicitOriginalTarget.dataset) {
105 | this.firstOver = true;
106 | }
107 |
108 | this.event_sequence = [0];
109 |
110 | };
111 |
112 |
113 |
114 | // @ts-ignore
115 | for (const sqv of sequenceViewers) {
116 |
117 | sqv.onmouseover = (e: any) => {
118 |
119 | if (!(1 in this.event_sequence)){
120 | this.event_sequence.push(1);
121 | }
122 |
123 | if(this.firstOver) {
124 |
125 | this.set_start(e);
126 | }
127 |
128 | let element;
129 | if (e.path) { element = e.path[0]; } else { element = e.originalTarget; }
130 |
131 | if (this.start) {
132 |
133 | this.lastOver = {y: element.dataset.resY, x: element.dataset.resX, sqvId: element.dataset.resId};
134 |
135 | const elements = document.querySelectorAll('[data-res-id=' + element.dataset.resId + ']');
136 | if (this.lastId == element.dataset.resId) {
137 | this.selectionhighlight(elements);
138 | }
139 |
140 | }
141 |
142 | };
143 | }
144 |
145 |
146 | document.body.onmouseup = () => {
147 | this.event_sequence.push(2);
148 | this.firstOver = false;
149 | if (this.start) {
150 | this.start = undefined;
151 | }
152 | if (this.event_sequence[0] == 0 && this.event_sequence[1] == 2) {
153 | const elements = document.querySelectorAll('[data-res-id=' + this.lastId + ']');
154 | // @ts-ignore
155 | for (const selection of elements) {
156 | selection.classList.remove('highlight');
157 | }
158 | }
159 |
160 | };
161 |
162 | document.body.addEventListener('keydown', (e: any) => {
163 | const elements = document.querySelectorAll('[data-res-id=' + this.lastId + ']');
164 | // @ts-ignore
165 | e = e || window.event;
166 | const key = e.which || e.keyCode; // keyCode detection
167 | const ctrl = e.ctrlKey ? e.ctrlKey : ((key === 17)); // ctrl detection
168 | if ( key === 67 && ctrl ) {
169 | let textToPaste = '';
170 | const textDict = {};
171 | let row = '';
172 | // tslint:disable-next-line:forin
173 | // @ts-ignore
174 | for ( const selection of elements) {
175 | if (selection.classList.contains('highlight')) {
176 | if (!textDict[selection.getAttribute('data-res-y')]) {
177 | textDict[selection.getAttribute('data-res-y')] = '';
178 | }
179 | // new line when new row
180 | if (selection.getAttribute('data-res-y') !== row && row !== '') {
181 | textDict[selection.getAttribute('data-res-y')] += selection.innerText;
182 | } else {
183 | textDict[selection.getAttribute('data-res-y')] += selection.innerText;
184 | }
185 | row = selection.getAttribute('data-res-y');
186 | }
187 | }
188 |
189 | let flag;
190 | for (const textRow in textDict) {
191 | if (flag) {
192 | textToPaste += '\n' + textDict[textRow];
193 | } else {
194 | textToPaste += textDict[textRow];
195 | flag = true;
196 | }
197 | }
198 | if (textToPaste !== '') {
199 | // copy to clipboard for the paste event
200 | const dummy = document.createElement('textarea');
201 | document.body.appendChild(dummy);
202 | dummy.value = textToPaste;
203 |
204 |
205 | dummy.select();
206 | document.execCommand('copy');
207 | document.body.removeChild(dummy);
208 |
209 |
210 | const evt = new CustomEvent('onHighlightSelection', {detail: {text: textToPaste, eventType: 'highlight selection'}} );
211 | window.dispatchEvent(evt);
212 | }
213 | }
214 | }, false);
215 |
216 |
217 | }}
218 |
--------------------------------------------------------------------------------
/src/lib/sequenceInfoModel.ts:
--------------------------------------------------------------------------------
1 | export class SequenceInfoModel {
2 |
3 | process(regions, sequences) {
4 | const labels = [];
5 | const startIndexes = [];
6 | const tooltips = [];
7 | let flag;
8 | sequences.sort((a, b) => a.id - b.id);
9 | for (const seq of sequences) {
10 | if (!seq) { continue; }
11 | if (seq.startIndex) {
12 | startIndexes.push(seq.startIndex);
13 | } else {
14 | startIndexes.push(1);
15 | }
16 | if (seq.labelTooltip) {
17 | tooltips.push(seq.labelTooltip);
18 | } else {
19 | tooltips.push(' ');
20 | }
21 | if (seq.label && !this.isHTML(seq.label) ) {
22 | labels.push(seq.label);
23 | flag = true // to check if I have at least one label
24 | } else {
25 | labels.push('');
26 | }
27 | }
28 |
29 | return [labels, startIndexes, tooltips, flag];
30 | }
31 |
32 | isHTML = (str) => {
33 | const fragment = document.createRange().createContextualFragment(str);
34 |
35 | // remove all non text nodes from fragment
36 | fragment.querySelectorAll('*').forEach(el => el.parentNode.removeChild(el));
37 |
38 | // if there is textContent, then not a pure HTML
39 | return !(fragment.textContent || '').trim();
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "es2015",
5 | "declaration": true,
6 | "outDir": "./dist",
7 | "lib": [
8 | "es6",
9 | "dom",
10 | "es2017"
11 | ],
12 | "baseUrl": ".",
13 | "paths": {
14 | "assets/*": [
15 | "assets/*"
16 | ]
17 | }
18 | },
19 | "include": [
20 | "src/index.ts",
21 | "src/lib/*"
22 | ],
23 | "exclude": [
24 | "node_modules"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | entry: './dist/index.js',
3 | output: {
4 | filename: 'sqv-bundle.js'
5 | },
6 | optimization: {
7 | minimize: false
8 | },
9 | mode: "production"
10 | };
11 |
--------------------------------------------------------------------------------