1"""
2================
3Embedding WebAgg
4================
5
6This example demonstrates how to embed matplotlib WebAgg interactive
7plotting in your own web application and framework.  It is not
8necessary to do all this if you merely want to display a plot in a
9browser or use matplotlib's built-in Tornado-based server "on the
10side".
11
12The framework being used must support web sockets.
13"""
14
15import io
16
17try:
18    import tornado
19except ImportError:
20    raise RuntimeError("This example requires tornado.")
21import tornado.web
22import tornado.httpserver
23import tornado.ioloop
24import tornado.websocket
25
26
27from matplotlib.backends.backend_webagg_core import (
28    FigureManagerWebAgg, new_figure_manager_given_figure)
29from matplotlib.figure import Figure
30
31import numpy as np
32
33import json
34
35
36def create_figure():
37    """
38    Creates a simple example figure.
39    """
40    fig = Figure()
41    a = fig.add_subplot(111)
42    t = np.arange(0.0, 3.0, 0.01)
43    s = np.sin(2 * np.pi * t)
44    a.plot(t, s)
45    return fig
46
47
48# The following is the content of the web page.  You would normally
49# generate this using some sort of template facility in your web
50# framework, but here we just use Python string formatting.
51html_content = """
52<html>
53  <head>
54    <!-- TODO: There should be a way to include all of the required javascript
55               and CSS so matplotlib can add to the set in the future if it
56               needs to. -->
57    <link rel="stylesheet" href="_static/css/page.css" type="text/css">
58    <link rel="stylesheet" href="_static/css/boilerplate.css" type="text/css" />
59    <link rel="stylesheet" href="_static/css/fbm.css" type="text/css" />
60    <link rel="stylesheet" href="_static/jquery-ui-1.12.1/jquery-ui.min.css" >
61    <script src="_static/jquery-ui-1.12.1/external/jquery/jquery.js"></script>
62    <script src="_static/jquery-ui-1.12.1/jquery-ui.min.js"></script>
63    <script src="mpl.js"></script>
64
65    <script>
66      /* This is a callback that is called when the user saves
67         (downloads) a file.  Its purpose is really to map from a
68         figure and file format to a url in the application. */
69      function ondownload(figure, format) {
70        window.open('download.' + format, '_blank');
71      };
72
73      $(document).ready(
74        function() {
75          /* It is up to the application to provide a websocket that the figure
76             will use to communicate to the server.  This websocket object can
77             also be a "fake" websocket that underneath multiplexes messages
78             from multiple figures, if necessary. */
79          var websocket_type = mpl.get_websocket_type();
80          var websocket = new websocket_type("%(ws_uri)sws");
81
82          // mpl.figure creates a new figure on the webpage.
83          var fig = new mpl.figure(
84              // A unique numeric identifier for the figure
85              %(fig_id)s,
86              // A websocket object (or something that behaves like one)
87              websocket,
88              // A function called when a file type is selected for download
89              ondownload,
90              // The HTML element in which to place the figure
91              $('div#figure'));
92        }
93      );
94    </script>
95
96    <title>matplotlib</title>
97  </head>
98
99  <body>
100    <div id="figure">
101    </div>
102  </body>
103</html>
104"""
105
106
107class MyApplication(tornado.web.Application):
108    class MainPage(tornado.web.RequestHandler):
109        """
110        Serves the main HTML page.
111        """
112
113        def get(self):
114            manager = self.application.manager
115            ws_uri = "ws://{req.host}/".format(req=self.request)
116            content = html_content % {
117                "ws_uri": ws_uri, "fig_id": manager.num}
118            self.write(content)
119
120    class MplJs(tornado.web.RequestHandler):
121        """
122        Serves the generated matplotlib javascript file.  The content
123        is dynamically generated based on which toolbar functions the
124        user has defined.  Call `FigureManagerWebAgg` to get its
125        content.
126        """
127
128        def get(self):
129            self.set_header('Content-Type', 'application/javascript')
130            js_content = FigureManagerWebAgg.get_javascript()
131
132            self.write(js_content)
133
134    class Download(tornado.web.RequestHandler):
135        """
136        Handles downloading of the figure in various file formats.
137        """
138
139        def get(self, fmt):
140            manager = self.application.manager
141
142            mimetypes = {
143                'ps': 'application/postscript',
144                'eps': 'application/postscript',
145                'pdf': 'application/pdf',
146                'svg': 'image/svg+xml',
147                'png': 'image/png',
148                'jpeg': 'image/jpeg',
149                'tif': 'image/tiff',
150                'emf': 'application/emf'
151            }
152
153            self.set_header('Content-Type', mimetypes.get(fmt, 'binary'))
154
155            buff = io.BytesIO()
156            manager.canvas.figure.savefig(buff, format=fmt)
157            self.write(buff.getvalue())
158
159    class WebSocket(tornado.websocket.WebSocketHandler):
160        """
161        A websocket for interactive communication between the plot in
162        the browser and the server.
163
164        In addition to the methods required by tornado, it is required to
165        have two callback methods:
166
167            - ``send_json(json_content)`` is called by matplotlib when
168              it needs to send json to the browser.  `json_content` is
169              a JSON tree (Python dictionary), and it is the responsibility
170              of this implementation to encode it as a string to send over
171              the socket.
172
173            - ``send_binary(blob)`` is called to send binary image data
174              to the browser.
175        """
176        supports_binary = True
177
178        def open(self):
179            # Register the websocket with the FigureManager.
180            manager = self.application.manager
181            manager.add_web_socket(self)
182            if hasattr(self, 'set_nodelay'):
183                self.set_nodelay(True)
184
185        def on_close(self):
186            # When the socket is closed, deregister the websocket with
187            # the FigureManager.
188            manager = self.application.manager
189            manager.remove_web_socket(self)
190
191        def on_message(self, message):
192            # The 'supports_binary' message is relevant to the
193            # websocket itself.  The other messages get passed along
194            # to matplotlib as-is.
195
196            # Every message has a "type" and a "figure_id".
197            message = json.loads(message)
198            if message['type'] == 'supports_binary':
199                self.supports_binary = message['value']
200            else:
201                manager = self.application.manager
202                manager.handle_json(message)
203
204        def send_json(self, content):
205            self.write_message(json.dumps(content))
206
207        def send_binary(self, blob):
208            if self.supports_binary:
209                self.write_message(blob, binary=True)
210            else:
211                data_uri = "data:image/png;base64,{0}".format(
212                    blob.encode('base64').replace('\n', ''))
213                self.write_message(data_uri)
214
215    def __init__(self, figure):
216        self.figure = figure
217        self.manager = new_figure_manager_given_figure(
218            id(figure), figure)
219
220        super(MyApplication, self).__init__([
221            # Static files for the CSS and JS
222            (r'/_static/(.*)',
223             tornado.web.StaticFileHandler,
224             {'path': FigureManagerWebAgg.get_static_file_path()}),
225
226            # The page that contains all of the pieces
227            ('/', self.MainPage),
228
229            ('/mpl.js', self.MplJs),
230
231            # Sends images and events to the browser, and receives
232            # events from the browser
233            ('/ws', self.WebSocket),
234
235            # Handles the downloading (i.e., saving) of static images
236            (r'/download.([a-z0-9.]+)', self.Download),
237        ])
238
239
240if __name__ == "__main__":
241    figure = create_figure()
242    application = MyApplication(figure)
243
244    http_server = tornado.httpserver.HTTPServer(application)
245    http_server.listen(8080)
246
247    print("http://127.0.0.1:8080/")
248    print("Press Ctrl+C to quit")
249
250    tornado.ioloop.IOLoop.instance().start()
251