"vscode:/vscode.git/clone" did not exist on "cd873f9c2f7c5615c1fd2712c9136296dd001317"
visualization.py 6.83 KB
Newer Older
Ivan Bogatyy's avatar
Ivan Bogatyy committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
"""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


Ivan Bogatyy's avatar
Ivan Bogatyy committed
57
58
59
60
61
62
63
64
65
def _optional_master_spec_json(master_spec):
  """Helper function to return 'null' or a master spec JSON string."""
  if master_spec is None:
    return 'null'
  else:
    return json_format.MessageToJson(
        master_spec, preserving_proto_field_name=True)


Ivan Bogatyy's avatar
Ivan Bogatyy committed
66
67
68
69
70
71
72
73
74
75
def _container_div(height='700px', contents=''):
  elt_id = str(uuid.uuid4())
  html = """
  <div id="{elt_id}" style="width: 100%; min-width: 200px; height: {height};">
  {contents}</div>
  """.format(
      elt_id=elt_id, height=height, contents=contents)
  return elt_id, html


Ivan Bogatyy's avatar
Ivan Bogatyy committed
76
77
78
79
80
def trace_html(trace,
               convert_to_unicode=True,
               height='700px',
               script=None,
               master_spec=None):
Ivan Bogatyy's avatar
Ivan Bogatyy committed
81
82
83
84
85
86
87
88
89
90
91
  """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.
Ivan Bogatyy's avatar
Ivan Bogatyy committed
92
93
    master_spec: Master spec proto (parsed), which can improve the layout. May
      be required in future versions.
Ivan Bogatyy's avatar
Ivan Bogatyy committed
94
95
96
97
98
99
100
101
102
103
104
105
106

  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 = """
  <meta charset="utf-8"/>
  {div_html}
  <script type='text/javascript'>
  {script}
Ivan Bogatyy's avatar
Ivan Bogatyy committed
107
  visualizeToDiv({json}, "{elt_id}", {master_spec_json});
Ivan Bogatyy's avatar
Ivan Bogatyy committed
108
109
  </script>
  """.format(
Ivan Bogatyy's avatar
Ivan Bogatyy committed
110
111
112
113
114
      script=script,
      json=json_trace,
      master_spec_json=_optional_master_spec_json(master_spec),
      elt_id=elt_id,
      div_html=div_html)
Ivan Bogatyy's avatar
Ivan Bogatyy committed
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
  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/<random-uuid>.html, and returns an HTML display
  element, which will call `window.open("/files/<filename>")`. 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 <random-uuid>.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("""
  <script type='text/javascript'>
  window.open("/files/{}");
  </script>
  """.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='<strong>{}</strong>'.format(init_message))
    html = """
    <meta charset="utf-8"/>
    {div_html}
    <script type='text/javascript'>
    {script}
    </script>
    """.format(
        script=script, div_html=div_html)
    return unicode(html, 'utf-8')  # IPython expects unicode.

Ivan Bogatyy's avatar
Ivan Bogatyy committed
196
  def show_trace(self, trace, master_spec=None):
Ivan Bogatyy's avatar
Ivan Bogatyy committed
197
198
199
200
    """Returns a JS script HTML fragment, which will populate the container.

    Args:
      trace: binary-encoded MasterTrace string.
Ivan Bogatyy's avatar
Ivan Bogatyy committed
201
202
      master_spec: Master spec proto (parsed), which can improve the layout. May
        be required in future versions.
Ivan Bogatyy's avatar
Ivan Bogatyy committed
203
204
205
206
207
208
209
210

    Returns:
      unicode with HTML contents.
    """
    html = """
    <meta charset="utf-8"/>
    <script type='text/javascript'>
    document.getElementById("{elt_id}").innerHTML = "";  // Clear previous.
Ivan Bogatyy's avatar
Ivan Bogatyy committed
211
    visualizeToDiv({json}, "{elt_id}", {master_spec_json});
Ivan Bogatyy's avatar
Ivan Bogatyy committed
212
213
    </script>
    """.format(
Ivan Bogatyy's avatar
Ivan Bogatyy committed
214
215
216
        json=parse_trace_json(trace),
        master_spec_json=_optional_master_spec_json(master_spec),
        elt_id=self.elt_id)
Ivan Bogatyy's avatar
Ivan Bogatyy committed
217
    return unicode(html, 'utf-8')  # IPython expects unicode.