├── notebookjs ├── __init__.py ├── resources │ ├── template.html │ └── CommAPI.js ├── _comm.py └── _display.py ├── Images ├── notebookJS.ai ├── notebookJS.png └── example_radial_bar.png ├── MANIFEST.in ├── Examples ├── 3_RadialBarChart │ ├── energy.csv │ ├── radial_bar.css │ ├── RadialBarChart.ipynb │ └── radial_bar_lib.js ├── 2_SimpleD3_Circle │ ├── draw_circle_lib.js │ └── SimpleD3_Circle.ipynb ├── 5_Webpack_BaseballAnnotator_Bidirectional │ ├── BaseballVisualizer │ │ ├── js │ │ │ ├── index.js │ │ │ ├── DraggableTimeline.js │ │ │ ├── field.svg │ │ │ ├── helpers.js │ │ │ ├── PlayDiagram.js │ │ │ └── TrajectoryAnnotator.js │ │ ├── webpack.config.js │ │ ├── play_annotated.csv │ │ └── package.json │ └── BaseballAnnotator.ipynb ├── 4_Bidirectional_Comm │ ├── bar_chart_lib.js │ └── Bar_Chart_Bidirectional_Comm.ipynb ├── 1_HelloWorld │ └── HelloWorld.ipynb ├── 7_D3_scatterplot │ ├── scatterplot_lib.js │ ├── D3_Scatter.ipynb │ └── Prices.csv └── 6_HelloWorld_Bidirectional │ └── HelloWorld_Bidirectional.ipynb ├── .gitignore ├── setup.py ├── LICENSE └── README.md /notebookjs/__init__.py: -------------------------------------------------------------------------------- 1 | from ._display import execute_js, save_html -------------------------------------------------------------------------------- /Images/notebookJS.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorgehpo/notebookJS/HEAD/Images/notebookJS.ai -------------------------------------------------------------------------------- /Images/notebookJS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorgehpo/notebookJS/HEAD/Images/notebookJS.png -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include notebookjs/resources/CommAPI.js 2 | include notebookjs/resources/template.html -------------------------------------------------------------------------------- /Examples/3_RadialBarChart/energy.csv: -------------------------------------------------------------------------------- 1 | name,value 2 | Jan,432 3 | Feb,340 4 | Mar,382 5 | Apr,398 6 | May,410 -------------------------------------------------------------------------------- /Images/example_radial_bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorgehpo/notebookJS/HEAD/Images/example_radial_bar.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg-info 2 | .DS_Store 3 | __pycache__ 4 | build 5 | dist 6 | node_modules 7 | .ipynb_checkpoints 8 | -------------------------------------------------------------------------------- /Examples/2_SimpleD3_Circle/draw_circle_lib.js: -------------------------------------------------------------------------------- 1 | function draw_circle(id, data){ 2 | d3.select(id) 3 | .append("div") 4 | .style("width", "50px") 5 | .style("height", "50px") 6 | .style("background-color", data.color) 7 | .style("border-radius", "50px") 8 | } -------------------------------------------------------------------------------- /notebookjs/resources/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 |
9 |
10 | 16 | 17 | -------------------------------------------------------------------------------- /Examples/5_Webpack_BaseballAnnotator_Bidirectional/BaseballVisualizer/js/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import { select } from "d3-selection"; 4 | import TrajectoryAnnotator from "./TrajectoryAnnotator"; 5 | 6 | export function renderBaseballAnnotator(divName, data){ 7 | ReactDOM.render( 8 | 9 | , select(divName).node()); 10 | } 11 | -------------------------------------------------------------------------------- /Examples/3_RadialBarChart/radial_bar.css: -------------------------------------------------------------------------------- 1 | body { 2 | font: 12px sans-serif; 3 | } 4 | 5 | svg { 6 | margin: 0px auto; 7 | display: block; 8 | } 9 | 10 | path.arc { 11 | opacity: 0.9; 12 | transition: opacity 0.5s; 13 | } 14 | 15 | path.arc:hover { 16 | opacity: 0.7; 17 | } 18 | 19 | .axis line, .axis circle { 20 | stroke: #cccccc; 21 | stroke-width: 1px 22 | } 23 | 24 | .axis circle { 25 | fill: none; 26 | } 27 | 28 | .r.axis text { 29 | text-anchor: end 30 | } 31 | 32 | .tooltip { 33 | position: absolute; 34 | display: none; 35 | background: rgba(0, 0, 0, 0.6); 36 | border-radius: 3px; 37 | box-shadow: -3px 3px 15px #888; 38 | color: white; 39 | padding: 6px; 40 | } -------------------------------------------------------------------------------- /Examples/4_Bidirectional_Comm/bar_chart_lib.js: -------------------------------------------------------------------------------- 1 | function bar_chart(div_id, data_dict){ 2 | const data = data_dict.array; 3 | const div = d3.select(div_id) 4 | const height = data.length * 20; 5 | 6 | const scaleY = d3.scale.ordinal() 7 | .domain(d3.range(data.length)) 8 | .rangeRoundBands([0, height], .1); 9 | const scaleX = d3.scale.linear() 10 | .domain(d3.extent(data)) 11 | .range([1, 120]); 12 | 13 | const svg = div.append("svg") 14 | .style("width", "120px") 15 | .style("height", height+"px"); 16 | 17 | svg.selectAll("rect") 18 | .data(data) 19 | .enter() 20 | .append("rect") 21 | .attr("x", 0) 22 | .attr("y", (d, i) => scaleY(i)) 23 | .attr("width", d => scaleX(d)) 24 | .attr("height", scaleY.rangeBand()); 25 | } 26 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | with open("README.md", "r") as fh: 4 | long_description = fh.read() 5 | 6 | setup( 7 | name="notebookjs", 8 | version="0.1.4", 9 | author="Jorge Piazentin Ono, Juliana Freire, Claudio Silva", 10 | author_email="jorgehpo@nyu.edu", 11 | description="notebookJS library - Seamless JavaScript integration in Python Notebooks", 12 | long_description=long_description, 13 | long_description_content_type="text/markdown", 14 | url="https://github.com/jorgehpo/notebookJS", 15 | packages=find_packages(exclude=['resources']), 16 | include_package_data=True, 17 | license="MIT", 18 | classifiers=[ 19 | "Programming Language :: Python :: 3", 20 | "License :: OSI Approved :: MIT License", 21 | "Operating System :: OS Independent", 22 | ], 23 | python_requires='>=3.6', 24 | install_requires=[ 25 | "notebook" 26 | ] 27 | ) 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 notebookJS Developers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Examples/5_Webpack_BaseballAnnotator_Bidirectional/BaseballVisualizer/webpack.config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | entry: ['./js/index.js'], 3 | output: { 4 | path: __dirname + '/build', 5 | filename: 'baseballvisualizer.js', 6 | library: 'baseballvisualizer' 7 | }, 8 | module: { 9 | rules: [ 10 | { 11 | test: /\.js$/, 12 | exclude: /node_modules/, 13 | loader: 'babel-loader', 14 | options: { 15 | presets: ['@babel/preset-env', '@babel/react'] 16 | } 17 | }, 18 | { 19 | test: /\.css$/, 20 | use: [ 21 | 'style-loader', 22 | 'css-loader' 23 | ] 24 | }, 25 | { 26 | test: /\.less$/, 27 | use: ['style-loader', 'css-loader', 'less-loader'], 28 | }, 29 | { 30 | test: /\.(jpg|png|svg)$/, 31 | loader: "url-loader", 32 | options: { 33 | limit: Infinity // everything 34 | } 35 | } 36 | ] 37 | }, 38 | resolve: { 39 | extensions: ['.js'] 40 | }, 41 | devServer:{ 42 | writeToDisk:true, 43 | hot:false, 44 | inline: false, 45 | }, 46 | mode: 'development' 47 | }; 48 | module.exports = config; 49 | -------------------------------------------------------------------------------- /notebookjs/resources/CommAPI.js: -------------------------------------------------------------------------------- 1 | const COMM_TYPES = { 2 | JUPYTER: 'JUPYTER', 3 | COLAB: 'COLAB' 4 | }; 5 | 6 | class CommAPI{ 7 | constructor(api_call_id, callback) { 8 | this.callback = callback; 9 | this.mode = null; 10 | if (window.Jupyter !== undefined) { 11 | this.mode = COMM_TYPES.JUPYTER; 12 | this.comm = window.Jupyter.notebook.kernel.comm_manager.new_comm(api_call_id, {}); 13 | this.comm.on_msg(msg => { 14 | const data = msg.content.data; 15 | callback(data); 16 | }); 17 | } else if (window.google !== undefined) { 18 | this.mode = COMM_TYPES.COLAB; 19 | this.comm = async function(msg){ 20 | const result = await google.colab.kernel.invokeFunction( 21 | api_call_id, 22 | [msg], // The argument 23 | {}); // kwargs 24 | callback(result.data['application/json']); 25 | }; 26 | } else { 27 | console.error(new Error("Cannot find Jupyter/Colab namespace from javascript")); 28 | } 29 | } 30 | 31 | call(msg) { 32 | if (this.comm){ 33 | if (this.mode === COMM_TYPES.JUPYTER){ 34 | this.comm.send(msg); 35 | } else if (this.mode === COMM_TYPES.COLAB){ 36 | this.comm(msg); 37 | } 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /notebookjs/_comm.py: -------------------------------------------------------------------------------- 1 | from IPython.core.display import display, HTML, Javascript 2 | from IPython import get_ipython 3 | 4 | 5 | def setup_comm_colab(api_call_id, callback): 6 | """Function that connects javascript call with a Colab Notebook""" 7 | from google.colab import output 8 | from IPython import display 9 | def _recv(msg): 10 | return display.JSON(callback(msg)) # Use display.JSON to transfer an object 11 | output.register_callback(api_call_id, _recv) 12 | 13 | def setup_comm_jupyter(api_call_id, callback): 14 | """Function that connects javascript call with a Jupyter Notebook""" 15 | def _comm_api(comm, open_msg): 16 | @comm.on_msg 17 | def _recv(msg): 18 | ret = callback(msg['content']['data']) 19 | comm.send(ret) 20 | get_ipython().kernel.comm_manager.register_target(api_call_id, _comm_api) 21 | 22 | def setup_comm_api(api_call_id, callback): 23 | """Function that abstracts notebook connection (Jupyter or Colab) to javascript""" 24 | try: 25 | jupyter_setup = True 26 | setup_comm_jupyter(api_call_id, callback) 27 | except Exception: 28 | jupyter_setup = False 29 | try: 30 | colab_setup = True 31 | setup_comm_colab(api_call_id, callback) 32 | except Exception: 33 | colab_setup = False 34 | if not jupyter_setup and not colab_setup: 35 | print("Error: Cannot find Jupyter/Colab namespace for Python") -------------------------------------------------------------------------------- /Examples/5_Webpack_BaseballAnnotator_Bidirectional/BaseballVisualizer/play_annotated.csv: -------------------------------------------------------------------------------- 1 | time_elapsed_sec,P_x,P_y,B_x,B_y,R@1_x,R@1_y,R@2_x,R@2_y,R@3_x,R@3_y,1B_x,1B_y,2B_x,2B_y,3B_x,3B_y,SS_x,SS_y,LF_x,LF_y,CF_x,CF_y,RF_x,RF_y,C_x,C_y,BALL_x,BALL_y 2 | 0.0,-1.0465815337,62.2709968754,-2.2541642719,-2.9384709841,,,,,,,,,,,-72.29396308400649,91.01148815468284,-59.010552964461894,136.89963220401873,,,,,,,-2.2541642719,-6.5612191986,, 3 | 3.243,,,,,,,,,,,,,,,,,,,,,,,,,,,0.1207582738,62.7943023833 4 | 3.443,,,,,,,,,,,,,,,,,,,,,,,,,,,-1.0868244643,8.453079167 5 | 3.68,,,,,,,,,,,55.7098071589,96.0833135434,,,,,,,,,,,,,,,, 6 | 3.98,,,-1.0465815337,-2.9384709841,,,,,,,,,,,,,,,,,,,,,,,, 7 | 4.03,,,,,,,,,,,,,,,,,,,,,,,139.03301609056658,260.0730714943413,,,, 8 | 4.44,,,,,,,,,,,,,,,,,,,,,-16.74515712954728,316.82946018694093,,,,,, 9 | 4.47,,,,,,,,,,,,,,,,,,,-129.0503517766061,243.16691316037546,,,,,,,, 10 | 4.88,,,,,,,,,,,,,,,,,-42.10439463049605,136.89963220401873,,,,,,,,,, 11 | 5.493,,,,,,,,,,,,,,,,,,,,,,,,,,,31.5179094655,156.9857559583 12 | 5.81,,,,,,,,,,,64.1628863259,81.5923206857,,,,,,,,,,,,,,,, 13 | 6.34,,,,,,,,,,,,,,,,,,,,,,,128.16477144730283,217.8076756594267,,,, 14 | 6.57,,,,,,,,,,,,,,,,,,,-102.48353153751694,233.50625125525212,,,,,,,, 15 | 7.25,,,,,,,,,,,,,,,,,,,,,,,,,3.7837494188,-0.5233055079,, 16 | 7.383,,,,,,,,,,,,,,,,,,,,,,,,,,,61.707477919,65.2094678596 17 | 7.503,,,,,,,,,,,,,30.3103267273,156.9857559583,,,,,,,,,,,,,, 18 | 8.8,,,,,,,,,,,,,,,,,,,,,-33.65131546351312,249.20482685107754,,,,,, 19 | 8.81,,,,,,,,,,,,,,,,,-48.142308321198136,126.03138756075496,,,,,,,,,, 20 | 8.85,3.7837494188,52.6103349703,,,,,,,,,,,,,,,,,-102.48353153751694,214.18492744500543,,,,,,,, 21 | 8.89,,,62.9553035877,65.8937450899,,,,,,,64.1628863259,61.0634141373,,,,,,,,,,,131.78751966172408,180.37261077707376,,,, 22 | 8.893,,,,,,,,,,,,,38.7634058943,153.3630077438,,,,,,,,,,,,,, 23 | -------------------------------------------------------------------------------- /Examples/1_HelloWorld/HelloWorld.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Hello World\n", 8 | "\n", 9 | "Adding a text element to a Notebook output cell using plain JavaScript" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": null, 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "from notebookjs import execute_js" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "### Writing the js function" 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": {}, 32 | "outputs": [], 33 | "source": [ 34 | "helloworld_js = \"\"\"\n", 35 | "function helloworld(div_id, data){\n", 36 | " document.querySelector(div_id).textContent=data.text;\n", 37 | "}\n", 38 | "\"\"\"" 39 | ] 40 | }, 41 | { 42 | "cell_type": "markdown", 43 | "metadata": {}, 44 | "source": [ 45 | "### Running the code" 46 | ] 47 | }, 48 | { 49 | "cell_type": "code", 50 | "execution_count": null, 51 | "metadata": {}, 52 | "outputs": [], 53 | "source": [ 54 | "execute_js(helloworld_js, \"helloworld\", {\"text\": \"Hello World\"})" 55 | ] 56 | } 57 | ], 58 | "metadata": { 59 | "kernelspec": { 60 | "display_name": "Python 3", 61 | "language": "python", 62 | "name": "python3" 63 | }, 64 | "language_info": { 65 | "codemirror_mode": { 66 | "name": "ipython", 67 | "version": 3 68 | }, 69 | "file_extension": ".py", 70 | "mimetype": "text/x-python", 71 | "name": "python", 72 | "nbconvert_exporter": "python", 73 | "pygments_lexer": "ipython3", 74 | "version": "3.8.5" 75 | } 76 | }, 77 | "nbformat": 4, 78 | "nbformat_minor": 4 79 | } 80 | -------------------------------------------------------------------------------- /Examples/5_Webpack_BaseballAnnotator_Bidirectional/BaseballVisualizer/js/DraggableTimeline.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from 'prop-types'; 3 | import * as d3 from 'd3'; 4 | import Slider from 'rc-slider'; 5 | import 'rc-slider/assets/index.css'; 6 | 7 | export default class DraggableTimeline extends React.Component { 8 | shouldComponentUpdate(nextProps, nextState) { 9 | if (this.props.tracking === nextProps.tracking) { 10 | return false; 11 | } 12 | return true; 13 | } 14 | 15 | render () { 16 | const tracking = this.props.tracking; 17 | const annotatedPoints = {}; 18 | 19 | tracking.forEach(({x, y, t}, idx) => 20 | { 21 | annotatedPoints[t] = {}; 22 | }); 23 | 24 | const createSliderWithTooltip = Slider.createSliderWithTooltip; 25 | const SliderTP = createSliderWithTooltip(Slider); 26 | 27 | return ( 28 |
29 | {this.props.onDragTime(value)}} 35 | marks={annotatedPoints} 36 | activeDotStyle={{ borderColor: 'steelblue' }} 37 | dotStyle={{borderColor: 'steelblue' }} 38 | trackStyle={{ backgroundColor: 'steelblue' }} 39 | railStyle={{ backgroundColor: 'steelblue' }} 40 | width={this.props.width} 41 | /> 42 |
43 | ); 44 | } 45 | } 46 | 47 | DraggableTimeline.propTypes = { 48 | width: PropTypes.number.isRequired, 49 | height: PropTypes.number.isRequired, 50 | tracking: PropTypes.array, 51 | playerHead: PropTypes.number, 52 | margin: PropTypes.object, 53 | onDragTime: PropTypes.func.isRequired, 54 | trackingDuration: PropTypes.number.isRequired, 55 | }; 56 | 57 | DraggableTimeline.defaultProps = { 58 | width: 400, 59 | height: 10, 60 | margin: {top:10, bottom: 10, left: 10, right:10} 61 | }; 62 | -------------------------------------------------------------------------------- /Examples/5_Webpack_BaseballAnnotator_Bidirectional/BaseballVisualizer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "baseballvisualizer", 3 | "version": "1.0.0", 4 | "description": "library to plot and edit baseball trajectories", 5 | "main": "./js/index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "webpack", 9 | "start": "webpack-dev-server --progress --colors --content-base dist/" 10 | }, 11 | "author": "Jorge Piazentin Ono", 12 | "license": "UNLICENSED", 13 | "devDependencies": { 14 | "@babel/core": "^7.12.9", 15 | "@babel/preset-env": "^7.12.7", 16 | "@babel/preset-react": "^7.12.7", 17 | "babel-loader": "^8.2.2", 18 | "css-loader": "^2.1.1", 19 | "file-loader": "^3.0.1", 20 | "less": "^4.0.0", 21 | "less-loader": "^7.1.0", 22 | "prop-types": "^15.6.2", 23 | "react": "^16.14.0", 24 | "react-dom": "^16.14.0", 25 | "style-loader": "^0.23.1", 26 | "url-loader": "^4.1.1", 27 | "webpack": "^4.44.2", 28 | "webpack-cli": "^3.3.11", 29 | "webpack-dev-server": "^3.11.0", 30 | "worker-loader": "^2.0.0" 31 | }, 32 | "dependencies": { 33 | "@babel/polyfill": "^7.12.1", 34 | "@material-ui/core": "^4.11.2", 35 | "@material-ui/icons": "^4.11.2", 36 | "crossfilter2": "^1.5.4", 37 | "d3": "^6.3.1", 38 | "d3-array": "^2.9.0", 39 | "d3-axis": "^1.0.12", 40 | "d3-fetch": "^1.1.2", 41 | "d3-scale": "^2.2.2", 42 | "d3-scale-chromatic": "^1.3.3", 43 | "d3-selection": "^1.4.0", 44 | "d3-shape": "^1.3.7", 45 | "d3-transition": "^1.3.2", 46 | "d3-zoom": "^1.8.3", 47 | "dagre": "^0.8.4", 48 | "everpolate": "0.0.3", 49 | "install": "^0.12.2", 50 | "jquery": "^3.5.1", 51 | "lodash": "^4.17.20", 52 | "npm": "^7.3.0", 53 | "rc-checkbox": "^2.3.1", 54 | "rc-select": "^11.5.3", 55 | "rc-slider": "^9.6.5", 56 | "react-bootstrap": "^1.4.0", 57 | "react-redux": "^6.0.0", 58 | "react-select": "^2.4.1", 59 | "redux": "^4.0.1", 60 | "redux-thunk": "^2.3.0", 61 | "styled-components": "^5.2.1" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Examples/5_Webpack_BaseballAnnotator_Bidirectional/BaseballVisualizer/js/field.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Examples/7_D3_scatterplot/scatterplot_lib.js: -------------------------------------------------------------------------------- 1 | function scatter(div_id, data){ 2 | // Code adapted from https://www.d3-graph-gallery.com/graph/scatter_animation_start.html 3 | // Dataset https://raw.githubusercontent.com/holtzy/data_to_viz/master/Example_dataset/2_TwoNum.csv 4 | // set the dimensions and margins of the graph 5 | var margin = {top: 10, right: 30, bottom: 30, left: 60}, 6 | width = 460 - margin.left - margin.right, 7 | height = 400 - margin.top - margin.bottom; 8 | // append the svg object to the body of the page 9 | var svg = d3.select(div_id) 10 | .append("svg") 11 | .attr("width", width + margin.left + margin.right) 12 | .attr("height", height + margin.top + margin.bottom) 13 | .append("g") 14 | .attr("transform", 15 | "translate(" + margin.left + "," + margin.top + ")") 16 | 17 | // Add X axis 18 | var x = d3.scale.linear() 19 | .domain([0, 0]) 20 | .range([ 0, width ]); 21 | svg.append("g") 22 | .attr("class", "myXaxis axis") // Note that here we give a class to the X axis, to be able to call it later and modify it 23 | .attr("transform", "translate(0," + height + ")") 24 | .call(d3.svg.axis().scale(x)) 25 | .attr("opacity", "0") 26 | 27 | // Add Y axis 28 | var y = d3.scale.linear() 29 | .domain([0, 500000]) 30 | .range([ height, 0]); 31 | svg.append("g") 32 | .attr("class", "axis") 33 | .call(d3.svg.axis() 34 | .orient('left') 35 | .scale(y)); 36 | 37 | // Add dots 38 | svg.append('g') 39 | .selectAll("dot") 40 | .data(data) 41 | .enter() 42 | .append("circle") 43 | .attr("cx", function (d) { return x(d.GrLivArea); } ) 44 | .attr("cy", function (d) { return y(d.SalePrice); } ) 45 | .attr("r", 1.5) 46 | .style("fill", "#69b3a2") 47 | 48 | // new X axis 49 | x.domain([0, 4000]) 50 | svg.select(".myXaxis") 51 | .transition() 52 | .duration(2000) 53 | .attr("opacity", "1") 54 | .call(d3.svg.axis().scale(x)); 55 | 56 | svg.selectAll("circle") 57 | .transition() 58 | .delay(function(d,i){return(i*3)}) 59 | .duration(2000) 60 | .attr("cx", function (d) { return x(d.GrLivArea); } ) 61 | .attr("cy", function (d) { return y(d.SalePrice); } ) 62 | 63 | } 64 | -------------------------------------------------------------------------------- /Examples/7_D3_scatterplot/D3_Scatter.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# D3 Scatter Plot" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "D3 V3 scatter plot. Code adapted from https://www.d3-graph-gallery.com/graph/scatter_animation_start.html" 15 | ] 16 | }, 17 | { 18 | "cell_type": "markdown", 19 | "metadata": {}, 20 | "source": [ 21 | "### Loading libraries" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": null, 27 | "metadata": {}, 28 | "outputs": [], 29 | "source": [ 30 | "with open(\"scatterplot_lib.js\", \"r\") as f:\n", 31 | " scatterplot_lib = f.read()\n", 32 | " \n", 33 | "d3_lib_url = \"https://d3js.org/d3.v3.min.js\"\n", 34 | "\n", 35 | "css = \"\"\"\n", 36 | ".axis path,\n", 37 | ".axis line {\n", 38 | " fill: none;\n", 39 | " stroke: slategray;\n", 40 | " shape-rendering: crispEdges;\n", 41 | "}\n", 42 | "\"\"\"" 43 | ] 44 | }, 45 | { 46 | "cell_type": "markdown", 47 | "metadata": {}, 48 | "source": [ 49 | "### Loading data" 50 | ] 51 | }, 52 | { 53 | "cell_type": "code", 54 | "execution_count": null, 55 | "metadata": {}, 56 | "outputs": [], 57 | "source": [ 58 | "import pandas as pd\n", 59 | "data = pd.read_csv(\"Prices.csv\").to_dict(orient=\"records\")" 60 | ] 61 | }, 62 | { 63 | "cell_type": "markdown", 64 | "metadata": {}, 65 | "source": [ 66 | "### Plotting Scatter Plot" 67 | ] 68 | }, 69 | { 70 | "cell_type": "code", 71 | "execution_count": null, 72 | "metadata": {}, 73 | "outputs": [], 74 | "source": [ 75 | "from notebookjs import execute_js\n", 76 | "execute_js(library_list=[d3_lib_url, scatterplot_lib], main_function=\"scatter\", data_dict=data, css_list=css)" 77 | ] 78 | }, 79 | { 80 | "cell_type": "code", 81 | "execution_count": null, 82 | "metadata": {}, 83 | "outputs": [], 84 | "source": [] 85 | } 86 | ], 87 | "metadata": { 88 | "kernelspec": { 89 | "display_name": "Python 3", 90 | "language": "python", 91 | "name": "python3" 92 | }, 93 | "language_info": { 94 | "codemirror_mode": { 95 | "name": "ipython", 96 | "version": 3 97 | }, 98 | "file_extension": ".py", 99 | "mimetype": "text/x-python", 100 | "name": "python", 101 | "nbconvert_exporter": "python", 102 | "pygments_lexer": "ipython3", 103 | "version": "3.8.5" 104 | } 105 | }, 106 | "nbformat": 4, 107 | "nbformat_minor": 4 108 | } 109 | -------------------------------------------------------------------------------- /Examples/3_RadialBarChart/RadialBarChart.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Radial Bar Chart\n", 8 | "\n", 9 | "We port the Radial Bar Chart implemented in D3 to a Jupyter Notebook.\n", 10 | "Code adapted from https://bl.ocks.org/AntonOrlov/6b42d8676943cc933f48a43a7c7e5b6c" 11 | ] 12 | }, 13 | { 14 | "cell_type": "markdown", 15 | "metadata": {}, 16 | "source": [ 17 | "### Loading the radial bar chart code. \n", 18 | "\n", 19 | "We load both js and css scripts" 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": null, 25 | "metadata": {}, 26 | "outputs": [], 27 | "source": [ 28 | "d3_lib_url = \"https://d3js.org/d3.v3.min.js\"\n", 29 | "\n", 30 | "with open(\"radial_bar.css\", \"r\") as f:\n", 31 | " radial_bar_css = f.read()\n", 32 | " \n", 33 | "with open (\"radial_bar_lib.js\", \"r\") as f:\n", 34 | " radial_bar_lib = f.read()" 35 | ] 36 | }, 37 | { 38 | "cell_type": "markdown", 39 | "metadata": {}, 40 | "source": [ 41 | "### Loading data" 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": null, 47 | "metadata": {}, 48 | "outputs": [], 49 | "source": [ 50 | "import pandas as pd\n", 51 | "energy = pd.read_csv(\"energy.csv\")" 52 | ] 53 | }, 54 | { 55 | "cell_type": "markdown", 56 | "metadata": {}, 57 | "source": [ 58 | "### Plotting the bar chart \n", 59 | "\n", 60 | "Radial Bar Chart of energy consumption over five months" 61 | ] 62 | }, 63 | { 64 | "cell_type": "code", 65 | "execution_count": null, 66 | "metadata": {}, 67 | "outputs": [], 68 | "source": [ 69 | "from notebookjs import execute_js\n", 70 | "execute_js(library_list=[d3_lib_url, radial_bar_lib], main_function=\"radial_bar\", \n", 71 | " data_dict=energy.to_dict(orient=\"records\"), css_list=[radial_bar_css])" 72 | ] 73 | }, 74 | { 75 | "cell_type": "code", 76 | "execution_count": null, 77 | "metadata": {}, 78 | "outputs": [], 79 | "source": [] 80 | } 81 | ], 82 | "metadata": { 83 | "kernelspec": { 84 | "display_name": "Python 3", 85 | "language": "python", 86 | "name": "python3" 87 | }, 88 | "language_info": { 89 | "codemirror_mode": { 90 | "name": "ipython", 91 | "version": 3 92 | }, 93 | "file_extension": ".py", 94 | "mimetype": "text/x-python", 95 | "name": "python", 96 | "nbconvert_exporter": "python", 97 | "pygments_lexer": "ipython3", 98 | "version": "3.8.5" 99 | } 100 | }, 101 | "nbformat": 4, 102 | "nbformat_minor": 4 103 | } 104 | -------------------------------------------------------------------------------- /Examples/2_SimpleD3_Circle/SimpleD3_Circle.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Simple D3 Circle\n", 8 | "\n", 9 | "This notebook shows how to load D3js from an URL and draw a simple circle in a notebook" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": null, 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "from notebookjs import execute_js" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "### Setting up the JavaScript libraries\n", 26 | "\n", 27 | "We are using two JavaScript libraries: D3 and a local \"draw_circle_lib\" library.\n", 28 | "\n", 29 | "- D3 is loaded from the web (notebookJS takes care of downloading files from URLs). \n", 30 | "\n", 31 | "- draw_circle_lib is loaded from a local file.\n", 32 | "\n", 33 | "**Note that we are using D3 V3.**\n", 34 | "More recent versions of D3 use ES6 and cannot be directly loaded in the notebook script tag. We recommend using a javascript build tool such as babel + webpack to use more modern libraries." 35 | ] 36 | }, 37 | { 38 | "cell_type": "code", 39 | "execution_count": null, 40 | "metadata": {}, 41 | "outputs": [], 42 | "source": [ 43 | "with open(\"./draw_circle_lib.js\", \"r\") as f:\n", 44 | " draw_circle_lib = f.read()\n", 45 | " \n", 46 | "d3_lib_url = \"https://d3js.org/d3.v3.min.js\"" 47 | ] 48 | }, 49 | { 50 | "cell_type": "markdown", 51 | "metadata": {}, 52 | "source": [ 53 | "This is the D3 function to draw a circle: " 54 | ] 55 | }, 56 | { 57 | "cell_type": "code", 58 | "execution_count": null, 59 | "metadata": {}, 60 | "outputs": [], 61 | "source": [ 62 | "print(draw_circle_lib)" 63 | ] 64 | }, 65 | { 66 | "cell_type": "markdown", 67 | "metadata": {}, 68 | "source": [ 69 | "### Running the code" 70 | ] 71 | }, 72 | { 73 | "cell_type": "code", 74 | "execution_count": null, 75 | "metadata": {}, 76 | "outputs": [], 77 | "source": [ 78 | "execute_js([d3_lib_url, draw_circle_lib], \"draw_circle\", {\"color\": \"#4682B4\"})" 79 | ] 80 | }, 81 | { 82 | "cell_type": "code", 83 | "execution_count": null, 84 | "metadata": {}, 85 | "outputs": [], 86 | "source": [] 87 | } 88 | ], 89 | "metadata": { 90 | "kernelspec": { 91 | "display_name": "Python 3", 92 | "language": "python", 93 | "name": "python3" 94 | }, 95 | "language_info": { 96 | "codemirror_mode": { 97 | "name": "ipython", 98 | "version": 3 99 | }, 100 | "file_extension": ".py", 101 | "mimetype": "text/x-python", 102 | "name": "python", 103 | "nbconvert_exporter": "python", 104 | "pygments_lexer": "ipython3", 105 | "version": "3.8.5" 106 | } 107 | }, 108 | "nbformat": 4, 109 | "nbformat_minor": 4 110 | } 111 | -------------------------------------------------------------------------------- /Examples/5_Webpack_BaseballAnnotator_Bidirectional/BaseballVisualizer/js/helpers.js: -------------------------------------------------------------------------------- 1 | import {linear} from 'everpolate'; 2 | 3 | export const constants = { 4 | gameElements: ["P", "B", "R@1", "R@2", "R@3", "1B", "2B", "3B", "SS", "LF", "CF", "RF", "C", 5 | "BALL"], 6 | gameElementsDesc: { 7 | "P": "Pitcher", 8 | "B": "Batter", 9 | "R@1": "Runner at first", 10 | "R@2": "Runner at second", 11 | "R@3": "Runner at third", 12 | "1B": "First baseman", 13 | "2B": "Second baseman", 14 | "3B": "Third baseman", 15 | "SS": "Shortstop", 16 | "LF": "Left fielder", 17 | "CF": "Center fielder", 18 | "RF": "Right fielder", 19 | "C": "Catcher", 20 | "BALL": "Ball" 21 | }, 22 | }; 23 | 24 | const input2ndBase = [0.0, 127.2792206], 25 | outputHomePlate = [125.2, 203.5], 26 | output2ndBase = [125.2, 150.8], 27 | inputScale = 1.0 / input2ndBase[1], 28 | outputScale = 1.0 / (output2ndBase[1] - outputHomePlate[1]); 29 | 30 | export function mapFieldSVGX(x){ 31 | const x2 = (((-x) * inputScale) / outputScale) + outputHomePlate[0]; 32 | return x2; 33 | } 34 | 35 | export function mapFieldSVGY(y){ 36 | const y2 = (y * inputScale) / outputScale + outputHomePlate[1]; 37 | return y2; 38 | } 39 | 40 | export function mapFieldSVG([x, y]){ 41 | const x2 = (((-x) * inputScale) / outputScale) + outputHomePlate[0]; 42 | const y2 = (y * inputScale) / outputScale + outputHomePlate[1]; 43 | return [x2, y2]; 44 | } 45 | 46 | export function mapSVGField([x2, y2]){ 47 | const x = -((x2 - outputHomePlate[0]) * outputScale) / inputScale; 48 | const y = ((y2 - outputHomePlate[1]) * outputScale) / inputScale; 49 | return [x, y]; 50 | } 51 | 52 | export function interpolatePositions(tQuery, positions) { 53 | //positions: array of [{x, y, t}, ...] 54 | if (positions.length === 1){ 55 | return {x: positions[0].x, y:positions[0].y}; 56 | } 57 | positions.sort((a,b) => a.t - b.t); 58 | if (tQuery <= positions[0].t) { 59 | return {x: positions[0].x, y: positions[0].y} 60 | } 61 | if (tQuery >= positions[positions.length-1].t) { 62 | return {x: positions[positions.length-1].x, y: positions[positions.length-1].y} 63 | } 64 | const t = positions.map(p=>p.t); 65 | const x = positions.map(p=>p.x); 66 | const y = positions.map(p=>p.y); 67 | const mappedX = linear(tQuery, t, x); 68 | const mappedY = linear(tQuery, t, y); 69 | return {x: mappedX, y: mappedY}; 70 | } 71 | 72 | 73 | export function convert_data_format(tracking){ 74 | let csvObj = {}; 75 | constants.gameElements.forEach(elem => { 76 | tracking[elem].forEach( ({x, y, t}) => { 77 | if (!csvObj[t]) { 78 | csvObj[t] = {"time_elapsed_sec": t}; 79 | constants.gameElements.forEach(ename => { 80 | csvObj[t][ename + "_x"] = null; 81 | csvObj[t][ename + "_y"] = null; 82 | }) 83 | } 84 | csvObj[t][elem + "_x"] = x; 85 | csvObj[t][elem + "_y"] = y; 86 | }); 87 | }); 88 | const timeKeys = Object.keys(csvObj); 89 | timeKeys.sort((a, b) => a - b); 90 | const csvArray = []; 91 | timeKeys.forEach(key => { 92 | csvArray.push(csvObj[key]); 93 | }); 94 | return csvArray; 95 | } -------------------------------------------------------------------------------- /Examples/4_Bidirectional_Comm/Bar_Chart_Bidirectional_Comm.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Bar chart example with bidirectional communication" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": null, 13 | "metadata": {}, 14 | "outputs": [], 15 | "source": [ 16 | "from notebookjs import execute_js" 17 | ] 18 | }, 19 | { 20 | "cell_type": "markdown", 21 | "metadata": {}, 22 | "source": [ 23 | "### Loading libraries" 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": null, 29 | "metadata": {}, 30 | "outputs": [], 31 | "source": [ 32 | "d3_lib_url = \"https://d3js.org/d3.v3.min.js\"\n", 33 | "\n", 34 | "with open(\"bar_chart_lib.js\", \"r\") as f:\n", 35 | " bar_chart_lib = f.read()" 36 | ] 37 | }, 38 | { 39 | "cell_type": "markdown", 40 | "metadata": {}, 41 | "source": [ 42 | "### The bar chart displays the array data in order" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": null, 48 | "metadata": {}, 49 | "outputs": [], 50 | "source": [ 51 | "data_dict = {\n", 52 | " \"array\": [1,2,3]\n", 53 | "}\n", 54 | "execute_js(library_list=[d3_lib_url, bar_chart_lib], main_function=\"bar_chart\", data_dict=data_dict)" 55 | ] 56 | }, 57 | { 58 | "cell_type": "markdown", 59 | "metadata": {}, 60 | "source": [ 61 | "### Set up data update callback" 62 | ] 63 | }, 64 | { 65 | "cell_type": "code", 66 | "execution_count": null, 67 | "metadata": {}, 68 | "outputs": [], 69 | "source": [ 70 | "import random\n", 71 | "\n", 72 | "def random_array(data):\n", 73 | " # Makes an array of random numbers\n", 74 | " n = data[\"n\"]\n", 75 | " return {'array': [random.random() for x in range(n)]}\n", 76 | "\n", 77 | "callbacks = {'random_array': random_array}" 78 | ] 79 | }, 80 | { 81 | "cell_type": "markdown", 82 | "metadata": {}, 83 | "source": [ 84 | "### Update the bar chart every 2 seconds using callback" 85 | ] 86 | }, 87 | { 88 | "cell_type": "code", 89 | "execution_count": null, 90 | "metadata": {}, 91 | "outputs": [], 92 | "source": [ 93 | "update_func_js = \"\"\"\n", 94 | "function set_update(div_id, _){ \n", 95 | " comm = new CommAPI(\"random_array\", (data) => {\n", 96 | " d3.select(div_id).selectAll(\"*\").remove();\n", 97 | " bar_chart(div_id, data);\n", 98 | " });\n", 99 | " comm.call({n: 5})\n", 100 | " setInterval(function(){ comm.call({n: 5}) }, 2000);\n", 101 | "}\n", 102 | "\"\"\"\n", 103 | "\n", 104 | "execute_js(library_list=[d3_lib_url, bar_chart_lib, update_func_js], main_function=\"set_update\", callbacks=callbacks)" 105 | ] 106 | }, 107 | { 108 | "cell_type": "code", 109 | "execution_count": null, 110 | "metadata": {}, 111 | "outputs": [], 112 | "source": [] 113 | } 114 | ], 115 | "metadata": { 116 | "kernelspec": { 117 | "display_name": "Python 3", 118 | "language": "python", 119 | "name": "python3" 120 | }, 121 | "language_info": { 122 | "codemirror_mode": { 123 | "name": "ipython", 124 | "version": 3 125 | }, 126 | "file_extension": ".py", 127 | "mimetype": "text/x-python", 128 | "name": "python", 129 | "nbconvert_exporter": "python", 130 | "pygments_lexer": "ipython3", 131 | "version": "3.8.5" 132 | } 133 | }, 134 | "nbformat": 4, 135 | "nbformat_minor": 4 136 | } 137 | -------------------------------------------------------------------------------- /Examples/5_Webpack_BaseballAnnotator_Bidirectional/BaseballVisualizer/js/PlayDiagram.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from "prop-types"; 3 | import { mapFieldSVG, mapSVGField, constants, mapFieldSVGY, mapFieldSVGX, interpolatePositions } from './helpers'; 4 | import $ from "jquery"; 5 | import * as d3 from "d3"; 6 | import fieldsvg from "./field.svg"; 7 | 8 | export default class PlayDiagram extends Component { 9 | render() { 10 | let ball = null; 11 | 12 | let line = d3.line() 13 | .x((d) => { 14 | return mapFieldSVGX(d.x); 15 | }) 16 | .y((d) => { 17 | return mapFieldSVGY(d.y); 18 | }); 19 | 20 | const lineElems = []; 21 | const endElems = []; 22 | 23 | constants.gameElements.forEach( elem => { 24 | if (this.props.tracking && this.props.tracking[elem] && this.props.tracking[elem].length > 0){ 25 | const tracking = this.props.tracking; 26 | const newTrackingElem = []; 27 | for (let i = 0; i < tracking[elem].length; ++i){ 28 | if (tracking[elem][i].t > this.props.playUpTo){ 29 | break; 30 | } 31 | newTrackingElem.push(tracking[elem][i]); 32 | } 33 | const lastPos = interpolatePositions(this.props.playUpTo, tracking[elem]); 34 | newTrackingElem.push( {...lastPos, t: this.props.playUpTo} ); 35 | lineElems.push( 36 | ); 47 | 48 | if (elem === this.props.annotationElem) { 49 | endElems.push(); 60 | 61 | } else { 62 | endElems.push( 63 | 73 | ); 74 | } 75 | } 76 | }); 77 | return ( 78 | { 80 | let container = $("#playDiagramBallSVG").get(0).getBoundingClientRect(); 81 | let x = evt.clientX - container.left; 82 | let y = evt.clientY - container.top; 83 | const scaleX = 250/this.props.width; 84 | const scaleY = 250/this.props.height; 85 | let mapped = mapSVGField([x * scaleX, y * scaleY]); 86 | this.props.onClick(mapped); 87 | }}> 88 | 89 | {lineElems} 90 | {endElems} 91 | {ball} 92 | 93 | ); 94 | } 95 | } 96 | 97 | PlayDiagram.propTypes = { 98 | width: PropTypes.number.isRequired, 99 | height: PropTypes.number.isRequired, 100 | onClick: PropTypes.func, 101 | playUpTo: PropTypes.number, 102 | tracking: PropTypes.object, 103 | annotationElem: PropTypes.string 104 | }; 105 | 106 | PlayDiagram.defaultProps = { 107 | width: 500, 108 | height: 500 109 | }; 110 | -------------------------------------------------------------------------------- /Examples/6_HelloWorld_Bidirectional/HelloWorld_Bidirectional.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "id": "IaxAGrTGttuV" 7 | }, 8 | "source": [ 9 | "# notebookJS Colab HelloWorld\n", 10 | "\n", 11 | "In this notebook, we show how to use notebookJS to run custom Javascript code. We also show how to send messages between Python and Javascript." 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": null, 17 | "metadata": { 18 | "id": "vYD-yjPtn_ik" 19 | }, 20 | "outputs": [], 21 | "source": [ 22 | "!pip install notebookjs" 23 | ] 24 | }, 25 | { 26 | "cell_type": "markdown", 27 | "metadata": { 28 | "id": "BjePMHoyt9TF" 29 | }, 30 | "source": [ 31 | "## Defining the JavaScript drawing function.\n", 32 | "\n", 33 | "The function requests a message to python every 1 second. The callback identifier is called *get_hello*" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": null, 39 | "metadata": { 40 | "id": "10am7Nm9oCc9" 41 | }, 42 | "outputs": [], 43 | "source": [ 44 | "helloworld_js = \"\"\"\n", 45 | "function helloworld(div_id, data){\n", 46 | " comm = new CommAPI(\"get_hello\", (ret) => {\n", 47 | " document.querySelector(div_id).textContent = ret.text;\n", 48 | " });\n", 49 | " setInterval(() => {comm.call({})}, 1000);\n", 50 | " comm.call({});\n", 51 | "}\n", 52 | "\"\"\"" 53 | ] 54 | }, 55 | { 56 | "cell_type": "markdown", 57 | "metadata": { 58 | "id": "zsXaw6XxuJF2" 59 | }, 60 | "source": [ 61 | "## Defining the Python callback" 62 | ] 63 | }, 64 | { 65 | "cell_type": "code", 66 | "execution_count": null, 67 | "metadata": { 68 | "id": "L0ucikc4oIOW" 69 | }, 70 | "outputs": [], 71 | "source": [ 72 | "import random\n", 73 | "def hello_world_random(data):\n", 74 | " hello_world_languages = [\n", 75 | " \"Ola Mundo\", # Portuguese\n", 76 | " \"Hello World\", # English\n", 77 | " \"Hola Mundo\", # Spanish\n", 78 | " \"Geiá sou Kósme\", # Greek\n", 79 | " \"Kon'nichiwa sekai\", # Japanese\n", 80 | " \"Hallo Welt\", # German\n", 81 | " \"namaste duniya\" #Hindi\n", 82 | " ]\n", 83 | " i = random.randint(0, len(hello_world_languages)-1)\n", 84 | " return {'text': hello_world_languages[i]}" 85 | ] 86 | }, 87 | { 88 | "cell_type": "markdown", 89 | "metadata": { 90 | "id": "g-rkBdq7uXSL" 91 | }, 92 | "source": [ 93 | "## Running the javascript function with Python callbacks" 94 | ] 95 | }, 96 | { 97 | "cell_type": "code", 98 | "execution_count": null, 99 | "metadata": { 100 | "id": "HaxtaaB-pj0z" 101 | }, 102 | "outputs": [], 103 | "source": [ 104 | "from notebookjs import execute_js\n", 105 | "execute_js(helloworld_js, \"helloworld\", callbacks={\"get_hello\": hello_world_random})" 106 | ] 107 | }, 108 | { 109 | "cell_type": "code", 110 | "execution_count": null, 111 | "metadata": { 112 | "id": "848VKX8jqGbW" 113 | }, 114 | "outputs": [], 115 | "source": [] 116 | } 117 | ], 118 | "metadata": { 119 | "colab": { 120 | "collapsed_sections": [], 121 | "name": "HelloWorld_notebookJS_Colab.ipynb", 122 | "provenance": [] 123 | }, 124 | "kernelspec": { 125 | "display_name": "Python 3", 126 | "language": "python", 127 | "name": "python3" 128 | }, 129 | "language_info": { 130 | "codemirror_mode": { 131 | "name": "ipython", 132 | "version": 3 133 | }, 134 | "file_extension": ".py", 135 | "mimetype": "text/x-python", 136 | "name": "python", 137 | "nbconvert_exporter": "python", 138 | "pygments_lexer": "ipython3", 139 | "version": "3.8.5" 140 | } 141 | }, 142 | "nbformat": 4, 143 | "nbformat_minor": 1 144 | } 145 | -------------------------------------------------------------------------------- /Examples/3_RadialBarChart/radial_bar_lib.js: -------------------------------------------------------------------------------- 1 | function radial_bar(div_id, data){ 2 | // Radial Bar Chart 3 | 4 | // Code adapted from: https://bl.ocks.org/AntonOrlov/6b42d8676943cc933f48a43a7c7e5b6c 5 | // We have changed the data loading lines (to receive the parameter data) and ported the code to D3 V3. 6 | 7 | const width = 960, 8 | height = 500, 9 | chartRadius = height / 2 - 40; 10 | 11 | const color = d3.scale.category10(); 12 | 13 | let svg = d3.select(div_id).append('svg') 14 | .attr('width', width) 15 | .attr('height', height) 16 | .append('g') 17 | .attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')'); 18 | 19 | let tooltip = d3.select('body').append('div') 20 | .attr('class', 'tooltip'); 21 | 22 | const PI = Math.PI, 23 | arcMinRadius = 10, 24 | arcPadding = 10, 25 | labelPadding = -5, 26 | numTicks = 10; 27 | 28 | 29 | let scale = d3.scale.linear() 30 | .domain([0, d3.max(data, d => d.value) * 1.1]) 31 | .range([0, 2 * PI]); 32 | 33 | let ticks = scale.ticks(numTicks).slice(0, -1); 34 | let keys = data.map((d, i) => d.name); 35 | //number of arcs 36 | const numArcs = keys.length; 37 | const arcWidth = (chartRadius - arcMinRadius - numArcs * arcPadding) / numArcs; 38 | 39 | let arc = d3.svg.arc() 40 | .innerRadius((d, i) => getInnerRadius(i)) 41 | .outerRadius((d, i) => getOuterRadius(i)) 42 | .startAngle(0) 43 | .endAngle((d, i) => scale(d)) 44 | 45 | let radialAxis = svg.append('g') 46 | .attr('class', 'r axis') 47 | .selectAll('g') 48 | .data(data) 49 | .enter().append('g'); 50 | 51 | radialAxis.append('circle') 52 | .attr('r', (d, i) => getOuterRadius(i) + arcPadding); 53 | 54 | radialAxis.append('text') 55 | .attr('x', labelPadding) 56 | .attr('y', (d, i) => -getOuterRadius(i) + arcPadding) 57 | .text(d => d.name); 58 | 59 | let axialAxis = svg.append('g') 60 | .attr('class', 'a axis') 61 | .selectAll('g') 62 | .data(ticks) 63 | .enter().append('g') 64 | .attr('transform', d => 'rotate(' + (rad2deg(scale(d)) - 90) + ')'); 65 | 66 | axialAxis.append('line') 67 | .attr('x2', chartRadius); 68 | 69 | axialAxis.append('text') 70 | .attr('x', chartRadius + 10) 71 | .style('text-anchor', d => (scale(d) >= PI && scale(d) < 2 * PI ? 'end' : null)) 72 | .attr('transform', d => 'rotate(' + (90 - rad2deg(scale(d))) + ',' + (chartRadius + 10) + ',0)') 73 | .text(d => d); 74 | 75 | //data arcs 76 | let arcs = svg.append('g') 77 | .attr('class', 'data') 78 | .selectAll('path') 79 | .data(data) 80 | .enter().append('path') 81 | .attr('class', 'arc') 82 | .style('fill', (d, i) => color(i)) 83 | 84 | arcs.transition() 85 | .delay((d, i) => i * 200) 86 | .duration(1000) 87 | .attrTween('d', arcTween); 88 | 89 | arcs.on('mousemove', showTooltip) 90 | arcs.on('mouseout', hideTooltip) 91 | 92 | 93 | function arcTween(d, i) { 94 | let interpolate = d3.interpolate(0, d.value); 95 | return t => arc(interpolate(t), i); 96 | } 97 | 98 | function showTooltip(d) { 99 | tooltip.style('left', (d3.event.pageX + 10) + 'px') 100 | .style('top', (d3.event.pageY - 25) + 'px') 101 | .style('display', 'inline-block') 102 | .html(d.value); 103 | } 104 | 105 | function hideTooltip() { 106 | tooltip.style('display', 'none'); 107 | } 108 | 109 | function rad2deg(angle) { 110 | return angle * 180 / PI; 111 | } 112 | 113 | function getInnerRadius(index) { 114 | return arcMinRadius + (numArcs - (index + 1)) * (arcWidth + arcPadding); 115 | } 116 | 117 | function getOuterRadius(index) { 118 | return getInnerRadius(index) + arcWidth; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Examples/5_Webpack_BaseballAnnotator_Bidirectional/BaseballVisualizer/js/TrajectoryAnnotator.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PlayDiagram from './PlayDiagram'; 3 | import {interpolatePositions, constants, convert_data_format} from './helpers'; 4 | import {Container, Row, Col, Button} from 'react-bootstrap'; 5 | import 'rc-select/assets/index.less'; 6 | 7 | import DraggableTimeline from "./DraggableTimeline"; 8 | import PropTypes from 'prop-types'; 9 | 10 | function submitTrajectoryToServer(tracking){ 11 | const alert_sent = ()=>{alert("Trajectory sent to Jupyter Notebook.")}; 12 | let comm = new CommAPI("submit_trajectory", alert_sent) 13 | 14 | // Send data 15 | comm.call({'trajectory': tracking}) 16 | } 17 | 18 | function convertRawTrackingToTrajectory(rawTracking) { 19 | if (rawTracking === null) return []; 20 | let tracking = Object.create(null); 21 | constants.gameElements.forEach(element => { 22 | tracking[element] = []; 23 | for (let i = 0; i < rawTracking.length; ++i) { 24 | if (rawTracking[i][element + "_x"] !== null) { 25 | tracking[element].push({ 26 | x: rawTracking[i][element + "_x"], 27 | y: rawTracking[i][element + "_y"], 28 | t: rawTracking[i]["time_elapsed_sec"] 29 | }); 30 | } 31 | } 32 | }); 33 | return tracking; 34 | } 35 | 36 | export default class TrajectoryAnnotator extends Component { 37 | constructor(props){ 38 | super(props); 39 | this.state = { 40 | tracking: convertRawTrackingToTrajectory(this.props.tracking), 41 | trackingDuration: this.props.tracking[this.props.tracking.length-1].time_elapsed_sec, 42 | playerHead: 0, 43 | curTracking: "B" 44 | }; 45 | this.clickPlayDiagram = this.clickPlayDiagram.bind(this); 46 | } 47 | 48 | clickPlayDiagram(position) { 49 | const curTracking = this.state.curTracking; 50 | let newTracking = this.state.tracking[curTracking].filter(annotation => { 51 | return Math.abs(annotation.t - this.state.playerHead) > 1e-1; 52 | }); 53 | newTracking.push({ 54 | x: position[0], 55 | y: position[1], 56 | t: this.state.playerHead 57 | }); 58 | const allTrackings = { 59 | ...this.state.tracking, 60 | }; 61 | allTrackings[curTracking] = newTracking; 62 | this.setState({tracking: allTrackings}); 63 | } 64 | 65 | render() { 66 | return ( 67 |
68 | Selected Position 69 |
70 | { 71 | constants.gameElements.map((elem) => { 72 | return 81 | }) 82 | } 83 |
84 | 92 | {this.setState({playerHead: time})}} 100 | trackingDuration={this.state.trackingDuration} 101 | /> 102 | 103 | 111 | 112 | 124 |
125 | ); 126 | } 127 | } 128 | 129 | 130 | TrajectoryAnnotator.propTypes = { 131 | tracking: PropTypes.arrayOf(PropTypes.object).isRequired 132 | }; -------------------------------------------------------------------------------- /Examples/5_Webpack_BaseballAnnotator_Bidirectional/BaseballAnnotator.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Baseball Visualizer and Annotator - Bidirectional communication" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "This visualization shows an interactive chart that displays baseball game trajectories. The user can control the progress of the play using a slider. Furthermore, the user can select a player or the ball to edit its trajectory (either clicking on the field, or using the button \"Clear trajectory\"). Visualization based on the paper HistoryTracker (Ono et al, 2019)." 15 | ] 16 | }, 17 | { 18 | "cell_type": "markdown", 19 | "metadata": {}, 20 | "source": [ 21 | "### Loading code bundle (built with Webpack)\n", 22 | "\n", 23 | "Source code for the bundle is in BaseballVisualizer/js.\n", 24 | "\n", 25 | "To build the library from scratch, run\n", 26 | "\n", 27 | "```\n", 28 | "cd BaseballVisualizer\n", 29 | "npm install\n", 30 | "npm run build\n", 31 | "```" 32 | ] 33 | }, 34 | { 35 | "cell_type": "code", 36 | "execution_count": null, 37 | "metadata": {}, 38 | "outputs": [], 39 | "source": [ 40 | "with open(\"BaseballVisualizer/build/baseballvisualizer.js\", \"r\") as f:\n", 41 | " code_bundle = f.read()" 42 | ] 43 | }, 44 | { 45 | "cell_type": "markdown", 46 | "metadata": {}, 47 | "source": [ 48 | "### Loading the data (baseball play trajectory in CSV)" 49 | ] 50 | }, 51 | { 52 | "cell_type": "code", 53 | "execution_count": null, 54 | "metadata": {}, 55 | "outputs": [], 56 | "source": [ 57 | "import pandas as pd\n", 58 | "play_csv = pd.read_csv(\"./BaseballVisualizer/play_annotated.csv\")\n", 59 | "data_dict = {'tracking': play_csv.to_json(orient=\"records\")}" 60 | ] 61 | }, 62 | { 63 | "cell_type": "markdown", 64 | "metadata": {}, 65 | "source": [ 66 | "### Setting up callback\n", 67 | "\n", 68 | "The callback function will set the received_trajectory variable when the user clicks the button \"Submit\"" 69 | ] 70 | }, 71 | { 72 | "cell_type": "code", 73 | "execution_count": null, 74 | "metadata": {}, 75 | "outputs": [], 76 | "source": [ 77 | "received_trajectory = None\n", 78 | "\n", 79 | "def receive_trajectory(data):\n", 80 | " global received_trajectory \n", 81 | " received_trajectory = data['trajectory']\n", 82 | " return {\"received\": True}\n", 83 | " \n", 84 | "callbacks = {'submit_trajectory': receive_trajectory}" 85 | ] 86 | }, 87 | { 88 | "cell_type": "markdown", 89 | "metadata": {}, 90 | "source": [ 91 | "In javascript, the callback call is done in BaseballVisualizer/js/TrajectoryAnnotator.js\n", 92 | "\n", 93 | "```Javascript\n", 94 | "function submitTrajectoryToServer(tracking){\n", 95 | " const alert_sent = ()=>{alert(\"Trajectory sent to Jupyter Notebook.\")};\n", 96 | " let comm = new CommAPI(\"submit_trajectory\", alert_sent)\n", 97 | "\n", 98 | " // Send data\n", 99 | " comm.call({'trajectory': tracking})\n", 100 | "}\n", 101 | "```" 102 | ] 103 | }, 104 | { 105 | "cell_type": "markdown", 106 | "metadata": {}, 107 | "source": [ 108 | "### Rendering Visualization" 109 | ] 110 | }, 111 | { 112 | "cell_type": "code", 113 | "execution_count": null, 114 | "metadata": {}, 115 | "outputs": [], 116 | "source": [ 117 | "from notebookjs import execute_js\n", 118 | "\n", 119 | "execute_js(library_list=code_bundle, \n", 120 | " main_function=\"baseballvisualizer.renderBaseballAnnotator\", \n", 121 | " data_dict=data_dict, \n", 122 | " callbacks=callbacks)" 123 | ] 124 | }, 125 | { 126 | "cell_type": "code", 127 | "execution_count": null, 128 | "metadata": {}, 129 | "outputs": [], 130 | "source": [ 131 | "received_trajectory" 132 | ] 133 | }, 134 | { 135 | "cell_type": "code", 136 | "execution_count": null, 137 | "metadata": {}, 138 | "outputs": [], 139 | "source": [] 140 | } 141 | ], 142 | "metadata": { 143 | "kernelspec": { 144 | "display_name": "Python 3", 145 | "language": "python", 146 | "name": "python3" 147 | }, 148 | "language_info": { 149 | "codemirror_mode": { 150 | "name": "ipython", 151 | "version": 3 152 | }, 153 | "file_extension": ".py", 154 | "mimetype": "text/x-python", 155 | "name": "python", 156 | "nbconvert_exporter": "python", 157 | "pygments_lexer": "ipython3", 158 | "version": "3.8.5" 159 | } 160 | }, 161 | "nbformat": 4, 162 | "nbformat_minor": 4 163 | } 164 | -------------------------------------------------------------------------------- /notebookjs/_display.py: -------------------------------------------------------------------------------- 1 | from IPython.core.display import display, HTML, Javascript 2 | from string import Template, ascii_uppercase 3 | import pkg_resources 4 | import random 5 | import re 6 | import json 7 | from ._comm import setup_comm_api 8 | 9 | def id_generator(size=15): 10 | """Helper function to generate random div ids.""" 11 | chars = list(ascii_uppercase) 12 | return ''.join(random.choice(chars) for i in range(size)) 13 | 14 | def make_html(library_list, main_function, parameter_dict, css_list): 15 | """Makes the HTML that will be added to the Notebook""" 16 | # Loading Python CommAPI 17 | comm_api_path = pkg_resources.resource_filename(__name__, "resources/CommAPI.js") 18 | with open(comm_api_path, "r") as f: 19 | comm_api_js = f.read() 20 | 21 | # Making sure library_list and css_list are lists. 22 | if type(library_list) is not list: 23 | library_list = [library_list] 24 | if type(css_list) is not list: 25 | css_list = [css_list] 26 | 27 | # Downloading web resources 28 | for idx in range(len(library_list)): 29 | if check_url(library_list[idx]): 30 | library_list[idx] = download_url(library_list[idx]) 31 | for idx in range(len(css_list)): 32 | if check_url(css_list[idx]): 33 | css_list[idx] = download_url(css_list[idx]) 34 | 35 | # Adding CommAPI to library_list 36 | library_list.insert(0, comm_api_js) 37 | 38 | # Generating HTML 39 | div_id = id_generator() 40 | library_bundle = '\n\n'.join(library_list) 41 | css_bundle = '\n'.join(css_list) 42 | template_path = pkg_resources.resource_filename(__name__, "resources/template.html") 43 | with open(template_path, "r") as f: 44 | html_all_template = f.read() 45 | html_all_template = Template(html_all_template) 46 | 47 | return html_all_template.substitute(div_id=div_id, 48 | library_bundle=library_bundle, 49 | main_function=main_function, 50 | parameter_dict=json.dumps(parameter_dict), 51 | css_bundle=css_bundle) 52 | 53 | # Regex expression to test if a string is a URL 54 | regex_url = re.compile( 55 | r'^(?:http|ftp)s?://' # http:// or https:// 56 | r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' #domain... 57 | r'localhost|' #localhost... 58 | r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip 59 | r'(?::\d+)?' # optional port 60 | r'(?:/?|[/?]\S+)$', re.IGNORECASE) 61 | 62 | def check_url(string): 63 | """Checks if the string argument is a URL""" 64 | return re.match(regex_url, string) is not None 65 | 66 | def download_url(url): 67 | """Downloads a URL file as a browser.""" 68 | import requests 69 | headers = {'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0'} 70 | r = requests.get(url, headers=headers, stream=False) 71 | return r.content.decode("utf-8") 72 | 73 | 74 | 75 | def save_html(html_dest, library_list, main_function, data_dict = {}, callbacks = None, css_list=[]): 76 | """Saves the bundled code (output of execute_js) to an HTML file 77 | 78 | Parameters 79 | ---------- 80 | html_dest : str 81 | Path to the output HTML dest file. Example: "./output.html" 82 | library_list : list of str 83 | List of strings containing either 1) URL to a javascript library, 2) javascript code 84 | main_function : str 85 | Name of the main function to be called. The function will be called with two parameters: 86 | , for example "#my_div", and . 87 | data_dict : dict 88 | Dictionary containing the data to be passed to 89 | callbacks : dict 90 | Dictionary of the form { : }. The javascript library can 91 | use callbacks to talk to python. 92 | css_list : list of str 93 | List of strings containing either 1) URL to a CSS stylesheet or 2) CSS styles 94 | """ 95 | if callbacks is not None: 96 | print ("Warning: Python callbacks do not work in standalone HTML file.") 97 | print ("Saving file...") 98 | 99 | html_all = make_html(library_list, main_function, data_dict, css_list) 100 | with open(html_dest, "w") as f: 101 | f.write(html_all) 102 | 103 | def execute_js(library_list, main_function, data_dict = {}, callbacks = {}, css_list=[]): 104 | """Executes a javascript function that can add content to an output div 105 | 106 | Parameters 107 | ---------- 108 | library_list : list of str 109 | List of strings containing either 1) URL to a javascript library, 2) javascript code 110 | main_function : str 111 | Name of the main function to be called. The function will be called with two parameters: 112 | , for example "#my_div", and . 113 | data_dict : dict 114 | Dictionary containing the data to be passed to 115 | callbacks : dict 116 | Dictionary of the form { : }. The javascript library can 117 | use callbacks to talk to python. 118 | css_list : list of str 119 | List of strings containing either 1) URL to a CSS stylesheet or 2) CSS styles 120 | """ 121 | html_all = make_html(library_list, main_function, data_dict, css_list) 122 | for callback_id in callbacks.keys(): 123 | setup_comm_api(callback_id, callbacks[callback_id]) 124 | display(HTML(html_all)) 125 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # notebookJS: seamless JavaScript integration in Python Notebooks 2 | 3 | [![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg)](https://www.python.org/) 4 | [![Open In Collab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/drive/1g8WOn9oZ5G_3-Y8DYmpV1MIj59dnd81u?usp=sharing) 5 | [![PyPI version](https://badge.fury.io/py/notebookjs.svg)](https://pypi.org/project/notebookjs) 6 | 7 | 8 | 14 | 15 | *notebookJS* enables the execution of custom JavaScript code in Python Notebooks (Jupyter Notebook and Google Colab). This Python library can be useful for implementing and reusing interactive Data Visualizations in the Notebook environment. 16 | 17 | *notebookJS* takes care of downloading and handling Javascript libraries and CSS stylesheets from the web. Furthermore, it supports bidirectional communication between Python and JavaScript. User interactions in HTML/JavaScript can trigger Python callbacks that process data on demand and send the results back to the front-end code. 18 | 19 | Implementation details in [our paper](https://ieeexplore.ieee.org/iel7/5992/9387473/09391750.pdf?casa_token=v05VFeWM3gwAAAAA:ra4uhd2Xpsd6lllS62Woz1IOjaSOZJGGhh4jpF_ZXOlm1Kq4HTBFHJU7Z-Ez6DDQOUE_djlI5Gk). 20 | 21 | See our [blog post](https://jorgehpo.medium.com/introducing-notebookjs-seamless-integration-between-python-and-javascript-in-computational-e654ec3fbd18). 22 | 23 | [![ScatterPlot](https://user-images.githubusercontent.com/14821895/114492279-478ae380-9be6-11eb-8750-30ec3a206816.gif)](https://github.com/jorgehpo/notebookJS/tree/main/Examples/7_D3_scatterplot) 24 | 25 | 26 | ## Install 27 | 28 | To install, run: 29 | `pip install notebookjs` 30 | 31 | Or clone this repository and run: 32 | `python setup.py install` 33 | 34 | ## API 35 | 36 | ### execute_js 37 | This method executes a javascript function and sets up the infrastructure for bidirectional communication between Python and Javascript using callbacks. 38 | 39 | ```python 40 | execute_js( 41 | library_list, 42 | main_function, 43 | data_dict={}, 44 | callbacks={}, 45 | css_list=[], 46 | ) 47 | ``` 48 | 49 | **Parameters** 50 | 51 | - library_list : list of str. 52 | List of strings containing either 1) URL to a javascript library, 2) javascript code, 3) javascript [bundle](https://github.com/jorgehpo/notebookJS/tree/main/Examples/5_Webpack_BaseballAnnotator_Bidirectional) (Plain JS only - No support for ES6 Modules) 53 | - main_function : str. 54 | Name of the main function to be called. The function will be called with two parameters: 55 | , for example "#my_div", and . 56 | - data_dict : dict. 57 | Dictionary containing the data to be passed to 58 | - callbacks : dict. 59 | Dictionary of the form { : }. The javascript library can 60 | use callbacks to talk to python. 61 | - css_list : list of str. 62 | List of strings containing either 1) URL to a CSS stylesheet or 2) CSS styles 63 | 64 | **Main Function** 65 | 66 | *main_function* is the javascript function that will be run when execute_js is called. It has the following signature: 67 | ```Javascript 68 | function main_function(div_id, data_dict) 69 | ``` 70 | 71 | **Example of Main Function** 72 | 73 | As a simple example, we can use D3 to add a circular div to the output cell: 74 | 75 | ```Javascript 76 | function draw_circle(div_id, data){ 77 | // Function that draws a circle of color inside the div using D3 78 | d3.select(div_id) 79 | .append("div") 80 | .style("width", "50px") 81 | .style("height", "50px") 82 | .style("background-color", data.color) 83 | .style("border-radius", "50px") 84 | } 85 | ``` 86 | 87 | **Callbacks** 88 | 89 | *callbacks* contains a dictionary that maps an identifier string to a Python function. Data is passed to/from callbacks using json/dicts. 90 | 91 | For example, the following callback computes the number to the power of 2. 92 | 93 | ``` Python 94 | def compute_power_2(data){ 95 | n = data.n 96 | n2 = n**2 97 | return {"power2": n2} 98 | } 99 | 100 | callbacks = { 101 | "compute_power_2": compute_power_2 102 | } 103 | 104 | execute_js(..., callbacks=callbacks) 105 | ``` 106 | 107 | In Javascript, we can call this callback with the class *CommAPI*. *CommAPI* is automatically injected in the Javascript by *notebookJS*. 108 | 109 | ``` Javascript 110 | let comm = new CommAPI("compute_power_2", (ret)=>{alert("The returned value is " + ret.power2)}) 111 | 112 | comm.call({n: 3}) 113 | // An alert will be shown with the message: "The returned value is 9" 114 | ``` 115 | 116 | Jupyter Notebook and Google Colab have different APIs for sending data to/from Javascript/Python. *CommAPI* abstracts the different APIs in a single convenient class. 117 | 118 | **Warning**: Callbacks between Python and JS are only available in Jupyter and Colab notebooks. Jupyter Lab is not supported currently. 119 | 120 | ### save_html 121 | This method creates a standalone HTML bundle (containing all data, JS and CSS resources) and saves it to disk. It accepts all parameters of execute_js, with the addition of *html_dest*, the path to the output file. For example, *html_dest="./output.html"* 122 | 123 | ```python 124 | save_html(, 125 | html_dest, 126 | library_list, 127 | main_function, 128 | data_dict={}, 129 | callbacks=None, 130 | css_list=[], 131 | ) 132 | ``` 133 | 134 | **Warning:** callbacks do not work in standalone HTML files. This parameter only exists to make *execute_js* and *save_html* interoperable. 135 | 136 | ## Examples 137 | 138 | ### Hello World - Python Callbacks 139 | 140 | In this example, we show how to display "hello world" in multiple languages using Javascript and Python. The Javascript is responsible for updating the front end and requesting a new message from Python. Python returns a random message every time the callback is invoked. 141 | 142 | ![Hello World Output Gif](https://user-images.githubusercontent.com/14821895/114482788-2d94d500-9bd5-11eb-9ec3-7ee5c5d62a86.gif) 143 | 144 | **Javascript to update the div with a hello world message** 145 | ```Python 146 | helloworld_js = """ 147 | function helloworld(div_id, data){ 148 | comm = new CommAPI("get_hello", (ret) => { 149 | document.querySelector(div_id).textContent = ret.text; 150 | }); 151 | setInterval(() => {comm.call({})}, 1000); 152 | comm.call({}); 153 | } 154 | """ 155 | ``` 156 | 157 | **Defining the Python Callback** 158 | ```Python 159 | import random 160 | def hello_world_random(data): 161 | hello_world_languages = [ 162 | "Ola Mundo", # Portuguese 163 | "Hello World", # English 164 | "Hola Mundo", # Spanish 165 | "Geiá sou Kósme", # Greek 166 | "Kon'nichiwa sekai", # Japanese 167 | "Hallo Welt", # German 168 | "Namaste duniya", # Hindi 169 | "Ni hao, shijiè" # Chinese 170 | ] 171 | i = random.randint(0, len(hello_world_languages)-1) 172 | return {'text': hello_world_languages[i]} 173 | ``` 174 | 175 | **Invoking the function helloworld in notebook** 176 | ```Python 177 | from notebookjs import execute_js 178 | execute_js(helloworld_js, "helloworld", callbacks={"get_hello": hello_world_random}) 179 | ``` 180 | 181 | See this [colab notebook](https://colab.research.google.com/drive/1g8WOn9oZ5G_3-Y8DYmpV1MIj59dnd81u?usp=sharing) for a live demo. 182 | 183 | ### Radial Bar Chart - Running D3 code in the Notebook 184 | 185 | Plotting a Radial Bar Chart with data loaded from Python. Adapted from this [bl.ock](https://bl.ocks.org/AntonOrlov/6b42d8676943cc933f48a43a7c7e5b6c). See [Examples/3_RadialBarChart](https://github.com/jorgehpo/notebookJS/blob/main/Examples/3_RadialBarChart/). 186 | 187 | ```Python 188 | # Loading libraries 189 | d3_lib_url = "https://d3js.org/d3.v3.min.js" 190 | 191 | with open("radial_bar.css", "r") as f: 192 | radial_bar_css = f.read() 193 | 194 | with open ("radial_bar_lib.js", "r") as f: 195 | radial_bar_lib = f.read() 196 | 197 | # Loading data 198 | import pandas as pd 199 | energy = pd.read_csv("energy.csv") 200 | 201 | # Plotting the Radial Bar Chart 202 | from notebookjs import execute_js 203 | execute_js(library_list=[d3_lib_url, radial_bar_lib], main_function="radial_bar", 204 | data_dict=energy.to_dict(orient="records"), css_list=[radial_bar_css]) 205 | ``` 206 | 207 | ![Radial Bar Chart](https://user-images.githubusercontent.com/14821895/114483438-536ea980-9bd6-11eb-8502-77f7a8864322.gif) 208 | 209 | ### More examples 210 | 211 | Please see the [Examples/](https://github.com/jorgehpo/notebookJS/blob/main/Examples/) folder for more examples. 212 | 213 | ## Reference 214 | 215 | If you use *notebookJS*, please reference our related work: 216 | 217 | "*Interactive Data Visualization in Jupyter Notebooks*. JP Ono, J Freire, CT Silva - Computing in Science & Engineering, 2021" 218 | 219 | Bibtex: 220 | ``` 221 | @article{ono2021interactive, 222 | title={Interactive Data Visualization in Jupyter Notebooks}, 223 | author={Ono, Jorge Piazentin and Freire, Juliana and Silva, Claudio T}, 224 | journal={Computing in Science \& Engineering}, 225 | volume={23}, 226 | number={2}, 227 | pages={99--106}, 228 | year={2021}, 229 | publisher={IEEE} 230 | } 231 | ``` 232 | -------------------------------------------------------------------------------- /Examples/7_D3_scatterplot/Prices.csv: -------------------------------------------------------------------------------- 1 | GrLivArea,SalePrice 2 | 1710,208500 3 | 1262,181500 4 | 1786,223500 5 | 1717,140000 6 | 2198,250000 7 | 1362,143000 8 | 1694,307000 9 | 2090,200000 10 | 1774,129900 11 | 1077,118000 12 | 1040,129500 13 | 2324,345000 14 | 912,144000 15 | 1494,279500 16 | 1253,157000 17 | 854,132000 18 | 1004,149000 19 | 1296,90000 20 | 1114,159000 21 | 1339,139000 22 | 2376,325300 23 | 1108,139400 24 | 1795,230000 25 | 1060,129900 26 | 1060,154000 27 | 1600,256300 28 | 900,134800 29 | 1704,306000 30 | 1600,207500 31 | 520,68500 32 | 1317,40000 33 | 1228,149350 34 | 1234,179900 35 | 1700,165500 36 | 1561,277500 37 | 2452,309000 38 | 1097,145000 39 | 1297,153000 40 | 1057,109000 41 | 1152,82000 42 | 1324,160000 43 | 1328,170000 44 | 884,144000 45 | 938,130250 46 | 1150,141000 47 | 1752,319900 48 | 2149,239686 49 | 1656,249700 50 | 1452,113000 51 | 955,127000 52 | 1470,177000 53 | 1176,114500 54 | 816,110000 55 | 1842,385000 56 | 1360,130000 57 | 1425,180500 58 | 1739,172500 59 | 1720,196500 60 | 2945,438780 61 | 780,124900 62 | 1158,158000 63 | 1111,101000 64 | 1370,202500 65 | 1710,140000 66 | 2034,219500 67 | 2473,317000 68 | 2207,180000 69 | 1479,226000 70 | 747,80000 71 | 2287,225000 72 | 2223,244000 73 | 845,129500 74 | 1718,185000 75 | 1086,144900 76 | 1605,107400 77 | 988,91000 78 | 952,135750 79 | 1285,127000 80 | 1768,136500 81 | 1230,110000 82 | 2142,193500 83 | 1337,153500 84 | 1563,245000 85 | 1065,126500 86 | 1474,168500 87 | 2417,260000 88 | 1560,174000 89 | 1224,164500 90 | 1526,85000 91 | 990,123600 92 | 1040,109900 93 | 1235,98600 94 | 964,163500 95 | 2291,133900 96 | 1786,204750 97 | 1470,185000 98 | 1588,214000 99 | 960,94750 100 | 835,83000 101 | 1225,128950 102 | 1610,205000 103 | 1732,178000 104 | 1535,118964 105 | 1226,198900 106 | 1818,169500 107 | 1992,250000 108 | 1047,100000 109 | 789,115000 110 | 1517,115000 111 | 1844,190000 112 | 1855,136900 113 | 1430,180000 114 | 2696,383970 115 | 2259,217000 116 | 2320,259500 117 | 1458,176000 118 | 1092,139000 119 | 1125,155000 120 | 3222,320000 121 | 1456,163990 122 | 988,180000 123 | 1123,100000 124 | 1080,136000 125 | 1199,153900 126 | 1586,181000 127 | 754,84500 128 | 958,128000 129 | 840,87000 130 | 1348,155000 131 | 1053,150000 132 | 2157,226000 133 | 2054,244000 134 | 1327,150750 135 | 1296,220000 136 | 1721,180000 137 | 1682,174000 138 | 1214,143000 139 | 1959,171000 140 | 1852,230000 141 | 1764,231500 142 | 864,115000 143 | 1734,260000 144 | 1385,166000 145 | 1501,204000 146 | 1728,125000 147 | 1709,130000 148 | 875,105000 149 | 2035,222500 150 | 1080,141000 151 | 1344,115000 152 | 969,122000 153 | 1710,372402 154 | 1993,190000 155 | 1252,235000 156 | 1200,125000 157 | 1096,79000 158 | 1040,109500 159 | 1968,269500 160 | 1947,254900 161 | 2462,320000 162 | 1232,162500 163 | 2668,412500 164 | 1541,220000 165 | 882,103200 166 | 1616,152000 167 | 1355,127500 168 | 1867,190000 169 | 2161,325624 170 | 1720,183500 171 | 1707,228000 172 | 1382,128500 173 | 1656,215000 174 | 1767,239000 175 | 1362,163000 176 | 1651,184000 177 | 2158,243000 178 | 2060,211000 179 | 1920,172500 180 | 2234,501837 181 | 968,100000 182 | 1525,177000 183 | 1802,200100 184 | 1340,120000 185 | 2082,200000 186 | 1252,127000 187 | 3608,475000 188 | 1217,173000 189 | 1656,135000 190 | 1224,153337 191 | 1593,286000 192 | 2727,315000 193 | 1479,184000 194 | 1431,192000 195 | 1709,130000 196 | 864,127000 197 | 1456,148500 198 | 1726,311872 199 | 3112,235000 200 | 2229,104000 201 | 1713,274900 202 | 1121,140000 203 | 1279,171500 204 | 1310,112000 205 | 848,149000 206 | 1284,110000 207 | 1442,180500 208 | 1696,143900 209 | 1100,141000 210 | 2062,277000 211 | 1092,145000 212 | 864,98000 213 | 1212,186000 214 | 1852,252678 215 | 990,156000 216 | 1392,161750 217 | 1236,134450 218 | 1436,210000 219 | 1328,107000 220 | 1954,311500 221 | 1248,167240 222 | 1498,204900 223 | 2267,200000 224 | 1552,179900 225 | 864,97000 226 | 2392,386250 227 | 1302,112000 228 | 2520,290000 229 | 987,106000 230 | 912,125000 231 | 1555,192500 232 | 1194,148000 233 | 2794,403000 234 | 987,94500 235 | 894,128200 236 | 1960,216500 237 | 987,89500 238 | 1414,185500 239 | 1744,194500 240 | 1694,318000 241 | 1487,113000 242 | 1566,262500 243 | 866,110500 244 | 1440,79000 245 | 1217,120000 246 | 2110,205000 247 | 1872,241500 248 | 1928,137000 249 | 1375,140000 250 | 1668,180000 251 | 2144,277000 252 | 1306,76500 253 | 1625,235000 254 | 1640,173000 255 | 1302,158000 256 | 1314,145000 257 | 2291,230000 258 | 1728,207500 259 | 1604,220000 260 | 1792,231500 261 | 882,97000 262 | 1382,176000 263 | 2574,276000 264 | 1212,151000 265 | 1316,130000 266 | 764,73000 267 | 1422,175500 268 | 1511,185000 269 | 2192,179500 270 | 778,120500 271 | 1113,148000 272 | 1939,266000 273 | 1363,241500 274 | 2270,290000 275 | 1632,139000 276 | 816,124500 277 | 1548,205000 278 | 1560,201000 279 | 864,141000 280 | 2121,415298 281 | 2022,192000 282 | 1982,228500 283 | 1262,185000 284 | 1314,207500 285 | 1468,244600 286 | 1575,179200 287 | 1250,164700 288 | 1734,159000 289 | 858,88000 290 | 900,122000 291 | 1396,153575 292 | 1919,233230 293 | 1716,135900 294 | 1716,131000 295 | 2263,235000 296 | 1644,167000 297 | 1003,142500 298 | 1558,152000 299 | 1950,239000 300 | 1743,175000 301 | 1152,158500 302 | 1336,157000 303 | 2452,267000 304 | 1541,205000 305 | 894,149900 306 | 3493,295000 307 | 2000,305900 308 | 2243,225000 309 | 1406,89500 310 | 861,82500 311 | 1944,360000 312 | 1501,165600 313 | 972,132000 314 | 1118,119900 315 | 2036,375000 316 | 1641,178000 317 | 1432,188500 318 | 2353,260000 319 | 1959,270000 320 | 2646,260000 321 | 1472,187500 322 | 2596,342643 323 | 2468,354000 324 | 2730,301000 325 | 1163,126175 326 | 2978,242000 327 | 803,87000 328 | 1719,324000 329 | 1383,145250 330 | 2134,214500 331 | 1192,78000 332 | 1728,119000 333 | 1056,139000 334 | 1629,284000 335 | 1358,207000 336 | 1638,192000 337 | 1786,228950 338 | 1922,377426 339 | 1536,214000 340 | 1621,202500 341 | 1215,155000 342 | 1908,202900 343 | 841,82000 344 | 1040,87500 345 | 1684,266000 346 | 1112,85000 347 | 1577,140200 348 | 958,151500 349 | 1478,157500 350 | 1626,154000 351 | 2728,437154 352 | 1869,318061 353 | 1453,190000 354 | 1111,95000 355 | 720,105900 356 | 1595,140000 357 | 1200,177500 358 | 1167,173000 359 | 1142,134000 360 | 1352,130000 361 | 1924,280000 362 | 912,156000 363 | 1505,145000 364 | 1922,198500 365 | 987,118000 366 | 1574,190000 367 | 1344,147000 368 | 1394,159000 369 | 1431,165000 370 | 1268,132000 371 | 1287,162000 372 | 1664,172400 373 | 1588,134432 374 | 752,125000 375 | 1319,123000 376 | 1928,219500 377 | 904,61000 378 | 914,148000 379 | 2466,340000 380 | 1856,394432 381 | 1800,179000 382 | 1691,127000 383 | 1301,187750 384 | 1797,213500 385 | 784,76000 386 | 1953,240000 387 | 1269,192000 388 | 1184,81000 389 | 1125,125000 390 | 1479,191000 391 | 2332,426000 392 | 1367,119000 393 | 1961,215000 394 | 882,106500 395 | 788,100000 396 | 1034,109000 397 | 1144,129000 398 | 894,123000 399 | 1812,169500 400 | 1077,67000 401 | 1550,241000 402 | 1288,245500 403 | 1310,164990 404 | 672,108000 405 | 2263,258000 406 | 1572,168000 407 | 1620,150000 408 | 1639,115000 409 | 1680,177000 410 | 2172,280000 411 | 2078,339750 412 | 1276,60000 413 | 1056,145000 414 | 1478,222000 415 | 1028,115000 416 | 2097,228000 417 | 1340,181134 418 | 1400,149500 419 | 2624,239000 420 | 1134,126000 421 | 1056,142000 422 | 1344,206300 423 | 1602,215000 424 | 988,113000 425 | 2630,315000 426 | 1196,139000 427 | 1389,135000 428 | 1644,275000 429 | 907,109008 430 | 1208,195400 431 | 1412,175000 432 | 987,85400 433 | 1198,79900 434 | 1365,122500 435 | 1604,181000 436 | 630,81000 437 | 1661,212000 438 | 1118,116000 439 | 904,119000 440 | 694,90350 441 | 1196,110000 442 | 2402,555000 443 | 1440,118000 444 | 1573,162900 445 | 1258,172500 446 | 1908,210000 447 | 1689,127500 448 | 1888,190000 449 | 1886,199900 450 | 1376,119500 451 | 1183,120000 452 | 813,110000 453 | 1533,280000 454 | 1756,204000 455 | 1590,210000 456 | 1728,188000 457 | 1242,175500 458 | 1344,98000 459 | 1663,256000 460 | 1666,161000 461 | 1203,110000 462 | 1935,263435 463 | 1135,155000 464 | 864,62383 465 | 1660,188700 466 | 1040,124000 467 | 1414,178740 468 | 1277,167000 469 | 1644,146500 470 | 1634,250000 471 | 1710,187000 472 | 1502,212000 473 | 1969,190000 474 | 1072,148000 475 | 1976,440000 476 | 1652,251000 477 | 970,132500 478 | 1493,208900 479 | 2643,380000 480 | 1718,297000 481 | 1131,89471 482 | 1850,326000 483 | 1792,374000 484 | 1826,155000 485 | 1216,164000 486 | 999,132500 487 | 1113,147000 488 | 1073,156000 489 | 1484,175000 490 | 2414,160000 491 | 630,86000 492 | 1304,115000 493 | 1578,133000 494 | 1456,172785 495 | 1269,155000 496 | 886,91300 497 | 720,34900 498 | 3228,430000 499 | 1820,184000 500 | 899,130000 501 | 912,120000 502 | 1218,113000 503 | 1768,226700 504 | 1214,140000 505 | 1801,289000 506 | 1322,147000 507 | 1960,124500 508 | 1911,215000 509 | 1218,208300 510 | 1378,161000 511 | 1041,124500 512 | 1363,164900 513 | 1368,202665 514 | 864,129900 515 | 1080,134000 516 | 789,96500 517 | 2020,402861 518 | 2119,158000 519 | 2344,265000 520 | 1796,211000 521 | 2080,234000 522 | 1294,106250 523 | 1244,150000 524 | 1664,159000 525 | 4676,184750 526 | 2398,315750 527 | 1266,176000 528 | 928,132000 529 | 2713,446261 530 | 605,86000 531 | 2515,200624 532 | 1509,175000 533 | 1362,128000 534 | 827,107500 535 | 334,39300 536 | 1414,178000 537 | 1347,107500 538 | 1724,188000 539 | 864,111250 540 | 1159,158000 541 | 1601,272000 542 | 1838,315000 543 | 2285,248000 544 | 1680,213250 545 | 767,133000 546 | 1496,179665 547 | 2183,229000 548 | 1635,210000 549 | 768,129500 550 | 825,125000 551 | 2094,263000 552 | 1069,140000 553 | 928,112500 554 | 1717,255500 555 | 1126,108000 556 | 2046,284000 557 | 1048,113000 558 | 1092,141000 559 | 1336,108000 560 | 1446,175000 561 | 1557,234000 562 | 1392,121500 563 | 1389,170000 564 | 996,108000 565 | 1674,185000 566 | 2295,268000 567 | 1647,128000 568 | 2504,325000 569 | 1535,214000 570 | 2132,316600 571 | 943,135960 572 | 1728,142600 573 | 864,120000 574 | 1692,224500 575 | 1430,170000 576 | 1109,139000 577 | 1216,118500 578 | 1477,145000 579 | 1320,164500 580 | 1392,146000 581 | 1795,131500 582 | 1429,181900 583 | 2042,253293 584 | 816,118500 585 | 2775,325000 586 | 1573,133000 587 | 2028,369900 588 | 838,130000 589 | 860,137000 590 | 1473,143000 591 | 935,79500 592 | 1582,185900 593 | 2296,451950 594 | 816,138000 595 | 848,140000 596 | 924,110000 597 | 1826,319000 598 | 1368,114504 599 | 1402,194201 600 | 1647,217500 601 | 1556,151000 602 | 1904,275000 603 | 1375,141000 604 | 1915,220000 605 | 1200,151000 606 | 1494,221000 607 | 1986,205000 608 | 1040,152000 609 | 2008,225000 610 | 3194,359100 611 | 1029,118500 612 | 2153,313000 613 | 1032,148000 614 | 1872,261500 615 | 1120,147000 616 | 630,75500 617 | 1054,137500 618 | 1509,183200 619 | 832,105500 620 | 1828,314813 621 | 2262,305000 622 | 864,67000 623 | 2614,240000 624 | 980,135000 625 | 1512,168500 626 | 1790,165150 627 | 1116,160000 628 | 1422,139900 629 | 1520,153000 630 | 2080,135000 631 | 1350,168500 632 | 1750,124000 633 | 1554,209500 634 | 1411,82500 635 | 1056,139400 636 | 1056,144000 637 | 3395,200000 638 | 800,60000 639 | 1387,93000 640 | 796,85000 641 | 1567,264561 642 | 1518,274000 643 | 1929,226000 644 | 2704,345000 645 | 1620,152000 646 | 1766,370878 647 | 981,143250 648 | 1048,98300 649 | 1094,155000 650 | 1839,155000 651 | 630,84500 652 | 1665,205950 653 | 1510,108000 654 | 1716,191000 655 | 1469,135000 656 | 2113,350000 657 | 1092,88000 658 | 1053,145500 659 | 1502,149000 660 | 1458,97500 661 | 1486,167000 662 | 1935,197900 663 | 2448,402000 664 | 1392,110000 665 | 1181,137500 666 | 2097,423000 667 | 1936,230500 668 | 2380,129000 669 | 1679,193500 670 | 1437,168000 671 | 1180,137500 672 | 1476,173500 673 | 1369,103600 674 | 1208,165000 675 | 1839,257500 676 | 1136,140000 677 | 1441,148500 678 | 1774,87000 679 | 792,109500 680 | 2046,372500 681 | 988,128500 682 | 923,143000 683 | 1520,159434 684 | 1291,173000 685 | 1668,285000 686 | 1839,221000 687 | 2090,207500 688 | 1761,227875 689 | 1102,148800 690 | 1419,392000 691 | 1362,194700 692 | 848,141000 693 | 4316,755000 694 | 2519,335000 695 | 1073,108480 696 | 1539,141500 697 | 1137,176000 698 | 616,89000 699 | 1148,123500 700 | 894,138500 701 | 1391,196000 702 | 1800,312500 703 | 1164,140000 704 | 2576,361919 705 | 1812,140000 706 | 1484,213000 707 | 1092,55000 708 | 1824,302000 709 | 1324,254000 710 | 1456,179540 711 | 904,109900 712 | 729,52000 713 | 1178,102776 714 | 1228,189000 715 | 960,129000 716 | 1479,130500 717 | 1350,165000 718 | 2554,159500 719 | 1178,157000 720 | 2418,341000 721 | 971,128500 722 | 1742,275000 723 | 848,143000 724 | 864,124500 725 | 1470,135000 726 | 1698,320000 727 | 864,120500 728 | 1680,222000 729 | 1232,194500 730 | 1776,110000 731 | 1208,103000 732 | 1616,236500 733 | 1146,187500 734 | 2031,222500 735 | 1144,131400 736 | 948,108000 737 | 1768,163000 738 | 1040,93500 739 | 1801,239900 740 | 1200,179000 741 | 1728,190000 742 | 1432,132000 743 | 912,142000 744 | 1349,179000 745 | 1464,175000 746 | 1337,180000 747 | 2715,299800 748 | 2256,236000 749 | 2640,265979 750 | 1720,260400 751 | 1529,98000 752 | 1140,96500 753 | 1320,162000 754 | 1494,217000 755 | 2098,275500 756 | 1026,156000 757 | 1471,172500 758 | 1768,212000 759 | 1386,158900 760 | 1501,179400 761 | 2531,290000 762 | 864,127500 763 | 1301,100000 764 | 1547,215200 765 | 2365,337000 766 | 1494,270000 767 | 1506,264132 768 | 1714,196500 769 | 1750,160000 770 | 1836,216837 771 | 3279,538000 772 | 858,134900 773 | 1220,102000 774 | 1117,107000 775 | 912,114500 776 | 1973,395000 777 | 1204,162000 778 | 1614,221500 779 | 894,142500 780 | 2020,144000 781 | 1004,135000 782 | 1253,176000 783 | 1603,175900 784 | 1430,187100 785 | 1110,165500 786 | 1484,128000 787 | 1342,161500 788 | 1652,139000 789 | 2084,233000 790 | 901,107900 791 | 2087,187500 792 | 1145,160200 793 | 1062,146800 794 | 2013,269790 795 | 1496,225000 796 | 1895,194500 797 | 1564,171000 798 | 1285,143500 799 | 773,110000 800 | 3140,485000 801 | 1768,175000 802 | 1688,200000 803 | 1196,109900 804 | 1456,189000 805 | 2822,582933 806 | 1128,118000 807 | 1428,227680 808 | 980,135500 809 | 1576,223500 810 | 1086,159950 811 | 2138,106000 812 | 1309,181000 813 | 848,144500 814 | 1044,55993 815 | 1442,157900 816 | 1250,116000 817 | 1661,224900 818 | 1008,137000 819 | 1689,271000 820 | 1052,155000 821 | 1358,224000 822 | 1640,183000 823 | 936,93000 824 | 1733,225000 825 | 1489,139500 826 | 1489,232600 827 | 2084,385000 828 | 784,109500 829 | 1434,189000 830 | 2126,185000 831 | 1223,147400 832 | 1392,166000 833 | 1200,151000 834 | 1829,237000 835 | 1516,167000 836 | 1144,139950 837 | 1067,128000 838 | 1559,153500 839 | 987,100000 840 | 1099,144000 841 | 1200,130500 842 | 1482,140000 843 | 1539,157500 844 | 1165,174900 845 | 1800,141000 846 | 1416,153900 847 | 1701,171000 848 | 1775,213000 849 | 864,133500 850 | 2358,240000 851 | 1855,187000 852 | 848,131500 853 | 1456,215000 854 | 1646,164000 855 | 1445,158000 856 | 1779,170000 857 | 1040,127000 858 | 1026,147000 859 | 1481,174000 860 | 1370,152000 861 | 2654,250000 862 | 1426,189950 863 | 1039,131500 864 | 1097,152000 865 | 1148,132500 866 | 1372,250580 867 | 1002,148500 868 | 1646,248900 869 | 1120,129000 870 | 2320,169000 871 | 1949,236000 872 | 894,109500 873 | 1682,200500 874 | 910,116000 875 | 1268,133000 876 | 1131,66500 877 | 2610,303477 878 | 1040,132250 879 | 2224,350000 880 | 1155,148000 881 | 864,136500 882 | 1090,157000 883 | 1717,187500 884 | 1593,178000 885 | 2230,118500 886 | 892,100000 887 | 1709,328900 888 | 1712,145000 889 | 1393,135500 890 | 2217,268000 891 | 1505,149500 892 | 924,122900 893 | 1683,172500 894 | 1068,154500 895 | 1383,165000 896 | 1535,118858 897 | 1796,140000 898 | 951,106500 899 | 2240,142953 900 | 2364,611657 901 | 1236,135000 902 | 858,110000 903 | 1306,153000 904 | 1509,180000 905 | 1670,240000 906 | 902,125500 907 | 1063,128000 908 | 1636,255000 909 | 2057,250000 910 | 902,131000 911 | 1484,174000 912 | 2274,154300 913 | 1268,143500 914 | 1015,88000 915 | 2002,145000 916 | 1224,173733 917 | 1092,75000 918 | 480,35311 919 | 1229,135000 920 | 2127,238000 921 | 1414,176500 922 | 1721,201000 923 | 2200,145900 924 | 1316,169990 925 | 1617,193000 926 | 1686,207500 927 | 1126,175000 928 | 2374,285000 929 | 1978,176000 930 | 1788,236500 931 | 2236,222000 932 | 1466,201000 933 | 925,117500 934 | 1905,320000 935 | 1500,190000 936 | 2069,242000 937 | 747,79900 938 | 1200,184900 939 | 1971,253000 940 | 1962,239799 941 | 2403,244400 942 | 1728,150900 943 | 2060,214000 944 | 1440,150000 945 | 1632,143000 946 | 1344,137500 947 | 1869,124900 948 | 1144,143000 949 | 1629,270000 950 | 1776,192500 951 | 1381,197500 952 | 864,129000 953 | 965,119900 954 | 768,133900 955 | 1968,172000 956 | 980,127500 957 | 1958,145000 958 | 1229,124000 959 | 1057,132000 960 | 1337,185000 961 | 1416,155000 962 | 858,116500 963 | 2872,272000 964 | 1548,155000 965 | 1800,239000 966 | 1894,214900 967 | 1484,178900 968 | 1308,160000 969 | 1098,135000 970 | 968,37900 971 | 1095,140000 972 | 1192,135000 973 | 1626,173000 974 | 918,99500 975 | 1428,182000 976 | 2019,167500 977 | 1382,165000 978 | 869,85500 979 | 1241,199900 980 | 894,110000 981 | 1121,139000 982 | 999,178400 983 | 2612,336000 984 | 1266,159895 985 | 2290,255900 986 | 1734,126000 987 | 1164,125000 988 | 1635,117000 989 | 1940,395192 990 | 2030,195000 991 | 1576,197000 992 | 2392,348000 993 | 1742,168000 994 | 1851,187000 995 | 1500,173900 996 | 1718,337500 997 | 1230,121600 998 | 1050,136500 999 | 1442,185000 1000 | 1077,91000 1001 | 1208,206000 1002 | 944,82000 1003 | 691,86000 1004 | 1574,232000 1005 | 1680,136905 1006 | 1504,181000 1007 | 985,149900 1008 | 1657,163500 1009 | 1092,88000 1010 | 1710,240000 1011 | 1522,102000 1012 | 1271,135000 1013 | 1664,100000 1014 | 1502,165000 1015 | 1022,85000 1016 | 1082,119200 1017 | 1665,227000 1018 | 1504,203000 1019 | 1360,187500 1020 | 1472,160000 1021 | 1506,213490 1022 | 1132,176000 1023 | 1220,194000 1024 | 1248,87000 1025 | 1504,191000 1026 | 2898,287000 1027 | 882,112500 1028 | 1264,167500 1029 | 1646,293077 1030 | 1376,105000 1031 | 1218,118000 1032 | 1928,160000 1033 | 3082,197000 1034 | 2520,310000 1035 | 1654,230000 1036 | 954,119750 1037 | 845,84000 1038 | 1620,315500 1039 | 2263,287000 1040 | 1344,97000 1041 | 630,80000 1042 | 1803,155000 1043 | 1632,173000 1044 | 1306,196000 1045 | 2329,262280 1046 | 2524,278000 1047 | 1733,139600 1048 | 2868,556581 1049 | 990,145000 1050 | 1771,115000 1051 | 930,84900 1052 | 1302,176485 1053 | 1316,200141 1054 | 1977,165000 1055 | 1526,144500 1056 | 1989,255000 1057 | 1523,180000 1058 | 1364,185850 1059 | 1850,248000 1060 | 2184,335000 1061 | 1991,220000 1062 | 1338,213500 1063 | 894,81000 1064 | 2337,90000 1065 | 1103,110500 1066 | 1154,154000 1067 | 2260,328000 1068 | 1571,178000 1069 | 1611,167900 1070 | 2521,151400 1071 | 893,135000 1072 | 1048,135000 1073 | 1556,154000 1074 | 1456,91500 1075 | 1426,159500 1076 | 1240,194000 1077 | 1740,219500 1078 | 1466,170000 1079 | 1096,138800 1080 | 848,155900 1081 | 990,126000 1082 | 1258,145000 1083 | 1040,133000 1084 | 1459,192000 1085 | 1251,160000 1086 | 1498,187500 1087 | 996,147000 1088 | 1092,83500 1089 | 1953,252000 1090 | 1709,137500 1091 | 1247,197000 1092 | 1040,92900 1093 | 1252,160000 1094 | 1694,136500 1095 | 1200,146000 1096 | 936,129000 1097 | 1314,176432 1098 | 1355,127000 1099 | 1088,170000 1100 | 1324,128000 1101 | 1601,157000 1102 | 438,60000 1103 | 950,119500 1104 | 1134,135000 1105 | 1194,159500 1106 | 1302,106000 1107 | 2622,325000 1108 | 1442,179900 1109 | 2021,274725 1110 | 1690,181000 1111 | 1836,280000 1112 | 1658,188000 1113 | 1964,205000 1114 | 816,129900 1115 | 1008,134500 1116 | 833,117000 1117 | 1734,318000 1118 | 1419,184100 1119 | 894,130000 1120 | 1601,140000 1121 | 1040,133700 1122 | 1012,118400 1123 | 1552,212900 1124 | 960,112000 1125 | 698,118000 1126 | 1482,163900 1127 | 1005,115000 1128 | 1555,174000 1129 | 1530,259000 1130 | 1959,215000 1131 | 936,140000 1132 | 1981,135000 1133 | 974,93500 1134 | 2210,117500 1135 | 2020,239500 1136 | 1600,169000 1137 | 986,102000 1138 | 1252,119000 1139 | 1020,94000 1140 | 1567,196000 1141 | 1167,144000 1142 | 952,139000 1143 | 1868,197500 1144 | 2828,424870 1145 | 1006,80000 1146 | 924,80000 1147 | 1576,149000 1148 | 1298,180000 1149 | 1564,174500 1150 | 1111,116900 1151 | 1482,143000 1152 | 932,124000 1153 | 1466,149900 1154 | 1811,230000 1155 | 816,120500 1156 | 1820,201800 1157 | 1437,218000 1158 | 1265,179900 1159 | 1314,230000 1160 | 1580,235128 1161 | 1876,185000 1162 | 1456,146000 1163 | 1640,224000 1164 | 894,129000 1165 | 1258,108959 1166 | 1432,194000 1167 | 1502,233170 1168 | 1694,245350 1169 | 1671,173000 1170 | 2108,235000 1171 | 3627,625000 1172 | 1118,171000 1173 | 1261,163000 1174 | 1250,171900 1175 | 3086,200500 1176 | 2345,239000 1177 | 2872,285000 1178 | 923,119500 1179 | 1224,115000 1180 | 1343,154900 1181 | 1124,93000 1182 | 2514,250000 1183 | 1652,392500 1184 | 4476,745000 1185 | 1130,120000 1186 | 1572,186700 1187 | 1221,104900 1188 | 1699,95000 1189 | 1624,262000 1190 | 1660,195000 1191 | 1804,189000 1192 | 1622,168000 1193 | 1441,174000 1194 | 1472,125000 1195 | 1224,165000 1196 | 1352,158000 1197 | 1456,176000 1198 | 1863,219210 1199 | 1690,144000 1200 | 1212,178000 1201 | 1382,148000 1202 | 864,116050 1203 | 1779,197900 1204 | 1348,117000 1205 | 1630,213000 1206 | 1074,153500 1207 | 2196,271900 1208 | 1056,107000 1209 | 1700,200000 1210 | 1283,140000 1211 | 1660,290000 1212 | 1845,189000 1213 | 1752,164000 1214 | 672,113000 1215 | 960,145000 1216 | 999,134500 1217 | 894,125000 1218 | 1902,112000 1219 | 1314,229456 1220 | 912,80500 1221 | 1218,91500 1222 | 912,115000 1223 | 1211,134000 1224 | 1846,143000 1225 | 2136,137900 1226 | 1490,184000 1227 | 1138,145000 1228 | 1933,214000 1229 | 912,147000 1230 | 1702,367294 1231 | 1507,127000 1232 | 2620,190000 1233 | 1190,132500 1234 | 1224,101800 1235 | 1188,142000 1236 | 1964,130000 1237 | 1784,138887 1238 | 1626,175500 1239 | 1948,195000 1240 | 1141,142500 1241 | 1484,265900 1242 | 1768,224900 1243 | 1689,248328 1244 | 1173,170000 1245 | 2076,465000 1246 | 1517,230000 1247 | 1868,178000 1248 | 1553,186500 1249 | 1034,169900 1250 | 2058,129500 1251 | 988,119000 1252 | 2110,244000 1253 | 1405,171750 1254 | 874,130000 1255 | 2167,294000 1256 | 1656,165400 1257 | 1367,127500 1258 | 1987,301500 1259 | 864,99900 1260 | 1166,190000 1261 | 1054,151000 1262 | 1675,181000 1263 | 1050,128900 1264 | 1788,161500 1265 | 1824,180500 1266 | 1337,181000 1267 | 1452,183900 1268 | 1889,122000 1269 | 2018,378500 1270 | 3447,381000 1271 | 1524,144000 1272 | 1524,260000 1273 | 1489,185750 1274 | 935,137000 1275 | 1357,177000 1276 | 1250,139000 1277 | 1920,137000 1278 | 1395,162000 1279 | 1724,197900 1280 | 2031,237000 1281 | 1128,68400 1282 | 1573,227000 1283 | 1339,180000 1284 | 1040,150500 1285 | 1824,139000 1286 | 2447,169000 1287 | 1412,132500 1288 | 1328,143000 1289 | 1582,190000 1290 | 1659,278000 1291 | 1970,281000 1292 | 1152,180500 1293 | 1302,119500 1294 | 2372,107500 1295 | 1664,162900 1296 | 864,115000 1297 | 1052,138500 1298 | 1128,155000 1299 | 1072,140000 1300 | 5642,160000 1301 | 1246,154000 1302 | 1983,225000 1303 | 1494,177500 1304 | 2526,290000 1305 | 1616,232000 1306 | 1708,130000 1307 | 1652,325000 1308 | 1368,202500 1309 | 990,138000 1310 | 1122,147000 1311 | 1294,179200 1312 | 1902,335000 1313 | 1274,203000 1314 | 2810,302000 1315 | 2599,333168 1316 | 948,119000 1317 | 2112,206900 1318 | 1630,295493 1319 | 1352,208900 1320 | 1787,275000 1321 | 948,111000 1322 | 1478,156500 1323 | 720,72500 1324 | 1923,190000 1325 | 708,82500 1326 | 1795,147000 1327 | 796,55000 1328 | 774,79000 1329 | 816,130500 1330 | 2792,256000 1331 | 1632,176500 1332 | 1588,227000 1333 | 954,132500 1334 | 816,100000 1335 | 1360,125500 1336 | 1365,125000 1337 | 1334,167900 1338 | 1656,135000 1339 | 693,52500 1340 | 1861,200000 1341 | 864,128500 1342 | 872,123000 1343 | 1114,155000 1344 | 2169,228500 1345 | 1913,177000 1346 | 1456,155835 1347 | 960,108500 1348 | 2156,262500 1349 | 1776,283463 1350 | 1494,215000 1351 | 2358,122000 1352 | 2634,200000 1353 | 1716,171000 1354 | 1176,134900 1355 | 3238,410000 1356 | 1865,235000 1357 | 1920,170000 1358 | 892,110000 1359 | 1078,149900 1360 | 1573,177500 1361 | 1980,315000 1362 | 2601,189000 1363 | 1530,260000 1364 | 1738,104900 1365 | 1412,156932 1366 | 1200,144152 1367 | 1674,216000 1368 | 1790,193000 1369 | 1475,127000 1370 | 848,144000 1371 | 1668,232000 1372 | 1374,105000 1373 | 1661,165500 1374 | 2097,274300 1375 | 2633,466500 1376 | 1958,250000 1377 | 1571,239000 1378 | 790,91000 1379 | 1604,117000 1380 | 987,83000 1381 | 1394,167500 1382 | 864,58500 1383 | 2117,237500 1384 | 1762,157000 1385 | 1416,112000 1386 | 1258,105000 1387 | 1154,125500 1388 | 2784,250000 1389 | 2526,136000 1390 | 1746,377500 1391 | 1218,131000 1392 | 1525,235000 1393 | 1584,124000 1394 | 900,123000 1395 | 1912,163000 1396 | 1500,246578 1397 | 2482,281213 1398 | 1687,160000 1399 | 1513,137500 1400 | 1904,138000 1401 | 1608,137450 1402 | 1158,120000 1403 | 1593,193000 1404 | 1294,193879 1405 | 1464,282922 1406 | 1214,105000 1407 | 1646,275000 1408 | 768,133000 1409 | 833,112000 1410 | 1363,125500 1411 | 2093,215000 1412 | 1840,230000 1413 | 1668,140000 1414 | 1040,90000 1415 | 1844,257000 1416 | 1848,207000 1417 | 1569,175900 1418 | 2290,122500 1419 | 2450,340000 1420 | 1144,124000 1421 | 1844,223000 1422 | 1416,179900 1423 | 1069,127500 1424 | 848,136500 1425 | 2201,274970 1426 | 1344,144000 1427 | 1252,142000 1428 | 2127,271000 1429 | 1558,140000 1430 | 804,119000 1431 | 1440,182900 1432 | 1838,192140 1433 | 958,143750 1434 | 968,64500 1435 | 1792,186500 1436 | 1126,160000 1437 | 1537,174000 1438 | 864,120500 1439 | 1932,394617 1440 | 1236,149700 1441 | 1725,197000 1442 | 2555,191000 1443 | 848,149300 1444 | 2007,310000 1445 | 952,121000 1446 | 1422,179600 1447 | 913,129000 1448 | 1188,157900 1449 | 2090,240000 1450 | 1346,112000 1451 | 630,92000 1452 | 1792,136000 1453 | 1578,287090 1454 | 1072,145000 1455 | 1140,84500 1456 | 1221,185000 1457 | 1647,175000 1458 | 2073,210000 1459 | 2340,266500 1460 | 1078,142125 1461 | 1256,147500 --------------------------------------------------------------------------------