• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

eweitz / ideogram / 2119

pending completion
2119

push

travis-ci-com

eweitz
Fix test: hints -> leads

1782 of 2271 branches covered (78.47%)

Branch coverage included in aggregate %.

4666 of 5173 relevant lines covered (90.2%)

34824.3 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.6
/src/js/annotations/annotations.js
1
/**
2
 * @fileoverview Methods for ideogram annotations.
3
 * Annotations are graphical objects that represent features of interest
4
 * located on the chromosomes, e.g. genes or variations.  They can
5
 * appear beside a chromosome, overlaid on top of it, or between multiple
6
 * chromosomes.
7
 */
8

9
import {BedParser} from '../parsers/bed-parser';
10
import {TsvParser} from '../parsers/tsv-parser';
11
import {drawHeatmaps, deserializeAnnotsForHeatmap} from './heatmap';
12
import {inflateThresholds} from './heatmap-lib';
13
import {inflateHeatmaps} from './heatmap-collinear';
14
import {
15
  onLoadAnnots, onDrawAnnots, startHideAnnotTooltipTimeout,
16
  onWillShowAnnotTooltip, onDidShowAnnotTooltip, showAnnotTooltip, onClickAnnot
17
} from './events';
18

19
import {
20
  addAnnotLabel, removeAnnotLabel, fillAnnotLabels, clearAnnotLabels
21
  // fadeOutAnnotLabels
22
} from './labels';
23

24
import {drawAnnots, drawProcessedAnnots} from './draw';
25
import {getHistogramBars} from './histogram';
26
import {drawSynteny} from './synteny';
27
import {
28
  restoreDefaultTracks, setOriginalTrackIndexes, updateDisplayedTracks
29
} from './filter';
30
import {processAnnotData} from './process';
31
import {ExpressionMatrixParser} from '../parsers/expression-matrix-parser';
32
import {downloadAnnotations} from './download';
33

34
function initNumTracksAndBarWidth(ideo, config) {
35

36
  if (config.annotationTracks) {
32✔
37
    ideo.config.numAnnotTracks = config.annotationTracks.length;
8✔
38
  } else if (config.annotationsNumTracks) {
24✔
39
    ideo.config.numAnnotTracks = config.annotationsNumTracks;
5✔
40
  } else {
41
    ideo.config.numAnnotTracks = 1;
19✔
42
  }
43
  ideo.config.annotTracksHeight =
32✔
44
    config.annotationHeight * config.numAnnotTracks;
45

46
  if (typeof config.barWidth === 'undefined') {
32✔
47
    ideo.config.barWidth = 3;
29✔
48
  }
49
}
50

51
function initTooltip(ideo, config) {
52
  if (config.showAnnotTooltip !== false) {
85!
53
    ideo.config.showAnnotTooltip = true;
85✔
54
  }
55

56
  if (config.onWillShowAnnotTooltip) {
85!
57
    ideo.onWillShowAnnotTooltipCallback = config.onWillShowAnnotTooltip;
×
58
  }
59

60
  if (config.onDidShowAnnotTooltip) {
85!
61
    ideo.onDidShowAnnotTooltipCallback = config.onDidShowAnnotTooltip;
×
62
  }
63
}
64

65
function initAnnotLabel(ideo, config) {
66
  if (config.addAnnotLabel !== false) {
85!
67
    ideo.config.addAnnotLabel = true;
85✔
68
  }
69

70
  if (config.onWillAddAnnotLabel) {
85!
71
    ideo.onWillAddAnnotLabelCallback = config.onWillAddAnnotLabel;
×
72
  }
73
}
74

75
function initAnnotHeight(ideo) {
76
  var config = ideo.config;
85✔
77
  var annotHeight;
78

79
  if (!config.annotationHeight) {
85✔
80
    if (config.annotationsLayout === 'heatmap') {
66✔
81
      annotHeight = config.chrWidth - 1;
4✔
82
    } else {
83
      annotHeight = Math.round(config.chrHeight / 100);
62✔
84
      if (annotHeight < 3) annotHeight = 3;
62✔
85
    }
86
    ideo.config.annotationHeight = annotHeight;
66✔
87
  }
88
}
89

90
/**
91
 * Initializes various annotation settings.  Constructor help function.
92
 */
