Class |
Line # |
Actions |
|||
---|---|---|---|---|---|
FeatureRendererModel | 57 | 374 | 188 | ||
FeatureRendererModel.FeatureSettingsBean | 63 | 4 | 1 |
1 | /* | |
2 | * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) | |
3 | * Copyright (C) $$Year-Rel$$ The Jalview Authors | |
4 | * | |
5 | * This file is part of Jalview. | |
6 | * | |
7 | * Jalview is free software: you can redistribute it and/or | |
8 | * modify it under the terms of the GNU General Public License | |
9 | * as published by the Free Software Foundation, either version 3 | |
10 | * of the License, or (at your option) any later version. | |
11 | * | |
12 | * Jalview is distributed in the hope that it will be useful, but | |
13 | * WITHOUT ANY WARRANTY; without even the implied warranty | |
14 | * of MERCHANTABILITY or FITNESS FOR A PARTICULAR | |
15 | * PURPOSE. See the GNU General Public License for more details. | |
16 | * | |
17 | * You should have received a copy of the GNU General Public License | |
18 | * along with Jalview. If not, see <http://www.gnu.org/licenses/>. | |
19 | * The Jalview Authors are detailed in the 'AUTHORS' file. | |
20 | */ | |
21 | package jalview.viewmodel.seqfeatures; | |
22 | ||
23 | import java.awt.Color; | |
24 | import java.beans.PropertyChangeListener; | |
25 | import java.beans.PropertyChangeSupport; | |
26 | import java.util.ArrayList; | |
27 | import java.util.Arrays; | |
28 | import java.util.Comparator; | |
29 | import java.util.HashMap; | |
30 | import java.util.HashSet; | |
31 | import java.util.Hashtable; | |
32 | import java.util.Iterator; | |
33 | import java.util.List; | |
34 | import java.util.Map; | |
35 | import java.util.Set; | |
36 | import java.util.concurrent.ConcurrentHashMap; | |
37 | ||
38 | import jalview.api.AlignViewportI; | |
39 | import jalview.api.FeatureColourI; | |
40 | import jalview.api.FeaturesDisplayedI; | |
41 | import jalview.datamodel.AlignedCodonFrame; | |
42 | import jalview.datamodel.AlignedCodonFrame.SequenceToSequenceMapping; | |
43 | import jalview.datamodel.AlignmentI; | |
44 | import jalview.datamodel.MappedFeatures; | |
45 | import jalview.datamodel.SearchResultMatchI; | |
46 | import jalview.datamodel.SearchResults; | |
47 | import jalview.datamodel.SearchResultsI; | |
48 | import jalview.datamodel.SequenceFeature; | |
49 | import jalview.datamodel.SequenceI; | |
50 | import jalview.datamodel.features.FeatureMatcherSetI; | |
51 | import jalview.datamodel.features.SequenceFeatures; | |
52 | import jalview.renderer.seqfeatures.FeatureRenderer; | |
53 | import jalview.schemes.FeatureColour; | |
54 | import jalview.util.ColorUtils; | |
55 | import jalview.util.Platform; | |
56 | ||
57 | public abstract class FeatureRendererModel | |
58 | implements jalview.api.FeatureRenderer | |
59 | { | |
60 | /* | |
61 | * a data bean to hold one row of feature settings from the gui | |
62 | */ | |
63 | public static class FeatureSettingsBean | |
64 | { | |
65 | public final String featureType; | |
66 | ||
67 | public final FeatureColourI featureColour; | |
68 | ||
69 | public final FeatureMatcherSetI filter; | |
70 | ||
71 | public final Boolean show; | |
72 | ||
73 | 39 | public FeatureSettingsBean(String type, FeatureColourI colour, |
74 | FeatureMatcherSetI theFilter, Boolean isShown) | |
75 | { | |
76 | 39 | featureType = type; |
77 | 39 | featureColour = colour; |
78 | 39 | filter = theFilter; |
79 | 39 | show = isShown; |
80 | } | |
81 | } | |
82 | ||
83 | /* | |
84 | * global transparency for feature | |
85 | */ | |
86 | protected float transparency = 1.0f; | |
87 | ||
88 | /* | |
89 | * colour scheme for each feature type | |
90 | */ | |
91 | protected Map<String, FeatureColourI> featureColours = new ConcurrentHashMap<>(); | |
92 | ||
93 | /* | |
94 | * visibility flag for each feature group | |
95 | */ | |
96 | protected Map<String, Boolean> featureGroups = new ConcurrentHashMap<>(); | |
97 | ||
98 | /* | |
99 | * filters for each feature type | |
100 | */ | |
101 | protected Map<String, FeatureMatcherSetI> featureFilters = new HashMap<>(); | |
102 | ||
103 | protected String[] renderOrder; | |
104 | ||
105 | Map<String, Float> featureOrder = null; | |
106 | ||
107 | protected AlignViewportI av; | |
108 | ||
109 | private PropertyChangeSupport changeSupport = new PropertyChangeSupport( | |
110 | this); | |
111 | ||
112 | 416677 | @Override |
113 | public AlignViewportI getViewport() | |
114 | { | |
115 | 416701 | return av; |
116 | } | |
117 | ||
118 | 2 | public FeatureRendererSettings getSettings() |
119 | { | |
120 | 2 | return new FeatureRendererSettings(this); |
121 | } | |
122 | ||
123 | 36 | public void transferSettings(FeatureRendererSettings fr) |
124 | { | |
125 | 36 | this.renderOrder = fr.renderOrder; |
126 | 36 | this.featureGroups = fr.featureGroups; |
127 | 36 | this.featureColours = fr.featureColours; |
128 | 36 | this.transparency = fr.transparency; |
129 | 36 | this.featureOrder = fr.featureOrder; |
130 | } | |
131 | ||
132 | /** | |
133 | * update from another feature renderer | |
134 | * | |
135 | * @param fr | |
136 | * settings to copy | |
137 | */ | |
138 | 64 | public void transferSettings(jalview.api.FeatureRenderer _fr) |
139 | { | |
140 | 64 | FeatureRenderer fr = (FeatureRenderer) _fr; |
141 | 64 | FeatureRendererSettings frs = new FeatureRendererSettings(fr); |
142 | 64 | this.renderOrder = frs.renderOrder; |
143 | 64 | this.featureGroups = frs.featureGroups; |
144 | 64 | this.featureColours = frs.featureColours; |
145 | 64 | this.featureFilters = frs.featureFilters; |
146 | 64 | this.transparency = frs.transparency; |
147 | 64 | this.featureOrder = frs.featureOrder; |
148 | 64 | if (av != null && av != fr.getViewport()) |
149 | { | |
150 | // copy over the displayed feature settings | |
151 | 4 | if (_fr.getFeaturesDisplayed() != null) |
152 | { | |
153 | 4 | FeaturesDisplayedI fd = getFeaturesDisplayed(); |
154 | 4 | if (fd == null) |
155 | { | |
156 | 4 | setFeaturesDisplayedFrom(_fr.getFeaturesDisplayed()); |
157 | } | |
158 | else | |
159 | { | |
160 | 0 | synchronized (fd) |
161 | { | |
162 | 0 | fd.clear(); |
163 | 0 | for (String type : _fr.getFeaturesDisplayed() |
164 | .getVisibleFeatures()) | |
165 | { | |
166 | 0 | fd.setVisible(type); |
167 | } | |
168 | } | |
169 | } | |
170 | } | |
171 | } | |
172 | } | |
173 | ||
174 | 4 | public void setFeaturesDisplayedFrom(FeaturesDisplayedI featuresDisplayed) |
175 | { | |
176 | 4 | av.setFeaturesDisplayed(new FeaturesDisplayed(featuresDisplayed)); |
177 | } | |
178 | ||
179 | 18 | @Override |
180 | public void setVisible(String featureType) | |
181 | { | |
182 | 18 | FeaturesDisplayedI fdi = av.getFeaturesDisplayed(); |
183 | 18 | if (fdi == null) |
184 | { | |
185 | 5 | av.setFeaturesDisplayed(fdi = new FeaturesDisplayed()); |
186 | } | |
187 | 18 | if (!fdi.isRegistered(featureType)) |
188 | { | |
189 | 17 | pushFeatureType(Arrays.asList(new String[] { featureType })); |
190 | } | |
191 | 18 | fdi.setVisible(featureType); |
192 | } | |
193 | ||
194 | 1 | @Override |
195 | public void setAllVisible(List<String> featureTypes) | |
196 | { | |
197 | 1 | FeaturesDisplayedI fdi = av.getFeaturesDisplayed(); |
198 | 1 | if (fdi == null) |
199 | { | |
200 | 1 | av.setFeaturesDisplayed(fdi = new FeaturesDisplayed()); |
201 | } | |
202 | 1 | List<String> nft = new ArrayList<>(); |
203 | 1 | for (String featureType : featureTypes) |
204 | { | |
205 | 1 | if (!fdi.isRegistered(featureType)) |
206 | { | |
207 | 1 | nft.add(featureType); |
208 | } | |
209 | } | |
210 | 1 | if (nft.size() > 0) |
211 | { | |
212 | 1 | pushFeatureType(nft); |
213 | } | |
214 | 1 | fdi.setAllVisible(featureTypes); |
215 | } | |
216 | ||
217 | /** | |
218 | * push a set of new types onto the render order stack. Note - this is a | |
219 | * direct mechanism rather than the one employed in updateRenderOrder | |
220 | * | |
221 | * @param types | |
222 | */ | |
223 | 18 | private void pushFeatureType(List<String> types) |
224 | { | |
225 | ||
226 | 18 | int ts = types.size(); |
227 | 18 | String neworder[] = new String[(renderOrder == null ? 0 |
228 | : renderOrder.length) + ts]; | |
229 | 18 | types.toArray(neworder); |
230 | 18 | if (renderOrder != null) |
231 | { | |
232 | 12 | System.arraycopy(neworder, 0, neworder, renderOrder.length, ts); |
233 | 12 | System.arraycopy(renderOrder, 0, neworder, 0, renderOrder.length); |
234 | } | |
235 | 18 | renderOrder = neworder; |
236 | } | |
237 | ||
238 | protected Map<String, float[][]> minmax = new Hashtable<>(); | |
239 | ||
240 | 28 | public Map<String, float[][]> getMinMax() |
241 | { | |
242 | 28 | return minmax; |
243 | } | |
244 | ||
245 | /** | |
246 | * normalise a score against the max/min bounds for the feature type. | |
247 | * | |
248 | * @param sequenceFeature | |
249 | * @return byte[] { signed, normalised signed (-127 to 127) or unsigned | |
250 | * (0-255) value. | |
251 | */ | |
252 | 0 | protected final byte[] normaliseScore(SequenceFeature sequenceFeature) |
253 | { | |
254 | 0 | float[] mm = minmax.get(sequenceFeature.type)[0]; |
255 | 0 | final byte[] r = new byte[] { 0, (byte) 255 }; |
256 | 0 | if (mm != null) |
257 | { | |
258 | 0 | if (r[0] != 0 || mm[0] < 0.0) |
259 | { | |
260 | 0 | r[0] = 1; |
261 | 0 | r[1] = (byte) ((int) 128.0 |
262 | + 127.0 * (sequenceFeature.score / mm[1])); | |
263 | } | |
264 | else | |
265 | { | |
266 | 0 | r[1] = (byte) ((int) 255.0 * (sequenceFeature.score / mm[1])); |
267 | } | |
268 | } | |
269 | 0 | return r; |
270 | } | |
271 | ||
272 | boolean newFeatureAdded = false; | |
273 | ||
274 | boolean findingFeatures = false; | |
275 | ||
276 | 25913 | protected boolean updateFeatures() |
277 | { | |
278 | 25913 | if (av.getFeaturesDisplayed() == null || renderOrder == null |
279 | || newFeatureAdded) | |
280 | { | |
281 | 0 | findAllFeatures(); |
282 | 0 | if (av.getFeaturesDisplayed().getVisibleFeatureCount() < 1) |
283 | { | |
284 | 0 | return false; |
285 | } | |
286 | } | |
287 | // TODO: decide if we should check for the visible feature count first | |
288 | 25913 | return true; |
289 | } | |
290 | ||
291 | /** | |
292 | * search the alignment for all new features, give them a colour and display | |
293 | * them. Then fires a PropertyChangeEvent on the changeSupport object. | |
294 | * | |
295 | */ | |
296 | 19 | protected void findAllFeatures() |
297 | { | |
298 | 19 | synchronized (firing) |
299 | { | |
300 | 19 | if (firing.equals(Boolean.FALSE)) |
301 | { | |
302 | 19 | firing = Boolean.TRUE; |
303 | 19 | findAllFeatures(true); // add all new features as visible |
304 | 19 | notifyFeaturesChanged(); |
305 | 19 | firing = Boolean.FALSE; |
306 | } | |
307 | } | |
308 | } | |
309 | ||
310 | 84 | @Override |
311 | public void notifyFeaturesChanged() | |
312 | { | |
313 | 84 | changeSupport.firePropertyChange("changeSupport", null, null); |
314 | } | |
315 | ||
316 | 18 | @Override |
317 | public List<SequenceFeature> findFeaturesAtColumn(SequenceI sequence, | |
318 | int column) | |
319 | { | |
320 | /* | |
321 | * include features at the position provided their feature type is | |
322 | * displayed, and feature group is null or marked for display | |
323 | */ | |
324 | 18 | List<SequenceFeature> result = new ArrayList<>(); |
325 | 18 | if (!av.areFeaturesDisplayed() || getFeaturesDisplayed() == null) |
326 | { | |
327 | 1 | return result; |
328 | } | |
329 | ||
330 | 17 | Set<String> visibleFeatures = getFeaturesDisplayed() |
331 | .getVisibleFeatures(); | |
332 | 17 | String[] visibleTypes = visibleFeatures |
333 | .toArray(new String[visibleFeatures.size()]); | |
334 | 17 | List<SequenceFeature> features = sequence.findFeatures(column, column, |
335 | visibleTypes); | |
336 | ||
337 | /* | |
338 | * include features unless they are hidden (have no colour), based on | |
339 | * feature group visibility, or a filter or colour threshold | |
340 | */ | |
341 | 17 | for (SequenceFeature sf : features) |
342 | { | |
343 | 33 | if (getColour(sf) != null) |
344 | { | |
345 | 23 | result.add(sf); |
346 | } | |
347 | } | |
348 | 17 | return result; |
349 | } | |
350 | ||
351 | /** | |
352 | * Searches alignment for all features and updates colours | |
353 | * | |
354 | * @param newMadeVisible | |
355 | * if true newly added feature types will be rendered immediately | |
356 | * TODO: check to see if this method should actually be proxied so | |
357 | * repaint events can be propagated by the renderer code | |
358 | */ | |
359 | 132 | @Override |
360 | public synchronized void findAllFeatures(boolean newMadeVisible) | |
361 | { | |
362 | 132 | newFeatureAdded = false; |
363 | ||
364 | 132 | if (findingFeatures) |
365 | { | |
366 | 0 | newFeatureAdded = true; |
367 | 0 | return; |
368 | } | |
369 | ||
370 | 132 | findingFeatures = true; |
371 | 132 | if (av.getFeaturesDisplayed() == null) |
372 | { | |
373 | 60 | av.setFeaturesDisplayed(new FeaturesDisplayed()); |
374 | } | |
375 | 132 | FeaturesDisplayedI featuresDisplayed = av.getFeaturesDisplayed(); |
376 | ||
377 | 132 | Set<String> oldfeatures = new HashSet<>(); |
378 | 132 | if (renderOrder != null) |
379 | { | |
380 | 352 | for (int i = 0; i < renderOrder.length; i++) |
381 | { | |
382 | 296 | if (renderOrder[i] != null) |
383 | { | |
384 | 296 | oldfeatures.add(renderOrder[i]); |
385 | } | |
386 | } | |
387 | } | |
388 | ||
389 | 132 | AlignmentI alignment = av.getAlignment(); |
390 | 132 | List<String> allfeatures = new ArrayList<>(); |
391 | ||
392 | 1067 | for (int i = 0; i < alignment.getHeight(); i++) |
393 | { | |
394 | 935 | SequenceI asq = alignment.getSequenceAt(i); |
395 | 935 | for (String group : asq.getFeatures().getFeatureGroups(true)) |
396 | { | |
397 | 792 | boolean groupDisplayed = true; |
398 | 792 | if (group != null) |
399 | { | |
400 | 770 | if (featureGroups.containsKey(group)) |
401 | { | |
402 | 688 | groupDisplayed = featureGroups.get(group); |
403 | } | |
404 | else | |
405 | { | |
406 | 82 | groupDisplayed = newMadeVisible; |
407 | 82 | featureGroups.put(group, groupDisplayed); |
408 | } | |
409 | } | |
410 | 792 | if (groupDisplayed) |
411 | { | |
412 | 791 | Set<String> types = asq.getFeatures() |
413 | .getFeatureTypesForGroups(true, group); | |
414 | 791 | for (String type : types) |
415 | { | |
416 | 1905 | if (!allfeatures.contains(type)) // or use HashSet and no test? |
417 | { | |
418 | 336 | allfeatures.add(type); |
419 | } | |
420 | 1905 | updateMinMax(asq, type, true); // todo: for all features? |
421 | } | |
422 | } | |
423 | } | |
424 | } | |
425 | ||
426 | // uncomment to add new features in alphebetical order (but JAL-2575) | |
427 | // Collections.sort(allfeatures, String.CASE_INSENSITIVE_ORDER); | |
428 | 132 | if (newMadeVisible) |
429 | { | |
430 | 130 | for (String type : allfeatures) |
431 | { | |
432 | 326 | if (!oldfeatures.contains(type)) |
433 | { | |
434 | 132 | featuresDisplayed.setVisible(type); |
435 | 132 | setOrder(type, 0); |
436 | } | |
437 | } | |
438 | } | |
439 | ||
440 | 132 | updateRenderOrder(allfeatures); |
441 | 132 | findingFeatures = false; |
442 | } | |
443 | ||
444 | /** | |
445 | * Updates the global (alignment) min and max values for a feature type from | |
446 | * the score for a sequence, if the score is not NaN. Values are stored | |
447 | * separately for positional and non-positional features. | |
448 | * | |
449 | * @param seq | |
450 | * @param featureType | |
451 | * @param positional | |
452 | */ | |
453 | 1905 | protected void updateMinMax(SequenceI seq, String featureType, |
454 | boolean positional) | |
455 | { | |
456 | 1905 | float min = seq.getFeatures().getMinimumScore(featureType, positional); |
457 | 1905 | if (Float.isNaN(min)) |
458 | { | |
459 | 26 | return; |
460 | } | |
461 | ||
462 | 1879 | float max = seq.getFeatures().getMaximumScore(featureType, positional); |
463 | ||
464 | /* | |
465 | * stored values are | |
466 | * { {positionalMin, positionalMax}, {nonPositionalMin, nonPositionalMax} } | |
467 | */ | |
468 | 1879 | if (minmax == null) |
469 | { | |
470 | 0 | minmax = new Hashtable<>(); |
471 | } | |
472 | 1879 | synchronized (minmax) |
473 | { | |
474 | 1879 | float[][] mm = minmax.get(featureType); |
475 | 1879 | int index = positional ? 0 : 1; |
476 | 1879 | if (mm == null) |
477 | { | |
478 | 272 | mm = new float[][] { null, null }; |
479 | 272 | minmax.put(featureType, mm); |
480 | } | |
481 | 1879 | if (mm[index] == null) |
482 | { | |
483 | 272 | mm[index] = new float[] { min, max }; |
484 | } | |
485 | else | |
486 | { | |
487 | 1607 | mm[index][0] = Math.min(mm[index][0], min); |
488 | 1607 | mm[index][1] = Math.max(mm[index][1], max); |
489 | } | |
490 | } | |
491 | } | |
492 | ||
493 | protected Boolean firing = Boolean.FALSE; | |
494 | ||
495 | /** | |
496 | * replaces the current renderOrder with the unordered features in | |
497 | * allfeatures. The ordering of any types in both renderOrder and allfeatures | |
498 | * is preserved, and all new feature types are rendered on top of the existing | |
499 | * types, in the order given by getOrder or the order given in allFeatures. | |
500 | * Note. this operates directly on the featureOrder hash for efficiency. TODO: | |
501 | * eliminate the float storage for computing/recalling the persistent ordering | |
502 | * New Cability: updates min/max for colourscheme range if its dynamic | |
503 | * | |
504 | * @param allFeatures | |
505 | */ | |
506 | 132 | private void updateRenderOrder(List<String> allFeatures) |
507 | { | |
508 | 132 | List<String> allfeatures = new ArrayList<>(allFeatures); |
509 | 132 | String[] oldRender = renderOrder; |
510 | 132 | renderOrder = new String[allfeatures.size()]; |
511 | 132 | boolean initOrders = (featureOrder == null); |
512 | 132 | int opos = 0; |
513 | 132 | if (oldRender != null && oldRender.length > 0) |
514 | { | |
515 | 350 | for (int j = 0; j < oldRender.length; j++) |
516 | { | |
517 | 296 | if (oldRender[j] != null) |
518 | { | |
519 | 296 | if (initOrders) |
520 | { | |
521 | 9 | setOrder(oldRender[j], |
522 | (1 - (1 + (float) j) / oldRender.length)); | |
523 | } | |
524 | 296 | if (allfeatures.contains(oldRender[j])) |
525 | { | |
526 | 204 | renderOrder[opos++] = oldRender[j]; // existing features always |
527 | // appear below new features | |
528 | 204 | allfeatures.remove(oldRender[j]); |
529 | 204 | if (minmax != null) |
530 | { | |
531 | 204 | float[][] mmrange = minmax.get(oldRender[j]); |
532 | 204 | if (mmrange != null) |
533 | { | |
534 | 202 | FeatureColourI fc = featureColours.get(oldRender[j]); |
535 | 202 | if (fc != null && !fc.isSimpleColour() && fc.isAutoScaled() |
536 | && !fc.isColourByAttribute()) | |
537 | { | |
538 | 0 | fc.updateBounds(mmrange[0][0], mmrange[0][1]); |
539 | } | |
540 | } | |
541 | } | |
542 | } | |
543 | } | |
544 | } | |
545 | } | |
546 | 132 | if (allfeatures.size() == 0) |
547 | { | |
548 | // no new features - leave order unchanged. | |
549 | 54 | return; |
550 | } | |
551 | 78 | int i = allfeatures.size() - 1; |
552 | 78 | int iSize = i; |
553 | 78 | boolean sort = false; |
554 | 78 | String[] newf = new String[allfeatures.size()]; |
555 | 78 | float[] sortOrder = new float[allfeatures.size()]; |
556 | 78 | for (String newfeat : allfeatures) |
557 | { | |
558 | 132 | newf[i] = newfeat; |
559 | 132 | if (minmax != null) |
560 | { | |
561 | // update from new features minmax if necessary | |
562 | 132 | float[][] mmrange = minmax.get(newf[i]); |
563 | 132 | if (mmrange != null) |
564 | { | |
565 | 123 | FeatureColourI fc = featureColours.get(newf[i]); |
566 | 123 | if (fc != null && !fc.isSimpleColour() && fc.isAutoScaled() |
567 | && !fc.isColourByAttribute()) | |
568 | { | |
569 | 0 | fc.updateBounds(mmrange[0][0], mmrange[0][1]); |
570 | } | |
571 | } | |
572 | } | |
573 | 132 | if (initOrders || !featureOrder.containsKey(newf[i])) |
574 | { | |
575 | 0 | int denom = initOrders ? allfeatures.size() : featureOrder.size(); |
576 | // new unordered feature - compute persistent ordering at head of | |
577 | // existing features. | |
578 | 0 | setOrder(newf[i], i / (float) denom); |
579 | } | |
580 | // set order from newly found feature from persisted ordering. | |
581 | 132 | sortOrder[i] = 2 - featureOrder.get(newf[i]).floatValue(); |
582 | 132 | if (i < iSize) |
583 | { | |
584 | // only sort if we need to | |
585 | 54 | sort = sort || sortOrder[i] > sortOrder[i + 1]; |
586 | } | |
587 | 132 | i--; |
588 | } | |
589 | 78 | if (iSize > 1 && sort) |
590 | { | |
591 | 0 | jalview.util.QuickSort.sort(sortOrder, newf); |
592 | } | |
593 | 78 | sortOrder = null; |
594 | 78 | System.arraycopy(newf, 0, renderOrder, opos, newf.length); |
595 | } | |
596 | ||
597 | /** | |
598 | * get a feature style object for the given type string. Creates a | |
599 | * java.awt.Color for a featureType with no existing colourscheme. | |
600 | * | |
601 | * @param featureType | |
602 | * @return | |
603 | */ | |
604 | 15660 | @Override |
605 | public FeatureColourI getFeatureStyle(String featureType) | |
606 | { | |
607 | 15660 | FeatureColourI fc = featureColours.get(featureType); |
608 | 15660 | if (fc == null) |
609 | { | |
610 | 70 | Color col = ColorUtils.createColourFromName(featureType); |
611 | 70 | fc = new FeatureColour(col); |
612 | 70 | featureColours.put(featureType, fc); |
613 | } | |
614 | 15660 | return fc; |
615 | } | |
616 | ||
617 | 8326 | @Override |
618 | public Color getColour(SequenceFeature feature) | |
619 | { | |
620 | 8326 | FeatureColourI fc = getFeatureStyle(feature.getType()); |
621 | 8326 | return getColor(feature, fc); |
622 | } | |
623 | ||
624 | /** | |
625 | * Answers true if the feature type is currently selected to be displayed, | |
626 | * else false | |
627 | * | |
628 | * @param type | |
629 | * @return | |
630 | */ | |
631 | 746872 | public boolean showFeatureOfType(String type) |
632 | { | |
633 | 747612 | return type == null ? false |
634 | 747552 | : (av.getFeaturesDisplayed() == null ? true |
635 | : av.getFeaturesDisplayed().isVisible(type)); | |
636 | } | |
637 | ||
638 | 100 | @Override |
639 | public void setColour(String featureType, FeatureColourI col) | |
640 | { | |
641 | 100 | featureColours.put(featureType, col); |
642 | } | |
643 | ||
644 | 68 | @Override |
645 | public void setTransparency(float value) | |
646 | { | |
647 | 68 | transparency = value; |
648 | } | |
649 | ||
650 | 28995 | @Override |
651 | public float getTransparency() | |
652 | { | |
653 | 28995 | return transparency; |
654 | } | |
655 | ||
656 | /** | |
657 | * analogous to colour - store a normalized ordering for all feature types in | |
658 | * this rendering context. | |
659 | * | |
660 | * @param type | |
661 | * Feature type string | |
662 | * @param position | |
663 | * normalized priority - 0 means always appears on top, 1 means | |
664 | * always last. | |
665 | */ | |
666 | 146 | public float setOrder(String type, float position) |
667 | { | |
668 | 146 | if (featureOrder == null) |
669 | { | |
670 | 61 | featureOrder = new Hashtable<>(); |
671 | } | |
672 | 146 | featureOrder.put(type, Float.valueOf(position)); |
673 | 146 | return position; |
674 | } | |
675 | ||
676 | /** | |
677 | * get the global priority (0 (top) to 1 (bottom)) | |
678 | * | |
679 | * @param type | |
680 | * @return [0,1] or -1 for a type without a priority | |
681 | */ | |
682 | 204 | public float getOrder(String type) |
683 | { | |
684 | 204 | if (featureOrder != null) |
685 | { | |
686 | 204 | if (featureOrder.containsKey(type)) |
687 | { | |
688 | 204 | return featureOrder.get(type).floatValue(); |
689 | } | |
690 | } | |
691 | 0 | return -1; |
692 | } | |
693 | ||
694 | 59 | @Override |
695 | public Map<String, FeatureColourI> getFeatureColours() | |
696 | { | |
697 | 59 | return featureColours; |
698 | } | |
699 | ||
700 | /** | |
701 | * Replace current ordering with new ordering | |
702 | * | |
703 | * @param data | |
704 | * an array of { Type, Colour, Filter, Boolean } | |
705 | * @return true if any visible features have been reordered, else false | |
706 | */ | |
707 | 13 | public boolean setFeaturePriority(FeatureSettingsBean[] data) |
708 | { | |
709 | 13 | return setFeaturePriority(data, true); |
710 | } | |
711 | ||
712 | /** | |
713 | * Sets the priority order for features, with the highest priority (displayed | |
714 | * on top) at the start of the data array | |
715 | * | |
716 | * @param data | |
717 | * an array of { Type, Colour, Filter, Boolean } | |
718 | * @param visibleNew | |
719 | * when true current featureDisplay list will be cleared | |
720 | * @return true if any visible features have been reordered or recoloured, | |
721 | * else false (i.e. no need to repaint) | |
722 | */ | |
723 | 16 | public boolean setFeaturePriority(FeatureSettingsBean[] data, |
724 | boolean visibleNew) | |
725 | { | |
726 | /* | |
727 | * note visible feature ordering and colours before update | |
728 | */ | |
729 | 16 | List<String> visibleFeatures = getDisplayedFeatureTypes(); |
730 | 16 | Map<String, FeatureColourI> visibleColours = new HashMap<>( |
731 | getFeatureColours()); | |
732 | ||
733 | 16 | FeaturesDisplayedI av_featuresdisplayed = null; |
734 | 16 | if (visibleNew) |
735 | { | |
736 | ? | if ((av_featuresdisplayed = av.getFeaturesDisplayed()) != null) |
737 | { | |
738 | 12 | av.getFeaturesDisplayed().clear(); |
739 | } | |
740 | else | |
741 | { | |
742 | 1 | av.setFeaturesDisplayed( |
743 | av_featuresdisplayed = new FeaturesDisplayed()); | |
744 | } | |
745 | } | |
746 | else | |
747 | { | |
748 | 3 | av_featuresdisplayed = av.getFeaturesDisplayed(); |
749 | } | |
750 | 16 | if (data == null) |
751 | { | |
752 | 0 | return false; |
753 | } | |
754 | // The feature table will display high priority | |
755 | // features at the top, but these are the ones | |
756 | // we need to render last, so invert the data | |
757 | 16 | renderOrder = new String[data.length]; |
758 | ||
759 | 16 | if (data.length > 0) |
760 | { | |
761 | 57 | for (int i = 0; i < data.length; i++) |
762 | { | |
763 | 41 | String type = data[i].featureType; |
764 | 41 | setColour(type, data[i].featureColour); |
765 | 41 | if (data[i].show) |
766 | { | |
767 | 29 | av_featuresdisplayed.setVisible(type); |
768 | } | |
769 | ||
770 | 41 | renderOrder[data.length - i - 1] = type; |
771 | } | |
772 | } | |
773 | ||
774 | /* | |
775 | * get the new visible ordering and return true if it has changed | |
776 | * order or any colour has changed | |
777 | */ | |
778 | 16 | List<String> reorderedVisibleFeatures = getDisplayedFeatureTypes(); |
779 | 16 | if (!visibleFeatures.equals(reorderedVisibleFeatures)) |
780 | { | |
781 | /* | |
782 | * the list of ordered visible features has changed | |
783 | */ | |
784 | 11 | return true; |
785 | } | |
786 | ||
787 | /* | |
788 | * return true if any feature colour has changed | |
789 | */ | |
790 | 5 | for (String feature : visibleFeatures) |
791 | { | |
792 | 17 | if (visibleColours.get(feature) != getFeatureStyle(feature)) |
793 | { | |
794 | 0 | return true; |
795 | } | |
796 | } | |
797 | 5 | return false; |
798 | } | |
799 | ||
800 | /** | |
801 | * @param listener | |
802 | * @see java.beans.PropertyChangeSupport#addPropertyChangeListener(java.beans.PropertyChangeListener) | |
803 | */ | |
804 | 1 | public void addPropertyChangeListener(PropertyChangeListener listener) |
805 | { | |
806 | 1 | changeSupport.addPropertyChangeListener(listener); |
807 | } | |
808 | ||
809 | /** | |
810 | * @param listener | |
811 | * @see java.beans.PropertyChangeSupport#removePropertyChangeListener(java.beans.PropertyChangeListener) | |
812 | */ | |
813 | 1 | public void removePropertyChangeListener(PropertyChangeListener listener) |
814 | { | |
815 | 1 | changeSupport.removePropertyChangeListener(listener); |
816 | } | |
817 | ||
818 | 1 | public Set<String> getAllFeatureColours() |
819 | { | |
820 | 1 | return featureColours.keySet(); |
821 | } | |
822 | ||
823 | 16 | public void clearRenderOrder() |
824 | { | |
825 | 16 | renderOrder = null; |
826 | } | |
827 | ||
828 | 31867 | public boolean hasRenderOrder() |
829 | { | |
830 | 31869 | return renderOrder != null; |
831 | } | |
832 | ||
833 | /** | |
834 | * Returns feature types in ordering of rendering, where last means on top | |
835 | */ | |
836 | 255 | public List<String> getRenderOrder() |
837 | { | |
838 | 255 | if (renderOrder == null) |
839 | { | |
840 | 40 | return Arrays.asList(new String[] {}); |
841 | } | |
842 | 215 | return Arrays.asList(renderOrder); |
843 | } | |
844 | ||
845 | 2 | public int getFeatureGroupsSize() |
846 | { | |
847 | 2 | return featureGroups != null ? 0 : featureGroups.size(); |
848 | } | |
849 | ||
850 | 164 | @Override |
851 | public List<String> getFeatureGroups() | |
852 | { | |
853 | // conflict between applet and desktop - featureGroups returns the map in | |
854 | // the desktop featureRenderer | |
855 | 164 | return (featureGroups == null) ? Arrays.asList(new String[0]) |
856 | : Arrays.asList(featureGroups.keySet().toArray(new String[0])); | |
857 | } | |
858 | ||
859 | 49 | public boolean checkGroupVisibility(String group, |
860 | boolean newGroupsVisible) | |
861 | { | |
862 | 49 | if (featureGroups == null) |
863 | { | |
864 | // then an exception happens next.. | |
865 | } | |
866 | 49 | if (featureGroups.containsKey(group)) |
867 | { | |
868 | 49 | return featureGroups.get(group).booleanValue(); |
869 | } | |
870 | 0 | if (newGroupsVisible) |
871 | { | |
872 | 0 | featureGroups.put(group, Boolean.valueOf(true)); |
873 | 0 | return true; |
874 | } | |
875 | 0 | return false; |
876 | } | |
877 | ||
878 | /** | |
879 | * get visible or invisible groups | |
880 | * | |
881 | * @param visible | |
882 | * true to return visible groups, false to return hidden ones. | |
883 | * @return list of groups | |
884 | */ | |
885 | 16 | @Override |
886 | public List<String> getGroups(boolean visible) | |
887 | { | |
888 | 16 | if (featureGroups != null) |
889 | { | |
890 | 16 | List<String> gp = new ArrayList<>(); |
891 | ||
892 | 16 | for (String grp : featureGroups.keySet()) |
893 | { | |
894 | 60 | Boolean state = featureGroups.get(grp); |
895 | 60 | if (state.booleanValue() == visible) |
896 | { | |
897 | 0 | gp.add(grp); |
898 | } | |
899 | } | |
900 | 16 | return gp; |
901 | } | |
902 | 0 | return null; |
903 | } | |
904 | ||
905 | 123 | @Override |
906 | public void setGroupVisibility(String group, boolean visible) | |
907 | { | |
908 | 123 | featureGroups.put(group, Boolean.valueOf(visible)); |
909 | } | |
910 | ||
911 | 0 | @Override |
912 | public void setGroupVisibility(List<String> toset, boolean visible) | |
913 | { | |
914 | 0 | if (toset != null && toset.size() > 0 && featureGroups != null) |
915 | { | |
916 | 0 | boolean rdrw = false; |
917 | 0 | for (String gst : toset) |
918 | { | |
919 | 0 | Boolean st = featureGroups.get(gst); |
920 | 0 | featureGroups.put(gst, Boolean.valueOf(visible)); |
921 | 0 | if (st != null) |
922 | { | |
923 | 0 | rdrw = rdrw || (visible != st.booleanValue()); |
924 | } | |
925 | } | |
926 | 0 | if (rdrw) |
927 | { | |
928 | // set local flag indicating redraw needed ? | |
929 | } | |
930 | } | |
931 | } | |
932 | ||
933 | 22 | @Override |
934 | public Map<String, FeatureColourI> getDisplayedFeatureCols() | |
935 | { | |
936 | 22 | Map<String, FeatureColourI> fcols = new Hashtable<>(); |
937 | 22 | if (getViewport().getFeaturesDisplayed() == null) |
938 | { | |
939 | 5 | return fcols; |
940 | } | |
941 | 17 | Set<String> features = getViewport().getFeaturesDisplayed() |
942 | .getVisibleFeatures(); | |
943 | 17 | for (String feature : features) |
944 | { | |
945 | 26 | fcols.put(feature, getFeatureStyle(feature)); |
946 | } | |
947 | 17 | return fcols; |
948 | } | |
949 | ||
950 | 29267 | @Override |
951 | public FeaturesDisplayedI getFeaturesDisplayed() | |
952 | { | |
953 | 29267 | return av.getFeaturesDisplayed(); |
954 | } | |
955 | ||
956 | /** | |
957 | * Returns a (possibly empty) list of visible feature types, in render order | |
958 | * (last is on top) | |
959 | */ | |
960 | 106 | @Override |
961 | public List<String> getDisplayedFeatureTypes() | |
962 | { | |
963 | 106 | List<String> typ = getRenderOrder(); |
964 | 106 | List<String> displayed = new ArrayList<>(); |
965 | 106 | FeaturesDisplayedI feature_disp = av.getFeaturesDisplayed(); |
966 | 106 | if (feature_disp != null) |
967 | { | |
968 | 105 | synchronized (feature_disp) |
969 | { | |
970 | 105 | for (String type : typ) |
971 | { | |
972 | 303 | if (feature_disp.isVisible(type)) |
973 | { | |
974 | 285 | displayed.add(type); |
975 | } | |
976 | } | |
977 | } | |
978 | } | |
979 | 106 | return displayed; |
980 | } | |
981 | ||
982 | 10 | @Override |
983 | public List<String> getDisplayedFeatureGroups() | |
984 | { | |
985 | 10 | List<String> _gps = new ArrayList<>(); |
986 | 10 | for (String gp : getFeatureGroups()) |
987 | { | |
988 | 13 | if (checkGroupVisibility(gp, false)) |
989 | { | |
990 | 11 | _gps.add(gp); |
991 | } | |
992 | } | |
993 | 10 | return _gps; |
994 | } | |
995 | ||
996 | /** | |
997 | * Answers true if the feature belongs to a feature group which is not | |
998 | * currently displayed, else false | |
999 | * | |
1000 | * @param sequenceFeature | |
1001 | * @return | |
1002 | */ | |
1003 | 49745 | public boolean featureGroupNotShown(final SequenceFeature sequenceFeature) |
1004 | { | |
1005 | 49746 | return featureGroups != null && sequenceFeature.featureGroup != null |
1006 | && sequenceFeature.featureGroup.length() != 0 | |
1007 | && featureGroups.containsKey(sequenceFeature.featureGroup) | |
1008 | && !featureGroups.get(sequenceFeature.featureGroup) | |
1009 | .booleanValue(); | |
1010 | } | |
1011 | ||
1012 | /** | |
1013 | * {@inheritDoc} | |
1014 | */ | |
1015 | 62 | @Override |
1016 | public List<SequenceFeature> findFeaturesAtResidue(SequenceI sequence, | |
1017 | int fromResNo, int toResNo) | |
1018 | { | |
1019 | 62 | List<SequenceFeature> result = new ArrayList<>(); |
1020 | 62 | if (!av.areFeaturesDisplayed() || getFeaturesDisplayed() == null) |
1021 | { | |
1022 | 0 | return result; |
1023 | } | |
1024 | ||
1025 | /* | |
1026 | * include features at the position provided their feature type is | |
1027 | * displayed, and feature group is null or the empty string | |
1028 | * or marked for display | |
1029 | */ | |
1030 | 62 | List<String> visibleFeatures = getDisplayedFeatureTypes(); |
1031 | 62 | String[] visibleTypes = visibleFeatures |
1032 | .toArray(new String[visibleFeatures.size()]); | |
1033 | 62 | List<SequenceFeature> features = sequence.getFeatures() |
1034 | .findFeatures(fromResNo, toResNo, visibleTypes); | |
1035 | ||
1036 | 62 | for (SequenceFeature sf : features) |
1037 | { | |
1038 | 73 | if (!featureGroupNotShown(sf) && getColour(sf) != null) |
1039 | { | |
1040 | 73 | result.add(sf); |
1041 | } | |
1042 | } | |
1043 | 62 | return result; |
1044 | } | |
1045 | ||
1046 | /** | |
1047 | * Removes from the list of features any whose group is not shown, or that are | |
1048 | * visible and duplicate the location of a visible feature of the same type. | |
1049 | * Should be used only for features of the same, simple, feature colour (which | |
1050 | * normally implies the same feature type). No filtering is done if | |
1051 | * transparency, or any feature filters, are in force. | |
1052 | * | |
1053 | * @param features | |
1054 | */ | |
1055 | 1412 | public void filterFeaturesForDisplay(List<SequenceFeature> features) |
1056 | { | |
1057 | /* | |
1058 | * fudge: JalviewJS's IntervalStore lacks the sort method called :-( | |
1059 | */ | |
1060 | 1412 | if (Platform.isJS()) |
1061 | { | |
1062 | 0 | return; |
1063 | } | |
1064 | ||
1065 | /* | |
1066 | * don't remove 'redundant' features if | |
1067 | * - transparency is applied (feature count affects depth of feature colour) | |
1068 | * - filters are applied (not all features may be displayable) | |
1069 | */ | |
1070 | 1412 | if (features.isEmpty() || transparency != 1f |
1071 | || !featureFilters.isEmpty()) | |
1072 | { | |
1073 | 2 | return; |
1074 | } | |
1075 | ||
1076 | 1410 | SequenceFeatures.sortFeatures(features, true); |
1077 | 1410 | SequenceFeature lastFeature = null; |
1078 | ||
1079 | 1410 | Iterator<SequenceFeature> it = features.iterator(); |
1080 | 18006 | while (it.hasNext()) |
1081 | { | |
1082 | 16596 | SequenceFeature sf = it.next(); |
1083 | 16596 | if (featureGroupNotShown(sf)) |
1084 | { | |
1085 | 2 | it.remove(); |
1086 | 2 | continue; |
1087 | } | |
1088 | ||
1089 | /* | |
1090 | * a feature is redundant for rendering purposes if it has the | |
1091 | * same extent as another (so would just redraw the same colour); | |
1092 | * (checking type and isContactFeature as a fail-safe here, although | |
1093 | * currently they are guaranteed to match in this context) | |
1094 | */ | |
1095 | 16594 | if (lastFeature != null && sf.getBegin() == lastFeature.getBegin() |
1096 | && sf.getEnd() == lastFeature.getEnd() | |
1097 | && sf.isContactFeature() == lastFeature.isContactFeature() | |
1098 | && sf.getType().equals(lastFeature.getType())) | |
1099 | { | |
1100 | 3 | it.remove(); |
1101 | } | |
1102 | 16594 | lastFeature = sf; |
1103 | } | |
1104 | } | |
1105 | ||
1106 | 15 | @Override |
1107 | public Map<String, FeatureMatcherSetI> getFeatureFilters() | |
1108 | { | |
1109 | 15 | return featureFilters; |
1110 | } | |
1111 | ||
1112 | 0 | @Override |
1113 | public void setFeatureFilters(Map<String, FeatureMatcherSetI> filters) | |
1114 | { | |
1115 | 0 | featureFilters = filters; |
1116 | } | |
1117 | ||
1118 | 212 | @Override |
1119 | public FeatureMatcherSetI getFeatureFilter(String featureType) | |
1120 | { | |
1121 | 212 | return featureFilters.get(featureType); |
1122 | } | |
1123 | ||
1124 | 23 | @Override |
1125 | public void setFeatureFilter(String featureType, | |
1126 | FeatureMatcherSetI filter) | |
1127 | { | |
1128 | 23 | if (filter == null || filter.isEmpty()) |
1129 | { | |
1130 | 0 | featureFilters.remove(featureType); |
1131 | } | |
1132 | else | |
1133 | { | |
1134 | 23 | featureFilters.put(featureType, filter); |
1135 | } | |
1136 | } | |
1137 | ||
1138 | /** | |
1139 | * Answers the colour for the feature, or null if the feature is excluded by | |
1140 | * feature group visibility, by filters, or by colour threshold settings. This | |
1141 | * method does not take feature type visibility into account. | |
1142 | * | |
1143 | * @param sf | |
1144 | * @param fc | |
1145 | * @return | |
1146 | */ | |
1147 | 25383 | public Color getColor(SequenceFeature sf, FeatureColourI fc) |
1148 | { | |
1149 | /* | |
1150 | * is the feature group displayed? | |
1151 | */ | |
1152 | 25383 | if (featureGroupNotShown(sf)) |
1153 | { | |
1154 | 8 | return null; |
1155 | } | |
1156 | ||
1157 | /* | |
1158 | * does the feature pass filters? | |
1159 | */ | |
1160 | 25375 | if (!featureMatchesFilters(sf)) |
1161 | { | |
1162 | 6 | return null; |
1163 | } | |
1164 | ||
1165 | 25369 | return fc.getColor(sf); |
1166 | } | |
1167 | ||
1168 | /** | |
1169 | * Answers true if there no are filters defined for the feature type, or this | |
1170 | * feature matches the filters. Answers false if the feature fails to match | |
1171 | * filters. | |
1172 | * | |
1173 | * @param sf | |
1174 | * @return | |
1175 | */ | |
1176 | 25413 | protected boolean featureMatchesFilters(SequenceFeature sf) |
1177 | { | |
1178 | 25413 | FeatureMatcherSetI filter = featureFilters.get(sf.getType()); |
1179 | 25413 | return filter == null ? true : filter.matches(sf); |
1180 | } | |
1181 | ||
1182 | /** | |
1183 | * Answers true unless the specified group is set to hidden. Defaults to true | |
1184 | * if group visibility is not set. | |
1185 | * | |
1186 | * @param group | |
1187 | * @return | |
1188 | */ | |
1189 | 0 | public boolean isGroupVisible(String group) |
1190 | { | |
1191 | 0 | if (!featureGroups.containsKey(group)) |
1192 | { | |
1193 | 0 | return true; |
1194 | } | |
1195 | 0 | return featureGroups.get(group); |
1196 | } | |
1197 | ||
1198 | /** | |
1199 | * Orders features in render precedence (last in order is last to render, so | |
1200 | * displayed on top of other features) | |
1201 | * | |
1202 | * @param order | |
1203 | */ | |
1204 | 65 | public void orderFeatures(Comparator<String> order) |
1205 | { | |
1206 | 65 | Arrays.sort(renderOrder, order); |
1207 | } | |
1208 | ||
1209 | 2 | @Override |
1210 | public MappedFeatures findComplementFeaturesAtResidue( | |
1211 | final SequenceI sequence, final int pos) | |
1212 | { | |
1213 | 2 | SequenceI ds = sequence.getDatasetSequence(); |
1214 | 2 | if (ds == null) |
1215 | { | |
1216 | 0 | ds = sequence; |
1217 | } | |
1218 | 2 | final char residue = ds.getCharAt(pos - ds.getStart()); |
1219 | ||
1220 | 2 | List<SequenceFeature> found = new ArrayList<>(); |
1221 | 2 | List<AlignedCodonFrame> mappings = this.av.getAlignment() |
1222 | .getCodonFrame(sequence); | |
1223 | ||
1224 | /* | |
1225 | * fudge: if no mapping found, check the complementary alignment | |
1226 | * todo: only store in one place? StructureSelectionManager? | |
1227 | */ | |
1228 | 2 | if (mappings.isEmpty()) |
1229 | { | |
1230 | 2 | mappings = this.av.getCodingComplement().getAlignment() |
1231 | .getCodonFrame(sequence); | |
1232 | } | |
1233 | ||
1234 | /* | |
1235 | * todo: direct lookup of CDS for peptide and vice-versa; for now, | |
1236 | * have to search through an unordered list of mappings for a candidate | |
1237 | */ | |
1238 | 2 | SequenceToSequenceMapping mapping = null; |
1239 | 2 | SequenceI mapFrom = null; |
1240 | ||
1241 | 2 | for (AlignedCodonFrame acf : mappings) |
1242 | { | |
1243 | 2 | mapping = acf.getCoveringCodonMapping(ds); |
1244 | 2 | if (mapping == null) |
1245 | { | |
1246 | 0 | continue; |
1247 | } | |
1248 | 2 | SearchResultsI sr = new SearchResults(); |
1249 | 2 | mapping.markMappedRegion(ds, pos, sr); |
1250 | 2 | for (SearchResultMatchI match : sr.getResults()) |
1251 | { | |
1252 | 2 | int fromRes = match.getStart(); |
1253 | 2 | int toRes = match.getEnd(); |
1254 | 2 | mapFrom = match.getSequence(); |
1255 | 2 | List<SequenceFeature> fs = findFeaturesAtResidue(mapFrom, fromRes, |
1256 | toRes); | |
1257 | 2 | for (SequenceFeature sf : fs) |
1258 | { | |
1259 | 3 | if (!found.contains(sf)) |
1260 | { | |
1261 | 3 | found.add(sf); |
1262 | } | |
1263 | } | |
1264 | } | |
1265 | ||
1266 | /* | |
1267 | * just take the first mapped features we find | |
1268 | */ | |
1269 | 2 | if (!found.isEmpty()) |
1270 | { | |
1271 | 2 | break; |
1272 | } | |
1273 | } | |
1274 | 2 | if (found.isEmpty()) |
1275 | { | |
1276 | 0 | return null; |
1277 | } | |
1278 | ||
1279 | /* | |
1280 | * sort by renderorder (inefficiently but ok for small scale); | |
1281 | * NB this sorts 'on top' feature to end, for rendering | |
1282 | */ | |
1283 | 2 | List<SequenceFeature> result = new ArrayList<>(); |
1284 | 2 | final int toAdd = found.size(); |
1285 | 2 | int added = 0; |
1286 | 2 | for (String type : renderOrder) |
1287 | { | |
1288 | 3 | for (SequenceFeature sf : found) |
1289 | { | |
1290 | 4 | if (type.equals(sf.getType())) |
1291 | { | |
1292 | 3 | result.add(sf); |
1293 | 3 | added++; |
1294 | } | |
1295 | 4 | if (added == toAdd) |
1296 | { | |
1297 | 2 | break; |
1298 | } | |
1299 | } | |
1300 | } | |
1301 | ||
1302 | 2 | return new MappedFeatures(mapping.getMapping(), mapFrom, pos, residue, |
1303 | result); | |
1304 | } | |
1305 | ||
1306 | 46 | @Override |
1307 | public boolean isVisible(SequenceFeature feature) | |
1308 | { | |
1309 | 46 | if (feature == null) |
1310 | { | |
1311 | 1 | return false; |
1312 | } | |
1313 | 45 | if (getFeaturesDisplayed() == null |
1314 | || !getFeaturesDisplayed().isVisible(feature.getType())) | |
1315 | { | |
1316 | 1 | return false; |
1317 | } | |
1318 | 44 | if (featureGroupNotShown(feature)) |
1319 | { | |
1320 | 2 | return false; |
1321 | } | |
1322 | 42 | FeatureColourI fc = featureColours.get(feature.getType()); |
1323 | 42 | if (fc != null && fc.isOutwithThreshold(feature)) |
1324 | { | |
1325 | 4 | return false; |
1326 | } | |
1327 | 38 | if (!featureMatchesFilters(feature)) |
1328 | { | |
1329 | 3 | return false; |
1330 | } | |
1331 | 35 | return true; |
1332 | } | |
1333 | } |