| Bio-Graphics documentation | Contained in the Bio-Graphics distribution. |
Bio::Graphics::Glyph::heat_map - The "heat_map" glyph
See Bio::Graphics::Panel and Bio::Graphics::Glyph.
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.
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.
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
Please report them.
Bio::Graphics::Panel, Bio::Graphics::Glyph, Bio::Graphics::Glyph::graded_segments, Bio::Graphics::Glyph::heterogeneous_segments, Bio::Graphics::Feature, Bio::DB::GFF GD
Sheldon McKay <mckays@cshl.edu>
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.
| Bio-Graphics documentation | Contained in the Bio-Graphics distribution. |
package Bio::Graphics::Glyph::heat_map; #$Id: heat_map.pm,v 1.4 2009-05-15 13:42:37 pruzanov Exp $ use strict; 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 use vars '@ISA'; @ISA = qw/Bio::Graphics::Glyph::minmax/; sub my_description { return <<END; This glyph draws "scored" features using a continuous color gradient is the HSV color space. The color of each segment is proportional to the score. Either monochrome gradients (for example the default white->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;