93
function initAnnotSettings() {
94
  var ideo = this,
85✔
95
    config = ideo.config;
85✔
96

97
  initAnnotHeight(ideo);
85✔
98

99
  if (
85✔
100
    config.annotationsPath || config.localAnnotationsPath ||
268✔
101
    ideo.annots || config.annotations
102
  ) {
103
    initNumTracksAndBarWidth(ideo, config);
32✔
104
  } else {
105
    ideo.config.annotTracksHeight = 0;
53✔
106
    ideo.config.numAnnotTracks = 0;
53✔
107
  }
108

109
  if (typeof config.annotationsColor === 'undefined') {
85!
110
    ideo.config.annotationsColor = '#F00';
85✔
111
  }
112

113
  if (config.onClickAnnot) {
85!
114
    ideo.onClickAnnotCallback = config.onClickAnnot;
×
115
  }
116

117
  initTooltip(ideo, config);
85✔
118
  initAnnotLabel(ideo, config);
85✔
119
}
120

121
function validateAnnotsUrl(annotsUrl) {
122
  var tmp, extension;
123

124
  tmp = annotsUrl.split('?')[0].split('.');
24✔
125
  extension = tmp[tmp.length - 1];
24✔
126

127
  if (['bed', 'json', 'tsv'].includes(extension) === false) {
24✔
128
    extension = extension.toUpperCase();
2✔
129
    alert(
2✔
130
      'Ideogram.js only supports BED and Ideogram JSON and TSV ' +
131
      'at the moment.  ' +
132
      'Sorry, check back soon for ' + extension + ' support!'
133
    );
134
    return;
2✔
135
  }
136
  return extension;
22✔
137
}
138

139
/** Find redundant chromosomes in raw annotations */
140
function detectDuplicateChrsInRawAnnots(ideo) {
141
  const seen = {};
22✔
142
  const duplicates = [];
22✔
143
  const chrs = ideo.rawAnnots.annots.map(annot => annot.chr);
459✔
144

145
  chrs.forEach((chr) => {
22✔
146
    if (chr in seen) duplicates.push(chr);
459✔
147
    seen[chr] = 1;
459✔
148
  });
149

150
  if (duplicates.length > 0) {
22✔
151
    const message =
152
      `Duplicate chromosomes detected.\n` +
1✔
153
      `Chromosome list: ${chrs}.  Duplicates: ${duplicates}.\n` +
154
      `To fix this, edit your raw annotations JSON data to remove redundant ` +
155
      `chromosomes.`;
156
    throw Error(message);
1✔
157
  }
158
}
159

160
function afterRawAnnots() {
161
  var ideo = this,
23✔
162
    config = ideo.config;
23✔
163

164
  // Ensure annots are ordered by chromosome
165
  ideo.rawAnnots.annots = ideo.rawAnnots.annots.sort(Ideogram.sortChromosomes);
23✔
166

167
  if (ideo.onLoadAnnotsCallback) {
23!
168
    ideo.onLoadAnnotsCallback();
×
169
  }
170

171
  if (
23✔
172
    'heatmapThresholds' in config ||
48✔
173
    'metadata' in ideo.rawAnnots &&
174
    'heatmapThresholds' in ideo.rawAnnots.metadata
175
  ) {
176
    if (config.annotationsLayout === 'heatmap') {
3✔
177
      inflateHeatmaps(ideo);
2✔
178
    } else if (config.annotationsLayout === 'heatmap-2d') {
1!
179
      ideo.config.heatmapThresholds = inflateThresholds(ideo);
1✔
180
    }
181
  }
182

183
  if (config.heatmaps) {
22✔
184
    ideo.deserializeAnnotsForHeatmap(ideo.rawAnnots);
3✔
185
  }
186

187
  detectDuplicateChrsInRawAnnots(ideo);
22✔
188
}
189

190
/**
191
 * Converts list of annotation-by-chromosome objects to list of annot objects
192
 */
193
function flattenAnnots() {
194
  const ideo = this;
3✔
195
  return ideo.annots.reduce((accumulator, annots) => {
3✔
196
    return [...accumulator, ...annots.annots];
72✔
197
  }, []);
198
}
199

200
/**
201
 * Requests annotations URL via HTTP, sets ideo.rawAnnots for downstream
202
 * processing.
203
 *
204
 * @param annotsUrl Absolute or relative URL for native or BED annotations file
205
 */
