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

eweitz / ideogram / 12131714885

03 Dec 2024 02:21AM UTC coverage: 82.273% (-1.1%) from 83.356%
12131714885

push

github

web-flow
Merge pull request #375 from eweitz/smooth-repeat-hover

Avoid hiding tooltip on first hover of previous clicked annotation

2359 of 3225 branches covered (73.15%)

Branch coverage included in aggregate %.

10 of 15 new or added lines in 3 files covered. (66.67%)

455 existing lines in 18 files now uncovered.

5410 of 6218 relevant lines covered (87.01%)

27653.67 hits per line

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

91.82
/src/js/annotations/draw.js
1
import {d3} from '../lib';
2
import {writeHistogramAnnots} from './histogram';
3
import {writeLegend} from './legend';
4

5
function parseFriendlyAnnots(friendlyAnnots, rawAnnots) {
6
  var i, j, annot, rawAnnot;
7

8
  for (i = 0; i < friendlyAnnots.length; i++) {
59✔
9
    annot = friendlyAnnots[i];
360✔
10

11
    for (j = 0; j < rawAnnots.length; j++) {
360✔
12
      if (annot.chr === rawAnnots[j].chr) {
4,250✔
13
        rawAnnot = [
360✔
14
          annot.name,
15
          annot.start,
16
          annot.stop - annot.start
17
        ];
18
        if ('color' in annot) rawAnnot.push(annot.color);
360✔
19
        if ('shape' in annot) rawAnnot.push(annot.shape);
360✔
20
        rawAnnots[j].annots.push(rawAnnot);
360✔
21
        break;
360✔
22
      }
23
    }
24
  }
25

26
  return rawAnnots;
59✔
27
}
28

29
function parseFriendlyKeys(friendlyAnnots) {
30
  var keys = ['name', 'start', 'length'];
59✔
31
  if ('color' in friendlyAnnots[0]) {
59✔
32
    keys.push('color');
55✔
33
  }
34
  if ('shape' in friendlyAnnots[0]) {
59!
35
    keys.push('shape');
×
36
  }
37
  return keys;
59✔
38
}
39

40
/**
41
 * Draws annotations defined by user
42
 */
43
function drawAnnots(friendlyAnnots, layout, keep=false, isOtherLayout=false) {
96✔
44
  var keys, chr,
45
    rawAnnots = [],
68✔
46
    ideo = this,
68✔
47
    chrs = ideo.chromosomes[ideo.config.taxid]; // TODO: multiorganism
68✔
48

49
  if (friendlyAnnots.length === 0) {
68!
UNCOV
50
    ideo.annots = [];
×
UNCOV
51
    return;
×
52
  }
53

54
  if (
68✔
55
    'annots' in friendlyAnnots[0] || // When filtering
127✔
56
    'values' in friendlyAnnots[0] // When drawing cached expression matrices
57
  ) {
58
    return ideo.drawProcessedAnnots(friendlyAnnots, layout);
9✔
59
  }
60

61
  for (chr in chrs) {
59✔
62
    rawAnnots.push({chr: chr, annots: []});
1,360✔
63
  }
64
  rawAnnots = parseFriendlyAnnots(friendlyAnnots, rawAnnots);
59✔
65

66
  keys = parseFriendlyKeys(friendlyAnnots);
59✔
67

68
  ideo.rawAnnots = {keys: keys, annots: rawAnnots};
59✔
69

70
  const processedAnnots = ideo.processAnnotData(ideo.rawAnnots);
59✔
71
  if (!isOtherLayout) {
59✔
72
    ideo.annots = processedAnnots;
39✔
73
  } else {
74
    ideo.annotsOther = processedAnnots;
20✔
75
  }
76

77
  ideo.drawProcessedAnnots(processedAnnots, layout, keep);
59✔
78
}
79

80
function getShapes(annotHeight) {
81
  var triangle, circle, rectangle, r;
82

83
  triangle =
50✔
84
    'm0,0 l -' + annotHeight + ' ' + (2 * annotHeight) +
85
    ' l ' + (2 * annotHeight) + ' 0 z';
86

87
  // From http://stackoverflow.com/a/10477334, with a minor change ("m -r, r")
88
  // Circles are supported natively via <circle>, but having it as a path
89
  // simplifies handling triangles, circles and other shapes in the same
90
  // D3 call
91
  r = annotHeight;
50✔
92
  circle =
50✔
93
    'm -' + r + ', ' + r +
94
    'a ' + r + ',' + r + ' 0 1,0 ' + (r * 2) + ',0' +
95
    'a ' + r + ',' + r + ' 0 1,0 -' + (r * 2) + ',0';
96

97
  rectangle =
50✔
98
    'm0,0 l 0 ' + (2 * annotHeight) +
99
    'l ' + annotHeight + ' 0' +
100
    'l 0 -' + (2 * annotHeight) + 'z';
101

102
  return {triangle: triangle, circle: circle, rectangle: rectangle};
50✔
103
}
104

105
function getChrAnnotNodes(filledAnnots, ideo) {
106
  return d3.selectAll(ideo.selector + ' .chromosome')
81✔
107
    .data(filledAnnots)
108
    .selectAll('path.annot')
109
    .data(function(d) {
110
      return d.annots;
1,867✔
111
    })
112
    .enter();
113
}
114

115
function determineShape(d, shapes) {
116
  if (!d.shape || d.shape === 'triangle') {
7,819✔
117
    return shapes.triangle;
5,819✔
118
  } else if (d.shape === 'circle') {
2,000✔
119
    return shapes.circle;
1,000✔
120
  } else if (d.shape === 'rectangle') {
1,000!
UNCOV
121
    return shapes.rectangle;
×
122
  } else {
123
    return d.shape;
1,000✔
124
  }
125
}
126

