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 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/genericLocks" },
30
- "locationLocks": { "$ref": "#/definitions/locks/genericLocks" },
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
- "genericLocks": {
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
- <MenuItem key={x} eventKey={x}>
768
+ <option key={x} value={x}>
775
769
  {x}
776
- </MenuItem>
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
- <FormGroup
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
- <DropdownButton
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
- </DropdownButton>
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
- <button
844
+ <SearchIcon
857
845
  onClick={this.buttonClick.bind(this)}
858
- styleName={classNameButton}
859
- type="button"
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 { Modal, Button } from 'react-bootstrap';
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
- <Modal className="hg-modal" onHide={this.props.onCancel} show={true}>
202
- <Modal.Header closeButton>
203
- <Modal.Title>Custom Color Map</Modal.Title>
204
- </Modal.Header>
205
- <Modal.Body>
206
- <table className="table-track-options">
207
- <thead />
208
- <tbody style={{ verticalAlign: 'top' }}>
209
- <tr>
210
- <td className="td-track-options">
211
- <tr>
212
- <td className="td-track-options">Preview</td>
213
- </tr>
214
- <tr>
215
- <td className="td-track-options">
216
- <div style={{ width: 200 }}>
217
- <HiGlassComponent
218
- options={{ bounded: false }}
219
- viewConfig={mvConfig}
220
- />
221
- </div>
222
- </td>
223
- </tr>
224
- </td>
225
- <td className="td-track-options">
226
- <tr>
227
- <td className="td-track-options">Colors</td>
228
- </tr>
229
- <tr>
230
- <td className="td-track-options">
231
- {addButton}
232
- <div style={{ position: 'relative' }}>{colorFields}</div>
233
- </td>
234
- </tr>
235
- </td>
236
- </tr>
237
- </tbody>
238
- </table>
239
- </Modal.Body>
240
- <Modal.Footer>
241
- <Button onClick={this.props.onCancel}>Cancel</Button>
242
- <Button onClick={this.handleSubmit.bind(this)}>Submit</Button>
243
- </Modal.Footer>
244
- </Modal>
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
- this.locationLocks[viewUid] =
3199
- viewConfig.locationLocks.locksDict[
3200
- viewConfig.locationLocks.locksByViewUid[viewUid]
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 = loadedChromInfo.chrToAbs([chr, txStart]);
4095
- const endAbs = loadedChromInfo.chrToAbs([chr, txEnd]);
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; let endY;
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;
@@ -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 of the tiles
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
- console.warn('Not implemented yet!');
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
  /**
@@ -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
+ }