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
|
@@ -48,6 +48,10 @@ export default function stateModelFactory(
|
|
|
48
48
|
* #property
|
|
49
49
|
*/
|
|
50
50
|
rowProportion: 0.8,
|
|
51
|
+
/**
|
|
52
|
+
* #property
|
|
53
|
+
*/
|
|
54
|
+
showAllLetters: false,
|
|
51
55
|
}),
|
|
52
56
|
)
|
|
53
57
|
.volatile(() => ({
|
|
@@ -66,6 +70,12 @@ export default function stateModelFactory(
|
|
|
66
70
|
setRowProportion(n: number) {
|
|
67
71
|
self.rowProportion = n
|
|
68
72
|
},
|
|
73
|
+
/**
|
|
74
|
+
* #action
|
|
75
|
+
*/
|
|
76
|
+
setShowAllLetters(f: boolean) {
|
|
77
|
+
self.showAllLetters = f
|
|
78
|
+
},
|
|
69
79
|
}))
|
|
70
80
|
.views(self => ({
|
|
71
81
|
/**
|
|
@@ -75,11 +85,9 @@ export default function stateModelFactory(
|
|
|
75
85
|
const r = self.adapterConfig.samples as
|
|
76
86
|
| string[]
|
|
77
87
|
| { id: string; label: string; color?: string }[]
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
return r
|
|
82
|
-
}
|
|
88
|
+
return isStrs(r)
|
|
89
|
+
? r.map(elt => ({ id: elt, label: elt, color: undefined }))
|
|
90
|
+
: r
|
|
83
91
|
},
|
|
84
92
|
|
|
85
93
|
/**
|
|
@@ -114,12 +122,20 @@ export default function stateModelFactory(
|
|
|
114
122
|
* #method
|
|
115
123
|
*/
|
|
116
124
|
renderProps() {
|
|
125
|
+
const {
|
|
126
|
+
showAllLetters,
|
|
127
|
+
rendererConfig,
|
|
128
|
+
samples,
|
|
129
|
+
rowHeight,
|
|
130
|
+
rowProportion,
|
|
131
|
+
} = self
|
|
117
132
|
return {
|
|
118
133
|
...superRenderProps(),
|
|
119
|
-
config:
|
|
120
|
-
samples
|
|
121
|
-
rowHeight
|
|
122
|
-
rowProportion
|
|
134
|
+
config: rendererConfig,
|
|
135
|
+
samples,
|
|
136
|
+
rowHeight,
|
|
137
|
+
rowProportion,
|
|
138
|
+
showAllLetters,
|
|
123
139
|
}
|
|
124
140
|
},
|
|
125
141
|
/**
|
|
@@ -137,17 +153,26 @@ export default function stateModelFactory(
|
|
|
137
153
|
])
|
|
138
154
|
},
|
|
139
155
|
},
|
|
156
|
+
{
|
|
157
|
+
label: 'Show all letters',
|
|
158
|
+
type: 'checkbox',
|
|
159
|
+
checked: self.showAllLetters,
|
|
160
|
+
onClick: () => {
|
|
161
|
+
self.setShowAllLetters(!self.showAllLetters)
|
|
162
|
+
},
|
|
163
|
+
},
|
|
140
164
|
]
|
|
141
165
|
},
|
|
142
166
|
}
|
|
143
167
|
})
|
|
144
168
|
.actions(self => {
|
|
169
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
145
170
|
const { renderSvg: superRenderSvg } = self
|
|
146
171
|
return {
|
|
147
172
|
/**
|
|
148
173
|
* #action
|
|
149
174
|
*/
|
|
150
|
-
async renderSvg(opts: ExportSvgDisplayOptions)
|
|
175
|
+
async renderSvg(opts: ExportSvgDisplayOptions) {
|
|
151
176
|
const { renderSvg } = await import('./renderSvg')
|
|
152
177
|
return renderSvg(self, opts, superRenderSvg)
|
|
153
178
|
},
|
|
@@ -18,6 +18,13 @@ export function getContrastBaseMap(theme: Theme) {
|
|
|
18
18
|
)
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
interface RenderArgs extends RenderArgsDeserialized {
|
|
22
|
+
samples: { id: string; color?: string }[]
|
|
23
|
+
rowHeight: number
|
|
24
|
+
rowProportion: number
|
|
25
|
+
showAllLetters: boolean
|
|
26
|
+
}
|
|
27
|
+
|
|
21
28
|
export function getColorBaseMap(theme: Theme) {
|
|
22
29
|
const { bases } = theme.palette
|
|
23
30
|
return {
|
|
@@ -32,31 +39,29 @@ function makeImageData({
|
|
|
32
39
|
renderArgs,
|
|
33
40
|
}: {
|
|
34
41
|
ctx: CanvasRenderingContext2D
|
|
35
|
-
renderArgs:
|
|
36
|
-
samples: { id: string; color?: string }[]
|
|
37
|
-
rowHeight: number
|
|
38
|
-
rowProportion: number
|
|
39
|
-
}
|
|
42
|
+
renderArgs: RenderArgs & { features: Map<string, Feature> }
|
|
40
43
|
}) {
|
|
41
44
|
const {
|
|
42
45
|
regions,
|
|
43
46
|
bpPerPx,
|
|
44
47
|
rowHeight,
|
|
48
|
+
showAllLetters,
|
|
45
49
|
theme: configTheme,
|
|
46
50
|
samples,
|
|
47
51
|
rowProportion,
|
|
52
|
+
features,
|
|
48
53
|
} = renderArgs
|
|
49
54
|
const [region] = regions
|
|
50
|
-
const
|
|
51
|
-
const h = rowHeight
|
|
55
|
+
const h = rowHeight * rowProportion
|
|
52
56
|
const theme = createJBrowseTheme(configTheme)
|
|
53
57
|
const colorForBase = getColorBaseMap(theme)
|
|
54
58
|
const contrastForBase = getContrastBaseMap(theme)
|
|
55
59
|
const sampleToRowMap = new Map(samples.map((s, i) => [s.id, i]))
|
|
56
60
|
const scale = 1 / bpPerPx
|
|
57
61
|
const f = 0.4
|
|
58
|
-
const h2 =
|
|
59
|
-
const
|
|
62
|
+
const h2 = rowHeight / 2
|
|
63
|
+
const hp2 = h / 2
|
|
64
|
+
const offset = (rowHeight - h) / 2
|
|
60
65
|
|
|
61
66
|
// sample as alignments
|
|
62
67
|
ctx.font = 'bold 10px Courier New,monospace'
|
|
@@ -74,7 +79,7 @@ function makeImageData({
|
|
|
74
79
|
throw new Error(`unknown sample encountered: ${sample}`)
|
|
75
80
|
}
|
|
76
81
|
|
|
77
|
-
const t =
|
|
82
|
+
const t = rowHeight * row
|
|
78
83
|
|
|
79
84
|
// gaps
|
|
80
85
|
ctx.beginPath()
|
|
@@ -91,30 +96,32 @@ function makeImageData({
|
|
|
91
96
|
}
|
|
92
97
|
ctx.stroke()
|
|
93
98
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
99
|
+
if (!showAllLetters) {
|
|
100
|
+
// matches
|
|
101
|
+
ctx.beginPath()
|
|
102
|
+
ctx.fillStyle = 'lightgrey'
|
|
103
|
+
for (let i = 0, o = 0; i < alignment.length; i++) {
|
|
104
|
+
if (seq[i] !== '-') {
|
|
105
|
+
const c = alignment[i]
|
|
106
|
+
const l = leftPx + scale * o
|
|
107
|
+
if (seq[i] === c && c !== '-') {
|
|
108
|
+
ctx.rect(l, offset + t, scale + f, h)
|
|
109
|
+
}
|
|
110
|
+
o++
|
|
103
111
|
}
|
|
104
|
-
o++
|
|
105
112
|
}
|
|
113
|
+
ctx.fill()
|
|
106
114
|
}
|
|
107
|
-
ctx.fill()
|
|
108
115
|
|
|
109
116
|
// mismatches
|
|
110
117
|
for (let i = 0, o = 0; i < alignment.length; i++) {
|
|
111
118
|
const c = alignment[i]
|
|
112
119
|
if (seq[i] !== '-') {
|
|
113
|
-
if (seq[i] !== c && c !== '-') {
|
|
120
|
+
if ((showAllLetters || seq[i] !== c) && c !== '-') {
|
|
114
121
|
const l = leftPx + scale * o
|
|
115
122
|
ctx.fillStyle =
|
|
116
|
-
colorForBase[c as keyof typeof colorForBase] ?? '
|
|
117
|
-
ctx.fillRect(l, offset + t, scale + f,
|
|
123
|
+
colorForBase[c as keyof typeof colorForBase] ?? 'black'
|
|
124
|
+
ctx.fillRect(l, offset + t, scale + f, h)
|
|
118
125
|
}
|
|
119
126
|
o++
|
|
120
127
|
}
|
|
@@ -128,9 +135,9 @@ function makeImageData({
|
|
|
128
135
|
const l = leftPx + scale * o
|
|
129
136
|
const offset = (scale - charSize.w) / 2 + 1
|
|
130
137
|
const c = alignment[i]
|
|
131
|
-
if (seq[i] !== c && c !== '-') {
|
|
132
|
-
ctx.fillStyle = contrastForBase[c] ?? '
|
|
133
|
-
ctx.fillText(origAlignment[i], l + offset,
|
|
138
|
+
if ((showAllLetters || seq[i] !== c) && c !== '-') {
|
|
139
|
+
ctx.fillStyle = contrastForBase[c] ?? 'white'
|
|
140
|
+
ctx.fillText(origAlignment[i], l + offset, hp2 + t + 3)
|
|
134
141
|
}
|
|
135
142
|
o++
|
|
136
143
|
}
|
|
@@ -154,7 +161,7 @@ function makeImageData({
|
|
|
154
161
|
throw new Error(`unknown sample encountered: ${sample}`)
|
|
155
162
|
}
|
|
156
163
|
|
|
157
|
-
const t =
|
|
164
|
+
const t = rowHeight * row
|
|
158
165
|
|
|
159
166
|
ctx.beginPath()
|
|
160
167
|
ctx.fillStyle = 'purple'
|
|
@@ -166,11 +173,11 @@ function makeImageData({
|
|
|
166
173
|
}
|
|
167
174
|
i++
|
|
168
175
|
}
|
|
169
|
-
if (ins.length) {
|
|
170
|
-
const l = leftPx + scale * o -
|
|
171
|
-
ctx.rect(l, offset + t,
|
|
172
|
-
ctx.rect(l - 2, offset + t,
|
|
173
|
-
ctx.rect(l - 2, offset + t +
|
|
176
|
+
if (ins.length > 0) {
|
|
177
|
+
const l = leftPx + scale * o - 1
|
|
178
|
+
ctx.rect(l, offset + t + 1, 1, h - 1)
|
|
179
|
+
ctx.rect(l - 2, offset + t, 5, 1)
|
|
180
|
+
ctx.rect(l - 2, offset + t + h - 1, 5, 1)
|
|
174
181
|
}
|
|
175
182
|
o++
|
|
176
183
|
}
|
|
@@ -190,16 +197,10 @@ export default class LinearMafRenderer extends FeatureRendererType {
|
|
|
190
197
|
end: Math.ceil(end + bpExpansion),
|
|
191
198
|
}
|
|
192
199
|
}
|
|
193
|
-
async render(
|
|
194
|
-
renderProps: RenderArgsDeserialized & {
|
|
195
|
-
samples: { id: string; color?: string }[]
|
|
196
|
-
rowHeight: number
|
|
197
|
-
rowProportion: number
|
|
198
|
-
},
|
|
199
|
-
) {
|
|
200
|
+
async render(renderProps: RenderArgs) {
|
|
200
201
|
const { regions, bpPerPx, samples, rowHeight } = renderProps
|
|
201
202
|
const [region] = regions
|
|
202
|
-
const height = samples.length * rowHeight
|
|
203
|
+
const height = samples.length * rowHeight + 100
|
|
203
204
|
const width = (region.end - region.start) / bpPerPx
|
|
204
205
|
const features = await this.getFeatures(renderProps)
|
|
205
206
|
const res = await renderToAbstractCanvas(width, height, renderProps, ctx =>
|
|
@@ -2,7 +2,10 @@ import { PrerenderedCanvas } from '@jbrowse/core/ui'
|
|
|
2
2
|
import { observer } from 'mobx-react'
|
|
3
3
|
import React from 'react'
|
|
4
4
|
|
|
5
|
-
const LinearMafRendering = observer(function (props:
|
|
5
|
+
const LinearMafRendering = observer(function (props: {
|
|
6
|
+
width: number
|
|
7
|
+
height: number
|
|
8
|
+
}) {
|
|
6
9
|
return <PrerenderedCanvas {...props} />
|
|
7
10
|
})
|
|
8
11
|
|
|
@@ -43,6 +43,7 @@ export default function MultiMAFWidget({ model }: { model: AddTrackModel }) {
|
|
|
43
43
|
const [error, setError] = useState<unknown>()
|
|
44
44
|
const [trackName, setTrackName] = useState('MAF track')
|
|
45
45
|
const [choice, setChoice] = useState('BigMafAdapter')
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
46
47
|
const rootModel = getRoot<any>(model)
|
|
47
48
|
return (
|
|
48
49
|
<Paper className={classes.paper}>
|
|
@@ -58,24 +58,15 @@ export default class BigMafAdapter extends BaseFeatureDataAdapter {
|
|
|
58
58
|
const data = (feature.get('field5') as string).split(',')
|
|
59
59
|
const alignments = {} as Record<string, OrganismRecord>
|
|
60
60
|
const alns = data.map(elt => elt.split(':')[5])
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
// remove extraneous data in other alignments
|
|
64
|
-
// reason being: cannot represent missing data in main species that are in others)
|
|
65
|
-
// for (let i = 0; i < aln.length; i++) {
|
|
66
|
-
// if (aln[i] !== '-') {
|
|
67
|
-
// for (let j = 0; j < data.length; j++) {
|
|
68
|
-
// alns2[j] += alns[j][i]
|
|
69
|
-
// }
|
|
70
|
-
// }
|
|
71
|
-
// }
|
|
72
|
-
for (let j = 0; j < data.length; j++) {
|
|
73
|
-
const elt = data[j]
|
|
61
|
+
|
|
62
|
+
for (const [j, elt] of data.entries()) {
|
|
74
63
|
const ad = elt.split(':')
|
|
75
|
-
const
|
|
64
|
+
const idx = ad[0].lastIndexOf('.')
|
|
65
|
+
const org = ad[0].slice(0, idx)
|
|
66
|
+
const last = ad[0].slice(idx + 1)
|
|
76
67
|
|
|
77
68
|
alignments[org] = {
|
|
78
|
-
chr,
|
|
69
|
+
chr: last,
|
|
79
70
|
start: +ad[1],
|
|
80
71
|
srcSize: +ad[2],
|
|
81
72
|
strand: ad[3] === '-' ? -1 : 1,
|