jbrowse-plugin-mafviewer 1.0.3 → 1.0.5
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/CHANGELOG.md +7 -0
- package/README.md +183 -0
- package/dist/jbrowse-plugin-mafviewer.umd.development.js +82 -81
- package/dist/jbrowse-plugin-mafviewer.umd.development.js.map +1 -1
- package/dist/jbrowse-plugin-mafviewer.umd.production.min.js +1 -1
- package/dist/jbrowse-plugin-mafviewer.umd.production.min.js.map +1 -1
- package/package.json +8 -10
- package/src/BigMafAdapter/BigMafAdapter.ts +8 -18
- package/src/LinearMafDisplay/components/ColorLegend.tsx +5 -3
- package/src/LinearMafDisplay/components/ReactComponent.tsx +4 -1
- package/src/LinearMafDisplay/components/SetRowHeight.tsx +2 -2
- package/src/LinearMafDisplay/components/YScaleBars.tsx +6 -2
- package/src/LinearMafDisplay/renderSvg.tsx +2 -2
- package/src/LinearMafDisplay/stateModel.ts +35 -10
- package/src/LinearMafRenderer/LinearMafRenderer.ts +42 -41
- package/src/LinearMafRenderer/components/ReactComponent.tsx +4 -1
- package/src/MafAddTrackWorkflow/AddTrackWorkflow.tsx +1 -0
- package/src/MafTabixAdapter/MafTabixAdapter.ts +6 -15
package/CHANGELOG.md
ADDED
package/README.md
CHANGED
|
@@ -6,3 +6,186 @@ This is a port of the JBrowse 1 plugin https://github.com/cmdcolin/mafviewer to
|
|
|
6
6
|
JBrowse 2
|
|
7
7
|
|
|
8
8
|

|
|
9
|
+
|
|
10
|
+
## Demo
|
|
11
|
+
|
|
12
|
+
https://jbrowse.org/code/jb2/main/?config=%2Fdemos%2Fmafviewer%2Fhg38%2Fdistconfig.json&session=share-O3sxhB3iS2&password=8Ysiv
|
|
13
|
+
|
|
14
|
+
## GUI usage (e.g. in JBrowse Desktop)
|
|
15
|
+
|
|
16
|
+
This short screenshot workflow shows how you can load your own custom MAF files
|
|
17
|
+
via the GUI
|
|
18
|
+
|
|
19
|
+
First install the plugin via the plugin store
|
|
20
|
+
|
|
21
|
+

|
|
22
|
+
|
|
23
|
+
Then use the custom "Add track workflow"
|
|
24
|
+
|
|
25
|
+

