Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New SVG gene glyph with directional arrows #2775

Merged
merged 19 commits into from
Mar 18, 2022
5 changes: 1 addition & 4 deletions packages/core/configuration/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,7 @@ export function readConfObject(
) {
subConf = confObject.get(slotName)
}
if (!subConf) {
return undefined
}
return readConfObject(subConf, newPath, args)
return subConf ? readConfObject(subConf, newPath, args) : undefined
}
return readConfObject(confObject, slotName, args)
}
Expand Down
17 changes: 9 additions & 8 deletions packages/core/util/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -504,21 +504,22 @@ function roundToNearestPointOne(num: number): number {
*/
export function bpToPx(
bp: number,
region: { start: number; end: number; reversed?: boolean },
{
reversed,
end = 0,
start = 0,
}: { start?: number; end?: number; reversed?: boolean },
bpPerPx: number,
): number {
if (region.reversed) {
return roundToNearestPointOne((region.end - bp) / bpPerPx)
}
return roundToNearestPointOne((bp - region.start) / bpPerPx)
) {
return roundToNearestPointOne((reversed ? end - bp : bp - start) / bpPerPx)
}

const oneEightyOverPi = 180.0 / Math.PI
const piOverOneEighty = Math.PI / 180.0
export function radToDeg(radians: number): number {
export function radToDeg(radians: number) {
return (radians * oneEightyOverPi) % 360
}
export function degToRad(degrees: number): number {
export function degToRad(degrees: number) {
return (degrees * piOverOneEighty) % (2 * Math.PI)
}

Expand Down
1 change: 1 addition & 0 deletions packages/core/util/simpleFeature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface Feature {
get(name: 'refName'): string
get(name: 'start'): number
get(name: 'end'): number
get(name: 'subfeatures'): Feature[] | undefined
// eslint-disable-next-line @typescript-eslint/no-explicit-any
get(name: string): any

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React, { useState, useRef, useMemo } from 'react'
import { observer } from 'mobx-react'
import { Portal, alpha, useTheme, makeStyles } from '@material-ui/core'
import { getConf } from '@jbrowse/core/configuration'
import { Menu } from '@jbrowse/core/ui'
import { observer } from 'mobx-react'
import { usePopper } from 'react-popper'

// locals
Expand Down Expand Up @@ -56,7 +56,6 @@ const Tooltip = observer(
const classes = useStyles()
const { featureUnderMouse } = model
const [width, setWidth] = useState(0)

const [popperElt, setPopperElt] = useState<HTMLDivElement | null>(null)

// must be memoized a la https://github.com/popperjs/react-popper/issues/391
Expand Down Expand Up @@ -111,7 +110,7 @@ const BaseLinearDisplay = observer(
const classes = useStyles()
const theme = useTheme()
const ref = useRef<HTMLDivElement>(null)
const [clientRect, setClientRect] = useState<ClientRect>()
const [clientRect, setClientRect] = useState<DOMRect>()
const [offsetMouseCoord, setOffsetMouseCoord] = useState<Coord>([0, 0])
const [clientMouseCoord, setClientMouseCoord] = useState<Coord>([0, 0])
const [contextCoord, setContextCoord] = useState<Coord>()
Expand Down Expand Up @@ -139,17 +138,15 @@ const BaseLinearDisplay = observer(
}
}}
onMouseMove={event => {
if (ref.current) {
const rect = ref.current.getBoundingClientRect()
setOffsetMouseCoord([
event.clientX - rect.left,
event.clientY - rect.top,
])
setClientMouseCoord([event.clientX, event.clientY])
setClientRect(rect)
if (!ref.current) {
return
}
const rect = ref.current.getBoundingClientRect()
const { left, top } = rect
setOffsetMouseCoord([event.clientX - left, event.clientY - top])
setClientMouseCoord([event.clientX, event.clientY])
setClientRect(rect)
}}
role="presentation"
>
{DisplayMessageComponent ? (
<DisplayMessageComponent model={model} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -541,7 +541,6 @@ exports[`<LinearGenomeView /> renders one track, one region 1`] = `
<div
class="makeStyles-display"
data-testid="display-testConfig-LinearBareDisplay"
role="presentation"
>
<div
class="makeStyles-linearBlocks"
Expand Down Expand Up @@ -1434,7 +1433,6 @@ exports[`<LinearGenomeView /> renders two tracks, two regions 1`] = `
<div
class="makeStyles-display"
data-testid="display-testConfig-LinearBareDisplay"
role="presentation"
>
<div
class="makeStyles-linearBlocks"
Expand All @@ -1449,17 +1447,12 @@ exports[`<LinearGenomeView /> renders two tracks, two regions 1`] = `
class="makeStyles-contentBlock"
style="width: 100px;"
>
<div
style="position: relative;"
>
<svg
class="SvgFeatureRendering"
data-testid="svgfeatures"
height="100"
style="display: block;"
width="100"
/>
</div>
<svg
class="SvgFeatureRendering"
data-testid="svgfeatures"
height="100"
width="100"
/>
</div>
<div
class="makeStyles-interRegionPaddingBlock"
Expand All @@ -1469,17 +1462,12 @@ exports[`<LinearGenomeView /> renders two tracks, two regions 1`] = `
class="makeStyles-contentBlock"
style="width: 1000px;"
>
<div
style="position: relative;"
>
<svg
class="SvgFeatureRendering"
data-testid="svgfeatures"
height="100"
style="display: block;"
width="1000"
/>
</div>
<svg
class="SvgFeatureRendering"
data-testid="svgfeatures"
height="100"
width="1000"
/>
</div>
<div
class="makeStyles-boundaryPaddingBlock"
Expand Down Expand Up @@ -1589,7 +1577,6 @@ exports[`<LinearGenomeView /> renders two tracks, two regions 1`] = `
<div
class="makeStyles-display"
data-testid="display-testConfig2-LinearBareDisplay"
role="presentation"
>
<div
class="makeStyles-linearBlocks"
Expand All @@ -1604,17 +1591,12 @@ exports[`<LinearGenomeView /> renders two tracks, two regions 1`] = `
class="makeStyles-contentBlock"
style="width: 100px;"
>
<div
style="position: relative;"
>
<svg
class="SvgFeatureRendering"
data-testid="svgfeatures"
height="100"
style="display: block;"
width="100"
/>
</div>
<svg
class="SvgFeatureRendering"
data-testid="svgfeatures"
height="100"
width="100"
/>
</div>
<div
class="makeStyles-interRegionPaddingBlock"
Expand All @@ -1624,17 +1606,12 @@ exports[`<LinearGenomeView /> renders two tracks, two regions 1`] = `
class="makeStyles-contentBlock"
style="width: 1000px;"
>
<div
style="position: relative;"
>
<svg
class="SvgFeatureRendering"
data-testid="svgfeatures"
height="100"
style="display: block;"
width="1000"
/>
</div>
<svg
class="SvgFeatureRendering"
data-testid="svgfeatures"
height="100"
width="1000"
/>
</div>
<div
class="makeStyles-boundaryPaddingBlock"
Expand Down
43 changes: 43 additions & 0 deletions plugins/svg/src/SvgFeatureRenderer/components/Arrow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react'
import { observer } from 'mobx-react'

import {
AnyConfigurationModel,
readConfObject,
} from '@jbrowse/core/configuration'
import { Feature } from '@jbrowse/core/util/simpleFeature'
import { SceneGraph } from '@jbrowse/core/util/layouts'

const Arrow = ({
feature,
featureLayout,
config,
}: {
feature: Feature
featureLayout: SceneGraph
config: AnyConfigurationModel
}) => {
const strand = feature.get('strand')
const size = 5
const offset = 7 * strand
const { left = 0, top = 0, width = 0, height = 0 } = featureLayout.absolute
const color2 = readConfObject(config, 'color2', { feature })
const p = strand === -1 ? left : strand === 1 ? left + width : null
const y = top + height / 2

return p ? (
<>
<line x1={p} x2={p + offset} y1={y} y2={y} stroke={color2} />
<polygon
points={[
[p + offset / 2, y - size / 2],
[p + offset / 2, y + size / 2],
[p + offset, y],
].toString()}
stroke={color2}
/>
</>
) : null
}

export default observer(Arrow)
66 changes: 0 additions & 66 deletions plugins/svg/src/SvgFeatureRenderer/components/Box.js

This file was deleted.

63 changes: 63 additions & 0 deletions plugins/svg/src/SvgFeatureRenderer/components/Box.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React from 'react'
import {
AnyConfigurationModel,
readConfObject,
} from '@jbrowse/core/configuration'
import { Region } from '@jbrowse/core/util/types'
import { Feature } from '@jbrowse/core/util/simpleFeature'
import { observer } from 'mobx-react'
import { isUTR } from './util'
import Arrow from './Arrow'
import { SceneGraph } from '@jbrowse/core/util/layouts'

const utrHeightFraction = 0.65

function Box(props: {
feature: Feature
region: Region
config: AnyConfigurationModel
featureLayout: SceneGraph
bpPerPx: number
selected?: boolean
topLevel?: boolean
children?: React.ReactNode
}) {
const { feature, region, config, featureLayout, bpPerPx, topLevel } = props
const { start, end } = region
const screenWidth = (end - start) / bpPerPx
const { left = 0, width = 0 } = featureLayout.absolute
let { top = 0, height = 0 } = featureLayout.absolute

if (left + width < 0) {
return null
}

if (isUTR(feature)) {
top += ((1 - utrHeightFraction) / 2) * height
height *= utrHeightFraction
}
const leftWithinBlock = Math.max(left, 0)
const diff = leftWithinBlock - left
const widthWithinBlock = Math.max(1, Math.min(width - diff, screenWidth))

return (
<>
{topLevel ? <Arrow {...props} /> : null}
<rect
data-testid={`box-${feature.id()}`}
x={leftWithinBlock}
y={top}
width={widthWithinBlock}
height={height}
fill={
isUTR(feature)
? readConfObject(config, 'color3', { feature })
: readConfObject(config, 'color1', { feature })
}
stroke={readConfObject(config, 'outline', { feature }) as string}
/>
</>
)
}

export default observer(Box)
Loading