@jbrowse/plugin-variants 4.0.4 → 4.1.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.
Files changed (120) hide show
  1. package/esm/LDDisplay/SharedLDConfigSchema.d.ts +102 -0
  2. package/esm/LDDisplay/SharedLDConfigSchema.js +83 -0
  3. package/esm/LDDisplay/afterAttach.d.ts +2 -0
  4. package/esm/LDDisplay/afterAttach.js +123 -0
  5. package/esm/LDDisplay/components/BaseDisplayComponent.d.ts +15 -0
  6. package/esm/LDDisplay/components/BaseDisplayComponent.js +39 -0
  7. package/esm/LDDisplay/components/LDColorLegend.d.ts +15 -0
  8. package/esm/LDDisplay/components/LDColorLegend.js +75 -0
  9. package/esm/LDDisplay/components/LDDisplayComponent.d.ts +5 -0
  10. package/esm/LDDisplay/components/LDDisplayComponent.js +203 -0
  11. package/esm/LDDisplay/components/LinesConnectingMatrixToGenomicPosition.d.ts +16 -0
  12. package/esm/LDDisplay/components/LinesConnectingMatrixToGenomicPosition.js +109 -0
  13. package/esm/LDDisplay/configSchema1.d.ts +115 -0
  14. package/esm/LDDisplay/configSchema1.js +16 -0
  15. package/esm/LDDisplay/configSchema2.d.ts +115 -0
  16. package/esm/LDDisplay/configSchema2.js +16 -0
  17. package/esm/LDDisplay/index.d.ts +2 -0
  18. package/esm/LDDisplay/index.js +35 -0
  19. package/esm/LDDisplay/renderSvg.d.ts +3 -0
  20. package/esm/LDDisplay/renderSvg.js +36 -0
  21. package/esm/LDDisplay/shared.d.ts +367 -0
  22. package/esm/LDDisplay/shared.js +467 -0
  23. package/esm/LDDisplay/stateModel1.d.ts +365 -0
  24. package/esm/LDDisplay/stateModel1.js +10 -0
  25. package/esm/LDDisplay/stateModel2.d.ts +365 -0
  26. package/esm/LDDisplay/stateModel2.js +10 -0
  27. package/esm/LDRenderer/LDRenderer.d.ts +30 -0
  28. package/esm/LDRenderer/LDRenderer.js +109 -0
  29. package/esm/LDRenderer/components/LDRendering.d.ts +2 -0
  30. package/esm/LDRenderer/components/LDRendering.js +4 -0
  31. package/esm/LDRenderer/configSchema.d.ts +8 -0
  32. package/esm/LDRenderer/configSchema.js +10 -0
  33. package/esm/LDRenderer/index.d.ts +2 -0
  34. package/esm/LDRenderer/index.js +11 -0
  35. package/esm/LDRenderer/makeImageData.d.ts +20 -0
  36. package/esm/LDRenderer/makeImageData.js +158 -0
  37. package/esm/LDRenderer/types.d.ts +8 -0
  38. package/esm/LDRenderer/types.js +1 -0
  39. package/esm/LDTrack/configSchema.d.ts +85 -0
  40. package/esm/LDTrack/configSchema.js +7 -0
  41. package/esm/LDTrack/index.d.ts +2 -0
  42. package/esm/LDTrack/index.js +14 -0
  43. package/esm/LinearVariantDisplay/model.d.ts +126 -42
  44. package/esm/LinearVariantDisplay/model.js +46 -8
  45. package/esm/MultiLinearVariantDisplay/configSchema.d.ts +27 -1
  46. package/esm/MultiLinearVariantDisplay/model.d.ts +2635 -31
  47. package/esm/MultiLinearVariantDisplay/model.js +6 -0
  48. package/esm/MultiLinearVariantDisplay/renderSvg.d.ts +10 -2
  49. package/esm/MultiLinearVariantMatrixDisplay/configSchema.d.ts +25 -0
  50. package/esm/MultiLinearVariantMatrixDisplay/configSchema.js +26 -0
  51. package/esm/MultiLinearVariantMatrixDisplay/model.d.ts +2636 -32
  52. package/esm/MultiLinearVariantMatrixDisplay/model.js +6 -0
  53. package/esm/MultiLinearVariantMatrixRenderer/MultiLinearVariantMatrixRenderer.d.ts +2 -2
  54. package/esm/MultiLinearVariantMatrixRenderer/MultiLinearVariantMatrixRenderer.js +11 -9
  55. package/esm/MultiLinearVariantMatrixRenderer/components/MultiLinearVariantMatrixRendering.d.ts +8 -0
  56. package/esm/MultiLinearVariantMatrixRenderer/components/MultiLinearVariantMatrixRendering.js +14 -2
  57. package/esm/MultiLinearVariantRenderer/MultiVariantRenderer.d.ts +2 -2
  58. package/esm/MultiLinearVariantRenderer/MultiVariantRenderer.js +4 -3
  59. package/esm/MultiLinearVariantRenderer/components/MultiLinearVariantRendering.d.ts +4 -0
  60. package/esm/MultiLinearVariantRenderer/components/MultiLinearVariantRendering.js +23 -2
  61. package/esm/MultiLinearVariantRenderer/makeImageData.js +4 -1
  62. package/esm/PlinkLDAdapter/PlinkLDAdapter.d.ts +25 -0
  63. package/esm/PlinkLDAdapter/PlinkLDAdapter.js +147 -0
  64. package/esm/PlinkLDAdapter/PlinkLDTabixAdapter.d.ts +24 -0
  65. package/esm/PlinkLDAdapter/PlinkLDTabixAdapter.js +156 -0
  66. package/esm/PlinkLDAdapter/configSchema.d.ts +10 -0
  67. package/esm/PlinkLDAdapter/configSchema.js +25 -0
  68. package/esm/PlinkLDAdapter/configSchemaTabix.d.ts +24 -0
  69. package/esm/PlinkLDAdapter/configSchemaTabix.js +46 -0
  70. package/esm/PlinkLDAdapter/index.d.ts +2 -0
  71. package/esm/PlinkLDAdapter/index.js +25 -0
  72. package/esm/PlinkLDAdapter/types.d.ts +29 -0
  73. package/esm/PlinkLDAdapter/types.js +1 -0
  74. package/esm/VariantFeatureWidget/VariantSampleGrid/VariantSampleGrid.js +1 -1
  75. package/esm/VariantRPC/MultiVariantGetFeatureDetails.d.ts +14 -0
  76. package/esm/VariantRPC/MultiVariantGetFeatureDetails.js +15 -0
  77. package/esm/VariantRPC/getLDMatrix.d.ts +48 -0
  78. package/esm/VariantRPC/getLDMatrix.js +388 -0
  79. package/esm/VariantRPC/getLDMatrixFromPlink.d.ts +16 -0
  80. package/esm/VariantRPC/getLDMatrixFromPlink.js +105 -0
  81. package/esm/VariantRPC/types.d.ts +3 -0
  82. package/esm/VcfAdapter/VcfAdapter.d.ts +1 -1
  83. package/esm/VcfAdapter/VcfAdapter.js +1 -2
  84. package/esm/VcfExtensionPoints/index.js +29 -3
  85. package/esm/VcfFeature/index.d.ts +2 -1
  86. package/esm/VcfFeature/index.js +4 -2
  87. package/esm/index.d.ts +1 -0
  88. package/esm/index.js +23 -0
  89. package/esm/shared/MultiVariantBaseModel.d.ts +2626 -26
  90. package/esm/shared/MultiVariantBaseModel.js +88 -39
  91. package/esm/shared/SharedVariantConfigSchema.d.ts +27 -1
  92. package/esm/shared/SharedVariantConfigSchema.js +28 -1
  93. package/esm/shared/VariantFeatureCache.d.ts +27 -0
  94. package/esm/shared/VariantFeatureCache.js +48 -0
  95. package/esm/shared/VariantRendererType.d.ts +23 -0
  96. package/esm/shared/VariantRendererType.js +15 -0
  97. package/esm/shared/applyColorPalette.d.ts +9 -0
  98. package/esm/shared/applyColorPalette.js +23 -0
  99. package/esm/shared/colorByAutorun.d.ts +10 -0
  100. package/esm/shared/colorByAutorun.js +39 -0
  101. package/esm/shared/components/AddFiltersDialog.d.ts +3 -3
  102. package/esm/shared/components/AddFiltersDialog.js +29 -22
  103. package/esm/shared/components/LDFilterDialog.d.ts +13 -0
  104. package/esm/shared/components/LDFilterDialog.js +102 -0
  105. package/esm/shared/components/MAFFilterDialog.js +23 -16
  106. package/esm/shared/components/RecombinationTrack.d.ts +21 -0
  107. package/esm/shared/components/RecombinationTrack.js +54 -0
  108. package/esm/shared/components/RecombinationYScaleBar.d.ts +7 -0
  109. package/esm/shared/components/RecombinationYScaleBar.js +34 -0
  110. package/esm/shared/components/SetColorDialogRowPalettizer.d.ts +3 -8
  111. package/esm/shared/components/SetColorDialogRowPalettizer.js +2 -14
  112. package/esm/shared/drawAlleleCount.js +9 -0
  113. package/esm/shared/drawPhased.d.ts +1 -1
  114. package/esm/shared/drawPhased.js +31 -2
  115. package/esm/shared/mafFilterUtils.d.ts +5 -0
  116. package/esm/shared/mafFilterUtils.js +17 -0
  117. package/esm/shared/minorAlleleFrequencyUtils.d.ts +2 -0
  118. package/esm/shared/minorAlleleFrequencyUtils.js +259 -17
  119. package/esm/shared/setupMultiVariantAutoruns.js +2 -0
  120. package/package.json +11 -10
