Bio-Graphics-2.37000755001750001750 012165075746 13710 5ustar00lsteinlstein000000000000Bio-Graphics-2.37/DISCLAIMER.txt000555001750001750 216512165075746 16231 0ustar00lsteinlstein000000000000The Bio::Graphics package and all associated files are Copyright (c) 2001 Cold Spring Harbor Laboratory. This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See the Artistic License file in the main Perl distribution for specific terms and conditions of use. In addition, the following disclaimers apply: CSHL makes no representations whatsoever as to the SOFTWARE contained herein. It is experimental in nature and is provided WITHOUT WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE OR ANY OTHER WARRANTY, EXPRESS OR IMPLIED. CSHL MAKES NO REPRESENTATION OR WARRANTY THAT THE USE OF THIS SOFTWARE WILL NOT INFRINGE ANY PATENT OR OTHER PROPRIETARY RIGHT. By downloading this SOFTWARE, your Institution hereby indemnifies CSHL against any loss, claim, damage or liability, of whatsoever kind or nature, which may arise from your Institution's respective use, handling or storage of the SOFTWARE. If publications result from research using this SOFTWARE, we ask that CSHL be acknowledged and/or credit be given to CSHL scientists, as scientifically appropriate. Bio-Graphics-2.37/MANIFEST000444001750001750 1412512165075746 15221 0ustar00lsteinlstein000000000000Build.PL Changes DISCLAIMER.txt eg/feature_data.gff eg/feature_data.txt eg/testit.pl eg/testit2.pl eg/testit3.pl lib/Bio/Graphics.pm lib/Bio/Graphics/ConfiguratorI.pm lib/Bio/Graphics/DrawTransmembrane.pm lib/Bio/Graphics/Feature.pm lib/Bio/Graphics/FeatureBase.pm lib/Bio/Graphics/FeatureDir.pm lib/Bio/Graphics/FeatureFile.pm lib/Bio/Graphics/FeatureFile/Iterator.pm lib/Bio/Graphics/GDWrapper.pm lib/Bio/Graphics/Glyph.pm lib/Bio/Graphics/Glyph/alignment.pm lib/Bio/Graphics/Glyph/allele_tower.pm lib/Bio/Graphics/Glyph/anchored_arrow.pm lib/Bio/Graphics/Glyph/arrow.pm lib/Bio/Graphics/Glyph/box.pm lib/Bio/Graphics/Glyph/broken_line.pm lib/Bio/Graphics/Glyph/cds.pm lib/Bio/Graphics/Glyph/christmas_arrow.pm lib/Bio/Graphics/Glyph/cross.pm lib/Bio/Graphics/Glyph/crossbox.pm lib/Bio/Graphics/Glyph/dashed_line.pm lib/Bio/Graphics/Glyph/decorated_gene.pm lib/Bio/Graphics/Glyph/decorated_transcript.pm lib/Bio/Graphics/Glyph/diamond.pm lib/Bio/Graphics/Glyph/dna.pm lib/Bio/Graphics/Glyph/dot.pm lib/Bio/Graphics/Glyph/dumbbell.pm lib/Bio/Graphics/Glyph/ellipse.pm lib/Bio/Graphics/Glyph/ex.pm lib/Bio/Graphics/Glyph/extending_arrow.pm lib/Bio/Graphics/Glyph/Factory.pm lib/Bio/Graphics/Glyph/fb_shmiggle.pm lib/Bio/Graphics/Glyph/fixedwidth.pm lib/Bio/Graphics/Glyph/flag.pm lib/Bio/Graphics/Glyph/gene.pm lib/Bio/Graphics/Glyph/generic.pm lib/Bio/Graphics/Glyph/graded_segments.pm lib/Bio/Graphics/Glyph/group.pm lib/Bio/Graphics/Glyph/hat.pm lib/Bio/Graphics/Glyph/heat_map.pm lib/Bio/Graphics/Glyph/heat_map_ideogram.pm lib/Bio/Graphics/Glyph/heterogeneous_segments.pm lib/Bio/Graphics/Glyph/hidden.pm lib/Bio/Graphics/Glyph/hybrid_plot.pm lib/Bio/Graphics/Glyph/ideogram.pm lib/Bio/Graphics/Glyph/image.pm lib/Bio/Graphics/Glyph/lightning.pm lib/Bio/Graphics/Glyph/line.pm lib/Bio/Graphics/Glyph/merge_parts.pm lib/Bio/Graphics/Glyph/merged_alignment.pm lib/Bio/Graphics/Glyph/minmax.pm lib/Bio/Graphics/Glyph/operon.pm lib/Bio/Graphics/Glyph/oval.pm lib/Bio/Graphics/Glyph/pairplot.pm lib/Bio/Graphics/Glyph/pentagram.pm lib/Bio/Graphics/Glyph/phylo_align.pm lib/Bio/Graphics/Glyph/pinsertion.pm lib/Bio/Graphics/Glyph/point_glyph.pm lib/Bio/Graphics/Glyph/primers.pm lib/Bio/Graphics/Glyph/processed_transcript.pm lib/Bio/Graphics/Glyph/protein.pm lib/Bio/Graphics/Glyph/ragged_ends.pm lib/Bio/Graphics/Glyph/rainbow_gene.pm lib/Bio/Graphics/Glyph/read_pair.pm lib/Bio/Graphics/Glyph/redgreen_box.pm lib/Bio/Graphics/Glyph/redgreen_segment.pm lib/Bio/Graphics/Glyph/repeating_shape.pm lib/Bio/Graphics/Glyph/rndrect.pm lib/Bio/Graphics/Glyph/ruler_arrow.pm lib/Bio/Graphics/Glyph/saw_teeth.pm lib/Bio/Graphics/Glyph/scale.pm lib/Bio/Graphics/Glyph/segmented_keyglyph.pm lib/Bio/Graphics/Glyph/segments.pm lib/Bio/Graphics/Glyph/smoothing.pm lib/Bio/Graphics/Glyph/so_transcript.pm lib/Bio/Graphics/Glyph/span.pm lib/Bio/Graphics/Glyph/spectrogram.pm lib/Bio/Graphics/Glyph/splice_site.pm lib/Bio/Graphics/Glyph/stackedplot.pm lib/Bio/Graphics/Glyph/ternary_plot.pm lib/Bio/Graphics/Glyph/text_in_box.pm lib/Bio/Graphics/Glyph/three_letters.pm lib/Bio/Graphics/Glyph/tic_tac_toe.pm lib/Bio/Graphics/Glyph/toomany.pm lib/Bio/Graphics/Glyph/trace.pm lib/Bio/Graphics/Glyph/track.pm lib/Bio/Graphics/Glyph/transcript.pm lib/Bio/Graphics/Glyph/transcript2.pm lib/Bio/Graphics/Glyph/translation.pm lib/Bio/Graphics/Glyph/triangle.pm lib/Bio/Graphics/Glyph/two_bolts.pm lib/Bio/Graphics/Glyph/vista_plot.pm lib/Bio/Graphics/Glyph/wave.pm lib/Bio/Graphics/Glyph/weighted_arrow.pm lib/Bio/Graphics/Glyph/whiskerplot.pm lib/Bio/Graphics/Glyph/wiggle_box.pm lib/Bio/Graphics/Glyph/wiggle_data.pm lib/Bio/Graphics/Glyph/wiggle_density.pm lib/Bio/Graphics/Glyph/wiggle_whiskers.pm lib/Bio/Graphics/Glyph/wiggle_xyplot.pm lib/Bio/Graphics/Glyph/xyplot.pm lib/Bio/Graphics/Layout.pm lib/Bio/Graphics/Panel.pm lib/Bio/Graphics/Pictogram.pm lib/Bio/Graphics/RendererI.pm lib/Bio/Graphics/Util.pm lib/Bio/Graphics/Wiggle.pm lib/Bio/Graphics/Wiggle/Loader.pm Makefile.PL MANIFEST MANIFEST.SKIP META.json META.yml README README.bioperl scripts/contig_draw.pl scripts/feature_draw.pl scripts/frend.pl scripts/glyph_help.pl scripts/index_cov_files.pl scripts/render_msa.pl scripts/search_overview.pl t/BioGraphics.t t/data/decorated_transcript_t1.gff t/data/decorated_transcript_t1.png t/data/feature_data.txt t/data/t1.gif t/data/t1.png t/data/t1/version1.gif t/data/t1/version1.png t/data/t1/version10.png t/data/t1/version11.gif t/data/t1/version11.png t/data/t1/version12.gif t/data/t1/version12.png t/data/t1/version13.gif t/data/t1/version13.png t/data/t1/version14.gif t/data/t1/version14.png t/data/t1/version2.gif t/data/t1/version2.png t/data/t1/version3.gif t/data/t1/version3.png t/data/t1/version4.png t/data/t1/version5.png t/data/t1/version6.png t/data/t1/version7.png t/data/t1/version8.png t/data/t1/version9.gif t/data/t1/version9.png t/data/t2.gif t/data/t2.png t/data/t2/version1.gif t/data/t2/version1.png t/data/t2/version10.png t/data/t2/version11.png t/data/t2/version12.png t/data/t2/version13.png t/data/t2/version14.gif t/data/t2/version14.png t/data/t2/version15.png t/data/t2/version16.gif t/data/t2/version16.png t/data/t2/version17.gif t/data/t2/version17.png t/data/t2/version18.gif t/data/t2/version18.png t/data/t2/version19.gif t/data/t2/version19.png t/data/t2/version2.gif t/data/t2/version2.png t/data/t2/version20.gif t/data/t2/version20.png t/data/t2/version3.gif t/data/t2/version3.png t/data/t2/version4.png t/data/t2/version5.png t/data/t2/version6.png t/data/t2/version7.png t/data/t2/version8.png t/data/t2/version9.png t/data/t3.gif t/data/t3.png t/data/t3/version1.gif t/data/t3/version1.png t/data/t3/version10.gif t/data/t3/version10.png t/data/t3/version11.gif t/data/t3/version11.png t/data/t3/version12.gif t/data/t3/version12.png t/data/t3/version13.gif t/data/t3/version13.png t/data/t3/version14.gif t/data/t3/version14.png t/data/t3/version15.gif t/data/t3/version15.png t/data/t3/version2.gif t/data/t3/version2.png t/data/t3/version3.gif t/data/t3/version3.png t/data/t3/version4.png t/data/t3/version5.png t/data/t3/version6.png t/data/t3/version7.png t/data/t3/version8.gif t/data/t3/version8.png t/data/t3/version9.png t/data/wig_data.wig t/decorated_transcript_t1.pl t/Wiggle.t Bio-Graphics-2.37/README.bioperl000555001750001750 35712165075746 16350 0ustar00lsteinlstein000000000000Bio::Graphics requires BioPerl version 1.6 ($VERSION 1.006 or higher), which is expected to be released by the end of 2008. Until then, you should use the version of Bio::Graphics that is in the current (1.5 or lower) version of BioPerl. Bio-Graphics-2.37/Build.PL000555001750001750 215712165075746 15351 0ustar00lsteinlstein000000000000#!/usr/bin/perl use strict; use warnings; use Module::Build; my $build = Module::Build->new( module_name => 'Bio::Graphics', license => 'perl', dist_version_from => 'lib/Bio/Graphics.pm', dist_author => 'Lincoln Stein ', requires => { 'Bio::Root::Version' => '1.005009001', 'GD' => 2.30, 'Statistics::Descriptive' => 2.6, # required for Bio::Graphics::Wiggle::Loader #and tests fail without it }, recommends => { 'GD::SVG' => 0.32, 'Text::ParseWords' => 3.26, # required for Bio::Graphics::Wiggle::Loader # 'Bio::SCF' => 1.01, # required for Bio::Graphics::Glyph::trace }, script_files => ['scripts/contig_draw.pl', 'scripts/feature_draw.pl', 'scripts/frend.pl', 'scripts/glyph_help.pl', 'scripts/search_overview.pl', 'scripts/render_msa.pl', ], create_makefile_pl => 'passthrough', build_class => 'Module::Build', ); $build->create_build_script; exit 0; Bio-Graphics-2.37/Changes000555001750001750 4061712165075746 15373 0ustar00lsteinlstein000000000000Revision history for Perl extension Bio::Graphics. 2.37 - Merge fix for incorrect parsing of feature_file sections (issue #6) - Added decorated_transcript glyph - Added nathanweeks fix for broken heatmap glyph 2.36 - Fix regression in glyph parent_feature() method so that it once again returns the parent of the current feature. - Remove recommended prerequisite of Bio::SCF, which is hardly used now. 2.35 - Change Glyph/segments.pm to work with both variants of CIGAR strings. - Workaround for broken useFontConfig() support in versions of GD prior to 2.50. - Fix "gdTinyFont doesn't support height() method error in xyplot". 2.34 Thu May 16 15:42:25 CDT 2013 - Fixed silent crashes when rendering with the GD::SVG class. 2.33 Fri Feb 22 15:58:10 EST 2013 - Add truetype support. Enable by passing -truetype=>1 to Bio::Graphics::Panel->new() 2.32 Mon Dec 10 05:47:45 EST 2012 - Clean up appearance of crossbox to avoid odd black bar in the middle. 2.32 Mon Dec 10 05:47:45 EST 2012 - Clean up appearance of crossbox to avoid odd black bar in the middle. 2.31 Tue Sep 25 22:39:43 EDT 2012 - Fix infinite loop when drawing wiggle_xyplots with z-score scaling over regions that do not vary much. 2.30 - Added the glyph for FlyBase's "stacked wiggle" plot (fb_shmiggle.pm) as well as a utility script for preparing the data (index_cov_files.pl). 2.29 - Fixes for Bio::Graphics::Wiggle to prevent crash when the "bottom" keystyle selected. - Add ability to obtain Bio::Graphics::Wiggle features directly from the Bio::Graphics::Wiggle::Loader without creating an intermediate gff3 file. 2.28 - Fixes to restore density plot functionality in hybrid_plot and vista_plot. - Fix handling of Bio::Graphics::Wiggle files in xyplots. 2.27 - Fix handling of code ref subs to allow for option=>\&Packagename::functionname. 2.26 - Support for normalizing quantitative plots across entire track. - Support for transparency within tracks, allowing features to overlap. - Support for -color_series and -color_cycle options (see Bio::Graphics::Panel) which cycle dynamically among a fixed series of background colors. 2.25 - Deprecate xyplot "histogram" subtype and default to "boxes". - Add xyplot overlay feature. - Fix gene glyph so that it collapses appropriately when bump explicitly set to 0. - Highlighting now works on features contained within groups. 2.24 - In feature file configuration sections, allow a tag to contain ":". 2.23 - Workaround a problem in BioPerl 1.069 which prevents FeatureFile from rendering properly. Fixed in 1.06902 and higher. - Fix issue which caused the track bgcolor property to be ignored in some cases. 2.22 - Changed package "Math" to "Bio::Graphics::Math" to avoid CPAN namespace collisions. 2.21 - Changed almost all occurrences of attributes() into eval{$feature->get_tag_values()} to achieve compatibility with Bio::SeqFeatureI. One exception is in spectrogram glyph, which depends on the non-standard behavior of attributes() when called with no args. 2.20 - Fixed the various "wiggle" glyphs so that scale is always drawn even if no data is to be shown in selected region. 2.19 - Made "record_label_positions" default to "true" for wiggle_density plot. This activates a fixed-position floating label when density plots are being shown in GBrowse. - Fixed a bug in wiggle_xyplot that prevented plot from being drawn in SVG renderings. 2.18 - Made handling of min/max scale calculations consistent across all xyplot glyphs and their derivatives. - Added following autoscale options to wiggle_xyplot and wiggle_whiskers: "z-score" to rescale data such that mean is zero and values are standard deviation-fold change; and "clipped_global" to scale to global mean +/- some number of standard deviations indicated by "z_score_bounds". - Added "z_score_bounds" option to wiggle_xyplot and wiggle_whiskers to control how many standard deviations to show. 2.17 - In segments glyph, fixed bleedover of "deletion" color when a deletion is followed by an insertion. - In segments glyph, fixed the display of inserted bases such that they will not bleed into a preceding deletion. - In segments glyph, fixed the mismatch color highlighting when the alignment is to the negative strand. - In segments glyph, fixed the default colors for mismatch, insertion and deletion. - In segments glyph, fixed treatment of soft clipping as an "insertion"; this will avoid the insertion color from appearing at the ends of soft-clipped reads. - In gene glyph, fixed occasional linking of two neighboring transcripts. 2.16 - Distinguish between "chromosome" and "global" autoscaling in the xyplot and density glyphs. Global autoscaling only works when underlying database is bigwig. - Regularized options which select glyph subtypes with an option named "glyph_subtype". 2.15 - Fixed documentation bug: sort_order options to sort by feature length should be "longest" and "shortest" rather than "longer and "shorter". - Improved layout algorithm, achieving speedup of ~4x on busy tracks. - Fixed font color problems when displaying multiple alignments at DNA level in the segments glyph. - Fixed problem of DNA alignment indels disappearing from view when they span entire region. - Fixed problem of mismatch and indel colors leaking off ends of feature arrows. 2.14 - Fixes to the way that "fast bump" works so that tracks never fast bump. This fixes problems when using groups to simulate subtracks, and the groups are of different heights. - The group glyph will now add the group labels to the list of track keys stored in the panel and retrieved from the call to $panel->key_boxes(). 2.13 - Changed default namespace for callbacks to make them portable across freeze/thaw cycles. 2.12 Tue Aug 31 10:42:06 EDT 2010 - Created a read_pairs glyph that contains the settings most often used for SAM paired end reads/mate pairs; this fixes the mate-pairs overlap bug. - xyplot glyph now obeys "flip" setting. 2.11 Tue Jun 29 15:37:03 EDT 2010 - Cleaned up stylesheet-based rendering of features when the main glyph has a type of "hat" and subfeatures overlap with each other. This occurred when rendering certain DAS sources. 2.10 Mon May 24 13:58:26 PDT 2010 - Fixed a long-standing but rarely-seen bug in layout algorithm that caused some features to be displaced downward further than they should be. - Added support for labeling groups (on the left side). This is used by GBrowse to create subtracks. 2.09 Fri May 14 11:46:51 EDT 2010 - Fixed Bio::Graphics::FeatureFile cached callbacks so that they are evaluated in the same package as they were when the file was originally evaluated and cached. 2.08 Thu May 13 17:06:29 EDT 2010 - Fixed Bio::Graphics::FeatureFile init_code so that it is reinvoked when retrieving a cached copy of parsed featurefiles. 2.07 Sun May 9 11:56:10 EDT 2010 - Added FeatureFile->cachedir argument to help with GBrowse tests. 2.06 Wed May 5 00:51:00 EDT 2010 - Segments glyph now displays correct mismatch color for sequences that begin or end with soft clips. - Indels displayed in correct color when indel begins or ends outside current visible region. 2.05 Identical to 2.04. 2.04 Sun Apr 18 17:26:01 EDT 2010 - Segments glyph now smarter about fetching reference sequence; this improves performance on multiple alignments. - -show_mismatch option now takes following arguments: 0 (false), "always", "base level" (draw only when DNA is in view), or a number, in which case mismatches will only be drawn when the length of the window is <= that number. For compatibility, a value of "1" is the same as "base level." - Fixed display of protein sequence in genes when -draw_protein is true. - Fixed display of long deletions in segments glyph base-pair alignments to avoid drop-out of sequence from the right end of the alignment following deleted region. - Fixed display of insertions in segments glyph base-pair alignments so that the length of the deletion is displayed when there is more than one digit of length. - Fixed Wiggle loader routine to handle statistics on chromosomes that have a combination of fixed and variable step declarations. 2.03 Fri Mar 26 16:32:02 EDT 2010 - Bad CPAN upload. Do not use. 2.02 Sun Mar 14 18:44:04 EDT 2010 - Added a wiggle_whiskers glyph that works nicely with BigWig and BigBed data. - Fixed processed_transcript (and gene) glyphs so that thin UTRs are more attractive. - Fixed the behavior of transcript arrows to be more customizable and not to get too large. - Segments glyph now shows mismatches when zoomed out and -show_mismatches is true. - Added an -indel_color option to segments glyph to show indels in a different color from simple nucleotide substitutions. - Added a -mismatch_only option to segments glyph that only shows mismatching base pairs when zoomed in. 2.01 Thu Feb 25 16:46:08 EST 2010 - Fixed display of alignments that have hard-clipping in their CIGAR strings. 2.00 Wed Jan 20 11:12:13 EST 2010 - Added the "cross" glyph for DAS compatibility. - Fixed the triangle glyph so that DAS stylesheets can set orientation properly. - Fixed wiggle_xyplot/density documentation of smoothing options. - Turn off sampling from wiggle loader by default (turn it on with --sample option). - Multiple sequence alignment code in the segments glyph has been updated to deal with the Samtools case of both reference DNA and target reporting minus strand alignments. - It felt like time to go to 2.00. 1.995 Wed Jan 6 10:06:17 EST 2010 - Fixed the gene glyph so that non-SO genes (gene=>exon without an intervening transcript) display properly. Otherwise the exons were bumping. - Added support for "featureRGB" and "featureScore" special color names. This provides an additional level of UCSC graphics compatibility. 1.994 Thu Dec 10 10:07:19 EST 2009 - The GFF3 Gap attribute (which contains a CIGAR string) is now supported in the segments glyph. Set -split_on_cigar=>1 when creating the track in order to activate this feature. 1.993 Thu Dec 3 06:36:06 EST 2009 - Fixed issue which caused GD::SVG rendering of xyplot glyph to show scale but no values under some circumstances. 1.992 Wed Nov 18 13:06:07 EST 2009 - Fixed issue in which the connector vanishes when zoomed in to the region between two parts (such as the region between two exons) 1.991 Mon Nov 16 09:20:18 EST 2009 - CPAN upload failed due to lack of $VERSION in hybrid_plot. Uploaded again. 1.99 Mon Nov 16 08:15:23 EST 2009 - Segments glyph now handles indels for features that have CIGAR strings. 1.982 Wed Aug 26 17:57:43 EDT 2009 - Fixed DAS stylesheet support so that Ensembl and Dazzle sources both work properly. 1.981 Wed Aug 19 15:21:31 EDT 2009 - Peter Ruzanov fixed bug in wiggle_xyplot that caused histogram to go to background color under some circumstances. 1.98 Mon Jul 6 09:48:58 EDT 2009 - Documented -feature_limit in the Panel docs as well as in Glyph. - Fixed bug in wiggle_xyplot that caused an ugly 1-pixel rectangle to be drawn in the case of a zero-height data value. 1.97 Thu Jul 2 15:22:21 EDT 2009 - Added "feature_limit" option to put a cap on the maximum number of features that a track will attempt to display. This only works correctly if features are added one at a time with add_feature(). 1.96 Thu Jun 4 17:49:57 EDT 2009 - Added "hat" and "hidden" glyphs, and changed way that the "line" glyph works in order to support DAS 1.5. 1.95 Sat May 30 18:07:21 EDT 2009 - In the substitution pattern rules, $id is replaced with the output of either the feature_id or primary_id methods depending on which one is implemented. - The image.pm glyph will now render into SVG if a sufficiently-current GD::SVG module is available. - Some fixes to the segments glyph to work better with Bio::DB::Bam rendering at the base pair level. - The glyph_help.pl script can now create SVGs. 1.94 Wed Apr 29 05:59:02 EDT 2009 - Added a "fast" bumping option suitable for very dense tracks in which all features have identical height. Activate it using -bump=>3 or -bump=>'fast'. - Fixed division by zero error in xyplot glyph when min_score==max_score. 1.93 Thu Apr 2 18:20:35 EDT 2009 - Many fixes to ideograph glyph to be more stable. - Fixed minor display issues involving bumping and directional arrows. - Continued documenting glyphs. About 75% done. 1.92 Tue Mar 31 00:38:16 EDT 2009 - Added documentation system for glyphs, but only half the glyphs are documented this way so far. - Bug fixes for GBrowse on Windows. 1.91 Tue Mar 17 09:54:02 EDT 2009 - wiggle_density now supports local scaling - wiggle loader now defaults to clipping at 2 stdev - adjusted default smoothing parameters to do less smoothing 1.90 Sun Mar 15 01:11:28 EDT 2009 - Optimized Bio::Graphics::Wiggle to sample directly from disk when the desired visualization size is significantly (less than 100 fold) smaller than the length of the region to be sampled. 1.88 Sat Mar 14 23:31:46 EDT 2009 -Cleaned up calculation of min and max values for scaling and introduced the "autoscale" option. 1.87 Sat Mar 14 20:56:47 EDT 2009 -Fixed bug in xyplot visualization. 1.86 Sat Mar 14 12:22:50 EDT 2009 -Improved performance of wiggle xyplot drawing about 5-fold -Added minor ticks to xyplots 1.83 Fri Jan 9 20:44:41 MST 2009 - Made #include directive case-insensitive. 1.80 December 2008 - Split off from Bio::Perl after a 6-year hiatus. 1.05 Fri Apr 12 20:53:42 EDT 2002 - Version 1.04 had a fatal bug. Do not use. 1.05 fixes this and is identical to BioPerl 1.01. 1.04 Fri Apr 12 08:20:35 EDT 2002 - Take advantage of optimizations in Bio::DB::GFF::Feature so don't have to retrieve subfeatures at low magnifications (when you can't see 'em anyway). 1.03 Fri Apr 12 00:26:12 EDT 2002 - Fixes to handle case of a transcript glyph that is zoomed in so far that only an intron shows (no exons). 1.02 Sun Mar 31 16:19:35 EST 2002 - Make the Bio::Graphics::Feature objects more-or-less compatible with Bio::SeqFeatureI and Bio::LocationI. Slightly difficult because this is a moving target. - Minor bugfixes to support Generic Genome browser version 1.37 0.98 Fri Feb 22 14:02:11 EST 2002 -Fixed up the scale so that numbers don't (or shouldn't) overlap. 0.97 Mon Feb 18 22:04:57 EST 2002 -Added the "dna" glyph, which supports display of raw DNA and a GC content histogram. 0.96 Fri Jan 11 13:23:03 EST 2002 -Added support for displaying heterogeneous features, such as WABA similarities. 0.95 Thu Jan 3 08:50:01 EST 2002 (LS) -Removed generic genome browser from project, it is now part of Generic-Genome-Browser. 0.92 Sat Dec 8 23:28:19 EST 2001 (LS) -Fixed up key glyph so that it correctly tracks what appears on screen -Fixed wormbase_transcript glyph so that it uses the skinny arrow when there isn't enough room to show the filled arrow. 0.91 Sun Nov 18 22:59:14 EST 2001 (LS) -Modified Feature.pm to accept vanilla GFF format. -Fixed Panel.pm so that empty tracks (height zero) do not take up space in the map. 0.90 Sun Nov 18 21:46:46 EST 2001 (LS) - Added the gbrowse genome browser script and supporting files - Added a wormbase_transcript glyph for Wormbase's curated/uncurated genes. 0.85 Mon Oct 1 16:41:57 EDT 2001 (LS) - Changed all instances of stop() to end() so that BioSeqFeatureI is supported correctly. 0.82 Fri Jul 20 10:36:57 EDT 2001 (LS) - Removed perl 5.6'isms. - Added appropriate documentation to Feature. 0.81 Tue Jun 12 09:30:10 EDT 2001 (LS) - Messed up sourceforge upload, so bumping version no to clean up. 0.80 Tue Jun 12 09:10:15 EDT 2001 (LS) - Basically functional - examples in eg/ - Bio::Graphics::Panel documentation complete, other documentation incomplete 0.01 Tue Jun 5 07:26:52 2001 (LS) - original version; created by h2xs 1.20 with options -n Bio::Graphics -A Bio-Graphics-2.37/META.yml000444001750001750 2555512165075746 15352 0ustar00lsteinlstein000000000000--- abstract: 'Generate GD images of Bio::Seq objects' author: - 'Lincoln Stein ' build_requires: {} configure_requires: Module::Build: 0.38 dynamic_config: 1 generated_by: 'Module::Build version 0.38, CPAN::Meta::Converter version 2.110440' license: perl meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: 1.4 name: Bio-Graphics provides: Bio::Graphics: file: lib/Bio/Graphics.pm version: 2.37 Bio::Graphics::ConfiguratorI: file: lib/Bio/Graphics/ConfiguratorI.pm version: 0 Bio::Graphics::DrawTransmembrane: file: lib/Bio/Graphics/DrawTransmembrane.pm version: 0 Bio::Graphics::Feature: file: lib/Bio/Graphics/Feature.pm version: 0 Bio::Graphics::FeatureBase: file: lib/Bio/Graphics/FeatureBase.pm version: 0 Bio::Graphics::FeatureDir: file: lib/Bio/Graphics/FeatureDir.pm version: 0 Bio::Graphics::FeatureFile: file: lib/Bio/Graphics/FeatureFile.pm version: 0 Bio::Graphics::FeatureFile::Iterator: file: lib/Bio/Graphics/FeatureFile/Iterator.pm version: 0 Bio::Graphics::FileSplitter: file: lib/Bio/Graphics/FeatureDir.pm version: 0 Bio::Graphics::GDWrapper: file: lib/Bio/Graphics/GDWrapper.pm version: 0 Bio::Graphics::Glyph: file: lib/Bio/Graphics/Glyph.pm version: 0 Bio::Graphics::Glyph::Factory: file: lib/Bio/Graphics/Glyph/Factory.pm version: 0 Bio::Graphics::Glyph::alignment: file: lib/Bio/Graphics/Glyph/alignment.pm version: 0 Bio::Graphics::Glyph::allele_tower: file: lib/Bio/Graphics/Glyph/allele_tower.pm version: 0 Bio::Graphics::Glyph::anchored_arrow: file: lib/Bio/Graphics/Glyph/anchored_arrow.pm version: 0 Bio::Graphics::Glyph::arrow: file: lib/Bio/Graphics/Glyph/arrow.pm version: 0 Bio::Graphics::Glyph::box: file: lib/Bio/Graphics/Glyph/box.pm version: 0 Bio::Graphics::Glyph::broken_line: file: lib/Bio/Graphics/Glyph/broken_line.pm version: 0 Bio::Graphics::Glyph::cds: file: lib/Bio/Graphics/Glyph/cds.pm version: 0 Bio::Graphics::Glyph::christmas_arrow: file: lib/Bio/Graphics/Glyph/christmas_arrow.pm version: 0 Bio::Graphics::Glyph::cross: file: lib/Bio/Graphics/Glyph/cross.pm version: 0 Bio::Graphics::Glyph::crossbox: file: lib/Bio/Graphics/Glyph/crossbox.pm version: 0 Bio::Graphics::Glyph::dashed_line: file: lib/Bio/Graphics/Glyph/dashed_line.pm version: 0 Bio::Graphics::Glyph::decorated_gene: file: lib/Bio/Graphics/Glyph/decorated_gene.pm version: 0 Bio::Graphics::Glyph::decorated_transcript: file: lib/Bio/Graphics/Glyph/decorated_transcript.pm version: 0 Bio::Graphics::Glyph::diamond: file: lib/Bio/Graphics/Glyph/diamond.pm version: 0 Bio::Graphics::Glyph::dna: file: lib/Bio/Graphics/Glyph/dna.pm version: 0 Bio::Graphics::Glyph::dot: file: lib/Bio/Graphics/Glyph/dot.pm version: 0 Bio::Graphics::Glyph::dumbbell: file: lib/Bio/Graphics/Glyph/dumbbell.pm version: 0 Bio::Graphics::Glyph::ellipse: file: lib/Bio/Graphics/Glyph/ellipse.pm version: 0 Bio::Graphics::Glyph::ex: file: lib/Bio/Graphics/Glyph/ex.pm version: 0 Bio::Graphics::Glyph::extending_arrow: file: lib/Bio/Graphics/Glyph/extending_arrow.pm version: 0 Bio::Graphics::Glyph::fb_shmiggle: file: lib/Bio/Graphics/Glyph/fb_shmiggle.pm version: 0 Bio::Graphics::Glyph::fixedwidth: file: lib/Bio/Graphics/Glyph/fixedwidth.pm version: 0 Bio::Graphics::Glyph::flag: file: lib/Bio/Graphics/Glyph/flag.pm version: 0 Bio::Graphics::Glyph::gene: file: lib/Bio/Graphics/Glyph/gene.pm version: 0 Bio::Graphics::Glyph::generic: file: lib/Bio/Graphics/Glyph/generic.pm version: 0 Bio::Graphics::Glyph::graded_segments: file: lib/Bio/Graphics/Glyph/graded_segments.pm version: 0 Bio::Graphics::Glyph::group: file: lib/Bio/Graphics/Glyph/group.pm version: 0 Bio::Graphics::Glyph::hat: file: lib/Bio/Graphics/Glyph/hat.pm version: 0 Bio::Graphics::Glyph::heat_map: file: lib/Bio/Graphics/Glyph/heat_map.pm version: 0 Bio::Graphics::Glyph::heat_map_ideogram: file: lib/Bio/Graphics/Glyph/heat_map_ideogram.pm version: 0 Bio::Graphics::Glyph::heterogeneous_segments: file: lib/Bio/Graphics/Glyph/heterogeneous_segments.pm version: 0 Bio::Graphics::Glyph::hidden: file: lib/Bio/Graphics/Glyph/hidden.pm version: 0 Bio::Graphics::Glyph::hybrid_plot: file: lib/Bio/Graphics/Glyph/hybrid_plot.pm version: 1.0 Bio::Graphics::Glyph::ideogram: file: lib/Bio/Graphics/Glyph/ideogram.pm version: 0 Bio::Graphics::Glyph::image: file: lib/Bio/Graphics/Glyph/image.pm version: 0 Bio::Graphics::Glyph::lightning: file: lib/Bio/Graphics/Glyph/lightning.pm version: 0 Bio::Graphics::Glyph::line: file: lib/Bio/Graphics/Glyph/line.pm version: 0 Bio::Graphics::Glyph::merge_parts: file: lib/Bio/Graphics/Glyph/merge_parts.pm version: 0 Bio::Graphics::Glyph::merged_alignment: file: lib/Bio/Graphics/Glyph/merged_alignment.pm version: 0 Bio::Graphics::Glyph::minmax: file: lib/Bio/Graphics/Glyph/minmax.pm version: 0 Bio::Graphics::Glyph::operon: file: lib/Bio/Graphics/Glyph/operon.pm version: 0 Bio::Graphics::Glyph::oval: file: lib/Bio/Graphics/Glyph/oval.pm version: 0 Bio::Graphics::Glyph::pairplot: file: lib/Bio/Graphics/Glyph/pairplot.pm version: 0 Bio::Graphics::Glyph::pentagram: file: lib/Bio/Graphics/Glyph/pentagram.pm version: 0 Bio::Graphics::Glyph::phylo_align: file: lib/Bio/Graphics/Glyph/phylo_align.pm version: 0 Bio::Graphics::Glyph::pinsertion: file: lib/Bio/Graphics/Glyph/pinsertion.pm version: 0 Bio::Graphics::Glyph::point_glyph: file: lib/Bio/Graphics/Glyph/point_glyph.pm version: 0 Bio::Graphics::Glyph::primers: file: lib/Bio/Graphics/Glyph/primers.pm version: 0 Bio::Graphics::Glyph::processed_transcript: file: lib/Bio/Graphics/Glyph/processed_transcript.pm version: 0 Bio::Graphics::Glyph::protein: file: lib/Bio/Graphics/Glyph/protein.pm version: 0 Bio::Graphics::Glyph::ragged_ends: file: lib/Bio/Graphics/Glyph/ragged_ends.pm version: 0 Bio::Graphics::Glyph::rainbow_gene: file: lib/Bio/Graphics/Glyph/rainbow_gene.pm version: 0 Bio::Graphics::Glyph::read_pair: file: lib/Bio/Graphics/Glyph/read_pair.pm version: 0 Bio::Graphics::Glyph::redgreen_box: file: lib/Bio/Graphics/Glyph/redgreen_box.pm version: 0 Bio::Graphics::Glyph::redgreen_segment: file: lib/Bio/Graphics/Glyph/redgreen_segment.pm version: 0 Bio::Graphics::Glyph::repeating_shape: file: lib/Bio/Graphics/Glyph/repeating_shape.pm version: 0 Bio::Graphics::Glyph::rndrect: file: lib/Bio/Graphics/Glyph/rndrect.pm version: 0 Bio::Graphics::Glyph::ruler_arrow: file: lib/Bio/Graphics/Glyph/ruler_arrow.pm version: 0 Bio::Graphics::Glyph::saw_teeth: file: lib/Bio/Graphics/Glyph/saw_teeth.pm version: 0 Bio::Graphics::Glyph::scale: file: lib/Bio/Graphics/Glyph/scale.pm version: 0 Bio::Graphics::Glyph::segmented_keyglyph: file: lib/Bio/Graphics/Glyph/segmented_keyglyph.pm version: 0 Bio::Graphics::Glyph::segments: file: lib/Bio/Graphics/Glyph/segments.pm version: 0 Bio::Graphics::Glyph::smoothing: file: lib/Bio/Graphics/Glyph/smoothing.pm version: 0 Bio::Graphics::Glyph::so_transcript: file: lib/Bio/Graphics/Glyph/so_transcript.pm version: 0 Bio::Graphics::Glyph::span: file: lib/Bio/Graphics/Glyph/span.pm version: 0 Bio::Graphics::Glyph::spectrogram: file: lib/Bio/Graphics/Glyph/spectrogram.pm version: 0 Bio::Graphics::Glyph::splice_site: file: lib/Bio/Graphics/Glyph/splice_site.pm version: 0 Bio::Graphics::Glyph::stackedplot: file: lib/Bio/Graphics/Glyph/stackedplot.pm version: 0 Bio::Graphics::Glyph::ternary_plot: file: lib/Bio/Graphics/Glyph/ternary_plot.pm version: 0 Bio::Graphics::Glyph::text_in_box: file: lib/Bio/Graphics/Glyph/text_in_box.pm version: 0 Bio::Graphics::Glyph::three_letters: file: lib/Bio/Graphics/Glyph/three_letters.pm version: 0 Bio::Graphics::Glyph::tic_tac_toe: file: lib/Bio/Graphics/Glyph/tic_tac_toe.pm version: 0 Bio::Graphics::Glyph::toomany: file: lib/Bio/Graphics/Glyph/toomany.pm version: 0 Bio::Graphics::Glyph::trace: file: lib/Bio/Graphics/Glyph/trace.pm version: 0 Bio::Graphics::Glyph::track: file: lib/Bio/Graphics/Glyph/track.pm version: 0 Bio::Graphics::Glyph::transcript: file: lib/Bio/Graphics/Glyph/transcript.pm version: 0 Bio::Graphics::Glyph::transcript2: file: lib/Bio/Graphics/Glyph/transcript2.pm version: 0 Bio::Graphics::Glyph::translation: file: lib/Bio/Graphics/Glyph/translation.pm version: 0 Bio::Graphics::Glyph::triangle: file: lib/Bio/Graphics/Glyph/triangle.pm version: 0 Bio::Graphics::Glyph::two_bolts: file: lib/Bio/Graphics/Glyph/two_bolts.pm version: 0 Bio::Graphics::Glyph::vista_plot: file: lib/Bio/Graphics/Glyph/vista_plot.pm version: 1.0 Bio::Graphics::Glyph::wave: file: lib/Bio/Graphics/Glyph/wave.pm version: 0 Bio::Graphics::Glyph::weighted_arrow: file: lib/Bio/Graphics/Glyph/weighted_arrow.pm version: 0 Bio::Graphics::Glyph::whiskerplot: file: lib/Bio/Graphics/Glyph/whiskerplot.pm version: 0 Bio::Graphics::Glyph::wiggle_box: file: lib/Bio/Graphics/Glyph/wiggle_box.pm version: 0 Bio::Graphics::Glyph::wiggle_data: file: lib/Bio/Graphics/Glyph/wiggle_data.pm version: 0 Bio::Graphics::Glyph::wiggle_density: file: lib/Bio/Graphics/Glyph/wiggle_density.pm version: 0 Bio::Graphics::Glyph::wiggle_whiskers: file: lib/Bio/Graphics/Glyph/wiggle_whiskers.pm version: 0 Bio::Graphics::Glyph::wiggle_xyplot: file: lib/Bio/Graphics/Glyph/wiggle_xyplot.pm version: 0 Bio::Graphics::Glyph::xyplot: file: lib/Bio/Graphics/Glyph/xyplot.pm version: 0 Bio::Graphics::Layout: file: lib/Bio/Graphics/Layout.pm version: 0 Bio::Graphics::Layout::Contour: file: lib/Bio/Graphics/Layout.pm version: 0 Bio::Graphics::Math: file: lib/Bio/Graphics/Layout.pm version: 0 Bio::Graphics::Panel: file: lib/Bio/Graphics/Panel.pm version: 0 Bio::Graphics::Pictogram: file: lib/Bio/Graphics/Pictogram.pm version: 0 Bio::Graphics::RendererI: file: lib/Bio/Graphics/RendererI.pm version: 0 Bio::Graphics::Util: file: lib/Bio/Graphics/Util.pm version: 0 Bio::Graphics::Wiggle: file: lib/Bio/Graphics/Wiggle.pm version: 1.0 Bio::Graphics::Wiggle::Loader: file: lib/Bio/Graphics/Wiggle/Loader.pm version: 0 recommends: GD::SVG: 0.32 Text::ParseWords: 3.26 requires: Bio::Root::Version: 1.005009001 GD: 2.3 Statistics::Descriptive: 2.6 resources: license: http://dev.perl.org/licenses/ version: 2.37 Bio-Graphics-2.37/MANIFEST.SKIP000444001750001750 17612165075746 15727 0ustar00lsteinlstein000000000000^MYMETA.yml$ ^MYMETA\.json$ ^_build \.tar\.gz$ ^blib/ ^\. Build$ ^[^/]+\.pl$ ^[^/]+\.t$ ^[^/]+\.gff3 MANIFEST\.bak ~$ \.patch Bio-Graphics-2.37/README000555001750001750 1144412165075746 14754 0ustar00lsteinlstein000000000000Bio::Graphics - Generate GD images of Bio::Seq objects SYNOPSIS This is a simple GD-based renderer (diagram drawer) for DNA and protein sequences. It is used from Perl like this: use Bio::Graphics; use Bio::DB::BioFetch; # or some other Bio::SeqI generator # get a Bio::SeqI object somehow my $bf = Bio::DB::BioFetch->new; my $cosmid = $bf->get_Seq_by_id('CEF58D5'); my @features = $cosmid->all_SeqFeatures; my @CDS = grep {$_->primary_tag eq 'CDS'} @features; my @gene = grep {$_->primary_tag eq 'gene'} @features; my @tRNAs = grep {$_->primary_tag eq 'tRNA'} @features; # let the drawing begin... my $panel = Bio::Graphics::Panel->new( -segment => $cosmid, -width => 800 ); $panel->add_track(arrow => $cosmid, -bump => 0, -double=>1, -tick => 2); $panel->add_track(transcript => \@gene, -bgcolor => 'blue', -fgcolor => 'black', -key => 'Genes', -bump => +1, -height => 10, -label => 1, -description=> 1 ) ; $panel->add_track(transcript2 => \@CDS, -bgcolor => 'cyan', -fgcolor => 'black', -key => 'CDS', -bump => +1, -height => 10, -label => 1, -description=> 1, ); $panel->add_track(generic => \@tRNAs, -bgcolor => 'red', -fgcolor => 'black', -key => 'tRNAs', -bump => +1, -height => 8, -label => 1, ); my $gd = $panel->gd; print $gd->can('png') ? $gd->png : $gd->gif; DESCRIPTION The Bio::Graphics::Panel class provides drawing and formatting services for any object that implements the Bio::SeqFeatureI interface, including Ace::Sequence::Feature, Das::Segment::Feature and Bio::DB::Graphics objects. It can be used to draw sequence annotations, physical (contig) maps, protein domains, or any other type of map in which a set of discrete ranges need to be laid out on the number line. The module supports a drawing style in which each type of feature occupies a discrete "track" that spans the width of the display. Each track will have its own distinctive "glyph", a configurable graphical representation of the feature. The module also supports a more flexible style in which several different feature types and their associated glyphs can occupy the same track. The choice of glyph is under run-time control. Semantic zooming (for instance, changing the type of glyph depending on the density of features) is supported by a callback system for configuration variables. The module has built-in support for Bio::Das stylesheets, and stylesheet-driven configuration can be intermixed with semantic zooming, if desired. You can add a key to the generated image using either of two key styles. One style places the key captions at the top of each track. The other style generates a graphical key at the bottom of the image. Note that this modules depends on Bio::Perl, GD and the Text::Parsewords module. A good tutorial on this module can be found at http://www.bioperl.org/wiki/HOWTO:Graphics. BUILDING AND INSTALLING From the directory in which this README file is located: % perl ./Build.PL % ./Build test % ./Build install The last step may need to be run as root. You can test whether the module is working by running the testit.pl script located in the eg subdirectory. It produces a PNG file, so you should pipe the output to an image displayer, such as xv: eg/testit.pl | xv - THE FEATURE_DRAW.PL SCRIPT This module contains a simple script named "feature_draw.pl" that takes a file of annotations and generates an image. When first built, the script lives in the "scripts" subdirectory. After installation, the script will be moved into your system-wide scripts directory (usually /usr/bin or something similar). There is a sample features file in the "eg" subdirectory. After building, but before installation, you can run the following command from the directory in which this README file is located: % scripts/feature_draw.pl eg/feature_data.txt | display - This is piping the output directly to the "display" program, part of the ImageMagick package. Replace with your favorite PNG-capable display program. After installation, feature_draw.pl will be available on your command path. Run "man feature_draw.pl" to see the manual page, or use the -h option to get a quick summary of usage. THE GENERIC GENOME BROWSER The gbrowse script, part of the Generic Genome Browser package, has been moved into its own package. It creates a high-performance web-based genome browser based on the Bio::Graphics package. You can get it by anonymous CVS to SourceForge: cvs -d:pserver:anonymous@cvs.gmod.sourceforge.net:/cvsroot/gmod \ co Generic-Genome-Browser Lincoln Stein 12 December 2008 Bio-Graphics-2.37/Makefile.PL000444001750001750 226312165075746 16022 0ustar00lsteinlstein000000000000# Note: this file was auto-generated by Module::Build::Compat version 0.3800 unless (eval "use Module::Build::Compat 0.02; 1" ) { print "This module requires Module::Build to install itself.\n"; require ExtUtils::MakeMaker; my $yn = ExtUtils::MakeMaker::prompt (' Install Module::Build now from CPAN?', 'y'); unless ($yn =~ /^y/i) { die " *** Cannot install without Module::Build. Exiting ...\n"; } require Cwd; require File::Spec; require CPAN; # Save this 'cause CPAN will chdir all over the place. my $cwd = Cwd::cwd(); CPAN::Shell->install('Module::Build::Compat'); CPAN::Shell->expand("Module", "Module::Build::Compat")->uptodate or die "Couldn't install Module::Build, giving up.\n"; chdir $cwd or die "Cannot chdir() back to $cwd: $!"; } eval "use Module::Build::Compat 0.02; 1" or die $@; Module::Build::Compat->run_build_pl(args => \@ARGV); my $build_script = 'Build'; $build_script .= '.com' if $^O eq 'VMS'; exit(0) unless(-e $build_script); # cpantesters convention require Module::Build; Module::Build::Compat->write_makefile(build_class => 'Module::Build'); Bio-Graphics-2.37/META.json000444001750001750 3643212165075746 15516 0ustar00lsteinlstein000000000000{ "abstract" : "Generate GD images of Bio::Seq objects", "author" : [ "Lincoln Stein " ], "dynamic_config" : 1, "generated_by" : "Module::Build version 0.38, CPAN::Meta::Converter version 2.110440", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : "2" }, "name" : "Bio-Graphics", "prereqs" : { "configure" : { "requires" : { "Module::Build" : "0.38" } }, "runtime" : { "recommends" : { "GD::SVG" : "0.32", "Text::ParseWords" : "3.26" }, "requires" : { "Bio::Root::Version" : "1.005009001", "GD" : "2.3", "Statistics::Descriptive" : "2.6" } } }, "provides" : { "Bio::Graphics" : { "file" : "lib/Bio/Graphics.pm", "version" : "2.37" }, "Bio::Graphics::ConfiguratorI" : { "file" : "lib/Bio/Graphics/ConfiguratorI.pm", "version" : 0 }, "Bio::Graphics::DrawTransmembrane" : { "file" : "lib/Bio/Graphics/DrawTransmembrane.pm", "version" : 0 }, "Bio::Graphics::Feature" : { "file" : "lib/Bio/Graphics/Feature.pm", "version" : 0 }, "Bio::Graphics::FeatureBase" : { "file" : "lib/Bio/Graphics/FeatureBase.pm", "version" : 0 }, "Bio::Graphics::FeatureDir" : { "file" : "lib/Bio/Graphics/FeatureDir.pm", "version" : 0 }, "Bio::Graphics::FeatureFile" : { "file" : "lib/Bio/Graphics/FeatureFile.pm", "version" : 0 }, "Bio::Graphics::FeatureFile::Iterator" : { "file" : "lib/Bio/Graphics/FeatureFile/Iterator.pm", "version" : 0 }, "Bio::Graphics::FileSplitter" : { "file" : "lib/Bio/Graphics/FeatureDir.pm", "version" : 0 }, "Bio::Graphics::GDWrapper" : { "file" : "lib/Bio/Graphics/GDWrapper.pm", "version" : 0 }, "Bio::Graphics::Glyph" : { "file" : "lib/Bio/Graphics/Glyph.pm", "version" : 0 }, "Bio::Graphics::Glyph::Factory" : { "file" : "lib/Bio/Graphics/Glyph/Factory.pm", "version" : 0 }, "Bio::Graphics::Glyph::alignment" : { "file" : "lib/Bio/Graphics/Glyph/alignment.pm", "version" : 0 }, "Bio::Graphics::Glyph::allele_tower" : { "file" : "lib/Bio/Graphics/Glyph/allele_tower.pm", "version" : 0 }, "Bio::Graphics::Glyph::anchored_arrow" : { "file" : "lib/Bio/Graphics/Glyph/anchored_arrow.pm", "version" : 0 }, "Bio::Graphics::Glyph::arrow" : { "file" : "lib/Bio/Graphics/Glyph/arrow.pm", "version" : 0 }, "Bio::Graphics::Glyph::box" : { "file" : "lib/Bio/Graphics/Glyph/box.pm", "version" : 0 }, "Bio::Graphics::Glyph::broken_line" : { "file" : "lib/Bio/Graphics/Glyph/broken_line.pm", "version" : 0 }, "Bio::Graphics::Glyph::cds" : { "file" : "lib/Bio/Graphics/Glyph/cds.pm", "version" : 0 }, "Bio::Graphics::Glyph::christmas_arrow" : { "file" : "lib/Bio/Graphics/Glyph/christmas_arrow.pm", "version" : 0 }, "Bio::Graphics::Glyph::cross" : { "file" : "lib/Bio/Graphics/Glyph/cross.pm", "version" : 0 }, "Bio::Graphics::Glyph::crossbox" : { "file" : "lib/Bio/Graphics/Glyph/crossbox.pm", "version" : 0 }, "Bio::Graphics::Glyph::dashed_line" : { "file" : "lib/Bio/Graphics/Glyph/dashed_line.pm", "version" : 0 }, "Bio::Graphics::Glyph::decorated_gene" : { "file" : "lib/Bio/Graphics/Glyph/decorated_gene.pm", "version" : 0 }, "Bio::Graphics::Glyph::decorated_transcript" : { "file" : "lib/Bio/Graphics/Glyph/decorated_transcript.pm", "version" : 0 }, "Bio::Graphics::Glyph::diamond" : { "file" : "lib/Bio/Graphics/Glyph/diamond.pm", "version" : 0 }, "Bio::Graphics::Glyph::dna" : { "file" : "lib/Bio/Graphics/Glyph/dna.pm", "version" : 0 }, "Bio::Graphics::Glyph::dot" : { "file" : "lib/Bio/Graphics/Glyph/dot.pm", "version" : 0 }, "Bio::Graphics::Glyph::dumbbell" : { "file" : "lib/Bio/Graphics/Glyph/dumbbell.pm", "version" : 0 }, "Bio::Graphics::Glyph::ellipse" : { "file" : "lib/Bio/Graphics/Glyph/ellipse.pm", "version" : 0 }, "Bio::Graphics::Glyph::ex" : { "file" : "lib/Bio/Graphics/Glyph/ex.pm", "version" : 0 }, "Bio::Graphics::Glyph::extending_arrow" : { "file" : "lib/Bio/Graphics/Glyph/extending_arrow.pm", "version" : 0 }, "Bio::Graphics::Glyph::fb_shmiggle" : { "file" : "lib/Bio/Graphics/Glyph/fb_shmiggle.pm", "version" : 0 }, "Bio::Graphics::Glyph::fixedwidth" : { "file" : "lib/Bio/Graphics/Glyph/fixedwidth.pm", "version" : 0 }, "Bio::Graphics::Glyph::flag" : { "file" : "lib/Bio/Graphics/Glyph/flag.pm", "version" : 0 }, "Bio::Graphics::Glyph::gene" : { "file" : "lib/Bio/Graphics/Glyph/gene.pm", "version" : 0 }, "Bio::Graphics::Glyph::generic" : { "file" : "lib/Bio/Graphics/Glyph/generic.pm", "version" : 0 }, "Bio::Graphics::Glyph::graded_segments" : { "file" : "lib/Bio/Graphics/Glyph/graded_segments.pm", "version" : 0 }, "Bio::Graphics::Glyph::group" : { "file" : "lib/Bio/Graphics/Glyph/group.pm", "version" : 0 }, "Bio::Graphics::Glyph::hat" : { "file" : "lib/Bio/Graphics/Glyph/hat.pm", "version" : 0 }, "Bio::Graphics::Glyph::heat_map" : { "file" : "lib/Bio/Graphics/Glyph/heat_map.pm", "version" : 0 }, "Bio::Graphics::Glyph::heat_map_ideogram" : { "file" : "lib/Bio/Graphics/Glyph/heat_map_ideogram.pm", "version" : 0 }, "Bio::Graphics::Glyph::heterogeneous_segments" : { "file" : "lib/Bio/Graphics/Glyph/heterogeneous_segments.pm", "version" : 0 }, "Bio::Graphics::Glyph::hidden" : { "file" : "lib/Bio/Graphics/Glyph/hidden.pm", "version" : 0 }, "Bio::Graphics::Glyph::hybrid_plot" : { "file" : "lib/Bio/Graphics/Glyph/hybrid_plot.pm", "version" : "1.0" }, "Bio::Graphics::Glyph::ideogram" : { "file" : "lib/Bio/Graphics/Glyph/ideogram.pm", "version" : 0 }, "Bio::Graphics::Glyph::image" : { "file" : "lib/Bio/Graphics/Glyph/image.pm", "version" : 0 }, "Bio::Graphics::Glyph::lightning" : { "file" : "lib/Bio/Graphics/Glyph/lightning.pm", "version" : 0 }, "Bio::Graphics::Glyph::line" : { "file" : "lib/Bio/Graphics/Glyph/line.pm", "version" : 0 }, "Bio::Graphics::Glyph::merge_parts" : { "file" : "lib/Bio/Graphics/Glyph/merge_parts.pm", "version" : 0 }, "Bio::Graphics::Glyph::merged_alignment" : { "file" : "lib/Bio/Graphics/Glyph/merged_alignment.pm", "version" : 0 }, "Bio::Graphics::Glyph::minmax" : { "file" : "lib/Bio/Graphics/Glyph/minmax.pm", "version" : 0 }, "Bio::Graphics::Glyph::operon" : { "file" : "lib/Bio/Graphics/Glyph/operon.pm", "version" : 0 }, "Bio::Graphics::Glyph::oval" : { "file" : "lib/Bio/Graphics/Glyph/oval.pm", "version" : 0 }, "Bio::Graphics::Glyph::pairplot" : { "file" : "lib/Bio/Graphics/Glyph/pairplot.pm", "version" : 0 }, "Bio::Graphics::Glyph::pentagram" : { "file" : "lib/Bio/Graphics/Glyph/pentagram.pm", "version" : 0 }, "Bio::Graphics::Glyph::phylo_align" : { "file" : "lib/Bio/Graphics/Glyph/phylo_align.pm", "version" : 0 }, "Bio::Graphics::Glyph::pinsertion" : { "file" : "lib/Bio/Graphics/Glyph/pinsertion.pm", "version" : 0 }, "Bio::Graphics::Glyph::point_glyph" : { "file" : "lib/Bio/Graphics/Glyph/point_glyph.pm", "version" : 0 }, "Bio::Graphics::Glyph::primers" : { "file" : "lib/Bio/Graphics/Glyph/primers.pm", "version" : 0 }, "Bio::Graphics::Glyph::processed_transcript" : { "file" : "lib/Bio/Graphics/Glyph/processed_transcript.pm", "version" : 0 }, "Bio::Graphics::Glyph::protein" : { "file" : "lib/Bio/Graphics/Glyph/protein.pm", "version" : 0 }, "Bio::Graphics::Glyph::ragged_ends" : { "file" : "lib/Bio/Graphics/Glyph/ragged_ends.pm", "version" : 0 }, "Bio::Graphics::Glyph::rainbow_gene" : { "file" : "lib/Bio/Graphics/Glyph/rainbow_gene.pm", "version" : 0 }, "Bio::Graphics::Glyph::read_pair" : { "file" : "lib/Bio/Graphics/Glyph/read_pair.pm", "version" : 0 }, "Bio::Graphics::Glyph::redgreen_box" : { "file" : "lib/Bio/Graphics/Glyph/redgreen_box.pm", "version" : 0 }, "Bio::Graphics::Glyph::redgreen_segment" : { "file" : "lib/Bio/Graphics/Glyph/redgreen_segment.pm", "version" : 0 }, "Bio::Graphics::Glyph::repeating_shape" : { "file" : "lib/Bio/Graphics/Glyph/repeating_shape.pm", "version" : 0 }, "Bio::Graphics::Glyph::rndrect" : { "file" : "lib/Bio/Graphics/Glyph/rndrect.pm", "version" : 0 }, "Bio::Graphics::Glyph::ruler_arrow" : { "file" : "lib/Bio/Graphics/Glyph/ruler_arrow.pm", "version" : 0 }, "Bio::Graphics::Glyph::saw_teeth" : { "file" : "lib/Bio/Graphics/Glyph/saw_teeth.pm", "version" : 0 }, "Bio::Graphics::Glyph::scale" : { "file" : "lib/Bio/Graphics/Glyph/scale.pm", "version" : 0 }, "Bio::Graphics::Glyph::segmented_keyglyph" : { "file" : "lib/Bio/Graphics/Glyph/segmented_keyglyph.pm", "version" : 0 }, "Bio::Graphics::Glyph::segments" : { "file" : "lib/Bio/Graphics/Glyph/segments.pm", "version" : 0 }, "Bio::Graphics::Glyph::smoothing" : { "file" : "lib/Bio/Graphics/Glyph/smoothing.pm", "version" : 0 }, "Bio::Graphics::Glyph::so_transcript" : { "file" : "lib/Bio/Graphics/Glyph/so_transcript.pm", "version" : 0 }, "Bio::Graphics::Glyph::span" : { "file" : "lib/Bio/Graphics/Glyph/span.pm", "version" : 0 }, "Bio::Graphics::Glyph::spectrogram" : { "file" : "lib/Bio/Graphics/Glyph/spectrogram.pm", "version" : 0 }, "Bio::Graphics::Glyph::splice_site" : { "file" : "lib/Bio/Graphics/Glyph/splice_site.pm", "version" : 0 }, "Bio::Graphics::Glyph::stackedplot" : { "file" : "lib/Bio/Graphics/Glyph/stackedplot.pm", "version" : 0 }, "Bio::Graphics::Glyph::ternary_plot" : { "file" : "lib/Bio/Graphics/Glyph/ternary_plot.pm", "version" : 0 }, "Bio::Graphics::Glyph::text_in_box" : { "file" : "lib/Bio/Graphics/Glyph/text_in_box.pm", "version" : 0 }, "Bio::Graphics::Glyph::three_letters" : { "file" : "lib/Bio/Graphics/Glyph/three_letters.pm", "version" : 0 }, "Bio::Graphics::Glyph::tic_tac_toe" : { "file" : "lib/Bio/Graphics/Glyph/tic_tac_toe.pm", "version" : 0 }, "Bio::Graphics::Glyph::toomany" : { "file" : "lib/Bio/Graphics/Glyph/toomany.pm", "version" : 0 }, "Bio::Graphics::Glyph::trace" : { "file" : "lib/Bio/Graphics/Glyph/trace.pm", "version" : 0 }, "Bio::Graphics::Glyph::track" : { "file" : "lib/Bio/Graphics/Glyph/track.pm", "version" : 0 }, "Bio::Graphics::Glyph::transcript" : { "file" : "lib/Bio/Graphics/Glyph/transcript.pm", "version" : 0 }, "Bio::Graphics::Glyph::transcript2" : { "file" : "lib/Bio/Graphics/Glyph/transcript2.pm", "version" : 0 }, "Bio::Graphics::Glyph::translation" : { "file" : "lib/Bio/Graphics/Glyph/translation.pm", "version" : 0 }, "Bio::Graphics::Glyph::triangle" : { "file" : "lib/Bio/Graphics/Glyph/triangle.pm", "version" : 0 }, "Bio::Graphics::Glyph::two_bolts" : { "file" : "lib/Bio/Graphics/Glyph/two_bolts.pm", "version" : 0 }, "Bio::Graphics::Glyph::vista_plot" : { "file" : "lib/Bio/Graphics/Glyph/vista_plot.pm", "version" : "1.0" }, "Bio::Graphics::Glyph::wave" : { "file" : "lib/Bio/Graphics/Glyph/wave.pm", "version" : 0 }, "Bio::Graphics::Glyph::weighted_arrow" : { "file" : "lib/Bio/Graphics/Glyph/weighted_arrow.pm", "version" : 0 }, "Bio::Graphics::Glyph::whiskerplot" : { "file" : "lib/Bio/Graphics/Glyph/whiskerplot.pm", "version" : 0 }, "Bio::Graphics::Glyph::wiggle_box" : { "file" : "lib/Bio/Graphics/Glyph/wiggle_box.pm", "version" : 0 }, "Bio::Graphics::Glyph::wiggle_data" : { "file" : "lib/Bio/Graphics/Glyph/wiggle_data.pm", "version" : 0 }, "Bio::Graphics::Glyph::wiggle_density" : { "file" : "lib/Bio/Graphics/Glyph/wiggle_density.pm", "version" : 0 }, "Bio::Graphics::Glyph::wiggle_whiskers" : { "file" : "lib/Bio/Graphics/Glyph/wiggle_whiskers.pm", "version" : 0 }, "Bio::Graphics::Glyph::wiggle_xyplot" : { "file" : "lib/Bio/Graphics/Glyph/wiggle_xyplot.pm", "version" : 0 }, "Bio::Graphics::Glyph::xyplot" : { "file" : "lib/Bio/Graphics/Glyph/xyplot.pm", "version" : 0 }, "Bio::Graphics::Layout" : { "file" : "lib/Bio/Graphics/Layout.pm", "version" : 0 }, "Bio::Graphics::Layout::Contour" : { "file" : "lib/Bio/Graphics/Layout.pm", "version" : 0 }, "Bio::Graphics::Math" : { "file" : "lib/Bio/Graphics/Layout.pm", "version" : 0 }, "Bio::Graphics::Panel" : { "file" : "lib/Bio/Graphics/Panel.pm", "version" : 0 }, "Bio::Graphics::Pictogram" : { "file" : "lib/Bio/Graphics/Pictogram.pm", "version" : 0 }, "Bio::Graphics::RendererI" : { "file" : "lib/Bio/Graphics/RendererI.pm", "version" : 0 }, "Bio::Graphics::Util" : { "file" : "lib/Bio/Graphics/Util.pm", "version" : 0 }, "Bio::Graphics::Wiggle" : { "file" : "lib/Bio/Graphics/Wiggle.pm", "version" : "1.0" }, "Bio::Graphics::Wiggle::Loader" : { "file" : "lib/Bio/Graphics/Wiggle/Loader.pm", "version" : 0 } }, "release_status" : "stable", "resources" : { "license" : [ "http://dev.perl.org/licenses/" ] }, "version" : "2.37" } Bio-Graphics-2.37/lib000755001750001750 012165075746 14456 5ustar00lsteinlstein000000000000Bio-Graphics-2.37/lib/Bio000755001750001750 012165075746 15167 5ustar00lsteinlstein000000000000Bio-Graphics-2.37/lib/Bio/Graphics.pm000555001750001750 645012165075746 17432 0ustar00lsteinlstein000000000000package Bio::Graphics; use strict; use Bio::Graphics::Panel; our $VERSION = '2.37'; 1; =head1 NAME Bio::Graphics - Generate GD images of Bio::Seq objects =head1 SYNOPSIS # This script generates a PNG picture of a 10K region containing a # set of red features and a set of blue features. Call it like this: # red_and_blue.pl > redblue.png # you can now view the picture with your favorite image application # This script parses a GenBank or EMBL file named on the command # line and produces a PNG rendering of it. Call it like this: # render.pl my_file.embl | display - use strict; use Bio::Graphics; use Bio::SeqFeature::Generic; use Bio::SeqIO; my $file = shift or die "provide a sequence file as the argument"; my $io = Bio::SeqIO->new(-file=>$file) or die "could not create Bio::SeqIO"; my $seq = $io->next_seq or die "could not find a sequence in the file"; my @features = $seq->all_SeqFeatures; # sort features by their primary tags my %sorted_features; for my $f (@features) { my $tag = $f->primary_tag; push @{$sorted_features{$tag}},$f; } my $wholeseq = Bio::SeqFeature::Generic->new(-start=>1,-end=>$seq->length); my $panel = Bio::Graphics::Panel->new( -length => $seq->length, -key_style => 'between', -width => 800, -pad_left => 10, -pad_right => 10, ); $panel->add_track($wholeseq, -glyph => 'arrow', -bump => 0, -double=>1, -tick => 2); $panel->add_track($wholeseq, -glyph => 'generic', -bgcolor => 'blue', -label => 1, ); # general case my @colors = qw(cyan orange blue purple green chartreuse magenta yellow aqua); my $idx = 0; for my $tag (sort keys %sorted_features) { my $features = $sorted_features{$tag}; $panel->add_track($features, -glyph => 'generic', -bgcolor => $colors[$idx++ % @colors], -fgcolor => 'black', -font2color => 'red', -key => "${tag}s", -bump => +1, -height => 8, -label => 1, -description => 1, ); } print $panel->png; exit 0; =head1 DESCRIPTION Please see L for the full interface. Also try the script glyph_help.pl for quick help on glyphs and their options. =head1 SEE ALSO L, L, L, L, L, L, L, L L =head1 FEEDBACK =head2 Mailing Lists User feedback is an integral part of the evolution of this and other Bioperl modules. Send your comments and suggestions preferably to the Bioperl mailing list. Your participation is much appreciated. bioperl-l@bioperl.org - General discussion http://bioperl.org/wiki/Mailing_lists - About the mailing lists =head2 Reporting Bugs Report bugs to the Bioperl bug tracking system to help us keep track of the bugs and their resolution. Bug reports can be submitted via the web: http://bugzilla.open-bio.org/ =head1 AUTHOR Lincoln Stein Elstein@cshl.orgE. Copyright (c) 2001 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics000755001750001750 012165075746 16727 5ustar00lsteinlstein000000000000Bio-Graphics-2.37/lib/Bio/Graphics/Panel.pm000555001750001750 32050512165075746 20531 0ustar00lsteinlstein000000000000package Bio::Graphics::Panel; use strict; use Bio::Graphics::Glyph::Factory; use Bio::Graphics::Feature; use Bio::Graphics::GDWrapper; # KEYLABELFONT must be treated as string until image_class is established use constant KEYLABELFONT => 'gdMediumBoldFont'; use constant KEYSPACING => 5; # extra space between key columns use constant KEYPADTOP => 5; # extra padding before the key starts use constant KEYCOLOR => 'wheat'; use constant KEYSTYLE => 'bottom'; use constant KEYALIGN => 'left'; use constant GRIDCOLOR => 'lightcyan'; use constant GRIDMAJORCOLOR => 'lightgrey'; use constant MISSING_TRACK_COLOR =>'gray'; use constant EXTRA_RIGHT_PADDING => 30; use base qw(Bio::Root::Root); our $GlyphScratch; my %COLORS; # translation table for symbolic color names to RGB triple my $IMAGEMAP = 'bgmap00001'; read_colors(); sub api_version { 1.8 } # Create a new panel of a given width and height, and add lists of features # one by one sub new { my $class = shift; $class = ref($class) || $class; my %options = @_; $class->read_colors() unless %COLORS; my $length = $options{-length} || 0; my $offset = $options{-offset} || 0; my $spacing = $options{-spacing} || 5; my $bgcolor = $options{-bgcolor} || 'white'; my $keyfont = $options{-key_font} || KEYLABELFONT; my $keycolor = $options{-key_color} || KEYCOLOR; my $keyspacing = $options{-key_spacing} || KEYSPACING; my $keystyle = $options{-key_style} || KEYSTYLE; my $keyalign = $options{-key_align} || KEYALIGN; my $suppress_key = $options{-suppress_key} || 0; my $allcallbacks = $options{-all_callbacks} || 0; my $gridcolor = $options{-gridcolor} || GRIDCOLOR; my $gridmajorcolor = $options{-gridmajorcolor} || GRIDMAJORCOLOR; my $grid = $options{-grid} || 0; my $extend_grid = $options{-extend_grid}|| 0; my $flip = $options{-flip} || 0; my $empty_track_style = $options{-empty_tracks} || 'key'; my $autopad = defined $options{-auto_pad} ? $options{-auto_pad} : 1; my $truecolor = $options{-truecolor} || 0; my $truetype = $options{-truetype} || 0; my $image_class = ($options{-image_class} && $options{-image_class} =~ /SVG/) ? 'GD::SVG' : $options{-image_class} || 'GD'; # Allow users to specify GD::SVG using SVG my $linkrule = $options{-link}; my $titlerule = $options{-title}; my $targetrule = $options{-target}; my $background = $options{-background}; my $postgrid = $options{-postgrid}; $options{-stop}||= $options{-end}; # damn damn damn my $add_categories= $options{-add_category_labels}; if (my $seg = $options{-segment}) { $offset = eval {$seg->start-1} || 0; $length = $seg->length; } $offset ||= $options{-start}-1 if defined $options{-start}; $length ||= $options{-stop}-$options{-start}+1 if defined $options{-start} && defined $options{-stop}; # bring in the image generator class, since we will need it soon anyway eval "require $image_class; 1" or $class->throw($@); return bless { tracks => [], width => $options{-width} || 600, pad_top => $options{-pad_top}||0, pad_bottom => $options{-pad_bottom}||0, pad_left => $options{-pad_left}||0, pad_right => $options{-pad_right}||0, global_alpha => $options{-alpha} || 0, length => $length, offset => $offset, gridcolor => $gridcolor, gridmajorcolor => $gridmajorcolor, grid => $grid, extend_grid => $extend_grid, bgcolor => $bgcolor, height => 0, # AUTO spacing => $spacing, key_font => $keyfont, key_color => $keycolor, key_spacing => $keyspacing, key_style => $keystyle, key_align => $keyalign, suppress_key => $suppress_key, background => $background, postgrid => $postgrid, autopad => $autopad, all_callbacks => $allcallbacks, truecolor => $truecolor, truetype => $truetype, flip => $flip, linkrule => $linkrule, titlerule => $titlerule, targetrule => $targetrule, empty_track_style => $empty_track_style, image_class => $image_class, image_package => $image_class . '::Image', # Accessors polygon_package => $image_class . '::Polygon', add_category_labels => $add_categories, key_boxes => [], },$class; } sub rotate { my $self = shift; my $d = $self->{rotate}; $self->{rotate} = shift if @_; $d; } sub pad_left { my $self = shift; my $g = $self->{pad_left}; $self->{pad_left} = shift if @_; $g; } sub pad_right { my $self = shift; my $g = $self->{pad_right}; $self->{pad_right} = shift if @_; $g; } sub pad_top { my $self = shift; my $g = $self->{pad_top}; $self->{pad_top} = shift if @_; $g; } sub pad_bottom { my $self = shift; my $g = $self->{pad_bottom}; $self->{pad_bottom} = shift if @_; $g; } sub extend_grid { my $self = shift; my $g = $self->{extend_grid}; $self->{extend_grid} = shift if @_; $g; } sub flip { my $self = shift; my $g = $self->{flip}; $self->{flip} = shift if @_; $g; } sub truetype { my $self = shift; my $g = $self->{truetype}; $self->{truetype} = shift if @_; $g; } # values of empty_track_style are: # "suppress" -- suppress empty tracks entirely (default) # "key" -- show just the key in "between" mode # "line" -- draw a thin grey line # "dashed" -- draw a dashed line sub empty_track_style { my $self = shift; my $g = $self->{empty_track_style}; $self->{empty_track_style} = shift if @_; $g; } sub key_style { my $self = shift; my $g = $self->{key_style}; $self->{key_style} = shift if @_; $g; } sub auto_pad { my $self = shift; my $g = $self->{autopad}; $self->{autopad} = shift if @_; $g; } # public routine for mapping from a base pair # location to pixel coordinates sub location2pixel { my $self = shift; my $end = $self->end + 1; my @coords = $self->{flip} ? map { $end-$_ } @_ : @_; $self->map_pt(@coords); } # numerous direct calls into array used here for performance considerations sub map_pt { my $self = shift; my $offset = $self->{offset}; my $scale = $self->{scale} || $self->scale; my $pl = $self->{pad_left}; my $pr = $self->{width}; my $flip = $self->{flip}; my $length = $self->{length}; my @result; foreach (@_) { my $val = $flip ? $pr - ($length - ($_- 1)) * $scale : ($_-$offset-1) * $scale; $val = int($val + 0.5 * ($val<=>0)); $val = -1 if $val < 0; $val = $pr+1 if $val > $pr; push @result,$val; } @result; } sub map_no_trunc { my $self = shift; my $offset = $self->{offset}; my $scale = $self->scale; my $pl = $self->{pad_left}; my $pr = $pl + $self->{width}; # - $self->{pad_right}; my $flip = $self->{flip}; my $length = $self->{length}; my $end = $offset+$length; my @result; foreach (@_) { my $val = $flip ? int (0.5 + $pl + ($end - ($_- 1)) * $scale) : int (0.5 + $pl + ($_-$offset-1) * $scale); push @result,$val; } @result; } sub scale { my $self = shift; $self->{scale} ||= $self->width/($self->length); } sub start { shift->{offset}+1} sub end { $_[0]->start + $_[0]->{length}-1} sub offset { shift->{offset} } sub width { my $self = shift; my $d = $self->{width}; $self->{width} = shift if @_; $d; } sub left { my $self = shift; $self->pad_left; } sub right { my $self = shift; $self->pad_left + $self->width; # - $self->pad_right; } sub top { shift->pad_top; } sub bottom { my $self = shift; $self->height - $self->pad_bottom; } sub spacing { my $self = shift; my $d = $self->{spacing}; $self->{spacing} = shift if @_; $d; } sub key_spacing { my $self = shift; my $d = $self->{key_spacing}; $self->{key_spacing} = shift if @_; $d; } sub length { my $self = shift; my $d = $self->{length}; if (@_) { my $l = shift; $l = $l->length if ref($l) && $l->can('length'); $self->{length} = $l; } $d; } sub gridcolor {shift->{gridcolor}} sub gridmajorcolor {shift->{gridmajorcolor}} sub all_callbacks { shift->{all_callbacks} } sub add_track { my $self = shift; $self->_do_add_track(scalar(@{$self->{tracks}}),@_); } sub unshift_track { my $self = shift; $self->_do_add_track(0,@_); } sub insert_track { my $self = shift; my $position = shift; $self->_do_add_track($position,@_); } # create a feature and factory pair # see Factory.pm for the format of the options # The thing returned is actually a generic Glyph sub _do_add_track { my $self = shift; my $position = shift; # due to indecision, we accept features # and/or glyph types in the first two arguments my ($features,$glyph_name) = ([],undef); while ( @_ && $_[0] !~ /^-/) { my $arg = shift; $features = $arg and next if ref($arg); $glyph_name = $arg and next unless ref($arg); } my %args = @_; my ($map,$ss,%options); foreach (keys %args) { (my $canonical = lc $_) =~ s/^-//; if ($canonical eq 'glyph') { $map = $args{$_}; delete $args{$_}; } elsif ($canonical eq 'stylesheet') { $ss = $args{$_}; delete $args{$_}; } else { $options{$canonical} = $args{$_}; } } $glyph_name = $map if defined $map; $glyph_name ||= 'generic'; local $^W = 0; # uninitialized variable warnings under 5.00503 my $panel_map = ref($map) eq 'CODE' ? sub { my $feature = shift; return 'track' if eval { defined $feature->primary_tag && $feature->primary_tag eq 'track' }; return 'group' if eval { defined $feature->primary_tag && $feature->primary_tag eq 'group' }; return 'scale' if eval { defined $feature->primary_tag && $feature->primary_tag eq 'scale' }; return $map->($feature,'glyph',$self); } : ref($map) eq 'HASH' ? sub { my $feature = shift; return 'track' if eval { defined $feature->primary_tag && $feature->primary_tag eq 'track' }; return 'group' if eval { defined $feature->primary_tag && $feature->primary_tag eq 'group' }; return 'scale' if eval { defined $feature->primary_tag && $feature->primary_tag eq 'scale' }; return eval {$map->{$feature->primary_tag}} || 'generic'; } : sub { my $feature = shift; return 'track' if eval { defined $feature->primary_tag && $feature->primary_tag eq 'track' }; return 'group' if eval { defined $feature->primary_tag && $feature->primary_tag eq 'group' }; return 'scale' if eval { defined $feature->primary_tag && $feature->primary_tag eq 'scale' }; return $glyph_name; }; $self->_add_track($position,$features,-map=>$panel_map,-stylesheet=>$ss,-options=>\%options); } sub _add_track { my $self = shift; my ($position,$features,@options) = @_; # build the list of features into a Bio::Graphics::Feature object $features = [$features] unless ref $features eq 'ARRAY'; # optional middle-level glyph is the group foreach my $f (grep {ref $_ eq 'ARRAY'} @$features) { next unless ref $f eq 'ARRAY'; $f = Bio::Graphics::Feature->new( -segments=>$f, -type => 'group' ); } # top-level glyph is the track my $feature = Bio::Graphics::Feature->new( -segments=>$features, -start => $self->offset+1, -stop => $self->offset+$self->length, -type => 'track' ); my $factory = Bio::Graphics::Glyph::Factory->new($self,@options); my $track = $factory->make_glyph(-1,$feature); splice(@{$self->{tracks}},$position,0,$track); return $track; } sub _expand_padding { my $self = shift; my $track = shift; my $extra_padding = $self->extra_right_padding; my $keystyle = $self->key_style; my $empty_track_style = $self->empty_track_style; return unless $keystyle eq 'left' or $keystyle eq 'right'; return unless $self->auto_pad; $self->setup_fonts(); my $width = $self->{key_font}->width; my $key = $self->track2key($track); return unless defined $key; my $has_parts = $track->parts; next if !$has_parts && $empty_track_style eq 'suppress'; my $width_needed = $self->{key_font}->width * CORE::length($key)+3; if ($keystyle eq 'left') { my $width_i_have = $self->pad_left; $self->pad_left($width_needed) if $width_needed > $width_i_have; } elsif ($keystyle eq 'right') { $width_needed += $extra_padding; my $width_i_have = $self->pad_right; $self->pad_right($width_needed) if $width_needed > $width_i_have; } } sub extra_right_padding { EXTRA_RIGHT_PADDING } sub height { my $self = shift; $self->setup_fonts; for my $track (@{$self->{tracks}}) { $self->_expand_padding($track); } my $spacing = $self->spacing; my $key_height = $self->format_key; my $empty_track_style = $self->empty_track_style; my $key_style = $self->key_style; my $bottom_key = $key_style eq 'bottom'; my $between_key = $key_style eq 'between'; my $side_key = $key_style =~ /left|right/; my $draw_empty = $empty_track_style =~ /^(line|dashed)$/; my $keyheight = $self->{key_font}->height; my $height = 0; for my $track (@{$self->{tracks}}) { my $draw_between = $between_key && $track->option('key'); my $has_parts = $track->parts; next if !$has_parts && ($empty_track_style eq 'suppress' or $empty_track_style eq 'key' && $bottom_key); $height += $keyheight if $draw_between; $height += $self->spacing; my $layout_height = $track->layout_height; $height += ($side_key && $keyheight > $layout_height) ? $keyheight : $layout_height; } # get rid of spacing under last track $height -= $self->spacing unless $bottom_key; return $height + $key_height + $self->pad_top + $self->pad_bottom + 2; } sub setup_fonts { my $self = shift; return if ref $self->{key_font}; my $image_class = $self->image_class; my $keyfont = $self->{key_font}; my $font_obj = $image_class->$keyfont; $self->{key_font} = $font_obj; } sub gd { my $self = shift; my $existing_gd = shift; local $^W = 0; # can't track down the uninitialized variable warning return $self->{gd} if $self->{gd}; $self->setup_fonts; unless ($existing_gd) { my $image_class = $self->image_class; eval "require $image_class; 1" or $self->throw($@); } my $height = $self->height; my $width = $self->width + $self->pad_left + $self->pad_right; my $pkg = $self->image_package; $height = 12 if $height < 1; # so GD doesn't crash $width = 1 if $width < 1; # ditto my $gd = $existing_gd || $pkg->new($width,$height, ($self->{truecolor} && $pkg->can('isTrueColor') ? 1 : ()) ); $gd->{debug} = 0 if $gd->isa('GD::SVG::Image'); # hack $self->{gd} = $gd; if ($self->{truecolor} && $pkg->can('saveAlpha')) { $gd->saveAlpha(1); } my %translation_table; my $global_alpha = $self->{global_alpha} || 0; for my $name (keys %COLORS) { my $idx = $gd->colorAllocate(@{$COLORS{$name}}); $translation_table{$name} = $idx; } $self->{translations} = \%translation_table; $self->{gd} = $gd->isa('GD::SVG::Image') ? $gd : $self->truetype ? Bio::Graphics::GDWrapper->new($gd,$self->truetype) : $gd; eval {$gd->alphaBlending(0)}; if ($self->bgcolor) { $gd->fill(0,0,$self->bgcolor); } elsif (eval {$gd->isTrueColor}) { $gd->fill(0,0,$translation_table{'white'}); } eval {$gd->alphaBlending(1)}; my $pl = $self->pad_left; my $pt = $self->pad_top; my $offset = $pt; my $keyheight = $self->{key_font}->height; my $bottom_key = $self->{key_style} eq 'bottom'; my $between_key = $self->{key_style} eq 'between'; my $left_key = $self->{key_style} eq 'left'; my $right_key = $self->{key_style} eq 'right'; my $empty_track_style = $self->empty_track_style; my $spacing = $self->spacing; # we draw in two steps, once for background of tracks, and once for # the contents. This allows the grid to sit on top of the track background. for my $track (@{$self->{tracks}}) { my $draw_between = $between_key && $track->option('key'); next if !$track->parts && ($empty_track_style eq 'suppress' or $empty_track_style eq 'key' && $bottom_key); $gd->filledRectangle($pl, $offset, $width-$self->pad_right, $offset+$track->layout_height + ($between_key ? $self->{key_font}->height : 0), $track->tkcolor) if defined $track->tkcolor; $offset += $keyheight if $draw_between; $offset += $track->layout_height + $spacing; } $self->startGroup($gd); $self->draw_background($gd,$self->{background}) if $self->{background}; $self->draw_grid($gd) if $self->{grid}; $self->draw_background($gd,$self->{postgrid}) if $self->{postgrid}; $self->endGroup($gd); $offset = $pt; for my $track (@{$self->{tracks}}) { $self->startGroup($gd); my $draw_between = $between_key && $track->option('key'); my $has_parts = $track->parts; my $side_key_height = 0; next if !$has_parts && ($empty_track_style eq 'suppress' or $empty_track_style eq 'key' && $bottom_key); if ($draw_between) { $offset += $self->draw_between_key($gd,$track,$offset); } $self->draw_empty($gd,$offset,$empty_track_style) if !$has_parts && $empty_track_style=~/^(line|dashed)$/; $track->draw($gd,$pl,$offset,0,1); if ($self->{key_style} =~ /^(left|right)$/) { $side_key_height = $self->draw_side_key($gd,$track,$offset,$self->{key_style}); } $self->track_position($track,$offset); my $layout_height = $track->layout_height; $offset += ($side_key_height > $layout_height ? $side_key_height : $layout_height)+$spacing; $self->endGroup($gd); } $self->startGroup($gd); $self->draw_bottom_key($gd,$pl,$offset) if $self->{key_style} eq 'bottom'; $self->endGroup($gd); return $self->{gd} = $self->rotate ? $gd->copyRotate90 : $gd; } sub gdfont { my $self = shift; my $font = shift; my $img_class = $self->image_class; if (!UNIVERSAL::isa($font,$img_class . '::Font') && $font =~ /^(gd|sanserif)/) { my $ref = $self->{gdfonts} ||= { gdTinyFont => $img_class->gdTinyFont(), gdSmallFont => $img_class->gdSmallFont(), gdMediumBoldFont => $img_class->gdMediumBoldFont(), gdLargeFont => $img_class->gdLargeFont(), gdGiantFont => $img_class->gdGiantFont(), sanserif => $img_class->gdSmallFont(), }; return $ref->{$font} || $ref->{gdSmallFont}; } else { return $font; } } sub string_width { my $self = shift; my ($font,$string) = @_; my $class = $self->image_class; return $font->width*CORE::length($string) unless $self->truetype && $class ne 'GD::SVG'; return Bio::Graphics::GDWrapper->string_width($font,$string); } sub string_height { my $self = shift; my ($font,$string) = @_; my $class = $self->image_class; return $font->height unless $self->truetype && eval{$class eq 'GD' || $class->isa('GD::Image')}; return Bio::Graphics::GDWrapper->string_height($font,$string); } sub startGroup { my $self = shift; my $gd = shift; $gd->startGroup if $gd->can('startGroup'); } sub endGroup { my $self = shift; my $gd = shift; $gd->endGroup if $gd->can('endGroup'); } # Package accessors # GD (and GD::SVG)'s new() resides in GD::Image sub image_class { return shift->{image_class}; } sub image_package { return shift->{image_package}; } sub polygon_package { return shift->{polygon_package}; } sub boxes { my $self = shift; if (my $boxes = $self->{boxes}){ # cached result return wantarray ? @$boxes : $boxes; } my @boxes; my $offset = 0; $self->setup_fonts; my $pl = $self->pad_left; my $pt = $self->pad_top; my $between_key = $self->{key_style} eq 'between'; my $bottom_key = $self->{key_style} eq 'bottom'; my $empty_track_style = $self->empty_track_style; my $keyheight = $self->{key_font}->height; my $spacing = $self->spacing; my $rotate = $self->rotate; for my $track (@{$self->{tracks}}) { my $draw_between = $between_key && $track->option('key'); next if !$track->parts && ($empty_track_style eq 'suppress' or $empty_track_style eq 'key' && $bottom_key); $offset += $keyheight if $draw_between; my $height = $track->layout_height; my $boxes = $track->boxes($pl,$offset+$pt); $self->track_position($track,$offset); push @boxes,@$boxes; $offset += $track->layout_height + $self->spacing; } if ($rotate) { my $x_offset = $self->height-1; @boxes = map { @{$_}[1,2,3,4] = @{$_}[4,1,2,3]; ($_->[1],$_->[3]) = map {$x_offset - $_} @{$_}[1,3]; $_; } @boxes; } $self->{boxes} = \@boxes; return wantarray ? @boxes : \@boxes; } sub track_position { my $self = shift; my $track = shift; my $d = $self->{_track_position}{$track}; $self->{_track_position}{$track} = shift if @_; $d; } # draw the keys -- between sub draw_between_key { my $self = shift; my ($gd,$track,$offset) = @_; my $key = $self->track2key($track) or return 0; my $x = $self->{key_align} eq 'center' ? $self->width - (CORE::length($key) * $self->{key_font}->width)/2 : $self->{key_align} eq 'right' ? $self->width - CORE::length($key) : $self->pad_left; # Key color hard-coded. Should be configurable for the control freaks. my $color = $self->translate_color('black'); $gd->string($self->{key_font},$x,$offset,$key,$color) unless $self->{suppress_key}; $self->add_key_box($track,$key,$x,$offset); return $self->{key_font}->height; } # draw the keys -- left or right side sub draw_side_key { my $self = shift; my ($gd,$track,$offset,$side) = @_; my $key = $self->track2key($track) or return; my $pos = $side eq 'left' ? $self->pad_left - $self->{key_font}->width * CORE::length($key)-3 : $self->pad_left + $self->width + EXTRA_RIGHT_PADDING; my $color = $self->translate_color('black'); unless ($self->{suppress_key}) { $gd->filledRectangle($pos,$offset, $pos+$self->{key_font}->width*CORE::length($key),$offset,#-$self->{key_font}->height)/2, $self->bgcolor); $gd->string($self->{key_font},$pos,$offset,$key,$color); } $self->add_key_box($track,$key,$pos,$offset); return $self->{key_font}->height; } # draw the keys -- bottom sub draw_bottom_key { my $self = shift; my ($gd,$left,$top) = @_; my $key_glyphs = $self->{key_glyphs} or return; my $color = $self->translate_color($self->{key_color}); $gd->filledRectangle($left,$top,$self->width - $self->pad_right,$self->height-$self->pad_bottom,$color); my $text_color = $self->translate_color('black'); $gd->string($self->{key_font},$left,KEYPADTOP+$top,"KEY:",$text_color) unless $self->{suppress_key}; $top += $self->{key_font}->height + KEYPADTOP; $_->draw($gd,$left,$top) foreach @$key_glyphs; } # Format the key section, and return its height sub format_key { my $self = shift; return 0 unless $self->key_style eq 'bottom'; return $self->{key_height} if defined $self->{key_height}; my $suppress = $self->{empty_track_style} eq 'suppress'; my $between = $self->{key_style} eq 'between'; if ($between) { my @key_tracks = $suppress ? grep {$_->option('key') && $_->parts} @{$self->{tracks}} : grep {$_->option('key')} @{$self->{tracks}}; return $self->{key_height} = @key_tracks * $self->{key_font}->height; } elsif ($self->{key_style} eq 'bottom') { my ($height,$width) = (0,0); my %tracks; my @glyphs; local $self->{flip} = 0; # don't want to worry about flipped keys! # determine how many glyphs become part of the key # and their max size for my $track (@{$self->{tracks}}) { next unless $track->option('key'); next if $suppress && !$track->parts; my $glyph; if (my @parts = $track->parts) { $glyph = $parts[0]->keyglyph; } else { my $t = Bio::Graphics::Feature->new(-segments=> [Bio::Graphics::Feature->new(-start => $self->offset, -stop => $self->offset+$self->length)]); my $g = $track->factory->make_glyph(0,$t); $glyph = $g->keyglyph; } next unless $glyph; $tracks{$track} = $glyph; my ($h,$w) = ($glyph->layout_height, $glyph->layout_width); $height = $h if $h > $height; $width = $w if $w > $width; push @glyphs,$glyph; } $width += $self->key_spacing; # no key glyphs, no key return $self->{key_height} = 0 unless @glyphs; # now height and width hold the largest glyph, and $glyph_count # contains the number of glyphs. We will format them into a # box that is roughly 3 height/4 width (golden mean) my $rows = 0; my $cols = 0; my $maxwidth = $self->width - $self->pad_left - $self->pad_right; while (++$rows) { $cols = @glyphs / $rows; $cols = int ($cols+1) if $cols =~ /\./; # round upward for fractions my $total_width = $cols * $width; my $total_height = $rows * $width; last if $total_width < $maxwidth; } # move glyphs into row-major format my $spacing = $self->key_spacing; my $i = 0; for (my $c = 0; $c < $cols; $c++) { for (my $r = 0; $r < $rows; $r++) { my $x = $c * ($width + $spacing); my $y = $r * ($height + $spacing); next unless defined $glyphs[$i]; $glyphs[$i]->move($x,$y); $i++; } } $self->{key_glyphs} = \@glyphs; # remember our key glyphs # remember our key height return $self->{key_height} = ($height+$spacing) * $rows + $self->{key_font}->height +KEYPADTOP; } else { # no known key style, neither "between" nor "bottom" return $self->{key_height} = 0; } } sub add_key_box { my $self = shift; my ($track,$label,$x,$y, $is_legend) = @_; my $value = [$label,$x,$y,$x+$self->{key_font}->width*CORE::length($label),$y+$self->{key_font}->height,$track]; push @{$self->{key_boxes}},$value; } sub key_boxes { my $ref = shift->{key_boxes}; return wantarray ? @$ref : $ref; } sub add_category_labels { my $self = shift; my $d = $self->{add_category_labels}; $self->{add_category_labels} = shift if @_; $d; } sub track2key { my $self = shift; my $track = shift; return $track->make_key_name(); } sub draw_empty { my $self = shift; my ($gd,$offset,$style) = @_; $offset += $self->spacing/2; my $left = $self->pad_left; my $right = $self->width-$self->pad_right; my $color = $self->translate_color(MISSING_TRACK_COLOR); my $ic = $self->image_class; if ($style eq 'dashed') { $gd->setStyle($color,$color,$ic->gdTransparent(),$ic->gdTransparent()); $gd->line($left,$offset,$right,$offset,$ic->gdStyled()); } else { $gd->line($left,$offset,$right,$offset,$color); } $offset; } # draw a grid sub draw_grid { my $self = shift; my $gd = shift; my $gridcolor = $self->translate_color($self->{gridcolor}); my $gridmajorcolor = $self->translate_color($self->{gridmajorcolor}); my @positions; my ($major,$minor); if (ref $self->{grid} eq 'ARRAY') { @positions = @{$self->{grid}}; } else { ($major,$minor) = $self->ticks; my $first_tick = $minor * int($self->start/$minor); for (my $i = $first_tick; $i <= $self->end+1; $i += $minor) { push @positions,$i; } } my $pl = $self->pad_left; my $pt = $self->extend_grid ? 0 : $self->pad_top; my $pr = $self->right; my $pb = $self->extend_grid ? $self->height : $self->height - $self->pad_bottom; my $offset = $self->{offset}+$self->{length}+1; for my $tick (@positions) { my ($pos) = $self->map_pt($self->{flip} ? $offset - $tick : $tick); my $color = (defined $major && $tick % $major == 0) ? $gridmajorcolor : $gridcolor; $gd->line($pl+$pos,$pt,$pl+$pos,$pb,$color); } } # draw an image (or invoke a drawing routine) sub draw_background { my $self = shift; my ($gd,$image_or_routine) = @_; if (ref $image_or_routine eq 'CODE') { return $image_or_routine->($gd,$self); } if (-f $image_or_routine) { # a file to draw my $method = $image_or_routine =~ /\.png$/i ? 'newFromPng' : $image_or_routine =~ /\.jpe?g$/i ? 'newFromJpeg' : $image_or_routine =~ /\.gd$/i ? 'newFromGd' : $image_or_routine =~ /\.gif$/i ? 'newFromGif' : $image_or_routine =~ /\.xbm$/i ? 'newFromXbm' : ''; return unless $method; my $image = eval {$self->image_package->$method($image_or_routine)}; unless ($image) { warn $@; return; } my ($src_width,$src_height) = $image->getBounds; my ($dst_width,$dst_height) = $gd->getBounds; # tile the thing on for (my $x = 0; $x < $dst_width; $x += $src_width) { for (my $y = 0; $y < $dst_height; $y += $src_height) { $gd->copy($image,$x,$y,0,0,$src_width,$src_height); } } } } # calculate major and minor ticks, given a start position sub ticks { my $self = shift; my ($length,$minwidth) = @_; my $img = $self->image_class; $length = $self->{length} unless defined $length; $minwidth = $img->gdSmallFont->width*7 unless defined $minwidth; my ($major,$minor); # figure out tick mark scale # we want no more than 1 major tick mark every 40 pixels # and enough room for the labels my $scale = $self->scale; my $interval = 10; while (1) { my $pixels = $interval * $scale; last if $pixels >= $minwidth; $interval *= 10; } # to make sure a major tick shows up somewhere in the first half # # $interval *= .5 if ($interval > 0.5*$length); return ($interval,$interval/10); } # reverse of translate(); given index, return rgb triplet sub rgb { my $self = shift; my $idx = shift; my $gd = $self->{gd} or return; return $gd->rgb($idx); } sub transparent_color { my $self = shift; my ($opacity,@colors) = @_; return $self->_translate_color($opacity,@colors); } sub translate_color { my $self = shift; my @colors = @_; return $self->_translate_color(1.0,@colors); } sub _translate_color { my $self = shift; my ($opacity,@colors) = @_; $opacity = '1.0' if $opacity == 1; my $default_alpha = $self->adjust_alpha($opacity); $default_alpha ||= 0; my $ckey = "@{colors}_${default_alpha}"; return $self->{closestcache}{$ckey} if exists $self->{closestcache}{$ckey}; my $index; my $gd = $self->gd or return 1; my $table = $self->{translations} or return 1; if (@colors == 3) { $index = $gd->colorAllocateAlpha(@colors,$default_alpha); } elsif ($colors[0] =~ /^\#([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i) { my ($r,$g,$b,$alpha) = (hex($1),hex($2),hex($3),hex($4)); $index = $gd->colorAllocateAlpha($r,$g,$b,$alpha); } elsif ($colors[0] =~ /^\#([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i) { my ($r,$g,$b) = (hex($1),hex($2),hex($3)); $index = $gd->colorAllocateAlpha($r,$g,$b,$default_alpha); } elsif ($colors[0] =~ /^(\d+),(\d+),(\d+),([\d.]+)$/i || $colors[0] =~ /^rgba\((\d+),(\d+),(\d+),([\d.]+)\)$/) { my $alpha = $self->adjust_alpha($4); my (@rgb) = map {/(\d+)%/ ? int(255 * $1 / 100) : $_} ($1,$2,$3); $index = $gd->colorAllocateAlpha(@rgb,$4); } elsif ($colors[0] =~ /^(\d+),(\d+),(\d+)$/i || $colors[0] =~ /^rgb\((\d+),(\d+),(\d+)\)$/i ) { my (@rgb) = map {/(\d+)%/ ? int(255 * $1 / 100) : $_} ($1,$2,$3); $index = $gd->colorAllocateAlpha(@rgb,$default_alpha); } elsif ($colors[0] eq 'transparent') { $index = $gd->colorAllocateAlpha(255,255,255,127); } elsif ($colors[0] =~ /^(\w+):([\d.]+)/) { # color:alpha my @rgb = $self->color_name_to_rgb($1); @rgb = (0,0,0) unless @rgb; my $alpha = $self->adjust_alpha($2); $index = $gd->colorAllocateAlpha(@rgb,$alpha); } elsif ($default_alpha < 127) { my @rgb = $self->color_name_to_rgb($colors[0]); @rgb = (0,0,0) unless @rgb; $index = $gd->colorAllocateAlpha(@rgb,$default_alpha); } else { $index = defined $table->{$colors[0]} ? $table->{$colors[0]} : 1; } return $self->{closestcache}{$ckey} = $index; } # change CSS opacity values (0-1.0) into GD opacity values (127-0) sub adjust_alpha { my $self = shift; my $value = shift; my $alpha = $value =~ /\./ # floating point ? int(127-($value*127)+0.5) : $value; $alpha = 0 if $alpha < 0; $alpha = 127 if $alpha > 127; return $alpha; } # workaround for bad GD sub colorClosest { my ($self,$gd,@c) = @_; return $gd->colorResolve(@c) if $GD::VERSION < 2.04; my $index = $gd->colorResolve(@c); return $index if $index >= 0; my $value; for (keys %COLORS) { my ($r,$g,$b) = @{$COLORS{$_}}; my $dist = ($r-$c[0])**2 + ($g-$c[1])**2 + ($b-$c[2])**2; ($value,$index) = ($dist,$_) if !defined($value) || $dist < $value; } return $self->{translations}{$index}; } sub bgcolor { my $self = shift; return unless $self->{bgcolor}; return $self->translate_color($self->{bgcolor}); } sub set_pen { my $self = shift; my ($linewidth,$color) = @_; return $self->{pens}{$linewidth,$color} if $self->{pens}{$linewidth,$color}; my $gd = $self->{gd}; my $pkg = $self->image_package; my $pen = $self->{pens}{$linewidth} = $pkg->new($linewidth,$linewidth); my @rgb = $self->rgb($color); my $bg = $pen->colorAllocate(255,255,255); my $fg = $pen->colorAllocate(@rgb); $pen->fill(0,0,$fg); $gd->setBrush($pen); return $self->image_class->gdBrushed(); } sub png { my $gd = shift->gd; $gd->png; } sub svg { my $gd = shift->gd; $gd->svg; } # WARNING: THIS STUFF IS COPIED FROM Bio::Graphics::Browser.pm AND # Bio::Graphics::FeatureFile AND MUST BE REFACTORED # write a png image to disk and generate an image map in a convenient # CGIish way. sub image_and_map { my $self = shift; my %args = @_; my $link_rule = $args{-link} || $self->{linkrule}; my $title_rule = $args{-title} || $self->{titlerule}; my $target_rule = $args{-target} || $self->{targetrule}; my $tmpurl = $args{-url} || '/tmp'; my $docroot = $args{-root} || $ENV{DOCUMENT_ROOT} || ''; my $mapname = $args{-mapname} || $IMAGEMAP++; $docroot .= '/' if $docroot && $docroot !~ m!/$!; # get rid of any netstat part please (my $tmpurlbase = $tmpurl) =~ s!^\w+://[^/]+!!; my $tmpdir = "${docroot}${tmpurlbase}"; my $url = $self->create_web_image($tmpurl,$tmpdir); my $map = $self->create_web_map($mapname,$link_rule,$title_rule,$target_rule); return ($url,$map,$mapname); } sub create_web_image { my $self = shift; my ($tmpurl,$tmpdir) = @_; # create directory if it isn't there already # we need to untaint tmpdir before calling mkpath() return unless $tmpdir =~ /^(.+)$/; my $path = $1; unless (-d $path) { require File::Path unless defined &File::Path::mkpath; File::Path::mkpath($path,0,0777) or $self->throw("Couldn't create temporary image directory $path: $!"); } unless (defined &Digest::MD5::md5_hex) { eval "require Digest::MD5; 1" or $self->throw("Sorry, but the image_and_map() method requires the Digest::MD5 module."); } my $data = $self->png; my $signature = Digest::MD5::md5_hex($data); my $extension = 'png'; # untaint signature for use in open $signature =~ /^([0-9A-Fa-f]+)$/g or return; $signature = $1; my $url = sprintf("%s/%s.%s",$tmpurl,$signature,$extension); my $imagefile = sprintf("%s/%s.%s",$tmpdir,$signature,$extension); open (my $F,">", $imagefile) || $self->throw("Can't open image file $imagefile for writing: $!\n"); binmode($F); print $F $data; return $url; } sub create_web_map { my $self = shift; my ($name,$linkrule,$titlerule,$targetrule) = @_; $name ||= 'map'; my $boxes = $self->boxes; my (%track2link,%track2title,%track2target); eval "require CGI" unless CGI->can('escapeHTML'); my $map = qq(\n); foreach (@$boxes){ my ($feature,$left,$top,$right,$bottom,$track) = @$_; next unless $feature->can('primary_tag'); my $lr = $track2link{$track} ||= (defined $track->option('link') ? $track->option('link') : $linkrule); next unless $lr; my $tr = exists $track2title{$track} ? $track2title{$track} : $track2title{$track} ||= (defined $track->option('title') ? $track->option('title') : $titlerule); my $tgr = exists $track2target{$track} ? $track2target{$track} : $track2target{$track} ||= (defined $track->option('target')? $track->option('target') : $targetrule); my $href = $self->make_link($lr,$feature); my $title = CGI::escapeHTML($self->make_link($tr,$feature,1)); my $target = CGI::escapeHTML($self->make_link($tgr,$feature,1)); my $a = $title ? qq(title="$title") : ''; my $t = $target ? qq(target="$target") : ''; $map .= qq(\n) if $href; } $map .= "\n"; $map; } sub make_link { my $self = shift; my ($linkrule,$feature,$escapeHTML) = @_; eval "require Bio::Graphics::FeatureFile;1" unless Bio::Graphics::FeatureFile->can('link_pattern'); return Bio::Graphics::FeatureFile->link_pattern($linkrule,$feature,$self,$escapeHTML); } sub make_title { my $self = shift; my $feature = shift; eval "require Bio::Graphics::FeatureFile;1" unless Bio::Graphics::FeatureFile->can('make_title'); return Bio::Graphics::FeatureFile->make_title($feature); } sub read_colors { my $class = shift; local ($/) = "\n"; local $_; while () { chomp; last if /^__END__/; my ($name,$r,$g,$b) = split /\s+/; @{$COLORS{$name}} = (hex $r,hex $g, hex $b); } } sub color_name_to_rgb { my $class = shift; my $color_name = shift; $class->read_colors() unless %COLORS; return unless $COLORS{$color_name}; return wantarray ? @{$COLORS{$color_name}} : $COLORS{$color_name}; } sub color_names { my $class = shift; $class->read_colors unless %COLORS; return wantarray ? keys %COLORS : [keys %COLORS]; } sub glyph_scratch { my $self = shift; my $d = $GlyphScratch; $GlyphScratch = shift if @_; $d; } sub finished { my $self = shift; for my $track (@{$self->{tracks} || []}) { $track->finished(); } delete $self->{tracks}; } 1; __DATA__ white FF FF FF black 00 00 00 aliceblue F0 F8 FF antiquewhite FA EB D7 aqua 00 FF FF aquamarine 7F FF D4 azure F0 FF FF beige F5 F5 DC bisque FF E4 C4 blanchedalmond FF EB CD blue 00 00 FF blueviolet 8A 2B E2 brown A5 2A 2A burlywood DE B8 87 cadetblue 5F 9E A0 chartreuse 7F FF 00 chocolate D2 69 1E coral FF 7F 50 cornflowerblue 64 95 ED cornsilk FF F8 DC crimson DC 14 3C cyan 00 FF FF darkblue 00 00 8B darkcyan 00 8B 8B darkgoldenrod B8 86 0B darkgray A9 A9 A9 darkgreen 00 64 00 darkkhaki BD B7 6B darkmagenta 8B 00 8B darkolivegreen 55 6B 2F darkorange FF 8C 00 darkorchid 99 32 CC darkred 8B 00 00 darksalmon E9 96 7A darkseagreen 8F BC 8F darkslateblue 48 3D 8B darkslategray 2F 4F 4F darkturquoise 00 CE D1 darkviolet 94 00 D3 deeppink FF 14 100 deepskyblue 00 BF FF dimgray 69 69 69 dodgerblue 1E 90 FF firebrick B2 22 22 floralwhite FF FA F0 forestgreen 22 8B 22 fuchsia FF 00 FF gainsboro DC DC DC ghostwhite F8 F8 FF gold FF D7 00 goldenrod DA A5 20 gray 80 80 80 grey 80 80 80 green 00 80 00 greenyellow AD FF 2F honeydew F0 FF F0 hotpink FF 69 B4 indianred CD 5C 5C indigo 4B 00 82 ivory FF FF F0 khaki F0 E6 8C lavender E6 E6 FA lavenderblush FF F0 F5 lawngreen 7C FC 00 lemonchiffon FF FA CD lightblue AD D8 E6 lightcoral F0 80 80 lightcyan E0 FF FF lightgoldenrodyellow FA FA D2 lightgreen 90 EE 90 lightgrey D3 D3 D3 lightpink FF B6 C1 lightsalmon FF A0 7A lightseagreen 20 B2 AA lightskyblue 87 CE FA lightslategray 77 88 99 lightsteelblue B0 C4 DE lightyellow FF FF E0 lime 00 FF 00 limegreen 32 CD 32 linen FA F0 E6 magenta FF 00 FF maroon 80 00 00 mediumaquamarine 66 CD AA mediumblue 00 00 CD mediumorchid BA 55 D3 mediumpurple 100 70 DB mediumseagreen 3C B3 71 mediumslateblue 7B 68 EE mediumspringgreen 00 FA 9A mediumturquoise 48 D1 CC mediumvioletred C7 15 85 midnightblue 19 19 70 mintcream F5 FF FA mistyrose FF E4 E1 moccasin FF E4 B5 navajowhite FF DE AD navy 00 00 80 oldlace FD F5 E6 olive 80 80 00 olivedrab 6B 8E 23 orange FF A5 00 orangered FF 45 00 orchid DA 70 D6 palegoldenrod EE E8 AA palegreen 98 FB 98 paleturquoise AF EE EE palevioletred DB 70 100 papayawhip FF EF D5 peachpuff FF DA B9 peru CD 85 3F pink FF C0 CB plum DD A0 DD powderblue B0 E0 E6 purple 80 00 80 red FF 00 00 rosybrown BC 8F 8F royalblue 41 69 E1 saddlebrown 8B 45 13 salmon FA 80 72 sandybrown F4 A4 60 seagreen 2E 8B 57 seashell FF F5 EE sienna A0 52 2D silver C0 C0 C0 skyblue 87 CE EB slateblue 6A 5A CD slategray 70 80 90 snow FF FA FA springgreen 00 FF 7F steelblue 46 82 B4 tan D2 B4 8C teal 00 80 80 thistle D8 BF D8 tomato FF 63 47 turquoise 40 E0 D0 violet EE 82 EE wheat F5 DE B3 whitesmoke F5 F5 F5 yellow FF FF 00 yellowgreen 9A CD 32 gradient1 00 ff 00 gradient2 0a ff 00 gradient3 14 ff 00 gradient4 1e ff 00 gradient5 28 ff 00 gradient6 32 ff 00 gradient7 3d ff 00 gradient8 47 ff 00 gradient9 51 ff 00 gradient10 5b ff 00 gradient11 65 ff 00 gradient12 70 ff 00 gradient13 7a ff 00 gradient14 84 ff 00 gradient15 8e ff 00 gradient16 99 ff 00 gradient17 a3 ff 00 gradient18 ad ff 00 gradient19 b7 ff 00 gradient20 c1 ff 00 gradient21 cc ff 00 gradient22 d6 ff 00 gradient23 e0 ff 00 gradient24 ea ff 00 gradient25 f4 ff 00 gradient26 ff ff 00 gradient27 ff f4 00 gradient28 ff ea 00 gradient29 ff e0 00 gradient30 ff d6 00 gradient31 ff cc 00 gradient32 ff c1 00 gradient33 ff b7 00 gradient34 ff ad 00 gradient35 ff a3 00 gradient36 ff 99 00 gradient37 ff 8e 00 gradient38 ff 84 00 gradient39 ff 7a 00 gradient40 ff 70 00 gradient41 ff 65 00 gradient42 ff 5b 00 gradient43 ff 51 00 gradient44 ff 47 00 gradient45 ff 3d 00 gradient46 ff 32 00 gradient47 ff 28 00 gradient48 ff 1e 00 gradient49 ff 14 00 gradient50 ff 0a 00 __END__ =head1 NAME Bio::Graphics::Panel - Generate GD images of Bio::Seq objects =head1 SYNOPSIS # This script parses a GenBank or EMBL file named on the command # line and produces a PNG rendering of it. Call it like this: # render.pl my_file.embl | display - use strict; use Bio::Graphics; use Bio::SeqIO; my $file = shift or die "provide a sequence file as the argument"; my $io = Bio::SeqIO->new(-file=>$file) or die "could not create Bio::SeqIO"; my $seq = $io->next_seq or die "could not find a sequence in the file"; my @features = $seq->all_SeqFeatures; # sort features by their primary tags my %sorted_features; for my $f (@features) { my $tag = $f->primary_tag; push @{$sorted_features{$tag}},$f; } my $panel = Bio::Graphics::Panel->new( -length => $seq->length, -key_style => 'between', -width => 800, -pad_left => 10, -pad_right => 10, ); $panel->add_track( arrow => Bio::SeqFeature::Generic->new(-start=>1, -end=>$seq->length), -bump => 0, -double=>1, -tick => 2); $panel->add_track(generic => Bio::SeqFeature::Generic->new(-start=>1, -end=>$seq->length), -glyph => 'generic', -bgcolor => 'blue', -label => 1, ); # general case my @colors = qw(cyan orange blue purple green chartreuse magenta yellow aqua); my $idx = 0; for my $tag (sort keys %sorted_features) { my $features = $sorted_features{$tag}; $panel->add_track($features, -glyph => 'generic', -bgcolor => $colors[$idx++ % @colors], -fgcolor => 'black', -font2color => 'red', -key => "${tag}s", -bump => +1, -height => 8, -label => 1, -description => 1, ); } print $panel->png; $panel->finished; exit 0; =head1 DESCRIPTION The Bio::Graphics::Panel class provides drawing and formatting services for any object that implements the Bio::SeqFeatureI interface, including Ace::Sequence::Feature and Das::Segment::Feature objects. It can be used to draw sequence annotations, physical (contig) maps, or any other type of map in which a set of discrete ranges need to be laid out on the number line. The module supports a drawing style in which each type of feature occupies a discrete "track" that spans the width of the display. Each track will have its own distinctive "glyph", a configurable graphical representation of the feature. The module also supports a more flexible style in which several different feature types and their associated glyphs can occupy the same track. The choice of glyph is under run-time control. Semantic zooming (for instance, changing the type of glyph depending on the density of features) is supported by a callback system for configuration variables. The module has built-in support for Bio::Das stylesheets, and stylesheet-driven configuration can be intermixed with semantic zooming, if desired. You can add a key to the generated image using either of two key styles. One style places the key captions at the top of each track. The other style generates a graphical key at the bottom of the image. Note that this module depends on GD. The optional SVG output depends on GD::SVG and SVG. The installed script glyph_help.pl provides quick help on glyphs and their options. =head1 METHODS This section describes the class and object methods for Bio::Graphics::Panel. Typically you will begin by creating a new Bio::Graphics::Panel object, passing it the desired width of the image to generate and an origin and length describing the coordinate range to display. The Bio::Graphics::Panel-Enew() method has many configuration variables that allow you to control the appearance of the image. You will then call add_track() one or more times to add sets of related features to the picture. add_track() places a new horizontal track on the image, and is likewise highly configurable. When you have added all the features you desire, you may call png() to convert the image into a PNG-format image, or boxes() to return coordinate information that can be used to create an imagemap. =head2 CONSTRUCTORS new() is the constructor for Bio::Graphics::Panel: =over 4 =item $panel = Bio::Graphics::Panel-Enew(@options) The new() method creates a new panel object. The options are a set of tag/value pairs as follows: Option Value Default ------ ----- ------- -offset Base pair to place at extreme left none of image, in zero-based coordinates -length Length of sequence segment, in bp none -start Start of range, in 1-based none coordinates. -stop Stop of range, in 1-based none coordinates. -end Same as -stop. -segment A Bio::SeqI or Das::Segment none object, used to derive sequence range if not otherwise specified. -width Desired width of image, in pixels 600 -spacing Spacing between tracks, in pixels 5 -pad_top Additional whitespace between top 0 of image and contents, in pixels -pad_bottom Additional whitespace between top 0 of image and bottom, in pixels -pad_left Additional whitespace between left 0 of image and contents, in pixels -pad_right Additional whitespace between right 0 of image and bottom, in pixels -bgcolor Background color for the panel as a white whole -key_color Background color for the key printed wheat at bottom of panel (if any) -key_spacing Spacing between key glyphs in the 10 key printed at bottom of panel (if any) -key_font Font to use in printed key gdMediumBoldFont captions. -key_style Whether to print key at bottom of none panel ("bottom"), between each track ("between"), to the left of each track ("left"), to the right of each track ("right") or not at all ("none"). -add_category_labels false Whether to add the "category" to the track key. The category is an optional argument that can be attached to each track. If a category is present, and this option is true, then the category will be added to the track label in parentheses. For example, if -key is "Protein matches" and -category is "vertebrate", then the track will be labeled "Protein matches (vertebrate)". -auto_pad If "left" or "right" keys are in use true then setting auto_pad to a true value will allow the panel to adjust its width in order to accomodate the length of the longest key. -empty_tracks What to do when a track is empty. suppress Options are to suppress the track completely ("suppress"), to show just the key in "between" mode ("key"), to draw a thin grey line ("line"), or to draw a dashed line ("dashed"). -flip flip the drawing coordinates left false to right, so that lower coordinates are to the right. This can be useful for drawing (-) strand features. -all_callbacks Whether to invoke callbacks on false the automatic "track" and "group" glyphs. -grid Whether to draw a vertical grid in false the background. Pass a scalar true value to have a grid drawn at regular intervals (corresponding to the minor ticks of the arrow glyph). Pass an array reference to draw the grid at the specified positions. -gridcolor Color of the grid lightcyan -gridmajorcolor Color of grid major intervals cyan -extend_grid If true, extend the grid into the pad false top and pad_bottom regions -background An image or callback to use for the none background of the image. Will be invoked I drawing the grid. -postgrid An image or callback to use for the none background of the image. Will be invoked I drawing the grid. -truecolor Create a truecolor (24-bit) image. false Useful when working with the "image" glyph. -truetype Render text using scaleable vector false fonts rather than bitmap fonts. -image_class To create output in scalable vector graphics (SVG), optionally pass the image class parameter 'GD::SVG'. Defaults to using vanilla GD. See the corresponding image_class() method below for details. -link, -title, -target These options are used when creating imagemaps for display on the web. See L. Typically you will pass new() an object that implements the Bio::RangeI interface, providing a length() method, from which the panel will derive its scale. $panel = Bio::Graphics::Panel->new(-segment => $sequence, -width => 800); new() will return undef in case of an error. Note that if you use the "left" or "right" key styles, you are responsible for allocating sufficient -pad_left or -pad_right room for the labels to appear. The necessary width is the number of characters in the longest key times the font width (gdMediumBoldFont by default) plus 3 pixels of internal padding. The simplest way to calculate this is to iterate over the possible track labels, find the largest one, and then to compute its width using the formula: $width = gdMediumBoldFont->width * length($longest_key) +3; In order to obtain scalable vector graphics (SVG) output, you should pass new() the -image_class=E'GD::SVG' parameter. This will cause Bio::Graphics::Panel to load the optional GD::SVG module. See the gd() and svg() methods below for additional information. You can tile an image onto the panel either before or after it draws the grid. Simply provide the filename of the image in the -background or -postgrid options. The image file must be of type PNG, JPEG, XBM or GIF and have a filename ending in .png, .jpg, .jpeg, .xbm or .gif. You can also pass a code ref for the -background or -postgrid option, in which case the subroutine will be invoked at the appropriate time with the GD::Image object and the Panel object as its two arguments. You can then use the panel methods to map base pair coordinates into pixel coordinates and do some custom drawing. For example, this code fragment will draw a gray rectangle between bases 500 and 600 to indicate a "gap" in the sequence: my $panel = Bio::Graphics::Panel->new(-segment=>$segment, -grid=>1, -width=>600, -postgrid=> \&draw_gap); sub gap_it { my $gd = shift; my $panel = shift; my ($gap_start,$gap_end) = $panel->location2pixel(500,600); my $top = $panel->top; my $bottom = $panel->bottom; my $gray = $panel->translate_color('gray'); $gd->filledRectangle($gap_start,$top,$gap_end,$bottom,$gray); } The B<-truetype> argument will activate rendering of labels using antialiased vector fonts. If it is a value of "1", then labels will be rendered using the default font (Verdana). Pass a font name to use this font as the default: -truetype => 'Times New Roman', Note that you can change the font on a track-by-track basis simply by using a truetype font name as add_track()'s -font argument. =back =head2 OBJECT METHODS =over 4 =item $track = $panel-Eadd_track($glyph,$features,@options) The add_track() method adds a new track to the image. Tracks are horizontal bands which span the entire width of the panel. Each track contains a number of graphical elements called "glyphs", corresponding to a sequence feature. There are a large number of glyph types. By default, each track will be homogeneous on a single glyph type, but you can mix several glyph types on the same track by providing a code reference to the -glyph argument. Other options passed to add_track() control the color and size of the glyphs, whether they are allowed to overlap, and other formatting attributes. The height of a track is determined from its contents and cannot be directly influenced. The first two arguments are the glyph name and an array reference containing the list of features to display. The order of the arguments is irrelevant, allowing either of these idioms: $panel->add_track(arrow => \@features); $panel->add_track(\@features => 'arrow'); The glyph name indicates how each feature is to be rendered. A variety of glyphs are available, and the number is growing. You may omit the glyph name entirely by providing a B<-glyph> argument among @options, as described below. Currently, the following glyphs are available: Name Description ---- ----------- anchored_arrow a span with vertical bases |---------|. If one or the other end of the feature is off-screen, the base will be replaced by an arrow. arrow An arrow; can be unidirectional or bidirectional. It is also capable of displaying a scale with major and minor tickmarks, and can be oriented horizontally or vertically. box A filled rectangle, nondirectional. Subfeatures are ignored. cds Draws CDS features, using the phase information to show the reading frame usage. At high magnifications draws the protein translation. crossbox A box with a big "X" inside it. diamond A diamond, useful for point features like SNPs. dna At high magnification draws the DNA sequence. At low magnifications draws the GC content. dot A circle, useful for point features like SNPs, stop codons, or promoter elements. ellipse An oval. extending_arrow Similar to arrow, but a dotted line indicates when the feature extends beyond the end of the canvas. generic A filled rectangle, nondirectional. Subfeatures are shown as rectangles that are not connected together. graded_segments Similar to segments, but the intensity of the color is proportional to the score of the feature. This is used for showing the intensity of blast hits or other alignment features. group A group of related features connected by a dashed line. This is used internally by Panel. image A pixmap image that will be layered on top of the graphic. heterogeneous_segments Like segments, but you can use the source field of the feature to change the color of each segment. line A simple line. pinsertion A triangle designed to look like an insertion location (e.g. a transposon insertion). processed_transcript multi-purpose representation of a spliced mRNA, including positions of UTRs primers Two inward pointing arrows connected by a line. Used for STSs. redgreen_box A box that changes from green->yellow->red as the score of the feature increases from 0.0 to 1.0. Useful for representing microarray results. rndrect A round-cornered rectangle. segments A set of filled rectangles connected by solid lines. Used for interrupted features, such as gapped alignments. ruler_arrow An arrow with major and minor tick marks and interval labels. toomany Tries to show many features as a cloud. Not very successful. track A group of related features not connected by a line. This is used internally by Panel. transcript Similar to segments, but the connecting line is a "hat" shape, and the direction of transcription is indicated by a small arrow. transcript2 Similar to transcript, but the direction of transcription is indicated by a terminal exon in the shape of an arrow. translation 1, 2 and 3-frame translations. At low magnifications, can be configured to show start and stop codon locations. At high magnifications, shows the multi-frame protein translation. triangle A triangle whose width and orientation can be altered. xyplot Histograms and other graphs plotted against the genome. stackedplot A column plot showing multiple data series across multiple categories. ternary_plot Ternary (triangle) plots. whiskerplot Box and whisker plot for statistical data If the glyph name is omitted from add_track(), the "generic" glyph will be used by default. To get more information about a glyph, run perldoc on "Bio::Graphics::Glyph::glyphname", replacing "glyphname" with the name of the glyph you are interested in. The "box" glyph is optimized for single features with no subfeatures. If you are drawing such a feature, using "box" will be noticeably faster than "generic." The @options array is a list of name/value pairs that control the attributes of the track. Some options are interpretered directly by the track. Others are passed down to the individual glyphs (see L<"GLYPH OPTIONS">). The following options are track-specific: Option Description Default ------ ----------- ------- -tkcolor Track color white -glyph Glyph class to use. "generic" -color_series Dynamically choose false bgcolor. -stylesheet Bio::Das::Stylesheet to none use to generate glyph classes and options. B<-tkcolor> controls the background color of the track as a whole. B<-glyph> controls the glyph type. If present, it supersedes the glyph name given in the first or second argument to add_track(). The value of B<-glyph> may be a constant string, a hash reference, or a code reference. In the case of a constant string, that string will be used as the class name for all generated glyphs. If a hash reference is passed, then the feature's primary_tag() will be used as the key to the hash, and the value, if any, used to generate the glyph type. If a code reference is passed, then this callback will be passed arguments consisting of the feature and the panel object. The callback is expected to examine the feature and return a glyph name as its single result. Example: $panel->add_track(\@exons, -glyph => sub { my ($feature,$panel) = @_; $feature->source_tag eq 'curated' ? 'ellipse' : 'box'; } ); The B<-stylesheet> argument is used to pass a Bio::Das stylesheet object to the panel. This stylesheet will be called to determine both the glyph and the glyph options. If both a stylesheet and direct options are provided, the latter take precedence. The B<-color_series> argument causes the track to ignore the -bgcolor setting and instead to assign glyphs a series of contrasting colors. This is usually used in combination with -bump=>'overlap' in order to create overlapping features. A true value activates the color series. You may adjust the default color series using the B<-color_cycle> option, which is either a reference to an array of Bio::Graphics color values, or a space-delimited string of color names/value. If successful, add_track() returns an Bio::Graphics::Glyph object. You can use this object to add additional features or to control the appearance of the track with greater detail, or just ignore it. Tracks are added in order from the top of the image to the bottom. To add tracks to the top of the image, use unshift_track(). B It is not uncommon to add a group of features which are logically connected, such as the 5' and 3' ends of EST reads. To group features into sets that remain on the same horizontal position and bump together, pass the sets as an anonymous array. For example: $panel->add_track(segments => [[$abc_5,$abc_3], [$xxx_5,$xxx_3], [$yyy_5,$yyy_3]] ); Typical usage is: $panel->add_track( transcript => \@genes, -fillcolor => 'green', -fgcolor => 'black', -bump => +1, -height => 10, -label => 1); The track object is simply a specialized type of glyph. See L for a description of the methods that it supports. =item $track = unshift_track($glyph,$features,@options) unshift_track() works like add_track(), except that the new track is added to the top of the image rather than the bottom. =item $track = $panel-Einsert_track($position,$glyph,$features,@options) This works like add_track(), but the track is inserted into the indicated position. The track will be inserted B the indicated position; thus specify a track of 0 to insert the new track at the beginning. =item $gd = $panel-Egd([$gd]) The gd() method lays out the image and returns a GD::Image object containing it. You may then call the GD::Image object's png() or jpeg() methods to get the image data. Optionally, you may pass gd() a preexisting GD::Image object that you wish to draw on top of. If you do so, you should call the width() and height() methods first to ensure that the image has sufficient dimensions. If you passed new() the -image_class=E'GD::SVG' parameter, the gd() method returns a GD::SVG::Image object. This object overrides GD::Image methods in order to generate SVG output. It behaves exactly as described for GD::Image objects with one exception: it implements and svg() method instead of the png() or jpeg() methods. Currently there is no direct access to underlying SVG calls but this is subject to change in the future. =item $png = $panel-Epng The png() method returns the image as a PNG-format drawing, without the intermediate step of returning a GD::Image object. =item $svg = $panel-Esvg The svg() method returns the image in an XML-ified SVG format. =item $panel-Efinished Bio::Graphics creates memory cycles. When you are finished with the panel, you should call its finished() method. Otherwise you will have memory leaks. This is only an issue if you're going to create several panels in a single program. =item $image_class = $panel-Eimage_class The image_class() method returns the current drawing package being used, currently one of GD or GD::SVG. This is primarily used internally to ensure that calls to GD's exported methods are called in an object-oriented manner to avoid compile time undefined string errors. This is usually not needed for external use. =item $image_package = $panel-Eimage_package This accessor method, like image_class() above is provided as a convenience. It returns the current image package in use, currently one of GD::Image or GD::SVG::Image. This is not normally used externally. =item $polygon_package = $panel-Epolygon_package This accessor method, like image_package() above is provided as a convenience. It returns the current polygon package in use, currently one of GD::Polygon or GD::SVG::Polygon. This is not normally used externally except in the design of glyphs. =item $boxes = $panel-Eboxes =item @boxes = $panel-Eboxes The boxes() method returns a list of arrayrefs containing the coordinates of each glyph. The method is useful for constructing an image map. In a scalar context, boxes() returns an arrayref. In an list context, the method returns the list directly. Each member of the list is an arrayref of the following format: [ $feature, $x1, $y1, $x2, $y2, $track ] The first element is the feature object; either an Ace::Sequence::Feature, a Das::Segment::Feature, or another Bioperl Bio::SeqFeatureI object. The coordinates are the topleft and bottomright corners of the glyph, including any space allocated for labels. The track is the Bio::Graphics::Glyph object corresponding to the track that the feature is rendered inside. =item $boxes = $panel-Ekey_boxes =item @boxes = $panel-Ekey_boxes Returns the positions of the track keys as an arrayref or a list, depending on context. Each value in the list is an arrayref of format: [ $key_text, $x1, $y1, $x2, $y2, $track ] =item $position = $panel-Etrack_position($track) After calling gd() or boxes(), you can learn the resulting Y coordinate of a track by calling track_position() with the value returned by add_track() or unshift_track(). This will return undef if called before gd() or boxes() or with an invalid track. =item $rotate = $panel-Erotate([$new_value]) Gets or sets the "rotate" flag. If rotate is set to true (default false), then calls to gd(), png(), gif(), boxes(), and image_and_map() will all return an image and/or imagemap that has been rotated to the right by 90 degrees. This is mostly useful for drawing karyotypes with the ideogram glyph, in order to rotate the chromosomes into the usual vertical position. =item @pixel_coords = $panel-Elocation2pixel(@feature_coords) Public routine to map feature coordinates (in base pairs) into pixel coordinates relative to the left-hand edge of the picture. If you define a -background callback, the callback may wish to invoke this routine in order to translate base coordinates into pixel coordinates. =item $left = $panel-Eleft =item $right = $panel-Eright =item $top = $panel-Etop =item $bottom = $panel-Ebottom Return the pixel coordinates of the I of the panel, that is, exclusive of the padding. =back =head1 GLYPH OPTIONS Each glyph has its own specialized subset of options, but some are shared by all glyphs: Option Description Default ------ ----------- ------- -key Description of track for undef display in the track label. -category The category of the track undef for display in the track label. -fgcolor Foreground color black -bgcolor Background color turquoise -linewidth Width of lines drawn by 1 glyph -height Height of glyph 10 -font Glyph font gdSmallFont -fontcolor Primary font color black -font2color Secondary font color turquoise -opacity Value from 0.0 (invisible) 1.0 to 1.0 (opaque) which controls the translucency of overlapping features. -label Whether to draw a label false -description Whether to draw a false description -bump Bump direction 0 -sort_order Specify layout sort order "default" -feature_limit Maximum number of features undef (unlimited) to display -bump_limit Maximum number of levels undef (unlimited) to bump -hbumppad Additional horizontal 0 padding between bumped features -strand_arrow Whether to indicate undef (false) strandedness -stranded Synonym for -strand_arrow undef (false) -part_labels Whether to label individual undef (false) subparts. -part_label_merge Whether to merge undef (false) adjacent subparts when labeling. -connector Type of connector to none use to connect related features. Options are "solid," "hat", "dashed", "quill" and "none". -all_callbacks Whether to invoke undef callbacks for autogenerated "track" and "group" glyphs -subpart_callbacks Whether to invoke false callbacks for subparts of the glyph. -box_subparts Return boxes around feature 0 subparts rather than around the feature itself. -link, -title, -target These options are used when creating imagemaps for display on the web. See L. -filter Select which features to display. Must be a CODE reference. B Colors can be expressed in either of two ways: as symbolic names such as "cyan", as HTML-style #RRGGBB triples, and r,g,b comma-separated numbers. The symbolic names are the 140 colors defined in the Netscape/Internet Explorer color cube, and can be retrieved using the Bio::Graphics::Panel-Ecolor_names() method. Transparent and semi-transparent colors can be specified using the following syntax: #RRGGBBAA - red, green, blue and alpha r,g,b,a - red, green, blue, alpha blue:alpha - symbolic name and alpha rgb(r,g,b) - CSS style rgb values rgba(r,g,b,a) - CSS style rgba values Alpha values can be specified as GD style integers ranging from 0 (opaque) to 127 (transparent), or as CSS-style floating point numbers ranging from 0.0 (transparent) through 1.0 (opaque). As a special case, a completely transparent color can be specified using the color named "transparent". In the rgb() and rgba() forms, red, green, blue values can be specified as percentages, as in rgb(100%,0%,50%); otherwise, the values are integers from 0 to 255. In addition, the -fgcolor and -bgcolor options accept the special color names "featureScore" and "featureRGB". In the first case, Bio::Graphics will examine each feature in the track for a defined "score" tag (or the presence of a score() method) with a numeric value ranging from 0-1000. It will draw a grayscale color ranging from lightest (0) to darkest (1000). If the color is named "featureRGB", then Bio::Graphics will look for a tag named "RGB" and will use that as the color. B The -fgcolor option controls the foreground color, including the edges of boxes and the like. B The -bgcolor option controls the background used for filled boxes and other "solid" glyphs. The foreground color controls the color of lines and strings. The -tkcolor argument controls the background color of the entire track. BFor truecolor images, you can apply a default opacity value to both foreground and background colors by supplying a B<-opacity> argument. This is specified as a CSS-style floating point number from 0.0 to 1.0. If the color has an explicit alpha, then the default is ignored. B The -tkcolor option used to specify the background of the entire track. B The -font option controls which font will be used. If the Panel was created without passing a true value to -truecolor, then only GD bitmapped fonts are available to you. These include 'gdTinyFont', 'gdSmallFont', 'gdLargeFont', 'gdMediumBoldFont', and 'gdGiantFont'. If the Panel was creaed using a truevalue for -truecolor, then you can pass the name of any truetype font installed on the server system. Any of these formats will work: -font => 'Times New Roman', # Times font, let the system pick size -font => 'Times New Roman-12' # Times font, 12 points -font => 'Times New Roman-12:Italic' # Times font, 12 points italic -font => 'Times New Roman-12:Bold' # Times font, 12 points bold B The -fontcolor option controls the color of primary text, such as labels B The -font2color option controls the color of secondary text, such as descriptions. B The -label argument controls whether or not the ID of the feature should be printed next to the feature. It is accepted by all glyphs. By default, the label is printed just above the glyph and left aligned with it. -label can be a constant string or a code reference. Values can be any of: -label value Description ------------ ----------- 0 Don't draw a label 1 Calculate a label based on primary tag of sequence "a string" Use "a string" as the label code ref Invoke the code reference to compute the label A known bug with this naming scheme is that you can't label a feature with the string "1". To work around this, use "1 " (note the terminal space). B The -description argument controls whether or not a brief description of the feature should be printed next to it. By default, the description is printed just below the glyph and left-aligned with it. A value of 0 will suppress the description. A value of 1 will "magically" look for tags of type "note" or "description" and draw them if found, otherwise the source tag, if any, will be displayed. A code reference will be invoked to calculate the description on the fly. Anything else will be treated as a string and used verbatim. B A glyph can contain subglyphs, recursively. The top level glyph is the track, which contains one or more groups, which contain features, which contain subfeatures, and so forth. By default, the "group" glyph draws dotted lines between each of its subglyphs, the "segment" glyph draws a solid line between each of its subglyphs, and the "transcript" and "transcript2" glyphs draw hat-shaped lines between their subglyphs. All other glyphs do not connect their components. You can override this behavior by providing a -connector option, to explicitly set the type of connector. Valid options are: "hat" an upward-angling conector "solid" a straight horizontal connector "quill" a decorated line with small arrows indicating strandedness (like the UCSC Genome Browser uses) "dashed" a horizontal dashed line. The B<-connector_color> option controls the color of the connector, if any. B The B<-bump> argument controls what happens when glyphs collide. By default, they will simply overlap (value 0). A -bump value of +1 will cause overlapping glyphs to bump downwards until there is room for them. A -bump value of -1 will cause overlapping glyphs to bump upwards. You may also provide a -bump value of +2 or -2 to activate a very simple type of collision control in which each feature occupies its own line. This is useful for showing dense, nearly-full length features such as similarity hits. A bump of 3 or the string "fast" will turn on a faster collision-detection algorithm that only works properly with the default "left" sort order. Finally, a bump value of "overlap" will cause features to overlap each other and to made partially translucent (the translucency can be controlled with the -opacity setting). Features that are on opposite strands will bump, but those on the same strand will not. The bump argument can also be a code reference; see below. For convenience and backwards compatibility, if you specify a -bump of 1 and use the default sort order, the faster algorithm will be used. If you would like to see more horizontal whitespace between features that occupy the same line, you can specify it with the B<-hbumppad> option. Positive values increase the amount of whitespace between features. Negative values decrease the whitespace. B The -key argument declares that the track is to be shown in a key appended to the bottom of the image. The key contains a picture of a glyph and a label describing what the glyph means. The label is specified in the argument to -key. B Ordinarily, when you invoke the boxes() methods to retrieve the rectangles surrounding the glyphs (which you need to do to create clickable imagemaps, for example), the rectangles will surround the top level features. If you wish for the rectangles to surround subpieces of the glyph, such as the exons in a transcript, set box_subparts to a true numeric value. The value you specify will control the number of levels of subfeatures that the boxes will descend into. For example, if using the "gene" glyph, set -box_subparts to 2 to create boxes for the whole gene (level 0), the mRNAs (level 1) and the exons (level 2). B If set to true, each subpart of a multipart feature will be labeled with a number starting with 1 at the 5'-most part. This is useful for counting exons. You can pass a callback to this argument; the part number and the total number of parts will be arguments three and four. For example, to label the exons as "exon 1", "exon 2" and so on: -part_labels => sub { my ($feature,undef,$partno) = @_; return 'exon '.($partno+1); } The B<-label> argument must also be true. B If true, changes the behavior of -part_labels so that features that abut each other without a gap are treated as a single feature. Useful if you want to count the UTR and CDS segments of an exon as a single unit, and the default for transcript glyphs. B If set to true, some glyphs will indicate their strandedness, usually by drawing an arrow. For this to work, the Bio::SeqFeature must have a strand of +1 or -1. The glyph will ignore this directive if the underlying feature has a strand of zero or undef. B: By default, features are drawn with a layout based only on the position of the feature, assuring a maximal "packing" of the glyphs when bumped. In some cases, however, it makes sense to display the glyphs sorted by score or some other comparison, e.g. such that more "important" features are nearer the top of the display, stacked above less important features. The -sort_order option allows a few different built-in values for changing the default sort order (which is by "left" position): "low_score" (or "high_score") will cause features to be sorted from lowest to highest score (or vice versa). "left" (or "default") and "right" values will cause features to be sorted by their position in the sequence. "longest" (or "shortest") will cause the longest (or shortest) features to be sorted first, and "strand" will cause the features to be sorted by strand: "+1" (forward) then "0" (unknown, or NA) then "-1" (reverse). In all cases, the "left" position will be used to break any ties. To break ties using another field, options may be strung together using a "|" character; e.g. "strand|low_score|right" would cause the features to be sorted first by strand, then score (lowest to highest), then by "right" position in the sequence. Finally, a subroutine coderef with a $$ prototype can be provided. It will receive two B as arguments and should return -1, 0 or 1 (see Perl's sort() function for more information). For example, to sort a set of database search hits by bits (stored in the features' "score" fields), scaled by the log of the alignment length (with "start" position breaking any ties): sort_order = sub ($$) { my ($glyph1,$glyph2) = @_; my $a = $glyph1->feature; my $b = $glyph2->feature; ( $b->score/log($b->length) <=> $a->score/log($a->length) ) || ( $a->start <=> $b->start ) } It is important to remember to use the $$ prototype as shown in the example. Otherwise Bio::Graphics will quit with an exception. The arguments are subclasses of Bio::Graphics::Glyph, not the features themselves. While glyphs implement some, but not all, of the feature methods, to be safe call the two glyphs' feature() methods in order to convert them into the actual features. The '-always_sort' option, if true, will sort features even if bumping is turned off. This is useful if you would like overlapping features to stack in a particular order. Features towards the end of the list will overlay those towards the beginning of the sort order. B<-feature_limit>: When this option is set to a non-zero value, calls to a track's add_feature() method will maintain a count of features added to a track. Once the feature count exceeds the value set in -feature_limit, additional features will displace existing ones in a way that effects a uniform sampling of the total feature set. This is useful to protect against excessively large tracks. The total number of features added can be retrieved by calling the track's feature_count() method. B<-bump_limit>: When bumping is chosen, colliding features will ordinarily move upward or downward without limit. When many features collide, this can lead to excessively high images. You can limit the number of levels that features will bump by providing a numeric B option. After the limit is hit, features will pile up on top of each other, usually as a band at the bottom of the track. The B<-filter> option, which must be a CODE reference, will be invoked once for each feature prior to rendering it. The coderef will receive the feature as its single option and should return true if the feature is to be shown and false otherwise. =head2 Options and Callbacks Instead of providing a constant value to an option, you may subsitute a code reference. This code reference will be called every time the panel needs to configure a glyph. The callback will be called with three arguments like this: sub callback { my ($feature,$option_name,$part_no,$total_parts,$glyph) = @_; # do something which results in $option_value being set return $option_value; } The five arguments are C<$feature>, a reference to the IO::SeqFeatureI object, C<$option_name>, the name of the option to configure, C<$part_no>, an integer index indicating which subpart of the feature is being drawn, C<$total_parts>, an integer indicating the total number of subfeatures in the feature, and finally C<$glyph>, the Glyph object itself. The latter fields are useful in the case of treating the first or last subfeature differently, such as using a different color for the terminal exon of a gene. Usually you will only need to examine the first argument. This example shows a callback examining the score() attribute of a feature (possibly a BLAST hit) and return the color "red" for high-scoring features, and "green" for low-scoring features: sub callback { my $feature = shift; if ($feature->score > 90) { return 'red'; else { return 'green'; } } The callback should return a string indicating the desired value of the option. To tell the panel to use the default value for this option, return the string "*default*". The callback for -grid is slightly different because at the time this option is needed there is no glyph defined. In this case, the callback will get two arguments: the feature and the panel object: -glyph => sub { my ($feature,$panel) = @_; return 'gene' if $panel->length < 10_000; return 'box'; } When you install a callback for a feature that contains subparts, the callback will be invoked first for the top-level feature, and then for each of its subparts (recursively). You should make sure to examine the feature's type to determine whether the option is appropriate. Also be aware that some options are only called for subfeatures. For example, when using multi-segmented features, the "bgcolor" and "fgcolor" options apply to the subfeatures and not to the whole feature; therefore the corresponding callbacks will only be invoked for the subfeatures and not for the top-level feature. To get information that applies to the top-level feature, use the glyph's parent_feature() method. This returns: * the parent if called with no arguments or with an argument of (1) * the parent's parent if called with an argument of (2) * the parent's parent's parent if called with an argument of (3) * etc. The general way to take advantage of this feature is: sub callback { my ($feature,$option_name,$part_no,$total_parts,$glyph) = @_; my $parent = $glyph->parent_feature(); # do something which results in $option_value being set return $option_value; } or, more concisely: sub callback { my $feature = shift; # first argument my $glyph = pop; # last argument my $parent = $glyph->parent_feature(); # do something which results in $option_value being set return $option_value; } Some glyphs deliberately disable recursion into subparts. The "track", "group", "transcript", "transcript2" and "segments" glyphs selectively disable the -bump, -label and -description options. This is to avoid, for example, a label being attached to each exon in a transcript, or the various segments of a gapped alignment bumping each other. You can override this behavior and force your callback to be invoked by providing add_track() with a true B<-all_callbacks> argument. In this case, you must be prepared to handle configuring options for the "group" and "track" glyphs. In particular, this means that in order to control the -bump option with a callback, you should specify -all_callbacks=E1, and turn on bumping when the callback is in the track or group glyphs. The -subpart_callbacks options is similar, except that when this is set to true callbacks are invoked for the main glyph and its subparts. This option only affects the -label and -description options. =head2 ACCESSORS The following accessor methods provide access to various attributes of the panel object. Called with no arguments, they each return the current value of the attribute. Called with a single argument, they set the attribute and return its previous value. Note that in most cases you must change attributes prior to invoking gd(), png() or boxes(). These three methods all invoke an internal layout() method which places the tracks and the glyphs within them, and then caches the result. Accessor Name Description ------------- ----------- width() Get/set width of panel spacing() Get/set spacing between tracks key_spacing() Get/set spacing between keys length() Get/set length of segment (bp) flip() Get/set coordinate flipping pad_top() Get/set top padding pad_left() Get/set left padding pad_bottom() Get/set bottom padding pad_right() Get/set right padding start() Get the start of the sequence (bp; read only) end() Get the end of the sequence (bp; read only) left() Get the left side of the drawing area (pixels; read only) right() Get the right side of the drawing area (pixels; read only) =head2 COLOR METHODS The following methods are used internally, but may be useful for those implementing new glyph types. =over 4 =item @names = Bio::Graphics::Panel-Ecolor_names Return the symbolic names of the colors recognized by the panel object. In a scalar context, returns an array reference. =item ($red,$green,$blue) = Bio::Graphics::Panel-Ecolor_name_to_rgb($color) Given a symbolic color name, returns the red, green, blue components of the color. In a scalar context, returns an array reference to the rgb triplet. Returns undef for an invalid color name. =item @rgb = $panel-Ergb($index) Given a GD color index (between 0 and 140), returns the RGB triplet corresponding to this index. This method is only useful within a glyph's draw() routine, after the panel has allocated a GD::Image and is populating it. =item $index = $panel-Etranslate_color($color) Given a color, returns the GD::Image index. The color may be symbolic, such as "turquoise", or a #RRGGBB triple, as in #F0E0A8. This method is only useful within a glyph's draw() routine, after the panel has allocated a GD::Image and is populating it. =item $panel-Eset_pen($width,$color) Changes the width and color of the GD drawing pen to the values indicated. This is called automatically by the GlyphFactory fgcolor() method. It returns the GD value gdBrushed, which should be used for drawing. =back =head2 Creating Imagemaps You may wish to use Bio::Graphics to create clickable imagemaps for display on the web. The main method for achieving this is image_and_map(). Under special circumstances you may instead wish to call either or both of create_web_image() and create_web_map(). Here is a synopsis of how to use image_and_map() in a CGI script, using CGI.pm calls to provide the HTML scaffolding: print h2('My Genome'); my ($url,$map,$mapname) = $panel->image_and_map(-root => '/var/www/html', -url => '/tmpimages', -link => 'http://www.google.com/search?q=$name'); print img({-src=>$url,-usemap=>"#$mapname"}); print $map; We call image_and_map() with various arguments (described below) to generate a three element list consisting of the URL at which the image can be accessed, an HTML fragment containing the clickable imagemap data, and the name of the map. We print out an EimageE tag that uses the URL of the map as its src attribute and the name of the map as the value of its usemap attribute. It is important to note that we must put a "#" in front of the name of the map in order to indicate that the map can be found in the same document as the EimageE tag. Lastly, we print out the map itself. =over 4 =item ($url,$map,$mapname) = $panel-Eimage_and_map(@options) Create the image in a web-accessible directory and return its URL, its clickable imagemap, and the name of the imagemap. The following options are recognized: Option Description ------ ----------- -url The URL to store the image at. -root The directory path that should be appended to the start of -url in order to obtain a physical directory path. -link A string pattern or coderef that will be used to generate the outgoing hypertext links for the imagemap. -title A string pattern or coderef that will be used to generate the "title" tags of each element in the imagemap (these appear as popup hint boxes in certain browsers). -target A string pattern or coderef that will be used to generate the window target for each element. This can be used to pop up a new window when the user clicks on an element. -mapname The name to use for the EmapE tag. If not provided, a unique one will be autogenerated for you. This method returns a three element list consisting of the URL at which the image has been written to, the imagemap HTML, and the name of the map. Usually you will incorporate this information into an HTML document like so: my ($url,$map,$mapname) = $panel->image_and_map(-link=>'http://www.google.com/search?q=$name'); print qq(),"\n"; print $map,"\n"; =item $url = $panel-Ecreate_web_image($url,$root) Create the image, write it into the directory indicated by concatenating $root and $url (i.e. "$root/$url"), and return $url. =item $map = $panel-Ecreate_web_map('mapname',$linkrule,$titlerule,$targetrule) Create a clickable imagemap named "mapname" using the indicated rules to generate the hypertext links, the element titles, and the window targets for the graphical elements. Return the HTML for the map, including the enclosing EmapE tag itself. =back To use this method effectively, you will need a web server and an image directory in the document tree that is writable by the web server user. For example, if your web server's document root is located at /var/www/html, you might want to create a directory named "tmpimages" for this purpose: mkdir /var/www/html/tmpimages chmod 1777 /var/www/html/tmpimages The 1777 privilege will allow anyone to create files and subdirectories in this directory, but only the owner of the file will be able to delete it. When you call image_and_map(), you must provide it with two vital pieces of information: the URL of the image directory and the physical location of the web server's document tree. In our example, you would call: $panel->image_and_map(-root => '/var/www/html',-url=>'/tmpimages'); If you are working with virtual hosts, you might wish to provide the hostname:portnumber part of the URL. This will work just as well: $panel->image_and_map(-root => '/var/www/html', -url => 'http://myhost.com:8080/tmpimages'); If you do not provide the -root argument, the method will try to figure it out from the DOCUMENT_ROOT environment variable. If you do not provide the -url argument, the method will assume "/tmp". During execution, the image_and_map() method will generate a unique name for the image using the Digest::MD5 module. You can get this module on CPAN and it B be installed in order to use image_and_map(). The imagename will be a long hexadecimal string such as "e7457643f12d413f20843d4030c197c6.png". Its URL will be /tmpimages/e7457643f12d413f20843d4030c197c6.png, and its physical path will be /var/www/html/tmpimages/e7457643f12d413f20843d4030c197c6.png In addition to providing directory information, you must also tell image_and_map() how to create outgoing links for each graphical feature, and, optionally, how to create the "hover title" (the popup yellow box displayed by most modern browsers), and the name of the window or frame to link to when the user clicks on it. There are three ways to specify the link destination: =over 4 =item 1. By configuring one or more tracks with a -link argument. =item 2. By configuring the panel with a -link argument. =item 3. By passing a -link argument in the call to image_and_map(). =back The -link argument can be either a string or a coderef. If you pass a string, it will be interpreted as a URL pattern containing runtime variables. These variables begin with a dollar sign ($), and are replaced at run time with the information relating to the selected annotation. Recognized variables include: $name The feature's name (display name) $id The feature's id (eg, PK from a database) $class The feature's class (group class) $method The feature's method (same as primary tag) $source The feature's source $ref The name of the sequence segment (chromosome, contig) on which this feature is located $description The feature's description (notes) $start The start position of this feature, relative to $ref $end The end position of this feature, relative to $ref $length Length of this feature $segstart The left end of $ref displayed in the detailed view $segend The right end of $ref displayed in the detailed view For example, to link each feature to a Google search on the feature's description, use the argument: -link => 'http://www.google.com/search?q=$description' Be sure to use single quotes around the pattern, or Perl will attempt to perform variable interpretation before image_and_map() has a chance to work on it. You may also pass a code reference to -link, in which case the code will be called every time a URL needs to be generated for the imagemap. The subroutine will be called with two arguments, the feature and the Bio::Graphics::Panel object, and it should return the URL to link to, or an empty string if a link is not desired. Here is a simple example: -link => sub { my ($feature,$panel) = @_; my $type = $feature->primary_tag; my $name = $feature->display_name; if ($primary_tag eq 'clone') { return "http://www.google.com/search?q=$name"; } else { return "http://www.yahoo.com/search?p=$name"; } The -link argument cascades. image_and_map() will first look for a -link option in the track configuration, and if that's not found, it will look in the Panel configuration (created during Bio::Graphics::Panel-Enew). If no -link configuration option is found in either location, then image_and_map() will use the value of -link passed in its argument list, if any. The -title and -target options behave in a similar manner to -link. -title is used to assign each feature "title" and "alt" attributes. The "title" attribute is used by many browsers to create a popup hints box when the mouse hovers over the feature's glyph for a preset length of time, while the "alt" attribute is used to create navigable menu items for the visually impaired. As with -link, you can set the title by passing either a substitution pattern or a code ref, and the -title option can be set in the track, the panel, or the method call itself in that order of priority. If not provided, image_and_map() will autogenerate its own title in the form "EmethodE Edisplay_nameE EseqidE:start..end". The -target option can be used to specify the window or frame that clicked features will link to. By default, when the user clicks on a feature, the loaded URL will replace the current page. You can modify this by providing -target with the name of a preexisting or new window name in order to create effects like popup windows, multiple frames, popunders and the like. The value of -target follows the same rules as -title and -link, including variable substitution and the use of code refs. NOTE: Each time you call image_and_map() it will generate a new image file. Images that are identical to an earlier one will reuse the same name, but those that are different, even by one pixel, will result in the generation of a new image. If you have limited disk space, you might wish to check the images directory periodically and remove those that have not been accessed recently. The following cron script will remove image files that haven't been accessed in more than 20 days. 30 2 * * * find /var/www/html/tmpimages -type f -atime +20 -exec rm {} \; =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L L L =head1 AUTHOR Lincoln Stein Elstein@cshl.orgE Copyright (c) 2001 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/DrawTransmembrane.pm000555001750001750 13602012165075746 23103 0ustar00lsteinlstein000000000000package Bio::Graphics::DrawTransmembrane; use strict; use warnings; use GD; use base 'Bio::Root::Root'; my %DRAWOPTIONS = ( ## general parameters 'topology' => { 'private' => 'topology_array', 'default' => []}, 'topology_string' => { 'private' => 'topology_string', 'default' => 0}, 'n_terminal' => { 'private' => 'n_term', 'default' => 'out', }, 'title' => { 'private' => 'title', 'default' => ''}, 'inside_label' => { 'private' => 'in', 'default' => "Cytoplasmic"}, 'outside_label' => { 'private' => 'out', 'default' => "Extracellular"}, 'membrane_label' => { 'private' => 'membrane', 'default' => "Plasma Membrane"}, ## dimensions 'helix_height' => { 'private' => 'helix_height', 'default' => 130}, 'helix_width' => { 'private' => 'helix_width', 'default' => 50}, 'loop_width' => { 'private' => 'loop_width', 'default' => 20}, 'vertical_padding' => { 'private' => 'vertical_padding', 'default' => 140}, 'horizontal_padding' => { 'private' => 'horizontal_padding', 'default' => 150}, 'membrane_offset' => { 'private' => 'offset', 'default' => 6}, ## loop lengths and limits 'short_loop_height' => { 'private' => 'short_loop', 'default' => 90}, 'medium_loop_height' => { 'private' => 'medium_loop', 'default' => 120}, 'long_loop_height' => { 'private' => 'long_loop', 'default' => 150}, 'short_loop_limit' => { 'private' => 'short_loop_limit', 'default' => 15}, 'long_loop_limit' => { 'private' => 'long_loop_limit', 'default' => 30}, 'n_terminal_height' => { 'private' => 'n_terminal_height', 'default' => 150}, 'c_terminal_height' => { 'private' => 'c_terminal_height', 'default' => 80}, 'loop_heights' => { 'private' => 'loop_heights', 'default' => {}}, 'n_terminal_offset' => { 'private' => 'n_term_offset', 'default' => 0}, 'c_terminal_offset' => { 'private' => 'c_term_offset', 'default' => 0}, ## colour scheme & display options 'show_labels' => { 'private' => 'labels', 'default' => 'on'}, 'bold_helices' => { 'private' => 'bold_helices', 'default' => 1}, 'bold_labels' => { 'private' => 'bold_labels', 'default' => 0}, 'colour_scheme' => { 'private' => 'scheme', 'default' => 'yellow'}, 'draw_cytosol' => { 'private' => 'draw_cytosol', 'default' => 0}, 'draw_bilayer' => { 'private' => 'draw_bilayer', 'default' => 1}, 'draw_loops' => { 'private' => 'draw_loops', 'default' => 1}, 'draw_terminai' => { 'private' => 'draw_terminai', 'default' => 1}, 'draw_helices' => { 'private' => 'draw_helices', 'default' => 1}, ## labeling options 'labels' => { 'private' => 'loop_labels', 'default' => []}, 'text_offset' => { 'private' => 'text_offset', 'default' => 0}, 'helix_label' => { 'private' => 'helix_label', 'default' => 'S'}, 'n_term_label' => { 'private' => 'n_term_label', 'default' => 'N-Terminal'}, 'c_term_label' => { 'private' => 'c_term_label', 'default' => 'C-Terminal'}, 'dontsort' => { 'private' => 'dontsort', 'default' => 0}, 'ttf_font' => { 'private' => 'ttf_font', 'default' => 0}, 'ttf_font_size' => { 'private' => 'ttf_font_size', 'default' => 8}, ); sub new { my ($class, @args) = @_; my $self = $class->SUPER::new(@args); my %opt = @args; %opt = map {my $k = $_; $k =~ s{^-}{}; $k => $opt{$_}} keys %opt; # need to shore up private variables, check for req'd parameters for my $param (sort keys %DRAWOPTIONS) { my ($priv, $def) = ($DRAWOPTIONS{$param}->{'private'},$DRAWOPTIONS{$param}->{'default'}); $self->{$priv} = (exists $opt{$param}) ? $opt{$param} : $def; } $self->{'loop_count'} = 1; return $self; } sub png { my $self = shift; my @numeric = ('helix_height','helix_width','loop_width','vertical_padding','horizontal_padding','short_length','medium_loop_length','long_loop_length','short_loop_limit','long_loop_limit','n_terminal_height','membrane_offset','text_offset','n_term_offset','c_term_offset'); foreach (@numeric){ die "\nParameter $_ must be numeric.\n\n" if exists $self->{$_} && $self->{$_} =~ /-{?}\D+/; } foreach (keys %{$self->{'loop_labels'}}){ die "\nLabel position $_ must be numeric.\n\n" if $_ =~ /\D+/; } foreach (keys %{$self->{'loop_heights'}}){ die "\nLoop number $_ must be numeric.\n\n" if $_ =~ /\D+/; } foreach (values %{$self->{'loop_heights'}}){ die "\nLoop height $_ must be numeric.\n\n" if $_ =~ /\D+/; } ## n-terminal defaults to outside in it's not in,inside,out,outside $self->{'n_term'} = 'out' if (($self->{'n_term'} ne 'in')&&($self->{'n_term'} ne 'inside')&&($self->{'n_term'} ne 'out')||$self->{'n_term'} eq 'outside'); $self->{'n_term'} = 'in' if $self->{'n_term'} eq 'inside'; if ($self->{'topology_string'}){ $self->{'topology_string'} =~ s/\D\.//g; $self->{'topology_string'} =~ s/;/,/g; @{$self->{'topology_array'}} = split(/,/,$self->{'topology_string'}); } ## check to make sure we have pairs of helix boundaries and that data is numeric otherwise quit if (scalar @{$self->{'topology_array'}} % 2){ die "\nUneven number of helix boundaries.\n\n"; } foreach (@{$self->{'topology_array'}}){ if ($_ =~ /\D/){ die "\nTopology data is not numeric. $_\n\n"; } } ## check to make sure the TTF font exists, otherwise use gdSmallFont if ($self->{'ttf_font'}){ unless (-e $self->{'ttf_font'}){ print "\nCan't find font ".$self->{'ttf_font'}.".\n"; $self->{'ttf_font'} = 0; } } my @sorted_topology = sort {$a <=> $b} @{$self->{'topology_array'}}; ## Don't automatically sort the topology array @sorted_topology = @{$self->{'topology_array'}} if $self->{'dontsort'}; $self->{'helix_count'} = scalar @{$self->{'topology_array'}} / 2; unless ($self->{'helix_count'}){ die "\nNo topology data found.\n\n"; } ## put helix start/stop points in $self->{'helix_span'} and loop lengths in $self->{'loop_length'} foreach (0..($self->{'helix_count'} - 1)){ my $count = $_ * 2; $self->{'helix_span'}{$_ + 1}{'start'} = $sorted_topology[$count]; $self->{'helix_span'}{$_ + 1}{'stop'} = $sorted_topology[$count + 1]; $self->{'loop_length'}{$_ + 1} = scalar ($sorted_topology[$count + 2] - $sorted_topology[$count + 1]) unless ($_ + 1 == $self->{'helix_count'}); } $self->{'width'} = ($self->{'horizontal_padding'} * 2) + ($self->{'helix_width'} * $self->{'helix_count'}) + ($self->{'loop_width'} * ($self->{'helix_count'} - 1)); $self->{'height'} = $self->{'helix_height'} + ($self->{'vertical_padding'} * 2); ## create a new image $self->{'im'} = new GD::Image($self->{'width'},$self->{'height'}); $self->{'black'} = $self->{'im'}->colorAllocate(0,0,0); $self->{'white'} = $self->{'im'}->colorAllocate(255,255,255); $self->{'im'}->fill(0,0,$self->{'white'}); ## write title if ($self->{'ttf_font'}){ $self->{'im'}->stringFT($self->{'black'},$self->{'ttf_font'},$self->{'ttf_font_size'},0,4,12,$self->{'title'},{linespacing=>0.6,charmap => 'Unicode',}) if $self->{'title'}; }else{ $self->{'im'}->string(gdSmallFont,4,3,$self->{'title'},$self->{'black'}) if $self->{'title'}; } $self->draw_cytosol if $self->{'draw_cytosol'}; $self->draw_bilayer if $self->{'draw_bilayer'}; $self->draw_loops if $self->{'draw_loops'}; $self->draw_terminai if $self->{'draw_terminai'}; $self->draw_helices if $self->{'draw_helices'}; ## use GD to convert to png return $self->{'im'}->GD::Image::png; } sub add_tmhmm_feat { my $self = shift; my $feat = shift; #print Dumper $feat; ## add a helix from a tmhmm feature if ($feat->{'_primary_tag'} eq 'transmembrane'){ push @{$self->{'topology_array'}},$feat->{'_location'}{'_start'}; push @{$self->{'topology_array'}},$feat->{'_location'}{'_end'}; } ## i've made a few changes to TmHmm.pm to include the inside/outside loops. ## this bit looks for the topology of the 1st residue so we can now position the n-terminal if ($feat->{'_location'}{'_start'} == 1){ if ($feat->{'_primary_tag'} =~ /(\w+)_loop/){ $self->{'n_term'} = $1; } } return $self; } sub draw_cytosol { my $self = shift; my $cytosol_offset = 5; my $light_grey = $self->{'im'}->colorAllocate(164,164,164); ## draw cytosol $self->{'im'}->filledRectangle(($self->{'horizontal_padding'} / 3) ,($self->{'vertical_padding'} + $self->{'helix_height'} - $cytosol_offset),($self->{'width'} - ($self->{'horizontal_padding'} / 3)),($self->{'vertical_padding'} + ($self->{'helix_height'} * 2 ) + $cytosol_offset),$light_grey); return $self; } sub draw_bilayer { my $self = shift; my $dark_grey = $self->{'im'}->colorAllocate(40,40,40); my $dark_grey1 = $self->{'im'}->colorAllocate(50,50,50); my $dark_grey2 = $self->{'im'}->colorAllocate(60,60,60); my $dark_grey3 = $self->{'im'}->colorAllocate(70,70,70); ## label either side of membrane if ($self->{'ttf_font'}){ $self->{'im'}->stringFT($self->{'black'},$self->{'ttf_font'},$self->{'ttf_font_size'},0,($self->{'horizontal_padding'} / 3) + 2,($self->{'vertical_padding'} + $self->{'offset'} - 3),$self->{'out'},{linespacing=>0.6,charmap => 'Unicode',}) if $self->{'labels'}; $self->{'im'}->stringFT($self->{'black'},$self->{'ttf_font'},$self->{'ttf_font_size'},0,($self->{'horizontal_padding'} / 3) + 2,($self->{'vertical_padding'} + $self->{'helix_height'} - $self->{'offset'} + 12),$self->{'in'},{linespacing=>0.6,charmap => 'Unicode',}) if $self->{'labels'}; }else{ $self->{'im'}->string(gdSmallFont,($self->{'horizontal_padding'} / 3) + 2,($self->{'vertical_padding'} + $self->{'offset'} - 14),$self->{'out'},$self->{'black'}) if $self->{'labels'}; $self->{'im'}->string(gdSmallFont,($self->{'horizontal_padding'} / 3) + 2,($self->{'vertical_padding'} + $self->{'helix_height'} - $self->{'offset'} + 1),$self->{'in'},$self->{'black'}) if $self->{'labels'}; } ## draw membrane with graded fill $self->{'im'}->filledRectangle(($self->{'horizontal_padding'} / 3),($self->{'vertical_padding'} + $self->{'offset'}),($self->{'width'} - $self->{'horizontal_padding'} / 3),($self->{'vertical_padding'} + $self->{'helix_height'} - $self->{'offset'}),$dark_grey); $self->{'im'}->filledRectangle(($self->{'horizontal_padding'} / 3) + 1,($self->{'vertical_padding'} + $self->{'offset'}) + 1,($self->{'width'} - $self->{'horizontal_padding'} / 3) - 1,($self->{'vertical_padding'} + $self->{'helix_height'} - $self->{'offset'}) - 1,$dark_grey1); $self->{'im'}->filledRectangle(($self->{'horizontal_padding'} / 3) + 2,($self->{'vertical_padding'} + $self->{'offset'}) + 2,($self->{'width'} - $self->{'horizontal_padding'} / 3) - 2,($self->{'vertical_padding'} + $self->{'helix_height'} - $self->{'offset'}) - 2,$dark_grey2); $self->{'im'}->filledRectangle(($self->{'horizontal_padding'} / 3) + 3,($self->{'vertical_padding'} + $self->{'offset'}) + 3,($self->{'width'} - $self->{'horizontal_padding'} / 3) - 3,($self->{'vertical_padding'} + $self->{'helix_height'} - $self->{'offset'}) - 3,$dark_grey3); ## label membrane if ($self->{'ttf_font'}){ $self->{'im'}->stringFT($self->{'white'},$self->{'ttf_font'},$self->{'ttf_font_size'},0,($self->{'horizontal_padding'} + ($self->{'helix_count'} * $self->{'helix_width'}) + (($self->{'helix_count'} - 1) * $self->{'loop_width'}) + 4) + 1,($self->{'vertical_padding'} + $self->{'helix_height'} - $self->{'offset'} - 3),$self->{'membrane'},{linespacing=>0.6,charmap => 'Unicode',}) if $self->{'labels'}; }else{ $self->{'im'}->string(gdSmallFont,($self->{'horizontal_padding'} + ($self->{'helix_count'} * $self->{'helix_width'}) + (($self->{'helix_count'} - 1) * $self->{'loop_width'}) + 4) + 1,($self->{'vertical_padding'} + $self->{'helix_height'} - $self->{'offset'} - 14),$self->{'membrane'},$self->{'white'}) if $self->{'labels'}; } return $self; } sub draw_helices { my $self = shift; my $x = $self->{'horizontal_padding'}; my $y = $self->{'vertical_padding'}; my ($colour,$colour1,$colour2,$colour3,$colour4,$colour5,$colour6); if($self->{'scheme'} eq 'blue'){ $colour = $self->{'im'}->colorAllocate(90,160,255); $colour1 = $self->{'im'}->colorAllocate(80,150,255); $colour2 = $self->{'im'}->colorAllocate(70,140,255); $colour3 = $self->{'im'}->colorAllocate(60,130,255); $colour4 = $self->{'im'}->colorAllocate(50,120,255); $colour5 = $self->{'im'}->colorAllocate(40,110,255); $colour6 = $self->{'im'}->colorAllocate(30,100,255); }elsif($self->{'scheme'} eq 'pink'){ $colour = $self->{'im'}->colorAllocate(255,1,255); $colour1 = $self->{'im'}->colorAllocate(240,1,255); $colour2 = $self->{'im'}->colorAllocate(230,1,255); $colour3 = $self->{'im'}->colorAllocate(220,1,255); $colour4 = $self->{'im'}->colorAllocate(200,1,255); $colour5 = $self->{'im'}->colorAllocate(180,1,255); $colour6 = $self->{'im'}->colorAllocate(160,1,255); }elsif($self->{'scheme'} eq 'green'){ $colour = $self->{'im'}->colorAllocate(5,240,0); $colour1 = $self->{'im'}->colorAllocate(5,230,0); $colour2 = $self->{'im'}->colorAllocate(5,220,0); $colour3 = $self->{'im'}->colorAllocate(5,205,0); $colour4 = $self->{'im'}->colorAllocate(5,195,0); $colour5 = $self->{'im'}->colorAllocate(5,185,0); $colour6 = $self->{'im'}->colorAllocate(5,155,0); }elsif($self->{'scheme'} eq 'red'){ $colour = $self->{'im'}->colorAllocate(240,0,0); $colour1 = $self->{'im'}->colorAllocate(230,0,0); $colour2 = $self->{'im'}->colorAllocate(220,0,0); $colour3 = $self->{'im'}->colorAllocate(205,0,0); $colour4 = $self->{'im'}->colorAllocate(190,0,0); $colour5 = $self->{'im'}->colorAllocate(170,0,0); $colour6 = $self->{'im'}->colorAllocate(150,0,0); }elsif($self->{'scheme'} eq 'white'){ $colour = $self->{'white'}; $colour1 = $self->{'white'}; $colour2 = $self->{'white'}; $colour3 = $self->{'white'}; $colour4 = $self->{'white'}; $colour5 = $self->{'black'}; $colour6 = $self->{'black'}; }else{ ## default is yellow $colour = $self->{'im'}->colorAllocate(255,235,55); $colour1 = $self->{'im'}->colorAllocate(255,230,50); $colour2 = $self->{'im'}->colorAllocate(255,220,40); $colour3 = $self->{'im'}->colorAllocate(255,210,30); $colour4 = $self->{'im'}->colorAllocate(255,200,20); $colour5 = $self->{'im'}->colorAllocate(255,190,10); $colour6 = $self->{'im'}->colorAllocate(255,180,0); } for (1..$self->{'helix_count'}){ ## draw helix, with graduated fill $self->{'im'}->filledRectangle($x,$y,($x + $self->{'helix_width'})-1,($y + $self->{'helix_height'})-1,$colour6); $self->{'im'}->filledRectangle($x+1,$y+1,($x + $self->{'helix_width'})-1,($y + $self->{'helix_height'})-1,$colour5); $self->{'im'}->filledRectangle($x+2,$y+2,($x + $self->{'helix_width'})-2,($y + $self->{'helix_height'})-2,$colour4); $self->{'im'}->filledRectangle($x+3,$y+3,($x + $self->{'helix_width'})-3,($y + $self->{'helix_height'})-3,$colour3); $self->{'im'}->filledRectangle($x+4,$y+4,($x + $self->{'helix_width'})-4,($y + $self->{'helix_height'})-4,$colour2); $self->{'im'}->filledRectangle($x+5,$y+5,($x + $self->{'helix_width'})-5,($y + $self->{'helix_height'})-5,$colour1); $self->{'im'}->filledRectangle($x+6,$y+6,($x + $self->{'helix_width'})-6,($y + $self->{'helix_height'})-6,$colour); ## draw a white box around it if ($self->{'bold_helices'}){ $self->{'im'}->rectangle($x,$y,($x + $self->{'helix_width'}),($y + $self->{'helix_height'}),$self->{'black'}); $self->{'im'}->rectangle($x - 1,$y - 1,($x + $self->{'helix_width'} + 1),($y + $self->{'helix_height'} + 1),$self->{'white'}); }else{ $self->{'im'}->rectangle($x,$y,($x + $self->{'helix_width'}),($y + $self->{'helix_height'}),$self->{'white'}); } ## this is the text on each helix my $text = substr($self->{'helix_label'},0,1).$_; ## draw a white box in the centre and label the helix my $x_offset = 5; $x_offset = 8 if $_ >= 10; my $white_box = 12; $white_box = 17 if $_ >= 10; $self->{'im'}->filledRectangle(($x + ($self->{'helix_width'} / 2) - $x_offset) - 5,($y + ($self->{'helix_height'} / 2) - 7) - 3,$white_box + ($x + ($self->{'helix_width'} / 2) - $x_offset) + 3,12 + ($y + ($self->{'helix_height'} / 2) - 4),$colour1) if $self->{'labels'}; $self->{'im'}->filledRectangle(($x + ($self->{'helix_width'} / 2) - $x_offset) - 4,($y + ($self->{'helix_height'} / 2) - 7) - 2,$white_box + ($x + ($self->{'helix_width'} / 2) - $x_offset) + 2,12 + ($y + ($self->{'helix_height'} / 2) - 5),$colour2) if $self->{'labels'}; $self->{'im'}->filledRectangle(($x + ($self->{'helix_width'} / 2) - $x_offset) - 3,($y + ($self->{'helix_height'} / 2) - 7) - 1,$white_box + ($x + ($self->{'helix_width'} / 2) - $x_offset) + 1,12 + ($y + ($self->{'helix_height'} / 2) - 6),$colour3) if $self->{'labels'}; $self->{'im'}->filledRectangle(($x + ($self->{'helix_width'} / 2) - $x_offset) - 2,($y + ($self->{'helix_height'} / 2) - 7),$white_box + ($x + ($self->{'helix_width'} / 2) - $x_offset),12 + ($y + ($self->{'helix_height'} / 2) - 7),$self->{'white'}) if $self->{'labels'}; if ($self->{'ttf_font'}){ $self->{'im'}->stringFT($self->{'black'},$self->{'ttf_font'},$self->{'ttf_font_size'},0,($x + ($self->{'helix_width'} / 2) - $x_offset - 1),($y + ($self->{'helix_height'} / 2) + 4),$text,{linespacing=>0.6,charmap => 'Unicode',}) if $self->{'labels'}; }else{ $self->{'im'}->string(gdSmallFont,($x + ($self->{'helix_width'} / 2) - $x_offset),($y + ($self->{'helix_height'} / 2) - 7),$text,$self->{'black'}) if $self->{'labels'}; } ## label start and end positions of helices $self->{'x'} = $x; $self->{'y'} = $y; if ($self->{'labels'}){ if (($self->{'n_term'} eq 'out')&&($_ % 2)){ $self->label_helix_o_i(); }elsif($self->{'n_term'} eq 'out'){ $self->label_helix_i_o(); }elsif(($self->{'n_term'} eq 'in')&&($_ % 2)){ $self->label_helix_i_o(); }else{ $self->label_helix_o_i(); } } $x = $self->{'horizontal_padding'} + ($_ * ($self->{'helix_width'} + $self->{'loop_width'})); } if ($self->{'labels'}){ $x = $self->{'horizontal_padding'}; $y = $self->{'vertical_padding'}; for (1..$self->{'helix_count'}){ my $y_mod = 0; foreach my $l (sort {$b <=> $a} keys %{$self->{'loop_labels'}}){ if (($l >= $self->{'helix_span'}{$_}{'start'})&&($l <= $self->{'helix_span'}{$_}{'stop'})){ my $label_length = 0; if ($self->{'ttf_font'}){ ## Might need to fiddle with this my $size_dif = $self->{'ttf_font_size'} - 8; $label_length = 6 + (6 * (length $self->{'loop_labels'}{$l}) + (8 * $size_dif)); }else{ $label_length = 9 + (6 * length $self->{'loop_labels'}{$l}); } if ($_ % 2){ if ($self->{'bold_labels'}){ $self->{'im'}->filledRectangle(($x + ($self->{'helix_width'} / 2)) + 5,($y + ($self->{'helix_height'} / 2) - 30) + $y_mod,$label_length + ($x + ($self->{'helix_width'} / 2)) + 2,12 + ($y + ($self->{'helix_height'} / 2) - 24) + $y_mod,$self->{'black'}); my $b = new GD::Polygon; $b->addPt(($x + ($self->{'helix_width'} / 2)) + 4,($y + ($self->{'helix_height'} / 2) - 30) + $y_mod,$label_length + ($x + ($self->{'helix_width'} / 2))); $b->addPt(($x + ($self->{'helix_width'} / 2)) - 5,($y + ($self->{'helix_height'} / 2) - 21) + $y_mod,$label_length + ($x + ($self->{'helix_width'} / 2))); $b->addPt(($x + ($self->{'helix_width'} / 2)) + 4,($y + ($self->{'helix_height'} / 2) - 12) + $y_mod,$label_length + ($x + ($self->{'helix_width'} / 2))); $self->{'im'}->filledPolygon($b,$self->{'black'}); } ## add darker box $self->{'im'}->filledRectangle(($x + ($self->{'helix_width'} / 2)) + 6,($y + ($self->{'helix_height'} / 2) - 29) + $y_mod,$label_length + ($x + ($self->{'helix_width'} / 2)) + 1,12 + ($y + ($self->{'helix_height'} / 2) - 25) + $y_mod,$colour6); ## add white box $self->{'im'}->filledRectangle(($x + ($self->{'helix_width'} / 2)) + 7,($y + ($self->{'helix_height'} / 2) - 28) + $y_mod,$label_length + ($x + ($self->{'helix_width'} / 2)),12 + ($y + ($self->{'helix_height'} / 2) - 26) + $y_mod,$self->{'white'}); ## draw darker arrowhead my $poly = new GD::Polygon; $poly->addPt(($x + ($self->{'helix_width'} / 2)) + 5,($y + ($self->{'helix_height'} / 2) - 29) + $y_mod,$label_length + ($x + ($self->{'helix_width'} / 2))); $poly->addPt(($x + ($self->{'helix_width'} / 2)) - 3,($y + ($self->{'helix_height'} / 2) - 21) + $y_mod,$label_length + ($x + ($self->{'helix_width'} / 2))); $poly->addPt(($x + ($self->{'helix_width'} / 2)) + 5,($y + ($self->{'helix_height'} / 2) - 13) + $y_mod,$label_length + ($x + ($self->{'helix_width'} / 2))); $self->{'im'}->filledPolygon($poly,$colour6); ## draw white arrowhead my $poly2 = new GD::Polygon; $poly2->addPt(($x + ($self->{'helix_width'} / 2)) + 6,($y + ($self->{'helix_height'} / 2) - 28) + $y_mod,$label_length + ($x + ($self->{'helix_width'} / 2))); $poly2->addPt(($x + ($self->{'helix_width'} / 2)) - 1,($y + ($self->{'helix_height'} / 2) - 21) + $y_mod,$label_length + ($x + ($self->{'helix_width'} / 2))); $poly2->addPt(($x + ($self->{'helix_width'} / 2)) + 6,($y + ($self->{'helix_height'} / 2) - 14) + $y_mod,$label_length + ($x + ($self->{'helix_width'} / 2))); $self->{'im'}->filledPolygon($poly2,$self->{'white'}); ## add label if ($self->{'ttf_font'}){ $self->{'im'}->stringFT($self->{'black'},$self->{'ttf_font'},$self->{'ttf_font_size'},0,($x + ($self->{'helix_width'} / 2)) + 10,($y + ($self->{'helix_height'} / 2) - 16) + $y_mod,$self->{'loop_labels'}{$l},{linespacing=>0.6,charmap => 'Unicode',}); }else{ $self->{'im'}->string(gdSmallFont,($x + ($self->{'helix_width'} / 2)) + 9,($y + ($self->{'helix_height'} / 2) - 27) + $y_mod,$self->{'loop_labels'}{$l},$self->{'black'}); } $y_mod = $y_mod - 19; }else{ if ($self->{'bold_labels'}){ $self->{'im'}->filledRectangle(($x + ($self->{'helix_width'} / 2)) + 5,($y + ($self->{'helix_height'} / 2) + 9) + $y_mod,$label_length + ($x + ($self->{'helix_width'} / 2)) + 2,13 + ($y + ($self->{'helix_height'} / 2) + 15) + $y_mod,$self->{'black'}); my $b = new GD::Polygon; $b->addPt(($x + ($self->{'helix_width'} / 2)) + 4,($y + ($self->{'helix_height'} / 2) + 10) + $y_mod,$label_length + ($x + ($self->{'helix_width'} / 2))); $b->addPt(($x + ($self->{'helix_width'} / 2)) - 5,($y + ($self->{'helix_height'} / 2) + 19) + $y_mod,$label_length + ($x + ($self->{'helix_width'} / 2))); $b->addPt(($x + ($self->{'helix_width'} / 2)) + 4,($y + ($self->{'helix_height'} / 2) + 28) + $y_mod,$label_length + ($x + ($self->{'helix_width'} / 2))); $self->{'im'}->filledPolygon($b,$self->{'black'}); } ## add darker box $self->{'im'}->filledRectangle(($x + ($self->{'helix_width'} / 2)) + 6,($y + ($self->{'helix_height'} / 2) + 11) + $y_mod,$label_length + ($x + ($self->{'helix_width'} / 2)) + 1,12 + ($y + ($self->{'helix_height'} / 2) + 15) + $y_mod,$colour6); ## add white box $self->{'im'}->filledRectangle(($x + ($self->{'helix_width'} / 2)) + 7,($y + ($self->{'helix_height'} / 2) + 12) + $y_mod,$label_length + ($x + ($self->{'helix_width'} / 2)),12 + ($y + ($self->{'helix_height'} / 2) + 14) + $y_mod,$self->{'white'}); ## draw darker arrowhead my $poly = new GD::Polygon; $poly->addPt(($x + ($self->{'helix_width'} / 2)) + 5,($y + ($self->{'helix_height'} / 2) + 11) + $y_mod,$label_length + ($x + ($self->{'helix_width'} / 2))); $poly->addPt(($x + ($self->{'helix_width'} / 2)) - 3,($y + ($self->{'helix_height'} / 2) + 19) + $y_mod,$label_length + ($x + ($self->{'helix_width'} / 2))); $poly->addPt(($x + ($self->{'helix_width'} / 2)) + 5,($y + ($self->{'helix_height'} / 2) + 27) + $y_mod,$label_length + ($x + ($self->{'helix_width'} / 2))); $self->{'im'}->filledPolygon($poly,$colour6); ## draw white arrowhead my $poly2 = new GD::Polygon; $poly2->addPt(($x + ($self->{'helix_width'} / 2)) + 6,($y + ($self->{'helix_height'} / 2) + 12) + $y_mod,$label_length + ($x + ($self->{'helix_width'} / 2))); $poly2->addPt(($x + ($self->{'helix_width'} / 2)) - 1,($y + ($self->{'helix_height'} / 2) + 19) + $y_mod,$label_length + ($x + ($self->{'helix_width'} / 2))); $poly2->addPt(($x + ($self->{'helix_width'} / 2)) + 6,($y + ($self->{'helix_height'} / 2) + 26) + $y_mod,$label_length + ($x + ($self->{'helix_width'} / 2))); $self->{'im'}->filledPolygon($poly2,$self->{'white'}); ## add label if ($self->{'ttf_font'}){ $self->{'im'}->stringFT($self->{'black'},$self->{'ttf_font'},$self->{'ttf_font_size'},0,($x + ($self->{'helix_width'} / 2)) + 10,($y + ($self->{'helix_height'} / 2) + 24) + $y_mod,$self->{'loop_labels'}{$l},{linespacing=>0.6,charmap => 'Unicode',}); }else{ $self->{'im'}->string(gdSmallFont,($x + ($self->{'helix_width'} / 2)) + 9,($y + ($self->{'helix_height'} / 2) + 12) + $y_mod,$self->{'loop_labels'}{$l},$self->{'black'}); } $y_mod = $y_mod + 19; } } } $x = $self->{'horizontal_padding'} + ($_ * ($self->{'helix_width'} + $self->{'loop_width'})); } } return $self; } sub draw_terminai { my $self = shift; my $loop_number = ($self->{'helix_count'} - 1); ## width of terminal $self->{'w'} = $self->{'helix_width'} + $self->{'loop_width'}; $self->{'cx'} = $self->{'horizontal_padding'} - ($self->{'loop_width'} / 2); $self->{'cy'} = $self->{'vertical_padding'}; ## draw N-terminal if ($self->{'n_term'} eq 'out'){ $self->{'im'}->arc(($self->{'cx'} - $self->{'n_term_offset'}),$self->{'cy'},($self->{'w'} + (2 * $self->{'n_term_offset'})),$self->{'n_terminal_height'},270,360,$self->{'black'}); if ($self->{'ttf_font'}){ $self->{'im'}->stringFT($self->{'black'},$self->{'ttf_font'},$self->{'ttf_font_size'},0,($self->{'horizontal_padding'} - ($self->{'w'} / 2) - 33 - $self->{'n_term_offset'}),($self->{'cy'} - ($self->{'n_terminal_height'} / 2) + 5),$self->{'n_term_label'},{linespacing=>0.6,charmap => 'Unicode',}) if $self->{'labels'}; }else{ $self->{'im'}->string(gdSmallFont,($self->{'horizontal_padding'} - ($self->{'w'} / 2) - 40 - $self->{'n_term_offset'}),($self->{'cy'} - ($self->{'n_terminal_height'} / 2) - 6),$self->{'n_term_label'},$self->{'black'}) if $self->{'labels'}; } ## label n-terminal my $y_mod = 0; foreach (sort {$b <=> $a} keys %{$self->{'loop_labels'}}){ if ($_ <= $self->{'helix_span'}{1}{'start'}){ if ($self->{'ttf_font'}){ $self->{'im'}->stringFT($self->{'black'},$self->{'ttf_font'},$self->{'ttf_font_size'},0,($self->{'horizontal_padding'} - ($self->{'w'} / 2) - 33 - $self->{'n_term_offset'}),($self->{'cy'} - ($self->{'n_terminal_height'} / 2) - 6) - 4 + $y_mod,$self->{'loop_labels'}{$_},{linespacing=>0.6,charmap => 'Unicode',}) if $self->{'labels'}; }else{ $self->{'im'}->string(gdSmallFont,($self->{'horizontal_padding'} - ($self->{'w'} / 2) - 40 - $self->{'n_term_offset'}),($self->{'cy'} - ($self->{'n_terminal_height'} / 2) - 6) - 15 + $y_mod,$self->{'loop_labels'}{$_},$self->{'black'}) if $self->{'labels'}; } $y_mod = $y_mod - 15; } } }else{ $self->{'cy'} = $self->{'cy'} + $self->{'helix_height'}; $self->{'im'}->arc(($self->{'cx'} - $self->{'n_term_offset'}),$self->{'cy'},($self->{'w'} + (2 * $self->{'n_term_offset'})),$self->{'n_terminal_height'},0,90,$self->{'black'}); if ($self->{'ttf_font'}){ $self->{'im'}->stringFT($self->{'black'},$self->{'ttf_font'},$self->{'ttf_font_size'},0,($self->{'horizontal_padding'} - ($self->{'w'} / 2) - 33 - $self->{'n_term_offset'}),($self->{'cy'} + ($self->{'n_terminal_height'} / 2) + 5),$self->{'n_term_label'},{linespacing=>0.6,charmap => 'Unicode',}) if $self->{'labels'}; }else{ $self->{'im'}->string(gdSmallFont,($self->{'horizontal_padding'} - ($self->{'w'} / 2) - 40 - $self->{'n_term_offset'}),($self->{'cy'} + ($self->{'n_terminal_height'} / 2) - 6),$self->{'n_term_label'},$self->{'black'}) if $self->{'labels'}; } ## label n-terminal my $y_mod = 0; foreach (sort {$a <=> $b} keys %{$self->{'loop_labels'}}){ if ($_ <= $self->{'helix_span'}{1}{'start'}){ if ($self->{'ttf_font'}){ $self->{'im'}->stringFT($self->{'black'},$self->{'ttf_font'},$self->{'ttf_font_size'},0,($self->{'horizontal_padding'} - ($self->{'w'} / 2) - 33 - $self->{'n_term_offset'}),($self->{'cy'} + ($self->{'n_terminal_height'} / 2) - 6) + 26 + $y_mod,$self->{'loop_labels'}{$_},{linespacing=>0.6,charmap => 'Unicode',}) if $self->{'labels'}; }else{ $self->{'im'}->string(gdSmallFont,($self->{'horizontal_padding'} - ($self->{'w'} / 2) - 40 - $self->{'n_term_offset'}),($self->{'cy'} + ($self->{'n_terminal_height'} / 2) - 6) + 15 + $y_mod,$self->{'loop_labels'}{$_},$self->{'black'}) if $self->{'labels'}; } $y_mod = $y_mod + 15; } } } $self->{'cx'} = ($self->{'helix_count'} * $self->{'helix_width'}) + (($self->{'helix_count'} - 1) * $self->{'loop_width'}) + $self->{'horizontal_padding'} + ($self->{'loop_width'} / 2); ## draw C-terminal if (($self->{'n_term'} eq 'out')&&($loop_number % 2)){ $self->draw_ext_c_term; }elsif($self->{'n_term'} eq 'out'){ $self->draw_int_c_term; }elsif(($self->{'n_term'} eq 'in')&&($loop_number % 2)){ $self->draw_int_c_term; }elsif($self->{'n_term'} eq 'in'){ $self->draw_ext_c_term; } return $self; } sub draw_loops { my $self = shift; $self->{'x'} = $self->{'horizontal_padding'} + ($self->{'helix_width'} / 2); $self->{'h'} = $self->{'medium_loop'}; $self->{'w'} = $self->{'helix_width'} + $self->{'loop_width'}; for (1..($self->{'helix_count'} - 1)){ ## Alter loop height according to its actual length if ($self->{'loop_length'}{$_} < $self->{'short_loop_limit'}){ $self->{'h'} = $self->{'short_loop'}; }elsif($self->{'loop_length'}{$_} > $self->{'long_loop_limit'}){ $self->{'h'} = $self->{'long_loop'}; } $self->{'l_start'} = $self->{'helix_span'}{$_}{'stop'}; $self->{'l_stop'} = $self->{'helix_span'}{$_ + 1}{'start'}; if (($self->{'n_term'} eq 'out')&&($_ % 2)){ $self->draw_int_loop; }elsif($self->{'n_term'} eq 'out'){ $self->draw_ext_loop; }elsif(($self->{'n_term'} eq 'in')&&($_ % 2)){ $self->draw_ext_loop; }elsif($self->{'n_term'} eq 'in'){ $self->draw_int_loop; } $self->{'x'} = $self->{'x'} + $self->{'helix_width'} + $self->{'loop_width'}; $self->{'x'} = $self->{'horizontal_padding'} if $_ == ($self->{'helix_count'} - 1); } return $self; } sub label_helix_o_i { my $self = shift; my $offset = 6; $offset = 3 if $self->{'helix_span'}{$_}{'start'} < 100; if ($self->{'ttf_font'}){ $self->{'im'}->stringFT($self->{'black'},$self->{'ttf_font'},$self->{'ttf_font_size'},0,($self->{'x'} + ($self->{'helix_width'} / 2) - $offset) - 1,$self->{'y'} + 13,$self->{'helix_span'}{$_}{'start'},{linespacing=>0.6,charmap => 'Unicode',}); }else{ $self->{'im'}->string(gdSmallFont,($self->{'x'} + ($self->{'helix_width'} / 2) - $offset) - 1,$self->{'y'} + 1,$self->{'helix_span'}{$_}{'start'},$self->{'black'}); } $offset = 3 if $self->{'helix_span'}{$_}{'stop'} < 100; $offset = 6 if $self->{'helix_span'}{$_}{'stop'} >= 100; if ($self->{'ttf_font'}){ $self->{'im'}->stringFT($self->{'black'},$self->{'ttf_font'},$self->{'ttf_font_size'},0,($self->{'x'} + ($self->{'helix_width'} / 2) - $offset) - 1,($self->{'y'} + $self->{'helix_height'} - 3),$self->{'helix_span'}{$_}{'stop'},{linespacing=>0.6,charmap => 'Unicode',}); }else{ $self->{'im'}->string(gdSmallFont,($self->{'x'} + ($self->{'helix_width'} / 2) - $offset) - 1,($self->{'y'} + $self->{'helix_height'} - 14),$self->{'helix_span'}{$_}{'stop'},$self->{'black'}); } return $self; } sub label_helix_i_o { my $self = shift; my $offset = 6; $offset = 3 if $self->{'helix_span'}{$_}{'stop'} < 100; if ($self->{'ttf_font'}){ $self->{'im'}->stringFT($self->{'black'},$self->{'ttf_font'},$self->{'ttf_font_size'},0,($self->{'x'} + ($self->{'helix_width'} / 2) - $offset) - 1,$self->{'y'} + 13,$self->{'helix_span'}{$_}{'stop'},{linespacing=>0.6,charmap => 'Unicode',}); }else{ $self->{'im'}->string(gdSmallFont,($self->{'x'} + ($self->{'helix_width'} / 2) - $offset) - 1,$self->{'y'} + 1,$self->{'helix_span'}{$_}{'stop'},$self->{'black'}); } $offset = 3 if $self->{'helix_span'}{$_}{'start'} < 100; if ($self->{'ttf_font'}){ $self->{'im'}->stringFT($self->{'black'},$self->{'ttf_font'},$self->{'ttf_font_size'},0,($self->{'x'} + ($self->{'helix_width'} / 2) - $offset) - 1,($self->{'y'} + $self->{'helix_height'} - 3),$self->{'helix_span'}{$_}{'start'},{linespacing=>0.6,charmap => 'Unicode',}); }else{ $self->{'im'}->string(gdSmallFont,($self->{'x'} + ($self->{'helix_width'} / 2) - $offset) - 1,($self->{'y'} + $self->{'helix_height'} - 14),$self->{'helix_span'}{$_}{'start'},$self->{'black'}); } return $self; } sub draw_int_c_term { my $self = shift; ## draw internal c-terminal $self->{'im'}->arc(($self->{'cx'} + $self->{'c_term_offset'}),($self->{'vertical_padding'} + $self->{'helix_height'}),($self->{'w'} + (2 * $self->{'c_term_offset'})),$self->{'c_terminal_height'},90,180,$self->{'black'}); if ($self->{'ttf_font'}){ $self->{'im'}->stringFT($self->{'black'},$self->{'ttf_font'},$self->{'ttf_font_size'},0,($self->{'cx'} + 4 + $self->{'c_term_offset'}),(($self->{'vertical_padding'} + $self->{'helix_height'}) + ($self->{'c_terminal_height'} / 2) + 5),$self->{'c_term_label'},{linespacing=>0.6,charmap => 'Unicode',}) if $self->{'labels'}; }else{ $self->{'im'}->string(gdSmallFont,($self->{'cx'} + 3 + $self->{'c_term_offset'}),(($self->{'vertical_padding'} + $self->{'helix_height'}) + ($self->{'c_terminal_height'} / 2) - 6),$self->{'c_term_label'},$self->{'black'}) if $self->{'labels'}; } ## label terminal if ($self->{'labels'}){ my $y_mod = 0; foreach (sort {$a <=> $b} keys %{$self->{'loop_labels'}}){ if ($_ >= $self->{'helix_span'}{$self->{'helix_count'}}{'stop'}){ if ($self->{'ttf_font'}){ $self->{'im'}->stringFT($self->{'black'},$self->{'ttf_font'},$self->{'ttf_font_size'},0,($self->{'cx'} + 4 + $self->{'c_term_offset'}),(($self->{'vertical_padding'} + $self->{'helix_height'}) + ($self->{'c_terminal_height'} / 2) - 6) + 26 + $y_mod,$self->{'loop_labels'}{$_},{linespacing=>0.6,charmap => 'Unicode',}) if $self->{'labels'}; }else{ $self->{'im'}->string(gdSmallFont,($self->{'cx'} + 3 + $self->{'c_term_offset'}),(($self->{'vertical_padding'} + $self->{'helix_height'}) + ($self->{'c_terminal_height'} / 2) - 6) + 15 + $y_mod,$self->{'loop_labels'}{$_},$self->{'black'}); } $y_mod = $y_mod + 15; } } } return $self; } sub draw_ext_c_term { my $self = shift; ## draw external c-terminal $self->{'im'}->arc(($self->{'cx'} + $self->{'c_term_offset'}),$self->{'vertical_padding'},($self->{'w'} + (2 * $self->{'c_term_offset'})),$self->{'c_terminal_height'},180,270,$self->{'black'}); if ($self->{'ttf_font'}){ $self->{'im'}->stringFT($self->{'black'},$self->{'ttf_font'},$self->{'ttf_font_size'},0,,($self->{'cx'} + 3 + $self->{'c_term_offset'}),($self->{'vertical_padding'} - ($self->{'c_terminal_height'} / 2) + 5),$self->{'c_term_label'},{linespacing=>0.6,charmap => 'Unicode',}) if $self->{'labels'}; }else{ $self->{'im'}->string(gdSmallFont,($self->{'cx'} + 3 + $self->{'c_term_offset'}),($self->{'vertical_padding'} - ($self->{'c_terminal_height'} / 2) - 6),$self->{'c_term_label'},$self->{'black'}) if $self->{'labels'}; } ## label terminal if ($self->{'labels'}){ my $y_mod = 0; foreach (sort {$b <=> $a} keys %{$self->{'loop_labels'}}){ if ($_ >= $self->{'helix_span'}{$self->{'helix_count'}}{'stop'}){ if ($self->{'ttf_font'}){ $self->{'im'}->stringFT($self->{'black'},$self->{'ttf_font'},$self->{'ttf_font_size'},0,($self->{'cx'} + 3 + $self->{'c_term_offset'}),($self->{'vertical_padding'} - ($self->{'c_terminal_height'} / 2) - 6) - 4 - $y_mod,$self->{'loop_labels'}{$_},{linespacing=>0.6,charmap => 'Unicode',}) if $self->{'labels'}; }else{ $self->{'im'}->string(gdSmallFont,($self->{'cx'} + 3 + $self->{'c_term_offset'}),($self->{'vertical_padding'} - ($self->{'c_terminal_height'} / 2) - 6) - 15 - $y_mod,$self->{'loop_labels'}{$_},$self->{'black'}); } $y_mod = $y_mod + 15; } } } return $self; } sub draw_int_loop { my $self = shift; ## draw internal loop $self->{'cx'} = $self->{'x'} + ($self->{'helix_width'} / 2) + ($self->{'loop_width'} / 2); ## this sets the height to the value given by the loop_heights hash foreach (sort {$a <=> $b} keys %{$self->{'loop_heights'}}){ $self->{'h'} = $self->{'loop_heights'}{$_} if $_ == $self->{'loop_count'}; } $self->{'im'}->arc($self->{'cx'},($self->{'vertical_padding'} + $self->{'helix_height'}),$self->{'w'},$self->{'h'},0,180,$self->{'black'}); ## label loop if ($self->{'labels'}){ my $y_mod = 0; foreach (sort {$a <=> $b} keys %{$self->{'loop_labels'}}){ if (($_ >= $self->{'l_start'})&&($_ <= $self->{'l_stop'})){ $self->{'im'}->line($self->{'cx'},($self->{'vertical_padding'} + $self->{'helix_height'} + ($self->{'h'} / 2)),$self->{'cx'},($self->{'vertical_padding'} + $self->{'helix_height'} + ($self->{'h'} / 2) + 5),$self->{'black'}) unless $y_mod; if ($self->{'ttf_font'}){ $self->{'im'}->stringFT($self->{'black'},$self->{'ttf_font'},$self->{'ttf_font_size'},0,($self->{'cx'} + $self->{'text_offset'} - 3),($self->{'vertical_padding'} + $self->{'helix_height'} + ($self->{'h'} / 2) + 17) + $y_mod,$self->{'loop_labels'}{$_},{linespacing=>0.6,charmap => 'Unicode',}) if $self->{'labels'}; }else{ $self->{'im'}->string(gdSmallFont,($self->{'cx'} + $self->{'text_offset'}),($self->{'vertical_padding'} + $self->{'helix_height'} + ($self->{'h'} / 2) + 6) + $y_mod,$self->{'loop_labels'}{$_},$self->{'black'}) ; } $y_mod = $y_mod + 15; } } } $self->{'loop_count'}++; return $self; } sub draw_ext_loop { my $self = shift; ## draw external loop $self->{'cx'} = $self->{'x'} + ($self->{'helix_width'} / 2) + ($self->{'loop_width'} / 2); ## this sets the height to the value given by the loop_heights hash foreach (sort {$a <=> $b} keys %{$self->{'loop_heights'}}){ $self->{'h'} = $self->{'loop_heights'}{$_} if $_ == $self->{'loop_count'}; } $self->{'im'}->arc($self->{'cx'},$self->{'vertical_padding'},$self->{'w'},$self->{'h'},180,360,$self->{'black'}); ## label loop if ($self->{'labels'}){ my $y_mod = 0; foreach (sort {$b <=> $a} keys %{$self->{'loop_labels'}}){ if (($_ >= $self->{'l_start'})&&($_ <= $self->{'l_stop'})){ $self->{'im'}->line($self->{'cx'},($self->{'vertical_padding'} - ($self->{'h'} / 2)),$self->{'cx'},($self->{'vertical_padding'} - ($self->{'h'} / 2) - 5),$self->{'black'}) unless $y_mod; if ($self->{'ttf_font'}){ $self->{'im'}->stringFT($self->{'black'},$self->{'ttf_font'},$self->{'ttf_font_size'},0,($self->{'cx'} + $self->{'text_offset'} - 3),($self->{'vertical_padding'} - ($self->{'h'} / 2) - 8) + $y_mod,$self->{'loop_labels'}{$_},{linespacing=>0.6,charmap => 'Unicode',}) if $self->{'labels'}; }else{ $self->{'im'}->string(gdSmallFont,($self->{'cx'} + $self->{'text_offset'}),($self->{'vertical_padding'} - ($self->{'h'} / 2) - 19) + $y_mod,$self->{'loop_labels'}{$_},$self->{'black'}); } $y_mod = $y_mod - 15; } } } $self->{'loop_count'}++; return $self; } 1; =head1 NAME Bio::Graphics::DrawTransmembrane - draw a cartoon of an Alpha-helical transmembrane protein. =head1 SYNOPSIS use Bio::Graphics::DrawTransmembrane; my @topology = (20,45,59,70,86,109,145,168,194,220); ## Simple use - -topology is the only option that is required my $im = Bio::Graphics::DrawTransmembrane->new( -title => 'This is a cartoon displaying transmembrane helices.', -topology => \@topology); ## More advanced use my %labels = (5 => '5 - Sulphation Site', 21 => '1st Helix', 47 => '40 - Mutation', 60 => 'Voltage Sensor', 72 => '72 - Mutation 2', 73 => '73 - Mutation 3', 138 => '138 - Glycosylation Site', 170 => '170 - Phosphorylation Site', 200 => 'Last Helix'); my $im = Bio::Graphics::DrawTransmembrane->new(-n_terminal=> 'out', -topology => \@topology, -bold_helices=> 1, -labels=> \%labels, -text_offset=> -15, -outside_label=>'Lumen', -inside_label=>'Cytoplasm', -membrane_label=>'Membrane', -vertical_padding=> 155); ## Parse Tmhmm data use Bio::Tools::Tmhmm; my $im = Bio::Graphics::DrawTransmembrane->new( -title=>'Let\'s parse some Tmhmm output...', -bold_helices=> 1); open(FILE, 'tmhmm.out'); my $parser = new Bio::Tools::Tmhmm(-fh => \*FILE ); while(my $tmhmm_feat = $parser->next_result ) { ## Load features into DrawTransmembrane object $im->add_tmhmm_feat($tmhmm_feat); } close FILE; ## Now write the image to a .png file open(OUTPUT, ">output.png"); binmode OUTPUT; print OUTPUT $im->png; close OUTPUT; =head1 DESCRIPTION A module to draw a cartoon of an alpha-helical transmembrane protein. It uses GD and allows the image to be written to a .png file. The options are a set of tag/value pairs as follows: Option Value Default ------ ----- ------- -topology Array containing transmembrane helix none boundaries. This is the only option that is required -topology_string Alternative to -topology, provide a string none containing the topology data in the form A.11,31;B.41,59;C.86,107;D.145,166 -n_terminal Location of the N-terminal of the sequence, out either 'in' or 'out' -title Title to add to the image none -inside_label Label for the inside of the membrane Cytoplasmic -outside_label Label for the outside of the membrane Extracellular -membrane_label Label for the membrane Plasma Membrane -colour_scheme Colour scheme to use. Current choices are blue blue, yellow, red, green, pink or white. -labels Label loops and helices using data from a none hash, e.g. %labels = (138 => 'Glycosylation Site', 190 => 'Binding Site'); The hash key must be numeric, ranges are not allowed. -bold_helices Draws black boxes round helices 1 -bold_labels Draws black boxes round labels 0 -text_offset Shift the text labeling the loops. Use a 0 negative value to shift it left, a positive value to shift it right -helix_height Transmembrane helix height 130 -helix_width Transmembrane helix width 50 -loop_width Loop width 20 -vertical_padding Vertical padding 140 -horizontal_padding Horizontal Padding 150 -membrane_offset Offest between helix end and membrane 6 -short_loop_height Height of short loops 90 -medium_loop_height Height of medium loops 120 -long_loop_height Height of long loops 150 -short_loop_limit Length in residues below which a loop is 15 classed as short -long_loop_limit Length in residues above which a loop is 30 classed as long -loop_heights Explicitly set heights of each loop, e.g. %loop_heights = (1 => 45, 2 => 220, 3 => 50, 4 => 220, 9 => 70); The key corresponds to the loop number. Both key and value must be numeric. If you use -loop_height and there is a defined height for the current loop then other height values will be overridden -n_terminal_height Height of N-terminal 150 -c_terminal_height Height of C-terminal 80 -n_terminal_offset Shift the N-terminal left by this amount 0 -c_terminal_offset Shift the C-terminal right by this amount 0 -helix_label Change the 'S' label on each helix. Only 1 S character is allowed -show_labels Display text labels on -draw_cytosol Show the cytosol false -draw_bilayer Show the membrane true -draw_loops Show the loops true -draw_terminai Show the terminai true -draw_helices Show the helices true -dontsort Don't automatically sort the topology array 0 -ttf_font Path to TTF font, e.g. none /usr/share/fonts/msttcorefonts/arial.ttf -ttf_font_size Default size for TTF font. Use 7-9 with 8 Arial for best results Height, width, padding and other numerical values can gernerally be left alone. They are useful if your labels consists of a lot of text as this may lead to them overlapping. In this case try increasing the loop_width or helix_width options. -text_offset is also very useful for avoiding overlapping. =head1 AUTHOR Tim Nugent Etimnugent@gmail.comE =cut Bio-Graphics-2.37/lib/Bio/Graphics/RendererI.pm000555001750001750 512612165075746 21310 0ustar00lsteinlstein000000000000=head1 NAME Bio::Graphics::RendererI - A renderer for the Bio::Graphics class that renders Bio::SeqFeature::CollectionI objects onto Bio::Graphics::Panels using configuration information provided by a Bio::Graphics::ConfiguratorI. =head1 SYNOPSIS # Get a renderer somehow, called $renderer # create a new panel and render contents a feature collection onto it my $config = new ConfigIO( $config_file )->getConfig(); my $features = $data_provider->getCollection(); my ( $tracks_rendered, $panel ) = $renderer->render( $features, $prefs ); =head1 DESCRIPTION Renderer of Bio::SeqFeature::CollectionIs (collections of features) onto a Bio::Graphics::Panel using a Bio::Graphics::ConfiguratorI for general and track-specific rendering options. =head1 FEEDBACK =head2 Mailing Lists User feedback is an integral part of the evolution of this and other Bioperl modules. Send your comments and suggestions preferably to the Bioperl mailing list. Your participation is much appreciated. bioperl-l@bioperl.org - General discussion http://bioperl.org/wiki/Mailing_lists - About the mailing lists =head2 Reporting Bugs Report bugs to the Bioperl bug tracking system to help us keep track of the bugs and their resolution. Bug reports can be submitted via the web: http://bugzilla.open-bio.org/ =head1 AUTHOR Paul Edlefsen Epaul@systemsbiology.orgE. Copyright (c) 2003 Institute for Systems Biology This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =head1 APPENDIX The rest of the documentation details each of the object methods. Internal methods are usually preceded with a _ =cut # Let the code begin... package Bio::Graphics::RendererI; use strict; use base qw(Bio::Root::RootI); =head2 render Title : render Usage : ( $rendered, $panel ) = $renderer->render( $collection, $configurator [, $panel ] ); Function: Renders the SeqFeatures in the given collection onto a Bio::Graphics::Panel (if no panel is given, one will be created), using the given Bio::Graphics::ConfiguratorI for general and track-specific rendering options. Returns : In a scalar context returns the number of tracks rendered. In a list context, returns a two-element list containing the number of features rendered and the panel. Args : A Bio::SeqFeature::CollectionI and a Bio::Graphics::ConfiguratorI and optionally a Bio::Graphics::Panel. Status : Public =cut sub render { shift->throw_not_implemented(); } 1; Bio-Graphics-2.37/lib/Bio/Graphics/FeatureBase.pm000555001750001750 127612165075746 21621 0ustar00lsteinlstein000000000000package Bio::Graphics::FeatureBase; =head1 NAME Bio::Graphics::FeatureBase - Compatibility module =head1 SYNOPSIS This module has been replaced by Bio::SeqFeature::Lite but exists only for compatibility with legacy applications. =cut use strict; use base 'Bio::SeqFeature::Lite'; 1; __END__ =head1 SEE ALSO L, L, L,L, L =head1 AUTHOR Lincoln Stein Elstein@cshl.orgE. Copyright (c) 2006 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Util.pm000555001750001750 173512165075746 20350 0ustar00lsteinlstein000000000000package Bio::Graphics::Util; # Non object-oriented utilities used here-and-there in Bio::Graphics modules =head1 NAME Bio::Graphics::Util - non-object-oriented utilities used in Bio::Graphics modules =cut use strict; require Exporter; use base qw(Exporter); use vars '@EXPORT','@EXPORT_OK'; @EXPORT = 'frame_and_offset'; use Bio::Root::Version; =over 4 =item ($frame,$offset) = frame_and_offset($pos,$strand,$phase) Calculate the reading frame for a given genomic position, strand and phase. The offset is the offset from $pos to the first nucleotide of the reading frame. In a scalar context, returns the frame only. =back =cut sub frame_and_offset { my ($pos,$strand,$phase) = @_; $strand ||= +1; $phase ||= 0; my $codon_start = $strand >= 0 ? $pos + $phase : $pos - $phase; # probably wrong my $frame = ($codon_start-1) % 3; my $offset = $strand >= 0 ? $phase : -$phase; return wantarray ? ($frame,$offset) : $frame; } 1; Bio-Graphics-2.37/lib/Bio/Graphics/Pictogram.pm000555001750001750 3300212165075746 21370 0ustar00lsteinlstein000000000000# BioPerl module for Bio::Graphics::Pictogram # # Cared for by Shawn Hoon # # Copyright Shawn Hoon # # You may distribute this module under the same terms as perl itself # POD documentation - main docs before the code =head1 NAME Bio::Graphics::Pictogram - generate SVG output of Pictogram display for consensus motifs =head1 SYNOPSIS use Bio::Graphics::Pictogram; use Bio::SeqIO; my $sio = Bio::SeqIO->new(-file=>$ARGV[0],-format=>'fasta'); my @seq; while(my $seq = $sio->next_seq){ push @seq, $seq; } my $picto = Bio::Graphics::Pictogram->new(-width=>"800", -height=>"500", -fontsize=>"60", -plot_bits=>1, -background=>{ 'A'=>0.25, 'C'=>0.18, 'T'=>0.32, 'G'=>0.25}, -color=>{'A'=>'red', 'G'=>'blue', 'C'=>'green', 'T'=>'magenta'}); my $svg = $picto->make_svg(\@seq); print $svg->xmlify."\n"; #Support for Bio::Matrix::PSM::SiteMatrix now included use Bio::Matrix::PSM::IO; my $picto = Bio::Graphics::Pictogram->new(-width=>"800", -height=>"500", -fontsize=>"60", -plot_bits=>1, -background=>{ 'A'=>0.25, 'C'=>0.18, 'T'=>0.32, 'G'=>0.25}, -color=>{'A'=>'red', 'G'=>'blue', 'C'=>'green', 'T'=>'magenta'}); my $psm = $psmIO->next_psm; my $svg = $picto->make_svg($psm); print $svg->xmlify; =head1 DESCRIPTION A module for generating SVG output of Pictogram display for consensus motifs. This method of representation was describe by Burge and colleagues: (Burge, C.B.,Tuschl, T., Sharp, P.A. in The RNA world II, 525-560, CSHL press, 1999) This is a simple module that takes in an array of sequences (assuming equal lengths) and calculates relative base frequencies where the height of each letter reflects the frequency of each nucleotide at a given position. It can also plot the information content at each position scaled by the background frequencies of each nucleotide. It requires the SVG-2.26 or later module by Ronan Oger available at http://www.cpan.org Recommended viewing of the SVG is the plugin available at Adobe: http://www.adobe.com/svg =head1 FEEDBACK =head2 Mailing Lists User feedback is an integral part of the evolution of this and other Bioperl modules. Send your comments and suggestions preferably to one of the Bioperl mailing lists. Your participation is much appreciated. bioperl-l@bioperl.org - General discussion http://bioperl.org/wiki/Mailing_lists - About the mailing lists =head2 Reporting Bugs Report bugs to the Bioperl bug tracking system to help us keep track the bugs and their resolution. Bug reports can be submitted via the web: http://bugzilla.open-bio.org/ =head1 AUTHOR - Shawn Hoon Email shawnh@fugu-sg.org =head1 APPENDIX The rest of the documentation details each of the object methods. Internal methods are usually preceded with a "_". =cut package Bio::Graphics::Pictogram; use strict; use SVG 2.26; use Bio::SeqIO; use base qw(Bio::Root::Root); use constant MAXBITS => 2; =head2 new Title : new Usage : my $picto = Bio::Graphics::Pictogram->new(-width=>"800", -height=>"500", -fontsize=>"60", -plot_bits=>1, -background=>{ 'A'=>0.25, 'C'=>0.18, 'T'=>0.32, 'G'=>0.25}, -color=>{'A'=>'red', 'G'=>'blue', 'C'=>'green', 'T'=>'magenta'}); Function: Constructor for Pictogram Object Returns : L =cut sub new { my ($caller,@args) = @_; my $self = $caller->SUPER::new(@args); my ($width,$height,$fontsize,$color,$background,$bit,$normalize) = $self->_rearrange([qw(WIDTH HEIGHT FONTSIZE COLOR BACKGROUND PLOT_BITS NORMALIZE)],@args); $width||=800; $height||=600; my $svg = SVG->new(width=>$width,height=>$height); $self->svg_obj($svg); $fontsize ||= 80; $self->fontsize($fontsize) if $fontsize; $color = $color || {'T'=>'black','C'=>'blue','G'=>'green','A'=>'red'}; $self->color($color); $background = $background || {'T'=>0.25,'C'=>0.25,'G'=>0.25,'A'=>0.25}; $self->background($background); $self->plot_bits($bit) if $bit; $self->normalize($normalize) if $normalize; return $self; } =head2 make_svg Title : make_svg Usage : $picto->make_svg(); Function: make the SVG object Returns : L Arguments: A fasta file or array ref of L objects or a L =cut sub make_svg { my ($self,$input) = @_; my $fontsize = $self->fontsize; my $size = $fontsize * 0.75; my $width= $size; my $height= $size+40; my $color = $self->color; #starting x coordinate for pictogram my $x = 45+$size/2; my $pos_y = $size * 2; my $bit_y = $pos_y+40; my @pwm; my $bp = 1; #input can be file or array ref of sequences if(ref($input) eq 'ARRAY'){ @pwm = @{$self->_make_pwm($input)}; } elsif(ref($input) && $input->isa("Bio::Matrix::PSM::SiteMatrixI")){ @pwm = $self->_make_pwm_from_site_matrix($input); } else { my $sio = Bio::SeqIO->new(-file=>$input,-format=>"fasta"); my @seq; while (my $seq = $sio->next_seq){ push @seq, $seq; } @pwm = @{$self->_make_pwm(\@seq)}; } my $svg = $self->svg_obj; my $seq_length = scalar(@pwm + 1) * $width + $x + $x; my $seq_grp; #scale the svg if length greater than svg width if($seq_length > $svg->{-document}->{'width'}){ my $ratio = $svg->{-document}->{'width'}/($seq_length); $seq_grp = $svg->group(transform=>"scale($ratio,1)"); } else { $seq_grp= $svg->group(); } #do the drawing, each set is a base position foreach my $set(@pwm){ my ($A,$C,$G,$T,$bits) = @$set; my @array; push @array, ['a',($A)]; push @array, ['g',($G)]; push @array, ['c',($C)]; push @array, ['t',($T)]; @array = sort {$b->[1]<=>$a->[1]}@array; my $count = 1; my $pos_group = $seq_grp->group(id=>"bp $bp"); my $prev_size; my $y_trans; #draw each letter at each position foreach my $letter(@array){ my $scale; if($self->normalize){ $scale = $letter->[1]; } else { $scale = $letter->[1] * ($bits / MAXBITS); } if($count == 1){ if($self->normalize){ $y_trans = 0; } else { $y_trans = (1 - ($bits / MAXBITS)) * $size; } } else { $y_trans += $prev_size; } $pos_group->text('id'=> uc($letter->[0]).$bp,height=>$height, 'width'=>$width,x=>$x,y=>$size, 'transform'=>"translate(0,$y_trans),scale(1,$scale)", 'style'=>{"font-size"=>$fontsize, 'text-anchor'=>'middle', 'font-family'=>'Verdana', 'fill'=>$color->{uc $letter->[0]}})->cdata(uc $letter->[0]) if $scale > 0; $prev_size = $scale * $size; $count++; } #plot the bit if required if($self->plot_bits){ $seq_grp->text('x'=>$x, 'y'=>$bit_y, 'style'=>{"font-size"=>'10', 'text-anchor'=>'middle', 'font-family'=>'Verdana', 'fill'=>'black'})->cdata($bits); } $bp++; $x+=$width; } #plot the tags $seq_grp->text(x=>int($width/2),y=>$bit_y,style=>{"font-size"=>'10','text-anchor'=>'middle','font-family'=>'Verdana','fill'=>'black'})->cdata("Bits:") if $self->plot_bits; $seq_grp->text(x=>int($width/2),y=>$pos_y,style=>{"font-size"=>'10','text-anchor'=>'middle','font-family'=>'Verdana','fill'=>'black'})->cdata("Position:"); #plot the base positions $x = 45+$size/2-int($width/2); foreach my $nbr(1..($bp-1)){ $seq_grp->text(x=>$x+int($width/2),y=>$pos_y,style=>{"font-size"=>'10','text-anchor'=>'left','font-family'=>'Verdana','fill'=>'black'})->cdata($nbr); $x+=$width; } # $seq_grp->transform("scale(2,2)"); return $self->svg_obj($svg); } sub _make_pwm_from_site_matrix{ my ($self,$matrix) = @_; my $bgd = $self->background; my @pwm; my $consensus = $matrix->consensus; foreach my $i(1..length($consensus)){ my %base = $matrix->next_pos; my $bits; $bits+=($base{pA} * log2($base{pA}/$bgd->{'A'})); $bits+=($base{pC} * log2($base{pC}/$bgd->{'C'})); $bits+=($base{pG} * log2($base{pG}/$bgd->{'G'})); $bits+=($base{pT} * log2($base{pT}/$bgd->{'T'})); push @pwm, [$base{pA},$base{pC},$base{pG},$base{pT},abs(sprintf("%.3f",$bits))]; } return @pwm; } sub _make_pwm { my ($self,$input) = @_; my $count = 1; my %hash; my $bgd = $self->background; #sum up the frequencies at each base pair foreach my $seq(@$input){ my $string = $seq->seq; $string = uc $string; my @motif = split('',$string); my $pos = 1; foreach my $t(@motif){ $hash{$pos}{$t}++; $pos++; } $count++; } #calculate relative freq my @pwm; #decrement last count $count--; foreach my $pos(sort{$a<=>$b} keys %hash){ my @array; push @array,($hash{$pos}{'A'}||0)/$count; push @array,($hash{$pos}{'C'}||0)/$count; push @array,($hash{$pos}{'G'}||0)/$count; push @array,($hash{$pos}{'T'}||0)/$count; #calculate bits # relative entropy (RelEnt) or Kullback-Liebler distance # relent = sum fk * log2(fk/gk) where fk is frequency of nucleotide k and # gk the background frequency of nucleotide k my $bits; $bits+=(($hash{$pos}{'A'}||0) / $count) * log2((($hash{$pos}{'A'}||0)/$count) / ($bgd->{'A'})); $bits+=(($hash{$pos}{'C'}||0) / $count) * log2((($hash{$pos}{'C'}||0)/$count) / ($bgd->{'C'})); $bits+=(($hash{$pos}{'G'}||0) / $count) * log2((($hash{$pos}{'G'}||0)/$count) / ($bgd->{'G'})); $bits+=(($hash{$pos}{'T'}||0) / $count) * log2((($hash{$pos}{'T'}||0)/$count) / ($bgd->{'T'})); push @array, abs(sprintf("%.3f",$bits)); push @pwm,\@array; } return $self->pwm(\@pwm); } ###various get/sets =head2 fontsize Title : fontsize Usage : $picto->fontsize(); Function: get/set for fontsize Returns : int Arguments: int =cut sub fontsize { my ($self,$obj) = @_; if($obj){ $self->{'_fontsize'} = $obj; } return $self->{'_fontsize'}; } =head2 color Title : color Usage : $picto->color(); Function: get/set for color Returns : a hash reference Arguments: a hash reference =cut sub color { my ($self,$obj) = @_; if($obj){ $self->{'_color'} = $obj; } return $self->{'_color'}; } =head2 svg_obj Title : svg_obj Usage : $picto->svg_obj(); Function: get/set for svg_obj Returns : L Arguments: L =cut sub svg_obj { my ($self,$obj) = @_; if($obj){ $self->{'_svg_obj'} = $obj; } return $self->{'_svg_obj'}; } =head2 plot_bits Title : plot_bits Usage : $picto->plot_bits(); Function: get/set for plot_bits to indicate whether to plot information content at each base position Returns :1/0 Arguments: 1/0 =cut sub plot_bits { my ($self,$obj) = @_; if($obj){ $self->{'_plot_bits'} = $obj; } return $self->{'_plot_bits'}; } =head2 normalize Title : normalize Usage : $picto->normalize($newval) Function: get/set to make all columns the same height. default is to scale height with information content. Returns : value of normalize (a scalar) Args : on set, new value (a scalar or undef, optional) =cut sub normalize{ my $self = shift; return $self->{'normalize'} = shift if @_; return $self->{'normalize'}; } =head2 background Title : background Usage : $picto->background(); Function: get/set for hash reference of nucleodtide bgd frequencies Returns : hash reference Arguments: hash reference =cut sub background { my ($self,$obj) = @_; if($obj){ $self->{'_background'} = $obj; } return $self->{'_background'}; } =head2 pwm Title : pwm Usage : $picto->pwm(); Function: get/set for pwm Returns : int Arguments: int =cut sub pwm { my ($self,$pwm) = @_; if($pwm){ $self->{'_pwm'} = $pwm; } return $self->{'_pwm'}; } #utility method for returning log 2 sub log2 { my ($val) = @_; return 0 if $val==0; return log($val)/log(2); } 1; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Bio-Graphics-2.37/lib/Bio/Graphics/GDWrapper.pm�����������������������������������������������������000444��001750��001750�� 4453�12165075746� 21263� 0����������������������������������������������������������������������������������������������������ustar�00lstein��������������������������lstein��������������������������000000��000000�������������������������������������������������������������������������������������������������������������������������������������������������������������������������package Bio::Graphics::GDWrapper; use base 'GD::Image'; use Memoize 'memoize'; use Carp 'cluck'; memoize('_match_font'); my $DefaultFont; #from http://reeddesign.co.uk/test/points-pixels.html my %Pixel2Point = ( 8 => 6, 9 => 7, 10 => 7.5, 11 => 8, 12 => 9, 13 => 10, 14 => 10.5, 15 =>11, 16 => 12, 17 => 13, 18 => 13.5, 19 => 14, 20 => 14.5, 21 => 15, 22 => 16, 23 => 17, 24 => 18, 25 => 19, 26 => 20 ); my $GdInit; sub new { my $self = shift; my ($gd,$default_font) = @_; $DefaultFont = $default_font unless $default_font eq '1'; $gd->useFontConfig(1); return bless $gd,ref $self || $self; } sub default_font { return $DefaultFont || 'Arial' } # print with a truetype string sub string { my $self = shift; my ($font,$x,$y,$string,$color) = @_; return $self->SUPER::string(@_) if $self->isa('GD::SVG'); my $fontface = $self->_match_font($font); # warn "$font => $fontface"; my ($fontsize) = $fontface =~ /-(\d+)/; $self->stringFT($color,$fontface,$fontsize,0,$x,$y+$fontsize+1,$string); } sub string_width { my $self = shift; my ($font,$string) = @_; my $fontface = $self->_match_font($font); my ($fontsize) = $fontface =~ /-([\d.]+)/; my @bounds = GD::Image->stringFT(0,$fontface,$fontsize,0,0,0,$string); return abs($bounds[2]-$bounds[0]); } sub string_height { my $self = shift; my ($font,$string) = @_; my $fontface = $self->_match_font($font); my ($fontsize) = $fontface =~ /-(\d+)/; my @bounds = GD::Image->stringFT(0,$fontface,$fontsize,0,0,0,$string); return abs($bounds[5]-$bounds[3]); } # find a truetype match for a built-in font sub _match_font { my $self = shift; my $font = shift; return $font unless ref $font && $font->isa('GD::Font'); # work around older versions of GD that require useFontConfig to be called from a GD::Image instance $GdInit++ || eval{GD::Image->useFontConfig(1)} || GD::Image->new(10,10)->useFontConfig(1); my $fh = $font->height-1; my $height = $Pixel2Point{$fh} || $fh; my $style = $font eq GD->gdMediumBoldFont ? 'bold' :$font eq GD->gdGiantFont ? 'bold' :'normal'; my $ttfont = $self->default_font; return "$ttfont-$height:$style"; } 1; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Bio-Graphics-2.37/lib/Bio/Graphics/Glyph.pm���������������������������������������������������������000555��001750��001750�� 223352�12165075746� 20557� 0����������������������������������������������������������������������������������������������������ustar�00lstein��������������������������lstein��������������������������000000��000000�������������������������������������������������������������������������������������������������������������������������������������������������������������������������package Bio::Graphics::Glyph; use strict; use Carp 'croak','cluck'; use constant BUMP_SPACING => 2; # vertical distance between bumped glyphs use Bio::Root::Version; use Bio::Graphics::Layout; use Memoize 'memoize'; memoize('options') unless $^O =~ /mswin/i; # memoize('option',NORMALIZER=>'_normalize_objects'); # helps ?? my %OptionCache; # works better? use base qw(Bio::Root::Root); my %LAYOUT_COUNT; our @FEATURE_STACK; # the CM1 and CM2 constants control the size of the hash used to # detect collisions. use constant CM1 => 20; # big bin, x axis use constant CM2 => 20; # big bin, y axis use constant CM3 => 50; # small bin, x axis use constant CM4 => 50; # small bin, y axis use constant INF => 1<<16; use constant NINF => -INF(); use constant DEBUG => 0; use constant QUILL_INTERVAL => 8; # number of pixels between Jim Kent style intron "quills" ########################################################## # glyph-specific options # # the data structure returned by my_options will be merged # with values returned by this method in subclasses to # create a merged hash of all options that can be invoked # # retrieve this merged hash with # Bio::Graphics::Glyph::the_subclass->options # ########################################################## sub my_description { return < [ 'integer', 10, 'Height of the glyph.'], box_subparts=> [ 'integer', 0, 'If this option is greater than zero, then imagemaps constructed from this glyph will contain', 'bounding boxes around each subpart of a feature (e.g. each exon in a gene). The value of the', 'option indicates the depth of recursion.' ], fgcolor => [ ['color','featureScore','featureRGB'], 'black', 'The foreground color of the glyph, used for drawing outlines.', 'A value of "featureScore" will produce a greyscale gradient from the', "feature's score value based on a range from 0 (lightest) to 1000 (darkest).", 'A value of "featureRGB" will look for a feature tag named "RGB" and use that', 'for the color value.', 'See the next section for color choices.'], bgcolor => [ ['color','featureScore','featureRGB'], 'turquoise', 'The background color of the glyph, used for filling its contents.', 'A value of "featureScore" will produce a greyscale gradient from the', "feature's score value based on a range from 0 (lightest) to 1000 (darkest).", 'A value of "featureRGB" will look for a feature tag named "RGB" and use that', 'for the color value.', 'See the next section for color choices.'], fillcolor => [ 'color', 'turquoise', 'A synonym for -bgcolor.'], tkcolor => [ 'color', undef, 'Rarely-used option to flood-fill entire glyph with a single color', 'prior to rendering it.'], opacity => [ 'float', '1.0', 'Default opacity to apply to glyph background and foreground colors.', 'This is a value between 0.0 (completely transparent) to 1.0 (completely opaque.', 'If the color contains an explicit opacity (alpha) value, the default value', 'will be ignored'], linewidth => [ 'integer', 1, 'Thickness of line used to draw the glyph\'s outline.'], strand_arrow => [ 'boolean', undef, "Whether to indicate the feature's strandedness. If equal to 'ends'", "then only the right and left ends of multi-part features will show", "strandedness." ], stranded => [ 'boolean', undef, 'Synonym for -strand_arrow.', "Indicates whether to indicate the feature's strandedness. If equal to 'ends'", "then only the right and left ends of multi-part features will show", "strandedness." ], key => [ 'string', undef, 'The printed label to use to describe this track.'], category => [ 'string', undef, 'A descriptive category that will be added to the track key.'], no_subparts => [ 'boolean', undef, 'Set this option to a true value to suppress drawing of all its subparts.'], ignore_sub_part => [ 'string', undef, 'Pass a space-delimited list of primary_tag() names in order to selectively', 'suppress the drawing of subparts that match those primary tags.'], maxdepth => [ 'integer', undef, 'Specifies how many levels deep the glyph should traverse features looking', 'for subfeatures. A value of undef allows unlimited traversal. A value of', '0 suppresses traversal entirely for the same effect as -no_subparts.'], sort_order => [ ['left','right','low_score','high_score','longest','shortest','strand','name'], 'left', 'Control how features are layed out so that more "important" features sort', 'towards the top. See the Bio::Graphics::Glyph documentation for a description of how this' , 'works.'], always_sort => [ 'boolean', undef, 'Sort even when bumping is off.'], bump => [ 'integer', 1, 'This option dictates the behavior of the glyph when two features collide horizontally.', 'A value of +1 will bump the colliding feature downward using an algorithm that uses spaces efficiently.', 'A value of -1 will bump the colliding feature upward using the same algorithm.', 'Values of +2 and -2 will bump using a simple algorithm that is faster but does not use space as efficiently.', 'A value of 3 or "fast" will turn on a faster collision detection algorithm which', 'is only compatible with the default "left" sorting order.', 'A value of 0 suppresses collision control entirely.'], bump_limit => [ 'integer', -1, 'This option will cause bumping to stop after the indicated number of features', 'pile up. Subsequent collisions will not be bumped.'], feature_limit => [ 'integer', 0, 'This option will set an upper bound on the number of features to be displayed.', 'For this to work properly, features must be added one at a time using add_feature().'], hbumppad => [ 'integer', 2, 'Ordinarily collison control prevents two features from overlapping if they come within', '2 pixels of each other. This option allows you to change this value to give glyphs', 'more or less breathing space on the left and right.' ], hilite => [ 'color', undef, 'Highlight the glyph in the indicated color. Usually used as a callback to', 'selectively highlight glyphs that meet certain criteria.'], link => [ 'string', undef, 'When generating an imagemap, specify the pattern or callback for formatting', 'the link URL associated with the glyph.'], title => [ 'string', undef, 'When generating an imagemap, specify the pattern or callback for formatting', 'the link title associated with the glyph.'], target => [ 'string', undef, 'When generating an imagemap, specify the pattern or callback for formatting', 'the link target associated with the glyph.'], }; } # return a demo feature for the user to play with # The feature must not be longer than 500 bp for this to work. # Default is to return nothing. sub demo_feature { return; } sub gd { shift->panel->current_gd } # a bumpable graphical object that has bumpable graphical subparts # args: -feature => $feature_object (may contain subsequences) # -factory => $factory_object (called to create glyphs for subsequences) # In this scheme, the factory decides based on stylesheet information what glyph to # draw and what configurations options to us. This allows for heterogeneous tracks. sub new { my $class = shift; my %arg = @_; my $feature = $arg{-feature} or $class->throw("No feature $class"); my $factory = $arg{-factory} || $class->default_factory; my $level = $arg{-level} || 0; my $flip = $arg{-flip}; push @FEATURE_STACK,($feature,undef); my $self = bless {},$class; $self->{feature} = $feature; $self->{factory} = $factory; $self->{level} = $level; $self->{flip}++ if $flip; $self->{top} = 0; my $panel = $factory->panel; my $p_start = $panel->start; my $p_end = $panel->end; my @subfeatures; my @subglyphs; warn $self if DEBUG; warn $feature if DEBUG; @subfeatures = $self->subfeat($feature); if ($self->option('ignore_sub_part')) { my @tmparray; foreach (@subfeatures) { my $type = $_->method; my @ignore_list = split /\s+/, $self->option('ignore_sub_part'); my $ignore_str = join('|', @ignore_list); unless ($type =~ /$ignore_str/) { push @tmparray, $_; } } @subfeatures = @tmparray; } my @visible_subfeatures = grep {$p_start <= $_->end && $p_end >= $_->start} @subfeatures; $self->feature_has_subparts(@subfeatures>0); if (@visible_subfeatures) { # dynamic glyph resolution @subglyphs = map { $_->[0] } sort { $a->[1] <=> $b->[1] } map { [$_, $_->left ] } $self->make_subglyph($level+1,@visible_subfeatures); $self->{feature_count} = scalar @subglyphs; $self->{parts} = \@subglyphs; } # warn "type=",$feature->type,", glyph=$self, subglyphs=@subglyphs"; my ($start,$stop) = ($self->start, $self->stop); if (defined $start && defined $stop && $start ne '') { # more paranoia ($start,$stop) = ($stop,$start) if $start > $stop; # sheer paranoia # the +1 here is critical for allowing features to meet nicely at nucleotide resolution my ($left,$right) = $factory->map_pt($start,$stop+1); $self->{left} = $left; $self->{width} = $right - $left + 1; } if (@subglyphs) { my $l = $subglyphs[0]->left; # this clashes with the pad_left calculation and is unecessary # $self->{left} = $l if !defined($self->{left}) || $l < $self->{left}; my $right = ( sort { $b<=>$a } map {$_->right} @subglyphs)[0]; my $w = $right - $self->{left} + 1; # this clashes with the pad_right calculation and is unecessary # $self->{width} = $w if !defined($self->{width}) || $w > $self->{width}; } $self->{point} = $arg{-point} ? $self->height : undef; splice(@FEATURE_STACK,-2); return $self; } # override this if you want to make a particular type of glyph rather than have the # factory decide. sub make_subglyph { my $self = shift; my $level = shift; my $factory = $self->{factory}; $factory->make_glyph($level,@_); } sub parts { my $self = shift; return unless $self->{parts}; return wantarray ? @{$self->{parts}} : $self->{parts}; } sub feature_count { my $self = shift; return $self->{feature_count} || 0; } sub features_clipped { my $self = shift; my $d = $self->{features_clipped}; $self->{features_clipped} = shift if @_; return $d; } sub _bump_feature_count { my $self = shift; my $count = shift || 1; return $self->{feature_count} += $count; } # this is different than parts(). parts() will return subglyphs # that are contained within the current viewing range. feature_has_subparts() # will return true if the feature has any subparts, even if they are off the # screen. sub feature_has_subparts { my $self = shift; return $self->{feature_has_subparts} = shift if @_; return 0 if $self->maxdepth == 0; my $feature = $self->feature; return 1 if $feature->can('compound') && $feature->compound; return $self->{feature_has_subparts}; } sub feature { shift->{feature} } sub factory { shift->{factory} } sub panel { shift->factory->panel } sub point { shift->{point} } sub scale { shift->factory->scale } sub flip { my $self = shift; my $d = $self->{flip}; $self->{flip} = shift if @_; $d; } sub start { my $self = shift; return $self->{start} if exists $self->{start}; if ($self->{flip}) { $self->{start} = defined $self->{feature}->end ? $self->panel->end + 1 - $self->{feature}->end : 0; } else { $self->{start} = defined $self->{feature}->start ? $self->{feature}->start : $self->panel->offset - 1 } return $self->{start}; } sub stop { my $self = shift; return $self->{stop} if exists $self->{stop}; if ($self->{flip}) { $self->{stop} = defined $self->{feature}->start ? $self->panel->end + 1 - $self->{feature}->start : $self->panel->offset - 1; } else { $self->{stop} = defined $self->{feature}->end ? $self->{feature}->end : $self->panel->offset+$self->panel->length+1; } return $self->{stop} } sub end { shift->stop } sub length { my $self = shift; $self->stop - $self->start }; sub score { my $self = shift; return $self->{score} if exists $self->{score}; return $self->{score} = ($self->{feature}->score || 0); } sub strand { my $self = shift; return $self->{strand} if exists $self->{strand}; return $self->{strand} = ($self->{feature}->strand || 0); } sub map_pt { shift->{factory}->map_pt(@_) } sub map_no_trunc { shift->{factory}->map_no_trunc(@_) } # add a feature (or array ref of features) to the list sub add_feature { my $self = shift; my $factory = $self->factory; for my $feature (@_) { if (ref $feature eq 'ARRAY') { $self->add_group(@$feature); $self->_bump_feature_count(scalar @$feature); } else { warn $factory if DEBUG; my $parts = $self->{parts} ||= []; my $limit = $self->feature_limit; my $count = $self->_bump_feature_count; if (!$limit || $count <= $limit) { push @$parts,$factory->make_glyph(0,$feature); } elsif (rand() < $limit/$count) { $self->features_clipped(1); $parts->[rand @$parts] = $factory->make_glyph(0,$feature); # subsample } } } } # link a set of features together so that they bump as a group sub add_group { my $self = shift; my @features = ref($_[0]) eq 'ARRAY' ? @{$_[0]} : @_; my $f = Bio::Graphics::Feature->new( -segments=>\@features, -type => 'group', ); $self->add_feature($f); $f; } sub top { my $self = shift; my $g = $self->{top}; $self->{top} = shift if @_; $g; } sub left { my $self = shift; return $self->{left} - $self->pad_left; } sub right { my $self = shift; return $self->left + $self->layout_width - 1; } sub bottom { my $self = shift; $self->top + $self->layout_height - 1; } sub height { my $self = shift; return $self->{height} if exists $self->{height}; my $baseheight = $self->option('height'); # what the factory says return $self->{height} = $baseheight; } sub width { my $self = shift; my $g = $self->{width}; $self->{width} = shift if @_; return $g; } sub layout_height { my $self = shift; push @FEATURE_STACK,$self->feature; my $result = $self->layout; pop @FEATURE_STACK; return $result; } sub layout_width { my $self = shift; return $self->width + $self->pad_left + $self->pad_right; } # returns the rectangle that surrounds the physical part of the # glyph, excluding labels and other "extra" stuff sub calculate_boundaries {return shift->bounds(@_);} sub bounds { my $self = shift; my ($dx,$dy) = @_; $dx += 0; $dy += 0; ($dx + $self->{left}, $dy + $self->top + $self->pad_top, $dx + $self->{left} + $self->{width} - 1, $dy + $self->bottom - $self->pad_bottom); } sub box { my $self = shift; my @result = ($self->left,$self->top,$self->right,$self->bottom); return @result; } sub unfilled_box { my $self = shift; my $gd = shift; my ($x1,$y1,$x2,$y2,$fg,$bg,$lw) = @_; $lw = $self->linewidth; unless ($fg) { $fg ||= $self->fgcolor; $fg = $self->set_pen($lw,$fg) if $lw > 1; } unless ($bg) { $bg ||= $self->bgcolor; $bg = $self->set_pen($lw,$bg) if $lw > 1; } # draw a box $gd->rectangle($x1,$y1,$x2,$y2,$fg); # if the left end is off the end, then cover over # the leftmost line my ($width) = $gd->getBounds; $gd->line($x1,$y1+$lw,$x1,$y2-$lw,$bg) if $x1 < $self->panel->pad_left; $gd->line($x2,$y1+$lw,$x2,$y2-$lw,$bg) if $x2 > $width - $self->panel->pad_right; } # return boxes surrounding each part sub boxes { my $self = shift; push @FEATURE_STACK,$self->feature; my ($left,$top,$parent) = @_; $top += 0; $left += 0; my @result; $self->layout; $parent ||= $self; my $subparts = $self->box_subparts || 0; for my $part ($self->parts) { my $type = $part->feature->primary_tag || ''; if ($type eq 'group' or $subparts > $part->level) { push @result,$part->boxes($left,$top+$self->top+$self->pad_top,$parent); next if $type eq 'group'; } my ($x1,$y1,$x2,$y2) = $part->box; $x2++ if $x1==$x2; push @result,[$part->feature, $left + $x1,$top+$self->top+$self->pad_top+$y1, $left + $x2,$top+$self->top+$self->pad_top+$y2, $parent]; } pop @FEATURE_STACK; return wantarray ? @result : \@result; } sub box_subparts { my $self = shift; return $self->{box_subparts} if exists $self->{box_subparts}; return $self->{box_subparts} = $self->_box_subparts; } sub _box_subparts { shift->option('box_subparts') } # this should be overridden for labels, etc. # allows glyph to make itself thicker or thinner depending on # domain-specific knowledge sub pad_top { my $self = shift; return 0; } sub pad_bottom { my $self = shift; return 0; } sub pad_left { my $self = shift; my @parts = $self->parts or return 0; my $max = 0; foreach (@parts) { my $pl = $_->pad_left; $max = $pl if $max < $pl; } $max; } sub pad_right { my $self = shift; my @parts = $self->parts or return 0; my $max = 0; my $max_right = 0; foreach (@parts) { my $right = $_->right; my $pr = $_->pad_right; if ($max_right < $pr+$right) { $max = $pr; $max_right = $pr+$right; } } $max; } # move relative to parent sub move { my $self = shift; my ($dx,$dy) = @_; $self->{left} += $dx; $self->{top} += $dy; # because the feature parts use *absolute* not relative addressing # we need to move each of the parts horizontally, but not vertically $_->move($dx,0) foreach $self->parts; } # get an option sub option { my $self = shift; my $option_name = shift; local $^W=0; my $cache_key = join ';',(%$self,$option_name); return $OptionCache{$cache_key} if exists $OptionCache{$cache_key}; my @args = ($option_name,@{$self}{qw(partno total_parts)}); my $factory = $self->{factory} or return; return $OptionCache{$cache_key} = $factory->option($self,@args); } # get an option that might be a code reference sub code_option { my $self = shift; my $option_name = shift; my $factory = $self->factory or return; $factory->get_option($option_name); } # set an option globally sub configure { my $self = shift; my $factory = $self->factory; my $option_map = $factory->option_map; while (@_) { my $option_name = shift; my $option_value = shift; ($option_name = lc $option_name) =~ s/^-//; $option_map->{$option_name} = $option_value; } } # some common options sub color { my $self = shift; my $color = shift; my $index = $self->option($color); # turn into a color index return $self->translate_color($index) if defined $index; return 0; } sub translate_color { my $self = shift; my $color = shift; return $self->_translate_color($color); } sub _translate_color { my $self = shift; my $color = shift; my $opacity = $self->default_opacity; return $opacity < 1 ? $self->factory->transparent_color($opacity,$color) : $self->factory->translate_color($color); } # return value: # 0 no bumping # +1 bump down # -1 bump up # +2 simple bump down # -2 simple bump up # +3 optimized (fast) bumping sub bump { my $self = shift; my $bump = $self->option('bump'); return $bump; } # control horizontal and vertical collision control sub hbumppad { my $self = shift; return $self->{_hbumppad} if exists $self->{_hbumppad}; my $hbumppad = $self->option('hbumppad'); $hbumppad = 2 unless defined $hbumppad; return $self->{_hbumppad}= $hbumppad; } sub default_opacity { my $self = shift; return $self->{default_opacity} if defined $self->{default_opacity}; my $o = $self->option('opacity'); return $self->{default_opacity} = defined $o ? $o : 1.0; } # we also look for the "color" option for Ace::Graphics compatibility sub fgcolor { my $self = shift; my $fgcolor = $self->option('color') || $self->option('fgcolor'); my $index = $fgcolor; $index = 'black' unless defined $index; if ($index eq 'featureRGB') { ($index) = eval{$self->feature->get_tag_values('RGB')}; $index ||= $fgcolor; } elsif ($index eq 'featureScore') { $index = $self->score_to_color; } return $self->_translate_color($index); } #add for compatibility sub fillcolor { my $self = shift; return $self->bgcolor; } # we also look for the "fillcolor" option for Ace::Graphics compatibility sub bgcolor { my $self = shift; my ($bgcolor) = eval{$self->feature->get_tag_values('bgcolor')}; $bgcolor ||= $self->option('bgcolor'); # Let feature attribute override color my $index = defined $bgcolor ? $bgcolor : $self->option('fillcolor'); $index = 'white' unless defined $index; if ($index eq 'featureRGB') { ($index) = eval{$self->feature->get_tag_values('RGB')}; $index ||= $bgcolor; } elsif ($index eq 'featureScore') { $index = $self->score_to_color; } return $self->_translate_color($index); } # for compatibility with UCSC genome browser useScore option sub score_to_color { my $self = shift; my $feature = $self->feature; my ($score) = $feature->can('score') ? $feature->score : eval{$feature->has_tag('score')} || 0; my $max_score = 945; # defined by UCSC docs my $min_score = 166; my $min_gray = 0; my $max_gray = 255; my $rgb_per_score = ($max_gray-$min_gray)/($max_score-$min_score); $score = $max_score if $score > $max_score; $score = $min_score if $score < $min_score; my $gray = int($max_gray - ($min_gray + ($score-$min_score) * $rgb_per_score)); return "rgb($gray,$gray,$gray)"; } sub getfont { my $self = shift; my $option = shift || 'font'; my $default = shift; my $font = $self->option($option) || $default; return unless $font; my $gdfont = $self->panel->gdfont($font); $self->configure($option => $gdfont); return $gdfont; } sub tkcolor { # "track color" my $self = shift; $self->option('tkcolor') or return; return $self->color('tkcolor') } sub image_class { shift->{factory}->{panel}->{image_class}; } sub polygon_package { shift->{factory}->{panel}->{polygon_package}; } sub layout_sort { my $self = shift; my $sortfunc; my $opt = $self->code_option("sort_order"); if (!$opt) { $sortfunc = sub { $a->start <=> $b->start }; } elsif (ref $opt eq 'CODE') { $self->throw('sort_order subroutines must use the $$ prototype') unless prototype($opt) eq '$$'; $sortfunc = $opt; } elsif ($opt =~ /^sub\s+\{/o) { $sortfunc = eval $opt; } else { # build $sortfunc for ourselves: my @sortbys = split(/\s*\|\s*/o, $opt); $sortfunc = 'sub { '; my $sawleft = 0; # not sure I can make this schwartzian transformed for my $sortby (@sortbys) { if ($sortby eq "left" || $sortby eq "default") { $sortfunc .= '($a->start <=> $b->start) || '; $sawleft++; } elsif ($sortby eq "right") { $sortfunc .= '($a->end <=> $b->end) || '; } elsif ($sortby eq "low_score") { $sortfunc .= '($a->score <=> $b->score) || '; } elsif ($sortby eq "high_score") { $sortfunc .= '($b->score <=> $a->score) || '; } elsif ($sortby eq "longest") { $sortfunc .= '(($b->length) <=> ($a->length)) || '; } elsif ($sortby eq "shortest") { $sortfunc .= '(($a->length) <=> ($b->length)) || '; } elsif ($sortby eq "strand") { $sortfunc .= '($b->strand <=> $a->strand) || '; } elsif ($sortby eq "name") { $sortfunc .= '($a->feature->display_name cmp $b->feature->display_name) || '; } } unless ($sawleft) { $sortfunc .= ' ($a->left <=> $b->left) '; } else { $sortfunc .= ' 0'; } $sortfunc .= '}'; $sortfunc = eval $sortfunc; } # cache this # $self->factory->set_option(sort_order => $sortfunc); my @things = sort $sortfunc @_; return @things; } # handle collision detection sub layout { my $self = shift; return $self->{layout_height} if exists $self->{layout_height}; my @parts = $self->parts; return $self->{layout_height} = $self->height + $self->pad_top + $self->pad_bottom unless @parts; my $bump_direction = $self->bump; my $bump_limit = $self->bump_limit || -1; $bump_direction = 'fast' if $bump_direction && $bump_direction == 1 && !$self->code_option('sort_order'); $_->layout foreach @parts; # recursively lay out # no bumping requested, or only one part here, or the tracks are supposed to be overlay if (@parts == 1 || !$bump_direction || ($bump_direction eq 'fast' and $self->code_option('overlay') == 1)) { my $highest = 0; foreach (@parts) { my $height = $_->layout_height; $highest = $height > $highest ? $height : $highest; } return $self->{layout_height} = $highest + $self->pad_top + $self->pad_bottom; } if ($bump_direction eq 'fast' or $bump_direction == 3) { return $self->{layout_height} = $self->optimized_layout(\@parts) + $self->pad_bottom + $self->pad_top -1;# - $self->top + 1; } my (%bin1,%bin2); my $limit = 0; my $recent_pos = 0; my $max_pos = 0; # strand bumping turns on bumping for features that are in opposite strands! # features in the same strand are allowed to overlap my $strand_bumping; if ($bump_direction eq 'overlap') { $bump_direction = 1; $strand_bumping++; } for my $g ($self->layout_sort(@parts)) { my $height = $g->{layout_height}; # Simple +/- 2 bumping. Every feature gets its very own line if (abs($bump_direction) >= 2) { $g->move(0,$limit); $limit += $height + BUMP_SPACING if $bump_direction > 0; $limit -= $height + BUMP_SPACING if $bump_direction < 0; next; } # we get here for +/- 1 bumping my $pos = 0; my $bumplevel = 0; my $left = $g->left; my $right = $g->right; my $strand = $g->strand || 0; my $search_mode = 'down'; while (1) { # stop bumping if we've gone too far down if ($bump_limit > 0 && $bumplevel++ >= $bump_limit) { $g->{overbumped}++; # this flag can be used to suppress label and description foreach ($g->parts) { $_->{overbumped}++; } last; } # look for collisions my $bottom = $pos + $height; my $bin = \%bin1; $bin = $strand >= 0 ? \%bin1 : \%bin2 if $strand_bumping; my $collision = $self->collides($bin,CM1,CM2,$left,$pos,$right,$bottom) or last; if ($bump_direction > 0) { $pos = $collision->[3] + BUMP_SPACING; # collision, so bump } else { $pos -= BUMP_SPACING; } $pos++ if $pos % 2; # correct for GD rounding errors } $g->move(0,$pos); my $bin = \%bin1; $bin = $strand >= 0 ? \%bin2 : \%bin1 if $strand_bumping; # note reversed order - features in opposite strands bump $self->add_collision($bin,CM1,CM2,$left,$g->top,$right,$g->bottom); $recent_pos = $pos; $max_pos = $pos if $pos > $max_pos; } # If -1 bumping was allowed, then normalize so that the top glyph is at zero if ($bump_direction < 0) { my $topmost; foreach (@parts) { my $top = $_->top; $topmost = $top if !defined($topmost) or $top < $topmost; } my $offset = - $topmost; $_->move(0,$offset) foreach @parts; } # find new height my $bottom = 0; foreach (@parts) { $bottom = $_->bottom if $_->bottom > $bottom; } return $self->{layout_height} = $self->pad_bottom + $self->pad_top + $bottom - $self->top + 1; } # the $%occupied structure is a hash of {left,top} = [left,top,right,bottom] sub collides { my $self = shift; my ($occupied,$cm1,$cm2,$left,$top,$right,$bottom) = @_; my @keys = $self->_collision_keys($cm1,$cm2,$left,$top,$right,$bottom); my $hspacing = $self->hbumppad; my $collides = 0; for my $k (@keys) { next unless exists $occupied->{$k}; for my $bounds (@{$occupied->{$k}}) { my ($l,$t,$r,$b) = @$bounds; next unless $right+$hspacing > $l and $left-$hspacing < $r and $bottom >= $t and $top <= $b; $collides = $bounds; last; } } $collides; } sub add_collision { my $self = shift; my ($occupied,$cm1,$cm2,$left,$top,$right,$bottom) = @_; my $value = [$left,$top,$right,$bottom]; my @keys = $self->_collision_keys($cm1,$cm2,@$value); push @{$occupied->{$_}},$value foreach @keys; } sub _collision_keys { my $self = shift; my ($binx,$biny,$left,$top,$right,$bottom) = @_; my @keys; my $bin_left = int($left/$binx); my $bin_right = int($right/$binx); my $bin_top = int($top/$biny); my $bin_bottom = int($bottom/$biny); for (my $x=$bin_left;$x<=$bin_right; $x++) { for (my $y=$bin_top;$y<=$bin_bottom; $y++) { push @keys,join(',',$x,$y); } } @keys; } # jbrowse layout that acts by keeping track of contours of the free space sub optimized_layout { my $self = shift; my $parts = shift; my $hspacing = $self->hbumppad; my $bump_limit = $self->bump_limit; my @rects = map { $_ => [ $_->left, $_->right + $hspacing, $_->{layout_height}+BUMP_SPACING ] } $self->layout_sort(@$parts); my $layout = Bio::Graphics::Layout->new(0,$self->panel->right); my $overbumped; while (@rects) { my ($part,$rect) = splice(@rects,0,2); my $offset = $layout->addRect("$part",@$rect); if ($overbumped && $offset > $overbumped) { $part->move(0,$overbumped); next; } $part->move(0,$offset); $overbumped = $offset if $bump_limit > 0 && $offset >= $bump_limit * $rect->[2]; } return $overbumped && $overbumped < $layout->totalHeight ? $overbumped : $layout->totalHeight; } sub draw_it { my $self = shift; push @FEATURE_STACK,$self->feature; $self->draw(@_); pop @FEATURE_STACK; } sub draw { my $self = shift; my $gd = shift; my ($left,$top,$partno,$total_parts) = @_; $self->panel->startGroup($gd); my $connector = $self->connector; if (my @parts = $self->parts) { # invoke sorter if user wants to sort always and we haven't already sorted # during bumping. @parts = $self->layout_sort(@parts) if !$self->bump && $self->option('always_sort'); my $x = $left; my $y = $top + $self->top + $self->pad_top; $self->draw_connectors($gd,$x,$y) if $connector && $connector ne 'none'; my $last_x; for (my $i=0; $i<@parts; $i++) { # lie just a little bit to avoid lines overlapping and make the picture prettier my $fake_x = $x; $fake_x-- if defined $last_x && $parts[$i]->left - $last_x == 1; $parts[$i]->draw_highlight($gd,$fake_x,$y); $parts[$i]->draw_it($gd,$fake_x,$y,$i,scalar(@parts)); $last_x = $parts[$i]->right; } } else { # no part $self->draw_connectors($gd,$left,$top) if $connector && $connector ne 'none'; # && $self->{level} == 0; $self->draw_component($gd,$left,$top,$partno,$total_parts) unless $self->feature_has_subparts; } $self->panel->endGroup($gd); } sub connector { return } sub parts_overlap { my $self = shift; return $self->option('parts_overlap'); } sub bump_limit { shift->option('bump_limit') } # the "level" is the level of testing of the glyph # groups are level -1, top level glyphs are level 0, subcomponents are level 1 and so forth. sub level { shift->{level}; } # return the feature's parent; sub parent_feature { my $self = shift; my $ancestors = shift; $ancestors = 1 unless defined $ancestors; return unless @FEATURE_STACK; my $index = $#FEATURE_STACK - $ancestors; return unless $index >= 0; return $FEATURE_STACK[$index]; } sub draw_connectors { my $self = shift; return if $self->{overbumped}; my $gd = shift; my ($dx,$dy) = @_; my @parts = sort { $a->left <=> $b->left } $self->parts; for (my $i = 0; $i < @parts-1; $i++) { # don't let connectors double-back on themselves next if ($parts[$i]->bounds)[2] > ($parts[$i+1]->bounds)[0] && !$self->parts_overlap; $self->_connector($gd,$dx,$dy,$parts[$i]->bounds,$parts[$i+1]->bounds); } # extra connectors going off ends if (@parts) { my($x1,$y1,$x2,$y2) = $self->bounds(0,0); my($xl,$xt,$xr,$xb) = $parts[0]->bounds; $self->_connector($gd,$dx,$dy,$x1,$xt,$x1,$xb,$xl,$xt,$xr,$xb) if $x1 < $xl; @parts = sort {$a->right<=>$b->right} @parts; my ($xl2,$xt2,$xr2,$xb2) = $parts[-1]->bounds; if ($x2 > $xr2) { $self->_connector($gd,$dx,$dy,$parts[-1]->bounds,$x2,$xt2,$x2,$xb2); } } else { # This code draws the connectors from end-to-end when there are no parts in # view (e.g. zoomed into a gap in an alignment). my ($x1,$y1,$x2,$y2) = $self->bounds($dx,$dy); $self->draw_connector($gd,$y1,$y2,$x1,$y1,$y2,$x2); } } # return true if this feature should be highlited sub hilite_color { my $self = shift; return if $self->level>0; # only highlite top level glyphs my $index = $self->option('hilite') or return; $self->factory->translate_color($index); } sub draw_highlight { my $self = shift; my ($gd,$left,$top) = @_; my $color = $self->hilite_color or return; my @bounds = $self->bounds; $gd->filledRectangle($bounds[0]+$left - 3, $bounds[1]+$top - 3, $bounds[2]+$left + 3, $bounds[3]+$top + 3, $color); } sub _connector { my $self = shift; my ($gd, $dx,$dy, $xl,$xt,$xr,$xb, $yl,$yt,$yr,$yb) = @_; my $left = $dx + $xr; my $right = $dx + $yl; my $top1 = $dy + $xt; my $bottom1 = $dy + $xb; my $top2 = $dy + $yt; my $bottom2 = $dy + $yb; # restore this comment if you don't like the group dash working # its way backwards. return if $right-$left < 1 && !$self->isa('Bio::Graphics::Glyph::group'); $self->draw_connector($gd, $top1,$bottom1,$left, $top2,$bottom2,$right, ); } sub draw_connector { my $self = shift; my $gd = shift; my $color = $self->connector_color; my $connector_type = $self->connector or return; if ($connector_type eq 'hat') { $self->draw_hat_connector($gd,$color,@_); } elsif ($connector_type eq 'solid') { $self->draw_solid_connector($gd,$color,@_); } elsif ($connector_type eq 'dashed') { $self->draw_dashed_connector($gd,$color,@_); } elsif ($connector_type eq 'quill') { $self->draw_quill_connector($gd,$color,@_); } elsif ($connector_type eq 'crossed') { $self->draw_crossed_connector($gd,$color,@_); } else { ; # draw nothing } } sub draw_hat_connector { my $self = shift; my $gd = shift; my $color = shift; my ($top1,$bottom1,$left,$top2,$bottom2,$right) = @_; cluck "gd object is $gd" unless ref $gd; my $center1 = ($top1 + $bottom1)/2; my $quarter1 = $top1 + ($bottom1-$top1)/4; my $center2 = ($top2 + $bottom2)/2; my $quarter2 = $top2 + ($bottom2-$top2)/4; if ($center1 != $center2) { $self->draw_solid_connector($gd,$color,@_); return; } if ($right - $left > 4) { # room for the inverted "V" my $middle = $left + int(($right - $left)/2); $gd->line($left,$center1,$middle,$top1,$color); $gd->line($middle,$top1,$right-1,$center1,$color); } elsif ($right-$left > 1) { # no room, just connect $gd->line($left,$quarter1,$right-1,$quarter1,$color); } } sub draw_solid_connector { my $self = shift; my $gd = shift; my $color = shift; my ($top1,$bottom1,$left,$top2,$bottom2,$right) = @_; my $center1 = ($top1 + $bottom1)/2; my $center2 = ($top2 + $bottom2)/2; $gd->line($left,$center1,$right,$center2,$color); } sub draw_dashed_connector { my $self = shift; my $gd = shift; my $color = shift; my ($top1,$bottom1,$left,$top2,$bottom2,$right) = @_; my $center1 = ($top1 + $bottom1)/2; my $center2 = ($top2 + $bottom2)/2; my $image_class = $self->panel->image_class; my $gdTransparent = $image_class->gdTransparent; my $gdStyled = $image_class->gdStyled; $gd->setStyle($color,$color,$gdTransparent,$gdTransparent); $gd->line($left,$center1,$right,$center2,$gdStyled); } sub draw_quill_connector { my $self = shift; my $gd = shift; my $color = shift; my ($top1,$bottom1,$left,$top2,$bottom2,$right) = @_; my $center1 = ($top1 + $bottom1)/2; my $center2 = ($top2 + $bottom2)/2; $gd->line($left,$center1,$right,$center2,$color); my $direction = $self->feature->strand; return unless $direction; $direction *= -1 if $self->{flip}; if ($direction > 0) { my $start = $left+4; my $end = $right-1; for (my $position=$start; $position <= $end; $position += QUILL_INTERVAL) { $gd->line($position,$center1,$position-2,$center1-2,$color); $gd->line($position,$center1,$position-2,$center1+2,$color); } } else { my $start = $left+1; my $end = $right-4; for (my $position=$start; $position <= $end; $position += QUILL_INTERVAL) { $gd->line($position,$center1,$position+2,$center1-2,$color); $gd->line($position,$center1,$position+2,$center1+2,$color); } } } sub draw_crossed_connector { my $self = shift; my $gd = shift; my $color = shift; my ($top1,$bottom1,$left,$top2,$bottom2,$right) = @_; #Draw the horizontal line my $center1 = ($top1 + $bottom1)/2; my $center2 = ($top2 + $bottom2)/2; $gd->line($left,$center1,$right,$center2,$color); #Extra validations ($left, $right) = ($right, $left) if ($right < $left); ($top1, $bottom1) = ($bottom1, $top1) if ($bottom1 < $top1); ($top2, $bottom2) = ($bottom2, $top2) if ($bottom2 < $top2); #Draw the "X" my $middle = int(($right - $left) / 2) + $left; my $midLen = int(($bottom1 - $top1) / 2); $gd->line($middle-$midLen,$top1, $middle+$midLen,$bottom2,$color); $gd->line($middle-$midLen,$bottom1,$middle+$midLen,$top2,$color); } sub filled_box { my $self = shift; my $gd = shift; my ($x1,$y1,$x2,$y2,$bg,$fg,$lw) = @_; $bg ||= $self->bgcolor; $fg ||= $self->fgcolor; $lw ||= $self->option('linewidth') || 1; $x2 = $x1+1 if abs($x2-$x1) < 1; $gd->filledRectangle($x1,$y1,$x2,$y2,$bg); $fg = $self->set_pen($lw,$fg) if $lw > 1; # draw a box $gd->rectangle($x1,$y1,$x2,$y2,$fg); # if the left end is off the end, then cover over # the leftmost line $self->blunt($gd,$x1,$y1,$x2,$y2,$bg,$fg,$lw); } sub blunt { my $self = shift; my $gd = shift; my ($x1,$y1,$x2,$y2,$bg,$fg,$lw) = @_; # if the left end is off the end, then cover over # the leftmost line my ($width) = $gd->getBounds; $bg = $self->set_pen($lw,$bg) if $lw > 1; $gd->line($x1,$y1+$lw,$x1,$y2-$lw,$bg) if $x1 < $self->panel->pad_left; $gd->line($x2,$y1+$lw,$x2,$y2-$lw,$bg) if $x2 > $width - $self->panel->pad_right; } sub filled_oval { my $self = shift; my $gd = shift; my ($x1,$y1,$x2,$y2,$bg,$fg,$lw) = @_; my $cx = ($x1+$x2)/2; my $cy = ($y1+$y2)/2; $fg ||= $self->fgcolor; $bg ||= $self->bgcolor; $lw ||= $self->linewidth; $fg = $self->set_pen($lw) if $lw > 1; # Maintain backwards compatability with gd 1.8.4 # which does not support the ellipse methods. # can() method fails with GD::SVG... if ($gd->can('ellipse') || $gd =~ /SVG/ ) { $gd->filledEllipse($cx,$cy,$x2-$x1,$y2-$y1,$bg); # Draw the edge around the ellipse $gd->ellipse($cx,$cy,$x2-$x1,$y2-$y1,$fg); } else { $gd->arc($cx,$cy,$x2-$x1,$y2-$y1,0,360,$fg); $gd->fillToBorder($cx,$cy,$fg,$bg); } } sub oval { my $self = shift; my $gd = shift; my ($x1,$y1,$x2,$y2) = @_; my $cx = ($x1+$x2)/2; my $cy = ($y1+$y2)/2; my $fg = $self->fgcolor; my $linewidth = $self->linewidth; $fg = $self->set_pen($linewidth) if $linewidth > 1; # Maintain backwards compatability with gd 1.8.4 which does not # support the ellipse method. if ($gd->can('ellipse') || $gd =~ /SVG/ ) { $gd->ellipse($cx,$cy,$x2-$x1,$y2-$y1,$fg); } else { $gd->arc($cx,$cy,$x2-$x1,$y2-$y1,0,360,$fg); } } sub filled_arrow { my $self = shift; my $gd = shift; my $orientation = shift; my ($x1,$y1,$x2,$y2,$fg,$bg,$force) = @_; $orientation *= -1 if $self->{flip}; my ($width) = $gd->getBounds; my $indent = $y2-$y1 < $x2-$x1 ? $y2-$y1 : ($x2-$x1)/2; my $panel = $self->panel; my $offend_left = $x1 < $panel->pad_left; my $offend_right = $x2 > $panel->width + $panel->pad_left; return $self->filled_box($gd,@_) if !$force && (($orientation == 0) or ($x1 < 0 && $orientation < 0) or ($x2 > $width && $orientation > 0) or ($indent <= 0) or ($x2 - $x1 < 3) or ($offend_left && $orientation < 0) or ($offend_right && $orientation > 0)); $fg ||= $self->fgcolor; $bg ||= $self->bgcolor; my $lw = $self->option('linewidth') || 1; $fg = $self->set_pen($lw,$fg) if $lw > 1; my $pkg = $self->polygon_package; my $poly = $pkg->new(); if ($orientation >= 0) { $poly->addPt($x1,$y1); $poly->addPt($x2-$indent,$y1); $poly->addPt($x2,($y2+$y1)/2); $poly->addPt($x2-$indent,$y2); $poly->addPt($x1,$y2); } else { $poly->addPt($x2,$y1); $poly->addPt($x2,$y2); $poly->addPt($x1+$indent,$y2); $poly->addPt($x1,($y2+$y1)/2); $poly->addPt($x1+$indent,$y1); } $gd->filledPolygon($poly,$bg); $gd->polygon($poly,$fg); # blunt it a bit if off the end $self->blunt($gd,$x1,$y1,$x2,$y2,$bg,$fg,$lw) if ($offend_left && $orientation > 0) or ($offend_right && $orientation < 0); } sub linewidth { shift->option('linewidth') || 1; } sub font_width { my $self = shift; my $font = shift; $self->panel->string_width($font||$self->font,'m'); } sub font_height { my $self = shift; my $font = shift; $self->panel->string_height($font||$self->font,'hj'); } sub string_width { my $self = shift; my ($string,$font) = @_; $self->panel->string_width($font||$self->font,$string||'m'); } sub string_height { my $self = shift; my ($string,$font) = @_; $self->panel->string_height($font||$self->font,$string||'hj'); } sub fill { my $self = shift; my $gd = shift; my ($x1,$y1,$x2,$y2) = @_; if ( ($x2-$x1) >= 2 && ($y2-$y1) >= 2 ) { $gd->fill($x1+1,$y1+1,$self->bgcolor); } } sub set_pen { my $self = shift; my ($linewidth,$color) = @_; $linewidth ||= $self->linewidth; $color ||= $self->fgcolor; return $color unless $linewidth > 1; $self->panel->set_pen($linewidth,$color); } sub draw_component { my $self = shift; my ($gd,$left,$top,$partno,$total_parts) = @_; my($x1,$y1,$x2,$y2) = $self->bounds($left,$top); # clipping my $panel = $self->panel; return unless $x2 >= $panel->left and $x1 <= $panel->right; if ($self->stranded) { $self->filled_arrow($gd, $self->feature->strand, $x1, $y1, $x2, $y2) } else { $self->filled_box($gd, $x1, $y1, $x2, $y2) } } sub show_strand { my $self = shift; my $s = $self->option('strand_arrow'); return $s if defined $s; return $self->option('stranded'); } sub stranded { my $self = shift; my $s = $self->show_strand; return unless $s; return 1 unless $s eq 'ends'; my $f = $self->feature; my $strand = $f->strand; $strand *= -1 if $self->{flip}; my $part_no = $self->{partno}; my $parts = $self->{total_parts}; return ($strand > 0 && $part_no == $parts-1) || ($strand < 0 && $part_no == 0); } sub no_subparts { return shift->option('no_subparts'); } sub maxdepth { my $self = shift; my $maxdepth = $self->option('maxdepth'); return $maxdepth if defined $maxdepth; # $feature->compound is an artefact from aggregators. Sadly, an aggregated feature can miss # parts that are out of the query range - this is a horrible mis-feature. Aggregated features have # a compound flag to hack around this. my $feature = $self->feature; return 1 if $feature->can('compound') && $feature->compound; return; } sub feature_limit { return shift->option('feature_limit') || 0; } sub exceeds_depth { my $self = shift; my $max_depth = $self->maxdepth; return unless defined $max_depth; my $current_depth = $self->level || 0; return $current_depth >= $max_depth; } # memoize _subfeat -- it's a bottleneck with segments sub subfeat { my $self = shift; my $feature = shift; return $self->_subfeat($feature) unless ref $self; # protect against class invocation return if $self->level == 0 && $self->no_subparts; return if $self->exceeds_depth; return @{$self->{cached_subfeat}{$feature}} if exists $self->{cached_subfeat}{$feature}; my @ss = $self->_subfeat($feature); $self->{cached_subfeat}{$feature} = \@ss; @ss; } sub _subfeat { my $class = shift; my $feature = shift; return $feature->segments if $feature->can('segments'); my @split = eval { my $id = $feature->location->seq_id; my @subs = $feature->location->sub_Location; grep {$id eq $_->seq_id} @subs; }; return @split if @split; # Either the APIs have changed, or I got confused at some point... return $feature->get_SeqFeatures if $feature->can('get_SeqFeatures'); return $feature->sub_SeqFeature if $feature->can('sub_SeqFeature'); return; } # synthesize a key glyph sub keyglyph { my $self = shift; my $feature = $self->make_key_feature; my $factory = $self->factory->clone; $factory->set_option(label => 1); $factory->set_option(description => 0); $factory->set_option(bump => 0); $factory->set_option(connector => 'solid'); return $factory->make_glyph(0,$feature); } # synthesize a key glyph sub make_key_feature { my $self = shift; my $scale = 1/$self->scale; # base pairs/pixel # one segments, at pixels 0->80 my $offset = $self->panel->offset; my $feature = Bio::Graphics::Feature->new(-start =>0 * $scale +$offset, -end =>80*$scale+$offset, -name => $self->make_key_name(), -strand => '+1'); return $feature; } sub make_key_name { my $self = shift; # breaking encapsulation - this should be handled by the panel my $key = $self->option('key') || ''; return $key unless $self->panel->add_category_labels; my $category = $self->option('category'); my $name = defined $category ? "$key ($category)" : $key; return $name; } sub all_callbacks { my $self = shift; return $self->{all_callbacks} if exists $self->{all_callbacks}; # memoize return $self->{all_callbacks} = $self->_all_callbacks; } sub _all_callbacks { my $self = shift; my $track_level = $self->option('all_callbacks'); return $track_level if defined $track_level; return $self->panel->all_callbacks; } sub subpart_callbacks { my $self = shift; return $self->{subpart_callbacks} if exists $self->{subpart_callbacks}; # memoize return $self->{subpart_callbacks} = $self->_subpart_callbacks; } sub _subpart_callbacks { my $self = shift; return 1 if $self->all_callbacks; my $do_subparts = $self->option('subpart_callbacks'); return $self->{level} == 0 || ($self->{level} > 0 && $do_subparts); } sub default_factory { croak "no default factory implemented"; } sub finished { my $self = shift; delete $self->{factory}; foreach (@{$self->{parts} || []}) { $_->finished; } delete $self->{parts}; } ############################################################ # autogeneration of options documentation ############################################################ sub options { my $self = shift; my $seenit = shift || {}; no strict 'refs'; my $class = ref $self || $self; my $isa = "$class\:\:ISA"; $seenit->{$class}++; my $options = $self->my_options if defined &{"$class\:\:my_options"}; my @inherited_options; for my $base (@$isa) { next if $seenit->{$base}++; $base->can('options') or next; my $o = $base->options($seenit); push @inherited_options,%$o; } return wantarray ? ($options,{@inherited_options}) : {@inherited_options,%$options}; } sub options_usage { my $self = shift; my ($read,$write); pipe($read,$write); my $child = fork(); unless ($child) { close $read; print $write $self->options_pod; exit 0; } close $write; eval "use Pod::Usage"; pod2usage({-input =>$read, -verbose=>2, }); } sub options_man { my $self = shift; my $nroff; chomp($nroff = `which nroff`) if $ENV{SHELL}; unless ($nroff) { $self->options_usage; return; } my $class = ref $self || $self; my $extra = ''; if ($ENV{TERM} && $ENV{TERM}=~/^(xterm|vt10)/) { my ($pager) = grep {`which $_`} ($ENV{PAGER},'less','more'); $extra = "|$pager"; } open my $fh,"| pod2man -n $class | $nroff -man $extra" or die; print $fh $self->options_pod; close $fh; # exit 0 ?? } sub options_pod { my $self = shift; my ($new_options,$old_options) = $self->options; my $class = ref $self || $self; my ($glyph_name) = $class =~ /([^:]+)$/; my $description = join "\n",$self->my_description; my $pod = ''; $pod .= "=head1 NAME\n\n"; $pod .= < glyph. END ; $pod .= "=head1 SYNOPSIS\n\n"; $pod .= <<"END"; $description See the L manual page for full details. \$panel->add_track(\$features, -glyph => $glyph_name, -option1 => \$value1, -option2 => \$value2...); To experiment with this glyph\'s options, use the glyph_help.pl script with either the -v or -p switch. Run "glyph_help -help" for details. END ; $pod .= "=head1 OPTIONS DEFINED IN THIS GLYPH\n\n"; $pod .= "Glyph-specific options for the I<$glyph_name> glyph:\n\n"; $pod .= "=over 4\n\n"; $pod .= $self->_pod_options($new_options || {}); $pod .= "=back\n\n"; $pod .= "=head1 INHERITED OPTIONS\n\n"; $pod .= "Options inherited from more general glyph classes:\n\n"; $pod .= "=over 4\n\n"; $pod .= $self->_pod_options($old_options || {}); $pod .= "=back\n\n"; $pod .= "=head1 COLOR OPTIONS\n\n"; $pod .= "The following list of named colors can be used as an argument to any option "; $pod .= "that takes a color:\n\n"; eval "require Bio::Graphics::Panel" unless Bio::Graphics::Panel->can('color_names'); for my $c (sort Bio::Graphics::Panel->color_names) { $pod .= " $c\n"; } $pod; } sub _pod_options { my $self = shift; my $options = shift; my $pod = %$options ? '' : "B<(none)>\n\n"; for my $option (sort keys %$options) { my ($range,$default,@description) = @{$options->{$option}}; $default = $range eq 'boolean' ? "'undef' (false)" : "'undef'" unless defined $default; $default = "1 (true)" if $range eq 'boolean' && $default == 1; $range = join ', ',map {"'$_'"} @$range if ref $range eq 'ARRAY'; $pod .= "=item B<-$option> <$range> [default $default]\n\n"; $pod .= join "\n",@description; if ($range eq 'font') { $pod .= "\nValid choices: 'gdTinyFont', 'gdSmallFont', 'gdMediumBoldFont', 'gdLargeFont', 'gdGiantFont'"; } elsif ($range eq 'color') { $pod .= "\nSee next section for color choices.\n"; } $pod .= "\n\n"; } return $pod; } # normalizer for memoize sub _normalize_objects { my ($obj,$option_name) = @_; my @args = (%$obj,$option_name); return "@args"; } 1; __END__ =head1 NAME Bio::Graphics::Glyph - Base class for Bio::Graphics::Glyph objects =head1 SYNOPSIS See L. =head1 DESCRIPTION Bio::Graphics::Glyph is the base class for all glyph objects. Each glyph is a wrapper around an Bio:SeqFeatureI object, knows how to render itself on an Bio::Graphics::Panel, and has a variety of configuration variables. End developers will not ordinarily work directly with Bio::Graphics::Glyph objects, but with Bio::Graphics::Glyph::generic and its subclasses. Similarly, most glyph developers will want to subclass from Bio::Graphics::Glyph::generic because the latter provides labeling and arrow-drawing facilities. =head1 METHODS This section describes the class and object methods for Bio::Graphics::Glyph. =head2 CONSTRUCTORS Bio::Graphics::Glyph objects are constructed automatically by an Bio::Graphics::Glyph::Factory, and are not usually created by end-developer code. =over 4 =item $glyph = Bio::Graphics::Glyph-Enew(-feature=E$feature,-factory=E$factory) Given a sequence feature, creates an Bio::Graphics::Glyph object to display it. The B<-feature> argument points to the Bio:SeqFeatureI object to display, and B<-factory> indicates an Bio::Graphics::Glyph::Factory object from which the glyph will fetch all its run-time configuration information. Factories are created and manipulated by the Bio::Graphics::Panel object. A standard set of options are recognized. See L. =back =head2 OBJECT METHODS Once a glyph is created, it responds to a large number of methods. In this section, these methods are grouped into related categories. Retrieving glyph context: =over 4 =item $factory = $glyph-Efactory Get the Bio::Graphics::Glyph::Factory associated with this object. This cannot be changed once it is set. =item $panel = $glyph-Epanel Get the Bio::Graphics::Panel associated with this object. This cannot be changed once it is set. =item $feature = $glyph-Efeature Get the sequence feature associated with this object. This cannot be changed once it is set. =item $feature = $glyph-Eparent_feature() Within callbacks only, the parent_feature() method returns the parent of the current feature, if there is one. Called with a numeric argument, ascends the parentage tree: parent_feature(1) will return the parent, parent_feature(2) will return the grandparent, etc. If there is no parent, returns undef. =item $feature = $glyph-Eadd_feature(@features) Add the list of features to the glyph, creating subparts. This is most common done with the track glyph returned by Bio::Graphics::Panel-Eadd_track(). If the Bio::Graphics::Panel was initialized with B<-feature_limit> set to a non-zero value, then calls to a track glyph's add_feature() method will maintain a count of features added to the track. Once the feature count exceeds the value set in -feature_limit, additional features will displace existing ones in a way that effects a uniform sampling of the total feature set. This is useful to protect against excessively large tracks. The total number of features added can be retrieved by calling the glyph's feature_count() method. =item $feature = $glyph-Eadd_group(@features) This is similar to add_feature(), but the list of features is treated as a group and can be configured as a set. =item $glyph-Efinished When you are finished with a glyph, you can call its finished() method in order to break cycles that would otherwise cause memory leaks. finished() is typically only used by the Panel object. =item $subglyph = $glyph-Emake_subglyph($level,@sub_features) This method is called to create subglyphs from a list of subfeatures. The $level indicates the current level of the glyph (top-level glyphs are level 0, subglyphs are level 1, etc). Ordinarily this method simply calls $self-Efactory-Emake_subglyph($level,@sub_features). Override it in subclasses to create subglyphs of a particular type. For example: sub make_subglyph { my $self = shift; my $level = shift; my $factory = $self->factory; $factory->make_glyph($factory,'arrow',@_); } =item $count = $glyph-Efeature_count() Return the number of features added to this glyph via add_feature(). =item $flag = $glyph->features_clipped() If the panel was initialized with -feature_limit set to a non-zero value, then calls to add_features() will limit the number of glyphs to the indicated value. If this value was exceeded, then features_clipped() will return true. =back Retrieving glyph options: =over 4 =item $fgcolor = $glyph-Efgcolor =item $bgcolor = $glyph-Ebgcolor =item $fontcolor = $glyph-Efontcolor =item $fontcolor = $glyph-Efont2color =item $fillcolor = $glyph-Efillcolor These methods return the configured foreground, background, font, alternative font, and fill colors for the glyph in the form of a GD::Image color index. =item $color = $glyph-Etkcolor This method returns a color to be used to flood-fill the entire glyph before drawing (currently used by the "track" glyph). =item ($left,$top,$right,$bottom) = $glyph-Ebounds($dx,$dy) Given the topleft coordinates of the glyph, return the bounding box of its contents, exclusive of padding. This is typically called by the draw() and draw_component() methods to recover the position of the glyph. =item ($left,$top,$right,$bottom) = $glyph-Ecalculate_boundaries($dx,$dy) An alias for bounds(), used by some glyphs for compatibility with older versions of this module. =item $width = $glyph-Ewidth([$newwidth]) Return the width of the glyph, not including left or right padding. This is ordinarily set internally based on the size of the feature and the scale of the panel. =item $width = $glyph-Elayout_width Returns the width of the glyph including left and right padding. =item $width = $glyph-Eheight Returns the height of the glyph, not including the top or bottom padding. This is calculated from the "height" option and cannot be changed. =item $font = $glyph-Efont Return the font for the glyph. =item $option = $glyph-Eoption($option) Return the value of the indicated option. =item $index = $glyph-Ecolor($option_name) Given an option name that corresponds to a color (e.g. 'fgcolor') look up the option and translate it into a GD color index. =item $index = $glyph-Etranslate_color($color) Given a symbolic or #RRGGBB-form color name, returns its GD index. =item $level = $glyph-Elevel The "level" is the nesting level of the glyph. Groups are level -1, top level glyphs are level 0, subparts (e.g. exons) are level 1 and so forth. =item @parts = $glyph-Eparts For glyphs that can contain subparts (e.g. the segments glyph), this method will return the list of subglyphs it contains. Subglyphs are created automatically by the new() method and are created subject to the maximum recursion depth specified by the maxdepth() method and/or the -maxdepth option. =back Setting an option: =over 4 =item $glyph-Econfigure(-name=E$value) You may change a glyph option after it is created using set_option(). This is most commonly used to configure track glyphs. =back Retrieving information about the sequence: =over 4 =item $start = $glyph-Estart =item $end = $glyph-Eend These methods return the start and end of the glyph in base pair units. =item $offset = $glyph-Eoffset Returns the offset of the segment (the base pair at the far left of the image). =item $length = $glyph-Elength Returns the length of the sequence segment. =back Retrieving formatting information: =over 4 =item $top = $glyph-Etop =item $left = $glyph-Eleft =item $bottom = $glyph-Ebottom =item $right = $glyph-Eright These methods return the top, left, bottom and right of the glyph in pixel coordinates. =item $height = $glyph-Eheight Returns the height of the glyph. This may be somewhat larger or smaller than the height suggested by the GlyphFactory, depending on the type of the glyph. =item $scale = $glyph-Escale Get the scale for the glyph in pixels/bp. =item $height = $glyph-Elabelheight Return the height of the label, if any. =item $label = $glyph-Elabel Return a human-readable label for the glyph. =back These methods are called by Bio::Graphics::Track during the layout process: =over 4 =item $glyph-Emove($dx,$dy) Move the glyph in pixel coordinates by the indicated delta-x and delta-y values. =item ($x1,$y1,$x2,$y2) = $glyph-Ebox Return the current position of the glyph. =back These methods are intended to be overridden in subclasses: =over 4 =item $glyph-Ecalculate_height Calculate the height of the glyph. =item $glyph-Ecalculate_left Calculate the left side of the glyph. =item $glyph-Ecalculate_right Calculate the right side of the glyph. =item $glyph-Edraw($gd,$left,$top) Optionally offset the glyph by the indicated amount and draw it onto the GD::Image object. =item $glyph-Edraw_label($gd,$left,$top) Draw the label for the glyph onto the provided GD::Image object, optionally offsetting by the amounts indicated in $left and $right. =item $glyph-Emaxdepth() This returns the maximum number of levels of feature subparts that the glyph will recurse through. For example, returning 0 indicates that the glyph will only draw the top-level feature. Returning 1 indicates that it will only draw the top-level feature and one level of subfeatures. Returning 2 will descend down two levels. Overriding this method will speed up rendering by avoiding creating of a bunch of subglyphs that will never be drawn. The default behavior is to return undef (unlimited levels of descent) unless the -maxdepth option is passed, in which case this number is returned. Note that Bio::Graphics::Glyph::generic overrides maxdepth() to return 0, meaning no descent into subparts will be performed. =back These methods are useful utility routines: =over 4 =item @pixels = $glyph-Emap_pt(@bases); Map the list of base position, given in base pair units, into pixels, using the current scale and glyph position. This method will accept a single base position or an array. =item $glyph-Efilled_box($gd,$x1,$y1,$x2,$y2) Draw a filled rectangle with the appropriate foreground and fill colors, and pen width onto the GD::Image object given by $gd, using the provided rectangle coordinates. =item $glyph-Efilled_oval($gd,$x1,$y1,$x2,$y2) As above, but draws an oval inscribed on the rectangle. =item $glyph-Eexceeds_depth Returns true if descending into another level of subfeatures will exceed the value returned by maxdepth(). =back =head2 OPTIONS The following options are standard among all Glyphs. See individual glyph pages for more options. Also try out the glyph_help.pl script, which attempts to document each glyph's shared and specific options and provides an interface for graphically inspecting the effect of different options. Option Description Default ------ ----------- ------- -fgcolor Foreground color black -bgcolor Background color turquoise -fillcolor Synonym for -bgcolor -linewidth Line width 1 -height Height of glyph 10 -font Glyph font gdSmallFont -connector Connector type undef (false) -connector_color Connector color black -strand_arrow Whether to indicate undef (false) strandedness -stranded Whether to indicate undef (false) strandedness (same as above)) -label Whether to draw a label undef (false) -description Whether to draw a description undef (false) -no_subparts Set to true to prevent undef (false) drawing of the subparts of a feature. -ignore_sub_part Give the types/methods of undef subparts to ignore (as a space delimited list). -maxdepth Specifies the maximum number undef (unlimited) child-generations to decend when getting subfeatures -sort_order Specify layout sort order "default" -always_sort Sort even when bumping is off undef (false) -bump_limit Maximum number of levels to bump undef (unlimited) -hilite Highlight color undef (no color) -link, -title, -target These options are used when creating imagemaps for display on the web. See L. For glyphs that consist of multiple segments, the B<-connector> option controls what's drawn between the segments. The default is undef (no connector). Options include: "hat" an upward-angling conector "solid" a straight horizontal connector "quill" a decorated line with small arrows indicating strandedness (like the UCSC Genome Browser uses) "dashed" a horizontal dashed line. "crossed" a straight horizontal connector with an "X" on it (Can be used when segments are not yet validated by some internal experiments...) The B<-connector_color> option controls the color of the connector, if any. The label is printed above the glyph. You may pass an anonymous subroutine to B<-label>, in which case the subroutine will be invoked with the feature as its single argument and is expected to return the string to use as the label. If you provide the numeric value "1" to B<-label>, the label will be read off the feature's seqname(), info() and primary_tag() methods will be called until a suitable name is found. To create a label with the text "1", pass the string "1 ". (A 1 followed by a space). The description is printed below the glyph. You may pass an anonymous subroutine to B<-description>, in which case the subroutine will be invoked with the feature as its single argument and is expected to return the string to use as the description. If you provide the numeric value "1" to B<-description>, the description will be read off the feature's source_tag() method. To create a description with the text "1", pass the string "1 ". (A 1 followed by a space). In the case of ACEDB Ace::Sequence feature objects, the feature's info(), Brief_identification() and Locus() methods will be called to create a suitable description. The B<-strand_arrow> option, if true, requests that the glyph indicate which strand it is on, usually by drawing an arrowhead. Not all glyphs will respond to this request. For historical reasons, B<-stranded> is a synonym for this option. Multisegmented features will draw an arrowhead on each component unless you specify a value of "ends" to -strand_arrow, in which case only the rightmost component (for + strand features) or the leftmost component (for - strand features) will have arrowheads. B: By default, features are drawn with a layout based only on the position of the feature, assuring a maximal "packing" of the glyphs when bumped. In some cases, however, it makes sense to display the glyphs sorted by score or some other comparison, e.g. such that more "important" features are nearer the top of the display, stacked above less important features. The -sort_order option allows a few different built-in values for changing the default sort order (which is by "left" position): "low_score" (or "high_score") will cause features to be sorted from lowest to highest score (or vice versa). "left" (or "default") and "right" values will cause features to be sorted by their position in the sequence. "longest" (or "shortest") will cause the longest (or shortest) features to be sorted first, and "strand" will cause the features to be sorted by strand: "+1" (forward) then "0" (unknown, or NA) then "-1" (reverse). Finally, "name" will sort by the display_name of the features. In all cases, the "left" position will be used to break any ties. To break ties using another field, options may be strung together using a "|" character; e.g. "strand|low_score|right" would cause the features to be sorted first by strand, then score (lowest to highest), then by "right" position in the sequence. Finally, a subroutine coderef with a $$ prototype can be provided. It will receive two B as arguments and should return -1, 0 or 1 (see Perl's sort() function for more information). For example, to sort a set of database search hits by bits (stored in the features' "score" fields), scaled by the log of the alignment length (with "start" position breaking any ties): sort_order = sub ($$) { my ($glyph1,$glyph2) = @_; my $a = $glyph1->feature; my $b = $glyph2->feature; ( $b->score/log($b->length) <=> $a->score/log($a->length) ) || ( $a->start <=> $b->start ) } It is important to remember to use the $$ prototype as shown in the example. Otherwise Bio::Graphics will quit with an exception. The arguments are subclasses of Bio::Graphics::Glyph, not the features themselves. While glyphs implement some, but not all, of the feature methods, to be safe call the two glyphs' feature() methods in order to convert them into the actual features. The '-always_sort' option, if true, will sort features even if bumping is turned off. This is useful if you would like overlapping features to stack in a particular order. Features towards the end of the list will overlay those towards the beginning of the sort order. The B<-hilite> option draws a colored box behind each feature using the indicated color. Typically you will pass it a code ref that returns a color name. For example: -hilite => sub { my $name = shift->display_name; return 'yellow' if $name =~ /XYZ/ } The B<-no_subparts> option will prevent the glyph from searching its feature for subfeatures. This may enhance performance if you know in advance that none of your features contain subfeatures. =head1 SUBCLASSING Bio::Graphics::Glyph By convention, subclasses are all lower-case. Begin each subclass with a preamble like this one: package Bio::Graphics::Glyph::crossbox; use strict; use base qw(Bio::Graphics::Glyph); Then override the methods you need to. Typically, just the draw() method will need to be overridden. However, if you need additional room in the glyph, you may override calculate_height(), calculate_left() and calculate_right(). Do not directly override height(), left() and right(), as their purpose is to cache the values returned by their calculating cousins in order to avoid time-consuming recalculation. A simple draw() method looks like this: sub draw { my $self = shift; $self->SUPER::draw(@_); my $gd = shift; # and draw a cross through the box my ($x1,$y1,$x2,$y2) = $self->calculate_boundaries(@_); my $fg = $self->fgcolor; $gd->line($x1,$y1,$x2,$y2,$fg); $gd->line($x1,$y2,$x2,$y1,$fg); } This subclass draws a simple box with two lines criss-crossed through it. We first call our inherited draw() method to generate the filled box and label. We then call calculate_boundaries() to return the coordinates of the glyph, disregarding any extra space taken by labels. We call fgcolor() to return the desired foreground color, and then call $gd-Eline() twice to generate the criss-cross. For more complex draw() methods, see Bio::Graphics::Glyph::transcript and Bio::Graphics::Glyph::segments. Please avoid using a specific image class (via "use GD" for example) within your glyph package. Instead, rely on the image package passed to the draw() method. This approach allows for future expansion of supported image classes without requiring glyph redesign. If you need access to the specific image classes such as Polygon, Image, or Font, generate them like such: sub draw { my $self = shift; my $image_class = shift; my $polygon_package = $self->polygon_package->new() ... } =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Lincoln Stein Elstein@cshl.orgE Copyright (c) 2001 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Bio-Graphics-2.37/lib/Bio/Graphics/Feature.pm�������������������������������������������������������000555��001750��001750�� 27513�12165075746� 21050� 0����������������������������������������������������������������������������������������������������ustar�00lstein��������������������������lstein��������������������������000000��000000�������������������������������������������������������������������������������������������������������������������������������������������������������������������������package Bio::Graphics::Feature; =head1 NAME Bio::Graphics::Feature - A simple feature object for use with Bio::Graphics::Panel =head1 SYNOPSIS use Bio::Graphics::Feature; # create a simple feature with no internal structure $f = Bio::Graphics::Feature->new(-start => 1000, -stop => 2000, -type => 'transcript', -name => 'alpha-1 antitrypsin', -desc => 'an enzyme inhibitor', ); # create a feature composed of multiple segments, all of type "similarity" $f = Bio::Graphics::Feature->new(-segments => [[1000,1100],[1500,1550],[1800,2000]], -name => 'ABC-3', -type => 'gapped_alignment', -subtype => 'similarity'); # build up a gene exon by exon $e1 = Bio::Graphics::Feature->new(-start=>1,-stop=>100,-type=>'exon'); $e2 = Bio::Graphics::Feature->new(-start=>150,-stop=>200,-type=>'exon'); $e3 = Bio::Graphics::Feature->new(-start=>300,-stop=>500,-type=>'exon'); $f = Bio::Graphics::Feature->new(-segments=>[$e1,$e2,$e3],-type=>'gene'); =head1 DESCRIPTION This is a simple Bio::SeqFeatureI-compliant object that is compatible with Bio::Graphics::Panel. With it you can create lightweight feature objects for drawing. All methods are as described in L with the following additions: =head2 The new() Constructor $feature = Bio::Graphics::Feature->new(@args); This method creates a new feature object. You can create a simple feature that contains no subfeatures, or a hierarchically nested object. Arguments are as follows: -seq_id the reference sequence -start the start position of the feature -end the stop position of the feature -stop an alias for end -name the feature name (returned by seqname()) -type the feature type (returned by primary_tag()) -primary_tag the same as -type -source the source tag -score the feature score (for GFF compatibility) -desc a description of the feature -segments a list of subfeatures (see below) -subtype the type to use when creating subfeatures -strand the strand of the feature (one of -1, 0 or +1) -phase the phase of the feature (0..2) -id an alias for -name -seqname an alias for -name -display_id an alias for -name -display_name an alias for -name (do you get the idea the API has changed?) -primary_id unique database ID -url a URL to link to when rendered with Bio::Graphics -configurator an object (like a Bio::Graphics::FeatureFile) that knows how to configure the graphical representation of the object based on its type. -attributes a hashref of tag value attributes, in which the key is the tag and the value is an array reference of values -factory a reference to a feature factory, used for compatibility with more obscure parts of Bio::DB::GFF The subfeatures passed in -segments may be an array of Bio::Graphics::Feature objects, or an array of [$start,$stop] pairs. Each pair should be a two-element array reference. In the latter case, the feature type passed in -subtype will be used when creating the subfeatures. If no feature type is passed, then it defaults to "feature". =head2 Non-SeqFeatureI methods A number of new methods are provided for compatibility with Ace::Sequence, which has a slightly different API from SeqFeatureI: =over 4 =item attributes() An alternative interface to get_tag_values. Pass the name of an attribute to get the value(s) of that attribute: $expression_level = $gene->attributes('expression'); Call attributes() without any arguments to get a hash of all attributes: %attributes = $gene->attributes; =item url() Get/set the URL that the graphical rendering of this feature will link to. =item add_segment(@segments) Add one or more segments (a subfeature). Segments can either be Feature objects, or [start,stop] arrays, as in the -segments argument to new(). The feature endpoints are automatically adjusted. =item my @features = get_SeqFeatures('type1','type2','type3'...) Get the subfeatures of this feature. If an optional list of types is provided, then only returns subfeatures with the indicated primary_tag. (This is an extension of the Bio::SeqFeatureI interface). =item $feature->add_hit($hit) For nucleotide alignments, add a feature that is a "hit" on the feature. =item $hit = $feature->hit Return the hit. =cut sub add_hit { my $self = shift; my $hit = shift; $self->{_hit} = $hit; } sub hit { shift->{_hit} } sub get_SeqFeatures { my $self = shift; my %filter = map {$_=>1} @_; my @pieces = %filter ? grep {$filter{$_->primary_tag}} $self->SUPER::get_SeqFeatures() : $self->SUPER::get_SeqFeatures; return @pieces; } sub each_tag_value { my $self = shift; my $tag = shift; my $value = $self->{attributes}{$tag} or return; my $ref = CORE::ref $value; return $ref && $ref eq 'ARRAY' ? @{$self->{attributes}{$tag}} : $self->{attributes}{$tag}; } =item segments() An alias for get_SeqFeatures(). =item get_all_SeqFeatures() Alias for get_SeqFeatures() =item merged_segments() Another alias for sub_SeqFeature(). =item stop() An alias for end(). =item name() An alias for seqname(). =item exons() An alias for sub_SeqFeature() (you don't want to know why!) =item configurator() Get/set the configurator that knows how to adjust the graphical representation of this feature based on its type. Currently the only configurator that will work is Bio::Graphics::FeatureFile. =back =cut use strict; use base 'Bio::SeqFeature::Lite'; # usage: # Bio::Graphics::Feature->new( # -start => 1, # -end => 100, # -name => 'fred feature', # -strand => +1); # # Alternatively, use -segments => [ [start,stop],[start,stop]...] # to create a multisegmented feature. sub new { my $self = shift->SUPER::new(@_); my %arg = @_; for my $option (qw(factory configurator)) { $self->{$option} = $arg{"-$option"} if exists $arg{"-$option"}; } $self; } =head2 factory Title : factory Usage : $factory = $obj->factory([$new_factory]) Function: Returns the feature factory from which this feature was generated. Mostly for compatibility with weird dependencies in gbrowse. Returns : A feature factory Args : None =cut sub factory { my $self = shift; my $d = $self->{factory}; $self->{factory} = shift if @_; $d; } =head2 display_name Title : display_name Usage : $id = $obj->display_name or $obj->display_name($newid); Function: Gets or sets the display id, also known as the common name of the Seq object. The semantics of this is that it is the most likely string to be used as an identifier of the sequence, and likely to have "human" readability. The id is equivalent to the LOCUS field of the GenBank/EMBL databanks and the ID field of the Swissprot/sptrembl database. In fasta format, the >(\S+) is presumed to be the id, though some people overload the id to embed other information. Bioperl does not use any embedded information in the ID field, and people are encouraged to use other mechanisms (accession field for example, or extending the sequence object) to solve this. Notice that $seq->id() maps to this function, mainly for legacy/convenience issues. Returns : A string Args : None or a new id =head2 accession_number Title : accession_number Usage : $unique_biological_key = $obj->accession_number; Function: Returns the unique biological id for a sequence, commonly called the accession_number. For sequences from established databases, the implementors should try to use the correct accession number. Notice that primary_id() provides the unique id for the implemetation, allowing multiple objects to have the same accession number in a particular implementation. For sequences with no accession number, this method should return "unknown". Returns : A string Args : None =head2 alphabet Title : alphabet Usage : if( $obj->alphabet eq 'dna' ) { /Do Something/ } Function: Returns the type of sequence being one of 'dna', 'rna' or 'protein'. This is case sensitive. This is not called because this would cause upgrade problems from the 0.5 and earlier Seq objects. Returns : a string either 'dna','rna','protein'. NB - the object must make a call of the type - if there is no type specified it has to guess. Args : none Status : Virtual =head2 desc Title : desc Usage : $seqobj->desc($string) or $seqobj->desc() Function: Sets or gets the description of the sequence Example : Returns : The description Args : The description or none =head2 location Title : location Usage : my $location = $seqfeature->location() Function: returns a location object suitable for identifying location of feature on sequence or parent feature Returns : Bio::LocationI object Args : none =head2 location_string Title : location_string Usage : my $string = $seqfeature->location_string() Function: Returns a location string in a format recognized by gbrowse Returns : a string Args : none This is a convenience function used by the generic genome browser. It returns the location of the feature and its subfeatures in the compact form "start1..end1,start2..end2,...". Use $seqfeature-Elocation()-EtoFTString() to obtain a standard GenBank/EMBL location representation. =head2 configurator Title : configurator Usage : my $configurator = $seqfeature->configurator([$new_configurator]) Function: Get/set an object that provides configuration information for this feature Returns : configurator object Args : new configurator object (optional) A configurator object provides hints to the Bio::Graphics::Feature as to how to display itself on a canvas. Currently this stores the Bio::Graphics::FeatureFile and descendents. =cut # get/set the configurator (Bio::Graphics::FeatureFile) for this feature sub configurator { my $self = shift; my $d = $self->{configurator}; $self->{configurator} = shift if @_; $d; } =head2 url Title : url Usage : my $url = $seqfeature->url([$new_url]) Function: Get/set the URL associated with this feature Returns : a URL string Args : new URL (optional) Features link to URLs when displayed as a clickable image map. This field holds that information. =cut # get/set the url for this feature sub url { my $self = shift; my $d = $self->{url}; $self->{url} = shift if @_; $d; } =head2 make_link Title : make_link Usage : my $url = $seqfeature->make_link() Function: Create a URL for the feature Returns : a URL string Args : none This method will invoke the configurator in order to turn the feature into a link. Used by Bio::Graphics::Panel to create imagemaps. =cut # make a link sub make_link { my $self = shift; if (my $url = $self->url) { return $url; } elsif (my $configurator = $self->configurator) { return $configurator->make_link($self) if $configurator->can('make_link'); } else { return; } } 1; __END__ =head1 SEE ALSO L,L, L L =head1 AUTHOR Lincoln Stein Elstein@cshl.eduE. Copyright (c) 2001 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Bio-Graphics-2.37/lib/Bio/Graphics/FeatureDir.pm����������������������������������������������������000555��001750��001750�� 15044�12165075746� 21503� 0����������������������������������������������������������������������������������������������������ustar�00lstein��������������������������lstein��������������������������000000��000000�������������������������������������������������������������������������������������������������������������������������������������������������������������������������package Bio::Graphics::FeatureDir; =head1 NAME Bio::Graphics::FeatureDir -- A directory of feature files and conf files =head1 SYNOPSIS my $fd = Bio::Graphics::FeatureDir->new('/path/to/dir'); $fd->add_file('tracks.conf'); $fd->add_file('foo.gff3'); $fd->add_file('foo.wig'); $fd->add_fh(\*STDIN); my $option = $fd->setting('EST' => 'bgcolor'); my @features = $fd->get_features_by_name('M101'); =head1 DESCRIPTION This class implements most of the methods of Bio::Graphics::FeatureFile, but stores the data files and features in a directory indexed by the Bio::DB::SeqFeature::Store::berkeleydb adaptor. Therefore it is fast. =head2 Methods =over 4 =cut use strict; use warnings; use base 'Bio::Graphics::FeatureFile'; use Bio::DB::SeqFeature::Store; use File::Path; use File::Spec; use File::Basename 'basename'; use File::Temp 'tempdir','mktemp'; use Carp 'croak'; =item $fd = Bio::Graphics::FeatureDir->new('/path/to/dir'); =item $fd = Bio::Graphics::FeatureDir->new(-dir => '/path/to/dir'); Create a new FeatureDir, based in the indicated directory. In addition to the -dir directory argument, it takes any of the options that can be passed to Bio::Graphics::FeatureFile except for the -file and -text arguments; =cut sub new { my $class = shift; my %args; if (@_ == 1) { %args = (-dir=>shift); } else { %args = @_; } $args{-dir} ||= tempdir(CLEANUP=>1); delete $args{-file}; delete $args{-text}; my $self = $class->SUPER::new(%args); $self->{dir} = $args{-dir}; $self->{verbose} = $args{-verbose}; $self->_init_featuredb; $self->_init_conf; return $self; } =item $db->_init_featuredb Internal method. Initializes the underlying feature database. =cut sub _init_featuredb { my $self = shift; my $dir = $self->dir; my $needs_init = $self->_maybe_create_dir($dir); my $index = File::Spec->catfile($dir,"indexes"); $needs_init++ unless -e $index; my @args = (-adaptor => 'berkeleydb', -dir => $dir, -write => 1, ); push @args,(-create => 1) if $needs_init; push @args,(-verbose => 1) if $self->{verbose}; $self->{db} = Bio::DB::SeqFeature::Store->new(@args) or die "Couldn't initialize database"; return $self->{db}; } =item $db->_init_conf Internal method -- initialize the configuration file(s) =cut sub _init_conf { my $self = shift; my $dir = $self->dir; my $needs_init = $self->_maybe_create_dir($dir); my $master_conf = File::Spec->catfile($dir,'indexes','master.conf'); $needs_init++ unless -e $master_conf; if ($needs_init) { open my $fh,'>',$master_conf or die "$master_conf: $!"; my $pack = __PACKAGE__; print $fh <{conf} = Bio::Graphics::FeatureFile->new(-file=>$master_conf); } =item $created = $db->_maybe_create_dir($dir) Create $dir and its parents if it doesn't exist. Return true if the directory was created. Throws an exception on filesystem errors. =cut sub _maybe_create_dir { my $self = shift; my $dir = shift || $self->{dir}; unless (-e $dir && -d $dir) { mkpath($dir) or die "Couldn't create directory $dir: $!"; return 1; } return; } =item $dir = $db->dir Returns the base directory. =cut sub dir {shift->{dir}} =item $conf = $db->conf Returns the underlying Bio::Graphics::FeatureFile object =cut sub conf {shift->{conf}} =back =item $db = $db->db Returns the underlying Bio::DB::SeqFeature::Store object =cut sub db { my $self = shift; return $self->{db} ||= Bio::DB::SeqFeature::Store->new( -adaptor => 'berkeleydb', -dir => $self->dir, -write => 1); } =item $db->add_file($file) Add the file to the directory. Can add files of type .fa, .gff, .gff3, .conf and .ff. =cut sub add_file { my $self = shift; my $file = shift; my $basename = basename $file; open my $fh,$file or croak "Couldn't open $file: $!"; $self->add_fh($fh,$basename); close $fh; } =item $db->add_fh(\*FILEHANDLE [,'name']) Add the contents of the indicated filehandle to repository. Name is optional; if provided it will be used as the base for all files created. =cut sub add_fh { my $self = shift; my ($fh,$name) = @_; $name =~ s/\.\w+$//; # get rid of extensions $name ||= mktemp('XXXXXXXX'); # status == unknown # config # gff3 # gff2 # ff # wiggle # fasta my ($status,$new_status); my $dir = $self->dir; my %splitter; while (<$fh>) { # figure out transitions $new_status = /^\#\#gff-version\s+3/i ? 'gff3' :/^\#\#gff/i ? 'gff2' : /^track/i ? 'wig' : /^\[(.+)\]/i ? 'conf' : /^>\w+/i ? 'fa' : /^reference/i ? 'ff' : undef; unless ($status || $new_status) { # guess what it is my @tokens = split /\s+/; $new_status = 'gff3' if @tokens >= 9 && $tokens[8] =~ /=/; $new_status = 'ff' if $tokens[2] =~ /\d+(\.\.|-)\d+/; } if ($new_status) { # this will create a new conf file for each section if ($new_status eq 'conf') { $splitter{conf} = Bio::Graphics::FileSplitter->new( File::Spec->catfile($dir,"${name}.$1.${new_status}")); } else { $splitter{$new_status} ||= Bio::Graphics::FileSplitter->new( File::Spec->catfile($dir,"${name}.${new_status}")); } $status = $new_status; } next unless $splitter{$status}; $splitter{$status}->write($_); } undef %splitter; $self->db->auto_reindex($dir); $self->_init_conf; } package Bio::Graphics::FileSplitter; sub new { my $class = shift; my $path = shift; open my $fh,'>',$path or die "Could not open $path for writing: $!"; return bless {fh=>$fh},ref $class || $class; } sub write { my $self = shift; $self->{fh}->print($_) foreach @_; } sub DESTROY { my $fh = shift->{fh}; close $fh if $fh; } =cut =head1 SEE ALSO L, L =head1 AUTHOR Lincoln Stein Elincoln.stein@gmail.comE. Copyright (c) 2009 Ontario Institute for Cancer Research This package and its accompanying libraries is free software; you can redistribute it and/or modify it under the terms of the GPL (either version 1, or at your option, any later version) or the Artistic License 2.0. Refer to LICENSE for the full license text. In addition, please see DISCLAIMER.txt for disclaimers of warranty. =cut 1; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Bio-Graphics-2.37/lib/Bio/Graphics/Layout.pm��������������������������������������������������������000555��001750��001750�� 35534�12165075746� 20734� 0����������������������������������������������������������������������������������������������������ustar�00lstein��������������������������lstein��������������������������000000��000000�������������������������������������������������������������������������������������������������������������������������������������������������������������������������package Bio::Graphics::Layout; # shamelessly stolen from Mitch Skinner's JBrowse package and ported to perl. # Original copyright here #Copyright (c) 2007-2010 The Evolutionary Software Foundation # #Created by Mitchell Skinner # #This package and its accompanying libraries are free software; you can #redistribute it and/or modify it under the terms of the LGPL (either #version 2.1, or at your option, any later version) or the Artistic #License 2.0. Refer to LICENSE for the full license text. use strict; # /* # * Code for laying out rectangles, given that layout is also happening # * in adjacent blocks at the same time # * # * This code does a lot of linear searching; n should be low enough that # * it's not a problem but if it turns out to be, some of it can be changed to # * binary searching without too much work. Another possibility is to merge # * contour spans and give up some packing closeness in exchange for speed # * (the code already merges spans that have the same x-coord and are vertically # * contiguous). # */ sub new { my $class = shift; my ($leftBound, $rightBound) = @_; my $self = bless {},ref $class || $class; $self->{leftBound} = $leftBound; $self->{rightBound} = $rightBound; # a Layout contains a left contour and a right contour; # the area between the contours is allocated, and the # area outside the contours is free. $self->{leftContour} = Bio::Graphics::Layout::Contour->new(); $self->{rightContour} = Bio::Graphics::Layout::Contour->new(); $self->{seen} = {}; $self->{leftOverlaps} = []; $self->{rightOverlaps} = []; $self->{totalHeight} = 0; return $self; } sub totalHeight {shift->{totalHeight}} sub addRect { my $self = shift; my ($id,$left,$right,$height) = @_; if (defined $self->{seen}{$id}) {return $self->{seen}{$id}}; # for each contour, we test the fit on the near side of the given rect, my $leftFit = $self->tryLeftFit($left, $right, $height, 0); my $rightFit = $self->tryRightFit($left, $right, $height, 0); my $top; # and insert the far side from the side we tested # (we want to make sure the near side fits, but we want to extend # the contour to cover the far side) if ($leftFit->{top} < $rightFit->{top}) { $top = $leftFit->{top}; $self->{leftContour}->insertFit($leftFit->{fit}, $self->{rightBound} - $left, $top, $height); $self->{rightContour}->unionWith($right - $self->{leftBound}, $top, $height); } else { $top = $rightFit->{top}; $self->{rightContour}->insertFit($rightFit->{fit}, $right - $self->{leftBound}, $top, $height); $self->{leftContour}->unionWith($self->{rightBound} - $left, $top, $height); } my $existing = {id => $id, left => $left, right => $right, top => $top, height => $height}; $self->{seen}{$id} = $top; if ($left <= $self->{leftBound}) { push(@{$self->{leftOverlaps}},$existing); if ($self->{leftLayout}) { $self->{leftLayout}->addExisting($existing); } } if ($right >= $self->{rightBound}) { push(@{$self->{rightOverlaps}},$existing); if ($self->{rightLayout}) { $self->{rightLayout}->addExisting($existing); } } $self->{seen}{$id} = $top; $self->{totalHeight} = Bio::Graphics::Math::max($self->{totalHeight}, $top + $height); return $top; } # this method is called by the block to the left to see if a given fit works # in this layout # takes: proposed rectangle # returns: {top: value that makes the rectangle fit in this layout, # fit: "fit" for passing to insertFit} sub tryLeftFit { my $self = shift; my ($left,$right,$height,$top) = @_; my ($fit, $nextFit); my $curTop = $top; while (1) { # check if the rectangle fits at curTop $fit = $self->{leftContour}->getFit($self->{rightBound} - $right, $height, $curTop); $curTop = Bio::Graphics::Math::max($self->{leftContour}->getNextTop($fit), $curTop); # if the rectangle extends onto the next block to the right; if ($self->{rightLayout} && ($right >= $self->{rightBound})) { # check if the rectangle fits into that block at this position $nextFit = $self->{rightLayout}->tryLeftFit($left, $right, $height, $curTop); # if not, nextTop will be the next y-value where the rectangle # fits into that block if ($nextFit->{top} > $curTop) { # in that case, try again to see if that y-value works $curTop = $nextFit->{top}; next; } } last; } return {top=> $curTop, fit=> $fit}; } # this method is called by the block to the right to see if a given fit works # in this layout # takes: proposed rectangle # returns: {top: value that makes the rectangle fit in this layout, # fit: "fit" for passing to insertFit} sub tryRightFit { my $self = shift; my ($left,$right,$height,$top) = @_; my ($fit, $nextFit); my $curTop = $top; while (1) { # check if the rectangle fits at curTop $fit = $self->{rightContour}->getFit($left - $self->{leftBound}, $height, $curTop); $curTop = Bio::Graphics::Math::max($self->{rightContour}->getNextTop($fit), $curTop); # if the rectangle extends onto the next block to the left; if ($self->{leftLayout} && ($left <= $self->{leftBound})) { # check if the rectangle fits into that block at this position $nextFit = $self->{leftLayout}->tryRightFit($left, $right, $height, $curTop); # if not, nextTop will be the next y-value where the rectangle # fits into that block if ($nextFit->{top} > $curTop) { # in that case, try again to see if that y-value works $curTop = $nextFit->{top}; next; } } last } return {top => $curTop, fit => $fit}; } sub hasSeen { my $self = shift; my $id = shift; return defined $self->{seen}{$id}; } sub setLeftLayout { my $self = shift; my $left = shift; for (my $i = 0; $i < @{$self->{leftOverlaps}}; $i++) { $left->addExisting($self->{leftOverlaps}[$i]); } $self->{leftLayout} = $left; }; sub setRightLayout { my $self = shift; my $right = shift; for (my $i = 0; $i < @{$self->{rightOverlaps}}; $i++) { $right->addExisting($self->{rightOverlaps}[$i]); } $self->{rightLayout} = $right; }; sub cleanup { my $self = shift; undef $self->{leftLayout}; undef $self->{rightLayout}; }; # expects an {id, left, right, height, top} object sub addExisting { my $self = shift; my $existing = shift; if (defined $self->{seen}[$existing->{id}]) {return}; $self->{seen}{$existing->{id}} = $existing->{top}; $self->{totalHeight} = Bio::Graphics::Math::max($self->{totalHeight}, $existing->{top} + $existing->{height}); if ($existing->{left} <= $self->{leftBound}) { push(@{$self->{leftOverlaps}},$existing); if ($self->{leftLayout}) { $self->{leftLayout}->addExisting($existing); } } if ($existing->{right} >= $self->{rightBound}) { push(@{$self->{rightOverlaps}},$existing); if ($self->{rightLayout}) { $self->{rightLayout}->addExisting($existing); } } $self->{leftContour}->unionWith($self->{rightBound} - $existing->left, $existing->{top}, $existing->{height}); $self->rightContour->unionWith($existing->{right} - $self->{leftBound}, $existing->{top}, $existing->{height}); } package Bio::Graphics::Layout::Contour; use constant INF => 1<<16; sub new { my $class = shift; my $top = shift; # /* # * A contour is described by a set of vertical lines of varying heights, # * like this: # * | # * | # * | # * | # * | # * | # * # * The contour is the union of the rectangles ending on the right side # * at those lines, and extending leftward toward negative infinity. # * # * <=======================| # * <=======================| # * <==========| # * <=================| # * <=================| # * <=================| # * # * x --> # * # * As we add new vertical spans, the contour expands, either downward # * or in the direction of increasing x. # */ # // takes: top, a number indicating where the first span of the contour # // will go $top ||= 0; # // spans is an array of {top, x, height} objects representing # // the boundaries of the contour # // they're always sorted by top return bless {spans => [ {top=> $top, x => INF, height => 0} ] },ref $class || $class; } sub spans {shift->{spans}} # // finds a space in the contour into which the given span fits # // (i.e., the given span has higher x than the contour over its vertical span) # // returns an ojbect {above, count}; above is the index of the last span above # // where the given span will fit, count is the number of spans being # // replaced by the given span sub getFit { my $self = shift; my ($x,$height,$minTop) = @_; my ($aboveBottom, $curSpan); my $above = 0; my $spans = $self->spans; if ($minTop) { # set above = (index of the first span that starts below minTop) for (; $spans->[$above]{top} < $minTop; $above++) { if ($above >= (@$spans - 1)) { return {above=> @$spans - 1, count=> 0}; } } } # slide down the contour my $count; ABOVE: for (; $above < @$spans; $above++) { $aboveBottom = $spans->[$above]{top} + $spans->[$above]{height}; for ($count = 1; $above + $count < @$spans; $count++) { $curSpan = $spans->[$above + $count]; if (($aboveBottom + $height) <= $curSpan->{top}) { # the given span fits between span[above] and # curSpan, keeping curSpan return {above=> $above, count=> $count - 1}; } if ($curSpan->{x} > $x) { # the span at [above + count] overlaps the given span, # so we continue down the contour next ABOVE; } if (($curSpan->{x} <= $x) && (($aboveBottom + $height) < ($curSpan->{top} + $curSpan->{height}))) { # the given span partially covers curSpan, and # will overlap it, so we keep curSpan return {above=> $above, count=> $count - 1}; } } # the given span fits below span[above], replacing any # lower spans in the contour return {above=> $above, count => $count - 1}; } # the given span fits at the end of the contour, replacing no spans return {above => $above, count => 0}; } # add the given span to this contour where it fits, as given # by getFit sub insertFit { my $self = shift; my ($fit,$x,$top,$height) = @_; my $spans = $self->spans; # if the previous span and the current span have the same x-coord, # and are vertically contiguous, merge them. my $prevSpan = $spans->[$fit->{above}]; if ((abs($prevSpan->{x} - $x) < 1) && (abs(($prevSpan->{top} + $prevSpan->{height}) - $top) < 1) ) { $prevSpan->{height} = ($top + $height) - $prevSpan->{top}; # a bit of slop here is conservative if we take the max # (means things might get laid out slightly farther apart # than they would otherwise) $prevSpan->{x} = Bio::Graphics::Math::max($prevSpan->{x}, $x); splice(@$spans,$fit->{above} + 1, $fit->{count}); } else { splice(@$spans,$fit->{above} + 1, $fit->{count}, { top => $top, x => $x, height => $height }); } } # add the given span to this contour at the given location, if # it would extend the contour sub unionWith { my $self = shift; my ($x,$top,$height) = @_; my ($startBottom, $startIndex, $endIndex, $startSpan, $endSpan); my $bottom = $top + $height; my $spans = $self->spans; START: for ($startIndex = 0; $startIndex < @$spans; $startIndex++) { $startSpan = $spans->[$startIndex]; $startBottom = $startSpan->{top} + $startSpan->{height}; if ($startSpan->{top} > $top) { # the given span extends above an existing span $endIndex = $startIndex; last START; } if ($startBottom > $top) { # if startSpan covers (at least some of) the given span, if ($startSpan->{x} >= $x) { my $covered = $startBottom - $top; # we don't have to worry about the covered area any more $top += $covered; $height -= $covered; # if we've eaten up the whole span, then it's submerged # and we don't have to do anything if ($top >= $bottom) { return }; next; } else { # find the first span not covered by the given span for ($endIndex = $startIndex; $endIndex < @$spans; $endIndex++) { $endSpan = $spans->[$endIndex]; # if endSpan extends below or to the right # of the given span, then we need to keep it if ((($endSpan->{top} + $endSpan->{height}) > $bottom) || $endSpan->{x} > $x) { last START; } } last START; } } } # if the previous span and the current span have the same x-coord, # and are vertically contiguous, merge them. my $prevSpan = $spans->[$startIndex - 1]; if ((abs($prevSpan->{x} - $x) < 1) && (abs(($prevSpan->{top} + $prevSpan->{height}) - $top) < 1) ) { $prevSpan->{height} = ($top + $height) - $prevSpan->{top}; $prevSpan->{x} = Bio::Graphics::Math::max($prevSpan->{x}, $x); splice(@$spans,$startIndex, $endIndex - $startIndex); } else { splice(@$spans,$startIndex, $endIndex - $startIndex, { top => $top, x => $x, height => $height }); } } # returns the top of the to-be-added span that fits into "fit" # (as returned by getFit) sub getNextTop { my $self = shift; my $fit = shift; return $self->spans->[$fit->{above}]{top} + $self->spans->[$fit->{above}]{height}; }; package Bio::Graphics::Math; sub max {$_[0] > $_[1] ? $_[0] : $_[1]} 1; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������Bio-Graphics-2.37/lib/Bio/Graphics/FeatureFile.pm���������������������������������������������������000555��001750��001750�� 161110�12165075746� 21660� 0����������������������������������������������������������������������������������������������������ustar�00lstein��������������������������lstein��������������������������000000��000000�������������������������������������������������������������������������������������������������������������������������������������������������������������������������package Bio::Graphics::FeatureFile; # This package parses and renders a simple tab-delimited format for features. # It is simpler than GFF, but still has a lot of expressive power. # See __END__ for the file format =head1 NAME Bio::Graphics::FeatureFile -- A set of Bio::Graphics features, stored in a file =head1 SYNOPSIS use Bio::Graphics::FeatureFile; my $data = Bio::Graphics::FeatureFile->new(-file => 'features.txt'); # create a new panel and render contents of the file onto it my $panel = $data->new_panel; my $tracks_rendered = $data->render($panel); # or do it all in one step my ($tracks_rendered,$panel) = $data->render; # for more control, render tracks individually my @feature_types = $data->types; for my $type (@feature_types) { my $features = $data->features($type); my %options = $data->style($type); $panel->add_track($features,%options); # assuming we have a Bio::Graphics::Panel } # get individual settings my $est_fg_color = $data->setting(EST => 'fgcolor'); # or create the FeatureFile by hand # add a type $data->add_type(EST => {fgcolor=>'blue',height=>12}); # add a feature my $feature = Bio::Graphics::Feature->new( # params ); # or some other SeqI $data->add_feature($feature=>'EST'); =head1 DESCRIPTION The Bio::Graphics::FeatureFile module reads and parses files that describe sequence features and their renderings. It accepts both GFF format and a more human-friendly file format described below. Once a FeatureFile object has been initialized, you can interrogate it for its consistuent features and their settings, or render the entire file onto a Bio::Graphics::Panel. This module is a precursor of Jason Stajich's Bio::Annotation::Collection class, and fulfills a similar function of storing a collection of sequence features. However, it also stores rendering information about the features, and does not currently follow the CollectionI interface. =head1 The File Format There are two types of entry in the file format: feature entries, and formatting entries. They can occur in any order. See the Appendix for a full example. =head2 Formatting Entries Formatting entries are in the form: [Stanza Name] option1 = value1 option2 = value2 option3 = value3 [Stanza Name 2] option1 = value1 option2 = value2 ... There can be zero or more stanzas, each with a unique name. The names can contain any character except the [] characters. Each stanza consists of one or more option = value pairs, where the option and the value are separated by an "=" sign and optional whitespace. Values can be continued across multiple lines by indenting the continuation lines by one or more spaces, as in: [Named Genes] feature = gene glyph = transcript2 description = These are genes that have been named by the international commission on gene naming (The Hague). Typically configuration stanzas will consist of several Bio::Graphics formatting options. A -option=>$value pair passed to Bio::Graphics::Panel->add_track() becomes a "option=value" pair in the feature file. =head2 Feature Entries Feature entries can take several forms. At their simplest, they look like this: Gene B0511.1 Chr1:516..11208 This means that a feature of type "Gene" and name "B0511.1" occupies the range between bases 516 and 11208 on a sequence entry named Chr1. Columns are separated using whitespace (tabs or spaces). Embedded whitespace can be escaped using quote marks or backslashes: Gene "My Favorite Gene" Chr1:516..11208 =head2 Specifying Positions and Ranges A feature position is specified using a sequence ID (a genbank accession number, a chromosome name, a contig, or any other meaningful reference system, followed by a colon and a position range. Ranges are two integers separated by double dots or the hyphen. Examples: "Chr1:516..11208", "ctgA:1-5000". Negative coordinates are allowed, as in "Chr1:-187..1000". A discontinuous range ("split location") uses commas to separate the ranges. For example: Gene B0511.1 Chr1:516..619,3185..3294,10946..11208 In the case of a split location, the sequence id only has to appear in front of the first range. Alternatively, a split location can be indicated by repeating the features type and name on multiple adjacent lines: Gene B0511.1 Chr1:516..619 Gene B0511.1 Chr1:3185..3294 Gene B0511.1 Chr1:10946..11208 If all the locations are on the same reference sequence, you can specify a default chromosome using a "reference=": reference=Chr1 Gene B0511.1 516..619 Gene B0511.1 3185..3294 Gene B0511.1 10946..11208 The default seqid is in effect until the next "reference" line appears. =head2 Feature Tags Tags can be added to features by adding a fourth column consisting of "tag=value" pairs: Gene B0511.1 Chr1:516..619,3185..3294 Note="Putative primase" Tags and their values take any form you want, and multiple tags can be separated by semicolons. You can also repeat tags multiple times: Gene B0511.1 Chr1:516..619,3185..3294 GO_Term=GO:100;GO_Term=GO:2087 Several tags have special meanings: Tag Meaning --- ------- Type The primary tag for a subfeature. Score The score of a feature or subfeature. Phase The phase of a feature or subfeature. URL A URL to link to (via the Bio::Graphics library). Note A note to attach to the feature for display by the Bio::Graphics library. For example, in the common case of an mRNA, you can use the "Type" tag to distinguish the parts of the mRNA into UTR and CDS: mRNA B0511.1 Chr1:1..100 Type=UTR mRNA B0511.1 Chr1:101..200,300..400,500..800 Type=CDS mRNA B0511.1 Chr1:801..1000 Type=UTR The top level feature's primary tag will be "mRNA", and its subparts will have types UTR and CDS as indicated. Additional tags that are placed in the first line of the feature will be applied to the top level. In this example, the note "Putative primase" will be applied to the mRNA at the top level of the feature: mRNA B0511.1 Chr1:1..100 Type=UTR;Note="Putative primase" mRNA B0511.1 Chr1:101..200,300..400,500..800 Type=CDS mRNA B0511.1 Chr1:801..1000 Type=UTR =head2 Feature Groups Features can be grouped so that they are rendered by the "group" glyph. To start a group, create a two-column feature entry showing the group type and a name for the group. Follow this with a list of feature entries with a blank type. For example: EST yk53c10 yk53c10.3 15000-15500,15700-15800 yk53c10.5 18892-19154 This example is declaring that the ESTs named yk53c10.3 and yk53c10.5 belong to the same group named yk53c10. =head2 Comments Lines that begin with the # sign are treated as comments and ignored. When a # sign appears within a line, everything to the right of the symbol is also ignored, unless it looks like an HTML fragment or an HTML color, e.g.: # this is ignored [Example] glyph = generic # this comment is ignored bgcolor = #FF0000 link = http://www.google.com/search?q=$name#results Be careful, because the processing of # signs uses a regexp heuristic. To be safe, always put a space after the # sign to make sure it is treated as a comment. =head2 The #include and #exec Directives The special comment "#include 'filename'" acts like the C preprocessor directive and will insert the comments of a named file into the position at which it occurs. Relative paths will be treated relative to the file in which the #include occurs. Nested #include directives (a #include located in a file that is itself an include file) are #allowed. You may also use one of the shell wildcard characters * and #? to include all matching files in a directory. The following are examples of valid #include directives: #include "/usr/local/share/my_directives.txt" #include 'my_directives.txt' #include chromosome3_features.gff3 #include gff.d/*.conf You can enclose the file path in single or double quotes as shown above. If there are no spaces in the filename the quotes are optional. The #include directive is case insensitive, allowing you to use #INCLUDE or #Include if you prefer. Include file processing is not very smart and will not catch all circular #include references. You have been warned! The special comment "#exec 'command'" will spawn a shell and incorporate the output of the command into the configuration file. This command will be executed quite frequently, so it is suggested that any time-consuming processing that does not need to be performed on the fly each time should be cached in a local file. =cut use strict; use Bio::Graphics::Feature; use Bio::DB::GFF::Util::Rearrange; use Carp 'cluck','carp','croak'; use IO::File; use File::Glob ':glob'; use Text::ParseWords 'shellwords'; use Bio::DB::SeqFeature::Store; use File::Basename 'dirname'; use File::Spec; use Cwd 'getcwd'; # default colors for unconfigured features my @COLORS = qw(cyan blue red yellow green wheat turquoise orange); # package variable which holds the limited set of libraries accessible # from within the Safe::World container (please see the description of # the -safe_world option). # my $SAFE_LIB; use constant WIDTH => 600; use constant MAX_REMAP => 100; =head2 METHODS =over 4 =item $version = Bio::Graphics::FeatureFile-Eversion Return the version number -- needed for API checking by GBrowse =cut sub version { return 2 } =item $features = Bio::Graphics::FeatureFile-Enew(@args) Create a new Bio::Graphics::FeatureFile using @args to initialize the object. Arguments are -name=Evalue pairs: Argument Value -------- ----- -file Read data from a file path or filehandle. Use "-" to read from standard input. -text Read data from a text scalar. -allow_whitespace If true, relax GFF2 and GFF3 parsing rules to allow columns to be delimited by whitespace rather than tabs. -map_coords Coderef containing a subroutine to use for remapping all coordinates. -smart_features Flag indicating that the features created by this module should be made aware of the FeatureFile object by calling their configurator() method. -safe Indicates that the contents of this file is trusted. Any option value that begins with the string "sub {" or \&subname will be evaluated as a code reference. -safe_world If the -safe option is not set, and -safe_world is set to a true value, then Bio::Graphics::FeatureFile will evalute "sub {}" options in a L environment with minimum permissions. Subroutines will be able to access and interrogate Bio::DB::SeqFeature objects and perform basic Perl operations, but will have no ability to load or access other modules, to access the file system, or to make system calls. This feature depends on availability of the CPAN-installable L module. The -file and -text arguments are mutually exclusive, and -file will supersede the other if both are present. -map_coords points to a coderef with the following signature: ($newref,[$start1,$end1],[$start2,$end2]....) = coderef($ref,[$start1,$end1],[$start2,$end2]...) See the Bio::Graphics::Browser (part of the generic genome browser package) for an illustration of how to use this to do wonderful stuff. The -smart_features flag is used by the generic genome browser to provide features with a way to access the link-generation code. See gbrowse for how this works. If the file is trusted, and there is an option named "init_code" in the [GENERAL] section of the file, it will be evaluated as perl code immediately after parsing. You can use this to declare global variables and subroutines for use in option values. =cut # args array: # -file => parse from a file (- allowed for ARGV) # -text => parse from a text scalar # -map_coords => code ref to do coordinate mapping # called with ($ref,[$start1,$stop1],[$start2,$stop2]...) # returns ($newref,$new_coord1,$new_coord2...) sub new { shift->_new(@_); } sub _new { my $class = shift; my %args = @_; my $self = bless { config => {}, features => {}, seenit => {}, types => [], max => undef, min => undef, stat => [], refs => {}, safe => undef, safe_world => undef, },$class; $self->{coordinate_mapper} = $args{-map_coords} if exists $args{-map_coords} && ref($args{-map_coords}) eq 'CODE'; $self->smart_features($args{-smart_features}) if exists $args{-smart_features}; $self->{safe} = $args{-safe} if exists $args{-safe}; $self->safe_world(1) if $args{-safe_world}; $self->allow_whitespace(1) if $args{-allow_whitespace}; $self->init_parse(); # call with # -file # -text if (my $file = $args{-file}) { no strict 'refs'; if (defined fileno($file)) { # a filehandle $self->parse_fh($file); } elsif ($file eq '-') { $self->parse_argv(); } else { $self->parse_file($file); } } elsif (my $text = $args{-text}) { $self->parse_text($text); } $self->finish_parse(); return $self; } =item $features = Bio::Graphics::FeatureFile-Enew_from_cache(@args) Like new() but caches the parsed file in /tmp/bio_graphics_ff_cache_* (where * is the UID of the current user). This can speed up parsing tremendously for files that have many includes. Note that the presence of an #exec statement always invalidates the cache and causes a full parse. =cut sub new_from_cache { my $self = shift; my %args = @_; my $has_libs; unless ($has_libs = defined &nfreeze) { $has_libs = eval <_new(@_); (my $name = $args{-file}) =~ s!/!_!g; my $cachefile = $self->cachefile($name); if (-e $cachefile && (stat(_))[9] >= $self->file_mtime($args{-file})) { # cache is valid # if (-e $cachefile && -M $cachefile < 0) { # cache is valid my $parsed_file = lock_retrieve($cachefile); $parsed_file->initialize_code if $parsed_file->safe; return $parsed_file; } else { mkpath(dirname($cachefile)); my $parsed = $self->_new(@_); $parsed->initialize_code(); eval {lock_store($parsed,$cachefile)}; warn $@ if $@; return $parsed; } } sub cachedir { my $self = shift; my $uid = $<; return File::Spec->catfile(File::Spec->tmpdir,"bio_graphics_ff_cache_${uid}"); } sub cachefile { my $self = shift; my $name = shift; return File::Spec->catfile($self->cachedir,$name); } =item $mtime = Bio::Graphics::FeatureFile->file_mtime($path) Return the modification time of the indicated feature file without performing a full parse. This takes into account the various #include and #exec directives and returns the maximum mtime of any of the included files. Any #exec directive will return the current time. This is useful for caching the parsed data structure. =back =cut sub file_mtime { my $self = shift; my $file = shift; my $mtime = 0; for my $f (glob($file)) { my $m = (stat($f))[9] or next; $mtime = $m if $mtime < $m; open my $fh,'<',$file or next; my $cwd = getcwd(); chdir(dirname($file)); local $_; while (<$fh>) { if (/^\#exec/) { return time(); # now! } if (/^\#include\s+(.+)/i) { # #include directive my ($include_file) = shellwords($1); my $m = $self->file_mtime($include_file); $mtime = $m if $mtime < $m; } } chdir($cwd); } return $mtime; } sub file_list { my $self = shift; my @list = (); my $file = shift; for my $f (glob($file)) { open my $fh,'<',$file or next; my $cwd = getcwd(); chdir(dirname($file)); while (<$fh>) { if (/^\#include\s+(.+)/i) { # #include directive my ($include_file) = shellwords($1); my @files = glob($include_file); @files ? @list = (@list,@files) : push(@list,$include_file); } } chdir($cwd); } return \@list; } # render our features onto a panel using configuration data # return the number of tracks inserted =over 4 =item ($rendered,$panel,$tracks) = $features-Erender([$panel, $position_to_insert, $options, $max_bump, $max_label, $selector]) Render features in the data set onto the indicated Bio::Graphics::Panel. If no panel is specified, creates one. All arguments are optional. $panel is a Bio::Graphics::Panel that has previously been created and configured. $position_to_insert indicates the position at which to start inserting new tracks. The last current track on the panel is assumed. $options is a scalar used to control automatic expansion of the tracks. 0=auto, 1=compact, 2=expanded, 3=expand and label, 4=hyperexpand, 5=hyperexpand and label. $max_bump and $max_label indicate the maximum number of features before bumping and labeling are turned off. $selector is a code ref that can be used to filter which features to render. It receives a feature and should return true to include the feature and false to exclude it. In a scalar context returns the number of tracks rendered. In a list context, returns a three-element list containing the number of features rendered, the created panel, and an array ref of all the track objects created. Instead of a Bio::Graphics::Panel object, you can provide a hash reference containing the arguments that you would pass to Bio::Graphics::Panel->new(). For example, to render an SVG image, you could do this: my ($tracks_rendered,$panel) = $data->render({-image_class=>'GD::SVG'}); print $panel->svg; =back =cut #" sub render { my $self = shift; my $panel = shift; # 8 arguments my ($position_to_insert, $options, $max_bump, $max_label, $selector, $range, $override_options ) = @_; my %seenit; unless ($panel && UNIVERSAL::isa($panel,'Bio::Graphics::Panel')) { $panel = $self->new_panel($panel); } # count up number of tracks inserted my @tracks; my $color; my @labels = $self->labels; # we need to add a dummy section for each type that isn't # specifically configured my %types = map {$_=>1 } map { shellwords ($self->setting($_=>'feature')||$_) } @labels; my %lc_types = map {lc($_)}%types; my @unconfigured_types = sort grep {!exists $lc_types{lc $_} && !exists $lc_types{lc $_->method} } $self->types; my @configured_types = keys %types; my @labels_to_render = (@labels,@unconfigured_types); my @base_config = $self->style('general'); my @pack_options = (); if ($options && ref $options eq 'HASH') { @pack_options = %$options; } else { $options ||= 0; if ($options == 1) { # compact push @pack_options,(-bump => 0,-label=>0); } elsif ($options == 2) { #expanded push @pack_options,(-bump=>1); } elsif ($options == 3) { #expand and label push @pack_options,(-bump=>1,-label=>1); } elsif ($options == 4) { #hyperexpand push @pack_options,(-bump => 2); } elsif ($options == 5) { #hyperexpand and label push @pack_options,(-bump => 2,-label=>1); } } for my $label (@labels_to_render) { my @types = shellwords($self->setting($label=>'feature')||''); @types = $label unless @types; next if defined $selector and !$selector->($self,$label); my @features = !$range ? grep {$self->_visible($_)} $self->features(\@types) : $self->features(-types => \@types, -seq_id => $range->seq_id, -start => $range->start, -end => $range->end ); next unless @features; # suppress tracks for features that don't appear # fix up funky group hack foreach (@features) {$_->primary_tag('group') if $_->has_tag('_ff_group')}; my $features = \@features; my @auto_bump; push @auto_bump,(-bump => @$features < $max_bump) if defined $max_bump; push @auto_bump,(-label => @$features < $max_label) if defined $max_label; my @more_arguments = $override_options ? @$override_options : (); my @config = ( -glyph => 'segments', # really generic -bgcolor => $COLORS[$color++ % @COLORS], -label => 1, -description => 1, -key => $features[0]->type || $label, @auto_bump, @base_config, # global $self->style($label), # feature-specific @pack_options, @more_arguments, ); if (defined($position_to_insert)) { push @tracks,$panel->insert_track($position_to_insert++,$features,@config); } else { push @tracks,$panel->add_track($features,@config); } } return wantarray ? (scalar(@tracks),$panel,\@tracks) : scalar @tracks; } sub _stat { my $self = shift; my $file = shift; defined fileno($file) or return; my @stat = stat($file) or return; if ($self->{stat} && @{$self->{stat}}) { # merge #includes so that mtime etc are max age for (8,9,10) { $self->{stat}[$_] = $stat[$_] if $stat[$_] > $self->{stat}[$_]; } $self->{stat}[7] += $stat[7]; } else { $self->{stat} = \@stat; } } sub _visible { my $self = shift; my $feat = shift; my $min = $self->min; my $max = $self->max; return $feat->start<=$max && $feat->end>=$min; } =over 4 =item $error = $features-Eerror([$error]) Get/set the current error message. =back =cut sub error { my $self = shift; my $d = $self->{error}; $self->{error} = shift if @_; $d; } =over 4 =item $smart_features = $features-Esmart_features([$flag] Get/set the "smart_features" flag. If this is set, then any features added to the featurefile object will have their configurator() method called using the featurefile object as the argument. =back =cut sub smart_features { my $self = shift; my $d = $self->{smart_features}; $self->{smart_features} = shift if @_; $d; } sub parse_argv { my $self = shift; local $/ = "\n"; local $_; while (<>) { chomp; $self->parse_line($_); } } sub parse_file { my $self = shift; my $file = shift; $file =~ s/(\s)/\\$1/g; # escape whitespace from glob expansion for my $f (glob($file)) { my $fh = IO::File->new($f) or return; my $cwd = getcwd(); chdir(dirname($f)); $self->parse_fh($fh); chdir($cwd); } } sub parse_fh { my $self = shift; my $fh = shift; $self->_stat($fh); local $/ = "\n"; local $_; while (<$fh>) { chomp; $self->parse_line($_) || last; } } sub parse_text { my $self = shift; my $text = shift; foreach (split m/\015?\012|\015\012?/,$text) { $self->parse_line($_); } } sub parse_line { my $self = shift; my $line = shift; $line =~ s/\015//g; # get rid of carriage returns left over by MS-DOS/Windows systems $line =~ s/\s+$//; # get rid of trailing whitespace if (/^#include\s+(.+)/i) { # #include directive my ($include_file) = shellwords($1); # detect some loops croak "#include loop detected at $include_file" if $self->{includes}{$include_file}++; $self->parse_file($include_file); return 1; } if (/^#exec\s+(.+)/i) { # #exec directive my ($command,@args) = shellwords($1); open (my $fh,'-|') || exec $command,@args; $self->parse_fh($fh); return 1; } return 1 if $line =~ /^\s*\#[^\#]?$/; # comment line # Are we in a configuration section or a data section? # We start out in 'config' state, and are triggered to # reenter config state whenever we see a /^\[ pattern (config section) my $old_state = $self->{state}; my $new_state = $self->_state_transition($line); if ($new_state ne $old_state) { delete $self->{current_config}; delete $self->{current_tag}; } if ($new_state eq 'config') { $self->parse_config_line($line); } elsif ($new_state eq 'data') { $self->parse_data_line($line); } $self->{state} = $new_state; 1; } sub _state_transition { my $self = shift; my $line = shift; my $current_state = $self->{state}; if ($current_state eq 'data') { return 'config' if $line =~ m/^\s*\[([^\]]+)\]/; # start of a configuration section } elsif ($current_state eq 'config') { return 'data' if $line =~ /^\#\#(\w+)/; # GFF3 meta instruction return 'data' if $line =~ /^reference\s*=/; # feature-file reference sequence directive return 'config' if $line =~ /^\s*$/; #empty line return 'config' if $line =~ m/^\[(.+)\]/; # section beginning return 'config' if $line =~ m/^[\w:\s]+=/ && $self->{current_config}; # configuration line return 'config' if $line =~ m/^\s+(.+)/ && $self->{current_tag}; # continuation section return 'config' if $line =~ /^\#/; # comment -not a meta return 'data'; } return $current_state; } sub parse_config_line { my $self = shift; local $_ = shift; # strip right-column comments unless they look like colors or html fragments s/\s*\#.*$// unless /\#[0-9a-f]{6,8}\s*$/i || /\w+\#\w+/ || /\w+\"*\s*\#\d+$/; if (/^\s+(.+)/ && $self->{current_tag}) { # configuration continuation line my $value = $1; my $cc = $self->{current_config} ||= 'general'; # in case no configuration named $self->{config}{$cc}{$self->{current_tag}} .= ' ' . $value; # respect newlines in code subs $self->{config}{$cc}{$self->{current_tag}} .= "\n" if $self->{config}{$cc}{$self->{current_tag}}=~ /^sub\s*\{/; return 1; } elsif (/^\[(.+)\]/) { # beginning of a configuration section my $label = $1; my $cc = $label =~ /^(general|default)$/i ? 'general' : $label; # normalize push @{$self->{types}},$cc unless $cc eq 'general'; $self->{current_config} = $cc; return 1; } elsif (/^([\w: -]+?)\s*=\s*(.*)/) { # key value pair within a configuration section my $tag = lc $1; my $cc = $self->{current_config} ||= 'general'; # in case no configuration named my $value = defined $2 ? $2 : ''; $self->{config}{$cc}{$tag} = $value; $self->{current_tag} = $tag; return 1; } elsif (/^$/) { # empty line # no longer required -- new sections are indicated by the start of a [stanza] # line and not by termination with a blank line # undef $self->{current_tag}; return 1; } } sub parse_data_line { my $self = shift; my $line = shift; $self->{loader} ||= $self->_make_loader($line) or return; $self->{loader}->load_line($line); } sub _make_loader { my $self = shift; local $_ = shift; my $db = $self->db; my $type; # we support gff2, gff3 and featurefile formats if (/^\#\#gff-version\s+([23])/) { $type = "Bio::DB::SeqFeature::Store::GFF$1Loader"; } elsif (/^reference\s*=.+/) { $type = "Bio::DB::SeqFeature::Store::FeatureFileLoader"; } else { my @tokens = shellwords($_); unshift @tokens,'' if /^\s+/ and length $tokens[0]; if (@tokens >=8 && $tokens[3]=~ /^-?\d+$/ && $tokens[4]=~ /^-?\d+$/) { $type = 'Bio::DB::SeqFeature::Store::GFF3Loader'; } else { $type = 'Bio::DB::SeqFeature::Store::FeatureFileLoader'; } } eval "require $type" unless $type->can('new'); my $loader = $type->new(-store => $db, -map_coords => $self->{coordinate_mapper}, -index_subfeatures => 0, ); eval {$loader->allow_whitespace(1)} if $self->allow_whitespace; # gff2 and gff3 loaders allow this $loader->start_load() if $loader; return $loader; } sub db { my $self = shift; return $self->{db} ||= Bio::DB::SeqFeature::Store->new(-adaptor=>'memory', -write => 1); } =over 4 =item $flat = $features-Eallow_whitespace([$new_flag]) If true, then GFF3 and GFF2 parsing is relaxed to allow whitespace to delimit the columns. Default is false. =back =cut sub allow_whitespace { my $self = shift; my $d = $self->{allow_whitespace}; $self->{allow_whitespace} = shift if @_; $d; } =over 4 =item $features-Eadd_feature($feature [=E$type]) Add a new Bio::FeatureI object to the set. If $type is specified, the object's primary_tag() will be set to that type. Otherwise, the method will use the feature's existing primary_tag() to index and store the feature. =back =cut # add a feature of given type to our list # we use the primary_tag() method sub add_feature { my $self = shift; my ($feature,$type) = @_; $feature->configurator($self) if $self->smart_features; $feature->primary_tag($type) if defined $type; $self->db->store($feature); } =over 4 =item $features-Eadd_type($type=E$hashref) Add a new feature type to the set. The type is a string, such as "EST". The hashref is a set of key=Evalue pairs indicating options to set on the type. Example: $features->add_type(EST => { glyph => 'generic', fgcolor => 'blue'}) When a feature of type "EST" is rendered, it will use the generic glyph and have a foreground color of blue. =back =cut # Add a type to the list. Hash values are used for key/value pairs # in the configuration. Call as add_type($type,$configuration) where # $configuration is a hashref. sub add_type { my $self = shift; my ($type,$type_configuration) = @_; my $cc = $type =~ /^(general|default)$/i ? 'general' : $type; # normalize push @{$self->{types}},$cc unless $cc eq 'general' or $self->{config}{$cc}; if (defined $type_configuration) { for my $tag (keys %$type_configuration) { $self->{config}{$cc}{lc $tag} = $type_configuration->{$tag}; } } } =over 4 =item $features-Eset($type,$tag,$value) Change an individual option for a particular type. For example, this will change the foreground color of EST features to my favorite color: $features->set('EST',fgcolor=>'chartreuse') =back =cut # change configuration of a type. Call as set($type,$tag,$value) # $type will be added if not already there. sub set { my $self = shift; croak("Usage: \$featurefile->set(\$type,\$tag,\$value\n") unless @_ == 3; my ($type,$tag,$value) = @_; unless ($self->{config}{$type}) { return $self->add_type($type,{$tag=>$value}); } else { $self->{config}{$type}{lc $tag} = $value; } } # break circular references sub finished { my $self = shift; delete $self->{features}; } sub DESTROY { my $self = shift; $self->finished(@_); # $self->{safe_context}->unlink_all_worlds # if $self->{safe_context}; } =over 4 =item $value = $features-Esetting($stanza =E $option) In the two-element form, the setting() method returns the value of an option in the configuration stanza indicated by $stanza. For example: $value = $features->setting(general => 'height') will return the value of the "height" option in the [general] stanza. Call with one element to retrieve all the option names in a stanza: @options = $features->setting('general'); Call with no elements to retrieve all stanza names: @stanzas = $features->setting; =back =cut sub setting { my $self = shift; if (@_ > 2) { $self->{config}->{$_[0]}{$_[1]} = $_[2]; } elsif (@_ <= 1) { return $self->_setting(@_); } elsif ($self->safe) { return $self->code_setting(@_); } elsif ($self->safe_world) { return $self->safe_setting(@_); } else { $self->{code_check}++ && $self->clean_code(); # not safe; clean coderefs return $self->_setting(@_); } } =head2 fallback_setting() $value = $browser->setting(gene => 'fgcolor'); Tries to find the setting for designated label (e.g. "gene") first. If this fails, looks in [TRACK DEFAULTS]. If this fails, looks in [GENERAL]. =cut sub fallback_setting { my $self = shift; my ($label,$option) = @_; for my $key ($label,'TRACK DEFAULTS','GENERAL') { my $value = $self->setting($key,$option); return $value if defined $value; } return; } # return configuration information # arguments are ($type) => returns tags for type # ($type=>$tag) => returns values of tag on type # ($type=>$tag,$value) => sets value of tag sub _setting { my $self = shift; my $config = $self->{config} or return; return keys %{$config} unless @_; return keys %{$config->{$_[0]}} if @_ == 1; return $config->{$_[0]}{$_[1]} if @_ == 2 && defined $_[0] && exists $config->{$_[0]}; return $config->{$_[0]}{$_[1]} = $_[2] if @_ > 2; return; } =over 4 =item $value = $features-Ecode_setting($stanza=E$option); This works like setting() except that it is also able to evaluate code references. These are options whose values begin with the characters "sub {". In this case the value will be passed to an eval() and the resulting codereference returned. Use this with care! =back =cut sub code_setting { my $self = shift; my $section = shift; my $option = shift; croak 'Cannot call code_setting unless feature file is marked as safe' unless $self->safe; my $setting = $self->_setting($section=>$option); return unless defined $setting; return $setting if ref($setting) eq 'CODE'; if ($setting =~ /^\\&([:\w]+)/) { # coderef in string form my $subroutine_name = $1; my $package = $self->base2package; my $codestring = $subroutine_name =~ /::/ ? "\\&$subroutine_name" : "\\&${package}\:\:${subroutine_name}" ; my $coderef = eval $codestring; $self->_callback_complain($section,$option) if $@; $self->set($section,$option,$coderef); $self->set_callback_source($section,$option,$setting); return $coderef; } elsif ($setting =~ /^sub\s*(\(\$\$\))*\s*\{/) { my $package = $self->base2package; my $coderef = eval "package $package; $setting"; $self->_callback_complain($section,$option) if $@; $self->set($section,$option,$coderef); $self->set_callback_source($section,$option,$setting); return $coderef; } else { return $setting; } } sub _callback_complain { my $self = shift; my ($section,$option) = @_; carp "An error occurred while evaluating the callback at section='$section', option='$option':\n => $@"; } =over 4 =item $value = $features-Esafe_setting($stanza=E$option); This works like code_setting() except that it evaluates anonymous code references in a "Safe::World" compartment. This depends on the L module being installed and the -safe_world option being set to true during object construction. =back =cut sub safe_setting { my $self = shift; my $section = shift; my $option = shift; my $setting = $self->_setting($section=>$option); return unless defined $setting; return $setting if ref($setting) eq 'CODE'; if ($setting =~ /^sub\s*(\(\$\$\))*\s*\{/ && (my $context = $self->{safe_context})) { # turn setting from an anonymous sub into a named # sub in the context namespace # create proper symbol name my $subname = "${section}_${option}"; $subname =~ tr/a-zA-Z0-9_//cd; $subname =~ s/^\d+//; my ($prototype) = $setting =~ /^sub\s*\(\$\$\)/; $setting =~ s/^sub?.*?\{/sub $subname {/; my $success = $context->eval("$setting; 1"); $self->_callback_complain($section,$option) if $@; unless ($success) { $self->set($section,$option,1); # if call fails, it becomes a generic "true" value return 1; } my $coderef = $prototype ? sub ($$) { return $context->call($subname,$_[0],$_[1]) } : sub { if ($_[-1]->isa('Bio::Graphics::Glyph')) { my %newglyph = %{$_[-1]}; $_[-1] = bless \%newglyph,'Bio::Graphics::Glyph'; # make generic } $context->call($subname,@_); }; $self->set($section,$option,$coderef); $self->set_callback_source($section,$option,$setting); return $coderef; } else { return $setting; } } =over 4 =item $flag = $features-Esafe([$flag]); This gets or sets and "safe" flag. If the safe flag is set, then calls to setting() will invoke code_setting(), allowing values that begin with the string "sub {" to be interpreted as anonymous subroutines. This is a potential security risk when used with untrusted files of features, so use it with care. =back =cut sub safe { my $self = shift; my $d = $self->{safe}; $self->{safe} = shift if @_; $self->evaluate_coderefs if $self->{safe} && !$d; $d; } =over 4 =item $flag = $features-Esafe_world([$flag]); This gets or sets and "safe_world" flag. If the safe_world flag is set, then values that begin with the string "sub {" will be evaluated in a "safe" compartment that gives minimal access to the system. This is not a panacea for security risks, so use with care. =back =cut sub safe_world { my $self = shift; my $safe = shift; if ($safe && !$self->{safe_content}) { # initialise the thing eval "require Safe::World; 1"; unless (Safe::World->can('new')) { warn "The Safe::World module is not installed on this system. Can't use it to evaluate codesubs in a safe context"; return; } unless ($self->{safe_lib}) { $self->{safe_lib} = Safe::World->new(sharepack => ['Bio::DB::SeqFeature', 'Bio::Graphics::Feature', 'Bio::SeqFeature::Lite', 'Bio::Graphics::Glyph', ]) or return; $self->{safe_lib}->eval(<{safe_context} = Safe::World->new(root => $self->base2package) or return; $self->{safe_context}->op_permit_only(':default'); $self->{safe_context}->link_world($self->{safe_lib}); $self->{safe_world} = $safe; } return $self->{safe_world}; } =over 4 =item $features-Eset_callback_source($type,$tag,$value) =item $features-Eget_callback_source($type,$tag) These routines are used internally to get and set the source of a sub {} callback. =back =cut sub set_callback_source { my $self = shift; my ($type,$tag,$value) = @_; $self->{source}{$type}{lc $tag} = $value; } sub get_callback_source { my $self = shift; my ($type,$tag) = @_; $self->{source}{$type}{lc $tag}; } =over 4 =item @args = $features-Estyle($type) Given a feature type, returns a list of track configuration arguments suitable for suitable for passing to the Bio::Graphics::Panel-Eadd_track() method. =back =cut # turn configuration into a set of -name=>value pairs suitable for add_track() sub style { my $self = shift; my $type = shift; my $config = $self->{config} or return; my $hashref = $config->{$type}; unless ($hashref) { $type =~ s/:.+$//; $hashref = $config->{$type} or return; } return map {("-$_" => $hashref->{$_})} keys %$hashref; } =over 4 =item $glyph = $features-Eglyph($type); Return the name of the glyph corresponding to the given type (same as $features-Esetting($type=E'glyph')). =back =cut # retrieve just the glyph part of the configuration sub glyph { my $self = shift; my $type = shift; my $config = $self->{config} or return; my $hashref = $config->{$type} or return; return $hashref->{glyph}; } =over 4 =item @types = $features-Econfigured_types() Return a list of all the feature types currently known to the feature file set. Roughly equivalent to: @types = grep {$_ ne 'general'} $features->setting; =back =cut # return list of configured types, in proper order sub configured_types { my $self = shift; my $types = $self->{types} or return; return @$types; } sub labels { return shift->configured_types; } =over 4 =item @types = $features-Etypes() This is similar to the previous method, but will return *all* feature types, including those that are not configured with a stanza. =back =cut sub types { my $self = shift; my $db = $self->db; $self->_patch_old_bioperl; return $self->db->types; } sub _patch_old_bioperl { my $self = shift; if ($Bio::Root::Version::VERSION >= 1.0069 && $Bio::Root::Version::VERSION <= 1.006901 ) { # bad version! local $^W=0; *Bio::DB::SeqFeature::Store::memory::types = sub { my $self = shift; eval "require Bio::DB::GFF::Typename" unless Bio::DB::GFF::Typename->can('new'); my @types; for my $primary_tag ( keys %{$$self{_index}{type}} ) { for my $source_tag ( keys %{$$self{_index}{type}{$primary_tag}} ) { push @types, Bio::DB::GFF::Typename->new($primary_tag,$source_tag); } } return @types; } } } =over 4 =item $features = $features-Efeatures($type) Return a list of all the feature types of type "$type". If the featurefile object was created by parsing a file or text scalar, then the features will be of type Bio::Graphics::Feature (which follow the Bio::FeatureI interface). Otherwise the list will contain objects of whatever type you added with calls to add_feature(). Two APIs: 1) original API: # Reference to an array of all features of type "$type" $features = $features-Efeatures($type) # Reference to an array of all features of all types $features = $features-Efeatures() # A list when called in a list context @features = $features-Efeatures() 2) Bio::Das::SegmentI API: @features = $features-Efeatures(-type=>['list','of','types']); # variants $features = $features-Efeatures(-type=>['list','of','types']); $features = $features-Efeatures(-type=>'a type'); $iterator = $features-Efeatures(-type=>'a type',-iterator=>1); $iterator = $features-Efeatures(-type=>'a type',-seq_id=>$id,-start=>$start,-end=>$end); =back =cut # return features sub features { my $self = shift; my ($types,$iterator,$seq_id,$start,$end,@rest) = defined($_[0] && $_[0]=~/^-/) ? rearrange([['TYPE','TYPES'],'ITERATOR','SEQ_ID','START','END'],@_) : (\@_); $types = [$types] if $types && !ref($types); my @args = $types && @$types ? (-type=>$types) : (); push @args,(-seq_id => $seq_id) if $seq_id; push @args,(-start => $start) if defined $start; push @args,(-end => $end) if defined $end; my $db = $self->db; if ($iterator) { return $db->get_seq_stream(@args); } else { my @f = $db->features(@args); return wantarray ? @f : \@f; } } =over 4 =item @features = $features-Efeatures($type) Return a list of all the feature types of type "$type". If the featurefile object was created by parsing a file or text scalar, then the features will be of type Bio::Graphics::Feature (which follow the Bio::FeatureI interface). Otherwise the list will contain objects of whatever type you added with calls to add_feature(). =back =cut sub make_strand { local $^W = 0; return +1 if $_[0] =~ /^\+/ || $_[0] > 0; return -1 if $_[0] =~ /^\-/ || $_[0] < 0; return 0; } =head2 get_seq_stream Title : get_seq_stream Usage : $stream = $s->get_seq_stream(@args) Function: get a stream of features that overlap this segment Returns : a Bio::SeqIO::Stream-compliant stream Args : see below Status : Public This is the same as feature_stream(), and is provided for Bioperl compatibility. Use like this: $stream = $s->get_seq_stream('exon'); while (my $exon = $stream->next_seq) { print $exon->start,"\n"; } =cut sub get_seq_stream { my $self = shift; local $^W = 0; my @args = $_[0] =~ /^-/ ? (@_,-iterator=>1) : (-types=>\@_,-iterator=>1); $self->features(@args); } =head2 get_feature_by_name Usage : $db->get_feature_by_name(-name => $name) Function: fetch features by their name Returns : a list of Bio::DB::GFF::Feature objects Args : the name of the desired feature Status : public This method can be used to fetch a named feature from the file. The full syntax is as follows. Features can be filtered by their reference, start and end positions @f = $db->get_feature_by_name(-name => $name, -ref => $sequence_name, -start => $start, -end => $end); This method may return zero, one, or several Bio::Graphics::Feature objects. =cut sub get_feature_by_name { my $self = shift; my ($name,$ref,$start,$end) = rearrange(['NAME','REF','START','END'],@_); my @args; push @args,(-name => $name) if defined $name; push @args,(-seq_id => $ref) if defined $ref; push @args,(-start => $start)if defined $start; push @args,(-end => $end) if defined $end; return $self->db->features(@args); } sub get_features_by_name { shift->get_feature_by_name(@_) } =head2 search_notes Title : search_notes Usage : @search_results = $db->search_notes("full text search string",$limit) Function: Search the notes for a text string Returns : array of results Args : full text search string, and an optional row limit Status : public Each row of the returned array is a arrayref containing the following fields: column 1 Display name of the feature column 2 The text of the note column 3 A relevance score. =cut sub search_notes { my $self = shift; return $self->db->search_notes(@_); } =head2 get_feature_stream(), top_SeqFeatures(), all_SeqFeatures() Provided for compatibility with older BioPerl and/or Bio::DB::GFF APIs. =cut *get_feature_stream = \&get_seq_stream; *top_SeqFeatures = *all_SeqFeatures = \&features; =over 4 =item @refs = $features-Erefs Return the list of reference sequences referred to by this data file. =back =cut sub refs { my $self = shift; my $refs = $self->{refs} or return; keys %$refs; } =over 4 =item $min = $features-Emin Return the minimum coordinate of the leftmost feature in the data set. =back =cut sub min { my $self = shift; $self->_min_max(); $self->{min}; } =over 4 =item $max = $features-Emax Return the maximum coordinate of the rightmost feature in the data set. =back =cut sub max { my $self = shift; $self->_min_max(); $self->{max}; } sub _min_max { my $self = shift; return if defined $self->{min} and defined $self->{max}; my ($min,$max); if (my $bases = $self->setting(general=>'bases')) { ($min,$max) = $bases =~ /^(-?\d+)(?:\.\.|-)(-?\d+)/; } if (!defined $min) { # otherwise sort through the features my $fs = $self->get_seq_stream; while (my $f = $fs->next_seq) { $min = $f->start if !defined $min or $min > $f->start; $max = $f->end if !defined $max or $max < $f->start; } } @{$self}{'min','max'} = ($min,$max); } sub init_parse { my $s = shift; $s->{max} = $s->{min} = undef; $s->{types} = []; $s->{features} = {}; $s->{config} = {}; $s->{loader} = undef; $s->{state} = 'config'; $s->{feature_count}= 0; } sub finish_parse { my $s = shift; if ($s->safe) { $s->initialize_code; $s->evaluate_coderefs; } elsif ($s->safe_world) { $s->evaluate_safecoderefs; } $s->{loader}->finish_load() if $s->{loader}; $s->{loader} = undef; $s->{state} = 'config'; } sub evaluate_coderefs { my $self = shift; for my $s ($self->_setting) { for my $o ($self->_setting($s)) { $self->code_setting($s,$o); } } } sub evaluate_safecoderefs { my $self = shift; for my $s ($self->_setting) { for my $o ($self->_setting($s)) { $self->safe_setting($s,$o); } } } sub clean_code { my $self = shift; for my $s ($self->_setting) { for my $o ($self->_setting($s)) { $self->_setting($s,$o,1) if $self->_setting($s,$o) =~ /\Asub\s*{/; } } } sub initialize_code { my $self = shift; my $package = $self->base2package; my $init_code = $self->_setting(general => 'init_code') or return; my $code = "package $package; $init_code; 1;"; eval $code; $self->_callback_complain(general=>'init_code') if $@; } sub base2package { my $self = shift; return $self->{base2package} if exists $self->{base2package}; my $rand = int rand(1000000); return $self->{base2package} = "Bio::Graphics::FeatureFile::CallBack::P$rand"; } sub split_group { my $self = shift; my $gff = $self->{gff} ||= Bio::DB::GFF->new(-adaptor=>'memory'); return $gff->split_group(shift, $self->{gff_version} > 2); } # create a panel if needed sub new_panel { my $self = shift; my $options = shift; eval "require Bio::Graphics::Panel" unless Bio::Graphics::Panel->can('new'); # general configuration of the image here my $width = $self->setting(general => 'pixels') || $self->setting(general => 'width') || WIDTH; my ($start,$stop); my $range_expr = '(-?\d+)(?:-|\.\.)(-?\d+)'; if (my $bases = $self->setting(general => 'bases')) { ($start,$stop) = $bases =~ /([\d-]+)(?:-|\.\.)([\d-]+)/; } if (!defined $start || !defined $stop) { $start = $self->min unless defined $start; $stop = $self->max unless defined $stop; } my $new_segment = Bio::Graphics::Feature->new(-start=>$start,-stop=>$stop); my @panel_options = %$options if $options && ref $options eq 'HASH'; my $panel = Bio::Graphics::Panel->new(-segment => $new_segment, -width => $width, -key_style => 'between', $self->style('general'), @panel_options ); $panel; } =over 4 =item $mtime = $features-Emtime =item $atime = $features-Eatime =item $ctime = $features-Ectime =item $size = $features-Esize Returns stat() information about the data file, for featurefile objects created using the -file option. Size is in bytes. mtime, atime, and ctime are in seconds since the epoch. =back =cut sub mtime { my $self = shift; my $d = $self->{m_time} || $self->{stat}->[9]; $self->{m_time} = shift if @_; $d; } sub atime { shift->{stat}->[8]; } sub ctime { shift->{stat}->[10]; } sub size { shift->{stat}->[7]; } =over 4 =item $label = $features-Efeature2label($feature) Given a feature, determines the configuration stanza that bests describes it. Uses the feature's type() method if it has it (DasI interface) or its primary_tag() method otherwise. =back =cut sub feature2label { my $self = shift; my $feature = shift; my $type = $feature->can('type') ? $feature->type : $feature->primary_tag; $type or return; (my $basetype = $type) =~ s/:.+$//; my @labels = $self->type2label($type); @labels = $self->type2label($basetype) unless @labels; @labels = ($type) unless @labels; wantarray ? @labels : $labels[0]; } =over 4 =item $link = $features-Elink_pattern($linkrule,$feature,$panel) Given a feature, tries to generate a URL to link out from it. This uses the 'link' option, if one is present. This method is a convenience for the generic genome browser. =back =cut sub link_pattern { my $self = shift; my ($linkrule,$feature,$panel,$dont_escape) = @_; $panel ||= 'Bio::Graphics::Panel'; if (ref($linkrule) && ref($linkrule) eq 'CODE') { my $val = eval {$linkrule->($feature,$panel)}; $self->_callback_complain(none=>"linkrule for $feature") if $@; return $val; } require CGI unless defined &CGI::escape; my $escape_method = $dont_escape ? sub {shift} : \&CGI::escape; my $n; $linkrule ||= ''; # prevent uninit warning my $seq_id = $feature->can('seq_id') ? $feature->seq_id() : $feature->location->seq_id(); $seq_id ||= $feature->seq_id; #fallback $linkrule =~ s!\$(\w+)! $escape_method->( $1 eq 'ref' ? (($n = $seq_id) && "$n") || '' : $1 eq 'name' ? (($n = $feature->display_name) && "$n") || '' : $1 eq 'class' ? eval {$feature->class} || '' : $1 eq 'type' ? eval {$feature->method} || $feature->primary_tag || '' : $1 eq 'method' ? eval {$feature->method} || $feature->primary_tag || '' : $1 eq 'source' ? eval {$feature->source} || $feature->source_tag || '' : $1 =~ 'seq_?id' ? eval{$feature->seq_id} || eval{$feature->location->seq_id} || '' : $1 eq 'start' ? $feature->start || '' : $1 eq 'end' ? $feature->end || '' : $1 eq 'stop' ? $feature->end || '' : $1 eq 'segstart' ? $panel->start || '' : $1 eq 'segend' ? $panel->end || '' : $1 eq 'length' ? $feature->length || 0 : $1 eq 'description' ? eval {join '',$feature->notes} || '' : $1 eq 'id' ? eval {$feature->feature_id} || eval {$feature->primary_id} || '' : '$'.$1 ) !exg; return $linkrule; } sub make_link { my $self = shift; my ($feature,$panel) = @_; my ($linkrule) = $feature->each_tag_value('link'); unless ($linkrule) { for my $label ($self->feature2label($feature)) { $linkrule ||= $self->setting($label,'link'); $linkrule ||= $self->setting(general=>'link'); } } return $self->link_pattern($linkrule,$feature,$panel); } sub make_title { my $self = shift; my $feature = shift; for my $label ($self->feature2label($feature)) { my $linkrule = $self->setting($label,'title'); $linkrule ||= $self->setting(general=>'title'); next unless $linkrule; return $self->link_pattern($linkrule,$feature,undef,1); } my $method = eval {$feature->method} || $feature->primary_tag; my $seqid = $feature->can('seq_id') ? $feature->seq_id : $feature->location->seq_id; my $title = eval { if ($feature->can('target') && (my $target = $feature->target)) { join (' ', $method, (defined $seqid ? "$seqid:" : ''). $feature->start."..".$feature->end, $feature->target.':'. $feature->target->start."..".$feature->target->end); } else { join(' ', $method, $feature->can('display_name') ? $feature->display_name : $feature->info, (defined $seqid ? "$seqid:" : ''). ($feature->start||'?')."..".($feature->end||'?') ); } }; warn $@ if $@; $title; } # given a feature type, return its label(s) sub type2label { my $self = shift; my $type = shift; $self->{_type2label} ||= $self->invert_types; my @labels = keys %{$self->{_type2label}{lc $type}}; wantarray ? @labels : $labels[0] } sub invert_types { my $self = shift; my $config = $self->{config} or return; my %inverted; for my $label (keys %{$config}) { my $feature = $config->{$label}{feature} || $label; foreach (shellwords($feature||'')) { $inverted{lc $_}{$label}++; } } \%inverted; } =over 4 =item $citation = $features-Ecitation($feature) Given a feature, tries to generate a citation for it, using the "citation" option if one is present. This method is a convenience for the generic genome browser. =back =cut # This routine returns the "citation" field. It is here in order to simplify the logic # a bit in the generic browser sub citation { my $self = shift; my $feature = shift || 'general'; return $self->setting($feature=>'citation'); } =over 4 =item $name = $features-Ename([$feature]) Get/set the name of this feature set. This is a convenience method useful for keeping track of multiple feature sets. =back =cut # give this feature file a nickname sub name { my $self = shift; my $d = $self->{name}; $self->{name} = shift if @_; $d; } 1; __END__ =head1 Appendix -- Sample Feature File # file begins [general] pixels = 1024 bases = 1-20000 reference = Contig41 height = 12 [mRNA] glyph = gene key = Spliced genes [Cosmid] glyph = segments fgcolor = blue key = C. elegans conserved regions [EST] glyph = segments bgcolor= yellow connector = dashed height = 5; [FGENESH] glyph = transcript2 bgcolor = green description = 1 mRNA B0511.1 Chr1:1..100 Type=UTR;Note="putative primase" mRNA B0511.1 Chr1:101..200,300..400,500..800 Type=CDS mRNA B0511.1 Chr1:801..1000 Type=UTR reference = Chr3 Cosmid B0511 516..619 Cosmid B0511 3185..3294 Cosmid B0511 10946..11208 Cosmid B0511 13126..13511 Cosmid B0511 11394..11539 EST yk260e10.5 15569..15724 EST yk672a12.5 537..618,3187..3294 EST yk595e6.5 552..618 EST yk595e6.5 3187..3294 EST yk846e07.3 11015..11208 EST yk53c10 yk53c10.3 15000..15500,15700..15800 yk53c10.5 18892..19154 EST yk53c10.5 16032..16105 SwissProt PECANEX 13153-13656 Note="Swedish fish" FGENESH "Predicted gene 1" 1-205,518-616,661-735,3187-3365,3436-3846 "Pfam domain" # file ends =head1 SEE ALSO L, L, L, L, L =head1 AUTHOR Lincoln Stein Elstein@cshl.orgE. Copyright (c) 2001 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Bio-Graphics-2.37/lib/Bio/Graphics/Wiggle.pm��������������������������������������������������������000555��001750��001750�� 64366�12165075746� 20702� 0����������������������������������������������������������������������������������������������������ustar�00lstein��������������������������lstein��������������������������000000��000000�������������������������������������������������������������������������������������������������������������������������������������������������������������������������package Bio::Graphics::Wiggle; =head1 NAME Bio::Graphics::Wiggle -- Binary storage for dense genomic features =head1 SYNOPSIS # all positions are 1-based my $wig = Bio::Graphics::Wiggle->new('./test.wig', $writeable, { seqid => $seqid, start => $start, step => $step, min => $min, max => $max }); $wig->erase; my $seqid = $wig->seqid('new_id'); my $max = $wig->max($new_max); my $min = $wig->min($new_min); my $step = $wig->step($new_step); # data stored at modulus step == 0; all else is blank $wig->set_value($position => $value); # store $value at position $wig->set_values($position => \@values); # store array of values at position $wig->set_range($start=>$end,$value); # store the same $value from $start to $end my $value = $wig->value($position); # fetch value from position my $values = $wig->values($start,$end); # fetch range of data from $start to $end $wig->window(100); # sample window size $wig->smoothing('mean'); # when sampling, compute the mean value across sample window my $values = $wig->values($start,$end,$samples); # fetch $samples data points from $start to $end =head1 DESCRIPTION IMPORTANT NOTE: This implementation is still not right. See http://genomewiki.ucsc.edu/index.php/Wiggle for a more space-efficient implementation. This module stores "wiggle" style quantitative genome data for display in a genome browser application. The data for each chromosome (or contig, or other reference sequence) is stored in a single file in the following format: 256 byte header 50 bytes seqid, zero-terminated C string 4 byte long integer, value of "step" (explained later) 4 byte perl native float, the "min" value 4 byte perl native float, the "max" value 4 byte long integer, value of "span" 4 byte perl native float, the mean 4 byte perl native float, the standard deviation 2 byte unsigned short, the version number (currently version 0) 4 byte long integer, sequence start position (in 0-based coordinates) null padding to 256 bytes for future use The remainder of the file consists of 8-bit unsigned scaled integer values. This means that all quantitative data will be scaled to 8-bit precision! For a convenient method of creating Wiggle files from UCSC-type WIG input and creating GFF3 output, please see L. =head1 METHODS =head2 Constructor and Accessors =over 4 =item $wig = Bio::Graphics::Wiggle->new($filename,$writeable,{options}) Open/create a wiggle-format data file: $filename -- path to the file to open/create $writeable -- boolean value indicating whether file is writeable. Missing files will only be created if $writeable set to a true value. If path is empty (undef or empty string) and writeable is true, new() will create a temporary file that will be deleted when the object goes out of scope. {options} -- hash ref of the following named options, only valid when creating a new wig file with $writeable true. option name description default ----------- ----- ------- seqid name/id of sequence empty name min minimum value of data points 0 max maximum value of data points 255 step interval between data points 1 span width of data points value of "step" The "step" can be used to create sparse files to save space. By default, step is set to 1, in which case a data value will be stored at each base of the sequence. By setting step to 10, then each value is taken to correspond to 10 bp, and the file will be 10x smaller. For example, consider this step 5 data set: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 20 . . . . 60 . . . . 80 . . . We have stored the values "20" "60" and "80" at positions 1, 6 and 11, respectively. When retrieving this data, it will appear as if positions 1 through 5 have a value of 20, positions 6-10 have a value of 60, and positions 11-14 have a value of 80. In the data file, we store, positions 1,6,and 11 in adjacent bytes. Note that no locking is performed by this module. If you wish to allow multi-user write access to the databases files, you will need to flock() the files yourself. =item $seqid = $wig->seqid(['new_id']) =item $max = $wig->max([$new_max]) =item $min = $wig->min([$new_min]) =item $step = $wig->step([$new_step]) =item $span = $wig->span([$new_span]) =item $mean = $wig->mean([$new_mean]); =item $stdev = $wig->stdev([$new_stdev]); These accessors get or set the corresponding values. Setting is only allowed if the file was opened for writing. Note that changing the min, max and step after writing data to the file under another parameter set will produce unexpected (and invalid) results, as the existing data is not automatically updated to be consistent. =item $trim = $wig->trim([$new_trim]); The trim method sets the trimming method, which can be used to trim out extreme values. Three methods are currently supported: none No trimming stdev Trim 1 standard deviation above and below mean stdevN Trim N standard deviations above and below the mean In "stdevN", any can be any positive integer. =back =head2 Setting Data =over 4 =item $wig->set_value($position => $value) This method sets the value at $position to $value. If a step>1 is in force, then $position will be rounded down to the nearest multiple of step. =item $wig->set_range($start=>$end, $value) This method sets the value of all bases between $start and $end to $value, honoring step. =item $sig->set_values($position => \@values) This method writes an array of values into the datababase beginning at $position (or the nearest lower multiple of step). If step>1, then values will be written at step intervals. =back =head2 Retrieving Data =over 4 =item $value = $wig->value($position) Retrieve the single data item at position $position, or the nearest lower multiple of $step if step>1. =item $values = $wig->values($start=>$end) Retrieve the values in the range $start to $end and return them as an array ref. Note that you will always get an array of size ($end-$start+1) even if step>1; the data in between the step intervals will be filled in. =item $values = $wig->values($start=>$end,$samples) Retrieve a sampling of the values between $start and $end. Nothing very sophisticated is done here; the code simply returns the number of values indicated in $samples, smoothed according to the smoothing method selected (default to "mean"), then selected at even intervals from the range $start to $end. The return value is an arrayref of exactly $samples values. =item $string = $wig->export_to_wif($start,$end) =item $string = $wig->export_to_wif64($start,$end) Export the region from start to end in the "wif" format. This data can later be imported into another Bio::Graphics::Wiggle object. The first version returns a binary string. The second version returns a base64 encoded version that is safe for ascii-oriented formata such as GFF3 and XML. =item $wig->import_from_wif($string) =item $wig->import_from_wif64($string) Import a wif format data string into the Bio::Graphics::Wiggle object. The first version expects a binary string. The second version expects a base64 encoded version that is safe for ascii-oriented formata such as GFF3 and XML. =back =cut # read/write genome tiling data, to be compatible with Jim Kent's WIG format use strict; use warnings; use IO::File; use Carp 'croak','carp','confess'; use constant HEADER_LEN => 256; # seqid, step, min, max, span, mean, stdev, version, start use constant HEADER => '(Z50LFFLFFSL)@'.HEADER_LEN; use constant BODY => 'C'; use constant DEBUG => 0; use constant DEFAULT_SMOOTHING => 'mean'; use constant VERSION => 0; our $VERSION = '1.0'; sub new { my $class = shift; my ($path,$write,$options) = @_; $path ||= ''; # to avoid uninit warning my $mode = $write ? -e $path # if file already exists... ? '+<' # ...open for read/write : '+>' # ...else clobber and open a new one : '<'; # read only my $fh = $class->new_fh($path,$mode); $fh or die (($path||'temporary file').": $!"); $options ||= {}; my $self = bless {fh => $fh, write => $write, dirty => scalar keys %$options }, ref $class || $class; my $stored_options = eval {$self->_readoptions} || {}; $options->{start}-- if defined $options->{start}; # 1-based ==> 0-based coordinates my %merged_options = (%$stored_options,%$options); # warn "merged options = ",join ' ',%merged_options; $merged_options{version}||= 0; $merged_options{seqid} ||= 'chrUnknown'; $merged_options{min} ||= 0; $merged_options{max} ||= 255; $merged_options{mean} ||= 128; $merged_options{stdev} ||= 255; $merged_options{trim} ||= 'none'; $merged_options{step} ||= 1; $merged_options{start} ||= 0; $merged_options{span} ||= $merged_options{step}; $self->{options} = \%merged_options; $self->_do_trim unless $self->trim eq 'none'; return $self; } sub new_fh { my $self = shift; my ($path,$mode) = @_; return $path ? IO::File->new($path,$mode) : IO::File->new_tmpfile; } sub end { my $self = shift; unless (defined $self->{end}) { my $size = (stat($self->fh))[7]; my $data_len = $size - HEADER_LEN(); return unless $data_len>0; # undef end $self->{end} = ($self->start-1) + $data_len * $self->step; } return $self->{end}; } sub DESTROY { shift->write } sub erase { my $self = shift; $self->fh->truncate(HEADER_LEN); } sub fh { shift->{fh} } sub seek { shift->fh->seek(shift,0) } sub tell { shift->fh->tell() } sub _option { my $self = shift; my $option = shift; my $d = $self->{options}{$option}; if (@_) { $self->{dirty}++; $self->{options}{$option} = shift; delete $self->{scale} if $option eq 'min' or $option eq 'max'; } return $d; } sub version { shift->_option('version',@_) } sub seqid { shift->_option('seqid',@_) } sub min { shift->_option('min',@_) } sub max { shift->_option('max',@_) } sub step { shift->_option('step',@_) } sub span { shift->_option('span',@_) } sub mean { shift->_option('mean',@_) } sub stdev { shift->_option('stdev',@_) } sub trim { shift->_option('trim',@_) } sub start { # slightly different because we have to deal with 1 vs 0-based coordinates my $self = shift; my $start = $self->_option('start'); $start++; # convert into 1-based coordinates if (@_) { my $newstart = shift; $self->_option('start',$newstart-1); # store in zero-based coordinates } return $start; } sub smoothing { my $self = shift; my $d = $self->{smoothing} || DEFAULT_SMOOTHING; $self->{smoothing} = shift if @_; $d; } sub write { my $self = shift; if ($self->{dirty} && $self->{write}) { $self->_writeoptions($self->{options}); undef $self->{dirty}; $self->fh->flush; } } sub _readoptions { my $self = shift; my $fh = $self->fh; my $header; $fh->seek(0,0); return unless $fh->read($header,HEADER_LEN) == HEADER_LEN; return $self->_parse_header($header); } sub _parse_header { my $self = shift; my $header = shift; my ($seqid,$step,$min,$max,$span, $mean,$stdev,$version,$start) = unpack(HEADER,$header); return { seqid => $seqid, step => $step, span => $span, min => $min, max => $max, mean => $mean, stdev => $stdev, version => $version, start => $start, }; } sub _generate_header { my $self = shift; my $options = shift; return pack(HEADER,@{$options}{qw(seqid step min max span mean stdev version start)}); } sub _writeoptions { my $self = shift; my $options = shift; my $fh = $self->fh; my $header = $self->_generate_header($options); $fh->seek(0,0); $fh->print($header) or die "write failed: $!"; } sub _do_trim { my $self = shift; # don't trim if there is no score range ($self->max - $self->min) or return; my $trim = lc $self->trim; my ($method,$arg); if ($trim =~ /([a-z]+)(\d+)/) { $method = "_trim_${1}"; $arg = $2; } else { $method = "_trim_${trim}"; } unless ($self->can($method)) { carp "invalid trim method $trim"; return; } $self->$method($arg); } # trim n standard deviations from the mean sub _trim_stdev { my $self = shift; my $factor = shift || 1; my $mean = $self->mean; my $stdev = $self->stdev * $factor; my $min = $self->min > $mean - $stdev ? $self->min : $mean - $stdev; my $max = $self->max < $mean + $stdev ? $self->max : $mean + $stdev; warn "_trim_stdev (* $factor) : setting min to $min, max to $max (was ",$self->min,',',$self->max,')' if DEBUG; $self->min($min); $self->max($max); } sub set_value { my $self = shift; croak "usage: \$wig->set_value(\$position => \$value)" unless @_ == 2; $self->value(@_); } sub set_range { my $self = shift; croak "usage: \$wig->set_range(\$start_position => \$end_position, \$value)" unless @_ == 3; $self->value(@_); } sub value { my $self = shift; my $position = shift; my $offset = $self->_calculate_offset($position); $offset >= HEADER_LEN or die "Tried to retrieve data from before start position"; $self->seek($offset) or die "Seek failed: $!"; if (@_ == 2) { my $end = shift; my $new_value = shift; my $step = $self->step; my $scaled_value = $self->scale($new_value); $self->fh->print(pack('C*',($scaled_value)x(($end-$position+1)/$step))) or die "Write failed: $!"; $self->{end} = $end if !exists $self->{end} || $self->{end} < $end; } elsif (@_==1) { my $new_value = shift; my $scaled_value = $self->scale($new_value); $self->fh->print(pack('C*',$scaled_value)) or die "Write failed: $!"; $self->{end} = $position if !exists $self->{end} || $self->{end} < $position; return $new_value; } else { # retrieving data my $buffer; $self->fh->read($buffer,1) or die "Read failed: $!"; my $scaled_value = unpack('C*',$buffer); # missing data, so look back at most span values to get it if ($scaled_value == 0 && (my $span = $self->span) > 1) { $offset = $self->_calculate_offset($position-$span+1); $offset >= HEADER_LEN or die "Tried to retrieve data from before start position"; $self->seek($offset) or die "Seek failed: $!"; $self->fh->read($buffer,$span/$self->step); for (my $i=length($buffer)-2;$i>=0;$i--) { my $val = substr($buffer,$i,1); next if $val eq "\0"; $scaled_value = unpack('C*',$val); last; } } return $self->unscale($scaled_value); } } sub _calculate_offset { my $self = shift; my $position = shift; my $step = $self->step; my $start = $self->start; return HEADER_LEN + int(($position-$start)/$step); } sub set_values { my $self = shift; croak "usage: \$wig->set_values(\$position => \@values)" unless @_ == 2 and ref $_[1] eq 'ARRAY'; $self->values(@_); } # read or write a series of values sub values { my $self = shift; my $start = shift; if (ref $_[0] && ref $_[0] eq 'ARRAY') { $self->_store_values($start,@_); } else { $self->_retrieve_values($start,@_); } } sub export_to_wif64 { my $self = shift; my $data = $self->export_to_wif(@_); eval "require MIME::Base64" unless MIME::Base64->can('encode_base64'); return MIME::Base64::encode_base64($data); } sub import_from_wif64 { my $self = shift; my $data = shift; eval "require MIME::Base64" unless MIME::Base64->can('decode_base64'); return $self->import_from_wif(MIME::Base64::decode_base64($data)); } # subregion in "wiggle interchange format" (wif) sub export_to_wif { my $self = shift; my ($start,$end) = @_; # get the 256 byte header my $data = $self->_generate_header($self->{options}); # add the range to the data (8 bytes overhead) $data .= pack("L",$start); $data .= pack("L",$end); # add the packed data for this range $data .= $self->_retrieve_packed_range($start,$end-$start+1,$self->step); return $data; } sub export_to_bedgraph { my $self = shift; my ($start,$end,$fh) = @_; my $max_range = 100_000; $start ||= 1; $end ||= $self->end; my $lines; for (my $s=$start;$s<$end;$s+=$max_range) { my $e = $s + $max_range - 1; $e = $end if $e > $end; my $b = $self->values($s,$e); $lines .= $self->_bedgraph_lines($s,$b,$fh); } return $lines; } sub _bedgraph_lines { my $self = shift; my ($start,$values,$fh) = @_; my $seqid = $self->seqid; my $result; my ($last_val,$last_start,$end); $last_start = $start-1; # 0 based indexing for (my $i=0;$i<@$values;$i++) { my $v = $values->[$i]; if (!defined $v) { if (defined $last_val) { $result .= $self->_append_or_print_bedgraph($fh,$seqid,$last_start,$start+$i-1,$last_val); undef $last_val; } $last_start = $start+$i; next; } if (defined $last_val && $last_val != $v) { $result .= $self->_append_or_print_bedgraph($fh,$seqid,$last_start,$start+$i-1,$last_val); $last_start = $start+$i-1; } $last_val = $v; $end = $start+$i-1; } $result .= $self->_append_or_print_bedgraph($fh,$seqid,$last_start,$end+1,$last_val) if $last_val; return $result; } sub _append_or_print_bedgraph { my $self = shift; my ($fh,$seqid,$start,$end,$val) = @_; my $data = join("\t",$seqid,$start,$end,sprintf("%.2f",$val))."\n"; if ($fh) { print $fh $data; return ''; } else { return $data; } } sub import_from_wif { my $self = shift; my $wifdata = shift; # BUG: should check that header is compatible my $header = substr($wifdata,0,HEADER_LEN); my $start = unpack('L',substr($wifdata,HEADER_LEN, 4)); my $end = unpack('L',substr($wifdata,HEADER_LEN+4,4)); my $options = $self->_parse_header($header); my $stored_options = eval {$self->_readoptions} || {}; my %merged_options = (%$stored_options,%$options); $self->{options} = \%merged_options; $self->{dirty}++; # write the data $self->seek($self->_calculate_offset($start)); $self->fh->print(substr($wifdata,HEADER_LEN+8)) or die "write failed: $!"; $self->{end} = $end if !defined $self->{end} or $self->{end} < $end; } sub _retrieve_values { my $self = shift; my ($start,$end,$samples) = @_; my $data_start = $self->start; my $step = $self->step; my $span = $self->span; croak "Value of start position ($start) is less than data start of $data_start" unless $start >= $data_start; croak "Value of end position ($end) is greater than data end of ",$self->end+$span, unless $end <= $self->end + $span; # generate list of positions to sample from my $length = $end-$start+1; $samples ||= $length; # warn "samples = $samples, length=$length, span=$span, step=$step"; # if the length is grossly greater than the samples, then we won't even # bother fetching all the data, but just sample into the disk file if ($length/$samples > 100 && $step == 1) { my @result; # my $window = 20*($span/$step); my $interval = $length/$samples; # my $window = 100*$interval/$span; my $window = $interval/2; # warn "window = $window, interval = $interval"; for (my $i=0;$i<$samples;$i++) { my $packed_data = $self->_retrieve_packed_range(int($start+$i*$interval-$window), int($window), $step); my @bases= grep {$_} unpack('C*',$packed_data); if (@bases) { local $^W = 0; my $arry = $self->unscale(\@bases); my $n = @$arry; my $total = 0; $total += $_ foreach @$arry; my $mean = $total/$n; my $max; for (@$arry) { $max = $_ if !defined $max || $max < $_ } # warn $start+$i*$interval,': ',join(',',map {int($_)} @$arry), # " mean = $mean, max = $max"; # push @result,$mean; push @result,$max; } else { push @result,0; } } return \@result; } my $packed_data = $self->_retrieve_packed_range($start,$length,$step); my @bases; $#bases = $length-1; if ($step == $span) { # in this case, we do not have any partially-empty # steps, so can operate on the step-length data structure # directly @bases = unpack('C*',$packed_data); } else { # In this case some regions may have partially missing data, # so we create an array equal to the length of the requested region, # fill it in, and then sample it for (my $i=0; $iunscale(\@bases); $r = $self->sample($r,$samples); $r = $self->smooth($r,$self->window * $samples/@bases) if defined $self->window && $self->window>1; return $r; } sub _retrieve_packed_range { my $self = shift; my ($start,$length,$step) = @_; my $span = $self->span; my $offset = $self->_calculate_offset($start); $self->seek($offset); my $packed_data; $self->fh->read($packed_data,$length/$step); # pad data up to required amount $packed_data .= "\0" x ($length/$step-length($packed_data)) if length $packed_data < $length/$step; return $packed_data; } sub sample { my $self = shift; my ($values,$samples) = @_; my $length = @$values; my $window_size = $length/$samples; my @samples; $#samples = $samples-1; if ($window_size < 2) { # no data smoothing needed @samples = map { $values->[$_*$window_size] } (0..$samples-1); } else { my $smoothsub = $self->smoothsub; for (my $i=0; $i<$samples; $i++) { my $start = $i * $window_size; my $end = $start + $window_size - 1; my @window = @{$values}[$start..$end]; my $value = $smoothsub->(\@window); $samples[$i] = $value; } } return \@samples; } sub smoothsub { my $self = shift; my $smoothing = $self->smoothing; my $smoothsub = $smoothing eq 'mean' ? \&sample_mean :$smoothing eq 'max' ? \&sample_max :$smoothing eq 'min' ? \&sample_min :$smoothing eq 'none' ? \&sample_center :croak("invalid smoothing type '$smoothing'"); return $smoothsub; } sub smooth { my ($self,$data,$window) = @_; my $smoothing = $self->smoothing; $window ||= $self->window; return $data if $smoothing eq 'none' || $window < 2; my @data = @$data; my $smoother = $self->smoothsub; $window++ unless $window % 2; my $offset = int($window/2); for (my $i=$offset; $i<@$data-$offset; $i++) { my $start = $i - $offset; my $end = $i + $offset; my @subset = @data[$start..$end]; $data->[$i] = $smoother->(\@subset); } return $data; } sub window { my $self = shift; my $d = $self->{window}; $self->{window} = shift if @_; $d; } sub sample_mean { my $values = shift; my ($total,$items); for my $v (@$values) { next unless defined $v; $items++; $total+=$v; } return $items ? $total/$items : undef; } sub sample_max { my $values = shift; my $max; for my $v (@$values) { next unless defined $v; $max = $v if !defined $max or $max < $v; } return $max; } sub sample_min { my $values = shift; my $min; for my $v (@$values) { next unless defined $v; $min = $v if !defined $min or $min > $v; } return $min; } sub sample_center { my $values = shift; return $values->[@$values/2]; } sub _store_values { my $self = shift; my ($position,$data) = @_; # where does data start my $offset = $self->_calculate_offset($position); my $fh = $self->fh; my $step = $self->step; my $scaled = $self->scale($data); $self->seek($offset); my $packed_data = pack('C*',@$scaled); $fh->print($packed_data) or die "Write failed: $!"; my $new_end = $position+@$data-1; $self->{end} = $new_end if !exists $self->{end} || $self->{end} < $new_end; } # zero means "no data" # everything else is scaled from 1-255 sub scale { my $self = shift; my $values = shift; my $scale = $self->_get_scale; my $min = $self->{options}{min}; if (ref $values && ref $values eq 'ARRAY') { my @return = map { my $i = ($_ - $min)/$scale; my $v = 1 + int($i+0.5*($i<=>0)); # avoid call to round() $v = 1 if $v < 1; $v = 255 if $v > 255; $v; } @$values; return \@return; } else { my $v = 1 + round (($values - $min)/$scale); $v = 1 if $v < 1; $v = 255 if $v > 255; return $v; } } sub unscale { my $self = shift; my $values = shift; my $scale = $self->_get_scale; my $min = $self->{options}{min}; if (ref $values && ref $values eq 'ARRAY') { my @return = map {$_ ? (($_-1) * $scale + $min) : undef} @$values; return \@return; } else { return $values ? ($values-1) * $scale + $min : undef; } } sub _get_scale { my $self = shift; unless ($self->{scale}) { my $min = $self->{options}{min}; my $max = $self->{options}{max}; my $range = $max - $min || 0.001; # can't be zero! $self->{scale} = $range/254; } return $self->{scale}; } sub round { return int($_[0]+0.5*($_[0]<=>0)); } 1; __END__ =head1 SEE ALSO L, L, L, L, L =head1 AUTHOR Lincoln Stein Elstein@cshl.orgE. Copyright (c) 2007 Cold Spring Harbor Laboratory This package and its accompanying libraries is free software; you can redistribute it and/or modify it under the terms of the GPL (either version 1, or at your option, any later version) or the Artistic License 2.0. Refer to LICENSE for the full license text. In addition, please see DISCLAIMER.txt for disclaimers of warranty. =cut ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Bio-Graphics-2.37/lib/Bio/Graphics/ConfiguratorI.pm�������������������������������������������������000555��001750��001750�� 10427�12165075746� 22224� 0����������������������������������������������������������������������������������������������������ustar�00lstein��������������������������lstein��������������������������000000��000000�������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BioPerl module for Bio::Graphics::ConfiguratorI # # Cared for by Robert Hubley # # Copyright Robert Hubley # # You may distribute this module under the same terms as perl itself # POD documentation - main docs before the code =head1 NAME Bio::Graphics::ConfiguratorI - A sectioned map of configuration options (a map of maps), with a default section. Intended to augment existing tag-Evalue semantics (ie. of Bio::AnnotationCollectionI) for object-representation information (eg. foreground color), and for general interface preferences (eg. image width in gbrowse). =head1 SYNOPSIS # get a ConfiguratorI somehow my $fg_color = $configurator->get('fgcolor'); =head1 DESCRIPTION This object contains various configuration parameters. It is divided up into sections and tags. This is essentially a multi-level map (section-Etag-Evalue). There is also the concept of a default section which is referenced when no section is passed to the ConfiguratorI methods. =head1 FEEDBACK =head2 Mailing Lists User feedback is an integral part of the evolution of this and other Bioperl modules. Send your comments and suggestions preferably to the Bioperl mailing list. Your participation is much appreciated. bioperl-l@bioperl.org - General discussion http://bioperl.org/wiki/Mailing_lists - About the mailing lists =head2 Reporting Bugs Report bugs to the Bioperl bug tracking system to help us keep track of the bugs and their resolution. Bug reports can be submitted via the web: http://bugzilla.open-bio.org/ =head1 AUTHOR - Robert Hubley Email rhubley@systemsbiology.org =head1 CONTRIBUTORS Paul Edlefsen, pedlefsen@systemsbiology.org Lincoln Stein, lstein@cshl.org Heikki Lehvaslaiho, heikki-at-bioperl-dot-org =head1 APPENDIX The rest of the documentation details each of the object methods. Internal methods are usually preceded with a _ =cut # Let the code begin... package Bio::Graphics::ConfiguratorI; use strict; use Carp; use base qw(Bio::Root::RootI); =head2 get_sections Title : get_sections Usage : my @values = $configurator->get_sections(); Function: Returns a list of the valid sections except the default or undef. Returns : A list of the sections which can be queried. Args : (optional section as string, tag as string) =cut sub get_sections { my ($self) = @_; $self->throw_not_implemented(); } =head2 get_tags Title : get_tags Usage : my @values = $configurator->get_tags(); or my @values = $configurator->get_tags('dna'); Function: Returns a list of tags for a given section or only the default tags section if no section is given. Returns : A scalar list of tags Args : =cut sub get_tags { my ($self) = @_; $self->throw_not_implemented(); } =head2 get Title : get Usage : my $value = $configurator->get('height'); or my $value = $configurator->get('dna','height'); Function: Returns a tag value from a configurator from the either the default "_general" section or from a specified section or undef. Returns : A scalar value for the tag Args : (optional section as string, tag as string) =cut sub get { my ($self) = @_; $self->throw_not_implemented(); } =head2 set Title : set Usage : $configurator->set('fgcolor','chartreuse'); or $configurator->set('EST','fgcolor','chartreuse'); Function: Set a value for a tag Returns : The old value of the tag Args : (optional section as string, tag as string, value as scalar) =cut sub set { my ($self) = @_; $self->throw_not_implemented(); } =head2 get_and_eval Title : get_and_eval Usage : my $value = $configurator->get_and_eval('height'); or my $value = $configurator->get_and_eval('dna','height'); Function: This works like get() except that it is also able to evaluate code references. These are options whose values begin with the characters "sub {". In this case the value will be passed to an eval() and the resulting codereference returned. Returns : A value of the tag or undef. Args : (optional section as string, tag as string) =cut sub get_and_eval { my ($self) = @_; $self->throw_not_implemented(); } 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Bio-Graphics-2.37/lib/Bio/Graphics/Wiggle�����������������������������������������������������������000755��001750��001750�� 0�12165075746� 20145� 5����������������������������������������������������������������������������������������������������ustar�00lstein��������������������������lstein��������������������������000000��000000�������������������������������������������������������������������������������������������������������������������������������������������������������������������������Bio-Graphics-2.37/lib/Bio/Graphics/Wiggle/Loader.pm�������������������������������������������������000555��001750��001750�� 50121�12165075746� 22070� 0����������������������������������������������������������������������������������������������������ustar�00lstein��������������������������lstein��������������������������000000��000000�������������������������������������������������������������������������������������������������������������������������������������������������������������������������package Bio::Graphics::Wiggle::Loader; =head1 SYNOPSIS my $loader = Bio::Graphics::Wiggle::Loader->new('/base/directory/for/wigfiles','wibfilename'); my $fh = IO::File->new('uploaded_file.txt'); $loader->load($fh); my $gff3_file = $loader->featurefile('gff3',$method,$source); my $featurefile = $loader->featurefile('featurefile'); my @features = $loader->features(); =head1 USAGE This module loads Bio::Graphics::Wiggle files from source files that use Jim Kent's "WIG" format: http://genome.ucsc.edu/google/goldenPath/help/wiggle.html Several data sets can be grouped together in a single WIG source file. The load() method accepts the path to a WIG source file, and will create one or more .wib ("wiggle binary") databases of quantitative data in the directory indicated when you created the loader. Call the featurefile() method to return a text file in either GFF3 or Bio::Graphics::FeatureFile format, suitable for loading into a gbrowse database. =head2 METHODS =over 4 =item $loader = Bio::Graphics::Wiggle::Loader->new('/base/directory' [,'my_data']) Create a new loader. The first argument specifies the base directory in which the loaded .wib files will be created. The second argument specifies the base name for the created .wib files, or "track" if not specified. =item $loader->load($fh) Load the data from a source WIG file opened on a filehandle. =item $data = $loader->featurefile($type [,$method,$source]) Return the data corresponding to a GFF3 or Bio::Graphics::FeatureFile. The returned file will have one feature per WIG track, and a properly formatted "wigfile" attribute that directs Bio::Graphics to the location of the quantitative data. $type is one of "gff3" or "featurefile". In the case of "gff3", you may specify an optional method and source for use in describing each feature. In the case of "featurefile", the returned file will contain GBrowse stanzas that describe a reasonable starting format to display the data. =item @features = $loader->features Returns one or more Bio::Graphics::Features objects, which can be used to create Bio::Graphics tracks with the wiggle_xyplot (and related) glyphs. =item $loader->allow_sampling(1) If allow_sampling() is passed a true value, then very large files (more than 5 MB) will undergo a sampling procedure to find their minimum and maximum values and standard deviation. Otherwise, file will be read in its entirety to generate those statistics. =back =head2 EXTENSIONS Several extensions to the WIG format "track" declaration are recognized. =over 4 =item transform= Specify a transform to be performed on all numeric data within this track prior to loading into the binary wig file. Currently, the following three declarations are recognized: transform=logtransform y' = 0 for y == 0 y' = log(y) for y > 0 y' = -log(-y) for y < 0 transform=logsquared y' = log(y**2) for y != 0 y' = 0 for y == 0 transform=none y' = y (no transform - the default) =item trim= Specify a trimming function to be performed on the data prior to scaling. Currently, the following trim functions are recognized: trim=stdev1 trim to plus/minus 1 standard deviation of the mean trim=stdev2 trim to plus/minus 2 standard deviations of the mean (default) trim=stdevN trim to plus/minus N standard deviations of the mean trim=none no trimming =back Example entended track declaration: track type=wiggle_0 name="example" description="20 degrees, 2 hr" \ trim=stdev2 transform=logsquared =cut use strict; use Carp 'croak'; use Statistics::Descriptive; use IO::Seekable; use File::Spec; use Bio::Graphics::Wiggle; use Bio::Graphics::FeatureFile; use Text::ParseWords(); use File::stat; use CGI 'escape'; use vars '%color_name'; # If a WIG file is very large (> 5 Mb) use constant BIG_FILE => 5_000_000; use constant BIG_FILE_SAMPLES => 5_000; # number of probes to make use constant DEFAULT_METHOD => 'microarray_oligo'; use constant DEFAULT_SOURCE => '.'; sub new { my $class = shift; my $base = shift or croak "Usage: Bio::Graphics::Wiggle::Loader->new('/base/path','trackname')"; my $trackname = shift || 'track'; my $wigclass = shift || 'Bio::Graphics::Wiggle'; -d $base && -w _ or croak "$base is not a writeable directory"; return bless { base => $base, tracks => {}, trackname => $trackname, tracknum => '000', track_options => {}, allow_sampling => 0, wigclass => $wigclass, },ref $class || $class; } sub allow_sampling { my $self = shift; my $d = $self->{allow_sampling}; $self->{allow_sampling} = shift if @_; $d; } sub wigclass { my $self = shift; my $d = $self->{wigclass}; $self->{wigclass} = shift if @_; return $d; } sub basedir { shift->{base} } sub wigfiles { shift->{wigfiles} } sub conf_stanzas { my $self = shift; my ($method,$source) = @_; $method ||= DEFAULT_METHOD; $source ||= DEFAULT_SOURCE; my $tracks = $self->{tracks}; my @lines = (); for my $track (sort keys %$tracks) { my $options = $tracks->{$track}{display_options}; my $name = $options->{name} ||= $track; $options->{visibility} ||= 'dense'; $options->{color} ||= $options->{visibility} =~ /pack/i ? '255,0,0' : '0,0,0'; $options->{altColor} ||= $options->{visibility} =~ /pack/i ? '0,0,255' : '0,0,0'; # stanza push @lines,"[$track]"; if (my $graph_type = $options->{glyph}) { if ($graph_type =~ /box/) { push @lines, "glyph = wiggle_box"; } else { push @lines,"glyph = ". ($graph_type =~/density/ ? 'wiggle_density' : 'wiggle_xyplot'); } } else { push @lines,"glyph = ". ($options->{visibility}=~/pack/ ? 'wiggle_density' : 'wiggle_xyplot'); } push @lines,"key = $options->{name}" if $options->{name}; push @lines,"description = $options->{description}" if $options->{description}; if (my $color = $options->{color}) { push @lines,"bgcolor=".format_color($color); } if (my $color = $options->{altColor}) { push @lines,"fgcolor=" . format_color($color); } if (exists $options->{viewLimits} and my ($low,$hi) = split ':',$options->{viewLimits}) { push @lines,"min_score = $low"; push @lines,"max_score = $hi"; } if (exists $options->{maxHeightPixels} and my ($max,$default,$min) = split ':',$options->{maxHeightPixels}) { push @lines,"height = $default"; } push @lines,"smoothing = $options->{windowingFunction}" if $options->{windowingFunction}; my $smoothing_window = $options->{smoothingWindow} || 0; push @lines,"smoothing window = $options->{smoothingWindow}" if $options->{smoothingWindow}; push @lines,''; } return join "\n",@lines; } sub featurefile { my $self = shift; my $type = shift; my ($method,$source) = @_; $method ||= DEFAULT_METHOD; $source ||= DEFAULT_SOURCE; $type ||= 'featurefile'; $type =~ /^(gff3|featurefile)$/i or croak "featurefile type must be one of 'gff3' or 'featurefile'"; my @lines; my $tracks = $self->{tracks}; if ($type eq 'gff3') { push @lines,"##gff-version 3",""; } else { push @lines,$self->conf_stanzas($method,$source),""; } for my $track (sort keys %$tracks) { my $options = $tracks->{$track}{display_options}; my $name = $options->{name} ||= $track; my $seqids = $tracks->{$track}{seqids}; my $note = escape($options->{description}); my @attributes; push @attributes,qq(Name=$name) if defined $name; push @attributes,qq(Note=$note) if defined $note; # data, sorted by chromosome my @seqid = sort keys %$seqids; for my $seqid (@seqid) { $seqid or next; $tracks->{$track}{seqids}{$seqid}{wig}->write(); my $attributes = join ';',(@attributes,"wigfile=$seqids->{$seqid}{wigpath}"); if ($type eq 'gff3') { push @lines,join "\t",($seqid,$source,$method, $seqids->{$seqid}{start}, $seqids->{$seqid}{end}, '.','.','.', $attributes ); } else { push @lines,''; push @lines,"reference=$seqid"; push @lines,"$track $seqid.data $seqids->{$seqid}{start}..$seqids->{$seqid}{end} $attributes"; } } } return join("\n",@lines)."\n"; } sub features { my $self = shift; my $text = $self->featurefile('featurefile'); my $file = Bio::Graphics::FeatureFile->new(-text=>$text); return $file->features; } sub load { my $self = shift; my $infh = shift; my $format = 'none'; local $_; LINE: while (<$infh>) { chomp; next if /^#/; next unless /\S/; if (/^track/) { $self->process_track_line($_); next; } if (/^fixedStep/) { $self->process_fixed_step_declaration($_); $format = 'fixed'; } if (/^variableStep/) { $self->process_variable_step_declaration($_); $format = 'variable'; } if (/^\S+\s+\d+\s+\d+\s+-?[\dEe.]+/) { $self->process_first_bedline($_); $format = 'bed'; } if ($format ne 'none') { # remember where we are, find min and max values, return my $pos = tell($infh); $self->minmax($infh,$format eq 'bed' ? $_ : '') unless $self->{track_options}{chrom} && exists $self->current_track->{seqids}{$self->{track_options}{chrom}}{min}; seek($infh,$pos,0); $self->process_bed($infh,$_) if $format eq 'bed'; $self->process_fixedline($infh) if $format eq 'fixed'; $self->process_variableline($infh) if $format eq 'variable'; $format = 'none'; } redo LINE if defined $_ && /^(track|variableStep|fixedStep)/; } return 1; } sub process_track_line { my $self = shift; my $line = shift; my @tokens = shellwords($line); shift @tokens; my %options = map {split '='} @tokens; $options{type} eq 'wiggle_0' or croak "invalid/unknown wiggle track type $options{type}"; delete $options{type}; $self->{tracknum}++; $self->current_track->{display_options} = \%options; } sub process_fixed_step_declaration { my $self = shift; my $line = shift; my @tokens = shellwords($line); shift @tokens; my %options = map {split '='} @tokens; exists $options{chrom} or croak "invalid fixedStep line: need a chrom option"; exists $options{start} or croak "invalid fixedStep line: need a start option"; exists $options{step} or croak "invalid fixedStep line: need a step option"; $self->{track_options} = \%options; } sub process_variable_step_declaration { my $self = shift; my $line = shift; my @tokens = shellwords($line); shift @tokens; my %options = map {split '='} @tokens; exists $options{chrom} or croak "invalid variableStep line: need a chrom option"; $self->{track_options} = \%options; } sub process_first_bedline { my $self = shift; my $line = shift; my @tokens = shellwords($line); $self->{track_options} = {chrom => $tokens[0]}; } sub current_track { my $self = shift; return $self->{tracks}{$self->{tracknum}} ||= {}; } sub minmax { my $self = shift; my ($infh,$bedline) = @_; local $_; my $transform = $self->get_transform; my $seqids = ($self->current_track->{seqids} ||= {}); my $chrom = $self->{track_options}{chrom}; if ($self->allow_sampling && (my $size = stat($infh)->size) > BIG_FILE) { warn "Wiggle file is very large; resorting to genome-wide sample statistics for $chrom.\n"; $self->{FILEWIDE_STATS} ||= $self->sample_file($infh,BIG_FILE_SAMPLES); for (keys %{$self->{FILEWIDE_STATS}}) { $seqids->{$chrom}{$_} = $self->{FILEWIDE_STATS}{$_}; } return; } my %stats; if ($bedline) { # left-over BED line my @tokens = split /\s+/,$bedline; my $seqid = $tokens[0]; my $value = $tokens[-1]; $value = $transform->($self,$value) if $transform; $stats{$seqid} ||= Statistics::Descriptive::Sparse->new(); $stats{$seqid}->add_data($value); } while (<$infh>) { last if /^track/; last if /chrom=(\S+)/ && $1 ne $chrom; next if /^\#|fixedStep|variableStep/; my @tokens = split(/\s+/,$_) or next; my $seqid = @tokens > 3 ? $tokens[0] : $chrom; my $value = $tokens[-1]; $value = $transform->($self,$value) if $transform; $stats{$seqid} ||= Statistics::Descriptive::Sparse->new(); $stats{$seqid}->add_data($value); } for my $seqid (keys %stats) { $seqids->{$seqid}{min} = $stats{$seqid}->min(); $seqids->{$seqid}{max} = $stats{$seqid}->max(); $seqids->{$seqid}{mean} = $stats{$seqid}->mean(); $seqids->{$seqid}{stdev} = $stats{$seqid}->standard_deviation(); } } sub sample_file { my $self = shift; my ($fh,$samples) = @_; my $transform = $self->get_transform; my $stats = Statistics::Descriptive::Sparse->new(); my $size = stat($fh)->size; my $count=0; while ($count < $samples) { seek($fh,int(rand $size),0) or die; scalar <$fh>; # toss first line my $line = <$fh>; # next full line $line or next; my @tokens = split /\s+/,$line; my $value = $tokens[-1]; next unless $value =~ /^[\d\seE.+-]+$/; # non-numeric $value = $transform->($self,$value) if $transform; $stats->add_data($value); $count++; } return { min => $stats->min, max => $stats->max, mean => $stats->mean, stdev => $stats->standard_deviation, }; } sub get_transform { my $self = shift; my $transform = $self->current_track->{display_options}{transform}; return $self->can($transform) if $transform; } # one and only transform currently defined # Natural log of the square of the value. # Return 0 if the value is 0 sub logsquared { my $self = shift; my $value = shift; return 0 if $value == 0; return log($value**2); } sub logtransform { my $self = shift; my $value = shift; return 0 if $value == 0; if ($value < 0) { return -log(-$value); } else { return log($value); } } sub process_bed { my $self = shift; my $infh = shift; my $oops = shift; my $transform = $self->get_transform; $self->process_bedline($oops) if $oops; while (<$infh>) { last if /^track/; next if /^#/; chomp; $self->process_bedline($_); } } sub process_bedline { my $self = shift; my ($line,$transform) = @_; my ($seqid,$start,$end,$value) = split /\s+/,$line; $value = $transform->($self,$value) if $transform; $start++; # to 1-based coordinates my $wigfile = $self->wigfile($seqid); $wigfile->set_range($start=>$end, $value); # update span $self->current_track->{seqids}{$seqid}{start} = $start unless exists $self->current_track->{seqids}{$seqid}{start} and $self->current_track->{seqids}{$seqid}{start} < $start; $self->current_track->{seqids}{$seqid}{end} = $end unless exists $self->current_track->{seqids}{$seqid}{end} and $self->current_track->{seqids}{$seqid}{end} > $end; } sub process_fixedline { my $self = shift; my $infh = shift; my $seqid = $self->{track_options}{chrom}; my $wigfile = $self->wigfile($seqid); my $start = $self->{track_options}{start}; my $step = $self->{track_options}{step}; my $span = $wigfile->span; # update start and end positions $self->{track_options}{span} ||= $wigfile->span || 1; my $chrom = $self->current_track->{seqids}{$seqid}; $chrom->{start} = $start if !defined $chrom->{start} || $chrom->{start} > $start; my $end = $chrom->{start} + $span - 1; $chrom->{end} = $end if !defined $chrom->{end} || $chrom->{end} < $end; my $transform = $self->get_transform; # write out data in 500K chunks for efficiency my @buffer; while (<$infh>) { last if /^(track|variableStep|fixedStep)/; next if /^#/; chomp; push @buffer,$_; if (@buffer >= 500_000) { @buffer = map {$transform->($self,$_)} @buffer if $transform; $wigfile->set_values($start=>\@buffer); my $big_step = $step * @buffer; $start += $big_step; $self->current_track->{seqids}{$seqid}{end} = $start + $big_step - 1 + $span; @buffer = (); # reset at the end } } @buffer = map {$transform->($self,$_)} @buffer if $transform; $wigfile->set_values($start=>\@buffer) if @buffer; $self->current_track->{seqids}{$seqid}{end} = $start + @buffer*$step - 1 + $span; } sub process_variableline { my $self = shift; my $infh = shift; my $seqid = $self->{track_options}{chrom}; my $chrom = $self->current_track->{seqids}{$seqid}; my $wigfile = $self->wigfile($seqid); my $span = $wigfile->span; my $transform = $self->get_transform; while (<$infh>) { last if /^(track|variableStep|fixedStep)/; next if /^#/; chomp; my ($start,$value) = split /\s+/ or next; $value = $transform->($self,$value) if $transform; eval { $wigfile->set_value($start=>$value); 1; } or croak "Data error on line $.: $_\nDetails: $@"; # update span $chrom->{start} = $start if !defined $chrom->{start} || $chrom->{start} > $start; my $end = $start + $span - 1; $chrom->{end} = $end if !defined $chrom->{end} || $chrom->{end} < $end; } $self->current_track->{seqids}{$seqid}{end} ||= $self->current_track->{seqids}{$seqid}{start}; } sub wigfile { my $self = shift; my $seqid = shift; my $ts = time(); my $current_track = $self->{tracknum}; my $tname = $self->{trackname} || 'track'; unless (exists $self->current_track->{seqids}{$seqid}{wig}) { my $path = File::Spec->catfile($self->{base},"$tname\_$current_track.$seqid.$ts.wib"); my @stats; foreach (qw(min max mean stdev)) { my $value = $self->current_track->{seqids}{$seqid}{$_} || $self->{FILEWIDE_STATS}{$_} || next; push @stats,($_=>$value); } my $step = $self->{track_options}{step} || 1; my $span = $self->{track_options}{span} || $self->{track_options}{step} || 1; my $trim = $self->current_track->{display_options}{trim} || 'stdev10'; my $transform = $self->current_track->{display_options}{transform}; my $class = $self->wigclass; unless ($class->can('new')) { warn "loading $class"; eval "require $class"; die $@ if $@; } my $wigfile = $class->new( $path, 1, { seqid => $seqid, step => $step, span => $span, trim => $trim, @stats, }, ); $wigfile or croak "Couldn't create wigfile $wigfile: $!"; $self->current_track->{seqids}{$seqid}{wig} = $wigfile; $self->current_track->{seqids}{$seqid}{wigpath} = $path; } return $self->current_track->{seqids}{$seqid}{wig}; } sub format_color { my $rgb = shift; return $rgb unless $rgb =~ /\d+,\d+,\d+/; my ($r,$g,$b) = split ',',$rgb; my $hex = '#'.join '',map {sprintf("%02X",$_)}($r,$g,$b); return translate_color($hex); } # use English names for the most common colors sub translate_color { my $clr = shift; unless (%color_name) { while () { chomp; my ($hex,$name) = split or next; $color_name{$hex} = $name; } } return $color_name{$clr} || $clr; } # work around an annoying uninit variable warning from Text::Parsewords sub shellwords { my @args = @_; return unless @args; foreach(@args) { s/^\s+//; s/\s+$//; $_ = '' unless defined $_; } my @result = Text::ParseWords::shellwords(@args); return @result; } 1; __DATA__ #000000 black #FFFFFF white #0000FF blue #00FF00 green #FF0000 red #FFFF00 yellow #00FFFF cyan #FF00FF magenta #C0C0C0 gray __END__ =head1 SEE ALSO L, L, L, L, L, L, L =head1 AUTHOR Lincoln Stein Elstein@cshl.orgE. Copyright (c) 2007 Cold Spring Harbor Laboratory This package and its accompanying libraries is free software; you can redistribute it and/or modify it under the terms of the GPL (either version 1, or at your option, any later version) or the Artistic License 2.0. Refer to LICENSE for the full license text. In addition, please see DISCLAIMER.txt for disclaimers of warranty. =cut �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Bio-Graphics-2.37/lib/Bio/Graphics/Glyph������������������������������������������������������������000755��001750��001750�� 0�12165075746� 20012� 5����������������������������������������������������������������������������������������������������ustar�00lstein��������������������������lstein��������������������������000000��000000�������������������������������������������������������������������������������������������������������������������������������������������������������������������������Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/wiggle_box.pm����������������������������������������������000555��001750��001750�� 24741�12165075746� 22666� 0����������������������������������������������������������������������������������������������������ustar�00lstein��������������������������lstein��������������������������000000��000000�������������������������������������������������������������������������������������������������������������������������������������������������������������������������package Bio::Graphics::Glyph::wiggle_box; use strict; use base qw(Bio::Graphics::Glyph::box Bio::Graphics::Glyph::smoothing); use File::Spec; sub draw { my $self = shift; my ($gd,$left,$top,$partno,$total_parts) = @_; my $feature = $self->feature; my $drawnit; my ($wigfile) = eval{$feature->get_tag_values('wigfile')}; if ($wigfile) { $self->draw_wigfile($feature,$self->rel2abs($wigfile),@_); $drawnit++; } my ($wigdata) = eval{$feature->get_tag_values('wigdata')}; if ($wigdata) { $self->draw_wigdata($feature,$wigdata,@_); $drawnit++; } my ($coverage) = eval{$feature->get_tag_values('coverage')}; if ($coverage) { $self->draw_coverage($feature,$coverage,@_); $drawnit++; } # support for BigWig/BigBed if ($feature->can('statistical_summary')) { my $stats = $feature->statistical_summary($self->width); my @vals = map {$_->{validCount} ? $_->{sumData}/$_->{validCount}:0} @$stats; $self->draw_coverage($feature,\@vals,@_); $drawnit++; } if ($drawnit) { $self->draw_label(@_) if $self->option('label'); $self->draw_description(@_) if $self->option('description'); return; } return $self->SUPER::draw(@_); } sub wig { my $self = shift; my $d = $self->{wig}; $self->{wig} = shift if @_; $d; } sub draw_wigdata { my $self = shift; my $feature = shift; my $data = shift; eval "require MIME::Base64" unless MIME::Base64->can('decode_base64'); my $unencoded_data = MIME::Base64::decode_base64($data); my $wig = eval { Bio::Graphics::Wiggle->new() }; unless ($wig) { warn $@; return $self->SUPER::draw(@_); } $wig->import_from_wif($unencoded_data); $self->wig($wig); $self->_draw_wigfile($feature,$wig,@_); } sub draw_wigfile { my $self = shift; my $feature = shift; my $wigfile = shift; eval "require Bio::Graphics::Wiggle" unless Bio::Graphics::Wiggle->can('new'); my $wig = Bio::Graphics::Wiggle->new($wigfile) or die; $self->wig($wig); $self->_draw_wigfile($feature,$wig,@_); } sub _draw_wigfile { my $self = shift; my $feature = shift; my $wig = shift; my ($gd,$left,$top) = @_; my $start = $self->smooth_start; my $end = $self->smooth_end; my ($x1,$y1,$x2,$y2) = $self->bounds($left,$top); $self->draw_segment($gd, $start,$end, $wig,$start,$end, 1,1, $x1,$y1,$x2,$y2); } sub draw_coverage { my $self = shift; my $feature = shift; my $array = shift; $array = [split ',',$array] unless ref $array; my ($gd,$left,$top) = @_; my ($start,$end) = $self->effective_bounds($feature); my $length = $end - $start + 1; my $bases_per_bin = ($end-$start)/@$array; my @parts; my $samples = $length < $self->panel->width ? $length : $self->panel->width; my $samples_per_base = $samples/$length; for (my $i=0;$i<$samples;$i++) { my $offset = $i/$samples_per_base; my $v = $array->[$offset/$bases_per_bin]; push @parts,$v; } my ($x1,$y1,$x2,$y2) = $self->bounds($left,$top); $self->draw_segment($gd, $start,$end, \@parts, $start,$end, 1,1, $x1,$y1,$x2,$y2); } sub effective_bounds { # copied from wiggle_xyplot -- ouch! my $self = shift; my $feature = shift; my $panel_start = $self->panel->start; my $panel_end = $self->panel->end; my $start = $feature->start>$panel_start ? $feature->start : $panel_start; my $end = $feature->end<$panel_end ? $feature->end : $panel_end; return ($start,$end); } sub draw_segment { my $self = shift; my ($gd, $start,$end, $seg_data, $seg_start,$seg_end, $step,$span, $x1,$y1,$x2,$y2) = @_; # clip, because wig files do no clipping $seg_start = $start if $seg_start < $start; $seg_end = $end if $seg_end > $end; # figure out where we're going to start my $scale = $self->scale; # pixels per base pair my $pixels_per_span = $scale * $span + 1; my $pixels_per_step = 1; my $length = $end-$start+1; # if the feature starts before the data starts, then we need to draw # a line indicating missing data (this only happens if something went # wrong upstream) if ($seg_start > $start) { my $terminus = $self->map_pt($seg_start); $start = $seg_start; $x1 = $terminus; } # if the data ends before the feature ends, then we need to draw # a line indicating missing data (this only happens if something went # wrong upstream) if ($seg_end < $end) { my $terminus = $self->map_pt($seg_end); $end = $seg_end; $x2 = $terminus; } return unless $start < $end; # get data values across the area my $samples = $length < $self->panel->width ? $length : $self->panel->width; my $data = ref $seg_data eq 'ARRAY' ? $seg_data : $seg_data->values($start,$end,$samples); # scale the glyph if the data end before the panel does my $data_width = $end - $start; my $data_width_ratio; if ($data_width < $self->panel->length) { $data_width_ratio = $data_width/$self->panel->length; } else { $data_width_ratio = 1; } return unless $data && ref $data && @$data > 0 && grep {$_} @$data; # allocate colors my $bg_idx = $self->panel->translate_color($self->panel->rgb($self->bgcolor)); my $fg_idx = $self->panel->translate_color($self->panel->rgb($self->fgcolor)) || $bg_idx; $pixels_per_step = $scale * $step; $pixels_per_step = 1 if $pixels_per_step < 1; my $datapoints_per_base = @$data/$length; my $pixels_per_datapoint = $self->panel->width/@$data * $data_width_ratio; my $xstart; for (my $i = 0; $i <= @$data ; $i++) { $xstart ||= $x1 + $pixels_per_datapoint * $i if $data->[$i]; # trigger to draw the previous box is empty space of the end of the stack if (!$data->[$i] || ($i+1 == @$data)) { $xstart || next; my $xend = $x1 + $pixels_per_datapoint * $i; $self->filled_box($gd,$xstart,$y1,$xend,$y2,$bg_idx,$fg_idx); undef $xstart; } } } sub rel2abs { my $self = shift; my $wig = shift; my $path = $self->option('basedir'); return File::Spec->rel2abs($wig,$path); } 1; __END__ =head1 NAME Bio::Graphics::Glyph::wiggle_box - A generic box glyph compatible with dense "wig"data =head1 SYNOPSIS See and . =head1 DESCRIPTION This glyph works like the regular 'box' glyph but takes value data in Bio::Graphics::Wiggle file format: reference = chr1 ChipCHIP Feature1 1..10000 wigfile=./test.wig;wigstart=0 ChipCHIP Feature2 10001..20000 wigfile=./test.wig;wigstart=656 ChipCHIP Feature3 25001..35000 wigfile=./test.wig;wigstart=1312 The "wigfile" attribute gives a relative or absolute pathname to a Bio::Graphics::Wiggle format file. The optional "wigstart" option gives the offset to the start of the data. If not specified, a linear search will be used to find the data. The data consist of a packed binary representation of the values in the feature, using a constant step such as present in tiling array data. This glyph is intended for dense, qualitative feature data. Any score data for each data point is only evaluated for true/false, when true, a box of the specified bgcolor is drawn, when false, nothing is drawn. No data smoothing is used. Two primary benefits of using this glyph (with wiggle data) are: 1) For large, genome-wide data sets, the speed of panel rendering is greatly improved. 2) Large sets of related features can be rendered as a UCSC-style subtrack without the need for aggregation or a GFF3 containment hierarchy. A disadvantage to this approach is that individual features will have no attributes associated with them and will appear as anonymous blocks within a sub-track. An example use for this glyph is annotated transcribed regions from microarray experiments. Such regions are identified based on raw microarray data but do not necessarily have a score associated with them. In this case, using the wiggle_box glyph provides a graphical summary of an expression array experiment. =head2 DATA The wiggle data used for this glyph should be loaded using the 'BED' format in order to allow features of variable width. The fourth column should be a true value, with numeric or ".". An example is shown below: track type=wiggle_0 name="transfrags" description="D. melanogaster transcribed fragments 0-2hrs" 2L 9309 9451 1 2L 10697 11021 1 2L 11101 11345 1 2L 11410 11521 1 2L 11771 12243 1 2L 12314 12954 1 2L 13516 15746 1 2L 17033 17191 1 2L 18232 18580 1 2L 19860 19999 1 =head2 OPTIONS This glyph accepts the standard generic option set. It differs in that the label and description and title/mouseover labels apply to the whole, panel-wide sub-track feature rather than to individual boxes. See Bio::Graphics::Glyph::wiggle_xyplot for a description of the wiggle-specific options and data formats. =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Sheldon McKay Emckays@cshl.eduE. Copyright (c) 2008 Cold Spring Harbor Laboratory This package and its accompanying libraries is free software; you can redistribute it and/or modify it under the terms of the GPL (either version 1, or at your option, any later version) or the Artistic License 2.0. Refer to LICENSE for the full license text. In addition, please see DISCLAIMER.txt for disclaimers of warranty. =cut �������������������������������Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/read_pair.pm�����������������������������������������������000555��001750��001750�� 2451�12165075746� 22440� 0����������������������������������������������������������������������������������������������������ustar�00lstein��������������������������lstein��������������������������000000��000000�������������������������������������������������������������������������������������������������������������������������������������������������������������������������package Bio::Graphics::Glyph::read_pair; #specialized for SAM read pairs use base 'Bio::Graphics::Glyph::segments'; sub my_description { return <level == 0 ? 'dashed' : 'solid'; } sub parts_overlap { 1 } sub box_subparts { 2 } sub stranded { my $self = shift; my $s = $self->SUPER::stranded; return defined $s ? $s : 1; } sub bgcolor { my $self = shift; my $bg = $self->option('bgcolor'); $bg = $self->feature->strand > 0 ? 'red' : 'blue' unless defined $bg; return $self->factory->translate_color($bg); } sub draw_target { my $self = shift; my $t = $self->option('draw_target'); return $t if defined $t; return 1; } sub show_mismatch { my $self = shift; my $t = $self->option('show_mismatch'); return $t if defined $t; return 1; } sub label_position { my $self = shift; my $t = $self->option('label_position'); return $t if defined $t; return 'left'; } sub maxdepth { 2 } 1; �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/so_transcript.pm�������������������������������������������000555��001750��001750�� 2015�12165075746� 23400� 0����������������������������������������������������������������������������������������������������ustar�00lstein��������������������������lstein��������������������������000000��000000�������������������������������������������������������������������������������������������������������������������������������������������������������������������������package Bio::Graphics::Glyph::so_transcript; use strict; use base qw(Bio::Graphics::Glyph::processed_transcript); 1; __END__ =head1 NAME Bio::Graphics::Glyph::so_transcript - The sequence ontology transcript glyph =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This is a sequence-ontology compatible glyph, which works hand-in-hand with the so_transcript aggregator in BioPerl. This glyph is identical to "processed_transcript," which is described in detail in L. =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L =head1 AUTHOR Lincoln Stein Elstein@cshl.orgE Copyright (c) 2005 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/wiggle_whiskers.pm�����������������������������������������000555��001750��001750�� 22056�12165075746� 23732� 0����������������������������������������������������������������������������������������������������ustar�00lstein��������������������������lstein��������������������������000000��000000�������������������������������������������������������������������������������������������������������������������������������������������������������������������������package Bio::Graphics::Glyph::wiggle_whiskers; use strict; use base qw(Bio::Graphics::Glyph::wiggle_data Bio::Graphics::Glyph::wiggle_xyplot ); sub my_description { return < [ 'color', 'black', 'The color drawn from the zero value to the mean value.' ], mean_color_neg => [ 'color', 'same as mean_color', 'The color drawn from the zero value to the mean value, for negative values.' ], stdev_color => [ 'color', 'grey', 'The color drawn from the mean value to +stdev.' ], stdev_color_neg => [ 'color', 'same as stdev_color', 'The color drawn from the mean value to -stdev.' ], max_color => [ 'color', 'lightgrey', 'The color drawn from +stdev to max.' ], min_color => [ 'color', 'same as max_color', 'The color drawn from -stdev to min.' ], graph_type => [ ['histogram','whiskers'], 'histogram', 'Type of graph to generate. Options are "histogram" (for a barchart),', 'or "whiskers" (for a whiskerplot showing mean, +/- stdev and max/min.', 'The deprecated "boxes" subtype is a synonym for "histogram."' ], } } sub color_series { my $self = shift; return $self->{color_series} if exists $self->{color_series}; return $self->{color_series} = $self->option('color_series'); } sub overlaps { my $self = shift; return $self->{overlaps} if exists $self->{overlaps}; return $self->{overlaps} = $self->bump eq 'overlap'; } sub pad_top { my $self = shift; return $self->Bio::Graphics::Glyph::wiggle_xyplot::pad_top; } sub extra_label_pad { return 8 } sub graph_type { shift->glyph_subtype; } sub glyph_subtype { my $self = shift; return $self->option('glyph_subtype') || $self->option('graph_type') || 'histogram'; } sub mean_color { my $self = shift; return $self->bgcolor if $self->color_series; return $self->color('mean_color') || $self->translate_color('black'); } sub mean_color_neg { my $self = shift; return $self->bgcolor if $self->color_series; return $self->color('mean_color_neg') || $self->mean_color; } sub stdev_color { my $self = shift; return $self->bgcolor if $self->color_series; return $self->color('stdev_color') || $self->translate_color('grey'); } sub stdev_color_neg { my $self = shift; return $self->bgcolor if $self->color_series; return $self->color('stdev_color_neg') || $self->stdev_color; } sub max_color { my $self = shift; return $self->bgcolor if $self->color_series; return $self->color('max_color') || $self->translate_color('lightgrey'); } sub min_color { my $self = shift; return $self->bgcolor if $self->color_series; return $self->color('min_color') || $self->max_color; } sub draw { my $self = shift; my ($gd,$dx,$dy) = @_; my ($left,$top,$right,$bottom) = $self->calculate_boundaries($dx,$dy); $self->panel->startGroup($gd); my $feature = $self->feature; my $stats = eval {$feature->statistical_summary($self->width)}; if ($@ =~ /can\'t locate object method/i) { warn "This glyph only works properly with features that have a statistical_summary() method, but you passed a ",ref($feature)," object"; return; } $stats ||= []; my ($min_score,$max_score,$mean,$stdev) = $self->minmax($stats); my $rescale = $self->option('autoscale') eq 'z_score'; my $side = $self->_determine_side(); # if a scale is called for, then we adjust the max and min to be even # multiples of a power of 10. my ($scaled_min,$scaled_max); if ($rescale) { my $bound = $self->z_score_bound; $scaled_min = int(($min_score-$mean)/$stdev + 0.5); $scaled_max = int(($max_score-$mean)/$stdev + 0.5); $scaled_max = $bound if $scaled_max > $bound; $scaled_min = -$bound if $scaled_min < -$bound; $self->{_stdev} = $stdev; $self->{_mean} = $mean; $self->{_zfold} = $bound; } elsif ($side) { $scaled_min = Bio::Graphics::Glyph::xyplot::max10($min_score); $scaled_max = Bio::Graphics::Glyph::xyplot::min10($max_score); } my $height = $bottom - $top; my $scale = $scaled_max > $scaled_min ? $height/($scaled_max-$scaled_min) : 1; my $x = $left; my $y = $top + $self->pad_top; # position of "0" on the scale my $y_origin = $scaled_min <= 0 ? $bottom - (0 - $scaled_min) * $scale : $bottom; $y_origin = $top if $scaled_max < 0; my $clip_ok = $self->option('clip'); $self->{_clip_ok} = $clip_ok; $self->{_scale} = $scale; $self->{_min_score} = $scaled_min; $self->{_max_score} = $scaled_max; $self->{_top} = $top; $self->{_bottom} = $bottom; $self->panel->startGroup($gd); $self->_draw_grid($gd,$scale,$scaled_min,$scaled_max,$dx,$dy,$y_origin); $self->panel->endGroup($gd); $self->panel->startGroup($gd); $self->_draw_whiskers($gd,$dx,$dy,$y_origin,$stats); $self->panel->endGroup($gd); $self->panel->startGroup($gd); $self->_draw_scale($gd,$scale,$scaled_min,$scaled_max,$dx,$dy,$y_origin); $self->panel->endGroup($gd); $self->draw_label(@_) if $self->option('label') or $self->record_label_positions; $self->draw_description(@_) if $self->option('description'); $self->panel->endGroup($gd); # inhibit the scale if we are non-bumping $self->configure(-scale => 'none') if $self->overlaps; } sub _draw_whiskers { my $self = shift; my ($gd,$dx,$dy,$origin,$stats) = @_; my $scale = $self->{_scale}; my $mean_color = $self->mean_color; my $mean_color_neg = $self->mean_color_neg; my $stdev_color = $self->stdev_color; my $stdev_color_neg = $self->stdev_color_neg; my $max_color = $self->max_color; my $min_color = $self->min_color; my $clip_color = $self->clip_color; my $graph_type = $self->graph_type; my ($left,$top,$right,$bottom) = $self->calculate_boundaries($dx,$dy); my $pos = $self->{flip} ? $right : $left; for my $bin (@$stats) { next unless $bin->{validCount}; my $mean = $bin->{sumData}/$bin->{validCount}; my $stdev = $self->calcStdFromSums($bin->{sumData}, $bin->{sumSquares}, $bin->{validCount}); my $max = $bin->{maxVal}; my $min = $bin->{minVal}; if (my $fold = $self->{_zfold}) { $mean = ($mean - $self->{_mean}) / $self->{_stdev}; $max = ($max - $self->{_mean}) / $self->{_stdev}; $min = ($min - $self->{_mean}) / $self->{_stdev}; $stdev /= $self->{_stdev}; } my $mean_pos = $self->score2position($mean); my $plus_one = $self->score2position($mean+$stdev); my $minus_one = $self->score2position($mean-$stdev); my $max_pos = $self->score2position($max); my $min_pos = $self->score2position($min); my ($clip_top,$clip_bottom); foreach (\$mean_pos,\$plus_one,\$minus_one,\$max_pos,\$min_pos) { if (int($$_) < $top - 2) { $$_ = $top; $clip_top++; } elsif (int($$_) > $bottom + 2) { $$_ = $bottom; $clip_bottom++; } } if ($graph_type =~ /histogram|boxes/) { if ($mean >= 0) { $gd->line($pos,$origin,$pos,$mean_pos, $mean_color); $gd->line($pos,$mean_pos,$pos,$plus_one,$stdev_color) if $mean_pos != $plus_one; $gd->line($pos,$plus_one,$pos,$max_pos, $max_color) if $plus_one != $max_pos; } else { $gd->line($pos,$origin,$pos,$mean_pos, $mean_color_neg); $gd->line($pos,$mean_pos,$pos,$minus_one,$stdev_color_neg) if $mean_pos != $minus_one; $gd->line($pos,$minus_one,$pos,$min_pos, $min_color) if $minus_one != $min_pos; } } else { $gd->setPixel($pos,$mean_pos,$mean_color); $gd->line($pos,$mean_pos-1,$pos,$plus_one,$stdev_color) if $plus_one != $mean_pos; $gd->line($pos,$plus_one-1,$pos,$max_pos,$max_color) if $max_pos != $mean_pos; $gd->line($pos,$mean_pos+1,$pos,$minus_one,$stdev_color) if $minus_one != $mean_pos; $gd->line($pos,$minus_one+1,$pos,$min_pos,$min_color) if $min_pos != $mean_pos; } # this tops off clipped peaks with a distinct color, but I just don't like how it looks $gd->line($pos,$top-2, $pos,$top, $clip_color) if $clip_top; $gd->line($pos,$bottom,$pos,$bottom+2, $clip_color) if $clip_bottom; } continue { $self->{flip} ? $pos-- : $pos++; } } sub calcStdFromSums { my $self = shift; my ($sum,$sumSquares,$n) = @_; my $var = $sumSquares - $sum*$sum/$n; if ($n > 1) { $var /= $n-1; } return 0 if $var < 0; return sqrt($var); } 1; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/generic.pm�������������������������������������������������000555��001750��001750�� 70140�12165075746� 22146� 0����������������������������������������������������������������������������������������������������ustar�00lstein��������������������������lstein��������������������������000000��000000�������������������������������������������������������������������������������������������������������������������������������������������������������������������������package Bio::Graphics::Glyph::generic; use strict; use Bio::Graphics::Util qw(frame_and_offset); use base qw(Bio::Graphics::Glyph); use Memoize 'memoize'; #memoize('pad_left'); #memoize('pad_right'); my %complement = (g=>'c',a=>'t',t=>'a',c=>'g', G=>'C',A=>'T',T=>'A',C=>'G'); # new options are 'label' -- short label to print over glyph # 'description' -- long label to print under glyph # label and description can be flags or coderefs. # If a flag, label will be taken from seqname, if it exists or primary_tag(). # description will be taken from source_tag(). sub my_description { return < [ 'string', undef, 'Whether to label the feature. A value of 1 will label the feature with', 'the value returned by its display_name() method. Any other true value', 'will label the feature with the provided value. Undef will suppress labeling', 'entirely.'], label_position => [ [qw(top left alignment_left)], 'top', 'Where to place the feature label.', '"top" will place the label above the glyph aligned with its left side.', '"left" will place the label to the left of the glyph, vertically centered with it.', '"alignment_left" will place the label to the left of the glyph in the panel pad-left positon.', 'The last option is used internally for drawing DNA alignments which span the screen.' ], part_labels => [ 'boolean', undef, 'If false, do not label subparts of the feature. If equal to a value of 1, subparts', 'are labeled with their display_name(). Any other true value, will be used as the subpart label.', 'A false value suppresses the printing of subpart labels.'], arrowhead => [ [qw(regular filled)], 'regular', 'Set the style of arrowhead used when drawing a stranded feature.', '"regular" will generate a thin arrowhead that protrudes from the feature.', '"filled" will taper the feature itself to turn it into an arrowhead.'], description => [ 'string', undef, 'Whether to place a description underneath the feature. ', 'A value of 1 will describe the feature using the values returned', 'by its source_tag() method. Any other true value', 'will label the feature with the provided value. Undef will suppress labeling', 'entirely.'], draw_translation => [ 'boolean', undef, 'Draw the protein translation of the feature (assumes that the feature is attached to a DNA sequence.' ], draw_dna => [ 'boolean', undef, 'If true, draw the dna residues when magnification level', 'allows (assumes that the feature is attached to a DNA sequence.'], pad_top => [ 'integer', 0, 'Additional whitespace (in pixels) to add to the top of this glyph.'], pad_bottom => [ 'integer', 0, 'Additional whitespace (in pixels) to add to the bottom of this glyph.'], pad_right=> [ 'integer', 0, 'Additional whitespace (in pixels) to add to the right of this glyph.'], pad_left=> [ 'integer', 0, 'Additional whitespace (in pixels) to add to the left of this glyph.'], labelcolor => [ 'color', 'black', 'The color to use for drawing label text in this glyph (also known as fontcolor).'], fontcolor => [ 'color', 'black', 'The color to use for drawing label text in this glyph (also known as labelcolor).'], font2color => [ 'color', 'black', 'The color to use for drawing description text in this glyph (also known as descriptioncolor.'], descriptioncolor => [ 'color', 'black', 'The color to use for drawing description text in this glyph (also known as font2color.'], basecolor => [ 'color', 'black', 'The color to use for drawing DNA/protein residues at the base level', ], font => [ 'font', 'gdSmallFont', 'Font for glyph label and description.'], connector=>[ [qw(hat solid quill dashed crossed undef)], undef, 'Type of line to use for connecting discontinuous pieces of the feature.', 'Leave this undef to draw no connector at all.'], connector_color => [ 'color', 'black', 'Color to use for lines connecting discontinuous pieces of the feature.'], record_label_positions => [ 'integer', undef, 'If true, remember the coordinates of the glyph label and return it', 'by calling $panel->key_boxes. If -1, then remember coordinates, but', "don't actually draw the label", ] } } sub connector { return shift->option('connector',@_); } sub connector_color { my $self = shift; $self->color('connector_color') || $self->fgcolor; } sub mono_font { return GD->gdSmallFont; } sub font { my $self = shift; return $self->getfont('font','gdSmallFont'); } sub fontcolor { my $self = shift; my $fontcolor = $self->color('labelcolor') || $self->color('fontcolor'); return defined $fontcolor ? $fontcolor : $self->fgcolor; } sub font2color { my $self = shift; my $font2color = $self->color('descriptioncolor') || $self->color('font2color'); return defined $font2color ? $font2color : $self->fgcolor; } sub basecolor { my $self = shift; my $basecolor = $self->color('basecolor'); return defined $basecolor ? $basecolor : $self->fgcolor; } sub labelcolor {shift->fontcolor} sub descriptioncolor {shift->font2color} sub record_label_positions { shift->option('record_label_positions') } sub height { my $self = shift; my $h = $self->SUPER::height; return $h unless $self->option('draw_translation') && $self->protein_fits or $self->option('draw_dna') && $self->dna_fits; my $fh = $self->font_height + 2; return $h > $fh ? $h : $fh; } sub pad_top { my $self = shift; my $top = $self->option('pad_top'); return $top if defined $top; my $pad = $self->SUPER::pad_top; $pad += $self->labelheight if $self->label && $self->label_position eq 'top'; $pad; } sub pad_bottom { my $self = shift; my $bottom = $self->option('pad_bottom'); return $bottom if defined $bottom; my $pad = $self->SUPER::pad_bottom; $pad += $self->labelheight+6 if $self->description; $pad += $self->labelheight+6 if $self->part_labels && $self->label_position eq 'top'; $pad; } sub pad_right { my $self = shift; my $pad = $self->SUPER::pad_right; return $pad unless $self->label; my $label_width = $self->label_position eq 'top' ? $self->labelwidth : 0; my $description_width = $self->descriptionwidth; my $max = $label_width > $description_width ? $label_width : $description_width; my $right = $max - $self->width; return $pad > $right ? $pad : $right; } sub pad_left { my $self = shift; my $pad = $self->SUPER::pad_left; return $pad unless $self->label_position eq 'left' && $self->label; $pad += $self->labelwidth + 3; $pad; } sub labelfont { my $self = shift; return $self->getfont('label_font',$self->font); } sub descfont { my $self = shift; return $self->getfont('desc_font',$self->font); } sub labelwidth { my $self = shift; return $self->{labelwidth} ||= $self->string_width($self->label||'',$self->labelfont); } sub descriptionwidth { my $self = shift; return $self->{descriptionwidth} ||= $self->string_width($self->description||'',$self->descfont); } sub labelheight { my $self = shift; return $self->{labelheight} ||= $self->string_height($self->labelfont); } sub label_position { my $self = shift; return $self->{labelposition} ||= $self->option('label_position') || 'top'; } sub label { my $self = shift; return if $self->{overbumped}; # set by the bumper when we have hit bump limit return unless $self->subpart_callbacks; # returns true if this is level 0 or if subpart callbacks allowed return $self->_label if $self->{level} >= 0; return exists $self->{label} ? $self->{label} : ($self->{label} = $self->_label); } sub description { my $self = shift; return if $self->{overbumped}; # set by the bumper when we have hit bump limit return unless $self->subpart_callbacks; # returns true if this is level 0 or if subpart callbacks allowed return $self->_description if $self->{level} > 0; return exists $self->{description} ? $self->{description} : ($self->{description} = $self->_description); } sub part_labels { my $self = shift; my @parts = $self->parts; return ($self->{level} == 0) && @parts && @parts>1 && $self->option('part_labels'); } sub part_label_merge { shift->option('part_label_merge'); } sub maxdepth { my $self = shift; my $maxdepth = $self->option('maxdepth'); return $maxdepth if defined $maxdepth; return 1; } sub _label { my $self = shift; # allow caller to specify the label my $label = $self->option('label'); return unless defined $label; return "1" if $label eq '1 '; # 1 with a space return $label unless $label eq '1'; # figure it out ourselves my $f = $self->feature; if ($f->can('display_name') && (my $name = $f->display_name)) { return $name; } if ($f->can('attributes') && (my @aliases = $f->attributes('Alias'))) { return $aliases[0]; } return $f->info if $f->can('info'); # deprecated API return $f->seq_id if $f->can('seq_id'); return eval{$f->primary_tag}; } sub _description { my $self = shift; # allow caller to specify the long label my $label = $self->option('description'); return unless defined $label; return "1" if $label eq '1 '; return $label unless $label eq '1'; return $self->{_description} if exists $self->{_description}; return $self->{_description} = $self->get_description($self->feature); } sub get_description { my $self = shift; my $feature = shift; local $^W = 0; # common places where we can get descriptions return join '; ',$feature->notes if $feature->can('notes'); return $feature->desc if $feature->can('desc'); if ($feature->can('has_tag')) { return join '; ',$feature->get_tag_values('note') if $feature->has_tag('note'); return join '; ',$feature->get_tag_values('Note') if $feature->has_tag('Note'); return join '; ',$feature->get_tag_values('description') if $feature->has_tag('description'); } my $tag = $feature->source_tag; return if $tag eq ''; $tag; } sub draw { my $self = shift; my ($gd,$left,$top,$partno,$total_parts) = @_; local($self->{partno},$self->{total_parts}); @{$self}{qw(partno total_parts)} = ($partno,$total_parts); $self->calculate_cds() if $self->option('draw_translation') && $self->protein_fits; $self->panel->startGroup($gd); $self->SUPER::draw(@_); $self->draw_label(@_) if $self->option('label'); $self->draw_description(@_) if $self->option('description'); $self->draw_part_labels(@_) if $self->option('label') && $self->option('part_labels'); $self->panel->endGroup($gd); } sub draw_component { my $self = shift; $self->SUPER::draw_component(@_); $self->draw_translation(@_) if $self->{cds_translation}; # created earlier by calculate_cds() $self->draw_sequence(@_) if $self->option('draw_dna') && $self->dna_fits; } # mostly stolen from cds.pm -- draw the protein translation sub draw_translation { my $self = shift; my $gd = shift; my ($x1,$y1,$x2,$y2) = $self->bounds(@_); my $feature = $self->feature; my $strand = $feature->strand; my $font = $self->mono_font; my $pixels_per_residue = $self->scale * 3; my $y = $y1 + ($self->height - $font->height)/2; my $fontwidth = $font->width; my $color = $self->basecolor; $strand *= -1 if $self->{flip}; # have to remap feature start and end into pixel coords in order to: # 1) correctly align the amino acids with the nucleotide seq # 2) correct for the phase offset my $start = $self->map_no_trunc($feature->start + $self->{cds_offset}); my $stop = $self->map_no_trunc($feature->end + $self->{cds_offset}); ($start,$stop) = ($stop,$start) if $stop < $start; # why does this keep happening? my $x_fudge = $self->{flip} ? -1 : 2; my $right = $self->panel->right; my $left = $self->panel->left; my @residues = split '',$self->{cds_translation}; # warn "residues = @residues, start=$start, stop=$stop, strand=$strand, x1=$x1, x2=$x2, cds_offset = $self->{cds_offset}"; push @residues,$self->{cds_splice_residue_tail} if $self->{cds_splice_residue_tail}; for (my $i=0;$i<@residues;$i++) { my $x = $strand > 0 ? $start + $i * $pixels_per_residue : $stop - $i * $pixels_per_residue; $x -= $fontwidth + 1 if $self->{flip}; # align right when flipped my $pos = $x+$x_fudge; $gd->char($font,$pos,$y,$residues[$i],$color) if $pos >= $x1 && $pos <= $x2; } if ($self->{cds_splice_residue_head}) { $gd->char($font,$x1+2,$y,$self->{cds_splice_residue_head},$color) if $strand > 0 && $start >= $left; $gd->char($font,$x2-$fontwidth-2,$y,$self->{cds_splice_residue_head},$color) if $strand < 0 && $stop <= $right; } } sub draw_sequence { my $self = shift; my $gd = shift; my ($x1,$y1,$x2,$y2) = $self->bounds(@_); my $feature = $self->feature; my $strand = $feature->strand; my $font = $self->mono_font; my $pixels_per_base = $self->scale; my $y = $y1 + ($self->height - $font->height)/2 - 1; my $fontwidth = $font->width; my $color = $self->basecolor; $strand *= -1 if $self->{flip}; # have to remap feature start and end into pixel coords in order to: my $start = $self->map_no_trunc($feature->start); my $stop = $self->map_no_trunc($feature->end); ($start,$stop) = ($stop,$start) if $stop < $start; # why does this keep happening? my $x_fudge = $self->{flip} ? 1 : 2; my $right = $self->panel->right; my $left = $self->panel->left; my $seq = $self->get_dna($self->feature); my $canonical = $self->option('canonical_strand'); my @bases = split '',$seq; for (my $i=0;$i<@bases;$i++) { my $x = $strand >= 0 ? $start + $i * $pixels_per_base : $stop - $i * $pixels_per_base; next unless ($x >= $x1 && $x <= $x2); $x -= $fontwidth + 1 if $self->{flip}; # align right when flipped if ($strand >= 0) { last if $x + $fontwidth > $right; } else { next if $x >= $right; last if $x < $left; } my $base = $self->{flip} ? $complement{$bases[$i]} : $bases[$i]; $base = $complement{$base} if $canonical && $strand < 0; $gd->char($font,$x+$x_fudge,$y,$base,$color); } } sub min { $_[0] <= $_[1] ? $_[0] : $_[1] } sub max { $_[0] >= $_[1] ? $_[0] : $_[1] } sub draw_label { my $self = shift; my ($gd,$left,$top,$partno,$total_parts) = @_; my $label = $self->label or return; local $self->{default_opacity} = 1; my $x = $self->left + $left; # valid for both "top" and "left" because the left-hand side is defined by pad_left my $font = $self->labelfont; if ($self->label_position eq 'top') { $x += $self->pad_left; # offset to beginning of the drawn part of the feature $x = $self->panel->left + 1 if $x <= $self->panel->left; $self->render_label($gd, $font, $x, $self->top + $top - 1, $label); } elsif ($self->label_position eq 'left') { my $y = $self->{top} + ($self->height - $self->string_height($font))/2 + $top; $y = $self->{top} + $top if $y < $self->{top} + $top; $self->render_label($gd, $font, $x, $y, $label); # used for alignments, doesn't account for padding, viewer discretion is advised... } elsif ($self->label_position eq 'alignment_left') { my $y = $self->{top} + ($self->height - $font->height)/2 + $top; $self->render_label($gd, $font, 1, $y, $label); } } sub render_label { my $self = shift; my ($gd,$font,$x,$y,$label,$is_legend) = @_; my $rlp = $self->record_label_positions; unless ($rlp || $is_legend) { $gd->string($font,$x,$y,$label,$self->labelcolor); } $self->panel->add_key_box($self,$label,$x,$y) if $rlp } sub draw_description { my $self = shift; my ($gd,$dx,$dy,$partno,$total_parts) = @_; my $label = $self->description or return; local $self->{default_opacity} = 1; my ($left,$top,$right,$bottom) = $self->bounds($dx,$dy); $bottom += $self->pad_bottom; $bottom -= $self->labelheight; $bottom -= $self->labelheight if $self->part_labels && $self->label_position eq 'top'; $gd->string($self->descfont, $left, $bottom-3, $label, $self->descriptioncolor); } sub draw_part_labels { my $self = shift; my ($gd,$left,$top,$partno,$total_parts) = @_; return unless $self->{level} == 0; my @p = $self->parts or return; local $self->{default_opacity} = 1; @p > 1 or return; @p = reverse @p if $self->flip; my $font = $self->font; my $width = $font->width; my $color = $self->labelcolor; my $y = $top + $self->bottom - $self->pad_bottom; my $merge_em = $self->part_label_merge; my @parts; my $previous; if ($merge_em) { my $current_contig = []; for my $part (@p) { if (!$previous || $part->feature->start - $previous->feature->end <= 1) { push @$current_contig,$part; } else { push @parts,$current_contig; $current_contig = [$part]; } $previous = $part; } push @parts,$current_contig; } else { @parts = map {[$_]} @p; } my $last_x; # avoid overlapping labels for (my $i=0; $i<@parts; $i++) { my $x1 = $parts[$i][0]->left; my $x2 = $parts[$i][-1]->right; my $string = $self->part_label($i,scalar @parts); my $x = $left + $x1 + ($x2 - $x1 - $width*length($string))/2; my $w = $width * length($string); next if defined $last_x && $self->flip ? $x + $w > $last_x : $x < $last_x; $gd->string($font, $x,$y, $string, $color); $last_x = $x + ($self->flip ? 0 : $w); } } sub part_label { my $self = shift; my ($part,$total) = @_; local $self->{partno} = $self->feature->strand < 0 ? $total - $part -1 : $part; my $label = $self->option('part_labels'); return unless defined $label; return "1" if $label eq '1 '; return $label unless $label eq '1'; return $self->{partno}+1; } sub dna_fits { my $self = shift; my $pixels_per_base = $self->scale; my $font = $self->mono_font; my $font_width = $font->width; return $pixels_per_base >= $font_width; } sub protein_fits { my $self = shift; my $font = $self->mono_font; my $font_width = $font->width; my $pixels_per_residue = $self->scale * 3; return $pixels_per_residue >= $font_width; } sub arrowhead { my $self = shift; my $image = shift; my ($x,$y,$height,$orientation) = @_; my $fg = $self->set_pen; my $style = $self->option('arrowstyle') || 'regular'; if ($style eq 'filled') { my $poly_pkg = $self->polygon_package; my $poly = $poly_pkg->new(); if ($orientation >= 0) { $poly->addPt($x-$height,$y-$height); $poly->addPt($x,$y); $poly->addPt($x-$height,$y+$height,$y); } else { $poly->addPt($x+$height,$y-$height); $poly->addPt($x,$y); $poly->addPt($x+$height,$y+$height,$y); } $image->filledPolygon($poly,$fg); } else { if ($orientation >= 0) { $image->line($x,$y,$x-$height,$y-$height,$fg); $image->line($x,$y,$x-$height,$y+$height,$fg); } else { $image->line($x,$y,$x+$height,$y-$height,$fg); $image->line($x,$y,$x+$height,$y+$height,$fg); } } } sub arrow { my $self = shift; my $image = shift; my ($x1,$x2,$y) = @_; my $fg = $self->set_pen; my $height = $self->height/4; $height = 3 if $height < 3; $image->line($x1,$y,$x2,$y,$fg); $self->arrowhead($image,$x2,$y,$height,+1) if $x1 < $x2; $self->arrowhead($image,$x2,$y,$height,-1) if $x2 < $x1; } sub reversec { my $self = shift; my $dna = shift; $dna =~ tr/gatcGATC/ctagCTAG/; $dna = reverse $dna; return $dna; } # This gets invoked if the user has requested that the protein translation # gets drawn using the draw_translation option and protein_fits() returns # true. It is a rather specialized function and possibly belongs somewhere else, # but putting it here makes it possible for any feature to display its protein # translation. sub calculate_cds { my $self = shift; return if exists $self->{cds_translation}; my $f = $self->feature; my @subfeats = $self->find_subfeats_with_phase($f) or return; my @parts = $self->feature_has_subparts ? $self->parts : $self; my @parts_with_phase = grep {defined eval {$_->feature->phase}} @parts; my %parts = map {$_->feature->start => $_} @parts_with_phase; my $codon_table = $self->option('codontable'); $codon_table = 1 unless defined $codon_table; require Bio::Tools::CodonTable unless Bio::Tools::CodonTable->can('new'); my $translate_table = Bio::Tools::CodonTable->new(-id=>$codon_table); my $strand = $f->strand; $strand *= -1 if $self->{flip}; my $panel_start = $self->panel->start; my $panel_end = $self->panel->end; for (my $i=0; $i < @subfeats; $i++) { my $feature = $subfeats[$i]; my $prior = $subfeats[$i-1] if $i>0; my $next = $subfeats[$i+1] if $i<$#subfeats; ($prior,$next) = ($next,$prior) if $f->strand < 0; my $part = $parts{$feature->start} or next; my $pos = $feature->strand >= 0 ? $feature->start : $feature->end; my $phase = eval {$feature->phase}; next unless defined $phase; my $seq = $self->get_seq($feature); next unless defined $seq; my ($frame,$offset) = frame_and_offset($pos, $feature->strand, $phase); $part->{cds_frame} = $frame; $part->{cds_offset} = $offset; # do in silico splicing in order to find the codon that # arises from the splice my $protein = $seq->translate(undef,undef,$phase,$codon_table)->seq; $part->{cds_translation} = $protein; # warn "protein = $protein"; if ($phase == 2 && $prior) { # get 1 bp from end of previous my $dna = $self->get_dna($feature); my $prior_dna = $self->get_dna($prior); my $spliced_codon = substr($prior_dna,-1,1); $spliced_codon .= substr($dna,0,2); $part->{cds_splice_residue_head} = $translate_table->translate($spliced_codon); # warn "codon = $spliced_codon, splice_residue_head = $part->{cds_splice_residue_head}"; } if ($next && $next->phase == 1) { my $dna = $self->get_dna($feature); my $next_dna = $self->get_dna($next) if $next; my $spliced_codon = substr($dna,-2,2); $spliced_codon .= substr($next_dna,0,1); $part->{cds_splice_residue_tail} = $translate_table->translate($spliced_codon); # warn "codon = $spliced_codon, splice_residue_tail = $part->{cds_splice_residue_tail}"; } } return; } sub find_subfeats_with_phase { my $self = shift; my $feat = shift; return $feat if $feat->can('phase') && defined $feat->phase; return grep {$_->can('phase') && defined $_->phase} $feat->get_SeqFeatures; } # hack around changed feature API sub get_seq { my $self = shift; my $feature = shift; my $dna = $self->get_dna($feature); return Bio::PrimarySeq->new(-seq=>$dna); } sub get_dna { my $self = shift; # could be a PrimarySeq, or some kind of feature my $thing = shift or return; my $key = join ':', map { eval{ $thing->$_->() } || '' } qw( seq_id start end strand ); my $panel = $self->panel; if (exists $panel->{_seqcache}{$key}) { return $panel->{_seqcache}{$key}; } else { my $obj = $thing->seq; $obj = $obj->seq if ref $obj; return $panel->{_seqcache}{$key} = $obj; } } 1; =head1 NAME Bio::Graphics::Glyph::generic - The "generic" glyph =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This is identical to the "box" glyph except that it will draw the subparts of features that contain subfeatures. The subparts are not connected -- use the "segments" glyph for that. "Generic" is the default glyph used when not otherwise specified. =head2 METHODS This module overrides the maxdepth() method to return 0 unless the -maxdepth option is provided explicitly. This means that any module that inherits from generic will need to override maxdepth() again in order to draw subfeatures. In general, those implementing multi-segmented feature glyphs should inherit from Bio::Graphics::Glyph::segments, which allows for one level of descent. In addition, the following new methods are implemented: =over 4 =item labelfont(), descfont(), labelwidth(), descriptionwidth() Return the font, width for the label or description. =item label() Return the glyph label text (printed above the glyph). =item description() Return the glyph description text (printed below the glyph). =item draw_translation() Draw the protein translation of the feature (assumes that the feature is attached to a DNA sequence). =item draw_sequence() Draw the sequence of the feature (either DNA or protein). =back =head2 OPTIONS The following options are standard among all Glyphs. See L for a full explanation. Option Description Default ------ ----------- ------- -fgcolor Foreground color black -bgcolor Background color turquoise -fillcolor Synonym for -bgcolor -linewidth Line width 1 -height Height of glyph 10 -font Default font gdSmallFont -label_font Font used for label gdSmallFont -desc_font Font used for description gdSmallFont -connector Connector type 0 (false) -connector_color Connector color black -pad_top Top padding 0 -pad_bottom Bottom padding 0 -label Whether to draw a label 0 (false) -label_position Where to draw the label "top" (default) or "left" -description Whether to draw a description 0 (false) -strand_arrow Whether to indicate 0 (false) strandedness -hilite Highlight color undef (no color) -draw_dna If true, draw the dna residues 0 (false) when magnification level allows. -canonical_strand If true, draw the dna residues 0 (false) as they appear on the plus strand even if the feature is on the minus strand. -pad_top and -pad_bottom allow you to insert some blank space between the glyph's boundary and its contents. This is useful if you are changing the glyph's height dynamically based on its feature's score. =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Allen Day Eday@cshl.orgE, Lincoln Stein Elincoln.stein@gmail.comE Copyright (c) 2001 Cold Spring Harbor Laboratory Copyright (c) 2010 Ontario Institute for Cancer Research This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/crossbox.pm������������������������������������������������000555��001750��001750�� 5422�12165075746� 22355� 0����������������������������������������������������������������������������������������������������ustar�00lstein��������������������������lstein��������������������������000000��000000�������������������������������������������������������������������������������������������������������������������������������������������������������������������������package Bio::Graphics::Glyph::crossbox; use strict; use base qw(Bio::Graphics::Glyph::generic); sub my_description { "This is a box with an 'X' inside the glyph." } # override draw_component to draw a crossed box rather than empty sub draw_component { my $self = shift; my $gd = shift; my $fg = $self->fgcolor; my ($left,$top) = @_; my($x1,$y1,$x2,$y2) = $self->bounds(@_); $self->filled_box($gd, $x1, $y1, $x2, $y2); $gd->line($x1,$y1,$x2,$y2,$fg); $gd->line($x1,$y2,$x2,$y1,$fg); } 1; __END__ =head1 NAME Bio::Graphics::Glyph::crossbox - The "crossbox" glyph =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This is a box with an 'X' inside the glyph. =head2 OPTIONS The following options are standard among all Glyphs. See L for a full explanation. Option Description Default ------ ----------- ------- -fgcolor Foreground color black -outlinecolor Synonym for -fgcolor -bgcolor Background color turquoise -fillcolor Synonym for -bgcolor -linewidth Line width 1 -height Height of glyph 10 -font Glyph font gdSmallFont -connector Connector type 0 (false) -connector_color Connector color black -label Whether to draw a label 0 (false) -description Whether to draw a description 0 (false) -hilite Highlight color undef (no color) =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Allen Day Eday@cshl.orgE. Copyright (c) 2001 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/anchored_arrow.pm������������������������������������������000555��001750��001750�� 14707�12165075746� 23536� 0����������������������������������������������������������������������������������������������������ustar�00lstein��������������������������lstein��������������������������000000��000000�������������������������������������������������������������������������������������������������������������������������������������������������������������������������package Bio::Graphics::Glyph::anchored_arrow; # package to use for drawing an arrow use strict; use base qw(Bio::Graphics::Glyph::arrow); sub my_description { return < [ [0..2], 0, 'Draw a scale with tickmarks on the arrow.', 'A value of 0 suppresses the scale.', 'A value of 1 draws major ticks only.', 'A value of 2 draws major and minor ticks.',], relative_coords=> [ 'boolean', undef, 'When drawing the scale, start numbering at position 1 instead of at', 'the start of the feature in global (e.g. chromosomal) coordinates.'], relative_coords_offset=> [ 'integer', 1, 'When drawing a scale with relative_coords set to true, begin numbering', 'the scale at this starting value.'], no_arrows => [ 'boolean', undef, "Do not draw an arrow when the glyph is partially offscreen."], }; } sub draw_label { my $self = shift; my ($gd,$left,$top,$partno,$total_parts) = @_; my $label = $self->label or return; my $label_align = $self->option('label_align'); if ($label_align && ($label_align eq 'center' || $label_align eq 'right')) { my $x = $self->left + $left; my $font = $self->option('labelfont') || $self->font; my $middle = $self->left + $left + ($self->right - $self->left) / 2; my $label_width = $self->string_width($label,$font); if ($label_align eq 'center') { my $new_x = $middle - $label_width / 2; $x = $new_x if ($new_x > $x);; } else { my $new_x = $left + $self->right - $label_width; $x = $new_x if ($new_x > $x); } $x = $self->panel->left + 1 if $x <= $self->panel->left; #detect collision (most likely no bump when want centering label) #lay down all features on one line e.g. cyto bands return if (!$self->option('bump') && ($label_width + $x) > $self->right); $gd->string($font, $x, $self->top + $top, $label, $self->fontcolor); } else { $self->SUPER::draw_label(@_); } } sub arrowheads { my $self = shift; my ($ne,$sw,$base_e,$base_w); my $feature = $self->feature; my $gstart = $feature->start; my $gend = $feature->end; my $pstart = $self->panel->start; my $pend = $self->panel->end; if (!defined $gstart || $gstart <= $pstart) { # off left end $sw = 1; } if (!defined $gend || $gend >= $pend) { # off right end $ne = 1; } ($sw,$ne) = ($ne,$sw) if $self->panel->{flip}; return ($sw,$ne,!$sw,!$ne); } sub no_trunc { !shift->option('no_arrows'); } 1; __END__ =head1 NAME Bio::Graphics::Glyph::anchored_arrow - The "anchored_arrow" glyph =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This glyph draws an arrowhead which is anchored at one or both ends (has a vertical base) or has one or more arrowheads. The arrowheads indicate that the feature does not end at the edge of the picture, but continues. For example: |-----------------------------| both ends in picture <----------------------| left end off picture |----------------------------> right end off picture <------------------------------------> both ends off picture You can also set the glyph so that the end is just truncated at the end of the picture. |----------------------------- =head2 OPTIONS The following options are standard among all Glyphs. See L for a full explanation. Option Description Default ------ ----------- ------- -fgcolor Foreground color black -outlinecolor Synonym for -fgcolor -bgcolor Background color turquoise -fillcolor Synonym for -bgcolor -linewidth Line width 1 -height Height of glyph 10 -font Glyph font gdSmallFont -connector Connector type 0 (false) -connector_color Connector color black -label Whether to draw a label 0 (false) -description Whether to draw a description 0 (false) -hilite Highlight color undef (no color) In addition to the standard options, this glyph recognizes the following: Option Description Default -tick draw a scale 0 (false) -relative_coords use relative coordinates 0 (false) for scale -relative_coords_offset set the relative offset 1 for scale -no_arrows don't draw an arrow when 0 (false) glyph is partly offscreen The argument for B<-tick> is an integer between 0 and 2 and has the same interpretation as the B<-tick> option in Bio::Graphics::Glyph::arrow. If B<-rel_coords> is set to a true value, then the scale drawn on the glyph will be in relative (1-based) coordinates relative to the beginning of the glyph. =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Allen Day Eday@cshl.orgE. Copyright (c) 2001 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut ���������������������������������������������������������Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/redgreen_box.pm��������������������������������������������000555��001750��001750�� 11726�12165075746� 23202� 0����������������������������������������������������������������������������������������������������ustar�00lstein��������������������������lstein��������������������������000000��000000�������������������������������������������������������������������������������������������������������������������������������������������������������������������������package Bio::Graphics::Glyph::redgreen_box; use strict; use base qw(Bio::Graphics::Glyph::generic); sub bgcolor { my $self = shift; $self->{force_bgcolor}; } sub fgcolor { my $self = shift; return $self->option('border') ? $self->SUPER::fgcolor : $self->{force_bgcolor}; } sub color_subparts { shift->option('color_subparts') } sub bump { my $self = shift; return 0 if $self->color_subparts; return $self->SUPER::bump; } sub draw { my $self = shift; my $val = $self->feature->score; # we're going to force all our parts to share the same colors # unless otherwise requested my @parts = $self->parts; @parts = $self if !@parts && $self->level == 0; unless ($self->color_subparts) { my @rgb = map {int($_)} HSVtoRGB(120*(1-$val),1,255); my $color = $self->panel->translate_color(@rgb); $_->{force_bgcolor} = $color foreach @parts; } else { foreach (@parts) { my $val = $_->feature->score; my @rgb = map {int($_)} HSVtoRGB(120*(1-$val),1,255); my $color = $self->panel->translate_color(@rgb); $_->{force_bgcolor} = $color; } } $self->SUPER::draw(@_); } sub HSVtoRGB ($$$) { my ($h,$s,$v)=@_; my ($r,$g,$b,$i,$f,$p,$q,$t); if( $s == 0 ) { ## achromatic (grey) return ($v,$v,$v); } $h /= 60; ## sector 0 to 5 $i = int($h); $f = $h - $i; ## factorial part of h $p = $v * ( 1 - $s ); $q = $v * ( 1 - $s * $f ); $t = $v * ( 1 - $s * ( 1 - $f ) ); if($i<1) { $r = $v; $g = $t; $b = $p; } elsif($i<2){ $r = $q; $g = $v; $b = $p; } elsif($i<3){ $r = $p; $g = $v; $b = $t; } elsif($i<4){ $r = $p; $g = $q; $b = $v; } elsif($i<5){ $r = $t; $g = $p; $b = $v; } else { $r = $v; $g = $p; $b = $q; } return ($r,$g,$b); } sub mMin { my $n=10000000000000; map { $n=($n>$_) ? $_ : $n } @_; return($n); } sub mMax { my $n=0; map { $n=($n<$_) ? $_ : $n } @_; return($n); } 1; =head1 NAME Bio::Graphics::Glyph::redgreen_box - The "redgreen_box" glyph =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This glyph is similar to the graded_segments glyph except that it generates a green-Ered gradient suitable for use with microarray data. A feature score of 0 is full green; a feature score of 1.0 is full red; intermediate scores are shades of yellow. =head2 OPTIONS The following options are standard among all Glyphs. See L for a full explanation. Option Description Default ------ ----------- ------- -fgcolor Foreground color black -outlinecolor Synonym for -fgcolor -bgcolor Background color turquoise -fillcolor Synonym for -bgcolor -linewidth Line width 1 -height Height of glyph 10 -font Glyph font gdSmallFont -connector Connector type 0 (false) -connector_color Connector color black -label Whether to draw a label 0 (false) -description Whether to draw a description 0 (false) -hilite Highlight color undef (no color) The following glyph-specific option is recognized: -border Draw a fgcolor border around 0 (false) the box -color_subparts Give each subpart a separate 0 (false) color based on its score If the B<-color_subparts> option is true, then the glyph will individually coloriz each of its subparts. In addition, internal bumping of features will be turned off. This will produce an effect similar to graded_segments. =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Lincoln Stein Elstein@cshl.orgE Copyright (c) 2001 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut ������������������������������������������Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/broken_line.pm���������������������������������������������000555��001750��001750�� 14635�12165075746� 23030� 0����������������������������������������������������������������������������������������������������ustar�00lstein��������������������������lstein��������������������������000000��000000�������������������������������������������������������������������������������������������������������������������������������������������������������������������������package Bio::Graphics::Glyph::broken_line; use strict; use base qw(Bio::Graphics::Glyph::generic); sub my_description { return < [ 'boolean', 1, 'If true, draw the "break."'], shear => [ 'integer', 5, 'Vertical distance between the main line and the segment.'], shear_up => [ 'boolean', 1, 'Whether to shift the segment up (true) or down (false).'], break => [ 'integer', 8, 'Width of the break in the line.'], extend => [ 'boolean', 1, 'Whether to extend the line or to keep the length fixed.'], size => [ 'integer', 30, 'Total length of the line and its break, if extend is false.'], omit_left => [ 'boolean', undef, 'If true, omit the left half of the main line.'], omit_right => [ 'boolean', undef, 'If true, omit the right half of the main line.'], } } sub default_draw_beak { return 1; } sub default_shear { return 5; } sub default_shear_up { return 1; } sub default_break { return 8; } sub default_extend { return 1; } sub default_size { return 30; } sub default_omit_left { return 0; } sub default_omit_right { return 0; } sub draw_component { my $self = shift; my $gd = shift; my ($x1,$y1,$x2,$y2) = $self->calculate_boundaries(@_); my $fg = $self->fgcolor; my $bg = $self->bgcolor; my $shear = defined $self->option('shear') ? $self->option('shear') : $self->default_shear(); my $shear_up = defined $self->option('shear_up') ? $self->option('shear_up') : $self->default_shear_up(); my $break = defined $self->option('break') ? $self->option('break') : $self->default_break(); my $draw_beak = defined $self->option('draw_beak') ? $self->option('draw_beak') : $self->default_draw_beak(); my $extend= defined $self->option('extend') ? $self->option('extend') : $self->default_extend(); my $size = defined $self->option('size') ? $self->option('size') : $self->default_size(); my $omit_left = defined $self->option('omit_left') ? $self->option('omit_left') : $self->default_omit_left(); my $omit_right = defined $self->option('omit_right') ? $self->option('omit_right') : $self->default_omit_right(); my $midY = ($y1+$y2)/2; if ($x2-$x1 < $size) { $gd->line($x1, $midY, $x2, $midY, $bg); return; } my $midX = ($x1+$x2)/2; my $break_start = $midX - $break/2; my $break_end = $midX + $break/2; my ($x11, $x12, $x21, $x22); $x12 = $break_start; $x21 = $break_end; if ($omit_left) { $break_start = $x1; $break_end = $x1+$break; $x21 = $break_end; $x22 = ($extend ? $x2 : $x21 + $size - $break); } elsif ($omit_right) { $x11 = $x1; $x12 = ($extend ? $x2 - $break : $x11 + $size - $break); $break_end = $x12+$break; $break_start = $x12; } else { if ($extend) { $x11 = $x1; $x22 = $x2; } else { $x11 = $break_start - ($size - $break) / 2; $x22 = $break_end + ($size - $break) / 2; } } unless ($omit_left) { $gd->line($x11, $midY, $x12, $midY, $bg); } my $shear_y = ($shear_up ? $midY - $shear : $midY + $shear); $gd->line($break_start, $shear_y, $break_end, $shear_y, $fg); if ($draw_beak) { $midX = ($break_start + $break_end) / 2; my $beak_y1 = $shear_up ? $midY + $shear/2 : $midY - $shear/2; my $beak_y2 = $shear_up ? $midY - $shear/2 : $midY + $shear/2; $gd->line($midX, $beak_y1, $midX-$shear, $beak_y2, $fg); $gd->line($midX, $beak_y1, $midX+$shear, $beak_y2, $fg); } unless ($omit_right) { $gd->line($x21, $midY, $x22, $midY, $bg); } } 1; __END__ =head1 NAME Bio::Graphics::Glyph::broken_line - The "broken line" glyph =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This glyph draws a straight line whose segment is shifted ("sheared") up or down. There can be an optional "break" (two diagonal lines passing between the main line and its segment). Either the left or the right side of the main line can be absent. The line can be of fixed size or extend to take up all available space. =head2 OPTIONS In addition to the common options, the following glyph-specific options are recognized: Option Description Default ------ ----------- ------- -draw_beak Whether to draw the 'break'. 1 -shear Vertical distance between 5 the main line and the segment -shear_up Whether to shift the segment 1 up or down (1 or 0) -break Width of the break in the line 8 -extend Whether to extend the line or 1 to keep the length fixed (1 or 0) -size Total length of the line and 30 the break, if extend is 0 -omit_left Whether to omit the left 0 half of the main line -omit_right Whether to omit the right 0 half of the main line =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Vsevolod (Simon) Ilyushchenko Esimonf@cshl.eduE. Copyright (c) 2004 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut ���������������������������������������������������������������������������������������������������Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/dot.pm�����������������������������������������������������000555��001750��001750�� 11236�12165075746� 21321� 0����������������������������������������������������������������������������������������������������ustar�00lstein��������������������������lstein��������������������������000000��000000�������������������������������������������������������������������������������������������������������������������������������������������������������������������������package Bio::Graphics::Glyph::dot; # DAS-compatible package to use for drawing a ring or filled circle use strict; use base qw(Bio::Graphics::Glyph::point_glyph); use constant PI => 3.14159; sub my_description { return < [ 'boolean', undef, 'If true, draws a fixed-radius ellipse at the center of the feature,', 'regardless of the feature\'s length.'], } } sub draw { my $self = shift; my $gd = shift; my $fg = $self->fgcolor; # now draw a circle my ($left,$top) = @_; my ($x1,$y1,$x2,$y2) = $self->calculate_boundaries(@_); my $xmid = (($x1+$x2)/2); my $width = abs($x2-$x1); my $ymid = (($y1+$y2)/2); my $height = abs($y2-$y1); # only point ovals allowed now my $r = $self->height; # Code to maintain compliancy with gd 1.8.4 # gd 1.8.4 does not support the ellipse() or filledEllipse() methods. # Let's maintain the filledEllipse approach for installations # using gd2 or for drawing images with GD::SVG # Otherwise, we will use fill as before. # The can() method fails with GD::SVG. Why? my $bg = $self->bgcolor; if ($gd->can('filledEllipse') || $gd =~ /SVG/ ) { $gd->filledEllipse($xmid,$ymid,$r,$r,$bg) if ($bg); # Draw the border (or unfilled ellipse) $gd->ellipse($xmid,$ymid,$r,$r,$fg); } else { # Let's draw a circle in the gd 1.8.4 manner $gd->arc($xmid,$ymid,$r,$r,0,360,$fg); $gd->fillToBorder($xmid,$ymid,$fg,$bg) if ($bg); } #how about a fuse for the bomb? #work in degrees, not radians. So we define PI above if(defined $self->option('stem')){ my $angle = $self->option('stem'); $gd->line($xmid+($r/PI*sin($angle*PI/180)), $ymid+($r/PI*cos($angle*PI/180)), $xmid+($r*sin($angle*PI/180)), $ymid+($r*cos($angle*PI/180)),$fg); } $self->draw_label($gd,@_) if $self->option('label'); } 1; __END__ =head1 NAME Bio::Graphics::Glyph::dot - The "dot" glyph =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This glyph draws an ellipse the width of the scaled feature passed, and height a possibly configured height (See Bio::Graphics::Glyph). =head2 OPTIONS The following options are standard among all Glyphs. See L for a full explanation. Option Description Default ------ ----------- ------- -fgcolor Foreground color black -outlinecolor Synonym for -fgcolor -bgcolor Background color turquoise -fillcolor Synonym for -bgcolor -linewidth Line width 1 -height Height of glyph 10 -font Glyph font gdSmallFont -connector Connector type 0 (false) -connector_color Connector color black -label Whether to draw a label 0 (false) -description Whether to draw a description 0 (false) -hilite Highlight color undef (no color) In addition to the common options, the following glyph-specific options are recognized: Option Description Default ------ ----------- ------- -point Whether to draw an ellipse feature width the scaled width of the feature or with radius point. =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Allen Day Eday@cshl.orgE. Copyright (c) 2001 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/stackedplot.pm���������������������������������������������000555��001750��001750�� 31074�12165075746� 23052� 0����������������������������������������������������������������������������������������������������ustar�00lstein��������������������������lstein��������������������������000000��000000�������������������������������������������������������������������������������������������������������������������������������������������������������������������������package Bio::Graphics::Glyph::stackedplot; use strict; use base 'Bio::Graphics::Glyph::fixedwidth'; use GD::Simple; use Carp 'cluck'; use Memoize; memoize('scale_width'); memoize('width_needed'); sub width_needed { my $self = shift; my $column_width = $self->column_width; my $column_spacing = $self->column_spacing; my $columns = $self->data_series; my $needed = @$columns * ($column_width + $column_spacing); return $needed; } #sub pad_right { # my $self = shift; # my $pr = $self->SUPER::pad_right; # my $sw = $self->scale_width; # return $pr+$sw; #} sub pad_right { my $self = shift; my $pr = $self->SUPER::pad_right; my $sw = $self->scale_width + $self->column_width/2; my $content_width = $self->width_needed; my $total = $sw + $content_width; my $additional = $pr - $total; return $pr if $pr > $additional; return $additional; } sub scale_width { my $self = shift; return 0 unless $self->do_draw_scale; my ($min,$max) = $self->min_max; my $middle = ($min+$max)/2; my ($longest) = sort {$b<=>$a} map {length($_)} ($min,$middle,$max); return $longest * $self->scale_font->width; } sub pad_bottom { my $self = shift; my @labels = $self->column_labels; my $bottom = $self->SUPER::pad_bottom; return $bottom unless @labels; return $bottom + $self->font('gdTinyFont')->height; } sub column_width { shift->option('column_width') || 8 } sub column_spacing { shift->option('column_spacing') || 2 } sub maxdepth { 0 } sub min_max { my $self = shift; my $min_score = $self->option('min_score') || 0.0; my $max_score = $self->option('max_score') || 1.0; return ($min_score,$max_score); } # this behaves more like the image glyph -- it draws a generic glyph, two diagonal lines, and then the # plot underneath. sub draw_contents { my $self = shift; my ($gd,$left,$top,$right,$bottom) = @_; my ($min_score,$max_score) = $self->min_max; my $height = $bottom-$top; my $scale = $max_score > $min_score ? $height/($max_score-$min_score) : 1; my $y_origin = $min_score <= 0 ? $bottom - (0 - $min_score) * $scale : $bottom; my $scale_width = $self->scale_width; $y_origin = $top if $max_score < 0; $self->draw_stackedplot($gd,$left,$right,$top,$y_origin,$scale,$min_score,$max_score); $self->draw_scale($gd,$left,$top,$right,$bottom); } sub draw_stackedplot { my $self = shift; my ($gd,$left,$right,$top,$bottom,$scale,$min,$max) = @_; my $fgcolor = $self->fgcolor; my $bgcolor = $self->bgcolor; my @colors = $self->series_colors; my @labels = $self->column_labels; my $column_width = $self->column_width; my $column_spacing = $self->column_spacing; my $font = $self->column_font; my $fwidth = $font->width; my $fontcolor = $self->fontcolor; # data_series() returns 1 or more values to stack upwards # the totals of the values must be no greater than max_score if (my $values = $self->data_series) { my $x_offset = 0; for (my $cluster = 0; $cluster < @$values; $cluster++) { # this will give us a series of data series my $series = $values->[$cluster]; my $y_offset = 0; for (my $i = 0; $i < @$series; $i++) { my $value = $series->[$i]; my $v = $self->clip($value,$min,$max); my $color = $colors[$i] || $bgcolor; my $y = $bottom - ($v-$min) * $scale; my $box_bottom = $bottom - $y_offset; my $box_top = $y - $y_offset; $self->filled_box($gd,$left+$x_offset,$box_top,$left+$column_width+$x_offset,$box_bottom,$color); $y_offset += $box_bottom-$box_top; } if (@labels) { my $x = $left+$x_offset+($column_width-$fwidth*$labels[$cluster])/2-1; $gd->string($font,$x,$bottom,$labels[$cluster],$fontcolor); } $x_offset += $column_spacing+$column_width; } } } sub clip { my $self = shift; my ($value,$min,$max) = @_; $value = $min if defined $min && $value < $min; $value = $max if defined $max && $value > $max; return $value; } sub series_colors { my $self = shift; my $values = $self->option('series_colors'); my @colors; if ($values && !ref $values) { @colors = split /\s+/,$values; } elsif (ref $values eq 'ARRAY') { @colors = @$values; } else { @colors = qw(red blue green orange brown grey black); } return map {$self->translate_color($_)} @colors; } sub column_labels { my $self = shift; my $values = $self->option('column_labels'); my @labels; if ($values && !ref $values) { @labels = split /\s+/,$values; } elsif (ref $values eq 'ARRAY') { @labels = @$values; } return @labels; } # NOTE! # probably data_series should return this: # [series1 => [value1,value2,value3,value4], # series2 => [value1,value2,value3,value4], # series3 => [value1,value2,value3,value4], # ... # ] sub data_series { my $self = shift; my $values = $self->option('series'); return $values if defined $values; # otherwise get it from the feature my @values; my @tagvalues = $self->feature->get_tag_values('series'); for my $v (@tagvalues) { if (ref $v && ref $v eq 'ARRAY') { # already in right format push @values,$v; } else { push @values,[split /[,\s]+/,$v]; } } return \@values; } sub do_draw_scale { my $self = shift; my $drawit = $self->option('draw_scale'); return defined $drawit ? $drawit : 1; } sub scale_font { my $self = shift; $self->getfont('scale_font','gdTinyFont'); } sub column_font { my $self = shift; $self->getfont('column_font','gdSmallFont'); } sub draw_scale { my $self = shift; my ($gd,$left,$top,$right,$bottom) = @_; my ($min,$max) = $self->min_max; $self->panel->startGroup($gd); my $simple = GD::Simple->new($gd); $simple->font($self->scale_font); my $dx = 1; my $dy = $simple->font->height/2; # these drew a vertical scale line, which didn't look very nice # $simple->moveTo($right,$bottom); # $simple->lineTo($right,$top); $simple->moveTo($right,$top); $simple->line(3); $simple->move($dx,$dy); $simple->string($max); $simple->moveTo($right,($bottom+$top)/2); $simple->line(3); $simple->move($dx,$dy); $simple->string(($max+$min)/2); $simple->moveTo($right,$bottom); $simple->line(3); $simple->move($dx,$dy); $simple->string($min); $self->panel->endGroup($gd); } 1; __END__ =head1 NAME Bio::Graphics::Glyph::stackedplot - The stackedplot glyph =head1 SYNOPSIS See L and L. =head1 DESCRIPTION The stackedplot glyph can be used to draw quantitative feature data using a stacked column plot. It differs from the xyplot glyph in that the plot applies to a single top level feature, not a group of subfeatures. The data to be graphed is derived from an attribute called "data_series." The data to be graphed is represented as a list of arrays: ( [1, 2, 8], [6, 1, 1], [10,8, 0], [1, 1, 1], ) Each array is a column in the stacked plot. Its values become the subdivisions of the column. In this example, there are four columns, each of which has three subdivisions. You can add labels to the columns and change the colors of the subdivisions. To assign data to a feature, you can add a "series" tag: $snp1 = Bio::SeqFeature::Generic ->new (-start => 500,-end=>501, -display_name =>'example', -tag=> { series => [ [10,20,30], [30,30,0], [5,45,10], [5,45,10], [5,45,10], [50,0,50], ], } ); Note that the series tag must consist of an array of arrays. If you are using a gff3 representation, you can load a database with data that looks like this: chr1 test feature 1 1000 . . . series=10 20 30;series=30 30 0;series=5 45 10... If you are using a gff2 representation, you can load a database with data that looks like this: chr1 test feature 1 1000 . . . series 10 20 30; series 30 30 0 series 5 45 10... Or you can pass a callback to the -series option: $panel->add_track(\@data, -glyph => 'stackedplot', -series => sub { my $feature = shift; return [ [10,20,30], [30,30,0], [5,45,10], ] } ); =head2 OPTIONS The following options are standard among all Glyphs. See L for a full explanation. Option Description Default ------ ----------- ------- -fgcolor Foreground color black -outlinecolor Synonym for -fgcolor -bgcolor Background color turquoise -fillcolor Synonym for -bgcolor -linewidth Line width 1 -height Height of glyph 10 -font Glyph font gdSmallFont -label Whether to draw a label 0 (false) -description Whether to draw a description 0 (false) -hilite Highlight color undef (no color) In addition, the alignment glyph recognizes all the options of the xyplot glyph, as well as the following glyph-specific option: Option Description Default ------ ----------- ------- -fixed_gap Vertical distance between 8 the rectangle that shows the start:end range of the feature and the fixed width stacked plot. -series_colors A list giving a series of red,blue,green,orange, color names for the data brown,grey,black series (the values inside each stacked column). -column_labels A list of labels to print -none- underneath each column. -column_width The width of each column. 8 -column_spacing Spacing between each 2 column. -min_score Minimum score for the 0.0 sum of the members of each data series. -max_score Maximum score for the 1.0 sum of the members of each data series. -scale_font Font to use for the scale. gdTinyFont -column_font Font to use for the column gdSmallFont labels. -draw_scale Whether to draw a scale to true right of the columns. Note that -min_score and -max_score represent the minimum and maximum SUM of all the values in the data series. For example, if your largest column contains the series (10,20,30), then the -max_score is 60. =head1 EXAMPLE To understand how this glyph works, try running and modifying the following example: #!/usr/bin/perl use strict; use warnings; use Bio::Graphics; use Bio::SeqFeature::Generic; my $segment = Bio::Graphics::Feature->new(-start=>1,-end=>700); my $snp1 = Bio::SeqFeature::Generic ->new (-start => 500,-end=>590, -display_name =>'fred', -tag=> { series => [ [10,20,30], [30,30,0], [5,45,10], [5,45,10], [5,45,10], [50,0,50], ], }, -source=>'A test', ); my $snp2 = Bio::SeqFeature::Generic->new(-start => 300, -end => 301, -display_name => 'rs12345', -tag=> { series => [ [30,20,10 ], [80,10,10 ], ], }, -source=>'Another test', ); my $panel = Bio::Graphics::Panel->new(-segment=>$segment,-width=>800); $panel->add_track($segment,-glyph=>'arrow',-double=>1,-tick=>2); $panel->add_track([$snp1,$snp2], -height => 50, -glyph => 'stackedplot', -fixed_gap => 12, -series_colors => [qw(red blue lavender)], -column_labels => [qw(a b c d e f g)], -min_score => 0, -max_score => 100, -column_width => 8, -column_font => 'gdMediumBoldFont', -scale_font => 'gdTinyFont', -label => 1, -description=>1, ); print $panel->png; =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, =head1 AUTHOR Lincoln Stein Elstein@cshl.orgE Copyright (c) 2006 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/redgreen_segment.pm����������������������������������������000555��001750��001750�� 7110�12165075746� 24024� 0����������������������������������������������������������������������������������������������������ustar�00lstein��������������������������lstein��������������������������000000��000000�������������������������������������������������������������������������������������������������������������������������������������������������������������������������package Bio::Graphics::Glyph::redgreen_segment; use strict; use base qw(Bio::Graphics::Glyph::graded_segments); sub calculate_color { my $self = shift; my $val = shift; return (0,0,0) unless $val =~ /^[\d.]+$/; return HSVtoRGB(120*(1-$val),1,255); } sub HSVtoRGB ($$$) { my ($h,$s,$v)=@_; my ($r,$g,$b,$i,$f,$p,$q,$t); if( $s == 0 ) { ## achromatic (grey) return ($v,$v,$v); } $h /= 60; ## sector 0 to 5 $i = int($h); $f = $h - $i; ## factorial part of h $p = $v * ( 1 - $s ); $q = $v * ( 1 - $s * $f ); $t = $v * ( 1 - $s * ( 1 - $f ) ); if($i<1) { $r = $v; $g = $t; $b = $p; } elsif($i<2){ $r = $q; $g = $v; $b = $p; } elsif($i<3){ $r = $p; $g = $v; $b = $t; } elsif($i<4){ $r = $p; $g = $q; $b = $v; } elsif($i<5){ $r = $t; $g = $p; $b = $v; } else { $r = $v; $g = $p; $b = $q; } return ($r,$g,$b); } sub mMin { my $n=10000000000000; map { $n=($n>$_) ? $_ : $n } @_; return($n); } sub mMax { my $n=0; map { $n=($n<$_) ? $_ : $n } @_; return($n); } 1; =head1 NAME Bio::Graphics::Glyph::redgreen_segment - The "redgreen_segments" glyph =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This glyph is similar to the graded_segments glyph except that it generates a green-Ered gradient suitable for use with microarray data. A feature score of 0 is full green; a feature score of 1.0 is full red; intermediate scores are shades of yellow. =head2 OPTIONS The following options are standard among all Glyphs. See L for a full explanation. Option Description Default ------ ----------- ------- -fgcolor Foreground color black -outlinecolor Synonym for -fgcolor -bgcolor Background color turquoise -fillcolor Synonym for -bgcolor -linewidth Line width 1 -height Height of glyph 10 -font Glyph font gdSmallFont -connector Connector type 0 (false) -connector_color Connector color black -label Whether to draw a label 0 (false) -description Whether to draw a description 0 (false) =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Lincoln Stein Elstein@cshl.orgE Copyright (c) 2001 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/wiggle_data.pm���������������������������������������������000555��001750��001750�� 31456�12165075746� 23010� 0����������������������������������������������������������������������������������������������������ustar�00lstein��������������������������lstein��������������������������000000��000000�������������������������������������������������������������������������������������������������������������������������������������������������������������������������package Bio::Graphics::Glyph::wiggle_data; use strict; use base qw(Bio::Graphics::Glyph::minmax); use File::Spec; sub minmax { my $self = shift; my $parts = shift; my $autoscale = $self->option('autoscale') || 'local'; my $min_score = $self->min_score unless $autoscale eq 'z_score'; my $max_score = $self->max_score unless $autoscale eq 'z_score'; my $do_min = !defined $min_score; my $do_max = !defined $max_score; if (@$parts && $self->feature->can('statistical_summary')) { my ($min,$max,$mean,$stdev) = eval {$self->bigwig_stats($autoscale,$self->feature)}; $min_score = $min if $do_min; $max_score = $max if $do_max; return $self->sanity_check($min_score,$max_score,$mean,$stdev); } elsif (eval {$self->wig}) { if (my ($min,$max,$mean,$stdev) = eval{$self->wig_stats($autoscale,$self->wig)}) { $min_score = $min if $do_min; $max_score = $max if $do_max; return $self->sanity_check($min_score,$max_score,$mean,$stdev); } } if ($do_min or $do_max) { my $first = $parts->[0]; for my $part (@$parts) { my $s = ref $part ? $part->[2] : $part; next unless defined $s; $min_score = $s if $do_min && (!defined $min_score or $s < $min_score); $max_score = $s if $do_max && (!defined $max_score or $s > $max_score); } } return $self->sanity_check($min_score,$max_score); } sub bigwig_stats { my $self = shift; my ($autoscale,$feature) = @_; my $s; if ($autoscale =~ /global/ or $autoscale eq 'z_score') { $s = $feature->global_stats; } elsif ($autoscale eq 'chromosome') { $s = $feature->chr_stats; } else { $s = $feature->score; } return $self->clip($autoscale, $s->{minVal},$s->{maxVal},Bio::DB::BigWig::binMean($s),Bio::DB::BigWig::binStdev($s)); } sub wig_stats { my $self = shift; my ($autoscale,$wig) = @_; if ($autoscale =~ /global|chromosome|z_score/) { my $min_score = $wig->min; my $max_score = $wig->max; my $mean = $wig->mean; my $stdev = $wig->stdev; return $self->clip($autoscale,$min_score,$max_score,$mean,$stdev); } else { return; } } sub clip { my $self = shift; my ($autoscale,$min,$max,$mean,$stdev) = @_; return ($min,$max,$mean,$stdev) unless $autoscale =~ /clipped/; my $fold = $self->z_score_bound; my $clip_max = $mean + $stdev*$fold; my $clip_min = $mean - $stdev*$fold; $min = $clip_min if $min < $clip_min; $max = $clip_max if $max > $clip_max; return ($min,$max,$mean,$stdev); } sub z_score_bound { my $self = shift; return $self->option('z_score_bound') || 4; } # change the scaling of the data points if z-score autoscaling requested sub rescale { my $self = shift; my $points = shift; return $points unless $self->option('autoscale') eq 'z_score'; my ($min,$max,$mean,$stdev) = $self->minmax($points); foreach (@$points) { $_ = ($_ - $mean) / $stdev; } return $points; } sub global_mean_and_variance { my $self = shift; if (my $wig = $self->wig) { return ($wig->mean,$wig->stdev); } elsif ($self->feature->can('global_mean')) { my $f = $self->feature; return ($f->global_mean,$f->global_stdev); } return; } sub global_min_max { my $self = shift; if (my $wig = $self->wig) { return ($wig->min,$wig->max); } elsif (my $stats = eval {$self->feature->global_stats}) { return ($stats->{minVal},$stats->{maxVal}); } return; } sub series_stdev { my $self = shift; my ($mean,$stdev) = $self->global_mean_and_variance; return $stdev; } sub series_mean { my $self = shift; my ($mean) = $self->global_mean_and_variance; return $mean; } sub series_min { my $self = shift; return ($self->global_min_max)[0]; } sub series_max { my $self = shift; return ($self->global_min_max)[1]; } sub wig { my $self = shift; my $d = $self->{wig}; $self->{wig} = shift if @_; $d; } sub datatype { my $self = shift; my $feature = $self->feature; warn $feature->display_name; my ($tag,$value); for my $t ('wigfile','wigdata','densefile','coverage') { if (my ($v) = eval{$feature->get_tag_values($t)}) { $value = $v; $tag = $t; last; } } unless ($value) { $tag = 'statistical_summary'; $value = eval{$feature->statistical_summary}; $value or warn "track data object '",ref($feature),"' does not support statistical_summary() method; please add wigfile,wigdata,densefile or coverage attribute to data file"; } $tag ||= 'generic'; return wantarray ? ($tag,$value) : $tag; } sub get_parts { my $self = shift; my $feature = $self->feature; my ($start,$end) = $self->effective_bounds($feature); my ($datatype,$data) = $self->datatype; return $self->subsample($data,$start,$end) if $datatype eq 'wigdata'; return $self->create_parts_from_wigfile($data,$start,$end) if $datatype eq 'wigfile'; return $self->create_parts_for_dense_feature($data,$start,$end) if $datatype eq 'densefile'; return $self->create_parts_from_coverage($data,$start,$end) if $datatype eq 'coverage'; return $self->create_parts_from_summary($data,$start,$end) if $datatype eq 'statistical_summary'; return []; } sub effective_bounds { my $self = shift; my $feature = shift; my $panel_start = $self->panel->start; my $panel_end = $self->panel->end; my $start = $feature->start>$panel_start ? $feature->start : $panel_start; my $end = $feature->end<$panel_end ? $feature->end : $panel_end; return ($start,$end); } sub create_parts_for_dense_feature { my $self = shift; my ($dense,$start,$end) = @_; my $span = $self->scale> 1 ? $end - $start : $self->width; my $data = $dense->values($start,$end,$span); my $points_per_span = ($end-$start+1)/$span; my @parts; for (my $i=0; $i<$span;$i++) { my $offset = $i * $points_per_span; my $value = shift @$data; next unless defined $value; push @parts,[$start + int($i * $points_per_span), $start + int($i * $points_per_span), $value]; } return \@parts; } sub create_parts_from_coverage { my $self = shift; my ($array,$start,$end) = @_; $array = [split ',',$array] unless ref $array; return unless @$array; my $bases_per_bin = ($end-$start)/@$array; my $pixels_per_base = $self->scale; my @parts; for (my $pixel=0;$pixel<$self->width;$pixel++) { my $offset = $pixel/$pixels_per_base; my $s = $start + $offset; my $e = $s+1; # fill in gaps my $v = $array->[$offset/$bases_per_bin]; push @parts,[$s,$s,$v]; } return \@parts; } sub create_parts_from_summary { my $self = shift; my ($stats,$start,$end) = @_; $stats ||= []; my @vals = map {$_->{validCount} ? $_->{sumData}/$_->{validCount}:0} @$stats; return \@vals; } sub create_parts_from_wigfile { my $self = shift; my ($path,$start,$end) = @_; if (ref $path && $path->isa('Bio::Graphics::Wiggle')) { return $self->create_parts_for_dense_feature($path,$start,$end); } $path = $self->rel2abs($path); if ($path =~ /\.wi\w{1,3}$/) { eval "require Bio::Graphics::Wiggle" unless Bio::Graphics::Wiggle->can('new'); my $wig = eval { Bio::Graphics::Wiggle->new($path)}; return $self->create_parts_for_dense_feature($wig,$start,$end); } elsif ($path =~ /\.bw$/i) { eval "use Bio::DB::BigWig" unless Bio::DB::BigWig->can('new'); my $bigwig = Bio::DB::BigWig->new(-bigwig=>$path); my ($summary) = $bigwig->features(-seq_id => $self->feature->segment->ref, -start => $start, -end => $end, -type => 'summary'); return $self->create_parts_from_summary($summary->statistical_summary($self->width)); } } sub subsample { my $self = shift; my ($data,$start,$end) = @_; my $span = $self->scale > 1 ? $end - $start : $self->width; my $points_per_span = ($end-$start+1)/$span; my @parts; for (my $i=0; $i<$span;$i++) { my $offset = $i * $points_per_span; my $value = $data->[$offset + $points_per_span/2]; push @parts,[$start + int($i*$points_per_span), $start + int($i*$points_per_span), $value]; } return \@parts; } sub rel2abs { my $self = shift; my $wig = shift; return $wig if ref $wig; my $path = $self->option('basedir'); return File::Spec->rel2abs($wig,$path); } sub draw { my $self = shift; my ($gd,$dx,$dy) = @_; my $feature = $self->feature; my $datatype = $self->datatype; my $retval; $retval = $self->draw_wigfile($feature,@_) if $datatype eq 'wigfile'; $retval = $self->draw_wigdata($feature,@_) if $datatype eq 'wigdata'; $retval = $self->draw_densefile($feature,@_) if $datatype eq 'densefile'; $retval = $self->draw_coverage($feature,@_) if $datatype eq 'coverage'; $retval = $self->draw_statistical_summary($feature,@_) if $datatype eq 'statistical_summary'; $retval = $self->SUPER::draw(@_) if $datatype eq 'generic'; return $retval; } sub draw_wigfile { my $self = shift; my $feature = shift; my ($wigfile) = eval{$feature->get_tag_values('wigfile')}; $wigfile = $self->rel2abs($wigfile); eval "require Bio::Graphics::Wiggle" unless Bio::Graphics::Wiggle->can('new'); my $wig = ref $wigfile && $wigfile->isa('Bio::Graphics::Wiggle') ? $wigfile : eval { Bio::Graphics::Wiggle->new($wigfile) }; unless ($wig) { warn $@; return $self->SUPER::draw(@_); } $self->_draw_wigfile($feature,$wigfile,@_); } sub draw_wigdata { my $self = shift; my $feature = shift; my ($data) = eval{$feature->get_tag_values('wigdata')}; if (ref $data eq 'ARRAY') { my ($start,$end) = $self->effective_bounds($feature); my $parts = $self->subsample($data,$start,$end); $self->draw_plot($parts,@_); } else { my $wig = eval { Bio::Graphics::Wiggle->new() }; unless ($wig) { warn $@; return $self->SUPER::draw(@_); } $wig->import_from_wif64($data); $self->_draw_wigfile($feature,$wig,@_); } } sub draw_densefile { my $self = shift; my $feature = shift; my ($densefile) = eval{$feature->get_tag_values('densefile')}; $densefile = $self->rel2abs($densefile); my ($denseoffset) = eval{$feature->get_tag_values('denseoffset')}; my ($densesize) = eval{$feature->get_tag_values('densesize')}; $denseoffset ||= 0; $densesize ||= 1; my $smoothing = $self->get_smoothing; my $smooth_window = $self->smooth_window; my $start = $self->smooth_start; my $end = $self->smooth_end; my $fh = IO::File->new($densefile) or die "can't open $densefile: $!"; eval "require Bio::Graphics::DenseFeature" unless Bio::Graphics::DenseFeature->can('new'); my $dense = Bio::Graphics::DenseFeature->new(-fh=>$fh, -fh_offset => $denseoffset, -start => $feature->start, -smooth => $smoothing, -recsize => $densesize, -window => $smooth_window, ) or die "Can't initialize DenseFeature: $!"; my $parts = $self->get_parts; $self->draw_plot($parts); } sub draw_coverage { my $self = shift; my $feature = shift; my ($array) = eval{$feature->get_tag_values('coverage')}; $self->_draw_coverage($feature,$array,@_); } sub draw_statistical_summary { my $self = shift; my $feature = shift; my $stats = $feature->statistical_summary($self->width); $stats ||= []; my @vals = map {$_->{validCount} ? $_->{sumData}/$_->{validCount}:0} @$stats; return $self->_draw_coverage($feature,\@vals,@_); } sub _draw_coverage { my $self = shift; my $feature = shift; my $array = shift; $array = [split ',',$array] unless ref $array; return unless @$array; my ($start,$end) = $self->effective_bounds($feature); my $bases_per_bin = ($end-$start)/@$array; my $pixels_per_base = $self->scale; my @parts; for (my $pixel=0;$pixel<$self->width;$pixel++) { my $offset = $pixel/$pixels_per_base; my $s = $start + $offset; my $e = $s+1; # fill in gaps my $v = $array->[$offset/$bases_per_bin]; push @parts,[$s,$s,$v]; } $self->draw_plot(\@parts,@_); } sub _draw_wigfile { my $self = shift; my $feature = shift; my $wigfile = shift; $self->feature->remove_tag('wigfile') if $self->feature->has_tag('wigfile'); $self->feature->add_tag_value('wigfile',$wigfile); eval "require Bio::Graphics::Wiggle" unless Bio::Graphics::Wiggle->can('new'); my $wig = ref $wigfile && $wigfile->isa('Bio::Graphics::Wiggle') ? $wigfile : eval { Bio::Graphics::Wiggle->new($wigfile) }; $wig->smoothing($self->get_smoothing); $wig->window($self->smooth_window); $self->wig($wig); my $parts = $self->get_parts; $self->draw_plot($parts,@_); } 1; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/splice_site.pm���������������������������������������������000555��001750��001750�� 5655�12165075746� 23026� 0����������������������������������������������������������������������������������������������������ustar�00lstein��������������������������lstein��������������������������000000��000000�������������������������������������������������������������������������������������������������������������������������������������������������������������������������package Bio::Graphics::Glyph::splice_site; use strict; use base qw(Bio::Graphics::Glyph::generic); use constant PWIDTH => 3; sub draw_component { my $self = shift; my $gd = shift; my ($left,$top) = @_; my($x1,$y1,$x2,$y2) = $self->bounds(@_); my $center = int(0.5+($x1+$x2)/2); my $direction = $self->option('direction'); my $height = $y2 - $y1; my $fraction = $self->option('height_fraction') || 1.0; my $bottom = $y2; $top = $y2 - $fraction * $height; # draw the base my $fgcolor = $self->fgcolor; $gd->line($center,$bottom,$center,$top,$fgcolor); if ($direction eq 'right') { $gd->line($center,$top,$center + PWIDTH,$top,$fgcolor); } elsif ($direction eq 'left') { $gd->line($center,$top,$center - PWIDTH,$top,$fgcolor); } } 1; =head1 NAME Bio::Graphics::Glyph::splice_site - The "splice_site" glyph =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This glyph was designed to show an inverted "L" representing splice donors and acceptors. The vertical part of the L points downwards and is positioned in the center of the range (even if the range is quite large). In addition to the usual glyph options, this glyph recognizes: Option Value Description ------ ----- ----------- direction "left" or "right" direction the short part of the L points height_fraction 0.0 - 1.0 fractional height of the glyph, usually a callback that uses the feature's score to determine its height =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Xiaokang Pan Epan@cshl.orgE Copyright (c) 2001 BDGP This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut �����������������������������������������������������������������������������������Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/rainbow_gene.pm��������������������������������������������000555��001750��001750�� 24550�12165075746� 23175� 0����������������������������������������������������������������������������������������������������ustar�00lstein��������������������������lstein��������������������������000000��000000�������������������������������������������������������������������������������������������������������������������������������������������������������������������������package Bio::Graphics::Glyph::rainbow_gene; use strict; use base qw(Bio::Graphics::Glyph::gene Bio::Graphics::Glyph::heat_map); sub my_descripton { return <red), or gradients progressing through the colors of the rainbow (magenta->blue->green->yelloe->red) can be created. For example: # a white->red heat map start_color = white stop_color = red # a rainbow start_color = magenta stop_color = red # green->yellow->red start_color = green stop_color = red This glyph is a subclass of the gene and heat_maps glyphs, and recognizes the same options. END } sub my_options { { transcript_only => [ 'boolean', undef, 'If true, thes score of the transcript or mRNA feature will be '. 'will be used to calculate the background color. Otherwise the '. 'score of exon sub-features are used' ], } } sub draw { my $self = shift; my @parts = $self->parts; @parts = $self if !@parts && $self->level == 0; return $self->SUPER::draw(@_) unless @parts; my $top_score = $self->feature->score if $self->option('transcript_only'); $self->calculate_gradient(\@parts); my $low_rgb = $self->low_rgb; for my $part (@parts) { my $score = $top_score || $part->feature->score; unless (defined $score && $self->score_range ) { $part->{partcolor} = $self->color_index(@$low_rgb); } else { my @rgb = $self->calculate_color($score); $part->{partcolor} = $self->color_index(@rgb); } } return $self->SUPER::draw(@_); } sub extra_arrow_length { my $self = shift; return 0 unless $self->{level} == 1; local $self->{level} = 0; # fake out superclass return $self->SUPER::extra_arrow_length; } sub pad_left { my $self = shift; my $type = $self->feature->primary_tag; return 0 unless $type =~ /gene|mRNA/; $self->SUPER::pad_left; } sub pad_right { my $self = shift; return 0 unless $self->{level} < 2; # don't invoke this expensive call on exons my $strand = $self->feature->strand; $strand *= -1 if $self->{flip}; my $pad = $self->SUPER::pad_right; return $pad unless defined($strand) && $strand > 0; my $al = $self->arrow_length; return $al > $pad ? $al : $pad; } sub pad_bottom { my $self = shift; return 0 unless $self->{level} < 2; # don't invoke this expensive call on exons return $self->SUPER::pad_bottom; } sub pad_top { my $self = shift; return 0 unless $self->{level} < 2; # don't invoke this expensive call on exons return $self->SUPER::pad_top; } sub bump { my $self = shift; return 1 # top level bumps, other levels don't unless specified in config if $self->{level} == 0 && lc $self->feature->primary_tag eq 'gene'; return $self->SUPER::bump; } sub label { my $self = shift; return unless $self->{level} < 2; if ($self->label_transcripts && $self->{feature}->primary_tag =~ /RNA|pseudogene/i) { return $self->_label; } else { return $self->SUPER::label; } } sub label_position { my $self = shift; return 'top' if $self->{level} == 0; return 'left'; } sub label_transcripts { my $self = shift; return $self->{label_transcripts} if exists $self->{label_transcripts}; return $self->{label_transcripts} = $self->_label_transcripts; } sub _label_transcripts { my $self = shift; return $self->option('label_transcripts'); } sub draw_connectors { my $self = shift; if ($self->feature->primary_tag eq 'gene') { my @parts = $self->parts; return if @parts && $parts[0] =~ /rna|transcript|pseudogene/i; } $self->SUPER::draw_connectors(@_); } sub maxdepth { my $self = shift; my $md = $self->Bio::Graphics::Glyph::maxdepth; return $md if defined $md; return 2; } sub _subfeat { my $class = shift; my $feature = shift; if ($feature->primary_tag =~ /^gene/i) { my @transcripts; for my $t (qw/mRNA tRNA snRNA snoRNA miRNA ncRNA pseudogene/) { push @transcripts, $feature->get_SeqFeatures($t); } return @transcripts if @transcripts; return $feature->get_SeqFeatures; # no transcripts?! return whatever's there } elsif ($feature->primary_tag =~ /^CDS/i) { my @parts = $feature->get_SeqFeatures(); return ($feature) if $class->{level} == 0 and !@parts; return @parts; } my @subparts; if ($class->option('sub_part')) { @subparts = $feature->get_SeqFeatures($class->option('sub_part')); } elsif ($feature->primary_tag =~ /^mRNA/i) { @subparts = $feature->get_SeqFeatures(qw(CDS five_prime_UTR three_prime_UTR UTR)); } else { @subparts = $feature->get_SeqFeatures('exon'); } # The CDS and UTRs may be represented as a single feature with subparts or as several features # that have different IDs. We handle both cases transparently. my @result; foreach (@subparts) { if ($_->primary_tag =~ /CDS|UTR/i) { my @cds_seg = $_->get_SeqFeatures; if (@cds_seg > 0) { push @result,@cds_seg } else { push @result,$_ } } else { push @result,$_; } } # fall back to drawing a solid box if no subparts and level 0 return ($feature) if $class->{level} == 0 && !@result; return @result; } sub bgcolor { my $self = shift; return defined $self->{partcolor} ? $self->{partcolor} : $self->SUPER::bgcolor; } sub fgcolor { my $self = shift; return $self->option('vary_fg') ? $self->bgcolor : $self->SUPER::fgcolor; } 1; __END__ =head1 NAME Bio::Graphics::Glyph::gene - A GFF3-compatible gene glyph =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This glyph is used for drawing genes that may have alternatively-spliced transcripts. The various isoforms are stacked on top of each other and given a single label and description that apply to the entire stack. Each individual transcript's name is optionally printed to the left of the transcript glyph. Transcripts (splice isoforms) are drawn using the processed_transcript glyph. CDS features are drawn in the background color, and the UTRs are drawn in an alternate color selected by the utr_color option. In addition, you can make the UTRs thinner than the CDS by setting the "thin_utr" option. This glyph is designed to work properly with GFF3-style three-tier genes, in which the top level feature has the Sequence Ontology type of "gene", the second level feature(s) have the SO type "mRNA", and the third level feature(s) have the SO type "CDS", "five_prime_utr" and "three_prime_utr." Subparts named "UTR" are also honored. The feature can contain other subparts as well (e.g. exon, intron, translation), but they are currently ignored unless the option sub_part is supplied. If the sub_part option is used that feature type will be used and CDS and UTR features will be excluded. This could be used for specifying that exons be used instead, for example. This glyph is a subclass of processed_transcript, and recognizes the same options. =head2 OPTIONS The following options are standard among all Glyphs. See L for a full explanation. Option Description Default ------ ----------- ------- -fgcolor Foreground color black -outlinecolor Synonym for -fgcolor -bgcolor Background color turquoise -fillcolor Synonym for -bgcolor -linewidth Line width 1 -height Height of glyph 10 -font Glyph font gdSmallFont -connector Connector type undef (false) -connector_color Connector color black -label Whether to draw a label undef (false) -description Whether to draw a description undef (false) -strand_arrow Whether to indicate undef (false) strandedness -hilite Highlight color undef (no color) In addition, the gene glyph recognizes the following glyph-specific options: Option Description Default ------ ----------- ------- -label_transcripts undef (false) Flag. If true, then the display name of each transcript will be drawn to the left of the transcript glyph. -thin_utr Flag. If true, UTRs will undef (false) be drawn at 2/3 of the height of CDS segments. -utr_color Color of UTR segments. Gray #D0D0D0 -decorate_introns Draw strand with little arrows undef (false) on the intron. The B<-adjust_exons> and B<-implied_utrs> options are inherited from processed_transcript, but are quietly ignored. Please use the processed_transcript glyph for this type of processing. =head1 BUGS The SO terms are hard-coded. They should be more flexible and should recognize ISA relationships. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Lincoln Stein Elstein@cshl.orgE Copyright (c) 2001 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut ��������������������������������������������������������������������������������������������������������������������������������������������������������Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/xyplot.pm��������������������������������������������������000555��001750��001750�� 67753�12165075746� 22111� 0����������������������������������������������������������������������������������������������������ustar�00lstein��������������������������lstein��������������������������000000��000000�������������������������������������������������������������������������������������������������������������������������������������������������������������������������package Bio::Graphics::Glyph::xyplot; use strict; #use GD 'gdTinyFont'; use base qw(Bio::Graphics::Glyph::segments Bio::Graphics::Glyph::minmax); use constant DEFAULT_POINT_RADIUS=>4; use Bio::Root::Version; our $VERSION = ${Bio::Root::Version::VERSION}; use constant DEBUG=>0; use constant EXTRA_LABEL_PAD=>8; sub my_description { return <<'END'; This glyph is used for drawing features that have a position on the genome and a numeric value. It can be used to represent gene prediction scores, motif-calling scores, percent similarity, microarray intensities, or other features that require a line plot. The plot is designed to work on a single feature group that contains subfeatures. It is the subfeatures that carry the score information. For a more efficient implementation that is suitable for dense genome-wide data, use Bio::Graphics::Wiggle and the wiggle_xyplot glyph. END } sub my_options { { point_radius => [ 'integer', 1, 'When drawing data points, this specifies the radius of each point.', ], clip => [ 'boolean', 0, 'If min_score and/or max_score are manually specified,', 'then setting this to true will cause values outside the', 'range to be clipped.' ], graph_type => [ ['histogram','line','points','linepoints'], 'histogram', 'Type of graph to generate. Options are "boxes",', '"line","points", or "linepoints".', 'The deprecated "boxes" subtype is equivalent to "histogram".' ], point_symbol => [ 'string', 'none', 'Symbol to use for each data point when drawing line graphs.', 'Options are "triangle", "square", "disc", "filled_triangle",', '"filled_square", "filled_disc", "point" and "none"', ], scale => [ 'string', 'three', 'Position where the Y axis scale is drawn, if any.', 'Options are one of "left", "right", "both", "three" or "none".', '"three" will cause the scale to be drawn in the left, right and center.', ], scale_color => [ 'color', 'fgcolor', 'Color of the X and Y scales. Defaults to the same as fgcolor.', ], }; } my %SYMBOLS = ( triangle => \&draw_triangle, square => \&draw_square, disc => \&draw_disc, point => \&draw_point, ); sub extra_label_pad { return EXTRA_LABEL_PAD; } # Default pad_left is recursive through all parts. We certainly # don't want to do this for all parts in the graph. sub pad_left { my $self = shift; return 0 unless $self->level == 0; my $left = $self->SUPER::pad_left(@_); my $side = $self->_determine_side; $left += $self->extra_label_pad if $self->label_position eq 'left' && $side =~ /left|both|three/; return $left; } # Default pad_left is recursive through all parts. We certainly # don't want to do this for all parts in the graph. sub pad_right { my $self = shift; return 0 unless $self->level == 0; return $self->SUPER::pad_right(@_); } sub point_radius { shift->option('point_radius') || DEFAULT_POINT_RADIUS; } sub pad_top { my $self = shift; my $pad = $self->Bio::Graphics::Glyph::generic::pad_top(@_); if ($pad < $self->font_height($self->getfont('gdTinyFont'))+8) { $pad = $self->font_height($self->getfont('gdTinyFont'))+8; # extra room for the scale } $pad; } sub pad_bottom { my $self = shift; my $pad = $self->Bio::Graphics::Glyph::generic::pad_bottom(@_); if ($pad < $self->font_height($self->getfont('gdTinyFont'))/4) { $pad = $self->font_height($self->getfont('gdTinyFont'))/4; # extra room for the scale } $pad; } sub scalecolor { my $self = shift; local $self->{default_opacity} = 1; my $color = $self->color('scale_color') || $self->fgcolor; } sub default_scale { return 'three'; } sub record_label_positions { my $self = shift; my $rlp = $self->option('record_label_positions'); return $rlp if defined $rlp; return -1; } sub graph_type { my $self = shift; $self->option('graph_type') || $self->option('graphtype') || 'boxes'; } sub draw { my $self = shift; my ($gd,$dx,$dy) = @_; my ($left,$top,$right,$bottom) = $self->calculate_boundaries($dx,$dy); my @parts = $self->parts; return $self->SUPER::draw(@_) unless @parts > 0; $self->panel->startGroup($gd); my ($min_score,$max_score) = $self->minmax(\@parts); my $side = $self->_determine_side(); # if a scale is called for, then we adjust the max and min to be even # multiples of a power of 10. if ($side) { $max_score = max10($max_score); $min_score = min10($min_score); } my $height = $bottom - $top; my $scale = $max_score > $min_score ? $height/($max_score-$min_score) : 1; my $x = $left; my $y = $top + $self->pad_top; # position of "0" on the scale my $y_origin = $min_score <= 0 ? $bottom - (0 - $min_score) * $scale : $bottom; $y_origin = $top if $max_score < 0; my $clip_ok = $self->option('clip'); $self->{_clip_ok} = $clip_ok; $self->{_scale} = $scale; $self->{_min_score} = $min_score; $self->{_max_score} = $max_score; $self->{_top} = $top; $self->{_bottom} = $bottom; # now seed all the parts with the information they need to draw their positions foreach (@parts) { my $s = $_->score; $_->{_y_position} = $self->score2position($s); warn "y_position = $_->{_y_position}" if DEBUG; } my $type = $self->option('graph_type') || $self->option('graphtype') || 'boxes'; my (@draw_methods) = $self->lookup_draw_method($type); $self->throw("Invalid graph type '$type'") unless @draw_methods; $self->panel->startGroup($gd); $self->_draw_grid($gd,$scale,$min_score,$max_score,$dx,$dy,$y_origin); $self->panel->endGroup($gd); for my $draw_method (@draw_methods) { $self->$draw_method($gd,$dx,$dy,$y_origin); } $self->panel->startGroup($gd); $self->_draw_scale($gd,$scale,$min_score,$max_score,$dx,$dy,$y_origin); $self->panel->endGroup($gd); $self->draw_label(@_) if $self->option('label') or $self->record_label_positions; $self->draw_description(@_) if $self->option('description'); $self->draw_legend(@_) if $self->option('overlay'); $self->panel->endGroup($gd); } sub lookup_draw_method { my $self = shift; my $type = shift; return '_draw_boxes' if $type eq 'histogram'; # same thing return '_draw_boxes' if $type eq 'boxes'; return qw(_draw_line _draw_points) if $type eq 'linepoints'; return '_draw_line' if $type eq 'line'; return '_draw_points' if $type eq 'points'; return; } sub normalize_track { my $self = shift; my @glyphs_in_track = @_; my ($global_min,$global_max); for my $g (@glyphs_in_track) { my ($min_score,$max_score) = $g->minmax($g->get_parts); $global_min = $min_score if !defined $global_min || $min_score < $global_min; $global_max = $max_score if !defined $global_max || $max_score > $global_max; } # note that configure applies to the whole track $glyphs_in_track[0]->configure(-min_score => $global_min); $glyphs_in_track[0]->configure(-max_score => $global_max); } sub get_parts { my $self = shift; my @parts = $self->parts; return \@parts; } sub score { my $self = shift; my $s = $self->option('score'); return $s if defined $s; return eval { $self->feature->score }; } sub score2position { my $self = shift; my $score = shift; return undef unless defined $score; if ($self->{_clip_ok} && $score < $self->{_min_score}) { return $self->{_bottom}; } elsif ($self->{_clip_ok} && $score > $self->{_max_score}) { return $self->{_top}; } else { warn "score = $score, _top = $self->{_top}, _bottom = $self->{_bottom}, max = $self->{_max_score}, min=$self->{_min_score}" if DEBUG; my $position = ($score-$self->{_min_score}) * $self->{_scale}; warn "position =$position" if DEBUG; return $self->{_bottom} - $position; } } sub log10 { log(shift)/log(10) } sub max10 { my $a = shift; return 0 if $a==0; return -min10(-$a) if $a<0; return max10($a*10)/10 if $a < 1; my $l=int(log10($a)); $l = 10**$l; my $r = $a/$l; return $r*$l if int($r) == $r; return $l*int(($a+$l)/$l); } sub min10 { my $a = shift; return 0 if $a==0; return -max10(-$a) if $a<0; return min10($a*10)/10 if $a < 1; my $l=int(log10($a)); $l = 10**$l; my $r = $a/$l; return $r*$l if int($r) == $r; return $l*int($a/$l); } sub _draw_boxes { my $self = shift; my ($gd,$left,$top,$y_origin) = @_; my @parts = $self->parts; my $lw = $self->linewidth; # Make the boxes transparent my $positive = $self->pos_color + 1073741824; my $negative = $self->neg_color + 1073741824; my $height = $self->height; my $midpoint = $self->midpoint ? $self->score2position($self->midpoint) : $y_origin; my $partcolor = $self->code_option('part_color'); my $factory = $self->factory; # draw each of the boxes as a rectangle for (my $i = 0; $i < @parts; $i++) { my $part = $parts[$i]; my $next = $parts[$i+1]; my ($color,$negcolor); # special check here for the part_color being defined so as not to introduce lots of # checking overhead when it isn't if ($partcolor) { $color = $self->translate_color($factory->option($part,'part_color',0,0)); $negcolor = $color; } else { $color = $positive; $negcolor = $negative; } my ($x1,$y1,$x2,$y2) = $part->calculate_boundaries($left,$top); next unless defined $part->{_y_position}; # prevent boxes from being less than 1 pixel $x2 = $x1+1 if $x2-$x1 < 1; if ($part->{_y_position} < $midpoint) { $gd->filledRectangle($x1,$part->{_y_position},$x2,$y_origin,$color); } else { $gd->filledRectangle($x1,$y_origin,$x2,$part->{_y_position},$negcolor); } } # That's it. } sub _draw_line { my $self = shift; my ($gd,$left,$top) = @_; my @parts = $self->parts; my $fgcolor = $self->fgcolor; my $bgcolor = $self->bgcolor; # connect to center positions of each interval my $first_part = shift @parts; my ($x1,$y1,$x2,$y2) = $first_part->calculate_boundaries($left,$top); my $current_x = ($x1+$x2)/2; my $current_y = $first_part->{_y_position}; for my $part (@parts) { ($x1,$y1,$x2,$y2) = $part->calculate_boundaries($left,$top); my $next_x = ($x1+$x2)/2; my $next_y = $part->{_y_position}; $gd->line($current_x,$current_y,$next_x,$next_y,$fgcolor) if defined $current_y and defined $next_y; ($current_x,$current_y) = ($next_x,$next_y); } } sub _draw_points { my $self = shift; my ($gd,$left,$top) = @_; my $symbol_name = $self->option('point_symbol') || 'point'; my $filled = $symbol_name =~ s/^filled_//; my $symbol_ref = $SYMBOLS{$symbol_name}; my @parts = $self->parts; my $fgcolor = $self->fgcolor; my $bgcolor = $self->bgcolor; my $pr = $self->point_radius; my $partcolor = $self->code_option('part_color'); my $factory = $self->factory; for my $part (@parts) { my ($x1,$y1,$x2,$y2) = $part->calculate_boundaries($left,$top); my $x = ($x1+$x2)/2; my $y = $part->{_y_position}; next unless defined $y; my $color; if ($partcolor) { $color = $self->translate_color($factory->option($part,'part_color',0,0)); } else { $color = $fgcolor; } $symbol_ref->($gd,$x,$y,$pr,$color,$filled); } } sub _determine_side { my $self = shift; my $side = $self->option('scale'); return if $side eq 'none'; $side ||= $self->default_scale(); return $side; } sub _draw_scale { my $self = shift; my ($gd,$scale,$min,$max,$dx,$dy,$y_origin) = @_; my ($x1,$y1,$x2,$y2) = $self->calculate_boundaries($dx,$dy); my $crosses_origin = $min < 0 && $max > 0; my $side = $self->_determine_side() or return; my $fg = $self->scalecolor; my $font = $self->font('gdTinyFont'); my $middle = ($x1+$x2)/2; # minor ticks - multiples of 10 my $y_scale = $self->minor_ticks($min,$max,$y1,$y2); my $p = $self->panel; my $gc = $self->translate_color($p->gridcolor); my $mgc= $self->translate_color($p->gridmajorcolor); $gd->line($x1,$y1,$x1,$y2,$fg) if $side eq 'left' || $side eq 'both' || $side eq 'three'; $gd->line($x2,$y1,$x2,$y2,$fg) if $side eq 'right' || $side eq 'both' || $side eq 'three'; $gd->line($middle,$y1,$middle,$y2,$fg) if $side eq 'three'; $gd->line($x1,$y_origin,$x2,$y_origin,$mgc); my @points = ([$y1,$max],[$y2,$min]); push @points,$crosses_origin ? [$y_origin,0] : [($y1+$y2)/2,($min+$max)/2]; my $last_font_pos = -99999999999; for (sort {$a->[0]<=>$b->[0]} @points) { $gd->line($x1-3,$_->[0],$x1,$_->[0],$fg) if $side eq 'left' || $side eq 'both' || $side eq 'three'; $gd->line($x2,$_->[0],$x2+3,$_->[0],$fg) if $side eq 'right' || $side eq 'both' || $side eq 'three'; $gd->line($middle,$_->[0],$middle+3,$_->[0],$fg) if $side eq 'three'; my $font_pos = $_->[0]-($self->font_height($font)/2); $font_pos-=2 if $_->[1] < 0; # jog a little bit for neg sign next unless $font_pos > $last_font_pos + $self->font_height($font)/2; # prevent labels from clashing if ($side eq 'left' or $side eq 'both' or $side eq 'three') { $gd->string($font, $x1 - $self->string_width($_->[1],$font) - 3,$font_pos, $_->[1], $fg); } if ($side eq 'right' or $side eq 'both' or $side eq 'three') { $gd->string($font, $x2 + 5,$font_pos, $_->[1], $fg); } if ($side eq 'three') { $gd->string($font, $middle + 5,$font_pos, $_->[1], $fg); } $last_font_pos = $font_pos; } for (my $y = $y2-$y_scale; $y > $y1; $y -= $y_scale) { my $yr = int($y+0.5); $gd->line($x1-3,$yr,$x1,$yr,$fg) if $side eq 'left' or $side eq 'both' or $side eq 'three'; $gd->line($x2,$yr,$x2+3,$yr,$fg) if $side eq 'right' or $side eq 'both' or $side eq 'three'; $gd->line($middle-1,$yr,$middle+2,$yr,$fg) if $side eq 'three'; } } sub _draw_grid { my $self = shift; my ($gd,$scale,$min,$max,$dx,$dy,$y_origin) = @_; my $side = $self->_determine_side(); return unless $side; my ($x1,$y1,$x2,$y2) = $self->calculate_boundaries($dx,$dy); my $p = $self->panel; my $gc = $self->translate_color($p->gridcolor); my $y_scale = $self->minor_ticks($min,$max,$y1,$y2); for (my $y = $y2-$y_scale; $y > $y1; $y -= $y_scale) { my $yr = int($y+0.5); $gd->line($x1-1,$yr,$x2,$yr,$gc); } $gd->line($x1,$y1,$x2,$y1,$gc); $gd->line($x1,$y2,$x2,$y2,$gc); } sub minor_ticks { my $self = shift; my ($min,$max,$top,$bottom) = @_; my $interval = 1; my $height = $bottom-$top; my $y_scale = 1; if ($max > $min) { while ($height/(($max-$min)/$interval) < 2) { $interval *= 10 } $y_scale = $height/(($max-$min)/$interval); } } # Let the feature attributes override the labelcolor sub labelcolor { my $self = shift; my ($labelcolor) = eval {$self->feature->get_tag_values('labelcolor')}; return $labelcolor ? $self->translate_color($labelcolor) : $self->SUPER::labelcolor; } # we are unbumpable! sub bump { return 0; } sub connector { my $self = shift; my $type = $self->option('graph_type'); return 1 if $type eq 'line' or $type eq 'linepoints'; } sub height { my $self = shift; return $self->option('graph_height') || $self->SUPER::height; } sub draw_description { my $self = shift; return if $self->bump eq 'overlap'; return $self->SUPER::draw_description(@_); } sub draw_triangle { my ($gd,$x,$y,$pr,$color,$filled) = @_; $pr /= 2; my ($vx1,$vy1) = ($x-$pr,$y+$pr); my ($vx2,$vy2) = ($x, $y-$pr); my ($vx3,$vy3) = ($x+$pr,$y+$pr); my $poly = GD::Polygon->new; $poly->addPt($vx1,$vy1,$vx2,$vy2); $poly->addPt($vx2,$vy2,$vx3,$vy3); $poly->addPt($vx3,$vy3,$vx1,$vy1); if ($filled) { $gd->filledPolygon($poly,$color); } else { $gd->polygon($poly,$color); } } sub draw_square { my ($gd,$x,$y,$pr,$color,$filled) = @_; $pr /= 2; my $poly = GD::Polygon->new; $poly->addPt($x-$pr,$y-$pr); $poly->addPt($x+$pr,$y-$pr); $poly->addPt($x+$pr,$y+$pr); $poly->addPt($x-$pr,$y+$pr); if ($filled) { $gd->filledPolygon($poly,$color); } else { $gd->polygon($poly,$color); } } sub draw_disc { my ($gd,$x,$y,$pr,$color,$filled) = @_; if ($filled) { $gd->filledArc($x,$y,$pr,$pr,0,360,$color); } else { $gd->arc($x,$y,$pr,$pr,0,360,$color); } } sub draw_point { my ($gd,$x,$y,$pr,$color) = @_; $gd->setPixel($x,$y,$color); } sub keyglyph { my $self = shift; my $scale = 1/$self->scale; # base pairs/pixel my $feature = Bio::Graphics::Feature->new( -segments=>[ [ 0*$scale,9*$scale], [ 10*$scale,19*$scale], [ 20*$scale, 29*$scale] ], -name => 'foo bar', -strand => '+1'); ($feature->segments)[0]->score(10); ($feature->segments)[1]->score(50); ($feature->segments)[2]->score(25); my $factory = $self->factory->clone; $factory->set_option(label => 1); $factory->set_option(bump => 0); $factory->set_option(connector => 'solid'); my $glyph = $factory->make_glyph(0,$feature); return $glyph; } sub symbols { my $self = shift; return \%SYMBOLS; } sub draw_label { my $self = shift; my ($gd,$left,$top,$partno,$total_parts) = @_; my $label = $self->label or return; if ($self->bump eq 'overlap') { my $x = $self->left + $left + $self->pad_left; $x = $self->panel->left + 1 if $x <= $self->panel->left; $x += ($self->panel->glyph_scratch||0); my $font = $self->labelfont; my $width = $self->string_width($label,$font)+4; my $height= $self->string_height('',$font); unless ($self->record_label_positions) { $gd->filledRectangle($x,$top,$x+$width+6,$top+$height,$self->bgcolor); local $self->{default_opacity} = 1; $gd->string($font,$x+3,$top,$label,$self->contrasting_label_color($gd,$self->bgcolor)); } $self->panel->glyph_scratch($self->panel->glyph_scratch + $width); $self->panel->add_key_box($self,$label,$x,$top) if $self->record_label_positions; } elsif ($self->label_position eq 'left') { my $font = $self->labelfont; my $x = $self->left + $left - $self->string_width($label,$font) - $self->extra_label_pad; my $y = $self->{top} + $top; $self->render_label($gd, $font, $x, $y, $label); } else { $self->SUPER::draw_label(@_); } } sub contrasting_label_color { my $self = shift; my ($gd,$bgcolor) = @_; my ($r,$g,$b) = $gd->rgb($bgcolor); my $avg = ($r+$g+$b)/3; return $self->translate_color($avg > 128 ? 'black' : 'white'); } sub draw_legend { my $self = shift; my ($gd,$left,$top,$partno,$total_parts) = @_; return if $self->bump eq 'overlap'; my $color = $self->option('fgcolor'); my $name = $self->feature->{name}; my $label = " " . $name . "" or return; my $font = $self->labelfont; my $x = $self->left + $left - $self->string_width($label,$font) - $self->extra_label_pad; my $y = $self->{top} + $top; my $is_legend = 1; $self->render_label($gd, $font, $x, $y, $label, $is_legend); } 1; __END__ =head1 NAME Bio::Graphics::Glyph::xyplot - The xyplot glyph =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This glyph is used for drawing features that have a position on the genome and a numeric value. It can be used to represent gene prediction scores, motif-calling scores, percent similarity, microarray intensities, or other features that require a line plot. The X axis represents the position on the genome, as per all other glyphs. The Y axis represents the score. Options allow you to set the height of the glyph, the maximum and minimum scores, the color of the line and axis, and the symbol to draw. The plot is designed to work on a single feature group that contains subfeatures. It is the subfeatures that carry the score information. The best way to arrange for this is to create an aggregator for the feature. We'll take as an example a histogram of repeat density in which interval are spaced every megabase and the score indicates the number of repeats in the interval; we'll assume that the database has been loaded in in such a way that each interval is a distinct feature with the method name "density" and the source name "repeat". Furthermore, all the repeat features are grouped together into a single group (the name of the group is irrelevant). If you are using Bio::DB::GFF and Bio::Graphics directly, the sequence of events would look like this: my $agg = Bio::DB::GFF::Aggregator->new(-method => 'repeat_density', -sub_parts => 'density:repeat'); my $db = Bio::DB::GFF->new(-dsn=>'my_database', -aggregators => $agg); my $segment = $db->segment('Chr1'); my @features = $segment->features('repeat_density'); my $panel = Bio::Graphics::Panel->new(-pad_left=>40,-pad_right=>40); $panel->add_track(\@features, -glyph => 'xyplot', -graph_type=>'points', -point_symbol=>'disc', -point_radius=>4, -scale=>'both', -height=>200, ); If you are using Generic Genome Browser, you will add this to the configuration file: aggregators = repeat_density{density:repeat} clone alignment etc Note that it is a good idea to add some padding to the left and right of the panel; otherwise the scale will be partially cut off by the edge of the image. The "boxes" variant allows you to specify a pivot point such that scores above the pivot point are drawn in one color, and scores below are drawn in a different color. These "bicolor" plots are controlled by the options -bicolor_pivot, -pos_color and -neg_color, as described below. =head2 OPTIONS The following options are standard among all Glyphs. See L for a full explanation. Option Description Default ------ ----------- ------- -fgcolor Foreground color black -outlinecolor Synonym for -fgcolor -bgcolor Background color turquoise -fillcolor Synonym for -bgcolor -linewidth Line width 1 -height Height of glyph 10 -font Glyph font gdSmallFont -label Whether to draw a label 0 (false) -description Whether to draw a description 0 (false) -hilite Highlight color undef (no color) In addition, the xyplot glyph recognizes the following glyph-specific options: Option Description Default ------ ----------- ------- -max_score Maximum value of the Calculated feature's "score" attribute -min_score Minimum value of the Calculated feature's "score" attributes -graph_type Type of graph to generate. Histogram Options are: "histogram", "boxes", "line", "points", or "linepoints". -point_symbol Symbol to use. Options are none "triangle", "square", "disc", "filled_triangle", "filled_square", "filled_disc","point", and "none". -point_radius Radius of the symbol, in 4 pixels (does not apply to "point") -scale Position where the Y axis none scale is drawn if any. It should be one of "left", "right", "both" or "none" -graph_height Specify height of the graph Same as the "height" option. -part_color For boxes & points only, none bgcolor of each part (should be a callback). Supersedes -neg_color. -scale_color Color of the scale Same as fgcolor -clip If min_score and/or max_score false are manually specified, then setting this to true will cause values outside the range to be clipped. -bicolor_pivot 0 Where to pivot the two colors when drawing bicolor plots. Scores greater than this value will be drawn using -pos_color. Scores lower than this value will be drawn using -neg_color. -pos_color When drawing bicolor plots, same as bgcolor the fill color to use for values that are above the pivot point. -neg_color When drawing bicolor plots, same as bgcolor the fill color to use for values that are below the pivot point. Note that when drawing scales on the left or right that the scale is actually drawn a few pixels B the boundaries of the glyph. You may wish to add some padding to the image using -pad_left and -pad_right when you create the panel. The B<-part_color> option can be used to color each part of the graph. Only the "boxes", "points" and "linepoints" styles are affected by this. Here's a simple example: $panel->add_track->(\@affymetrix_data, -glyph => 'xyplot', -graph_type => 'boxes', -part_color => sub { my $score = shift->score; return 'red' if $score < 0; return 'lightblue' if $score < 500; return 'blue' if $score >= 500; } ); =head2 METHODS For those developers wishing to derive new modules based on this glyph, the main method to override is: =over 4 =item 'method_name' = $glyph-Elookup_draw_method($type) This method accepts the name of a graph type (such as 'histogram') and returns the name of a method that will be called to draw the contents of the graph, for example '_draw_histogram'. This method will be called with three arguments: $self->$draw_method($gd,$left,$top,$y_origin) where $gd is the GD object, $left and $top are the left and right positions of the whole glyph (which includes the scale and label), and $y_origin is the position of the zero value on the y axis (in pixels). By the time this method is called, the y axis and labels will already have been drawn, and the scale of the drawing (in pixels per unit score) will have been calculated and stored in $self-E{_scale}. The y position (in pixels) of each point to graph will have been stored into the part, as $part-E{_y_position}. Hence you could draw a simple scatter plot with this code: sub lookup_draw_method { my $self = shift; my $type = shift; if ($type eq 'simple_scatterplot') { return 'draw_points'; } else { return $self->SUPER::lookup_draw_method($type); } } sub draw_points { my $self = shift; my ($gd,$left,$top) = @_; my @parts = $self->parts; my $bgcolor = $self->bgcolor; for my $part (@parts) { my ($x1,$y1,$x2,$y2) = $part->calculate_boundaries($left,$top); my $x = ($x1+$x2)/2; # take center my $y = $part->{_y_position}; $gd->setPixel($x,$y,$bgcolor); } lookup_draw_method() may return multiple method names if needed. Each will be called in turn. =item $y_position = $self-Escore2position($score) Translate a score into a y pixel position, obeying clipping rules and min and max values. =back =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, =head1 AUTHOR Lincoln Stein Elstein@cshl.orgE Copyright (c) 2001 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/ruler_arrow.pm000555001750001750 2504412165075746 23100 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::ruler_arrow; # package to use for drawing an arrow as ruler (5' and 3' are marked as label) # Non object-oriented utilities used here-and-there in Bio::Graphics modules =head1 NAME Bio::Graphics::Glyph::ruler_arrow - glyph for drawing an arrow as ruler (5' and 3' are marked as label) =cut use strict; use base qw(Bio::Graphics::Glyph::generic); my %UNITS = (K => 1000, M => 1_000_000, G => 1_000_000_000); sub pad_bottom { my $self = shift; my $val = $self->SUPER::pad_bottom(@_); $val += $self->font_height if $self->option('tick'); $val; } # override draw method sub draw { my $self = shift; my $parallel = $self->option('parallel'); $parallel = 1 unless defined $parallel; $self->draw_parallel(@_) if $parallel; $self->draw_perpendicular(@_) unless $parallel; $self->draw_label(@_) if ($self->option('label')); } sub draw_perpendicular { my $self = shift; my $gd = shift; my ($dx,$dy) = @_; my ($x1,$y1,$x2,$y2) = $self->bounds(@_); my $ne = $self->option('northeast'); my $sw = $self->option('southwest'); $ne = $sw = 1 unless defined($ne) || defined($sw); # draw a perpendicular arrow at position indicated by $x1 my $fg = $self->set_pen; my $a2 = ($y2-$y1)/4; my @positions = $x1 == $x2 ? ($x1) : ($x1,$x2); for my $x (@positions) { if ($ne) { $gd->line($x,$y1,$x,$y2,$fg); $gd->line($x-$a2,$y1+$a2,$x,$y1,$fg); $gd->line($x+$a2,$y1+$a2,$x,$y1,$fg); } if ($sw) { $gd->line($x,$y1,$x,$y2,$fg); $gd->line($x-$a2,$y2-$a2,$x,$y2,$fg); $gd->line($x+$a2,$y2-$a2,$x,$y2,$fg); } } # add a label if requested # $self->draw_label($gd,$dx,$dy) if ($self->option('label') && !$self->option('ruler')); # this draws the label aligned to the left } sub draw_parallel { my $self = shift; my $gd = shift; my ($dx,$dy) = @_; my ($x1,$y1,$x2,$y2) = $self->bounds(@_); my $fg = $self->set_pen; my $a2 = ($self->height)/2; my $center = $y1+$a2; $x1 = $self->panel->left if $x1 < $self->panel->left; $x2 = $self->panel->right if $x2 > $self->panel->right; my ($sw,$ne,$base_w,$base_e) = $self->arrowheads; $gd->line($x1,$center,$x2,$center,$fg); $self->arrowhead($gd,$x1,$center,$a2,-1) if $sw; # west arrow $self->arrowhead($gd,$x2,$center,$a2,+1) if $ne; # east arrow $gd->line($x2,$center-$a2,$x2,$center+$a2,$fg) if $base_e; #east base $gd->line($x1,$center-$a2,$x1,$center+$a2,$fg) if $base_w; #west base # turn on ticks if ($self->option('tick')) { local $^W = 0; # dumb uninitialized variable warning my $font = $self->font; my $font_color = $self->fontcolor; my $height = $self->height; my $relative = $self->option('relative_coords'); my $start = $relative ? 1 : $self->feature->start; my $stop = $start + $self->feature->length - 1; my $offset = $relative ? $self->feature->start-1 : 0; my $reversed = $self->feature->strand < 0; my $units = $self->option('units') || ''; my $divisor = $UNITS{$units} || 1 if $units; my ($major_ticks,$minor_ticks) = $self->panel->ticks($start,$stop,$font,$divisor); ## Does the user want to override the internal scale? my $scale = $self->option('scale'); my $left = $sw ? $x1+$height : $x1; my $right = $ne ? $x2-$height : $x2; my $format = ($major_ticks->[1]-$major_ticks->[0])/($divisor||1) < 1 ? "%.1f$units" : "%d$units"; for my $i (@$major_ticks) { my $tickpos = $dx + ($reversed ? $self->map_pt($stop - $i + $offset) : $self->map_pt($i + $offset)); next if $tickpos < $left or $tickpos > $right; $gd->line($tickpos,$center-$a2,$tickpos,$center+$a2,$fg); my $label = $scale ? $i / $scale : $i; if ($units) { my $scaled = $label/$divisor; $label = sprintf($format,$scaled); } my $middle = $tickpos - $self->string_width($label)/2; $gd->string($font,$middle,$center+$a2-1,$label,$font_color) unless ($self->option('no_tick_label')); } if ($self->option('tick') >= 2) { my $a4 = $self->height/4; for my $i (@$minor_ticks) { my $tickpos = $dx + ($reversed ? $self->map_pt($stop - $i + $offset) : $self->map_pt($i + $offset)); next if $tickpos < $left or $tickpos > $right; $gd->line($tickpos,$center-$a4,$tickpos,$center+$a4,$fg); } } } # add a label if requested # $self->draw_label($gd,$dx,$dy) if ($self->option('label'); # $self->draw_description($gd,$dx,$dy) if $self->option('description'); } sub arrowheads { my $self = shift; my ($ne,$sw,$base_e,$base_w); if ($self->option('double')) { $ne = $sw = 1; } else { $ne = $self->option('northeast') || $self->option('east'); $sw = $self->option('southwest') || $self->option('west'); } # otherwise use strandedness to define the arrow unless (defined($ne) || defined($sw)) { # turn on both if neither specified $ne = 1 if $self->feature->strand > 0; $sw = 1 if $self->feature->strand < 0; } return ($sw,$ne,0,0) unless $self->option('base'); return ($sw,$ne,!$sw,!$ne); } sub draw_label { my $self = shift; my ($gd,$left,$top) = @_; my $label5 = "5'"; my $label3 = "3'"; my $relative = $self->option('relative_coords'); my $start = $relative ? 1 : $self->feature->start; my $stop = $start + $self->feature->length - 1; my $offset = $relative ? $self->feature->start-1 : 0; my $reversed = $self->feature->strand < 0; my $units = $self->option('units') || ''; my $divisor = $UNITS{$units} || 1 if $units; my ($major_ticks,$minor_ticks) = $self->panel->ticks($start,$stop,$self->font,$divisor); my $tick_scale = " ($major_ticks bp/"; $tick_scale .= ($self->option('tick') >= 2)?"major tick)":"tick)"; my $top_left_label = $label5; $top_left_label .= $tick_scale if ($self->option('no_tick_label') && $self->option('tick')); #-1 direction mean lower end is 3' (minus strand on top) ($label5, $label3) = ($label3, $label5) if ($self->option('direction') == -1); my $x = $self->left + $left; $x = $self->panel->left + 1 if $x <= $self->panel->left; my $font = $self->option('labelfont') || $self->font; $gd->string($font, $x, $self->top + $top, $top_left_label, $self->fontcolor); my $x1 = $left + $self->panel->right - $self->string_width($label3); $gd->string($font, $x1, $self->top + $top, $label3, $self->fontcolor); if ($self->option('both')) {#minus strand as well $gd->string($font, $x, $self->bottom - $self->pad_bottom + $top, $label3, $self->fontcolor); my $x1 = $left + $self->panel->right - $self->string_width($label5); $gd->string($font, $x1, $self->bottom - $self->pad_bottom + $top, $label5, $self->fontcolor); } } 1; __END__ =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This glyph draws arrows. Label, if requested, will be 5' and 3' at both ends and tick scale is printed if no_tick_label option is set and tick option set. Depending on options, the arrows can be labeled, be oriented vertically or horizontally, or can contain major and minor ticks suitable for use as a scale. =head2 OPTIONS In addition to the common options, the following glyph-specific options are recognized: Option Description Default ------ ----------- ------- -tick Whether to draw major 0 and minor ticks. 0 = no ticks 1 = major ticks 2 = minor ticks -label 5' at start, 3' at end 0 above arrow -both 5', 3' above, 0 and 3', 5' below arrow -direction 0 = ruler is plus strand 0 -1 = ruler is minus strand -parallel Whether to draw the arrow true parallel to the sequence or perpendicular to it. -northeast Force a north or east true arrowhead(depending on orientation) -east synonym of above -southwest Force a south or west true arrowhead(depending on orientation) -west synonym of above -double force-doubleheaded arrow -base Draw a vertical base at the false non-arrowhead side -scale Reset the labels on the arrow false to reflect an externally established scale. Set -parallel to false to display a point-like feature such as a polymorphism, or to indicate an important location. If the feature start == end, then the glyph will draw a single arrow at the designated location: ^ | Otherwise, there will be two arrows at the start and end: ^ ^ | | Scale: Pass in a externally established scale to reset the labels on the arrow. This is particularly useful for manually constructed images where the founding parameters of the panel are not 1-based. For example, a genetic map interval ranging from 0.1 - 0.3 can be constructed by first multiplying every value by 100. Passing arrow(-scale=>100); will draw tick marks labelled appropriately to your external scale. =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Shengqiang Shu Esshu@bdgp.lbl.govE Lincoln Stein Elstein@cshl.orgE. Copyright (c) 2001 BDGP, Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/ideogram.pm000555001750001750 5047712165075746 22334 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::ideogram; # Glyph to draw chromosome ideograms use strict qw/vars refs/; use vars '@ISA'; use GD; use base qw(Bio::Graphics::Glyph::generic Bio::Graphics::Glyph::heat_map); sub my_description { return < [ 'string', ' gneg:white gpos25:silver gpos50:gray gpos:gray gpos75:darkgray gpos100:black acen:cen gvar:var', 'This option is redefined to map each chromosome band\'s "stain" attribute', 'into a color or pattern. The default value is saying to use ', '"white" for features whose stain attribute is', '"gneg", "silver" for those whose stain attribute is "gpos25", and so', 'on. Several special values are recognized: "B" draws a narrower', 'gray region and is usually used to indicate an acrocentric', 'stalk. "B" creates a diagonal black-on-white pattern if B<-pattern> is enabled.', '"B" draws a centromere.', 'If -bgcolor is just a color name, like "yellow", the glyph will ignore', 'all bands and just draw a filled in chromosome.'], bgfallback => [ 'color', 'yellow', 'Color to use when no bands are present.'], pattern => [ 'boolean', undef, 'Enable drawing a vertical line pattern for centromeres and "var" regions.', 'This is off by default due to an intermittent gd2 library crash on certain 64-bit platforms.'], } } sub demo_feature { my $self = shift; my $data = <can('new'); my $db = Bio::Graphics::FeatureFile->new(-text=>$data) or die; return $db->get_features_by_name('Chr22'); } sub bgfallback { my $self = shift; return $self->option('bgfallback') || 'yellow'; } sub bgcolor { my $self = shift; my $bgcolor = $self->option('bgcolor'); return $bgcolor if defined $bgcolor; return 'gneg:white gpos25:silver gpos50:gray gpos:gray gpos75:darkgray gpos100:black acen:cen gvar:var'; } sub can_pattern { my $self = shift; return unless $self->option('pattern'); return $self->panel->image_class !~ /svg/i; } sub draw { my $self = shift; my ($gd,$left,$top,$partno,$total_parts) = @_; my $fstart = $self->feature->start; my $fstop = $self->feature->end; my @parts = $self->parts; # Draw the sides for the whole chromosome (in case # there are missing data). $self->draw_component(@_) if $self->level == 0; if (@parts) { $left += $self->left + $self->pad_left; $top += $self->top + $self->pad_top; } else { @parts = ($self); } # Make unaggregated bands invisible if requested. # This is for making image maps for individual # bands of whole aggregate chromosomes. $self->{invisible} ||= $self->option('invisible') unless @parts > 1; $parts[0]->{single}++ if @parts == 1; # if the bands are subfeatures of an aggregate chromosome, # we can draw the centomere and telomeres last to improve # the appearance my @last; for my $part (@parts) { push @last, $part and next if $part->feature->primary_tag =~ /centromere/i || $part->feature->start <= $fstart || $part->feature->end >= $fstop; my $tile = $part->create_tile('left'); $part->draw_component($gd,$left,$top); } for my $part (@last) { my $tile; if ($part->feature->method =~ /centromere/) { $tile = $self->create_tile('right'); } else { $tile = $part->create_tile('left'); } my $status = $part->{single} ? 'single' : $part->feature->method =~ /centromere/ ? 'centromere' : $part->feature->start <= $fstart ? 'left telomere' : $part->feature->end >= $fstop ? 'right telomere' : undef; $part->draw_component($gd,$left,$top,$status); } $self->draw_label(@_) if $self->option('label'); $self->draw_description(@_) if $self->option('description'); } sub draw_component { my $self = shift; my $gd = shift; my ($x,$y,$status) = @_; my $feat = $self->feature; my $arcradius = $self->option('arcradius') || 7; my ($x1, $y1, $x2, $y2 ) = $self->bounds(@_); return if $x2 <= $self->panel->left; return if $x1 >= $self->panel->right; $x2 = $self->panel->right if $x2 > $self->panel->right; # force odd width so telomere arcs are centered $y2 ++ if ($y2 - $y1) % 2; my ($stain) = $feat->get_tag_values('stain'); ($stain) = $feat->get_tag_values('Stain') unless $stain; # Some genome sequences don't contain substantial telomere sequence (i.e. Arabidopsis) # We can suggest their presence at the tips of the chromosomes by setting fake_telomeres = 1 # in the configuration file, resulting in the tips of the chromosome being painted black. my $fake_telomeres = $self->option('fake_telomeres') || 0; my $bgcolor_index = $self->bgcolor; if ((my $fallback = $self->bgfallback) && !$stain) { $bgcolor_index = $fallback; } elsif ($bgcolor_index =~ /\w+:/) { ($bgcolor_index) = $self->bgcolor =~ /$stain:(\S+)/ if $stain; ($bgcolor_index,$stain) = qw/white none/ if !$stain; } my $black = $gd->colorAllocate( 0, 0, 0 ); my $cm_color = $self->{cm_color} ||= $self->cm_color; my $var_color = $self->{var_color} ||= $self->var_color; my $bgcolor = $self->factory->translate_color($bgcolor_index); my $fgcolor = $self->fgcolor; # special color for gvar bands if ( $bgcolor_index =~ /var/) { $bgcolor = $self->can_pattern ? gdTiled : $var_color; } if ( $feat->method !~ /centromere/i && $stain ne 'acen') { # are we at the end of the chromosome? if (($status eq 'single' || $status eq 'left telomere') && $stain ne 'tip') { # left telomere my $state = $status eq 'single' ? -1 : $self->panel->flip ? 0 : 1; $bgcolor = $black if $fake_telomeres; $self->draw_telomere( $gd, $x1, $y1, $x2, $y2, $bgcolor, $fgcolor, $arcradius, $state ); } elsif ($status eq 'right telomere' && $stain ne 'tip') { # right telomere my $state = $self->panel->flip ? 1 : 0; $bgcolor = $black if $fake_telomeres; $self->draw_telomere( $gd, $x1, $y1, $x2, $y2, $bgcolor, $fgcolor, $arcradius, $state ); } # or a stalk? elsif ( $stain eq 'stalk') { $self->draw_stalk( $gd, $x1, $y1, $x2, $y2, $bgcolor, $fgcolor ); } # or a regular band? else { $self->draw_cytoband( $gd, $x1, $y1, $x2, $y2, $bgcolor, $fgcolor ); $self->draw_outline( $gd,$x1,$y1,$x2,$y2,$bgcolor,$fgcolor) if $bgcolor_index =~ /var/i; } } # or a centromere? else { if ( $self->can_pattern ) { my $tile = $self->create_tile('right'); $self->draw_centromere( $gd, $x1, $y1, $x2, $y2, gdTiled, $fgcolor ); } else { $self->draw_centromere( $gd, $x1, $y1, $x2, $y2, $cm_color, $fgcolor ); } } } sub draw_cytoband { my $self = shift; my ( $gd, $x1, $y1, $x2, $y2, $bgcolor, $fgcolor) = @_; # draw the filled box $self->filled_box($gd,$x1,$y1,$x2,$y2,$bgcolor,$bgcolor); # outer border $gd->line($x1,$y1,$x2,$y1,$fgcolor); $gd->line($x1,$y2,$x2,$y2,$fgcolor); } sub draw_outline { my $self = shift; my ( $gd, $x1, $y1, $x2, $y2, $bgcolor, $fgcolor) = @_; # side borders $gd->line($x1,$y1,$x1,$y2,$fgcolor); $gd->line($x2,$y1,$x2,$y2,$fgcolor); } sub draw_centromere { my $self = shift; my ( $gd, $x1, $y1, $x2, $y2, $bgcolor, $fgcolor ) = @_; # blank slate $self->wipe(@_); # draw a sort of hour-glass shape to represent the centromere my $poly = GD::Polygon->new; $poly->addPt( $x1, $y1 ); $poly->addPt( $x1, $y2 ); $poly->addPt( $x2, $y1 ); $poly->addPt( $x2, $y2 ); $gd->filledPolygon( $poly, $bgcolor ); # filled $gd->line( $x2 - 1, $y1 + 1, $x2 - 1, $y2 - 1, $fgcolor ); $gd->polygon( $poly, $fgcolor ); # outline } sub draw_telomere { my $self = shift; my ($gd, $x1, $y1, $x2, $y2, $bgcolor, $fgcolor, $arcradius, $state ) = @_; # blank slate $self->wipe(@_); # For single, unaggregated bands, make the terminal band # a bit wider to accomodate the arc if ($self->{single}) { $x1 -= 5 if $state == 1; $x2 += 5 if $state == 0; } # state should be one of: # 0 right telomere # 1 left telomere # -1 round at both ends (whole chromosome) my $outline++ if $state == -1; my $arcsize = $y2 - $y1; my $bwidth = $x2 - $x1; my $new_x1 = $x1 + $arcradius - 1; my $new_x2 = $x2 - $arcradius; my $new_y = $y1 + int($arcsize/2 + 0.5); my $orange = $self->panel->translate_color('lemonchiffon'); my $bg = $self->panel->bgcolor; $self->draw_cytoband( $gd, $x1, $y1, $x2, $y2, $bgcolor, $fgcolor ); $self->draw_outline( $gd, $x1, $y1, $x2, $y2, $bgcolor, $fgcolor ); if ( $state ) { # left telomere my $x = $new_x1; my $y = $new_y; # erase extra stuff $gd->line($x1,$y1,$x1+5,$y1,$bg); $gd->line($x1,$y1,$x1,$y2,$bg); $gd->line($x1,$y2,$x1+5,$y2,$bg); $gd->arc( $x, $y, $arcradius * 2, $arcsize, 90, 270, $fgcolor); # erase off-target colors $gd->fill($x1+1,$y1+1,$bg); $gd->fill($x1+1,$y2-1,$bg); } if ( $state < 1 ) { # right telomere my $x = $new_x2; my $y = $new_y; # erase extra stuff $gd->line($x2-5,$y1,$x2,$y1,$bg); $gd->line($x2,$y1,$x2,$y2,$bg); $gd->line($x2-5,$y2,$x2,$y2,$bg); $gd->arc( $x, $y, $arcradius * 2, $arcsize, 270, 90, $fgcolor); # erase off-target colors $gd->fill($x2-1,$y1+1,$bg); $gd->fill($x2-1,$y2-1,$bg); } unless ( $self->can_pattern ) { $self->draw_cytoband( $gd, $new_x1 - 1, $y1 + 2, $new_x1 + 1, $y2 - 2, $bgcolor, $bgcolor ); } } # for acrocentric stalk structure, draw a narrower cytoband sub draw_stalk { my $self = shift; my ( $gd, $x1, $y1, $x2, $y2, $bgcolor, $fgcolor, $inset ) = @_; # blank slate $self->wipe(@_); my $height = $self->height; $inset ||= $height > 10 ? int( $height / 10 + 0.5 ) : 2; $_[2] += $inset; $_[4] -= $inset; $self->draw_cytoband(@_); $gd->line( $x1, $y1, $x1, $y2, $fgcolor ); $gd->line( $x2, $y1, $x2, $y2, $fgcolor ); } sub create_tile { my $self = shift; my $direction = shift; my $gd = $self->panel->gd; return unless $gd->can('setTile'); # Prepare tile to use for filling an area my $tile; if ( $direction eq 'right' ) { $tile = GD::Image->new(3,3); my $black = $tile->colorAllocate(0,0,0); my $white = $tile->colorAllocate(255,255,255); $tile->filledRectangle(0, 0, 3, 3, $white); $tile->line( 0, 0, 3, 3, $black); } elsif ( $direction eq 'left' ) { $tile = GD::Image->new(4,4); my $black = $tile->colorAllocate(0,0,0); my $white = $tile->colorAllocate(255,255,255); $tile->filledRectangle(0,0,4,4, $white); $tile->line( 4, 0, 0, 4, $black); } $gd->setTile($tile); return $tile; } # This overrides the Glyph::parts method until I # can figure out how the bands get mangled there sub parts { my $self = shift; my $f = $self->feature; my $level = $self->level + 1; my @subf = sort {$a->start <=> $b->start} $f->get_SeqFeatures; return $self->factory->make_glyph($level,@subf); } # erase anthing that might collide. This is for # clean telomeres, centromeres and stalks sub wipe { my $self = shift; my $whitewash = $self->panel->bgcolor; $self->filled_box(@_[0..4],$whitewash,$whitewash); } # Disable bumping entirely, since it messes up the ideogram sub bump { return 0; } sub cm_color { my $self = shift; my $bgcolor = $self->bgcolor; my ($c) = $bgcolor =~ /cen:(\S+)/; $c ||= 'lightgrey'; return $self->translate_color($c); } sub var_color { my $self = shift; my $bgcolor = $self->bgcolor; my ($c) = $bgcolor =~ /var:(\S+)/; $c ||= '#805080'; return $self->translate_color($c); } 1; __END__ =head1 NAME Bio::Graphics::Glyph::ideogram - The "ideogram" glyph =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This glyph draws a section of a chromosome ideogram. It relies on certain data from the feature to determine which color should be used (stain) and whether the segment is a telomere or centromere or a regular cytoband. The centromeres and 'var'-marked bands are rendered with diagonal black-on-white patterns if the "-patterns" option is true, otherwise they are rendered in dark gray. This is to prevent a libgd2 crash on certain 64-bit platforms when rendering patterned images. The cytobandband features would typically be formatted like this in GFF3: ... ChrX UCSC cytoband 136700001 139000000 . . . Parent=ChrX;Name=Xq27.1;Alias=ChrXq27.1;stain=gpos75; ChrX UCSC cytoband 139000001 140700000 . . . Parent=ChrX;Name=Xq27.2;Alias=ChrXq27.2;stain=gneg; ChrX UCSC cytoband 140700001 145800000 . . . Parent=ChrX;Name=Xq27.3;Alias=ChrXq27.3;stain=gpos100; ChrX UCSC cytoband 145800001 153692391 . . . Parent=ChrX;Name=Xq28;Alias=ChrXq28;stain=gneg; ChrY UCSC cytoband 1 1300000 . . . Parent=ChrY;Name=Yp11.32;Alias=ChrYp11.32;stain=gneg; which in this case is a GFF-ized cytoband coordinate file from UCSC: http://hgdownload.cse.ucsc.edu/goldenPath/hg16/database/cytoBand.txt.gz and the corresponding GBrowse config options would be like this to create an ideogram overview track for the whole chromosome: The 'chromosome' feature below would aggregated from bands and centromere using the default chromosome aggregator [CYT:overview] feature = chromosome glyph = ideogram fgcolor = black bgcolor = gneg:white gpos25:silver gpos50:gray gpos:gray gpos75:darkgray gpos100:black acen:cen gvar:var arcradius = 6 height = 25 bump = 0 label = 0 A script to reformat UCSC annotations to GFF3 format can be found at the end of this documentation. =head2 OPTIONS The following options are standard among all Glyphs. See L for a full explanation. Option Description Default ------ ----------- ------- -fgcolor Foreground color black -outlinecolor Synonym for -fgcolor -linewidth Line width 1 -height Height of glyph 10 -font Glyph font gdSmallFont -connector Connector type 0 (false) -connector_color Connector color black -label Whether to draw a label 0 (false) -description Whether to draw a description 0 (false) The following options are specific to the ideogram glyph. Option Description Default ------ ----------- ------- -bgcolor Band coloring string none -bgfallback Coloring to use when no bands yellow are present B<-bgcolor> is used to map each chromosome band's "stain" attribute into a color or pattern. It is a string that looks like this: gneg:white gpos25:silver gpos50:gray \ gpos:gray gpos75:darkgray gpos100:black acen:cen gvar:var This is saying to use "white" for features whose stain attribute is "gneg", "silver" for those whose stain attribute is "gpos25", and so on. Several special values are recognized: "B" draws a narrower gray region and is usually used to indicate an acrocentric stalk. "B" creates a diagonal black-on-white pattern. "B" draws a centromere. If -bgcolor is just a color name, like "yellow", the glyph will ignore all bands and just draw a filled in chromosome. If -bgfallback is set to a color name or value, then the glyph will fall back to the indicated background color if the chromosome contains no bands. =head1 UCSC TO GFF CONVERSION SCRIPT The following short script can be used to convert a UCSC cytoband annotation file into GFF format. If you have the lynx web-browser installed you can call it like this in order to download and convert the data in a single operation: fetchideogram.pl http://hgdownload.cse.ucsc.edu/goldenPath/hg18/database/cytoBand.txt.gz Otherwise you will need to download the file first. Note the difference between this script and input data from previous versions of ideogram.pm: UCSC annotations are used in place of NCBI annotations. #!/usr/bin/perl use strict; my %stains; my %centros; my %chrom_ends; foreach (@ARGV) { if (/^(ftp|http|https):/) { $_ = "lynx --dump $_ |gunzip -c|"; } elsif (/\.gz$/) { $_ = "gunzip -c $_ |"; } print STDERR "Processing $_\n"; } print "##gff-version 3\n"; while(<>) { chomp; my($chr,$start,$stop,$band,$stain) = split /\t/; $start++; $chr = ucfirst($chr); if(!(exists($chrom_ends{$chr})) || $chrom_ends{$chr} < $stop) { $chrom_ends{$chr} = $stop; } my ($arm) = $band =~ /(p|q)\d+/; $stains{$stain} = 1; if ($stain eq 'acen') { $centros{$chr}->{$arm}->{start} = $stop; $centros{$chr}->{$arm}->{stop} = $start; next; } $chr =~ s/chr//i; print qq/$chr\tUCSC\tcytoband\t$start\t$stop\t.\t.\t.\tParent=$chr;Name=$chr;Alias=$chr$band;stain=$stain;\n/; } foreach my $chr(sort keys %chrom_ends) { my $chr_orig = $chr; $chr =~ s/chr//i; print qq/$chr\tUCSC\tcentromere\t$centros{$chr_orig}->{p}->{stop}\t$centros{$chr_orig}->{q}->{start}\t.\t+\t.\tParent=$chr;Name=$chr\_cent\n/; } =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Gudmundur A. Thorisson Emummi@cshl.eduE Copyright (c) 2001-2006 Cold Spring Harbor Laboratory =head1 CONTRIBUTORS Sheldon McKay Emckays@cshl.edu This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/cross.pm000555001750001750 33612165075746 21623 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::cross; use strict; use base qw(Bio::Graphics::Glyph::crossbox); sub my_description { "This is identical to the crossbox glyph and is present here for ". "DAS compatibility."; } 1; Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/wiggle_density.pm000555001750001750 4704412165075746 23556 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::wiggle_density; use strict; use base qw(Bio::Graphics::Glyph::wiggle_data Bio::Graphics::Glyph::box Bio::Graphics::Glyph::smoothing Bio::Graphics::Glyph::xyplot ); sub my_description { return <export_to_wif(). coverage-- a simple comma-delimited string containing the quantitative values, assumed to be one value per pixel. END } sub my_options { { basedir => [ 'string', undef, 'If a relative path is used for "wigfile", then this option provides', 'the base directory on which to resolve the path.' ], z_score_bounds => [ 'integer', 4, 'When using z_score autoscaling, this option controls how many standard deviations', 'above and below the mean to show.' ], autoscale => [ ['local','chromosome','global','z_score','clipped_global'], 'clipped_global', 'If set to "global" , then the minimum and maximum values of the XY plot', 'will be taken from the wiggle file as a whole. If set to "chromosome", then', 'scaling will be to minimum and maximum on the current chromosome.', '"clipped_global" is similar to "global", but clips the top and bottom values', 'to the multiples of standard deviations indicated by "z_score_bounds"', 'If set to "z_score", then the whole plot will be rescaled to z-scores in which', 'the "0" value corresponds to the mean across the genome, and the units correspond', 'to standard deviations above and below the mean. The number of SDs to show are', 'controlled by the "z_score_bound" option.', 'Otherwise, the plot will be', 'scaled to the minimum and maximum values of the region currently on display.', 'min_score and max_score override autoscaling if one or both are defined' ], graph_type => [ undef, undef, 'Unused option', ], }; } sub pad_top { my $self = shift; my $overlap = $self->bump eq 'overlap'; return $overlap ?$self->Bio::Graphics::Glyph::xyplot::pad_top(@_) :$self->SUPER::pad_top(@_); } sub draw { my $self = shift; my ($gd,$dx,$dy) = @_; my $retval = $self->SUPER::draw(@_); if ($retval) { $self->draw_label(@_) if $self->option('label'); $self->draw_description(@_) if $self->option('description'); $self->panel->endGroup($gd); return $retval; } else { return $self->Bio::Graphics::Glyph::box::draw(@_); } } sub draw_coverage { my $self = shift; my $feature = shift; my $array = shift; if (! $array || ref($array) ne 'ARRAY'){ unshift(@_,$array); my @arr = (eval{$feature->get_tag_values('coverage')}); $array = $arr[0]; } else { $array = [split ',',$array] unless ref $array; } return unless @$array; my ($gd,$left,$top) = @_; my ($start,$end) = $self->effective_bounds($feature); my $length = $end - $start + 1; my $bases_per_bin = ($end-$start)/@$array; my @parts; my $samples = $length < $self->panel->width ? $length : $self->panel->width; my $samples_per_base = $samples/$length; for (my $i=0;$i<$samples;$i++) { my $offset = $i/$samples_per_base; my $v = $array->[$offset/$bases_per_bin]; push @parts,$v; } my ($x1,$y1,$x2,$y2) = $self->bounds($left,$top); $self->draw_segment($gd, $start,$end, \@parts, $start,$end, 1,1, $x1,$y1,$x2,$y2); } sub draw_segment { my $self = shift; my ($gd, $start,$end, $seg_data, $seg_start,$seg_end, $step,$span, $x1,$y1,$x2,$y2) = @_; # clip, because wig files do no clipping $seg_start = $start if $seg_start < $start; $seg_end = $end if $seg_end > $end; # figure out where we're going to start my $scale = $self->scale; # pixels per base pair my $pixels_per_span = $scale * $span + 1; my $pixels_per_step = 1; my $length = $end-$start+1; # if the feature starts before the data starts, then we need to draw # a line indicating missing data (this only happens if something went # wrong upstream) if ($seg_start > $start) { my $terminus = $self->map_pt($seg_start); $start = $seg_start; $x1 = $terminus; } # if the data ends before the feature ends, then we need to draw # a line indicating missing data (this only happens if something went # wrong upstream) if ($seg_end < $end) { my $terminus = $self->map_pt($seg_end); $end = $seg_end; $x2 = $terminus; } return unless $start < $end; # get data values across the area my $samples = $length < $self->panel->width ? $length : $self->panel->width; my $data = ref $seg_data eq 'ARRAY' ? $seg_data : $seg_data->values($start,$end,$samples); # scale the glyph if the data end before the panel does my $data_width = $end - $start; my $data_width_ratio; if ($data_width < $self->panel->length) { $data_width_ratio = $data_width/$self->panel->length; } else { $data_width_ratio = 1; } return unless $data && ref $data && @$data > 0; my $min_value = $self->min_score; my $max_value = $self->max_score; my ($min,$max,$mean,$stdev) = $self->minmax($data); unless (defined $min_value && defined $max_value) { $min_value ||= $min; $max_value ||= $max; } my $rescale = $self->option('autoscale') eq 'z_score'; my ($scaled_min,$scaled_max); if ($rescale) { my $bound = $self->z_score_bound; $scaled_min = -$bound; $scaled_max = +$bound; } else { ($scaled_min,$scaled_max) = ($min_value,$max_value); } my $t = 0; for (@$data) {$t+=$_} # allocate colors # There are two ways to do this. One is a scale from min to max. The other is a # bipartite scale using one color range from zero to min, and another color range # from 0 to max. The latter behavior is triggered when the config file contains # entries for "pos_color" and "neg_color" and the data ranges from < 0 to > 0. my $poscolor = $self->pos_color || $self->fgcolor; my $negcolor = $self->neg_color || $self->bgcolor; my $data_midpoint = $self->midpoint; $data_midpoint = 0 if $rescale; my $bicolor = $poscolor != $negcolor && $scaled_min < $data_midpoint && $scaled_max > $data_midpoint; my ($rgb_pos,$rgb_neg,$rgb); if ($bicolor) { $rgb_pos = [$self->panel->rgb($poscolor)]; $rgb_neg = [$self->panel->rgb($negcolor)]; } else { $rgb = $scaled_max > $scaled_min ? ([$self->panel->rgb($poscolor)] || [$self->panel->rgb($self->bgcolor)]) : ([$self->panel->rgb($negcolor)] || [$self->panel->rgb($self->bgcolor)]); } my %color_cache; @$data = reverse @$data if $self->flip; if (@$data <= $self->panel->width) { # data fits in width, so just draw it $pixels_per_step = $scale * $step; $pixels_per_step = 1 if $pixels_per_step < 1; my $datapoints_per_base = @$data/$length; my $pixels_per_datapoint = $self->panel->width/@$data * $data_width_ratio; my %temps; map{$temps{$_}++} (@$data); my %colorss = (); for (my $i = 0; $i <= @$data ; $i++) { my $x = $x1 + $pixels_per_datapoint * $i; my $data_point = $data->[$i]; defined $data_point || next; $data_point = ($data_point-$mean)/$stdev if $rescale; $data_point = $scaled_min if $scaled_min > $data_point; $data_point = $scaled_max if $scaled_max < $data_point; my ($r,$g,$b) = $bicolor ? $data_point > $data_midpoint ? $self->calculate_color($data_point,$rgb_pos, $data_midpoint,$scaled_max) : $self->calculate_color($data_point,$rgb_neg, $data_midpoint,$scaled_min) : $self->calculate_color($data_point,$rgb, $scaled_min,$scaled_max); my $idx = $color_cache{$r,$g,$b} ||= $self->panel->translate_color($r,$g,$b); $colorss{$idx} = $data_point; $self->filled_box($gd,$x,$y1,$x+$pixels_per_datapoint,$y2,$idx,$idx); } (keys %colorss); # Alleviate a silent crash somewhere in GD that causes density graph get drawn as a solid-colored box } else { # use Sheldon's code to subsample data $pixels_per_step = $scale * $step; my $pixels = 0; # only draw boxes 2 pixels wide, so take the mean value # for n data points that span a 2 pixel interval my $binsize = 2/$pixels_per_step; my $pixelstep = $pixels_per_step; $pixels_per_step *= $binsize; $pixels_per_step *= $data_width_ratio; $pixels_per_span = 2; my $scores = 0; my $defined; for (my $i = $start; $i < $end ; $i += $step) { # draw the box if we have accumulated >= 2 pixel's worth of data. if ($pixels >= 2) { my $data_point = $defined ? $scores/$defined : 0; $scores = 0; $defined = 0; $data_point = $scaled_min if $scaled_min > $data_point; $data_point = $scaled_max if $scaled_max < $data_point; my ($r,$g,$b) = $bicolor ? $data_point > $data_midpoint ? $self->calculate_color($data_point,$rgb_pos, $data_midpoint,$scaled_max) : $self->calculate_color($data_point,$rgb_neg, $data_midpoint,$scaled_min) : $self->calculate_color($data_point,$rgb, $scaled_min,$max_value); my $idx = $color_cache{$r,$g,$b} ||= $self->panel->translate_color($r,$g,$b); $self->filled_box($gd,$x1,$y1,$x1+$pixels_per_span,$y2,$idx,$idx); $x1 += $pixels; $pixels = 0; } my $val = shift @$data; # don't include undef scores in the mean calculation # $scores is the numerator; $defined is the denominator $scores += $val if defined $val; $defined++ if defined $val; # keep incrementing until we exceed 2 pixels # the step is a fraction of a pixel, not an integer $pixels += $pixelstep; } } } sub draw_plot { my $self = shift; my $parts = shift; my ($gd,$dx,$dy) = @_; my $x_scale = $self->scale; my $panel_start = $self->panel->start; my $feature = $self->feature; my $f_start = $feature->start > $panel_start ? $feature->start : $panel_start; my ($left,$top,$right,$bottom) = $self->calculate_boundaries($dx,$dy); # There is a minmax inherited from xyplot as well as wiggle_data, and I don't want to # rely on Perl's multiple inheritance DFS to find the right one. my ($min_score,$max_score,$mean,$stdev) = $self->minmax($parts); my $rescale = $self->option('autoscale') eq 'z_score'; my ($scaled_min,$scaled_max); if ($rescale) { $scaled_min = int(($min_score-$mean)/$stdev + 0.5); $scaled_max = int(($max_score-$mean)/$stdev + 0.5); my $bound = $self->z_score_bound; $scaled_max = $bound if $scaled_max > $bound; $scaled_min = -$bound if $scaled_min < -$bound; } else { ($scaled_min,$scaled_max) = ($min_score,$max_score); } my $pivot = $self->bicolor_pivot; my $positive = $self->pos_color; my $negative = $self->neg_color; my $midpoint = $self->midpoint; my ($rgb_pos,$rgb_neg,$rgb); if ($pivot) { $rgb_pos = [$self->panel->rgb($positive)]; $rgb_neg = [$self->panel->rgb($negative)]; } else { $rgb = $scaled_max > $scaled_min ? ([$self->panel->rgb($positive)] || [$self->panel->rgb($self->bgcolor)]) : ([$self->panel->rgb($negative)] || [$self->panel->rgb($self->bgcolor)]); } my %color_cache; my $flip = $self->{flip}; $self->panel->startGroup($gd); foreach (@$parts) { my ($start,$end,$score) = @$_; $score = ($score-$mean)/$stdev if $rescale; $score = $scaled_min if $scaled_min > $score; $score = $scaled_max if $scaled_max < $score; my $x1 = $left + ($start - $f_start) * $x_scale; my $x2 = $left + ($end - $f_start) * $x_scale; if ($flip) { $x1 = $right - ($x1-$left); $x2 = $right - ($x2-$left); ($x1,$x2) = ($x2,$x1); } my ($r,$g,$b) = $pivot ? ($score > $midpoint ? $self->calculate_color($score,$rgb_pos, $midpoint,$scaled_max) : $self->calculate_color($score,$rgb_neg, $midpoint,$scaled_min) ) : $self->calculate_color($score,$rgb, $scaled_min,$scaled_max); my $idx = $color_cache{$r,$g,$b} ||= $self->panel->translate_color($r,$g,$b); $self->filled_box($gd,$x1,$top,$x2,$bottom,$idx,$idx); } return 1; } sub _draw_coverage { my $self = shift; my $feature = shift; my $array = shift; $array = [split ',',$array] unless ref $array; return unless @$array; my ($start,$end) = $self->effective_bounds($feature); my $bases_per_bin = ($end-$start)/@$array; my $pixels_per_base = $self->scale; my @parts; for (my $pixel=0;$pixel<$self->width;$pixel++) { my $offset = $pixel/$pixels_per_base; my $s = $start + $offset; my $e = $s+1; # fill in gaps my $v = $array->[$offset/$bases_per_bin]; push @parts,[$s,$s,$v]; } $self->Bio::Graphics::Glyph::wiggle_density::draw_plot(\@parts,@_); } sub calculate_color { my $self = shift; my ($s,$rgb,$min_score,$max_score) = @_; $s ||= $min_score; return (255,255,255) if $max_score <= $min_score; # avoid div by zero my $relative_score = ($s-$min_score)/($max_score-$min_score); $relative_score = 0 if $relative_score < 0; $relative_score = 1 if $relative_score > 1; return map { int(255 - (255-$_) * $relative_score) } @$rgb; } sub min { $_[0] < $_[1] ? $_[0] : $_[1] } sub max { $_[0] > $_[1] ? $_[0] : $_[1] } sub record_label_positions { my $self = shift; my $rlp = $self->option('record_label_positions'); return $rlp if defined $rlp; return 1; } sub draw_label { shift->Bio::Graphics::Glyph::xyplot::draw_label(@_); } 1; __END__ =head1 NAME Bio::Graphics::Glyph::wiggle_density - A density plot compatible with dense "wig"data =head1 SYNOPSIS See and . =head1 DESCRIPTION This glyph works like the regular density but takes value data in Bio::Graphics::Wiggle file format: reference = chr1 ChipCHIP Feature1 1..10000 wigfile=./test.wig;wigstart=0 ChipCHIP Feature2 10001..20000 wigfile=./test.wig;wigstart=656 ChipCHIP Feature3 25001..35000 wigfile=./test.wig;wigstart=1312 The "wigfile" attribute gives a relative or absolute pathname to a Bio::Graphics::Wiggle format file. The optional "wigstart" option gives the offset to the start of the data. If not specified, a linear search will be used to find the data. The data consist of a packed binary representation of the values in the feature, using a constant step such as present in tiling array data. =head2 OPTIONS The same as the regular graded_segments glyph, except that the following options are recognized: Name Value Description ---- ----- ----------- basedir path Path to be used to resolve "wigfile" and "densefile" tags giving relative paths. Default is to use the current working directory. Absolute wigfile & densefile paths will not be changed. autoscale "local" or "global" If one or more of min_score and max_score options are absent, then these values will be calculated automatically. The "autoscale" option controls how the calculation is done. The "local" value will scale values according to the minimum and maximum values present in the window being graphed. "global" will use chromosome-wide statistics for the entire wiggle or dense file to find min and max values. smoothing method name Smoothing method: one of "mean", "max", "min" or "none" smoothing_window integer Number of values across which data should be smoothed. bicolor_pivot name Where to pivot the two colors when drawing bicolor plots. Options are "mean" and "zero". A numeric value can also be provided. pos_color color When drawing bicolor plots, the fill color to use for values that are above the pivot point. neg_color color When drawing bicolor plots, the fill color to use for values that are below the pivot point. =head2 SPECIAL FEATURE TAGS The glyph expects one or more of the following tags (attributes) in feature it renders: Name Value Description ---- ----- ----------- wigfile path name Path to the Bio::Graphics::Wiggle file for vales. (required) densefile path name Path to a Bio::Graphics::DenseFeature object (deprecated) denseoffset integer Integer offset to where the data begins in the Bio::Graphics::DenseFeature file (deprecated) densesize integer Integer size of the data in the Bio::Graphics::DenseFeature file (deprecated) =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Lincoln Stein Esteinl@cshl.eduE. Copyright (c) 2007 Cold Spring Harbor Laboratory This package and its accompanying libraries is free software; you can redistribute it and/or modify it under the terms of the GPL (either version 1, or at your option, any later version) or the Artistic License 2.0. Refer to LICENSE for the full license text. In addition, please see DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/ex.pm000555001750001750 635312165075746 21133 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::ex; # Non object-oriented utilities used here-and-there in Bio::Graphics modules =head1 NAME Bio::Graphics::Glyph::ex - the "ex", or "crossed box" glyph =cut use strict; use base 'Bio::Graphics::Glyph::generic'; sub my_description { return <fgcolor; my ($left,$top) = @_; my($x1,$y1,$x2,$y2) = $self->bounds(@_); #if widthless if($self->option('point')){ my $arm = int($self->height/2); my $minx = $x2 > $x1 ? $x1 : $x2; my $centerx = abs($x2 - $x1) + $minx; my $miny = $y2 > $y1 ? $y1 : $y2; my $centery = abs($y2 - $y1) + $miny; $gd->line($centerx-$arm, $centery-$arm, $centerx+$arm, $centery+$arm, $fg); $gd->line($centerx-$arm, $centery+$arm, $centerx+$arm, $centery-$arm, $fg); return; } else { $gd->line($x1,$y1,$x2,$y2,$fg); $gd->line($x1,$y2,$x2,$y1,$fg); } } 1; __END__ =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This is a box with an 'X' inside glyph. =head2 OPTIONS The following options are standard among all Glyphs. See L for a full explanation. Option Description Default ------ ----------- ------- -fgcolor Foreground color black -outlinecolor Synonym for -fgcolor -bgcolor Background color turquoise -fillcolor Synonym for -bgcolor -linewidth Line width 1 -height Height of glyph 10 -font Glyph font gdSmallFont -connector Connector type 0 (false) -connector_color Connector color black -label Whether to draw a label 0 (false) -description Whether to draw a description 0 (false) -hilite Highlight color undef (no color) =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Allen Day Eday@cshl.orgE. Copyright (c) 2001 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/scale.pm000555001750001750 477712165075746 21616 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::scale; use strict; use base qw(Bio::Graphics::Glyph::segmented_keyglyph Bio::Graphics::Glyph::xyplot); sub my_description { return <calculate_boundaries($dx,$dy); $self->panel->startGroup($gd); my $max_score = $self->max_score || 100; my $min_score = $self->min_score || 0; $max_score = Bio::Graphics::Glyph::xyplot::max10($max_score); $min_score = Bio::Graphics::Glyph::xyplot::min10($min_score); my $height = $bottom - $top; my $scale = $max_score > $min_score ? $height/($max_score-$min_score) : 1; my $x = $left; my $y = $top + $self->pad_top; # position of "0" on the scale my $y_origin = $min_score <= 0 ? $bottom - (0 - $min_score) * $scale : $bottom; $y_origin = $top if $max_score < 0; $self->panel->startGroup($gd); $self->_draw_scale($gd,$scale,$min_score,$max_score,$dx,$dy,$y_origin); $self->panel->endGroup($gd); } sub _determine_side { my $self = shift; return 'three'; } # Added pad_top subroutine (pad_top of Glyph.pm, which is called when executing $self->pad_top # returns 0, so we need to override it here) sub pad_top { my $self = shift; my $pad = $self->Bio::Graphics::Glyph::generic::pad_top(@_); if ($pad < ($self->font('gdTinyFont')->height)) { $pad = $self->font('gdTinyFont')->height; # extra room for the scale } $pad; } sub pad_left { my $self = shift; my $pad = $self->SUPER::pad_left(@_); return $pad unless $self->option('variance_band'); $pad += length('+1sd')/2 * $self->font('gdTinyFont')->width+3; return $pad; } sub new { my $self = shift; return $self->SUPER::new(@_,-level=>-1); } 1; __END__ =head1 NAME Bio::Graphics::Glyph::scale - The "scale" glyph =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This glyph is used internally by GBrowse to draw a scale bar. It should not be used explicitly. =head1 BUGS Please report them. =head1 SEE ALSO L, =head1 AUTHOR Copyright (c) 2010 Ontario Institute for Cancer Research This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/phylo_align.pm000555001750001750 10376312165075746 23067 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::phylo_align; use strict; use base qw(Bio::Graphics::Glyph::generic Bio::Graphics::Glyph::xyplot); use Bio::TreeIO; use Bio::Graphics::Wiggle; use POSIX qw(log10); use Carp 'croak','cluck'; use Data::Dumper; my %complement = (g=>'c',a=>'t',t=>'a',c=>'g',n=>'n', G=>'C',A=>'T',T=>'A',C=>'G',N=>'N'); # turn off description sub description { 0 } # turn off label # sub label { 1 } sub height { my $self = shift; my $font = $self->mono_font; #adjust the space to take if conservation scores are drawn instead if (! $self->dna_fits) { my $species_spacing_score = $self->option('species_spacing_score') || 5; $self->factory->set_option('species_spacing', $species_spacing_score); } my $species_spacing = $self->option('species_spacing') || 1; #Height = NumSpecies x Spacing/species x FontHeight my $height = ($self->known_species + $self->unknown_species + 1) * $species_spacing * $font->height; #use this if you want to show only those species that have alignments in the viewing window #$height = ($self->extract_features + 1) * 2 * $font->height; $self->factory->set_option('height', $height); return $height; } ##### # TODO: extract the wigfiles covering the range as well ##### # get all features within the viewing window sub extract_features { my $self = shift; #my $segment = $self->feature->{'factory'}->segment($self->feature->refseq, # $self->feature->start => $self->feature->stop); #my @match = $segment->features('submatch:pa'); my @match = $self->feature->features('submatch:pa'); warn $self->feature->features; #print "Match has ",$#match,"

\n"; # exract wifiles here too: my @wmatch = $self->feature->features('wfile:pa'); #push @match, $self->feature->features('wfile:pa'); # print "xxxxxx

",Dumper(@wmatch),"
cccccc"; #print "WMatch has ",$#wmatch,"

\n"; #print "Halfwaypoint

"; #for my $feature (@match,@wmatch) { #my %attributes = $feature->attributes; #my $species = $attributes{'species'}; #print "

Feature $species:\n",Dumper(%attributes),"
\n"; #} my %alignments; # for my $feature (@match) { for my $feature (@match,@wmatch) { my %attributes = $feature->attributes; my $species = $attributes{'species'}; push @{$alignments{$species}}, $feature; } %alignments; } #known species (all those that are in the Phylo tree) sub known_species { my $self = shift; my $tree = shift; if ($tree) { my @leaves = $tree->get_leaf_nodes; my @allspecies = map {$_->id} @leaves; return @allspecies } else { #this may be too simple of an assumption, especially for non newick files my $tree_file = $self->option('tree_file'); open (FH, $tree_file); my $newick = ; close FH; my @allspecies = $newick =~ /([a-zA-Z]\w*)/g; return @allspecies; } } sub unknown_species { my $self = shift; my %alignments; #all species in viewing window my $refspecies; #all species from cladogram info my @current_species; #all species in viewing window my @known_species; #species in GFF but and clado my @unknown_species; #species in GFF but not in clado # current - known = unknown if (@_) { %alignments = %{$_[0]}; $refspecies = $_[1]; @current_species = @{$_[2]}; @known_species = @{$_[3]}; } else { %alignments = $self->extract_features; $refspecies = $self->option('reference_species'); @current_species = keys %alignments; #all species in viewing window @known_species = $self->known_species; #all species from cladogram info } #would have combined the two cases into one line using || but Perl will treat the arrays as num of elem #do set subtraction to see which species in viewing range but not in tree my %seen; # build lookup table @seen{@known_species} = (); foreach my $item (@current_species, $refspecies) { push(@unknown_species, $item) unless exists $seen{$item}; } return @unknown_species; } sub set_tree { my $self = shift; #warn"My species are ".Dumper(@species); my $tree_file = $self->option('tree_file'); my $tree_format = $self->option('tree_format') || 'newick'; my $treeio = new Bio::TreeIO(-file => $tree_file, -format => $tree_format); my $tree = $treeio->next_tree; my $root = $tree->get_root_node; # would be ideal to remove all species that don't have features (alignments) within # viewing window but there is a bug in Bio::Tree library where you can't remove the # first leaf node. # $tree->remove_Node('dog'); # etc... #set the leaf x coodinate (make all evenly spaced) my @leaves = $tree->get_leaf_nodes; for (my $i=0; $i<@leaves; $i++) { my $leaf = $leaves[$i]; #note that leaves can use "description" functions while intermediate nodes cannot #thus objects must be handled directly $leaf->description({'x'=>$i}); } #set root height to 0 $root->{'description'}{'y'} = 0; #set the x and y coordinates of all intermediate nodes get_n_set_next_treenode($root, 0); flip_xy($tree); $tree; } sub get_max_height { my $tree = shift; my $max_height; #get the max height for my $child ($tree->get_leaf_nodes) { my $x = $child->{'_description'}{'x'}; $max_height = $x if $max_height < $x; } $max_height; } sub draw_clado { my $self = shift; my $tree = shift; my $gd = shift; my ($x1, $y1, $x2, $y2, $color, $xscale, $yscale, $xoffset, $yoffset, $start_x, $draw_clado_left) = @_; my @bounds = $gd->getBounds; my $root = $tree->get_root_node; my @nodes = $root->get_all_Descendents; #draw bg for cladogram my $font = $self->mono_font; my $clado_bg = $self->color('clado_bg') || $self->bgcolor; my @coords = (0, $y1, $start_x+$xoffset+$font->width-1, $y2+1); my @coords2 = ($x1, $y1, $start_x+$xoffset/2, $y2); if ($draw_clado_left) { $gd->filledRectangle(@coords, $clado_bg); $gd->filledRectangle(@coords2, $self->color('clado_bg')); $gd->filledRectangle($x2, $x1, $bounds[0], $bounds[1], $self->color('bg_color')) if $self->dna_fits; } else { $gd->filledRectangle($bounds[0]-$coords[2], $coords[1], $bounds[0]-$coords[0], $coords[3], $self->color('bg_color')); $gd->filledRectangle($bounds[0]-$coords2[2], $coords2[1], $bounds[0]-$coords2[0], $coords2[3], $clado_bg); $gd->filledRectangle(0, $y1, $x1, $y2+1, $self->color('bg_color')) if $self->dna_fits; } #draw the lines of the tree for my $node ($root,@nodes) { next if $node->is_Leaf; my $x = $node->{'_description'}{'x'} * $xscale; #draw vertical line covering all children my $topx = $node->{'_description'}{'childmin'} * $yscale; my $botx = $node->{'_description'}{'childmax'} * $yscale; @coords = ($x+$xoffset, $topx+$yoffset, $x+$xoffset, $botx+$yoffset); if ($draw_clado_left) { $gd->line(@coords, $self->fgcolor); } else { $gd->line($bounds[0]-$coords[2], $coords[1], $bounds[0]-$coords[0], $coords[3], $self->fgcolor); } #draw a line connecting the bar to each child my @children = $node->each_Descendent; for my $child (@children) { my $cx = $child->{'_description'}{'x'} * $xscale; my $cy = $child->{'_description'}{'y'} * $yscale; $cx = $start_x if $child->is_Leaf; #print"($cx, $cy)"; @coords = ($x+$xoffset, $cy+$yoffset, $cx+$xoffset, $cy+$yoffset); if ($draw_clado_left) { $gd->line(@coords, $self->fgcolor); } else { $gd->line($bounds[0]-$coords[2], $coords[1], $bounds[0]-$coords[0], $coords[3], $self->fgcolor); } } } #my $tree = $treeio->next_tree; #my @nodes = grep { $_->bootstrap > 70 } $tree->get_nodes; #warn"my nodes are:\n".Dumper(@nodes); print""; $start_x + $xscale; } #tree is made with root on top but this will switch x and y coords so root is on left sub flip_xy { my $tree = shift; my $root = $tree->get_root_node; my @nodes = $root->get_all_Descendents; for my $node ($root, @nodes) { if ($node->is_Leaf) { $node->{'_description'} = { 'x' => $node->{'_description'}{'y'}, 'y' => $node->{'_description'}{'x'} } } else { $node->{'_description'} = { 'x' => $node->{'_description'}{'y'}, 'y' => $node->{'_description'}{'x'}, 'child_pos' => $node->{'_description'}{'child_pos'}, 'childmin' => $node->{'_description'}{'childmin'}, 'childmax' => $node->{'_description'}{'childmax'} } } } } #recursive function that sets the x and y coordinates of tree nodes sub get_n_set_next_treenode { my $node = shift; my $height = $node->{'_description'}{'y'}; my @children = $node->each_Descendent; my $x = 0; my $min_child_x = -1; my $max_child_x = -1; #iterate through children to find the x's and set the y's (height's) for my $child (@children) { #set the y coordinate as parent's height + 1 $child->{'_description'}{'y'} = $height + 1; get_n_set_next_treenode($child); #retrieve the child's x coordinate my $child_x = $child->{'_description'}{'x'} || 0; $x += $child_x; $min_child_x = $child_x if $min_child_x==-1 || $child_x < $min_child_x; $max_child_x = $child_x if $max_child_x==-1 || $max_child_x < $child_x; #$x += $child->discription->{'x'}; #cannot do this for intermediate nodes #store the x values of all children push @{$node->{'_description'}{'child_pos'}}, $child_x; } #set the current x coordinate as the average of all children's x's if (@children) { $x = $x / @children; $node->{'_description'}{'x'} = $x; $node->{'_description'}{'childmin'} = $min_child_x; $node->{'_description'}{'childmax'} = $max_child_x; } $node->{'_description'}{'y'} = $height; } sub get_legend_and_scale { my $yscale = shift; my $height = shift; if ($yscale < 2*$height - 1) { $height = 0; } ######### # chage scale later so that the base can be anything and not just 1!! # scale legend goes in order from min, axis, max, if either min or max = 1, then will only have min & max my @order = sort {$a <=> $b} (1, @_); my $graph_scale = - ($yscale - $height) / (log10($order[2]) - log10($order[0])); my $graph_legend = {1 => $graph_scale * (log10(1) - log10($order[2])), $order[0] => $graph_scale * (log10($order[0]) - log10($order[2])), $order[2] => 0}; #print "order is @order and the yscale is $yscale and height is $height
"; return ($graph_legend, $graph_scale); } #main method that draws everything sub draw { my $self = shift; my $font = $self->mono_font; my $height = $font->height; my $scale = $self->scale; my $gd = shift; my ($left,$top,$partno,$total_parts) = @_; my ($x1,$y1,$x2,$y2) = $self->bounds($left, $top); my @bounds = $gd->getBounds; my $draw_clado_left = $self->option('draw_clado_left'); #spacing of either DNA alignments or score histograms in units of font height my $species_spacing = $self->option('species_spacing') || 1; my $xscale = $font->width; my $yscale = $height * $species_spacing; my $xoffset = $x1; my $yoffset = $y1 + 0.5*$font->height; #method that reads the tree file to create the tree objects my $tree = $self->set_tree; my $max_height = get_max_height($tree); my $start_x =($max_height-1) * $xscale +$xoffset; my $connector = $self->connector; #all species having alignments in viewing window (key=name, val=feat obj) my %alignments = $self->extract_features; #print "Species are:",keys %alignments,"
\n"; my ($min_score, $max_score) = $self->get_score_bounds(%alignments); #$min_score = 0 unless $min_score; my ($graph_legend, $graph_scale) = get_legend_and_scale($yscale, $height, $min_score, $max_score); #print "min/max scores: $min_score, $max_score
\n", #"graph legend and scale: $graph_legend, $graph_scale"; # TODO: Gap entries give an undef for the min values for some reason my $refspecies = $self->option('reference_species'); my @current_species = keys %alignments; #all species in viewing window my @known_species = $self->known_species($tree); #all species from cladogram info my @unknown_species = $self->unknown_species(\%alignments, $refspecies, \@current_species, \@known_species); #species in GFF but not in clado ########is this even used? #my @allfeats; #for my $species (keys %alignments) { # push @allfeats, @{$alignments{$species}}; #} #this y value is the base for the next species' alignment/histogram and is incremented at each step my $y = $y1; for my $species (@known_species,@unknown_species) { my $y_track_top = $y + $height; my $y_track_bottom = $y + $yscale; if ($yscale < 2*$height-1) { #small scale $y_track_top = $y;# + $height; my $y_track_bottom = $y + $height; } #process the reference sequence differently if ($species eq $refspecies) { #draw DNA alignments if zoomed close enough my ($fx1,$fy1) = ($x1, $y_track_top); my ($fx2,$fy2) = ($x2,$y_track_bottom); if ($self->dna_fits) { my $dna = eval { $self->feature->seq }; $dna = $dna->seq if ref($dna) and $dna->can('seq'); # to catch Bio::PrimarySeqI objects my $bg_color = $self->color('ref_color') || $self->bgcolor; $fy2 = $fy1 + $font->height || $y2; $self->_draw_dna($gd,$dna,$fx1,$fy1,$fx2,$fy2, $self->fgcolor, $bg_color); } else { } my $x_label_start = $start_x + $xoffset + $font->width; $self->species_label($gd, $draw_clado_left, $x_label_start, $y, $species) unless ($self->option('hide_label')); $y += $yscale; next; } #skip if the there is no alignments for this species in this window unless ($alignments{$species}) { my $x_label_start = $start_x + $xoffset + $font->width; $self->species_label($gd, $draw_clado_left, $x_label_start, $y, $species) unless ($self->option('hide_label')); $y += $yscale; next; } my @features = @{$alignments{$species}}; #draw the axis for the plots $self->draw_pairwisegraph_axis($gd, $graph_legend, $x1, $x2, $y_track_top, $y_track_bottom, $draw_clado_left, @bounds) unless $self->dna_fits; #iterate through the wigfiles and put them on the graph ### # todo ### #iterate through features, and put them on the graph for my $feat (@features) { my ($start, $stop, %attributes) = ($feat->start, $feat->stop, $feat->attributes); my ($fx1,$fy1) = ($x1 + ($start-$self->start)*$scale, $y_track_top); my ($fx2,$fy2) = ($x1 + ($stop-$self->start)*$scale,$y_track_bottom); my $gapstr = $attributes{'Gap'} || "M".($stop-$start+1); my @gapstr = split " ", $gapstr; my @gaps; for my $gap (@gapstr) { my ($type, $num) = $gap =~ /^(.)(\d+)/; push @gaps, [$type, $num+0]; } #draw DNA alignments if zoomed close enough if ($self->dna_fits) { my $test = 0; my $hit = $feat->hit; if (!defined $hit) { warn "No hit for feature $feat, skipping drawing DNA"; next; } my $hit_seq = $hit->seq || print "No seq for hit
\n"; my $targ_dna = $hit_seq->seq || print "No seq for hit_seq
\n"; my $seq = $feat->seq || print "No sequence object for feature
\n"; my $ref_dna = $seq->seq || print "No ref dna"; #doesn't work as planned # my $ref_dna = $feat->seq->seq || print "No ref dna"; # my $targ_dna = $feat->hit->seq->seq || print "No targ dna"; next if !defined $ref_dna || !defined $targ_dna; $self->draw_dna($gd,$ref_dna, $targ_dna,$fx1,$fy1,$fx2,$fy2,\@gaps); } else { my $wigfile = $attributes{'wigfile'}; if ($wigfile) { if (-e $wigfile) { $self->pairwise_draw_wig_graph($gd, $feat, $x1, $scale, \@gaps, $graph_legend->{1}, $graph_scale, $fx1, $fy1, $fx2, $fy2,$wigfile); } else { warn "Wigfile $wigfile does not exist, skipping ..."; } } else { $self->pairwise_draw_graph($gd, $feat, $x1, $scale, \@gaps, $graph_legend->{1}, $graph_scale, $fx1, $fy1, $fx2, $fy2); } } } #label the species in the cladogram my $x_label_start = $start_x + $xoffset + $font->width; $self->species_label($gd, $draw_clado_left, $x_label_start, $y, $species) unless ($self->option('hide_label')); $y += $yscale; } $self->draw_clado($tree, $gd, $x1, $y1, $x2, $y2, $self->fgcolor, $xscale, $yscale, $xoffset, $yoffset, $start_x, $draw_clado_left); } sub species_label { my $self = shift; my $gd = shift; my $draw_clado_left = shift; my $x_start = shift; my $y_start = shift; my $species = shift; my $font = $self->mono_font; $x_start += 2; my $text_width = $font->width * length($species); my $bgcolor = $self->color('bg_color'); #make label if ($draw_clado_left) { $gd->filledRectangle($x_start-2, $y_start, $x_start + $text_width, $y_start+$font->height, $bgcolor); $gd->rectangle($x_start-2, $y_start, $x_start + $text_width, $y_start+$font->height, $self->fgcolor); $gd->string($font, $x_start, $y_start, $species, $self->fgcolor); } else { my ($x_max, $y_max) = $gd->getBounds; my $write_pos = $x_max - $x_start - $text_width; $gd->filledRectangle($write_pos, $y_start, $write_pos + $text_width+2, $y_start+$font->height, $bgcolor); $gd->rectangle($write_pos, $y_start, $write_pos + $text_width+2, $y_start+$font->height, $self->fgcolor); $gd->string($font, $write_pos+2, $y_start, $species, $self->fgcolor); } } # draws the legends on the conservation scale sub draw_pairwisegraph_axis { my $self = shift; my ($gd, $graph_legend, $x1, $x2, $y_track_top, $y_track_bottom, $draw_clado_left, @bounds) = @_; my $font = $self->mono_font; my $axis_color = $self->color('axis_color') || $self->fgcolor; my $mid_axis_color = $self->color('mid_axis_color') || $axis_color; for my $label (keys %$graph_legend) { my $y_label = $graph_legend->{$label} + $y_track_top; my $col = $axis_color; $col = $mid_axis_color if ($y_label != $y_track_top && $y_label != $y_track_bottom); $gd->line($x1,$y_label,$x2,$y_label,$col); my @coords = (0, $y_label, $x1, $y_label); if ($draw_clado_left) { #draw the legend on the right $coords[0] = $bounds[0] - $coords[0]; $coords[2] = $bounds[0] - $coords[2]; my $x_text_offset = length($label) * $font->width; $gd->string($font, $coords[0]-$x_text_offset, $coords[1], $label, $self->fgcolor); $gd->line(@coords, $self->fgcolor); $gd->line($x2,$y_track_top,$x2,$y_track_bottom,$self->fgcolor); } else { #draw the legned on the left $gd->string($font, @coords[0..1], $label, $self->fgcolor); $gd->line(@coords, $self->fgcolor); $gd->line($x1,$y_track_top,$x1,$y_track_bottom,$self->fgcolor); } } } #find min and max from features within the bounds sub get_score_bounds { my $self = shift; my %alignments = @_; my $min = -1; my $max = -1; for my $species (keys %alignments) { for my $feature (@{$alignments{$species}}) { my $score = $feature->score; #check to see if wigfile if ($score == undef) { my %attributes = $feature->attributes; if (-e $attributes{'wigfile'}) { ($min, $max) = $self->get_score_bounds_wigfile($feature,$min,$max,$attributes{'wigfile'}); } next; } $min = $score if $min == -1 || $score < $min; $max = $score if $max == -1 || $max < $score; } } my @parts = $self->parts; return ($min, $max) } #find min and max of sampled wigfile sub get_score_bounds_wigfile { my $self = shift; my $feature = shift; my ($min,$max,$wigfile) = @_; my $start = $feature->start < $self->start ? $self->start : $feature->start; my $stop = $self->stop < $feature->stop ? $self->stop : $feature->stop; #print $self->stop, "-", $feature->stop, "checking @_ $start - $stop\n"; #extract wig file contents my $wig = Bio::Graphics::Wiggle->new($wigfile, 0, {step => 1}) or die; #todo: make step configurable my $step = 100; for (my $i=$start; $i<$stop; $i += $step) { my $v = $wig->value($i); next unless defined $v; $min = $v if !defined $min or $v < $min; $max = $v if !defined $max or $max < $v; } #print "min and max are $min , $max
\n"; return ($min,$max); } sub pairwise_draw_graph { my $self = shift; my $gd = shift; my $feat = shift; # current feature object my $x_edge = shift; # x start position of the track my $scale = shift; # pixels / bp my $gaps = shift; # gap data for insertions, deletions and matches my $zero_y = shift; # y coordinate of 0 position my $graph_scale = shift; # scale for the graph. y_coord = graph_scale x log(score) my ($x1,$y1,$x2,$y2) = @_; my $fgcolor = $self->fgcolor; my $errcolor = $self->color('errcolor') || $fgcolor; my $score = $feat->score; my %attributes = $feat->attributes; my $log_y = log10($score); my $y_bottom = log10($score) * $graph_scale + $zero_y + $y1; my $y_top = $zero_y+$y1; my @y = sort {$a <=> $b} ($y_bottom, $y_top); #missing gap data unless ($gaps) { $x1 = $x_edge if $x1 < $x_edge; return if $x2 < $x_edge; #$gd->filledRectangle($x1,$y[0],$x2,$y[1],$fgcolor); return; } my $bp = 0; #draw a bar representing the score for the span of base pairs for my $tuple (@$gaps) { my ($type, $num) = @$tuple; #warn"$type and $num"; if ($type eq "M") { my $x_left = $x1 + ($bp*$scale); my $x_right = $x_left + $num*$scale; $bp += $num; $x_left = $x_edge if $x_left < $x_edge; next if $x_right < $x_edge; $gd->filledRectangle($x_left,$y[0],$x_right,$y[1],$fgcolor); } elsif ($type eq "D") { $bp += $num; } elsif ($type eq "I") { my $x_left = $x1 + ($bp*$scale); $gd->line($x_left-2, $y1-4, $x_left, $y1, $errcolor); $gd->line($x_left, $y1, $x_left+2, $y1-4, $errcolor); } } } sub pairwise_draw_wig_graph { my $self = shift; my $gd = shift; my $feat = shift; # current feature object my $x_edge = shift; # x start position of the track my $scale = shift; # pixels / bp my $gaps = shift; # gap data for insertions, deletions and matches my $zero_y = shift; # y coordinate of 0 position my $graph_scale = shift; # scale for the graph. y_coord = graph_scale x log(score) my ($x1,$y1,$x2,$y2,$wigfile) = @_; my $fgcolor = $self->fgcolor; my $errcolor = $self->color('errcolor') || $fgcolor; my $score = $feat->score; my %attributes = $feat->attributes; # my $log_y = log10($score); # my $y_bottom = log10($score) * $graph_scale + $zero_y + $y1; # my $y_top = $zero_y+$y1; # # my @y = sort {$a <=> $b} ($y_bottom, $y_top); #print "checking wigfile $wigfile
\n"; #todo: make step variable my $start = $feat->start < $self->start ? $self->start : $feat->start; my $stop = $self->stop < $feat->stop ? $self->stop : $feat->stop; # my $wig = Bio::Graphics::Wiggle->new($wigfile, 0) or die; my $wig = Bio::Graphics::Wiggle->new($wigfile, 0, {step => 1}) or die; ##### not sure why this was here # my $vals = $wig->values($start,$stop); # my ($vmin,$vmax); # for my $v (@$vals) { # next unless defined $v; # $vmin = $v if !defined $vmin or $v < $vmin; # $vmax = $v if !defined $vmax or $vmax < $v; # } # print "min and max are $vmin , $vmax\n"; # $min = $min < $vmin ? $min : $vmin; # $max = $vmax < $max ? $max : $vmax; # print "min and max are $min , $max\n"; # return ($min,$max); # print Dumper($vals); # my $pos = $start; # $gd->rectangle($x1,$y1,$x2,$y2,$fgcolor); # $gd->line($x1,$y1,$x2,$y2,$fgcolor); # $gd->line($x2,$y1,$x1,$y2,$fgcolor); # print "Start and stop: $start and $stop
\n"; # print "
\n$x1, $x2, $x_edge, $start, $stop
\n"; if ($scale < 1) { #### Algorithm 1, when zoomed at scale 1bp / pixel or more #### sample 10 values across the pixel (prevents redrawing same pixel over #### and over). The sample values are averaged. my $bp = $start; for (my $pix=$x1; $pix < $x2; $pix++) { $bp = ($pix - $x1)/$scale + $start; #todo: make samplesize an option my $samplesize = 10; my $score = 0; my $trial; for ($trial=0; $trial < $samplesize; $trial++) { my $samp_bp = $bp + ($trial/$samplesize)/$scale; last if $samp_bp > $stop; $score += $wig->value($samp_bp); #print "trial $trial: $samp_bp\tPixel $pix\tpos $x1
\n" if ($bp > 1700 && $bp < 1750); } #print "Pixel: $pix : Total $score and trial $trial
\n"; next if $trial == 0 || $score == 0; $score = $score / $trial; $self->draw_log10_rectangle($score, $graph_scale, $zero_y, $y1, $zero_y, $pix, $pix, $gd, $fgcolor); } } else { #### Algorithm 2, when zoomed in close at less than 1bp / pixel (1 bp spans #### 1 pixel or more), draw each value directly my $step = 1; for (my $i=$start; $i<$stop; $i += $step) { my $val = $wig->value($i); next if !defined $val; my $bp = $i - $start; my $x_left = $x1 + ($bp*$scale); my $x_right = $x_left + 1*$scale; $score = $val; $self->draw_log10_rectangle($score, $graph_scale, $zero_y, $y1, $zero_y, $x_left, $x_right, $gd, $fgcolor); } } return; } sub draw_log10_rectangle { my $self = shift; my $score = shift; my $graph_scale = shift; my $zero_y = shift; my $y1 = shift; $zero_y = shift; # oy vey - this will be overwritten my $x_left = shift; my $x_right = shift; my $gd = shift; my $fgcolor = shift; my $log_y = log10($score); my $y_bottom = log10($score) * $graph_scale + $zero_y + $y1; my $y_top = $zero_y+$y1; my @y = sort {$a <=> $b} ($y_bottom, $y_top); #print "$x_left,$y[0],$x_right,$y[1]
\n"; $gd->filledRectangle($x_left,$y[0],$x_right,$y[1],$fgcolor); } sub draw_dna { my $self = shift; my ($gd,$ref_dna, $dna,$x1,$y1,$x2,$y2, $gaps) = @_; my $pixels_per_base = $self->scale; my $fgcolor = $self->fgcolor; my $bg_color = $self->color('targ_color') || $self->bgcolor; my $errcolor = $self->color('errcolor') || $fgcolor; my $font = $self->mono_font; $y2 = $y1 + $font->height || $y2; #missing gap data, draw as is unless ($gaps) { warn"no gap data for DNA sequence $dna"; $self->_draw_dna($gd, $dna, $x1, $y1, $x2, $y2); return; } #parse the DNA segments by the gaps for my $tuple (@$gaps) { my ($type, $num) = @$tuple; if ($type eq "M") { my $dnaseg = substr($dna, 0, $num); my $ref_dnaseg = substr($ref_dna, 0, $num); $self->_draw_dna($gd,$dnaseg, $x1, $y1, $x2, $y2, $fgcolor, $bg_color,$ref_dnaseg); $dna = substr($dna, $num); $ref_dna = substr($ref_dna, $num); $x1 += $num * $pixels_per_base; } elsif ($type eq "D") { my $dnaseg = '-' x $num; $self->_draw_dna($gd, $dnaseg, $x1, $y1, $x2, $y2, $fgcolor); $gd->rectangle($x1, $y1-1, $x1+$num * $pixels_per_base, $y2+1, $errcolor); $ref_dna = substr($ref_dna, $num); $x1 += $num * $pixels_per_base; } elsif ($type eq "I") { $dna = substr($dna, $num); $gd->line($x1-2, $y1-2, $x1+2, $y1-2, $errcolor); $gd->line($x1, $y1-1, $x1, $y2+1, $errcolor); $gd->line($x1-2, $y2+1, $x1+2, $y2+1, $errcolor); } } } sub _draw_dna { my $self = shift; #the last argument is optional. If the reference seq is given, it will check it with target my ($gd,$dna,$x1,$y1,$x2,$y2, $color, $bg_color, $ref_dna) = @_; my $pixels_per_base = $self->scale; my $feature = $self->feature; unless ($ref_dna) { $gd->filledRectangle($x1+1, $y1, $x2, $y2, $bg_color); } my $strand = $feature->strand || 1; $strand *= -1 if $self->{flip}; my @bases = split '',$strand >= 0 ? $dna : $self->reversec($dna); my @refbases = split '',$strand >= 0 ? $ref_dna : $self->reversec($ref_dna); $color = $self->fgcolor unless $color; $bg_color = 0 unless $bg_color; my $font = $self->mono_font; my $lineheight = $font->height; # $y1 -= $lineheight/2 - 3; ##################NOT SURE WHY THIS WAS HERE BEFORE my $strands = $self->option('strand') || 'auto'; my ($forward,$reverse); if ($strands eq 'auto') { $forward = $feature->strand >= 0; $reverse = $feature->strand <= 0; } elsif ($strands eq 'both') { $forward = $reverse = 1; } elsif ($strands eq 'reverse') { $reverse = 1; } else { $forward = 1; } # minus strand features align right, not left $x1 += $pixels_per_base - $font->width - 1 if $strand < 0; for (my $i=0;$i<@bases;$i++) { my $x = $x1 + $i * $pixels_per_base; my $x_next = $x + $pixels_per_base; #draw background if DNA base aligns with reference (if ref given) $gd->filledRectangle($x+1, $y1, $x_next, $y2, $bg_color) if ( ($forward && $bases[$i] eq $refbases[$i]) || ($reverse && $complement{$bases[$i]} eq $refbases[$i]) ); $gd->char($font,$x+2,$y1,$bases[$i],$color) if $forward; $gd->char($font,$x+2,$y1+($forward ? $lineheight:0), $complement{$bases[$i]}||$bases[$i],$color) if $reverse; } } 1; __END__ =head1 NAME Bio::Graphics::Glyph::phylo_align - The "phylogenetic alignment" glyph =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This glyph draws a cladogram for any set of species along with their alignment data in relation to the reference species. At high magnification, base pair alignements will be displayed. At lower magnification, a conservation score plot will be drawn. Gaps as specified by CIGAR are supported. Currently the scores are drawn to a log plot with the restriction that the score will be the same across all base pairs within an alignment. It is hoped that this restriction can be addressed in the future. For this glyph to work, the feature must return a DNA sequence string in response to the dna() method. Also, a valid tree file must be available in a format readable by the Bio::Tree library. =head2 OPTIONS The following options are standard among all Glyphs. See L for a full explanation. Option Description Default ------ ----------- ------- -fgcolor Foreground color black -outlinecolor Synonym for -fgcolor -bgcolor Background color turquoise -fillcolor Synonym for -bgcolor -linewidth Line width 1 -height Height of glyph 10 -font Glyph font gdSmallFont -connector Connector type 0 (false) -connector_color Connector color black -label Whether to draw a label 0 (false) -description Whether to draw a description 0 (false) -hilite Highlight color undef (no color) In addition to the common options, the following glyph-specific options are recognized: Option Description Default ------ ----------- ------- -draw_clado_left Draws the Cladogram on left 0 -species_spacing Spacing of species in DNA 1 mode in units of font height -species_spacing_score Spacing of spcies in 5 conservation view in units of font height -hide_label Whether to label spcies 0 -tree_file Path of file containing undef cladogram tree information -tree_format Format of tree file newick -axis_color Color of the vertical axes fgcolor in the GC content graph -errcolor Color of all misalignment fgcolor indicators -mid_axis_color Color of the middle axis of the conservation score graph axis_color -clado_bg Color of the clado bg bgcolor indicators -ref_color Color of base pair bg for bgcolor the reference sequence -targ_color Color of base pair bg for bgcolor all base pairs that match reference =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHORS Hisanaga Mark Okada Lincoln Stein Elstein@cshl.orgE. Copyright (c) 2001 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/box.pm000555001750001750 646212165075746 21310 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::box; # DAS-compatible package to use for drawing a box use strict; use base qw(Bio::Graphics::Glyph::generic); sub my_description { return <draw_component(@_); $self->draw_label(@_) if $self->option('label'); $self->draw_description(@_) if $self->option('description'); } sub maxdepth { my $self = shift; my $maxdepth = $self->option('maxdepth'); return $maxdepth if defined $maxdepth; return 0; } sub subseq { return (); } 1; __END__ =head1 NAME Bio::Graphics::Glyph::box - The "box" glyph =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This is the most basic glyph. It draws a filled box and optionally a label. It does *NOT* draw subparts, and so is useful for semantic zooming when one is zoomed out too far to see substructure. =head2 OPTIONS The following options are standard among all Glyphs. See L for a full explanation. Option Description Default ------ ----------- ------- -fgcolor Foreground color black -outlinecolor Synonym for -fgcolor -bgcolor Background color turquoise -fillcolor Synonym for -bgcolor -linewidth Line width 1 -height Height of glyph 10 -font Glyph font gdSmallFont -connector Connector type 0 (false) -connector_color Connector color black -label Whether to draw a label 0 (false) -description Whether to draw a description 0 (false) -strand_arrow Whether to indicate 0 (false) strandedness -hilite Highlight color undef (no color) =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Allen Day Eday@cshl.orgE. Copyright (c) 2001 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/merge_parts.pm000555001750001750 720312165075746 23022 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::merge_parts; use strict; use base qw(Bio::Graphics::Glyph); sub my_description { return < [ 'integer', undef, 'This is the maximum gap, measured in bp, across which the glyph will', 'attempt to merge subfeatures in an attempt to simplify the appearance', 'at low magnifications. If undef, the max_gap will be calculated using', 'a simple exponential heuristic.'], } } sub merge_parts { my ($self,@parts) = @_; # This is the largest gap across which adjacent segments will be merged my $max_gap = $self->max_gap; my $last_part; my @sorted_parts = sort {$a->start <=> $b->start} @parts; for my $part (@sorted_parts) { if ($last_part) { my $start = $part->start; my $end = $part->stop; my $score = $part->score; my $pstart = $last_part->start; my $pend = $last_part->stop; my $pscore = $last_part->score || 0; my $len = 1 + abs($end - $start); my $plen = 1 + abs($pend - $pstart); # weighted average score my $new_score = (($score*$len)+($pscore*$plen))/($len+$plen); # don't merge if there is a gap > than the allowed size my $gap = abs($start - $pend); my $total = abs($end - $pstart); my $last_f = $last_part->feature; if ($gap > $max_gap) { $last_part = $part; next; } $part->{start} = $pstart; $part->{score} = $new_score; my ($left,$right) = $self->map_pt($pstart,$end+1); $part->{left} = $left; $part->{width} = ($right - $left) + 1; # flag the left feature for removal $last_part->{remove} = 1; } $last_part = $part; } @parts = grep {!defined $_->{remove}} @parts; return @parts; } sub max_gap { my $self = shift; $self->panel->{max_gap} ||= $self->option('max_gap'); return $self->panel->{max_gap} || $self->calculate_max_gap; } sub calculate_max_gap { my $self = shift; my $segment_length = $self->panel->length; # allow more aggressive merging for larger segments # by exponentially increasing max_gap my $max_gap = ($segment_length/10000)*($segment_length/500); $self->panel->{max_gap} = $max_gap; return $max_gap; } 1; __END__ =head1 NAME Bio::Graphics::Glyph::merge_parts - a base class which suppors semantic zooming of scored alignment features =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This is a base class for Bio::Graphics::Glyph::graded_segments, Bio::Graphics::Glyph::heterogeneous_segments and Bio::Graphics::Glyph::merged_alignment. It adds internal methods to support semantic zooming of scored alignment features. It is not intended for end users. =head1 BUGS Please report them. =head1 SEE ALSO L, L, L L L =head1 AUTHOR Sheldon McKay Emckays@cshl.eduE Copyright (c) 2005 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/hybrid_plot.pm000555001750001750 2265112165075746 23055 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::hybrid_plot; use strict; use base qw(Bio::Graphics::Glyph::wiggle_xyplot); use constant DEBUG=>0; use constant NEGCOL=>"orange"; use constant POSCOL=>"blue"; our $VERSION = '1.0'; sub my_options { { min_score => [ 'integer', undef, "Minimum value of the signal graph feature's \"score\" attribute."], max_score => [ 'integer', undef, "Maximum value of the signal graph feature's \"score\" attribute."], flip_sign => [ 'boolean', 0, "Optionally flip the signal for wigfileB (if the scores are positive but we wish to paint the signal along negative y-axis)"] }; } sub my_description { return <option('u_method') || 'match'; } # Override height and pad functions (needed to correctly space features with different sources): sub height { my $self = shift; my $h = $self->SUPER::height; return $self->feature->method eq $self->_check_uni ? 3 : $h; } sub pad_top { my $self = shift; return $self->feature->method eq $self->_check_uni ? 0 : 4; } sub pad_bottom { my $self = shift; return $self->feature->method eq $self->_check_uni ? 0 : 4; } # we override the draw method so that it dynamically creates the parts needed # from the wig file rather than trying to fetch them from the database sub draw { my $self = shift; my ($gd,$dx,$dy) = @_; my ($left,$top,$right,$bottom) = $self->calculate_boundaries($dx,$dy); my $height = $bottom - $top; my $feature = $self->feature; my $set_flip = $self->option('flip_sign') | 0; #Draw individual features for reads (unlike wiggle features reads will have scores) my $t_id = $feature->method; if($t_id && $t_id eq $self->_check_uni){return Bio::Graphics::Glyph::generic::draw_component($self,@_);} #Draw multiple graph if we don't have a score my @wiggles = $self->get_wiggles($feature); my ($fasta) = $feature->get_tag_values('fasta'); my($scale,$y_origin,$min_score,$max_score); $self->panel->startGroup($gd); #Depending on what we have (wiggle or BigWig) pick the way to paint the signal graph for(my $w = 0; $w < @wiggles; $w++){ if ($w > 0) { $self->configure('bgcolor', NEGCOL); $self->configure('no_grid', 1); } else { $self->configure('bgcolor', POSCOL); } if ($wiggles[$w] =~ /\.wi\w{1,3}$/) { $self->draw_wigfile($feature,$wiggles[$w],@_); } elsif ($wiggles[$w] =~ /\.bw$/) { my $flip = ($w > 0 && $set_flip) ? -1 : 1; eval "require Bio::DB::BigWig;1" or die $@; eval "require Bio::DB::Sam; 1" or die $@; my @args = (-bigwig => "$wiggles[$w]"); push @args,(-fasta => Bio::DB::Sam::Fai->open($fasta)) if $fasta; my $wig = Bio::DB::BigWig->new(@args); my ($summary) = $wig->features(-seq_id => $feature->segment->ref, -start => $self->panel->start, -end => $self->panel->end, -type => 'summary'); my $stats = $summary->statistical_summary($self->width); my @vals = map {$_->{validCount} ? $_->{sumData}/$_->{validCount}*$flip:0} @$stats; $self->_draw_coverage($summary,\@vals,@_); } } } sub get_wiggles { my $self = shift; my $feature = shift; my @wiggles; foreach ('A'..'Z') { my $filename = 'wigfile'.$_; my ($wiggle) = $feature->get_tag_values('wigfile'.$_); push (@wiggles, $wiggle) if $wiggle; } return @wiggles; } sub minmax { my $self = shift; my $parts = shift; my $autoscale = $self->option('autoscale') || 'local'; my $min_score = $self->min_score unless $autoscale eq 'z_score'; my $max_score = $self->max_score unless $autoscale eq 'z_score'; my $do_min = !defined $min_score; my $do_max = !defined $max_score; my @wiggles = $self->get_wiggles($self->feature); my ($min,$max,$mean,$stdev); my @args = (-seq_id => (eval{$self->feature->segment->ref}||''), -start => $self->panel->start, -end => $self->panel->end, -type => 'summary'); for my $w (@wiggles) { my ($a,$b,$c,$d); if ($w =~ /\.bw$/) { eval "require Bio::DB::BigWig;1" or die $@; my $wig = Bio::DB::BigWig->new(-bigwig=>$w) or next; ($a,$b,$c,$d) = $self->bigwig_stats($autoscale,$wig->features(@args)); } elsif ($w =~ /\.wi\w{1,3}$/) { eval "require Bio::Graphics::Wiggle;1" or die $@; my $wig = Bio::Graphics::Wiggle->new($w); ($a,$b,$c,$d) = $self->wig_stats($autoscale,$wig); } $min = $a if !defined $min || $min > $a; $max = $b if !defined $max || $max < $b; $mean += $c; $stdev += $d**2; } $stdev = sqrt($stdev); $min_score = $min if $do_min; $max_score = $max if $do_max; return $self->sanity_check($min_score,$max_score,$mean,$stdev); } 1; __END__ =head1 NAME Bio::Graphics::Glyph::hybrid_plot - An xyplot plot drawing dual graph using data from two or more wiggle files per track =head1 SYNOPSIS See and . =head1 DESCRIPTION Note that for full functionality this glyph requires Bio::Graphics::Glyph::generic (generic glyph is used for drawing individual matches for small RNA alignments at a high zoom level, specified by semantic zooming in GBrowse conf file) Unlike the regular xyplot, this glyph draws two overlapping graphs using value data in Bio::Graphics::Wiggle file format: track type=wiggle_0 name="Experiment" description="snRNA seq data" visibility=pack viewLimits=-2:2 color=255,0,0 altColor=0,0,255 windowingFunction=mean smoothingWindow=16 2L 400 500 0.5 2L 501 600 0.5 2L 601 700 0.4 2L 701 800 0.1 2L 800 900 0.1 ##gff-version 3 2L Sample_rnaseq rnaseq_wiggle 41 3009 . . . ID=Samlpe_2L;Name=Sample;Note=YourNoteHere;wigfileA=/datadir/track_001.2L.wig;wigfileB=/datadir/track_002.2L.wig The "wigfileA" and "wigfileB" attributes give a relative or absolute pathname to Bio::Graphics::Wiggle format files for two concurrent sets of data. Basically, these wigfiles contain the data on signal intensity (counts) for sequences aligned with genomic regions. In wigfileA these data are additive, so for each sequence region the signal is calculated as a sum of signals from overlapping matches (signal). In wigfileB the signal represents the maximum value among all sequences (signal quality) aligned with the current region so the user can see the difference between accumulated signal from overlapping multiple matches (which may likely be just a noise from products of degradation) and high-quality signal from unique sequences. For a third wiggle file use the attribute "wigfileC" and so forth. It is essential that wigfile entries in gff file do not have score, because score used to differentiate between data for dual graph and data for matches (individual features visible at higher magnification). After an update to wiggle_xyplot code colors for dual plot are now hard-coded (blue for signal and orange for signal quality). Alpha channel is also handled by wiggle_xyplot code now. =head2 OPTIONS In addition to some of the wiggle_xyplot glyph options, the following options are recognized: Name Value Description ---- ----- ----------- wigfileA path name Path to a Bio::Graphics::Wiggle file for accumulated vales in 10-base bins wigfileB path name Path to a Bio::Graphics::Wiggle file for max values in 10-base bins fasta path name Path to fasta file to enable BigWig drawing u_method method name Use method of [method name] to identify individual features (like alignment matches) to show at high zoom level. By default it is set to 'match' =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Peter Ruzanov Epruzanov@oicr.on.caE. Copyright (c) 2008 Ontario Institute for Cancer Research This package and its accompanying libraries is free software; you can redistribute it and/or modify it under the terms of the GPL (either version 1, or at your option, any later version) or the Artistic License 2.0. Refer to LICENSE for the full license text. In addition, please see DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/three_letters.pm000555001750001750 612012165075746 23360 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::three_letters; # DAS-compatible package to use for drawing a line of groups of three letters # Non object-oriented utilities used here-and-there in Bio::Graphics modules =head1 NAME Bio::Graphics::Glyph::three_letters - DAS-compatible package to use for drawing a line of groups of three letters =cut use strict; use base qw(Bio::Graphics::Glyph::repeating_shape); sub pad_top { my $self = shift; my $top = $self->SUPER::pad_top; my $extra = 0.2 * $self->font->height; return $top + $extra; } sub default_interval { return 20; } sub default_text { return "CAG"; } sub draw_repeating_shape { my ($self, $gd, $x1, $y1, $x2, $y2, $fg) = @_; my $text = defined $self->option('text') ? $self->option('text') : $self->default_text(); while (length $text < 3) { $text .= " "; } $text = substr($text,0,3); my @letters = split //, $text; my $oneThird = ($x2-$x1) / 3; my $secondLetterX = $x1 + $oneThird; my $thirdLetterX = $x1 + 2*$oneThird; my $font = $self->option('labelfont') || $self->font; $gd->string($font, $x1, $y2-$font->height, $letters[0], $self->fontcolor); $gd->string($font, $secondLetterX, $y2-1.7*$font->height, $letters[1], $self->fontcolor); $gd->string($font, $thirdLetterX, $y2-$font->height, $letters[2], $self->fontcolor); } 1; __END__ =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This glyph draws groups of three letters separated by horizontal lines. =head2 OPTIONS In addition to the common options, the following glyph-specific options are recognized: Option Description Default ------ ----------- ------- -text The three letters to show "CAG" -width Width of one letter group 20 -interval Interval between 10 letter groups =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Vsevolod (Simon) Ilyushchenko Esimonf@cshl.eduE. Copyright (c) 2004 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/heterogeneous_segments.pm000555001750001750 1253312165075746 25315 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::heterogeneous_segments; # this glyph acts like graded_segments but the bgcolor of each segment is # controlled by the source field of the feature. Use the source field name # to set the background color: # -waba_strong_color => 'blue' # -waba_weak_color => 'red' # -waba_coding_color => 'green' use strict; use base qw(Bio::Graphics::Glyph::graded_segments); sub my_description { return < \$color For example, if the feature consists of a gene containing both confirmed and unconfirmed exons, you can make the confirmed exons green and the unconfirmed ones red this way: -confirmed_color => 'green', -unconfirmed_color => 'red' END } # override draw method to calculate the min and max values for the components sub draw { my $self = shift; # bail out if this isn't the right kind of feature # handle both das-style and Bio::SeqFeatureI style, # which use different names for subparts. my @parts = $self->parts; @parts = $self if !@parts && $self->level == 0; return $self->SUPER::draw(@_) unless @parts; @parts = $self->merge_parts(@parts) if $self->option('merge_parts'); # figure out the colors $self->{source2color} ||= {}; my $fill = $self->bgcolor; for my $part (@parts) { my $s = eval { $part->feature->source_tag } or next; $self->{source2color}{$s} ||= $self->color(lc($s)."_color") || $fill; $part->{partcolor} = $self->{source2color}{$s}; } $self->Bio::Graphics::Glyph::segments::draw(@_); } # synthesize a key glyph sub keyglyph { my $self = shift; my $scale = 1/$self->scale; # base pairs/pixel # two segments, at pixels 0->50, 60->80 my $offset = $self->panel->offset; my $feature = Bio::Graphics::Feature->new( -segments=>[ [ 0*$scale +$offset,25*$scale+$offset], [ 25*$scale +$offset,50*$scale+$offset], [ 50*$scale+$offset, 75*$scale+$offset] ], -name => $self->option('key'), -strand => '+1'); my @sources = grep {/_color$/} $self->factory->options; foreach (@sources) {s/_color$//} ($feature->segments)[0]->source_tag($sources[1]); ($feature->segments)[1]->source_tag($sources[0]); ($feature->segments)[2]->source_tag($sources[2]); my $factory = $self->factory->clone; $factory->set_option(label => 1); $factory->set_option(bump => 0); $factory->set_option(connector => 'solid'); my $glyph = $factory->make_glyph(0,$feature); return $glyph; } 1; =head1 NAME Bio::Graphics::Glyph::heterogeneous_segments - The "heterogeneous_segments" glyph =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This glyph acts like graded_segments but the bgcolor of each segment (sub-feature) can be individually set using the source field of the feature. Each segment type color is specified using the following nomenclature: -{source}_color => $color For example, if the feature consists of a gene containing both confirmed and unconfirmed exons, you can make the confirmed exons green and the unconfirmed ones red this way: -confirmed_color => 'green', -unconfirmed_color => 'red' =head2 OPTIONS The following options are standard among all Glyphs. See L for a full explanation. Option Description Default ------ ----------- ------- -fgcolor Foreground color black -outlinecolor Synonym for -fgcolor -bgcolor Background color turquoise -fillcolor Synonym for -bgcolor -linewidth Line width 1 -height Height of glyph 10 -font Glyph font gdSmallFont -connector Connector type 0 (false) -connector_color Connector color black -label Whether to draw a label 0 (false) -description Whether to draw a description 0 (false) -hilite Highlight color undef (no color) =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Lincoln Stein Elstein@cshl.orgE Copyright (c) 2001 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/flag.pm000555001750001750 610412165075746 21422 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::flag; # Non object-oriented utilities used here-and-there in Bio::Graphics modules sub my_description { return < [ 'string', 'ori', 'Text to draw next to the flag.'], width => [ 'integer', 20, 'Width of the flag.'], } } =head1 NAME Bio::Graphics::Glyph::flag - the "flag" glyph =cut use strict; use base qw(Bio::Graphics::Glyph::generic); sub default_text { return "ori"; } sub default_width { return 20; } sub draw_component { my $self = shift; my $gd = shift; my ($x1,$y1,$x2,$y2) = $self->calculate_boundaries(@_); my $fg = $self->fgcolor; my $bg = $self->bgcolor; my $width = $self->option('width') || $self->default_width; my $text = $self->option('text') || $self->default_text; my $oneThirdY = $y1 + ($y2-$y1) / 3; my $twoThirdsY = $y1 + 2 * ($y2-$y1) / 3; my $poly_pkg = $self->polygon_package; my $polygon = $poly_pkg->new(); $polygon->addPt($x1, $y1); $polygon->addPt($x1+$width, $oneThirdY); $polygon->addPt($x1, $twoThirdsY); $gd->polygon($polygon, $fg); $gd->fillToBorder($x1+$width/2, $oneThirdY, $fg, $bg); $gd->line($x1, $y1, $x1, $y2, $fg); my $font = $self->option('labelfont') || $self->font; $gd->string($font, $x1 + 3, $twoThirdsY-3, $text, $self->fontcolor); } 1; __END__ =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This glyph draws a flag with a text next to it. =head2 OPTIONS In addition to the common options, the following glyph-specific options are recognized: Option Description Default ------ ----------- ------- -text Text to draw next to the flag ori -width Width of the flag 20 =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Vsevolod (Simon) Ilyushchenko Esimonf@cshl.eduE. Copyright (c) 2004 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/oval.pm000555001750001750 527012165075746 21455 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::oval; use strict; use base qw(Bio::Graphics::Glyph::ellipse); sub my_description { return < and L. =head1 DESCRIPTION This glyph draws an oval instead of a box. It is an alias for the "ellipse" glyph. =head2 OPTIONS The following options are standard among all Glyphs. See L for a full explanation. Option Description Default ------ ----------- ------- -fgcolor Foreground color black -outlinecolor Synonym for -fgcolor -bgcolor Background color turquoise -fillcolor Synonym for -bgcolor -linewidth Line width 1 -height Height of glyph 10 -font Glyph font gdSmallFont -connector Connector type 0 (false) -connector_color Connector color black -label Whether to draw a label 0 (false) -description Whether to draw a description 0 (false) -hilite Highlight color undef (no color) =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Lincoln Stein Elstein@cshl.orgE Copyright (c) 2001 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/transcript2.pm000555001750001750 1437412165075746 23014 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::transcript2; use strict; use base qw(Bio::Graphics::Glyph::transcript); use constant MIN_WIDTH_FOR_ARROW => 8; sub show_strand { my $self = shift; my $s = $self->SUPER::show_strand; return $s if defined $s; return 'ends'; } sub extra_arrow_length { my $self = shift; my $strand = $self->feature->strand || 0; $strand *= -1 if $self->{flip}; my $first = $strand < 0 ? ($self->parts)[0] : ($self->parts)[-1]; $first ||= $self; my @rect = $first->bounds(); my $width = abs($rect[2] - $rect[0]); return 0 if $width >= MIN_WIDTH_FOR_ARROW; return $self->arrow_length; } sub pad_left { my $self = shift; my $pad = $self->Bio::Graphics::Glyph::generic::pad_left; return $pad if $self->feature->strand > 0; my $extra_arrow_length = $self->extra_arrow_length; if ($self->label_position eq 'left' && $self->label) { return $extra_arrow_length+$pad; } else { return $extra_arrow_length > $pad ? $extra_arrow_length : $pad; } } sub pad_right { my $self = shift; my $pad = $self->Bio::Graphics::Glyph::generic::pad_right; return $pad if $self->feature->strand < 0; my $extra_arrow_length = $self->extra_arrow_length; return $extra_arrow_length > $pad ? $extra_arrow_length : $pad; } sub draw_connectors { my $self = shift; my ($gd,$dx,$dy) = @_; my $part; my $strand = $self->feature->strand; $strand *= -1 if $self->{flip}; #sigh if (my @parts = $self->parts) { $part = $strand >= 0 ? $parts[-1] : $parts[0]; } elsif ($self->feature_has_subparts) { # no parts -- so draw an intron spanning whole thing my($x1,$y1,$x2,$y2) = $self->bounds(0,0); $self->_connector($gd,$dx,$dy,$x1,$y1,$x1,$y2,$x2,$y1,$x2,$y2); $part = $self; } else { return; } my @rect = $part->bounds(); my $width = abs($rect[2] - $rect[0]); my $filled = $width >= MIN_WIDTH_FOR_ARROW; if ($filled) { $self->Bio::Graphics::Glyph::generic::draw_connectors(@_); } else { $self->SUPER::draw_connectors(@_); } } sub draw_component { my $self = shift; return unless $self->level > 0; my $gd = shift; my ($left,$top) = @_; my @rect = $self->bounds(@_); my $f = $self->feature; my $strand = $f->strand; my $str = $strand * ($self->{flip} ? -1 : 1); my $width = abs($rect[2] - $rect[0]); my $filled = defined($self->{partno}) && $width >= MIN_WIDTH_FOR_ARROW; my ($pwidth) = $gd->getBounds; $filled = 0 if $str < 0 && $rect[0] < $self->panel->pad_left; $filled = 0 if $str > 0 && $rect[2] > $pwidth - $self->panel->pad_right; if ($self->stranded && $filled) { my ($first,$last) = ($self->{partno} == 0 , $self->{partno} == $self->{total_parts}-1); ($first,$last) = ($last,$first) if $self->{flip}; if ($strand < 0 && $first) { # first exon, minus strand transcript $self->filled_arrow($gd,-1,@rect); } elsif ($strand >= 0 && $last) { # last exon, plus strand $self->filled_arrow($gd,+1,@rect); } else { $self->filled_box($gd,@rect); } } else { $self->filled_box($gd,@rect); } # copied from generic::draw_component $self->draw_translation($gd,@_) if $self->{cds_translation}; # created earlier by calculate_cds() $self->draw_sequence($gd,@_) if $self->option('draw_dna') && $self->dna_fits; } sub bump { my $self = shift; return $self->SUPER::bump(@_) if $self->all_callbacks; return 0; # never allow our components to bump } 1; __END__ =head1 NAME Bio::Graphics::Glyph::transcript2 - The "transcript2" glyph =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This glyph is used for drawing transcripts. It is like "transcript" except that if there is sufficient room the terminal exon is shaped like an arrow in order to indicate the direction of transcription. If there isn't enough room, a small arrow is drawn. =head2 OPTIONS The following options are standard among all Glyphs. See L for a full explanation. Option Description Default ------ ----------- ------- -fgcolor Foreground color black -outlinecolor Synonym for -fgcolor -bgcolor Background color turquoise -fillcolor Synonym for -bgcolor -linewidth Line width 1 -height Height of glyph 10 -font Glyph font gdSmallFont -connector Connector type 0 (false) -connector_color Connector color black -label Whether to draw a label 0 (false) -description Whether to draw a description 0 (false) -strand_arrow Whether to indicate 0 (false) strandedness -hilite Highlight color undef (no color) In addition, the alignment glyph recognizes the following glyph-specific options: Option Description Default ------ ----------- ------- -arrow_length Length of the directional 8 arrow. =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Lincoln Stein Elstein@cshl.orgE Copyright (c) 2001 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/pairplot.pm000555001750001750 2165712165075746 22375 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::pairplot; # Triangle plot for showing pairwise quantitative relationships. # Similar to pairwise_plot, which was originally distributed in GBrowse, # except that the signal intensity is handled using a callback that takes # the two features to be compared. sub my_description { return < [ 'CODEREF', undef, 'Pass a coderef to a subroutine with the following signature: sub ($$). The', 'two arguments are the first and second feature to compare. Return a floating point', 'number to indicate the score at the intersection of these two features.' ], min_score => [ 'float', 0.0, 'Minimum possible pairwise score.' ], max_score => [ 'float', 1.0, 'Maximum possible pairwise score.' ], angle => [ 'float', 45, 'Angle between the side of the triangle and the base, in degrees.' ], } } use strict; use Math::Trig; use base 'Bio::Graphics::Glyph::generic'; sub maxdepth { my $self = shift; my $md = $self->Bio::Graphics::Glyph::maxdepth; return $md if defined $md; return 1; } # return angle in radians sub angle { my $self = shift; my $angle = $self->{angle} ||= $self->option('angle') || 45; $self->{angle} = shift if @_; deg2rad($angle); } sub slope { my $self = shift; return $self->{slope} if exists $self->{slope}; return $self->{slope} = tan($self->angle); } sub x2y { my $self = shift; shift() * $self->slope; } sub intercept { my $self = shift; my ($x1,$x2) = @_; my $mid = ($x1+$x2)/2; my $y = $self->x2y($mid-$x1); return (int($mid+0.5),int($y+0.5)); } # height calculated from width sub layout_height { my $self = shift; return $self->{height} if exists $self->{height}; return $self->{height} = $self->x2y($self->width)/2; } sub min_score { my $self = shift; my $min = $self->option('min_score'); return 0 unless defined $min; } sub max_score { my $self = shift; my $max = $self->option('max_score'); return 1.0 unless defined $max; } sub calculate_color { my $self = shift; my ($s,$rgb) = @_; return $self->{colors}{$s} if exists $self->{colors}{$s}; my $max_score = $self->max_score; my $min_score = $self->min_score; my $scale = 255/($max_score-$min_score); my $value = ($s-$min_score) * $scale; # will range from 0 to 255 return $self->{colors}{$s} = $self->panel->translate_color(map { 255 - $value} @$rgb); } sub draw { my $self = shift; my $gd = shift; my ($left,$top,$partno,$total_parts) = @_; my $fgcolor = $self->fgcolor; my ($red,$green,$blue) = $self->panel->rgb($self->bgcolor); my @points = $self->get_points(); $gd->line($self->left+$left, $top+1, $self->right+$left,$top+1, $fgcolor); my $points = $self->option('point'); my @parts = sort {$a->left<=>$b->left} $self->parts; $_->draw_component($gd,$left,$top-10) foreach @parts; # assumption: parts are not overlapping if ($points) { @points = map { int (($parts[$_]->right+$parts[$_+1]->left)/2)} (0..$#parts-1); unshift @points,int($parts[0]->left); push @points,int($parts[-1]->right); } for (my $ia=0;$ia<@parts-1;$ia++) { for (my $ib=$ia+1;$ib<@parts;$ib++) { my ($l1,$r1,$l2,$r2); if (@points) { ($l1,$r1) = ($points[$ia]+1,$points[$ia+1]-1); ($l2,$r2) = ($points[$ib]+1,$points[$ib+1]-1); } else { ($l1,$r1) = ($parts[$ia]->left,$parts[$ia]->right); ($l2,$r2) = ($parts[$ib]->left,$parts[$ib]->right); } my $intensity = eval{$self->feature->pair_score($parts[$ia],$parts[$ib])}; warn $@ if $@; $intensity = 1.0 unless defined $intensity; my $c = $self->calculate_color($intensity,[$red,$green,$blue]); # left corner my ($lcx,$lcy) = $self->intercept($l1,$l2); my ($tcx,$tcy) = $self->intercept($r1,$l2); my ($rcx,$rcy) = $self->intercept($r1,$r2); my ($bcx,$bcy) = $self->intercept($l1,$r2); my $poly = GD::Polygon->new(); $poly->addPt($lcx+$left,$lcy+$top); $poly->addPt($tcx+$left,$tcy+$top); $poly->addPt($rcx+$left,$rcy+$top); $poly->addPt($bcx+$left,$bcy+$top); $gd->filledPolygon($poly,$c); } } } sub get_points { my $self = shift; my @points; my @parts = $self->parts; return unless @parts; for my $g (@parts) { push @points,$g->left; push @points,$g->right; } @points; } # never allow our internal parts to bump; sub bump { 0 } 1; __END__ =head1 NAME Bio::Graphics::Glyph::pairplot - The "pairwise plot" glyph =head1 SYNOPSIS use Bio::Graphics; # create the panel, etc. See Bio::Graphics::Panel # for the synopsis # Create one big feature using the PairFeature # glyph (see end of synopsis for an implementation) my $block = PairFeature->new(-start=> 2001, -end => 10000); # It will contain a series of subfeatures. my $start = 2001; while ($start < 10000) { my $end = $start+120; $block->add_SeqFeature($bsg->new(-start=>$start, -end =>$end ),'EXPAND'); $start += 200; } $panel->add_track($block, -glyph => 'pairplot', -angle => 45, -bgcolor => 'red', -point => 1, ); print $panel->png; package PairFeature; use base 'Bio::SeqFeature::Generic'; sub pair_score { my $self = shift; my ($sf1,$sf2) = @_; # simple distance function my $dist = $sf2->end - $sf1->start; my $total = $self->end - $self->start; return sprintf('%2.2f',1-$dist/$total); } =head1 DESCRIPTION This glyph draws a "triangle plot" similar to the ones used to show linkage disequilibrium between a series of genetic markers. It is basically a dotplot drawn at a 45 degree angle, with each diamond-shaped region colored with an intensity proportional to an arbitrary scoring value relating one feature to another (typically a D' value in LD studies). This glyph requires more preparation than other glyphs. First, you must create a subclass of Bio::SeqFeature::Generic (or Bio::Graphics::Feature, if you prefer) that has a pair_score() method. The pair_score() method will take two features and return a numeric value between 0.0 and 1.0, where higher values mean more intense. You should then create a feature of this new type and use add_SeqFeature() to add to it all the genomic features that you wish to compare. Then add this feature to a track using the pairplot glyph. When the glyph renders the feature, it will interrogate the pair_score() method for each pair of subfeatures. =head2 OPTIONS In addition to the common options, the following glyph-specific options are recognized: Option Description Default ------ ----------- ------- -point If true, the plot will be 0 drawn relative to the midpoint between each adjacent subfeature. This is appropriate for point-like subfeatures, such as SNPs. -angle Angle to draw the plot. Values 45 between 1 degree and 89 degrees are valid. Higher angles give a more vertical plot. -bgcolor The color of the plot. cyan =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Lincoln Stein Elstein@cshl.edu. Copyright (c) 2004 Cold Spring Harbor Laboratory This package and its accompanying libraries is free software; you can redistribute it and/or modify it under the terms of the GPL (either version 1, or at your option, any later version) or the Artistic License 2.0. Refer to LICENSE for the full license text. In addition, please see DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/spectrogram.pm000555001750001750 1412612165075746 23062 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::spectrogram; use strict; use Bio::Graphics::Glyph::generic; use GD::Simple; use GD; use List::Util qw/sum max/; use Data::Dumper; use vars '@ISA'; @ISA = 'Bio::Graphics::Glyph::generic'; # saturation value is fixed at the maximum use constant SAT => 255; use constant GDS => GD::Simple->new(1,1); # each spectrogram feature will be a standalone # (unaggregated) feature. sub draw { my $self = shift; my $gd = shift; my ( $x1, $y1, $x2, $y2 ) = $self->bounds(@_); my $v_offset = $y1; my $last_y = 0; my $last_label; my $height = $self->option('height'); my $win = $self->option('win'); my $feat = $self->feature; # API change? my $att; if (ref $feat->attributes) { $att = $feat->attributes; } else { $att = {$feat->attributes}; } my $max = $att->{max}; my $black = $gd->colorResolve(0,0,0); my %seen; my $rows = @{$att->{g}}; my $step = int $height/$rows; my $lbl_type = shift @{$att->{labels}} if defined $att->{labels}; for my $g (@{$att->{g}}) { my $a = shift @{$att->{a}}; my $t = shift @{$att->{t}}; my $c = shift @{$att->{c}}; my $lbl = shift @{$att->{labels}} if defined $att->{labels}; my ($hue,$bri) = get_bg_color($g,$a,$t,$c); $hue = int(($hue/360) * 255); $hue += 255 if $hue < 0; $hue -= 255 if $hue > 255; $bri = int(($bri/$max) * 255); my @rgb = GDS->HSVtoRGB($hue,SAT,$bri); my $bgcolor = $gd->colorResolve(@rgb); $self->filled_box($gd, $x1, $y1, $x2, $y1+$step, $bgcolor, $bgcolor); if ($lbl && $y1 > $last_y+10) { my $label = sprintf '%4s', int $lbl; # print labels for the number closest to an integer # we use the previous value to catch the transition if ($last_label ne $label) { $gd->string(gdSmallFont, $x1-25, $last_y-5, $last_label, $black); } # this will hopefully catch the label at the bottom of the stack elsif (!$att->{labels}->[0]) { $gd->string(gdSmallFont, $x1-25, $y1-5, $label, $black); } $last_y = $y1 + 15; $last_label = $label; } if ($lbl) { $gd->stringUp(gdSmallFont, $x1-27, $y2-5, $lbl_type, $black); } $y1 += $step; } } # HSV color space: # Hue (0-360 degrees) # Saturation (0-100) # Brightness (0-100) sub get_bg_color { my ($g,$a,$t,$c) = @_; my $total = sum(@_) || return (0,0); my $max = max (@_); my $angle; # Assign the angular coordinate for the # dominant base (>50% of signal) if ($max == $g && $max >= $total/2) { $angle = 60; # yellow } elsif ($max == $a && $max >= $total/2) { $angle = 240; # blue } elsif ($max == $t && $max >= $total/2) { $angle = 0; # red } elsif ($max == $c && $max >= $total/2) { $angle = 120; # green } # or else take the weighted average coordinate # This is not perfect, as the coordinates are not # equidistant, but most spots will fall into # the above category else { my $acg = 60*$g + 120*$c + 240*$a; my $t_angle = $acg/($g + $c + $a) > 180 ? 360 : 1; $angle = ($t_angle*$t + $acg)/$total; } $angle += 0.5; return (int($angle), $total); } # make sure bumping is off to get an aligned spectrogram sub bump { 0 } 1; =head1 NAME Bio::Graphics::Glyph::spectrogram - The "spectrogram" glyph =head1 SYNOPSIS See L, L and L =head1 DESCRIPTION This glyph is designed to draw DNA spectrograms for the Spectrogram plugin. It is not meant to be used as a standalone glyph and has few public options. Most of the glyph's behavior is controlled via the spectrogram plugin. The glyph expects unaggregated 1D spectrogram features, each of which is a vertical column, with one row for each integer frequency. The number of frequencies is controlled by the window size and/or the Spectrogram plugin. The width of the feature corresponds to the size of the overlap between adjacent windows. The values for each frequency are in four channels, one for each base. The color of each row ("spot") represents the dominant base(s) and the intensity represents the magnitude of the signal at that frequency The entire 2D spectrogram is a series of unaggregated, unbumped 1D spectrogram features. The spectrogram glyph assigns colors using the HSV color space, where an angular coordinate for hue is assigned to each base (G yellow [60]; A blue [240]; C green [120]; T red[0/360]). The saturation value is fixed at the maximum of 100 and the brightness value is scaled according to the magnitude for each frequency, ranging from black to the pure hue. The hue is determined in one of two ways: If the signal for one base is dominant (> 50% of total for the four channels) the angular coordinate for that base is used. The brightness is calculated using the total signal from all four channels. If no base has a dominant signal, the weighted average angular coordinate is calculated using the relative contribution from each channel. The brightness is calculated from the total signal from all four channels. The y-axis labels require at least 40 pixels of left-padding. They will be truncated if less than 40 of padding is specified in the configuration file. =head2 OPTIONS The following standard options are accepted: Option Description Default ------ ----------- ------- -height Height of glyph calculated -bump Whether to bump features off The following glyph-specific options are also used: -win window size used to calculate calculated the spectrogram values =head1 BUGS Please report them. =head1 AUTHOR Sheldon McKay Emckays@cshl.orgE. Copyright (c) 2006 Cold Spring Harbor Laboratory This package and its accompanying libraries is free software; you can redistribute it and/or modify it under the terms of the GPL (either version 1, or at your option, any later version) or the Artistic License 2.0. Refer to LICENSE for the full license text. In addition, please see DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/saw_teeth.pm000555001750001750 411512165075746 22474 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::saw_teeth; # DAS-compatible package to use for drawing a line of saw teeth use strict; use base qw(Bio::Graphics::Glyph::repeating_shape); sub draw_repeating_shape { my ($self, $gd, $x1, $y1, $x2, $y2, $fg) = @_; my $midX = ($x2-$x1) / 2 + $x1; $gd->line($x1,$y2,$midX,$y1,$fg); $gd->line($midX,$y1,$x2,$y2,$fg); } 1; __END__ =head1 NAME Bio::Graphics::Glyph::saw_teeth - The "saw teeth" glyph =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This glyph draws a line of saw teeth. =head2 OPTIONS In addition to the common options, the following glyph-specific options are recognized: Option Description Default ------ ----------- ------- -width Width of one tooth 10 -interval Interval between teeth 10 =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Vsevolod (Simon) Ilyushchenko Esimonf@cshl.eduE. Copyright (c) 2004 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/fb_shmiggle.pm000444001750001750 5332212165075746 23000 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::fb_shmiggle; # [ to see proper formatting set tab==2 ] # # 2009-2010 Victor Strelets, FlyBase.org use constant DEBUG => 0; #use strict; use GD; use vars '@ISA'; use Bio::Graphics::Glyph::generic; BEGIN{ @ISA = 'Bio::Graphics::Glyph::generic'; local($colors_selected,$black,$red,$white,$grey); @colors= (); @colcycle= ( # red/orange 'A62A2A', # + 'CC3299', # + 'FF7F00', # + # yellow/brown 'B87333', # + # greenish '6B8E23', # + '238E68', # + # blue/violet '6982CA', # + '2222AD', # + ); %Indices= (); local(*DATF); } #-------------------------- sub draw { my $self = shift; my $gd = shift; my ($left,$top,$partno,$total_parts) = @_; my $ft= $self->feature; my $ftid= $ft->{id}; my $pn= $self->panel; my $fgc = $self->fgcolor; my $bgc = $self->bgcolor; my($r,$g,$b); unless( $colors_selected ) { $black= $gd->colorClosest(0,0,0); $yellow= $gd->colorClosest(255,255,0); $white= $gd->colorClosest(255,255,255); $red= $gd->colorClosest(255,0,0); $lightgrey= $gd->colorClosest(225,225,225); $grey= $gd->colorClosest(200,200,200); $darkgrey= $gd->colorClosest(125,125,125); foreach my $ccc ( @colcycle ) { if( $ccc=~/^[#]?(\S\S)(\S\S)(\S\S)$/ ) { ($r,$g,$b)= ($1,$2,$3); eval('$r=hex("0x'.$r.'");'); eval('$g=hex("0x'.$g.'");'); eval('$b=hex("0x'.$b.'");'); } my $c= $gd->colorClosest($r,$g,$b); push(@colors,$c); } $colors_selected= 1; } my($pnstart,$pnstop)= ($pn->start,$pn->end); # in seq coordinates my $nseqpoints= $pnstop - $pnstart + 1; my($xf1,$yf1,$xf2,$yf2)= $self->calculate_boundaries($left,$top); my $leftpad= $pn->pad_left; my $datadir= $ENV{SERVER_PATH} . $ft->{datadir}; my($start,$stop)= $pn->location2pixel(($ft->{start},$ft->{end})); my $ftscrstop= $stop + $leftpad; my $ftscrstart= $start + $leftpad; my $chromosome= $ft->{ref}; #warn("pn start stop leftpad nseq dir = $pnstart $pnstop $leftpad $datadir $chromosome\n"); my $flipped= $self->{flip} ? 1 : 0; my($subsets,$subsetsnames,$signals)= $self->getData($ft,$datadir,$chromosome,$pnstart,$pnstop,$xf1,$xf2,$flipped); my $poly_pkg = $self->polygon_package; my @orderedsubsets= @{$subsets}; my $nsets= $#orderedsubsets+1; my($xstep,$ystep)= (2,int(100.0/$nsets)); $ystep= 7 unless $ystep>=7; # empiricaly found - to read lines of tiny fonts $ystep= 12 if $ystep>12; # empirically found - to preserve topo feel when number of subsets is small $ystep= 7 if $ystep>7; # tmp unification my($xw,$yw)= ( $nsets*$xstep, ($nsets-1)*$ystep ); my $polybg= $poly_pkg->new(); $polybg->addPt($xf1,$yf2-$yw); $polybg->addPt($xf2,$yf2-$yw); $polybg->addPt($xf2-$xw, $yf2); $polybg->addPt($xf1-$xw, $yf2); $gd->filledPolygon($polybg,$lightgrey); # background for( my $xx= $xf1+2; $xx<$xf2; $xx+=6 ) { $gd->line($xx,$yf2-$yw,$xx-$xw,$yf2,$grey); } # grid-helper my $xshift= 0; my $yshift= $nsets * $ystep; ($r,$g,$b)= (10,150,80); my $colcycler= 0; my @screencoords= @{$signals->{screencoords}}; my $max_signal= 30; my $koeff= 4; if( exists $signals->{max_signal} ) { $max_signal= $signals->{max_signal}; $koeff= 80.0/$max_signal; } my $predictor_cutoff= int($max_signal*0.95); # empirically found my @prevx= (); my @prevy= (); my @prevvals= (); my $profilen= 0; my %SPEEDUP= (); foreach my $subset ( @orderedsubsets ) { my $edgecolor= ($xshift==0) ? $black : $yellow; #my $color= ($xshift==0) ? $darkgrey : $gd->colorClosest(oct($r),oct($g),oct($b)); my $color= ($profilen==0) ? $darkgrey : $colors[$colcycler]; $xshift -= $xstep; $yshift -= $ystep; my @values= @{$signals->{$subset}}; my($xold,$yold)= ($xf1+$xshift,$yf2-$yshift+1); my $xpos= 0; my $poly= $poly_pkg->new(); $poly->addPt($xold,$yold+1); my @allx= ($xold); my @ally= ($yold); my @allvals= (0); my $runx= $xf1 + $xshift; foreach my $val ( @values ) { $scrx += $leftpad; my $x= $screencoords[$xpos] + $xshift; my $visval; if( exists $SPEEDUP{$val} ) { $visval= $SPEEDUP{$val}; } else { $visval= int($val*$koeff); $SPEEDUP{$val}= $visval; } my $y= $yf2 - $yshift - $visval; push(@allx,$x); push(@ally,$y); push(@allvals,$visval); if( $xpos>0 ) { $poly->addPt($x,$y+1); } ($xold,$yold)= ($x,$y); $xpos++; } $poly->addPt($xf2+$xshift, $yf2-$yshift+1); $gd->filledPolygon($poly,$color) unless $profilen==0; # not on MAX predictor ($xold,$yold)= ($allx[0],$ally[0]); for( my $en=1; $en<=$#allx; $en++ ) { my $x= $allx[$en]; my $y= $ally[$en]; $gd->line($xold,$yold,$x,$y,$edgecolor); ($xold,$yold)= ($x,$y); } if( $profilen==0 ) { # drawing mRNA (cutoff-based) predictor on MAX subset my($xxx,$yyy)= ($allx[1]-1,$yf2-$yw); $gd->line($xxx-4,$yyy,$xxx-2,$yyy,$black); $gd->string(GD::Font->Tiny,$xxx-12, $yyy-3,'0',$black); $gd->line($xxx-2,$yyy,$xxx-2,$yyy-50,$black); $gd->line($xxx-4,$yyy-47,$xxx-2,$yyy-50,$black); $gd->line($xxx,$yyy-47,$xxx-2,$yyy-50,$black); $gd->line($xxx-4,$yyy-44,$xxx-2,$yyy-44,$black); $gd->string(GD::Font->Tiny,$xxx-18, $yyy-47,$max_signal,$black); my($inexon,$exstart,$exend,$ymax)= (0,0,0,999); for( my $en=1; $en<=$#allx; $en++ ) { my $y= $ally[$en]; $ymax= $y if $y < $ymax; if( $allvals[$en]>=$predictor_cutoff ) { my $x= $allx[$en]; unless( $inexon ) { $inexon= 1; $exstart= $x; } # start exon $exend= $x; } elsif( $inexon ) { # end exon and draw it $inexon= 0; $ymax -= 6; my $allowedymax= $yf2-$yshift-45; $ymax= $allowedymax if $ymax < $allowedymax; # set limit for huge peaks $gd->line($exstart,$ymax,$exstart,$ymax+2,$red); $gd->line($exstart,$ymax,$exend,$ymax,$red); $gd->line($exend,$ymax,$exend,$ymax+2,$red); ($inexon,$exstart,$exend,$ymax)= (0,0,0,999); } } if( $inexon ) { # exon which ends beyond this screen $ymax -= 6; my $allowedymax= $yf2-$yshift-45; $ymax= $allowedymax if $ymax < $allowedymax; # set limit for huge peaks $gd->line($exstart,$ymax,$exstart,$ymax+2,$red); $gd->line($exstart,$ymax,$exend,$ymax,$red); } } if( 0 && $profilen>1 ) { # blocked - drawing roof lines doesn't work well with this view.. for( my $en=3; $en<=$#allx; $en+=6 ) { next if $allvals[$en]==0 || $prevvals[$en]==0; my $y= $ally[$en]; $yold= $prevy[$en]; if( $yold<$y ) { my $x= $allx[$en]; $xold= $prevx[$en]; $gd->line($xold,$yold,$x,$y,$darkgrey); } } } $gd->string(GD::Font->Tiny,$xf2+$xshift+3, $yf2-$yshift-5,$subsetsnames->{$subset},$color); $colcycler++; $colcycler= 0 if $colcycler>$#colors; unless( $profilen==0 ) { @prevx= @allx; @prevy= @ally; @prevvals= @allvals; } $profilen++; } return; } #-------------------------- sub getData { my $self = shift; my($ft,$datadir,$chromosome,$start,$stop,$scrstart,$scrstop,$flipped) = @_; my %Signals= (); $self->openDataFiles($datadir); my @subsets= (exists $ft->{'subsetsorder'}) ? @{$ft->{'subsetsorder'}} : sort split(/\t+/,$Indices{'subsets'}); shift(@subsets) if $subsets[0] eq 'MAX'; warn("subsets: @subsets\n") if DEBUG; my %SubsetsNames= (exists $ft->{'subsetsnames'}) ? %{$ft->{'subsetsnames'}} : map { $_, $_ } @subsets; $SubsetsNames{MAX}= 'MAX'; my $screenstep= ($scrstop-$scrstart+1) * 1.0 / ($stop-$start+1); my $donecoords= 0; foreach my $subset ( @subsets ) { my $nstrings= 0; # scan seq ranges offsets to see where to start reading my $key= $subset.':'.$chromosome; my $poskey= $key.':offsets'; my $ranges_pos= (exists $Indices{$poskey}) ? int($Indices{$poskey}) : -1; if( $ranges_pos == -1 ) { next; } # no such signal.. warn(" positioning for $poskey starts at $ranges_pos\n") if DEBUG; if( $start>=1000000 ) { my $bigstep= int($start/1000000.0); if( exists $Indices{$key.':offsets:'.$bigstep} ) { my $jumpval= $Indices{$key.':offsets:'.$bigstep}; warn(" jump in offset search to $jumpval\n") if DEBUG; $ranges_pos= int($jumpval); } } seek(DATF,$ranges_pos,0); my($offset,$offset1)= (0,0); my $lastseqloc= -999999999; my $useoffset= 0; while( (my $strs=) ) { $nstrings++ if DEBUG; if( DEBUG ) { chop($strs); warn(" positioning read for coord $start ($strs)\n"); } last unless $strs=~m/^(-?\d+)[ \t]+(\d+)/; my($seqloc,$fileoffset)= ($1,$2); if( DEBUG ) { chop($strs); warn(" positioning read for $poskey => $seqloc, $fileoffset ($strs)\n"); } $offset1= $offset; $offset= $fileoffset; $lastseqloc= $seqloc; if( $seqloc > $start ) { $useoffset= int($offset1); last; } } warn(" will use offset $useoffset\n") if DEBUG; warn(" (scanned $nstrings offset strings)\n") if DEBUG; if( $useoffset==0 ) { # data offset cannot be 0 - means didn't find where to read required data.. next; my @emptyvals= (); for( my $ii= $scrstart; $ii++ <= $scrstop; ) { push(@emptyvals,0); } $Signals{$subset}= \@emptyvals; } $nstrings= 0; # read signal profile seek(DATF,$useoffset,0); $lastseqloc= -999999999; my $lastsignal= 0; my($scrx,$scrxold)= ($scrstart,$scrstart-1); my $runmax= 0; my @values= (); my @xscreencoords= (); while( (my $str=) ) { $nstrings++ if DEBUG; unless( $str=~m/^(-?\d+)[ \t]+(\d+)/ ) { warn(" header read: $str") if DEBUG; last; # because no headers were indexed at the beginning of data packs } my($seqloc,$signal)= ($1,$2); warn(" signal read: $seqloc, $signal line: $str") if DEBUG; last if $lastseqloc > $seqloc; # just in case, as all sits merged in one file.. if( $seqloc>=$start ) { # current is the next one after the one we need to start from.. unless( $lastseqloc== -999999999 ) { # expand previous $lastseqloc= $start-2 if $lastseqloc<$start; # limit empty steps (they may start from -200000) while( $lastseqloc < $seqloc ) { # until another (one we just retrieved) wiggle reading last if $lastseqloc > $stop; # end of subset data next if $lastseqloc++ < $start; # we have actual new seq position in our required range my $scrpos= int($scrx); $runmax= $lastsignal if $runmax < $lastsignal; if( $scrpos != $scrxold ) { # we have actual new seq _and_ screen position push(@values,$runmax); push(@xscreencoords,$scrpos) unless $donecoords; $scrxold= $scrpos; $runmax= 0; } $scrx += $screenstep; # remember - it is not integer } } } ($lastseqloc,$lastsignal)= ($seqloc,$signal); last if $seqloc > $stop; # end of subset data } if( $lastseqloc < $stop ) { # if on the end of signal profile, but still in screen range # just assume that we are getting one more reading with signal == 0 my $signal= 0; while( $lastseqloc++ < $stop ) { my $scrpos= int($scrx); if( $scrpos != $scrxold ) { # we have actual new seq _and_ screen position push(@values,$signal); push(@xscreencoords,$scrpos) unless $donecoords; $scrxold= $scrpos; } $scrx += $screenstep; } } warn(" (scanned $nstrings signal strings)\n") if DEBUG; $nstrings= 0; if( $flipped ) { my @ch= reverse @values; @values= @ch; } warn(" ".$subset."=> ".@values." values @values\n") if DEBUG && $#values<1000; $Signals{$subset}= \@values; $Signals{screencoords}= \@xscreencoords unless $donecoords; $donecoords= 1; } # foreach my $subset ( @subsets ) { if( exists $Indices{max_signal} ) { $Signals{max_signal}= $Indices{max_signal}; warn(" max_signal=> ".$Indices{max_signal}." \n") if DEBUG; } # prepare MAX profile - will be used as a base for exon/UTR prediction my @maxprofile= (); my @ruler= @{$Signals{screencoords}}; for( my $npos= 0; $npos<=$#ruler; $npos++ ) { my $maxval= 0; foreach my $subset ( @subsets ) { my $p= $Signals{$subset}; my $val= $p->[$npos]; $maxval= $val if $maxval < $val; } push(@maxprofile,$maxval); } $Signals{MAX}= \@maxprofile; warn(" MAX=> ".@maxprofile." values @maxprofile\n") if DEBUG && $#maxprofile<1000; unshift(@subsets,'MAX'); return(\@subsets,\%SubsetsNames, \%Signals); } #-------------------------- sub openDataFiles { my $self = shift; my $datadir= shift; $datadir.= '/' unless $datadir=~m|/$|; my $datafile= $datadir.'data.cat'; open(DATF,$datafile) || warn("cannot open $datafile\n"); use BerkeleyDB; # caller should already used proper 'use lib' command with path my $bdbfile= $datadir . 'index.bdbhash'; tie %Indices, "BerkeleyDB::Hash", -Filename => $bdbfile, -Flags => DB_RDONLY || warn("can't read BDBHash $bdbfile\n"); if( DEBUG ) { foreach my $kk ( sort keys %Indices ) { warn(" $kk => ".$Indices{$kk}."\n"); } } return; } 1; =pod =head1 TopoView Glyph =begin html Warning: This software is still in the developmental stage and is distributed "as is", without packaging and in the same exact condition as currently used by FlyBase. You are free to use it, modify and develop further, but proper reference to the original author and FlyBase is required.

"fb_shmiggle.pm" TopoView (AKA shmiggle) glyph was developed for fast 3D-like demonstration of RNA-seq data consisting of multiple individual subsets. Main purposes were to compact presentation as much as possible (in one reasonably sized track) and to allow easy visual detection of coordinated behavior of the expression profiles of different subsets.

It was found that log2 conversion dramatically changes perception of expression profiles and kind of illuminates coordinated behavior of different subsets. Glyph and data indexer/formatter were in fact modified with the assumption that final data produced by indexer/formatter will always be a log2 conversion of the original coverage, therefore represented by short integer with values in range of 0-200 or so.

Comparing performance (retrieval of several Kbp of data profiles for several subsets of some RNA-seq experiment) of wiggle binary method and of several possible alternatives, it was discovered that one of the approaches remarkably outperforms wiggle bin method (although it requires several times more space for formatted data storage). Optimal storage/retrieval method stores all experiment data (all subsets of the experiment) in one text file, where structure of the file in fact is one of the most simple wiggle (coverage files) formats with the addition of some positioning data (two-column format, without runlength specification, without omission of zero values). This is the only format which glyph is able to handle (there are many reasons for that) so any modification of indexer/formatter _must_ produce exact equivalent of that format. In my experience, 90% of the debugging with new incoming data was related to the problems of that exact format conversion. Example of the formatted data:

# subset=BS107_all_unique chromosome=2LHet
-200000 0
0       0
19955   1
19959   0
19967   2
19972   0
19977   2
20027   0
20031   2
20035   0
20043   1
20045   0
20049   1
20055   0
20062   2
20069   0
20073   2
20082   0
20097   3
20115   0
20125   3
20127   0
20134   3
20139   0
20140   3
20144   0
20145   3
20150   0
20157   3
20162   0
20172   3
20183   0

Glyph is supplied with a "index_cov_files.pl" data indexer/formatter which is converting original coverage (wiggle) files into data structure which will be used for fast retrieval. You should run this script in some separate directory, containing original coverage files (gzipped form works too). After it finishes, directory will contain two new files: data.cat and index.bdbhash. Both files required for data retrieval by glyph. Files can be moved freely between different directories or even operational systems (Mac and PC included, I think). Content of the dat file is subject of accurate check - this is if you want to avoid long debugging sessions on the level of running GBrowse. Size of files is quite big, but in my experience it is like twice less than gzipped size of all initial coverage files - which is quite acceptable.

Example of GBrowse conf file insert (shows actual FlyBase config sections for Baylor and modENCODE RNA-seq tracks):

[baylor_wiggle]
feature       = RNAseq_profile:Baylor
glyph         = fb_shmiggle
height        = 124
bgcolor       = sub { my $f= shift;
        $f->{datadir}= '/.data/genomes/dmel/current/rnaseq-gff/baylor/'; # trick it this way..
        my @subsetsorder= qw(
                E2-4hr
                E2-16hr
                E2-16hr100
                E14-16hr
                L
                L3i
                L3i100
                P
                P3d
                MA3d
                FA3d
                A17d
                );
        $f->{subsetsorder}= \@subsetsorder;
        return 'lightgrey';
        }
key           = Baylor group RNA-seq coverage by subsets (devel.stages) [log2 converted]
category      = RNA-seq data
label         = ""
title         = ""
link = sub { my $f= shift;
  my $id= $f->{'id'};
  my $lnk="javascript:void(0);";
  "$lnk\" id=\"$id\" onmouseover=\"showdata_description('Baylor');return false;\" onmouseout=\"delsumm_overlib();";
  }

[celniker_wiggle]
feature       = RNAseq_profile:Celniker 
glyph 				= fb_shmiggle
height      	= 250
bgcolor       = sub { my $f= shift;
	$f->{datadir}= '/.data/genomes/dmel/current/rnaseq-gff/celniker/'; # trick it this way..
	my @subsetsorder= qw(
		BS40_all_unique
		BS43_all_unique
		BS46_all_unique
		BS49_all_unique
		BS54_all_unique
		BS55_all_unique
		BS58_all_unique
		BS62_all_unique
		BS66_all_unique
		BS67_all_unique
		BS71_all_unique
		BS73_all_unique
		BS107_all_unique
		BS111_all_unique 
		BS113_all_unique 
		BS196_all_unique 
		BS200_all_unique 
		BS203_all_unique 
		BS129_all_unique 
		BS133_all_unique 
		BS136_all_unique 
		BS137_all_unique 
		BS140_all_unique 
		BS143_all_unique 
		BS150_all_unique 
		BS156_all_unique 
		BS162_all_unique 
		BS153_all_unique 
		BS159_all_unique 
		BS165_all_unique 
		);
	$f->{subsetsorder}= \@subsetsorder;
	my %subsetsnames= qw(
		BS40_all_unique em0-2hr
		BS43_all_unique em2-4hr
		BS46_all_unique em4-6hr
		BS49_all_unique em6-8hr
		BS54_all_unique em8-10hr
		BS55_all_unique em10-12hr
		BS58_all_unique em12-14hr
		BS62_all_unique em14-16hr
		BS66_all_unique em16-18hr
		BS67_all_unique em18-20hr
		BS71_all_unique em20-22hr
		BS73_all_unique em22-24hr
		BS107_all_unique L1
		BS111_all_unique L2
		BS113_all_unique L3_12hr
		BS196_all_unique L3_PS1-2
		BS200_all_unique L3_PS3-6
		BS203_all_unique L3_PS7-9
		BS129_all_unique WPP
		BS133_all_unique WPP_12hr
		BS136_all_unique WPP_24hr
		BS137_all_unique WPP_2days
		BS140_all_unique WPP_3days
		BS143_all_unique WPP_4days
		BS150_all_unique AdM_Ecl_1days
		BS156_all_unique AdM_Ecl_5days
		BS162_all_unique AdM_Ecl_30days
		BS153_all_unique AdF_Ecl_1days
		BS159_all_unique AdF_Ecl_5days
		BS165_all_unique AdF_Ecl_30days
		);
	$f->{subsetsnames}= \%subsetsnames;
	return 'lightgrey';
	}
key           = modENCODE Transcription Group RNA-seq coverage (unique reads only) by subsets (devel. stages) [log2 converted]
category      = RNA-seq data
label         = "" 
title         = ""
link = sub { my $f= shift;
  my $id= $f->{'id'};
	my $lnk="javascript:void(0);";
	"$lnk\" id=\"$id\" onmouseover=\"showdata_description('Celniker');return false;\" onmouseout=\"delsumm_overlib();";
	}

In configuration, it is very important to set 'datadir' variable (relative to server DOCUMENT_ROOT) so that glyph will know where to take data and index.

Setting 'subsetsorder' allows you to display expression profiles of subsets in some predefined order. If setting omitted, glyph will display sets in alphabetical order of the initial subsets names.

Setting 'subsetsnames' allows to rename subsets (very important as in most cases workflow names of subsets are unsutable for intelligent data display to end users). If setting omitted, initial subsets names will be used for display.

For the glyph to be properly activated, you need to insert in all of your GFF files (ones for which you have RNA-seq data) virtual contig-long features which will activate expression data display. To cover whole range of the contig (chromosome arm), it is better to use coordinates presented in 'sequence-region' definition at the top of GFF file. Example of such feature lines for FlyBase data is shown below:

2LHet   Baylor  RNAseq_profile  1       368874  .       +       .       Comment=This is a reference feature for RNAseq wiggle tracks
2L      Baylor  RNAseq_profile  1       23011544        .       +       .       Comment=This is a reference feature for RNAseq wiggle tracks
2RHet   Baylor  RNAseq_profile  1       3288763 .       +       .       Comment=This is a reference feature for RNAseq wiggle tracks
2R      Baylor  RNAseq_profile  1       21146708        .       +       .       Comment=This is a reference feature for RNAseq wiggle tracks
3LHet   Baylor  RNAseq_profile  1       2555493 .       +       .       Comment=This is a reference feature for RNAseq wiggle tracks
3L      Baylor  RNAseq_profile  1       24543557        .       +       .       Comment=This is a reference feature for RNAseq wiggle tracks
3RHet   Baylor  RNAseq_profile  1       2517509 .       +       .       Comment=This is a reference feature for RNAseq wiggle tracks
3R      Baylor  RNAseq_profile  1       27905053        .       +       .       Comment=This is a reference feature for RNAseq wiggle tracks
4       Baylor  RNAseq_profile  1       1351857 .       +       .       Comment=This is a reference feature for RNAseq wiggle tracks
XHet    Baylor  RNAseq_profile  1       204113  .       +       .       Comment=This is a reference feature for RNAseq wiggle tracks
X       Baylor  RNAseq_profile  1       22422827        .       +       .       Comment=This is a reference feature for RNAseq wiggle tracks
YHet    Baylor  RNAseq_profile  1       347040  .       +       .       Comment=This is a reference feature for RNAseq wiggle tracks

Questions about TopoView glyph should be directed to Victor Strelets (strelets@bio.indiana.edu). =end html Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/lightning.pm000555001750001750 1164212165075746 22517 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::lightning; # A lightning bolt glyph to add some pizazz to your displays. Yeow! use strict; use base qw(Bio::Graphics::Glyph::generic); sub my_description { return < [ [qw(N S)], 'N', 'Control the direction of the lightning bolt. One of', 'orth, or outh'], } } sub draw_component { my $self = shift; my $gd = shift; my $fg = $self->fgcolor; # find the center and vertices my ($x1,$y1,$x2,$y2) = $self->calculate_boundaries(@_); my $h = $self->option('height'); my $w = $h*0.6; my $poly_pkg = $self->polygon_package; my $polygon = $poly_pkg->new(); # lightning bolt points up or down if ($self->option('orient') eq 'N') { $y1 = $y1 + $h/4; $polygon->addPt($x2,$y1+($h/2)); $polygon->addPt($x2-($w*0.7),$y1+($h/2)); $polygon->addPt($x2-($w*0.2),$y1+($h*0.15)); $polygon->addPt($x2-($w*0.6),$y1+($h*0.15)); $polygon->addPt($x2,$y1-$h/2); $polygon->addPt($x2-($w*0.1),$y1-($h*0.05)); $polygon->addPt($x2+($w*0.5),$y1-($h*0.05)); $polygon->addPt($x2,$y1+($h/2)); } else { $y1 = $y1 + $h/2; $polygon->addPt($x1,$y1-($h/2)); $polygon->addPt($x1+($w*0.7),$y1-($h/2)); $polygon->addPt($x1+($w*0.2),$y1-($h*0.15)); $polygon->addPt($x1+($w*0.6),$y1-($h*0.15)); $polygon->addPt($x1,$y1+$h/2); $polygon->addPt($x1+($w*0.1),$y1+($h*0.05)); $polygon->addPt($x1-($w*0.5),$y1+($h*0.05)); $polygon->addPt($x1,$y1-($h/2)); } # Have to draw TWO polygons for fills in order to get an outline # because filledPolygon in GD croaks with extra parameters (and # doesn't support drawing of stroke anyways). if (my $c = $self->bgcolor) { $gd->filledPolygon($polygon,$c); $gd->polygon($polygon,$fg); } else { $gd->polygon($polygon,$fg); } } 1; __END__ =head1 NAME Bio::Graphics::Glyph::lightning - The "lightning" glyph =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This glyph draws a lightning bolt of specified height with relative width, with the point of the lightning bolt centered on the feature. The height of the bolt is specified by the "height" option. Due to the complexity of this glyph, it doesn't resolve well with heights less than 11 pixels. This glyph was designed to indicate point mutations on a nucleotide or protein backbone. =head2 OPTIONS The following options are standard among all Glyphs. See L for a full explanation. Option Description Default ------ ----------- ------- -fgcolor Foreground color black -outlinecolor Synonym for -fgcolor -bgcolor Background color turquoise -fillcolor Synonym for -bgcolor -linewidth Line width 1 -height Height of glyph 10 -font Glyph font gdSmallFont -connector Connector type 0 (false) -connector_color Connector color black -label Whether to draw a label 0 (false) -description Whether to draw a description 0 (false) -hilite Highlight color undef (no color) The following options are specific to this Glyph. Option Description Default ------ ----------- ------- -orient direction of lightning bolt N =head1 BUGS No reported bugs. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Todd Harris Eharris@cshl.orgE Copyright (c) 2004 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/minmax.pm000555001750001750 1116312165075746 22023 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::minmax; use strict; use base qw(Bio::Graphics::Glyph); sub my_description { return < [ 'float', undef, 'The minimum score of the quantitative range.'], max_score => [ 'float', undef, 'The maximum score of the quantitative range.'], bicolor_pivot => [ ['mean','zero','float','max','min','1SD','2SD','3SD'], undef, 'A value to pivot the display on. Typically this involves changing the color of the', 'glyph (and scale axis) depending on whether the feature is above or below the pivot value.', 'Provide "mean" to pivot on the mean of the data series, "zero" to pivot on the', 'zero value, "min" to pivot on the min and "max" on max of data series, also it is', 'possible to use any arbitrary integer or floating point number to pivot at that value.'], pos_color => [ 'color', undef, 'The color to use for values that exceed the bicolor_pivot value.'], neg_color => [ 'color', undef, 'The color to use for values that are below the bicolor_pivot value.'], }; } sub min_score { shift->option('min_score'); } sub max_score { shift->option('max_score'); } sub minmax { my $self = shift; my $parts = shift; # figure out the colors my $max_score = $self->max_score; my $min_score = $self->min_score; my $do_min = !defined $min_score; my $do_max = !defined $max_score; if ($do_min or $do_max) { my $first = $parts->[0]; for my $part (@$parts) { my $s = eval { $part->feature->score } || $part; next unless defined $s; $max_score = $s if $do_max && (!defined $max_score or $s > $max_score); $min_score = $s if $do_min && (!defined $min_score or $s < $min_score); } } return $self->sanity_check($min_score,$max_score); } sub sanity_check { my $self = shift; my ($min_score,$max_score,@rest) = @_; return ($min_score,$max_score,@rest) if $max_score > $min_score; if ($max_score > 0) { $min_score = 0; } else { $max_score = $min_score + 1; } return ($min_score,$max_score,@rest); } sub midpoint { my $self = shift; my $default = shift; my $pivot = $self->bicolor_pivot; if ($pivot eq 'none') { return } elsif ($pivot eq 'zero') { return 0; } elsif ($pivot eq 'mean') { return eval {$self->series_mean} || 0; } elsif ($pivot eq 'min') { return eval {$self->series_min} || 0; } elsif ($pivot eq 'max') { return eval {$self->series_max} || 0; } elsif ($pivot =~ /^(\d+)SD/i) { my $stdevs = $1; return eval {$self->series_mean + $self->series_stdev * $stdevs} || 0; } elsif ($pivot =~ /^[\d.eE+-]+$/){ return $pivot; } else { my $min = $self->min_score or return $default; my $max = $self->max_score or return $default;; return (($min+$max)/2); } } sub bicolor_pivot { my $self = shift; my $pivot = $self->option('bicolor_pivot'); return if defined $pivot && $pivot eq 'none'; return $pivot; } sub pos_color { my $self = shift; my $pivot = $self->bicolor_pivot || 'none'; return $self->bgcolor if $pivot eq 'none'; return defined $self->color('pos_color') ? $self->color('pos_color') : $self->bgcolor; } sub neg_color { my $self = shift; my $pivot = $self->bicolor_pivot || 'none'; return $self->bgcolor if $pivot eq 'none'; return defined $self->color('neg_color') ? $self->color('neg_color') : $self->bgcolor; } # change the scaling of the y axis sub rescale { my $self = shift; my ($min,$max) = @_; return ($min,$max); # don't do anything here } 1; __END__ =head1 NAME Bio::Graphics::Glyph::minmax - The minmax glyph =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This glyph is a common base class for L and L. It adds an internal method named minmax() for calculating the upper and lower boundaries of scored features, and is not intended for end users. =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, =head1 AUTHOR Lincoln Stein Elstein@cshl.orgE Copyright (c) 2003 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/Factory.pm000555001750001750 2677012165075746 22153 0ustar00lsteinlstein000000000000=head1 NAME Bio::Graphics::Glyph::Factory - Factory for Bio::Graphics::Glyph objects =head1 SYNOPSIS See L. =head1 DESCRIPTION This class is used internally by Bio::Graphics to generate new Glyph objects by combining a list of features with the user's desired configuration. It is intended to be used internally by Bio::Graphics. =head1 FEEDBACK =head2 Mailing Lists User feedback is an integral part of the evolution of this and other Bioperl modules. Send your comments and suggestions preferably to one of the Bioperl mailing lists. Your participation is much appreciated. bioperl-l@bioperl.org - General discussion http://bioperl.org/wiki/Mailing_lists - About the mailing lists =head2 Reporting Bugs Report bugs to the Bioperl bug tracking system to help us keep track the bugs and their resolution. Bug reports can be submitted via the web: http://bugzilla.open-bio.org/ =head1 AUTHOR - Lincoln Stein Email - lstein@cshl.org =head1 SEE ALSO L =head1 APPENDIX The rest of the documentation details each of the object methods. Internal methods are usually preceded with an "_" (underscore). =cut package Bio::Graphics::Glyph::Factory; use strict; use Carp qw(:DEFAULT cluck); use Bio::Root::Version; use base qw(Bio::Root::Root); #use Memoize 'memoize'; #memoize('option'); my %LOADED_GLYPHS = (); my %GENERIC_OPTIONS = ( bgcolor => 'turquoise', fgcolor => 'black', fontcolor => 'black', font2color => 'blue', height => 8, font => 'gdSmallFont', # This must be a string not method call bump => +1, # bump by default (perhaps a mistake?) ); =head2 new Title : new Usage : $f = Bio::Graphics::Glyph::Factory->new( -stylesheet => $stylesheet, -glyph_map => $glyph_map, -options => $options); Function : create a new Bio::Graphics::Glyph::Factory object Returns : the new object Args : $stylesheet is a Bio::Das::Stylesheet object that can convert Bio::Das feature objects into glyph names and associated options. $glyph_map is a hash that maps primary tags to glyph names. $options is a hash that maps option names to their values. Status : Internal to Bio::Graphics =cut sub new { my $class = shift; my $panel = shift; my %args = @_; my $stylesheet = $args{-stylesheet}; # optional, for Bio::Das compatibility my $map = $args{-map}; # map type name to glyph name my $options = $args{-options}; # map type name to glyph options return bless { stylesheet => $stylesheet, glyph_map => $map, options => $options, panel => $panel, },$class; } =head2 clone Title : clone Usage : $f2 = $f->clone Function : Deep copy of a factory object Returns : a deep copy of the factory object Args : None Status : Internal to Bio::Graphics =cut sub clone { my $self = shift; my %new = %$self; my $new = bless \%new,ref($self); $new; } =head2 stylesheet Title : stylesheet Usage : $stylesheet = $f->stylesheet Function : accessor for stylesheet Returns : a Bio::Das::Stylesheet object Args : None Status : Internal to Bio::Graphics =cut sub stylesheet { my $self = shift; my $d = $self->{stylesheet}; $self->{stylesheet} = shift if @_; $d; } =head2 glyph_map Title : glyph_map Usage : $map = $f->glyph_map Function : accessor for the glyph map Returns : a hash mapping primary tags to glyphs Args : None Status : Internal to Bio::Graphics =cut sub glyph_map { shift->{glyph_map} } =head2 option_map Title : option_map Usage : $map = $f->option_map Function : accessor for the option map Returns : a hash mapping option names to values Args : None Status : Internal to Bio::Graphics =cut sub option_map { shift->{options} } =head2 global_opts Title : global_opts Usage : $map = $f->global_opts Function : accessor for global options Returns : a hash mapping option names to values Args : None Status : Internal to Bio::Graphics This returns a set of defaults for option values. =cut sub global_opts{ shift->{global_opts} } =head2 panel Title : panel Usage : $panel = $f->panel Function : accessor for Bio::Graphics::Panel Returns : a Bio::Graphics::Panel Args : None Status : Internal to Bio::Graphics This returns the panel with which the factory is associated. =cut sub panel { shift->{panel} } =head2 scale Title : scale Usage : $scale = $f->scale Function : accessor for the scale Returns : a floating point number Args : None Status : Internal to Bio::Graphics This returns the scale, in pixels/bp for glyphs constructed by this factory. =cut sub scale { shift->{panel}->scale } =head2 font Title : font Usage : $font = $f->font Function : accessor for the font Returns : a font name Args : None Status : Internal to Bio::Graphics This returns a GD font name. =cut sub font { my $self = shift; my $glyph = shift; $self->option($glyph,'font') || $self->{font}; } =head2 map_pt Title : map_pt Usage : @pixel_positions = $f->map_pt(@bp_positions) Function : map bp positions to pixel positions Returns : a list of pixel positions Args : a list of bp positions Status : Internal to Bio::Graphics The real work is done by the panel, but factory subclasses can override if desired. =cut sub map_pt { my $self = shift; my @result = $self->panel->map_pt(@_); return wantarray ? @result : $result[0]; } =head2 map_no_trunc Title : map_no_trunc Usage : @pixel_positions = $f->map_no_trunc(@bp_positions) Function : map bp positions to pixel positions Returns : a list of pixel positions Args : a list of bp positions Status : Internal to Bio::Graphics Same as map_pt(), but it will NOT clip pixel positions to be within the drawing frame. =cut sub map_no_trunc { my $self = shift; my @result = $self->panel->map_no_trunc(@_); return wantarray ? @result : $result[0]; } =head2 translate_color Title : translate_color Usage : $index = $f->translate_color($color_name) Function : translate symbolic color names into GD indexes Returns : an integer Args : a color name in format "green" or "#00FF00" Status : Internal to Bio::Graphics The real work is done by the panel, but factory subclasses can override if desired. =cut sub translate_color { my $self = shift; my $color_name = shift; $self->panel->translate_color($color_name); } =head2 transparent_color Title : transparent_color Usage : $index = $f->transparent_color($opacity,$color_name) Function : translate symbolic color names into GD indexes, with an opacity value taken into account Returns : an integer Args : an opacity value from 0-1.0, plus a color name in format "green" or "#00FF00" Status : Internal to Bio::Graphics The real work is done by the panel, but factory subclasses can override if desired. =cut sub transparent_color { my $self = shift; $self->panel->transparent_color(@_); } =head2 make_glyph Title : make_glyph Usage : @glyphs = $f->glyph($level,[$type,]$feature1,$feature2...) Function : transform features into glyphs. Returns : a list of Bio::Graphics::Glyph objects Args : a feature "level", followed by a list of FeatureI objects. Status : Internal to Bio::Graphics The level is used to track the level of nesting of features that have subfeatures. The option $type argument can be used to force the glyph type =cut # create a glyph sub make_glyph { my $self = shift; my $level = shift; my $forced_type = shift unless ref($_[0]); my @result; my $panel = $self->panel; my $flip = $panel->flip; for my $f (@_) { my $type = $forced_type || $self->feature_to_glyph($f); my $glyphclass = 'Bio::Graphics::Glyph'; $type ||= 'generic'; $glyphclass .= "\:\:\L$type"; unless ($LOADED_GLYPHS{$glyphclass}++) { $self->throw("The requested glyph class, ``$type'' is not available: $@") unless (eval "require $glyphclass"); } my $glyph = $glyphclass->new(-feature => $f, -factory => $self, -flip => $flip, -level => $level); push @result,$glyph; } return wantarray ? @result : $result[0]; } =head2 feature_to_glyph Title : feature_to_glyph Usage : $glyph_name = $f->feature_to_glyph($feature) Function : choose the glyph name given a feature Returns : a glyph name Args : a Bio::Seq::FeatureI object Status : Internal to Bio::Graphics =cut sub feature_to_glyph { my $self = shift; my $feature = shift; my $val; if ($self->{stylesheet} && $feature->type !~ /track|group/) { $val = scalar $self->{stylesheet}->glyph($feature); return $val || 'generic'; } my $map = $self->glyph_map; if ($map) { if (ref($map) eq 'CODE') { $val = eval {$map->($feature)}; warn $@ if $@; } else { $val = $map->{$feature->primary_tag}; } } return $val || 'generic'; } =head2 set_option Title : set_option Usage : $f->set_option($option_name=>$option_value) Function : set or change an option Returns : nothing Args : a name/value pair Status : Internal to Bio::Graphics =cut sub set_option { my $self = shift; my ($option_name,$option_value) = @_; $self->{overriding_options}{lc $option_name} = $option_value; } # options: # the overriding_options hash has precedence # ...followed by the option_map # ...followed by the stylesheet # ...followed by generic options sub option { my $self = shift; my ($glyph,$option_name,$partno,$total_parts) = @_; return unless defined $option_name; $option_name = lc $option_name; # canonicalize return $self->{overriding_options}{$option_name} if exists $self->{overriding_options} && exists $self->{overriding_options}{$option_name}; if (exists $self->{stylesheet} && (my $ss = $self->{stylesheet})) { my(undef,%options) = $ss->glyph($glyph->feature); my $value = $options{$option_name}; if (defined $value) { # some cleanup on DAS glyphs $value =~ s/yes/1/i; $value =~ s/no/0/i; } return $value if defined $value; } if (exists $self->{options} && (my $map = $self->{options})) { if (exists $map->{$option_name} && defined(my $value = $map->{$option_name})) { my $feature = $glyph->feature; return $value unless ref $value eq 'CODE'; my $val = eval { $value->($feature,$option_name,$partno,$total_parts,$glyph)}; warn "Error returned while evaluating value of '$option_name' option for glyph $glyph, feature $feature: ",$@,"\n" if $@; return defined $val && $val eq '*default*' ? $GENERIC_OPTIONS{$option_name} : $val; } } return $GENERIC_OPTIONS{$option_name}; } sub get_option { my $self = shift; my $option_name = shift; my $map = $self->{options} or return; $map->{$option_name}; } =head2 options Title : options Usage : @option_names = $f->options Function : return all configured option names Returns : a list of option names Args : none Status : Internal to Bio::Graphics =cut # return names of all the options in the option hashes sub options { my $self = shift; my %options; if (my $map = $self->option_map) { $options{lc($_)}++ foreach keys %$map; } $options{lc($_)}++ foreach keys %GENERIC_OPTIONS; return keys %options; } 1; Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/image.pm000555001750001750 3541512165075746 21622 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::image; use strict; use GD; use base 'Bio::Graphics::Glyph::generic'; our @ISA; # # |--------------------| true position ('height' high) # . . # . . diagonal (vertical spacing high) # . . # +--------------------------+ # | | # | | # | | image # | | # | | # | | # +--------------------------+ use constant VERTICAL_SPACING => 20; sub my_description { return < [ 'string', undef, 'Specify the image path or URL to use for the feature.', 'If no image option is specified, then the glyph will look', 'inside the feature itself for an image path or URL in a tag named "image"', ], image_prefix => [ 'string', undef, 'A string to prepend to each image path.', 'You may use this to prepend a directory path or a partial URL.'], vertical_spacing => [ 'integer', 20, 'Vertical distance from the box that shows the physical span of the', 'feature to the top of the picture, in pixels.'], glyph_delegate => [ 'string', 'generic', 'The glyph to use for the part of the glyph that shows the physical', 'span of features.'] } } sub demo_feature { my $self = shift; my $ex_image = 'http://www.catch-fly.com/sites/awhittington/_files/Image/Drosophila-melanogaster.jpg'; return Bio::Graphics::Feature->new(-start=>1, -end=>500, -name=>$ex_image, -attributes => { image=>$ex_image, }, ); } sub new { my $self = shift->SUPER::new(@_); $self->{image} = $self->get_image(); return $self; } sub get_image { my $self = shift; my ($format,$image) = eval { $self->image_data }; unless ($image) { warn $@ if $@; return; } my $gd = $format eq 'image/png' ? GD::Image->newFromPngData($image,1) : $format eq 'image/jpeg' ? GD::Image->newFromJpegData($image,1) : $format eq 'image/gif' ? GD::Image->newFromGifData($image) : $format eq 'image/gd' ? GD::Image->newFromGdData($image) : $format eq 'image/gd2' ? GD::Image->newFromGd2Data($image) : $self->throw("This module cannot handle images of type $format"); return $gd; } sub _guess_format { my $self = shift; my $path = shift; return 'image/png' if $path =~ /\.png$/i; return 'image/jpeg' if $path =~ /\.jpe?g$/i; return 'image/gif' if $path =~ /\.gif(87)?$/i; return 'image/gd' if $path =~ /\.gd$/i; return 'image/gd2' if $path =~ /\.gd2$/i; my ($extension) = $path =~ /\.(\w+)$/; #cop-out return $extension; } sub image_path { my $self = shift; my $feature = $self->feature or $self->throw("no feature!"); my $dirname = $self->image_dir; my $basename = $self->option('image'); # can't get it from callback, so try looking for an 'image' attribute if (!$basename && $feature->can('has_tag') && $feature->has_tag('image')) { ($basename) = $feature->get_tag_values('image'); } return unless $basename; return $basename if $basename =~ m!^\w+:/!; # looks like a URL return $basename if $basename =~ m!^/!; # looks like an abs path return "$dirname/$basename"; } sub image_data { my $self = shift; my $path = $self->image_path or return; if ($path =~ m!^\w+:/!) { # looks like a URL require LWP::UserAgent; my $ua = LWP::UserAgent->new(env_proxy => 1); my $response = $ua->get($path); if ($response->is_success) { return ($response->content_type,$response->content); } else { $self->throw($response->status_line); } } else { my $content_type = $self->_guess_format($path); open F,$path or $self->throw("Can't open $path: $!"); binmode F; my $data; $data .= $_ while read(F,$_,1024); close F; return ($content_type,$data); } } sub pad_left { my $self = shift; my $pad = $self->SUPER::pad_left; my $image = $self->{image} or return $pad; my $width_needed = ($image->width - $self->width)/2; return $pad > $width_needed ? $pad : $width_needed; } sub pad_right { my $self = shift; my $pad = $self->SUPER::pad_right; my $image = $self->{image} or return $pad; my $width_needed = ($image->width - $self->width)/2; return $pad > $width_needed ? $pad : $width_needed; } sub pad_bottom { my $self = shift; my $pb = $self->SUPER::pad_bottom; my $image = $self->{image} or return $pb; $pb += $self->vertical_spacing; $pb += $image->height; return $pb; } sub vertical_spacing { my $self = shift; my $vs = $self->option('vertical_spacing'); return $vs if defined $vs; return VERTICAL_SPACING; } sub draw_description { my $self = shift; my ($gd,$left,$top,$partno,$total_parts) = @_; $self->SUPER::draw_description($gd,$left,$top,$partno,$total_parts); } sub image_dir { my $self = shift; return $self->option('image_prefix'); } sub draw_component { my $self = shift; my $gd = shift; my($x1,$y1,$x2,$y2) = $self->bounds(@_); my $delegate = $self->option('glyph_delegate') || 'generic'; if ($delegate eq 'generic') { $self->SUPER::draw_component($gd,@_); } else { eval "require Bio::Graphics::Glyph::$delegate"; local @ISA = ("Bio::Graphics::Glyph::$delegate"); my $method = "Bio::Graphics::Glyph::${delegate}::draw_component"; $self->$method($gd,@_); } my $image = $self->{image} or return; my $fgcolor = $self->fgcolor; my $bgcolor = $self->bgcolor; my $height = $self->option('height'); my $half = 4; my $vs = $self->vertical_spacing; my $delta = (($x2-$x1) - $image->width)/2; my($x,$y) = ($x1+$delta,$y1+$vs+$self->height); if ($gd->can('copy') && !$gd->isa('GD::SVG::Image')) { $gd->copy($image,$x,$y,0,0,$image->width,$image->height) ; } elsif ($gd->isa('GD::SVG::Image') && $self->image_path =~ m!^(ftp|http)+:/!) { # a URL my ($img,$id) = $gd->_prep($x,$y); $img->image('x' => $x, 'y' => $y, width => $image->width, height => $image->height, id => $id, 'xlink:href' => $self->image_path); } else { my $gray = $self->panel->translate_color('gray'); $gd->filledRectangle($x,$y,$x+$image->width,$y+$image->height,$gray); } if ($vs > 0) { $gd->line($x1,$y2+2,$x1,$y2+$half,$fgcolor); $gd->line($x2,$y2+2,$x2,$y2+$half,$fgcolor); $gd->line($x1,$y2+$half,$x,$y-$half,$fgcolor); $gd->line($x2,$y2+$half,$x+$image->width-1,$y-$half,$fgcolor); $gd->line($x,$y-$half,$x,$y-2,$fgcolor); $gd->line($x+$image->width-1,$y-$half,$x+$image->width-1,$y-2,$fgcolor); } } 1; __END__ =head1 NAME Bio::Graphics::Glyph::image - A glyph that draws photographs & other images =head1 SYNOPSIS use Bio::Graphics; use Bio::Seq; use Bio::SeqFeature::Generic; my $bsg = 'Bio::SeqFeature::Generic'; my $seq = Bio::Seq->new(-length=>1000); my $whole = $bsg->new(-display_name => 'Clone82', -start => 1, -end => $seq->length); my $image1 = $bsg->new(-start => 100, -end => 300, -display_name => 'Excretory System', -tag=>{ image=>"http://www.flybase.org/anatomy/image-browser_files/excretory-system.gif" } ); my $image2 = $bsg->new(-start => 500, -end => 800, -display_name => 'Expression Pattern', -tag=>{ image=>"http://www.flybase.org/anatomy/image-browser_files/embryonic-expression-pattern.gif" } ); my $panel = Bio::Graphics::Panel->new(-length => $seq->length, -width => 800, -truecolor => 1, -key_style => 'between', -pad_left => 10, -pad_right => 10, ); $panel->add_track($whole, -glyph => 'arrow', -double => 1, -tick => 2, -label => 1, ); $panel->add_track([$image1,$image2], -glyph => 'image', -label => 1, -key => 'Example images'); binmode STDOUT; print $panel->png; =head1 DESCRIPTION This glyph inserts an image into the track at the indicated feature coordinates. The image can be in PNG, JPEG, GIF or GD format, and can be either 8-bit or 24-bit ("truecolor"). The image can be located on the local filesystem or located at a remote URL (provided that you have the LWP module installed). When working with photographic images, you may wish to have Bio::Graphics::Panel create 24-bit (truecolor) images in order to avoid running out of colors. The symptom of this is that images appear posterized. To turn on truecolor images, pass the -truecolor option to Bio::Graphics::Panel as shown in the synopsis. =head2 OPTIONS The following options are standard among all Glyphs. See L for a full explanation. Option Description Default ------ ----------- ------- -fgcolor Foreground color black -outlinecolor Synonym for -fgcolor -bgcolor Background color turquoise -fillcolor Synonym for -bgcolor -linewidth Line width 1 -height Height of glyph 10 -font Glyph font gdSmallFont -connector Connector type 0 (false) -connector_color Connector color black -label Whether to draw a label 0 (false) -description Whether to draw a description 0 (false) -hilite Highlight color undef (no color) The following additional options are available to the "image" glyph: Option Description Default ------ ----------- ------- -image Specify the image path or URL none to use for this feature. -image_prefix String to prepend to none each image path. You may prepend a directory or a partial URL. -vertical_spacing Vertical distance from the box 20 that shows the physical span of of the feature to the top of the picture (in pixels). -glyph_delegate Glyph to use for the part of 'generic' the glyph that shows the physical span of the feature. Set B<-vertical_spacing> to 0 to completely suppress the diagonal lines that connect the physical span of the feature to the image. =head2 Specifying the Image The path to the image can be specified in two ways. First, you can place it in the feature itself using a tag named "image". Second, you can specify it as a track option using a callback: $panel->add_track(\@features, -glyph=>'image', -image => sub { my $feature = shift; my $image_path = do_something(); return $image } ); You can of course give -image a constant string, in which case each feature will show the same image. The image can be a file on the local operating system or a URL. However, URL fetching will only work if the LWP module is installed on your system. Otherwise the glyph will fail with an error message. If the image is a relative path (it does not begin with a slash or a URL protocol), then the contents of -image_prefix will be prepended to it. This allows you to specify images that are relative to a particular directory or a partial URL. Example: $panel->add_track(\@features, -glyph => 'image', -image_prefix => 'http://www.flybase.org/anatomy/image-browser_files', ); This specifies that each feature's "image" tag is to be appended to the partial FlyBase URL, thereby saving space. =head2 Glyph Delegation The image glyph consists of two parts: an upper part that shows the extent of the feature in base pair coordinates, and a lower part that shows the image. No scaling of the image is done; its height and width are fixed. By default the upper part uses the "generic" glyph, which is a simple rectangle filled with the bgcolor and outlined with the fgcolor. To use a different glyph in the upper part, specify the -glyph_delegate option, giving the name of the glyph you wish to use. For instance, to use the "span" glyph: $panel->add_track(\@features, -glyph => 'image', -glyph_delegate => 'span' ); This feature does not work with all glyphs, and in particular requires a recent CVS checkout of Bio::Perl to work properly with the "arrow", "span" and "primers" glyphs (support for the feature did not make it into version 1.5). =head1 BUGS AND LIMITATIONS This glyph does not work with GD::SVG. If you try to render it onto a GD::SVG panel, the image will be shown as a gray box. This will be fixed in a future version of GD::SVG. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Lincoln Stein Elstein@cshl.orgE, Todd Harris Eharris@cshl.orgE Copyright (c) 2001 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/triangle.pm000555001750001750 752212165075746 22323 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::triangle; # DAS-compatible package to use for drawing a triangle use strict; use base qw(Bio::Graphics::Glyph::point_glyph); sub pad_left { my $self = shift; my $left = $self->SUPER::pad_left; return $left unless $self->option('point'); my $extra = $self->option('height')/3; return $extra > $left ? $extra : $left; } sub pad_right { my $self = shift; my $right = $self->SUPER::pad_right; return $right unless $self->option('point'); my $extra = $self->option('height')/3; return $extra > $right ? $extra : $right; } sub orient { my $self = shift; my $o = $self->option('orient'); $o = $self->option('direction') unless defined $o; return $o || 'S'; } sub draw_component { my $self = shift; my $gd = shift; my $fg = $self->fgcolor; my $orient = $self->option('orient') || 'S'; # find the center and vertices my ($x1,$y1,$x2,$y2) = $self->calculate_boundaries(@_); my $xmid = ($x1+$x2)/2; my $ymid = ($y1+$y2)/2; my ($vx1,$vy1,$vx2,$vy2,$vx3,$vy3); #make an equilateral my ($p,$q) = ($self->option('height'),($x2-$x1)/2); if ($self->option('point')){ $q = $p/sqrt(3); #2; $x1 = $xmid - $q; $x2 = $xmid + $q; $y1 = $ymid - $q; $y2 = $ymid + $q; } if ($orient eq 'S'){$vx1=$x1;$vy1=$y1;$vx2=$x2;$vy2=$y1;$vx3=$xmid;$vy3=$y2;} elsif($orient eq 'N'){$vx1=$x1;$vy1=$y2;$vx2=$x2;$vy2=$y2;$vx3=$xmid;$vy3=$y1;} elsif($orient eq 'W'){$vx1=$x2;$vy1=$y1;$vx2=$x2;$vy2=$y2;$vx3=$x2-$q*2;$vy3=$ymid;} elsif($orient eq 'E'){$vx1=$x1;$vy1=$y1;$vx2=$x1;$vy2=$y2;$vx3=$x1+$q*2;$vy3=$ymid;} # now draw the triangle my $poly_pkg = $self->polygon_package; my $poly = $poly_pkg->new(); $poly->addPt($vx1,$vy1); $poly->addPt($vx2,$vy2); $poly->addPt($vx3,$vy3); if (my $c = $self->bgcolor){ $gd->filledPolygon($poly,$c); } $gd->polygon($poly,$fg); } 1; __END__ =head1 NAME Bio::Graphics::Glyph::triangle - The "triangle" glyph =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This glyph draws an equilateral triangle when -point is defined. It draws an isoceles triangle otherwise. It is possible to draw the triangle with the base on the N, S, E, or W side. =head2 OPTIONS In addition to the common options, the following glyph-specific options are recognized: Option Description Default ------ ----------- ------- -point If true, the triangle 0 will drawn at the center of the range, and not scaled to the feature width. -orient On which side shall the S base be? (NSEW) =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Allen Day Eday@cshl.orgE. Copyright (c) 2001 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/pinsertion.pm000555001750001750 621712165075746 22710 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::pinsertion; # package to use for drawing P insertion as a triangle # p insertion is a point (one base). use strict; use GD; use base qw(Bio::Graphics::Glyph::generic); sub box { my $self = shift; my $half = $self->insertion_width/2; return ($self->left-$half,$self->top,$self->right+$half,$self->bottom); } sub insertion_width { my $self = shift; return $self->option('insertion_width') || 6; } # override draw method sub draw { my $self = shift; my $gd = shift; my ($left,$top) = @_; my ($x1,$y1,$x2,$y2) = $self->calculate_boundaries(@_); my $height = $self->height; my $half = $self->insertion_width/2; my $fill = $self->bgcolor; my $border = $self->fgcolor; my $poly_pkg = $self->polygon_package; my $poly = $poly_pkg->new(); if ($self->feature->strand > 0) { #plus strand $poly->addPt($x1 - $half, $y1); $poly->addPt($x1 + ($half), $y1); $poly->addPt($x1, $y2); #pointer down } else { $poly->addPt($x1, $y1); #pointer up $poly->addPt($x1 - $half, $y2); $poly->addPt($x1 + ($half), $y2); } $gd->filledPolygon($poly,$fill); $gd->polygon($poly,$border); # add a label if requested $self->draw_label($gd,$left,$top) if $self->option('label'); $self->draw_description($gd,$left,$top) if $self->option('description'); } 1; =head1 NAME Bio::Graphics::Glyph::pinsertion - The "Drosophila P-element Insertion" glyph =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This glyph was designed to show P-element insertions in the Drosophila genome, but in fact is suitable for any type of zero-width feature. Also see the triangle glyph. =head2 OPTIONS In addition to the generic options, this glyph recognizes: Option Name Description Default ----------- ----------- ------- -insertion_width Width of glyph in pixels 3 =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Allen Day Eday@cshl.orgE, Shengqiang Shu Esshu@bdgp.lbl.govE Copyright (c) 2001 Cold Spring Harbor Laboratory, BDGP This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/gene.pm000555001750001750 2472612165075746 21461 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::gene; use strict; use base 'Bio::Graphics::Glyph::processed_transcript'; sub my_descripton { return < [ 'boolean', undef, 'If true, then the display_name of each transcript', 'will be drawn to the left of the transcript glyph.'], thin_utr => [ 'boolean', undef, 'If true, UTRs will be drawn at 2/3 of the height of CDS segments.'], utr_color => [ 'color', 'grey', 'Color of UTR segments.'], decorate_introns => [ 'boolean', undef, 'Draw chevrons on the introns to indicate direction of transcription.' ], } } sub extra_arrow_length { my $self = shift; return 0 if $self->feature->primary_tag =~ /exon|utr/i; return $self->SUPER::extra_arrow_length unless $self->feature->primary_tag =~ /gene/; return 0 unless $self->{level} == 1; local $self->{level} = 0; # fake out superclass return $self->SUPER::extra_arrow_length; } sub pad_left { my $self = shift; my $type = $self->feature->primary_tag; return 0 unless $type =~ /gene|mRNA|transcript/; $self->SUPER::pad_left; } sub pad_right { my $self = shift; return 0 unless $self->{level} < 2; # don't invoke this expensive call on exons return $self->SUPER::pad_right; } sub pad_bottom { my $self = shift; return 0 unless $self->{level} < 2 || $self->is_utr; # don't invoke this expensive call on exons return $self->SUPER::pad_bottom; } sub pad_top { my $self = shift; return 0 unless $self->{level} < 2 || $self->is_utr; # don't invoke this expensive call on exons return $self->SUPER::pad_top; } sub bump { my $self = shift; my $bump; if ($self->{level} == 0 && lc $self->feature->primary_tag eq 'gene' && eval {($self->subfeat($self->feature))[0]->type =~ /RNA|pseudogene/i}) { $bump = $self->option('bump'); } else { $bump = $self->SUPER::bump; } return $bump; } sub label { my $self = shift; return unless $self->{level} < 2; if ($self->{feature}->primary_tag =~ /rna|transcript|pseudogene/i && $self->label_transcripts) { return $self->_label; } else { return $self->SUPER::label; } } sub label_position { my $self = shift; return 'top' if $self->{level} == 0; return 'left'; } sub label_transcripts { my $self = shift; return $self->{label_transcripts} if exists $self->{label_transcripts}; return $self->{label_transcripts} = $self->_label_transcripts; } sub _label_transcripts { my $self = shift; return $self->option('label_transcripts'); } sub draw_connectors { my $self = shift; if ($self->feature->primary_tag eq 'gene') { my @parts = $self->parts; return if @parts && $parts[0]->feature->primary_tag =~ /rna|transcript|pseudogene/i; } $self->SUPER::draw_connectors(@_); } sub maxdepth { my $self = shift; my $md = $self->Bio::Graphics::Glyph::maxdepth; return $md if defined $md; return 2; } sub _subfeat { my $class = shift; my $feature = shift; if ($feature->primary_tag =~ /^gene/i) { my @transcripts; for my $t (qw/mRNA tRNA snRNA snoRNA miRNA ncRNA pseudogene/) { push @transcripts, $feature->get_SeqFeatures($t); } return @transcripts if @transcripts; my @features = $feature->get_SeqFeatures; # no transcripts?! return whatever's there return @features if @features; # fall back to drawing a solid box if no subparts and level 0 return ($feature) if $class->{level} == 0; } elsif ($feature->primary_tag =~ /^CDS/i) { my @parts = $feature->get_SeqFeatures(); return ($feature) if $class->{level} == 0 and !@parts; return @parts; } my @subparts; if ($class->option('sub_part')) { @subparts = $feature->get_SeqFeatures($class->option('sub_part')); } elsif ($feature->primary_tag =~ /^mRNA/i) { @subparts = $feature->get_SeqFeatures(qw(CDS five_prime_UTR three_prime_UTR UTR)); } else { @subparts = $feature->get_SeqFeatures('exon'); } # The CDS and UTRs may be represented as a single feature with subparts or as several features # that have different IDs. We handle both cases transparently. my @result; foreach (@subparts) { if ($_->primary_tag =~ /CDS|UTR/i) { my @cds_seg = $_->get_SeqFeatures; if (@cds_seg > 0) { push @result,@cds_seg } else { push @result,$_ } } else { push @result,$_; } } # fall back to drawing a solid box if no subparts and level 0 return ($feature) if $class->{level} == 0 && !@result; return @result; } 1; __END__ =head1 NAME Bio::Graphics::Glyph::gene - A GFF3-compatible gene glyph =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This glyph is used for drawing genes that may have alternatively-spliced transcripts. The various isoforms are stacked on top of each other and given a single label and description that apply to the entire stack. Each individual transcript's name is optionally printed to the left of the transcript glyph. Transcripts (splice isoforms) are drawn using the processed_transcript glyph. CDS features are drawn in the background color, and the UTRs are drawn in an alternate color selected by the utr_color option. In addition, you can make the UTRs thinner than the CDS by setting the "thin_utr" option. This glyph is designed to work properly with GFF3-style three-tier genes, in which the top level feature has the Sequence Ontology type of "gene", the second level feature(s) have the SO type "mRNA", and the third level feature(s) have the SO type "CDS", "five_prime_utr" and "three_prime_utr." Subparts named "UTR" are also honored. The feature can contain other subparts as well (e.g. exon, intron, translation), but they are currently ignored unless the option sub_part is supplied. If the sub_part option is used that feature type will be used and CDS and UTR features will be excluded. This could be used for specifying that exons be used instead, for example. This glyph is a subclass of processed_transcript, and recognizes the same options. =head2 OPTIONS The following options are standard among all Glyphs. See L for a full explanation. Option Description Default ------ ----------- ------- -fgcolor Foreground color black -outlinecolor Synonym for -fgcolor -bgcolor Background color turquoise -fillcolor Synonym for -bgcolor -linewidth Line width 1 -height Height of glyph 10 -font Glyph font gdSmallFont -connector Connector type undef (false) -connector_color Connector color black -label Whether to draw a label undef (false) -description Whether to draw a description undef (false) -strand_arrow Whether to indicate undef (false) strandedness -hilite Highlight color undef (no color) In addition, the gene glyph recognizes the following glyph-specific options: Option Description Default ------ ----------- ------- -label_transcripts undef (false) Flag. If true, then the display name of each transcript will be drawn to the left of the transcript glyph. -thin_utr Flag. If true, UTRs will undef (false) be drawn at 2/3 of the height of CDS segments. -utr_color Color of UTR segments. Gray #D0D0D0 -decorate_introns Draw strand with little arrows undef (false) on the intron. The B<-adjust_exons> and B<-implied_utrs> options are inherited from processed_transcript, but are quietly ignored. Please use the processed_transcript glyph for this type of processing. =head1 BUGS The SO terms are hard-coded. They should be more flexible and should recognize ISA relationships. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Lincoln Stein Elstein@cshl.orgE Copyright (c) 2001 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/pentagram.pm000555001750001750 1001212165075746 22500 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::pentagram; use strict; use base qw(Bio::Graphics::Glyph::generic); sub my_description { return <option('labelfont') || $self->font; my $pad = $font->height; if ($self->option('text')) { $pad *= 2; } return $pad; } sub default_text { return ''; } sub default_text_pad_x { return 0; } sub default_text_pad_y { return 3; } sub default_size { return 20; } sub draw_component { my $self = shift; my $gd = shift; my ($x1,$y1,$x2,$y2) = $self->calculate_boundaries(@_); my $fg = $self->fgcolor; my $bg = $self->bgcolor; my $size = defined $self->option('size') ? $self->option('size') : $self->default_size(); my $poly_pkg = $self->polygon_package; my $polygon = $poly_pkg->new(); if ($self->option('inverted') == 1) { $polygon->addPt($x1,$y2); $polygon->addPt($x1+$size/2,$y2-$size/2); $polygon->addPt($x1,$y2-$size); $polygon->addPt($x1+$size, $y2-$size); $polygon->addPt($x1+$size, $y2); } else { $polygon->addPt($x1,$y2); $polygon->addPt($x1,$y2-$size); $polygon->addPt($x1+$size/2,$y2-$size); $polygon->addPt($x1+$size, $y2-$size/2); $polygon->addPt($x1+$size/2, $y2); } $gd->filledPolygon($polygon, $bg); $gd->polygon($polygon,$fg); my $text = defined $self->option('text') ? $self->option('text') : $self->default_text(); if ($text) { my $text_pad_x = defined $self->option('text_pad_x') ? $self->option('text_pad_x') : $self->default_text_pad_x(); my $text_pad_y = defined $self->option('text_pad_y') ? $self->option('text_pad_y') : $self->default_text_pad_y(); my $font = $self->option('labelfont') || $self->font; $gd->string($font, $x1+$text_pad_x, $y2-$size-$text_pad_y-$font->height, $text, $fg); } } 1; __END__ =head1 NAME Bio::Graphics::Glyph::pentagram - The "pentagram" glyph =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This glyph draws a pentagram with the sharp angle pointing right or,if the 'inverted' option is set to 1, an "inverted" pentagram (with the sharp angle pointing inwards, not outwards). There may be an optional text above the glyph. =head2 OPTIONS In addition to the common options, the following glyph-specific options are recognized: Option Description Default ------ ----------- ------- -size Width and height of the 20 glyph -text Text to show none -text_pad_x Number of pixels between 0 the left edge of the glyph and the start of text -text_pad_x Number of pixels between 3 the pentagram and the text =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Vsevolod (Simon) Ilyushchenko Esimonf@cshl.eduE. Copyright (c) 2004 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/repeating_shape.pm000555001750001750 710412165075746 23650 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::repeating_shape; # DAS-compatible package to use for drawing a line of repeating shapes use strict; use base qw(Bio::Graphics::Glyph::generic); sub default_width { return 10; } sub default_interval { return 10; } sub connector { 'none' } sub draw_component { my $self = shift; my $gd = shift; my $fg = $self->fgcolor; my $width = defined $self->option('width') ? $self->option('width') : $self->default_width; my $interval = defined $self->option('interval') ? $self->option('interval') : $self->default_interval; # find the center and vertices my ($x1,$y1,$x2,$y2) = $self->calculate_boundaries(@_); my $bWidth = $x2-$x1; if ($bWidth < $width) { $self->draw_repeating_shape($gd,$x1,$y1,$x2,$y2,$fg); return; } if ($bWidth < $width+2*$interval) { my $leftoverInterval = $bWidth - $width; my $halfInt = $leftoverInterval/2; $halfInt = 0 unless $interval; $gd->line($x1,$y2,$x1+$halfInt,$y2,$fg); $self->draw_repeating_shape($gd,$x1+$halfInt,$y1,$x2-$halfInt,$y2,$fg); $gd->line($x2-$halfInt,$y2,$x2,$y2,$fg); return; } my $count = int ($bWidth / ($width+$interval)); my $leftoverInterval = $bWidth % ($width+$interval)+$interval; my $halfInt = $leftoverInterval/2; $halfInt = 0 unless $interval; $gd->line($x1,$y2,$x1+$halfInt,$y2,$fg); foreach (my $i=1; $i<=$count; $i++) { my $shapeStart = $x1 + $halfInt + ($i-1)*($width+$interval); $self->draw_repeating_shape($gd,$shapeStart,$y1,$shapeStart+$width,$y2,$fg); if ($i < $count) { $gd->line($shapeStart+$width,$y2,$shapeStart+$width+$interval,$y2,$fg); } } $gd->line($x2-$halfInt,$y2,$x2,$y2,$fg); } sub draw_repeating_shape { warn "Subclasses must implement 'draw_repeating_shape'!\n"; } 1; __END__ =head1 NAME Bio::Graphics::Glyph::repeating_shape - A glyph that draws the same shape repeatedly. =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This glyph is a generic superclass for drawing the same shape repeatedly. =head2 OPTIONS In addition to the common options, the following glyph-specific options are recognized: Option Description Default ------ ----------- ------- -width Width of one tooth 10 -interval Interval between teeth 10 =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Vsevolod (Simon) Ilyushchenko Esimonf@cshl.eduE. Copyright (c) 2004 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/text_in_box.pm000555001750001750 617312165075746 23041 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::text_in_box; use strict; use base qw(Bio::Graphics::Glyph::generic); sub default_text { return "3'"; } sub default_text_pad { return 3; } sub draw_component { my $self = shift; my $gd = shift; my ($x1,$y1,$x2,$y2) = $self->calculate_boundaries(@_); my $fg = $self->fgcolor; my $font = $self->option('labelfont') || $self->font; my $text = defined $self->option('text') ? $self->option('text') : $self->default_text(); my $text_pad = defined $self->option('text_pad') ? $self->option('text_pad') : $self->default_text_pad(); my $width = $self->string_width($text); my $height = $self->font_height; my $midY = ($y2+$y1) / 2; my $poly_pkg = $self->polygon_package; my $polygon = $poly_pkg->new(); $polygon->addPt($x1,$midY-$height/2-$text_pad); $polygon->addPt($x1+$width+2*$text_pad,$midY-$height/2-$text_pad); $polygon->addPt($x1+$width+2*$text_pad,$midY+$height/2+$text_pad); $polygon->addPt($x1, $midY+$height/2+$text_pad); if (defined (my $bgcolor = $self->option('text_bgcolor'))) { $gd->filledPolygon($polygon,$self->factory->translate_color($bgcolor)); } $gd->polygon($polygon,$fg); $gd->string($font, $x1+$text_pad, $midY-$height/2, $text, $self->fontcolor); } 1; __END__ =head1 NAME Bio::Graphics::Glyph::text_in_box - The "text in box" glyph =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This glyph draws the specified text in a rectangular box. =head2 OPTIONS In addition to the common options, the following glyph-specific options are recognized: Option Description Default ------ ----------- ------- -text The text to draw in the box 3' -text_pad The number of pixels to offset 3 the box -text_bgcolor none The background color of the box =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Vsevolod (Simon) Ilyushchenko Esimonf@cshl.eduE. Copyright (c) 2004 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/graded_segments.pm000555001750001750 2011212165075746 23657 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::graded_segments; use strict; use base qw(Bio::Graphics::Glyph::segments Bio::Graphics::Glyph::minmax Bio::Graphics::Glyph::merge_parts); sub my_description { return 'This glyph is used for drawing features that consist of discontinuous segments.', 'The color intensity of each subfeature proportional to its score tag.' } sub my_options { return { max_score => [ 'integer', undef, "Maximum value of the feature's \"score\" attribute."], min_score => [ 'integer', undef, "Minimum value of the feature's \"score\" attribute."], vary_fg => [ 'boolean', undef, "Vary the foreground color as well as the background."], merge_parts => [ 'boolean', undef, "At low magnifications, smooth small gaps to improve the visual display.", "See this glyph's manual page for the gory details."], max_gap => [ 'integer', undef, 'Do not merge across gaps that exceed this threshold.'], } } # override draw method to calculate the min and max values for the components sub draw { my $self = shift; # bail out if this isn't the right kind of feature # handle both das-style and Bio::SeqFeatureI style, # which use different names for subparts. my @parts = $self->parts; @parts = $self if !@parts && $self->level == 0; return $self->SUPER::draw(@_) unless @parts; my ($min_score,$max_score) = $self->minmax(\@parts); return $self->SUPER::draw(@_) unless defined($max_score) && defined($min_score) && $min_score < $max_score; my $span = $max_score - $min_score; # allocate colors my $fill = $self->bgcolor; my ($red,$green,$blue) = $self->panel->rgb($fill); @parts = $self->merge_parts(@parts) if $self->option('merge_parts'); foreach my $part (@parts) { my $s = eval { $part->feature->score }; unless (defined $s) { $part->{partcolor} = $fill; next; } my ($r,$g,$b) = $self->calculate_color($s,[$red,$green,$blue],$min_score,$span); my $idx = $self->panel->translate_color($r,$g,$b); $part->{partcolor} = $idx; } $self->SUPER::draw(@_); } sub calculate_color { my $self = shift; my ($s,$rgb,$min_score,$span) = @_; return map { 255 - (255-$_) * min(max( ($s-$min_score)/$span, 0), 1) } @$rgb; } sub min { $_[0] < $_[1] ? $_[0] : $_[1] } sub max { $_[0] > $_[1] ? $_[0] : $_[1] } sub subseq { my $class = shift; my $feature = shift; return $feature->segments if $feature->can('segments'); return $feature->sub_SeqFeature if $feature->can('sub_SeqFeature'); return; } # synthesize a key glyph sub keyglyph { my $self = shift; my $scale = 1/$self->scale; # base pairs/pixel # two segments, at pixels 0->50, 60->80 my $offset = $self->panel->offset; my $feature = Bio::Graphics::Feature->new( -segments=>[ [ 0*$scale +$offset,20*$scale+$offset], [ 30*$scale +$offset,50*$scale+$offset], [60*$scale+$offset, 80*$scale+$offset] ], -name => $self->option('key'), -strand => '+1'); ($feature->segments)[0]->score(10); ($feature->segments)[1]->score(50); ($feature->segments)[2]->score(100); my $factory = $self->factory->clone; $factory->set_option(label => 1); $factory->set_option(bump => 0); $factory->set_option(connector => 'solid'); return $factory->make_glyph($feature); } # component draws a shaded box sub bgcolor { my $self = shift; return defined $self->{partcolor} ? $self->{partcolor} : $self->SUPER::bgcolor; } sub fgcolor { my $self = shift; return $self->SUPER::fgcolor unless $self->option('vary_fg'); return defined $self->{partcolor} ? $self->{partcolor} : $self->SUPER::fgcolor; } 1; =head1 NAME Bio::Graphics::Glyph::graded_segments - The "graded_segments" glyph =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This is identical to the "alignment" glyph, and is used for drawing features that consist of discontinuous segments. The color intensity of each segment is proportionate to the score. =head2 OPTIONS The following options are standard among all Glyphs. See L for a full explanation. Option Description Default ------ ----------- ------- -fgcolor Foreground color black -outlinecolor Synonym for -fgcolor -bgcolor Background color turquoise -fillcolor Synonym for -bgcolor -linewidth Line width 1 -height Height of glyph 10 -font Glyph font gdSmallFont -connector Connector type 0 (false) -connector_color Connector color black -label Whether to draw a label 0 (false) -description Whether to draw a description 0 (false) -hilite Highlight color undef (no color) In addition, the alignment glyph recognizes the following glyph-specific options: Option Description Default ------ ----------- ------- -max_score Maximum value of the Calculated feature's "score" attribute -min_score Minimum value of the Calculated feature's "score" attribute -vary_fg Vary the foreground color as 0 (false) well as the background -merge_parts 0 (false) Whether to simplify the alignment at low magnification -max_gap Do not merge across gaps Calculated that exceed this threshold If max_score and min_score are not specified, then the glyph will calculate the local maximum and minimum scores at run time. Since many scoring functions are exponential you may wish to take the log of your scores before passing them to this glyph. =head2 Simplifying the display of alignment features for large segments The "merge_parts" option is used for semantic zooming. Specifically, if features are small and dense, they will not be displayed very well for large segments and the color-coding will be lost. If merge-parts is set to a true value, adjacent alignment parts will be merged until a gap exceeding a calculated or user-specified value is encountered. Unless specified, the maximum gap allowed for merging adjacent features is calculated as (L/10000)*(L/500), where L = the length of the sequence displayed in the browser. The exponentially increasing gap threshold allows more aggressive merging of alignment features as the size of the displayed sequence grows larger. The score of the merged feature is calculated as a weighted average. For example, consider two adjacent HSPs that are each 400 bp in length and have scores of 60% and 70%. If the merge_parts option is set to a true value, the two HSPs would be merged in the display to a single 800 bp alignment block with an average score of 65%. The merge_parts option is turned off by default. =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Lincoln Stein Elstein@cshl.orgE Copyright (c) 2001 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/point_glyph.pm000444001750001750 70312165075746 23021 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::point_glyph; use strict; use base qw(Bio::Graphics::Glyph::generic); sub box { my $self = shift; my @result = $self->SUPER::box(); return @result unless $self->option('point') && $result[2]-$result[0] < 3; my $h = $self->option('height')/2; my $mid = int(($result[2]+$result[0])/2); $result[0] = $mid-$h-1; # fudge a little to make it easier to click on $result[2] = $mid+$h+1; return @result; } 1; Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/merged_alignment.pm000555001750001750 2453412165075746 24041 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::merged_alignment; # this glyph acts like graded_segments but the bgcolor of each segment is # more configurable. Supply a list of colors and corresponding # bins. Each bin is a range of x-y scores, where score n is > x and <= y # e.g. # [ALIGNMENT] # feature = alignment # bins = 0-50 50-70 70-90 90-100 # bincolors = white powderblue cornflowerblue blue # for sematic zooming (at lower magnification), to # reduce visual complexity of alignment # [ALIGNMENT:20000] # segment length >= 20000 # feature = alignment # merge_parts = 1 # max_gap = 500 # do not merge across gaps > 500 bp use strict; use base qw(Bio::Graphics::Glyph::graded_segments); use constant COLORS => "lightgrey powderblue cornflowerblue blue"; sub my_description { return < [ 'integer', undef, "Maximum value of the feature's \"score\" attribute.", "If undef, the value will be calculated automatically."], min_score => [ 'integer', undef, "Minimum value of the feature's \"score\" attribute.", "If undef, the value will be calculated automatically."], bincolors => [ 'string', 'lightgrey powderblue cornflowerblue blue', 'A space-delimited string of colors to be assigned to score bins.'], bins => [ 'integer', undef, 'Size of the score bins. If undefined, the range of scores will be divided', 'into the number of bins indicated by the size of the list of bincolors.'], merge_parts => [ 'boolean', undef, 'Whether to merge small subfeatures to simplify the display at low magnifications.'], } } # override draw method sub draw { my $self = shift; # bail out if this isn't the right kind of feature # handle both das-style and Bio::SeqFeatureI style, # which use different names for subparts. my @parts = $self->parts; @parts = $self if !@parts && $self->level == 0; return $self->SUPER::draw(@_) unless @parts; my $cols = $self->option('bincolors') || COLORS; my @cols = split /\s+/, $cols; my $bins = $self->option('bins'); my @bins = $bins ? split /\s+/, $bins : $self->get_bins(\@parts, @cols); my %color; @color{@bins} = @cols; @parts = $self->merge_parts(@parts) if $self->option('merge_parts'); # figure out the colors for my $part (@parts) { my ($bin) = grep { $part->in_range($_) } @bins; my $idx = $bin ? $self->panel->translate_color($color{$bin}) : $self->panel->translate_color('white'); $part->{partcolor} = $idx; } $self->{parts} = \@parts; $self->Bio::Graphics::Glyph::merge_parts::draw(@_); } sub in_range { my $self = shift; my $range = shift; my ($low,$high) = split '-', $range; my $s = $self->score || shift; return 1 if $s >= $low && $s <= $high; return 0; } # overide background method to paint glyph white as # a last resort sub bgcolor { my $self = shift; return $self->{partcolor} || 'white'; } # used if bins are not defined in the configuration # makes equal sized bins corresponding to the number of # colors specified sub get_bins { my $self = shift; my $parts = shift; my $cols = @_; my ($min,$max) = $self->minmax($parts); my $range = $max - $min; return ($max) if $range == 0; my $increment = $range/$cols+1; my ($score,@bins) = $min; until ($score > $max) { my $range = "$score-"; $score += $increment; $range .= $score-1; push @bins, $range; } return @bins; } # synthesize a key glyph sub keyglyph { my $self = shift; my $scale = 1/$self->scale; # base pairs/pixel # two segments, at pixels 0->50, 60->80 my $offset = $self->panel->offset; my $feature = Bio::Graphics::Feature->new( -segments=>[ [ 0*$scale +$offset,25*$scale+$offset], [ 25*$scale +$offset,50*$scale+$offset], [ 50*$scale+$offset, 75*$scale+$offset] ], -name => $self->option('key'), -strand => '+1'); my @scores = $self->example_scores; my @segments = $feature->segments; for ($feature->segments) { $_->score(shift @scores); } my $factory = $self->factory->clone; $factory->set_option(label => 1); $factory->set_option(bump => 0); $factory->set_option(connector => 'solid'); my $glyph = $factory->make_glyph(0,$feature); } sub example_scores { my $self = shift; my $bins = $self->option('bins'); if ($bins) { my @bins = split /\s+/, $bins; $bins[0] =~ s/(\S+)\-\S+/$1/; $bins[-1] =~ s/\S+\-(\S+)/$1/; my $mid = $bins[0] + ($bins[-1] - $bins[0])/2; return ($bins[0], $mid, $bins[-1]); } if ($self->option('min_score') || $self->option('max_score')) { my ($min,$max) = $self->minmax; my $mid = $min + ($max - $min)/2; return($min,$mid,$max); } return (0,50,100); } 1; =pod =head1 NAME Bio::Graphics::Glyph::merged_alignment - The "merged_alignment" glyph =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This glyph acts like graded_segments but the bgcolor of segments (sub-feature) is controlled by binned scores. It also supports semantic zooming to optimize glyph drawing for larger sequence displays. =head2 OPTIONS The following options are standard among all Glyphs. See L for a full explanation. Option Description Default ------ ----------- ------- -fgcolor Foreground color black -outlinecolor Synonym for -fgcolor -bgcolor Background color turquoise -fillcolor Synonym for -bgcolor -linewidth Line width 1 -height Height of glyph 10 -font Glyph font gdSmallFont -connector Connector type 0 (false) -connector_color Connector color black -label Whether to draw a label 0 (false) -description Whether to draw a description 0 (false) -hilite Highlight color undef (no color) In addition, the merged-alignment glyph recognizes the following glyph-specific options: Option Description Default ------ ----------- ------- -max_score Maximum value of the Calculated feature's "score" attribute -min_score Minimum value of the Calculated feature's "score" attribute -bincolors Colors assigned to bins lightgrey powderblue cornflowerblue blue (in order) -bins Bins to which scores are Calculated assigned -merge_parts 0 (false) Whether to simplify the alignment at low magnification -max_gap Do not merge across gaps Calculated that exceed this threshold If max_score and min_score are not specified, then the glyph will calculate the local maximum and minimum scores at run time. If the bins are not specified, they will be calculated based on the number of colors assigned and the local (or user-specified) minimum and maximum scores. Calculated bins are equal in size. User-specified bins are expressed as ranges, bins = 0-50 50-70 70-90 90-100 where each range means greater than the lower number and less than or equal to the higher number. =head2 Simplifying the display of alignment features for large segments The "merge_parts" option is used for semantic zooming. Specifically, if features are small and dense, they will not be displayed very well for large segments and the color-coding will be lost. If merge-parts is set to a true value, adjacent alignment parts will be merged until a gap exceeding a calculated or user-specified value is encountered. Unless specified, the maximum gap allowed for merging adjacent features is calculated as (L/10000)*(L/500), where L = the length of the sequence displayed in the browser. The exponentially increasing gap threshold allows more aggressive merging of alignment features as the size of the displayed sequence grows larger. The score of the merged feature is calculated as a weighted average. For example, consider two adjacent HSPs that are each 400 bp in length and have scores of 60% and 70%. If the merge_parts option is set to a true value, the two HSPs would be merged in the display to a single 800 bp alignment block with an average score of 65%. The merge_parts option is turned off by default. =head2 SAMPLE CONFIGURATION Sample gbrowse configuration stanzas for an alignment feature using this glyph. The scores are assumed to be expressed as percent identity (0-100). # base configuration [BLASTZ] feature = blastz_alignment glyph = merged_alignment bincolors = #A0A0A0 powderblue cornflowerblue blue bins = 60-70 70-80 80-90 90-100 category = Sequence Similarity Tracks height = 6 bump = 1 label = 1 fgcolor = black key = BLASTZ Semantic zooming with defined maximum gap between merged features for different zoom levels # if the displayed segment is >= 20000 in length, # use the merge_parts option to simplify the alignment # display [BLASTZ:20000] feature = blastz_alignment merge_parts = 1 max_gap = 50 # do not merge across gaps > 50 bp # if the displayed segment is >= 50000 in length [BLASTZ:50000] feature = blastz_alignment merge_parts = 1 max_gap = 500 # do not merge across gaps > 500 bp --OR-- Semantic zooming with dynamically calculated maximum gap # if the displayed segment is >= 20000 in length, [BLASTZ:20000] feature = blastz_alignment merge_parts = 1 =head1 BUGS Please report them. =head1 SEE ALSO L, L L, L, L, L, L =head1 AUTHOR Sheldon McKay Emckays@cshl.eduE Copyright (c) 2005 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/heat_map.pm000555001750001750 4475712165075746 22327 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::heat_map; use strict; use base qw(Bio::Graphics::Glyph::generic); use Bio::Graphics::Glyph::minmax; # A glyph to draw a heat map for scored features along a continuous color # gradient calculated in HSV color space sub my_description { return <red), or gradients progressing through the colors of the rainbow (magenta->blue->green->yelloe->red) can be created. For example: # a white->red heat map start_color = white end_color = red # a rainbow start_color = magenta end_color = red # green->yellow->red start_color = green end_color = red END } sub my_options { { start_color => [ 'color', 'white', 'Beginning of the color gradient, expressed as a named color or', 'RGB hex string.'], end_color => [ 'color', 'red', 'End of the color gradient.'], brightness => [ 'integer', undef, 'Color brilliance: an integer between 0 and 100. This will override', 'the value calculated from the name color.'], saturation => [ 'integer', undef, 'Color saturation: an integer between 0 and 100. This will override', 'the value calculated from the named color.'], pure_hue => [ 'boolean', undef, 'Use the pure hue (brightness and saturation both at 100)', 'for the named color.'], min_score => [ 'integer', undef, "Minimum value of the feature's \"score\" attribute."], max_score => [ 'integer', undef, "Maximum value of the feature's \"score\" attribute."], vary_fg => [ 'boolean', 1, 'Vary both the foreground and background colors.'], }; } # set up getter/setter methods BEGIN { no strict 'refs'; my @subs = qw/ h_start s_start v_start h_range s_range v_range min_score max_score low_rgb low_hsv high_rgb score_range/; for my $sub ( @subs ) { *{$sub} = sub { my ($self, $v) = @_; my $k = "_$sub"; if (defined $v) { $self->{$k} = $v; } return $self->{$k}; } } } sub draw { my $self = shift; my @parts = $self->parts; @parts = $self if !@parts && $self->level == 0; return $self->SUPER::draw(@_) unless @parts; $self->calculate_gradient(\@parts); my $low_rgb = $self->low_rgb; for my $part (@parts) { my $score = $part->feature->score; # use start color if no score or no score gradient unless (defined $score && $self->score_range ) { $part->{partcolor} = $self->color_index(@$low_rgb); } else { my @rgb = $self->calculate_color($score); $part->{partcolor} = $self->color_index(@rgb); } } return $self->SUPER::draw(@_); } # We want an exact match, so allocate the color # if required sub color_index { my ($self, @rgb) = @_; my $gd = $self->panel->gd; return $gd->colorResolve(@rgb); } # Override minmax method to get user supplied # values. This will be helpful for single or # unaggregated features. sub minmax { my ($self, $parts) = @_; my $min = $self->option('min_score'); my $max = $self->option('max_score'); return ($min,$max) if $min && $max && $min < $max; return (0,$max) if $max && !$min; # minscore may be zero return (0,100) unless $parts; return $self->SUPER::minmax($parts); } # convert named color or hex string to RGB value, then HSV sub color2hsv { my ($self,$color) = @_; my $color_idx = $self->panel->translate_color($color); my @rgb = $self->panel->rgb($color_idx); return [$self->RGBtoHSV(@rgb)]; } sub calculate_gradient { my ($self, $parts) = @_; my $start_color = lc $self->option('start_color') || 'white'; my $stop_color = lc $self->option('end_color') || 'red'; my $hsv_start = $self->color2hsv($start_color); my $hsv_stop = $self->color2hsv($stop_color); my ($h_start,$s_start,$v_start) = @$hsv_start; my ($h_stop,$s_stop,$v_stop ) = @$hsv_stop; my $s_range = $s_stop - $s_start; my $v_range = $v_stop - $v_start; my $h_range; # special case: if start hue = end hue, we want to go round # the whole wheel once. Otherwise round the wheel clockwise # or counterclockwise depending on start and end coordinate if ($h_start != $h_stop) { my $direction = abs($h_stop - $h_start)/($h_stop - $h_start); my ($sstart,$sstop) = sort {$a <=> $b} ($h_start,$h_stop); $direction *= -1 if $sstop - $sstart > 256/2; #reverse the direction if we cross 0 $h_range = ($sstop - $sstart) <= 256/2 ? ($sstop - $sstart)*$direction : (256 - $sstop + $sstart)*$direction; } else { $h_range = 256; } # override brightness and saturation if required if (my $bri = $self->option('brightness')) { $bri = int($bri*255/100 + 0.5); $v_start = $v_stop = $bri; $v_range = 0; } if (my $sat = $self->option('saturation')) { $sat = int($sat*255/100 + 0.5); $s_start = $s_stop = $sat; $s_range = 0; } if ($self->option('pure_hue')) { $hsv_start = [$h_start,255,255]; $hsv_stop = [$h_stop,255,255]; $v_start = $v_stop = 255; $s_start = $s_stop = 255; $v_range = $s_range = 0; } # darkness or monochrome gradient? if ( !_isa_color($start_color) || !_isa_color($stop_color) ) { # hue (H) is fixed $h_range = 0; # gradient S V # white -> color 0->255 255 # color -> white 255->0 255 # white -> black 0 255->0 # black -> white 0 0->255 # black -> color 0->255 0->255 # color -> black 255->0 255->0 if ( $start_color eq 'white' && _isa_color($stop_color) ) { $s_range = 255; $s_start = 0; $v_range = 0; $v_start = 255; $h_start = $h_stop; } elsif ( _isa_color($start_color) && $stop_color eq 'white' ) { $s_range = -255; $s_start = 255; $v_range = 0; $v_start = 255; } elsif ( $start_color eq 'white' ) { # end black $s_range = 0; $s_start = 0; $v_range = -255; $v_start = 255; } elsif ( $stop_color eq 'white' ) { # start black $s_range = 0; $s_start = 0; $v_range = 255; $v_start = 0; } elsif ( _isa_color($start_color) ) { # end black $s_range = 255; $s_start = 0; $v_range = 255; $v_start = 0; } elsif ( _isa_color($stop_color) ) { # start black $s_range = -255; $s_start = 255; $v_range = -255; $v_start = 255; } } # store gradient info $self->h_range($h_range); $self->h_start($h_start); $self->s_start($s_start); $self->v_start($v_start); $self->s_range($s_range); $self->v_range($v_range); # store score info my ($min,$max) = $self->minmax($parts); $self->score_range($max - $min); $self->min_score($min); $self->max_score($max); # store color extremes my @low_rgb = $self->HSVtoRGB(@$hsv_start); my @high_rgb = $self->HSVtoRGB(@$hsv_stop); $self->low_hsv($hsv_start); $self->high_rgb(\@high_rgb); $self->low_rgb(\@low_rgb); return 1; } sub _isa_color { my $color = shift; return $color =~ /white|black|FFFFFF|000000/i ? 0 : 1; } sub calculate_color { my ($self,$score,$min,$max,$range) = @_; $score ||= 0; # relative score $min = $self->min_score || 0 unless defined $min; $max = $self->max_score unless defined $max; $range = $self->score_range || 1 unless defined $range; # reset off-scale scores $score = $min if $score < $min && $min; $score = $max if $score > $max && $max; my $score_diff = ($score - $min)/$range; # Hue my $hue = $self->h_start; my $h_diff = $score_diff * $self->h_range; $hue += $h_diff; $hue = $hue < 255 ? int($hue+0.5) : 255; # Saturation my $sat = $self->s_start; $sat += $score_diff * $self->s_range; $sat = $sat < 255 ? int($sat+0.5) : 255; # Brightness my $bri = $self->v_start; $bri += $score_diff * $self->v_range; $bri = $bri < 255 ? int($bri + 0.5) : 255; return $self->HSVtoRGB($hue,$sat,$bri); } # synthesize a key glyph sub keyglyph { my $self = shift; my $scale = 1/$self->scale; # base pairs/pixel my $offset = $self->panel->offset; my ($min,$max) = $self->minmax; my $range = $max - $min; my ($segments, $low); for my $start (0..9) { $start *= 10; push @$segments, [ $start*$scale + $offset, ($start + 10)*$scale + $offset ]; } my $feature = Bio::Graphics::Feature->new( -segments => $segments, -name => $self->option('key'), -strand => '+1' ); for (0..9) { my $score += ($range/10) * $_; ($feature->segments)[$_]->score($score); } my $factory = $self->factory->clone; $factory->set_option(label => 1); $factory->set_option(bump => 0); $factory->set_option(min_score => 0); $factory->set_option(max_score => 100); return $factory->make_glyph(0,$feature); } sub bgcolor { my $self = shift; return defined $self->{partcolor} ? $self->{partcolor} : $self->SUPER::bgcolor; } sub fgcolor { my $self = shift; return $self->option('vary_fg') ? $self->bgcolor : $self->SUPER::fgcolor;; } sub RGBtoHSV { my ($self, $r, $g ,$bl) = @_; my ($min,undef,$max) = sort {$a<=>$b} ($r,$g,$bl); my $range = $max - $min or return (0,0,$r); my $v = $max; my $s = 255 * ($max - $min)/$max; my $h; if ($max == $r) { $h = 60 * ($g-$bl)/$range; } elsif ($max == $g) { $h = 60 * ($bl-$r)/$range + 120; } else { $h = 60 * ($r-$g)/$range + 240; } $h = int($h*255/360 + 0.5); $h += 255 if $h < 0; $h -= 255 if $h > 255; return ($h, $s, $v); } # method courtesy of Lincoln Stein sub HSVtoRGB { my $self = shift; @_ == 3 or die "Usage: GD::Simple->HSVtoRGB(\$hue,\$saturation,\$value)"; my ($h,$s,$v)=@_; my ($r,$g,$b,$i,$f,$p,$q,$t); if( $s == 0 ) { ## achromatic (grey) return ($v,$v,$v); } $h %= 255; $s /= 255; ## scale saturation from 0.0-1.0 $h /= 255; ## scale hue from 0 to 1.0 $h *= 360; ## and now scale it to 0 to 360 $h /= 60; ## sector 0 to 5 $i = $h % 6; $f = $h - $i; ## factorial part of h $p = $v * ( 1 - $s ); $q = $v * ( 1 - $s * $f ); $t = $v * ( 1 - $s * ( 1 - $f ) ); if($i<1) { $r = $v; $g = $t; $b = $p; } elsif($i<2){ $r = $q; $g = $v; $b = $p; } elsif($i<3){ $r = $p; $g = $v; $b = $t; } elsif($i<4){ $r = $p; $g = $q; $b = $v; } elsif($i<5){ $r = $t; $g = $p; $b = $v; } else { $r = $v; $g = $p; $b = $q; } return (int($r+0.5),int($g+0.5),int($b+0.5)); } 1; =head1 NAME Bio::Graphics::Glyph::heat_map - The "heat_map" glyph =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This glyph draws "scored" features using a continuous color gradient is the HSV color space. The color of each segment is proportionate to the score. =head1 OPTIONS =head2 Global glyph options: See L =head2 Glyph-specific options: The color_map glyph recognizes the following glyph-specific options: Option Description Default ------ ----------- ------- -start_color Beginning of the color white gradient, expressed as a named color or RGB hex string -end_color End of the color gradient red -brightness Color brilliance: 0-100 Calculated This will override the value from the named color -saturation Color saturation: 0-100 Calculated This will override the value from the named color -pure_hue Use the pure hue (bright- 0 (false) ness and saturation both at 100) for the named color -max_score Maximum value of the Calculated feature's "score" attribute -min_score Minimum value of the Calculated feature's "score" attribute -vary_fg Vary the foreground color 1 (true) with the background color If max_score and min_score are not specified, the glyph will calculate the local maximum and minimum scores at run time. If single features, unaggregated features, or multiple aggregates are being drawn, this will result in an inconsistent color scale. It is recommended that global maximum and minimum scores be specified in the track configuration. Since many scoring functions are exponential, you may wish to take the log of your scores before passing them to this glyph. =head2 Color Gradients The color gradient is calculating by progressing along the rainbow spectrum from red to violet, also incrementing brightness and saturation, all proportate to the score value. To vary the hue only, "pure" hues can be used. Pure hues have brightness and saturation values of 100. Some examples, in order, are red, yellow, lime, aqua/cyan, blue and magenta. The gradient can progress in reverse orientation with the respect to the visible light spectrum if a lower-order color is used as the start and a higher order color used as the end (for example lime->red). Using the "pure_hue" option results in a brighter, more vibrant color spectrum, Choosing darker start and end colors, such as green or maroon, will result in a darker spectrum. A single color spectrum can be created by using black or white as the start or end color. A grayscale spectrum will result if black and white are used as start and end colors. One example of an effective visual heat map is to progress from white->red. For the start_color and end_color options, 140 named webcolors and their corresponsing RGB hex codes (listed below) are supported. steelblue #4682B4 royalblue #041690 cornflowerblue #6495ED lightsteelblue #B0C4DE mediumslateblue #7B68EE slateblue #6A5ACD darkslateblue #483D8B midnightblue #191970 navy #000080 darkblue #00008B mediumblue #0000CD blue #0000FF dodgerblue #1E90FF deepskyblue #00BFFF lightskyblue #87CEFA skyblue #87CEEB lightblue #ADD8E6 powderblue #B0E0E6 azure #F0FFFF lightcyan #E0FFFF paleturquoise #AFEEEE mediumturquoise #48D1CC lightseagreen #20B2AA darkcyan #008B8B teal #008080 cadetblue #5F9EA0 darkturquoise #00CED1 aqua #00FFFF cyan #00FFFF turquoise #40E0D0 aquamarine #7FFFD4 mediumaquamarine #66CDAA darkseagreen #8FBC8F mediumseagreen #3CB371 seagreen #2E8B57 darkgreen #006400 green #008000 forestgreen #228B22 limegreen #32CD32 lime #00FF00 chartreuse #7FFF00 lawngreen #7CFC00 greenyellow #ADFF2F yellowgreen #9ACD32 palegreen #98FB98 lightgreen #90EE90 springgreen #00FF7F mediumspringgreen #00FA9A darkolivegreen #556B2F olivedrab #6B8E23 olive #808000 darkkhaki #BDB76B darkgoldenrod #B8860B goldenrod #DAA520 gold #FFD700 yellow #FFFF00 khaki #F0E68C palegoldenrod #EEE8AA blanchedalmond #FFEBCD moccasin #FFE4B5 wheat #F5DEB3 navajowhite #FFDEAD burlywood #DEB887 tan #D2B48C rosybrown #BC8F8F sienna #A0522D saddlebrown #8B4513 chocolate #D2691E peru #CD853F sandybrown #F4A460 darkred #8B0000 maroon #800000 brown #A52A2A firebrick #B22222 indianred #CD5C5C lightcoral #F08080 salmon #FA8072 darksalmon #E9967A lightsalmon #FFA07A coral #FF7F50 tomato #FF6347 darkorange #FF8C00 orange #FFA500 orangered #FF4500 crimson #DC143C red #FF0000 deeppink #FF1493 fuchsia #FF00FF magenta #FF00FF hotpink #FF69B4 lightpink #FFB6C1 pink #FFC0CB palevioletred #DB7093 mediumvioletred #C71585 purple #800080 darkmagenta #8B008B mediumpurple #9370DB blueviolet #8A2BE2 indigo #4B0082 darkviolet #9400D3 darkorchid #9932CC mediumorchid #BA55D3 orchid #DA70D6 violet #EE82EE plum #DDA0DD thistle #D8BFD8 lavender #E6E6FA ghostwhite #F8F8FF aliceblue #F0F8FF mintcream #F5FFFA honeydew #F0FFF0 lightgoldenrodyellow #FAFAD2 lemonchiffon #FFFACD cornsilk #FFF8DC lightyellow #FFFFE0 ivory #FFFFF0 floralwhite #FFFAF0 linen #FAF0E6 oldlace #FDF5E6 antiquewhite #FAEBD7 bisque #FFE4C4 peachpuff #FFDAB9 papayawhip #FFEFD5 beige #F5F5DC seashell #FFF5EE lavenderblush #FFF0F5 mistyrose #FFE4E1 snow #FFFAFA white #FFFFFF whitesmoke #F5F5F5 gainsboro #DCDCDC lightgrey #D3D3D3 silver #C0C0C0 darkgray #A9A9A9 gray #808080 lightslategray #778899 slategray #708090 dimgray #696969 darkslategray #2F4F4F black #000000 =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L L =head1 AUTHOR Sheldon McKay Emckays@cshl.eduE Copyright (c) 2006 Cold Spring Harbor Laboratory This package and its accompanying libraries is free software; you can redistribute it and/or modify it under the terms of the GPL (either version 1, or at your option, any later version) or the Artistic License 2.0. Refer to LICENSE for the full license text. In addition, please see DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/vista_plot.pm000555001750001750 4276012165075746 22725 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::vista_plot; use strict; use base qw(Bio::Graphics::Glyph::wiggle_xyplot Bio::Graphics::Glyph::wiggle_density Bio::Graphics::Glyph::wiggle_whiskers Bio::Graphics::Glyph::heat_map Bio::Graphics::Glyph::smoothing); our $VERSION = '1.0'; sub my_options { { start_color => [ 'color', 'white', 'Beginning of the color gradient, expressed as a named color or', 'RGB hex string.'], end_color => [ 'color', 'red', 'End of the color gradient.'], min_peak => [ 'integer', 1, "Minimum value of the peak feature's \"score\" attribute."], max_peak => [ 'integer', 255, "Maximum value of the peak feature's \"score\" attribute."], min_score => [ 'integer', undef, "Minimum value of the signal graph feature's \"score\" attribute."], max_score => [ 'integer', undef, "Maximum value of the signal graph feature's \"score\" attribute."], peakwidth => [ 'integer', 3, "Line width determine the thickness of the line representing a peak."], glyph_subtype => [ ['peaks+signal','peaks','signal','density'], 'vista', "What to show, peaks or signal, both (vista plot) or density graph."], graph_type => [ ['whiskers','histogram','boxes','line','points','linepoints'], 'boxes', "Type of signal graph to show."], alpha => [ 'integer', 100, "Alpha transparency of peak regions", ], }; } sub my_description { return <{$k} = $v; } return $self->{$k}; } } } sub peakwidth { shift->option('peakwidth') || 3; } sub alpha_c { my $self = shift; return $self->option('alpha') || 100; } # Need to override wiggle_xyplot padding function to enable adequate height control in density mode sub pad_top { my $self = shift; return 0 if $self->glyph_subtype eq 'density'; my $pad = $self->Bio::Graphics::Glyph::generic::pad_top(@_); if ($pad < ($self->font('gdTinyFont')->height)) { $pad = $self->font('gdTinyFont')->height; # extra room for the scale } $pad; } sub bigwig_summary { my $self = shift; my $d = $self->{bigwig_summary}; $self->{bigwig_summary} = shift if @_; $d; } # Need to override this too b/c we need unconventional mean and stdev calculation sub global_mean_and_variance { my $self = shift; if (my $wig = $self->wig) { my @result = eval{($wig->mean,$wig->stdev)}; return @result if @result; } if (my $sum = $self->bigwig_summary){ use Bio::DB::BigWig qw(binMean binStdev); my $stats = $sum->statistical_summary(1); return eval{(binMean($stats->[0]),binStdev($stats->[0]))}; } return; } sub glyph_subtype { my $self = shift; my $only_show = $self->option('only_show') || $self->option('glyph_subtype') || 'vista'; $only_show = 'vista' if $only_show eq 'both' || $only_show eq 'peaks+signal'; return $only_show; } sub graph_type { my $self = shift; return $self->option('graph_type') || $self->options->{graph_type}[1]; } # we override the draw method so that it dynamically creates the parts needed # from the wig file rather than trying to fetch them from the database sub draw { my $self = shift; my($gd,$dx,$dy) = @_; my $only_show = $self->glyph_subtype; my $feature = $self->feature; # Draw dual graph if we have both types of attributes, BigWig and wiggle format supported my %features = (wig => (eval{$feature->get_tag_values('wigfile')})[0], peak => (eval{$feature->get_tag_values('peak_type')})[0], fasta=> (eval{$feature->get_tag_values('fasta')})[0]); $self->panel->startGroup($gd); $self->draw_signal($only_show,\%features,@_) if $only_show =~ /signal|density|vista/; $self->draw_peaks(\%features,@_) if $features{peak} && $only_show =~ /peaks|vista|both/; $self->Bio::Graphics::Glyph::xyplot::draw_label(@_) if $self->option('label'); $self->draw_description(@_) if $self->option('description'); $self->panel->endGroup($gd); } # this should be refactored from wiggle_xyplot and wiggle_density sub draw_signal { my $self = shift; my $signal_type = shift; my $paths = shift; my $feature = $self->feature; # Signal Graph drawing: if ($paths->{wig} && $paths->{wig}=~/\.wi\w{1,3}$/) { eval "require Bio::Graphics::Wiggle" unless Bio::Graphics::Wiggle->can('new'); my $wig = eval { Bio::Graphics::Wiggle->new($paths->{wig}) }; $self->wig($wig); $self->_draw_wigfile($feature,$wig,@_); } elsif ($paths->{wig} && $paths->{wig}=~/\.bw$/i) { eval "use Bio::DB::BigWig 'binMean'" unless Bio::DB::BigWig->can('new'); my @args = (-bigwig => "$paths->{wig}"); if ($paths->{fasta}) { eval "use Bio::DB::Sam" unless Bio::DB::Sam::Fai->can('open'); my $fasta_accessor = Bio::DB::Sam::Fai->can('open') ? Bio::DB::Sam::Fai->open("$paths->{fasta}") : Bio::DB::Fasta->new("$paths->{fasta}"); push @args,(-fasta => $fasta_accessor); } my $bigwig = Bio::DB::BigWig->new(@args); $self->wig($bigwig); my ($summary) = $bigwig->features(-seq_id => $feature->segment->ref, -start => $self->panel->start, -end => $self->panel->end, -type => 'summary'); local $self->{feature} = $summary; if ($signal_type ne 'density' and $self->graph_type eq 'whiskers') { $self->Bio::Graphics::Glyph::wiggle_whiskers::draw(@_); } else { my $stats = $summary->statistical_summary($self->width); my @vals = map {$_->{validCount} ? Bio::DB::BigWig::binMean($_) : 0} @$stats; $self->bigwig_summary($summary); if ($signal_type eq 'density') { $self->Bio::Graphics::Glyph::wiggle_density::_draw_coverage($summary,\@vals,@_); } else { $self->Bio::Graphics::Glyph::wiggle_data::_draw_coverage($summary,\@vals,@_); } } } } sub draw_peaks { my $self = shift; my $paths = shift; my($gd,$dx,$dy) = @_; my($left,$top,$right,$bottom) = $self->calculate_boundaries($dx,$dy); # Peak drawing: my $alpha_c = $self->alpha_c; my $feature = $self->feature; my $p_type = $paths->{peak}; my @peaks = $self->peaks(); my $x_scale = $self->scale; my $panel_start = $self->panel->start; my $f_start = $feature->start > $panel_start ? $feature->start : $panel_start; my $lw = $self->peakwidth; my($max_s,$min_s) = ($self->option('max_peak'),$self->option('min_peak')); $max_s = 255 if !defined $max_s; $min_s = 1 if !defined $min_s; my $grad_ok = 0; if (defined $max_s && defined $min_s) { $grad_ok = $self->calculate_gradient($min_s,$max_s); } my $flip = $self->{flip}; $self->{peak_cache} = []; # remember coordinates of the peaks foreach my $peak (@peaks) { my $x1 = $left + ($peak->{start} - $f_start) * $x_scale; my $x2 = $left + ($peak->{stop} - $f_start) * $x_scale; if ($x2 >= $left and $x1 <= $right) { my $y1 = $top; my $y2 = $bottom; $x1 = $left if $x1 < $left; $x2 = $right if $x2 > $right; $alpha_c = $alpha_c <=127 ? $alpha_c : 0; # Reset to zero if illegal value is passed my $score = $peak->{score}; if ($score eq "."){$score = 255;} # Set score to 255 if peak is unscored my $color; if ($grad_ok && defined $score && $score!=255) { my @rgb = $self->Bio::Graphics::Glyph::heat_map::calculate_color($score, $self->min_peak_score, $self->max_peak_score, $self->peak_score_range); $color = $self->color_index(@rgb); }else{ $color = $self->fgcolor; } my $bgcolor = $self->bgcolor; if ($alpha_c > 0){ $gd->alphaBlending(1); $bgcolor = $self->add_alpha($gd,$bgcolor,$alpha_c); } if ($flip) { $x1 = $right - ($x1-$left); $x2 = $right - ($x2-$left); ($x1,$x2) = ($x2,$x1); } my @rect = (int($x1+0.5),int($y1+0.5),int($x2+0.5),int($y2+0.5)); $self->filled_box($gd,@rect,$bgcolor,$bgcolor,0.5) if abs($y2-$y1) > 0; $gd->setThickness($lw); $gd->line(int($x1+0.5),int($y1+0.5),int($x2+0.5),int($y1+0.5),$color); $gd->setThickness(1); push @{$self->{peak_cache}},[$peak,@rect]; } } } # Adding alpha channel to a color: sub add_alpha { my($self,$im,$color,$alpha) = @_; my($r,$g,$b) = $im->rgb($color); return $im->colorAllocateAlpha($r,$g,$b,$alpha); } # Slightly modified function from heat_map.pm sub calculate_gradient { my($self, $min, $max) = @_; my $start_color = lc $self->option('start_color') || 'white'; my $stop_color = lc $self->option('end_color') || 'red'; my $hsv_start = $self->color2hsv($start_color); my $hsv_stop = $self->color2hsv($stop_color); my ($h_start,$s_start,$v_start) = @$hsv_start; my ($h_stop,$s_stop,$v_stop ) = @$hsv_stop; my $s_range = abs($s_stop - $s_start); my $v_range = abs($v_stop - $v_start); my $h_range; # special case: if start hue = end hue, we want to go round # the whole wheel once. Otherwise round the wheel clockwise # or counterclockwise depending on start and end coordinate if ($h_start != $h_stop) { my $direction = abs($h_stop - $h_start)/($h_stop - $h_start); my ($sstart,$sstop) = sort {$a <=> $b} ($h_start,$h_stop); $direction *= -1 if $sstop - $sstart > 256/2; #reverse the direction if we cross 0 $h_range = ($sstop - $sstart) <= 256/2 ? ($sstop - $sstart)*$direction : (256 - $sstop + $sstart)*$direction; } else { $h_range = 256; } # darkness or monochrome gradient? if ( !_isa_color($start_color) || !_isa_color($stop_color) ) { # hue (H) is fixed $h_range = 0; # gradient S V # white -> color 0->255 255 # color -> white 255->0 255 # white -> black 0 255->0 # black -> white 0 0->255 # black -> color 0->255 0->255 # color -> black 255->0 255->0 if ( $start_color eq 'white' && _isa_color($stop_color) ) { $s_range = 255; $s_start = 0; $v_range = 0; $v_start = 255; $h_start = $h_stop; } elsif ( _isa_color($start_color) && $stop_color eq 'white' ) { $s_range = -255; $s_start = 255; $v_range = 0; $v_start = 255; } elsif ( $start_color eq 'white' ) { # end black $s_range = 0; $s_start = 0; $v_range = -255; $v_start = 255; } elsif ( $stop_color eq 'white' ) { # start black $s_range = 0; $s_start = 0; $v_range = 255; $v_start = 0; } elsif ( _isa_color($start_color) ) { # end black $s_range = 255; $s_start = 0; $v_range = 255; $v_start = 0; } elsif ( _isa_color($stop_color) ) { # start black $s_range = -255; $s_start = 255; $v_range = -255; $v_start = 255; } } # store gradient info $self->h_range($h_range); $self->h_start($h_start); $self->s_start($s_start); $self->v_start($v_start); $self->s_range($s_range); $self->v_range($v_range); # store score info $self->peak_score_range($max - $min); $self->min_peak_score($min); $self->max_peak_score($max); # store color extremes my @low_rgb = $self->HSVtoRGB(@$hsv_start); my @high_rgb = $self->HSVtoRGB(@$hsv_stop); $self->low_hsv($hsv_start); $self->high_rgb(\@high_rgb); $self->low_rgb(\@low_rgb); return 1; } sub _isa_color { my $color = shift; return $color =~ /white|black|FFFFFF|000000/i ? 0 : 1; } sub level { -1 } # Need to override this so we have a nice image map for overlayed peaks sub boxes { my $self = shift; my($left,$top,$parent) = @_; return if $self->glyph_subtype eq 'density'; # No boxes for density plot my @boxes = $self->SUPER::boxes(@_); if (my $rects = $self->{peak_cache}) { push @boxes,[@$_,$parent] foreach @$rects; } return wantarray ? @boxes : \@boxes; } # Modified and fused functions from wiggle_density.pm and wiggle_xyplot.pm sub _draw_wigfile { my $self = shift; my $feature = shift; my $wig = shift; $wig->smoothing($self->get_smoothing); $wig->window($self->smooth_window); my ($gd,$left,$top) = @_; my ($start,$end) = $self->effective_bounds($feature); if ($self->glyph_subtype eq 'density') { my ($x1,$y1,$x2,$y2) = $self->bounds($left,$top); $self->draw_segment($gd, $start,$end, $wig,$start,$end, 1,1, $x1,$y1,$x2,$y2); $self->Bio::Graphics::Glyph::xyplot::draw_label(@_) if $self->option('label'); $self->draw_description(@_) if $self->option('description'); } else { my ($start,$end) = $self->effective_bounds($feature); $self->wig($wig); my $parts = $self->create_parts_for_dense_feature($wig,$start,$end); $self->draw_plot($parts,@_); } } sub peaks { my $self = shift; return @{$self->{_peaks}} if $self->{_peaks}; my $feature = $self->feature; my $db = $feature->object_store; my ($p_type) = eval{$feature->get_tag_values('peak_type')}; unless ($db && $p_type) { $self->{_peaks} = []; return; } my @peaks = $db->features(-seq_id => $feature->segment->ref, -start => $self->panel->start, -end => $self->panel->end, -type => $p_type); $self->{_peaks} = \@peaks; return @{$self->{_peaks}}; } 1; =head1 NAME Bio::Graphics::Glyph::vista_plot - The "vista_plot" glyph =head1 SYNOPSIS See L, L and L. =head1 DESCRIPTION This glyph draws peak calls (features with discreet boundaries, i.e. putative transcription sites, over signal graph (wiggle_xyplot) requires a special load gff file that uses attributes 'wigfile' and 'peak_type' B 2L chip_seq vista 5407 23011573 . . . Name=ChipSeq Exp 1;wigfile=SomeWigFile.wigdb;peak_type=binding_site:exp1 The glyph will draw the wiggle file first, than overlay the peaks (if there are any) over signal graph. Elsewhere in the GFF3 file, there should be one or more features of type "binding_site:exp1", e.g.: 2L exp1 binding_site 91934 92005 . . . Options like 'balloon hover' and 'link' are available to customize interaction with peaks in detail view. B Supported bigwig format also requires another attribute to be supplied in load gff file (fasta) which specifies sequence index file for the organism in use. The data file should have the 'bw' extension - it is used to detect the BigWig format by vista_plot 3L chip_seq vista 1 24543530 . . . Name=ChipSeq Exp 2;wigfile=SomeBigWigFile.bw;peak_type=binding_site:exp2;fasta=YourOrganism.fasta Note that all attributes should be present in load gff, as the code currently does not handle situation when only some of the attributes are in gff. To omit peak or signal drawing use "" (i.e. peak_type="") In both cases, the stanza code will look the same (only essential parameters shown): [VISTA_PLOT] feature = vista:chip_seq glyph = vista_plot label = 1 smoothing = mean smoothing_window = 10 bump density = 250 autoscale = local variance_band = 1 max_peak = 255 min_peak = 1 peakwidth = 3 start_color = lightgray end_color = black pos_color = blue neg_color = orange bgcolor = orange alpha = 80 fgcolor = black database = database_with_load_gff_data box_subparts = 1 bicolor_pivot = min key = VISTA plot =head1 OPTIONS Options are the same as for wiggle_xyplot and heat_map B B set transparency for peak area. B Display only 'peaks', 'signal', 'density' or 'peaks+signal'. Aliases for 'peaks+signal' include "both" and "vista". B for proper peak drawing transparency should be enabled by setting B in I file =head1 BUGS Please report them. =head1 SEE ALSO L L L L L =head1 AUTHOR Peter Ruzanov pruzanov@oicr.on.ca Copyright (c) 2010 Ontario Institute for Cancer Research This package and its accompanying libraries is free software; you can redistribute it and/or modify it under the terms of the GPL (either version 1, or at your option, any later version) or the Artistic License 2.0. Refer to LICENSE for the full license text. In addition, please see DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/dna.pm000555001750001750 3511712165075746 21301 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::dna; use strict; use base qw(Bio::Graphics::Glyph::generic); my %complement = (g=>'c',a=>'t',t=>'a',c=>'g',n=>'n', G=>'C',A=>'T',T=>'A',C=>'G',N=>'N'); sub my_description { return < [ 'boolean', 1, 'Whether to draw the GC graph at low magnifications.'], gc_window=> [ 'integer', undef, 'Size of the sliding window to use in the GC content', 'calculation. If this is not defined, non-overlapping', 'bins will be used. If this is set to "auto", then the', 'glyph will choose a window equal to 1% of the interval.'], gc_bins => [ 'integer', 100, 'Fixed number of intervals to sample across the track.'], axis_color => [ 'color', 'black', 'Color of the vertical axes in the GC content graph.'], strand => [ [qw(forward reverse both auto)], 'auto', 'Show both forward and reverse strand, one of "forward", "reverse",', '"both" or "auto". In "auto" mode, +1 strand features will', 'show the plus strand, -1 strand features will', 'show the reverse complement and strandless features will show both.'], }; } # turn off description sub description { 0 } # turn off label # sub label { 1 } sub pad_top { my $self = shift; my $font = $self->mono_font; my $pt = $self->SUPER::pad_top; return $self->dna_fits ? $pt + $font->height+5 : 16; } sub height { my $self = shift; my $font = $self->mono_font; return $self->dna_fits ? 2*$font->height : $self->do_gc ? $self->SUPER::height : 0; } sub do_gc { my $self = shift; my $do_gc = $self->option('do_gc'); return if defined($do_gc) && !$do_gc; return 1; } sub draw_component { my $self = shift; my $gd = shift; my ($x1,$y1,$x2,$y2) = $self->bounds(@_); my $dna = eval { $self->feature->seq }; $dna = $dna->seq if ref($dna) and $dna->can('seq'); # to catch Bio::PrimarySeqI objects $dna or return; # workaround for my misreading of interface -- LS $dna = $dna->seq if ref($dna) && $dna->can('seq'); if ($self->dna_fits) { $self->draw_dna($gd,$dna,$x1,$y1,$x2,$y2); } elsif ($self->do_gc) { $self->draw_gc_content($gd,$dna,$x1,$y1,$x2,$y2); } } sub draw_dna { my $self = shift; my ($gd,$dna,$x1,$y1,$x2,$y2) = @_; my $pixels_per_base = $self->scale; my $feature = $self->feature; my $strand = $feature->strand || 1; $strand *= -1 if $self->{flip}; my @bases = split '',$strand >= 0 ? $dna : $self->reversec($dna); my $color = $self->fgcolor; my $font = $self->mono_font; my $lineheight = $font->height; $y1 -= $lineheight/2 - 3; my $strands = $self->option('strand') || 'auto'; my ($forward,$reverse); if ($strands eq 'auto') { $forward = $feature->strand >= 0; $reverse = $feature->strand <= 0; } elsif ($strands eq 'both') { $forward = $reverse = 1; } elsif ($strands eq 'reverse') { $reverse = 1; } else { $forward = 1; } # minus strand features align right, not left $x1 += $pixels_per_base - $font->width - 1 if $strand < 0; for (my $i=0;$i<@bases;$i++) { my $x = $x1 + $i * $pixels_per_base; $gd->char($font,$x+2,$y1,$bases[$i],$color) if $forward; $gd->char($font,$x+2,$y1+($forward ? $lineheight:0), $complement{$bases[$i]}||$bases[$i],$color) if $reverse; } } sub draw_gc_content { my $self = shift; my $gd = shift; my $dna = shift; my ($x1,$y1,$x2,$y2) = @_; $dna = $self->reversec($dna) if $self->{flip}; my $font = $self->mono_font; # get the options that tell us how to draw the GC content my $bin_size = length($dna) / ($self->option('gc_bins') || 100); $bin_size = 10 if $bin_size < 10; my $gc_window = $self->option('gc_window'); # if ($gc_window && $gc_window eq 'auto' or $gc_window <= length($dna)) { if ($gc_window) { if ($gc_window eq 'auto') { $gc_window = length($dna)/100; } elsif ($gc_window > length($dna)) { $gc_window = length($dna); } } # Calculate the GC content... my (@bins, @datapoints, $i); my $gc = 0; my $maxgc = -1000; my $mingc = +1000; if ($gc_window) { # ...using a sliding window... my @dna = split '', $dna; for ($i = 0; $i < @dna; $i++) { if ($dna[$i] eq 'G' or $dna[$i] eq 'C' or $dna[$i] eq 'g' or $dna[$i] eq 'c') { $dna[$i] = 1; } else { $dna[$i] = 0; } } for ($i = 0; $i < $gc_window; $i++) {$gc += $dna[$i]} push @datapoints, $gc; $datapoints[$#datapoints] > $maxgc and $maxgc = $datapoints[$#datapoints]; $datapoints[$#datapoints] < $mingc and $mingc = $datapoints[$#datapoints]; for ($i = 0; $i < @dna - $gc_window; $i++) { $gc -= $dna[$i]; $gc += $dna[$i + $gc_window]; push @datapoints, $gc; $datapoints[$#datapoints] > $maxgc and $maxgc = $datapoints[$#datapoints]; $datapoints[$#datapoints] < $mingc and $mingc = $datapoints[$#datapoints]; } my $scale = $maxgc - $mingc; $scale = 1 unless $scale; for ($i = 0; $i < @datapoints; $i++) { $datapoints[$i] = ($datapoints[$i] - $mingc) / $scale; } $maxgc = int($maxgc * 100 / $gc_window); $mingc = int($mingc * 100 / $gc_window); } else { # ...or a fixed number of bins. for (my $i = 0; $i < length($dna) - $bin_size; $i+= $bin_size) { my $subseq = substr($dna,$i,$bin_size); my $gc = $subseq =~ tr/gcGC/gcGC/; my $content = $gc/$bin_size; $maxgc = $content if ($content > $maxgc); $mingc = $content if ($content < $mingc); push @bins,$content; } my $scale = $maxgc - $mingc; foreach (my $i; $i < @bins; $i++) { $bins[$i] = $scale != 0 ? ($bins[$i] - $mingc) / $scale : 0; } $maxgc = int($maxgc * 100); $mingc = int($mingc * 100); } # Calculate values that will be used in the layout push @bins,0.5 unless @bins; # avoid div by zero my $bin_width = ($x2-$x1)/@bins; my $bin_height = $y2-$y1; my $fgcolor = $self->fgcolor; my $bgcolor = $self->factory->translate_color( $self->option('grid color') || $self->panel->gridmajorcolor ); my $axiscolor = $self->color('axis_color') || $fgcolor; # Draw the axes my $fontwidth = $font->width; $gd->line($x1, $y1, $x1, $y2, $axiscolor); $gd->line($x2-2,$y1, $x2-2,$y2, $axiscolor); $gd->line($x1, $y1, $x1+3,$y1, $axiscolor); $gd->line($x1, $y2, $x1+3,$y2, $axiscolor); $gd->line($x1, ($y2+$y1)/2,$x1+3,($y2+$y1)/2,$axiscolor); $gd->line($x2-4,$y1, $x2-1, $y1, $axiscolor); $gd->line($x2-4,$y2, $x2-1, $y2, $axiscolor); $gd->line($x2-4,($y2+$y1)/2,$x2-1,($y2+$y1)/2,$axiscolor); $gd->line($x1+5,$y2, $x2-5,$y2, $bgcolor); $gd->line($x1+5,($y2+$y1)/2,$x2-5,($y2+$y1)/2,$bgcolor); $gd->line($x1+5,$y1, $x2-5,$y1, $bgcolor); $gd->string($self->font,$x1-$self->string_width('% gc',$self->font),$y1,'% gc',$axiscolor) if $bin_height > $self->font_height($font)*2; # If we are using a sliding window, the GC graph will be scaled to use the full # height of the glyph, so label the right vertical axis to show the scaling that# is in effect $gd->string($self->font,$x2+3,$y1,"${maxgc}%",$axiscolor) if $bin_height > $self->font_height*2.5; $gd->string($self->font,$x2+3,$y2-$self->font_height,"${mingc}%",$axiscolor) if $bin_height > $self->font_height*2.5; # Draw the GC content graph itself if ($gc_window) { my $graphwidth = $x2 - $x1; # the $points_to_draw variable here can be used in various ways to adjust the # sampling of the graphic output. It could be converted to a glphy # option that can be set by user. Here we are just using $graphwidth # my $points_to_draw = 1800; my $points_to_draw = $graphwidth; # if $points_to_draw is taken to mean the total number of points to show along the graph then my $inc = int (length($dna) / $points_to_draw) + 1; # it might be useful to set this to the x pixel resolution of your # screen or the "default width" option in the gbrowse config file or current pixel width # if $points_to_draw is taken to mean the number of points per $gc_window length then # my $inc = int ($gc_window / $points_to_draw) + 1; my $gc_window_width = $gc_window * $self->panel->scale; # pixels for the gc_window length my $scale = ($graphwidth - $gc_window_width) / @datapoints; for ($i = $inc; $i < @datapoints; $i += $inc) { my $x = $i + $gc_window / 2; # the base at the center of the gc_window my $xlo = $x1 + ($x - $inc) * $scale; # pixel coordinate for previous point my $xhi = $x1 + $x * $scale; $gd->line($xlo, $y2 - ($bin_height * $datapoints[$i-$inc]), $xhi, $y2 - ($bin_height * $datapoints[$i]), $fgcolor); } } else { for (my $i = 0; $i < @bins; $i++) { my $bin_start = $x1+$i*$bin_width; my $bin_stop = $bin_start + $bin_width; my $y = $y2 - ($bin_height*$bins[$i]); $gd->line($bin_start,$y, $bin_stop,$y, $fgcolor); $gd->line($bin_stop,$y, $bin_stop,$y2 - ($bin_height*$bins[$i+1]), $fgcolor) if $i < @bins-1; } } } sub make_key_feature { my $self = shift; my @gatc = qw(g a t c); my $offset = $self->panel->offset; my $scale = 1/$self->scale; # base pairs/pixel my $start = $offset+1; my $stop = $offset+100*$scale; my $feature = Bio::Graphics::Feature->new(-start=> $start, -stop => $stop, -seq => join('',map{$gatc[rand 4]} (1..500)), -name => $self->option('key'), -strand => '+1', ); $feature; } 1; __END__ =head1 NAME Bio::Graphics::Glyph::dna - The "dna" glyph =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This glyph draws DNA sequences. At high magnifications, this glyph will draw the actual base pairs of the sequence (both strands). At low magnifications, the glyph will plot the GC content. By default, the GC calculation will use non-overlapping bins, but this can be changed by specifying the gc_window option, in which case, a sliding window calculation will be used. For this glyph to work, the feature must return a Bio::PrimarySeq DNA object in response to the seq() method. For example, you can use a Bio::SeqFeature::Generic object with an attached Bio::PrimarySeq like this: my $dna = Bio::PrimarySeq->new( -seq => 'A' x 1000 ); my $feature = Bio::SeqFeature::Generic->new( -start => 1, -end => 800 ); $feature->attach_seq($dna); $panel->add_track( $feature, -glyph => 'dna' ); A Bio::Graphics::Feature object may also be used. =head2 OPTIONS The following options are standard among all Glyphs. See L for a full explanation. Option Description Default ------ ----------- ------- -fgcolor Foreground color black -outlinecolor Synonym for -fgcolor -bgcolor Background color turquoise -fillcolor Synonym for -bgcolor -linewidth Line width 1 -height Height of glyph 10 -font Glyph font gdSmallFont -connector Connector type 0 (false) -connector_color Connector color black -label Whether to draw a label 0 (false) -description Whether to draw a description 0 (false) -hilite Highlight color undef (no color) In addition to the common options, the following glyph-specific options are recognized: Option Description Default ------ ----------- ------- -do_gc Whether to draw the GC true graph at low mags -gc_window Size of the sliding window EnoneE to use in the GC content calculation. If this is not defined, non- overlapping bins will be used. If this is set to "auto", then the glyph will choose a window equal to 1% of the interval. -gc_bins Fixed number of intervals 100 to sample across the panel. -axis_color Color of the vertical axes fgcolor in the GC content graph -strand Show both forward and auto reverse strand, one of "forward", "reverse", "both" or "auto". In "auto" mode, +1 strand features will show the plus strand -1 strand features will show the reverse complement and strandless features will show both NOTE: -gc_window=E'auto' gives nice results and is recommended for drawing GC content. The GC content axes draw slightly outside the panel, so you may wish to add some extra padding on the right and left. =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Lincoln Stein Elstein@cshl.orgE. Sliding window GC calculation added by Peter Ashton Epda@sanger.ac.ukE. Copyright (c) 2001 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/tic_tac_toe.pm000555001750001750 537712165075746 23001 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::tic_tac_toe; use strict; use base qw(Bio::Graphics::Glyph::generic); sub default_mode { return 'x'; } sub default_size { return 10; } sub draw_component { my $self = shift; my $gd = shift; my ($x1,$y1,$x2,$y2) = $self->calculate_boundaries(@_); my $fg = $self->fgcolor; my $size = defined $self->option('size') ? $self->option('size') : $self->default_size(); my $mode= defined $self->option('mode') ? $self->option('mode') : $self->default_mode(); my $midY = ($y1+$y2)/2; for (my $i=0; $i<($x2-$x1)/$size; $i++) { my $start = $x1+$i*$size; my $end = $x1+($i+1)*$size; if ($mode eq "x" || ($mode eq "xo" && $i%2==0)) { $gd->line($start, $midY-$size/2, $end, $midY+$size/2, $fg); $gd->line($end, $midY-$size/2, $start, $midY+$size/2, $fg); } elsif ($mode eq "o" || ($mode eq "xo" && $i%2==1)) { $gd->ellipse(($start+$end)/2, $midY, $size, $size, $fg); } } } 1; __END__ =head1 NAME Bio::Graphics::Glyph::tic_tac_toe - The "tic-tac-toe" glyph =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This glyph draws a sequence of either 'xxx', 'ooo' or 'xoxo', depending on the value of 'mode'. =head2 OPTIONS In addition to the common options, the following glyph-specific options are recognized: Option Description Default ------ ----------- ------- -mode One of 'x', 'o', or 'xo'. 'x' -size Size of either 'x' or 'o' 10 =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Vsevolod (Simon) Ilyushchenko Esimonf@cshl.eduE. Copyright (c) 2004 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/hat.pm000555001750001750 603112165075746 21264 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::hat; # a simple inverted V (used by DAS) use strict; use base qw(Bio::Graphics::Glyph::line); sub my_description { return <fgcolor; my $center = ($high+$low)/2; my $middle = ($left+$right)/2; $gd->line($left,$center,$middle,$high,$fg); $gd->line($middle,$high,$right,$center,$fg); } 1; __END__ =head1 NAME Bio::Graphics::Glyph::hat - The "hat" glyph =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This glyph draws an inverted V parallel to the sequence segment. It is different from other glyphs in that it is designed to work with DAS tracks. The inverted V is drawn BETWEEN subparts as if you specified a connector type of "hat". =head2 OPTIONS This glyph takes only the standard options. See L for a full explanation. Option Description Default ------ ----------- ------- -fgcolor Foreground color black -outlinecolor Synonym for -fgcolor -bgcolor Background color turquoise -fillcolor Synonym for -bgcolor -linewidth Line width 1 -height Height of glyph 10 -font Glyph font gdSmallFont -connector Connector type 0 (false) -connector_color Connector color black -label Whether to draw a label 0 (false) -description Whether to draw a description 0 (false) -strand_arrow Whether to indicate 0 (false) strandedness -hilite Highlight color undef (no color) =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Allen Day Eday@cshl.orgE. Copyright (c) 2001 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/ellipse.pm000555001750001750 607412165075746 22154 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::ellipse; use strict; use base qw(Bio::Graphics::Glyph::generic); sub my_description { return <bounds(@_); $self->filled_oval($gd, $x1, $y1, $x2, $y2); $self->draw_label($gd,@_) if $self->option('label'); } 1; __END__ =head1 NAME Bio::Graphics::Glyph::ellipse - The "ellipse" glyph =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This glyph draws an oval instead of a box; otherwise it is similar to the "generic" or "box" glyphs. The width of the oval is determined by the feature width, and the height by the -height option. =head2 OPTIONS The following options are standard among all Glyphs. See L for a full explanation. Option Description Default ------ ----------- ------- -fgcolor Foreground color black -outlinecolor Synonym for -fgcolor -bgcolor Background color turquoise -fillcolor Synonym for -bgcolor -linewidth Line width 1 -height Height of glyph 10 -font Glyph font gdSmallFont -connector Connector type 0 (false) -connector_color Connector color black -label Whether to draw a label 0 (false) -description Whether to draw a description 0 (false) -hilite Highlight color undef (no color) =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Lincoln Stein Elstein@cshl.orgE Copyright (c) 2001 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/protein.pm000555001750001750 2064012165075746 22212 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::protein; use strict; use base qw(Bio::Graphics::Glyph::cds); # turn off description sub description { 0 } # turn off label # sub label { 1 } sub height { my $self = shift; my $font = $self->mono_font; return $self->dna_fits ? 2 * $font->height : $self->do_kd ? $self->SUPER::height : 0; } sub do_kd { my $self = shift; my $do_kd = $self->option('do_kd'); return if defined($do_kd) && !$do_kd; return 1; } sub draw_component { my $self = shift; my $gd = shift; my ($x1,$y1,$x2,$y2) = $self->bounds(@_); if ($self->protein_fits && $self->{cds_translation}) { return $self->SUPER::draw_component($gd,@_); } elsif ($self->{cds_translation}) { $self->draw_kd_plot($gd,$self->{cds_translation},$x1,$y1,$x2,$y2); } else { $self->Bio::Graphics::Glyph::generic::draw_component($gd,@_); } } sub do_cds_translation { return 1 } sub draw_protein { my $self = shift; my ($gd,$protein,$x1,$y1,$x2,$y2) = @_; my $pixels_per_base = $self->scale; my $feature = $self->feature; my @bases = split '', $protein; my $color = $self->fgcolor; my $font = $self->mono_font; my $lineheight = $font->height; $y1 -= $lineheight/2 - 3; my $start = $self->panel->left + $self->map_pt($feature->start); my $end = $self->panel->left + $self->map_pt($feature->end); my $offset = int(($x1-$start-1)/$pixels_per_base); for (my $i=$offset;$i<@bases;$i++) { my $x = $start + $i * $pixels_per_base; next if $x+1 < $x1; last if $x > $x2; $gd->char($font,$x+2,$y1,$bases[$i],$color); } } sub draw_kd_plot { my $self = shift; my $gd = shift; my $protein = shift; my ($x1,$y1,$x2,$y2) = @_; my $kd_window = $self->option('kd_window') || 9; # Calculate the KD plot ... my %scores = ( I => 4.5, V => 4.2, L => 3.8, F => 2.8, C => 2.5, M => 1.9, A => 1.8, G => -0.4, T => -0.7, W => -0.9, S => -0.8, Y => -1.3, P => -1.6, H => -3.2, E => -3.5, Q => -3.5, D => -3.5, N => -3.5, K => -3.9, R => -4.5, ); my @datapoints; my @seq = split('', uc($protein)); $kd_window = $kd_window < scalar(@seq) ? $kd_window : scalar(@seq); my $maxkd = 4.5; my $minkd = -4.5; my $kd = 0; for (my $i = 0 ; $i < @seq && $i < $kd_window ; $i++) { $kd += $scores{$seq[$i]} || 0; } my $content = $kd / $kd_window; push @datapoints, $content; for (my $i = $kd_window; $i < @seq; $i++) { $kd -= $scores{$seq[$i-$kd_window]} || 0; $kd += $scores{$seq[$i]} || 0; $content = $kd / $kd_window; push @datapoints, $content; } my $scale = $maxkd - $minkd; foreach (my $i = 0; $i < @datapoints; $i++) { $datapoints[$i] = ($datapoints[$i] - $minkd) / $scale; } # Calculate values that will be used in the layout my $bin_height = $y2-$y1; my $fgcolor = $self->fgcolor; my $bgcolor = $self->factory->translate_color($self->panel->gridcolor); my $axiscolor = $self->color('axis_color') || $fgcolor; # Draw the axes $gd->line($x1, $y1, $x1, $y2, $axiscolor); $gd->line($x2-2,$y1, $x2-2,$y2, $axiscolor); $gd->line($x1, $y1, $x1+3,$y1, $axiscolor); $gd->line($x1, $y2, $x1+3,$y2, $axiscolor); $gd->line($x1, ($y2+$y1)/2,$x1+3,($y2+$y1)/2,$axiscolor); $gd->line($x2-4,$y1, $x2-1, $y1, $axiscolor); $gd->line($x2-4,$y2, $x2-1, $y2, $axiscolor); $gd->line($x2-4,($y2+$y1)/2,$x2-1,($y2+$y1)/2,$axiscolor); $gd->line($x1+5,$y2, $x2-5,$y2, $bgcolor); $gd->line($x1+5,($y2+$y1)/2,$x2-5,($y2+$y1)/2,$bgcolor); $gd->line($x1+5,$y1, $x2-5,$y1, $bgcolor); my $label = 'Kyte-Doolittle hydropathy plot'; $gd->string($self->mono_font,$x1+5,$y1,$label,$axiscolor) if $bin_height > $self->mono_font->height*2 && $self->width > $self->mono_font->width*length($label); $gd->string($self->mono_font,$x2-20,$y1,$maxkd,$axiscolor) if $bin_height > $self->mono_font->height*2.5; $gd->string($self->mono_font,$x2-20,$y2-$self->mono_font->height,$minkd,$axiscolor) if $bin_height > $self->mono_font->height*2.5; my $graphwidth = $x2 - $x1; $scale = $graphwidth / (@datapoints + $kd_window - 1); for (my $i = 1; $i < @datapoints; $i++) { my $x = $i + $kd_window / 2; my $xlo = $x1 + ($x - 1) * $scale; my $xhi = $x1 + $x * $scale; my $y = $y2 - ($bin_height*$datapoints[$i]); $gd->line($xlo, $y2 - ($bin_height*$datapoints[$i-1]), $xhi, $y, $fgcolor); } } sub make_key_feature { my $self = shift; my @gatc = qw(A C D E F G H I K L M N P Q R S T V W Y); my $offset = $self->panel->offset; my $scale = 1/$self->scale; # base pairs/pixel my $start = $offset+1; my $stop = $offset+100*$scale; my $feature = Bio::Graphics::Feature->new(-start=> $start, -stop => $stop, -seq => join('',map{$gatc[rand 4]} (1..500)), -name => $self->option('key'), -strand => '+1', ); $feature; } 1; __END__ =head1 NAME Bio::Graphics::Glyph::protein - The "protein" glyph =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This glyph draws protein sequences. At high magnifications, this glyph will draw the actual amino acids of the sequence. At low magnifications, the glyph will plot the Kyte-Doolite hydropathy. By default, the KD plot will use a window size of 9 residues, but this can be changed by specifying the kd_window option. The feature may return either a protein sequence or a DNA sequence in response to the seq() method. However, if it returns a DNA sequence, then the feature must also return the correct phase and strand in order for the glyph to translate its open reading frame correctly! =head2 OPTIONS The following options are standard among all Glyphs. See L for a full explanation. Option Description Default ------ ----------- ------- -fgcolor Foreground color black -outlinecolor Synonym for -fgcolor -bgcolor Background color turquoise -fillcolor Synonym for -bgcolor -linewidth Line width 1 -height Height of glyph 10 -font Glyph font gdSmallFont -connector Connector type 0 (false) -connector_color Connector color black -label Whether to draw a label 0 (false) -description Whether to draw a description 0 (false) -hilite Highlight color undef (no color) In addition to the common options, the following glyph-specific options are recognized: Option Description Default ------ ----------- ------- -do_kd Whether to draw the Kyte- true Doolittle hydropathy plot at low mags -kd_window Size of the sliding window 9 to use in the KD hydropathy calculation. -axis_color Color of the vertical axes fgcolor in the KD hydropathy plot -phase_style The way phase is to be interpreted. One of "012" "012" or "021" =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Aaron J. Mackey, based on the "dna" glyphy by Lincoln Stein Elstein@cshl.orgE and Peter Ashton Epda@sanger.ac.ukE. This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/dashed_line.pm000555001750001750 1107412165075746 22772 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::dashed_line; use strict; use base qw(Bio::Graphics::Glyph::generic); sub my_description { return < [ 'integer', 6, 'Width of the dash.'], space_size => [ 'integer', 3, 'Width of the space between dashes.'], space_color => [ 'color', undef, 'Color of the interval between dashes.', 'Nothing will be drawn between dashes if undef.'], shear => [ 'boolean', undef, 'Whether to offset the dash and space to create an interlocking effect.'], } } sub default_linewidth { return 1; } sub default_dash_size { return 6; } sub default_space_size { return 3; } sub draw_component { my $self = shift; my $gd = shift; my ($x1,$y1,$x2,$y2) = $self->calculate_boundaries(@_); my $fg = $self->fgcolor; my $midY = ($y1+$y2) / 2; my $linewidth = defined $self->option('linewidth') ? $self->option('linewidth') : $self->default_linewidth(); my $dash_size = defined $self->option('dash_size') ? $self->option('dash_size') : $self->default_dash_size(); my $space_size = defined $self->option('space_size') ? $self->option('space_size') : $self->default_space_size(); my $space_color = $self->option('space_color'); my $shear = $self->option('shear') || ""; $space_color = $self->factory->translate_color($space_color) if $space_color; my ($x, $_y1, $_y2); $x = $x1; while ($x<$x2) { my $newX = $x+$dash_size; $newX = $x2 if $newX > $x2; if ($shear == 1) { $_y1 = $midY-$linewidth; $_y2 = $midY; } else { $_y1 = $midY - $linewidth/2; $_y2 = $midY + $linewidth/2; } $self->filled_box($gd,$x,$_y1,$newX,$_y2,$fg,$fg); last if $newX >= $x2; $x = $newX; $newX = $x+$space_size; $newX = $x2 if $newX > $x2; if ($space_color) { if ($shear == 1) { $_y1 = $midY; $_y2 = $midY+$linewidth; } else { $_y1 = $midY - $linewidth/2; $_y2 = $midY + $linewidth/2; } $self->filled_box($gd, $x,$_y1,$newX,$_y2,$space_color,$space_color); } $x = $newX; } } 1; __END__ =head1 NAME Bio::Graphics::Glyph::dashed_line - The "dashed line" glyph =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This glyph draws a dashed line. The lengths of the dash and the space are configurable. The space can be filled with a different color, thus making a two-colored line. Also, the two colors can be "sheared". =head2 OPTIONS In addition to the common options, the following glyph-specific options are recognized: Option Description Default ------ ----------- ------- -dash_size Width of one dash 6 -space_size Width of one space 3 between dashes -space_color Color of the space none between dashes -shear Whether to use shearing 0 (1 or 0) -linewidth Standard option, but 1 important here =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Vsevolod (Simon) Ilyushchenko Esimonf@cshl.eduE. Copyright (c) 2004 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/decorated_transcript.pm000444001750001750 11151712165075746 24756 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::decorated_transcript; use strict; use warnings; use Bio::Graphics::Panel; use List::Util qw[min max]; use constant DECORATION_TAG_NAME => 'protein_decorations'; use constant DEBUG => 0; my @color_names = Bio::Graphics::Panel::color_names; use base qw(Bio::Graphics::Glyph::processed_transcript Bio::Graphics::Glyph::segments); sub my_descripton { return < option (see glyph options). The following line is an example of an mRNA feature in a GFF file that contains two protein decorations, one signal peptide predicted by SignalP and one transmembrane domain predicted by TMHMM: chr1 my_source mRNA 74796 75599 . + . ID=rna_gene-1;protein_decorations=SignalP40:SP:1:23:0:my_comment,TMHMM:TM:187:209:0 Each protein decoration consists of six fields separated by a colon: 1) Type. For example used to specify decoration source (e.g. 'SignalP40') 2) Name. Decoration name. Used as decoration label by default (e.g. 'SP' for signal peptide) 3) Start. Start coordinate at the protein-level (1-based coordinate) 4) End. End coordinate at the protein-level 5) Score. Optional. Score associated with a decoration (e.g. Pfam E-value). This score can be used to dynamically filter or color decorations via callbacks (see glyph options). 6) Description. Optional. User-defined description of decoration. The glyph ignores this description, but it will be made available to callback functions for inspection. Special characters like ':' or ',' that might interfere with the GFF tag parser should be avoided. If callback functions are used as glyph parameters (see below), the callback is called for each decoration separately. That is, the callback can be called multiple times for the same CDS feature, but each time with a different decoration. The currently drawn (active) decoration is made available to the callback via the glyph method 'active_decoration'. The active decoration is returned in form of a Bio::Graphics::Feature object, with decoration data fields mapped to corresponding feature attributes in the following way: type --> \$glyph->active_decoration->type name --> \$glyph->active_decoration->name nucleotide start coordinate --> \$glyph->active_decoration->start nucleotide end coordinate --> \$glyph->active_decoration->end protein start coordinate --> \$glyph->active_decoration->get_tag_values('p_start') protein end coordinate --> \$glyph->active_decoration->get_tag_values('p_end') score --> \$glyph->active_decoration->score description --> \$glyph->active_decoration->description In addition, the glyph passed to the callback allows access to the parent glyph and parent feature if required (use \$glyph->parent or \$glyph->parent->feature). END } sub my_options { return { decoration_visible => [ 'boolean', 'false', 'Specifies whether decorations should be visible or not. For selective display of individual', 'decorations, specify a callback function and return 1 or 0 after inspecting the active', 'decoration of the glyph. '], decoration_color => [ 'color', undef, 'Decoration background color. If no color is specified, colors are assigned automatically', 'by decoration type and name, whereas decorations of identical type and name are assigned', 'the same color. A special color \'transparent\' can be used here in combination with', 'the option \'decoration_border\' to draw decorations as outlines.'], decoration_border => [ ['none', 'solid', 'dashed'], 'none', 'Decoration border style. By default, decorations are drawn without border (\'none\' or', '0). Other valid options here include \'solid\' or \'dashed\'.'], decoration_border_color => [ 'color', 'black', 'Color of decoration boder.'], decoration_label => [ 'string', undef, 'Decoration label. If not specified, the second data field of the decoration is used', 'as label. Set this option to 0 to get unlabeled decorations. If the label text', 'extends beyond the size of the decorated segment, the label will be clipped. Clipping', 'does not occur for SVG output.'], decoration_label_position => [ ['inside', 'above', 'below'], undef, 'Position of decoration label. Labels can be drawn \'inside\' decorations (default)', 'or \'above\' and \'below\' decorations.'], decoration_label_color => [ 'color', 'undef', 'Decoration label color. If not specified, this color is complementary to', 'decoration_color (e.g., yellow text on blue background, white on black, etc.). If the', 'decoration background color is transparent and no decoration label color is specified,', 'the foreground color of the underlying transcript glyph is used as default.'], additional_decorations => [ 'string', undef, 'Additional decorations to those specified in the GFF file. Expected is a string', 'in the same format as described above for GFF files. This parameter is intended', 'to be used as callback function, which inspects the currently processed transcript', 'feature (first parameter to callback) and returns additional protein decorations', 'that should be drawn.'], decoration_height => [ 'integer', undef, 'Decoration height. Unless specified otherwise, the height of the decoration is the', 'height of the underlying transcript glyph minus 2, such that the decoration is drawn', 'within transcript boundaries.'], decoration_position => [ ['inside'], 'inside', 'Currently, decorations can only be drawn inside CDS segments.'], flip_minus => [ 'boolean', 0, 'If set to 1, features on the negative strand will be drawn flipped.', 'This is not particularly useful in GBrowse, but becomes handy if multiple features', 'should be drawn within the same panel, left-aligned, and on top of each other,', 'for example to allow easy gene structure comparisons.'], } } sub new { my ( $class, @args ) = @_; my %param = @args; warn "new(): " . join( ",", @args ) . "\n" if (DEBUG == 2); my $feature = $param{'-feature'}; my $factory = $param{'-factory'}; my $flip_minus = $factory->get_option('flip_minus'); if ( $flip_minus and $feature->strand < 1 ) { for ( my $i = 0 ; $i < @args ; $i++ ) { $args[ $i + 1 ] = 1 if ( $args[$i] eq '-flip' ); } } my $self = $class->Bio::Graphics::Glyph::processed_transcript::new(@args); $self->{'parent'} = undef; $self->{'additional_decorations'} = undef; $self->{'active_decoration'} = undef; $self->{'add_pad_bottom'} = undef; # give sub-glyphs access to parent glyph's decorations if ($self->decorations_visible) { foreach my $sub_glyph ( $self->parts ) { $sub_glyph->{'parent'} = $self; } } bless( $self, $class ); return $self; } sub finished { my $self = shift; warn "finished(): ".$self->feature->primary_tag." ".$self->feature."\n" if (DEBUG == 2); foreach my $sub_glyph ( $self->parts ) { $sub_glyph->{'parent'} = undef; } $self->Bio::Graphics::Glyph::processed_transcript::finished(@_); } sub parent { my $self = shift; return $self->{'parent'}; } sub decorations { my $self = shift; my $feature = $self->feature; return $self->{'parent'}->decorations(@_) if ($self->{'parent'} and $feature->primary_tag ne "mRNA"); return $feature->get_tag_values(DECORATION_TAG_NAME); } # allows to retrieve additional decorations via callback sub additional_decorations { my $self = shift; my $feature = $self->feature; return $self->{'parent'}->additional_decorations(@_) if ($self->{'parent'} and $feature->primary_tag ne "mRNA"); return $self->{'additional_decorations'} if (defined $self->{'additional_decorations'}); my @additional_decorations; my $additional_decorations_str = $self->option('additional_decorations'); if ($additional_decorations_str) { push(@additional_decorations, split(",", $additional_decorations_str)); } $self->{'additional_decorations'} = \@additional_decorations; return \@additional_decorations; } sub all_decorations { my $self = shift; my @all_decorations; # no decorations at gene level return \@all_decorations if ($self->feature->primary_tag eq "gene"); # forward request to mRNA parent glyph; # allows CDS child glyphs to retrieve decoration information from mRNA return $self->{'parent'}->all_decorations(@_) if ($self->{'parent'} and $self->feature->primary_tag ne "mRNA"); # decoration data specified as feature tag @all_decorations = $self->decorations; # add additional decorations provided via callback push(@all_decorations, @{$self->additional_decorations}); return \@all_decorations; } # returns stack offset of decoration (only used if decoration is drawn stacked) sub stack_offset_bottom { my $self = shift; return $self->{'parent'}->stack_offset_bottom(@_) if ($self->{'parent'} and $self->feature->primary_tag ne "mRNA"); my $decoration = shift; return $self->{'stack_offset_bottom'}{$decoration} } sub active_decoration { my $self = shift; return $self->{'active_decoration'}; } # get all decorations, including mapped nucleotide coordinates sub mapped_decorations { my $self = shift; my $feature = $self->feature; return $self->{'parent'}->mapped_decorations(@_) if ($self->{'parent'} and $feature->primary_tag ne "mRNA"); $self->_map_decorations() if (!defined $self->{'mapped_decorations'}); return $self->{'mapped_decorations'}; } # get all mapped decorations, as sorted by user call-back (if provided) # by default, decorations are sorted by length, causing shorter decorations # to be drawn on top of longer decorations # TODO: document this new feature sub sorted_decorations { my $self = shift; # forward request to mRNA parent glyph; # allows CDS child glyphs to retrieve decoration information from mRNA return $self->{'parent'}->sorted_decorations(@_) if ($self->{'parent'} and $self->feature->primary_tag ne "mRNA"); # cache for faster access return $self->{'sorted_decorations'} if (defined $self->{'sorted_decorations'}); # get sorted decorations from callback my $sorted_decorations = $self->option('sorted_decorations'); # if no callback or bad return value, sort by length by default (causes longer # decorations to be drawn first) if (!$sorted_decorations or ref($sorted_decorations) ne 'ARRAY') { my @sorted = reverse sort { $a->length <=> $b->length } (@{$self->mapped_decorations}); $sorted_decorations = \@sorted; if (DEBUG) { print STDERR "sorted decorations: "; foreach my $sd (@$sorted_decorations) { print STDERR $sd->name."(".$sd->length.") "; } print STDERR "\n"; } } $self->{'sorted_decorations'} = $sorted_decorations; return $sorted_decorations; } sub _map_decorations { my $self = shift; my $feature = $self->feature; $self->_map_coordinates(); my @mapped_decorations; foreach my $h ( @{$self->all_decorations} ) { my ( $type, $name, $p_start, $p_end, $score, $desc ) = split( ":", $h ); if (!defined $p_end) { warn "_map_decorations(): WARNING: invalid decoration data for feature $feature: '$h'\n"; next; } my $nt_start = $self->_map_codon_start($p_start); if (!$nt_start) { warn "DECORATION=$h\n"; warn "_map_decorations(): WARNING: could not map decoration start coordinate on feature $feature(".$feature->primary_tag.")\n"; next; } my $nt_end = $self->_map_codon_end($p_end); if (!$nt_end) { warn "DECORATION=$h\n"; warn "_map_decorations(): WARNING: could not map decoration end coordinate on feature $feature(".$feature->primary_tag.")\n"; next; } ( $nt_start, $nt_end ) = ( $nt_end, $nt_start ) if ( $nt_start > $nt_end ); my $f = Bio::Graphics::Feature->new ( -type => $type, -name => $name, -start => $nt_start, -end => $nt_end, -score => $score, -desc => $desc, -seq_id => $feature->seq_id, -strand => $feature->strand, -attributes => { # remember protein coordinates for callbacks 'p_start' => $p_start, 'p_end' => $p_end } ); # my $mapped_decoration = "$h:$nt_start:$nt_end"; push( @mapped_decorations, $f ); # init stack offset for stacked decorations if ($self->decoration_position($f) eq 'stacked_bottom') { if (!defined $self->{'stack_offset_bottom'}{$f}) { $self->{'cur_stack_offset_bottom'} = 2 if (!defined $self->{'cur_stack_offset_bottom'}); $self->{'stack_offset_bottom'}{$f} = $self->{'cur_stack_offset_bottom'}; $self->{'cur_stack_offset_bottom'} += $self->decoration_height($f); warn "$self: stack offset ".$f->name."($f): ".$self->{'stack_offset_bottom'}{$f}."\n" if (DEBUG); } } warn "DECORATION=$h --> $nt_start:$nt_end\n" if (DEBUG); } $self->{'mapped_decorations'} = \@mapped_decorations; } sub _map_codon_start { my $self = shift; my $protein_coordinate = shift; $self->throw('protein coordinate not specified: ') if (!$protein_coordinate and DEBUG); return $self->{'p2n'}->{$protein_coordinate}->{'codon_start'}; } sub _map_codon_end { my $self = shift; my $protein_coordinate = shift; $self->throw('protein coordinate not specified') if (!$protein_coordinate and DEBUG); return $self->{'p2n'}->{$protein_coordinate}->{'codon_end'}; } # map protein to nucleotide coordinate sub _map_coordinates { my $self = shift; # sort all CDS features by coordinates # NOTE: filtering for CDS features by passing feature type to get_SeqFeatures() # does not work for some reason, probably when no feature store attached my @cds = grep { $_->primary_tag eq 'CDS' } $self->feature->get_SeqFeatures(); if ( $self->feature->strand > 0 ) { my ( $ppos, $residue ) = ( 1, 0 ); my @sorted_cds = sort { $a->start <=> $b->start } (@cds); foreach my $c (@sorted_cds) { $self->{'p2n'}{ $ppos - 1 }{'codon_end'} = $c->start + $residue - 1 if ($residue); for ( my $ntpos = $c->start + $residue ; $ntpos <= $c->end ; $ntpos += 3 ) { $self->{'p2n'}{$ppos}{'codon_start'} = $ntpos; $self->{'p2n'}{$ppos}{'codon_end'} = $ntpos + 2; $ppos++; $residue = $ntpos + 2 - $c->end; } } } else { my ( $ppos, $residue ) = ( 1, 0 ); my @sorted_cds = reverse sort { $a->start <=> $b->start } (@cds); foreach my $c (@sorted_cds) { $self->{'p2n'}{ $ppos - 1 }{'codon_end'} = $c->end - $residue + 1 if ($residue); for ( my $ntpos = $c->end - $residue ; $ntpos >= $c->start ; $ntpos -= 3 ) { $self->{'p2n'}{$ppos}{'codon_start'} = $ntpos; $self->{'p2n'}{$ppos}{'codon_end'} = $ntpos - 2; $ppos++; $residue = $c->start - ( $ntpos - 2 ); } } } } sub decoration_top { my $self = shift; my $decoration = shift; if (!$decoration) { $self->throw("decoration not specified") if (DEBUG); return $self->top; } my $decoration_height = $self->decoration_height($decoration); my $decoration_position = $self->decoration_position($decoration); if ($decoration_position eq 'stacked_bottom') { $self->throw("$self: stack offset unknown for decoration ".$decoration->name."($decoration)") if (!defined $self->stack_offset_bottom($decoration) and DEBUG); return $self->bottom + $self->stack_offset_bottom($decoration); } else { $self->throw("invalid decoration_position: $decoration_position") if (($decoration_position ne 'inside') and DEBUG); return int(($self->bottom-$self->pad_bottom+$self->top+$self->pad_top)/2 - $decoration_height/2 + 0.5); } } sub decoration_bottom { my $self = shift; my $decoration = shift; if (!$decoration) { $self->throw("decoration not specified") if (DEBUG); return $self->bottom; } return $self->decoration_top($decoration) + $self->decoration_height($decoration) - 1; } sub _calc_add_padding { my $self = shift; my $height = $self->height; my ($add_pad_bottom, $add_pad_top) = (0, 0); foreach my $decoration (@{$self->mapped_decorations}) { my $h_height = $self->decoration_height($decoration); my ($label_pad_top, $label_pad_bottom) = (0, 0); if ($self->decoration_label($decoration)) { $label_pad_top = $self->labelfont->height if ($self->decoration_label_position($decoration) eq "above"); $label_pad_bottom = $self->labelfont->height if ($self->decoration_label_position($decoration) eq "below"); } if (($h_height - $height)/2 + $label_pad_top > $add_pad_top) { $add_pad_top = ($h_height - $height)/2 + $label_pad_top; } if (($h_height - $height)/2 + $label_pad_bottom > $add_pad_bottom) { $add_pad_bottom = ($h_height - $height)/2 + $label_pad_bottom; } if ($self->{'stack_offset_bottom'}{$decoration} and $self->{'stack_offset_bottom'}{$decoration}+$h_height > $add_pad_bottom) { $add_pad_bottom = $self->{'stack_offset_bottom'}{$decoration}+$h_height; } } $self->{'add_pad_bottom'} = $add_pad_bottom; $self->{'add_pad_top'} = $add_pad_top; } # add extra padding if decoration exceeds transcript boundaries and if labeled outside sub pad_bottom { my $self = shift; my $bottom = $self->option('pad_bottom'); return $bottom if defined $bottom; $self->_calc_add_padding() if (!defined $self->{'add_pad_bottom'}); my $pad = $self->Bio::Graphics::Glyph::processed_transcript::pad_bottom; return $pad if ($self->{'add_pad_bottom'} < 0); return $pad + $self->{'add_pad_bottom'}; } sub pad_top { my $self = shift; my $top = $self->option('pad_top'); return $top if defined $top; $self->_calc_add_padding() if (!defined $self->{'add_pad_top'}); my $pad = $self->Bio::Graphics::Glyph::processed_transcript::pad_top; return $pad if ($self->{'add_pad_top'} < 0); return $pad + $self->{'add_pad_top'}; } sub decoration_height { my $self = shift; my $decoration = shift; if (!$decoration) { $self->throw("decoration not specified") if (DEBUG); return $self->height; } $self->{'active_decoration'} = $decoration; # set active decoration for callback my $decoration_height = $self->option('decoration_height'); $decoration_height = $self->height-2 if ( !$decoration_height ); return $decoration_height; } sub decoration_position { my $self = shift; my $decoration = shift; if (!$decoration) { $self->throw("decoration not specified") if (DEBUG); return "inside"; } $self->{'active_decoration'} = $decoration; # set active decoration for callback my $decoration_position = $self->option('decoration_position'); $decoration_position = 'inside' if ( !$decoration_position ); if ($decoration_position ne 'inside' and $decoration_position ne 'stacked_bottom') { $self->throw('invalid decoration_position: '.$decoration_position) if (DEBUG); $decoration_position = 'inside'; } return $decoration_position; } sub _hash { my $hash = 0; foreach ( split //, shift ) { $hash = $hash * 33 + ord($_); } return $hash; } sub decoration_label_color { my $self = shift; my $decoration = shift; if (!$decoration) { $self->throw("decoration not specified") if (DEBUG); return "black"; } $self->{'active_decoration'} = $decoration; # set active decoration for callback my $decoration_label_color = $self->option('decoration_label_color'); return $decoration_label_color if ( defined $decoration_label_color and $decoration_label_color ne 'auto' and $decoration_label_color ne '' ); my $decoration_color = $self->decoration_color($decoration); return $self->fgcolor if ((!$decoration_label_color or $decoration_label_color eq 'auto') and $decoration_color eq "transparent"); # assign color complementary to decoration color my ( $red, $green, $blue ) = Bio::Graphics::Panel->color_name_to_rgb($decoration_color); $decoration_label_color = sprintf( "#%02X%02X%02X", 255 - $red, 255 - $green, 255 - $blue ); # background complement return $decoration_label_color; } sub decoration_label { my $self = shift; my $decoration = shift; if (!$decoration) { $self->throw("decoration not specified") if (DEBUG); return ""; } $self->{'active_decoration'} = $decoration; # set active decoration for callback my $decoration_label = $self->option('decoration_label'); return undef if ( defined $decoration_label and $decoration_label eq "0" ); return $decoration_label if ( $decoration_label and $decoration_label ne "1"); # assign decoration name as default label return $decoration->name; } sub decoration_label_position { my $self = shift; my $decoration = shift; if (!$decoration) { $self->throw("decoration not specified") if (DEBUG); return ""; } $self->{'active_decoration'} = $decoration; # set active decoration for callback my $decoration_label_position = $self->option('decoration_label_position'); return "inside" if (!$decoration_label_position); return $decoration_label_position; } sub decoration_border { my $self = shift; my $decoration = shift; if (!$decoration) { $self->throw("decoration not specified") if (DEBUG); return ""; } $self->{'active_decoration'} = $decoration; # set active decoration for callback return $self->option('decoration_border'); } sub decoration_color { my $self = shift; my $decoration = shift; if (!$decoration) { $self->throw("decoration not specified") if (DEBUG); return "white"; } $self->{'active_decoration'} = $decoration; # set active decoration for callback my $decoration_color = $self->option('decoration_color'); return $decoration_color if ( defined $decoration_color and $decoration_color ne 'auto' and $decoration_color ne '' ); # automatically assign color by hashing feature name to color index my $col_idx = _hash($decoration->type.":".$decoration->name) % scalar(@color_names); # decoration background should be different from CDS background while ( $self->factory->translate_color($color_names[$col_idx]) eq $self->bgcolor ) { $col_idx = ($col_idx + 1) % scalar(@color_names); } return $color_names[$col_idx]; } sub decoration_border_color { my $self = shift; my $decoration = shift; if (!$decoration) { $self->throw("decoration not specified") if (DEBUG); return "black"; } $self->{'active_decoration'} = $decoration; # set active decoration for callback my $decoration_border_color = $self->option('decoration_border_color'); return "black" if (!$decoration_border_color); return $decoration_border_color; } sub decorations_visible { my $self = shift; return $self->code_option('decoration_visible'); } sub decoration_visible { my $self = shift; my $decoration = shift; if (!$decoration) { $self->throw("decoration not specified") if (DEBUG); return 1; } $self->{'active_decoration'} = $decoration; # set active decoration for callback my $decoration_visible = $self->option('decoration_visible'); return $decoration_visible if ( defined $decoration_visible and $decoration_visible ne "" ); return 1; } #sub draw { # my $self = shift; # # warn "draw(): level " . $self->level . " " . $self->feature . "\n" # if (DEBUG); # # $self->Bio::Graphics::Glyph::processed_transcript::draw(@_); # #} sub draw_component { my $self = shift; warn "draw_component(): " . ref($self) . " " . $self->feature . "\n" if (DEBUG == 2); # draw regular glyph first if ( $self->feature->source eq 'legend' ) { # hack, but processed_transcript cannot be drawn without arrow... $self->Bio::Graphics::Glyph::segments::draw_component(@_); } else { $self->Bio::Graphics::Glyph::processed_transcript::draw_component(@_); } # draw decorations if parent information available if ( $self->{'parent'} and $self->feature->primary_tag eq "CDS") { return $self->draw_decorations(@_); } } sub draw_decorations { my $self = shift; my ( $gd, $dx, $dy ) = @_; warn "draw_decorations(): " . $self->feature . "\n" if (DEBUG == 2); my ( $left, $top, $right, $bottom ) = $self->bounds( $dx, $dy ); warn " bounds: left:$left,top:$top,right:$right,bottom:$bottom\n" if (DEBUG == 2); foreach my $mh (@{$self->sorted_decorations}) { # skip invisible decorations next if ( !$self->decoration_visible($mh) ); # determine overlapping segments between protein decorations and feature components my $overlap_start_nt = max( $self->feature->start, $mh->start ); my $overlap_end_nt = min( $self->feature->end, $mh->end ); if ( $overlap_start_nt <= $overlap_end_nt ) { # manual override; forces flip to be drawn flipped $self->factory->panel->flip( $self->flip ) if ( $self->option('flip_minus') ); my ( $h_left, $h_right ) = $self->map_no_trunc( $overlap_start_nt, $overlap_end_nt + 1 ); ( $h_left, $h_right ) = ( $h_right, $h_left ) if ( $h_left > $h_right ); # my ($h_top, $h_bottom) = ($dy + $self->top + $self->pad_top, $dy + $self->bottom - $self->pad_bottom); my $h_top = $dy + $self->decoration_top($mh); my $h_bottom = $dy + $self->decoration_bottom($mh); my $color = $self->decoration_color($mh); # don't draw over borders; not supported by SVG $gd->clip( $left + 1, $h_top, $right - 1, $h_bottom ) if ( !$gd->isa("GD::SVG::Image") ); if ($color ne 'transparent') { warn "filledRectangle: left=$h_left,top=$h_top,right=$h_right,bottom=$h_bottom\n" if (DEBUG == 2); $gd->filledRectangle( $h_left, $h_top, $h_right, $h_bottom, $self->factory->translate_color($color) ); } if ($self->decoration_border($mh)) { my ($b_left, $b_top, $b_right, $b_bottom) = ($h_left, $h_top, $h_right, $h_bottom); my $border_color = $self->factory->translate_color($self->decoration_border_color($mh)); warn "border rectangle: left=$b_left,top=$b_top,right=$b_right,bottom=$b_bottom\n" if (DEBUG == 2); if ($self->decoration_border($mh) eq "dashed") { my $image_class = $self->panel->image_class; my $gdTransparent = $image_class->gdTransparent; my $gdStyled = $image_class->gdStyled; $gd->setStyle($border_color,$border_color,$border_color,$gdTransparent,$gdTransparent); $gd->rectangle( $b_left, $b_top, $b_right, $b_bottom, $gdStyled ); } else { $gd->rectangle( $b_left, $b_top, $b_right, $b_bottom, $border_color ); } } $gd->clip( 0, 0, $gd->width, $gd->height ) if ( !$gd->isa("GD::SVG::Image") ); # draw label on first overlapping component my $h_label = $self->decoration_label($mh); if ( $h_label and ( ( $self->feature->strand > 0 and $mh->start >= $self->feature->start ) or ( $self->feature->strand <= 0 and $mh->end <= $self->feature->end ) ) ) { $self->draw_decoration_label( $gd, $dx, $dy, $mh, $h_top, $h_left, $h_bottom, $h_right, $h_label ); } } } } sub draw_decoration_label { my $self = shift; my ( $gd, $dx, $dy, $mh, $h_top, $h_left, $h_bottom, $h_right, $label ) = @_; warn "draw_decoration_label(): " . $self->feature . "\n" if (DEBUG == 2); my $font = $self->labelfont; my $label_top = $h_top + ($self->decoration_height($mh)-$font->height)/2; my $label_pos = $self->decoration_label_position($mh); if ( $label_pos and $label_pos eq "above" ) { $label_top = $h_top - $font->height - 1; } elsif ( $label_pos and $label_pos eq "below" ) { $label_top = $h_top + $self->decoration_height($mh); } my $label_color = $self->decoration_label_color($mh); $gd->clip( $h_left + 1, $label_top, $h_right - 1, $label_top + $font->height ) if ( !$gd->isa("GD::SVG::Image") ); $gd->string( $font, $h_left + 2, $label_top, $label, $self->factory->translate_color($label_color) ); $gd->clip( 0, 0, $gd->width, $gd->height ) if ( !$gd->isa("GD::SVG::Image") ); } 1; __END__ =head1 NAME Bio::Graphics::Glyph::decorated_transcript - draws processed transcript with protein decorations =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This glyph extends the functionality of the L glyph and allows to draw protein decorations (e.g., signal peptides, transmembrane domains, protein domains) on top of gene models. Currently, the glyph can draw decorations in form of colored or outlined boxes inside or around CDS segments. Protein decorations are specified at the 'mRNA' transcript level in protein coordinates. Protein coordinates are automatically mapped to nucleotide coordinates by the glyph. Decorations are allowed to span exon-exon junctions, in which case decorations are split between exons. By default, the glyph automatically assigns different colors to different types of protein decorations, whereas decorations of the same type are always assigned the same color. Protein decorations are provided either with mRNA features inside GFF files (see example below) or dynamically via callback function using the B option (see glyph options). The following line is an example of an mRNA feature in a GFF file that contains two protein decorations, one signal peptide predicted by SignalP and one transmembrane domain predicted by TMHMM: C Each protein decoration consists of six fields separated by a colon: =over =item 1. type Decoration type. For example used to specify decoration source (e.g. 'SignalP40') =item 2. name Decoration name. Used as decoration label by default (e.g. 'SP' for signal peptide) =item 3. start Start coordinate at the protein-level (1-based coordinate) =item 4. end End coordinate at the protein-level =item 5. score Optional. Score associated with a decoration (e.g. Pfam E-value). This score can be used to dynamically filter or color decorations via callbacks (see glyph options). =item 6. description Optional. User-defined description of decoration. The glyph ignores this description, but it will be made available to callback functions for inspection. Special characters like ':' or ',' that might interfere with the GFF tag parser should be avoided. =back If callback functions are used as glyph parameters (see below), the callback is called for each decoration separately. That is, the callback can be called multiple times for a given CDS feature, but each time with a different decoration that overlaps with this CDS. The currently drawn (active) decoration is made available to the callback via the glyph method 'active_decoration'. The active decoration is returned in form of a Bio::Graphics::Feature object, with decoration data fields mapped to corresponding feature attributes in the following way: =over =item * type --> $glyph->active_decoration->type =item * name --> $glyph->active_decoration->name =item * nucleotide start coordinate --> $glyph->active_decoration->start =item * nucleotide end coordinate --> $glyph->active_decoration->end =item * protein start coordinate --> $glyph->active_decoration->get_tag_values('p_start') =item * protein end coordinate --> $glyph->active_decoration->get_tag_values('p_end') =item * score --> $glyph->active_decoration->score =item * description --> $glyph->active_decoration->description =back In addition, the glyph passed to the callback allows access to the parent glyph and parent feature if required (use $glyph->parent or $glyph->parent->feature). =head2 OPTIONS This glyph inherits all options from the L glyph. In addition, it recognizes the following glyph-specific options: Option Description Default ------ ----------- ------- -decoration_visible Specifies whether decorations should be visible false or not. For selective display of individual decorations, specify a callback function and return 1 or 0 after inspecting the active decoration of the glyph. -decoration_color Decoration background color. If no color is specified, colors are assigned automatically by decoration type and name, whereas decorations of identical type and name are assigned the same color. A special color 'transparent' can be used here in combination with the option 'decoration_border' to draw decorations as outlines. -decoration_border Decoration border style. By default, decorations are 0 (none) drawn without border ('none' or 0). Other valid options here include 'solid' or 'dashed'. -decoration_border_color Color of decoration border. black -decoration_label Decoration label. If not specified, the second data true field of the decoration is used as label. Set this (decoration name) option to 0 to get unlabeled decorations. If the label text extends beyond the size of the decorated segment, the label will be clipped. Clipping does not occur for SVG output. -decoration_label_position Position of decoration label. Labels can be drawn inside 'inside' decorations (default) or 'above' and 'below' decorations. -decoration_label_color Decoration label color. If not specified, this color is complementary to decoration_color (e.g., yellow text on blue background, white on black, etc.). If the decoration background color is transparent and no decoration label color is specified, the foreground color of the underlying transcript glyph is used as default. -additional_decorations Additional decorations to those specified in the GFF undefined file. Expected is a string in the same format as described above for GFF files. This parameter is intended to be used as callback function, which inspects the currently processed transcript feature (first parameter to callback) and returns additional protein decorations that should be drawn. -decoration_height Decoration height. Unless specified otherwise, CDS height-2 the height of the decoration is the height of the underlying transcript glyph minus 2, such that the decoration is drawn within transcript boundaries. -decoration_position Currently decorations can only be drawn inside inside CDS segments. -flip_minus If set to 1, features on the negative strand will be false drawn flipped. This is not particularly useful in GBrowse, but becomes handy if multiple features should be drawn within the same panel, left-aligned, and on top of each other, for example to allow for easy gene structure comparisons. =head1 BUGS Strandedness arrows are decorated incorrectly. Currently, the glyph plots a rectangular box over the arrow instead of properly coloring the arrow. Overlapping decorations are drawn on top of each other without particular order. The only solution to this problem at this point is to reduce decorations to a non-overlapping set. For SVG output or if drawn not inside decorations, decoration labels are not clipped. Similar as for overlapping decorations, this can result in labels being drawn on top of each other. Please report all errors. =head1 SEE ALSO L, L, L, L =head1 AUTHOR Christian Frech Ecfa24@sfu.caE This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/smoothing.pm000555001750001750 274412165075746 22526 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::smoothing; use base 'Bio::Graphics::Glyph'; use strict; use constant SMOOTHING => 'mean'; sub my_options { { smoothing => [ ['none','mean','max','min'], 'none', 'Whether to smooth data values across a defined window.', 'Mean smoothing will run a rolling mean across the window.', 'Max smoothing will take the maximum value across the window,', 'and min smoothing will take the minimum value.' ], smoothing_window => [ 'integer', undef, 'Size of the smoothing window. If not specified, the window', 'will be taken to be 10% of the region under display.' ], }; } sub get_smoothing { my $self = shift; return 'none' if $self->smooth_window == 1; return $self->option('smoothing') or SMOOTHING; } sub smooth_window { my $self = shift; my $smooth_window = $self->option('smoothing_window') || $self->option('smoothing window'); # drat! return $smooth_window if defined $smooth_window; my $start = $self->smooth_start; my $end = $self->smooth_end; $smooth_window = int (($end - $start)/(10*$self->width)); $smooth_window = 1 unless $smooth_window > 2; return $smooth_window; } sub smooth_start { my $self = shift; my ($start) = sort {$b<=>$a} ($self->feature->start,$self->panel->start); return $start; } sub smooth_end { my $self = shift; my ($end) = sort {$a<=>$b} ($self->feature->end,$self->panel->end); return $end; } 1; Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/wave.pm000555001750001750 614512165075746 21460 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::wave; use strict; use base qw(Bio::Graphics::Glyph::generic); sub default_spread { return 0.3; } sub default_radius { return 5; } sub connector { 'none' } sub draw_component { my $self = shift; my $gd = shift; my ($x1,$y1,$x2,$y2) = $self->calculate_boundaries(@_); my $spread = defined $self->option('spread') ? $self->option('spread') : $self->default_spread(); my $fg = $self->fgcolor; my $height = ($y2-$y1)/2; my $midY = $y1 + $height; if ($self->option('circle') == 1) { my $radius = defined $self->option('radius') ? $self->option('radius') : $self->default_radius(); $gd->ellipse($x1+$radius,$midY,2*$radius,2*$radius,$fg); $x1 = $x1+2*$radius; } if ($self->option('line') == 1) { if ($x1 < $x2) { $gd->line($x1,$midY,$x2,$midY,$fg); } return; } my ($oldX, $oldY); foreach my $x ($x1..$x2) { my $y = -$height * sin ($spread * ($x-$x1))+$midY; if ($x>$x1) { $gd->line($oldX,$oldY,$x,$y,$fg); } $oldX=$x; $oldY=$y; } } 1; __END__ =head1 NAME Bio::Graphics::Glyph::wave - The "wave" glyph =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This glyph draws a sine wave with an optional circle in the beginning. The wave can also become a straight line. =head2 OPTIONS In addition to the common options, the following glyph-specific options are recognized: Option Description Default ------ ----------- ------- -spread The "spread" of the sine curve 0.3 Values from 0.1 to 0.5 look best -line Whether to draw a line 0 instead of a wave (1 or 0) -circle Whether to draw a circle 0 in the left corner (1 or 0) -radius The radius of the circle 5 if present =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Vsevolod (Simon) Ilyushchenko Esimonf@cshl.eduE. Copyright (c) 2004 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/christmas_arrow.pm000555001750001750 621612165075746 23724 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::christmas_arrow; use strict; use base qw(Bio::Graphics::Glyph::generic); use Math::Trig; sub my_description { return < ['integer',4, 'Radius of the circle glyph.'], length => ['integer',20,'Length of the arrow.'], } } sub default_radius { return 4; } sub default_length { return 20; } sub draw_component { my $self = shift; my $gd = shift; my ($x1,$y1,$x2,$y2) = $self->calculate_boundaries(@_); my $fg = $self->fgcolor; my $radius = defined $self->option('radius') ? $self->option('radius') : $self->default_radius (); $gd->filledEllipse($x1+$radius, $y2-$radius, 2*$radius, 2*$radius, $fg); my $length = defined $self->option('length') ? $self->option('length') : $self->default_length(); my $angle = deg2rad(30); my $dx = 6; my $dy = 4; my $midX = $x2-$dx; my $midY = $y1+$dy; $gd->line($x1+$radius, $y2-$radius, $x1+$radius, $y1+$dy, $fg); return if ($x2-$x1 <= $radius); $x2 = $x1+$radius+$length; $gd->line($x1+$radius, $midY, $x2, $midY, $fg); $gd->line($x2, $midY, $x2-$dx, $y1, $fg); $gd->line($x2, $midY, $x2-$dx, $y1+2*$dy, $fg); } 1; __END__ =head1 NAME Bio::Graphics::Glyph::christmas_arrow - The "christmas arrow" glyph =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This glyph draws an arrow which has a circle ("christmas ball") dangling at one end. =head2 OPTIONS In addition to the common options, the following glyph-specific options are recognized: Option Description Default ------ ----------- ------- -radius Radius of the circle 4 glyph -length Length of the arrow 20 -height Standard option, but 10 important here =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Vsevolod (Simon) Ilyushchenko Esimonf@cshl.eduE. Copyright (c) 2004 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/decorated_gene.pm000444001750001750 1025212165075746 23455 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::decorated_gene; use strict; use base 'Bio::Graphics::Glyph::decorated_transcript'; sub extra_arrow_length { my $self = shift; return 0 unless $self->{level} == 1; local $self->{level} = 0; # fake out superclass return $self->SUPER::extra_arrow_length; } sub pad_left { my $self = shift; my $type = $self->feature->primary_tag; return 0 unless $type =~ /gene|mRNA/; $self->SUPER::pad_left; } sub pad_right { my $self = shift; return 0 unless $self->{level} < 2; # don't invoke this expensive call on exons my $strand = $self->feature->strand; $strand *= -1 if $self->{flip}; my $pad = $self->SUPER::pad_right; return $pad unless defined($strand) && $strand > 0; my $al = $self->arrow_length; return $al > $pad ? $al : $pad; } sub pad_bottom { my $self = shift; return 0 unless $self->{level} < 2; # don't invoke this expensive call on exons return $self->SUPER::pad_bottom; } sub pad_top { my $self = shift; return 0 unless $self->{level} < 2; # don't invoke this expensive call on exons return $self->SUPER::pad_top; } sub bump { my $self = shift; return 1 if $self->{level} == 0; # top level bumps, other levels don't unless specified in config return $self->SUPER::bump; } sub label { my $self = shift; return unless $self->{level} < 2; if ($self->label_transcripts && $self->{feature}->primary_tag eq 'mRNA') { # the mRNA return $self->_label; } else { return $self->SUPER::label; } } sub label_position { my $self = shift; return 'top' if $self->{level} == 0; return 'left'; } sub label_transcripts { my $self = shift; return $self->{label_transcripts} if exists $self->{label_transcripts}; return $self->{label_transcripts} = $self->_label_transcripts; } sub _label_transcripts { my $self = shift; return $self->option('label_transcripts'); } sub draw_connectors { my $self = shift; return if $self->feature->primary_tag eq 'gene'; $self->SUPER::draw_connectors(@_); } sub maxdepth { my $self = shift; my $md = $self->Bio::Graphics::Glyph::maxdepth; return $md if defined $md; return 2; } sub _subfeat { my $class = shift; my $feature = shift; return $feature->get_SeqFeatures('mRNA') if $feature->primary_tag eq 'gene'; my @subparts; if ($class->option('sub_part')) { @subparts = $feature->get_SeqFeatures($class->option('sub_part')); } else { @subparts = $feature->get_SeqFeatures(qw(CDS five_prime_UTR three_prime_UTR UTR)); } # The CDS and UTRs may be represented as a single feature with subparts or as several features # that have different IDs. We handle both cases transparently. my @result; foreach (@subparts) { if ($_->primary_tag =~ /CDS|UTR/i) { my @cds_seg = $_->get_SeqFeatures; if (@cds_seg > 0) { push @result,@cds_seg } else { push @result,$_ } } else { push @result,$_; } } return @result; } 1; __END__ =head1 NAME Bio::Graphics::Glyph::decorated_gene - A GFF3-compatible gene glyph with protein decorations =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This glyph has the same functionality as the L glyph, but uses the L glyph instead of the L glyph to draw transcripts. One usecase for the 'decorated_gene' glyph is to highlight protein features of different splice forms of the same gene to see how splice forms differ in terms of protein features, for example the presence of predicted signal peptides or protein domains. See L for a description of how to provide protein decorations for transcripts. =head1 BUGS =head1 SEE ALSO L, L =head1 AUTHOR Christian Frech Ecfa24@sfu.caE This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/primers.pm000555001750001750 751612165075746 22202 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::primers; # package to use for drawing something that looks like # primer pairs. use strict; use base qw(Bio::Graphics::Glyph::generic); use constant HEIGHT => 8; # override draw method sub draw_component { my $self = shift; my $gd = shift; my ($x1,$y1,$x2,$y2) = $self->calculate_boundaries(@_); my $height = $self->option('height') || $self->option('size') || HEIGHT; my $fg = $self->fgcolor; my $a2 = $height/2; my $center = $y1 + $a2; # just draw us as a solid line -- very simple if ($x2-$x1 < $height*2) { $gd->line($x1,$center,$x2,$center,$fg); return; } # otherwise draw two pairs of arrows # --> <-- my $trunc_left = $x1 < $self->panel->left; my $trunc_right = $x2 > $self->panel->right; unless ($trunc_left) { $gd->setThickness(2) if $height > 6; $gd->line($x1,$center,$x1 + $height,$center,$fg); $gd->line($x1 + $height,$center,$x1 + $height - $a2,$center-$a2,$fg); $gd->line($x1 + $height,$center,$x1 + $height - $a2,$center+$a2,$fg); $gd->setThickness(1); } unless ($trunc_right) { $gd->setThickness(2) if $height > 6; $gd->line($x2,$center,$x2 - $height,$center,$fg); $gd->line($x2 - $height,$center,$x2 - $height + $a2,$center+$a2,$fg); $gd->line($x2 - $height,$center,$x2 - $height + $a2,$center-$a2,$fg); $gd->setThickness(1); } # connect the dots if requested if ($self->connect) { my $c = $self->color('connect_color') || $self->bgcolor; $gd->line($x1 + ($trunc_left ? 0 : $height + 2),$center, $x2 - ($trunc_right ? 0 : $height + 2),$center, $c); } # add a label if requested $self->draw_label($gd,@_) if $self->option('label'); $self->draw_description($gd,@_) if $self->option('description'); } sub connect { my $self = shift; return $self->option('connect') if defined $self->option('connect'); 1; # default } 1; __END__ =head1 NAME Bio::Graphics::Glyph::primers - The "STS primers" glyph =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This glyph draws two arrows oriented towards each other and connected by a line of a contrasting color. The length of the arrows is immaterial, but the length of the glyph itself corresponds to the length of the scaled feature. =head2 OPTIONS In addition to the common options, the following glyph-specific options are recognized: Option Description Default ------ ----------- ------- -connect Whether to connect the true two arrowheads by a line. -connect_color The color to use for the bgcolor connecting line. =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Allen Day Eday@cshl.orgE. Copyright (c) 2001 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/cds.pm000555001750001750 4265312165075746 21313 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::cds; use strict; use Bio::Graphics::Glyph::segments; use Bio::Graphics::Util qw(frame_and_offset); use Bio::Tools::CodonTable; use base qw(Bio::Graphics::Glyph::segmented_keyglyph Bio::Graphics::Glyph::translation); sub my_description { return < [ 'color', undef, 'Color for the first (+) frame. If undefined, uses the bgcolor.'], frame1f => [ 'color', undef, 'Color for the second (+) frame. If undefined, uses the bgcolor.'], frame2f => [ 'color', undef, 'Color for the third (+) frame. If undefined, uses the bgcolor.'], frame0r => [ 'color', undef, 'Color for the first (-) frame. If undefined, uses the bgcolor.'], frame1r => [ 'color', undef, 'Color for the first (-) frame. If undefined, uses the bgcolor.'], frame2r => [ 'color', undef, 'Color for the third (-) frame. If undefined, uses the bgcolor.'], gridcolor => [ 'color', 'lightslategray', 'Color for the "staff".'], translation => [ ['3frame','6frame'], '3frame', 'Number of lines of reading frames to show.', 'For best results, specify a height of at least 30 pixels for "6frame",', 'and at least 15 pixels for 3frame.'], sixframe => [ 'boolean', undef, 'Draw a six-frame staff. This option overrides -translation,', 'which essentially does the same thing.'], require_subparts => [ 'boolean', undef, "Don't try to draw reading frames unless the feature has subparts."], sub_part => [ 'string', undef, 'For features with multiple subpart types, define which one is the CDS', 'part that contains phase information.'], codontable => [ 'integer', 1, 'Which codon table to use for translations, see L.'], phase_style => [ ['012','021'], '012', 'The way the phase method is to be interpreted. See the manual page of this', 'glyph for an explanation.'], ignore_empty_phase => [ 'boolean', undef, 'Only draw features that have a phase defined.'], cds_only => [ 'boolean', undef, 'Only draw features of type "CDS".'], } } my %default_colors = qw( frame0f cornflowerblue frame1f blue frame2f darkblue frame0r magenta frame1r red frame2r darkred ); my %swap_phase = ( 0 => 0, 1 => 2, 2 => 1, '' => 0); sub connector { 0 }; sub description { my $self = shift; return if $self->level; return $self->SUPER::description; }; sub default_color { my ($self,$key) = @_; return $self->factory->translate_color($default_colors{$key}); } sub sixframe { my $self = shift; return $self->{sixframe} if exists $self->{sixframe}; my $sixframe = $self->option('sixframe'); $sixframe = $self->option('translation') eq '6frame' unless defined $sixframe; return $self->{sixframe} = $sixframe; } sub maxdepth { 1 }; sub require_subparts { my $self = shift; my $rs = $self->option('require_subparts'); $rs = $self->feature->type eq 'coding' if !defined $rs; # shortcut for the "coding" aggregator $rs; } sub ignore_undef_phase { shift->option('ignore_empty_phase'); } sub ignore_non_cds { shift->option('cds_only'); } sub phase_style { shift->option('phase_style') || '012'; } # figure out (in advance) the color of each component sub draw { my $self = shift; my ($gd,$left,$top) = @_; $self->panel->startGroup($gd); my @parts = $self->parts; @parts = $self if !@parts && $self->level == 0 && !$self->require_subparts; my $fits = $self->protein_fits; my $strand = $self->feature->strand || 1; # draw the staff (musically speaking) if ($self->level == 0) { my ($x1,$y1,$x2,$y2) = $self->bounds($left,$top); my $line_count = $self->sixframe ? 6 : 3; my $height = ($y2-$y1)/$line_count; my $grid = $self->gridcolor; for (0..$line_count-1) { my $offset = $y1+$height*$_+1; $gd->line($x1,$offset,$x2,$offset,$grid); # with three-frame translation, the position of the arrows changes depending on # the strand of the feature. With six-frame translation, we draw the first three # staff lines with an arrow to the right, and the second three to the left my $forward = ($line_count == 6) ? ($_ < 3) : ($strand > 0); if ($forward) { $gd->line($x2,$offset,$x2-2,$offset-2,$grid); $gd->line($x2,$offset,$x2-2,$offset+2,$grid); } else { $gd->line($x1,$offset,$x1+2,$offset-2,$grid); $gd->line($x1,$offset,$x1+2,$offset+2,$grid); } } } $self->{cds_part2color} ||= {}; my $fill = $self->bgcolor; # figure out the colors of each part # sort minus strand features backward @parts = map { $_->[0] } sort { $b->[1] <=> $a->[1] } map { [$_, $_->left ] } @parts if $strand < 0; my $codon_table = $self->option('codontable'); $codon_table = 1 unless defined $codon_table; my $translate_table = Bio::Tools::CodonTable->new(-id=>$codon_table); my $ignore_undef_phase = $self->ignore_undef_phase; my $ignore_non_cds = $self->ignore_non_cds; my $broken_phase = $self->phase_style eq '021'; for (my $i=0; $i < @parts; $i++) { my $part = $parts[$i]; my $feature = $part->feature; my $type = $feature->can('method') ? $feature->method : $feature->can('type') ? $feature->type : ''; next if ($self->option('sub_part') && $type ne $self->option('sub_part')); next if $ignore_non_cds && lc($type) ne 'cds'; my $pos = $feature->strand >= 0 ? $feature->start : $feature->end; my $phase = $feature->can('phase') ? $feature->phase # bioperl uses "frame" but this is incorrect usage :$feature->can('frame') ? $feature->frame :undef; next if $ignore_undef_phase && !defined($phase); $phase ||= 0; $phase = $swap_phase{$phase} if $broken_phase; my $strand = $feature->strand; my ($frame,$offset) = frame_and_offset($pos, $strand, $phase); my $suffix = $strand < 0 ? 'r' : 'f'; my $key = "frame$frame$suffix"; $self->{cds_frame2color}{$key} ||= $self->color($key) || $self->default_color($key) || $fill; $part->{cds_partcolor} = $self->{cds_frame2color}{$key}; $part->{cds_frame} = $frame; $part->{cds_offset} = $offset; if ($self->do_cds_translation && (my $seq = $feature->seq)) { BLOCK: { $seq = $self->get_seq($seq); # do in silico splicing in order to find the codon that # arises from the splice my $protein = $seq->translate(undef,undef,$phase,$codon_table)->seq; $part->{cds_translation} = $protein; length $protein >= $feature->length/3 and last BLOCK; ($feature->length - $phase) % 3 == 0 and last BLOCK; my $next_part = $parts[$i+1] or do { $part->{cds_splice_residue} = '?'; last BLOCK; }; my $next_feature = $next_part->feature or last BLOCK; my $next_phase = eval {$next_feature->phase} or last BLOCK; my $splice_codon = ''; my $left_of_splice = substr($self->get_seq($feature->seq), -$next_phase, $next_phase); my $right_of_splice = substr($self->get_seq($next_feature->seq),0 , 3-$next_phase); $splice_codon = $left_of_splice . $right_of_splice; length $splice_codon == 3 or last BLOCK; my $amino_acid = $translate_table->translate($splice_codon); $part->{cds_splice_residue} = $amino_acid; } } } $self->Bio::Graphics::Glyph::generic::draw($gd,$left,$top); $self->panel->endGroup($gd); } sub do_cds_translation { return shift->protein_fits } # draw the notes on the staff sub draw_component { my $self = shift; my $gd = shift; my ($x1,$y1,$x2,$y2) = $self->bounds(@_); my $color = $self->{cds_partcolor} or return; my $feature = $self->feature; my $frame = $self->{cds_frame}; my $linecount = $self->sixframe ? 6 : 3; unless ($self->protein_fits && $self->{cds_translation}) { my $height = ($y2-$y1)/$linecount; my $offset = $y1 + $height*$frame; $offset += ($y2-$y1)/2 if $self->sixframe && $self->strand < 0; # ugh. This works, but I don't know why $offset = $y1 + (($y2-$y1) - ($offset-$y1))-$height if $self->{flip}; $gd->filledRectangle($x1,$offset,$x2,$offset+2,$color); return; } # we get here if there's room to draw the primary sequence my $font = $self->mono_font; my $pixels_per_residue = $self->pixels_per_residue; my $strand = $feature->strand; my $y = $y1-1; my $fontwidth = $font->width; $strand *= -1 if $self->{flip}; $y += ($y2-$y1)/2 if $self->sixframe && $strand < 0; # have to remap feature start and end into pixel coords in order to: # 1) correctly align the amino acids with the nucleotide seq # 2) correct for the phase offset my $start = $self->map_no_trunc($feature->start + $self->{cds_offset}); my $stop = $self->map_no_trunc($feature->end + $self->{cds_offset}); ($start,$stop) = ($stop,$start) if $stop < $start; # why does this keep happening? my @residues = split '',$self->{cds_translation}; push @residues,$self->{cds_splice_residue} if $self->{cds_splice_residue}; for (my $i=0;$i<@residues;$i++) { my $x = $strand > 0 ? $start + $i * $pixels_per_residue : $stop - $i * $pixels_per_residue; next unless ($x >= $x1 && $x <= $x2); $x -= $fontwidth + 1 if $self->{flip}; # align right when flipped $gd->char($font,$x+1,$y,$residues[$i],$color); } } sub make_key_feature { my $self = shift; my @gatc = qw(g a t c); my $offset = $self->panel->offset; my $scale = 1/$self->scale; # base pairs/pixel my $start = $offset; my $stop = $offset + 100 * $scale; my $seq = join('',map{$gatc[rand 4]} (1..1500)); my $feature = Bio::Graphics::Feature->new(-start=> $start, -end => $stop, -seq => $seq, -name => $self->option('key'), -strand=> +1, ); $feature->add_segment(Bio::Graphics::Feature->new( -start=> $start, -end => $start + ($stop - $start)/2, -seq => $seq, -name => $self->option('key'), -strand=> +1, ), Bio::Graphics::Feature->new( -start=> $start + ($stop - $start)/2+1, -end => $stop, -seq => $seq, -name => $self->option('key'), -phase=> 1, -strand=> +1, )); $feature; } # never allow our components to bump sub bump { my $self = shift; return $self->SUPER::bump(@_) if $self->all_callbacks; return 0; } 1; __END__ =head1 NAME Bio::Graphics::Glyph::cds - The "cds" glyph =head1 SYNOPSIS See L and L. =head1 DESCRIPTION This glyph draws features that are associated with a protein coding region. At high magnifications, draws a series of boxes that are color-coded to indicate the frame in which the translation occurs. At low magnifications, draws the amino acid sequence of the resulting protein. Amino acids that are created by a splice are optionally shown in a distinctive color. =head2 OPTIONS The following options are standard among all Glyphs. See L for a full explanation. Option Description Default ------ ----------- ------- -fgcolor Foreground color black -outlinecolor Synonym for -fgcolor -bgcolor Background color turquoise -fillcolor Synonym for -bgcolor -linewidth Line width 1 -height Height of glyph 10 -font Glyph font gdSmallFont -connector Connector type 0 (false) -connector_color Connector color black -label Whether to draw a label 0 (false) -description Whether to draw a description 0 (false) -strand_arrow Whether to indicate 0 (false) strandedness -hilite Highlight color undef (no color) In addition, the cds glyph recognizes the following glyph-specific options: Option Description Default ------ ----------- ------- -frame0f Color for first (+) frame background color -frame1f Color for second (+) frame background color -frame2f Color for third (+) frame background color -frame0r Color for first (-) frame background color -frame1r Color for second (-) frame background color -frame2r Color for third (-) frame background color -gridcolor Color for the "staff" lightslategray -translation Number of lines of reading 3frame frames to show. One of "3frame", or "6frame". For 6frame, specify a height of at least 30 pixels. -sixframe Draw a six-frame staff 0 (false; usually draws 3 frame) This value overrides -translation, which essentially does the same thing. -require_subparts Don't draw the reading frame 0 false unless it is a feature subpart. -sub_part For objects with multiple undef subpart types, defines which is the CDS part. -codontable Codon table to use 1 (see Bio::Tools::CodonTable) -phase_style The way phase is to be interpreted. One of "012" "012" or "021" -ignore_empty_phase false Only draw features that have their phase defined. -cds_only Only draw features of type false 'CDS' This glyph is more sensitive to the underlying data model than usual, so there are a few additional options to use to help adapt the glyph to different environments. The -require_subparts option is suggested when rendering spliced transcripts which contain multiple CDS subparts. Otherwise, the glyph will hickup when zoomed way down onto an intron between two CDSs (a phantom reading frame will appear). For unspliced sequences, do *not* use -require_subparts. The -phase_style controls how the value returned by the phase() or frame() methods is to be interpreted. The official interpretation is that the phase value indicates the offset into the feature at which the reading frame starts -- e.g. a phase of "2" means the reading frame starts after skipping two bases from the beginning of the feature. However, many GFF2 format feature files interpret this field to mean the position reading frame of the first base of the feature -- e.g. a phase of "2" means that the reading frame starts after skipping just one base from the beginning of the feature. Specify "012" to interpret the phase field in the correct way, and "021" to interpret the phase field in the legacy way. The default is "012." Here is how the option names were chosen: * * * Base the reading frame starts on A B C A B C A B C... 0 1 2 PHASE REPRESENTED CORRECTLY 0 2 1 PHASE REPRESENTED IN THE LEGACY WAY Set the -ignore_empty_phase option to true if you wish to skip subfeatures that do not have a defined phase() or frame(). This is useful if you are rendering exons that have both translated and untranslated parts, and you wish to skip the untranslated parts. Set the -cds_only option to true if you wish to draw the glyph only for subfeatures of type 'CDS'. This is recommended. =head1 SUGGESTED STANZA FOR GENOME BROWSER Using the "coding" aggregator, this produces a nice gbrowse display. [CDS] feature = coding glyph = cds frame0f = cadetblue frame1f = blue frame2f = darkblue frame0r = darkred frame1r = red frame2r = crimson description = 0 height = 13 label = CDS frame key = CDS citation = This track shows CDS reading frames. =head1 BUGS Please report them. =head1 SEE ALSO L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L =head1 AUTHOR Lincoln Stein Elstein@cshl.orgE Copyright (c) 2001 Cold Spring Harbor Laboratory This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See DISCLAIMER.txt for disclaimers of warranty. =cut Bio-Graphics-2.37/lib/Bio/Graphics/Glyph/ternary_plot.pm000555001750001750 1653612165075746 23265 0ustar00lsteinlstein000000000000package Bio::Graphics::Glyph::ternary_plot; # Draw ternary (triangle plots) use strict; use base qw(Bio::Graphics::Glyph::generic); use Bio::Graphics::Glyph::xyplot; use constant Sin60 =>0.866025403784439; use constant Tan60 =>1.73205080756888; sub calculate_side { my $self = shift; $self->option('height') / Sin60; } # positioning this properly is a bit tricky, because if the side of the triangle # is greater than the width of the feature, then we need to add extra left and right # padding. sub pad_left { my $self = shift; my $feature = $self->feature; my $left = $self->SUPER::pad_left; my $side = $self->calculate_side; my ($a,$b) = $self->map_pt($feature->start,$feature->stop); my $width = abs($b-$a); my $extra = $width > $side ? 0 : ($side-$width)/2; return $extra > $left ? $extra : $left; } sub pad_right { my $self = shift; my $right = $self->SUPER::pad_right; my $side = $self->calculate_side; my $feature = $self->feature; my ($a,$b) = $self->map_pt($feature->start,$feature->stop); my $width = abs($b-$a); my $extra = $width > $side ? 0 : ($side-$width)/2; return $extra > $right ? $extra : $right; } sub pad_top { my $self = shift; my $pad = $self->SUPER::pad_top; return $pad unless $self->option('vertices'); my $font = $self->image_class->gdTinyFont(); my $lh = $font->height/2; return $pad > $lh ? $pad : $lh; } sub pad_bottom { my $self = shift; my $pad = $self->SUPER::pad_bottom; return $pad unless $self->option('vertices'); my $font = $self->image_class->gdTinyFont(); my $lh = $font->height/2; return $pad > $lh ? $pad : $lh; } sub triples { my $self = shift; my $triples = $self->option('triples'); return $triples if defined $triples; my @triples = $self->feature->get_tag_values('triples'); for my $t (@triples) { next if ref $t && ref $t eq 'ARRAY'; # already in right format $t = [split /[,\w]/,$t]; } return \@triples; } sub draw_component { my $self = shift; my $gd = shift; my $fg = $self->fgcolor; my $bg = $self->bgcolor; # position the left (A) edge my ($x1,$y1,$x2,$y2) = $self->calculate_boundaries(@_); my $xmid = ($x1+$x2)/2; my $side = $self->calculate_side; my $left = $xmid - $side/2; my $right = $left + $side; my $top = $y1; my $bottom=$y2; # draw the triangle my $poly_pkg = $self->polygon_package; my $poly = $poly_pkg->new(); $poly->addPt($left,$bottom); $poly->addPt($right,$bottom); $poly->addPt($xmid,$top); $gd->polygon($poly,$fg); # draw vertex labels, if any my $fontcolor = $self->fontcolor; my $font = $self->image_class->gdTinyFont(); my $lh = $font->height; my $lw = $font->width; if (my $vertex_labels = $self->option('vertices')) { my @labels = @$vertex_labels; $gd->string($font,$left-$lw*length($labels[0]),$bottom+$lh/2,$labels[0],$fontcolor); $gd->string($font,$right,$bottom+$lh/2,$labels[1],$fontcolor); $gd->string($font,$xmid-$lw*length($labels[2])-3,$top-3,$labels[2],$fontcolor); } # get triples my $data = $self->triples; for my $triple (@$data) { my ($a,$b,$c,$color,$label) = @$triple; $color = defined $color ? $self->factory->translate_color($color) : $bg; my $x = $xmid + $side * ($b - $a)/2; my $y = $bottom - $side * ($c * Tan60)/2; draw_disc($gd,$x,$y,3,$color); if ($label) { $gd->string($font,$x+3,$y,$label,$fontcolor); } } } sub draw_disc { my ($gd,$x,$y,$pr,$color) = @_; $gd->filledArc($x,$y,$pr,$pr,0,360,$color); } 1; __END__ =head1 NAME Bio::Graphics::Glyph::ternary_plot - Draw ternary plot data =head1 SYNOPSIS #!/usr/bin/perl use strict; use warnings; use Bio::Graphics; use Bio::Graphics::Feature; my $segment = Bio::Graphics::Feature->new(-start=>1,-end=>700); my $snp1 = Bio::Graphics::Feature->new(-start => 500, -end => 501, -name => 'rs000001', -attributes=> {triples => [ [0.01, 0.81, 0.18, 'red', 'CEPH'], [0.25, 0.25, 0.50, 'blue', 'JPT+CHB'], [0.81, 0.01, 0.18, 'green','YRI'], ] } ); my $snp2 = Bio::Graphics::Feature->new(-start => 300, -end => 301, -name => 'rs12345', -attributes=> {triples => [ [0.04, 0.64, 0.32, 'red', 'Controls'], [0.16, 0.36, 0.48, 'blue', 'Cases'], ] } ); my $panel = Bio::Graphics::Panel->new(-segment=>$segment,-width=>800); $panel->add_track($segment,-glyph=>'arrow',-double=>1,-tick=>2); $panel->add_track([$snp1,$snp2], -glyph => 'ternary_plot', -height => 80, -fgcolor => 'lightgrey', -vertices => ['AA','GG','AG'], -label => 1, ); print $panel->png; =head1 DESCRIPTION This glyph draws a light gray equilateral triangle with its base centered on the feature. The top of the equilateral triangle is equal to the specified height. To look good, please choose a height of E= 15. Inside, the glyph will plot one or more data points using ternary plot conventions (see http://en.wikipedia.org/wiki/Ternary_plot). The data consists of a series of (A,B,C) triplets chosen such that the range of each component is [0.0,1.0] and A + B + C = 1.0. The left, right and apex of the triangle represent the proportions of A, B and C respectively. As a component approaches 1.0, it gets closer to its corresponding vertex. The data can be represented as one or more feature tags called "triples" each in the format: A1,B1,C1,,

Configuration Section

If not provided, this page generates a reasonable default configuration section for you, so you do not need to provide a configuration section to get a reasonable image. However, to tune the appearance of the image, you will probably want to tweak the configuration. Here is an excerpt from the configuration section:

# example file
[general]
bases = -1000..21000
height = 12

[EST]
glyph = segments
bgcolor= yellow
connector = solid
height = 5

[FGENES]
glyph = transcript2
bgcolor = green
description = 1

The configuration section is divided into a set of sections, each one labeled with a [section title]. The [general] section specifies global options for the entire image. Other sections apply to particular feature types. In the example above, the configuration in the [EST] section applies to features labeled as ESTs, while the configuration in the [FGENES] section applies to features labeled as predictions from the FGENES gene prediction program.

Inside each section is a series of name=value pairs, where the name is the name of an option to set. You can put whitespace around the = sign to make it more readable, or even use a colon (:) if you prefer. The following option names are recognized:

OptionValueExample
basesMin & max of the sequence range (bp)1200..60000
widthwidth of the image (pixels) 600
heightHeight of each graphical element (pixels)10
glyphStyle of each graphical element (see below)transcript
fgcolor Foreground color of each element yellow
bgcolor Background color of each element blue
linewidth Width of lines 3
label Print the feature's name 1
description Whether to print the feature's description 0
bump Elements are not allowed to collide 1
ticks Print tick marks on arrows 1
connector Type of group connector (dashed, hat or solid) dashed

The "bases" and "width" options are only relevant in the [general] section. The rest can be located in any section, but if present in the [general] section will set defaults for the others.

Colors are English-language color names or Web-style #RRGGBB colors (see a book on HTML for an explanation). True/false values are 1 for true, and 0 for false. Numeric ranges can be expressed in start..end fashion with two dots, or as start-end with a hyphen.

The "glyph" option controls how the features are rendered. The following glyphs are implemented:

NameDescription
box A filled rectangle, nondirectional.
ellipseAn oval.
arrow An arrow; can be unidirectional or bidirectional. It is also capable of displaying a scale with major and minor tickmarks, and can be oriented horizontally or vertically.
segments A set of filled rectangles connected by solid lines. Used for interrupted features, such as gapped alignments.
transcript Similar to segments, but the connecting line is a "hat" shape, and the direction of transcription is indicated by a small arrow.
transcript2 Similar to transcript, but the direction of transcription is indicated by a terminal segment in the shape of an arrow.
primers Two inward pointing arrows connected by a line. Used for STSs.

The bump option is the most important option for controlling the look of the image. If set to false (the number 0), then the features are allowed to overlap. If set to true (the number 1), then the features will move vertically to avoid colliding. If not specified, bump is turned on if the number of any given type of sequence feature is greater than ${\BUMP_THRESHOLD}.

Data Section

The data section can follow or proceed the configuration section. The two sections can also be intermixed. The data section is a tab or whitespace-delimited file which you can export from a spreadsheet application or word processor file (be sure to save as text only!)

Here is an example data section:

Cosmid	   B0511	.	516-619
Cosmid	   B0511	.	3185-3294
Cosmid	   B0511	.	10946-11208
Cosmid	   B0511	.	13126-13511
Cosmid	   B0511	.	66-208
Cosmid	   B0511	.	6354-6499
Cosmid	   B0511	.	13955-14115
EST	   yk595e6.5	+	3187-3294
EST	   yk846e07.3	-	11015-11208
EST	   yk53c10
	   yk53c10.5	+	18892-19154
	   yk53c10.3	-	15000-15500,15700-15800
EST	   yk53c10.5	+	16032-16105
SwissProt  PECANEX	+	13153-13656	Swedish fish
FGENESH	   "Gene 1"	-	1-205,518-616,661-735,3187-3365,3436-3846	Transmembrane domain
FGENESH	   "Gene 2"	-	16626-17396,17451-17597	Kinase and sushi domains

Each line of the file contains five columns. The columns are:

Column #Column Description
1feature type
2feature name
3strand
4coordinates
5description

The feature type should correspond to one of the [feature type] headings in the configuration section. If it doesn't, the [general] options will be applied to the feature when rendering it. The feature name is a name for the feature. Use a "." or "-" if this is not relevant. If the name contains whitespace, put single or double quotes ("") around the name.

The strand indicates which strand the feature is on. It is one of "+" for the forward strand, "-" for the reverse strand, or "." for features that are not stranded.

The coordinates column is a set of one or more ranges that the feature occupies. Ranges are written using ".." as in start..stop, or with hyphens, as in start-stop. For features that are composed of multiple ranges &em; for example transcripts that have multiple exons &em; you can either put the ranges on the same line separated by commas or spaces, or put the ranges on individual lines and just use the same feature name and type to group them. In the example above, the Cosmid B0511 features use the individual line style, while the FGENESH features use the all-ranges-on-one-line style.

The last column contains some descriptive text. If the description option is set to true, this text will be printed underneath the feature in the rendering.

Finally, it is possible to group related features together. An example is the ESTs yk53c10.5 and yk53c10.3, which are related by being reads from the two ends of the clone yk53c10. To indicate this relationship, generate a section that looks like this:

EST	   yk53c10
	   yk53c10.5	+	18892-19154
	   yk53c10.3	-	15000-15500,15700-15800

The group is indicated by a line that contains just two columns containing the feature type and a unique name for the group. Follow this line with all the features that form the group, but leave the first column (the feature type) blank. The group will be rendered by drawing a dashed line between all the members of the group. You can change this by specifying a different connector option in the configuration section for this feature type. END ; } sub test_data { my $config = shift; my $header = <<'END'; [general] bases = -1000..21000 height = 12 reference = B0511 [Cosmid] glyph = segments fgcolor = blue key = C. elegans conserved regions [EST] glyph = segments bgcolor= yellow connector = solid height = 5 [FGENESH] glyph = transcript2 bgcolor = green description = 1 [SwissProt] glyph = arrow base = 1 linewidth = 2 fgcolor = red description = 1 [P-element] glyph = triangle orient = S bgcolor = red fgcolor = white label = 1 point = 1 END ; my $data =<<'END'; Cosmid B0511 516-619 Cosmid B0511 3185-3294 Cosmid B0511 10946-11208 Cosmid B0511 13126-13511 Cosmid B0511 11394-11539 Cosmid B0511 14383-14490 Cosmid B0511 15569-15755 Cosmid B0511 18879-19178 Cosmid B0511 15850-16110 Cosmid B0511 66-208 Cosmid B0511 6354-6499 Cosmid B0511 13955-14115 Cosmid B0511 7985-8042 Cosmid B0511 11916-12046 P-element "" 500-500 P-element MrQ 700-700 P-element MrR 10000-10000 EST yk260e10.5 15569-15724 EST yk672a12.5 537-618,3187-3294 EST yk595e6.5 552-618 EST yk595e6.5 3187-3294 EST yk846e07.3 11015-11208 EST yk53c10 yk53c10.3 12876-13577,13882-14121,14169-14535 yk53c10.5 18892-19154,15853-16219 SwissProt "PECANEX Protein" 5513-16656 "From SwissProt" FGENESH "Predicted gene 1" -1200--500,518-616,661-735,3187-3365,3436-3846 Pfam domain FGENESH "Predicted gene 2" 5513-6497,7968-8136,8278-8383,8651-8839,9462-9515,10032-10705,10949-11340,11387-11524,11765-12067,12876-13577,13882-14121,14169-14535,15006-15209,15259-15462,15513-15753,15853-16219 Mysterious FGENESH "Predicted gene 3" 16626-17396,17451-17597 FGENESH "Predicted gene 4" 18459-18722,18882-19176,19221-19513,19572-30000 "Transmembrane protein" END return $config ? $header . $data : $data; } __END__ Bio-Graphics-2.37/scripts/index_cov_files.pl000444001750001750 3365712165075746 21267 0ustar00lsteinlstein000000000000#!/usr/bin/env perl # # index_cov_files.pl # # [ to see proper formatting set tab==2 ] # # 2009-2010 Victor Strelets, FlyBase.org ##$testing= 300000; #$debug= 1; $do_only_chr= '2L'; #$debug= 1; $do_only_chr= '2L'; $do_only_subset= 'BS40_all_unique'; #$debug= 1; $do_only_subset= 'Female_Heads'; $do_only_chr= '2'; $apply_log= 0; $log2= log(2); %LogTabs= ( 0, 0, 1, 0 ); # for our purposes, these start values are right $log_magnifier= 1.0; if( @ARGV && $ARGV[0]=~/^\-?log/i ) { $apply_log= 1; print "applying log\n"; } print "\nIndexing COV files for use with the fb_shmiggle glyph\n"; eval {use lib("/LS/common/system-local/perl/lib"); }; use BerkeleyDB; my $filesmask= '*.cov*'; $filemask= shift(@ARGV) if @ARGV; $filemask=~s/\./\\\./g; $filemask=~s/[\*]/\\*/g; indexfeatdir('./',$filesmask) ; exit(); #************************************************************************* # #************************************************************************* sub indexfeatdir { my($dir,$mask)= @_; local(*D); opendir(D, $dir) || warn "can't open $dir"; my @files= grep( /\.(cov|wig)/i, readdir(D)); closedir(D); my $datfilename= 'data.cat'; system("rm $datfilename") if -e $datfilename; open(OUTDATF,'>'.$datfilename) || die "Cannot open $datfilename!"; my $bdbfilename= 'index.bdbhash'; unlink($bdbfilename) if( -e $bdbfilename ); %ResIndexHash= (); # !!GLOBAL tie %ResIndexHash, "BerkeleyDB::Hash", -Filename => $bdbfilename, -Flags => DB_CREATE; $max_signal = 0; @SubsetNames= (); foreach my $file (sort @files) { indexCoverageFile($file); # coverage files are in fact wiggle files.. } $ResIndexHash{'subsets'}= join("\t",@SubsetNames); # record subsets, just in case.. $ResIndexHash{'max_signal'}= $max_signal; my @all_keys= keys %ResIndexHash; foreach my $kkey ( sort @all_keys ) { print "\t$kkey => ".$ResIndexHash{$kkey}."\n"; } if( $max_signal>10000 ) { print "WARNING: max_signal=$max_signal - TOO HIGH!! Re-run with '-log' option\n"; } untie %ResIndexHash; chmod(0666,$bdbfilename); # ! sometimes very important close(OUTDATF); return; } #************************************************************************* # #************************************************************************* sub indexCoverageFile { my($file)= @_; if( $debug && defined $do_only_subset ) { return unless $file=~/^${do_only_subset}\./; } my $zcat= get_zcat($file); local(*INF); open(INF,"$zcat $file |") || die "Can't open $file"; print "\t$file\n"; my $SubsetName= ($file=~/^([^\.]+)\./) ? $1 : $file; push(@SubsetNames,$SubsetName); my $chromosome= ""; my @offsets= (); # following setting is very important for performance (in some cases) # value 1000 (otherwise good) on K.White dataset was causing start of reading 100K before the actually required point.. my $step= 1000; # step in coverage file lines ()signal reads to save start-offset my $coordstep= 20000; # step in coords to save start-offset my $counter= 0; my $offset= tell(OUTDATF); $ResIndexHash{$SubsetName}= $offset; # record offset where new subset data starts my $old_signal= 0; my $oldcoord= -200000; my $FileFormat= 1; my $StartCoord= 0; my $lastRecordedCoord= -200000; while( (my $str= ) ) { $offset= tell(OUTDATF); # correct variant of GEO preferred subset spec if( $str=~m/^(track[ \t]+type=wiggle_0)\s*\n$/i ) { # new subset starting $str= $1 . ' name="' . $SubsetName ."\"\n"; } # following is a GEO preferred subset spec if( $str=~m/^track[ \t]+type=wiggle_0[ \t]+name="([^"]+)"/i ) { # new subset starting $FileFormat= 4; $SubsetName= $1; #$chromosome= ""; next; # because it is not a signal, should not be printed in this data loop } # fix for K.White files elsif( $str=~m/^track[ \t]+type=bedGraph[ \t]+name="([^"]+)"/i ) { # new subset starting $FileFormat= 4; $SubsetName=~s/_(combined|coverage)$//i; $SubsetName=~s/_(combined|coverage)$//i; $SubsetName=~s/^G[A-Z]{2}\d+[_\-]//; #$chromosome= ""; next; # because it is not a signal, should not be printed in this data loop } elsif( $str=~m/^variableStep[ \t]+(chr(om(osome)?)?|arm)=(\w+)/i ) { # potentially new arm starting $FileFormat= 4; my $new_chromosome= $4; $new_chromosome=~s/^chr(omosome)?//i; if( $new_chromosome ne $chromosome ) { dumpOffsets($SubsetName.':'.$chromosome,@offsets) unless $chromosome eq ""; # previous subset:arm $chromosome= $new_chromosome; print OUTDATF "# subset=$SubsetName chromosome=$chromosome\n"; $offset= tell(OUTDATF); $ResIndexHash{$SubsetName.':'.$chromosome}= $offset; # record offset where new subset:arm data starts @offsets= ("-200000\t$offset"); print OUTDATF "-200000\t0\n"; # insert one fictive zero read $offset= tell(OUTDATF); print OUTDATF "0\t0\n"; # insert one more fictive zero read push(@offsets,"0\t$offset"); print "\t\t$SubsetName:$chromosome\n"; $counter= 0; $old_signal= 0; $oldcoord= 0; $lastRecordedCoord= 0; } next; # because it is not a signal, should not be printed in this data loop } elsif( $str=~m/^FixedStep[ \t]+(chr(om(osome)?)?|arm)=(\w+)[ \t]+Start=(\d+)/i ) { # potentially new arm starting $FileFormat= 3; my $new_chromosome= $4; $StartCoord= $5; $new_chromosome=~s/^chr(omosome)?//i; if( $new_chromosome ne $chromosome ) { dumpOffsets($SubsetName.':'.$chromosome,@offsets) unless $chromosome eq ""; # previous subset:arm $chromosome= $new_chromosome; print OUTDATF "# subset=$SubsetName chromosome=$chromosome\n"; $offset= tell(OUTDATF); $ResIndexHash{$SubsetName.':'.$chromosome}= $offset; # record offset where new subset:arm data starts @offsets= ("-200000\t$offset"); print OUTDATF "-200000\t0\n"; # insert one fictive zero read $offset= tell(OUTDATF); print OUTDATF "0\t0\n"; # insert one more fictive zero read push(@offsets,"0\t$offset"); print "\t\t$SubsetName:$chromosome\n"; $counter= 0; $old_signal= 0; $oldcoord= 0; $lastRecordedCoord= 0; } elsif( $StartCoord>$oldcoord+1 ) { # hole, fill with zeros $oldcoord++; #print " hole (zeros) from $oldcoord to $StartCoord-1\n" if $debug; print OUTDATF $oldcoord."\t0\n" unless $old_signal==0; $old_signal= 0; next if $signal==0; # no need to duplicate zeros.. } elsif( $StartCoord<$oldcoord ) { print "WARNING: backward ref in $file: $str"; } next; # because it is not a signal, should not be printed in this data loop } elsif( $str=~m/^[#]?.*(chr(om(osome)?)?|arm)=(\w+)/ ) { # potentially new arm starting $FileFormat= 1; my $new_chromosome= $4; $new_chromosome=~s/^chr(omosome)?//i; if( $new_chromosome ne $chromosome ) { dumpOffsets($SubsetName.':'.$chromosome,@offsets) unless $chromosome eq ""; # previous subset:arm $chromosome= $new_chromosome; print OUTDATF "# subset=$SubsetName chromosome=$chromosome\n"; $offset= tell(OUTDATF); $ResIndexHash{$SubsetName.':'.$chromosome}= $offset; # record offset where new subset:arm data starts @offsets= ("-200000\t$offset"); print OUTDATF "-200000\t0\n"; # insert one fictive zero read $offset= tell(OUTDATF); print OUTDATF "0\t0\n"; # insert one more fictive zero read push(@offsets,"0\t$offset"); print "\t\t$SubsetName:$chromosome\n"; $counter= 0; $old_signal= 0; $oldcoord= 0; $lastRecordedCoord= 0; } next; # because it is not a signal, should not be printed in this data loop } elsif( $str=~m/^[#]/ ) { next; } # other unspecified comments elsif( $str=~m/^(\d+)[ \t]+(\d+)\s*\n/ ) { # [coord signal] format $FileFormat= 1; my($coord,$signal)= ($1,$2); $signal= modifySignal($signal); if( $signal==$old_signal ) { $oldcoord= $coord; next; } $max_signal= $signal if $max_signal<$signal; if( $counter++>$step || $coord-$lastRecordedCoord>$coordstep ) { push(@offsets,"$coord\t$offset"); $counter= 0; $lastRecordedCoord= $coord; } $str= $coord."\t".$signal."\n"; $oldcoord= $coord; $old_signal= $signal; } # following is a GEO preferred format elsif( $str=~m/^(\w+)[ \t]+(\d+)[ \t]+(\d+)[ \t]+[\-]?(\d+)\s*\n/ ) { # [chr coord tocoord signal] format, all positions and skipped zeros $FileFormat= 4; my($new_chromosome,$coord,$tocoord,$signal)= ($1,$2,$3,$4); my $samesignal_l= $tocoord-$coord; $new_chromosome=~s/^chr(omosome)?//i; if( $debug && defined $do_only_chr ) { next unless $new_chromosome eq $do_only_chr; } $signal= modifySignal($signal); if( $new_chromosome ne $chromosome ) { dumpOffsets($SubsetName.':'.$chromosome,@offsets) unless $chromosome eq ""; # previous subset:arm $chromosome= $new_chromosome; print OUTDATF "# subset=$SubsetName chromosome=$chromosome\n"; $offset= tell(OUTDATF); $ResIndexHash{$SubsetName.':'.$chromosome}= $offset; # record offset where new subset:arm data starts @offsets= ("-200000\t$offset"); print OUTDATF "-200000\t0\n"; # insert one fictive zero read $offset= tell(OUTDATF); print OUTDATF "0\t0\n"; # insert one more fictive zero read push(@offsets,"0\t$offset"); print "\t\t$SubsetName:$chromosome\n"; $counter= 0; $old_signal= 0; $oldcoord= 0; $lastRecordedCoord= 0; } if( $coord>$oldcoord+1 ) { # hole, fill with zeros $oldcoord++; #print " hole (zeros) from $oldcoord to $coord-1\n" if $debug; print OUTDATF $oldcoord."\t0\n" unless $old_signal==0; $old_signal= 0; next if $signal==0; # no need to duplicate zeros.. } elsif( $signal==$old_signal ) { $oldcoord= $coord; next; } $max_signal= $signal if $max_signal<$signal; if( $counter++>$step || $coord-$lastRecordedCoord>$coordstep ) { push(@offsets,"$coord\t$offset"); $counter= 0; $lastRecordedCoord= $coord; } $str= $coord."\t".$signal."\n"; $oldcoord= $coord+$samesignal_l-1; $old_signal= $signal; } elsif( $str=~m/^(\w+)[ \t]+(\d+)[ \t]+(\d+)\s*\n/ ) { # [chr coord signal] format, all positions but skipped zeros $FileFormat= 2; my($new_chromosome,$coord,$signal)= ($1,$2,$3); $new_chromosome=~s/^chr(omosome)?//i; if( $debug && defined $do_only_chr ) { next unless $new_chromosome eq $do_only_chr; } $signal= modifySignal($signal); if( $new_chromosome ne $chromosome ) { dumpOffsets($SubsetName.':'.$chromosome,@offsets) unless $chromosome eq ""; # previous subset:arm $chromosome= $new_chromosome; print OUTDATF "# subset=$SubsetName chromosome=$chromosome\n"; $offset= tell(OUTDATF); $ResIndexHash{$SubsetName.':'.$chromosome}= $offset; # record offset where new subset:arm data starts @offsets= ("-200000\t$offset"); print OUTDATF "-200000\t0\n"; # insert one fictive zero read $offset= tell(OUTDATF); print OUTDATF "0\t0\n"; # insert one more fictive zero read push(@offsets,"0\t$offset"); print "\t\t$SubsetName:$chromosome\n"; $counter= 0; $old_signal= 0; $oldcoord= 0; $lastRecordedCoord= 0; } if( $coord>$oldcoord+1 ) { # hole, fill with zeros $oldcoord++; #print " hole (zeros) from $oldcoord to $coord-1\n" if $debug; print OUTDATF $oldcoord."\t0\n" unless $old_signal==0; $old_signal= 0; next if $signal==0; # no need to duplicate zeros.. } elsif( $signal==$old_signal ) { $oldcoord= $coord; next; } $max_signal= $signal if $max_signal<$signal; if( $counter++>$step || $coord-$lastRecordedCoord>$coordstep ) { push(@offsets,"$coord\t$offset"); $counter= 0; $lastRecordedCoord= $coord; } $str= $coord."\t".$signal."\n"; $oldcoord= $coord; $old_signal= $signal; } elsif( $str=~m/^(\d+)\s*\n/ ) { # [signal] format, all positions and skipped zeros $FileFormat= 3; my($coord,$signal)= ($StartCoord++,$1); $signal= modifySignal($signal); if( $signal==$old_signal ) { $oldcoord= $coord; next; } $max_signal= $signal if $max_signal<$signal; if( $counter++>$step || $coord-$lastRecordedCoord>$coordstep ) { push(@offsets,"$coord\t$offset"); $counter= 0; $lastRecordedCoord= $coord; } $str= $coord."\t".$signal."\n"; $oldcoord= $coord; $old_signal= $signal; } else { next; } # skip other data - unknown format print OUTDATF $str; } # don't forget to dump offsets data on file end.. dumpOffsets($SubsetName.':'.$chromosome,@offsets) unless $chromosome eq ""; # previous subset:arm close(INF); return; } #************************************************************************* # #************************************************************************* sub modifySignal { my $signal= shift; return($signal) unless $apply_log; if( exists $LogTabs{$signal} ) { $signal= $LogTabs{$signal}; } else { my $newval= int(log($signal)*$log_magnifier/$log2); # make it larger (magnification) $LogTabs{$signal}= $newval; $signal= $newval; } return($signal); } #************************************************************************* # #************************************************************************* sub dumpOffsets { my($key,@offsetlines)= @_; print OUTDATF "# offsets for $key\n"; my $offset= tell(OUTDATF); my $prevoffset= $offset; $ResIndexHash{$key.':offsets'}= $offset; # record offset where offsets VALUES for subset:arm data start (skip header) my $oldbigstep= 0; foreach my $str ( @offsetlines ) { print OUTDATF $str . "\n"; my($coord,$floffset)= split(/[ \t]+/,$str); # following wasn't working properly.. my $newbigstep= int($coord/1000000.0); if( $newbigstep>$oldbigstep ) { $ResIndexHash{$key.':offsets:'.$newbigstep}= $prevoffset; # one before is the right start $oldbigstep= $newbigstep; } $prevoffset= $offset; $offset= tell(OUTDATF); } return; } #*********************************************************** # #*********************************************************** sub get_zcat { my $fullfile= shift; if( $fullfile=~/\.gz$/i ) { my $zcat= `which zcat`; if( $? != 0 ) { $zcat=`which gzcat`; } chomp($zcat); return($zcat); } elsif( $fullfile=~/\.bz2$/i ) { return('bzcat'); } return('/bin/cat'); } #*********************************************************** # #*********************************************************** Bio-Graphics-2.37/scripts/search_overview.pl000555001750001750 1024512165075746 21311 0ustar00lsteinlstein000000000000#!/usr/bin/perl -w =head1 NAME search_overview -- Render a SearchIO parser report into a simple overview graphic =head1 SYNOPSIS search_overview -i filename [-f format] [-o outputfilename] [--labels] =head1 DESCRIPTION This script will take any Bio::SearchIO parseable report and turn it into a simple overview graphic of the report. For our purposes we are assuming BLAST and the BLAST scores when assigning colors. Output is a PNG format file. This is not intended to be an overly customized script, rather it should probably just be either a quick and dirty look at a report or a starting point for more complicated implementations. The color is determined by the hit score which is currently pegged to the NCBI scheme which looks like this RED E= 200 PURPLE 80-200 GREEN 50-80 BLUE 40-50 BLACK E40 Options: -i/--input The input filename, otherwise input is assumed from STDIN -o/--output The output filename, this is optional, if you do not provide the output filename the script will create a file using the name of the query sequence and will process all the sequences in the file. If an output filename IS provided the script will only display an image for the first one. -f/--format The SearchIO format parser to use, if not provided SearchIO will guess based on the file extension. -l/--labels Display the hit sequence name as a label in the overview. For lots of sequences this will make the image very long so by default it is turned off. =head1 AUTHOR Jason Stajich Jason Stajich, jason[-at-]open-bio[-dot-]org. =cut use strict; use Bio::Graphics::Panel; use Bio::Graphics::Feature; use Bio::Graphics::FeatureFile; use Bio::SearchIO; use Getopt::Long; use constant WIDTH => 600; # default width my ($in,$format,$out); my $showlabels = 0; # This defines the color order # For NCBI it is typically defined like this # Score # RED >= 200 # PURPLE 80-200 # GREEN 50-80 # BLUE 40-50 # BLACK <40 my @COLORS = qw(red magenta green blue black); my @SCORES = (200,80,50,40,0); GetOptions( 'i|in|input:s' => \$in, 'f|format:s' => \$format, 'o|output:s' => \$out, 'l|labels' => \$showlabels ); my $parser = new Bio::SearchIO(-file => $in, -format => $format); while( my $r = $parser->next_result ) { my ($qname,$qlen) = ($r->query_name, $r->query_length); my $max = 0; my (@features,@configs); while(my $h = $r->next_hit ) { next if $h->num_hsps == 0; my ($left,$right) = ( $h->start('query'), $h->end ('query') ); if( ! $qlen ) { $max = MAX($max,abs($right-$left)); } my $bin = 0; my $score = $h->score; for my $s ( @SCORES ) { last if( $score > $s); $bin++; } push @features, Bio::Graphics::Feature->new(-start => $left, -stop => $right, -type => 'similarity', -name => $h->name, -desc => $h->description ); push @configs, [ ( -glyph => 'segments', -bgcolor => $COLORS[$bin], -fgcolor => $COLORS[$bin], -label => $showlabels, -height => 1, )]; } my $panel = Bio::Graphics::Panel->new(-length => $qlen || $max, -bgcolor => 'white', -pad_left=> 10, -pad_right=> 10); $panel->add_track('arrow' => Bio::Graphics::Feature->new (-start => 1, -end => $qlen || $max), -bump => 0, -double => 1, -tick => 2, ); foreach my $f ( @features ) { my $c = shift @configs; $panel->add_track($f, @$c); } if( $out ) { open(OUT,">$out") || die("cannot open $out: $!"); binmode(OUT); print OUT $panel->png; close(OUT); if( $parser->result_count > 1 ) { print STDERR "only printing the first result, do not provide a outfile name if you want to see them all\n"; } last; } else { open(OUT, ">$qname.png") || die("$qname: $!"); binmode(OUT); print OUT $panel->png; close(OUT); } } sub MAX {return $_[0] < $_[1] ? $_[1] : $_[0] } sub MIN {return $_[0] > $_[1] ? $_[1] : $_[0] } Bio-Graphics-2.37/scripts/feature_draw.pl000555001750001750 3001012165075746 20556 0ustar00lsteinlstein000000000000#!/usr/bin/perl -w use strict; use lib './blib/lib','../blib/lib'; use Bio::Graphics::Panel; use Bio::Graphics::Feature; use Bio::Graphics::FeatureFile; use Getopt::Long; use constant WIDTH => 600; my ($WIDTH,$RANGE,$BOXES); GetOptions ('width:i' => \$WIDTH, 'range:s' => \$RANGE, 'boxes' => \$BOXES, ) || die < Set width of image (${\WIDTH} pixels default) --range Set range of region (base pairs, start-stop) --boxes Draw grey boxes around the features (for debugging) Render a Bio::Graphics feature file and produce a PNG image. See the manual page for Bio::Graphics::FeatureFile for a description of the file format. USAGE my @COLORS = qw(cyan blue red yellow green wheat turquoise orange); # default colors my $color = 0; # position in color cycle my $data = Bio::Graphics::FeatureFile->new(-file => '-'); # general configuration of the image here my $width = $WIDTH || $data->setting(general => 'pixels') || $data->setting(general => 'width') || WIDTH; my ($start,$stop); my $range_expr = '(-?\d+)(?:-|\.\.)(-?\d+)'; if (defined $RANGE) { ($start,$stop) = $RANGE =~ /$range_expr/o or die "$RANGE: invalid range specification"; } elsif (my $bases = $data->setting(general => 'bases')) { ($start,$stop) = $bases =~ /([\d-]+)(?:-|\.\.)([\d-]+)/; } if (!defined $start || !defined $stop) { $start = $data->min unless defined $start; $stop = $data->max unless defined $stop; } # Use the order of the stylesheet to determine features. Whatever is left # over is presented in alphabetic order my %types = map {$_=>1} $data->configured_types; my @configured_types = grep {exists $data->features->{$_}} $data->configured_types; my @unconfigured_types = sort grep {!exists $types{$_}} $data->types; # create the segment,the panel and the arrow with tickmarks my $segment = Bio::Graphics::Feature->new(-start=>$start,-stop=>$stop); my $panel = Bio::Graphics::Panel->new(-segment => $segment, -width => $width, -key_style => 'between'); $panel->add_track($segment,-glyph=>'arrow',-tick=>2); my @base_config = $data->style('general'); for my $type (@configured_types,@unconfigured_types) { my @config = ( -glyph => 'segments', # really generic -bgcolor => $COLORS[$color++ % @COLORS], -label => 1, -key => $type, @base_config, # global $data->style($type), # feature-specificp ); my $features = $data->features($type); $panel->add_track($features,@config); } my $gd = $panel->gd; if ($BOXES) { # debugging code my $boxes = $panel->boxes; debugging_rectangles($gd,$boxes); } print $gd->can('png') ? $gd->png : $gd->gif; sub debugging_rectangles { my ($image,$boxes) = @_; my $grey = $image->colorClosest(100,100,100); foreach (@$boxes) { my @rect = @{$_}[1,2,3,4]; $image->rectangle(@{$_}[1,2,3,4],$grey); } } =head1 NAME feature_draw.pl -- Render a Bio::Graphics Feature File =head1 SYNOPSIS feature_draw.pl [options] file.txt [file2.txt...] > rendering.png feature_draw.pl [options] file.txt [file2.txt...] | display - =head1 DESCRIPTION The feature_draw.pl script is a thin front end around the Bio::Graphics module. It accepts a list of files containing sequence (protein, nucleotide) feature coordinates from the file(s) listed on the command line or on standard input, renders them, and produces a PNG file on standard output. =head2 Options This script uses GNU-style long options. This allows you to specify the image width option, for example, with any of the following alternative forms: --width=800 --width 800 -width 800 -w 800 =over 4 =item --width This sets the width of the image, in pixels. The default is 800 pixels. =item --range This sets the range of the region displayed, in base pairs from start to stop. Any of the following formats are accepted: --range 1..1000 --range 1,1000 --range 1-1000 Negative ranges are allowed. =back =head1 Feature Files Format This script accepts and processes sequence annotations in a simple tab-delimited format or in GFF format. The feature file format has a configuration section and a data section. The configuration section sets up the size and overall properties of the image, and the data section gives the feature data itself. =head2 Configuration Section If not provided, this scripts generates a reasonable default configuration section for you, so you do not need to provide a configuration section to get a reasonable image. However, to tune the appearance of the image, you will probably want to tweak the configuration. Here is an excerpt from the configuration section: # example file [general] bases = -1000..21000 height = 12 [EST] glyph = segments bgcolor= yellow connector = dashed height = 5 [FGENES] glyph = transcript2 bgcolor = green description = 1 The configuration section is divided into a set of sections, each one labeled with a [section title]. The [general] section specifies global options for the entire image. Other sections apply to particular feature types. In the example above, the configuration in the [EST] section applies to features labeled as ESTs, while the configuration in the [FGENES] section applies to features labeled as predictions from the FGENES gene prediction program. Inside each section is a series of name=value pairs, where the name is the name of an option to set. You can put whitespace around the = sign to make it more readable, or even use a colon (:) if you prefer. The following option names are recognized: Option Value Example ------ ----- ------- bases Min & max of the sequence range (bp) 1200..60000 width width of the image (pixels) 600 height Height of each graphical element (pixels) 10 glyph Style of each graphical element (see below) transcript fgcolor Foreground color of each element yellow bgcolor Background color of each element blue linewidth Width of lines 3 label Print the feature's name 1 description Whether to print the feature's description 0 bump Elements are not allowed to collide 1 ticks Print tick marks on arrows 1 connector Type of group connector (dashed, hat or solid) dashed The "bases" and "width" options are only relevant in the [general] section. They are overridden by the like-named command-line options. The remainder of the options can be located in any section, but if present in the [general] section will set defaults for the others. Colors are English-language color names or Web-style #RRGGBB colors (see a book on HTML for an explanation). True/false values are 1 for true, and 0 for false. Numeric ranges can be expressed in start..end fashion with two dots, or as start-end with a hyphen. The "glyph" option controls how the features are rendered. The following glyphs are implemented: Name Description ---- ----------- box A filled rectangle, nondirectional. ellipse An oval. arrow An arrow; can be unidirectional or bidirectional. It is also capable of displaying a scale with major and minor tickmarks, and can be oriented horizontally or vertically. segments A set of filled rectangles connected by solid lines. Used for interrupted features, such as gapped alignments and exon groups. transcript Similar to segments, but the connecting line is a "hat" shape, and the direction of transcription is indicated by a small arrow. transcript2 Similar to transcript, but the direction of transcription is indicated by a terminal segment in the shape of an arrow. primers Two inward pointing arrows connected by a line. Used for STSs. The bump option is the most important option for controlling the look of the image. If set to false (the number 0), then the features are allowed to overlap. If set to true (the number 1), then the features will move vertically to avoid colliding. If not specified, bump is turned on if the number of any given type of sequence feature is greater than 50. =head2 Data Section The data section can follow or proceed the configuration section. The two sections can also be intermixed. The data section is a tab or whitespace-delimited file which you can export from a spreadsheet application or word processor file (be sure to save as text only!) Here is an example data section: Cosmid B0511 . 516-619 Cosmid B0511 . 3185-3294 Cosmid B0511 . 10946-11208 Cosmid B0511 . 13126-13511 Cosmid B0511 . 66-208 Cosmid B0511 . 6354-6499 Cosmid B0511 . 13955-14115 EST yk595e6.5 + 3187-3294 EST yk846e07.3 - 11015-11208 EST yk53c10 yk53c10.5 + 18892-19154 yk53c10.3 - 15000-15500,15700-15800 EST yk53c10.5 + 16032-16105 SwissProt PECANEX + 13153-13656 Swedish fish FGENESH "Gene 1" - 1-205,518-616,661-735,3187-3365,3436-3846 Transmembrane domain FGENESH "Gene 2" - 16626-17396,17451-17597 Kinase and sushi domains Each line of the file contains five columns. The columns are: Column # Description -------- ----------- 1 feature type 2 feature name 3 strand 4 coordinates 5 description =over 4 =item Feature type The feature type should correspond to one of the [feature type] headings in the configuration section. If it doesn't, the [general] options will be applied to the feature when rendering it. The feature name is a name for the feature. Use a "." or "-" if this is not relevant. If the name contains whitespace, put single or double quotes ("") around the name. =item Strand The strand indicates which strand the feature is on. It is one of "+" for the forward strand, "-" for the reverse strand, or "." for features that are not stranded. =item Coordinates The coordinates column is a set of one or more ranges that the feature occupies. Ranges are written using ".." as in start..stop, or with hyphens, as in start-stop. For features that are composed of multiple ranges &em; for example transcripts that have multiple exons &em; you can either put the ranges on the same line separated by commas or spaces, or put the ranges on individual lines and just use the same feature name and type to group them. In the example above, the Cosmid B0511 features use the individual line style, while the FGENESH features use the all-ranges-on-one-line style. =item Description The last column contains some descriptive text. If the description option is set to true, this text will be printed underneath the feature in the rendering. =back Finally, it is possible to group related features together. An example is the ESTs yk53c10.5 and yk53c10.3, which are related by being reads from the two ends of the clone yk53c10. To indicate this relationship, generate a section that looks like this: EST yk53c10 yk53c10.5 + 18892-19154 yk53c10.3 - 15000-15500,15700-15800 The group is indicated by a line that contains just two columns containing the feature type and a unique name for the group. Follow this line with all the features that form the group, but leave the first column (the feature type) blank. The group will be rendered by drawing a dashed line between all the members of the group. You can change this by specifying a different connector option in the configuration section for this feature type. =head1 BUGS Please report them to the author. =head1 SEE ALSO L =head1 AUTHOR Lincoln Stein, lstein@cshl.org =cut Bio-Graphics-2.37/eg000755001750001750 012165075746 14303 5ustar00lsteinlstein000000000000Bio-Graphics-2.37/eg/feature_data.gff000555001750001750 215612165075746 17557 0ustar00lsteinlstein000000000000# this is a regular GFF-format file [intron:curated] glyph = segments description = 1 bgcolor = green height = 5 key = Curated Introns [structural:GenePair_STS] glyph = primers fgcolor = black bgcolor = blue connect = 1 III curated Sequence 2729913 2752540 . + . Sequence "H19M22.2a" III curated Sequence 2729913 2752540 . + . Sequence "H19M22.2b" III Expr_profile Expression 2731006 2732545 . + . Expr_profile "H19M22.2" III GenePair_STS structural 2731006 2732545 . + . PCR_product "sjj_H19M22.2" III curated Sequence 2748310 2752540 . + . Sequence "H19M22.2c" III curated intron 2730004 2730635 . + . Sequence "H19M22.2b" ; Confirmed_by_EST III curated intron 2730004 2730635 . + . Sequence "H19M22.2a" ; Confirmed_by_EST III curated intron 2730705 2730846 . + . Sequence "H19M22.2a" ; Confirmed_by_EST III curated intron 2730705 2730846 . + . Sequence "H19M22.2b" ; Confirmed_by_EST III curated intron 2731102 2731151 . + . Sequence "H19M22.2a" ; Confirmed_by_EST III curated intron 2731102 2731151 . + . Sequence "H19M22.2b" ; Confirmed_by_EST III curated intron 2731541 2732220 . + . Sequence "H19M22.2b" ; Confirmed_by_cDNA Bio-Graphics-2.37/eg/testit.pl000555001750001750 1422512165075746 16340 0ustar00lsteinlstein000000000000#!/usr/bin/perl -w use lib '.','..','./blib/lib','../blib/lib'; use strict; use Bio::Graphics::Panel; use Bio::Graphics::Feature; my $ftr = 'Bio::Graphics::Feature'; my $segment = $ftr->new(-start=>-100,-end=>1000,-name=>'ZK154',-type=>'clone'); my $zk154_1 = $ftr->new(-start=>-50,-end=>800,-name=>'ZK154.1',-type=>'gene'); my $zk154_2 = $ftr->new(-start=>380,-end=>500,-name=>'ZK154.2',-type=>'gene'); my $zk154_3 = $ftr->new(-start=>900,-end=>1200,-name=>'ZK154.3',-type=>'gene'); my $zed_27 = $ftr->new(-segments=>[[400,500],[550,600],[800,950]], -name=>'zed-27', -subtype=>'exon',-type=>'transcript'); my $abc3 = $ftr->new(-segments=>[[100,200],[350,400],[500,550]], -name=>'abc53', -strand => -1, -subtype=>'exon',-type=>'transcript'); my $xyz4 = $ftr->new(-segments=>[[40,80],[100,120],[200,280],[300,320]], -name=>'xyz4', -subtype=>'predicted',-type=>'alignment'); my $m3 = $ftr->new(-segments=>[[20,40],[30,60],[90,270],[290,300]], -name=>'M3', -subtype=>'predicted',-type=>'alignment'); my $bigone = $ftr->new(-segments=>[[-200,-120],[90,270],[290,300]], -name=>'big one', -subtype=>'predicted',-type=>'alignment'); my $fred_12 = $ftr->new(-segments=>[$xyz4,$zed_27], -type => 'group', -name =>'fred-12'); my $confirmed_exon1 = $ftr->new(-start=>1,-stop=>20, -type=>'exon', -source=>'confirmed', -name => 'confirmed1', ); my $predicted_exon1 = $ftr->new(-start=>30,-stop=>50, -type=>'exon', -name=>'predicted1', -source=>'predicted'); my $predicted_exon2 = $ftr->new(-start=>60,-stop=>100, -name=>'predicted2', -type=>'exon',-source=>'predicted'); my $confirmed_exon3 = $ftr->new(-start=>150,-stop=>190, -type=>'exon',-source=>'confirmed', -name=>'abc123'); my $partial_gene = $ftr->new(-segments=>[$confirmed_exon1,$predicted_exon1,$predicted_exon2,$confirmed_exon3], -name => 'partial gene', -type => 'transcript', -source => '(from a big annotation pipeline)' ); my @segments = $partial_gene->segments; my $score = 10; foreach (@segments) { $_->score($score); $score += 10; } my $panel = Bio::Graphics::Panel->new( # -grid => [50,100,150,200,250,300,310,320,330], -gridcolor => 'lightcyan', -grid => 1, -segment => $segment, # -offset => 300, # -length => 1000, -spacing => 15, -width => 600, -pad_top => 20, -pad_bottom => 20, -pad_left => 20, -pad_right=> 20, # -bgcolor => 'teal', # -key_style => 'between', -key_style => 'bottom', ); my @colors = $panel->color_names(); my $t = $panel->add_track( # generic => [$abc3,$zed_27], transcript2 => [$abc3,$zed_27], -label => 1, -bump => 1, -key => 'Prophecies', # -tkcolor => $colors[rand @colors], ); $t->configure(-bump=>1); $panel->add_track($segment, -glyph => 'arrow', -label => 'base pairs', -double => 1, -bump => 0, -height => 10, -arrowstyle=>'regular', -linewidth=>1, # -tkcolor => $colors[rand @colors], -tick => 2, ); $panel->unshift_track(generic => [$segment,$zk154_1,$zk154_2,$zk154_3,[$xyz4,$zed_27]], -label => sub { my $feature = shift; $feature->sub_SeqFeature>0}, -bgcolor => sub { shift->primary_tag eq 'predicted' ? 'olive' : 'red'}, -connector => sub { my $feature = shift; my $type = $feature->primary_tag; $type eq 'group' ? 'dashed' : $type eq 'transcript' ? 'hat' : $type eq 'alignment' ? 'solid' : undef}, -all_callbacks => 1, -connector_color => 'black', -height => 10, -bump => 1, -linewidth=>2, # -tkcolor => $colors[rand @colors], -key => 'Signs', ); my $track = $panel->add_track('transcript2', -label => sub { $_[-1]->level == 0 } , -connector => sub { return shift->type eq 'group' ? 'dashed' : ''}, -point => 0, -orient => 'N', -height => 8, -base => 1, -relative_coords => 1, -tick => 2, -all_callbacks => 1, -bgcolor => 'red', -key => 'Dynamically Added'); $track->add_feature($bigone,$zed_27,$abc3); $track->add_group($predicted_exon1,$predicted_exon2,$confirmed_exon3); $panel->add_track( [$abc3,$zed_27,$partial_gene], -bgcolor => sub { shift->source_tag eq 'predicted' ? 'green' : 'blue'}, -glyph => 'transcript', # -glyph => sub { my $feature = shift; # return $feature->source_tag eq 'predicted' # ? 'ellipse' : 'transcript'}, -label => sub { shift->sub_SeqFeature > 0 }, # -label => 1, # -description => sub { shift->sub_SeqFeature > 0 }, -description => sub { my $feature = shift; return 1 if $feature->primary_tag eq 'transcript'; return '*' if $feature->source_tag eq 'predicted'; return; }, -font2color => 'red', -bump => +1, # -tkcolor => $colors[rand @colors], -key => 'Portents', ); $panel->add_track(segments => [$segment,$zk154_1,[$zk154_2,$xyz4]], -label => 1, -bgcolor => sub { shift->primary_tag eq 'predicted' ? 'green' : 'blue'}, -connector => sub { my $primary_tag = shift->primary_tag; $primary_tag eq 'transcript' ? 'hat' : $primary_tag eq 'alignment' ? 'solid' : undef}, -connector_color => 'black', -height => 10, -bump => 1, # -tkcolor => $colors[rand @colors], -key => 'Signals', ); $panel->add_track(generic => [], -key => 'Foobar'); $panel->add_track(graded_segments => $partial_gene, -bgcolor =>'blue', -label => 1, -key => 'Scored thing'); $panel->add_track(diamond => [$segment,$zk154_1,$zk154_2,$zk154_3,$xyz4,$zed_27], -bgcolor =>'blue', -label => 1, -key => 'pointy thing'); #print $panel->png; my $gd = $panel->gd; my @boxes = $panel->boxes; my $red = $panel->translate_color('red'); for my $box (@boxes) { my ($feature,@points) = @$box; # $gd->rectangle(@points,$red); } #$gd->filledRectangle(0,0,20,200,1); #$gd->filledRectangle(600-20,0,600,200,1); print $gd->png; Bio-Graphics-2.37/eg/feature_data.txt000555001750001750 310012165075746 17622 0ustar00lsteinlstein000000000000[general] pixels = 750 bases = -1000..21000 height = 12 reference = B0511 [Cosmid] glyph = segments fgcolor = blue key = C. elegans conserved regions [EST] glyph = segments bgcolor= yellow connector = solid height = 5 [FGENESH] glyph = transcript2 bgcolor = green description = 1 [SwissProt] glyph = arrow base = 1 linewidth = 2 fgcolor = red description = 1 [P-element] glyph = triangle orient = S bgcolor = red fgcolor = white label = 1 point = 1 Cosmid B0511 516-619 Cosmid B0511 3185-3294 Cosmid B0511 10946-11208 Cosmid B0511 13126-13511 Cosmid B0511 11394-11539 Cosmid B0511 14383-14490 Cosmid B0511 15569-15755 Cosmid B0511 18879-19178 Cosmid B0511 15850-16110 Cosmid B0511 66-208 Cosmid B0511 6354-6499 Cosmid B0511 13955-14115 Cosmid B0511 7985-8042 Cosmid B0511 11916-12046 P-element "" 500-500 P-element MrQ 700-700 P-element MrR 10000-10000 EST yk260e10.5 15569-15724 EST yk672a12.5 537-618,3187-3294 EST yk595e6.5 552-618 EST yk595e6.5 3187-3294 EST yk846e07.3 11015-11208 EST yk53c10 yk53c10.3 12876-13577,13882-14121,14169-14535 yk53c10.5 18892-19154,15853-16219 SwissProt "PECANEX Protein" 5513-16656 "From SwissProt" FGENESH "Predicted gene 1" -1200--500,518-616,661-735,3187-3365,3436-3846 Pfam domain FGENESH "Predicted gene 2" 5513-6497,7968-8136,8278-8383,8651-8839,9462-9515,10032-10705,10949-11340,11387-11524,11765-12067,12876-13577,13882-14121,14169-14535,15006-15209,15259-15462,15513-15753,15853-16219 Mysterious FGENESH "Predicted gene 3" 16626-17396,17451-17597 FGENESH "Predicted gene 4" 18459-18722,18882-19176,19221-19513,19572-30000 "Transmembrane protein" Bio-Graphics-2.37/eg/testit2.pl000555001750001750 501712165075746 16401 0ustar00lsteinlstein000000000000#!/usr/bin/perl -w use lib './lib','../lib','./blib/lib','../blib/lib'; use strict; use Bio::Graphics::Panel; use Bio::Graphics::Feature; my $ftr = 'Bio::Graphics::Feature'; my $segment = $ftr->new(-start=>1,-end=>1000,-name=>'ZK154',-type=>'clone'); my $zk154_1 = $ftr->new(-start=>-50,-end=>800,-name=>'ZK154.1',-type=>'gene'); my $zk154_2 = $ftr->new(-start=>380,-end=>500,-name=>'ZK154.2',-type=>'gene'); my $zed_27 = $ftr->new(-segments=>[[400,500],[550,600],[800,950]], -name=>'zed-27', -subtype=>'exon',-type=>'transcript'); my $abc3 = $ftr->new(-segments=>[[100,200],[350,400],[500,550]], -name=>'abc3', -strand => -1, -subtype=>'exon',-type=>'transcript'); my $xyz4 = $ftr->new(-segments=>[[40,80],[100,120],[200,280],[300,320]], -name=>'xyz4', -subtype=>'predicted',-type=>'alignment'); my $m3 = $ftr->new(-segments=>[[20,40],[30,60],[90,270],[290,300]], -name=>'M3', -subtype=>'predicted',-type=>'alignment'); my $fred_12 = $ftr->new(-segments=>[$xyz4,$zed_27], -type => 'group', -name =>'fred-12'); my $confirmed_exon1 = $ftr->new(-start=>1,-stop=>20, -type=>'exon',-source=>'confirmed'); my $predicted_exon1 = $ftr->new(-start=>30,-stop=>50, -type=>'exon',-source=>'predicted'); my $predicted_exon2 = $ftr->new(-start=>60,-stop=>100, -type=>'exon',-source=>'predicted'); my $confirmed_exon3 = $ftr->new(-start=>150,-stop=>190, -type=>'exon',-source=>'confirmed'); my $partial_gene = $ftr->new(-segments=>[$confirmed_exon1,$predicted_exon1,$predicted_exon2,$confirmed_exon3], -name => 'partial_gene'); my $panel = Bio::Graphics::Panel->new( -segment => $segment, # -offset => 300, # -length => 1000, -spacing => 15, -width => 600, -pad_top => 20, -pad_bottom => 20, -pad_left => 20, -pad_right=> 20, -key_style => 'between', ); $panel->add_track( [$abc3,$zed_27,$partial_gene], -bgcolor => sub { shift->source_tag eq 'predicted' ? 'green' : 'blue'}, -glyph => sub { my $feature = shift; return $feature->source_tag eq 'predicted' ? 'ellipse' : 'transcript'}, -label => 1, -bump => 1, -key => 'portents', ); #print $panel->png; my $gd = $panel->gd; my @boxes = $panel->boxes; my $red = $panel->translate_color('red'); for my $box (@boxes) { my ($feature,@points) = @$box; # $gd->rectangle(@points,$red); } #$gd->filledRectangle(0,0,20,200,1); #$gd->filledRectangle(600-20,0,600,200,1); print $gd->png; Bio-Graphics-2.37/eg/testit3.pl000555001750001750 1016612165075746 16423 0ustar00lsteinlstein000000000000#!/usr/bin/perl -w use lib '.','..','./blib/lib','../blib/lib'; use strict; use Bio::Graphics::Panel; use Bio::Graphics::Feature; use GD 'gdMediumBoldFont'; my $ftr = 'Bio::Graphics::Feature'; my $segment = $ftr->new(-start=>-100,-end=>1400,-name=>'ZK154',-type=>'clone'); my $zk154_1 = $ftr->new(-start=>-50,-end=>800,-name=>'ZK154.1',-type=>'gene'); my $zk154_2 = $ftr->new(-segments=>[[200,300],[380,800]],-name=>'ZK154.2',-type=>'gene'); my $zk154_3 = $ftr->new(-start=>900,-end=>1200,-name=>'ZK154.3',-type=>'gene'); my $zed_27 = $ftr->new(-segments=>[[550,600],[800,950],[1200,1300]], -name=>'zed-27', -subtype=>'exon',-type=>'transcript'); my $abc3 = $ftr->new(-segments=>[[100,200],[350,400],[500,550]], -name=>'abc53', -strand => -1, -subtype=>'exon',-type=>'transcript'); my $xyz4 = $ftr->new(-segments=>[[40,80],[100,120],[200,280],[300,320]], -name=>'xyz4', -subtype=>'predicted',-type=>'alignment'); my $m3 = $ftr->new(-segments=>[[20,40],[30,60],[90,270],[290,300]], -name=>'M3', -subtype=>'predicted',-type=>'alignment'); my $bigone = $ftr->new(-segments=>[[-200,-120],[90,270],[290,300]], -name=>'big one', -subtype=>'predicted',-type=>'alignment'); my $fred_12 = $ftr->new(-segments=>[$xyz4,$zed_27], -type => 'group', -name =>'fred-12'); my $confirmed_exon1 = $ftr->new(-start=>1,-stop=>20, -type=>'exon', -source=>'confirmed', -name => 'confirmed1', ); my $predicted_exon1 = $ftr->new(-start=>30,-stop=>50, -type=>'exon', -name=>'predicted1', -source=>'predicted'); my $predicted_exon2 = $ftr->new(-start=>60,-stop=>100, -name=>'predicted2', -type=>'exon',-source=>'predicted'); my $confirmed_exon3 = $ftr->new(-start=>150,-stop=>190, -type=>'exon',-source=>'confirmed', -name=>'abc123'); my $partial_gene = $ftr->new(-segments=>[$confirmed_exon1,$predicted_exon1,$predicted_exon2,$confirmed_exon3], -name => 'partial gene', -type => 'transcript', -source => '(from a big annotation pipeline)' ); my @segments = $partial_gene->segments; my $score = 10; foreach (@segments) { $_->score($score); $score += 10; } my $panel = Bio::Graphics::Panel->new( -gridcolor => 'lightcyan', -grid => 1, -segment => $segment, -spacing => 15, -width => 600, -pad_top => 20, -pad_bottom => 20, -pad_left => 20, -pad_right=> 20, -key_style => 'between', ); my @colors = $panel->color_names(); my $t = $panel->add_track( transcript2 => [$abc3,$zed_27], -label => 1, -bump => 1, -key => 'Prophecies', # -tkcolor => $colors[rand @colors], ); $t->configure(-bump=>1); $panel->add_track($segment, -glyph => 'arrow', -label => sub {scalar localtime}, -labelfont => gdMediumBoldFont, -double => 1, -bump => 0, -height => 10, -arrowstyle=>'regular', -linewidth=>1, -tick => 2, ); $panel->add_track(generic => [$segment,$abc3,$zk154_1,[$zk154_2,$xyz4]], -label => sub { $_[-1]->level == 0 } , -bgcolor => sub { shift->primary_tag eq 'predicted' ? 'green' : 'blue'}, -connector => sub { my $primary_tag = shift->primary_tag; $primary_tag eq 'transcript' ? 'hat' : $primary_tag eq 'alignment' ? 'solid' : 'solid'}, -connector_color => 'black', -height => 10, -bump => 1, # -tkcolor => $colors[rand @colors], -key => 'Signals', ); my $track = $panel->add_track('transcript2'=>[$bigone], -label => 1, -connector => 'solid', -point => 0, -orient => 'N', -height => 8, -base => 1, -relative_coords => 1, -tick => 2, -bgcolor => 'red', -key => 'Dynamically Added'); #$track->add_feature($bigone,$zed_27,$abc3); #$track->add_group($predicted_exon1,$predicted_exon2,$confirmed_exon3); $track->add_group($bigone,$zed_27,$zk154_2,$bigone); my $gd = $panel->gd; my @boxes = $panel->boxes; my $red = $panel->translate_color('red'); for my $box (@boxes) { my ($feature,@points) = @$box; } print $gd->png; Bio-Graphics-2.37/t000755001750001750 012165075746 14153 5ustar00lsteinlstein000000000000Bio-Graphics-2.37/t/Wiggle.t000555001750001750 220112165075746 15711 0ustar00lsteinlstein000000000000#-*-Perl-*- # Before `make install' is performed this script should be runnable with # `make test'. After `make install' it should work as `perl test.t' use strict; use ExtUtils::MakeMaker; use File::Temp qw(tempfile); use FindBin '$Bin'; use constant TEST_COUNT => 11; use lib "$Bin/../lib","$Bin/../blib/lib","$Bin/../blib/arch"; use Test::More tests => TEST_COUNT; use File::Temp 'tempdir'; use Bio::Graphics::Wiggle::Loader; my $source = "$Bin/data/wig_data.wig"; my $tmpdir = tempdir(CLEANUP=>1); my $loader = Bio::Graphics::Wiggle::Loader->new($tmpdir,'mywibfile'); ok($loader); my $fh = IO::File->new($source); ok($loader->load($fh)); my $gff3 = $loader->featurefile('gff3'); undef $loader; # force a flush ok($gff3); my ($wibfile) = $gff3 =~ m!wigfile=(.+\.wib)!; ok($wibfile); ok($wibfile =~ /mywibfile/); my $wig = Bio::Graphics::Wiggle->new($wibfile); ok($wig); is($wig->seqid,'I'); ok(abs($wig->values(87=>87)->[0]-0.22) < 0.01); ok(abs($wig->values(173=>173)->[0]-0.52) < 0.01); my $h = $wig->values(101=>300); is(@$h,200); my $result = $wig->export_to_bedgraph(1,5000); my @lines = split "\n",$result; is(@lines,57); exit 0; Bio-Graphics-2.37/t/BioGraphics.t000555001750001750 3215112165075746 16714 0ustar00lsteinlstein000000000000# -*-Perl-*- Test Harness script for Bioperl use strict; use File::Spec; use FindBin '$Bin'; use File::Glob ':glob'; # In order to properly run the image comparison tests the images may need to be # regenerated from scratch; this is primarily due to changes in GD versions, OS, # Bio::Graphics, problems with storing binary data in CVS, etc. # We'll need to reconfigure these tests to allow do_write() # the ability to regenerate those files when passing the --write option # for now, the image tests are turned off use lib "$Bin/../lib"; use constant IMAGE_TESTS => 1; BEGIN { use lib '.'; use Test::More tests => 49 + (IMAGE_TESTS ? 3 : 0); use_ok('GD::Image'); use_ok('Bio::Graphics::FeatureFile'); use_ok('Bio::Graphics::Panel'); } my $images = File::Spec->catfile($Bin,'data'); my @images = IMAGE_TESTS ? qw(t1 t2 t3) : (); # parse command line arguments my $write = 0; while (@ARGV && $ARGV[0] =~ /^--?(\w+)/) { my $arg = $1; if ($arg eq 'write') { warn "Writing regression test images into ",$images,".........\n"; $write++; } shift; } foreach (@images) { if ($write) { warn "$_...\n"; do_write($_) } else { eval { do_compare($_) } } } my $data = Bio::Graphics::FeatureFile->new(-file => File::Spec->catfile($Bin,'data','feature_data.txt'), -safe => 0, ) or die; ok defined $data; is $data->render, 5; is $data->setting(general=>'pixels'), 750; is $data->setting('general'), 3; is $data->setting, 6; is $data->glyph('EST'), 'segments'; my %style = $data->style('EST'); is $style{-connector}, 'solid'; is $style{-height}, 5; is $style{-bgcolor}, 'yellow'; is $data->configured_types, 5; is @{$data->features('EST')}, 5; my $thing = $data->features('EST'); is $thing->[0]->seq_id,'B0511'; my ($feature) = grep {$_->name eq 'Predicted gene 1'} @{$data->features('FGENESH')}; ok $feature; is $feature->desc, "Pfam"; is $feature->score, 20; # test handling of things that look like comments is $data->setting(EST=>'bgcolor'),'yellow'; is $data->setting(EST=>'fgcolor'),'#EE00FF'; is $data->setting(EST=>'link'),'http://www.google.com/search?q=$name#results'; # test handling of adding features $data->add_type(TEST=>{bgcolor=>'green', feature=>'test_feature', glyph => 'generic'}); is $data->setting(TEST=>'bgcolor'),'green'; is $data->setting(TEST=>'feature'),'test_feature'; $data->add_feature(Bio::Graphics::Feature->new(-seq_id => 'chr1', -start => 1, -end => 1000, -primary_tag=> 'test_feature')); $data->add_feature(Bio::Graphics::Feature->new(-seq_id => 'chr2', -start => 2, -end => 2000, -primary_tag=> 'test_feature')); $data->add_feature(Bio::Graphics::Feature->new(-seq_id => 'chr3', -start => 3, -end => 3000), 'test_feature'); my @f = $data->features('test_feature'); is scalar @f,3; # test FeatureBase my $bfg = 'Bio::Graphics::Feature'; $feature = $bfg->new(-seq_id=>'chr2',-start=>201,-end=>300,-strand=>1); is $feature->seq_id,'chr2'; is $feature->start,201; is $feature->end,300; is $feature->strand,1; # plus strand feature, plus strand ref sequence my $ref = $bfg->new(-seq_id=>'chr2',-start=>201,-end=>300,-strand=>1); $feature->refseq($ref); is $feature->start,1; is $feature->end,100; is $feature->strand,1; is $feature->abs_start,201; is $feature->abs_end,300; is $feature->abs_strand,1; # plus strand feature, minus strand ref sequence $ref = $bfg->new(-seq_id=>'chr2',-start=>201,-end=>300,-strand=>-1); $feature->refseq($ref); is $feature->start,100; # expect flipping so that start > end is $feature->end,1; is $feature->strand,-1; # minus strand feature, plus strand ref $feature = $bfg->new(-seq_id=>'chr2',-start=>201,-end=>300,-strand=>-1); $ref = $bfg->new(-seq_id=>'chr2',-start=>201,-end=>300,-strand=>1); $feature->refseq($ref); is $feature->start,1; is $feature->end,100; is $feature->strand,-1; # minus strand feature, minus strand ref $ref = $bfg->new(-seq_id=>'chr2',-start=>201,-end=>300,-strand=>-1); $feature->refseq($ref); is $feature->start,100; # expect flipping so that start > end is $feature->end,1; is $feature->strand,1; # test safety of callbacks is $data->safe,0; is ref $data->setting(SwissProt=>'fill'),''; is eval{ref $data->code_setting(SwissProt=>'fill')},undef; $data = Bio::Graphics::FeatureFile->new(-file => File::Spec->catfile($Bin,'data', 'feature_data.txt'), -safe => 1, ) or die; is $data->safe,1; is ref $data->setting(SwissProt=>'fill'),'CODE'; is eval{ref $data->code_setting(SwissProt=>'fill')},'CODE'; exit 0; sub do_write { my $test = shift; my $canpng = GD::Image->can('png'); my $cangif = GD::Image->can('gif'); my $test_sub = $test; if ($canpng) { my $output_file = File::Spec->catfile($Bin,'data',$test).'.png'; my $panel = eval "$test_sub()" or die "Couldn't run test: $@"; open OUT,">$output_file" or die "Couldn't open $output_file for writing: $!"; print OUT $panel->gd->png; close OUT; } if ($cangif) { my $output_file = File::Spec->catfile($Bin,'data',$test).'.gif'; my $panel = eval "$test_sub()" or die "Couldn't run test: $@"; open OUT,">$output_file" or die "Couldn't open $output_file for writing: $!"; print OUT $panel->gd->gif; close OUT; } } sub do_compare { my $test = shift; my $canpng = GD::Image->can('png'); my @input_files = glob($images . ($canpng ? "/$test/*.png" : "/$test/*.gif")); my $test_sub = $test; my $panel = eval "$test_sub()" or die "Couldn't run test"; my $ok = 0; my $test_data = $canpng ? $panel->gd->png : $panel->gd->gif; foreach (@input_files) { my $reference_data = read_file($_); if ($reference_data eq $test_data) { $ok++; last; } } ok($ok); } sub read_file { my $f = shift; open F,$f or die "Can't open $f: $!"; binmode(F); my $data = ''; while (read(F,$data,1024,length $data)) { 1 } close F; $data; } sub t1 { my $ftr = 'Bio::Graphics::Feature'; my $segment = $ftr->new(-start=>1,-end=>1000,-name=>'ZK154',-type=>'clone'); my $subseg1 = $ftr->new(-start=>1,-end=>500,-name=>'seg1',-type=>'gene'); my $subseg2 = $ftr->new(-start=>250,-end=>500,-name=>'seg2',-type=>'gene'); my $subseg3 = $ftr->new(-start=>250,-end=>500,-name=>'seg3',-type=>'gene'); my $subseg4 = $ftr->new(-start=>1,-end=>400,-name=>'seg4',-type=>'gene'); my $subseg5 = $ftr->new(-start=>400,-end=>800,-name=>'seg5',-type=>'gene'); my $subseg6 = $ftr->new(-start=>550,-end=>800,-name=>'seg6',-type=>'gene'); my $subseg7 = $ftr->new(-start=>550,-end=>800,-name=>'seg7',-type=>'gene'); my $subseg8 = $ftr->new(-segments=>[[100,200],[300,400],[420,800]],-name=>'seg8',-type=>'gene'); my $panel = Bio::Graphics::Panel->new( -grid => 1, -segment => $segment, -key_style => 'bottom'); $panel->add_track(segments=>[$subseg1,$subseg2,$subseg3,$subseg4, $subseg5,$subseg6,$subseg7,$subseg8], -bump => 1, -label => 1, -key => '+1 bumping'); $panel->add_track(segments=>[$subseg1,$subseg2,$subseg3,$subseg4, $subseg5,$subseg6,$subseg7,$subseg8], -bump => -1, -label => 1, -bgcolor => 'blue', -key => '-1 bumping'); $panel->add_track(segments=>[$subseg1,$subseg2,$subseg3,$subseg4, $subseg5,$subseg6,$subseg7,$subseg8], -bump => +2, -label => 1, -bgcolor => 'orange', -key => '+2 bumping'); $panel->add_track(segments=>[$subseg1,$subseg2,$subseg3,$subseg4, $subseg5,$subseg6,$subseg7,$subseg8], -bump => -2, -label => 1, -bgcolor => 'yellow', -key => '-2 bumping'); return $panel; } sub t2 { my $ftr = 'Bio::Graphics::Feature'; my $segment = $ftr->new(-start=>-100,-end=>1000,-name=>'ZK154',-type=>'clone'); my $zk154_1 = $ftr->new(-start=>-50,-end=>800,-name=>'ZK154.1',-type=>'gene'); my $zk154_2 = $ftr->new(-start=>380,-end=>500,-name=>'ZK154.2',-type=>'gene'); my $zk154_3 = $ftr->new(-start=>900,-end=>1200,-name=>'ZK154.3',-type=>'gene'); my $zed_27 = $ftr->new(-segments=>[[400,500],[550,600],[800,950]], -name=>'zed-27', -strand => 1, -subtype=>'exon',-type=>'transcript'); my $abc3 = $ftr->new(-segments=>[[100,200],[350,400],[500,550]], -name=>'abc53', -strand => -1, -subtype=>'exon',-type=>'transcript'); my $xyz4 = $ftr->new(-segments=>[[40,80],[100,120],[200,280],[300,320]], -name=>'xyz4', -subtype=>'predicted',-type=>'alignment'); my $m3 = $ftr->new(-segments=>[[20,40],[30,60],[90,270],[290,300]], -name=>'M3', -subtype=>'predicted',-type=>'alignment'); my $bigone = $ftr->new(-segments=>[[-200,-120],[90,270],[290,300]], -name=>'big one', -strand => 1, -subtype=>'predicted',-type=>'alignment'); my $fred_12 = $ftr->new(-segments=>[$xyz4,$zed_27], -type => 'group', -name =>'fred-12'); my $confirmed_exon1 = $ftr->new(-start=>1,-stop=>20, -type=>'exon', -desc=>'confirmed', -name => 'confirmed1', ); my $predicted_exon1 = $ftr->new(-start=>30,-stop=>50, -type=>'exon', -name=>'predicted1', -desc=>'predicted'); my $predicted_exon2 = $ftr->new(-start=>60,-stop=>100, -name=>'predicted2', -type=>'exon',-desc=>'predicted'); my $confirmed_exon3 = $ftr->new(-start=>150,-stop=>190, -type=>'exon',-desc=>'confirmed', -name=>'abc123'); my $partial_gene = $ftr->new(-segments=>[$confirmed_exon1,$predicted_exon1,$predicted_exon2,$confirmed_exon3], -name => 'partial gene', -type => 'transcript', -strand => 1, -desc => '(from a big annotation pipeline)' ); my @segments = $partial_gene->segments; my $score = 10; foreach (@segments) { $_->score($score); $score += 10; } my $panel = Bio::Graphics::Panel->new( -gridcolor => 'lightcyan', -grid => 1, -segment => $segment, -spacing => 15, -width => 600, -pad_top => 20, -pad_bottom => 20, -pad_left => 20, -pad_right=> 20, -key_style => 'between', -empty_tracks => 'suppress', ); my @colors = $panel->color_names(); my $t = $panel->add_track( transcript2 => [$abc3,$zed_27], -label => 1, -bump => 1, -key => 'Prophecies', ); $t->configure(-bump=>1); $panel->add_track($segment, -glyph => 'arrow', -label => 'base pairs', -double => 1, -bump => 0, -height => 10, -arrowstyle=>'regular', -linewidth=>1, -tick => 2, ); $panel->unshift_track(generic => [$segment,$zk154_1,$zk154_2,$zk154_3,[$xyz4,$zed_27]], -label => sub { my $feature = shift; $feature->sub_SeqFeature>0}, -bgcolor => sub { shift->primary_tag eq 'predicted' ? 'olive' : 'red'}, -connector => sub { my $feature = shift; my $type = $feature->primary_tag; $type eq 'group' ? 'dashed' : $type eq 'transcript' ? 'hat' : $type eq 'alignment' ? 'solid' : undef}, -all_callbacks => 1, -connector_color => 'black', -height => 10, -bump => 1, -linewidth=>2, -key => 'Signs', -empty_tracks => 'suppress', ); my $track = $panel->add_track(-glyph=> sub { shift->primary_tag =~ /transcript|alignment/ ? 'transcript2': 'generic'}, -label => sub { $_[-1]->level == 0 } , -connector => sub { return shift->type eq 'group' ? 'dashed' : 'hat'}, -point => 0, -orient => 'N', -height => 8, -base => 1, -relative_coords => 1, -tick => 2, -all_callbacks => 1, -bgcolor => 'red', -key => 'Dynamically Added'); $track->add_feature($bigone,$zed_27,$abc3); $track->add_group($predicted_exon1,$predicted_exon2,$confirmed_exon3); $panel->add_track( [$abc3,$zed_27,$partial_gene], -bgcolor => sub { shift->source_tag eq 'predicted' ? 'green' : 'blue'}, -glyph => 'transcript', -label => sub { shift->sub_SeqFeature > 0 }, -description => sub { my $feature = shift; return 1 if $feature->primary_tag eq 'transcript'; return '*' if $feature->source_tag eq 'predicted'; return; }, -font2color => 'red', -bump => +1, -key => 'Portents', ); $panel->add_track(segments => [$segment,$zk154_1,[$zk154_2,$xyz4]], -label => 1, -bgcolor => sub { shift->primary_tag eq 'predicted' ? 'green' : 'blue'}, -connector => sub { my $primary_tag = shift->primary_tag; $primary_tag eq 'transcript' ? 'hat' : $primary_tag eq 'alignment' ? 'solid' : undef}, -connector_color => 'black', -height => 10, -bump => 1, -key => 'Signals', ); $panel->add_track(generic => [], -key => 'Empty'); $panel->add_track(graded_segments => $partial_gene, -bgcolor =>'blue', -vary_fg => 1, -label => 1, -key => 'Scored thing'); $panel->add_track(diamond => [$segment,$zk154_1,$zk154_2,$zk154_3,$xyz4,$zed_27], -bgcolor =>'blue', -label => 1, -key => 'pointy thing'); return $panel; } sub t3 { my $data = Bio::Graphics::FeatureFile->new(-file => File::Spec->catfile($Bin,'data','feature_data.txt') ) or die; my ($tracks,$panel) = $data->render; return $panel; } Bio-Graphics-2.37/t/decorated_transcript_t1.pl000444001750001750 750112165075746 21457 0ustar00lsteinlstein000000000000#!/usr/bin/perl use strict; use warnings; use Bio::Graphics; use Bio::Graphics::Panel; use Bio::Graphics::Glyph::decorated_transcript; use Bio::DB::SeqFeature::Store; use Bio::SeqFeature::Generic; use Data::Dumper; # load features my $store = Bio::DB::SeqFeature::Store->new ( -adaptor => 'memory', -dsn => 'data/decorated_transcript_t1.gff' ); my ($gene1) = $store->features(-name => 'PFA0680c'); #print Dumper($rna1); # draw panel my $panel = Bio::Graphics::Panel->new ( -length => $gene1->end-$gene1->start+102, -offset => $gene1->start-100, -key_style => 'between', -width => 1024, -pad_left => 100 ); # ruler $panel->add_track ( Bio::SeqFeature::Generic->new(-start => $gene1->start-100, -end => $gene1->end), -glyph => 'arrow', -bump => 0, -double => 1, -tick => 2 ); $panel->add_track ( $gene1, -glyph => 'decorated_gene', -label_transcripts => 1, -description => 'Signal peptide spans intron, isoform1 has extra callback decoration, isoform2 lacks TM domain', -label => 1, -height => 12, -decoration_visible => sub { my ($feature, $option_name, $part_no, $total_parts, $glyph) = @_; return 0 if ($glyph->active_decoration->name eq "TM" and $glyph->active_decoration->score < 8); }, -decoration_color => sub { my ($feature, $option_name, $part_no, $total_parts, $glyph) = @_; return 'black' if ($glyph->active_decoration->name eq "TM"); return 'red' if ($glyph->active_decoration->name eq "VTS"); }, -decoration_label_color => sub { my ($feature, $option_name, $part_no, $total_parts, $glyph) = @_; return 'white' if ($glyph->active_decoration->name eq "VTS"); }, -additional_decorations => sub { my $feature = shift; my ($id) = $feature->get_tag_values('load_id'); my %add_h = ( "rna_PFA0680c-1" => "test:callback:100:130:0" ); return $add_h{$id}; } ); # decoration outside transcript boundaries, transparent background my ($gene2) = $store->features(-name => 'test1'); { $panel->add_track ( $gene2, -glyph => 'decorated_gene', -description => sub { "Gene label and description do not bump with extended decoration boundaries" }, -label => 1, -label_position => 'top', -height => 12, -decoration_visible => 1, -decoration_border => "dashed", -decoration_color => "transparent", -decoration_label_position => "above", -decoration_label => 1, -decoration_height => 17, -decoration_border_color => "blue" ); } # use of decorated_transcript glyph directly, with mRNA feature { my ($rna2) = $gene2->get_SeqFeatures('mRNA'); $panel->add_track ( $rna2, -glyph => 'decorated_transcript', -description => sub { "This text should not bump with decoration label" }, -label => 1, -label_position => 'top', -height => 16, -decoration_visible => 1, -decoration_border => "solid", -decoration_color => "yellow", -decoration_label_position => sub { return "below" if ($_[4]->active_decoration->type eq "method1"); return "inside"; }, -decoration_label => sub { return "another interesting region" if ($_[4]->active_decoration->type eq "method1"); return 1; # return 1 to draw default label }, -decoration_height => 20, -decoration_border_color => "red" ); } # gene with UTR { my ($gene) = $store->features(-name => 'PVX_000640'); $panel->add_track ( $gene, -glyph => 'decorated_gene', -description => 1, -label => 1, -height => 12, -decoration_color => "yellow", -label_position => 'top', -decoration_visible => 1, ); } # write image my $imgfile = "data/decorated_transcript_t1.png"; open(IMG,">$imgfile") or die "could not write to file $imgfile"; print IMG $panel->png; close(IMG); print "Image written to $imgfile\n"; Bio-Graphics-2.37/t/data000755001750001750 012165075746 15064 5ustar00lsteinlstein000000000000Bio-Graphics-2.37/t/data/t3.gif000444001750001750 1305712165075746 16264 0ustar00lsteinlstein000000000000GIF87a(Qͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ xXn`v l 3ҨEw^:4kѧUI{[әsͻ7۽[#\ײf6Bvt5,gp_q BlU[ áv@ H"HL&:PH*ZH &,zcY2*bFTj8x̣PH =I&m/^(S}RP)"dK9W~lՃP)TKD܆ȴg) O 3L@AV2 *+es0䴩cz/&3@uSS%+T} *D}Ɣ .pΒ=)}$N5OF9L~bNڨCP[s 8T=PIQu% &=i4J9zN' ^IӊR)ꢤd^5T}&AXEP#_Xq1'[J׵*H:&XwJSu'I0Kgf9!0],d%C؂,ezKʬ hFq jkݴ6-A_`@p[۝nBk[%mn;_+q.+[鎋4]hκk΋1t|\;^n-+F :v!R,ox{귾1oqFno\ nAK`"˥w@Ldr1'Y&i1ClR&b%?-63DUiӚzjWqOJI)uN0Ayn)eS.A. O<6sy('oZf.:w'ĺR2W:fΦ'*:hV1yXWtzNgINr*1ӺuOF_n}f7yqzjŹ)6M1mnKIn#o[nsC pt&~NO;b"HԈBTҘ!PgJGSANHr@h\t&d\SEf2yӣsg HO y_(Pe~\QϺַ{`l)7?a*QXujvq.OIg${ۭj5H?ִQ 3Ad*S!6,^mgK"UƦ'?'߈;'zqI٤"3RU3>O`ަ爟Rj_Y}oG#J eTw {KsV?W,ǴvJfY|xOrgTVimexgjjေW{H2MPVLV|v$؁V(f wvFKR X}7!|YwHN4wDG#y0kFXHkq !(ж!l'vi7xc˖}`gzEldƄ#Z8gXdZmzȆLA~5Rt Dxܷm(ȇv7%刑sׇ4G$6.XR&.r4L1/r8~H^g~"WgCfP+_H5ZxG|1ȋҊs:8f茻؍hMkэhwglzQHFHyJH唍<}VȊJ!㈆JXRƈ%7qԋ uӘy"ฏ )"!Sl8 Upᑻ6.]lQ/2iI_#yZ6( st,ɔ99WUhJyoNF]H!֎4Y%xOz)i" ḑ({MIik8hZUYpNiC a5'HٸlwQIixvW8A)'i$ @x͸#9҉wJɜY%)1!QHsi99 M8fU۩ra9߹繗ϸ[ t 9ʝ$ VI$ (I s ZJ*azDyX9ɢI 'j+P1zȠRYq*>fZf"Sʛ؟(8Xʗ \^ a \rI 73 GZJwJv?R4Gzavjڪ^TB)dE?A<:%)-,zp-;īJ;:B«.14dj8zS}W,ɊLԪ:ԪzхxH22+DZ)$0Jګwk'zqfwymҺDʬR皱[ڱk|'XQ{|7,z/0;bfY~ɷ&27{+;1r5[8EhVSDP!(7rS[TB\kQH SyJXz%YgrZxIlj ΁!g2Jk*Fe9k:o{Ko[;ox+nK٥˺ꗨ6ֺ噘ƻwj%Fȫ_[m|{ZћڻKd3-r[K[2km쪴ڴj кꋮL{s6KKˬZXG[k닮 [2,Ί \G+{;N,2KC*+l+[,ժL4\3gQa,BJOFBgLIWW u qOBr"y%kׄ|90,LУmГ ]]} "=$]&}(*, 02=4]6}8#;Л=-ԊF}]LN5tfk9HP46<[X}H/̾)L0/:* ՅBB :J4[~E: XNk.k\ݡ@Gl'VdO%nn1η6-=nlX9S5rZt^vx'Tg~~R~T~肮v>l;Bio-Graphics-2.37/t/data/wig_data.wig000444001750001750 13127512165075746 17561 0ustar00lsteinlstein000000000000variableStep chrom=I span=50 87 0.22 173 0.52 259 0.51 345 0.27 431 0.19 517 0.16 603 0.52 689 0.26 775 0.05 861 0.63 947 0.47 1033 0.26 1119 -0.05 1205 0.35 1291 -0.26 1377 0.08 1463 -0.36 1549 0.34 1635 0.01 1721 0.16 1807 -0.06 1893 -0.02 1979 0.35 2065 0.55 2151 0.25 2237 0.52 2330 0.52 2416 0.48 2502 0.38 2638 0.13 2724 0.17 2810 0.33 2896 0.23 2982 0.45 3068 0.34 3154 0.01 3240 -0.10 3335 0.22 3421 0.39 3507 0.12 3593 0.31 3679 0.12 3765 0.46 3851 0.58 3937 0.74 4023 0.42 4109 0.47 4195 0.50 4281 -0.29 4367 0.08 4453 0.28 4539 -0.07 4625 -0.20 4711 0.05 4797 0.12 4883 0.00 4969 0.48 5055 -0.10 5143 -0.19 5229 -0.39 5315 0.21 5405 0.19 5500 0.07 5586 0.05 5692 -0.12 5778 0.04 5864 0.22 5950 -0.22 6036 0.25 6122 0.20 6208 0.14 6294 -0.13 6380 0.11 6466 -0.29 6552 0.23 6638 -0.24 6724 0.30 6810 -0.08 6896 -0.05 6982 0.29 7068 -0.01 7154 0.27 7240 0.63 7326 -0.03 7412 0.12 7498 -0.14 7584 0.24 7670 -0.07 7756 0.25 7842 0.02 7928 -0.09 8014 0.02 8100 -0.03 8186 0.07 8272 -0.13 8358 0.05 8444 -0.17 8530 0.16 8616 -0.29 8702 -0.27 8788 0.08 8874 0.05 8960 0.03 9046 0.02 9132 0.33 9218 0.34 9304 0.34 9390 0.20 9476 -0.11 9648 -0.40 9734 0.15 9820 0.34 9906 0.20 10021 0.10 10107 -0.24 10193 0.09 10279 -0.10 10365 -0.06 10451 0.12 10537 0.26 10623 0.09 10709 0.02 10795 0.22 10881 0.21 10967 -0.37 11053 0.36 11139 0.12 11225 0.37 11311 0.15 11397 -0.27 11483 0.22 11573 0.12 11659 0.05 11745 0.06 11831 0.06 11917 -0.06 12003 0.09 12089 -0.18 12175 -0.12 12261 0.28 12347 0.18 12433 -0.03 12528 0.17 12614 0.31 12700 -0.04 12786 -0.02 12872 -0.09 12958 0.10 13044 0.12 13130 0.08 13216 1.07 13302 0.53 13388 0.39 13474 0.34 13560 0.20 13646 0.79 13732 -0.01 13818 0.16 13904 0.28 13990 0.15 14076 0.76 14162 0.68 14330 0.28 14416 -0.05 14502 -0.25 14588 -0.05 14674 0.05 14760 0.22 14846 -0.14 14932 0.20 15018 0.44 15104 0.54 15190 0.35 15278 0.61 15364 -0.01 15451 0.22 15537 0.37 15623 0.79 15709 0.66 15795 0.05 15881 0.27 15967 -0.04 16053 0.33 16139 0.17 16225 0.19 16311 -0.11 16397 0.26 16483 0.28 16569 0.12 16655 0.15 16741 0.07 16827 0.20 16913 0.55 16999 0.53 17085 0.50 17180 0.39 17266 0.08 17352 0.48 17438 0.35 17524 0.40 17610 -0.07 17696 0.23 17782 0.14 17868 0.17 17954 -0.13 18040 0.09 18126 -0.16 18212 -0.49 18298 0.20 18384 0.19 18471 -0.28 18557 0.18 18643 0.29 18729 0.21 18815 0.29 18913 0.44 18999 0.39 19085 -0.45 19171 0.15 19285 0.29 19371 0.06 19457 0.04 19543 0.34 19629 0.18 19715 0.45 19826 0.10 19912 0.17 19998 -0.01 20084 0.23 20170 0.62 20256 -0.01 20342 0.17 20428 0.05 20514 0.37 20600 0.05 20686 0.10 20772 0.06 20858 0.00 20944 -0.13 21030 0.12 21116 -0.04 21202 -0.06 21288 0.32 21376 0.39 21462 -0.35 21548 0.08 21634 -0.00 21720 -0.06 21806 0.39 21892 -0.03 21978 0.30 22064 0.13 22150 0.26 22236 0.38 22322 0.32 22408 0.50 22494 0.07 22580 0.19 22666 0.61 22752 0.26 22838 0.47 22924 0.29 23010 0.69 23096 0.22 23182 0.36 23268 0.25 23354 0.24 23440 0.30 23526 -0.19 23612 0.30 23698 -0.07 23787 -0.09 23873 -0.11 23959 0.18 24045 -0.03 24131 0.21 24217 0.22 24303 -0.11 24391 0.32 24477 0.24 24563 0.24 24649 0.16 24735 -0.01 24821 -0.11 24907 0.18 24993 -0.04 25079 0.34 25165 0.36 25251 -0.16 25337 -0.16 25423 -0.03 25509 -0.05 25595 0.09 25681 0.40 25769 -0.01 25855 0.40 25941 0.46 26027 0.10 26113 0.05 26199 0.25 26285 -0.16 26371 0.32 26457 0.08 26543 0.16 26629 0.25 26715 0.11 26801 0.12 26887 0.12 26973 0.22 27059 0.69 27145 0.27 27231 0.53 27317 0.23 27409 -0.07 27495 0.34 27581 0.12 27667 0.12 27753 0.34 27839 -0.56 27925 0.14 28012 -0.11 28098 -0.01 28184 0.26 28270 0.45 28356 0.22 28442 -0.17 28540 0.07 28627 0.20 28713 -0.31 28799 0.19 28885 0.27 28992 -0.02 29078 -0.05 29164 -0.15 29250 0.05 29336 0.03 29422 0.34 29508 0.07 29594 0.03 29680 0.02 29766 0.08 29852 0.14 29938 0.25 30024 0.28 30110 0.41 30196 0.33 30282 0.28 30368 0.29 30454 0.37 30540 0.42 30626 0.11 30712 -0.17 30798 -0.08 30884 -0.34 30970 -0.06 31056 0.08 31142 -0.07 31228 -0.01 31314 -0.02 31400 -0.22 31487 -0.34 31573 -0.15 31659 0.05 31745 0.06 31831 0.06 31917 0.03 32003 -0.02 32089 0.09 32175 0.30 32261 0.18 32347 0.17 32433 0.10 32519 -0.06 32605 -0.01 32691 0.06 32777 -0.03 32863 -0.05 32949 0.03 33035 0.42 33121 0.27 33207 0.38 33293 0.08 33379 0.20 33465 0.29 33560 -0.17 33646 0.20 33732 0.02 33818 -0.34 33904 0.04 33990 0.19 34076 0.01 34162 0.47 34248 0.22 34334 0.37 34420 0.16 34506 -0.10 34592 -0.12 34678 0.81 34764 -0.22 34850 0.04 34936 -0.05 35022 -0.05 35108 0.06 35194 0.35 35280 0.27 35366 0.11 35452 0.62 35538 0.34 35624 0.16 35710 0.25 35796 0.23 35882 0.36 35968 0.09 36054 0.24 36140 0.53 36226 0.15 36312 0.52 36398 0.19 36484 0.23 36570 0.10 36656 0.18 36742 -0.08 36828 -0.19 36914 -0.09 37000 0.06 37086 -0.22 37172 0.22 37258 0.21 37344 0.05 37430 0.05 37516 -0.15 37602 0.06 37688 0.22 37774 -0.31 37860 -0.19 37946 0.10 38032 -0.07 38118 0.10 38204 0.23 38290 0.13 38420 -0.27 38534 0.29 38620 0.16 38706 -0.10 38792 0.18 38878 0.06 38964 0.37 39050 0.49 39136 -0.07 39222 0.11 39308 -0.33 39394 -0.09 39480 -0.08 39566 0.05 39652 -0.23 39738 0.13 39824 0.07 39910 0.23 39996 0.07 40082 0.14 40168 -0.19 40254 0.22 40340 0.21 40426 0.19 40512 0.08 40598 0.03 40684 0.01 40770 0.32 40856 0.47 40942 0.04 41028 0.31 41114 0.21 41200 0.39 41286 -0.41 41372 0.11 41458 0.01 41544 0.13 41630 0.15 41716 -0.02 41802 0.12 41888 0.49 41974 0.12 42060 0.02 42146 0.13 42232 -0.05 42318 -0.03 42404 -0.08 42490 0.21 42576 0.23 42662 -0.36 42748 0.31 42834 0.06 42920 0.09 43006 -0.10 43092 0.05 43178 0.11 43264 0.00 43350 0.01 43436 0.11 43522 -0.03 43608 0.25 43694 -0.05 43780 0.08 43866 -0.12 43952 0.00 44038 0.34 44124 0.30 44210 -0.03 44296 -0.13 44382 -0.08 44468 0.12 44554 -0.27 44640 -0.01 44726 0.35 44812 0.02 44898 0.27 44984 0.35 45070 0.60 45156 0.46 45242 0.17 45328 0.09 45414 0.25 45500 0.12 45586 0.11 45672 -0.22 45759 0.24 45845 0.07 45931 0.69 46017 0.03 46103 0.03 46189 -0.11 46275 0.14 46361 0.47 46447 0.30 46533 0.18 46619 0.06 46705 0.23 46791 -0.01 46877 0.05 46963 0.10 47049 0.22 47135 0.28 47221 0.31 47307 0.26 47393 0.18 47479 0.37 47565 0.25 47651 0.34 47737 0.00 47823 -0.12 47909 0.43 47995 0.10 48091 0.45 48177 0.40 48263 0.16 48349 0.05 48435 -0.44 48521 0.14 48607 0.30 48719 0.14 48832 0.30 48918 0.26 49004 0.04 49171 -0.13 49257 -0.26 49343 -0.05 49429 -0.52 49515 -0.34 49601 -0.06 49687 0.26 49773 0.25 49874 0.42 49960 0.02 50046 0.34 50133 0.04 50219 0.42 50305 0.28 50393 0.55 50479 -0.23 50565 0.28 50715 0.15 50801 -0.35 50887 0.12 50973 0.29 51059 0.00 51145 0.34 51237 0.07 51331 -0.73 51417 -0.02 51503 -0.09 51589 -0.27 51675 -0.22 51761 0.04 51847 0.11 51933 -0.07 52019 -0.12 52105 -0.02 52191 -0.05 52277 0.17 52363 -0.23 52449 0.10 52535 0.06 52621 0.07 52714 0.39 52800 0.27 52890 0.10 52976 0.32 53062 -0.02 53148 0.15 53234 -0.01 53320 0.03 53406 0.12 53492 0.11 53578 -0.06 53664 0.79 53787 0.15 53873 0.10 53959 0.08 54045 -0.16 54131 -0.12 54217 -0.38 54303 -0.22 54389 0.12 54475 0.05 54561 0.12 54649 0.19 54747 0.19 54833 -0.18 54919 0.12 55005 0.24 55091 0.01 55191 -0.39 55277 -0.24 55363 -0.04 55449 0.18 55535 -0.14 55621 -0.15 55707 0.12 55793 0.20 55879 -0.15 55965 0.41 56051 0.29 56137 0.06 56223 -0.14 56309 0.08 56395 0.02 56481 -0.02 56567 -0.08 56653 -0.28 56739 0.40 56825 0.10 56911 0.20 56997 -0.15 57083 0.02 57169 -0.10 57255 -0.27 57341 -0.28 57427 -0.05 57513 -0.32 57599 -0.04 57685 -0.07 57771 0.05 57857 0.16 57983 0.17 58069 0.01 58155 0.29 58241 0.19 58327 -0.08 58413 0.63 58499 -0.12 58585 -0.05 58671 -0.08 58757 0.17 58843 0.18 58929 0.23 59015 0.22 59101 -0.07 59187 -0.08 59273 -0.16 59359 0.03 59445 -0.06 59531 0.53 59617 0.07 59703 0.29 59789 0.15 59875 0.05 59961 0.23 60047 0.02 60133 -0.05 60219 0.06 60305 -0.17 60391 0.25 60477 -0.00 60563 -0.17 60649 0.28 60735 0.09 60821 0.41 60907 -0.13 60993 -0.07 61079 -0.25 61165 -0.19 61251 -0.03 61337 -0.05 61423 -0.20 61509 0.25 61595 0.10 61681 0.09 61767 0.24 61853 -0.03 61939 -0.37 62025 0.01 62111 0.34 62234 0.13 62320 0.09 62406 0.29 62492 0.15 62578 -0.08 62664 0.10 62750 -0.18 62836 -0.10 62922 -0.06 63008 0.24 63107 -0.03 63193 -0.03 63279 0.16 63365 0.51 63451 0.44 63537 0.29 63623 0.17 63709 0.06 63795 -0.09 63881 -0.10 63967 -0.03 64053 0.11 64139 -0.42 64225 0.02 64311 -0.10 64397 0.05 64483 -0.19 64569 -0.26 64655 -0.26 64741 -0.16 64827 -0.10 64913 -0.06 64999 0.23 65085 0.13 65171 0.45 65257 0.26 65367 0.28 65453 0.24 65539 0.18 65625 0.44 65711 0.27 65797 -0.09 65883 -0.01 65969 0.02 66055 -0.07 66141 0.23 66227 0.35 66313 0.28 66399 0.38 66485 0.25 66571 0.19 66657 0.36 66743 0.48 66829 0.26 66915 0.51 67001 0.13 67087 0.54 67173 -0.07 67259 0.23 67345 0.05 67431 0.39 67517 0.06 67603 0.23 67689 0.17 67775 -0.01 67861 0.16 67947 0.13 68033 -0.05 68119 -0.07 68205 0.44 68291 -0.33 68377 -0.11 68463 -0.02 68549 0.05 68635 0.42 68721 0.51 68807 0.17 68893 0.20 68979 -0.28 69065 0.01 69151 -0.04 69237 0.03 69323 0.07 69409 0.16 69495 0.10 69581 -0.04 69667 0.29 69758 0.30 69844 0.32 69930 0.01 70016 0.70 70102 0.44 70188 0.36 70274 0.28 70360 0.37 70446 0.31 70532 0.32 70618 0.09 70704 0.02 70790 0.12 70881 -0.03 70967 0.33 71053 -0.06 71139 0.02 71225 0.02 71311 0.25 71397 0.15 71483 0.06 71569 0.05 71655 0.43 71741 0.44 71827 0.21 71913 0.04 71999 0.12 72085 -0.02 72171 0.47 72257 0.37 72343 0.28 72429 0.40 72515 0.14 72601 0.04 72687 0.37 72773 0.04 72859 -0.04 72945 0.35 73031 0.17 73117 0.38 73203 0.09 73290 0.35 73379 0.24 73465 0.45 73551 0.46 73637 -0.35 73723 0.26 73809 0.26 73895 -0.14 73981 -0.20 74096 0.16 74182 0.17 74268 -0.37 74354 0.00 74440 0.01 74526 -0.19 74612 0.03 74698 0.06 74784 0.06 74870 -0.13 74956 -0.01 75042 -0.08 75128 0.06 75214 0.24 75300 0.00 75386 0.12 75472 0.11 75558 0.27 75644 -0.07 75730 0.19 75816 0.24 75902 0.25 75988 0.01 76074 0.17 76160 0.15 76246 -0.07 76332 0.21 76429 -0.03 76515 0.07 76601 -0.12 76689 -0.17 76775 0.20 76861 0.25 76947 0.23 77033 -0.06 77119 0.01 77205 0.19 77308 0.27 77394 0.38 77511 0.04 77597 0.12 77683 0.25 77769 0.11 77855 0.32 77941 0.15 78027 0.12 78113 -0.09 78199 0.25 78318 0.17 78468 -0.16 78554 0.36 78640 0.30 78726 -0.13 78872 0.49 78970 0.33 79056 0.04 79142 0.08 79228 0.15 79314 -0.03 79400 -0.16 79486 -0.01 79573 0.58 79659 0.24 79745 0.36 79831 0.48 79947 0.20 80033 -0.10 80119 0.45 80205 0.32 80291 0.06 80377 0.23 80463 0.06 80549 0.18 80639 0.55 80725 0.92 80811 0.51 80897 -0.04 80983 0.23 81069 0.10 81159 0.28 81245 0.04 81335 0.49 81421 0.19 81507 0.07 81600 0.12 81707 0.25 81793 0.49 81879 0.05 81965 0.06 82051 -0.08 82137 -0.01 82223 0.08 82309 0.06 82403 0.38 82489 0.13 82575 -0.02 82661 0.02 82747 0.19 82833 -0.21 82919 0.08 83005 0.26 83111 0.37 83197 -0.02 83283 -0.41 83369 -0.05 83455 0.39 83541 0.15 83627 0.26 83713 0.19 83799 -0.31 83885 0.35 83997 0.04 84099 0.36 84185 -0.23 84271 0.06 84357 0.14 84443 0.29 84529 0.19 84615 -0.07 84701 -0.08 84787 0.35 84873 0.08 84959 0.35 85045 0.01 85131 -0.31 85217 -0.26 85303 0.05 85389 0.02 85475 -0.09 85561 -0.07 85647 0.19 85733 -0.16 85819 0.01 85905 0.09 85991 0.27 86077 0.59 86163 0.08 86249 0.34 86335 0.03 86421 0.04 86507 0.18 86593 0.02 86679 0.26 86765 0.53 86851 0.06 86952 0.05 87038 -0.07 87124 -0.37 87210 0.07 87330 0.23 87416 0.10 87502 0.27 87588 0.28 87674 -0.00 87760 0.23 87846 -0.07 87932 -0.05 88019 -0.27 88105 -0.08 88191 -0.19 88277 -0.11 88363 0.37 88449 0.42 88535 0.22 88621 -0.00 88707 -0.45 88793 0.13 88879 -0.03 88965 0.08 89051 -0.10 89137 0.44 89237 0.10 89323 0.08 89409 0.05 89495 0.05 89581 0.41 89768 0.48 89887 0.18 89973 0.24 90059 -0.01 90145 0.30 90237 0.26 90323 0.12 90409 0.16 90495 0.26 90581 0.32 90667 0.03 90753 -0.15 90839 0.07 90925 0.43 91012 0.28 91098 0.00 91184 -0.54 91273 -0.05 91359 0.08 91445 0.06 91531 0.34 91617 0.03 91703 0.14 91789 0.11 91875 0.25 91961 0.18 92047 0.32 92133 -0.08 92219 0.12 92305 0.04 92391 -0.04 92477 0.23 92563 0.29 92649 0.35 92735 0.26 92821 0.18 92907 0.09 92995 0.35 93081 0.60 93167 0.63 93253 0.25 93339 0.21 93425 0.50 93511 0.01 93597 0.19 93683 0.32 93769 0.26 93855 0.14 93941 0.31 94027 0.30 94113 0.40 94199 0.29 94285 0.50 94371 0.22 94457 0.32 94543 0.19 94629 -0.09 94715 0.07 94801 0.19 94887 -0.25 94973 -0.03 95059 0.17 95145 0.04 95231 0.44 95324 0.01 95410 -0.14 95496 -0.30 95582 0.10 95668 -0.33 95754 0.03 95863 -0.20 95949 0.20 96035 0.12 96121 -0.02 96207 -0.02 96293 0.41 96379 0.40 96466 0.10 96552 0.29 96638 -0.04 96726 0.77 96812 0.15 96898 0.10 96984 0.41 97070 0.30 97156 0.47 97242 0.13 97328 0.59 97415 0.24 97501 -0.11 97587 0.21 97673 0.05 97759 -0.07 97845 0.10 97931 -0.20 98017 0.15 98103 -0.27 98189 -0.20 98275 0.09 98361 0.17 98447 0.13 98533 0.60 98619 0.29 98705 0.16 98791 -0.11 98877 0.35 98963 0.14 99049 0.15 99135 0.52 99221 -0.00 99307 0.30 99393 0.05 99479 -0.07 99565 0.11 99651 0.15 99737 -0.01 99823 -0.04 99909 -0.30 99995 0.20 100081 -0.19 100167 0.33 100253 0.05 100339 0.27 100425 0.29 100511 0.43 100597 -0.04 100683 0.12 100769 0.16 100855 0.13 100941 0.01 101027 0.21 101149 0.50 101244 -0.15 101343 0.22 101429 -0.08 101515 0.21 101601 0.07 101687 0.32 101773 0.15 101859 -0.14 101945 -0.09 102031 0.01 102119 0.21 102205 -0.40 102313 0.26 102410 0.04 102496 0.22 102582 0.12 102668 0.57 102754 0.40 102840 0.22 102926 0.02 103012 0.21 103098 0.02 103184 0.26 103270 0.13 103356 0.05 103442 0.06 103528 -0.38 103614 -0.63 103700 -0.47 103786 0.26 103873 -0.05 103959 -0.05 104045 -1.38 104229 0.18 104316 0.18 104402 -0.02 104488 -0.03 104574 -0.23 104660 -0.08 104746 -0.11 104832 -0.25 104918 0.01 105004 0.01 105090 0.24 105176 0.20 105262 -0.08 105348 -0.23 105434 0.13 105520 0.16 105606 0.19 105692 0.18 105778 0.27 105865 0.14 105960 -0.15 106046 0.27 106132 0.27 106218 -0.29 106304 0.13 106390 0.29 106476 0.24 106562 0.38 106648 -0.04 106734 -0.03 106820 -0.02 106906 -0.14 106992 0.04 107078 0.04 107164 -0.09 107250 0.62 107336 0.05 107422 0.39 107508 -0.01 107594 0.28 107680 0.40 107766 0.31 107852 0.52 107938 0.36 108024 0.12 108110 0.46 108205 0.47 108291 0.31 108377 0.50 108463 0.43 108549 0.41 108635 0.36 108721 0.20 108807 -0.03 108893 0.28 108979 0.04 109065 -0.52 109151 0.06 109237 0.08 109323 0.22 109409 0.47 109495 0.25 109581 -0.01 109667 0.14 109753 -0.15 109839 -0.05 109925 0.14 110011 0.32 110097 0.06 110183 0.12 110269 -0.03 110355 0.36 110441 0.21 110527 0.70 110613 0.54 110699 0.63 110788 0.79 110874 0.63 110960 0.51 111046 0.31 111132 0.37 111218 0.05 111304 0.13 111390 0.30 111476 0.16 111562 0.36 111648 0.51 111734 -0.06 111820 0.09 111906 0.25 111992 0.05 112078 0.14 112164 -0.03 112250 0.23 112336 0.35 112422 0.08 112508 0.09 112594 -0.03 112680 0.14 112766 -0.06 112852 -0.27 112938 0.61 113024 -0.13 113110 0.41 113196 0.33 113282 -0.11 113368 0.07 113454 0.14 113540 -0.07 113626 -0.07 113712 0.20 113798 0.08 113884 0.46 113970 0.21 114056 0.07 114142 -0.08 114228 0.02 114314 -0.06 114400 0.23 114486 0.63 114572 -0.03 114658 0.06 114744 0.06 114830 0.25 114916 -0.59 115002 0.02 115088 -0.17 115174 0.14 115260 -0.04 115346 0.14 115446 0.36 115532 -0.03 115618 0.23 115704 -0.00 115790 0.17 115876 -0.06 115963 0.14 116049 -0.05 116135 0.31 116221 0.08 116307 0.04 116393 0.29 116479 0.07 116565 0.16 116651 0.38 116737 0.11 116823 0.32 116909 0.56 116995 0.49 117081 0.05 117167 0.73 117253 0.45 117339 0.52 117425 0.12 117511 0.19 117597 -0.11 117683 -0.01 117769 -0.14 117855 -0.37 117941 -0.04 118027 -0.59 118113 -0.05 118199 -0.25 118285 -0.11 118371 0.18 118457 0.00 118543 -0.17 118629 0.25 118715 -0.37 118801 0.09 118887 0.20 118973 0.01 119059 0.08 119145 -0.07 119231 0.47 119317 0.23 119403 0.21 119489 -0.06 119575 0.03 119661 0.21 119747 -0.14 119833 0.67 119919 0.12 120005 0.01 120091 -0.09 120177 0.04 120263 0.12 120349 -0.07 120435 0.02 120521 0.22 120607 0.21 120693 0.34 120779 0.37 120865 0.44 120951 0.38 121037 0.06 121123 -0.10 121209 -0.18 121295 0.03 121381 -0.50 121467 0.22 121553 0.42 121639 0.08 121725 0.31 121811 -0.04 121897 -0.04 121983 0.07 122069 0.05 122155 0.21 122241 0.19 122327 -0.21 122413 1.21 122499 0.36 122585 0.05 122671 0.05 122757 0.16 122843 -0.23 122932 0.41 123018 -0.05 123104 0.36 123190 0.06 123276 -0.15 123362 -0.14 123448 0.02 123534 0.32 123620 0.12 123706 0.01 123792 0.24 123878 0.09 123964 0.28 124050 0.35 124136 0.12 124222 -0.05 124308 0.20 124394 -0.10 124480 0.01 124566 0.12 124652 0.49 124738 0.16 124824 0.24 124910 0.25 124998 0.07 125084 0.21 125170 0.11 125256 0.21 125342 -0.15 125428 0.39 125514 -0.08 125600 0.05 125686 -0.01 125772 0.04 125858 -0.25 125944 -0.34 126030 -0.19 126123 -0.32 126209 0.10 126310 0.42 126396 -0.52 126482 0.38 126568 0.13 126654 -0.15 126740 -0.41 126826 -0.29 126912 -0.39 126998 -0.22 127084 0.15 127170 0.01 127256 -0.13 127356 0.12 127442 -0.31 127528 -0.02 127614 -0.16 127700 0.26 127786 -0.08 127872 0.03 127958 0.38 128044 0.36 128130 0.22 128216 0.46 128302 0.18 128388 0.24 128474 0.51 128560 0.26 128646 0.15 128732 0.13 128818 0.27 128904 0.35 128990 0.22 129076 0.21 129162 0.35 129248 0.41 129334 0.31 129420 0.09 129506 0.04 129592 0.36 129678 0.23 129764 0.04 129850 -0.02 129936 0.16 130022 -0.15 130108 0.16 130194 0.41 130280 0.14 130366 0.38 130452 0.41 130538 0.35 130624 0.43 130710 0.54 130796 0.55 130882 0.37 130968 0.59 131054 0.21 131140 -0.00 131226 0.03 131312 0.05 131398 0.18 131484 0.04 131570 -0.03 131656 0.14 131742 0.03 131828 -0.01 131914 0.05 132000 0.04 132086 0.02 132172 0.25 132258 0.54 132344 0.48 132430 0.28 132516 0.32 132602 0.59 132688 0.59 132809 0.32 132895 -0.15 132981 0.42 133081 0.70 133170 0.36 133256 0.09 133342 0.36 133428 0.03 133514 0.23 133600 0.26 133686 0.15 133772 -0.06 133858 0.17 133944 -0.27 134030 -0.05 134116 0.01 134202 -0.14 134288 -0.03 134374 0.08 134460 0.31 134546 0.31 134632 0.22 134718 -0.18 134804 0.21 134890 0.28 134976 -0.04 135062 0.21 135148 0.11 135234 0.21 135320 0.06 135406 0.10 135492 -0.11 135578 0.17 135664 -0.21 135750 0.25 135836 0.03 135922 0.47 136008 0.22 136094 0.07 136180 0.41 136266 0.26 136352 0.09 136438 0.14 136524 0.44 136610 -0.07 136696 0.25 136782 0.92 136868 0.59 136954 0.41 137040 0.15 137126 -0.09 137212 -0.08 137298 0.31 137384 0.32 137470 -0.07 137556 0.15 137642 -0.10 137728 0.12 137814 0.26 137900 0.13 137986 -0.09 138072 0.12 138158 0.38 138244 -0.24 138330 0.22 138416 0.14 138502 -0.08 138588 0.10 138674 0.01 138760 0.02 138846 0.10 138932 -0.16 139018 0.21 139104 -0.08 139190 -0.13 139276 -0.37 139362 -0.08 139448 0.18 139534 0.13 139620 -0.01 139706 -0.17 139792 -0.24 139878 -0.06 139964 0.03 140050 0.03 140136 0.18 140222 -0.03 140308 -0.11 140394 -0.16 140480 0.29 140566 -0.34 140652 -0.44 140738 -0.03 140824 -0.06 140910 -0.29 140996 -0.17 141082 -0.04 141168 0.05 141254 -0.05 141340 -0.17 141426 0.13 141512 0.14 141598 0.11 141684 0.08 141770 -0.39 141856 0.12 141942 -0.19 142028 -0.21 142114 0.20 142201 -0.34 142287 -0.14 142373 -0.01 142459 -0.18 142545 -0.10 142631 -0.10 142717 -0.56 142803 -0.09 142889 -0.19 142975 -0.36 143061 -0.02 143147 -0.39 143233 -0.42 143319 -0.57 143405 -0.49 143491 -0.14 143577 -0.00 143663 0.02 143749 -0.04 143836 0.03 143922 -0.18 144008 0.12 144094 0.24 144180 -0.18 144266 -0.13 144352 -0.18 144438 -0.38 144524 -0.20 144610 0.19 144696 -0.15 144782 -0.17 144868 -0.08 144954 -0.21 145040 -0.29 145141 0.01 145227 -0.18 145313 0.34 145399 -0.06 145485 0.24 145571 0.39 145657 -0.00 145743 -0.20 145829 -0.01 145915 -0.33 146001 -0.05 146087 -0.06 146174 0.08 146260 0.02 146346 -0.21 146432 0.12 146518 -0.12 146604 -0.06 146690 -0.36 146776 -0.27 146862 -0.07 146948 -0.18 147034 -0.41 147120 -0.25 147206 0.07 147292 -0.30 147378 -0.34 147464 -0.17 147550 0.02 147636 -0.28 147722 -0.27 147808 -0.12 147894 -0.52 147980 -0.29 148066 -0.35 148152 -0.06 148238 -0.16 148324 -0.34 148410 -0.01 148496 0.24 148582 -0.07 148668 0.29 148754 -0.08 148840 -0.35 148926 0.13 149012 -0.14 149098 -0.08 149184 -0.11 149270 0.18 149356 -0.19 149442 -0.39 149528 0.03 149614 -0.41 149700 0.09 149786 -0.25 149872 -0.04 149958 -0.09 150044 -0.07 150130 -0.00 150216 -0.29 150302 0.05 150388 -0.35 150474 -0.39 150560 -0.20 150646 -0.23 150732 -0.62 150818 -0.15 150904 -0.08 150990 -0.10 151076 -0.12 151162 -0.38 151248 -0.18 151334 -0.42 151420 -0.38 151506 -0.22 151592 0.05 151678 -0.15 151764 -0.02 151850 0.12 151936 0.12 152022 0.56 152108 -0.16 152194 -0.03 152280 0.01 152366 -0.30 152452 -0.03 152538 0.05 152624 0.05 152710 -0.33 152796 -0.14 152882 0.03 152968 0.28 153054 -0.38 153140 0.15 153226 0.07 153312 -0.02 153398 -0.20 153484 0.12 153570 -0.11 153656 -0.06 153742 0.29 153828 0.14 153914 0.03 154000 0.26 154086 0.03 154172 -0.02 154258 -0.25 154344 -0.27 154430 0.15 154516 -0.00 154602 -0.46 154688 -0.06 154774 0.28 154860 -0.15 154946 -0.19 155032 -0.05 155118 0.09 155222 0.63 155308 0.32 155394 0.03 155535 0.68 155621 -0.18 155707 0.25 155793 -0.03 155879 0.01 155965 -0.01 156051 0.18 156137 0.14 156223 0.34 156309 0.14 156395 0.25 156481 0.20 156567 0.40 156653 -0.22 156739 -0.21 156825 -0.14 156911 0.24 156997 -0.05 157083 -0.07 157169 0.10 157255 0.56 157341 0.43 157438 0.33 157524 0.30 157611 0.20 157697 0.19 157783 0.14 157869 -0.02 157955 -0.30 158041 0.02 158130 -0.14 158216 0.92 158302 0.23 158388 -0.05 158474 0.04 158560 0.06 158646 0.18 158732 -0.28 158821 0.34 158907 -0.06 158993 0.05 159103 -0.12 159189 -0.22 159275 0.18 159361 0.17 159447 0.30 159533 0.20 159619 0.00 159714 0.25 159800 0.18 159886 0.04 159972 -0.05 160062 -0.00 160148 0.18 160234 0.50 160320 0.35 160406 0.03 160492 0.16 160578 0.10 160664 0.33 160750 0.14 160836 -0.05 160922 -0.21 161025 0.14 161127 0.20 161216 -0.34 161302 0.43 161388 0.32 161474 0.04 161560 -0.08 161646 -0.29 161732 -0.19 161818 0.26 161904 0.12 161990 0.10 162076 0.12 162162 0.19 162248 -0.00 162334 -0.05 162420 0.09 162506 0.44 162592 -0.04 162678 -0.11 162764 -0.04 162851 0.46 162937 0.27 163028 0.34 163114 0.27 163200 0.47 163286 0.15 163372 -0.21 163458 -0.11 163544 0.00 163630 -0.16 163716 0.05 163802 -0.04 163888 0.10 163974 -0.22 164060 -0.29 164146 -0.43 164232 -0.19 164318 0.20 164404 -0.07 164490 -0.10 164576 -0.29 164662 -0.04 164748 -0.11 164834 -0.04 164920 0.10 165006 0.06 165092 -0.35 165178 0.08 165264 -0.05 165350 -0.10 165436 -0.35 165522 -0.02 165608 0.05 165694 -0.03 165783 0.28 165869 -0.38 165955 -0.34 166041 -0.16 166127 0.17 166213 0.00 166299 0.16 166385 -0.08 166471 0.12 166557 0.72 166651 0.25 166737 0.24 166823 -0.15 166909 0.14 166995 0.04 167081 0.31 167167 0.14 167253 -0.29 167339 -0.18 167425 -0.13 167511 -0.18 167597 -0.22 167683 -0.05 167769 0.22 167855 0.16 167941 0.34 168027 0.34 168113 0.18 168199 0.22 168295 0.17 168388 0.16 168477 0.35 168563 -0.05 168649 0.36 168735 0.13 168821 0.35 168907 -0.03 168993 0.18 169079 0.25 169165 0.09 169251 0.14 169337 0.30 169423 0.20 169509 0.15 169595 0.25 169681 -0.40 169767 0.03 169853 0.04 169939 0.08 170025 0.13 170111 0.15 170197 -0.24 170283 -0.11 170369 -0.09 170455 -0.03 170547 -0.12 170633 0.07 170719 0.17 170805 0.06 170891 0.01 170977 0.27 171063 -0.18 171149 -0.18 171235 -0.07 171321 0.00 171407 0.20 171493 -0.12 171579 -0.14 171665 0.07 171751 0.09 171837 -0.17 171923 -0.31 172009 0.14 172095 0.09 172181 0.61 172280 0.38 172366 -0.02 172452 0.22 172538 0.08 172624 -0.04 172710 -0.02 172796 0.12 172882 0.07 172968 0.09 173054 0.01 173140 0.41 173226 0.32 173312 0.33 173398 0.26 173484 0.10 173570 0.27 173656 0.43 173742 -0.27 173828 0.46 173914 0.25 174000 0.31 174086 0.39 174172 0.24 174258 -0.04 174344 0.23 174430 0.41 174516 0.15 174602 -0.26 174688 0.35 174774 -0.02 174860 -0.26 174946 0.23 175032 0.29 175118 0.14 175204 -0.07 175290 -0.08 175376 -0.26 175462 -0.12 175548 0.01 175634 0.10 175720 -0.16 175806 -0.09 175892 0.01 175978 0.28 176064 0.05 176150 0.10 176236 -0.09 176322 -0.11 176408 0.59 176495 0.17 176581 0.11 176667 -0.18 176753 0.22 176839 -0.06 176925 -0.13 177011 -0.05 177097 -0.03 177183 0.16 177269 -0.09 177355 0.24 177441 0.05 177527 -0.12 177617 -0.01 177703 0.15 177789 0.18 177875 0.00 177961 0.08 178047 0.16 178133 0.19 178219 0.15 178305 0.08 178391 0.09 178477 0.11 178563 -0.09 178649 0.20 178765 0.00 178851 0.39 178937 0.41 179023 0.32 179109 0.48 179195 0.08 179281 0.14 179367 0.35 179453 0.63 179539 0.17 179625 0.23 179711 0.14 179797 -0.00 179883 0.05 179971 0.17 180057 -0.11 180143 0.01 180229 0.38 180315 0.02 180401 0.06 180493 0.33 180579 0.20 180665 0.02 180751 0.06 180837 0.05 180923 0.08 181009 0.24 181095 0.28 181185 0.46 181271 0.18 181357 0.24 181443 0.18 181531 -0.12 181620 0.50 181706 0.10 181792 -0.00 181878 -0.02 181964 -0.34 182050 -0.01 182136 -0.04 182222 -0.12 182308 0.06 182394 -0.02 182480 0.06 182566 0.17 182652 0.04 182738 0.20 182824 0.22 182910 0.52 182996 0.20 183082 0.30 183168 0.17 183254 0.00 183340 0.10 183426 0.14 183512 0.18 183598 0.01 183684 -0.04 183770 -0.08 183856 -0.22 183942 0.07 184028 0.61 184114 -0.17 184200 0.24 184286 -0.08 184372 0.74 184458 0.27 184573 -0.32 184659 0.41 184756 0.35 184842 0.36 184928 0.30 185014 0.58 185100 0.22 185186 0.13 185272 0.32 185358 0.30 185446 -0.01 185536 0.28 185622 0.32 185708 0.10 185794 0.16 185880 0.15 185966 0.14 186052 0.08 186138 -0.02 186224 -0.27 186310 -0.08 186396 0.02 186482 -0.02 186568 0.06 186654 0.15 186740 -0.26 186826 -0.25 186912 -0.26 186998 -0.26 187084 -0.62 187170 -0.38 187256 0.06 187342 -0.29 187428 0.08 187514 0.07 187600 0.28 187686 -0.08 187772 0.10 187858 0.18 187944 -0.18 188030 -0.10 188116 0.09 188202 0.07 188302 -0.03 188388 -0.08 188474 0.22 188560 0.10 188646 -0.02 188732 0.41 188819 0.38 188915 -0.28 189001 -0.52 189087 -0.17 189173 -0.20 189259 0.03 189354 0.10 189440 0.16 189549 0.01 189635 -0.08 189721 0.14 189807 0.19 189893 0.37 189979 0.34 190065 0.32 190151 0.19 190237 -0.03 190323 0.15 190409 -0.08 190495 0.10 190581 0.17 190667 -0.04 190753 0.30 190839 0.16 190925 0.10 191011 0.12 191097 0.30 191183 0.27 191269 0.20 191355 0.31 191441 0.18 191527 -0.31 191613 -0.19 191699 0.02 191785 -0.36 191871 -0.13 191957 -0.27 192043 0.26 192129 0.06 192215 0.15 192301 0.38 192387 0.38 192473 0.22 192559 0.03 192652 -0.05 192738 0.07 192824 -0.01 192910 -0.06 192996 0.14 193082 -0.07 193168 -0.14 193281 0.59 193367 0.07 193537 -0.35 193623 -0.03 193709 -0.00 193795 0.06 193881 -0.11 193967 -0.19 194053 0.11 194139 -0.06 194225 0.26 194311 0.12 194400 0.05 194486 0.08 194572 -0.23 194658 0.46 194744 0.19 194830 0.18 194916 0.04 195002 0.27 195088 -0.02 195174 0.16 195260 0.50 195346 -0.04 195432 -0.12 195518 0.04 195604 -0.54 195692 -0.02 195814 -0.56 195900 0.11 196005 0.63 196091 0.19 196177 -0.04 196263 -0.08 196349 0.08 196435 0.18 196521 0.04 196619 -0.08 196705 0.38 196791 0.21 196885 -0.08 196971 0.98 197057 0.87 197143 0.56 197229 -0.11 197315 0.35 197401 0.38 197487 0.13 197573 0.33 197659 -0.03 197745 0.13 197831 -0.52 197917 -0.25 198003 -0.30 198089 -0.21 198175 0.27 198262 0.21 198348 0.23 198434 0.19 198520 0.05 198606 0.19 198692 0.17 198778 0.22 198864 0.44 198960 1.01 199046 0.05 199132 -0.09 199218 0.04 199304 -0.14 199390 0.59 199476 0.14 199562 -0.09 199648 0.36 199734 0.11 199820 0.04 199906 0.05 199992 0.01 200078 0.11 200164 0.28 200250 0.04 200336 0.00 200422 0.11 200508 0.27 200594 0.07 200680 -0.02 200766 0.70 200852 0.06 200942 0.09 201030 0.54 201116 0.47 201202 -0.08 201288 -0.11 201374 -0.12 201460 -0.46 201546 -0.03 201632 0.07 201774 0.38 201860 -0.32 201962 0.19 202051 0.24 202137 0.22 202223 -0.03 202309 -0.22 202395 0.45 202481 0.47 202567 0.12 202653 0.15 202739 -0.06 202825 0.31 202911 -0.24 202997 -0.42 203083 0.20 203169 -0.14 203255 -0.23 203341 0.46 203427 -0.16 203553 0.20 203710 0.04 203796 -0.10 203882 0.05 203970 0.08 204056 0.16 204144 0.05 204230 -0.01 204316 -0.35 204402 -0.04 204488 -0.31 204574 -0.26 204660 0.09 204746 0.07 204832 0.12 204918 0.64 205004 0.19 205090 0.26 205176 -0.11 205262 0.03 205348 -0.23 205434 0.34 205520 -0.04 205606 -0.03 205692 0.25 205778 0.37 205864 0.59 205950 0.38 206036 0.19 206122 0.63 206208 0.90 206294 0.88 206380 0.20 206466 0.04 206552 0.13 206646 0.23 206732 0.08 206818 0.24 206904 -0.05 206993 0.52 207079 0.12 207165 0.09 207251 0.17 207337 0.12 207423 0.14 207510 0.27 207596 0.15 207682 -0.13 207768 0.13 207854 0.18 207940 0.33 208026 0.32 208137 0.42 208223 0.01 208309 0.23 208395 -0.20 208481 0.29 208581 0.28 208667 -0.03 208753 -0.23 208847 0.15 208933 0.11 209019 0.25 209110 0.14 209196 0.06 209282 0.01 209368 0.21 209454 0.03 209540 0.00 209626 0.10 209712 0.14 209798 0.04 209884 -0.12 209970 -0.10 210056 -0.29 210142 -0.13 210228 -0.09 210314 0.15 210403 -0.03 210489 0.09 210575 -0.33 210661 -0.17 210747 0.30 210833 0.27 210919 0.48 211006 0.16 211092 0.31 211178 0.24 211264 0.08 211350 0.52 211436 0.27 211522 -0.24 211608 -0.05 211694 0.21 211781 -0.29 211867 0.11 211953 0.16 212039 0.12 212125 0.22 212211 0.15 212297 0.23 212383 0.26 212469 -0.01 212555 0.07 212643 -0.17 212729 0.29 212815 0.23 212901 -0.14 212988 -0.03 213074 0.86 213160 -0.10 213246 -0.01 213332 0.14 213448 -0.11 213534 -0.16 213620 0.01 213706 0.20 213792 0.23 213878 0.23 213964 0.06 214050 0.11 214136 -0.17 214224 0.15 214310 0.01 214396 0.20 214482 -0.12 214577 0.18 214663 0.29 214749 0.53 214835 0.54 214921 0.28 215007 0.58 215093 0.45 215179 0.43 215265 0.20 215351 0.12 215437 0.09 215523 -0.03 215609 0.07 215695 0.50 215781 0.00 215867 0.33 215953 0.41 216039 0.11 216125 -0.08 216211 0.04 216297 0.09 216383 0.44 216469 0.22 216555 0.48 216641 0.27 216727 0.27 216813 0.17 216899 0.07 216985 -0.11 217071 -0.16 217157 -0.20 217243 -0.21 217329 -0.07 217415 0.04 217501 -0.01 217587 -0.06 217673 -0.10 217759 -0.05 217845 0.04 217931 0.00 218029 0.13 218115 -0.46 218201 0.21 218324 0.45 218410 0.30 218496 0.30 218605 0.86 218723 0.12 218809 0.09 218895 0.11 218981 -0.03 219072 0.00 219158 0.14 219244 0.11 219330 0.50 219416 0.23 219502 0.34 219588 0.45 219679 0.46 219765 -0.24 219851 -0.17 219937 -0.03 220023 0.52 220109 0.25 220195 -0.15 220281 0.20 220367 0.27 220453 0.01 220539 -0.11 220625 0.14 220711 0.63 220808 0.25 220894 0.02 220980 0.36 221066 0.11 221152 0.52 221238 0.42 221324 0.38 221410 0.06 221496 0.41 221582 0.24 221668 0.08 221754 -0.08 221840 0.05 221926 0.10 222012 0.13 222098 0.29 222184 -0.14 222270 0.02 222356 -0.10 222442 -0.16 222528 -0.07 222614 -0.01 222700 -0.49 222786 0.46 222875 0.06 222961 0.06 223047 0.40 223133 0.38 223219 0.39 223305 0.08 223391 0.20 223477 0.21 223563 0.15 223649 0.15 223735 0.07 223821 0.34 223907 0.47 223993 -0.04 224079 -0.49 224165 -0.25 224251 0.11 224337 -0.43 224423 0.01 224509 -0.34 224595 0.14 224681 0.05 224767 0.06 224853 0.00 224939 0.13 225031 -0.07 225117 -0.40 225203 -0.32 225289 -0.20 225375 0.14 225461 -0.22 225547 -0.18 225633 -0.14 225719 -0.09 225805 -0.32 225891 -0.03 225977 -0.42 226063 -0.37 226149 -0.01 226235 -0.32 226321 -0.15 226407 -0.34 226493 -0.37 226579 -0.27 226665 0.11 226751 -0.09 226837 0.27 226923 0.13 227009 -0.05 227095 0.28 227181 0.39 227267 0.35 227353 -0.11 227439 -0.08 227525 0.10 227611 -0.10 227697 0.09 227783 -0.04 227869 0.19 227955 -0.04 228041 0.43 228127 0.33 228213 0.08 228299 -0.09 228385 -0.24 228471 -0.23 228557 0.08 228643 -0.14 228729 -0.05 228815 -0.00 228901 0.13 228987 0.38 229073 -0.07 229159 -0.07 229245 -0.29 229331 -0.30 229417 -0.34 229503 0.11 229589 -0.06 229675 -0.14 229761 -0.35 229847 -0.37 229933 -0.47 230019 -0.14 230105 -0.28 230191 -0.15 230277 -0.04 230363 0.09 230449 0.07 230535 -0.03 230621 0.03 230707 0.11 230793 -0.06 230879 -0.24 230965 -0.01 231052 0.02 231147 0.15 231255 0.23 231341 -0.16 231427 -0.04 231513 0.05 231599 0.05 231685 -0.05 231771 0.43 231857 0.24 231943 0.21 232029 0.00 232115 0.43 232201 0.07 232291 0.21 232377 0.30 232463 0.18 232549 0.51 232635 -0.03 232721 0.37 232807 0.07 232893 0.10 232979 0.44 233065 -0.02 233151 0.29 233237 0.24 233323 -0.07 233409 0.03 233495 -0.01 233581 0.24 233667 -0.19 233753 0.11 233842 -0.14 233928 0.10 234014 -0.03 234100 0.18 234186 0.52 234272 0.26 234358 0.04 234444 0.08 234530 0.10 234616 0.26 234702 -0.25 234788 0.01 234874 0.36 234960 0.18 235046 -0.21 235132 0.17 235218 -0.10 235304 0.07 235390 -0.21 235476 -0.27 235562 -0.12 235648 -0.36 235734 -0.20 235820 0.01 235906 -0.16 235992 -0.05 236078 0.18 236164 0.25 236250 0.14 236336 -0.13 236422 0.09 236508 0.01 236594 0.11 236680 -0.50 236766 -0.05 236852 -0.20 236938 0.09 237024 -0.00 237110 0.28 237196 0.04 237282 0.13 237368 0.08 237454 0.10 237540 -0.07 237647 0.46 237733 0.30 237819 0.15 237905 0.13 237991 0.35 238077 0.19 238163 0.27 238249 0.25 238335 0.30 238421 0.07 238507 -0.06 238593 -0.02 238679 0.34 238765 0.37 238851 0.06 238937 0.13 239023 0.12 239109 0.19 239195 0.06 239281 0.23 239367 -0.25 239453 0.41 239539 0.13 239625 0.13 239711 0.04 239797 -0.09 239883 0.04 239969 0.32 240055 1.00 240141 0.07 240227 0.18 240313 -0.02 240399 0.16 240485 0.00 240571 -0.15 240657 -0.09 240743 -0.14 240829 -0.01 240915 -0.24 241001 -0.05 241087 -0.02 241173 -0.08 241259 -0.05 241345 -0.05 241431 -0.26 241517 -0.06 241603 0.45 241689 0.01 241775 0.02 241861 -0.58 241947 0.09 242033 -0.28 242119 -0.05 242205 0.06 242291 -0.18 242377 -0.08 242463 -0.11 242549 -0.07 242635 0.25 242722 -0.01 242808 -0.20 242894 -0.08 242980 -0.11 243066 0.35 243152 -0.22 243238 -0.30 243324 -0.24 243410 -0.09 243496 -0.18 243582 -0.19 243668 -0.06 243754 0.11 243840 -0.25 243926 0.01 244012 -0.02 244098 -0.10 244184 -0.37 244270 -0.18 244377 0.10 244463 -0.07 244549 0.43 244635 -0.42 244721 0.05 244807 0.14 244893 -0.25 244979 0.14 245065 -0.17 245151 -0.21 245237 -0.13 245323 -0.09 245409 -0.32 245495 0.27 245581 0.42 245667 -0.04 245753 -0.20 245839 0.23 245925 0.10 246011 0.24 246097 0.30 246183 0.65 246269 0.06 246355 0.07 246441 0.08 246542 0.03 246628 0.06 246714 0.35 246800 -0.08 246886 0.09 246972 -0.37 247058 0.06 247144 0.26 247230 -0.14 247316 0.18 247402 -0.19 247488 -0.17 247574 -0.06 247660 0.26 247746 0.05 247832 -0.12 247918 0.35 248004 0.06 248090 0.23 248176 0.28 248262 -0.21 248348 -0.08 248434 0.04 248520 0.10 248606 -0.11 248692 -0.03 248778 0.12 248864 -0.17 248950 0.08 249036 -0.01 249122 0.01 249208 -0.06 249294 0.35 249380 0.42 249466 0.02 249552 0.14 249638 0.35 249724 -0.04 249810 -0.14 249896 -0.07 249982 0.14 250068 0.51 250154 -0.03 250264 0.29 250381 0.06 250467 0.04 250553 -0.05 250639 -0.02 250725 -0.45 250811 0.07 250897 0.37 250983 0.43 251069 0.47 251155 0.63 251241 0.21 251327 0.03 251417 0.05 251503 0.33 251589 0.42 251675 0.37 251761 0.15 251858 0.20 251944 0.03 252030 0.35 252116 0.12 252202 0.10 252288 -0.02 252380 0.06 252466 0.10 252552 0.33 252638 0.28 252724 0.19 252810 0.10 252896 0.06 252982 0.39 253070 0.45 253161 -0.07 253247 0.28 253333 0.35 253419 0.28 253519 0.62 253663 0.40 253749 -0.04 253835 0.68 253921 0.30 254007 0.06 254093 -0.02 254179 0.15 254265 -0.12 254351 -0.16 254437 0.09 254523 -0.36 254609 0.69 254695 -0.21 254781 0.17 254867 0.13 254953 -0.17 255039 0.32 255125 0.14 255211 -0.18 255297 0.29 255383 0.17 255469 -0.06 255556 -0.14 255642 0.05 255728 0.03 255814 0.20 255900 -0.04 255986 0.24 256072 -0.02 256180 0.09 256266 0.26 256352 0.03 256438 -0.03 256524 -0.25 256610 0.39 256696 0.12 256782 0.02 256868 0.06 256954 0.61 257040 0.29 257126 0.37 257212 -0.06 257298 0.51 257384 -0.30 257470 0.63 257556 -0.04 257642 0.22 257728 0.14 257814 0.24 257900 0.19 257986 0.27 258072 0.26 258158 0.41 258244 0.07 258330 -0.04 258416 -0.03 258502 0.08 258588 -0.01 258674 -0.01 258760 0.08 258846 0.28 258932 0.00 259018 0.07 259104 -0.24 259190 0.07 259276 -0.07 259362 -0.15 259448 -0.10 259534 -0.15 259620 -0.18 259706 0.14 259792 -0.14 259878 0.07 259964 0.19 260050 0.30 260136 0.18 260222 0.16 260308 0.32 260394 0.33 260480 0.09 260566 0.34 260652 -0.01 260738 -0.11 260824 -0.17 260910 0.03 260996 -0.31 261082 -0.04 261168 0.03 261254 -0.02 261340 -0.04 261426 0.17 261512 0.07 261598 -0.20 261684 0.04 261770 0.22 261856 0.01 261942 0.28 262033 0.19 262119 -0.05 262205 -0.14 262291 -0.12 262377 -0.00 262463 -0.23 262549 -0.10 262635 0.08 262721 0.03 262807 0.14 262893 0.08 262979 0.12 263065 0.02 263151 0.22 263237 -0.18 263323 -0.03 263409 -0.30 263495 -0.27 263581 0.33 263667 -0.10 263753 -0.17 263839 -0.02 263925 0.07 264011 -0.13 264097 0.23 264183 -0.11 264316 0.16 264402 0.09 264488 0.02 264574 0.09 264660 0.14 264746 0.00 264832 -0.31 264918 -0.22 265004 -0.17 265090 -0.21 265176 -0.04 265262 -0.08 265348 -0.25 265434 0.01 265520 0.00 265606 0.54 265692 0.32 265778 0.22 265864 0.49 265950 -0.02 266036 0.09 266122 0.11 266208 0.01 266294 -0.15 266380 0.12 266466 -0.12 266552 -0.38 266638 0.14 266724 -0.46 266810 -0.13 266896 -0.16 266982 0.05 267068 0.29 267154 0.47 267240 0.08 267326 0.02 267412 0.11 267498 0.32 267584 0.27 267670 0.15 267756 0.15 267842 0.21 267928 0.09 268014 0.10 268100 0.02 268186 -0.10 268272 0.41 268358 -0.03 268515 -0.07 268601 0.03 268687 -0.17 268773 0.01 268859 -0.00 268945 0.06 269031 0.20 269117 0.09 269213 0.09 269299 -0.18 269385 0.04 269471 -0.26 269557 -0.19 269643 0.30 269729 0.45 269815 0.02 269901 0.11 270006 0.10 270092 0.14 270178 -0.52 270264 -0.27 270350 0.06 270436 -0.34 270522 -0.25 270619 -0.29 270716 0.24 270802 0.18 270929 -0.16 271015 -0.14 271101 -0.25 271187 -0.07 271273 0.06 271359 0.49 271461 -0.95 271547 -0.10 271633 0.07 271719 -0.12 271805 -0.03 271891 0.20 271977 -0.04 272063 0.57 272159 -0.30 272245 -0.47 272331 -0.27 272417 -0.17 272503 -0.08 272589 -0.17 272675 -0.38 272761 -0.03 272847 0.23 272933 0.12 273023 0.26 273109 -0.00 273195 0.12 273281 0.12 273367 0.20 273453 -0.04 273539 -0.30 273625 -0.16 273711 0.05 273797 -0.06 273883 0.04 273969 -0.11 274055 -0.13 274141 -0.03 274227 -0.15 274313 0.09 274399 -0.24 274485 -0.02 274571 0.20 274657 -0.23 274743 0.28 274829 0.56 274915 0.46 275004 0.23 275090 0.16 275176 0.13 275262 0.15 275350 0.01 275436 0.06 275522 0.35 275608 -0.04 275694 0.29 275780 -0.21 275866 0.17 275952 -0.20 276038 0.26 276126 0.19 276212 0.38 276299 0.11 276385 0.09 276471 0.34 276557 0.19 276643 -0.22 276729 -0.00 276815 -0.00 276901 0.19 276987 0.22 277073 -0.00 277159 0.50 277245 0.25 277331 0.28 277424 0.39 277510 0.03 277596 0.08 277682 -0.15 277768 0.01 277854 -0.01 277940 -0.21 278026 -0.02 278115 -0.19 278201 0.31 278287 0.30 278376 0.19 278462 0.14 278548 0.14 278688 -0.24 278774 0.39 278917 0.24 279003 -0.01 279089 0.04 279175 0.29 279261 0.25 279347 0.26 279433 0.12 279519 0.07 279609 0.09 279695 0.24 279781 0.13 279867 0.35 279953 0.15 280039 0.40 280125 0.33 280211 0.40 280297 0.43 280383 0.21 280469 0.11 280655 -0.03 280741 0.60 281070 0.30 281259 0.02 281345 0.92 281431 0.46 281517 0.58 281603 1.29 281689 0.75 281775 0.39 281861 0.59 281947 0.32 282033 0.75 282119 0.52 282205 0.59 282291 0.37 282377 0.17 282463 0.29 282549 0.33 282635 -0.17 282721 0.04 282807 -0.06 282893 -0.39 282979 0.32 283065 0.12 283151 -0.15 283237 0.08 283330 -0.10 283416 -0.16 283502 0.07 283588 0.16 283703 -0.07 283789 0.07 283875 0.14 283961 -0.08 284047 0.09 284133 -0.04 284219 0.77 284305 -0.08 284391 -0.08 284477 0.05 284563 -0.07 284649 0.11 284735 0.18 284821 0.20 284907 0.00 284993 0.18 285079 0.41 285165 0.21 285251 0.07 285337 0.27 285423 -0.12 285509 0.22 285595 0.22 285681 0.08 285803 0.07 285889 -0.01 285975 0.53 286061 -0.13 286147 0.02 286233 -0.19 286323 -0.08 286409 0.08 286495 0.35 286581 -0.10 286667 0.02 286753 0.04 286839 -0.46 286925 -0.09 287011 -0.02 287097 -0.10 287194 0.30 287280 0.29 287419 0.29 287505 -0.11 287591 -0.08 287709 0.16 287795 -0.18 287881 0.04 287967 0.08 288053 0.36 288139 -0.05 288225 0.22 288311 -1.34 288397 0.48 288483 0.17 288569 0.30 288655 0.21 288741 0.16 288827 0.41 288925 0.41 289011 0.37 289097 0.18 289183 0.39 289269 0.62 289355 0.67 289441 0.29 289527 0.25 289613 0.19 289699 -0.06 289785 -0.10 289871 -0.08 289957 -0.41 290043 -0.02 290129 -0.24 290215 0.13 290301 -0.11 290387 -0.10 290473 0.01 290560 0.18 290646 -0.22 290732 -0.17 290818 0.08 290904 0.02 290990 0.01 291085 0.30 291171 -0.35 291257 -0.20 291343 0.22 291429 -0.13 291515 0.07 291607 0.38 291693 0.20 291779 -0.23 291865 -0.21 291951 -0.12 292037 -0.07 292123 -0.06 292209 0.49 292295 0.15 292381 0.02 292467 0.16 292553 -0.13 292639 0.29 292725 -0.06 292811 -0.17 292897 -0.00 292983 0.04 293069 0.35 293155 0.19 293241 0.42 293327 0.21 293413 -0.43 293499 0.01 293585 -0.03 293671 0.29 293757 -0.04 293848 -0.05 293934 -0.24 294020 -0.06 294106 -0.13 294192 -0.09 294285 0.55 294371 -0.20 294457 0.03 294543 -0.51 294629 0.26 294715 -0.05 294801 -0.09 294887 0.40 294973 -0.29 295059 0.02 295145 -0.11 295231 0.14 295317 0.09 295403 -0.04 295489 0.18 295575 -0.27 295661 0.17 295747 -0.08 295833 0.03 295919 0.36 296005 -0.19 296091 -0.13 296177 0.17 296263 0.20 296349 0.09 296435 -0.08 296526 0.20 296612 -0.03 296698 -0.30 296784 -0.08 296870 -0.19 296956 -0.04 297042 -0.09 297128 0.13 297214 -0.04 297300 0.20 297386 0.14 297472 0.23 297558 0.40 297644 0.18 297730 0.33 297816 0.10 297902 0.34 297988 0.27 298074 -0.10 298160 0.03 298246 0.40 298332 -0.21 298418 0.08 298504 0.01 298590 0.03 298676 0.10 298762 0.07 298848 -0.11 298947 -0.18 299033 -0.10 299119 -0.24 299205 0.16 299291 -0.19 299377 -0.14 299463 -0.14 299549 0.33 299635 0.23 299721 0.39 299807 0.53 299893 0.27 299979 0.29 300065 0.21 300151 0.25 300237 0.33 300323 0.21 300413 0.17 300499 0.22 300585 -0.04 300671 0.00 300757 -0.02 300843 -0.30 300929 0.03 301015 1.14 301107 0.14 301193 0.37 301279 -0.05 301365 0.48 301451 0.24 301537 0.35 301623 -0.09 301709 0.05 301795 -0.11 301881 0.10 301967 -0.19 302053 0.20 302139 -0.13 302225 -0.17 302311 -0.12 302397 0.12 302483 0.16 302569 0.07 302655 0.22 302741 0.17 302827 0.10 302913 0.11 302999 -0.27 303091 0.04 303177 0.36 303263 0.31 303349 -0.32 303435 0.19 303521 0.15 303607 -0.21 303693 -0.12 303779 -0.03 303865 -0.09 303951 -0.15 304042 -0.34 304128 -0.15 304214 0.02 304300 0.09 304386 0.16 304472 0.22 304573 0.26 304659 0.55 304750 0.08 304836 1.00 304922 0.07 305008 -0.10 305094 0.24 305180 -0.01 305266 0.03 305352 -0.05 305480 0.05 305566 0.75 305652 0.33 305738 0.08 305824 -0.09 305910 0.05 305996 0.19 306082 -0.30 306177 -0.43 306263 0.09 306349 0.13 306435 -0.05 306521 0.06 306607 -0.43 306693 -0.23 306779 -0.01 306865 -0.15 306951 0.07 307044 0.01 307130 -0.18 307216 0.03 307302 -0.06 307388 0.38 307474 -0.10 307560 -0.20 307646 0.22 307732 0.31 307818 -0.09 307904 0.05 307990 0.32 308076 0.77 308162 0.40 308248 0.34 308334 0.01 308420 0.15 308506 0.30 308592 0.12 308678 -0.10 308764 0.21 308850 0.22 308936 0.04 309022 0.27 309108 -0.19 309194 0.29 309280 0.21 309366 -0.02 309452 0.01 309538 0.11 309624 0.05 309710 0.03 309796 0.38 309882 -0.05 309968 0.08 310054 0.24 310140 0.57 310226 0.16 310312 0.23 310398 -0.14 310484 -0.08 310570 0.03 310656 0.25 310742 0.35 310828 0.09 310914 0.40 311000 0.44 311086 0.17 311172 0.28 311258 0.60 311344 -0.20 311430 0.22 311516 0.28 311602 0.27 311688 0.35 311774 0.19 311860 0.16 311946 0.19 312032 0.26 312118 -0.01 312204 0.07 312290 0.03 312376 0.16 312462 0.07 312548 0.27 312634 0.16 312720 0.01 312806 0.26 312892 0.48 312978 0.57 313064 0.50 313150 0.22 313236 0.53 313322 0.40 313408 0.18 313494 0.33 313580 0.51 313666 0.72 313752 0.53 313838 0.14 313924 0.35 314010 0.32 314096 0.13 314182 0.24 314268 0.07 314354 0.28 314440 0.16 314526 0.07 314612 0.14 314698 -0.07 314784 0.02 314870 0.22 314956 0.60 315042 0.27 315128 0.44 315214 0.84 315300 0.71 315386 0.46 315472 0.09 315558 0.39 315644 0.35 315730 0.18 315816 0.54 315902 0.64 315994 0.71 316080 0.46 316166 0.59 316252 0.55 316338 0.61 316424 0.29 316510 0.04 316596 -0.12 316682 0.02 316768 0.15 316854 0.15 316940 0.18 317026 0.07 317112 0.48 317198 0.25 317284 0.16 317370 0.36 317456 0.11 317542 0.03 317628 -0.09 317714 -0.09 317800 -0.10 317886 -0.14 317972 0.06 318058 0.34 318144 0.14 318230 0.13 318316 0.11 318402 0.22 318488 0.29 318574 0.00 318660 -0.21 318746 -0.05 318832 0.30 318918 -0.05 319004 0.02 319090 0.12 319237 0.61 319323 -0.01 319416 -0.69 319602 0.25 319688 0.68 319774 0.00 319860 0.51 319946 0.45 320032 0.53 320118 0.14 320204 0.23 320290 -0.24 320376 -0.17 320462 -0.68 320560 0.14 320655 0.20 320819 -0.14 320905 -0.04 320991 -0.16 321077 0.26 321163 0.26 321249 0.30 321335 0.40 321421 0.64 321507 0.16 321593 0.56 321679 0.39 321765 0.19 321851 0.28 321937 0.44 322023 0.19 322109 0.45 322195 0.34 322281 0.36 322367 0.25 322453 0.38 322539 0.19 322625 0.01 322711 0.11 322797 0.63 322883 0.18 322969 0.40 323055 0.42 323141 0.16 323227 0.17 323313 0.39 323399 0.52 323485 0.55 323571 0.50 323657 0.51 323743 0.60 323829 0.67 323915 0.57 324001 0.11 324087 0.09 324173 0.18 324259 0.10 324345 0.03 324431 -0.01 324517 0.30 324603 0.39 324689 0.21 324775 -0.58 324861 0.31 324947 0.70 325202 0.18 325288 -0.25 325374 -0.04 325460 0.19 325546 0.27 325632 0.35 325718 0.00 325804 -0.06 325890 -0.02 325976 -0.11 326062 0.24 326219 -0.05 326305 0.22 326461 -0.04 326559 0.26 326645 0.06 326742 0.43 326828 0.31 326914 0.34 327000 -0.39 327086 -0.30 327172 0.45 327258 0.37 327344 0.18 327435 0.63 327525 0.56 327641 0.53 327743 -0.06 327829 0.01 327915 0.02 328047 0.16 328158 0.45 328279 0.23 328366 0.19 328460 0.31 328546 0.07 328632 0.32 328718 0.20 328804 0.43 328890 0.08 328976 -0.34 329062 -0.25 329148 -0.24 329234 0.19 329320 -0.01 329406 -0.20 329492 0.17 329578 -0.01 329664 0.17 329750 0.22 329836 0.00 329931 0.17 330018 0.32 330104 -0.05 330190 -0.19 330276 0.22 330362 -0.19 330448 0.27 330534 0.08 330620 -0.00 330706 0.33 330792 0.50 330883 0.38 330979 0.10 331065 0.11 331151 0.36 331237 0.41 331323 -0.26 331409 0.36 331495 -0.06 331581 0.37 331667 -0.20 331753 -0.00 331839 -0.42 331925 -0.28 332011 -0.05 332097 -0.11 332216 0.14 332302 0.08 332388 -0.59 332474 -0.43 332560 0.09 332646 -0.08 332732 0.19 332818 0.04 Bio-Graphics-2.37/t/data/t2.gif000444001750001750 5265712165075746 16274 0ustar00lsteinlstein000000000000GIF87aQͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ |܁!H] n6ހV_fR!qIb~bh$nh~~c(Fxa/r#8c#&)cDiEB"RRcVbiaHN7VeITX#jg#ZRnFdN& g^frf)&BY执I5 }N( 6\李h2v:2ڪBnffFJZI(ohbZ^쳜2{ɪ,x#@iv殷k㤓fun*z{oV ~*0ΒInv.>S11O<+nrޞL~ /+(pAsH2\nQGB ے<0<']rdGi^s]$ýFuR]Dq:5G=z\&ۅ#.7M1&+>r^G7C?ߜyƶ.n/o'G/Wog;/o觯/o _FRL:7@p̠7z F(L Wp~ _H8̡Cp@ H"&:9DGG*ZX̢.{^ H2h 05eSf8McJgx46#GL" =>#'IJZ$|!0I (G9@MbK&?9V;,ꢃJ 򔬄.wKro\LfbzE2Ijڏs4#MmӚ 8чi&9vS崣Ub̧>hnQڧ@JЂ"@-:s( *9Zlwt֚9fΜE?'CCU:/ `6S )NCR2OXR Qԕfs`:}jKT洧鲒zѡf-l:SƴV)UcdR g\kZ:b\ַ25A` KWԬ5]k#d򱃍ZWjA[ X*UVjW̵Vt[W"! Z֭b-m\X’Ru$E.4&bmc[F4bͭY&[vt^"ָXETYI95Еx5{ޛWrRj\4׺oY{ T]hy#K4oXslsY.pV+\"tɉ;>u|[u-H&,xCjz|VӒdN} mDYnueqduh䝭ixmTsT qn6lEXȉ^Kb?Cuu.4ۭN{ݧiOZw/׮o)D+YŌNi0-nڵz]GC{L]!mt7ry3V)'qk|u7]gOm /ݗqSgt_G/j˺7]$G%{F}x%pgq5JF=ˎY힫.;֝u`RfNu>SLS`{juuu(7}w.sS:zkdW| >Ê7zutUYQÇj||fgJG^fӗ~~|{}bg|@rWu$|G@}CxDf*[B7'wG|;}ȁA4C{E~.(X0}%|K8tg'AJ~xTG^f7؀8ԅ8dvzf?x_HzrXcjGlxD6h8(tM(-X)T@nj8xcph=`8gA(؇xHhFxuH芝(f}xh(~,苕ͨhr8X(Ψh¨h(H؎Ȏh8h䈊܈ĵ㘆i8刌(X iyIex " $ɑ#9l'i qG .x<*1[v%: : )y(J[ 1J3=*u fsz |d8XZy+ZੋqxȢTޚ.۱DžUNkPޡQNBT~=s؝(NaiΝǮn&n<]޴]خ8Ԟ&=d^GӕFwB0l  ύ\o_erfbn莾K% $z&?/'?21580>O;/-7_3DH:oIMCCOOShRS_ABNcWd6/GlostufV~rawh.ц?L_`/?oxV|/_+ŭcP?_H3O?/_Nȍݿ_şʼVI`'0A$p F\(ѡFRQȋ+r$ǎ-[$G#_LIdț4w~L@@DTRG?tITϘTZ2'T?kOBmJ%SδmѾiP ¥l\sVX@ZS7X["3U%sze˝ .WҢŞufҨknzvKֶMLpw, 7V9PWrztڹ]|x{u%+'m'))vоy4{ͅc?*@63" osAǛlBqs/0pCBLqALDQh-X r܀iŃ BKɵdR2%4(lo4,=#y[2˜TJ0-6ikKm"*nTF4R2CcFd4EhRA=zT?촿u2P1kBHKԈRK5Χ|47qۓOrb\FWUpY159UTWwT[նZaPuW}VmK 726S^ũ^?\VсV kX5i-xaEN8&IHdv-ONQu^^uy`[>^v܎1.wE yVff 8in7ܔ-M9fպf[_A{ 6wA&-ӲYSR_(YHKi-!"6:'y1źs5M_դ3}qdDƇ1tRiOMZolLptXUSVԯ{=0OΚ~Wg|UIv}G{} Tc r&=\GNoh|՗- f=R'>K#sBJ4 hCjp8! ]XC 7Qaq"6/A$x%08fq"8$&Q)^Ø4fzu,IfϚzv kS?β;+^GK.N,dRukkڟ ,nZն-kgZ7%,tUuy\n]d[^"enq.ռhnxϛ_vG?ulқCi66|ާ>-,S3$1jXi교B/{ǻ0})O8{eA4یK%VՉYŠM%g,W.95.yoWy|PPA;ⰞrF*I>~7YΤհ O-Ae&>I?y/dZ(E#g C7χ| 8 ةM<2Ѭ PLPʛ_jNXҷ=W9LJF4l@'YvhC+v] 9u޶giaP|6]czݣn؝nP;5+5}g7_յonXBZ<.]vC<ʥmz;UM6J`u{f=|I.sO׹r|YܾU+H|}Iw$ApC}UG`]x?u&9ysLzzTv?Y*ԢS-ڴ}dW;glp {w~x_}{e~o}߼C.jnRqyLU ͱwd,WyJWWus9G{tO|ixne">>UZV~ҭ}}Lb@ v?T>盿 >[:@Nj@= <+14DٜK 4 D?/[b3 >$@@ ·ܻB˾ '”,\,tB#lk(B+,BB04C1伯4TA9,C!tCۨ=,c?@A$B;=CdFĸCp? F\ILtD[> cD R4SDTTUdVtWDEzS^EX\\/ :E]6D23B[dTe$EI/$$>CF@Ffl55ZLFmGYGF̴F8wC,3ͺR9O R,QkܤS>8MM|?5-RMT:E* N RTPE,SD5PL=?<=Q D[HMLH}T8m@]OKMTa1`J=}UTTFUŌLLUXUUf TGMVUSfTORڌUQeGc-U]SgVJ+ՉV}LIbMӊTubחך9[p`MSOEUU|}W"X u}XO`UP U{ݍ3!Ut ؊UUneUBuUEEX+S~uX}Yl5eY YEUԈQTW=ZoRiYŀt$ .Z[%YhWv4SD[џq0V]Bm]ȵAhYqڜ =79Ա7. ܘߙ]ۦ-_PS1Ob]C2BU۶}\mLeF0x\ݢ\<`\ME- ~9 v5w_=_M]]YBSKZݽa6eZ5X 6``fE5(ǯ `^%Nb6$^ݚ`0Xu Mabԍ!.ba c-W5~)` 6b:8.c^ Z˽D&@&d>b9`=`GBaFXLbIfccdHcOcRdSaJFdcУf#4Yi>e`e-!\VAGfgFfbPd%5( S;*QOgced ](3+~vN`^uvg*h5Uanw^bzc)h%]Fc.ki&0˰Ď?k\˧i~hꚔJ~l BA,q|D cf`0vk^l~PUivG.F_4 l¶k>lflJ0@;HT*F>kNf&nH{vm!Tn>hk/^φ+ndnN&c~&v)F'dRnhFlظ6ClGc\hNom.l6&n>~ppNp^pp /q poVgp_pn3f'^$(Fpop ?qqq%oqm&_nrrwۺq|N[r$r1/r)q4Or6?s/sq/qV6osgk=/]s8osBs ?t/tD?a'ks~ttFr}^tEOts'ot;sJrLbIqKsrr+u,u-tQu"j^u`sCvNs0u*_vZov./vPv]revfvgvht~v3/ukvavp/wOwu3Rk lWtX?w9u2o$2X9xSz'u7qo|Wv5USKY A:6µ E?wl_ymwuP6z2Υvujs4CX;u!?z:GZ9=|/qVty:/ 2+zFcxytysy*E7- 4_3JguGuWugzo|x/{{zsGWwgw7xΧgXoW#Oxgqۧ}Gu3hd[}ޗzd g\ xOuq|wyRc4^z602u_EYS~j7{(/~}YM"N;!X.\8pAqX0bE?3(G@I$ I%̐1;tiM'UެgSfϙ8.թt92:uԪOl0יNZEJٰWמ-ܸ_j%K%F/TH^D-h`j͎5 ȐQޥڸc5N4Q/gmعW:ʬ۶mҨk-7gƽJ^ ,Wp׽ W&7nzތwgνc;7yPwމ{{ǚ^;xژ_g 68p !Qgi\^}fMEMPuWVvGڅF(xBE塅e"ڧa{.G`%#c\_]_$&GJ$]ZLPآz4NA!hYD2cg!mXye<ک_Z7ޜ9'Hee g_gFFߝ:)v|ښJZ'I奖k 髵Ȫ?(kj堝+*'jȚ(6aZmjŖ٬q*,pvWb.B.dvb&.e/ /lR5pj0K\/E˒;,{E |2$ S2G|Ǽ3=r-1[(/"q4Qhr[ܵ[3N% =X˖h=kmbq&aށ sڎk8Hx)^۷l3>x N7}R/-:X=m.3\5:,ۋϹ8GN(쿛<ڴ^f"wTF6o*ҧl<]5O]v> _^/A}qϔw.ONc  7!{֓,{QdbA NK! E-v JHPWmBl]m(jo;/Ώ3DdQWNĢ w1f+We"yাh0rpc [5(=dH9?CAdlG)⒣,$[[z,oI RviN-~7V hOuZs-jM EU%kFX}zbӦXW rvL>%-3kJa-6U`E9@dQL7W닟\d.]edYf|fasef$;xN3e1sYh3i MgBYi/ h3Gzm,dTethDoӤ4CLy}651=gI_ɣua= ދes\kMi] VcW=kK[Qڦsgl7jmO>T)B^6}n~ me?;X7 @ o+۩6wo0*4߇~W.}C ̛_)3|y\񆇻"JGCj w ?I;t: ; cδT#Ns=l:uwho;.=Eָqn+Tfpg7Ǻ܇yxS_|#OwT]~:+dC\LSHɀ]R=|Pw| _F `WX#l?W~{zϗ~ob~Hh0yϻj~/~7ꜟѨK_K贛!`k}E`=^ `ANܟ͍*Q^U=O .` ꁒ_r _ :R! jWѐ M I`ba Nr!!Z!V!!bU R!#^ &b#bb%@"%J"'%(*"F"*~`)^$f""z&*+b),b,[--^ޢ.0ڑ1a&!^5c3> 4 55Vc0v6v5V^77zc8;#q;#c<:Nc;#>j#7?#."1&/Ue2n#DdA*d9"?Z"("<$4#@F$9#H#I ZdEbId>I~$L6$eJ=dH^F{fZg™P yfgwgZr'{zufYٹL &#ʧgb&(vݬYjZ舽h(Vc9ңhR[QlZhfh ib#K&ːm''֦)abVńjV' iNh^h"&2nzBXe):)钆铎h@OBDI R^U&tNXihbꠒjr60 F*P)(q(`Rfɣ*l*Nj*F**]r"jt9k(*kVbkj+hEwvZk2rz+kk*z閒^*=",&.,6>,F~!/`^,fn,v~,Ȧb ņɞ,ʦʮ,˶,h,֬,r,Q,,&.-6-}:N-V,v^-v~-v -ٖٞ-fL,۶۾-ƭ-mÒir[ޮ^rv 컚뼢)."nkRnb)ilj«߀F4jn:nkn⚮ꨘiy4LPfjZiҩn"BjĢd*ޮFo>.Zƣ\~n.b/Zz/sM. o/joӈǘo6/Vn_/@Qgn/f_W+rt$ǂ Fn/pnpvp ~/pҰ~ /q ?|_nbcp/bح1^1'a 01ױoa߱1 G߰?_ 2"riN0 %2$G2/262$g&{-%-\qn(v2~r!֒2+2Țߢ#?ײ-2.r-r q s/c1k0;2rg;222Ks6Wr731ks6{z%_-9O%ytj 8s9* 2$ݺ(* Cg?s:d.]CsDAIB̢nQC{:#Zs Z H+7OyTO==»~V7g_3˽?35sLkص6G`tiϋ}Г#}𥰒g{{߻~'ϽOV;o=GnW>$?8g_sDPNXgswփ//>☧&@G`'.LaN. \(0@-*0"Ď1" Treʖ#;|&L,I)SM>mْ֤Π-͹ЙO,Q=6պjTSzڕ)װVǞm[o$QEO֥[׫\'ܭXy,YU\42bɗˊՌvOWC5%MwfvtӖ ~~緷qߖ:5瘮mveڰ.YKpP(-B30?Ь+2: PCBdp/QB=dF[ yss>J5P=ߣQ ֻ4 W5.dMVUQ$VPFK%JX4TYcQiMDkOdU IU043W44)hoͽNlلIw?}bMhCYj6QycUօHr`uw\^ZYNSͷeY +s)Csd1“?"%KؘJv㒧e`i;wӡW^>}NY 3yܗnW4ygXd]tZǪAVc/>.{jWv,[/yW| &^qM!^2{/ݣ\i}7qSgt[c߱s.;{(wy2GwOt䋏wlo|vfy+/j|z櫇=7gw$`?O?EuK[fPy[`G{M0參_AB#TXnԋ" RSOPt gj|bjcJkyM]pF Bl!(C0J\!J(#-♢hd}:}H1ƋܞH:kIFݑtKTqsl)gJ롒}4+HIR@Owj 5dDQMTv #HΌS(v]mqcu{_طy?*_gFo x^760oloX30ӷWmp~]ybS]nm֔ȿM^mb,f0bVM>IEpxpxc*6HO_A2R&sL`cLFe#[ FƗ X~Avg02[eW|_9+5m-I~seңO%fֶsw`݇M}᫟}!]?=k_qo/oBoc-o+/j(@0EpIpN7P,J0epIlqhpyC.A0p 0 p \05|p 0 0 p ɰ u p:g  p0pH1 1qp/)Q-5 9A41QJ0Q>qCO7' p/0׏sQo`ƀqfoP"Ѱ1QjQqQ/}"( AQQkJ RnbJK/"lM nɑ1f!I'f RPfkrf "'+#)++yL.%Jl\x,lT r&"Q&$ (ACZeT'\&g]R)I*όf &l^1ʌ2 /f;qrǒdrgJR*.w-GF r R,Rc,igr#W1RE>-MQ"+S/#˒5o'F4l +q5)2/{-3,q248a7R8ϑ-S97m #r7=P;YL|O5%:E3]/|I:ҳO1?3>Gų;QBpP>8C?GP?tA T&=F@nF tC9C=אBybA CUtE;EA0BB/tj(s<]4YtG0G 4ƾQz'br@{HGS.ɒ=9˥IG%>!99I =ɓK%;)<4;ǔ:NLy@SMߔJO1TKP8MPTMQ˔PSNN:]0d.nu/aD6'DO77UU5OP+jؒOh#A-$#of'V?515.f6,W!llr " K4%"(75X]Q+Y;5Wh1%%fp[CR4UdzNuQgkp~L1-ϵ3ï$s5^OSS^q׾tJ/ Kzth\ +WKͶlq[#U1VR!~H&+wz"{tn/7mfIi0P1V=x xG1x_b7>W e Wu\UR7g#DN)tU|uwUxxw@(vVseWIk6-pGqy2U"xSH/wj3o7xڑsϷ2 2vdO|m7oa`7kcc7W}osxmwv|3WqփUh RMX_5ǎ_y?l؈Wvgxoߘ㘆;؍؉P~Dax鸍mxLd۵gv7)mᘒyLW5&x-v7WK`n\uUؐ9ؖm.x&ZQvlqO1vn֙1]t5ZKَCv]h9VC;ّ9oyYYt )Ju&}ÙuYُٕU׋ԌٟZ3:'nטI,;@ TV9Y9yig#lFq8"EC(kXZ+Y~Q#Q!EB'ly O:Py5z`Z'} .ЧɦFUY5z}>ڂՓA}6/Z5ucU65eH6FBj(WS?қb2b!VH BXB ֈX?/*?X>,b)V>^5ܖ.ڂ-WUWYa+]&[kG3 m-q)|1XubV_ٚV [բU٧bڿ8cuyX=Xo5X4>o=V@ޯLKONX:)X!A, b!VH BXg-abfXG`X؟XĂE,X`*ϟ09LJcÞwXj4Vx`Y>ҁk:Y{l;.qkY+sn BOG|b_B,z7X!kяv\b(;sXĂE,X`sU',:!jzˁƊUZRˈ'iٳb$-{VTIZKjn,[>XX},t6VT+OҲdIZXX -uEqDYQVlYb5gk "@E V?+*hJ ֞E,XĂa 6X/+C,ڿXQ _欦X/.VX/XVXubYb~@gc5jL޹Ŋ{˒/K@,XGVS%:8VX׊X,b Yb5gk "@K4c%jk",bX!6+ـ.Vyx4Gl:J5;Īj5[LfG6X/dkcLfK6Xn֐ّM'sN6lfUB5łcuYt҉zA, b!VH BXB IbˢcM,XĂuf `}XĂE,X` ևE.WlvhG9l6X#V K9l:h?&-VwZk2K=bŰ*]bƱ:},:FZ5sv BXB b+$uXe `YĂ-V`l9XW[t6VTZXFX..VX/{i,:+*^ˁjrb5&nbEڽֽ4lXXK֗tDqDYQV/kYb5gk "@E V?+*hJ ֞E,XĂam*,b=ĢKǎХcj| B'VSVN9}agb]:e]U3ݥ|`YMvY4lXWt,Y}ұd Ăud5Z"+c%ʊzI_;"@E V?XA~X,b YQD8VX,b";YÞ409!J `4\ a7뱗3 ^Ă 'ވ09;s,b?Wl?;>Vx`Y>ҁkY{l;.qkѢY+pn BOG|b_B,z7X!kяv\b(;sXĂE,Xt&69Pmtlٽ-=K5eH6Fsn%jWOY&h''N{bB b+$X!Aʖ0DJ#0,O,b"|<-t_E[%zЯpz-' Ƕt6}Uf͡b]:B5DZ.z}d!ٚvb,YsXբ5j|]:sXt҉a ]:Eɫ\]OY&hFy-A, b+$X!A, bER,Ѭ X",bX>,а'E'4 аb  %аY:aĢ6脆lX+Vbib5gk "@E V?XA~ #1Vx`Y>ҁkպY{l;.qkJYb,b!VHu v\OXX+$b-1xێK Ųq{X"ln}@gc=z\Mo9XQJkU*bb5ˮ4lXUc9?j,(쪚aﬦXƲjUO޷ˢ@gcEڽ֍en,lXX -uEqDYQVlYb5gk "@E V?+*hJ ֞E,XĂa 6X/+C,ڿXQ _欦X/.VX/XVXubYb~@gc5jL޹Ŋ{˒/K@,XGVS%:8VX׊X,b Yb5gk "@K4c%jk",bX!6+ـ.Vyx4Gl:J5;Īj5[LfG6X/dkcLfK6Xn֐ّM'sN6lfUB5łcuYt҉zA, b!VH BXB IbˢcM,XĂuf `}XĊdKI+Tyu_ĊbKX_sXFcYI4 ĚVeX٧2r+ZeVO,C'!V [? `K]Ԫ+ zh;"b!bXX>,b!VXY4VX>,b"Xq6wLJxԌXlieUђlbeU&xXQ9El\1*Zۿ nEk̊giBX4>XkzhkX׊X,b!b!XX"bň%EcX",bX>,b"FY:d ;ftB#V K'E,wftB#V [OˬhyNhĊa ЎE곈5]' mrg  |XB,B,+F,,+ĂE,X` ևEHeU[XgW&[WXĚ_]$ˁڿ Ekck"XnEKubVXfE˳!V [hr,VEY삥V]Y4V5MkEA,w ˇE,B,a b͢B,XĂa `}Xf^5*45[*UVy^;%W#MFjO-lbj< tb5хXl6^7iilf׍r5;}ҩن,VJf6Xql6N_đVx")V=K)V$/XVX/,:)OҲg5IZXWeœ,Y`YM,\Xb^wb5gk "@E V?XA~VT,(()=X"ln 뱬XhcE*b5ťЉUx_YXuW`W {g5j-,~VSz]:+*/KV_,ڿ,Y`YM,Xb^+b5gk "@E V?XA~VT,(()=X"Xy%Þ4N09!J `MZ\ a7I3 &-Ă gt09;s,b?Wl?;>Vx`Y>ҁkڦY{l;.qkjY+Nun BOG|b_B,z7X!kяv\b(;sXĂE,X`H bmإg/ן?^9cn*\ a3?l v{c]/7lUg39cL (xa;{ >*}wwt`cZDhi #{Ir:E,"Ard 9epY28H, # RXĂa `}ضXvbg[+H g,أlK!aXVbf_1y}Z4>K b;#V,^Ŧ[Az$"@E V?XA~X,b Yn7bA X>,b"X1b}"!A,"!A,"!A,"!Ͽ"`.6}]P>"}ȑ\T+r(g0Ȕ\ y;OnF1y' R"w"!A,Xsޛxms3նV{`u\v~NXrqܾuWA[~>s?Smxӭ< %bCͨR4zϧz-vlIX#'-?W?͟ o<7glk r>OLwJ܇em'H zf("5gܖ}OtHH7&7~)m&bն8& ~ ;_v\U8cM%p]>bM%p]>bMvpHHHHHH?$n43IENDB`Bio-Graphics-2.37/t/data/decorated_transcript_t1.gff000444001750001750 366312165075746 22524 0ustar00lsteinlstein000000000000##gff-version 3 ##feature-ontology so.obo ##attribute-ontology gff3_attributes.obo ##sequence-region MAL1 1 643292 MAL1 PlasmoDB_70 supercontig 1 643292 . + . ID=MAL1;Name=MAL1;size=643292;molecule_type=dsDNA;organism_name=Plasmodium falciparum MAL1 PlasmoDB_70 gene 549319 550102 . - . ID=PFA0680c;Name=PFA0680c;Note=Pfmc-2TM+Maurer%27s+cleft+two+transmembrane+protein MAL1 PlasmoDB_70 mRNA 549319 550102 . - . ID=rna_PFA0680c-1;Name=isoform1;protein_decorations=TMHMM:TM:157:179:5,TMHMM:TM:184:203:10,SignalP4:SP:1:25:0.444,exportpred:VTS:42:48:7.50725;Parent=PFA0680c MAL1 PlasmoDB_70 CDS 550034 550102 . - 0 ID=cds_PFA0680c-1-1;Name=cds_PFA0680c-1-1;Parent=rna_PFA0680c-1 MAL1 PlasmoDB_70 CDS 549319 549939 . - 0 ID=cds_PFA0680c-1-2;Name=cds_PFA0680c-2-2;Parent=rna_PFA0680c-1 MAL1 PlasmoDB_70 mRNA 549500 550102 . - . ID=rna_PFA0680c-2;Name=isoform2;protein_decorations=SignalP4:SP:1:25:0.444,exportpred:VTS:42:48:7.50725;Parent=PFA0680c MAL1 PlasmoDB_70 CDS 550034 550102 . - 0 ID=cds_PFA0680c-2-1;Name=cds_PFA0680c-2-1;Parent=rna_PFA0680c-2 MAL1 PlasmoDB_70 CDS 549500 549939 . - 0 ID=cds_PFA0680c-2-2;Name=cds_PFA0680c-2-2;Parent=rna_PFA0680c-2 MAL1 PlasmoDB_70 gene 549319 549619 . + . ID=test1;Name=test1;Note=My+test+transcript MAL1 PlasmoDB_70 mRNA 549319 549619 . + . ID=rna_test1-1;Name=rna_test1-1;protein_decorations=SignalP:SP:1:10:1.0,method1:very interesting region:30:80:0.88;Parent=test1 MAL1 PlasmoDB_70 CDS 549319 549619 . + 0 ID=cds_test1-1;Parent=rna_test1-1 MAL1 PlasmoDB_70 gene 549319 549619 . - . ID=PVX_000640;Name=PVX_000640;Note=Highlight starts with CDS and skips UTR MAL1 PlasmoDB_70 mRNA 549319 549619 . - . ID=rna_PVX_000640-1;Name=PVX_000640-1;Parent=PVX_000640;protein_decorations=PfamA25:Ribosomal_S12:1:20:5.2e-43 MAL1 PlasmoDB_70 UTR 549589 549619 . - 0 ID=utr_PVX_000640-1;Name=utr_PVX_000640-1;Parent=rna_PVX_000640-1 MAL1 PlasmoDB_70 CDS 549319 549588 . - 0 ID=cds_PVX_000640-1;Name=cds_PVX_000640-1;Parent=rna_PVX_000640-1 Bio-Graphics-2.37/t/data/feature_data.txt000444001750001750 335212165075746 20411 0ustar00lsteinlstein000000000000[general] pixels = 750 bases = -1000..21000 height = 12 reference = B0511 [Cosmid] glyph = segments fgcolor = blue key = C. elegans conserved regions [EST] glyph = segments bgcolor= yellow # this is a comment fgcolor = #EE00FF link = http://www.google.com/search?q=$name#results connector = solid height = 5 [FGENESH] glyph = transcript2 bgcolor = green description = 1 [SwissProt] glyph = arrow base = 1 linewidth = 2 fgcolor = red fill = sub { return 'blue' } description = 1 [P-element] glyph = triangle orient = S bgcolor = red fgcolor = white fontcolor = black label = 1 point = 1 Cosmid B0511 516-619 Cosmid B0511 3185-3294 Cosmid B0511 10946-11208 Cosmid B0511 13126-13511 Cosmid B0511 11394-11539 Cosmid B0511 14383-14490 Cosmid B0511 15569-15755 Cosmid B0511 18879-19178 Cosmid B0511 15850-16110 Cosmid B0511 66-208 Cosmid B0511 6354-6499 Cosmid B0511 13955-14115 Cosmid B0511 7985-8042 Cosmid B0511 11916-12046 P-element "" 500-500 P-element MrQ 700-700 P-element MrR 10000-10000 EST yk260e10.5 15569-15724 EST yk672a12.5 537-618,3187-3294 EST yk595e6.5 552-618,3187-3294 EST yk846e07.3 11015-11208 EST yk53c10 yk53c10.3 12876-13577,13882-14121,14169-14535 yk53c10.5 18892-19154,15853-16219 SwissProt "PECANEX Protein" 5513-16656 "From SwissProt" FGENESH "Predicted gene 1" -1200--500,518-616,661-735,3187-3365,3436-3846 description=Pfam;score=20 FGENESH "Predicted gene 2" 5513-6497,7968-8136,8278-8383,8651-8839,9462-9515,10032-10705,10949-11340,11387-11524,11765-12067,12876-13577,13882-14121,14169-14535,15006-15209,15259-15462,15513-15753,15853-16219 Mysterious FGENESH "Predicted gene 3" 16626-17396,17451-17597 FGENESH "Predicted gene 4" 18459-18722,18882-19176,19221-19513,19572-30000 note=Transmembrane+protein;score=20,50;score=80 Bio-Graphics-2.37/t/data/t2.png000444001750001750 2370712165075746 16305 0ustar00lsteinlstein000000000000PNG  IHDRCCPLTEQͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ j=섵,m:r߀AgR#x 4! @A@R5HI $UjT A@R5HI $UjT A@R5Q>//_ 40ְJ]`D*eC@`a"Q25_`D~/Z ?H&Qpi_k@aI,E\m0dlt0yh2z+?-M0rm_r+!;~"0[jA@R5HI $Uj8H*Id uE`DN XO! ҝH\*&HI $Uj:kBC@3ގ~XC@`D؄kn?Ig8CQ#[S_'g,Eg 9MWֱc 7TVu~-_Va" h6ZSܺ}\ۿ'm L{uPs ƒk;vbh =OAme[VHP[sD6AG w="[^+5T埼ͬ^+_I[/r}$9_!Xa (TdGFz0}:n҆qYeByT~s,8,臇9U'QXvɮ 8]: N ՘[AD&nE~}0Vd-( -` npr~D͝ʣ-"!`>! 9D\M--/b+A@`l"jdE!=0L`"zD0 `-A[&xLSor"=0& 06L! `" MA@`l"C@`D&D0&p|?D\3 zbsJX0 :vO~ć"0M nn.lVsD-%\9^ˇ 3j&r]eR@c *FM0k0 u] (jXHáF9`E`"[, `" }{d0&tO --LEw:Q| ǃ4 ,pJ+zqa" 4xpwqأ%pUg4!m9&R_ <ђDT0&"a G5w]lViО>?2el | &\#npa. jV̚"'M"`"8.o͹TsN#ؙϱt5"/ x 5]v0i@k f  s^A.fE,b*6G0 06L!`o0NZ>TcK]3n߁KML<<./= {TxYvqɞ `=w߳}9q=ߒ=Af TA5] BX70& `*7y-pt,]l'JXXk=)n`ǣ9}ɾsCKv+h%?g Ptv-+GAlIxWSYMn+٪Œ-:A?n#-ْz"s j# *s*i1ۣ/Y,yL= A@`l"C@`D&D0& 06L! `" MA@` xfJkX, `").&ʽj[~>[>$TcA?pV@u.<&f2\Ꟶ'Y+Ы-YsD6K+lݜ&xj# ``t][ vr":a5Lu'`;L$-ΓoOOLv0}8'~g9`e`꤀QpGoUhZ`]^4+7G&un ?X X H j > L!=a玂5)L`` D?̾OMߐ9QYS))gӰ2wZ> "m>_Ag&Pպg` v;_T5^*A$;/?\ 8{i8G:b*N [a)u ~~Y[D4ga" MA@`l"C@`D&D0& 06L! `" MA@` h{ynLGbE؎(g;9`E`"gD_IPy-pfHB_YO PTFIa"{HPϧJIh:P,l0k&%oxiv{?=w߄kX # 0-/ ?l m[-F`"!FK *B6lHND6둉^$`0{jaO'SsDv\_*i,̸i\ׂ! `"õf?Ra*cATK֚nݛ[* dhꗿ +)jn=uY[ʿXftO/ e&eyl5^1n׎K4L$ j(N\sl}4|!q=90ol0Ovl'`"y`7I4^@K֬ $t=Yꞃw=ߒ*`8yZaɸX0.Z@%s0_ς ax^!O>?=\ܥMDɮ,~#497^[qB%)]ɘ㎀;kۯPK^Hq7J{L@gF#-u^,**hۦ_gLVD'kֲ=!`܃E\Sew[7eoR j6.٥GFf.S x(: AD x`VY7@ND{&D0& 0bs^xYGUSˑ:9"6 +椨y "m1HڜK59)KW#6 rdh9ˁ`"X&Ҁs V4/%+]8|+A@`l"C@`D&D0& 06L! `" MA@`l"kOv8h/w5L$!` aMAQ|h2z+[jCVjXHOmꭀkXB?oF9`E`"t)lZ@6`"{;Am 9V\n ?C@`DuVsAPN/`_t#k'd} xS5/G \>[lF.C5g/Pa20*8S_p*7`u0^p~a9[hɞmbOC]s'.g*5gO-)_Ɖ\G= l&x݉2ӶXs+sC0`{X;mMT{_YN #{nzR7VoBKr{wbM`njXHMm Ca]MDl7CKŰiv^;j.9]27UŦ~d/>$`0r Y uP;Ü'r/KJ,Ҏd߹d;Ѱ*ϻsZP-) `")] PjV&pSֽ@S"0QG4~> ^]愀8 _C@`D7rC6~ !A@R5HI $UjTMܑ\?C@`DXW}͍n2xf󥻬9`E`"WZ@/=E8\P/4M rV/ ]/3`` V?" tJE؝dw(xV&ie?{G5LD0֕O=FHw־ A@R5HI $Uj<L! `"lBUZs>q` 엊0݅Q]hR6>_'w:l{ݶTA ga"ϰ{ج>j ׶nm&qc$ZUz6 "L9WjJ#5c adwOhQ^ a@"`pƶz};wbh[{+yH@ `[kXGE?<] "<W><mg`&ax L0&RM5sJ8̝[N+mi#0L&ax L0&‰$" 06L! `" MA@`l"C@`D&D0& 06L>vlgt9%-+${RLYsD~= ?城lf?['0 `ڷ=D hZL+IX^:PQ>X= M5Ld {uzNQsDvOÈ3AjJ$`° xcaA@`# hG( 6_"0g ?U "0\g`9 \>[f4.LmlIMb`9h>3'kM"0-"rO`\N8 Um`ka]MD h$pMmq hE.o[@ &rA ʽ7nBٚV&Rz|f.vSsDj h[ 3j&¥8`c A>?/lSWӸ0."0."0\09)."0."0."0."0NDC@`D&D0& 06L! `" MA@`l"k_/Ffj`7 y#5L$1X+}hke2`5e~Z+k&r V케n?/.y2`5HLFތ?$Q`mK}@`w4:. - vS`DX*?h^sD-ڙf0z+I :m^´ jF[u-r_6S`DvT$4{P竀o ?H(X[ף`m?Z@`Q2'Ɖh`D6 Kx>&LdBw3IDAT8 ZB .P맠5m`]u.`R'M5XV@!')- q)˶6u>`GT_\?ZH5޷D 5&L! `" M9+9ܦ|ͽ[f6Xttȕ }uؔo9``^@\-W}iYIɴ/g3Ӭ@b"Q߹?  +B gOTaN4.nH iݞpJG7{ ;k?_č? ҂B0*ԹA5,< c7U 0~sU%ۭ1QK_a:]Kۋ54mRհ.C΢Q=9NàQO9󊀗V& -`\۱Z@T ?if5 ZoH^Q%ۭ L! `"lBUZs~ ]Յ/_!COubh}w>Z{׾8[ش/Wʌ;gVi5鵭[~o n\ؐs= VuD@M%yN@dZ9P^S{d% yht[: p~!`!:{ҋi깦[@܉o6"0V)LvYIXw`֬A@sbV!nO0ͩ XN/v˜ߪk[-b5w%^.\&Vz'o׿KXv4Lt݅ >͇pB]/*bjD녡l݉6r\opyy0C@`D&`Ca*v*vsY^ A9Ԧ#P {7n׎K4L`v)9>o `,x b䇀'`"6 ~tBL 1" n`" {mld߅ uCڰm[s`ߩ({YKo<ۻ3]7[@8"[/waљ}>PAVnۻᒕoOh%a|>}Z?]ٹBKT !06~)a" MA@`l"HT@B A@R5HI $UjT A@R5HI $UjT A@R5ՇV:IENDB`Bio-Graphics-2.37/t/data/decorated_transcript_t1.png000444001750001750 1332412165075746 22561 0ustar00lsteinlstein000000000000PNG  IHDRd }"gPLTEQͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ fU:d1isZy|if3ӘŬtx@)HLA2` S d$ 0)J&=*v4+'+=p!=H|T?.:;LAlx4+03SB iV2D?qKV+)`DGq[GLXҪd$ 0)Kf:\w%3 lFߠf{9{5_?K}T̯mi]2Fy1s]2__qUHLi]2~}_J#?LyT l{EY.7t/Y$HF^2/K0ϼH2_Хf؉d|My#$#˯=st[_.$ aVKo !fz@i ]wɤQK^p,st[_eW>fYfwnwJJFn|" rT[$>dfq|}}ndʋtϾkKs2P͠ ɴ^{ɘɘ_e61$0HfT~5U?F2>WJOy:̬ۉ_Ӿ&&~ҜS6=wĩ(ĉߘ^̩A_ aw&AN({ͼF$GUc:Frd>b#%ƥK#A2` g|p}!q.8tn}o砘sSH<~\^(691 /D1燧iXX PSH4̅(c}>z6%,۳Kru})l$s$k+//4sjdx pa#c͍d̺?憆d$ 0)HLA2`J-m}TUCR?-eZhEZ),JeF2[{Uw3m+YhӚZHPZKKxzG82}Z~NKU*UuS=25WdN9kX _-S~y4|dGZ7ٮЊsV[ӪdµOxŻc1ŕd,Md.QZ%n*ىTE Y2Q]l(Sk- q{宩n#/j~4+uʋYq^MěK^2OI:̯Ûth짢:%x]Y5Goƽ-Q2յ4,tepOQ&`}꾬}A2dǒ)+e}4r-Kj{>߫Z/"hU2E/|$E/%%3Si%S9Z TntQ1G#ԩhWe އg$mgua$'J69).8ySUfNMSj rӷ<=.|Qh.GP=#q{:W|:Y_H~ay۵[{!Ѓd;]qaI/ S d$ 0)HLA2` S Ҭd>xXt'}rhU2pXN)#}iU2K.B㒙/8L㒑 ,0nk`'~ ])@2` S d%}_|XrU=QѮd?$rdrm8c̥Oc&7-EYtiD%r:]uTig]Ʉ.k/,^lJΠ<,_|[cLdoAj#'0H9AsZ2~/8!MK}[w4 qRd )KKЮdR4 ]H`q$]ܚC ehW2aZ8H%Rȩ`9Ƒwdy~ҰdblfKDžodf%!Ӭdvr}r&hU2=lG>eiV2;ɠx$>I{4+ۿO)\V%$sd ;s C+=\ymA^])@2` S df%#/,?~PNY^ԮQnZ MnҗX-'^HH3Zr_*i˴^Yΰ}JQyr:ҊXnVάӰkE[h6>m9."#bA[-ᳱ|\GҧuqC)&v2/I_m:nWl6hdG̩JBK4,?w\Mw|YGK{bJ/aYRH&sȭ]>seȬo]tt<1v]_!=/a/<2&h%RHkOn%5nrnX?8풖LY.6l6zy/L Ѫdt1|5vģwդh*jMrU$OԼEʠ<&-ۘV.;)YuO'…@2 S d$ 0)HLA2` S d$3, 3 9e$JfbZ,5AܗS 30؎f%3$_ a1KnLiV2q+b0_ )lGQڐ^qr#: 8ǹ|d6#uyFRuV%S:! 0Y~!Ugl 1N#hU27|$vS>pe:?r=ɼT4d S A3`JÒ-z\A3Jq^Z%fږ_$#h^]`A3* KndH4-yNƪ(/ӮdįUO2(ޠ]a$b-mN2(  $b%W=MJ/V4+2f *JS d6frǙJ8HfcH&֥] $8޺m9)zBʔ,A#[bs2" O&#2r1ƭịKs+ʏjDA2qpr=l;0yy$︄:9|<:s KʐΉdT OdՒ+[suRr5!8)H##1a,fGQct:ͶF2o '\1Yp d$ 0)Jp>*6;jYO-eಜ;ӮdnA2` S d$ 0)HLA2` S Ҫd~ya/Ҭd\ҋ̰iH)=HF4Lp#ؐ%2djn Jf?%0lN Eܭd6qDm,OƭLXНdʏOV%S2 &#H)Kf9µh]2O/݂A2mK#$3p38i4ӥCBU**өO&mkUZ5v٨V]{hYYJ|n\tڝW,ܢ~U+w֫`xK8ȏ[u+͜髧GpÚ~Vulжg}3۰] xԻ\7H~BιaؼKή9e{/S9t>&x9_7ܚ@~}W jй6׀.a*h x&יuNh"b nX˵a{u_Jxa1 PљH]܊. >>&$S>{٨M*Ye\*RhI%nkm[aeqG&{'_7"9]$G19P)y6N&1ZaK:s&)蝙'š4ꣻv*jyZJYi2;+*k"*J+,ZW+k覫+"G/-~ l' 7G,Wl\w ON:!,00l32L#T|@-7LD'4F+MTWmM\u~udm]h-rnm+x]Potn'#b6*ozk烃^觓޹=:?:˾ο_?|?{3{[iag{jwk>O=~C~~*P4- {4[9:ʯ{b&HAhI\B:N$^0/B3H@~t%1 C5ْp7$p<NH((>Q-DK8;W2p_ Ò9ms#}h,105`CG鑊X#s((ƈLDb7f 'BBϮ4tL S`5" Cʍ2Pi?4*Q}ZA*qGu3+BHMi0*՜Fu^*X:U޴rV֬.d\ͪTd?iRY*˝UXJמUiZ W_%,_?¡^ۊVrvdϊ'Ѳ`YVv]b%ptu,nkF-j M$H}mkEOtSܟ"ϭnS:envX|׺U+Dϻ]>ֽ/x[P]-~ܤδ^k"]O`5&FVtp,o8녦v`{(; c#SL㖭 ]&Kd5n|5#0YPkeaE½.O$2vWݽo{,^2n[f5\gϾ=ֳŤ܎E-JC5.o[EН)MLJWk!}[9{Τ. դMa]9֡tﲑAka'lOBvmnzf 9r[ّfn'N7kvӛaVg}Z[7[ ;k43no /8W}s{kwܗy^'| xRv)_x_7}籕=/zC.5^5^{'$"}v^'M#Ͼ۾ߏʹuŗQ~iÛ{Pvv|l}|0J'f~ h#p}|~WhyHhGv..w'X!5ccGb13w=y'v90eǂ%({H#*7**x8|LaOH>ąXdP?y7`/S](腥/Xݤ@d(ćFSh(omhxyȈf舜󣈉(GD؁gU}GxC4h(hwY(_hIȃS8~$Zh(HC}ŦXƥ؇hሌ]5~(Tɨ3\vb}>nY XhXQH[琥 yɍI%Ɏ8h."9s80l؋H)9•By0;)?y'T6LO2Y=)IMٔEIY49"UaWGQ_Ra eycyVr92@Jٖ*9\ɑ"3 I i藃iɘZI)阘y6ioɓ izy+i閩)I铵 )ɖ )ُ Iiٙəi阴虂I|[nK9ٜ٘晞YAx|IڙIQ}y왙9YejY,(iG]5Y *ɡIIiI| ,-+':67J?ڣ0* >zCʣJ*L;tP:`TMKi)kij]YJQ[ݩ|a:8Y١\ielnrkZXxy`٧~:jU*]Jv _i: zʧڠpj &Z8jUEJ;D7zʫC|ت:5zz8׀g :ib&ʨJjZ*꫖"乮SʬjگʪFj6$劭㚫 "zJ ʩ sb[JT&XvJzڮ,k"+5۲(˳?o7 bʔڗ K37۫{syڴZI;VZK\˴O5G{o[W;_BD ȊjKNw;;[%۳k@zc;˷*0[F˱nk9K+ y +{K˸6[68k:Š}˹++ ׶뺧'W[ۼN *ǶK۵pkd۽˼r+[k ˾ػ񆯜x LkvK;FJl![ % )+,kӛ!¦۷È&7苴>Lv6:<ul 1,'KQܦI BēħvK(q3ųX+9L`4 rf l,}\v<ā…ƒ ƕČĀLǂ|0|wȎɐlșȒ\~ɓ^ȥ ۻɖMʍ ˫!;ʭˠȹ˻ܱ|*ɔLȢL̤|ƨƣD4ˬL͜|i|/Z|ͷ+n,ap-}5/ ( lb: $d1=`N&&ȶi V63[T).WhW2|!aev&+%V|lLm -qׁ ؊,؅Mا ׊lG э-{SA3|=ʬؖmL׍HG"o}ك=A)]ـBxL}عlۦ٬(vZE-=ڎR$ZM}@["۫1:;W1GhB׼ΝߒBýڞ+Eޭum ٔ*Mm߄YM YK٪=ۤ<̲+=~n!.C*^`qEq`}(NT6>dL㔱@~5>~EDMIaG^RMONUD"V.BUCV>dR-^3$|$>w]ons -m]&N}uM׬qN+ǂ.Wy菾!1>. EtN%=魮a\ލѶ^" ᝮq vގ 롾>-m%t}^ພ ~BFG+>~\ΎcR~~>NnȞ.oknҝs$_8&K0^DM'-e/2X4_Bvan<_c>@?B?)VM&̕dJ9gNy1͘GtiէS]h١MneݴpōG\r͝?]tխ_7iخ~vxBzݿ_|ǟ_~)3&֨@ 'B /0C CtDk0D"ܰE_1FcD!;sDm@1țXH#D2IkT7!mIvAW#RI-.Ҷ*lR2<K/dM7-sJ)F4PA%PC-.S;TFr)\SI)U0R4SG>QuT5[QK;=LQYR$5\ 5^F}bqm57aL%bo-pYTkUaSZYcEZg5Unle.?iɭpVrTh/]mwyPVU$6l,&NZw`+58_|e_!ة3c^ _a-#vڐIyߟinDa~f_q^h[uء.:ᠿUjR#`nXjܫV};S50"ҹF[ā>Gl//\_'<9̾5p;zOϞIcxaO7t7]k?|x&^q?xWgWdD>{Nvz|o/ϙ9ߧ>yD~Tyzw?F^%8A8!}k`x瑏6}D#!Ѕ/[uBP =Ї?C!p&a#Piˠؿ'K(3T"è*j*#l(+m$XB'q^4b2ww#XA ҍ d6?J yF5}Ԏ@LŒv,AHns$%v7yr,$A)SrT21,f\cYYr$MlD))Lhڲ+eg>mf-Cɺ[mS\Jvf<'ʩBcfJpN3t?O,I΄sl(8Љ. GE:R*L]$ZKs) eZ@:<_MS.t?N1zl洤1rT 'OҜWS)JIUKZ WS|`(T&$alEY:ו5sB6hWcى 9ۺvb;; ɮ/KսoaNe7mn7{>na 넻{7ؗx*,u\+;&oF|m3ͯ砣vw|Cx:쓏| ~_~o|su }w_=7v}Cŷ"eo=׿iGO?wx۹sKӥ rۮ;3@Sd- @<=| A @:=6A4GDDF\@lR"DH|DID3#J\âQNlDP=TԽUBVtELCsC+,$BZ][B;d@OT#b#ct?DKM#E[G\d_tB:ƚ E),Gt^GC/@$1ot Fdu|#|w9FnFFjHl% \HCLDH\Eǖ{z$G{TɔdIsB| yG IiIF$qzeɝOILJ#sIŕtI_|ʗ$(lJGx|B|H{ʖJJJWt^ň,o^?c,^=-&bMdN`]d@.A>BΘPee%F\MeRte2&dZޏQ~eTe>dd>ae-6N~OF6[17&fDCh\]scnco>fvfwfb>gyNgrvfx.gzg|g?gud{Vu|hS^~Uh~Vhޡ chg:舦8ȗE^iiE4&0I.iV阖,#&ini>jhTnhk{ۜFjngVꑾj^0Rj^jUj}).kwjiNkgkg^kk>3jFl:}.jHf_~efXea읒M`fkBl6}l7llXm8,mmӦ֮m m'Nm^Fn4|m.nnn~k~nnnn`m>monnin>o.o^onnoi~oi0npoFg>p7^o.opkɆO Ooq^q iq'kkpܫEmqnqaA"mq?rq /e!(Wpwl_2r$r/'67s:-o~(m =/Wp47t nLr8Ў?LOtR uUDCd1OU7ou uu#L_BGtbWaOsKvGC?vcWf/d^uhKqivjgsmwptovq m_vvB'`Gw?k>gu_wfww/q&ntPxwwsgwa? YZEkA~5qrW|wQvxu yg?w?iGxr*yOuxl[y/w/sb{wx?ztϯwxzszyx_u_zuK_GtSKTz?yoo'E{z/{'Exm$zy7|MD|^LJwZg/v/}^{}_o7}xowڧzַ|'}}O_y}zO}?}}w~/w}}Ο7~~6fOgܿlN;(A bCFH‡1ď";82$ɓWzLٰ%ʘ,A|)اAgJ;ШEߎskKܷ޻%/`ѧu^ؗ'w듕Nkŧg~{հ5~ww`|"؂{ҧ~n6`1 !%(p0&ʨݍѸ,A 9$Ei$$M.QJ9%UZy%YjYd1v9cey&i&g\c9'ufkv'}' :x~Y:5("E)j)ii/F*ۢ[M꩟[:$.*:룠Hושi+{,z Z^짹J-b-ɬ%Z%N-u.z.E[poJ/P<0|⫮ {0 v1_\s2oLr"Kh6.? 2G18몲<'H@gsBws,htLLHt6MuP#֤l`R\f/m\Mnǽ.R_l7rW ~wbmI!xSF-BvڀxhK 囇lW 6:Svw<{^rOMxւ {טG]]A^z䮟k^W.ȴzv?KywՃ+?wo=6)_2o~ǸTw>~Yg@UDt:`>AI~],x5 2p,`D6N0|4Bΐ2 MXCΰ(!(! 愯!;Ps,zmo}C$BG^"=+fnZ`5Sb cߡYTۨ=*fcH摏d)9y03~0#, S03 s0C,&>1S.~1c,Ӹ61s>1,!F>2%3N~2,)SV2-s^fQf!!e×Ӭ5cFlf3ǹv3b }NʟAІ0Mg>Ў~4#h1YΕ43;3Ζ4C-,GUsF3MVӺֶFro]׾5-a>6i}i9[x-vv=I3s,do{ 4OMrk8fw<[ܰsa-fO/n7=hyߋwC 73|׷Av^7%w'%~}0yǼ^ 9?>w+6yӱNj{8ӫxw:^g~v{T_;ڳ>t|O!{ծujw.wp]³!_.t_*y>|%wy3>#{s|uԙqӞҫaF߿?s-|=]_t7ҟ#ﳿ*}^R:*_5Y\Z[9 -5 >Fb IՖfAXv`I V I Q```r j Y2a n  5`fr!Jb!IIFI:I~6 !!  "!!""&"."#6#>";Bio-Graphics-2.37/t/data/t3.png000444001750001750 1137012165075746 16277 0ustar00lsteinlstein000000000000PNG  IHDR(0@PLTEQͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ ʅ߁M8艛,O@DIwzoAT<' "RATbQ*z^nuBu7 A<͆, <' UuТX])hv۱6M 9]&rMON8M5''_:20Oo*5p[N"Ԕ҂Yp tCG aP9=,cN5˟'c#PLh )=hͭ{n-C[;tĮ*Clif@wtXE(?&]֎ݒ#%Ӷ&f*:cT/sڳ~&7Y]yT&N%YrcͫK~ T.L:]K%~imIbiےe*hX;{~a;{@t?,tGGCwE?E]tvYD0r+Awt@wt@wt@w!~4}c vSAwюHt7a&t@wt8{8Ҥη Oro?\e1ut_zϡLXJe .ϵ?[F d-{U޳Dw>L$BRu)%8hD0Eńu,[ɶ6gяiK9dVsGݷֲT\6%}t? oXi_-ᶱaɭX$eٺadZN я{rZ"6u!aQ-ϺڔI-+)K및,1ݍz.oaQX8 Y?t:Ke27e#ݺgPՖ.K]/az;+.5خDTsUo݊-yjE+lN XN 9T}Hwl蚨뢮exv2m If}s;4Nݹk2v~/V2ha;N߽k:;@;tCG;tCG;tCG;tCG &\.A.umQo;/Y>i/usCGV8=CVOR8pKh.@gF<U?3O,¤set}Yu!^]gselm͖ʡlT7U/ùҫuhn0-~J\[ )8tx `f^]gs]yyueU[W\UD1xClh`)gu*ܟc/''Co?r6)4kU}6”l|Bzkiꭏ~7ß5e5 e,7Y#Uƚ{FLR|F4'ݧa1Jr?b I.h^G-+LV)mktϬ[}Q[? ؝fγ+w]&6aYLjj2%ݳUA[=*8BIf~~P2~B UW SHno}筇v$IJ+[d-Һt~t_(g~Q}ܨ:t_(ygp U=3ܦ~/,kimgV(r' b}^͘s=;T[8tv 4#;;3ߠmwDǵfV~8[)*tCG;tCG\wu k:h}yjL2=,&KLЮCv]xC$2K7:\4#_&Uޮ{$u#uS urh}w|g&ҙ&{{bho 9tttnCآOmDxhA;i==sAc0I\#uW[Cs Z-8oe;GFLg&GlnUO> Um}Uب5ݺ_+ڙAwh_0A]l)ݸR}_0Aپ#@ʎu+M78̄'tm tobI~Hw|t1tNݫ8Ɓ {-I};Zj9o'^sRvнݫu8MwbW$t‰GU#I}$Gu?9;W3"u?73ײw t?5+W{G$C{;tĹN"vh t1t@wtbzs*?aIENDB`Bio-Graphics-2.37/t/data/t1000755001750001750 012165075746 15410 5ustar00lsteinlstein000000000000Bio-Graphics-2.37/t/data/t1/version7.png000444001750001750 1170512165075746 20053 0ustar00lsteinlstein000000000000PNG  IHDRXL(c@PLTEQͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ MĂuXRx+zg# !\`ap,+Fbmw!dTJL.­\}bKCqӥ`bBt$֥Ê}-ۇw:{rVKg وE t^M,Jc'wVb]B, b$X&A3K/ &bAĂ:D,(!V+4+̇*c .c_\KVyO,bXeB, b$X&AJyN'D,(LĂuX "!b Xư }ӉE'4bِNKO,:/a:F,.DdB,V1XWg_ b$X&A,2 b!I"gӉ  bAUxX"bQƨbZ!b0X.nJ *4*4 *4 *4*֊UJAr/į0Xq V3'L b5zXD V3'̨b̨0XP `r oE+QD,tUB6U[U)͌*־KWTzX*yxaTVE K *CELSgFQ.X=Af"VOj&bX=AfF+hgFAłD,XP%*a q襁c*r <J/ B8MSziXv4Ŏ7D,v6bZMb*AusQXNEspl LOu3QI>XXGXeI<ΣA D,XP/D:g# !\nap,+FbFȊuR𘶔h|(ێ&lĚb 82[m! qOnj"XYʼݦ;b=XNz6b-+V dYR;Tz_&w%ű;. b!I LXeB, UbeXA%֗υg bA"ȐcK2Ȭ1%}=VV#,Gĩ֥í+N%oUqץXB+MN8IvZ8ջtNF-C=s!t0ZU.ALV=ĺtSn**nbyW XޣXeB, b$#i[K/ &bAĂ:D,XP b~ǒ"cH'tz ONtB"D:WtB! ':GNXtB=sw!:0ZUNALV=ĺ &&EĢYQ*AaPؗFAb_uFk/M]TzXm1`"/4(R@,^UdPK?3**wy\=L b5zXD V3'L b53XA?3* *T'bAĂ:D,\[J.5FХ͠bպt*1X.V,%Fkߥ3!.= *3X+V)R@,*E˥&!3¨byՃXD V3'L b5zXD V343 bAu"D,CĂe*oRA8kC!!b ?0X\tbX6=SSXZX6d2c:.q<˻X b$X&A,2 b!I"gӉ  bA"D,CĂuXtB#F1,tB_tb X6NKN,:ˆw k0YˆUfL'|b/Y LXeB, b$uX3ĂXu*aDLWcTJtTZ B%^uF^q^u^uFkE*E ZȠbԗ4fTU,zj&bX=Af"VOjfTfTT,NĂuX0X("]:j*VKGAŪuíPAŪuQfFkߥëR *VK{TP^v<0X+B"QEˡ")3¨byՃXD V3'L b5zXD V343 bAu"D,CĂb08t1b]9ބXpz}q[!)v4XpX^Ăbh";@Vd&bVabyDQ,Q ZN86X&'։$XE,BA,2XN usQX"D,OĂU" Nsk.708m#z]_d:R)xL[J4[>m bu6bMyNVepy6'X{5X,eXnp\Շ?uR'=K+Y,eYj*Wq˻؉ՃX$X&A,2 b!I*LX ^)VwDZХwdɒl +BrTVxn*ACdgK't$; -]:Y Yh#VS!p]:cyE &^b]R)y7c{7+ф^,ՃXA,2 b!I LXeXA%֗υg bA"D,ib"&A,b"&A,b"&A,b"&s9Mot_"-YLl+r*V$"#"iWSv֧L$aeb-g~ odn6ym~LdEAg6X=KəW~𱸗{LskμWuN#|4]9?w3:G^ ٨g 3SrTQB,zOɑ}ħΔۛ<כKק7헮O~)b!y#b-/֒b-/ZŠ5@_X5F$p3db"&A,b"&A,b@wl>IENDB`Bio-Graphics-2.37/t/data/t1/version13.gif000444001750001750 3444112165075746 20113 0ustar00lsteinlstein000000000000GIF87aXQͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ i4ӥCBU**өO&mkUZ5v٨V]{hYYJ|n\tڝW,ܢ~U+w0֫`xK8ȏ[u+͜髧GpÚ~Vulжg}3۰] xԻ\7HBιaؼKή9e{/S9t>&x9_7ܚ@~}W jй6׀.a*h x&יuNh"b nX˵a{u_Jxa1 PљH]܊. >>&$S>{٨M*Ye\*RhI%nkm[aeqG&{'_7"9]$G19P)y6N&1ZaK:s&)蝙'š4ꣻv*jyZJYi2;+*k"*J+,ZW+k覫+"G/- #l' 7G,Wl\w ON:!,00l32L#|@-7LD'4F+MTWmM\u~udm]h-rnm+x]Potn'#b6*ozk烃^觓޹=:?:˾ο_?|?{3{[iag{jwk>O=~C~~*P4- {4[9:ʯ{b&HAhI\B:N$^0/B3H@~t%1 C5ْp7$p<NH((>Q-DK8;W2p_ Ò9ms#}h,105`CG鑊X#s((ƈLDb7f 'BBϮ4tL S`5" Cʍ2Pi?4*Q}ZA*qGu3+BHMi0*՜Fu^*X:U޴rV֬.d\ͪTd?iRY*˝UXJמUiZ W_%,_?¡^ۊVrvdϊ'Ѳ`YVv]b%ptu,nkF-j M$H}mkEOtSܟ"ϭnS:envX|׺U+Dϻ]>ֽ/x[P]-~ܤδ^k"]O`5&FVtp,o8녦v`{(; c#SL㖭 ]&Kd5n|5#0YPkeaE½.O$2vWݽo{,^2n[f5\gϾ=ֳŤ܎E-JC5.o[EН)MLJWk!}[9{Τ. դMa]9֡tﲑAka'lOBvmnzf 9r[ّfn'N7kvӛaVg}Z[7[ ;k43no /8W}s{kwܗy^'| xRv)_x_7}籕=/zC.5^5^{'$"}v^'M#Ͼ۾ߏʹuŗQ~iÛ{Pvv|l}|0J'f~ h#p}|~WhyHhGv..w'X!5ccGb13w=y'v90eǂ%({H#*7**x8|LaOH>ąXdP?y7`/S](腥/Xݤ@d(ćFSh(omhxyȈf舜󣈉(GD؁gU}GxC4h(hwY(_hIȃS8~$Zh(HC}ŦXƥ؇hሌ]5~(Tɨ3\vb}>nY XhXQH[琥 yɍI%Ɏ8h."9s80l؋H)9•By0;)?y'T6LO2Y=)IMٔEIY49"UaWGQ_Ra eycyVr92@Jٖ*9\ɑ"3 I i藃iɘZI)阘y6ioɓ izy+i閩)I铵 )ɖ )ُ Iiٙəi阴虂I|[nK9ٜ٘晞YAx|IڙIQ}y왙9YejY,(iG]5Y *ɡIIiI| ,-+':67J?ڣ0* >zCʣJ*L;tP:`TMKĐZ\ڥ^`b:dZfzhjl kijq" mzxz|ڧ~ڥoݩ|uڨY١piez*Zzeکyt9:j :mJ Z*ڠ* z &Z8jUEJ;TZGժzڪ:ȗת ޺W{ 8Z J&$*zJj 늬*ڰ溰*ZJ;:j& jjC+[\ʱ Jm/۬z??f;+1kK#{O U+ ˴ꪱƪ %;ꂿ`ʶcV寫5v*~[\z۳TkJ}wK3˳77[ʸ)K;ʩe[s}⺶C˹u[}+{{;+{sn_{nK b۵۴{+;KKūǫ˼ۻm +h&۹۸Үؗ{軾[;9!ˉa{ۿ0V+B û€dL?̽1î<{%YL[C `M,k!ǻ_|ƭtL5nNVwgcALƈȌ&bZF`|2K}d+sr=ײm۹YԻ( @[-k۽ SmaB}պ< Ml#ݫ}V1@zM|HG"!޵M OmeQ}wrn`I^zv{}[y.13莾>3ڗ!>ΨNN+-t馾K~wnu>+E>S~}ŻV,XN>_ \&.]P.qw.=dc?Et6~>~bT.nE,ORΟ,-OO~^VHWON(>/ntT~_~ĥ2!^"5|++MoJC><Z>D^Op O=4i^gonϷr.3^zD{^Z??{rm^~]1~?y`cu4Kl]nOl濯?k>Sooe/Ogۯ_oR?ʏWoޏTjF^0 0!` tCBaF=V"H!IHQT̖0mdSK Sgϛy,J_G>MҨPN}y5VN~*kX\ˊkδJծZ ;x7oP?=0E\`8+1aʋ#_kqē7'6ن?-7d=cmXk&7`ݷ}ׄM[xo57YtrAuniLW\u펻]{]廎;r;}^蹳wힷ-H#7cK:@cBeBc 9lOq;q5܈> F[оFAdqz<" lr|1JH+ 1%nr.4QDPMTMT15R7l9 < qFG4RI'RK/4SM7SO?uRDQ+SW_5VYgV[o5W]wW_6XaTOFLC.YlXiZk6[ms5YrY-1YҢ6]ue]wvo YP}_8t5\},ԄU]U8b'vYz x܍[bG&d`-ДxφذPgfo9gwv43W Xv?饕jZErĮdK1:i._tNTVfXv6 gny>^mOq/7r|q3))2rI7R]e1fw5Uc}tGs5k2L=iGy=/qN֓_{w~돎_"q+=΍/c@MzFЕ~Uc`wAO xG2Ep! h?!Ѓ B4IN2d_X>p-!zȑb-!1vIgE 2M"yH 2g_c8FLaьWD̍o,ʢ.ngTcSG>Q`r";qzc"/@Ɛ;$ ID.ҒV#&Qё19JRJzEJ1,e,eYS͕&J2җf@B+!@V$$ Jh63s5Ljfδe5y@N[fIsSޔ1j 6۹Mzvs䧄gyS42 ZNcs Ct…9Tф=wPԢ%hB[Egz`JIϋ6iI4u}I)ӓԨi2Qu8 NWeiDT3NjW:$2UU*X*V5 YՊΩvF׼ʕokQ*(|.5:a+6ֱ^zHXJlPYv֏_هVN#NPYQmjYHR"M BMLmU[q~bmlqRN׻".WoJYB*}jѻ^ס%W:ޣ}CK"+oo`_N6nUi \`w,K1pl2Xzjyb8,5_`4ZO#_X17x41 },b?F0cz%ȞL埒K! D~2'lN 2d_^s_TЈ`ٌg7K6sVm yX͇2=9Y2+. d=oK/]ϭҏ泦'QO9͉FShF[p;ԟ|ή'xjUQٮkL3z1͒le ذvr_l[cHԮlyvp,Ƶ,y3ݮ6z7y߁umnĕޡvhm{n8 N[\rF7m{_xA<ƫ|*KpJ{-G;nrx\x$:y*h'Yyq1:K ;;{==,B+AE|A?AA\I*PcB3D4B2;MOOO W!OOU -MUa .%^vb)."^ ^ea_b =*ncb&>c3Mb&c>cAA7f>@>;FbFv:9_EvcGc4Qʺc#DF䓰01.ee%d9-KNNeO^2.\&]nd˸|dTdR+~e;ѽSZ&[cCdd,t=-fU>f:fdeͬ3 SjopVXImqgx^duve}f\ղa^hhvfw_FMPx^Vh\jgXwn}~hK456Nhfeub/V.a0.hi~䎶VfbivioLk6iN&n\{>蠞^g2ꨖjgn~jie~iigfihj.k>kYigipkk6kk&jg>~Vlvl&kflʥ[ȶlj6E)Tͦll(blֶmϦmnkjԾ؎a.n׆nlv:DvnnNnVo&yVoon?\ofoowp ?m.7o mp o6 pHiN^qәgkDޱfn#Grg뷾qG#O)r(^rnٰ*/''G60G47+Sn3vq(ppps8p><=7t GtW _t.?FsAsBDgLwtKgqNG_褶jQwkRorSN.uW?uXOOt y%& %}\'eTՙUm&qff`Jf|ch||agh_N"u2cN꘢Q^*ff:犡"蓟~iʪJiZkjkz*Ă 6l,κVddBn˩v[븷kR5؜ɞkZK֋n oK_̒K.mpˋ 1t0ǐ \ĐRLoZc,'U)_p2_372 F}{*~ܴ:g{h.L&w2-q|g1oI[pEOMy<0bk6H. ceݯ8Ȗ 9{>ߪFoyϜ~䙟>괳z绻mko+<;Y%sy[=?Wߓo_o?ۏ#z}< pǹU,}A o(/ 3x% *\Cp} S*̃^!Y*!$Cl` dF<"l)["də&4epK 6OJBIr&{ OF󌥬)KKSgpxDS;{f/] S1vo~ lb>1̼ X2[%H?YQ6t|kƑ=]hNYg1-9MoM}bXoҝm]j\yuujl>6e3~6-iS־6ms6-q>7ӭu~7-yӻ7}7.?838#.S83s8C.\-))s\%9c.-G6Y~>9%cl=2E':Г3F?΅^SVQnso^:{.s=f?;?^s+HO;.*g;o;/?<3{qϛC|.m[_9|Kԛ{oW'[߶z滷;u~}/GG?壿sw}-3o{ON?}}|O؏_v3_~uU_E)韷_ zmu n= J`` 2 Ơ ~`^ j޸^ B_` !- ۹ ``~ ^Zaa ~_N_]EvJ!j]"aM _!r"¡"!#=m"iry"&fb]=#&*"b,"-bUIb۽.U./T0-N0$2.#363>#42#Fc406f6n#7v7~6 859#::#;c:;5b1J5>448@8d8a<&B.$<.;!==#E^d2bFf$@G~$1`Hbc9:Id1`JeTeH_NVU^eVVO=W2^&dFdN&eVe^&fffn&gj\@;Bio-Graphics-2.37/t/data/t1/version9.png000444001750001750 1371312165075746 20056 0ustar00lsteinlstein000000000000PNG  IHDRXsCPLTEQͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ z 8 *E۰{8{ [cpzsxzx,5DZ/ aXɄkk9( \Gn59α뷖"bOSabnz!}VŪLޗɁnњn|Khyv?Oկ9T9xXeSXt`c*=rvm2Mּkvu A BXB bz"y)hfu}",bOj(.SZJmգKc*ۅ~BJɖ *% `\}bt=5DZ.u5բUMNrj]:}-;b'Z}.1,]:tİ.Ǣa)K75г_׳LkJ'WVbB b+$X!Aʖ0D3K#0,3` և,V~*aaGU*E^09!J1{PJ_ B؅=xz~ {{pKA,b{ =;yg;B*mg]j>Vl;G:rbZ1ym%bMZ6yE b!VHu v\OϋXX+$bMz`c {;RRY"aaUv Kéva{i8bkKXz} {;v vXۏUƃ|2,vt`ĚeێKŚhn.'B@F=$X b!VH,Ś6xێK Ųp{X>Hm.SZKmգKc*W9bʂǼiTzm 7JRne>XXfsrv]MhUvӳ\{5}RKU''k_|gIPnG,ٲY2y,G:1btC)Q=u=4fD;9qb~e IA, b!VH BX:Kl K4:>c `}غX1lKX.V.ڂ-cB,$^s[T[v*UY5Aؗw9u ֛K'X.g]:ŪL޿? P-ZM?ϱVߢKg K]:1lKDZhX}rʍWC9g&hFy-A, b+$X!A, bER, E,XĂa `}XĂ=Q,&U1bVaĢa6a6J,Vadz4E*lK* bѰ ;#&i^?5"VGE V;XAv:X,buwV1X."X>i =}-|簵V*K ڿ Bأl `EfբazG,؈;bei=A,ڿ`|#քUMsct+H v#X;ɑE,"Nrd`'9ONrd@ X>,b";XXÞÎp!b-ǧ#bk8ƩvX5]F/ {{pUnk9®1¯#b3ǂ!c`îc5 #X9fǶG&)"b$X2yl%ĚXn Bb)֤v\b(;sXĂE,X`sU',:!jzˁƊUjRˈXv@gc559XQveU3X6}`YMh,bE/-B?5ٲ]AZkckoZ")׌Xk@Y8/r+Zݷ![њnˬhy/Ċa _E곈5Ԫ) 5{h="b!bXX>,b!VX4VX>,b"X ׫f6X塗ƣf 6XeפUkjvdUiڮTْ&֯=+M'V]fuf͖l6}\͎l:V_tja3~مM-lMӗCMϤ}N+:b!VH BXB bN+]&lb"3XڈUjkGCqdE*ejUkThjG,+VTMZ;iٳbq'-{VT#wҲdNZXXS.5DqDYQfYbjg#"VGE V;+*hJ ֞E,XĂa 6XXVXٱbڿYMwTjUk*gVl;G:rbZ4ym%bMZ9yEש b!VHu v\OϋXX+$bMzUڮ``sX>ҁk 1I%2U v#X;ɑE,"Nrd`'9ONrd@ X>,b"[;\wpѰlmi{q*=ڿ 0[kB,l `#& {XbH6F +|cgV bjg#"VGE V;XAv2U w, ",bOBX$$EBX$$EBX$$EBX$$R>?Y>d7BhK"r"YH. *e+Fٞ  eVp5'l.7|˳ xE+$$EBX$$wJmޛxmsj??^jo/hegۏv|U+k_}ի-owتsJx2ߺa}i["\njFUퟞ۩p3 O0V{ێ;ICs}U9a{[sƖ bէ_pOö6w5۲DkԿϸ-nGM,B:sE繷&t>/vd;%O%Xu9b;b -b -#^Xן# bMm HIHHHHHHH[[@7 IENDB`Bio-Graphics-2.37/t/data/t1/version11.png000444001750001750 1367712165075746 20140 0ustar00lsteinlstein000000000000PNG  IHDRX>=PLTEQͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ bu}m'ǫ@E[eR\B !dKc_~./8&AتKC [sҹWS-Z}I.VK38[wb8yGWz\ݷҙҥCN [q,Vͷl&jWOY&h''N{bB b+$X!Aʖ0DJ#0,O,b";YÞ409!J1{PF_ B؍=x1; {{p1; {;RzzÞΝ3ǂ!s`cu #X9V-ǶG-@K+$:Y{d;.'E,BWXK<}m%bY޸=E,XĂam*-:!gˁƊUZˈسb\{VT4jY>XX},t6VT+ϥd4XX VtEqDYQV/kYb5gk "@E V?+*hJ ֞E,XĂam*tXbѥcNJU1g5jup)b5jupƲbEŪt>ˊSz,:)Vc/VT+]:XtXb:b-YűeEZgk "@E V?XA~XXQ+QVS,X{` ևEmU]r=j`ӉUZ[fG6XuOPfK6XȦil~l6hlfkjvdӉ\S YsP.lj`lbfytb^,xv BXB b+$uXe `YĂE,X` ևE,X a=^5Ţ氅NSlb X1,Ģ6XtB#V [MF,ЈvjvaSf˳dA, b!VH BXB IbˢcM,XĂuf 6X@gc=ĺ_n9XQJk!bbhZb?lXO{i,:)^ˁj՘skZXt6V_rb:b-Y_űeEZ<gk "@E V?XA~XXQ+QVS,X{` ևlƊ.;VTB9)VY7\ XMZ]:ŊUwvUͰ7VSvg5Ū'exb^ұdŢKǒ5 ֑k((+*%} V?XA~X,b Yb5gEXbڳX>dr; {;SX"ð簇*rOĂGjp)؃g^X3Īzi vXϟx#ðs'̱`g\edَK}  bRE?zcqX7`a `}*Eǫ@E[eR\EWX/1cZ6K ,y2u?{2*!\ TVe[{7ٺg)wz'}kW'_<c;X;|[{ jlXJ@ޯL+NNX:)X!A, b!VH BXg-abfXG`X؟XĂE,X-x[ҹ?`KXq_ [ZO9mup)|mt;۪,͚CŪt%铅Dkc[]:"B5G.g?YHPOTd{9[taҡK'-t8 [6s$φrum?eXWXk XB b+$5"SDJ#0,O,b"X",btBÞ$аs*tBE'4lK'4lXtBg鄆 NhNhĢvG:1bX-k}+ V?XA~X,b Yb5g0),bX>buqasfg=m RYhX}Ue[_{m]o2 ϶ۿ8cFL (K b;#V,kZÃȦ[AX簈epY28H,b$G #XɑvArdA X>,b";YXzaagUx\ `'U7q) ֯uMÞcy.{vΥsX'̱`g\eֿ/b!bBXY{l;.19,b"X*~5@gcE*Uejh,|xbWext6VT jb˖l?)V=y.kZ7Yt6V_rb:b-XűeEZgk "@E V?XA~XXQ+QVS,X{` ևE,bڿXhcE*bڿZbڿ8cYb_ejb1yv+*/KV_,ڿ,Y`YM,Xb^+b5gk "@E V?XA~VT,(()=X",b۬ܫf6X5[*Gfn,Wlf2њtb=m,ُ&2͚-lbՓYCfG6XU:5۰?W ¦ 6&Kg1J'Ug+$X!A, b!VH BX'.Q6` ֙E,XĂa+-%G(K*븿ŖL8fƲi5R?&eV$+&著XX,O,Cbb"4`UWbMkZv˝E,B,a  |XBDh `}XĂE,XmU3bɳSX; R%˪hM\^sXbU$[܊d;b-X1l˱hX}f+ZdX!4VmYB,B, ˇE,ĊK4 ` ևE,XĂa `}XĂE,3vU3bɳtB:w,bbFN=XY:o,bbF#Y,Ј:gkbqObMA,w ˇE,B,a b͢B,XĂa `}XĊd _VE[~vEkqiѯ9EYlERتk@Y8&_V$[![њl5ygeVTْ&֯3+M'V]fyf͖l6{ W#N*mbjvaSfP˳kγXB b+$X!AJE(X",b6bĿڑ+CYQJk'bbڳZbOzeŊIrb$-{VS,eϊ{IZX,bˊ/;VTB9)V\\ XMZ_ŊUvUͰ7VSvg5Ū'exb^idŢ˒5 ֑kR((+*굢 V?XA~X,b Yb5gEXbڳX>,b:UW"09LJcÞT8cjB, b=FÞΝ3ǂ!s`cu #X9VmǶGƜT+$:Y{d;.'E,BWXK<}m%bY޸=E,XĂa LtѰl-ֶ]hX}S+g,bM\K!Qrv/ `'9V>r)ްU-V_ w0yG,أ%_4>p2asX>ҁk1ZAZ8ce[_\ a/Ă=̶ڿ 0nB,؈GjE곴 _X>ҁkŒ(UlGX,b Yb5gk "@vH *",b#!A,"!A,"!A,"!A,R>>>'BަE&EA("(Fy"wxB^oc)^C; b  M1Mj~xw:A[.fo{m'ȷXrUΟ{ A[u>%~ooݰm_"\jFU=KLa?>Ɔ|7b۱u'ɫbc|_u4*>{{sƖ bݧ_pOöAZجEܖ}WUf۲ +}?}mۿù ӿSm3o_E,ĺ=b+b] 5b] 5YX:'X!A, vpHHHHHHZ7ۧ IENDB`Bio-Graphics-2.37/t/data/t1/version14.png000444001750001750 1402612165075746 20130 0ustar00lsteinlstein000000000000PNG  IHDRX4gXPLTEQͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ iLP _b56rq܎hq]n/Dz}\‰zXoql'0y_'Eks9楟9xl>Oկ9T9x\eSs_viaa_K"9ѵ0!9ktu A BXB bΈEs(hF¾b `}XĂR5Bv;{(ֺr[h 6m.5+W~.ZM3 --q)|qr/zڿ)5DZvuaZ*k\RglnO6O.%ڿ&ڿg[5У?׫ kNj'OVb]B b+$X!AĚ-abfX-0,+` ևE,XrXI^6JϻB,F O\ aWE_ϻװMb>J^öu+ { "V>ygC*g{U2,zX[EQzo~\(֠Wtg bd>}[Xn Bb)֠o{o~\b(;kXĂE,X`+=VOXlXϫ-z6VTTZXFX,b"Vʽj6`g+==f v:R5;7+Tjdgach͎tb6&qcf͖lbYCfGv:6_ןtjag\%T ;Xqlb]kx b+$X!A, b!VH"ˠc4X",bX>,b"X]D ŢKĢbXZakĢblN=bufiG\%T ;Xqlb]|xw b+$X!A, b!VH"ˠc4X"b-z6C=+*V*-C,#VSR B+VS^ˁ밗rgc5:XM wn7xbmy/@ꋕXlXXC֗TDqDYQF/+Y:ĪgDEAzNY:ĪgEXbڳX>Xe3VXtرb%tYMϺRjUIW/l/VTK'`G `5*w ,zVS|^z6VT͖.KV_,t,Y`YMXb^Wbճu"U"։ V=X'X,bbճu"Uϊ%űe5łg `}b>05lORam+e> ƶUR-bzi8c+A,b?F^öOޙckux`^b՟ &,mk^۶85h\]N7X!Ozߖd>ƿ/b!bBX5^۶ey` և}!V.ڄ]8^5.7.ڂMK}J7`Sa㼏iY 6K,ىqj`yϖ]>KjѪ?s8yo3i6YRKiX}r6/䋖gy>VeaӾgɠ¾bkn%jGW&h'Nџĺ(X!A, b!VH BXW5[(Z`XW,b"[|܇Mt;߲ ^eXWXՕXA, b!VH BX:#ϡX%V E,XĂa `}XĂE,X,kaE'4lаb .tBFNhb .tBFKtBvbŢX&.?RĪgDEAzNY:ĪgDvÙQX4>X"bUqװX3g E곥ĥ] _.]&jѰl3l`[%ڿ`;bۉ]Z`XW| #; RĺE,"Ard 9epY28H, #8 ",b mr,aa{x\ lfXǸvWZSaaZǺ¥v˶8K}l} XUƃ[\e vTͰVSrcg5'exgcEl2ˁ+o,X`YM,[Xb^wYbճu"U"։ V=X'X,bbճu"Uϊ%űe5łg `}XĂ[D!_vX/sVSRB+VSRg,+VTXM+f9гb&nbElidŢ˒5 ֑kb((+*赢 V=X'X,bbճu"U"։ V=X'XXQ+QVS,X{` ևE,X aYWbGxlN'V*=@fGv6Ʋ|Z͖lb:l,ّN2ٚ:n,ӬْM|7kN'sN6b՟jva 6M˗p Ϡc4XW^B b+$X!A, b]$ttf `YĂE,X"ٔV*:~^ĊbSX_sXBcYiՍ˦~budweV$+&S? "XXX1leX쀥VU4VMkEA,w ˇE,B,a b͠B,XĂa `}XĊc׹W͈%>?LţfRdSa(+HdOC,5sŒzq,bud!Wђlnp+Z'eVy_/=+*fK%/_XXZUEqDYQF-Y:ĪgDEAzNY:ĪgEXbڳX>,b:+>05lORam+eƿ/b!bBX5^۶ey` ևE,+JE곹X:va]n Y+q)me? |omKjѰRy& g,؀;bKKhX}YKhX}viaa_ˈ%BCcxee 5,b$G #XɑE,"Ard`pűhX}` ևE, wv+Y/\4>V&XlK!l3ڿ ]/Ămf_1yjѰ_gh.-0,+vX-1EH@HHHHHHdIO,6ݿ.ҔzH?biǠ3 HSHEzd)D,%[,cwҖW7H4;$$EBX$$R}ޛxk=վ|^;X=ľ'ȏXrUyZ?}_Sk 薞otfTsfBaW3e;Qoľc$uUC]ϩ=O_?\+ #Vt11+]_y g=lϾ u7H5۳+Vq{_~CB/aϽis:A<CvN'}YB#V{ A5u"bڃXn7\i R"!A,"!A,"!A,"7bIENDB`Bio-Graphics-2.37/t/data/t1/version2.png000444001750001750 1233312165075746 20044 0ustar00lsteinlstein000000000000PNG  IHDRX>gP PLTE{h޸_k/dp֊+pؿd/OOp@cGӇ2ͅ?ޭH=""piii༏k#Uiݠݸ jZҴܐ.W\\EE<ڥ wFfͪ**r̀""i22p zAiڹKz|Ԛ2PUk/H̘հ`[:WK=XXlX<}c0 .?lɩ籭%[X뉯]g]gn3gr^O7b8~*:Tx׫tc զzq>81.?_cmuƊ:+U]nXÒg&V#m*SwGb %X.A,r b!KkF,2_J(>b ߛ bRk #I!Z`aplfbBK!duJ,.Ʃ籸X.z.GA=ۧ. CSNqz]:Xwb|Xp^\ͻұ!]:ObѥcF95_O +X'%X.A,r b!K,M#X Jժ Ī+XĂkKT}q<=cbcX<=!kt UaucQXAhVMZgB,@{d?*'Vq  bDR{l?* BĂXX("Ej*V"{. B%&\V,%f\RbRQgRQgV6dRXUL*VH}@gFYŊ.X3Aa"Lk5&bX3Aaf+igFIłD,XPx)Z"bѥƬb5tT^©PIuQafkߥ]o)&ߥ=*(/VbfkCn4(R@,n*2X1Cf+&bX3Aa"Lk5&bU&  bA"D,WFN,/VLz%z;kC!!bYzbSXN,҃X>]SSXdB,VXWڄ'f],xv \XB, b%uX3ĂX "!bAĂ:D,:˃tB# gXtB#>ĢNhB2&kbʍ:28<1\O%X.A,r b!K \X'U.1\N,XP PG Jt5f KIvp*bRz4-~TpYAo%V&zTP^~#<:*ֆhPXUdRK?3**Vty\?5LĚ b fXD 0k&5LĚ b 3XI?3*L*T'bAĂ:D,X\KJ.5fѥͤbt8*1XEazIzu  "I"D, )D,%VOz~m3  "!bAĂ:SW/ Xǥ@iCĂb8B>5ړ^ 0,8mNpCb=VeCĂ틻]s-ڐ ZĊ:DQJN(E,rI=Gzb ^ b!K$ 1A D,XP PGFEĢ^YjTaR:e B%&kXb)1X2bRh,SgRh,SgV6dRXUL*VH@gFYŊX3Aa"Lk5&bX3Aaf+igFIłD,XP PGFEĢKYji3XSkj*־ST~WX_O)U ѠH5ȤbZ %~fTUZ~k5&bX3Aa"Lk5fV&~fTT,NĂuXJ/`6Lzz;kXF*bY犝zb\SXN,+X>]ӬXdB,VXp'f]zv \XB, b%uX3ĂX "[2rc+4 IJc5eUˌƲVk,\X|j,CCQ'e{f ra 1YˇUn,'P቙zbEWtB, b%X.A,r b$Vs9 bAa"D,CĂjT"(QD,ZԘU֫D,&۞ũPI5iQafkߤUiL*VI+zTP^oX]Yڐ [L*V@PgFYŊX3Aa"Lk5&bX3Aaf+igFIłD,XP PGFEĢKYji3X/NJL*RgVxF83ԙT,ѥάbmZHXTbό ]+b fXD 0k&5LĚ b fX*Vό ՉX "ԡXJ<6I ڈvA,<}rIS!\]운XX&-ĂbѤ]ĢI !kt UducQXA՘VMZjB,@{d?*'Vq  bDR{l?* BĂXeeG=./vYaM\S!<˶mk XWTx% "+<ȫMwĂk]$}J18*EIOz)N!bA2 bBĂe:ĪImw"bAĂ:D,XP??b]* =W6V,xKS!{n?ƻa5r}[W..[/n;h]Qvj7ѱ86ߴ ~*|zϧ?#Qhώu*w]"ݟ o{.i۞Ug5r~4;]w=FnoϢ~*տP=]/}w])Kw6y?lگi~vOE,Z?XyE{ź^{źa2OE,Z?XA,r NuPL\X%E\X%E\fC3IENDB`Bio-Graphics-2.37/t/data/t1/version2.gif000444001750001750 2216512165075746 20031 0ustar00lsteinlstein000000000000GIF87aXL{hpAiecG[QG=2@(p9hQ5*MbɜF*RKR:U׮`*,ٳYǦ5ֶp>UWvu7n߼x K8pa&x\-+6Lɗ%k\7/朙gѝQ@hӪOÞ=vjڷG^Zҿ}ܱ@- "Wq.z?^{/<#V}y>{h!Z7>(U~:KE\~ -^ "$nW(,~97n5.I<&@)FFyqY8"TrEryQ'f饔N%^IpvqIsڙ'ujfH&y栂'}zt6ZF*餔Vj饘f馜v駠*ꨤ^Zꪝ2[A 무j뭸뮼+k~W 6lfvL׵kn.n+o+kV;U,˯V0S/J/,Wglî ,#(Z')0,4lެ33I>mtHr9tDC4S/muTtYKUc bthlwm{Օovwj7[psy8VʹywdZ#.4_㬏%h'^kNz_fz# 賂>Xޭ>QOG٫ړo062b]߾~y.s?q9NxW7Nd(0y6z0TGHBKFK W"0U.ؽ8 gjl N }Hqa)vDpPa(*l4dSŅao[bĿ Qh Q4΋Ha H:lI;G8Yd)Bnq$d" DE62|"')Jb𒁔[&-IL:ғdP$VPvD)3Jahqe,U9TC笷 ZrҖ f-Q82)Lf.qR^6>ӗt6 hʉꛑMl4<'9ɎL3<9}ҳ|5ς hP6|hK=Nj2юѠ,(/8BJh&J]*mNUSGR>7iGxd }Hu6$A=T{ӆ|O\T˕өzZUI̩WB'hX#Wx`RT+` nMXXX.6ucWNֱͬe3ȒaE ʎNܬjOYv6`g)aZֶ̮Sm MۘLso m ̽ 8Zw΅k"v.inqw6h]֞{u+񲗼ց.~T͑~<xT p3 Uᨘmo` /.\7LQ 1M+XU[ vؾ qFa4fぇ)xFqKXG2A KK1ce*kMsfaN3#,涤h2fl~nWsK OwVs>:]/wFNyw2dzј4< =e*IϠ&4L9}^;[@]j9ӪtIkKkzղk"ZSuhW<vǼkBTU6Kla\Fl>ۺJyMXmHoYeMu6Mn{7k$8-p'j~G|\xÙpC9;>3R"8!lywnKr_rg~<4>+O=B_ۜH/:N>[}Qyt'Zt̷׎`g6*B uZ*߽y߻kXZ]Ó|)u=n{g,zě}O/wa{ޣ=/OX;u>?ߗҷ@~}M}yz?Ĺs/->[~at~wxDucyd\uԀ w""Q}-` *,؂.0284X6x8:<؃1yd>XFxHJL@'A)؄TXVxX4Zb8d\xt8e؆npHgIiHTxzHsouX5zI&vj؈8$^xj؉X8oHITWȈ"w~7;艬4'a<hx9ȋV犌DӋĸZsOS}8g5ȌgʼnH8Qck(mܸu{n؇8gXl8cqmYhaH@lȏx LW ii(eِɃ{Hd|ّ?"zi%y,09j$g3j!9Oh/ٓisGE7?Y;MdF:hȓUًW <[T9 9ek]iLI-TVqɕy`rYt SYyi9XlsO} p_ F?X3?"Gڱh :|iN;*9&BW;{Iaۅ6eZ?;#lzf [v{Qq ۷jR[0 K۸Xh[{K{;ۮyMfӹ{fx*+Jzu3 6˶Aseۻi˻u+mZʛ[:Ϋ !+Y˩(h!{,k׵<[9X8k}<+n4ݫFۼ#o۴}PXT>(K ` -7۶ZQĪI(\Z*.A4\6|8:<>@BvDŽ\Ȇ|<ƈH)ɒ<||?1!Þ3[!\L©|,JʲLnʌ*uj!t,^[,jʆdzs̩ר˧|Ԕl{h鑪1K~zͭ\| &|Φr#@h{7&̓*< * wĚ$]5<Ѭ4 e&m# 'M)M%0ҧӒ5D8-yӋlo<{@-xcJ=z׸53=RU]OVS WlF#^ I[u ie6Z jIn]v ld ߥ}fИUՌ~om=uٜ݊ QUP ծRw|RFkN-ۅڮ}b 렫Qm)ܕٴMȪ]ۂ#ʝź m ͱZ5ۼPU&MڠEmڝW}-={a c}k߬* ; NR :miM$Rضu<+ލ%z 27[m7^ h,1.i'j6|K.GKd:3`nweN7x}͸o>>*^xs+t.u>(#n\>bIp^諑~&,&NHZL.9~y{^n5蒮Tz9rT^>TX]~QWíi^캾rÞZ -eήо.S>.>Nnށ7n^ d}D~N\^oޤw}5~ _*{OnSۮ6U," -?7@E:)?4o^} -/JNl,shV_PoT`_]?Zܬ^Ofd/j:IgOvϿhii=/N'b[:boz)_zn "o\OR?U?_?@eĿOooӟڿ8n__iq (oTB@ DPB >QD`IF9z#G$EhReJ!Wl92L5QiΗ:{9ПCsEj4QI>%)ԩR^uj5kUX~5VZ{(Pe6nZh.޽nνWo`| 6Xp9/>qcʓ-KYʙ9_ gѥ;keKЫMfjس[vnڽ[[n^;ʼn3GysHS.]9c]sW/z?n^/1c&>~x׾|a^꽏C&|75}ſ?/~/ؗ?id(? -Ђj?GOHs^E Bp+ SA"Ѕp` W(C5` iBސm8C!paVD&щ=Z15ъMb]'żL"x1^v\ 4QdۆFEj,\=QvA|CR۽яc"?is$)(IC*ratċ%ND$(IDe*QyIFd,O)KoamGA:RtRW@JyhKW:S4T1}LkJSr?iNЦ|6E*Ob5e|QJԩpyS h7LԬ2EPU8ҭXjS<%iժ\Sq8j^jH;nkbUsYJA.lfVd) Rsw:E-(ϺӖH^SmC;TSֶ3ka[nխsTkR6׹Ys˼B.2겊fWەw;^ +/F۹7{ _#WE(}]2poݩ߇ .}t%qWx5qwyE>r'GyUr/ye>s7yus?zЅ>tGGzҕt7Ozԥ>uWWzֵuw_{>vgG{վv<@r{v:w~z|x?< W<s|E?zg~8Kzַ^W=]?{{A/q> wGG~_/χ~?}W~}w[`@?/| o?S|i~O{3@#@;@K@@@@@ + @tSAA dtA [ <,t@+$DB"T@3BlBTB*T!tB#@*'(/l%* @)<&TC,D9-È;9B;= @.?1C6t@lÏ84)C@CAtCƃBDH:8EDdEDKI?lE%4J$4DCRDW<9]4D^D4;l"Y\C_d,eDcD`7$DAb u>sy<~v!|[aַ֙EqyV:wvÁĸ{xJ{X̼VUz &&EĢYQ*AaPjp+TbPjҰb)1X}iKAŢGAŢGQZJbV)2X.% F˻<j&bX=Af"VOj&bU  bA"L.Vh%EUfPj]:B!֥SGU}zK1X.QAy*[lo!UyAbҨ"PQaTKAf"VOj&bX=Af"VOQ QaP: "!bAIJTa$bJkK}Ttb($DdKO,v鹄bIJ᝚zL*IJ!kӉuy{|9XłW$X&A,2 b!I LX.>N,XP  bA"D,CĢ,H'4ba NhIJ!З0XtB_tb X6=]XZX6d2c:. v<dA,2 b!I LXeB, Eb< "&bAĂ:EĢ2]Q*B ^*V08uFB8ങ{Ӄ۱5EV,ǴD96[I-T-JASSGX) lC QR\W}WviXGxO.C:aXˊbp,{Ԏb5׫]vqAXeB, b$uXb&VxsaD,XP+;XyVu:F*BŠu8պta۩ʺw]:u,|t䝎dgSK't!; mĪ%{l&w%Pދ=zK X&A,2 b!I LX=b+h0l"D,CĂuX ^)Vq,i[+b =tB: D'( NAzH' pzވE' 3wz 3UUkCKc?%{l&5]=%B, b$X&AH{ފ4^b}\~6 "!bK#$Xsmia<׋U~Bc-j]:[1X.:]γ Kgy6I.VK.Zub>Dҥ.A,t\S(VS}XޕhN,ĺ(X&A,2 b!I LXW-fbX_>MĂuXPBWh VljUj \Ǿҗ­Nx_X_XMbh#֊UD,*8L,rQ=w(T6AK$:Q=sXu4X&{<* BĂX0X7("Ej*V"Uۗ[UۗKQŪKXJ *? *?*֊UJAr/į0Xq V3'L b5zXD V3'̨b̨0XP `r oE+QD,tUB6Uҙ 1X.?*̨btx[AŪwx ʋUb~[ 16b FT,ό ]Wb5zXD V3'L b5zX͌*Vό ՉX " #$PBXCXj%8kC!!b \|bK%L' w#`V! Y̘N+m3.:X&A,2 b!I LXeĺHttbAĂD,XP  bA"ЈeA:k W0XtB# 鄾ĢNhIJBL*IJ!kӉuye{|9XKB, b$X&A,2 b]$Vs: bAa"D,C o+QD,*UR+C,EíPAŪQfFkK 2(4ޣbyxaTVE K *CuISgFQ.X=Af"VOj&bX=AfF+hgFAłD,XPUx+Z"bѥƨbtTZB%oU,vgPFAbuFkE*E ZȠb4fTU,zj&bX=Af"VOjfTfTT,NĂuXPBpzǜ^8Fby<ŕ^npǃҰbbzi ^h"4FYXUpX^%({<*Q˩hnu b!Iu"{<*'u h L")_G]yT"( bSa$!Ěˍ# N7=]_d:R)xL[JtikS`mbMe:?TNepy6'X{5X,}%Amwvub>4:YڈX!'̒g)K*VS}XޕhN,ĺ(X&A,2 b!I LXW-fbX_>MĂuXJr3?.'oeYc$Kz-+XGXj; ʉSK[VJުqץXB+MN8IvZ8ջtNF-C=s!t0ZU.ALV=ĺtSn**nbyW XޣXeB, b$#i[K/ &bAĂ:D,XPMAIIId?|!'3i-vS S1cПQxENeڊQdDB"C2m*z'p6/7<᝜˫H  1 b 1SR6?.g. :Enbݧekק78u<*֕ oOdr>gH_7СuL)ot}xK #ֈkxkx VbRB5"X& O)& 1 b 1 bWpn]lԥIENDB`Bio-Graphics-2.37/t/data/t1/version12.gif000444001750001750 3444112165075746 20112 0ustar00lsteinlstein000000000000GIF87aXQͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ i4ӥCBU**өO&mkUZ5v٨V]{hYYJ|n\tڝW,ܢ~U+w֫`xK8ȏ[u+͜髧GpÚ~Vulжg}3۰] xԻ\7H~BιaؼKή9e{/S9t>&x9_7ܚ@~}W jй6׀.a*h x&יuNh"b nX˵a{u_Jxa1 PљH]܊. >>&$S>{٨M*Ye\*RhI%nkm[aeqG&{'_7"9]$G19P)y6N&1ZaK:s&)蝙'š4ꣻv*jyZJYi2;+*k"*J+,ZW+k覫+"G/-~ l' 7G,Wl\w ON:!,00l32L#T|@-7LD'4F+MTWmM\u~udm]h-rnm+x]Potn'#b6*ozk烃^觓޹=:?:˾ο_?|?{3{[iag{jwk>O=~C~~*P4- {4[9:ʯ{b&HAhI\B:N$^0/B3H@~t%1 C5ْp7$p<NH((>Q-DK8;W2p_ Ò9ms#}h,105`CG鑊X#s((ƈLDb7f 'BBϮ4tL S`5" Cʍ2Pi?4*Q}ZA*qGu3+BHMi0*՜Fu^*X:U޴rV֬.d\ͪTd?iRY*˝UXJמUiZ W_%,_?¡^ۊVrvdϊ'Ѳ`YVv]b%ptu,nkF-j M$H}mkEOtSܟ"ϭnS:envX|׺U+Dϻ]>ֽ/x[P]-~ܤδ^k"]O`5&FVtp,o8녦v`{(; c#SL㖭 ]&Kd5n|5#0YPkeaE½.O$2vWݽo{,^2n[f5\gϾ=ֳŤ܎E-JC5.o[EН)MLJWk!}[9{Τ. դMa]9֡tﲑAka'lOBvmnzf 9r[ّfn'N7kvӛaVg}Z[7[ ;k43no /8W}s{kwܗy^'| xRv)_x_7}籕=/zC.5^5^{'$"}v^'M#Ͼ۾ߏʹuŗQ~iÛ{Pvv|l}|0J'f~ h#p}|~WhyHhGv..w'X!5ccGb13w=y'v90eǂ%({H#*7**x8|LaOH>ąXdP?y7`/S](腥/Xݤ@d(ćFSh(omhxyȈf舜󣈉(GD؁gU}GxC4h(hwY(_hIȃS8~$Zh(HC}ŦXƥ؇hሌ]5~(Tɨ3\vb}>nY XhXQH[琥 yɍI%Ɏ8h."9s80l؋H)9•By0;)?y'T6LO2Y=)IMٔEIY49"UaWGQ_Ra eycyVr92@Jٖ*9\ɑ"3 I i藃iɘZI)阘y6ioɓ izy+i閩)I铵 )ɖ )ُ Iiٙəi阴虂I|[nK9ٜ٘晞YAx|IڙIQ}y왙9YejY,(iG]5Y *ɡIIiI| ,-+':67J?ڣ0* >zCʣJ*L;tP:`TMKꤲZ\ڥ^`b:dZfzhjl kijqmzxz|ڧ~ڥoݩ|uڨY١piez*Zzeکyt9:j :mJ Z*ڠ* z &Z8jUEJ;TZGժzڪ:ȗת ޺W{ 8Z J&$*zJj 늬*ڰ溰*ZJ;:j& jjC+[\ʱ Jm/۬z??f;+1kK#{O U+ ˴ꪱƪ %;ꂿ`ʶcV寫5v*~[\z۳TkJ}wK3˳77[ʸ)K;ʩe[s}⺶C˹u[}+{{;+{sn_{nK b۵۴{+;KKūǫ˼ۻm +h&۹۸Үؗ{軾[;9!ˉa{ۿ0V+B û€dL?̽1î<{%YL[C `M,k!ǻ_|ƭtL5nNVwgcALƈȌ&bZF`|2K}d+sr=ײm۹YԻ( @[-k۽ SmaB}պ< Ml#ݫ}V1@zM|HG"!޵M OmeQ}wrn`I^zv{}[y.13莾>3ڗ!>ΨNN+-t馾K~wnu>+E>S~}ŻV,XN>_ \&.]P.qw.=dc?Et6~>~bT.nE,ORΟ,-OO~^VHWON(>/ntT~_~ĥ2!^"5|++MoJC><Z>D^Op O=4i^gonϷr.3^zD{^Z??{rm^~]1~?y`cu4Kl]nOl濯?k>Sooe/Ogۯ_oR?ʏWoޏTjF^ѩ  0!B* tCBaF=V"H!IHQT̖0mdSKSgϛy,JNG>MҨPN}y5VN~*kX\ˊkδJծZ9x7oP?=0E\`8+1aʋ#_kqē7'6ن?-7d=cmXk&7`ݷ}ׄM[xo57YtrAuniLW\u펻]{]廎;r;}^蹳wힷ-H#7cK:@cBeBc 9lOq;q5܈> F[оFAdqz<" lr|1JH+ 1%nr.4QDPMTMT15R7l9 < qFG4RI'RK/4SM7SO?uRDQ+ScW_5VYgV[o5W]wW_6XaTOFLC.YlXiZk6[ms5YrY-1YҢ6]ue]wvo YP}_8t5\},ԄU]U8b'vYz x܍[bG&d`-ДxφذPgfo9gwv43W Xv?饕jZErĮdK1:i._tNTVfXv6 gny>^mOq/7r|q3))2rI7R]e1fw5Uc}tGs5k2L=iGy=/qN֓_{w~돎_"q+=΍/c@MzFЕ~Uc`wAO xG2Ep! h?!Ѓ B4IN2d_X>p-!zȑb-!1vIgE 2M"yH 2g_c8FLaьWD̍o,ʢ.ngTcSG>Q`r";qzc"/@Ɛ;$ ID.ҒV#&Qё19JRJzEJ1,e,eYS͕&J2җf@B+!@V$$ Jh63s5Ljfδe5y@N[fIsSޔ1j 6۹Mzvs䧄gyS42 ZNcs uCt…9Tф=wPԢ%hB[Egz`JIϋ6iI4u}I)ӓԨi2Qu8 NWeiDT3NjW:$2UU*X*V5 YՊΩvF׼ʕokQ*(|.5:a+6ֱ^zHXJlPYv֏_هVN#NPYQmjYHR"M BMLmU[q~bmlqRN׻".WoJYB*}jѻ^ס%W:ޣ}CK"+oo`_N6nUi \`w,K1pl2Xzjyb8,5_`4ZO#_X17x41 },b?F0cz%ȞL埒K! D~2'lN 2d_^s_TЈ`ٌg7K6sVm yX͇2=9Y2+. d=oK/]ϭҏ泦'QO9͉FShF[p;ԟ|ή'xjUQٮkL3z1͒le ذvr_l[cHԮlyvp,Ƶ,y3ݮ6z7y߁umnĕޡvhm{n8 N[\rF7m{_xA<ƫ|*KpJ{-G;nrx\x$:y*h'Yyq1:K ;;{==,B+AE|A?AA\I*PcB3D4B2;MOOO W!OOU -MUa .%^vb)."^ ^ea_b =*ncb&>c3Mb&c>cAA7f>@>;FbFv:9_EvcGc4Qʺc#DF䓰01.ee%d9-KNNeO^2.\&]nd˸|dTdR+~e;ѽSZ&[cCdd,t=-fU>f:fdeͬ3 SjopVXImqgx^duve}f\ղa^hhvfw_FMPx^Vh\jgXwn}~hK456Nhfeub/V.a0.hi~䎶VfbivioLk6iN&n\{>蠞^g2ꨖjgn~jie~iigfihj.k>kYigipkk6kk&jg>~Vlvl&kflʥ[ȶlj6E)Tͦll(blֶmϦmnkjԾ؎a.n׆nlv:DvnnNnVo&yVoon?\ofoowp ?m.7o mp o6 pHiN^qәgkDޱfn#Grg뷾qG#O)r(^rnٰ*/''G60G47+Sn3vq(ppps8p><=7t GtW _t.?FsAsBDgLwtKgqNG_褶jQwkRorSN.uW?uXOOt y%& %}\'eTՙUm&qff`Jf|ch||agh_N"u2cN꘢Q^*ff:犡"蓟~iʪJiZkjkz*Ă 6l,κVddBn˩v[븷kR5؜ɞkZK֋n oK_̒K.mpˋ 1t0ǐ \ĐRLoZc,'U)_p2_372 F}{*~ܴ:g{h.L&w2-q|g1oI[pEOMy<0bk6H. ceݯ8Ȗ 9{>ߪFoyϜ~䙟>괳z绻mko+<;Y%sy[=?Wߓo_o?ۏ#z}< pǹU,}A o(/ 3x% *\Cp} S*̃^!Y*!$Cl` dF<"l)["də&4epK 6OJBIr&{ OF󌥬)KKSgpxDS;{f/] S1vo~ lb>1̼ X2[%H?YQ6t|kƑ=]hNYg1-9MoM}bXoҝm]j\yuujl͡>6e3~6-iS־6ms6-q>7ӭu~7-yӻ7}7.?838#.S83s8C.\-))3%9c.-G6Y~>9%cl=2E':Г3F?΅^SVQnso^:{.s=f?;?^s+HO;.*g;o;/?<3{qϛC|.m[_9|Kԛ{oW'[߶z滷;u~}/GG?壿sw}-3o{ON?}}|O؏_v3_~uU_E)韷_ zmu n= J`` 2 Ơ ~`^ j޸^ B_` !- ۹ ``~ ^Zaa ~_N_]EvJ!j]"aM _!r"¡"!#=m"iry"&fb]=#&*"b,"-bUIb۽.U./T0-N$2.#363>#42#Fc46f6n#7v7~6 85.9#::#;c:;5b1J5>448@8d8a<&B.$<.;!==#E^d2bFf$@G~,`Hbc9:I`JeTeH_NVU^eVVO=W2^&dFdN&eVe^&fffn&gj\@;Bio-Graphics-2.37/t/data/t1/version1.png000444001750001750 1173112165075746 20044 0ustar00lsteinlstein000000000000PNG  IHDRXL(c@PLTE{hpAiecG[QG=2@(pqZ1Lm=;3Cg,a3ZU=,y&^bҞwS=WqҚbyb!I LXeB, VX=o /}. ? bVFB6YV0*B[!dTJL.­\}bKCqӥ`bBt$֥Ê}-ۇw:{rVKg وE t^M,Jc'wVb]B, b$X&A3Ko &bAĂ:D,(!V+4+̇*c .c_\KVyإ]zˆwjk0YˆUfL'6|by ^B, b$X&A,2 b]$Vs: bAa"D,CĂuXNhIJ Ј5N+N,:ˆtB_|b } ӉE'4bpNw!b &kbِʌĺ2=>O$X&A,2 b!I LX.>N,XP PGJt5F KAŪvp+bPj4TQ8 ?Jz/XFxuUyAbҨ"P]QaTAf"VOj&bX=Af"VOQ QaP: "!bbފVXt1X.m֥íPAbuFoT,vQgPFQZJbV)2X.51 F˻j&bX=Af"VOj&bU  bA"11~hXpz}q[!)v4XpX^ĂĢE/ kE*h"k&W ʨ;Jr*uݠcXe|bȨ9J]B,:B,HQ=w!bAĂD,xX%9?;GIrfMnrYT Ӗ`nX'XS@*NOcY~+\^ 1D!I-s^M+V7=K߉Pݦ;b=XNz6b-+V dYR;Tz_&w%ű;. b!I LXeB, UbeXA%ַυg bA"ȐcK[dɒl +BrTVxn*vu !Њqӥw:NN.q,x)e{x\s||.1̼VUŢKgU.)JﱽXޕhBy/A, b$X&A,2 bEVX "!bAĂ:D,xXDZmE' ]B'УH'F,:N#cH'‰NQz#Ѓ{x]s||N1̼VUŢzU.)JﱽXּ{t @ LXeB, bz"y+VxsaD,XP/p~v0bͲb\/V !# NR◶xfunbtw]:|.CK'X.V~th>#ޓ]:cHF,tХsMFXM*nbyW];z b$X&A,2 b]%V4^b}\~6 "!bA J7_Xd>'VXp9J_ B8MS{ |sb{ |Sb{ 4cX+VAX09F]yT(Sڨ-B,DF]yTO,"b! bDR,DP, "'bbVX=1XVe Cnb=Dbus߳m=#ޓXgi#ֲb0K,#XM*nbyW];z b$X&A,2 b]%V4^b}\~6 "!b+8tAf,ʶЯba,>*'N.nGXv*yn]b"; 87]:y$YhT:gB*B Q̕?LJkUU,t1ZҍOɻ۫]&byb!I LXeB, VX=o /}. ?  bAN!A,b"&A,b"&A,b"&A,bϟOۦENe/TO,r&ӏA6F9i+F  ɴ)ټ0wr.^ +r*NLX$ELX$OJwۼ̻,_'tuw=3կ^9ZWn.#?x?əԟ#I~ݰC2?3?7G 7OBjȊl8hQQz~3^,=)R[cq/ե1ͭ1;_9uԙ2zǷzS~/E,Ě?X#rD%Z2^%ZXKXK #ֈ b. u>sy<~v!|[aַ֙EqyV:wvÁĸ{xJ{X̼VUz &&EĢYQ*AaPjp+TbPjҰb)1X}iKAŢGAŢGQZJbV)2X.% F˻<j&bX=Af"VOj&bU  bA"L.Vh%EUfPj]:B!֥SGU}zK1X.QAy*[lo!UyAbҨ"PQaTKAf"VOj&bX=Af"VOQ QaP: "!bAIJTa$bJkK}Ttb($DdKO,v鹄bIJ᝚zL*IJ!kӉuy{|9XłW$X&A,2 b!I LX.>N,XP  bA"D,CĢ,H'4ba NhIJ!З0XtB_tb X6=]XZX6d2c:. v<dA,2 b!I LXeB, Eb< "&bAĂ:EĢ2]Q*B ^*V08uFB8ങ{Ӄ۱5EV,ǴD96[I-T-JASSGX) lC QR\W}WviXGxO.C:aXˊbp,{Ԏb5׫]vqAXeB, b$uXb&VxsaD,XP+;XyVu:F*BŠu8պta۩ʺw]:u,|t䝎dgSK't!; mĪ%{l&w%Pދ=zK X&A,2 b!I LX=b+h0l"D,CĂuX ^)Vq,i[+b =tB: D'( NAzH' pzވE' 3wz 3UUkCKc?%{l&5]=%B, b$X&AH{ފ4^b}\~6 "!bK#$Xsmia<׋U~Bc-j]:[1X.:]γ Kgy6I.VK.Zub>Dҥ.A,t\S(VS}XޕhN,ĺ(X&A,2 b!I LXW-fbX_>MĂuXPBWh VljUj \Ǿҗ­Nx_X_XMbh#֊UD,*8L,rQ=w(T6AK$:Q=sXu4X&{<* BĂX0X7("Ej*V"Uۗ[UۗKQŪKXJ *? *?*֊UJAr/į0Xq V3'L b5zXD V3'̨b̨0XP `r oE+QD,tUB6Uҙ 1X.?*̨btx[AŪwx ʋUb~[ 16b FT,ό ]Wb5zXD V3'L b5zX͌*Vό ՉX " #$PBXCXj%8kC!!b \|bK%L' w#`V! Y̘N+m3.:X&A,2 b!I LXeĺHttbAĂD,XP  bA"ЈeA:k W0XtB# 鄾ĢNhIJBL*IJ!kӉuye{|9XKB, b$X&A,2 b]$Vs: bAa"D,C o+QD,*UR+C,EíPAŪQfFkK 2(4ޣbyxaTVE K *CuISgFQ.X=Af"VOj&bX=AfF+hgFAłD,XPUx+Z"bѥƨbtTZB%oU,vgPFAbuFkE*E ZȠb4fTU,zj&bX=Af"VOjfTfTT,NĂuXPBpzǜ^8Fby<ŕ^npǃҰbbzi ^h"4FYXUpX^%({<*Q˩hnu b!Iu"{<*'u h L")_G]yT"( bSa$!Ěˍ# N7=]_d:R)xL[JtikS`mbMe:?TNepy6'X{5X,}%Amwvub>4:YڈX!'̒g)K*VS}XޕhN,ĺ(X&A,2 b!I LXW-fbX_>MĂuXJr3?.'oeYc$Kz-+XGXj; ʉSK[VJުqץXB+MN8IvZ8ջtNF-C=s!t0ZU.ALV=ĺtSn**nbyW XޣXeB, b$#i[K/ &bAĂ:D,XPMAIIId?|!'3i-vS S1cПQxENeڊQdDB"C2m*z'p6/7<᝜˫H  1 b 1SR6?.g. :Enbݧekק78u<*֕ oOdr>gH_7СuL)ot}xK #ֈkxkx VbRB5"X& O)& 1 b 1 b@IENDB`Bio-Graphics-2.37/t/data/t1/version10.png000444001750001750 1366312165075746 20132 0ustar00lsteinlstein000000000000PNG  IHDRXs=PLTEQͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ bu}m'ǫ@E[eR\B !dKc_~./8&AتKC [sҹWS-Z}I.VK38[wb8yGWz\ݷҙҥCN [q,Vͷl&jWOY&h''N{bB b+$X!Aʖ0DJ#0,O,b";YÞ409!J1{PF_ B؍=x1; {{p1; {;RzzÞΝ3ǂ!s`cu #X9V-ǶG-@K+$:Y{d;.'E,BWXK<}m%bY޸=E,XĂam*-:!gˁƊUZˈسb\{VT4jY>XX},t6VT+ϥd4XX VtEqDYQV/kYb5gk "@E V?+*hJ ֞E,XĂam*tXbѥcNJU1g5jup)b5jupƲbEŪt>ˊSz,:)Vc/VT+]:XtXb:b-YűeEZgk "@E V?XA~XXQ+QVS,X{` ևEmU]r=j`ӉUZ[fG6XuOPfK6XȦil~l6hlfkjvdӉ\S YsP.lj`lbfytb^,xv BXB b+$uXe `YĂE,X` ևEgr bѰXsB)l6hXEstbѰzM,V+&4"dUĊaUB5łcuYt҉_ b+$X!A, b!VH$e1&,b:[rrbݯLlXXMZ]4\ XMޟXt6VT4y/@gc5jL޹Ŋ{{i,:/VKc9X`YM/Xb^b5gk "@E V?XA~VT,(()=XڈUX6czE+*VKǜ.NX.rbEŪtf)VKg`bՓhbMmUsXrc-ز{uyX+gX1-o^¥-=K5eH6Fsn%jWOY&h''N{bB b+$X!Aʖ0DJ#0,O,b"|<-t^˟E[%zЯpz-' Ƕt6mUf͡b]:B5DZ.z}d!ٚvbϟ,YsX'Ek=k-ttХtg-9gCL+ь\յXA, b!VH BXXY%'` ևE,XĂa DX ;],VaUhX UU(hXҰ $ , 'E*$t`cZ6xV!A~X,b Yb5gk ra$x RXĂa `}Z`ðϾ3; {ZA{tѰlp)=ʶڿ 0jdV-VmqƂ#Q/Ģ vG:1bXִ4ɑMa 9epY28H,b$G #r`}XĂE,XvXɱÞ)bm+bNnR;A_e.A/==8*5\ aK`Oޙckux`iUƃXEzcqXVrzg BOG|b_B,z5X!kяv\b(;sXĂE,X`sUn',:!jzˁƊUZRˈXv@gc5zvlXUc{`W {c5j7-,~VSz]:+*n,lXuc@gc Ăud5Zl+c%ʊze;"@E V?XA~X,b YQD8VX,b"X*YbeNJUh2g5jq)b5jqƲbEŪۿ>ˊs,:)Vc/VT+_XYb:b-YlűeEZVgk "@E V?XA~XXQ+QVS,X{` ևE,X aYWlvݣ ,bű٫fĒgQ3b)vVEK'!UњlbE8&s˫hInw7֓w2+Z bBcѰ,bbWɢBib^+bXX>,b!b!X#hb"X",bn?{ՌX, ~EkXXV+au"DXXV+'eVȦkuJf6X*]bƱ:}9,:FZ b+$X!A, b!VH$e1&,b:X>X%vd=JGVTZXFX,.VX?9cYb=mҲlXdَK}  bRE?zcqX7`a `}XĂ=S"]4>[c.V} XuWR{KC,IeUm\ 7lUg39cL ({a;{ >*}wwt`cZDhi #b.uXɑE,"Ard 9epYn7$G",bm]*V  ϶VXGVBl `/Ă=̶ۿ 6bjѰ,_'E$t`cZ$6xn V?XA~X,b Yb5g0),bX>!A,"!A,"!A,"!A,R>>>d'BަEl&EA("(Fy"wxB^oc)^C; b  M1Mj~xw:A[.fo{m'ȷXrUΟ{ A[u>%~ooݰm_"\jFU=KLa?>Ɔ|7b۱u'ɫbc|_u4*>{{sƖ bݧ_pOöAZجEܖ}WUf۲ +}?}mۿù ӿSm3oߧE,ĺ=b+b] 5b] 5YX:'X!A, vpHHHHHHAA"IENDB`Bio-Graphics-2.37/t/data/t1/version4.png000444001750001750 1652112165075746 20051 0ustar00lsteinlstein000000000000PNG  IHDRXLCOIDATxёHT b,XƁ} cm>sI@*b#Tb9npe BB{7Oӏlp7Jva@3NŸ^;w`A8=Ð.e[5pk9H_/-pdd.54 R9Qvq=h}C$]QfuN2홐P׏θѩєRWNsG $8e;]1O 8i*w'?9/iGN)󣋂3^x 0!v(~LP6pN)f@\Ю QB&mCL TN{%/r\u FPdl4?Ԣ L̳QX2]prj̆@}kH5ad+*) s|]v?~">7*U_{VqeVs1Qf#qNҶ~VnskXz]!!UyLm[Z{n#*^*1+ی BA!U9Yy(9@#%_p7Vz] t %ɩQ #@@nEwvg; @ aV(P/2e ,Ɣ6tuv{K*P"VoM+ӛ%FOgAGؾ! [vxؑ _{oo$5BB&M  4Fg!;tu=#f+{ 4A@h!,Ct絺bB:](~ )3KqB]:tTm"BFҎCqs%{ BlF5,br芄{Se=kiǴ{"i4F2@@X k=P sAHS{Xmtf  4A@h!5JS̲Ҏ~SQ A  4A@h! BBzb-Ftʱ' 2AHS_  ݥKnoI%&,h!!vC%&iJοuxװ~ +Q#O-ceڦ#n  )$U' Bb#vtB)!AKmQB&M  4FiYv::Bc**WU!BB&d@ AXO0 K*ׯS@xnEwvg;ϗ 4Ffz&ԭ+Q#XN aݦkaI6ܝ,#2A> AXW>LP`G>Ckd( BB&MY 0u:ºØJG  4A@hMisz4WUvnI 2K*iCqӳwWi0]Z2F`&tE=j˩1n#v{U ^*w? §,T_v+o?uE|ߋܨT}ARJwF/bs>TiGX㜤m #7DeV/C! BB@hu?{#<ڵ啽:GcUZTbV! BB t24;{s` abCڐr9ec-|pOhոe8^[f<> :2Ε Kv&M  4A@hy֨I$C*̏TTj=-Tޯ> QB& |Bh >8fq}&6pz8S0YnոжsGMJ*ZCc ΩXt7߹'T/f\O}j4r7uKXF9b\`ĩQB&# ޛ[x%ˇ*І<){ol;3~p@h,qN][f> :b|JsPq- h* )oL/T/̸9ZͩљeWJX7F 㺻WDu{e%ܴٴ'xZnNJKDG8+uyԻö~ 'NBz)_bj G=[;N=& SJß{o+ y?oCM  4A@h$h K|pGl**5WSGq*W~( BB pas]nݒ LetY.xIźkU_^#m(nyzsHjQ_KKW(,TdHG`95fM>un 0KQATA nǹ}H^c~?{S]/SJß{oz5 ~"?7jT߬QAUv:Iڶ*:|n OT[k>D  *IVs:£]K[^{ϭs=ZK%fe\}A?H  *'>%t+. 8aJ u.Ar:95 'v]x(n9Zx3| Mll}q!P,SHG`95f*tuv{U՛&٨rwa3a^s19@ /FM  4A@h9֭Tj>! BBfLP cW.1@p:M{^p!6]twIu.;-<) l@2Fvwz),SHG`95lǦ#۴[RIO"znoZ,1z> p :aݺû}a?cEIؗk&M  4A@hfVϜCw6zF0 *5V~Ah! BB3YFkuu,P, 2AHSf ^Utx-D %漧K0b4%_؍:kXN  ̧F˖{2mҎiZEҔi*e!AH;^{:vpm s6 FM  4A@h)fkiG?^{T M  4A@h! BBsC=эV1tD#t:~ )EZCҎKnQB4vÐ{xHW}|ˀ4%_؍:kXN  ̧F˖{2mҎiZEҔi*e!AH;^{:vpmР%նa( BB&MY4,;`-!1+{! BBfLP< cW.N@p:M{^p!6]fvIu.3;-<) l@2FvwMv),SHG`95lǦ#۴[RIO"znoZ,1z> p :aݺû}a| 5BB&M  4Fg!;tu=#f+{ 4A@h!,pd:XrMfKGXEwUz] [קi%ɩQ \ELu}nT L)ƨ\EL5~nԨY+umUt柨콷^|  4A@hU^#t hV/5uG[{JJ6=~  4A@h?_ft{ol ,[l@X.lE TRףlt9ݾk+L^Rg[aWpY6߹'T/r\ EB>5J9Hʛ:%,FK,v 43sŸ|usjtgYR*M.8nՅ"|{^Y?7G6(V Sa਒R6:q]OĉPhC/ \L|gkǩ|[?ćyЮ6І%u8#+{8 +{Bpj! BB{7=cm&By%5Nb\_kI T2L[5pf=-G[?bu1' Ε =j}15zжSy3]²<5 ̸ ZW=hUkϡ.]15q S)nHp(}1fomww}|cT,s}p$UNDs^sӎRJGg`'Bh85Ql _#{+ ];'mB&M  [| K|ÍUTT6|blRy#N  4A@hN ! BB&M  4A@hN)SJ?EZƛ ?|jmsN.P0&٣`+;PލB)@u3ߛJO@v~О ,炾`T852ϮC]Y@u~f=BOP) G  4A@hsA|k F7? vԫfhm<Җ#(~lwC^;<< }VyG?|~1S)!Pt;??3}Gܥ{cm !39] l.yՎ[HO0G;j7qѿ}[GumrCvFWGg]Ƽp<ճi{owtتl ptpx(XG+3&S3QYCJvTdgv|աACF؀k7F;V9=Oh8ts;dk@)?ތw;<7_ /3??|ӿ l mB;5Þڋ2b!.xTCFxS&휖NK&Rʧ7m0li'85 @h! BBZ{Ps-IENDB`Bio-Graphics-2.37/t/data/t1/version14.gif000444001750001750 3444112165075746 20114 0ustar00lsteinlstein000000000000GIF87aXQͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ i4ӥCBU**өO&mkUZ5v٨V]{hYYJ|n\tڝW,ܢ~U+w0֫`xK8ȏ[u+͜髧GpÚ~Vulжg}3۰] xԻ\7HBιaؼKή9e{/S9t>&x9_7ܚ@~}W jй6׀.a*h x&יuNh"b nX˵a{u_Jxa1 PљH]܊. >>&$S>{٨M*Ye\*RhI%nkm[aeqG&{'_7"9]$G19P)y6N&1ZaK:s&)蝙'š4ꣻv*jyZJYi2;+*k"*J+,ZW+k覫+"G/- #l' 7G,Wl\w ON:!,00l32L#|@-7LD'4F+MTWmM\u~udm]h-rnm+x]Potn'#b6*ozk烃^觓޹=:?:˾ο_?|?{3{[iag{jwk>O=~C~~*P4- {4[9:ʯ{b&HAhI\B:N$^0/B3H@~t%1 C5ْp7$p<NH((>Q-DK8;W2p_ Ò9ms#}h,105`CG鑊X#s((ƈLDb7f 'BBϮ4tL S`5" Cʍ2Pi?4*Q}ZA*qGu3+BHMi0*՜Fu^*X:U޴rV֬.d\ͪTd?iRY*˝UXJמUiZ W_%,_?¡^ۊVrvdϊ'Ѳ`YVv]b%ptu,nkF-j M$H}mkEOtSܟ"ϭnS:envX|׺U+Dϻ]>ֽ/x[P]-~ܤδ^k"]O`5&FVtp,o8녦v`{(; c#SL㖭 ]&Kd5n|5#0YPkeaE½.O$2vWݽo{,^2n[f5\gϾ=ֳŤ܎E-JC5.o[EН)MLJWk!}[9{Τ. դMa]9֡tﲑAka'lOBvmnzf 9r[ّfn'N7kvӛaVg}Z[7[ ;k43no /8W}s{kwܗy^'| xRv)_x_7}籕=/zC.5^5^{'$"}v^'M#Ͼ۾ߏʹuŗQ~iÛ{Pvv|l}|0J'f~ h#p}|~WhyHhGv..w'X!5ccGb13w=y'v90eǂ%({H#*7**x8|LaOH>ąXdP?y7`/S](腥/Xݤ@d(ćFSh(omhxyȈf舜󣈉(GD؁gU}GxC4h(hwY(_hIȃS8~$Zh(HC}ŦXƥ؇hሌ]5~(Tɨ3\vb}>nY XhXQH[琥 yɍI%Ɏ8h."9s80l؋H)9•By0;)?y'T6LO2Y=)IMٔEIY49"UaWGQ_Ra eycyVr92@Jٖ*9\ɑ"3 I i藃iɘZI)阘y6ioɓ izy+i閩)I铵 )ɖ )ُ Iiٙəi阴虂I|[nK9ٜ٘晞YAx|IڙIQ}y왙9YejY,(iG]5Y *ɡIIiI| ,-+':67J?ڣ0* >zCʣJ*L;tP:`TMKĐZ\ڥ^`b:dZfzhjl kijq" mzxz|ڧ~ڥoݩ|uڨY١piez*Zzeکyt9:j :mJ Z*ڠ* z &Z8jUEJ;TZGժzڪ:ȗת ޺W{ 8Z J&$*zJj 늬*ڰ溰*ZJ;:j& jjC+[\ʱ Jm/۬z??f;+1kK#{O U+ ˴ꪱƪ %;ꂿ`ʶcV寫5v*~[\z۳TkJ}wK3˳77[ʸ)K;ʩe[s}⺶C˹u[}+{{;+{sn_{nK b۵۴{+;KKūǫ˼ۻm +h&۹۸Үؗ{軾[;9!ˉa{ۿ0V+B û€dL?̽1î<{%YL[C `M,k!ǻ_|ƭtL5nNVwgcALƈȌ&bZF`|2K}d+sr=ײm۹YԻ( @[-k۽ SmaB}պ< Ml#ݫ}V1@zM|HG"!޵M OmeQ}wrn`I^zv{}[y.13莾>3ڗ!>ΨNN+-t馾K~wnu>+E>S~}ŻV,XN>_ \&.]P.qw.=dc?Et6~>~bT.nE,ORΟ,-OO~^VHWON(>/ntT~_~ĥ2!^"5|++MoJC><Z>D^Op O=4i^gonϷr.3^zD{^Z??{rm^~]1~?y`cu4Kl]nOl濯?k>Sooe/Ogۯ_oR?ʏWoޏTjF^0 0!` tCBaF=V"H!IHQT̖0mdSK Sgϛy,J_G>MҨPN}y5VN~*kX\ˊkδJծZ ;x7oP?=0E\`8+1aʋ#_kqē7'6ن?-7d=cmXk&7`ݷ}ׄM[xo57YtrAuniLW\u펻]{]廎;r;}^蹳wힷ-H#7cK:@cBeBc 9lOq;q5܈> F[оFAdqz<" lr|1JH+ 1%nr.4QDPMTMT15R7l9 < qFG4RI'RK/4SM7SO?uRDQ+SW_5VYgV[o5W]wW_6XaTOFLC.YlXiZk6[ms5YrY-1YҢ6]ue]wvo YP}_8t5\},ԄU]U8b'vYz x܍[bG&d`-ДxφذPgfo9gwv43W Xv?饕jZErĮdK1:i._tNTVfXv6 gny>^mOq/7r|q3))2rI7R]e1fw5Uc}tGs5k2L=iGy=/qN֓_{w~돎_"q+=΍/c@MzFЕ~Uc`wAO xG2Ep! h?!Ѓ B4IN2d_X>p-!zȑb-!1vIgE 2M"yH 2g_c8FLaьWD̍o,ʢ.ngTcSG>Q`r";qzc"/@Ɛ;$ ID.ҒV#&Qё19JRJzEJ1,e,eYS͕&J2җf@B+!@V$$ Jh63s5Ljfδe5y@N[fIsSޔ1j 6۹Mzvs䧄gyS42 ZNcs Ct…9Tф=wPԢ%hB[Egz`JIϋ6iI4u}I)ӓԨi2Qu8 NWeiDT3NjW:$2UU*X*V5 YՊΩvF׼ʕokQ*(|.5:a+6ֱ^zHXJlPYv֏_هVN#NPYQmjYHR"M BMLmU[q~bmlqRN׻".WoJYB*}jѻ^ס%W:ޣ}CK"+oo`_N6nUi \`w,K1pl2Xzjyb8,5_`4ZO#_X17x41 },b?F0cz%ȞL埒K! D~2'lN 2d_^s_TЈ`ٌg7K6sVm yX͇2=9Y2+. d=oK/]ϭҏ泦'QO9͉FShF[p;ԟ|ή'xjUQٮkL3z1͒le ذvr_l[cHԮlyvp,Ƶ,y3ݮ6z7y߁umnĕޡvhm{n8 N[\rF7m{_xA<ƫ|*KpJ{-G;nrx\x$:y*h'Yyq1:K ;;{==,B+AE|A?AA\I*PcB3D4B2;MOOO W!OOU -MUa .%^vb)."^ ^ea_b =*ncb&>c3Mb&c>cAA7f>@>;FbFv:9_EvcGc4Qʺc#DF䓰01.ee%d9-KNNeO^2.\&]nd˸|dTdR+~e;ѽSZ&[cCdd,t=-fU>f:fdeͬ3 SjopVXImqgx^duve}f\ղa^hhvfw_FMPx^Vh\jgXwn}~hK456Nhfeub/V.a0.hi~䎶VfbivioLk6iN&n\{>蠞^g2ꨖjgn~jie~iigfihj.k>kYigipkk6kk&jg>~Vlvl&kflʥ[ȶlj6E)Tͦll(blֶmϦmnkjԾ؎a.n׆nlv:DvnnNnVo&yVoon?\ofoowp ?m.7o mp o6 pHiN^qәgkDޱfn#Grg뷾qG#O)r(^rnٰ*/''G60G47+Sn3vq(ppps8p><=7t GtW _t.?FsAsBDgLwtKgqNG_褶jQwkRorSN.uW?uXOOt y%& %}\'eTՙUm&qff`Jf|ch||agh_N"u2cN꘢Q^*ff:犡"蓟~iʪJiZkjkz*Ă 6l,κVddBn˩v[븷kR5؜ɞkZK֋n oK_̒K.mpˋ 1t0ǐ \ĐRLoZc,'U)_p2_372 F}{*~ܴ:g{h.L&w2-q|g1oI[pEOMy<0bk6H. ceݯ8Ȗ 9{>ߪFoyϜ~䙟>괳z绻mko+<;Y%sy[=?Wߓo_o?ۏ#z}< pǹU,}A o(/ 3x% *\Cp} S*̃^!Y*!$Cl` dF<"l)["də&4epK 6OJBIr&{ OF󌥬)KKSgpxDS;{f/] S1vo~ lb>1̼ X2[%H?YQ6t|kƑ=]hNYg1-9MoM}bXoҝm]j\yuujl>6e3~6-iS־6ms6-q>7ӭu~7-yӻ7}7.?838#.S83s8C.\-))s\%9c.-G6Y~>9%cl=2E':Г3F?΅^SVQnso^:{.s=f?;?^s+HO;.*g;o;/?<3{qϛC|.m[_9|Kԛ{oW'[߶z滷;u~}/GG?壿sw}-3o{ON?}}|O؏_v3_~uU_E)韷_ zmu n= J`` 2 Ơ ~`^ j޸^ B_` !- ۹ ``~ ^Zaa ~_N_]EvJ!j]"aM _!r"¡"!#=m"iry"&fb]=#&*"b,"-bUIb۽.U./T0-N0$2.#363>#42#Fc406f6n#7v7~6 859#::#;c:;5b1J5>448@8d8a<&B.$<.;!==#E^d2bFf$@G~$1`Hbc9:Id1`JeTeH_NVU^eVVO=W2^&dFdN&eVe^&fffn&gj\@;Bio-Graphics-2.37/t/data/t1/version3.png000444001750001750 1145612165075746 20052 0ustar00lsteinlstein000000000000PNG  IHDRXL(cPLTEͅ?.Wk#KHiH=F2̸ @ڹ/OOUk/rݠwޭ`""pfͪҴcGjZdӐiii{hzEUp֥**2޸Ep|޳pzڥ pAi+_ؿ Mm! qӥ\Z+V?]:oZ}.1Kg#]:Xҹv#Pz7+.XY=uQ LXeB, b+[ /|. ? X%@?2vA,}q/[!)=9=)=1FYXUpX^{<*Q˩mu b!Iu"{<*'u h L")_G]yT"( `r or+QD,UR+D,}iT,QgT$AAŪx ʋUi޿C 60X+B"QEˡ)3¨byՃXD V3'L b5zXD V343 bAu"D,CĂ*DQcT ]: *VK[UaRbT]:b)1XңΠbK:"k"bRdP\jb?3**wI_=L b5zXD V3'L b53XA?3* *T'bAĂ:D,X6#䓘^F!,>c:?"s E%L'?e;55UeC*3Js>b!I LXeB, b.+]|9X0 "!bAĂ:D,XPE'4bYNhB'L'ЈeC:/a>脾Ģl{x lZetb]^y'ɂXeB, b$X&AJyN'D,(LĂu#V c%EeUjxàbպh*1XҨ3X҈3XҨ3XҨ3X+V)R@,*E˥!3¨bƽ b5zXD V3'L b5zX͌*Vό ՉX &VEĢKGQ*th3Xսn 1X.?*̨btx[AŪwx ʋU߮wFkE^hPX4Ƞb9T4fTU,rAf"VOj&bX=Af"VOQ QaP: "!bA oys:ziʮoB,8=ǾKíNx^V,8B]/ bb 4oX+VAX0JPF]yT(Sܨ-'B,DF]yTO,"b! bDR,DP, "'bK*9HB5G6ˊsX/b`<-%-ʶ?s{GWb $X&A,2 b!IG,ҞbX_>MĂuX "!b+*!ϒ"cX脦z ONtB"D:WtB! ':GNXtB=sw!:0ZUNALV=ĺ &&EĢYQ*AaPؗFAb_uFk/M]TzXm1`"/4(R@,^UdPK?3**wy\=L b5zXD V3'L b53XA?3* *T'bAĂ:D,\[J.5FХ͠bպt*1X.V,%Fkߥ3!.= *3X+V)R@,*E˥&!3¨byՃXD V3'L b5zXD V343 bAu"D,CĂe*oRA8kC!!b ?0X\tbX6=SSXZX6d2c:.q<˻X b$X&A,2 b!I"gӉ  bA"D,CĂuXtB#F1,tB_tb X6NKN,:ˆw k0YˆUfL'|b/Y LXeB, b$uX3ĂXu*aDLWcTJtTZ B%^uF^q^u^uFkE*E ZȠbԗ4fTU,zj&bX=Af"VOjfTfTT,NĂuX0X("]:j*VKGAŪuíPAŪuQfFkߥëR *VK{TP^v<0X+B"QEˡ")3¨byՃXD V3'L b5zXD V343 bAu"D,CĂb08t1b]9ބXpz}q[!)v4XpX^Ăbh";@Vd&bVabyDQ,Q ZN86X&'։$XE,BA,2XN usQX"D,OĂU" Nsk.708m#z]_d:R)xL[J4[>m bu6bMyNVepy6'X{5X,eXnp\Շ?uR'=K+Y,eYj*Wq˻؉ՃX$X&A,2 b!I*LX ^)VwDZХwdɒl +BrTVxn*ACdgK't$; -]:Y Yh#VS!p]:cyE &^b]R)y7c{7+ф^,ՃXA,2 b!I LXeXA%֗υg bA"D,ib"&A,b"&A,b"&A,b"&s9Mot_"-YLl+r*V$"#"iWSv֧L$aeb-g~ odn6ym~LdEAg6X=KəW~𱸗{LskμWuN#|4]9?w3:G^ ٨g 3SrTQB,zOɑ}ħΔۛ<כKק7헮O~)b!y#b-/֒b-/ZŠ5@_X5F$p3db"&A,b"&A,b!aIENDB`Bio-Graphics-2.37/t/data/t1/version12.png000444001750001750 1372012165075746 20126 0ustar00lsteinlstein000000000000PNG  IHDRX4g=PLTEQͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ }{%^<[Sk{%^>;W<# ϳ\]OY&ֿ7Z]];eB b+$X!AH%Ubaab `}XĂ=S5/Dv;{(ֶr[h ]j+Vy\A{tњliͼѶĥb_[ݚSdkc[_E_jqg/~w^a{rvߢkK_1l˱hX}6z^~2V/q<9qb~e IA, b!VH BX:Kl K4:",b VyT_Þ4!ɽ`sCb~W{PFB؍=xy+ {{pd+ {;RJÞΝ3ǂ!s`cu #X9VǎGlk>+$:Y{8.'E,BWXKXX},t6VT#;iYb%k #)ւeK]Q+QVT.A~X,b Yb5gk ϊ%űe5łg `}XĂ-Vˊ/;VTB9)VK)V3+*VXVXlX;XQvYbejdUGJkZvE V?XA~X,b YbbFqDYM`YĂE,X`+f^5*w[xl&ّ&VXVPUْ&֯e5;zX&[Me5[Ī'u\͎l:v_tja3مM-lMӗp-Ϣ(XWB b+$X!A, b$V,zX,b"X",bEـ.-5-؟f{Ċai?M'-ĢbjN=bMfiG\%T Z,86X/9_EQ:KB b+$X!A, b$V,zX,b>0lX+-:+*Vi-C,#VSV B+VS4i/@gc5zKc9XMwn7xb^ˁUXt6@,XGVS%K:8VXǵX,b Yb5gk "@K4c%jk",b6bXbѥcNJU1g5juåЉեSx^YXuN`W {c5jw,,~VSz]:+*.KV_,t,Y`YMXb^b5gk "@E V?XA~VT,(()=XN<~`,aagUK\^09!J`/4\ a7󾗆3 ^Ă oD_09;s,b?Wl?;>Vx`Y>ҁkY{8.qkѢY+pn BOG|b^B,z5X!kяq\b(;sXĂE,Xt&69Pmtl=/m>XXٳ9VRxZ*Onn'@pf랥K5' [OޓNhyc;X/<,<#Y!^~2VD;9qb~e IA, b!VH BX:Kl K4:",bmsBY,\[ү* gWYH8եåTn4kAا; ǶtEB5G.g,YsX'Ek=k-ttХtg-9gCL+ь\յXA, b!VH BXXY%'` ևE,XĂa `}X:aONh9b:aĢ66J,:atBE'4lK'4 b ;#V}+ V?XA~X,b Yb5g07x RXĂa `}Z`aa_}g,_09lkE곭¥(jB,lzYhX} 6bXGYڿ`O/I,ƈbYIluXo#Xo#Xo#Xo#Xo#7ɑ),bX>d.c {;Sv)SOGĂ V8ƥvX5]ʃ^/\s)ݳs.GĂ>yg;C*g>Wl?G:rbZ5y%b-Z9yS,E, I>d9Ky  bRE?zcqX7`a `}XĂ-VlX-:+*ViJE,#VSFcj,9XQƲ2jn,[>XX},t6VTcXf9X}2ˁj`RWJkvE V?XA~X,b YbbFqDYM`YĂE,X`sUhb=ĢˎejjRhjjeŊU}")},:)Vc/VT#_XYb:b-YlűeEZVgk "@E V?XA~XXQ+QVS,X{` ևE,X aYWlv5[* kvdU7+Tjdich͎l:6fycf͖l6{ݬ!W#N*mbjvaSf%\˳=J'Ug+$X!A, b!VH BX'.ޣlb"3X"V$[JMXQRU"V[2XĚ6J4k[5&eV$ˎeV;!VˎeÖ;!,bI ZueX!4VmYB,B, ˇE,ĊK4 ` ևE,XĂa+^5#<{G͈ȖQVZ-6wC,5ƾbE8&վbU$[܊d;b-X1l˱hX}f+ZdX!4VmYB,B, ˇE,ĊK4 ` ևE,XĂa `}XĂE,3vU3bɳtB:w,bbFN=XY:o,bbF#Y,Ј:gkbqObMA,w ˇE,B,a b͢B,XĂa `}XĊd _VE[~vEkqiѯ9EYlERتk@Y8&_V$[![њl5ygeVTْ&֯]GbzޤY%Mz^7>ȦkuJf6X*]bƱ:}9,z҉zEA, b!VH BXB Ibˢ(X",b6bĿڑ+!XXMZY\ XMꝴ?9cYb=mҲlXejNZXGvҲdb'-K@,XGVS% :8VX]X,b Yb5gk "@K4c%jk",b[z,+C,ڿXQ _欦Xݹ:bڿ ;n*쪚aoXjUO޷ˢ@gcE=eE%k #)ւV]Q+QVTkEA~X,b Yb5gk ϊ%űe5łg `}XĂu?/b!bBXY{8.19,b"XgUgkuEo9cn*\ a3{i;̱uӶ˥zVhX}v)  6`XG KhX}>V黃eaaH6FKMrdR,aMrdMrdMrdMrdMrd`&9 E,X` ևmj.V ^hX}pƂ=ʶڿf[_{m!aXTgi=A,ڿ`'|#֊%Q1|WV b5gk "@E V?XA~ #1|W E,X` ևE,> b  b  b  bǟ?r0o6].r(X7,r$o *F9G0Ȕ=(rn7|O7H wIHHHnbWymwV[[ :r7G;A*w=w<|UБU/SA֭ %fTs>̄῞al(;ȷz#[w*ȉ?UOS{i?7g -V}:xn߹ g=ȾN͚QE^kֿϸ#鐐m>v; >ۯo8)??)O_EOYB#"5b] 5b]X[^UXXBpc 7HIHHHHHHH|00IENDB`Bio-Graphics-2.37/t/data/t1/version1.gif000444001750001750 2216512165075746 20030 0ustar00lsteinlstein000000000000GIF87aXL{hAicG@p9hQ5*MbɜF*RKR:U׮`*,ٳYǦ5ֶp>UWvu7n߼x K8pa&x\"+6Lɗ%k\P7/朙gѝQ@hӪOÞ=vjڷGZҿ}ܱ@" "Wq.z?^{/<#V}y>{h!Z7>(U~:KE\~ -^ "$nW(,~97n5.I<&@)FFyqY8"TrEryQ'f饔N%^IpvqIsڙ'ujfH&y栂'}zt6ZF*餔Vj饘f馜v駠*ꨤ^Zꪝ2[% 무j뭸뮼+k~6lfv L׵kn.n+o+kV;U,˯V0S/J/,Wglî ,#(Z')0,4lެ33I>mtHr9tDC4S/muTtYKUc bthlwm{Օovwj7[psy8VʹywdZ#.4_㬏%h'^kNz_fz# 賂>Xޭ>QOG٫ړo062b]߾~y.s?q9NxW7Nd(0y6z0TGHBKFK W"0U.ؽ8 gjl N }Hqa)vDpPa(*l4dSŅao[bĿ Qh Q4΋Ha H:lI;G8Yd)Bnq$d" DE62|"')Jb𒁔[&-IL:ғdP$VPvD)3Jahqe,U9TC笷 ZrҖ f-Q82)Lf.qR^6>ӗt6 hʉꛑMl4<'9ɎL3<9}ҳ|5ς hP6|hK=Nj2юѠ,(/8BJh&J]*mNUSGR>7iGxd }Hu6$A=T{ӆ|O\T˕өzZUI̩WB'hX#Wx`RT+` nMXXX.6ucWNֱͬe3ȒaE ʎNܬjOYv6`g)aZֶ̮Sm MۘLso m ̽ 8Zw΅k"v.inqw6h]֞{u+񲗼ց.~T͑~<xT p3 Uᨘmo` /.\7LQ 1M+XU[ vؾ qFa4fぇ)xFqKXG2A KK1ce*kMsfaN3#,涤h2fl~nWsK OwVs>:]/wFNyw2dzј4< =e*IϠ&4L9}^;[@]j9ӪtIkKkzղk"ZSuhW<vǼkBTU6Kla\Fl>ۺJyMXmHoYeMu6Mn{7k$8-p'j~G|\xÙpC9;>3R"8!lywnKr_rg~<4>+O=B_ۜH/:N>[}Qyt'Zt̷׎`g6*B uZ*߽y߻kXZ]Ó|)u=n{g,zě}O/wa{ޣ=/OX;u>?ߗҷ@~}M}yz?Ĺs/->[~at~wxDucyd\uԀ w""Q}"*,؂.0284X6x8:<؃1yd>XFxHJL@'A)؄TXVxXo4Zb8d\xt8e؆npHgIiHTxzHsouX5zI&vj؈8$^xj؉X8oHITWȈ"w~7;艬4'a<hx9ȋV犌DӋĸZsOS}8g5ȌgʼnH8Qck(mܸu{n؇8gXl8cqmYhaH@lȏx LW ii(eِɃ{Hd|ّ?"zi%y,09j$g3j!9Oh/ٓisGE7?Y;MdF:hȓUًW <[T9 9ek]iLI-TVqɕy`rYt SYyi9XlsO} p_ F?X3?"Gڱh :|iN;*9&BW;{Iaۅ6eZ?;#lzf [v{Qq ۷jR[0 K۸Xh[{K{;ۮyMfӹ{fx*+Jzu3 6˶Aseۻi˻u+mZʛ[:Ϋ !+Y˩(h!{,k׵<[9X8k}<+n4ݫFۼ#o۴}PXT>(K ` -7۶ZQĪI(\Z*.A4\6|8:<>@BvDŽ\Ȇ|1ƈH)ɒ<||?1!Þ3[!\L©|,JʲLnʌ*uj!t,^[,jʆdzs̩ר˧|Ԕl{h鑪1K~zͭ\| &|Φr#@h{7&̓*< * wĚ$]5<Ѭ4 e&m# 'M)M%0ҧӒ5D8-yӋlo<{@-xcJ=z׸53=RU]OVS WlF#^ I[u ie6Z jIn]v ld ߥ}fИUՌ~om=uٜ݊ QUP ծRw|RFkN-ۅڮ}b 렫Qm)ܕٴMȪ]ۂ#ʝź m ͱZ5ۼPU&MڠEmڝW}-={a c}k߬* ; NR :miM$Rضu<+ލ%z 27[m7^ h,1.i'j6|K.GKd:3`nweN7x}͸o>>*^xs+t.u>(#n\>bIp^諑~&,&NHZL.9~y{^n5蒮Tz9rT^>TX]~QWíi^캾rÞZ -eήо.S>.>Nnށ7n^ d}D~N\^oޤw}5~ _*{OnSۮ6U," -?7@E:)?4o^} -/JNl,shV_PoT`_]?Zܬ^Ofd/j:IgOvϿhii=/N'b[:boz)_zn "o\OR??_?@e*ĿOooӟڿ8n__iq (oT!B@ DPB >QDHF9z#G$EhReJ!Wl92L5QiΗ:{9ПCsEj4QI>%)ԩR^uj5kUX~5VZ{(Pe6nZh.޽nνWo`| 6Xp9/>qcʓ-KYʙ9_ gѥ;keKЫMfjس[vnڽ[[n^;ʼn3GysHS.]9vc]sW/z?n^1c&>~x׾|a^꽏C&|75}ſ?/~/ؗ?id(? -Ђj?GOHs^E Bp+ SA"Ѕp` W(C5` iBސm8C!paVD&щ=Z15ъMb]'żL"x1^v\ 4QdۆFEj,\=QvA|CR۽яc"?is$)(IC*ratċ%ND$(IDe*QyIFd,O)KoamGA:RtRW@JyhKW:S4T1}LkJSr?iNЦ|L6E*Ob5e|QJԩpyS h7LԬ2EPU8ҭXjS<%iժ\Sq8j^jH;nkbUsYJA.lfVd) Rsw:E-(ϺӖH^SmC;TSֶ3ka[nխsTkR6׹Ys˼B.2겊fWەw;^ +/F۹7{ _#WE(}]2poݩ߇ .}t%qWx5qwyE>r'GyUr/ye>s7yus?zЅ>tGGzҕt7Ozԥ>uWWzֵuw_{>vgG{վv<@w r{v:w~z|x?< W<s|E?zg~8Kzַ^W=]?{{A/q> wGG~_/χ~?}W~}wE`@?/| o?S|i~O{3@#@;@K@@@@@ + @tSAA dtA [ <,t@+$DB"T@3BlBTB*T!tB#@*'(/l%* @)<&TC,D9-È;9B;= @.?1C6t@lÏ84)C@CAtCƃBDH:8EDdEDKI?lE%4J$4DCRDW<9]4D^D4;l"Y\C_d,eDcD`7$DAbƐ|kvm&?6ҮrB!A,B!A,:"WFK_k bA?D,8U,Pwkg gA,}|S6P|}m"Q SD*%*Tx>kJNqJg]Cy+tlMJ'XJ8>zWŻMw:{p6_uB 坹*XDaΜ2ڏSfhi2N,GVbM bM~ bM~ bM~ bM~ bMg2ڏSfhc;D,X K 3W-Ēc .s?5ڒS!lOll+k$c'$*XUp&s:n~UslKiZ755b5q[%?.b#b]=55mb)= uܶxI"$X" .&':IhU,)MD,?4*}i8zQJaDbKXhT,IMb;$*t cGKe~IE;VҞWbUXGXD#Aj"֑ V5HV2xQw"D,臈%Nb7ZKXFŒ_D*XObV~UVگUJKRivUF*b~Z4QU6At /zQfTE3^hU,)} V5Hu$UM:Ī&b bUiU, z'bAĂ~X0_[J /0PJSիÉ%}Oڮpbq9'wpbI.=3N,3Pc(jÉ5}ztO,ɂXsXsXWb!b!b]=55 > !bAĂ~X"D,臈k *ËJhCa% K+g0Xxbz É%3N,VBa<6X5bpbM>`A9D9DĚCĚxbNÉ bA?D,\, cOt"3ӽѪXRX~hT,IU4]QJkiD*XMb҈`%%F*XObsVPсX4FR]RxUǕXD#Aj"֑ V5Hu$UM:ĪUF3^hT,蝈 !bb oE{XVVQ$Wp*Dbq*wqNbI7iT,xVPc?:#2'"BbiO+XGXD#Aj"֑ V5Hu$UMb~gШX; "CĂ.0iki`dKbnphKN0IZ^ Z`X439b.b~bXJb+XlKk JKZ96&:}XX(VCze?^O,"8"ՃXX&^mۏ*rMbA !bb3_;[d> bC,盱ozp]2b&OPJwRJJS_EP|޶U:uYhüU:qHBb= ێ}LFݦ-&%=N(G hM,Vtb*lJ>EM,h],A,A,B!A,B!A#b|h906 "CĂ bA?D,8SسQ.-%BVB +PX 2}Ym>o[VB!+7[a%t +7b凟z-\J_?( wW>/tBv>ZDobUvgԚ̷Vj)jbKj],A,A,B!A,B!A#b|h906 "CĂS ygvP|ĒۇX(7eC K'&BZ(0OԫRb*N'oltwt5JgyD(tu{ts㪗~UZ۴xG3_k['@Yޙh-*NV̙a(Z>eF{6,~d ֤ ǰ ǰ ǰ ǰ }<,Z>eF{> MĂ $0Xe>ܲM,)==Y-9&{ 6{ &F|XX=~bXJb+XlKk:GKZ96&:ѩy}XX(VCze?^O,"8"ՃXX&^mۏ*rMbA !bb or{VŒACbIߗS't_F,O*V4FŒ4*ӪXJbGb1VyQTTD3^hU,q V5Hu$UM:Ī&b bUiU, z'bAĂ~X0X[ўD,VxUU:iT,IU:I4*ViWiU*1QQ$VhWiT-6E ZkCI<GbҨGKaFLU;VҞWbUXGXD#Aj"֑ V5HV2xQw"D,臈k UJb S 4P1XdZJ 'wxbq) '$3.=sO %1Vbh1XgڨGҞ,8;55u ""ՃXsXsOps8"D,臈 !bAĂ~X0h+1XF>VB`8$zÉJ9'+0XX =b%kCIUZ,ƪq 's<,55u ""ՃXsXsOps8"D,臈%aNbf7ZKJ%F4*Vi-WiUZ1QQ$hWiTBx.ЪXJ?:F=ҨX K/*ru$UM:Ī&b bUXGXմ*w  bA?D,\,hOt"tѪX*4**NhT,xV7iT,I7oӪXJbGb1VyQTTD3^hU,)} V5Hu$UM:Ī&b bUiU, z'bAĂ~XЅX0>c- #~ivB,mɩf6_KK!A lk`fXY#Y>G,E,OlkCIUpe7MbiMAuܶxI"&Ҥ^5:okqkjHK≥\GĺzkqkRzmVEαI,? bA?D,8U,Pwkg gA,}|3Mn[kBF„GQJ6©}-V| $jb?_VO &JtDyOhn^-X!ŵ^Uinp\͗neygJ&2b(1%(sG6J)343lMƉAIA)4ۏaA)4ۏaA)4ۏaA)4ۏaA),yXFRq} |l^ !bb~gU:+%I ?K' #JBU)1Vp*<}HjvJN; mJ'n;)Yh#V~Y^aµiè۴xdD7]G>t ڙh-*N jYPӞ=89+֑Ȟ_ǫEwu>w)?6f؞Ϯc)r^n毬 xgH=uM1nNw}׿q{vd}~~]GJfG#_tFvE,G9#֒b-/֒b-A%XX#XC w)LM&CXdH b!A,2$r'~DIENDB`Bio-Graphics-2.37/t/data/t1/version13.png000444001750001750 1402612165075746 20127 0ustar00lsteinlstein000000000000PNG  IHDRX4gXPLTEQͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ iLP _b56rq܎hq]n/Dz}\‰zXoql'0y_'Eks9楟9xl>Oկ9T9x\eSs_viaa_K"9ѵ0!9ktu A BXB bΈEs(hF¾b `}XĂR5Bv;{(ֺr[h 6m.5+W~.ZM3 --q)|qr/zڿ)5DZvuaZ*k\RglnO6O.%ڿ&ڿg[5У?׫ kNj'OVb]B b+$X!AĚ-abfX-0,+` ևE,XrXI^6JϻB,F O\ aWE_ϻװMb>J^öu+ { "V>ygC*g{U2,zX[EQzo~\(֠Wtg bd>}[Xn Bb)֠o{o~\b(;kXĂE,X`+=VOXlXϫ-z6VTTZXFX,b"Vʽj6`g+==f v:R5;7+Tjdgach͎tb6&qcf͖lbYCfGv:6_ןtjag\%T ;Xqlb]kx b+$X!A, b!VH"ˠc4X",bX>,b"X]D ŢKĢbXZakĢblN=bufiG\%T ;Xqlb]|xw b+$X!A, b!VH"ˠc4X"b-z6C=+*V*-C,#VSR B+VS^ˁ밗rgc5:XM wn7xbmy/@ꋕXlXXC֗TDqDYQF/+Y:ĪgDEAzNY:ĪgEXbڳX>Xe3VXtرb%tYMϺRjUIW/l/VTK'`G `5*w ,zVS|^z6VT͖.KV_,t,Y`YMXb^Wbճu"U"։ V=X'X,bbճu"Uϊ%űe5łg `}b>05lORam+e> ƶUR-bzi8c+A,b?F^öOޙckux`^b՟ &,mk^۶85h\]N7X!Ozߖd>ƿ/b!bBX5^۶ey` և}!V.ڄ]8^5.7.ڂMK}J7`Sa㼏iY 6K,ىqj`yϖ]>KjѪ?s8yo3i6YRKiX}r6/䋖gy>VeaӾgɠ¾bkn%jGW&h'Nџĺ(X!A, b!VH BXW5[(Z`XW,b"[|܇Mt;߲ ^eXWXՕXA, b!VH BX:#ϡX%V E,XĂa `}XĂE,X,kaE'4lаb .tBFNhb .tBFKtBvbŢX&.?RĪgDEAzNY:ĪgDvÙQX4>X"bUqװX3g E곥ĥ] _.]&jѰl3l`[%ڿ`;bۉ]Z`XW| #; RĺE,"Ard 9epY28H, #8 ",b mr,aa{x\ lfXǸvWZSaaZǺ¥v˶8K}l} XUƃ[\e vTͰVSrcg5'exgcEl2ˁ+o,X`YM,[Xb^wYbճu"U"։ V=X'X,bbճu"Uϊ%űe5łg `}XĂ[D!_vX/sVSRB+VSRg,+VTXM+f9гb&nbElidŢ˒5 ֑kb((+*赢 V=X'X,bbճu"U"։ V=X'XXQ+QVS,X{` ևE,X aYWbGxlN'V*=@fGv6Ʋ|Z͖lb:l,ّN2ٚ:n,ӬْM|7kN'sN6b՟jva 6M˗p Ϡc4XW^B b+$X!A, b]$ttf `YĂE,X"ٔV*:~^ĊbSX_sXBcYiՍ˦~budweV$+&S? "XXX1leX쀥VU4VMkEA,w ˇE,B,a b͠B,XĂa `}XĊc׹W͈%>?LţfRdSa(+HdOC,5sŒzq,bud!Wђlnp+Z'eVy_/=+*fK%/_XXZUEqDYQF-Y:ĪgDEAzNY:ĪgEXbڳX>,b:+>05lORam+eƿ/b!bBX5^۶ey` ևE,+JE곹X:va]n Y+q)me? |omKjѰRy& g,؀;bKKhX}YKhX}viaa_ˈ%BCcxee 5,b$G #XɑE,"Ard`pűhX}` ևE, wv+Y/\4>V&XlK!l3ڿ ]/Ămf_1yjѰ_gh.-0,+vX-1EH@HHHHHHdIO,6ݿ.ҔzH?biǠ3 HSHEzd)D,%[,cwҖW7H4;$$EBX$$R}ޛxk=վ|^;X=ľ'ȏXrUyZ?}_Sk 薞otfTsfBaW3e;Qoľc$uUC]ϩ=O_?\+ #Vt11+]_y g=lϾ u7H5۳+Vq{_~CB/aϽis:A<CvN'}YB#V{ A5u"bڃXn7\i R"!A,"!A,"!A,"7bIENDB`Bio-Graphics-2.37/t/data/t1/version11.gif000444001750001750 3426212165075746 20112 0ustar00lsteinlstein000000000000GIF87aXQͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ i4ӥCBU**өO&mkUZ5v٨V]{hYYJ|n\tڝW,ܢ~U+w֫`xK8ȏ[u+͜髧GpÚ~Vulжg}3۰] xԻ\7H~BιaؼKή9e{/S9t>&x9_7ܚ@~}W jй6׀.a*h x&יuNh"b nX˵a{u_Jxa1 PљH]܊. >>&$S>{٨M*Ye\*RhI%nkm[aeqG&{'_7"9]$G19P)y6N&1ZaK:s&)蝙'š4ꣻv*jyZJYi2;+*k"*J+,ZW+k覫+"G/-~ l' 7G,Wl\w ON:!,00l32L#T|@-7LD'4F+MTWmM\u~udm]h-rnm+x]Potn'#b6*ozk烃^觓޹=:?:˾ο_?|?{!yh7,%V<;'Po-xj{|_O>?s_S_׾ pQ#%s{ rG' E?$$-- /V !L\fRٷN+ }llI8'!gCqE"zi~_'Dh])"RB>y\|Q_9퀦 B/kiHЈ:6Ѝ9,?0PD;8.RHG  ծIBђ`$XuI14 $Fn5IP2Ud%,CQx_9KU⏖]*5)\Bϖ[d(Sbȼ0f:\d$FjJ p)d5h.s 8Msgæ}ɗs}Sg OW>yRkxgw\hж)􏲜AJQ=TMh:Q< 8$$f U ^Aӕ/*]қbr-S2B܀F Lm#OZԙ҉=ݥVjԨq5m)NyNYSeZխ5Y"UDM[Vc,k(e!a=^׺Ծ.V{𪙕yix;vl3wg7Kv;T0΂ޮh<|ɢAwz҄4,jDGuEM+mHSԐ>4A+.DejYɼu`kPvui=jd[X5F7[kmhm/ir6Y\NXǛƷ m2ْ8=3[5v k%q:+{ O8L0u7M|hɓzûy9 䔇 9ugkƶko9}r'nu[]O֗X_9~ ;*ϛu~>tl2-G}'~ ~hC ct (җȁ 4+X%Hp(X3*@~9k34Ȃ6}׀#q>h05F7D8SqTH.@ȄϷyBH}Y|[h_Fȅq cM؂gڷhdf(j؅ 1(_!p'8r'XwHsbȈyh@ַke}XGyUx:h)i jP&z(*,ڢ.02:4Z6z8ʢ: 7B:DZFzH)A5Y*$TZVzXOM<*Z:dZfJ\٤`ʦypr:2R jtڧ~fj_{ XyzڣjX験*ZIj􉩛Qʩ꩟ :zJxGy:@*Ax嗪j =ў٥mʬktnjJIzũ|ꫪ˹ʪ9mwzܺJz u"vi Z+*"ʫ KkujJʰZa꯷FDZJ'k+ʲ/벬 >JtZ2 @n*E;ZL[CZG 0;{ӴXO;;U˳QKdK[ IKSk+c[pkg]{#|Ks;yz;vIJ^[ v*{ڹ{˸:k%)˺Zk[;Zi kKt`+kZB)Jk[ʌZĻm+akKޛ{z} ۾q᩶=˶竴zkx-K˯h[˽ K+曾\ ˫ͻ+ld{&,;\?۷@|{4<KLLCL$,I,/X O|S\G0{d[ {=,E<.c\plglP\Ts AJ:!?M6X[_9m.J=L nF^Y-bT|km8<`[}^}c} 5HݴEMGrʹ`rd0]_GM|LMm$ &,M ΉЋх ғm؍Б-ɗ f}ؘنaD7]ٴ{>M٢٪3vLہ{t$mٽ-< mɯ#\Vؿm/&WB}ܾQ&Y#܌ܨ\ܬ"-\,I_ ˈ}ƃҤ-Fpdeнzk})ɒKG-]x] dٮzĽ-{JJ+.9&)>->1359}q@>_m=HBe PKRMXu1Y[3Gb_>3ceNgd>l.iKatn^Z2|LB&綝ߩ8 *>->n鎽']:꣎6N+ |ڞ.޽W"n+m%}ϴ薞Σ}~1F}NNBq0N읮 G Ž<O9Nt|ﹾ쪊=!~#'?3/><%bN>_ؾx^hMcsnV縔JdP?LSN?X߷RWZ`s\JnNb_^/S`_jIzr_6r+z?䎳Ʉ~40 n_/ӎ$.NoЮ2On6?_o/ɏo(>/N"o?ȌI@ TР RA0aD +6x ā~ H %8Ɣ(W1ʁgR'˗2}3(BC{E*4)ѝ9*UZtӦFgJZuӚZR5U[B)dډlKܱHZVݺ_ū7p' ixa‹-Xqq_UK2G|6eʣk:eӣY.Zd%n<7׺Icʖnmu':r{a7gճcyg縧~>|鳇]}@OOeˌᎷ58\Xˊ3PiOwqa^i8dYN,TƘtg~旑n~jDRg:kN|60VZcs3zf: ޻?>oA3R+l>%|qܪCO=e>(9=[r-]M],EҌg8ww.wޗ/:mߝtꝷhmh!zg_?zvixzoX?z~g}_h;$?9ˁ`%x}' ^Qas2igЅ/ Q8 04 BЇ aP~B4D&IB"jXMMbD~EuSUB-ьL":`8G:+olG7Q}"摐3 HC.҂9#HE?򑉴#I#5 IRbR84&INp_[#2Fh "m = måN<&%J0k&+W]K'o6n4o)N^Ӛ53u󗟣f6AYڤ06<ӛg5Ӊ}U;_ghTle@!:MfZ2O: g<-̑* 9EJR+u>art'm)>qSԦݩN_Zќt#TOjTS"5i$KEBѫ=jMz*+k UbjV*U5Dmӱ0+:)հ/ B^u`;H%6}ݔd+XeWD4Zֵֶ QeYնmmq{۞*mUy;q-.rŚ[ֹU,HG\27]pK*T1<%nvjŐ/S]B-x+[ę; Dž/_(T&٧ ~p4 ~'|ףu%LEęʌa3m~O2v.o; 8F#/ֱ[|K E1;&xIo {"ղ|c x^L1OUfk̬6O?򕿜͂VRW@KO2X ˳}D?jA#4LMy0.-FzNs'fӥtUe|5aiCf},WӇ>^kZ`n=3{٦~t=jc;~6i;{оv-tv] .N5xwܴ90m}wm_6\0+8cõErS|&/^*9((Pe\ .to~p"N8iNt~'<rG}u]Czk.]zثnt\TN~j78vp}c;ݗf!F{=w???7D ZY\D[(EEa*bZ VF;FlEblAIj$NDPO:;F>o En4q8f'"m$%vaC6dIc[Im[O6.B;N&=cdD↕bJdKc\Nd[2K`W.d_dbec BMQP.;_gjVFb&hVꦶj.ꧾjjj6vaU~hha^k& TvkFj趾kbZ)fjl&cfj&솞]Mjjj&lLPvlFllFF>e;NmmYl6inf:m߶m~l nnn~nnn6o.ośfg>nNn>c>6_od.kgokk pv&  dopW'_ppWqqVnqm m!m"noV>q%or(qqOr#'o,r-m.gwr % ]ʾr$s0r;r֞.p6r9ws)gmw2>?^*BEM.^t5tIgrHO󘖍VӾtEgG@t4GpFg/J>/Y/q1r[ tT_uWtVotObS7b'__vgovnZqNG\q]r/snsosp/r<īv`Auv_fqwoplxw#n}g~vWuOwpπwwnxopwQaOuk'tuqggvvMst7yxyrl]ryym./OyPtWhyWyzyzDvz[z{wt3z?{{^Gdk?z{lCw92{?Wo0Oz_??yzjQ\ʏ8|_|{gxXW:}O}w/nw|շǯ}_x.GvS۷wGeNtɧ}G}}~{~~/~GooyOxy~wy~?'zON;(A bCFH‡1ď";82$ɓ~Ұl%̘2gҬi&Μ:q)ǔBQ59ϠD4T)ӣP5NMjdNNrUaUױfJ\-ܸrҝ3-^z%е;/ᓂ&flqMSܰʺ7s.Öbmƀ.zjժav-m׸7‡/n8ʗ/o\q灣Գ+}Qc~|LJO_z[_^|}2 twwk f5 jIۆ%8!^{$b}haw&Bb0&(c(c)l<裆r(7h#c(,d}D(PE6/ZU=֓H%[ ]pXR$A|wv&٢Q L& i{z@dcfh`>:ENJꩦ eo`*:hSZ孌efv]h[,y,Ʃ_6kPԞmnn҂:j~j㿪+K)Sk(C"&k |m7Lmiש.$Վ$|ƼnL6?3sM;=>= Q}5Yk5]k%?{1G{gi?q=7uv*O 6vυ7ٙ-}~8+SAρKxN^{M9Oa9821p{_~K:R=ؓR ?|;¹;?=;: -k][>>I.z?޾r؎{۟gpܷd (.(~L @Ё0-X ʏ /aQHA!C3* [C ֐U ](jP%< ih!MUo( " D[ԬdQKOD/&*̢ђGVcUGh\y!ю$ybwTA.ҍX$!Ɉ#?:K\$>|4%CNIO $#5JҍS T`Jz~<.kHW'`&9%3Jhҙ4'ِqyMjsz^ҩι]b9sfڙKqPb.})LcZd/+RT< iNSjS[]a6Eϝ5AԢ8TԠTXU*XzJ^jX}T1@T5oJJ(B񔈴c[ӊןݪZFC2PϚWÊUM*\֮M,]/V>U$hWZɵf5-Z+kTv5dB(^ql[KІk"Z7mpIvX?;>H)fu[["v%/pA\Vm{Kʴ/~_j]yVэQFM*.wn~?0)ػcpC,bX>/|<q؉Ӹ2)tݛ.>1<{'MV2sc^'52\e[V2e-4E3ю~4#-ISҖ43MsӞ4C-QԦ>5SUծ~5c-YӺֶ5s]׾5-a>6e3~6-iS־6mTfn?l>7mrw-yӻު o[7.F36S7-s!h&?9snr[O9c.sb|69s>9Ѓ.wj;~8#tnK:ٟ _睞ȑY^Uۉ1`9^`ʟ^Vv_& ܽ5 `M! B %[ J ~ `!驜EAaIaQ>5a!z n!>MM:M6 &!  "!! !Ȃ#>"$F$N"%Vb$%Z"#.'v'~"((')"#v}F""",b"""_&"."&%)"0)c)*v+"363>-"5V,5:&#7v7rb*rNq$2[4"΢32b6c&5nc8(7ݜc#@";6;£AJv>b?$F@2cAG^cB6dC>$7F$L$X2GK.GLL*GMM&KO$PP%QQ%R&R.%S6S>%TFTN%;Bio-Graphics-2.37/t/data/t1/version3.gif000444001750001750 3417012165075746 20031 0ustar00lsteinlstein000000000000GIF87aXQͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ i4ӥCBU**өO&mkUZ5v٨V]{hYYJ|n\tڝW,ܢ~U+w֫`xK8ȏ[u+͜髧GpÚ~Vulжg}3۰] xԻ\7H~BιaؼKή9e{/S9t>&x9_7ܚ@~}W jй6׀.a*h x&יuNh"b nX˵a{u_Jxa1 PљH]܊. >>&$S>{٨M*Ye\*RhI%nkm[aeqG&{'_7"9]$G19P)y6N&1ZaK:s&)蝙'š4ꣻv*jyZJYi2;+*k"*J+,ZW+k覫+"G/-~ l' 7G,Wl\w ON:!,00l32L#T|@-7LD'4F+MTWmM\u~udm]h-rnm+x]Potn'#b6*ozk烃^觓޹=:?:˾ο_?|?{!yh7,%V<;'Po-xj{|_O>?s_S_׾ pQ#%s{ rG' E?$$-- /V !L\fRٷN+ }llI8'!gCqE"zi~_'Dh])"RB>y\|Q_9퀦 B/kiHЈ:6Ѝ9,?0PD;8.RHG  ծIBђ`$XuI14 $Fn5IP2Ud%,CQx_9KU⏖]*5)\Bϖ[d(Sbȼ0f:\d$FjJ p)d5h.s 8Msgæ}ɗs}Sg OW>yRkxgw\hж)􏲜AJQ=TMh:Q< 8$$f U ^Aӕ/*]қbr-S2B܀F Lm#OZԙ҉=ݥVjԨq5m)NyNYSeZխ5Y"UDM[Vc,k(e!a=^׺Ծ.V{𪙕yix;vl3wg7Kv;T0΂ޮh<|ɢAwz҄4,jDGuEM+mHSԐ>4A+.DejYɼu`kPvui=jd[X5F7[kmhm/ir6Y\NXǛƷ m2ْ8=3[5v k%q:+{ O8L0u7M|hɓzûy9 䔇 9ugkƶko9}r'nu[]O֗X_9~ ;*ϛu~>tl2-G}'~ ~hC ct (җȁ 4+X%Hp(X3*@~9k34Ȃ6}׀#q>h05F7D8SqTH.@ȄϷyBH}Y|[h_Fȅq cM؂gڷhdf(j؅ 1(_!p'8r'XwHsbȈyh@ַke}XGyUx:h YB7 JLڤN IREb9dPZ\R:9ir]zhjZ_ZVbkZvzYڦU:EjycW)n{jrj駸)iIG:ɩ JZ#jo*|zJ{̨TJ:㹜* J jC|w IxiJ *[&êʮY' {ڧҺکzkyʫjzGS隯J˰ ۫ zzʱ *J#;Jpz &[ʊ8zU$K5*D;nʳ,;aʴ&XPGZI=۲C\S3aۮcuA]:zVo`j[9ʶ+4Zz/;p1k(۷z+Kk۱?~ |뭓U;WKp rK}zBK b۹[{+Zkk%Ԭ{k[KʌZ [ +z}4@K ׉{; +t]q6>_d>^l]R?;-C~YH!=PޥA1QNS3O^ZW>3[]N_>^daKY~li^kL?v2| 0>~-*n(Cl!.M^ N}~ 1~.7lR=tߜ/>轸ۼ>^"ܳ듮~~x>%ݨ6N펮$۽AVG^ۣ{zeߪN2~L.e]"< ދ^~g~N,*O^&/^$8RR:m-r>Ncq.?J`SFGLL_|5JTOTR[n1X\m/^?Z/Qa_vfcQiLnϣH/S=_v8vLkO53o^n /N_ O-?o/f{%Oo?1O/?@O>=%?b_ɏ˯ѩ  0!B* tCBaF=V"HF3 rȀ7DI9ɥg6Mtf΁Q6sg˯i=[sfy&=bٽG 5ő \}[c=?.yvۭvwؿC78:v^yuϾj]0@$@D0AdA/ӯB ',O ˏ ?1DG$DOD1EWdE_1FgYpC4/kH !MgrQM7#m=K6GR dUW_5VYg?ISPu4[u4_ \}vb#]vd+=6fq}iUk͖hoK%UjuUqVp}msw՝W{r|M߀=~Mڅ-.5Qx]&bݎ{ߐyPcSXˇ F~9ZY2ؒxbhgZhƖ裕vinNh\MMASIbsZYe݅i:SKꑵ[^ݼM{o&lnb魓y⯵bp9'7 "=溶ŻynoGn3еp=uQ/Ww-uo[M7WuF`+^)vy|ϝ֭^{=x'z^_ߣwoߗZ?Pl'>ouk )6Ё 8AV} |`5"{sBfj'Da:+.̓ /hІKZZش;D[hxC"qF9FЀ 8!qoJzx@&_c* >qz[\^3Ї,\E9QedEƑIy>3ꑐyCֱd1HCL -KB{]Y0P~od#=ROw ܬVNT-r t/FKN_.yJD"( 0*^d&&[dn,ifbӚf7Y9D!:ɩMs\ s=JtBL\0 i*<'A|fϜ<^= :ЊR},(HkP&]$J#ѓN4e6x ԦE>=jP+~vQԔYTF=ORZT41UQR#hUvX(UաjZS֒\zծdG:)L+zW25)"{bgvla%Խꕧe\1!lֳܺћեH9ږLllMPR)jQZ+lLYھSmkwk[|$q\N˕.s&KмѹZp ^ЉbB 9Zɽnu]v1K{['_"woX+`s" ^l^bp}Ӳp[L,PzUͻ,NeWt,{3 bkX;|E aGA.2,eYPLeXKZ2 |d,屓eAb5#Jn͛\vQr y*c/ 1ʹh 9!4,iRtkhD`s\6 Ioe*O'olI}֡[ G95eP:ټ. e;=~l6'Gm4ouyf-㊐T t/-Qgz/pq׵qe_JVtt戅 oy"ʥ}onW+ǣ+b~xsen{y|bnz͟rw7vW\J8}Zo;߮YBF6̗xuaӜTwk}ǶlL & ]OOnf!޵=O{K<]ik?76!;?ܗ=iO~=_=jt^Vӛܕ;S;t; @@/RKK@,@@ h\>il>e`|F[DlEmFgFnL?q\?j>r?pGu,GoFwFxF=BSĐQ"E>G9z,6 sG~=DH{ǀ|C(:+&sdt3DG=!7lČ,DsH#4ApȖȓ$IG|IWǔH#ƒHÞ\H JC9Dɑk\JV|ZTªtIʚlFFʨ«dlJma^^a ~aa^Nj=[aNana H-b%6آanb -:Y&>٦`}b!Nb&[`Zbb ;0*Ncb%c ݪ}'q95vbA[KPaB.& cwcZ-dHV;6aGcII3d4d:Ndc RTfaW&ePUSVe\eZ+~Mb[$eSeff`•?eflgnc~e_d[~r6fcgdgTn\6syNFek^oVd~.z&hFh?xff&2Vj{hH f@f8~]Yhifh h|i(.葆f&镶AiL陖nj~顶giV&StI_F>㘎!qKC[6jf篖:ѽnh~kHQ7-kkvk& ckwlx6kv>l2㱦l^l}N콞f6imˮ<35m&mnmm~<\^m̶ifܞmmlݎVnwQfnE6hl7fh~Fnfoso.稜^nQ&g%pZWm>vFoopppqnqqVqFqǾp7.KOq&q86pw*q+on6vYUkE.iq%?m-n37oD+ѝ+f"'Fs'뺎vss'87n,Mi!G2?3?PD!)GO엝r1Wt@mKVOp*VF?sXtjt:u\wA'u;uV]w:_qb ;7tQV_sucEivMg>%7oopfsruUguvfv\vvzovGxwwlu`wx{`Sv2Sv_?ax^mw?wFGYGwo/vyuwt7UxOy'y︤wSw?z7@z/unyyHc>nwStz_zr:kҢ9=:5kqlml^xX“/]v誑K]ׇcW l3N~y[?vӟOw{wo?wu `~ "D(P f } ֧yh$nxv_B,~xjxw9DH@ƨd-ꤢ}BjeA8]:fٗQޓ$#KXnenet`)f""E06Ye{֧[c*8'u6z褒Ff6$uj秏Z)"J|詓<ڭ:e R뮮\PLjZ诺6>kZg^mNbژ"knyR;-B. JnZ/r+o e/F ;pNK<1[|13鱂u,i<2%|2)\r1~?俯>kϯ<(? ~C— O{`<AvVYm5A6 gI0 !mvnJ O &. 7C0{j_Ap]9,erZ" 9gPLl BRx?e0F+IAE0pcluG$Qa^x [#A&щbƒ0c!!YG:QwDԴ'C2&C)H:MA>9rl+%IY^r(JVd)aswa/gyLΡnu|Z_ЖLݚ mrkҤ5eΆ&:9o4%9yNuҳ9c&iZꒈ'@JxS$?Ѕ 4(}]~0kEI03| k %wC,bmɵxջbw/Vq#6{,9D>\ /MF̹9PV2-s^2,1f>3Ӭ5n~3,9ӹv3=~3-AІ>4E3ю~4#-ISҖ43MsӞ4C-QԦ>5SUծ~5c-YZ'TO`k]־5-j\W-e34Oli~6mNvq}lu~7íuû,_7.id:w 8#.S83s g rA\"G4C>n,3o:γs,E}~[u ?Gq up;v.9ؕO==:Î?\7ב^\XW^wK';ޟv}8}gg<3^d< /wn|Y't2 !v(aF!a8!FA!fn!v~N,!!ԡ!!aLXa!"&""!!$F$ơ$!"&f&&! !~Z#)bP"*>b%+ʂPa'֢-.Pb (b0!#b"2!%"-ꢒ3!'"/!L# 6va11Rb3>9a44W(Z#66*0~c8#,29c::FS;j;rZvQaRHE!$R(E1Q8dEAQE^$FfFn$GvG~$HH$II$JJ$KZ@;Bio-Graphics-2.37/t/data/t1/version9.gif000444001750001750 3417012165075746 20037 0ustar00lsteinlstein000000000000GIF87aXQͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ i4ӥCBU**өO&mkUZ5v٨V]{hYYJ|n\tڝW,ܢ~U+w ֫`xK8ȏ[u+͜髧GpÚ~Vulжg}3۰] xԻ\7H΀BιaؼKή9e{/S9t>&x9_7ܚ@~}W jй6׀.a*h x&יuNh"b nX˵a{u_Jxa1 PљH]܊. >>&$S>{٨M*Ye\*RhI%nkm[aeqG&{'_7"9]$G19P)y6N&1ZaK:s&)蝙'š4ꣻv*jyZJYi2;+*k"*J+,ZW+k覫+"G/- l' 7G,Wl\w ON:!,00l32L#V|@-7LD'4F+MTWmM\u~udm]h-rnm+x]Potn'#b6*ozk烃^觓޹=:?:˾ο_?|?{!yh7,%V<;'Po-xj{|_O>?s_S_׾ pQ#%s{ rG' E?$$-- /V !L\fRٷN+ }llI8'!gCqE"zi~_'Dh])"RB>y\|Q_9퀦 B/kiHЈ:6Ѝ9,?0PD;8.RHG  ծIBђ`$XuI14 $Fn5IP2Ud%,CQx_9KU⏖]*5)\Bϖ[d(Sbȼ0f:\d$FjJ p)d5h.s 8Msgæ}ɗs}Sg OW>yRkxgw\hж)􏲜AJQ=TMh:Q< 8$$f U ^Aӕ/*]қbr-S2B܀F Lm#OZԙ҉=ݥVjԨq5m)NyNYSeZխ5Y"UDM[Vc,k(e!a=^׺Ծ.V{𪙕yix;vl3wg7Kv;T0΂ޮh<|ɢAwz҄4,jDGuEM+mHSԐ>4A+.DejYɼu`kPvui=jd[X5F7[kmhm/ir6Y\NXǛƷ m2ْ8=3[5v k%q:+{ O8L0u7M|hɓzûy9 䔇 9ugkƶko9}r'nu[]O֗X_9~ ;*ϛu~>tl2-G}'~ ~hC ct (җȁ 4+X%Hp(X3*@~9k34Ȃ6}׀#q>h05F7D8SqTH.@ȄϷyBH}Y|[h_Fȅq cM؂gڷhdf(j؅ 1(_!p'8r'XwHsbȈyh@ַke}XGyUx:h YB7 JLڤN IREb9dPZ\R:9ir]zhjZ_ZVbkZvzYڦU:EjycW)n{jrj駸)iIG:ɩ JZ#jo*|zJ{̨TJ:㹜* J jC|w IxiJ *[&êʮY' {ڧҺکzkyʫjzGS隯J˰ ۫ zzʱ *J#;Jpz &[ʊ8zU$K5*D;nʳ,;aʴ&XPGZI=۲C\S3aۮcuA]:zVo`j[9ʶ+4Zz/;p1k(۷z+Kk۱?~ |뭓U;WKp rK}zBK b۹[{+Zkk%Ԭ{k[KʌZ [ +z}4@K ׉{; +t]q6>_d>^l]R?;-C~YH!=PޥA1QNS3O^ZW>3[]N_>^daKY~li^kL?v2| 0>~-*n(Cl!.M^ N}~ 1~.7lR=tߜ/>轸ۼ>^"ܳ듮~~x>%ݨ6N펮$۽AVG^ۣ{zeߪN2~L.e]"< ދ^~g~N,*O^&/^$8RR:m-r>Ncq.?J`SFGLL_|5JTOTR[n1X\m/^?Z/Qa_vfcQiLnϣH/S=_v8vLkO53o^n /N_ O-?o/f{%Oo?1O/?@O>=%?b_ɏ˯  0!B+ tCBaF=V"HF3 rȀ7DI9ɥg6Mtf΁Q6sg˯i=[sfy&=bٽG 5ő \}[c=?.yvۭvwؿC78:v^yuϾj]0@$@D0AdA/ӯB ',O @1DG$DOD1EWdE_1FgYpC4/kH !MgrQM7#m=K6GR dUW_5VYg?ISPu4[u4_ \}vb#]vd+=6fq}iUk͖hoK%UjuUqVp}msw՝W{r|M߀=~Mڅ-.5Qx]&bݎ{ߐyPcSXˇ F~9ZY2ؒxbhgZhƖ裕vinNh\MMASIbsZYe݅i:SKꑵ[^ݼM{o&lnb魓y⯵bp9'7 "=溶ŻynoGn3еp=uQ/Ww-uo[M7WuF`+^)vy|ϝ֭^{=x'z^_ߣwoߗZ?Pl'>ouk )6Ё 8AV} |`5"{sBfj'Da:+.̓ /hІKZZش;D[hxC"qF9FЀ 8!qoJzx@&_c* >qz[\^3Ї,\E9QedEƑIy>3ꑐyCֱd1HCL -KB{]Y0P~od#=ROw ܬVNT-r /FKN_.yJD"( 0*^d&&[dn,ifbӚf7Y9D!:ɩMs\ s=JtBL\0 i*<'A|fϜ<^= :ЊR},(HkP&]$J#ѓN4e6x ԦE>=jP+~vQԔYTF=ORZT41UQR#hUvX(UաjZS֒\zծdG:)L+zW25)"{bgvla%Խꕧe\1!lֳܺћեH9ږLllMPR)jQZ+lLYھSmkwk[|$q\N˕.s&KмѹZp ^ЉbB 9Zɽnu]v1K{['_"woX+`s" ^l^bp}Ӳp[L,PzUͻ,NeWt,{3 bkX;|E aGA.2,eYPLeXKZ2 |d,屓eAb5#Jn͛\vQr y*c/ 1ʹh 9!4,iRtkhD`s\6 Ioe*O'olI}֡[ G95eP:ټ. e;=~l6'Gm4ouyf-㊐T t/-Qgz/pq׵qe_JVtt戅 oy"ʥ}onW+ǣ+b~xsen{y|bnz͟rw7vW\J8}Zo;߮YBF6̗xuaӜTwk}ǶlL & ]OOnf!޵=O{K<]ik?76!;?ܗ=iO~=_=jt^Vӛܕ;S;t; @@/RKK@,@@ h\>il>e`|F[DlEmFgFnL?q\?j>r?pGu,GoFwFxF=BSĐQ"E>G9z,6 sG~=DH{ǀ|C(:+&sdt3DG=!7lČ,DsH#4ApȖȓ$IG|IWǔH#ƒHÞ\H JC9Dɑk\JV|ZTªtIʚlFFʨ«dlJma^^a ~aa^Nj=[aNana H-b%6آanb -:Y&>٦`}b!Nb&[`Zbb ;0*Ncb%c ݪ}'q95vbA[KPaB.& cwcZ-dHV;6aGcII3d4d:Ndc RTfaW&ePUSVe\eZ+~Mb[$eSeff`•?eflgnc~e_d[~r6fcgdgTn\6syNFek^oVd~.z&hFh?xff&2Vj{hH f@f8~]Yhifh h|i(.葆f&镶AiL陖nj~顶giV&StI_F>㘎!qKC[6jf篖:ѽnh~kHQ7-kkvk& ckwlx6kv>l2㱦l^l}N콞f6imˮ<35m&mnmm~<\^m̶ifܞmmlݎVnwQfnE6hl7fh~Fnfoso.稜^nQ&g%pZWm>vFoopppqnqqVqFqǾp7.KOq&q86pw*q+on6vYUkE.iq%?m-n37oD+ѝ+f"'Fs'뺎vss'87n,Mi!G2?3?PD!)GO엝r1Wt@mKVOp*VF?sXtjt:u\wA'u;uV]w:_qb ;7tQV_sucEivMg>%7oopfsruUguvfv\vvzovGxwwlu`wx{`Sv2Sv_?ax^mw?wFGYGwo/vyuwt7UxOy'y︤wSw?z7@z/unyyHc>nwStz_zr:kҢ9=:5kqlml^xX“/]v誑K]ׇcW l3N~y[?vӟOw{wo?wu `~ " D(P f } ֧yh$nxv_B,~xjxw9DH@ƨd-ꤢ}BjeA8]:fٗQޓ$#KXnenet`)f""E06Ye{֧[c*8'u6z褒Ff6$uj秏Z)"J|詓<ڭ:e R뮮\PLjZ诺6>kZg^mNbژ"knyR;-B. JnZ/r+o e/F ;pNK<1[|13鱂u, k<2%|2)\r1~?俯>kϯ<(? ~D— O{`<AvVYm5A6 gI0 !mvnJ O &. 7C0{j_Ap]9,erZ" 9gPLl BRx?e0F+IAE0pcluG$Qa^x [#A&щbƒ0c!!YG:QwDԴ'C2&C)H:MA>9rl+%IY^r(JVd)aswa/gyLΡnu|Z_ЖLݚ mrkҤ5eΆ&:9o4%9yNuҳ9c&iZꒈ'@JxS$?Ѕ 4(}]~0kEI03| k %wC,bmɵxջbw/Vq#6{,9D>\ /MF̹:PV2-s^2,1f>3Ӭ5n~3,9ӹv3=~3-AІ>4E3ю~4#-ISҖ43MsӞ4C-QԦ>5SUծ~5c-YZ'TO`k]־5-j\W-e34Oli~6mNvq}lu~7íuû,_7.id:w 8#.S83s g rA\"G4C>n,3o:γs,E}~[u ?Gq up;v.9ؕO==:Î?\7ב^\XW^wK';ޟv}8}gg<3^d< /wn|Y't2 !v(aF!a8!FA!fn!v~N-!!aԡ!!aLXa!"&""!!$F$ơ$!"&f&&! !~Z#)bP"*>b%+҂Pa'֢-6Pb (b0!#b"2!%"-ꢒ3!'"/!L# 6va11Rb3>9a44W(Z#66*0~c8#,29c::FS;j;rZvQaRHE!$R(E1Q8dEAQE^$FfFn$GvG~$HH$II$JJ$KZ@;Bio-Graphics-2.37/t/data/t3000755001750001750 012165075746 15412 5ustar00lsteinlstein000000000000Bio-Graphics-2.37/t/data/t3/version7.png000444001750001750 1057712165075746 20063 0ustar00lsteinlstein000000000000PNG  IHDR)@PLTE{hpAiecG[QG=2@(pܻ;ME"x0\^/4ܡ+~ETҝ\vpGCppY/1|AF25oWr>A;K6/&}d,"1fyq^TdQ,\C=# :es:2+7tA: XpuffkI(8ә] 9]&rM!ȋjeǩ$d^ln*5PJM)-8Vo}EvN6lzbw+I<Ȝ t:rA *r"܏CI[=OLꤨi}%9{Q!ۻQk:7ٕ (WFw#O[6/ZH+{q{Uy%ra#+$'B|7EQ4"Jw#X#l7ۚl;F7ZSė1ιl{%Ije)qل{3~A*q% w-mk2TX%l?ҬIp!&Z"ױ7-*Hk{hb帒&ik*{)yN+_SIO‰O[|.{-0iVI4ʉ{ϋ5~Lw5)6aix rw%wik=,Q6}1 ro/֠u$SE,)n&?JBaAFw:6||44*sc'lvA|2ý /n..V̆!Fֿ1Arfd?ܛpŵb?يŷ9hY&mI>4'hqL>4z6PJZ$~g0A+>F?>\Jl¶0'Mkpy8hĞf{qA6lt&'EO}'"%{~I%w휜|"/x".k/K r7SS?knc\E 77˾ .'T$?] )wY%\tZm˧cu&w)t4,wK/%ppW12/W}sik}Y֚['%a"{+T}3s?h rWP0CTpYP66]ĥb\(o[d|]X7l4TC-CUBG!&@ЖC[ DžěwA(2?4LbrK=[\\CSOsruNPrW+I!wљ1:G˝ t rA wxDw&@[{;4 Cӄ%ja߭b䢀3t I~Ŕфa|]BKÚwaU,_P;DP\+{ܧD+bE"NrGpWTgFyK/ň1 vx\vc3r"/E7W/ܡ=:I/Ҟ_gsM>QIRڑEظݘrq-x{"C{K3}%wd>@ԟrO.@Nkr:Xd-Ckd_8@,ЮOm3J+%}uhpX|%Ѝ[Ѩۢ/wȋ͈1{cKy7%+HP5Ec|Gŋ>})ZPj`/_&L^l9"kqԮgS&L^l9r0y=`z`p"/A<TN`]㪏Ywf\S;=?1>;}|>*ڭ2|] ?j+Һím$E^Ρ7Ra;ܚr.c$ ֬]:3Q<)Y95{iXx0 CVگڌr;H$wֻrz:tG %z ~9C{,^mߜK+C{hy#pܡARg:3/qB$YՓCUlKQ;7%޼ܡsP;xXn`v l 3ҨEw^:4kѧUI{[әsͻ7۽[#\ײf6Bvt5,gp_q BlU[ áv@ H"HL&:PH*ZH &,zcY2*bFTj8x̣PH =I&m/^(S}RP)"dK9W~lՃP)TKD܆ȴg) O 3L@AV2 *+es0䴩cz/&3@uSS%+T} *D}Ɣ .pΒ=)}$N5OF9L~bNڨCP[s 8T=PIQu% &=i4J9zN' ^IӊR)ꢤd^5T}&AXEP#_Xq1'[J׵*H:&XwJSu'I0Kgf9!0],d%C؂,ezKʬ hFq jkݴ6-A_`@p[۝nBk[%mn;_! s>ѭtznqd5t&|\VMKB׸⥭ݛ[>`/X'La GU/Z2|j8&w?_x$Iⳬm[46q^x;L"HNU\nV8=Le>M5|hXʄT$GwfQ6ZSo2BR}r-ZgsDq}y󢛉#OН $;=%"zvL~[i2^s_`N<-ܐJjiU]Tz]q)'t<3G*sI "9F]'kŪNؤfukETQRS~|T7#y'mfzrJF6v2:TZ|c|O/?ͿV{#~J%gFh+?N',;@Z'kf1UQvUQfY|%yOg TVimxnkjA{H2MWVLV|Xw$8V.(gHxւKRa&}7|YׁONwDG#G%z6glLXNxkфx'(׶!'mvoxc}fhmzEreƄ#qaF>gXjZs(LA5Rz դxs((|7%Ehxs7:G$cPxXXYt1xLql(~؋hfEHNhIgP9X+`xG|jx8tox(rj6hCԘH"8∇!YFDx'XN8-G{"8|ٍVyiՍ!imUYX芻Xr9Ȓ,ْuA/W N )SksَBlfDAIwxM\Y Y^!H2g6yQaHSYY{HxԏLYMJ|Y4RAyhh}֕("PIiZk2 xѓzY)aĘra"Y⸙CwyGil)ɑ83A^؜ ؎дIřٞtc9xYіhQ9K)4y|I){YAy#y [qP 9﹜{Dx ڢɠhۨa1`hVɜbڏŞHFAQ$ $I!Qhw٠٘EJ颦*;hhѤ** \ Z{멣aɓ?jljʎl#wׇ~WpZyS$u)ȧz : F%BVT.#SS2EƂ ѫ 'Z/2N,J;CC)3B⬺.CZCzSW,LZڊѺ :ݺm'Q+D*Z/:VwkzxƅywmzJC%z k}'XX6|}7u̚J$1۱! ʬ [Eh&R=D)-Z2P;ZB>"j_vHz?:7`˭br>%X؅&Ym{'x{~WGZ ڱ꩜u;p瓾ǸC٨ƹȉ+[ƺ7 (F]j@ڨ۸ +ƻuƼy [m:k;K6CB2 S˱{2kvKk7芿Zۮ¶dM<[[[ \=",'̿iL#&<;B+g-<:+?, /˾9LvHJNPRktiUuYz+ le]GUqQuYv|rx,S~Lez-'nƇLdz "H7ud񅜇""Otɠʢ<ʤ\ʦ|dR#oTAVeI_66ː43qg[{GhHGV*yja% Ŭ~EkU|H<(|d|k | Hݬ|l~lNV,9+в] Mk "=$]&}(*,. 0=4]6}8:wn"qyl%TW}FḃdtdRFy\mkq=\f||ǮgW^d=FN1 rI'trɜjhHmz[C]qL &(V^|"⹜06~+8.[㺅>][?^,nH*m7NރLm+N2)No5nʳLwRA;p~V=>vNC0G|nPMz.LX^}n芾~I^~阞L;Bio-Graphics-2.37/t/data/t3/version9.png000444001750001750 1125712165075746 20061 0ustar00lsteinlstein000000000000PNG  IHDR =PLTEQͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ Juv]N1U74(f`'t/{s:)Mo=>:d%QNi2k12t?5[ '+DBwtϯLwإT:CkI i}QEKf-ݯX;{~eCwttw+Ad~r]_ ?UU:.."Ka a a G@Mf_Rw;^ؼzG5 t@UD; ` a <K&ɇr7Y,d;uf YϺÚb AӨ"岔 Gw|է*xI/zyҝT"7f N.?[Fd){U޳Dw=L$CRݏw-7KpFwQ(EgaZ7ڜA&^O~t/+2n]ZUιYtCZRqYt5tZ.XW0j tSMn*i)} #Urn\#3>GD_Mo]HATʋ.4ohRF C[6or GuG_pZRX۝j,lT"{}sQs)(D]ѤY͹xO5EωҘO1I0@wG-eY):?+hqV{GԭAY-Y굓!ht@;_`̏OJ\txˆ;N݅?ߡt·kգ#y'ӛ tC_ බ={U݄׻۰wq ~ ⶶ Xwk=ɨՙs 77ZF)NaV.aݳw(CUr舨G~(O3k67#3E}AL.yl8 l@V`?xZh1LC3hc;;4 mw@; @; @; @ 04=5g-še^S'2fԘ2Rw'RCLhX?owi QU1?ڦvGw萢tU?V9 i&t@wtxbjҹ[c[1sr[E*tۍpjzug/Y{:uݣ.SoBy3lg8=3lɳ]p]g*z."X!Bdvj諚ieW.Ҟ}/G'޾l SJi4/Jצd)1-Z[y>O$ߡMHnK-i=_"&ZvE:oI˱a1Jrc _Q.h^{SJe}iHCRHÜ@Sq+mo+M}籠{ɆϜ`\,ҕLr{Ytw+NwN4ݘF[]XvT&5=6{v+n{Z5SRo%T^,(jWfL_ݚj܋dbײnr^j8j 4ބ58wݖ~\ڄԙľLk09lAd^e߇8MQhwݢ{f+d|~>_01,yvnKFl4Llb2%ݳUA[=*'!vORE$1}M]ct?Onӷt|[nϺIB뾱vOo dJ3_+E!jN}%m­985@ _{~+}%^݆)!$H$mJ?@T;'+?*$Ǭz^GaCםRto'!db}.CclW2jJGfdץȡur~3"]B|E0aVw5wuBfBTvUYa";]ջi|(з-QWUt=tUmXjݩ"X/?Cwt'.8.\Mn 1^qGDht'"u׏ L"MttvNP'I;thXtѺӘAwt@wt X."8@;gzte5'W|܉})/Kp^P|SPўFU.[zs㔏erR^9&qQ+}q-G}Z_ˮcI7Sܰ%8\ú;]ֵ|Zӭ|7hFҵAY+~?sv2zJ;@;tCG;t݉? E}%be-OL3Ozm .ݿ}~fk>9wꇃ[rגɓ?e7k NN9ݍ˛A"mmY-‚SK%2J@ufaݳW(NMƁFP߿h}e&-4TNŀUP W(QbJ!꺨KgNÉKg\GoQgi]DqLwwOos5S8]Yџ#\e5vFowU s݃<{I0pWʧjF.ĺ[Zw!< 40ZDnT{.)P7K}wtYԝ*݊:;"Q83t^X8Sd !^;v/}+eSd;ݍ% q k̥yd,}+K($^F ~JL]ǝ{tEsW':39yJb&+^•͝Uu{6[j+7Ν^]{GDsAK zJުDhHatÛP#go•}UjGNlgt涪?yT•]UI7$Q̆6 ڸ.qZ_ŕҜ~edtyG0K9ETt0%[#&^VG5e5 e]oFr(5|xk>%iNO}*9c~>.f'-yMmb+9 Y5"SeJkiڵϏVt׫Z)ن'"?sʃq{>vW2mf:Юd:}r:lVwc2+mEwcQ!QHڸٵ?fMLI뾩펓Kc}*/cuqXT3vc2m,ɭHfvYnUW>WӾt_YsM_\Si/ֹ\ﻫ~U;rؗ >G]=42CZfQhsݢ{f-mY3Z p8:Ϯmڄf]3,鞝 bѢlq}$?tO2[-nև l# 92Tݔ/>Br=o懪ۋnolݓTK;ң}WZ?yP?uRtvV=3<~*ki^ b}xyuyIR<\4bOKw48(s2;H%WO"xa/8{s+ =iG=nd9#߷v~'t|1p.KcMl6L !E{)6˳Jw+hSyd|bFz!Αt뻇&9ݥoIC1c[TĬ$ߍ7֟,)Q M^mYw݃,ҽ+WtɾguWs/bSu"uWVA(Lʡ4+Leg6Y3 0օT6疡 :ѝƺ+;1t;gP"ㅳ7߭Aw(#WL&Aw(Ndo.PAizNwZwhCsΌEwh;ht@wt@wy$;tCkr6Ew(} =Gw(pon᳻GV>O ݡL ݍ$FǔцBL.Z#;囻?S<.KwcP&A ͿR}$#V$q9NgJEuf= `\Rd @g:ݡ/:ݡ#:ݡ#:ݡ#:"}3On f0槹 1y3L41̠oD뉴Pߡ؊f;KwUtde3 P~G|~efje/eU@wt@wuWGۿ{XѺߗ$ӓb4Bgv=LBxV'qYн624Һ׍hbx{8@%{u󝙬b>KgP5ݠ{#OwDp,Ri\w CG?)rrzJ u=^uAi;j; j;k;k;\mh\l%7tUZ^9]>o;mx559Tq~*|Z)nǠݵE\Zi]=}vx7kwŔ1EZw=%יIuHw9b{ӸҸt@wt@wt@wt@wtX=}V:@ ugG%$ȧ+HE#ڈPwzV{~3& ;T`ēvٹFꮶt[pf=|CwL*!ݪ|:8 Sa̠;TQoLG̠)vUn\>AJWϠW eG@w+n8p /@NZw|#'`y":SĠ#q8=q=y5y5Nq`pgt社`[j9 u社Ks YWHwvD_LQUcsRTY/̠;Iݯ ;T9/ Pq0Cu;tĉjݡ#8E :ݡ#:*wIENDB`Bio-Graphics-2.37/t/data/t3/version14.png000444001750001750 1141512165075746 20131 0ustar00lsteinlstein000000000000PNG  IHDR(0UPLTEQͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ =K)ݗie^t Bbψxt)%D˜24YVwmtI-3jm;&bdizu1cߙ̹&?IfT%7޿\۔,|dT7`qJzu3ӃdzZlߢ{ХvLk-'>6̵Mt yuڦX_Z^)UuIG!rhw3]M8w+g܊smOھ So?Rɥ4kWm2ܘdtD͖V+;L 6O~I~Bn7Z#Uϵz({[ ~Eq./dž$=63k7oȰL-m?RT-Ұ$PƔ]K7k+1{IHO\ҠM^,2[^u7|W>[)?ֈkXi{)"I tw\KktOVkfj]uw]aNϏCFߒ%{_j*j]I?^ݨ7J~P@l1v`F)^ݚͩf[{2'Cv 3^ Ȍ^,rhSf}VZ+Wke/S1A *ɖo>Әi}#eu@eWuƬUN~'u.bSQv2i&틤G ;uwHez*7 Pb~Awx-ݡE3ﺝݡEI[[~߬"RwIR>Kݡ+<ݽ;јQ/ם t@wt@wy"; C[vEwhc[ NGwh (y$\: ݡM;sSfY!BDq3;4xޑ}¡yQ==oݡM'b3Ϻun+bCwhpđPx3*^cF<3| |x#]U30c0000}@LUۆ: 6?MAw>|' WSOpLKs$;Ƿ*$Vut~qۦ2!;ld3ߠ}aD׵&J;[)* @; @\w b2 ~获h{:% xÅ7a!YeNJp[}WYv?lZ~4U~]@teZT:Nƥ< o=mLRw1e0uB%8IV[=r$nv FRGw-vtiwtiwt;C C=j])6mGw(A;UYn]nR/}9Y $ Gݕ.u95"1;ggN~|!9yv>o(fRuWήWvS{3{ ?&՘uHwUb{yM[zHa a a a "{tC~1-ƣ;Pw-߮h^"|k#CgLEUK` oڵfwjE;t[kJVnݡ?'3?b@wբ!CwpVsI@AN^&9%9ژAw蘳I@M)wUa\>%2 DH1нz<ݩ۟q"lN;?@p ?σ@j\;~Ɖ{-E}'Z\ݹ$^sQqн?ݫuMwb=W&9ytZ0t>蒫ߜl̠;EM @YvU:&4D;@?̈́q"l@w.@wt@wܨ;IENDB`Bio-Graphics-2.37/t/data/t3/version2.png000444001750001750 1035412165075746 20047 0ustar00lsteinlstein000000000000PNG  IHDR)PLTE{hAicG@puMP)؊a^ΛɆ-@Or_Sn@W8әnBG5DMEKԾ783ʄб$Gf,K8-1!w~XR l}gZ}t('FI.ɡpzx/\^liѺӺC>rG% r&wܑ{>1!>OLꤨi1I;xWtLGC u&~5rA ƒ^II)MNoR.&hMdYmd#%vҮү4 K~U&6#{Y&=$qU0}z]W"|էVv ~RՓ^<2!EnQ5i] ie/n*D>l$8yrDҢ2FD}n[zkV-f[S{{X~r*u6F9wm5I,%.}px/H2%mMfjsVGU[9 ?6S=פR_"sOBK:6łV%"iMyMWRZU=פR;|M%u/6ik* R8ieO]%RT>;ɜF9OyQƏ)<&>? 4/{A.m\!ʦO>dQWEdw?]<%Ä́'[P(1?Sdž/WexlzB7ȚOf>~7mݣŊpӗ57&hZΌ{V'[Q6m#ߤ1ǘU-ߕ0Fφ~|*gL?ٚ&@'Žwe6-44NTb*Fmrڼ O(2u]\mO~7lT _p&?ŵO;?h[ux z>6]_՛দwk띋[X]w1ŋz8p.v(pSӻUŊn9§hKLř_[svtflxO޼8T%~K.C]//U]]ޜi{fKu:qb᪗朩垹l0-U/9=RX8 `f.ܽ9՞Nׅ7w5}-\ׅ7&Ny1oQ)UGҽ* rQG'˿E⧔\ME.Y|DFVZ3/O2iKHPKI֊+%qr/Tt*.l-fG-\]ѹ;.{kHCVÜASʦwk*XtJ lÓ?9:Ǜr+qrW]RZ+o'wQkW'4J[ʱvTf5{m~Vn/{&eJZMmw]BESnOhs]OZɗrO1~MƖɫHfvk{jsl7h/{.32-e3 wrwW}Uې;r/ \ieCjN[{tn{6bQdMr?va1,<[2چJ3.rf%gO.8z?*-Bܓ̼}(i90'CMq~ekn/M|}cR+0}ՏW |3ߪ VW |ܳ]*}w(kiܯV~ br]+ œ4{Z9ٜә=DPH˖UU'@?ߵsr2,k8;,%l'LMy:q7q.ܤy,&PP6zvwKJF*eA p'k?_׷- QיܥнҧrBrs bV,Fg](\'Qmrq"YfZkBo/*(H=PaL" X[L]BQTUSe;C6+wuv>q@m{3vCcRPU = UM}B[m&foVDDd3C]/lqqM>?mɝ:A]$ܡ;Dg w-w:35rA wxDm1&@Ӽ'`MV|E}FV޿n[-$1SFB%FwE/rkޅU9@p[AorTr2F[8^n]Qa.Ʋ#zǀA ws98aP }=ܜ_վPn rXT$K{>-!r5FIZl&KkGZwh!bZwcǵAS!;,ΌiQ<QN?d e;Mfww(b؟Mܓ}CN?g+NK$Eԡ{kciB7nEn]#/>7#ƈ-ufr;ܔ#CTC /Arj;@u/_&L^l9r0y=ԮQŸMr0y=`z`/Q8u}>2g;ܙqNZ|QCk ʌNf zw-Kf {96KEEpk^]Q4,r[.w̤rGp[d1ڦa]_A P; 3 Yi;ǯj3E#][/frKzi}s.宮U돂lrId dUO> UMzijy/E˗xrB ;A wxLI0r'ڡ5`B V8IENDB`Bio-Graphics-2.37/t/data/t3/version2.gif000444001750001750 1330212165075746 20024 0ustar00lsteinlstein000000000000GIF87a{hpAiecG[QG=2@(pxXn`v l 3ҨEw^:4kѧUI{[әsͻ7۽[#q~_?I| KǷ7u|[=jρ F$Y]*׵6}la%nPass]Cb&:PH*ZX̢.z` H2X #4dY:ڱ*rG'It ɓaǐL"i,,JZ2%jD.Nz (GIRL*W0*WU`IJ69U-i'[i/(a 1LLU^VUtWJOz%.blF*2X}39gMqsOēFurJ'1MU>zMyfQu2fBYBӟBe INwgI4:PRBIR橡eHQpnԥ1hRj]H[S֓)8Oh33IjϧT;U@O>C\X 驚Jέ$VMmԞZIm+drZkXWףUKJ{F>b Jխ=dQ:Ou/%nצ6%);KkzjoV$cݨnONKUQ-im ҤLtO˫澤tYj׺"s!u0.VΛ U/MKڷe/~uՍ~#N]7nM" `dvc4%c`Q9ؽh"4Lb$gsCh6ٻfE?,`PL*[Xn"3Z*Y.YN *_X(deN.E,P'L!9M8:gX5=kj3YX5T1+Jtj3T:MZ~sm9煸ԡdJQYul_p&HL+r ss8uLʒ QqrIm"]ua#\W~!ts2xȜʼ;l]fkfU8,R6*T ̓IhfLK|wK6?UEfz'W.5l,JhNhYX],gu~}اǏݵ ٦D)\/N"K٣lY C-|MJR>{"ۥ;ۧmۭ} qj\۾me ܞIL ݮŔ۸ܐ3<AAKڜ;}=]]ȇC-Jt G H!- ^4 Lv~Ird,N~4R97'$sDI$Z (Rrsgw0e%<=VO]@gBD^FJKMfGP>،}KBrY{s| m>QeaN叮L>W.y>T̼Veq^qiэXp.уng^k Xq..Ф.+m扎Nq̘q͞~^Tюne^U>eRNn;Bio-Graphics-2.37/t/data/t3/version5.png000444001750001750 1040312165075746 20045 0ustar00lsteinlstein000000000000PNG  IHDR)PLTEͅ?.Wk#KHiH=F2̸ @ڹ/OOUk/rݠwޭ`""pfͪҴcGjZdӐiii{hzEUp֥**2޸Ep|޳pzڥ pAi+_ؿ ,阬e#w."jj!;xW LGLu&}5rAp#H^:II))MN^R.^&hMbXmcʽ#_%sұү42fE* riޑ=Os~[JUk۪ݱY#{Ƀu;ϮJn(K F#iT.?mF :.&{H.wj)]j5Rxv| Vȍ,={))ȦC`rw?>&ݧQ.e;lj;r#KK^{H 71)LSm_`һ8ҪYr=19&K8],.[ڐN.Ҙ9zw)-]V7v>_ }-p.OuL,Yӗ9;Le?m+c(R e^u{Al];ٰSh, {6r;3Ey7kR1Ǖ/-=e HR/mWrZ6YZ}-Q]kqo&S.Ċdk7&jZ^9ȍ{3VΖuqFv}1ǘE-փdϳq?K&{:40BW8ܷXZkVC$/&b&@߳OGod;{^uL5py?ܭZ\w6Ӣ ~XwcNPdY3۶| 8ܭMj-lC5B+Lq"ܘf^~{R\;tNeaDjrg ,F뜈8MZmuSVS8_媖/?x"IEk.㛏窖w-6Q>?Xlu廘ҧz8q>w(sU˻zYn<k$G6}̡3=yӡ: ޹k b]#-׻bV\6yۮ͑ Gl-q͛vqg)lg:-rcs3C7gvhg]Z~h7t.ͱTMF.Xu$#|No)?PIx,ScٿzET~جy+L~kgRVg+ʊWW㳖VlۤqѻJ}}P V!~T\bRRY{c7.u; Kʂ| urqIdRQA &~ަfɯze5 =tZNZ r?_%{Q ϔyM;.O,պ냼 w=Yhg]tR~UgRT>Հwt&~KYi.RqشlyWlcp585Y-JG*w+sS .WʽYCV'-K[^褍r?[u{ԘFvON8l8:MRE ɃmMan[sKUeUX&,swob*Q1UZCcUW2պ+StJ}uz&l7Y14}WANVZc(:ɽI~33&;ɽI~گ,bJ?"WL.'c39_MֽJ ѕ\ɯ>IzS74xAt^.E0$59$AXp"C>nfWhm>[4n۸&ĘЎT69wwSJF*deC]HSOʯ7 LRAs;c#S9E6u;G%*}>}~Q;ν1N`[Zk߷&i $D#zwߨ8'_L{A&t1)q*˦T1,]m]VĮbwȷ+r/ fy{CJ6疩ƣ|`Lzd[xyaŽ V"CyzNFvrO։l""}9X;A]=H pA08Z f`h;7Í@p#?s-9ܾ]󚀑;tM|U7?"=pQ s$) ~ ]jxe$y V"Lp;\5%Q.ίj(C8zI;?>SXrFIZ,fK@;tݘ r<=wܡ?䀥: 1;2_Oq"i3LUx5c|EuZ8Y˪Yx[@*cx>>olx\8hID7:|2+?$ u+rN^|.F:[.#w(E'rO-r*AH '.T"wӺӟ6o?l L>ؼ|y30`f`huߚ֝?y30`f`͛7ӟ6o#yYҦAÕygoy["R];T4%nQBރZxw4di89xvåy*wHi;\u`&;"%+'Fx}?e֏un4My(rgUmAF$~6ϫ~M~Ts v i紃;rW{rЪ kQFܡCr3/B"Y5SUqnKѺ6%^apP;܈/ ÍB3e])_}?҇k`?{Bwj"E;A_}A;Hޯj.evp'nu:_J^zvev=UN{ả* ả~@A^ԇ_뎺Aʝ! jr fr;UNp# W9*'\7Ý}@p'P;$a" IENDB`Bio-Graphics-2.37/t/data/t3/version12.gif000444001750001750 1310612165075746 20107 0ustar00lsteinlstein000000000000GIF87a#Qͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ xXn`v l 3ҨEw^:4kѧUI{[әsͻ7۽[#\ײf6Bvt5,gp_q BlU[ áv@ H"HL&:PH*ZH &,zcY2*bFTj8x̣PH =I&m/^(S}RP)"dK9W~lՃP)TKD܆ȴg) O 3L@AV2 *+es0䴩cz/&3@uSS%+T} *D}Ɣ .pΒ=)}$N5OF9L~bNڨCP[s 8T=PIQu% &=i4J9zN' ^IӊR)ꢤd^5T}&AXEP#_Xq1'[J׵*H:&XwJSu'I0Kgf9!0],d%C؂,ezKʬ hFq jkݴ6-A_`@p[۝nBk[%mn;_! s>ѭtznqd5t&|\VMKB׸⥭ݛ[>`/X'La GU/Z2|j8&w?_x$Iⳬm[46q^x;L"HNU\nV8=R&b%U5՘T>b &Fu;d'8Ax6̳RVcξ?h3%e)sζ'2dgnմ& ԪyFFP99MblaKgOo4J+jѡ^JgvxU]뜪"VAu^r-*2ۋxn6-j[H9}ҹ0 m%􆛍Ʊ=aJ~;wK)ކvZm{NO;5V657N4f(l#$)Ŧ:X.>8WI;ו|9:kܙ@ON\ ZԇeT3Է{`Nvٖ`nT7iHd4 YI>Cy7fT,^jG|Xdg&5sN:կ1_c(en7g}g§֟/8lRtխiyV3>ſYRN'w'gdtru~{oS#\cJGtZ鿞@OVҨYYFg7ĖxT2f|jM|{ƀ耤wnxYܤjeRaHNb!K%i|x¦.(dF׀xh7a}Y巁NdwD⃵G#zMmfPHwGOz{FzxUZDWv1aBbdwjnh%ZPfL8bwq4f'~yX|Y#5@'|pOXqle(k%K凒ȉ"H$cPxXn"Ƥt1҇qm؋hVsH|gq!|~+HȋfvG(XjxHt둊͈(r6ڸC،Չa(wVHwH؎WXNr2PVȏ{e ل4I h i*Y9Uw!D48}i*݈|ؑƘ~GiMItŽ 9UARyw#ɑAٔX-\q~hxf=\Yoy-yXf)O)!HɎqe ޸("Y2YyhVYkYGAeiyʢ j&/*C359L\9 ݙ#J4PJGA+$U b YZʣٚ*uhlx!:$ȚzftyG*::s zv?⪞GLoxTB)dE?AIyx,يLJ㚭گVwlTxzH-"MįJ20K**9U}w}^"6˪+KC&b'ۯ8VghTv3[D %c-1˭2Kk8V8:VZ2Kt˫ڮð`%Ĵe5Ȃ;r dVH LzN(zdO4o%fطۗ;[Ql[Kj #TRjpQ[cpaٹ 'iۍ'Wk҈} '_Z_jٻۋ pKZ{fpʸ{蛾7CТSU]+[Z`AKXFkWK ܿ ܿ+|_Z DK{-\v+{|*ܲLºe,((&˿/,B2l#L[Cܮ k.ÃNPT\V|XZ\^`ޡbo#|9!e\mI7k|mJTbq\dO+qm38r u9<ȄjXw,ɋi\{!i#Wɀyq(rvL|ʨʪʬʮ@B=D]F}1" ոmT]=Z\m5Į嚯÷Տ4#f}G51F umH-+;_;J!b׎G(]Jֈ=E ,ė}sMٕE |<ړġMGăm6 y\ڸۺۼ۾=]}ȫLie]DlQ݌4ptN˕esց}wmHbd<{xqw|ހ4%ɚud I$߃ ɊkE  d)KvR[p>' Jz>>Ce~[IZ~ge.#%搎N=^~Tv飴;Bio-Graphics-2.37/t/data/t3/version15.gif000444001750001750 1315012165075746 20111 0ustar00lsteinlstein000000000000GIF87a4Qͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ xXn`v l 3ҨEw^:4kѧUI{[әsͻ7۽[#\ײf6Bvt5,gp_q BlU[ áv@ H"HL&:PH*ZH &,zcY2*bFTj8x̣PH =I&m/^(S}RP)"dK9W~lՃP)TKD܆ȴg) O 3L@AV2 *+es0䴩cz/&3@uSS%+T} *D}Ɣ .pΒ=)}$N5OF9L~bNڨCP[s 8T=PIQu% &=i4J9zN' ^IӊR)ꢤd^5T}&AXEP#_Xq1'[J׵*H:&XwJSu'I0Kgf9!0],d%C؂,ezKʬ hFq jkݴ6-Aa`@p[۝nBk[%mn;as>ѭtznqd5t&|\VMKB׸⥭ݛ[>`/X'La GU/Z2|j8&w?_x$Iⳬm[46q^x;L"HNU\nV8=Le>M5|hXʄT$GwfQ6ZSo2BR}r-ZgsDq}y󢛉#OН $;=%"zvL~[i2^s_`N<-ܐJjiU]Tz]q)'t<3G*sI "9F]'kŪNؤfukETQRS~|T7#y'mfzrJF6v2:TZ|c|O/?ͿV{#~J%gFh+?N',;@Z'kf1UQvUQfY|%yOg TVimxnkjA{H2MWVLV|Xw$8V.(gHxւKRa&}7|YׁONwDG#G%z6glLXNxkфx'(׶!'mvoxc}fhmzEreƄ#qaF>gXjZs(LA)WM؈䈉HLwXRsshug6#PxXXYtQ xaql(؋hfEHNRXMIgP9X؋`xG|jx8to9!H~j&CԘ踋!8!YFDWH'r2E{"8|ٍVe]89ՍȎ V(J'ؑx|HwXXG,9RևJ6iM It xRLyJINT}%I%ZY!x"a/)~DytȏAg!)F G]ϔT\xh"B(V⡘I)y}ѓ{Ɏw.HZIgYJ똖ْA McVYaHGv– ZșiHIKܩ䙍y^IyIvGYIf:IYxcQ!iџJ٠Y ʠəʞ_2)Lq]"YGyܙKIrH|8 H)4S‰'j'#ظZ :ZzjPYU0iB)dE?A< xh)-^d,ÚȚ/{|z'z-D142#,ZJ;Պzw}eiM,몮L[:jZ; x~iK+D+Z/* [x"jXG-( { KC9K0&@a:7皰JdQ:?[>;F>ŅR8VX~Yh%3ʳk*1[B]KcXIz92d|;xsʪ +pIָHi- ;o{o둣+KoW*nk˒+n˧ۑm ëm;»ؼk[轉+;[{蛾껾K5d?Kq{+i% '˿Z:+ {dڶk#۲  L*{'ŵ'jKrIZ,LU||f{S ݼ9н+ѥ]ݹM|k(*,.02=4]6}8:<>+ @=D]F}HJLmB!}&M=ՁՂ[]ҵRb=d]f}k4CXBBh FW[h_F ,#3 xD-4=ŀ}Ď u{Z2,*F=V`24*^Vnt(W>IY[NI]\e/gdn3f㰌㩦mow6pA^H.`suC^R^x>R b$Ha~鍔嚎n镞~H>THK>NH^~븞D;Bio-Graphics-2.37/t/data/t3/version1.png000444001750001750 1057712165075746 20055 0ustar00lsteinlstein000000000000PNG  IHDR)@PLTE{hpAiecG[QG=2@(pܻ;ME"x0\^/4ܡ+~ETҝ\vpGCppY/1|AF25oWr>A;K6/&}d,"1fyq^TdQ,\C=# :es:2+7tA: XpuffkI(8ә] 9]&rM!ȋjeǩ$d^ln*5PJM)-8Vo}EvN6lzbw+I<Ȝ t:rA *r"܏CI[=OLꤨi}%9{Q!ۻQk:7ٕ (WFw#O[6/ZH+{q{Uy%ra#+$'B|7EQ4"Jw#X#l7ۚl;F7ZSė1ιl{%Ije)qل{3~A*q% w-mk2TX%l?ҬIp!&Z"ױ7-*Hk{hb帒&ik*{)yN+_SIO‰O[|.{-0iVI4ʉ{ϋ5~Lw5)6aix rw%wik=,Q6}1 ro/֠u$SE,)n&?JBaAFw:6||44*sc'lvA|2ý /n..V̆!Fֿ1Arfd?ܛpŵb?يŷ9hY&mI>4'hqL>4z6PJZ$~g0A+>F?>\Jl¶0'Mkpy8hĞf{qA6lt&'EO}'"%{~I%w휜|"/x".k/K r7SS?knc\E 77˾ .'T$?] )wY%\tZm˧cu&w)t4,wK/%ppW12/W}sik}Y֚['%a"{+T}3s?h rWP0CTpYP66]ĥb\(o[d|]X7l4TC-CUBG!&@ЖC[ DžěwA(2?4LbrK=[\\CSOsruNPrW+I!wљ1:G˝ t rA wxDw&@[{;4 Cӄ%ja߭b䢀3t I~Ŕфa|]BKÚwaU,_P;DP\+{ܧD+bE"NrGpWTgFyK/ň1 vx\vc3r"/E7W/ܡ=:I/Ҟ_gsM>QIRڑEظݘrq-x{"C{K3}%wd>@ԟrO.@Nkr:Xd-Ckd_8@,ЮOm3J+%}uhpX|%Ѝ[Ѩۢ/wȋ͈1{cKy7%+HP5Ec|Gŋ>})ZPj`/_&L^l9"kqԮgS&L^l9r0y=`z`p"/A<TN`]㪏Ywf\S;=?1>;}|>*ڭ2|] ?j+Һím$E^Ρ7Ra;ܚr.c$ ֬]:3Q<)Y95{iXx0 CVگڌr;H$wֻrz:tG %z ~9C{,^mߜK+C{hy#pܡARg:3/qB$YՓCUlKQ;7%޼ܡsP;A wxLIvhЃB݌IENDB`Bio-Graphics-2.37/t/data/t3/version8.png000444001750001750 1131512165075746 20053 0ustar00lsteinlstein000000000000PNG  IHDR CPLTEQͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ ck1՞Q/.ץ\-]=qf)K)Gb'z`ܷd&si$Փn<b)KpHw}Mےg<VKr%!pz Iu#֥<,U%n/:c9ai)~~$nd=vx)WwI6icrd} i)ku^F^ K9]r[L*k9.ְ%/~#瞥>ݍʑj#Q&rW6.MMJ9^I[sRNa{KT T7pYI1P{kiE9SPDl]ޤnY̱xe_um+Jc=Eݦ!'-cWtemDŽY|~lOD]I#:vc&<{5zu13oCS4"pΒ^FMg\Ykw|gLLJx܈ 3x^Ε^{f#|fj=smџ{ԥ7&pg@ħ4ws]:yi:s沢?{ZܻP$D!2thXw1YM4wg\snOӳSo?R6)4kU]2lH>pKi荛nb>SXPrz5[E\s$[ʮJg+s}>6D'Hzlg19.j5FM{o&abWa>0%PT\K墷k7Z=\Ձ<tV<Sب{.](%ʬ=tkYJ{y8֊fdVڂAUiHuR#}hkiAwq{Iljv_Uwѥs{(/XavX &=md%ݍuH'v)kvc}ѫns/t߮y.N~HWeW[S'>}`sقɴRJCZR=\YK+u}u?!n瘮qֵa>뒄X}e힬Zj3ݪ2|Bu?C { P;2mnסB=V~N /d݋'<ύ {ok6 'n6N+Ny~)[uO 9G/"x`8ws' =G=md>#߶'~Gu1dw*829e̿CƒR.-I@whUyoe!9&%us{=uُbmw_5ݙͩf{=u5w%b0Ȍ}T9_eݩܟf>~ H6kw-+_}1$=: M= |f|TwUI~,;]էil|Y/з.QWUu=tUݩ"KC_/?Cwt7\p\x!oDԽ  m}NѺptOsSC#OBwh՘1Ncݡ#:ݡ#:B_*``Fq=9+â;m7;TU7>#+8;8C =1e40R> ?8RKQGcYSS0Ҙ<"(z~W{O#;I>nNcJИQ]U%Q/;ԋ5U ttؘMux w R/U5wه ΪBG;tCG;tDgm}v؇(@u#4(O~Qu?aX}Hؓo>tcf}& o;PJG^UK1ݟ5xZ=mLVw53%{UMuto>tW"~F@m):yo;{}Gw8po ^{s<3;\k?C?^yWR.୶;kmGw8ڎp:/.uh\d57uVZrTt.tͷ>c2g3?jHwU5:8 Qwy`ݺ_+ژAw_M.6wUa\>\NݯHJW'~#Dh6&JϦvftn tPw|=`q":KĠ#v9v9~1~1?)E};8TsQN!E}A8v<NӝX7p?‰gU#E}P%Gu?99W1P#u?71ײP!t?5)W@$C};tLՁ."v t1t?27DIENDB`Bio-Graphics-2.37/t/data/t3/version8.gif000444001750001750 1276412165075746 20045 0ustar00lsteinlstein000000000000GIF87a Qͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ xXn`v l 3ҨEw^:4kѧUI{[әsͻ7۽[#\ײf6Bvt5,gp_q BlU[ áv@ H"HL&:PH*ZH &,zcY2*bFTj8x̣PH =I&m/^(S}RP)"dK9W~lՃP)TKD܆ȴg) O 3L@AV2 *+es0䴩cz/&3@uSS%+T} *D}Ɣ .pΒ=)}$N5OF9L~bNڨCP[s 8T=PIQu% &=i4J9zN' ^IӊR)ꢤd^5T}&AXEP#_Xq1'[J׵*H:&XwJSu'I0Kgf9!0],d%C؂,ezKʬ hFq jkݴ6-چV} nwX&I p_{ kXCn\V.ɍrEZؕz{^ww7Mzwbw͉}kR"~KQs[Ǘ.x`pIs3a*aAqIzuc0gLR&b-i+MUZ[}CZ<{<+v ukOm٨VeL?uOwf!WӚJJ5)lfն8^g [RIթU,JDֳJN7˵LՎu,OTTݸ,6 9Eɺ՛'jA;/]1g:<[l("@qC>v;mƌMn$*T.vMzη-*`D^LM1CD1t;i'%.E>S鴑h-9N+><6@ʉrpC(g^NJ#|Pi@ŰyHOҗ;]jL*[u,o5 zuj]g,!H۵X_kSxI_mZyZs+h)%sڿo9]t}nRN2ae?[oyT>cz쯅elUL'9|3@̈́w,LvjWS[1M?VLwe~wvTD}jYU&ŀ.SS*e6yM%YD|A‚Je+(x18y:dERwwOH%QDOLRXN^?#}[@G^m$^^jGw٦(&P-fp׆k[gv^qfSmG$e8XgtQņ;X[A1`P(fmwHN(eTJw+r걊g$([høXvHxT8]chH`ϨcYUXN8y8{VcG5ܸ}VHmAژ8!HK^sx'lQX$wxfDKIshf sh*+ɒ$H5yѐ"AijGxh7jh؏0gLeQ]TOCsc)("oɈ>4c],cf K ߒv< ٣5( >Fn>o=5o!.F~ H`.vN5T[$~O.Zb0U^`[:>Xn^jlI;Bio-Graphics-2.37/t/data/t3/version10.png000444001750001750 1132512165075746 20125 0ustar00lsteinlstein000000000000PNG  IHDR @PLTEQͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ zrYgk:?O7@P 3Rz3P#kZt@w]]Ut:| Ҋ+ '<*8J~Qz.:k%G|Ol*]BOe̫(f`#t_F2ݯ:=kH7m"}cQeб$+YrLyc)A;{Bt?.tGdG; άknI߼{`[T%mQygڝ=2t;{Bt?.tG/mE?U]uv] "D$1<1.A]h!<'@ȧfӘAwсHt4tCG;tCG=OiRm“|8O)~NbN/n,[gZ;)=*b+\.K[ys%rZd9&qQ}q)G}Z_ʮԋכ'y%rZr2c< V K%a"GĭkyX#JD)jIXGZʲlksxq?1=vx-Ww֪rȢR*_OBr.WKmljrIKYsYuDŽŏl{r?j"?޺g]diФލnq<ꎸwpH5N5}KT "إ룮?OICBkٛ-9>2ݯ6JcZ7z5ُnk{۳X%M9݆[eT߯D֖Xw17ߞc'::6tuE@g̽ˊYզk3n*z@G7ސD!2;thXw1YM4w+\snOSo?R6)4kU}2”l|DzKi_ncTI'w9mt9 ߿K$[ʮHg)s}Z~>63FI{lc19>j1E͋{oj[ȰL/miHi(c*nrѻk-7ǂي'"?sJq{<6KW2f:Юd:}RE:lVwc2mAwbQ!RHڸ٭{Il&jv_UwѥKڽXQ,s̟͘L]5e<.es;]~sq/zQ{N ?7ބ58w]~\ڄԙľLk09lAd^e߇8MQhw]{f+^3Z  ;bg7DMmF.rdJgg&{T-NB힤Ib~~rW>r(=]Uqn[[oy0U]uX}elZj ݪ2|߯"U'7~2|&B"~] >KVZͶZ:1辐st/|^(r= 75]jzivd7<-,'i BDrl~A!:bwzoq~E2>߽8ޑgsh5>NS&}> SBHxQeMyCnM uw*Oӝ죕/c]Ä[/#Dy0]kNw)7ӐXV{21> kwcl#\5%#3kRQнur|="۬]B|A 0jE*_ʊ6 PUf)tU&{1dfBߺF]U\UaNtA/]}Q~oD 8.&^5[FD\Q2Hu'SCUݧ9ݩݡNR!3ݡq4fi:ݡ#:ݡ#P ;tm N]+<ᝄ;_E|w=}lHbwLm=,τ5RK7|srޅb0މEPt7yTv"6tw+8]nuN1oE5f=`,w1Un1;T?;惠;Tl^٬2\fx3&\OvvUV<2ݡ^2͈N;T ,7h;C]~>h}pV:ݡ#:ݡ#]v]l!uݿ/#>O_I'$htz>0<$kI 7TKO)ԡyû}-fPJG[^7R7^p2.Jh]k3Y|tt7jAFCwq RIRY5Ҹth[y:=RGw8[^絾;\[}Gw޺Oo WкOWi;vt xZN罶;΋Km>~X-d'.xu9"3+ G|!%ղK?~Z)nǠŲӺ{9*bYtuShtYtuSrTwtP+8=]ֵuV@g[wvaz$]2ݡAbݍ|{[41H{'g7cy.Cu FuMP)؊a^ΛɆ-@Or_Sn@W8әnBG5DMEKԾ783ʄб$Gf,K8-1!w~XR l}gZ}t('FI.ɡpzx/\^liѺӺC>rG% r&wܑ{>1!>OLꤨi1I;xWtLGC u&~5rA ƒ^II)MNoR.&hMdYmd#%vҮү4 K~U&6#{Y&=$qU0}z]W"|էVv ~RՓ^<2!EnQ5i] ie/n*D>l$8yrDҢ2FD}n[zkV-f[S{{X~r*u6F9wm5I,%.}px/H2%mMfjsVGU[9 ?6S=פR_"sOBK:6łV%"iMyMWRZU=פR;|M%u/6ik* R8ieO]%RT>;ɜF9NyQƏ)<&>? 4/{A.m\!ʦO>dQWEdw?]<%Ä́'[P(1?Sdž/WexlzB7ȚOf>~7mݣŊpӗ57&hZΌ{V'[Q6m#ߤ1ǘU-ߕ0Fφ~|*gL?ٚ&@'Žwe6-44NTb*Fmrڼ O(2u]\mO~7lT _p&?ŵZ{yr?-Y&wa}w2LhMfY ȟC4B+uBOE-}Hwt%4݃jőwu)ݑ;HI UCEoאMyZMnLʼneq8pM|75[]-U,X\X)^ysCޭ-Vvq>E]vg(,\ݚSu3c{š,3\r zypLlm6[+v٨ Wl7L]-MFgiAzι?DѐztP+O~6uy xuvw.ܽn.ܽ96uycJ>Uqe88:\-E?F|m*FpZ#5ZҚ~9cԠI[חߤFrUZ0}﷈MV\h/s~yXVwan1;>jꢎYuX[[GjE ʘT6]Sǒ5ǜ_S g9w=ޔ{._"w7Z|;̿Z:ܭTڊUC2܃o"w~s0-SҺoju/r{ExҢN{ڍɴkr7,wuN^E2kEwom_kUU뾘cE~sYi/\V6-چِD}`稰'J#(Rsڢܣu3Qh"ӽh oi|gݖI_6Tq9Ė3-={ZuĹQQlq}$/dS6GMC}A>nt{}-[Pu{mc]i_T~R#垹V}\(:R­vOfVLu}G=_K~,1}Ƚ\Mi .}Pl~/y>札詏$BD2\dү:daO`Qe\ea)a;Afjga˿s&f7%䇲ѳ["Vr7R! . +?.\KmT`.>32{㘦Vb%@4:*_F?|nS;{mς5Z}k}qVD1LDruw ctfn-dRJ=r: .fYTkm3Oֶ搒bejB(?rh 50x8 %W&&],Rzig wh icN Jj%)!:3ChәA ;<Ã$hq|79~=#whUX-"5]\pSrn!ѯ28O/1v+B~7>xX.1jۢxKr1ܒhEHĉpcV̈5u0;<0y RxFCr^;ǢZ'_k c7Jb3 \\;ҺC׺?@8rOĝ wha)vfO5CsZ%CU(#i2}Mn3_GQ<|hm 'vw8^i$tZ"/].K$u+u[yq7Ftol38d=rZ@v`xѧ"?CUO<l9r0y=`z`/Tv?lj/_&L^l9Nde8Ȅ s\9Όvj3gܔR]UTft7cлkGmEXZw5ch9_*",r[Rea;ܚuk\g&;"%+F8}6 :bY8aJܹ =~UQ.rz]NOH~T7< vhE^Ks)wuE whZ4od;4H*Lg&%.V$zaj#C|)j\ěw;tj9|vx_}vL? G^RpOg{ ]H#B;%>{ =(Ӷ7j-w~"w4֠4;xL'4;xLk @ZYN;a]K0i/;jf97 C<Aʝ!?vځYN$YN$YN$Ó`<Oi ;< ?QE>eIENDB`Bio-Graphics-2.37/t/data/t3/version14.gif000444001750001750 1310312165075746 20106 0ustar00lsteinlstein000000000000GIF87a(Qͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ xXn`v l 3ҨEw^:4kѧUI{[әsͻ7۽[#\ײf6Bvt5,gp_q BlU[ áv@ H"HL&:PH*ZH &,zcY2*bFTj8x̣PH =I&m/^(S}RP)"dK9W~lՃP)TKD܆ȴg) O 3L@AV2 *+es0䴩cz/&3@uSS%+T} *D}Ɣ .pΒ=)}$N5OF9L~bNڨCP[s 8T=PIQu% &=i4J9zN' ^IӊR)ꢤd^5T}&AXEP#_Xq1'[J׵*H:&XwJSu'I0Kgf9!0],d%C؂,ezKʬ hFq jkݴ6-Aa`@p[۝nBk[%mn;as>ѭtznqd5t&|\VMKB׸⥭ݛ[>`/X'La GU/Z2|j8&w?_x$Iⳬm[46q^x;L"HNU\nV8=Le>M5|hXʄT$GwfQ6ZSo2BR}r-ZgsDq}y󢛉#OН $;=%"zvL~[i2^s_`N<-ܐJjiU]Tz]q)'t<3G*sI "9F]'kŪNؤfukETQRS~|T7#y'mfzrJF6v2:TZ|c|O/?ͿV{#~J%gFh+?N',;@Z'kf1UQvUQfY|%yOg TVimxnkjA{H2MWVLV|Xw$8V.(gHxւKRa&}7|YׁONwDG#G%z6glLXNxkфx'(׶!'mvoxc}fhmzEreƄ#qaF>gXjZs(LA5Rz դxs((|7%Ehxs7:G$PxXXYtQ xLql(؋hfEHNhIgP9X+`xG|jx8tox(rj6hCԘH"8∇!YFDx'XN8-G{"8|ٍVyiՍ!imUYX芻Xr9Ȓ,ْuA/W N )SksَBlfDAIwxM\Y Y^!H2g6yQaHSYY{HxԏLYMJ|Y4RAyhh}֕("PIiZk2 xѓzY)aĘra"Y⸙CwyGil)ɑ83A^؜ ؎дIřٞtc9xYіhQ9K)4y|I){YAy#y [qP 9﹜{Dx ڢɠhۨa1`hVɜbڏŞHFAQ$ $I!Qhw٠٘EJ颦*;hhѤ** \ Z{멣aɓ?jljʎl#wׇ~WpZyS$u)ȧz : F%BVT.#SS2EƂ ѫ 'Z/2N,J;CC)3B⬺.CZCzSW,LZڊѺ :ݺm'Q+D*Z/:VwkzxƅywmzJC%z k}'XX6|}7u̚J$1۱! ʬ [Eh&R=D)-Z2P;ZB>"j_vHz?:7`˭br>%X؅&Ym{'x{~WGZ ڱ꩜u;p瓾ǸC٨ƹȉ+[ƺ7 (F]j@ڨ۸ +ƻuƼy [m:k;K6CB2 S˱{2kvKk7芿Zۮ¶dM<[[[ \=",'̿iL#&<;B+g-<:+?, /˾9LvHJNPRktiUuYz+ le]GUqQuYv|rx,S~Lez-'nƇLdz "H7ud񅜇""Otɠʢ<ʤ\ʦ|dR#oTAVeI_66ː43qg[{GhHGV*yja% Ŭ~EkU|H<(|d|k | Hݬ|l~lNV,9+в] Mk "=$]&}(*,. 0=4]6}8:wn"qyl%TW}FḃdtdRFy\mkq=\f||ǮgWa >n ^d^nd > rI 't rɜjhH .z!~ \Q,.=V^l2˹6<:1.EF[@][JЌnP*m'^L庌mA.[Va~/Nn5瘾^E;Bio-Graphics-2.37/t/data/t3/version3.png000444001750001750 1062312165075746 20047 0ustar00lsteinlstein000000000000PNG  IHDR)PLTE{hAicG@pI1PQ f2X8sSOm*%I?SPɶkXK4rxphB#WXav֭%pbq p'fܭ^QLfh#w5Dz fOA$tL>~fདྷIEw ^o #rm=C>c.ܹ d3@Cw 1;ܘ%̠^֤Fpc;<NUA$/G$Ҥ^&w /)~f&1aё08kXV}WQyi7ۢixo4rEnמ%5tGxϪ5YOmLMH,iOsztgS#Wҥ|'4*{N6H#qUera=$'B|.5:)Ry[ F޽֔d!091IicKGڎR҅#~B-]-mk 4.Dnd]iUm,ؘ}SK{B%\OlE=mHbd'iLy͌Q\Z=4:Җ.|_ }#p./ul̞*|ҷ9=L?c-c$f-G:k: wQrW.]lX*˽;^-smcWI>ÖUquMB-}69˸!Nlеl [\ >{A_:t!V'_F1Qțpo׊l)zk}Y^b;;a@lC-o:?Xĺ]LSm{=9_:c#NJkډ#;׾wWPYmPI| \-Rq]#-ջbTh;׼oHY.Q& ]:׼kX#†CVF6s],ܺs沖u.ͱT׼z>e^٤Z+{[E Mˡ!V_̥Ieѩ ,(3lidhL֪҂mz/ÔRVx̻iqyԅlj/]}i̜E;{8=*T&4{eVy^jL}ʌĦe[/j8f+qšM]]UredFc,7(-[Թ R.f v//֣~CZW.%3*E}LI] !{cf p8X9&%+H)MJU-~gBje׺$F8ς5ni~ߚKBfd&?0)c:"{e2#sZr2V:4nA ~}gUOU]觹~FӔ긳 jL;G(h-.rWUi/K4R{a2ɣ^ӹXpֽ!%S5&勘_}!GޝVĪߡNF~rO։kp]>_m,7A]=H h3[A wxrٟcW|Pe)=#w4#xnLc /tWD=b!<!y=>Cpec[ syBAAvI 9\MfYY.O*TT %\09I^]dHl]?V3 [pà^$&8}rT% I`\%i.u)B{wc:{(Lyrd&4J(z|?\r uKM),|p"ZϛfٺTѯO>> 6 .I$rw>ˇK ݄NV{'/>#ƈ鍭Mfr.JQS\C  BX T"w׺ӟ6o?l L>ؼ|y30`f`h=ϣ?փ-/Bxи>\/Y}RȽPf"g} JBퟣޯr@"w~}Pͮ7-p<n'mv ͮ+R>fn r uw9mw r r>>w R\ ]NP{?pf0[?p< r]N$ w9x}@$r'ڡ7`A޼-IENDB`Bio-Graphics-2.37/t/data/t3/version12.png000444001750001750 1134112165075746 20125 0ustar00lsteinlstein000000000000PNG  IHDR#Z]t@PLTEQͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ gzte5'W|܉})/Kp^P|SPўFU.[zs㔏erR^9&qQ+}q-G}Z_ˮcI7Sܰ%8\ú;]ֵ|Zӭ|7hFҵAY+~?sv2zJ;@;tCG;t݉? E}%be-OL3Ozm .ݿ}~fk>9wꇃ[rגɓ?e7k NN9ݍ˛A"mmY-‚SK%2J@ufaݳW(NMƁFP߿h}e&-4TNŀUP W(QbJ!꺨KgNÉKg\GoQgi]DqLwwOos5S8]Yџ#\e5vFowU s݃<{I0pWʧjF.ĺ[Zw!< 40ZDnT{.)P7K}wtYԝ*݊:;"Q83t^X8Sd !^;v/}+eSd;ݍ% q k̥yd,}+K($^F ~JL]ǝ{tEsW':39yJb&+^•͝Uu{6[j+7Ν^]{GDsAK zJުDhHatÛP#go•}UjGNlgt涪?yT•]UI7$Q̆6 ڸ.qZ_ŕҜ~edtyG0K9ETt0%[#&^VG5e5 e]oFr(5|xk>%iNO}*9c~>.f'-yMmb+9 Y5"SeJkin`Ew2.mx!3<W'd|%ܖhfݮJg-gzfu7&VtW9Yt]K+/mD˔84jr2\ǎE5i7&֯nͲj܊df׊n]~sulzպ?{N?wބ5ϥ 8b˕-W 3!}`sTݓyK#(>iu6-g̣gtoWk8+pu]2W +ͺgRUY=;[uļߣE,dINd[ݬU4T=FNrd)M_}{zhn/IB뾱uOR-H~jG_{kʋAOԡJ=ۅ[̬uRtϯG)Y 1Rkt_;٠G!sYo*Tg;aJ /-nJ?@T;G+_*$Ǥ^{s0]kNw)7[PXVG21> kwclhg2jJ{fdפ({[֝m=E touw G:3`YTkݿHݕm r: SfMVLgf=z9̄u!bejCNtN/]}|CG;:xlMwkJĈkA%*}fJDo>ś;TE}ڹӝA$3cGNgݡ#:ݡ#:B*`9P"adhB$*囸[DSCw(w;Cw#1e0P>~H"now w`~;EPt7yTv"5t2Io7ƺәRQq~ûB7Й@w t@wt@wt@wtHt_|@ 3(b@in>Cu FE/M5%3hś1z";w*$/t]iF$w:ݡ:aY|c<ԅ)_ZK8GU#:ݡ#:q`7i$@ue1䳘Й]Ç톕d;(jCI\9e:totx hF M~{$u#u:

ifE;(At¨ڱ[R8zx`[TQɼj6Ne/Kڳ~&7I]xUa8l%Qy2be`#c1tGs@wtVׅ^^craԅuZ-)7EUZ"-R2uQSW~1tGs@wtVׅ^^ݺuyUE]\\DJtE;(A@wPĐG{q/,]H1cx;4f;(*("9f8@wP"q@pֿ)&ɇr7Y,l;u֯a YϸÚb !~؈Ro4}\٥#k܋'xaܷ M&f7OJ䊵<,!6ϖiY^no,]l'\="n]]T"0%Z֑7ۚA6]OL~tO/^if1Q9~d} y)Ke^/'^ k9]_-5n-mX$/eYadƥ-n6,~dܳا9-$*DjzBRu)CRN7Rqg-WwĽ|FI}mw8XlTݘ] >3qp<غIݲc/{oQ{4sd!awm9iu tw\,k7%M~1_ų[Q{a5s^"# aT Nj![ôO~]{T=]ݘpL^)?O8YeunpLwkd|ˇA&2 '!;cnpHwE;R.@Vw&7&D[we%]/aĵ]Q&k8_]o݈-nEp6Iu': 6R}4];ś-ζ醇SK>ݹdͤ渌ݺ_wջ jw؎6iFemw.AwP"tE;(Al݂2t^)unY|CHTwC37]I?S LQۨ>c~ ;MBUt7΁Hx)fE;(A*INlkxc{a8LE?T['kgN5iV>{zxA|\\VtWgm*`3m:@rŻq_ٕ .9'C+<ɱ&kU}2”bJDR M>OqI~B.7"M/5x,{KL8'ݧHrc Tw2/EB~MȤ3MCqTR.`AxSG~XѽXCgExkJ-׈q~I-u,̺s9t^t#\pSlLjz |Mb&jv_UwKD{(ׯZqvXf=o%ݍV7'v)֟۩QW>j{}/龰YR횗 |ZrKoMmC ZIf+gj=(>i' [imcV Z  ;j7DMmF 2 QA[=ʖ&vRC,1u?^SnfZaUWu%Ȟ8}{!ѽr;ڕn˺ݳK{[u?BԽPMr}!^l-ysGfM:t_Y̭ t_9WO>/sی }C]ӥvi8ǞnGvq$EVlava=YC/"E\Dy^D:A@wPCDG󸿣q_8IPbъ}t߽8ݑ_1n'C@ΝӣCإ)SOf6aJ 2> kokMє$n@T;G#?QHx;nG I:ݥoNC6Sړ[.͝FĮb\e>2#W>TOSԽИȽDw.[WHcIWUt=tUMX*=̄N”Yv=S>Ei&UDjKSYÄ ݡ;rbZĊ[ltj -"u$lY:]ttv=8 uƌAwx94fՠ;(A@wPTptE;(ݡEq-h+â;ɶ-;Ix[Cp0 WcCwhNܔ? h4iH~X(_?=4nvF;O8ts<]`;IDyYwmElm<.8=~NcZ%j̈G|;Xb j1@w"tE;(A@wP"LXg0mmAaAcOvOt_ɋg ?p x";Ƿ*$6v7ݹ''Jv6fz&ZvU:&jvDwtE?̈́8pCo;(K@@wP"\4KSIENDB`Bio-Graphics-2.37/t/data/t3/version10.gif000444001750001750 1307512165075746 20112 0ustar00lsteinlstein000000000000GIF87a Qͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ xXn`v l 3ҨEw^:4kѧUI{[әsͻ7۽[#\ײf6Bvt5,gp_q BlU[ áv@ H"HL&:PH*ZH &,zcY2*bFTj8x̣PH =I&m/^(S}RP)"dK9W~lՃP)TKD܆ȴg) O 3L@AV2 *+es0䴩cz/&3@uSS%+T} *D}Ɣ .pΒ=)}$N5OF9L~bNڨCP[s 8T=PIQu% &=i4J9zN' ^IӊR)ꢤd^5T}&AXEP#_Xq1'[J׵*H:&XwJSu'I0Kgf9!0],d%C؂,ezKʬ hFq jkݴ6-A_`@p[۝nBk[%mn;_! s>ѭtznqd5t&|\VMKB׸⥭ݛ[>`/X'La GU/Z2|j8&w?_x$Iⳬm[46q^x;L"H.eB{*XPTժjէ<+9Ӽ6ʶb'PԞ"s3eP0џS_7ɛt[\sc j'YU9T kk]? T[jG[༴KeGYTΨ\TY7KN{: ZoTlѠ&]I>2՟d]I[YA66lF*ـf6i[VqsL_;K>sK6qvOؾRխ; |NO;J$8ɚQL3JLCθoqdSWȈTjR2(_b$(9d:jd>.!{TrO$qXϺַ{`{nm67^aS'!ujvGqgvW:#y.x~GQ Ń'd*S!6^Wj(I"UTbsW-%<'sw^zcMP[Ek׵OQT~!odgNmj %,}j:-ڻa7zꝤ}F]?r>~N:7U٪eweykUYfx~Mv'}Qqܴ}j5eR|xOTkEgY5ex%ȁu6&hSG#9(&YxzYWN4w`F=X zIxJ?Ȅ'xSąb^'ZD_gh*Zaw8be7kHhFxlNFGxΡ{)Xnf%3wX\"aa8;2OfHcgmxx8!XxhV؊v䔋fфAt{~+uYG|ɨhXt"茨hxo19 A[EAHXtvWXNhkir(`XOg> ɍwPȍJ!ŏ)XҐPqhLiq'Yԇh)Ih("F H'ir(;Y%xȑ(WY S)j6\1J>5j 9a3QhJs?Nd%TiM ؕ'i8pɎ ?R|8#mumq8ar~8aˡ. r#9gni爒f9jɚ0ї9@a))W M9>XIə񔮘OɕƩyܷ!ωHѩq9A!J$؝8. u~ɌIh]I"ҟYG٠9{Yy)*\0)iZ} ZZe)(Dx'j/1-13zUZL ȣM {iZACXhiAgOZ# +J;)]Z晚ɢbiM*:Yu83!Wpyih&'QAd'wX ZZI%BVT.#sZ2EƢ ZGV%e3-DR142#,ʫ­J;ƚywwj-jLdڮ*zʫ5y'zɀ<ܺM* ˮ*:j{){ŧ{7}HjK4˯,[(ȚVWhXg̢J?;%%s:++zGV4X*HZ2 HBVފ%ĴMY1xڀd t8{7Wz4o+e̷~ ;hhJ ' Ou pQ9X 縿*;p۹{p뺯[pKdZkp˥͈˹(J[Kp+iK;p;[xLj{蛾7CТ]U` ݊c]˵J,ۿ&˰ۮZj +LdZJ+(ۭ{& ,zm!L\!|//H[vڊ6 |;B2\]'Bkkj.-ĕ P|R4blJe,w)dekoA"'swfuwt!~rz~%tJWȃoSב)GƋ mwNwȅslQOljOVɤ\ʦ|ʨʪʏT#tLiAVꀬX629\d Q'ȽE7~]BwN1D̍~jVl\I$hUXǬ,l.8g@5Νg,IZFǒ紷vܷ=9{{ m-}m}ѷ "=$]&}(*,.02=4]6}#8<>@B=D:MMIR}`ƾX{z;ZMIU1N˷"t!~d HD.Lfʖahmnj!~m(>.oY8~XON9j2f>Fpn}v~x^]5Ce~N5[S~Ҋ.d_N搞Zi>n鎎难NI;Bio-Graphics-2.37/t/data/t3/version1.gif000444001750001750 1331312165075746 20025 0ustar00lsteinlstein000000000000GIF87a{hAicG@pxXn`v l 3ҨEw^:4kѧUI{[әsͻ7۽[#l .77Tq54@9 u1M5ה9M-!5Km6o]5=KY5B7ܮ{߀^mnራwK+oG8ڃ޷咇vìkn}43IþՍڭ˸ﺻ{#_yS=/ߟփo^|N>Xx.{'?uE- zZk̸W J{$V@!03׷b̐?*.nݐ5ƕõaKaEl&:PH*ZX̢.z` H2hLC6Hp#Y#7x ->,IHB$FA!H|<$i ML QLz (GIRL*WVrt+X |'[Oz%/k +A9J,1L꘿py+c:K]T܌TB9i.D/ɩbTYaNl'3!Oe"ӛ+ O3IR<%j6 Eh?G9Ԣg,L>Pģgn96#*Ғ JSm4XZ(Oq%̐Ӥ, j:Uz]TKf;9ԉDV'OLU5VyTt ,+9mJMZi\uZ֗|uSDI Ԙ6TSb}SgWjAV^VLd Vº3~Md+.X-jJ]ݪ5,JB͏|:{ZTԒV MX+}27%hUt?UcjĹ/"W,vhMGʀH"ċhR|߻:o~ϗq/y;c/|u_/yvCLG8NWW—Q1l`V(;\cuk"7a1tY|UFL*[XβL޶&P%k0\,V%3ax#M@BTٕ3<):6e?>-Sim%/UK鶪͔h5i5ҥ֭V3Yε:lU۪ۦTas+HC3^L}MSN㴿 qW12]O7D$`xJη y_֯y*<越 Τv3'N[ϸ7{ $79W=f>/WЉXN\A)T19QqfEGЩt\PcrHQϺP\cO׺؇]65pN} tbo̽;{7uʹXO}|ßo}K֪sw:IfwOW_էsߝK{ufjGS4xk$Wff7XkelxV~WOjw]Ed }'~*XtTT^3hg%3(XcX7X}h}n=~WAU&wx h[{Gk$[{ !Fmzmȷ%PZXyXօ5|RgiQzbdwc&[E}HaanEvxnhdWT|fRR5*NNv]|x+V (KFEB8Uw((w(f8'h'DxȘшhQ}\`&Xeɸا(HLR8Nxoh*(&Xcxш`|Fhe>ccxw12wmk)WUH&I[hfWK!)V.Ői4`$ْy!!eQ(p|;iCєrv |.⋴5"tjʡdB>DN@>ip䂖VLORNPNrR}"Χ[Z\[0!%vj荮Xeh啾en癞e]uMP)؊a^ΛɆ-@Or_Sn@W8әnBG5DMEKԾ783ʄб$Gf,K8-1!w~XR l}gZ}t('FI.ɡpzx/\^liѺӺC>rG% r&wܑ{>1!>OLꤨi1I;xWtLGC u&~5rA ƒ^II)MNoR.&hMdYmd#%vҮү4 K~U&6#{Y&=$qU0}z]W"|էVv ~RՓ^<2!EnQ5i] ie/n*D>l$8yrDҢ2FD}n[zkV-f[S{{X~r*u6F9wm5I,%.}px/H2%mMfjsVGU[9 ?6S=פR_"sOBK:6łV%"iMyMWRZU=פR;|M%u/6ik* R8ieO]%RT>;ɜF9NyQƏ)<&>? 4/{A.m\!ʦO>dQWEdw?]<%Ä́'[P(1?Sdž/WexlzB7ȚOf>~7mݣŊpӗ57&hZΌ{V'[Q6m#ߤ1ǘU-ߕ0Fφ~|*gL?ٚ&@'Žwe6-44NTb*Fmrڼ O(2u]\mO~7lT _p&?ŵZ{yr?-Y&wa}w2LhMfY ȟC4B+uBOE-}Hwt%4݃jőwu)ݑ;HI UCEoאMyZMnLʼneq8pM|75[]-U,X\X)^ysCޭ-Vvq>E]vg(,\ݚSu3c{š,3\r zypLlm6[+v٨ Wl7L]-MFgiAzι?DѐztP+O~6uy xuvw.ܽn.ܽ96uycJ>Uqe88:\-E?F|m*FpZ#5ZҚ~9cԠI[חߤFrUZ0}﷈MV\h/s~yXVwan1;>jꢎYuX[[GjE ʘT6]Sǒ5ǜ_S g9w=ޔ{._"w7Z|;̿Z:ܭTڊUC2܃o"w~s0-SҺoju/r{ExҢN{ڍɴkr7,wuN^E2kEwom_kUU뾘cE~sYi/\V6-چِD}`稰'J#(Rsڢܣu3Qh"ӽh oi|gݖI_6Tq9Ė3-={ZuĹQQlq}$/dS6GMC}A>nt{}-[Pu{mc]i_T~R#垹V}\(:R­vOfVLu}G=_K~,1}Ƚ\Mi .}Pl~/y>札詏$BD2\dү:daO`Qe\ea)a;Afjga˿s&f7%䇲ѳ["Vr7R! . +?.\KmT`.>32{㘦Vb%@4:*_F?|nS;{mς5Z}k}qVD1LDruw ctfn-dRJ=r: .fYTkm3Oֶ搒bejB(?rh 50x8 %W&&],Rzig wh icN Jj%)!:3ChәA ;<Ã$hq|79~=#whUX-"5]\pSrn!ѯ28O/1v+B~7>xX.1jۢxKr1ܒhEHĉpcV̈5u0;<0y RxFCr^;ǢZ'_k c7Jb3 \\;ҺC׺?@8rOĝ wha)vfO5CsZ%CU(#i2}Mn3_GQ<|hm 'vw8^i$tZ"/].K$u+u[yq7Ftol38d=rZ@v`xѧ"?CUO<l9r0y=`z`/Tv?lj/_&L^l9Nde8Ȅ s\9Όvj3gܔR]UTft7cлkGmEXZw5ch9_*",r[Rea;ܚuk\g&;"%+F8}6 :bY8aJܹ =~UQ.rz]NOH~T7< vhE^Ks)wuE whZ4od;4H*Lg&%.V$zaj#C|)j\ěw;tj9|vx_}7vL? w#pq)}_}/ H#w2K|9}N~sP>A; jZJA0A0ͮ9RLk f9iv,=01ap;j >;j )wDro,'&r','xr','xr'x>A wxLIvhE>)IENDB`Bio-Graphics-2.37/t/data/t3/version13.png000444001750001750 1136312165075746 20132 0ustar00lsteinlstein000000000000PNG  IHDR(0@PLTEQͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ ZaGgFJOgZdw[qh&wDZqy'4#:ݡ#6>~oύIgu`ipi}I1Iټd˨ߦ}Iudi9Mo=>L J$4w1.`'~ 5[' DBwt/LwK1̲-%K2ͻubY~  Cw ߭RuSeqIݡ#:ݡ#:ݡ#:ݡ#G:ދ{d|B?!<'ۑOͦ3/ݡ#BGD;" #:ݡ#:8J:&<ɇcr7Y$ϒ;v/u,gnM1ՏiT[rY G>Zv ~SGUWO䆥\exl@ֲ[=KtD-${RX3FDN: STLFZ˲lks8^ֺ߼8I6ikU=~d}i-Ke'ޡKcCUW%663L5,[9V]It1!R_"s{BKϦ.$ YY74x%w{V3K#gVRK*L?Z~f%5'@G_IƟSv!ޥŜKw%1EOh}hu twܥk7E[_oТ["֠u,ܮB:ݡ#:ğp1|2U tF-d50M׮ҍݭ g9kɟ2|rȆ.VwYs+] VQXYWwYSK%2Jށllom򲶊F+w8tsƋk*Pue.D5wM_5&ݭђwJKV5\W5wb*vRȡj%މKkR˲?F|Ca`?6wt|whs5S}5m.н|mк~zӝ{t;Mg}w&Awt@wt@wt@wtL]2]t)Yy.2YtCHXwK3']0QS LhѺ՞o~ ;R,Pއ݊:;"p :ݡ#:ݡ#GD7'6-X8Se w^ԗUp򺩲Fށ{zx@|iu6-g̣gtoWk8+pu]2W +ͺgRUY=;[uļߣE,dINd[ݬU4T=FNrd)M_}{zhn/IB뾱uOR+H~jG_{kʫAOԡJ=ۅ[̬uRtϯGY 1Rkt_nr65>Ӆ󿩐SOن)!$$?5VNVn@T;G+_*$#[LuA!j^ݛ-i(f[G2 1ngĝh9LW)=3{({[֝ƽ4v~7ƆvX⾘D)DwXaTHgZEܓ++A(Lʡ4+LegZ}N_Ħb]LY3BwFCU12T!V'CWwb~Awxg[;tCNt9ݡFI\@/UmݡFNRYCSݧ9i%(Ս^Xtu3ݡ#:ݡ#:B*`9P#adB$-á9Jn SCwwNwߔ hnHP>~iH"wpn7y 7`~;ԉ#Vt?CyT"5t:nI8ƺәZQq6~ûB7Й@w t@wt@wt@wt@wD|4n f0槹 1ָ3L41̠oD뉴ߡ؊g;KwUtde3 ~G|\kfje2 p BG;tCG;tuWGۿ{Xvݿ/#>O_I'$x 0=|nXKֺoh>tĕSfCFwۀfuDnDn{\g]=ߙ.{tUS >t;"H.uE "/@JG*utjGwz}Gwj}GwZ}GwJ}Pk?]^ޮ{J;A;@;\O;\N;\Nŵn/}:[|č']/(u9w9Df.7NN[뿯1}MM(BN1Uܵ cWZpR".{]=}vx7[wŔ1EZw=%יIuHw9b{Ӹr${tC#~1LK;Xw#"m|j#Cc IY͘h Ofg"coy+ ݡ=2g:3?bhHwzajo웇*tN~:u';w30Gu%3a3=TEKqp+u#+]a?G$_0)W*n8p /@N^;?đ< )b8fWtl/?ݟۋqBw;@Rѝ8պsRAнu社{!N9~l/pź l/E#r'^ 3;GU#I}$gu8;wr3"'u63rt t4+wsxG$C{;tĹ'N"vh t1t@wt?y pJIENDB`Bio-Graphics-2.37/t/data/t3/version11.gif000444001750001750 1310412165075746 20104 0ustar00lsteinlstein000000000000GIF87a#Qͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ xXn`v l 3ҨEw^:4kѧUI{[әsͻ7۽[#\ײf6Bvt5,gp_q BlU[ áv@ H"HL&:PH*ZH &,zcY2*bFTj8x̣PH =I&m/^(S}RP)"dK9W~lՃP)TKD܆ȴg) O 3L@AV2 *+es0䴩cz/&3@uSS%+T} *D}Ɣ .pΒ=)}$N5OF9L~bNڨCP[s 8T=PIQu% &=i4J9zN' ^IӊR)ꢤd^5T}&AXEP#_Xq1'[J׵*H:&XwJSu'I0Kgf9!0],d%C؂,ezKʬ hFq jkݴ6-A_`@p[۝nBk[%mn;_! s>ѭtznqd5t&|\VMKB׸⥭ݛ[>`/X'La GU/Z2|j8&w?_x$Iⳬm[46q^x;L"HNU\nV8=R&b%U5՘T>b &Fu;d'8Ax6̳RVcξ?h3%e)sζ'2dgnմ& ԪyFFP99MblaKgOo4J+jѡ^JgvxU]뜪"VAu^r-*2ۋxn6-j[H9}ҹ0 m%􆛍Ʊ=aJ~;wK)ކvZm{NO;5V657N4f(l#$)Ŧ:X.>8WI;ו|9:kܙ@ON\ ZԇeT3Է{`Nvٖ`nT7iHd4 YI>Cy7fT,^jG|Xdg&5sN:կ1_c(en7g}g§֟/8lRtխiyV3>ſYRN'w'gdtru~{oS#\cJGtZ鿞@OVҨYYFg7ĖxT2f|jM|{ƀ耤wnxYܤjeRaHNb!K%i|x¦.(dF׀xh7a}Y巁NdwD⃵G#zMmfPHwGOz{FzxUZDWv1aBbdwjnh%ZPfL8bwq4f'~yX|Y#5@'|pOXqle(k%K凒ȉ"H$cPxXn"Ƥt1҇qm؋hVsH|gq!|~+HȋfvG(XjxHt둊͈(r6ڸpKꁎhZ8hxhItzeD)'# i`%WɐMxJ丐؏ؐ" 8Z&ɑJAIJx)l!ٍxwX ihADH'ؐxO P%@9wG)Ni / 1iH 퇆)!Xyѕf 8(e&tzqRzĨ(aP<"&ib9{hޑh6ɖu鋹XٓxٖJ d)iYYJRٓђ\șO19wo0ٍY/1u Meԛ!Y渘pv`9ٕy!ӉHIə ۙwhI깙ٞٸU)+Ω>5:Kfם Zi?2J١u9AXҟ )+ښ j&1JE*5 7 YLѣ^;ީi{$&ilT syb:2Gj>`jژ@iJkc":$(zftyG:::tzv?⪞GLoxTB)dE?Apb֒D:᪭:jjh5zX}kIʭ2,DگZ/+# ?I}u5V}Gi9UhBK4z/zlNh~HlnGJJ$Ak).˲5۱oeSm%)԰z ZBM[0X,-^&IZ{SK89dֺp{%fط뗁;[Ql[Kj #UYyzpQ [xbٹ 'iۍ'Ykψ~*j'ajajٻˋ pKZxfqʸ{蛾7CТS닯a˯C4lbZ,;,{ l[Bƴ:-j `ׂk/("Y[v-, ([l.,òEE˵̰.1\j@NP,T\V|XZ\^`Pbo#}9!e\mI7k|mJ"Qs!Ntwzi'(uMƃ\("sLQ{|O:ɔyqvt\ʦ|ʨʪʬLD5„ww&_uae#ȳjՅ5}f5Y!l˒f7zelwE}DfY7kWcviH4H)@z@B=D]F < OK;Tžʷ+d;\ IQ,d]֎J hp] 5ֆBaK<*+:~'甭Jz .[ ;Bio-Graphics-2.37/t/data/t3/version3.gif000444001750001750 1277212165075746 20037 0ustar00lsteinlstein000000000000GIF87a Qͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ xXn`v l 3ҨEw^:4kѧUI{[әsͻ7۽[#\ײf6Bvt5,gp_q BlU[ áv@ H"HL&:PH*ZH &,zcY2*bFTj8x̣PH =I&m/^(S}RP)"dK9W~lՃP)TKD܆ȴg) O 3L@AV2 *+es0䴩cz/&3@uSS%+T} *D}Ɣ .pΒ=)}$N5OF9L~bNڨCP[s 8T=PIQu% &=i4J9zN' ^IӊR)ꢤd^5T}&AXEP#_Xq1'[J׵*H:&XwJSu'I0Kgf9!0],d%C؂,ezKʬ hFq jkݴ6-چV} nwX&I p_{iHCn\V.ɍrEZؕz{^ww7Mzwbw͉}kR"~KQs[Ǘ.x`pIs3a*aAqIzuc0gLR&b-i+MUZ[}CZ<{<+v ukOm٨VeL?uOwf!WӚJJ5)lfն8^g [RIթU,JDֳJN7˵LՎu,OTTݸ,6 9Eɺ՛'jA;/]1g:<[l("@qC>v;mƌMn$*T.vMzη-*`D^LM1CD1t;i'%.E>S鴑h-9N+><6@ʉrpC(g^NJ#|Pi@ŰyHOҗ;]jL*[u,o5 zuj]g,!H۵X_kSxI_mZyZs+h)%sڿo9]t}nRN2ae?[oyT>cz쯅elUL'9|3@̈́w,LvjWS[1M?VLwe~wvTD}jYU&ŀ.SS*e6yM%YD|A‚Je+(x18y:dERwwOH%QDOLRXN^?#}[@G^m$^^jGw٦(&P-fp׆k[gv^qfSmG$c8Xgt1ņ;X[A1`~P(fmwHN(eTJw+r걊g$([hø8$KȈ9хa1(Ө ֌:_5Tqw`%8vю(XøGkȏi`ю?xxKrg9N8pHtđ#9'HfF 2+YOɸxM(xȍH :"1ij7h^!jhȏ0gLeOy]DOBsa ("mɈ=;?B`J 5GxRuxɋLizi@ZHh-Y)y7yZYYa"{bX8uomIM%~HT90vkɓr!򤙞;bqɉydSyDȄiH>Q9Y|I]ԙ9,i&މxY8YIrўUi9#҉HY[ʼnFQɞ*j * "Z!IsّiZ$+!VZ\!>A(miI/ 5j7zǩy鹡B`I&'9K{ibw*Hm:%+vwԇmlVxUB)dE?A<%)-^d,: Zmל%Q-Dr142#,⪪J;ʩJ7,LJڬZuY"tC(y窭+DʬZ/ʭ֒ʪϪecg(imgJz4$Z2ڬgPg}vRqqD:J[گZ*h%~VywjS2ݺIŚB"& %ı>Df4`>x@P7xjEXH3b҄LLP;T-qxvZ!0)Jʵ];w)Jڌd{oyhKb mkooqku[o^{}Ko۠)˷+o+^\ eyso{(k oKnq8)&Z^[{p3-t>+ o jኼ1ڲ,Kn1묿ZJ+۫k˭;2Ϋ2J' {۲Jf{۾ {Kf+B)"rShqr4O_ot*1×!*s!p'bH 9_QnB'UY{ R8wsa$q*pVNlr|Q񝁀s yr =7 4c}6cf M-ߒFf=-٣ k~4+^q5o+0H 1J`.P!>UV.[&Q\7Wb~Ra^Uh~Tjnp~H;Bio-Graphics-2.37/t/data/t2000755001750001750 012165075746 15411 5ustar00lsteinlstein000000000000Bio-Graphics-2.37/t/data/t2/version7.png000444001750001750 2466212165075746 20062 0ustar00lsteinlstein000000000000PNG  IHDRXJ PLTE{hAicG@p}R{3Sf׍-R{; X)RŚw˞XZJk PDNhsy1Ǎ w酀9k  `}X.]`!Bt  %;S,D(Ƽ_`[4B XljVV"[]`!Bt  %P|X?:F!㌾R;,` k,We+&-3Q ւ+̋z!s\}ݪ&'[=z"fF|-XvAJN6>։K̋FiZY Xƶbm\wk?k&13Zu`?BE5b 0(,13%;%{N2׈)S!śz"fF{*` އ(ՈմocCx9y@_e*%w̌Z^72vY2E'˗I.Cw*|8]TG f#+*˽ 5U"˺t7y YT`Mςh;g혮+X ˑk&we{ =S-f {t0 :ciMW[`7з*J3 `墺 `墺a沓rU I[ơ1^TrU Xwjso f8# `uWs'!X`.b"X`.b)` k5{XŚ,b`fKXw%X;,` k5{XŚ,b`fKXw%X;,>luZzk ,sA9Kez)`9Rjib[o0n-}sZז4soTf 2XfsmvoF lRKbe4K2~B/VKbޟ˔kWRX`Bu%V:xw@ ŦV[Kb`f}z+Ś,b`f*hw>'2(վXfZ2Nr,Ebf ,3Q%fFXd7/`Ȕ-&ŇFXSUyfy:_պ(ᾓyYPRgň'rXx(-%Q̌|U~yoY^KF㌾R;,` k5{X]_.~3cQab"2Da3cb'@r =z"f84ǗFؗk/رy6>@̌qFG),XnEz!f8d^©B3c;!,%I;,`5^γ]Dr(K{*hT^\VZ7}bD~` ;ZV%oXc8o}lTozXŽám2hyfJ|'z9'vW*P`Uo YeJZV!Xg])/R`eҙ1^41y=7ˌ"f~Oe=Q;UY0ƋmfKXw%X;,` k5{XŚ,b`fKXw%X;,` k5{XŚ,b`fKXv`#57JfcVKb[u6ucO1Jf ca4 6[KClJ1Jf ٻ|gnr8{^Qk,qRTʼnihx-5{oϢ^O,r`mz&(!XS䷆S! Tf:/:}Xw%X{[ٮ`!Y7$Ƕ $%Ҫm0+` r"t`,OÐ[o)XX-ߣ4(WK-E5{?EMXsd5zW =Vcfz,RKbp={av]ӽbdUܐ.Krf]x/ʩՄ߱l$%,.WWXkY*o"Ś_mEyE#M%SwG *;w#YI3XbsT%R&fne+/. MuPPORVrMo}F}Pb>~!}Xm\ֹd9n2`/`JNI$zc iURKu%Cm]RNJlUiJɆS{ȣSдu}4.>[9$,F$l$CKɺsTv/*rX1X[X #=B+ 7FfX,s^m5T)&?x/A '¶(#( W6hP"bu7$_t f7X2luӱߞTQZ%g:U$MU0 =)X %)m0 $b/~Q;,` k5{XŚ,b`fKXw%X;,` k5{XŚ,b`fKXw%X;,` k5{g߫c^7Ƥ5{og\9& V+ֻ}uY Z803r0 Ǿ,rǗftnmxuk | nzXf-zM-5{ %ji'7K`Mv1-I7m__ i>+Jň5{ eʜ ӛv}۾ӿ,޴3wC+:k/VӾӿ \luk.,R[f#]0X;,F;,` k ,s#fbs#fKXw`̔2Ě [Ś+|֙V133J5b9+:O_PJf_d/s+,,Qyf=-4E *gdܲYă*XEtOO|[όS,1357,X/Xx(WXK ܩ`3cх)J6V+"WtzVf/?+:etQk:,{b_,]bf3 t1(%f8K,]bf3t1+Ś,b`fKXw`QZt]2h>OlNS`o *,EbfUgHg)UY[Mݍ9nRJ (Fݍ,9lTwh*=ΥeeJ̌qF_)` k5{XŚ,b`fKXw%X;,` k5{XŚ,b`fKXw%X;,` k ">LFF_)!X?N{s_l9}SB-5{o9ZrbsroJTf 2,ۂ9qV}X.I?S2ߞ 7%RX`qVMya" J hb`fKXw%X;,]Xd_T&.BO}WD]3(KNZz~һә~yD ^.ݑy>ygϴs,Zͱ35ͅ=A5}&b ne1&;qT`t_&urCVgkzebfr9,L7E+I`oGOSr(ۑcJlJ=?}\,{vSa<8]'ff-vp`:F;W'f/++Xm%,k$No^\ajiOx\gvT#|NaxF m qf%b55ݞ @lNh.x8i*Η&Ƕs9&΂WTr(K{*sqnyW>0_2a=.[)k9+v"J٭SwZ1tFQq jam*13\w =uN>Wv` b~h4V\ɩ0?[S;`9``!g V_gbfps}Eڜ d%oo=%Bbf3j:(MfX=f`ȞnFdSQm7fٕYg%*13+A(i EzCl%e5i5bft3 OUe8y:_ժhS&*Zh`.ǨFOFWu.-K,Ubf3JfKXw%X;,` k5{XŚ,b`fKXw%X;,` k5{XŚ,b`f| ,}Xv`QSf) frrZ4Kg^z]3-XnMN-5{oVz*3PF-5{o1\VKb;eOw+0Jf ']TfSRXGk5{XŚˌUs.ތ̛nGRDŚkY"h"{uXXK`z`-xt(ZǩPvz`U,饃En vX ̫eIs|* X2K+R/E+XvkgR;,?XO^hZ}I %K,DX.]`!Bt 5`!sA,,=zXw%X;,]XT#|.6Ct";]W >X̌>FI*X"e. $ǚ-~>=mkXͼh`4fh"6yS| X8f6Q{ÄE+XS,^-:Ipy/1梗܂U Xbf40?ρJUb zۤ[GbfG( +p Gbf44J5b5+: (:5{U^.\YY[MF3A` e$`^O73(RFF6̨7k{QVFb0a^PfQo=6̨7 ԰Q%fFQ*13,5lTQoD_,wVeU1+Ś,b`fKXw%X;,` k5{XŚ,b`fKXw%X;,` k5{XŚ? yq MQ{;>4\>$%6 . 8Kkuk,Kye?'=.ĉmbޛN'ͮsܛrL9L-5{o 8@f,m55O뛾SCuk p۞ gJTfAx6Ǩ%X@>X?mQ,zFvQ9i+nsD!!ͼgt  +^-f8k5{XŚ,beǧ4g/1pHjXO0qŕGXDDgDĚ̷!5-aG3QV%/=gow4C?7i"Ybޯ6bN~,/!^a%n. ֣<wqIcl^0'&*<6ѧ, "1N?\=Mc"@kcqGqVH/`F/FV̋FNf^3JfKXw%Xٻ F\\l;QIENDB`Bio-Graphics-2.37/t/data/t2/version9.png000444001750001750 2406412165075746 20060 0ustar00lsteinlstein000000000000PNG  IHDRXJ @PLTE{hpAiecG[QG=2@(pX1xe{%b)L]7-UsM,hw IE 0U5ZϱJrq0?y]m?Vrndžof/+ MJ|ת]d8.[{oj]Jm ,u]g1$,ӏӇ;%ݴZ5X|>$2j:y{)=X$X/3G!Ǿ6wEO ,7~Ҧ:LZ)c]`Ń`pnq^ ыZLak˒˕`E/k1},7f?q-U5%X&m1yO\rS^jEDd6Lޣ-sޅ)0U-/}݄K)&a \SL,'XrM/2s}̷öFZZ{י~i`W#&Xi]~WF54b*R1Ub nLzpgQ6W|euuXҰm߱akQW{c^ W5\f^\gM\=&` ߰!Mk)x\7&~[TM&C\owA$X' 7#%V2b=`5To,T\O"GM~Oִqz6 Tf{+ R{ &.L Šs 6"X~,v+XvIܩy%yXV¶E9%n5r} Lwn+%|~6b>q^3k#s,yϭ ԫj3ǂ֔`,[S[S 8)S`MhKM NuSL,'XrM1 Ob!hdpXg%M`-HJ,Ny c0P4G,΁eO1H 1xǖ,΁5̇}k@ {s,kzV jX&VfShg,΁&dNݘ{#SL': ,33,'',tx%ӕզ.tT-{tug- z)& S#{L1,}X `SL,'XrM15 _uS]%Sٻ"Kh1}Lvҽ\|A6^Xܑ)'`w00rtscixb71as9ߍbz6|z# *SL=O^`ݩDbj>Lsz`K)Ddզɻ8SLՓNi?Qb S} VxlbzӇh~L1=,e Ob"X`b"X`m L1 U]g VVu%&X!q1K)'~Gb?dp/Sv_2t8 `SLϝC4bjm 9yfq6Ab`5$,?dl1}Ńe'X4ƯalӗX(Vn VAGJjG0e`[ ÌӰE2d23XK(Xo MզbGji7x+z7v9x VXM jPYbժU]g VVu%&X UA- VSr -˸K͗1fAK)[Û]tMNSL(w]q`ˏטb,r}ፙFJ"wv]%&X:K9Xn~[gw^×E =vDtou`K[! ]O SeBVӫd\$Xn zXn]tY޲7SӺWup^ƀ`"XӍ&&`ӦUILbVu%&X:KLZu`,1jUYbժU]g VVu%&X:KLZu`,1jUYbժU]g Vk)~シ%5FES3YM3u՚>޶`gl1}n 5FTx%1ˍ\azqK ,`M6p]y))";^2bM}w npx%\hG,'XrM1 Ob`5鍽}etbKw}-|,R1]SP%VIo"Hhz 㘊S>ZL_b`' Gw}vX4=V]Tڸ`5$,mqd|frL1D_|`E7 I}X\ӗ'ҝZL_>9:ckUŁ}Xɼ]Vn%eX`]"L1,UY.f^Q5X wպ ϧRVmX 1$X+#E*0,UYJWo׮42\{K}KLZu`,1jUYbժU]g VVu%&X:KLZu`,1jUYbժU]g VVu%&X:K>;iAK,g6X$էi~\TQ%-/,9O?'~f `k'Sb m -Nn0>Xl /k1}5эC׃v*l1}E`eGIx~X1>Ý;y,@{ncM/c1Yp@Y-_ оĄznY}`V2bE7όX%@=<=/' 1N޵bBLt) wy{ׁwUiY- z)&_ )i 2SL8be`icEc`iׁӗ`,1jUYbժU]g VVu%&X:KLZu`,1jUYbժU]g VVu%&X:KLZuXX7 4T+{LgSLԀӗX8Xսtp#OoٵiһE3N_!MC]>,ޤ$^ϧXf[hJm>y#̎M zժKvSL,'a[IDATXrM1 O˄-;^bKov&ZL_b`[,wtES`[T]23XiЄqLE`)|-/t ߒom%G,+`v7$X4=K)&ao7-2 )WIXA%& :n I]C`]OwuM] jՍ+G 8ǺS״˹Q=ynD%X_)WIX'L%&X:KLZu`,VW,%>FUK|翨~MT`yE{M6-&}/2 `pgұ+L'"Ki|{1XK}Fh'LCєǝػzS1 ` uXfkȀu1 C,զYeLr֥`#oQ?#XM-0˳5{RIFBb IG`ݲAmSLŸ7=x>tu:FH#$4EI녵VSDZ0*v"RqΙ΂ED$j:,3Q8bױF|9V"r_8Ǻy͓MqM :'ՏٸDzJ)EQEQEQEQEQEQEQEQEQEQEQM.BIIENDB`Bio-Graphics-2.37/t/data/t2/version11.png000444001750001750 2407212165075746 20130 0ustar00lsteinlstein000000000000PNG  IHDRXJ @PLTE{hpAiecG[QG=2@(p"` .*ϱ*o^<21C]<2i)^vqIqBr`O9 և ,Uj,,,,,,<uu*UiBKVx< IZzzHA:֑D/,,,,,R`MCzG:sLRL,I1 3O&$<MM۝ұ1fZ#q``p2'"fC۝>d.uJI1,D_Q.b__W`D&aoq+D`u"0KBLNaf`I(B ։8L, e"Xh0c$m 1>bZn"ATLc*3z<0KnRL,I152$&G'XO&t1ft]`iJyc'~>34C7?ͯ,Xv=n7?!E^S LP7oc̓x)b ۘ`i>U\g VXH; 8β mDMDf*Ӽ&mD ̃XfJJBVӫE/,@kLoW#vwuL 6ue3U ,@-̣h,UEVuW[ogj`bVq9&X:sLZu` 8kHc*B8&X?w10a!I1 34kSQ ZafeR{ qwho/S$|ä&,ޢM2qo4CWrtRL70xwDEsLZu`; ]dzGk6VxY%` /&l>lu`_f݁ƊZ^W@*~h'>m!2͈')DCa&Ce@ 6&XRƀIB,`|$JIF;I1Ih㇓sLRL,I1 3O&$<0KnRL,I1 3O&$<0KnRL,I1 3O&$<0KnRL,I1 3O&$<0KnRL,I\6sݪs&^\1}僵1v3wk> ρ2aq^;8J>s,,IwF&5L8["*TU1}u5=u&'3D=OCaIe=)XƁx(4iS~m81)vNbVqʏCʽy!B!Xsf9\c[_DO$X+,Pi웵q9nt2 @T:~,GzX* ﱦ, in醚XKj#"Xas4f]\gI>+B~_m+B5IjB _Y!,8`j|?XJqaLXvVPҎGL@utYX,]b!ր[j|?X./pvnIơ]b\ϔboƜ*+}Ye-,qeH;LNƷbN̗Y׆4IeJ Ωd+&X5D X{@`Մdz=w, _u,vKX|HhV Lݲ/1XzLHXHUX~S /Dkp&!CQ3L|UVYX{!ؘΒb+pׄc ѷg҆_Y^u!!q7ڦ6K{YZ,Zu'Xrbf`MIy%7)&a ܤ'Xrbf`MIy%7)&a ܤ'Xrbf`MIy%7)&a ܤ'Xrbf`MIy%7)&a ܤ'Xrbf`MIy%7)zͿ~l>ӏӇšrZ5X|>$5vSZX$X/KZ-֏{]},%X7Mq)lc]`ŝ`p 8vro ћ*5eZeIS7ULa++}3MC@3_ݍ-`,"X,v+XI]~bjj$D<&.'75NijBEIA󿳧`{,:=X#S{"{3Phq]\RLՓmƿPQ~bf¢xs#$ X/ Q~bz̼!Xʒbzu̶v$qe"X+w=߶2T&byMn׵"X S4ŧW_$XD-y$޶N`tҚ1`"X2"XqN'Jn-`1jיc*3U\g VVq9&X:sLZu`1jיc*3U\g VVq9&X:sLZu`̱p>?6d7_Y1}Ńe'XLz X_h|mXxI V0xfxx F Vc0/]1m=Xl}%8)&t2I'˟n2Cam-hƪ#оl:O2Z45[Ʈ+~Gno;d.?ց}| 7^oUyrlKLs @Vwؿ+{\ޗt;^͈1=Ҳ{BBq9b[Oce, cm->[g ӡsc[#*%BnRb|/bf`M :_ӷwL&s/ۨ,JB+-s.VA`;[?c{'>3OΟkL>:GvEorN~[X-XĦsMaF[[pf-&Xyan,Մ2`M]UXqh ˭~M.}%0ʂ{ V|(tM^mkeq+/47]CXZ=`q.0 V'NJ}c#hhdFHo\ V{I jיc*3U\g VVq9&XUAS+k)yd)l2~f{FӭV:OPgRL:(Ѱ?q`Ǐ$t'X&w(]kRL7f걒'Iߕ6,5`q`l%H>ǰJty2N[,ӼρYn! qL2)d5`->Z$"tpvӧ}ζuLM^ֵ}$ZM`8L7,uwN'Jn-`1jיc*3U\g VVq9&X:sLZu`1jיc*3U\g VVq9&X:sLZuX XO1X:X|v `2E4W ?׋}i˽zg9Vz(ucSa1|_91\zq9V=yaCaIp]yx(dRDvqGrBw&aKnRLs6O&$<0KnRL,Ied'wE7X:X?co6O&dlJa%c]TLc`ųy*jTLc%`tAŤg1,?bK>78V8糳b`EC>H8X,I1 3O&֑9#U > ձuuuuuuu(D`MgIGnRL,I1 3O&$<Snv_Qn?u-s"eLڼPU9V pptqھPU9 _o0<Xfk XL)YeL4"X%Ŕۄ=w|+tX-XK`M ̄bksLZuX+X-/ŠD[D[D[D[D[D[D[,Ju*9O$'Xrbf`MIy%7)CɆDn]o _VAǾ4X˄^ek~'nXFRL7FwXa"9}a= H"m8LW_&,35d"X] w"ptZ>#Ʊ.=Al5Ķm2Mc9I1? N7o-"ڸ \`sXIEUv..b!*Y8mXdV׃b)2bԉhcE.tXrLj݃y`)+=B,"XqtXSյ;f7 {K}9&X:sLZu`1jיc*3U\g VVq9&X:sLZu`1jיc*3U\g VVq9~Xb~~_1}˒DLZlzI9V%gigį?,ο}o@s,,w(8ktXc9«1bKk ^VXةb +{ԋXac)>AxyeVOI1,mj2Vi#,)` J`ilcEޓO70O$`1jיc*3U\g VVq9&X:sLZu`1jיc*3U\g VVq9&X:sLZuXXKp>b*"aSi;5`b 볚Kgab8fI-f8_!j,mXII^XۤGhH>y>m~̞xX-8z}jՀ%Tf'XrbfnUIDAT`MIy%7iLl)̄c3ýbK'|;1Is,;<=MQ9ڟ` IKq&|c*>% G7m%{,&=Vt=a?)j?X:X,I1 3{ҔQ^_\&a7V_D~> Tx!X'_ !I\<5t[״ѕ` *>X6Ǫc;uM]ε5q4,$`| /S.0O$`1jיc*3Z_Yܗ{֗U;Hr:7l5S)˘(Wtd۲f,.Lcw%X֝醝ݽXciOjmvi^ VX9>#bMh ]}(tXfkȀuu1 C,Iw1˘KG$ #'~xG4',Κ֘K3;XN3`E6tڊ? 71B^a%L' {'aUv"Rqƙ΂ED$j:,1Qcױ"V9[nuS#x. NxXǂXv᪑K^'Xrbf`MIy%7)CVM37"cyU=s+9%)THcI1?߾3_2&7I)De9~8)&a%Phբ?0 * `Vq8ܽ2kN<6~EYxR/KQY]#JV?|!iE,>U5N[WV_҈IV7NZQrͤv]6N"u͓-/X1ETǒcP챢cuu0XuI1jG- |܁!H] n6ހV_fR!qIb~bh$nh~~c(Fxa/r#8c#&)cDiEB"RRcVbiaHN7VeITX#jg#ZRnFdN& g^frf)&BY执I5 }N( 6\李h2v:2ڪBnffFJZI(ohbZ^쳜2{ɪ,x#@iv殷k㤓fun*z{oV ~*0ΒInv.>S11O<+nrޞL~ /+(pAsH2\nQGB ے<0<']rdGi^s]$ýFuR]Dq:5G=z\&ۅ#.7M1&+>r^G7C?ߜyƶ.n/o'G/Wog;G/o觯/o _FRL:7@p̠7z F(L Wp~ _H8̡Cp@ H"&:9DGG*ZX̢.{^ H2h 05eSf8McJgx46#GL" =>#'IJZ$|!0I (G9@MbK&?9V;,ꢃJ 򔬄.wKro\LfbzE2Ijڏs4#MmӚ 8чi&9vS崣Ub̧>hnQڧ@JЂ"@-:s( *9Zlwt֚9fΜE?'CCU:/ `6S )NCR2OXR Qԕfs`:}jKT洧鲒zѡf-l:SƴV)UcdR g\kZ:b\ַ25A` KWԬ5]k#d򱃍ZWjA[ X*UVjW̵Vt[W"! Z֭b-m\X’Ru$E.4&bmc[F4bͭY&[vt^"ָXETYI95Еx5{ޛWrRj\4׺oY{ T]hy#K4oXslsY.pV+\"tɉ;>u|[u-H&,xCjz|VӒdN} mDYnueqduh䝭ixmTsT qn6lEXȉ^Kb?Cuu.4ۭN{ݧiOZw͜|_W=i<[w>K}᫙6x^n_?_|gL{cs}6''b~h`3~'u |g^i7rZHd ׁz!Hgh'( ~@28=(Ȃ:6Xyy&a+Ȁ&QCXp8)x3iHȃlLx5h[VTQ%TH]4؆J(Xch~q Ć*8`~bdhy_f׈nx~px|H{W@XZrTzxXc8()f艃hHfȅeH| 8{x}z6Hd?HRRhxAxwG~(Ȉ،ȉX֍(f؆hHi؎hhxhV਎H'88يɌ6d(gIv ُ )&(ɑ鑽6i  X/ȓ?IH,f0I\2Ky= ;)x8◑LN#9)IqdהEgyY[G`qɕ)YfIrw9nYCmh |Y7iy}ٕ'-(E"ؘ3ək82򗖤ٚٙ%YB G, 铬()I雒IwbYxYI%T5f[Zy_R[fI9ht9i.t 5J!Pa_㚅闄)aiA ؝Vt4t Wzcy8ɖ)ɘ+ix.V 4GΩ:YHoi[+Jw5 LI٢8XYYعc?vS3QnveHGJZ_:::k7]2ZiZJjoJt,7ƦLi:kz߸*ʧc {١aʜ}0s~vdu%nS[q꧇*J. _}ϹJU yzZ \v57gjǪɊ_.uZOF:Iuj_u;v|j銧B: _!*,ګ]ꨥ*j*]Eʬ*ꯕ ck[A׮ڦ:k۪욯M)+ڬ۪%d;+z! -/KTOHb<;< ˭¨H뮃5`b;d[f{b[pppK{xuOx j2۳ [].y[+_<2gYMK.E(#;Zghlc ꋏ;xk};3CrI:;Kz:kK;^U;eI E+G?˽|Tыxҋ, 뼥OZ'T{UK0;;:K*묁 ;8kD+˻+. 4{Ik}ptsw \#췙Z {LcW ;;,L%9|.ͮ^\@ֱXMuI< }&׊ɈLhؚ*mѬ Msؽ ӅMه֕ ڟًL׉׌ڗҩّ٧mٙ oqٍMړ`-{}Lm-ͿؿL۱mۡK(ڻ үڭ ٷ ݣ-ӭ "]} x؛ܫݭݵm۝ߥ -Mm NM.m˭ߩ .↌mn/#㝜`,4=^8!NN)m̿ 135XZx71ss z(-,n^db[um~P& eȝP^}.vhVO+q!||J>C?>hllqDY1nc.nzH>~=ۏ3P8x{}ܩcΫ^.2Nš-~ GBGDCo?SHPOCZ/JV_a$ܜhX]^k`/t_u/v_c~}q?z_o~&e|?OoOo^/ƭcPH3_oկ?ܯ't׿OɯO?/I v"x0A 2,T^ĘQNNHȑI }=:vqO\xʫ?)xͯGP{]o}M{ϧs PC0 d0ț)P(?@(!K2@Q;1D D0(PEG"qċDW̫Et H85!Rr+*RF۠$#3h4408D(5cM挌sΟD2N/"(7VSeYgUkD<\i]]koƵS\馻%4?.(>M2m.fmB̧E}Бd7z#n!ջ5j7WT7~wGڙSw~'u=GYItxEn-W&ى>ߢ_?y7ط55{P&8lk47\, }~%_FA,~!@ ~+ \ߙ%tP0 !]|(= P4D ch $ NO zGƯ[DbM("bEXج5Ќ Ɖsԡ(D:Ɏ}T#(QkXD9ǐ,!.q.c!YEz叔DIQgL#;YCBM%8:2a*ӈx,emB^ŗ4/KY&.O any< dnҕ$L6׸MeFf%uirlxB,yzz`'+ÌRG,h/YƄs'j͈B|?'Jţ:ņ(*G*=r^l(O*єRs ;WƒT)M*͜i7΢3̨@w:ɦjt@TԐzL8We&Y5VVO=ZV~a5k(R&u+HVou6*QӯU`ۚXJ]bYZfyQ^ Ztk U[Ѹժo[ζxPkkѾ%-oUk\"]noNiskݦ,yYnwUw{^獮y5Y>wo{_s7+U.8l̑X)L!;`2w0\X(M} JW d0/̼3=q9˖mf?2StiX"Lnrd_ p3xϸk(ʒM8$4@R1muf^o\RKksOܧ9[idoM_' SjX!^uIfP/+i5= K(=Z$Tzj~ٻ^&g bD#G5e66M]Y؞&p+ۿ&fXG֞7mWpّnנK|G֑1Y*.ǚ穼lqyMG>6f՞6]V:QS3~<8Ʃj;?ӝo@h~^̞TwЮ`Y"9W:V᯿=6k9>$[=xwovii8 /۩Ý}'O\ng ɋo؃Nڡe zS5:q[<\c w3W;?{SubB'#>졟{! r91/g9>X2ßy_0l*db+g|9␘<-3cBk1S5ڻ8CX>>I>˻?=>@ 7{A[A$=LKA <: ֘I=#%Ak;= ?S,+@BXÎK<.*/T4ú3ÚA,\IFinF[F/IDn:&T4oTud[ܠQ3vGp5z O4rAǀdps,.IȅdH\c<ȟƆF+ "Lj’RHH`$HG>"ȏDntFLIf,@ÁxɛtGWlBlɟlF`ItIŘDC3ʔ ɾC2t5lʪ\xDʠ TIdʭȧT@cŴT˵\P$Ic˸t$Km2>˴7|ˍKDĥJ48HDLDDAL¤LETL;<$ ̿5Lo _!iQ;_k19MǼC4Üd=VXM55q<Q,*M$,dAT>  P>֤PTDlN:Q|O!J`;6`EPQ6ӬRØN?qY3t NOCMPB5Q- *;%OP=]=<,S7\TT->@=T)TDTFMLH5@JD5dEQ܄$iԹPDF5TTҌJ\]PWm6JaU*TKU==U5UxXUL%TeR]N+]|VTj-9$mm7uVqMeVMVJUjDfV.֌VoVmTGWMWcv]JUKUV5QEWkmV:%"w} su=[W]'}/ԓӄE؅bcGVהM5؋WB]DtQq80BA;U-Zu ڌ=WU}YHϸ 9Bڛ?ԝYG4${QVWUXkZK] R%RP4뻜K:%[X[SW5Lk'4\Z[ҝYZZ ]!}PN[ԵX]ɍ\U۳ߣA*\5]5]mޘ]]013N rN>Z][U]u-[QBK _Y}~L[_{Yk]ߞ}^^mX_nu_5U%`P\_F&E_` Fuܪݵ؟-u``Y^VMa0}Un`v`#&mx}bp>_Ub&b +(X)a&*.&c$n8>3_-fc.Fc$eᎵ^0@Bf>$4a:VcF^d=5_Kb;_IdGNd<^ kMbّd7aWJaK5c$ePQdHdOcDME&<3^=]C.@M_Ne1c^~\㙐5 nf):~Pf@- ev"Qfwfr~g2Bk65\.gyf{eo`e!5wˊYnn\@~Xx'Nc3gndXavvb.p~a`eg~dCq&~^iNigPyz6˩;lH \|,vL.ha IK,oڎopN^pfmXn /pnNgfj'^#kpzq~p ?poqppqqqN.>qpq"_vnq=.r)Or_rrr-rqϹoT^Uf/j.s9o&Ol6012o?'s&mBGmAsDhU-'o;7oKGoLGp:s7rNoQgpMwqS*uTqJ_sR GrI%{tUdYWqZwr<uU"R]ws^tGh nOolGG4e7#5'qWouas`qq7r[o4n/9#:G{_Gw*'KLM7kvQ9ww8O9_:SW="y?ww:9x&TW'Rqsxx_3{.-yvFph7s@/tovi?{eݠoOwjp7~qP/zpgq/vuuvsrrytWxgxwxwpv/wywwWr{~jh{vzClŏt0{Gz/d{Ɵ{ȟ_E444kz4Dϣ& >w_zۯzr<閿=QaP_yu?sC|34MA[F\>{ćgϺ1}ڽxxR0֟~X6u?.:$8A;QYРÅ>"Ă 1ZXD7fThdGG˕(_)Re˙7J'P5e,JH?~ \ISK]8}zp*T.:jS.w\XǜEѢظEFET)ȪW}6-`yfkѿ|,䱒5N[/͠7TܘaTBɬOUfRK51nǹAGKز»ZzՈbN|yxᝏ\sWNe3s.xGGΘkT?;Οxwm[u-ho "h yY}Uo[(R(!VX!Rb- 4^^P6VEgb9x΍(YF&"=7%5>eXI%Y 3{;o5xc=KZf'福|׻Ms>OpgO{n{"I:# Rg%(x*Ă 跾A"_J3O! Pw@0N4X0ҹA%z'\5JNC"#ĉJkE .І "9X bHE4nmh4ngDC ҡ+Dh4mBTdh5 :E- S=xʑҤebSa Rn>ЛmE}v(]Q:u3iM*>^RKDkOXIO˴gS)}!I˘Dr4#*baT$Q:OC1UcAiupmZ'TtE+@wJW+ޕ-JC;V$tl?Z/O Zu͛k#ީYt v}hwkf7$]wi1mn;7izILW-PYmoZϻz׿ŭz;&W ~L*MlTvpZV)l`L+pob I3כy5n;,nA/gv1Ql;wУJZwWnX .rs|dw5v;\=J0V/Tųg@CN汛{af*lmY>l4 f5yМ4~ZM -4yKmcPZ@?׵^tlRmekҶf߬kX['޺2[ȥ gRlѺ:лn3]mVtG^zG~UPy4.w}i|WNwcsw)F,'[JzK|O|vnk%&q}+{'9M|,5rw''xar5G}}G9_g;s=t=<A.mx㩢rNN#;n9v8ntF3v,._zoVtwFNqQG8QPu]3e4_}H_Oǯ\p<.5uM?wyc+L1/}t]u/q=Fmī>u=nOW[Oԟ~RTn_{on7m U_! )a^޴e% ^BYΕ DZ` zv`_ߴ`r`s٠j`!~ a  ^ !& JaU [2Z!b !_! a*e!`~!!ra!a^!"Ba 2&:b!>! 6"(]F"z $(fb(v(鸢,")~".V""^`"#)."*b"''c$ޗ/0b3bc21vj3"3n*.a*f]̡#]c:cc;c#cmF4<ݚmzijm"uת@BjޚjrjRb@ ] :(D.&jϮV&팔" Vxn6.^-.nHDm~[m>-- RNkr@Znt'JʮѺ..Nz#/ߊ.*/.roo.o/>.0/BdT?0zjD0gSoiVl0 ߝ)L 0 p#`l0 װp?0ڰRf 1pG7%!o1wkK11/o[ow1 W//" o j1~t}nSM:gq_/2EYr&?YX MͨͅvqqqWg&Gʁr+wV0EChX,晩#%;I~2|R*Ex131+ױ+77G*rFs#OV3P3s(n+crYg-כ.rJs]XsLtHd%13t=8AiiB3'3om^VD>42o;r#lHiv*0J3~ߘt1F2jb2/BgtCKCI;t.t>USuVcuW[uLT3Ot؉kpNg@5__L2`!^6b'b`#va 6Q/vYS+5-vb7`?v6g6i{6`DL6R2DZVG4YiVtv[׶n+i5jj_k[v jR,tWv_Ig*sG7cs#&m X^klpoq#Rlwh3wo3vnKzws5wuOWCSm;vzwt7av!o;1|3~ nVyn^6xr/,KʂӶ~;8w~6g+w8rf#7[Zõ8w]]͊|5PWuϷYo5Xg{}tw[yCrcg␧ruKysyuk _v~w8!kryySy8 :W{y3Cu#czz{z+쥃lz#zszlzkK:}zz#{ú&;W9e#y+3S{9q7ˆwsSx[z+3zyKXS; {{kyy;chyifWZs㹎b;'V[HWb{Wwsy_&v;RgE/﹝k6;|ttaJq|, )Z{#|^kbpr%grq{=]BnN_=/|'<}Tez-ˉvw[&߯ڷx0GhAٗr=k[ow~>#> {2# \$=j~?GS|9kLqet9y4e̟65ő-A-Y}; {uXrب3vy5s骣^Oji_S~԰vÛu{O{S?o{`i8O>ʋon;p|0: [ʽG5*z BNM&@1DGēpEܰHS*LH'PH#;e@S9M%UPBKSCKW4QѸDEU Mҏ.W.h8k 3O]sUc=+ZWmV[XeՕm[QMYkPͭUj酲]l}_vtwTxwZrq^tNؘ7} nzgŷl8b fs;cW%.YX_%YQi>9[~ Z衃6NZ饑TZꩡWgg唱fÎxl n;nn1o o(o;kjm^h[˥;q"7o WkCXK|R# 7iq6yEsu|6~ы/j}bZ綼'qgcQdw{^.ᧇ~rCU-d|L^ƿ/z tw=) Z $tt`< T.+2 op[_v%!r)ÊyBUe':MGDA]F(-Rb 3bQuV#w+21|^\ h1r|#I%1~\# C2v;B_|h=1~$%1f БTd&EiIål$1S~2|)HJG\$(Ir%*y r,.A`S$"hMZҖδ#LE:^f7i-rP$29oRLg'Y92s9]On33뙳2|%S4(AIQf17:24! FJx*t*GZ4j>Г )JoZMzt;@a*͢t*Q9:~&T(PO642fs:ե: HjUucY:5o)Oԯr]j[Md=k:W**6:Qn`Ye&'c*Պ>dGVḁ85?+5-i]v[=lc۔鶔%]fk56&]*q+KJ,K;;ԪVuWr=\\װku/D7 z@ o(ś^X/ s"W&l{mަZ6t}zQq1 7Moib߀8]t{^ +ث)F&lDpr_|8R"1z\b:x.{yhr)gWöᯛİ9"d"V~CY[9\uWu=r g%GXnm u_ڹ]n^noC\0ЄѼR>Գ_OCccZŅfiEuwxw)ڥi3|u?w-}mM1;9ص&mli9ڃƵflsۆvpsՇf ^d -X㠇l'2mǪٮOeػ=NM-^Òf޴DkS{&Gm}+7^J '@Qx;vwq5Oഎ(͓5M&a 8cr{~ve=vk<͵Q}s^;-:!uY(7>o.<{?>j–~@ }Wzˣu]er=o%yhz~C^SW䷡wq{^Q\/x|/3t/ O;K?|^o}7џ~GFpnPw0p!0%0w+u*0qo9=A5p),O0U&B]pWPo nP\ϗ+IGOXo5ꜭp i / k P / S M   P_RF PovL X`KZ~E$`o?/6Rf$qM >,eoL3 ?Z ېg-瘮,MLԜJ&R s"a sЫ$S"K/ːg0KFM QEoNy?Q FLG\B7cSoѱ Rk+"τ"L2IP@194. ;r )k"[D^18L!i&+#7r (N(()#m)5Q& Va*#P 1& G,02Z-yOocpj,O*QJ.SSr.'r'2_2 3s2)2/2q}hS,P+]2I4M4}p7"p203Ps6i4cSw4:3>ɒns7ks8/8K3/8+8Y3%7f3))-nG/( r+82#0:-)M-_)r(s*/>)-7=3*S , TǓ@@5? +A=+@xaަtPTF3XԎZT2.M95=PS [4TT[u=SU[YE-[1T_2Y"SMϵ'n^[<:1vi3 M9E`O[iKa7uVU5RY߄f,c GӂVoDV Owʫ_3WuKc`yW`'Rb[gEO8bscm\Tfu9rՌp6iq?vUUjK+9ǖfi"khghmh/>mgSeOiufil*Rhu-R6Y!Ygd)qWiw^mUllqoVd V$'q9aUo{9i_;sK l]1,v|pY"_7rup/j7ux^/wS47jvt7Z_6y\Wppu7f9{-{-tzkKa9o{2SWy}MvcvcWfo}6*J7s}}oNhճT'+V^7][7}ŗWƑFL(~}-X}Tg2 |UL%ymWŵy{w{w8;1XtIBCՇ_{Ua8 2FMuNVZ~ϗIH7u؁85x_؍8xouvu؎X=uc[8z9%&sQY2UWؓUrp%cV2Ms36pm68e>Ey{W=pW]/U҅o/kq9Ґ儹5o8g!j2S>7 yxmYmsV9ze.WZRĞ ?W9x/Ҙsٙ*D9w_N9\}Y3B zW XYuS9/yU$Zskڑ 1zG]7x?8Y]zکz:zɺ:zٺ:z麮:z;{ ;{!;%{)-1;5{9=A;E{IMQ;U{Y]a;e{imq;u{y};{;{;{: ;Bio-Graphics-2.37/t/data/t2/version14.png000444001750001750 1131512165075746 20127 0ustar00lsteinlstein000000000000PNG  IHDR CPLTEQͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ ck1՞Q/.ץ\-]=qf)K)Gb'z`ܷd&si$Փn<b)KpHw}Mےg<VKr%!pz Iu#֥<,U%n/:c9ai)~~$nd=vx)WwI6icrd} i)ku^F^ K9]r[L*k9.ְ%/~#瞥>ݍʑj#Q&rW6.MMJ9^I[sRNa{KT T7pYI1P{kiE9SPDl]ޤnY̱xe_um+Jc=Eݦ!'-cWtemDŽY|~lOD]I#:vc&<{5zu13oCS4"pΒ^FMg\Ykw|gLLJx܈ 3x^Ε^{f#|fj=smџ{ԥ7&pg@ħ4ws]:yi:s沢?{ZܻP$D!2thXw1YM4wg\snOӳSo?R6)4kU]2lH>pKi荛nb>SXPrz5[E\s$[ʮJg+s}>6D'Hzlg19.j5FM{o&abWa>0%PT\K墷k7Z=\Ձ<tV<Sب{.](%ʬ=tkYJ{y8֊fdVڂAUiHuR#}hkiAwq{Iljv_Uwѥs{(/XavX &=md%ݍuH'v)kvc}ѫns/t߮y.N~HWeW[S'>}`sقɴRJCZR=\YK+u}u?!n瘮qֵa>뒄X}e힬Zj3ݪ2|Bu?C { P;2mnסB=V~N /d݋'<ύ {ok6 'n6N+Ny~)[uO 9G/"x`8ws' =G=md>#߶'~Gu1dw*829e̿CƒR.-I@whUyoe!9&%us{=uُbmw_5ݙͩf{=u5w%b0Ȍ}T9_eݩܟf>~ H6kw-+_}1$=: M= |f|TwUI~,;]էil|Y/з.QWUu=tUݩ"KC_/?Cwt7\p\x!oDԽ  m}NѺptOsSC#OBwh՘1Ncݡ#:ݡ#:B_*``Fq=9+â;m7;TU7>#+8;8C =1e40R> ?8RKQGcYSS0Ҙ<"(z~W{O#;I>nNcJИQ]U%Q/;ԋ5U ttؘMux w R/U5wه ΪBG;tCG;tDgm}v؇(@u#4(O~Qu?aX}Hؓo>tcf}& o;PJG^UK1ݟ5xZ=mLVw53%{UMuto>tW"~F@m):yo;{}Gw8po ^{s<3;\k?C?^yWR.୶;kmGw8ڎp:/.uh\d57uVZrTt.tͷ>c2g3?jHwU5:8 Qwy`ݺ_+ژAw_M.6wUa\>\NݯHJW'~#Dh6&JϦvftn tPw|=`q":KĠ#v9v9~1~1?)E};8TsQN!E}A8v<NӝX7p?‰gU#E}P%Gu?99W1P#u?71ײP!t?5)W@$C};tLՁ."v t1t?27DIENDB`Bio-Graphics-2.37/t/data/t2/version19.png000444001750001750 2410712165075746 20137 0ustar00lsteinlstein000000000000PNG  IHDRCdPLTEQͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ #ei-:Oe'jWֱc wTVU~-_RVa" hvJQͭٗo˕{Rض4{쾾X5g\:< yfc'0Qi?H_5w➡ݻC ߲E?ߚV&r :] 1]l]Y})I[za^+_IKDdbBEww׳%&΅-񦒝pu𴙋m3 ps,3)E=+z"8S)`\c} 2k%;, "~F(V.mF>0.'jQy]4l0em"wnŦ-U_b+A@`l"-t8`C{`"kd [&{oE{`" [&{o:=0{D&D0& 06L! `" MA@`l"C@`D&- 0bKsE@{!ZĶ&ͷ.ɰoB0p sA?b%֜5M9Cm0/%ق6R9i`9%;_bjD^lWt9BL5\22ֿ!sƣ >,g|C6lp͝F)F@[ns #(g_T Y~)90,ddV5^*#Bf>?=]ԵDɮ~#4 ;fлL\$,@gn(٥0RX+|wkֲ=!`ܓUL\Ѭò;2O Xk6.٥GwcbeJς#D vi3@0i"RUjX0& 06Ly{ڗ Z;36Ӹ0:S_nfM\r3 S@mfq=} >7i-FnYEh4.LVh*+RO-Y ܺ| nqa" 9 ~ڥq%י6]ݎCc.Sw\f7R@Wȭꈀ]^R@IH0['!˄IHp$dŸ$씵;yub\.̸i\VjXȝFR)-oqa" O;KGLDJɀ] iYn3La5-0d<$LVťpf ȝo3<}i\SS`D¤"{}yT@^mWtӌ_C}3 yN@пFoΝoqa" #`Kvºn/EFLDPIHZ7͉6Ӹ0T;vuSsDP! <)Aa&}3 yPr2f&G78PsDZp>jXH |3 A@`G+A@`e`2O y+A@`l"C@`DZ0ƞLDнo}3 i@@-u]:-G`" v f&Ҁkpw$ђD 3DPv /6]ד<ђDT0&pIN g5w]l׫/ Q,-x~;Z]'\Z"nӑ[kN!B6+`͹TsN3XNΗX]O5wM%C@LY,A@r0Ka~)YjDkXC@`D&D0& 06L! `" MA@`l"C@`DX{%E{kF9`E`";ƞt?nDkXȎVߪ&sVO :v \.fV&'Oa 9П%mOBlcIvS`D&F>>xw]g5 ؉ jnj[@7ΰo/7xRK/)՝hɅy2TؠL:15.xm2T N/s5!C\y-0eP@˕mNBᚳܿVԔoƉ\G= .x=2Xs+sC\HQG-濻34_QQe:1m:zsv I+w'0o.vSsDj h[ 3f&RR ?HI7CK;?aDwЃjno=N]27W~d'/o}Hl~uٟϟ3̟Զ4zW3{ 6󾃖ef2+mۯUZ޾v\>pmD@,6P@3Y]G@ 00䎀v\:~ Vлw(]p n ^^o+`t$d Gf.8hIHx2`-=pF[sݶJxsY-Ps:1 #OB~>a &Ҋg iF@`D&D0V Gƅ4){?zy4.L|F֮ 6i@@%c.xDH. |BڻV&R_8L ulIȘ0-] T_-LMA@`l"HUYt8Y]_[jD#%0؃Mo]a߄`¥%"6ƍ#[kN!B6oڜK5T%ƣ!"-`!` &RQǀ;'G# >SË+{旒F 06L! `" MA@`l"C@`D&D0& <苨w^sDnGaHT@3HBȮy|;y^˵>^ULRsDnwf,.Kz}y)LkXH\@%tZ] X0$hw:xx8`` ${m_   h3$6-o7E+ ݶr-X0m+baI&Оh(7W C-\ ɊDvW*7e3LdO {.?# ԫ i^ %kXH\@#,XƏ×a/}%! 0.D&D0cLr5^? $UjT A@R5H&]@BH|?C@`DXWc͍2xf󥻬9`E`"~^Q>˚V&rA@mt4-Yi?nvUr.삁=&^LE@`O 7Н ;4jY 85L$[26=F^?ߚV&+`[L! `">yy XZR4HI $UjT O@2VztX0& ԅ sk-ǁ>4r*C0wFw!KƫWdpjN -6t"|Jٱmsa" }fQsn?ukh)v v'*p*D|U8k Э:8#81F~E ςdz'm$0sM߹C kXCNv_sDn>Sśr`y=nN-F>#`|%aH5D'&K8][.gYhwDpDpDpa9+\n &r"0& 06L! `" MA@`l"C@`D&D0&".?-g njXH\O,?oX!ݓ*N/5LdWX= M5L$*ປ`w -5L$*_p֓I2o5i]da8VU@Z@`u0 #njX3S@75L2_+0\.Soy 3f&RT@v".xϰ\N4?_`DJ _A>;bw'ujX&"0L9`E`"w J#󞀻"0LjX&"0L9`E`"%|0 06L! `" MA@`l"C@`D&D0&$ 6/{B X F7oSD_cŹvV9Z&V++`8ZJ XqX30dB`㼸 ȀR`";yH՛7j1`5,Tm;1 C ,Sn ?kP@moyG9`E`"q?/^@skrp5LdWei~-L 젔rIDATfUj7e3LP@ XȎ^7*L$.lsLG 0#, q!X0vQOc% N\ Pۧ5m`]u.`Q'M-XV@!Iה܂e[@NaOPaLm̉jKE,Gg , }uؔo9``^@\%7}8dZ~i oި\|Ostͳ'WU Uwƅ}i=-;B@s"0AR rag넸VvgJZpIHx~\V^[˂/t\sDց]ՃVИ^רV㯰mEO6)fXW!gѨF~Pӷ[raШyEK~RX- ǀ?Rqc_ sVCݟ4jmt-7L{~ШՃVk&D06Panpoe?Ta.B@ UȐ~Z@]*/6-2ΙMG͹jm֮_S06\OU@P/U<'a2ל{( S;zd% _@p.Z wWa)b'!\keΝZ@k+yH@ `[l9؅%qW o47+n[N1,,Rs}ݜ‰n0Y[} aE.¾ۻKG'kʹ}ʼn[/SD,^'o.`|ۅ 2ayEE,Wx0;1cƝ_@΂ Un`"O خ{+A@`l"C`Ca*N*vsY^ A sSMG:(]KWo4i&Sr|*ۥߒ=AX@+fD0mn op$uA@`- l{d%.dxֆmoKݹ50պm߿vA<ѝI}mB ,ml<L bxSY5-6݂wnC17WnŤ/%{ߏy?P٠nT{mA+Utu? مf c Tn'[ :~v Vd1`Eʾyl[?K6,3trHUpCq +{`򓽃`X |r VHl<\C$OXGǿW|6X&9+$n'}%$*Nb.#Y_o%$V `ThEb*ntː+h5,UB17:][>'{Lݬ?M 5&"`id7,L |n"X`0 VDP3*aR/2&`f~Uä_d&XMtz{\(yl-'&CeKI? /*,wlm?w;ů.(+=@ٲBlE> &"X7{X |?٥Ջ{[^I=Vd(z<_C,3üS(&yA`@(&Yw2B1"ˈӧB Ť_d&X bR/2C1"`E)`m37fJqy[}FG3LdB-^[0ٌ z co3EF ,t^ǜ^B18Y)sW@G(Z`e16/)XsT,z~*&X|ULj` V1S0{)̯_`)XiTLjnŞX,iC1ʴ~%0~1R/MALPLE暡'ӊd.&X&"sP %TobR/2+qnw`W1bUw s8GsV]љ[mJJ@ ̿+KuM~tK%Zu/zs2&? ֞>V`^L@V2'z2#؉0ިuӊ}m$XH]aIs5ڒW]d&Xu&F ֛`] IE`Q*7W-G]bRot*~'z~`5I=LŤ_d&X bR/2C1 VáPLEfp(&"3j8z~`5I=LŤ_d&X bR/2C1 VáPLEfp(&"3j8z~`5/+y|!`pWq`<|;'ky|{`Bl,_M{B%"%7kZp0{ov'y|M8%XI`r1S=_̓%뽨]K,Xk}#=_̓ > ?BybJGJ{b-ˮ{ӊ ICIHs-hŹyS>Ƹ=V\KWB: XOpFNjPL_js9,(S֣W ΑSf0$Dzν2hS>nqubN,"<پܮ!1 lؑw'>f+!f+ -Xv5byO4=@r:>Zwi/nh""9xlXCb2ȷUH밮z;kNV=)=@PLEfp(&"3j84_b=_d~,q/߭ي{M5fz7b{gy2jco̊s=fk'祈Tۡ 4|m WW{fa1}D"|9hdOSM-󷹦y`++n[Me۪|wnCe@`aYMUvh}AnwC$X`È* ͈U_d&X bȗU5ZnYM^KxgV[ЊAEVqH! r|(>qppdIs{W jN?N&A{,1xV旚_}V8:zFM4߆~*2&:m0ޔ'f'+Nb*5h \1(F`n݄`Rot` %,bṖMMBB>*mA+"+UN(&"3j8z~`5I=LŤ;mnV[B1oӿC1 yv cBoZO.PLEBg2/'w!jc(|B=מ^syo( XgI2PLE5X]a w݆b*wyv8x? -@Ĭ{Xú*&Xp7} TOF TOF TOF TOF TCbR'XI=LŤ_d&X bR˗n{ksp?/ HŤ0Iھ oKŤ^3m;hg"X}b˗e92.YG1p(&Yr,(PL`s(e@4ם!5E;&@PL^3/!X bRUW``)C1i'v Ť+4PLC1iŤKЗbӡ4 ObO~ݐj8z~`5I=LŤ2g=T`5I=?iƻ{PLY6o;ݜdHyPLy9Ik^=h(zY6A^M{B9l9sg(4[vWhȱ)aUWq`-!B+.=@:˓+74[I]wf˂'v ?\;C1 VáPLEfp(&"3j8z~`58_+Y?K*>GeQG+ls=f,лȓ~>gZnR=x<K'5ͧ#A[$۔o?QU2 X(v߷&YHr J»=Pb S6؟=Xf(cJrBs'ElŖvt;v3*_nXî0|ࡹ"~28mcAĂd+2b5 )ăwU2 X=xoTK 7樦wC$X,¾\hC]@| >j8{Cv_fX89Ih]|BTTk)&ѻCǓC̓u]/1T(d[qw-hŠ]d~% Ť_d&X bE}[|̵Q؂HsƯbB=Xnw,*F'+!0?-3%eze5-̝5Mp D_P; J[6Ǿr˹Yƌ`2wV6¿iլhͽ|;5~NɎ*0 +X~*w`M9j,5r{V+<] ֡5%5zBB8X].k܎X;t]tޛVovsVow#?Gyph摔iEVuI=?i+p*%Xo V-`Z*&X|ULj`M+T"@ X+NNj'X bRf[Hw\fC1fJgwk<Z2ۀefWq`mwfalaOcU:XS,;r{X?pl\@W, c}^o3b]ǃwF{U_d&X bZf*/2C1 VáPLEfph̝dZ"h>7FhW,hm.*,1OSPX -+|l#X O ШЂX!=_ŭ%kpgz+TZ{&"3j8z~`5;όV6XSnEGDGDGDGDGDGDGE!kќ*nO:)XZ*&X|U+XHMRw출jj_*ă%@$+XvcU1Xb[.18`_zUWq`-Yt܈%po3),]` )/>vWK$8"Xbbj[N` );Jٿ`-yPLEfp(&"3j8Sl5OŠ4 XoRFa"X#"X#"X#"X#"X#"X#"X#,3uVQ~*~'z~`5I=LŤ_d&X bR7f+ Xf9> z2~OQ ]Ѓk .tȵSqv0K#l/4jy\29O+ä [h_=&)Vd{e]aćI#l)4Uq+4-Xf'8x4ؒePL V|l2 TPp0X 7 I1_J/XKŮzD䎳5&Ry7T//"+TqV uwTOVD`ETkU eq/ _P#,9Xr5RE,Sy>"X,Qy~Oxe49X8F7q̯W1bU7Xo V-`Z*&X|ULj` V1bU7Xo V-`/c IDATZ*&X|ULj` Vo|>|De Ikl}`# ֏Xw!gy3w> 7'Bf;m?t+_G,˔7ˊ{vGu\Y+;9+|D+]ZHrOhBs'mWB1ae?>~Y~ Bs'mWB1aebwO<4w26~%oi3b Vd"X? { ńKnBR|͘ ń`'y~{PLXJ+T ńC1akPLX~ֿço`xD: ńs8OńC1akPLEWB` V1bU7Xo V-`Z*&X|ULj` V1bU7Xo V-`Z*&X|ULj3r.)po>"9Sj=9`HWq`}ҙ0?!b|С>+8y ln+Ril}6τ>Oy aC\!yKוAfk2acVXhXI=LŤ_d&X bR/2C%aK2'p?cU:X_y̳Iz ZVUX\^'Tb*w[(8xovR Ť/%{Gk{~9JM|<Zr.ۇf$c ӄ.3gvgfݮ`寛?/-rxͽ=!/NY$?`yin xl+rT +c[(ֹr.Ƭ쮂SbtSQ7I## ք`eL[!>!XYXK=JhZ*&X|ULj`)ŽLZ>k}t0+/?7oHŤ_d/.핐2z~js,e߷Ĥ Ť_d*~28M |aR/2W 5 yPMUv(&"sMvHxZ>=3zS+-\ )ŤX|`ENCŝD7yp"+}"i&حgc}]|"nET4gtp:ihl=||Hb]d oxǻ%MO47SA7\9#V>Xfcb[E}Ͷ0oPoV Xfz~hE8 QEQEQEQEQEQEQEQEQEQEQE=iZXIENDB`Bio-Graphics-2.37/t/data/t2/version2.gif000444001750001750 4473112165075746 20035 0ustar00lsteinlstein000000000000GIF87aX{hpAiecG[QG=2@(pzЭgNv]Jӣ^^}㟏/o= Z7 2|߃Ga~EH^*∥ &a 0z8a$"5n(:d'!(= 2KB٤O8U*v"A_ĉH@X9j$m^ ƩIR$(T&(c'o"zI磐.hV8-٥bsF猡nYiZ*i`~٩]Z*f*޹+B2,y( 禹j$H .b[gr[`'vF~nH[&.fj;wp0 {fw ,$l(,0,4wlR8<@-?-H'L7PG-TWmXg\w`-dm6elp-tmx/Hk߀.n8{7G.M'gw礗n騧u.쌳Ѹmto'4 G/ԯL.m|w_"k:觯~iсoGˏ 8/ES:ll@̠S L7H 0 %@ ЂˡwÝYzH"!xC{aDV1)>[S0}eBsŸXEy\_}4F/;ibc6+f#h.‘X6HzX"X ?I2Vҍs<'I'CNs(,sIO:\EEULs3=ᶭT-(l\R71H!BQmw``>o#\X)ۍXTr|b.̚C2xob1BRaA͛tsi9BΡDqc|ޭ,aFw4}'}-Icz,N3Qr@cAzF٫7k\l落k)9llֺb1 Tm+xe\9J`/묦得Fjkԭ媝nSwޏw{S]Nxnm7 kwqoN[ܻ8!iEv]\%g7\x G0Coo3Q~@o-}?KOznS nwH۩z\uW^:u޶>_x9\nvdW:~=a_bcG~?=cgy[xчwؓt_JL~};??}Oz}~zWw@' ~w|wJՀt8zgh*1|X(Gβ!0hw'(:F(yE.Ʀ)%K-!x&KdRq}DHC7v.),+"-,22^CxX*8L?XJxs+NXhwx9Oh4z ))j膃-oȆm82؅W4qx>.mH2w$E؉~|_?qg2X(r$[͈MG{@nAȍhȅ;~uvXH8X ȉ8Ȏh*8SH؍ _Xg0KUd֏8㘏wwvNƁTѴPGhEو)^TER`S l&)*"yI|4MNQ*yy:(MSS1nx5YUIјuROQK](di$4d%;EM 3Wiu+huԓAf\M>ii9YiyXNyvXE$kxzY9D cyb&0FŔCZ ⚯ə2y9Y0v -P) Iͥө iɧYU FC9Ui/%Z)Zٞùi)I~I IQ47Dyi9ijH9YI#wJ(*J*ʌg ɕ!~'ڢ9F1)~=)ʠ# )\EPz_DI#BH` Y+et j*f 9rw\zyJW}u6a]6Cɦ9ɤ%*c'VA) IJ_SNdJiz*tjTQ@VE*)h*cYhQOETi9էVjXVRRҹO*J]Z[oj@⬫ѫ>zȊ:Z NI$QV sjID*J:kz]| ۠m:I Ɉ`:"{_1ȵuz(#ʰEʣ-RVUCqMJ8[iKZQeJCz\e,E@{P۵UƱʴZDW."M9\;~rK)3-k4GkEV۞Sk:˷F;Z˧$k -ۣ+ ){J;z{;qK4;U0+~%k򚯋۸K;{j{Λ{+ }I'ֻʼ K+s[N뾧˾⛀櫽;ߛK[M \,̸㛽K[|,+{j°! ̧b[ )l* )Ի4|{ 3,=$ /£ؽ;K<1 .DV* wea|z ]\A0#5KŞĺRO.ʰ_iUY|Ġʽ,{>ca1MYsyh2lk΁dɯSAMY˝ɉb,%ű\ЁlmU nиﺴqҎ ̈́͌.%;WC}0]~j ]K68 CX)A OP^_W=[M]`fel}kmo]r szmq|w׀׃]؆-؈ͩ>}Ӈ؄ׁ؇hؒ A}ٚMb=ٖٜ٢=ڛס٤]٩i'k٥yۨ]۶}ڴ}۬Mڹ`ۻ-=tSʺ=]}؝N]ܿݺ=m^muɭ=nɽUض ߙߚmMyTmS 9}RF^G %2(4M ]".g⳹/ 1^@.( MݼW& 0*Ԭ/~ApLLH>b*) ಝ,,dfLźMݔz[Etk$z^m9nOYb*y/PY,~!Ob-U*3MO K#&!u͏aYͻn :!.Oj>R;#N@ɹ)]UNE?r^霼/=.p92 o_  /YB9ߍ<1_#b.-R DJ٬w9NO^OKϿNЄRzb>'Rg?iV~=voG^;Re$Ds.N>t]z{~q?3ڊnQ;M婾}#J?g/ =HL!}'ߜ\_xo[Y!пԯSs\%l2,oW@@ DXp  | DZE;z1dDK~H-?N\%˗3iޔYSgȜ8c 3Н=}=ZTRFc&Ujӧ@^M)U[Zװd͎kӳjˢu6.\Sޝ0ڢrŲ` 6{x/Nń:^`x-KEd3w oc=G>-'Ԫ[Kf쩬_ˎ۵nعQ o·K[]oN4ȥs^ݺvηc_>g==y죛o}ᗗ\Gϴ6 0+./{P>4B! %̐CA mr0:1;\QD A{+2=$1tܰEfK|GyQqIƐMD '\ ,In4RE"arH+{+0YJ-l'/1IJΆ\Έ PT3-4PEL3F3<\"A!A#JtM{PC<)KLU7Y.AG/LA*J/^sJ]eQ5H-EuXLTO:VZ`lyW\=i\]_/UwPX_MVTlTf MكV"j Om]]/5~Ͷ܀]XXeyE3^9W_V[r`AR7]kaOՍdvD39zug 5%4W7e6"kXf-V zc^lx-ho&w镑7~5e~߶ߜ벽&^E;m\$I;eQ^\r˝>yfv[%u+&tc{?qMWS7pړ[1tg}}wk_y 0yOz۾c'÷>z}?[訅Z_S_Z=@n%?8n y@.x/c ؐĀ#9!H.RKB %w:ػ9~4! YK(b0ћ#A9NrӜDg: J6r\Rg]NSҝ&ժ<)L9ήWkX*@rBHK:%3əVR4D<ֺQhEzwE\acY󞆅,P׋D;7r4'bھkNueS:=,C+8Y k][2&mOy )n# hr{z3˅p]ҟn@'[w^nbSy;Mdxmfz K' .,0~! ڏ]_EFX{ =b5Fd%mjBޒP\a-bV^ǵ a&M*'d\щ;E8r},{ %LCjardW=G)aH/q<4xj/gُ3+,C.%=f|f%ڂL]!_arK]kBCJWկ>p7vҰ2/-e[Yz|^h}4=Bud:65h{~L+w=mrڼ3J61tw;Ӄf6wo=l: nuK\ܘ&mFmqP7n=o<e3pkvyՓ4u/FmMGE>ucv}H'v;o uDeg+d8ĩr<$b"5iOΝ.yS}JɾKG˕y},fךq_=.tr[]gx>"Rz?"*?|߷W5;ѝ3[=;|[8PgS^'khdL˛0as=>>n'[c=>l*@@QTDKtE3tB 8Q K5SDAJWB)DE#|į@,S$ FFE4AR6?Ջ1hSZ;5Z"$`,E_Bp,D ŝ2 Hz]de\VtDM)At *=n==cKLGVDaEAۣJ#"ʣD?GEGqHrGIG(<&dLjȄpǂ4č;Cl'IDLIbIsŝDIIɓd0o-,L,Ix$J|J0d,TJ_,2ITMIHCgHA7@MNSx 7G}N~ Ph}8-pquP4S-X`EXmR:}ևeX%~EWpX֦Dы/X5HTW2X52T+X7-ٗM؏ӝ-՝yXeҢ3YZ!ZEڨ=ښXYyYYXMeMڡړڭ5[OwmαuڲuسٴAZPlZ:zZ5ڞMکۿ[[mXX \Za%P[mՔE\U[yTܸ\Ymܹ[[ ]uֵYЭ\eYb\}]M=Teݛ5}Rەح=\t 9 ]\-]]e\*}@^M]]M]޵iʁ] ^UZ !ޒ pӍ\9]υu`QPeQ] u~e^`Ma VE=n1Λ==X ^a &`6`uH͜#&aam|bmݽ5]+\eb*6\.a/]1_eߕbEc}(S-X6.c9]<V/^|],c?c2+0U*wK6:c>`LNэ?9K2ڲnbDfc:ZFβ[?3kwHP3> G뤹ġ]Ved@>>?G!;#RuGb_?m?,f]e d)'W=THCgP1"pKS 5e?PHcL>l6g{n>~VzH ۰=Az4f+d=Xd;Yqyuĥt.тhNmT{If3dCFc~TW.?4Қ"2.e(d>Wvs^TdEbN{H~vi 9&bg`Erz|B.nkTi׬'[jbP~¹ktz C&"K 6l12ĶVVM'Ǧި޲slfJۻEMfvmʆl6.^rXvlTC#n˾k~.u>\d~Tܾm֠fJIgєdn~^r^nmmk&n"vb]m6pN$jN.no~m 7iwmHi+p 丹Lq o OnYt]qZ G]k[(r'Uףq%r[5f ۖjq@*G-jan_rlrX(.]x*sxݸBYs8gA_Mq:ψ7ssXsP\hsĈwnpn[V.J>_=*Qg S/QsR?IZV/PouRo(Pj6S% u`^sZua/dWv@sN/bCwݑuQtT`vD_^7 Zv[cIguWtr?uu]s]:_vh@,v4r}w_oGuooUuIG/us=oxvwxhu{xVtOGyWWy'?-_/l_y6;gxa'yrxusxub'?y|zvgwc2g QOGx_qo`7 EGGz~7yewWxnZKiBGƘ{s\wu`uXuT?z|awWwg|/Og>|vp6xڽu`xMğ+ &ԡ|0ό1&n|͍_|B~~~~];}j}ojtŷg,H`A & Å%BÌ7^GA qdI'Ud˘0?ʇ6si'РB sO3klQT*eZjϥHbM+׬ kU٪eѲmHrMZ1t/y..a\X㷒'#|1ft7s;6B-Nhzrq6[ԃ[]{.ӯ4_???c7 zyA1G FxU߅M8]!iRs-XkG!h.vHև b8x"HT&Ҹ@xP5FIf=U2٤uiG)jim)iFvb7zgN*x\zt(:(E)y$e顖 K!z*΁Ԃ%:+z+(KQzi {JO +egɪvMݲz{k`)" (f B[^j[)1Srn[Zu6LΊ|p&jYu۲1Ζ8L>+9t2j4;ٳ?tC\4%wK}}L:ڏlrsl7` 2-8d]w-ߊt?S9f{\ׄ#֥oNz}x=n:~:P~{i7{+Gk+tf9Df1LDz򘥜&8!]+9Pfۤ%7oK_2k(Iͱ ;Imfҟg8Kis ;=Q\&zʋ3BQCnh5/Q9ΒʃAhJcJR4uJ&S<-Ia Q-AC* t檗V3*QөM0EOՔtD4HIϊP0/:$jUⴥ]ZVo5aM Un]Y ЯzER䗴^{*f9:Եu"oEfzNMkPrdeQ`3UV+^E6a$5[j6-qkqp5ao[\*7m>wmw۸=/zӫ}/|+ҷӠ/,>0uƻ&~0#, SZ| s0C, Fo1S.~WvӸ61s'm:1,!11%35Ő)SVV2e-s^2,1 #K+VjwɽNv.-j3b\{gB'̓6j{]4,rUTLu~3r iGєK3j`G-6Y(v\_@;Y{jӲu\ :ЏI-&z'q,Igڴ5-b״7[1_L6Xk{u7EJ|iN[]t~td6QMP{߷^7]폷{3xt/-/y=l\ߡ~8X>9Ѓ. ʎ9a|3w;:g`9V:k&^:ػ5KyfQzӮ*4S.;)g+Fvާ@;ωt1aJޝ薿<3w*VgPC'}w|w}c{.#U<ߺ-r/~΍mۯ ~'/qت5h]nGW84E{g9WVmw?s='>O?0^W80Iކ` g om ͐IIϨN d_A! Q`[ߤ ߶\ U_Dd`vೕSaUEkP__`%ɤ [,ʦ XcAzGR݄i jaǽ __Ia"~ z MAJvYϕdޟ!rb!&H )))(")>a"+Ƣ,΢),bυTޛG"n"-*"."t#36#"29.b'VG0Zcw3>cE#6z7c+-J./ [#c+B 2r=3>-?Uڠ?NcU# "clAJc>c,&㞐>2#4F:AO6^GN Cr(>d8b8V9^$+Jh<^c'&"0>v#IIv"Ed:KOA&FU/*""(ޤO.#O:JcF~Pa-URzτ$8r%Rb&bKm%WѤ0!6$\"$ ٭eV![2GʥB%7ڥ*/e\e!e<*;.fd6&c&b$^R^Q"Qbd>ea6e&&LZz^fF$Qdf#&krV_^#mgnfg}$HfZ&ep*j:bWޥp&suefg>m&nzge6'rJt6`"`zv'yVvgq{'h&x}ҡqxb~'~%uLʧ2fml.hv&B`gyZhnnj&oV (p.~sjc^+$,whxBZgJ(?̬D]&҈g'hr2h|vN:(I(h}r.b꧔ަ E 1Aq@5riAuVAaRaNhii*D-@@Ғf~)jg5nR1QR&)zēʓJ*&Ybi*~*i.21=75VD0@%j*ڪf):Rj71)-*jihre(bS7jCpFDYjjz*k[NhMn i&q"ꡊG 鳲)czzf+&l+},kkjlb (hڨkrfEXb'V,dި\^pU鼾i6k>,_>Ūeڂ d Z XF@rl*~bJ'ې-ֲal%- B̶N:m0\`U\EъFDo ,ž-Ϊ-z^V& VfBlެ*~`?6aihI8mJJ⫯jDkU!FVnlRʶ+zrv&*lښn2ԫg঍m 鞫S`"@ /"^4 ]/no.J/ro*pZ&h@ 0_0zNg*Sh_oTPFcym./,   0 bo{aFn.p 0~.끎p0OpK_qcq1HJoȈ?1ksp/p/?orpӆ0rq260"kq{0 Croźqek#O(.o$r q$2wl/+1K2./p #cr0 2#-q"W&l)w)0s/'/4sr>3液53Xa2/39'"C5 s;.sctJgAUk>W^4ILt9':4NX*{F{=4!52Rg4U#LSV 騷r,i+阢5Y)Hҏ2t,<u]?GozS@R2aR[\\5^C5Xwuѣӟ v(a:&vcT ]5d0l_v;eR6`{kNKKmuSvERRe^`;7ggCFcsWCwI5@uaCUe)r2cbvts۴d'vb bǷWOwTvQN# HJxyӷswi;}Wu@wwt482n?yvvzv5mG4L/+VxWm. r 7]8g8w4ת_z2CiSs![U3|v|7tQ`d!9H3O4DxkcJKxskynsysKw熠8)3GE׹8u/!ADStSOBˏ[+Ztmx-oMp^ު9{z7yzT ׃G::kaC1Y, ;c]CWO;|%{W]CȢ9fسX'{{;w{[z3c9cyǴ79z;|;z;Krx;7G;k<|;G6[Zׂ :_|s[yH ڷ[oV~"sqܟeWScoă}GWн; 7^#mНz?9Jӿ^@8`A&D!% >l(1"D-fF=v rE/Ty%/aƄyp$D7qԉdȕ6{hQe&UiSOF:jUWfezkׄ?QPe j]jpg[oPm]wջo_U(l* VJncd!%Rpe1gּ]˟)P K4NLEjІh>)QSASQc$M[/ԱY|TDclaq@WX5Vu#g%SVcAS]笶fz@`7n/UCOUUW-6pمMvx-^s]5݀ku_"j1"ޓuPecn7x``DSQw]5MubmQScAeLE8a3>g&YDEu^`(攛fg_ڭZ[[׶PsWFS%(RVgYlpfo Go oz{'e/FsÕ[iGW<%dzi kimU0Ovh?GJ>:EkᅇRωr*/]xnQ⠏w +'&>yVw8Kv4GTwzw<*| h!f-#P"76Kx'm0q ] IL YP+s =# y= z0dݯ}ov:aUw'Žx7{C/{ᴚ6۱0zhES!)~Y$HEd΁%\bǜNg Cqyġ!XG&Rh[1Kzld&>ILd+[Y:R&aJZҕ$y KM6r4,?i>qE$R,k5hs#+wy,S^I˗Cb<8#)pzPZH3g9Sy_STPfg?zK\ % kaC{Ҝ?gC)Ș Tj!\ QY$-OeNZub4UbJS8ӋG㦰Iτʔ ՐFdGJB5\U+BHb鶾"խ0;Jو;]5D] 4\+ֳ*{Qq* q;J^#ƿy}+ ?.c;&Nz*[+Ƃ-J(ԪmMd:j1DlZ>K)x+-b/ۛ/F̖qMВjnʻC s@+KJث `)1a oAb%6Qb-vacϘ5qc=d!E6򑑜d%/Mve)OU򕱜e-o]f1e6ќf5mvg9ϙug=}#;Bio-Graphics-2.37/t/data/t2/version5.png000444001750001750 2407112165075746 20052 0ustar00lsteinlstein000000000000PNG  IHDRXJ @PLTE{h޸_k/dp֊+pؿd/OOp@cGz2ͅ?ޭH=""piiipe[QG=2k#(Uiݠݸ jZҴܐ.W\\ EE<ڥ wFfͪ**r̀""i22p zAiڹKz|Ԛ2PUk/H̘հ (2=GQ`"X  S*ϱ~1K>Knz%Bos,!bj`byC!+0C!eX7^O{,ޙt).VTLc`}( nQ5X_nnnnnncQ7`Q,_O*doljC`IDnnnnn(e:4'+t:3$$<0KnRL,I1d޴+c?R '~~-bi~?q_ر INFq`مQr( Mǚ`e0] 3X&Stλ٤.!슙fA2q.oc cR|(4jQN|~&* qNFi9,_۱Bܾl:1ʸ{5XZ1&wl_PǠ._k`+Ŝ"2lR+a)^]\q;$j VЮ]zX+&q+,1XNX눿ܩ\¯3DX1Gk\c`W*9:֫ Bv`.t2db)lC!.XBt5;+vy5eLNafv׾L &X'0 3O$`D&a 2,4`$Z$"tyfp<Yjwwo[D`8YW6S塰*<"XZ`Uo|Uv& [*1jיc*3U\g V ;:&X._ Qc*@q ?0Ma?>epE&a[&[*28L7Ljbq-+IvI~)>x(O'$|C ww@$X?U\g Vu<+mqfcՎב{zh_^ `R|moƊ`X%kv( m%uXt\`z&~mo.+ӌqHN?f8^Y *@Ihc%o $1WXL k6~8>$$<0KnRL,I1 3O&$<0KnRL,I1 3O&$<0KnRL,I1 3O&$<0KnRL,I1 3O&$<^e;׭j>oX>X3Ok9?{'?Ks,(lӏM3礼>ۛ|gdXÄ)©bJUXX3^hbq2Cc4<^Phܓa.eXN+ ->׆C!{b*o*1j[+8ܛR y d8;@m5Ed@E%+aȾYhיIGZ+3, " K'Ⱥ9pT/'k`ұk,R ~nY`\ </P6,8WNcuj(ն"dQ.H&dνe Β ve/˪4ԁege)~Ą TLWUȲܕ,f ^n Xݿ"9x/_>oߙh(fHd%!XL!oI qRڗUL2~WԘHd|+/-@|umITLd1+` JIlUÒd VMHs˿βUbaWJ${{e؎`鱪-X,ʄԁXOnx0uNܰBT7o2$5#Wke ,/vA; wM86}{6qx1n-mUgY:qW{mjjXȢu\gIIy%7)&a ܤ'Xrbf`MIy%7)&a ܤ'Xrbf`MIy%7)&a ܤ'Xrbf`MIy%7)&a ܤ'Xrbf`MIy%7)&a ܤ'XrbWF38}(),IX+KI"X/KZ_#ow:EMEr`(G8οI^zÏ^I6V|>p=o,f)b X#X5X.X.<+zS^B7S\sǚ󉫘6V֐d`͇=rC{V+""s(GdX.b `ɿ:b+k>y(_c`MIy%7)&a ܤ'Xr?dxoaV)ֱb9/X 4bd_DOIw~g~'3|)aϢ?^W kjXxҰkoZ+~vNw`Krϱv̋slz%On Xm7Xx8$ݲVrpz-~]KīE9~:i}bh=xmXqr2*ϱ&9KnRLB旁jp%vZhRߋ=0Qơ}Albo3F `}4>4! t9uX~{.?"BA%"heώʓ'Z^Wv,ʡ(_Mw[-K un5^F,bq))=gX{,xOXpy ԫj3Ƃ`,[ᙤkWuI`{zG/1;jיgbf`MIy%7)&a ܤn0y1ܼ֙` N&$/घN7,4aiX*b:Ǹ+'hpE9\[>#G4&ta,ޞ[shxZ&tX{Eg֤΁e2`efq7 Ҙ92 Í?sE`OwI6O&D:~?X-TULU,QI17 7S'%]=j ')&aH1*,,I1=g=="ORLu@'),),eI1,mL{b"Xۘ`m bfVq9&X:sLZu`EWbघk){%7O@}P+)gn)8H't8KcRL-2C!ڒbf~,I1 3O&$<Wlk'AYVa-~rm+Le"v䶟y]-H0X 2UISjZ|zHEH$XGb;KmtV N'Icm. E*0,U~tҪqLR1} VVq9&X:sLZu`1jיc*3U\g VVq9&X:sLZu`1jיc*3U\g V jO&Xe*rw/QHfO`ؗ|͗ ~3X''kI־D4198ZA `eg[(H2}IwU܌؁}jc!-+*$=p(*1V&0my&j0:n 17:RR(.U))wҊ()&a ܤi3u?}k۩ˤo9AmBnJ,b:`揺3w36i 3sky a^oYFޯ.7Xߩ,>'9ܱE ߂ElZ;w+Pf oUgl Rn&r]Mh!5^eX'wa'X ,X`ŇBPV,ъMBEoP8ğ)5z:1XC CСaurzhX=Vz<21XLo{MmF_%kژ`ŋ:`q9&X:sLZu`1jיc5POKT1mLG6,7?nƘg,I1j acH u&tr kLJI {(zMRLwerB޵&tyc+ydxH]I;iRS1} Vw{zV;kQ~ mD'C=d2;[.JJBVӫE/,@-|LoWl7}l{o[4`]{(<\G$ Xt "X'o|G/tҪqLR1} VVq9&X:sLZu`1jיc*3U\g VVq9&X:sLZu`1jיc*3U\g~^^1}Gg`1) ֏,SI3q՚ǾV[ܫwVLc`BQ761ULc` SK\zWLcU`ÝX<vZѕB&EdXwKz,w(8xgRX!Ѝ6O&4aKnRL,I1 3O&$<^Nv;}g^tS1}SY~)?FfKnRHΦP2O9V<[ҡM9VI'$XLz 㘊C>*ϱtso{>;{,&=Vt=TڸI%<0Knj3X`P`Q`Q`Q`Q`Q`Q`Q,BT $a6t&$<0KnRL,I16aGj~6[>Z-XD[UULc` [ Gwg[UULc`ٛ+ CaI1mefŤXD#+YRLMsɷBG[]Q1}ՂnpK ;*ϱV:>U\gR iK(LEEEEEEEjI X3DR}IIy%7)&a ܤ'Xrb<4lXiN`ty{JL[yFe$t>|=TTU1}kB⎭-"=}Lfu=X-,#v:}A6&XH`8Lw5+ގD=w~=r2c,$!"X,U`8LU:Z] lzp۸ WLc*3U\g VVq9&X:sLZu`1jיc*3U\g VVq9&X:sLZu`1jיw,|>X:X m,IϤqQFTLc`YrOr{' ĩ>rBF׍o=c*Û*ϱtА2~`1`oo叝 +ϱpGl=Ҋs,aǃwzhI1]c'tY~XoOIy%7)&a ܤ'Xrb:MtO&b ۸#E I2ų3ߺ,v!ēPʩ6~X~ IY MĄ|=Xtom>+鱢3=zIRL X~<G{}BwI1`a}_&b ۸{L7tom>Y*m+?S+W`ƯO1X/`&<"Xʒb:6&X=I1`tCI1 3DR} VVq9&X:sLZu`1jיc*3U\g VVq9&X:sLZu`1jיc*3U\gU;+3)X)29fI1SK*ϱp>t0f'FavZ_bƉ2iRI^ZdN5Xx*ʼn0MZ-䊆!37Ή0;NZ쉇Ղc'V X\BEjXoOIy%7)&a ,IDATܤ'Xr˄ɖL8f=3+ϱt~ `x·꫘> S,3 9qc1)9`1i @h8"s,,;[ptVb`ES1}%<0'-Meaf~c5H@uu5p tM]εC`]O7uM] jcՍi+{ 8ƺS\ۨ_wM3Bqy } 20 3DR} VVq9&X:s,%>}nn}QU1}/Hj$/sæ_S0rEgM6-j&}/J2Ϥa=vWoݙn;ΉE=om6`|e3B+Ht<~ՇBwak XK:ef XWX+;R3 Iк,{}D0rwKsR aanp1ѽTL_?Vt!i[N7XIII^oKz`#VtQpRwjHKQXj z-"-ul,XDD6=V{+ z,ju5~=O^70"z_u,!NoxrI*ULRwVnk^[1[jp}~; Te"Xx~XkksuhA*VHb]< fVq9&X:sLZu` 8|0 V "Xi&a%u'?7W~DLz'dsa u\ JeaMIIꓹ=LZ㇓bf]Ҹ_&^-I1 3 1jw]0zK(xAj'\'54e 88bd#IV4b;ISeZul%dq.Lm5o-R()m<)ZN},9%K+?XWsU6~$ N¾~+tOeǓٸLq"X=9~&)EQEQEQEQEQEQEQEQEQEQEQM?ϞpF+IENDB`Bio-Graphics-2.37/t/data/t2/version18.png000444001750001750 2370512165075746 20141 0ustar00lsteinlstein000000000000PNG  IHDRCCPLTEQͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ Fo 趾rU_W sI $UjT A@R5HI $UjT A@R5HI yoNLlkX@. `"q2! 0J/ `"I?vPiyߗvc L$cy(4ul0$B".R`D2\6 <4&cJ/l9앐jXHL@E-A@R5 )$UjT A@R5HrTM\WL䤀H\7 XPUljT A@R5HCX&/4$A:p1 06LM(֟Ù}0,_5얷 ]@V|[g63ux:_ubh_}FZt&^ke;1~Cmfp3ppi .f`V&l(!̩uE@X>TqWӸ0.3:``T0jl]dj0<@VsDOD-4z+A@`baA@`ϻp&أ%4){|Zhɀma" hN/Ӊʴ[8L^`YSڼ_}5 i@[{Ug-Lz8L UlAȘ0}n_a X&Ҁ 06L/ O> 0bsNDUo]a߄KsDm.܊YsR<]¶IDLg ڜK59)KW#p`_]eO #0"`v0D!`NavY ,ksD ?C@`D礕C=d?Nd$̴qs͓o)c zOmifS}=gq `lZ<-0xk^@\3e X nuA@`l"f=}bpӟr GŦ~d <{vpv<㎀;ܗ;7,jV?/s NlG۲~DwU(?䶒*^,3x&82=-Yz'o06"=+=ŒDس0& 06L! `" MA@`l"C@`D&D0֠g.`&pbܫv˜>qkXHB@>jT/pZk) `"Mk}/9Y;~?}L"0=Z?;pr+Z5Ldor&lA6 VJ@ݵZ?`Z@`0+'^sDXwS`D״ #5m0!}&;[~8 [<\)s#Q "ЖPtf_\ Y{\LoK%[ OU㥒_Cgh*w3=ĬrI|^*vjoN Lv̟8] lkL x v9#౼B@=Sa̭0L_(5X~m:ID>FV&9"`[-5wf| *j 3; {+ݶvO|;qQR2p hcԺlYn7"? >ޟ{sK;Q0`NI 1C0K-x{/Tz R``j.^ND{&D0& 06L! `" MA@`l"C@`D&D0֩ft$V]rV&~FT1g$5KEj&'G[Dnu>|ЏCF9`"Yiy FkWN`Mh>KBkXHFŻi &{o ds0&4LdAAKgNtb?5LdW˜Œƅp-&?\kO+?HEod &߽ba* 9K֎z~LܰʽSGڜ K%kV@jrϰ_fb~^;_sQydODқ5ϖGk';(0cMF ln&x& vsT@TA˟+d B[O؃9q-YX0+!a XaRT2g 5q,gQS0e]O0B.OJYo0J.R|5]ZsU1wֶ_g߹doΣ썀'FZe9Y&T:UѶML9Ϙ`Oֆe{B %v\n>)`0(e-m\K:"˔> Bޟ/إ=!`2? 9D 06L! `" Ӿ|Ľ_,C,<m5 #`鷀fe ܴ!WӸ0;TlVwP|l`]\MDnP;i+Q:wP0j&ro O]o_rph5mi;<&iRe/})T{< ^>F6jPIUl!]D8Y>gQB;*YBFY'kn\'Œƅ a{KLavny}5 U}ڧL=j&WJ:L uHM#`0T#DM0Nä/ak&ԉ>fp4.L+!D0Tyvi\H}e7m "0`Ms-Dh5 yR@{w}psN7MCD0w#b֗M@0Gvxz-/A79WӸ0g75*wƅ<*l& i/ƅ< Vk=9j&` V&>`5Lecƅ< NC+ytV&҂ڪ9`E`"-x 4.L!`5L# a4r"40& 06Lûa족J@8j&ҤIk4.Lu0ǃ4 ߃j%Xi\H=a߹GK,/iB@s2&LtI_BGK,i@@`SD&R_dwķjW#^0~Qbkpgr$N~piMGnʬ9)j.ra$"` &ү6R9i`gJv<Ո lk\- `r `9,4 \= KJW#_sD&D0& 06L! `" MA@`l"C@`D&p) <]3z+I{B8{X@}PA6^sD6пթ&sV&lFSۭz+vV&rA@PϛkX9x M0,Ȟ i{bCB&D06kw\P6(Ա ~oډa=;,Y#^+xTK/O;2 P4TؠL:15&xm2ܧ X_jN#kC=Za"e[ekP@ ˙mʼpS_ jʗhqb,QϷ./ ^w̴-\`\,L&^ N@;?Aj[@(UW֯^[7z^lT՛nR!ޝGsvV&RS@PqWӸ0.FPhRg1l3K-aNMt( os 9W0 q%S%iGV_PŏhX@[~^]J+/ޞJKmSK7K1>c-YxwlUb.-ꝟ7K-๒uxɎf/ZWpҕ4wՔKqWrdZ/ٕD4`"l$ߟwo;;`ovڷvSm'؟N#ۮwoQ/]2<įލqa"Eeiߟ&V]oy6~^ϴ凉2n>+~vV@^jĐy"G`3ڶFj_xo6fwVfݿu|"C0m6]WB;n/Fԁʹo 4SEq;Q:hM@&S.㗀n!=!-&[b|뤀M!\8+!JNmAv+AOewF͍@^.< =Z2`[H+nZa.LMA@`l"C@`DZ0W8j&Ҥ5)VӸ0TJ.8L|F{%a" 4x kfjXH}'K#0&TM0!cD `q`"C"iP=9재%X0t5"5L! `" MA@`l"C@`D&D0& 06L5,N_Dտ{V&u DAfD~y/ {W1I+lI3YF9`E`"Ƀju-`&!̪i(l0~ &I>{Ħ令9`E`"WBl+baɹg:, `"\ &BaS_ڟc6`^@s.d'++I~-}^] ݀X0mA\~F@` V5+I)^ )kXH(X[ף`m?Z@`Y/.sB@`/L! `"R !^? $UjT A@R5H&_@BH\L! `"+;F}w<3]"0+-P"zwYsD.n|9+mڮJŗM0TOS %t"NZzu@;1<$ -`5L}Т.vvo4*3g[ `D0 C[`"\0ܩ4} ~F}0YX&IA$ op": ;Hq{pbhǩ9`E`" MA@`l"C@`D&D0& 06L! `" MEσۙ19`E`"q?jN y Tq0ӴC"0ilBghFky#Y/? f&6}x0Z_sD!VvVOe~jDy+;k|C"00 )v|Ҵ. X0* - X`D M`75LOB75L6mNvO+w5 )*r[{k'XN'?õ2XY2F_p 5_@+v^@мU[XL$~q#UoƟߨŀ x0SѶ% > ;tcI) `"ApzWyG9`E`" h}Lvn3$\6eaZ@`hW5P/) `"U@DnUL$qQ6- O(ExD4~a" %QU 3jjfV ^:b?:CJ T>i:lʷb0/y >dZ~i oި\|Ostlͳ'Wu }0'Kjf}^OnOS Uω\I%T#Tڽ5㟯jX AkiM!i{]\Vk˂Ot\sDց]ՃVИ^רV㯰mEO6)jX!gѨF~Pל[raШyEK+~RX- ?rq}_ sVCݟ4jmt-7L{~ШՃVk&D06PanewoeTa.B@ կȐ:1Tp u-=kC_-lZ+eƝ3s}֭]7.`lȹT:"JϦU<'a2ל{( =zݒՄ< ~}ҭv}u vr}0Lc=E4m\-{v?]XCN~&;,$,㎻b0kN Y1vr!nO0ͩ XN/v˜ߪk[-b5w%^.\&Vz'o׿KXv4Lt݅ >͇pB]/*bjD녡l݉6r\opyy0C@`D&`Ca*v*vsY^ A9Ԧ#P {7n׎K4L`v)9>o `,x b䇀'`"6 ~tBL 1" n`" {mld߅ uCڰm[s`ߩ({YKo<ۻ3]7[@8"[/waљ}>PAVnۻᒕoOh%a|>}Z?]ٹBKT !06~)a" MA@`l"HT@B A@R5HI $UjT A@R5HI $UjT A@R5/ gT8,IENDB`Bio-Graphics-2.37/t/data/t2/version1.png000444001750001750 2406612165075746 20052 0ustar00lsteinlstein000000000000PNG  IHDRXJ @PLTE{hpAiecG[QG=2@(pǪqxPȤ EtPȤz1Ǎw&]a ˁU?X6XD[T ֗E[D[D[D[D[D[XM"X-*WS -Y[4'iq"XGD[D[D[D[D[,Ju4 I:]3I1 3O&$<0KnRL77mwJƘiTeɄd:_qZ_mw@du!vlB㧓b QXva mf X8LW7 &ݭn:~6) q-$b&,jL ;5bXq MZ㧓b:㣉 C㧓b0QZ1}89@[h1D9z&?s~E9~Xv` KX'X7⡐IӸj`c2xw@LiS1} VkaKnRL,I1 3O&|x;*8÷E51o̿eLN33kE50b)A*i Y XOIHnꯙ2\1v2,WB-Pژ`Ik& mLd6~#*%Bn$$N3I1 3O&$<0KnRL,I1 3O&$<0KnRL,I1 3O&$<0KnRL,I1 3O&$<0KnRL,I1 3O&ru+zq9 a~b+<ʸ[|yD+;*ϱx&%0napRU9.'XX> =&=D `֢⡰Ӥ wOi>PǤ98sLZ *?)恇TC|#`:rGpm}m<`@ɊaX,o.Zuuч K2R .zՋct,ǚ>K:~,ߧyjWcm. `Әvq9~&)?j{f; }YDԸK>' s/|fYdd`˲*Mą1u`YCvJ;1a!ӕf`-,we/Y[ro`t6H˗õw&/J'*wIq=V՟7~L?N k!Kzj}~:`Rx˒V۝NiQGc`,iX?Jv9}woR짗`6ŹW*uw2O-\˽2Doʫ6%j % OޔW1m,|*U5;%X!m1xO\PޤՊ 'GG6?ཋXgN>Jiט'Xrbf`MIy%7)&a ܤy>+6bjUu,{K+5?`q=W2SݣbYcb nL:_Jxس(O'WcZ蹺*4ll-FqkRs]oe1?^ɓۣCaV[ +>Ilղ?^CD`R|(4jQNZ0xZ'z":Vs z<SE~Pej~9nnOj {z`KTRLCʼnwqI1=hwWO:xBEII'sL KpRLϙ?OrӃ`,PDI1`)K9KYRLKc,6&X[`t">U\g VVq9&Xay%8)'~^G't8a/))&+t_<J~q7,I1n>w3ҘS LP7gKpRL,I1 3O&t1I|vvUpXo`%|4S-7g^n`% VLU^-~` V+R{:]&IkgX `8L`Ua:*=mTLc*3U\g VVq9&X:sLZu`1jיc*3U\g VVq9&X:sLZu`1jיc*3|e9`1-`:. gͷULc`MBKCwN7k.UX{m-?-{I>4tokI:+L/3C̍mz KUJ򝾴b;JIy%7)&8wZ|OZuv2d}P[l+ ط1 Xk;o?9ƯM3}fwqw[pfgM:wj:wgloa>`79~Cgmn-:ll`uoc\WwȀ5uMWV` b-,6k |@+ o%X5y,9Krt[:g `MkN VP8lhP&t(vX+VEnp4̣} V6!S旃sIڷ6&X"N'%XA\g VVq9&X:sLZu` ;TULaA"*˸G͏1fAKpRL7{X?%BI1g~Gӻv=?J^ӝ`ܡwI1gޘJ$WNڸkTLc*3ǝǞ g:|[+}c;YlLNV>f|*1idՀj mC +[Mq;25{X Wh5  E*0h~o`B ы:*=mTLc*3U\g VVq9&X:sLZu`1jיc*3U\g VVq9&X:sLZu`1jיc-`}>ߺWLc`Qk$XLc&bL\&Fw|_/,X8X_cԍML}X:XC,sXXp?1'V8Et⡐I1 :ޙVtc,I1́z<0KnRL,I1 3O&}CbTLc`2~ַvʏ<)9vS1}tuS1}eI k8"s,,[IOnp6 b` 4O&$؏,k9bb C<=|6Q$t>%* +:i`Lh. s'*qO`'UUL>ڹck,f|Oq`_F&KY;`ND,uIE$DVe{oG`e"?U;FXK9X`DVe *J\ie6sY8mX+1jיc*3U\g VVq9&X:sLZu`1jיc*3U\g VVq9&X:sLZu`;|k>s,g6X$gj` K*ϱ,9O?'~e `ukTULc`C_71^M}X:XC`hH]?Dz77NX8X٣^V oKi90gh=xwdrd1b,?{'$<0KnRL,I1&:'efeP1mX"K$m u` ׿IoYf_;hI(TLa,?C̬&k'a37`6&Lzeɂe??IeULa,7 hNhb|[v6~XƁXd`$)&,?x=d>!֤P0/]1m=Xa&w6~X#WVmgyb ۘ`$D4>`̃U`)K8X{b"Xۘ`$D4ze9&X:sLZu`1jיc*3U\g VVq9&X:sLZu`1jיc*3U\g VVq9VVϤJ`eD='TN X/>D1NujE'WȤ!ZKmw'iz}j}9`'6i+Gϼz8'8iO'V ^Z5`s c=)&a ܤ1IDAT'Xrbf`MZ/&[ 3p>)e NLb+N4$aŤrSTh.Ť23XbR 㘊C>*ϱtoɷM[IOn? - Ky%7)&aoOZ2 ˔$kχ/k$$隺kfןnw6,"A]FWX`qu˹Q=x&fDAeafVq9&X:sLZuX+X+K|}c/rRb_~I_TM`*`Κl~[VL_4eIzߺ3ݰ3w{,IN8m؋38'gVL_?4 y@^t` ];fVw:.f`ܡu)X[aYss=>z)cf'{#>˩6~tCXnP[?0^AWߖtu:FH+$za: vZDZ U8oYDmB7& {:VXc "gk1{naD/EI/X1Bx.\5qzث?0KnRL,I1 3O&ty]Ê١iXd,O FB$@EdooVWQI4 EQEQEQEQEQEQEQEQEQEQEQMciQ^Shיgbf`M1O&OKc`x2Y9V/cǾ<``MZPhucM?P1}5DL-,r]TLcU`ك8x(dc`"x(dR\i;.0s,EA"X-KQ"X-"X-"X-"X-"X-"X-y,&,Ue)X,xՓ8t#V_"X-"X-"X-"X-"X-L:`$`E.Zu'Xrbf`MIy%7)̛;}cc̴G~2zdO2֯E8-/;} x:;6a!I1](,0JIX3L+akd{by7?Ӆ`ك8]1Ì`5V&{L{_&^-I1DX{!I1](>]k{;7UWmP_Q㝸c:uXwB+6 `{{`eSV&Wj:r{c`%,ū+ٗ$X[XS+#Xkհ;`d5.wew= k;U;Ku0u1晨+&hq~ϐr,WX%p3]V=z5"XN,`NVWV ?|v]>5Rb"X8]ug.,B ։8LW`D&a 2,4`$U\g5y%7)&a ܤ'Xrbjd>IMŏ` N L)lc594*ҔSN;}fiBUI1o~_EY {,MI1n~B` )`ܡwmI1oޘǚRB1P1} V0v$qe"X7eLN33kE50b)A*i Y XOIHnꯙ2\1v2,WB-Pژ`Ik& mLd6~#*%Bn$$N3I1 3O&$<0KnRL,I1 3O&$<0KnRL,I1 3O&$<0KnRL,I1 3O&$<0KnRL,I1 3O&ru+zq9 a~b+<ʸ[|yD+;*ϱx&%0napRU9.'XX> =&=D `֢⡰Ӥ wOi>PǤ98sLZ *?)恇TC|#`:rGpm}m<`@ɊaX,o.Zuuч K2R .zՋct,ǚ>K:~,ߧyjWcm. `Әvq9~&)?j{f; }YDԸK>' s/|fYdd`˲*Mą1u`YCvJ;1a!ӕf`-,we/Y[ro`t6H˗õw&/J'*wIq=V՟7~L?N k!Kzj}~:`Rx˒V۝NiQGc`,iX?Jv9}woR짗`6ŹW*uw2O-\˽2Doʫ6%j % OޔW1m,|*U5;%X!m1xO\PޤՊ 'GG6?ཋXgN>Jiט'Xrbf`MIy%7)&a ܤy>+6bjUu,{K+5?`q=W2SݣbYcb nL:_Jxس(O'WcZ蹺*4ll-FqkRs]oe1?^ɓۣCaV[ +>Ilղ?^CD`R|(4jQNZ0xZ'z":Vs z<SE~Pej~9nnOj {z`KTRLCʼnwqI1=hwWO:xBEII'sL KpRLϙ?OrӃ`,PDI1`)K9KYRLKc,6&X[`t">U\g VVq9&Xay%8)'~^G't8a/))&+t_<J~q7,I1n>w3ҘS LP7gKpRL,I1 3O&t1I|vvUpXo`%|4S-7g^n`% VLU^-~` V+R{:]&IkgX `8L`Ua:*=mTLc*3U\g VVq9&X:sLZu`1jיc*3U\g VVq9&X:sLZu`1jיc*3|e9`1-`:. gͷULc`MBKCwN7k.UX{m-?-{I>4tokI:+L/3C̍mz KUJ򝾴b;JIy%7)&8wZ|OZuv2d}P[l+ ط1 Xk;o?9ƯM3}fwqw[pfgM:wj:wgloa>`79~Cgmn-:ll`uoc\WwȀ5uMWV` b-,6k |@+ o%X5y,9Krt[:g `MkN VP8lhP&t(vX+VEnp4̣} V6!S旃sIڷ6&X"N'%XA\g VVq9&X:sLZu` ;TULaA"*˸G͏1fAKpRL7{X?%BI1g~Gӻv=?J^ӝ`ܡwI1gޘJ$WNڸkTLc*3ǝǞ g:|[+}c;YlLNV>f|*1idՀj mC +[Mq;25{X Wh5  E*0h~o`B ы:*=mTLc*3U\g VVq9&X:sLZu`1jיc*3U\g VVq9&X:sLZu`1jיc-`}>ߺWLc`Qk$XLc&bL\&Fw|_/,X8X_cԍML}X:XC,sXXp?1'V8Et⡐I1 :ޙVtc,I1́z<0KnRL,I1 3O&}CbTLc`2~ַvʏ<)9vS1}tuS1}eI k8"s,,[IOnp6 b` 4O&$؏,k9bb C<=|6Q$t>%* +:i`Lh. s'*qO`'UUL>ڹck,f|Oq`F&KY;`ND,uIE$DVe{oG`e"?U;FXK9X`DVe *J\ie6sY8mX+1jיc*3U\g VVq9&X:sLZu`1jיc*3U\g VVq9&X:sLZu`;|k>s,g6X$gj` K*ϱ,9O?'~e `ukTULc`C_71^M}X:XC`hH]?Dz77NX8X٣^V oKi90gh=xwdrd1b,?{'$<0KnRL,I1&:'efeP1mX"K$m u` ׿IoYf_;hI(TLa,?C̬&k'a37`6&Lzeɂe??IeULa,7 hNhb|[v6~XƁXd`$)&,?x=d>!֤P0/]1m=Xa&w6~X_^'Ҷi>ɜkTavZ ɣCg^=oavZۧ XOZܹޞ0KnRLPN?IDAT,I1 3O& -pzfWLc`2o'&IW1}eXgtsbS`)*Gs4Cb``1i)ЄqLE`!|X:Xv·[覭dŤ'N7؟݄Iπ%<0'-Meaf~c5H@uu5p tM]εC`]O7uM] jcՍi+{ 8ƺS\ۨ_wM3Bqy } 20 3DR} VVq9&X:s,%>}nn}QU1}/Hj$/sæ_S0rEgM6-j&}/J2Ϥa=vWoݙn;ΉE=om6`|e3B+Ht<~ՇBwak XK:ef XWX+;R3 Iк,{}D0rwKsR aanp1ѽTL_?Vt!i[N7XIII^oKz`#VtQpRwjHKQXj z-"-ul,XDD6=V{+ z,ju5~=O^70"z_u,!NoxrI*ULRwVnk^[1[jp}~; Te"Xx~XkksuhA*VHb]< fVq9&X:sLZu` 8|0 V "Xi&a%u'?7W~DLz'dsa u\ JeaMIIꓹ=LZ㇓bf]Ҹ_&^-I1 3 1jw]0zK(xAj'\'54e 88bd#IV4b;ISeZul%dq.Lm5o-R()m<)ZN},9%K+?XWsU6~$ N¾~+tOeǓٸLq"X=9~&)EQEQEQEQEQEQEQEQEQEQEQMۆ(FIENDB`Bio-Graphics-2.37/t/data/t2/version10.png000444001750001750 2336512165075746 20133 0ustar00lsteinlstein000000000000PNG  IHDRIPLTEQͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ //_! c`"c0ƃTʨjD*e0ƃdA_ڵ=x0U,BAa,%\0ƃt0k2{ -*mۀJ/n v *0A%A@4 $Mi4 A@4H〤q4MW6L䠀H7( zWYi4 A@4Hf@\9'/4$Qp'1U 0 {jĚ ͙=!`¨I\`Ebh-=GoO˸NL-kyYZӡ j}Xvbnj۪S@sOK^z]Jv;L>Wk}\ۿ'} L{ Ps ❐kvbjr;!ڣ;1- X"7 ]ǭ9`U`"W {Pڠ=؉nɖN/ٙGOֻsl%;3Tb' j.9t~psĿM@@v.nlؑqw2S9~gwFYq|&VN0 Oe|'?09E7e\6 #-` eۀ'ϕlD9ADdn"9ADBzz jD@ &x Lso"50̽Ek`"{D0!KC^A@`A0A@`A0A@`A0A@`A09,`oU$ܺ%"A@_jJx0-ݍ:?!Bbl5 Ldk=4ϧ2=m*x=l5 L$j/QF 3̻慉pX/n *9H:^FO0;(rl5 L@tQ;O9`U`"l{, `" }?2\d0.ta &ҁ;L h~D: E8Ӽ0\EWxH=]vkɀ%`" !`"]R`vB愉sj'$ևZ2` H{L! DzP.AzNզk5hGZo]a߄h)":7~~ *9)j.ra$"` &^SSmΩsl8xJsWtBL5X\2/qdXRcK}Uvn8PaA@`A0<’ܤd8Lo^=`KiEReĒ!(0u^ٳnM|)go9pn ` ,g+wqKv?L ޚ5P;!/ Y2 +bD@`D{L T,Nk9˦xdRcQGZ6Zឫ7 7/o.(Yff5"`$~ZslCۺ~DL(=䲒Olkwql(,?w4AXZVRn È}R)[f"^C@` 0 C@` 0 C@` 0 C@` <,kX, `"9 {u[!ːykXHF@n^eo.[ݻ&_n}l;M[7&Ul Rfk9`U`"[ॕVnJf[鷇GF;\`c>cpVO9`u`ꠀFoʿ-Uq {}="޸ԩa:, `^$)`R* - - ۀ xd3Zǻ V8!3炁uY h B X|yߐQEFc)!garL+-o]_Rf/##” B'dfL.`K U㩒]_Ch*w1}=,rI|^v*Էcb:s;S8/$D5z [(Zosp_ek/P9yq:s Ls%'JwV^26i#9OINX Z@3aKYoʼgqzen)o'N6J6y}5Unm/ V 6#g! ߷Aoİ/̜ 8znGXJll{1c#pR` 0z-o9aj&؃`" =&؃`" =&؃`" =&؃`" =&؃`"lPm/H'N~`5 L$)騶3єNkOdLU4KEj|(LdK@rl5Gk73T))ںTn2K#M)зO A4Z^g%~MUKw0y)ph}xg[?Ol-)`PU섬vYӉIÞZ;!N M99[É*0MñS1mݕw1 \00 HZ@W|Fi;Rɋ/,Yo7/niX{Φ+h#=߫_2X ?¡#mbFReĒu+ _ىzgukU+1nׁKv7L$ j(\sh5? ss0^0wzvl`"e`7 h* yc#[NHt2=;!|%Uxx&Rp&$n;+ `"6JHaF/t;ϧh\:w(ٙٯ `&.sZsUS0 7_f߿dfoΓvFuzLQſMtxm~rd_0Bֆj!`ܝ?26ˮ\)`Upb'WdfXdfXdfCbho"h`D{L~%|2xli^H5sɢ6׉M慉\)2w$Ҹ~vbnn4m&|'e,,\ ?b&r߱ɴOkߣN|V Q:_~`4/LЙ懆=ۗ #:7-0\f\F7R@pT e0 VA@%TUZ` c'd |,]3WvBJY;kn^'̻慉 iU\FHa6.}1 TU_8b&gJqQ/W'%=$%>3b&L@*{-/\X=b&`|*ج\/1Ӽ0;!NHY/-ya"7 yd05 L=E¨ξ慉ܻ*8UsDn0}5 L9`U`"=x l4/L!5 L=GajXH/=&؃`" *s#Ӽ0.t(̾慉t O[,#|0Ԯ7zD9m/6b&ҁ\-9Rvkɀ%`" !`"]R`vB愉OZ[K,@@` 0 H{sie ~Ws'`jS<_xZe{I-E͡L+"3k kNZ!AX7+`9UsN=ؑOv5"`j.gK6X2X&%`"x2`uRՈ*0A@`A0A@`A0A@`A0A@` SJUdL,t\*0U0zf:dNVsD-^ .5k*0b?of9`U`"t]D;!vJx0A@`3 hoZ1:w7XEepoB;1P%~0L} |q&RS#Wk;cۉHeZ sS0TܠPqF@{hk 7TWs*zb|hWު[Kv7Ln ,s t͡ v*7ל=5|V{',o՗^q}\`\(L*؞ N@;?B[@(UE+׉i~+-[-Utaj'ĭ;N*wt']Uж?f4/LSqp c)Y<2NKtm39\27Vܥƾdgfz?Y]5Փ^%cefici}悒Ue?O ౒mtd+R|hXVJTuA@`3 }Fxޥw{ۍa{2;1o0E#Eat)3_;[LD 蛗?Mο]oy6#~Zϸ凉2n:+~z V@3@/DjgwbJ&҅* f'dNH{.T_n-L= &؃`"-}VzNզxdaclc}K1 85E\g b֜L9Bn0i'_X'L9j`>T)֪ƭ!jMK0A@D: Fߪ+= KjW#N_sD{L! D{L! D{L! D{Lu,FOջqV&u D[LdLOpg{k?U}mLI*0NK tea)ЮfUt/0Z~ *A6Aj*x#jXHX`DJNٽ l<炁! D;!C_EnjXȃ+ L$ zydZ@Ma \v`Z3M@ L$#ɧ{[g9`U`"`m {4~n> N.SB@`6.L! D2rA<,B6i4 A@4HI ir "sZx09*Yv+薽{Ui_W޽5 L䄀%gZ]&)\|Ϭ& ~" tJvEؕdoCaa4;7vblBv3-uOk15 L$b{X@m L\@h9`U`"[;!߫`0w 8E< #vB̔R;!п`[K;%`°  xaaA@`3 hN69`U`"U*0*.V qqfr80.ya"UTnek~0s?˚V&R\RZ@a* ,f4/LFowV ۚV&RlOi'm;yni0KvBB .K'(ތ qnkXHUL8Li)m(̼i^M#?Q>7el0mjX*0,9`U`"W 9 0+&B X\sDU `qAV&h`A0A@`A0A@`A0A@`A0A@` X_ .pD)kXHR@87>U>-V+`̯j}q<+&r<qv^Z@VJ`"3{RF+x`5LTm°  3 C 저x0֡?pp^sD-ډl\f0{ !؃`" G ?eoҾu;Vp]_X Ax9:p%CQ`gݚu A""r/CgqKv;LK`8|W  `ۨ'jlO/0bW 0. H}2[Wf*)d%X"x uatX6ۼm]~EV\Vn’ lPXAfٹ{ڔkhwK x m@iۀ-`d?٩CKR!04~)a"I +HI i$Mi4 A@4HI i$Mi4 A@4HI?xWIENDB`Bio-Graphics-2.37/t/data/t2/version18.gif000444001750001750 5325712165075746 20127 0ustar00lsteinlstein000000000000GIF87aQͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ |܁!H] n6ހV_fR!qIb~bh$nh~~c(Fxa/r#8c#&)cDiEB"RRcVbiaHN7VeITX#jg#ZRnFdN& g^frf)&BY执I5 }N( 6\李h2v:2ڪBnffFJZI(ohbZ^쳜2{ɪ,x#@iv殷k㤓fun*z{oV ~*0ΒInv.>S11O<+nrޞL~ /+(pAsH2\nQGB ے<0<']rdGi^s]$ýFuR]Dq:5G=z\&ۅ#.7M1&+>r^G7C?ߜyƶ.n/o'G/Wog;G/o觯/o _FRL:7@p̠7z F(L Wp~ _H8̡Cp@ H"&:9DGG*ZX̢.{^ H2h 05eSf8McJgx46#GL" =>#'IJZ$|!0I (G9@MbK&?9V;,ꢃJ 򔬄.wKro\LfbzE2Ijڏs4#MmӚ 8чi&9vS崣Ub̧>hnQڧ@JЂ"@-:s( *9Zlwt֚9fΜE?'CCU:/ `6S )NCR2OXR Qԕfs`:}jKT洧鲒zѡf-l:SƴV)UcdR g\kZ:b\ַ25A` KWԬ5]k#d򱃍ZWjA[ X*UVjW̵Vt[W"! Z֭b-m\X’Ru$E.4&bmc[F4bͭY&[vt^"ָXETYI95Еx5{ޛWrRj\4׺oY{ T]hy#K4oXslsY.pV+\"tɉ;>u|[u-H&,xCjz|VӒdN} mDYnueqduh䝭ixmTsT qn6lEXȉ^Kb?Cuu.4ۭN{ݧiOZwEvs]_.|l7ߎw,?zaS8}q< 6>|9ҹn_;sB/}uͳ5t=Q+mSUve-Ϣ'w-|5˽>իg[x[V?;5{;~]?F~t~To_w~f6~svwq}W~'Qh4X 8d~yFHg'6q ho \"^GG{Lh}7~3W58y%Hd-xJE8gyM`ǁ QJ8T?mTǀ^ȃsBƆ(h&CV xUH;2(8ԅw~9Ȅ(lC DRAzxXc &,(|T 􉍨X{(aUHxPgd芾h6lvv(f8X:(؇Ob!QH8=8{XHh]8XrHmX8hȍ荬h؊fx`iI(ȋꨋ i﨑ȑ؎鸑ًؑ#yx"i0YِH+X,i c6Y]89-/AYُIOH)J:KM ]I;LCGEy~bG9VY2yW$Xٓyٗ9Uɖy_9rX~YII٘i[aɘ!阃 hyYQS9I)Qoi^Yy 3)uT՛ĉٜ\ɗ)y)I靸ə3YB G, hK(dYBfbg 9ɜ:%5f[Zy_R[f jYY9$i..V"A!~I*ٞ2zj5"J3ey7(ڠwCʢg1#Z3'z3PJsD*J:FJ)y*vTQeա"ڥ0J9YWzvC5ve-H*9+&w5_踥 L * q1:]xʛYzɥZ ک' 1cꚣɪLzzÚH'vbZv*I{ۉdu8 _瞶s:홭IveWSxu}ʨ*ZJc.uZOS*jMگʄ]DZczJ {ZƊ%Bjʱ{:+W.Zۭ%ښ˰ʳ=;~򮾒735T~*+(+2ס@ǚJ׺&ڰ.˴,MO{ *욁f:ĺRu[=nf$sn3{U:˯x 0 ;[|W gZB*ȱ+yw5{e{{Z[w6[ۍY[뵲U;t;3CˠI:+;K+J]m ደ  L۾aK ;\\˾ 85LW < gK kpeVRWWEGll'蛸/۵\h+X{l|=; M,Nk1G3lۼCOԪXJ= 9L]E#_ 3;W,Wb &V\m]׫#h}γv _ NnQ_LsnK?֠-}un.淽Ν9gָn珞X3C{فܦ<sm%y3̅Y57Ε,W.cL}x:n.6v>⮘ v>_xk/l oɮNƮw.#H^=/0,.mnFzMPcN,WY0SXOaώUYS߂cr̎|sort|_|}o?OO_n~_O_O/&ތx?ӿ/oO_I`'? ,PB :<Ȱ -^Ę=~RH%MDRJ-]SL5męSN 5C!FlhѥUTéI^ժV[veZlTcz}5lٴhɺ=kV[pm[w׺}Wl -7T"&/Ô ,ʙ'{|C>MџM^m5֩Ʀ]zCnj}7N y@ᐋ#+zA/)PBCK0D/H(9dZ0CAqm|?PEM.W/!DHEg섔Q*c\F'|rK+tK)I-TD2\/6J0Ρ#lH:n3d7Q1ePFL?stEcTO4 M/SHmSN'ŎJU@!9QUJrJZmh?R㕣t1zKNX\VU YK5usv[8}Xh˓UU : te] T^ \q=IPO][D{e\,>-Z~O)mG$hcx+u >6w]f~L!\*pm.ָK9d^[{:~Wm8: }o.V Niw;]oj eG=UueG]:`|ܿqtiu|!fXl6gŶ{; #W88{{.[o37}B qDnHɪYo7w /?E=SMx?C7z b[e6l7{NY^lՏZb⇾i;sb=Ii[)q LskvA!p+ Bت.ʢm (2k`xvA3XmFj|R!zhŧaI4R=9"šh ^ҟx56d~EkbC텵Bf@ gl)G _8##b8<;HM%d"IG>Z+dYxSMJc kA+R+1J]mH)3d/gh2p$f9Ӛ,&M`nx<澪Ld&8)s :)9rJl-YOkӝڄ9i JC2@SP{VT=(?R2f4'<)GT)F=ΗǦ4 TO9ғt,mL9L4ܩ ϟJr7> ԔN~[gRTFudYI֚FlYV וQBX1BxʕJ+NΛ*R+S>g-lGRիj_[~ȯRoհehg):6]k3۷V-mEV6onvnFi%\:WpGK\pֳ}u]vrwE{]>ULC[{\i߀궾=pl |67pu/lv»%ײ&x(p o ri`<oWw,aow.zI\^IK.9PFLeZXc VU1|b-O n,8_y+߬c33Xu_ hX?KR?F^4oyχ~I9`0}|v)slGjXYwiHymdYZܭj%s$E j?.g.<좋Zվ-g ^؄j`̞'gZؖWBsݺv n~ΏPoss]v^p:2\doi<Eg>y˝pal{xsMkM|󮴛~|fT5**[.)('T6|)؞o{׾9s/fm'u{PGҟcrbNsH=]Go=/MwOKцwog5^΁gmqc\M{gF7{91|kwK0+۩oj/d;nkԓ]f=ћ!VwmY߾/}Ї|ޘ?; ˶>Է?K+j,{k҃K1@[#I?S?5+@,T'!sA׃AA DAПI̿;A=K,!ϋA3 *sCÿ)4#03SbYs@9#D='B9BD@=>#HCѡ A A| c=+? ??tA$6 4C4A$T@a$+D|D=?"@LAP,BK9Wl4 U\>""!2l9aBXJV>>LBQABY;(D#EF'AԄSV!FFG2ZdT`RQM'4L}܅^*5b!sF]^MgV3,%vԎ_<_U^^ )ۻgۚmD:]Vj-X]y`, Tq5-EsC ^}`]n-Ւd-gɣBjTa aӵ[i%!UAڦ[m_&_US)_رIo%0]1&{SL)b c Nb]}{Qa%dFޱ-a}9ZVa5ceQFT=';KFeWd%S=]Z (L.MdR f`%V,)][d_Ȕ_^_je;fflUn6Z_ae^.M>g!_ueY6ezFgGe|gUf~gyfgNgxguVnh6aw.}v艞+]hf.hXhi־MY>iNit^ii~iihihXfggninjadj.jwh6(i~jܓd'ckjj&fdbMᰀfk͵ꡖ\pźk$v氲f>e~kLYy Yžƾjg4EnVl:gUnm?e_-*>n+lkiJrU)lFcN>jȖXj mn봖boPkϾNW|^>lmoNo߾ﬞo~o+O觓8@cO }lmɨ KT]vMW:HxqͺضgN TpI+؉Nߞݬc^Tm t$p'Y2rBrNV[)׭-O)_np~݆*P1w006Gfn`q+m8Gnr?ogf#Opp)0tîpCjB!oLWFMo;jnEug٩[ڢc_g5o6(qV⠔)yI"UUWbIjZ&X>Va啩YJ蚸*:k*j(g*[fFW¢ZPĨ XU휫]z̪E&*Ŋ)kibLNKmSIEОr;bzm|qV; zq:)J:0;%).󱷽|+ JlD Kɐ"]:s՛N`vnb.ѓN K6MY,_(+ ,n2˷]?8r5n`=k+]%<g#f@6)IRJSd)ȣ̑$%$cK拖Re"WIa2DAfJ);%눞gQR&mqte/eaV,4CM3לQ6b6 \=9}fq!8]hgʜ$#ZŞzFE(эސi +8(gfN:2W-՗Jc@cc˴^3SӝԦ+i<zS{^NPx&ը#KT.Y.Ԩ+ )*wO7 lL: tI\%ɫzzįrUZh[kVCs bW DZ=ͪeP\wYB^jTZ1QnhNN̕!sl\IkWNĥq ۣVs  k>W&ko;w7\xVfjk}7[|յ]+nzɎH.`[҅B,]uzA됸ovېcM-o낶>Xlu_ꭘM$g]vƔ5o:bwصoG#$!Έbx_/`dM^rsW(32{UZƖ/cxs!dµrg%9 NȈh-7_9b665O+ F p_;K8|ta*o8&GkaEy/5ЬiɾY&jWr%+BX<y~߽n3n˕!w* Vq;>9S.cGu%.\Dw̗n6zM)S|[v7x'}Ď> }`t>w ^l:w.;Nmk_ݐB:򄯼^xcwW+ m-j_o[/zCܬ!trZ\0O'y>oe^/%[ V+繗o?UO|+DWO1ќI_HMOE=ұ1EXLL_ `)͟.^` f MQR  J` a VVf`1RaV ffab_.a f a*aFa(LOT| OY %T! aRpTd`b(^ =):_$)FRPÔ%vԙ jT.m!>(Bn!:zb'bFeB!9)B}!pb?#9)0vp#M"8S)f:Qc=b.5"5JaI@$M L6IOV\IK$e^ޗ_Xcd]fcvffV&\Jz%a%KeT~&ebbl"&imd&dfn^&^^o¦hJn*qrqX:gsff&gd&nfaz'uj'pu vx"gSW'yffWv'wD'ag}2gz|wJgUngBgZh%{ˇpѧ]l>hr"{f@^U4)`h{*0%(|^|~%/e ݌'⨆>VgNI* V (QY肾h~@im~ɩl<␡F(百fM de(hN[)Ʊ) ݌ nP%fV6x(Ȩ (:c腞_F*6e!1#(ʒ&k.$ gڪ(ʪ?J!=)Ni:z&+~i~j˅*J*mhik궦' aZ" kbk|*NM ++b+DiTk":k) v Ś 8J樷 ~+&X֞,_ِz,J:,HUxU!?,6v,NkzGVZZNF-rlZkά*uC~ؖkm^lR--mZm".*~'R. .bƂE~.膮.閮.ꦮ.^d-AƮ.֮.B\.//%n.6>/FN/.^/fn/:v/N/o/֯/9Ũ/.O0'/07V.JƬά-+bpق7nn .z0N_nm6-f,Xp 0߲pS g #b:2)P /.ޚ pRlGn*^Yh onG.t\ 1sqpmKk'F ,'.,Ԣmh M$p{4#S!!q +q {$brAsrCݘM{K%3;*kr+r-"7M΋;%/g33߲!1 qr52pZ3w;-+"kn8ss9Q3 { 3&r4cs;k0|<>3??3$EA4B:s%4DGDB;:tMFo4Gˢ<,FKIG4IwtE20oIK@s'K״MIF޴O42P5RsP相J4TGTO5UCuL3A&P?s.W144OuNk5C%uW_tYuXS\[V5S73Jt0Eq]Y]Zj_D @ v[ī`iQ_su^J2vMXO["eE jưrf7t\5)1 jRl?v Q$E(%kG25\'/G6]6^xjJbvn6bwWQiwt UXwI0QzKwt6t=ڜJӌ ^hg{>EwZϷ}wQ{1sv}#-la5c}RLs7ֈ/xA j,_*$^vovr_ 7gxq++wj/y9CCz等;vc9}kyv`cT&H99Y\9繞T6 9﹟zc Kv!:KI/ ::gz#3:K;/.;wsZxzy9 :#4zy[9{yU:'Ц.7{#вkͦ X:`9l;yϭ;;) GLg{Kѻzck{{; ﺸ;;g|yx:9zNj/g1~:!Ԛ"9(yW|#79|s;{}3; ||g[xGy#9Ksz:|~ó~ӳ˽tsd{ԣK=}Oy~ky ~gϛ3S+҃~CS#;~_|{=;VO[~}~ӳ~Kޓ}_;5{}3e ceysf;~K{k c["?@tB`A LxaCTvb(1ĉ*̈ŠAjxbB!9L$ʔ'U$ʉ0UfF:c9S%N6gӝOF:jՈ?U4*R#&u(٣.U˵Xi ւn˾՛ܽg2߾tݮV!GZg6`Bnv#/5D=F{KrGϢ:q; {3D:ioEwpc$1 k!@D/El&jɉ(rJ朄CqT071ԱGЌQH4L sp3Ӽ61LC*|2' DL9Al$",I)IP24EaBaAe~4t?16tIA'] `keY|,,B/_u>ҭ~"}h?N"hsω,Ԭ (H1ܢ۪m< d,x X挰Wt,H11^ܤ 8, $y>)L͖ĝ]P"wd$/Xqj(]Qkk1ST%<3f'b~%7B38HMZHl5No9D'uiy14g3 oZ'AMħ2WP@B`('% nM0@R! ,PR)5/zNPz6MDdM-QzҖ.u/}gL Sk6T iUYTNUB%*GTkL5JUS:ͩ;SR\ Ω4mX + =ae֙^bӇ6֎ʫ=ieZ k`ZVĚUo=uZucd'JY^jg? Z*uMem׵m,M[[Z ^֟[]YDHmoY\j2WčiW>nd+^rV.YY]:c{7og{Zwf]w}j߮UU/j` .o6_ӆx$a_h~X'&pka {MzML26/qY4x:q!qFqp"<~~a,aZ#3Ytrf{\ך+3|g5[ϛ0zf,e4zn}pfg17ȋNtf;qliRtwXg:ˆrB QKZd5]LGzϞ&uch>Z*gݗkL/Xӫ3O]lW-kc|6y_k{&%ms ״v[eݽ5 o\˻p;w ;x{=(y*9F|yo .Y^+(wgUNwˢl'[ǽ@G:7OcO*-/0O<%эDTho {n&ʏM:pJHǮKJ.0 ٧i0) Ҿ΍ \ܨd*1 GP C йnQڨ(Ip p#P5p#M殄 PP pOPu~P!qP- p A[kc:0oP81DkQEQq kQoxm|,QqQ5 ,O[q2 r 2!uq&!!r r")"-"12#5r#9##$ #M$Q2%Ur%Y%]%$OG&ma2'ur'y'w&r&l('r)a)r!!QQ{k)){ +rE2q,+o,g1 r(A*E.mN.-..AQ7WQ0wQ0 /_ےr,+21R1S*2@V0 玀?*S--/s311(LJ0"px0FN2W[/S0IЅH}6eӕ0_PdsE)3źSȾ8s3p 0gvs(X(zPS53(893;6W=439ћQ4SR,Q0Dd),ֳ-Nh94252O5GEH=C_ 4c%);c; i;E5[T8 av G03#4qIe(t0IUL-qOKN邰gPM3\RQݐMTo UMHcQSQ+U%QRN4GrsZWuWyW+2Wr"~J"!Uo>WYY$ɭI\DK׀uu[YWYO[ǵ[uU(Vr#j=O)5,]#SUK=nuNy^=r*_=OQ%`et^4OUQTQR42!`4Vu.GU=.bbS;v8A f2WcMee cgSiekVe Vd+Qhb a TBViL3I3nf[aVHT^6E J!gkߐcwv74x7lk<(;[b漍o6jIdVqypGSoq |Hm'OB)hS6q9i')X0Aoi'-gVl.sET 4&n>Wjdhs%bJuFS[NmYnVgTfg;n/Fh -;81q|7/&lɋ#4TUj&eRANE^m7Y|q?y㆗J`/#)Sp;vVuDix1 ː؅wnKį߄X 5V,xU#$Sw_9&XRĬjWefx#?I d'v8N.ָ(x ؋4:?^Xk]׈' DW Bxgyxx_pV(9xqwq9b=ybȇmq6;1kcku}ٕb#L@Xy+T K[x%9-\+t~ >~!>%~)-1>5~9=A>E~IM= ;Bio-Graphics-2.37/t/data/t2/version4.png000444001750001750 2562712165075746 20061 0ustar00lsteinlstein000000000000PNG  IHDRXJ PLTE{hAicG@pEfph>4OS#;|(1>/y|mvf0eo3 3k2 cUXf7^2X. c}v;Cw>L޸?m>`Q(?ǢQ&XFqvĖL~ Iq"XwX"X#"X#"X#"X#"X#,3n I}ߝ V;PLEfp(&"3j8z1M_QN"Lj%I6+y㖻yZ&b*~;BwXD:e;D">L2` XKk2í]PL,'n[nOhB^1ch 7Bbk`]PLM z+qhC1)K!c{;7T6k^[ЊA7:]Oev+XB{mA+[ߣB]YcLX䫲o5ۯ%O K \7`U+vd X?a.\qz(,,3U,߭o>DI Ik~ ISu+[a I*n4"X17`20zgk<%|KUPLōNWGS7OvBI=LEfD7,L |n"Xc0 VD7I=LŤ_d&X bR/2C1 VáPLEfp(&"3j8z~`5I=LŤ_d&X bR/2C1 VáPLEfp(&"3j8z~`5)': g(4[ɶ,}RONj#]%3w7Ǝt ʊ{L6"7XL 1<%W**PL3Nff,@3"Xf -E'?y :?+ Ť_d~% V-`Z*&X|ULj^;/?*&X| f;#?rb3Zf'@' #KZPLE溡2_ED">LE檡_ṟ~i+~7z~fɴ4K VćI=\1ԀB g7Uۡ5J""XULj`0Cδ1ќeW|tfV[R3~*;7mʒ`]_ ;m+g݋)䜌ς'+6:'-hSz=P̭uvb97jݴ"vh V0zcWX\d(&F ֻ`ƀIEf&XbRot&Xw ,UiQ]d~% Ť_d&X bR/2C1 VáPLEfp(&"3j8z~`5I=LŤ_d&X bR/2C1 VáPLEfp(&"3j8z~`5I=LŤ_d&X K|.g(4_X?cU>X'O_N}~fI)Zg(4_},Xf13wy46y|H F%L^sg(4_S?N cp)\TWq`z/uW nZ_HWq`wO}rsP+=@둒"*3)D?̅%Aq8ג;o>Y3ǦEnBkB ðD?̝ VqYs%!#XDLˊ K>p('keōX˲g7B.=;~PR3\K,%Zq%wޔ}|wf1&~8גPN+ѹھ*~'S~\7 ʔ6òsTY$ ɱ,sL9Z}b(~Ӳ2;F2Oe/kHLEį 2O.eߔ9Zb]ss7>~,=[EQ|[ e=!U"vĒXsq?A]WoJ-ora>δKrxjCYZdiY6FXW&񦜂uU,Od>3NKuY?bXWEroSvu屘hJ"XJPa1R :#Lخ`##"0,%}:migU" Yjgn,Ëh/6#M]S1!eW K&RIǻgL@N˫ı!S۳pXn.5eacz#z,%l*e),u+UN(&"3j8z~`5I=LŤ_d&X bR/2C1 VáPLEfp(&"3j8z~`5I=LŤ_d&X bR/2C1 VáPLEfp(&"3j8z~`5I=Ltg}糼\sҖ0dmJh>_] XQJ>Gl}7dY$X?- ֗3C܇-XfiǭmwXyl `'v]e? 4=@يe:5OS0-X4=@يp ynXk~:ٸbPU\50RH1En}+n:\?\,Y/޲S 叓IK6ހAWcg>ᡎ.+/dmf{ͷ!-}e{b97}|ىS/iy *XpKS5W Jс:X[q7!X`Šk%H zhSSĢPOJ[ЊA7JhI=LŤ_d&X bR/2C1N;|yPLpPLEBЛKs2SQG 4?E]`(೐cO'h8\c(Y, 5SQzMkVdW,]aajsn^/EO{f$X\) }PLC1ixh[N7'<h(z>hހeNҚW* Ťf *ݥ,B1Cf_VD݇yle?bh/[eǯi6~d7} ͖r,bnpJ|;vucU,6Ƚcnk `>X,Ô$!Y~68J j[=ݎ]LW+ ..xhL+N,`mF,YXe `]C?;x<9i Ve2/ڐsl#jW8Ń2~@1՛\0bۃ݈zd)y$/@~~m}bROe~omJ(&9C bU7Xo V-`Z*&X Uwzy.ӧB Ť"ˈISlWB1 VáPLY7pY{ؗ~.z7/Cd47>05ģ r7A&+t Ej7"~ +2ZwuPLެ+cD X`ݫA_Y(&,KETѓ*͊[}e̯W1bU7Xo V-`Z*&X|ULj` V1bU7Xo V-`Z*&X|ULj` Vq/`}>w^o3|`1L0`1X_3Gy6`wcU8X]`[X0ˎ\~2;{鏱+>4[~B"2XیXvWhc VáVPLEfp(&"3j8z~`5/sg'ևO+ſ_ŭ<Į'}ᇋ#;KӔ3<#C 5[C>DӸĮ`14#,w?FWq`dZ+e n7 bއI=LŤ_d&X f3# j`,,,,,,,EQrZ4&y8=_o V-` 9Ri=-Wz{;` 饇]Xw K g{?$Xb狏n `bXVhi!Xb"R/E+Xg(z~`5I=L[ES1( ۧQLU_d~% Ť_d&X bR/2C1 VáYnB3Yu<`%LSBs`-ZK&rTF(|̶?5ۋ%̓e"L0)hx ^6qWa7*udk%MUJ(&| 0^n7q <``}vl" rE:+2[uTqBRb*'X+|E"*O~xQϾ& 覢>n"z~*&X|ULj` V1bU7Xo V-`Z*&X|ULj` V1bUE IDAT7Xo V-`Z* >ݜǾZ^cU:XH~!`/l2,Og~?ucU8XvWhplnefaY7} d6v"X |`X7VtqR(=@ZÃw{ 7{KXI+X?I-#֯bR/2C1 VáPLb^-b*Ηg[D ńr}C'.yN+qe?t''4w26~%oY^v߷+X I-XhdmJ(&,b S42!XhdmJ(&,߀L#`YނNƯb4mF,YXGB1a=xzx>^xx9x_{|-¾\hC]@|,$oOw ={QHIobe\5R(&,`  o`xD: ńs8OńC1aaPLEWB` V1bU7Xo V-`Z*&X|ULj` V1bU7Xo V-`Z*&X|ULj3r.)po>"9Sj=9`HWq`}ҙ0?!b|С>+8y ln+Ril}6τ>Oy aC\!yKוAfk2acVXhXI=LŤ_d&X bR/2C%aK2'p?cU:X_y̳Iz ZVUXLEfp(&"GFaY,z~laҩ`Ell$ZNmi𷥳4"W.|621֓闲mOg3D-6χ QbE_Y(&"+ULj` V1^2ȴe1'nWzB 6vvvk TK$ 59m_Jwav`o`gWJe/j3WN4no72XaV,s]|UB6C{=[Z}WR6 ])%vkwu؉|ߊ}BKee J9#q;ۉ\^'Tb*w[(8xovR Ť/%{Gk{~9JM|<Zr.ۇf$c ӄ.3gvgfݮ`寛?/-rxͽ=!/NY$?`yin xl+rT +c[(ֹr.Ƭ쮂SbtSQ7I## ք`eL[!>!XYXK=JhZ*&X|ULj`)ŽLZ>k}t0+/?7oHŤ_d/.핐2z~js,e߷Ĥ Ť_d*~28M |aR/2W 5 yPMUv(&"sMvHxZ>=3zS+-\ )ŤX|`ENCŝD7yp"+}"i&حgc}]|"nET4gtp:ihl=||Hb]d oxǻ%MO47SA7\9#V>Xfcb[E}Ͷ0oPoV Xfz~hE8 QEQEQEQEQEQEQEQEQEQEQE=~(XIENDB`Bio-Graphics-2.37/t/data/t2/version14.gif000444001750001750 1276412165075746 20121 0ustar00lsteinlstein000000000000GIF87a Qͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ xXn`v l 3ҨEw^:4kѧUI{[әsͻ7۽[#\ײf6Bvt5,gp_q BlU[ áv@ H"HL&:PH*ZH &,zcY2*bFTj8x̣PH =I&m/^(S}RP)"dK9W~lՃP)TKD܆ȴg) O 3L@AV2 *+es0䴩cz/&3@uSS%+T} *D}Ɣ .pΒ=)}$N5OF9L~bNڨCP[s 8T=PIQu% &=i4J9zN' ^IӊR)ꢤd^5T}&AXEP#_Xq1'[J׵*H:&XwJSu'I0Kgf9!0],d%C؂,ezKʬ hFq jkݴ6-چV} nwX&I p_{ kXCn\V.ɍrEZؕz{^ww7Mzwbw͉}kR"~KQs[Ǘ.x`pIs3a*aAqIzuc0gLR&b-i+MUZ[}CZ<{<+v ukOm٨VeL?uOwf!WӚJJ5)lfն8^g [RIթU,JDֳJN7˵LՎu,OTTݸ,6 9Eɺ՛'jA;/]1g:<[l("@qC>v;mƌMn$*T.vMzη-*`D^LM1CD1t;i'%.E>S鴑h-9N+><6@ʉrpC(g^NJ#|Pi@ŰyHOҗ;]jL*[u,o5 zuj]g,!H۵X_kSxI_mZyZs+h)%sڿo9]t}nRN2ae?[oyT>cz쯅elUL'9|3@̈́w,LvjWS[1M?VLwe~wvTD}jYU&ŀ.SS*e6yM%YD|A‚Je+(x18y:dERwwOH%QDOLRXN^?#}[@G^m$^^jGw٦(&P-fp׆k[gv^qfSmG$e8XgtQņ;X[A1`P(fmwHN(eTJw+r걊g$([høXvHxT8]chH`ϨcYUXN8y8{VcG5ܸ}VHmAژ8!HK^sx'lQX$wxfDKIshf sh*+ɒ$H5yѐ"AijGxh7jh؏0gLeQ]TOCsc)("oɈ>4c],cf K ߒv< ٣5( >Fn>o=5o!.F~ H`.vN5T[$~O.Zb0U^`[:>Xn^jlI;Bio-Graphics-2.37/t/data/t2/version3.png000444001750001750 2562512165075746 20056 0ustar00lsteinlstein000000000000PNG  IHDRXJ PLTE{hAicG@p?;}̻ g(4_]f;oyYBL rCXww g(B|2X{xН7*nEA"X#돢EGDGDGDGDGDGﱨDG |]?+%+CFR/`}HA֝ 5L7CFR`w'}UN(&"3j8z~`5I=LŤorWS,Z`I`}q͊Ed^fy)u!XŤ(,3Nvlَ2FL+X2wR2Zpkk+~7Fofii2?+ä/W>ŘZÍb-X~W(*~;Syw}ƪJ?PLRb~xGގ `s F}|bPōNW`]hʀ>P^[ЊAg`ESV$gj~k +kR8;9C-׍$X 70&?;ρuذ'`d}5=Kut~U+wk%n3Bq_BҡT(9b*VXBRnŠMVLō&X+F 9̾^Or&*;/X/RU*Sq!z}DP3*aR/2?qOvBI=L& Vz~`5M 5&"3jjX>LEfD7bR/2C1 VáPLEfp(&"3j8z~`5I=LŤ_d&X bR/2C1 VáPLEfp(&"3j8z~`5I=LŤ_d&X bJ9DžB7} rmb={_>rW ,i}#]ꂲo3-+48YfO[`.Wq`-K]Zf+ e{j7} V,;qWЭ/[[rsn V-فȏL P *~;z~nLWQ2z~js,etĤ Ť_d*~28MmaR/2W 5 yPMUv(&"sMvHxZ*&X|7y<3m~4ge-ՖxԌ t{$XHN[YbJ'9'c` 3`F /ZŔ^h%s`'3XZ7')FU`+4W-z%IEf.X1`Rot` ů]d B!KysUrE(&F_ wB1 VáPLEfp(&"3j8z~`5I=LŤ_d&X bR/2C1 VáPLEfp(&"3j8z~`5I=LŤ_d&X bR/2C1 Vá _"| Xz~JV7} z(7 y]^.4͆2g(4_bo.Rrx'{ ӿWfw\7} ͗ߏSd F,#?cUsaa |euܰD+εΛrn_Dr1P0&s'}U\;tv\I=i/%7R5ɚ8buYq#ֲ: KθD?Ԍ9 aVk7n,ccE*ε~,E lto Ŕ6͂2e=;|Ͱ9m Cr,ܻ.S<%;F._'洬"ŽmX̰TΈ%S%+XteF%b; K jNr>uZ|8qYeC~ڙ"C#pSהsuHu3e뱔uPŇ*ql,>ܲ:֯. ,gg)nX9HK &>JهbJ'#KJhI=LŤ_d&X bR/2C1 VáPLEfp(&"3j8z~`5I=LŤ_d&X bR/2C1 VáPLEfp(&"3j8z~`5I=LŤ_d&X bR/2C1]>ٟ{,/% 7hWW1&VǑD~,4[ߍ|>&i4Y ֏f˂d̐c>;g Y;hqk[|]k=Vd(4[{A0؉y,|`}B#~D(&c|[4ozo3I=LŤ_d&X bR/2C%#ER*D/Pcv`w#&X[_wF'31?v=VϬ?cy}^IKNÇۆ}ugwnL+˗F(X:2kglj+ VnhB]1XH0_~WV1X$%To/ F;x;DUVh?hڌX%Efp(|)QU]oE5|4|6nTdW 9-LJCL[qߊ[+Kt= 컷-pŠHdG7`e~y8qP`z)ckx ꋮjt8Y[Dm"cr c_XMy~ovbT(KZ^#f*cRot`VMX1(F` R"+|8..(h}bP.2_bR/2C1 VáPLje(z6;j8SQgwz>+ To/{&hyr쮆!X=b*7,Sz퉨5,;Xb*߀|/Cz T^pWm(|wn mw2(_J̚b*'X#b*OlUW:=byPLEkPLEkPLEkPLEkPLE;{PLq)}=Ьxu*~WI=JhZ*&X|ULj` V1bU7Xo V-`Z*&X|ULj` V1bU7Xo V-`Z*nWU|>|g(4[hYO˖;pk?>YM{Bew]]X7 ro-t{";No3s$'4wZVliwAc<bUv8e; ç ;-'ӊӶA:K,XKV"#V`B+ 89_e;-%2&p@wr:}p­ƾ?+ɜ{` 8kK#&7*v[*> P+3|vE-"?lT^"v-pŠNnX:,숕++N2ֵ_tk`''6(uJZ'KEKűb;tHaR/2C1 Váٺj`e?XzDzDzDzDzDzDzD`Q\ͩ8s2NW1bU7XŽedZ(un˩vA^NL &q5c*kEWMF|4Ͷ i,Y"`ISI#߂evsp<[@؉|ߊo-9X ŤnϡKlŇ&+ۚPLn  |~.]q;bWH`mTx .NN8[c"Q,wPLIdv>X5R OME}D(|E"*'X+8Xb](|a`b}a̖u1w,E}e <`_%fgƳ/,y7Ť_d~% V-`Z*&X|ULj` V1bU7Xo V-`Z*&X|ULj` t` IDATV1bU7Xog7󱯖Xs&,cH"XcfEi~ 9˫3xF /=@,917cEB1a{_<ɷ >bp\ ͝ _ ń@W&] B{:E ;h ˷X,Ô$Mi;H ;h 7`-Xî0|ࡹ+|{4MKV"#PLX?xfoxk(&,_r/ڐslW` 'XbC)8XB1ańC1akPLX>n8z~*&X|ULj` V1bU7Xo V-`Z*&X|ULj` V1bU7Xo V-`Z* ܇Kycb 0GŔZOX?cU8Xót& LO9thO &+N|^!C/[mJhTC~>4[3`S,>sl=WpH>u|:~pC.!##֯bR/2C1 Vá|ؒLI8r|2XW,lFhw,%$ALX ->r6't,}sx99QL`C1z[K& 3G,|`-Aнz~`5I=|hQjV+ Ť_d>Dytj!Xx:A/e%tml#M Ga4G xtlt} m!Q{˶ &":HԬXWI=JhZ*&X|ULjW̯,2g_b̉[vAbP;Ȳ'/) oMN[j3WRkǝ3nX&Xb[.ؙ;e yLU*?w9 VF,\fbPM^V.`͇Bdk=b]`u*v"߷bgRkY wxGz5tv"_7QJdd;h`U+awB1Bo6~,pFt/J#V-ty{uUcC`{ۗk|նɜ3S`ua{{սNKxcE)<ƺY-_Uo#z7)70>LUFԇ^~=SI=LŤ_d&X bR/2C1f,cMe:\ ]RhыS rTF(|̶?5'/N O'X1&|B1qOS6׉LF|4Ͷ Ϟn) կPL,axNV|l2TL(&|wtpm#؊׉4Uq; +]TvB1)KڞF-'lR*>$:ρ`%!XC4 d&̙ݙY+&XzfKwpsoOKpb%OC*&X^m~$)޽<~d\mJ( u\˭1k``TMbusB&<ƺ5!XVOVD=R/2_o V-`Z*&X| fq/ş:lOZD>LE抡O͛l+?iC1d{%L+ä_d%KY1iwC1kL+N`}C H,+pvSI=\!,5^Oόpq;H B1)9XPq5M'9#JH vX8A'_[E}7}[c5_-(F+[ neI͍2jcbP.2W#XyQDey-qg,\r%B;~}0c-/1:XC"EK ,/n izPD74ݪ+c}|=8yNPXbc(uQTuuuuuuIE`Ye*t~J[yՓ<XgV_"X-"X-"X-"X-"X-L':kCz":KL` L` L` L77mWfbcj~2zq2'}5N}J(~aEiS*Fɦ06kf`M!6n]vYS*e7!v08 W`edom-q)mOto㭉6&\Meo]k{o?*D:Fԝ[ibjIj&`a66uEX `Z1X,1QGj:JX|κ$j VnܒU<0meu`:vz yoeW=˔Iֺ5vڗ00y9晨+&loտB*16X_1bm%wS:o,%YiErΐۅ-&u>OFUU[2,N$]ugU/l&%-&Xd ;X絯l&%-&Xd O`I օ:,f"XbuN& .`!4z#S,\S,\S,\S,\S,\S,\S,\S,\S,\S,\S,\S,\S,\S,\S,\S,\S6dٿƅӗ`[01ӗ `M]5Oշ:r74M늕&rm9yT"OKLZuX#X'X2'X2'X25 I/w?l*`M?- l~r,M25 o tqg&)Te*kC˾a∥TOH,rT3X=~Dh1}L4U]g Vx0"$=H*ɗE5 oJm"w䖟y^,Ax!VdLSJZ|zKËh!n1ľK-r3,qx9Y5pSXd@j^"X2*.~}!XMU]g VVu%&X*g&&X*`;QhCOc]QvZ=͸3U77S ,|CSM2u2ogKoC%~T& ژ ""X%&X:KLZ՝(dz:nGKVx]Y%`]- ]`=X&X.?։uɆw'B+kI{^Vnt&`j5E+{wJVMf;ORv օŒMafDK(>&X(`~c X_aeBg:-SQ `сL}Ϙ`````````````````ᚖKrq[L_7ӗ;Ou~]5`@w`w~wqѕ|-/1wæ#V2m/U]o}!^,qkӯ/-%" Ke'U:pT/*'k215}`)LXOsww7<ȸkwu rc@Vxs%\iYvu%~Tr+n_m J%钏&%%wECuIx%['om|?XqØ2]C47 (.4+k\#./V"9x_>hhݒ;KJ;[sOeʯֺ\2uKKڸX$DNbJP2f]ZbR’3\,We]A/ɞ.X%D XG@`dFtbE27Vehe.63qXmF,71XEcX,ш))K2b?#”%qB Q42>pʐT\Es$W.6^`%gpl!l%sżiCoYZ"׉l%qkMVKкϘ```````````````````tN38}(]BMu$kAga&"ziX#%?v g?)XKhK3SǺ';˽ZLak˒9˕`E/ⵘ>VVb yNX\)c` Af ּI[Lޓ/״XM<$hwa*SjwӗX X>SnB_'<5 ,<5 ,<5 ,<5-1b5*ak?]g-5_`u>64b*R1Ub nLzpgO+]uuXҰm߱akQW{c^ W5\f.Jm چobr!M6VM˵EtV<.śB irz>!m ͈:j1}5Xυ'X2ܩ8%SUEX['id# EΘC3P)::cJEΘC ֝Z#^2m\e]vu%~T& T& T& T/f^Lwu&X2ւw8l*%{"xxLOsRi*5<iMS5xr唜CMe韀5 Vҽqo` X'l}y?}Xf>igXN O!X2rxk*SMjSuTSǽuugS`T& XFl*`X`T L` L` L›uݏL›uݏLOOrM%bZ#Sea­ݏLMÛ nM-Tgç7Yp1D5 ߝ Täj>gVHLej>@dOXm -NÙ`tnOc5T&OKL66#X2=9=ݏLu@TT,e2,}L{Le"X` Le U]g VVu%&X!q1 T'~GMe?dp/T&#)e*3'l8HMe=|dT`ܦwm2~u2U?L` L` L˦Em`;o_Uu$1"Xk[`DVaL`DVaL OKLZu`,1jUYbժU]g VVu%&X:KLZu`,1jUYbժU]g VVu%&X:KLZuϨ">-/1Xd⦰_bme[l )شX0O!~q%;Me OpMe OpMe OpMe OpMe OpMeoOզ2wn1[.qeV7dIXC )_Xw;$~X-qƆo3ji~#|{HаŦ1ɭ?( M Ƌ8u)4 X r#-fWhBL VabL ~`-3-O7Z~X'X:VrW,?-7/۲Lxf 8`eF,Pq)ڦk0yny2yɻ ` q` k,`S`7cyibga Kђ'^WuM-ASͧIGe>X9qKTy8&pNJ> ~1X;'kI޺D4198ZAKno oKBfN8P F,IϢX'6u}XZ~{1}XXZ|ͷm1}XͿSǝ)β?]32VUmT}~qG2'X2ï,+ [[_>~!X?J!Xm]X@6$􏆃~3+ r7]i 3np~%x .]/^ V^M]gq?-,g,bӻK6aEKKp-M:ύ71Xn ;d&7 +bh ˥E.uVheݽ+.-A *Q V^R_뷵)l5F:1XM'`LP:E#V4N'+LhGS݌/Bڽ>~#X/'Lگ>&XCM[L_bt%X452e`4SW >qg,-/18XЎ_h ` SKFĪ;1̱)޴XaѕBJdXKF,)qNSBc=`4 T& T& T& ״\N7>$ӗQ,6.g27'X"{7p%c]i1}SP%VIo"Hhz 㘊S>ZL_btuo_w}vX4V]Tڸ OpMe OpMuT*K~c,,,,,,,EQ5 L` L` L` LEؙZ|D}cUZXDSUĊ'N~O=Tb˞\'\n {6i,35,ʔ]?,2&YLe-O:܃?-/Z nr)^GKZL_bժZjy(UCZz2J&E"E"E"E"E"E"E"5`QʤK?a/32'X2'X2'X2^4lbT7F _ֆ /ҸX&Gƍ1Le?p88v% (1Lya=`S$\̶LL5C` "X g"p./tF>T `G$imPqLe? `E7 )}XTDKXNEU-/q`!:co.a~Oq`GF&v9X%n,+|@D,U!Le"XN{@`e*U.;?fXK9X9b,!"X,UX'"Xu2UkkWҕ:=]%&X:KLZu`,1jUYbժU]g VVu%&X:KLZu`,1jUYbժU]g VVu%~X4%FYA%`iZ6zIK,K?{T`Mɍ__7/#c*<` !!E`/川 [L_bp[^#ӗaǓwzdk22UvS,\S,\S,\S.7eܕ qG`)X7iSǯLWgo7;,Nw4M(qZLa, IYm M|\ LB{ncM/c1Y&X-_ оĄznY}`V2bE7όX%2 k7x=`o٭i+Gpcb6?fw<8ziJr 9bT& T& T& ״\&l) Ǭ ӗQ,MR X$rڟ;t,^` [uMTbeo|+VrĢ gwCKE+`'X2tQ2ru2yNu1|n˵yiMIDAT:t[u&XD *#V7G5G8ǺSuZߨש XH4n^u_q$^L:?a/1jUYbժU]geg/1-/5ZL_ ;Hr:wl5E-1Wd۲nҗi˄<˟3ug$1XM=ϟN8`~2.IO>);0W{S1 ` uXfkȀU{gX!jS],2&9C*X[aOfS 쬹canp1s{!㴘>~hwCXnPb=$~T&MOo3]F+fT^X;p"RqkC`:cu ;gm A.tbĝ)#V{kXc$"Ws}g>d vnMvJϲkHh7ȬӉ V=[w;+wwz@ KCNZ͢H YWΤ- r= [-ĢN6W]@LK`TǟsS VVX'L%&X:KLZu`,1jUW8|0U]E[N&LOͤ^Ǿf"X'JE&=29Xb:^ HeϚit^}roaS·3ǥxShⷡ?m*X`mL`Vu8_<nn*|mhS8mVb1SXqĪ]9VLX}+N>~+t'ϛٸNDzJ)EQEQEQEQEQEQEQEQEQEQEQMA~츰yIENDB`Bio-Graphics-2.37/t/data/t2/version15.png000444001750001750 2334312165075746 20134 0ustar00lsteinlstein000000000000PNG  IHDRCPLTEQͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ Fo ,ЅuoeXX%҄4̿Z i$Mi4 A@4HI i$Mi4 A@4HI i$M5ـ=&:P l .km;( `"ɃE˿x0 &Mf9`U`"!mP)- !V&PrI4MoBI i$Mi4 < i$M)89)`?!R;%FC@d8[J)d$Mi4 A@4 HJL_V&؃`"knΤN~a$.#-o0&rf%-4mK1u8#ei-OeZ7_N-P-14~-_Va" h6ZSܺ}\ۿ'} L{ Ps ƒkvbj =OAmg[VHnPQǭ9`U`"W xxPޠ#X]@bN#`KUx'f󖗬7b L̨bz [@E%;*4v$K=ۘ:KLs2x n9,8>Y5щ9%`_WrK 3:s@Z"co JvThD@D@ "y$"Mm")IDBzťy:}$;c5" A[&x LSo"50LEk`"z[X%C!j 0 C@` 0 C@` 0 C@` 0 C@`pr&^sD.w" vbKJئ0ƃ 5OiG-g9`U`"{`y>O4cښMvjXH^K3>˛ 3j&R.P) V_@ &XL$C@nW'9qV&"pLkXD l<CޅG4n--LKfjEح%t 9N'SO!|0?{eNiEo4/Lo)jzWZ2`H{W 0.TM0!sD }asևZ2`H{L! Dz!0飚+_W[xv^'Ml | &\܊YsR<] ¶IDLEcmNQ9i`gJvՈ?, U.Zt `Y#%CœWK9=g/دʖx0A?%\.݀%C!`~H0{%C.a[5ReĒ!(0t^ ٫nfܠst\a"[V@+7%&o ƒk,K 1" a`" =&oO*seyKt7o2oIA>ݫPЌn؍x0&ww͠cGC VsDj)?m V@?V5 Ldor&-lGld nk7:~7`"%'^sDpS`D{z`Ӆ639|RS'tSlaa˙2wZ "m>_AgfPu_R;_T5jt:C@?GSA!fe}!ןė2l\0\k'Nɋ%+T0Rv0%?'T挀 L2[M'aJL _26i#N00I!V@NÖ; sy}5gvʰ-S:N6J6y}5*6J-kF!+䏂's8n>y a'v^? W9~̩q4ɷ0f8G,uo\z+?8[OS ~Y[D4{a" =&؃`" =&؃`" =&؃`" =&؃`" =&,_j}t'v^sDcD褊 dHB_YO PTFD(V#{v?SP@40,} 3}:9`U`"'C| Y:1-/ 'v^sD6Z-^i I0݃x%\U!i6t$Ga"DmBD! oϜt85 LdWqQaL[wea]MD =&O 7_ XtҰ0c+h#ޫ_X ?SGڜ >ˈ%V@7r~1업fk^1nׁKv7L$ j(N\sl_^>89Dn n7vP`vvl'`"y` h*<ys#[AHp{=!z%Upp&q%$l;+ `"\6JLafJj]`9澣d%_\~|_k +jUMwY~}悒}F{9Vm; qD~6⎶} eʙM| kE/~6T 7|3_nٕkMp;l ,p\KKVJVJV;|VY/ȁ "v/L! Dpsĝfΰ?"ߘm5 i#`0[@y|3~nY>)WӼ0+TfD"+go)VFIn1w[,\MD.P;3i{)Q:X~`4/LЙoS=/δ6ܴDnppK@lJFQa"7l ^&X*)m dH!˄AHp dŸ l;ub^.̼i^VjXȵ!{tf熟WӼ0KݥxJ-ya"XR2`0,)rH: "0T3D:M0Nä/ak&r׉5j&•`A0L&{m;%:9`U`"},STaGya"wn94NU@%tOEߺ_ya" ^_l\i^H3%*qci^HM 6fFzIϾ慉8Y~{j&rG SsDS-:9i^Ƚ!ɻc_5 LVPsDz0yV&҃_MDQsDsLUr"S`" =&ҋ`cw/u%W# M(5~s{$λN(Z"n3[2kN!B6+`)9'LɎXMO5WKisփ `@z0,Li_JVpV&؃`" =&؃`" =&؃`" =&؃`"?>8ᒢ=;+g9`U`" cC" 47fi;{[jvSMd5 L$ꍀTۥf+vV&R ](5 L䜀n,M h&XLdO@b=!! 8LaA@`A06v`p`5 / S05-{bNll7\ծqDm')ߏk/M5g/Pa24*8S_pS{XaWs*xb8\+oUح%&R\VVl`\l; UkΞr|V{'p}Ve7NT־j.wexx&Rl/k'[cڝ!|-iUW6^[7z^lT՛R!ѝGs3vqV&R@PyWӼ0.Fc0\vY; t\.6%+ os 9W 9]2?W+m_\P߱*xdx{*.َ ;tl\VJXXDGrzglֺdE_E]u^i-Nΰ]b5g4Lf|]^ωh`DL?ovw.o9vsmg_N#ۮw oQ/]2 ^]8 l\C@`$nH%d X,Bvi4 A@4HI i| "sYx09+ٔov`ޣ}9`U`"%-Pf񥇬9`U`"*i_Jvsf 6*g*K@wP])QK^9`U`"QCQ *0NDC@` rCMj4 A@4HI i$M3d&o- `" =&F@U0ܲo ~U!` &WqZ@k3e;?kg\k@x&r} l'oU:F͹$鵭[{O \C7GPꈀ*K&rf㫃ьz9xZ%Tt#e-xZ׎NL-[yA(alnzp'пU*0ֺU\xtPt!3sw'Q0ؚEL0&I^i&Ü0s+g0Zd5" "`&ax L0&‰$"U 0 C@` 0 C@` 0 C@` 0 /7<~bV&s3)?oX%]/ŃU$c*7{H/?2Yßfg` [``ڷ㶟h{ZƯ9`U`"ɃKj;3k'಼|?U|oNo[)jXiqb;Yƺ4- "k}@`MV l<Y@t`;8LGpUPiwwy(Zl'wo-Wy 3j&RU@6$&x` L3'k)9`U`"u[E*nymt͡ q̻慉H-}&^v[sDo8v- > &rA ʽ7nBۚV&R{|f.SsDZ h[ 3j&¥8`s |~O"Ŧ_MD0dD0dDsI#sJ䓮pB-`vɀU `vɀU `vɀU `vɀUp"{L! D{L! D{L! D{L! DX=CCy`5 ܼpʚV&xX|Vh*0|ZW>V u^@V케n?/.yj`90d =zi?F-X  m[0B@L;)06Lu(r髼*0t 4v!;^sD.6eaZ@`hjZ@~MaPjV&\u:_\k_`DG k4W%! 0ND&SX!YZD55B SqTTXWCSq kkJ H\*- b] UWOR׭3hMg%k A@`A06f?xs2E[ݧn*̼oٯ]-x*>rHI T6Uɷj0/TUN+_嬴@a{ VOIDAT"V%/3\/IcA`Eh5`3*OdZM i*! 9pr럦j{qJװzu@Y[`-- $< c/2+M+tXs0vmYpikXsC6{[z/Y;n)`F-mt-(ШE~RKVo)^#`sߣi4)zX7inj)?hSO0%Q+l[kia" XAK[AC@`PU*̥5~쇪0ՅQChR+pNL-:F ] 廪.U:F͹_zm֞7Sl;0T:"J/5'`,kw4^d-a"7m_Lch_:__U`A"`pƶzwbjǖV&rZ eT 'hݩbP Xp3:- 70‰ܮ0Y[U"Vs/R7~6hK8sY;/%bj//_! 40ְJ]`D*eC@`a"Q25_`D~Ͽ/Z ?H*Qpi_k@aI,E\m0dt0k2z+ -*cۀJ/l 왐 jXHL@E-A@R5 )$UjT A@R5H〤rTMWL䤀H\7 X{WUljT A@R5HCX&/4$A:p'1 06LM(L?>/D v[̆.YHki+>˭3MR:|#ei-:Oeωn[@Z<.~mR=yN@˷=il[@e=eV_K3.^@\3رC NH&vBl[- ߲E?ߚV&rů :] 1l]}) Omf0]J_,5l<~9_!Xa (TdGF\yܳK𰍉-D˄V%pFYqY5с9%`ӯ+]) ֙Epr9ʵ@Z":co/_+Q0&ö9s7@s1&7x";YhY4X0LE{`"z:`"zD0 `-A[&xLSor"=0& 06L! `" MA@`l"C@`D&D0֗e3xV&pRB:LdO@w_|n[G9`E`"{`"<54]ʛUvjXH^K3vktCw1 .2Q) V^@*5H:\9pV& "0։-X0 Yhn=Z2`[HЂGKl i@@sxN]D_!x0?{eNiFo4.Lo>.{d"0! 0&TU0;!cD |a=Z2`H &D0V >A.Ŧk5h?g[;o]a߄`)"6~~ nʬ9)j.ra$"` &R_KcmΥsΔx8xIkWtBL5]2<.y0dSc}UvmQaA@`l"^Cݤd?L^aIiRǒ!`/0ܼg};.0̂v}Lg%{&9u=k_([a"֬a* yf˒!`N]# &D0x{R+;Λ♹Dg xjӗw4w/on(Ybn5"`$~Zs NlG۲~D)Q`,m%[UX?Xf"Updo@yd?|fE xxFLhKK2A@`D&D0& 06L! `" MA@`l"C@`DvV&5 ?HJ ^{]Vݐeȸ5L$!Zy܂{}6oG ?Hz+AFW>djXȞ^-埆h?,tȚV& ^Z`dVKAY+%[Uo*Lʁe"0֝X0;z==B0 㜀{Z怕' Uqu{}=#ܸԥa, `^$*`f+ - - ۀ xf3Zû: V8"3炁5 h 0>O5CfGeu`Öaˑ 0e|v5JAD6rm3*Ό +ZoBV잁)?30s%{RF3?s4w3}s{YFY3'z Pk}uL_L`p`ʵ`]]F/`f \ `JSN@d*'9:S`n1)udBK6mc4I"cdo#fȻiRs'aV`2ƫ0wn)o'.6J1y5^nm/Z-kmFϐC{v9|n>x a'6^ ~99̩q0ɷ0q~cəuXz+?8[<S}~;³ Áh`D&D0& 06L! `" MA@`l"C@`D&:幙27[mr??V&~FT1g$5ʿ8)LdO@r\ UJ {[סMfidI#0,}<K~{w߄kX # L/ 6^sD6Z-O#L0ݝx%\U;!iVt$Ga"uDmvB@݋` o\-~jXȮK1mݝw1 \006LdV*L%,ߑ^|aZЍ{qKT?s6^A^g}n%{?Eͭ9JK֬aL|^;_sQydODҫ5珖Gs;(0cUF l]n&x& vST@TN+d vBKO؝ q-YۀX03!a XaTT2g 5r,gmQN)<]l'Jve G'%^[qB%g)]ɘ⎀;K/PK^Hq7J{L@eF=-2uZs NwmS/Sά3FX+|aakٞ0~AA,fͲ;2O XL5JY|?#6`d2#D vi1@O5xC@4ga" My^eW>}T{}D1b&RG`跀fe \쐋i\ȝ*D"+_Rև&M(2eb^ ?b&rܿco&V>uʶZu2Jxˏ ƅ:sE@Kn#mns+nZa" M %o6J#`0V/`dVPVj%L䉝ev'$8wd^ɺbV)CF_LD8 MyL@W%)]u@+iCx oh1 yrl/n/͹KN#P0GT^B%Ewh9b&.MJ.!Ӹ0jWyߪąգ/qa"Vg*F}1 |%7b&򠀉.C9`E`"lw N;b&NHxV+yT="0L&n -Ӹ0}"060="0VD&D0V  Cza4.LI% /qa" dr"8L=QN uƅ4 z\p[xHIأ%pUg4! &R_?entkm_-LMA@`l"H}Si ~}Ts`bS^xYeG-EͩHLkwpiMGnʬ9)j.ra$"` &ү6R9i`gJv<Ո k\- `| `94 \= KJW#_sD&D0& 06L! `" MA@`l"C@`DX{w!S\[>z+I{>.{X@s q 4쭂E5;пթ&sV&lFup+vV&rA@](5L䜀iU0,Ȟ~'NibCvB&D06msw\P6<)Ա Uܷ_Dvo7Nk8hw Ƕː P0TؠL:15*xm2Pk z/s5~e=Za"e[esfh(kVȶPePy!5K81k[՗^Qam\`\,L*؞ N@;?Bj[@(UW֯V[6z=_lT՛nR;!ޝGs#vV&RS@PqӸ0NFMG@gitg5Ü.+{bc?Qٗ[>fG$9W~7 q%c%fiGV_PُhX@[~Zz%ϕoO%6ҩ%Rǻ:TTr[ xdQUŋ%4O w>o89Zs%[~- _/W+/`i^+}bJ+9]2_Ws X06;oKxy?[}X'3 5[K O;ջ4.Ly,ߪx>fO4WMG㾃׏N h{N )G/b|T Fp;?m hf,fl}-ˌ`e6[WW.>)/۶_mӵ}%\~p6" h}n(.# A  2!묀vtD pm*80#[' !Yv,\- wXnwhkvB0] wB~,+3jn\'raNoЧ7ђDZp Cva"lC@`D&ҊuZӸ0&tG/Oƅ4 ψV݂DPgDX &ҀK']L+/`di&҄* f'dLH}.o:a X&Ҁ 06Lqoůj]lW/uSdw7ߺdþ KSDm:p+VfIQt#D&c0zuQXs2c*v# T$\6u/p5L$^6- ì'})! 0D&D0ܐluFnT A@R5HI $U/ !w$WnN&D0U:ir{x.kXȕ zKwYsD.L rV w8_3`` V?CS %t"NZnu/kxV&ae?{^??ߚV&h`l"J'/!;k_HI $UjT A@R5 HJoN&D0:BM$탉$c1lL_/lP3ßfGQ.mChGWкV& ʽt(ϸG@`\/{4"0UU:w 8D+= #vB̔b;!пXvIaXUi&F69`E`"U"0ڜG`9㻚 \-gq 3b&RT@V *xя`P_5s&kXHpJ_^[]sG.i*20.qa"4} hZ3|~fkXHU=]D`"섬-,^{3f'ĭ9`E`"WgF0b75L0.qa"6A?IN61 A+A+K)P甀ɾp@-`vɀ `vɀ `vɀ `vɀp &D0& 06L! `" MA@`l"C@`DXuK0)0VQ9`E`" e_qnUI gk8`Y! Hr`"3zF-:V^-Q!0N MaA@` |-ӛ>;z+I^@k's5L$)y O {D@ӹmյu~LaPjV&Мu]nο/ `"`m\ih=|F3˔ A@`(,ى4V0,hM )8WxCa .ִuչkB4`YLĥ, p Uۀ QE*|jo '`AXAk+YM_(IDATC@`DXWUm.tz0.afv# D^@/S~9V 4O!rчC {DL rV 0s{?E,[I;_3`.IcA`Eh=`U@C*wƅui=-TB@ns"0w *^{jX z ҂vB0$;ӴA5,< c7U 0~mK%ۭ1Q_a:]Kۋ54mRŰC΢Q=/97àQO9󊀗& -`\۱Z@Tۀ ?if5 ZoH^Q%ۭ L! `"uT sk-[3r[^GE`¨.4)[gyr]_{wG]yٯ~|zRh_} q ` Ga]V R ".Hc0ژ9c((%(?dB޸"BcRVy%Zn\+"AcH&\ fMb']:9xvy'D@i" d3ɡ"y&}h"FJx2(VjL Ҧda*ꮼjګZX,N(I hjfXmx"l߶2bghj:z- /ڻ聫umƫw20kgw ,$l(,0ǜI2l8<@LsmH'L7PG-TWmXg\w`-a9Rdlp-t׍"m|߀.qބ'7+m8HGngOQ圇.褗nz֞{鬷'zGAnо/oo̒{7GwgO֫}o>i2Hv_/0toK_GA@ ~˓ZL"M:̠G BY"H u8̡s=H/%ۡH"v!J|%CyЉX0kILUTKT*/zš8F1Z "%5rQN,ձh;1aS5nюb!vH=62}Ld0EGUu'F[c ]+ZVb4P&N۪)]G Ǿ36_#0&J<̰]_pS, ,A_~ VֺfSkW[vͭn]嶶Id!݀իvkh)jFF!l\r3tڂA}mr};ވ ox{]B |ٰzRvЁEs{FͷL/KG/m+y3-ܻRFioR I% ‹;p.xu0kX'V0C cU=;w<33 &$ K.Eiaˉ@d,RyU@x|{g,51W*F&CbxV6𗯻wYAH樭 61vr :ùWƲeXA?nMV0q% ,VF5 n1;3˖6ET2VKL2$U69 ]mS$貍UOk^QlDwN6׳{ڴ]tUvmwKHm#Mpt S Mt6ýWhgw6+R xKt<WK<`d-jc9Jk3ma.9eZE{t_{M:՝.tW](9IJ;5N;m4<˾u˽~Hx}j7;#X.>y|Ž}q}=xTʠ/}Ϯyѧ^Cx%7ct^U{Vm䝧=a?˷#S -ϞO~?/q~c^OGB>ݿ~M|xGgxYg~ X}w X&'g($~r@Bg~肦w6gh&ط/+xWx$!-k+.D)&cB&19h0;Ȃ16X2҅҅RJxw'GsCI,JHӢNx(,Chj>}7QgxB(|h8(xhXhLcL, k*TK*-JLtGt˚SrߍOg陾Tl-ׄުxOcL*PG*!QIRZWR9$l"þrW7i-,)#־nҾ/,1>E0ެ%<)*NQSϙ>#{ίaOc!cOo4}Y m \{#_-_:!#̔}fF~ Od/Pn=?IMF+S_#&B% Gn?ϜmsO \g^b?()ߑwOӁnˡ.`Y]N0|o6RT =^ojWjQR!o.W:\Rr&N P_~ /ӌ|mNdf.O"-?#~T^я%=ӝo$֌&@  ,80>TD#^QcC;bFMYʍ'[tR&K)S֌N=}i3N0,zҤ4"ʔhTR>TTU:5+űbÒ<Zl|KYqޥݼ{8M zd[ƍ-y%_`%JY3͏cV2hM'X5Ԥ=O:أ]6<۶׽onZOھ㮝9l㻕}<龡ݽC]|x)o|٥ |w}=O_oʾ K|P PƻPA K1Cp%<M,aLP Y\lKQAu|)QEd ˙ $),K˜Q."l@ӭ`/H#Mh mң4RQ']&M5FG#KӅ8Ռ\֑hE;E]9EU /c4WXrUVi3vi{5SdC6nb[f#UVLix孷 kV\-w7^ E?%ᅁթC1]ŗ w [| `#vߌOw1Tc)wXiϳx'e77VoEYe=:wF9asךZ`of窥j|Xi錕uZ!z9;fv䘽zkqnkEa~n~[ڼ'uZi\o9CXl)GhcϼUt>nF}Ex M'vq[㇗y GzmN-Rz_W63 }?AW{,}7~c_>`p~C`gt"%V0~:FDAlЃ Ae'DHA8p!, ;8B0xB7 tCxІ wp_jεb7O!=rL1D$c'zҍVꯇ3 WX'qZHi4%jL@e, 9L\,DVx*}zI~yt|BW9 oVUzkӡ~)5%s|[.p<"9֛w3'#Lv_lwt*E<5yvc'pDŽQ&LiL_{ɽ o|ϟ%q̘gj@;@L=_}}߿O迉A-.U=|?{.w[iji~?"#ۛtS'S3I{= L={ @83?Y=p"3@[@@AA?A?SAɃ6AJ>/k@s/>YC&,($LSCA-d{>*+|*,B2 B3\.݋>1 /LC/AtB;++<"TC <@`C1;LAEBn•(?,+A$BF|.2Ŀt?E$1ADKtA6DB:$ȜNnKZRH͌eɞ҄MԉlG!dLϼM:Q@R4@<PCS%P /jZϷZΡx,1{/pLTӺZBRRe)p'(ẫ2QDmT]!}$V\7PՐc3L.ORPUfU"Er,@BΉjeU= qVؚV,u=DU,US-u:WZ:דCkWϒV-$}Q.V~MWrW> Y%TqVr XIT:ؘ؈zBאeӑ-O=:SԎEY[%Ԕm֕SG]=geԙYYY5XvYEЁS2%X%YZep59ΥuHJMЪeҚZڊm=xٱڛPѭe?Wں۸}ZHۉ[U[% 2Z5ܘ۾ Z|YU\]\ȥBV\[ƕý[x[Ɲږmڣm[ʝܳ ]ǽTٕޅֵ]][ҍUYU\\-T]^M]òeQM[Zڍެ홁 )^^" m]^ћI!`K1 _ۜ-_yթ}Չ n]͖ 6ٖ^E]a]]QanQ_a .a$_& &a{ `%Ub.`=ub_Pz"f_]5a}Znf`;f$~.c9W1[>bХs eN&q#ރ ]f%PE5d]i )@Aνf6މ%6@V%="?볛LcQjSG^UT&`o.33q)-4&fg{^fneY2"w%1E{!qFKV^[dKnTJ¤mꮤ WdW]Ԉ^,M ֫~'>bo&o gnNUpBpSp7q.ȍ.lp׎2 qנ&p. g^pp#$W(%W,o5VX%ԅ=:^l+k4wi.X egNk;hO9Vl=%.(LhtDyi(Ш(tA:s>.^~MoGoM7DH UW ThRf<uWpuSL D0)IGa/8_O`vfgvVOvV?X_`bmevgWpwm_v;u@sB'ovttb T7vlvnv}vb/x}׈Kq7it?uv~_xhwvwtt\w:z'Hvx8Wta/gviw}/GxiG)gjOl!sJfzwz?zxj eWyWxGyyyz'Or߈wOou:W{{eSxxgOG_{z/vZk6u>mWty|7oh_|{gLWvg'nwnWxivoxwCf<LJ 'u{7>t^s~^_u{~~oz/tr^u@wzg~H „ 2lxp`A# P"Ɗ7f"HC$I(WlI0SƜ)c͜4wޤ'P8y %jSϢALèRu*֥WfEuR\dž-kճd6EVZojnܧyK_p0a/,ֱbL1ɀ#)炠$yiөWw:j]l{lڑe L3D]tk/pvn觋߮?>toGONqgO|s[^^;Ρ̿7g"~8݀ j'yg~闒 |Y(b :VV`D'R1!x"{>B2hdKq߆ ';2YXEG2N&]hrތm]>HYIcqNfd-9|ueZxbihj|$hHiWg飘 ɩIn頒N*U~7*ʺfקIJkVZ,[6LJ;-Z{-~ROي]ي;pގn챝Jʢ=ݺ{ߖG"m FZ,;1KqL>1J% k7j2sr|ko92-llG. 3χ 6/s5[Y4c=4rPkO/|ԗͩ;kM624eltz3uр;]QƐ ,㍃Go:.rFO.|k|xB#Śfzz׭4۠gڰ~G^~{h?]<ۍ3{ʟWoO}N=MO?oϯ~_W:.~+HQπ̞=u/: F@10H!`GHAs|!0 ]p3Q G p,dN !)$q¾$jBaYNE(Zⱜ:02PS1{9&aqcƁ )!hC#*|b x8D"%9GG2򑋤#$%)LfE@d$?IP &ȇ $%gII\']JM61(iI]&l, iBT%1a)Ur+%8L<%ymfd; pڲ缤3}s.3T0 n .ɬ&99rSfBPq'EM%|d>oKRq(A+:P4I9[WP U]`ժHGlլVu*^e NîS)ծZ]*"vIR*XnR,f)KôfYQrXBuE,]jغNpUw0Z֦afX[Jt3q ֬jO2kW@}6Ƚm3[b׫EYt&TksMk\Vxavͮ73_W/_r>03~0#, Sp|; s0C,&>1Sl- o*~1c,Ӹ61>1,!;Vk%3N~܎,CV2eKTT2,1]ԗˬ5n7ӹvL& ~3-AІf ň2:gM`*ћ ib'_y!mi6ԙ4RmN/qvGjO+j+#Uoqcl{֦BUyFɚǮ[f5ܪWiot`q{_旪 /y5k>_f)JԨ&Dz+/\{u7b6p\rŕzm|o\ wxoT<{ی /̉o/6U5`%-˜v/WiÚԛ6/L՟.u\6W١.f';ӯ ex~;>A25ctߺٮgWCݻ&OU?ճɜ!ӾU~=ԟ=/|ʉk}so/ڰwG?o~ Vɯ>R?׋}~_Zo_Bk⏿؈[%ٗr\ dŲ֭dV YI_gh -ڡ_h_ĕh@:]aMt1_]M Hc  򁚧ܚP nQaReaǷ= [\y_hyac  iX\pؠ`_i N^'*n| bU(! `XȊ,"~  ap 1#2"@2364N#5&Q3ښec/_]c5J2N#72H4#;5c!nc/zQb!5 Z$KʤKO 6$J$ ܤBJ>PdLN:d+&)c,.%Z@>J2bUVU.j*. 8>$-梯]#\[cI`b=N"IaN'2R*d'JALfWbd[.&`f`)"f&g&&NriNMgң_kfhbk&i%fmek:0nfj[V&dZc"r.gd6mn%m'nC^Sf`g gl s%wyn'bf(F`'s]ugz{fvbbpgo'|zjgr~bxt>|{'.f}:&cf~Ng^(tV(~gnvggBhbO(ezhb~PR?芦(7:FUK,Z(h鑾,EԎ*iF8i4Q(uVN6%TJ)6*:H~'ZafBRTV*=5TzΦeʨ<N*.өjCj=EjI**(+VӛJ*ꯚ~)f*Rk*ʪi*iҝƩ"*kD-IkRLyfjj*t]z+,2(v ޫ竦i&6uF,*kfl*lfhlz,"vJa-m.z4wLI=ɮC|m*֪NnZ-V BV.lhΑmZ&ά //pR).`/دF0 003pRnn׬GpNpgFێ6ςV.Ά0  ` /h 'X "p 1ްڶC@ L{ kЖ:kS֩/kiUp9WRA㲲,*>fj^kYwB2^KvZ5Qw5:fD6B5^lsh#T3uLkuHSAETvETm?j*A_^S6tOV25*?vk+Zkh+csg1Kvobal[`Ckks>n5ziuеwjc7jLvFۋ+O}rt3[S7Nx.Wv;x/Zp5dKY_y7x]t>Owo78c!o qu8'xϸi$jx#8'PҠ׭"4yy39`a zl 5ùw_9Gyi8k8:k&Jb+ OK t9)yA;:y }nrIGvz׸~w9[y׹:LzK%yo04GoSzhNg^q6 g{b{߁;ք{U˶ûۙ/мaY;&c;qǺ4W:H/z:۹ȿg?:×g;;x'˼k<CzǗ|{#_smI;|<ht}P{3CF\!dA[|c|n>F߼ԯ~> :K{O!u}pE⢾}= U>V\!~+~&"ˮwΫ|}gtn{o!{Շ#& k-l??W{-?Ϻ3~7=@D@,8paB.|bE'ft1bG-j(2CGDYrI*%Ɣ9f͕7]LeO:} ShQGPiSOFJiЪCyZjtɫ\5,NOTׯivm,\ջzrhSՍ3u9RѧW}{W :{3O ,@,/4ʶkP?sHKк95 и /Ԏ1 6~BQFf,q.@LCsIF >ylj+_DQH'J#JzDDd J.;ݘ( L)l.ݬN;Eē9ˬ3M֤rOAsA= %OCCPFsPKQE8?ut4OtR4RUuUM;e5SPgu.[KmTTC%%dW/l `,H\ad蜑bliڗ͠`h7k4W#bX?>ۿ[,뚼6AdbZndí>p.hg~um2 3(AtT^P<_[x/Ct;&sS 3hA}ڛhȩ@%@eGU7gmޱQV [HBF*Ko`HOnh)6ύk#'%sҫ ʬeo:$ByK0|te-u9:Q04e0 ōЗ,%11L`RƄ/w(͹њӼ3aN`3qڲd'7݉5R"%0)Opl<|.ۼ:7PR;@:P3D(cYQ:A4ZAYΌFS%W7,])/gьՔߴfk65jO >TTGm'&S,w{SGzUN M9WT"$*RZRlU4[SS~貪f=jY'3JӯblU+8 ׿_IGabԱ,2IRM2_bV[\WXYx?СTf֤-nBZ4N(7,oֹ6׈N$]]Ltxo5\au`n;T׹ t D] S+d\?Շ-z^*z)WxmhuYUΖoSu})ٞbğl-؊eb7d!E6򑑜d%/Mve)OU򕱜e-o]f1e6ќf5mvg9ϙug=}BhAЅ6hE/эv!iIOҕ1iMoӝAjQԥ6QjUխH@;Bio-Graphics-2.37/t/data/t2/version6.png000444001750001750 2407412165075746 20056 0ustar00lsteinlstein000000000000PNG  IHDRXJ @PLTE{hpAiecG[QG=2@(p]k{;7UWmP_Q㝸c:uXwB+6 `w{`eSV&Wj:r{c`%,ū+ٗ$X[XS+#Xkհ;`d5.wew= k;U;Ku0u1晨+&hq~ϐr,WX%p3]V=z5"XN,`NVWV ?|v]>5Rb"X8]ug.,B ։8LW`D&a 2,4`$U\g5y%7)&a ܤ'Xrbjd>IMŏ` N L)lc594*ҔSN;}fiBUI1o~_EY {,MI1n~B` )`ܡwmI1oޘǚRB1P1} V0v$qe"X7?2T&by˙Mn׵"X ͔ S4ŧW_$Xn7cLGb;Kmtl'f<V% XZG"X2C`~y1zK9&X:sLZu`1jwAp'TU܅`+3!qLZv4!>c`B㧓bfiR3ǧ.$|ˤ4z+v}W_IIMX 3NE`e0 3.i/ŇB餘oVa``1jw gu=l:zOK+:_ M`X}dn%$+;U`"XO}į7BVee;NRv ։L+t!X( mL6&X2 IB?B!7uvb''Xrbf`MIy%7)&a ܤ'Xrbf`MIy%7)&a ܤ'Xrbf`MIy%7)&a ܤ'Xrbf`MIy%7)&a ܤ'Xr lvUM`bkci0?gtg \1}ŃeÂ->vqѕ|XBr|9_rƶ6 HVXd0,7k:s:Cke`Ad`tXY=Ed T:cM%X ?< 5 6w`FE i|?S}=VA3TMV,"j%ԄL߹vCYqx~eY&˜:̡b;ӏJ YŬCrݭk~]:_$; C$ĸ+)u;9)!.U~WJʒ[f/XRʐv`ioڝ(/ 1iʐ1b_,weSA/VMj`L X{2`YX,Jdo,ۑ\U?6= =Ve_bˁX: 7Ή_1MC$fc쏱Br1?9y]%[?Vr džof;# !, _B5CjoMmR Y3I1 3O&$<0KnRL,I1 3O&$<0KnRL,I1 3O&$<0KnRL,I1 3O&$<0KnRL,I1 3O&$<0KnRL{?ߛ?|5%=i>?k`e)|qS*'_.y(|ojEDd ޣ#sERL3WTULc%`4wk,I1 3O&$<0Knz <C1l5*:_=gKFLҸz+c)Q1UU1]{&/%x(O'WO r `+\NFQ9Vc=g`M"Y2R p5NMj{Qu568/cPM`H}Ƈ&$N6g+Zo6:XD1R}?YVqUyRXK rUŽE9%n_wIwߍUsƫE;.%Ŵg̞qi .}zXmXО}+<`V" lTR`O%:u'X:sLRL,I1 3O&$< W3/:,I1ւe;өFߡ7>KeRL7w%hqWNKC9&Xv6&X*1jӽJ7(>ǯ+^'$<0KnRLI1]woډH'tv,I1]igH۾ig X:b.oܖSigPCRL5xrܴC'te"Xڍ+(w?I1] ɀ{N$q_`a9xi` NZN'Xrb2?zuRLUzV)l*=Yv1"XI`ilce'6]1mLc')&{t0KnRL,I153_5.S;Y%TLaEzk/斤wwXy|$ԼIP1ˉxL8\γOzopGI1=k>gyÅrӃgO,Xt*{,QI157U== G%*)Dfա4'X$$I9&XE?%8)̟GDIAN_($yC%y,)& zORLKc-xAWRL?Tc*3U\g Vq] `M?e#Xbf0ҔS~/ u%t8 घn7`iLX&w(][RL_݌%8)&a ܤ'Xrb|m$H>x;*8÷EVzmeLNӛ3kE +A*i Y XOIH+[Hlgݽm.j5cDVe&E*0NZ6 [*1jיc*3U\g VVq9&X:sLZu`1jיc*3U\g VVq9&X:sLZu`1jיc`}~ZmobO~뿆*ϱxC%!;›}s,)&bȿt-86O&6X⡰ߤ*e[ x(8iXa>CtJw&$<0KnRL,I1 3O&$<yӦʏm/''E Z`I23q>~S CG[/29vq'hXOr?τI VM `'N] MV2ݗ,=R1TGߐ`e&,n$0 hq4FXtGpUY-#|o<Ƴo?eLX 3Nê 8`ez,`R|(4꫓›ajix<Q; `a}_&b ۸{KpRL Xd /36,Nf?d [њU;^GV}t 럠Ce"ik|է]V.P8w% , \~`ρ2^oƽ`- ڗ5&6qX?hl E V/w;/cP z,eXr\=VŶ=$}XXZ|ͷ5$}XͿCǍ!ƶ?}=XGTJ ֥*%tN_Z1%$<;-uo:x;wM2>_-QYP V[X]]QwN`}f?צۙ>3m;}uv8o;k-8v33&Xy;5?;3D[Mkn Ì3j[M:71X ;d&׫ +1X[N\5 K>Xa7P蚼 %Zqu V^hoȃ- 3]@'+{(6]4Xa(:;NsQ Co78G>+Zv←aq]xX, 3U\g VVq9&X:sLi * VS S]e# %8)[͛=tCΤ3?uPa]I;ir%XINLP֤3oc%O +i'm5Xj*1jיrcOJ|vq3 ܏a->d,X&by+B>`e4X 2UISjZ|zHE6-Ommpƽk+VH "Xqn470!XENZ6 [*1jיc*3U\g VVq9&X:sLZu`1jיc*3U\g VVq9&X:sLZu`̱>bo+ϱt5,&E1e`1i&ZT;>Wj {Ίs,Ph1&cs,!bj Bs ,{`V+"`PȤzIvL +1 ܤ@c=l`MIy%7)&a ܤ'Xr>Nzc!Ӌn*ϱt~ `?o;l`M ٔJY>g} T:Ժ>J2$Iς5cSX~G9}npptggŤ'N7J}`1q'Xrbf`MZ#sFP,|c,,,,,,,ՃEQj$ܤ'Xrbf`MIy%7)&H-ԏF3pZ@UX+XE˘"3y롪s,t7,}롪s,{spax(9) @Sv;>C˘hdE:K) {>Vhs ;*ϱZ r)G9 Vg`̱VZ^A]!-`=}I`Q`Q`Q`Q`Q`Q`QH X2)TsH3I1 3O&$<0KnRL7 +6 uӉ871}Oi\ 3x0O(ño8'NcfI;QD r,$) {n{E; 3qȿ?L:XfkȀLQE ,Df;]f}Fc1I1]?!zkmqd|g(rb:~n4[&Eq`'ғ*q`\]ܱBUp۸ɬ+ReX/`]RcI1,U.fQ5XOպίRVf5{,$$X+#E*0,UJWkw?a\otwsLZu`1jיc*3U\g VVq9&X:sLZu`1jיc*3U\g VVq9&X:s>Ś4bKYA %`Z?.*(s,K_Xn/{8UX8XPhqr躱MrLWcxS_9@F,&M Sa9V›úRZ1}5̃xZx=-c=)s.I1 3O&$<0KnRLNIـY$TLawVH?I6~Xfzxvy[7wYN3xJ9SO!3CIuM'5i ^`c`Ϗa@YS/#+ڇ0떝q`%=V4x"X/I ޣhx<Oq5)&,̼DWLawVV:K"wVmgyb }`b"X`)e҄GKYRLc`')&,n7)&aH1jיc*3U\g VVq9&X:sLZu`1jיc*3U\g VVq9&X:sLZu`̱2j'|}&T+;E&'9)vjzI9g5& Ĉq"̮V++Z8qB& q"Xj۸#8IV +O8fI\ѐ<:}9fI} =Zpժ˝KHI1 3O&$0rIDAT<0Knz0R Ǭg{}9O,OvbT}XXvy&A7'{,&=r?G3t,&= MTQ1}e'|KnJXLzt{¶1~R~*ϱt'Xrbf)0L8Lo|BNAB˹ykv|i+"TP-|챺5mteUXwꚺkՃkiYH4.//$^_\&aH1jיc*3U\geg/1-/5*Iw:tmIWWoc4JB. NN8XMi8 \`XEx9X36H&t0XpcpjcE|A1V"r/cݼ8F2]To 2#U#3I1 3O&$<0KnRL7N5fnN/E{cW,{sJtR< 2b:}gdLl5oNRy,KU9%Od0-9$mX&sWl3bIf&Z=V>Q$t>%* +:i`7 h. H9 2&սL!VA埮ǐjOx|P9/ɱo ʙŕYC鸹 \'@.ƝhEÇW.IEJIj.ʵPmk+Z~UogvL `tM~-Vł V "XA?Tc*3U\g VVq&\:#1jw!XA+$|=fvcOցpsIw.1P1w+!\W],I1 30it_}2VrpRL̷KKīE9~:)&aU""X9&X F/qX{ ex/(Ym$X_&L'|G 6~$iBҊ&Xl' }Lk6nЛIs۹mE%'[ _xbP2©%Ǡ$cIcE`bƏ$uI[xRoNIWx: 0N'$(((((((((((IBnu]IENDB`Bio-Graphics-2.37/t/data/t2/version20.png000444001750001750 2413712165075746 20132 0ustar00lsteinlstein000000000000PNG  IHDR*^dPLTEQͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ #ei-:Oe'jWֱc wTVU~-_RVa" hvJQͭٗo˕{Rض4{쾾X5g\:< yfc'0Qi?H_5w➡ݻC ߲E?ߚV&r :] 1]l]Y})I[za^+_IKDdbBEww׳%&΅-񦒝pu𴙋m3 ps,3)E=+z"8S)`\c} 2k%;, "~F(V.mF>0.'jQy]4l0em"wnŦ-U_b+A@`l"-t8`C{`"kd [&{oE{`" [&{o:=0{D&D0& 06L! `" MA@`l"C@`D&- 0bKsE@{!ZĶ&ͷ.ɰoB0p sA?b%֜5M9Cm0/%ق6R9i`9%;_bjD^lWt9BL5\22ֿ!sƣ >,g|C6lp͝F)F@[ns #(g_T Y~)90,ddV5^*#Bf>?=]ԵDɮ~#4 ;fлL\$,@gn(٥0RX+|wkֲ=!`ܓUL\Ѭò;2O Xk6.٥GwcbeJς#D vi3@0i"RUjX0& 06Ly{ڗ Z;36Ӹ0:S_nfM\r3 S@mfq=} >7i-FnYEh4.LVh*+RO-Y ܺ| nqa" 9 ~ڥq%י6]ݎCc.Sw\f7R@Wȭꈀ]^R@IH0['!˄IHp$dŸ$씵;yub\.̸i\VjXȝFR)-oqa" O;KGLDJɀ] iYn3La5-0d<$LVťpf ȝo3<}i\SS`D¤"{}yT@^mWtӌ_C}3 yN@пFoΝoqa" #`Kvºn/EFLDPIHZ7͉6Ӹ0T;vuSsDP! <)Aa&}3 yPr2f&G78PsDZp>jXH |3 A@`G+A@`e`2O y+A@`l"C@`DZ0ƞLDнo}3 i@@-u]:-G`" v f&Ҁkpw$ђD 3DPv /6]ד<ђDT0&pIN g5w]l׫/ Q,-x~;Z]'\Z"nӑ[kN!B6+`͹TsN3XNΗX]O5wM%C@LY,A@r0Ka~)YjDkXC@`D&D0& 06L! `" MA@`l"C@`DX{%E{kF9`E`";ƞt?nDkXȎVߪ&sVO :v \.fV&'Oa 9П%mOBlcIvS`D&F>>xw]g5 ؉ jnj[@7ΰo/7xRK/)՝hɅy2TؠL:15.xm2T N/s5!C\y-0eP@˕mNBᚳܿVԔoƉ\G= .x=2Xs+sC\HQG-濻34_QQe:1m:zsv I+w'0o.vSsDj h[ 3f&RR ?HI7CK;?aDwЃjno=N]27W~d'/o}Hl~uٟϟ3̟Զ4zW3{ 6󾃖ef2+mۯUZ޾v\>pmD@,6P@3Y]G@ 00䎀v\:~ Vлw(]p n ^^o+`t$d Gf.8hIHx2`-=pF[sݶJxsY-Ps:1 #OB~>a &Ҋg iF@`D&D0& 0Tۑ^lR}7Ӹ0&tLDPˑv"C`" HIn0v#Dpi8!39`E`" WB `"Mc`NBƄozh3\-{d"06L! `"U<|p'g5w]l/Ǝób6ouI} Oi{J9`E`"q3g | z.KP3kXHTu7+vJ[!jXHTDC'!fIejK$`p  xcaA@`# hG( v_"0g jnjXHIAeW` \-f4.LlE]bah5~x&kXHQ"v`\.8 um`kaLD h$pê]l+)*ۍnZ@.*D`"Et'k nK7'h̻9 qfkXHYf0b75L0nqa"%|0|&wN"0L9`E`"\sDF(=wG;Ea"w @  "0L9`E`"\sDJ |aA@`l"C@`D&D0& 06L! `" MI0m_ A؍nަ8d+ ƊsprLVVp̯W! f`" `tyq[Dv4󸑪7Oobj`/'҂{Z@w:=, 9uօ `0lVX]}wtN .fiΫ_`XOv] %LMe8/ċXzah[wbpǀ;1DPq]'&V&D0T^U20& o**֛ #CuP2滖j߯޸i^;.0L١TK%{&)0W+?̀ `۠+fl,I# &[7.6J]^7 ޖsk` L;uu%s/u۾"y;mںի1QXvxx.3B5&kwbL^4a驪KVn=!ǀ:k[tfb6d %s/Q/Xw (ҳ 06L! `"Q y*HI $UjT A@R5HI $UjT A@R5HI]hIENDB`Bio-Graphics-2.37/t/data/t2/version19.gif000444001750001750 5352312165075746 20124 0ustar00lsteinlstein000000000000GIF87aQͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ |܁!H] n6ހV_fR!qIb~bh$nh~c(Fxa/r#8c#&)cDiEB"RRcVbiaHN7VeIX#jg#ZRnFdN& g^frf)&BY执I5 }N( 6\李h2v:2ڪB* nffFJZI(ohbZ^쳜2{ɪ,x#@i殷k㤓fun*z{oV ~*0ΒInv.>S11O<+nrޞL~ /+(pAsH2\nQG B ے<0<']rdGi^s]$ýFuR]Dq:5G=z\&ۅ#.7M1&+>r^G7C?ߜy'.n/o'7&=/Wogw}1o觯/o H>ׅL:'HA$7z GX $$L W0 G8̡wP$5H"aH&:PYX̢.z H2hL3eMegcYTD:nch1p ǧQkL"FNϏ-!7JZ̤ Y$I*ⓢ(GIRcq$+LR4,gIKruY%[Jl鲖 0;xBv'a9f:,f # *$2nz~Ԝ k :t@")lʳ̧0߉Gܳ =5IЂV8Kj"D8N2tX?͌ +h6*8cN3P1~nr.eK-:3i{W{s~GdU(^8'lGRGWc ؁u&wfW(Y'(w%X h-G6a5{*XbsF,VC(,:KSHbB)HI؄nx؂##xD}I4q2HൃU(WXf [d8@qhjX_l]Eԇ%ByGFHG6ȇHRwvf؅in(-&}F3Anu(coHb8A(oxe(GHv7xzXv9X=XTS8_X\xӈFȀ~u8TȍSȏ)V8樎؏ɨhؐ8` Xh#)h0*y!)r5)-ِ8Y8d?IfA)));I'i:VUi\^`yE)G,$gy6t]jZ:Z}'&]\Za*:hizs56]I ۞1]뷠J Kjeհq`uHֱ A^a`=!?"~:`H;.+[@ D+ZFh±7_Wʁ0[ڶy;Jj+ ˷Sۯn*먁;J并kG6C ;:\빭u*2M$Wo3 re;>ZXP ;[{›uѺuë3woHһ{;:KX#{{>ֺ;Iqeo7o۽xVG+ih拿kl\UȶīTʸ8Ӌ,/(L䪻˻+' Tbˆh À۬z+1,3:\HZK|ķ?ܷY뛵!k^x˿\-Ţ{~kƠkU<+Ź LmTǿ){<},Ȗ۾CwLyLtƃ,Ʌ ȇ^G7KeǏ\iAɁLɥ/ ZB. ȳgiǙ 9sabXW¤|ˊN]IYŋ,쿧ܸ|o+i``5Ŕl̜s ՜(d_rʹ϶\t̹f{4[5 SL }I} . -|͐-L҆LҖlɼl;V\+ެ#'1=+ºMa[|%J=t TŦ@} mm^ Zr6JOMR=b{:en7QH2\35 %i z}\x;׽4sֈ-s#sr̺PHm]Tolƽ K حb{ݧ7Է]\!۸K-Lܳ-.]l XT}*UC䍺] wFɍ[ދ\ӏuى,QDM0͗L n=.~٭%mߥ+N'n=],Mt--.1>?.=.3E.A. !#KnM.IȬYWYC)eNiU5B qO.yn{Qku7M[.KmNson⏎˅ԉߵm.n7kgݟn.Nn秎癎g.}n]./i]eY8>:تηƎ໮cӮ_eR4ꪁn m۩ܽn뛞M-LHz ȮMvvrfv>Y4~n4_qMΡ_޲[r,_^cJ#+>R U%9M޾caZ5|OZ/HUT,*B?F[F~$,agWe_Y~l~jnJ~]Ѩ^F㥼e {ؖڊ?ѡܣ-d˵,o?}ßo¯ҟտϏ_L>Oݯ?K@a4 >\D-NX"F7jd2cHIXR$ʋ&SlG0gY͝.wYX` Ea0CmSgΠS;JUէ2~'תek5kZdU;vܸ]뾕׮Խx%:mT` FXbƍ?Ydʕ-_ƜYfΝ=ZhҥM'&z0Ӂm /_guv뻹o׆wlu}\xw/&BKn(Bʛ3Mx??}vo\l{˯\/n\;Р[cT0B Z  - B A ;M,C6Detq[\FCpFqFs$rǖbH K2I#ܱ  $pJa~r,;B K'/,SF5cM7ۜqM98SIټSL8|3P }PsNDIK>}qO)T;4UI,UUK]EuUY_TSauVjQ4{S:mUW?6؁U,6YZbˤVhgf Zg SsUmTsr; |]7d0x,D(uO21`._2~#Fl `1dy)6zM\yS'7 1ZXdS63mHck]f1㛛 eqTZ;H~D+jY"gڢ+FƕY6jmtjGᴍwgGk܃v\,Oٰ ^񿗄VV#!fs'x=?q'=w}u^G|//oCu{SG\N>Ǖy}u7\uEpߣg7C5z}Ǒ^vPҌޞ'2@[Zf@a/}ZHos AOqg`/MCձFdN*?PNÙ c辻χtˠvļ!+ ?1BagE ey}&Cx|"قD9[ .!+QD32qbTew?8)|'HΆ;X ؾu) CnKj^k &H꒒c"1(b"ɲw> h8FB(1[ 1}S)tyrxf3zbܦ*js4IJqr_ĘYLU'|44l.]dW?O!ƨ[A鹽Z cR§Q PRtuDꪍ:D@P.TwMHSTMJMGzәs֓)9iiT:vzT4.EP Ӝ6KWTCS~ԥ VjU-J)9SUT] Y֯6kZWJ散Z+N -,Z᪸ukL[F]jb{Xjb%eV6~uiZ k{ڥʶ-m%ظX}[MT̲Qll=2շPRVnEv'-s Jw-v 7-d ogK鸣/~`+Uwoc^2᭰aZn౪x,a6A)=[cIq &YT Ky=2|Y+w /e$Cag ×u9|.9A~󞯜gÊ΍fl?K:Д/UF÷f~4|yKg:ԛU4H˥>uSV>gUCмuk N-Mlc:>6 hәٟe;FY]͇eft׋g.Jjgؓ&ýmh[׆6o%fSs0b"/ojKx1.g[&a𔱻 /u&Wyt︮oqK Ɠ FgnG0#R~!#d9W{x7c:/'{?ΐOug__yg^F}ײַ\'͑o;~}e7_cWC/vtDߗX^Wq}#YnB> '<>c6k2&«V) =S@8;;?D;ֺ@A<?AD4/q@A l#Br9?%̧ ,!B2xBYB4?CdBsBe#BiA:q0%<45|@b D9ɣC@\6BԨ?4<ܺ B;NԹQ=& ̻DWңCP$8;yV# V|Q$SĞS?cD/MC<3d?›[Em#^ĈQD9`,bAiNnҁFk]Epŀ+-q%=LG]$D@ܴ`KPcW$9ă,?)t\8p|1俦q3hĂLHjp %;yHlHduH1tI5\\m@əlEɗGLFXt4ClckGJ{ɧI4D ; pRB > J˵ ;SJK__a˻˼˽˾˿ ;KqD-Kdˀa4L|LlJK+ YLLBˬd֔<<SִMdEϼ̴:}B5$ab=82stK9L弹݄N̜RAl Lǫ9NTOl Ȱ\Oʔ@X}4z˟ OM O4ID$Ojs6<>P%P,IPTkƜPP,Q͠. mʌQ-tA R\Rw,RWAoE@ʥFhT4[MLF!M-;2<deS^Dӥ`ZqNtLJWc~T=W ԀJwRi#O-KlXnaW|TX2='ؔHxR:NpZ#OHUuXU7+SX!i ӪuYGXSZXOڛEoղ%WpZR3٨%R}Sә5Q W4W}߼pʥ%q ֗E%ֳEBMMۡZǝۍk>&;6B|ܵuRܸMֽ]-э\5R]STZ 2ɵ]$]}]YNs1^7U^e^=թ#eP1С\ܦŭm\qEшH^߱ߺߌ^ Cs_¶=]-U[ ` ne[`|*^n^_]_N-=BqHS ^u]y:ot` [a$~ALHb,< a'`mb,6ߝ ڨY5#%b bطskXCo4;bn~,a V[1륃U_D]a_:g=!&kRc7>M$n*I~eRT{Fze&V&PvfQwHX]ƟUfډ EOZ>Bd:n.ef#[c#D4[ d9y{^tYbdpcme]bΨsvcnaWo~hފv{v.fshe>-}膦go_iϴdvV\uhfen=XыhhCbEijg&꠾ߢ~jg^ЦjMvjN&ܰ.fN~i6ag~k^akhhnk^i&VjFAfižf"-gj&6lFl逆lҖunliC(;i F.Xt˚mkiN0~=2n9m6kjFN;$nl?enaXtn%nkjgnfpokNo>pjn.Q.=޵Fn نnp OjnA~q_qloq_omkq!o"Gn%Oq&ò_m۳D.!qu9_ +0K8vw#sJm6ۼ#9#/O;'ŦfdOsβ\5`tvFYng(J/sC?';WuS_=OWO UwMu5P`<kp"\VWdOuPl|lG7+[10_c.ua_anq>]?mLv6kX_$O w2rcqvm ow%/?s.w|sw3w?'xtxf;Lxsxx_q.yOymx&x(yO)wHi{_yxv׮wqwlryyoxήxzyz־nzoy_z{x?i_ayz_{zuziwbyY{{ZGr{3{z?|:iOm?y{u|}oz|/Qj{w}/z|z{{|~_||O}to{_|uǷuO|}w?}'uI{~o}o~}.w[D'QKgWygǧٙf~3oB+K7fT+,µi~tBRbMܗ\gߚokXwĿr琛?>\8ؙO;{hgϢ.Z|3f5sK}+ׇ}~ٽ_7؀gYu w}UYihulF(b܍ț Ʒxbd,袃V"4ާ h IBeVZIƦU5㋍X\Tem&eqa3f & )$mF=hvIrzb tiX'_f]9ڠ g(碊zb(jnZ(9)^jګꞭ +gY,*^, 좿jZ-]-b6*Vږ҂n2Zju>KnsN&MZqe؆kn6ׅ@q*[҅b͊a )>1sg߮W=K|cԷ3$n,t4 fHldĿad!H?[dظ)NP*#% :;OC X16VzX;$* k I1W݈Jr|_"Oz!YXT$̙Ƹ%.s ?a䲗إ/)_IcIejL\^ df!^.ּ./ ռXYr&:өNeBO̖ -r`s\'#)ael@h͟1~Wd%x&Ne'D'wRjh/SlŠZ# kLHBuEUd9ɘ47e, 9'34*>DTkpKWS*էA):խ& (<*O^1iU P0QRԴZ*׽, -i~j:i׫u`|zԋmd+W}'?ˣR\K U4ke'-n>bloGty?s˙>x3dWA\Enrȓ!Kvt#e(wbtg&=p>:_|OW;fV\i;xSv]-AN܅O\FOã7|d3|cwo8yևm/]Ow]}[כ^ǽ;_g,?5(}kOOvG?~蟏{ǃ_|޳nEL_~._ fONdPQ͟u}%1 Z@M眄Zq} B F`v߼4z}m*X V\ F U$Gn-:fʟa9xlH a_ z }atȎ`P!ޡv 6&!}Ӭޠ.\$1">"Z"  2W$(*`%a ~b-ʝ)/*",, -b-")6b(6(&"$a !Mnt(G6V#i7n!*"/5X1ʠdf1c`pcg#BJ(=. ^j :&c9Lf% 7nRc7Qiю#RU?~)F2:#:;z>"#t4O&U3Jc)N_ %HN4BB'NK"OA:V#AJ\;5eITPa9RTΤG"%->0\ɤAcTE:TUfXΝ2VF1 R6jXJTn58 ZFUNM~dRe\BQ`a& %b`VQaY# fae:ZN[Gchd0^f[Vcbjv& dWFfcNdjfLr&_lfnfbbPofflbd&emr"k2gQ"nJ'}f$jB&u'nfg 't%W&a&u%l'b>gGryigyrg|F}6mNw§hzzf{gz^hqb (ij'czg"{g:~srhhgglb育gJ(pRh}V2hZpShg6g~hBhhzbhhxBxp鉊j&jtFf(v'(&J"N ))(֨r]t)@)کi:dpJ`a)B*' )i i)&. 5 yT*ljjzj꒢hi5jBVj>ɥj@q*p ꧶Ak H*+**ZS)͒j7kjl*J6Enj+"+)|?Vk*l2jzj+kzlRz葶kȢ쬚ʬp:FlnfĮ,Ȏ!' B llzN"1&6 f lBbez OOZl,l]&.mZVжƦv,ۦFH`%y.Ҫ->&.hPQ`T-2.Ǻ^[ bnj*k&m6.oormҪjIuQYaolmnz*(ͥ//Ư/֯/P%/0~/07?0GOpSZOR0go0wd0 0  0 ǰ 0U000 1,/17iN1W_1go1˯1oͮn߂o.2omRmن±boq-rjБӱvϲV! o#r&&E!S%q'k*oFK̍P露rc+0,ND)7-/T,#o1srm#ò#HiSO-M*s*X53[n6l 72?Yr4c4q9+r2r+:/FiS3a31 g/A:3$.ڏirA 95r6t0'rt{tHCGHt:nKK">s;/tFmOJOnLtbK'R/5S7S=n(GWU_5F/Dc5XXTTr?{XZuX/2b\XkKW5_ u QG_a1`+`5Rc?v'Isu^#d_6fd8>5hh6ivY/TSMt>۴[4Bk]Sg4gv`tG6tl?teöCuQtPtq ocsW+pPwr#wt;wI`eyJwuM&nxP bvJw6ciw[n&܂CVs;rsSw,5Pv!q7V8S2;"o_7p6z@7_/wvM9@W8lCo{_~;sĆ$#8t8U`WjJpxG\-zdxv,Is|  d`֑x9C#.-cCu*Jw9h!bcCᛃ!w!Nv#kt?PBMy 7378¶8S6wxB;ɖGzO{w/4:w_zskhUMwhs/b0;% {?7O;Oc^B1z{+WzW{_g{{;l;vw븫9v`;<;7{zc:SۯgO;kt2I-[c2}S~C3~k~>{~~ϛ}݋{i~Mk#+#+zS[c~s[jK7|s?rʫ<@8`A # Á.l(@VuqƎ5rD'?dI)a\rNj3]4Θ7}fQFyiH-FmsRNRE u*OY{";lXKjkWmY"znRW׾5-^kfݹg lx_ &W2媕 cf[3Y!]X¤C~pkqu>ssϥ]|Ol5t[/@ջAϾ?Aws0A %3?GL< e\Gj RGY4' ȽT Y;RHrP*kH LGđJ1< )+0ռMuDsK3R.$sN[s7 NRK"\L9SAG34O0)}Hab'Q u1S=FIPCT(;%1U8-TMSHo"U&R""a/m V-o=Mg],i㜵DK]JKTh3U,RMa'ckU<poV[kevw׼yqOqׂ`P:ءQZ؄E-P~[5ۀKCBcWo#`\Pfu]؝zsqָFYeL\ANi#Q6^ZY3\ ")cuyXTUYzm-Fujw6duf汑E{Tَ݉c^9jΖ[|,Y{[/;'GEږMwwD==p{wgUrm~W_7hkw9t?x/=V|x3w~?uSOkO?-/K[j!t{$ π)+(  >r 3 R "عσ+ 58{P64UB PC};6B0: #"f%BтF` (DrхNT8Ik\Qn-RTS̡? d #F)Ĕh+qa +zDvHk)Un<$ a(HQDАٱ&0`qqmi6ʼn a _!GRQehLVS%uH$ d%=9DQ,&1O3QT9݃0Lʲ`<=ϘTh/΀cTA239r3cY49Zsf é MnDNLhc?z7o'x%NxSWx =^qL_\NCrk7GpS;jYi\:q.{Ntwӛ6ʑKֳ=99mmO]X׷{vbw{.w=qa, #c{NwG{#%>"vM_<г?S/y#\XpjK>ySMSyu?G=|}_ݔd8 s}7=? v=g7~Kmo0y/g/r(dg]o{O(N'{kLr&.NP?nq(&jE'ufh)oْږ0|zoa0TdM!3r0 P0pひCP UOP g PJpll"F 8Рp pM.,{B' P pO Spebqp | u/L0w1ϊr)0tz sOk=*qqq.[ 1Wϙi Q58QV sNl)[1pzv '//R _"/$GF]C%Q%5# # L,'ur'y'y %r0`q Qg'r)')2*r****r+)ӐU(g+,s2+r-ٲ--2.r.-.8'.Y/Ͳ.s0 0 031s1S.121-21335s393=3A0)S4]F4u24Us5Y5]55e%hf37?6y7{S7sS/Y2"=r"C8g7R9e+G&)6r82%'9Q(S(IdJ:6,<_2#%OcR>=IO&ۓ>[>;I?ߓ<3:?@@=&:rhIf(&s@"4~Od{EcRB-,B]X`\l<; Cӵnq/FQPXCpDsB*HICmCq(:sմ1Q1z/FmHoI;F?GC?4LgLe$%n%FI)Sj,Psٍ I;K~:*hP AtJdzMK4T'bHQEIBqN%OSAScStM0UcI UcBy)fHWknVSVAFkD!QcfV;J/uCtG367YW[k[\I4JM;rV5]Aq4k@ѵ]_7 'UMu?!24\ٵY)a9561']4}1ī:v'7veYe33eu<b4WH1]vgyg}g2`T O`6-viivd6i61m:bvjvkI6k T()zN(AV_kaVY+!1^)%QV]6lE 1u_ a?66LuFTp-6mun7MW\USqb7r7!+qs2WZUt UERC7MtUqOuG!Kur&KNrgDUDt sWs8p-wczTd*WSzߕ^'[^ONhFw]J%OIcRWos/su?/1*^I{MB{1Lwosr[) }p7QȖwj/@T)q.w8dTeke #؁~X2I3frw{RtUx\t~w% {R\h*n8؆b$u~sCX}X͕vo؏vxs5GEQQu{Y"'xo-*j!hMՐm6瓫PjQnX,mF+!IvזhҔOf62.̂م8rK+u+>9b1V4/)Y*WD1]YsPsqy9#jF3mb_T-9y1ysr*_A٨%tvu#9k"'Y-BD96 Wxzoԙ'[ CGٗ9Nod;c7Z|95ک#ؖzY^Zǵի:O?Y:W/waiPYaZvezo/ۯ뚧S7nV'ɘ :zLYZڱZ嶅Uyiل[{:wb)OUe۰; qY;,u{g;_[yܦoWIw;ۻ;aٷzgۍ{{5:9=;JUۑUOC[_׼ilGW"{| []{[8E8CÿK\EWl;$g'#,G\[۷ù-M|bz0RsBWڤKvBQ,[i ǁ{i{ p6̹Sܥ.W3fy6Yʧ?YãFg<`{/ \0%v]oNϋvE~IMQ>U~Y]a>e~imq>u~y}>~艾>~陾>~ꩾ>~빾>~ɾ>~پ>~>~? ?!?%);Bio-Graphics-2.37/t/data/t2/version13.png000444001750001750 2364212165075746 20134 0ustar00lsteinlstein000000000000PNG  IHDRXJ @PLTE{hpAiecG[QG=2@(pn=NELpcOK ևD"X-*GQ"X-"X-"X-"X-"X-"X-q,&,e)6zromVOH:cDnnnnn(e:YՓt}h]YgLe OpMe OpMe OpMe)i5cP ֈ ?a&GwztmWD;L/B؄OT}0J6!IX3LL505k= pϚT,[̄ai+S'Scwxݦb+M߆JL0ޚhkc=|KLӗ ~ͳo ~UrU(P'hヺs"M,T-2^ ݄,L&ܺH U~s8>y# ;Pb5lK&ˑR+[L_b`Y0?mH@_K a e^o1}jVo Oܧ_biKӗX'Xn4⦐i]RcyT"OKLZuX#X'X2'X2'X25 $&GMez'~z1ftC`i2Ixc'mp~PL( }KL"X/0X&)λ6So4b;#B)c%&X:KLƒ A}UqxO,Ixc#Q8mg[~yVXnJ)2M!+kC2.,n/ꯛ2<.{DdMaEx)Z`"Xx_]`5ILbVu%&X:KLZu`P5_I`?#?s"LbժDq' ?m*XfŏE#\kQ'X4F+vcWLN& MMxf{6i<.śB iS7kcm /1jUYbժD<ՙ6v;Z5=.jT|n6:&pNK6ޱ;XXKr0S -XٻM}So5ӌyH.fLOl k6SEwBc7`auV&>~32VʄKL` L` L` L` L` L` L` L` L` L` L` L` L` L` L` L` L` i$%~X/n1}ڙ|zwug bÃe͂->lj&GWw`EJ~02a]T0EӗXX=^dh6hZ.)4N~32ňMa ;6z4i{9''wbVuo{ME|(+NȚ;D6>-ضD;w$X+v,hw=$W>x$W]mXJ0< 0CKpaӆ߲bE%6KR6>riu]g1 ,<5 ,<5 ,<5 ,<5 ,<5 ,<5 ,<5 ,<5 ,<5 ,<5 ,<5 ,<5 ,<5 ,<5 ,<5 ,<5 ,<5 ,<5 ,<5Y{gqPxg`3 X+KI"X/3-6X#ME2b9FJ9u/R짗`6չgh-u2O=w{18D/ⵘ>%j %s+O^k1},7f?S*yyO\rS^bEDd6'yl~_;]:%V|Lۡ````ᚖdoooh n3KFLҺrwFpLttww֘j1{]z&/%*Ӧ4m}Wt_]m-4ll[woZԕ+vNw`Krv%~ִ\ɝۣMaV [,N;$òJiB.ǥxShⷡ?mZ>1ymm dDĚJF,\S oD`Mh*"M,ԭ`IMNuŴ|MۀuE"XgL ֡Su(uƔ``1%X"XgL ֡nN-L/ 6.B V?c*Xxk*Xxk*Xxk* g3/vwu&X2ւw8l*%{!||LOsRi*5<Ɲ~?X,\Skd U-/:=LUKLZto}L4JKCKLZto}z J[L_׃?``T͟·Tl^`tv,`Sj ə} KLM|N沜vj0*XG*wYNE; T XӋ`+[ɀuxַI}ev{E; T`]OpMeO^m*SUb 7b `D41@Me+[,Su6 ,<5 ,<5Yxs1]xs1Q $[L+wd*;p<[|1ixb񘰹g#nzoqG2=>gyTÏGO ,Xp*G,(S;U5= Ge*S"{1jShq;LIwe?Q S}  T_1A.XxC\xT& zLKc=xAL`0՗`,1jUYbժ+<u9/`XO3L__"XLej>=OM.SgAl*s,26yf*W```t{1lZd/^6 V!XU]!XM,EEVaL`DVaL`t?X<@U]g VVu%&X:KLZu`,1jUYbժU]g VVu%&X:KLZu`,1jUYbժU]gZ-_bÃe'X4qrӗiShqiұKUK T1:`e6,\bHJ)n 5-&XŦpŽM Cx:/N$NS,\S,\S,\S,\S,\S.Sť-f-n5! LRLefI>)qƆ'ƙ~Z,3Ocgj [lܪPٔ`mh~ĩMIJGZ̮ ф7,XÄ4L$/ZgZ̍n1Fp N6tdV,?-7mb&< 3NjeXɈef2#(Xo MvmSbyfx ~1X'kI޺D4198ZAKno oKBfN8P F,IϢX'6u}XZ~{+c>4c/k6 Oyu`5V:?c0p'wL>Gw^oߍ"쵛`uu"}/"6;+dSf ]_Tl +3`x3]冚нCihrʰ+6\oZa_'X`,X`śB`%HZE]7j77KK`6C ݈@'+)v;4XaW& (v:E#V[/Fv=h>+z7v˝kv]!.,ՠU]g VVu%&X:KLn @- VSr S]e-st l*ӭnVMNS ? PӻL;r%X1NLnSȝw2ޘiJ$ni'}5XjZL_bժĝv=` A{{eDCdm">0%$ ]/ SdBVӫd\$X> 21`G:LM^VM+%Z`:n cBօ;7uٴ^mU]g VVu%&X:KLZu`,1jUYbժU]g VVu%&X:KLZu`,1jUYbժZ|6?{/o1}s֗`T L)EL]&nsg|QcMo[ܳw`B;~}alb*4`YV(`c L?M|?3O˽_>Z NUK ZuX_Me2'X2'X2'X2]wē feCh1}XC $m u`}$̷n,˿;hI(qZLa,?C̬6&k'ax076I`6&Lzeɂe? b>~)XnXќоĄ|=Xto}>+3#zLB{4{x~X2'S>~5X{kk#T` 2`icEc*,nT&OKLZu`,1jUYbժU]g VVu%&X:KLZu`,1jUYbժU]g VVu%&X:K %8_a2m"al*rJzIK g5& Ĉq"̮M=8qBb,}X$sBX[b-WKm>9fǦ?l~xX=p*K(vS,\S,\S,\r0R Ǭg{}K֨'|0IӗXXvy&A7'G,^MQ?G3t,^` [uMT߅%FN|+i+9bXwmc`'X2tk(^L:]|RB. u@Rs*IDATv7Ug՟wNHPAqFuUTv7w,$7/o\L`0՗`,1jUYbժZ_Yܗ{ޗU-/qS$9Q;6"Ӗ˘+:jmYU7K\4e Izj/ݙ.w\G,^/}vleb?b$ h'LC.0W{S1 ` uXfkȀU{WX!jS],2&B*X[a,ͦYss{|RIxb IG`rAmS_7=y>tu:GH3RqzajHKqZDZ$**XDQЉw.Xu_0bqT'/Ǻy͓MqV?oZdf:)3EQEQEQEQEQEQEQEQEQEQEQ7r2##IENDB`Bio-Graphics-2.37/t/data/t2/version3.gif000444001750001750 5177012165075746 20037 0ustar00lsteinlstein000000000000GIF87aQͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ |܁!H] n6ހV_fR!qIb~bh$nh~~c(Fxa/r#8c#&)cDiEB"RRcVbiaHN7VeITX#jg#ZRnFdN& g^frf)&BY执I5 }N( 6\李h2v:2ڪBnffFJZI(ohbZ^쳜2{ɪ,x#@iv殷k㤓fun*z{oV ~*0ΒInv.>S11O<+nrޞL~ /+(pAsH2\nQGB ے<0<']rdGi^s]$ýFuR]Dq:5G=z\&ۅ#.7M1&+>r^G7C?ߜyƶ.n/o'G/Wog;G/o觯/o _FRL:7@p̠7z F(L Wp~ _H8̡Cp@ H"&:9DGG*ZX̢.{^ H2h 05eSf8McJgx46#GL" =>#'IJZ$|!0I (G9@MbK&?9V;,ꢃJ 򔬄.wKro\LfbzE2Ijڏs4#MmӚ 8чi&9vS崣Ub̧>hnQڧ@JЂ"@-:s( *9Zlwt֚9fΜE?'CCU:/ `6S )NCR2OXR Qԕfs`:}jKT洧鲒zѡf-l:SƴV)UcdR g\kZ:b\ַ25A` KWԬ5]k#d򱃍ZWjA[ X*UVjW̵Vt[W"! Z֭b-m\X’Ru$E.4&bmc[F4bͭY&[vt^"ָXETYI95Еx5{ޛWrRj\4׺oY{ T]hy#K4oXslsY.pV+\"tɉ;>u|[u-H&,xCjz|VӒdN} mDYnueqduh䝭ixmTsT qn6lEXȉ^Kb?Cuu.4ۭN{ݧiOZwy6_ϴ_s L}mHE)Wo}g~ɷ%b&`s~{H|ŀ hh~uG zWX)~Ɓy&a7xH1|42p'u6X赂؂D6~GA"ChJV`#g=(lC5@ xFMo8h&H&džC5}z XrxXZyWTxaH{cȃȂH8U(vV{5sW8󧊝HxXf'(Hwx8((ff(fHxȉȊhj%Lh(ghh8ԨHfxR8x`ي)ٍ8֐ȐY Yhy'8꘏,.-2 4Ɏ|H!i3)8yX*))ؓ9I9Ki>QiJyLG)~: %I"hɓ$ٌGI^`ɕi)ENIj (LRhؕWrk4%p1a{iY%t-WiYٗMɚYXa`e75)e!`f(xؖgy )CP1VKtJ'y[藎iYsɘIoVt4tWzC_teչ;YyeA'x.Y3J+aIs ꍗi)1Y5.ԩJw)ʠIietht79)n"*&Zyy'3Xz䕝j9ɣG*َu٠KJEi|Y9Yz5Z]ʥFUJW I[kꦠiSju1rizadp)7u|zPR:t ڧeWys[9{Eئj |Z.uZO&S{sjʦr z]DZc*;꧝ iCJ«ڗc I髁Zx.\ZJ5T4bQRrv0izꨲZ:JU[ʩ+(گ J: [ +~˰Zꪳ ijTOvb<;<77VhX# :<۳>@V gZA=J9; I2U8+`<{Y&su"A[_2ej Tkۥ"{q۷J{u;3K[I:;յW Oȷ[Sۨ{Ⱥ 0# vkq *;Xk۱ k2GCk {-+|x鲭gJ=[Y{*#K46+k{{]iዹ㫱˾ۺݻ˿s:YV:X\+q*L5,![Sl{a++˸lä?:/[̚uWHlh,U<7|̻s'lz̽VL:iGGL.,lG{ŗlK0 zǑɛ r x\xKclel}P%bFhjַ;XI<a,+ fp;<;I[Kɻlʍgk k70湅;`躺#Y|;eLٌΫ;+E\0#w\L< =yJͣģ,lL*,s܄KѴn*MIxUlҨ#co;9^O\.A}Zn-o~\5b`˙܁Z2> 12l$+vCBMn}ᝮ}r4 yE~鋫N,פӅ/Mmn{ޘsXNߥ0f,k( &,S0Z+ڬԮۮ{.rf6>+>_  /o__ ?_e^/2 ?/O03o-5@91FG/6_AKLMONh1CT:I_dH/\Bil=/p]f?u/t?{ícP?H4xw__v$_ox{59/o ?_ĿW/Rkoo̎P꼀\OUW~㏭VQuXXI%WFe_cI@ DH A|ÉFb'7z#F)D 2ȍ%MT3&K$?brIy3'P7S͸E6,l԰Gd SCD8rPgt0 Eȿ,k;qI;qB1hJ>dF<*IM⒰3J K>; tQOI3D3:I@|,Q`F)!%?* @߸34/ŊL3JR64W%P>4P,Z_Q֒Tpm]iТ 6 cMӢhV(nZXQjVu%LL5@a7-3[,v86?u6Mh]OVde}YˌQmU]Oy]XQp(ӅcܷS煸a \&Nw,S=C⍯,62eӮTȜ%FxV -  -Znw:i.-9z:f9i[GMe5|Rəen;3ߎ;VLkR[ CpM&"wb!>p%o%7\K#pZЄOm }dO?Rqs}DރTw~wo}y١*>ygw[l>e7z|˿7{r?^|'?yS_X!^Y @~ % }X;8o,!D@&z+44xBaPv)< >l CH 0#WA0-|`.hÃqλ" ]? Q$X,q&B*ƊO9QykG/QatVg/ rSlA4!EG)\\#8EBҒ{$yCI ?9LRvp4c&>rtd)9J/L2"EZrrq,c-]E^Vј{ YdNsBOhs%@[ BH=RTxUD τTE=Qьr4IAIptE_3іzLO |t,ŦOHӠ@&NIԤB}HW]:tL (TTMZQO^$Wө4Vm+VU.bukUmֈuHͪ_ ع-Xؒr-GSN԰Y.Nw+ck敬N^ZZbV[,WYYךvm,hڦճ 금 8dnۥUȍVgKRwEGOBVUĹQ$bܷ!W:S{ZZmF߻\쮷Ns{\EWB"/[27jwܢZ׽  ajXi_n[̯6pQfHR%Gq5 s ]fנŰr,im 0GOB^qudU0棌Lf68~0vLW/Tp,& fw׵b5/4>enzr9]8?In{s~sG=3}5ױʏ#_rO["Z/>;׵ۺQв<{G{wc=.n'U؝YnÞu g/+%߮οoy}{aO+~Qw~y7YJ)?/˿K={?< m;#>3>2 6 = ?ͳ7 < ? >j%Zz@l?Կ8+|l3Ӳ;SBۦ%t'()B%L<+*-7`BB'-0434r@!CB9:;<=>[#:׊C?4CC3y8 DC⺜7-ĝBtKLCH2*$u"CðDM4SDED+ETE t@ J4E^EN<5YBı'cD;ČWD{dt%2FhF?X;>gqD`FW$h_4tVl5BxD\ytGVG>lFaGGxFfz{<ȇ4G~9|H[\XlljqLH|F?4ɑ$Is‰,ɔ HIɌ*ɑTÆIL@>A|Kʟ$ƷDʅKyHK\[K0.a,pʞ J|3L;+U]AE˓CJMJKl̏,J"\9K _1HSJ01BqJ{̰D̹<ʮ$ʼɛ Sď0|4 T4N ғK,N=ް d5)?' y $<0NK լ&AY(O5}Q!BŌKϚ4 NU2F8Ly2oL*M =P %ѡ*кQ 5B1GtBϋ3DͥPQQ tR,m *Ł\<J$RER0]"P^j}G 4N$R>uҮsL@ 0s=(OTSLUUT=K7e8M75T^TWV&5V%U5%`SUB;M@gi-5Va \}<@`A[Us}TyEUjEW\aRY%W]S}UXtWzVZ]F]U+5؁~Fy}{=}>5'ͱjVqVS%X% VSxmyzWAX Q9{QT-)P=XMرVM\+=/uؙMڇTul՝qlcpa\[ ZXxeXT%Y^1ԉı;Tۢ-ּ WsZ ЦS!L ][[{Y [E\ebZLZ{[W5ىr֒Yׁۥ\ȳݭ}Xݮ]u݀5ZY-Վ Eݴrܼ"[l;ސU\Ͻ[]EV^tM5ۻXDƝ]T_е_Mm_]sQTU=e^WY:`Vum_9-`& a]na5 %`N[_U` a~W$ac}œab'ݽ^*b[߱mb1;cfNcnua"Fm3^!"&.څbc ~5a<dEN6n ̪;<%]Z`$_L[&Z5J3de=Fd(fd0G=$]c-n~e e eDe%$Nu>$eQ/aԔ)dmUkatTɁ^鈆ދ]5'ln+iZcDX5m V#NGh\jp<;z~c6hjrdi4ʶc~ЭjN~:Ʒ~f&k;i6b]h>"Fh>EnVgd>#=k"h4e&iTɝ枮^Ύvfe>vcGfk|Nhm>j6;Նe~mَm@g^nmVci>mޞnۆflnNm]o2nnvAmm^mBnno~on.n&OoVenm~nOoNnmnc /h?n ooNp Wk p_oopnpnnoqpq/q?h>qoqq q!Oqoo"_q m&nog^rOm(p%Xk>m'/o-p+`3OlvMѝrxs4?p.r2p5S 7 "*3/4=2ctr37r;Ar:pKs#_Ӷ(s3L^4rpGJDO6M\ME_2?:%w.{qדw> ]} 'g `} \J5b-x!eUUuHYg㡨!}"W؁).X_Q}6 F2.Q@Gq#f'P壑QF!d Z:zW@?~vcGd*YYkjףq7)ejo&Zkr :t]iemBewq <)TzF)ݤ3n:ZYF\fH9*ٗ("`uzYWGlѲ_dR֬Ѻp*Fv 䴺Za*.*[anB"vBW sH.st|tӥ50+]\?IVG9@DoOvɭ LS#Qʹ_n8u;n(bxg NwP ηfyE6`ý$懻8Wn9zk~ۜmkT(Pcw+ٻ<OPMۗy#Nȸ;;v;Z^Gw<,y8ga2 9}HU{c=ZO|-4퇺>a?:xrSyOt a#P|ID!0BE0 *AhDs=apɎፀDwhbAY/N,A6?Q7H%`d%I$DW 6cLI>c!0 tHX>/$@/BRǃ A\.zӥǧDx2S_N ܟ*Jpri,_e~}""E~(4߆)Hxf{,`@z^2xA앰XNEt8Ns1D +p9ҏjeI3jRt/5)F;%Wc"yz?ƥ4"E{*R ՛>MGԟ65:JTsjәtc[ejR T`u"iX:UuWYO9ժsT[UUk%kUWboKWvkakMձleXfE]zYFVli:YV%m7W&ֲoͪkGjVgC܊ulKR&pPWKN+!K: @mmq[zUhZ?KBw}ɐL(w蕭xWșn}v:NTXW v0C6zirm{ _Lb0FRa9l\~-1yX!mM] L $l6İ,2P2䂜!O^nL[xW޲^Ǭ_,{e\85kG+V[b*nqpgf4;t~fDf@3_:@h@774CwzfWRZl>0gH95[-cO8ίrkM&kд dн)Yb٫y kF}NEEBvGj SR9ۦfv=n˕ʼe5>g7Iiwˠ>Ia)]ozη+jps9Dts& m;2,4[fuƁ]Ԅ\@v8yBSa튋dyq—n{E|ZWmmWSƝN[YX7eu"ǮvΥGm]ctpAE<{c.x[i;{=8wӽ#\{7ɮq^3{e?Q{a==C{Oyc^/}^׷wM3]Y\5}y_`_!s)ߢ @EEBݾv `έM_ z `2`` j `9 r J Q8m`RbiH`zaFaf! "raA ١ N! *tba . S 8abiA#Z^`W(*N"'kjH*a!"!&b&/! ~"0~r" "%#Α0F#eM#,U!Pg,ؘ( 76J`$.bzb&&b#"XZF<̋#:+f59a:ZȥAXxك8^ "+F3"c'$1d8Br=r#1ZcD6c??!bNyd=ţK#>5DcNJNEʢ;v u%G.78HJIVbJNJbQnMNUdW>W$@2c!"ʵKamy9XBe@`U#YcCZe\JdTBT^j%)\ $X[b^abaWI_.Z`ޥ\j^.?^=A2-#g*`¥Wfj"jAfIE旑^[eeeVloSHdpn&nRM_Bg$hg>f)gi:s:g9vfk2fbbt%Al)PfuJeI|e}ҥpmŗlryMoug~hZc{~gS&iڧZ'Yfs6}f~慪!iw苢fшar茊hh[m)&.)6>)FN)6Hfn)v~)suݘ))bf)֩)橗iK꩟)*iG)&.*6!N*V^*BjI*v~**P*ƪ*֪>)hfZtb>jxhk;hfkEdg ih燊hG7JPׁ\+f+~nf(u+bld(G++*o&hLhZi*lE"벊Jk]˞EDr,Z,bkB,Jgrk dG{O6ln,z*a5JƧk,k,F,n*]Xl,ͬ:,ъlj>m{xY-{أ eiѺ.ւhm:+P-(jkm&-+lkߚli:ȭn.v~.RZl>I..vgBk .KzhlZL.nݮ:.ղPJ>/"MNB/fJںn>R* o/:1o/֯..׾-^",~pp3:p/on3|NnΆm{Fnc*gڞáAk 3m\Z ׊'rc$ 1 W/ֶkզ9zpkT1-_1Ok,E1k0%ghDq'7 OqpYnE #{ig.[ ˂41rzo KI&Вppiah$rI$%ZZq$1lxT ˝DZ0l#;r!+7pk7",B/ KRB-bL)rRr3 3r [)8s5*S.,os2'op>p:s*3#+.s:GnLgUAW ӯ8gN2=׽fkcq~)S?@tB`A <0!.d0bE) t#ċ9J9rcȇE<$KSXaM7qjqdL/5#Џ"8hD&ӈG6sϙ]3lÝeH+L#rIظcͺkrڳuҕnoqA9!G֩0Ŏ&%4sFH?s&YУTLGuYe;>nKˍaߕ}adzs8ޯߍy扉#7>YruГK׍p#>|ٛwy˿7[wG\|>{k6ϺqB-:K-6kP TC㰾4P E,ÏDcC!\pkF=\G1RsFID$TR~/L1,3dYl7ll2'z1Ԭ=@'$ts+SSR1ѬR5TE7d5FF%srT+st҂,LL5 MNͰWe5c=mYW\5-SUVhߣk%oքLCW0\`MWuM]YVZg nUQ ŗTyuVmVs-MO_eW? 7/[8Ńy_W#w`㝷΄vĘ~Kdomje`yޙ)6{hyUcVac&B8k.xY!ͮ^dՆiU;`npo''1QD;9MZ%yID^ё'(EX RL&k[qy\(Jҗo.EKӐ0mxY򘬴d5KIdܦIKlFrQ8JAt&&tNsl"iM\3Ԝ9{ԦA!rpGC"rfBNb7MWFhE"@(E9:6gMC Ό4e>Rˬ4#]fL}E:5*OTy `.FF k VԼ4)Usў"խS(NJGnu4E_ի!!j`њ8l\zӷUIMT!GJR+W )i>ؾֳ |,T5ם"ʭef9RBGoUִ9,pC+ܳV;1´usu4-hW+v ok \plUNJwuvk.EBRYQR^}/bY#Xֺ}֗;CR z[a _|k˸6=6\-6S?_Y6ڶr|覘a1?1cyV2ؘ sWD%\b[Xv xL+ZWC^ՃMN21G.|g'SY[ .6Q޳fDY:s$OfB zGgE:%~-7:FV fs=}֌gU|HS՜#e0ΓeȥemՁH.jmRج^5mnD*MjGڐvn^6Vќֳ^Ƶjw;6}mc_w[=))l7[q/[ߒ]=ρt&q*[?uO=sձ. oT.]7{umwwϝ&o?qx7wO}/مxO1yo_{y7Qӏ=~PqԽ^esi̊?e;͋|)31'zq.|S揞Ρ;D9'|M7gwus~t򿿑'%˵/dn*Oop -;@¬2-m<}/ݏ*p' 0¡<ٚihͼ *PPP`֚Ֆކ!J@On"*|-PҘ̲o p0Р0 g*pOK Ր۰#0b OÐfͫ.,mf <# P^0. *2!2c ipkoJ)RQY M) \Mͼ)P1 k7mƂ*q31 71*  pl1TPHqT q1%N>/R I!4r0j11"n"Kq}#n#Ur%YP~`H$`mFZr'y'}ED"5%'r){)%&-&]ҿni0R*r+.+]hyB+$(Q+R/H& qNHҝв$2q/1:R/Ւ.q 2s 2712]P.S2?<32-42o/AݺIي̊31s2MsSfHS5fQ3=C73as8 3㨊S L)t340&S80;[3J1< =) [E :39S!3MM0 Jz>ۓC_s4W=Mi3-mS:),4",<sC;A6 9=? R9m?:s: C:1AwLQВF 7͓qq5/#Ijr=!#45I NFQS$7TBߴ@C@qT8d9p\qL=dcô,znQOPU.M-dlô/M\z:cɲMRMOTL8id'&\X"3&mVPux؎J-jaG x'8 R;؉NZ51OWV57cW|܁!H] n6ހV_fR!qIb~bh$nh~c(Fxa/r#8c#&)cDiEB"RRcVbiaHN7VeIX#jg#ZRnFdN& g^frf)&BY执I5 }N( 6\李h2v:2ڪB* nffFJZI(ohbZ^쳜2{ɪ,x#@i殷k㤓fun*z{oV ~*0ΒInv.>S11O<+nrޞL~ /+(pAsH2\nQG B ے<0<']rdGi^s]$ýFuR]Dq:5G=z\&ۅ#.7M1&+>r^G7C?ߜy'.n/o'7&=/Wogw}1o觯/o H>ׅL:'HA$7z GX $$L W0 G8̡wP$5H"aH&:PYX̢.z H2hL3eMegcYTD:nch1p ǧQkL"FNϏ-!7JZ̤ Y$I*ⓢ(GIRcq$+LR4,gIKruY%[Jl鲖 0;xBv'a9f:,f # *$2nz~Ԝ k :t@")lʳ̧0߉Gܳ =5IЂV8Kj"D8N2tX?͌ +h6*8cN3P1~nr.eK-:3i{W{s~GdU(^8'lGRGWc ؁u&wfW(Y'(w%X h-G6a5{*XbsF,VC(,:KSHbB)HI؄nx؂##xD}I4q2HൃU(WXf [d8@qhjX_l]Eԇ%ByGFHG6ȇHRwvf؅in(-&}F3Anu(coHb8A(oxe(GHv7xzXv9X=XTS8_X\xӈFȀ~u8TȍSȏ)V8樎؏ɨhؐ8` Xh#)h0*y!)r5)-ِ8Y8d?IfA)));I'i:VUi\^`yE)G,$gy6t]jZ:Z}'&]\Za*:hizs56]I ۞1]뷠J Kjeհq`uHֱ A^a`=!?"~:`H;.+[@ D+ZFh±7_Wʁ0[ڶy;Jj+ ˷Sۯn*먁;J并kG6C ;:\빭u*2M$Wo3 re;>ZXP ;[{›uѺuë3woHһ{;:KX#{{>ֺ;Iqeo7o۽xVG+ih拿kl\UȶīTʸ8Ӌ,/(L䪻˻+' Tbˆh À۬z+1,3:\HZK|ķ?ܷY뛵!k^x˿\-Ţ{~kƠkU<+Ź LmTǿ){<},Ȗ۾CwLyLtƃ,Ʌ ȇ^G7KeǏ\iAɁLɥ/ ZB. ȳgiǙ 9sabXW¤|ˊN]IYŋ,쿧ܸ|o+i``5Ŕl̜s ՜(d_rʹ϶\t̹f{4[5 SL }I} . -|͐-L҆LҖlɼl;V\+ެ#'1=+ºMa[|%J=t TŦ@} mm^ Zr6JOMR=b{:en7QH2\35 %i z}\x;׽4sֈ-s#sr̺PHm]Tolƽ K حb{ݧ7Է]\!۸K-Lܳ-.]l XT}*UC䍺] wFɍ[ދ\ӏuى,QDM0͗L n=.~٭%mߥ+N'n=],Mt--.1>?.=.3E.A. !#KnM.IȬYWYC)eNiU5B qO.yn{Qku7M[.KmNson⏎˅ԉߵm.n7kgݟn.Nn秎癎g.}n]./i]eY8>:تηƎ໮cӮ_eR4ꪁn m۩ܽn뛞M-LHz ȮMvvrfv>Y4~n4_qMΡ_޲[r,_^cJ#+>R U%9M޾caZ5|OZ/HUT,*B?F[F~$,agWe_Y~l~jnJ~]Ѩ^F㥼e {ؖڊ?ѡܣ-d˵,o?}ßo¯ҟտϏ_L>Oݯ?K@a4 >\D-NX"F7jd2cHIXR$ʋ&SlG0gY͝.wYX` Ea0CmSgΠS;JUէ2~'תek5kZdU;vܸ]뾕׮Խx%:mT` FXbƍ?Ydʕ-_ƜYfΝ=ZhҥM'&z0Ӂm /_guv뻹o׆wlu}\xw/&BKn(Bʛ3Mx??}vo\l{˯\/n\;Р[cT0B Z  - B A ;M,C6Detq[\FCpFqFs$rǖbH K2I#ܱ  $pJa~r,;B K'/,SF5cM7ۜqM98SIټSL8|3P }PsNDIK>}qO)T;4UI,UUK]EuUY_TSauVjQ4{S:mUW?6؁U,6YZbˤVhgf Zg SsUmTsr; |]7d0x,D(uO21`._2~#Fl `1dy)6zM\yS'7 1ZXdS63mHck]f1㛛 eqTZ;H~D+jY"gڢ+FƕY6jmtjGᴍwgGk܃v\,Oٰ ^񿗄VV#!fs'x=?q'=w}u^G|//oCu{SG\N>Ǖy}u7\uEpߣg7C5z}Ǒ^vPҌޞ'2@[Zf@a/}ZHos AOqg`/MCձFdN*?PNÙ c辻χtˠvļ!+ ?1BagE ey}&Cx|"قD9[ .!+QD32qbTew?8)|'HΆ;X ؾu) CnKj^k &H꒒c"1(b"ɲw> h8FB(1[ 1}S)tyrxf3zbܦ*js4IJqr_ĘYLU'|44l.]dW?O!ƨ[A鹽Z cR§Q PRtuDꪍ:D@P.TwMHSTMJMGzәs֓)9iiT:vzT4.EP Ӝ6KWTCS~ԥ VjU-J)9SUT] Y֯6kZWJ散Z+N -,Z᪸ukL[F]jb{Xjb%eV6~uiZ k{ڥʶ-m%ظX}[MT̲Qll=2շPRVnEv'-s Jw-v 7-d ogK鸣/~`+Uwoc^2᭰aZn౪x,a6A)=[cIq &YT Ky=2|Y+w /e$Cag ×u9|.9A~󞯜gÊ΍fl?K:Д/UF÷f~4|yKg:ԛU4H˥>uSV>gUCмuk N-Mlc:>6 hәٟe;FY]͇eft׋g.Jjgؓ&ýmh[׆6o%fSs0b"/ojKx1.g[&a𔱻 /u&Wyt︮oqK Ɠ FgnG0#R~!#d9W{x7c:/'{?ΐOug__yg^F}ײַ\'͑o;~}e7_cWC/vtDߗX^Wq}#YnB> '<>c6k2&«V) =S@8;;?D;ֺ@A<?AD4/q@A l#Br9?%̧ ,!B2xBYB4?CdBsBe#BiA:q0%<45|@b D9ɣC@\6BԨ?4<ܺ B;NԹQ=& ̻DWңCP$8;yV# V|Q$SĞS?cD/MC<3d?›[Em#^ĈQD9`,bAiNnҁFk]Epŀ+-q%=LG]$D@ܴ`KPcW$9ă,?)t\8p|1俦q3hĂLHjp %;yHlHduH1tI5\\m@əlEɗGLFXt4ClckGJ{ɧI4D ; pRB > J˵ ;SJK__a˻˼˽˾˿ ;KqD-Kdˀa4L|LlJK+ YLLBˬd֔<<SִMdEϼ̴:}B5$ab=82stK9L弹݄N̜RAl Lǫ9NTOl Ȱ\Oʔ@X}4z˟ OM O4ID$Ojs6<>P%P,IPTkƜPP,Q͠. mʌQ-tA R\Rw,RWAoE@ʥFhT4[MLF!M-;2<deS^Dӥ`ZqNtLJWc~T=W ԀJwRi#O-KlXnaW|TX2='ؔHxR:NpZ#OHUuXU7+SX!i ӪuYGXSZXOڛEoղ%WpZR3٨%R}Sә5Q W4W}߼pʥ%q ֗E%ֳEBMMۡZǝۍk>&;6B|ܵuRܸMֽ]-э\5R]STZ 2ɵ]$]}]YNs1^7U^e^=թ#eP1С\ܦŭm\qEшH^߱ߺߌ^ Cs_¶=]-U[ ` ne[`|*^n^_]_N-=BqHS ^u]y:ot` [a$~ALHb,< a'`mb,6ߝ ڨY5#%b bطskXCo4;bn~,a V[1륃U_D]a_:g=!&kRc7>M$n*I~eRT{Fze&V&PvfQwHX]ƟUfډ EOZ>Bd:n.ef#[c#D4[ d9y{^tYbdpcme]bΨsvcnaWo~hފv{v.fshe>-}膦go_iϴdvV\uhfen=XыhhCbEijg&꠾ߢ~jg^ЦjMvjN&ܰ.fN~i6ag~k^akhhnk^i&VjFAfižf"-gj&6lFl逆lҖunliC(;i F.Xt˚mkiN0~=2n9m6kjFN;$nl?enaXtn%nkjgnfpokNo>pjn.Q.=޵Fn نnp OjnA~q_qloq_omkq!o"Gn%Oq&ò_m۳D.!qu9_ +0K8vw#sJm6ۼ#9#/O;'ŦfdOsβ\5`tvFYng(J/sC?';WuS_=OWO UwMu5P`<kp"\VWdOuPl|lG7+[10_c.ua_anq>]?mLv6kX_$O w2rcqvm ow%/?s.w|sw3w?'xtxf;Lxsxx_q.yOymx&x(yO)wHi{_yxv׮wqwlryyoxήxzyz־nzoy_z{x?i_ayz_{zuziwbyY{{ZGr{3{z?|:iOm?y{u|}oz|/Qj{w}/z|z{{|~_||O}to{_|uǷuO|}w?}'uI{~o}o~}.w[D'QKgWygǧٙf~3oB+K7fT+,µi~tBRbMܗ\gߚokXwĿr琛?>\8ؙO;{hgϢ.Z|3f5sK}+ׇ}~ٽ_7؀gYu w}UYihulF(b܍ț Ʒxbd,袃V"4ާ h IBeVZIƦU5㋍X\Tem&eqa3f & )$mF=hvIrzb tiX'_f]9ڠ g(碊zb(jnZ(9)^jګꞭ +gY,*^, 좿jZ-]-b6*Vږ҂n2Zju>KnsN&MZqe؆kn6ׅ@q*[҅b͊a )>1sg߮W=K|cԷ3$n,t4 fHldĿad!H?[dظ)NP*#% :;OC X16VzX;$* k I1W݈Jr|_"Oz!YXT$̙Ƹ%.s ?a䲗إ/)_IcIejL\^ df!^.ּ./ ռXYr&:өNeBO̖ -r`s\'#)ael@h͟1~Wd%x&Ne'D'wRjh/SlŠZ# kLHBuEUd9ɘ47e, 9'34*>DTkpKWS*էA):խ& (<*O^1iU P0QRԴZ*׽, -i~j:i׫u`|zԋmd+W}'?ˣR\K U4ke'-n>bl6Dvck;<! _(x綰Ž[8[ OLi˻شqZædzو.xMG%ּn8F 3Y6׋tf=}\$.yo 釛z~?^z1}=|~׿Wd᷾1X__NM_џ ~ZUE Xai JQZ_ > ƽ`2`a2z`6 ! !*aJra 2&]юl naVa RYR.Dqe[ ҡ! !Z@MDj"!*>a ~ a6!OGTbb R +!HD|D;,"!v b'rba,8O /b+6 ""4ʡ#sMQ!Px.3b.b1b4+Z~ӬޠFF9#6B"9c1*@d@RcY#$>A*$L~e5#%7U&G#(i!YO%Xv&PvXd㋌eaE$e \:]"Oz%cd%d^Sf6De"f^J0~FҌ\u%\&=!nRi^Kors$M&lݖn3*&qkj#^itΨ锞R)i韎iΩi*:( jBjirjbJ*bhz*b_y=%()ix6&ɾH":iv*~~iuVΪ檭ijw`<%+.JRjjڣрȯF\kj+hjk}ؽ yt+l䫩jNk.(99V8+)>IV N캾+tbl#vJ:l%!n¦: 6,l"*bZ̫F &-ʆ,+Ҷ*f,[ b+Rؚj"fk͞lBrz-mي^ՒR^iNB!OomҭɎi.l쓁#:5^JB,м"$a-26.V-%!,..>,:=..n o"/J/JFdO–":BpogzU@.#.eo𞯉 KcV/ooo֯ )oSn n[:jAK0R0⭟k_/ ' 0 װ 000&1'/177O1W_1go1inr111G}1ױ1X1  r˱1!'"/2#7rn#G$O2%G!pg&o2'w'2((pp n,n*׮m),p suyp3p p/㲉r.󲢚p r-+n,OfzE8~o`I@Š3+GO3o8sdx 7M@2/3-sAsDo870-#=kt96NnPDV"EkJҢ=w4ACW l 4?U748B s3G3HHiSlI+kJJ3uK3u^*u:Z}tP'O3s1+3C'Pgt]ot`]/.3A[Y(7c?6dGdpYV%gfoauL?%g6i6WE_66kkNsUGk׶mgsC6O޶of^v&p7r7p527b%7tGw/5tgv1ubLx7yy7j˵juXt\CulRw|b[{5=u}u~XKvubuu#v_whtn vv4o+8OxZxO/Y.=*5U*TCJpSx3xcx4)vOiL 68 _ C!yV82x688{Q;o$~Ϸ|M9M37cyr9x;5GvEH̔O1g#oBΒdM|(WzJ1эws8e9/s#?C+|"yn~x Msx.NzWK/~κ׺H'/z[;c3w3{+ 5x;W_g{o;kkQhy9/0'/<%|#% G_<+_Ŀ "e |O{;;*pg|owǼʳ|Ļ<||R<_l;¼KpQ=c<ͯ|<|E|k$q_=/<%i}[0IؗѣUwS>F:3ϼ׼>ӓ>|Գ;;xM;;zC~z{׹|>K={{Q[c02ԻSŘo}ZK>?@5@$W.Ȑaā)tP"Nj=~8aH'Ob4F*IK,_Λ+tgQEe yPm2sRWfj)RF]vlYQT kҵnٞ%ڕnUi{Y_ ܾo ?w\:+xkewۍ,o>!{*mџC6]w2՚FLlٛkZӿ骦;e㕅]8Ɂ;Ozr}3_.]u{7޽:og\=zt{???uBJR.4?v.Ï Ã!ܮ$ >DDm+@Q܏C [qF.|d1F }AKQC䑹I<0 ')Ѹ(m̭J%kRH* $326oM/2Lҿ;ǤO3iO0,TQ,rOtP9E ETRLьtK,/TP 5Fm8P>X;=LVWQ͕OOCXZ]57_!I8ZO-euXm:OZS4Yq7 6VgZe˭oUQw.@Uu^o녲[bM\|S ^]5 ~qa$ vx_w`fuW؊%Y9V[!8F(;>1h-gmM^q]~ue*mS!D&vialfg7jxz싰Ho[ᶧ\[h[16hSViƑo! -Zʱ|s/;f{u[lrvv%jjʜoQtwY]v͎lcp"j_╧E^ua~{?&@pP">s[Q˕N[ٰG;-S૸@^{JB ^(<]0kH:f[Rjbp(뉙r`C8G:σ6Ԣ>B}?"Ȩ4w'"E4Z񅶋!m$G) !dF: ,N4$ LBpu<[%O79G@2d(UI*rd%qdJU!{;s&iLZ#;K/%Fy"IV2H2`# p!a ?D˰1a _X{I+JpK]-FƁO4q ӛK(ib1Vke /*U5d^M^׸ Ȓ l9'/rRv2g9[u^ηm^}hpל (Ts:=y]sh.9+_LD'xь vŗ4piR:Şf+C=Q3 1SHSԵOk" zG/ jDԾr ð>7=hp:-z[l166_ser[ݘmĞwoN lfz 4םY7\Er%fۏ5S|8 g\$!Y.r܎ so3w1~\揄q'}mz.\Z?yGNX9F\rag:ǩt}Wzg]{w>6;m߹؁~Zk[׳ˮG>u~~|;wUPsx,Jt ʃφobO"PN"nKz^௢ \knn!o(do̧g%onhj&50J%P-0P|}n/h6Ќ QP  pI(rqm pZF''H𼄐 1S ppz~  qVP]Fg$c, / OSW1^Qq:Qyr=trJsƯ6cL lК1j1pʋgh h[ N*h o)m1Ϩ!/ijhp!%!3oqq"req1/*(s ,!S1aQ&5'n2' %gQ#M#!U#r'7';'?2(aR)U)q0O,r%iO+y*}ll...߇,Y,]m#ߒ+/ 0 sΌ!1s111!32%22-/+- +2A34g2I4M4Q35Us5Y5]4E36Cs՚vDd37+5y7}738s885u39 %Q9G83:s:::3;7 nu3>ۓ66;S-RuC??Q@33=?c?r-9q)AA2w3js+D3+C]A+)r*ݒD3.O*cD5*oTFsF4-E1t/5,{t3-TE#EAHM_6BQIGԽ/ "#\&P즵@J3=.qXjs 6y$K0 S UGGJtN=ئ"k+"  +OY(G ] PgQWQRgiQtTS JjKcFSB;uJAD5IIPU}4E JK#*WSFE$u&Y*J-+XBUH/3#? KYQUK]tW4It]Y4.RU00?TBTaGEKt_T ֵ٘EEEFVcc7VWQu^Sd9urI3vG?Vfat"(t9UXW^[ʨqhgUUo/h//dyj1㬁JVhi@V=.Cak=9Sʴᶖ.6mvml2lL8vCeTtmvoo,' [zSWI|i["!r)ezwƛ\Ÿ\{]|v2zʩʿʳ<ůWaN6WDߋҼϼziϺ5X\-R2v<еeoasu:暞S'n }[=8|σχzrVpwK3;C|Yӳ3 $U 5m9xsrmՏ7oWօ4C wFl[ĉɅIWZ6}F= u=BӗVW;^ѝIG.=d9ü{G eN]3)- 7C:?CGѡKgsc{?O>}<υW~Ջ#^޻̴~빾>~ɾ>~پ>~>~? ?!?%)-1?59=A?EIMQ?UY]a?eimq?uy}???> ;Bio-Graphics-2.37/t/data/t2/version16.gif000444001750001750 5227412165075746 20123 0ustar00lsteinlstein000000000000GIF87aQͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ |܁!H] n6ހV_fR!qIb~bh$nh~~c(Fxa/r#8c#&)cDiEB"RRcVbiaHN7VeITX#jg#ZRnFdN& g^frf)&BY执I5 }N( 6\李h2v:2ڪBnffFJZI(ohbZ^쳜2{ɪ,x#@iv殷k㤓fun*z{oV ~*0ΒInv.>S11O<+nrޞL~ /+(pAsH2\nQGB ے<0<']rdGi^s]$ýFuR]Dq:5G=z\&ۅ#.7M1&+>r^G7C?ߜyƶ.n/o'G/Wog;G/o觯/o _FRL:7@p̠7z F(L Wp~ _H8̡Cp@ H"&:9DGG*ZX̢.{^ H2h 05eSf8McJgx46#GL" =>#'IJZ$|!0I (G9@MbK&?9V;,ꢃJ 򔬄.wKro\LfbzE2Ijڏs4#MmӚ 8чi&9vS崣Ub̧>hnQڧ@JЂ"@-:s( *9Zlwt֚9fΜE?'CCU:/ `6S )NCR2OXR Qԕfs`:}jKT洧鲒zѡf-l:SƴV)UcdR g\kZ:b\ַ25A` KWԬ5]k#d򱃍ZWjA[ X*UVjW̵Vt[W"! Z֭b-m\X’Ru$E.4&bmc[F4bͭY&[vt^"ָXETYI95Еx5{ޛWrRj\4׺oY{ T]hy#K4oXslsY.pV+\"tɉ;>u|[u-H&,xCjz|VӒdN} mDYnueqduh䝭ixmTsT qn6lEXȉ^Kb?Cuu.4ۭN{ݧiOZw͜|_W=i<[w>K}᫙6x^n_?_|gL{cs}6''b~h`3~'u |g^i7rZHd ׁz!Hgh'( ~@28=(Ȃ:6Xyy&a+Ȁ&QCXp8)x3iHȃlLx5h[VTQ%TH]4؆J(Xch~q Ć*8`~bdhy_f׈nx~px|H{W@XZrTzxXc8()f艃hHfȅeH| 8{x}z6Hd?HRRhxAxwG~(Ȉ،ȉX֍(f؆hHi؎hhxhV਎H'88يɌ6d(gIv ُ )&(ɑ鑽6i  X/ȓ?IH,f0I\2Ky= ;)x8◑LN#9)IqdהEgyY[G`qɕ)YfIrw9nYCmh |Y7iy}ٕ'-(E"ؘ3ək82򗖤ٚٙ%YB G, 铬()I雒IwbYxYI%T5f[Zy_R[fI9ht9i.t 5J!Pa_㚅闄)aiA ؝Vt4t Wzcy8ɖ)ɘ+ix.V 4GΩ:YHoi[+Jw5 LI٢8XYYعc?vS3QnveHGJZ_:::k7]2ZiZJjoJt,7ƦLi:kz߸*ʧc {١aʜ}0s~vdu%nS[q꧇*J. _}ϹJU yzZ \v57gjǪɊ_.uZOF:Iuj_u;v|j銧B: _!*,ګ]ꨥ*j*]Eʬ*ꯕ ck[A׮ڦ:k۪욯M)+ڬ۪%d;+z! -/KTOHb<;< ˭¨H뮃5`b;d[f{b[pppK{xuOx j2۳ [].y[+_<2gYMK.E(#;Zghlc ꋏ;xk};3CrI:;Kz:kK;^U;eI E+G?˽|Tыxҋ, 뼥OZ'T{UK0;;:K*묁 ;8kD+˻+. 4{Ik}ptsw \#췙Z {LcW ;;,L%9|.ͮ^\@ֱXMuI< }&׊ɈLhؚ*mѬ Msؽ ӅMه֕ ڟًL׉׌ڗҩّ٧mٙ oqٍMړ`-{}Lm-ͿؿL۱mۡK(ڻ үڭ ٷ ݣ-ӭ "]} x؛ܫݭݵm۝ߥ -Mm NM.m˭ߩ .↌mn/#㝜`,4=^8!NN)m̿ 135XZx71ss z(-,n^db[um~P& eȝP^}.vhVO+q!||J>C?>hllqDY1nc.nzH>~=ۏ3P8x{}ܩcΫ^.2Nš-~ GBGDCo?SHPOCZ/JV_a$ܜhX]^k`/t_u/v_c~}q?z_o~&e|?OoOo^/ƭcPH3_oկ?ܯ't׿OɯO?/I v"x0A 2,T^ĘQNNHȑI }=:vqO\xʫ?)xͯGP{]o}M{ϧs PC0 d0ț)P(?@(!K2@Q;1D D0(PEG"qċDW̫Et H85!Rr+*RF۠$#3h4408D(5cM挌sΟD2N/"(7VSeYgUkD<\i]]koƵS\馻%4?.(>M2m.fmB̧E}Бd7z#n!ջ5j7WT7~wGڙSw~'u=GYItxEn-W&ى>ߢ_?y7ط55{P&8lk47\, }~%_FA,~!@ ~+ \ߙ%tP0 !]|(= P4D ch $ NO zGƯ[DbM("bEXج5Ќ Ɖsԡ(D:Ɏ}T#(QkXD9ǐ,!.q.c!YEz叔DIQgL#;YCBM%8:2a*ӈx,emB^ŗ4/KY&.O any< dnҕ$L6׸MeFf%uirlxB,yzz`'+ÌRG,h/YƄs'j͈B|?'Jţ:ņ(*G*=r^l(O*єRs ;WƒT)M*͜i7΢3̨@w:ɦjt@TԐzL8We&Y5VVO=ZV~a5k(R&u+HVou6*QӯU`ۚXJ]bYZfyQ^ Ztk U[Ѹժo[ζxPkkѾ%-oUk\"]noNiskݦ,yYnwUw{^獮y5Y>wo{_s7+U.8l̑X)L!;`2w0\X(M} JW d0/̼3=q9˖mf?2StiX"Lnrd_ p3xϸk(ʒM8$4@R1muf^o\RKksOܧ9[idoM_' SjX!^uIfP/+i5= K(=Z$Tzj~ٻ^&g bD#G5e66M]Y؞&p+ۿ&fXG֞7mWpّnנK|G֑1Y*.ǚ穼lqyMG>6f՞6]V:QS3~<8Ʃj;?ӝo@h~^̞TwЮ`Y"9W:V᯿=6k9>$[=xwovii8 /۩Ý}'O\ng ɋo؃Nڡe zS5:q[<\c w3W;?{SubB'#>졟{! r91/g9>X2ßy_0l*db+g|9␘<-3cBk1S5ڻ8CX>>I>˻?=>@ 7{A[A$=LKA <: ֘I=#%Ak;= ?S,+@BXÎK<.*/T4ú3ÚA,\IFinF[F/IDn:&T4oTud[ܠQ3vGp5z O4rAǀdps,.IȅdH\c<ȟƆF+ "Lj’RHH`$HG>"ȏDntFLIf,@ÁxɛtGWlBlɟlF`ItIŘDC3ʔ ɾC2t5lʪ\xDʠ TIdʭȧT@cŴT˵\P$Ic˸t$Km2>˴7|ˍKDĥJ48HDLDDAL¤LETL;<$ ̿5Lo _!iQ;_k19MǼC4Üd=VXM55q<Q,*M$,dAT>  P>֤PTDlN:Q|O!J`;6`EPQ6ӬRØN?qY3t NOCMPB5Q- *;%OP=]=<,S7\TT->@=T)TDTFMLH5@JD5dEQ܄$iԹPDF5TTҌJ\]PWm6JaU*TKU==U5UxXUL%TeR]N+]|VTj-9$mm7uVqMeVMVJUjDfV.֌VoVmTGWMWcv]JUKUV5QEWkmV:%"w} su=[W]'}/ԓӄE؅bcGVהM5؋WB]DtQq80BA;U-Zu ڌ=WU}YHϸ 9Bڛ?ԝYG4${QVWUXkZK] R%RP4뻜K:%[X[SW5Lk'4\Z[ҝYZZ ]!}PN[ԵX]ɍ\U۳ߣA*\5]5]mޘ]]013N rN>Z][U]u-[QBK _Y}~L[_{Yk]ߞ}^^mX_nu_5U%`P\_F&E_` Fuܪݵ؟-u``Y^VMa0}Un`v`#&mx}bp>_Ub&b +(X)a&*.&c$n8>3_-fc.Fc$eᎵ^0@Bf>$4a:VcF^d=5_Kb;_IdGNd<^ kMbّd7aWJaK5c$ePQdHdOcDME&<3^=]C.@M_Ne1c^~\㙐5 nf):~Pf@- ev"Qfwfr~g2Bk65\.gyf{eo`e!5wˊYnn\@~Xx'Nc3gndXavvb.p~a`eg~dCq&~^iNigPyz6˩;lH \|,vL.ha IK,oڎopN^pfmXn /pnNgfj'^#kpzq~p ?poqppqqqN.>qpq"_vnq=.r)Or_rrr-rqϹoT^Uf/j.s9o&Ol6012o?'s&mBGmAsDhU-'o;7oKGoLGp:s7rNoQgpMwqS*uTqJ_sR GrI%{tUdYWqZwr<uU"R]ws^tGh nOolGG4e7#5'qWouas`qq7r[o4n/9#:G{_Gw*'KLM7kvQ9ww8O9_:SW="y?ww:9x&TW'Rqsxx_3{.-yvFph7s@/tovi?{eݠoOwjp7~qP/zpgq/vuuvsrrytWxgxwxwpv/wywwWr{~jh{vzClŏt0{Gz/d{Ɵ{ȟ_E444kz4Dϣ& >w_zۯzr<閿=QaP_yu?sC|34MA[F\>{ćgϺ1}ڽxxR0֟~X6u?.:$8A;QYРÅ>"Ă 1ZXD7fThdGG˕(_)Re˙7J'P5e,JH?~ \ISK]8}zp*T.:jS.w\XǜEѢظEFET)ȪW}6-`yfkѿ|,䱒5N[/͠7TܘaTBɬOUfRK51nǹAGKز»ZzՈbN|yxᝏ\sWNe3s.xGGΘkT?;Οxwm[u-ho "h yY}Uo[(R(!VX!Rb- 4^^P6VEgb9x΍(YF&"=7%5>eXI%Y 3{;o5xc=KZf'福|׻Ms>OpgO{n{"I:# Rg%(x*Ă 跾A"_J3O! Pw@0N4X0ҹA%z'\5JNC"#ĉJkE .І "9X bHE4nmh4ngDC ҡ+Dh4mBTdh5 :E- S=xʑҤebSa Rn>ЛmE}v(]Q:u3iM*>^RKDkOXIO˴gS)}!I˘Dr4#*baT$Q:OC1UcAiupmZ'TtE+@wJW+ޕ-JC;V$tl?Z/O Zu͛k#ީYt v}hwkf7$]wi1mn;7izILW-PYmoZϻz׿ŭz;&W ~L*MlTvpZV)l`L+pob I3כy5n;,nA/gv1Ql;wУJZwWnX .rs|dw5v;\=J0V/Tųg@CN汛{af*lmY>l4 f5yМ4~ZM -4yKmcPZ@?׵^tlRmekҶf߬kX['޺2[ȥ gRlѺ:лn3]mVtG^zG~UPy4.w}i|WNwcsw)F,'[JzK|O|vnk%&q}+{'9M|,5rw''xar5G}}G9_g;s=t=<A.g˝$sGp*XٹusW!;zq3cIt{W62v}0:]wZuw@-lçKG:xB~:S9w-m[}퓏[mԾ|ck}#)}-6|7> 0oѣ?_ɜM}]E)NўZiYRf^]Y IBS ͠`M v Uj ޠ 6a: B! \n J!*!ajq2a!  Nv~aaf`ǥ! " *"2 b"Bb >6a$%"%R` a''](fm"%,})za%cc!:)c"".0&c,z&b4.b("3Z66b55"13c*""z8c7b8>"4;(Fb=1"5>#0&<.=@b7^B6]b5ޢB6;$PeHHLJB:d;^JK3jR#kV6ZW Xxd)(|iM.c6**i2*:y.]pi bj'h)(Jjd2*ƪ*֪*檮*]P++&+*P*>+FN+Vrδn+v~+1+i++&++֫먼+++l,&.le+>,F>,&^,fn,v~,*^**fNh(,,O&>*6N*~j:*iͦj΢lЪJZ)pYW,϶l꤁j0(W$rHBm͞,"0Ӳe]HZ̢jmaJ!miيme|jpE" ,m,H>6.z&ҢbӦi6YrbJK้֮.!Ve>NVB) mnjn--Ֆm./ֲjκnJVRH06?&.A_0G__f0)C% 0 ; ?| 0 k g ^ 00Fp0,wv0?1&1Y 1A_1goK0Rn0/箯//~11V-./1oю1S "Ki!0o}21"ODZr4܃E$$18p'îfr+oaS4Kኅ)o*S{20GIm`J113.Ihm_dg3n+wa0,]-o3TLsL 092+X'V"Dn`w{]3E`uj3=W/zW?guhhk׶a6lKLc]cuQǵHWoKm㵝(vr#v aƛ%tFi7X\[6q/7nlVg/r7bvuswvuUGe{f6Z7yqvT|fxwm7`뷵wq7wR#}Ϸl׷7ux3n GPZ#؉ncuOz{ 7UVxxUx+y 9{yvewtCy[#;sy39vK9"3iqj_/d'SxqϪjyGvOC{y9|\Syckz{zco[z8Cyzzkzzz{o=NAJz;{{{+sS{zk{{q[{s{c{'g{#{WCe2 +z;|yHg.'fH{y|{et;F zK|ij* /ehAG ѯ խ<+}ҏ ftwfkZtLJZffanC=ʋ}؇~Գ>W>g>_:ݗ==C%g??k7?{?~;G𫿘O<s?sw@trщ ",(p!C :$HaĈ1bhBAfdI;ԘI(U ʂg|fD;[tI3OB'QG:haUW6)VIYzQ,Nbĵ`eFՙnOEm٧qB5 ݿ_ w{: 1V˗-yK*b賥O.:!efhѩ'ߥ;Tqas%O~Sqʈﭴs޿_Ns9lD^6lG_;}ͧG~n=ܒBoKs;242;P=-S(PߴSM9IʹUNcH5CYу,>SKXL81Ub5KO,YbʦTO_eUiMeKkSMEse1lUwnTHnQ>E׳oK~ץ^wηՃ][dw1s 'NYKُb;IϓKVeO;8ݖ_N7r+엟煀)裑Nh:ei駡 vgxAjㅙ#tks~2J!ZfZ$3h4媅[ծ|Ҳ4\_m!_:c)n l3WGu6=@^l<#iv*]e]ugIzAwv}xQLtw}d\nf7f;M^73z^j^Ó7~ſg5 |^.CқAr8+`D2cb)'IJ#~r$gV҈d1wJd2Md __2YaNڬ 9o̙f4eNjfӚI~*T4(iцCс%DL[fNя(4MJj4Mi8y9ӚQ);yzT}3B)QTt(j ΫtiWI՛Nu5JTF. {S:ժcLWUZ+PZ}>իs[V{>lcV*U3kaٽUA+e `1K<Ȏ!yÎvETmWUW[YA{[[ޞ5n{he.9 JƸ۵sP6U-VE{^g݈ne 佱ZT^=\ߕg+ty$D[(m4em/[*.yfxic߃+_F2+ǘPa9WD1T<`/g0ueoaTYe 꿎X|Z0xIfrs\GN+ ?De,#yȡ5zb@6Gyj毝\d9Vq h"Ѓִj蚑ؽƢ#]j3u;V:}6p[]z'pӶ{鏟7џ~clWpooXqvp!0%p)vޮ.ps=A0E9-mlOLCca0+[FlMPKn$\𤄐oRtrN芐HO[ /ʐ OxPPȺpL 0֣Q PP P+Qhn=,Ne(lj[Cp 1 OPWdZ J y&=VkQP IQr ˮ۔`nѮ+s[o bQ$EpGu}d#\QB 5Q1EٴѻHdK`Q-L-2\=QP kV"ͅ QY+m2 K.RV2aQK17]pgp G.t2q=/XRrω"# Q)q'[K )O)sf?2 0$sr*+?Pn''280搲 ,uHr+#v201)0w#r,wvO0(.1R15s3903'vrclf;s5Y5] ^d!035us7[7o'4ō44P,Ӳ.}ys9/9O83B824)s/SA T.2<3;+=oB!CS@wDۓ_"|CPC,8ь.LQPU5P>2B=fBQ$LBtC0LyRM\0QD=1MϑK(F3>)DN4F!'K.S-մ QBgDM4G$4H9N%UHTu./R?$L1KN!4ѼkKwQqAQX%]R\#U5YKQW 62dHCU;]E[4^.՗8D2Tw5Ko'^N^pi3@0aw?Kb:)^TW_aE#D9VA3)oTvM/``g5`G6Mbas(dFPch'hsH*!gc5?b$q5(Gh^_keI_`6g%Ghccik6ni/jc5n8ek'vNA%fW2Ntwfs2m@V\!WV5)"ab%R_Go'7LwHh9<]V^=k!M֚ULew9pO`mIspGx:C9s\9ZS97.zcg99wI9"?3IY49L$7Z٣QZPCxy+zZSըxy::zٺ:z麮:z;{ ;{!;%{)-1;5{9=A;E{IMQ;U{Y]a;e{imq;u{y};{;{;{;{{;Bio-Graphics-2.37/t/data/t2/version17.png000444001750001750 2353412165075746 20140 0ustar00lsteinlstein000000000000PNG  IHDRCPLTEQͅ?.Wk#K2HiH=F2̸ G@ڹ/OOUk/er=ݠwޭ`""p2fͪeҴcGjZ d(iiiQ{hzEGUp֥**2޸Ep|޳[zpzڥ pAi(+_ؿ //_! 40ְJ]`D*eC@`a"Q25_`D~Ͽ/Z ?H*Qpi_k@aI,E\m0dt0k2z+ -*cۀJ/l 왐 jXHL@E-A@R5 )$UjT A@R5H〤rTMWL䤀H\7 X{WUljT A@R5HCX&/4$A:p'1 06LM(L?>/D v[̆.YHki+>˭3MR:|#ei-:Oeωn[@Z<.~mR=yN@˷=il[@e=eV_K3.^@\3رC NH&vBl[- ߲E?ߚV&rů :] 1l]}) Omf0]J_,5l<~9_!Xa (TdGF\yܳK𰍉-D˄V%pFYqY5с9%`ӯ+]) ֙Epr9ʵ@Z":co/_+Q0&ö9s7@s1&7x";YhY4X0LE{`"z:`"zD0 `-A[&xLSor"=0& 06L! `" MA@`l"C@`D&D0֗e3xV&pRB:LdO@w_|n[G9`E`"{`"<54]ʛUvjXH^K3vktCw1 .2Q) V^@*5H:\9pV& "0։-X0 Yhn=Z2`[HЂGKl i@@sxN]D_!x0?{eNiFo4.Lo>.{d"0! 0&TU0;!cD |a=Z2`H &D0V >A.Ŧk5h?g[;o]a߄`)"6~~ nʬ9)j.ra$"` &R_KcmΥsΔx8xIkWtBL5]2<.y0dSc}UvmQaA@`l"^Cݤd?L^aIiRǒ!`/0ܼg};.0̂v}Lg%{&9u=k_([a"֬a* yf˒!`N]# &D0x{R+;Λ♹Dg xjӗw4w/on(Ybn5"`$~Zs NlG۲~D)Q`,m%[UX?Xf"Updo@yd?|fE xxFLhKK2A@`D&D0& 06L! `" MA@`l"C@`DvV&5 ?HJ ^{]Vݐeȸ5L$!Zy܂{}6oG ?Hz+AFW>djXȞ^-埆h?,tȚV& ^Z`dVKAY+%[Uo*Lʁe"0֝X0;z==B0 㜀{Z怕' Uqu{}=#ܸԥa, `^$*`f+ - - ۀ xf3Zû: V8"3炁5 h 0>O5CfGeu`Öaˑ 0e|v5JAD6rm3*Ό +ZoBV잁)?30s%{RF3?s4w3}s{YFY3'z Pk}uL_L`p`ʵ`]]F/`f \ `JSN@d*'9:S`n1)udBK6mc4I"cdo#fȻiRs'aV`2ƫ0wn)o'.6J1y5^nm/Z-kmFϐC{v9|n>x a'6^ ~99̩q0ɷ0q~cəuXz+?8[<S}~;³ Áh`D&D0& 06L! `" MA@`l"C@`D&:幙27[mr??V&~FT1g$5ʿ8)LdO@r\ UJ {[סMfidI#0,}<K~{w߄kX # L/ 6^sD6Z-O#L0ݝx%\U;!iVt$Ga"uDmvB@݋` o\-~jXȮK1mݝw1 \006LdV*L%,ߑ^|aZЍ{qKT?s6^A^g}n%{?Eͭ9JK֬aL|^;_sQydODҫ5珖Gs;(0cUF l]n&x& vST@TN+d vBKO؝ q-YۀX03!a XaTT2g 5r,gmQN)<]l'Jve G'%^[qB%g)]ɘ⎀;K/PK^Hq7J{L@eF=-2uZs NwmS/Sά3FX+|aakٞ0~AA,fͲ;2O XL5JY|?#6`d2#D vi1@O5xC@4ga" My^eW>}T{}D1b&RG`跀fe \쐋i\ȝ*D"+_Rև&M(2eb^ ?b&rܿco&V>uʶZu2Jxˏ ƅ:sE@Kn#mns+nZa" M %o6J#`0V/`dVPVj%L䉝ev'$8wd^ɺbV)CF_LD8 MyL@W%)]u@+iCx oh1 yrl/n/͹KN#P0GT^B%Ewh9b&.MJ.!Ӹ0jWyߪąգ/qa"Vg*F}1 |%7b&򠀉.C9`E`"lw N;b&NHxV+yT="0L&n -Ӹ0}"060="0VD&D0V  Cza4.LI% /qa" dr"8L=QN uƅ4 z\p[xHIأ%pUg4! &R_?entkm_-LMA@`l"H}Si ~}Ts`bS^xYeG-EͩHLkwpiMGnʬ9)j.ra$"` &ү6R9i`gJv<Ո k\- `| `94 \= KJW#_sD&D0& 06L! `" MA@`l"C@`DX{w!S\[>z+I{>.{X@s q 4쭂E5;пթ&sV&lFup+vV&rA@](5L䜀iU0,Ȟ~'NibCvB&D06msw\P6<)Ա Uܷ_Dvo7Nk8hw Ƕː P0TؠL:15*xm2Pk z/s5~e=Za"e[esfh(kVȶPePy!5K81k[՗^Qam\`\,L*؞ N@;?Bj[@(UW֯V[6z=_lT՛nR;!ޝGs#vV&RS@PqӸ0NFMG@gitg5Ü.+{bc?Qٗ[>fG$9W~7 q%c%fiGV_PُhX@[~Zz%ϕoO%6ҩ%Rǻ:TTr[ xdQUŋ%4O w>o89Zs%[~- _/W+/`i^+}bJ+9]2_Ws X06;oKxy?[}X'3 5[K O;ջ4.Ly,ߪx>fO4WMG㾃׏N h{N )G/b|T Fp;?m hf,fl}-ˌ`e6[WW.>)/۶_mӵ}%\~p6" h}n(.# A  2!묀vtD pm*80#[' !Yv,\- wXnwhkvB0] wB~,+3jn\'raNoЧ7ђDZp Cva"lC@`D&ҊuZӸ0&tG/Oƅ4 ψV݂DPgDX &ҀK']L+/`di&҄* f'dLH}.o:a X&Ҁ 06L, V.ŦxbalOI?<LLvg}K6 4E¸~~p+VfIQt#D&c0zuQXs2c*vYP) `")s] |F@` V5+Il)^ !kXHb/X[׽`m?Z@`YO.SB@`/L! `" R !^뾍 $UjT A@R5H&_@BHݜL! `"g4un<]"0+-P чϗ\P/wə嬴?@@` hq.gVP~" tJvE؝^85L0(s~B5LD0֕O^CHw־ A@R5HI $Uj<L! `"uT sk-Hoy^p ]Յ/w_Ná:1>ZY?smU"Y-eii5:Ik[ށj*qcn$ZUz2 "L9WO3j\J=5c c7藡]k~/X7;Ppbh vBz08 c[=t[;wbh[{+yH@ `[kXۀGE.^stvo4*3'[ `D0 C[`"\xSaPrܾpdc?LV#"  `D0 C[`"N!RZqjXC@`D&D0& 06L! `" MA@`l"/W'逧V&s9)?oX!}M 8d+I Wc1w٘ ^٠fˇ??l͎]Xۆо-uOk1B5L$b{P@mq L/_@h:9`E`"{;!߫`ӫupV&{F섘)vBn-M 쒀% 6 +L,+XmvSsD09`E`"%4G9hs0w5 [f4.LʭlAUbt 3k|M"0-"rЏ`\8 Ue`ka]LD h$v9 g"0`{:N;mm Dn Y[@wY8AfN[}7[sDJό`njXHMm Ca]LD8l,$l ,=b&%V&%V&rS)}!0Z+A+A+A+@406L! `" MA@`l"C@`D&D0& =!`S`:v9YsDʾ@]-0-UtJ_qX30B`tyq[D-gSi ZtX m[0lC@קqah&P}G[n7}wV&nfNd2kXHRisU۪k&+ZL$!9:_ܜ_`D{ k{0g)! 0D&QXid5a" XКTGMRp\>i3s D:i .b 9|MiK%Xdηأ|Up4j#x2N yIDAT4V0& 4!\:~Pa]LmvjKG,P'\i_rmc5 hBP/嬴?@a kXhv.gV]ƂЂ{V:>Uh1 >/'҂{Z@*Da4%T=Tڽʭ?_;č47턄aI&wiEۿkXXx./ n< qA@`XvږKV[Ac x]W5[¶ukhT#?ۤ{aEA{_rn>Ar/-lGM@ZJ^cl4ʩٷ-X uҨjӵ0AKV[AC@`D ֚[f߷~쏊0݅Q]hR|y0/گC V `|[z/6-ߪbz]2s~鵭[QbܸzPꈀ*=KV&f嫃gy^s/!`xJck햬&LUs'W-`pW+wW)b;!k ;1mϖV&򐀴j7*yBqS12f'f\+RgG)D`hp' 'pda̷lVX]}HG'kŴ^F{ʼn+/CR"ƃD? ]v܂NsEE,Wx0;1mƝ_@ Ucm70^98x&=!D0iٺd &x!`6L3 RwEd ٧mMqɞ `v"{g7" p|eϸ f DڬivAL1" n`" K~m/Rkd߅ /5BڰU+r`_(({Y\u"xwal2a_Kz5Fۻ ,ol<܅E'X;p1 &o/-a撇KV)!ۀ:kK~S1 C;aniS `D&jT A@R5HI $UjT A@R5HI $UjTaL?IENDB`