Skip to content

Commit

Permalink
feat: support basic templates for given data type (#96)
Browse files Browse the repository at this point in the history
* feat: support basic templates for given data type

* feat: support higlass-multivec type data

* refactor: fixing tests with new track type, DataTract

* refactor: fix tests

* refactor: add tests
  • Loading branch information
sehilyi authored Jan 11, 2021
1 parent c130edd commit 81d59eb
Show file tree
Hide file tree
Showing 13 changed files with 389 additions and 1,344 deletions.
1,458 changes: 150 additions & 1,308 deletions schema/geminid.schema.json

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion src/core/compile.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { GeminidSpec } from './geminid.schema';
import { compileLayout } from './layout/layout';
import { HiGlassSpec } from './higlass.schema';
import { fixSpecDownstream } from './utils/spec-preprocess';
import { fixSpecDownstream, overrideTemplates } from './utils/spec-preprocess';
import { Size } from './utils/bounding-box';

export function compile(gm: GeminidSpec, setHg: (hg: HiGlassSpec, size: Size) => void) {
// Override default visual encoding (`DataTrack` => `BasicSingleTrack`)
overrideTemplates(gm);

// Fix track specs by looking into the root-level spec
fixSpecDownstream(gm);

Expand Down
11 changes: 10 additions & 1 deletion src/core/geminid.schema.guards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ import {
OneOfFilter,
RangeFilter,
IncludeFilter,
DataDeepTileset
DataDeepTileset,
DataTrack
} from './geminid.schema';
import { SUPPORTED_CHANNELS } from './mark';
import { isArray } from 'lodash';
Expand Down Expand Up @@ -54,6 +55,14 @@ export function IsDataTransform(_: DataTransform | ChannelDeep | ChannelValue):
}
//

export function IsDataTrack(_: Track): _ is DataTrack {
return 'data' in _ && 'metadata' in _ && !('mark' in _);
}

export function IsTemplate(_: Track): boolean {
return !!('data' in _ && 'metadata' in _ && (!('mark' in _) || _.overrideTemplate) && !IsSuperposedTrack(_));
}

export function IsDataDeep(
data:
| DataDeep
Expand Down
23 changes: 20 additions & 3 deletions src/core/geminid.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,9 @@ export interface OneOfFilter {
not: boolean;
}

export type Track = SingleTrack | SuperposedTrack | SuperposedTrackTwoLevels;
export type Track = SingleTrack | SuperposedTrack | DataTrack; // | SuperposedTrackTwoLevels; // we could support this in the future

export type SingleTrack = BasicSingleTrack | CustomChannel;
export type SingleTrack = BasicSingleTrack; // | CustomChannel; // we could support this in the future

// TODO: how to exclude keys defined in the `BasicSingleTrack`?
export type CustomChannel = {
Expand All @@ -138,7 +138,7 @@ export type CustomChannel = {
[k in CHANNEL_KEYS]?: never;
};

export interface BasicSingleTrack {
export interface CommonTrackDef {
title?: string;
subtitle?: string;
description?: string;
Expand All @@ -156,7 +156,21 @@ export interface BasicSingleTrack {
innerRadius?: number;
startAngle?: number; // [0, 360]
endAngle?: number; // [0, 360]
}

/**
* Partial specification of `BasicSingleTrack` to use default visual encoding predefined by data type.
*/
export interface DataTrack extends CommonTrackDef {
// !!! The below properties should be required ones since metadata determines the visualization type.
data: DataDeep;
metadata: DataMetadata;
}

/**
* The baic form of a track definition
*/
export interface BasicSingleTrack extends CommonTrackDef {
// Data
data: DataDeep;
metadata?: DataMetadata; // we could remove this and get this information from the server
Expand Down Expand Up @@ -202,6 +216,9 @@ export interface BasicSingleTrack {

// Styling
style?: TrackStyle;

// Override a spec template that is defined for a given data type
overrideTemplate?: boolean;
}

/**
Expand Down
91 changes: 90 additions & 1 deletion src/core/utils/spec-preprocess.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { GeminidSpec } from '../geminid.schema';
import { BasicSingleTrack, GeminidSpec } from '../geminid.schema';
import { IsDataMetadata, IsTemplate } from '../geminid.schema.guards';
import assign from 'lodash/assign';

/**
* Update track-level specs considering the root-level specs (e.g., arrangements).
Expand Down Expand Up @@ -35,3 +37,90 @@ export function fixSpecDownstream(spec: GeminidSpec) {
});
}
}

/**
* Get an encoding template for the `higlass-vector` data type.
* @param column
* @param value
*/
export function getVectorTemplate(column: string, value: string): BasicSingleTrack {
return {
data: { type: 'tileset', url: 'https://localhost:8080/api/v1/tileset_info/?d=VLFaiSVjTjW6mkbjRjWREA' },
metadata: {
type: 'higlass-vector',
column,
value
},
mark: 'bar',
x: { field: column, type: 'genomic', axis: 'bottom' },
y: { field: value, type: 'quantitative' }
};
}

export function getMultivecTemplate(
row: string,
column: string,
value: string,
categories: string[] | undefined
): BasicSingleTrack {
return categories && categories.length < 10
? {
data: { type: 'tileset', url: 'https://localhost:8080/api/v1/tileset_info/?d=VLFaiSVjTjW6mkbjRjWREA' },
metadata: {
type: 'higlass-multivec',
row,
column,
value,
categories
},
mark: 'bar',
x: { field: column, type: 'genomic', axis: 'bottom' },
y: { field: value, type: 'quantitative' },
row: { field: row, type: 'nominal', legend: true },
color: { field: row, type: 'nominal' }
}
: {
data: { type: 'tileset', url: 'https://localhost:8080/api/v1/tileset_info/?d=VLFaiSVjTjW6mkbjRjWREA' },
metadata: {
type: 'higlass-multivec',
row,
column,
value,
categories
},
mark: 'rect',
x: { field: column, type: 'genomic', axis: 'bottom' },
row: { field: row, type: 'nominal', legend: true },
color: { field: value, type: 'quantitative' }
};
}

/**
* Override default visual encoding in each track for given data type.
* @param spec
*/
export function overrideTemplates(spec: GeminidSpec) {
spec.tracks.forEach((t, i) => {
if (!t.metadata || !IsDataMetadata(t.metadata)) {
// if `metadata` is not specified, we can not provide a correct template since we do not know the exact data type.
return;
}

if (!IsTemplate(t)) {
// This is not partial specification that we need to use templates
return;
}

switch (t.metadata.type) {
case 'higlass-vector':
spec.tracks[i] = assign(getVectorTemplate(t.metadata.column, t.metadata.value), t);
break;
case 'higlass-multivec':
spec.tracks[i] = assign(
getMultivecTemplate(t.metadata.row, t.metadata.column, t.metadata.value, t.metadata.categories),
t
);
break;
}
});
}
17 changes: 11 additions & 6 deletions src/core/utils/superpose.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
import { AxisPosition, BasicSingleTrack, SingleTrack, SuperposedTrack, Track } from '../geminid.schema';
import { AxisPosition, BasicSingleTrack, SuperposedTrack, Track } from '../geminid.schema';
import assign from 'lodash/assign';
import { IsChannelDeep, IsSuperposedTrack } from '../geminid.schema.guards';
import { IsChannelDeep, IsDataTrack, IsSuperposedTrack } from '../geminid.schema.guards';

/**
* Resolve superposed tracks into multiple track specifications.
* Some options are corrected to ensure the resolved tracks use consistent visual properties, such as the existence of the axis for genomic coordinates.
*/
export function resolveSuperposedTracks(track: Track): SingleTrack[] {
export function resolveSuperposedTracks(track: Track): BasicSingleTrack[] {
if (IsDataTrack(track)) {
// no BasicSingleTrack to return
return [];
}

if (!IsSuperposedTrack(track)) {
// no `superpose` to resolve
return [track];
}

const base: SingleTrack = JSON.parse(JSON.stringify(track));
const base: BasicSingleTrack = JSON.parse(JSON.stringify(track));
delete (base as Partial<SuperposedTrack>).superpose; // remove `superpose` from the base spec

const resolved: SingleTrack[] = [];
const resolved: BasicSingleTrack[] = [];
track.superpose.forEach((subSpec, i) => {
const spec = assign(JSON.parse(JSON.stringify(base)), subSpec) as BasicSingleTrack;
if (spec.title && i !== 0) {
Expand All @@ -37,7 +42,7 @@ export function resolveSuperposedTracks(track: Track): SingleTrack[] {
return {
...d,
x: { ...d.x, axis: xAxisPosition }
} as SingleTrack;
} as BasicSingleTrack;
});

// height
Expand Down
6 changes: 3 additions & 3 deletions src/core/utils/validate.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Ajv from 'ajv';
import { ChannelDeep, ChannelTypes, Track } from '../geminid.schema';
import { BasicSingleTrack, ChannelDeep, ChannelTypes, SuperposedTrack, Track } from '../geminid.schema';
import { IsChannelDeep } from '../geminid.schema.guards';
import { resolveSuperposedTracks } from './superpose';
import GeminidSchema from '../../../schema/geminid.schema.json';
Expand Down Expand Up @@ -81,7 +81,7 @@ export function validateTrack(track: Track) {
* Find an axis channel that is encoded with genomic coordinate.
* `undefined` if not found.
*/
export function getGenomicChannelFromTrack(track: Track): ChannelDeep | undefined {
export function getGenomicChannelFromTrack(track: BasicSingleTrack | SuperposedTrack): ChannelDeep | undefined {
// we do not support using two genomic coordinates yet
let genomicChannel: ChannelDeep | undefined = undefined;
['x', 'y', 'xe', 'ye', 'x1', 'y1', 'x1e', 'y1e'].reverse().forEach(channelType => {
Expand All @@ -98,7 +98,7 @@ export function getGenomicChannelFromTrack(track: Track): ChannelDeep | undefine
* `undefined` if not found.
*/
export function getGenomicChannelKeyFromTrack(
track: Track
track: BasicSingleTrack | SuperposedTrack
): 'x' | 'xe' | 'y' | 'ye' | 'x1' | 'y1' | 'x1e' | 'y1e' | undefined {
// we do not support using two genomic coordinates yet
let genomicChannelKey: string | undefined = undefined;
Expand Down
57 changes: 57 additions & 0 deletions src/editor/example/basic/simplest-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { GeminidSpec } from '../../../core/geminid.schema';

export const EXAMPLE_SIMPLEST: GeminidSpec = {
tracks: [
{
data: {
type: 'tileset',
url: 'https://resgen.io/api/v1/tileset_info/?d=VLFaiSVjTjW6mkbjRjWREA'
},
metadata: {
type: 'higlass-vector',
column: 'position',
value: 'peak',
bin: 16
},
mark: 'line',
overrideTemplate: true
},
{
data: {
url: 'https://resgen.io/api/v1/tileset_info/?d=UvVPeLHuRDiYA3qwFlm7xQ',
type: 'tileset'
},
metadata: {
type: 'higlass-multivec',
row: 'sample',
column: 'position',
value: 'peak',
categories: ['sample 1', 'sample 2', 'sample 3', 'sample 4']
}
},
{
data: {
url: 'https://resgen.io/api/v1/tileset_info/?d=UvVPeLHuRDiYA3qwFlm7xQ',
type: 'tileset'
},
metadata: {
type: 'higlass-multivec',
row: 'sample',
column: 'position',
value: 'peak',
categories: [
'sample 1',
'sample 2',
'sample 3',
'sample 4',
'sample 5',
'sample 6',
'sample 7',
'sample 8',
'sample 9',
'sample 10'
]
}
}
]
};
29 changes: 15 additions & 14 deletions src/editor/example/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { GENOCAT_SWAV } from './genocat-swav';
import { GENOCAT_GIVE } from './genocat-give';
import { GENOCAT_GREMLIN } from './genocat-gremlin';
import { GENOCAT_MIZBEE } from './genocat-mizbee';
import { EXAMPLE_SIMPLEST } from './basic/simplest-spec';

export const examples: ReadonlyArray<{
name: string;
Expand All @@ -53,14 +54,12 @@ export const examples: ReadonlyArray<{
{
name: '[GenoCAT] GIVE',
spec: GENOCAT_GIVE,
underDevelopment: true,
forceShow: false
underDevelopment: true
},
{
name: '[GenoCAT] Gremlin',
spec: GENOCAT_GREMLIN,
underDevelopment: true,
forceShow: true
underDevelopment: true
},
{
name: '[GenoCAT] MizBee',
Expand All @@ -70,8 +69,7 @@ export const examples: ReadonlyArray<{
{
name: '[GenoCAT] CNVkit',
spec: GENOCAT_CNVKIT,
underDevelopment: true,
forceShow: false
underDevelopment: true
},
{
name: '[GenoCAT] SWAV',
Expand All @@ -82,29 +80,32 @@ export const examples: ReadonlyArray<{
name: 'UCSC Cyto band (hg38)',
spec: EXAMPLE_CYTOAND_HG38,
underDevelopment: false,
hidden: false,
forceShow: false
hidden: false
},
{
name: 'Wenger et al. 2019 (SV)',
spec: EXAMPLE_2019_WENGER,
underDevelopment: false,
hidden: false,
forceShow: true
hidden: false
},
{
name: 'Corces et al. 2020 (Nature Genetics)',
spec: CORCES_2020_NATURE_GENETICS,
underDevelopment: false,
hidden: false,
forceShow: false
hidden: false
},
{
name: 'Geminid Logo',
spec: EXAMPLE_LOGO,
underDevelopment: false,
hidden: true,
forceShow: false
hidden: true
},
{
name: 'Simplest Spec',
spec: EXAMPLE_SIMPLEST,
underDevelopment: false,
hidden: false,
forceShow: true
},
{
name: 'Basic Marks',
Expand Down
Loading

0 comments on commit 81d59eb

Please sign in to comment.