Skip to content

Commit

Permalink
Karyogram geena (#537)
Browse files Browse the repository at this point in the history
* Implemented Idiogram with data sources
  • Loading branch information
akmorrow13 authored Nov 26, 2019
1 parent 9f78f7f commit 006635e
Show file tree
Hide file tree
Showing 19 changed files with 6,023 additions and 6 deletions.
5 changes: 5 additions & 0 deletions examples/data.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ var sources = [
}),
name: 'Reference'
},
{
viz: pileup.viz.idiogram(),
data: pileup.formats.cytoBand('/test-data/cytoBand.txt.gz'),
name: 'Idiogram'
},
{
viz: pileup.viz.scale(),
name: 'Scale'
Expand Down
1 change: 1 addition & 0 deletions lib/underscore.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ declare module "underscore" {
declare function groupBy<K, T>(a: Array<T>, iteratee: (val: T, index: number)=>K): {[key:K]: T[]};

declare function min<T>(a: Array<T>|{[key:any]: T}, iteratee: (val: T)=>any): T;
declare function max<T>(a: Array<T>|{[key:any]: T}, iteratee: (val: T)=>any): T;
declare function max<T>(a: Array<T>|{[key:any]: T}): T;

declare function values<T>(o: {[key: any]: T}): T[];
Expand Down
57 changes: 57 additions & 0 deletions src/main/data/chromosome.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* Class for parsing chromosome bands for idiograms.
* Format taken from https://github.com/hammerlab/idiogrammatik.
* @flow
*/
'use strict';

import ContigInterval from '../ContigInterval';
import type {CoverageCount} from '../viz/pileuputils';
import _ from 'underscore';

// chromosomal band (see https://github.com/hammerlab/idiogrammatik/blob/master/data/basic-chromosomes.json)
// for an example
export type Band = {
start: number;
end: number;
name: string;
value: string;
}

class Chromosome {
name: string;
bands: Band[];
position: ContigInterval<string>;

constructor(chromosome: Object) {
this.name = chromosome.name;
this.bands = chromosome.bands;
// create region for chromosome
var start = _.min(this.bands, band => band.start).start;


var stop = _.max(this.bands, band => band.end).end;
this.position = new ContigInterval(this.name, start, stop);
}

getKey(): string {
return this.name;
}

getInterval(): ContigInterval<string> {
return this.position;
}

getCoverage(referenceSource: Object): CoverageCount {
return {
range: this.getInterval(),
opInfo: null
};
}

intersects(range: ContigInterval<string>): boolean {
return range.intersects(this.position);
}
}

module.exports = Chromosome;
82 changes: 82 additions & 0 deletions src/main/data/cytoBand.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/**
* Fetcher/parser for gzipped Cytoband files. Cytoband files can be downloaded
* or accessed from http://hgdownload.cse.ucsc.edu/goldenpath for a genome build.
*
* extracts CONTIG, START, END, NAME and VALUE
*
* @flow
*/
'use strict';

import type AbstractFile from '../AbstractFile';
import type Q from 'q';
import _ from 'underscore';
import ContigInterval from '../ContigInterval';
import Chromosome from './chromosome';
import pako from 'pako/lib/inflate'; // for gzip inflation

function extractLine(cytoBandLine: string): Object {
var split = cytoBandLine.split('\t');
return {
contig: split[0],
band: {
start: Number(split[1]),
end: Number(split[2]),
name: split[3],
value: split[4]
}
};
}

function groupedBandsToChromosome(grouped: Object[]): Chromosome {
var bands = _.map(grouped, g => g.band);
return new Chromosome(
{name: grouped[0].contig,
bands: bands,
position: new ContigInterval(grouped[0].contig, bands[0].start, bands.slice(-1)[0].end)}
);
}

class ImmediateCytoBandFile {
chrs: {[key:string]:Chromosome};

constructor(chrs: {[key:string]:Chromosome}) {
this.chrs = chrs;
}

getFeaturesInRange(range: ContigInterval<string>): Chromosome {
return this.chrs[range.contig];
}
}

class CytoBandFile {
remoteFile: AbstractFile;
immediate: Q.Promise<ImmediateCytoBandFile>;

constructor(remoteFile: AbstractFile) {
this.remoteFile = remoteFile;

this.immediate = this.remoteFile.getAll().then(bytes => {
var txt = pako.inflate(bytes, {to: 'string'});
var txtLines = _.filter(txt.split('\n'), i => i); // gets rid of empty lines
var lines = txtLines.map(extractLine);
return lines;
}).then(lines => {
// group bands by contig
var grouped = _.groupBy(lines, l => l.contig);
var chrs = _.mapObject(grouped, g => groupedBandsToChromosome(g));
return new ImmediateCytoBandFile(chrs);
});
this.immediate.done();
}

getFeaturesInRange(range: ContigInterval<string>): Q.Promise<Chromosome[]> {
return this.immediate.then(immediate => {
return immediate.getFeaturesInRange(range);
});
}
}

module.exports = {
CytoBandFile
};
4 changes: 2 additions & 2 deletions src/main/data/genericFeature.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ class GenericFeature {
gFeature: Object;

constructor(id: string, position: ContigInterval<string>, genericFeature: Object) {
this.id = genericFeature.id;
this.position = genericFeature.position;
this.id = id;
this.position = position;
this.gFeature = genericFeature;
}
}
Expand Down
55 changes: 55 additions & 0 deletions src/main/json/IdiogramJson.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* A data source which implements generic JSON protocol.
* Currently only used to load alignments.
* @flow
*/
'use strict';

import type {DataSource} from '../sources/DataSource';
import Chromosome from '../data/chromosome';

import _ from 'underscore';
import {Events} from 'backbone';

import ContigInterval from '../ContigInterval';
import type {GenomeRange} from '../types';

function create(json: string): DataSource<Chromosome> {

// parse json
var parsedJson = JSON.parse(json);
var chromosomes: Chromosome[] = [];

// fill chromosomes with json
if (!_.isEmpty(parsedJson)) {
chromosomes = _.values(parsedJson).map(chr => new Chromosome(chr));
}

function rangeChanged(newRange: GenomeRange) {
// Data is already parsed, so immediately return
var range = new ContigInterval(newRange.contig, newRange.start, newRange.stop);
o.trigger('newdata', range);
o.trigger('networkdone');
return;
}

function getFeaturesInRange(range: ContigInterval<string>): Chromosome[] {
return _.filter(chromosomes, chr => range.chrOnContig(chr.name));
}

var o = {
rangeChanged,
getFeaturesInRange,

on: () => {},
once: () => {},
off: () => {},
trigger: (string, any) => {}
};
_.extend(o, Events);
return o;
}

module.exports = {
create
};
7 changes: 7 additions & 0 deletions src/main/pileup.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import saveAs from 'file-saver';

// Data sources
import TwoBitDataSource from './sources/TwoBitDataSource';
import CytoBandDataSource from './sources/CytoBandDataSource';

import BigBedDataSource from './sources/BigBedDataSource';
import VcfDataSource from './sources/VcfDataSource';
import BamDataSource from './sources/BamDataSource';
Expand All @@ -29,6 +31,7 @@ import Interval from './Interval';
import GA4GHAlignmentJson from './json/GA4GHAlignmentJson';
import GA4GHVariantJson from './json/GA4GHVariantJson';
import GA4GHFeatureJson from './json/GA4GHFeatureJson';
import IdiogramJson from './json/IdiogramJson';

// GA4GH sources
import GA4GHAlignmentSource from './sources/GA4GHAlignmentSource';
Expand All @@ -41,6 +44,7 @@ import CoverageTrack from './viz/CoverageTrack';
import GenomeTrack from './viz/GenomeTrack';
import GeneTrack from './viz/GeneTrack';
import FeatureTrack from './viz/FeatureTrack';
import IdiogramTrack from './viz/IdiogramTrack';
import LocationTrack from './viz/LocationTrack';
import PileupTrack from './viz/PileupTrack';
import ScaleTrack from './viz/ScaleTrack';
Expand Down Expand Up @@ -236,6 +240,8 @@ var pileup = {
alignmentJson: GA4GHAlignmentJson.create,
variantJson: GA4GHVariantJson.create,
featureJson: GA4GHFeatureJson.create,
idiogramJson: IdiogramJson.create,
cytoBand: CytoBandDataSource.create,
vcf: VcfDataSource.create,
twoBit: TwoBitDataSource.create,
bigBed: BigBedDataSource.create,
Expand All @@ -250,6 +256,7 @@ var pileup = {
genome: makeVizObject(GenomeTrack),
genes: makeVizObject(GeneTrack),
features: makeVizObject(FeatureTrack),
idiogram: makeVizObject(IdiogramTrack),
location: makeVizObject(LocationTrack),
scale: makeVizObject(ScaleTrack),
variants: makeVizObject(VariantTrack),
Expand Down
83 changes: 83 additions & 0 deletions src/main/sources/CytoBandDataSource.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**
* The "glue" between cytoBand.js and IdiogramTrack.js.
*
* Allows loading remote gzipped cytoband files into an Idiogram visualization.
*
* @flow
*/
'use strict';

import _ from 'underscore';
import {Events} from 'backbone';

import ContigInterval from '../ContigInterval';
import Chromosome from '../data/chromosome';
import type {GenomeRange} from '../types';
import {CytoBandFile} from '../data/cytoBand';
import type {DataSource} from '../sources/DataSource';

import RemoteFile from '../RemoteFile';
import utils from '../utils';

var createFromCytoBandFile = function(remoteSource: CytoBandFile): DataSource<Chromosome> {
// Local cache of genomic data.
var contigMap: {[key:string]: Chromosome} = {};

// This either adds or removes a 'chr' as needed.
function normalizeRange(range: ContigInterval<string>): ContigInterval<string> {
if (contigMap[range.contig] !== undefined) {
return range;
}
var altContig = utils.altContigName(range.contig);
if (contigMap[altContig] !== undefined) {
return new ContigInterval(altContig, range.start(), range.stop());
}
return range;
}

function fetch(range: ContigInterval<string>) {
remoteSource.getFeaturesInRange(range)
.then(chr => {
contigMap[chr.name] = chr;
}).then(() => {
o.trigger('newdata', range);
}).done();
}

function getFeaturesInRange(range: ContigInterval<string>): Chromosome[] {
return [contigMap[normalizeRange(range).contig]];
}

var o = {
// The range here is 0-based, inclusive
rangeChanged: function(newRange: GenomeRange) {
// Check if this interval is already in the cache.
if ( contigMap[newRange.contig] !== undefined ) {
return;
}
fetch(new ContigInterval(newRange.contig, newRange.start, newRange.stop));
},
getFeaturesInRange,
// These are here to make Flow happy.
on: () => {},
once: () => {},
off: () => {},
trigger: (status: string, param: any) => {}
};
_.extend(o, Events); // Make this an event emitter

return o;
};

function create(url:string): DataSource<Chromosome> {
if (!url) {
throw new Error(`Missing URL from track: ${url}`);
}
return createFromCytoBandFile(new CytoBandFile(new RemoteFile(url)));
}


module.exports = {
create,
createFromCytoBandFile,
};
3 changes: 2 additions & 1 deletion src/main/sources/DataSource.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ import type {GenomeRange} from '../types';
import type ContigInterval from '../ContigInterval';

import type {Alignment} from '../Alignment';
import Chromosome from '../data/chromosome';
import Feature from '../data/feature';
import Gene from '../data/gene';

// Flow type for export.
export type DataSource<T: ( Gene | Feature | Alignment )> = {
export type DataSource<T: ( Gene | Feature | Alignment | Chromosome)> = {
rangeChanged: (newRange: GenomeRange) => void;
getFeaturesInRange: (range: ContigInterval<string>, resolution: ?number) => T[];
on: (event: string, handler: Function) => void;
Expand Down
15 changes: 15 additions & 0 deletions src/main/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,21 @@ module.exports = {
READ_SPACING: 2, // vertical spacing between reads
READ_HEIGHT: 13, // Height of read

// Idiogram track
IDIOGRAM_LINEWIDTH: 1,
IDIOGRAM_COLORS: {
"gpos100": "#000000",
"gpos": "#000000",
"gpos75": "#828282",
"gpos66": "#a0a0a0",
"gpos50": "#c8c8c8",
"gpos33": "#d2d2d2",
"gpos25": "#c8c8c8",
"gvar": "#dcdcdc",
"gneg": "#ffffff",
"acen": "#d92f27",
"stalk": "#647fa4",
},

// Coverage track
COVERAGE_FONT_STYLE: `bold 9px 'Helvetica Neue', Helvetica, Arial, sans-serif`,
Expand Down
2 changes: 1 addition & 1 deletion src/main/viz/GeneTrack.js
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ class GeneTrack extends React.Component<VizProps<DataSource<Gene>>, State> {

ctx.popObject();
});
} // end typecheck for canvasß
} // end typecheck for canvas
}
}

Expand Down
Loading

0 comments on commit 006635e

Please sign in to comment.