@jbrowse/plugin-breakpoint-split-view 2.6.1 → 2.6.2
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/dist/BreakpointAlignmentsFeatureDetail/BreakpointAlignmentsFeatureDetail.js +0 -1
- package/dist/BreakpointAlignmentsFeatureDetail/index.js +0 -1
- package/dist/BreakpointSplitView/BreakpointSplitView.js +0 -1
- package/dist/BreakpointSplitView/components/AlignmentConnections.js +0 -1
- package/dist/BreakpointSplitView/components/Breakends.js +0 -1
- package/dist/BreakpointSplitView/components/BreakpointSplitView.js +0 -1
- package/dist/BreakpointSplitView/components/ExportSvgDialog.js +0 -1
- package/dist/BreakpointSplitView/components/Overlay.js +0 -1
- package/dist/BreakpointSplitView/components/Translocations.js +0 -1
- package/dist/BreakpointSplitView/components/util.js +0 -1
- package/dist/BreakpointSplitView/index.js +0 -1
- package/dist/BreakpointSplitView/model.d.ts +1 -1
- package/dist/BreakpointSplitView/model.js +3 -3
- package/dist/BreakpointSplitView/svgcomponents/SVGBackground.js +0 -1
- package/dist/BreakpointSplitView/svgcomponents/SVGBreakpointSplitView.js +0 -1
- package/dist/BreakpointSplitView/util.js +0 -1
- package/dist/index.js +0 -1
- package/esm/BreakpointAlignmentsFeatureDetail/BreakpointAlignmentsFeatureDetail.js +0 -1
- package/esm/BreakpointAlignmentsFeatureDetail/index.js +0 -1
- package/esm/BreakpointSplitView/BreakpointSplitView.js +0 -1
- package/esm/BreakpointSplitView/components/AlignmentConnections.js +0 -1
- package/esm/BreakpointSplitView/components/Breakends.js +0 -1
- package/esm/BreakpointSplitView/components/BreakpointSplitView.js +0 -1
- package/esm/BreakpointSplitView/components/ExportSvgDialog.js +0 -1
- package/esm/BreakpointSplitView/components/Overlay.js +0 -1
- package/esm/BreakpointSplitView/components/Translocations.js +0 -1
- package/esm/BreakpointSplitView/components/util.js +0 -1
- package/esm/BreakpointSplitView/index.js +0 -1
- package/esm/BreakpointSplitView/model.d.ts +1 -1
- package/esm/BreakpointSplitView/model.js +3 -3
- package/esm/BreakpointSplitView/svgcomponents/SVGBackground.js +0 -1
- package/esm/BreakpointSplitView/svgcomponents/SVGBreakpointSplitView.js +0 -1
- package/esm/BreakpointSplitView/util.js +0 -1
- package/esm/index.js +0 -1
- package/package.json +3 -4
- package/dist/BreakpointAlignmentsFeatureDetail/BreakpointAlignmentsFeatureDetail.js.map +0 -1
- package/dist/BreakpointAlignmentsFeatureDetail/index.js.map +0 -1
- package/dist/BreakpointSplitView/BreakpointSplitView.js.map +0 -1
- package/dist/BreakpointSplitView/components/AlignmentConnections.js.map +0 -1
- package/dist/BreakpointSplitView/components/Breakends.js.map +0 -1
- package/dist/BreakpointSplitView/components/BreakpointSplitView.js.map +0 -1
- package/dist/BreakpointSplitView/components/ExportSvgDialog.js.map +0 -1
- package/dist/BreakpointSplitView/components/Overlay.js.map +0 -1
- package/dist/BreakpointSplitView/components/Translocations.js.map +0 -1
- package/dist/BreakpointSplitView/components/util.js.map +0 -1
- package/dist/BreakpointSplitView/index.js.map +0 -1
- package/dist/BreakpointSplitView/model.js.map +0 -1
- package/dist/BreakpointSplitView/svgcomponents/SVGBackground.js.map +0 -1
- package/dist/BreakpointSplitView/svgcomponents/SVGBreakpointSplitView.js.map +0 -1
- package/dist/BreakpointSplitView/util.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/esm/BreakpointAlignmentsFeatureDetail/BreakpointAlignmentsFeatureDetail.js.map +0 -1
- package/esm/BreakpointAlignmentsFeatureDetail/index.js.map +0 -1
- package/esm/BreakpointSplitView/BreakpointSplitView.js.map +0 -1
- package/esm/BreakpointSplitView/components/AlignmentConnections.js.map +0 -1
- package/esm/BreakpointSplitView/components/Breakends.js.map +0 -1
- package/esm/BreakpointSplitView/components/BreakpointSplitView.js.map +0 -1
- package/esm/BreakpointSplitView/components/ExportSvgDialog.js.map +0 -1
- package/esm/BreakpointSplitView/components/Overlay.js.map +0 -1
- package/esm/BreakpointSplitView/components/Translocations.js.map +0 -1
- package/esm/BreakpointSplitView/components/util.js.map +0 -1
- package/esm/BreakpointSplitView/index.js.map +0 -1
- package/esm/BreakpointSplitView/model.js.map +0 -1
- package/esm/BreakpointSplitView/svgcomponents/SVGBackground.js.map +0 -1
- package/esm/BreakpointSplitView/svgcomponents/SVGBreakpointSplitView.js.map +0 -1
- package/esm/BreakpointSplitView/util.js.map +0 -1
- package/esm/index.js.map +0 -1
- package/src/BreakpointAlignmentsFeatureDetail/BreakpointAlignmentsFeatureDetail.tsx +0 -24
- package/src/BreakpointAlignmentsFeatureDetail/index.ts +0 -35
- package/src/BreakpointSplitView/BreakpointSplitView.ts +0 -100
- package/src/BreakpointSplitView/components/AlignmentConnections.tsx +0 -166
- package/src/BreakpointSplitView/components/Breakends.tsx +0 -141
- package/src/BreakpointSplitView/components/BreakpointSplitView.tsx +0 -95
- package/src/BreakpointSplitView/components/ExportSvgDialog.tsx +0 -149
- package/src/BreakpointSplitView/components/Overlay.tsx +0 -29
- package/src/BreakpointSplitView/components/Translocations.tsx +0 -147
- package/src/BreakpointSplitView/components/util.ts +0 -127
- package/src/BreakpointSplitView/index.ts +0 -17
- package/src/BreakpointSplitView/model.ts +0 -333
- package/src/BreakpointSplitView/svgcomponents/SVGBackground.tsx +0 -21
- package/src/BreakpointSplitView/svgcomponents/SVGBreakpointSplitView.tsx +0 -179
- package/src/BreakpointSplitView/util.ts +0 -89
- package/src/index.test.ts +0 -3
- package/src/index.ts +0 -15
|
@@ -1,333 +0,0 @@
|
|
|
1
|
-
import React, { lazy } from 'react'
|
|
2
|
-
import {
|
|
3
|
-
types,
|
|
4
|
-
getParent,
|
|
5
|
-
onAction,
|
|
6
|
-
addDisposer,
|
|
7
|
-
getPath,
|
|
8
|
-
Instance,
|
|
9
|
-
} from 'mobx-state-tree'
|
|
10
|
-
import { autorun } from 'mobx'
|
|
11
|
-
import { saveAs } from 'file-saver'
|
|
12
|
-
|
|
13
|
-
// jbrowse
|
|
14
|
-
import {
|
|
15
|
-
LinearGenomeViewModel,
|
|
16
|
-
LinearGenomeViewStateModel,
|
|
17
|
-
} from '@jbrowse/plugin-linear-genome-view'
|
|
18
|
-
import PluginManager from '@jbrowse/core/PluginManager'
|
|
19
|
-
import { BaseViewModel } from '@jbrowse/core/pluggableElementTypes/models'
|
|
20
|
-
import { getSession, Feature, notEmpty } from '@jbrowse/core/util'
|
|
21
|
-
import { AnyConfigurationModel, getConf } from '@jbrowse/core/configuration'
|
|
22
|
-
|
|
23
|
-
// icons
|
|
24
|
-
import PhotoCamera from '@mui/icons-material/PhotoCamera'
|
|
25
|
-
import LinkIcon from '@mui/icons-material/Link'
|
|
26
|
-
|
|
27
|
-
// locals
|
|
28
|
-
import { intersect } from './util'
|
|
29
|
-
|
|
30
|
-
// lazies
|
|
31
|
-
const ExportSvgDialog = lazy(() => import('./components/ExportSvgDialog'))
|
|
32
|
-
|
|
33
|
-
function calc(
|
|
34
|
-
track: { displays: { searchFeatureByID: (str: string) => LayoutRecord }[] },
|
|
35
|
-
feat: Feature,
|
|
36
|
-
) {
|
|
37
|
-
return track.displays[0].searchFeatureByID(feat.id())
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export interface ExportSvgOptions {
|
|
41
|
-
rasterizeLayers?: boolean
|
|
42
|
-
filename?: string
|
|
43
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
44
|
-
Wrapper?: React.FC<any>
|
|
45
|
-
fontSize?: number
|
|
46
|
-
rulerHeight?: number
|
|
47
|
-
textHeight?: number
|
|
48
|
-
paddingHeight?: number
|
|
49
|
-
headerHeight?: number
|
|
50
|
-
cytobandHeight?: number
|
|
51
|
-
trackLabels?: string
|
|
52
|
-
themeName?: string
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
type LGV = LinearGenomeViewModel
|
|
56
|
-
|
|
57
|
-
export interface Breakend {
|
|
58
|
-
MateDirection: string
|
|
59
|
-
Join: string
|
|
60
|
-
Replacement: string
|
|
61
|
-
MatePosition: string
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export type LayoutRecord = [number, number, number, number]
|
|
65
|
-
|
|
66
|
-
async function getBlockFeatures(
|
|
67
|
-
model: BreakpointViewModel,
|
|
68
|
-
track: { configuration: AnyConfigurationModel },
|
|
69
|
-
) {
|
|
70
|
-
const { views } = model
|
|
71
|
-
const { rpcManager, assemblyManager } = getSession(model)
|
|
72
|
-
const assemblyName = model.views[0].assemblyNames[0]
|
|
73
|
-
const assembly = await assemblyManager.waitForAssembly(assemblyName)
|
|
74
|
-
if (!assembly) {
|
|
75
|
-
return undefined // throw new Error(`assembly not found: "${assemblyName}"`)
|
|
76
|
-
}
|
|
77
|
-
const sessionId = track.configuration.trackId
|
|
78
|
-
return Promise.all(
|
|
79
|
-
views.map(async view =>
|
|
80
|
-
(
|
|
81
|
-
(await rpcManager.call(sessionId, 'CoreGetFeatures', {
|
|
82
|
-
adapterConfig: getConf(track, ['adapter']),
|
|
83
|
-
sessionId,
|
|
84
|
-
regions: view.staticBlocks.contentBlocks,
|
|
85
|
-
})) as Feature[][]
|
|
86
|
-
).flat(),
|
|
87
|
-
),
|
|
88
|
-
)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export default function stateModelFactory(pluginManager: PluginManager) {
|
|
92
|
-
const minHeight = 40
|
|
93
|
-
const defaultHeight = 400
|
|
94
|
-
const model = types
|
|
95
|
-
.model('BreakpointSplitView', {
|
|
96
|
-
type: types.literal('BreakpointSplitView'),
|
|
97
|
-
height: types.optional(
|
|
98
|
-
types.refinement(
|
|
99
|
-
'viewHeight',
|
|
100
|
-
types.number,
|
|
101
|
-
(n: number) => n >= minHeight,
|
|
102
|
-
),
|
|
103
|
-
defaultHeight,
|
|
104
|
-
),
|
|
105
|
-
trackSelectorType: 'hierarchical',
|
|
106
|
-
showIntraviewLinks: true,
|
|
107
|
-
linkViews: false,
|
|
108
|
-
interactToggled: false,
|
|
109
|
-
views: types.array(
|
|
110
|
-
pluginManager.getViewType('LinearGenomeView')
|
|
111
|
-
.stateModel as LinearGenomeViewStateModel,
|
|
112
|
-
),
|
|
113
|
-
})
|
|
114
|
-
.volatile(() => ({
|
|
115
|
-
width: 800,
|
|
116
|
-
matchedTrackFeatures: {} as { [key: string]: Feature[][] },
|
|
117
|
-
}))
|
|
118
|
-
.views(self => ({
|
|
119
|
-
/**
|
|
120
|
-
* #method
|
|
121
|
-
* creates an svg export and save using FileSaver
|
|
122
|
-
*/
|
|
123
|
-
async exportSvg(opts: ExportSvgOptions = {}) {
|
|
124
|
-
const { renderToSvg } = await import(
|
|
125
|
-
'./svgcomponents/SVGBreakpointSplitView'
|
|
126
|
-
)
|
|
127
|
-
const html = await renderToSvg(self as BreakpointViewModel, opts)
|
|
128
|
-
const blob = new Blob([html], { type: 'image/svg+xml' })
|
|
129
|
-
saveAs(blob, opts.filename || 'image.svg')
|
|
130
|
-
},
|
|
131
|
-
}))
|
|
132
|
-
.views(self => ({
|
|
133
|
-
// Find all track ids that match across multiple views
|
|
134
|
-
get matchedTracks() {
|
|
135
|
-
return intersect(
|
|
136
|
-
elt => elt.configuration.trackId as string,
|
|
137
|
-
...self.views.map(view => view.tracks),
|
|
138
|
-
)
|
|
139
|
-
},
|
|
140
|
-
|
|
141
|
-
// Get tracks with a given trackId across multiple views
|
|
142
|
-
getMatchedTracks(trackConfigId: string) {
|
|
143
|
-
return self.views
|
|
144
|
-
.map(view => view.getTrack(trackConfigId))
|
|
145
|
-
.filter(f => !!f)
|
|
146
|
-
},
|
|
147
|
-
|
|
148
|
-
// Translocation features are handled differently
|
|
149
|
-
// since they do not have a mate e.g. they are one sided
|
|
150
|
-
hasTranslocations(trackConfigId: string) {
|
|
151
|
-
return [...this.getTrackFeatures(trackConfigId).values()].find(
|
|
152
|
-
f => f.get('type') === 'translocation',
|
|
153
|
-
)
|
|
154
|
-
},
|
|
155
|
-
|
|
156
|
-
// Get a composite map of featureId->feature map for a track across
|
|
157
|
-
// multiple views
|
|
158
|
-
getTrackFeatures(trackConfigId: string) {
|
|
159
|
-
return new Map(
|
|
160
|
-
self.matchedTrackFeatures[trackConfigId]
|
|
161
|
-
?.flat()
|
|
162
|
-
.map(f => [f.id(), f]),
|
|
163
|
-
)
|
|
164
|
-
},
|
|
165
|
-
|
|
166
|
-
getMatchedFeaturesInLayout(trackConfigId: string, features: Feature[][]) {
|
|
167
|
-
// use reverse to search the second track first
|
|
168
|
-
const tracks = this.getMatchedTracks(trackConfigId)
|
|
169
|
-
|
|
170
|
-
return features.map(c =>
|
|
171
|
-
c
|
|
172
|
-
.map(feature => {
|
|
173
|
-
const level = tracks.findIndex(track => calc(track, feature))
|
|
174
|
-
return level !== -1
|
|
175
|
-
? {
|
|
176
|
-
feature,
|
|
177
|
-
layout: calc(tracks[level], feature),
|
|
178
|
-
level,
|
|
179
|
-
}
|
|
180
|
-
: undefined
|
|
181
|
-
})
|
|
182
|
-
.filter(notEmpty),
|
|
183
|
-
)
|
|
184
|
-
},
|
|
185
|
-
}))
|
|
186
|
-
.actions(self => ({
|
|
187
|
-
afterAttach() {
|
|
188
|
-
addDisposer(
|
|
189
|
-
self,
|
|
190
|
-
onAction(
|
|
191
|
-
self,
|
|
192
|
-
({
|
|
193
|
-
name,
|
|
194
|
-
path,
|
|
195
|
-
args,
|
|
196
|
-
}: {
|
|
197
|
-
name: string
|
|
198
|
-
path?: string
|
|
199
|
-
args?: unknown[]
|
|
200
|
-
}) => {
|
|
201
|
-
if (self.linkViews) {
|
|
202
|
-
const actions = [
|
|
203
|
-
'horizontalScroll',
|
|
204
|
-
'zoomTo',
|
|
205
|
-
'setScaleFactor',
|
|
206
|
-
'showTrack',
|
|
207
|
-
'toggleTrack',
|
|
208
|
-
'hideTrack',
|
|
209
|
-
'setTrackLabels',
|
|
210
|
-
'toggleCenterLine',
|
|
211
|
-
]
|
|
212
|
-
if (actions.includes(name) && path) {
|
|
213
|
-
this.onSubviewAction(name, path, args)
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
},
|
|
217
|
-
),
|
|
218
|
-
)
|
|
219
|
-
},
|
|
220
|
-
|
|
221
|
-
onSubviewAction(actionName: string, path: string, args?: unknown[]) {
|
|
222
|
-
self.views.forEach(view => {
|
|
223
|
-
const ret = getPath(view)
|
|
224
|
-
if (ret.lastIndexOf(path) !== ret.length - path.length) {
|
|
225
|
-
// @ts-ignore
|
|
226
|
-
view[actionName](args?.[0])
|
|
227
|
-
}
|
|
228
|
-
})
|
|
229
|
-
},
|
|
230
|
-
|
|
231
|
-
setWidth(newWidth: number) {
|
|
232
|
-
self.width = newWidth
|
|
233
|
-
self.views.forEach(v => v.setWidth(newWidth))
|
|
234
|
-
},
|
|
235
|
-
|
|
236
|
-
removeView(view: LGV) {
|
|
237
|
-
self.views.remove(view)
|
|
238
|
-
},
|
|
239
|
-
|
|
240
|
-
closeView() {
|
|
241
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
242
|
-
getParent<any>(self, 2).removeView(self)
|
|
243
|
-
},
|
|
244
|
-
|
|
245
|
-
toggleInteract() {
|
|
246
|
-
self.interactToggled = !self.interactToggled
|
|
247
|
-
},
|
|
248
|
-
toggleIntraviewLinks() {
|
|
249
|
-
self.showIntraviewLinks = !self.showIntraviewLinks
|
|
250
|
-
},
|
|
251
|
-
toggleLinkViews() {
|
|
252
|
-
self.linkViews = !self.linkViews
|
|
253
|
-
},
|
|
254
|
-
setMatchedTrackFeatures(obj: { [key: string]: Feature[][] }) {
|
|
255
|
-
self.matchedTrackFeatures = obj
|
|
256
|
-
},
|
|
257
|
-
}))
|
|
258
|
-
.actions(self => ({
|
|
259
|
-
afterAttach() {
|
|
260
|
-
addDisposer(
|
|
261
|
-
self,
|
|
262
|
-
autorun(async () => {
|
|
263
|
-
try {
|
|
264
|
-
if (!self.views.every(view => view.initialized)) {
|
|
265
|
-
return
|
|
266
|
-
}
|
|
267
|
-
self.setMatchedTrackFeatures(
|
|
268
|
-
Object.fromEntries(
|
|
269
|
-
await Promise.all(
|
|
270
|
-
self.matchedTracks.map(async track => [
|
|
271
|
-
track.configuration.trackId,
|
|
272
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
273
|
-
await getBlockFeatures(self as any, track),
|
|
274
|
-
]),
|
|
275
|
-
),
|
|
276
|
-
),
|
|
277
|
-
)
|
|
278
|
-
} catch (e) {
|
|
279
|
-
console.error(e)
|
|
280
|
-
getSession(self).notify(`${e}`, 'error')
|
|
281
|
-
}
|
|
282
|
-
}),
|
|
283
|
-
)
|
|
284
|
-
},
|
|
285
|
-
|
|
286
|
-
menuItems() {
|
|
287
|
-
return [
|
|
288
|
-
...self.views
|
|
289
|
-
.map((view, idx) => [idx, view.menuItems?.()] as const)
|
|
290
|
-
.filter(f => !!f[1])
|
|
291
|
-
.map(f => ({ label: `View ${f[0] + 1} Menu`, subMenu: f[1] })),
|
|
292
|
-
|
|
293
|
-
{
|
|
294
|
-
label: 'Show intra-view links',
|
|
295
|
-
type: 'checkbox',
|
|
296
|
-
checked: self.showIntraviewLinks,
|
|
297
|
-
onClick: () => self.toggleIntraviewLinks(),
|
|
298
|
-
},
|
|
299
|
-
{
|
|
300
|
-
label: 'Allow clicking alignment squiggles?',
|
|
301
|
-
type: 'checkbox',
|
|
302
|
-
checked: self.interactToggled,
|
|
303
|
-
onClick: () => self.toggleInteract(),
|
|
304
|
-
},
|
|
305
|
-
|
|
306
|
-
{
|
|
307
|
-
label: 'Link views',
|
|
308
|
-
type: 'checkbox',
|
|
309
|
-
icon: LinkIcon,
|
|
310
|
-
checked: self.linkViews,
|
|
311
|
-
onClick: () => {
|
|
312
|
-
self.toggleLinkViews()
|
|
313
|
-
},
|
|
314
|
-
},
|
|
315
|
-
{
|
|
316
|
-
label: 'Export SVG',
|
|
317
|
-
icon: PhotoCamera,
|
|
318
|
-
onClick: (): void => {
|
|
319
|
-
getSession(self).queueDialog(handleClose => [
|
|
320
|
-
ExportSvgDialog,
|
|
321
|
-
{ model: self, handleClose },
|
|
322
|
-
])
|
|
323
|
-
},
|
|
324
|
-
},
|
|
325
|
-
]
|
|
326
|
-
},
|
|
327
|
-
}))
|
|
328
|
-
|
|
329
|
-
return types.compose(BaseViewModel, model)
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
export type BreakpointViewStateModel = ReturnType<typeof stateModelFactory>
|
|
333
|
-
export type BreakpointViewModel = Instance<BreakpointViewStateModel>
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { useTheme } from '@mui/material'
|
|
3
|
-
|
|
4
|
-
export default function SVGBackground({
|
|
5
|
-
width,
|
|
6
|
-
height,
|
|
7
|
-
shift,
|
|
8
|
-
}: {
|
|
9
|
-
width: number
|
|
10
|
-
height: number
|
|
11
|
-
shift: number
|
|
12
|
-
}) {
|
|
13
|
-
const theme = useTheme()
|
|
14
|
-
return (
|
|
15
|
-
<rect
|
|
16
|
-
width={width + shift * 2}
|
|
17
|
-
height={height}
|
|
18
|
-
fill={theme.palette.background.default}
|
|
19
|
-
/>
|
|
20
|
-
)
|
|
21
|
-
}
|
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { renderToStaticMarkup } from 'react-dom/server'
|
|
3
|
-
import { when } from 'mobx'
|
|
4
|
-
import {
|
|
5
|
-
AbstractSessionModel,
|
|
6
|
-
getSession,
|
|
7
|
-
max,
|
|
8
|
-
measureText,
|
|
9
|
-
sum,
|
|
10
|
-
} from '@jbrowse/core/util'
|
|
11
|
-
import { ThemeProvider } from '@mui/material'
|
|
12
|
-
import { createJBrowseTheme } from '@jbrowse/core/ui'
|
|
13
|
-
|
|
14
|
-
// locals
|
|
15
|
-
import {
|
|
16
|
-
SVGTracks,
|
|
17
|
-
SVGRuler,
|
|
18
|
-
totalHeight,
|
|
19
|
-
LinearGenomeViewModel,
|
|
20
|
-
} from '@jbrowse/plugin-linear-genome-view'
|
|
21
|
-
|
|
22
|
-
// locals
|
|
23
|
-
import SVGBackground from './SVGBackground'
|
|
24
|
-
import { ExportSvgOptions, BreakpointViewModel } from '../model'
|
|
25
|
-
import { getTrackName } from '@jbrowse/core/util/tracks'
|
|
26
|
-
import Overlay from '../components/Overlay'
|
|
27
|
-
|
|
28
|
-
type BSV = BreakpointViewModel
|
|
29
|
-
|
|
30
|
-
function getTrackNameMaxLen(
|
|
31
|
-
views: LinearGenomeViewModel[],
|
|
32
|
-
fontSize: number,
|
|
33
|
-
session: AbstractSessionModel,
|
|
34
|
-
) {
|
|
35
|
-
return max(
|
|
36
|
-
views.flatMap(view =>
|
|
37
|
-
view.tracks.map(t =>
|
|
38
|
-
measureText(getTrackName(t.configuration, session), fontSize),
|
|
39
|
-
),
|
|
40
|
-
),
|
|
41
|
-
0,
|
|
42
|
-
)
|
|
43
|
-
}
|
|
44
|
-
function getTrackOffsets(
|
|
45
|
-
view: LinearGenomeViewModel,
|
|
46
|
-
textOffset: number,
|
|
47
|
-
extra = 0,
|
|
48
|
-
) {
|
|
49
|
-
const offsets = {} as { [key: string]: number }
|
|
50
|
-
let curr = textOffset
|
|
51
|
-
for (let i = 0; i < view.tracks.length; i++) {
|
|
52
|
-
const track = view.tracks[i]
|
|
53
|
-
offsets[track.configuration.trackId] = curr + extra
|
|
54
|
-
curr += track.displays[0].height + textOffset
|
|
55
|
-
}
|
|
56
|
-
return offsets
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// render LGV to SVG
|
|
60
|
-
export async function renderToSvg(model: BSV, opts: ExportSvgOptions) {
|
|
61
|
-
const {
|
|
62
|
-
textHeight = 18,
|
|
63
|
-
headerHeight = 30,
|
|
64
|
-
rulerHeight = 30,
|
|
65
|
-
fontSize = 13,
|
|
66
|
-
trackLabels = 'offset',
|
|
67
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
68
|
-
Wrapper = ({ children }: any) => <>{children}</>,
|
|
69
|
-
themeName = 'default',
|
|
70
|
-
} = opts
|
|
71
|
-
const session = getSession(model)
|
|
72
|
-
const theme = session.allThemes?.()[themeName]
|
|
73
|
-
const { width, views } = model
|
|
74
|
-
const shift = 50
|
|
75
|
-
const offset = headerHeight + rulerHeight
|
|
76
|
-
|
|
77
|
-
const heights = views.map(
|
|
78
|
-
v => totalHeight(v.tracks, textHeight, trackLabels) + offset,
|
|
79
|
-
)
|
|
80
|
-
const totalHeightSvg = sum(heights) + 100
|
|
81
|
-
const displayResults = await Promise.all(
|
|
82
|
-
views.map(
|
|
83
|
-
async view =>
|
|
84
|
-
({
|
|
85
|
-
view,
|
|
86
|
-
data: await Promise.all(
|
|
87
|
-
view.tracks.map(async track => {
|
|
88
|
-
const d = track.displays[0]
|
|
89
|
-
await when(() => (d.ready !== undefined ? d.ready : true))
|
|
90
|
-
return { track, result: await d.renderSvg({ ...opts, theme }) }
|
|
91
|
-
}),
|
|
92
|
-
),
|
|
93
|
-
} as const),
|
|
94
|
-
),
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
const trackLabelMaxLen = getTrackNameMaxLen(views, fontSize, session) + 40
|
|
98
|
-
const trackLabelOffset = trackLabels === 'left' ? trackLabelMaxLen : 0
|
|
99
|
-
const textOffset = trackLabels === 'offset' ? textHeight : 0
|
|
100
|
-
const trackOffsets = [
|
|
101
|
-
getTrackOffsets(views[0], textOffset, fontSize + offset),
|
|
102
|
-
getTrackOffsets(views[1], textOffset, fontSize + heights[0] + offset),
|
|
103
|
-
]
|
|
104
|
-
const w = width + trackLabelOffset
|
|
105
|
-
const t = createJBrowseTheme(theme)
|
|
106
|
-
|
|
107
|
-
// the xlink namespace is used for rendering <image> tag
|
|
108
|
-
return renderToStaticMarkup(
|
|
109
|
-
<ThemeProvider theme={t}>
|
|
110
|
-
<Wrapper>
|
|
111
|
-
<svg
|
|
112
|
-
width={width}
|
|
113
|
-
height={totalHeightSvg}
|
|
114
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
115
|
-
xmlnsXlink="http://www.w3.org/1999/xlink"
|
|
116
|
-
viewBox={[0, 0, w + shift * 2, totalHeightSvg].toString()}
|
|
117
|
-
>
|
|
118
|
-
<SVGBackground width={w} height={totalHeightSvg} shift={shift} />
|
|
119
|
-
<g transform={`translate(${shift} ${fontSize})`}>
|
|
120
|
-
<g transform={`translate(${trackLabelOffset})`}>
|
|
121
|
-
<text x={0} fontSize={fontSize} fill={t.palette.text.primary}>
|
|
122
|
-
{views[0].assemblyNames.join(', ')}
|
|
123
|
-
</text>
|
|
124
|
-
|
|
125
|
-
<SVGRuler model={displayResults[0].view} fontSize={fontSize} />
|
|
126
|
-
</g>
|
|
127
|
-
<SVGTracks
|
|
128
|
-
textHeight={textHeight}
|
|
129
|
-
trackLabels={trackLabels}
|
|
130
|
-
fontSize={fontSize}
|
|
131
|
-
model={displayResults[0].view}
|
|
132
|
-
displayResults={displayResults[0].data}
|
|
133
|
-
offset={offset}
|
|
134
|
-
trackLabelOffset={trackLabelOffset}
|
|
135
|
-
/>
|
|
136
|
-
</g>
|
|
137
|
-
|
|
138
|
-
<g transform={`translate(${shift} ${fontSize + heights[0]})`}>
|
|
139
|
-
<g transform={`translate(${trackLabelOffset})`}>
|
|
140
|
-
<text x={0} fontSize={fontSize} fill={t.palette.text.primary}>
|
|
141
|
-
{views[1].assemblyNames.join(', ')}
|
|
142
|
-
</text>
|
|
143
|
-
<SVGRuler model={displayResults[1].view} fontSize={fontSize} />
|
|
144
|
-
</g>
|
|
145
|
-
<SVGTracks
|
|
146
|
-
textHeight={textHeight}
|
|
147
|
-
trackLabels={trackLabels}
|
|
148
|
-
fontSize={fontSize}
|
|
149
|
-
model={displayResults[1].view}
|
|
150
|
-
displayResults={displayResults[1].data}
|
|
151
|
-
offset={offset}
|
|
152
|
-
trackLabelOffset={trackLabelOffset}
|
|
153
|
-
/>
|
|
154
|
-
</g>
|
|
155
|
-
|
|
156
|
-
<defs>
|
|
157
|
-
<clipPath id="clip-bsv">
|
|
158
|
-
<rect x={0} y={0} width={width} height={totalHeightSvg} />
|
|
159
|
-
</clipPath>
|
|
160
|
-
</defs>
|
|
161
|
-
<g
|
|
162
|
-
transform={`translate(${trackLabelOffset + shift})`}
|
|
163
|
-
clipPath="url(#clip-bsv)"
|
|
164
|
-
>
|
|
165
|
-
{model.matchedTracks.map(track => (
|
|
166
|
-
<Overlay
|
|
167
|
-
parentRef={{ current: null }}
|
|
168
|
-
key={track.configuration.trackId}
|
|
169
|
-
model={model}
|
|
170
|
-
trackId={track.configuration.trackId}
|
|
171
|
-
getTrackYPosOverride={(id, level) => trackOffsets[level][id]}
|
|
172
|
-
/>
|
|
173
|
-
))}
|
|
174
|
-
</g>
|
|
175
|
-
</svg>
|
|
176
|
-
</Wrapper>
|
|
177
|
-
</ThemeProvider>,
|
|
178
|
-
)
|
|
179
|
-
}
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect } from 'react'
|
|
2
|
-
import { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
|
|
3
|
-
import { clamp } from '@jbrowse/core/util'
|
|
4
|
-
|
|
5
|
-
// locals
|
|
6
|
-
import { LayoutRecord } from './model'
|
|
7
|
-
|
|
8
|
-
type LGV = LinearGenomeViewModel
|
|
9
|
-
|
|
10
|
-
interface Display {
|
|
11
|
-
height: number
|
|
12
|
-
scrollTop: number
|
|
13
|
-
SNPCoverageDisplay?: { height: number }
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
interface Track {
|
|
17
|
-
displays: Display[]
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const [, TOP, , BOTTOM] = [0, 1, 2, 3]
|
|
21
|
-
|
|
22
|
-
function cheight(chunk: LayoutRecord) {
|
|
23
|
-
return chunk[BOTTOM] - chunk[TOP]
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function heightFromSpecificLevel(
|
|
27
|
-
views: LGV[],
|
|
28
|
-
trackId: string,
|
|
29
|
-
level: number,
|
|
30
|
-
getYPosOverride?: (trackId: string, level: number) => number,
|
|
31
|
-
) {
|
|
32
|
-
return getYPosOverride
|
|
33
|
-
? getYPosOverride(trackId, level)
|
|
34
|
-
: views[level].trackRefs[trackId]?.getBoundingClientRect().top || 0
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function getPxFromCoordinate(view: LGV, refName: string, coord: number) {
|
|
38
|
-
return ((view.bpToPx({ refName, coord }) || {}).offsetPx || 0) - view.offsetPx
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// get's the yposition of a layout record in a track
|
|
42
|
-
export function yPos(
|
|
43
|
-
trackId: string,
|
|
44
|
-
level: number,
|
|
45
|
-
views: LGV[],
|
|
46
|
-
tracks: Track[],
|
|
47
|
-
c: LayoutRecord,
|
|
48
|
-
getYPosOverride?: (trackId: string, level: number) => number,
|
|
49
|
-
) {
|
|
50
|
-
const display = tracks[level].displays[0]
|
|
51
|
-
const min = 0
|
|
52
|
-
const max = display.height
|
|
53
|
-
let offset = 0
|
|
54
|
-
const { SNPCoverageDisplay } = display
|
|
55
|
-
if (SNPCoverageDisplay) {
|
|
56
|
-
offset = SNPCoverageDisplay.height
|
|
57
|
-
}
|
|
58
|
-
const yPos = getYPosOverride ? 0 : display.scrollTop
|
|
59
|
-
return (
|
|
60
|
-
clamp(c[TOP] - yPos + cheight(c) / 2 + offset, min, max) +
|
|
61
|
-
heightFromSpecificLevel(views, trackId, level, getYPosOverride) +
|
|
62
|
-
display.scrollTop
|
|
63
|
-
)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// we combo a useEffect and useState combo to force rerender on snap changing.
|
|
67
|
-
// the setup of this being a useEffect+useState makes it re-render once the
|
|
68
|
-
// useEffect is called, which is generally the "next frame". If we removed the
|
|
69
|
-
// below use
|
|
70
|
-
export const useNextFrame = (variable: unknown) => {
|
|
71
|
-
const [, setNextFrameState] = useState<unknown>()
|
|
72
|
-
useEffect(() => {
|
|
73
|
-
setNextFrameState(variable)
|
|
74
|
-
}, [variable])
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// https://stackoverflow.com/a/49186706/2129219 the array-intersection package
|
|
78
|
-
// on npm has a large kb size, and we are just intersecting open track ids so
|
|
79
|
-
// simple is better
|
|
80
|
-
export function intersect<T>(
|
|
81
|
-
cb: (l: T) => string,
|
|
82
|
-
a1: T[] = [],
|
|
83
|
-
a2: T[] = [],
|
|
84
|
-
...rest: T[][]
|
|
85
|
-
): T[] {
|
|
86
|
-
const ids = new Set(a2.map(elt => cb(elt)))
|
|
87
|
-
const a12 = a1.filter(value => ids.has(cb(value)))
|
|
88
|
-
return rest.length === 0 ? a12 : intersect(cb, a12, ...rest)
|
|
89
|
-
}
|
package/src/index.test.ts
DELETED
package/src/index.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import PluginManager from '@jbrowse/core/PluginManager'
|
|
2
|
-
import Plugin from '@jbrowse/core/Plugin'
|
|
3
|
-
import BreakpointAlignmentsWidgetF from './BreakpointAlignmentsFeatureDetail'
|
|
4
|
-
import BreakpointSplitViewF from './BreakpointSplitView'
|
|
5
|
-
|
|
6
|
-
export default class BreakpointSplitViewPlugin extends Plugin {
|
|
7
|
-
name = 'BreakpointSplitViewPlugin'
|
|
8
|
-
|
|
9
|
-
install(pluginManager: PluginManager) {
|
|
10
|
-
BreakpointSplitViewF(pluginManager)
|
|
11
|
-
BreakpointAlignmentsWidgetF(pluginManager)
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
configure() {}
|
|
15
|
-
}
|