From b0ae2008db6f60ac54929e89905ab6db197e0b45 Mon Sep 17 00:00:00 2001 From: Dou Date: Sat, 11 Apr 2020 11:56:37 +0200 Subject: [PATCH 1/5] Mark new verion 1.0.0 --- package.json | 2 +- version_check.py | 21 +++++++++++++++++++++ widget_code_input/_frontend.py | 2 +- widget_code_input/_version.py | 2 +- 4 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 version_check.py diff --git a/package.json b/package.json index 248269a..f2cd6d3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "widget-code-input", - "version": "0.2.0", + "version": "1.0.0", "description": "A widget to allow input of a python function, with syntax highlighting.", "keywords": [ "jupyter", diff --git a/version_check.py b/version_check.py new file mode 100644 index 0000000..c27ae25 --- /dev/null +++ b/version_check.py @@ -0,0 +1,21 @@ +from setupbase import get_version +from os.path import join as pjoin +import json +from widget_code_input._frontend import module_version + +name = 'widget_code_input' + +module_version = module_version[1:] +version_py = get_version(pjoin(name, '_version.py')) + +with open('package.json') as json_file: + data = json.load(json_file) + +version_npm = data['version'] + +if version_py != version_npm or module_version != version_npm: + raise ValueError('The version number are NOT equal') +else: + print(version_py) + print('Check fine for the version number') + diff --git a/widget_code_input/_frontend.py b/widget_code_input/_frontend.py index c9d605f..7b008b1 100644 --- a/widget_code_input/_frontend.py +++ b/widget_code_input/_frontend.py @@ -9,4 +9,4 @@ """ module_name = "widget-code-input" -module_version = "^0.2.0" +module_version = "^1.0.0" diff --git a/widget_code_input/_version.py b/widget_code_input/_version.py index 6e61df7..1453270 100644 --- a/widget_code_input/_version.py +++ b/widget_code_input/_version.py @@ -4,5 +4,5 @@ # Copyright (c) Giovanni Pizzi and Dou Du. # Distributed under the terms of the Modified BSD License. -version_info = (0, 1, 0, 'dev') +version_info = (1, 0, 0) __version__ = ".".join(map(str, version_info)) From 5f41e31849e996fe992da1d11b19b48e615d8dcc Mon Sep 17 00:00:00 2001 From: Dou Date: Sat, 11 Apr 2020 11:59:46 +0200 Subject: [PATCH 2/5] Add the workflows from github --- .github/workflows/build.yml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..ecefee7 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,33 @@ +name: Build + +on: + push: + branches: '*' + pull_request: + branches: '*' + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v1 + - name: Install node + uses: actions/setup-node@v1 + with: + node-version: '10.x' + - name: Install Python + uses: actions/setup-python@v1 + with: + python-version: '3.7' + architecture: 'x64' + - name: Install dependencies + run: python -m pip install jupyterlab==2.1.0 + - name: Build the extension + run: | + jlpm && jlpm run build + jupyter labextension install . + python -m jupyterlab.browser_check + - name: Check PyPi and NPM version + run: | + python version_check.py From 518fd7b2753547c9315b83557dda05f428e2f9b9 Mon Sep 17 00:00:00 2001 From: Dou Date: Sat, 11 Apr 2020 17:30:38 +0200 Subject: [PATCH 3/5] Upate the code theme --- README.md | 6 ++++ css/nord.css | 42 ++++++++++++++++++++++++++ examples/introduction.ipynb | 18 +++++------ package.json | 4 +-- src/plugin.ts | 2 +- src/widget.ts | 3 +- tests/src/utils.spec.ts | 2 +- widget_code_input/widget_code_input.py | 3 +- 8 files changed, 65 insertions(+), 15 deletions(-) create mode 100644 css/nord.css diff --git a/README.md b/README.md index 662028b..bc50744 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,12 @@ the nbextension: ```bash jupyter nbextension enable --py [--sys-prefix|--user|--system] widget_code_input ``` +There are seven different code themes can be chosen. They are "eclipse", +"idea", "material", "midnight", "monokai", "nord" and "solarized". +You can check the appearance of the code themes at: + +[https://codemirror.net/demo/theme.html](https://codemirror.net/demo/theme.html) + # Acknowlegements diff --git a/css/nord.css b/css/nord.css new file mode 100644 index 0000000..41a8ad7 --- /dev/null +++ b/css/nord.css @@ -0,0 +1,42 @@ +/* Based on arcticicestudio's Nord theme */ +/* https://github.com/arcticicestudio/nord */ + +.cm-s-nord.CodeMirror { background: #2e3440; color: #d8dee9; } +.cm-s-nord div.CodeMirror-selected { background: #434c5e; } +.cm-s-nord .CodeMirror-line::selection, .cm-s-nord .CodeMirror-line > span::selection, .cm-s-nord .CodeMirror-line > span > span::selection { background: #3b4252; } +.cm-s-nord .CodeMirror-line::-moz-selection, .cm-s-nord .CodeMirror-line > span::-moz-selection, .cm-s-nord .CodeMirror-line > span > span::-moz-selection { background: #3b4252; } +.cm-s-nord .CodeMirror-gutters { background: #2e3440; border-right: 0px; } +.cm-s-nord .CodeMirror-guttermarker { color: #4c566a; } +.cm-s-nord .CodeMirror-guttermarker-subtle { color: #4c566a; } +.cm-s-nord .CodeMirror-linenumber { color: #4c566a; } +.cm-s-nord .CodeMirror-cursor { border-left: 1px solid #f8f8f0; } + +.cm-s-nord span.cm-comment { color: #4c566a; } +.cm-s-nord span.cm-atom { color: #b48ead; } +.cm-s-nord span.cm-number { color: #b48ead; } + +.cm-s-nord span.cm-comment.cm-attribute { color: #97b757; } +.cm-s-nord span.cm-comment.cm-def { color: #bc9262; } +.cm-s-nord span.cm-comment.cm-tag { color: #bc6283; } +.cm-s-nord span.cm-comment.cm-type { color: #5998a6; } + +.cm-s-nord span.cm-property, .cm-s-nord span.cm-attribute { color: #8FBCBB; } +.cm-s-nord span.cm-keyword { color: #81A1C1; } +.cm-s-nord span.cm-builtin { color: #81A1C1; } +.cm-s-nord span.cm-string { color: #A3BE8C; } + +.cm-s-nord span.cm-variable { color: #d8dee9; } +.cm-s-nord span.cm-variable-2 { color: #d8dee9; } +.cm-s-nord span.cm-variable-3, .cm-s-nord span.cm-type { color: #d8dee9; } +.cm-s-nord span.cm-def { color: #8FBCBB; } +.cm-s-nord span.cm-bracket { color: #81A1C1; } +.cm-s-nord span.cm-tag { color: #bf616a; } +.cm-s-nord span.cm-header { color: #b48ead; } +.cm-s-nord span.cm-link { color: #b48ead; } +.cm-s-nord span.cm-error { background: #bf616a; color: #f8f8f0; } + +.cm-s-nord .CodeMirror-activeline-background { background: #3b4252; } +.cm-s-nord .CodeMirror-matchingbracket { + text-decoration: underline; + color: white !important; +} diff --git a/examples/introduction.ipynb b/examples/introduction.ipynb index 3b6723f..5ff0583 100644 --- a/examples/introduction.ipynb +++ b/examples/introduction.ipynb @@ -9,7 +9,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 34, "metadata": {}, "outputs": [], "source": [ @@ -18,7 +18,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 35, "metadata": {}, "outputs": [], "source": [ @@ -29,24 +29,24 @@ " Input the docstring here.\n", " \"\"\",\n", " function_body=\"# Give information for the function\\n\",\n", - " code_theme = 'material'\n", + " code_theme = 'nord'\n", ")" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 36, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "9fa1bd2adc99418a99ac1c28ece9a910", + "model_id": "71643d6bc51c4abcae882313c3aafac0", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "WidgetCodeInput(code_theme='material', docstring='\\n Input the docstring here.\\n ', function_body='# Give …" + "WidgetCodeInput(code_theme='nord', docstring='\\n Input the docstring here.\\n ', function_body='# Give info…" ] }, "metadata": {}, @@ -59,7 +59,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 37, "metadata": {}, "outputs": [], "source": [ @@ -68,7 +68,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 38, "metadata": {}, "outputs": [], "source": [ @@ -92,7 +92,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.1" + "version": "3.8.2" } }, "nbformat": 4, diff --git a/package.json b/package.json index f2cd6d3..41acf68 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "watch:nbextension": "webpack --watch" }, "dependencies": { - "@jupyter-widgets/base": "^1.1.10 || ^2", + "@jupyter-widgets/base": "^1.1.10 || ^2 || ^3", "codemirror": "~5.49.2" }, "devDependencies": { @@ -76,7 +76,7 @@ "source-map-loader": "^0.2.4", "style-loader": "^1.0.0", "ts-loader": "^5.2.1", - "typescript": "~3.1.2", + "typescript": "~3.8", "webpack": "^4.20.2", "webpack-cli": "^3.1.2", "underscore": "^1.9.1", diff --git a/src/plugin.ts b/src/plugin.ts index 1d8622a..94f9d42 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -29,7 +29,7 @@ const examplePlugin: IPlugin, void> = { requires: [IJupyterWidgetRegistry], activate: activateWidgetExtension, autoStart: true -}; +} as unknown as IPlugin, void>; export default examplePlugin; diff --git a/src/widget.ts b/src/widget.ts index d7df12d..2648d11 100644 --- a/src/widget.ts +++ b/src/widget.ts @@ -26,6 +26,7 @@ import '../css/eclipse.css'; import '../css/material.css'; import '../css/solarized.css'; import '../css/idea.css'; +import '../css/nord.css'; import 'codemirror/mode/python/python.js'; @@ -44,7 +45,7 @@ class WidgetCodeModel extends DOMWidgetModel { } initialize(){ - DOMWidgetModel.prototype.initialize.apply(this, arguments); + DOMWidgetModel.prototype.initialize.apply(this, arguments as any); this.attributes['function_body_id'] = _.uniqueId('function_body'); } diff --git a/tests/src/utils.spec.ts b/tests/src/utils.spec.ts index edf744b..ba3d49a 100644 --- a/tests/src/utils.spec.ts +++ b/tests/src/utils.spec.ts @@ -21,7 +21,7 @@ class MockComm { on_msg(fn: Function | null) { this._on_msg = fn; } - _process_msg(msg: services.KernelMessage.ICommMsg) { + _process_msg(msg: services.KernelMessage.ICommMsgMsg) { if (this._on_msg) { return this._on_msg(msg); } else { diff --git a/widget_code_input/widget_code_input.py b/widget_code_input/widget_code_input.py index ba7b093..cb6dcf8 100644 --- a/widget_code_input/widget_code_input.py +++ b/widget_code_input/widget_code_input.py @@ -57,7 +57,7 @@ def _valid_docstring(self, docstring): raise TraitError('The docstring cannot contain triple double quotes (""")') return docstring['value'] - def __init__(self, function_name, function_parameters='', docstring='\n', function_body='', code_theme='midnight'): + def __init__(self, function_name, function_parameters='', docstring='\n', function_body='', code_theme=''): """ Creates a new widget to show a box to enter code. @@ -68,6 +68,7 @@ def __init__(self, function_name, function_parameters='', docstring='\n', functi :param docstring: the docstring of the function. It cannot contain triple double quotes ("). :param function_body: the content of the function body. + :param code_theme: the code theme of the code input box. """ super(WidgetCodeInput, self).__init__() self.function_name = function_name From 2c97938dfb8b10560c837070a64e2ef5e8febe87 Mon Sep 17 00:00:00 2001 From: Dou Date: Sat, 11 Apr 2020 17:44:13 +0200 Subject: [PATCH 4/5] Add the deoms --- demos/explanation.png | Bin 0 -> 5928 bytes demos/explanation.svg | 323 ++++++++++++++++++++ demos/index.ipynb | 69 +++++ demos/projectile-inline.ipynb | 441 +++++++++++++++++++++++++++ demos/projectile-notebook.ipynb | 441 +++++++++++++++++++++++++++ demos/showcase.ipynb | 297 ++++++++++++++++++ demos/static-appearance-example.html | 85 ++++++ 7 files changed, 1656 insertions(+) create mode 100644 demos/explanation.png create mode 100644 demos/explanation.svg create mode 100644 demos/index.ipynb create mode 100644 demos/projectile-inline.ipynb create mode 100644 demos/projectile-notebook.ipynb create mode 100644 demos/showcase.ipynb create mode 100644 demos/static-appearance-example.html diff --git a/demos/explanation.png b/demos/explanation.png new file mode 100644 index 0000000000000000000000000000000000000000..c124acebb71ea42a83f6870a555143d54aadc544 GIT binary patch literal 5928 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 literal 0 HcmV?d00001 diff --git a/demos/explanation.svg b/demos/explanation.svg new file mode 100644 index 0000000..569eb4a --- /dev/null +++ b/demos/explanation.svg @@ -0,0 +1,323 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + y + x + + + + + + D + + + h + + + (vx, vy) + + diff --git a/demos/index.ipynb b/demos/index.ipynb new file mode 100644 index 0000000..fea67bf --- /dev/null +++ b/demos/index.ipynb @@ -0,0 +1,69 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# widget-code-input demos\n", + "\n", + "You can visualize here a few demos of the `widget-code-input` widget for Jupyter, a widget that allows you to write snippets of code in the form of a function, and then test them.\n", + "\n", + "This widget is best used in combination with the [appmode](https://pypi.org/project/appmode/) plugin for Jupyter.\n", + "\n", + "Click on the headers to go to the respective demo." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## [Simple showcase](./showcase.ipynb)\n", + "This demo is a simple showcase of the functionality of this widget.\n", + "It shows:\n", + "\n", + "- how to use the widget\n", + "- how to get and set the content of the function body from python\n", + "- how to monitor events from python (e.g. perform on operation in python every time the code is changed by the user)\n", + "- that it is possible in insert multiple, independent widgets in the same notebook\n", + "- how to obtain the function object from the widget and how to run it" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Exercise: compute the point at which a projectile would hit the ground \n", + "### [Non-zooming version](./projectile-inline.ipynb) - [Interactive version](./projectile-notebook.ipynb)\n", + "This demo shows a possible exercise for students: given the initial parameters of a projectile (height of launch, velocity), ask the student to write the function to compute the position where the projectile will hit the ground." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 2", + "language": "python", + "name": "python2" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.5" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/demos/projectile-inline.ipynb b/demos/projectile-inline.ipynb new file mode 100644 index 0000000..07a5183 --- /dev/null +++ b/demos/projectile-inline.ipynb @@ -0,0 +1,441 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Exercise: computing the distance at which a projectile will hit the ground\n", + "\n", + "**Author: Giovanni Pizzi, EPFL**\n", + "\n", + "In this exercise, you are given three parameters, defining the initial conditions at which a projectile is launched. \n", + "In particular, you are given in input:\n", + "\n", + "- the height $h$ above the ground from which the projectile is launched\n", + "- the two components (horizontal $v_x$ and vertical $v_y$) of the velocity, $\\vec v = (v_x, v_y)$ at which the projectile is launched\n", + "\n", + "![Simple schematic](./explanation.png)\n", + "\n", + "## Task\n", + "**Your task is to write a python function that, given these three parameters, computes the horizontal position $D$ at which the projectile will hit the ground.**\n", + "\n", + "## How to test the results\n", + "To test your function, you can move the sliders below that determine the initial conditions of the projectile.\n", + "\n", + "A real-time visualization will show the correct solution for the problem (solid curve), where the launch point is marked by a black dot and the correct hitting point by a black cross.\n", + "\n", + "You will also see the result of your proposed solution as a large red circle. Finally, You can inspect possible errors of your function by opening the tab \"Results of the validation of your function\"." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\n", + "Bad key \"text.kerning_factor\" on line 4 in\n", + "/home/dou/anaconda3/envs/jlab2.0/lib/python3.8/site-packages/matplotlib/mpl-data/stylelib/_classic_test_patch.mplstyle.\n", + "You probably need to get an updated matplotlibrc file from\n", + "https://github.com/matplotlib/matplotlib/blob/v3.1.3/matplotlibrc.template\n", + "or from the matplotlib source distribution\n" + ] + } + ], + "source": [ + "%matplotlib inline\n", + "import numpy as np\n", + "import pylab as pl\n", + "\n", + "import tabulate\n", + "from ipywidgets import Label, Button, Output, FloatSlider, HBox, VBox, Layout, HTML, Accordion\n", + "from widget_code_input import WidgetCodeInput\n", + "from IPython.display import display" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "# Value of the vertical (downwards) acceleration\n", + "g = 9.81 # m/s^2" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "432120a79fc64d1e84abf87e29316d15", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "WidgetCodeInput(docstring=\"\\nA function to compute the hit coordinate of a projectile \\non the ground, knowing…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "code_widget = WidgetCodeInput(\n", + " function_name=\"get_hit_coordinate\", \n", + " function_parameters=\"vertical_position, horizontal_v, vertical_v, g={}\".format(g),\n", + " docstring=\"\"\"\n", + "A function to compute the hit coordinate of a projectile \n", + "on the ground, knowing the initial launch parameters.\n", + "\n", + ":param vertical_position: launch vertical position [m]\n", + ":param horizontal_v: launch horizontal position [m/s]\n", + ":param vertical_v: launch vertical position [m/s] \n", + " (positive values means upward velocity)\n", + ":param g: the vertical (downwards) acceleration (default: Earth's gravity)\n", + " \n", + ":return: the position at which the projectile will hit the ground [m]\n", + "\"\"\",\n", + " function_body=\"# Input here your solution\\n# After changing the function, move one of the sliders to validate your function\")\n", + "display(code_widget)\n", + "\n", + "## The solution:\n", + "# import math\n", + "# return horizontal_v * (vertical_v + math.sqrt(vertical_v**2 + 2. * g * vertical_position)) / g" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "43240995570947849c995deb2b44c9df", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HBox(children=(VBox(children=(FloatSlider(value=6.0, continuous_update=False, description='Vertical position […" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "vertical_position_widget = FloatSlider(\n", + " value=6, min=0, max=10, \n", + " description=\"Vertical position [m]\",\n", + " continuous_update=False, \n", + " style={'description_width': 'initial'}, layout=Layout(width='50%', min_width='350px'))\n", + "horizontal_v_widget = FloatSlider(\n", + " value=5, min=-10, max=10, \n", + " description=\"Horizontal velocity [m/s]\",\n", + " continuous_update=False, \n", + " style={'description_width': 'initial'}, layout=Layout(width='50%', min_width='350px'))\n", + "vertical_v_widget = FloatSlider(\n", + " value=3, min=-10, max=10, \n", + " description=\"Vertical velocity [m/s]\",\n", + " continuous_update=False, \n", + " style={'description_width': 'initial'}, layout=Layout(width='50%', min_width='350px'))\n", + "\n", + "plot_box = Output()\n", + "\n", + "input_box = VBox([vertical_position_widget, horizontal_v_widget, vertical_v_widget])\n", + "display(HBox([input_box, plot_box]))" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "6376783bb9904c70a1dde34f230567b8", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Accordion(children=(Output(),), selected_index=None, _titles={'0': 'Results of the validation of your function…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "check_function_output = Output()\n", + "check_accordion = Accordion(children=[check_function_output], selected_index=None)\n", + "check_accordion.set_title(0, 'Results of the validation of your function (click here to see them)')\n", + "\n", + "display(check_accordion)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "def trajectory(t, vertical_position, horizontal_v, vertical_v, g):\n", + " \"\"\"\n", + " Return the coordinates (x, y) at time t\n", + " \"\"\"\n", + " # We define the initial x coordinate to be zero\n", + " x0 = 0\n", + " \n", + " x = x0 + horizontal_v * t\n", + " y = -0.5 * g* t**2 + vertical_v * t + vertical_position\n", + " \n", + " return x, y \n", + "\n", + "def hit_conditions(vertical_position, horizontal_v, vertical_v, g):\n", + " \"\"\"\n", + " Return (t, D), where t is the time at which the ground is hit, and D \n", + " is the distance at which the projectile hits the ground\n", + " \"\"\"\n", + " \n", + " # We define the initial x coordinate to be zero\n", + " x0 = 0\n", + " \n", + " # x = x0 + horizontal_v * t => t = (x-x0) / horizontal_v\n", + " # y = -0.5 * g* t**2 + vertical_v * t + vertical_position => \n", + " #\n", + " # y == 0 => \n", + " a = -0.5 * g\n", + " b = vertical_v\n", + " c = vertical_position\n", + " \n", + " # the two solutions; I want the solution with positive t, \n", + " # that will in any case be t1, because\n", + " # t1 > t2 for any value of a, b, c (since a < 0)\n", + " t1 = (-b - np.sqrt(b**2 - 4 * a * c)) / (2. * a)\n", + " #t2 = (-b + np.sqrt(b**2 - 4 * a * c)) / (2. * a)\n", + " \n", + " t = t1\n", + " \n", + " D = x0 + horizontal_v * t\n", + " \n", + " return t, D\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "def check_user_value():\n", + " # I don't catch exceptions so that the users can see the traceback\n", + " error_string = \"YOUR FUNCTION DOES NOT SEEM RIGHT, PLEASE TRY TO FIX IT\"\n", + " ok_string = \"YOUR FUNCTION SEEMS TO BE CORRECT!! CONGRATULATIONS!\"\n", + " \n", + " test_table = []\n", + " last_exception = None\n", + " type_warning = False\n", + " \n", + " check_function_output.clear_output(wait=True)\n", + " with check_function_output:\n", + " user_function = code_widget.get_function_object() \n", + "\n", + " test_values_vpos = range(1,7)\n", + " test_values_vx = range(-2,3)\n", + " test_values_vy = range(-2,3) \n", + " for test_vpos in test_values_vpos:\n", + " for test_vx in test_values_vx:\n", + " for test_vy in test_values_vy:\n", + " correct_value = hit_conditions(vertical_position=test_vpos, \n", + " horizontal_v=test_vx,\n", + " vertical_v=test_vy,\n", + " g=g\n", + " )[1] # [1] because this gives D ([0] is instead t_hit)\n", + " try:\n", + " user_hit_position = user_function(\n", + " vertical_position=test_vpos, \n", + " horizontal_v=test_vx,\n", + " vertical_v=test_vy,\n", + " )\n", + " try:\n", + " error = abs(user_hit_position - correct_value)\n", + " except Exception:\n", + " type_warning = True\n", + " error = 1. # Large value so it triggers a failed test\n", + " except Exception as exc:\n", + " last_exception = exc\n", + " test_table.append([test_vpos, test_vx, test_vy, correct_value, \"ERROR\", False])\n", + " else:\n", + " if error > 1.e-8:\n", + " test_table.append([test_vpos, test_vx, test_vy, str(correct_value), str(user_hit_position), False])\n", + " else:\n", + " test_table.append([test_vpos, test_vx, test_vy, str(correct_value), str(user_hit_position), True])\n", + "\n", + " num_tests = len(test_table)\n", + " num_passed_tests = len([test for test in test_table if test[5]])\n", + " failed_tests = [test[:-1] for test in test_table if not test[5]] # Keep only failed tests, and remove last column\n", + " MAX_FAILED_TESTS = 5\n", + " if num_passed_tests < num_tests:\n", + " html_table = HTML(\"\" + \n", + " tabulate.tabulate(\n", + " failed_tests[:MAX_FAILED_TESTS], \n", + " tablefmt='html',\n", + " headers=[\"vertical_position\", \"horizontal_v\", \"vertical_v\", \"Expected value\", \"Your value\"]\n", + " ))\n", + " \n", + " if num_passed_tests < num_tests:\n", + " print(\"Your function does not seem correct; only {}/{} tests passed\".format(num_passed_tests, num_tests))\n", + " print(\"Printing up to {} failed tests:\".format(MAX_FAILED_TESTS))\n", + " display(html_table)\n", + " else:\n", + " print(\"Your function is correct! Very good! All {} tests passed\".format(num_tests))\n", + " \n", + " if type_warning:\n", + " print(\"WARNING! in at least one case, your function did not return a valid float number, please double check!\".format(num_tests))\n", + " \n", + " # Raise the last exception obtained\n", + " if last_exception is not None:\n", + " print(\"I obtained at least one exception\")\n", + " raise last_exception from None\n", + " \n", + "def get_user_value():\n", + " \"\"\"\n", + " This function returns the value computed by the user's\n", + " function for the current sliders' value, or None if there is an exception\n", + " \"\"\"\n", + " with check_function_output:\n", + " user_function = code_widget.get_function_object() \n", + " try:\n", + " user_hit_position = user_function(\n", + " vertical_position=vertical_position_widget.value, \n", + " horizontal_v=horizontal_v_widget.value,\n", + " vertical_v=vertical_v_widget.value,\n", + " )\n", + " except Exception as exc:\n", + " return None\n", + " return user_hit_position " + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "def replot(vertical_position, horizontal_v, vertical_v):\n", + " #global the_figure, the_plot, g\n", + " global g\n", + " \n", + " the_figure = pl.figure(figsize=(4,3))\n", + " the_plot = pl.subplot(1,1,1)\n", + " pl.xlabel(\"x [m]\")\n", + " pl.xlabel(\"y [m]\")\n", + " \n", + " \n", + " # Compute correct values\n", + " t_hit, D = hit_conditions(vertical_position, horizontal_v, vertical_v, g)\n", + " t_array = np.linspace(0,t_hit, 100)\n", + " x_array, y_array = trajectory(t_array, vertical_position, horizontal_v, vertical_v, g)\n", + "\n", + " # Plot orrect curves and points\n", + " pl.plot([0], [vertical_position], 'ok')\n", + " pl.plot([D], [0], 'xk') \n", + " pl.plot(x_array, y_array, '-b')\n", + "\n", + " \n", + " ## (Try to) plot user value\n", + " user_value = None\n", + " try:\n", + " user_value = get_user_value()\n", + " except Exception:\n", + " # Just a guard not to break the visualization, we should not end up here\n", + " pass \n", + " try:\n", + " if user_value is not None:\n", + " the_plot.plot([user_value], [0], 'or') \n", + " except Exception:\n", + " # We might end up here if the function does not return a float value\n", + " pass \n", + "\n", + " pl.axhline(0, color='gray')\n", + " # Set zoom to fixed value\n", + " the_plot.set_xlim([-30, 30])\n", + " the_plot.set_ylim([-1, 16])\n", + " \n", + " # Redraw\n", + " pl.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "def recompute(e):\n", + " global plot_box, g\n", + " \n", + " if e is not None:\n", + " if e['type'] != 'change' or e['name'] not in ['value', 'function_body']:\n", + " return \n", + " plot_box.clear_output(wait=True)\n", + " with plot_box:\n", + " replot(\n", + " vertical_position=vertical_position_widget.value, \n", + " horizontal_v=horizontal_v_widget.value,\n", + " vertical_v=vertical_v_widget.value,\n", + " )\n", + " \n", + " # Print info on the \"correctness\" of the user's function\n", + " check_user_value()\n", + " \n", + "# Bind the sliders to the event\n", + "vertical_position_widget.observe(recompute)\n", + "horizontal_v_widget.observe(recompute)\n", + "vertical_v_widget.observe(recompute)\n", + "\n", + "# Bind also the code widget\n", + "code_widget.observe(recompute)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "# Perform the first recomputation (to create the plot)\n", + "_ = recompute(None)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.2" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/demos/projectile-notebook.ipynb b/demos/projectile-notebook.ipynb new file mode 100644 index 0000000..b54b508 --- /dev/null +++ b/demos/projectile-notebook.ipynb @@ -0,0 +1,441 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Exercise: computing the distance at which a projectile will hit the ground\n", + "\n", + "**Author: Giovanni Pizzi, EPFL**\n", + "\n", + "In this exercise, you are given three parameters, defining the initial conditions at which a projectile is launched. \n", + "In particular, you are given in input:\n", + "\n", + "- the height $h$ above the ground from which the projectile is launched\n", + "- the two components (horizontal $v_x$ and vertical $v_y$) of the velocity, $\\vec v = (v_x, v_y)$ at which the projectile is launched\n", + "\n", + "![Simple schematic](./explanation.png)\n", + "\n", + "## Task\n", + "**Your task is to write a python function that, given these three parameters, computes the horizontal position $D$ at which the projectile will hit the ground.**\n", + "\n", + "## How to test the results\n", + "To test your function, you can move the sliders below that determine the initial conditions of the projectile.\n", + "\n", + "A real-time visualization will show the correct solution for the problem (solid curve), where the launch point is marked by a black dot and the correct hitting point by a black cross.\n", + "\n", + "You will also see the result of your proposed solution as a large red circle. Finally, You can inspect possible errors of your function by opening the tab \"Results of the validation of your function\"." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\n", + "Bad key \"text.kerning_factor\" on line 4 in\n", + "/home/dou/anaconda3/envs/jlab2.0/lib/python3.8/site-packages/matplotlib/mpl-data/stylelib/_classic_test_patch.mplstyle.\n", + "You probably need to get an updated matplotlibrc file from\n", + "https://github.com/matplotlib/matplotlib/blob/v3.1.3/matplotlibrc.template\n", + "or from the matplotlib source distribution\n" + ] + } + ], + "source": [ + "%matplotlib notebook\n", + "import numpy as np\n", + "import pylab as pl\n", + "\n", + "import tabulate\n", + "from ipywidgets import Label, Button, Output, FloatSlider, HBox, VBox, Layout, HTML, Accordion\n", + "from widget_code_input import WidgetCodeInput\n", + "from IPython.display import display" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "# Value of the vertical (downwards) acceleration\n", + "g = 9.81 # m/s^2" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "31d644fb7933439fa91dd2c272d9eeb9", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "WidgetCodeInput(docstring=\"\\nA function to compute the hit coordinate of a projectile \\non the ground, knowing…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "code_widget = WidgetCodeInput(\n", + " function_name=\"get_hit_coordinate\", \n", + " function_parameters=\"vertical_position, horizontal_v, vertical_v, g={}\".format(g),\n", + " docstring=\"\"\"\n", + "A function to compute the hit coordinate of a projectile \n", + "on the ground, knowing the initial launch parameters.\n", + "\n", + ":param vertical_position: launch vertical position [m]\n", + ":param horizontal_v: launch horizontal position [m/s]\n", + ":param vertical_v: launch vertical position [m/s] \n", + " (positive values means upward velocity)\n", + ":param g: the vertical (downwards) acceleration (default: Earth's gravity)\n", + " \n", + ":return: the position at which the projectile will hit the ground [m]\n", + "\"\"\",\n", + " function_body=\"# Input here your solution\\n# After changing the function, move one of the sliders to validate your function\")\n", + "display(code_widget)\n", + "\n", + "## The solution:\n", + "# import math\n", + "# return horizontal_v * (vertical_v + math.sqrt(vertical_v**2 + 2. * g * vertical_position)) / g" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "3383ae2a018a4bb89706a94f2096489f", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HBox(children=(VBox(children=(FloatSlider(value=6.0, continuous_update=False, description='Vertical position […" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "vertical_position_widget = FloatSlider(\n", + " value=6, min=0, max=10, \n", + " description=\"Vertical position [m]\",\n", + " continuous_update=False, \n", + " style={'description_width': 'initial'}, layout=Layout(width='50%', min_width='350px'))\n", + "horizontal_v_widget = FloatSlider(\n", + " value=5, min=-10, max=10, \n", + " description=\"Horizontal velocity [m/s]\",\n", + " continuous_update=False, \n", + " style={'description_width': 'initial'}, layout=Layout(width='50%', min_width='350px'))\n", + "vertical_v_widget = FloatSlider(\n", + " value=3, min=-10, max=10, \n", + " description=\"Vertical velocity [m/s]\",\n", + " continuous_update=False, \n", + " style={'description_width': 'initial'}, layout=Layout(width='50%', min_width='350px'))\n", + "\n", + "plot_box = Output()\n", + "\n", + "input_box = VBox([vertical_position_widget, horizontal_v_widget, vertical_v_widget])\n", + "display(HBox([input_box, plot_box]))" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "5d3bc4a5bfd14bd8a2c7225bed6108d1", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Accordion(children=(Output(),), selected_index=None, _titles={'0': 'Results of the validation of your function…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "check_function_output = Output()\n", + "check_accordion = Accordion(children=[check_function_output], selected_index=None)\n", + "check_accordion.set_title(0, 'Results of the validation of your function (click here to see them)')\n", + "\n", + "display(check_accordion)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "def trajectory(t, vertical_position, horizontal_v, vertical_v, g):\n", + " \"\"\"\n", + " Return the coordinates (x, y) at time t\n", + " \"\"\"\n", + " # We define the initial x coordinate to be zero\n", + " x0 = 0\n", + " \n", + " x = x0 + horizontal_v * t\n", + " y = -0.5 * g* t**2 + vertical_v * t + vertical_position\n", + " \n", + " return x, y \n", + "\n", + "def hit_conditions(vertical_position, horizontal_v, vertical_v, g):\n", + " \"\"\"\n", + " Return (t, D), where t is the time at which the ground is hit, and D \n", + " is the distance at which the projectile hits the ground\n", + " \"\"\"\n", + " \n", + " # We define the initial x coordinate to be zero\n", + " x0 = 0\n", + " \n", + " # x = x0 + horizontal_v * t => t = (x-x0) / horizontal_v\n", + " # y = -0.5 * g* t**2 + vertical_v * t + vertical_position => \n", + " #\n", + " # y == 0 => \n", + " a = -0.5 * g\n", + " b = vertical_v\n", + " c = vertical_position\n", + " \n", + " # the two solutions; I want the solution with positive t, \n", + " # that will in any case be t1, because\n", + " # t1 > t2 for any value of a, b, c (since a < 0)\n", + " t1 = (-b - np.sqrt(b**2 - 4 * a * c)) / (2. * a)\n", + " #t2 = (-b + np.sqrt(b**2 - 4 * a * c)) / (2. * a)\n", + " \n", + " t = t1\n", + " \n", + " D = x0 + horizontal_v * t\n", + " \n", + " return t, D\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "def check_user_value():\n", + " # I don't catch exceptions so that the users can see the traceback\n", + " error_string = \"YOUR FUNCTION DOES NOT SEEM RIGHT, PLEASE TRY TO FIX IT\"\n", + " ok_string = \"YOUR FUNCTION SEEMS TO BE CORRECT!! CONGRATULATIONS!\"\n", + " \n", + " test_table = []\n", + " last_exception = None\n", + " type_warning = False\n", + " \n", + " check_function_output.clear_output(wait=True)\n", + " with check_function_output:\n", + " user_function = code_widget.get_function_object() \n", + "\n", + " test_values_vpos = range(1,7)\n", + " test_values_vx = range(-2,3)\n", + " test_values_vy = range(-2,3) \n", + " for test_vpos in test_values_vpos:\n", + " for test_vx in test_values_vx:\n", + " for test_vy in test_values_vy:\n", + " correct_value = hit_conditions(vertical_position=test_vpos, \n", + " horizontal_v=test_vx,\n", + " vertical_v=test_vy,\n", + " g=g\n", + " )[1] # [1] because this gives D ([0] is instead t_hit)\n", + " try:\n", + " user_hit_position = user_function(\n", + " vertical_position=test_vpos, \n", + " horizontal_v=test_vx,\n", + " vertical_v=test_vy,\n", + " )\n", + " try:\n", + " error = abs(user_hit_position - correct_value)\n", + " except Exception:\n", + " type_warning = True\n", + " error = 1. # Large value so it triggers a failed test\n", + " except Exception as exc:\n", + " last_exception = exc\n", + " test_table.append([test_vpos, test_vx, test_vy, correct_value, \"ERROR\", False])\n", + " else:\n", + " if error > 1.e-8:\n", + " test_table.append([test_vpos, test_vx, test_vy, str(correct_value), str(user_hit_position), False])\n", + " else:\n", + " test_table.append([test_vpos, test_vx, test_vy, str(correct_value), str(user_hit_position), True])\n", + "\n", + " num_tests = len(test_table)\n", + " num_passed_tests = len([test for test in test_table if test[5]])\n", + " failed_tests = [test[:-1] for test in test_table if not test[5]] # Keep only failed tests, and remove last column\n", + " MAX_FAILED_TESTS = 5\n", + " if num_passed_tests < num_tests:\n", + " html_table = HTML(\"\" + \n", + " tabulate.tabulate(\n", + " failed_tests[:MAX_FAILED_TESTS], \n", + " tablefmt='html',\n", + " headers=[\"vertical_position\", \"horizontal_v\", \"vertical_v\", \"Expected value\", \"Your value\"]\n", + " ))\n", + " \n", + " if num_passed_tests < num_tests:\n", + " print(\"Your function does not seem correct; only {}/{} tests passed\".format(num_passed_tests, num_tests))\n", + " print(\"Printing up to {} failed tests:\".format(MAX_FAILED_TESTS))\n", + " display(html_table)\n", + " else:\n", + " print(\"Your function is correct! Very good! All {} tests passed\".format(num_tests))\n", + " \n", + " if type_warning:\n", + " print(\"WARNING! in at least one case, your function did not return a valid float number, please double check!\".format(num_tests))\n", + " \n", + " # Raise the last exception obtained\n", + " if last_exception is not None:\n", + " print(\"I obtained at least one exception\")\n", + " raise last_exception from None\n", + " \n", + "def get_user_value():\n", + " \"\"\"\n", + " This function returns the value computed by the user's\n", + " function for the current sliders' value, or None if there is an exception\n", + " \"\"\"\n", + " with check_function_output:\n", + " user_function = code_widget.get_function_object() \n", + " try:\n", + " user_hit_position = user_function(\n", + " vertical_position=vertical_position_widget.value, \n", + " horizontal_v=horizontal_v_widget.value,\n", + " vertical_v=vertical_v_widget.value,\n", + " )\n", + " except Exception as exc:\n", + " return None\n", + " return user_hit_position " + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "with plot_box:\n", + " the_figure = pl.figure(figsize=(4,3))\n", + " the_plot = the_figure.add_subplot(1,1,1)\n", + " the_plot.set_xlabel(\"x [m]\")\n", + " the_plot.set_xlabel(\"y [m]\")\n", + "\n", + "def replot(vertical_position, horizontal_v, vertical_v):\n", + " global the_plot, g\n", + " \n", + " # Compute correct values\n", + " t_hit, D = hit_conditions(vertical_position, horizontal_v, vertical_v, g)\n", + " t_array = np.linspace(0,t_hit, 100)\n", + " x_array, y_array = trajectory(t_array, vertical_position, horizontal_v, vertical_v, g)\n", + "\n", + " # Clean up the graph\n", + " the_plot.axes.clear()\n", + " # Plot orrect curves and points\n", + " the_plot.plot([0], [vertical_position], 'ok')\n", + " the_plot.plot([D], [0], 'xk') \n", + " the_plot.plot(x_array, y_array, '-b')\n", + "\n", + " \n", + " ## (Try to) plot user value\n", + " user_value = None\n", + " try:\n", + " user_value = get_user_value()\n", + " except Exception:\n", + " # Just a guard not to break the visualization, we should not end up here\n", + " pass \n", + " try:\n", + " if user_value is not None:\n", + " the_plot.plot([user_value], [0], 'or') \n", + " except Exception:\n", + " # We might end up here if the function does not return a float value\n", + " pass \n", + "\n", + " the_plot.axhline(0, color='gray')\n", + " # Set zoom to fixed value\n", + " the_plot.set_xlim([-30, 30])\n", + " the_plot.set_ylim([-1, 16])\n", + " \n", + " # Redraw\n", + " the_figure.canvas.draw()\n", + " the_figure.canvas.flush_events()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "def recompute(e):\n", + " global the_plot, g\n", + " \n", + " if e is not None:\n", + " if e['type'] != 'change' or e['name'] not in ['value', 'function_body']:\n", + " return \n", + " replot(\n", + " vertical_position=vertical_position_widget.value, \n", + " horizontal_v=horizontal_v_widget.value,\n", + " vertical_v=vertical_v_widget.value,\n", + " )\n", + " \n", + " # Print info on the \"correctness\" of the user's function\n", + " check_user_value()\n", + " \n", + "# Bind the sliders to the event\n", + "vertical_position_widget.observe(recompute)\n", + "horizontal_v_widget.observe(recompute)\n", + "vertical_v_widget.observe(recompute)\n", + "\n", + "# Bind also the code widget\n", + "code_widget.observe(recompute)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "# Perform the first recomputation (to create the plot)\n", + "_ = recompute(None)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.2" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/demos/showcase.ipynb b/demos/showcase.ipynb new file mode 100644 index 0000000..d846974 --- /dev/null +++ b/demos/showcase.ipynb @@ -0,0 +1,297 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from widget_code_input import WidgetCodeInput\n", + "from IPython.display import display, Markdown\n", + "from ipywidgets import Label, Button, Output, Layout" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "2c3091cf07b04074a76e6049f6ad6791", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "WidgetCodeInput(function_name='is_even', function_parameters='number')" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "widget = WidgetCodeInput(function_name=\"is_even\", function_parameters=\"number\")\n", + "display(widget)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "widget.function_body = 'if number % 2:\\n return False\\nreturn True'" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "d96cc93702304310a94909e536970f5d", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Output()" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "content=Output()\n", + "display(content)\n", + "with content:\n", + " display(Markdown('Reprinting here below the function body:\\n\\n```python\\n{}\\n```'.format(widget.full_function_code)))\n", + "\n", + "def update_label(event):\n", + " global content, widget\n", + " if event['type'] == 'change':# and event['name'] == 'function_body': # Removed this part because also e.g. the docstring could change\n", + " content.clear_output(wait=True) # wait=True prevents flickering\n", + " with content:\n", + " #display(Markdown('Reprinting here below the function body:\\n\\n```python\\n{}\\n```'.format(event['new'])))\n", + " display(Markdown('Reprinting here below the function body:\\n\\n```python\\n{}\\n```'.format(widget.full_function_code)))\n", + " \n", + "widget.observe(update_label)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "3eecdc05d09d4f8fbe5216e34aabbb24", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Button(description='Check function results', layout=Layout(width='300px'), style=ButtonStyle())" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "807177d37f9c462c82c84c91188b4b45", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Output()" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "check_function_output = Output()\n", + "\n", + "def check_function(clicked_widget):\n", + " global widget\n", + " check_function_output.clear_output(wait=True)\n", + " with check_function_output:\n", + " user_function = widget.get_function_object() # This could raise\n", + " for i in range(7):\n", + " print(\"Is {} even? {}\".format(i, user_function(i)))\n", + "\n", + "button = Button(description=\"Check function results\", layout=Layout(width='300px'))\n", + "button.on_click(check_function)\n", + "\n", + "display(button)\n", + "display(check_function_output)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "7ed88ac5948348c0aac458d2dd389b32", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "WidgetCodeInput(function_name='some_function', function_parameters=\"name='example', value\")" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "widget2 = WidgetCodeInput(function_name='some_function', function_parameters=\"name='example', value\")\n", + "display(widget2)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "7a3cecd48b9540b290939edbff972180", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Output()" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "content2=Output()\n", + "display(content2)\n", + "with content2:\n", + " display(Markdown('Reprinting here below the function body:\\n\\n```python\\n{}\\n```'.format(widget2.full_function_code)))\n", + "\n", + "def update_label2(event):\n", + " global content2, widget2\n", + " if event['type'] == 'change':# and event['name'] == 'function_body': # Removed this part because also e.g. the docstring could change\n", + " content2.clear_output(wait=True) # wait=True prevents flickering\n", + " with content2:\n", + " #display(Markdown('Reprinting here below the function body:\\n\\n```python\\n{}\\n```'.format(event['new'])))\n", + " display(Markdown('Reprinting here below the function body:\\n\\n```python\\n{}\\n```'.format(widget2.full_function_code)))\n", + " \n", + "widget2.observe(update_label2)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "24a2acfa939542b9aeee36267dc70b94", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Button(description='Toggle function name', style=ButtonStyle())" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "ffe8d60f693946029a4ba32c60cc5dfc", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Button(description='Toggle docstring', style=ButtonStyle())" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "current_function_name_idx = 0\n", + "current_docstring_idx = 0\n", + "\n", + "def toggle_function_name(clicked_widget):\n", + " global widget2, current_function_name_idx\n", + " values = [\"some_function\", \"another_function_name\"]\n", + " current_function_name_idx += 1\n", + " current_function_name_idx %= len(values)\n", + " widget2.function_name = values[current_function_name_idx]\n", + "\n", + "def toggle_docstring(clicked_widget):\n", + " global widget2, current_docstring_idx\n", + " values = [\n", + " \"\",\n", + " \"\"\"\n", + "This function does something.\n", + "\n", + ":param name: The name of the object\n", + ":param value: The (integer) value of the object\n", + "\"\"\"]\n", + " current_docstring_idx += 1\n", + " current_docstring_idx %= len(values)\n", + " widget2.docstring = values[current_docstring_idx]\n", + " \n", + "button_fname = Button(description=\"Toggle function name\")\n", + "button_fname.on_click(toggle_function_name)\n", + "\n", + "button_docs = Button(description=\"Toggle docstring\")\n", + "button_docs.on_click(toggle_docstring)\n", + "\n", + "display(button_fname)\n", + "display(button_docs)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.2" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/demos/static-appearance-example.html b/demos/static-appearance-example.html new file mode 100644 index 0000000..7129028 --- /dev/null +++ b/demos/static-appearance-example.html @@ -0,0 +1,85 @@ + + + + + + Test CodeMirror Python fixed API + + + + + + + + + + + +
+ + + +
+ + + + \ No newline at end of file From 7d08f2185c9df846d40d749c6927e38b77ca04f9 Mon Sep 17 00:00:00 2001 From: Dou Date: Sat, 11 Apr 2020 17:54:15 +0200 Subject: [PATCH 5/5] Update the workflows --- .github/workflows/build.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ecefee7..951b278 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,7 +22,9 @@ jobs: python-version: '3.7' architecture: 'x64' - name: Install dependencies - run: python -m pip install jupyterlab==2.1.0 + run: | + python -m pip install jupyterlab==2.1.0 + python -m pip install ipywidgets - name: Build the extension run: | jlpm && jlpm run build