@milaboratories/multi-sequence-alignment 1.45.0

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.
Files changed (114) hide show
  1. package/.turbo/turbo-build.log +52 -0
  2. package/.turbo/turbo-lint.log +12 -0
  3. package/.turbo/turbo-test.log +13 -0
  4. package/.turbo/turbo-type-check.log +6 -0
  5. package/dist/Consensus.vue.d.ts +9 -0
  6. package/dist/Consensus.vue.js +10 -0
  7. package/dist/Consensus.vue.js.map +1 -0
  8. package/dist/Consensus.vue2.js +122 -0
  9. package/dist/Consensus.vue2.js.map +1 -0
  10. package/dist/Consensus.vue3.js +9 -0
  11. package/dist/Consensus.vue3.js.map +1 -0
  12. package/dist/Legend.vue.d.ts +6 -0
  13. package/dist/Legend.vue.js +10 -0
  14. package/dist/Legend.vue.js.map +1 -0
  15. package/dist/Legend.vue2.js +28 -0
  16. package/dist/Legend.vue2.js.map +1 -0
  17. package/dist/Legend.vue3.js +13 -0
  18. package/dist/Legend.vue3.js.map +1 -0
  19. package/dist/MultiSequenceAlignmentView.vue.d.ts +25 -0
  20. package/dist/MultiSequenceAlignmentView.vue.js +10 -0
  21. package/dist/MultiSequenceAlignmentView.vue.js.map +1 -0
  22. package/dist/MultiSequenceAlignmentView.vue2.js +138 -0
  23. package/dist/MultiSequenceAlignmentView.vue2.js.map +1 -0
  24. package/dist/MultiSequenceAlignmentView.vue3.js +31 -0
  25. package/dist/MultiSequenceAlignmentView.vue3.js.map +1 -0
  26. package/dist/PhylogeneticTree.vue.d.ts +8 -0
  27. package/dist/PhylogeneticTree.vue.js +10 -0
  28. package/dist/PhylogeneticTree.vue.js.map +1 -0
  29. package/dist/PhylogeneticTree.vue2.js +77 -0
  30. package/dist/PhylogeneticTree.vue2.js.map +1 -0
  31. package/dist/PhylogeneticTree.vue3.js +9 -0
  32. package/dist/PhylogeneticTree.vue3.js.map +1 -0
  33. package/dist/PlMultiSequenceAlignment.vue.d.ts +71 -0
  34. package/dist/PlMultiSequenceAlignment.vue.js +10 -0
  35. package/dist/PlMultiSequenceAlignment.vue.js.map +1 -0
  36. package/dist/PlMultiSequenceAlignment.vue2.js +224 -0
  37. package/dist/PlMultiSequenceAlignment.vue2.js.map +1 -0
  38. package/dist/PlMultiSequenceAlignment.vue3.js +9 -0
  39. package/dist/PlMultiSequenceAlignment.vue3.js.map +1 -0
  40. package/dist/SeqLogo.vue.d.ts +8 -0
  41. package/dist/SeqLogo.vue.js +10 -0
  42. package/dist/SeqLogo.vue.js.map +1 -0
  43. package/dist/SeqLogo.vue2.js +127 -0
  44. package/dist/SeqLogo.vue2.js.map +1 -0
  45. package/dist/SeqLogo.vue3.js +9 -0
  46. package/dist/SeqLogo.vue3.js.map +1 -0
  47. package/dist/Toolbar.vue.d.ts +16 -0
  48. package/dist/Toolbar.vue.js +10 -0
  49. package/dist/Toolbar.vue.js.map +1 -0
  50. package/dist/Toolbar.vue2.js +228 -0
  51. package/dist/Toolbar.vue2.js.map +1 -0
  52. package/dist/Toolbar.vue3.js +19 -0
  53. package/dist/Toolbar.vue3.js.map +1 -0
  54. package/dist/_virtual/_plugin-vue_export-helper.js +10 -0
  55. package/dist/_virtual/_plugin-vue_export-helper.js.map +1 -0
  56. package/dist/assets/multi-sequence-alignment.worker-Cm0gZp19.js +6 -0
  57. package/dist/assets/multi-sequence-alignment.worker-Cm0gZp19.js.map +1 -0
  58. package/dist/assets/phylogenetic-tree.worker-4CrExYEo.js +5 -0
  59. package/dist/assets/phylogenetic-tree.worker-4CrExYEo.js.map +1 -0
  60. package/dist/cell-size.d.ts +4 -0
  61. package/dist/cell-size.js +8 -0
  62. package/dist/cell-size.js.map +1 -0
  63. package/dist/chemical-properties.d.ts +44 -0
  64. package/dist/chemical-properties.js +132 -0
  65. package/dist/chemical-properties.js.map +1 -0
  66. package/dist/data.d.ts +61 -0
  67. package/dist/data.js +370 -0
  68. package/dist/data.js.map +1 -0
  69. package/dist/index.d.ts +1 -0
  70. package/dist/index.js +6 -0
  71. package/dist/index.js.map +1 -0
  72. package/dist/markup.d.ts +16 -0
  73. package/dist/markup.js +84 -0
  74. package/dist/markup.js.map +1 -0
  75. package/dist/migrations.d.ts +3 -0
  76. package/dist/migrations.js +24 -0
  77. package/dist/migrations.js.map +1 -0
  78. package/dist/multi-sequence-alignment.worker.d.ts +6 -0
  79. package/dist/node_modules/.pnpm/@milaboratories_helpers@1.12.0/node_modules/@milaboratories/helpers/dist/objects.js +33 -0
  80. package/dist/node_modules/.pnpm/@milaboratories_helpers@1.12.0/node_modules/@milaboratories/helpers/dist/objects.js.map +1 -0
  81. package/dist/phylogenetic-tree.worker.d.ts +7 -0
  82. package/dist/residue-counts.d.ts +2 -0
  83. package/dist/residue-counts.js +13 -0
  84. package/dist/residue-counts.js.map +1 -0
  85. package/dist/settings.d.ts +2 -0
  86. package/dist/settings.js +9 -0
  87. package/dist/settings.js.map +1 -0
  88. package/dist/types.d.ts +5 -0
  89. package/dist/useMiPlots.d.ts +4 -0
  90. package/dist/useMiPlots.js +19 -0
  91. package/dist/useMiPlots.js.map +1 -0
  92. package/eslint.config.js +66 -0
  93. package/package.json +45 -0
  94. package/src/Consensus.vue +165 -0
  95. package/src/Legend.vue +44 -0
  96. package/src/MultiSequenceAlignmentView.vue +299 -0
  97. package/src/PhylogeneticTree.vue +110 -0
  98. package/src/PlMultiSequenceAlignment.vue +314 -0
  99. package/src/README.md +216 -0
  100. package/src/SeqLogo.vue +166 -0
  101. package/src/Toolbar.vue +228 -0
  102. package/src/cell-size.ts +4 -0
  103. package/src/chemical-properties.ts +199 -0
  104. package/src/data.ts +661 -0
  105. package/src/index.ts +2 -0
  106. package/src/markup.ts +141 -0
  107. package/src/migrations.ts +46 -0
  108. package/src/multi-sequence-alignment.worker.ts +54 -0
  109. package/src/phylogenetic-tree.worker.ts +89 -0
  110. package/src/residue-counts.ts +124 -0
  111. package/src/settings.ts +7 -0
  112. package/src/types.ts +3 -0
  113. package/src/useMiPlots.ts +23 -0
  114. package/tsconfig.json +10 -0