127
function writeTrackAnnots(chrAnnot, ideo) {
128
  var shapes,
129
    annotHeight = ideo.config.annotationHeight;
50✔
130

131
  shapes = getShapes(annotHeight);
50✔
132

133
  chrAnnot.append('g')
50✔
134
    .attr('id', function(d) {return d.domId;})
7,819✔
135
    .attr('class', 'annot')
136
    .attr('transform', function(d) {
137
      var y = ideo.config.chrWidth + (d.trackIndex * annotHeight * 2);
7,819✔
138
      return 'translate(' + d.px + ',' + y + ')';
7,819✔
139
    })
140
    .append('path')
141
    .attr('d', function(d) {return determineShape(d, shapes);})
7,819✔
142
    .attr('fill', function(d) {return d.color;})
7,819✔
143
    .on('mouseover', function(event, d) {ideo.showAnnotTooltip(d, this);})
16✔
144
    .on('mouseout', function() {ideo.startHideAnnotTooltipTimeout();})
2✔
UNCOV
145
    .on('click', function(event, d) {ideo.onClickAnnot(d);});
×
146
}
147

148
/**
149
 * Overlaid annotations appear directly on chromosomes
150
 */
151
function writeOverlayAnnots(chrAnnot, ideo) {
152
  chrAnnot.append('polygon')
23✔
153
    .attr('id', function(d) {return d.id;})
1,047✔
154
    .attr('class', 'annot')
155
    .attr('points', function(d) {
156
      var x1, x2,
157
        chrWidth = ideo.config.chrWidth;
1,047✔
158

159
      if (d.stopPx - d.startPx > 1) {
1,047✔
160
        x1 = d.startPx;
43✔
161
        x2 = d.stopPx;
43✔
162
      } else {
163
        x1 = d.px - 0.5;
1,004✔
164
        x2 = d.px + 0.5;
1,004✔
165
      }
166

167
      return (
1,047✔
168
        x1 + ',' + chrWidth + ' ' + x2 + ',' + chrWidth + ' ' +
169
        x2 + ',0 ' + x1 + ',0'
170
      );
171
    })
172
    .attr('fill', function(d) {return d.color;})
1,047✔
UNCOV
173
    .on('mouseover', function(event, d) {ideo.showAnnotTooltip(d, this);})
×
UNCOV
174
    .on('mouseout', function() {ideo.startHideAnnotTooltipTimeout();});
×
175
}
176

177
function warnIfTooManyAnnots(layout, annots) {
178
  var i, numAnnots;
179

180
  if (!/heatmap/.test(layout) && layout !== 'histogram') {
81✔
181
    numAnnots = 0;
73✔
182
    for (i = 0; i < annots.length; i++) {
73✔
183
      numAnnots += annots[i].annots.length;
1,660✔
184
    }
185
    if (numAnnots > 2000) {
73!
UNCOV
186
      console.warn(
×
187
        'Rendering more than 2000 annotations in Ideogram?\n' +
188
        'Try setting "annotationsLayout" to "heatmap" or "histogram" in your ' +
189
        'Ideogram configuration object for better layout and performance.'
190
      );
191
    }
192
  }
193
}
194

195
function drawAnnotsByLayoutType(layout, annots, ideo) {
196
  var filledAnnots, chrAnnot;
197

198
  warnIfTooManyAnnots(layout, annots);
81✔
199

200
  if (layout === 'histogram') annots = ideo.getHistogramBars(annots);
81✔
201

202
  filledAnnots = ideo.fillAnnots(annots);
81✔
203

204
  chrAnnot = getChrAnnotNodes(filledAnnots, ideo);
81✔
205

206
  if (layout === 'tracks') {
81✔
207
    writeTrackAnnots(chrAnnot, ideo);
50✔
208
  } else if (layout === 'overlay') {
31✔
209
    writeOverlayAnnots(chrAnnot, ideo);
23✔
210
  } else if (layout === 'histogram') {
8!
211
    writeHistogramAnnots(chrAnnot, ideo);
8✔
212
  }
213
}
214

215
/**
216
 * Draws genome annotations on chromosomes.
217
 * Annotations can be rendered as either overlaid directly
218
 * on a chromosome, or along one or more "tracks"
219
 * running parallel to each chromosome.
220
 */
221
function drawProcessedAnnots(annots, layout, keep=false) {
29✔
222
  var ideo = this;
88✔
223

224
  if (ideo.onBeforeDrawAnnotsCallback) {
88✔
225
    ideo.onBeforeDrawAnnotsCallback();
53✔
226
  }
227

228
  if (!keep) {
88✔
229
    d3.selectAll(ideo.selector + ' .annot').remove();
68✔
230
  }
231

232
  if (layout === undefined) layout = 'tracks';
88✔
233
  if (ideo.config.annotationsLayout) layout = ideo.config.annotationsLayout;
88✔
234

235
  if ('legend' in ideo.config) writeLegend(ideo);
88✔
236

237
  if (/heatmap/.test(layout)) {
88✔
238
    ideo.drawHeatmaps(annots);
7✔
239
    return;
7✔
240
  }
241

242
  drawAnnotsByLayoutType(layout, annots, ideo);
81✔
243
  if (ideo.onDrawAnnotsCallback) ideo.onDrawAnnotsCallback();
81✔
244
}
245

246
export {drawAnnots, drawProcessedAnnots, getShapes};
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