oncoprintjs 5.0.3 → 6.0.1
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.
- package/README.md +34 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.es.js +14746 -0
- package/dist/index.es.js.map +1 -0
- package/dist/index.js +14760 -0
- package/dist/index.js.map +1 -0
- package/dist/js/CachedProperty.d.ts +10 -10
- package/dist/js/binarysearch.d.ts +1 -1
- package/dist/js/bucketsort.d.ts +16 -16
- package/dist/js/clustering.d.ts +14 -14
- package/dist/js/extractrgba.d.ts +4 -4
- package/dist/js/haselementsininterval.d.ts +1 -1
- package/dist/js/heatmapcolors.d.ts +5 -4
- package/dist/js/makesvgelement.d.ts +1 -1
- package/dist/js/modelutils.d.ts +7 -7
- package/dist/js/oncoprint.d.ts +168 -170
- package/dist/js/oncoprintheaderview.d.ts +23 -22
- package/dist/js/oncoprintlabelview.d.ts +79 -78
- package/dist/js/oncoprintlegendrenderer.d.ts +32 -31
- package/dist/js/oncoprintminimapview.d.ts +69 -68
- package/dist/js/oncoprintmodel.d.ts +403 -398
- package/dist/js/oncoprintruleset.d.ts +176 -177
- package/dist/js/oncoprintshape.d.ts +67 -67
- package/dist/js/oncoprintshapetosvg.d.ts +2 -2
- package/dist/js/oncoprintshapetovertexes.d.ts +5 -5
- package/dist/js/oncoprinttooltip.d.ts +23 -22
- package/dist/js/oncoprinttrackinfoview.d.ts +40 -39
- package/dist/js/oncoprinttrackoptionsview.d.ts +58 -57
- package/dist/js/oncoprintwebglcellview.d.ts +168 -167
- package/dist/js/oncoprintzoomslider.d.ts +28 -27
- package/dist/js/polyfill.d.ts +4 -4
- package/dist/js/precomputedcomparator.d.ts +13 -13
- package/dist/js/shaders.d.ts +2 -2
- package/dist/js/svgfactory.d.ts +24 -23
- package/dist/js/utils.d.ts +16 -16
- package/dist/js/workers/clustering-worker.d.ts +19 -20
- package/dist/test/gradientCategoricalRuleset.spec.d.ts +1 -1
- package/dist/test/monolith.spec.d.ts +1 -1
- package/jest.config.ts +2 -0
- package/package.json +20 -26
- package/rollup.config.ts +14 -0
- package/rules/geneticrules.ts +344 -305
- package/server.js +11 -0
- package/src/img/menudots.svg +9 -9
- package/src/img/zoomtofit.svg +12 -12
- package/src/index.tsx +13 -0
- package/src/js/CachedProperty.ts +6 -7
- package/src/js/binarysearch.ts +8 -3
- package/src/js/bucketsort.ts +89 -47
- package/src/js/clustering.ts +22 -10
- package/src/js/extractrgba.ts +16 -12
- package/src/js/haselementsininterval.ts +8 -4
- package/src/js/heatmapcolors.ts +515 -515
- package/src/js/main.js +1 -1
- package/src/js/makesvgelement.ts +2 -2
- package/src/js/modelutils.ts +11 -8
- package/src/js/oncoprint.ts +706 -385
- package/src/js/oncoprintheaderview.ts +165 -125
- package/src/js/oncoprintlabelview.ts +388 -170
- package/src/js/oncoprintlegendrenderer.ts +203 -72
- package/src/js/oncoprintminimapview.ts +965 -423
- package/src/js/oncoprintmodel.ts +905 -532
- package/src/js/oncoprintruleset.ts +694 -379
- package/src/js/oncoprintshape.ts +240 -97
- package/src/js/oncoprintshapetosvg.ts +77 -26
- package/src/js/oncoprintshapetovertexes.ts +153 -48
- package/src/js/oncoprinttooltip.ts +58 -27
- package/src/js/oncoprinttrackinfoview.ts +115 -59
- package/src/js/oncoprinttrackoptionsview.ts +354 -187
- package/src/js/oncoprintwebglcellview.ts +951 -415
- package/src/js/oncoprintzoomslider.ts +172 -107
- package/src/js/polyfill.ts +7 -3
- package/src/js/precomputedcomparator.ts +133 -50
- package/src/js/shaders.ts +2 -4
- package/src/js/svgfactory.ts +128 -73
- package/src/js/utils.ts +51 -31
- package/src/js/workers/clustering-worker.ts +50 -42
- package/src/test/gradientCategoricalRuleset.spec.ts +55 -38
- package/src/test/monolith.spec.ts +718 -285
- package/test/generate_data.py +108 -0
- package/test/glyphmap-data.js +1041 -0
- package/test/heatmap-data.js +1027 -0
- package/test/index.html +21 -0
- package/test/oncoprint-glyphmap.js +79 -0
- package/test/oncoprint-heatmap.js +123 -0
- package/tsconfig.json +4 -10
- package/tsconfig.test.json +11 -0
- package/.idea/misc.xml +0 -6
- package/.idea/modules.xml +0 -8
- package/.idea/oncoprintjs.iml +0 -12
- package/.idea/vcs.xml +0 -6
- package/.idea/workspace.xml +0 -105
- package/dist/.gitkeep +0 -0
- package/dist/js/minimaputils.d.ts +0 -0
- package/dist/oncoprint.bundle.js +0 -44313
- package/jest.config.js +0 -12
- package/src/js/minimaputils.ts +0 -0
- package/typings/custom.d.ts +0 -7
- package/typings/missing.d.ts +0 -7
- package/webpack.config.js +0 -43
package/src/js/svgfactory.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import makeSVGElement from './makesvgelement';
|
|
2
2
|
import shapeToSVG from './oncoprintshapetosvg';
|
|
3
|
-
import {ComputedShapeParams} from
|
|
4
|
-
import {RGBAColor} from
|
|
5
|
-
import {rgbString} from
|
|
3
|
+
import { ComputedShapeParams } from './oncoprintshape';
|
|
4
|
+
import { RGBAColor } from './oncoprintruleset';
|
|
5
|
+
import { rgbString } from './utils';
|
|
6
6
|
|
|
7
7
|
function makeIdCounter() {
|
|
8
8
|
let id = 0;
|
|
9
|
-
return function
|
|
9
|
+
return function() {
|
|
10
10
|
id += 1;
|
|
11
11
|
return id;
|
|
12
12
|
};
|
|
@@ -15,131 +15,186 @@ function makeIdCounter() {
|
|
|
15
15
|
const gradientId = makeIdCounter();
|
|
16
16
|
|
|
17
17
|
export default {
|
|
18
|
-
text: function(
|
|
18
|
+
text: function(
|
|
19
|
+
content: string,
|
|
20
|
+
x?: number,
|
|
21
|
+
y?: number,
|
|
22
|
+
size?: number,
|
|
23
|
+
family?: string,
|
|
24
|
+
weight?: string,
|
|
25
|
+
alignment_baseline?: string,
|
|
26
|
+
fill?: string,
|
|
27
|
+
text_decoration?: string
|
|
28
|
+
) {
|
|
19
29
|
size = size || 12;
|
|
20
30
|
var alignment_baseline_y_offset = size;
|
|
21
|
-
if (alignment_baseline ===
|
|
22
|
-
alignment_baseline_y_offset = size/2;
|
|
23
|
-
} else if (alignment_baseline ===
|
|
31
|
+
if (alignment_baseline === 'middle') {
|
|
32
|
+
alignment_baseline_y_offset = size / 2;
|
|
33
|
+
} else if (alignment_baseline === 'bottom') {
|
|
24
34
|
alignment_baseline_y_offset = 0;
|
|
25
35
|
}
|
|
26
36
|
var elt = makeSVGElement('text', {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
'font-size':size,
|
|
30
|
-
'font-family':
|
|
31
|
-
'font-weight':
|
|
32
|
-
'text-anchor':'start',
|
|
33
|
-
|
|
34
|
-
'text-decoration':text_decoration
|
|
37
|
+
x: x || 0,
|
|
38
|
+
y: (y || 0) + alignment_baseline_y_offset,
|
|
39
|
+
'font-size': size,
|
|
40
|
+
'font-family': family || 'serif',
|
|
41
|
+
'font-weight': weight || 'normal',
|
|
42
|
+
'text-anchor': 'start',
|
|
43
|
+
fill: fill,
|
|
44
|
+
'text-decoration': text_decoration,
|
|
35
45
|
});
|
|
36
46
|
elt.textContent = content + '';
|
|
37
47
|
return elt as SVGTextElement;
|
|
38
48
|
},
|
|
39
|
-
group: function(x:number|undefined,y:number|undefined) {
|
|
49
|
+
group: function(x: number | undefined, y: number | undefined) {
|
|
40
50
|
x = x || 0;
|
|
41
51
|
y = y || 0;
|
|
42
52
|
return makeSVGElement('g', {
|
|
43
|
-
|
|
53
|
+
transform: 'translate(' + x + ',' + y + ')',
|
|
44
54
|
}) as SVGGElement;
|
|
45
55
|
},
|
|
46
|
-
svg: function(width:number|undefined, height:number|undefined) {
|
|
56
|
+
svg: function(width: number | undefined, height: number | undefined) {
|
|
47
57
|
return makeSVGElement('svg', {
|
|
48
|
-
|
|
49
|
-
|
|
58
|
+
width: width || 0,
|
|
59
|
+
height: height || 0,
|
|
50
60
|
}) as SVGSVGElement;
|
|
51
61
|
},
|
|
52
|
-
wrapText: function(in_dom_text_svg_elt:SVGTextElement, width:number) {
|
|
62
|
+
wrapText: function(in_dom_text_svg_elt: SVGTextElement, width: number) {
|
|
53
63
|
const text = in_dom_text_svg_elt.textContent;
|
|
54
|
-
in_dom_text_svg_elt.textContent =
|
|
64
|
+
in_dom_text_svg_elt.textContent = '';
|
|
55
65
|
|
|
56
|
-
const words = text.split(
|
|
66
|
+
const words = text.split(' ');
|
|
57
67
|
let dy = 0;
|
|
58
|
-
let tspan = makeSVGElement('tspan', {
|
|
68
|
+
let tspan = makeSVGElement('tspan', {
|
|
69
|
+
x: '0',
|
|
70
|
+
dy: dy,
|
|
71
|
+
}) as SVGTSpanElement;
|
|
59
72
|
in_dom_text_svg_elt.appendChild(tspan);
|
|
60
73
|
|
|
61
|
-
let curr_tspan_words:string[] = [];
|
|
62
|
-
for (var i=0; i<words.length; i++) {
|
|
74
|
+
let curr_tspan_words: string[] = [];
|
|
75
|
+
for (var i = 0; i < words.length; i++) {
|
|
63
76
|
curr_tspan_words.push(words[i]);
|
|
64
|
-
tspan.textContent = curr_tspan_words.join(
|
|
77
|
+
tspan.textContent = curr_tspan_words.join(' ');
|
|
65
78
|
if (tspan.getComputedTextLength() > width) {
|
|
66
|
-
tspan.textContent = curr_tspan_words
|
|
79
|
+
tspan.textContent = curr_tspan_words
|
|
80
|
+
.slice(0, curr_tspan_words.length - 1)
|
|
81
|
+
.join(' ');
|
|
67
82
|
dy = in_dom_text_svg_elt.getBBox().height;
|
|
68
83
|
curr_tspan_words = [words[i]];
|
|
69
|
-
tspan = makeSVGElement('tspan', {
|
|
84
|
+
tspan = makeSVGElement('tspan', {
|
|
85
|
+
x: '0',
|
|
86
|
+
dy: dy,
|
|
87
|
+
}) as SVGTSpanElement;
|
|
70
88
|
in_dom_text_svg_elt.appendChild(tspan);
|
|
71
89
|
tspan.textContent = words[i];
|
|
72
90
|
}
|
|
73
91
|
}
|
|
74
92
|
},
|
|
75
|
-
fromShape: function(
|
|
93
|
+
fromShape: function(
|
|
94
|
+
oncoprint_shape_computed_params: ComputedShapeParams,
|
|
95
|
+
offset_x: number,
|
|
96
|
+
offset_y: number
|
|
97
|
+
) {
|
|
76
98
|
return shapeToSVG(oncoprint_shape_computed_params, offset_x, offset_y);
|
|
77
99
|
},
|
|
78
|
-
polygon: function(points:[number,number][], fill:RGBAColor) {
|
|
79
|
-
return makeSVGElement('polygon', {
|
|
100
|
+
polygon: function(points: [number, number][], fill: RGBAColor) {
|
|
101
|
+
return makeSVGElement('polygon', {
|
|
102
|
+
points: points,
|
|
103
|
+
fill: rgbString(fill),
|
|
104
|
+
'fill-opacity': fill[3],
|
|
105
|
+
}) as SVGPolygonElement;
|
|
80
106
|
},
|
|
81
|
-
rect: function(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
107
|
+
rect: function(
|
|
108
|
+
x: number,
|
|
109
|
+
y: number,
|
|
110
|
+
width: number,
|
|
111
|
+
height: number,
|
|
112
|
+
fillSpecification:
|
|
113
|
+
| {
|
|
114
|
+
type: 'rgba';
|
|
115
|
+
value: RGBAColor;
|
|
116
|
+
}
|
|
117
|
+
| {
|
|
118
|
+
type: 'gradientId';
|
|
119
|
+
value: string;
|
|
120
|
+
}
|
|
121
|
+
) {
|
|
122
|
+
let fill: string;
|
|
89
123
|
let fillOpacity = 1;
|
|
90
|
-
if (fillSpecification.type ===
|
|
124
|
+
if (fillSpecification.type === 'rgba') {
|
|
91
125
|
fill = rgbString(fillSpecification.value);
|
|
92
126
|
fillOpacity = fillSpecification.value[3];
|
|
93
127
|
} else {
|
|
94
128
|
fill = `url(#${fillSpecification.value})`;
|
|
95
129
|
}
|
|
96
|
-
return makeSVGElement('rect', {
|
|
130
|
+
return makeSVGElement('rect', {
|
|
131
|
+
x: x,
|
|
132
|
+
y: y,
|
|
133
|
+
width: width,
|
|
134
|
+
height: height,
|
|
135
|
+
fill: fill,
|
|
136
|
+
'fill-opacity': fillOpacity,
|
|
137
|
+
}) as SVGRectElement;
|
|
97
138
|
},
|
|
98
|
-
bgrect: function(width:number, height:number, fill:RGBAColor) {
|
|
99
|
-
return makeSVGElement('rect', {
|
|
139
|
+
bgrect: function(width: number, height: number, fill: RGBAColor) {
|
|
140
|
+
return makeSVGElement('rect', {
|
|
141
|
+
width: width,
|
|
142
|
+
height: height,
|
|
143
|
+
fill: rgbString(fill),
|
|
144
|
+
'fill-opacity': fill[3],
|
|
145
|
+
}) as SVGRectElement;
|
|
100
146
|
},
|
|
101
|
-
path: function(
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
147
|
+
path: function(
|
|
148
|
+
points: [number, number][],
|
|
149
|
+
stroke: RGBAColor,
|
|
150
|
+
fill: RGBAColor,
|
|
151
|
+
linearGradient: SVGGradientElement
|
|
152
|
+
) {
|
|
153
|
+
let pointsStrArray = points.map(function(pt) {
|
|
154
|
+
return pt.join(',');
|
|
155
|
+
});
|
|
156
|
+
pointsStrArray[0] = 'M' + points[0];
|
|
157
|
+
for (var i = 1; i < points.length; i++) {
|
|
158
|
+
pointsStrArray[i] = 'L' + points[i];
|
|
106
159
|
}
|
|
107
160
|
return makeSVGElement('path', {
|
|
108
|
-
|
|
109
|
-
|
|
161
|
+
d: pointsStrArray.join(' '),
|
|
162
|
+
stroke: linearGradient
|
|
163
|
+
? 'url(#' + linearGradient.getAttribute('id') + ')'
|
|
164
|
+
: rgbString(stroke),
|
|
110
165
|
'stroke-opacity': linearGradient ? 0 : stroke[3],
|
|
111
|
-
|
|
112
|
-
|
|
166
|
+
fill: linearGradient
|
|
167
|
+
? 'url(#' + linearGradient.getAttribute('id') + ')'
|
|
168
|
+
: rgbString(fill),
|
|
169
|
+
'fill-opacity': linearGradient ? 1 : fill[3],
|
|
113
170
|
}) as SVGPathElement;
|
|
114
171
|
},
|
|
115
|
-
stop: function
|
|
172
|
+
stop: function(offset: number, color: RGBAColor) {
|
|
116
173
|
return makeSVGElement('stop', {
|
|
117
|
-
|
|
174
|
+
offset: offset + '%',
|
|
118
175
|
'stop-color': rgbString(color),
|
|
119
|
-
'stop-opacity': color[3]
|
|
176
|
+
'stop-opacity': color[3],
|
|
120
177
|
}) as SVGStopElement;
|
|
121
178
|
},
|
|
122
|
-
linearGradient: function
|
|
123
|
-
return makeSVGElement('linearGradient', {
|
|
179
|
+
linearGradient: function() {
|
|
180
|
+
return makeSVGElement('linearGradient', {
|
|
181
|
+
id: 'linearGradient' + gradientId(),
|
|
182
|
+
}) as SVGLinearGradientElement;
|
|
124
183
|
},
|
|
125
184
|
defs: function() {
|
|
126
185
|
return makeSVGElement('defs', {}) as SVGDefsElement;
|
|
127
186
|
},
|
|
128
|
-
gradient: function(colorFn:(val:number)=>RGBAColor) {
|
|
187
|
+
gradient: function(colorFn: (val: number) => RGBAColor) {
|
|
129
188
|
const gradient = makeSVGElement('linearGradient', {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
189
|
+
id: 'gradient' + gradientId(),
|
|
190
|
+
x1: 0,
|
|
191
|
+
y1: 0,
|
|
192
|
+
x2: 1,
|
|
193
|
+
y2: 0,
|
|
135
194
|
});
|
|
136
|
-
for (let i=0; i<=100; i++) {
|
|
137
|
-
gradient.appendChild(
|
|
138
|
-
this.stop(i, colorFn(i/100))
|
|
139
|
-
);
|
|
195
|
+
for (let i = 0; i <= 100; i++) {
|
|
196
|
+
gradient.appendChild(this.stop(i, colorFn(i / 100)));
|
|
140
197
|
}
|
|
141
|
-
return gradient as SVGLinearGradientElement
|
|
142
|
-
}
|
|
198
|
+
return gradient as SVGLinearGradientElement;
|
|
199
|
+
},
|
|
143
200
|
};
|
|
144
|
-
|
|
145
|
-
|
package/src/js/utils.ts
CHANGED
|
@@ -1,26 +1,30 @@
|
|
|
1
|
-
import {ComputedShapeParams} from
|
|
2
|
-
import {RGBAColor} from
|
|
1
|
+
import { ComputedShapeParams } from './oncoprintshape';
|
|
2
|
+
import { RGBAColor } from './oncoprintruleset';
|
|
3
3
|
|
|
4
4
|
export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
|
|
5
5
|
|
|
6
|
-
export function cloneShallow<T extends Object>(obj:T) {
|
|
7
|
-
const ret:Partial<T> = {};
|
|
8
|
-
for (const key of
|
|
6
|
+
export function cloneShallow<T extends Object>(obj: T) {
|
|
7
|
+
const ret: Partial<T> = {};
|
|
8
|
+
for (const key of Object.keys(obj) as (keyof T)[]) {
|
|
9
9
|
ret[key] = obj[key];
|
|
10
10
|
}
|
|
11
11
|
return ret as T;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
export function extendArray(target:any[], source:any[]) {
|
|
15
|
-
for (let i=0; i<source.length; i++) {
|
|
14
|
+
export function extendArray(target: any[], source: any[]) {
|
|
15
|
+
for (let i = 0; i < source.length; i++) {
|
|
16
16
|
target.push(source[i]);
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
export function doesCellIntersectPixel(
|
|
20
|
+
export function doesCellIntersectPixel(
|
|
21
|
+
cellHitzone: [number, number],
|
|
22
|
+
pixelX: number
|
|
23
|
+
) {
|
|
21
24
|
// checks intersection with the half-open interval [pixelX, pixelX+1)
|
|
22
25
|
|
|
23
|
-
const lower = cellHitzone[0],
|
|
26
|
+
const lower = cellHitzone[0],
|
|
27
|
+
upper = cellHitzone[1];
|
|
24
28
|
if (upper < pixelX) {
|
|
25
29
|
return false;
|
|
26
30
|
} else if (lower < pixelX + 1) {
|
|
@@ -30,28 +34,37 @@ export function doesCellIntersectPixel(cellHitzone:[number, number], pixelX:numb
|
|
|
30
34
|
}
|
|
31
35
|
}
|
|
32
36
|
|
|
33
|
-
export function ifndef<T>(x:T|undefined, val:T):T {
|
|
34
|
-
return
|
|
37
|
+
export function ifndef<T>(x: T | undefined, val: T): T {
|
|
38
|
+
return typeof x === 'undefined' ? val : x;
|
|
35
39
|
}
|
|
36
40
|
|
|
37
|
-
export function shallowExtend<T extends Object, S extends Object>(
|
|
38
|
-
|
|
39
|
-
|
|
41
|
+
export function shallowExtend<T extends Object, S extends Object>(
|
|
42
|
+
target: T,
|
|
43
|
+
source: S
|
|
44
|
+
): T & S {
|
|
45
|
+
const ret: Partial<T & S> = {};
|
|
46
|
+
for (const key of Object.keys(target) as (keyof T & S)[]) {
|
|
40
47
|
ret[key] = target[key as keyof T] as any;
|
|
41
48
|
}
|
|
42
|
-
for (const key of Object.keys(source) as (keyof T&S)[]) {
|
|
49
|
+
for (const key of Object.keys(source) as (keyof T & S)[]) {
|
|
43
50
|
ret[key] = source[key as keyof S] as any;
|
|
44
51
|
}
|
|
45
|
-
return ret as T&S;
|
|
52
|
+
return ret as T & S;
|
|
46
53
|
}
|
|
47
54
|
|
|
48
|
-
export function objectValues<T extends Object>(obj:T):
|
|
49
|
-
return Object.keys(obj).map(function(key:string&keyof T) {
|
|
55
|
+
export function objectValues<T extends Object>(obj: T): T[keyof T][] {
|
|
56
|
+
return Object.keys(obj).map(function(key: string & keyof T) {
|
|
57
|
+
return obj[key];
|
|
58
|
+
});
|
|
50
59
|
}
|
|
51
60
|
|
|
52
|
-
export function arrayFindIndex<T>(
|
|
61
|
+
export function arrayFindIndex<T>(
|
|
62
|
+
arr: T[],
|
|
63
|
+
predicate: (t: T) => boolean,
|
|
64
|
+
start_index?: number
|
|
65
|
+
) {
|
|
53
66
|
start_index = start_index || 0;
|
|
54
|
-
for (let i=start_index; i<arr.length; i++) {
|
|
67
|
+
for (let i = start_index; i < arr.length; i++) {
|
|
55
68
|
if (predicate(arr[i])) {
|
|
56
69
|
return i;
|
|
57
70
|
}
|
|
@@ -59,7 +72,7 @@ export function arrayFindIndex<T>(arr:T[], predicate:(t:T)=>boolean, start_index
|
|
|
59
72
|
return -1;
|
|
60
73
|
}
|
|
61
74
|
|
|
62
|
-
export function sgndiff(a:number, b:number) {
|
|
75
|
+
export function sgndiff(a: number, b: number) {
|
|
63
76
|
if (a < b) {
|
|
64
77
|
return -1;
|
|
65
78
|
} else if (a > b) {
|
|
@@ -69,10 +82,13 @@ export function sgndiff(a:number, b:number) {
|
|
|
69
82
|
}
|
|
70
83
|
}
|
|
71
84
|
|
|
72
|
-
export function clamp(x:number, lower:number, upper:number) {
|
|
85
|
+
export function clamp(x: number, lower: number, upper: number) {
|
|
73
86
|
return Math.max(lower, Math.min(upper, x));
|
|
74
87
|
}
|
|
75
|
-
export function z_comparator(
|
|
88
|
+
export function z_comparator(
|
|
89
|
+
shapeA: ComputedShapeParams,
|
|
90
|
+
shapeB: ComputedShapeParams
|
|
91
|
+
) {
|
|
76
92
|
const zA = shapeA.z;
|
|
77
93
|
const zB = shapeB.z;
|
|
78
94
|
if (zA < zB) {
|
|
@@ -84,27 +100,31 @@ export function z_comparator(shapeA:ComputedShapeParams, shapeB:ComputedShapePar
|
|
|
84
100
|
}
|
|
85
101
|
}
|
|
86
102
|
|
|
87
|
-
export function fastParseInt10(
|
|
103
|
+
export function fastParseInt10(
|
|
104
|
+
x: string,
|
|
105
|
+
substringStart?: number,
|
|
106
|
+
substringEnd?: number
|
|
107
|
+
) {
|
|
88
108
|
// simple, fast parseInt when you know its a base-10 int and
|
|
89
109
|
// you don't need any error handling.
|
|
90
110
|
// Performance testing shows this is 85% faster than built-in parseInt
|
|
91
111
|
substringStart = substringStart || 0;
|
|
92
112
|
substringEnd = substringEnd || x.length;
|
|
93
113
|
let ret = 0;
|
|
94
|
-
for (let i=substringStart; i<substringEnd; i++){
|
|
114
|
+
for (let i = substringStart; i < substringEnd; i++) {
|
|
95
115
|
ret *= 10;
|
|
96
|
-
ret += x.charCodeAt(i)-48; // the integer character codes from 0 to 9 are 48, 49, ..., 58
|
|
116
|
+
ret += x.charCodeAt(i) - 48; // the integer character codes from 0 to 9 are 48, 49, ..., 58
|
|
97
117
|
}
|
|
98
118
|
return ret;
|
|
99
119
|
}
|
|
100
120
|
|
|
101
|
-
export function fastParseInt16(x:string) {
|
|
121
|
+
export function fastParseInt16(x: string) {
|
|
102
122
|
// simple, fast parseInt when you know its a base-16 int and
|
|
103
123
|
// you don't need any error handling.
|
|
104
124
|
// Performance testing shows this is 43% faster than parseInt(x,16)
|
|
105
125
|
let ret = 0;
|
|
106
|
-
let nextCharCode:number;
|
|
107
|
-
for (let i=0; i<x.length; i++) {
|
|
126
|
+
let nextCharCode: number;
|
|
127
|
+
for (let i = 0; i < x.length; i++) {
|
|
108
128
|
ret *= 16;
|
|
109
129
|
nextCharCode = x.charCodeAt(i);
|
|
110
130
|
if (nextCharCode > 96) {
|
|
@@ -119,6 +139,6 @@ export function fastParseInt16(x:string) {
|
|
|
119
139
|
return ret;
|
|
120
140
|
}
|
|
121
141
|
|
|
122
|
-
export function rgbString(color:RGBAColor) {
|
|
142
|
+
export function rgbString(color: RGBAColor) {
|
|
123
143
|
return `rgb(${color[0]},${color[1]},${color[2]})`;
|
|
124
|
-
}
|
|
144
|
+
}
|
|
@@ -22,63 +22,67 @@
|
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
// @ts-ignore
|
|
25
|
-
import clusterfck from
|
|
25
|
+
import clusterfck from 'tayden-clusterfck';
|
|
26
26
|
// @ts-ignore
|
|
27
|
-
import jStat from
|
|
27
|
+
import jStat from 'jstat';
|
|
28
28
|
|
|
29
29
|
type Item = {
|
|
30
|
-
orderedValueList:number[];
|
|
31
|
-
}
|
|
30
|
+
orderedValueList: number[];
|
|
31
|
+
};
|
|
32
32
|
|
|
33
33
|
type ProcessedItem = Item & {
|
|
34
|
-
isAllNaNs:boolean;
|
|
35
|
-
preProcessedValueList:number[];
|
|
36
|
-
}
|
|
34
|
+
isAllNaNs: boolean;
|
|
35
|
+
preProcessedValueList: number[];
|
|
36
|
+
};
|
|
37
37
|
|
|
38
|
-
export type EntityItem = Item & { entityId: string};
|
|
38
|
+
export type EntityItem = Item & { entityId: string };
|
|
39
39
|
export type CaseItem = Item & { caseId: string };
|
|
40
40
|
|
|
41
41
|
export type CasesAndEntities = {
|
|
42
|
-
[caseId:string]:{
|
|
43
|
-
[entityId:string]:number
|
|
44
|
-
}
|
|
42
|
+
[caseId: string]: {
|
|
43
|
+
[entityId: string]: number;
|
|
44
|
+
};
|
|
45
45
|
};
|
|
46
46
|
|
|
47
47
|
export type ClusteringMessage = {
|
|
48
|
-
dimension:
|
|
49
|
-
casesAndEntities:CasesAndEntities;
|
|
48
|
+
dimension: 'CASES' | 'ENTITIES';
|
|
49
|
+
casesAndEntities: CasesAndEntities;
|
|
50
50
|
};
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
const ctx:Worker = (self as any as Worker);
|
|
52
|
+
const ctx: Worker = (self as any) as Worker;
|
|
54
53
|
/**
|
|
55
54
|
* "Routing" logic for this worker, based on given message.
|
|
56
55
|
*
|
|
57
56
|
* @param m : message object with m.dimension (CASES or ENTITIES) and m.casesAndEntitites
|
|
58
57
|
* which is the input for the clustering method.
|
|
59
58
|
*/
|
|
60
|
-
ctx.onmessage = function(m:MessageEvent) {
|
|
59
|
+
ctx.onmessage = function(m: MessageEvent) {
|
|
61
60
|
console.log('Clustering worker received message');
|
|
62
61
|
var result = null;
|
|
63
|
-
if ((m.data as ClusteringMessage).dimension ===
|
|
62
|
+
if ((m.data as ClusteringMessage).dimension === 'CASES') {
|
|
64
63
|
result = hclusterCases((m.data as ClusteringMessage).casesAndEntities);
|
|
65
|
-
} else if ((m.data as ClusteringMessage).dimension ===
|
|
66
|
-
result = hclusterGeneticEntities(
|
|
64
|
+
} else if ((m.data as ClusteringMessage).dimension === 'ENTITIES') {
|
|
65
|
+
result = hclusterGeneticEntities(
|
|
66
|
+
(m.data as ClusteringMessage).casesAndEntities
|
|
67
|
+
);
|
|
67
68
|
} else {
|
|
68
|
-
throw new Error(
|
|
69
|
+
throw new Error(
|
|
70
|
+
'Illegal argument given to clustering-worker.js for m.data.dimension: ' +
|
|
71
|
+
m.data.dimension
|
|
72
|
+
);
|
|
69
73
|
}
|
|
70
74
|
console.log('Posting clustering result back to main script');
|
|
71
75
|
ctx.postMessage(result);
|
|
72
|
-
}
|
|
76
|
+
};
|
|
73
77
|
|
|
74
78
|
/**
|
|
75
79
|
* Returns false if any value is a valid number != 0.0,
|
|
76
80
|
* and true otherwise.
|
|
77
81
|
*/
|
|
78
|
-
function isAllNaNs(values:any[]) {
|
|
82
|
+
function isAllNaNs(values: any[]) {
|
|
79
83
|
for (let i = 0; i < values.length; i++) {
|
|
80
84
|
const val = values[i];
|
|
81
|
-
if (!isNaN(val) && val != null && val != 0.0
|
|
85
|
+
if (!isNaN(val) && val != null && val != 0.0) {
|
|
82
86
|
return false;
|
|
83
87
|
}
|
|
84
88
|
}
|
|
@@ -91,13 +95,12 @@ function isAllNaNs(values:any[]) {
|
|
|
91
95
|
* of item.orderedValueList.
|
|
92
96
|
*
|
|
93
97
|
*/
|
|
94
|
-
function preRankedSpearmanDist(item1:ProcessedItem, item2:ProcessedItem) {
|
|
98
|
+
function preRankedSpearmanDist(item1: ProcessedItem, item2: ProcessedItem) {
|
|
95
99
|
//rules for NaN values:
|
|
96
100
|
if (item1.isAllNaNs && item2.isAllNaNs) {
|
|
97
101
|
//return distance 0
|
|
98
102
|
return 0;
|
|
99
|
-
}
|
|
100
|
-
else if (item1.isAllNaNs || item2.isAllNaNs) {
|
|
103
|
+
} else if (item1.isAllNaNs || item2.isAllNaNs) {
|
|
101
104
|
//return large distance:
|
|
102
105
|
return 3;
|
|
103
106
|
}
|
|
@@ -111,7 +114,7 @@ function preRankedSpearmanDist(item1:ProcessedItem, item2:ProcessedItem) {
|
|
|
111
114
|
//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
115
|
//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
116
|
//correlation returns NaN. In theory this could happen on small number of entities being clustered. We give this a large distance:
|
|
114
|
-
console.log(
|
|
117
|
+
console.log('NaN in correlation calculation');
|
|
115
118
|
r = -2;
|
|
116
119
|
}
|
|
117
120
|
return 1 - r;
|
|
@@ -122,7 +125,7 @@ function preRankedSpearmanDist(item1:ProcessedItem, item2:ProcessedItem) {
|
|
|
122
125
|
* It will pre-calculate ranks and store this in inputItems[x].preProcessedValueList.
|
|
123
126
|
* This pre-calculation significantly improves the performance of the clustering step itself.
|
|
124
127
|
*/
|
|
125
|
-
function _prepareForDistanceFunction(inputItems:Item[]) {
|
|
128
|
+
function _prepareForDistanceFunction(inputItems: Item[]) {
|
|
126
129
|
//pre-calculate ranks, and
|
|
127
130
|
// split up into allNaN and notAllNaN
|
|
128
131
|
var allNaN = [];
|
|
@@ -144,12 +147,10 @@ function _prepareForDistanceFunction(inputItems:Item[]) {
|
|
|
144
147
|
}
|
|
145
148
|
return {
|
|
146
149
|
notAllNaN: notAllNaN,
|
|
147
|
-
allNaN: allNaN
|
|
150
|
+
allNaN: allNaN,
|
|
148
151
|
};
|
|
149
152
|
}
|
|
150
153
|
|
|
151
|
-
|
|
152
|
-
|
|
153
154
|
/**
|
|
154
155
|
* @param casesAndEntitites: Object with sample(or patient)Id and map
|
|
155
156
|
* of geneticEntity/value pairs. Example:
|
|
@@ -166,7 +167,7 @@ function _prepareForDistanceFunction(inputItems:Item[]) {
|
|
|
166
167
|
*
|
|
167
168
|
* @return the reordered list of sample(or patient) ids, after clustering.
|
|
168
169
|
*/
|
|
169
|
-
function hclusterCases(casesAndEntitites:CasesAndEntities):CaseItem[] {
|
|
170
|
+
function hclusterCases(casesAndEntitites: CasesAndEntities): CaseItem[] {
|
|
170
171
|
var refEntityList = null;
|
|
171
172
|
var inputItems = [];
|
|
172
173
|
//add orderedValueList to all items, so the values are
|
|
@@ -189,16 +190,15 @@ function hclusterCases(casesAndEntitites:CasesAndEntities):CaseItem[] {
|
|
|
189
190
|
if (refEntityList.length == 1) {
|
|
190
191
|
//this is a special case, where the "clustering" becomes a simple sorting in 1 dimension:
|
|
191
192
|
//so, just sort and return inputItems:
|
|
192
|
-
inputItems.sort(function
|
|
193
|
+
inputItems.sort(function(i1, i2) {
|
|
193
194
|
var val1 = i1.orderedValueList[0];
|
|
194
195
|
var val2 = i2.orderedValueList[0];
|
|
195
196
|
//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 =
|
|
197
|
-
val2 =
|
|
197
|
+
val1 = val1 == null || isNaN(val1) ? Number.MAX_VALUE : val1;
|
|
198
|
+
val2 = val2 == null || isNaN(val2) ? Number.MAX_VALUE : val2;
|
|
198
199
|
if (val1 > val2) {
|
|
199
200
|
return 1;
|
|
200
|
-
}
|
|
201
|
-
else if (val1 < val2) {
|
|
201
|
+
} else if (val1 < val2) {
|
|
202
202
|
return -1;
|
|
203
203
|
}
|
|
204
204
|
return 0;
|
|
@@ -207,11 +207,14 @@ function hclusterCases(casesAndEntitites:CasesAndEntities):CaseItem[] {
|
|
|
207
207
|
}
|
|
208
208
|
//else, normal clustering:
|
|
209
209
|
var processedInputItems = _prepareForDistanceFunction(inputItems);
|
|
210
|
-
var clusters = clusterfck.hcluster(
|
|
210
|
+
var clusters = clusterfck.hcluster(
|
|
211
|
+
processedInputItems.notAllNaN,
|
|
212
|
+
preRankedSpearmanDist
|
|
213
|
+
);
|
|
211
214
|
return clusters.clusters(1)[0].concat(processedInputItems.allNaN); // add all nan elements to the end post-sorting
|
|
212
215
|
}
|
|
213
216
|
|
|
214
|
-
function getRefList(caseItem:CasesAndEntities[
|
|
217
|
+
function getRefList(caseItem: CasesAndEntities['']) {
|
|
215
218
|
var result = [];
|
|
216
219
|
for (var entityId in caseItem) {
|
|
217
220
|
result.push(entityId);
|
|
@@ -224,7 +227,9 @@ function getRefList(caseItem:CasesAndEntities[""]) {
|
|
|
224
227
|
*
|
|
225
228
|
* @return the reordered list of entity ids, after clustering.
|
|
226
229
|
*/
|
|
227
|
-
function hclusterGeneticEntities(
|
|
230
|
+
function hclusterGeneticEntities(
|
|
231
|
+
casesAndEntitites: CasesAndEntities
|
|
232
|
+
): EntityItem[] {
|
|
228
233
|
var refEntityList = null;
|
|
229
234
|
var inputItems = [];
|
|
230
235
|
var refCaseIdList = [];
|
|
@@ -253,8 +258,11 @@ function hclusterGeneticEntities(casesAndEntitites:CasesAndEntities):EntityItem[
|
|
|
253
258
|
inputItems.push(inputItem);
|
|
254
259
|
}
|
|
255
260
|
var processedInputItems = _prepareForDistanceFunction(inputItems);
|
|
256
|
-
var clusters = clusterfck.hcluster(
|
|
261
|
+
var clusters = clusterfck.hcluster(
|
|
262
|
+
processedInputItems.notAllNaN,
|
|
263
|
+
preRankedSpearmanDist
|
|
264
|
+
);
|
|
257
265
|
return clusters.clusters(1)[0].concat(processedInputItems.allNaN); // add all nan elements to the end post-sorting
|
|
258
266
|
}
|
|
259
267
|
|
|
260
|
-
export default null;
|
|
268
|
+
// export default null;
|