Skip to content

Commit

Permalink
feat: enable using custom assembly by specifying chrom sizes (#776)
Browse files Browse the repository at this point in the history
* feat: support custom chrom sizes

* chore: add a util function for creating chromsize URL
  • Loading branch information
sehilyi committed Jul 24, 2022
1 parent 4fa101d commit b08334a
Show file tree
Hide file tree
Showing 14 changed files with 259 additions and 68 deletions.
5 changes: 4 additions & 1 deletion editor/example/json-spec/sars-cov-2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const EX_TRACK_SARS_COV_2_GENES: OverlaidTracks = {
data: {
type: 'csv',
url: 'https://s3.amazonaws.com/gosling-lang.org/data/COVID/NC_045512.2-Genes.csv',
chromosomeField: 'Accession',
genomicFields: ['Start', 'Stop']
},
tracks: [
Expand Down Expand Up @@ -49,7 +50,7 @@ export const EX_TRACK_SARS_COV_2_GENES: OverlaidTracks = {
export const EX_SPEC_SARS_COV_2: GoslingSpec = {
title: 'SARS-CoV-2',
subtitle: 'Data Source: WashU Virus Genome Browser, NCBI, GISAID',
assembly: 'unknown',
assembly: [['NC_045512.2', 29903]],
layout: 'linear',
spacing: 50,
views: [
Expand Down Expand Up @@ -112,6 +113,7 @@ export const EX_SPEC_SARS_COV_2: GoslingSpec = {
data: {
type: 'csv',
url: 'https://s3.amazonaws.com/gosling-lang.org/data/COVID/sars-cov-2_Sprot_annot_sorted.bed',
chromosomeField: 'Accession',
genomicFields: ['Start', 'Stop']
},
tracks: [
Expand Down Expand Up @@ -171,6 +173,7 @@ export const EX_SPEC_SARS_COV_2: GoslingSpec = {
data: {
type: 'csv',
url: 'https://s3.amazonaws.com/gosling-lang.org/data/COVID/TRS-L-dependent_recombinationEvents_sorted.bed',
chromosomeField: 'Accession',
genomicFields: ['Start1', 'Stop1', 'Start2', 'Stop2'],
sampleLength: 100
},
Expand Down
65 changes: 54 additions & 11 deletions schema/gosling.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,43 @@
"type": "string"
},
"Assembly": {
"enum": [
"hg38",
"hg19",
"hg18",
"hg17",
"hg16",
"mm10",
"mm9",
"unknown"
],
"type": "string"
"anyOf": [
{
"const": "hg38",
"type": "string"
},
{
"const": "hg19",
"type": "string"
},
{
"const": "hg18",
"type": "string"
},
{
"const": "hg17",
"type": "string"
},
{
"const": "hg16",
"type": "string"
},
{
"const": "mm10",
"type": "string"
},
{
"const": "mm9",
"type": "string"
},
{
"const": "unknown",
"type": "string"
},
{
"$ref": "#/definitions/ChromSizes"
}
]
},
"AxisPosition": {
"enum": [
Expand Down Expand Up @@ -289,6 +315,23 @@
],
"type": "object"
},
"ChromSizes": {
"description": "Custom chromosome sizes, e.g., [[\"foo\", 1000], [\"bar\", 300], [\"baz\", 240]]",
"items": {
"items": [
{
"type": "string"
},
{
"type": "number"
}
],
"maxItems": 2,
"minItems": 2,
"type": "array"
},
"type": "array"
},
"Chromosome": {
"enum": [
"1",
Expand Down
65 changes: 54 additions & 11 deletions schema/higlass.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,60 @@
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"Assembly": {
"enum": [
"hg38",
"hg19",
"hg18",
"hg17",
"hg16",
"mm10",
"mm9",
"unknown"
],
"type": "string"
"anyOf": [
{
"const": "hg38",
"type": "string"
},
{
"const": "hg19",
"type": "string"
},
{
"const": "hg18",
"type": "string"
},
{
"const": "hg17",
"type": "string"
},
{
"const": "hg16",
"type": "string"
},
{
"const": "mm10",
"type": "string"
},
{
"const": "mm9",
"type": "string"
},
{
"const": "unknown",
"type": "string"
},
{
"$ref": "#/definitions/ChromSizes"
}
]
},
"ChromSizes": {
"description": "Custom chromosome sizes, e.g., [[\"foo\", 1000], [\"bar\", 300], [\"baz\", 240]]",
"items": {
"items": [
{
"type": "string"
},
{
"type": "number"
}
],
"maxItems": 2,
"minItems": 2,
"type": "array"
},
"type": "array"
},
"CombinedTrack": {
"additionalProperties": false,
Expand Down
65 changes: 54 additions & 11 deletions schema/template.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,43 @@
"type": "string"
},
"Assembly": {
"enum": [
"hg38",
"hg19",
"hg18",
"hg17",
"hg16",
"mm10",
"mm9",
"unknown"
],
"type": "string"
"anyOf": [
{
"const": "hg38",
"type": "string"
},
{
"const": "hg19",
"type": "string"
},
{
"const": "hg18",
"type": "string"
},
{
"const": "hg17",
"type": "string"
},
{
"const": "hg16",
"type": "string"
},
{
"const": "mm10",
"type": "string"
},
{
"const": "mm9",
"type": "string"
},
{
"const": "unknown",
"type": "string"
},
{
"$ref": "#/definitions/ChromSizes"
}
]
},
"AxisPosition": {
"enum": [
Expand Down Expand Up @@ -443,6 +469,23 @@
}
]
},
"ChromSizes": {
"description": "Custom chromosome sizes, e.g., [[\"foo\", 1000], [\"bar\", 300], [\"baz\", 240]]",
"items": {
"items": [
{
"type": "string"
},
{
"type": "number"
}
],
"maxItems": 2,
"minItems": 2,
"type": "array"
},
"type": "array"
},
"Chromosome": {
"enum": [
"1",
Expand Down
4 changes: 3 additions & 1 deletion scripts/setup-vitest.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,15 @@ apis.forEach(api => {
global.window[api] = canvasWindow[api];
});

// jsdom doesn't come with a WebCrypto implementation (required for uuid)
beforeAll(() => {
// jsdom doesn't come with a WebCrypto implementation (required for uuid)
global.crypto = {
getRandomValues: function (buffer) {
return randomFillSync(buffer);
}
};
// jsdom doesn't come with a `URL.createObjectURL` implementation
global.URL.createObjectURL = () => { return ''; };
});

afterAll(() => {
Expand Down
5 changes: 4 additions & 1 deletion src/core/gosling.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,10 @@ export type ResponsiveSpecOfMultipleViews = {

export type Layout = 'linear' | 'circular';
export type Orientation = 'horizontal' | 'vertical';
export type Assembly = 'hg38' | 'hg19' | 'hg18' | 'hg17' | 'hg16' | 'mm10' | 'mm9' | 'unknown';

/** Custom chromosome sizes, e.g., [["foo", 1000], ["bar", 300], ["baz", 240]] */
export type ChromSizes = [string, number][];
export type Assembly = 'hg38' | 'hg19' | 'hg18' | 'hg17' | 'hg16' | 'mm10' | 'mm9' | 'unknown' | ChromSizes;
export type ZoomLimits = [number | null, number | null];

export interface CommonViewDef {
Expand Down
4 changes: 2 additions & 2 deletions src/core/higlass-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { insertItemToArray } from './utils/array';

export const HIGLASS_AXIS_SIZE = 30;

const getViewTemplate = (assembly?: string) => {
const getViewTemplate = (assembly?: Assembly) => {
return {
genomePositionSearchBoxVisible: false,
genomePositionSearchBox: {
Expand Down Expand Up @@ -74,7 +74,7 @@ export class HiGlassModel {
return this;
}

public addDefaultView(uid: string, assembly?: string) {
public addDefaultView(uid: string, assembly?: Assembly) {
this.hg.views.push(JSON.parse(JSON.stringify({ ...getViewTemplate(assembly), uid })));
return this;
}
Expand Down
14 changes: 11 additions & 3 deletions src/core/utils/assembly.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,22 @@ import { getChromInterval, getChromTotalSize, getRelativeGenomicPosition, GET_CH
import { CHROM_SIZE_HG38 } from './chrom-size';

describe('Assembly', () => {
it('Random chromosome name', () => {
it('Missing chromosome name', () => {
expect(GET_CHROM_SIZES().total).toBeDefined();
expect(GET_CHROM_SIZES('chrmosome1').total).toBeDefined();
expect(GET_CHROM_SIZES('random').total).toBeDefined();
});
it('hg38 ChromSizes', () => {
expect(GET_CHROM_SIZES('hg38').size).toEqual(CHROM_SIZE_HG38);
});
it('Custom ChromSizes', () => {
expect(
GET_CHROM_SIZES([
['foo', 100],
['bar', 1000]
]).total
).toEqual(1100);
// use default assembly instead when the size is zero
expect(GET_CHROM_SIZES([]).total).toEqual(GET_CHROM_SIZES().total);
});
it('Chromosome total size calculation', () => {
expect(getChromTotalSize({ 1: 1, 2: 999 })).toEqual(1000);
});
Expand Down
27 changes: 23 additions & 4 deletions src/core/utils/assembly.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { GenomicPosition } from '@gosling.schema';
import type { Assembly, ChromSizes, GenomicPosition } from '@gosling.schema';
import {
CHROM_SIZE_HG16,
CHROM_SIZE_HG17,
Expand All @@ -19,7 +19,7 @@ export interface ChromSize {
/**
* Get relative chromosome position (e.g., `100` => `{ chromosome: 'chr1', position: 100 }`)
*/
export function getRelativeGenomicPosition(absPos: number, assembly?: string): GenomicPosition {
export function getRelativeGenomicPosition(absPos: number, assembly?: Assembly): GenomicPosition {
const [chromosome, absInterval] = Object.entries(GET_CHROM_SIZES(assembly).interval).find(d => {
const [start, end] = d[1];
return start <= absPos && absPos < end;
Expand All @@ -33,13 +33,32 @@ export function getRelativeGenomicPosition(absPos: number, assembly?: string): G
return { chromosome, position: absPos - absInterval[0] };
}

/**
* Generate a URL for custom chrom sizes
* @param chromSizes A custom assembly that specifies chromosomes and their sizes
*/
function createChromSizesUrl(chromSizes: ChromSizes): string {
const text = chromSizes.map(d => d.join('\t')).join('\n');
const tsv = new Blob([text], { type: 'text/tsv' });
return URL.createObjectURL(tsv);
}

/**
* Get chromosome sizes.
* @param assembly (default: 'hg38')
*/
export function GET_CHROM_SIZES(assembly?: string): ChromSize {
if (assembly && assembly in CRHOM_SIZES) {
export function GET_CHROM_SIZES(assembly?: Assembly): ChromSize {
if (assembly && typeof assembly === 'string' && assembly in CRHOM_SIZES) {
return CRHOM_SIZES[assembly];
} else if (Array.isArray(assembly) && assembly.length !== 0) {
// custom assembly
const size = Object.fromEntries(assembly);
return {
size,
interval: getChromInterval(size),
total: getChromTotalSize(size),
path: createChromSizesUrl(assembly)
};
} else {
// We do not have that assembly prepared, so return a default one.
return CRHOM_SIZES.hg38;
Expand Down
2 changes: 1 addition & 1 deletion src/core/utils/scales.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export function getNumericDomain(domain: Domain, assembly?: Assembly) {
if (domain.chromosome.includes('chr')) {
domain.chromosome = domain.chromosome.replace('chr', '') as Chromosome;
}
if (!Object.keys(GET_CHROM_SIZES().interval).find(chr => chr === `chr${domain.chromosome}`)) {
if (!Object.keys(GET_CHROM_SIZES(assembly).interval).find(chr => chr === `chr${domain.chromosome}`)) {
// we did not find any, so use '1' by default
domain.chromosome = '1';
}
Expand Down

0 comments on commit b08334a

Please sign in to comment.