jbrowse-plugin-mafviewer 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +8 -0
  3. package/dist/jbrowse-plugin-mafviewer.umd.development.js +1310 -0
  4. package/dist/jbrowse-plugin-mafviewer.umd.development.js.map +1 -0
  5. package/dist/jbrowse-plugin-mafviewer.umd.production.min.js +2 -0
  6. package/dist/jbrowse-plugin-mafviewer.umd.production.min.js.map +1 -0
  7. package/package.json +90 -0
  8. package/src/BigMafAdapter/BigMafAdapter.ts +120 -0
  9. package/src/BigMafAdapter/configSchema.ts +33 -0
  10. package/src/BigMafAdapter/index.ts +15 -0
  11. package/src/LinearMafDisplay/components/ColorLegend.tsx +59 -0
  12. package/src/LinearMafDisplay/components/ReactComponent.tsx +23 -0
  13. package/src/LinearMafDisplay/components/RectBg.tsx +14 -0
  14. package/src/LinearMafDisplay/components/YScaleBars.tsx +63 -0
  15. package/src/LinearMafDisplay/configSchema.ts +23 -0
  16. package/src/LinearMafDisplay/index.ts +21 -0
  17. package/src/LinearMafDisplay/renderSvg.tsx +26 -0
  18. package/src/LinearMafDisplay/stateModel.ts +111 -0
  19. package/src/LinearMafRenderer/LinearMafRenderer.ts +172 -0
  20. package/src/LinearMafRenderer/components/ReactComponent.tsx +9 -0
  21. package/src/LinearMafRenderer/configSchema.ts +19 -0
  22. package/src/LinearMafRenderer/index.ts +16 -0
  23. package/src/MafAddTrackWorkflow/AddTrackWorkflow.tsx +166 -0
  24. package/src/MafAddTrackWorkflow/index.ts +17 -0
  25. package/src/MafTabixAdapter/MafTabixAdapter.ts +104 -0
  26. package/src/MafTabixAdapter/configSchema.ts +45 -0
  27. package/src/MafTabixAdapter/index.ts +15 -0
  28. package/src/MafTrack/configSchema.ts +20 -0
  29. package/src/MafTrack/index.ts +18 -0
  30. package/src/declare.d.ts +1 -0
  31. package/src/index.ts +26 -0
