higlass 1.11.8 → 1.11.11
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/CHANGELOG.md +13 -0
- package/app/schema.json +83 -5
- package/app/scripts/GenomePositionSearchBox.js +10 -25
- package/app/scripts/HeatmapOptions.js +51 -45
- package/app/scripts/HiGlassComponent.js +167 -7
- package/app/scripts/HorizontalGeneAnnotationsTrack.js +2 -0
- package/app/scripts/TrackRenderer.js +2 -1
- package/app/scripts/api.js +36 -6
- package/app/scripts/icons.js +17 -0
- package/app/scripts/utils/ndarray-assign.js +22 -10
- package/app/scripts/utils/ndarray-to-list.js +15 -9
- package/app/styles/ViewHeader.module.scss +10 -5
- package/dist/hglib.css +1 -1
- package/dist/hglib.js +3744 -8062
- package/dist/hglib.min.js +2 -90
- package/package.json +3 -4
- package/app/scripts/CollapsePanel.js +0 -38
- package/app/scripts/SeriesOptions.js +0 -70
package/CHANGELOG.md
CHANGED
|
@@ -1,12 +1,25 @@
|
|
|
1
1
|
# Release notes
|
|
2
2
|
|
|
3
|
+
## v1.11.11
|
|
4
|
+
|
|
5
|
+
- Bump terser-webpack-plugin version to fix build issue
|
|
6
|
+
|
|
7
|
+
## v1.11.10
|
|
8
|
+
|
|
9
|
+
- Remove dependency on `cwise`
|
|
10
|
+
- Fix color issue in GeneAnnotation SVG export
|
|
11
|
+
|
|
3
12
|
## v1.11.8
|
|
4
13
|
|
|
14
|
+
- Remove react-bootstrap from the GenomePositionSearchBox
|
|
5
15
|
- Make vertical chromosome labels as well as loading status labels readable in vertical tracks
|
|
6
16
|
- Added an option menu item for rectangle domain fill opacity
|
|
17
|
+
- Added a parameter in `zoomToGene` to allow specifying padding around gene
|
|
7
18
|
- Add data fetchers to `AVAILABLE_FOR_PLUGINS`
|
|
8
19
|
- Update track list in `AVAILABLE_FOR_PLUGINS`
|
|
9
20
|
- Correctly setup initial scales of vertical tracks when the width of a center track is zero.
|
|
21
|
+
- Config-wise, allow axis-specific location locks (e.g., lock the vertical axis in a view to the horizontal axis in another).
|
|
22
|
+
- Add `reload` implementation to `HiGlassComponenet` API.
|
|
10
23
|
|
|
11
24
|
_[Detailed changes since v1.11.5](https://github.com/higlass/higlass/compare/v1.11.7...develop)_
|
|
12
25
|
|
package/app/schema.json
CHANGED
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
"items": { "type": "string" },
|
|
27
27
|
"minLength": 1
|
|
28
28
|
},
|
|
29
|
-
"zoomLocks": { "$ref": "#/definitions/locks/
|
|
30
|
-
"locationLocks": { "$ref": "#/definitions/locks/
|
|
29
|
+
"zoomLocks": { "$ref": "#/definitions/locks/zoomLocks" },
|
|
30
|
+
"locationLocks": { "$ref": "#/definitions/locks/locationLocks" },
|
|
31
31
|
"valueScaleLocks": { "$ref": "#/definitions/locks/valueScaleLocks" },
|
|
32
32
|
"views": {
|
|
33
33
|
"type": "array",
|
|
@@ -61,7 +61,48 @@
|
|
|
61
61
|
}
|
|
62
62
|
},
|
|
63
63
|
|
|
64
|
-
"
|
|
64
|
+
"axisSpecificLocks": {
|
|
65
|
+
"additionalProperties": false,
|
|
66
|
+
"properties": {
|
|
67
|
+
"axis": {
|
|
68
|
+
"enum": ["x", "y"],
|
|
69
|
+
"type": "string"
|
|
70
|
+
},
|
|
71
|
+
"lock": {
|
|
72
|
+
"type": "string"
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
"required": ["lock", "axis"],
|
|
76
|
+
"type": "object"
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
"locationLocksByViewUid": {
|
|
80
|
+
"type": "object",
|
|
81
|
+
"additionalProperties": false,
|
|
82
|
+
"patternProperties": {
|
|
83
|
+
".": {
|
|
84
|
+
"anyOf": [
|
|
85
|
+
{
|
|
86
|
+
"type": "string"
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"additionalProperties": false,
|
|
90
|
+
"properties": {
|
|
91
|
+
"x": {
|
|
92
|
+
"$ref": "#/definitions/locks/axisSpecificLocks"
|
|
93
|
+
},
|
|
94
|
+
"y": {
|
|
95
|
+
"$ref": "#/definitions/locks/axisSpecificLocks"
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
"type": "object"
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
"zoomLocks": {
|
|
65
106
|
"type": "object",
|
|
66
107
|
"additionalProperties": false,
|
|
67
108
|
"required": [],
|
|
@@ -93,6 +134,40 @@
|
|
|
93
134
|
}
|
|
94
135
|
},
|
|
95
136
|
|
|
137
|
+
"locationLocks": {
|
|
138
|
+
"type": "object",
|
|
139
|
+
"additionalProperties": false,
|
|
140
|
+
"required": [],
|
|
141
|
+
"properties": {
|
|
142
|
+
"locksByViewUid": {
|
|
143
|
+
"$ref": "#/definitions/locks/locationLocksByViewUid"
|
|
144
|
+
},
|
|
145
|
+
"locksDict": {
|
|
146
|
+
"type": "object",
|
|
147
|
+
"additionalProperties": false,
|
|
148
|
+
"patternProperties": {
|
|
149
|
+
".": {
|
|
150
|
+
"type": "object",
|
|
151
|
+
"additionalProperties": false,
|
|
152
|
+
"properties": {
|
|
153
|
+
"uid": { "$ref": "#/definitions/locks/slug" }
|
|
154
|
+
},
|
|
155
|
+
"patternProperties": {
|
|
156
|
+
"^(?!uid).": {
|
|
157
|
+
"type": "array",
|
|
158
|
+
"minLength": 3,
|
|
159
|
+
"maxLength": 3,
|
|
160
|
+
"items": {
|
|
161
|
+
"type": "number"
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
|
|
96
171
|
"valueScaleLocks": {
|
|
97
172
|
"type": "object",
|
|
98
173
|
"additionalProperties": false,
|
|
@@ -253,7 +328,9 @@
|
|
|
253
328
|
"type": "integer",
|
|
254
329
|
"title": "Height",
|
|
255
330
|
"default": 12
|
|
256
|
-
}
|
|
331
|
+
},
|
|
332
|
+
"moved": { "type": "boolean" },
|
|
333
|
+
"static": { "type": "boolean" }
|
|
257
334
|
}
|
|
258
335
|
},
|
|
259
336
|
|
|
@@ -307,7 +384,8 @@
|
|
|
307
384
|
"position": { "type": "string" },
|
|
308
385
|
"server": { "type": "string" },
|
|
309
386
|
"tilesetUid": { "type": "string" },
|
|
310
|
-
"width": { "type": "number" }
|
|
387
|
+
"width": { "type": "number" },
|
|
388
|
+
"transforms": { "type": "array" }
|
|
311
389
|
}
|
|
312
390
|
},
|
|
313
391
|
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import { select, event } from 'd3-selection';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import slugid from 'slugid';
|
|
4
|
-
import {
|
|
5
|
-
FormGroup,
|
|
6
|
-
Glyphicon,
|
|
7
|
-
DropdownButton,
|
|
8
|
-
MenuItem,
|
|
9
|
-
} from 'react-bootstrap';
|
|
10
4
|
import PropTypes from 'prop-types';
|
|
11
5
|
|
|
12
6
|
import Autocomplete from './Autocomplete';
|
|
@@ -23,7 +17,7 @@ import { scalesCenterAndK, dictKeys, toVoid } from './utils';
|
|
|
23
17
|
|
|
24
18
|
// HOCS
|
|
25
19
|
import withTheme from './hocs/with-theme';
|
|
26
|
-
|
|
20
|
+
import { SearchIcon } from './icons';
|
|
27
21
|
// Configs
|
|
28
22
|
import { THEME_DARK, ZOOM_TRANSITION_DURATION } from './configs';
|
|
29
23
|
|
|
@@ -771,37 +765,31 @@ class GenomePositionSearchBox extends React.Component {
|
|
|
771
765
|
|
|
772
766
|
render() {
|
|
773
767
|
const assemblyMenuItems = this.state.availableAssemblies.map((x) => (
|
|
774
|
-
<
|
|
768
|
+
<option key={x} value={x}>
|
|
775
769
|
{x}
|
|
776
|
-
</
|
|
770
|
+
</option>
|
|
777
771
|
));
|
|
778
772
|
|
|
779
773
|
let className = this.state.isFocused
|
|
780
774
|
? 'styles.genome-position-search-focus'
|
|
781
775
|
: 'styles.genome-position-search';
|
|
782
776
|
|
|
783
|
-
const classNameButton = this.state.isFocused
|
|
784
|
-
? 'styles.genome-position-search-bar-button-focus'
|
|
785
|
-
: 'styles.genome-position-search-bar-button';
|
|
786
|
-
|
|
787
777
|
if (this.props.theme === THEME_DARK) {
|
|
788
778
|
className += ' styles.genome-position-search-dark';
|
|
789
779
|
}
|
|
790
780
|
|
|
791
781
|
return (
|
|
792
|
-
<
|
|
782
|
+
<div
|
|
793
783
|
ref={(c) => {
|
|
794
784
|
this.gpsbForm = c;
|
|
795
785
|
}}
|
|
796
|
-
bsSize="small"
|
|
797
786
|
styleName={className}
|
|
798
787
|
>
|
|
799
788
|
{!this.props.hideAvailableAssemblies && (
|
|
800
|
-
<
|
|
789
|
+
<select
|
|
801
790
|
ref={(c) => {
|
|
802
791
|
this.assemblyPickButton = c;
|
|
803
792
|
}}
|
|
804
|
-
bsSize="small"
|
|
805
793
|
className={styles['genome-position-search-bar-button']}
|
|
806
794
|
id={this.uid}
|
|
807
795
|
onSelect={this.handleAssemblySelect.bind(this)}
|
|
@@ -812,7 +800,7 @@ class GenomePositionSearchBox extends React.Component {
|
|
|
812
800
|
}
|
|
813
801
|
>
|
|
814
802
|
{assemblyMenuItems}
|
|
815
|
-
</
|
|
803
|
+
</select>
|
|
816
804
|
)}
|
|
817
805
|
|
|
818
806
|
<Autocomplete
|
|
@@ -853,14 +841,11 @@ class GenomePositionSearchBox extends React.Component {
|
|
|
853
841
|
wrapperStyle={{ width: '100%' }}
|
|
854
842
|
/>
|
|
855
843
|
|
|
856
|
-
<
|
|
844
|
+
<SearchIcon
|
|
857
845
|
onClick={this.buttonClick.bind(this)}
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
<Glyphicon glyph="search" />
|
|
862
|
-
</button>
|
|
863
|
-
</FormGroup>
|
|
846
|
+
theStyle="multitrack-header-icon"
|
|
847
|
+
/>
|
|
848
|
+
</div>
|
|
864
849
|
);
|
|
865
850
|
}
|
|
866
851
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import PropTypes from 'prop-types';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import
|
|
3
|
+
import Dialog from './Dialog';
|
|
4
4
|
|
|
5
5
|
import HiGlassComponent from './HiGlassComponent';
|
|
6
6
|
import SketchInlinePicker from './SketchInlinePicker';
|
|
@@ -198,50 +198,56 @@ class HeatmapOptions extends React.Component {
|
|
|
198
198
|
) : null; // addButton
|
|
199
199
|
|
|
200
200
|
return (
|
|
201
|
-
<
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
201
|
+
<Dialog
|
|
202
|
+
okayTitle="Submit"
|
|
203
|
+
onCancel={this.props.onCancel}
|
|
204
|
+
onOkay={this.handleSubmit.bind(this)}
|
|
205
|
+
title="Custom Color Map"
|
|
206
|
+
>
|
|
207
|
+
<table className="table-track-options">
|
|
208
|
+
<thead />
|
|
209
|
+
<tbody style={{ verticalAlign: 'top' }}>
|
|
210
|
+
<tr>
|
|
211
|
+
<td className="td-track-options">
|
|
212
|
+
<table>
|
|
213
|
+
<tbody>
|
|
214
|
+
<tr>
|
|
215
|
+
<td className="td-track-options">Preview</td>
|
|
216
|
+
</tr>
|
|
217
|
+
<tr>
|
|
218
|
+
<td className="td-track-options">
|
|
219
|
+
<div style={{ width: 200 }}>
|
|
220
|
+
<HiGlassComponent
|
|
221
|
+
options={{ bounded: false }}
|
|
222
|
+
viewConfig={mvConfig}
|
|
223
|
+
/>
|
|
224
|
+
</div>
|
|
225
|
+
</td>
|
|
226
|
+
</tr>
|
|
227
|
+
</tbody>
|
|
228
|
+
</table>
|
|
229
|
+
</td>
|
|
230
|
+
<td className="td-track-options">
|
|
231
|
+
<table>
|
|
232
|
+
<tbody>
|
|
233
|
+
<tr>
|
|
234
|
+
<td className="td-track-options">Colors</td>
|
|
235
|
+
</tr>
|
|
236
|
+
<tr>
|
|
237
|
+
<td className="td-track-options">
|
|
238
|
+
{addButton}
|
|
239
|
+
<div style={{ position: 'relative' }}>
|
|
240
|
+
{colorFields}
|
|
241
|
+
</div>
|
|
242
|
+
</td>
|
|
243
|
+
</tr>
|
|
244
|
+
</tbody>
|
|
245
|
+
</table>
|
|
246
|
+
</td>
|
|
247
|
+
</tr>
|
|
248
|
+
</tbody>
|
|
249
|
+
</table>
|
|
250
|
+
</Dialog>
|
|
245
251
|
);
|
|
246
252
|
}
|
|
247
253
|
}
|
|
@@ -143,6 +143,9 @@ class HiGlassComponent extends React.Component {
|
|
|
143
143
|
this.zoomLocks = {};
|
|
144
144
|
this.locationLocks = {};
|
|
145
145
|
|
|
146
|
+
// axis-specific location lock
|
|
147
|
+
this.locationLocksAxisWise = { x: {}, y: {} };
|
|
148
|
+
|
|
146
149
|
// locks that keep the value scales synchronized between
|
|
147
150
|
// *tracks* (which can be in different views)
|
|
148
151
|
this.valueScaleLocks = {};
|
|
@@ -1724,6 +1727,132 @@ class HiGlassComponent extends React.Component {
|
|
|
1724
1727
|
}
|
|
1725
1728
|
}
|
|
1726
1729
|
|
|
1730
|
+
if (this.locationLocksAxisWise.x[uid]) {
|
|
1731
|
+
// the x axis of this view is locked to an axis of another view
|
|
1732
|
+
const lockGroup = this.locationLocksAxisWise.x[uid].lock;
|
|
1733
|
+
const lockGroupItems = dictItems(lockGroup);
|
|
1734
|
+
|
|
1735
|
+
// this means the x axis of this view (uid) is locked to the y axis of another view
|
|
1736
|
+
const lockCrossAxis = this.locationLocksAxisWise.x[uid].axis !== 'x';
|
|
1737
|
+
|
|
1738
|
+
// eslint-disable-next-line no-unused-vars
|
|
1739
|
+
const [centerX, centerY, k] = scalesCenterAndK(
|
|
1740
|
+
this.xScales[uid],
|
|
1741
|
+
this.yScales[uid],
|
|
1742
|
+
);
|
|
1743
|
+
|
|
1744
|
+
for (let i = 0; i < lockGroupItems.length; i++) {
|
|
1745
|
+
const key = lockGroupItems[i][0];
|
|
1746
|
+
const value = lockGroupItems[i][1];
|
|
1747
|
+
|
|
1748
|
+
if (!this.xScales[key] || !this.yScales[key]) {
|
|
1749
|
+
continue;
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
// eslint-disable-next-line no-unused-vars
|
|
1753
|
+
const [keyCenterX, keyCenterY, keyK] = scalesCenterAndK(
|
|
1754
|
+
this.xScales[key],
|
|
1755
|
+
this.yScales[key],
|
|
1756
|
+
);
|
|
1757
|
+
|
|
1758
|
+
if (key === uid) {
|
|
1759
|
+
// no need to notify oneself that the scales have changed
|
|
1760
|
+
continue;
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
const dx = value[0] - lockGroup[uid][0];
|
|
1764
|
+
|
|
1765
|
+
const newCenterX = centerX + dx;
|
|
1766
|
+
|
|
1767
|
+
if (!this.setCenters[key]) {
|
|
1768
|
+
continue;
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
const [newXScale, newYScale] = this.setCenters[key](
|
|
1772
|
+
lockCrossAxis ? keyCenterX : newCenterX,
|
|
1773
|
+
lockCrossAxis ? newCenterX : keyCenterY,
|
|
1774
|
+
keyK,
|
|
1775
|
+
false,
|
|
1776
|
+
);
|
|
1777
|
+
|
|
1778
|
+
// because the setCenters call above has a 'false' notify, the new scales won't
|
|
1779
|
+
// be propagated from there, so we have to store them here
|
|
1780
|
+
this.xScales[key] = newXScale;
|
|
1781
|
+
this.yScales[key] = newYScale;
|
|
1782
|
+
|
|
1783
|
+
// notify the listeners of all locked views that the scales of
|
|
1784
|
+
// this view have changed
|
|
1785
|
+
if (this.scalesChangedListeners.hasOwnProperty(key)) {
|
|
1786
|
+
dictValues(this.scalesChangedListeners[key]).forEach((x) => {
|
|
1787
|
+
x(newXScale, newYScale);
|
|
1788
|
+
});
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
if (this.locationLocksAxisWise.y[uid]) {
|
|
1794
|
+
// the y axis of this view is locked to an axis of another view
|
|
1795
|
+
const lockGroup = this.locationLocksAxisWise.y[uid].lock;
|
|
1796
|
+
const lockGroupItems = dictItems(lockGroup);
|
|
1797
|
+
|
|
1798
|
+
// this means the y axis of this view (uid) is locked to the x axis of another view
|
|
1799
|
+
const lockCrossAxis = this.locationLocksAxisWise.y[uid].axis !== 'y';
|
|
1800
|
+
|
|
1801
|
+
// eslint-disable-next-line no-unused-vars
|
|
1802
|
+
const [centerX, centerY, k] = scalesCenterAndK(
|
|
1803
|
+
this.xScales[uid],
|
|
1804
|
+
this.yScales[uid],
|
|
1805
|
+
);
|
|
1806
|
+
|
|
1807
|
+
for (let i = 0; i < lockGroupItems.length; i++) {
|
|
1808
|
+
const key = lockGroupItems[i][0];
|
|
1809
|
+
const value = lockGroupItems[i][1];
|
|
1810
|
+
|
|
1811
|
+
if (!this.xScales[key] || !this.yScales[key]) {
|
|
1812
|
+
continue;
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
// eslint-disable-next-line no-unused-vars
|
|
1816
|
+
const [keyCenterX, keyCenterY, keyK] = scalesCenterAndK(
|
|
1817
|
+
this.xScales[key],
|
|
1818
|
+
this.yScales[key],
|
|
1819
|
+
);
|
|
1820
|
+
|
|
1821
|
+
if (key === uid) {
|
|
1822
|
+
// no need to notify oneself that the scales have changed
|
|
1823
|
+
continue;
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
const dy = value[1] - lockGroup[uid][1];
|
|
1827
|
+
|
|
1828
|
+
const newCenterY = centerY + dy;
|
|
1829
|
+
|
|
1830
|
+
if (!this.setCenters[key]) {
|
|
1831
|
+
continue;
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
const [newXScale, newYScale] = this.setCenters[key](
|
|
1835
|
+
lockCrossAxis ? newCenterY : keyCenterX,
|
|
1836
|
+
lockCrossAxis ? keyCenterY : newCenterY,
|
|
1837
|
+
keyK,
|
|
1838
|
+
false,
|
|
1839
|
+
);
|
|
1840
|
+
|
|
1841
|
+
// because the setCenters call above has a 'false' notify, the new scales won't
|
|
1842
|
+
// be propagated from there, so we have to store them here
|
|
1843
|
+
this.xScales[key] = newXScale;
|
|
1844
|
+
this.yScales[key] = newYScale;
|
|
1845
|
+
|
|
1846
|
+
// notify the listeners of all locked views that the scales of
|
|
1847
|
+
// this view have changed
|
|
1848
|
+
if (this.scalesChangedListeners.hasOwnProperty(key)) {
|
|
1849
|
+
dictValues(this.scalesChangedListeners[key]).forEach((x) => {
|
|
1850
|
+
x(newXScale, newYScale);
|
|
1851
|
+
});
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1727
1856
|
this.animate();
|
|
1728
1857
|
|
|
1729
1858
|
// Call view change handler
|
|
@@ -3195,10 +3324,40 @@ class HiGlassComponent extends React.Component {
|
|
|
3195
3324
|
|
|
3196
3325
|
if (viewConfig.locationLocks) {
|
|
3197
3326
|
for (const viewUid of dictKeys(viewConfig.locationLocks.locksByViewUid)) {
|
|
3198
|
-
|
|
3199
|
-
viewConfig.locationLocks.
|
|
3200
|
-
|
|
3201
|
-
]
|
|
3327
|
+
if (
|
|
3328
|
+
typeof viewConfig.locationLocks.locksByViewUid[viewUid] !== 'object'
|
|
3329
|
+
) {
|
|
3330
|
+
this.locationLocks[viewUid] =
|
|
3331
|
+
viewConfig.locationLocks.locksDict[
|
|
3332
|
+
viewConfig.locationLocks.locksByViewUid[viewUid]
|
|
3333
|
+
];
|
|
3334
|
+
} else {
|
|
3335
|
+
// This means we need to link x and y axes separately.
|
|
3336
|
+
|
|
3337
|
+
// x-axis specific locks. The x-axis of this view is linked with an axis in another view.
|
|
3338
|
+
if ('x' in viewConfig.locationLocks.locksByViewUid[viewUid]) {
|
|
3339
|
+
const lockInfo =
|
|
3340
|
+
viewConfig.locationLocks.locksDict[
|
|
3341
|
+
viewConfig.locationLocks.locksByViewUid[viewUid].x.lock
|
|
3342
|
+
];
|
|
3343
|
+
this.locationLocksAxisWise.x[viewUid] = {
|
|
3344
|
+
lock: lockInfo,
|
|
3345
|
+
axis: viewConfig.locationLocks.locksByViewUid[viewUid].x.axis, // The axis of another view, either 'x' or 'y'
|
|
3346
|
+
};
|
|
3347
|
+
}
|
|
3348
|
+
|
|
3349
|
+
// y-axis specific locks. The y-axis of this view is linked with an axis in another view.
|
|
3350
|
+
if ('y' in viewConfig.locationLocks.locksByViewUid[viewUid]) {
|
|
3351
|
+
const lockInfo =
|
|
3352
|
+
viewConfig.locationLocks.locksDict[
|
|
3353
|
+
viewConfig.locationLocks.locksByViewUid[viewUid].y.lock
|
|
3354
|
+
];
|
|
3355
|
+
this.locationLocksAxisWise.y[viewUid] = {
|
|
3356
|
+
lock: lockInfo,
|
|
3357
|
+
axis: viewConfig.locationLocks.locksByViewUid[viewUid].y.axis, // The axis of another view, either 'x' or 'y'
|
|
3358
|
+
};
|
|
3359
|
+
}
|
|
3360
|
+
}
|
|
3202
3361
|
}
|
|
3203
3362
|
}
|
|
3204
3363
|
|
|
@@ -4055,7 +4214,7 @@ class HiGlassComponent extends React.Component {
|
|
|
4055
4214
|
this.setCenters[viewUid](centerX, centerY, k, false, animateTime);
|
|
4056
4215
|
}
|
|
4057
4216
|
|
|
4058
|
-
zoomToGene(viewUid, geneName, animateTime) {
|
|
4217
|
+
zoomToGene(viewUid, geneName, padding, animateTime) {
|
|
4059
4218
|
if (!(viewUid in this.setCenters)) {
|
|
4060
4219
|
throw Error(
|
|
4061
4220
|
`Invalid viewUid. Current uuids: ${Object.keys(this.setCenters).join(
|
|
@@ -4091,8 +4250,9 @@ class HiGlassComponent extends React.Component {
|
|
|
4091
4250
|
this.state.views[viewUid].chromInfoPath,
|
|
4092
4251
|
(loadedChromInfo) => {
|
|
4093
4252
|
// using the absolution positions, zoom to the position near a gene
|
|
4094
|
-
const startAbs =
|
|
4095
|
-
|
|
4253
|
+
const startAbs =
|
|
4254
|
+
loadedChromInfo.chrToAbs([chr, txStart]) - padding;
|
|
4255
|
+
const endAbs = loadedChromInfo.chrToAbs([chr, txEnd]) + padding;
|
|
4096
4256
|
|
|
4097
4257
|
const [centerX, centerY, k] = scalesCenterAndK(
|
|
4098
4258
|
this.xScales[viewUid].copy().domain([startAbs, endAbs]),
|
|
@@ -141,6 +141,8 @@ function externalInitTile(track, tile, options) {
|
|
|
141
141
|
const geneId = track.geneId(geneInfo, td.type);
|
|
142
142
|
const strand = td.strand || geneInfo[5];
|
|
143
143
|
|
|
144
|
+
td.strand = td.strand || strand;
|
|
145
|
+
|
|
144
146
|
let fill = plusStrandColor || DEFAULT_PLUS_STRAND_COLOR;
|
|
145
147
|
|
|
146
148
|
if (strand === '-') {
|
|
@@ -604,7 +604,8 @@ class TrackRenderer extends React.Component {
|
|
|
604
604
|
])
|
|
605
605
|
.range([initialXDomain[0], initialXDomain[1]]);
|
|
606
606
|
|
|
607
|
-
let startY;
|
|
607
|
+
let startY;
|
|
608
|
+
let endY;
|
|
608
609
|
if (this.currentProps.centerWidth === 0) {
|
|
609
610
|
// If the width of the center track is zero, we do not want to make startY and endY equal.
|
|
610
611
|
startY = this.currentProps.paddingTop + this.currentProps.topHeight;
|
package/app/scripts/api.js
CHANGED
|
@@ -15,6 +15,7 @@ const forceUpdate = (self) => {
|
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
const createApi = function api(context, pubSub) {
|
|
18
|
+
/** @type {import('./HiGlassComponent').default} */
|
|
18
19
|
const self = context;
|
|
19
20
|
|
|
20
21
|
let pubSubs = [];
|
|
@@ -112,10 +113,38 @@ const createApi = function api(context, pubSub) {
|
|
|
112
113
|
},
|
|
113
114
|
|
|
114
115
|
/**
|
|
115
|
-
* Reload all
|
|
116
|
+
* Reload all or specific tiles for viewId/trackId
|
|
117
|
+
*
|
|
118
|
+
* @param {array} target Should be an array of type
|
|
119
|
+
* ({ viewId: string, trackId: string } | string).
|
|
120
|
+
* If the array is just strings, it's interpreted
|
|
121
|
+
* as a list of views whose tracks to reload.
|
|
116
122
|
*/
|
|
117
|
-
reload() {
|
|
118
|
-
|
|
123
|
+
reload(target) {
|
|
124
|
+
/** @type {{ viewId: string, trackId: string}[]} */
|
|
125
|
+
let tracks;
|
|
126
|
+
if (!target) {
|
|
127
|
+
tracks = self.iterateOverTracks();
|
|
128
|
+
} else {
|
|
129
|
+
tracks = target.flatMap((d) =>
|
|
130
|
+
typeof d === 'string' ? self.iterateOverTracksInView(d) : d,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
for (const { viewId, trackId } of tracks) {
|
|
135
|
+
const selectedTrack = self.getTrackObject(viewId, trackId);
|
|
136
|
+
// iterate over childTracks if CombinedTrack
|
|
137
|
+
for (const track of selectedTrack.childTracks || [selectedTrack]) {
|
|
138
|
+
// reload tiles for tracks with tiles.
|
|
139
|
+
if (track.fetchedTiles) {
|
|
140
|
+
track.removeTiles(Object.keys(track.fetchedTiles));
|
|
141
|
+
track.fetching.clear();
|
|
142
|
+
track.refreshTiles();
|
|
143
|
+
}
|
|
144
|
+
// second argument forces re-render
|
|
145
|
+
track.rerender(track.options, true);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
119
148
|
},
|
|
120
149
|
|
|
121
150
|
/**
|
|
@@ -473,13 +502,14 @@ const createApi = function api(context, pubSub) {
|
|
|
473
502
|
*
|
|
474
503
|
* @param {string} viewUid The identifier of the view to zoom
|
|
475
504
|
* @param {string} geneName The name of gene symbol to search
|
|
505
|
+
* @param {string} padding The padding (base pairs) around a given gene for the navigation
|
|
476
506
|
* @param {Number} animateTime The time to spend zooming to the specified location
|
|
477
507
|
* @example
|
|
478
508
|
* // Zoom to the location near 'MYC'
|
|
479
|
-
* hgApi.zoomToGene('view1', 'MYC', 2000);
|
|
509
|
+
* hgApi.zoomToGene('view1', 'MYC', 100, 2000);
|
|
480
510
|
*/
|
|
481
|
-
zoomToGene(viewUid, geneName, animateTime = 0) {
|
|
482
|
-
self.zoomToGene(viewUid, geneName, animateTime);
|
|
511
|
+
zoomToGene(viewUid, geneName, padding = 0, animateTime = 0) {
|
|
512
|
+
self.zoomToGene(viewUid, geneName, padding, animateTime);
|
|
483
513
|
},
|
|
484
514
|
|
|
485
515
|
/**
|
package/app/scripts/icons.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import React from 'react';
|
|
1
2
|
import { select } from 'd3-selection';
|
|
3
|
+
import '../styles/ViewHeader.module.scss';
|
|
2
4
|
|
|
3
5
|
export const COG = {
|
|
4
6
|
id: 'cog',
|
|
@@ -369,3 +371,18 @@ export const svgArrowheadDomainsIcon = parser.parseFromString(
|
|
|
369
371
|
arrowHeadDomainsStr,
|
|
370
372
|
'text/xml',
|
|
371
373
|
).documentElement;
|
|
374
|
+
|
|
375
|
+
export function SearchIcon({ theStyle }) {
|
|
376
|
+
return (
|
|
377
|
+
<svg
|
|
378
|
+
styleName={theStyle}
|
|
379
|
+
viewBox="0 0 12 13"
|
|
380
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
381
|
+
>
|
|
382
|
+
<g fill="none" stroke="#6c6c6c" strokeWidth="2">
|
|
383
|
+
<path d="M11.29 11.71l-4-4" />
|
|
384
|
+
<circle cx="5" cy="5" r="4" />
|
|
385
|
+
</g>
|
|
386
|
+
</svg>
|
|
387
|
+
);
|
|
388
|
+
}
|