@@ -0,0 +1,165 @@
1
+ <script lang="ts" setup>
2
+ import type {
3
+ ChartInterface,
4
+ DataByColumns,
5
+ Settings,
6
+ } from '@milaboratories/miplots4';
7
+ import { PlAlert } from '@milaboratories/uikit';
8
+ import {
9
+ computed,
10
+ onBeforeUnmount,
11
+ shallowRef,
12
+ useCssModule,
13
+ useTemplateRef,
14
+ watchEffect,
15
+ } from 'vue';
16
+ import { cellSize } from './cell-size';
17
+ import type { ResidueCounts } from './types';
18
+ import { useMiPlots } from './useMiPlots';
19
+
20
+ const props = defineProps<{
21
+ residueCounts: ResidueCounts;
22
+ labelsClass: string;
23
+ }>();
24
+
25
+ const classes = useCssModule();
26
+
27
+ const plotEl = useTemplateRef('plotEl');
28
+
29
+ const columns = computed(() =>
30
+ props.residueCounts.map((column) => {
31
+ let totalCount = 0;
32
+ let topResidue = { label: '', count: 0 };
33
+ for (const [residue, count] of Object.entries(column)) {
34
+ totalCount += count;
35
+ if (residue === '-') continue;
36
+ if (count > topResidue.count) topResidue = { label: residue, count };
37
+ }
38
+ const confidence = CSS.percent(topResidue.count / totalCount * 100);
39
+ return {
40
+ label: topResidue.label,
41
+ color: `color-mix(in oklab, ${confidence} #3056AE, #C1CDE9)`,
42
+ };
43
+ }),
44
+ );
45
+
46
+ const settings = computed(() => {
47
+ const width = props.residueCounts.length * cellSize.inline;
48
+ return ({
49
+ type: 'discrete',
50
+ y: {
51
+ type: 'column',
52
+ value: 'countKey',
53
+ },
54
+ legend: { show: false },
55
+ primaryGrouping: {
56
+ columnName: {
57
+ type: 'column',
58
+ value: 'columnKey',
59
+ },
60
+ order: props.residueCounts.map((_, i) => i),
61
+ inheritedAes: Object.fromEntries(
62
+ columns.value.map(({ color }) => ({ fillColor: color })).entries(),
63
+ ),
64
+ },
65
+ layers: [{
66
+ type: 'bar',
67
+ height: 'max',
68
+ aes: {
69
+ ...props.residueCounts.length && {
70
+ width: (width - props.residueCounts.length + 1)
71
+ / props.residueCounts.length,
72
+ },
73
+ fillColor: {
74
+ type: 'primaryGrouping',
75
+ },
76
+ lineColor: '#ffffff',
77
+ },
78
+ }],
79
+ title: {
80
+ name: '',
81
+ show: false,
82
+ },
83
+ size: {
84
+ width,
85
+ height: 60,
86
+ outerOffset: 0,
87
+ innerOffset: 0,
88
+ },
89
+ xAxis: {
90
+ title: '',
91
+ showGrid: false,
92
+ showTicks: false,
93
+ hiddenLabels: true,
94
+ },
95
+ yAxis: {
96
+ title: '',
97
+ showGrid: false,
98
+ showTicks: false,
99
+ hiddenLabels: true,
100
+ },
101
+ frame: {
102
+ type: 'empty',
103
+ },
104
+ } satisfies Settings);
105
+ });
106
+
107
+ const data = computed<DataByColumns>(() => {
108
+ const countKey: number[] = [];
109
+ const columnKey: number[] = [];
110
+ for (const [columnIndex, column] of props.residueCounts.entries()) {
111
+ for (const [residue, count] of Object.entries(column)) {
112
+ if (residue === '-') continue;
113
+ countKey.push(count);
114
+ columnKey.push(columnIndex);
115
+ }
116
+ }
117
+ return ({
118
+ type: 'columns',
119
+ id: `consensus-${crypto.randomUUID()}`,
120
+ values: { countKey, columnKey },
121
+ });
122
+ });
123
+
124
+ const plot = shallowRef<ChartInterface>();
125
+
126
+ const { miplots, error } = useMiPlots();
127
+
128
+ watchEffect(async () => {
129
+ if (!plotEl.value || !miplots.value) return;
130
+ if (!plot.value) {
131
+ plot.value = miplots.value.newPlot(data.value, settings.value);
132
+ plot.value.mount(plotEl.value);
133
+ } else {
134
+ plot.value.updateSettingsAndData(data.value, settings.value);
135
+ }
136
+ });
137
+
138
+ onBeforeUnmount(() => {
139
+ plot.value?.unmount();
140
+ });
141
+ </script>
142
+
143
+ <template>
144
+ <PlAlert v-if="error" type="error">
145
+ {{ error.message }}
146
+ </PlAlert>
147
+ <div v-else :class="classes.container">
148
+ <div :class="props.labelsClass">
149
+ {{ columns.map(column => column.label).join('') }}
150
+ </div>
151
+ <div ref="plotEl" />
152
+ </div>
153
+ </template>
154
+
155
+ <style module>
156
+ .container {
157
+ display: flex;
158
+ flex-direction: column;
159
+ gap: 4px;
160
+
161
+ svg {
162
+ display: block;
163
+ }
164
+ }
165
+ </style>
package/src/Legend.vue ADDED
@@ -0,0 +1,44 @@
1
+ <script setup lang="ts">
2
+ import { useCssModule } from 'vue';
3
+ import type { HighlightLegend } from './types';
4
+
5
+ const props = defineProps<{
6
+ legend: HighlightLegend;
7
+ }>();
8
+
9
+ const classes = useCssModule();
10
+ </script>
11
+
12
+ <template>
13
+ <div :class="classes.container">
14
+ <div
15
+ v-for="({ label, color }, key) of props.legend"
16
+ :key="key"
17
+ :class="classes.item"
18
+ >
19
+ <div :class="classes.colorSample" :style="{ backgroundColor: color }" />
20
+ {{ label }}
21
+ </div>
22
+ </div>
23
+ </template>
24
+
25
+ <style module>
26
+ .container {
27
+ display: flex;
28
+ flex-wrap: wrap;
29
+ gap: 12px;
30
+ max-inline-size: fit-content;
31
+ }
32
+
33
+ .item {
34
+ display: flex;
35
+ gap: 4px;
36
+ }
37
+
38
+ .colorSample {
39
+ display: inline-block;
40
+ block-size: 18px;
41
+ inline-size: 18px;
42
+ border-radius: 3px;
43
+ }
44
+ </style>
@@ -0,0 +1,299 @@
1
+ <script lang="ts" setup>
2
+ import type { PlMultiSequenceAlignmentWidget } from '@platforma-sdk/model';
3
+ import {
4
+ computed,
5
+ onBeforeMount,
6
+ onBeforeUnmount,
7
+ onWatcherCleanup,
8
+ ref,
9
+ useCssModule,
10
+ useTemplateRef,
11
+ watch,
12
+ } from 'vue';
13
+ import { cellSize } from './cell-size';
14
+ import Consensus from './Consensus.vue';
15
+ import Legend from './Legend.vue';
16
+ import type { TreeNodeData } from './phylogenetic-tree.worker';
17
+ import PhylogeneticTree from './PhylogeneticTree.vue';
18
+ import SeqLogo from './SeqLogo.vue';
19
+ import type { HighlightLegend, ResidueCounts } from './types';
20
+
21
+ const props = defineProps<{
22
+ sequences: {
23
+ name: string;
24
+ rows: string[];
25
+ residueCounts: ResidueCounts;
26
+ highlightImageUrl?: string;
27
+ }[];
28
+ labels: {
29
+ rows: string[];
30
+ }[];
31
+ highlightLegend: HighlightLegend | undefined;
32
+ phylogeneticTree: TreeNodeData[] | undefined;
33
+ widgets: PlMultiSequenceAlignmentWidget[];
34
+ }>();
35
+
36
+ const classes = useCssModule();
37
+
38
+ const rootEl = useTemplateRef('rootRef');
39
+ defineExpose({ rootEl });
40
+
41
+ const rowCount = computed(() => props.sequences.at(0)?.rows.length ?? 0);
42
+
43
+ const targetCellInlineSize = CSS.px(cellSize.inline).toString();
44
+ const targetCellBlockSize = CSS.px(cellSize.block).toString();
45
+
46
+ const referenceCellRef = useTemplateRef('referenceCell');
47
+ const referenceCellInlineSize = ref<number>();
48
+
49
+ const cornerRef = useTemplateRef('corner');
50
+ const cornerInlineSize = ref<number>();
51
+
52
+ const letterSpacing = computed(() =>
53
+ referenceCellInlineSize.value
54
+ ? CSS.px(cellSize.inline - referenceCellInlineSize.value).toString()
55
+ : undefined,
56
+ );
57
+
58
+ const sequenceNameInsetInlineStart = computed(() =>
59
+ CSS.px(cornerInlineSize.value ?? 0).toString(),
60
+ );
61
+
62
+ let observer: ResizeObserver;
63
+
64
+ onBeforeMount(() => {
65
+ const getInlineSize = (entry: ResizeObserverEntry) =>
66
+ entry.borderBoxSize.find(Boolean)?.inlineSize;
67
+
68
+ observer = new ResizeObserver((entries) => {
69
+ for (const entry of entries) {
70
+ switch (entry.target) {
71
+ case referenceCellRef.value:
72
+ referenceCellInlineSize.value = getInlineSize(entry);
73
+ break;
74
+ case cornerRef.value:
75
+ cornerInlineSize.value = getInlineSize(entry);
76
+ break;
77
+ }
78
+ }
79
+ });
80
+ });
81
+
82
+ onBeforeUnmount(() => {
83
+ observer.disconnect();
84
+ });
85
+
86
+ for (const ref of [referenceCellRef, cornerRef]) {
87
+ watch(ref, (el, prevEl) => {
88
+ if (el) observer.observe(el);
89
+ onWatcherCleanup(() => {
90
+ if (prevEl) observer.unobserve(prevEl);
91
+ });
92
+ });
93
+ }
94
+ </script>
95
+
96
+ <template>
97
+ <div ref="rootRef" :class="classes.root">
98
+ <div ref="referenceCell" :class="classes.referenceCell">x</div>
99
+ <div :class="['pl-scrollable', classes.table]">
100
+ <div :class="classes.sidebar">
101
+ <PhylogeneticTree
102
+ v-if="props.widgets.includes('tree') && props.phylogeneticTree"
103
+ :tree="props.phylogeneticTree"
104
+ :class="classes.phylogeneticTree"
105
+ />
106
+ <div :class="classes.labels">
107
+ <template
108
+ v-for="({ rows }, columnIndex) of props.labels"
109
+ :key="columnIndex"
110
+ >
111
+ <div v-for="(row, rowIndex) of rows" :key="rowIndex">
112
+ {{ row }}
113
+ </div>
114
+ </template>
115
+ </div>
116
+ </div>
117
+ <template v-if="letterSpacing !== undefined">
118
+ <div
119
+ v-for="(column, columnIndex) of props.sequences"
120
+ :key="columnIndex"
121
+ :class="classes.sequenceColumn"
122
+ >
123
+ <div :class="classes.sequenceHeader">
124
+ <div
125
+ v-show="props.sequences.length > 1"
126
+ :class="classes.sequenceName"
127
+ >
128
+ {{ column.name }}
129
+ </div>
130
+ <Consensus
131
+ v-if="props.widgets.includes('consensus')"
132
+ :residue-counts="column.residueCounts"
133
+ :labels-class="classes.sequenceRow"
134
+ />
135
+ <SeqLogo
136
+ v-if="props.widgets.includes('seqLogo')"
137
+ :residue-counts="column.residueCounts"
138
+ />
139
+ </div>
140
+ <div
141
+ :class="classes.sequenceRowsContainer"
142
+ :style="{
143
+ backgroundImage: column.highlightImageUrl
144
+ ? `url(${column.highlightImageUrl})`
145
+ : undefined,
146
+ }"
147
+ >
148
+ <div
149
+ v-for="(row, rowIndex) of column.rows"
150
+ :key="rowIndex"
151
+ :class="classes.sequenceRow"
152
+ >
153
+ {{ row }}
154
+ </div>
155
+ </div>
156
+ </div>
157
+ </template>
158
+ <div ref="corner" :class="classes.corner" />
159
+ </div>
160
+ <Legend
161
+ v-if="props.widgets.includes('legend') && props.highlightLegend"
162
+ :legend="props.highlightLegend"
163
+ />
164
+ </div>
165
+ </template>
166
+
167
+ <style module>
168
+ .root {
169
+ display: flex;
170
+ flex-direction: column;
171
+ gap: 12px;
172
+ min-block-size: 0;
173
+ -webkit-print-color-adjust: exact;
174
+ print-color-adjust: exact;
175
+ container-type: inline-size;
176
+
177
+ &[data-pre-print] {
178
+ container-type: unset;
179
+
180
+ .sidebar {
181
+ max-inline-size: unset;
182
+ }
183
+ }
184
+ }
185
+
186
+ .referenceCell {
187
+ position: fixed;
188
+ visibility: hidden;
189
+ font-family: Spline Sans Mono;
190
+ font-weight: 600;
191
+ line-height: v-bind('targetCellBlockSize');
192
+ }
193
+
194
+ .table {
195
+ display: grid;
196
+ grid-template-columns:
197
+ [sidebar-start] auto [sidebar-end] repeat(
198
+ v-bind('props.sequences.length'),
199
+ [column-start] auto [column-end]
200
+ );
201
+ grid-template-rows:
202
+ [header-start] auto [header-end]
203
+ repeat(v-bind('rowCount'), [row-start] auto [row-end]);
204
+ justify-content: start;
205
+ position: relative;
206
+ @media print {
207
+ overflow: visible;
208
+ }
209
+ }
210
+
211
+ .sidebar {
212
+ grid-column: sidebar;
213
+ grid-row: 1 row-start / -1 row-end;
214
+ display: grid;
215
+ grid-template-rows: subgrid;
216
+ position: sticky;
217
+ inset-inline-start: 0;
218
+ background-color: #fff;
219
+ inline-size: min-content;
220
+ max-inline-size: 30cqi;
221
+ overflow: scroll;
222
+ overscroll-behavior-inline: none;
223
+ scrollbar-width: none;
224
+ }
225
+
226
+ .phylogeneticTree {
227
+ grid-row: 1 row-start / -1 row-end;
228
+ }
229
+
230
+ .labels {
231
+ grid-row: 1 row-start / -1 row-end;
232
+ display: grid;
233
+ grid-template-columns: repeat(v-bind('props.labels.length'), auto);
234
+ grid-template-rows: subgrid;
235
+ grid-auto-flow: column;
236
+ column-gap: 12px;
237
+ padding-inline-end: 12px;
238
+ font-family: Spline Sans Mono;
239
+ line-height: v-bind('targetCellBlockSize');
240
+ white-space: nowrap;
241
+ }
242
+
243
+ .sequenceColumn {
244
+ grid-row: header-start / -1 row-end;
245
+ display: grid;
246
+ grid-template-rows: subgrid;
247
+ & + & {
248
+ margin-inline-start: 24px;
249
+ }
250
+ }
251
+
252
+ .sequenceHeader {
253
+ grid-row: header;
254
+ display: flex;
255
+ flex-direction: column;
256
+ justify-content: end;
257
+ min-inline-size: 0;
258
+ position: sticky;
259
+ inset-block-start: 0;
260
+ background-color: #fff;
261
+ }
262
+
263
+ .sequenceName {
264
+ margin-block-end: 4px;
265
+ font-weight: 700;
266
+ line-height: 20px;
267
+ inline-size: fit-content;
268
+ position: sticky;
269
+ inset-inline-start: v-bind('sequenceNameInsetInlineStart');
270
+ }
271
+
272
+ .sequenceRowsContainer {
273
+ grid-row: 1 row-start / -1 row-end;
274
+ display: grid;
275
+ grid-template-rows: subgrid;
276
+ }
277
+
278
+ .sequenceRow {
279
+ font-family: Spline Sans Mono;
280
+ font-weight: 600;
281
+ line-height: v-bind('targetCellBlockSize');
282
+ letter-spacing: v-bind('letterSpacing');
283
+ text-indent: calc(v-bind('letterSpacing') / 2);
284
+ inline-size: calc-size(
285
+ min-content,
286
+ round(down, size, v-bind('targetCellInlineSize'))
287
+ );
288
+ white-space: nowrap;
289
+ }
290
+
291
+ .corner {
292
+ grid-column: sidebar;
293
+ grid-row: header;
294
+ position: sticky;
295
+ inset-inline-start: 0;
296
+ inset-block-start: 0;
297
+ background-color: #fff;
298
+ }
299
+ </style>
@@ -0,0 +1,110 @@
1
+ <script lang="ts" setup>
2
+ import type {
3
+ ChartInterface,
4
+ DataByColumns,
5
+ Settings,
6
+ } from '@milaboratories/miplots4';
7
+ import { PlAlert } from '@milaboratories/uikit';
8
+ import {
9
+ computed,
10
+ onBeforeUnmount,
11
+ shallowRef,
12
+ useCssModule,
13
+ useTemplateRef,
14
+ watchEffect,
15
+ } from 'vue';
16
+ import { cellSize } from './cell-size';
17
+ import type { TreeNodeData } from './phylogenetic-tree.worker';
18
+ import { useMiPlots } from './useMiPlots';
19
+
20
+ const props = defineProps<{
21
+ tree: TreeNodeData[];
22
+ }>();
23
+
24
+ const classes = useCssModule();
25
+
26
+ const plotEl = useTemplateRef('plotEl');
27
+
28
+ const settings: Settings = {
29
+ type: 'dendro',
30
+ title: { show: false, name: '' },
31
+ size: {
32
+ width: 149,
33
+ minCellHeight: cellSize.block,
34
+ maxCellHeight: cellSize.block,
35
+ marginLeft: 1,
36
+ marginRight: 0,
37
+ marginTop: 0,
38
+ marginBottom: 0,
39
+ },
40
+ mode: 'normal',
41
+ leavesMode: 'alignLeavesToLine',
42
+ leavesOrder: 'indexAsc',
43
+ legend: { show: false },
44
+ id: {
45
+ type: 'column',
46
+ value: 'nodeId',
47
+ },
48
+ parentId: {
49
+ type: 'column',
50
+ value: 'parentId',
51
+ },
52
+ height: {
53
+ type: 'column',
54
+ value: 'distance',
55
+ },
56
+ showNodes: false,
57
+ showNodesLabels: false,
58
+ showLeavesLabels: false,
59
+ rootPosition: 'left',
60
+ };
61
+
62
+ const data = computed<DataByColumns>(() => {
63
+ const nodeId: number[] = [];
64
+ const parentId: (number | null)[] = [];
65
+ const distance: number[] = [];
66
+ for (const node of props.tree) {
67
+ nodeId.push(node.id);
68
+ parentId.push(node.parentId ?? null);
69
+ distance.push(node.length ?? 0);
70
+ }
71
+ return ({
72
+ type: 'columns',
73
+ id: `phylogeneticTree-${crypto.randomUUID()}`,
74
+ values: { nodeId, parentId, distance },
75
+ });
76
+ });
77
+
78
+ const { miplots, error } = useMiPlots();
79
+
80
+ const plot = shallowRef<ChartInterface>();
81
+
82
+ watchEffect(async () => {
83
+ if (!plotEl.value || !miplots.value) return;
84
+ if (!plot.value) {
85
+ plot.value = miplots.value.newPlot(data.value, settings);
86
+ plot.value.mount(plotEl.value);
87
+ } else {
88
+ plot.value.updateSettingsAndData(data.value, settings);
89
+ }
90
+ });
91
+
92
+ onBeforeUnmount(() => {
93
+ plot.value?.unmount();
94
+ });
95
+ </script>
96
+
97
+ <template>
98
+ <PlAlert v-if="error" type="error">
99
+ {{ error.message }}
100
+ </PlAlert>
101
+ <div v-else ref="plotEl" :class="classes.container" />
102
+ </template>
103
+
104
+ <style module>
105
+ .container {
106
+ svg {
107
+ display: block;
108
+ }
109
+ }
110
+ </style>