Note
Go to the endto download the full example code.
Embedding WebAgg#
This example demonstrates how to embed Matplotlib WebAgg interactive plottingin your own web application and framework. It is not necessary to do all thisif you merely want to display a plot in a browser or use Matplotlib's built-inTornado-based server "on the side".
The framework being used must support web sockets.
importargparseimportioimportjsonimportmimetypesfrompathlibimportPathimportsignalimportsockettry:importtornadoexceptImportErroraserr:raiseRuntimeError("This example requires tornado.")fromerrimporttornado.httpserverimporttornado.ioloopimporttornado.webimporttornado.websocketimportnumpyasnpimportmatplotlibasmplfrommatplotlib.backends.backend_webaggimport(FigureManagerWebAgg,new_figure_manager_given_figure)frommatplotlib.figureimportFiguredefcreate_figure():""" Creates a simple example figure. """fig=Figure()ax=fig.add_subplot()t=np.arange(0.0,3.0,0.01)s=np.sin(2*np.pi*t)ax.plot(t,s)returnfig# The following is the content of the web page. You would normally# generate this using some sort of template facility in your web# framework, but here we just use Python string formatting.html_content="""<!DOCTYPE html><html lang="en"> <head> <!-- TODO: There should be a way to include all of the required javascript and CSS so matplotlib can add to the set in the future if it needs to. --> <link rel="stylesheet" href="_static/css/page.css" type="text/css"> <link rel="stylesheet" href="_static/css/boilerplate.css" type="text/css"> <link rel="stylesheet" href="_static/css/fbm.css" type="text/css"> <link rel="stylesheet" href="_static/css/mpl.css" type="text/css"> <script src="mpl.js"></script> <script> /* This is a callback that is called when the user saves (downloads) a file. Its purpose is really to map from a figure and file format to a url in the application. */ function ondownload(figure, format) { window.open('download.' + format, '_blank'); }; function ready(fn) { if (document.readyState != "loading") { fn(); } else { document.addEventListener("DOMContentLoaded", fn); } } ready( function() { /* It is up to the application to provide a websocket that the figure will use to communicate to the server. This websocket object can also be a "fake" websocket that underneath multiplexes messages from multiple figures, if necessary. */ var websocket_type = mpl.get_websocket_type(); var websocket = new websocket_type("%(ws_uri)sws"); // mpl.figure creates a new figure on the webpage. var fig = new mpl.figure( // A unique numeric identifier for the figure%(fig_id)s, // A websocket object (or something that behaves like one) websocket, // A function called when a file type is selected for download ondownload, // The HTML element in which to place the figure document.getElementById("figure")); } ); </script> <title>matplotlib</title> </head> <body> <div id="figure"> </div> </body></html>"""classMyApplication(tornado.web.Application):classMainPage(tornado.web.RequestHandler):""" Serves the main HTML page. """defget(self):manager=self.application.managerws_uri=f"ws://{self.request.host}/"content=html_content%{"ws_uri":ws_uri,"fig_id":manager.num}self.write(content)classMplJs(tornado.web.RequestHandler):""" Serves the generated matplotlib javascript file. The content is dynamically generated based on which toolbar functions the user has defined. Call `FigureManagerWebAgg` to get its content. """defget(self):self.set_header('Content-Type','application/javascript')js_content=FigureManagerWebAgg.get_javascript()self.write(js_content)classDownload(tornado.web.RequestHandler):""" Handles downloading of the figure in various file formats. """defget(self,fmt):manager=self.application.managerself.set_header('Content-Type',mimetypes.types_map.get(fmt,'binary'))buff=io.BytesIO()manager.canvas.figure.savefig(buff,format=fmt)self.write(buff.getvalue())classWebSocket(tornado.websocket.WebSocketHandler):""" A websocket for interactive communication between the plot in the browser and the server. In addition to the methods required by tornado, it is required to have two callback methods: - ``send_json(json_content)`` is called by matplotlib when it needs to send json to the browser. `json_content` is a JSON tree (Python dictionary), and it is the responsibility of this implementation to encode it as a string to send over the socket. - ``send_binary(blob)`` is called to send binary image data to the browser. """supports_binary=Truedefopen(self):# Register the websocket with the FigureManager.manager=self.application.managermanager.add_web_socket(self)ifhasattr(self,'set_nodelay'):self.set_nodelay(True)defon_close(self):# When the socket is closed, deregister the websocket with# the FigureManager.manager=self.application.managermanager.remove_web_socket(self)defon_message(self,message):# The 'supports_binary' message is relevant to the# websocket itself. The other messages get passed along# to matplotlib as-is.# Every message has a "type" and a "figure_id".message=json.loads(message)ifmessage['type']=='supports_binary':self.supports_binary=message['value']else:manager=self.application.managermanager.handle_json(message)defsend_json(self,content):self.write_message(json.dumps(content))defsend_binary(self,blob):ifself.supports_binary:self.write_message(blob,binary=True)else:data_uri=("data:image/png;base64,"+blob.encode('base64').replace('\n',''))self.write_message(data_uri)def__init__(self,figure):self.figure=figureself.manager=new_figure_manager_given_figure(id(figure),figure)super().__init__([# Static files for the CSS and JS(r'/_static/(.*)',tornado.web.StaticFileHandler,{'path':FigureManagerWebAgg.get_static_file_path()}),# Static images for the toolbar(r'/_images/(.*)',tornado.web.StaticFileHandler,{'path':Path(mpl.get_data_path(),'images')}),# The page that contains all of the pieces('/',self.MainPage),('/mpl.js',self.MplJs),# Sends images and events to the browser, and receives# events from the browser('/ws',self.WebSocket),# Handles the downloading (i.e., saving) of static images(r'/download.([a-z0-9.]+)',self.Download),])if__name__=="__main__":parser=argparse.ArgumentParser()parser.add_argument('-p','--port',type=int,default=8080,help='Port to listen on (0 for a random port).')args=parser.parse_args()figure=create_figure()application=MyApplication(figure)http_server=tornado.httpserver.HTTPServer(application)sockets=tornado.netutil.bind_sockets(args.port,'')http_server.add_sockets(sockets)forsinsockets:addr,port=s.getsockname()[:2]ifs.familyissocket.AF_INET6:addr=f'[{addr}]'print(f"Listening on http://{addr}:{port}/")print("Press Ctrl+C to quit")ioloop=tornado.ioloop.IOLoop.instance()defshutdown():ioloop.stop()print("Server stopped")old_handler=signal.signal(signal.SIGINT,lambdasig,frame:ioloop.add_callback_from_signal(shutdown))try:ioloop.start()finally:signal.signal(signal.SIGINT,old_handler)