├── .gitignore
├── .npmignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── example.html
├── example.scss
├── example.ts
├── icons
├── icons8-chevron-down-48.png
├── icons8-chevron-right-48.png
├── icons8-download-48.png
├── icons8-help-48.png
└── icons8-level-up-48.png
├── index.ts
├── package-lock.json
├── package.json
├── src
├── calculate.ts
├── commons.ts
├── custom-d3.ts
├── feature-viewer.ts
├── fillsvg.ts
├── helper.ts
├── interfaces.ts
├── styles
│ └── styles.scss
├── tooltip.ts
└── transition.ts
├── tsconfig.json
└── webpack.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Exclude WebStorm project files
2 | .idea
3 | # Exclude node modules
4 | node_modules
5 | # Exclude built project
6 | dist
7 | # Ignore types
8 | **/*.d.ts
9 | # Ignore .js files
10 | **/*.js.map
11 | src/*.js
12 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | ## Getting started
4 |
5 | Before you begin:
6 | - Check out the [existing issues](https://github.com/BioComputingUP/feature-viewer-typescript/issues).
7 |
8 | ### Don't see your issue? Open one
9 |
10 | If you spot something new, open an issue. We'll use the issue to have a conversation about the problem you want to fix.
11 |
12 | ### Ready to make a change? Fork the repo
13 |
14 | Fork using GitHub Desktop:
15 |
16 | - [Getting started with GitHub Desktop](https://docs.github.com/en/desktop/installing-and-configuring-github-desktop/getting-started-with-github-desktop) will guide you through setting up Desktop.
17 | - Once Desktop is set up, you can use it to [fork the repo](https://docs.github.com/en/desktop/contributing-and-collaborating-using-github-desktop/cloning-and-forking-repositories-from-github-desktop)!
18 |
19 | Fork using the command line:
20 |
21 | - [Fork the repo](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo#fork-an-example-repository) so that you can make your changes without affecting the original project until you're ready to merge them.
22 |
23 | Fork with [GitHub Codespaces](https://github.com/features/codespaces):
24 |
25 | - [Fork, edit, and preview](https://docs.github.com/en/free-pro-team@latest/github/developing-online-with-codespaces/creating-a-codespace) using [GitHub Codespaces](https://github.com/features/codespaces) without having to install and run the project locally.
26 |
27 | ### Make your update:
28 | Make your changes to the file(s) you'd like to update. Here are some tips and tricks for [using the docs codebase](#working-in-the-githubdocs-repository).
29 | - Are you making changes to the application code? You'll need **Node.js lts/dubnium** to run the library locally.
30 |
31 | ### Open a pull request
32 | When you're done making changes and you'd like to propose them for review, open your pull request (PR).
33 |
34 | ### Submit your PR & get it reviewed
35 | - Once you submit your PR, we will review it with you. The first thing you're going to want to do is a [self review](#self-review).
36 | - After that, we may have questions, check back on your PR to keep up with the conversation.
37 |
38 | ### Your PR is merged!
39 | Congratulations! The whole GitHub community thanks you. :sparkles:
40 | Once your PR is merged, you will be proudly listed as a contributor in the [contributor chart](https://github.com/BioComputingUP/feature-viewer-typescript/graphs/contributors).
41 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015, SIB Swiss Institute of Bioinformatics
2 |
3 | This program is free software; you can redistribute it and/or modify it under
4 | the terms of the GNU General Public License as published by the Free Software Foundation;
5 | either version 2 of the License, or (at your option) any later version.
6 |
7 |
8 | GNU GENERAL PUBLIC LICENSE
9 | Version 2, June 1991
10 |
11 |
12 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
13 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
14 | Everyone is permitted to copy and distribute verbatim copies
15 | of this license document, but changing it is not allowed.
16 |
17 | Preamble
18 |
19 | The licenses for most software are designed to take away your
20 | freedom to share and change it. By contrast, the GNU General Public
21 | License is intended to guarantee your freedom to share and change free
22 | software--to make sure the software is free for all its users. This
23 | General Public License applies to most of the Free Software
24 | Foundation's software and to any other program whose authors commit to
25 | using it. (Some other Free Software Foundation software is covered by
26 | the GNU Lesser General Public License instead.) You can apply it to
27 | your programs, too.
28 |
29 | When we speak of free software, we are referring to freedom, not
30 | price. Our General Public Licenses are designed to make sure that you
31 | have the freedom to distribute copies of free software (and charge for
32 | this service if you wish), that you receive source code or can get it
33 | if you want it, that you can change the software or use pieces of it
34 | in new free programs; and that you know you can do these things.
35 |
36 | To protect your rights, we need to make restrictions that forbid
37 | anyone to deny you these rights or to ask you to surrender the rights.
38 | These restrictions translate to certain responsibilities for you if you
39 | distribute copies of the software, or if you modify it.
40 |
41 | For example, if you distribute copies of such a program, whether
42 | gratis or for a fee, you must give the recipients all the rights that
43 | you have. You must make sure that they, too, receive or can get the
44 | source code. And you must show them these terms so they know their
45 | rights.
46 |
47 | We protect your rights with two steps: (1) copyright the software, and
48 | (2) offer you this license which gives you legal permission to copy,
49 | distribute and/or modify the software.
50 |
51 | Also, for each author's protection and ours, we want to make certain
52 | that everyone understands that there is no warranty for this free
53 | software. If the software is modified by someone else and passed on, we
54 | want its recipients to know that what they have is not the original, so
55 | that any problems introduced by others will not reflect on the original
56 | authors' reputations.
57 |
58 | Finally, any free program is threatened constantly by software
59 | patents. We wish to avoid the danger that redistributors of a free
60 | program will individually obtain patent licenses, in effect making the
61 | program proprietary. To prevent this, we have made it clear that any
62 | patent must be licensed for everyone's free use or not licensed at all.
63 |
64 | The precise terms and conditions for copying, distribution and
65 | modification follow.
66 |
67 | GNU GENERAL PUBLIC LICENSE
68 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
69 |
70 | 0. This License applies to any program or other work which contains
71 | a notice placed by the copyright holder saying it may be distributed
72 | under the terms of this General Public License. The "Program", below,
73 | refers to any such program or work, and a "work based on the Program"
74 | means either the Program or any derivative work under copyright law:
75 | that is to say, a work containing the Program or a portion of it,
76 | either verbatim or with modifications and/or translated into another
77 | language. (Hereinafter, translation is included without limitation in
78 | the term "modification".) Each licensee is addressed as "you".
79 |
80 | Activities other than copying, distribution and modification are not
81 | covered by this License; they are outside its scope. The act of
82 | running the Program is not restricted, and the output from the Program
83 | is covered only if its contents constitute a work based on the
84 | Program (independent of having been made by running the Program).
85 | Whether that is true depends on what the Program does.
86 |
87 | 1. You may copy and distribute verbatim copies of the Program's
88 | source code as you receive it, in any medium, provided that you
89 | conspicuously and appropriately publish on each copy an appropriate
90 | copyright notice and disclaimer of warranty; keep intact all the
91 | notices that refer to this License and to the absence of any warranty;
92 | and give any other recipients of the Program a copy of this License
93 | along with the Program.
94 |
95 | You may charge a fee for the physical act of transferring a copy, and
96 | you may at your option offer warranty protection in exchange for a fee.
97 |
98 | 2. You may modify your copy or copies of the Program or any portion
99 | of it, thus forming a work based on the Program, and copy and
100 | distribute such modifications or work under the terms of Section 1
101 | above, provided that you also meet all of these conditions:
102 |
103 | a) You must cause the modified files to carry prominent notices
104 | stating that you changed the files and the date of any change.
105 |
106 | b) You must cause any work that you distribute or publish, that in
107 | whole or in part contains or is derived from the Program or any
108 | part thereof, to be licensed as a whole at no charge to all third
109 | parties under the terms of this License.
110 |
111 | c) If the modified program normally reads commands interactively
112 | when run, you must cause it, when started running for such
113 | interactive use in the most ordinary way, to print or display an
114 | announcement including an appropriate copyright notice and a
115 | notice that there is no warranty (or else, saying that you provide
116 | a warranty) and that users may redistribute the program under
117 | these conditions, and telling the user how to view a copy of this
118 | License. (Exception: if the Program itself is interactive but
119 | does not normally print such an announcement, your work based on
120 | the Program is not required to print an announcement.)
121 |
122 | These requirements apply to the modified work as a whole. If
123 | identifiable sections of that work are not derived from the Program,
124 | and can be reasonably considered independent and separate works in
125 | themselves, then this License, and its terms, do not apply to those
126 | sections when you distribute them as separate works. But when you
127 | distribute the same sections as part of a whole which is a work based
128 | on the Program, the distribution of the whole must be on the terms of
129 | this License, whose permissions for other licensees extend to the
130 | entire whole, and thus to each and every part regardless of who wrote it.
131 |
132 | Thus, it is not the intent of this section to claim rights or contest
133 | your rights to work written entirely by you; rather, the intent is to
134 | exercise the right to control the distribution of derivative or
135 | collective works based on the Program.
136 |
137 | In addition, mere aggregation of another work not based on the Program
138 | with the Program (or with a work based on the Program) on a volume of
139 | a storage or distribution medium does not bring the other work under
140 | the scope of this License.
141 |
142 | 3. You may copy and distribute the Program (or a work based on it,
143 | under Section 2) in object code or executable form under the terms of
144 | Sections 1 and 2 above provided that you also do one of the following:
145 |
146 | a) Accompany it with the complete corresponding machine-readable
147 | source code, which must be distributed under the terms of Sections
148 | 1 and 2 above on a medium customarily used for software interchange; or,
149 |
150 | b) Accompany it with a written offer, valid for at least three
151 | years, to give any third party, for a charge no more than your
152 | cost of physically performing source distribution, a complete
153 | machine-readable copy of the corresponding source code, to be
154 | distributed under the terms of Sections 1 and 2 above on a medium
155 | customarily used for software interchange; or,
156 |
157 | c) Accompany it with the information you received as to the offer
158 | to distribute corresponding source code. (This alternative is
159 | allowed only for noncommercial distribution and only if you
160 | received the program in object code or executable form with such
161 | an offer, in accord with Subsection b above.)
162 |
163 | The source code for a work means the preferred form of the work for
164 | making modifications to it. For an executable work, complete source
165 | code means all the source code for all modules it contains, plus any
166 | associated interface definition files, plus the scripts used to
167 | control compilation and installation of the executable. However, as a
168 | special exception, the source code distributed need not include
169 | anything that is normally distributed (in either source or binary
170 | form) with the major components (compiler, kernel, and so on) of the
171 | operating system on which the executable runs, unless that component
172 | itself accompanies the executable.
173 |
174 | If distribution of executable or object code is made by offering
175 | access to copy from a designated place, then offering equivalent
176 | access to copy the source code from the same place counts as
177 | distribution of the source code, even though third parties are not
178 | compelled to copy the source along with the object code.
179 |
180 | 4. You may not copy, modify, sublicense, or distribute the Program
181 | except as expressly provided under this License. Any attempt
182 | otherwise to copy, modify, sublicense or distribute the Program is
183 | void, and will automatically terminate your rights under this License.
184 | However, parties who have received copies, or rights, from you under
185 | this License will not have their licenses terminated so long as such
186 | parties remain in full compliance.
187 |
188 | 5. You are not required to accept this License, since you have not
189 | signed it. However, nothing else grants you permission to modify or
190 | distribute the Program or its derivative works. These actions are
191 | prohibited by law if you do not accept this License. Therefore, by
192 | modifying or distributing the Program (or any work based on the
193 | Program), you indicate your acceptance of this License to do so, and
194 | all its terms and conditions for copying, distributing or modifying
195 | the Program or works based on it.
196 |
197 | 6. Each time you redistribute the Program (or any work based on the
198 | Program), the recipient automatically receives a license from the
199 | original licensor to copy, distribute or modify the Program subject to
200 | these terms and conditions. You may not impose any further
201 | restrictions on the recipients' exercise of the rights granted herein.
202 | You are not responsible for enforcing compliance by third parties to
203 | this License.
204 |
205 | 7. If, as a consequence of a court judgment or allegation of patent
206 | infringement or for any other reason (not limited to patent issues),
207 | conditions are imposed on you (whether by court order, agreement or
208 | otherwise) that contradict the conditions of this License, they do not
209 | excuse you from the conditions of this License. If you cannot
210 | distribute so as to satisfy simultaneously your obligations under this
211 | License and any other pertinent obligations, then as a consequence you
212 | may not distribute the Program at all. For example, if a patent
213 | license would not permit royalty-free redistribution of the Program by
214 | all those who receive copies directly or indirectly through you, then
215 | the only way you could satisfy both it and this License would be to
216 | refrain entirely from distribution of the Program.
217 |
218 | If any portion of this section is held invalid or unenforceable under
219 | any particular circumstance, the balance of the section is intended to
220 | apply and the section as a whole is intended to apply in other
221 | circumstances.
222 |
223 | It is not the purpose of this section to induce you to infringe any
224 | patents or other property right claims or to contest validity of any
225 | such claims; this section has the sole purpose of protecting the
226 | integrity of the free software distribution system, which is
227 | implemented by public license practices. Many people have made
228 | generous contributions to the wide range of software distributed
229 | through that system in reliance on consistent application of that
230 | system; it is up to the author/donor to decide if he or she is willing
231 | to distribute software through any other system and a licensee cannot
232 | impose that choice.
233 |
234 | This section is intended to make thoroughly clear what is believed to
235 | be a consequence of the rest of this License.
236 |
237 | 8. If the distribution and/or use of the Program is restricted in
238 | certain countries either by patents or by copyrighted interfaces, the
239 | original copyright holder who places the Program under this License
240 | may add an explicit geographical distribution limitation excluding
241 | those countries, so that distribution is permitted only in or among
242 | countries not thus excluded. In such case, this License incorporates
243 | the limitation as if written in the body of this License.
244 |
245 | 9. The Free Software Foundation may publish revised and/or new versions
246 | of the General Public License from time to time. Such new versions will
247 | be similar in spirit to the present version, but may differ in detail to
248 | address new problems or concerns.
249 |
250 | Each version is given a distinguishing version number. If the Program
251 | specifies a version number of this License which applies to it and "any
252 | later version", you have the option of following the terms and conditions
253 | either of that version or of any later version published by the Free
254 | Software Foundation. If the Program does not specify a version number of
255 | this License, you may choose any version ever published by the Free Software
256 | Foundation.
257 |
258 | 10. If you wish to incorporate parts of the Program into other free
259 | programs whose distribution conditions are different, write to the author
260 | to ask for permission. For software which is copyrighted by the Free
261 | Software Foundation, write to the Free Software Foundation; we sometimes
262 | make exceptions for this. Our decision will be guided by the two goals
263 | of preserving the free status of all derivatives of our free software and
264 | of promoting the sharing and reuse of software generally.
265 |
266 | NO WARRANTY
267 |
268 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
269 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
270 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
271 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
272 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
273 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
274 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
275 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
276 | REPAIR OR CORRECTION.
277 |
278 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
279 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
280 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
281 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
282 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
283 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
284 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
285 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
286 | POSSIBILITY OF SUCH DAMAGES.
287 |
288 | END OF TERMS AND CONDITIONS
289 |
290 | How to Apply These Terms to Your New Programs
291 |
292 | If you develop a new program, and you want it to be of the greatest
293 | possible use to the public, the best way to achieve this is to make it
294 | free software which everyone can redistribute and change under these terms.
295 |
296 | To do so, attach the following notices to the program. It is safest
297 | to attach them to the start of each source file to most effectively
298 | convey the exclusion of warranty; and each file should have at least
299 | the "copyright" line and a pointer to where the full notice is found.
300 |
301 | {description}
302 | Copyright (C) {year} {fullname}
303 |
304 | This program is free software; you can redistribute it and/or modify
305 | it under the terms of the GNU General Public License as published by
306 | the Free Software Foundation; either version 2 of the License, or
307 | (at your option) any later version.
308 |
309 | This program is distributed in the hope that it will be useful,
310 | but WITHOUT ANY WARRANTY; without even the implied warranty of
311 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
312 | GNU General Public License for more details.
313 |
314 | You should have received a copy of the GNU General Public License along
315 | with this program; if not, write to the Free Software Foundation, Inc.,
316 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
317 |
318 | Also add information on how to contact you by electronic and paper mail.
319 |
320 | If the program is interactive, make it output a short notice like this
321 | when it starts in an interactive mode:
322 |
323 | Gnomovision version 69, Copyright (C) year name of author
324 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
325 | This is free software, and you are welcome to redistribute it
326 | under certain conditions; type `show c' for details.
327 |
328 | The hypothetical commands `show w' and `show c' should show the appropriate
329 | parts of the General Public License. Of course, the commands you use may
330 | be called something other than `show w' and `show c'; they could even be
331 | mouse-clicks or menu items--whatever suits your program.
332 |
333 | You should also get your employer (if you work as a programmer) or your
334 | school, if any, to sign a "copyright disclaimer" for the program, if
335 | necessary. Here is a sample; alter the names:
336 |
337 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
338 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
339 |
340 | {signature of Ty Coon}, 1 April 1989
341 | Ty Coon, President of Vice
342 |
343 | This General Public License does not permit incorporating your program into
344 | proprietary programs. If your program is a subroutine library, you may
345 | consider it more useful to permit linking proprietary applications with the
346 | library. If this is what you want to do, use the GNU Lesser General
347 | Public License instead of this License.
348 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🚀 Welcome to your new awesome project!
2 |
3 | This project has been created using **webpack-cli**, you can now run
4 |
5 | ```
6 | npm run build
7 | ```
8 |
9 | or
10 |
11 | ```
12 | yarn build
13 | ```
14 |
15 | to bundle your application
16 |
--------------------------------------------------------------------------------
/example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Webpack App
6 |
7 |
8 | Feature viewer example
9 | Tip: Check your console
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/example.scss:
--------------------------------------------------------------------------------
1 | // Add style to feature viewer container
2 | #feature-viewer {
3 | outline: 1px solid red;
4 | }
5 |
--------------------------------------------------------------------------------
/example.ts:
--------------------------------------------------------------------------------
1 | // Import feature viewer
2 | import {FeatureViewer} from "./src/feature-viewer";
3 |
4 | // Import styles
5 | import './example.scss';
6 |
7 | // TODO Wait for page to load
8 | window.onload = () => {
9 | // Define sequence
10 | let sequence = "MDPGQQPPPQPAPQGQGQPPSQPPQGQGPPSGPGQPAPAATQAAPQAPPAGHQIVHVRGDSETDLEALFNAVMNPKTANVPQTVPMRLRKLPDSF" +
11 | "FKPPEPKSHSRQASTDAGTAGALTPQHVRAHSSPASLQLGAVSPGTLTPTGVVSGPAATPTAQHLRQSSFEIPDDVPLPAGWEMAKTSSGQRYFL" +
12 | "NHIDQTTTWQDPRKAMLSQMNVTAPTSPPVQQNMMNSASGPLPDGWEQAMTQDGEIYYINHKNKTTSWLDPRLDPRFAMNQRISQSAPVKQPPPL" +
13 | "APQSPQGGVMGGSNSNQQQQMRLQQLQMEKERLRLKQQELLRQAMRNINPSTANSPKCQELALRSQLPTLEQDGGTQNPVSSPGMSQELRTMTTN" +
14 | "SSDPFLNSGTYHSRDESTDSGLSMSSYSVPRTPDDFLNSVDEMDTGDTINQSTLPSQQNRFPDYLEAIPGTNVDLGTLEGDGMNIEGEELMPSLQ" +
15 | "EALSSDILNDMESVLAATKLDKESFLTWL";
16 | // Instantiate feature viewer
17 | let featureViewer = new FeatureViewer(sequence, '#feature-viewer',
18 | // Define optional settings
19 | {
20 | toolbar: true,
21 | toolbarPosition: 'left',
22 | brushActive: true,
23 | zoomMax: 10,
24 | flagColor: 'white',
25 | flagTrack: 170,
26 | flagTrackMobile: 150
27 | },
28 | // Define optional features
29 | [
30 | {
31 | type: 'curve',
32 | id: 'Curve1',
33 | label: 'Random values',
34 | color: 'red',
35 | data: Array.from(new Array(sequence.length), (x, i) => ({x: i + 1, y: Math.random()})),
36 | subfeatures: [
37 | {
38 | type: 'curve',
39 | id: 'Curve2',
40 | label: 'Random values',
41 | color: 'blue',
42 | height: 1,
43 | data: Array.from(new Array(sequence.length), (x, i) => ({x: i + 1, y: Math.random() /2 }))
44 | }
45 | ]
46 | },
47 | {
48 | type: 'rect',
49 | id: 'Degrons',
50 | label: 'Degrons',
51 | color:'grey',
52 | data: [{"x": 186, "y": 194, "color": "#006dc4", "tooltip": "186-194 xRxxLxx[LIVM]x", "stroke": "black"}, {"x": 348, "y": 356, "color": "#006dc4", "tooltip": "348-356 xRxxLxx[LIVM]x", "stroke": "black"}, {"x": 1, "y": 2, "color": "#c90076", "tooltip": "1-2 Mx", "stroke": "black"}, {"x": 1, "y": 3, "color": "#c90076", "tooltip": "1-3 M{0,1}([ED])x", "stroke": "black"}, {"x": 399, "y": 405, "color": "#006dc4", "tooltip": "399-405 D(S)Gx{2,3}([ST])", "stroke": "black"}, {"x": 180, "y": 184, "color": "#006dc4", "tooltip": "180-184 [AVP]x[ST][ST][ST]", "stroke": "black"}, {"x": 399, "y": 403, "color": "#006dc4", "tooltip": "399-403 DSGLS", "stroke": "black"}],
53 | },
54 | {
55 | type: 'rect',
56 | id: 'IDRs',
57 | label: 'IDRs',
58 | data: [{"x": 1, "y": 58, "color": "#9e7398", "stroke": "black"}, {"x": 60, "y": 61, "color": "#9e7398", "stroke": "black"}, {"x": 75, "y": 82, "color": "#9e7398", "stroke": "black"}, {"x": 101, "y": 170, "color": "#9e7398", "stroke": "black"}, {"x": 206, "y": 230, "color": "#9e7398", "stroke": "black"}, {"x": 264, "y": 300, "color": "#9e7398", "stroke": "black"}, {"x": 330, "y": 504, "color": "#9e7398", "stroke": "black"}],
59 | color:'grey'
60 | },
61 | {
62 | type: 'rect',
63 | id: 'Coils',
64 | label: 'Coils',
65 | data: [{"x": 1, "y": 49, "color": "#e3b9e5", "stroke": "black"}, {"x": 52, "y": 58, "color": "#e3b9e5", "stroke": "black"}, {"x": 74, "y": 74, "color": "#e3b9e5", "stroke": "black"}, {"x": 78, "y": 85, "color": "#e3b9e5", "stroke": "black"}, {"x": 91, "y": 92, "color": "#e3b9e5", "stroke": "black"}, {"x": 98, "y": 108, "color": "#e3b9e5", "stroke": "black"}, {"x": 110, "y": 168, "color": "#e3b9e5", "stroke": "black"}, {"x": 172, "y": 174, "color": "#e3b9e5", "stroke": "black"}, {"x": 182, "y": 182, "color": "#e3b9e5", "stroke": "black"}, {"x": 185, "y": 186, "color": "#e3b9e5", "stroke": "black"}, {"x": 201, "y": 201, "color": "#e3b9e5", "stroke": "black"}, {"x": 212, "y": 213, "color": "#e3b9e5", "stroke": "black"}, {"x": 215, "y": 220, "color": "#e3b9e5", "stroke": "black"}, {"x": 223, "y": 224, "color": "#e3b9e5", "stroke": "black"}, {"x": 226, "y": 228, "color": "#e3b9e5", "stroke": "black"}, {"x": 231, "y": 233, "color": "#e3b9e5", "stroke": "black"}, {"x": 241, "y": 241, "color": "#e3b9e5", "stroke": "black"}, {"x": 245, "y": 245, "color": "#e3b9e5", "stroke": "black"}, {"x": 260, "y": 260, "color": "#e3b9e5", "stroke": "black"}, {"x": 276, "y": 297, "color": "#e3b9e5", "stroke": "black"}, {"x": 334, "y": 334, "color": "#e3b9e5", "stroke": "black"}, {"x": 340, "y": 340, "color": "#e3b9e5", "stroke": "black"}, {"x": 353, "y": 383, "color": "#e3b9e5", "stroke": "black"}, {"x": 386, "y": 415, "color": "#e3b9e5", "stroke": "black"}, {"x": 417, "y": 418, "color": "#e3b9e5", "stroke": "black"}, {"x": 420, "y": 443, "color": "#e3b9e5", "stroke": "black"}, {"x": 447, "y": 474, "color": "#e3b9e5", "stroke": "black"}, {"x": 494, "y": 494, "color": "#e3b9e5", "stroke": "black"}, {"x": 496, "y": 496, "color": "#e3b9e5", "stroke": "black"}, {"x": 503, "y": 504, "color": "#e3b9e5", "stroke": "black"}],
66 | color:'grey'
67 | },
68 | {
69 | type: 'rect',
70 | id: 'Buried',
71 | label: 'Buried residues',
72 | data: [{"x": 166, "y": 166, "color": "#D3D3D3", "stroke": "black"}, {"x": 179, "y": 179, "color": "#D3D3D3", "stroke": "black"}, {"x": 188, "y": 188, "color": "#D3D3D3", "stroke": "black"}, {"x": 190, "y": 190, "color": "#D3D3D3", "stroke": "black"}, {"x": 200, "y": 202, "color": "#D3D3D3", "stroke": "black"}, {"x": 232, "y": 232, "color": "#D3D3D3", "stroke": "black"}, {"x": 235, "y": 236, "color": "#D3D3D3", "stroke": "black"}, {"x": 238, "y": 238, "color": "#D3D3D3", "stroke": "black"}, {"x": 247, "y": 249, "color": "#D3D3D3", "stroke": "black"}, {"x": 259, "y": 261, "color": "#D3D3D3", "stroke": "black"}],
73 | color:'grey'
74 | },
75 | {
76 | type: 'lollipop',
77 | id: 'Ubiquitination',
78 | label: 'Ubiquitination',
79 | color:'grey',
80 | data: [{"x": 497, "y": 497, "color": "#ffe800", "tooltip": "497K Ubiquitination(PhosphoSitePlus)", "stroke": "black"}, {"x": 181, "y": 181, "color": "#ffe800", "tooltip": "181K Ubiquitination(PhosphoSitePlus)", "stroke": "black"}, {"x": 102, "y": 102, "color": "#ffe800", "tooltip": "102K Ubiquitination(PhosphoSitePlus)", "stroke": "black"}, {"x": 97, "y": 97, "color": "#ffe800", "tooltip": "97K Ubiquitination(PhosphoSitePlus)", "stroke": "black"}, {"x": 90, "y": 90, "color": "#ffe800", "tooltip": "90K Ubiquitination(PhosphoSitePlus)", "stroke": "black"}, {"x": 280, "y": 280, "color": "#ffe800", "tooltip": "280K Ubiquitination(PhosphoSitePlus)", "stroke": "black"}, {"x": 315, "y": 315, "color": "#ffe800", "tooltip": "315K Ubiquitination(PhosphoSitePlus)", "stroke": "black"}, {"x": 321, "y": 321, "color": "#ffe800", "tooltip": "321K Ubiquitination(PhosphoSitePlus)", "stroke": "black"}, {"x": 342, "y": 342, "color": "#ffe800", "tooltip": "342K Ubiquitination(PhosphoSitePlus)", "stroke": "black"}, {"x": 254, "y": 254, "color": "#ffe800", "tooltip": "254K Ubiquitination(PhosphoSitePlus)", "stroke": "black"}],
81 | },
82 | {
83 | type: 'lollipop',
84 | id: 'Phosphorylation',
85 | label: 'Phosphorylation',
86 | color:'grey',
87 | data: [{"x": 473, "y": 473, "color": "#ffba01", "tooltip": "473S Phosphorylation(iPTMNet)", "stroke": "black"}, {"x": 405, "y": 405, "color": "#ffba01", "tooltip": "405S Phosphorylation(PhosphoSitePlus)", "stroke": "black"}, {"x": 407, "y": 407, "color": "#ffba01", "tooltip": "407Y Phosphorylation(PhosphoSitePlus/iPTMNet)", "stroke": "black"}, {"x": 109, "y": 109, "color": "#ffba01", "tooltip": "109S Phosphorylation(PhosphoSitePlus/iPTMNet)", "stroke": "black"}, {"x": 419, "y": 419, "color": "#ffba01", "tooltip": "419S Phosphorylation(PhosphoSitePlus)", "stroke": "black"}, {"x": 436, "y": 436, "color": "#ffba01", "tooltip": "436S Phosphorylation(PhosphoSitePlus)", "stroke": "black"}, {"x": 499, "y": 499, "color": "#ffba01", "tooltip": "499S Phosphorylation(PhosphoSitePlus)", "stroke": "black"}, {"x": 412, "y": 412, "color": "#ffba01", "tooltip": "412T Phosphorylation(PhosphoSitePlus/iPTMNet)", "stroke": "black"}, {"x": 403, "y": 403, "color": "#ffba01", "tooltip": "403S Phosphorylation(PhosphoSitePlus/iPTMNet)", "stroke": "black"}, {"x": 366, "y": 366, "color": "#ffba01", "tooltip": "366S Phosphorylation(PhosphoSitePlus)", "stroke": "black"}, {"x": 398, "y": 398, "color": "#ffba01", "tooltip": "398T Phosphorylation(PhosphoSitePlus)", "stroke": "black"}, {"x": 164, "y": 164, "color": "#ffba01", "tooltip": "164S Phosphorylation(PhosphoSitePlus/iPTMNet)", "stroke": "black"}, {"x": 188, "y": 188, "color": "#ffba01", "tooltip": "188Y Phosphorylation(PhosphoSitePlus/iPTMNet)", "stroke": "black"}, {"x": 217, "y": 217, "color": "#ffba01", "tooltip": "217S Phosphorylation(PhosphoSitePlus)", "stroke": "black"}, {"x": 227, "y": 227, "color": "#ffba01", "tooltip": "227S Phosphorylation(PhosphoSitePlus)", "stroke": "black"}, {"x": 105, "y": 105, "color": "#ffba01", "tooltip": "105S Phosphorylation(PhosphoSitePlus)", "stroke": "black"}, {"x": 274, "y": 274, "color": "#ffba01", "tooltip": "274S Phosphorylation(PhosphoSitePlus/iPTMNet)", "stroke": "black"}, {"x": 103, "y": 103, "color": "#ffba01", "tooltip": "103S Phosphorylation(PhosphoSitePlus)", "stroke": "black"}, {"x": 163, "y": 163, "color": "#ffba01", "tooltip": "163S Phosphorylation(PhosphoSitePlus/iPTMNet)", "stroke": "black"}, {"x": 276, "y": 276, "color": "#ffba01", "tooltip": "276S Phosphorylation(PhosphoSitePlus)", "stroke": "black"}, {"x": 300, "y": 300, "color": "#ffba01", "tooltip": "300S Phosphorylation(PhosphoSitePlus)", "stroke": "black"}, {"x": 340, "y": 340, "color": "#ffba01", "tooltip": "340S Phosphorylation(PhosphoSitePlus)", "stroke": "black"}, {"x": 400, "y": 400, "color": "#ffba01", "tooltip": "400S Phosphorylation(PhosphoSitePlus/iPTMNet)", "stroke": "black"}, {"x": 94, "y": 94, "color": "#ffba01", "tooltip": "94S Phosphorylation(PhosphoSitePlus/iPTMNet)", "stroke": "black"}, {"x": 83, "y": 83, "color": "#ffba01", "tooltip": "83T Phosphorylation(PhosphoSitePlus)", "stroke": "black"}, {"x": 354, "y": 354, "color": "#ffba01", "tooltip": "354T Phosphorylation(PhosphoSitePlus)", "stroke": "black"}, {"x": 361, "y": 361, "color": "#ffba01", "tooltip": "361T Phosphorylation(PhosphoSitePlus)", "stroke": "black"}, {"x": 77, "y": 77, "color": "#ffba01", "tooltip": "77T Phosphorylation(PhosphoSitePlus)", "stroke": "black"}, {"x": 289, "y": 289, "color": "#ffba01", "tooltip": "289S Phosphorylation(PhosphoSitePlus/iPTMNet)", "stroke": "black"}, {"x": 63, "y": 63, "color": "#ffba01", "tooltip": "63T Phosphorylation(PhosphoSitePlus/iPTMNet)", "stroke": "black"}, {"x": 156, "y": 156, "color": "#ffba01", "tooltip": "156T Phosphorylation(PhosphoSitePlus/iPTMNet)", "stroke": "black"}, {"x": 110, "y": 110, "color": "#ffba01", "tooltip": "110T Phosphorylation(PhosphoSitePlus)", "stroke": "black"}, {"x": 131, "y": 131, "color": "#ffba01", "tooltip": "131S Phosphorylation(PhosphoSitePlus/iPTMNet)", "stroke": "black"}, {"x": 141, "y": 141, "color": "#ffba01", "tooltip": "141T Phosphorylation(PhosphoSitePlus/iPTMNet)", "stroke": "black"}, {"x": 143, "y": 143, "color": "#ffba01", "tooltip": "143T Phosphorylation(PhosphoSitePlus/iPTMNet)", "stroke": "black"}, {"x": 128, "y": 128, "color": "#ffba01", "tooltip": "128S Phosphorylation(PhosphoSitePlus/iPTMNet)", "stroke": "black"}, {"x": 127, "y": 127, "color": "#ffba01", "tooltip": "127S Phosphorylation(PhosphoSitePlus/iPTMNet)", "stroke": "black"}, {"x": 119, "y": 119, "color": "#ffba01", "tooltip": "119T Phosphorylation(PhosphoSitePlus/iPTMNet)", "stroke": "black"}, {"x": 114, "y": 114, "color": "#ffba01", "tooltip": "114T Phosphorylation(PhosphoSitePlus)", "stroke": "black"}, {"x": 149, "y": 149, "color": "#ffba01", "tooltip": "149S Phosphorylation(PhosphoSitePlus/iPTMNet)", "stroke": "black"}, {"x": 154, "y": 154, "color": "#ffba01", "tooltip": "154T Phosphorylation(PhosphoSitePlus/iPTMNet)", "stroke": "black"}, {"x": 145, "y": 145, "color": "#ffba01", "tooltip": "145T Phosphorylation(PhosphoSitePlus)", "stroke": "black"}, {"x": 138, "y": 138, "color": "#ffba01", "tooltip": "138S Phosphorylation(PhosphoSitePlus/iPTMNet)", "stroke": "black"}, {"x": 41, "y": 41, "color": "#ffba01", "tooltip": "41T Phosphorylation(PhosphoSitePlus)", "stroke": "black"}, {"x": 367, "y": 367, "color": "#ffba01", "tooltip": "367S Phosphorylation(PhosphoSitePlus/iPTMNet)", "stroke": "black"}, {"x": 381, "y": 381, "color": "#ffba01", "tooltip": "381S Phosphorylation(PhosphoSitePlus/iPTMNet)", "stroke": "black"}, {"x": 382, "y": 382, "color": "#ffba01", "tooltip": "382S Phosphorylation(PhosphoSitePlus/iPTMNet)", "stroke": "black"}, {"x": 371, "y": 371, "color": "#ffba01", "tooltip": "371S Phosphorylation(PhosphoSitePlus/iPTMNet)", "stroke": "black"}, {"x": 61, "y": 61, "color": "#ffba01", "tooltip": "61S Phosphorylation(PhosphoSitePlus/iPTMNet)", "stroke": "black"}, {"x": 390, "y": 390, "color": "#ffba01", "tooltip": "390T Phosphorylation(PhosphoSitePlus)", "stroke": "black"}, {"x": 391, "y": 391, "color": "#ffba01", "tooltip": "391Y Phosphorylation(PhosphoSitePlus)", "stroke": "black"}, {"x": 397, "y": 397, "color": "#ffba01", "tooltip": "397S Phosphorylation(PhosphoSitePlus/iPTMNet)", "stroke": "black"}, {"x": 388, "y": 388, "color": "#ffba01", "tooltip": "388S Phosphorylation(PhosphoSitePlus/iPTMNet)", "stroke": "black"}],
88 | },
89 | {
90 | type: 'lollipop',
91 | id: 'Other',
92 | label: 'Other PTMs',
93 | color:'grey',
94 | data: [{"x": 109, "y": 109, "color": "#F4B5C7", "tooltip": "109S O-glycosylation(PhosphoSitePlus)", "stroke": "black"}, {"x": 241, "y": 241, "color": "#F4B5C7", "tooltip": "241T O-glycosylation(PhosphoSitePlus)", "stroke": "black"}, {"x": 497, "y": 497, "color": "#FF6961", "tooltip": "497K Methylation(PhosphoSitePlus)", "stroke": "black"}, {"x": 494, "y": 494, "color": "#FF6961", "tooltip": "494K Methylation(PhosphoSitePlus)", "stroke": "black"}, {"x": 97, "y": 97, "color": "#e83484", "tooltip": "97K Sumoylation(PhosphoSitePlus)", "stroke": "black"}, {"x": 280, "y": 280, "color": "#86CBED", "tooltip": "280K Sumoylation(PhosphoSitePlus)", "stroke": "black"}, {"x": 315, "y": 315, "color": "#A7E99C", "tooltip": "315K Acetylation(PhosphoSitePlus)", "stroke": "black"}],
95 | },
96 | {
97 | type: 'circle',
98 | id: 'Missense',
99 | label: 'Missense mutations',
100 | color:'grey',
101 | data: [{"x": 132, "y": 0.1, "color": "#00008b", "tooltip": "L132R", "stroke": "black"}, {"x": 180, "y": 0.1, "color": "#00008b", "tooltip": "A180T", "stroke": "black"}, {"x": 354, "y": 0.1, "color": "#00008b", "tooltip": "T354A", "stroke": "black"}, {"x": 456, "y": 0.1, "color": "#00008b", "tooltip": "G456E", "stroke": "black"}, {"x": 119, "y": 0.1, "color": "#00008b", "tooltip": "T119S", "stroke": "black"}, {"x": 133, "y": 0.1, "color": "#00008b", "tooltip": "Q133H", "stroke": "black"}, {"x": 501, "y": 0.1, "color": "#00008b", "tooltip": "L501I", "stroke": "black"}, {"x": 209, "y": 0.2, "color": "#00008b", "tooltip": "Q209L Q209H", "stroke": "black"}, {"x": 390, "y": 0.1, "color": "#00008b", "tooltip": "T390I", "stroke": "black"}, {"x": 436, "y": 0.1, "color": "#00008b", "tooltip": "S436T", "stroke": "black"}, {"x": 383, "y": 0.2, "color": "#00008b", "tooltip": "D383H D383G", "stroke": "black"}, {"x": 279, "y": 0.1, "color": "#00008b", "tooltip": "V279L", "stroke": "black"}, {"x": 127, "y": 0.1, "color": "#00008b", "tooltip": "S127F", "stroke": "black"}, {"x": 396, "y": 0.1, "color": "#00008b", "tooltip": "E396K", "stroke": "black"}, {"x": 478, "y": 0.1, "color": "#00008b", "tooltip": "L478F", "stroke": "black"}, {"x": 459, "y": 0.1, "color": "#00008b", "tooltip": "E459K", "stroke": "black"}, {"x": 399, "y": 0.2, "color": "#00008b", "tooltip": "D399G D399H", "stroke": "black"}, {"x": 488, "y": 0.1, "color": "#00008b", "tooltip": "S488Y", "stroke": "black"}, {"x": 124, "y": 0.2, "color": "#00008b", "tooltip": "R124P R124Q", "stroke": "black"}, {"x": 371, "y": 0.1, "color": "#00008b", "tooltip": "S371F", "stroke": "black"}, {"x": 145, "y": 0.1, "color": "#00008b", "tooltip": "T145S", "stroke": "black"}, {"x": 106, "y": 0.1, "color": "#00008b", "tooltip": "R106G", "stroke": "black"}, {"x": 225, "y": 0.1, "color": "#00008b", "tooltip": "M225I", "stroke": "black"}, {"x": 151, "y": 0.1, "color": "#00008b", "tooltip": "P151A", "stroke": "black"}, {"x": 61, "y": 0.1, "color": "#00008b", "tooltip": "S61W", "stroke": "black"}, {"x": 231, "y": 0.1, "color": "#00008b", "tooltip": "P231S", "stroke": "black"}, {"x": 367, "y": 0.1, "color": "#00008b", "tooltip": "S367F", "stroke": "black"}, {"x": 429, "y": 0.1, "color": "#00008b", "tooltip": "I429L", "stroke": "black"}, {"x": 167, "y": 0.1, "color": "#00008b", "tooltip": "I167T", "stroke": "black"}, {"x": 306, "y": 0.1, "color": "#00008b", "tooltip": "M306I", "stroke": "black"}, {"x": 427, "y": 0.1, "color": "#00008b", "tooltip": "D427H", "stroke": "black"}, {"x": 210, "y": 0.1, "color": "#00008b", "tooltip": "M210I", "stroke": "black"}, {"x": 73, "y": 0.1, "color": "#00008b", "tooltip": "M73I", "stroke": "black"}, {"x": 460, "y": 0.1, "color": "#00008b", "tooltip": "G460E", "stroke": "black"}, {"x": 481, "y": 0.1, "color": "#00008b", "tooltip": "D481H", "stroke": "black"}, {"x": 234, "y": 0.1, "color": "#00008b", "tooltip": "D234H", "stroke": "black"}, {"x": 319, "y": 0.1, "color": "#00008b", "tooltip": "R319Q", "stroke": "black"}, {"x": 162, "y": 0.1, "color": "#00008b", "tooltip": "Q162R", "stroke": "black"}, {"x": 125, "y": 0.1, "color": "#00008b", "tooltip": "A125G", "stroke": "black"}, {"x": 266, "y": 0.1, "color": "#00008b", "tooltip": "R266C", "stroke": "black"}, {"x": 166, "y": 0.1, "color": "#00008b", "tooltip": "E166G", "stroke": "black"}, {"x": 246, "y": 0.1, "color": "#00008b", "tooltip": "I246M", "stroke": "black"}],
102 | },
103 | ]);
104 | };
105 |
--------------------------------------------------------------------------------
/icons/icons8-chevron-down-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BioComputingUP/FeatureViewerTypeScript/46d7be45262f6e8ecad0679ed094130e831f7d1e/icons/icons8-chevron-down-48.png
--------------------------------------------------------------------------------
/icons/icons8-chevron-right-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BioComputingUP/FeatureViewerTypeScript/46d7be45262f6e8ecad0679ed094130e831f7d1e/icons/icons8-chevron-right-48.png
--------------------------------------------------------------------------------
/icons/icons8-download-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BioComputingUP/FeatureViewerTypeScript/46d7be45262f6e8ecad0679ed094130e831f7d1e/icons/icons8-download-48.png
--------------------------------------------------------------------------------
/icons/icons8-help-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BioComputingUP/FeatureViewerTypeScript/46d7be45262f6e8ecad0679ed094130e831f7d1e/icons/icons8-help-48.png
--------------------------------------------------------------------------------
/icons/icons8-level-up-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BioComputingUP/FeatureViewerTypeScript/46d7be45262f6e8ecad0679ed094130e831f7d1e/icons/icons8-level-up-48.png
--------------------------------------------------------------------------------
/index.ts:
--------------------------------------------------------------------------------
1 | // Just export feature viewer
2 | export * from './src/feature-viewer';
3 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "feature-viewer-typescript",
3 | "version": "2.3.20",
4 | "description": "Feature Viewer (Typescript)",
5 | "main": "./dist/index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "build": "webpack --mode=production --node-env=production",
9 | "build:dev": "webpack --mode=development",
10 | "build:prod": "webpack --mode=production --node-env=production",
11 | "prepare": "echo \"Building...\" && npm run build",
12 | "watch": "webpack --watch",
13 | "serve": "webpack serve"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "git+https://github.com/BioComputingUP/FeatureViewerTypeScript.git"
18 | },
19 | "keywords": [],
20 | "author": "Martina Bevilacqua, Lisanna Paladin and collaborators",
21 | "license": "ISC",
22 | "bugs": {
23 | "url": "https://github.com/BioComputingUP/FeatureViewerTypeScript/issues"
24 | },
25 | "homepage": "https://github.com/BioComputingUP/FeatureViewerTypeScript#readme",
26 | "dependencies": {
27 | "@types/d3-axis": "1.0.12",
28 | "@types/d3-brush": "1.0.6",
29 | "@types/d3-scale": "2.1.1",
30 | "@types/d3-selection": "1.4.1",
31 | "@types/d3-shape": "1.3.1",
32 | "@types/d3-transition": "^1.3.2",
33 | "@types/underscore": "1.8.16",
34 | "d3-axis": "^1.0.12",
35 | "d3-brush": "^1.0.6",
36 | "d3-scale": "2.2.2",
37 | "d3-selection": "1.4.0",
38 | "d3-shape": "1.3.5",
39 | "underscore": "^1.9.1"
40 | },
41 | "devDependencies": {
42 | "@types/node": "^18.7.23",
43 | "@webpack-cli/generators": "^2.5.0",
44 | "css-loader": "^6.7.1",
45 | "html-webpack-plugin": "^5.5.0",
46 | "mini-css-extract-plugin": "^2.6.1",
47 | "prettier": "^2.7.1",
48 | "sass": "^1.55.0",
49 | "sass-loader": "^13.0.2",
50 | "ts-loader": "^9.4.1",
51 | "typescript": "^4.8.4",
52 | "webpack": "^5.74.0",
53 | "webpack-cli": "^4.10.0",
54 | "webpack-dev-server": "^4.9.3"
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/calculate.ts:
--------------------------------------------------------------------------------
1 | import Commons from './commons';
2 | import {FeaturesList} from "./interfaces";
3 | import * as _ from "underscore";
4 |
5 | const commons = new Commons();
6 |
7 | class Calculate {
8 |
9 | public commons;
10 |
11 | private uniq(a) {
12 | return a.sort().filter(function(item, pos, ary) {
13 | return !pos || item != ary[pos - 1];
14 | })
15 | };
16 |
17 | private orderBy(values: any[], orderType: any) {
18 | return values.sort((a, b) => {
19 | if (a[orderType] < b[orderType]) {
20 | return -1;
21 | }
22 | if (a[orderType] > b[orderType]) {
23 | return 1;
24 | }
25 | return 0
26 | });
27 | }
28 |
29 | public yxPoints(d) {
30 | this.commons.flagsHeight = 18;
31 | let h = d.y + this.commons.flagsHeight;
32 | let htail = d.y + (this.commons.flagsHeight / 2)
33 | let w = (this.commons.viewerOptions.margin.left - 15) - (20 * (d.flagLevel-1));
34 | let wtail = (this.commons.viewerOptions.margin.left - 7) - (20 * (d.flagLevel-1));
35 | let poligon = [5, (d.y - 3), (5), (h), (w), (h), (wtail), (htail), (w), (d.y - 3)].join(',')
36 | return poligon;
37 | //return "5,57,5,78,25,78,33,69,25,57";
38 | };
39 |
40 | public getTransf(el) {
41 | return [el.transform.baseVal.getItem(0).matrix.e, el.transform.baseVal.getItem(0).matrix.f]
42 | };
43 |
44 | public getMarginLeft() {
45 | let flagwidht = this.commons.yAxisSVG.select(".flagBackground").node()
46 | let marginleft = (flagwidht).getBoundingClientRect().width;
47 | return marginleft
48 | }
49 |
50 | public addNLines(array) {
51 |
52 | let dataLevels = [];
53 |
54 | // sort array
55 | array = this.orderBy(array, 'x');
56 | array.forEach((d) => {
57 |
58 | // init
59 | if (dataLevels.length === 0) {
60 |
61 | dataLevels[0] = [d];
62 | d.level = 0;
63 |
64 | } else {
65 |
66 | let pulled = false;
67 | for (let lv in dataLevels) {
68 | // check superimposition, compare with last
69 | let last = dataLevels[lv].length - 1;
70 | if (d.x > dataLevels[lv][last].y) {
71 | // same level
72 | dataLevels[lv].push(d);
73 | d.level = lv;
74 | pulled = true;
75 | break;
76 | }
77 | }
78 | if (!pulled) {
79 | let newlv = dataLevels.length
80 | dataLevels[newlv] = [d];
81 | d.level = newlv;
82 | };
83 |
84 | }
85 |
86 | });
87 | return dataLevels.length;
88 | }
89 |
90 | public getHeightRect(level) {
91 | return (level-1) * 20 + 15;
92 | };
93 |
94 | public searchTree(element, matchingId){
95 | if (element.id == matchingId) {
96 | return element;
97 | } else if (element.subfeatures) {
98 | var i;
99 | var result = null;
100 | for (i = 0; result == null && i < element.subfeatures.length; i++) {
101 | result = this.searchTree(element.subfeatures[i], matchingId);
102 | }
103 | return result;
104 | }
105 | return null;
106 | }
107 |
108 | public unflatten = function( array: FeaturesList, parent=null, processedIds=null, tree=null ){
109 |
110 | tree = tree !== null ? tree : [];
111 | parent = parent !== null ? parent : { id: 0 };
112 | processedIds = processedIds !== null ? processedIds : new Set();
113 |
114 | var children = _.filter( array, (child) => {
115 | if (child.parentId === undefined) {
116 | child.parentId = 0
117 | }
118 | if (child.parentId === parent.id) {
119 | processedIds.add(child.id)
120 | }
121 | return child.parentId == parent.id;
122 | });
123 |
124 | if( !_.isEmpty( children ) ){
125 | if( parent.id == 0 ){
126 | tree = children;
127 | }else{
128 | parent['subfeatures'] = children
129 | }
130 | _.each( children, ( child ) => { this.unflatten( array, child, processedIds ) } );
131 | }
132 |
133 | let res = {
134 | tree:tree,
135 | ids:processedIds
136 | }
137 |
138 | return res;
139 | }
140 |
141 | public flatten(features, flatted=[], parent=null) {
142 | for (let i in features) {
143 | let ft = features[i];
144 | if (!parent) { ft.parent = []; } else {
145 | if (ft.parent) {ft.parent.concat(parent)} else {ft.parent = parent}
146 | }
147 | flatted.push(ft);
148 | if (ft.subfeatures) {
149 | this.flatten(ft.subfeatures, flatted=flatted, parent=ft.parent.concat(ft.id))
150 | }
151 | }
152 | return flatted
153 | }
154 |
155 | public displaySequence (seq) {
156 | // checks if dotted sequence or letters
157 | return this.commons.viewerOptions.width / seq > 8; // regulate sequence letter distance
158 | };
159 |
160 | public updateSVGHeight(position) {
161 | this.commons.svg.attr("height", position + 60 + "px");
162 | this.commons.svg.select("clipPath rect").attr("height", position + 60 + "px");
163 | };
164 |
165 | constructor(commons: {}) {
166 | this.commons = commons;
167 | }
168 | }
169 |
170 | export default Calculate
171 |
--------------------------------------------------------------------------------
/src/commons.ts:
--------------------------------------------------------------------------------
1 | import { FeatureObject, ViewerOptions, FeaturesList, FeatureData, FeatureViewerLog } from './interfaces';
2 |
3 | // let commons: Commons;
4 |
5 | type MyType = {};
6 |
7 | class Commons {
8 | public fvLength: number;
9 | public events = {
10 | FEATURE_SELECTED_EVENT: "feature-viewer-position-selected",
11 | CLEAR_SELECTION_EVENT: "feature-viewer-clear-selection",
12 | FLAG_SELECTED_EVENT: "feature-viewer-flag-selected",
13 | FLAG_SELECTED_INTERRUPTED_EVENT: "feature-viewer-flag-interrupted-event",
14 | TAG_SELECTED_EVENT: "feature-viewer-button-selected",
15 | ZOOM_EVENT: "feature-viewer-zoom-altered",
16 | ANIMATIONS_FALSE_EVENT: "feature-viewer-animations-off"
17 | };
18 | public viewerOptions: ViewerOptions = {
19 | showSequence: true,
20 | showSequenceLabel: true,
21 | brushActive: false,
22 | verticalLine: false,
23 | dottedSequence: true,
24 | showHelper: false,
25 | flagColor: "#DFD5D3",
26 | tooltipColor: '#fff',
27 | showSubFeatures: true,
28 | sideBar: true,
29 | labelTrackWidth: 0,
30 | labelTrackWidthMobile: 0,
31 | tagsTrackWidth: 0,
32 | mobileMode: false,
33 | margin: {
34 | top: 10,
35 | bottom: 20,
36 | left: 0,
37 | right: 0
38 | },
39 | backup: {
40 | labelTrackWidth: 0,
41 | tagsTrackWidth: 0,
42 | features: []
43 | },
44 | width: null,
45 | height: null,
46 | zoomMax: 10,
47 | unit: null,
48 | maxDepth: 3,
49 | animation: true,
50 | toolbar: true,
51 | bubbleHelp: true,
52 | showAxis: true,
53 | positionWithoutLetter: null,
54 | drawLadder: true,
55 | ladderWidth: 15,
56 | ladderSpacing: 10,
57 | ladderHeight: 18,
58 | };
59 | public mobilesize = 951;
60 | public radius = 5;
61 | public flagsHeight = 18;
62 | public maxNumberOfButtons = 0;
63 | public features: FeaturesList;
64 | public yData: any;
65 | public seqShift = 1;
66 | public scalingPosition: any;
67 | public lineYScale: any;
68 | public featureSelected: any;
69 | public flagSelected = [];
70 | public animation = true;
71 | public trigger: any;
72 | public level = 0;
73 | public svgElement: any;
74 | public svg: any;
75 | public svgContainer: any;
76 | public tagsContainer: any;
77 | public tooltipDiv: any;
78 | public customTooltipDiv: any;
79 | public divId: string;
80 | public right_chevron: any;
81 | public down_chevron: any;
82 | public yAxisSVG: any;
83 | public yAxisSVGGroup: any;
84 | public xAxis: any;
85 | public line: any;
86 | public lineBond: any;
87 | public brush: any;
88 | public extent: any;
89 | public pathLevel = 0;
90 | public step: number = 30; // or 20
91 | public elementHeight: number = Math.floor(this.step / 2); // or 1.5 if step is 20
92 | public YPosition: number = this.step;
93 | public scaling: any;
94 | public lineGen: any;
95 | public headMargin: number;
96 | public stringSequence : string;
97 | public calculatedTransitions: any={}
98 | public d3helper: any={};
99 | public style: any;
100 | public logger: any;
101 | public backgroundcolor: string;
102 | public currentposition: number;
103 | public currentzoom: number;
104 |
105 | private _current_extend = null;
106 |
107 | public get current_extend() {
108 | let extend = this._current_extend;
109 | this._current_extend = null;
110 | return extend || {
111 | length: this.viewerOptions.offset.end - this.viewerOptions.offset.start,
112 | start: this.viewerOptions.offset.start,
113 | end: this.viewerOptions.offset.end
114 | }
115 | };
116 |
117 | public set current_extend(current_extend) {
118 | this._current_extend = current_extend;
119 | }
120 |
121 | /*constructor() {
122 | if (!commons) {
123 | commons = this;
124 | }
125 | return commons
126 | }*/
127 | constructor() {
128 | }
129 | }
130 |
131 | export default Commons;
132 |
--------------------------------------------------------------------------------
/src/custom-d3.ts:
--------------------------------------------------------------------------------
1 | export * from 'd3-selection';
2 | export * from 'd3-transition';
3 | export * from 'd3-scale';
4 | export * from 'd3-shape';
5 | export * from 'd3-axis';
6 | export * from 'd3-brush';
7 |
--------------------------------------------------------------------------------
/src/feature-viewer.ts:
--------------------------------------------------------------------------------
1 | import * as d3 from './custom-d3'
2 | import {event as currentEvent} from 'd3-selection';
3 | import * as _ from 'underscore';
4 | import {FeatureObject, FeaturesList, FeatureViewerLog, UserOptions} from './interfaces';
5 | import Commons from './commons';
6 | import {SubfeaturesTransition, Transition} from "./transition";
7 | import FillSVG from "./fillsvg";
8 | import Calculate from "./calculate";
9 | import Tool from "./tooltip";
10 | // import * as htmlToImage from 'html-to-image';
11 | // import { toPng, toJpeg, toBlob, toPixelData, toSvg } from 'html-to-image';
12 |
13 | // import * as fvstyles from './../assets/fv.scss';
14 |
15 | // Import styles
16 | import './styles/styles.scss';
17 |
18 | class FeatureViewer {
19 | private commons: Commons;
20 | private divId: string;
21 | private sequence: string;
22 |
23 | private transition: Transition;
24 | private subfeaturesTransition: SubfeaturesTransition;
25 | private fillSVG: FillSVG;
26 | private calculate: Calculate;
27 | private tool: Tool;
28 | private lastHighlight: any;
29 | private lastLen: number;
30 |
31 |
32 | private parseUserOptions(options: UserOptions): void {
33 |
34 | const simple_keys = [
35 | 'showAxis',
36 | 'showSequence',
37 | 'showSequenceLabel',
38 | 'brushActive',
39 | 'toolbar',
40 | 'toolbarPosition',
41 | 'zoomMax',
42 | 'showSubFeatures',
43 | 'flagColor',
44 | 'flagTrack',
45 | 'flagTrackMobile',
46 | 'breakpoint',
47 | 'sideBar',
48 | 'unit',
49 | 'backgroundcolor'
50 | ];
51 | // and: breakpoint, offset
52 |
53 | for (let key of simple_keys) {
54 | if (options && key in options) {
55 | this.commons.viewerOptions[key] = options[key];
56 | }
57 | }
58 |
59 | if (options.maxDepth) {
60 | this.commons.viewerOptions.maxDepth = options.maxDepth;
61 | }
62 | this.commons.viewerOptions.maxDepth ++
63 |
64 | // when to mobile mode
65 | if (options.breakpoint) {
66 | if (typeof options.breakpoint === 'string') {
67 | this.commons.mobilesize = Number(options.breakpoint);
68 | } else if (typeof options.breakpoint === 'number') {
69 | this.commons.mobilesize = options.breakpoint;
70 | } else if (typeof options.breakpoint === 'boolean') {
71 | this.commons.mobilesize = 951;
72 | }
73 | }
74 |
75 | // if zoom specified
76 | this.commons.viewerOptions.offset = {start: 0, end: this.commons.fvLength + 1};
77 | if (options && options.offset) {
78 | this.commons.viewerOptions.offset = options.offset;
79 | if (options.offset.start < 1) {
80 | this.commons.viewerOptions.offset.start = 1;
81 | this.commons.logger.warn("Offset.start should be > 0. Thus, it has been reset to 1.", {fvId:this.divId});
82 | }
83 | }
84 |
85 | // brush active
86 | this.commons.viewerOptions.brushActive = options.brushActive? options.brushActive: true;
87 |
88 | // set width of sidebar
89 | this.commons.viewerOptions.tagsTrackWidth = 0;
90 | if (options && options.sideBar) {
91 | if (typeof options.sideBar === 'string') {
92 | this.commons.viewerOptions.tagsTrackWidth = Number(options.sideBar.match(/[0-9]+/g)[0]);
93 | } else if (typeof options.sideBar === 'number') {
94 | this.commons.viewerOptions.tagsTrackWidth = options.sideBar;
95 | } else if (typeof options.sideBar === 'boolean') {
96 | if (options.sideBar) {
97 | this.commons.viewerOptions.tagsTrackWidth = 100;
98 | } else {
99 | this.commons.viewerOptions.tagsTrackWidth = 0;
100 | }
101 | } else {
102 | this.commons.viewerOptions.tagsTrackWidth = 100;
103 | this.commons.logger.warn(`Automatically set tagsTrackWidth to ${this.commons.viewerOptions.tagsTrackWidth}`, {fvId:this.divId});
104 | }
105 | }
106 | this.commons.viewerOptions.backup.tagsTrackWidth = this.commons.viewerOptions.tagsTrackWidth;
107 |
108 | // set width of flags
109 | this.commons.viewerOptions.labelTrackWidth = 200;
110 | if (options && options.flagTrack) {
111 | if (typeof options.flagTrack === 'string') {
112 | this.commons.viewerOptions.labelTrackWidth = Number(options.flagTrack.match(/[0-9]+/g)[0]);
113 | } else if (typeof options.flagTrack === 'number') {
114 | this.commons.viewerOptions.labelTrackWidth = options.flagTrack;
115 | } else if (typeof options.flagTrack === 'boolean') {
116 | this.commons.viewerOptions.labelTrackWidth = options.flagTrack ? 200 : 0;
117 | } else {
118 | this.commons.viewerOptions.labelTrackWidth = 200;
119 | this.commons.logger.warn(`Automatically set tagsTrackWidth to ${this.commons.viewerOptions.tagsTrackWidth}`, {fvId:this.divId});
120 | }
121 | }
122 | this.commons.viewerOptions.backup.labelTrackWidth = this.commons.viewerOptions.labelTrackWidth;
123 |
124 | // set width of flags when mobile
125 | this.commons.viewerOptions.labelTrackWidthMobile = 30;
126 | if (options && options.flagTrackMobile) {
127 | if (typeof options.flagTrackMobile === 'string') {
128 | this.commons.viewerOptions.labelTrackWidthMobile = Number(options.flagTrackMobile.match(/[0-9]+/g)[0]);
129 | } else if (typeof options.flagTrackMobile === 'number') {
130 | this.commons.viewerOptions.labelTrackWidthMobile = options.flagTrackMobile;
131 | } else if (typeof options.flagTrackMobile === 'boolean') {
132 | this.commons.viewerOptions.labelTrackWidthMobile = options.flagTrackMobile ? 30 : 0;
133 | } else {
134 | this.commons.viewerOptions.labelTrackWidthMobile = 30;
135 | this.commons.logger.warn(`Automatically set tagsTrackWidth to ${this.commons.viewerOptions.tagsTrackWidth}`, {fvId:this.divId});
136 | }
137 | }
138 |
139 | // set margins
140 | this.commons.viewerOptions.margin = {
141 | top: 10,
142 | bottom: 20,
143 | left: this.commons.viewerOptions.labelTrackWidth,
144 | right: this.commons.viewerOptions.tagsTrackWidth
145 | };
146 |
147 | // resize if width < 480, initial view
148 | let myd3node = d3.select(`#${this.divId}`).node();
149 | if (myd3node) {
150 | let totalwidth = (myd3node).getBoundingClientRect().width;
151 | if (totalwidth < this.commons.mobilesize) {
152 | this.commons.viewerOptions.mobileMode = true;
153 | this.commons.viewerOptions.margin.left = this.commons.viewerOptions.labelTrackWidthMobile;
154 | if (this.commons.viewerOptions.tagsTrackWidth !== 0) {
155 | this.commons.viewerOptions.margin.right = 80
156 | }
157 | }
158 | }
159 |
160 | let myvod3node = d3.select(`#${this.divId}`).node();
161 | if (myvod3node) {
162 | this.commons.viewerOptions.width = (myvod3node).getBoundingClientRect().width;
163 | this.commons.viewerOptions.height = 600 - this.commons.viewerOptions.margin.top - this.commons.viewerOptions.margin.bottom;
164 | }
165 | // backgroundcolor
166 | this.commons.backgroundcolor = options.backgroundcolor? options.backgroundcolor : "white";
167 |
168 | };
169 |
170 | // Y Axis, responsive to click and triggers modifications in feature list
171 | private addYAxis() {
172 | // flags box
173 | this.commons.yAxisSVG = this.commons.svg.append("g")
174 | .attr("class", "pro axis")
175 | .attr("transform", "translate(0," + this.commons.viewerOptions.margin.top + ")");
176 | this.commons.yAxisSVG.append("rect")
177 | .attr("width", this.commons.viewerOptions.margin.left)
178 | .attr("class", "flagBackground")
179 | .attr("height", "100%")
180 | .attr("fill", this.commons.backgroundcolor)
181 | .attr("fill-opacity", 1);
182 | this.updateYAxis();
183 |
184 | };
185 |
186 | private updateYAxis() {
187 | // create g
188 | this.commons.yAxisSVGGroup = this.commons.yAxisSVG
189 | .selectAll(".yAxis")
190 | .data(this.commons.yData)
191 | .enter()
192 | .append("g")
193 | .attr("id", function (d) {
194 | // return divId + '_' + d.title.split(" ").join("_") + '_g'
195 | if (d.title === "Sequence") {
196 | return 'sequence'
197 | } else {
198 | return d.id
199 | }
200 | })
201 | .attr("class", (d) => {
202 | if (this.commons.viewerOptions.showSubFeatures && d.hasSubFeatures) {
203 | return "flag withsubfeatures"
204 | } else {
205 | return "flag"
206 | }
207 | })
208 | .on('click', (d) => {
209 | // if (this.commons.viewerOptions.showSubFeatures && d.hasSubFeatures) {
210 | // this.clickFlag(d)
211 | // }
212 | this.clickFlag(d)
213 | }) // change color feature when mouseover
214 | .on('mouseover', (d) => {
215 | if (this.commons.viewerOptions.showSubFeatures && d.hasSubFeatures) {
216 | d3.select(`#${this.divId}`).select(`#${d.id}`).selectAll(".Arrow").style("fill-opacity", 0.9);
217 | }
218 | })
219 | .on('mouseout', (d) => {
220 | if (this.commons.viewerOptions.showSubFeatures && d.hasSubFeatures) {
221 | d3.select(`#${this.divId}`).select(`#${d.id}`).selectAll(".Arrow").style("fill-opacity", 0.6);
222 | }
223 | })
224 | .call(this.commons.d3helper.flagTooltip());
225 |
226 | // create polygon
227 | this.commons.yAxisSVGGroup
228 | .append("polygon") // attach a polygon
229 | .attr("class", (d) => {
230 | if (this.commons.viewerOptions.showSubFeatures && d.hasSubFeatures) {
231 | return "boxshadow Arrow withsubfeatures"
232 | } else {
233 | return "boxshadow Arrow"
234 | }
235 | })
236 | .style("stroke", (d) => {
237 | return d.flagColor ? d.flagColor : this.commons.viewerOptions.flagColor;
238 | }) // colour the border if selected
239 | .attr("points", (d) => {
240 | if (d.ladderLabel == null) {
241 | // match points with subFeature level
242 | return this.calculate.yxPoints(d)
243 | }
244 | })
245 | .attr("transform", (d) => {
246 | let y = 0
247 | if (d.id == 'mycurve' || d.id == 'useUniqueId') { y = 5 }
248 | return "translate(" + (20 * (d.flagLevel - 1)) + ", "+ y + ")"
249 | })
250 | .style("fill", (d) => {
251 | return d.flagColor ? d.flagColor : this.commons.viewerOptions.flagColor;
252 | });
253 |
254 | // foreingObject for chevron
255 |
256 | this.commons.yAxisSVGGroup
257 | .append("g") // position
258 | .attr("transform", (d) => {
259 | let x = 0;
260 | // horizontal flag placement
261 | this.commons.headMargin = 0;//20
262 | if (d.flagLevel) {
263 | this.commons.headMargin = 0 * (d.flagLevel - 1);//20*
264 | // x = this.commons.headMargin + 5;// uncommented
265 | }
266 | // vertical flag placement
267 | let y = d.y;
268 | return "translate(" + x + "," + y + ")"
269 |
270 | })
271 | .append("path")
272 | .attr("id", "chevron")
273 | .attr("class", (d) => {
274 | if (this.commons.viewerOptions.showSubFeatures && d.hasSubFeatures) {
275 | return "chevron withsubfeatures"
276 | } else {
277 | return "chevron"
278 | }
279 | })
280 | .attr("fill", "rgba(39, 37, 37, 0.71)")
281 | .attr("d", (d) => {
282 | if (this.commons.viewerOptions.showSubFeatures && d.hasSubFeatures) {
283 | if (d.isOpen) {return this.commons.down_chevron} else {return this.commons.right_chevron}
284 | } else {
285 | return ''
286 | }
287 | });
288 |
289 |
290 |
291 | // text
292 | this.commons.yAxisSVGGroup
293 | .append("foreignObject")
294 | // text
295 | .attr("class", (d) => {
296 | if (this.commons.viewerOptions.showSubFeatures && d.hasSubFeatures) {
297 | return "yAxis withsubfeatures"
298 | } else { return "yAxis" }
299 | })
300 | .attr("x", (d) => {
301 | let cvm = 0;
302 | if (this.commons.viewerOptions.showSubFeatures && d.hasSubFeatures) {
303 | // chevron margin, placed rightly
304 | cvm = 17
305 | }
306 | // horizontal flag placement
307 | this.commons.headMargin = 20;
308 | if (d.ladderLabel == null) {
309 | if (d.flagLevel) {
310 | this.commons.headMargin = 20 * (d.flagLevel - 1);
311 | return cvm + this.commons.headMargin + 8;
312 | } else {
313 | return cvm + 8
314 | }
315 | }
316 | })
317 | .attr("y", d => {
318 | // vertical flag placement
319 | let y = d.y + this.commons.step/6
320 | if (d.id == 'mycircle') { y = y - 5}
321 | return y
322 | })
323 | .attr('font-size', '.8125rem')
324 | .attr("width", (d) => {
325 | // text only if space is enough
326 | if (this.commons.viewerOptions.mobileMode) {
327 | return this.calcFlagWidth(d);
328 | } else {
329 | let margin = 20 + this.commons.viewerOptions.ladderSpacing * this.commons.viewerOptions.maxDepth // 20 + (20 * d.flagLevel) --> 0
330 | return this.commons.viewerOptions.margin.left - margin; // chevron margin and text indent
331 | }
332 | })
333 | .attr("height", this.commons.step)
334 | .html((d) => {
335 | return d.label;
336 | });
337 |
338 |
339 | const ladderGroup = this.commons.yAxisSVGGroup
340 | .selectAll('.ladder')
341 | .data((e) => {
342 | return [...Array(e.flagLevel)].map((obj, x) => [x, e])
343 | })
344 | .enter()
345 | .append('g').attr('id', 'ladder-group')
346 |
347 | // ladderGroup.append('rect')
348 | // ladderGroup.append('path').attr('d', ([i, d]) => {
349 | // return this.roundedRect(0, 0, 16, 20, 5 , false, true, true, false);
350 | // })
351 |
352 | // badges code, removing this will remove badges
353 | ladderGroup.append('foreignObject')
354 | .attr("width", "30px")
355 | .attr("height", "30px")
356 | .html(([i, d]) => {
357 | if (d.ladderLabel) {
358 | return (d.id !== 'fv_sequence' && i===d.flagLevel-1) ?
359 | `${d.ladderLabel}
`: ''}
361 | }
362 | )
363 |
364 | ladderGroup.attr('transform', ([i, d]) => {
365 | if (d.ladderLabel) {
366 | const margin = (this.commons.viewerOptions.margin.left + (i - 1) * this.commons.viewerOptions.ladderSpacing);
367 | const ladderWidth = this.commons.viewerOptions.ladderSpacing * this.commons.viewerOptions.maxDepth;
368 | const x = margin - ladderWidth;
369 | const y = (d.y + this.commons.step / 6);
370 | return "translate(" + x + "," + y + ")"
371 | }
372 | })
373 |
374 |
375 | .attr('width', this.commons.viewerOptions.ladderWidth).attr('height', this.commons.viewerOptions.ladderHeight)
376 | .attr('fill', ([i, d]) => {
377 | return (d.id !== 'fv_sequence' && i===d.flagLevel-1) ? d.ladderColor : 'rgba(255, 255, 255, 0)'
378 | })
379 | .attr('class', d => `ladder`)
380 | .attr('rx', 0)
381 | .attr('ry', 2)
382 |
383 | ladderGroup.append('text')
384 | .attr('transform', ([i, d]) => {
385 | if (d.ladderLabel) {
386 | const margin = (this.commons.viewerOptions.margin.left + (i - 1) * this.commons.viewerOptions.ladderSpacing);
387 | const ladderWidth = this.commons.viewerOptions.ladderSpacing * this.commons.viewerOptions.maxDepth;
388 | const x = margin - ladderWidth + this.commons.viewerOptions.ladderWidth / 2;
389 | const y = (d.y + this.commons.step);
390 | return "translate(" + x + "," + y + ")"
391 | }
392 | })
393 | // .text(([i, d]) => i this.commons.viewerOptions.zoomMax) {
456 | this.commons.current_extend = {
457 | length: extentLength,
458 | start: start,
459 | end: end
460 | };
461 |
462 | // variables for logger
463 | zoomScale = (this.commons.fvLength / extentLength).toFixed(1);
464 | d3.select(`#${this.divId}`).select(".zoomUnit")
465 | .text(zoomScale.toString());
466 |
467 | //modify scale
468 | this.commons.scaling.domain([start, end]);
469 | this.commons.scalingPosition.range([start, end]);
470 |
471 | let currentShift = start ? start : this.commons.viewerOptions.offset.start;
472 |
473 | // apply transitions
474 | this.transition_data(this.commons.features, currentShift);
475 | this.fillSVG.reset_axis();
476 |
477 | // remove sequence
478 |
479 | this.commons.svgContainer.select(".mySequence").remove();
480 | // draw sequence
481 | if (this.commons.viewerOptions.showSequence) {
482 |
483 | if (seqCheck === false) {
484 | this.fillSVG.sequenceLine();
485 | }
486 | else if (seqCheck === true) {
487 | this.commons.seqShift = start;
488 | this.fillSVG.sequence(this.sequence.substring(start, end), this.commons.seqShift);
489 | }
490 | }
491 |
492 | } else {
493 |
494 | zoomScale = "Prevented";
495 | this.commons.logger.warn("Zoom greater than " + this.commons.viewerOptions.zoomMax + " is prevented", {fvId:this.divId});
496 | }
497 |
498 | this.commons.currentzoom = zoomScale;
499 | // if (CustomEvent) {
500 | // // zooming in
501 | // this.commons.svgElement.dispatchEvent(new CustomEvent(this.commons.events.ZOOM_EVENT, {
502 | // detail: {
503 | // start: start,
504 | // end: end,
505 | // zoom: zoomScale
506 | // }
507 | // }));
508 | // }
509 | // if (this.commons.trigger) this.commons.trigger(this.commons.events.ZOOM_EVENT, {
510 | // start: start,
511 | // end: end,
512 | // zoom: zoomScale
513 | // });
514 | }
515 |
516 | // remove brush now that transition is complete
517 | d3.select(`#${this.divId}`).select(".brush").call(this.commons.brush.move, null);
518 |
519 | } else {
520 | return
521 | }
522 |
523 |
524 | }
525 |
526 | // Mobile responsiveness
527 | private resizeForMobile() {
528 | // change flags
529 | let flags = d3.select(`#${this.divId}`).selectAll(".Arrow")
530 | .attr("points", (d) => {
531 | // match points with subFeature level
532 | return this.calculate.yxPoints(d)
533 | });
534 | this.commons.yAxisSVG.select(".flagBackground").attr("width", this.commons.viewerOptions.margin.left);
535 | let flags_text = d3.select(`#${this.divId}`).selectAll(".yAxis")
536 | .attr("width", (d) => {
537 | // text only if space is enough
538 |
539 | if (this.commons.viewerOptions.mobileMode) {
540 | // text width depends on mobile width, flaglevel and presence of subfeatures icon
541 | return this.calcFlagWidth(d);
542 | } else {
543 | let margin = 20 + this.commons.viewerOptions.ladderSpacing * this.commons.viewerOptions.maxDepth // 20 + (20 * d['flagLevel']) --> 0
544 | return this.commons.viewerOptions.margin.left - margin; // chevron margin and text indent
545 | }
546 | });
547 |
548 | if (this.commons.viewerOptions.mobileMode) {
549 | d3.select(`#${this.divId}`).selectAll("#ladder-group").attr("transform", ([i, d]) => {
550 | return "translate(" + ((d.flagLevel - 1) * this.commons.viewerOptions.ladderSpacing ) + "," + (d['y'] + 3.14) + ")"
551 | });
552 | } else {
553 | d3.select(`#${this.divId}`).selectAll("#ladder-group").attr("transform", ([i, d]) => {
554 | const margin = 20 + this.commons.viewerOptions.ladderSpacing * this.commons.viewerOptions.maxDepth
555 | return "translate(" + (this.commons.viewerOptions.labelTrackWidth - margin + (d.flagLevel * this.commons.viewerOptions.ladderSpacing)) + "," + (d['y'] + 3.14) + ")"
556 | });
557 | }
558 |
559 | // background containers, update width
560 | this.commons.svgContainer.attr("transform", "translate(" + (this.commons.viewerOptions.margin.left).toString() + ",12)");
561 | // this.commons.tagsContainer.attr("transform","translate(" + (this.commons.viewerOptions.width + this.commons.viewerOptions.margin.left) + "," + this.commons.viewerOptions.margin.top + ")")
562 | }
563 |
564 | private calcFlagWidth(d) {
565 | this.commons.headMargin = 20; // 20 * (d.flagLevel - 1)
566 | let totalspace = 0
567 | if ('hasSubFeatures' in d && d.hasSubFeatures) {totalspace} // {totalspace += 20}
568 |
569 | if (this.commons.headMargin) {totalspace += this.commons.headMargin + 8}
570 | let space = this.commons.viewerOptions.labelTrackWidthMobile - 15 - totalspace
571 | if (space < 20) {
572 | return '0px';
573 | } else {
574 | return space +'px';
575 | }
576 | }
577 |
578 | private updateWindow() {
579 | // change width now
580 | if (d3.select(`#${this.divId}`).node() !== null) {
581 |
582 | let d3node = d3.select(`#${this.commons.divId}`).node();
583 | let totalwidth = (d3node).getBoundingClientRect().width;
584 |
585 | // resize for mobile
586 | if (totalwidth < this.commons.mobilesize) {
587 | if (!this.commons.viewerOptions.mobileMode) {
588 | this.commons.viewerOptions.mobileMode = true;
589 | // update margins according to flagBackground width
590 | if (this.commons.viewerOptions.tagsTrackWidth !== 0) {
591 | this.commons.viewerOptions.margin.right = 80
592 | }
593 | this.commons.viewerOptions.margin.left = this.calculate.getMarginLeft() - (this.commons.viewerOptions.labelTrackWidth - this.commons.viewerOptions.labelTrackWidthMobile)
594 | } else {
595 | this.commons.viewerOptions.margin.left = this.calculate.getMarginLeft();
596 | }
597 | } else {
598 | let margins = this.calculate.getMarginLeft();
599 | if (this.commons.viewerOptions.mobileMode) {
600 | // no mobile size, resize flags
601 | this.commons.viewerOptions.mobileMode = false;
602 | margins += (this.commons.viewerOptions.labelTrackWidth - this.commons.viewerOptions.labelTrackWidthMobile)
603 | }
604 | this.commons.viewerOptions.margin.left = margins;
605 | }
606 |
607 | // update margins according to flagBackground width
608 | // resize for mobile
609 | this.resizeForMobile()
610 | this.commons.viewerOptions.width = totalwidth - this.commons.viewerOptions.margin.left - this.commons.viewerOptions.margin.right - 17;
611 |
612 | // resize containers
613 | this.commons.svg
614 | .attr("width", totalwidth);
615 | this.commons.svg.select("clipPath rect")
616 | .attr("width", this.commons.viewerOptions.width);
617 | if (this.commons.viewerOptions.brushActive) {
618 | d3.select(`${this.commons.divId} .background`).attr("width", this.commons.viewerOptions.width - 10);
619 | }
620 |
621 | // resize brush
622 | d3.select(`#${this.commons.divId}`).select(".fvbrush").call(this.commons.brush.move, null);
623 |
624 | // new scaling
625 | this.commons.scaling.range([2, this.commons.viewerOptions.width - 2]);
626 | this.commons.scalingPosition.domain([0, this.commons.viewerOptions.width]);
627 |
628 | // update seq visualization
629 | let seq = this.calculate.displaySequence(this.lastLen);
630 | // let seq = this.calculate.displaySequence(this.commons.viewerOptions.offset.end - this.commons.viewerOptions.offset.start);
631 | this.commons.svgContainer.select(".mySequence").remove();
632 | if (this.commons.viewerOptions.showSequence) {
633 | if (this.getCurrentZoom() == undefined || seq === false) {
634 | this.fillSVG.sequenceLine();
635 | } else if (this.getCurrentZoom() && seq) {
636 | this.fillSVG.sequence(this.sequence.substring(this.commons.viewerOptions.offset.start, this.commons.viewerOptions.offset.end), this.commons.viewerOptions.offset.start);
637 | }
638 | // if (seq === false) {
639 | // this.fillSVG.sequenceLine();
640 | // } else if (seq === true) {
641 | // this.fillSVG.sequence(this.sequence.substring(this.commons.viewerOptions.offset.start, this.commons.viewerOptions.offset.end), this.commons.viewerOptions.offset.start);
642 | // }
643 | }
644 | if (this.commons.animation) {
645 | // @ts-ignore
646 | d3.select(`#${this.commons.divId}`).select('#tags_container').transition().duration(500)
647 | }
648 | d3.select(`#${this.commons.divId}`).select('#tags_container')
649 | .attr("transform", "translate(" + (this.commons.viewerOptions.margin.left + this.commons.viewerOptions.width + 10).toString() + ",10)");
650 |
651 | this.transition_data(this.commons.features, this.commons.current_extend.start);
652 | this.fillSVG.reset_axis();
653 | this.fillSVG.resizeBrush()
654 |
655 | }
656 | }
657 |
658 | private transition_data(features, start) {
659 | // no usage of start
660 | // apply transition data recursively?
661 | for (const o of features) {
662 | if (o.type === "rect") {
663 | this.transition.rectangle(o);
664 | } else if (o.type === "multipleRect") {
665 | this.transition.multiRec(o);
666 | } else if (o.type === "unique") {
667 | this.transition.unique(o);
668 | } else if (o.type === "circle") {
669 | this.transition.circle(o);
670 | } else if (o.type === "path") {
671 | this.transition.path(o);
672 | } else if (o.type === "lollipop") {
673 | this.transition.lollipop(o);
674 | } else if (o.type === "curve") {
675 | this.transition.lineTransition(o);
676 | }
677 | // resize basal line too
678 | this.transition.basalLine(o);
679 | // apply recursively to subfeatures (if shown)
680 | if (o.subfeatures && o.isOpen) {
681 | this.transition_data(o.subfeatures, start)
682 | }
683 | }
684 | }
685 | // init viewer
686 | private init(div) {
687 |
688 | // first element is 0
689 | this.sequence = " " + this.sequence;
690 | this.commons.stringSequence = this.sequence;
691 |
692 | d3.select(div)
693 | .style("position", "relative")
694 | .style("padding", "0px")
695 | .style("z-index", "2");
696 |
697 | this.commons.scaling = d3.scaleLinear()
698 | .domain([this.commons.viewerOptions.offset.start, this.commons.viewerOptions.offset.end])
699 | .range([0, this.commons.viewerOptions.width]); // borders
700 |
701 | this.commons.scalingPosition = d3.scaleLinear()
702 | .domain([0, this.commons.viewerOptions.width])
703 | .range([this.commons.viewerOptions.offset.start, this.commons.viewerOptions.offset.end]);
704 |
705 | // init objects
706 |
707 | this.tool.initTooltip(div, this.divId);
708 |
709 | // init overlay div
710 | let overlayhtml = '
'
711 | d3.select(`#${this.divId}`)
712 | .append("foreignObject")
713 | .html(overlayhtml)
714 |
715 | this.commons.lineBond = d3.line()
716 | .curve(d3.curveStepBefore)
717 | .x((d) => {
718 | return this.commons.scaling(d['x']);
719 | // return this.commons.scaling(d[0]);
720 | // return this.commons.scaling(d[0].x);
721 | })
722 | .y((d) => {
723 | //return -d[1] * 10 + this.commons.pathLevel;
724 | return -d['y'] * 10 + this.commons.pathLevel;
725 | // return -(d[0] as any).y * 10 + this.commons.pathLevel;
726 | });
727 |
728 | this.commons.lineGen = d3.line()
729 | .x((d) => {
730 | // return this.commons.scaling(d[0]);
731 | return this.commons.scaling(d['x']);
732 | // return this.commons.scaling((d[0] as any).x);
733 | })
734 | .curve(d3.curveBasis);
735 |
736 | this.commons.lineYScale = d3.scaleLinear()
737 | .domain([0, -30])
738 | .range([0, -20]);
739 |
740 | this.commons.line = d3.line()
741 | .curve(d3.curveLinear)
742 | .x((d) => {
743 | // return this.commons.scaling(d[0]);
744 | return this.commons.scaling(d['x']);
745 | // return this.commons.scaling((d[0] as any).x);
746 | })
747 | .y((d) => {
748 | // return d[1] + 6;
749 | return d['y'] + 6;
750 | //return (d[0] as any).y + 6;
751 | });
752 |
753 | let rtickStep = Math.round(this.commons.fvLength/10); // fraction of a tenth
754 | let tickStep = Math.round(rtickStep/10)*10; // nearest 10th multiple
755 |
756 | let tickArray = Array.from(Array(this.commons.fvLength).keys())
757 | .filter(function (value, index, ar) {
758 | return (index % tickStep == 0 && index !== 0);
759 | });
760 |
761 | //Create Axis
762 | this.commons.xAxis = d3.axisBottom(this.commons.scaling)
763 | .tickValues(tickArray)
764 | //.scale(this.commons.scaling) // TODO
765 | //.tickFormat(d3.format("d"));
766 |
767 | let yAxisScale = d3.scaleBand()
768 | //.domain([0, this.commons.yData.length])
769 | .rangeRound([0, 500]);
770 |
771 | // Y Axis
772 | d3.axisLeft(yAxisScale)
773 | //.scale(yAxisScale) // TODO
774 | //.tickValues(this.commons.yData)
775 | .tickFormat(function (d) {
776 | return d
777 | });
778 |
779 | this.commons.brush = d3.brushX()
780 | //.extent([[this.commons.scaling.range()[0], 0], [this.commons.scaling.range()[1], 1]])
781 | .on("end", () => {
782 | this.brushend()
783 | });
784 |
785 | this.commons.right_chevron = "M12.95 10.707l0.707-0.707-5.657-5.657-1.414 1.414 4.242 4.243-4.242 4.243 1.414 1.414 4.95-4.95z"
786 | this.commons.down_chevron = "M9.293 12.95l0.707 0.707 5.657-5.657-1.414-1.414-4.243 4.242-4.243-4.242-1.414 1.414z"
787 |
788 | // Define the divs for the tooltip
789 | this.commons.tooltipDiv = d3.select(`#${this.divId}`)
790 | .append("div")
791 | .attr("class", "fvtooltip")
792 | .attr("id", "fvtooltip")
793 | .style("opacity", 0)
794 | .style("z-index", 1070);
795 | this.commons.customTooltipDiv = d3.select(`#${this.divId}`)
796 | .append("div")
797 | .attr("class", "fvcustomtooltip")
798 | .attr("id", "fvcustomtooltip")
799 | .style("opacity", 0)
800 | .style("z-index", 1070);
801 |
802 | this.commons.style = d3.select(`#${this.divId}`)
803 | // .append("style")
804 | // .html(`${fvstyles}`)
805 |
806 |
807 | // Create SVG
808 | if (this.commons.viewerOptions.toolbar) {
809 | let headerOptions = document.querySelector(div + " .svgHeader") ? d3.select(div + " .svgHeader") : d3.select(div).append("div").attr("class", "svgHeader");
810 |
811 | if (this.commons.viewerOptions.toolbarPosition) {
812 | // flex-end
813 | if (this.commons.viewerOptions.toolbarPosition === "right") {this.commons.viewerOptions.toolbarPosition = "flex-end"}
814 | else if (this.commons.viewerOptions.toolbarPosition === "left") {this.commons.viewerOptions.toolbarPosition = "flex-start"}
815 | headerOptions.attr("style", "color: rgba(39, 37, 37, 0.71); display: flex; justify-content: " + this.commons.viewerOptions.toolbarPosition)
816 | }
817 | else {
818 | headerOptions.attr("style", "color: rgba(39, 37, 37, 0.71);");
819 | }
820 |
821 | // position
822 | if (!document.querySelector(div + ' .header-position')) {
823 | let headerPosition = headerOptions
824 | .append("div")
825 | .attr("class", "header-position")
826 | .style("display", "inline-block")
827 | .style("padding-top", "5px")
828 | let button = headerPosition
829 | .append("div")
830 | .attr("class", "position-label")
831 | .style("display", "inline-block")
832 | button
833 | // draw icon
834 | .append("svg")
835 | .attr("class", "helperButton")
836 | .append("path")
837 | .attr("d", "M10 20s-7-9.13-7-13c0-3.866 3.134-7 7-7s7 3.134 7 7v0c0 3.87-7 13-7 13zM10 9c1.105 0 2-0.895 2-2s-0.895-2-2-2v0c-1.105 0-2 0.895-2 2s0.895 2 2 2v0z");
838 | button
839 | .append("text")
840 | .text("Position:")
841 | headerPosition
842 | .append("div")
843 | .style("display", "inline-block")
844 | .style("padding-left", "5px")
845 | .append("div")
846 | .style("padding-right", "15px")
847 | .style("width", "80px") // fix width otherwise responsive to length number;
848 | .attr("id", "zoomPosition")
849 | .text("0")
850 | }
851 |
852 | //zoom
853 | let headerZoom;
854 | if (!document.querySelector(div + ' .header-zoom')) {
855 |
856 | headerZoom = headerOptions
857 | .append("div")
858 | .attr("class", "header-zoom")
859 | .style("display", "inline-block")
860 | .style("padding-top", "5px");
861 | let button = headerZoom
862 | .append("div")
863 | .attr("class", "zoom-label")
864 | .style("display", "inline-block")
865 | button
866 | // draw icon
867 | .append("svg")
868 | .attr("class", "helperButton")
869 | .append("path")
870 | .attr("d", "M12.9 14.32c-1.34 1.049-3.050 1.682-4.908 1.682-4.418 0-8-3.582-8-8s3.582-8 8-8c4.418 0 8 3.582 8 8 0 1.858-0.633 3.567-1.695 4.925l0.013-0.018 5.35 5.33-1.42 1.42-5.33-5.34zM8 14c3.314 0 6-2.686 6-6s-2.686-6-6-6v0c-3.314 0-6 2.686-6 6s2.686 6 6 6v0z");
871 | button
872 | .append("text")
873 | .text("Zoom:")
874 |
875 | headerZoom
876 | .append("div")
877 | .style("display", "inline-block")
878 | .append("div")
879 | .style("padding-left", "5px")
880 | .style("width", "80px") // fix width otherwise responsive to length number;
881 | .append("span")
882 | .text("x ")
883 | .append("span")
884 | .style("padding-right", "15px")
885 | .attr("class", "zoomUnit")
886 | .text("1");
887 | }
888 |
889 | // help
890 | if (!document.querySelector(div + ' .header-help')) {
891 |
892 | let headerHelp = headerOptions
893 | .append("div")
894 | .attr("class", "header-help")
895 | .style("display", "inline-block")
896 |
897 | headerHelp
898 | .append("button")
899 | .attr("class", "mybuttoncircle")
900 | .attr("id", "downloadButton")
901 | .on("click", () => {
902 | this.downloadSvg()
903 | })
904 | // draw icon
905 | .append("svg")
906 | .attr("class", "helperButton")
907 | .append("path")
908 | .attr("d", "M13 8v-6h-6v6h-5l8 8 8-8h-5zM0 18h20v2h-20v-2z")
909 |
910 |
911 | headerHelp
912 | .append("button")
913 | .attr("id", "helpButton")
914 | .attr("class", "mybuttoncircle")
915 | .on("click", () => {
916 | this.fillSVG.showHelp()
917 | })
918 | // draw icon
919 | .append("svg")
920 | .attr("class", "helperButton")
921 | .append("path")
922 | .attr("d", "M2.93 17.070c-1.884-1.821-3.053-4.37-3.053-7.193 0-5.523 4.477-10 10-10 2.823 0 5.372 1.169 7.19 3.050l0.003 0.003c1.737 1.796 2.807 4.247 2.807 6.947 0 5.523-4.477 10-10 10-2.7 0-5.151-1.070-6.95-2.81l0.003 0.003zM9 11v4h2v-6h-2v2zM9 5v2h2v-2h-2z")
923 |
924 | }
925 |
926 | }
927 |
928 | this.commons.svg = d3.select(div).append("svg")
929 | .attr("width", this.commons.viewerOptions.width + this.commons.viewerOptions.margin.left + this.commons.viewerOptions.margin.right)
930 | .attr("height", this.commons.viewerOptions.height + this.commons.viewerOptions.margin.top + this.commons.viewerOptions.margin.bottom)
931 | .style("z-index", "2")
932 | .attr("id", "svgContent")
933 | .on("dblclick", (d,i)=>{
934 | // react to double click
935 | this.resetZoom();
936 | // if (CustomEvent) {
937 | // let event = new CustomEvent(this.commons.events.CLEAR_SELECTION_EVENT, {detail: {}});
938 | // this.commons.svgElement.dispatchEvent(event);
939 | // } else {
940 | // this.commons.logger.warn("CustomEvent is not defined", {fvId:this.divId});
941 | // }
942 | // if (this.commons.trigger) this.commons.trigger(this.commons.events.CLEAR_SELECTION_EVENT);
943 | })
944 | .on("contextmenu", (d, i) => {
945 | // react on right click
946 | this.resetZoom();
947 | if (CustomEvent) {
948 | let event = new CustomEvent(this.commons.events.CLEAR_SELECTION_EVENT, {detail: {}});
949 | this.commons.svgElement.dispatchEvent(event);
950 | } else {
951 | this.commons.logger.warn("CustomEvent is not defined", {fvId:this.divId});
952 | }
953 | if (this.commons.trigger) this.commons.trigger(this.commons.events.CLEAR_SELECTION_EVENT);
954 | });
955 |
956 | this.commons.svgElement = d3.select(`#${this.divId}`).select('svg').node();
957 |
958 |
959 | // features track box
960 | this.commons.svgContainer = this.commons.svg
961 | .append("g")
962 | .attr("transform", "translate(" + this.commons.viewerOptions.margin.left + "," + this.commons.viewerOptions.margin.top + ")")
963 | .attr("id", "tracks_container")
964 | // prevent right-click
965 | .on("contextmenu", function (d, i) {
966 | currentEvent.preventDefault();
967 | // react on right-clicking
968 | });
969 |
970 |
971 | // background
972 | this.commons.svgContainer.append("rect")
973 | .attr("width", "100%")
974 | .attr("height", "100%")
975 | .attr("fill", this.commons.backgroundcolor);
976 |
977 | // tags space
978 | this.commons.tagsContainer = this.commons.svg.append("g")
979 | .attr("transform", "translate(" + (this.commons.viewerOptions.width + this.commons.viewerOptions.margin.left) + "," + this.commons.viewerOptions.margin.top + ")")
980 | .attr("id", "tags_container");
981 |
982 | if (this.commons.viewerOptions.sideBar) {
983 | // add white rect to hide feature zoom exceeding the viewer length
984 | this.commons.tagsContainer.append("rect")
985 | .attr("x", -6)
986 | .attr("width", "100%")
987 | .attr("height", "100%")
988 | .attr("fill", this.commons.backgroundcolor);
989 | }
990 |
991 | this.commons.svgContainer.on('mousemove', () => {
992 | let absoluteMousePos = d3.mouse(this.commons.svgContainer.node());
993 |
994 | let posN = Math.round(this.commons.scalingPosition(absoluteMousePos[0]));
995 | let pos;
996 | if (!this.commons.viewerOptions.positionWithoutLetter) {
997 | pos = `${posN}${this.sequence[posN] || ""}`;
998 | } else {
999 | pos = posN.toString();
1000 | }
1001 | this.commons.currentposition = pos;
1002 | if (this.commons.viewerOptions.toolbar) {
1003 | // d3.select(`${this.divId} #zoomPosition`).text(pos);
1004 | document.querySelector(`#${this.divId} #zoomPosition`).innerHTML = pos;
1005 | }
1006 | });
1007 |
1008 | if (this.commons.viewerOptions.showSequence) {
1009 | if (this.calculate.displaySequence(this.commons.viewerOptions.offset.end - this.commons.viewerOptions.offset.start)) {
1010 | this.fillSVG.sequence(this.sequence.substring(this.commons.viewerOptions.offset.start, this.commons.viewerOptions.offset.end), this.commons.viewerOptions.offset.start);
1011 | }
1012 | else {
1013 | this.fillSVG.sequenceLine();
1014 | }
1015 | // check if sequence already initialized, alse add it to yData
1016 | // if (this.commons.yData.filter((e) => {e.id === 'fv_sequence'}).length === 0) {
1017 | // features
1018 | // this.commons.features.push({
1019 | // data: this.sequence,
1020 | // label: "Sequence",
1021 | // className: "AA",
1022 | // color: "black",
1023 | // type: "sequence",
1024 | // id: "fv_sequence"
1025 | // });
1026 | // yData
1027 | if (this.commons.viewerOptions.showSequenceLabel) {
1028 | this.commons.yData.push({
1029 | id: "fv_sequence",
1030 | label: "Sequence",
1031 | y: this.commons.YPosition - 10,
1032 | flagLevel: 1
1033 | });
1034 | }
1035 | // }
1036 | // this.commons.yData.push({
1037 | // id: "fv_sequence",
1038 | // label: "",
1039 | // y: this.commons.YPosition - 8,
1040 | // flagLevel: 1
1041 | // });
1042 | }
1043 |
1044 | this.fillSVG.addXAxis(this.commons.YPosition);
1045 | this.addYAxis();
1046 |
1047 | if (this.commons.viewerOptions.brushActive) {
1048 | // this.commons.viewerOptions.brushActive = true;
1049 | this.fillSVG.addBrush();
1050 | }
1051 | /* feature removed
1052 | if (this.commons.viewerOptions.verticalLine) {
1053 | // this.commons.viewerOptions.verticalLine = true;
1054 | this.addVerticalLine();
1055 | }
1056 | */
1057 |
1058 | this.calculate.updateSVGHeight(this.commons.YPosition);
1059 |
1060 | // listen to resizing
1061 | window.addEventListener("resize", () => {
1062 | this.updateWindow()
1063 | }); // window.addEventListener works, but iupdateWindow needs to access to internal commons
1064 | }
1065 |
1066 | // interact with features
1067 | private addFeatureCore(object, flagLevel = 1, position = null) {
1068 | this.commons.YPosition += this.commons.step;
1069 | // if no label is given, id on flag
1070 | if (!object.label) {object.label = object.id}
1071 | // deselect it if no isOpen input
1072 | if (!object.isOpen) {
1073 | object.isOpen = false;
1074 | }
1075 | if (this.commons.animation) {
1076 | if (CustomEvent) {
1077 | let event = new CustomEvent(this.commons.events.ANIMATIONS_FALSE_EVENT, {detail: {}});
1078 | if (this.commons.svgElement) {
1079 | this.commons.svgElement.dispatchEvent(event);
1080 | }
1081 | } else {
1082 | this.commons.logger.warn("CustomEvent is not defined", {fvId:this.divId});
1083 | }
1084 | if (this.commons.trigger) this.commons.trigger(this.commons.events.ANIMATIONS_FALSE_EVENT);
1085 | }
1086 |
1087 | if (!object.className) {
1088 | object.className = object.type + "fv";
1089 | }
1090 | else {
1091 | // initialized by user or by viewer?
1092 | if (object.className !== object.type + "fv") {
1093 | object.className = object.className + " " + object.type + "fv";
1094 | }
1095 | }
1096 |
1097 | if (!object.color) {
1098 | object.color = "#DFD5F5";
1099 | }
1100 |
1101 | //object.height = this.commons.elementHeight;
1102 | object.flagLevel = flagLevel;
1103 |
1104 | this.fillSVG.typeIdentifier(object);
1105 | // flags
1106 | this.updateYAxis();
1107 | if (object.type === "curve" || object.type === "path") {
1108 | // this.updateWindow();
1109 | }
1110 | }
1111 |
1112 | private drawFeatures() {
1113 | // turn off features if more than 100
1114 | if (this.commons.features.length > 100) {
1115 | this.commons.animation = false;
1116 | this.commons.logger.warn("Animation is turned off with more than 100 features", {method:"addFeatureCore", fvId:this.divId, featuresNumber:this.commons.features.length})
1117 | }
1118 | for (const ft of this.commons.features) {
1119 | this.addFeature(ft)
1120 | }
1121 |
1122 | this.fillSVG.updateXAxis(this.commons.YPosition);
1123 | this.calculate.updateSVGHeight(this.commons.YPosition + 5);
1124 |
1125 | // update brush
1126 | if (this.commons.viewerOptions.brushActive) {
1127 | this.fillSVG.resizeBrush()
1128 | }
1129 |
1130 | }
1131 |
1132 | private recursivelyRemove(ft) {
1133 | // remove subfeatures
1134 | if (ft.subfeatures) {
1135 | for (const sft of ft.subfeatures) {
1136 | this.recursivelyRemove(sft)
1137 | }
1138 | }
1139 | // remove from feature array and from html
1140 | d3.select(`#t${ft.id}_tagarea`).remove();
1141 | d3.select(`#c${ft.id}_container`).remove();
1142 | d3.select(`#${ft.id}`).remove();
1143 | }
1144 |
1145 | private recursiveClose (array) {
1146 | for (const sbt of array) {
1147 | sbt.isOpen = false
1148 | if (sbt.subfeatures) {
1149 | this.recursiveClose(sbt.subfeatures)
1150 | }
1151 | }
1152 | }
1153 |
1154 | private changeFeature(feature, bool) {
1155 |
1156 | // freeze viewer
1157 | this.flagLoading(feature.id);
1158 | // close or open it
1159 | feature.isOpen = bool;
1160 | // if close, reset children status
1161 | if (!feature.isOpen) {
1162 | if (feature.subfeatures) {
1163 | this.recursiveClose(feature.subfeatures)
1164 | }
1165 | }
1166 |
1167 | // overlay if opening many subfeatures
1168 | if (feature.isOpen) {
1169 | if (feature.subfeatures.length > 200) {
1170 | setTimeout(()=>{
1171 | // empty features
1172 | this.commons.features = this.emptyFeatures()
1173 | // redraw features
1174 | this.drawFeatures()
1175 | // defreeze viewer
1176 | this.stopFlagLoading(feature.id)
1177 | }, 1)
1178 | return
1179 | }
1180 | }
1181 |
1182 | // empty features
1183 | this.commons.features = this.emptyFeatures()
1184 | // redraw features
1185 | this.drawFeatures()
1186 | // defreeze viewer
1187 | this.stopFlagLoading(feature.id)
1188 |
1189 |
1190 | }
1191 |
1192 | private resetTooltip(tooltipdiv) {
1193 | // empty custom tooltip in reset
1194 | tooltipdiv.transition()
1195 | .duration(500)
1196 | .style("opacity", 0);
1197 | tooltipdiv.html("");
1198 | tooltipdiv.status = 'closed';
1199 | }
1200 |
1201 |
1202 | /*** PUBLIC FUNCTIONS ***/
1203 |
1204 | public getCurrentPosition() {
1205 | return this.commons.currentposition;
1206 | }
1207 |
1208 | public getCurrentZoom() {
1209 | return this.commons.currentzoom;
1210 | }
1211 |
1212 | public showHelp() {
1213 | this.fillSVG.showHelp()
1214 | }
1215 |
1216 | public resetHighlight(resetLastHighlight=true) {
1217 | // empty custom tooltip in reset
1218 | this.resetTooltip(this.commons.customTooltipDiv);
1219 | this.resetTooltip(this.commons.tooltipDiv);
1220 |
1221 | if (CustomEvent) {
1222 | let event = new CustomEvent(this.commons.events.CLEAR_SELECTION_EVENT, {detail: {}});
1223 | this.commons.svgElement.dispatchEvent(event);
1224 | } else {
1225 | this.commons.logger.warn("CustomEvent is not defined", {fvId:this.divId});
1226 | }
1227 | if (this.commons.trigger) this.commons.trigger(this.commons.events.CLEAR_SELECTION_EVENT);
1228 |
1229 | // remove selected features
1230 | if (this.commons.featureSelected) {
1231 | d3.select(`#${this.divId}`).select(`#${this.commons.featureSelected}`).style("fill-opacity", "0.6");
1232 | this.commons.featureSelected = null;
1233 | }
1234 | // remove selection rectangle
1235 | d3.select(`#${this.divId}`).selectAll(".selectionRect").remove();
1236 |
1237 | if (resetLastHighlight === true) {
1238 | this.lastHighlight = null;
1239 | }
1240 | }
1241 |
1242 | public resetZoom() {
1243 | this.resetHighlight()
1244 | //reset scale
1245 | d3.select(`#${this.divId}`).select(".zoomUnit").text("1");
1246 | this.commons.scaling.domain([this.commons.viewerOptions.offset.start, this.commons.viewerOptions.offset.end]);
1247 | this.commons.scalingPosition.range([this.commons.viewerOptions.offset.start, this.commons.viewerOptions.offset.end]);
1248 | let seq = this.calculate.displaySequence(this.commons.viewerOptions.offset.end - this.commons.viewerOptions.offset.start);
1249 |
1250 | d3.select(`#${this.divId}`).select(".fvbrush").call(this.commons.brush.move, null);
1251 |
1252 | // remove sequence
1253 | this.commons.svgContainer.select(".mySequence").remove();
1254 | // draw sequence
1255 | if (this.commons.viewerOptions.showSequence) {
1256 | if (seq === false) {
1257 | this.fillSVG.sequenceLine();
1258 | }
1259 | else if (seq === true) {
1260 | this.fillSVG.sequence(this.sequence.substring(this.commons.viewerOptions.offset.start, this.commons.viewerOptions.offset.end), this.commons.viewerOptions.offset.start);
1261 | }
1262 | }
1263 | this.commons.current_extend = {
1264 | length: this.commons.viewerOptions.offset.end - this.commons.viewerOptions.offset.start,
1265 | start: this.commons.viewerOptions.offset.start,
1266 | end: this.commons.viewerOptions.offset.end
1267 | };
1268 | this.commons.seqShift = 0;
1269 |
1270 | this.transition_data(this.commons.features, this.commons.viewerOptions.offset.start);
1271 | this.fillSVG.reset_axis();
1272 |
1273 | // Fire Event
1274 | if (CustomEvent) {
1275 | this.commons.svgElement.dispatchEvent(new CustomEvent(this.commons.events.ZOOM_EVENT, {
1276 | detail: {
1277 | start: 1,
1278 | end: this.sequence.length-1,
1279 | zoom: 1
1280 | }
1281 | }));
1282 | }
1283 | if (this.commons.trigger) this.commons.trigger(this.commons.events.ZOOM_EVENT, {
1284 | start: 1,
1285 | end: this.sequence.length-1,
1286 | zoom: 1
1287 | });
1288 | this.commons.currentzoom = 1;
1289 | }
1290 |
1291 | public resetAll() {
1292 |
1293 | this.resetHighlight()
1294 | this.resetZoom()
1295 |
1296 | this.emptyFeatures()
1297 | // empty features
1298 | this.commons.features = this.commons.viewerOptions.backup.features;
1299 | // redraw features
1300 | this.drawFeatures()
1301 | }
1302 |
1303 | public downloadSvg() {
1304 |
1305 | let svg_el = document.getElementById('svgContent')
1306 | let filename = "feature_viewer.svg";
1307 |
1308 | // htmlToImage.toJpeg(document.getElementById(this.divId), { quality: 0.95 })
1309 | // .then(function (dataUrl) {
1310 | // var link = document.createElement('a');
1311 | // link.download = filename;
1312 | // link.href = dataUrl;
1313 | // link.click();
1314 | // });
1315 |
1316 |
1317 | svg_el.setAttribute("xmlns", "http://www.w3.org/2000/svg");
1318 | let svgData = svg_el.outerHTML;
1319 | let preface = '\r\n';
1320 | let svgBlob = new Blob([preface, svgData], {type: "image/svg+xml;charset=utf-8"});
1321 | let svgUrl = URL.createObjectURL(svgBlob);
1322 | let downloadLink = document.createElement("a");
1323 | downloadLink.href = svgUrl;
1324 | downloadLink.download = filename;
1325 | document.body.appendChild(downloadLink);
1326 | downloadLink.click();
1327 | document.body.removeChild(downloadLink);
1328 |
1329 | }
1330 |
1331 | public clickFlag(d) {
1332 | if (d) {
1333 | // remove selected features
1334 | if (this.commons.featureSelected) {
1335 | d3.select(`#${this.commons.divId}`).select(`#${this.commons.featureSelected}`).style("fill-opacity", "0.6");
1336 | this.commons.featureSelected = null;
1337 | }
1338 | // remove selection rectangle
1339 | d3.select(`#${this.commons.divId}`).select(".selectionRect").remove();
1340 |
1341 | // empty custom tooltip
1342 | this.commons.customTooltipDiv.transition()
1343 | .duration(500)
1344 | .style("opacity", 0);
1345 | this.commons.customTooltipDiv.html("");
1346 |
1347 | // dispatches selected flag event
1348 | let id = d.id;
1349 | let flag_detail_object = {
1350 | points: this.calculate.yxPoints(d),
1351 | y: d.y,
1352 | id: d.id,
1353 | label: d.label,
1354 | flagLevel: d.flagLevel
1355 | };
1356 | // trigger flag_selected event
1357 | if (CustomEvent) {
1358 |
1359 | let eventDetail = {detail: flag_detail_object},
1360 | event = new CustomEvent(this.commons.events.FLAG_SELECTED_EVENT, eventDetail);
1361 | this.commons.svgElement.dispatchEvent(event);
1362 |
1363 | if (this.commons.viewerOptions.showSubFeatures && d.hasSubFeatures) {
1364 | this.commons.flagSelected.push(flag_detail_object.id);
1365 |
1366 | // transition to open if closed and viceversa
1367 | // add offset to margin
1368 | // update flagBackground width
1369 | // let width = d.isOpen? this.commons.viewerOptions.labelTrackWidth + (20 * d.flagLevel) - 20 : this.commons.viewerOptions.labelTrackWidth + (20 * d.flagLevel);
1370 | let width = d.isOpen? this.commons.viewerOptions.labelTrackWidth : this.commons.viewerOptions.labelTrackWidth;
1371 | if (this.commons.viewerOptions.mobileMode) {
1372 | // width = d.isOpen? this.commons.viewerOptions.labelTrackWidthMobile + (20 * d.flagLevel) - 20 : this.commons.viewerOptions.labelTrackWidthMobile + (20 * d.flagLevel);
1373 | width = d.isOpen? this.commons.viewerOptions.labelTrackWidthMobile : this.commons.viewerOptions.labelTrackWidthMobile;
1374 | }
1375 | this.commons.yAxisSVG.select(".flagBackground").attr("width", width);
1376 | this.updateWindow()
1377 |
1378 | var i;
1379 | var result = null;
1380 | for (i = 0; result == null && i < this.commons.features.length; i++) {
1381 | result = this.calculate.searchTree(this.commons.features[i], flag_detail_object.id);
1382 | }
1383 | let featureToChange = result;
1384 | if (featureToChange) {
1385 | this.changeFeature(featureToChange, !featureToChange.isOpen);
1386 | } else {
1387 | this.commons.logger.warn("Feature not found in feature array", {fvId:this.commons.divId, featureId:flag_detail_object.id})
1388 | }
1389 | }
1390 |
1391 |
1392 | } else {
1393 | this.commons.logger.warn("CustomEvent is not defined", {fvId:this.commons.divId});
1394 | }
1395 |
1396 | if (this.commons.trigger) this.commons.trigger(this.commons.events.FLAG_SELECTED_EVENT, flag_detail_object);
1397 | }
1398 |
1399 | };
1400 |
1401 | public emptyFeatures() {
1402 |
1403 | // clean feature object
1404 | let deepCopy = JSON.parse(JSON.stringify(this.commons.features))
1405 | for (const ft of this.commons.features) {
1406 | this.recursivelyRemove(ft)
1407 | }
1408 |
1409 | function checkSequence(ft) {
1410 | return ft.id === 'fv_sequence';
1411 | }
1412 |
1413 | // re-init features and yData
1414 | // this.commons.features = [];
1415 | // this.commons.yData = [];
1416 | this.commons.features = this.commons.features.filter(checkSequence)
1417 | this.commons.yData = this.commons.yData.filter(checkSequence);
1418 |
1419 | // fix axis
1420 | this.fillSVG.updateXAxis(this.commons.step)
1421 |
1422 | // transit svgContent
1423 | let container = d3.select(`#${this.divId} #svgContent`);
1424 | let newContainerH = this.commons.step * 3;
1425 | this.subfeaturesTransition.containerH(container, newContainerH); // header, sequence, axis
1426 |
1427 | // final updates based on svg heigth
1428 | if (this.commons.viewerOptions.brushActive) {
1429 | this.commons.svgContainer.selectAll(".brush rect")
1430 | .attr('height', newContainerH);
1431 | }
1432 | // re-init YPosition
1433 | this.commons.YPosition = this.commons.step;
1434 |
1435 | return deepCopy
1436 |
1437 | }
1438 |
1439 | public flagLoading(id) {
1440 | d3.select(`#${this.commons.divId}`).select("#fvoverlay").attr("class", "pageoverlay")
1441 | };
1442 |
1443 | public highlightRegion(region, featureid) {
1444 | this.resetHighlight();
1445 | let flatted = this.calculate.flatten(this.commons.features)
1446 | // features in viewer?
1447 | let feature = flatted.find(i => i.id === featureid);
1448 | if (feature) {
1449 | // find feature in the tree and all its parents;
1450 | if (feature.parent) {
1451 | // let parents = feature.parent.replace("null_", "").split("_")
1452 | for (const i of Object.keys(feature.parent)) {
1453 | let ptftid = feature.parent[i]
1454 | // let parentft = flatted.find(i => i.id === ptftid)
1455 | let parentft = this.commons.yData.find(i => i.id === ptftid);
1456 | if (!parentft.isOpen) {this.clickFlag(parentft)}
1457 | }
1458 | }
1459 | // let newid = region.id? region.id : featureid;
1460 | let regionid = "f_" + featureid + '_' + region.x + '-' + region.y;
1461 | this.tool.colorSelectedFeat(regionid, feature, this.commons.divId);
1462 | } else { this.commons.logger.warn("Selected feature id does not exist!") }
1463 |
1464 | };
1465 |
1466 | public highlightPosition(region, reset=true) {
1467 | if (reset === true) {
1468 | this.resetHighlight();
1469 | }
1470 | let start = this.commons.scaling(region.start - .5);
1471 | let end = this.commons.scaling(region.end + .5);
1472 | // remove selection rectangle if already there
1473 | let selectRect;
1474 | // color the background
1475 | if (this.commons.svgContainer.node()) {
1476 | let currentContainer = this.commons.svgContainer.node().getBoundingClientRect();
1477 | // create
1478 | selectRect = this.commons.svgContainer
1479 | .select(".brush")
1480 | .append("rect")
1481 | .attr("class", "selectionRect box-shadow")
1482 | // add shadow?
1483 | .attr("height", currentContainer.height)
1484 | // place
1485 | selectRect
1486 | .style("display", "block") // remove display none
1487 | .attr("width", end - start) // - shift from the beginning
1488 | .attr("transform", () => {
1489 | return "translate(" + start + ",0)"
1490 | })
1491 | }
1492 | this.lastHighlight = {type: 'single', selection: region}
1493 | }
1494 |
1495 | public highlightPositions(regions) {
1496 | this.resetHighlight();
1497 |
1498 | for (const region of regions) {
1499 | this.highlightPosition(region, false);
1500 | }
1501 | this.lastHighlight = {type: 'multi', selection: regions}
1502 | }
1503 |
1504 | private recursiveClick(f, condition) {
1505 | if (f.isOpen === condition) {
1506 | this.clickFlag(this.commons.yData.find(i => i.id === f.id))
1507 | if (f.subfeatures) {
1508 | for (const i of Object.keys(f.subfeatures)) {
1509 | let sf = f.subfeatures[i];
1510 | if (sf.isOpen === condition) {
1511 | // this.clickFlag(this.commons.yData.find(i => i.id === sf.id))
1512 | this.recursiveClick(sf, condition)
1513 | }
1514 | }
1515 | }
1516 | }
1517 | }
1518 |
1519 |
1520 | public collapseAll() {
1521 | for (const i of Object.keys(this.commons.features)) {
1522 | this.recursiveClick(this.commons.features[i], true)
1523 | }
1524 | }
1525 |
1526 | public expandAll() {
1527 | for (const i of Object.keys(this.commons.features)) {
1528 | this.recursiveClick(this.commons.features[i], false)
1529 | }
1530 | }
1531 |
1532 | /**
1533 | * @function
1534 | * @methodOf FeatureViewer
1535 | * @name onRegionSelected
1536 | * @return {object} Object describing the selected feature */
1537 | public onRegionSelected(listener) {
1538 | this.commons.svgElement.addEventListener(this.commons.events.FEATURE_SELECTED_EVENT, listener);
1539 | };
1540 |
1541 | public removeResizeListener() {
1542 | window.removeEventListener("resize", this.updateWindow);
1543 | };
1544 |
1545 | // edit: listener of selected flag
1546 | /**
1547 | * @function
1548 | * @methodOf FeatureViewer
1549 | * @name onFeatureSelected
1550 | * @description Expected usage: once flag is selected, addSubFeature()
1551 | * @return {object} Object describing the selected flag */
1552 | public onFeatureSelected(listener) {
1553 | this.commons.svgElement.addEventListener(this.commons.events.FLAG_SELECTED_EVENT, listener);
1554 | };
1555 |
1556 | /**
1557 | * @function
1558 | * @methodOf FeatureViewer
1559 | * @name onButtonSelected
1560 | * @return {object} Object describing the selected 3D button */
1561 | public onButtonSelected(listener) {
1562 | this.commons.svgElement.addEventListener(this.commons.events.TAG_SELECTED_EVENT, listener);
1563 | };
1564 |
1565 | /**
1566 | * @function
1567 | * @methodOf FeatureViewer
1568 | * @name onZoom
1569 | * @return {object} Object describing the zoom event */
1570 | public onZoom(listener) {
1571 | this.commons.svgElement.addEventListener(this.commons.events.ZOOM_EVENT, listener);
1572 | };
1573 |
1574 | // edit: listener of clear selection
1575 | /**
1576 | * @function
1577 | * @methodOf FeatureViewer
1578 | * @name onClearSelection
1579 | * @return {object} Object describing the zoom out/clear selection event */
1580 | public onClearSelection(listener) {
1581 | this.commons.svgElement.addEventListener(this.commons.events.CLEAR_SELECTION_EVENT, listener);
1582 | };
1583 |
1584 | // edit: listener of animation off
1585 | /**
1586 | * @function
1587 | * @methodOf FeatureViewer
1588 | * @name onAnimationOff
1589 | * @return {object} Object describing the zoom out/clear selection event */
1590 | public onAnimationOff(listener) {
1591 | this.commons.svgElement.addEventListener(this.commons.events.ANIMATIONS_FALSE_EVENT, listener);
1592 | };
1593 |
1594 | public stopFlagLoading = function (id) {
1595 | d3.select(`#${this.divId}`).select("#fvoverlay").attr("class", null)
1596 | };
1597 |
1598 | // function to call resize from external
1599 | /**
1600 | * @function
1601 | * @methodOf FeatureViewer
1602 | * @name resizeViewer
1603 | * @description resizes viewer if element dimensions are changed. Please note: resizing is automatic when window changes, call this function when other elements change
1604 | */
1605 | public resizeViewer = function () {
1606 | this.updateWindow()
1607 | };
1608 |
1609 | /**
1610 | * @function
1611 | * @methodOf FeatureViewer
1612 | * @name addFeature
1613 | * @param {object} object - The input feature
1614 | * @param {number} flagLevel - The indent level for rendering flag
1615 | * @property {Array} feature.data
1616 | * @property {int} feature.data..x - first position
1617 | * @property {int} feature.data..y - last position (or a value for features of type "curve")
1618 | * @property {string} [feature.data..id] - id
1619 | * @property {string} [feature.data..description] - description
1620 | * @property {string} [feature.data..color] - color
1621 | * @property {string} [feature.data..tooltip] - message for the region tooltip
1622 | * @property {string} feature.type - ("rect","curve","unique","circle") : The type of feature, for a specific rendering
1623 | * @property {string} [feature.name] - The name of theses features, which will be display as a label on the Y-axis
1624 | * @property {string} [feature.className] - a class name, for further personal computing
1625 | * @property {int} [feature.height] - height of the feature
1626 | * @property {string} [feature.color] - The color of the features
1627 | * @property {boolean} [feature.hasSubFeatures] - determines if object is clickable and expands for subFeature visualization
1628 | * @property {string} [feature.filter] - a class filter, for further personal computing
1629 | * @property {number} [feature.disorderContent] - content of disorder content tag (right side of viewer)
1630 | * @property {number} [feature.tooltip] - message for the flag tooltip
1631 | * @property {Array} [feature.links]
1632 | * @property {string} [feature.links..name] - The button name, used to identify click event
1633 | * @property {string} [feature.links..icon] - Glyphicon code or text, specify glyphicon in unicode format, ex. \ue030
1634 | * @property {string} [feature.links..message] - The message for tooltip
1635 | * @property {string} [feature.links..color] - Optional color for the visualized glyphicon
1636 | */
1637 | private addFeature(object: FeatureObject, flagLevel=1) {
1638 | this.addFeatureCore(object, flagLevel);
1639 | if (object.subfeatures && object.isOpen) {
1640 | flagLevel+=1
1641 | for (const sft of object.subfeatures) {
1642 | this.addFeature(sft, flagLevel)
1643 | }
1644 | } return object.id
1645 | }
1646 |
1647 | public addFeatures(featureList: FeaturesList) {
1648 | this.commons.viewerOptions.backup.features = featureList;
1649 | let featureids = featureList.map(item => item.id).filter(e => {return e});
1650 |
1651 | // check ids are present
1652 | if (featureids.length !== featureList.length) {
1653 | this.commons.logger.error("Feature ids are not present on all features, creating random ids",
1654 | {method:'addFeatures',fvId:this.divId})
1655 | for (let f of featureList) {
1656 | if (!f.id) {f.id = 'customid' + Math.random().toString(36).substring(7);}
1657 | }
1658 | featureids = featureList.map(item => item.id).filter(e => {return e});
1659 | }
1660 |
1661 | let regexIds = new RegExp('^[a-zA-Z]')
1662 |
1663 | // check ids are valid
1664 | for (let f of featureList) {
1665 | if (!regexIds.test(f.id)) {
1666 | this.commons.logger.error("Id " + f.id + "is not a valid html id, substituting it randomly",
1667 | {method:'addFeatures',fvId:this.divId})
1668 | f.id = 'customid' + Math.random().toString(36).substring(7);
1669 | }
1670 | }
1671 | featureids = featureList.map(item => item.id).filter(e => {return e});
1672 |
1673 | // check ids are unique
1674 | const uniqueIds = [...new Set(featureids)];
1675 | if (uniqueIds.length !== featureList.length) {
1676 | this.commons.logger.error("Feature ids are not unique, substituting the non unique ones randomly",
1677 | {method:'addFeatures',fvId:this.divId})
1678 | let idsencountered = [];
1679 | for (let f of featureList) {
1680 | if (idsencountered.includes(f.id)) {f.id = 'customid' + Math.random().toString(36).substring(7);}
1681 | idsencountered.push(f.id);
1682 | }
1683 | featureids = featureList.map(item => item.id).filter(e => {return e});
1684 | }
1685 | // add to viewer
1686 |
1687 | // features already in viewer?
1688 | let unflatted = this.calculate.unflatten(
1689 | featureList,
1690 | null,
1691 | null,
1692 | this.commons.features.length !== 0 ? this.commons.features : null
1693 | );
1694 |
1695 | // add new structured features to the old ones (if any, else sequence)
1696 | this.commons.features = this.commons.features.concat(unflatted.tree);
1697 | let ftsIds = unflatted.ids;
1698 |
1699 | if (!this.commons.viewerOptions.maxDepth) {
1700 | this.commons.viewerOptions.maxDepth = Math.max(...this.commons.features.map(f => this.getLevel(f, 1)));
1701 | }
1702 |
1703 |
1704 | // check if features are missing from the tree
1705 | let unprocessedIds = uniqueIds.filter((x)=>{
1706 | return !(ftsIds.has(x))
1707 | });
1708 | if (unprocessedIds.length !== 0) {
1709 | this.commons.logger.error("Subfeatures with no known parentId", {method:'addFeatures', fvId: this.divId, features:unprocessedIds})
1710 | }
1711 |
1712 | // features already in viewer? empty it before drawing
1713 | this.commons.features = this.emptyFeatures()
1714 |
1715 | // draw the viewer
1716 | this.drawFeatures()
1717 |
1718 | }
1719 |
1720 | private getLevel(f, l) {
1721 | l++
1722 | if (f.hasOwnProperty('subfeatures')) {
1723 | l = this.getLevel(f.subfeatures, l)
1724 | }
1725 | return l
1726 | }
1727 |
1728 | constructor(sequence: string, div: string, options?: UserOptions, features?: FeaturesList) {
1729 |
1730 | this.commons = new Commons();
1731 |
1732 | // init commons
1733 | this.commons.yData = [];
1734 | this.commons.features = [];
1735 | this.commons.YPosition = this.commons.step;
1736 |
1737 | // read divId
1738 | this.commons.divId = this.divId = div.slice(1).toString();
1739 | this.commons.logger = new FeatureViewerLog(); // TODO this.divId as input
1740 |
1741 | // sequence and seq length
1742 | this.sequence = sequence;
1743 | this.commons.fvLength = sequence.length;
1744 |
1745 | // parse user options
1746 | if (options) {
1747 | this.parseUserOptions(options);
1748 | // sets width too, new re-set it again but in case of window resize
1749 | } else {
1750 | this.parseUserOptions({});
1751 | }
1752 |
1753 | this.fillSVG = new FillSVG(this.commons);
1754 | this.subfeaturesTransition = new SubfeaturesTransition(this.commons);
1755 | this.transition = new Transition(this.commons); // extends computingFunctions
1756 | this.calculate = new Calculate(this.commons);
1757 | this.tool = new Tool(this.commons);
1758 |
1759 | this.init(div);
1760 |
1761 | // features?
1762 | if (features) {
1763 | this.addFeatures(features)
1764 | }
1765 | this.resizeViewer();
1766 | }
1767 | }
1768 |
1769 | export {FeatureViewer};
1770 |
--------------------------------------------------------------------------------
/src/fillsvg.ts:
--------------------------------------------------------------------------------
1 | import ComputingFunctions from "./helper";
2 | import Calculate from "./calculate";
3 | import * as d3 from './custom-d3';
4 |
5 | class PreComputing {
6 |
7 | private commons;
8 | private calculate: Calculate;
9 |
10 | public path(object) {
11 |
12 | let height;
13 | if (object.height) { height = object.height } else { height = 3 }
14 | object.data.sort((a, b) => {
15 | return a.x - b.x;
16 | });
17 | this.commons.level = this.calculate.addNLines(object.data);
18 | object.data = object.data.map((d) => {
19 | return [{
20 | x: d.x,
21 | y: 0,
22 | id: d.id,
23 | description: d.label || '',
24 | tooltip: d.tooltip || '',
25 | color: d.color,
26 | stroke: d.stroke,
27 | opacity: d.opacity
28 | }, {
29 | x: d.y,
30 | y: d.level + 1,
31 | id: d.id
32 | }, {
33 | x: d.y,
34 | y: 0,
35 | id: d.id
36 | }]
37 | });
38 | // object.pathLevel = this.commons.level * height + 5; // changed this according to Necci's last commit
39 | object.pathLevel = (this.commons.level * height) / 2 + 5;
40 | this.commons.pathLevel = this.commons.level * height + 5;
41 | object.height = this.commons.level * height + 5;
42 |
43 | };
44 |
45 | public preComputingLine(object) {
46 | const yScores = object.data[0].map(o => o.y);
47 | const maxScore = Math.max(...yScores);
48 | const minScore = Math.min(...yScores);
49 |
50 | if (!object.height) { object.height = this.commons.step / 2 }
51 | let shift = parseInt(object.height);
52 | let level = 0;
53 |
54 | for (const i of object.data.keys()) {
55 | object.data[i].sort((a, b) => {
56 | return a.x - b.x;
57 | });
58 | if (object.data[i][0].y !== 0) {
59 | object.data[i].unshift({
60 | x: object.data[i][0].x - 1,
61 | y: 0
62 | })
63 | }
64 | if (object.data[i][object.data[i].length - 1].y !== 0) {
65 | object.data[i].push({
66 | x: object.data[i][object.data[i].length - 1].x + 1,
67 | y: 0
68 | })
69 | }
70 | let maxValue = Math.max.apply(Math, object.data[i].map((o) => {
71 | //return Math.abs(o.y);
72 | //return Math.round(Math.abs(o.y))+1;
73 | return 1;
74 | }));
75 | // overwrite this value if given option ymax
76 | if ('yLim' in object) {
77 | // maxValue = maxScore
78 | maxValue = object['yLim'];
79 | }
80 | level = maxValue > level ? maxValue : level;
81 |
82 | object.data[i] = [object.data[i].map((d) => {
83 | let yValue = d.y;
84 | if (d.y > maxValue) {
85 | yValue = maxValue;
86 | }
87 | return {
88 | x: d.x ,
89 | y: yValue,
90 | id: d.id,
91 | description: d.label || '',
92 | tooltip: d.tooltip || ''
93 | }
94 | })
95 | ]
96 | }
97 |
98 | // this.commons.lineYScale.range([0, -(shift)]).domain([0, -(level)]);
99 | this.commons.lineYScale.domain([minScore, maxScore]).range([0, this.commons.step/11]);
100 |
101 | object.pathLevel = shift * 10 + 5;
102 | object.level = level;
103 | object.shift = shift * 10 + 5;
104 |
105 | };
106 |
107 | public multipleRect(object) {
108 | object.data.sort((a, b) => {
109 | return a.x - b.x;
110 | });
111 | object.level = this.calculate.addNLines(object.data);
112 | object.pathLevel = this.commons.level * 10 + 5;
113 |
114 | this.commons.level = object.level;
115 | this.commons.pathLevel = object.pathLevel;
116 |
117 | };
118 |
119 | constructor(commons) {
120 | this.commons = commons;
121 | this.calculate = new Calculate(commons);
122 | }
123 | }
124 |
125 | class FillSVG extends ComputingFunctions {
126 |
127 | private preComputing: PreComputing;
128 | private calculate: Calculate;
129 | private storeData;
130 |
131 | private hexToRgb(hex) {
132 | // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
133 | var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
134 | hex = hex.replace(shorthandRegex, function(m, r, g, b) {
135 | return r + r + g + g + b + b;
136 | });
137 |
138 | var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
139 | return result ? {
140 | r: parseInt(result[1], 16),
141 | g: parseInt(result[2], 16),
142 | b: parseInt(result[3], 16)
143 | } : null;
144 | };
145 |
146 | private isLight(mycolor) {
147 | let color = mycolor.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)$/);
148 | let r = color[1];
149 | let g = color[2];
150 | let b = color[3];
151 | // HSP (Highly Sensitive Poo) equation from http://alienryderflex.com/hsp.html
152 | let hsp = Math.sqrt(
153 | 0.299 * (r * r) +
154 | 0.587 * (g * g) +
155 | 0.114 * (b * b)
156 | );
157 | // Using the HSP value, determine whether the color is light or dark
158 | if (hsp>127.5) {
159 | return true;
160 | }
161 | else {
162 | return false;
163 | }
164 | }
165 |
166 | private sbcRip(d, i, r) {
167 | let l = d.length, RGB = {};
168 | if (l > 9) {
169 | d = d.split(",");
170 | if (d.length < 3 || d.length > 4) return null;//ErrorCheck
171 | RGB[0] = i(d[0].slice(4));
172 | RGB[1] = i(d[1]);
173 | RGB[2] = i(d[2]);
174 | RGB[3] = d[3] ? parseFloat(d[3]) : -1;
175 | } else {
176 | if (l === 8 || l === 6 || l < 4) return null; //ErrorCheck
177 | if (l < 6) d = "#" + d[1] + d[1] + d[2] + d[2] + d[3] + d[3] + (l > 4 ? d[4] + "" + d[4] : ""); //3 digit
178 | d = i(d.slice(1), 16);
179 | RGB[0] = d >> 16 & 255;
180 | RGB[1] = d >> 8 & 255;
181 | RGB[2] = d & 255;
182 | RGB[3] = l === 9 || l === 5 ? r(((d >> 24 & 255) / 255) * 10000) / 10000 : -1;
183 | }
184 | return RGB;
185 | };
186 |
187 | private shadeBlendConvert(p, from="#000", to=null) {
188 | if (typeof(p) !== "number" ||
189 | p < -1 ||
190 | p > 1 ||
191 | typeof(from) !== "string" ||
192 | (from[0] !== 'r' && from[0] !== '#') ||
193 | (typeof(to) !== "string" && typeof(to) !== "undefined")) return null; //ErrorCheck
194 | let i = parseInt;
195 | let r = Math.round;
196 | let h = from.length > 9;
197 | h = typeof(to) === "string" ? to.length > 9 ? true : to === "c" ? !h : false : h;
198 | let b = p < 0;
199 | p = b ? p * -1 : p;
200 | to = to && to !== "c" ? to : b ? "#000000" : "#FFFFFF";
201 | let f = this.sbcRip(from, i, r);
202 | let t = this.sbcRip(to, i, r);
203 | if (!f || !t) return null; //ErrorCheck
204 | if (h) return "rgb(" + r((t[0] - f[0]) * p + f[0]) + "," + r((t[1] - f[1]) * p + f[1]) + "," + r((t[2] - f[2]) * p + f[2]) + (f[3] < 0 && t[3] < 0 ? ")" : "," + (f[3] > -1 && t[3] > -1 ? r(((t[3] - f[3]) * p + f[3]) * 10000) / 10000 : t[3] < 0 ? f[3] : t[3]) + ")");
205 | else return "#" + (0x100000000 + (f[3] > -1 && t[3] > -1 ? r(((t[3] - f[3]) * p + f[3]) * 255) : t[3] > -1 ? r(t[3] * 255) : f[3] > -1 ? r(f[3] * 255) : 255) * 0x1000000 + r((t[0] - f[0]) * p + f[0]) * 0x10000 + r((t[1] - f[1]) * p + f[1]) * 0x100 + r((t[2] - f[2]) * p + f[2])).toString(16).slice(f[3] > -1 || t[3] > -1 ? 1 : 3);
206 | }
207 |
208 | public typeIdentifier(feature) {
209 | let thisYPosition;
210 | if (feature.type === "curve") {
211 | if (!feature.height) { feature.height = 10 }
212 | let shift = parseInt(feature.height);
213 |
214 | thisYPosition = this.commons.YPosition //+ shift * 10 ;
215 | } else {
216 | thisYPosition = this.commons.YPosition;
217 | }
218 |
219 | this.tagArea(feature, thisYPosition);
220 | // yData is data for flags, this.rectangle etc. draw the actual objects
221 |
222 | this.commons.yData.push({
223 | hasSubFeatures: feature.subfeatures ? true: false,
224 | tooltip: feature.tooltip,
225 | label: feature.label,
226 | id: feature.id,
227 | y: thisYPosition,
228 | flagColor: feature.flagColor,
229 | flagLevel: feature.flagLevel,
230 | isOpen: feature.isOpen,
231 | ladderColor: feature.ladderColor? feature.ladderColor : null,
232 | ladderLabel: feature.ladderLabel? feature.ladderLabel : null,
233 | ladderBgColor: feature.ladderBgColor? feature.ladderBgColor : null,
234 | ladderBorderColor: feature.ladderBorderColor? feature.ladderBorderColor : null,
235 | yMin: feature.yMin? feature.yMin : null,
236 | yMax: feature.yMax? feature.yMax : null
237 | });
238 |
239 | if (feature.type === "rect") {
240 |
241 | this.preComputing.multipleRect(feature);
242 | this.rectangle(feature, this.commons.YPosition);
243 |
244 | }
245 | else if (feature.type === "text") {
246 |
247 | this.commons.scaling.range([5, this.commons.viewerOptions.width - 5]);
248 | let seq = this.displaySequence(this.commons.current_extend.length);
249 | if (seq === false) {
250 | this.sequenceLine();
251 | }
252 | else if (seq === true) {
253 | this.sequence(feature.data, this.commons.YPosition);
254 | }
255 | //fillSVG.sequence(object.data, YPosition);
256 |
257 | }
258 | else if (feature.type === "unique") {
259 |
260 | this.unique(feature, this.commons.YPosition);
261 | // this.commons.YPosition += 5;
262 |
263 | }
264 | else if (feature.type === "circle") {
265 |
266 | this.circle(feature, this.commons.YPosition);
267 | // this.commons.YPosition += 5;
268 |
269 | }
270 | else if (feature.type === "multipleRect") {
271 |
272 | this.preComputing.multipleRect(feature);
273 | this.multipleRect(feature, this.commons.YPosition, this.commons.level);
274 | this.commons.YPosition += (this.commons.level - 1) * 10;
275 |
276 | }
277 | else if (feature.type === "path") {
278 |
279 | // this type of object overwrites object data, after fillSVG go back to original
280 | this.storeData = feature.data;
281 | this.preComputing.path(feature);
282 | this.path(feature, this.commons.YPosition - 8);
283 | feature.data = this.storeData;
284 | // this.commons.YPosition += this.commons.pathLevel;
285 |
286 | }
287 | else if (feature.type === "curve") {
288 |
289 | // this type of object overwrites object data, after fillSVG go back to original
290 | this.storeData = feature.data;
291 | if (!(Array.isArray(feature.data[0]))) feature.data = [feature.data];
292 | if (!(Array.isArray(feature.color))) feature.color = [feature.color];
293 | let negativeNumbers = false;
294 | feature.data.forEach((d) => {
295 | if (d.filter((l) => {
296 | return l.y < 0
297 | }).length) negativeNumbers = true;
298 | });
299 | this.preComputing.preComputingLine(feature);
300 |
301 | this.fillSVGLine(feature, this.commons.YPosition);
302 | feature.data = this.storeData;
303 | this.commons.YPosition += this.commons.step // feature.pathLevel; // 7
304 | // this.commons.YPosition += negativeNumbers ? feature.pathLevel - 5 : 0;
305 |
306 | }
307 | else if (feature.type === "lollipop") {
308 | this.commons.YPosition += 7;
309 | this.lollipop(feature, this.commons.YPosition);
310 |
311 | }
312 | }
313 |
314 | public tagArea(object, thisYPosition) {
315 |
316 | // var threeArray = [showDisorderContentTag, showViewerTag, showLinkTag];
317 |
318 | // adjust height not triangle
319 | if (object.type !== 'rect') {thisYPosition -= 5}
320 |
321 | let id = 't' + object.id + "_tagarea";
322 | let featureArea = this.commons.tagsContainer.append("g")
323 | .attr("class", "tagGroup")
324 | .attr("id", id)
325 | .attr("transform", "translate(0," + thisYPosition + ")");
326 |
327 | // ad areas in any case
328 | if (object.sidebar) {
329 |
330 | let objectPos = 0;
331 | // check type and add html elements accordingly
332 | for (const bt of object.sidebar) {
333 | if (bt.type) {
334 | if (bt.type !== "button" && bt.type !== "percentage" && bt.type !== "link" && bt.type !== "icon") {
335 | this.commons.logger.error("Unknown type of button", {method:'addFeatures',fvId:this.commons.divId,featureId:object.id,buttonId:bt.buttonId})
336 | } else {
337 |
338 | let gButton = featureArea
339 | .append('g')
340 | .attr("id", id + '_button_' + bt.id)
341 | .attr("transform", "translate(" + objectPos + ",0)")
342 | .data([{
343 | label: object.label,
344 | featureId: object.id,
345 | data: object,
346 | type: "button",
347 | id: bt.id,
348 | tooltip: bt.tooltip
349 | }]);
350 |
351 | let content;
352 | if (bt.type == "button") {
353 | let cl = this.hexToRgb(this.commons.viewerOptions.flagColor);
354 | let col = 'rgba(' + [cl.b, cl.g, cl.r].join(',') + ')'
355 | let colalph = 'rgba(' + [cl.b, cl.g, cl.r].join(',') + ',0.8)'
356 | let coltext = this.isLight(col) ? 'black' : 'white';
357 | content = `${bt.label} `
358 | }
359 | else if (bt.type == "percentage") {
360 | let disordersString = bt.label.toString() + '%';
361 | let colorrgb = this.hexToRgb(this.gradientColor(bt.label));
362 | let color = 'rgba(' + [colorrgb.r, colorrgb.g, colorrgb.b].join(',') + ')';
363 | let textColor = this.isLight(color) ? "black" : "white";
364 | content = `${disordersString} `
365 | }
366 | else if (bt.type == "link") {
367 | let linkicon = "M9.26 13c-0.167-0.286-0.266-0.63-0.266-0.996 0-0.374 0.103-0.724 0.281-1.023l-0.005 0.009c1.549-0.13 2.757-1.419 2.757-2.99 0-1.657-1.343-3-3-3-0.009 0-0.019 0-0.028 0l0.001-0h-4c-1.657 0-3 1.343-3 3s1.343 3 3 3v0h0.080c-0.053 0.301-0.083 0.647-0.083 1s0.030 0.699 0.088 1.036l-0.005-0.036h-0.080c-2.761 0-5-2.239-5-5s2.239-5 5-5v0h4c0.039-0.001 0.084-0.002 0.13-0.002 2.762 0 5.002 2.239 5.002 5.002 0 2.717-2.166 4.927-4.865 5l-0.007 0zM10.74 7c0.167 0.286 0.266 0.63 0.266 0.996 0 0.374-0.103 0.724-0.281 1.023l0.005-0.009c-1.549 0.13-2.757 1.419-2.757 2.99 0 1.657 1.343 3 3 3 0.009 0 0.019-0 0.028-0l-0.001 0h4c1.657 0 3-1.343 3-3s-1.343-3-3-3v0h-0.080c0.053-0.301 0.083-0.647 0.083-1s-0.030-0.699-0.088-1.036l0.005 0.036h0.080c2.761 0 5 2.239 5 5s-2.239 5-5 5v0h-4c-0.039 0.001-0.084 0.002-0.13 0.002-2.762 0-5.002-2.239-5.002-5.002 0-2.717 2.166-4.927 4.865-5l0.007-0z"
368 | content = ` `
369 | }
370 | else if (bt.type == "icon") {
371 | content = ` `
372 | }
373 |
374 | gButton
375 | .append('foreignObject') // foreignObject can be styled with no limitation by user
376 | .attr("width", "100%")
377 | .attr("height", "100%")
378 | .attr("y",-6)
379 | .html(content)
380 |
381 | if (bt.type !== "percentage") {
382 | gButton.call(this.commons.d3helper.genericTooltip(bt));
383 | }
384 |
385 | // update object position
386 | objectPos += (d3.select('#'+bt.id).node()).getBoundingClientRect().width + 3;
387 |
388 | }
389 | }
390 | else if (bt.content) {
391 |
392 | let gHtml = featureArea
393 | .append('g')
394 | .attr("id", id + '_button_' + bt.id)
395 | .attr("transform", "translate(" + (objectPos + 3) + ",0)")
396 | .data([{
397 | label: object.label,
398 | featureId: object.id,
399 | data: object,
400 | type: "button",
401 | id: bt.id
402 | }]);
403 |
404 | gHtml
405 | .append('foreignObject')
406 | .attr("y", -6)
407 | .attr("width", "100%")
408 | .attr("height", "100%")
409 | .attr("height", "100%")
410 | .append('xhtml:body')
411 | .style("margin", "0")
412 | .attr("id", bt.id)
413 | .html(bt.content)
414 | .call(this.commons.d3helper.genericTooltip(bt));
415 |
416 | // objectPos += 50;
417 | // get width of the drawn object
418 | try {
419 | let contentwidth = 0;
420 | if (bt.width)
421 | contentwidth = bt.width;
422 | else
423 | contentwidth = (d3.select(`#${bt.id}`).select('*').node()).getBoundingClientRect().width
424 | objectPos += contentwidth + 5;
425 | } catch (e) {
426 | objectPos += 100;
427 | }
428 |
429 | } else {
430 | this.commons.logger.error("Neither html content nor type of button is specified", {method:'addFeatures',fvId:this.commons.divId,featureId:object.id,buttonId:bt.buttonId})
431 | }
432 | }
433 |
434 | }
435 | }
436 |
437 | public sequence(seq, start = 0) {
438 | // remove eventual sequence still there (in transitions)
439 | this.commons.svgContainer.selectAll(".mySequence").remove();
440 | //Create group of sequence
441 | let sequenceAAs = this.commons.svgContainer.append("g").attr("class", "mySequence sequenceGroup");
442 | sequenceAAs
443 | .selectAll(".AA")
444 | .data(seq)
445 | .enter()
446 | .append("text")
447 | //.attr("clip-path", "url(#clip)") // firefox compatibility
448 | .attr("class", "AA")
449 | .attr("text-anchor", "middle")
450 | .attr("x", (d, i) => {
451 | // index starts from 0
452 | return this.commons.scaling.range([2, this.commons.viewerOptions.width - 2])(i + start)
453 | })
454 | .attr("y", this.commons.step)
455 | .attr("font-size", "12px")
456 | .attr("font-family", "monospace")
457 | .text((d) => {
458 | return d
459 |
460 | })
461 |
462 | }
463 |
464 | public sequenceLine() {
465 | // remove eventual sequence already there (in transitions)
466 | this.commons.svgContainer.selectAll(".mySequence").remove();
467 | //Create line to represent the sequence
468 | if (this.commons.viewerOptions.dottedSequence) {
469 | let dottedSeqLine = this.commons.svgContainer.selectAll(".sequenceLine")
470 | .data([[{x: 1, y: this.commons.step - this.commons.elementHeight / 2}, {
471 | x: this.commons.fvLength,
472 | y: this.commons.step - this.commons.elementHeight / 2
473 | }]])
474 | //.scale(scaling)
475 | .enter()
476 | .append("path")
477 | // .attr("clip-path", "url(#clip)") // firefox compatibility
478 | .attr("d", this.commons.line)
479 | .attr("class", "mySequence sequenceLine")
480 | .style("z-index", "0")
481 | .style("stroke", "grey")
482 | .style("stroke-dasharray", "1,3")
483 | .style("stroke-width", "1px")
484 | .style("stroke-opacity", 0);
485 |
486 | dottedSeqLine
487 | .transition()
488 | .duration(500)
489 | .style("stroke-opacity", 1);
490 | }
491 | }
492 |
493 | public rectangle(object, position) {
494 |
495 | //var rectShift = 20;
496 | if (!object.height) object.height = this.commons.elementHeight;
497 | let rectHeight = this.commons.elementHeight;
498 |
499 | let rectShift = rectHeight + rectHeight / 3;
500 | let lineShift = rectHeight / 2 - 6;
501 | position = Number(position) + 3; // center line
502 |
503 | let rectsPro = this.commons.svgContainer.append("g")
504 | .attr("class", "rectangle featureLine")
505 | //.attr("clip-path", "url(#clip)") // firefox compatibility
506 | .attr("transform", "translate(0," + position + ")")
507 | .attr("id", () => {
508 | // random string
509 | // return divId + '_' + d.title.split(" ").join("_") + '_g'
510 | return 'c' + object.id + '_container'
511 | });
512 | // commenting to dist
513 |
514 | let dataLine = [];
515 | // case with empty data
516 | if (!this.commons.level) {
517 | this.commons.level = 1
518 | }
519 | for (let i = 0; i < this.commons.level; i++) {
520 | dataLine.push([{
521 | x: 1,
522 | y: (i * rectShift + lineShift),
523 | }, {
524 | x: this.commons.fvLength,
525 | y: (i * rectShift + lineShift)
526 | }]);
527 | }
528 |
529 | rectsPro.selectAll(".line " + object.className)
530 | .data(dataLine)
531 | .enter()
532 | .append("path")
533 | .attr("d", this.commons.line)
534 | .attr("class", () => {
535 | return "line " + object.className
536 | })
537 | .style("z-index", "0")
538 | .style("stroke", object.color)
539 | .style("stroke-width", "1px");
540 |
541 |
542 | let rectsProGroup = rectsPro.selectAll("." + object.className + "Group")
543 | .data(object.data)
544 | .enter()
545 | .append("g")
546 | .attr("class", object.className + "Group")
547 | .attr("transform", (d) => {
548 | return "translate(" + this.rectX(d) + ",0)"
549 | });
550 |
551 | rectsProGroup
552 | .append("rect")
553 | .attr("class", "element " + object.className)
554 | .attr("id", (d) => {
555 | // add id to object
556 | let id = "f_" + object.id + '_' + d.x + '-' + d.y;
557 | d.id = id;
558 | return id;
559 | })
560 | .attr("y", (d) => {
561 | return d.level * rectShift
562 | })
563 | .attr("ry", (d) => {
564 | return this.commons.radius;
565 | })
566 | .attr("rx", (d) => {
567 | return this.commons.radius;
568 | })
569 | .attr("width", (d) => {
570 | return this.rectWidth2(d)
571 | })
572 | .attr("height", this.commons.elementHeight)
573 | .style("fill", (d) => {
574 | return d.color || object.color
575 | })
576 | .style("fill-opacity", (d) => {
577 | if (d.opacity) {
578 | return d.opacity
579 | } else if (object.opacity) {
580 | return object.opacity
581 | } else {
582 | return "0.6"
583 | }
584 | })
585 | .style("stroke", (d) => {
586 | if ("stroke" in d) {
587 | return d.stroke
588 | } else if ("stroke" in object) {
589 | return object.stroke
590 | } else {
591 | return d.color
592 | }
593 | })
594 | .style("z-index", "13")
595 | .call(this.commons.d3helper.tooltip(object));
596 |
597 |
598 | rectsProGroup
599 | .append("text")
600 | .attr("class", "element " + object.className + "Text")
601 | .attr("y", (d) => {
602 | return d.level * rectShift + rectHeight / 2
603 | })
604 | .attr("dy", "0.35em")
605 | .style("font-size", "10px")
606 | .text((d) => {
607 | return d.label
608 | })
609 | // .style("fill", "rgba(39, 37, 37, 0.9)")
610 | // .style("z-index", "15")
611 | .style("visibility", (d) => {
612 | if (d.label) {
613 | return (this.commons.scaling(d.y) - this.commons.scaling(d.x)) > d.label.length * 8 && object.height > 11 ? "visible" : "hidden";
614 | } else return "hidden";
615 | })
616 | .call(this.commons.d3helper.tooltip(object));
617 |
618 | this.forcePropagation(rectsProGroup);
619 | let uniqueShift = rectHeight > 12 ? rectHeight - 6 : 0;
620 | this.commons.YPosition += this.commons.level < 2 ? uniqueShift : (this.commons.level - 1) * rectShift + uniqueShift;
621 |
622 | }
623 |
624 | public unique(object, position) {
625 |
626 | let rectsPro = this.commons.svgContainer.append("g")
627 | .attr("class", "uniquePosition featureLine")
628 | .attr("transform", "translate(0," + position + ")")
629 | .attr("id", () => {
630 | // random string'
631 | return 'c' + object.id + '_container'
632 | });
633 |
634 | let dataLine = [];
635 | // case with empty data
636 | if (!this.commons.level) {
637 | this.commons.level = 1
638 | }
639 | for (let i = 0; i < this.commons.level; i++) {
640 | dataLine.push([{
641 | x: 1,
642 | y: 0,
643 | }, {
644 | x: this.commons.fvLength,
645 | y: 0
646 | }]);
647 | }
648 |
649 | rectsPro.selectAll(".line " + object.className)
650 | .data(dataLine)
651 | .enter()
652 | .append("path")
653 | .attr("d", this.commons.line)
654 | .attr("class", () => {
655 | return "line " + object.className
656 | })
657 | .style("z-index", "0")
658 | .style("stroke", object.color)
659 | .style("stroke-width", "1px");
660 |
661 | let readyData = [...object.data];
662 |
663 | rectsPro.selectAll("." + object.className + 'Unique')
664 | .data(readyData)
665 | .enter()
666 | .append("rect")
667 | // .attr("clip-path", "url(#clip)") // firefox compatibility
668 | .attr("class", "element " + object.className)
669 | .attr("id", (d) => {
670 | let id = "f_" + object.id + '_' + d.x + '-' + d.y;
671 | d.id = id;
672 | return id;
673 | })
674 | .attr("x", (d) => {
675 | return this.commons.scaling(d.x - 0.4)
676 | })
677 | .attr("width", (d) => {
678 | if (this.commons.scaling(d.x + 0.4) - this.commons.scaling(d.x - 0.4) < 2) return 2;
679 | else return this.commons.scaling(d.x + 0.4) - this.commons.scaling(d.x - 0.4);
680 | })
681 | .attr("height", this.commons.elementHeight)
682 | .style("fill", (d) => {
683 | return d.color || object.color
684 | })
685 | .style("z-index", "3")
686 | .call(this.commons.d3helper.tooltip(object));
687 |
688 | this.forcePropagation(rectsPro);
689 | }
690 |
691 | public lollipop(object, position) {
692 | let circlesPro = this.commons.svgContainer.append("g")
693 | .attr("class", "pointPosition featureLine")
694 | .attr("transform", "translate(0," + position + ")")
695 | .attr("id", () => {
696 | // random string
697 | // return divId + '_' + d.title.split(" ").join("_") + '_g'
698 | return 'c' + object.id + '_container'
699 | });
700 |
701 | let dataLine = [];
702 | dataLine.push([{
703 | x: 1,
704 | y: 0
705 | }, {
706 | x: this.commons.fvLength,
707 | y: 0
708 | }]);
709 |
710 | // basal line
711 | circlesPro.selectAll(".line " + object.className)
712 | .data(dataLine)
713 | .enter()
714 | .append("path")
715 | .attr("d", this.commons.line)
716 | .attr("class", () => {
717 | return "line " + object.className
718 | })
719 | .style("z-index", "0")
720 | .style("stroke", 'gray')
721 | .style("stroke-width", "0.5px");
722 |
723 | let readyData = [...object.data];
724 |
725 | // lollipop base
726 | circlesPro.selectAll("." + object.className + 'Lollipop')
727 | .data(readyData)
728 | .enter()
729 | .append("line")
730 | .attr("x1", (d) => {return this.commons.scaling(d.x)})
731 | .attr("x2", (d) => {return this.commons.scaling(d.x)})
732 | .attr("y2", (d) => {
733 | let w = this.commons.scaling(d.x + 0.4) - this.commons.scaling(d.x - 0.4);
734 | if (this.commons.scaling(d.x + 0.4) - this.commons.scaling(d.x - 0.4) < 2) w = 2;
735 | return w + 2;
736 | })
737 | .attr("y1", -8)
738 | .attr("class", "lineElement " + object.className)
739 | .style("stroke", (d) => {
740 | return d.color || object.color
741 | })
742 | .style("stroke-width", 1)
743 |
744 | // lollipop head
745 | circlesPro.selectAll("." + object.className + 'Lollipop')
746 | .data(readyData)
747 | .enter()
748 | .append("circle")
749 | //.attr("clip-path", "url(#clip)")
750 | .attr("class", "element " + object.className)
751 | .attr("id", (d) => {
752 | return "f_" + object.id;
753 | })
754 | // circle dimensions
755 | .attr("cx", (d) => {
756 | return this.commons.scaling(d.x)
757 | })
758 | .attr("cy", "-8") // same as height
759 | // circle radius
760 | .attr("r", (d) => {
761 | if (d.y<=1) {
762 | return d.y*this.commons.elementHeight*0.5
763 | } else {
764 | this.commons.logger.warn("Maximum circle radius is 1", {method:'addFeatures',fvId:this.commons.divId,featureId:object.id})
765 | return this.commons.elementHeight*0.5
766 | }
767 | })
768 | .attr("width", (d) => {
769 | let w = this.commons.scaling(d.x + 0.4) - this.commons.scaling(d.x - 0.4);
770 | if (this.commons.scaling(d.x + 0.4) - this.commons.scaling(d.x - 0.4) < 2) w = 2;
771 | return w;
772 | })
773 | .style("fill", (d) => {
774 | return d.color || object.color
775 | })
776 | .style("fill-opacity", (d) => {
777 | if (d.opacity) {
778 | return d.opacity
779 | } else if (object.opacity) {
780 | return object.opacity
781 | } else {
782 | return "1"
783 | }
784 | })
785 | .style("stroke", (d) => {
786 | if ("stroke" in d) {
787 | return d.stroke
788 | } else if ("stroke" in object) {
789 | return object.stroke
790 | } else {
791 | return d.color
792 | }
793 | })
794 | .call(this.commons.d3helper.tooltip(object));
795 |
796 | this.forcePropagation(circlesPro);
797 | }
798 |
799 | public circle(object, position) {
800 | let circlesPro = this.commons.svgContainer.append("g")
801 | .attr("class", "pointPosition featureLine")
802 | .attr("transform", "translate(0," + position + ")")
803 | .attr("id", () => {
804 | // random string
805 | // return divId + '_' + d.title.split(" ").join("_") + '_g'
806 | return 'c' + object.id + '_container'
807 | });
808 |
809 | let dataLine = [];
810 | dataLine.push([{
811 | x: 1,
812 | y: 0
813 | }, {
814 | x: this.commons.fvLength,
815 | y: 0
816 | }]);
817 |
818 | // basal line
819 | circlesPro.selectAll(".line " + object.className)
820 | .data(dataLine)
821 | .enter()
822 | .append("path")
823 | .attr("d", this.commons.line)
824 | .attr("class", "line " + object.className)
825 | .style("z-index", "0")
826 | .style("stroke", 'grey')
827 | .style("stroke-width", "0.5px");
828 |
829 | let readyData = [...object.data];
830 |
831 | circlesPro.selectAll("." + object.className + 'Circle')
832 | .data(readyData)
833 | .enter()
834 | .append("circle")
835 | //.attr("clip-path", "url(#clip)")
836 | .attr("class", "element " + object.className)
837 | .attr("id", (d) => {
838 | return "f_" + object.id;
839 | })
840 | // circle dimensions
841 | .attr("cx", (d) => {
842 | return this.commons.scaling(d.x)
843 | })
844 | .attr("cy", "5") // same as height
845 | // circle radius
846 | .attr("r", (d) => {
847 | if (d.y<=1) {
848 | return d.y*this.commons.elementHeight
849 | } else {
850 | this.commons.logger.warn("Maximum circle radius is 1", {method:'addFeatures',fvId:this.commons.divId,featureId:object.id})
851 | return this.commons.elementHeight
852 | }
853 | })
854 | .attr("width", (d) => {
855 | if (this.commons.scaling(d.x + 0.4) - this.commons.scaling(d.x - 0.4) < 2) return 2;
856 | else return this.commons.scaling(d.x + 0.4) - this.commons.scaling(d.x - 0.4);
857 | })
858 | .style("fill", (d) => {
859 | return d.color || object.color
860 | })
861 | .style("fill-opacity", (d) => {
862 | if (d.opacity) {
863 | return d.opacity
864 | } else if (object.opacity) {
865 | return object.opacity
866 | } else {
867 | return "1"
868 | }
869 | })
870 | .style("stroke", (d) => {
871 | if ("stroke" in d) {
872 | return d.stroke
873 | } else if ("stroke" in object) {
874 | return object.stroke
875 | } else {
876 | return d.color
877 | }
878 | })
879 | .call(this.commons.d3helper.tooltip(object));
880 |
881 | this.forcePropagation(circlesPro);
882 | }
883 |
884 | public path(object, position) {
885 |
886 | if (!object.height) object.height = this.commons.elementHeight;
887 | let pathHeight = this.commons.elementHeight;
888 |
889 | let pathsDB = this.commons.svgContainer.append("g")
890 | .attr("class", "pathing featureLine")
891 | //.attr("clip-path", "url(#clip)") // firefox compatibility
892 | .attr("transform", "translate(0," + position + ")")
893 | .attr("id", () => {
894 | // random string
895 | // return divId + '_' + d.title.split(" ").join("_") + '_g'
896 | return 'c' + object.id + '_container'
897 | });
898 |
899 | let dataLine = [];
900 | dataLine.push([{
901 | x: 1,
902 | y: 0
903 | }, {
904 | x: this.commons.fvLength,
905 | y: 0
906 | }]);
907 |
908 | pathsDB.selectAll(".line " + object.className)
909 | .data(dataLine)
910 | .enter()
911 | .append("path")
912 | .attr("d", this.commons.line)
913 | .attr("class", "line " + object.className)
914 | .style("z-index", "0")
915 | .style("stroke", 'grey')
916 | .style("stroke-width", "0.5px");
917 |
918 | pathsDB.selectAll(".path" + object.className)
919 | .data(object.data)
920 | .enter()
921 | .append("path")
922 | //.attr("clip-path", "url(#clip)") // firefox compatibility
923 | .attr("class", "element " + object.className)
924 | .attr("id", (d) => {
925 | return "f_" + d[0].id + Math.random().toString(36).substring(7);
926 | })
927 | .attr("d", this.commons.lineBond)
928 | .style("fill", "none")
929 | .style("stroke", (d) => {
930 | return d[0].color || object.color
931 | })
932 | .style("z-index", "3")
933 | .style("stroke-width", (d) => {
934 | return d[0].opacity || object.opacity
935 | })
936 | .call(this.commons.d3helper.tooltip(object));
937 |
938 | this.forcePropagation(pathsDB);
939 | // re-init object.heigth
940 | object.height = this.commons.step / 2;
941 |
942 | }
943 |
944 | public fillSVGLine(object, position = 0) {
945 | // if (!object.interpolation) object.interpolation = "curveBasis"; // TODO: not sensitive to interpolation now
946 | if (object.fill === undefined) object.fill = true;
947 | let histoG = this.commons.svgContainer.append("g")
948 | // necessary id to get height when placing tags
949 | .attr("id", () => {return 'c' + object.id + '_container'})
950 | .attr("class", "lining featureLine")
951 | .attr("transform", "translate(0," + position + ")")
952 | .attr("heigth", object.curveHeight);
953 |
954 | let dataLine = [];
955 | dataLine.push([{
956 | x: 1,
957 | y: 0
958 | }, {
959 | x: this.commons.fvLength,
960 | y: 0
961 | }]);
962 |
963 | /*histoG.selectAll(".line " + object.className)
964 | .data(dataLine)
965 | .enter()
966 | .append("path")
967 | .attr("clip-path", "url(#clip)")
968 | .attr("d", this.commons.lineBond)
969 | .attr("class", "line " + object.className)
970 | .style("z-index", "0")
971 | .style("stroke", "black")
972 | .style("stroke-width", "1px");*/
973 | // interpolate
974 | //this.commons.lineGen().curve(object.interpolation)
975 |
976 | object.data.forEach((dd, i) => {
977 |
978 | histoG.selectAll("." + object.className + i)
979 | .data(dd)
980 | .enter()
981 | .append("path")
982 | //.attr("clip-path", "url(#clip)") // firefox compatibility
983 | .attr("class", "element " + object.className + " " + object.className + i)
984 | // d3 v4
985 | .attr("d", this.commons.lineGen.y((d) => {
986 | return this.commons.lineYScale(-d.y) * 10 + object.shift;
987 | })
988 | )
989 | //.style("fill", object.fill ? this.shadeBlendConvert(0.6, object.color[i]) || this.shadeBlendConvert(0.6, "#000") : "none")
990 | .style("fill", object.color)
991 | .style("fill-opacity", "0.8")
992 | .style("stroke", object.color[i] || "#000")
993 | .style("z-index", "3")
994 | .style("stroke-width", "2px")
995 | .call(this.commons.d3helper.tooltip(object));
996 |
997 | });
998 |
999 | this.forcePropagation(histoG);
1000 | }
1001 |
1002 | public multipleRect(object, position = 0, level = this.commons.level) {
1003 | let rectHeight = 8;
1004 | let rectShift = 10;
1005 |
1006 | let rects = this.commons.svgContainer.append("g")
1007 | .attr("class", "multipleRects featureLine")
1008 | .attr("transform", "translate(0," + position + ")");
1009 |
1010 | for (let i = 0; i < level; i++) {
1011 | rects.append("path")
1012 | .attr("d", this.fillSVGLine([{
1013 | x: 1,
1014 | y: (i * rectShift - 2)
1015 | }, {
1016 | x: this.commons.fvLength,
1017 | y: (i * rectShift - 2)
1018 | }]))
1019 | .attr("class", () => {
1020 | return "line " + object.className
1021 | })
1022 | .style("z-index", "0")
1023 | .style("stroke", object.color)
1024 | .style("stroke-width", "1px");
1025 | }
1026 |
1027 | rects.selectAll("." + object.className)
1028 | .data(object.data)
1029 | .enter()
1030 | .append("rect")
1031 | //.attr("clip-path", "url(#clip)") // firefox compatibility
1032 | .attr("class", "element " + object.className)
1033 | .attr("id", (d) => {
1034 | return "f_" + object.id + Math.random().toString(36).substring(7);
1035 | })
1036 | .attr("x", (d) => {
1037 | return this.commons.scaling(d.x);
1038 | })
1039 | .attr("y", (d) => {
1040 | return d.level * rectShift;
1041 | })
1042 | .attr("ry", (d) => {
1043 | return this.commons.radius;
1044 | })
1045 | .attr("rx", (d) => {
1046 | return this.commons.radius;
1047 | })
1048 | .attr("width", (d) => {
1049 | return (this.commons.scaling(d.y) - this.commons.scaling(d.x));
1050 | })
1051 | .attr("height", rectHeight)
1052 | .style("fill", (d) => {
1053 | return d.color || object.color
1054 | })
1055 | .style("z-index", "13")
1056 | .call(this.commons.d3helper.tooltip(object));
1057 |
1058 | this.forcePropagation(rects);
1059 | }
1060 |
1061 | // AXIS FUNCTIONS
1062 | public reset_axis() {
1063 | if (this.commons.animation) {
1064 | this.commons.svgContainer.transition().duration(500);
1065 | }
1066 | this.commons.svgContainer
1067 | .select(".x.axis")
1068 | .call(this.commons.xAxis);
1069 | }
1070 |
1071 | public addXAxis(position) {
1072 | this.commons.svgContainer.append("g")
1073 | .attr("class", "x axis XAxis")
1074 | .attr("transform", "translate(0," + (position + 20) + ")")
1075 | .call(this.commons.xAxis);
1076 | if (!this.commons.viewerOptions.showAxis) {
1077 | d3.select(`#${this.commons.divId}`).selectAll(".tick")
1078 | .attr("display", "none")
1079 | }
1080 | };
1081 |
1082 | public updateXAxis(position) {
1083 | this.commons.svgContainer.selectAll(".XAxis")
1084 | .attr("transform", "translate(0," + (position + this.commons.step) + ")");
1085 | };
1086 |
1087 | // BRUSH FUNCTION
1088 |
1089 | public resizeBrush() {
1090 |
1091 | if (this.commons.svgContainer) {
1092 | if (this.commons.svgContainer.node() !== null) {
1093 | let rectArea = this.commons.svgContainer.node().getBoundingClientRect();
1094 | let thisbrush = this.commons.svgContainer.select(".brush");
1095 | thisbrush.select("rect")
1096 | .attr('height', rectArea.height)
1097 | .attr('width', rectArea.width);
1098 | }
1099 | }
1100 | };
1101 |
1102 | public addBrush() {
1103 |
1104 | this.commons.svgContainer
1105 | .append("g")
1106 | .attr("class", "brush")
1107 | .attr("id", "fvbrush")
1108 | .call(this.commons.brush)
1109 | //.call(this.commons.brush.move, this.commons.scaling());
1110 | this.resizeBrush()
1111 |
1112 | };
1113 |
1114 | // TOOLBAR ON TOP
1115 |
1116 | public showHelp() {
1117 |
1118 | /*let helpContent = "To zoom in : Left click to select area of interest
" +
1119 | "To zoom out : Right click to reset the scale
" +
1120 | "Zoom max : Limited to " + this.commons.zoomMax.toString() + " " + this.commons.viewerOptions.unit + "
";*/
1121 | let helpContent = "To zoom in : Left click to select area of interest\n To zoom out : Right click to reset the scale\n Zoom max : Limited to " +
1122 | this.commons.viewerOptions.zoomMax.toString();
1123 |
1124 | alert(helpContent)
1125 | }
1126 |
1127 |
1128 | constructor(commons: {}) {
1129 | super(commons);
1130 | this.preComputing = new PreComputing(commons);
1131 | }
1132 | }
1133 |
1134 | export default FillSVG;
1135 |
--------------------------------------------------------------------------------
/src/helper.ts:
--------------------------------------------------------------------------------
1 |
2 | import {event as currentEvent} from 'd3-selection';
3 |
4 | class ComputingFunctions {
5 |
6 | public commons;
7 |
8 | public rectWidth2 = (d) => {
9 | /*if (d.x === d.y) {
10 | if (this.commons.scaling([d.x + 0.4]) - this.commons.scaling([d.x - 0.4]) < 2) return 2;
11 | else return this.commons.scaling([d.x + 0.4]) - this.commons.scaling([d.x - 0.4]);
12 | }*/
13 | return (this.commons.scaling([d.y + 0.4])- this.commons.scaling([d.x - 0.4]));
14 | };
15 |
16 | public rectX = (object) => {
17 | /*if (object.x === object.y) {
18 | return this.commons.scaling([object.x - 0.4]);
19 | }*/
20 | let scale = this.commons.scaling([object.x - 0.4]);
21 | //if (scale<0) {scale = 0};
22 | return scale
23 | };
24 |
25 | protected displaySequence(seq) {
26 | return this.commons.viewerOptions.width / seq > 5;
27 | }
28 |
29 | protected gradientColor(stringContent) {
30 | let percent = Number(stringContent);
31 | //value from 0 to 100
32 | //let hue = ((1 - percent) * 120).toString(10);
33 | let hue;
34 | if (percent === 0) {
35 | hue = '#ffffff'
36 | } else if (percent > 0 && percent <= 15) {
37 | hue = '#d2d2f8'
38 | } else if (percent > 15 && percent < 50) {
39 | hue = '#a6a6f1'
40 | } else if (percent >= 50 && percent < 100) {
41 | hue = '#7a7aeb'
42 | } else if (percent === 100) {
43 | hue = '#4e4ee4'
44 | }
45 | return hue;
46 | //return ["hsl(", hue, ",100%,50%)"].join("");
47 | };
48 |
49 | protected forcePropagation(item) {
50 | item.on('mousedown', () => {
51 | let new_click_event = new Event('mousedown');
52 | new_click_event['pageX'] = currentEvent.layerX;
53 | new_click_event['clientX'] = currentEvent.clientX;
54 | new_click_event['pageY'] = currentEvent.layerY;
55 | new_click_event['clientY'] = currentEvent.clientY;
56 | });
57 | }
58 |
59 | constructor(commons: {}) {
60 | this.commons = commons;
61 | }
62 | }
63 |
64 | export default ComputingFunctions
65 |
--------------------------------------------------------------------------------
/src/interfaces.ts:
--------------------------------------------------------------------------------
1 |
2 | export interface UserOptions {
3 | offset?: {
4 | start: number,
5 | end: number
6 | },
7 | breakpoint?: number,
8 | showAxis?: boolean,
9 | showSequence?: boolean,
10 | showSequenceLabel?: boolean,
11 | brushActive?: boolean,
12 | toolbar?: boolean,
13 | toolbarPosition?: string,
14 | zoomMax?: number,
15 | showSubFeatures?: boolean,
16 | flagColor?: string,
17 | flagTrack?: number | string | boolean,
18 | flagTrackMobile?: number | string | boolean,
19 | sideBar?: number | string | boolean,
20 | animation?: boolean,
21 | unit?: string,
22 | backgroundcolor?: string,
23 | maxDepth?: number
24 | }
25 |
26 | export interface ViewerOptions {
27 | showSequence: boolean,
28 | showSequenceLabel?: boolean,
29 | brushActive: boolean,
30 | verticalLine: boolean,
31 | dottedSequence: boolean,
32 | offset?: {
33 | start: number,
34 | end: number
35 | },
36 | tooltipColor: string,
37 | showHelper: boolean,
38 | flagColor: string,
39 | showSubFeatures: boolean,
40 | sideBar: boolean,
41 | labelTrackWidth: number,
42 | labelTrackWidthMobile: number,
43 | tagsTrackWidth: number,
44 | maxDepth?: number,
45 | margin: {
46 | top: number,
47 | bottom: number,
48 | left: number,
49 | right: number
50 | },
51 | backup: {
52 | labelTrackWidth: number,
53 | tagsTrackWidth: number,
54 | features: any
55 | },
56 | width: number,
57 | height: number,
58 | zoomMax: number,
59 | mobileMode: boolean,
60 | unit: string,
61 | animation: boolean,
62 | toolbar: boolean,
63 | toolbarPosition?: string,
64 | bubbleHelp: boolean,
65 | showAxis: boolean,
66 | positionWithoutLetter: number,
67 | drawLadder: boolean,
68 | ladderWidth: number,
69 | ladderHeight: number,
70 | ladderSpacing: number,
71 | labelSidebarWidth?: number
72 | }
73 |
74 | export interface FeaturesList extends Array{}
75 |
76 | export interface FeatureObject {
77 | id: string,
78 | type: string // "rect" | "path" | "curve" | "unique" | "circle" | "sequence" | "lollipop",
79 | data: Array | string,
80 | parentId?: any,
81 | label?: string,
82 | className?: string,
83 | height?: number,
84 | yLim?: number,
85 | color?: string,
86 | stroke?: string,
87 | opacity?: number,
88 | tooltip?: string,
89 | sidebar?: Array,
90 | isOpen?: boolean,
91 | flagLevel?: number,
92 | subfeatures?: Array
93 | }
94 |
95 | export interface FeatureData {
96 | x: number,
97 | y?: any,
98 | label?: string,
99 | className?: string,
100 | color?: string,
101 | stroke?: string,
102 | opacity?: number,
103 | tooltip?: string
104 | }
105 |
106 | export interface SideBarObject {
107 | id: string,
108 | tooltip?: string, // or html
109 | content?: string,
110 | type?: string,
111 | width?: number,
112 | label?: string | number
113 | }
114 |
115 | export interface FeatureViewerLogger {
116 | debug(primaryMessage: string, ...supportingData: any[]): void;
117 | warn(primaryMessage: string, ...supportingData: any[]): void;
118 | error(primaryMessage: string, ...supportingData: any[]): void;
119 | info(primaryMessage: string, ...supportingData: any[]): void;
120 | }
121 |
122 | export class FeatureViewerLog implements FeatureViewerLogger {
123 |
124 | public debug(msg: string, ...supportingDetails): void {
125 | this.emitLogMessage("debug", msg, supportingDetails)
126 | }
127 |
128 | public info(msg: string, ...supportingDetails): void {
129 | this.emitLogMessage("info", msg, supportingDetails)
130 | }
131 |
132 | public warn(msg: string, ...supportingDetails): void {
133 | this.emitLogMessage("warn", msg, supportingDetails)
134 | }
135 |
136 | public error(msg: string, ...supportingDetails): void {
137 | this.emitLogMessage("error", msg, supportingDetails)
138 | }
139 |
140 | private emitLogMessage(msgType:"debug"|"info"|"warn"|"error", msg:string, supportingDetails:any[]){
141 | if(supportingDetails.length > 0) {
142 | console[msgType](msg, supportingDetails);
143 | } else {
144 | console[msgType](msg);
145 | }
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/src/styles/styles.scss:
--------------------------------------------------------------------------------
1 |
2 | @keyframes loadingcolorflag {
3 | 50% {fill: grey }
4 | }
5 |
6 | // non-svg objects in svg
7 | foreignObject > body {
8 | overflow: visible;
9 | }
10 |
11 | // header
12 | .helperButton {
13 | width: 20px;
14 | height: 20px;
15 | }
16 | .helperButton path {
17 | fill: rgba(39, 37, 37, 0.71);
18 | }
19 |
20 | // brush selection
21 | .selectionRect {
22 | fill: rgba(0,0,0,0.15);
23 | fill-opacity: .5;
24 | }
25 | .brush .selection{
26 | stroke: #fff;
27 | fill: #4682b4;
28 | fill-opacity: .125;
29 | shape-rendering: crispEdges;
30 | display: block;
31 | height: 100%;
32 | }
33 |
34 | // fv buttons
35 | .mybutton {
36 | -moz-border-radius: 10px;
37 | -webkit-border-radius: 10px;
38 | border-radius: 10px; /* future proofing */
39 | -khtml-border-radius: 10px; /* for old Konqueror browsers */
40 | background: darkgrey;
41 | border: none;
42 | color: rgb(0,0,0);
43 | height: 30px;
44 | min-width: 36px;
45 | outline: none;
46 | text-decoration: none;
47 | text-align: center;
48 | line-height: 24px;
49 | vertical-align: middle;
50 | overflow: visible !important;
51 | }
52 | .mybuttonsquare {
53 | border-radius: 0px;
54 | font-size: 12px;
55 | height: 30px;
56 | cursor: default;
57 | margin: auto;
58 | min-width: 30px;
59 | width:70px;
60 | font-family:'Roboto';
61 | font-size:10px;
62 | background: white;
63 | box-shadow: 0 2px 3px 0 rgba(0,0,0,0.12), 0 2px 2px 0 rgba(0,0,0,0.24);
64 | line-height: normal
65 | }
66 | .mybuttoncircle {
67 | border-radius: 50%;
68 | font-size: 12px;
69 | height: 36px;
70 | width: 36px;
71 | cursor: pointer;
72 | background: white;
73 | line-height: normal
74 | }
75 | // fv axis
76 | .axis {
77 | font: 14px sans-serif;
78 | }
79 | // fv flags
80 | .Arrow {
81 | fill-opacity: 0.6;
82 | }
83 | // fv tooltips
84 | .fvtooltip {
85 | position: absolute;
86 | text-align: center;
87 | color: white;
88 | width: auto;
89 | height: auto;
90 | padding: 2px;
91 | font-family: "Arial", sans-serif;
92 | font-size: 10px;
93 | font-weight: normal;
94 | background-color:rgba(0, 0, 0, 0.7);
95 | border: 0px;
96 | border-radius: 4px;
97 | pointer-events: none;
98 |
99 | }
100 |
101 | // fv custom tooltips
102 | .fvcustomtooltip {
103 | position: absolute;
104 | text-align: center;
105 | color: white;
106 | height: auto;
107 | background-color:rgba(0, 0, 0, 0.7);
108 | pointer-events: none;
109 |
110 | }
111 |
112 |
113 | // loading
114 | .pageoverlay {
115 | position: fixed; /* Sit on top of the page content */
116 | width: 100%; /* Full width (cover the whole page) */
117 | height: 100%; /* Full height (cover the whole page) */
118 | top: 0;
119 | left: 0;
120 | right: 0;
121 | bottom: 0;
122 | background-color: rgba(0,0,0,0.3); /* Black background with opacity */
123 | z-index: 2; /* Specify a stack order in case you're using a different order for other elements */
124 | cursor: pointer; /* Add a pointer on hover */
125 | }
126 |
--------------------------------------------------------------------------------
/src/tooltip.ts:
--------------------------------------------------------------------------------
1 |
2 | import Calculate from "./calculate";
3 | import * as d3 from './custom-d3';
4 |
5 | class Tool extends Calculate {
6 |
7 | private calculate: Calculate;
8 |
9 | public colorSelectedFeat(feat, object, divId) {
10 | // remove previous selected features
11 | if (this.commons.featureSelected) {
12 | d3.select(`#${divId}`).select(`#${this.commons.featureSelected}`).style("fill-opacity", "0.6");
13 | }
14 | // color selected rectangle
15 | if (object.type !== "path" && object.type !== "curve" && feat) {
16 |
17 | this.commons.featureSelected = feat;
18 | let thisfeat = d3.select(`#${divId}`).select(`#${feat}`);
19 | thisfeat.style("fill-opacity", "1");
20 |
21 | if (object.type !== 'unique') {
22 | // color the background
23 | let currentContainer = this.commons.svgContainer.node().getBoundingClientRect();
24 |
25 | let selectRect;
26 | d3.select(`#${divId}`).selectAll(".selectionRect").remove();
27 | selectRect = this.commons.svgContainer
28 | .select(".brush")
29 | .append("rect")
30 | .attr("class", "selectionRect box-shadow")
31 | // add shadow?
32 | .attr("height", currentContainer.height)
33 | let thisy = this.getTransf((thisfeat.node()).parentElement)[0];
34 | let myd3node = thisfeat.node();
35 | let bcr = (myd3node).getBoundingClientRect().width;
36 | selectRect
37 | .style("display", "block") // remove display none
38 | .attr("width", bcr) // - shift from the beginning
39 | .attr("transform", () => {
40 | return "translate(" + thisy + ",0)"
41 | })
42 | }
43 | }
44 | };
45 |
46 | public colorSelectedCoordinates(start, end, divId) {
47 |
48 | }
49 |
50 | private updateLineTooltip(mouse, pD, scalingFunction, labelTrackWidth) {
51 | let xP = mouse - labelTrackWidth;
52 | let elemHover = "";
53 | for (let l = 1; l < pD.length; l++) {
54 | let scalingFirst = scalingFunction(pD[l - 1].x);
55 | let scalingSecond = scalingFunction(pD[l].x);
56 | let halfway = (scalingSecond-scalingFirst)/2;
57 | if (scalingFirst+halfway < xP && scalingSecond+halfway > xP) {
58 | elemHover = pD[l];
59 | break;
60 | }
61 | }
62 | return elemHover;
63 | };
64 |
65 | private clickTagFunction(d) {
66 | // trigger tag_selected event: buttons clicked
67 | if (CustomEvent) {
68 | let event = new CustomEvent(this.commons.events.TAG_SELECTED_EVENT, {
69 | detail: d
70 | });
71 | this.commons.svgElement.dispatchEvent(event);
72 | } else {
73 | console.warn("CustomEvent is not defined....");
74 | }
75 | if (this.commons.trigger) this.commons.trigger(this.commons.events.TAG_SELECTED_EVENT, event);
76 | };
77 |
78 | public initTooltip(div, divId) {
79 |
80 | let getMessage = (thing, type='default') => {
81 |
82 | // first line
83 | let tooltip_message = '';
84 |
85 | // case of flags
86 | if (thing.hasOwnProperty('title')) {
87 | tooltip_message += '';
88 | tooltip_message += thing.title;
89 | tooltip_message += '
';
90 |
91 | } else {
92 |
93 | if (thing.hasOwnProperty('x') || thing.hasOwnProperty('y')) {
94 | tooltip_message += '';
95 | tooltip_message += (+thing.x).toString();
96 | if (+thing.y !== +thing.x) {
97 | if (type =='curve') {
98 | tooltip_message += ' - ' + (+thing.y).toFixed(2).toString()
99 | } else if (type == 'circle') {
100 | // pass
101 | } else {
102 | tooltip_message += ' - ' + (+thing.y).toString()
103 | }
104 | }
105 | tooltip_message += '
';
106 | }
107 | // case of feature
108 | // if (thing.hasOwnProperty('tooltip')) {
109 | // tooltip_message += '';
110 | // tooltip_message += thing.tooltip;
111 | // tooltip_message += '
';
112 | // }
113 |
114 | }
115 |
116 | return tooltip_message
117 | };
118 |
119 | let drawTooltip = (tooltipDiv, absoluteMousePos) => {
120 | // angular material tooltip
121 | tooltipDiv
122 | .style('top', (absoluteMousePos[1] - 55) + 'px')
123 | .style("display", "block")
124 | .style('background-color', 'grey')
125 | .style('color', "#fff")
126 | .style('width', 'auto')
127 | .style('max-width', '170px')
128 | .style("height", "auto")
129 | .style('cursor', 'help')
130 | .style('pointer-events', 'none')
131 | .style('borderRadius', '2px')
132 | .style('overflow', 'hidden')
133 | .style('whiteSpace', 'nowrap')
134 | .style('textOverflow', 'ellipsis')
135 | .style('padding', '8px')
136 | .style('font', '10px sans-serif')
137 | .style('text-align', 'center')
138 | .style('position', 'absolute') // don't change this for compatibility to angular2
139 | .style('z-index', 45)
140 | .style('box-shadow', '0 1px 2px 0 #656565')
141 | .style('fontWeight', '500');
142 | };
143 |
144 | let scalingFunction = this.commons.scaling;
145 | let labelTrackWidth = this.commons.viewerOptions.labelTrackWidth;
146 | let updateLineTooltipFunction = this.updateLineTooltip;
147 |
148 | this.commons.d3helper = {};
149 | // d3['helper'] = {};
150 |
151 | this.commons.d3helper.flagTooltip = () => {
152 |
153 | let tooltipDiv = this.commons.tooltipDiv;
154 | let bodyNode = d3.select(div).node();
155 |
156 | let tooltip = (selection) => {
157 |
158 | let absoluteMousePos;
159 | let drawMyTooltip = (pD) => {
160 |
161 | absoluteMousePos = d3.mouse(bodyNode);
162 |
163 | let left, top;
164 | left = absoluteMousePos[0].toString();
165 | top = absoluteMousePos[1].toString();
166 |
167 | // mobilemode labels overwrite tooltips
168 | if (this.commons.viewerOptions.mobileMode) {
169 | tooltipDiv.transition()
170 | .duration(200)
171 | .style("opacity", 1);
172 | tooltipDiv
173 | .html(pD['label'] || pD['id'])
174 | .style("left", left+'px')
175 | .style("top", top+'px');
176 | } else if (pD['tooltip']) {
177 | tooltipDiv.transition()
178 | .duration(200)
179 | .style("opacity", 1);
180 | tooltipDiv
181 | .html(pD['tooltip'])
182 | .style("left", left+'px')
183 | .style("top", top+'px');
184 | }
185 | };
186 |
187 | selection
188 | // tooltip
189 | .on('mouseover.tooltip', (pD) => {
190 | // if (this.commons.viewerOptions.mobileMode) {
191 | drawMyTooltip(pD);
192 | // }
193 | })
194 | .on('mousemove.tooltip', (pD) => {
195 | // if (this.commons.viewerOptions.mobileMode) {
196 | drawMyTooltip(pD);
197 | // }
198 | })
199 | .on('mouseout.tooltip', () => {
200 | // Remove tooltip
201 | tooltipDiv.transition()
202 | .duration(500)
203 | .style("opacity", 0);
204 | })
205 | };
206 |
207 | return tooltip;
208 |
209 | };
210 |
211 | this.commons.d3helper.genericTooltip = (object) => {
212 |
213 | let tooltipDiv = this.commons.tooltipDiv;
214 | let bodyNode = d3.select(div).node();
215 | let message = object.tooltip;
216 |
217 | let tooltip = (selection) => {
218 |
219 | let absoluteMousePos;
220 | let drawMyTooltip = (pD) => {
221 |
222 | absoluteMousePos = d3.mouse(bodyNode);
223 |
224 | let left, top;
225 | left = absoluteMousePos[0].toString();
226 | top = absoluteMousePos[1].toString();
227 |
228 | if (message) {
229 | tooltipDiv.transition()
230 | .duration(200)
231 | .style("opacity", 1);
232 | tooltipDiv
233 | .html(message)
234 | .style("left", left+'px')
235 | .style("top", top+'px');
236 | }
237 | };
238 |
239 | selection
240 | // tooltip
241 | .on('mouseover', (pD) => {
242 | drawMyTooltip(pD);
243 | })
244 | .on('mousemove', (pD) => {
245 | drawMyTooltip(pD);
246 | })
247 | .on('mouseout', () => {
248 | // Remove tooltip
249 | tooltipDiv.transition()
250 | .duration(500)
251 | .style("opacity", 0);
252 | })
253 | .on('click', (pD) => {
254 | // TODO
255 | // from message to object with button id too
256 | this.clickTagFunction(object)
257 | })
258 | };
259 |
260 | return tooltip;
261 |
262 | };
263 |
264 | this.commons.d3helper.tooltip = (object) => {
265 |
266 | let tooltipDiv = this.commons.tooltipDiv;
267 | let customTooltipDiv = this.commons.customTooltipDiv;
268 | let viewerWidth = this.commons.viewerOptions.width;
269 |
270 | let bodyNode = d3.select(div).node();
271 | // let tooltipColor = this.commons.viewerOptions.tooltipColor ? this.commons.viewerOptions.tooltipColor : "#fff";
272 |
273 | let tooltip = (selection) => {
274 |
275 | let absoluteMousePos;
276 |
277 | let getPositions = (absoluteMousePos) => {
278 | let rightSide = (absoluteMousePos[0] > viewerWidth);
279 | let topshift = 25;
280 | let left = 0,
281 | top = 0;
282 | if (rightSide) {
283 | left = absoluteMousePos[0] + 10 - (tooltipDiv.node().getBoundingClientRect().width);
284 | top = absoluteMousePos[1] - topshift;
285 | } else {
286 | left = absoluteMousePos[0] - 15;
287 | top = absoluteMousePos[1] - topshift;
288 | }
289 | let positions = {
290 | top: top,
291 | left: left
292 | };
293 | return positions
294 | };
295 |
296 | let getMyMessage = (pD) => {
297 | let tooltip_message = '';
298 | if (object.type === "path") {
299 | let reformat = {
300 | x: pD[0].x,
301 | y: pD[1].x
302 | }
303 | tooltip_message = getMessage(reformat);
304 | }
305 | else if (object.type === "curve") {
306 | let elemHover = updateLineTooltipFunction(absoluteMousePos[0], pD, scalingFunction, labelTrackWidth);
307 | tooltip_message = getMessage(elemHover, 'curve');
308 | }
309 | else if (object.type === "circle") {
310 | tooltip_message = getMessage(pD, 'circle');
311 | }
312 | else if (object.type === "button") {
313 | tooltip_message = getMessage(object);
314 | } else {
315 | // e.g. rect
316 | tooltip_message = getMessage(pD);
317 | }
318 | return tooltip_message
319 | };
320 |
321 | let drawMyTooltip = (pD) => {
322 | if (pD.tooltip || object.tooltip) {
323 | customTooltipDiv.html("");
324 | let html = '';
325 | if (pD.tooltip) { html = pD.tooltip } else { html = object.tooltip; }
326 | drawCustomTooltip(html);
327 | } else {
328 | absoluteMousePos = d3.mouse(bodyNode);
329 | let positions = getPositions(absoluteMousePos);
330 | let tooltip_message = getMyMessage(pD);
331 |
332 | tooltipDiv.transition()
333 | .duration(200)
334 | .style("opacity", 1);
335 | tooltipDiv
336 | .html(tooltip_message)
337 | .style("left", positions['left']+'px')
338 | .style("top", positions['top']+'px');
339 | }
340 |
341 | };
342 |
343 | let drawCustomTooltip = (tooltiphtml) => {
344 |
345 | // remove tooltip div
346 | tooltipDiv.transition()
347 | .duration(500)
348 | .style("opacity", 0);
349 |
350 | // open tooltip if no source or if source is click and status is open
351 | absoluteMousePos = d3.mouse(bodyNode);
352 | let clickposition = (this.commons.viewerOptions.width/2) - absoluteMousePos[0] > 0 ? -1 : 1;
353 | let positions = getPositions(absoluteMousePos);
354 |
355 | // console.log('Selection:', d3.select(`#customTooltipDivContent`))
356 | // d3.select(`#customTooltipDivContent`).html(tooltiphtml)
357 |
358 | // now fill it
359 | customTooltipDiv.transition()
360 | .duration(200)
361 | .style("opacity", 1);
362 | customTooltipDiv
363 | .style("top", positions['top']+'px')
364 | .style("left", positions['left']+'px')
365 | .append('foreignObject') // foreignObject can be styled with no limitation by user
366 | .attr("width", "100%")
367 | .attr("height", "100%")
368 | .html(tooltiphtml)
369 | // transition if clickposition
370 | if (clickposition) {
371 | customTooltipDiv.style("top", (positions['top']+35)+'px')
372 | }
373 | if (clickposition == 1) {
374 | let transitiondata = customTooltipDiv.node().getBoundingClientRect()
375 | if (transitiondata) {
376 | customTooltipDiv.style("left", (positions['left']-transitiondata.width+55)+'px')
377 | // customTooltipDiv.transition()
378 | // .duration(500)
379 | // .style("left", transitiondata.width);
380 | }
381 | }
382 |
383 |
384 |
385 | }
386 |
387 | selection
388 | .on("mouseover", (pD) => {
389 | drawMyTooltip(pD);
390 | })
391 | .on('mousemove', (pD) => {
392 | drawMyTooltip(pD);
393 | })
394 | .on("mouseout", function(pD) {
395 | if (pD.tooltip || object.tooltip) {
396 | // remove custom tooltip
397 | customTooltipDiv.html("");
398 | customTooltipDiv.transition()
399 | .duration(500)
400 | .style("opacity", 0);
401 | } else {
402 | // remove normal tooltip
403 | tooltipDiv.transition()
404 | .duration(500)
405 | .style("opacity", 0);
406 | }
407 | })
408 | .on('click', (pD) => {
409 | // not button: feature
410 | if (object.type !== "button") { // feature
411 |
412 | // TODO: define data for event exporting when clicking rects
413 | // TODO: fix exports.event.target.__data__ is undefined
414 | let elemHover;
415 |
416 | let forSelection = pD;
417 | // curve specifics
418 | if (object.type === "curve") {
419 | elemHover = updateLineTooltipFunction(absoluteMousePos[0], pD, scalingFunction, labelTrackWidth);
420 | forSelection = elemHover;
421 | }
422 |
423 | // path is array of pD, line is elemHover, all the rest is a pD object
424 | object['selectedRegion'] = forSelection;
425 | let feature_detail_object = object;
426 |
427 | this.colorSelectedFeat(pD.id, object, divId);
428 |
429 |
430 | // trigger feature_selected event
431 | if (CustomEvent) {
432 | let event = new CustomEvent(this.commons.events.FEATURE_SELECTED_EVENT, {
433 | detail: feature_detail_object
434 | });
435 | this.commons.svgElement.dispatchEvent(event);
436 | } else {
437 | console.warn("CustomEvent is not defined....");
438 | }
439 | if (this.commons.trigger) this.commons.trigger(this.commons.events.FEATURE_SELECTED_EVENT, feature_detail_object);
440 |
441 | } else {
442 |
443 | // button
444 | this.clickTagFunction(object)
445 | }
446 | });
447 | };
448 |
449 | return tooltip;
450 | };
451 |
452 | }
453 |
454 | constructor(commons: {}) {
455 | super(commons);
456 | }
457 | }
458 |
459 | export default Tool
460 |
--------------------------------------------------------------------------------
/src/transition.ts:
--------------------------------------------------------------------------------
1 | import ComputingFunctions from './helper';
2 |
3 | export class SubfeaturesTransition extends ComputingFunctions {
4 |
5 | public area(gElement, newY) {
6 | gElement
7 | .attr("transform", "translate(0," + newY +")")
8 | .transition().duration(500);
9 | }
10 | public position(gElement, parentElementRow) {
11 | gElement
12 | .attr("position", "element(#"+ parentElementRow +")");
13 | }
14 | public Xaxis(axis, newY) {
15 | axis
16 | .attr("transform", "translate(0," + newY +")")
17 | .transition().duration(500);
18 | }
19 | public containerH(container, newH) {
20 | container
21 | .attr("height", newH)
22 | }
23 |
24 | constructor(commons: {}) {
25 | super(commons);
26 | }
27 | }
28 |
29 | export class Transition extends ComputingFunctions {
30 |
31 | public basalLine(object) {
32 |
33 | let container = this.commons.svgContainer.select(`#c${object.id}_container`);
34 | container.selectAll(".line")
35 | .attr("d", this.commons.line);
36 |
37 | }
38 |
39 | public rectangle(object) {
40 |
41 | let container = this.commons.svgContainer.select(`#c${object.id}_container`);
42 | // line does not require transition
43 |
44 | let transit1, transit2;
45 | // group selection
46 | transit1 = container.selectAll("." + "rectfv" + "Group")
47 | transit2 = container.selectAll("." + "rectfv")
48 | // transition
49 | if (this.commons.animation) {
50 | transit1
51 | .transition()
52 | .duration(500);
53 | transit2
54 | .transition()
55 | .duration(500);
56 | }
57 | // transition
58 | transit1.attr("transform", (d) => {
59 | return "translate(" + this.rectX(d) + ",0)"
60 | });
61 | transit2
62 | .attr("width", this.rectWidth2);
63 |
64 | // transition to text
65 | container.selectAll("." + object.className + "Text")
66 | .attr("transform", (d) => {
67 | if (d.label && this.commons.scaling(d['x'])<0) {
68 | return "translate(" + -this.rectX(d) + ",0)"
69 | }
70 | })
71 | .style("visibility", (d) => {
72 | if (d.label && this.commons.scaling(d['x'])>0) {
73 | return (this.commons.scaling(d.y) - this.commons.scaling(d['x'])) > d.label.length * 8 && object.height > 11 ? "visible" : "hidden";
74 | } else return "hidden";
75 | });
76 | }
77 |
78 | public multiRec(object) {
79 |
80 | let container = this.commons.svgContainer.select(`#c${object.id}_container`);
81 | container.selectAll("." + "rectfv")
82 | .attr("x", (d) => {
83 | return this.commons.scaling(d['x'])
84 | })
85 | .attr("width", (d) => {
86 | return this.commons.scaling(d.y) - this.commons.scaling(d['x'])
87 | });
88 |
89 | }
90 |
91 | public unique(object) {
92 |
93 | let container = this.commons.svgContainer.select(`#c${object.id}_container`);
94 | // line does not require transition
95 |
96 | let transit;
97 | if (this.commons.animation) {
98 | transit = container.selectAll(".element")
99 | .transition()
100 | .duration(500);
101 | }
102 | else {
103 | transit = container.selectAll(".element");
104 | }
105 | transit
106 | .attr("x", (d) => {
107 | return this.commons.scaling(d['x'] - 0.4)
108 | })
109 | .attr("width", (d) => {
110 | if (this.commons.scaling(d['x'] + 0.4) - this.commons.scaling(d['x'] - 0.4) < 2) return 2;
111 | else return this.commons.scaling(d['x'] + 0.4) - this.commons.scaling(d['x'] - 0.4);
112 | });
113 |
114 | }
115 |
116 | public lollipop(object) {
117 |
118 | let container = this.commons.svgContainer.select(`#c${object.id}_container`);
119 | // line does not require transition
120 |
121 | let transit1, transit2;
122 | if (this.commons.animation) {
123 | transit1 = container.selectAll(".element")
124 | .transition()
125 | .duration(500);
126 | transit2 = container.selectAll(".lineElement")
127 | .transition()
128 | .duration(500);
129 | }
130 | else {
131 | transit1 = container.selectAll(".element");
132 | transit2 = container.selectAll(".lineElement");
133 | }
134 | transit1
135 | .attr("cx", (d) => {
136 | return this.commons.scaling(d.x)
137 | });
138 | transit2
139 | .attr("x1", (d) => {
140 | return this.commons.scaling(d.x)
141 | })
142 | .attr("x2", (d) => {
143 | return this.commons.scaling(d.x)
144 | })
145 | // .attr("y2", (d) => {
146 | // let w = this.commons.scaling(d.x + 0.4) - this.commons.scaling(d.x - 0.4);
147 | // if (this.commons.scaling(d.x + 0.4) - this.commons.scaling(d.x - 0.4) < 2) w = 2;
148 | // return w + 4;
149 | // });
150 |
151 | }
152 |
153 | public circle(object) {
154 |
155 | let container = this.commons.svgContainer.select(`#c${object.id}_container`);
156 | // line does not require transition
157 |
158 | let transit;
159 | if (this.commons.animation) {
160 | transit = container.selectAll(".element")
161 | .transition()
162 | .duration(500);
163 | }
164 | else {
165 | transit = container.selectAll(".element");
166 | }
167 | transit
168 | .attr("cx", (d) => {
169 | return this.commons.scaling(d['x'])
170 | })
171 | .attr("width", (d) => {
172 | if (this.commons.scaling(d['x'] + 0.4) - this.commons.scaling(d['x'] - 0.4) < 2) return 2;
173 | else return this.commons.scaling(d['x'] + 0.4) - this.commons.scaling(d['x'] - 0.4);
174 | });
175 |
176 | }
177 |
178 | public path(object) {
179 |
180 | let container = this.commons.svgContainer.select(`#c${object.id}_container`);
181 | container.selectAll(".line")
182 | .attr("d", this.commons.lineBond.x((d) => {
183 | return this.commons.scaling(d['x']);
184 | })
185 | .y( (d) => {
186 | return -d['y'] * 10 + object.height;
187 | })
188 | );
189 | let transit;
190 | if (this.commons.animation) {
191 | transit = container.selectAll("." + "pathfv")
192 | .transition()
193 | .duration(0);
194 | }
195 | else {
196 | transit = container.selectAll("." + "pathfv");
197 | }
198 | transit
199 | .attr("d", this.commons.lineBond.y((d) => {
200 | return -1 * d['y'] * 10 + object.height;
201 | }));
202 | }
203 |
204 | public lineTransition(object) {
205 | let container = this.commons.svgContainer.select(`#c${object.id}_container`);
206 | const yScores = object.data.map(o => o.y);
207 | const maxScore = Math.max(...yScores);
208 | const minScore = Math.min(...yScores);
209 | // keep height
210 | this.commons.lineYScale.domain([minScore, maxScore]).range([0, this.commons.step/11]);
211 | container.selectAll(".line " + object.className)
212 | .attr("d", (d) => {
213 | return this.commons.lineYScale(-d.y) * 10 + object.shift
214 | });
215 |
216 | // transit line
217 | let transit;
218 | if (this.commons.animation) {
219 | transit = container.selectAll("." + object.className)
220 | .transition()
221 | .duration(0);
222 | }
223 | else {
224 | transit = container.selectAll("." + object.className);
225 | }
226 |
227 | transit
228 | .attr("d", this.commons.lineGen.y((d) => {
229 | return this.commons.lineYScale(-d.y) * 10 + object.shift;
230 | })
231 | );
232 | }
233 |
234 | public text(object, start) {
235 |
236 | let container = this.commons.svgContainer.select(`#c${object.id}_container`);
237 | let transit;
238 | if (this.commons.animation) {
239 | transit = container.selectAll("." + object.className)
240 | .transition()
241 | .duration(500);
242 | }
243 | else {
244 | transit = container.selectAll("." + object.className);
245 | }
246 | transit
247 | .attr("x", (d, i) => {
248 | return this.commons.scaling(i + start)
249 | });
250 | }
251 |
252 | constructor(commons: {}) {
253 | super(commons);
254 | }
255 | };
256 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": false,
4 | "declaration": true,
5 | "allowSyntheticDefaultImports": true,
6 | "noImplicitAny": false,
7 | "allowJs": true,
8 | // Module defines the output module resolution system
9 | "module": "es6",
10 | // Target defines the compiled code targets
11 | "target": "es6",
12 | // Skip type checking all .d.ts files.
13 | "skipLibCheck": true
14 | },
15 | // Define files to be included
16 | "files": ["./index.ts", "./example.ts"],
17 | // Include sources
18 | "include": ["src"],
19 | // Exclude node modules
20 | "exclude": ["node_modules"]
21 | }
22 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | // Generated using webpack-cli https://github.com/webpack/webpack-cli
2 | // Dependencies
3 | const path = require("path");
4 | // Plugins
5 | const HtmlWebpackPlugin = require("html-webpack-plugin");
6 | const MiniCssExtractPlugin = require('mini-css-extract-plugin');
7 | // Define whether current environment is production or not
8 | const isProduction = process.env.NODE_ENV === "production";
9 | // Main configuration options
10 | const config = {
11 | // Entry point for the app
12 | entry: {
13 | 'index': { import: './index.ts' },
14 | 'example': { import: './example.ts', dependOn: 'index' },
15 | },
16 | // Define output file and directory
17 | output: {
18 | // Define output file name
19 | filename: "[name].js",
20 | // Define output directory
21 | path: path.resolve(__dirname, "dist"),
22 | // Define a library (Universal Module Definition)
23 | library: "feature-viewer-typescript",
24 | libraryTarget: "umd",
25 | umdNamedDefine: true,
26 | // Clean the output directory before emit.
27 | clean: true,
28 | },
29 | // Live development server
30 | devServer: {
31 | open: true,
32 | host: "localhost",
33 | },
34 | plugins: [
35 | new HtmlWebpackPlugin({
36 | template: "example.html",
37 | }),
38 | new MiniCssExtractPlugin(),
39 | // Add your plugins here
40 | // Learn more about plugins from https://webpack.js.org/configuration/plugins/
41 | ],
42 | module: {
43 | rules: [
44 | {
45 | test: /\.(ts|tsx)$/i,
46 | loader: "ts-loader",
47 | exclude: /node_modules/,
48 | },
49 | {
50 | test: /\.s[ac]ss$/i,
51 | use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"],
52 | // use: ["css-loader", "sass-loader"],
53 | },
54 | {
55 | test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
56 | type: "asset",
57 | },
58 | // Add your rules for custom modules here
59 | // Learn more about loaders from https://webpack.js.org/loaders/
60 | ],
61 | },
62 | resolve: {
63 | extensions: [".tsx", ".ts", ".jsx", ".js", "..."],
64 | },
65 | };
66 | // Export actual module
67 | module.exports = () => {
68 | if (isProduction) {
69 | config.mode = "production";
70 | } else {
71 | config.mode = "development";
72 | }
73 | return config;
74 | };
75 |
--------------------------------------------------------------------------------