').appendTo(this.$buttons_ctr).css({'position': 'absolute', 'left': '0px', 'top': top + 'px', 'white-space': 'nowrap'});
179 | $img = $('
![]()
').appendTo($div)
180 | .attr({
181 | 'src': menuDotsIcon,
182 | 'width': this.img_size,
183 | 'height': this.img_size
184 | })
185 | .css({
186 | 'float': 'left',
187 | 'cursor': 'pointer',
188 | 'border': '1px solid rgba(125,125,125,0)'
189 | }).addClass(TOGGLE_BTN_CLASS).addClass(NTH_CLASS_PREFIX+(index+1));
190 | $sortarrow = $('
').appendTo($div).css({'position': 'absolute', 'top': Math.floor(this.img_size / 4) + 'px'});
191 | $dropdown = $('').appendTo(this.$dropdown_ctr)
192 | .css({
193 | 'position':'absolute',
194 | 'width': 120,
195 | 'display': 'none',
196 | 'list-style-type': 'none',
197 | 'padding-left': '6',
198 | 'padding-right': '6',
199 | 'float': 'right',
200 | 'background-color': 'rgb(255,255,255)',
201 | 'left':'0px', 'top': top + this.img_size + 'px'
202 | }).addClass(DROPDOWN_CLASS).addClass(NTH_CLASS_PREFIX+(index+1));
203 | this.track_options_$elts[track_id] = {'$div': $div, '$img': $img, '$dropdown': $dropdown};
204 |
205 | OncoprintTrackOptionsView.renderSortArrow($sortarrow, model, track_id);
206 |
207 | const self = this;
208 | $img.hover(function (evt) {
209 | if (!self.menu_shown[track_id]) {
210 | $(this).css({'border': '1px solid rgba(125,125,125,0.3)'});
211 | }
212 | }, function (evt) {
213 | if (!self.menu_shown[track_id]) {
214 | $(this).css({'border': '1px solid rgba(125,125,125,0)'});
215 | }
216 | });
217 | $img.click(function (evt) {
218 | evt.stopPropagation();
219 | if ($dropdown.is(":visible")) {
220 | $img.addClass(TOGGLE_BTN_OPEN_CLASS);
221 | self.hideTrackMenu(track_id);
222 | } else {
223 | $img.removeClass(TOGGLE_BTN_OPEN_CLASS);
224 | self.showTrackMenu(track_id);
225 | }
226 | self.hideMenusExcept(track_id);
227 | });
228 |
229 | const movingDisabled = model.getTrackMovable(track_id) && model.isTrackInClusteredGroup(track_id);
230 |
231 | if (model.getTrackMovable(track_id)) {
232 | $dropdown.append(OncoprintTrackOptionsView.$makeDropdownOption('Move up', 'normal', movingDisabled, function (evt) {
233 | evt.stopPropagation();
234 | self.moveUpCallback(track_id);
235 | }));
236 | $dropdown.append(OncoprintTrackOptionsView.$makeDropdownOption('Move down', 'normal', movingDisabled, function (evt) {
237 | evt.stopPropagation();
238 | self.moveDownCallback(track_id);
239 | }));
240 | }
241 | if (model.isTrackRemovable(track_id)) {
242 | $dropdown.append(OncoprintTrackOptionsView.$makeDropdownOption('Remove track', 'normal', false, function (evt) {
243 | evt.stopPropagation();
244 | self.removeCallback(track_id);
245 | }));
246 | }
247 | if (model.isTrackSortDirectionChangeable(track_id)) {
248 | $dropdown.append(OncoprintTrackOptionsView.$makeDropdownSeparator());
249 | let $sort_inc_li:JQuery;
250 | let $sort_dec_li:JQuery;
251 | let $dont_sort_li:JQuery;
252 | $sort_inc_li = OncoprintTrackOptionsView.$makeDropdownOption('Sort a-Z', (model.getTrackSortDirection(track_id) === 1 ? 'bold' : 'normal'), false, function (evt) {
253 | evt.stopPropagation();
254 | $sort_inc_li.css('font-weight', 'bold');
255 | $sort_dec_li.css('font-weight', 'normal');
256 | $dont_sort_li.css('font-weight', 'normal');
257 | self.sortChangeCallback(track_id, 1);
258 | OncoprintTrackOptionsView.renderSortArrow($sortarrow, model, track_id);
259 | });
260 | $sort_dec_li = OncoprintTrackOptionsView.$makeDropdownOption('Sort Z-a', (model.getTrackSortDirection(track_id) === -1 ? 'bold' : 'normal'), false, function (evt) {
261 | evt.stopPropagation();
262 | $sort_inc_li.css('font-weight', 'normal');
263 | $sort_dec_li.css('font-weight', 'bold');
264 | $dont_sort_li.css('font-weight', 'normal');
265 | self.sortChangeCallback(track_id, -1);
266 | OncoprintTrackOptionsView.renderSortArrow($sortarrow, model, track_id);
267 | });
268 | $dont_sort_li = OncoprintTrackOptionsView.$makeDropdownOption('Don\'t sort track', (model.getTrackSortDirection(track_id) === 0 ? 'bold' : 'normal'), false, function (evt) {
269 | evt.stopPropagation();
270 | $sort_inc_li.css('font-weight', 'normal');
271 | $sort_dec_li.css('font-weight', 'normal');
272 | $dont_sort_li.css('font-weight', 'bold');
273 | self.sortChangeCallback(track_id, 0);
274 | OncoprintTrackOptionsView.renderSortArrow($sortarrow, model, track_id);
275 | });
276 | $dropdown.append($sort_inc_li);
277 | $dropdown.append($sort_dec_li);
278 | $dropdown.append($dont_sort_li);
279 | }
280 | if (model.isTrackExpandable(track_id)) {
281 | $dropdown.append(OncoprintTrackOptionsView.$makeDropdownOption(
282 | model.getExpandButtonText(track_id),
283 | 'normal',
284 | false,
285 | function (evt) {
286 | evt.stopPropagation();
287 | // close the menu to discourage clicking again, as it
288 | // may take a moment to finish expanding
289 | self.renderAllOptions(model);
290 | model.expandTrack(track_id);
291 | }));
292 | }
293 | if (model.isTrackExpanded(track_id)) {
294 | $dropdown.append(OncoprintTrackOptionsView.$makeDropdownOption(
295 | 'Remove expansion',
296 | 'normal',
297 | false,
298 | function (evt) {
299 | evt.stopPropagation();
300 | self.unexpandCallback(track_id);
301 | }));
302 | }
303 | if (model.getTrackCanShowGaps(track_id)) {
304 | $dropdown.append(OncoprintTrackOptionsView.$makeDropdownSeparator());
305 | const $show_gaps_opt = OncoprintTrackOptionsView.$makeDropdownOption(
306 | 'Show gaps',
307 | model.getTrackShowGaps(track_id) ? 'bold' : 'normal',
308 | false,
309 | function(evt) {
310 | evt.stopPropagation();
311 | $show_gaps_opt.css('font-weight', 'bold');
312 | $dont_show_gaps_opt.css('font-weight', 'normal');
313 | self.showGapsCallback(track_id, true);
314 | }
315 | );
316 | const $dont_show_gaps_opt = OncoprintTrackOptionsView.$makeDropdownOption(
317 | "Don't show gaps",
318 | model.getTrackShowGaps(track_id) ? 'normal' : 'bold',
319 | false,
320 | function(evt) {
321 | evt.stopPropagation();
322 |
323 | $show_gaps_opt.css('font-weight', 'normal');
324 | $dont_show_gaps_opt.css('font-weight', 'bold');
325 | self.showGapsCallback(track_id, false);
326 | }
327 | );
328 | $dropdown.append($show_gaps_opt);
329 | $dropdown.append($dont_show_gaps_opt);
330 | }
331 | // Add custom options
332 | const custom_options = model.getTrackCustomOptions(track_id);
333 | if (custom_options && custom_options.length > 0) {
334 | for (var i=0; inumber) {
367 | this.rendering_suppressed = false;
368 | this.renderAllOptions(model);
369 | this.resize(model, getCellViewHeight);
370 | this.scroll(model.getVertScroll());
371 | }
372 | public setScroll(model:OncoprintModel) {
373 | this.setVertScroll(model);
374 | }
375 | public setHorzScroll(model:OncoprintModel) {
376 | }
377 | public setVertScroll(model:OncoprintModel) {
378 | this.scroll(model.getVertScroll());
379 | }
380 | public setZoom(model:OncoprintModel, getCellViewHeight:()=>number) {
381 | this.setVertZoom(model, getCellViewHeight);
382 | }
383 | public setVertZoom(model:OncoprintModel, getCellViewHeight:()=>number) {
384 | this.renderAllOptions(model);
385 | this.resize(model, getCellViewHeight);
386 | }
387 | public setTrackGroupHeader(model:OncoprintModel, getCellViewHeight:()=>number) {
388 | this.renderAllOptions(model);
389 | this.resize(model, getCellViewHeight);
390 | }
391 | public sort(model:OncoprintModel, getCellViewHeight:()=>number) {
392 | this.renderAllOptions(model);
393 | this.resize(model, getCellViewHeight);
394 | }
395 | public setViewport(model:OncoprintModel, getCellViewHeight:()=>number) {
396 | this.renderAllOptions(model);
397 | this.resize(model, getCellViewHeight);
398 | this.scroll(model.getVertScroll());
399 | }
400 | public getWidth() {
401 | if (this.$buttons_ctr.is(":empty")) {
402 | return 0;
403 | } else {
404 | return 18 + this.img_size;
405 | }
406 | }
407 | public setTrackShowGaps(model:OncoprintModel, getCellViewHeight:()=>number) {
408 | this.renderAllOptions(model);
409 | this.resize(model, getCellViewHeight);
410 | }
411 | public addTracks(model:OncoprintModel, getCellViewHeight:()=>number) {
412 | this.renderAllOptions(model);
413 | this.resize(model, getCellViewHeight);
414 | }
415 | public moveTrack(model:OncoprintModel, getCellViewHeight:()=>number) {
416 | this.renderAllOptions(model);
417 | this.resize(model, getCellViewHeight);
418 | }
419 | public setTrackGroupOrder(model:OncoprintModel) {
420 | this.renderAllOptions(model);
421 | }
422 | public setSortConfig(model:OncoprintModel) {
423 | this.renderAllOptions(model);
424 | }
425 | public removeTrack(model:OncoprintModel, track_id:TrackId, getCellViewHeight:()=>number) {
426 | delete this.track_options_$elts[track_id];
427 | this.renderAllOptions(model);
428 | this.resize(model, getCellViewHeight);
429 | }
430 | public destroy() {
431 | $(document).off("click", this.clickHandler);
432 | $(document).off(CLOSE_MENUS_EVENT);
433 | };
434 | public setTrackCustomOptions(model:OncoprintModel) {
435 | this.renderAllOptions(model);
436 | };
437 | public setTrackMovable(model:OncoprintModel) {
438 | this.renderAllOptions(model);
439 | }
440 | }
441 |
--------------------------------------------------------------------------------
/src/js/oncoprintzoomslider.ts:
--------------------------------------------------------------------------------
1 | import $ from "jquery";
2 | import MouseMoveEvent = JQuery.MouseMoveEvent;
3 | import MouseDownEvent = JQuery.MouseDownEvent;
4 |
5 | const VERTICAL = "v";
6 | const HORIZONTAL = "h";
7 |
8 | function clamp(x:number) {
9 | return Math.max(Math.min(x, 1), 0);
10 | }
11 |
12 | export type OncoprintZoomSliderParams = {
13 | btn_size: number,
14 | horizontal?: boolean,
15 | width?:number,
16 |
17 | vertical?:boolean, // either horizontal and width, or vertical and height, must be set
18 | height?:number,
19 |
20 | init_val: number,
21 | left:number,
22 | top:number,
23 | onChange:(val:number)=>void
24 | };
25 |
26 | export default class OncoprintZoomSlider {
27 | private $div:JQuery;
28 | private onChange:OncoprintZoomSliderParams["onChange"];
29 | private value:number;
30 | private slider_bar_size:number;
31 | private orientation:"v"|"h";
32 | private $slider:JQuery;
33 | private $plus_btn:JQuery;
34 | private $minus_btn:JQuery;
35 |
36 | constructor($container:JQuery, params?:Partial) {
37 | this.$div = $('').css({'position':'absolute',
38 | 'top': params.top || 0,
39 | 'left': params.left || 0}).appendTo($container);
40 | params = params || {};
41 | params.btn_size = params.btn_size || 13;
42 | this.onChange = params.onChange || function() {};
43 |
44 | this.initialize(params as OncoprintZoomSliderParams);
45 |
46 | this.value = params.init_val === undefined ? 0.5 : params.init_val;
47 | this.slider_bar_size = (this.orientation === VERTICAL ? params.height : params.width) - 2*params.btn_size;
48 | this.updateSliderPos();
49 | }
50 |
51 | private initialize(params:OncoprintZoomSliderParams) {
52 | var $ctr = this.$div;
53 | var icon_size = Math.round(params.btn_size * 0.7);
54 | var icon_padding = Math.round((params.btn_size - icon_size)/2);
55 | var $slider_bar = $('
').css({'position':'absolute',
56 | 'background-color':'#ffffff',
57 | 'outline': 'solid 1px black'}).appendTo($ctr);
58 | var $slider = $('
').css({'position':'absolute',
59 | 'background-color':'#ffffff',
60 | 'border': 'solid 1px black',
61 | 'border-radius': '3px',
62 | 'cursor': 'pointer'}).appendTo($ctr);
63 |
64 | var $plus_btn = $('
').css({'position':'absolute',
65 | 'min-height': params.btn_size,
66 | 'min-width': params.btn_size,
67 | 'background-color':'#ffffff',
68 | 'border': 'solid 1px black',
69 | 'border-radius': '3px',
70 | 'cursor': 'pointer'})
71 | .appendTo($ctr);
72 | $('
').addClass("icon fa fa-plus").css({'position':'absolute',
73 | 'top':icon_padding,
74 | 'left':icon_padding,
75 | 'min-width':icon_size,
76 | 'min-height':icon_size})
77 | .appendTo($plus_btn);
78 | var $minus_btn = $('').css({'position':'absolute',
79 | 'min-height': params.btn_size,
80 | 'min-width': params.btn_size,
81 | 'background-color':'#ffffff',
82 | 'border': 'solid 1px black',
83 | 'border-radius': '3px',
84 | 'cursor': 'pointer'})
85 | .appendTo($ctr);
86 | $('
').addClass("icon fa fa-minus").css({'position':'absolute',
87 | 'top':icon_padding,
88 | 'left':icon_padding,
89 | 'min-width':icon_size,
90 | 'min-height':icon_size})
91 | .appendTo($minus_btn);
92 | if (params.vertical) {
93 | $slider_bar.css({'min-height': params.height - 2 * params.btn_size,
94 | 'min-width': Math.round(params.btn_size / 5)});
95 | $slider.css({'min-height': Math.round(params.btn_size / 2),
96 | 'min-width': params.btn_size});
97 |
98 | $plus_btn.css({'top': 0, 'left': 0});
99 | $minus_btn.css({'top': params.height - params.btn_size, 'left': 0});
100 | $slider_bar.css({'top': params.btn_size, 'left': 0.4 * params.btn_size});
101 | $slider.css({'left': 0});
102 | this.orientation = VERTICAL;
103 | } else {
104 | $slider_bar.css({'min-height': Math.round(params.btn_size / 5),
105 | 'min-width': params.width - 2 * params.btn_size});
106 | $slider.css({'min-height': params.btn_size,
107 | 'min-width': Math.round(params.btn_size / 2)});
108 |
109 | $plus_btn.css({'top': 0, 'left': params.width - params.btn_size});
110 | $minus_btn.css({'top': 0, 'left': 0});
111 | $slider_bar.css({'top': 0.4*params.btn_size, 'left': params.btn_size});
112 | $slider.css({'top': 0});
113 | this.orientation = HORIZONTAL;
114 | }
115 |
116 | const self = this;
117 |
118 | $plus_btn.click(function() {
119 | self.value /= 0.7;
120 | params.onChange(self.value);
121 | });
122 | $minus_btn.click(function() {
123 | self.value *= 0.7;
124 | params.onChange(self.value);
125 | });
126 |
127 | [$slider, $plus_btn, $minus_btn].map(function($btn) { $btn.hover(function() {
128 | $(this).css({'background-color':'#cccccc'});
129 | }, function() {
130 | $(this).css({'background-color': '#ffffff'});
131 | }); });
132 |
133 |
134 |
135 | this.$slider = $slider;
136 | this.$plus_btn = $plus_btn;
137 | this.$minus_btn = $minus_btn;
138 |
139 | (function setUpSliderDrag() {
140 | let start_mouse:number;
141 | let start_val:number;
142 | let dragging:boolean;
143 | function handleSliderDrag(evt:MouseMoveEvent) {
144 | evt.stopPropagation();
145 | evt.preventDefault();
146 | let delta_mouse;
147 | if (self.orientation === VERTICAL) {
148 | delta_mouse = start_mouse - evt.pageY; // vertical zoom, positive is up, but CSS positive is down, so we need to invert
149 | } else {
150 | delta_mouse = evt.pageX - start_mouse;
151 | }
152 | const delta_val = delta_mouse / self.slider_bar_size;
153 | self.setSliderValue(start_val + delta_val);
154 | }
155 | function stopSliderDrag() {
156 | if (dragging && start_val !== self.value) {
157 | self.onChange(self.value);
158 | }
159 | dragging = false;
160 | }
161 | self.$slider.on("mousedown", function (evt:MouseDownEvent) {
162 | if (self.orientation === VERTICAL) {
163 | start_mouse = evt.pageY;
164 | } else {
165 | start_mouse = evt.pageX;
166 | }
167 | start_val = self.value;
168 | dragging = true;
169 | $(document).on("mousemove", handleSliderDrag);
170 | });
171 | $(document).on("mouseup click", function () {
172 | $(document).off("mousemove", handleSliderDrag);
173 | stopSliderDrag();
174 | });
175 | })()
176 | };
177 |
178 | private updateSliderPos() {
179 | const proportion = this.value;
180 | var $slider = this.$slider;
181 | var bounds = this.getSliderBounds();
182 | if (this.orientation === VERTICAL) {
183 | $slider.css('top', bounds.bottom*(1-proportion) + bounds.top*proportion);
184 | } else if (this.orientation === HORIZONTAL) {
185 | $slider.css('left', bounds.left*(1-proportion) + bounds.right*proportion);
186 | }
187 | };
188 |
189 | private getSliderBounds() {
190 | if (this.orientation === VERTICAL) {
191 | return {bottom: parseInt(this.$minus_btn.css('top'), 10) - parseInt(this.$slider.css('min-height'), 10),
192 | top: parseInt(this.$plus_btn.css('top'), 10) + parseInt(this.$plus_btn.css('min-height'), 10)};
193 | } else {
194 | return {left: parseInt(this.$minus_btn.css('left'), 10) + parseInt(this.$minus_btn.css('min-width'), 10),
195 | right: parseInt(this.$plus_btn.css('left'), 10) - parseInt(this.$slider.css('min-width'), 10)};
196 | }
197 | };
198 |
199 |
200 | public setSliderValue(proportion:number) {
201 | this.value = clamp(proportion);
202 | this.updateSliderPos();
203 | }
204 | }
205 |
--------------------------------------------------------------------------------
/src/js/polyfill.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2016 Memorial Sloan-Kettering Cancer Center.
3 | *
4 | * This library is distributed in the hope that it will be useful, but WITHOUT
5 | * ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS
6 | * FOR A PARTICULAR PURPOSE. The software and documentation provided hereunder
7 | * is on an "as is" basis, and Memorial Sloan-Kettering Cancer Center has no
8 | * obligations to provide maintenance, support, updates, enhancements or
9 | * modifications. In no event shall Memorial Sloan-Kettering Cancer Center be
10 | * liable to any party for direct, indirect, special, incidental or
11 | * consequential damages, including lost profits, arising out of the use of this
12 | * software and its documentation, even if Memorial Sloan-Kettering Cancer
13 | * Center has been advised of the possibility of such damage.
14 | */
15 |
16 | /*
17 | * This file is part of cBioPortal.
18 | *
19 | * cBioPortal is free software: you can redistribute it and/or modify
20 | * it under the terms of the GNU Affero General Public License as
21 | * published by the Free Software Foundation, either version 3 of the
22 | * License.
23 | *
24 | * This program is distributed in the hope that it will be useful,
25 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
26 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 | * GNU Affero General Public License for more details.
28 | *
29 | * You should have received a copy of the GNU Affero General Public License
30 | * along with this program. If not, see .
31 | */
32 |
33 | export type OMath = Math & { log2:(x:number)=>number };
34 | export const OMath:OMath = (Math as any);
35 |
36 | OMath.log2 = OMath.log2 || function(x:number) { return Math.log(x) / Math.LN2; };
--------------------------------------------------------------------------------
/src/js/precomputedcomparator.ts:
--------------------------------------------------------------------------------
1 | import * as BucketSort from "./bucketsort";
2 | import binarysearch from "./binarysearch";
3 | import hasElementsInInterval from "./haselementsininterval";
4 | import {
5 | ColumnId,
6 | TrackSortComparator,
7 | TrackSortDirection,
8 | TrackSortSpecification,
9 | TrackSortSpecificationComparators, TrackSortSpecificationVectors, TrackSortVector
10 | } from "./oncoprintmodel";
11 | import {SortingVector} from "./bucketsort";
12 |
13 | type DatumWithVectors = {
14 | d:T;
15 | preferred_vector:SortingVector;
16 | mandatory_vector:SortingVector;
17 | };
18 |
19 | export default class PrecomputedComparator {
20 |
21 | private preferred_change_points:number[];
22 | private mandatory_change_points:number[];
23 | private id_to_index:{[columnId:string]:number};
24 |
25 | constructor(list:T[], comparator:TrackSortSpecification, sort_direction:TrackSortDirection, element_identifier_key:string&keyof T) {
26 | if (comparator.isVector) {
27 | this.initializeVector(list, comparator, sort_direction, element_identifier_key);
28 | } else {
29 | this.initializeComparator(list, comparator as TrackSortSpecificationComparators, sort_direction, element_identifier_key);
30 | }
31 | }
32 |
33 | private initializeComparator(list:T[], comparator:TrackSortComparator | TrackSortSpecificationComparators, sort_direction:TrackSortDirection, element_identifier_key:keyof T) {
34 | // initializeComparator initializes the PrecomputedComparator in the case that
35 | // the sort order is given using a comparator
36 | let preferred, mandatory;
37 | if (typeof comparator === "function") {
38 | preferred = comparator;
39 | mandatory = comparator;
40 | } else {
41 | preferred = comparator.preferred;
42 | mandatory = comparator.mandatory;
43 | }
44 | function makeDirectedComparator(cmp:TrackSortComparator) {
45 | return function (d1:T, d2:T) {
46 | if (sort_direction === 0) {
47 | return 0;
48 | }
49 | const res = cmp(d1, d2);
50 | if (res === 2) {
51 | return 1;
52 | } else if (res === -2) {
53 | return -1;
54 | } else {
55 | return res * sort_direction;
56 | }
57 | };
58 | }
59 | const preferredComparator = makeDirectedComparator(preferred);
60 | const mandatoryComparator = makeDirectedComparator(mandatory);
61 | const sorted_list = list.sort(preferredComparator);
62 |
63 | // i is a change point iff comp(elt[i], elt[i+1]) !== 0
64 | this.preferred_change_points = [0]; // i is a preferred change pt iff its a change pt with comp = preferredComparator but not with comp = mandatoryComparator
65 | this.mandatory_change_points = [0]; // i is a mandatory change pt iff its a change pt with comp = mandatoryComparator
66 |
67 | // note that by the following process, preferred_change_points and mandatory_change_points are sorted
68 | for (let i=1; i, sort_direction:TrackSortDirection, element_identifier_key:keyof T) {
82 | // initializeVector initializes the PrecomputedComparator in the case that the sort order is specified by vectors for bucket sort
83 | function makeDirectedVector(vec:TrackSortVector) {
84 | if (sort_direction === 0) {
85 | return function(d:T) { return 0; };
86 | } else {
87 | return function(d:T) {
88 | return vec(d).map(function(n:number|string) {
89 | if (typeof n === "number") {
90 | return n * sort_direction;
91 | } else {
92 | return n;
93 | }
94 | });
95 | }
96 | }
97 | }
98 | const preferredVector = makeDirectedVector(getVector.preferred);
99 | const mandatoryVector = makeDirectedVector(getVector.mandatory);
100 |
101 | // associate each data to its vector and sort them together
102 | const list_with_vectors:DatumWithVectors[] = list.map(function(d) {
103 | return { d: d, preferred_vector: preferredVector(d), mandatory_vector: mandatoryVector(d) };
104 | }) as DatumWithVectors[];
105 | // sort by preferred vector
106 | const _compareEquals = getVector.compareEquals;
107 | const compareEquals = _compareEquals ? function(d1:DatumWithVectors, d2:DatumWithVectors) {
108 | return _compareEquals(d1.d, d2.d);
109 | } : undefined;
110 | const sorted_list = BucketSort.bucketSort(
111 | list_with_vectors,
112 | function(d) { return d.preferred_vector; },
113 | compareEquals
114 | );
115 |
116 | // i is a change point iff comp(elt[i], elt[i+1]) !== 0
117 | this.preferred_change_points = [0]; // i (besides 0) is a preferred change pt iff its a change pt with comp = preferredComparator but not with comp = mandatoryComparator
118 | this.mandatory_change_points = [0]; // i (besides 0) is a mandatory change pt iff its a change pt with comp = mandatoryComparator
119 |
120 | // note that by the following process, preferred_change_points and mandatory_change_points are sorted
121 | const getMandatoryVector = function(d:{ mandatory_vector:(number|string)[]}) { return d.mandatory_vector; };
122 | const getPreferredVector = function(d:{ preferred_vector:(number|string)[]}) { return d.preferred_vector; };
123 | for (let i=1; i= upper_excl) {
47 | break;
48 | }
49 |
50 | int middle = (lower_incl + upper_excl)/2;
51 | if (columnsRightAfterGaps[middle] < aVertexOncoprintColumn) {
52 | // G(c) > middle
53 | lower_incl = middle + 1;
54 | } else if (columnsRightAfterGaps[middle] == aVertexOncoprintColumn) {
55 | // G(c) = middle + 1
56 | numGaps = middle + 1;
57 | break;
58 | } else {
59 | // columnsRightAfterGaps[middle] > column, so G(c) <= middle
60 | if (middle == 0) {
61 | // 0 <= G(c) <= 0 -> G(c) = 0
62 | numGaps = 0;
63 | break;
64 | } else if (columnsRightAfterGaps[middle-1] < aVertexOncoprintColumn) {
65 | // G(c) = middle
66 | numGaps = middle;
67 | break;
68 | } else {
69 | // columnsRightAfterGaps[middle-1] >= column, so G(c) <= middle-1
70 | upper_excl = middle;
71 | }
72 | }
73 | }
74 |
75 | // multiply it by the gap size to get the total offset
76 | return float(numGaps)*gapSize;
77 | }
78 |
79 | void main(void) {
80 | gl_Position = vec4(getUnpackedPositionVec3(), 1.0);
81 | gl_Position[0] += aVertexOncoprintColumn*columnWidth;
82 | gl_Position *= vec4(zoomX, zoomY, 1.0, 1.0);
83 |
84 | // gaps should not be affected by zoom:
85 | gl_Position[0] += getGapOffset();
86 |
87 | // offsetY is given zoomed:
88 | gl_Position[1] += offsetY;
89 |
90 | gl_Position -= vec4(scrollX, scrollY, 0.0, 0.0);
91 | gl_Position[0] *= supersamplingRatio;
92 | gl_Position[1] *= supersamplingRatio;
93 | gl_Position = uPMatrix * uMVMatrix * gl_Position;
94 |
95 | texCoord = (aColVertex + 0.5) / texSize;
96 | }`;
97 | }
98 |
99 | export function getFragmentShaderSource() {
100 | return `precision mediump float;
101 | varying float texCoord;
102 | uniform sampler2D uSampler;
103 | void main(void) {
104 | gl_FragColor = texture2D(uSampler, vec2(texCoord, 0.5));
105 | }`;
106 | }
--------------------------------------------------------------------------------
/src/js/svgfactory.ts:
--------------------------------------------------------------------------------
1 | import makeSVGElement from './makesvgelement';
2 | import shapeToSVG from './oncoprintshapetosvg';
3 | import {ComputedShapeParams} from "./oncoprintshape";
4 | import {RGBAColor} from "./oncoprintruleset";
5 | import {rgbString} from "./utils";
6 |
7 | function makeIdCounter() {
8 | let id = 0;
9 | return function () {
10 | id += 1;
11 | return id;
12 | };
13 | }
14 |
15 | const gradientId = makeIdCounter();
16 |
17 | export default {
18 | text: function(content:string,x?:number,y?:number,size?:number,family?:string,weight?:string,alignment_baseline?:string,fill?:string,text_decoration?:string) {
19 | size = size || 12;
20 | var alignment_baseline_y_offset = size;
21 | if (alignment_baseline === "middle") {
22 | alignment_baseline_y_offset = size/2;
23 | } else if (alignment_baseline === "bottom") {
24 | alignment_baseline_y_offset = 0;
25 | }
26 | var elt = makeSVGElement('text', {
27 | 'x':(x || 0),
28 | 'y':(y || 0) + alignment_baseline_y_offset,
29 | 'font-size':size,
30 | 'font-family':(family || 'serif'),
31 | 'font-weight':(weight || 'normal'),
32 | 'text-anchor':'start',
33 | 'fill':fill,
34 | 'text-decoration':text_decoration
35 | });
36 | elt.textContent = content + '';
37 | return elt as SVGTextElement;
38 | },
39 | group: function(x:number|undefined,y:number|undefined) {
40 | x = x || 0;
41 | y = y || 0;
42 | return makeSVGElement('g', {
43 | 'transform':'translate('+x+','+y+')'
44 | }) as SVGGElement;
45 | },
46 | svg: function(width:number|undefined, height:number|undefined) {
47 | return makeSVGElement('svg', {
48 | 'width':(width || 0),
49 | 'height':(height || 0),
50 | }) as SVGSVGElement;
51 | },
52 | wrapText: function(in_dom_text_svg_elt:SVGTextElement, width:number) {
53 | const text = in_dom_text_svg_elt.textContent;
54 | in_dom_text_svg_elt.textContent = "";
55 |
56 | const words = text.split(" ");
57 | let dy = 0;
58 | let tspan = makeSVGElement('tspan', {'x':'0', 'dy':dy}) as SVGTSpanElement;
59 | in_dom_text_svg_elt.appendChild(tspan);
60 |
61 | let curr_tspan_words:string[] = [];
62 | for (var i=0; i width) {
66 | tspan.textContent = curr_tspan_words.slice(0, curr_tspan_words.length-1).join(" ");
67 | dy = in_dom_text_svg_elt.getBBox().height;
68 | curr_tspan_words = [words[i]];
69 | tspan = makeSVGElement('tspan', {'x':'0', 'dy':dy}) as SVGTSpanElement;
70 | in_dom_text_svg_elt.appendChild(tspan);
71 | tspan.textContent = words[i];
72 | }
73 | }
74 | },
75 | fromShape: function(oncoprint_shape_computed_params:ComputedShapeParams, offset_x:number, offset_y:number) {
76 | return shapeToSVG(oncoprint_shape_computed_params, offset_x, offset_y);
77 | },
78 | polygon: function(points:[number,number][], fill:RGBAColor) {
79 | return makeSVGElement('polygon', {'points': points, 'fill':rgbString(fill), 'fill-opacity':fill[3]}) as SVGPolygonElement;
80 | },
81 | rect: function(x:number,y:number,width:number,height:number,fillSpecification:{
82 | type: "rgba",
83 | value: RGBAColor
84 | }| {
85 | type: "gradientId",
86 | value: string
87 | }) {
88 | let fill:string;
89 | let fillOpacity = 1;
90 | if (fillSpecification.type === "rgba") {
91 | fill = rgbString(fillSpecification.value);
92 | fillOpacity = fillSpecification.value[3];
93 | } else {
94 | fill = `url(#${fillSpecification.value})`;
95 | }
96 | return makeSVGElement('rect', {'x':x, 'y':y, 'width':width, 'height':height, 'fill':fill, 'fill-opacity':fillOpacity}) as SVGRectElement;
97 | },
98 | bgrect: function(width:number, height:number, fill:RGBAColor) {
99 | return makeSVGElement('rect', {'width':width, 'height':height, 'fill':rgbString(fill), 'fill-opacity':fill[3]}) as SVGRectElement;
100 | },
101 | path: function(points:[number,number][], stroke:RGBAColor, fill:RGBAColor, linearGradient:SVGGradientElement) {
102 | let pointsStrArray = points.map(function(pt) { return pt.join(","); });
103 | pointsStrArray[0] = 'M'+points[0];
104 | for (var i=1; iRGBAColor) {
129 | const gradient = makeSVGElement('linearGradient', {
130 | 'id': 'gradient'+gradientId(),
131 | 'x1':0,
132 | 'y1':0,
133 | 'x2':1,
134 | 'y2':0
135 | });
136 | for (let i=0; i<=100; i++) {
137 | gradient.appendChild(
138 | this.stop(i, colorFn(i/100))
139 | );
140 | }
141 | return gradient as SVGLinearGradientElement
142 | }
143 | };
144 |
145 |
146 |
--------------------------------------------------------------------------------
/src/js/utils.ts:
--------------------------------------------------------------------------------
1 | import {ComputedShapeParams} from "./oncoprintshape";
2 | import {RGBAColor} from "./oncoprintruleset";
3 |
4 | export type Omit = Pick>;
5 |
6 | export function cloneShallow(obj:T) {
7 | const ret:Partial = {};
8 | for (const key of (Object.keys(obj) as (keyof T)[])) {
9 | ret[key] = obj[key];
10 | }
11 | return ret as T;
12 | }
13 |
14 | export function extendArray(target:any[], source:any[]) {
15 | for (let i=0; i(x:T|undefined, val:T):T {
34 | return (typeof x === "undefined" ? val : x);
35 | }
36 |
37 | export function shallowExtend(target:T, source:S):T&S {
38 | const ret:Partial = {};
39 | for (const key of Object.keys(target) as (keyof T&S)[]) {
40 | ret[key] = target[key as keyof T] as any;
41 | }
42 | for (const key of Object.keys(source) as (keyof T&S)[]) {
43 | ret[key] = source[key as keyof S] as any;
44 | }
45 | return ret as T&S;
46 | }
47 |
48 | export function objectValues(obj:T):(T[keyof T][]) {
49 | return Object.keys(obj).map(function(key:string&keyof T) { return obj[key]; });
50 | }
51 |
52 | export function arrayFindIndex(arr:T[], predicate:(t:T)=>boolean, start_index?:number) {
53 | start_index = start_index || 0;
54 | for (let i=start_index; i b) {
66 | return 1;
67 | } else {
68 | return 0;
69 | }
70 | }
71 |
72 | export function clamp(x:number, lower:number, upper:number) {
73 | return Math.max(lower, Math.min(upper, x));
74 | }
75 | export function z_comparator(shapeA:ComputedShapeParams, shapeB:ComputedShapeParams) {
76 | const zA = shapeA.z;
77 | const zB = shapeB.z;
78 | if (zA < zB) {
79 | return -1;
80 | } else if (zA > zB) {
81 | return 1;
82 | } else {
83 | return 0;
84 | }
85 | }
86 |
87 | export function fastParseInt10(x:string, substringStart?:number, substringEnd?:number) {
88 | // simple, fast parseInt when you know its a base-10 int and
89 | // you don't need any error handling.
90 | // Performance testing shows this is 85% faster than built-in parseInt
91 | substringStart = substringStart || 0;
92 | substringEnd = substringEnd || x.length;
93 | let ret = 0;
94 | for (let i=substringStart; i 96) {
111 | ret += nextCharCode - 87; // lower case letters start at 97. a is 97, should be 10
112 | } else if (nextCharCode > 64) {
113 | ret += nextCharCode - 55; // capital letters start at 65. A is 65, should be 10
114 | } else {
115 | // otherwise, its an integer
116 | ret += nextCharCode - 48;
117 | }
118 | }
119 | return ret;
120 | }
121 |
122 | export function rgbString(color:RGBAColor) {
123 | return `rgb(${color[0]},${color[1]},${color[2]})`;
124 | }
--------------------------------------------------------------------------------
/src/js/workers/clustering-worker.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2016 The Hyve B.V.
3 | * This code is licensed under the GNU Affero General Public License,
4 | * version 3, or (at your option) any later version.
5 | */
6 |
7 | /*
8 | * This file is part of cBioPortal.
9 | *
10 | * cBioPortal is free software: you can redistribute it and/or modify
11 | * it under the terms of the GNU Affero General Public License as
12 | * published by the Free Software Foundation, either version 3 of the
13 | * License.
14 | *
15 | * This program is distributed in the hope that it will be useful,
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | * GNU Affero General Public License for more details.
19 | *
20 | * You should have received a copy of the GNU Affero General Public License
21 | * along with this program. If not, see .
22 | */
23 |
24 | // @ts-ignore
25 | import clusterfck from "tayden-clusterfck";
26 | // @ts-ignore
27 | import jStat from "jstat";
28 |
29 | type Item = {
30 | orderedValueList:number[];
31 | }
32 |
33 | type ProcessedItem = Item & {
34 | isAllNaNs:boolean;
35 | preProcessedValueList:number[];
36 | }
37 |
38 | export type EntityItem = Item & { entityId: string};
39 | export type CaseItem = Item & { caseId: string };
40 |
41 | export type CasesAndEntities = {
42 | [caseId:string]:{
43 | [entityId:string]:number
44 | }
45 | };
46 |
47 | export type ClusteringMessage = {
48 | dimension: "CASES" | "ENTITIES";
49 | casesAndEntities:CasesAndEntities;
50 | };
51 |
52 |
53 | const ctx:Worker = (self as any as Worker);
54 | /**
55 | * "Routing" logic for this worker, based on given message.
56 | *
57 | * @param m : message object with m.dimension (CASES or ENTITIES) and m.casesAndEntitites
58 | * which is the input for the clustering method.
59 | */
60 | ctx.onmessage = function(m:MessageEvent) {
61 | console.log('Clustering worker received message');
62 | var result = null;
63 | if ((m.data as ClusteringMessage).dimension === "CASES") {
64 | result = hclusterCases((m.data as ClusteringMessage).casesAndEntities);
65 | } else if ((m.data as ClusteringMessage).dimension === "ENTITIES") {
66 | result = hclusterGeneticEntities((m.data as ClusteringMessage).casesAndEntities);
67 | } else {
68 | throw new Error("Illegal argument given to clustering-worker.js for m.data.dimension: " + m.data.dimension);
69 | }
70 | console.log('Posting clustering result back to main script');
71 | ctx.postMessage(result);
72 | }
73 |
74 | /**
75 | * Returns false if any value is a valid number != 0.0,
76 | * and true otherwise.
77 | */
78 | function isAllNaNs(values:any[]) {
79 | for (let i = 0; i < values.length; i++) {
80 | const val = values[i];
81 | if (!isNaN(val) && val != null && val != 0.0 ) {
82 | return false;
83 | }
84 | }
85 | return true;
86 | }
87 |
88 | /**
89 | * Distance measure using 1-spearman's correlation. This function does expect that item1 and item2
90 | * are an item than contains a item.preProcessedValueList attribute which is the ranked version
91 | * of item.orderedValueList.
92 | *
93 | */
94 | function preRankedSpearmanDist(item1:ProcessedItem, item2:ProcessedItem) {
95 | //rules for NaN values:
96 | if (item1.isAllNaNs && item2.isAllNaNs) {
97 | //return distance 0
98 | return 0;
99 | }
100 | else if (item1.isAllNaNs || item2.isAllNaNs) {
101 | //return large distance:
102 | return 3;
103 | }
104 | //take the arrays from the preProcessedValueList:
105 | var ranks1 = item1.preProcessedValueList;
106 | var ranks2 = item2.preProcessedValueList;
107 | //calculate spearman's rank correlation coefficient, using pearson's distance
108 | //for correlation of the ranks:
109 | var r = jStat.corrcoeff(ranks1, ranks2);
110 | if (isNaN(r)) {
111 | //assuming the ranks1 and ranks2 lists do not contain NaN entries (and this code DOES assume all missing values have been imputed by a valid number),
112 | //this specific scenario should not occur, unless all values are the same (and given the same rank). In this case, there is no variation, and
113 | //correlation returns NaN. In theory this could happen on small number of entities being clustered. We give this a large distance:
114 | console.log("NaN in correlation calculation");
115 | r = -2;
116 | }
117 | return 1 - r;
118 | }
119 |
120 | /**
121 | * Prepares the data for using spearman method in the distance function.
122 | * It will pre-calculate ranks and store this in inputItems[x].preProcessedValueList.
123 | * This pre-calculation significantly improves the performance of the clustering step itself.
124 | */
125 | function _prepareForDistanceFunction(inputItems:Item[]) {
126 | //pre-calculate ranks, and
127 | // split up into allNaN and notAllNaN
128 | var allNaN = [];
129 | var notAllNaN = [];
130 | for (var i = 0; i < inputItems.length; i++) {
131 | var inputItem = inputItems[i] as ProcessedItem;
132 | //check if all NaNs:
133 | inputItem.isAllNaNs = isAllNaNs(inputItem.orderedValueList);
134 | if (inputItem.isAllNaNs) {
135 | allNaN.push(inputItem);
136 | continue;
137 | } else {
138 | notAllNaN.push(inputItem);
139 | }
140 | //rank using fractional ranking:
141 | var ranks = jStat.rank(inputItem.orderedValueList);
142 | //store for later use:
143 | inputItem.preProcessedValueList = ranks;
144 | }
145 | return {
146 | notAllNaN: notAllNaN,
147 | allNaN: allNaN
148 | };
149 | }
150 |
151 |
152 |
153 | /**
154 | * @param casesAndEntitites: Object with sample(or patient)Id and map
155 | * of geneticEntity/value pairs. Example:
156 | *
157 | * var a =
158 | * {
159 | * "TCGA-AO-AA98-01":
160 | * {
161 | * "TP53": 0.045,
162 | * "BRA1": -0.89
163 | * }
164 | * },
165 | * ...
166 | *
167 | * @return the reordered list of sample(or patient) ids, after clustering.
168 | */
169 | function hclusterCases(casesAndEntitites:CasesAndEntities):CaseItem[] {
170 | var refEntityList = null;
171 | var inputItems = [];
172 | //add orderedValueList to all items, so the values are
173 | //compared in same order:
174 | for (var caseId in casesAndEntitites) {
175 | var caseObj = casesAndEntitites[caseId];
176 | var inputItem = new Object() as CaseItem;
177 | inputItem.caseId = caseId;
178 | inputItem.orderedValueList = [];
179 | if (refEntityList == null) {
180 | refEntityList = getRefList(caseObj);
181 | }
182 | for (var j = 0; j < refEntityList.length; j++) {
183 | var entityId = refEntityList[j];
184 | var value = caseObj[entityId];
185 | inputItem.orderedValueList.push(value);
186 | }
187 | inputItems.push(inputItem);
188 | }
189 | if (refEntityList.length == 1) {
190 | //this is a special case, where the "clustering" becomes a simple sorting in 1 dimension:
191 | //so, just sort and return inputItems:
192 | inputItems.sort(function (i1, i2) {
193 | var val1 = i1.orderedValueList[0];
194 | var val2 = i2.orderedValueList[0];
195 | //ensure NaNs are moved out (NaN or null which are seen here as equivalents to NA (not available)) to the end of the list:
196 | val1 = (val1 == null || isNaN(val1) ? Number.MAX_VALUE : val1);
197 | val2 = (val2 == null || isNaN(val2) ? Number.MAX_VALUE : val2);
198 | if (val1 > val2) {
199 | return 1;
200 | }
201 | else if (val1 < val2) {
202 | return -1;
203 | }
204 | return 0;
205 | });
206 | return inputItems;
207 | }
208 | //else, normal clustering:
209 | var processedInputItems = _prepareForDistanceFunction(inputItems);
210 | var clusters = clusterfck.hcluster(processedInputItems.notAllNaN, preRankedSpearmanDist);
211 | return clusters.clusters(1)[0].concat(processedInputItems.allNaN); // add all nan elements to the end post-sorting
212 | }
213 |
214 | function getRefList(caseItem:CasesAndEntities[""]) {
215 | var result = [];
216 | for (var entityId in caseItem) {
217 | result.push(entityId);
218 | }
219 | return result;
220 | }
221 |
222 | /**
223 | * @param casesAndEntitites: same as used in hclusterCases above.
224 | *
225 | * @return the reordered list of entity ids, after clustering.
226 | */
227 | function hclusterGeneticEntities(casesAndEntitites:CasesAndEntities):EntityItem[] {
228 | var refEntityList = null;
229 | var inputItems = [];
230 | var refCaseIdList = [];
231 | //add orderedValueList to all items, so the values are
232 | //compared in same order:
233 | for (var caseId in casesAndEntitites) {
234 | var caseObj = casesAndEntitites[caseId];
235 | if (refEntityList == null) {
236 | refEntityList = getRefList(caseObj);
237 | }
238 | //refCaseIdList:
239 | refCaseIdList.push(caseId);
240 | }
241 | //iterate over genes, and get sample values:
242 | for (var i = 0; i < refEntityList.length; i++) {
243 | var entityId = refEntityList[i];
244 | var inputItem = new Object() as EntityItem;
245 | inputItem.entityId = entityId;
246 | inputItem.orderedValueList = [];
247 | for (var j = 0; j < refCaseIdList.length; j++) {
248 | var caseId = refCaseIdList[j];
249 | var caseObj = casesAndEntitites[caseId];
250 | var value = caseObj[entityId];
251 | inputItem.orderedValueList.push(value);
252 | }
253 | inputItems.push(inputItem);
254 | }
255 | var processedInputItems = _prepareForDistanceFunction(inputItems);
256 | var clusters = clusterfck.hcluster(processedInputItems.notAllNaN, preRankedSpearmanDist);
257 | return clusters.clusters(1)[0].concat(processedInputItems.allNaN); // add all nan elements to the end post-sorting
258 | }
259 |
260 | export default null;
--------------------------------------------------------------------------------
/src/test/gradientCategoricalRuleset.spec.ts:
--------------------------------------------------------------------------------
1 | import OncoprintRuleSet, {RuleSetParams, RuleSetType} from "../js/oncoprintruleset";
2 | import {assert} from "chai";
3 |
4 | type Datum = {
5 | id:string;
6 | category:string|undefined;
7 | profile_data:number|null;
8 | truncation?:any;
9 | };
10 |
11 | describe("GradientCategoricalRuleSet", function() {
12 |
13 | const mixParams:RuleSetParams = {
14 | type: RuleSetType.GRADIENT_AND_CATEGORICAL,
15 | legend_label: "this is a label",
16 | value_key: "profile_data",
17 | value_range: [1,8],
18 | value_stop_points: [1,2,8],
19 | colors: [[255,0,0,1],[0,0,0,1],[0,255,0,1]],
20 | null_color: [224,224,224,1],
21 | category_key: "category"
22 | };
23 |
24 | const categoryDatum:Datum = {
25 | id:"a",
26 | category: ">8",
27 | profile_data: 8
28 | };
29 |
30 | const gradientDatumLargest:Datum = {
31 | id:"b",
32 | category: undefined,
33 | profile_data: 8
34 | };
35 |
36 | const gradientDatumSmallest:Datum = {
37 | id:"c",
38 | category: undefined,
39 | profile_data: 1
40 | };
41 |
42 | const naDatum:Datum = {
43 | id:"d",
44 | category: undefined,
45 | profile_data: null,
46 | truncation: undefined
47 | };
48 |
49 | it("Formats gradient value", function() {
50 | var mixRuleSet = OncoprintRuleSet(mixParams);
51 | var elements = mixRuleSet.getSpecificShapesForDatum([gradientDatumLargest, gradientDatumSmallest, naDatum], 12, 12, undefined, "id");
52 | assert.equal(elements.length, 3);
53 | assert.deepEqual(elements[0][0].fill,[0,255,0,1]);
54 | assert.deepEqual(elements[1][0].fill,[255,0,0,1]);
55 | assert.deepEqual(elements[2][0].fill,[224,224,224,1]);
56 | });
57 |
58 | it("Formats categorical value", function() {
59 | var mixRuleSet = OncoprintRuleSet(mixParams);
60 | var elements = mixRuleSet.getSpecificShapesForDatum([categoryDatum], 12, 12, undefined, "id");
61 | assert.equal(elements.length, 1);
62 | });
63 |
64 | it("Suppresses duplicate No Data rules", function() {
65 | var mixRuleSet = OncoprintRuleSet(mixParams);
66 | var elements = mixRuleSet.getSpecificRulesForDatum();
67 | assert.equal(elements.length, 2);
68 | });
69 |
70 | });
71 |
--------------------------------------------------------------------------------
/src/test/mocks/empty-module.js:
--------------------------------------------------------------------------------
1 | module.exports = '';
2 |
--------------------------------------------------------------------------------
/src/test/monolith.spec.ts:
--------------------------------------------------------------------------------
1 | import Oncoprint from "../js/oncoprint";
2 | import * as BucketSort from "../js/bucketsort";
3 | import binarySearch from "../js/binarysearch";
4 | import {assert} from "chai";
5 | import {doesCellIntersectPixel} from "../js/utils";
6 |
7 | describe("test", function() {
8 | it("should have oncoprint object", function() {
9 | assert.isDefined(Oncoprint);
10 | });
11 | });
12 |
13 | describe("binarySearch", function() {
14 | it("case: empty input", function() {
15 | assert.equal(binarySearch([], 0, function(x) { return x; }, true), -1);
16 | });
17 |
18 | it("case: key not found, return closest option false", function() {
19 | assert.equal(binarySearch([0,1,2], 1.5, function(x) { return x; }, false), -1);
20 | });
21 |
22 | it("case: key not found, return closest option true - should give the nearest smaller index", function() {
23 | assert.equal(binarySearch([0,1,2], -0.5, function(x) { return x; }, true), 0);
24 | assert.equal(binarySearch([0,1,2], 0.5, function(x) { return x; }, true), 0);
25 | assert.equal(binarySearch([0,1,2], 1.5, function(x) { return x; }, true), 1);
26 | assert.equal(binarySearch([0,1,2], 2.5, function(x) { return x; }, true), 2);
27 | assert.equal(binarySearch([0,1,2], 3.5, function(x) { return x; }, true), 2);
28 |
29 | assert.equal(binarySearch([0,1,2,3,4,5,6,7], -1.5, function(x) { return x; }, true), 0);
30 | assert.equal(binarySearch([0,1,2,3,4,5,6,7], -0.5, function(x) { return x; }, true), 0);
31 | assert.equal(binarySearch([0,1,2,3,4,5,6,7], 0.5, function(x) { return x; }, true), 0);
32 | assert.equal(binarySearch([0,1,2,3,4,5,6,7], 1.5, function(x) { return x; }, true), 1);
33 | assert.equal(binarySearch([0,1,2,3,4,5,6,7], 2.5, function(x) { return x; }, true), 2);
34 | assert.equal(binarySearch([0,1,2,3,4,5,6,7], 3.5, function(x) { return x; }, true), 3);
35 | assert.equal(binarySearch([0,1,2,3,4,5,6,7], 4.5, function(x) { return x; }, true), 4);
36 | assert.equal(binarySearch([0,1,2,3,4,5,6,7], 5.5, function(x) { return x; }, true), 5);
37 | assert.equal(binarySearch([0,1,2,3,4,5,6,7], 6.5, function(x) { return x; }, true), 6);
38 | assert.equal(binarySearch([0,1,2,3,4,5,6,7], 7.5, function(x) { return x; }, true), 7);
39 | assert.equal(binarySearch([0,1,2,3,4,5,6,7], 8.5, function(x) { return x; }, true), 7);
40 | });
41 |
42 | it("case: key found", function() {
43 | assert.equal(binarySearch([0,1,2], 0, function(x) { return x*x; }, true), 0);
44 | assert.equal(binarySearch([0,1,2], 1, function(x) { return x*x; }, true), 1);
45 | assert.equal(binarySearch([0,1,2], 4, function(x) { return x*x; }, true), 2);
46 |
47 | assert.equal(binarySearch([0,1,2,3,4,5,6,7], 0, function(x) { return x*x; }, true), 0);
48 | assert.equal(binarySearch([0,1,2,3,4,5,6,7], 1, function(x) { return x*x; }, true), 1);
49 | assert.equal(binarySearch([0,1,2,3,4,5,6,7], 4, function(x) { return x*x; }, true), 2);
50 | assert.equal(binarySearch([0,1,2,3,4,5,6,7], 9, function(x) { return x*x; }, true), 3);
51 | assert.equal(binarySearch([0,1,2,3,4,5,6,7], 16, function(x) { return x*x; }, true), 4);
52 | assert.equal(binarySearch([0,1,2,3,4,5,6,7], 25, function(x) { return x*x; }, true), 5);
53 | assert.equal(binarySearch([0,1,2,3,4,5,6,7], 36, function(x) { return x*x; }, true), 6);
54 | assert.equal(binarySearch([0,1,2,3,4,5,6,7], 49, function(x) { return x*x; }, true), 7);
55 | });
56 | });
57 |
58 | describe("bucketSort", function() {
59 | describe("bucketSort", function() {
60 | it("case: empty input", function() {
61 | assert.deepEqual(
62 | BucketSort.bucketSort([]),
63 | []
64 | );
65 | });
66 | it("case: size 1 vectors", function() {
67 | assert.deepEqual(
68 | BucketSort.bucketSort([[3],[1],[2]]),
69 | [[1],[2],[3]]
70 | );
71 | });
72 | it("case: sorting with infinity", function() {
73 | assert.deepEqual(
74 | BucketSort.bucketSort([[Number.POSITIVE_INFINITY, 0], [0, 1]]),
75 | [[0,1],[Number.POSITIVE_INFINITY, 0]]
76 | );
77 | assert.deepEqual(
78 | BucketSort.bucketSort([[0, 0], [Number.NEGATIVE_INFINITY, 1]]),
79 | [[Number.NEGATIVE_INFINITY,1],[0, 0]]
80 | );
81 | });
82 | it("case: simple size 4 vectors", function() {
83 | assert.deepEqual(
84 | BucketSort.bucketSort([
85 | [3,3,3,3],
86 | [0,0,0,0],
87 | [2,2,2,2],
88 | [1,1,1,1]
89 | ]),
90 | [
91 | [0,0,0,0],
92 | [1,1,1,1],
93 | [2,2,2,2],
94 | [3,3,3,3]
95 | ]
96 | );
97 | });
98 | it("case: general size 4 vectors", function() {
99 | assert.deepEqual(
100 | BucketSort.bucketSort([
101 | [13,10,-11,5],
102 | [-8,1,-8,-24],
103 | [-12, 23,-17,15],
104 | [-21,6,11,4],
105 | [12,22,21,8]
106 | ]),
107 | [
108 | [-21,6,11,4],
109 | [-12, 23,-17,15],
110 | [-8,1,-8,-24],
111 | [12,22,21,8],
112 | [13,10,-11,5]
113 | ]
114 | );
115 |
116 | assert.deepEqual(
117 | BucketSort.bucketSort([
118 | [13,10,-11,5],
119 | [13,1,-8,-24],
120 | [-12, 23,-17,15],
121 | [-12,6,11,4],
122 | [12,22,21,8]
123 | ]),
124 | [
125 | [-12,6,11,4],
126 | [-12, 23,-17,15],
127 | [12,22,21,8],
128 | [13,1,-8,-24],
129 | [13,10,-11,5]
130 | ]
131 | );
132 | assert.deepEqual(
133 | BucketSort.bucketSort([
134 | [0,10,-11,5],
135 | [0,1,-8,-24],
136 | [0, 23,-17,15],
137 | [0,6,11,4],
138 | [0,22,21,8]
139 | ]),
140 | [
141 | [0,1,-8,-24],
142 | [0,6,11,4],
143 | [0,10,-11,5],
144 | [0,22,21,8],
145 | [0, 23,-17,15]
146 | ]
147 | );
148 | assert.deepEqual(
149 | BucketSort.bucketSort([
150 | [0,10,-11,5],
151 | [0,10],
152 | [0],
153 | [0,10,-11]
154 | ]),
155 | [
156 | [0],
157 | [0,10],
158 | [0,10,-11],
159 | [0,10,-11,5]
160 | ]
161 | );
162 | });
163 | it("case: size 2 vectors with compareEquals on the sample id", function() {
164 | assert.deepEqual(
165 | BucketSort.bucketSort([
166 | {sample:"D", vector:[13,8]},
167 | {sample:"A", vector:[13,10]},
168 | {sample:"C", vector:[12,10]}
169 | ], function(d) { return d.vector; }, function(d1, d2) { return d1.sample.localeCompare(d2.sample); }),
170 | [
171 | {sample:"C", vector:[12,10]},
172 | {sample:"D", vector:[13,8]},
173 | {sample:"A", vector:[13,10]}
174 | ]
175 | );
176 |
177 | assert.deepEqual(
178 | BucketSort.bucketSort([
179 | {sample:"D", vector:[13,10]},
180 | {sample:"A", vector:[13,10]},
181 | {sample:"C", vector:[12,10]}
182 | ], function(d) { return d.vector; }, function(d1, d2) { return d1.sample.localeCompare(d2.sample); }),
183 | [
184 | {sample:"C", vector:[12,10]},
185 | {sample:"A", vector:[13,10]},
186 | {sample:"D", vector:[13,10]}
187 | ]
188 | );
189 | });
190 | it("case: randomized tests", function() {
191 | function randInt(magnitude:number) {
192 | var ret = Math.round(Math.random()*magnitude - Math.random()*magnitude);
193 | if (ret === 0) {
194 | // to deal w issues w negative zero
195 | ret = 0;
196 | }
197 | return ret;
198 | }
199 |
200 | function generateVector(size:number) {
201 | var ret = [];
202 | var vectorSize = size+Math.round(Math.random()*4);
203 | for (var i=0; i
2 |
3 |
4 |
5 |
6 |
7 |
8 | Oncoprint Genomic Alterations
9 |
10 | Oncoprint Heatmap
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |