@jbrowse/plugin-alignments 1.7.9 → 1.7.10

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.
@@ -388,6 +388,7 @@ var SNPCoverageAdapter = /*#__PURE__*/function (_BaseFeatureDataAdapt) {
388
388
  if (bins[_i] === undefined) {
389
389
  bins[_i] = {
390
390
  total: 0,
391
+ all: 0,
391
392
  ref: 0,
392
393
  '-1': 0,
393
394
  '0': 0,
@@ -401,6 +402,7 @@ var SNPCoverageAdapter = /*#__PURE__*/function (_BaseFeatureDataAdapt) {
401
402
 
402
403
  if (j !== fend) {
403
404
  bins[_i].total++;
405
+ bins[_i].all++;
404
406
  bins[_i].ref++;
405
407
  bins[_i][fstrand]++;
406
408
  }
@@ -547,6 +549,7 @@ var SNPCoverageAdapter = /*#__PURE__*/function (_BaseFeatureDataAdapt) {
547
549
  _bin.total--;
548
550
  } else if (!interbase && colorSNPs) {
549
551
  inc(_bin, fstrand, 'cov', base);
552
+ _bin.refbase = mismatch.altbase;
550
553
  }
551
554
  }
552
555
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jbrowse/plugin-alignments",
3
- "version": "1.7.9",
3
+ "version": "1.7.10",
4
4
  "description": "JBrowse 2 alignments adapters, tracks, etc.",
5
5
  "keywords": [
6
6
  "jbrowse",
@@ -57,5 +57,5 @@
57
57
  "publishConfig": {
58
58
  "access": "public"
59
59
  },
60
- "gitHead": "a6504c385d703ce6e755d05652ef659ffe28c864"
60
+ "gitHead": "02d8c1e88e5603ea5855faed4ccb814e28071b32"
61
61
  }
@@ -77,12 +77,22 @@ function AlignmentFlags(props: { feature: any }) {
77
77
 
78
78
  function Formatter({ value }: { value: unknown }) {
79
79
  const [show, setShow] = useState(false)
80
+ const [copied, setCopied] = useState(false)
80
81
  const display = String(value)
81
82
  if (display.length > 100) {
82
83
  return (
83
84
  <>
84
- <button type="button" onClick={() => copy(display)}>
85
- Copy
85
+ <button
86
+ type="button"
87
+ onClick={() => {
88
+ copy(display)
89
+ setCopied(true)
90
+ setTimeout(() => {
91
+ setCopied(false)
92
+ }, 700)
93
+ }}
94
+ >
95
+ {copied ? 'Copied to clipboard' : 'Copy'}
86
96
  </button>
87
97
  <button type="button" onClick={() => setShow(val => !val)}>
88
98
  {show ? 'Show less' : 'Show more'}
@@ -1,30 +1,20 @@
1
1
  import { lazy } from 'react'
2
2
  import PluginManager from '@jbrowse/core/PluginManager'
3
3
  import { ConfigurationSchema } from '@jbrowse/core/configuration'
4
- import { ElementId } from '@jbrowse/core/util/types/mst'
5
4
  import { types } from 'mobx-state-tree'
6
5
  import WidgetType from '@jbrowse/core/pluggableElementTypes/WidgetType'
6
+ import { stateModelFactory as baseModelFactory } from '@jbrowse/core/BaseFeatureWidget'
7
7
 
8
8
  const configSchema = ConfigurationSchema('AlignmentsFeatureWidget', {})
9
9
 
10
10
  export function stateModelFactory(pluginManager: PluginManager) {
11
- return types
12
- .model('AlignmentsFeatureWidget', {
13
- id: ElementId,
11
+ const baseModel = baseModelFactory(pluginManager)
12
+ return types.compose(
13
+ baseModel,
14
+ types.model('AlignmentsFeatureWidget', {
14
15
  type: types.literal('AlignmentsFeatureWidget'),
15
- featureData: types.frozen(),
16
- view: types.safeReference(
17
- pluginManager.pluggableMstType('view', 'stateModel'),
18
- ),
19
- })
20
- .actions(self => ({
21
- setFeatureData(data: unknown) {
22
- self.featureData = data
23
- },
24
- clearFeatureData() {
25
- self.featureData = undefined
26
- },
27
- }))
16
+ }),
17
+ )
28
18
  }
29
19
 
30
20
  export default function register(pluginManager: PluginManager) {
@@ -39,6 +39,7 @@ export function cigarToMismatches(
39
39
  start: roffset + j,
40
40
  type: 'mismatch',
41
41
  base: seq[soffset + j],
42
+ altbase: ref[roffset + j],
42
43
  length: 1,
43
44
  })
44
45
  }
@@ -1,5 +1,9 @@
1
1
  import { lazy } from 'react'
2
+ import { autorun, observable } from 'mobx'
3
+ import { cast, types, addDisposer, getEnv, Instance } from 'mobx-state-tree'
4
+ import copy from 'copy-to-clipboard'
2
5
  import {
6
+ AnyConfigurationModel,
3
7
  ConfigurationReference,
4
8
  readConfObject,
5
9
  getConf,
@@ -13,26 +17,25 @@ import {
13
17
  Feature,
14
18
  } from '@jbrowse/core/util'
15
19
 
16
- import VisibilityIcon from '@material-ui/icons/Visibility'
17
- import { ContentCopy as ContentCopyIcon } from '@jbrowse/core/ui/Icons'
18
20
  import {
19
21
  LinearGenomeViewModel,
20
22
  BaseLinearDisplay,
21
23
  } from '@jbrowse/plugin-linear-genome-view'
22
- import { cast, types, addDisposer, getEnv, Instance } from 'mobx-state-tree'
23
- import copy from 'copy-to-clipboard'
24
+
25
+ // icons
26
+ import VisibilityIcon from '@material-ui/icons/Visibility'
27
+ import { ContentCopy as ContentCopyIcon } from '@jbrowse/core/ui/Icons'
24
28
  import MenuOpenIcon from '@material-ui/icons/MenuOpen'
25
29
  import SortIcon from '@material-ui/icons/Sort'
26
30
  import PaletteIcon from '@material-ui/icons/Palette'
27
31
  import FilterListIcon from '@material-ui/icons/ClearAll'
28
32
 
29
- import { autorun, observable } from 'mobx'
30
- import { AnyConfigurationModel } from '@jbrowse/core/configuration/configurationSchema'
33
+ // locals
31
34
  import { LinearPileupDisplayConfigModel } from './configSchema'
32
35
  import LinearPileupDisplayBlurb from './components/LinearPileupDisplayBlurb'
33
-
34
36
  import { getUniqueTagValues, getUniqueModificationValues } from '../shared'
35
37
 
38
+ // async
36
39
  const ColorByTagDlg = lazy(() => import('./components/ColorByTag'))
37
40
  const FilterByTagDlg = lazy(() => import('./components/FilterByTag'))
38
41
  const SortByTagDlg = lazy(() => import('./components/SortByTag'))
@@ -94,6 +97,7 @@ const stateModelFactory = (configSchema: LinearPileupDisplayConfigModel) =>
94
97
  .volatile(() => ({
95
98
  colorTagMap: observable.map<string, string>({}),
96
99
  modificationTagMap: observable.map<string, string>({}),
100
+ featureUnderMouseVolatile: undefined as undefined | Feature,
97
101
  ready: false,
98
102
  }))
99
103
  .actions(self => ({
@@ -128,7 +132,9 @@ const stateModelFactory = (configSchema: LinearPileupDisplayConfigModel) =>
128
132
  },
129
133
 
130
134
  updateColorTagMap(uniqueTag: string[]) {
131
- // pale color scheme https://cran.r-project.org/web/packages/khroma/vignettes/tol.html e.g. "tol_light"
135
+ // pale color scheme
136
+ // https://cran.r-project.org/web/packages/khroma/vignettes/tol.html
137
+ // e.g. "tol_light"
132
138
  const colorPalette = [
133
139
  '#BBCCEE',
134
140
  'pink',
@@ -150,6 +156,9 @@ const stateModelFactory = (configSchema: LinearPileupDisplayConfigModel) =>
150
156
  }
151
157
  })
152
158
  },
159
+ setFeatureUnderMouse(feat?: Feature) {
160
+ self.featureUnderMouseVolatile = feat
161
+ },
153
162
  }))
154
163
  .actions(self => ({
155
164
  afterAttach() {
@@ -226,6 +235,48 @@ const stateModelFactory = (configSchema: LinearPileupDisplayConfigModel) =>
226
235
  { delay: 1000 },
227
236
  ),
228
237
  )
238
+
239
+ // autorun synchronizes featureUnderMouse with featureIdUnderMouse
240
+ addDisposer(
241
+ self,
242
+ autorun(async () => {
243
+ const session = getSession(self)
244
+ try {
245
+ const featureId = self.featureIdUnderMouse
246
+ if (self.featureUnderMouse?.id() !== featureId) {
247
+ if (!featureId) {
248
+ self.setFeatureUnderMouse(undefined)
249
+ } else {
250
+ const sessionId = getRpcSessionId(self)
251
+ const view = getContainingView(self)
252
+ const { feature } = (await session.rpcManager.call(
253
+ sessionId,
254
+ 'CoreGetFeatureDetails',
255
+ {
256
+ featureId,
257
+ sessionId,
258
+ layoutId: view.id,
259
+ rendererType: 'PileupRenderer',
260
+ },
261
+ )) as { feature: unknown }
262
+
263
+ // check featureIdUnderMouse is still the same as the
264
+ // feature.id that was returned e.g. that the user hasn't
265
+ // moused over to a new position during the async operation
266
+ // above
267
+ // @ts-ignore
268
+ if (self.featureIdUnderMouse === feature.uniqueId) {
269
+ // @ts-ignore
270
+ self.setFeatureUnderMouse(new SimpleFeature(feature))
271
+ }
272
+ }
273
+ }
274
+ } catch (e) {
275
+ console.error(e)
276
+ session.notify(`${e}`, 'error')
277
+ }
278
+ }),
279
+ )
229
280
  },
230
281
  selectFeature(feature: Feature) {
231
282
  const session = getSession(self)
@@ -337,6 +388,9 @@ const stateModelFactory = (configSchema: LinearPileupDisplayConfigModel) =>
337
388
  ? self.mismatchAlpha
338
389
  : readConfObject(this.rendererConfig, 'mismatchAlpha')
339
390
  },
391
+ get featureUnderMouse() {
392
+ return self.featureUnderMouseVolatile
393
+ },
340
394
  }))
341
395
  .views(self => {
342
396
  const {
@@ -395,6 +449,8 @@ const stateModelFactory = (configSchema: LinearPileupDisplayConfigModel) =>
395
449
  colorBy,
396
450
  filterBy,
397
451
  rpcDriverName,
452
+ currBpPerPx,
453
+ ready,
398
454
  } = self
399
455
 
400
456
  const superProps = superRenderProps()
@@ -403,8 +459,8 @@ const stateModelFactory = (configSchema: LinearPileupDisplayConfigModel) =>
403
459
  ...superProps,
404
460
  notReady:
405
461
  superProps.notReady ||
406
- !self.ready ||
407
- (sortedBy && self.currBpPerPx !== view.bpPerPx),
462
+ !ready ||
463
+ (sortedBy && currBpPerPx !== view.bpPerPx),
408
464
  rpcDriverName,
409
465
  displayModel: self,
410
466
  sortedBy,
@@ -414,7 +470,7 @@ const stateModelFactory = (configSchema: LinearPileupDisplayConfigModel) =>
414
470
  modificationTagMap: JSON.parse(JSON.stringify(modificationTagMap)),
415
471
  showSoftClip: self.showSoftClipping,
416
472
  config: self.rendererConfig,
417
- async onFeatureClick(_: unknown, featureId: string | undefined) {
473
+ async onFeatureClick(_: unknown, featureId?: string) {
418
474
  const session = getSession(self)
419
475
  const { rpcManager } = session
420
476
  try {
@@ -444,14 +500,12 @@ const stateModelFactory = (configSchema: LinearPileupDisplayConfigModel) =>
444
500
  session.notify(`${e}`)
445
501
  }
446
502
  },
503
+
447
504
  onClick() {
448
505
  self.clearFeatureSelection()
449
506
  },
450
507
  // similar to click but opens a menu with further options
451
- async onFeatureContextMenu(
452
- _: unknown,
453
- featureId: string | undefined,
454
- ) {
508
+ async onFeatureContextMenu(_: unknown, featureId?: string) {
455
509
  const session = getSession(self)
456
510
  const { rpcManager } = session
457
511
  try {
@@ -507,19 +561,17 @@ const stateModelFactory = (configSchema: LinearPileupDisplayConfigModel) =>
507
561
  disabled: self.showSoftClipping,
508
562
  subMenu: [
509
563
  ...['Start location', 'Read strand', 'Base pair'].map(
510
- option => {
511
- return {
512
- label: option,
513
- onClick: () => self.setSortedBy(option),
514
- }
515
- },
564
+ option => ({
565
+ label: option,
566
+ onClick: () => self.setSortedBy(option),
567
+ }),
516
568
  ),
517
569
  {
518
570
  label: 'Sort by tag...',
519
571
  onClick: () => {
520
- getSession(self).queueDialog(doneCallback => [
572
+ getSession(self).queueDialog(handleClose => [
521
573
  SortByTagDlg,
522
- { model: self, handleClose: doneCallback },
574
+ { model: self, handleClose },
523
575
  ])
524
576
  },
525
577
  },
@@ -7,37 +7,51 @@ import { Tooltip } from '@jbrowse/plugin-wiggle'
7
7
  type Count = {
8
8
  [key: string]: {
9
9
  total: number
10
+ '-1': number
11
+ '0': number
12
+ '1': number
10
13
  }
11
14
  }
12
15
 
13
16
  type SNPInfo = {
14
- ref: Count
15
17
  cov: Count
16
18
  lowqual: Count
17
19
  noncov: Count
18
20
  delskips: Count
21
+ refbase: string
19
22
  total: number
23
+ ref: number
24
+ all: number
20
25
  '-1': number
21
26
  '0': number
22
27
  '1': number
23
28
  }
24
29
 
25
30
  const en = (n: number) => n.toLocaleString('en-US')
31
+ const toP = (s = 0) => +(+s).toFixed(1)
32
+ const pct = (n: number, total: number) => `${toP((n / (total || 1)) * 100)}%`
26
33
 
27
34
  const TooltipContents = React.forwardRef(
28
- ({ feature }: { feature: Feature }, ref: any) => {
35
+ ({ feature }: { feature: Feature }, reactRef: any) => {
29
36
  const start = feature.get('start')
30
37
  const end = feature.get('end')
31
38
  const name = feature.get('refName')
32
- const info = feature.get('snpinfo') as SNPInfo
39
+ const {
40
+ refbase,
41
+ all,
42
+ total,
43
+ ref,
44
+ '-1': rn1,
45
+ '1': r1,
46
+ '0': r0,
47
+ ...info
48
+ } = feature.get('snpinfo') as SNPInfo
33
49
  const loc = [name, start === end ? en(start) : `${en(start)}..${en(end)}`]
34
50
  .filter(f => !!f)
35
51
  .join(':')
36
52
 
37
- const total = info?.total
38
-
39
53
  return (
40
- <div ref={ref}>
54
+ <div ref={reactRef}>
41
55
  <table>
42
56
  <caption>{loc}</caption>
43
57
  <thead>
@@ -52,37 +66,37 @@ const TooltipContents = React.forwardRef(
52
66
  <tbody>
53
67
  <tr>
54
68
  <td>Total</td>
55
- <td>{info.total}</td>
69
+ <td>{all}</td>
56
70
  </tr>
57
71
  <tr>
58
- <td>REF</td>
59
- <td>{info.ref}</td>
72
+ <td>REF {refbase ? `(${refbase.toUpperCase()})` : ''}</td>
73
+ <td>{ref}</td>
74
+ <td>{pct(ref, all)}</td>
60
75
  <td>
61
- {info['-1'] ? `${info['-1']}(-)` : ''}
62
- {info['1'] ? `${info['1']}(+)` : ''}
76
+ {rn1 ? `${rn1}(-)` : ''}
77
+ {r1 ? `${r1}(+)` : ''}
63
78
  </td>
64
79
  <td />
65
80
  </tr>
66
81
 
67
- {Object.entries(info).map(([key, entry]) =>
68
- Object.entries(entry).map(([base, score]) => (
69
- <tr key={base}>
70
- <td>{base.toUpperCase()}</td>
71
- <td>{score.total}</td>
72
- <td>
73
- {base === 'total' || base === 'skip'
74
- ? '---'
75
- : `${Math.floor(
76
- (score.total / (total || score.total || 1)) * 100,
77
- )}%`}
78
- </td>
79
- <td>
80
- {score['-1'] ? `${score['-1']}(-)` : ''}
81
- {score['1'] ? `${score['1']}(+)` : ''}
82
- </td>
83
- <td>{key}</td>
84
- </tr>
85
- )),
82
+ {Object.entries(info as unknown as Record<string, Count>).map(
83
+ ([key, entry]) =>
84
+ Object.entries(entry).map(([base, score]) => (
85
+ <tr key={base}>
86
+ <td>{base.toUpperCase()}</td>
87
+ <td>{score.total}</td>
88
+ <td>
89
+ {base === 'total' || base === 'skip'
90
+ ? '---'
91
+ : pct(score.total, all)}
92
+ </td>
93
+ <td>
94
+ {score['-1'] ? `${score['-1']}(-)` : ''}
95
+ {score['1'] ? `${score['1']}(+)` : ''}
96
+ </td>
97
+ <td>{key}</td>
98
+ </tr>
99
+ )),
86
100
  )}
87
101
  </tbody>
88
102
  </table>