@jbrowse/plugin-lollipop 2.6.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 (88) hide show
  1. package/LICENSE +201 -0
  2. package/dist/LinearLollipopDisplay/configSchema.d.ts +26 -0
  3. package/dist/LinearLollipopDisplay/configSchema.js +13 -0
  4. package/dist/LinearLollipopDisplay/configSchema.js.map +1 -0
  5. package/dist/LinearLollipopDisplay/index.d.ts +2 -0
  6. package/dist/LinearLollipopDisplay/index.js +8 -0
  7. package/dist/LinearLollipopDisplay/index.js.map +1 -0
  8. package/dist/LinearLollipopDisplay/model.d.ts +303 -0
  9. package/dist/LinearLollipopDisplay/model.js +36 -0
  10. package/dist/LinearLollipopDisplay/model.js.map +1 -0
  11. package/dist/LollipopRenderer/Layout.d.ts +55 -0
  12. package/dist/LollipopRenderer/Layout.js +116 -0
  13. package/dist/LollipopRenderer/Layout.js.map +1 -0
  14. package/dist/LollipopRenderer/LollipopRenderer.d.ts +13 -0
  15. package/dist/LollipopRenderer/LollipopRenderer.js +52 -0
  16. package/dist/LollipopRenderer/LollipopRenderer.js.map +1 -0
  17. package/dist/LollipopRenderer/components/Lollipop.d.ts +3 -0
  18. package/dist/LollipopRenderer/components/Lollipop.js +63 -0
  19. package/dist/LollipopRenderer/components/Lollipop.js.map +1 -0
  20. package/dist/LollipopRenderer/components/LollipopRendering.d.ts +3 -0
  21. package/dist/LollipopRenderer/components/LollipopRendering.js +80 -0
  22. package/dist/LollipopRenderer/components/LollipopRendering.js.map +1 -0
  23. package/dist/LollipopRenderer/components/ScoreText.d.ts +15 -0
  24. package/dist/LollipopRenderer/components/ScoreText.js +20 -0
  25. package/dist/LollipopRenderer/components/ScoreText.js.map +1 -0
  26. package/dist/LollipopRenderer/components/Stick.d.ts +15 -0
  27. package/dist/LollipopRenderer/components/Stick.js +12 -0
  28. package/dist/LollipopRenderer/components/Stick.js.map +1 -0
  29. package/dist/LollipopRenderer/configSchema.d.ts +56 -0
  30. package/dist/LollipopRenderer/configSchema.js +59 -0
  31. package/dist/LollipopRenderer/configSchema.js.map +1 -0
  32. package/dist/LollipopRenderer/index.d.ts +3 -0
  33. package/dist/LollipopRenderer/index.js +13 -0
  34. package/dist/LollipopRenderer/index.js.map +1 -0
  35. package/dist/index.d.ts +6 -0
  36. package/dist/index.js +60 -0
  37. package/dist/index.js.map +1 -0
  38. package/esm/LinearLollipopDisplay/configSchema.d.ts +26 -0
  39. package/esm/LinearLollipopDisplay/configSchema.js +9 -0
  40. package/esm/LinearLollipopDisplay/configSchema.js.map +1 -0
  41. package/esm/LinearLollipopDisplay/index.d.ts +2 -0
  42. package/esm/LinearLollipopDisplay/index.js +3 -0
  43. package/esm/LinearLollipopDisplay/index.js.map +1 -0
  44. package/esm/LinearLollipopDisplay/model.d.ts +303 -0
  45. package/esm/LinearLollipopDisplay/model.js +32 -0
  46. package/esm/LinearLollipopDisplay/model.js.map +1 -0
  47. package/esm/LollipopRenderer/Layout.d.ts +55 -0
  48. package/esm/LollipopRenderer/Layout.js +111 -0
  49. package/esm/LollipopRenderer/Layout.js.map +1 -0
  50. package/esm/LollipopRenderer/LollipopRenderer.d.ts +13 -0
  51. package/esm/LollipopRenderer/LollipopRenderer.js +23 -0
  52. package/esm/LollipopRenderer/LollipopRenderer.js.map +1 -0
  53. package/esm/LollipopRenderer/components/Lollipop.d.ts +3 -0
  54. package/esm/LollipopRenderer/components/Lollipop.js +58 -0
  55. package/esm/LollipopRenderer/components/Lollipop.js.map +1 -0
  56. package/esm/LollipopRenderer/components/LollipopRendering.d.ts +3 -0
  57. package/esm/LollipopRenderer/components/LollipopRendering.js +75 -0
  58. package/esm/LollipopRenderer/components/LollipopRendering.js.map +1 -0
  59. package/esm/LollipopRenderer/components/ScoreText.d.ts +15 -0
  60. package/esm/LollipopRenderer/components/ScoreText.js +14 -0
  61. package/esm/LollipopRenderer/components/ScoreText.js.map +1 -0
  62. package/esm/LollipopRenderer/components/Stick.d.ts +15 -0
  63. package/esm/LollipopRenderer/components/Stick.js +7 -0
  64. package/esm/LollipopRenderer/components/Stick.js.map +1 -0
  65. package/esm/LollipopRenderer/configSchema.d.ts +56 -0
  66. package/esm/LollipopRenderer/configSchema.js +57 -0
  67. package/esm/LollipopRenderer/configSchema.js.map +1 -0
  68. package/esm/LollipopRenderer/index.d.ts +3 -0
  69. package/esm/LollipopRenderer/index.js +4 -0
  70. package/esm/LollipopRenderer/index.js.map +1 -0
  71. package/esm/index.d.ts +6 -0
  72. package/esm/index.js +31 -0
  73. package/esm/index.js.map +1 -0
  74. package/package.json +56 -0
  75. package/src/LinearLollipopDisplay/configSchema.ts +14 -0
  76. package/src/LinearLollipopDisplay/index.ts +2 -0
  77. package/src/LinearLollipopDisplay/model.ts +40 -0
  78. package/src/LollipopRenderer/Layout.ts +172 -0
  79. package/src/LollipopRenderer/LollipopRenderer.js +29 -0
  80. package/src/LollipopRenderer/components/Lollipop.tsx +113 -0
  81. package/src/LollipopRenderer/components/LollipopRendering.test.js +45 -0
  82. package/src/LollipopRenderer/components/LollipopRendering.tsx +140 -0
  83. package/src/LollipopRenderer/components/ScoreText.tsx +43 -0
  84. package/src/LollipopRenderer/components/Stick.tsx +36 -0
  85. package/src/LollipopRenderer/components/__snapshots__/LollipopRendering.test.js.snap +37 -0
  86. package/src/LollipopRenderer/configSchema.ts +63 -0
  87. package/src/LollipopRenderer/index.ts +3 -0
  88. package/src/index.ts +41 -0
