@jbrowse/plugin-circular-view 1.6.8 → 1.7.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.
- package/dist/BaseChordDisplay/components/BaseChordDisplay.d.ts +9 -9
- package/dist/BaseChordDisplay/components/BaseChordDisplay.js +53 -0
- package/dist/BaseChordDisplay/components/DisplayError.d.ts +4 -4
- package/dist/BaseChordDisplay/components/DisplayError.js +70 -0
- package/dist/BaseChordDisplay/components/Loading.d.ts +10 -10
- package/dist/BaseChordDisplay/components/Loading.js +122 -0
- package/dist/BaseChordDisplay/components/RpcRenderedSvgGroup.d.ts +4 -4
- package/dist/BaseChordDisplay/components/RpcRenderedSvgGroup.js +96 -0
- package/dist/BaseChordDisplay/index.d.ts +3 -3
- package/dist/BaseChordDisplay/index.js +31 -0
- package/dist/BaseChordDisplay/models/BaseChordDisplayModel.d.ts +99 -99
- package/dist/BaseChordDisplay/models/BaseChordDisplayModel.js +261 -0
- package/dist/BaseChordDisplay/models/baseChordDisplayConfig.d.ts +2 -2
- package/dist/BaseChordDisplay/models/baseChordDisplayConfig.js +20 -0
- package/dist/BaseChordDisplay/models/renderReaction.d.ts +30 -30
- package/dist/BaseChordDisplay/models/renderReaction.js +142 -0
- package/dist/CircularView/components/CircularView.d.ts +4 -4
- package/dist/CircularView/components/CircularView.js +203 -0
- package/dist/CircularView/components/ImportForm.d.ts +5 -5
- package/dist/CircularView/components/ImportForm.js +97 -0
- package/dist/CircularView/components/Ruler.d.ts +5 -5
- package/dist/CircularView/components/Ruler.js +205 -0
- package/dist/CircularView/models/CircularView.d.ts +97 -97
- package/dist/CircularView/models/CircularView.js +422 -0
- package/dist/CircularView/models/slices.d.ts +27 -27
- package/dist/CircularView/models/slices.js +111 -0
- package/dist/CircularView/models/slices.test.js +83 -0
- package/dist/CircularView/models/viewportVisibleRegion.d.ts +6 -6
- package/dist/CircularView/models/viewportVisibleRegion.js +280 -0
- package/dist/CircularView/models/viewportVisibleRegion.test.js +130 -0
- package/dist/index.d.ts +8 -8
- package/dist/index.js +180 -6
- package/package.json +5 -7
- package/dist/CircularView/models/slices.test.d.ts +0 -1
- package/dist/CircularView/models/viewportVisibleRegion.test.d.ts +0 -1
- package/dist/plugin-circular-view.cjs.development.js +0 -3013
- package/dist/plugin-circular-view.cjs.development.js.map +0 -1
- package/dist/plugin-circular-view.cjs.production.min.js +0 -2
- package/dist/plugin-circular-view.cjs.production.min.js.map +0 -1
- package/dist/plugin-circular-view.esm.js +0 -3004
- package/dist/plugin-circular-view.esm.js.map +0 -1
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
|
|
5
|
+
Object.defineProperty(exports, "__esModule", {
|
|
6
|
+
value: true
|
|
7
|
+
});
|
|
8
|
+
exports.Slice = void 0;
|
|
9
|
+
exports.calculateStaticSlices = calculateStaticSlices;
|
|
10
|
+
exports.sliceIsVisible = sliceIsVisible;
|
|
11
|
+
|
|
12
|
+
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
|
|
13
|
+
|
|
14
|
+
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
|
|
15
|
+
|
|
16
|
+
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
|
|
17
|
+
|
|
18
|
+
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
19
|
+
|
|
20
|
+
var _util = require("@jbrowse/core/util");
|
|
21
|
+
|
|
22
|
+
var _viewportVisibleRegion = require("./viewportVisibleRegion");
|
|
23
|
+
|
|
24
|
+
function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; }
|
|
25
|
+
|
|
26
|
+
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
|
|
27
|
+
|
|
28
|
+
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
|
|
29
|
+
|
|
30
|
+
var Slice = /*#__PURE__*/function () {
|
|
31
|
+
function Slice(view, region, currentRadianOffset, radianWidth) {
|
|
32
|
+
(0, _classCallCheck2["default"])(this, Slice);
|
|
33
|
+
this.region = region;
|
|
34
|
+
this.radianWidth = radianWidth;
|
|
35
|
+
(0, _defineProperty2["default"])(this, "key", void 0);
|
|
36
|
+
(0, _defineProperty2["default"])(this, "offsetRadians", void 0);
|
|
37
|
+
(0, _defineProperty2["default"])(this, "startRadians", void 0);
|
|
38
|
+
(0, _defineProperty2["default"])(this, "endRadians", void 0);
|
|
39
|
+
(0, _defineProperty2["default"])(this, "bpPerRadian", void 0);
|
|
40
|
+
(0, _defineProperty2["default"])(this, "flipped", void 0);
|
|
41
|
+
var bpPerRadian = view.bpPerRadian;
|
|
42
|
+
this.key = (0, _util.assembleLocString)(region);
|
|
43
|
+
this.offsetRadians = currentRadianOffset;
|
|
44
|
+
this.bpPerRadian = bpPerRadian;
|
|
45
|
+
this.flipped = false;
|
|
46
|
+
this.startRadians = this.offsetRadians;
|
|
47
|
+
this.endRadians = region.widthBp / this.bpPerRadian + this.offsetRadians;
|
|
48
|
+
Object.freeze(this);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
(0, _createClass2["default"])(Slice, [{
|
|
52
|
+
key: "bpToXY",
|
|
53
|
+
value: function bpToXY(bp, radiusPx) {
|
|
54
|
+
var offsetBp;
|
|
55
|
+
|
|
56
|
+
if (this.region.elided) {
|
|
57
|
+
offsetBp = this.region.widthBp / 2;
|
|
58
|
+
} else if (this.flipped) {
|
|
59
|
+
offsetBp = this.region.end - bp;
|
|
60
|
+
} else {
|
|
61
|
+
offsetBp = bp - this.region.start;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
var totalRadians = offsetBp / this.bpPerRadian + this.offsetRadians;
|
|
65
|
+
return (0, _util.polarToCartesian)(radiusPx, totalRadians);
|
|
66
|
+
}
|
|
67
|
+
}, {
|
|
68
|
+
key: "toJSON",
|
|
69
|
+
value: function toJSON() {
|
|
70
|
+
return (0, _util.objectFromEntries)(Object.entries(this));
|
|
71
|
+
}
|
|
72
|
+
}]);
|
|
73
|
+
return Slice;
|
|
74
|
+
}(); // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
exports.Slice = Slice;
|
|
78
|
+
|
|
79
|
+
function calculateStaticSlices(self) {
|
|
80
|
+
var slices = [];
|
|
81
|
+
var currentRadianOffset = 0;
|
|
82
|
+
|
|
83
|
+
var _iterator = _createForOfIteratorHelper(self.elidedRegions),
|
|
84
|
+
_step;
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
88
|
+
var _region = _step.value;
|
|
89
|
+
|
|
90
|
+
var _radianWidth = _region.widthBp / self.bpPerRadian + self.spacingPx / self.pxPerRadian;
|
|
91
|
+
|
|
92
|
+
slices.push(new Slice(self, _region, currentRadianOffset, _radianWidth));
|
|
93
|
+
currentRadianOffset += _radianWidth;
|
|
94
|
+
}
|
|
95
|
+
} catch (err) {
|
|
96
|
+
_iterator.e(err);
|
|
97
|
+
} finally {
|
|
98
|
+
_iterator.f();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return slices;
|
|
102
|
+
} // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
function sliceIsVisible(self, slice) {
|
|
106
|
+
var _self$visibleSection$ = (0, _slicedToArray2["default"])(self.visibleSection.theta, 2),
|
|
107
|
+
visibleThetaMin = _self$visibleSection$[0],
|
|
108
|
+
visibleThetaMax = _self$visibleSection$[1];
|
|
109
|
+
|
|
110
|
+
return (0, _viewportVisibleRegion.thetaRangesOverlap)(slice.offsetRadians + self.offsetRadians, slice.radianWidth, visibleThetaMin, visibleThetaMax - visibleThetaMin);
|
|
111
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
|
|
5
|
+
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
|
|
6
|
+
|
|
7
|
+
var _slices2 = require("./slices");
|
|
8
|
+
|
|
9
|
+
test('one slice', function () {
|
|
10
|
+
var view = {
|
|
11
|
+
elidedRegions: [{
|
|
12
|
+
refName: 'toast',
|
|
13
|
+
start: 0,
|
|
14
|
+
end: 10000,
|
|
15
|
+
widthBp: 10000
|
|
16
|
+
}],
|
|
17
|
+
spacingPx: 5,
|
|
18
|
+
radiusPx: 1000,
|
|
19
|
+
totalBp: 10000,
|
|
20
|
+
bpPerRadian: 10000 / (2 * Math.PI),
|
|
21
|
+
pxPerRadian: 1000
|
|
22
|
+
};
|
|
23
|
+
var slices = (0, _slices2.calculateStaticSlices)(view); // console.log(slices)
|
|
24
|
+
|
|
25
|
+
expect(slices.length).toBe(1);
|
|
26
|
+
|
|
27
|
+
var _slices = (0, _slicedToArray2["default"])(slices, 1),
|
|
28
|
+
slice = _slices[0]; // expect(slices).toMatchSnapshot()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
expect(slice).toMatchSnapshot();
|
|
32
|
+
});
|
|
33
|
+
test('two slices', function () {
|
|
34
|
+
var view = {
|
|
35
|
+
elidedRegions: [{
|
|
36
|
+
refName: 'toast',
|
|
37
|
+
start: 0,
|
|
38
|
+
end: 10000,
|
|
39
|
+
widthBp: 10000
|
|
40
|
+
}, {
|
|
41
|
+
refName: 'teest',
|
|
42
|
+
start: 0,
|
|
43
|
+
end: 10000,
|
|
44
|
+
widthBp: 10000
|
|
45
|
+
}],
|
|
46
|
+
spacingPx: 5,
|
|
47
|
+
radiusPx: 1000,
|
|
48
|
+
pxPerRadian: 1000,
|
|
49
|
+
totalBp: 20000,
|
|
50
|
+
bpPerRadian: 20000 / (2 * Math.PI)
|
|
51
|
+
};
|
|
52
|
+
var slices = (0, _slices2.calculateStaticSlices)(view); // console.log(slices)
|
|
53
|
+
|
|
54
|
+
expect(slices.length).toBe(2);
|
|
55
|
+
expect(slices).toMatchSnapshot();
|
|
56
|
+
});
|
|
57
|
+
test('volvox', function () {
|
|
58
|
+
var totalBp = 50001 + 6079;
|
|
59
|
+
var view = {
|
|
60
|
+
elidedRegions: [{
|
|
61
|
+
refName: 'ctgA',
|
|
62
|
+
start: 0,
|
|
63
|
+
end: 50001,
|
|
64
|
+
assemblyName: 'volvox',
|
|
65
|
+
widthBp: 50001
|
|
66
|
+
}, {
|
|
67
|
+
refName: 'ctgB',
|
|
68
|
+
start: 0,
|
|
69
|
+
end: 6079,
|
|
70
|
+
assemblyName: 'volvox',
|
|
71
|
+
widthBp: 6079
|
|
72
|
+
}],
|
|
73
|
+
spacingPx: 5,
|
|
74
|
+
radiusPx: 1000,
|
|
75
|
+
pxPerRadian: 1000,
|
|
76
|
+
totalBp: totalBp,
|
|
77
|
+
bpPerRadian: totalBp / (2 * Math.PI)
|
|
78
|
+
};
|
|
79
|
+
var slices = (0, _slices2.calculateStaticSlices)(view); // console.log(slices)
|
|
80
|
+
|
|
81
|
+
expect(slices.length).toBe(2);
|
|
82
|
+
expect(slices).toMatchSnapshot();
|
|
83
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export declare function cartesianToPolar(x: number, y: number): number[];
|
|
2
|
-
export declare function thetaRangesOverlap(r1start: number, r1length: number, r2start: number, r2length: number): boolean;
|
|
3
|
-
export declare function viewportVisibleSection(viewSides: [number, number, number, number], circleCenter: [number, number], circleRadius: number): {
|
|
4
|
-
rho: number[];
|
|
5
|
-
theta: number[];
|
|
6
|
-
};
|
|
1
|
+
export declare function cartesianToPolar(x: number, y: number): number[];
|
|
2
|
+
export declare function thetaRangesOverlap(r1start: number, r1length: number, r2start: number, r2length: number): boolean;
|
|
3
|
+
export declare function viewportVisibleSection(viewSides: [number, number, number, number], circleCenter: [number, number], circleRadius: number): {
|
|
4
|
+
rho: number[];
|
|
5
|
+
theta: number[];
|
|
6
|
+
};
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
|
|
5
|
+
Object.defineProperty(exports, "__esModule", {
|
|
6
|
+
value: true
|
|
7
|
+
});
|
|
8
|
+
exports.cartesianToPolar = cartesianToPolar;
|
|
9
|
+
exports.thetaRangesOverlap = thetaRangesOverlap;
|
|
10
|
+
exports.viewportVisibleSection = viewportVisibleSection;
|
|
11
|
+
|
|
12
|
+
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
|
|
13
|
+
|
|
14
|
+
function findCircleIntersectionX(y, cx, cy, r, resultArray) {
|
|
15
|
+
var d = Math.abs(y - cy);
|
|
16
|
+
|
|
17
|
+
if (d > r) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (d === r) {
|
|
22
|
+
resultArray.push([cx, y]);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
var solution = Math.sqrt(r * r - d * d);
|
|
26
|
+
resultArray.push([cx - solution, y]);
|
|
27
|
+
resultArray.push([cx + solution, y]);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function findCircleIntersectionY(x, cx, cy, r, resultArray) {
|
|
31
|
+
var d = Math.abs(x - cx);
|
|
32
|
+
|
|
33
|
+
if (d > r) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (d === r) {
|
|
38
|
+
resultArray.push([x, cy]);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
var solution = Math.sqrt(r * r - d * d);
|
|
42
|
+
resultArray.push([x, cy - solution]);
|
|
43
|
+
resultArray.push([x, cy + solution]);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function cartesianToTheta(x, y) {
|
|
47
|
+
var theta = (Math.atan(y / x) + 2 * Math.PI) % (2 * Math.PI);
|
|
48
|
+
|
|
49
|
+
if (x < 0) {
|
|
50
|
+
if (y <= 0) {
|
|
51
|
+
theta += Math.PI;
|
|
52
|
+
} else {
|
|
53
|
+
theta -= Math.PI;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return theta;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function cartesianToPolar(x, y) {
|
|
61
|
+
var rho = Math.sqrt(x * x + y * y);
|
|
62
|
+
|
|
63
|
+
if (rho === 0) {
|
|
64
|
+
return [0, 0];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
var theta = cartesianToTheta(x, y);
|
|
68
|
+
return [rho, theta];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
var twoPi = 2 * Math.PI;
|
|
72
|
+
|
|
73
|
+
function thetaRangesOverlap(r1start, r1length, r2start, r2length) {
|
|
74
|
+
if (r1length <= 0 || r2length <= 0) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (r1length + 0.0001 >= twoPi || r2length + 0.0001 >= twoPi) {
|
|
79
|
+
return true;
|
|
80
|
+
} // put both range starts between 2π and 4π
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
r1start = (r1start % twoPi + twoPi) % twoPi + twoPi;
|
|
84
|
+
r2start = (r2start % twoPi + twoPi) % twoPi + twoPi;
|
|
85
|
+
|
|
86
|
+
if (r1start < r2start + r2length && r1start + r1length > r2start) {
|
|
87
|
+
return true;
|
|
88
|
+
} // move r2 2π to the left and check
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
r2start -= twoPi;
|
|
92
|
+
|
|
93
|
+
if (r1start < r2start + r2length && r1start + r1length > r2start) {
|
|
94
|
+
return true;
|
|
95
|
+
} // move it 2π to the right and check
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
r2start += twoPi + twoPi;
|
|
99
|
+
return r1start < r2start + r2length && r1start + r1length > r2start;
|
|
100
|
+
} // return which arc range has any part of the circle visible in the viewport
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
function viewportVisibleSection(viewSides, circleCenter, circleRadius) {
|
|
104
|
+
var _viewSides = (0, _slicedToArray2["default"])(viewSides, 4),
|
|
105
|
+
viewL = _viewSides[0],
|
|
106
|
+
viewR = _viewSides[1],
|
|
107
|
+
viewT = _viewSides[2],
|
|
108
|
+
viewB = _viewSides[3];
|
|
109
|
+
|
|
110
|
+
var _circleCenter = (0, _slicedToArray2["default"])(circleCenter, 2),
|
|
111
|
+
cx = _circleCenter[0],
|
|
112
|
+
cy = _circleCenter[1]; // transform coordinate system to center of circle
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
viewL -= cx;
|
|
116
|
+
viewR -= cx;
|
|
117
|
+
viewT -= cy;
|
|
118
|
+
viewB -= cy;
|
|
119
|
+
var centerIsInsideViewport = viewL < 0 && viewR > 0 && viewT < 0 && viewB > 0;
|
|
120
|
+
|
|
121
|
+
if (centerIsInsideViewport) {
|
|
122
|
+
var _vertices = [[viewL, viewT], [viewR, viewT], [viewL, viewB], [viewR, viewB]];
|
|
123
|
+
var maxRho = -Infinity;
|
|
124
|
+
|
|
125
|
+
for (var i = 0; i < _vertices.length; i += 1) {
|
|
126
|
+
var _vertices$i = (0, _slicedToArray2["default"])(_vertices[i], 2),
|
|
127
|
+
x = _vertices$i[0],
|
|
128
|
+
y = _vertices$i[1];
|
|
129
|
+
|
|
130
|
+
var rho = Math.sqrt(x * x + y * y);
|
|
131
|
+
|
|
132
|
+
if (rho > maxRho) {
|
|
133
|
+
maxRho = rho;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
rho: [0, Math.min(circleRadius, maxRho)],
|
|
139
|
+
theta: [0, 2 * Math.PI]
|
|
140
|
+
};
|
|
141
|
+
} // const viewportCompletelyContainsCircle =
|
|
142
|
+
// circleCenter[0] - viewL >= circleRadius &&
|
|
143
|
+
// viewR - circleCenter[0] >= circleRadius &&
|
|
144
|
+
// circleCenter[1] - viewT >= circleRadius &&
|
|
145
|
+
// viewB - circleCenter[1] >= circleRadius
|
|
146
|
+
// if (viewportCompletelyContainsCircle) {
|
|
147
|
+
// return [0, 2 * Math.PI]
|
|
148
|
+
// }
|
|
149
|
+
// const distToCenterSquared = ([x, y]) => {
|
|
150
|
+
// const [cx, cy] = circleCenter
|
|
151
|
+
// const sq = n => n * n
|
|
152
|
+
// return sq(x - cx) + sq(y - cy)
|
|
153
|
+
// }
|
|
154
|
+
// const circleRadiusSquared = circleRadius * circleRadius
|
|
155
|
+
// const tlInside = distToCenterSquared([viewL, viewT]) <= circleRadiusSquared
|
|
156
|
+
// const trInside = distToCenterSquared([viewR, viewT]) <= circleRadiusSquared
|
|
157
|
+
// const blInside = distToCenterSquared([viewL, viewB]) <= circleRadiusSquared
|
|
158
|
+
// const brInside = distToCenterSquared([viewR, viewB]) <= circleRadiusSquared
|
|
159
|
+
// const noIntersection = !tlInside && !trInside && !blInside && !brInside
|
|
160
|
+
// if (noIntersection) return undefined
|
|
161
|
+
// const circleCompletelyContainsViewport =
|
|
162
|
+
// tlInside && trInside && blInside && brInside
|
|
163
|
+
// if (circleCompletelyContainsViewport) {
|
|
164
|
+
// // viewport is in the circle, but the center is not in it, so take max
|
|
165
|
+
// // and min of thetas to the center
|
|
166
|
+
// const thetas = [
|
|
167
|
+
// Math.atan(viewT / viewL),
|
|
168
|
+
// Math.atan(viewT / viewR),
|
|
169
|
+
// Math.atan(viewB / viewL),
|
|
170
|
+
// Math.atan(viewB / viewR),
|
|
171
|
+
// ]
|
|
172
|
+
// return [Math.min(...thetas), Math.max(...thetas)]
|
|
173
|
+
// }
|
|
174
|
+
// if we get here, the viewport is partly in, partly out of the circle
|
|
175
|
+
// const viewLIntersects = Math.abs(viewL - circleCenter[0]) <= circleRadius
|
|
176
|
+
// const viewRIntersects = Math.abs(viewR - circleCenter[0]) <= circleRadius
|
|
177
|
+
// const viewTIntersects = Math.abs(viewT - circleCenter[1]) <= circleRadius
|
|
178
|
+
// const viewBIntersects = Math.abs(viewB - circleCenter[1]) <= circleRadius
|
|
179
|
+
// const numIntersectingSides =
|
|
180
|
+
// Number(viewLIntersects) +
|
|
181
|
+
// Number(viewRIntersects) +
|
|
182
|
+
// Number(viewTIntersects) +
|
|
183
|
+
// Number(viewBIntersects)
|
|
184
|
+
// if (numIntersectingSides === 4) return [0, 2 * Math.PI]
|
|
185
|
+
// if (numIntersectingSides === 3) {
|
|
186
|
+
// // TODO calculate the thetas of the
|
|
187
|
+
// } else if (numIntersectingSides === 2) {
|
|
188
|
+
// // TODO calculate the thetas of the 2 intersection points
|
|
189
|
+
// } else if (numIntersectingSides === 1) {
|
|
190
|
+
// // TODO calculate the thetas of the 1-2 intersection points of the line, and the angle between
|
|
191
|
+
// }
|
|
192
|
+
// make a list of vertices-of-interest that lie inside both shapes to examine
|
|
193
|
+
// to determine the range covered by their intersection
|
|
194
|
+
// transform coordinates to have the circle as the origin and find the intersections
|
|
195
|
+
// of the circle and the view rectangle
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
var vertices = [[viewL, viewT], [viewR, viewT], [viewL, viewB], [viewR, viewB]];
|
|
199
|
+
findCircleIntersectionY(viewL, 0, 0, circleRadius, vertices);
|
|
200
|
+
findCircleIntersectionY(viewR, 0, 0, circleRadius, vertices);
|
|
201
|
+
findCircleIntersectionX(viewT, 0, 0, circleRadius, vertices);
|
|
202
|
+
findCircleIntersectionX(viewB, 0, 0, circleRadius, vertices); // for each edge, also look at the closest point to center if it is inside the circle
|
|
203
|
+
|
|
204
|
+
if (-viewL < circleRadius) {
|
|
205
|
+
vertices.push([viewL, 0]);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (viewR < circleRadius) {
|
|
209
|
+
vertices.push([viewR, 0]);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (-viewT < circleRadius) {
|
|
213
|
+
vertices.push([0, viewT]);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (viewB < circleRadius) {
|
|
217
|
+
vertices.push([0, viewB]);
|
|
218
|
+
} // const verticesOriginal = vertices.map(([x, y]) => [x + cx, y + cy])
|
|
219
|
+
// now convert them all to polar and take the max and min of rho and theta
|
|
220
|
+
// const viewportCenterTheta = cartesianToTheta(viewR + viewL, viewT + viewB)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
var reflect = viewL >= 0 ? -1 : 1; // viewportCenterTheta < Math.PI / 2 || viewportCenterTheta > 1.5 * Math.PI
|
|
224
|
+
// ? -1
|
|
225
|
+
// : 1
|
|
226
|
+
|
|
227
|
+
var rhoMin = Infinity;
|
|
228
|
+
var rhoMax = -Infinity;
|
|
229
|
+
var thetaMin = Infinity;
|
|
230
|
+
var thetaMax = -Infinity;
|
|
231
|
+
|
|
232
|
+
for (var _i = 0; _i < vertices.length; _i += 1) {
|
|
233
|
+
// ignore vertex if outside the viewport
|
|
234
|
+
var _vertices$_i = (0, _slicedToArray2["default"])(vertices[_i], 2),
|
|
235
|
+
vx = _vertices$_i[0],
|
|
236
|
+
vy = _vertices$_i[1];
|
|
237
|
+
|
|
238
|
+
if (vx >= viewL && vx <= viewR && vy >= viewT && vy <= viewB) {
|
|
239
|
+
var _cartesianToPolar = cartesianToPolar(vx * reflect, vy * reflect),
|
|
240
|
+
_cartesianToPolar2 = (0, _slicedToArray2["default"])(_cartesianToPolar, 2),
|
|
241
|
+
_rho = _cartesianToPolar2[0],
|
|
242
|
+
theta = _cartesianToPolar2[1]; // ignore vertex if outside the circle
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
if (_rho <= circleRadius + 0.001) {
|
|
246
|
+
// ignore theta if rho is 0
|
|
247
|
+
if (theta < thetaMin && _rho > 0.0001) {
|
|
248
|
+
thetaMin = theta;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (theta > thetaMax && _rho > 0.0001) {
|
|
252
|
+
thetaMax = theta;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (_rho < rhoMin) {
|
|
256
|
+
rhoMin = _rho;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (_rho > rhoMax) {
|
|
260
|
+
rhoMax = _rho;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (reflect === -1) {
|
|
267
|
+
thetaMin += Math.PI;
|
|
268
|
+
thetaMax += Math.PI;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (thetaMin > 2 * Math.PI && thetaMax > 2 * Math.PI) {
|
|
272
|
+
thetaMin -= 2 * Math.PI;
|
|
273
|
+
thetaMax -= 2 * Math.PI;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return {
|
|
277
|
+
rho: [rhoMin, Math.min(circleRadius, rhoMax)],
|
|
278
|
+
theta: [thetaMin, thetaMax]
|
|
279
|
+
};
|
|
280
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
|
|
5
|
+
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
|
|
6
|
+
|
|
7
|
+
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
|
|
8
|
+
|
|
9
|
+
var _viewportVisibleRegion = require("./viewportVisibleRegion");
|
|
10
|
+
|
|
11
|
+
describe('viewportVisibleSection', function () {
|
|
12
|
+
// test('circle contained in viewport', () => {
|
|
13
|
+
// const result = viewportVisibleSection([0, 1, 0, 1], [0.5, 0.5], 0.3)
|
|
14
|
+
// expect(result).toEqual({ rho: [0, 0.3], theta: [0, 2 * Math.PI] })
|
|
15
|
+
// })
|
|
16
|
+
// test('viewport completely inside circle', () => {
|
|
17
|
+
// const result = viewportVisibleSection([0, 1, 0, 1], [0.5, 0.5], 20)
|
|
18
|
+
// expect(result.theta).toEqual([0, 2 * Math.PI])
|
|
19
|
+
// expect(result.rho[0]).toEqual(0)
|
|
20
|
+
// expect(result.rho[1]).toBeCloseTo(0.7071)
|
|
21
|
+
// })
|
|
22
|
+
// test('viewport on left half of circle', () => {
|
|
23
|
+
// const result = viewportVisibleSection([200, 500, 0, 1000], [500, 500], 20)
|
|
24
|
+
// expect(result).toEqual({
|
|
25
|
+
// rho: [0, 20],
|
|
26
|
+
// theta: [Math.PI / 2, 1.5 * Math.PI],
|
|
27
|
+
// })
|
|
28
|
+
// })
|
|
29
|
+
// test('viewport on right half of circle', () => {
|
|
30
|
+
// const result = viewportVisibleSection([200, 500, 0, 1000], [200, 500], 20)
|
|
31
|
+
// expect(result).toEqual({
|
|
32
|
+
// rho: [0, 20],
|
|
33
|
+
// theta: [1.5 * Math.PI, 2.5 * Math.PI],
|
|
34
|
+
// })
|
|
35
|
+
// })
|
|
36
|
+
// test('viewport corner in circle', () => {
|
|
37
|
+
// const { theta, rho } = viewportVisibleSection(
|
|
38
|
+
// [200, 500, 0, 700],
|
|
39
|
+
// [199, 701],
|
|
40
|
+
// 100,
|
|
41
|
+
// )
|
|
42
|
+
// expect(rho).toEqual([1.4142135623730951, 100])
|
|
43
|
+
// expect(theta[0]).toBeCloseTo(1.5 * Math.PI, 1)
|
|
44
|
+
// expect(theta[1]).toBeCloseTo(2 * Math.PI, 1)
|
|
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
|
+
// test('viewport on center right 2', () => {
|
|
58
|
+
// const { theta, rho } = viewportVisibleSection(
|
|
59
|
+
// [1816, 2937, 1074, 1474],
|
|
60
|
+
// [1468.6015446723616, 1468.6015446723616],
|
|
61
|
+
// 1448.6015446723616,
|
|
62
|
+
// )
|
|
63
|
+
// expect(theta[1]).toBeGreaterThan(2 * Math.PI)
|
|
64
|
+
// expect(theta[1]).toBeLessThan(2.5 * Math.PI)
|
|
65
|
+
// expect(theta[0]).toBeGreaterThan(1.5 * Math.PI)
|
|
66
|
+
// expect(theta[0]).toBeLessThan(2 * Math.PI)
|
|
67
|
+
// })
|
|
68
|
+
// test('viewport on lower center', () => {
|
|
69
|
+
// const { theta, rho } = viewportVisibleSection(
|
|
70
|
+
// [259, 1350, 1176, 1576],
|
|
71
|
+
// [787.7952717090081, 787.7952717090081],
|
|
72
|
+
// 767.7952717090081,
|
|
73
|
+
// )
|
|
74
|
+
// expect(theta[1]).toBeGreaterThan(Math.PI / 2)
|
|
75
|
+
// expect(theta[1]).toBeLessThan(Math.PI)
|
|
76
|
+
// expect(theta[0]).toBeGreaterThan(0)
|
|
77
|
+
// expect(theta[0]).toBeLessThan(Math.PI / 2)
|
|
78
|
+
// })
|
|
79
|
+
// test('viewport on upper center', () => {
|
|
80
|
+
// const { theta, rho } = viewportVisibleSection(
|
|
81
|
+
// [286, 1377, 0, 400],
|
|
82
|
+
// [787.7952717090081, 787.7952717090081],
|
|
83
|
+
// 767.7952717090081,
|
|
84
|
+
// )
|
|
85
|
+
// expect(theta[1]).toBeGreaterThan(1.5 * Math.PI)
|
|
86
|
+
// expect(theta[1]).toBeLessThan(2 * Math.PI)
|
|
87
|
+
// expect(theta[0]).toBeGreaterThan(Math.PI)
|
|
88
|
+
// expect(theta[0]).toBeLessThan(1.5 * Math.PI)
|
|
89
|
+
// })
|
|
90
|
+
test('viewport on upper center 2', function () {
|
|
91
|
+
// [180.48708681644143, 359.3411680673888] [4.6042679453532855, 541.6042679453533]
|
|
92
|
+
// see '~/Desktop/Screen Shot 2019-06-28 at 3.01.22 PM.png'
|
|
93
|
+
var _viewportVisibleSecti = (0, _viewportVisibleRegion.viewportVisibleSection)([0, 962, 157, 557], [561.6042679453533, 561.6042679453533], 541.6042679453533),
|
|
94
|
+
theta = _viewportVisibleSecti.theta;
|
|
95
|
+
|
|
96
|
+
expect(theta[1]).toBeGreaterThan(1.75 * Math.PI);
|
|
97
|
+
expect(theta[1]).toBeLessThan(2 * Math.PI);
|
|
98
|
+
expect(theta[0]).toBeGreaterThan(Math.PI);
|
|
99
|
+
expect(theta[0]).toBeLessThan(1.1 * Math.PI);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
describe('cartesian to polar', function () {
|
|
103
|
+
;
|
|
104
|
+
[[[-1, -1], [1.414, 180 + 45]], [[-1, 1], [1.414, 90 + 45]], [[1, 1], [1.414, 45]], [[1, -1], [1.414, 360 - 45]], [[0, 1], [1, 90]], [[0, -1], [1, 270]], [[-1, 0], [1, 180]], [[1, 0], [1, 0]]].forEach(function (testCase) {
|
|
105
|
+
var _testCase = (0, _slicedToArray2["default"])(testCase, 2),
|
|
106
|
+
input = _testCase[0],
|
|
107
|
+
output = _testCase[1];
|
|
108
|
+
|
|
109
|
+
test("".concat(input, " -> ").concat(output), function () {
|
|
110
|
+
var result = _viewportVisibleRegion.cartesianToPolar.apply(void 0, (0, _toConsumableArray2["default"])(input));
|
|
111
|
+
|
|
112
|
+
expect(result[0]).toBeCloseTo(output[0]);
|
|
113
|
+
expect(result[1] * 180 / Math.PI).toBeCloseTo(output[1]);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
describe('theta overlap testing', function () {
|
|
118
|
+
;
|
|
119
|
+
[[[0, 2 * Math.PI, 0, 2 * Math.PI], true], [[6.1, Math.PI / 2, 0, Math.PI / 2], true], [[6.1, Math.PI / 2, 0, 0.1], true], [[6.1, 0.1, 6.12, 0.05], true], [[-12, 0.1, -12.05, 0.05], false], [[-12, 0.1, -12.05, 0.06], true]].forEach(function (testCase) {
|
|
120
|
+
var _testCase2 = (0, _slicedToArray2["default"])(testCase, 2),
|
|
121
|
+
input = _testCase2[0],
|
|
122
|
+
output = _testCase2[1];
|
|
123
|
+
|
|
124
|
+
test("".concat(input, " -> ").concat(output), function () {
|
|
125
|
+
var result = _viewportVisibleRegion.thetaRangesOverlap.apply(void 0, (0, _toConsumableArray2["default"])(input));
|
|
126
|
+
|
|
127
|
+
expect(result).toBe(output);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
});
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import PluginManager from '@jbrowse/core/PluginManager';
|
|
2
|
-
import Plugin from '@jbrowse/core/Plugin';
|
|
3
|
-
export default class CircularViewPlugin extends Plugin {
|
|
4
|
-
name: string;
|
|
5
|
-
install(pluginManager: PluginManager): void;
|
|
6
|
-
configure(pluginManager: PluginManager): void;
|
|
7
|
-
}
|
|
8
|
-
export { BaseChordDisplayModel, baseChordDisplayConfig, BaseChordDisplayComponentFactory, } from './BaseChordDisplay';
|
|
1
|
+
import PluginManager from '@jbrowse/core/PluginManager';
|
|
2
|
+
import Plugin from '@jbrowse/core/Plugin';
|
|
3
|
+
export default class CircularViewPlugin extends Plugin {
|
|
4
|
+
name: string;
|
|
5
|
+
install(pluginManager: PluginManager): void;
|
|
6
|
+
configure(pluginManager: PluginManager): void;
|
|
7
|
+
}
|
|
8
|
+
export { BaseChordDisplayModel, baseChordDisplayConfig, BaseChordDisplayComponentFactory, } from './BaseChordDisplay';
|