mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Partial implementation of Circos visualization using D3.
This commit is contained in:
@@ -826,6 +826,29 @@ class TracksController( BaseUIController, UsesVisualization, UsesHistoryDatasetA
|
||||
|
||||
return self.add_track_async( trans, output_dataset.id )
|
||||
|
||||
@web.expose
|
||||
@web.require_login( "use Galaxy visualizations", use_panels=True )
|
||||
def circos( self, trans, hda_ldda, dataset_id ):
|
||||
# Get dataset.
|
||||
dataset = self._get_dataset( trans, hda_ldda, dataset_id )
|
||||
|
||||
# Get genome info.
|
||||
dbkey = dataset.dbkey
|
||||
chroms_info = self.genomes.chroms( trans, dbkey=dbkey )
|
||||
genome = { 'dbkey': dbkey, 'chroms_info': chroms_info }
|
||||
|
||||
# Get summary tree data for dataset.
|
||||
data_sources = self._get_datasources( trans, dataset )
|
||||
tracks_dataset_type = data_sources['index']['name']
|
||||
converted_dataset = dataset.get_converted_dataset( trans, tracks_dataset_type )
|
||||
indexer = get_data_provider( tracks_dataset_type )( converted_dataset, dataset )
|
||||
dataset_summary = []
|
||||
for chrom_info in chroms_info[ 'chrom_info' ]:
|
||||
summary = indexer.get_summary( chrom_info[ 'chrom' ], 0, chrom_info[ 'len' ], level=4 )
|
||||
dataset_summary.append( summary )
|
||||
|
||||
return trans.fill_template_mako( "visualization/circos.mako", dataset=dataset, dataset_summary=dataset_summary, genome=genome )
|
||||
|
||||
# -----------------
|
||||
# Helper methods.
|
||||
# -----------------
|
||||
|
||||
Vendored
+9384
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,9 @@
|
||||
/**
|
||||
* Model classes for Galaxy tools and tool panel. Models have no references to views,
|
||||
* instead using events to indicate state changes; this is advantageous because
|
||||
* multiple views can use the same object and models can be used without views.
|
||||
* Model, view, and controller objects for Galaxy tools and tool panel.
|
||||
*
|
||||
* Models have no references to views, instead using events to indicate state
|
||||
* changes; this is advantageous because multiple views can use the same object
|
||||
* and models can be used without views.
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -481,7 +483,6 @@ var ToolFormView = Backbone.View.extend({
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Integrated tool menu + tool execution.
|
||||
*/
|
||||
|
||||
@@ -1,3 +1,187 @@
|
||||
/**
|
||||
* Model, view, and controller objects for Galaxy tools and tool panel.
|
||||
*
|
||||
* Models have no references to views, instead using events to indicate state
|
||||
* changes; this is advantageous because multiple views can use the same object
|
||||
* and models can be used without views.
|
||||
*/
|
||||
|
||||
/**
|
||||
* -- Models --
|
||||
*/
|
||||
|
||||
/**
|
||||
* A genome build.
|
||||
*/
|
||||
var Genome = Backbone.Model.extend({
|
||||
defaults: {
|
||||
name: null,
|
||||
key: null,
|
||||
chroms_info: null
|
||||
},
|
||||
|
||||
get_chroms_info: function() {
|
||||
return this.attributes.chroms_info.chrom_info;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* A histogram dataset.
|
||||
*/
|
||||
var HistogramDataset = Backbone.Model.extend({
|
||||
|
||||
initialize: function(data) {
|
||||
// Set max across dataset.
|
||||
this.attributes.data = data;
|
||||
this.attributes.max = _.max(data, function(d) {
|
||||
if (!d || typeof d === "string") { return 0; }
|
||||
return d[1];
|
||||
})[1];
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
* Layout for a histogram dataset in a circos visualization.
|
||||
*/
|
||||
var CircosHistogramDatasetLayout = Backbone.Model.extend({
|
||||
// TODO: should accept genome and dataset and use these to generate layout data.
|
||||
|
||||
/**
|
||||
* Returns arc layouts for genome's chromosomes/contigs. Arcs are arranged in a circle
|
||||
* separated by gaps.
|
||||
*/
|
||||
chroms_layout: function() {
|
||||
// Setup chroms layout using pie.
|
||||
var chroms_info = this.attributes.genome.get_chroms_info(),
|
||||
pie_layout = d3.layout.pie().value(function(d) { return d.len; }).sort(null),
|
||||
init_arcs = pie_layout(chroms_info),
|
||||
gap_per_chrom = this.attributes.total_gap / chroms_info.length,
|
||||
chrom_arcs = _.map(init_arcs, function(arc, index) {
|
||||
// For short chroms, endAngle === startAngle.
|
||||
var new_endAngle = arc.endAngle - gap_per_chrom;
|
||||
arc.endAngle = (new_endAngle > arc.startAngle ? new_endAngle : arc.startAngle);
|
||||
return arc;
|
||||
});
|
||||
|
||||
// TODO: remove arcs for chroms that are too small and recompute?
|
||||
|
||||
return chrom_arcs;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns layouts for drawing a chromosome's data. For now, only works with summary tree data.
|
||||
*/
|
||||
chrom_data_layout: function(chrom_arc, chrom_data, inner_radius, outer_radius, max) {
|
||||
// If no chrom data, return null.
|
||||
if (!chrom_data || typeof chrom_data === "string") {
|
||||
return null;
|
||||
}
|
||||
|
||||
var data = chrom_data[0],
|
||||
delta = chrom_data[3],
|
||||
scale = d3.scale.linear()
|
||||
.domain( [0, max] )
|
||||
.range( [inner_radius, outer_radius] ),
|
||||
arc_layout = d3.layout.pie().value(function(d) {
|
||||
return delta;
|
||||
})
|
||||
.startAngle(chrom_arc.startAngle)
|
||||
.endAngle(chrom_arc.endAngle),
|
||||
arcs = arc_layout(data);
|
||||
|
||||
// Use scale to assign outer radius.
|
||||
_.each(data, function(datum, index) {
|
||||
arcs[index].outerRadius = scale(datum[1]);
|
||||
});
|
||||
|
||||
return arcs;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
* -- Views --
|
||||
*/
|
||||
|
||||
var CircosView = Backbone.View.extend({
|
||||
className: 'circos',
|
||||
|
||||
initialize: function(options) {
|
||||
this.width = options.width;
|
||||
this.height = options.height;
|
||||
this.total_gap = options.total_gap;
|
||||
this.genome = options.genome;
|
||||
this.dataset = options.dataset;
|
||||
this.radius_start = options.radius_start;
|
||||
this.dataset_arc_height = options.dataset_arc_height;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
// -- Layout viz. --
|
||||
|
||||
var radius_start = this.radius_start,
|
||||
dataset_arc_height = this.dataset_arc_height,
|
||||
|
||||
// Layout chromosome arcs.
|
||||
arcs_layout = new CircosHistogramDatasetLayout({
|
||||
genome: this.genome,
|
||||
total_gap: this.total_gap
|
||||
}),
|
||||
chrom_arcs = arcs_layout.chroms_layout(),
|
||||
|
||||
// Merge chroms layout with data.
|
||||
layout_and_data = _.zip(chrom_arcs, this.dataset.attributes.data),
|
||||
dataset_max = this.dataset.attributes.max,
|
||||
|
||||
// Do dataset layout for each chromosome's data using pie layout.
|
||||
chroms_data_layout = _.map(layout_and_data, function(chrom_info) {
|
||||
var chrom_arc = chrom_info[0],
|
||||
chrom_data = chrom_info[1];
|
||||
return arcs_layout.chrom_data_layout(chrom_arc, chrom_data, radius_start, radius_start + dataset_arc_height, dataset_max);
|
||||
});
|
||||
|
||||
// -- Render viz. --
|
||||
|
||||
var svg = d3.select(this.$el[0])
|
||||
.append("svg")
|
||||
.attr("width", this.width)
|
||||
.attr("height", this.height)
|
||||
.append("g")
|
||||
.attr("transform", "translate(" + this.width / 2 + "," + this.height / 2 + ")");
|
||||
|
||||
// Draw background arcs for each chromosome.
|
||||
var base_arc = svg.append("g").attr("id", "inner-arc"),
|
||||
arc_gen = d3.svg.arc()
|
||||
.innerRadius(radius_start)
|
||||
.outerRadius(radius_start + dataset_arc_height),
|
||||
// Draw arcs.
|
||||
chroms_elts = base_arc.selectAll("#inner-arc>path")
|
||||
.data(chrom_arcs).enter().append("path")
|
||||
.attr("d", arc_gen)
|
||||
.style("stroke", "#ccc")
|
||||
.style("fill", "#ccc")
|
||||
.append("title").text(function(d) { return d.data.chrom; });
|
||||
|
||||
// For each chromosome, draw dataset.
|
||||
_.each(chroms_data_layout, function(chrom_layout) {
|
||||
if (!chrom_layout) { return; }
|
||||
|
||||
var group = svg.append("g"),
|
||||
arc_gen = d3.svg.arc().innerRadius(radius_start),
|
||||
dataset_elts = group.selectAll("path")
|
||||
.data(chrom_layout).enter().append("path")
|
||||
.attr("d", arc_gen)
|
||||
.style("stroke", "red")
|
||||
.style("fill", "red");
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* -- Routers --
|
||||
*/
|
||||
|
||||
/**
|
||||
* Router for track browser.
|
||||
*/
|
||||
|
||||
Vendored
+1
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
var TrackBrowserRouter=Backbone.Router.extend({initialize:function(b){this.view=b.view;this.route(/([\w]+)$/,"change_location");this.route(/([\w]+\:[\d,]+-[\d,]+)$/,"change_location");var a=this;a.view.on("navigate",function(c){a.navigate(c)})},change_location:function(a){this.view.go_to(a)}});
|
||||
var Genome=Backbone.Model.extend({defaults:{name:null,key:null,chroms_info:null},get_chroms_info:function(){return this.attributes.chroms_info.chrom_info}});var HistogramDataset=Backbone.Model.extend({initialize:function(a){this.attributes.data=a;this.attributes.max=_.max(a,function(b){if(!b||typeof b==="string"){return 0}return b[1]})[1]}});var CircosHistogramDatasetLayout=Backbone.Model.extend({chroms_layout:function(){var b=this.attributes.genome.get_chroms_info(),d=d3.layout.pie().value(function(f){return f.len}).sort(null),e=d(b),a=this.attributes.total_gap/b.length,c=_.map(e,function(h,g){var f=h.endAngle-a;h.endAngle=(f>h.startAngle?f:h.startAngle);return h});return c},chrom_data_layout:function(j,b,g,f,h){if(!b||typeof b==="string"){return null}var d=b[0],i=b[3],c=d3.scale.linear().domain([0,h]).range([g,f]),e=d3.layout.pie().value(function(k){return i}).startAngle(j.startAngle).endAngle(j.endAngle),a=e(d);_.each(d,function(k,l){a[l].outerRadius=c(k[1])});return a}});var CircosView=Backbone.View.extend({className:"circos",initialize:function(a){this.width=a.width;this.height=a.height;this.total_gap=a.total_gap;this.genome=a.genome;this.dataset=a.dataset;this.radius_start=a.radius_start;this.dataset_arc_height=a.dataset_arc_height},render:function(){var d=this.radius_start,e=this.dataset_arc_height,j=new CircosHistogramDatasetLayout({genome:this.genome,total_gap:this.total_gap}),i=j.chroms_layout(),g=_.zip(i,this.dataset.attributes.data),h=this.dataset.attributes.max,b=_.map(g,function(m){var n=m[0],l=m[1];return j.chrom_data_layout(n,l,d,d+e,h)});var c=d3.select(this.$el[0]).append("svg").attr("width",this.width).attr("height",this.height).append("g").attr("transform","translate("+this.width/2+","+this.height/2+")");var k=c.append("g").attr("id","inner-arc"),f=d3.svg.arc().innerRadius(d).outerRadius(d+e),a=k.selectAll("#inner-arc>path").data(i).enter().append("path").attr("d",f).style("stroke","#ccc").style("fill","#ccc").append("title").text(function(l){return l.data.chrom});_.each(b,function(l){if(!l){return}var o=c.append("g"),n=d3.svg.arc().innerRadius(d),m=o.selectAll("path").data(l).enter().append("path").attr("d",n).style("stroke","red").style("fill","red")})}});var TrackBrowserRouter=Backbone.Router.extend({initialize:function(b){this.view=b.view;this.route(/([\w]+)$/,"change_location");this.route(/([\w]+\:[\d,]+-[\d,]+)$/,"change_location");var a=this;a.view.on("navigate",function(c){a.navigate(c)})},change_location:function(a){this.view.go_to(a)}});
|
||||
@@ -0,0 +1,39 @@
|
||||
<%inherit file="/base.mako"/>
|
||||
|
||||
<%def name="stylesheets()">
|
||||
${parent.stylesheets()}
|
||||
</%def>
|
||||
|
||||
<%def name="javascripts()">
|
||||
${parent.javascripts()}
|
||||
|
||||
${h.js( "libs/d3", "mvc/visualization" )}
|
||||
|
||||
<script type="text/javascript">
|
||||
$(function() {
|
||||
// -- Viz set up. --
|
||||
|
||||
var genome = new Genome(JSON.parse('${ h.to_json_string( genome ) }')),
|
||||
dataset = new HistogramDataset(JSON.parse('${ h.to_json_string( dataset_summary ) }')),
|
||||
circos = new CircosView({
|
||||
width: 600,
|
||||
height: 600,
|
||||
// Gap is difficult to set because it very dependent on chromosome size and organization.
|
||||
total_gap: 2 * Math.PI * 0.5,
|
||||
genome: genome,
|
||||
dataset: dataset,
|
||||
radius_start: 100,
|
||||
dataset_arc_height: 50
|
||||
});
|
||||
|
||||
// -- Render viz. --
|
||||
|
||||
circos.render();
|
||||
$('body').append(circos.$el);
|
||||
});
|
||||
</script>
|
||||
</%def>
|
||||
|
||||
<%def name="body()">
|
||||
<h1>Circos plot for '${dataset.name}'</h1>
|
||||
</%def>
|
||||
Reference in New Issue
Block a user