{"action":"created","comment":{"url":"https://api.github.com/repos/higlass/higlass/pulls/comments/234075048","pull_request_review_id":175639534,"id":234075048,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIzNDA3NTA0OA==","diff_hunk":"@@ -14,622 +15,622 @@ import {\n   MOUSE_TOOL_SELECT,\n } from './configs';\n \n-import pubSub, { create } from './services/pub-sub';\n-\n-let stack = {};\n-let pubSubs = [];\n-\n-const apiPubSub = create(stack);\n+const api = function api(context, pubSub) {\n+  const self = context;\n+  const apiPubSub = createPubSub();\n \n-export const destroy = () => {\n-  pubSubs.forEach(subscription => pubSub.unsubscribe(subscription));\n-  pubSubs = [];\n-  stack = {};\n-};\n+  let pubSubs = [];\n \n-const api = function api(context) {\n-  const self = context;\n+  const destroy = () => {\n+    pubSubs.forEach(subscription => pubSub.unsubscribe(subscription));\n+    pubSubs = [];\n+  };\n \n-  // Public API\n+  // Internal API\n   return {\n-    /**\n-     * Set an auth header to be included with all tile requests.\n-     *\n-     * @param {string} newHeader The contensts of the header to be included.\n-     * Example: ``hgapi.setAuthHeader('JWT xyz')``\n-     */\n-    setAuthHeader(newHeader) {\n-      setTileProxyAuthHeader(newHeader);\n-\n-      // we need to re-request all the tiles\n-      this.reload();\n-    },\n-\n-    /**\n-     * Get the currently set auth header\n-     */\n-    getAuthHeader() {\n-      return setTileProxyAuthHeader();\n-    },\n-\n-    /**\n-     * Get a reference to the React HiGlassComponent\n-     *\n-     * @returns {HiGlassComponent}\n-     */\n-    getComponent() {\n-      return self;\n-    },\n-\n-    /**\n-     * Reload all of the tiles\n-     */\n-    reload() {\n-      console.warn('Not implemented yet!');\n-    },\n-\n-    destroy() {\n-      ReactDOM.unmountComponentAtNode(self.topDiv.parentNode);\n-    },\n-\n-    /**\n-     * Force integer range selections.\n-     *\n-     * @example\n-     *\n-     * hgv.activateTool('select'); // Activate select tool\n-     * hgv.setRangeSelectionToFloat(); // Allow float range selections\n-     */\n-    setRangeSelectionToInt() {\n-      self.setState({ rangeSelectionToInt: true });\n-    },\n-\n-\n-    /**\n-     * Force float range selections.\n-     *\n-     * @example\n-     *\n-     * hgv.activateTool('select'); // Activate select tool\n-     * hgv.setRangeSelectionToFloat(); // Allow float range selections\n-     */\n-    setRangeSelectionToFloat() {\n-      self.setState({ rangeSelectionToInt: false });\n-    },\n-\n-    /**\n-     *\n-     *  The following enpoint restricts the size of range selection equally for 1D or\n-     *  2D tracks to a certain length (specified in absolute coordinates).\n-     *\n-     * @param {Number} [minSize = 0]  Minimum range selection. ``undefined`` unsets the value.\n-     * @param {Number} [maxSize = Infinity] Maximum range selection. ``undefined`` unsets the value.\n-     * @example\n-     *\n-     * hgv.activateTool('select'); // Activate select tool\n-     * hgv.setRangeSelection1dSize(5000, 10000); // Force selections to be between 5 and 10 Kb\n-     */\n-    setRangeSelection1dSize(minSize = 0, maxSize = Infinity) {\n-      self.setState({\n-        rangeSelection1dSize: [minSize, maxSize]\n-      });\n-    },\n-\n-    /**\n-     * Set a new view config to define the layout and data\n-     * of this component\n-     *\n-     * @param {obj} newViewConfig A JSON object that defines\n-     *    the state of the HiGlassComponent\n-     * @example\n-     *\n-     * const p = hgv.setViewConfig(newViewConfig);\n-     * p.then(() => {\n-     *   // the initial set of tiles has been loaded\n-     * });\n-     *\n-     * @return {Promise} dataLoaded A promise that resolves when\n-     *   all of the data for this viewconfig is loaded\n-     */\n-    setViewConfig(newViewConfig) {\n-      const viewsByUid = self.processViewConfig(newViewConfig);\n-      const p = new Promise((resolve) => {\n-        this.requestsInFlight = 0;\n-\n-        pubSubs.push(pubSub.subscribe('requestSent', () => {\n-          this.requestsInFlight += 1;\n-        }));\n-\n-        pubSubs.push(pubSub.subscribe('requestReceived', () => {\n-          this.requestsInFlight -= 1;\n-\n-          if (this.requestsInFlight === 0) {\n-            resolve();\n-          }\n-        }));\n-\n+    destroy,\n+    publish: apiPubSub.publish,\n+    // Public API\n+    public: {\n+      /**\n+       * Set an auth header to be included with all tile requests.\n+       *\n+       * @param {string} newHeader The contensts of the header to be included.\n+       * Example: ``hgapi.setAuthHeader('JWT xyz')``\n+       */\n+      setAuthHeader(newHeader) {\n+        setTileProxyAuthHeader(newHeader);\n+\n+        // we need to re-request all the tiles\n+        self.reload();\n+      },\n+\n+      /**\n+       * Get the currently set auth header\n+       */\n+      getAuthHeader() {\n+        return setTileProxyAuthHeader();\n+      },\n+\n+      /**\n+       * Get a reference to the React HiGlassComponent\n+       *\n+       * @returns {HiGlassComponent}\n+       */\n+      getComponent() {\n+        return self;\n+      },\n+\n+      /**\n+       * Reload all of the tiles\n+       */\n+      reload() {\n+        console.warn('Not implemented yet!');\n+      },\n+\n+      destroy() {\n+        destroy();\n+        ReactDOM.unmountComponentAtNode(self.topDiv.parentNode);\n+      },\n+\n+      /**\n+       * Force integer range selections.\n+       *\n+       * @example\n+       *\n+       * hgv.activateTool('select'); // Activate select tool\n+       * hgv.setRangeSelectionToFloat(); // Allow float range selections\n+       */\n+      setRangeSelectionToInt() {\n+        self.setState({ rangeSelectionToInt: true });\n+      },\n+\n+\n+      /**\n+       * Force float range selections.\n+       *\n+       * @example\n+       *\n+       * hgv.activateTool('select'); // Activate select tool\n+       * hgv.setRangeSelectionToFloat(); // Allow float range selections\n+       */\n+      setRangeSelectionToFloat() {\n+        self.setState({ rangeSelectionToInt: false });\n+      },\n+\n+      /**\n+       *\n+       *  The following enpoint restricts the size of range selection equally for 1D or\n+       *  2D tracks to a certain length (specified in absolute coordinates).\n+       *\n+       * @param {Number} [minSize = 0]  Minimum range selection. ``undefined`` unsets the value.\n+       * @param {Number} [maxSize = Infinity] Maximum range selection. ``undefined`` unsets the value.\n+       * @example\n+       *\n+       * hgv.activateTool('select'); // Activate select tool\n+       * hgv.setRangeSelection1dSize(5000, 10000); // Force selections to be between 5 and 10 Kb\n+       */\n+      setRangeSelection1dSize(minSize = 0, maxSize = Infinity) {\n         self.setState({\n-          viewConfig: newViewConfig,\n-          views: viewsByUid,\n-        }, () => {\n-\n+          rangeSelection1dSize: [minSize, maxSize]\n         });\n-      });\n-\n-      return p;\n-    },\n-\n-    /**\n-     * Retrieve the visible viewconf.\n-     *\n-     * @returns (Object) A JSON object describing the visible views\n-     */\n-    getViewConfig() {\n-      return self.getViewsAsJson();\n-    },\n-    /**\n-     * Get the minimum and maximum visible values for a given track.\n-     *\n-     * @param {string} viewId The id of the view containing the track.\n-     * @param {string} trackId The id of the track to query.\n-     * @param {bool} [ignoreOffScreenValues=false] If ``true`` only truly visible values\n-     *  are considered. Otherwise the values of visible tiles are used. Not that\n-     *  considering only the truly visible\n-     *  values results in a roughly 10x slowdown (from 0.1 to 1 millisecond).\n-     * @param {bool} [ignoreFixedScale=false]  If ``true`` potentially fixed scaled values are\n-     *  ignored. I.e., if the\n-     *  absolute range is ``[1, 18]`` but you have fixed the output range to\n-     *  ``[4, 5]`` you would normally retrieve ``[4, 5]``. Having this option set to\n-     *  ``true`` retrieves the absolute ``[1, 18]`` range.\n-     * @example\n-     * const [minVal, maxVal] = hgv.getMinMaxValue('myView', 'myTrack');\n-     * @returns {Array} The minimum and maximum value\n-     */\n-    getMinMaxValue(\n-      viewId,\n-      trackId,\n-      ignoreOffScreenValues = false,\n-      ignoreFixedScale = false\n-    ) {\n-      return self.getMinMaxValue(\n+      },\n+\n+      /**\n+       * Set a new view config to define the layout and data\n+       * of this component\n+       *\n+       * @param {obj} newViewConfig A JSON object that defines\n+       *    the state of the HiGlassComponent\n+       * @example\n+       *\n+       * const p = hgv.setViewConfig(newViewConfig);\n+       * p.then(() => {\n+       *   // the initial set of tiles has been loaded\n+       * });\n+       *\n+       * @return {Promise} dataLoaded A promise that resolves when\n+       *   all of the data for this viewconfig is loaded\n+       */\n+      setViewConfig(newViewConfig) {\n+        const viewsByUid = self.processViewConfig(newViewConfig);\n+        const p = new Promise((resolve) => {\n+          this.requestsInFlight = 0;\n+\n+          pubSubs.push(pubSub.subscribe('requestSent', () => {\n+            this.requestsInFlight += 1;\n+          }));\n+\n+          pubSubs.push(pubSub.subscribe('requestReceived', () => {\n+            this.requestsInFlight -= 1;\n+\n+            if (this.requestsInFlight === 0) {\n+              resolve();\n+            }\n+          }));\n+\n+          self.setState({\n+            viewConfig: newViewConfig,\n+            views: viewsByUid,\n+          }, () => {\n+\n+          });\n+        });\n+\n+        return p;\n+      },\n+\n+      /**\n+       * Retrieve the visible viewconf.\n+       *\n+       * @returns (Object) A JSON object describing the visible views\n+       */\n+      getViewConfig() {\n+        return self.getViewsAsJson();\n+      },\n+      /**\n+       * Get the minimum and maximum visible values for a given track.\n+       *\n+       * @param {string} viewId The id of the view containing the track.\n+       * @param {string} trackId The id of the track to query.\n+       * @param {bool} [ignoreOffScreenValues=false] If ``true`` only truly visible values\n+       *  are considered. Otherwise the values of visible tiles are used. Not that\n+       *  considering only the truly visible\n+       *  values results in a roughly 10x slowdown (from 0.1 to 1 millisecond).\n+       * @param {bool} [ignoreFixedScale=false]  If ``true`` potentially fixed scaled values are\n+       *  ignored. I.e., if the\n+       *  absolute range is ``[1, 18]`` but you have fixed the output range to\n+       *  ``[4, 5]`` you would normally retrieve ``[4, 5]``. Having this option set to\n+       *  ``true`` retrieves the absolute ``[1, 18]`` range.\n+       * @example\n+       * const [minVal, maxVal] = hgv.getMinMaxValue('myView', 'myTrack');\n+       * @returns {Array} The minimum and maximum value\n+       */\n+      getMinMaxValue(\n         viewId,\n         trackId,\n-        ignoreOffScreenValues,\n-        ignoreFixedScale\n-      );\n-    },\n-\n-    /**\n-     * Generate a sharable link to the current view config. The `url` parameter should contain\n-     * the API endpoint used to export the view link (e.g. 'http://localhost:8989/api/v1/viewconfs').\n-     * If it is not provided, the value is taken from the `exportViewUrl` value of the viewconf.\n-     *\n-     * @param {string}  url  Custom URL that should point to a higlass server's\n-     *   view config endpoint, i.e.,\n-     *   `http://my-higlass-server.com/api/v1/viewconfs/`.\n-     * @returns {Object}  Promise resolving to the link ID and URL.\n-     * @example\n-     * hgv.shareViewConfigAsLink('http://localhost:8989/api/v1/viewconfs')\n-     * .then((sharedViewConfig) => {\n-     *   console.log(`Shared view config (ID: ${sharedViewConfig.id}) is available at ${sharedViewConfig.url}`)\n-     * })\n-     * .catch((err) => { console.error('Something did not work. Sorry', err); })\n-     */\n-    shareViewConfigAsLink(url) {\n-      return self.handleExportViewsAsLink(url, true);\n-    },\n-\n-    /**\n-     * Show overlays where this track can be positioned\n-     *\n-     * @param {obj} track { server, tilesetUid, datatype }\n-     */\n-    showAvailableTrackPositions(track) {\n-      self.setState({\n-        draggingHappening: track,\n-      });\n-    },\n-\n-    /**\n-     * Hide the overlay showing where a track can be positioned\n-     */\n-    hideAvailableTrackPositions() {\n-      self.setState({\n-        draggingHappening: null,\n-      });\n-    },\n-\n-    /**\n-     *\n-     * When comparing different 1D tracks it can be desirable to fix their y or value\n-     * scale\n-     *\n-     * @param {string} [viewId=''] The view identifier. If you only have one view this\n-     * parameter can be omitted.\n-     *\n-     * @param {string} [trackId=null] The track identifier.\n-     * @param [Number] [minValue=null] Minimum value used for scaling the track.\n-     * @param [Number] [maxValue=null] Maximum value used for scaling the track.\n-     *\n-     * @example\n-     *\n-     * hgv.setTrackValueScale(myView, myTrack, 0, 100); // Sets the scaling to [0, 100]\n-     * hgv.setTrackValueScale(myView, myTrack); // Unsets the fixed scaling, i.e., enables \n-     * dynamic scaling again.\n-     */\n-    setTrackValueScaleLimits(viewId, trackId, minValue, maxValue) {\n-      self.setTrackValueScaleLimits(viewId, trackId, minValue, maxValue);\n-    },\n-\n-    /**\n-     * Choose a theme.\n-     */\n-    setDarkTheme(darkTheme) {\n-      setDarkTheme(!!darkTheme);\n-    },\n-\n-    /**\n-     * Change the current view port to a certain data location.  When ``animateTime`` is\n-     * greater than 0, animate the transition.\n-\n-     * If working with genomic data, a chromosome info file will need to be used in\n-     * order to calculate \"data\" coordinates from chromosome coordinates. \"Data\"\n-     * coordinates are simply the coordinates as if the chromosomes were placed next\n-     * to each other.\n-     *\n-     * @param {string} viewUid The identifier of the view to zoom\n-     * @param {Number} start1Abs The x start position\n-     * @param {Number} end1Abs The x end position\n-     * @param {Number} start2Abs (optional) The y start position. If not specified\n-     *    start1Abs will be used.\n-     * @param {Number} end2Abs (optional) The y end position. If not specified\n-     *    end1Abs will be used\n-     * @param {Number} animateTime The time to spend zooming to the specified location\n-     * @example\n-     *    // Absolute coordinates\n-     * hgApi.zoomTo('view1', 1000000, 1100000, 2000000, 2100000, 500);\n-     * // Chromosomal coordinates\n-     * hglib\n-     *   // Pass in the URL of your chrom sizes\n-     *   .ChromosomeInfo('//s3.amazonaws.com/pkerp/data/hg19/chromSizes.tsv')\n-     *   // Now we can use the chromInfo object to convert\n-     *   .then((chromInfo) => {\n-     *     // Go to PTEN\n-     *     hgApi.zoomTo(\n-     *       viewConfig.views[0].uid,\n-     *       chromInfo.chrToAbs(['chr10', 89596071]),\n-     *       chromInfo.chrToAbs(['chr10', 89758810]),\n-     *       chromInfo.chrToAbs(['chr10', 89596071]),\n-     *       chromInfo.chrToAbs(['chr10', 89758810]),\n-     *       2500  // Animation time\n-     *     );\n-     *   });\n-     *   // Just in case, let us catch errors\n-     *   .catch(error => console.error('Oh boy...', error))\n-     */\n-    zoomTo(\n-      viewUid,\n-      start1Abs,\n-      end1Abs,\n-      start2Abs,\n-      end2Abs,\n-      animateTime = 0,\n-    ) {\n-      self.zoomTo(viewUid, start1Abs, end1Abs, start2Abs, end2Abs, animateTime);\n-    },\n-\n-    /**\n-     * Zoom so that the entirety of all the datasets in a view\n-     * are visible.\n-     * The passed in ``viewUid`` should refer to a view which is present. If it\n-     * doesn't, an exception will be thrown. Note that if this function is invoked\n-     * directly after a HiGlass component is created, the information about the\n-     * visible tilesets will not have been retrieved from the server and\n-     * ``zoomToDataExtent`` will not work as expected. To ensure that the\n-     * visible data has been loaded from the server, use the ``setViewConfig``\n-     * function and place ``zoomToDataExtent`` in the promise resolution.\n-     *\n-     * @param {string} viewUid The view uid of the view to zoom\n-     * @example\n-     *\n-     * const p = hgv.setViewConfig(newViewConfig);\n-     * p.then(() => {\n-     *     hgv.zoomToDataExtent('viewUid');\n-     * });\n-     */\n-    zoomToDataExtent(viewUid) {\n-      self.handleZoomToData(viewUid);\n-    },\n-\n-    /**\n-     * The endpoint allows you to reset the viewport to the initially defined X and Y\n-     * domains of your view config.\n-     *\n-     * @param {string} viewId The view identifier. If you have only one view you can\n-     * omit this parameter.\n-     *\n-     * @example\n-     *\n-     * hgv.resetViewport(); // Resets the first view\n-     */\n-    resetViewport(viewId) {\n-      self.resetViewport(viewId);\n-    },\n-\n-    /**\n-     * Some tools needs conflicting mouse events such as mousedown or mousemove. To\n-     * avoid complicated triggers for certain actions HiGlass supports different mouse\n-     * tools for different interactions. The default mouse tool enables pan&zoom. The\n-     * only other mouse tool available right now is ``select``, which lets you brush\n-     * on to a track to select a range for annotating regions.\n-     *\n-     * @param {string} [mouseTool='']  Select a mouse tool to use. Currently there\n-     * only 'default' and 'select' are available.\n-     *\n-     * @example\n-     *\n-     * hgv.activateTool('select'); // Select tool is active\n-     * hgv.activateTool(); // Default pan&zoom tool is active\n-     */\n-    activateTool(tool) {\n-      switch (tool) {\n-        case 'select':\n-          self.setMouseTool(MOUSE_TOOL_SELECT);\n-          break;\n-\n-        default:\n-          self.setMouseTool(MOUSE_TOOL_MOVE);\n-          break;\n-      }\n-    },\n-    \n-    /**\n-     * Get a Promise which returns a Blob containing a PNG for the current view.\n-     * It's possible to get string of the PNG bytes from that:\n-     *\n-     * hgApi.exportAsPngBlobPromise().then(function(blob) {\n-     *   var reader = new FileReader();\n-     *   reader.addEventListener(\"loadend\", function() {\n-     *     var array = new Uint8Array(reader.result.slice(0,8));\n-     *     console.log(array);\n-     *     console.log(new TextDecoder(\"iso-8859-2\").decode(array));\n-     *   });\n-     *   reader.readAsArrayBuffer(blob);\n-     * });\n-     *\n-     * @returns {promise}\n-     */\n-    exportAsPngBlobPromise() {\n-      return self.createPNGBlobPromise();\n-    },\n-\n-    /**\n-     * Get the current view as an SVG. Relies on all the tracks implementing\n-     * their respective exportAsSVG methods.\n-     *\n-     * @returns {string} An SVG string of the current view.\n-     */\n-    exportAsSvg() {\n-      return self.createSVGString();\n-    },\n-\n-    /**\n-     * Export the current view as a Viewconf.\n-     *\n-     * @returns {string} A stringified version of the current viewconf\n-     */\n-    exportAsViewConfString() {\n-      return self.getViewsAsString();\n-    },\n-\n-    /**\n-     * Get the current range selection\n-     *\n-     * @return {Array} The current range selection\n-     */\n-    getRangeSelection() {\n-      return self.rangeSelection;\n-    },\n-\n-    /**\n-     * Get the current location for a view.\n-     *\n-     * @param {string} [viewId=null] The id of the view to get the location for\n-     * @returns {obj} A an object containing two Arrays representing the domains of\n-     *  the x andy scales of the view.\n-     * @example\n-     *\n-     * const {xScale, yScale} = hgv.getLocation('viewId');\n-     */\n-    getLocation(viewId) {\n-      const wurstId = viewId\n-        ? self.xScales[viewId] && self.yScales[viewId] && viewId\n-        : Object.values(self.tiledPlots)[0] && Object.values(self.tiledPlots)[0].props.uid;\n-\n-      if (!wurstId) {\n-        return 'Please provide a valid view UUID sweetheart 😙';\n-      }\n-\n-      return {\n-        xDomain: self.xScales[wurstId].domain(),\n-        yDomain: self.yScales[wurstId].domain()\n-      };\n-    },\n-\n-    /**\n-     * Return the track's javascript object. This is useful for subscribing to\n-     * data events (dataChanged)\n-     */\n-    getTrackObject(viewId, trackId) {\n-      let newViewId = viewId;\n-      let newTrackId = trackId;\n-\n-      if (!trackId) {\n-        newViewId = Object.values(self.state.views)[0].uid;\n-        newTrackId = viewId;\n-      }\n-\n-      return getTrackObjectFromHGC(self, newViewId, newTrackId);\n-    },\n-\n-    /**\n-     * Cancel a subscription.\n-     *\n-     * @param {string} event One of the available events\n-     * @param {function} listener The function to unsubscribe\n-     * @param {string} viewId The viewId to unsubscribe it from (not strictly necessary)\n-     * The variables used in the following examples are coming from the examples of ``on()``.\n-     *\n-     * @example\n-     *\n-     * hgv.off('location', listener, 'viewId1');\n-     * hgv.off('rangeSelection', rangeListener);\n-     * hgv.off('viewConfig', viewConfigListener);\n-     * hgv.off('mouseMoveZoom', mmz);\n-     */\n-    off(event, listenerId, viewId) {\n-      const callback = typeof listenerId === 'object'\n-        ? listenerId.callback\n-        : listenerId;\n-\n-      switch (event) {\n-        case 'location':\n-          self.offLocationChange(viewId, listenerId);\n-          break;\n-\n-        case 'mouseMoveZoom':\n-          apiPubSub.unsubscribe('mouseMoveZoom', callback);\n-          break;\n-\n-        case 'rangeSelection':\n-          apiPubSub.unsubscribe('rangeSelection', callback);\n-          break;\n-\n-        case 'viewConfig':\n-          self.offViewChange(listenerId);\n-          break;\n-\n-        default:\n-          // nothing\n-          break;\n-      }\n-    },\n-\n-    /**\n-     * Subscribe to events\n-     *\n-     *\n-     * HiGlass exposes the following event, which one can subscribe to via this method:\n-     *\n-     * - location\n-     * - rangeSelection\n-     * - viewConfig\n-     * - mouseMoveZoom\n-     *\n-     * **Event types**\n-     *\n-     * ``location:`` Returns an object describing the visible region\n-     *\n-     * .. code-block:: javascript\n-     *\n-     *    {\n-     *        xDomain: [1347750580.3773856, 1948723324.787681],\n-     *        xRange: [0, 346],\n-     *        yDomain: [1856870481.5391564, 2407472678.0075483],\n-     *        yRange: [0, 317]\n-     *    }\n-     *\n-     * ``rangeSelection:`` Returns a BED- (1D) or BEDPE (1d) array of the selected data and \n-     * genomic range (if chrom-sizes are available)\n-     *\n-     * .. code-block:: javascript\n-     *\n-     *  // Global output\n-     *  {\n-     *    dataRange: [...]\n-     *    genomicRange: [...]\n-     *  }\n-     *\n-     *  // 1D data range\n-     *  [[1218210862, 1528541001], null]\n-     *\n-     *  // 2D data range\n-     *  [[1218210862, 1528541001], [1218210862, 1528541001]]\n-     *\n-     *  // 1D or BED-like array\n-     *  [[\"chr1\", 249200621, \"chrM\", 50000], null]\n-     *\n-     *  // 2D or BEDPE-like array\n-     *  [[\"chr1\", 249200621, \"chr2\", 50000], [\"chr3\", 197972430, \"chr4\", 50000]]\n-     *\n-     * ``viewConfig:`` Returns the current view config.\n-     *\n-     * ``mouseMoveZoom:`` Returns the raw data around the mouse cursors screen location\n-     * and the related genomic location.\n-     *\n-     * .. code-block:: javascript\n-     *\n-     *  {\n-     *    data, // Raw Float32Array\n-     *    dim,  // Dimension of the lens (the lens is squared)\n-     *    toRgb,  // Current float-to-rgb converter\n-     *    center,  // BED array of the cursors genomic location\n-     *    xRange,  // BEDPE array of the x genomic range\n-     *    yRange,  // BEDPE array of the y genomic range\n-     *    rel  // If true the above three genomic locations are relative\n-     *  }\n-     *\n-     * @param {string} event One of the events described below\n-     *\n-     * @param {function} callback A callback to be called when the event occurs\n-     *\n-     * @param {string} viewId The view ID to listen to events\n-     *\n-     * @example\n-     *\n-     *  let locationListenerId;\n-     * hgv.on(\n-     *   'location',\n-     *   location => console.log('Here we are:', location),\n-     *   'viewId1',\n-     *   listenerId => locationListenerId = listenerId\n-     * );\n-     *\n-     * const rangeListenerId = hgv.on(\n-     *   'rangeSelection',\n-     *   range => console.log('Selected', range)\n-     * );\n-     *\n-     * const viewConfigListenerId = hgv.on(\n-     *   'viewConfig',\n-     *   range => console.log('Selected', range)\n-     * );\n-     *\n-     *  const mmz = event => console.log('Moved', event);\n-     *  hgv.on('mouseMoveZoom', mmz);\n-     */\n-    on(event, callback, viewId, callbackId) {\n-      switch (event) {\n-        case 'location':\n-          // returns a set of scales (xScale, yScale) on every zoom event\n-          return self.onLocationChange(viewId, callback, callbackId);\n-\n-        case 'mouseMoveZoom':\n-          return apiPubSub.subscribe('mouseMoveZoom', callback);\n-\n-        case 'rangeSelection':\n-          return apiPubSub.subscribe('rangeSelection', callback);\n-\n-        case 'viewConfig':\n-          return self.onViewChange(callback);\n-\n-        default:\n-          return undefined;\n-      }\n-    },\n+        ignoreOffScreenValues = false,\n+        ignoreFixedScale = false\n+      ) {\n+        return self.getMinMaxValue(\n+          viewId,\n+          trackId,\n+          ignoreOffScreenValues,\n+          ignoreFixedScale\n+        );\n+      },\n+\n+      /**\n+       * Generate a sharable link to the current view config. The `url` parameter should contain\n+       * the API endpoint used to export the view link (e.g. 'http://localhost:8989/api/v1/viewconfs').\n+       * If it is not provided, the value is taken from the `exportViewUrl` value of the viewconf.\n+       *\n+       * @param {string}  url  Custom URL that should point to a higlass server's\n+       *   view config endpoint, i.e.,\n+       *   `http://my-higlass-server.com/api/v1/viewconfs/`.\n+       * @returns {Object}  Promise resolving to the link ID and URL.\n+       * @example\n+       * hgv.shareViewConfigAsLink('http://localhost:8989/api/v1/viewconfs')\n+       * .then((sharedViewConfig) => {\n+       *   console.log(`Shared view config (ID: ${sharedViewConfig.id}) is available at ${sharedViewConfig.url}`)\n+       * })\n+       * .catch((err) => { console.error('Something did not work. Sorry', err); })\n+       */\n+      shareViewConfigAsLink(url) {\n+        return self.handleExportViewsAsLink(url, true);\n+      },\n+\n+      /**\n+       * Show overlays where this track can be positioned\n+       *\n+       * @param {obj} track { server, tilesetUid, datatype }\n+       */\n+      showAvailableTrackPositions(track) {\n+        self.setState({\n+          draggingHappening: track,\n+        });\n+      },\n+\n+      /**\n+       * Hide the overlay showing where a track can be positioned\n+       */\n+      hideAvailableTrackPositions() {\n+        self.setState({\n+          draggingHappening: null,\n+        });\n+      },\n+\n+      /**\n+       *\n+       * When comparing different 1D tracks it can be desirable to fix their y or value\n+       * scale\n+       *\n+       * @param {string} [viewId=''] The view identifier. If you only have one view this\n+       * parameter can be omitted.\n+       *\n+       * @param {string} [trackId=null] The track identifier.\n+       * @param [Number] [minValue=null] Minimum value used for scaling the track.\n+       * @param [Number] [maxValue=null] Maximum value used for scaling the track.\n+       *\n+       * @example\n+       *\n+       * hgv.setTrackValueScale(myView, myTrack, 0, 100); // Sets the scaling to [0, 100]\n+       * hgv.setTrackValueScale(myView, myTrack); // Unsets the fixed scaling, i.e., enables\n+       * dynamic scaling again.\n+       */\n+      setTrackValueScaleLimits(viewId, trackId, minValue, maxValue) {\n+        self.setTrackValueScaleLimits(viewId, trackId, minValue, maxValue);\n+      },\n+\n+      /**\n+       * Choose a theme.\n+       */\n+      setDarkTheme(darkTheme) {\n+        setDarkTheme(!!darkTheme);\n+      },\n+\n+      /**\n+       * Change the current view port to a certain data location.  When ``animateTime`` is\n+       * greater than 0, animate the transition.\n+\n+       * If working with genomic data, a chromosome info file will need to be used in\n+       * order to calculate \"data\" coordinates from chromosome coordinates. \"Data\"\n+       * coordinates are simply the coordinates as if the chromosomes were placed next\n+       * to each other.\n+       *\n+       * @param {string} viewUid The identifier of the view to zoom\n+       * @param {Number} start1Abs The x start position\n+       * @param {Number} end1Abs The x end position\n+       * @param {Number} start2Abs (optional) The y start position. If not specified\n+       *    start1Abs will be used.\n+       * @param {Number} end2Abs (optional) The y end position. If not specified\n+       *    end1Abs will be used\n+       * @param {Number} animateTime The time to spend zooming to the specified location\n+       * @example\n+       *    // Absolute coordinates\n+       * hgApi.zoomTo('view1', 1000000, 1100000, 2000000, 2100000, 500);\n+       * // Chromosomal coordinates\n+       * hglib\n+       *   // Pass in the URL of your chrom sizes\n+       *   .ChromosomeInfo('//s3.amazonaws.com/pkerp/data/hg19/chromSizes.tsv')\n+       *   // Now we can use the chromInfo object to convert\n+       *   .then((chromInfo) => {\n+       *     // Go to PTEN\n+       *     hgApi.zoomTo(\n+       *       viewConfig.views[0].uid,\n+       *       chromInfo.chrToAbs(['chr10', 89596071]),\n+       *       chromInfo.chrToAbs(['chr10', 89758810]),\n+       *       chromInfo.chrToAbs(['chr10', 89596071]),\n+       *       chromInfo.chrToAbs(['chr10', 89758810]),\n+       *       2500  // Animation time\n+       *     );\n+       *   });\n+       *   // Just in case, let us catch errors\n+       *   .catch(error => console.error('Oh boy...', error))\n+       */\n+      zoomTo(\n+        viewUid,\n+        start1Abs,\n+        end1Abs,\n+        start2Abs,\n+        end2Abs,\n+        animateTime = 0,\n+      ) {\n+        self.zoomTo(viewUid, start1Abs, end1Abs, start2Abs, end2Abs, animateTime);\n+      },\n+\n+      /**\n+       * Zoom so that the entirety of all the datasets in a view\n+       * are visible.\n+       * The passed in ``viewUid`` should refer to a view which is present. If it\n+       * doesn't, an exception will be thrown. Note that if this function is invoked\n+       * directly after a HiGlass component is created, the information about the\n+       * visible tilesets will not have been retrieved from the server and\n+       * ``zoomToDataExtent`` will not work as expected. To ensure that the\n+       * visible data has been loaded from the server, use the ``setViewConfig``\n+       * function and place ``zoomToDataExtent`` in the promise resolution.\n+       *\n+       * @param {string} viewUid The view uid of the view to zoom\n+       * @example\n+       *\n+       * const p = hgv.setViewConfig(newViewConfig);\n+       * p.then(() => {\n+       *     hgv.zoomToDataExtent('viewUid');\n+       * });\n+       */\n+      zoomToDataExtent(viewUid) {\n+        self.handleZoomToData(viewUid);\n+      },\n+\n+      /**\n+       * The endpoint allows you to reset the viewport to the initially defined X and Y\n+       * domains of your view config.\n+       *\n+       * @param {string} viewId The view identifier. If you have only one view you can\n+       * omit this parameter.\n+       *\n+       * @example\n+       *\n+       * hgv.resetViewport(); // Resets the first view\n+       */\n+      resetViewport(viewId) {\n+        self.resetViewport(viewId);\n+      },\n+\n+      /**\n+       * Some tools needs conflicting mouse events such as mousedown or mousemove. To\n+       * avoid complicated triggers for certain actions HiGlass supports different mouse\n+       * tools for different interactions. The default mouse tool enables pan&zoom. The\n+       * only other mouse tool available right now is ``select``, which lets you brush\n+       * on to a track to select a range for annotating regions.\n+       *\n+       * @param {string} [mouseTool='']  Select a mouse tool to use. Currently there\n+       * only 'default' and 'select' are available.\n+       *\n+       * @example\n+       *\n+       * hgv.activateTool('select'); // Select tool is active\n+       * hgv.activateTool(); // Default pan&zoom tool is active\n+       */\n+      activateTool(tool) {\n+        switch (tool) {\n+          case 'select':\n+            self.setMouseTool(MOUSE_TOOL_SELECT);\n+            break;\n+\n+          default:\n+            self.setMouseTool(MOUSE_TOOL_MOVE);\n+            break;\n+        }\n+      },\n+\n+      /**\n+       * Get a Promise which returns a Blob containing a PNG for the current view.\n+       * It's possible to get string of the PNG bytes from that:\n+       *\n+       * hgApi.exportAsPngBlobPromise().then(function(blob) {\n+       *   var reader = new FileReader();\n+       *   reader.addEventListener(\"loadend\", function() {\n+       *     var array = new Uint8Array(reader.result.slice(0,8));\n+       *     console.log(array);\n+       *     console.log(new TextDecoder(\"iso-8859-2\").decode(array));\n+       *   });\n+       *   reader.readAsArrayBuffer(blob);\n+       * });\n+       *\n+       * @returns {promise}\n+       */\n+      exportAsPngBlobPromise() {\n+        return self.createPNGBlobPromise();\n+      },\n+\n+      /**\n+       * Get the current view as an SVG. Relies on all the tracks implementing\n+       * their respective exportAsSVG methods.\n+       *\n+       * @returns {string} An SVG string of the current view.\n+       */\n+      exportAsSvg() {\n+        return self.createSVGString();\n+      },\n+\n+      /**\n+       * Export the current view as a Viewconf.\n+       *\n+       * @returns {string} A stringified version of the current viewconf\n+       */\n+      exportAsViewConfString() {\n+        return self.getViewsAsString();\n+      },\n+\n+      /**\n+       * Get the current range selection\n+       *\n+       * @return {Array} The current range selection\n+       */\n+      getRangeSelection() {\n+        return self.rangeSelection;\n+      },\n+\n+      /**\n+       * Get the current location for a view.\n+       *\n+       * @param {string} [viewId=null] The id of the view to get the location for\n+       * @returns {obj} A an object containing two Arrays representing the domains of\n+       *  the x andy scales of the view.\n+       * @example\n+       *\n+       * const {xScale, yScale} = hgv.getLocation('viewId');\n+       */\n+      getLocation(viewId) {\n+        const wurstId = viewId\n+          ? self.xScales[viewId] && self.yScales[viewId] && viewId\n+          : Object.values(self.tiledPlots)[0] && Object.values(self.tiledPlots)[0].props.uid;\n+\n+        if (!wurstId) {\n+          return 'Please provide a valid view UUID sweetheart 😙';\n+        }\n+\n+        return {\n+          xDomain: self.xScales[wurstId].domain(),\n+          yDomain: self.yScales[wurstId].domain()\n+        };\n+      },\n+\n+      /**\n+       * Return the track's javascript object. This is useful for subscribing to\n+       * data events (dataChanged)\n+       */\n+      getTrackObject(viewId, trackId) {\n+        let newViewId = viewId;\n+        let newTrackId = trackId;\n+\n+        if (!trackId) {\n+          newViewId = Object.values(self.state.views)[0].uid;\n+          newTrackId = viewId;\n+        }\n+\n+        return getTrackObjectFromHGC(self, newViewId, newTrackId);\n+      },\n+\n+      /**\n+       * Cancel a subscription.\n+       *\n+       * @param {string} event One of the available events\n+       * @param {function} listener The function to unsubscribe\n+       * @param {string} viewId The viewId to unsubscribe it from (not strictly necessary)\n+       * The variables used in the following examples are coming from the examples of ``on()``.\n+       *\n+       * @example\n+       *\n+       * hgv.off('location', listener, 'viewId1');\n+       * hgv.off('rangeSelection', rangeListener);\n+       * hgv.off('viewConfig', viewConfigListener);\n+       * hgv.off('mouseMoveZoom', mmz);\n+       */\n+      off(event, listenerId, viewId) {\n+        const callback = typeof listenerId === 'object'\n+          ? listenerId.callback\n+          : listenerId;\n+\n+        switch (event) {\n+          case 'location':\n+            self.offLocationChange(viewId, listenerId);\n+            break;\n+\n+          case 'mouseMoveZoom':\n+            apiPubSub.unsubscribe('mouseMoveZoom', callback);\n+            break;\n+\n+          case 'rangeSelection':\n+            apiPubSub.unsubscribe('rangeSelection', callback);\n+            break;\n+\n+          case 'viewConfig':\n+            self.offViewChange(listenerId);\n+            break;\n+\n+          default:\n+            // nothing\n+            break;\n+        }\n+      },\n+\n+      /**\n+       * Subscribe to events\n+       *\n+       *\n+       * HiGlass exposes the following event, which one can subscribe to via this method:\n+       *\n+       * - location\n+       * - rangeSelection\n+       * - viewConfig\n+       * - mouseMoveZoom\n+       *\n+       * **Event types**\n+       *\n+       * ``location:`` Returns an object describing the visible region\n+       *\n+       * .. code-block:: javascript\n+       *\n+       *    {\n+       *        xDomain: [1347750580.3773856, 1948723324.787681],\n+       *        xRange: [0, 346],\n+       *        yDomain: [1856870481.5391564, 2407472678.0075483],\n+       *        yRange: [0, 317]\n+       *    }\n+       *\n+       * ``rangeSelection:`` Returns a BED- (1D) or BEDPE (1d) array of the selected data and\n+       * genomic range (if chrom-sizes are available)\n+       *\n+       * .. code-block:: javascript\n+       *\n+       *  // Global output\n+       *  {\n+       *    dataRange: [...]\n+       *    genomicRange: [...]\n+       *  }\n+       *\n+       *  // 1D data range\n+       *  [[1218210862, 1528541001], null]\n+       *\n+       *  // 2D data range\n+       *  [[1218210862, 1528541001], [1218210862, 1528541001]]\n+       *\n+       *  // 1D or BED-like array\n+       *  [[\"chr1\", 249200621, \"chrM\", 50000], null]\n+       *\n+       *  // 2D or BEDPE-like array\n+       *  [[\"chr1\", 249200621, \"chr2\", 50000], [\"chr3\", 197972430, \"chr4\", 50000]]\n+       *\n+       * ``viewConfig:`` Returns the current view config.\n+       *\n+       * ``mouseMoveZoom:`` Returns the raw data around the mouse cursors screen location\n+       * and the related genomic location.\n+       *\n+       * .. code-block:: javascript\n+       *\n+       *  {\n+       *    data, // Raw Float32Array\n+       *    dim,  // Dimension of the lens (the lens is squared)\n+       *    toRgb,  // Current float-to-rgb converter\n+       *    center,  // BED array of the cursors genomic location\n+       *    xRange,  // BEDPE array of the x genomic range\n+       *    yRange,  // BEDPE array of the y genomic range\n+       *    rel  // If true the above three genomic locations are relative\n+       *  }\n+       *\n+       * @param {string} event One of the events described below\n+       *\n+       * @param {function} callback A callback to be called when the event occurs\n+       *\n+       * @param {string} viewId The view ID to listen to events\n+       *\n+       * @example\n+       *\n+       *  let locationListenerId;\n+       * hgv.on(\n+       *   'location',\n+       *   location => console.log('Here we are:', location),\n+       *   'viewId1',\n+       *   listenerId => locationListenerId = listenerId\n+       * );\n+       *\n+       * const rangeListenerId = hgv.on(\n+       *   'rangeSelection',\n+       *   range => console.log('Selected', range)\n+       * );\n+       *\n+       * const viewConfigListenerId = hgv.on(\n+       *   'viewConfig',\n+       *   range => console.log('Selected', range)\n+       * );\n+       *\n+       *  const mmz = event => console.log('Moved', event);\n+       *  hgv.on('mouseMoveZoom', mmz);\n+       */\n+      on(event, callback, viewId, callbackId) {\n+        switch (event) {\n+          case 'location':\n+            // returns a set of scales (xScale, yScale) on every zoom event\n+            return self.onLocationChange(viewId, callback, callbackId);\n+\n+          case 'mouseMoveZoom':\n+            return apiPubSub.subscribe('mouseMoveZoom', callback);\n+\n+          case 'rangeSelection':\n+            return apiPubSub.subscribe('rangeSelection', callback);\n+\n+          case 'viewConfig':\n+            return self.onViewChange(callback);\n+\n+          default:\n+            return undefined;\n+        }\n+      },\n+    }","path":"app/scripts/api.js","position":1228,"original_position":1228,"commit_id":"80465ecc8146eac92f8f4c4ba0433bd9c523bf7c","original_commit_id":"c376bcb08e675b11e72463cc7dbc07b315d74c1f","user":{"login":"pkerpedjiev","id":2143629,"node_id":"MDQ6VXNlcjIxNDM2Mjk=","avatar_url":"https://avatars3.githubusercontent.com/u/2143629?v=4","gravatar_id":"","url":"https://api.github.com/users/pkerpedjiev","html_url":"https://github.com/pkerpedjiev","followers_url":"https://api.github.com/users/pkerpedjiev/followers","following_url":"https://api.github.com/users/pkerpedjiev/following{/other_user}","gists_url":"https://api.github.com/users/pkerpedjiev/gists{/gist_id}","starred_url":"https://api.github.com/users/pkerpedjiev/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pkerpedjiev/subscriptions","organizations_url":"https://api.github.com/users/pkerpedjiev/orgs","repos_url":"https://api.github.com/users/pkerpedjiev/repos","events_url":"https://api.github.com/users/pkerpedjiev/events{/privacy}","received_events_url":"https://api.github.com/users/pkerpedjiev/received_events","type":"User","site_admin":false},"body":"OK, I didn't realize that it wasn't user-facing. I was under the impression that `api.js` was generally meant for public facing functions.","created_at":"2018-11-16T02:37:07Z","updated_at":"2018-11-16T02:37:07Z","html_url":"https://github.com/higlass/higlass/pull/370#discussion_r234075048","pull_request_url":"https://api.github.com/repos/higlass/higlass/pulls/370","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/higlass/higlass/pulls/comments/234075048"},"html":{"href":"https://github.com/higlass/higlass/pull/370#discussion_r234075048"},"pull_request":{"href":"https://api.github.com/repos/higlass/higlass/pulls/370"}},"in_reply_to_id":232891689},"pull_request":{"url":"https://api.github.com/repos/higlass/higlass/pulls/370","id":229511318,"node_id":"MDExOlB1bGxSZXF1ZXN0MjI5NTExMzE4","html_url":"https://github.com/higlass/higlass/pull/370","diff_url":"https://github.com/higlass/higlass/pull/370.diff","patch_url":"https://github.com/higlass/higlass/pull/370.patch","issue_url":"https://api.github.com/repos/higlass/higlass/issues/370","number":370,"state":"open","locked":false,"title":"Flekschas/pub sub provider","user":{"login":"flekschas","id":932103,"node_id":"MDQ6VXNlcjkzMjEwMw==","avatar_url":"https://avatars1.githubusercontent.com/u/932103?v=4","gravatar_id":"","url":"https://api.github.com/users/flekschas","html_url":"https://github.com/flekschas","followers_url":"https://api.github.com/users/flekschas/followers","following_url":"https://api.github.com/users/flekschas/following{/other_user}","gists_url":"https://api.github.com/users/flekschas/gists{/gist_id}","starred_url":"https://api.github.com/users/flekschas/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/flekschas/subscriptions","organizations_url":"https://api.github.com/users/flekschas/orgs","repos_url":"https://api.github.com/users/flekschas/repos","events_url":"https://api.github.com/users/flekschas/events{/privacy}","received_events_url":"https://api.github.com/users/flekschas/received_events","type":"User","site_admin":false},"body":"Right now we're using a global pubSub service. Which is not an issue in most cases but it leads to weird issues when multiple instances are running on the same page.\r\n\r\nThis PR attempts to solve this by factorizing and providerizing the pubSub service. For the React part this is fairly easy to achieve. For all the non-react code (e.g., tracks) I decided to pass the pubSub service down the class hierarchy just like the PIXI scene etc. Let me know what you think. Irrespective of the fact that we should limit the use of the pubSub as much as possible there's a clear need as a global application-specific messaging service.\r\n\r\nBy the way, I am using the same pubSub architecture for HGA and it works nicely and the provider pattern feels right as the usage is similar to other global state objects like the redux store or router.\r\n\r\n- [x] Global pubSub localized\r\n- [x] Track initialization adjusted\r\n- [x] pubSub pulled from [external library](https://github.com/flekschas/pub-sub)\r\n- [x] Test the HGC and JsApi independence","created_at":"2018-11-08T21:16:56Z","updated_at":"2018-11-16T02:37:07Z","closed_at":null,"merged_at":null,"merge_commit_sha":"7c0ea62088e537a12f1871c02f9cc1f01db73c80","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[{"id":356587143,"node_id":"MDU6TGFiZWwzNTY1ODcxNDM=","url":"https://api.github.com/repos/higlass/higlass/labels/enhancement","name":"enhancement","color":"c5def5","default":true},{"id":397558494,"node_id":"MDU6TGFiZWwzOTc1NTg0OTQ=","url":"https://api.github.com/repos/higlass/higlass/labels/important","name":"important","color":"fef2c0","default":false}],"milestone":null,"commits_url":"https://api.github.com/repos/higlass/higlass/pulls/370/commits","review_comments_url":"https://api.github.com/repos/higlass/higlass/pulls/370/comments","review_comment_url":"https://api.github.com/repos/higlass/higlass/pulls/comments{/number}","comments_url":"https://api.github.com/repos/higlass/higlass/issues/370/comments","statuses_url":"https://api.github.com/repos/higlass/higlass/statuses/80465ecc8146eac92f8f4c4ba0433bd9c523bf7c","head":{"label":"higlass:flekschas/pub-sub-provider","ref":"flekschas/pub-sub-provider","sha":"80465ecc8146eac92f8f4c4ba0433bd9c523bf7c","user":{"login":"higlass","id":44274216,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ0Mjc0MjE2","avatar_url":"https://avatars3.githubusercontent.com/u/44274216?v=4","gravatar_id":"","url":"https://api.github.com/users/higlass","html_url":"https://github.com/higlass","followers_url":"https://api.github.com/users/higlass/followers","following_url":"https://api.github.com/users/higlass/following{/other_user}","gists_url":"https://api.github.com/users/higlass/gists{/gist_id}","starred_url":"https://api.github.com/users/higlass/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/higlass/subscriptions","organizations_url":"https://api.github.com/users/higlass/orgs","repos_url":"https://api.github.com/users/higlass/repos","events_url":"https://api.github.com/users/higlass/events{/privacy}","received_events_url":"https://api.github.com/users/higlass/received_events","type":"Organization","site_admin":false},"repo":{"id":56026057,"node_id":"MDEwOlJlcG9zaXRvcnk1NjAyNjA1Nw==","name":"higlass","full_name":"higlass/higlass","private":false,"owner":{"login":"higlass","id":44274216,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ0Mjc0MjE2","avatar_url":"https://avatars3.githubusercontent.com/u/44274216?v=4","gravatar_id":"","url":"https://api.github.com/users/higlass","html_url":"https://github.com/higlass","followers_url":"https://api.github.com/users/higlass/followers","following_url":"https://api.github.com/users/higlass/following{/other_user}","gists_url":"https://api.github.com/users/higlass/gists{/gist_id}","starred_url":"https://api.github.com/users/higlass/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/higlass/subscriptions","organizations_url":"https://api.github.com/users/higlass/orgs","repos_url":"https://api.github.com/users/higlass/repos","events_url":"https://api.github.com/users/higlass/events{/privacy}","received_events_url":"https://api.github.com/users/higlass/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/higlass/higlass","description":"Fast contact matrix visualization for the web.","fork":false,"url":"https://api.github.com/repos/higlass/higlass","forks_url":"https://api.github.com/repos/higlass/higlass/forks","keys_url":"https://api.github.com/repos/higlass/higlass/keys{/key_id}","collaborators_url":"https://api.github.com/repos/higlass/higlass/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/higlass/higlass/teams","hooks_url":"https://api.github.com/repos/higlass/higlass/hooks","issue_events_url":"https://api.github.com/repos/higlass/higlass/issues/events{/number}","events_url":"https://api.github.com/repos/higlass/higlass/events","assignees_url":"https://api.github.com/repos/higlass/higlass/assignees{/user}","branches_url":"https://api.github.com/repos/higlass/higlass/branches{/branch}","tags_url":"https://api.github.com/repos/higlass/higlass/tags","blobs_url":"https://api.github.com/repos/higlass/higlass/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/higlass/higlass/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/higlass/higlass/git/refs{/sha}","trees_url":"https://api.github.com/repos/higlass/higlass/git/trees{/sha}","statuses_url":"https://api.github.com/repos/higlass/higlass/statuses/{sha}","languages_url":"https://api.github.com/repos/higlass/higlass/languages","stargazers_url":"https://api.github.com/repos/higlass/higlass/stargazers","contributors_url":"https://api.github.com/repos/higlass/higlass/contributors","subscribers_url":"https://api.github.com/repos/higlass/higlass/subscribers","subscription_url":"https://api.github.com/repos/higlass/higlass/subscription","commits_url":"https://api.github.com/repos/higlass/higlass/commits{/sha}","git_commits_url":"https://api.github.com/repos/higlass/higlass/git/commits{/sha}","comments_url":"https://api.github.com/repos/higlass/higlass/comments{/number}","issue_comment_url":"https://api.github.com/repos/higlass/higlass/issues/comments{/number}","contents_url":"https://api.github.com/repos/higlass/higlass/contents/{+path}","compare_url":"https://api.github.com/repos/higlass/higlass/compare/{base}...{head}","merges_url":"https://api.github.com/repos/higlass/higlass/merges","archive_url":"https://api.github.com/repos/higlass/higlass/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/higlass/higlass/downloads","issues_url":"https://api.github.com/repos/higlass/higlass/issues{/number}","pulls_url":"https://api.github.com/repos/higlass/higlass/pulls{/number}","milestones_url":"https://api.github.com/repos/higlass/higlass/milestones{/number}","notifications_url":"https://api.github.com/repos/higlass/higlass/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/higlass/higlass/labels{/name}","releases_url":"https://api.github.com/repos/higlass/higlass/releases{/id}","deployments_url":"https://api.github.com/repos/higlass/higlass/deployments","created_at":"2016-04-12T02:58:30Z","updated_at":"2018-11-15T02:47:36Z","pushed_at":"2018-11-16T02:31:11Z","git_url":"git://github.com/higlass/higlass.git","ssh_url":"git@github.com:higlass/higlass.git","clone_url":"https://github.com/higlass/higlass.git","svn_url":"https://github.com/higlass/higlass","homepage":"http://higlass.io","size":144793,"stargazers_count":98,"watchers_count":98,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":22,"mirror_url":null,"archived":false,"open_issues_count":139,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":22,"open_issues":139,"watchers":98,"default_branch":"develop"}},"base":{"label":"higlass:develop","ref":"develop","sha":"b0e6a311bec3ecc93585e287d3517eb518d9c253","user":{"login":"higlass","id":44274216,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ0Mjc0MjE2","avatar_url":"https://avatars3.githubusercontent.com/u/44274216?v=4","gravatar_id":"","url":"https://api.github.com/users/higlass","html_url":"https://github.com/higlass","followers_url":"https://api.github.com/users/higlass/followers","following_url":"https://api.github.com/users/higlass/following{/other_user}","gists_url":"https://api.github.com/users/higlass/gists{/gist_id}","starred_url":"https://api.github.com/users/higlass/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/higlass/subscriptions","organizations_url":"https://api.github.com/users/higlass/orgs","repos_url":"https://api.github.com/users/higlass/repos","events_url":"https://api.github.com/users/higlass/events{/privacy}","received_events_url":"https://api.github.com/users/higlass/received_events","type":"Organization","site_admin":false},"repo":{"id":56026057,"node_id":"MDEwOlJlcG9zaXRvcnk1NjAyNjA1Nw==","name":"higlass","full_name":"higlass/higlass","private":false,"owner":{"login":"higlass","id":44274216,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ0Mjc0MjE2","avatar_url":"https://avatars3.githubusercontent.com/u/44274216?v=4","gravatar_id":"","url":"https://api.github.com/users/higlass","html_url":"https://github.com/higlass","followers_url":"https://api.github.com/users/higlass/followers","following_url":"https://api.github.com/users/higlass/following{/other_user}","gists_url":"https://api.github.com/users/higlass/gists{/gist_id}","starred_url":"https://api.github.com/users/higlass/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/higlass/subscriptions","organizations_url":"https://api.github.com/users/higlass/orgs","repos_url":"https://api.github.com/users/higlass/repos","events_url":"https://api.github.com/users/higlass/events{/privacy}","received_events_url":"https://api.github.com/users/higlass/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/higlass/higlass","description":"Fast contact matrix visualization for the web.","fork":false,"url":"https://api.github.com/repos/higlass/higlass","forks_url":"https://api.github.com/repos/higlass/higlass/forks","keys_url":"https://api.github.com/repos/higlass/higlass/keys{/key_id}","collaborators_url":"https://api.github.com/repos/higlass/higlass/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/higlass/higlass/teams","hooks_url":"https://api.github.com/repos/higlass/higlass/hooks","issue_events_url":"https://api.github.com/repos/higlass/higlass/issues/events{/number}","events_url":"https://api.github.com/repos/higlass/higlass/events","assignees_url":"https://api.github.com/repos/higlass/higlass/assignees{/user}","branches_url":"https://api.github.com/repos/higlass/higlass/branches{/branch}","tags_url":"https://api.github.com/repos/higlass/higlass/tags","blobs_url":"https://api.github.com/repos/higlass/higlass/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/higlass/higlass/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/higlass/higlass/git/refs{/sha}","trees_url":"https://api.github.com/repos/higlass/higlass/git/trees{/sha}","statuses_url":"https://api.github.com/repos/higlass/higlass/statuses/{sha}","languages_url":"https://api.github.com/repos/higlass/higlass/languages","stargazers_url":"https://api.github.com/repos/higlass/higlass/stargazers","contributors_url":"https://api.github.com/repos/higlass/higlass/contributors","subscribers_url":"https://api.github.com/repos/higlass/higlass/subscribers","subscription_url":"https://api.github.com/repos/higlass/higlass/subscription","commits_url":"https://api.github.com/repos/higlass/higlass/commits{/sha}","git_commits_url":"https://api.github.com/repos/higlass/higlass/git/commits{/sha}","comments_url":"https://api.github.com/repos/higlass/higlass/comments{/number}","issue_comment_url":"https://api.github.com/repos/higlass/higlass/issues/comments{/number}","contents_url":"https://api.github.com/repos/higlass/higlass/contents/{+path}","compare_url":"https://api.github.com/repos/higlass/higlass/compare/{base}...{head}","merges_url":"https://api.github.com/repos/higlass/higlass/merges","archive_url":"https://api.github.com/repos/higlass/higlass/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/higlass/higlass/downloads","issues_url":"https://api.github.com/repos/higlass/higlass/issues{/number}","pulls_url":"https://api.github.com/repos/higlass/higlass/pulls{/number}","milestones_url":"https://api.github.com/repos/higlass/higlass/milestones{/number}","notifications_url":"https://api.github.com/repos/higlass/higlass/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/higlass/higlass/labels{/name}","releases_url":"https://api.github.com/repos/higlass/higlass/releases{/id}","deployments_url":"https://api.github.com/repos/higlass/higlass/deployments","created_at":"2016-04-12T02:58:30Z","updated_at":"2018-11-15T02:47:36Z","pushed_at":"2018-11-16T02:31:11Z","git_url":"git://github.com/higlass/higlass.git","ssh_url":"git@github.com:higlass/higlass.git","clone_url":"https://github.com/higlass/higlass.git","svn_url":"https://github.com/higlass/higlass","homepage":"http://higlass.io","size":144793,"stargazers_count":98,"watchers_count":98,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":22,"mirror_url":null,"archived":false,"open_issues_count":139,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":22,"open_issues":139,"watchers":98,"default_branch":"develop"}},"_links":{"self":{"href":"https://api.github.com/repos/higlass/higlass/pulls/370"},"html":{"href":"https://github.com/higlass/higlass/pull/370"},"issue":{"href":"https://api.github.com/repos/higlass/higlass/issues/370"},"comments":{"href":"https://api.github.com/repos/higlass/higlass/issues/370/comments"},"review_comments":{"href":"https://api.github.com/repos/higlass/higlass/pulls/370/comments"},"review_comment":{"href":"https://api.github.com/repos/higlass/higlass/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/higlass/higlass/pulls/370/commits"},"statuses":{"href":"https://api.github.com/repos/higlass/higlass/statuses/80465ecc8146eac92f8f4c4ba0433bd9c523bf7c"}},"author_association":"MEMBER"}}
{"actor":{"display_login":"pkerpedjiev"}}