Skip to content

Commit

Permalink
feat: support an inline legend and 'unknown' type assembly (#252)
Browse files Browse the repository at this point in the history
  • Loading branch information
sehilyi authored Feb 23, 2021
1 parent 386092d commit 2ed5ea7
Show file tree
Hide file tree
Showing 16 changed files with 373 additions and 95 deletions.
39 changes: 30 additions & 9 deletions schema/gosling.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
"hg17",
"hg16",
"mm10",
"mm9"
"mm9",
"unknown"
],
"type": "string"
},
Expand Down Expand Up @@ -425,7 +426,7 @@
}
]
},
"xLinkID": {
"xLinkingId": {
"type": "string"
}
},
Expand Down Expand Up @@ -1237,7 +1238,7 @@
}
]
},
"xLinkID": {
"xLinkingId": {
"type": "string"
}
},
Expand Down Expand Up @@ -1348,6 +1349,9 @@
"endAngle": {
"type": "number"
},
"flipY": {
"type": "boolean"
},
"height": {
"type": "number"
},
Expand Down Expand Up @@ -1392,6 +1396,9 @@
"endAngle": {
"type": "number"
},
"flipY": {
"type": "boolean"
},
"innerRadius": {
"type": "number"
},
Expand Down Expand Up @@ -1496,7 +1503,7 @@
}
]
},
"xLinkID": {
"xLinkingId": {
"type": "string"
},
"xe": {
Expand Down Expand Up @@ -1620,7 +1627,7 @@
}
]
},
"xLinkID": {
"xLinkingId": {
"type": "string"
},
"xe": {
Expand Down Expand Up @@ -1749,7 +1756,7 @@
}
]
},
"xLinkID": {
"xLinkingId": {
"type": "string"
}
},
Expand Down Expand Up @@ -1824,7 +1831,7 @@
}
]
},
"xLinkID": {
"xLinkingId": {
"type": "string"
}
},
Expand Down Expand Up @@ -1858,6 +1865,9 @@
"endAngle": {
"type": "number"
},
"flipY": {
"type": "boolean"
},
"height": {
"type": "number"
},
Expand Down Expand Up @@ -1977,7 +1987,7 @@
}
]
},
"xLinkID": {
"xLinkingId": {
"type": "string"
},
"xe": {
Expand Down Expand Up @@ -2045,7 +2055,7 @@
}
]
},
"xLinkID": {
"xLinkingId": {
"type": "string"
}
},
Expand Down Expand Up @@ -2157,6 +2167,9 @@
"dy": {
"type": "number"
},
"inlineLegend": {
"type": "boolean"
},
"linePattern": {
"additionalProperties": false,
"properties": {
Expand Down Expand Up @@ -2189,6 +2202,14 @@
"strokeWidth": {
"type": "number"
},
"textAnchor": {
"enum": [
"start",
"middle",
"end"
],
"type": "string"
},
"textFontSize": {
"type": "number"
},
Expand Down
4 changes: 2 additions & 2 deletions src/core/gosling-to-higlass.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { goslingToHiGlass } from './gosling-to-higlass';
import { SingleTrack } from './gosling.schema';
import { HiGlassModel } from './higlass-model';
import { EXAMPLE_TRACK_SEMANTIC_ZOOM } from '../editor/example/semantic-zoom';
import { EX_TRACK_SEMANTIC_ZOOM } from '../editor/example/semantic-zoom';

describe('Should convert gosling spec to higlass view config.', () => {
it('Should return a generated higlass view config correctly', () => {
const model = new HiGlassModel();
const higlass = goslingToHiGlass(
model,
EXAMPLE_TRACK_SEMANTIC_ZOOM.cytoband,
EX_TRACK_SEMANTIC_ZOOM.cytoband,
{
width: 1000,
height: 100,
Expand Down
19 changes: 11 additions & 8 deletions src/core/gosling.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export interface MultipleViews extends CommonViewDef {
}

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

export interface CommonViewDef {
layout?: Layout;
Expand All @@ -42,7 +42,7 @@ export interface CommonViewDef {
assembly?: Assembly;

xDomain?: DomainInterval | DomainChrInterval | DomainChr; // We can support `DomainGene` as well later.
xLinkID?: string;
xLinkingId?: string;
xAxis?: AxisPosition; // not supported currently

/**
Expand Down Expand Up @@ -138,6 +138,7 @@ export interface SingleTrack extends CommonTrackDef {

// Experimental
stackY?: boolean; // Eventually, will be added to y's `Channel` w/ gap
flipY?: boolean;

// Stretch the size to the given range? (e.g., [x, xe])
stretch?: boolean;
Expand Down Expand Up @@ -173,11 +174,13 @@ export interface TrackStyle {
outline?: string;
outlineWidth?: number;
circularLink?: boolean; // draw arc instead of bazier curve?
inlineLegend?: boolean; // show legend in a single horizontal line?
// below options could instead be used with channel options (e.g., size, stroke, strokeWidth)
textFontSize?: number;
textStroke?: string;
textStrokeWidth?: number;
textFontWeight?: 'bold' | 'normal';
textAnchor?: 'start' | 'middle' | 'end';
//
stroke?: string; // deprecated
strokeWidth?: number; // deprecated
Expand Down Expand Up @@ -295,6 +298,10 @@ export type Aggregate = 'max' | 'min' | 'mean' | 'bin' | 'count';
/* ----------------------------- DATA ----------------------------- */
export type DataDeep = JSONData | CSVData | BIGWIGData | MultivecData | BEDDBData | VectorData;

export interface Datum {
[k: string]: number | string;
}

export interface JSONData {
type: 'json';
values: Datum[];
Expand All @@ -310,10 +317,6 @@ export interface JSONData {
}[];
}

export interface Datum {
[k: string]: number | string;
}

export interface CSVData {
type: 'csv';
url: string;
Expand All @@ -322,11 +325,11 @@ export interface CSVData {
chromosomeField?: string;
genomicFields?: string[];
sampleLength?: number; // This limit the total number of rows fetched (default: 1000)
// experimental

// !!! below is experimental
headerNames?: string[];
chromosomePrefix?: string;
longToWideId?: string;
// !!! experimental
genomicFieldsToConvert?: {
chromosomeField: string;
genomicFields: string[];
Expand Down
102 changes: 72 additions & 30 deletions src/core/mark/legend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export const LEGEND_LABEL_STYLE = {
fill: 'black',
background: 'white',
lineJoin: 'round'
// Other possible options:
// stroke: '#ffffff',
// strokeThickness: 2
};
Expand Down Expand Up @@ -39,37 +40,78 @@ export function drawColorLegend(HGC: any, trackInfo: any, tile: any, tm: Gosling

const recipe: { x: number; y: number; color: string }[] = [];

colorCategories.forEach(category => {
if (cumY > trackInfo.dimensions[1]) {
// We do not draw labels overflow
return;
}

const color = tm.encodedValue('color', category);

const textGraphic = new HGC.libraries.PIXI.Text(category, { ...LEGEND_LABEL_STYLE });
textGraphic.anchor.x = 1;
textGraphic.anchor.y = 0;
textGraphic.position.x = trackInfo.position[0] + trackInfo.dimensions[0] - paddingX;
textGraphic.position.y = trackInfo.position[1] + cumY;

graphics.addChild(textGraphic);

const textStyleObj = new HGC.libraries.PIXI.TextStyle(LEGEND_LABEL_STYLE);
const textMetrics = HGC.libraries.PIXI.TextMetrics.measureText(category, textStyleObj);

if (maxWidth < textMetrics.width + paddingX * 3) {
maxWidth = textMetrics.width + paddingX * 3;
}

recipe.push({
x: trackInfo.position[0] + trackInfo.dimensions[0] - textMetrics.width - paddingX * 2,
y: trackInfo.position[1] + cumY + textMetrics.height / 2.0,
color
if (spec.style?.inlineLegend) {
// Show legend in a single horizontal line

// !! reversed to add the last category first from the right side
colorCategories
.map(d => d)
.reverse()
.forEach(category => {
if (maxWidth > trackInfo.dimensions[0]) {
// We do not draw labels overflow
return;
}

const color = tm.encodedValue('color', category);
const textGraphic = new HGC.libraries.PIXI.Text(category, { ...LEGEND_LABEL_STYLE });
textGraphic.anchor.x = 1;
textGraphic.anchor.y = 0;
textGraphic.position.x = trackInfo.position[0] + trackInfo.dimensions[0] - maxWidth - paddingX;
textGraphic.position.y = trackInfo.position[1] + paddingY;

graphics.addChild(textGraphic);

const textStyleObj = new HGC.libraries.PIXI.TextStyle(LEGEND_LABEL_STYLE);
const textMetrics = HGC.libraries.PIXI.TextMetrics.measureText(category, textStyleObj);

if (cumY < textMetrics.height + paddingY * 3) {
cumY = textMetrics.height + paddingY * 3;
}

recipe.push({
x: trackInfo.position[0] + trackInfo.dimensions[0] - textMetrics.width - maxWidth - paddingX * 2,
y: trackInfo.position[1] + paddingY + textMetrics.height / 2.0,
color
});

maxWidth += textMetrics.width + paddingX * 3;
});
} else {
// Show legend vertically

colorCategories.forEach(category => {
if (cumY > trackInfo.dimensions[1]) {
// We do not draw labels overflow
return;
}

const color = tm.encodedValue('color', category);

const textGraphic = new HGC.libraries.PIXI.Text(category, { ...LEGEND_LABEL_STYLE });
textGraphic.anchor.x = 1;
textGraphic.anchor.y = 0;
textGraphic.position.x = trackInfo.position[0] + trackInfo.dimensions[0] - paddingX;
textGraphic.position.y = trackInfo.position[1] + cumY;

graphics.addChild(textGraphic);

const textStyleObj = new HGC.libraries.PIXI.TextStyle(LEGEND_LABEL_STYLE);
const textMetrics = HGC.libraries.PIXI.TextMetrics.measureText(category, textStyleObj);

if (maxWidth < textMetrics.width + paddingX * 3) {
maxWidth = textMetrics.width + paddingX * 3;
}

recipe.push({
x: trackInfo.position[0] + trackInfo.dimensions[0] - textMetrics.width - paddingX * 2,
y: trackInfo.position[1] + cumY + textMetrics.height / 2.0,
color
});

cumY += textMetrics.height + paddingY * 2;
});

cumY += textMetrics.height + paddingY * 2;
});
}

graphics.beginFill(colorToHex('white'), 0.7);
graphics.lineStyle(
Expand Down
2 changes: 1 addition & 1 deletion src/core/mark/link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export function drawLink(g: PIXI.Graphics, model: GoslingTrackModel) {
0.5 // alignment of the line to draw, (0 = inner, 0.5 = middle, 1 = outter)
);

const flipY = IsChannelDeep(spec.y) && spec.y.flip;
const flipY = (IsChannelDeep(spec.y) && spec.y.flip) || spec.flipY;
const baseY = rowPosition + (flipY ? 0 : rowHeight);

if (isBand) {
Expand Down
7 changes: 6 additions & 1 deletion src/core/mark/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,12 @@ export function drawText(HGC: any, trackInfo: any, tile: any, tm: GoslingTrackMo
}

textGraphic.alpha = actualOpacity;
textGraphic.anchor.x = 0.5;
textGraphic.anchor.x =
!spec.style?.textAnchor || spec.style?.textAnchor === 'middle'
? 0.5
: spec.style.textAnchor === 'start'
? 0
: 1;
textGraphic.anchor.y = 0.5;

if (circular) {
Expand Down
4 changes: 2 additions & 2 deletions src/core/utils/json-crush.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// @ts-ignore
import { JSONCrush, JSONUncrush } from './json-crush';
import stringify from 'json-stringify-pretty-compact';
import { EXAMPLE_TRACK_SEMANTIC_ZOOM } from '../../editor/example/semantic-zoom';
import { EX_TRACK_SEMANTIC_ZOOM } from '../../editor/example/semantic-zoom';

describe('JSONCrush', () => {
it('Should not loss information', () => {
const specStr = stringify({ tracks: [{ ...EXAMPLE_TRACK_SEMANTIC_ZOOM.cytoband }] });
const specStr = stringify({ tracks: [{ ...EX_TRACK_SEMANTIC_ZOOM.cytoband }] });
expect(JSONUncrush(decodeURIComponent(JSONCrush(specStr)))).toEqual(specStr);
});
});
Loading

0 comments on commit 2ed5ea7

Please sign in to comment.