#!/usr/bin/perl
# Program: cvit.pl
# Use: Generates a images in a range of sizes displaying one or more chromosomes
# and ranges or positions on those chromosomes, indicated with bars or dots
# and optional labels. Also produces a GFF file describing all data and
# image attributes that could be used by another application.
# Data in: config file(s) and one GFF file specifying ranges and positions. Ranges
# and positions would both be in one file.
# Supports GFF version 2 & 3.
# Data out: Images in a range of sizes and a GFF file describing the images and
# all ranges and positions represented in the images.
# http://www.sequenceontology.org/gff3.shtml
# Types of GFF records interpreted:
# chromosome - defines a chromosome (or piece of chromosome)
# marker-hit - a hit on a marker, displayed as a bar
# marker - defines a marker, displayed as a dot
# clone - names and gives the range of a clone, displayed by horizontal
# lines directly on the chromosome
# hit - a specialized position, probably a blast hit, displayed as a dot
# centromere - locates a centromere, displayed by wider gray bar directly on
# chromosome.
# measure - attaches a measure of importance to a range. Could be e-value,
# hits per location, et cetera. Value of measure is in attributes,
# value=
# otherwise:
# an undefined position is displayed as a dot
# and undefined range is displayed as a bar to right of chromosome.
#
# Documentation:
# http://search.cpan.org/dist/GD/
# http://search.cpan.org/~tcaine/GD-Arrow-0.01/lib/GD/Arrow.pm
# http://search.cpan.org/~wadg/Config-IniFiles-2.38/IniFiles.pm
#TODO: fully support or drop SVG
#TODO: standard logging package?
#TODO: consider re-implementing association (i.e. synteny) support
my $VERSION = "1.00"; # see version notes at bottom of file.
use strict;
use IO::File;
use POSIX qw(tmpnam);
use Getopt::Std;
use Config::IniFiles;
use Data::Dumper; # for debugging
# Defaults; GD package will vary between GD and GD::SVG
my $gd = "GD";
my $image_format = "png";
my $image_class = "GD::Image";
my $arrow_pkg = "GD::Arrow";
###############################################################################
######### Global params #######################################################
my $debug = 1; # set to 0 to turn off debugging
my $config_file = "config/cvit.cfg"; # default config file
# colors:
my %colors; # holds indices for allocated colors, keyed by color name
my %color_codes; # holds codes for all possible colors (based on rgb.txt)
my @map_colors; # used for hits of different classes
my @heat_colors; # used to display 'measure' records
my @class_color_names; # holds all possible names for class colors
my %class_colors; # associates color names to seq class (class= attribute)
# files and directories
my $out_filename;
# GFF fields
my $GFF_SEQID = 0;
my $GFF_SOURCE = 1;
my $GFF_FEATURE = 2;
my $GFF_START = 3;
my $GFF_END = 4;
my $GFF_SCORE = 5;
my $GFF_STRAND = 6;
my $GFF_FRAME = 7;
my $GFF_ATTRIBUTES = 8;
#######
### Get command line information
my $use_svg = 0;
my $associations_file = '';
my $title = '';
my %options = ();
getopts("c:o:st:", \%options);
if (defined($options{'c'})) { $config_file = $options{'c'}; }
if (defined($options{'o'})) { $out_filename = $options{'o'}; }
if (defined($options{'s'})) { $use_svg = 1; }
if (defined($options{'t'})) { $title = $options{'t'}; }
#######
### verify that we have enough information to run the script:
my $warning = <<EOF;
Usage for the CVIT script:
perl cvit.pl [opt] gff-file-in [gff-file-in]*
-c <file> alternative config file (default: config/cvit.cfg)
-o <string> base filename (default: unique id)
-s create SVG rather than PNG output
-t <string> title for image
*Multiple gff input files make possible various layers: chromosomes, centromeres, borders, etc.
For example (ignore line wraps):
perl cvit.pl -c config/cvit_histogram.cfg -v histogram -o MtChrXxMtLjTEs
data/MtChrs.gff3 data/BACborders.gff3 data/MtCentromeres.gff3
/web/medicago/htdocs/genome/upload/MtChrXxMtLjTEs.gff
The GFF data MUST contain some sequence records of type 'chromosome' or
there will be no way to draw the picture.
EOF
if ( !($ARGV[0]) && scalar(keys(%options)) == 0 ) { die $warning }
#######
### Get config information:
my $ini = new Config::IniFiles( -file => $config_file);
die "\nERROR: Couldn't parse $config_file! File may be missing or contain an error\n\n" if (!$ini);
#######
### Set debugging/logging information:
my $logfile = $ini->val('general', 'logfile');
my $errorfile = $ini->val('general', 'errorfile');
require "./errorlog.pm";
my $dbg = ErrorLog->new();
$dbg->createLog($debug, $logfile, $errorfile, "f"); # s=stdout, b=browser, f=log file
$dbg->logMessage("\n\n\n-----------------START----------------\n");
my $error; #always enabled
#######
### use appropriate GD package
if ($use_svg) {
$gd = "GD::SVG";
$image_format = "svg";
$image_class = "GD::SVG::Image";
$arrow_pkg = "GD::SVG::Arrow"; # does such a thing exist?
} else {
$gd = "GD";
$image_format = "png";
$image_class = "GD::Image";
$arrow_pkg = "GD::Arrow";
}
#$dbg->logMessage("Use_svg: [$use_svg], gd: [$gd], image_format: [$image_format], image_class: [$image_class]\n");
eval "use $gd";
eval "use $arrow_pkg";
# create fonts
my $gdLargeFont = eval "$gd" . "::gdLargeFont";
my $gdMediumBoldFont = eval "$gd" . "::gdMediumBoldFont";
my $gdSmallFont = eval "$gd" . "::gdSmallFont";
my $gdTinyFont = eval "$gd" . "::gdTinyFont";
my @fonts = ($gdLargeFont, $gdMediumBoldFont, $gdSmallFont, $gdTinyFont);
#######
### Get output file names; create unique base name if none given
if (!$out_filename || length($out_filename) == 0) {
$out_filename = getUniqueID(10);
}
#######
#### get user-defined sequence types; indicated by existence of 'feature' attribute
my %custom_types;
foreach my $section ($ini->Sections()) {
if ($ini->val($section, 'feature')) {
my $feature_name = $ini->val($section, 'feature');
$custom_types{$feature_name} = $section;
}
}#each section
#######
#print "custom_types:\n" . Dumper(%custom_types) . "\n";
### Read and parse gff input file(s) into tables
my @chromosomes; # array of chromosomes (reference, or backbone sequence) in GFF
my @ranges; # array of all ranges found in GFF data
my @positions; # array of all positions found in GFF data
my @borders; # array of all borders (e.g. of BACs) found in GFF data
my @markers; # array of all markers found in GFF data
my @centromeres; # array of all centromeres found in GFF data
my @measures; # array of all measure-value records in GFF data
foreach my $gfffile (@ARGV) {
$dbg->logMessage("\nRead gff file $gfffile\n");
read_gff($gfffile);
if (length($error) > 0) { #report file error, if any
$dbg->reportError("$error\n");
$error = "";
}
}
#######
#######################################
### Process data and draw the image
# these vars are set in store_chromosomes()
#TODO: is chrcolors used?
my (@chr_nm_sz, @chrnames, @chrstarts, @chrszs, @chrcolors);
my ($num_chroms, $ruler_min, $ruler_max);
store_chromosomes();
# these vars are set in set_up_chromosomes()
#TODO: difference between %chryloc and %chrstarts_pixels?
my (%chrxloc, %chryloc, %chrstarts_pixels); # chromosome x and y locs, start coords
my (%chrxloc, %chryloc); # chromosome x and y locs, start coords
my ($image_width, $image_height);
# Create base image: draw scale, title, and chromosomes
my $im = set_up_chromosomes_and_png();
# draw features on chromosomes
$im = draw_centromeres($im, \@centromeres, \%chrxloc, \%chryloc);
$im = draw_positions($im, \@positions, \%chrxloc, \%chryloc);
$im = draw_ranges($im, \@ranges, \%chrxloc, \%chryloc);
$im = draw_borders($im, \@borders, \%chrxloc, \%chryloc);
$im = draw_markers($im, \@markers, \%chrxloc, \%chryloc);
$im = draw_measures($im, \@measures, \%chrxloc, \%chryloc);
# #TODO: maybe delete this
# $im = draw_associations($im, \%marker_pos, $associations_file);
# print png image
my $out_image = "$out_filename.$image_format";
print_image($im, $out_image);
#######################################
###############################################################################
################################# subs ########################################
###############################################################################
# read_gff
# Read a gff file and put the records into position and range arrays
# according to start-stop fields (GFF_START, GFF_END)
###############################################################################
sub read_gff {
my $filename = $_[0];
open GFF, "<$filename" or $error = "Unable to open $filename because: $!";
if ($error) {
$dbg->reportError("$error\n");
$error = "";
}
else {
my $marker_count = 0;
my $line_count = 0;
while (<GFF>) {
$line_count++;
chomp; # get rid of line ending
# The ##FASTA directive indicates the remainder of the file contains
# fasta data, per the GFF3 specification
last if (/^>/ or /##FASTA/); # finished if we've reached fasta data
next if (/^#/); # skip comment lines
next if ((length) == 0); # skip blank lines
my $line = $_;
# NOTE: GFF3 specification does not allow use of spaces to separate
# columns, but CViT does.
my @record = split /\s+/, $line, 9; # permit more than one space char
# do a spot of error checking and reporting
if ( (scalar @record) != 9) {
# make sure this isn't really just a blank record
$line =~ s/\s//g;
next if ((length $line) == 0);
$dbg->reportError("Incorrect number of fields in file $filename, record $line_count [$_]\n " . scalar @record . " fields found, 9 expected.\n");
next;
}
my ($seqname, $source, $type, $start, $end, $score, $strand, $frame,
$attributes) = @record;
#$dbg->logMessage("$seqname | $source | $type | $start | $end | $score | $strand | $frame | $attributes\n");
if ($type =~ /chromosome/i) { # defines a chromosome
push @chromosomes, [@record];
}
elsif ($type =~ /centromere/i) { #defines a centromere
push @centromeres, [@record];
}
elsif ($type =~ /marker/i) { #defines a marker location
push @markers, [@record];
#TODO: what's this?
# $record[$GFF_ATTRIBUTES] =~ /Name=(.*)/;
# if ($1) {
# $marker_indices{$1} = $marker_count;
# }
# $marker_count++;
}
elsif ($type =~ /measure/i) { #defines a measure of importance
push @measures, [@record];
}
elsif (in_array("$source:$type", keys %custom_types)) {
my $section = $custom_types{"$source:$type"};
my $glyph = $ini->val($section, 'glyph');
#print "push @" . $glyph . "s, [\@record]\n";
eval "push @" . $glyph . "s, [\@record]";
}
elsif ($start == $end) { #assume a generic position
push @positions, [@record];
}
else { #assume a generic range
push @ranges, [@record];
}
#last if $line_count > 4;
}#each line
close(GFF);
}#GFF file exists
}#read_gff
###############################################################################
# store_chromosomes
# Put names, sizes (in chr units) into constructed-on-the-fly AoA
# Sets several global variables
###############################################################################
sub store_chromosomes {
# number of chromosome:
$num_chroms = scalar @chromosomes;
# Values in .ini file will be overridden if exceeded by chromosome start/end
$ruler_min = $ini->val('general', 'ruler_min', undef);
$ruler_max = $ini->val('general', 'ruler_max', undef);
# array of chromosome names and sizes:
foreach my $record (@chromosomes) {
push @chrnames, @$record[$GFF_SEQID];
push @chrstarts, @$record[$GFF_START];
my $size = @$record[$GFF_END] - @$record[$GFF_START];
push @chrszs, $size;
if (!$ruler_min || $ruler_min > @$record[$GFF_START]) {
$ruler_min = @$record[$GFF_START];
}
if (!$ruler_max || $ruler_max < @$record[$GFF_END]) {
$ruler_max = @$record[$GFF_END];
}
my $attributes = @$record[$GFF_ATTRIBUTES];
$attributes =~ /.*color=(.*);/;
my $chr_color = $1;
if ($chr_color && length($chr_color) > 0) {
push @chrcolors, $chr_color;
}
else {
push @chrcolors, "";
}
}#foreach chromosome
# construct AoA code dynamically, then evaluate.
my $code = '@chr_nm_sz = (';
for my $i (0 .. $num_chroms) {
$code .= '[$chrnames['.$i.'],$chrszs['.$i.']],'
}
chop $code; # get rid of trailing comma
$code .= ');';
eval $code;
}#store_chromosomes
###############################################################################
# set_up_chromosomes_and_png
# 1. Calculate $image_width, $image_height
# 2. make image object and colors for GD. Colors are indexed from 0;
# order matters.
# 3. print chromosome backgrounds
###############################################################################
sub set_up_chromosomes_and_png {
my $scale_factor = $ini->val('general', 'scale_factor');
my $image_padding = int($ini->val('general', 'image_padding'));
my $chrom_spacing = int($ini->val('general', 'chrom_spacing'));
my $chrom_width = int($ini->val('general', 'chrom_width'));
my $chrom_x_start = int($ini->val('general', 'chrom_x_start'));
my $show_strands = int($ini->val('general', 'show_strands'));
my $tick_interval = int($ini->val('general', 'tick_interval'));
my $tick_line_width = int($ini->val('general', 'tick_line_width'));
my $minor_tick_divisions = int($ini->val('general', 'minor_tick_divisions'));
# Full ruler length:
my $rule_len_pixels = ($ruler_max - $ruler_min) * $scale_factor;
# Get image sizes
$image_width = $num_chroms * $chrom_spacing
+ 2 * $image_padding
+ 2 * $chrom_width;
$image_height = $rule_len_pixels + (2 * $image_padding);
# Make image object.
my $im = new $image_class($image_width, $image_height) or die;
die "Unable to create image of size $image_width X $image_height\n" if (!$im);
# Create colors for GD. Colors are indexed from 0; order matters.
assign_colors($im);
#print "created " . scalar (keys %colors) . " colors.\n";
#print Dumper(%colors);
# calculate where the chromosomes will be located (in pixels)
for my $i (0 .. $#chr_nm_sz-1) {
# x-coord
$chrxloc{$chrnames[$i]}
= $chrom_x_start + $image_padding + ($chrom_spacing * $i);
# y-coord
$chryloc{$chrnames[$i]}
= $image_padding + ($chrstarts[$i] - $ruler_min) * $scale_factor;
$chrstarts_pixels{$chrnames[$i]} = $chrstarts[$i];
}#each chromosome
# border rectangle around entire image
# x,y for upper left; then x,y for lower right.
$im->rectangle(0, 0, $image_width-1, $image_height-1, get_color($im, 'gray15'));
# draw the title
if (!$title || $title == '') {
$title = $ini->val('general', 'title');
}
$title =~ s/"//g;
$im->string($gdMediumBoldFont,
5,
$ini->val('general', 'title_height'),
$title,
get_color($im, 'black'));
# Show the ruler
if ($ini->val('general', 'display_ruler') == 1) {
# This many units on the ruler
my $num_units = $ruler_max - $ruler_min;
my $num_of_ticks
= int($num_units / $tick_interval);
my $scale_color = get_color($im, 'gray60');
# Draw units label
$im->string($gdMediumBoldFont,
5,
$image_padding - 15,
$ini->val('general', 'ruler_units'),
$scale_color);
# left scale starts here:
my $x_start = 5;
# top of both scales:
my $y_start = $image_padding;
# draw scale backbone on both sides of image
$im->line($x_start,
$y_start,
$x_start,
$rule_len_pixels + $y_start,
$scale_color);
$im->line($image_width-5,
$image_padding,
$image_width-5,
$rule_len_pixels + $y_start,
$scale_color);
# draw tick marks
for my $i (0 .. $num_of_ticks) {
my $tick_pixels = $i * $tick_interval * $scale_factor;
my $tick_label = int($ruler_min + ($i * $tick_interval));
# major tick
my $v_major = $tick_pixels + $image_padding;
# major tick marks left then right
$im->line(5,
$v_major,
5 + $tick_line_width,
$v_major,
$scale_color);
$im->line($image_width - 5,
$v_major,
$image_width - 5 - $tick_line_width,
$v_major,
$scale_color);
# minor tick marks left then right
for my $j (0 .. $minor_tick_divisions) {
my $v_minor = $v_major
+ $j * $tick_interval
* $scale_factor / $minor_tick_divisions;
last if ($v_minor >= $rule_len_pixels); # stop if past end of chromosome
$im->line(5,
$v_minor,
5 + $tick_line_width/2,
$v_minor,
$scale_color);
$im->line($image_width - 5,
$v_minor,
$image_width - 5 - $tick_line_width/2,
$v_minor,
$scale_color);
}#minor tick marks
#TODO: draw units on right scale also; need to be right-aligned
# draw numbers at tick marks
$im->string($gdMediumBoldFont,
5 + 2 * $tick_line_width,
$v_major - 5,
"$tick_label",
$scale_color);
}#tick marks
}#display ruler
print "Draw " . $#chr_nm_sz . " chromosomes.\n";
# draw chromosomes
for my $i (0 .. $#chr_nm_sz-1) {
my $chr_name = $chr_nm_sz[$i][0];
my $chr_len = $chr_nm_sz[$i][1] * $scale_factor;
#print "$chr_name is ".$chr_nm_sz[$i][1]." long\n";
my $x0 = $chrxloc{$chr_name};
my $y0 = $chryloc{$chr_name};
my $x1 = $x0 + $chrom_width;
my $y1 = $y0 + $chr_len;
#get color
my $chr_color = get_color($im, 'gray50');
if (length($chrcolors[$i]) > 0) {
$chr_color = get_color($im, $chrcolors[$i]);
}
# Draw chromosome
if ($show_strands == 0) {
$im->filledRectangle($x0, $y0, $x1, $y1, $chr_color);
}
else {
my $width = $chrom_width/2 - 2;
my $pos_strand
= GD::Arrow::RightHalf->new(-X1=>$x0+$width, -Y1=>$y1,
-X2=>$x0+$width, -Y2=>$y0,
-WIDTH=>$width);
$im->filledPolygon($pos_strand, $chr_color);
my $neg_strand
= GD::Arrow::RightHalf->new(-X1=>$x1-$width, -Y1=>$y0,
-X2=>$x1-$width, -Y2=>$y1,
-WIDTH=>$width);
$im->filledPolygon($neg_strand, $chr_color);
}
# chromosome label
$im->string($gdMediumBoldFont,
$x0 - 15,
$image_padding - 20,
$chrnames[$i],
get_color($im, 'gray50'));
if ($show_strands) {
# show +/- at both ends of each chromosome
$im->string($gdSmallFont, $x0, $image_padding-10, '+',
get_color($im, 'gray50'));
$im->string($gdSmallFont, $x1-2, $image_padding-10, '-',
get_color($im, 'gray50'));
$im->string($gdSmallFont, $x0, $y1, '+',
get_color($im, 'gray50'));
$im->string($gdSmallFont, $x1-2, $y1, '-',
get_color($im, 'gray50'));
}
}#draw chr backgrounds
return $im;
}#set_up_chromosomes_and_png
###############################################################################
# assign_colors
# Create colors corresponding to the standard colors available for X11
###############################################################################
sub assign_colors {
my $im = @_[0];
# order matters; start with these two:
$colors{'white'} = $im->colorAllocate(255, 255, 255);
$colors{'black'} = $im->colorAllocate(0, 0, 0);
open COLORS, "<rgb.txt" or die "Unable to open color file rgb.txt";
while (<COLORS>) {
next if (/^!/); # skip comment line(s)
chomp;
$_ =~ /(\d+)\s+(\d+)\s+(\d+)\s+(\w+)/;
my $red = $1;
my $green = $2;
my $blue = $3;
my $name = lc($4);
$color_codes{$name} = [$red, $green, $blue];
}#all lines in color file
# Create and array of class color names
@class_color_names = split /,\s*/, $ini->val('general', 'class_colors');
}#assign_colors
###############################################################################
# create_heat_colors
###############################################################################
sub create_heat_colors {
my ($im, $heat_colors_ref) = @_;
my $index = 0; # because loop counter runs backward
for (my $i=170; $i>=0; $i--) {
my ($red, $green);
if ($i <= 85) {
# creates red to yellow colors
$red = 255;
$green = (3 * $i);
}
else {
# creates yellow to green colors
$red = (255 - (3 * ($i - 85)));
$green = 255;
}
my $new_color = $im->colorAllocate($red, $green, 0);
if ($new_color == -1) {
$new_color = $im->colorClosest($red, $green, 0);
}
if ($new_color != -1) {
$heat_colors_ref->[$index] = $new_color;
$index++;
}
}#for each color
#print "Created heat_colors array\n" . Dumper($heat_colors_ref) . "\n";
return $im;
}#create_heat_colors
###############################################################################
# draw_borders
###############################################################################
sub draw_borders {
my ($im, $borders_ref, $chrxloc_ref, $chryloc_ref) = @_;
# dereference arrays and hashes
my @borders = @$borders_ref;
my %chrxloc = %$chrxloc_ref;
my %chryloc = %$chryloc_ref;
my $scale_factor = $ini->val('general', 'scale_factor', 1);
my $chrom_width = int($ini->val('general', 'chrom_width', 5));
my $def_label_offset = int($ini->val('border', 'label_offset', 5));
my $def_color = $ini->val('border', 'color', 'red');
my $def_fill = $ini->val('border', 'fill', 0);
my $def_draw_label = $ini->val('border', 'draw_label', 1);
my $def_font = $ini->val('border', 'font', 1);
my $def_label_offset = $ini->val('border', 'label_offset', 5);
# each class of border will have a different color:
my $next_class_color = 0;
print "Draw " . @borders . " borders.\n";
foreach my $record (@borders) {
my ($chromosome, $source, $type, $start, $end, $score, $strand, $frame,
$attrs) = @$record;
#TODO: put this in a function
my %attributes = get_attributes($attrs);
# These can be overriden within a specific .ini file section:
my ($color, $fill, $draw_label, $font, $label_offset);
my $section;
if ($custom_types{"$source:$type"}) {
$section = $custom_types{"$source:$type"};
# check for overrides
$color = $ini->val($section, 'color', $def_color);
$fill = $ini->val($section, 'fill', $def_fill);
$draw_label = $ini->val($section, 'draw_label', $def_draw_label);
$font = $ini->val($section, 'font', $def_font);
$label_offset = $ini->val($section, 'label_offset', $def_label_offset);
}
else {
$color = $def_color;
$fill = $def_fill;
$draw_label = $def_draw_label;
$font = $def_font;
$label_offset = $def_label_offset;
}
# get border name
my $name = '';
if ($attributes{'name'}) {
$name = $attributes{'name'};
}
elsif ($attributes{'clone'}) {
$name = $attributes{'clone'};
}
# use class color?
if ($attributes{'class'}) {
my $class = $attributes{'class'};
if (!$class_colors{$class}) {
my $new_color
= get_color($im, $class_color_names[$next_class_color]);
$class_colors{$class} = $class_color_names[$next_class_color];
$color = $class_colors{$class};
$next_class_color++;
}
else {
$color = $class_colors{$class};
}
}
if ($attributes{'color'} && $attributes{'color'} ne '') {
# overrides setting in .ini file and class color
$color = $attributes{'color'};
}
# calculate relative value (starting from lowest value on scale)
my $rel_start = $start - $ruler_min;
my $rel_end = $end - $ruler_min;
# calculate location:
# feature start is relative to chr start
my $range_size = $end - $start;
my $y0 = $chryloc{$chromosome} + $scale_factor * $rel_start;
my $y1 = $y0 + $range_size * $scale_factor;
# check for negative length
$y1 = $y0 + 1 if ($y1-$y0 <= 0);
my $x0 = $chrxloc{$chromosome};
my $x1 = $x0 + $chrom_width;
# draw and label range
if ($fill == 1) {
$im->filledRectangle($x0, $y0, $x1, $y1, get_color($im, $color)); # draw a bar
$im->line($x0, $y0, $x1, $y0, get_color($im, 'black'));
$im->line($x0, $y1, $x1, $y1, get_color($im, 'black'));
}
else {
# indicate just top and bottom border of range with horizontal lines
$im->line($x0, $y0, $x1, $y0, get_color($im, $color));
$im->line($x0, $y1, $x1, $y1, get_color($im, $color));
}
if ($draw_label == 1) {
my $font_size = $fonts[$font];
$im->string($font_size,
$x1 + $label_offset,
$y0 + ($y1 - $y0) / 2 - 6, # attempt vertical centering
$name,
get_color($im, 'black'));
}
}#each border
return $im;
}#draw_borders
###############################################################################
# draw_centromeres
# Draws centromeres as rectangles on top of the chromosomes;
# can handle points or ranges.
###############################################################################
sub draw_centromeres {
my ($im, $centromeres_ref, $chrxloc_ref, $chryloc_ref) = @_;
# dereference arrays and hashes
my @centromeres = @$centromeres_ref;
my %chrxloc = %$chrxloc_ref;
my %chryloc = %$chryloc_ref;
my $chrom_width = int($ini->val('general', 'chrom_width'));
my $image_padding = int($ini->val('general', 'image_padding'));
my $scale_factor = $ini->val('general', 'scale_factor');
my $centromere_overhang = int($ini->val('centromere', 'centromere_overhang'));
my $def_color = $ini->val('centromere', 'centromere_color');
print "Draw " . @centromeres . " centromeres.\n";
# draw each centromere:
foreach my $centromere (@centromeres) {
my ($chromosome, $source, $type, $start, $end, $score, $strand, $frame,
$attributes) = @$centromere;
my %attributes = get_attributes($attributes);
# This can be overriden within a specific .ini file section:
my $color;
my $section;
if ($custom_types{"$source:$type"}) {
$section = $custom_types{"$source:$type"};
# check for overrides
$color = $ini->val($section, 'color', $def_color);
}
else {
$color = $def_color;
}
# calculate relative value (starting from lowest value on scale)
my $rel_start = $start - $ruler_min;
my $rel_end = $end - $ruler_min;
# calculate location:
my $x0 = $chrxloc{$chromosome} - $centromere_overhang;
my $x1 = $x0 + $chrom_width + 2 * $centromere_overhang;
my $y0 = $image_padding + $scale_factor * $rel_start;
my $y1 = $image_padding + $scale_factor * $rel_end;
# draw centromere as filled rectangle
$im->filledRectangle($x0, $y0, $x1, $y1, get_color($im, $color)); # draw a bar
}
return $im;
}#draw_centromeres
#####################################################
# draw_dot_and_label
#####################################################
sub draw_dot_and_label {
my ($im, $x1, $y1, $label, $color, $shape, $width, $draw_label, $font,
$label_offset) = @_;
# center the shape on $y1
$y1 - $width/2;
# keep x and y from drawing outside of image borders
if ($x1 > $image_width-10) {$x1 = $image_width-10}
if ($y1 > $image_height-10) {$y1 = $image_height-10}
# draw dot and label
if ($shape =~ /^circle/) { # circle
# center the circle on the y position
$im->arc($x1,
$y1 + $width/2,
$width,
$width,
0, 360, $color);
$im->fill($x1, $y1 + $width/2, $color);
}
elsif ($shape =~ /^rect/) { # rectangle
if ($width == 1) { # min rect height seems to be 3 pixels
$im->line($x1, $y1, $x1+$width, $y1, $color);
}
else {
$im->filledRectangle($x1, $y1, $x1 + $width,
$y1 + $width - 1, $color);
}
}
else { die " unknown dot shape [$shape] (should be circle or rect)\n" }
# draw dot labels
if ($draw_label == 1) {
my $font_size = $fonts[$font];
my $x_shift = int($label_offset);
$im->string($font_size,
$x1 + $x_shift,
$y1 - 0.5*$width,
$label,
$color);
}
}#draw_dot_and_label
###############################################################################
# draw_heat_measures
###############################################################################
sub draw_heat_measures {
my ($im, $measures_ref, $chrxloc_ref, $chryloc_ref, $max_value,
$heat_colors_ref) = @_;
# dereference arrays and hashes
my @measures = @$measures_ref;
my @heat_colors = @$heat_colors_ref;
my %chrxloc = %$chrxloc_ref;
my %chryloc = %$chryloc_ref;
my $chrom_width = $ini->val('general', 'chrome_width', 5);
my $scale_factor = $ini->val('general', 'scale_factor', 1);
my $offset = $ini->val('measure', 'offset', 0);
my $width = $ini->val('measure', 'width', 2);
if ($max_value > 0) {
# $heat_color_unit will be multiplied by each measure-value to get
# heat-color array index
my $heat_color_unit = (scalar @heat_colors) / $max_value;
print "Draw " . @measures . " heat measures.\n";
# draw all the measures
foreach my $record (@measures) {
my ($chromosome, $source, $type, $start, $end, $score, $strand, $frame,
$attributes) = @$record;
my %attributes = get_attributes($attributes);
my $value = $attributes{'value'};
my $color_index = int ($value * $heat_color_unit);
if ($color_index >= scalar @heat_colors) {
$color_index = (scalar @heat_colors - 1);
}
my $color = $heat_colors[$color_index];
# calculate relative start and end coordinates (starting from lowest
# value on scale)
my $rel_start = $start - $ruler_min;
my $rel_end = $end - $ruler_min;
# calculate location:
my $x0 = $chrxloc{$chromosome} + $chrom_width + $offset;
my $y0 = $chryloc{$chromosome} + $scale_factor * $rel_start;
my $x1 = $x0 + $width;
my $y1 = $chryloc{$chromosome} + $scale_factor * $rel_end;
# make sure range is at least 1 pixel:
if ( int ($y1 - $y0) < 1 ) { $y1 = $y0+1; }
# draw measure as a range
$im->filledRectangle($x0, $y0, $x1, $y1, $color);
}#foreach record
}#there are some values to display
return $im;
}#draw_heat_measures
###############################################################################
# draw_histogram_measures
###############################################################################
sub draw_histogram_measures {
my ($im, $measures_ref, $chrxloc_ref, $chryloc_ref, $max_value) = @_;
# dereference arrays and hashes
my @measures = @$measures_ref;
my %chrxloc = %$chrxloc_ref;
my %chryloc = %$chryloc_ref;
my $image_padding = int($ini->val('general', 'image_padding', 60));
my $chrom_spacing = $ini->val('general', 'chrome_spacing', 100);
my $chrom_width = $ini->val('general', 'chrome_width', 5);
my $scale_factor = $ini->val('general', 'scale_factor', 1);
my $def_color = $ini->val('measure', 'color', 'red');
my $histogram_scaling = $ini->val('measure', 'histogram_scaling', .5);
print "Draw " . @measures . " histogram measures.\n";
if ($max_value > 0) {
my $max_histogram = $chrom_spacing - 2 * $chrom_width;
my $histogram_unit = $histogram_scaling * ($max_histogram / $max_value);
# draw all the measures
foreach my $record (@measures) {
my ($chromosome, $source, $type, $start, $end, $score, $strand, $frame,
$attributes) = @$record;
my %attributes = get_attributes($attributes);
# This can be overriden within a specific .ini file section:
my $color;
my $section;
if ($custom_types{"$source:$type"}) {
$section = $custom_types{"$source:$type"};
# check for overrides
$color = $ini->val($section, 'color', $def_color);
}
else {
$color = $def_color;
}
my $value = $attributes{'value'};
# calculate relative start and end coordinates (starting from lowest value on scale)
my $rel_start = $start - $ruler_min;
my $rel_end = $end - $ruler_min;
# calculate histogram rectangle:
my $x0 = $chrxloc{$chromosome} + $chrom_width + 1;
my $y0 = $chryloc{$chromosome} + $scale_factor * $rel_start;
my $x1 = $x0 + $value * $histogram_unit;
my $y1 = $chryloc{$chromosome} + $scale_factor * $rel_end;
# make sure histogram bar is at least 1 pixel high:
if ( int ($y1 - $y0) < 1 ) { $y1 = $y0+1; }
# draw measure as a histogram bar
$im->filledRectangle($x0, $y0, $x1, $y1, get_color($im, $color));
}#foreach record
}#there are some actual values to display
return $im;
}#draw_histogram_measures
###############################################################################
# draw_markers
###############################################################################
sub draw_markers {
my ($im, $markers_ref, $chrxloc_ref, $chryloc_ref) = @_;
# dereference arrays and hashes
my @markers = @$markers_ref;
my %chrxloc = %$chrxloc_ref;
my %chryloc = %$chryloc_ref;
my $image_padding = int($ini->val('general', 'image_padding', 60));
my $scale_factor = $ini->val('general', 'scale_factor', 1);
my $chrom_width = int($ini->val('general', 'chrom_width', 5));
my $def_color = $ini->val('marker', 'color', 'red');
my $def_width = int($ini->val('marker', 'width', 2));
my $def_offset = int($ini->val('marker', 'offset', 1));
my $def_label_offset = int($ini->val('marker', 'label_offset', 5));
my $def_font = int($ini->val('marker', 'font', 5));
my $def_draw_label = $ini->val('marker', 'draw_label', 1);
my ($x0, $x1, $y0, $y1);
# each class of marker will have a different color:
my $next_class_color = 0;
print "Draw " . @markers . " markers.\n";
# map markers by chromosome and start position (start)
foreach my $record (@markers) {
# get record and read label and class (query sequence) from attributes
my ($chromosome, $source, $type, $start, $end, $score, $strand, $frame,
$attrs) = @$record;
my %attributes = get_attributes($attrs);
# These can be overriden within a specific .ini file section:
my ($color, $width, $offset, $draw_label, $font, $label_offset);
my $section;
if ($custom_types{"$source:$type"}) {
$section = $custom_types{"$source:$type"};
# check for overrides
$color = $ini->val($section, 'color', $def_color);
$width = $ini->val($section, 'width', $def_width);
$offset = $ini->val($section, 'offset', $def_offset);
$draw_label = $ini->val($section, 'draw_label', $def_draw_label);
$font = $ini->val($section, 'font', $def_font);
$label_offset = $ini->val($section, 'label_offset', $def_label_offset);
}
else {
$color = $def_color;
$width = $def_width;
$offset = $def_offset;
$draw_label = $def_draw_label;
$font = $def_font;
$label_offset = $def_label_offset;
}
# use class color?
if ($attributes{'class'}) {
my $class = $attributes{'class'};
if (!$class_colors{$class}) {
my $new_color
= get_color($im, $class_color_names[$next_class_color]);
$class_colors{$class} = $class_color_names[$next_class_color];
$color = $class_colors{$class};
$next_class_color++;
}
else {
$color = $class_colors{$class};
}
}
# color= attributes overrides everything else
if ($attributes{'color'}) {
$color = $attributes{'color'};
}
my $label = $attributes{'name'};
# calculate relative start coordinate (starting from lowest value on scale)
my $rel_start = $start - $ruler_min;
# calculate y location on image for marker
$y1 = $image_padding + $scale_factor * $rel_start;
# calculate x locations on image for marker
if ($ini->val('general', 'show_strands') == 1) {
if ($strand eq '+') {
$x0 = $chrxloc{$chromosome} - $width - $offset;
$x1 = $x0 + $width;
}
elsif ($strand eq '-') {
$x0 = $chrxloc{$chromosome} + $chrom_width + $offset;
$x1 = $x0 + $width;
}
else {
$x0 = $chrxloc{$chromosome};
$x1 = $x0 + $chrom_width;
}
}
else {
$x0 = $chrxloc{$chromosome} + $chrom_width + $offset;
$x1 = $x0 + $width;
}
# attribute overrides color
if ($attributes{'color'}) {
$color = $attributes{'color'};
}
# draw a line for the marker
$im->line($x0, $y1, $x1, $y1, get_color($im, $color));
# if there is a label and labels should be displayed, draw the label
if ($draw_label == 1
&& $label && length $label > 0) {
$im->string($fonts[$font],
$x1 + $label_offset,
$y1 - 3, # attempt vertical centering
$label,
get_color($im, $color));
}
}#each marker
return $im;
}#draw_markers
###############################################################################
# draw_measures
###############################################################################
sub draw_measures {
my ($im, $measures_ref, $chrxloc_ref, $chryloc_ref) = @_;
# dereference arrays and hashes
my @measures = @$measures_ref;
my %chrxloc = %$chrxloc_ref;
my %chryloc = %$chryloc_ref;
my $display = $ini->val('measure', 'display', 'histogram');
# calculate overall max value
my $max_value;
my $record_count = 0;
foreach my $record (@measures) {
$record_count++;
my ($seqid, $source, $type, $start, $end, $score, $strand, $frame,
$attributes) = @$record;
my %attributes = get_attributes($attributes);
if (!$attributes{'value'}) {
$dbg->reportError("No value in record $record_count");
}
my $value = int($attributes{'value'});
if ($max_value < $value) { $max_value = $value; }
}#each record
if ($max_value > 0) {
if ($display eq 'histogram') {
$im = draw_histogram_measures($im, \@measures, \%chrxloc, \%chryloc,
$max_value);
}
else {
my @heat_colors = []; # set by create_heat_colors()
$im = create_heat_colors($im, \@heat_colors);
$im = draw_heat_measures($im, \@measures, \%chrxloc, \%chryloc,
$max_value, \@heat_colors);
}
}
return $im;
}#draw_measures
###############################################################################
# draw_positions
# for each chr #$i, print $jth locus and query.
# Pile up nearby hits.
###############################################################################
sub draw_positions {
my ($im, $positions_ref, $chrxloc_ref, $chryloc_ref) = @_;
# dereference arrays and hashes
my @positions = @$positions_ref;
my %chrxloc = %$chrxloc_ref;
my %chryloc = %$chryloc_ref;
my $chrom_width = int($ini->val('general', 'chrom_width', 5));
my $image_padding = int($ini->val('general','image_padding', 60));
my $scale_factor = $ini->val('general', 'scale_factor', 1);
my $def_color = $ini->val('position', 'color', 'red');
my $def_width = int($ini->val('position', 'width', 5));
my $def_shape = $ini->val('position', 'shape', 'circle');
my $def_draw_label = $ini->val('position', 'draw_label', 1);
my $def_font = $ini->val('position', 'font', 1);
my $def_label_offset = $ini->val('position', 'label_offset', 5);
# holds # of positions in each ah-sized bin for each chr
my %chr_bin_contents;
# each class of position will have a different color:
my $next_class_color = 0;
print "Draw " . @positions . " positions.\n";
foreach my $record (@positions) {
# map positions by chromosome and start position (start)
my ($chromosome, $source, $type, $start, $end, $score, $strand, $frame,
$attrs) = @$record;
my %attributes = get_attributes($attrs);
# These can be overriden within a specific .ini file section:
my ($color, $width, $shape, $draw_label, $font, $label_offset);
my $section;
if ($custom_types{"$source:$type"}) {
$section = $custom_types{"$source:$type"};
# check for overrides
$color = $ini->val($section, 'color', $def_color);
$width = $ini->val($section, 'width', $def_width);
$shape = $ini->val($section, 'shape', $def_shape);
$draw_label = $ini->val($section, 'draw_label', $def_draw_label);
$font = $ini->val($section, 'font', $def_font);
$label_offset = $ini->val($section, 'label_offset', $def_label_offset);
}
else {
$color = $def_color;
$width = $def_width;
$shape = $def_shape;
$draw_label = $def_draw_label;
$font = $def_font;
$label_offset = $def_label_offset;
}
if ($width < 1) { $width = 1; }
# use class color?
if ($attributes{'class'}) {
my $class = $attributes{'class'};
if (!$class_colors{$class}) {
my $new_color
= get_color($im, $class_color_names[$next_class_color]);
$class_colors{$class} = $class_color_names[$next_class_color];
$color = $class_colors{$class};
$next_class_color++;
}
else {
$color = $class_colors{$class};
}
}
# color= attribute overrides everything
if ($attributes{'color'} && $attributes{'color'} ne '') {
# overrides setting in .ini file and class color
$color = $attributes{'color'};
}
# Get the label (if any)
my $name = '';
if ($attributes{'name'}) {
$name = $attributes{'name'};
}
# calculate relative value (starting from lowest value on scale)
my $rel_start = $start - $ruler_min;
# calculate y location:
my $y = int($image_padding + $scale_factor * $rel_start);
# calculate x position
my ($x, $pileup_count);
my $bin = $y / $width;
if ($ini->val('general', 'show_strands') == 1) {
if ($strand eq '+') {
$pileup_count = ++$chr_bin_contents{$chromosome}{'plus'}[$bin];
$x = int($chrxloc{$chromosome} - $pileup_count * 3 * $width/4);
}
elsif ($strand eq '-') {
$pileup_count = ++$chr_bin_contents{$chromosome}{'minus'}[$bin];
$x = int($chrxloc{$chromosome} + $chrom_width + $pileup_count * 3 * $width/4);
}
else {
$x = int($chrxloc{$chromosome});
}
}#showing both strands
else {
# see if the position has to be stepped out to avoid overlapping others
$pileup_count = ++$chr_bin_contents{$chromosome}[$bin];
$x = int($chrxloc{$chromosome} + $chrom_width + $pileup_count * 3 * $width/4);
}
#print "pileup count: $pileup_count\n";
#print "$name: x, y: $x, $y\n";
# draw position
draw_dot_and_label($im, $x, $y, $name, get_color($im, $color), $shape,
$width, $draw_label, $font, $label_offset);
}#foreach record
return $im;
}#draw_positions
###############################################################################
# draw_ranges
###############################################################################
sub draw_ranges {
my ($im, $ranges_ref, $chrxloc_ref, $chryloc_ref) = @_;
my @unsorted_ranges = @$ranges_ref; # dereferance array
my %chrxloc = %$chrxloc_ref;
my %chryloc = %$chryloc_ref;
# order ranges by chromosome and start position
my @ranges = sort {
if ($a->[0] gt $b->[0]) { return 1; }
elsif ($a->[0] lt $b->[0]) { return -1; }
else {
if ($a->[4] > $b->[4]) { return 1; }
elsif ($a->[4] < $b->[4]) { return -1; }
else { return 0; }
}
} @unsorted_ranges;
my $scale_factor = $ini->val('general', 'scale_factor', 1);
my $chrom_width = int($ini->val('general', 'chrom_width', 5));
my $def_offset = int($ini->val('range', 'offset', 0));
my $def_color = $ini->val('range', 'color', 'red');
my $def_width = int($ini->val('range', 'width', 0));
my $def_draw_label = $ini->val('range', 'draw_label', '');
my $def_label_offset = int($ini->val('range', 'label_offset', 0));
my $def_font = $ini->val('range', 'font', 1);
# Holds the end of the last range drawn adjacent to the chromosome.
# Subsequent ranges will be bumped out until a range start moves past this
# point.
my $pileup_end;
my $bumpout;
# draw bars for each region, looping through the color list;
# each class of position will have a different color:
my $max_colors = 0;
# each class of range will have a different color:
my $next_class_color = 0;
print "Draw " . @ranges . " ranges.\n";
foreach my $record (@ranges) {
my ($chromosome, $source, $type, $start, $end, $score, $strand, $frame,
$attrs) = @$record;
my %attributes = get_attributes($attrs);
# These can be overriden within a specific .ini file section:
my ($offset, $color, $width, $draw_label, $label_offset, $font);
my $section;
if ($custom_types{"$source:$type"}) {
$section = $custom_types{"$source:$type"};
# check for overrides
$offset = $ini->val($section, 'offset', $def_offset);
$color = $ini->val($section, 'color', $def_color);
$width = $ini->val($section, 'width', $def_width);
$draw_label = $ini->val($section, 'draw_label', $def_draw_label);
$label_offset = $ini->val($section, 'label_offset', $def_label_offset);
$font = $ini->val($section, 'font', $def_font);
}
else {
$offset = $def_offset;
$color = $def_color;
$width = $def_width;
$draw_label = $def_draw_label;
$label_offset = $def_label_offset;
$font = $def_font;
}
# get range name
my $name = '';
if ($attributes{'name'}) {
$name = $attributes{'name'};
}
elsif ($attributes{'clone'}) {
$name = $attributes{'clone'};
}
# use class color?
if ($attributes{'class'}) {
my $class = $attributes{'class'};
if (!$class_colors{$class}) {
my $new_color
= get_color($im, $class_color_names[$next_class_color]);
$class_colors{$class} = $class_color_names[$next_class_color];
$color = $class_colors{$class};
$next_class_color++;
}
else {
$color = $class_colors{$class};
}
}
# color= attributes overrides everything else
if ($attributes{'color'}) {
$color = $attributes{'color'};
}
# calculate relative value (starting from lowest value on scale)
my $rel_start = $start - $ruler_min;
my $rel_end = $end - $ruler_min;
# calculate location
my ($x0, $x1, $y0, $y1);
# feature start is relative to chr start
my $range_size = $end - $start;
$y0 = $chryloc{$chromosome} + $scale_factor * $rel_start;
$y1 = $y0 + $range_size * $scale_factor;
# check for negative length
$y1 = $y0 + 1 if ($y1-$y0 <= 0);
# check to see if this range needs to be bumped out
my ($rt_pileup_end, $lf_pileup_end);
if ($ini->val('general', 'show_strands') == 1 || $strand ne '+') {
if (!$rt_pileup_end
|| $rel_end*$scale_factor < $rt_pileup_end) {
# starting a new chromosome; reset pileup_end
$rt_pileup_end = int($rel_end * $scale_factor);
$bumpout = 0;
}
elsif (int($rel_start*$scale_factor) <= $rt_pileup_end) {
# bump out the range bar
$bumpout += $width + 2;
}
else {
# start a new pileup
$rt_pileup_end = int($rel_end * $scale_factor);
$bumpout = 0;
}
}#range on right side of chromosome
elsif ($ini->val('general', 'show_strands') == 1 && $strand eq '+') {
if (!$lf_pileup_end
|| $rel_end*$scale_factor < $lf_pileup_end) {
# starting a new chromosome; reset pileup_end
$lf_pileup_end = int($rel_end * $scale_factor);
$bumpout = 0;
}
elsif (int($rel_start*$scale_factor) <= $lf_pileup_end) {
# bump out the range bar
$bumpout += $width + 2;
}
else {
# start a new pileup
$lf_pileup_end = int($rel_end * $scale_factor);
$bumpout = 0;
}
}#range on left side of chromosome
if ($ini->val('general', 'show_strands') == 1) {
if ($strand eq '+') {
# draw to the left of the chromosome
$x0 = $chrxloc{$chromosome} - $offset - $bumpout - $width;
$x1 = $x0 + $width;
}
elsif ($strand eq '-') {
# draw to the right of the chromosome
$x0 = $chrxloc{$chromosome}
+ $chrom_width + $offset + $bumpout;
$x1 = $x0 + $width;
}
else {
# draw on top of the two strands
$x0 = $chrxloc{$chromosome} + 2;
$x1 = $x0 + $chrom_width - 4;
}
}#show chromosome strands
else {
# draw right of chromosome according to offset
$x0 = $chrxloc{$chromosome}
+ $chrom_width + $offset + $bumpout;
$x1 = $x0 + $width;
}
# draw and label range
$im->filledRectangle($x0, $y0, $x1, $y1, get_color($im, $color)); # draw a bar
$im->line($x0, $y1, $x1, $y1, get_color($im, 'black'));
if ($draw_label == 1) {
$im->string($fonts[$font],
$x1 + $label_offset,
$y0 + ($y1 - $y0) / 2 - 6, # attempt vertical centering
$name,
get_color($im, 'black'));
}
}
return $im;
}#draw_ranges
###############################################################################
# print_image
# print image out
###############################################################################
sub print_image {
my ($im, $in_path_and_file) = @_;
open (PNG1, "> $in_path_and_file")
or die "can't open out $in_path_and_file: $!";
binmode (PNG1);
print PNG1 $im->$image_format;
close PNG1;
}#print_image
###############################################################################
# get_attributes
###############################################################################
sub get_attributes {
my $attrs = $_[0];
my @attribute_list = split /;/, $attrs;
return map { lc(attr_key($_)) => attr_val($_) } @attribute_list;
}#get_attributes
sub attr_key {
my @parts = split(/=/, $_);
return $parts[0];
}
sub attr_val {
my @parts = split(/=/, $_);
return $parts[1];
}
###############################################################################
###############################################################################
# Bitty utility functions
###############################################################################
sub get_color {
my ($im, $name, $default) = @_;
$name = lc($name);
if ($colors{$name}) {
return $colors{$name};
}
elsif ($default && $colors{$default}) {
return $colors{$default};
}
else {
if ($color_codes{$name}) {
my ($red, $green, $blue) = @{$color_codes{$name}};
my $new_color = $im->colorAllocate($red, $green, $blue);
if ($new_color == -1) {
$new_color = $im->colorClosest($red, $green, $blue);
}
if ($new_color != -1) {
$colors{$name} = $new_color;
return $new_color;
}
}#color appears in table
}#color doesn't already exist
# failed to find the color; take a wild guess
$dbg->reportError("Unable to find color [$name]\n");
return $colors{'black'}; # guaranteed to exist
}#get_color
sub getUniqueID {
my $length = $_[0];
my $unique_id = "";
for(my $i=0 ; $i<$length ;) {
my $ch = chr(int(rand(127)));
if( $ch =~ /[a-zA-Z0-9]/) {
$unique_id .=$ch;
$i++;
}
}
return $unique_id;
}#getUniqueID
sub in_array {
#print "incoming: " . Dumper(@_);
my $string = shift @_;
#print "check for $string in " . Dumper(@_);
#if (!(grep $_ eq $string, @_)) { print " not found\n"; } else { print " in array\n"; }
return (grep $_ eq $string, @_);
}#in_array
###############################################################################
######### VERSIONS ############################################################
#
# v x June+ blast_viewer072904.pl Atif- Start. Doesn't separate nearby hits.
# v x+ July30 04 SC: rewrite - Steven. Maybe hits mapped to wrong chroms?
# v0.50 July31'04 SC: Construct AoA for N chromosomes dynamically and eval.
# Simplify window- and chromosome-drawing parameters. Document.
# Add more colors. Add tick marks, chromosome names.
# v0.51 SC: Minor cleanup
# v0.52 SC: Add minor tick marks. More cleanup.
# v0.53 SC: Allow additional field for BAC name in input file
# v0.54 SC: Remove preexisting before creating new. Messed with paths.
# Posted for server
# v0.55 SC: Fix bad bug: was assigning hits to first chromosome encountered.
# Required ugly hashes. Also fixed out-of-image-bounds bug; added $xinc
# v0.56 Aug10'04 SC: Change name to chromviewer.
# Start versioning for all chromviewer scripts
# v0.58 Aug16'04 SC: handle multiple blast targets.
# v0.59 Aug22'04 SC: Fix bug that stopped output if input had interior line return.
# Modify colors. Add text output: queries & colors; queries & positions.
# Project-wide, remove temp files.
# Optionally, resort parsed output by query name.
# v0.60 Aug23'04 SC: chromviewer.pl pulls background image from loaded file.
# All programs draw params from config files.
# Take colors in from config file.
# v0.61 Sept1-9'04 EC: Major restructuring: move config stuff into config.pm.
# Add draw_regions. SC: minor tweaks. Handle missing labels.
# v0.62 Sept12'04 SC: Add params draw_labels, x_label_offset, start_dots_x.
# Add sub draw_dot_and_label. Strip out some debug code.
# Test colors with testcolors.pl. Remove max_color_num.
# v0.62b Sept12'04 SC: working copy. Tweak $window_width
# v0.62c Sept19'04 SC: add check for null IN line
# v0.63 Sept23'04 SC,SW: added chrom_x_start to this file and config.pm
# v0.64 Oct 15'04 EC: fixed(?) image size calculation.
# Added 3 output options (including straight image data)
# Added commandline overriding of config values
# Changed debug output to log file rather than standard out
# Added -s command line option to change scaling on the fly.
# v0.65 Jan 27'04 SC: tweak at parse_bac_info to skip comment lines in data file
# change background drawing colors, and title positioning
# v0.66 Jan 29'04 SC: add new output_type and sub print_html_with_hit_list
# to print html without hit list (so we don't print hit list multiple
# times if we generate html for multiple images)
# v0.70 Apr11'05 EC: re-constructed as CVIT. Added config and errorlog libraries;
# streamlined drawing functions.
# v0.71 May12'05 EC: can create up to 3 images of different sizes.
# v0.72 June15'05 EC: use config file to show/hide labels
# v0.73 June22'05 EC: separated blast-hit drawing into its own function so that
# range values can be preserved (dot is drawn at midpoint in range).
# v0.74 Aug3'05 EC: BACborder lines show contigs a little more clearly.
# v0.76 Aug22'05 EC: Added a "measure" GFF record type which is displayed as a "heat"
# graph.
# v0.77 Aug23'05 EC: Added the option of displaying "measure" records as either
# a "heat" graph or a histogram.
# v0.78 Sept27'05 EC: minor bug fix: blast hits were off by half an "ah"
# v0.79 Oct12'05 EC: set a minimum maximum historgram size.
# v0.80 Oct18'05 EC: added cone (closed) gap types based on color. Experimented with gap
# glyphs, but code is commented out for now.
# v0.81 Nov17'05 EC: added ability to generate SVG output
# v0.82 NovXX'05 EC: ??
# v0.83 NovXX'05 SC: Add some documentation.
# v0.84 Dec10'05 EC: Can turn off scale via config;modified appearance of markers;
# v0.85 Dec12'05 EC: Improved error handling for gff files
# v0.86 Jan11'06 EC: Range colors can be passed in via command line
# v1.00 Octxx'10 EC: Cleaned up, branches merged for new round of development