206
function fetchAnnots(annotsUrl) {
207
  var extension, is2dHeatmap,
208
    ideo = this,
24✔
209
    config = ideo.config;
24✔
210

211
  is2dHeatmap = config.annotationsLayout === 'heatmap-2d';
24✔
212

213
  var extension = validateAnnotsUrl(annotsUrl);
24✔
214

215
  if (annotsUrl.slice(0, 4) !== 'http' && !is2dHeatmap && extension !== 'tsv') {
24✔
216
    ideo.fetch(annotsUrl)
18✔
217
      .then(function(data) {
218
        ideo.rawAnnotsResponse = data; // Preserve truly raw response content
18✔
219
        ideo.rawAnnots = data; // Sometimes gets partially processed
18✔
220
        ideo.afterRawAnnots();
18✔
221
      });
222
    return;
18✔
223
  }
224

225
  extension = (is2dHeatmap ? '' : extension);
6✔
226

227
  ideo.fetch(annotsUrl, 'text')
6✔
228
    .then(function(text) {
229
      ideo.rawAnnotsResponse = text;
4✔
230
      if (is2dHeatmap) {
4✔
231
        var parser = new ExpressionMatrixParser(text, ideo);
1✔
232
        parser.setRawAnnots().then(function(d) {
1✔
233
          ideo.rawAnnots = d;
1✔
234
          ideo.afterRawAnnots();
1✔
235
        });
236
      } else {
237
        if (extension === 'tsv') {
3✔
238
          ideo.rawAnnots = new TsvParser(text, ideo).rawAnnots;
2✔
239
        } else if (extension === 'bed') {
1!
240
          ideo.rawAnnots = new BedParser(text, ideo).rawAnnots;
1✔
241
        } else {
242
          ideo.rawAnnots = JSON.parse(text);
×
243
        }
244
        ideo.afterRawAnnots();
3✔
245
      }
246
    });
247
}
248

249
/**
250
 * Fills out annotations data structure such that its top-level list of arrays
251
 * matches that of this ideogram's chromosomes list in order and number
252
 * Fixes https://github.com/eweitz/ideogram/issues/66
253
 */
254
function fillAnnots(annots) {
255
  var filledAnnots, chrs, chrArray, i, chr, annot, chrIndex;
256

257
  filledAnnots = [];
55✔
258
  chrs = [];
55✔
259
  chrArray = this.chromosomesArray;
55✔
260

261
  for (i = 0; i < chrArray.length; i++) {
55✔
262
    chr = chrArray[i].name;
1,243✔
263
    chrs.push(chr);
1,243✔
264
    filledAnnots.push({chr: chr, annots: []});
1,243✔
265
  }
266

267
  for (i = 0; i < annots.length; i++) {
55✔
268
    annot = annots[i];
1,202✔
269
    chrIndex = chrs.indexOf(annot.chr);
1,202✔
270
    if (chrIndex !== -1) {
1,202!
271
      filledAnnots[chrIndex] = annot;
1,202✔
272
    }
273
  }
274

275
  return filledAnnots;
55✔
276
}
277

278
export function applyRankCutoff(annots, cutoff, ideo) {
279
  const rankedAnnots = sortAnnotsByRank(annots, ideo);
×
280

281
  // Take the top N ranked genes, where N is `cutoff`
282
  annots = rankedAnnots.slice(0, cutoff);
×
283

284
  return annots;
×
285
}
286

287
export function setAnnotRanks(annots, ideo) {
288
  if ('geneCache' in ideo === false) return annots;
1,026!
289

290
  const ranks = ideo.geneCache.interestingNames;
1,026✔
291

292
  return annots.map(annot => {
1,026✔
293
    if (ranks.includes(annot.name)) {
1,436✔
294
      annot.rank = ranks.indexOf(annot.name) + 1;
1,120✔
295
    } else {
296
      annot.rank = 1E10;
316✔
297
    }
298
    return annot;
1,436✔
299
  });
300
}
301

302
export function sortAnnotsByRank(annots, ideo) {
303

304
  if (ideo) {
271!
305
    annots = setAnnotRanks(annots, ideo);
271✔
306
  }
307
  // Ranks annots by popularity
308
  return annots.sort((a, b) => {
271✔
309

310
    // // Search gene is most important, regardless of popularity
311
    // if (a.color === 'red') return -1;
312
    // if (b.color === 'red') return 1;
313

314
    // Rank 3 is more important than rank 30
315
    return a.rank - b.rank;
2,135✔
316
  });
317
}
318

319
export {
320
  onLoadAnnots, onDrawAnnots, processAnnotData, restoreDefaultTracks,
321
  updateDisplayedTracks, initAnnotSettings, fetchAnnots, drawAnnots,
322
  getHistogramBars, drawHeatmaps, deserializeAnnotsForHeatmap, fillAnnots,
323
  drawProcessedAnnots, drawSynteny, startHideAnnotTooltipTimeout,
324
  showAnnotTooltip, onWillShowAnnotTooltip, onDidShowAnnotTooltip,
325
  setOriginalTrackIndexes,
326
  afterRawAnnots, onClickAnnot, downloadAnnotations, addAnnotLabel,
327
  removeAnnotLabel, fillAnnotLabels, clearAnnotLabels, flattenAnnots
328
  // fadeOutAnnotLabels
329
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc