jbrowse-plugin-mafviewer 1.0.3 → 1.0.4
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 +3 -0
- package/README.md +183 -0
- package/dist/jbrowse-plugin-mafviewer.umd.development.js +78 -78
- 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 +33 -28
- package/src/LinearMafRenderer/components/ReactComponent.tsx +4 -1
- package/src/MafAddTrackWorkflow/AddTrackWorkflow.tsx +1 -0
- package/src/MafTabixAdapter/MafTabixAdapter.ts +2 -13
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.4";
|
|
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,19 @@
|
|
|
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, } = renderArgs;
|
|
998
1006
|
const [region] = regions;
|
|
999
1007
|
const features = renderArgs.features;
|
|
1000
|
-
const h = rowHeight;
|
|
1008
|
+
const h = rowHeight * rowProportion;
|
|
1001
1009
|
const theme = ui.createJBrowseTheme(configTheme);
|
|
1002
1010
|
const colorForBase = getColorBaseMap(theme);
|
|
1003
1011
|
const contrastForBase = getContrastBaseMap(theme);
|
|
1004
1012
|
const sampleToRowMap = new Map(samples.map((s, i) => [s.id, i]));
|
|
1005
1013
|
const scale = 1 / bpPerPx;
|
|
1006
1014
|
const f = 0.4;
|
|
1007
|
-
const h2 =
|
|
1008
|
-
const
|
|
1015
|
+
const h2 = rowHeight / 2;
|
|
1016
|
+
const hp2 = h / 2;
|
|
1017
|
+
const offset = (rowHeight - h) / 2;
|
|
1009
1018
|
// sample as alignments
|
|
1010
1019
|
ctx.font = 'bold 10px Courier New,monospace';
|
|
1011
1020
|
for (const feature of features.values()) {
|
|
@@ -1019,7 +1028,7 @@
|
|
|
1019
1028
|
if (row === undefined) {
|
|
1020
1029
|
throw new Error(`unknown sample encountered: ${sample}`);
|
|
1021
1030
|
}
|
|
1022
|
-
const t =
|
|
1031
|
+
const t = rowHeight * row;
|
|
1023
1032
|
// gaps
|
|
1024
1033
|
ctx.beginPath();
|
|
1025
1034
|
ctx.fillStyle = 'black';
|
|
@@ -1034,29 +1043,31 @@
|
|
|
1034
1043
|
}
|
|
1035
1044
|
}
|
|
1036
1045
|
ctx.stroke();
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
+
if (!showAllLetters) {
|
|
1047
|
+
// matches
|
|
1048
|
+
ctx.beginPath();
|
|
1049
|
+
ctx.fillStyle = 'lightgrey';
|
|
1050
|
+
for (let i = 0, o = 0; i < alignment.length; i++) {
|
|
1051
|
+
if (seq[i] !== '-') {
|
|
1052
|
+
const c = alignment[i];
|
|
1053
|
+
const l = leftPx + scale * o;
|
|
1054
|
+
if (seq[i] === c && c !== '-') {
|
|
1055
|
+
ctx.rect(l, offset + t, scale + f, h);
|
|
1056
|
+
}
|
|
1057
|
+
o++;
|
|
1046
1058
|
}
|
|
1047
|
-
o++;
|
|
1048
1059
|
}
|
|
1060
|
+
ctx.fill();
|
|
1049
1061
|
}
|
|
1050
|
-
ctx.fill();
|
|
1051
1062
|
// mismatches
|
|
1052
1063
|
for (let i = 0, o = 0; i < alignment.length; i++) {
|
|
1053
1064
|
const c = alignment[i];
|
|
1054
1065
|
if (seq[i] !== '-') {
|
|
1055
|
-
if (seq[i] !== c && c !== '-') {
|
|
1066
|
+
if ((showAllLetters || seq[i] !== c) && c !== '-') {
|
|
1056
1067
|
const l = leftPx + scale * o;
|
|
1057
1068
|
ctx.fillStyle =
|
|
1058
|
-
colorForBase[c] ?? '
|
|
1059
|
-
ctx.fillRect(l, offset + t, scale + f,
|
|
1069
|
+
colorForBase[c] ?? 'black';
|
|
1070
|
+
ctx.fillRect(l, offset + t, scale + f, h);
|
|
1060
1071
|
}
|
|
1061
1072
|
o++;
|
|
1062
1073
|
}
|
|
@@ -1069,9 +1080,9 @@
|
|
|
1069
1080
|
const l = leftPx + scale * o;
|
|
1070
1081
|
const offset = (scale - charSize.w) / 2 + 1;
|
|
1071
1082
|
const c = alignment[i];
|
|
1072
|
-
if (seq[i] !== c && c !== '-') {
|
|
1073
|
-
ctx.fillStyle = contrastForBase[c] ?? '
|
|
1074
|
-
ctx.fillText(origAlignment[i], l + offset,
|
|
1083
|
+
if ((showAllLetters || seq[i] !== c) && c !== '-') {
|
|
1084
|
+
ctx.fillStyle = contrastForBase[c] ?? 'white';
|
|
1085
|
+
ctx.fillText(origAlignment[i], l + offset, hp2 + t + 3);
|
|
1075
1086
|
}
|
|
1076
1087
|
o++;
|
|
1077
1088
|
}
|
|
@@ -1092,7 +1103,7 @@
|
|
|
1092
1103
|
if (row === undefined) {
|
|
1093
1104
|
throw new Error(`unknown sample encountered: ${sample}`);
|
|
1094
1105
|
}
|
|
1095
|
-
const t =
|
|
1106
|
+
const t = rowHeight * row;
|
|
1096
1107
|
ctx.beginPath();
|
|
1097
1108
|
ctx.fillStyle = 'purple';
|
|
1098
1109
|
for (let i = 0, o = 0; i < alignment.length; i++) {
|
|
@@ -1103,11 +1114,11 @@
|
|
|
1103
1114
|
}
|
|
1104
1115
|
i++;
|
|
1105
1116
|
}
|
|
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 +
|
|
1117
|
+
if (ins.length > 0) {
|
|
1118
|
+
const l = leftPx + scale * o - 1;
|
|
1119
|
+
ctx.rect(l, offset + t + 1, 1, h - 1);
|
|
1120
|
+
ctx.rect(l - 2, offset + t, 5, 1);
|
|
1121
|
+
ctx.rect(l - 2, offset + t + h - 1, 5, 1);
|
|
1111
1122
|
}
|
|
1112
1123
|
o++;
|
|
1113
1124
|
}
|
|
@@ -1129,7 +1140,7 @@
|
|
|
1129
1140
|
async render(renderProps) {
|
|
1130
1141
|
const { regions, bpPerPx, samples, rowHeight } = renderProps;
|
|
1131
1142
|
const [region] = regions;
|
|
1132
|
-
const height = samples.length * rowHeight;
|
|
1143
|
+
const height = samples.length * rowHeight + 100;
|
|
1133
1144
|
const width = (region.end - region.start) / bpPerPx;
|
|
1134
1145
|
const features = await this.getFeatures(renderProps);
|
|
1135
1146
|
const res = await util.renderToAbstractCanvas(width, height, renderProps, ctx => makeImageData({
|
|
@@ -1240,19 +1251,7 @@
|
|
|
1240
1251
|
const data = feature.get('field5').split(',');
|
|
1241
1252
|
const alignments = {};
|
|
1242
1253
|
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];
|
|
1254
|
+
for (const [j, elt] of data.entries()) {
|
|
1256
1255
|
const ad = elt.split(':');
|
|
1257
1256
|
const [org, chr] = ad[0].split('.');
|
|
1258
1257
|
alignments[org] = {
|
|
@@ -1313,6 +1312,7 @@
|
|
|
1313
1312
|
const [error, setError] = React.useState();
|
|
1314
1313
|
const [trackName, setTrackName] = React.useState('MAF track');
|
|
1315
1314
|
const [choice, setChoice] = React.useState('BigMafAdapter');
|
|
1315
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1316
1316
|
const rootModel = mobxStateTree.getRoot(model);
|
|
1317
1317
|
return (React.createElement(material.Paper, { className: classes.paper },
|
|
1318
1318
|
React.createElement(material.Paper, null,
|
|
@@ -1396,9 +1396,9 @@
|
|
|
1396
1396
|
}
|
|
1397
1397
|
|
|
1398
1398
|
async function renderSvg(self, opts, superRenderSvg) {
|
|
1399
|
-
const { height } = self;
|
|
1399
|
+
const { height, id } = self;
|
|
1400
1400
|
const { offsetPx, width } = util.getContainingView(self);
|
|
1401
|
-
const clipid = `mafclip-${
|
|
1401
|
+
const clipid = `mafclip-${id}`;
|
|
1402
1402
|
return (React.createElement(React.Fragment, null,
|
|
1403
1403
|
React.createElement("defs", null,
|
|
1404
1404
|
React.createElement("clipPath", { id: clipid },
|