@@ -0,0 +1,120 @@
1
+ import { BaseFeatureDataAdapter } from '@jbrowse/core/data_adapters/BaseAdapter'
2
+ import { getSnapshot } from 'mobx-state-tree'
3
+ import { Feature, Region, SimpleFeature } from '@jbrowse/core/util'
4
+ import { ObservableCreate } from '@jbrowse/core/util/rxjs'
5
+ import { firstValueFrom, toArray } from 'rxjs'
6
+
7
+ interface OrganismRecord {
8
+ chr: string
9
+ start: number
10
+ srcSize: number
11
+ strand: number
12
+ unknown: number
13
+ data: string
14
+ }
15
+ export default class BigMafAdapter extends BaseFeatureDataAdapter {
16
+ public setupP?: Promise<{ adapter: BaseFeatureDataAdapter }>
17
+
18
+ async setup() {
19
+ if (!this.getSubAdapter) {
20
+ throw new Error('no getSubAdapter available')
21
+ }
22
+ const adapter = await this.getSubAdapter({
23
+ ...getSnapshot(this.config),
24
+ type: 'BigBedAdapter',
25
+ })
26
+ return {
27
+ adapter: adapter.dataAdapter as BaseFeatureDataAdapter,
28
+ }
29
+ }
30
+ async setupPre() {
31
+ if (!this.setupP) {
32
+ this.setupP = this.setup().catch(e => {
33
+ this.setupP = undefined
34
+ throw e
35
+ })
36
+ }
37
+ return this.setupP
38
+ }
39
+
40
+ async getRefNames() {
41
+ const { adapter } = await this.setup()
42
+ return adapter.getRefNames()
43
+ }
44
+
45
+ async getHeader() {
46
+ const { adapter } = await this.setup()
47
+ return adapter.getHeader()
48
+ }
49
+
50
+ getFeatures(query: Region) {
51
+ return ObservableCreate<Feature>(async observer => {
52
+ const { adapter } = await this.setup()
53
+ const features = await firstValueFrom(
54
+ adapter.getFeatures(query).pipe(toArray()),
55
+ )
56
+ for (const feature of features) {
57
+ const maf = feature.get('mafBlock') as string
58
+ const blocks = maf.split(';')
59
+ let aln: string | undefined
60
+ const alns = [] as string[]
61
+ const alignments = {} as Record<string, OrganismRecord>
62
+ const blocks2 = [] as string[]
63
+ for (const block of blocks) {
64
+ if (block[0] === 's') {
65
+ if (!aln) {
66
+ aln = block.split(/ +/)[6]
67
+ alns.push(aln)
68
+ blocks2.push(block)
69
+ } else {
70
+ alns.push(block.split(/ +/)[6])
71
+ blocks2.push(block)
72
+ }
73
+ }
74
+ }
75
+ const alns2 = alns.map(() => '')
76
+
77
+ if (aln) {
78
+ for (let i = 0; i < aln?.length; i++) {
79
+ if (aln[i] !== '-') {
80
+ for (let j = 0; j < alns.length; j++) {
81
+ alns2[j] += alns[j][i]
82
+ }
83
+ }
84
+ }
85
+ }
86
+
87
+ for (let i = 0; i < blocks2.length; i++) {
88
+ const elt = blocks2[i]
89
+ const ad = elt.split(/ +/)
90
+ const y = ad[1].split('.')
91
+ const org = y[0]
92
+ const chr = y[1]
93
+
94
+ alignments[org] = {
95
+ chr: chr,
96
+ start: +ad[1],
97
+ srcSize: +ad[2],
98
+ strand: ad[3] === '+' ? 1 : -1,
99
+ unknown: +ad[4],
100
+ data: alns2[i],
101
+ }
102
+ }
103
+ observer.next(
104
+ new SimpleFeature({
105
+ id: feature.id(),
106
+ data: {
107
+ start: feature.get('start'),
108
+ end: feature.get('end'),
109
+ refName: feature.get('refName'),
110
+ seq: alns2[0],
111
+ alignments: alignments,
112
+ },
113
+ }),
114
+ )
115
+ }
116
+ observer.complete()
117
+ })
118
+ }
119
+ freeResources(): void {}
120
+ }
@@ -0,0 +1,33 @@
1
+ import { ConfigurationSchema } from '@jbrowse/core/configuration'
2
+
3
+ /**
4
+ * #config BigMafAdapter
5
+ * used to configure BigMaf adapter
6
+ */
7
+ function x() {} // eslint-disable-line @typescript-eslint/no-unused-vars
8
+
9
+ const configSchema = ConfigurationSchema(
10
+ 'BigMafAdapter',
11
+ {
12
+ /**
13
+ * #slot
14
+ */
15
+ samples: {
16
+ type: 'stringArray',
17
+ defaultValue: [],
18
+ },
19
+ /**
20
+ * #slot
21
+ */
22
+ bigBedLocation: {
23
+ type: 'fileLocation',
24
+ defaultValue: {
25
+ uri: '/path/to/my.bb',
26
+ locationType: 'UriLocation',
27
+ },
28
+ },
29
+ },
30
+ { explicitlyTyped: true },
31
+ )
32
+
33
+ export default configSchema
@@ -0,0 +1,15 @@
1
+ import PluginManager from '@jbrowse/core/PluginManager'
2
+ import { AdapterType } from '@jbrowse/core/pluggableElementTypes'
3
+ import configSchema from './configSchema'
4
+ import BigMafAdapter from './BigMafAdapter'
5
+
6
+ export default function BigMafAdapterF(pluginManager: PluginManager) {
7
+ return pluginManager.addAdapterType(
8
+ () =>
9
+ new AdapterType({
10
+ name: 'BigMafAdapter',
11
+ AdapterClass: BigMafAdapter,
12
+ configSchema,
13
+ }),
14
+ )
15
+ }
@@ -0,0 +1,59 @@
1
+ import React from 'react'
2
+ import { observer } from 'mobx-react'
3
+
4
+ // locals
5
+ import { LinearMafDisplayModel } from '../stateModel'
6
+ import RectBg from './RectBg'
7
+
8
+ const ColorLegend = observer(function ({
9
+ model,
10
+ labelWidth,
11
+ }: {
12
+ model: LinearMafDisplayModel
13
+ labelWidth: number
14
+ }) {
15
+ const { samples, rowHeight } = model
16
+ const svgFontSize = Math.min(rowHeight, 12)
17
+ const canDisplayLabel = rowHeight > 11
18
+ const colorBoxWidth = 15
19
+ const legendWidth = labelWidth + colorBoxWidth + 5
20
+ const extraOffset = 0
21
+
22
+ return samples ? (
23
+ <>
24
+ {samples.map((source, idx) => {
25
+ const boxHeight = Math.min(20, rowHeight)
26
+ return (
27
+ <React.Fragment key={`${source.name}-${idx}`}>
28
+ {source.color ? (
29
+ <RectBg
30
+ y={idx * rowHeight + 1}
31
+ x={extraOffset}
32
+ width={colorBoxWidth}
33
+ height={boxHeight}
34
+ color={source.color}
35
+ />
36
+ ) : null}
37
+ <RectBg
38
+ y={idx * rowHeight + 1}
39
+ x={extraOffset}
40
+ width={legendWidth}
41
+ height={boxHeight}
42
+ />
43
+ {canDisplayLabel ? (
44
+ <text
45
+ y={idx * rowHeight + 13}
46
+ x={extraOffset + colorBoxWidth + 2}
47
+ fontSize={svgFontSize}
48
+ >
49
+ {source.name}
50
+ </text>
51
+ ) : null}
52
+ </React.Fragment>
53
+ )
54
+ })}
55
+ </>
56
+ ) : null
57
+ })
58
+
59
+ export default ColorLegend
@@ -0,0 +1,23 @@
1
+ import React from 'react'
2
+ import { getEnv } from '@jbrowse/core/util'
3
+ import { observer } from 'mobx-react'
4
+ import YScaleBars from './YScaleBars'
5
+
6
+ const LinearMafDisplay = observer(function (props: any) {
7
+ const { model } = props
8
+ const { pluginManager } = getEnv(model)
9
+
10
+ const LinearGenomePlugin = pluginManager.getPlugin(
11
+ 'LinearGenomeViewPlugin',
12
+ ) as import('@jbrowse/plugin-linear-genome-view').default
13
+ const { BaseLinearDisplayComponent } = LinearGenomePlugin.exports
14
+
15
+ return (
16
+ <div>
17
+ <BaseLinearDisplayComponent {...props} />
18
+ <YScaleBars model={model} />
19
+ </div>
20
+ )
21
+ })
22
+
23
+ export default LinearMafDisplay
@@ -0,0 +1,14 @@
1
+ import React from 'react'
2
+
3
+ const RectBg = (props: {
4
+ x: number
5
+ y: number
6
+ width: number
7
+ height: number
8
+ color?: string
9
+ }) => {
10
+ const { color = 'rgb(255,255,255,0.8)' } = props
11
+ return <rect {...props} fill={color} />
12
+ }
13
+
14
+ export default RectBg
@@ -0,0 +1,63 @@
1
+ import React from 'react'
2
+ import { measureText, getContainingView } from '@jbrowse/core/util'
3
+ import { observer } from 'mobx-react'
4
+
5
+ // locals
6
+ import { LinearMafDisplayModel } from '../stateModel'
7
+ import ColorLegend from './ColorLegend'
8
+
9
+ const Wrapper = observer(function ({
10
+ children,
11
+ model,
12
+ exportSVG,
13
+ }: {
14
+ model: LinearMafDisplayModel
15
+ children: React.ReactNode
16
+ exportSVG?: boolean
17
+ }) {
18
+ if (exportSVG) {
19
+ return <>{children}</>
20
+ } else {
21
+ const { rowHeight, samples } = model
22
+ return (
23
+ <svg
24
+ style={{
25
+ position: 'absolute',
26
+ top: 0,
27
+ left: 0,
28
+ pointerEvents: 'none',
29
+ height: samples.length * rowHeight,
30
+ width: getContainingView(model).width,
31
+ }}
32
+ >
33
+ {children}
34
+ </svg>
35
+ )
36
+ }
37
+ })
38
+
39
+ export const YScaleBars = observer(function (props: {
40
+ model: LinearMafDisplayModel
41
+ orientation?: string
42
+ exportSVG?: boolean
43
+ }) {
44
+ const { model } = props
45
+ const { rowHeight, samples } = model
46
+ const svgFontSize = Math.min(rowHeight, 12)
47
+ const canDisplayLabel = rowHeight > 11
48
+ const minWidth = 20
49
+
50
+ const labelWidth = Math.max(
51
+ ...(samples
52
+ .map(s => measureText(s, svgFontSize))
53
+ .map(width => (canDisplayLabel ? width : minWidth)) || [0]),
54
+ )
55
+
56
+ return (
57
+ <Wrapper {...props}>
58
+ <ColorLegend model={model} labelWidth={labelWidth} />
59
+ </Wrapper>
60
+ )
61
+ })
62
+
63
+ export default YScaleBars
@@ -0,0 +1,23 @@
1
+ import PluginManager from '@jbrowse/core/PluginManager'
2
+ import { ConfigurationSchema } from '@jbrowse/core/configuration'
3
+
4
+ export default function configSchemaF(pluginManager: PluginManager) {
5
+ const LinearGenomePlugin = pluginManager.getPlugin(
6
+ 'LinearGenomeViewPlugin',
7
+ ) as import('@jbrowse/plugin-linear-genome-view').default
8
+ // @ts-expect-error
9
+ const { linearBasicDisplayConfigSchemaFactory } = LinearGenomePlugin.exports
10
+ return ConfigurationSchema(
11
+ 'LinearMafDisplay',
12
+ {
13
+ /**
14
+ * #slot
15
+ */
16
+ renderer: pluginManager.pluggableConfigSchemaType('renderer'),
17
+ },
18
+ {
19
+ baseConfiguration: linearBasicDisplayConfigSchemaFactory(pluginManager),
20
+ explicitlyTyped: true,
21
+ },
22
+ )
23
+ }
@@ -0,0 +1,21 @@
1
+ import PluginManager from '@jbrowse/core/PluginManager'
2
+ import { DisplayType } from '@jbrowse/core/pluggableElementTypes'
3
+ import configSchemaF from './configSchema'
4
+ import stateModelFactory from './stateModel'
5
+ import ReactComponent from './components/ReactComponent'
6
+
7
+ export default function LinearMafDisplayF(pluginManager: PluginManager) {
8
+ pluginManager.addDisplayType(() => {
9
+ const configSchema = configSchemaF(pluginManager)
10
+ const stateModel = stateModelFactory(configSchema, pluginManager)
11
+ return new DisplayType({
12
+ name: 'LinearMafDisplay',
13
+ configSchema,
14
+ stateModel,
15
+ ReactComponent,
16
+ viewType: 'LinearGenomeView',
17
+ trackType: 'MafTrack',
18
+ displayName: 'MAF display',
19
+ })
20
+ })
21
+ }
@@ -0,0 +1,26 @@
1
+ import React from 'react'
2
+ import { getContainingView } from '@jbrowse/core/util'
3
+ import {
4
+ ExportSvgDisplayOptions,
5
+ LinearGenomeViewModel,
6
+ } from '@jbrowse/plugin-linear-genome-view'
7
+
8
+ // locals
9
+ import { LinearMafDisplayModel } from './stateModel'
10
+ import YScaleBars from './components/YScaleBars'
11
+
12
+ export async function renderSvg(
13
+ self: LinearMafDisplayModel,
14
+ opts: ExportSvgDisplayOptions,
15
+ superRenderSvg: (opts: ExportSvgDisplayOptions) => Promise<React.ReactNode>,
16
+ ) {
17
+ const { offsetPx } = getContainingView(self) as LinearGenomeViewModel
18
+ return (
19
+ <>
20
+ <g id="snpcov">{await superRenderSvg(opts)}</g>
21
+ <g transform={`translate(${Math.max(-offsetPx, 0)})`}>
22
+ <YScaleBars model={self} orientation="left" exportSVG />
23
+ </g>
24
+ </>
25
+ )
26
+ }
@@ -0,0 +1,111 @@
1
+ import { Instance, types } from 'mobx-state-tree'
2
+ import {
3
+ AnyConfigurationModel,
4
+ AnyConfigurationSchemaType,
5
+ ConfigurationReference,
6
+ getConf,
7
+ } from '@jbrowse/core/configuration'
8
+ import { getEnv } from '@jbrowse/core/util'
9
+ import PluginManager from '@jbrowse/core/PluginManager'
10
+ import { ExportSvgDisplayOptions } from '@jbrowse/plugin-linear-genome-view'
11
+
12
+ /**
13
+ * #stateModel LinearMafDisplay
14
+ * extends LinearBasicDisplay
15
+ */
16
+ export default function stateModelFactory(
17
+ configSchema: AnyConfigurationSchemaType,
18
+ pluginManager: PluginManager,
19
+ ) {
20
+ const LinearGenomePlugin = pluginManager.getPlugin(
21
+ 'LinearGenomeViewPlugin',
22
+ ) as import('@jbrowse/plugin-linear-genome-view').default
23
+ // @ts-expect-error
24
+ const { linearBasicDisplayModelFactory } = LinearGenomePlugin.exports
25
+
26
+ return types
27
+ .compose(
28
+ 'LinearMafDisplay',
29
+ linearBasicDisplayModelFactory(configSchema),
30
+ types.model({
31
+ /**
32
+ * #property
33
+ */
34
+ type: types.literal('LinearMafDisplay'),
35
+ /**
36
+ * #property
37
+ */
38
+ configuration: ConfigurationReference(configSchema),
39
+ }),
40
+ )
41
+ .volatile(() => ({
42
+ prefersOffset: true,
43
+ }))
44
+ .views(self => ({
45
+ /**
46
+ * #getter
47
+ */
48
+ get samples() {
49
+ const r = self.adapterConfig.samples as string[]
50
+ return r.map(elt => ({ name: elt, color: undefined }))
51
+ },
52
+ /**
53
+ * #getter
54
+ */
55
+ get rowHeight() {
56
+ return 20
57
+ },
58
+ /**
59
+ * #getter
60
+ */
61
+ get rendererTypeName() {
62
+ return 'LinearMafRenderer'
63
+ },
64
+ /**
65
+ * #getter
66
+ */
67
+ get rendererConfig(): AnyConfigurationModel {
68
+ const configBlob = getConf(self, ['renderer']) || {}
69
+ const config = configBlob as Omit<typeof configBlob, symbol>
70
+
71
+ return self.rendererType.configSchema.create(
72
+ {
73
+ ...config,
74
+ type: 'LinearMafRenderer',
75
+ },
76
+ getEnv(self),
77
+ )
78
+ },
79
+ }))
80
+ .views(self => {
81
+ const { renderProps: superRenderProps } = self
82
+ return {
83
+ /**
84
+ * #method
85
+ */
86
+ renderProps() {
87
+ return {
88
+ ...superRenderProps(),
89
+ samples: self.samples,
90
+ rowHeight: self.rowHeight,
91
+ }
92
+ },
93
+ }
94
+ })
95
+ .actions(self => {
96
+ const { renderSvg: superRenderSvg } = self
97
+ return {
98
+ /**
99
+ * #action
100
+ */
101
+ async renderSvg(opts: ExportSvgDisplayOptions): Promise<any> {
102
+ const { renderSvg } = await import('./renderSvg')
103
+ // @ts-expect-error
104
+ return renderSvg(self, opts, superRenderSvg)
105
+ },
106
+ }
107
+ })
108
+ }
109
+
110
+ export type LinearMafDisplayStateModel = ReturnType<typeof stateModelFactory>
111
+ export type LinearMafDisplayModel = Instance<LinearMafDisplayStateModel>
@@ -0,0 +1,172 @@
1
+ import { FeatureRendererType } from '@jbrowse/core/pluggableElementTypes'
2
+ import { RenderArgsDeserialized } from '@jbrowse/core/pluggableElementTypes/renderers/BoxRendererType'
3
+ import { createJBrowseTheme } from '@jbrowse/core/ui'
4
+ import {
5
+ Feature,
6
+ featureSpanPx,
7
+ renderToAbstractCanvas,
8
+ } from '@jbrowse/core/util'
9
+ import { Theme } from '@mui/material'
10
+
11
+ function getCorrectionFactor(scale: number) {
12
+ if (scale >= 1) {
13
+ return 0.6
14
+ } else if (scale >= 0.2) {
15
+ return 0.05
16
+ } else if (scale >= 0.02) {
17
+ return 0.03
18
+ } else {
19
+ return 0.02
20
+ }
21
+ }
22
+
23
+ export function getContrastBaseMap(theme: Theme) {
24
+ return Object.fromEntries(
25
+ Object.entries(getColorBaseMap(theme)).map(([key, value]) => [
26
+ key,
27
+ theme.palette.getContrastText(value),
28
+ ]),
29
+ )
30
+ }
31
+
32
+ export function getColorBaseMap(theme: Theme) {
33
+ const { bases } = theme.palette
34
+ return {
35
+ a: bases.A.main,
36
+ c: bases.C.main,
37
+ g: bases.G.main,
38
+ t: bases.T.main,
39
+ }
40
+ }
41
+ function makeImageData({
42
+ ctx,
43
+ renderArgs,
44
+ }: {
45
+ ctx: CanvasRenderingContext2D
46
+ renderArgs: RenderArgsDeserialized & {
47
+ samples: { name: string; color?: string }[]
48
+ rowHeight: number
49
+ }
50
+ }) {
51
+ const {
52
+ regions,
53
+ bpPerPx,
54
+ rowHeight,
55
+ theme: configTheme,
56
+ samples,
57
+ } = renderArgs
58
+ const [region] = regions
59
+ const features = renderArgs.features as Map<string, Feature>
60
+ const h = rowHeight
61
+ const theme = createJBrowseTheme(configTheme)
62
+ const colorForBase = getColorBaseMap(theme)
63
+ const contrastForBase = getContrastBaseMap(theme)
64
+ const sampleToRowMap = new Map(samples.map((s, i) => [s.name, i]))
65
+ const scale = 1 / bpPerPx
66
+ const correctionFactor = getCorrectionFactor(bpPerPx)
67
+
68
+ // sample as alignments
69
+ ctx.font = 'bold 10px Courier New,monospace'
70
+
71
+ for (const feature of features.values()) {
72
+ const [leftPx] = featureSpanPx(feature, region, bpPerPx)
73
+ const vals = feature.get('alignments') as Record<string, { data: string }>
74
+ const seq = feature.get('seq').toLowerCase()
75
+ for (const [sample, val] of Object.entries(vals)) {
76
+ const origAlignment = val.data
77
+ const alignment = origAlignment.toLowerCase()
78
+
79
+ // gaps
80
+ ctx.beginPath()
81
+ ctx.fillStyle = 'black'
82
+ const offset0 = (5 / 12) * h
83
+ const h6 = h / 6
84
+ const row = sampleToRowMap.get(sample)
85
+ if (row === undefined) {
86
+ throw new Error(`unknown sample encountered: ${sample}`)
87
+ }
88
+ const t = h * row
89
+ for (let i = 0; i < alignment.length; i++) {
90
+ const l = leftPx + scale * i
91
+ if (alignment[i] === '-') {
92
+ ctx.rect(l, offset0 + t, scale + correctionFactor, h6)
93
+ }
94
+ }
95
+ ctx.fill()
96
+ const offset = (1 / 4) * h
97
+ const h2 = h / 2
98
+
99
+ // matches
100
+ ctx.beginPath()
101
+ ctx.fillStyle = 'lightgrey'
102
+ for (let i = 0; i < alignment.length; i++) {
103
+ const c = alignment[i]
104
+ const l = leftPx + scale * i
105
+ if (seq[i] === c && c !== '-') {
106
+ ctx.rect(l, offset + t, scale + correctionFactor, h2)
107
+ }
108
+ }
109
+ ctx.fill()
110
+
111
+ // mismatches
112
+ for (let i = 0; i < alignment.length; i++) {
113
+ const c = alignment[i]
114
+ if (seq[i] !== c && c !== '-') {
115
+ const l = leftPx + scale * i
116
+ ctx.fillStyle =
117
+ colorForBase[c as keyof typeof colorForBase] ?? 'purple'
118
+ ctx.fillRect(l, offset + t, scale + correctionFactor, h2)
119
+ }
120
+ }
121
+
122
+ // font
123
+ const charSize = { w: 10 }
124
+ if (scale >= charSize.w) {
125
+ for (let i = 0; i < alignment.length; i++) {
126
+ const l = leftPx + scale * i
127
+ const offset = (scale - charSize.w) / 2 + 1
128
+ const c = alignment[i]
129
+ if (seq[i] !== c && c !== '-') {
130
+ ctx.fillStyle = contrastForBase[c] ?? 'black'
131
+ ctx.fillText(origAlignment[i], l + offset, h2 + t + 3)
132
+ }
133
+ }
134
+ }
135
+ }
136
+ }
137
+ }
138
+ export default class LinearMafRenderer extends FeatureRendererType {
139
+ async render(
140
+ renderProps: RenderArgsDeserialized & {
141
+ samples: { name: string; color?: string }[]
142
+ rowHeight: number
143
+ },
144
+ ) {
145
+ const { regions, bpPerPx, samples, rowHeight } = renderProps
146
+ const [region] = regions
147
+ const height = samples.length * rowHeight
148
+ const width = (region.end - region.start) / bpPerPx
149
+ const features = await this.getFeatures(renderProps)
150
+ const res = await renderToAbstractCanvas(width, height, renderProps, ctx =>
151
+ makeImageData({
152
+ ctx,
153
+ renderArgs: {
154
+ ...renderProps,
155
+ features,
156
+ },
157
+ }),
158
+ )
159
+ const results = await super.render({
160
+ ...renderProps,
161
+ ...res,
162
+ width,
163
+ height,
164
+ })
165
+ return {
166
+ ...results,
167
+ ...res,
168
+ width,
169
+ height,
170
+ }
171
+ }
172
+ }
@@ -0,0 +1,9 @@
1
+ import { PrerenderedCanvas } from '@jbrowse/core/ui'
2
+ import { observer } from 'mobx-react'
3
+ import React from 'react'
4
+
5
+ const LinearMafRendering = observer(function (props: any) {
6
+ return <PrerenderedCanvas {...props} />
7
+ })
8
+
9
+ export default LinearMafRendering