@platforma-sdk/ui-vue 1.45.35 → 1.45.36
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/.turbo/turbo-build.log +203 -234
- package/.turbo/turbo-type-check.log +1 -1
- package/CHANGELOG.md +6 -0
- package/dist/AgGridVue/useAgGridOptions.js +2 -3
- package/dist/AgGridVue/useAgGridOptions.js.map +1 -1
- package/dist/components/PlAgDataTable/PlAgRowCount.vue.js +7 -8
- package/dist/components/PlAgDataTable/PlAgRowCount.vue.js.map +1 -1
- package/dist/components/PlAgRowNumCheckbox/PlAgRowNumCheckbox.vue.js +9 -10
- package/dist/components/PlAgRowNumCheckbox/PlAgRowNumCheckbox.vue.js.map +1 -1
- package/dist/components/PlAgRowNumHeader.vue.js +2 -3
- package/dist/components/PlAgRowNumHeader.vue.js.map +1 -1
- package/dist/index.js +48 -50
- package/dist/index.js.map +1 -1
- package/dist/lib.d.ts +0 -1
- package/package.json +4 -5
- package/src/lib.ts +0 -2
- package/dist/assets/multi-sequence-alignment.worker-Cm0gZp19.js +0 -6
- package/dist/assets/multi-sequence-alignment.worker-Cm0gZp19.js.map +0 -1
- package/dist/assets/phylogenetic-tree.worker-4CrExYEo.js +0 -5
- package/dist/assets/phylogenetic-tree.worker-4CrExYEo.js.map +0 -1
- package/dist/components/PlMultiSequenceAlignment/Consensus.vue.d.ts +0 -9
- package/dist/components/PlMultiSequenceAlignment/Consensus.vue.js +0 -10
- package/dist/components/PlMultiSequenceAlignment/Consensus.vue.js.map +0 -1
- package/dist/components/PlMultiSequenceAlignment/Consensus.vue2.js +0 -122
- package/dist/components/PlMultiSequenceAlignment/Consensus.vue2.js.map +0 -1
- package/dist/components/PlMultiSequenceAlignment/Consensus.vue3.js +0 -9
- package/dist/components/PlMultiSequenceAlignment/Consensus.vue3.js.map +0 -1
- package/dist/components/PlMultiSequenceAlignment/Legend.vue.d.ts +0 -6
- package/dist/components/PlMultiSequenceAlignment/Legend.vue.js +0 -10
- package/dist/components/PlMultiSequenceAlignment/Legend.vue.js.map +0 -1
- package/dist/components/PlMultiSequenceAlignment/Legend.vue2.js +0 -28
- package/dist/components/PlMultiSequenceAlignment/Legend.vue2.js.map +0 -1
- package/dist/components/PlMultiSequenceAlignment/Legend.vue3.js +0 -13
- package/dist/components/PlMultiSequenceAlignment/Legend.vue3.js.map +0 -1
- package/dist/components/PlMultiSequenceAlignment/MultiSequenceAlignmentView.vue.d.ts +0 -25
- package/dist/components/PlMultiSequenceAlignment/MultiSequenceAlignmentView.vue.js +0 -10
- package/dist/components/PlMultiSequenceAlignment/MultiSequenceAlignmentView.vue.js.map +0 -1
- package/dist/components/PlMultiSequenceAlignment/MultiSequenceAlignmentView.vue2.js +0 -138
- package/dist/components/PlMultiSequenceAlignment/MultiSequenceAlignmentView.vue2.js.map +0 -1
- package/dist/components/PlMultiSequenceAlignment/MultiSequenceAlignmentView.vue3.js +0 -31
- package/dist/components/PlMultiSequenceAlignment/MultiSequenceAlignmentView.vue3.js.map +0 -1
- package/dist/components/PlMultiSequenceAlignment/PhylogeneticTree.vue.d.ts +0 -8
- package/dist/components/PlMultiSequenceAlignment/PhylogeneticTree.vue.js +0 -10
- package/dist/components/PlMultiSequenceAlignment/PhylogeneticTree.vue.js.map +0 -1
- package/dist/components/PlMultiSequenceAlignment/PhylogeneticTree.vue2.js +0 -77
- package/dist/components/PlMultiSequenceAlignment/PhylogeneticTree.vue2.js.map +0 -1
- package/dist/components/PlMultiSequenceAlignment/PhylogeneticTree.vue3.js +0 -9
- package/dist/components/PlMultiSequenceAlignment/PhylogeneticTree.vue3.js.map +0 -1
- package/dist/components/PlMultiSequenceAlignment/PlMultiSequenceAlignment.vue.d.ts +0 -71
- package/dist/components/PlMultiSequenceAlignment/PlMultiSequenceAlignment.vue.js +0 -10
- package/dist/components/PlMultiSequenceAlignment/PlMultiSequenceAlignment.vue.js.map +0 -1
- package/dist/components/PlMultiSequenceAlignment/PlMultiSequenceAlignment.vue2.js +0 -224
- package/dist/components/PlMultiSequenceAlignment/PlMultiSequenceAlignment.vue2.js.map +0 -1
- package/dist/components/PlMultiSequenceAlignment/PlMultiSequenceAlignment.vue3.js +0 -9
- package/dist/components/PlMultiSequenceAlignment/PlMultiSequenceAlignment.vue3.js.map +0 -1
- package/dist/components/PlMultiSequenceAlignment/SeqLogo.vue.d.ts +0 -8
- package/dist/components/PlMultiSequenceAlignment/SeqLogo.vue.js +0 -10
- package/dist/components/PlMultiSequenceAlignment/SeqLogo.vue.js.map +0 -1
- package/dist/components/PlMultiSequenceAlignment/SeqLogo.vue2.js +0 -127
- package/dist/components/PlMultiSequenceAlignment/SeqLogo.vue2.js.map +0 -1
- package/dist/components/PlMultiSequenceAlignment/SeqLogo.vue3.js +0 -9
- package/dist/components/PlMultiSequenceAlignment/SeqLogo.vue3.js.map +0 -1
- package/dist/components/PlMultiSequenceAlignment/Toolbar.vue.d.ts +0 -16
- package/dist/components/PlMultiSequenceAlignment/Toolbar.vue.js +0 -10
- package/dist/components/PlMultiSequenceAlignment/Toolbar.vue.js.map +0 -1
- package/dist/components/PlMultiSequenceAlignment/Toolbar.vue2.js +0 -228
- package/dist/components/PlMultiSequenceAlignment/Toolbar.vue2.js.map +0 -1
- package/dist/components/PlMultiSequenceAlignment/Toolbar.vue3.js +0 -19
- package/dist/components/PlMultiSequenceAlignment/Toolbar.vue3.js.map +0 -1
- package/dist/components/PlMultiSequenceAlignment/cell-size.d.ts +0 -4
- package/dist/components/PlMultiSequenceAlignment/cell-size.js +0 -8
- package/dist/components/PlMultiSequenceAlignment/cell-size.js.map +0 -1
- package/dist/components/PlMultiSequenceAlignment/chemical-properties.d.ts +0 -44
- package/dist/components/PlMultiSequenceAlignment/chemical-properties.js +0 -132
- package/dist/components/PlMultiSequenceAlignment/chemical-properties.js.map +0 -1
- package/dist/components/PlMultiSequenceAlignment/data.d.ts +0 -61
- package/dist/components/PlMultiSequenceAlignment/data.js +0 -370
- package/dist/components/PlMultiSequenceAlignment/data.js.map +0 -1
- package/dist/components/PlMultiSequenceAlignment/index.d.ts +0 -1
- package/dist/components/PlMultiSequenceAlignment/markup.d.ts +0 -16
- package/dist/components/PlMultiSequenceAlignment/markup.js +0 -84
- package/dist/components/PlMultiSequenceAlignment/markup.js.map +0 -1
- package/dist/components/PlMultiSequenceAlignment/migrations.d.ts +0 -3
- package/dist/components/PlMultiSequenceAlignment/migrations.js +0 -24
- package/dist/components/PlMultiSequenceAlignment/migrations.js.map +0 -1
- package/dist/components/PlMultiSequenceAlignment/multi-sequence-alignment.worker.d.ts +0 -6
- package/dist/components/PlMultiSequenceAlignment/phylogenetic-tree.worker.d.ts +0 -7
- package/dist/components/PlMultiSequenceAlignment/residue-counts.d.ts +0 -2
- package/dist/components/PlMultiSequenceAlignment/residue-counts.js +0 -13
- package/dist/components/PlMultiSequenceAlignment/residue-counts.js.map +0 -1
- package/dist/components/PlMultiSequenceAlignment/settings.d.ts +0 -2
- package/dist/components/PlMultiSequenceAlignment/settings.js +0 -9
- package/dist/components/PlMultiSequenceAlignment/settings.js.map +0 -1
- package/dist/components/PlMultiSequenceAlignment/types.d.ts +0 -5
- package/dist/components/PlMultiSequenceAlignment/useMiPlots.d.ts +0 -4
- package/dist/components/PlMultiSequenceAlignment/useMiPlots.js +0 -19
- package/dist/components/PlMultiSequenceAlignment/useMiPlots.js.map +0 -1
- package/src/components/PlMultiSequenceAlignment/Consensus.vue +0 -165
- package/src/components/PlMultiSequenceAlignment/Legend.vue +0 -44
- package/src/components/PlMultiSequenceAlignment/MultiSequenceAlignmentView.vue +0 -299
- package/src/components/PlMultiSequenceAlignment/PhylogeneticTree.vue +0 -110
- package/src/components/PlMultiSequenceAlignment/PlMultiSequenceAlignment.vue +0 -314
- package/src/components/PlMultiSequenceAlignment/README.md +0 -216
- package/src/components/PlMultiSequenceAlignment/SeqLogo.vue +0 -166
- package/src/components/PlMultiSequenceAlignment/Toolbar.vue +0 -228
- package/src/components/PlMultiSequenceAlignment/cell-size.ts +0 -4
- package/src/components/PlMultiSequenceAlignment/chemical-properties.ts +0 -199
- package/src/components/PlMultiSequenceAlignment/data.ts +0 -661
- package/src/components/PlMultiSequenceAlignment/index.ts +0 -1
- package/src/components/PlMultiSequenceAlignment/markup.ts +0 -141
- package/src/components/PlMultiSequenceAlignment/migrations.ts +0 -46
- package/src/components/PlMultiSequenceAlignment/multi-sequence-alignment.worker.ts +0 -54
- package/src/components/PlMultiSequenceAlignment/phylogenetic-tree.worker.ts +0 -89
- package/src/components/PlMultiSequenceAlignment/residue-counts.ts +0 -124
- package/src/components/PlMultiSequenceAlignment/settings.ts +0 -7
- package/src/components/PlMultiSequenceAlignment/types.ts +0 -3
- package/src/components/PlMultiSequenceAlignment/useMiPlots.ts +0 -23
|
@@ -1,661 +0,0 @@
|
|
|
1
|
-
import { isJsonEqual } from '@milaboratories/helpers';
|
|
2
|
-
import type { ListOptionNormalized } from '@milaboratories/uikit';
|
|
3
|
-
import {
|
|
4
|
-
Annotation,
|
|
5
|
-
type CalculateTableDataRequest,
|
|
6
|
-
type CalculateTableDataResponse,
|
|
7
|
-
type CanonicalizedJson,
|
|
8
|
-
canonicalizeJson,
|
|
9
|
-
createRowSelectionColumn,
|
|
10
|
-
ensureError,
|
|
11
|
-
getAxisId,
|
|
12
|
-
getRawPlatformaInstance,
|
|
13
|
-
isLabelColumn,
|
|
14
|
-
isLinkerColumn,
|
|
15
|
-
type JoinEntry,
|
|
16
|
-
matchAxisId,
|
|
17
|
-
parseJson,
|
|
18
|
-
type PColumnIdAndSpec,
|
|
19
|
-
type PFrameHandle,
|
|
20
|
-
type PlMultiSequenceAlignmentColorSchemeOption,
|
|
21
|
-
type PlMultiSequenceAlignmentSettings,
|
|
22
|
-
type PlSelectionModel,
|
|
23
|
-
type PObjectId,
|
|
24
|
-
type PTableColumnId,
|
|
25
|
-
type PTableSorting,
|
|
26
|
-
pTableValue,
|
|
27
|
-
readAnnotation,
|
|
28
|
-
readAnnotationJson,
|
|
29
|
-
} from '@platforma-sdk/model';
|
|
30
|
-
import { onWatcherCleanup, ref, watch } from 'vue';
|
|
31
|
-
import { objectHash } from '../../objectHash';
|
|
32
|
-
import { highlightByChemicalProperties } from './chemical-properties';
|
|
33
|
-
import type { Markup } from './markup';
|
|
34
|
-
import {
|
|
35
|
-
highlightByMarkup,
|
|
36
|
-
markupAlignedSequence,
|
|
37
|
-
parseMarkup,
|
|
38
|
-
} from './markup';
|
|
39
|
-
import type * as MultiSequenceAlignmentWorker from './multi-sequence-alignment.worker';
|
|
40
|
-
import type * as PhylogeneticTreeWorker from './phylogenetic-tree.worker';
|
|
41
|
-
import { getResidueCounts } from './residue-counts';
|
|
42
|
-
import type { HighlightLegend, ResidueCounts } from './types';
|
|
43
|
-
|
|
44
|
-
const getPFrameDriver = () => getRawPlatformaInstance().pFrameDriver;
|
|
45
|
-
|
|
46
|
-
export const SEQUENCE_LIMIT = 1000;
|
|
47
|
-
|
|
48
|
-
export const useSequenceColumnsOptions = refreshOnDeepChange(
|
|
49
|
-
getSequenceColumnsOptions,
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
export const useLabelColumnsOptions = refreshOnDeepChange(
|
|
53
|
-
getLabelColumnsOptions,
|
|
54
|
-
);
|
|
55
|
-
|
|
56
|
-
export const useMarkupColumnsOptions = refreshOnDeepChange(
|
|
57
|
-
getMarkupColumnsOptions,
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
export const useMultipleAlignmentData = refreshOnDeepChange(
|
|
61
|
-
getMultipleAlignmentData,
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
async function getSequenceColumnsOptions({ pFrame, sequenceColumnPredicate }: {
|
|
65
|
-
pFrame: PFrameHandle | undefined;
|
|
66
|
-
sequenceColumnPredicate: (column: PColumnIdAndSpec) => boolean;
|
|
67
|
-
}): Promise<OptionsWithDefaults<PObjectId> | undefined> {
|
|
68
|
-
if (!pFrame) return;
|
|
69
|
-
|
|
70
|
-
const pFrameDriver = getPFrameDriver();
|
|
71
|
-
const columns = await pFrameDriver.listColumns(pFrame);
|
|
72
|
-
|
|
73
|
-
const options = columns.values()
|
|
74
|
-
.filter((column) => sequenceColumnPredicate(column))
|
|
75
|
-
.map(({ spec, columnId }) => ({
|
|
76
|
-
label: readAnnotation(spec, Annotation.Label) ?? 'Unlabeled column',
|
|
77
|
-
value: columnId,
|
|
78
|
-
}))
|
|
79
|
-
.toArray();
|
|
80
|
-
|
|
81
|
-
const defaults = options.map(({ value }) => value);
|
|
82
|
-
|
|
83
|
-
return { options, defaults };
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
async function getLabelColumnsOptions({ pFrame, sequenceColumnIds }: {
|
|
87
|
-
pFrame: PFrameHandle | undefined;
|
|
88
|
-
sequenceColumnIds: PObjectId[] | undefined;
|
|
89
|
-
}): Promise<OptionsWithDefaults<PTableColumnId> | undefined> {
|
|
90
|
-
if (!pFrame || !sequenceColumnIds) return;
|
|
91
|
-
|
|
92
|
-
const pFrameDriver = getPFrameDriver();
|
|
93
|
-
const columns = await pFrameDriver.listColumns(pFrame);
|
|
94
|
-
|
|
95
|
-
const sequenceColumnsAxes = new Map(
|
|
96
|
-
sequenceColumnIds.values().flatMap((id) => {
|
|
97
|
-
const column = columns.find(({ columnId }) => columnId === id);
|
|
98
|
-
if (!column) {
|
|
99
|
-
throw new Error(`Couldn't find sequence column (ID: \`${id}\`).`);
|
|
100
|
-
}
|
|
101
|
-
return column.spec.axesSpec.values()
|
|
102
|
-
.map((spec) => [canonicalizeJson(getAxisId(spec)), spec]);
|
|
103
|
-
}),
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
const optionMap = new Map<CanonicalizedJson<PTableColumnId>, string>();
|
|
107
|
-
for (const [axisIdJson, axisSpec] of sequenceColumnsAxes.entries()) {
|
|
108
|
-
const axisId = parseJson(axisIdJson);
|
|
109
|
-
const labelColumn = columns.find(({ spec }) =>
|
|
110
|
-
isLabelColumn(spec) && matchAxisId(axisId, getAxisId(spec.axesSpec[0])),
|
|
111
|
-
);
|
|
112
|
-
optionMap.set(
|
|
113
|
-
labelColumn
|
|
114
|
-
? canonicalizeJson({ type: 'column', id: labelColumn.columnId })
|
|
115
|
-
: canonicalizeJson({ type: 'axis', id: axisId }),
|
|
116
|
-
readAnnotation(labelColumn?.spec, Annotation.Label)
|
|
117
|
-
?? readAnnotation(axisSpec, Annotation.Label)
|
|
118
|
-
?? 'Unlabeled axis',
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const { hits: compatibleColumns } = await pFrameDriver.findColumns(pFrame, {
|
|
123
|
-
columnFilter: {},
|
|
124
|
-
compatibleWith: sequenceColumnsAxes.keys()
|
|
125
|
-
.map((axisIdJson) => parseJson(axisIdJson))
|
|
126
|
-
.toArray(),
|
|
127
|
-
strictlyCompatible: false,
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
for (const { columnId, spec } of compatibleColumns) {
|
|
131
|
-
const columnIdJson = canonicalizeJson<PTableColumnId>({
|
|
132
|
-
type: 'column',
|
|
133
|
-
id: columnId,
|
|
134
|
-
});
|
|
135
|
-
if (optionMap.has(columnIdJson)) continue;
|
|
136
|
-
optionMap.set(
|
|
137
|
-
columnIdJson,
|
|
138
|
-
readAnnotation(spec, Annotation.Label) ?? 'Unlabeled column',
|
|
139
|
-
);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const options = optionMap.entries()
|
|
143
|
-
.map(([value, label]) => ({ label, value: parseJson(value) }))
|
|
144
|
-
.toArray();
|
|
145
|
-
|
|
146
|
-
const defaults = options.values()
|
|
147
|
-
.filter(({ value }) => {
|
|
148
|
-
if (value.type === 'axis') return true;
|
|
149
|
-
const column = columns.find(({ columnId }) => columnId === value.id);
|
|
150
|
-
return column && isLabelColumn(column.spec);
|
|
151
|
-
})
|
|
152
|
-
.map(({ value }) => value)
|
|
153
|
-
.toArray();
|
|
154
|
-
|
|
155
|
-
return { options, defaults };
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
async function getMarkupColumnsOptions({ pFrame, sequenceColumnIds }: {
|
|
159
|
-
pFrame: PFrameHandle | undefined;
|
|
160
|
-
sequenceColumnIds: PObjectId[] | undefined;
|
|
161
|
-
}): Promise<ListOptionNormalized<PObjectId[]>[] | undefined> {
|
|
162
|
-
if (!pFrame || !sequenceColumnIds) return;
|
|
163
|
-
|
|
164
|
-
const pFrameDriver = getPFrameDriver();
|
|
165
|
-
const columns = await pFrameDriver.listColumns(pFrame);
|
|
166
|
-
|
|
167
|
-
const sequenceColumns = sequenceColumnIds.map((columnId) => {
|
|
168
|
-
const column = columns.find((column) => column.columnId === columnId);
|
|
169
|
-
if (!column) {
|
|
170
|
-
throw new Error(
|
|
171
|
-
`Couldn't find sequence column (ID: \`${sequenceColumnIds[0]}\`).`,
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
return column;
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
const columnPairs = sequenceColumns
|
|
178
|
-
.flatMap((sequenceColumn) =>
|
|
179
|
-
columns
|
|
180
|
-
.filter((column) =>
|
|
181
|
-
readAnnotationJson(column.spec, Annotation.Sequence.IsAnnotation)
|
|
182
|
-
&& isJsonEqual(sequenceColumn.spec.axesSpec, column.spec.axesSpec)
|
|
183
|
-
&& Object.entries(sequenceColumn.spec.domain ?? {})
|
|
184
|
-
.every(([key, value]) => column.spec.domain?.[key] === value),
|
|
185
|
-
)
|
|
186
|
-
.map((markupColumn) => ({ markupColumn, sequenceColumn })),
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
const groupedByDomainDiff = Map.groupBy(
|
|
190
|
-
columnPairs,
|
|
191
|
-
({ markupColumn, sequenceColumn }) => {
|
|
192
|
-
const domainDiff = Object.fromEntries(
|
|
193
|
-
Object.entries(markupColumn.spec.domain ?? {})
|
|
194
|
-
.filter(([key]) => sequenceColumn.spec.domain?.[key] == undefined),
|
|
195
|
-
);
|
|
196
|
-
return canonicalizeJson(domainDiff);
|
|
197
|
-
},
|
|
198
|
-
);
|
|
199
|
-
|
|
200
|
-
return groupedByDomainDiff.entries()
|
|
201
|
-
.map(([domainDiffJson, columnPairs]) => ({
|
|
202
|
-
label: Object.values(parseJson(domainDiffJson)).join(', '),
|
|
203
|
-
value: columnPairs.map(({ markupColumn }) => markupColumn.columnId),
|
|
204
|
-
}))
|
|
205
|
-
.toArray();
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
async function getMultipleAlignmentData(
|
|
209
|
-
{
|
|
210
|
-
pFrame,
|
|
211
|
-
sequenceColumnIds,
|
|
212
|
-
labelColumnIds,
|
|
213
|
-
selection,
|
|
214
|
-
colorScheme,
|
|
215
|
-
alignmentParams,
|
|
216
|
-
shouldBuildPhylogeneticTree,
|
|
217
|
-
}: {
|
|
218
|
-
pFrame: PFrameHandle | undefined;
|
|
219
|
-
sequenceColumnIds: PObjectId[] | undefined;
|
|
220
|
-
labelColumnIds: PTableColumnId[] | undefined;
|
|
221
|
-
selection: PlSelectionModel | undefined;
|
|
222
|
-
colorScheme: PlMultiSequenceAlignmentColorSchemeOption;
|
|
223
|
-
alignmentParams: PlMultiSequenceAlignmentSettings['alignmentParams'];
|
|
224
|
-
shouldBuildPhylogeneticTree: boolean;
|
|
225
|
-
},
|
|
226
|
-
abortSignal: AbortSignal,
|
|
227
|
-
): Promise<MultipleAlignmentData | undefined> {
|
|
228
|
-
if (!pFrame || !sequenceColumnIds?.length || !labelColumnIds) return;
|
|
229
|
-
|
|
230
|
-
const table = await getTableData({
|
|
231
|
-
pFrame,
|
|
232
|
-
sequenceColumnIds,
|
|
233
|
-
labelColumnIds,
|
|
234
|
-
selection,
|
|
235
|
-
colorScheme,
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
const rowCount = table.at(0)?.data.data.length ?? 0;
|
|
239
|
-
|
|
240
|
-
if (rowCount < 2) return;
|
|
241
|
-
|
|
242
|
-
const exceedsLimit = rowCount > SEQUENCE_LIMIT;
|
|
243
|
-
const rawSequences = extractSequences(sequenceColumnIds, table);
|
|
244
|
-
const labels = extractLabels(labelColumnIds, table);
|
|
245
|
-
const markups = colorScheme.type === 'markup'
|
|
246
|
-
? extractMarkups(colorScheme.columnIds, table)
|
|
247
|
-
: undefined;
|
|
248
|
-
|
|
249
|
-
const highlightLegend: HighlightLegend = {};
|
|
250
|
-
|
|
251
|
-
const alignedSequences = await Promise.all(
|
|
252
|
-
rawSequences.map(async ({ name, rows }) => ({
|
|
253
|
-
name,
|
|
254
|
-
rows: await alignSequences(
|
|
255
|
-
rows,
|
|
256
|
-
JSON.parse(JSON.stringify(alignmentParams)),
|
|
257
|
-
abortSignal,
|
|
258
|
-
),
|
|
259
|
-
})),
|
|
260
|
-
);
|
|
261
|
-
|
|
262
|
-
let phylogeneticTree: PhylogeneticTreeWorker.TreeNodeData[] | undefined;
|
|
263
|
-
if (shouldBuildPhylogeneticTree) {
|
|
264
|
-
phylogeneticTree = await buildPhylogeneticTree(
|
|
265
|
-
alignedSequences,
|
|
266
|
-
abortSignal,
|
|
267
|
-
);
|
|
268
|
-
const rowOrder = phylogeneticTree.values()
|
|
269
|
-
.filter(({ id }) => id >= 0)
|
|
270
|
-
.map(({ id }) => id)
|
|
271
|
-
.toArray();
|
|
272
|
-
for (const sequencesColumn of alignedSequences) {
|
|
273
|
-
sequencesColumn.rows = rowOrder.map((i) => sequencesColumn.rows[i]);
|
|
274
|
-
}
|
|
275
|
-
for (const labelsColumn of labels) {
|
|
276
|
-
labelsColumn.rows = rowOrder.map((i) => labelsColumn.rows[i]);
|
|
277
|
-
}
|
|
278
|
-
for (const markupsColumn of markups ?? []) {
|
|
279
|
-
markupsColumn.rows = rowOrder.map((i) => markupsColumn.rows[i]);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
const sequences = await Promise.all(
|
|
284
|
-
alignedSequences.map(async ({ name, rows }, index) => {
|
|
285
|
-
const residueCounts = getResidueCounts(rows);
|
|
286
|
-
const image = generateHighlightImage({
|
|
287
|
-
colorScheme,
|
|
288
|
-
sequences: rows,
|
|
289
|
-
residueCounts,
|
|
290
|
-
markup: markups?.at(index),
|
|
291
|
-
});
|
|
292
|
-
if (image) {
|
|
293
|
-
Object.assign(highlightLegend, image.legend);
|
|
294
|
-
}
|
|
295
|
-
return {
|
|
296
|
-
name,
|
|
297
|
-
rows,
|
|
298
|
-
residueCounts,
|
|
299
|
-
...image && {
|
|
300
|
-
highlightImageUrl: await blobToBase64(image.blob),
|
|
301
|
-
},
|
|
302
|
-
} satisfies MultipleAlignmentData['sequences'][number];
|
|
303
|
-
}),
|
|
304
|
-
);
|
|
305
|
-
|
|
306
|
-
return {
|
|
307
|
-
sequences,
|
|
308
|
-
labels,
|
|
309
|
-
...Object.keys(highlightLegend).length && {
|
|
310
|
-
highlightLegend,
|
|
311
|
-
},
|
|
312
|
-
...phylogeneticTree && {
|
|
313
|
-
phylogeneticTree,
|
|
314
|
-
},
|
|
315
|
-
exceedsLimit,
|
|
316
|
-
};
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
async function getTableData(
|
|
320
|
-
{ pFrame, sequenceColumnIds, labelColumnIds, selection, colorScheme }: {
|
|
321
|
-
pFrame: PFrameHandle;
|
|
322
|
-
sequenceColumnIds: PObjectId[];
|
|
323
|
-
labelColumnIds: PTableColumnId[];
|
|
324
|
-
selection: PlSelectionModel | undefined;
|
|
325
|
-
colorScheme: PlMultiSequenceAlignmentColorSchemeOption;
|
|
326
|
-
},
|
|
327
|
-
): Promise<CalculateTableDataResponse> {
|
|
328
|
-
const pFrameDriver = getPFrameDriver();
|
|
329
|
-
const columns = await pFrameDriver.listColumns(pFrame);
|
|
330
|
-
const linkerColumns = columns.filter((column) => isLinkerColumn(column.spec));
|
|
331
|
-
|
|
332
|
-
const filterColumn = createRowSelectionColumn({ selection });
|
|
333
|
-
|
|
334
|
-
// inner join of sequence columns
|
|
335
|
-
let primaryEntry: JoinEntry<PObjectId> = {
|
|
336
|
-
type: 'inner',
|
|
337
|
-
entries: sequenceColumnIds.map((column) => ({
|
|
338
|
-
type: 'column',
|
|
339
|
-
column,
|
|
340
|
-
})),
|
|
341
|
-
};
|
|
342
|
-
|
|
343
|
-
// if we have linkers, left join them
|
|
344
|
-
if (linkerColumns.length > 0) {
|
|
345
|
-
primaryEntry = {
|
|
346
|
-
type: 'outer',
|
|
347
|
-
primary: primaryEntry,
|
|
348
|
-
secondary: linkerColumns.map(({ columnId }) => ({
|
|
349
|
-
type: 'column',
|
|
350
|
-
column: columnId,
|
|
351
|
-
})),
|
|
352
|
-
};
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
// inner join with filters
|
|
356
|
-
if (filterColumn) {
|
|
357
|
-
primaryEntry = {
|
|
358
|
-
type: 'inner',
|
|
359
|
-
entries: [
|
|
360
|
-
primaryEntry,
|
|
361
|
-
{
|
|
362
|
-
type: 'inlineColumn',
|
|
363
|
-
column: filterColumn,
|
|
364
|
-
},
|
|
365
|
-
],
|
|
366
|
-
};
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
// left join with labels
|
|
370
|
-
const secondaryEntry: JoinEntry<PObjectId>[] = labelColumnIds
|
|
371
|
-
.flatMap((column) => {
|
|
372
|
-
if (column.type !== 'column') return [];
|
|
373
|
-
return { type: 'column', column: column.id };
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
// and markup
|
|
377
|
-
if (colorScheme.type === 'markup') {
|
|
378
|
-
for (const column of colorScheme.columnIds) {
|
|
379
|
-
secondaryEntry.push({ type: 'column', column });
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
const sorting: PTableSorting[] = Array.from(
|
|
384
|
-
new Set(
|
|
385
|
-
sequenceColumnIds.values().flatMap((id) => {
|
|
386
|
-
const column = columns.find(({ columnId }) => columnId === id);
|
|
387
|
-
if (!column) {
|
|
388
|
-
throw new Error(`Couldn't find sequence column (ID: ${id})`);
|
|
389
|
-
}
|
|
390
|
-
return column.spec.axesSpec
|
|
391
|
-
.map((spec) => canonicalizeJson(getAxisId(spec)));
|
|
392
|
-
}),
|
|
393
|
-
),
|
|
394
|
-
)
|
|
395
|
-
.sort()
|
|
396
|
-
.map((id) => ({
|
|
397
|
-
column: { type: 'axis', id: parseJson(id) },
|
|
398
|
-
ascending: true,
|
|
399
|
-
naAndAbsentAreLeastValues: true,
|
|
400
|
-
}));
|
|
401
|
-
|
|
402
|
-
const request: CalculateTableDataRequest<PObjectId> = {
|
|
403
|
-
src: {
|
|
404
|
-
type: 'outer',
|
|
405
|
-
primary: primaryEntry,
|
|
406
|
-
secondary: secondaryEntry,
|
|
407
|
-
},
|
|
408
|
-
filters: [],
|
|
409
|
-
sorting,
|
|
410
|
-
};
|
|
411
|
-
|
|
412
|
-
return pFrameDriver.calculateTableData(
|
|
413
|
-
pFrame,
|
|
414
|
-
JSON.parse(JSON.stringify(request)),
|
|
415
|
-
{
|
|
416
|
-
offset: 0,
|
|
417
|
-
// +1 is a hack to check whether the selection is over the limit
|
|
418
|
-
length: SEQUENCE_LIMIT + 1,
|
|
419
|
-
},
|
|
420
|
-
);
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
const extractSequences = (
|
|
424
|
-
columnIds: PObjectId[],
|
|
425
|
-
table: CalculateTableDataResponse,
|
|
426
|
-
): { name: string; rows: string[] }[] =>
|
|
427
|
-
columnIds.map((columnId) => {
|
|
428
|
-
const column = table.find(({ spec }) => spec.id === columnId);
|
|
429
|
-
if (!column) {
|
|
430
|
-
throw new Error(`Couldn't find sequence column (ID: \`${columnId}\`).`);
|
|
431
|
-
}
|
|
432
|
-
const name = readAnnotation(column.spec.spec, Annotation.Label)
|
|
433
|
-
?? 'Unlabeled column';
|
|
434
|
-
const rows = column.data.data
|
|
435
|
-
.keys()
|
|
436
|
-
.take(SEQUENCE_LIMIT)
|
|
437
|
-
.map((row) =>
|
|
438
|
-
pTableValue(column.data, row, { absent: '', na: '' })?.toString()
|
|
439
|
-
?? '',
|
|
440
|
-
)
|
|
441
|
-
.toArray();
|
|
442
|
-
return { name, rows };
|
|
443
|
-
});
|
|
444
|
-
|
|
445
|
-
const extractLabels = (
|
|
446
|
-
columnIds: PTableColumnId[],
|
|
447
|
-
table: CalculateTableDataResponse,
|
|
448
|
-
): { rows: string[] }[] =>
|
|
449
|
-
columnIds.map((columnId) => {
|
|
450
|
-
const column = table.find(({ spec }) => {
|
|
451
|
-
if (columnId.type === 'axis' && spec.type === 'axis') {
|
|
452
|
-
return isJsonEqual(columnId.id, spec.id);
|
|
453
|
-
}
|
|
454
|
-
if (columnId.type === 'column' && spec.type === 'column') {
|
|
455
|
-
return columnId.id === spec.id;
|
|
456
|
-
}
|
|
457
|
-
});
|
|
458
|
-
if (!column) {
|
|
459
|
-
throw new Error(`Couldn't find label column (ID: \`${columnId}\`).`);
|
|
460
|
-
}
|
|
461
|
-
const rows = column.data.data
|
|
462
|
-
.keys()
|
|
463
|
-
.take(SEQUENCE_LIMIT)
|
|
464
|
-
.map((row) =>
|
|
465
|
-
pTableValue(column.data, row, { absent: '', na: '' })?.toString()
|
|
466
|
-
?? '',
|
|
467
|
-
)
|
|
468
|
-
.toArray();
|
|
469
|
-
return { rows };
|
|
470
|
-
});
|
|
471
|
-
|
|
472
|
-
const extractMarkups = (
|
|
473
|
-
columnIds: PObjectId[],
|
|
474
|
-
table: CalculateTableDataResponse,
|
|
475
|
-
): { labels: Record<string, string>; rows: Markup[] }[] =>
|
|
476
|
-
columnIds.map((columnId) => {
|
|
477
|
-
const column = table.find(({ spec }) => spec.id === columnId);
|
|
478
|
-
if (!column) {
|
|
479
|
-
throw new Error(`Couldn't find markup column (ID: \`${columnId}\`).`);
|
|
480
|
-
}
|
|
481
|
-
const labels = readAnnotationJson(
|
|
482
|
-
column.spec.spec,
|
|
483
|
-
Annotation.Sequence.Annotation.Mapping,
|
|
484
|
-
) ?? {};
|
|
485
|
-
const rows = column.data.data
|
|
486
|
-
.keys()
|
|
487
|
-
.take(SEQUENCE_LIMIT)
|
|
488
|
-
.map((row) =>
|
|
489
|
-
parseMarkup(
|
|
490
|
-
pTableValue(column.data, row, { absent: '', na: '' })?.toString()
|
|
491
|
-
?? '',
|
|
492
|
-
),
|
|
493
|
-
)
|
|
494
|
-
.toArray();
|
|
495
|
-
return { labels, rows };
|
|
496
|
-
});
|
|
497
|
-
|
|
498
|
-
const alignSequences = (() => {
|
|
499
|
-
const cache = new Map<string, string[]>();
|
|
500
|
-
return async (
|
|
501
|
-
sequences: string[],
|
|
502
|
-
alignmentParams: PlMultiSequenceAlignmentSettings['alignmentParams'],
|
|
503
|
-
abortSignal: AbortSignal,
|
|
504
|
-
): Promise<string[]> => {
|
|
505
|
-
const hash = await objectHash([sequences, alignmentParams]);
|
|
506
|
-
let result = cache.get(hash);
|
|
507
|
-
if (result) return result;
|
|
508
|
-
result = await runInWorker<
|
|
509
|
-
MultiSequenceAlignmentWorker.RequestMessage,
|
|
510
|
-
MultiSequenceAlignmentWorker.ResponseMessage
|
|
511
|
-
>(
|
|
512
|
-
new Worker(
|
|
513
|
-
new URL('./multi-sequence-alignment.worker.ts', import.meta.url),
|
|
514
|
-
{ type: 'module' },
|
|
515
|
-
),
|
|
516
|
-
{ sequences, params: alignmentParams },
|
|
517
|
-
abortSignal,
|
|
518
|
-
);
|
|
519
|
-
cache.set(hash, result);
|
|
520
|
-
return result;
|
|
521
|
-
};
|
|
522
|
-
})();
|
|
523
|
-
|
|
524
|
-
function generateHighlightImage(
|
|
525
|
-
{ colorScheme, sequences, residueCounts, markup }: {
|
|
526
|
-
colorScheme: PlMultiSequenceAlignmentColorSchemeOption;
|
|
527
|
-
sequences: string[];
|
|
528
|
-
residueCounts: ResidueCounts;
|
|
529
|
-
markup: { labels: Record<string, string>; rows: Markup[] } | undefined;
|
|
530
|
-
},
|
|
531
|
-
): { blob: Blob; legend: HighlightLegend } | undefined {
|
|
532
|
-
if (colorScheme.type === 'chemical-properties') {
|
|
533
|
-
return highlightByChemicalProperties({ sequences, residueCounts });
|
|
534
|
-
}
|
|
535
|
-
if (colorScheme.type === 'markup') {
|
|
536
|
-
if (!markup) {
|
|
537
|
-
throw new Error('Missing markup data.');
|
|
538
|
-
}
|
|
539
|
-
return highlightByMarkup({
|
|
540
|
-
markupRows: sequences.map((sequence, row) => {
|
|
541
|
-
const markupRow = markup.rows.at(row);
|
|
542
|
-
if (!markupRow) throw new Error(`Missing markup for row ${row}.`);
|
|
543
|
-
return markupAlignedSequence(sequence, markupRow);
|
|
544
|
-
}),
|
|
545
|
-
columnCount: sequences.at(0)?.length ?? 0,
|
|
546
|
-
labels: markup.labels,
|
|
547
|
-
});
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
const blobToBase64 = (blob: Blob): Promise<string> =>
|
|
552
|
-
new Promise<string>((resolve, reject) => {
|
|
553
|
-
const reader = new FileReader();
|
|
554
|
-
reader.addEventListener('load', () => resolve(reader.result as string));
|
|
555
|
-
reader.addEventListener('error', () => reject(reader.error));
|
|
556
|
-
reader.readAsDataURL(blob);
|
|
557
|
-
});
|
|
558
|
-
|
|
559
|
-
const buildPhylogeneticTree = (() => {
|
|
560
|
-
const cache = new Map<string, PhylogeneticTreeWorker.TreeNodeData[]>();
|
|
561
|
-
return async (data: { rows: string[] }[], abortSignal: AbortSignal) => {
|
|
562
|
-
const concatenatedSequences = data.at(0)?.rows
|
|
563
|
-
.keys()
|
|
564
|
-
.map((row) => data.map((column) => column.rows.at(row) ?? '').join(''))
|
|
565
|
-
.toArray() ?? [];
|
|
566
|
-
const hash = await objectHash(concatenatedSequences);
|
|
567
|
-
let result = cache.get(hash);
|
|
568
|
-
if (result) return result;
|
|
569
|
-
result = await runInWorker<
|
|
570
|
-
PhylogeneticTreeWorker.RequestMessage,
|
|
571
|
-
PhylogeneticTreeWorker.ResponseMessage
|
|
572
|
-
>(
|
|
573
|
-
new Worker(
|
|
574
|
-
new URL('./phylogenetic-tree.worker.ts', import.meta.url),
|
|
575
|
-
{ type: 'module' },
|
|
576
|
-
),
|
|
577
|
-
concatenatedSequences,
|
|
578
|
-
abortSignal,
|
|
579
|
-
);
|
|
580
|
-
cache.set(hash, result);
|
|
581
|
-
return result;
|
|
582
|
-
};
|
|
583
|
-
})();
|
|
584
|
-
|
|
585
|
-
const runInWorker = <RequestMessage, ResponseMessage>(
|
|
586
|
-
worker: Worker,
|
|
587
|
-
message: RequestMessage,
|
|
588
|
-
abortSignal: AbortSignal,
|
|
589
|
-
) =>
|
|
590
|
-
new Promise<ResponseMessage>((resolve, reject) => {
|
|
591
|
-
worker.addEventListener('message', ({ data }) => {
|
|
592
|
-
resolve(data);
|
|
593
|
-
worker.terminate();
|
|
594
|
-
});
|
|
595
|
-
worker.addEventListener('error', ({ error, message }) => {
|
|
596
|
-
reject(error ?? message);
|
|
597
|
-
worker.terminate();
|
|
598
|
-
});
|
|
599
|
-
abortSignal.addEventListener('abort', () => {
|
|
600
|
-
reject(abortSignal.reason);
|
|
601
|
-
worker.terminate();
|
|
602
|
-
});
|
|
603
|
-
worker.postMessage(message);
|
|
604
|
-
});
|
|
605
|
-
|
|
606
|
-
function refreshOnDeepChange<T, P>(
|
|
607
|
-
cb: (params: P, abortSignal: AbortSignal) => Promise<T>,
|
|
608
|
-
) {
|
|
609
|
-
const data = ref<T>();
|
|
610
|
-
const isLoading = ref(true);
|
|
611
|
-
const error = ref<Error>();
|
|
612
|
-
let requestId: symbol;
|
|
613
|
-
return (paramsGetter: () => P) => {
|
|
614
|
-
watch(paramsGetter, async (params, prevParams) => {
|
|
615
|
-
if (isJsonEqual(params, prevParams)) return;
|
|
616
|
-
const abortController = new AbortController();
|
|
617
|
-
const currentRequestId = requestId = Symbol();
|
|
618
|
-
onWatcherCleanup(() => {
|
|
619
|
-
abortController.abort();
|
|
620
|
-
});
|
|
621
|
-
try {
|
|
622
|
-
error.value = undefined;
|
|
623
|
-
isLoading.value = true;
|
|
624
|
-
const result = await cb(params, abortController.signal);
|
|
625
|
-
if (currentRequestId === requestId) {
|
|
626
|
-
data.value = result;
|
|
627
|
-
}
|
|
628
|
-
} catch (err) {
|
|
629
|
-
console.error(err);
|
|
630
|
-
if (currentRequestId === requestId) {
|
|
631
|
-
error.value = ensureError(err);
|
|
632
|
-
}
|
|
633
|
-
} finally {
|
|
634
|
-
if (currentRequestId === requestId) {
|
|
635
|
-
isLoading.value = false;
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
}, { immediate: true });
|
|
639
|
-
return { data, isLoading, error };
|
|
640
|
-
};
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
type MultipleAlignmentData = {
|
|
644
|
-
sequences: {
|
|
645
|
-
name: string;
|
|
646
|
-
rows: string[];
|
|
647
|
-
residueCounts: ResidueCounts;
|
|
648
|
-
highlightImageUrl?: string;
|
|
649
|
-
}[];
|
|
650
|
-
labels: {
|
|
651
|
-
rows: string[];
|
|
652
|
-
}[];
|
|
653
|
-
highlightLegend?: HighlightLegend;
|
|
654
|
-
phylogeneticTree?: PhylogeneticTreeWorker.TreeNodeData[];
|
|
655
|
-
exceedsLimit: boolean;
|
|
656
|
-
};
|
|
657
|
-
|
|
658
|
-
type OptionsWithDefaults<T> = {
|
|
659
|
-
options: ListOptionNormalized<T>[];
|
|
660
|
-
defaults: T[];
|
|
661
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { default as PlMultiSequenceAlignment } from './PlMultiSequenceAlignment.vue';
|