import json
import logging
import os
import threading
import time
import ipywidgets as widgets
from traitlets import Bool, Dict, Float, Int, List, Unicode, Union
import slugid
from ._version import __version__
[docs]def save_b64_image_to_png(filename, b64str):
"""Save a base64 encoded image to a file."""
import base64
imgdata = base64.b64decode(b64str.split(",")[1])
with open(filename, "wb") as f:
f.write(imgdata)
@widgets.register
class HiGlassDisplay(widgets.DOMWidget):
_view_name = Unicode("HiGlassDisplayView").tag(sync=True)
_model_name = Unicode("HiGlassDisplayModel").tag(sync=True)
_view_module = Unicode("higlass-jupyter").tag(sync=True)
_model_module = Unicode("higlass-jupyter").tag(sync=True)
_view_module_version = Unicode(__version__).tag(sync=True)
_model_module_version = Unicode(__version__).tag(sync=True)
_model_data = List([]).tag(sync=True)
viewconf = Dict({}).tag(sync=True)
height = Int().tag(sync=True)
dom_element_id = Unicode(read_only=True).tag(sync=True)
# Read-only properties that get updated by HiGlass exclusively
location = List(Union([Float(), List()]), read_only=True).tag(sync=True)
cursor_location = List([], read_only=True).tag(sync=True)
selection = List([], read_only=True).tag(sync=True)
# Short-hand options
auth_token = Unicode().tag(sync=True)
bounded = Bool(None, allow_none=True).tag(sync=True)
default_track_options = Dict({}).tag(sync=True)
dark_mode = Bool(False).tag(sync=True)
renderer = Unicode().tag(sync=True)
select_mode = Bool(False).tag(sync=True)
selection_on_alt = Bool(False).tag(sync=True)
# For any kind of options. Note that whatever is defined in options will
# be overwritten by the short-hand options
options = Dict({}).tag(sync=True)
def __init__(self, **kwargs):
self.on_msg(self._handle_js_events)
self.callbacks = {}
super(HiGlassDisplay, self).__init__(**kwargs)
def get_base64_img(self, callback):
uuid = slugid.nice()
self.callbacks[uuid] = callback
self.send(json.dumps({"request": "save_as_png", "params": {"uuid": uuid},}))
def _handle_js_events(self, widget, content, buffers=None):
try:
if self.callbacks[content["params"]["uuid"]]:
self.callbacks[content["params"]["uuid"]](content["imgData"])
del self.callbacks[content["params"]["uuid"]]
except Exception as e:
self.log.error(e)
self.log.exception("Unhandled exception while handling msg")
# def save_as_png(self, filename):
# """Save the currently visible plot to a png file"""
# from IPython.display import Javascript
# js = Javascript(
# f"""
# let d = document.getElementById('{self.dom_element_id}');
# d.api.exportAsPngBlobPromise().then(blob => {{
# let reader = new FileReader();
# reader.readAsDataURL(blob);
# reader.onloadend = function() {{
# let base64data = reader.result;
# let kernel = IPython.notebook.kernel;
# let command = `from higlass.viewer import save_b64_image_to_png; save_b64_image_to_png('{filename}', '''${{base64data}}''')`
# let ex = kernel.execute(command);
# }}
# }})
# """
# )
# return js
[docs]def display(
views,
location_syncs=[],
value_scale_syncs=[],
zoom_syncs=[],
host="localhost",
server_port=None,
dark_mode=False,
log_level=logging.ERROR,
fuse=True,
auth_token=None,
):
"""
Instantiate a HiGlass display with the given views.
Args:
views: A list of views to display. If the items in the list are
lists themselves, then automatically create views out of them.
location_syncs: A list of lists, each containing a list of views which
will scroll together.
value_scale_syncs: A list containing the value scale syncs. Each sync can be
one of:
1. a list of (View, Track) tuples
2. a list of Tracks (assumes that there is only one view)
3. a list of strings of the form "{viewUid}.{trackUid}"
zoom_syncs: A list of lists, each containing a list of views that
will zoom together.
host: The host on which the internal higlass server will be running on.
server_port: The port on which the internal higlass server will be running on.
dark_mode: Whether to use dark mode or not.
log_level: Level of logging to perform.
fuse: Whether to mount the fuse filesystem. Set to False if not loading any
data over http or https.
Returns:
(display: HiGlassDisplay, server: higlass.server.Server, higlass.client.viewconf) tuple
Display is an object used to create
a HiGlass viewer within a Jupyter notebook. The server object encapsulates
a Flask instance of a higlass server and the viewconf is a Python object
containing the viewconf describing the higlass dashboard.
"""
from .server import Server
from .client import CombinedTrack, DividedTrack, View, ViewConf, ViewportProjection
tilesets = []
# views can also be passed in as lists of tracks
new_views = []
for view in views:
if isinstance(view, (tuple, list)):
# view is a list of tracks
new_views.append(View(view))
else:
new_views.append(view)
views = new_views
for view in views:
for track in view.tracks:
if hasattr(track, "tracks"):
for track1 in track.tracks:
if not isinstance(track1, ViewportProjection) and track1.tileset:
tilesets += [track1.tileset]
if track.tileset:
tilesets += [track.tileset]
server = Server(
tilesets, host=host, port=server_port, fuse=fuse, log_level=log_level
)
server.start()
cloned_views = [View.from_dict(view.to_dict()) for view in views]
for view in cloned_views:
for track in view.tracks:
if isinstance(track, CombinedTrack):
for track1 in track.tracks:
if "fromViewUid" in track1.conf:
# this is a viewport projection and doesn't have
# a server
pass
elif "server" not in track1.conf or track1.conf["server"] is None:
track1.conf["server"] = server.api_address
elif "fromViewUid" in track.conf:
pass
elif "data" in track.conf:
# probably a divided track with a custom
# data fetcher
pass
else:
if "server" not in track.conf or track.conf["server"] is None:
track.conf["server"] = server.api_address
viewconf = ViewConf(
cloned_views,
location_syncs=location_syncs,
value_scale_syncs=value_scale_syncs,
zoom_syncs=zoom_syncs,
)
extra_args = {}
if auth_token:
extra_args["auth_token"] = auth_token
return (
HiGlassDisplay(
viewconf=viewconf.to_dict(),
hg_options={"theme": "dark" if dark_mode else "light",},
**extra_args
),
server,
viewconf,
)