#!/usr/bin/env python # coding: utf-8 # # Using Javascript in IPython Notebooks # # # Updated: March 11, 2016 # # These are my notes on using Javascript inside of the iPython Web Notebook. # # ## Getting Started # # The easiest way to access Javascript is with the `%%javascript` cell magic: # In[1]: get_ipython().run_cell_magic('javascript', '', 'console.log("Hello World!")\n') # (To view the Javascript console in Chrome & Firefox, use CONTROL+SHIFT+J). # ## Accessing the Cell Output Area # # The `element` Javascript variable refers to the current cell. For example: # In[2]: get_ipython().run_cell_magic('javascript', '', 'element.append("Hello World!");\n') # (NOTE: prior to version 2.0, there was a `container` variable, but the current docs refer to an `element` variable. For more information, see the source: [outputarea.js](https://github.com/ipython/ipython/blob/f1ad8aa9db9ab3ebcaeafb7c4d1bfc7521c65d55/IPython/html/static/notebook/js/outputarea.js)). # # You can also use the [`Javascript()`](http://ipython.org/ipython-doc/2/api/generated/IPython.core.display.html#IPython.core.display.Javascript) object, creates an object that will be rendered as Javascript by the IPython display system: # In[3]: from IPython.display import Javascript Javascript("element.append('Hello World, Again!');") # A lower-level approach is to implement a `_repr_javascript_()` object method that returns Javascript. This lets you add Javascript rendering to any object: # In[4]: class HelloWorld(): def _repr_javascript_(self): return "element.append('HelloWorld class');" hw = HelloWorld() hw # ## Global Variables # # It's sometimes handy to define a global variable or function that can be used in later Javascript cells. Attributes added to the `windows` object are available as global variables: # In[5]: get_ipython().run_cell_magic('javascript', '', 'window.foovar = 42;\n') # In[6]: get_ipython().run_cell_magic('javascript', '', 'element.append(foovar);\n') # ## Loading External Javascript Libraries # # You often need to load other Javascript libraries before your code is run. The `lib` argument to the `Javascript()` display object specifies required libraries: # In[7]: libs = ["https://cdnjs.cloudflare.com/ajax/libs/three.js/r74/three.js"] Javascript("""element.append(THREE.REVISION);""", lib=libs) # NOTE: __libraries are reloaded each time the cell is run__. The underlying implementation, as of IPython 2.1, uses jQuery's [getScript()](http://api.jquery.com/jquery.getscript/) method to load each library in the order specified. # # This behavior will break any running event loops (such as an animate event used with THREE.js), because the old event loop will be running with objects defined from the first library load, and the variables will get overwritten with new types based on the second library load. # # For example, in this THREE.js example, the `animate()` function is called 60 times/second to render the scene and rotate the cube. When the cell is reloaded, the call to `render.render(scene, camera)` will fail: # In[8]: libs = ["https://cdnjs.cloudflare.com/ajax/libs/three.js/r74/three.js"] Javascript(""" var renderer = new THREE.WebGLRenderer({ antialias: true }); var canvas = renderer.domElement; element.append(canvas); var camera = new THREE.PerspectiveCamera(70, canvas.width / canvas.height, 1, 1000); camera.position.z = 400; var scene = new THREE.Scene(); var material = new THREE.MeshDepthMaterial(); var mesh = new THREE.Mesh(new THREE.CubeGeometry(200, 200, 200), material); scene.add(mesh); function animate() { renderer.render(scene, camera); mesh.rotation.x += 0.005; mesh.rotation.y += 0.01; requestAnimationFrame(animate); } animate(); """, lib=libs) # Because of this, the animation event loop will stop when the library is reloaded. For example, executing the following cell will cause the rotating cube to stop: # In[9]: libs = ["https://cdnjs.cloudflare.com/ajax/libs/three.js/r74/three.js"] Javascript("""console.log(THREE);""", lib=libs) # A better approach is to use [require.js](https://requirejs.org/docs/api.html) to load external modules, which is supported starting in IPython 2.0. First, define the paths to the different modules: # In[10]: get_ipython().run_cell_magic('javascript', '', 'require.config({\n paths: {\n \'three\': "https://cdnjs.cloudflare.com/ajax/libs/three.js/r74/three",\n }\n});\n') # Now, use the `require()` function to wrap your code, specifying the libraries you need: # In[11]: get_ipython().run_cell_magic('javascript', '', "require(['three'], function() {\n element.append(THREE.REVISION);\n});\n") # Using the previous example: # In[12]: get_ipython().run_cell_magic('javascript', '', 'var renderer = new THREE.WebGLRenderer({ antialias: true });\nvar canvas = renderer.domElement;\nelement.append(canvas);\nvar camera = new THREE.PerspectiveCamera(70, canvas.width / canvas.height, 1, 1000);\ncamera.position.z = 400;\nvar scene = new THREE.Scene();\nvar material = new THREE.MeshDepthMaterial();\nvar mesh = new THREE.Mesh(new THREE.CubeGeometry(200, 200, 200), material);\nscene.add(mesh);\nfunction animate() {\n renderer.render(scene, camera);\n mesh.rotation.x += 0.005;\n mesh.rotation.y += 0.01;\n requestAnimationFrame(animate);\n}\nanimate();\n') # And now reloading doesn't break the animation event loop: # In[13]: get_ipython().run_cell_magic('javascript', '', "require(['three'], function() {\n element.append(THREE.REVISION);\n});\n") # ## Generating Javascript from Python # # You often want to generate Javascript from Python. # # Here's a hack that takes a fragment of Javascript, finds all the functions that are declared, and generates bound Python methods for each function: # In[14]: import types from functools import partial import json class JSCode(object): def __init__(self, code): self.code = code self.invokes = "" # Find all the function name( lines in the Javasript code for line in code.split("\n"): line = line.strip().split() if line and line[0] == 'function': funcname = line[1].split("(")[0] # For each create a bound method to add to the list of invocations def invoke(self, *args): argstr = ','.join([json.dumps(arg) for arg in args]) self.invokes += "%s(%s);\n" % (funcname, argstr) setattr(self, funcname, types.MethodType(invoke, self)) def _repr_javascript_(self): """Return the code definitions and all invocations""" return self.code + self.invokes # For example: # In[15]: code = JSCode(""" function test() { console.log("test!"); } function output(string) { element.append(string); } """) # Now, the `code` object has `test()` and `output()` methods corresponding to Javascript methods: # In[16]: code.test, code.output # When these methods are invoked, corresponding Javascript calls are added to the `code` object: # In[17]: code.output("foo") code.output(" and bar") code # Here is the corresponding Javascript fragment that was generated: # In[18]: print code._repr_javascript_() # ## References # # - Javascript code for the Notebok "output area": [outputarea.js](https://github.com/ipython/ipython/blob/f1ad8aa9db9ab3ebcaeafb7c4d1bfc7521c65d55/IPython/html/static/notebook/js/outputarea.js) # # - Relevant pull request and issue: https://github.com/ipython/ipython/pull/4646 # # - [IPython Notebook display system](http://nbviewer.ipython.org/github/ipython/ipython/blob/2.x/examples/Notebook/Display%20System.ipynb) # # - [Implementing custom display logic](http://nbviewer.ipython.org/github/ipython/ipython/blob/2.x/examples/Notebook/Custom%20Display%20Logic.ipynb) # In[ ]: