Skip to content

Commit 3e969f1

Browse files
author
martina
committed
# Conflicts: # package-lock.json # package.json # src/index.ts
2 parents d62a345 + 1227164 commit 3e969f1

1 file changed

Lines changed: 323 additions & 0 deletions

File tree

src/lib/sequence.viewer.ts

Lines changed: 323 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
1+
import { OptionsModel } from './options.model';
2+
import {RowsModel} from './rows.model';
3+
import {ColorsModel} from './colors.model';
4+
import {Log} from './log.model';
5+
import {SelectionModel} from './selection.model';
6+
import {IconsModel} from './icons.model';
7+
import {SequenceInfoModel} from './sequenceInfoModel';
8+
import {EventsModel} from './events.model';
9+
import {PatternsModel} from './patterns.model';
10+
import {InputModel} from './input.model';
11+
import {ConsensusModel} from './consensus.model';
12+
13+
export class SequenceViewer {
14+
static sqvList = [];
15+
divId: string;
16+
init: boolean;
17+
input: InputModel;
18+
params: OptionsModel;
19+
rows: RowsModel;
20+
consensus: ConsensusModel;
21+
regions: ColorsModel;
22+
patterns: PatternsModel;
23+
icons: IconsModel;
24+
labels: SequenceInfoModel;
25+
selection: SelectionModel;
26+
events: EventsModel;
27+
28+
constructor(divId?: string ) {
29+
this.divId = divId;
30+
this.init = false;
31+
this.input = new InputModel();
32+
this.params = new OptionsModel();
33+
this.rows = new RowsModel();
34+
this.consensus = new ConsensusModel();
35+
this.regions = new ColorsModel();
36+
this.patterns = new PatternsModel();
37+
this.icons = new IconsModel();
38+
this.labels = new SequenceInfoModel();
39+
this.selection = new SelectionModel();
40+
this.events = new EventsModel();
41+
42+
window.onresize = () => {
43+
for (const id of SequenceViewer.sqvList) {
44+
45+
const sqvBody = document.getElementById(id);
46+
if (!sqvBody) { Log.w(1, 'Cannot find sqv-body element.'); continue; }
47+
48+
const chunks = sqvBody.getElementsByClassName('cnk');
49+
if (!chunks) { Log.w(1, 'Cannot find chunk elements.'); continue; }
50+
51+
let oldTop = 0;
52+
let newTop;
53+
// tslint:disable-next-line:prefer-for-of
54+
for (let i = 0; i < chunks.length; i++) {
55+
newTop = chunks[i].getBoundingClientRect().top;
56+
if (chunks[i].getBoundingClientRect().top == 0) {
57+
newTop = chunks[i].getBoundingClientRect().height
58+
}
59+
60+
if (newTop > oldTop) {
61+
chunks[i].firstElementChild.className = 'idx';
62+
oldTop = newTop;
63+
} else {
64+
chunks[i].firstElementChild.className = 'idx hidden';
65+
}
66+
}
67+
}
68+
};
69+
}
70+
71+
public draw(input1?, input2?, input3?, input4?, input5?, input6?, input7?) {
72+
73+
SequenceViewer.sqvList.push(this.divId);
74+
75+
let inputs;
76+
let order;
77+
let labels;
78+
let startIndexes;
79+
let tooltips;
80+
let data;
81+
82+
/** check and process input */
83+
[inputs, order] = this.input.process(input1, input2, input3, input4, input5, input6, input7);
84+
85+
/** check and process parameters input */
86+
inputs.options = this.params.process(inputs.options);
87+
88+
/** check and consensus input and global colorScheme */
89+
[inputs.sequences, inputs.regions, order ] = this.consensus.process(inputs.sequences, inputs.regions, inputs.options, order);
90+
91+
92+
/** check and process patterns input */
93+
inputs.patterns = this.patterns.process(inputs.patterns, inputs.sequences);
94+
95+
/** check and process colors input */
96+
inputs.regions = this.regions.process(inputs, order);
97+
98+
/** check and process icons input */
99+
inputs.icons = this.icons.process(inputs.regions, inputs.sequences, inputs.iconsHtml, inputs.iconsPaths);
100+
101+
/** check and process sequences input */
102+
data = this.rows.process(inputs.sequences, inputs.icons, inputs.regions, inputs.options.colorScheme);
103+
104+
/** check and process labels input */
105+
[ labels, startIndexes, tooltips ] = this.labels.process(inputs.regions, inputs.sequences);
106+
107+
/** create/update sqv-body html */
108+
this.createGUI(data, labels, startIndexes, tooltips, inputs.options);
109+
110+
/** listen copy paste events */
111+
this.selection.process();
112+
113+
/** listen selection events */
114+
this.events.onRegionSelected();
115+
}
116+
117+
private generateLabels(idx, labels, startIndexes, topIndexes, chunkSize, fontSize, tooltips, data, rowMarginBottom) {
118+
let labelshtml = '';
119+
let labelsContainer = '';
120+
const noGapsLabels = [];
121+
122+
if (labels.length > 0) {
123+
if (topIndexes) {
124+
labelshtml += `<span class="lbl-hidden" style="margin-bottom:${rowMarginBottom};"></span>`;
125+
}
126+
let flag;
127+
let count = -1;
128+
let seqN = -1;
129+
for (const seqNum of data) {
130+
if (noGapsLabels.length < data.length) {
131+
noGapsLabels.push(0);
132+
}
133+
seqN += 1;
134+
for (const res in seqNum) {
135+
if (seqNum[res].char && seqNum[res].char.includes('svg')) {
136+
flag = true;
137+
break;
138+
}
139+
}
140+
141+
if (flag) {
142+
noGapsLabels[seqN] = '';
143+
if (idx) {
144+
// line with only icons, no need for index
145+
labelshtml += `<span class="lbl-hidden" style="margin-bottom:${rowMarginBottom}"><span class="lbl"> ${noGapsLabels[seqN]}</span></span>`;
146+
} else {
147+
labelshtml += `<span class="lbl-hidden" style="margin-bottom:${rowMarginBottom}"><span class="lbl"></span></span>`;
148+
}
149+
150+
} else {
151+
count += 1;
152+
if (idx) {
153+
if (!chunkSize) {
154+
// lateral index regular
155+
labelshtml += `<span class="lbl-hidden" style="width: ${fontSize};margin-bottom:${rowMarginBottom}">
156+
<span class="lbl" >${(startIndexes[count] - 1) + idx}</span></span>`;
157+
} else {
158+
let noGaps = 0;
159+
for (const res in seqNum) {
160+
if (+res <= (idx) && seqNum[res].char !== '-') {
161+
noGaps += 1;
162+
}
163+
}
164+
// lateral index gap
165+
noGapsLabels[seqN] = noGaps;
166+
labelshtml += `<span class="lbl-hidden" style="width: ${fontSize};margin-bottom:${rowMarginBottom}">
167+
<span class="lbl" >${(startIndexes[count] - 1) + noGapsLabels[seqN]}</span></span>`;
168+
}
169+
170+
} else {
171+
labelshtml += `<span class="lbl-hidden" style="margin-bottom:${rowMarginBottom}"><span class="lbl">${labels[count]}${tooltips[count]}</span></span>`;
172+
}
173+
}
174+
flag = false;
175+
}
176+
177+
labelsContainer = `<span class="lblContainer" style="display: inline-block">${labelshtml}</span>`;
178+
}
179+
return labelsContainer;
180+
}
181+
182+
private addTopIndexes(topIndexes, chunkSize, x, maxTop, rowMarginBottom) {
183+
184+
let cells = '';
185+
// adding top indexes
186+
if (topIndexes) {
187+
let chunkTopIndex;
188+
if (x % chunkSize === 0 && x <= maxTop) {
189+
chunkTopIndex = `<span class="cell" style="-webkit-user-select: none;direction: rtl;display:block;width:0.6em;margin-bottom:${rowMarginBottom}">${x}</span>`;
190+
191+
} else {
192+
chunkTopIndex = `<span class="cell" style="-webkit-user-select: none;display:block;visibility: hidden;margin-bottom:${rowMarginBottom}">0</span>`;
193+
}
194+
cells += chunkTopIndex;
195+
}
196+
return cells;
197+
}
198+
199+
private createGUI(data, labels, startIndexes, tooltips, options) {
200+
201+
const sqvBody = document.getElementById(this.divId);
202+
203+
if (!sqvBody) {
204+
Log.w(1, 'Cannot find sqv-body element.');
205+
return;
206+
}
207+
208+
const chunkSize = options.chunkSize;
209+
const fontSize = options.fontSize;
210+
const spaceSize = options.spaceSize;
211+
const topIndexes = options.topIndexes;
212+
const lateralIndexesGap = options.lateralIndexesGap;
213+
const oneLineSetting = options.oneLineSetting;
214+
const oneLineWidth = options.oneLineWidth;
215+
const rowMarginBottom = options.rowMarginBottom + ';';
216+
const fNum = +fontSize.substr(0, fontSize.length - 2);
217+
const fUnit = fontSize.substr(fontSize.length - 2, 2);
218+
219+
220+
221+
// maxIdx = length of the longest sequence
222+
let maxIdx = 0;
223+
let maxTop = 0;
224+
for (const row of data) {
225+
if (maxIdx < Object.keys(row).length) { maxIdx = Object.keys(row).length; }
226+
if (maxTop < Object.keys(row).length) { maxTop = Object.keys(row).length; }
227+
}
228+
229+
const lenghtIndex = maxIdx.toString().length;
230+
const indexWidth = (fNum * lenghtIndex).toString() + fUnit;
231+
232+
// consider the last chunk even if is not long enough
233+
if (chunkSize > 0) { maxIdx += (chunkSize - (maxIdx % chunkSize)) % chunkSize; }
234+
235+
// generate labels
236+
const labelsContainer = this.generateLabels(false, labels, startIndexes, topIndexes, false, indexWidth, tooltips, data, rowMarginBottom);
237+
238+
let index = '';
239+
let cards = '';
240+
let cell;
241+
let entity;
242+
let style;
243+
let html = '';
244+
let idxNum = 0;
245+
let idx;
246+
247+
for (let x = 1; x <= maxIdx; x++) {
248+
let cells = this.addTopIndexes(topIndexes, chunkSize, x, maxTop, rowMarginBottom);
249+
250+
for (let y = 0; y < data.length; y++) {
251+
entity = data[y][x];
252+
style = 'font-size: 1em;display:block;height:1em;line-height:1em;margin-bottom:' + rowMarginBottom;
253+
if (y === data.length - 1) { style = 'font-size: 1em;display:block;line-height:1em;margin-bottom:' + rowMarginBottom; }
254+
if (!entity) {
255+
// emptyfiller
256+
cell = `<span style="${style}"> </span>`;
257+
} else {
258+
if (entity.target) { style += `${entity.target}`; }
259+
if (entity.char && !entity.char.includes('svg')) {
260+
// y is the row, x is the column
261+
cell = `<span class="cell" data-res-x='${x}' data-res-y= '${y}' data-res-id= '${this.divId}'
262+
style="${style}">${entity.char}</span>`;
263+
} else {
264+
style += '-webkit-user-select: none;';
265+
cell = `<span style="${style}">${entity.char}</span>`;
266+
}
267+
}
268+
cells += cell;
269+
}
270+
271+
cards += `<div class="crd">${cells}</div>`; // width 3/5em to reduce white space around letters
272+
cells = '';
273+
274+
275+
if (chunkSize > 0 && x % chunkSize === 0) {
276+
// considering the row of top indexes
277+
if (topIndexes) {
278+
} else {
279+
idxNum += chunkSize; // lateral index (set only if top indexes missing)
280+
idx = idxNum - (chunkSize - 1);
281+
}
282+
// adding labels
283+
if (lateralIndexesGap && !topIndexes) {
284+
const gapsContainer = this.generateLabels(idx, labels, startIndexes, topIndexes, false, indexWidth, false, data, rowMarginBottom);
285+
if (oneLineSetting) {
286+
index = gapsContainer; // lateral number indexes + labels
287+
} else {
288+
index = labelsContainer + gapsContainer; // lateral number indexes + labels
289+
}
290+
291+
} else if (!lateralIndexesGap && !topIndexes) {
292+
const gapsContainer = this.generateLabels(idx, labels, startIndexes, topIndexes, chunkSize, indexWidth, false, data, rowMarginBottom);
293+
if (oneLineSetting) {
294+
index = gapsContainer; // lateral number indexes + labels
295+
} else {
296+
index = labelsContainer + gapsContainer; // lateral number indexes + labels
297+
}
298+
} else {
299+
index = labelsContainer;
300+
}
301+
302+
index = `<div class="idx hidden">${index}</div>`;
303+
style = `font-size: ${fontSize};`;
304+
305+
if (x !== maxIdx) { style += 'padding-right: ' + spaceSize + 'em;'; } else { style += 'margin-right: ' + spaceSize + 'em;'; }
306+
307+
const chunk = `<div class="cnk" style="${style}">${index}<div class="crds">${cards}</div></div>`;
308+
cards = '';
309+
index = '';
310+
html += chunk;
311+
}
312+
}
313+
314+
if (oneLineSetting) {
315+
sqvBody.innerHTML = `<div class="root" style="display: flex"><div style="${style}">${labelsContainer}</div>
316+
<div style="display:inline-block;overflow-x:scroll;white-space: nowrap;width:${oneLineWidth}"> ${html}</div></div>`;
317+
} else {
318+
sqvBody.innerHTML = `<div class="root">${html}</div>`;
319+
}
320+
321+
window.dispatchEvent(new Event('resize'));
322+
}
323+
}

0 commit comments

Comments
 (0)