"""Helper library for visualizations.
TODO(googleuser): Find a more reliable way to serve stuff from IPython
notebooks (e.g. determining where the root notebook directory is).
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import gzip
import os
import uuid
from google.protobuf import json_format
from dragnn.protos import trace_pb2
# Make a guess about where the IPython kernel root is.
_IPYTHON_KERNEL_PATH = os.path.realpath(os.getcwd())
# Bazel uses the 'data' attribute for this library to ensure viz.min.js.gz is
# packaged.
module_path = os.path.dirname(os.path.abspath(__file__))
viz_script = os.path.join(os.path.dirname(module_path), 'viz', 'viz.min.js.gz')
def _load_viz_script():
"""Reads the bundled visualization script.
Raises:
EnvironmentError: If the visualization script could not be found.
Returns:
str JavaScript source code.
"""
if not os.path.isfile(viz_script):
raise EnvironmentError(
'Visualization script should be built into {}'.format(viz_script))
with gzip.GzipFile(viz_script) as f:
return f.read()
def parse_trace_json(trace):
"""Converts a binary-encoded MasterTrace proto to a JSON parser trace.
Args:
trace: Binary string containing a MasterTrace.
Returns:
JSON str, as expected by visualization tools.
"""
as_proto = trace_pb2.MasterTrace.FromString(trace)
as_json = json_format.MessageToJson(
as_proto, preserving_proto_field_name=True)
return as_json
def _container_div(height='700px', contents=''):
elt_id = str(uuid.uuid4())
html = """
{contents}
""".format(
elt_id=elt_id, height=height, contents=contents)
return elt_id, html
def trace_html(trace, convert_to_unicode=True, height='700px', script=None):
"""Generates HTML that will render a master trace.
This will result in a self-contained "div" element.
Args:
trace: binary-encoded MasterTrace string.
convert_to_unicode: Whether to convert the output to unicode. Defaults to
True because IPython.display.HTML expects unicode, and we expect users to
often pass the output of this function to IPython.display.HTML.
height: CSS string representing the height of the element, default '700px'.
script: Visualization script contents, if the defaults are unacceptable.
Returns:
unicode or str with HTML contents.
"""
if script is None:
script = _load_viz_script()
json_trace = parse_trace_json(trace)
elt_id, div_html = _container_div(height=height)
as_str = """
{div_html}
""".format(
script=script, json=json_trace, elt_id=elt_id, div_html=div_html)
return unicode(as_str, 'utf-8') if convert_to_unicode else as_str
def open_in_new_window(html, notebook_html_fcn=None, temp_file_basename=None):
"""Opens an HTML visualization in a new window.
This function assumes that the module was loaded when the current working
directory is the IPython/Jupyter notebook root directory. Then it writes a
file ./tmp/_new_window_html/.html, and returns an HTML display
element, which will call `window.open("/files/")`. This works
because IPython serves files from the /files root.
Args:
html: HTML to write to a file.
notebook_html_fcn: Function to generate an HTML element; defaults to
IPython.display.HTML (lazily imported).
temp_file_basename: File name to write (defaults to .html).
Returns:
HTML notebook element, which will trigger the browser to open a new window.
"""
if isinstance(html, unicode):
html = html.encode('utf-8')
if notebook_html_fcn is None:
from IPython import display
notebook_html_fcn = display.HTML
if temp_file_basename is None:
temp_file_basename = '{}.html'.format(str(uuid.uuid4()))
rel_path = os.path.join('tmp', '_new_window_html', temp_file_basename)
abs_path = os.path.join(_IPYTHON_KERNEL_PATH, rel_path)
# Write the file, creating the directory if it doesn't exist.
if not os.path.isdir(os.path.dirname(abs_path)):
os.makedirs(os.path.dirname(abs_path))
with open(abs_path, 'w') as f:
f.write(html)
return notebook_html_fcn("""
""".format(rel_path))
class InteractiveVisualization(object):
"""Helper class for displaying visualizations interactively.
See usage in examples/dragnn/interactive_text_analyzer.ipynb.
"""
def initial_html(self, height='700px', script=None, init_message=None):
"""Returns HTML for a container, which will be populated later.
Args:
height: CSS string representing the height of the element, default
'700px'.
script: Visualization script contents, if the defaults are unacceptable.
init_message: Initial message to display.
Returns:
unicode with HTML contents.
"""
if script is None:
script = _load_viz_script()
if init_message is None:
init_message = 'Type a sentence and press (enter) to see the trace.'
self.elt_id, div_html = _container_div(
height=height, contents='{}'.format(init_message))
html = """
{div_html}
""".format(
script=script, div_html=div_html)
return unicode(html, 'utf-8') # IPython expects unicode.
def show_trace(self, trace):
"""Returns a JS script HTML fragment, which will populate the container.
Args:
trace: binary-encoded MasterTrace string.
Returns:
unicode with HTML contents.
"""
html = """
""".format(
json=parse_trace_json(trace), elt_id=self.elt_id)
return unicode(html, 'utf-8') # IPython expects unicode.