@jbrowse/plugin-circular-view 2.5.0 → 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/BaseChordDisplay/components/BaseChordDisplay.d.ts +1 -1
- package/dist/BaseChordDisplay/components/BaseChordDisplay.js +0 -1
- package/dist/BaseChordDisplay/components/DisplayError.d.ts +2 -1
- package/dist/BaseChordDisplay/components/DisplayError.js +0 -1
- package/dist/BaseChordDisplay/components/Loading.d.ts +2 -1
- package/dist/BaseChordDisplay/components/Loading.js +0 -1
- package/dist/BaseChordDisplay/index.js +0 -1
- package/dist/BaseChordDisplay/models/configSchema.js +0 -1
- package/dist/BaseChordDisplay/models/model.d.ts +8 -2
- package/dist/BaseChordDisplay/models/model.js +0 -1
- package/dist/BaseChordDisplay/models/renderReaction.js +0 -1
- package/dist/CircularView/components/CircularView.d.ts +2 -1
- package/dist/CircularView/components/CircularView.js +0 -1
- package/dist/CircularView/components/Controls.d.ts +2 -1
- package/dist/CircularView/components/Controls.js +0 -1
- package/dist/CircularView/components/ExportSvgDialog.d.ts +2 -1
- package/dist/CircularView/components/ExportSvgDialog.js +0 -1
- package/dist/CircularView/components/ImportForm.d.ts +2 -1
- package/dist/CircularView/components/ImportForm.js +0 -1
- package/dist/CircularView/components/Ruler.d.ts +2 -1
- package/dist/CircularView/components/Ruler.js +0 -1
- package/dist/CircularView/index.js +0 -1
- package/dist/CircularView/models/CircularView.d.ts +1 -3
- package/dist/CircularView/models/CircularView.js +30 -7
- package/dist/CircularView/models/slices.js +0 -1
- package/dist/CircularView/models/viewportVisibleRegion.js +0 -1
- package/dist/CircularView/svgcomponents/SVGBackground.d.ts +2 -1
- package/dist/CircularView/svgcomponents/SVGBackground.js +0 -1
- package/dist/CircularView/svgcomponents/SVGCircularView.js +0 -1
- package/dist/LaunchCircularView/index.js +0 -1
- package/dist/index.js +0 -1
- package/esm/BaseChordDisplay/components/BaseChordDisplay.d.ts +1 -1
- package/esm/BaseChordDisplay/components/BaseChordDisplay.js +0 -1
- package/esm/BaseChordDisplay/components/DisplayError.d.ts +2 -1
- package/esm/BaseChordDisplay/components/DisplayError.js +0 -1
- package/esm/BaseChordDisplay/components/Loading.d.ts +2 -1
- package/esm/BaseChordDisplay/components/Loading.js +0 -1
- package/esm/BaseChordDisplay/index.js +0 -1
- package/esm/BaseChordDisplay/models/configSchema.js +0 -1
- package/esm/BaseChordDisplay/models/model.d.ts +8 -2
- package/esm/BaseChordDisplay/models/model.js +0 -1
- package/esm/BaseChordDisplay/models/renderReaction.js +0 -1
- package/esm/CircularView/components/CircularView.d.ts +2 -1
- package/esm/CircularView/components/CircularView.js +0 -1
- package/esm/CircularView/components/Controls.d.ts +2 -1
- package/esm/CircularView/components/Controls.js +0 -1
- package/esm/CircularView/components/ExportSvgDialog.d.ts +2 -1
- package/esm/CircularView/components/ExportSvgDialog.js +0 -1
- package/esm/CircularView/components/ImportForm.d.ts +2 -1
- package/esm/CircularView/components/ImportForm.js +0 -1
- package/esm/CircularView/components/Ruler.d.ts +2 -1
- package/esm/CircularView/components/Ruler.js +0 -1
- package/esm/CircularView/index.js +0 -1
- package/esm/CircularView/models/CircularView.d.ts +1 -3
- package/esm/CircularView/models/CircularView.js +6 -6
- package/esm/CircularView/models/slices.js +0 -1
- package/esm/CircularView/models/viewportVisibleRegion.js +0 -1
- package/esm/CircularView/svgcomponents/SVGBackground.d.ts +2 -1
- package/esm/CircularView/svgcomponents/SVGBackground.js +0 -1
- package/esm/CircularView/svgcomponents/SVGCircularView.js +0 -1
- package/esm/LaunchCircularView/index.js +0 -1
- package/esm/index.js +0 -1
- package/package.json +3 -4
- package/dist/BaseChordDisplay/components/BaseChordDisplay.js.map +0 -1
- package/dist/BaseChordDisplay/components/DisplayError.js.map +0 -1
- package/dist/BaseChordDisplay/components/Loading.js.map +0 -1
- package/dist/BaseChordDisplay/index.js.map +0 -1
- package/dist/BaseChordDisplay/models/configSchema.js.map +0 -1
- package/dist/BaseChordDisplay/models/model.js.map +0 -1
- package/dist/BaseChordDisplay/models/renderReaction.js.map +0 -1
- package/dist/CircularView/components/CircularView.js.map +0 -1
- package/dist/CircularView/components/Controls.js.map +0 -1
- package/dist/CircularView/components/ExportSvgDialog.js.map +0 -1
- package/dist/CircularView/components/ImportForm.js.map +0 -1
- package/dist/CircularView/components/Ruler.js.map +0 -1
- package/dist/CircularView/index.js.map +0 -1
- package/dist/CircularView/models/CircularView.js.map +0 -1
- package/dist/CircularView/models/slices.js.map +0 -1
- package/dist/CircularView/models/viewportVisibleRegion.js.map +0 -1
- package/dist/CircularView/svgcomponents/SVGBackground.js.map +0 -1
- package/dist/CircularView/svgcomponents/SVGCircularView.js.map +0 -1
- package/dist/LaunchCircularView/index.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/esm/BaseChordDisplay/components/BaseChordDisplay.js.map +0 -1
- package/esm/BaseChordDisplay/components/DisplayError.js.map +0 -1
- package/esm/BaseChordDisplay/components/Loading.js.map +0 -1
- package/esm/BaseChordDisplay/index.js.map +0 -1
- package/esm/BaseChordDisplay/models/configSchema.js.map +0 -1
- package/esm/BaseChordDisplay/models/model.js.map +0 -1
- package/esm/BaseChordDisplay/models/renderReaction.js.map +0 -1
- package/esm/CircularView/components/CircularView.js.map +0 -1
- package/esm/CircularView/components/Controls.js.map +0 -1
- package/esm/CircularView/components/ExportSvgDialog.js.map +0 -1
- package/esm/CircularView/components/ImportForm.js.map +0 -1
- package/esm/CircularView/components/Ruler.js.map +0 -1
- package/esm/CircularView/index.js.map +0 -1
- package/esm/CircularView/models/CircularView.js.map +0 -1
- package/esm/CircularView/models/slices.js.map +0 -1
- package/esm/CircularView/models/viewportVisibleRegion.js.map +0 -1
- package/esm/CircularView/svgcomponents/SVGBackground.js.map +0 -1
- package/esm/CircularView/svgcomponents/SVGCircularView.js.map +0 -1
- package/esm/LaunchCircularView/index.js.map +0 -1
- package/esm/index.js.map +0 -1
- package/src/BaseChordDisplay/components/BaseChordDisplay.tsx +0 -26
- package/src/BaseChordDisplay/components/DisplayError.tsx +0 -47
- package/src/BaseChordDisplay/components/Loading.tsx +0 -111
- package/src/BaseChordDisplay/index.ts +0 -3
- package/src/BaseChordDisplay/models/configSchema.ts +0 -30
- package/src/BaseChordDisplay/models/model.tsx +0 -314
- package/src/BaseChordDisplay/models/renderReaction.ts +0 -75
- package/src/CircularView/components/CircularView.tsx +0 -128
- package/src/CircularView/components/Controls.tsx +0 -119
- package/src/CircularView/components/ExportSvgDialog.tsx +0 -132
- package/src/CircularView/components/ImportForm.tsx +0 -68
- package/src/CircularView/components/Ruler.tsx +0 -265
- package/src/CircularView/index.ts +0 -16
- package/src/CircularView/models/CircularView.ts +0 -620
- package/src/CircularView/models/__snapshots__/slices.test.js.snap +0 -91
- package/src/CircularView/models/slices.test.js +0 -70
- package/src/CircularView/models/slices.ts +0 -101
- package/src/CircularView/models/viewportVisibleRegion.test.js +0 -168
- package/src/CircularView/models/viewportVisibleRegion.ts +0 -272
- package/src/CircularView/svgcomponents/SVGBackground.tsx +0 -21
- package/src/CircularView/svgcomponents/SVGCircularView.tsx +0 -58
- package/src/LaunchCircularView/index.ts +0 -48
- package/src/index.ts +0 -43
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { calculateStaticSlices } from './slices'
|
|
2
|
-
|
|
3
|
-
test('one slice', () => {
|
|
4
|
-
const view = {
|
|
5
|
-
elidedRegions: [{ refName: 'toast', start: 0, end: 10000, widthBp: 10000 }],
|
|
6
|
-
spacingPx: 5,
|
|
7
|
-
radiusPx: 1000,
|
|
8
|
-
totalBp: 10000,
|
|
9
|
-
bpPerRadian: 10000 / (2 * Math.PI),
|
|
10
|
-
pxPerRadian: 1000,
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const slices = calculateStaticSlices(view)
|
|
14
|
-
// console.log(slices)
|
|
15
|
-
expect(slices.length).toBe(1)
|
|
16
|
-
const [slice] = slices
|
|
17
|
-
// expect(slices).toMatchSnapshot()
|
|
18
|
-
expect(slice).toMatchSnapshot()
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
test('two slices', () => {
|
|
22
|
-
const view = {
|
|
23
|
-
elidedRegions: [
|
|
24
|
-
{ refName: 'toast', start: 0, end: 10000, widthBp: 10000 },
|
|
25
|
-
{ refName: 'teest', start: 0, end: 10000, widthBp: 10000 },
|
|
26
|
-
],
|
|
27
|
-
spacingPx: 5,
|
|
28
|
-
radiusPx: 1000,
|
|
29
|
-
pxPerRadian: 1000,
|
|
30
|
-
totalBp: 20000,
|
|
31
|
-
bpPerRadian: 20000 / (2 * Math.PI),
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const slices = calculateStaticSlices(view)
|
|
35
|
-
// console.log(slices)
|
|
36
|
-
expect(slices.length).toBe(2)
|
|
37
|
-
expect(slices).toMatchSnapshot()
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
test('volvox', () => {
|
|
41
|
-
const totalBp = 50001 + 6079
|
|
42
|
-
const view = {
|
|
43
|
-
elidedRegions: [
|
|
44
|
-
{
|
|
45
|
-
refName: 'ctgA',
|
|
46
|
-
start: 0,
|
|
47
|
-
end: 50001,
|
|
48
|
-
assemblyName: 'volvox',
|
|
49
|
-
widthBp: 50001,
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
refName: 'ctgB',
|
|
53
|
-
start: 0,
|
|
54
|
-
end: 6079,
|
|
55
|
-
assemblyName: 'volvox',
|
|
56
|
-
widthBp: 6079,
|
|
57
|
-
},
|
|
58
|
-
],
|
|
59
|
-
spacingPx: 5,
|
|
60
|
-
radiusPx: 1000,
|
|
61
|
-
pxPerRadian: 1000,
|
|
62
|
-
totalBp,
|
|
63
|
-
bpPerRadian: totalBp / (2 * Math.PI),
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const slices = calculateStaticSlices(view)
|
|
67
|
-
// console.log(slices)
|
|
68
|
-
expect(slices.length).toBe(2)
|
|
69
|
-
expect(slices).toMatchSnapshot()
|
|
70
|
-
})
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import { polarToCartesian, assembleLocString, Region } from '@jbrowse/core/util'
|
|
2
|
-
import { thetaRangesOverlap } from './viewportVisibleRegion'
|
|
3
|
-
|
|
4
|
-
export type SliceElidedRegion = {
|
|
5
|
-
elided: true
|
|
6
|
-
widthBp: number
|
|
7
|
-
regions: Region[]
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export type SliceNonElidedRegion = {
|
|
11
|
-
elided: false
|
|
12
|
-
widthBp: number
|
|
13
|
-
start: number
|
|
14
|
-
end: number
|
|
15
|
-
refName: string
|
|
16
|
-
assemblyName: string
|
|
17
|
-
}
|
|
18
|
-
export type SliceRegion = SliceNonElidedRegion | SliceElidedRegion
|
|
19
|
-
|
|
20
|
-
export class Slice {
|
|
21
|
-
key: string
|
|
22
|
-
|
|
23
|
-
startRadians: number
|
|
24
|
-
|
|
25
|
-
endRadians: number
|
|
26
|
-
|
|
27
|
-
bpPerRadian: number
|
|
28
|
-
|
|
29
|
-
flipped: boolean
|
|
30
|
-
|
|
31
|
-
constructor(
|
|
32
|
-
view: { bpPerRadian: number },
|
|
33
|
-
public region: SliceRegion,
|
|
34
|
-
public offsetRadians: number,
|
|
35
|
-
public radianWidth: number,
|
|
36
|
-
) {
|
|
37
|
-
const { bpPerRadian } = view
|
|
38
|
-
this.key =
|
|
39
|
-
'regions' in region
|
|
40
|
-
? JSON.stringify(region.regions)
|
|
41
|
-
: assembleLocString(region)
|
|
42
|
-
this.bpPerRadian = bpPerRadian
|
|
43
|
-
this.flipped = false
|
|
44
|
-
|
|
45
|
-
this.startRadians = offsetRadians
|
|
46
|
-
this.endRadians = region.widthBp / this.bpPerRadian + offsetRadians
|
|
47
|
-
Object.freeze(this)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
bpToXY(bp: number, radiusPx: number) {
|
|
51
|
-
let offsetBp
|
|
52
|
-
if (this.region.elided) {
|
|
53
|
-
offsetBp = this.region.widthBp / 2
|
|
54
|
-
} else if (this.flipped) {
|
|
55
|
-
offsetBp = this.region.end - bp
|
|
56
|
-
} else {
|
|
57
|
-
offsetBp = bp - this.region.start
|
|
58
|
-
}
|
|
59
|
-
const totalRadians = offsetBp / this.bpPerRadian + this.offsetRadians
|
|
60
|
-
return polarToCartesian(radiusPx, totalRadians)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
toJSON() {
|
|
64
|
-
return Object.fromEntries(Object.entries(this))
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function calculateStaticSlices(self: {
|
|
69
|
-
elidedRegions: SliceRegion[]
|
|
70
|
-
bpPerRadian: number
|
|
71
|
-
spacingPx: number
|
|
72
|
-
pxPerRadian: number
|
|
73
|
-
}) {
|
|
74
|
-
const slices = []
|
|
75
|
-
let currentRadianOffset = 0
|
|
76
|
-
const { bpPerRadian, spacingPx, pxPerRadian } = self
|
|
77
|
-
for (const region of self.elidedRegions) {
|
|
78
|
-
const radianWidth = region.widthBp / bpPerRadian + spacingPx / pxPerRadian
|
|
79
|
-
slices.push(new Slice(self, region, currentRadianOffset, radianWidth))
|
|
80
|
-
currentRadianOffset += radianWidth
|
|
81
|
-
}
|
|
82
|
-
return slices
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function sliceIsVisible(
|
|
86
|
-
self: { offsetRadians: number; visibleSection: { theta: [number, number] } },
|
|
87
|
-
slice: Slice,
|
|
88
|
-
) {
|
|
89
|
-
const {
|
|
90
|
-
theta: [visibleThetaMin, visibleThetaMax],
|
|
91
|
-
} = self.visibleSection
|
|
92
|
-
|
|
93
|
-
return thetaRangesOverlap(
|
|
94
|
-
slice.offsetRadians + self.offsetRadians,
|
|
95
|
-
slice.radianWidth,
|
|
96
|
-
visibleThetaMin,
|
|
97
|
-
visibleThetaMax - visibleThetaMin,
|
|
98
|
-
)
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export { calculateStaticSlices, sliceIsVisible }
|
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
viewportVisibleSection,
|
|
3
|
-
cartesianToPolar,
|
|
4
|
-
thetaRangesOverlap,
|
|
5
|
-
} from './viewportVisibleRegion'
|
|
6
|
-
|
|
7
|
-
describe('viewportVisibleSection', () => {
|
|
8
|
-
// test('circle contained in viewport', () => {
|
|
9
|
-
// const result = viewportVisibleSection([0, 1, 0, 1], [0.5, 0.5], 0.3)
|
|
10
|
-
// expect(result).toEqual({ rho: [0, 0.3], theta: [0, 2 * Math.PI] })
|
|
11
|
-
// })
|
|
12
|
-
|
|
13
|
-
// test('viewport completely inside circle', () => {
|
|
14
|
-
// const result = viewportVisibleSection([0, 1, 0, 1], [0.5, 0.5], 20)
|
|
15
|
-
// expect(result.theta).toEqual([0, 2 * Math.PI])
|
|
16
|
-
// expect(result.rho[0]).toEqual(0)
|
|
17
|
-
// expect(result.rho[1]).toBeCloseTo(0.7071)
|
|
18
|
-
// })
|
|
19
|
-
|
|
20
|
-
// test('viewport on left half of circle', () => {
|
|
21
|
-
// const result = viewportVisibleSection([200, 500, 0, 1000], [500, 500], 20)
|
|
22
|
-
// expect(result).toEqual({
|
|
23
|
-
// rho: [0, 20],
|
|
24
|
-
// theta: [Math.PI / 2, 1.5 * Math.PI],
|
|
25
|
-
// })
|
|
26
|
-
// })
|
|
27
|
-
// test('viewport on right half of circle', () => {
|
|
28
|
-
// const result = viewportVisibleSection([200, 500, 0, 1000], [200, 500], 20)
|
|
29
|
-
// expect(result).toEqual({
|
|
30
|
-
// rho: [0, 20],
|
|
31
|
-
// theta: [1.5 * Math.PI, 2.5 * Math.PI],
|
|
32
|
-
// })
|
|
33
|
-
// })
|
|
34
|
-
|
|
35
|
-
// test('viewport corner in circle', () => {
|
|
36
|
-
// const { theta, rho } = viewportVisibleSection(
|
|
37
|
-
// [200, 500, 0, 700],
|
|
38
|
-
// [199, 701],
|
|
39
|
-
// 100,
|
|
40
|
-
// )
|
|
41
|
-
// expect(rho).toEqual([1.4142135623730951, 100])
|
|
42
|
-
// expect(theta[0]).toBeCloseTo(1.5 * Math.PI, 1)
|
|
43
|
-
// expect(theta[1]).toBeCloseTo(2 * Math.PI, 1)
|
|
44
|
-
// })
|
|
45
|
-
|
|
46
|
-
// test('viewport on center right', () => {
|
|
47
|
-
// const { theta, rho } = viewportVisibleSection(
|
|
48
|
-
// [1102, 2153, 880, 1280],
|
|
49
|
-
// [1068.8119697255406, 1068.8119697255406],
|
|
50
|
-
// 1048.8119697255406,
|
|
51
|
-
// )
|
|
52
|
-
// expect(theta[1]).toBeGreaterThan(2 * Math.PI)
|
|
53
|
-
// expect(theta[1]).toBeLessThan(2.5 * Math.PI)
|
|
54
|
-
// expect(theta[0]).toBeGreaterThan(1.5 * Math.PI)
|
|
55
|
-
// expect(theta[0]).toBeLessThan(2 * Math.PI)
|
|
56
|
-
// })
|
|
57
|
-
|
|
58
|
-
// test('viewport on center right 2', () => {
|
|
59
|
-
// const { theta, rho } = viewportVisibleSection(
|
|
60
|
-
// [1816, 2937, 1074, 1474],
|
|
61
|
-
// [1468.6015446723616, 1468.6015446723616],
|
|
62
|
-
// 1448.6015446723616,
|
|
63
|
-
// )
|
|
64
|
-
// expect(theta[1]).toBeGreaterThan(2 * Math.PI)
|
|
65
|
-
// expect(theta[1]).toBeLessThan(2.5 * Math.PI)
|
|
66
|
-
// expect(theta[0]).toBeGreaterThan(1.5 * Math.PI)
|
|
67
|
-
// expect(theta[0]).toBeLessThan(2 * Math.PI)
|
|
68
|
-
// })
|
|
69
|
-
|
|
70
|
-
// test('viewport on lower center', () => {
|
|
71
|
-
// const { theta, rho } = viewportVisibleSection(
|
|
72
|
-
// [259, 1350, 1176, 1576],
|
|
73
|
-
// [787.7952717090081, 787.7952717090081],
|
|
74
|
-
// 767.7952717090081,
|
|
75
|
-
// )
|
|
76
|
-
// expect(theta[1]).toBeGreaterThan(Math.PI / 2)
|
|
77
|
-
// expect(theta[1]).toBeLessThan(Math.PI)
|
|
78
|
-
// expect(theta[0]).toBeGreaterThan(0)
|
|
79
|
-
// expect(theta[0]).toBeLessThan(Math.PI / 2)
|
|
80
|
-
// })
|
|
81
|
-
|
|
82
|
-
// test('viewport on upper center', () => {
|
|
83
|
-
// const { theta, rho } = viewportVisibleSection(
|
|
84
|
-
// [286, 1377, 0, 400],
|
|
85
|
-
// [787.7952717090081, 787.7952717090081],
|
|
86
|
-
// 767.7952717090081,
|
|
87
|
-
// )
|
|
88
|
-
// expect(theta[1]).toBeGreaterThan(1.5 * Math.PI)
|
|
89
|
-
// expect(theta[1]).toBeLessThan(2 * Math.PI)
|
|
90
|
-
// expect(theta[0]).toBeGreaterThan(Math.PI)
|
|
91
|
-
// expect(theta[0]).toBeLessThan(1.5 * Math.PI)
|
|
92
|
-
// })
|
|
93
|
-
|
|
94
|
-
test('viewport on upper center 2', () => {
|
|
95
|
-
// [180.48708681644143, 359.3411680673888] [4.6042679453532855, 541.6042679453533]
|
|
96
|
-
// see '~/Desktop/Screen Shot 2019-06-28 at 3.01.22 PM.png'
|
|
97
|
-
const { theta } = viewportVisibleSection(
|
|
98
|
-
[0, 962, 157, 557],
|
|
99
|
-
[561.6042679453533, 561.6042679453533],
|
|
100
|
-
541.6042679453533,
|
|
101
|
-
)
|
|
102
|
-
expect(theta[1]).toBeGreaterThan(1.75 * Math.PI)
|
|
103
|
-
expect(theta[1]).toBeLessThan(2 * Math.PI)
|
|
104
|
-
expect(theta[0]).toBeGreaterThan(Math.PI)
|
|
105
|
-
expect(theta[0]).toBeLessThan(1.1 * Math.PI)
|
|
106
|
-
})
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
describe('cartesian to polar', () => {
|
|
110
|
-
;[
|
|
111
|
-
[
|
|
112
|
-
[-1, -1],
|
|
113
|
-
[1.414, 180 + 45],
|
|
114
|
-
],
|
|
115
|
-
[
|
|
116
|
-
[-1, 1],
|
|
117
|
-
[1.414, 90 + 45],
|
|
118
|
-
],
|
|
119
|
-
[
|
|
120
|
-
[1, 1],
|
|
121
|
-
[1.414, 45],
|
|
122
|
-
],
|
|
123
|
-
[
|
|
124
|
-
[1, -1],
|
|
125
|
-
[1.414, 360 - 45],
|
|
126
|
-
],
|
|
127
|
-
[
|
|
128
|
-
[0, 1],
|
|
129
|
-
[1, 90],
|
|
130
|
-
],
|
|
131
|
-
[
|
|
132
|
-
[0, -1],
|
|
133
|
-
[1, 270],
|
|
134
|
-
],
|
|
135
|
-
[
|
|
136
|
-
[-1, 0],
|
|
137
|
-
[1, 180],
|
|
138
|
-
],
|
|
139
|
-
[
|
|
140
|
-
[1, 0],
|
|
141
|
-
[1, 0],
|
|
142
|
-
],
|
|
143
|
-
].forEach(testCase => {
|
|
144
|
-
const [input, output] = testCase
|
|
145
|
-
test(`${input} -> ${output}`, () => {
|
|
146
|
-
const result = cartesianToPolar(...input)
|
|
147
|
-
expect(result[0]).toBeCloseTo(output[0])
|
|
148
|
-
expect((result[1] * 180) / Math.PI).toBeCloseTo(output[1])
|
|
149
|
-
})
|
|
150
|
-
})
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
describe('theta overlap testing', () => {
|
|
154
|
-
;[
|
|
155
|
-
[[0, 2 * Math.PI, 0, 2 * Math.PI], true],
|
|
156
|
-
[[6.1, Math.PI / 2, 0, Math.PI / 2], true],
|
|
157
|
-
[[6.1, Math.PI / 2, 0, 0.1], true],
|
|
158
|
-
[[6.1, 0.1, 6.12, 0.05], true],
|
|
159
|
-
[[-12, 0.1, -12.05, 0.05], false],
|
|
160
|
-
[[-12, 0.1, -12.05, 0.06], true],
|
|
161
|
-
].forEach(testCase => {
|
|
162
|
-
const [input, output] = testCase
|
|
163
|
-
test(`${input} -> ${output}`, () => {
|
|
164
|
-
const result = thetaRangesOverlap(...input)
|
|
165
|
-
expect(result).toBe(output)
|
|
166
|
-
})
|
|
167
|
-
})
|
|
168
|
-
})
|
|
@@ -1,272 +0,0 @@
|
|
|
1
|
-
function findCircleIntersectionX(
|
|
2
|
-
y: number,
|
|
3
|
-
cx: number,
|
|
4
|
-
cy: number,
|
|
5
|
-
r: number,
|
|
6
|
-
resultArray: [number, number][],
|
|
7
|
-
) {
|
|
8
|
-
const d = Math.abs(y - cy)
|
|
9
|
-
if (d > r) {
|
|
10
|
-
return
|
|
11
|
-
}
|
|
12
|
-
if (d === r) {
|
|
13
|
-
resultArray.push([cx, y])
|
|
14
|
-
}
|
|
15
|
-
const solution = Math.sqrt(r * r - d * d)
|
|
16
|
-
resultArray.push([cx - solution, y], [cx + solution, y])
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function findCircleIntersectionY(
|
|
20
|
-
x: number,
|
|
21
|
-
cx: number,
|
|
22
|
-
cy: number,
|
|
23
|
-
r: number,
|
|
24
|
-
resultArray: [number, number][],
|
|
25
|
-
) {
|
|
26
|
-
const d = Math.abs(x - cx)
|
|
27
|
-
if (d > r) {
|
|
28
|
-
return
|
|
29
|
-
}
|
|
30
|
-
if (d === r) {
|
|
31
|
-
resultArray.push([x, cy])
|
|
32
|
-
}
|
|
33
|
-
const solution = Math.sqrt(r * r - d * d)
|
|
34
|
-
resultArray.push([x, cy - solution], [x, cy + solution])
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function cartesianToTheta(x: number, y: number) {
|
|
38
|
-
let theta = (Math.atan(y / x) + 2 * Math.PI) % (2 * Math.PI)
|
|
39
|
-
if (x < 0) {
|
|
40
|
-
if (y <= 0) {
|
|
41
|
-
theta += Math.PI
|
|
42
|
-
} else {
|
|
43
|
-
theta -= Math.PI
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
return theta
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function cartesianToPolar(x: number, y: number) {
|
|
50
|
-
const rho = Math.sqrt(x * x + y * y)
|
|
51
|
-
if (rho === 0) {
|
|
52
|
-
return [0, 0]
|
|
53
|
-
}
|
|
54
|
-
const theta = cartesianToTheta(x, y)
|
|
55
|
-
return [rho, theta]
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const twoPi = 2 * Math.PI
|
|
59
|
-
export function thetaRangesOverlap(
|
|
60
|
-
r1start: number,
|
|
61
|
-
r1length: number,
|
|
62
|
-
r2start: number,
|
|
63
|
-
r2length: number,
|
|
64
|
-
) {
|
|
65
|
-
if (r1length <= 0 || r2length <= 0) {
|
|
66
|
-
return false
|
|
67
|
-
}
|
|
68
|
-
if (r1length + 0.0001 >= twoPi || r2length + 0.0001 >= twoPi) {
|
|
69
|
-
return true
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// put both range starts between 2π and 4π
|
|
73
|
-
r1start = (((r1start % twoPi) + twoPi) % twoPi) + twoPi
|
|
74
|
-
r2start = (((r2start % twoPi) + twoPi) % twoPi) + twoPi
|
|
75
|
-
|
|
76
|
-
if (r1start < r2start + r2length && r1start + r1length > r2start) {
|
|
77
|
-
return true
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// move r2 2π to the left and check
|
|
81
|
-
r2start -= twoPi
|
|
82
|
-
if (r1start < r2start + r2length && r1start + r1length > r2start) {
|
|
83
|
-
return true
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// move it 2π to the right and check
|
|
87
|
-
r2start += twoPi + twoPi
|
|
88
|
-
return r1start < r2start + r2length && r1start + r1length > r2start
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// return which arc range has any part of the circle visible in the viewport
|
|
92
|
-
export function viewportVisibleSection(
|
|
93
|
-
viewSides: [number, number, number, number],
|
|
94
|
-
circleCenter: [number, number],
|
|
95
|
-
circleRadius: number,
|
|
96
|
-
) {
|
|
97
|
-
let [viewL, viewR, viewT, viewB] = viewSides
|
|
98
|
-
const [cx, cy] = circleCenter
|
|
99
|
-
|
|
100
|
-
// transform coordinate system to center of circle
|
|
101
|
-
viewL -= cx
|
|
102
|
-
viewR -= cx
|
|
103
|
-
viewT -= cy
|
|
104
|
-
viewB -= cy
|
|
105
|
-
|
|
106
|
-
const centerIsInsideViewport =
|
|
107
|
-
viewL < 0 && viewR > 0 && viewT < 0 && viewB > 0
|
|
108
|
-
|
|
109
|
-
if (centerIsInsideViewport) {
|
|
110
|
-
const vertices = [
|
|
111
|
-
[viewL, viewT],
|
|
112
|
-
[viewR, viewT],
|
|
113
|
-
[viewL, viewB],
|
|
114
|
-
[viewR, viewB],
|
|
115
|
-
]
|
|
116
|
-
let maxRho = -Infinity
|
|
117
|
-
for (let i = 0; i < vertices.length; i += 1) {
|
|
118
|
-
const [x, y] = vertices[i]
|
|
119
|
-
const rho = Math.sqrt(x * x + y * y)
|
|
120
|
-
if (rho > maxRho) {
|
|
121
|
-
maxRho = rho
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
return {
|
|
125
|
-
rho: [0, Math.min(circleRadius, maxRho)] as [number, number],
|
|
126
|
-
theta: [0, 2 * Math.PI] as [number, number],
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
// const viewportCompletelyContainsCircle =
|
|
130
|
-
// circleCenter[0] - viewL >= circleRadius &&
|
|
131
|
-
// viewR - circleCenter[0] >= circleRadius &&
|
|
132
|
-
// circleCenter[1] - viewT >= circleRadius &&
|
|
133
|
-
// viewB - circleCenter[1] >= circleRadius
|
|
134
|
-
|
|
135
|
-
// if (viewportCompletelyContainsCircle) {
|
|
136
|
-
// return [0, 2 * Math.PI]
|
|
137
|
-
// }
|
|
138
|
-
|
|
139
|
-
// const distToCenterSquared = ([x, y]) => {
|
|
140
|
-
// const [cx, cy] = circleCenter
|
|
141
|
-
// const sq = n => n * n
|
|
142
|
-
// return sq(x - cx) + sq(y - cy)
|
|
143
|
-
// }
|
|
144
|
-
// const circleRadiusSquared = circleRadius * circleRadius
|
|
145
|
-
|
|
146
|
-
// const tlInside = distToCenterSquared([viewL, viewT]) <= circleRadiusSquared
|
|
147
|
-
// const trInside = distToCenterSquared([viewR, viewT]) <= circleRadiusSquared
|
|
148
|
-
// const blInside = distToCenterSquared([viewL, viewB]) <= circleRadiusSquared
|
|
149
|
-
// const brInside = distToCenterSquared([viewR, viewB]) <= circleRadiusSquared
|
|
150
|
-
|
|
151
|
-
// const noIntersection = !tlInside && !trInside && !blInside && !brInside
|
|
152
|
-
// if (noIntersection) return undefined
|
|
153
|
-
|
|
154
|
-
// const circleCompletelyContainsViewport =
|
|
155
|
-
// tlInside && trInside && blInside && brInside
|
|
156
|
-
// if (circleCompletelyContainsViewport) {
|
|
157
|
-
// // viewport is in the circle, but the center is not in it, so take max
|
|
158
|
-
// // and min of thetas to the center
|
|
159
|
-
// const thetas = [
|
|
160
|
-
// Math.atan(viewT / viewL),
|
|
161
|
-
// Math.atan(viewT / viewR),
|
|
162
|
-
// Math.atan(viewB / viewL),
|
|
163
|
-
// Math.atan(viewB / viewR),
|
|
164
|
-
// ]
|
|
165
|
-
|
|
166
|
-
// return [Math.min(...thetas), Math.max(...thetas)]
|
|
167
|
-
// }
|
|
168
|
-
|
|
169
|
-
// if we get here, the viewport is partly in, partly out of the circle
|
|
170
|
-
|
|
171
|
-
// const viewLIntersects = Math.abs(viewL - circleCenter[0]) <= circleRadius
|
|
172
|
-
// const viewRIntersects = Math.abs(viewR - circleCenter[0]) <= circleRadius
|
|
173
|
-
// const viewTIntersects = Math.abs(viewT - circleCenter[1]) <= circleRadius
|
|
174
|
-
// const viewBIntersects = Math.abs(viewB - circleCenter[1]) <= circleRadius
|
|
175
|
-
|
|
176
|
-
// const numIntersectingSides =
|
|
177
|
-
// Number(viewLIntersects) +
|
|
178
|
-
// Number(viewRIntersects) +
|
|
179
|
-
// Number(viewTIntersects) +
|
|
180
|
-
// Number(viewBIntersects)
|
|
181
|
-
|
|
182
|
-
// if (numIntersectingSides === 4) return [0, 2 * Math.PI]
|
|
183
|
-
// if (numIntersectingSides === 3) {
|
|
184
|
-
// // TODO calculate the thetas of the
|
|
185
|
-
// } else if (numIntersectingSides === 2) {
|
|
186
|
-
// // TODO calculate the thetas of the 2 intersection points
|
|
187
|
-
// } else if (numIntersectingSides === 1) {
|
|
188
|
-
// // TODO calculate the thetas of the 1-2 intersection points of the line, and the angle between
|
|
189
|
-
// }
|
|
190
|
-
|
|
191
|
-
// make a list of vertices-of-interest that lie inside both shapes to examine
|
|
192
|
-
// to determine the range covered by their intersection
|
|
193
|
-
|
|
194
|
-
// transform coordinates to have the circle as the origin and find the intersections
|
|
195
|
-
// of the circle and the view rectangle
|
|
196
|
-
const vertices: [number, number][] = [
|
|
197
|
-
[viewL, viewT],
|
|
198
|
-
[viewR, viewT],
|
|
199
|
-
[viewL, viewB],
|
|
200
|
-
[viewR, viewB],
|
|
201
|
-
]
|
|
202
|
-
findCircleIntersectionY(viewL, 0, 0, circleRadius, vertices)
|
|
203
|
-
findCircleIntersectionY(viewR, 0, 0, circleRadius, vertices)
|
|
204
|
-
findCircleIntersectionX(viewT, 0, 0, circleRadius, vertices)
|
|
205
|
-
findCircleIntersectionX(viewB, 0, 0, circleRadius, vertices)
|
|
206
|
-
|
|
207
|
-
// for each edge, also look at the closest point to center if it is inside the circle
|
|
208
|
-
if (-viewL < circleRadius) {
|
|
209
|
-
vertices.push([viewL, 0])
|
|
210
|
-
}
|
|
211
|
-
if (viewR < circleRadius) {
|
|
212
|
-
vertices.push([viewR, 0])
|
|
213
|
-
}
|
|
214
|
-
if (-viewT < circleRadius) {
|
|
215
|
-
vertices.push([0, viewT])
|
|
216
|
-
}
|
|
217
|
-
if (viewB < circleRadius) {
|
|
218
|
-
vertices.push([0, viewB])
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// const verticesOriginal = vertices.map(([x, y]) => [x + cx, y + cy])
|
|
222
|
-
|
|
223
|
-
// now convert them all to polar and take the max and min of rho and theta
|
|
224
|
-
|
|
225
|
-
// const viewportCenterTheta = cartesianToTheta(viewR + viewL, viewT + viewB)
|
|
226
|
-
const reflect = viewL >= 0 ? -1 : 1
|
|
227
|
-
// viewportCenterTheta < Math.PI / 2 || viewportCenterTheta > 1.5 * Math.PI
|
|
228
|
-
// ? -1
|
|
229
|
-
// : 1
|
|
230
|
-
let rhoMin = Infinity
|
|
231
|
-
let rhoMax = -Infinity
|
|
232
|
-
let thetaMin = Infinity
|
|
233
|
-
let thetaMax = -Infinity
|
|
234
|
-
for (let i = 0; i < vertices.length; i += 1) {
|
|
235
|
-
// ignore vertex if outside the viewport
|
|
236
|
-
const [vx, vy] = vertices[i]
|
|
237
|
-
if (vx >= viewL && vx <= viewR && vy >= viewT && vy <= viewB) {
|
|
238
|
-
const [rho, theta] = cartesianToPolar(vx * reflect, vy * reflect)
|
|
239
|
-
// ignore vertex if outside the circle
|
|
240
|
-
if (rho <= circleRadius + 0.001) {
|
|
241
|
-
// ignore theta if rho is 0
|
|
242
|
-
if (theta < thetaMin && rho > 0.0001) {
|
|
243
|
-
thetaMin = theta
|
|
244
|
-
}
|
|
245
|
-
if (theta > thetaMax && rho > 0.0001) {
|
|
246
|
-
thetaMax = theta
|
|
247
|
-
}
|
|
248
|
-
if (rho < rhoMin) {
|
|
249
|
-
rhoMin = rho
|
|
250
|
-
}
|
|
251
|
-
if (rho > rhoMax) {
|
|
252
|
-
rhoMax = rho
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
if (reflect === -1) {
|
|
259
|
-
thetaMin += Math.PI
|
|
260
|
-
thetaMax += Math.PI
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
if (thetaMin > 2 * Math.PI && thetaMax > 2 * Math.PI) {
|
|
264
|
-
thetaMin -= 2 * Math.PI
|
|
265
|
-
thetaMax -= 2 * Math.PI
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
return {
|
|
269
|
-
rho: [rhoMin, Math.min(circleRadius, rhoMax)] as [number, number],
|
|
270
|
-
theta: [thetaMin, thetaMax] as [number, number],
|
|
271
|
-
}
|
|
272
|
-
}
|
|
@@ -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,58 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { ThemeProvider } from '@mui/material'
|
|
3
|
-
import { renderToStaticMarkup } from 'react-dom/server'
|
|
4
|
-
import { when } from 'mobx'
|
|
5
|
-
import { getSession, radToDeg } from '@jbrowse/core/util'
|
|
6
|
-
import { createJBrowseTheme } from '@jbrowse/core/ui'
|
|
7
|
-
|
|
8
|
-
// locals
|
|
9
|
-
import { ExportSvgOptions, CircularViewModel } from '../models/CircularView'
|
|
10
|
-
import SVGBackground from './SVGBackground'
|
|
11
|
-
import Ruler from '../components/Ruler'
|
|
12
|
-
|
|
13
|
-
type CGV = CircularViewModel
|
|
14
|
-
|
|
15
|
-
export async function renderToSvg(model: CGV, opts: ExportSvgOptions) {
|
|
16
|
-
await when(() => model.initialized)
|
|
17
|
-
const { themeName = 'default', Wrapper = ({ children }) => <>{children}</> } =
|
|
18
|
-
opts
|
|
19
|
-
const session = getSession(model)
|
|
20
|
-
const theme = session.allThemes?.()[themeName]
|
|
21
|
-
const { width, tracks, height } = model
|
|
22
|
-
const shift = 50
|
|
23
|
-
const displayResults = await Promise.all(
|
|
24
|
-
tracks.map(async track => {
|
|
25
|
-
const display = track.displays[0]
|
|
26
|
-
await when(() => (display.ready !== undefined ? display.ready : true))
|
|
27
|
-
return { track, result: await display.renderSvg({ ...opts, theme }) }
|
|
28
|
-
}),
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
const { staticSlices, offsetRadians, centerXY } = model
|
|
32
|
-
const deg = radToDeg(offsetRadians)
|
|
33
|
-
|
|
34
|
-
// the xlink namespace is used for rendering <image> tag
|
|
35
|
-
return renderToStaticMarkup(
|
|
36
|
-
<ThemeProvider theme={createJBrowseTheme(theme)}>
|
|
37
|
-
<Wrapper>
|
|
38
|
-
<svg
|
|
39
|
-
width={width}
|
|
40
|
-
height={height}
|
|
41
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
42
|
-
xmlnsXlink="http://www.w3.org/1999/xlink"
|
|
43
|
-
viewBox={[0, 0, width + shift * 2, height].toString()}
|
|
44
|
-
>
|
|
45
|
-
<SVGBackground width={width} height={height} shift={shift} />
|
|
46
|
-
<g transform={`translate(${centerXY}) rotate(${deg})`}>
|
|
47
|
-
{staticSlices.map((slice, i) => (
|
|
48
|
-
<Ruler key={i} model={model} slice={slice} />
|
|
49
|
-
))}
|
|
50
|
-
{displayResults.map(({ result }, i) => (
|
|
51
|
-
<React.Fragment key={i}>{result}</React.Fragment>
|
|
52
|
-
))}
|
|
53
|
-
</g>
|
|
54
|
-
</svg>
|
|
55
|
-
</Wrapper>
|
|
56
|
-
</ThemeProvider>,
|
|
57
|
-
)
|
|
58
|
-
}
|