@jbrowse/plugin-linear-comparative-view 2.11.1 → 2.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/dist/LGVSyntenyDisplay/components/util.js +1 -1
  2. package/dist/LGVSyntenyDisplay/configSchemaF.d.ts +5 -0
  3. package/dist/LGVSyntenyDisplay/model.d.ts +7 -3
  4. package/dist/LinearComparativeView/model.d.ts +11 -36
  5. package/dist/LinearComparativeView/model.js +0 -23
  6. package/dist/LinearSyntenyDisplay/components/LinearSyntenyRendering.js +54 -101
  7. package/dist/LinearSyntenyDisplay/components/util.d.ts +10 -0
  8. package/dist/LinearSyntenyDisplay/components/util.js +87 -1
  9. package/dist/LinearSyntenyView/components/ImportForm/index.js +1 -0
  10. package/dist/LinearSyntenyView/model.d.ts +39 -139
  11. package/dist/LinearSyntenyView/model.js +0 -8
  12. package/dist/SyntenyFeatureDetail/SyntenyFeatureDetail.d.ts +5 -3
  13. package/dist/SyntenyFeatureDetail/SyntenyFeatureDetail.js +2 -4
  14. package/esm/LGVSyntenyDisplay/components/util.js +1 -1
  15. package/esm/LGVSyntenyDisplay/configSchemaF.d.ts +5 -0
  16. package/esm/LGVSyntenyDisplay/model.d.ts +7 -3
  17. package/esm/LinearComparativeView/model.d.ts +11 -36
  18. package/esm/LinearComparativeView/model.js +1 -24
  19. package/esm/LinearSyntenyDisplay/components/LinearSyntenyRendering.js +54 -101
  20. package/esm/LinearSyntenyDisplay/components/util.d.ts +10 -0
  21. package/esm/LinearSyntenyDisplay/components/util.js +84 -1
  22. package/esm/LinearSyntenyView/components/ImportForm/index.js +1 -0
  23. package/esm/LinearSyntenyView/model.d.ts +39 -139
  24. package/esm/LinearSyntenyView/model.js +0 -8
  25. package/esm/SyntenyFeatureDetail/SyntenyFeatureDetail.d.ts +5 -3
  26. package/esm/SyntenyFeatureDetail/SyntenyFeatureDetail.js +2 -4
  27. package/package.json +2 -2
@@ -1,12 +1,13 @@
1
1
  import React, { useState, useCallback, useRef } from 'react';
2
2
  import { observer } from 'mobx-react';
3
- import { assembleLocString, getContainingView, getSession, isSessionModelWithWidgets, } from '@jbrowse/core/util';
3
+ import { getContainingView } from '@jbrowse/core/util';
4
4
  import { transaction } from 'mobx';
5
5
  import { makeStyles } from 'tss-react/mui';
6
6
  // locals
7
7
  import SyntenyTooltip from './SyntenyTooltip';
8
8
  import { getId, MAX_COLOR_RANGE } from '../drawSynteny';
9
9
  import SyntenyContextMenu from './SyntenyContextMenu';
