react-msaview 6.0.0 → 6.1.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.
- package/bundle/index.js +98 -98
- package/bundle/index.js.map +4 -4
- package/dist/columnCounts.d.ts +7 -0
- package/dist/columnCounts.js +18 -0
- package/dist/columnCounts.js.map +1 -1
- package/dist/components/DragHandle.d.ts +25 -0
- package/dist/components/DragHandle.js +49 -0
- package/dist/components/DragHandle.js.map +1 -0
- package/dist/components/ResizeHandles.d.ts +4 -2
- package/dist/components/ResizeHandles.js +29 -32
- package/dist/components/ResizeHandles.js.map +1 -1
- package/dist/components/VerticalScrollbar.js +2 -2
- package/dist/components/VerticalScrollbar.js.map +1 -1
- package/dist/components/dialogs/InterProScanDialog.js +52 -129
- package/dist/components/dialogs/InterProScanDialog.js.map +1 -1
- package/dist/components/header/settingsMenuItems.js +15 -0
- package/dist/components/header/settingsMenuItems.js.map +1 -1
- package/dist/components/minimap/Minimap.js +32 -4
- package/dist/components/minimap/Minimap.js.map +1 -1
- package/dist/components/msa/MSACanvas.js +8 -2
- package/dist/components/msa/MSACanvas.js.map +1 -1
- package/dist/components/msa/MSACanvasBlock.js +12 -3
- package/dist/components/msa/MSACanvasBlock.js.map +1 -1
- package/dist/components/msa/msaRaster.d.ts +63 -0
- package/dist/components/msa/msaRaster.js +252 -0
- package/dist/components/msa/msaRaster.js.map +1 -0
- package/dist/components/msa/renderMSABlock.d.ts +2 -1
- package/dist/components/msa/renderMSABlock.js +13 -21
- package/dist/components/msa/renderMSABlock.js.map +1 -1
- package/dist/components/msa/tileColor.d.ts +6 -0
- package/dist/components/msa/tileColor.js +35 -0
- package/dist/components/msa/tileColor.js.map +1 -0
- package/dist/components/renderCtx.d.ts +1 -1
- package/dist/components/tracks/TrackBlocks.d.ts +14 -0
- package/dist/components/tracks/TrackBlocks.js +65 -0
- package/dist/components/tracks/TrackBlocks.js.map +1 -0
- package/dist/components/tracks/{renderTracksSvg.d.ts → drawTracks.d.ts} +22 -10
- package/dist/components/tracks/drawTracks.js +193 -0
- package/dist/components/tracks/drawTracks.js.map +1 -0
- package/dist/components/tree/TreeCanvas.js +8 -2
- package/dist/components/tree/TreeCanvas.js.map +1 -1
- package/dist/components/tree/TreeCanvasBlock.js +7 -4
- package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
- package/dist/components/tree/renderTreeCanvas.js +5 -2
- package/dist/components/tree/renderTreeCanvas.js.map +1 -1
- package/dist/constants.d.ts +3 -0
- package/dist/constants.js +8 -0
- package/dist/constants.js.map +1 -1
- package/dist/model/DataModel.d.ts +1 -4
- package/dist/model/DataModel.js +8 -10
- package/dist/model/DataModel.js.map +1 -1
- package/dist/model.d.ts +36 -7
- package/dist/model.js +113 -58
- package/dist/model.js.map +1 -1
- package/dist/neighborJoining.js +34 -18
- package/dist/neighborJoining.js.map +1 -1
- package/dist/renderTestEnv.d.ts +43 -0
- package/dist/renderTestEnv.js +88 -0
- package/dist/renderTestEnv.js.map +1 -0
- package/dist/renderToSvg.js +1 -1
- package/dist/renderToSvg.js.map +1 -1
- package/dist/sequenceLogo.d.ts +28 -0
- package/dist/sequenceLogo.js +47 -0
- package/dist/sequenceLogo.js.map +1 -0
- package/dist/types.d.ts +2 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
- package/src/columnCounts.test.ts +15 -0
- package/src/columnCounts.ts +19 -0
- package/src/components/DragHandle.tsx +81 -0
- package/src/components/ResizeHandles.tsx +46 -54
- package/src/components/VerticalScrollbar.tsx +3 -2
- package/src/components/dialogs/InterProScanDialog.tsx +54 -131
- package/src/components/header/settingsMenuItems.ts +18 -0
- package/src/components/minimap/Minimap.tsx +47 -4
- package/src/components/msa/MSACanvas.tsx +30 -10
- package/src/components/msa/MSACanvasBlock.tsx +13 -3
- package/src/components/msa/msaRaster.test.ts +145 -0
- package/src/components/msa/msaRaster.ts +350 -0
- package/src/components/msa/renderMSABlock.ts +15 -20
- package/src/components/msa/tileColor.ts +37 -0
- package/src/components/renderCtx.ts +2 -0
- package/src/components/tracks/TrackBlocks.tsx +111 -0
- package/src/components/tracks/drawTracks.ts +323 -0
- package/src/components/tree/TreeCanvas.tsx +16 -4
- package/src/components/tree/TreeCanvasBlock.tsx +18 -11
- package/src/components/tree/renderTreeCanvas.ts +5 -1
- package/src/constants.ts +10 -0
- package/src/impgRender.test.tsx +2 -57
- package/src/largeTree.test.ts +16 -2
- package/src/model/DataModel.ts +11 -11
- package/src/model.ts +128 -60
- package/src/neighborJoining.ts +36 -18
- package/src/renderDomainsSvg.test.tsx +2 -57
- package/src/renderMinimapSvg.test.tsx +2 -56
- package/src/renderSequenceLogoSvg.test.tsx +176 -0
- package/src/renderTestEnv.ts +96 -0
- package/src/renderToSvg.tsx +1 -1
- package/src/sequenceLogo.test.ts +88 -0
- package/src/sequenceLogo.ts +63 -0
- package/src/types.ts +7 -1
- package/src/version.ts +1 -1
- package/dist/components/ConservationTrack.d.ts +0 -8
- package/dist/components/ConservationTrack.js +0 -49
- package/dist/components/ConservationTrack.js.map +0 -1
- package/dist/components/ScrollbarThumb.d.ts +0 -14
- package/dist/components/ScrollbarThumb.js +0 -28
- package/dist/components/ScrollbarThumb.js.map +0 -1
- package/dist/components/TextTrack.d.ts +0 -8
- package/dist/components/TextTrack.js +0 -58
- package/dist/components/TextTrack.js.map +0 -1
- package/dist/components/tracks/renderTracksSvg.js +0 -126
- package/dist/components/tracks/renderTracksSvg.js.map +0 -1
- package/src/components/ConservationTrack.tsx +0 -99
- package/src/components/ScrollbarThumb.tsx +0 -48
- package/src/components/TextTrack.tsx +0 -103
- package/src/components/tracks/renderTracksSvg.ts +0 -247
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
//
|
|
3
|
+
// The logo track is the first track kind whose letters are drawn under a
|
|
4
|
+
// per-letter transform rather than at a fixed size, so it is also the first that
|
|
5
|
+
// can silently draw nothing if the export context loses that transform. These
|
|
6
|
+
// tests go through the real SVG export to pin the whole path: the track is off
|
|
7
|
+
// until asked for, the stack is ordered and scaled, and the glyphs land inside
|
|
8
|
+
// the track's own band.
|
|
9
|
+
import { createJBrowseTheme } from '@jbrowse/core/ui/theme'
|
|
10
|
+
import { enableStaticRendering } from 'mobx-react'
|
|
11
|
+
import { beforeAll, expect, test } from 'vitest'
|
|
12
|
+
|
|
13
|
+
import MSAModelF from './model.ts'
|
|
14
|
+
import { TEST_CHAR_WIDTH_RATIO, installRenderTestEnv } from './renderTestEnv.ts'
|
|
15
|
+
import { renderToSvg } from './renderToSvg.tsx'
|
|
16
|
+
|
|
17
|
+
beforeAll(() => {
|
|
18
|
+
enableStaticRendering(true)
|
|
19
|
+
installRenderTestEnv()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
// column 0 is fully conserved A, column 1 is a 3:1 split of C over G, and
|
|
23
|
+
// column 2 is an even four-way split carrying no information at all
|
|
24
|
+
const msa = `>s1\nACA\n>s2\nACC\n>s3\nACG\n>s4\nAGT\n`
|
|
25
|
+
const colWidth = 40
|
|
26
|
+
|
|
27
|
+
function makeModel() {
|
|
28
|
+
const model = MSAModelF().create({
|
|
29
|
+
type: 'MsaView',
|
|
30
|
+
msaFormat: 'fasta',
|
|
31
|
+
height: 400,
|
|
32
|
+
colWidth,
|
|
33
|
+
data: { msa },
|
|
34
|
+
})
|
|
35
|
+
model.setWidth(1000)
|
|
36
|
+
return model
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function exportSvg(model: ReturnType<typeof makeModel>) {
|
|
40
|
+
return renderToSvg(model, {
|
|
41
|
+
theme: createJBrowseTheme(),
|
|
42
|
+
exportType: 'entire',
|
|
43
|
+
includeTracks: true,
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// The logo's glyphs are the only <text> drawn at the track's own font size, and
|
|
48
|
+
// the only ones carrying a non-unit vertical scale, which is what separates them
|
|
49
|
+
// from the alignment letters and tree labels in the same document.
|
|
50
|
+
function logoGlyphs(svg: string, fontSize: number) {
|
|
51
|
+
return [
|
|
52
|
+
...svg.matchAll(
|
|
53
|
+
/<text[^>]*font-size="([\d.]+)px"[^>]*transform="matrix\(([^)]*)\)"[^>]*>([A-Z])<\/text>/g,
|
|
54
|
+
),
|
|
55
|
+
]
|
|
56
|
+
.filter(m => Number(m[1]) === fontSize)
|
|
57
|
+
.map(m => {
|
|
58
|
+
const [sx, , , sy, tx, ty] = m[2]!.split(/[\s,]+/).map(Number)
|
|
59
|
+
return { letter: m[3]!, sx: sx!, sy: sy!, x: tx!, y: ty! }
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function glyphsOf(model: ReturnType<typeof makeModel>) {
|
|
64
|
+
return logoGlyphs(await exportSvg(model), model.sequenceLogoTrackHeight)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
test('the logo track is hidden until it is asked for', () => {
|
|
68
|
+
const model = makeModel()
|
|
69
|
+
expect(model.tracks.map(t => t.model.id)).toContain('sequence-logo')
|
|
70
|
+
expect(model.turnedOnTracks.map(t => t.model.id)).not.toContain(
|
|
71
|
+
'sequence-logo',
|
|
72
|
+
)
|
|
73
|
+
// and being default-off writes nothing into the shareable snapshot
|
|
74
|
+
expect(model.turnedOffTracks.size).toBe(0)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
test('toggling it on shows it, and off again hides it', () => {
|
|
78
|
+
const model = makeModel()
|
|
79
|
+
model.toggleTrack('sequence-logo')
|
|
80
|
+
expect(model.turnedOnTracks.map(t => t.model.id)).toContain('sequence-logo')
|
|
81
|
+
model.toggleTrack('sequence-logo')
|
|
82
|
+
expect(model.turnedOnTracks.map(t => t.model.id)).not.toContain(
|
|
83
|
+
'sequence-logo',
|
|
84
|
+
)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
test('an on-by-default track still toggles off and back on', () => {
|
|
88
|
+
const model = makeModel()
|
|
89
|
+
expect(model.turnedOnTracks.map(t => t.model.id)).toContain('conservation')
|
|
90
|
+
model.toggleTrack('conservation')
|
|
91
|
+
expect(model.turnedOnTracks.map(t => t.model.id)).not.toContain(
|
|
92
|
+
'conservation',
|
|
93
|
+
)
|
|
94
|
+
model.toggleTrack('conservation')
|
|
95
|
+
expect(model.turnedOnTracks.map(t => t.model.id)).toContain('conservation')
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
test('the conserved column draws one full-height letter', async () => {
|
|
99
|
+
const model = makeModel()
|
|
100
|
+
model.toggleTrack('sequence-logo')
|
|
101
|
+
const col0 = (await glyphsOf(model)).filter(g => g.x === 0)
|
|
102
|
+
|
|
103
|
+
expect(col0.map(g => g.letter)).toEqual(['A'])
|
|
104
|
+
// a fully conserved nucleotide column is 2 of 2 bits, so the glyph stretches
|
|
105
|
+
// to the whole track height over the cap height of the reference font
|
|
106
|
+
expect(col0[0]!.sy).toBeCloseTo(1 / 0.72, 5)
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
test('a split column stacks the minor letter below the major one', async () => {
|
|
110
|
+
const model = makeModel()
|
|
111
|
+
model.toggleTrack('sequence-logo')
|
|
112
|
+
const col1 = (await glyphsOf(model)).filter(g => g.x === colWidth)
|
|
113
|
+
|
|
114
|
+
// ascending order means C (3 of 4) is drawn last, so it ends up on top
|
|
115
|
+
expect(col1.map(g => g.letter)).toEqual(['G', 'C'])
|
|
116
|
+
const [g, c] = col1
|
|
117
|
+
expect(c!.sy).toBeGreaterThan(g!.sy)
|
|
118
|
+
// and the taller letter sits higher up the track (smaller y is nearer the top)
|
|
119
|
+
expect(c!.y).toBeLessThan(g!.y)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
test('the uninformative column draws nothing', async () => {
|
|
123
|
+
const model = makeModel()
|
|
124
|
+
model.toggleTrack('sequence-logo')
|
|
125
|
+
const glyphs = await glyphsOf(model)
|
|
126
|
+
expect(glyphs.filter(g => g.x === colWidth * 2)).toEqual([])
|
|
127
|
+
// the other two columns did draw, so an empty column 2 is the logo's own
|
|
128
|
+
// verdict rather than the whole track missing
|
|
129
|
+
expect(glyphs).toHaveLength(3)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
test('every glyph stays inside the track band', async () => {
|
|
133
|
+
const model = makeModel()
|
|
134
|
+
model.toggleTrack('sequence-logo')
|
|
135
|
+
|
|
136
|
+
// the export stacks the tracks, so the logo's band is offset by whatever is
|
|
137
|
+
// drawn above it -- assert against that band rather than against 0..height,
|
|
138
|
+
// which would pass only while the logo happened to be the first track
|
|
139
|
+
const above = model.turnedOnTracks
|
|
140
|
+
.slice(
|
|
141
|
+
0,
|
|
142
|
+
model.turnedOnTracks.findIndex(t => t.model.id === 'sequence-logo'),
|
|
143
|
+
)
|
|
144
|
+
.reduce((a, t) => a + t.model.height, 0)
|
|
145
|
+
const bandTop = above
|
|
146
|
+
const bandBottom = above + model.sequenceLogoTrackHeight
|
|
147
|
+
|
|
148
|
+
const glyphs = await glyphsOf(model)
|
|
149
|
+
expect(glyphs.length).toBeGreaterThan(0)
|
|
150
|
+
for (const glyph of glyphs) {
|
|
151
|
+
// baselines run from the band's bottom edge upward, never past either end
|
|
152
|
+
expect(glyph.y).toBeGreaterThan(bandTop)
|
|
153
|
+
expect(glyph.y).toBeLessThanOrEqual(bandBottom)
|
|
154
|
+
}
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
test('letters are stretched to fill their column', async () => {
|
|
158
|
+
const model = makeModel()
|
|
159
|
+
model.toggleTrack('sequence-logo')
|
|
160
|
+
const glyphs = await glyphsOf(model)
|
|
161
|
+
|
|
162
|
+
const expected =
|
|
163
|
+
colWidth / (model.sequenceLogoTrackHeight * TEST_CHAR_WIDTH_RATIO)
|
|
164
|
+
for (const glyph of glyphs) {
|
|
165
|
+
expect(glyph.sx).toBeCloseTo(expected, 5)
|
|
166
|
+
}
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
test('the logo appears in the export only when the track is on', async () => {
|
|
170
|
+
const off = makeModel()
|
|
171
|
+
expect(await glyphsOf(off)).toEqual([])
|
|
172
|
+
|
|
173
|
+
const on = makeModel()
|
|
174
|
+
on.toggleTrack('sequence-logo')
|
|
175
|
+
expect((await glyphsOf(on)).length).toBeGreaterThan(0)
|
|
176
|
+
})
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* jsdom shims for the SVG-export tests. Test-only -- nothing in the viewer
|
|
3
|
+
* imports this.
|
|
4
|
+
*
|
|
5
|
+
* jsdom implements neither DOMMatrix nor a canvas 2D context, and
|
|
6
|
+
* `@jbrowse/svgcanvas` needs both: it tracks the current transform as a real
|
|
7
|
+
* matrix, and the renderers ask a throwaway canvas to measure text.
|
|
8
|
+
*
|
|
9
|
+
* The matrix stub has to accept the ARRAY constructor. svgcanvas normalizes
|
|
10
|
+
* every transform through `new DOMMatrix([a, b, c, d, e, f])`, so a stub that
|
|
11
|
+
* only takes six positional arguments silently binds the whole array to `a` and
|
|
12
|
+
* leaves the rest undefined -- every emitted transform then carries `NaN` in its
|
|
13
|
+
* x components, which no assertion on a drawn x-position can survive.
|
|
14
|
+
*/
|
|
15
|
+
export class TestDOMMatrix {
|
|
16
|
+
a: number
|
|
17
|
+
b: number
|
|
18
|
+
c: number
|
|
19
|
+
d: number
|
|
20
|
+
e: number
|
|
21
|
+
f: number
|
|
22
|
+
|
|
23
|
+
constructor(init?: number[]) {
|
|
24
|
+
const [a = 1, b = 0, c = 0, d = 1, e = 0, f = 0] = init ?? []
|
|
25
|
+
this.a = a
|
|
26
|
+
this.b = b
|
|
27
|
+
this.c = c
|
|
28
|
+
this.d = d
|
|
29
|
+
this.e = e
|
|
30
|
+
this.f = f
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
multiply(o: TestDOMMatrix) {
|
|
34
|
+
return new TestDOMMatrix([
|
|
35
|
+
this.a * o.a + this.c * o.b,
|
|
36
|
+
this.b * o.a + this.d * o.b,
|
|
37
|
+
this.a * o.c + this.c * o.d,
|
|
38
|
+
this.b * o.c + this.d * o.d,
|
|
39
|
+
this.a * o.e + this.c * o.f + this.e,
|
|
40
|
+
this.b * o.e + this.d * o.f + this.f,
|
|
41
|
+
])
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
translate(x: number, y = 0) {
|
|
45
|
+
return this.multiply(new TestDOMMatrix([1, 0, 0, 1, x, y]))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
scale(x: number, y = x) {
|
|
49
|
+
return this.multiply(new TestDOMMatrix([x, 0, 0, y, 0, 0]))
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export class TestDOMPoint {
|
|
54
|
+
constructor(
|
|
55
|
+
public x = 0,
|
|
56
|
+
public y = 0,
|
|
57
|
+
) {}
|
|
58
|
+
|
|
59
|
+
matrixTransform(m: TestDOMMatrix) {
|
|
60
|
+
return new TestDOMPoint(
|
|
61
|
+
m.a * this.x + m.c * this.y + m.e,
|
|
62
|
+
m.b * this.x + m.d * this.y + m.f,
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Install the shims. Call from `beforeAll` in any test that exports SVG.
|
|
69
|
+
*
|
|
70
|
+
* `measureText` reports 0.6em per character, close enough to a real sans-serif
|
|
71
|
+
* average for layout assertions and, more usefully, exactly predictable: a test
|
|
72
|
+
* can compute the width a renderer will see rather than hardcoding a number
|
|
73
|
+
* measured from one machine's fonts.
|
|
74
|
+
*/
|
|
75
|
+
export function installRenderTestEnv() {
|
|
76
|
+
const g = globalThis as Record<string, unknown>
|
|
77
|
+
g.DOMMatrix = TestDOMMatrix
|
|
78
|
+
g.DOMPoint = TestDOMPoint
|
|
79
|
+
HTMLCanvasElement.prototype.getContext = function () {
|
|
80
|
+
let font = '10px sans-serif'
|
|
81
|
+
return {
|
|
82
|
+
get font() {
|
|
83
|
+
return font
|
|
84
|
+
},
|
|
85
|
+
set font(v: string) {
|
|
86
|
+
font = v
|
|
87
|
+
},
|
|
88
|
+
measureText: (t: string) => ({
|
|
89
|
+
width: t.length * (Number.parseFloat(font) || 10) * 0.6,
|
|
90
|
+
}),
|
|
91
|
+
} as unknown as CanvasRenderingContext2D
|
|
92
|
+
} as unknown as typeof HTMLCanvasElement.prototype.getContext
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** the per-character width factor `measureText` above reports */
|
|
96
|
+
export const TEST_CHAR_WIDTH_RATIO = 0.6
|
package/src/renderToSvg.tsx
CHANGED
|
@@ -7,7 +7,7 @@ import { when } from 'mobx'
|
|
|
7
7
|
import MinimapSVG from './components/minimap/MinimapSVG.tsx'
|
|
8
8
|
import { renderBoxFeatureCanvasBlock } from './components/msa/renderBoxFeatureCanvasBlock.ts'
|
|
9
9
|
import { renderMSABlock } from './components/msa/renderMSABlock.ts'
|
|
10
|
-
import { renderAllTracks } from './components/tracks/
|
|
10
|
+
import { renderAllTracks } from './components/tracks/drawTracks.ts'
|
|
11
11
|
import { renderTreeCanvas } from './components/tree/renderTreeCanvas.ts'
|
|
12
12
|
import { colorContrast } from './util.ts'
|
|
13
13
|
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { expect, test } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { columnCountsFromColumns } from './columnCounts.ts'
|
|
4
|
+
import { columnLogoStack, maxBitsFor } from './sequenceLogo.ts'
|
|
5
|
+
|
|
6
|
+
const dnaBits = maxBitsFor('dna')
|
|
7
|
+
const aminoBits = maxBitsFor('amino')
|
|
8
|
+
|
|
9
|
+
function stackOf(column: string, maxBits = dnaBits) {
|
|
10
|
+
return columnLogoStack(columnCountsFromColumns([column]), 0, maxBits)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
test('maxBits is log2 of the alphabet', () => {
|
|
14
|
+
expect(maxBitsFor('dna')).toBe(2)
|
|
15
|
+
expect(maxBitsFor('rna')).toBe(2)
|
|
16
|
+
expect(maxBitsFor('amino')).toBeCloseTo(4.3219, 4)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test('a fully conserved column is one letter at the full height', () => {
|
|
20
|
+
const stack = stackOf('AAAA')
|
|
21
|
+
expect(stack).toHaveLength(1)
|
|
22
|
+
expect(stack[0]!.letter).toBe('A')
|
|
23
|
+
expect(stack[0]!.bits).toBeCloseTo(2, 10)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('an even four-way column carries no information', () => {
|
|
27
|
+
expect(stackOf('ACGT')).toEqual([])
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
test('a two-way split is half the height, shared evenly', () => {
|
|
31
|
+
const stack = stackOf('AACC')
|
|
32
|
+
expect(stack.map(s => s.letter)).toEqual(['A', 'C'])
|
|
33
|
+
// entropy 1 bit, so information content is 2 - 1 = 1, split evenly
|
|
34
|
+
expect(stack[0]!.bits).toBeCloseTo(0.5, 10)
|
|
35
|
+
expect(stack[1]!.bits).toBeCloseTo(0.5, 10)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
test('letters are ordered ascending so the tallest draws last, on top', () => {
|
|
39
|
+
const stack = stackOf('AAAC')
|
|
40
|
+
expect(stack.map(s => s.letter)).toEqual(['C', 'A'])
|
|
41
|
+
expect(stack[0]!.bits).toBeLessThan(stack[1]!.bits)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('ties break on letter so redraws are stable', () => {
|
|
45
|
+
expect(stackOf('GGCC').map(s => s.letter)).toEqual(['C', 'G'])
|
|
46
|
+
expect(stackOf('CCGG').map(s => s.letter)).toEqual(['C', 'G'])
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('gaps scale the stack down without skewing the frequencies', () => {
|
|
50
|
+
// 'A' is the only residue, so it takes the whole stack either way; the height
|
|
51
|
+
// is what the gaps change
|
|
52
|
+
const clean = stackOf('AAAA')
|
|
53
|
+
const gappy = stackOf('AA--')
|
|
54
|
+
expect(gappy[0]!.letter).toBe('A')
|
|
55
|
+
expect(gappy[0]!.bits).toBeCloseTo(clean[0]!.bits / 2, 10)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test('gaps do not make a two-residue column look like a four-way one', () => {
|
|
59
|
+
// frequencies come from the two real residues, so this is a 1-bit choice
|
|
60
|
+
// scaled by the half-gap occupancy, not a 4-way split
|
|
61
|
+
const stack = stackOf('AC--')
|
|
62
|
+
expect(stack.map(s => s.letter)).toEqual(['A', 'C'])
|
|
63
|
+
expect(stack[0]!.bits + stack[1]!.bits).toBeCloseTo(0.5, 10)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test('an all-gap column has no stack', () => {
|
|
67
|
+
expect(stackOf('----')).toEqual([])
|
|
68
|
+
expect(stackOf('....')).toEqual([])
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
test('the protein ceiling is higher than the nucleotide one', () => {
|
|
72
|
+
const protein = stackOf('WWWW', aminoBits)
|
|
73
|
+
expect(protein[0]!.bits).toBeCloseTo(aminoBits, 10)
|
|
74
|
+
expect(protein[0]!.bits).toBeGreaterThan(stackOf('AAAA')[0]!.bits)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
test('stack height never exceeds the ceiling', () => {
|
|
78
|
+
const columns = ['AAAA', 'AACG', 'A-C-', 'ACGT', 'AAAT']
|
|
79
|
+
for (const column of columns) {
|
|
80
|
+
const total = stackOf(column).reduce((a, b) => a + b.bits, 0)
|
|
81
|
+
expect(total).toBeLessThanOrEqual(dnaBits + 1e-9)
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
test('a column past the end of a ragged alignment is empty, not a crash', () => {
|
|
86
|
+
const counts = columnCountsFromColumns(['AC'])
|
|
87
|
+
expect(columnLogoStack(counts, 5, dnaBits)).toEqual([])
|
|
88
|
+
})
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { letterOfResidueSlot } from './columnCounts.ts'
|
|
2
|
+
|
|
3
|
+
import type { ColumnCounts } from './columnCounts.ts'
|
|
4
|
+
|
|
5
|
+
export interface LogoLetter {
|
|
6
|
+
letter: string
|
|
7
|
+
/** height of this letter's slice of the stack, in bits */
|
|
8
|
+
bits: number
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** the y-axis ceiling of a logo: the information content of a fully conserved
|
|
12
|
+
* column, log2 of the alphabet size */
|
|
13
|
+
export function maxBitsFor(sequenceType: 'dna' | 'rna' | 'amino') {
|
|
14
|
+
return Math.log2(sequenceType === 'amino' ? 20 : 4)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* One column of a sequence logo, tallest letter last so a caller can draw the
|
|
19
|
+
* stack bottom-up in array order.
|
|
20
|
+
*
|
|
21
|
+
* Heights follow Schneider & Stephens: the column's information content is
|
|
22
|
+
* `log2(alphabet) - H` bits, and each residue takes the share of that its own
|
|
23
|
+
* frequency earns. Frequencies come from the non-gap residues only, so a column
|
|
24
|
+
* of two residues and eight gaps is read as a two-way choice rather than a
|
|
25
|
+
* ten-way one -- otherwise every gappy column would look uninformative for the
|
|
26
|
+
* wrong reason.
|
|
27
|
+
*
|
|
28
|
+
* The whole stack is then scaled by the non-gap fraction, which plain WebLogo
|
|
29
|
+
* does not do. Here it earns its place: the track sits directly under an
|
|
30
|
+
* alignment where the gaps are visible, and a column that is 90% gap drawing a
|
|
31
|
+
* full-height stack off its one remaining pair of residues reads as a conserved
|
|
32
|
+
* site. Scaling makes stack height mean "how much this column tells you about
|
|
33
|
+
* the family", matching what the conservation track above it already plots.
|
|
34
|
+
*/
|
|
35
|
+
export function columnLogoStack(
|
|
36
|
+
colStats: ColumnCounts,
|
|
37
|
+
col: number,
|
|
38
|
+
maxBits: number,
|
|
39
|
+
): LogoLetter[] {
|
|
40
|
+
const total = colStats.total(col)
|
|
41
|
+
const nonGapTotal = total - colStats.gapCount(col)
|
|
42
|
+
if (nonGapTotal === 0) {
|
|
43
|
+
return []
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const informationContent =
|
|
47
|
+
Math.max(0, maxBits - colStats.entropy(col)) * (nonGapTotal / total)
|
|
48
|
+
if (informationContent === 0) {
|
|
49
|
+
return []
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const stack: LogoLetter[] = []
|
|
53
|
+
colStats.forEachResidue(col, (slot, count) => {
|
|
54
|
+
stack.push({
|
|
55
|
+
letter: letterOfResidueSlot(slot),
|
|
56
|
+
bits: (count / nonGapTotal) * informationContent,
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
// ascending, so drawing in order stacks the tallest letter on top. Ties break
|
|
60
|
+
// on letter to keep the stack order stable between redraws.
|
|
61
|
+
stack.sort((a, b) => a.bits - b.bits || a.letter.localeCompare(b.letter))
|
|
62
|
+
return stack
|
|
63
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -5,11 +5,17 @@ export interface Accession {
|
|
|
5
5
|
name: string
|
|
6
6
|
description: string
|
|
7
7
|
}
|
|
8
|
+
// which renderer draws a track's content. Every track kind draws into the same
|
|
9
|
+
// per-column coordinate space, so the kind picks the draw function rather than
|
|
10
|
+
// the geometry -- see drawTracks.ts, which dispatches on it.
|
|
11
|
+
export type TrackKind = 'text' | 'bar' | 'logo'
|
|
12
|
+
|
|
8
13
|
export interface BasicTrackModel {
|
|
9
14
|
id: string
|
|
10
15
|
name: string
|
|
11
16
|
associatedRowName?: string
|
|
12
17
|
height: number
|
|
18
|
+
kind: TrackKind
|
|
13
19
|
}
|
|
14
20
|
|
|
15
21
|
export interface TextTrackModel extends BasicTrackModel {
|
|
@@ -18,7 +24,7 @@ export interface TextTrackModel extends BasicTrackModel {
|
|
|
18
24
|
}
|
|
19
25
|
|
|
20
26
|
// a track that draws a per-column bar chart (conservation, property
|
|
21
|
-
// conservation)
|
|
27
|
+
// conservation)
|
|
22
28
|
export interface BarTrackModel extends BasicTrackModel {
|
|
23
29
|
barColor?: string
|
|
24
30
|
}
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '6.
|
|
1
|
+
export const version = '6.1.0'
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import type { MsaViewModel } from '../model.ts';
|
|
3
|
-
import type { BasicTrack } from '../types.ts';
|
|
4
|
-
declare const ConservationTrack: ({ model, track, }: {
|
|
5
|
-
model: MsaViewModel;
|
|
6
|
-
track: BasicTrack;
|
|
7
|
-
}) => React.JSX.Element;
|
|
8
|
-
export default ConservationTrack;
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { observer } from 'mobx-react';
|
|
3
|
-
import { useCanvasAutorun } from '../useCanvasAutorun.js';
|
|
4
|
-
import { ConservationTrackResizeHandle } from './ResizeHandles.js';
|
|
5
|
-
import { barTrackValues, drawConservationBars, } from './tracks/renderTracksSvg.js';
|
|
6
|
-
const ConservationBlock = observer(function ({ model, track, offsetX, trackHeight, }) {
|
|
7
|
-
const { blockSize, scrollX, highResScaleFactor } = model;
|
|
8
|
-
const ref = useCanvasAutorun({
|
|
9
|
-
draw: ctx => {
|
|
10
|
-
const { colWidth } = model;
|
|
11
|
-
ctx.resetTransform();
|
|
12
|
-
ctx.scale(highResScaleFactor, highResScaleFactor);
|
|
13
|
-
ctx.clearRect(0, 0, blockSize, trackHeight);
|
|
14
|
-
ctx.translate(-offsetX, 0);
|
|
15
|
-
drawConservationBars({
|
|
16
|
-
ctx,
|
|
17
|
-
values: barTrackValues(model, track.model.id),
|
|
18
|
-
color: track.model.barColor ?? 'gray',
|
|
19
|
-
colWidth,
|
|
20
|
-
trackHeight,
|
|
21
|
-
offsetX,
|
|
22
|
-
blockSize,
|
|
23
|
-
});
|
|
24
|
-
},
|
|
25
|
-
width: blockSize * highResScaleFactor,
|
|
26
|
-
height: trackHeight * highResScaleFactor,
|
|
27
|
-
deps: [model, track, offsetX, trackHeight],
|
|
28
|
-
});
|
|
29
|
-
return (React.createElement("canvas", { ref: ref, height: trackHeight * highResScaleFactor, width: blockSize * highResScaleFactor, style: {
|
|
30
|
-
position: 'absolute',
|
|
31
|
-
left: scrollX + offsetX,
|
|
32
|
-
width: blockSize,
|
|
33
|
-
height: trackHeight,
|
|
34
|
-
} }));
|
|
35
|
-
});
|
|
36
|
-
const ConservationTrack = observer(function ({ model, track, }) {
|
|
37
|
-
const { blocksX, msaAreaWidth } = model;
|
|
38
|
-
const trackHeight = track.model.height;
|
|
39
|
-
return (React.createElement("div", { style: {
|
|
40
|
-
position: 'relative',
|
|
41
|
-
height: trackHeight,
|
|
42
|
-
width: msaAreaWidth,
|
|
43
|
-
overflow: 'hidden',
|
|
44
|
-
} },
|
|
45
|
-
blocksX.map(bx => (React.createElement(ConservationBlock, { key: bx, model: model, track: track, offsetX: bx, trackHeight: trackHeight }))),
|
|
46
|
-
React.createElement(ConservationTrackResizeHandle, { model: model })));
|
|
47
|
-
});
|
|
48
|
-
export default ConservationTrack;
|
|
49
|
-
//# sourceMappingURL=ConservationTrack.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ConservationTrack.js","sourceRoot":"","sources":["../../src/components/ConservationTrack.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAErC,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AACzD,OAAO,EAAE,6BAA6B,EAAE,MAAM,oBAAqB,CAAA;AACnE,OAAO,EACL,cAAc,EACd,oBAAoB,GACrB,MAAM,6BAA6B,CAAA;AAKpC,MAAM,iBAAiB,GAAG,QAAQ,CAAC,UAAU,EAC3C,KAAK,EACL,KAAK,EACL,OAAO,EACP,WAAW,GAMZ;IACC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,kBAAkB,EAAE,GAAG,KAAK,CAAA;IAExD,MAAM,GAAG,GAAG,gBAAgB,CAAC;QAC3B,IAAI,EAAE,GAAG,CAAC,EAAE;YACV,MAAM,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAA;YAC1B,GAAG,CAAC,cAAc,EAAE,CAAA;YACpB,GAAG,CAAC,KAAK,CAAC,kBAAkB,EAAE,kBAAkB,CAAC,CAAA;YACjD,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,EAAE,SAAS,EAAE,WAAW,CAAC,CAAA;YAC3C,GAAG,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAA;YAE1B,oBAAoB,CAAC;gBACnB,GAAG;gBACH,MAAM,EAAE,cAAc,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC7C,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,QAAQ,IAAI,MAAM;gBACrC,QAAQ;gBACR,WAAW;gBACX,OAAO;gBACP,SAAS;aACV,CAAC,CAAA;QACJ,CAAC;QACD,KAAK,EAAE,SAAS,GAAG,kBAAkB;QACrC,MAAM,EAAE,WAAW,GAAG,kBAAkB;QACxC,IAAI,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,CAAC;KAC3C,CAAC,CAAA;IAEF,OAAO,CACL,gCACE,GAAG,EAAE,GAAG,EACR,MAAM,EAAE,WAAW,GAAG,kBAAkB,EACxC,KAAK,EAAE,SAAS,GAAG,kBAAkB,EACrC,KAAK,EAAE;YACL,QAAQ,EAAE,UAAU;YACpB,IAAI,EAAE,OAAO,GAAG,OAAO;YACvB,KAAK,EAAE,SAAS;YAChB,MAAM,EAAE,WAAW;SACpB,GACD,CACH,CAAA;AACH,CAAC,CAAC,CAAA;AAEF,MAAM,iBAAiB,GAAG,QAAQ,CAAC,UAAU,EAC3C,KAAK,EACL,KAAK,GAIN;IACC,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,KAAK,CAAA;IACvC,MAAM,WAAW,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CAAA;IAEtC,OAAO,CACL,6BACE,KAAK,EAAE;YACL,QAAQ,EAAE,UAAU;YACpB,MAAM,EAAE,WAAW;YACnB,KAAK,EAAE,YAAY;YACnB,QAAQ,EAAE,QAAQ;SACnB;QAEA,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CACjB,oBAAC,iBAAiB,IAChB,GAAG,EAAE,EAAE,EACP,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,EAAE,EACX,WAAW,EAAE,WAAW,GACxB,CACH,CAAC;QACF,oBAAC,6BAA6B,IAAC,KAAK,EAAE,KAAK,GAAI,CAC3C,CACP,CAAA;AACH,CAAC,CAAC,CAAA;AAEF,eAAe,iBAAiB,CAAA"}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
export declare const scrollbarThumbFill = "rgba(66,119,127,0.3)";
|
|
3
|
-
/**
|
|
4
|
-
* The draggable thumb shared by the horizontal Minimap and the
|
|
5
|
-
* VerticalScrollbar. `getStart` is read at mousedown and handed back to
|
|
6
|
-
* `onDrag` alongside the pixel delta since, so a caller only supplies the
|
|
7
|
-
* scroll offset it drives and where the thumb sits.
|
|
8
|
-
*/
|
|
9
|
-
export default function ScrollbarThumb({ axis, getStart, onDrag, style, }: {
|
|
10
|
-
axis: 'x' | 'y';
|
|
11
|
-
getStart: () => number;
|
|
12
|
-
onDrag: (delta: number, startValue: number) => void;
|
|
13
|
-
style: React.CSSProperties;
|
|
14
|
-
}): React.JSX.Element;
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
2
|
-
import { useDragScroll } from '../useDragScroll.js';
|
|
3
|
-
export const scrollbarThumbFill = 'rgba(66,119,127,0.3)';
|
|
4
|
-
const scrollbarThumbFillHovered = 'rgba(66,119,127,0.6)';
|
|
5
|
-
/**
|
|
6
|
-
* The draggable thumb shared by the horizontal Minimap and the
|
|
7
|
-
* VerticalScrollbar. `getStart` is read at mousedown and handed back to
|
|
8
|
-
* `onDrag` alongside the pixel delta since, so a caller only supplies the
|
|
9
|
-
* scroll offset it drives and where the thumb sits.
|
|
10
|
-
*/
|
|
11
|
-
export default function ScrollbarThumb({ axis, getStart, onDrag, style, }) {
|
|
12
|
-
const [hovered, setHovered] = useState(false);
|
|
13
|
-
const { startDrag } = useDragScroll(axis, onDrag);
|
|
14
|
-
return (React.createElement("div", { onMouseOver: () => {
|
|
15
|
-
setHovered(true);
|
|
16
|
-
}, onMouseOut: () => {
|
|
17
|
-
setHovered(false);
|
|
18
|
-
}, onMouseDown: event => {
|
|
19
|
-
startDrag(event, getStart());
|
|
20
|
-
}, style: {
|
|
21
|
-
position: 'absolute',
|
|
22
|
-
zIndex: 100,
|
|
23
|
-
cursor: 'pointer',
|
|
24
|
-
background: hovered ? scrollbarThumbFillHovered : scrollbarThumbFill,
|
|
25
|
-
...style,
|
|
26
|
-
} }));
|
|
27
|
-
}
|
|
28
|
-
//# sourceMappingURL=ScrollbarThumb.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ScrollbarThumb.js","sourceRoot":"","sources":["../../src/components/ScrollbarThumb.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAEvC,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAEnD,MAAM,CAAC,MAAM,kBAAkB,GAAG,sBAAsB,CAAA;AACxD,MAAM,yBAAyB,GAAG,sBAAsB,CAAA;AAExD;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,cAAc,CAAC,EACrC,IAAI,EACJ,QAAQ,EACR,MAAM,EACN,KAAK,GAMN;IACC,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC7C,MAAM,EAAE,SAAS,EAAE,GAAG,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IAEjD,OAAO,CACL,6BACE,WAAW,EAAE,GAAG,EAAE;YAChB,UAAU,CAAC,IAAI,CAAC,CAAA;QAClB,CAAC,EACD,UAAU,EAAE,GAAG,EAAE;YACf,UAAU,CAAC,KAAK,CAAC,CAAA;QACnB,CAAC,EACD,WAAW,EAAE,KAAK,CAAC,EAAE;YACnB,SAAS,CAAC,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAA;QAC9B,CAAC,EACD,KAAK,EAAE;YACL,QAAQ,EAAE,UAAU;YACpB,MAAM,EAAE,GAAG;YACX,MAAM,EAAE,SAAS;YACjB,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC,yBAAyB,CAAC,CAAC,CAAC,kBAAkB;YACpE,GAAG,KAAK;SACT,GACD,CACH,CAAA;AACH,CAAC"}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import type { MsaViewModel } from '../model.ts';
|
|
3
|
-
import type { BasicTrack } from '../types.ts';
|
|
4
|
-
declare const AnnotationTrack: ({ track, model, }: {
|
|
5
|
-
track: BasicTrack;
|
|
6
|
-
model: MsaViewModel;
|
|
7
|
-
}) => React.JSX.Element | null;
|
|
8
|
-
export default AnnotationTrack;
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { observer } from 'mobx-react';
|
|
3
|
-
import { setFontSize } from '../setFontSize.js';
|
|
4
|
-
import { useCanvasAutorun } from '../useCanvasAutorun.js';
|
|
5
|
-
import { useColorContrast } from '../useColorContrast.js';
|
|
6
|
-
import { drawTextTrackContent } from './tracks/renderTracksSvg.js';
|
|
7
|
-
const AnnotationBlock = observer(function ({ track, model, offsetX, }) {
|
|
8
|
-
const { blockSize, scrollX, colorScheme: modelColorScheme, rowHeight, highResScaleFactor, } = model;
|
|
9
|
-
const { model: { customColorScheme, data }, } = track;
|
|
10
|
-
const colorScheme = customColorScheme ?? modelColorScheme;
|
|
11
|
-
const { theme, contrastScheme } = useColorContrast(colorScheme);
|
|
12
|
-
const ref = useCanvasAutorun({
|
|
13
|
-
draw: ctx => {
|
|
14
|
-
const { bgColor, colWidth, fontSize } = model;
|
|
15
|
-
ctx.resetTransform();
|
|
16
|
-
ctx.scale(highResScaleFactor, highResScaleFactor);
|
|
17
|
-
ctx.clearRect(0, 0, blockSize, rowHeight);
|
|
18
|
-
ctx.translate(-offsetX, 0);
|
|
19
|
-
ctx.textAlign = 'center';
|
|
20
|
-
setFontSize(ctx, fontSize);
|
|
21
|
-
drawTextTrackContent({
|
|
22
|
-
ctx,
|
|
23
|
-
data,
|
|
24
|
-
colorScheme,
|
|
25
|
-
contrastScheme,
|
|
26
|
-
bgColor,
|
|
27
|
-
textColor: theme.palette.text.primary,
|
|
28
|
-
colWidth,
|
|
29
|
-
rowHeight,
|
|
30
|
-
offsetX,
|
|
31
|
-
blockSize,
|
|
32
|
-
});
|
|
33
|
-
},
|
|
34
|
-
width: blockSize * highResScaleFactor,
|
|
35
|
-
height: rowHeight * highResScaleFactor,
|
|
36
|
-
deps: [model, offsetX, theme, contrastScheme, colorScheme, data],
|
|
37
|
-
});
|
|
38
|
-
return (React.createElement("canvas", { ref: ref, height: rowHeight * highResScaleFactor, width: blockSize * highResScaleFactor, style: {
|
|
39
|
-
position: 'absolute',
|
|
40
|
-
left: scrollX + offsetX,
|
|
41
|
-
width: blockSize,
|
|
42
|
-
height: rowHeight,
|
|
43
|
-
} }));
|
|
44
|
-
});
|
|
45
|
-
const AnnotationTrack = observer(function ({ track, model, }) {
|
|
46
|
-
const { blocksX, msaAreaWidth, rowHeight } = model;
|
|
47
|
-
if (!track.model.data) {
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
return (React.createElement("div", { style: {
|
|
51
|
-
position: 'relative',
|
|
52
|
-
height: rowHeight,
|
|
53
|
-
width: msaAreaWidth,
|
|
54
|
-
overflow: 'hidden',
|
|
55
|
-
} }, blocksX.map(bx => (React.createElement(AnnotationBlock, { key: bx, track: track, model: model, offsetX: bx })))));
|
|
56
|
-
});
|
|
57
|
-
export default AnnotationTrack;
|
|
58
|
-
//# sourceMappingURL=TextTrack.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"TextTrack.js","sourceRoot":"","sources":["../../src/components/TextTrack.tsx"],"names":[],"mappings":"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"}
|