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