|
|
26
|
+
|
|
27
|
+
## Manual config entry
|
|
28
|
+
|
|
29
|
+
### Add plugin to your jbrowse 2 config.json
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"plugins": [
|
|
34
|
+
{
|
|
35
|
+
"name": "MafViewer",
|
|
36
|
+
"url": "https://unpkg.com/jbrowse-plugin-mafviewer/dist/jbrowse-plugin-mafviewer.umd.production.min.js"
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Example MafTabixAdapter config
|
|
43
|
+
|
|
44
|
+
The MafTabix track is created according to
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"type": "MafTrack",
|
|
49
|
+
"trackId": "chrI.bed",
|
|
50
|
+
"name": "chrI.bed",
|
|
51
|
+
"adapter": {
|
|
52
|
+
"type": "MafTabixAdapter",
|
|
53
|
+
"samples": ["ce10", "cb4", "caeSp111", "caeRem4", "caeJap4", "caePb3"],
|
|
54
|
+
"bedGzLocation": {
|
|
55
|
+
"uri": "chrI.bed.gz"
|
|
56
|
+
},
|
|
57
|
+
"index": {
|
|
58
|
+
"location": {
|
|
59
|
+
"uri": "chrI.bed.gz.tbi"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"assemblyNames": ["c_elegans"]
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Example BigMafAdapter config
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"type": "MafTrack",
|
|
72
|
+
"trackId": "bigMaf",
|
|
73
|
+
"name": "bigMaf (chr22_KI270731v1_random)",
|
|
74
|
+
"adapter": {
|
|
75
|
+
"type": "BigMafAdapter",
|
|
76
|
+
"samples": [
|
|
77
|
+
"hg38",
|
|
78
|
+
"panTro4",
|
|
79
|
+
"rheMac3",
|
|
80
|
+
"mm10",
|
|
81
|
+
"rn5",
|
|
82
|
+
"canFam3",
|
|
83
|
+
"monDom5"
|
|
84
|
+
],
|
|
85
|
+
"bigBedLocation": {
|
|
86
|
+
"uri": "bigMaf.bb"
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
"assemblyNames": ["hg38"]
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Example with customized sample names and colors
|
|
94
|
+
|
|
95
|
+
```json
|
|
96
|
+
{
|
|
97
|
+
"trackId": "MAF",
|
|
98
|
+
"name": "example",
|
|
99
|
+
"type": "MafTrack",
|
|
100
|
+
"assemblyNames": ["hg38"],
|
|
101
|
+
"adapter": {
|
|
102
|
+
"type": "MafTabixAdapter",
|
|
103
|
+
"bedGzLocation": {
|
|
104
|
+
"uri": "data.txt.gz"
|
|
105
|
+
},
|
|
106
|
+
"index": {
|
|
107
|
+
"location": {
|
|
108
|
+
"uri": "data.txt.gz.tbi"
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
"samples": [
|
|
112
|
+
{
|
|
113
|
+
"id": "hg38",
|
|
114
|
+
"label": "Human",
|
|
115
|
+
"color": "rgba(255,255,255,0.7)"
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"id": "panTro4",
|
|
119
|
+
"label": "Chimp",
|
|
120
|
+
"color": "rgba(255,0,0,0.7)"
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"id": "gorGor3",
|
|
124
|
+
"label": "Gorilla",
|
|
125
|
+
"color": "rgba(0,0,255,0.7)"
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"id": "ponAbe2",
|
|
129
|
+
"label": "Orangutan",
|
|
130
|
+
"color": "rgba(255,255,255,0.7)"
|
|
131
|
+
}
|
|
132
|
+
]
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
The samples array is either `string[]|{id:string,label:string,color?:string}[]`
|
|
138
|
+
|
|
139
|
+
## Prepare data
|
|
140
|
+
|
|
141
|
+
This is the same as the jbrowse 1 mafviewer plugin (currently the similar to
|
|
142
|
+
the). This plugin supports two formats
|
|
143
|
+
|
|
144
|
+
1. BigMaf format, which can be created following UCSC guidelines
|
|
145
|
+
|
|
146
|
+
2. MAF tabix based format, based on a custom BED created via conversion tools in
|
|
147
|
+
this repo.
|
|
148
|
+
|
|
149
|
+
The choice between the two is your convenience. BigMaf is a "standard" UCSC
|
|
150
|
+
format, basically just a specialized BigBed, so it requires JBrowse 1.14.0 or
|
|
151
|
+
newer for it's BigBed support. The custom BED format only requires JBrowse
|
|
152
|
+
1.12.3 or newer, so therefore some slightly older JBrowse versions can support
|
|
153
|
+
it.
|
|
154
|
+
|
|
155
|
+
_Note: Both formats start with a MAF as input, and note that your MAF file
|
|
156
|
+
should contain the species name and chromosome name e.g. hg38.chr1 in the
|
|
157
|
+
sequence identifiers._
|
|
158
|
+
|
|
159
|
+
### Preparing BigMaf
|
|
160
|
+
|
|
161
|
+
Follow instructions from https://genome.ucsc.edu/FAQ/FAQformat.html#format9.3
|
|
162
|
+
and set the storeType of your track as MAFViewer/Store/SeqFeature/BigMaf
|
|
163
|
+
|
|
164
|
+
### Preparing the tabix BED format
|
|
165
|
+
|
|
166
|
+
Start by converting the MAF into a pseudo-BED format using the maf2bed tool
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
# from https://github.com/cmdcolin/maf2bed
|
|
170
|
+
cargo install maf2bed
|
|
171
|
+
cat file.maf | maf2bed hg38 | bgzip > out.bed
|
|
172
|
+
tabix -p bed out.bed.gz
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
The second argument to maf2bed is the genome version e.g. hg38 used for the main
|
|
176
|
+
species in the MAF (if your MAF comes from a pipeline like Ensembl or UCSC, the
|
|
177
|
+
identifiers in the MAF file will say something like hg38.chr1, therefore, the
|
|
178
|
+
argument to maf2bed should just be hg38 to remove hg38 part of the identifier.
|
|
179
|
+
if your MAF file does not include the species name as part of the identifier,
|
|
180
|
+
you should add the species into them the those scaffold/chromosome e.g. create
|
|
181
|
+
hg38.chr1 if it was just chr1 before)
|
|
182
|
+
|
|
183
|
+
If all is well, your BED file should have 6 columns, with
|
|
184
|
+
`chr, start, end, id, score, alignment_data`, where `alignment_data` is
|
|
185
|
+
separated between each species by `;` and each field in the alignment is
|
|
186
|
+
separated by `:`.
|
|
187
|
+
|
|
188
|
+
### Footnote
|
|
189
|
+
|
|
190
|
+
If you can't use the `cargo install maf2bed` binary, there is a `bin/maf2bed.pl`
|
|
191
|
+
perl version of it in this repo
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.JBrowsePluginMafViewer = {}, global.JBrowseExports["@jbrowse/core/Plugin"], global.JBrowseExports["@jbrowse/core/pluggableElementTypes"], global.JBrowseExports["@jbrowse/core/configuration"], global.JBrowseExports["@jbrowse/core/data_adapters/BaseAdapter"], global.JBrowseExports["mobx-state-tree"], global.JBrowseExports["@jbrowse/core/util"], global.JBrowseExports["@jbrowse/core/util/rxjs"], global.JBrowseExports.react, global.JBrowseExports["mobx-react"], global.JBrowseExports["@mui/material"], global.JBrowseExports["@jbrowse/core/ui"], global.JBrowseExports["tss-react/mui"]));
|
|
5
5
|
})(this, (function (exports, Plugin, pluggableElementTypes, configuration, BaseAdapter, mobxStateTree, util, rxjs, React, mobxReact, material, ui, mui) { 'use strict';
|
|
6
6
|
|
|
7
|
-
var version = "1.0.
|
|
7
|
+
var version = "1.0.5";
|
|
8
8
|
|
|
9
9
|
const configSchema$2 = configuration.ConfigurationSchema('BigMafAdapter', {
|
|
10
10
|
/**
|
|
@@ -649,28 +649,19 @@
|
|
|
649
649
|
const alignments = {};
|
|
650
650
|
const blocks2 = [];
|
|
651
651
|
for (const block of blocks) {
|
|
652
|
-
if (block
|
|
653
|
-
if (
|
|
654
|
-
|
|
655
|
-
alns.push(aln);
|
|
652
|
+
if (block.startsWith('s')) {
|
|
653
|
+
if (aln) {
|
|
654
|
+
alns.push(block.split(/ +/)[6]);
|
|
656
655
|
blocks2.push(block);
|
|
657
656
|
}
|
|
658
657
|
else {
|
|
659
|
-
|
|
658
|
+
aln = block.split(/ +/)[6];
|
|
659
|
+
alns.push(aln);
|
|
660
660
|
blocks2.push(block);
|
|
661
661
|
}
|
|
662
662
|
}
|
|
663
663
|
}
|
|
664
|
-
|
|
665
|
-
if (aln) {
|
|
666
|
-
for (let i = 0; i < aln?.length; i++) {
|
|
667
|
-
if (aln[i] !== '-') {
|
|
668
|
-
for (let j = 0; j < alns.length; j++) {
|
|
669
|
-
alns2[j] += alns[j][i];
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
}
|
|
664
|
+
// eslint-disable-next-line unicorn/no-for-loop
|
|
674
665
|
for (let i = 0; i < blocks2.length; i++) {
|
|
675
666
|
const elt = blocks2[i];
|
|
676
667
|
const ad = elt.split(/ +/);
|
|
@@ -683,7 +674,7 @@
|
|
|
683
674
|
srcSize: +ad[2],
|
|
684
675
|
strand: ad[3] === '+' ? 1 : -1,
|
|
685
676
|
unknown: +ad[4],
|
|
686
|
-
data:
|
|
677
|
+
data: alns[i],
|
|
687
678
|
};
|
|
688
679
|
}
|
|
689
680
|
observer.next(new util.SimpleFeature({
|
|
@@ -692,7 +683,7 @@
|
|
|
692
683
|
start: feature.get('start'),
|
|
693
684
|
end: feature.get('end'),
|
|
694
685
|
refName: feature.get('refName'),
|
|
695
|
-
seq:
|
|
686
|
+
seq: alns[0],
|
|
696
687
|
alignments: alignments,
|
|
697
688
|
},
|
|
698
689
|
}));
|
|
@@ -802,6 +793,10 @@
|
|
|
802
793
|
* #property
|
|
803
794
|
*/
|
|
804
795
|
rowProportion: 0.8,
|
|
796
|
+
/**
|
|
797
|
+
* #property
|
|
798
|
+
*/
|
|
799
|
+
showAllLetters: false,
|
|
805
800
|
}))
|
|
806
801
|
.volatile(() => ({
|
|
807
802
|
prefersOffset: true,
|
|
@@ -819,6 +814,12 @@
|
|
|
819
814
|
setRowProportion(n) {
|
|
820
815
|
self.rowProportion = n;
|
|
821
816
|
},
|
|
817
|
+
/**
|
|
818
|
+
* #action
|
|
819
|
+
*/
|
|
820
|
+
setShowAllLetters(f) {
|
|
821
|
+
self.showAllLetters = f;
|
|
822
|
+
},
|
|
822
823
|
}))
|
|
823
824
|
.views(self => ({
|
|
824
825
|
/**
|
|
@@ -826,12 +827,9 @@
|
|
|
826
827
|
*/
|
|
827
828
|
get samples() {
|
|
828
829
|
const r = self.adapterConfig.samples;
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
else {
|
|
833
|
-
return r;
|
|
834
|
-
}
|
|
830
|
+
return isStrs(r)
|
|
831
|
+
? r.map(elt => ({ id: elt, label: elt, color: undefined }))
|
|
832
|
+
: r;
|
|
835
833
|
},
|
|
836
834
|
/**
|
|
837
835
|
* #getter
|
|
@@ -858,12 +856,14 @@
|
|
|
858
856
|
* #method
|
|
859
857
|
*/
|
|
860
858
|
renderProps() {
|
|
859
|
+
const { showAllLetters, rendererConfig, samples, rowHeight, rowProportion, } = self;
|
|
861
860
|
return {
|
|
862
861
|
...superRenderProps(),
|
|
863
|
-
config:
|
|
864
|
-
samples
|
|
865
|
-
rowHeight
|
|
866
|
-
rowProportion
|
|
862
|
+
config: rendererConfig,
|
|
863
|
+
samples,
|
|
864
|
+
rowHeight,
|
|
865
|
+
rowProportion,
|
|
866
|
+
showAllLetters,
|
|
867
867
|
};
|
|
868
868
|
},
|
|
869
869
|
/**
|
|
@@ -881,11 +881,20 @@
|
|
|
881
881
|
]);
|
|
882
882
|
},
|
|
883
883
|
},
|
|
884
|
+
{
|
|
885
|
+
label: 'Show all letters',
|
|
886
|
+
type: 'checkbox',
|
|
887
|
+
checked: self.showAllLetters,
|
|
888
|
+
onClick: () => {
|
|
889
|
+
self.setShowAllLetters(!self.showAllLetters);
|
|
890
|
+
},
|
|
891
|
+
},
|
|
884
892
|
];
|
|
885
893
|
},
|
|
886
894
|
};
|
|
887
895
|
})
|
|
888
896
|
.actions(self => {
|
|
897
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
889
898
|
const { renderSvg: superRenderSvg } = self;
|
|
890
899
|
return {
|
|
891
900
|
/**
|
|
@@ -904,15 +913,14 @@
|
|
|
904
913
|
return React.createElement("rect", { ...props, fill: color });
|
|
905
914
|
};
|
|
906
915
|
|
|
907
|
-
const ColorLegend = mobxReact.observer(function ({ model, labelWidth, }) {
|
|
916
|
+
const ColorLegend = mobxReact.observer(function ({ model, labelWidth, svgFontSize, }) {
|
|
908
917
|
const { samples, rowHeight } = model;
|
|
909
|
-
const svgFontSize = Math.min(rowHeight, 10);
|
|
910
918
|
const canDisplayLabel = rowHeight >= 10;
|
|
911
919
|
const boxHeight = Math.min(20, rowHeight);
|
|
912
920
|
return samples ? (React.createElement(React.Fragment, null,
|
|
913
|
-
samples.map((sample, idx) => (React.createElement(RectBg, { key: `${sample.id}-${idx}`, y: idx * rowHeight
|
|
921
|
+
samples.map((sample, idx) => (React.createElement(RectBg, { key: `${sample.id}-${idx}`, y: idx * rowHeight, x: 0, width: labelWidth + 5, height: boxHeight, color: sample.color }))),
|
|
914
922
|
canDisplayLabel
|
|
915
|
-
? samples.map((sample, idx) => (React.createElement("text", { key: `${sample.id}-${idx}`, y: idx * rowHeight +
|
|
923
|
+
? samples.map((sample, idx) => (React.createElement("text", { key: `${sample.id}-${idx}`, y: idx * rowHeight + rowHeight / 2, dominantBaseline: "middle", x: 2, fontSize: svgFontSize }, sample.label)))
|
|
916
924
|
: null)) : null;
|
|
917
925
|
});
|
|
918
926
|
|
|
@@ -935,14 +943,14 @@
|
|
|
935
943
|
const YScaleBars = mobxReact.observer(function (props) {
|
|
936
944
|
const { model } = props;
|
|
937
945
|
const { rowHeight, samples } = model;
|
|
938
|
-
const svgFontSize = Math.min(rowHeight,
|
|
946
|
+
const svgFontSize = Math.min(Math.max(rowHeight, 10), 14);
|
|
939
947
|
const canDisplayLabel = rowHeight >= 10;
|
|
940
948
|
const minWidth = 20;
|
|
941
949
|
const labelWidth = Math.max(...(samples
|
|
942
950
|
.map(s => util.measureText(s.label, svgFontSize))
|
|
943
951
|
.map(width => (canDisplayLabel ? width : minWidth)) || [0]));
|
|
944
952
|
return (React.createElement(Wrapper, { ...props },
|
|
945
|
-
React.createElement(ColorLegend, { model: model, labelWidth: labelWidth })));
|
|
953
|
+
React.createElement(ColorLegend, { model: model, labelWidth: labelWidth, svgFontSize: svgFontSize })));
|
|
946
954
|
});
|
|
947
955
|
|
|
948
956
|
const LinearMafDisplay = mobxReact.observer(function (props) {
|
|
@@ -994,18 +1002,18 @@
|
|
|
994
1002
|
};
|
|
995
1003
|
}
|
|
996
1004
|
function makeImageData({ ctx, renderArgs, }) {
|
|
997
|
-
const { regions, bpPerPx, rowHeight, theme: configTheme, samples, rowProportion, } = renderArgs;
|
|
1005
|
+
const { regions, bpPerPx, rowHeight, showAllLetters, theme: configTheme, samples, rowProportion, features, } = renderArgs;
|
|
998
1006
|
const [region] = regions;
|
|
999
|
-
const
|
|
1000
|
-
const h = rowHeight;
|
|
1007
|
+
const h = rowHeight * rowProportion;
|
|
1001
1008
|
const theme = ui.createJBrowseTheme(configTheme);
|
|
1002
1009
|
const colorForBase = getColorBaseMap(theme);
|
|
1003
1010
|
const contrastForBase = getContrastBaseMap(theme);
|
|
1004
1011
|
const sampleToRowMap = new Map(samples.map((s, i) => [s.id, i]));
|
|
1005
1012
|
const scale = 1 / bpPerPx;
|
|
1006
1013
|
const f = 0.4;
|
|
1007
|
-
const h2 =
|
|
1008
|
-
const
|
|
1014
|
+
const h2 = rowHeight / 2;
|
|
1015
|
+
const hp2 = h / 2;
|
|
1016
|
+
const offset = (rowHeight - h) / 2;
|
|
1009
1017
|
// sample as alignments
|
|
1010
1018
|
ctx.font = 'bold 10px Courier New,monospace';
|
|
1011
1019
|
for (const feature of features.values()) {
|
|
@@ -1019,7 +1027,7 @@
|
|
|
1019
1027
|
if (row === undefined) {
|
|
1020
1028
|
throw new Error(`unknown sample encountered: ${sample}`);
|
|
1021
1029
|
}
|
|
1022
|
-
const t =
|
|
1030
|
+
const t = rowHeight * row;
|
|
1023
1031
|
// gaps
|
|
1024
1032
|
ctx.beginPath();
|
|
1025
1033
|
ctx.fillStyle = 'black';
|
|
@@ -1034,29 +1042,31 @@
|
|
|
1034
1042
|
}
|
|
1035
1043
|
}
|
|
1036
1044
|
ctx.stroke();
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1045
|
+
if (!showAllLetters) {
|
|
1046
|
+
// matches
|
|
1047
|
+
ctx.beginPath();
|
|
1048
|
+
ctx.fillStyle = 'lightgrey';
|
|
1049
|
+
for (let i = 0, o = 0; i < alignment.length; i++) {
|
|
1050
|
+
if (seq[i] !== '-') {
|
|
1051
|
+
const c = alignment[i];
|
|
1052
|
+
const l = leftPx + scale * o;
|
|
1053
|
+
if (seq[i] === c && c !== '-') {
|
|
1054
|
+
ctx.rect(l, offset + t, scale + f, h);
|
|
1055
|
+
}
|
|
1056
|
+
o++;
|
|
1046
1057
|
}
|
|
1047
|
-
o++;
|
|
1048
1058
|
}
|
|
1059
|
+
ctx.fill();
|
|
1049
1060
|
}
|
|
1050
|
-
ctx.fill();
|
|
1051
1061
|
// mismatches
|
|
1052
1062
|
for (let i = 0, o = 0; i < alignment.length; i++) {
|
|
1053
1063
|
const c = alignment[i];
|
|
1054
1064
|
if (seq[i] !== '-') {
|
|
1055
|
-
if (seq[i] !== c && c !== '-') {
|
|
1065
|
+
if ((showAllLetters || seq[i] !== c) && c !== '-') {
|
|
1056
1066
|
const l = leftPx + scale * o;
|
|
1057
1067
|
ctx.fillStyle =
|
|
1058
|
-
colorForBase[c] ?? '
|
|
1059
|
-
ctx.fillRect(l, offset + t, scale + f,
|
|
1068
|
+
colorForBase[c] ?? 'black';
|
|
1069
|
+
ctx.fillRect(l, offset + t, scale + f, h);
|
|
1060
1070
|
}
|
|
1061
1071
|
o++;
|
|
1062
1072
|
}
|
|
@@ -1069,9 +1079,9 @@
|
|
|
1069
1079
|
const l = leftPx + scale * o;
|
|
1070
1080
|
const offset = (scale - charSize.w) / 2 + 1;
|
|
1071
1081
|
const c = alignment[i];
|
|
1072
|
-
if (seq[i] !== c && c !== '-') {
|
|
1073
|
-
ctx.fillStyle = contrastForBase[c] ?? '
|
|
1074
|
-
ctx.fillText(origAlignment[i], l + offset,
|
|
1082
|
+
if ((showAllLetters || seq[i] !== c) && c !== '-') {
|
|
1083
|
+
ctx.fillStyle = contrastForBase[c] ?? 'white';
|
|
1084
|
+
ctx.fillText(origAlignment[i], l + offset, hp2 + t + 3);
|
|
1075
1085
|
}
|
|
1076
1086
|
o++;
|
|
1077
1087
|
}
|
|
@@ -1092,7 +1102,7 @@
|
|
|
1092
1102
|
if (row === undefined) {
|
|
1093
1103
|
throw new Error(`unknown sample encountered: ${sample}`);
|
|
1094
1104
|
}
|
|
1095
|
-
const t =
|
|
1105
|
+
const t = rowHeight * row;
|
|
1096
1106
|
ctx.beginPath();
|
|
1097
1107
|
ctx.fillStyle = 'purple';
|
|
1098
1108
|
for (let i = 0, o = 0; i < alignment.length; i++) {
|
|
@@ -1103,11 +1113,11 @@
|
|
|
1103
1113
|
}
|
|
1104
1114
|
i++;
|
|
1105
1115
|
}
|
|
1106
|
-
if (ins.length) {
|
|
1107
|
-
const l = leftPx + scale * o -
|
|
1108
|
-
ctx.rect(l, offset + t,
|
|
1109
|
-
ctx.rect(l - 2, offset + t,
|
|
1110
|
-
ctx.rect(l - 2, offset + t +
|
|
1116
|
+
if (ins.length > 0) {
|
|
1117
|
+
const l = leftPx + scale * o - 1;
|
|
1118
|
+
ctx.rect(l, offset + t + 1, 1, h - 1);
|
|
1119
|
+
ctx.rect(l - 2, offset + t, 5, 1);
|
|
1120
|
+
ctx.rect(l - 2, offset + t + h - 1, 5, 1);
|
|
1111
1121
|
}
|
|
1112
1122
|
o++;
|
|
1113
1123
|
}
|
|
@@ -1129,7 +1139,7 @@
|
|
|
1129
1139
|
async render(renderProps) {
|
|
1130
1140
|
const { regions, bpPerPx, samples, rowHeight } = renderProps;
|
|
1131
1141
|
const [region] = regions;
|
|
1132
|
-
const height = samples.length * rowHeight;
|
|
1142
|
+
const height = samples.length * rowHeight + 100;
|
|
1133
1143
|
const width = (region.end - region.start) / bpPerPx;
|
|
1134
1144
|
const features = await this.getFeatures(renderProps);
|
|
1135
1145
|
const res = await util.renderToAbstractCanvas(width, height, renderProps, ctx => makeImageData({
|
|
@@ -1240,23 +1250,13 @@
|
|
|
1240
1250
|
const data = feature.get('field5').split(',');
|
|
1241
1251
|
const alignments = {};
|
|
1242
1252
|
const alns = data.map(elt => elt.split(':')[5]);
|
|
1243
|
-
|
|
1244
|
-
// const alns2 = data.map(() => '')
|
|
1245
|
-
// remove extraneous data in other alignments
|
|
1246
|
-
// reason being: cannot represent missing data in main species that are in others)
|
|
1247
|
-
// for (let i = 0; i < aln.length; i++) {
|
|
1248
|
-
// if (aln[i] !== '-') {
|
|
1249
|
-
// for (let j = 0; j < data.length; j++) {
|
|
1250
|
-
// alns2[j] += alns[j][i]
|
|
1251
|
-
// }
|
|
1252
|
-
// }
|
|
1253
|
-
// }
|
|
1254
|
-
for (let j = 0; j < data.length; j++) {
|
|
1255
|
-
const elt = data[j];
|
|
1253
|
+
for (const [j, elt] of data.entries()) {
|
|
1256
1254
|
const ad = elt.split(':');
|
|
1257
|
-
const
|
|
1255
|
+
const idx = ad[0].lastIndexOf('.');
|
|
1256
|
+
const org = ad[0].slice(0, idx);
|
|
1257
|
+
const last = ad[0].slice(idx + 1);
|
|
1258
1258
|
alignments[org] = {
|
|
1259
|
-
chr,
|
|
1259
|
+
chr: last,
|
|
1260
1260
|
start: +ad[1],
|
|
1261
1261
|
srcSize: +ad[2],
|
|
1262
1262
|
strand: ad[3] === '-' ? -1 : 1,
|
|
@@ -1313,6 +1313,7 @@
|
|
|
1313
1313
|
const [error, setError] = React.useState();
|
|
1314
1314
|
const [trackName, setTrackName] = React.useState('MAF track');
|
|
1315
1315
|
const [choice, setChoice] = React.useState('BigMafAdapter');
|
|
1316
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1316
1317
|
const rootModel = mobxStateTree.getRoot(model);
|
|
1317
1318
|
return (React.createElement(material.Paper, { className: classes.paper },
|
|
1318
1319
|
React.createElement(material.Paper, null,
|
|
@@ -1396,9 +1397,9 @@
|
|
|
1396
1397
|
}
|
|
1397
1398
|
|
|
1398
1399
|
async function renderSvg(self, opts, superRenderSvg) {
|
|
1399
|
-
const { height } = self;
|
|
1400
|
+
const { height, id } = self;
|
|
1400
1401
|
const { offsetPx, width } = util.getContainingView(self);
|
|
1401
|
-
const clipid = `mafclip-${
|
|
1402
|
+
const clipid = `mafclip-${id}`;
|
|
1402
1403
|
return (React.createElement(React.Fragment, null,
|
|
1403
1404
|
React.createElement("defs", null,
|
|
1404
1405
|
React.createElement("clipPath", { id: clipid },
|