oncoprintjs 6.0.3 → 6.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -30,6 +30,17 @@ type ColumnIdIndex = number;
30
30
  type PositionVertex = [number, number, number];
31
31
  type ColorVertex = [number, number, number, number];
32
32
 
33
+ type OncoprintGap = {
34
+ origin_x: number;
35
+ origin_y: number;
36
+ data: OncoprintGapConfig;
37
+ };
38
+
39
+ export type OncoprintGapConfig = {
40
+ labelFormatter: () => string;
41
+ tooltipFormatter: () => string;
42
+ };
43
+
33
44
  export type OncoprintWebGLContext = WebGLRenderingContext & {
34
45
  viewportWidth: number;
35
46
  viewportHeight: number;
@@ -87,6 +98,7 @@ export default class OncoprintWebGLCellView {
87
98
  private mouseMoveHandler: (evt: MouseMoveEvent) => void;
88
99
 
89
100
  private ctx: OncoprintWebGLContext | null;
101
+ private gap_ctx: CanvasRenderingContext2D | null;
90
102
  private ext: ANGLE_instanced_arrays | null;
91
103
  private overlay_ctx: CanvasRenderingContext2D | null;
92
104
  private column_label_ctx: CanvasRenderingContext2D | null;
@@ -129,6 +141,7 @@ export default class OncoprintWebGLCellView {
129
141
  private $container: JQuery,
130
142
  private $canvas: JQuery<HTMLCanvasElement>,
131
143
  private $overlay_canvas: JQuery<HTMLCanvasElement>,
144
+ private $gap_canvas: JQuery<HTMLCanvasElement>,
132
145
  private $column_label_canvas: JQuery<HTMLCanvasElement>,
133
146
  private $dummy_scroll_div_contents: JQuery,
134
147
  model: OncoprintModel,
@@ -249,6 +262,7 @@ export default class OncoprintWebGLCellView {
249
262
  const offset = self.$overlay_canvas.offset();
250
263
  const mouseX = evt.pageX - offset.left;
251
264
  const mouseY = evt.pageY - offset.top;
265
+
252
266
  let overlapping_cells = model.getOverlappingCells(
253
267
  mouseX + self.scroll_x,
254
268
  mouseY + self.scroll_y
@@ -294,9 +308,43 @@ export default class OncoprintWebGLCellView {
294
308
  )
295
309
  );
296
310
  } else {
297
- tooltip.hideIfNotAlreadyGoingTo(150);
298
311
  overlapping_cells = null;
299
312
  }
313
+
314
+ // find a gap which is in range of mouse position
315
+ const overlappingGap = self.gapTooltipTargets.find(
316
+ (t: any) => {
317
+ return (
318
+ _.inRange(mouseX - t.origin_x, 0, 20) &&
319
+ _.inRange(t.origin_y - mouseY, -10, 15)
320
+ );
321
+ }
322
+ );
323
+
324
+ // if there is no gap, turn
325
+ if (overlappingGap === undefined) {
326
+ self.hoveredGap = undefined;
327
+ } else if (self.hoveredGap === overlappingGap) {
328
+ // tooltip should already be showing, so do nothing
329
+ } else {
330
+ // we have a new hovered gap, so show a tooltip
331
+ const clientRect = self.$overlay_canvas[0].getBoundingClientRect();
332
+ self.hoveredGap = overlappingGap;
333
+ tooltip.center = false;
334
+ tooltip.show(
335
+ 250,
336
+ clientRect.left + overlappingGap.origin_x,
337
+ clientRect.top + overlappingGap.origin_y - 20,
338
+ $(
339
+ `<span>${overlappingGap.data.tooltipFormatter()}</span>`
340
+ ),
341
+ false
342
+ );
343
+ }
344
+
345
+ if (!overlapping_data && !overlappingGap) {
346
+ tooltip.hideIfNotAlreadyGoingTo(150);
347
+ }
300
348
  } else {
301
349
  overlapping_cells = null;
302
350
  drag_end_x = mouseX;
@@ -353,6 +401,21 @@ export default class OncoprintWebGLCellView {
353
401
  });
354
402
  }
355
403
 
404
+ private drawGapLabel(txt: string, x: number, y: number) {
405
+ this.gap_ctx.font = '15pt Arial';
406
+ this.gap_ctx.textAlign = 'right';
407
+
408
+ const origin_x = x * this.supersampling_ratio + 52;
409
+ const origin_y = y * this.supersampling_ratio + 4;
410
+
411
+ this.gap_ctx.fillText(txt, origin_x, origin_y);
412
+
413
+ return {
414
+ origin_x: x,
415
+ origin_y: y,
416
+ };
417
+ }
418
+
356
419
  private getNewCanvas() {
357
420
  const old_canvas = this.$canvas[0];
358
421
  const new_canvas = old_canvas.cloneNode() as HTMLCanvasElement;
@@ -364,6 +427,14 @@ export default class OncoprintWebGLCellView {
364
427
  this.ext = null;
365
428
  }
366
429
 
430
+ private getGapContext() {
431
+ try {
432
+ return this.$gap_canvas[0].getContext('2d');
433
+ } catch (e) {
434
+ return null;
435
+ }
436
+ }
437
+
367
438
  private getWebGLCanvasContext() {
368
439
  try {
369
440
  const canvas = this.$canvas[0];
@@ -534,11 +605,14 @@ export default class OncoprintWebGLCellView {
534
605
  highlightWidth,
535
606
  highlightHeight
536
607
  );
608
+
537
609
  this.overlay_ctx.restore();
538
610
  }
539
611
  }
540
612
 
541
613
  private getWebGLContextAndSetUpMatrices() {
614
+ this.gap_ctx = this.getGapContext();
615
+
542
616
  this.ctx = this.getWebGLCanvasContext();
543
617
  if (this.ctx) {
544
618
  this.ext = this.ctx.getExtension('ANGLE_instanced_arrays');
@@ -681,6 +755,13 @@ export default class OncoprintWebGLCellView {
681
755
  this.dummy_scroll_div_client_size.update();
682
756
  this.$canvas[0].height = this.supersampling_ratio * height;
683
757
  this.$canvas[0].style.height = height + 'px';
758
+ this.$gap_canvas[0].height = this.supersampling_ratio * height;
759
+ this.$gap_canvas[0].style.height = height + 'px';
760
+
761
+ this.$gap_canvas[0].width =
762
+ this.supersampling_ratio * visible_area_width;
763
+ this.$gap_canvas[0].style.width = visible_area_width + 'px';
764
+
684
765
  this.$overlay_canvas[0].height = this.supersampling_ratio * height;
685
766
  this.$overlay_canvas[0].style.height = height + 'px';
686
767
  this.$column_label_canvas[0].height = this.supersampling_ratio * height;
@@ -701,6 +782,10 @@ export default class OncoprintWebGLCellView {
701
782
  this.getColumnLabelsContext();
702
783
  }
703
784
 
785
+ public gapTooltipTargets: OncoprintGap[] = [];
786
+
787
+ public hoveredGap: OncoprintGap;
788
+
704
789
  private renderAllTracks(model: OncoprintModel, dont_resize?: boolean) {
705
790
  if (this.rendering_suppressed) {
706
791
  return;
@@ -743,11 +828,45 @@ export default class OncoprintWebGLCellView {
743
828
  this.ctx.clearColor(1.0, 1.0, 1.0, 1.0);
744
829
  this.ctx.clear(this.ctx.COLOR_BUFFER_BIT | this.ctx.DEPTH_BUFFER_BIT);
745
830
 
831
+ this.gap_ctx.clearRect(
832
+ 0,
833
+ 0,
834
+ this.$gap_canvas[0].width,
835
+ this.$gap_canvas[0].height
836
+ );
837
+
838
+ const gapOffsets = model.getGapOffsets();
839
+
746
840
  const tracks = model.getTracks();
841
+
842
+ this.gapTooltipTargets = [];
843
+
747
844
  for (let i = 0; i < tracks.length; i++) {
748
845
  const track_id = tracks[i];
749
846
  const cell_top = model.getCellTops(track_id);
750
847
  const cell_height = model.getCellHeight(track_id);
848
+
849
+ if (model.showGaps()) {
850
+ const gaps = this.getGaps(model, track_id);
851
+ if (gaps) {
852
+ gaps.forEach((gap: OncoprintGapConfig, i: number) => {
853
+ const x = gapOffsets[i] - scroll_x - model.getGapSize();
854
+ const y =
855
+ model.getZoomedTrackTops()[track_id] +
856
+ cell_height -
857
+ scroll_y;
858
+
859
+ this.drawGapLabel(gap.labelFormatter(), x, y);
860
+
861
+ this.gapTooltipTargets.push({
862
+ origin_x: x,
863
+ origin_y: y,
864
+ data: gap,
865
+ });
866
+ });
867
+ }
868
+ }
869
+
751
870
  if (
752
871
  cell_top / zoom_y >= window_bottom ||
753
872
  (cell_top + cell_height) / zoom_y < window_top
@@ -756,6 +875,7 @@ export default class OncoprintWebGLCellView {
756
875
  continue;
757
876
  }
758
877
  const buffers = this.getTrackBuffers(track_id);
878
+
759
879
  if (buffers.position.numItems === 0) {
760
880
  continue;
761
881
  }
@@ -1877,6 +1997,13 @@ export default class OncoprintWebGLCellView {
1877
1997
  return this.dummy_scroll_div_client_size.get();
1878
1998
  }
1879
1999
 
2000
+ getGaps(model: OncoprintModel, track_id: number) {
2001
+ const custom = model.getTrackCustomOptions(track_id);
2002
+ return _.isEmpty(model.ids_after_a_gap.get())
2003
+ ? undefined
2004
+ : custom.find(t => !!t.gapLabelsFn)?.gapLabelsFn(model);
2005
+ }
2006
+
1880
2007
  public toSVGGroup(
1881
2008
  model: OncoprintModel,
1882
2009
  offset_x: number,
@@ -1887,9 +2014,13 @@ export default class OncoprintWebGLCellView {
1887
2014
  const tracks = model.getTracks();
1888
2015
  const zoomedColumnLeft = model.getZoomedColumnLeft();
1889
2016
  // add cell shapes
2017
+
2018
+ const gapOffsets = model.getGapOffsets();
2019
+
1890
2020
  for (let i = 0; i < tracks.length; i++) {
1891
2021
  const track_id = tracks[i];
1892
2022
  const offset_y = cell_tops[track_id];
2023
+ const cell_height = model.getCellHeight(track_id);
1893
2024
  const universal_shapes = model.getTrackUniversalShapes(
1894
2025
  track_id,
1895
2026
  false
@@ -1898,6 +2029,31 @@ export default class OncoprintWebGLCellView {
1898
2029
  track_id,
1899
2030
  false
1900
2031
  );
2032
+
2033
+ const custom = model.getTrackCustomOptions(track_id);
2034
+ if (gapOffsets[0]) {
2035
+ const gaps = _.isEmpty(model.ids_after_a_gap.get())
2036
+ ? undefined
2037
+ : custom.find(t => !!t.gapLabelsFn)?.gapLabelsFn(model);
2038
+
2039
+ if (gaps) {
2040
+ gaps.forEach((gap: any, i: number) => {
2041
+ const textElt = makeSvgElement('text', {
2042
+ x: gapOffsets[i] - model.getGapSize() + 25,
2043
+ y: offset_y + cell_height - 3,
2044
+ 'font-size': '10',
2045
+ 'font-family': 'Arial',
2046
+ 'font-weight': 'normal',
2047
+ 'text-anchor': 'end',
2048
+ 'alignment-baseline': 'top',
2049
+ });
2050
+
2051
+ textElt.textContent = gap.labelFormatter();
2052
+ root.appendChild(textElt);
2053
+ });
2054
+ }
2055
+ }
2056
+
1901
2057
  for (let j = 0; j < identified_shape_list_list.length; j++) {
1902
2058
  const id_sl = identified_shape_list_list[j];
1903
2059
  const id = id_sl.id;