class CommSocket(object): """ 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 = False def __init__(self, manager): self.manager = manager self.uuid = uuid() #display(HTML("<div id='%s'></div>"%self.uuid)) self.comm = Comm('matplotlib', data={'id': self.uuid}) def open(self): # Register the websocket with the FigureManager. self.manager.add_web_socket(self) self.comm.on_msg(self.on_message) def on_close(self): # When the socket is closed, deregister the websocket with # the FigureManager. self.manager.remove_web_socket(self) self.comm.close() def send_json(self, content): self.comm.send({'data': json.dumps(content)}) def send_binary(self, blob): data_uri = "data:image/png;base64,{0}".format(b64encode(blob)) self.comm.send({'data': data_uri}) def on_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['content']['data']) if message['type'] == 'supports_binary': self.supports_binary = message['value'] else: self.manager.handle_json(message)
class InteractiveGraphics(object): def __init__(self, g, events=None, renderer="sage"): self._g = g if events is None: events = {} self._events = events self.renderer=renderer def figure(self, **kwds): if isinstance(self._g, matplotlib.figure.Figure): return self._g options = dict() options.update(self._g.SHOW_OPTIONS) options.update(self._g._extra_kwds) options.update(kwds) options.pop('dpi'); options.pop('transparent'); options.pop('fig_tight') fig = self._g.matplotlib(**options) return fig def save(self, filename, **kwds): if isinstance(self._g, matplotlib.figure.Figure): self._g.savefig(filename) else: # When fig_tight=True (the default), the margins are very slightly different. # I don't know how to properly account for this yet (or even if it is possible), # since it only happens at figsize time -- do "a=plot(sin); a.save??". # So for interactive graphics, we just set this to false no matter what. kwds['fig_tight'] = False self._g.save(filename, **kwds) def show(self, **kwds): STORED_INTERACTIVE_GRAPHICS.append(self); if self.renderer=="sage": return self.show_sage(**kwds) elif self.renderer=="matplotlib": return self.show_matplotlib(**kwds) def show_matplotlib(self, **kwds): self.fig = self.figure(**kwds) CommFigure(self.fig) for k,v in self._events.items(): self.fig.canvas.mpl_connect(k,v) def show_sage(self, **kwds): fig = self.figure(**kwds) from matplotlib.backends.backend_agg import FigureCanvasAgg canvas = FigureCanvasAgg(fig) fig.set_canvas(canvas) fig.tight_layout() # critical, since sage does this -- if not, coords all wrong ax = fig.axes[0] # upper left data coordinates xmin, ymax = ax.transData.inverted().transform( fig.transFigure.transform((0,1)) ) # lower right data coordinates xmax, ymin = ax.transData.inverted().transform( fig.transFigure.transform((1,0)) ) def to_data_coords(p): # 0<=x,y<=1 return ((xmax-xmin)*p[0] + xmin, (ymax-ymin)*(1-p[1]) + ymin) def on_msg(msg): data = msg['content']['data'] x,y = data['x'], data['y'] eventType = data['eventType'] if eventType in self._events: self._events[eventType](to_data_coords([x,y])) file_id = uuid() if kwds.get('svg',False): filename = '%s.svg'%file_id del kwds['svg'] else: filename = '%s.png'%file_id fig.savefig(filename) from comm import SageCellComm as Comm self.comm = Comm(data={"filename": filename}, target_name='graphicswidget') import sys sys._sage_.sent_files[filename] = os.path.getmtime(filename) self.comm.on_msg(on_msg)
class ThreeJS(object): def __init__(self, renderer=None, width=None, height=None, frame=True, camera_distance=10.0, background=None, foreground=None, **ignored): """ INPUT: - renderer -- None (automatic), 'canvas2d', or 'webgl' - width -- None (automatic) or an integer - height -- None (automatic) or an integer - frame -- bool (default: True); draw a frame that includes every object. - camera_distance -- float (default: 10); default camera distance. - background -- None (transparent); otherwise a color such as 'black' or 'white' - foreground -- None (automatic = black if transparent; otherwise opposite of background); or a color; this is used for drawing the frame and axes labels. """ self.id = uuid() self.comm = Comm(data={'renderer':renderer, 'width':noneint(width), 'height':noneint(height), 'camera_distance':float(camera_distance), 'background':background, 'foreground':foreground }, target_name='threejs') self.comm.on_msg(self.on_msg) self._graphics = [] def on_msg(self, msg): data = msg['content']['data'] x,y = data['x'], data['y'] print (x,y) def send(self, msg_type, data): d = {'msg_type': msg_type} d.update(data) self.comm.send(d) def lights(self, lights): self.send('lights', {'lights': [l.scenetree_json() for l in lights]}) def add(self, graphics3d, **kwds): kwds = graphics3d._process_viewing_options(kwds) self._frame = kwds.get('frame',False) self._graphics.append(graphics3d) obj = graphics3d_to_jsonable(graphics3d) self.send('add', {'obj': obj, 'wireframe':jsonable(kwds.get('wireframe'))}) self.set_frame(draw = self._frame) # update the frame def render_scene(self, force=True): self.send('render', {'force':force}) def add_text(self, pos, text, fontsize=18, fontface='Arial', sprite_alignment='topLeft'): self.send('add_text', obj={'pos':[float(pos[0]), float(pos[1]), float(pos[2])], 'text':str(text), 'fontsize':int(fontsize),'fontface':str(fontface), 'sprite_alignment':str(sprite_alignment)}) def animate(self, fps=None, stop=None, mouseover=True): self.send('animate', {'fps':noneint(fps), 'stop':stop, 'mouseover':mouseover}) def set_frame(self, xmin=None, xmax=None, ymin=None, ymax=None, zmin=None, zmax=None, color=None, draw=True): if not self._graphics: xmin, xmax, ymin, ymax, zmin, zmax = -1,1,-1,1,-1,1 else: b = self._graphics[0].bounding_box() xmin, xmax, ymin, ymax, zmin, zmax = b[0][0], b[1][0], b[0][1], b[1][1], b[0][2], b[1][2] for g in self._graphics[1:]: b = g.bounding_box() xmin, xmax, ymin, ymax, zmin, zmax = ( min(xmin,b[0][0]), max(b[1][0],xmax), min(b[0][1],ymin), max(b[1][1],ymax), min(b[0][2],zmin), max(b[1][2],zmax)) self.send('set_frame', { 'xmin':float(xmin), 'xmax':float(xmax), 'ymin':float(ymin), 'ymax':float(ymax), 'zmin':float(zmin), 'zmax':float(zmax), 'color':color, 'draw':draw})
class ThreeJS(object): def __init__(self, renderer=None, width=None, height=None, frame=True, camera_distance=10.0, background=None, foreground=None, **ignored): """ INPUT: - renderer -- None (automatic), 'canvas2d', or 'webgl' - width -- None (automatic) or an integer - height -- None (automatic) or an integer - frame -- bool (default: True); draw a frame that includes every object. - camera_distance -- float (default: 10); default camera distance. - background -- None (transparent); otherwise a color such as 'black' or 'white' - foreground -- None (automatic = black if transparent; otherwise opposite of background); or a color; this is used for drawing the frame and axes labels. """ self.id = uuid() self.comm = Comm(data={ 'renderer': renderer, 'width': noneint(width), 'height': noneint(height), 'camera_distance': float(camera_distance), 'background': background, 'foreground': foreground }, target_name='threejs') self.comm.on_msg(self.on_msg) self._graphics = [] def on_msg(self, msg): data = msg['content']['data'] x, y = data['x'], data['y'] print(x, y) def send(self, msg_type, data): d = {'msg_type': msg_type} d.update(data) self.comm.send(d) def lights(self, lights): self.send('lights', {'lights': [l.scenetree_json() for l in lights]}) def add(self, graphics3d, **kwds): kwds = graphics3d._process_viewing_options(kwds) self._frame = kwds.get('frame', False) self._graphics.append(graphics3d) obj = graphics3d_to_jsonable(graphics3d) self.send('add', { 'obj': obj, 'wireframe': jsonable(kwds.get('wireframe')) }) self.set_frame(draw=self._frame) # update the frame def render_scene(self, force=True): self.send('render', {'force': force}) def add_text(self, pos, text, fontsize=18, fontface='Arial', sprite_alignment='topLeft'): self.send('add_text', obj={ 'pos': [float(pos[0]), float(pos[1]), float(pos[2])], 'text': str(text), 'fontsize': int(fontsize), 'fontface': str(fontface), 'sprite_alignment': str(sprite_alignment) }) def animate(self, fps=None, stop=None, mouseover=True): self.send('animate', { 'fps': noneint(fps), 'stop': stop, 'mouseover': mouseover }) def set_frame(self, xmin=None, xmax=None, ymin=None, ymax=None, zmin=None, zmax=None, color=None, draw=True): if not self._graphics: xmin, xmax, ymin, ymax, zmin, zmax = -1, 1, -1, 1, -1, 1 else: b = self._graphics[0].bounding_box() xmin, xmax, ymin, ymax, zmin, zmax = b[0][0], b[1][0], b[0][1], b[ 1][1], b[0][2], b[1][2] for g in self._graphics[1:]: b = g.bounding_box() xmin, xmax, ymin, ymax, zmin, zmax = (min(xmin, b[0][0]), max(b[1][0], xmax), min(b[0][1], ymin), max(b[1][1], ymax), min(b[0][2], zmin), max(b[1][2], zmax)) self.send( 'set_frame', { 'xmin': float(xmin), 'xmax': float(xmax), 'ymin': float(ymin), 'ymax': float(ymax), 'zmin': float(zmin), 'zmax': float(zmax), 'color': color, 'draw': draw })
class InteractiveGraphics(object): def __init__(self, g, events=None, renderer="sage"): self._g = g if events is None: events = {} self._events = events self.renderer = renderer def figure(self, **kwds): if isinstance(self._g, matplotlib.figure.Figure): return self._g options = dict() options.update(self._g.SHOW_OPTIONS) options.update(self._g._extra_kwds) options.update(kwds) options.pop('dpi') options.pop('transparent') options.pop('fig_tight') fig = self._g.matplotlib(**options) return fig def save(self, filename, **kwds): if isinstance(self._g, matplotlib.figure.Figure): self._g.savefig(filename) else: # When fig_tight=True (the default), the margins are very slightly different. # I don't know how to properly account for this yet (or even if it is possible), # since it only happens at figsize time -- do "a=plot(sin); a.save??". # So for interactive graphics, we just set this to false no matter what. kwds['fig_tight'] = False self._g.save(filename, **kwds) def show(self, **kwds): STORED_INTERACTIVE_GRAPHICS.append(self) if self.renderer == "sage": return self.show_sage(**kwds) elif self.renderer == "matplotlib": return self.show_matplotlib(**kwds) def show_matplotlib(self, **kwds): self.fig = self.figure(**kwds) CommFigure(self.fig) for k, v in self._events.items(): self.fig.canvas.mpl_connect(k, v) def show_sage(self, **kwds): fig = self.figure(**kwds) from matplotlib.backends.backend_agg import FigureCanvasAgg canvas = FigureCanvasAgg(fig) fig.set_canvas(canvas) fig.tight_layout( ) # critical, since sage does this -- if not, coords all wrong ax = fig.axes[0] # upper left data coordinates xmin, ymax = ax.transData.inverted().transform( fig.transFigure.transform((0, 1))) # lower right data coordinates xmax, ymin = ax.transData.inverted().transform( fig.transFigure.transform((1, 0))) def to_data_coords(p): # 0<=x,y<=1 return ((xmax - xmin) * p[0] + xmin, (ymax - ymin) * (1 - p[1]) + ymin) def on_msg(msg): data = msg['content']['data'] x, y = data['x'], data['y'] eventType = data['eventType'] if eventType in self._events: self._events[eventType](to_data_coords([x, y])) file_id = uuid() if kwds.get('svg', False): filename = '%s.svg' % file_id del kwds['svg'] else: filename = '%s.png' % file_id fig.savefig(filename) from comm import SageCellComm as Comm self.comm = Comm(data={"filename": filename}, target_name='graphicswidget') import sys sys._sage_.sent_files[filename] = os.path.getmtime(filename) self.comm.on_msg(on_msg)