@datagrok/peptides 0.8.7 → 0.8.10
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/package.json +10 -12
- package/setup.cmd +18 -0
- package/src/model.ts +568 -90
- package/src/monomer-library.ts +19 -14
- package/src/package-test.ts +4 -4
- package/src/package.ts +58 -35
- package/src/peptides.ts +225 -105
- package/src/tests/peptide-space-test.ts +1 -1
- package/src/tests/peptides-tests.ts +14 -75
- package/src/tests/utils.ts +3 -2
- package/src/utils/cell-renderer.ts +120 -72
- package/src/utils/chem-palette.ts +100 -163
- package/src/utils/multiple-sequence-alignment.ts +33 -2
- package/src/utils/multivariate-analysis.ts +77 -0
- package/src/utils/peptide-similarity-space.ts +18 -28
- package/src/viewers/logo-viewer.ts +5 -4
- package/src/viewers/sar-viewer.ts +89 -129
- package/src/viewers/stacked-barchart-viewer.ts +348 -359
- package/src/viewers/subst-viewer.ts +103 -70
- package/src/widgets/analyze-peptides.ts +43 -25
- package/src/widgets/manual-alignment.ts +6 -4
- package/src/widgets/multiple-sequence-alignment.ts +9 -0
- package/src/widgets/peptide-molecule.ts +8 -6
- package/src/widgets/subst-table.ts +63 -0
- package/src/workers/dimensionality-reducer.ts +1 -1
- package/src/describe.ts +0 -535
- package/src/utils/split-aligned.ts +0 -72
|
@@ -1,409 +1,398 @@
|
|
|
1
1
|
import * as DG from 'datagrok-api/dg';
|
|
2
|
-
import * as ui from 'datagrok-api/ui';
|
|
3
|
-
import {scaleBand, scaleLinear} from 'd3';
|
|
4
|
-
import {ChemPalette} from '../utils/chem-palette';
|
|
5
2
|
import * as rxjs from 'rxjs';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
rxjs.fromEvent(grid.overlay, 'click').subscribe((mm:any) => {
|
|
21
|
-
mm = mm as MouseEvent;
|
|
3
|
+
import * as ui from 'datagrok-api/ui';
|
|
4
|
+
import {MonomerLibrary} from '../monomer-library';
|
|
5
|
+
import {PeptidesController} from '../peptides';
|
|
6
|
+
|
|
7
|
+
export function addViewerToHeader(grid: DG.Grid, barchart: StackedBarChart) {
|
|
8
|
+
if (grid.temp['containsBarchart'])
|
|
9
|
+
return;
|
|
10
|
+
|
|
11
|
+
function eventAction(mouseMove: MouseEvent) {
|
|
12
|
+
const cell = grid.hitTest(mouseMove.offsetX, mouseMove.offsetY);
|
|
13
|
+
if (cell !== null && cell?.isColHeader && cell.tableColumn?.semType == 'aminoAcids')
|
|
14
|
+
barchart.highlight(cell, mouseMove.offsetX, mouseMove.offsetY, mouseMove);
|
|
15
|
+
else
|
|
16
|
+
return;
|
|
22
17
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
barchart.unhighlight();
|
|
29
|
-
});
|
|
30
|
-
rxjs.fromEvent(grid.overlay, 'mouseout').subscribe((_: any) => {
|
|
18
|
+
if (cell?.isColHeader && cell.tableColumn?.semType == 'aminoAcids')
|
|
19
|
+
barchart.beginSelection(mouseMove);
|
|
20
|
+
else
|
|
31
21
|
barchart.unhighlight();
|
|
32
|
-
|
|
22
|
+
}
|
|
33
23
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
24
|
+
// The following events makes the barchart interactive
|
|
25
|
+
rxjs.fromEvent<MouseEvent>(grid.overlay, 'mousemove').subscribe((mouseMove: MouseEvent) => eventAction(mouseMove));
|
|
26
|
+
rxjs.fromEvent<MouseEvent>(grid.overlay, 'click').subscribe((mouseMove: MouseEvent) => eventAction(mouseMove));
|
|
27
|
+
rxjs.fromEvent<MouseEvent>(grid.overlay, 'mouseout').subscribe(() => barchart.unhighlight());
|
|
28
|
+
|
|
29
|
+
barchart.tableCanvas = grid.canvas;
|
|
30
|
+
|
|
31
|
+
//Setting grid options
|
|
32
|
+
grid.setOptions({'colHeaderHeight': 130});
|
|
33
|
+
|
|
34
|
+
grid.onCellTooltip((cell, x, y) => {
|
|
35
|
+
if (cell.tableColumn && ['aminoAcids', 'alignedSequence'].includes(cell.tableColumn.semType) ) {
|
|
36
|
+
if (!cell.isColHeader) {
|
|
37
|
+
const monomerLib = cell.cell.dataFrame.temp[MonomerLibrary.id];
|
|
38
|
+
PeptidesController.chemPalette.showTooltip(cell, x, y, monomerLib);
|
|
39
|
+
} else {
|
|
40
|
+
if (barchart.highlighted) {
|
|
41
|
+
let elements: HTMLElement[] = [];
|
|
42
|
+
elements = elements.concat([ui.divText(barchart.highlighted.aaName)]);
|
|
43
|
+
ui.tooltip.show(ui.divV(elements), x, y);
|
|
50
44
|
}
|
|
51
45
|
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
args.g.save();
|
|
55
|
-
args.g.beginPath();
|
|
56
|
-
args.g.rect(args.bounds.x, args.bounds.y, args.bounds.width, args.bounds.height);
|
|
57
|
-
args.g.clip();
|
|
58
|
-
|
|
59
|
-
if (args.cell.isColHeader && barchart.aminoColumnNames.includes(args.cell.gridColumn.name)) {
|
|
60
|
-
barchart.renderBarToCanvas(
|
|
61
|
-
args.g,
|
|
62
|
-
args.cell,
|
|
63
|
-
args.bounds.x,
|
|
64
|
-
args.bounds.y,
|
|
65
|
-
args.bounds.width,
|
|
66
|
-
args.bounds.height,
|
|
67
|
-
);
|
|
68
|
-
args.preventDefault();
|
|
69
|
-
}
|
|
70
|
-
args.g.restore();
|
|
71
|
-
});
|
|
46
|
+
}
|
|
47
|
+
return true;
|
|
72
48
|
});
|
|
49
|
+
|
|
50
|
+
grid.onCellRender.subscribe((args) => {
|
|
51
|
+
const context = args.g;
|
|
52
|
+
const boundX = args.bounds.x;
|
|
53
|
+
const boundY = args.bounds.y;
|
|
54
|
+
const boundWidth = args.bounds.width;
|
|
55
|
+
const boundHeight = args.bounds.height;
|
|
56
|
+
const cell = args.cell;
|
|
57
|
+
context.save();
|
|
58
|
+
context.beginPath();
|
|
59
|
+
context.rect(boundX, boundY, boundWidth, boundHeight);
|
|
60
|
+
context.clip();
|
|
61
|
+
|
|
62
|
+
if (cell.isColHeader && barchart.aminoColumnNames.includes(cell.gridColumn.name)) {
|
|
63
|
+
barchart.renderBarToCanvas(context, cell, boundX, boundY, boundWidth, boundHeight);
|
|
64
|
+
args.preventDefault();
|
|
65
|
+
}
|
|
66
|
+
context.restore();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
grid.temp['containsBarchart'] = true;
|
|
70
|
+
//FIXME: for some reason barchat didn't show when running analysis. This fixes it, but it's bad. Find a way to fix
|
|
71
|
+
// the problem
|
|
72
|
+
barchart.unhighlight();
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
type stackedBarChartDatatype = {
|
|
76
|
+
'name': string,
|
|
77
|
+
'data': {'name': string, 'count': number, 'selectedCount': number, 'fixedSelectedCount': number}[],
|
|
78
|
+
}[];
|
|
79
|
+
|
|
80
|
+
type bartStatsType = {
|
|
81
|
+
[Key: string]: {'name': string, 'count': number, 'selectedCount': number, 'fixedSelectedCount': number}[],
|
|
82
|
+
};
|
|
75
83
|
|
|
76
84
|
export class StackedBarChart extends DG.JsViewer {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
public dataEmptyAA: string;
|
|
86
|
+
public highlighted: {'colName' : string, 'aaName' : string} | null = null;
|
|
87
|
+
public tableCanvas: HTMLCanvasElement | undefined;
|
|
88
|
+
public aminoColumnNames: string[] = [];
|
|
89
|
+
private ord: { [Key: string]: number; } = {};
|
|
90
|
+
private aminoColumnIndices: {[Key: string]: number} = {};
|
|
91
|
+
private aggregatedTables: {[Key: string]: DG.DataFrame} = {};
|
|
92
|
+
private aggregatedHighlightedTables: {[Key: string]: DG.DataFrame} = {};
|
|
93
|
+
private max = 0;
|
|
94
|
+
private barStats:
|
|
95
|
+
{[Key: string]: {'name': string, 'count': number, 'highlightedCount': number, 'selectedCount': number}[]} = {};
|
|
96
|
+
private selected: {'colName' : string, 'aaName' : string}[] = [];
|
|
97
|
+
private highlightedMask: DG.BitSet | null = null;
|
|
98
|
+
private aggregatedSelectedTables: {[Key: string]: DG.DataFrame} = {};
|
|
99
|
+
|
|
100
|
+
constructor() {
|
|
101
|
+
super();
|
|
102
|
+
this.dataEmptyAA = this.string('dataEmptyAA', '-');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
init() {
|
|
106
|
+
const groups: {[key: string]: string[]} = {
|
|
107
|
+
'yellow': ['C', 'U'],
|
|
108
|
+
'red': ['G', 'P'],
|
|
109
|
+
'all_green': ['A', 'V', 'I', 'L', 'M', 'F', 'Y', 'W'],
|
|
110
|
+
'light_blue': ['R', 'H', 'K'],
|
|
111
|
+
'dark_blue': ['D', 'E'],
|
|
112
|
+
'orange': ['S', 'T', 'N', 'Q'],
|
|
86
113
|
};
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
private aminoColumnIndices: {[Key: string]: number} = {};
|
|
94
|
-
private aggregatedTables: {[Key: string]: DG.DataFrame} = {};
|
|
95
|
-
private aggregatedTablesUnselected: {[Key: string]: DG.DataFrame} = {};
|
|
96
|
-
private max = 0;
|
|
97
|
-
private barStats: {[Key: string]: {'name': string, 'count': number, 'selectedCount': number}[]} = {};
|
|
98
|
-
tableCanvas: HTMLCanvasElement | undefined;
|
|
99
|
-
private registered: {[Key: string]: DG.GridCell} = {};
|
|
100
|
-
|
|
101
|
-
constructor() {
|
|
102
|
-
super();
|
|
103
|
-
this.dataEmptyAA = this.string('dataEmptyAA', '-');
|
|
104
|
-
this.initialized = false;
|
|
114
|
+
let i = 0;
|
|
115
|
+
|
|
116
|
+
for (const value of Object.values(groups)) {
|
|
117
|
+
for (const obj of value)
|
|
118
|
+
this.ord[obj] = i++;
|
|
105
119
|
}
|
|
106
120
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
'yellow': ['C', 'U'],
|
|
110
|
-
'red': ['G', 'P'],
|
|
111
|
-
'all_green': ['A', 'V', 'I', 'L', 'M', 'F', 'Y', 'W'],
|
|
112
|
-
'light_blue': ['R', 'H', 'K'],
|
|
113
|
-
'dark_blue': ['D', 'E'],
|
|
114
|
-
'orange': ['S', 'T', 'N', 'Q'],
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
let i = 0;
|
|
118
|
-
for (const value of Object.values(groups)) {
|
|
119
|
-
i++;
|
|
120
|
-
for (const obj of value)
|
|
121
|
-
this.ord[obj] = i;
|
|
122
|
-
}
|
|
123
|
-
this.yScale = scaleLinear();
|
|
124
|
-
this.xScale = scaleBand();
|
|
125
|
-
this.data = [];
|
|
121
|
+
this.aminoColumnNames = [];
|
|
122
|
+
}
|
|
126
123
|
|
|
127
|
-
|
|
124
|
+
// Stream subscriptions
|
|
125
|
+
onTableAttached() {
|
|
126
|
+
this.init();
|
|
127
|
+
if (this.dataFrame) {
|
|
128
|
+
this.subs.push(DG.debounce(this.dataFrame.selection.onChanged, 50).subscribe((_) => this.computeData()));
|
|
129
|
+
this.subs.push(DG.debounce(this.dataFrame.filter.onChanged, 50).subscribe((_) => this.computeData()));
|
|
130
|
+
this.highlightedMask = DG.BitSet.create(this.dataFrame.rowCount);
|
|
128
131
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Cancel subscriptions when the viewer is detached
|
|
135
|
+
detach() {
|
|
136
|
+
this.subs.forEach((sub) => sub.unsubscribe());
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
computeData() {
|
|
140
|
+
this.aminoColumnNames = [];
|
|
141
|
+
this.aminoColumnIndices = {};
|
|
142
|
+
|
|
143
|
+
this.dataFrame!.columns.names().forEach((name: string) => {
|
|
144
|
+
if (this.dataFrame!.getCol(name).semType === 'aminoAcids' &&
|
|
145
|
+
!this.dataFrame!.getCol(name).categories.includes('COOH') &&
|
|
146
|
+
!this.dataFrame!.getCol(name).categories.includes('NH2')) {
|
|
147
|
+
this.aminoColumnIndices[name] = this.aminoColumnNames.length + 1;
|
|
148
|
+
this.aminoColumnNames.push(name);
|
|
139
149
|
}
|
|
140
|
-
}
|
|
150
|
+
});
|
|
141
151
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
152
|
+
this.aggregatedTables = {};
|
|
153
|
+
this.aggregatedHighlightedTables = {};
|
|
154
|
+
this.aggregatedSelectedTables = {};
|
|
155
|
+
//TODO: optimize it, why store so many tables?
|
|
156
|
+
this.aminoColumnNames.forEach((name) => {
|
|
157
|
+
this.aggregatedTables[name] = this.dataFrame!
|
|
158
|
+
.groupBy([name])
|
|
159
|
+
.whereRowMask(this.dataFrame!.filter)
|
|
160
|
+
.add('count', name, `${name}_count`)
|
|
161
|
+
.aggregate();
|
|
162
|
+
|
|
163
|
+
this.aggregatedHighlightedTables[name] = this.dataFrame!
|
|
164
|
+
.groupBy([name])
|
|
165
|
+
.whereRowMask(this.highlightedMask!)
|
|
166
|
+
.add('count', name, `${name}_count`)
|
|
167
|
+
.aggregate();
|
|
168
|
+
|
|
169
|
+
this.aggregatedSelectedTables[name] = this.dataFrame!
|
|
170
|
+
.groupBy([name])
|
|
171
|
+
.whereRowMask(this.dataFrame!.selection)
|
|
172
|
+
.add('count', name, `${name}_count`)
|
|
173
|
+
.aggregate();
|
|
174
|
+
});
|
|
146
175
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
this.aggregatedTables[name] = df
|
|
180
|
-
.groupBy([name])
|
|
181
|
-
.whereRowMask(df.filter)
|
|
182
|
-
.add('count', name, `${name}_count`)
|
|
183
|
-
.aggregate();
|
|
184
|
-
const buf1 = df.selection.getBuffer();
|
|
185
|
-
const buf2 = df.filter.getBuffer();
|
|
186
|
-
const resbuf = new Int32Array(df.rowCount);
|
|
187
|
-
|
|
188
|
-
for (let i = 0; i < buf2.length; i++)
|
|
189
|
-
resbuf[i] = buf1[i] & buf2[i];
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
// @ts-ignore
|
|
193
|
-
const mask = DG.BitSet.fromBytes(resbuf.buffer, df.rowCount);
|
|
194
|
-
// @ts-ignore
|
|
195
|
-
this.aggregatedTablesUnselected[name] = df
|
|
196
|
-
.groupBy([name])
|
|
197
|
-
.whereRowMask(mask)
|
|
198
|
-
.add('count', name, `${name}_count`)
|
|
199
|
-
.aggregate();
|
|
200
|
-
});
|
|
201
|
-
} else {
|
|
202
|
-
this.selectionMode = false;
|
|
203
|
-
this.aminoColumnNames.forEach((name) => {
|
|
204
|
-
// @ts-ignore
|
|
205
|
-
this.aggregatedTables[name] = df
|
|
206
|
-
.groupBy([name])
|
|
207
|
-
.whereRowMask(df.filter)
|
|
208
|
-
.add('count', name, `${name}_count`)
|
|
209
|
-
.aggregate();
|
|
210
|
-
},
|
|
211
|
-
);
|
|
212
|
-
}
|
|
213
|
-
this.data = [];
|
|
214
|
-
this.barStats = {};
|
|
215
|
-
for (const [name, df] of Object.entries(this.aggregatedTables)) {
|
|
216
|
-
const colObj: {
|
|
217
|
-
'name': string,
|
|
218
|
-
'data': { 'name': string, 'count': number, 'selectedCount': number }[],
|
|
219
|
-
} = {'name': name, 'data': []};
|
|
220
|
-
this.barStats[colObj['name']] = colObj['data'];
|
|
221
|
-
this.data.push(colObj);
|
|
222
|
-
for (let i = 0; i < df.rowCount; i++) {
|
|
223
|
-
const amino = df.getCol(name).get(i);
|
|
224
|
-
const aminoCount = df.getCol(`${name}_count`).get(i);
|
|
225
|
-
if ((!amino) || amino === this.dataEmptyAA)
|
|
226
|
-
continue;
|
|
227
|
-
|
|
228
|
-
const aminoObj = {'name': amino, 'count': aminoCount, 'selectedCount': 0};
|
|
229
|
-
colObj['data'].push(aminoObj);
|
|
230
|
-
for (let j = 0; j < this.aggregatedTablesUnselected[name].rowCount; j++) {
|
|
231
|
-
const unsAmino = this.aggregatedTablesUnselected[name].getCol(`${name}`).get(j);
|
|
232
|
-
if (unsAmino == amino) {
|
|
233
|
-
aminoObj['selectedCount'] = this.aggregatedTablesUnselected[name]
|
|
234
|
-
.getCol(`${name}_count`)
|
|
235
|
-
.get(j);
|
|
176
|
+
this.barStats = {};
|
|
177
|
+
|
|
178
|
+
for (const [name, df] of Object.entries(this.aggregatedTables)) {
|
|
179
|
+
const colObj: {
|
|
180
|
+
'name': string,
|
|
181
|
+
'data': { 'name': string, 'count': number, 'highlightedCount': number, 'selectedCount': number}[],
|
|
182
|
+
} = {'name': name, 'data': []};
|
|
183
|
+
const aminoCol = df.getCol(name);
|
|
184
|
+
const aminoCountCol = df.getCol(`${name}_count`);
|
|
185
|
+
this.barStats[colObj['name']] = colObj['data'];
|
|
186
|
+
|
|
187
|
+
for (let i = 0; i < df.rowCount; i++) {
|
|
188
|
+
const amino = aminoCol.get(i);
|
|
189
|
+
const aminoCount = aminoCountCol.get(i);
|
|
190
|
+
const aminoObj = {'name': amino, 'count': aminoCount, 'highlightedCount': 0, 'selectedCount': 0};
|
|
191
|
+
const aggHighlightedAminoCol = this.aggregatedHighlightedTables[name].getCol(`${name}`);
|
|
192
|
+
const aggHighlightedCountCol = this.aggregatedHighlightedTables[name].getCol(`${name}_count`);
|
|
193
|
+
const aggSelectedAminoCol = this.aggregatedSelectedTables[name].getCol(`${name}`);
|
|
194
|
+
const aggSelectedCountCol = this.aggregatedSelectedTables[name].getCol(`${name}_count`);
|
|
195
|
+
|
|
196
|
+
if (!amino || amino === this.dataEmptyAA)
|
|
197
|
+
continue;
|
|
198
|
+
|
|
199
|
+
colObj['data'].push(aminoObj);
|
|
200
|
+
|
|
201
|
+
for (const col of [aggHighlightedCountCol, aggSelectedCountCol]) {
|
|
202
|
+
for (let j = 0; j < col.length; j++) {
|
|
203
|
+
const highlightedAmino = aggHighlightedAminoCol.get(j);
|
|
204
|
+
const selectedAmino = aggSelectedAminoCol.get(j);
|
|
205
|
+
const curAmino = (col == aggHighlightedCountCol ? highlightedAmino : selectedAmino);
|
|
206
|
+
if (curAmino == amino) {
|
|
207
|
+
aminoObj[col == aggHighlightedCountCol ? 'highlightedCount' : 'selectedCount'] = col.get(j);
|
|
236
208
|
break;
|
|
237
209
|
}
|
|
238
210
|
}
|
|
239
211
|
}
|
|
240
|
-
|
|
241
|
-
if (this.ord[o1['name']] > this.ord[o2['name']])
|
|
242
|
-
return -1;
|
|
212
|
+
}
|
|
243
213
|
|
|
244
|
-
|
|
245
|
-
|
|
214
|
+
colObj['data'].sort((o1, o2) => this.ord[o2['name']] - this.ord[o1['name']]);
|
|
215
|
+
}
|
|
246
216
|
|
|
217
|
+
this.max = this.dataFrame!.filter.trueCount;
|
|
218
|
+
}
|
|
247
219
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
220
|
+
renderBarToCanvas(g: CanvasRenderingContext2D, cell: DG.GridCell, x: number, y: number, w: number, h: number) {
|
|
221
|
+
const name = cell.tableColumn!.name;
|
|
222
|
+
const colNameSize = g.measureText(name).width;
|
|
223
|
+
const barData = this.barStats[name];
|
|
224
|
+
const margin = 0.2;
|
|
225
|
+
const innerMargin = 0.02;
|
|
226
|
+
const selectLineRatio = 0.1;
|
|
227
|
+
let sum = 0;
|
|
253
228
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
x: number,
|
|
258
|
-
y: number,
|
|
259
|
-
w: number,
|
|
260
|
-
h: number,
|
|
261
|
-
) {
|
|
262
|
-
const margin = 0.2;
|
|
263
|
-
const innerMargin = 0.02;
|
|
264
|
-
const selectLineRatio = 0.1;
|
|
265
|
-
x = x + w * margin;
|
|
266
|
-
y = y + h * margin / 4;
|
|
267
|
-
w = w - w * margin * 2;
|
|
268
|
-
h = h - h * margin;
|
|
269
|
-
g.fillStyle = 'black';
|
|
270
|
-
g.textBaseline = 'top';
|
|
271
|
-
g.font = `${h * margin / 2}px`;
|
|
272
|
-
|
|
273
|
-
const name = cell.tableColumn!.name;
|
|
274
|
-
const colNameSize = g.measureText(name).width;
|
|
275
|
-
g.fillText(name,
|
|
276
|
-
x + (w - colNameSize)/2,
|
|
277
|
-
y + h + h * margin / 4);
|
|
278
|
-
const barData = this.barStats[name]? this.barStats[name]: this.barStats[name];
|
|
279
|
-
let sum = 0;
|
|
280
|
-
barData.forEach((obj) => {
|
|
281
|
-
sum += obj['count'];
|
|
282
|
-
});
|
|
283
|
-
let curSum = 0;
|
|
284
|
-
|
|
285
|
-
barData.forEach((obj, index) => {
|
|
286
|
-
const sBarHeight = h * obj['count'] / this.max;
|
|
287
|
-
const gapSize = sBarHeight * innerMargin;
|
|
288
|
-
g.fillStyle = cp.getColor(obj['name']);
|
|
289
|
-
g.fillRect(
|
|
290
|
-
x,
|
|
291
|
-
y + h * (this.max - sum + curSum) / this.max + gapSize / 2,
|
|
292
|
-
w,
|
|
293
|
-
sBarHeight - gapSize);
|
|
294
|
-
if (h * margin / 2 <= sBarHeight - gapSize && h * margin / 2 <= w) {
|
|
295
|
-
g.fillStyle = 'rgb(0,0,0)';
|
|
296
|
-
g.font = `${h * margin / 2}px`;
|
|
297
|
-
const [, aar] = cp.getColorAAPivot(obj['name']);
|
|
298
|
-
g.fillText(aar,
|
|
299
|
-
x + w / 2 - h * margin / 8,
|
|
300
|
-
y + h * (this.max - sum + curSum) / this.max + gapSize / 2 + (sBarHeight - gapSize)/2 - h * margin / 8);
|
|
301
|
-
}
|
|
229
|
+
barData.forEach((obj) => {
|
|
230
|
+
sum += obj['count'];
|
|
231
|
+
});
|
|
302
232
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
233
|
+
x = x + w * margin;
|
|
234
|
+
y = y + h * margin / 4;
|
|
235
|
+
w = w - w * margin * 2;
|
|
236
|
+
h = h - h * margin;
|
|
237
|
+
const barWidth = w - 10;
|
|
238
|
+
g.fillStyle = 'black';
|
|
239
|
+
g.textBaseline = 'top';
|
|
240
|
+
g.font = `${h * margin / 2}px`;
|
|
241
|
+
g.fillText(name, x + (w - colNameSize) / 2, y + h + h * margin / 4);
|
|
242
|
+
|
|
243
|
+
barData.forEach((obj) => {
|
|
244
|
+
const sBarHeight = h * obj['count'] / this.max;
|
|
245
|
+
const gapSize = sBarHeight * innerMargin;
|
|
246
|
+
const verticalShift = (this.max - sum) / this.max;
|
|
247
|
+
const [color, aarOuter] = PeptidesController.chemPalette.getColorAAPivot(obj['name']);
|
|
248
|
+
const textSize = g.measureText(aarOuter);
|
|
249
|
+
const fontSize = 11;
|
|
250
|
+
const leftMargin = (w - (aarOuter.length > 1 ? fontSize : textSize.width - 8)) / 2;
|
|
251
|
+
const subBartHeight = sBarHeight - gapSize;
|
|
252
|
+
const yStart = h * verticalShift + gapSize / 2;
|
|
253
|
+
const xStart = (w - barWidth) / 2;
|
|
254
|
+
const absX = x + leftMargin;
|
|
255
|
+
const absY = y + yStart + subBartHeight / 2 + (aarOuter.length == 1 ? + 4 : 0);
|
|
256
|
+
const eps = 0.1;
|
|
257
|
+
|
|
258
|
+
g.strokeStyle = color;
|
|
259
|
+
g.fillStyle = color;
|
|
260
|
+
if (textSize.width <= subBartHeight) {
|
|
261
|
+
const origTransform = g.getTransform();
|
|
262
|
+
|
|
263
|
+
if (color != PeptidesController.chemPalette.undefinedColor) {
|
|
264
|
+
g.fillRect(x + xStart, y + yStart, barWidth, subBartHeight);
|
|
265
|
+
g.fillStyle = 'black';
|
|
266
|
+
} else
|
|
267
|
+
g.strokeRect(x + xStart + 0.5, y + yStart, barWidth - 1, subBartHeight);
|
|
268
|
+
|
|
269
|
+
g.font = `${fontSize}px monospace`;
|
|
270
|
+
g.textAlign = 'center';
|
|
271
|
+
g.textBaseline = 'bottom';
|
|
272
|
+
|
|
273
|
+
if (aarOuter.length > 1) {
|
|
274
|
+
g.translate(absX, absY);
|
|
275
|
+
g.rotate(Math.PI / 2);
|
|
276
|
+
g.translate(-absX, -absY);
|
|
310
277
|
}
|
|
311
278
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
x,
|
|
317
|
-
y + h * (this.max - sum + curSum) / this.max + gapSize / 2,
|
|
318
|
-
w,
|
|
319
|
-
sBarHeight - gapSize);
|
|
320
|
-
}
|
|
279
|
+
g.fillText(aarOuter, absX, absY);
|
|
280
|
+
g.setTransform(origTransform);
|
|
281
|
+
} else
|
|
282
|
+
g.fillRect(x + xStart, y + yStart, barWidth, subBartHeight);
|
|
321
283
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
284
|
+
if (obj['selectedCount'] > eps) {
|
|
285
|
+
g.fillStyle = 'rgb(255,165,0)';
|
|
286
|
+
g.fillRect(
|
|
287
|
+
x + xStart - w * selectLineRatio * 2,
|
|
288
|
+
y + yStart,
|
|
289
|
+
barWidth * selectLineRatio,
|
|
290
|
+
h * obj['selectedCount'] / this.max - gapSize,
|
|
291
|
+
);
|
|
292
|
+
}
|
|
331
293
|
|
|
332
|
-
if (
|
|
333
|
-
|
|
334
|
-
|
|
294
|
+
if (obj['highlightedCount'] > eps && obj['highlightedCount'] > obj['selectedCount']) {
|
|
295
|
+
g.fillStyle = 'rgb(209,242,251)';
|
|
296
|
+
g.fillRect(
|
|
297
|
+
x + xStart - w * selectLineRatio * 2,
|
|
298
|
+
y + yStart + h * obj['selectedCount'] / this.max - gapSize,
|
|
299
|
+
barWidth * selectLineRatio,
|
|
300
|
+
h * (obj['highlightedCount'] - obj['selectedCount']) / this.max - gapSize,
|
|
301
|
+
);
|
|
335
302
|
}
|
|
336
|
-
return;
|
|
337
|
-
}
|
|
338
303
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
}
|
|
304
|
+
sum -= obj['count'];
|
|
305
|
+
});
|
|
306
|
+
}
|
|
343
307
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
308
|
+
highlight(cell: DG.GridCell, offsetX:number, offsetY:number, mouseEvent: MouseEvent) {
|
|
309
|
+
if (!cell.tableColumn?.name || !this.aminoColumnNames.includes(cell.tableColumn.name))
|
|
310
|
+
return;
|
|
347
311
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
312
|
+
const colName = cell.tableColumn?.name;
|
|
313
|
+
const innerMargin = 0.02;
|
|
314
|
+
const margin = 0.2;
|
|
315
|
+
const bound = cell.bounds;
|
|
316
|
+
const height = 130;
|
|
317
|
+
const x = bound.x + bound.width * margin;
|
|
318
|
+
const y = height * margin / 4;
|
|
319
|
+
const w = bound.width - bound.width * margin * 2;
|
|
320
|
+
const h = height - height * margin;
|
|
321
|
+
const barData = this.barStats[colName];
|
|
322
|
+
const barWidth = w - 10;
|
|
323
|
+
let sum = 0;
|
|
324
|
+
|
|
325
|
+
barData.forEach((obj) => {
|
|
326
|
+
sum += obj['count'];
|
|
327
|
+
});
|
|
352
328
|
|
|
329
|
+
this.highlighted = null;
|
|
330
|
+
barData.forEach((obj) => {
|
|
331
|
+
const sBarHeight = h * obj['count'] / this.max;
|
|
332
|
+
const gapSize = sBarHeight * innerMargin;
|
|
333
|
+
const verticalShift = (this.max - sum) / this.max;
|
|
334
|
+
const subBartHeight = sBarHeight - gapSize;
|
|
335
|
+
const yStart = h * verticalShift + gapSize / 2;
|
|
336
|
+
const xStart = (w - barWidth) / 2;
|
|
353
337
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
338
|
+
if (offsetX >= x + xStart &&
|
|
339
|
+
offsetY >= y + yStart &&
|
|
340
|
+
offsetX <= x + xStart + barWidth &&
|
|
341
|
+
offsetY <= y + yStart + subBartHeight)
|
|
342
|
+
this.highlighted = {'colName': colName, 'aaName': obj['name']};
|
|
357
343
|
|
|
358
|
-
const cell = this.registered[name];
|
|
359
|
-
const rect = cell.bounds;
|
|
360
|
-
this.renderBarToCanvas(this.tableCanvas.getContext('2d')!, cell, rect.x, rect.y, rect.width, rect.height);
|
|
361
|
-
}
|
|
362
344
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
if (!colName)
|
|
366
|
-
return;
|
|
367
|
-
|
|
368
|
-
const margin = 0.2;
|
|
369
|
-
const bound = cell.bounds;
|
|
370
|
-
const x = bound.x + bound.width * margin;
|
|
371
|
-
const y = 0 + 200 * margin / 4;
|
|
372
|
-
const w = bound.width - bound.width * margin * 2;
|
|
373
|
-
const h = 200 - 200 * margin / 2;
|
|
374
|
-
const barData = this.barStats[colName];
|
|
375
|
-
let sum = 0;
|
|
376
|
-
barData.forEach((obj) => {
|
|
377
|
-
sum += obj['count'];
|
|
378
|
-
});
|
|
379
|
-
let curSum = 0;
|
|
380
|
-
|
|
381
|
-
barData.forEach((obj, index) => {
|
|
382
|
-
const sBarHeight = h * obj['count'] / this.max;
|
|
383
|
-
if (offsetX>=x &&
|
|
384
|
-
offsetY>=y + h * (this.max - sum + curSum) / this.max &&
|
|
385
|
-
offsetX<=x+w &&
|
|
386
|
-
offsetY<=y + h * (this.max - sum + curSum) / this.max + sBarHeight) {
|
|
387
|
-
this.highlighted = {'colName': colName, 'aaName': obj['name']};
|
|
388
|
-
return;
|
|
389
|
-
}
|
|
345
|
+
sum -= obj['count'];
|
|
346
|
+
});
|
|
390
347
|
|
|
391
|
-
|
|
392
|
-
});
|
|
348
|
+
if (!this.highlighted)
|
|
393
349
|
return;
|
|
394
|
-
}
|
|
395
350
|
|
|
396
|
-
|
|
397
|
-
|
|
351
|
+
if (mouseEvent.type == 'click') {
|
|
352
|
+
let idx = -1;
|
|
353
|
+
|
|
354
|
+
for (let i = 0; i < this.selected.length; ++i) {
|
|
355
|
+
if (JSON.stringify(this.selected[i]) == JSON.stringify(this.highlighted))
|
|
356
|
+
idx = i;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (mouseEvent.shiftKey && idx == -1)
|
|
360
|
+
this.selected.push(this.highlighted);
|
|
361
|
+
|
|
362
|
+
if (mouseEvent.shiftKey && (mouseEvent.ctrlKey || mouseEvent.metaKey) && idx != -1)
|
|
363
|
+
this.selected.splice(idx, 1);
|
|
398
364
|
}
|
|
365
|
+
}
|
|
399
366
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
367
|
+
unhighlight() {
|
|
368
|
+
this.highlighted = null;
|
|
369
|
+
this.highlightedMask!.setAll(false);
|
|
370
|
+
this.computeData();
|
|
371
|
+
}
|
|
403
372
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
373
|
+
beginSelection(event: MouseEvent) {
|
|
374
|
+
if (!this.dataFrame)
|
|
375
|
+
return;
|
|
376
|
+
|
|
377
|
+
this.highlightedMask!.setAll(false);
|
|
378
|
+
|
|
379
|
+
this.dataFrame.selection.handleClick((i: number) => {
|
|
380
|
+
for (const high of this.selected) {
|
|
381
|
+
if (high['aaName'] === (this.dataFrame!.getCol(high['colName']).get(i)))
|
|
382
|
+
return true;
|
|
383
|
+
}
|
|
384
|
+
return false;
|
|
385
|
+
}, event);
|
|
386
|
+
|
|
387
|
+
if (this.highlighted) {
|
|
388
|
+
this.dataFrame.rows.match({[this.highlighted['colName']]: this.highlighted['aaName']}).highlight();
|
|
389
|
+
this.highlightedMask!.handleClick((i: number) => {
|
|
390
|
+
if (this.highlighted!['aaName'] === (this.dataFrame!.getCol(this.highlighted!['colName']).get(i)))
|
|
391
|
+
return true;
|
|
392
|
+
return false;
|
|
407
393
|
}, event);
|
|
408
394
|
}
|
|
395
|
+
|
|
396
|
+
this.computeData();
|
|
397
|
+
}
|
|
409
398
|
}
|