@jbrowse/plugin-dotplot-view 2.15.0 → 2.15.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/DotplotDisplay/stateModelFactory.d.ts +6 -1
- package/dist/DotplotRenderer/DotplotRenderer.d.ts +1 -9
- package/dist/DotplotRenderer/DotplotRenderer.js +25 -214
- package/dist/DotplotRenderer/drawDotplot.d.ts +21 -0
- package/dist/DotplotRenderer/drawDotplot.js +216 -0
- package/dist/DotplotView/components/{DotplotTooltip.d.ts → DotplotTooltipClick.d.ts} +2 -8
- package/dist/DotplotView/components/DotplotTooltipClick.js +21 -0
- package/dist/DotplotView/components/DotplotTooltipMouseover.d.ts +10 -0
- package/dist/DotplotView/components/DotplotTooltipMouseover.js +20 -0
- package/dist/DotplotView/components/DotplotView.js +6 -3
- package/dist/DotplotView/components/ImportForm/ImportCustomTrack.d.ts +2 -2
- package/dist/DotplotView/components/ImportForm/ImportCustomTrack.js +27 -6
- package/dist/DotplotView/model.d.ts +1 -0
- package/esm/DotplotDisplay/stateModelFactory.d.ts +6 -1
- package/esm/DotplotRenderer/DotplotRenderer.d.ts +1 -9
- package/esm/DotplotRenderer/DotplotRenderer.js +2 -214
- package/esm/DotplotRenderer/drawDotplot.d.ts +21 -0
- package/esm/DotplotRenderer/drawDotplot.js +213 -0
- package/esm/DotplotView/components/{DotplotTooltip.d.ts → DotplotTooltipClick.d.ts} +2 -8
- package/esm/DotplotView/components/DotplotTooltipClick.js +15 -0
- package/esm/DotplotView/components/DotplotTooltipMouseover.d.ts +10 -0
- package/esm/DotplotView/components/DotplotTooltipMouseover.js +15 -0
- package/esm/DotplotView/components/DotplotView.js +7 -4
- package/esm/DotplotView/components/ImportForm/ImportCustomTrack.d.ts +2 -2
- package/esm/DotplotView/components/ImportForm/ImportCustomTrack.js +27 -6
- package/esm/DotplotView/model.d.ts +1 -0
- package/package.json +2 -2
- package/dist/DotplotView/components/DotplotTooltip.js +0 -84
- package/esm/DotplotView/components/DotplotTooltip.js +0 -78
|
@@ -28,7 +28,7 @@ const material_1 = require("@mui/material");
|
|
|
28
28
|
const ui_1 = require("@jbrowse/core/ui");
|
|
29
29
|
const mobx_react_1 = require("mobx-react");
|
|
30
30
|
const util_1 = require("./util");
|
|
31
|
-
function getAdapter({ radioOption, assembly1, assembly2, fileLocation, bed1Location, bed2Location, }) {
|
|
31
|
+
function getAdapter({ radioOption, assembly1, assembly2, fileLocation, indexFileLocation, bed1Location, bed2Location, }) {
|
|
32
32
|
if (radioOption === '.paf') {
|
|
33
33
|
return {
|
|
34
34
|
type: 'PAFAdapter',
|
|
@@ -79,14 +79,23 @@ function getAdapter({ radioOption, assembly1, assembly2, fileLocation, bed1Locat
|
|
|
79
79
|
assemblyNames: [assembly1, assembly2],
|
|
80
80
|
};
|
|
81
81
|
}
|
|
82
|
+
else if (radioOption === '.pif.gz') {
|
|
83
|
+
return {
|
|
84
|
+
type: 'PairwiseIndexedPAFAdapter',
|
|
85
|
+
pifGzLocation: fileLocation,
|
|
86
|
+
index: { location: indexFileLocation },
|
|
87
|
+
assemblyNames: [assembly1, assembly2],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
82
90
|
else {
|
|
83
91
|
throw new Error(`Unknown to detect type ${radioOption} from filename (select radio button to clarify)`);
|
|
84
92
|
}
|
|
85
93
|
}
|
|
86
|
-
const
|
|
94
|
+
const ImportCustomTrack = (0, mobx_react_1.observer)(function ({ assembly1, assembly2, setSessionTrackData, }) {
|
|
87
95
|
const [bed2Location, setBed2Location] = (0, react_1.useState)();
|
|
88
96
|
const [bed1Location, setBed1Location] = (0, react_1.useState)();
|
|
89
97
|
const [fileLocation, setFileLocation] = (0, react_1.useState)();
|
|
98
|
+
const [indexFileLocation, setIndexFileLocation] = (0, react_1.useState)();
|
|
90
99
|
const [value, setValue] = (0, react_1.useState)('');
|
|
91
100
|
const [error, setError] = (0, react_1.useState)();
|
|
92
101
|
const fileName = (0, util_1.getName)(fileLocation);
|
|
@@ -107,6 +116,7 @@ const OpenTrack = (0, mobx_react_1.observer)(({ assembly1, assembly2, setSession
|
|
|
107
116
|
assembly1,
|
|
108
117
|
assembly2,
|
|
109
118
|
fileLocation,
|
|
119
|
+
indexFileLocation,
|
|
110
120
|
bed1Location,
|
|
111
121
|
bed2Location,
|
|
112
122
|
}),
|
|
@@ -124,12 +134,13 @@ const OpenTrack = (0, mobx_react_1.observer)(({ assembly1, assembly2, setSession
|
|
|
124
134
|
bed1Location,
|
|
125
135
|
bed2Location,
|
|
126
136
|
fileLocation,
|
|
137
|
+
indexFileLocation,
|
|
127
138
|
radioOption,
|
|
128
139
|
setSessionTrackData,
|
|
129
140
|
]);
|
|
130
141
|
return (react_1.default.createElement(material_1.Paper, { style: { padding: 12 } },
|
|
131
142
|
error ? react_1.default.createElement(ui_1.ErrorMessage, { error: error }) : null,
|
|
132
|
-
react_1.default.createElement(material_1.Typography, { style: { textAlign: 'center' } }, "Add a .paf, .out (MashMap), .delta (Mummer), .chain, .anchors or .anchors.simple (MCScan) file to view
|
|
143
|
+
react_1.default.createElement(material_1.Typography, { style: { textAlign: 'center' } }, "Add a .paf, .out (MashMap), .delta (Mummer), .chain, .anchors or .anchors.simple (MCScan) file to view. These file types can also be gzipped. The first assembly should be the query sequence (e.g. left column of the PAF) and the second assembly should be the target sequence (e.g. right column of the PAF)"),
|
|
133
144
|
react_1.default.createElement(material_1.RadioGroup, { value: radioOption, onChange: event => {
|
|
134
145
|
setValue(event.target.value);
|
|
135
146
|
} },
|
|
@@ -145,7 +156,9 @@ const OpenTrack = (0, mobx_react_1.observer)(({ assembly1, assembly2, setSession
|
|
|
145
156
|
react_1.default.createElement(material_1.Grid, { item: true },
|
|
146
157
|
react_1.default.createElement(material_1.FormControlLabel, { value: ".anchors", control: react_1.default.createElement(material_1.Radio, null), label: ".anchors" })),
|
|
147
158
|
react_1.default.createElement(material_1.Grid, { item: true },
|
|
148
|
-
react_1.default.createElement(material_1.FormControlLabel, { value: ".anchors.simple", control: react_1.default.createElement(material_1.Radio, null), label: ".anchors.simple" }))
|
|
159
|
+
react_1.default.createElement(material_1.FormControlLabel, { value: ".anchors.simple", control: react_1.default.createElement(material_1.Radio, null), label: ".anchors.simple" })),
|
|
160
|
+
react_1.default.createElement(material_1.Grid, { item: true },
|
|
161
|
+
react_1.default.createElement(material_1.FormControlLabel, { value: ".pif.gz", control: react_1.default.createElement(material_1.Radio, null), label: ".pif.gz" })))),
|
|
149
162
|
react_1.default.createElement(material_1.Grid, { container: true, justifyContent: "center" },
|
|
150
163
|
react_1.default.createElement(material_1.Grid, { item: true }, value === '.anchors' || value === '.anchors.simple' ? (react_1.default.createElement("div", null,
|
|
151
164
|
react_1.default.createElement("div", { style: { margin: 20 } },
|
|
@@ -166,8 +179,16 @@ const OpenTrack = (0, mobx_react_1.observer)(({ assembly1, assembly2, setSession
|
|
|
166
179
|
react_1.default.createElement("div", null,
|
|
167
180
|
react_1.default.createElement(ui_1.FileSelector, { name: "genome 2 .bed (right column of anchors file)", description: "", location: bed2Location, setLocation: loc => {
|
|
168
181
|
setBed2Location(loc);
|
|
169
|
-
} }))))) : (react_1.default.createElement(
|
|
182
|
+
} }))))) : value === '.pif.gz' ? (react_1.default.createElement("div", { style: { display: 'flex' } },
|
|
183
|
+
react_1.default.createElement("div", null,
|
|
184
|
+
react_1.default.createElement(ui_1.FileSelector, { name: `${value} location`, description: "", location: fileLocation, setLocation: loc => {
|
|
185
|
+
setFileLocation(loc);
|
|
186
|
+
} })),
|
|
187
|
+
react_1.default.createElement("div", null,
|
|
188
|
+
react_1.default.createElement(ui_1.FileSelector, { name: `${value} index location`, description: "", location: indexFileLocation, setLocation: loc => {
|
|
189
|
+
setIndexFileLocation(loc);
|
|
190
|
+
} })))) : (react_1.default.createElement(ui_1.FileSelector, { name: value ? `${value} location` : '', description: "", location: fileLocation, setLocation: loc => {
|
|
170
191
|
setFileLocation(loc);
|
|
171
192
|
} }))))));
|
|
172
193
|
});
|
|
173
|
-
exports.default =
|
|
194
|
+
exports.default = ImportCustomTrack;
|
|
@@ -194,6 +194,7 @@ export default function stateModelFactory(pm: PluginManager): import("mobx-state
|
|
|
194
194
|
trackMenuItems(): (import("@jbrowse/core/ui").MenuDivider | import("@jbrowse/core/ui").MenuSubHeader | import("@jbrowse/core/ui").NormalMenuItem | import("@jbrowse/core/ui").CheckboxMenuItem | import("@jbrowse/core/ui").RadioMenuItem | import("@jbrowse/core/ui").SubMenuItem | {
|
|
195
195
|
type: string;
|
|
196
196
|
label: string;
|
|
197
|
+
priority: number;
|
|
197
198
|
subMenu: {
|
|
198
199
|
type: string;
|
|
199
200
|
label: string;
|
|
@@ -25,7 +25,12 @@ export declare function stateModelFactory(configSchema: AnyConfigurationSchemaTy
|
|
|
25
25
|
error: unknown;
|
|
26
26
|
message: string | undefined;
|
|
27
27
|
} & {
|
|
28
|
-
readonly RenderingComponent: React.
|
|
28
|
+
readonly RenderingComponent: React.
|
|
29
|
+
/**
|
|
30
|
+
* #stateModel DotplotDisplay
|
|
31
|
+
* #category display
|
|
32
|
+
*/
|
|
33
|
+
FC<{
|
|
29
34
|
model: {
|
|
30
35
|
id: string;
|
|
31
36
|
type: string;
|
|
@@ -27,14 +27,6 @@ interface DotplotRenderArgs extends RenderArgs {
|
|
|
27
27
|
export default class DotplotRenderer extends ComparativeRenderer {
|
|
28
28
|
supportsSVG: boolean;
|
|
29
29
|
renameRegionsIfNeeded(args: DotplotRenderArgs): Promise<DotplotRenderArgs>;
|
|
30
|
-
drawDotplot(ctx: CanvasRenderingContext2D, props: DotplotRenderArgsDeserialized & {
|
|
31
|
-
views: Dotplot1DViewModel[];
|
|
32
|
-
}): Promise<{
|
|
33
|
-
warnings: {
|
|
34
|
-
message: string;
|
|
35
|
-
effect: string;
|
|
36
|
-
}[];
|
|
37
|
-
}>;
|
|
38
30
|
render(renderProps: DotplotRenderArgsDeserialized): Promise<{
|
|
39
31
|
height: number;
|
|
40
32
|
width: number;
|
|
@@ -52,7 +44,7 @@ export default class DotplotRenderer extends ComparativeRenderer {
|
|
|
52
44
|
offsetY: number;
|
|
53
45
|
bpPerPxX: number;
|
|
54
46
|
bpPerPxY: number;
|
|
55
|
-
reactElement:
|
|
47
|
+
reactElement: React.JSX.Element;
|
|
56
48
|
html?: string;
|
|
57
49
|
} | {
|
|
58
50
|
height: number;
|
|
@@ -1,22 +1,7 @@
|
|
|
1
|
-
import { readConfObject, } from '@jbrowse/core/configuration';
|
|
2
1
|
import { renameRegionsIfNeeded, renderToAbstractCanvas, } from '@jbrowse/core/util';
|
|
3
|
-
import { bpToPx } from '@jbrowse/core/util/Base1DUtils';
|
|
4
|
-
import { getSnapshot } from 'mobx-state-tree';
|
|
5
2
|
import ComparativeRenderer from '@jbrowse/core/pluggableElementTypes/renderers/ComparativeServerSideRendererType';
|
|
6
|
-
import { MismatchParser } from '@jbrowse/plugin-alignments';
|
|
7
3
|
// locals
|
|
8
4
|
import { Dotplot1DView } from '../DotplotView/model';
|
|
9
|
-
import { createJBrowseTheme } from '@jbrowse/core/ui';
|
|
10
|
-
const { parseCigar } = MismatchParser;
|
|
11
|
-
const r = 'fell outside of range due to CIGAR string';
|
|
12
|
-
const lt = '(less than min coordinate of feature)';
|
|
13
|
-
const gt = '(greater than max coordinate of feature)';
|
|
14
|
-
const fudgeFactor = 1; // allow 1px fuzzyness before warn
|
|
15
|
-
function drawCir(ctx, x, y, r = 1) {
|
|
16
|
-
ctx.beginPath();
|
|
17
|
-
ctx.arc(x, y, r / 2, 0, 2 * Math.PI);
|
|
18
|
-
ctx.fill();
|
|
19
|
-
}
|
|
20
5
|
export default class DotplotRenderer extends ComparativeRenderer {
|
|
21
6
|
constructor() {
|
|
22
7
|
super(...arguments);
|
|
@@ -42,204 +27,6 @@ export default class DotplotRenderer extends ComparativeRenderer {
|
|
|
42
27
|
view.vview.displayedRegions = await process(view.vview.displayedRegions);
|
|
43
28
|
return args;
|
|
44
29
|
}
|
|
45
|
-
async drawDotplot(ctx, props) {
|
|
46
|
-
var _a, _b;
|
|
47
|
-
const { config, views, height, drawCigar, theme } = props;
|
|
48
|
-
const color = readConfObject(config, 'color');
|
|
49
|
-
const posColor = readConfObject(config, 'posColor');
|
|
50
|
-
const negColor = readConfObject(config, 'negColor');
|
|
51
|
-
const colorBy = readConfObject(config, 'colorBy');
|
|
52
|
-
const lineWidth = readConfObject(config, 'lineWidth');
|
|
53
|
-
const thresholds = readConfObject(config, 'thresholds');
|
|
54
|
-
const palette = readConfObject(config, 'thresholdsPalette');
|
|
55
|
-
const isCallback = config.color.isCallback;
|
|
56
|
-
const hview = views[0];
|
|
57
|
-
const vview = views[1];
|
|
58
|
-
const db1 = (_a = hview.dynamicBlocks.contentBlocks[0]) === null || _a === void 0 ? void 0 : _a.offsetPx;
|
|
59
|
-
const db2 = (_b = vview.dynamicBlocks.contentBlocks[0]) === null || _b === void 0 ? void 0 : _b.offsetPx;
|
|
60
|
-
const warnings = [];
|
|
61
|
-
ctx.lineWidth = lineWidth;
|
|
62
|
-
// we operate on snapshots of these attributes of the hview/vview because
|
|
63
|
-
// it is significantly faster than accessing the mobx objects
|
|
64
|
-
const { bpPerPx: hBpPerPx } = hview;
|
|
65
|
-
const { bpPerPx: vBpPerPx } = vview;
|
|
66
|
-
function clampWithWarnX(num, min, max, feature) {
|
|
67
|
-
const strand = feature.get('strand') || 1;
|
|
68
|
-
if (strand === -1) {
|
|
69
|
-
;
|
|
70
|
-
[max, min] = [min, max];
|
|
71
|
-
}
|
|
72
|
-
if (num < min - fudgeFactor) {
|
|
73
|
-
let start = feature.get('start');
|
|
74
|
-
let end = feature.get('end');
|
|
75
|
-
const refName = feature.get('refName');
|
|
76
|
-
if (strand === -1) {
|
|
77
|
-
;
|
|
78
|
-
[end, start] = [start, end];
|
|
79
|
-
}
|
|
80
|
-
warnings.push({
|
|
81
|
-
message: `feature at (X ${refName}:${start}-${end}) ${r} ${lt}`,
|
|
82
|
-
effect: 'clipped the feature',
|
|
83
|
-
});
|
|
84
|
-
return min;
|
|
85
|
-
}
|
|
86
|
-
if (num > max + fudgeFactor) {
|
|
87
|
-
const strand = feature.get('strand') || 1;
|
|
88
|
-
const start = strand === 1 ? feature.get('start') : feature.get('end');
|
|
89
|
-
const end = strand === 1 ? feature.get('end') : feature.get('start');
|
|
90
|
-
const refName = feature.get('refName');
|
|
91
|
-
warnings.push({
|
|
92
|
-
message: `feature at (X ${refName}:${start}-${end}) ${r} ${gt}`,
|
|
93
|
-
effect: 'clipped the feature',
|
|
94
|
-
});
|
|
95
|
-
return max;
|
|
96
|
-
}
|
|
97
|
-
return num;
|
|
98
|
-
}
|
|
99
|
-
function clampWithWarnY(num, min, max, feature) {
|
|
100
|
-
if (num < min - fudgeFactor) {
|
|
101
|
-
const mate = feature.get('mate');
|
|
102
|
-
const { refName, start, end } = mate;
|
|
103
|
-
warnings.push({
|
|
104
|
-
message: `feature at (Y ${refName}:${start}-${end}) ${r} ${lt}`,
|
|
105
|
-
effect: 'clipped the feature',
|
|
106
|
-
});
|
|
107
|
-
return min;
|
|
108
|
-
}
|
|
109
|
-
if (num > max + fudgeFactor) {
|
|
110
|
-
const mate = feature.get('mate');
|
|
111
|
-
const { refName, start, end } = mate;
|
|
112
|
-
warnings.push({
|
|
113
|
-
message: `feature at (Y ${refName}:${start}-${end}) ${r} ${gt}`,
|
|
114
|
-
effect: 'clipped the feature',
|
|
115
|
-
});
|
|
116
|
-
return max;
|
|
117
|
-
}
|
|
118
|
-
return num;
|
|
119
|
-
}
|
|
120
|
-
const hsnap = {
|
|
121
|
-
...getSnapshot(hview),
|
|
122
|
-
staticBlocks: hview.staticBlocks,
|
|
123
|
-
width: hview.width,
|
|
124
|
-
};
|
|
125
|
-
const vsnap = {
|
|
126
|
-
...getSnapshot(vview),
|
|
127
|
-
staticBlocks: vview.staticBlocks,
|
|
128
|
-
width: vview.width,
|
|
129
|
-
};
|
|
130
|
-
const t = createJBrowseTheme(theme);
|
|
131
|
-
for (const feature of hview.features || []) {
|
|
132
|
-
const strand = feature.get('strand') || 1;
|
|
133
|
-
const start = strand === 1 ? feature.get('start') : feature.get('end');
|
|
134
|
-
const end = strand === 1 ? feature.get('end') : feature.get('start');
|
|
135
|
-
const refName = feature.get('refName');
|
|
136
|
-
const mate = feature.get('mate');
|
|
137
|
-
const mateRef = mate.refName;
|
|
138
|
-
let r = 'black';
|
|
139
|
-
if (colorBy === 'identity') {
|
|
140
|
-
const identity = feature.get('identity');
|
|
141
|
-
for (let i = 0; i < thresholds.length; i++) {
|
|
142
|
-
if (identity > +thresholds[i]) {
|
|
143
|
-
r = palette[i] || 'black';
|
|
144
|
-
break;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
else if (colorBy === 'meanQueryIdentity') {
|
|
149
|
-
r = `hsl(${feature.get('meanScore') * 200},100%,40%)`;
|
|
150
|
-
}
|
|
151
|
-
else if (colorBy === 'mappingQuality') {
|
|
152
|
-
r = `hsl(${feature.get('mappingQual')},100%,40%)`;
|
|
153
|
-
}
|
|
154
|
-
else if (colorBy === 'strand') {
|
|
155
|
-
r = strand === -1 ? negColor : posColor;
|
|
156
|
-
}
|
|
157
|
-
else if (colorBy === 'default') {
|
|
158
|
-
r = isCallback
|
|
159
|
-
? readConfObject(config, 'color', { feature })
|
|
160
|
-
: color === '#f0f'
|
|
161
|
-
? t.palette.text.primary
|
|
162
|
-
: color;
|
|
163
|
-
}
|
|
164
|
-
ctx.fillStyle = r;
|
|
165
|
-
ctx.strokeStyle = r;
|
|
166
|
-
const b10 = bpToPx({ self: hsnap, refName, coord: start });
|
|
167
|
-
const b20 = bpToPx({ self: hsnap, refName, coord: end });
|
|
168
|
-
const e10 = bpToPx({ self: vsnap, refName: mateRef, coord: mate.start });
|
|
169
|
-
const e20 = bpToPx({ self: vsnap, refName: mateRef, coord: mate.end });
|
|
170
|
-
if (b10 !== undefined &&
|
|
171
|
-
b20 !== undefined &&
|
|
172
|
-
e10 !== undefined &&
|
|
173
|
-
e20 !== undefined) {
|
|
174
|
-
const b1 = b10.offsetPx - db1;
|
|
175
|
-
const b2 = b20.offsetPx - db1;
|
|
176
|
-
const e1 = e10.offsetPx - db2;
|
|
177
|
-
const e2 = e20.offsetPx - db2;
|
|
178
|
-
if (Math.abs(b1 - b2) <= 4 && Math.abs(e1 - e2) <= 4) {
|
|
179
|
-
drawCir(ctx, b1, height - e1, lineWidth);
|
|
180
|
-
}
|
|
181
|
-
else {
|
|
182
|
-
let currX = b1;
|
|
183
|
-
let currY = e1;
|
|
184
|
-
const cigar = feature.get('CIGAR');
|
|
185
|
-
if (drawCigar && cigar) {
|
|
186
|
-
const cigarOps = parseCigar(cigar);
|
|
187
|
-
ctx.beginPath();
|
|
188
|
-
ctx.moveTo(currX, height - currY);
|
|
189
|
-
let lastDrawnX = currX;
|
|
190
|
-
let lastDrawnY = currX;
|
|
191
|
-
for (let i = 0; i < cigarOps.length; i += 2) {
|
|
192
|
-
const val = +cigarOps[i];
|
|
193
|
-
const op = cigarOps[i + 1];
|
|
194
|
-
if (op === 'M' || op === '=' || op === 'X') {
|
|
195
|
-
currX += (val / hBpPerPx) * strand;
|
|
196
|
-
currY += val / vBpPerPx;
|
|
197
|
-
}
|
|
198
|
-
else if (op === 'D' || op === 'N') {
|
|
199
|
-
currX += (val / hBpPerPx) * strand;
|
|
200
|
-
}
|
|
201
|
-
else if (op === 'I') {
|
|
202
|
-
currY += val / vBpPerPx;
|
|
203
|
-
}
|
|
204
|
-
currX = clampWithWarnX(currX, b1, b2, feature);
|
|
205
|
-
currY = clampWithWarnY(currY, e1, e2, feature);
|
|
206
|
-
// only draw a line segment if it is bigger than 0.5px
|
|
207
|
-
if (Math.abs(currX - lastDrawnX) > 0.5 ||
|
|
208
|
-
Math.abs(currY - lastDrawnY) > 0.5) {
|
|
209
|
-
ctx.lineTo(currX, height - currY);
|
|
210
|
-
lastDrawnX = currX;
|
|
211
|
-
lastDrawnY = currY;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
ctx.stroke();
|
|
215
|
-
}
|
|
216
|
-
else {
|
|
217
|
-
ctx.beginPath();
|
|
218
|
-
ctx.moveTo(b1, height - e1);
|
|
219
|
-
ctx.lineTo(b2, height - e2);
|
|
220
|
-
ctx.stroke();
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
else {
|
|
225
|
-
if (warnings.length <= 5) {
|
|
226
|
-
if (b10 === undefined || b20 === undefined) {
|
|
227
|
-
warnings.push({
|
|
228
|
-
message: `feature at (X ${refName}:${start}-${end}) not plotted, fell outside of range`,
|
|
229
|
-
effect: 'feature not rendered',
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
else {
|
|
233
|
-
warnings.push({
|
|
234
|
-
message: `feature at (Y ${mateRef}:${mate.start}-${mate.end}) not plotted, fell outside of range`,
|
|
235
|
-
effect: 'feature not rendered',
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
return { warnings };
|
|
242
|
-
}
|
|
243
30
|
async render(renderProps) {
|
|
244
31
|
var _a, _b;
|
|
245
32
|
const { width, height, view: { hview, vview }, } = renderProps;
|
|
@@ -255,7 +42,8 @@ export default class DotplotRenderer extends ComparativeRenderer {
|
|
|
255
42
|
regions: target.dynamicBlocks.contentBlocks,
|
|
256
43
|
});
|
|
257
44
|
target.setFeatures(feats);
|
|
258
|
-
const
|
|
45
|
+
const { drawDotplot } = await import('./drawDotplot');
|
|
46
|
+
const ret = await renderToAbstractCanvas(width, height, renderProps, ctx => drawDotplot(ctx, { ...renderProps, views }));
|
|
259
47
|
const results = await super.render({
|
|
260
48
|
...renderProps,
|
|
261
49
|
...ret,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { AnyConfigurationModel } from '@jbrowse/core/configuration';
|
|
2
|
+
import { RenderArgsDeserialized } from '@jbrowse/core/pluggableElementTypes/renderers/ComparativeServerSideRendererType';
|
|
3
|
+
import { Dotplot1DViewModel } from '../DotplotView/model';
|
|
4
|
+
export interface DotplotRenderArgsDeserialized extends RenderArgsDeserialized {
|
|
5
|
+
adapterConfig: AnyConfigurationModel;
|
|
6
|
+
height: number;
|
|
7
|
+
width: number;
|
|
8
|
+
highResolutionScaling: number;
|
|
9
|
+
view: {
|
|
10
|
+
hview: Dotplot1DViewModel;
|
|
11
|
+
vview: Dotplot1DViewModel;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export declare function drawDotplot(ctx: CanvasRenderingContext2D, props: DotplotRenderArgsDeserialized & {
|
|
15
|
+
views: Dotplot1DViewModel[];
|
|
16
|
+
}): Promise<{
|
|
17
|
+
warnings: {
|
|
18
|
+
message: string;
|
|
19
|
+
effect: string;
|
|
20
|
+
}[];
|
|
21
|
+
}>;
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { readConfObject, } from '@jbrowse/core/configuration';
|
|
2
|
+
import { bpToPx } from '@jbrowse/core/util/Base1DUtils';
|
|
3
|
+
import { getSnapshot } from 'mobx-state-tree';
|
|
4
|
+
import { MismatchParser } from '@jbrowse/plugin-alignments';
|
|
5
|
+
import { createJBrowseTheme } from '@jbrowse/core/ui';
|
|
6
|
+
const { parseCigar } = MismatchParser;
|
|
7
|
+
const r = 'fell outside of range due to CIGAR string';
|
|
8
|
+
const lt = '(less than min coordinate of feature)';
|
|
9
|
+
const gt = '(greater than max coordinate of feature)';
|
|
10
|
+
const fudgeFactor = 1; // allow 1px fuzzyness before warn
|
|
11
|
+
function drawCir(ctx, x, y, r = 1) {
|
|
12
|
+
ctx.beginPath();
|
|
13
|
+
ctx.arc(x, y, r / 2, 0, 2 * Math.PI);
|
|
14
|
+
ctx.fill();
|
|
15
|
+
}
|
|
16
|
+
export async function drawDotplot(ctx, props) {
|
|
17
|
+
var _a, _b;
|
|
18
|
+
const { config, views, height, drawCigar, theme } = props;
|
|
19
|
+
const color = readConfObject(config, 'color');
|
|
20
|
+
const posColor = readConfObject(config, 'posColor');
|
|
21
|
+
const negColor = readConfObject(config, 'negColor');
|
|
22
|
+
const colorBy = readConfObject(config, 'colorBy');
|
|
23
|
+
const lineWidth = readConfObject(config, 'lineWidth');
|
|
24
|
+
const thresholds = readConfObject(config, 'thresholds');
|
|
25
|
+
const palette = readConfObject(config, 'thresholdsPalette');
|
|
26
|
+
const isCallback = config.color.isCallback;
|
|
27
|
+
const hview = views[0];
|
|
28
|
+
const vview = views[1];
|
|
29
|
+
const db1 = (_a = hview.dynamicBlocks.contentBlocks[0]) === null || _a === void 0 ? void 0 : _a.offsetPx;
|
|
30
|
+
const db2 = (_b = vview.dynamicBlocks.contentBlocks[0]) === null || _b === void 0 ? void 0 : _b.offsetPx;
|
|
31
|
+
const warnings = [];
|
|
32
|
+
ctx.lineWidth = lineWidth;
|
|
33
|
+
// we operate on snapshots of these attributes of the hview/vview because
|
|
34
|
+
// it is significantly faster than accessing the mobx objects
|
|
35
|
+
const { bpPerPx: hBpPerPx } = hview;
|
|
36
|
+
const { bpPerPx: vBpPerPx } = vview;
|
|
37
|
+
function clampWithWarnX(num, min, max, feature) {
|
|
38
|
+
const strand = feature.get('strand') || 1;
|
|
39
|
+
if (strand === -1) {
|
|
40
|
+
;
|
|
41
|
+
[max, min] = [min, max];
|
|
42
|
+
}
|
|
43
|
+
if (num < min - fudgeFactor) {
|
|
44
|
+
let start = feature.get('start');
|
|
45
|
+
let end = feature.get('end');
|
|
46
|
+
const refName = feature.get('refName');
|
|
47
|
+
if (strand === -1) {
|
|
48
|
+
;
|
|
49
|
+
[end, start] = [start, end];
|
|
50
|
+
}
|
|
51
|
+
warnings.push({
|
|
52
|
+
message: `feature at (X ${refName}:${start}-${end}) ${r} ${lt}`,
|
|
53
|
+
effect: 'clipped the feature',
|
|
54
|
+
});
|
|
55
|
+
return min;
|
|
56
|
+
}
|
|
57
|
+
if (num > max + fudgeFactor) {
|
|
58
|
+
const strand = feature.get('strand') || 1;
|
|
59
|
+
const start = strand === 1 ? feature.get('start') : feature.get('end');
|
|
60
|
+
const end = strand === 1 ? feature.get('end') : feature.get('start');
|
|
61
|
+
const refName = feature.get('refName');
|
|
62
|
+
warnings.push({
|
|
63
|
+
message: `feature at (X ${refName}:${start}-${end}) ${r} ${gt}`,
|
|
64
|
+
effect: 'clipped the feature',
|
|
65
|
+
});
|
|
66
|
+
return max;
|
|
67
|
+
}
|
|
68
|
+
return num;
|
|
69
|
+
}
|
|
70
|
+
function clampWithWarnY(num, min, max, feature) {
|
|
71
|
+
if (num < min - fudgeFactor) {
|
|
72
|
+
const mate = feature.get('mate');
|
|
73
|
+
const { refName, start, end } = mate;
|
|
74
|
+
warnings.push({
|
|
75
|
+
message: `feature at (Y ${refName}:${start}-${end}) ${r} ${lt}`,
|
|
76
|
+
effect: 'clipped the feature',
|
|
77
|
+
});
|
|
78
|
+
return min;
|
|
79
|
+
}
|
|
80
|
+
if (num > max + fudgeFactor) {
|
|
81
|
+
const mate = feature.get('mate');
|
|
82
|
+
const { refName, start, end } = mate;
|
|
83
|
+
warnings.push({
|
|
84
|
+
message: `feature at (Y ${refName}:${start}-${end}) ${r} ${gt}`,
|
|
85
|
+
effect: 'clipped the feature',
|
|
86
|
+
});
|
|
87
|
+
return max;
|
|
88
|
+
}
|
|
89
|
+
return num;
|
|
90
|
+
}
|
|
91
|
+
const hsnap = {
|
|
92
|
+
...getSnapshot(hview),
|
|
93
|
+
staticBlocks: hview.staticBlocks,
|
|
94
|
+
width: hview.width,
|
|
95
|
+
};
|
|
96
|
+
const vsnap = {
|
|
97
|
+
...getSnapshot(vview),
|
|
98
|
+
staticBlocks: vview.staticBlocks,
|
|
99
|
+
width: vview.width,
|
|
100
|
+
};
|
|
101
|
+
const t = createJBrowseTheme(theme);
|
|
102
|
+
for (const feature of hview.features || []) {
|
|
103
|
+
const strand = feature.get('strand') || 1;
|
|
104
|
+
const start = strand === 1 ? feature.get('start') : feature.get('end');
|
|
105
|
+
const end = strand === 1 ? feature.get('end') : feature.get('start');
|
|
106
|
+
const refName = feature.get('refName');
|
|
107
|
+
const mate = feature.get('mate');
|
|
108
|
+
const mateRef = mate.refName;
|
|
109
|
+
let r = 'black';
|
|
110
|
+
if (colorBy === 'identity') {
|
|
111
|
+
const identity = feature.get('identity');
|
|
112
|
+
for (let i = 0; i < thresholds.length; i++) {
|
|
113
|
+
if (identity > +thresholds[i]) {
|
|
114
|
+
r = palette[i] || 'black';
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else if (colorBy === 'meanQueryIdentity') {
|
|
120
|
+
r = `hsl(${feature.get('meanScore') * 200},100%,40%)`;
|
|
121
|
+
}
|
|
122
|
+
else if (colorBy === 'mappingQuality') {
|
|
123
|
+
r = `hsl(${feature.get('mappingQual')},100%,40%)`;
|
|
124
|
+
}
|
|
125
|
+
else if (colorBy === 'strand') {
|
|
126
|
+
r = strand === -1 ? negColor : posColor;
|
|
127
|
+
}
|
|
128
|
+
else if (colorBy === 'default') {
|
|
129
|
+
r = isCallback
|
|
130
|
+
? readConfObject(config, 'color', { feature })
|
|
131
|
+
: color === '#f0f'
|
|
132
|
+
? t.palette.text.primary
|
|
133
|
+
: color;
|
|
134
|
+
}
|
|
135
|
+
ctx.fillStyle = r;
|
|
136
|
+
ctx.strokeStyle = r;
|
|
137
|
+
const b10 = bpToPx({ self: hsnap, refName, coord: start });
|
|
138
|
+
const b20 = bpToPx({ self: hsnap, refName, coord: end });
|
|
139
|
+
const e10 = bpToPx({ self: vsnap, refName: mateRef, coord: mate.start });
|
|
140
|
+
const e20 = bpToPx({ self: vsnap, refName: mateRef, coord: mate.end });
|
|
141
|
+
if (b10 !== undefined &&
|
|
142
|
+
b20 !== undefined &&
|
|
143
|
+
e10 !== undefined &&
|
|
144
|
+
e20 !== undefined) {
|
|
145
|
+
const b1 = b10.offsetPx - db1;
|
|
146
|
+
const b2 = b20.offsetPx - db1;
|
|
147
|
+
const e1 = e10.offsetPx - db2;
|
|
148
|
+
const e2 = e20.offsetPx - db2;
|
|
149
|
+
if (Math.abs(b1 - b2) <= 4 && Math.abs(e1 - e2) <= 4) {
|
|
150
|
+
drawCir(ctx, b1, height - e1, lineWidth);
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
let currX = b1;
|
|
154
|
+
let currY = e1;
|
|
155
|
+
const cigar = feature.get('CIGAR');
|
|
156
|
+
if (drawCigar && cigar) {
|
|
157
|
+
const cigarOps = parseCigar(cigar);
|
|
158
|
+
ctx.beginPath();
|
|
159
|
+
ctx.moveTo(currX, height - currY);
|
|
160
|
+
let lastDrawnX = currX;
|
|
161
|
+
let lastDrawnY = currX;
|
|
162
|
+
for (let i = 0; i < cigarOps.length; i += 2) {
|
|
163
|
+
const val = +cigarOps[i];
|
|
164
|
+
const op = cigarOps[i + 1];
|
|
165
|
+
if (op === 'M' || op === '=' || op === 'X') {
|
|
166
|
+
currX += (val / hBpPerPx) * strand;
|
|
167
|
+
currY += val / vBpPerPx;
|
|
168
|
+
}
|
|
169
|
+
else if (op === 'D' || op === 'N') {
|
|
170
|
+
currX += (val / hBpPerPx) * strand;
|
|
171
|
+
}
|
|
172
|
+
else if (op === 'I') {
|
|
173
|
+
currY += val / vBpPerPx;
|
|
174
|
+
}
|
|
175
|
+
currX = clampWithWarnX(currX, b1, b2, feature);
|
|
176
|
+
currY = clampWithWarnY(currY, e1, e2, feature);
|
|
177
|
+
// only draw a line segment if it is bigger than 0.5px
|
|
178
|
+
if (Math.abs(currX - lastDrawnX) > 0.5 ||
|
|
179
|
+
Math.abs(currY - lastDrawnY) > 0.5) {
|
|
180
|
+
ctx.lineTo(currX, height - currY);
|
|
181
|
+
lastDrawnX = currX;
|
|
182
|
+
lastDrawnY = currY;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
ctx.stroke();
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
ctx.beginPath();
|
|
189
|
+
ctx.moveTo(b1, height - e1);
|
|
190
|
+
ctx.lineTo(b2, height - e2);
|
|
191
|
+
ctx.stroke();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
if (warnings.length <= 5) {
|
|
197
|
+
if (b10 === undefined || b20 === undefined) {
|
|
198
|
+
warnings.push({
|
|
199
|
+
message: `feature at (X ${refName}:${start}-${end}) not plotted, fell outside of range`,
|
|
200
|
+
effect: 'feature not rendered',
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
warnings.push({
|
|
205
|
+
message: `feature at (Y ${mateRef}:${mate.start}-${mate.end}) not plotted, fell outside of range`,
|
|
206
|
+
effect: 'feature not rendered',
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return { warnings };
|
|
213
|
+
}
|
|
@@ -1,17 +1,11 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { DotplotViewModel } from '../model';
|
|
3
3
|
type Coord = [number, number] | undefined;
|
|
4
|
-
export declare const
|
|
5
|
-
model: DotplotViewModel;
|
|
6
|
-
mouserect: Coord;
|
|
7
|
-
mouserectClient: Coord;
|
|
8
|
-
xdistance: number;
|
|
9
|
-
}) => React.JSX.Element | null;
|
|
10
|
-
export declare const TooltipWhereClicked: ({ model, mousedown, mousedownClient, xdistance, ydistance, }: {
|
|
4
|
+
export declare const DotplotTooltipClick: ({ model, mousedown, mousedownClient, xdistance, ydistance, }: {
|
|
11
5
|
model: DotplotViewModel;
|
|
12
6
|
mousedown: Coord;
|
|
13
7
|
mousedownClient: Coord;
|
|
14
8
|
xdistance: number;
|
|
15
9
|
ydistance: number;
|
|
16
10
|
}) => React.JSX.Element | null;
|
|
17
|
-
export
|
|
11
|
+
export default DotplotTooltipClick;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { observer } from 'mobx-react';
|
|
3
|
+
import BaseTooltip from '@jbrowse/core/ui/BaseTooltip';
|
|
4
|
+
import { locstr } from './util';
|
|
5
|
+
export const DotplotTooltipClick = observer(function ({ model, mousedown, mousedownClient, xdistance, ydistance, }) {
|
|
6
|
+
const { hview, vview, viewHeight } = model;
|
|
7
|
+
const x = ((mousedownClient === null || mousedownClient === void 0 ? void 0 : mousedownClient[0]) || 0) - (xdistance < 0 ? 0 : 0);
|
|
8
|
+
const y = ((mousedownClient === null || mousedownClient === void 0 ? void 0 : mousedownClient[1]) || 0) - (ydistance < 0 ? 0 : 0);
|
|
9
|
+
return mousedown && Math.abs(xdistance) > 3 && Math.abs(ydistance) > 3 ? (React.createElement(BaseTooltip, { clientPoint: { x, y } },
|
|
10
|
+
`x - ${locstr(mousedown[0], hview)}`,
|
|
11
|
+
React.createElement("br", null),
|
|
12
|
+
`y - ${locstr(viewHeight - mousedown[1], vview)}`,
|
|
13
|
+
React.createElement("br", null))) : null;
|
|
14
|
+
});
|
|
15
|
+
export default DotplotTooltipClick;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { DotplotViewModel } from '../model';
|
|
3
|
+
type Coord = [number, number] | undefined;
|
|
4
|
+
declare const DotplotTooltipMouseover: ({ model, mouserect, mouserectClient, xdistance, }: {
|
|
5
|
+
model: DotplotViewModel;
|
|
6
|
+
mouserect: Coord;
|
|
7
|
+
mouserectClient: Coord;
|
|
8
|
+
xdistance: number;
|
|
9
|
+
}) => React.JSX.Element | null;
|
|
10
|
+
export default DotplotTooltipMouseover;
|