10
+ import { getTooltip, onSynClick, onSynContextClick } from './util';
10
11
  const useStyles = makeStyles()({
11
12
  pix: {
12
13
  imageRendering: 'pixelated',
@@ -31,30 +32,44 @@ const LinearSyntenyRendering = observer(function ({ model, }) {
31
32
  const view = getContainingView(model);
32
33
  const height = view.middleComparativeHeight;
33
34
  const width = view.width;
35
+ const delta = useRef(0);
36
+ const timeout = useRef();
34
37
  const [anchorEl, setAnchorEl] = useState();
35
38
  const [tooltip, setTooltip] = useState('');
36
39
  const [currX, setCurrX] = useState();
37
40
  const [mouseCurrDownX, setMouseCurrDownX] = useState();
38
41
  const [mouseInitialDownX, setMouseInitialDownX] = useState();
39
42
  const [currY, setCurrY] = useState();
43
+ const { mouseoverId } = model;
40
44
  // these useCallbacks avoid new refs from being created on any mouseover, etc.
41
45
  const k1 = useCallback((ref) => model.setMouseoverCanvasRef(ref),
42
46
  // eslint-disable-next-line react-hooks/exhaustive-deps
43
47
  [model, height, width]);
44
- const k2 = useCallback((ref) => model.setMainCanvasRef(ref),
45
- // eslint-disable-next-line react-hooks/exhaustive-deps
46
- [model, height, width]);
47
- const k3 = useCallback((ref) => model.setClickMapCanvasRef(ref),
48
- // eslint-disable-next-line react-hooks/exhaustive-deps
49
- [model, height, width]);
50
- const k4 = useCallback((ref) => model.setCigarClickMapCanvasRef(ref),
51
- // eslint-disable-next-line react-hooks/exhaustive-deps
52
- [model, height, width]);
53
- return (React.createElement("div", { className: classes.rel },
54
- React.createElement("canvas", { ref: k1, width: width, height: height, className: cx(classes.abs, classes.none) }),
55
- React.createElement("canvas", { ref: k2, onWheel: event => {
48
+ const k2 = useCallback((ref) => {
49
+ model.setMainCanvasRef(ref);
50
+ function onWheel(event) {
51
+ event.preventDefault();
52
+ if (event.ctrlKey === true) {
53
+ delta.current += event.deltaY / 500;
54
+ for (const v of view.views) {
55
+ v.setScaleFactor(delta.current < 0 ? 1 - delta.current : 1 / (1 + delta.current));
56
+ }
57
+ if (timeout.current) {
58
+ clearTimeout(timeout.current);
59
+ }
60
+ timeout.current = setTimeout(() => {
61
+ for (const v of view.views) {
62
+ v.setScaleFactor(1);
63
+ v.zoomTo(delta.current > 0
64
+ ? v.bpPerPx * (1 + delta.current)
65
+ : v.bpPerPx / (1 - delta.current), event.clientX - ((ref === null || ref === void 0 ? void 0 : ref.getBoundingClientRect().left) || 0));
66
+ }
67
+ delta.current = 0;
68
+ }, 300);
69
+ }
70
+ else {
56
71
  if (Math.abs(event.deltaY) < Math.abs(event.deltaX)) {
57
- xOffset.current += event.deltaX;
72
+ xOffset.current += event.deltaX / 2;
58
73
  }
59
74
  if (currScrollFrame.current === undefined) {
60
75
  currScrollFrame.current = requestAnimationFrame(() => {
@@ -67,7 +82,28 @@ const LinearSyntenyRendering = observer(function ({ model, }) {
67
82
  });
68
83
  });
69
84
  }
70
- }, onMouseMove: event => {
85
+ }
86
+ }
87
+ if (ref) {
88
+ ref.addEventListener('wheel', onWheel);
89
+ // this is a react 19-ism to have a cleanup in the ref callback
90
+ // https://react.dev/blog/2024/04/25/react-19#cleanup-functions-for-refs
91
+ // note: it warns in earlier versions of react
92
+ return () => ref.removeEventListener('wheel', onWheel);
93
+ }
94
+ return () => { };
95
+ },
96
+ // eslint-disable-next-line react-hooks/exhaustive-deps
97
+ [model, height, width]);
98
+ const k3 = useCallback((ref) => model.setClickMapCanvasRef(ref),
99
+ // eslint-disable-next-line react-hooks/exhaustive-deps
100
+ [model, height, width]);
101
+ const k4 = useCallback((ref) => model.setCigarClickMapCanvasRef(ref),
102
+ // eslint-disable-next-line react-hooks/exhaustive-deps
103
+ [model, height, width]);
104
+ return (React.createElement("div", { className: classes.rel },
105
+ React.createElement("canvas", { ref: k1, width: width, height: height, className: cx(classes.abs, classes.none) }),
106
+ React.createElement("canvas", { ref: k2, onMouseMove: event => {
71
107
  var _a;
72
108
  if (mouseCurrDownX !== undefined) {
73
109
  xOffset.current += mouseCurrDownX - event.clientX;
@@ -127,95 +163,12 @@ const LinearSyntenyRendering = observer(function ({ model, }) {
127
163
  setMouseCurrDownX(undefined);
128
164
  if (mouseInitialDownX !== undefined &&
129
165
  Math.abs(evt.clientX - mouseInitialDownX) < 5) {
130
- onSyntenyClick(evt, model);
166
+ onSynClick(evt, model);
131
167
  }
132
- }, onContextMenu: evt => {
133
- onSyntenyContextClick(evt, model, setAnchorEl);
134
- }, "data-testid": "synteny_canvas", className: classes.abs, width: width, height: height }),
168
+ }, onContextMenu: evt => onSynContextClick(evt, model, setAnchorEl), "data-testid": "synteny_canvas", className: classes.abs, width: width, height: height }),
135
169
  React.createElement("canvas", { ref: k3, className: classes.pix, width: width, height: height }),
136
170
  React.createElement("canvas", { ref: k4, className: classes.pix, width: width, height: height }),
137
- model.mouseoverId && tooltip && currX && currY ? (React.createElement(SyntenyTooltip, { title: tooltip })) : null,
171
+ mouseoverId && tooltip && currX && currY ? (React.createElement(SyntenyTooltip, { title: tooltip })) : null,
138
172
  anchorEl ? (React.createElement(SyntenyContextMenu, { model: model, anchorEl: anchorEl, onClose: () => setAnchorEl(undefined) })) : null));
139
173
  });
140
- function onSyntenyClick(event, model) {
141
- const ref1 = model.clickMapCanvas;
142
- const ref2 = model.cigarClickMapCanvas;
143
- if (!ref1 || !ref2) {
144
- return;
145
- }
146
- const rect = ref1.getBoundingClientRect();
147
- const ctx1 = ref1.getContext('2d');
148
- const ctx2 = ref2.getContext('2d');
149
- if (!ctx1 || !ctx2) {
150
- return;
151
- }
152
- const x = event.clientX - rect.left;
153
- const y = event.clientY - rect.top;
154
- const [r1, g1, b1] = ctx1.getImageData(x, y, 1, 1).data;
155
- const unitMultiplier = Math.floor(MAX_COLOR_RANGE / model.numFeats);
156
- const id = getId(r1, g1, b1, unitMultiplier);
157
- const feat = model.featPositions[id];
158
- if (feat) {
159
- const { f } = feat;
160
- model.setClickId(f.id());
161
- const session = getSession(model);
162
- if (isSessionModelWithWidgets(session)) {
163
- session.showWidget(session.addWidget('SyntenyFeatureWidget', 'syntenyFeature', {
164
- featureData: {
165
- feature1: f.toJSON(),
166
- feature2: f.get('mate'),
167
- },
168
- }));
169
- }
170
- }
171
- return feat;
172
- }
173
- function onSyntenyContextClick(event, model, setAnchorEl) {
174
- event.preventDefault();
175
- const ref1 = model.clickMapCanvas;
176
- const ref2 = model.cigarClickMapCanvas;
177
- if (!ref1 || !ref2) {
178
- return;
179
- }
180
- const rect = ref1.getBoundingClientRect();
181
- const ctx1 = ref1.getContext('2d');
182
- const ctx2 = ref2.getContext('2d');
183
- if (!ctx1 || !ctx2) {
184
- return;
185
- }
186
- const { clientX, clientY } = event;
187
- const x = clientX - rect.left;
188
- const y = clientY - rect.top;
189
- const [r1, g1, b1] = ctx1.getImageData(x, y, 1, 1).data;
190
- const unitMultiplier = Math.floor(MAX_COLOR_RANGE / model.numFeats);
191
- const id = getId(r1, g1, b1, unitMultiplier);
192
- const f = model.featPositions[id];
193
- if (f) {
194
- model.setClickId(f.f.id());
195
- setAnchorEl({ clientX, clientY, feature: f });
196
- }
197
- }
198
- function getTooltip(f, cigarOp, cigarOpLen) {
199
- // @ts-expect-error
200
- const f1 = f.toJSON();
201
- const f2 = f1.mate;
202
- const l1 = f1.end - f1.start;
203
- const l2 = f2.end - f2.start;
204
- const identity = f1.identity;
205
- const n1 = f1.name;
206
- const n2 = f2.name;
207
- return [
208
- `Loc1: ${assembleLocString(f1)}`,
209
- `Loc2: ${assembleLocString(f2)}`,
210
- `Inverted: ${f1.strand === -1}`,
211
- `Query len: ${l1.toLocaleString('en-US')}`,
212
- `Target len: ${l2.toLocaleString('en-US')}`,
213
- identity ? `Identity: ${identity.toPrecision(2)}` : '',
214
- cigarOp ? `CIGAR operator: ${cigarOp}${cigarOpLen}` : '',
215
- n1 ? `Name 1: ${n1}` : '',
216
- n2 ? `Name 1: ${n2}` : '',
217
- ]
218
- .filter(f => !!f)
219
- .join('<br/>');
220
- }
221
174
  export default LinearSyntenyRendering;
@@ -1,7 +1,14 @@
1
+ import React from 'react';
1
2
  import { Feature } from '@jbrowse/core/util';
3
+ import { LinearSyntenyDisplayModel } from '../model';
2
4
  interface Pos {
3
5
  offsetPx: number;
4
6
  }
7
+ export interface ClickCoord {
8
+ clientX: number;
9
+ clientY: number;
10
+ feature: any;
11
+ }
5
12
  interface FeatPos {
6
13
  p11: Pos;
7
14
  p12: Pos;
@@ -24,4 +31,7 @@ export declare function drawMatchSimple({ feature, ctx, offsets, cb, height, dra
24
31
  export declare function draw(ctx: CanvasRenderingContext2D, x1: number, x2: number, y1: number, x3: number, x4: number, y2: number, mid: number, drawCurves?: boolean): void;
25
32
  export declare function drawBox(ctx: CanvasRenderingContext2D, x1: number, x2: number, y1: number, x3: number, x4: number, y2: number): void;
26
33
  export declare function drawBezierBox(ctx: CanvasRenderingContext2D, x1: number, x2: number, y1: number, x3: number, x4: number, y2: number, mid: number): void;
34
+ export declare function onSynClick(event: React.MouseEvent, model: LinearSyntenyDisplayModel): import("../model").FeatPos | undefined;
35
+ export declare function onSynContextClick(event: React.MouseEvent, model: LinearSyntenyDisplayModel, setAnchorEl: (arg: ClickCoord) => void): void;
36
+ export declare function getTooltip(f: Feature, cigarOp?: string, cigarOpLen?: string): string;
27
37
  export {};
@@ -1,4 +1,6 @@
1
- import { doesIntersect2 } from '@jbrowse/core/util';
1
+ import { assembleLocString, doesIntersect2, getSession, isSessionModelWithWidgets, } from '@jbrowse/core/util';
2
+ // locals
3
+ import { getId, MAX_COLOR_RANGE } from '../drawSynteny';
2
4
  export function drawMatchSimple({ feature, ctx, offsets, cb, height, drawCurves, oobLimit, viewWidth, hideTiny, }) {
3
5
  const { p11, p12, p21, p22 } = feature;
4
6
  const x11 = p11.offsetPx - offsets[0];
@@ -74,3 +76,84 @@ export function drawBezierBox(ctx, x1, x2, y1, x3, x4, y2, mid) {
74
76
  ctx.closePath();
75
77
  ctx.fill();
76
78
  }
79
+ export function onSynClick(event, model) {
80
+ const ref1 = model.clickMapCanvas;
81
+ const ref2 = model.cigarClickMapCanvas;
82
+ if (!ref1 || !ref2) {
83
+ return;
84
+ }
85
+ const rect = ref1.getBoundingClientRect();
86
+ const ctx1 = ref1.getContext('2d');
87
+ const ctx2 = ref2.getContext('2d');
88
+ if (!ctx1 || !ctx2) {
89
+ return;
90
+ }
91
+ const x = event.clientX - rect.left;
92
+ const y = event.clientY - rect.top;
93
+ const [r1, g1, b1] = ctx1.getImageData(x, y, 1, 1).data;
94
+ const unitMultiplier = Math.floor(MAX_COLOR_RANGE / model.numFeats);
95
+ const id = getId(r1, g1, b1, unitMultiplier);
96
+ const feat = model.featPositions[id];
97
+ if (feat) {
98
+ const { f } = feat;
99
+ model.setClickId(f.id());
100
+ const session = getSession(model);
101
+ if (isSessionModelWithWidgets(session)) {
102
+ session.showWidget(session.addWidget('SyntenyFeatureWidget', 'syntenyFeature', {
103
+ featureData: {
104
+ feature1: f.toJSON(),
105
+ feature2: f.get('mate'),
106
+ },
107
+ }));
108
+ }
109
+ }
110
+ return feat;
111
+ }
112
+ export function onSynContextClick(event, model, setAnchorEl) {
113
+ event.preventDefault();
114
+ const ref1 = model.clickMapCanvas;
115
+ const ref2 = model.cigarClickMapCanvas;
116
+ if (!ref1 || !ref2) {
117
+ return;
118
+ }
119
+ const rect = ref1.getBoundingClientRect();
120
+ const ctx1 = ref1.getContext('2d');
121
+ const ctx2 = ref2.getContext('2d');
122
+ if (!ctx1 || !ctx2) {
123
+ return;
124
+ }
125
+ const { clientX, clientY } = event;
126
+ const x = clientX - rect.left;
127
+ const y = clientY - rect.top;
128
+ const [r1, g1, b1] = ctx1.getImageData(x, y, 1, 1).data;
129
+ const unitMultiplier = Math.floor(MAX_COLOR_RANGE / model.numFeats);
130
+ const id = getId(r1, g1, b1, unitMultiplier);
131
+ const f = model.featPositions[id];
132
+ if (f) {
133
+ model.setClickId(f.f.id());
134
+ setAnchorEl({ clientX, clientY, feature: f });
135
+ }
136
+ }
137
+ export function getTooltip(f, cigarOp, cigarOpLen) {
138
+ // @ts-expect-error
139
+ const f1 = f.toJSON();
140
+ const f2 = f1.mate;
141
+ const l1 = f1.end - f1.start;
142
+ const l2 = f2.end - f2.start;
143
+ const identity = f1.identity;
144
+ const n1 = f1.name;
145
+ const n2 = f2.name;
146
+ return [
147
+ `Loc1: ${assembleLocString(f1)}`,
148
+ `Loc2: ${assembleLocString(f2)}`,
149
+ `Inverted: ${f1.strand === -1}`,
150
+ `Query len: ${l1.toLocaleString('en-US')}`,
151
+ `Target len: ${l2.toLocaleString('en-US')}`,
152
+ identity ? `Identity: ${identity.toPrecision(2)}` : '',
153
+ cigarOp ? `CIGAR operator: ${cigarOp}${cigarOpLen}` : '',
154
+ n1 ? `Name 1: ${n1}` : '',
155
+ n2 ? `Name 1: ${n2}` : '',
156
+ ]
157
+ .filter(f => !!f)
158
+ .join('<br/>');
159
+ }
@@ -65,6 +65,7 @@ const LinearSyntenyViewImportForm = observer(function ({ model, }) {
65
65
  };
66
66
  })));
67
67
  model.views.forEach(view => view.setWidth(model.width));
68
+ model.views.forEach(view => view.showAllRegions());
68
69
  if (sessionTrackData) {
69
70
  session.addTrackConf(sessionTrackData);
70
71
  model.toggleTrack(sessionTrackData.trackId);