@@ -0,0 +1,102 @@
1
+ export default function sharedLDConfigFactory(): import("node_modules/@jbrowse/core/src/configuration/configurationSchema").ConfigurationSchemaType<{
2
+ minorAlleleFrequencyFilter: {
3
+ type: string;
4
+ defaultValue: number;
5
+ };
6
+ lengthCutoffFilter: {
7
+ type: string;
8
+ defaultValue: number;
9
+ };
10
+ lineZoneHeight: {
11
+ type: string;
12
+ defaultValue: number;
13
+ };
14
+ ldMetric: {
15
+ type: string;
16
+ model: import("@jbrowse/mobx-state-tree").ISimpleType<string>;
17
+ defaultValue: string;
18
+ };
19
+ colorScheme: {
20
+ type: string;
21
+ defaultValue: string;
22
+ };
23
+ showLegend: {
24
+ type: string;
25
+ defaultValue: boolean;
26
+ };
27
+ showLDTriangle: {
28
+ type: string;
29
+ defaultValue: boolean;
30
+ };
31
+ showRecombination: {
32
+ type: string;
33
+ defaultValue: boolean;
34
+ };
35
+ recombinationZoneHeight: {
36
+ type: string;
37
+ defaultValue: number;
38
+ };
39
+ fitToHeight: {
40
+ type: string;
41
+ defaultValue: boolean;
42
+ };
43
+ hweFilterThreshold: {
44
+ type: string;
45
+ defaultValue: number;
46
+ };
47
+ callRateFilter: {
48
+ type: string;
49
+ defaultValue: number;
50
+ };
51
+ showVerticalGuides: {
52
+ type: string;
53
+ defaultValue: boolean;
54
+ };
55
+ showLabels: {
56
+ type: string;
57
+ defaultValue: boolean;
58
+ };
59
+ tickHeight: {
60
+ type: string;
61
+ defaultValue: number;
62
+ };
63
+ useGenomicPositions: {
64
+ type: string;
65
+ defaultValue: boolean;
66
+ };
67
+ signedLD: {
68
+ type: string;
69
+ defaultValue: boolean;
70
+ };
71
+ jexlFilters: {
72
+ type: string;
73
+ defaultValue: never[];
74
+ };
75
+ }, import("node_modules/@jbrowse/core/src/configuration/configurationSchema").ConfigurationSchemaOptions<import("node_modules/@jbrowse/core/src/configuration/configurationSchema").ConfigurationSchemaType<{
76
+ maxFeatureScreenDensity: {
77
+ type: string;
78
+ description: string;
79
+ defaultValue: number;
80
+ };
81
+ fetchSizeLimit: {
82
+ type: string;
83
+ defaultValue: number;
84
+ description: string;
85
+ };
86
+ height: {
87
+ type: string;
88
+ defaultValue: number;
89
+ description: string;
90
+ };
91
+ mouseover: {
92
+ type: string;
93
+ description: string;
94
+ defaultValue: string;
95
+ contextVariable: string[];
96
+ };
97
+ jexlFilters: {
98
+ type: string;
99
+ description: string;
100
+ defaultValue: never[];
101
+ };
102
+ }, import("node_modules/@jbrowse/core/src/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, "displayId">>, undefined>>;
@@ -0,0 +1,83 @@
1
+ import { ConfigurationSchema } from '@jbrowse/core/configuration';
2
+ import { types } from '@jbrowse/mobx-state-tree';
3
+ import { baseLinearDisplayConfigSchema } from '@jbrowse/plugin-linear-genome-view';
4
+ export default function sharedLDConfigFactory() {
5
+ return ConfigurationSchema('SharedLDDisplay', {
6
+ minorAlleleFrequencyFilter: {
7
+ type: 'number',
8
+ defaultValue: 0.1,
9
+ },
10
+ lengthCutoffFilter: {
11
+ type: 'number',
12
+ defaultValue: Number.MAX_SAFE_INTEGER,
13
+ },
14
+ lineZoneHeight: {
15
+ type: 'number',
16
+ defaultValue: 100,
17
+ },
18
+ ldMetric: {
19
+ type: 'stringEnum',
20
+ model: types.enumeration('LDMetric', ['r2', 'dprime']),
21
+ defaultValue: 'r2',
22
+ },
23
+ colorScheme: {
24
+ type: 'string',
25
+ defaultValue: '',
26
+ },
27
+ showLegend: {
28
+ type: 'boolean',
29
+ defaultValue: false,
30
+ },
31
+ showLDTriangle: {
32
+ type: 'boolean',
33
+ defaultValue: true,
34
+ },
35
+ showRecombination: {
36
+ type: 'boolean',
37
+ defaultValue: false,
38
+ },
39
+ recombinationZoneHeight: {
40
+ type: 'number',
41
+ defaultValue: 50,
42
+ },
43
+ fitToHeight: {
44
+ type: 'boolean',
45
+ defaultValue: false,
46
+ },
47
+ hweFilterThreshold: {
48
+ type: 'number',
49
+ defaultValue: 0,
50
+ },
51
+ callRateFilter: {
52
+ type: 'number',
53
+ defaultValue: 0,
54
+ },
55
+ showVerticalGuides: {
56
+ type: 'boolean',
57
+ defaultValue: true,
58
+ },
59
+ showLabels: {
60
+ type: 'boolean',
61
+ defaultValue: false,
62
+ },
63
+ tickHeight: {
64
+ type: 'number',
65
+ defaultValue: 6,
66
+ },
67
+ useGenomicPositions: {
68
+ type: 'boolean',
69
+ defaultValue: false,
70
+ },
71
+ signedLD: {
72
+ type: 'boolean',
73
+ defaultValue: false,
74
+ },
75
+ jexlFilters: {
76
+ type: 'stringArray',
77
+ defaultValue: [],
78
+ },
79
+ }, {
80
+ baseConfiguration: baseLinearDisplayConfigSchema,
81
+ explicitlyTyped: true,
82
+ });
83
+ }
@@ -0,0 +1,2 @@
1
+ import type { SharedLDModel } from './shared.ts';
2
+ export declare function doAfterAttach(self: SharedLDModel): void;
@@ -0,0 +1,123 @@
1
+ import { getContainingView, getRpcSessionId, getSession, isAbortException, } from '@jbrowse/core/util';
2
+ import { createStopToken, stopStopToken } from '@jbrowse/core/util/stopToken';
3
+ import { addDisposer, isAlive } from '@jbrowse/mobx-state-tree';
4
+ import { drawCanvasImageData } from '@jbrowse/plugin-linear-genome-view';
5
+ import { autorun, untracked } from 'mobx';
6
+ export function doAfterAttach(self) {
7
+ const performRender = async () => {
8
+ if (self.isMinimized) {
9
+ return;
10
+ }
11
+ if (!self.showLDTriangle) {
12
+ return;
13
+ }
14
+ const view = getContainingView(self);
15
+ const { bpPerPx, dynamicBlocks } = view;
16
+ const regions = dynamicBlocks.contentBlocks;
17
+ if (!regions.length) {
18
+ return;
19
+ }
20
+ if (bpPerPx > 1000) {
21
+ return;
22
+ }
23
+ const { adapterConfig } = self;
24
+ const renderProps = untracked(() => self.renderProps());
25
+ try {
26
+ const session = getSession(self);
27
+ const { rpcManager } = session;
28
+ const rpcSessionId = getRpcSessionId(self);
29
+ const previousToken = untracked(() => self.renderingStopToken);
30
+ if (previousToken) {
31
+ stopStopToken(previousToken);
32
+ }
33
+ const stopToken = createStopToken();
34
+ self.setRenderingStopToken(stopToken);
35
+ self.setLoading(true);
36
+ self.setCanvasDrawn(false);
37
+ const result = (await rpcManager.call(rpcSessionId, 'CoreRender', {
38
+ sessionId: rpcSessionId,
39
+ rendererType: 'LDRenderer',
40
+ regions: [...regions],
41
+ adapterConfig,
42
+ bpPerPx,
43
+ stopToken,
44
+ ...renderProps,
45
+ }, {
46
+ statusCallback: (msg) => {
47
+ if (isAlive(self)) {
48
+ self.setStatusMessage(msg);
49
+ }
50
+ },
51
+ }));
52
+ if (result.imageData) {
53
+ self.setRenderingImageData(result.imageData);
54
+ self.setLastDrawnOffsetPx(view.offsetPx);
55
+ self.setLastDrawnBpPerPx(view.bpPerPx);
56
+ }
57
+ self.setFlatbushData(result.flatbush, result.items ?? [], result.ldData?.snps ?? [], result.maxScore ?? 1, result.yScalar ?? 1, result.w ?? 0);
58
+ self.setFilterStats(result.filterStats);
59
+ self.setRecombination(result.recombination);
60
+ }
61
+ catch (error) {
62
+ if (!isAbortException(error)) {
63
+ console.error(error);
64
+ if (isAlive(self)) {
65
+ self.setError(error);
66
+ }
67
+ }
68
+ }
69
+ finally {
70
+ if (isAlive(self)) {
71
+ self.setRenderingStopToken(undefined);
72
+ self.setLoading(false);
73
+ }
74
+ }
75
+ };
76
+ addDisposer(self, autorun(() => {
77
+ if (self.isMinimized) {
78
+ return;
79
+ }
80
+ const view = getContainingView(self);
81
+ if (!view.initialized) {
82
+ return;
83
+ }
84
+ const { dynamicBlocks } = view;
85
+ const regions = dynamicBlocks.contentBlocks;
86
+ self.ldMetric;
87
+ self.minorAlleleFrequencyFilter;
88
+ self.lengthCutoffFilter;
89
+ self.hweFilterThreshold;
90
+ self.callRateFilter;
91
+ self.colorScheme;
92
+ self.showLDTriangle;
93
+ self.fitToHeight;
94
+ self.useGenomicPositions;
95
+ self.signedLD;
96
+ if (self.fitToHeight) {
97
+ self.ldCanvasHeight;
98
+ }
99
+ if (untracked(() => self.error) || !regions.length) {
100
+ return;
101
+ }
102
+ performRender();
103
+ }, {
104
+ delay: 500,
105
+ name: 'LDDisplayRender',
106
+ }));
107
+ addDisposer(self, autorun(() => {
108
+ if (self.isMinimized) {
109
+ return;
110
+ }
111
+ const view = getContainingView(self);
112
+ if (!view.initialized) {
113
+ return;
114
+ }
115
+ const success = drawCanvasImageData(self.ref, self.renderingImageData);
116
+ if (isAlive(self)) {
117
+ self.setCanvasDrawn(success);
118
+ }
119
+ }, {
120
+ delay: 1000,
121
+ name: 'LDDisplayCanvas',
122
+ }));
123
+ }
@@ -0,0 +1,15 @@
1
+ import React from 'react';
2
+ export interface LDDisplayModel {
3
+ error?: unknown;
4
+ regionTooLarge?: boolean;
5
+ reload: () => void;
6
+ regionCannotBeRendered: () => React.ReactElement | null;
7
+ drawn: boolean;
8
+ loading: boolean;
9
+ statusMessage?: string;
10
+ }
11
+ declare const LDBaseDisplayComponent: ({ model, children, }: {
12
+ model: LDDisplayModel;
13
+ children?: React.ReactNode;
14
+ }) => import("react/jsx-runtime").JSX.Element | null;
15
+ export default LDBaseDisplayComponent;
@@ -0,0 +1,39 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import { ErrorMessage, LoadingEllipses } from '@jbrowse/core/ui';
4
+ import { makeStyles } from '@jbrowse/core/util/tss-react';
5
+ import { observer } from 'mobx-react';
6
+ const useStyles = makeStyles()({
7
+ loading: {
8
+ position: 'absolute',
9
+ top: 0,
10
+ left: 0,
11
+ right: 0,
12
+ bottom: 0,
13
+ backgroundColor: 'rgba(255, 255, 255, 0.15)',
14
+ backgroundImage: `repeating-linear-gradient(45deg, transparent, transparent 8px, rgba(0, 0, 0, 0.05) 8px, rgba(0, 0, 0, 0.05) 16px)`,
15
+ pointerEvents: 'none',
16
+ display: 'flex',
17
+ justifyContent: 'center',
18
+ alignItems: 'center',
19
+ zIndex: 1,
20
+ },
21
+ loadingMessage: {
22
+ zIndex: 2,
23
+ pointerEvents: 'none',
24
+ },
25
+ });
26
+ const LDBaseDisplayComponent = observer(function LDBaseDisplayComponent({ model, children, }) {
27
+ const { error, regionTooLarge } = model;
28
+ return error ? (_jsx(ErrorMessage, { error: error })) : regionTooLarge ? (model.regionCannotBeRendered()) : (_jsx(DataDisplay, { model: model, children: children }));
29
+ });
30
+ const DataDisplay = observer(function DataDisplay({ model, children, }) {
31
+ const { drawn, loading } = model;
32
+ return (_jsxs("div", { "data-testid": `drawn-${drawn}`, children: [children, loading ? _jsx(LoadingBar, { model: model }) : null] }));
33
+ });
34
+ const LoadingBar = observer(function LoadingBar({ model, }) {
35
+ const { classes } = useStyles();
36
+ const { statusMessage } = model;
37
+ return (_jsx("div", { className: classes.loading, children: _jsx("div", { className: classes.loadingMessage, children: _jsx(LoadingEllipses, { message: statusMessage }) }) }));
38
+ });
39
+ export default LDBaseDisplayComponent;
@@ -0,0 +1,15 @@
1
+ export declare function LDColorLegendContent({ ldMetric, signedLD, x, y, }: {
2
+ ldMetric: string;
3
+ signedLD?: boolean;
4
+ x?: number;
5
+ y?: number;
6
+ }): import("react/jsx-runtime").JSX.Element;
7
+ export declare function LDSVGColorLegend({ ldMetric, width, signedLD, }: {
8
+ ldMetric: string;
9
+ width: number;
10
+ signedLD?: boolean;
11
+ }): import("react/jsx-runtime").JSX.Element;
12
+ export default function LDColorLegend({ ldMetric, signedLD, }: {
13
+ ldMetric: string;
14
+ signedLD?: boolean;
15
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,75 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ const LEGEND_WIDTH = 120;
3
+ const LEGEND_HEIGHT = 40;
4
+ const BAR_WIDTH = 100;
5
+ const BAR_HEIGHT = 12;
6
+ const PADDING = 8;
7
+ const FONT_SIZE = 10;
8
+ function getColorStops(ldMetric, signedLD) {
9
+ if (signedLD) {
10
+ if (ldMetric === 'dprime') {
11
+ return [
12
+ { offset: '0%', color: 'rgb(0,100,0)' },
13
+ { offset: '25%', color: 'rgb(64,192,64)' },
14
+ { offset: '50%', color: 'rgb(255,255,255)' },
15
+ { offset: '75%', color: 'rgb(128,128,255)' },
16
+ { offset: '100%', color: 'rgb(0,0,160)' },
17
+ ];
18
+ }
19
+ return [
20
+ { offset: '0%', color: 'rgb(0,0,160)' },
21
+ { offset: '25%', color: 'rgb(128,128,255)' },
22
+ { offset: '50%', color: 'rgb(255,255,255)' },
23
+ { offset: '75%', color: 'rgb(255,128,128)' },
24
+ { offset: '100%', color: 'rgb(160,0,0)' },
25
+ ];
26
+ }
27
+ if (ldMetric === 'dprime') {
28
+ return [
29
+ { offset: '0%', color: 'rgb(255,255,255)' },
30
+ { offset: '50%', color: 'rgb(128,128,255)' },
31
+ { offset: '100%', color: 'rgb(0,0,160)' },
32
+ ];
33
+ }
34
+ return [
35
+ { offset: '0%', color: 'rgb(255,255,255)' },
36
+ { offset: '50%', color: 'rgb(255,128,128)' },
37
+ { offset: '100%', color: 'rgb(160,0,0)' },
38
+ ];
39
+ }
40
+ function getLabels(ldMetric, signedLD) {
41
+ if (signedLD) {
42
+ return {
43
+ min: '-1',
44
+ max: '1',
45
+ metric: ldMetric === 'dprime' ? "D'" : 'R',
46
+ };
47
+ }
48
+ return {
49
+ min: '0',
50
+ max: '1',
51
+ metric: ldMetric === 'dprime' ? "D'" : 'R²',
52
+ };
53
+ }
54
+ export function LDColorLegendContent({ ldMetric, signedLD = false, x = 0, y = 0, }) {
55
+ const gradientId = `ld-gradient-${ldMetric}-${signedLD ? 'signed' : 'unsigned'}`;
56
+ const stops = getColorStops(ldMetric, signedLD);
57
+ const labels = getLabels(ldMetric, signedLD);
58
+ return (_jsxs("g", { transform: `translate(${x}, ${y})`, children: [_jsx("defs", { children: _jsx("linearGradient", { id: gradientId, x1: "0%", y1: "0%", x2: "100%", y2: "0%", children: stops.map((stop, idx) => (_jsx("stop", { offset: stop.offset, style: { stopColor: stop.color, stopOpacity: 1 } }, idx))) }) }), _jsx("rect", { x: 0, y: 0, width: LEGEND_WIDTH, height: LEGEND_HEIGHT, fill: "rgba(255,255,255,0.9)", stroke: "#ccc", strokeWidth: 1, rx: 4 }), _jsx("rect", { x: PADDING, y: PADDING, width: BAR_WIDTH, height: BAR_HEIGHT, fill: `url(#${gradientId})`, rx: 2 }), _jsx("text", { x: PADDING, y: PADDING + BAR_HEIGHT + FONT_SIZE + 2, fontSize: FONT_SIZE, fill: "black", children: labels.min }), _jsx("text", { x: PADDING + BAR_WIDTH / 2, y: PADDING + BAR_HEIGHT + FONT_SIZE + 2, fontSize: FONT_SIZE, fill: "black", textAnchor: "middle", children: labels.metric }), _jsx("text", { x: PADDING + BAR_WIDTH, y: PADDING + BAR_HEIGHT + FONT_SIZE + 2, fontSize: FONT_SIZE, fill: "black", textAnchor: "end", children: labels.max })] }));
59
+ }
60
+ export function LDSVGColorLegend({ ldMetric, width, signedLD = false, }) {
61
+ const x = width - LEGEND_WIDTH - 10;
62
+ const y = 10;
63
+ return (_jsx(LDColorLegendContent, { ldMetric: ldMetric, signedLD: signedLD, x: x, y: y }));
64
+ }
65
+ export default function LDColorLegend({ ldMetric, signedLD = false, }) {
66
+ return (_jsx("svg", { style: {
67
+ position: 'absolute',
68
+ top: 4,
69
+ right: 4,
70
+ width: LEGEND_WIDTH,
71
+ height: LEGEND_HEIGHT,
72
+ zIndex: 10,
73
+ overflow: 'visible',
74
+ }, children: _jsx(LDColorLegendContent, { ldMetric: ldMetric, signedLD: signedLD }) }));
75
+ }
@@ -0,0 +1,5 @@
1
+ import type { SharedLDModel } from '../shared.ts';
2
+ declare const LDDisplayComponent: ({ model, }: {
3
+ model: SharedLDModel;
4
+ }) => import("react/jsx-runtime").JSX.Element;
5
+ export default LDDisplayComponent;
@@ -0,0 +1,203 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
+ import BaseTooltip from '@jbrowse/core/ui/BaseTooltip';
4
+ import { getContainingView } from '@jbrowse/core/util';
5
+ import Flatbush from '@jbrowse/core/util/flatbush';
6
+ import { observer } from 'mobx-react';
7
+ import BaseDisplayComponent from "./BaseDisplayComponent.js";
8
+ import LDColorLegend from "./LDColorLegend.js";
9
+ import LinesConnectingMatrixToGenomicPosition, { VariantLabels, Wrapper, } from "./LinesConnectingMatrixToGenomicPosition.js";
10
+ import RecombinationTrack from "../../shared/components/RecombinationTrack.js";
11
+ import RecombinationYScaleBar from "../../shared/components/RecombinationYScaleBar.js";
12
+ const SQRT2 = Math.sqrt(2);
13
+ function LDTooltip({ item, x, y, ldMetric, signedLD, }) {
14
+ let metricLabel;
15
+ if (ldMetric === 'dprime') {
16
+ metricLabel = "D'";
17
+ }
18
+ else {
19
+ metricLabel = signedLD ? 'R' : 'R²';
20
+ }
21
+ return (_jsxs(BaseTooltip, { clientPoint: { x: x + 15, y }, children: [_jsx("div", { children: item.snp1.id }), _jsx("div", { children: item.snp2.id }), _jsxs("div", { children: [metricLabel, ": ", item.ldValue.toFixed(3)] })] }));
22
+ }
23
+ function Crosshairs({ hoveredItem, cellWidth, genomicX1, genomicX2, yScalar, lineZoneHeight, tickHeight, width, height, useGenomicPositions, snps, regionStart, bpPerPx, canvasOffset, }) {
24
+ const { i, j } = hoveredItem;
25
+ const toScreen = (x, y) => {
26
+ const rx = (x + y) / SQRT2;
27
+ const ry = (y - x) / SQRT2;
28
+ return { x: rx, y: ry * yScalar + lineZoneHeight };
29
+ };
30
+ let hoveredCenter;
31
+ let snpJPos;
32
+ let snpIPos;
33
+ if (useGenomicPositions && snps.length > 0) {
34
+ const getBoundary = (idx) => {
35
+ const snpPos = snps[idx].start;
36
+ const prevPos = idx > 0 ? snps[idx - 1].start : regionStart;
37
+ const boundaryPos = (prevPos + snpPos) / 2;
38
+ return (boundaryPos - regionStart) / bpPerPx / SQRT2;
39
+ };
40
+ const getNextBoundary = (idx) => {
41
+ if (idx + 1 < snps.length) {
42
+ const snpPos = snps[idx].start;
43
+ const nextPos = snps[idx + 1].start;
44
+ return ((snpPos + nextPos) / 2 - regionStart) / bpPerPx / SQRT2;
45
+ }
46
+ const lastSnpPos = snps[snps.length - 1].start;
47
+ return (lastSnpPos + 50 * bpPerPx - regionStart) / bpPerPx / SQRT2;
48
+ };
49
+ const jBoundary = getBoundary(j);
50
+ const iBoundary = getBoundary(i);
51
+ const jNextBoundary = getNextBoundary(j);
52
+ const iNextBoundary = getNextBoundary(i);
53
+ const cellCenterX = (jBoundary + jNextBoundary) / 2;
54
+ const cellCenterY = (iBoundary + iNextBoundary) / 2;
55
+ hoveredCenter = toScreen(cellCenterX, cellCenterY);
56
+ snpJPos = { x: genomicX1, y: lineZoneHeight };
57
+ snpIPos = { x: genomicX2, y: lineZoneHeight };
58
+ }
59
+ else {
60
+ const w = cellWidth;
61
+ hoveredCenter = toScreen((j + 0.5) * w, (i + 0.5) * w);
62
+ snpJPos = toScreen((j + 0.5) * w, (j + 0.5) * w);
63
+ snpIPos = toScreen((i + 0.5) * w, (i + 0.5) * w);
64
+ }
65
+ return (_jsxs("svg", { style: {
66
+ position: 'absolute',
67
+ left: canvasOffset,
68
+ top: 0,
69
+ width,
70
+ height,
71
+ pointerEvents: 'none',
72
+ }, children: [_jsx("path", { stroke: "rgba(0, 0, 0, 0.6)", strokeWidth: 1, fill: "none", d: `M ${snpJPos.x} ${snpJPos.y} L ${hoveredCenter.x} ${hoveredCenter.y} L ${snpIPos.x} ${snpIPos.y}` }), _jsxs("g", { stroke: "#e00", strokeWidth: "1.5", fill: "none", children: [!useGenomicPositions ? (_jsxs(_Fragment, { children: [_jsx("path", { d: `M ${snpJPos.x} ${snpJPos.y} L ${genomicX1} ${tickHeight}` }), _jsx("path", { d: `M ${snpIPos.x} ${snpIPos.y} L ${genomicX2} ${tickHeight}` })] })) : null, _jsx("path", { d: `M ${genomicX1} 0 L ${genomicX1} ${tickHeight}` }), _jsx("path", { d: `M ${genomicX2} 0 L ${genomicX2} ${tickHeight}` })] })] }));
73
+ }
74
+ function screenToUnrotated(screenX, screenY, yScalar, lineZoneHeight) {
75
+ const matrixY = screenY - lineZoneHeight;
76
+ const scaledY = matrixY / yScalar;
77
+ const x = (screenX - scaledY) / SQRT2;
78
+ const y = (screenX + scaledY) / SQRT2;
79
+ return { x, y };
80
+ }
81
+ const LDCanvas = observer(function LDCanvas({ model, }) {
82
+ const view = getContainingView(model);
83
+ const width = Math.round(view.dynamicBlocks.totalWidthPx);
84
+ const { fullyDrawn, flatbush, flatbushItems, yScalar, cellWidth, showLegend, ldMetric, lineZoneHeight, fitToHeight, ldCanvasHeight, useGenomicPositions, snps, lastDrawnOffsetPx, signedLD, } = model;
85
+ const triangleHeight = width / 2;
86
+ const canvasOnlyHeight = fitToHeight ? ldCanvasHeight : triangleHeight;
87
+ const containerHeight = canvasOnlyHeight + lineZoneHeight;
88
+ const [hoveredItem, setHoveredItem] = useState();
89
+ const [mousePosition, setMousePosition] = useState();
90
+ const containerRef = useRef(null);
91
+ const region = view.dynamicBlocks.contentBlocks[0];
92
+ const bpPerPx = view.bpPerPx;
93
+ const genomicX1 = hoveredItem && region
94
+ ? (hoveredItem.snp2.start - region.start) / bpPerPx
95
+ : undefined;
96
+ const genomicX2 = hoveredItem && region
97
+ ? (hoveredItem.snp1.start - region.start) / bpPerPx
98
+ : undefined;
99
+ const canvasOffset = view.offsetPx >= 0
100
+ ? (lastDrawnOffsetPx ?? 0) - view.offsetPx
101
+ : Math.max(0, -view.offsetPx);
102
+ const guideOffset = canvasOffset;
103
+ useEffect(() => {
104
+ if (genomicX1 !== undefined &&
105
+ genomicX2 !== undefined &&
106
+ model.showVerticalGuides) {
107
+ view.setVolatileGuides([
108
+ { xPos: genomicX1 + guideOffset },
109
+ { xPos: genomicX2 + guideOffset },
110
+ ]);
111
+ }
112
+ else {
113
+ view.setVolatileGuides([]);
114
+ }
115
+ return () => {
116
+ view.setVolatileGuides([]);
117
+ };
118
+ }, [genomicX1, genomicX2, model.showVerticalGuides, view, guideOffset]);
119
+ const flatbushIndex = useMemo(() => (flatbush ? Flatbush.from(flatbush) : null), [flatbush]);
120
+ const cb = useCallback((ref) => {
121
+ model.setRef(ref);
122
+ }, [model, width, canvasOnlyHeight]);
123
+ const onMouseMove = useCallback((event) => {
124
+ const container = containerRef.current;
125
+ if (!container || !flatbushIndex || !flatbushItems.length) {
126
+ setHoveredItem(undefined);
127
+ setMousePosition(undefined);
128
+ return;
129
+ }
130
+ const rect = container.getBoundingClientRect();
131
+ const mouseCanvasOffset = canvasOffset;
132
+ const screenX = event.clientX - rect.left - mouseCanvasOffset;
133
+ const screenY = event.clientY - rect.top;
134
+ setMousePosition({ x: event.clientX, y: event.clientY });
135
+ if (screenY < lineZoneHeight) {
136
+ setHoveredItem(undefined);
137
+ return;
138
+ }
139
+ const { x, y } = screenToUnrotated(screenX, screenY, yScalar, lineZoneHeight);
140
+ const results = flatbushIndex.search(x - 1, y - 1, x + 1, y + 1);
141
+ if (results.length > 0) {
142
+ const item = flatbushItems[results[0]];
143
+ setHoveredItem(item);
144
+ }
145
+ else {
146
+ setHoveredItem(undefined);
147
+ }
148
+ }, [flatbushIndex, flatbushItems, yScalar, lineZoneHeight, canvasOffset]);
149
+ const onMouseLeave = useCallback(() => {
150
+ setHoveredItem(undefined);
151
+ setMousePosition(undefined);
152
+ }, []);
153
+ return (_jsxs("div", { ref: containerRef, style: {
154
+ cursor: hoveredItem && mousePosition ? 'crosshair' : undefined,
155
+ position: 'relative',
156
+ width,
157
+ height: containerHeight,
158
+ overflow: 'hidden',
159
+ }, onMouseMove: onMouseMove, onMouseLeave: onMouseLeave, children: [_jsx("canvas", { "data-testid": `ld_canvas${fullyDrawn ? '_done' : ''}`, ref: cb, style: {
160
+ width,
161
+ height: canvasOnlyHeight,
162
+ position: 'absolute',
163
+ left: canvasOffset,
164
+ top: lineZoneHeight,
165
+ }, width: width * 2, height: canvasOnlyHeight * 2 }), hoveredItem && genomicX1 !== undefined && genomicX2 !== undefined ? (_jsx(Crosshairs, { hoveredItem: hoveredItem, cellWidth: cellWidth, genomicX1: genomicX1, genomicX2: genomicX2, yScalar: yScalar, lineZoneHeight: lineZoneHeight, tickHeight: model.tickHeight, width: width, height: containerHeight, useGenomicPositions: useGenomicPositions, snps: snps, regionStart: region?.start ?? 0, bpPerPx: bpPerPx, canvasOffset: canvasOffset })) : null, hoveredItem && mousePosition ? (_jsx(LDTooltip, { item: hoveredItem, x: mousePosition.x, y: mousePosition.y, ldMetric: ldMetric, signedLD: signedLD })) : null, showLegend ? (_jsx(LDColorLegend, { ldMetric: ldMetric, signedLD: signedLD })) : null, useGenomicPositions ? (_jsx(Wrapper, { model: model, children: _jsx(VariantLabels, { model: model }) })) : (_jsx(LinesConnectingMatrixToGenomicPosition, { model: model })), model.showRecombination && model.recombination ? (_jsxs("div", { style: {
166
+ position: 'absolute',
167
+ left: canvasOffset,
168
+ top: lineZoneHeight / 2,
169
+ width,
170
+ height: lineZoneHeight / 2,
171
+ pointerEvents: 'none',
172
+ }, children: [_jsx(RecombinationTrack, { model: model, width: width, height: lineZoneHeight / 2, useGenomicPositions: useGenomicPositions, regionStart: region?.start, bpPerPx: bpPerPx }), _jsx(RecombinationYScaleBar, { height: lineZoneHeight / 2, maxValue: Math.max(...model.recombination.values, 0.1) })] })) : null] }));
173
+ });
174
+ const LDDisplayContent = observer(function LDDisplayContent({ model, }) {
175
+ const view = getContainingView(model);
176
+ const width = Math.round(view.dynamicBlocks.totalWidthPx);
177
+ const { height, showLDTriangle, showRecombination } = model;
178
+ if (view.bpPerPx > 1000) {
179
+ return (_jsx("div", { style: {
180
+ width,
181
+ height,
182
+ display: 'flex',
183
+ alignItems: 'center',
184
+ justifyContent: 'center',
185
+ color: '#666',
186
+ }, children: "Zoom in to see LD data" }));
187
+ }
188
+ if (!showLDTriangle && !showRecombination) {
189
+ return (_jsx("div", { style: {
190
+ width,
191
+ height,
192
+ display: 'flex',
193
+ alignItems: 'center',
194
+ justifyContent: 'center',
195
+ color: '#666',
196
+ }, children: "Enable LD triangle or recombination track in display settings" }));
197
+ }
198
+ return (_jsx("div", { style: { position: 'relative', width, height }, children: showLDTriangle ? _jsx(LDCanvas, { model: model }) : null }));
199
+ });
200
+ const LDDisplayComponent = observer(function LDDisplayComponent({ model, }) {
201
+ return (_jsx(BaseDisplayComponent, { model: model, children: _jsx(LDDisplayContent, { model: model }) }));
202
+ });
203
+ export default LDDisplayComponent;