@@ -0,0 +1,172 @@
1
+ import { readConfObject } from '@jbrowse/core/configuration'
2
+ import { doesIntersect2 } from '@jbrowse/core/util/range'
3
+ import { AnyConfigurationModel } from '@jbrowse/core/configuration'
4
+
5
+ interface LayoutItem {
6
+ uniqueId: string
7
+ anchorLocation: number
8
+ width: number
9
+ height: number
10
+ data: { score: number }
11
+ }
12
+
13
+ type LayoutEntry = LayoutItem & { x: number; y: number }
14
+
15
+ type LayoutMap = Map<string, LayoutEntry>
16
+
17
+ export class FloatingLayout {
18
+ width: number
19
+
20
+ totalHeight = 0
21
+
22
+ constructor({ width }: { width: number }) {
23
+ if (!width) {
24
+ throw new Error('width required to make a new FloatingLayout')
25
+ }
26
+ this.width = width
27
+ }
28
+
29
+ items: LayoutItem[] = []
30
+
31
+ layout: LayoutMap = new Map()
32
+
33
+ layoutDirty = false
34
+
35
+ add(
36
+ uniqueId: string,
37
+ anchorLocation: number,
38
+ width: number,
39
+ height: number,
40
+ data: { score: number },
41
+ ) {
42
+ this.items.push({ uniqueId, anchorLocation, width, height, data })
43
+ this.layoutDirty = true
44
+ }
45
+
46
+ /**
47
+ * @returns Map of `uniqueId => {x,y,anchorLocation,width,height,data}`
48
+ */
49
+ getLayout(configuration?: AnyConfigurationModel) {
50
+ if (!this.layoutDirty) {
51
+ return this.layout
52
+ }
53
+ if (!configuration) {
54
+ throw new Error('configuration object required')
55
+ }
56
+
57
+ const minY = readConfObject(configuration, 'minStickLength')
58
+
59
+ // sort them by score ascending, so higher scores will always end up
60
+ // stacked last (toward the bottom)
61
+ const sorted = this.items.sort((a, b) => a.data.score - b.data.score)
62
+
63
+ // bump them
64
+ let maxBottom = 0
65
+ const layoutEntries: [string, LayoutEntry][] = new Array(sorted.length)
66
+ for (let i = 0; i < sorted.length; i += 1) {
67
+ const currentItem = sorted[i]
68
+ const { anchorLocation, width, height } = currentItem
69
+ const start = anchorLocation - width / 2
70
+ const end = start + width
71
+ let top = minY
72
+ let bottom = top + height
73
+
74
+ // figure out how far down to put it
75
+ for (let j = 0; j < i; j += 1) {
76
+ const [, previouslyLaidOutItem] = layoutEntries[j]
77
+ const {
78
+ x: prevStart,
79
+ y: prevTop,
80
+ width: prevWidth,
81
+ height: prevHeight,
82
+ } = previouslyLaidOutItem
83
+ const prevEnd = prevStart + prevWidth
84
+ const prevBottom = prevTop + prevHeight
85
+ if (
86
+ doesIntersect2(prevStart, prevEnd, start, end) &&
87
+ doesIntersect2(prevTop, prevBottom, top, bottom)
88
+ ) {
89
+ // bump this one to the bottom of the previous one
90
+ top = prevBottom
91
+ bottom = top + height
92
+ j = -1 // we need to check all of them again after bumping
93
+ }
94
+ }
95
+
96
+ // record the entry and update the maxBottom
97
+ layoutEntries[i] = [
98
+ currentItem.uniqueId,
99
+ { ...currentItem, x: start, y: top },
100
+ ]
101
+ if (bottom > maxBottom) {
102
+ maxBottom = bottom
103
+ }
104
+ }
105
+
106
+ // try to tile them left to right all at the same level
107
+ // if they don't fit, try to alternate them on 2 levels, then 3
108
+ this.totalHeight = maxBottom
109
+ this.layout = new Map(layoutEntries)
110
+ this.layoutDirty = false
111
+ return this.layout
112
+ }
113
+
114
+ getTotalHeight() {
115
+ if (this.layoutDirty) {
116
+ throw new Error('getTotalHeight does not work when the layout is dirty.')
117
+ }
118
+ return this.totalHeight
119
+ }
120
+
121
+ serializeRegion() {
122
+ return this.toJSON()
123
+ }
124
+
125
+ toJSON() {
126
+ if (this.layoutDirty) {
127
+ throw new Error('toJSON does not work when the layout is dirty.')
128
+ }
129
+ return { pairs: [...this.getLayout()], totalHeight: this.getTotalHeight() }
130
+ }
131
+
132
+ static fromJSON() {
133
+ throw new Error('not supported')
134
+ }
135
+ }
136
+
137
+ export class PrecomputedFloatingLayout {
138
+ layout: LayoutMap
139
+
140
+ totalHeight: number
141
+
142
+ constructor({
143
+ pairs,
144
+ totalHeight,
145
+ }: {
146
+ pairs: [string, LayoutEntry][]
147
+ totalHeight: number
148
+ }) {
149
+ this.layout = new Map(pairs)
150
+ this.totalHeight = totalHeight
151
+ }
152
+
153
+ add(uniqueId: string) {
154
+ if (!this.layout.has(uniqueId)) {
155
+ throw new Error(`layout error, precomputed layout is missing ${uniqueId}`)
156
+ }
157
+ }
158
+
159
+ getLayout() {
160
+ return this.layout
161
+ }
162
+
163
+ getTotalHeight() {
164
+ return this.totalHeight
165
+ }
166
+
167
+ static fromJSON(
168
+ json: ConstructorParameters<typeof PrecomputedFloatingLayout>[0],
169
+ ) {
170
+ return new PrecomputedFloatingLayout(json)
171
+ }
172
+ }
@@ -0,0 +1,29 @@
1
+ import BoxRendererType, {
2
+ LayoutSession,
3
+ } from '@jbrowse/core/pluggableElementTypes/renderers/BoxRendererType'
4
+ import MultiLayout from '@jbrowse/core/util/layouts/MultiLayout'
5
+ import { FloatingLayout, PrecomputedFloatingLayout } from './Layout'
6
+
7
+ class FloatingLayoutSession extends LayoutSession {
8
+ makeLayout() {
9
+ 'sequenceAdapter'
10
+
11
+ const { end, start } = this.regions[0]
12
+ const widthPx = (end - start) / this.bpPerPx
13
+ return new MultiLayout(FloatingLayout, { width: widthPx })
14
+ }
15
+
16
+ layoutIsValid(/* layout */) {
17
+ return false // layout.left layout.width === this.width
18
+ }
19
+ }
20
+
21
+ export default class extends BoxRendererType {
22
+ createSession(args) {
23
+ return new FloatingLayoutSession(args)
24
+ }
25
+
26
+ deserializeLayoutInClient(json) {
27
+ return new PrecomputedFloatingLayout(json)
28
+ }
29
+ }
@@ -0,0 +1,113 @@
1
+ import React from 'react'
2
+ import { readConfObject } from '@jbrowse/core/configuration'
3
+ import { observer } from 'mobx-react'
4
+ import ScoreText from './ScoreText'
5
+
6
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
+ export default observer(function Lollipop(props: Record<string, any>) {
8
+ const { feature, config, layoutRecord, selectedFeatureId } = props
9
+ const {
10
+ anchorLocation,
11
+ y,
12
+ data: { radiusPx },
13
+ } = layoutRecord
14
+
15
+ const onFeatureMouseDown = (event: React.MouseEvent) => {
16
+ const { onFeatureMouseDown: handler, feature } = props
17
+ return handler?.(event, feature.id())
18
+ }
19
+
20
+ const onFeatureMouseEnter = (event: React.MouseEvent) => {
21
+ const { onFeatureMouseEnter: handler, feature } = props
22
+ return handler?.(event, feature.id())
23
+ }
24
+
25
+ const onFeatureMouseOut = (event: React.MouseEvent | React.FocusEvent) => {
26
+ const { onFeatureMouseOut: handler, feature } = props
27
+ return handler?.(event, feature.id())
28
+ }
29
+
30
+ const onFeatureMouseOver = (event: React.MouseEvent | React.FocusEvent) => {
31
+ const { onFeatureMouseOver: handler, feature } = props
32
+ return handler?.(event, feature.id())
33
+ }
34
+
35
+ const onFeatureMouseUp = (event: React.MouseEvent) => {
36
+ const { onFeatureMouseUp: handler, feature } = props
37
+ return handler?.(event, feature.id())
38
+ }
39
+
40
+ const onFeatureMouseLeave = (event: React.MouseEvent) => {
41
+ const { onFeatureMouseLeave: handler, feature } = props
42
+ return handler?.(event, feature.id())
43
+ }
44
+
45
+ const onFeatureMouseMove = (event: React.MouseEvent) => {
46
+ const { onFeatureMouseMove: handler, feature } = props
47
+ return handler?.(event, feature.id())
48
+ }
49
+
50
+ const onFeatureClick = (event: React.MouseEvent) => {
51
+ const { onFeatureClick: handler, feature } = props
52
+ event.stopPropagation()
53
+ return handler?.(event, feature.id())
54
+ }
55
+
56
+ const styleOuter = {
57
+ fill: readConfObject(config, 'strokeColor', { feature }),
58
+ }
59
+ if (String(selectedFeatureId) === String(feature.id())) {
60
+ styleOuter.fill = 'red'
61
+ }
62
+
63
+ const styleInner = {
64
+ fill: readConfObject(config, 'innerColor', { feature }),
65
+ }
66
+
67
+ const strokeWidth = readConfObject(config, 'strokeWidth', { feature })
68
+
69
+ return (
70
+ <g data-testid={feature.id()}>
71
+ <title>{readConfObject(config, 'caption', { feature })}</title>
72
+ <circle
73
+ cx={anchorLocation}
74
+ cy={y + radiusPx}
75
+ r={radiusPx}
76
+ style={styleOuter}
77
+ onMouseDown={onFeatureMouseDown}
78
+ onMouseEnter={onFeatureMouseEnter}
79
+ onMouseOut={onFeatureMouseOut}
80
+ onMouseOver={onFeatureMouseOver}
81
+ onMouseUp={onFeatureMouseUp}
82
+ onMouseLeave={onFeatureMouseLeave}
83
+ onMouseMove={onFeatureMouseMove}
84
+ onClick={onFeatureClick}
85
+ onFocus={onFeatureMouseOver}
86
+ onBlur={onFeatureMouseOut}
87
+ />
88
+ {radiusPx - strokeWidth <= 2 ? null : (
89
+ <circle
90
+ cx={anchorLocation}
91
+ cy={y + radiusPx}
92
+ r={radiusPx - strokeWidth}
93
+ style={styleInner}
94
+ onMouseDown={onFeatureMouseDown}
95
+ onMouseEnter={onFeatureMouseEnter}
96
+ onMouseOut={onFeatureMouseOut}
97
+ onMouseOver={onFeatureMouseOver}
98
+ onMouseUp={onFeatureMouseUp}
99
+ onMouseLeave={onFeatureMouseLeave}
100
+ onMouseMove={onFeatureMouseMove}
101
+ onClick={onFeatureClick}
102
+ onFocus={onFeatureMouseOver}
103
+ onBlur={onFeatureMouseOut}
104
+ />
105
+ )}
106
+ <ScoreText
107
+ feature={feature}
108
+ config={config}
109
+ layoutRecord={layoutRecord}
110
+ />
111
+ </g>
112
+ )
113
+ })
@@ -0,0 +1,45 @@
1
+ import SimpleFeature from '@jbrowse/core/util/simpleFeature'
2
+ import React from 'react'
3
+ import { render } from '@testing-library/react'
4
+ import ConfigSchema from '../configSchema'
5
+ import { FloatingLayout, PrecomputedFloatingLayout } from '../Layout'
6
+ import Rendering from './LollipopRendering'
7
+
8
+ // these tests do very little, let's try to expand them at some point
9
+ test('no features', () => {
10
+ const { container } = render(
11
+ <Rendering
12
+ width={500}
13
+ height={500}
14
+ regions={[{ refName: 'zonk', start: 0, end: 300 }]}
15
+ layout={new PrecomputedFloatingLayout({ pairs: [], totalHeight: 20 })}
16
+ config={{}}
17
+ bpPerPx={3}
18
+ />,
19
+ )
20
+
21
+ expect(container.firstChild).toMatchSnapshot()
22
+ })
23
+
24
+ test('one feature', () => {
25
+ const { container } = render(
26
+ <Rendering
27
+ width={500}
28
+ height={500}
29
+ regions={[{ refName: 'zonk', start: 0, end: 1000 }]}
30
+ layout={new FloatingLayout({ width: 100 })}
31
+ features={
32
+ new Map([
33
+ [
34
+ 'one',
35
+ new SimpleFeature({ uniqueId: 'one', score: 10, start: 1, end: 3 }),
36
+ ],
37
+ ])
38
+ }
39
+ config={ConfigSchema.create({})}
40
+ bpPerPx={3}
41
+ />,
42
+ )
43
+
44
+ expect(container.firstChild).toMatchSnapshot()
45
+ })
@@ -0,0 +1,140 @@
1
+ import React from 'react'
2
+ import {
3
+ AnyConfigurationModel,
4
+ readConfObject,
5
+ } from '@jbrowse/core/configuration'
6
+ import { Feature, Region, bpToPx } from '@jbrowse/core/util'
7
+ import { observer } from 'mobx-react'
8
+
9
+ // locals
10
+ import Lollipop from './Lollipop'
11
+ import Stick from './Stick'
12
+
13
+ function layoutFeat(args: {
14
+ feature: Feature
15
+ bpPerPx: number
16
+ region: Region
17
+ layout: { add: (...args: unknown[]) => void }
18
+ config: AnyConfigurationModel
19
+ }) {
20
+ const { feature, bpPerPx, config, region, layout } = args
21
+
22
+ const centerBp = Math.abs(feature.get('end') + feature.get('start')) / 2
23
+ const centerPx = bpToPx(centerBp, region, bpPerPx)
24
+ const radiusPx = readConfObject(config, 'radius', { feature })
25
+
26
+ if (!radiusPx) {
27
+ console.error(
28
+ new Error(
29
+ `lollipop radius ${radiusPx} configured for feature ${feature.id()}`,
30
+ ),
31
+ )
32
+ }
33
+ layout.add(feature.id(), centerPx, radiusPx * 2, radiusPx * 2, {
34
+ featureId: feature.id(),
35
+ anchorX: centerPx,
36
+ radiusPx,
37
+ score: readConfObject(args.config, 'score', { feature }),
38
+ })
39
+ }
40
+
41
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
42
+ export default observer(function LollipopRendering(props: Record<string, any>) {
43
+ const onMouseDown = (event: React.MouseEvent) => {
44
+ const { onMouseDown: handler } = props
45
+ return handler?.(event)
46
+ }
47
+
48
+ const onMouseUp = (event: React.MouseEvent) => {
49
+ const { onMouseUp: handler } = props
50
+ return handler?.(event)
51
+ }
52
+
53
+ const onMouseEnter = (event: React.MouseEvent | React.FocusEvent) => {
54
+ const { onMouseEnter: handler } = props
55
+ return handler?.(event)
56
+ }
57
+
58
+ const onMouseLeave = (event: React.MouseEvent | React.FocusEvent) => {
59
+ const { onMouseLeave: handler } = props
60
+ return handler?.(event)
61
+ }
62
+
63
+ const onMouseOver = (event: React.MouseEvent) => {
64
+ const { onMouseOver: handler } = props
65
+ return handler?.(event)
66
+ }
67
+
68
+ const onMouseOut = (event: React.MouseEvent) => {
69
+ const { onMouseOut: handler } = props
70
+ return handler?.(event)
71
+ }
72
+
73
+ const onClick = (event: React.MouseEvent) => {
74
+ const { onClick: handler } = props
75
+ return handler?.(event)
76
+ }
77
+
78
+ const {
79
+ regions,
80
+ bpPerPx,
81
+ layout,
82
+ config,
83
+ features = new Map(),
84
+ displayModel = {},
85
+ } = props
86
+ const { selectedFeatureId } = displayModel
87
+ const [region] = regions
88
+ for (const feature of features.values()) {
89
+ layoutFeat({
90
+ feature,
91
+ bpPerPx,
92
+ region,
93
+ config,
94
+ layout,
95
+ })
96
+ }
97
+
98
+ const width = (region.end - region.start) / bpPerPx
99
+ const records = [...layout.getLayout(config).values()]
100
+ const height = layout.getTotalHeight()
101
+
102
+ return (
103
+ <svg
104
+ width={width}
105
+ height={height}
106
+ style={{ position: 'relative' }}
107
+ onMouseDown={onMouseDown}
108
+ onMouseUp={onMouseUp}
109
+ onMouseEnter={onMouseEnter}
110
+ onMouseLeave={onMouseLeave}
111
+ onMouseOver={onMouseOver}
112
+ onMouseOut={onMouseOut}
113
+ onFocus={onMouseEnter}
114
+ onBlur={onMouseLeave}
115
+ onClick={onClick}
116
+ >
117
+ {records.map(layoutRecord => {
118
+ const feature = features.get(layoutRecord.data.featureId)
119
+ return (
120
+ <React.Fragment key={feature.id()}>
121
+ <Stick
122
+ {...props}
123
+ config={config}
124
+ layoutRecord={layoutRecord}
125
+ feature={feature}
126
+ key={`stick-${feature.id()}`}
127
+ />
128
+ <Lollipop
129
+ {...props}
130
+ layoutRecord={layoutRecord}
131
+ feature={feature}
132
+ key={`body-${feature.id()}`}
133
+ selectedFeatureId={selectedFeatureId}
134
+ />
135
+ </React.Fragment>
136
+ )
137
+ })}
138
+ </svg>
139
+ )
140
+ })
@@ -0,0 +1,43 @@
1
+ import React from 'react'
2
+ import {
3
+ AnyConfigurationModel,
4
+ readConfObject,
5
+ } from '@jbrowse/core/configuration'
6
+ import { contrastingTextColor } from '@jbrowse/core/util/color'
7
+ import { Feature } from '@jbrowse/core/util'
8
+
9
+ export default function ScoreText({
10
+ feature,
11
+ config,
12
+ layoutRecord: {
13
+ y,
14
+ data: { anchorX, radiusPx, score },
15
+ },
16
+ }: {
17
+ feature: Feature
18
+ config: AnyConfigurationModel
19
+ layoutRecord: {
20
+ y: number
21
+ data: { anchorX: number; radiusPx: number; score: number }
22
+ }
23
+ }) {
24
+ const innerColor = readConfObject(config, 'innerColor', { feature })
25
+
26
+ const scoreString = String(score)
27
+ const fontWidth = (radiusPx * 2) / scoreString.length
28
+ const fontHeight = fontWidth * 1.1
29
+ if (fontHeight < 12) {
30
+ return null
31
+ }
32
+ return (
33
+ <text
34
+ style={{ fontSize: fontHeight, fill: contrastingTextColor(innerColor) }}
35
+ x={anchorX}
36
+ y={y + radiusPx - fontHeight / 2.4}
37
+ textAnchor="middle"
38
+ dominantBaseline="hanging"
39
+ >
40
+ {scoreString}
41
+ </text>
42
+ )
43
+ }
@@ -0,0 +1,36 @@
1
+ import React from 'react'
2
+ import {
3
+ AnyConfigurationModel,
4
+ readConfObject,
5
+ } from '@jbrowse/core/configuration'
6
+ import { observer } from 'mobx-react'
7
+ import { Feature } from '@jbrowse/core/util'
8
+
9
+ export default observer(function Stick({
10
+ feature,
11
+ config,
12
+ layoutRecord: {
13
+ anchorLocation,
14
+ y,
15
+ data: { radiusPx },
16
+ },
17
+ }: {
18
+ feature: Feature
19
+ config: AnyConfigurationModel
20
+ layoutRecord: {
21
+ anchorLocation: number
22
+ y: number
23
+ data: { radiusPx: number }
24
+ }
25
+ }) {
26
+ return (
27
+ <line
28
+ x1={anchorLocation}
29
+ y1={0}
30
+ x2={anchorLocation}
31
+ y2={y + 2 * radiusPx}
32
+ stroke={readConfObject(config, 'stickColor', { feature })}
33
+ strokeWidth={readConfObject(config, 'stickWidth', { feature })}
34
+ />
35
+ )
36
+ })
@@ -0,0 +1,37 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`no features 1`] = `
4
+ <svg
5
+ height="20"
6
+ style="position: relative;"
7
+ width="100"
8
+ />
9
+ `;
10
+
11
+ exports[`one feature 1`] = `
12
+ <svg
13
+ height="16.286652959662007"
14
+ style="position: relative;"
15
+ width="333.3333333333333"
16
+ >
17
+ <line
18
+ stroke="black"
19
+ stroke-width="2"
20
+ x1="0.7"
21
+ x2="0.7"
22
+ y1="0"
23
+ y2="16.286652959662007"
24
+ />
25
+ <g
26
+ data-testid="one"
27
+ >
28
+ <title />
29
+ <circle
30
+ cx="0.7"
31
+ cy="10.643326479831003"
32
+ r="5.643326479831003"
33
+ style="fill: green;"
34
+ />
35
+ </g>
36
+ </svg>
37
+ `;
@@ -0,0 +1,63 @@
1
+ import { ConfigurationSchema } from '@jbrowse/core/configuration'
2
+
3
+ export default ConfigurationSchema(
4
+ 'LollipopRenderer',
5
+ {
6
+ strokeColor: {
7
+ type: 'color',
8
+ description: 'the outer color of each lollipop',
9
+ defaultValue: 'green',
10
+ contextVariable: ['feature'],
11
+ },
12
+ innerColor: {
13
+ type: 'color',
14
+ description: 'the inner color of each lollipop',
15
+ defaultValue: '#7fc75f',
16
+ contextVariable: ['feature'],
17
+ },
18
+ strokeWidth: {
19
+ type: 'number',
20
+ description: 'width of the stroked border',
21
+ defaultValue: 4,
22
+ contextVariable: ['feature'],
23
+ },
24
+ radius: {
25
+ type: 'number',
26
+ description: 'radius in pixels of each lollipop body',
27
+ defaultValue: `jexl:sqrt(max(3, (get(feature,'score')*10)/3.14))`,
28
+ contextVariable: ['feature'],
29
+ },
30
+ caption: {
31
+ type: 'string',
32
+ description:
33
+ 'the tooltip caption displayed when the mouse hovers over a lollipop',
34
+ defaultValue: `jexl:get(feature,'name')`,
35
+ contextVariable: ['feature'],
36
+ },
37
+ minStickLength: {
38
+ type: 'number',
39
+ description: 'minimum lollipop "stick" length, in pixels',
40
+ defaultValue: 5,
41
+ },
42
+ stickColor: {
43
+ type: 'color',
44
+ description: 'color of the lollipop stick',
45
+ defaultValue: 'black',
46
+ contextVariable: ['feature'],
47
+ },
48
+ stickWidth: {
49
+ type: 'number',
50
+ description: 'width in pixels of the lollipop stick',
51
+ defaultValue: 2,
52
+ contextVariable: ['feature'],
53
+ },
54
+ score: {
55
+ type: 'number',
56
+ description:
57
+ 'the "score" of each lollipop, displayed as a number in the center of the circle',
58
+ defaultValue: `jexl:get(feature,'score')`,
59
+ contextVariable: ['feature'],
60
+ },
61
+ },
62
+ { explicitlyTyped: true },
63
+ )
@@ -0,0 +1,3 @@
1
+ export { default as ReactComponent } from './components/LollipopRendering'
2
+ export { default as configSchema } from './configSchema'
3
+ export { default } from './LollipopRenderer'