package Bio::Graphics::Browser2::Render::TrackConfig;
use strict;
use warnings;
use CGI qw(:standard);
use Bio::Graphics::Browser2::Shellwords 'shellwords';
use constant MIN=>0;
use constant MAX=>999999999;
sub new {
my $class = shift;
my $render = shift;
return bless {render=>$render},ref $class || $class;
}
sub render { shift->{render} }
sub config_dialog {
my $self = shift;
my ($label,$revert_to_defaults) = @_;
my $render = $self->render;
my $state = $render->state();
my $data_source = $render->data_source();
my $length = $render->segment_length($label);
eval 'require Bio::Graphics::Browser2::OptionPick; 1'
unless Bio::Graphics::Browser2::OptionPick->can('new');
my $picker = Bio::Graphics::Browser2::OptionPick->new($render);
# summary options
my $can_summarize = $data_source->can_summarize($label);
my $summary_mode = $data_source->show_summary($label,$length,$state);
my $summary_length= $self->setting( $label => 'show summary' , $length, $summary_mode) || 0;
my @thresh = sort {$a<=>$b} ($data_source->semantic_thresholds($label),$summary_length);
my $semantic_min = (grep {$length >= $_} @thresh)[-1] || MIN;
my $semantic_max = (grep {$length < $_} @thresh)[0] || MAX;
my $slabel = $summary_mode ? $label : $data_source->semantic_label($label,$length);
if ($revert_to_defaults) {
$state->{features}{$label}{summary_override} = {} if $summary_mode;
$state->{features}{$label}{semantic_override} = {} unless $summary_mode;
delete $state->{features}{$label}{summary_mode_len};
}
my $semantic_override = $render->find_override_region($state->{features}{$label}{semantic_override},$length);
$semantic_override ||= 0;
# my ($semantic_level) = $slabel =~ /(\d+)$/;
# $semantic_level ||= 0;
# my @level = map {
# scalar $render->data_source->unit_label($_)
# } split ':',($semantic_override || $semantic_level);
# my $level = join '..',@level;
my ($low,$hi) = $render->find_override_bounds($state->{features}{$label}{semantic_override},$length);
$semantic_min = $low if defined $low;
$semantic_max = $hi if defined $hi;
my $level = join '..',map {scalar $render->data_source->unit_label($_)} $semantic_min,$semantic_max;
my $key = $render->label2key($label);
$key .= $summary_mode ? " (".$render->translate('FEATURE_SUMMARY').')'
:$level eq '0 bp' ? ''
:$level =~ /\.\./ ? " ($level)"
:$level ? " (>=$level)"
:'';
my $override = $summary_mode
? $state->{features}{$label}{summary_override} ||= {}
: $state->{features}{$label}{semantic_override}{$semantic_override} ||= {};
my $return_html = start_html();
my $showing = $render->data_source->unit_label($length);
my $title = div({-class=>'config-title'},$key,br(),div({-style=>'font-size:9pt'},$render->translate('Currently_Showing',$showing)));
my $dynamic = $render->translate('DYNAMIC_VALUE');
my $height = $self->setting( $label => 'height' , $length, $summary_mode) || 10;
my $width = $self->setting( $label => 'linewidth', $length, $summary_mode ) || 1;
my $glyph = $self->setting( $label => 'glyph', $length, $summary_mode ) || 'box';
my $stranded = $self->setting( $label => 'stranded', $length, $summary_mode);
$stranded++ if $glyph eq 'gene'; # workaround a inconsistency in gene glyph behavior
my $variance_band = $self->setting( $label => 'variance_band', $length, $summary_mode);
my $color_series = $self->setting( $label => 'color_series', $length, $summary_mode);
my $limit = $self->setting( $label => 'feature_limit' , $length, $summary_mode) || 0;
my $opacity = $override->{'opacity'} || $self->setting($label => 'opacity',$length,$summary_mode) || 0.3;
# options for wiggle & xy plots
my $min_score= $self->setting( $label => 'min_score' , $length, $summary_mode);
my $max_score= $self->setting( $label => 'max_score' , $length, $summary_mode);
$min_score = -1 unless defined $min_score;
$max_score = +1 unless defined $max_score;
my $autoscale = $self->setting( $label => 'autoscale' , $length, $summary_mode);
$autoscale ||= 'local';
my $sd_fold = $self->setting( $label => 'z_score_bound' , $length, $summary_mode);
$sd_fold ||= 8;
my $bicolor_pivot = $self->setting( $label => 'bicolor_pivot' , $length, $summary_mode);
my $graph_type = $self->setting( $label => 'graph_type' , $length, $summary_mode);
my $glyph_subtype = $self->setting( $label => 'glyph_subtype' , $length, $summary_mode);
# options for wiggle_whiskers
my $max_color = $self->setting( $label => 'max_color' , $length, $summary_mode);
my $mean_color = $self->setting( $label => 'mean_color' , $length, $summary_mode);
my $stdev_color = $self->setting( $label => 'stdev_color' , $length, $summary_mode);
# packing images
my $buttons = $data_source->globals->button_url;
my $red_peaks = "$buttons/red_peaks.png";
my $blue_peaks = "$buttons/blue_peaks.png";
my $opacity_thumb = "$buttons/opacity_thumb.png";
my @glyph_select = shellwords(
$self->setting( $label => 'glyph select', $length, $summary_mode )
);
unshift @glyph_select,$dynamic if ref $data_source->fallback_setting($label=>'glyph') eq 'CODE';
my $db = $data_source->open_database($label,$length);
my $quantitative = $glyph =~ /wiggle|vista|xy|density|hybrid/ || ref($db) =~ /bigwig/i;
my $can_whisker = $quantitative && ref($db) =~ /bigwig/i;
my $vista = $glyph =~ /vista/;
unless (@glyph_select) { # reasonable defaults
@glyph_select = $can_whisker ? qw(wiggle_xyplot wiggle_density wiggle_whiskers)
:$vista ? 'vista_plot'
:$quantitative ? qw(wiggle_xyplot wiggle_density)
: qw(arrow anchored_arrow box crossbox dashed_line diamond
dna dot dumbbell ellipse gene line primers saw_teeth segments
span site transcript triangle
two_bolts wave);
}
my $auto_packing_label = $quantitative ? $render->translate('Expand_Label') : $render->translate('Auto');
my %glyphs = map { $_ => 1 } ( $glyph, @glyph_select );
my @all_glyphs = sort keys %glyphs;
my $g = $override->{'glyph'} || $glyph;
my $url = url( -absolute => 1, -path => 1 );
my $mode = $summary_mode ? 'summary' : 'normal';
my $reset_js = <<END;
new Ajax.Request('$url',
{ method: 'get',
asynchronous: false,
parameters: 'action=configure_track&track=$label&track_defaults=1;mode=$mode',
onSuccess: function(t) { document.getElementById('contents').innerHTML=t.responseText;
t.responseText.evalScripts();
},
onFailure: function(t) { alert('AJAX Failure! '+t.statusText)}
}
);
END
my $self_url = url( -absolute => 1, -path => 1 );
my $form_name = 'track_config_form';
my $form = start_form(
-name => $form_name,
-id => $form_name,
);
# NOTE: the -class option determines which form elements are shown for
# which track types. See htdocs/js/track_config.js
my @rows;
push @rows, TR({-class=>'general'},
td( {-colspan => 2}, $title));
push @rows,TR( {-class => 'general',
-id => 'packing'},
th( { -align => 'right' }, $render->translate('Packing') ),
td( popup_menu(
-name => 'format_option',
-id => 'format_option',
-values => ($quantitative ? [0,4] : [ 0..4 ]),
-override => 1,
-default => $state->{features}{$label}{options},
-labels => {
0 => $auto_packing_label,
1 => $render->translate('Compact'),
2 => $render->translate('Expand'),
3 => $render->translate('Expand_Label'),
4 => $render->translate('Overlap'),
}
)
)
);
push @rows,TR( {-class => 'general',
-id => 'opacity'},
th( {-align => 'right' }, 'Opacity'),
td( input({-type => 'text',
-name => 'conf_opacity',
-id => 'opacity_value',
-style => "display:inline-block;position:relative;top:-4",
-value => '0.00',
-size => 2,
-maxlength => 4}),
div({-id=>'opacity_box',
-style => 'display:inline-block;position:relative;background:beige;width:100px;height:16px;border:inset 1px',
},
img({-id=>'opacity_thumb',
-style=>'position:absolute;left:0',
-src => $opacity_thumb})),
div({-style=>'display:inline-block;position:relative;width:20px;height:20px'},
img({-class=>'opacity',
-src => $red_peaks,
-style => 'position:absolute;left:2px;top:0px'}),
img({-class=>'opacity',
-src => $blue_peaks,
-style => 'position:absolute;left:0px;top:5px'}),
)));
push @rows,TR( {-class=>'general'},
th( { -align => 'right' }, $render->translate('GLYPH') ),
td($picker->popup_menu(
-name => 'conf_glyph',
-values => \@all_glyphs,
-default => ref $glyph eq 'CODE' ? $dynamic : $glyph,
-current => $override->{'glyph'},
-scripts => {-id=>'glyph_picker_id',-onChange => 'track_configure.glyph_select($(\'config_table\'),this)'}
)
)
);
for my $glyph (@all_glyphs) {
my $class = "Bio\:\:Graphics\:\:Glyph\:\:$glyph";
eval "require $class" unless $class->can('new');
if (my $subtypes = eval{$class->options->{glyph_subtype}}) {
my $options = $subtypes->[0];
next unless ref $options eq 'ARRAY';
push @rows,(TR {-class => $glyph,
-id => "conf_${glyph}_subtype"},
th({-align => 'right'}, $glyph,$render->tr('Subtype')),
td($picker->popup_menu(
-values => $options,
-name => "conf_${glyph}_subtype",
-override => 1,
-default => ref $glyph_subtype eq 'CODE' ? $dynamic : $glyph_subtype,
-scripts => { -id => "conf_${glyph}_subtype_id",
-onChange => 'track_configure.glyph_select($(\'config_table\'),$(\'glyph_picker_id\'))'},
-current => $override->{'glyph_subtype'})));
}
if (my $subgraphs = eval{$class->options->{graph_type}}) {
my $options = $subgraphs->[0];
$graph_type ||= $subgraphs->[1];
next unless ref $options eq 'ARRAY';
push @rows,(TR {-class => "$glyph graphtype",
-id => "conf_${glyph}_graphtype"},
th({-align => 'right'}, $render->tr('XYplot_type')),
td($picker->popup_menu(
-name => "conf_${glyph}_graphtype",
-values => $options,
-override => 1,
-default => ref $graph_type eq 'CODE' ? $dynamic : $graph_type,
-scripts => { -id => "conf_${glyph}_graphtype_id",
-onChange => 'track_configure.glyph_select($(\'config_table\'),$(\'glyph_picker_id\'))'},
-current => $override->{'graph_type'})));
}
}
#######################
# cycling colors
#######################
push @rows,TR({
-id => 'color_series',
-class=> 'general'
},
th ( { -align=>'right' }, $render->tr('AUTO_COLORS') ),
td(hidden(-name => 'conf_color_series',-value=>0),
checkbox(-name => 'conf_color_series',
-id => 'conf_color_series',
-override=> 1,
-value => 1,
-checked => defined $override->{'color_series'}
? $override->{'color_series'}
: $color_series,
-label => '')));
#######################
# bicolor pivot stuff
#######################
my $p = $override->{bicolor_pivot} || $bicolor_pivot || 'none';
my $has_pivot = $g =~ /wiggle_xyplot|wiggle_density|xyplot|hybrid/;
push @rows,TR( {-class=>'xyplot density color_picker',
-id =>'bicolor_pivot_id'},
th( { -align => 'right'}, $render->translate('BICOLOR_PIVOT')),
td( $picker->popup_menu(
-class => 'color_picker',
-name => 'conf_bicolor_pivot',
-values => [qw(none zero mean 1SD 2SD 3SD value)],
-labels => {value => 'value entered below',
'1SD' => 'mean + 1 standard deviation',
'2SD' => 'mean + 2 standard deviations',
'3SD' => 'mean + 3 standard deviations',
},
-default => $bicolor_pivot,
-current => $p =~ /^-?[\d.eE]+(?:SD)?$/i ? 'value' : $p,
-scripts => {-onChange => 'track_configure.pivot_select(this)',
-id => 'conf_bicolor_pivot'}
)
)
);
my $pv = $p =~ /^[\d.-eE]+$/ ? $p : 0.0;
push @rows,TR({-class =>'xyplot density color_picker',
-id=>'switch_point_other'},
th( {-align => 'right' },$render->translate('BICOLOR_PIVOT_VALUE')),
td( textfield(-name => 'bicolor_pivot_value',
-value => $pv)));
push @rows,TR({-class=>'switch_point_color xyplot density color_picker'},
th( { -align => 'right' }, $render->translate('BICOLOR_PIVOT_POS_COLOR')),
td( $picker->color_pick(
'conf_pos_color',
$self->setting( $label => 'pos_color', $length, $summary_mode ),
$override->{'pos_color'}
)
)
);
push @rows,TR( {-class=>'switch_point_color xyplot density color color_picker'},
th( { -align => 'right' }, $render->translate('BICOLOR_PIVOT_NEG_COLOR') ),
td( $picker->color_pick(
'conf_neg_color',
$self->setting( $label => 'neg_color', $length, $summary_mode ),
$override->{'neg_color'}
)
)
);
push @rows,TR( { -id => 'bgcolor_picker',
-class => 'xyplot density features peaks color_picker',
},
th( { -align => 'right' }, $render->translate('BACKGROUND_COLOR') ),
td( $picker->color_pick(
'conf_bgcolor',
$self->setting( $label => 'bgcolor', $length, $summary_mode ),
$override->{'bgcolor'},
)
)
);
push @rows,TR( { -id => 'startcolor_picker',
-class => 'peaks color_picker',
},
th( { -align => 'right' }, 'Peak gradient start'),
td( $picker->color_pick(
'conf_start_color',
$self->setting( $label => 'start_color', $length, $summary_mode ),
$override->{'start_color'}
)
)
);
push @rows,TR( { -id => 'endcolor_picker',
-class => 'peaks color_picker',
},
th( { -align => 'right' }, 'Peak gradient end'),
td( $picker->color_pick(
'conf_end_color',
$self->setting( $label => 'end_color', $length, $summary_mode ),
$override->{'end_color'}
)
)
);
push @rows,TR( {-class=>'xyplot features peaks color_picker'},
th( { -align => 'right' }, $render->translate('FG_COLOR') ),
td( $picker->color_pick(
'conf_fgcolor',
$self->setting( $label => 'fgcolor', $length, $summary_mode ),
$override->{'fgcolor'}
)
)
);
#######################
# wiggle colors
#######################
push @rows,TR( {-class=>'whiskers color_picker'},
th( { -align => 'right' }, $render->translate('WHISKER_MEAN_COLOR')),
td( $picker->color_pick(
'conf_mean_color',
$mean_color || 'black',
$override->{'mean_color'}
)
)
);
push @rows,TR( {-class=>'whiskers color_picker'},
th( { -align => 'right' }, $render->translate('WHISKER_STDEV_COLOR') ),
td( $picker->color_pick(
'conf_stdev_color',
$stdev_color || 'grey',
$override->{'stdev_color'}
)
)
);
push @rows,TR( {-class=>'whiskers color_picker'},
th( { -align => 'right' }, $render->translate('WHISKER_MAX_COLOR') ),
td( $picker->color_pick(
'conf_max_color',
$max_color || 'lightgrey',
$override->{'max_color'}
)
)
);
push @rows,TR({-class=>'xyplot autoscale',
-id => "xyplot_autoscale"
},
th( { -align => 'right' },$render->tr('AUTOSCALING')),
td( $picker->popup_menu(
-name => "conf_xyplot_autoscale",
-values => [qw(none local)],
-labels => {none=>'fixed',local=>'scale to view'},
-default => $autoscale,
-current => $override->{autoscale},
-scripts => {-onChange => 'track_configure.autoscale_select(this,$(\'glyph_picker_id\'))',
-id => "conf_xyplot_autoscale"
}
)));
push @rows,TR({-class=>'wiggle vista_plot autoscale',
-id => 'wiggle_autoscale'},
th( { -align => 'right' },$render->translate('AUTOSCALING')),
td( $picker->popup_menu(
-name => "conf_wiggle_autoscale",
-values => $summary_mode ? [qw(none local)] : [qw(none z_score local chromosome global clipped_global)],
-labels => {none =>'fixed',
z_score =>'scale to SD multiples',
local =>'scale to local min/max',
chromosome=>'scale to chromosome min/max',
global =>'scale to genome min/max',
clipped_global =>'clip to +/- SDs shown below'
},
-default => $autoscale,
-current => $override->{autoscale},
-scripts => {-onChange => 'track_configure.autoscale_select(this,$(\'glyph_picker_id\'))',
-id => "conf_wiggle_autoscale"
}
)));
push @rows,TR({-class=>'wiggle vista_plot autoscale',
-id => 'wiggle_z_fold'},
th( { -align => 'right' },$render->translate('SD_MULTIPLES')),
td( $picker->popup_menu(
-name => "conf_z_score_bound",
-values => [qw(1 2 3 4 5 6 8 10 20)],
-labels => {1 =>'1 SD',
2 =>'2 SD',
3 =>'3 SD',
4 =>'4 SD',
5 =>'5 SD',
6 =>'6 SD',
8 =>'8 SD',
10 =>'10 SD',
20 =>'20 SD',
},
-default => $sd_fold,
-current => $override->{z_score_bound}
)));
push @rows,TR( {-class=> 'xyplot density whiskers vista_plot autoscale',
-id => 'fixed_minmax'
},
th( { -align => 'right' },$render->translate('SCALING')),
td( textfield(-name => 'conf_min_score',
-class => 'score_bounds',
-size => 5,
-value => defined $override->{min_score} ? $override->{min_score}
: $summary_mode ? 0 : $min_score),
'-',
textfield(-name => 'conf_max_score',
-class => 'score_bounds',
-size => 5,
-value => defined $override->{max_score} ? $override->{max_score}
: $summary_mode ? 10 : $max_score)))
if $quantitative;
push @rows,TR({-class=>'xyplot'},
th( { -align => 'right' }, $render->translate('SHOW_VARIANCE')),
td(
hidden(-name=>'conf_variance_band',-value=>0),
checkbox(
-name => 'conf_variance_band',
-override=> 1,
-value => 1,
-checked => defined $override->{'variance_band'}
? $override->{'variance_band'}
: $variance_band,
-label => '',
)
)
);
push @rows,TR( {-class=>'features'},
th( { -align => 'right' }, $render->translate('LINEWIDTH') ),
td( $picker->popup_menu(
-name => 'conf_linewidth',
-current => $override->{'linewidth'},
-default => $width || 1,
-values => [ sort { $a <=> $b } ( $width, 1 .. 5 ) ]
)
)
);
push @rows,TR( {-class=>'general'},
th(
{ -align => 'right' }, $render->translate('HEIGHT') ),
td( $picker->popup_menu(
-name => 'conf_height',
-id => 'conf_height',
-current => $override->{'height'},
-default => $height,
-values => [
sort { $a <=> $b }
( $height, map { $_ * 5 } ( 1 .. 20 ) )
],
)
)
);
push @rows,TR({-class=>'features'},
th( { -align => 'right' }, $render->translate('Limit') ),
td( $picker->popup_menu(
-name => 'conf_feature_limit',
-values => [ 0, 5, 10, 25, 50, 100, 200, 500, 1000 ],
-labels => { 0 => $render->translate('NO_LIMIT') },
-current => $override->{feature_limit},
-override => 1,
-default => $limit,
)
)
);
push @rows,TR({-class=>'features'},
th( { -align => 'right' }, $render->translate('STRANDED') ),
td( hidden(-name=>'conf_stranded',-value=>0),
checkbox(
-name => 'conf_stranded',
-override=> 1,
-value => 1,
-checked => defined $override->{'stranded'}
? $override->{'stranded'}
: $stranded,
-label => '',
)
)
);
push @rows,TR({-class=>'general'},
th( {-align => 'right' },
$render->translate('APPLY_CONFIG')
),
td(
$self->region_size_menu('apply_semantic_low',$length,[$semantic_min,MIN],$semantic_min),
'-',
$self->region_size_menu('apply_semantic_hi',$length,[$semantic_max,MAX],$semantic_max),
)
) unless $summary_mode;
my $summ = defined $state->{features}{$label}{summary_mode_len}
?$state->{features}{$label}{summary_mode_len}
:$summary_length;
push @rows,TR({-class=>'general'},
th( { -align => 'right' }, $render->translate('SHOW_SUMMARY')),
td(
$self->region_size_menu('summary_mode',$summ)
)
) if $can_summarize && $summary_length;
my $submit_script = <<END;
Element.extend(this);
var ancestors = this.ancestors();
var form_element = ancestors.find(function(el) {return el.nodeName=='FORM'; });
Controller.reconfigure_track('$label',form_element,'$mode')
END
push @rows,TR({-class=>'general'},
td({-colspan=>2},
button(
-style => 'background:pink',
-name => $render->translate('Revert'),
-onClick => $reset_js
), br,
button(
-name => $render->translate('Cancel'),
-onClick => 'Balloon.prototype.hideTooltip(1)'
),
button(
-name => $render->translate('Change'),
-onClick => $submit_script
),
hidden(-name=>'segment_length',-value=>$length),
)
);
$form .= table({-id=>'config_table',-border => 0 },@rows);
$form .= end_form();
$return_html
.= table( TR( td( { -valign => 'top' }, [ $form ] ) ) );
$return_html .= script({-type=>'text/javascript'},"track_configure.init($opacity)");
$return_html .= end_html();
return $return_html;
}
sub setting {
my $self = shift;
my ($label,$option,$length,$is_summary) = @_;
my $data_source = $self->render->data_source();
# bad hack
if ($is_summary) {
if ($data_source->Bio::Graphics::FeatureFile::setting("$label:summary")) {
return $data_source->semantic_fallback_setting("$label:summary",$option,$length);
} else {
return 'wiggle_density' if $option eq 'glyph';
return 15 if $option eq 'height';
return 0 if $option eq 'min_score';
return 'local' if $option eq 'autoscale';
}
}
return $data_source->semantic_fallback_setting($label,$option,$length);
}
sub region_size_menu {
my $self = shift;
my ($name,$length,$extra_vals,$default) = @_;
$extra_vals ||= [];
my $source = $self->render->data_source;
my %seen;
my @r = sort {$a<=>$b} ($source->get_ranges(),$length,@$extra_vals);
my @ranges = grep {!$seen{$source->unit_label($_)}++} @r;
my %labels = map {$_=> scalar $source->unit_label($_)} @ranges;
$labels{MIN()} = $self->render->translate('MIN');
$labels{MAX()} = $self->render->translate('MAX');
@ranges = sort {$b||0<=>$a||0} @ranges;
$default = $length unless defined $default;
return popup_menu(-name => $name,
-values => \@ranges,
-labels => \%labels,
-default => $default,
-force => 1,
);
}
1;