├── static ├── .keep ├── tracking_tagger.gif └── icons │ ├── Blue_A_Icon.png │ ├── Pink_H_Icon.png │ └── Black_B_Icon.png ├── .gitignore ├── requirements.txt ├── Procfile ├── environment.yml ├── README.md └── tracking_tagger.ipynb /static/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .ipynb_checkpoints/ -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | bokeh==2.3.2 2 | panel==0.11.3 3 | pandas 4 | numpy 5 | jupyter 6 | notebook 7 | nbconvert -------------------------------------------------------------------------------- /static/tracking_tagger.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/znstrider/tracking_tagger/HEAD/static/tracking_tagger.gif -------------------------------------------------------------------------------- /static/icons/Blue_A_Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/znstrider/tracking_tagger/HEAD/static/icons/Blue_A_Icon.png -------------------------------------------------------------------------------- /static/icons/Pink_H_Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/znstrider/tracking_tagger/HEAD/static/icons/Pink_H_Icon.png -------------------------------------------------------------------------------- /static/icons/Black_B_Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/znstrider/tracking_tagger/HEAD/static/icons/Black_B_Icon.png -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: panel serve --address="0.0.0.0" --port=$PORT tracking_tagger.ipynb --allow-websocket-origin=tracking-tagger.herokuapp.com 2 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | channels: 2 | - pyviz 3 | 4 | packages: 5 | - pandas 6 | - numpy 7 | - jupyter 8 | - notebook 9 | - nbconvert 10 | - jupyter-panel-proxy 11 | - bokeh=2.3.2 12 | - panel=0.11.3 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Heroku](http://heroku-badge.herokuapp.com/?app=tracking-tagger&style=flat&svg=1) 2 | 3 |

Tracking Data and Event Tagger

4 | 5 | This is a small Panel App that allows you to make your own tracking data and download it. 6 | You can access the app on [Heroku](https://tracking-tagger.herokuapp.com/). 7 | 8 | 9 | 10 | Simply choose the team or the ball icon on the right and click to create a player. 11 | You can click&hold to move them as long as the corresponding icon is active. 12 | Remove a playermarker by clicking and then pressing the Backspace key. 13 | 14 | You can also clone this repository, install the requirements and run it with 15 | ``` 16 | panel serve --show tracking_tagger.ipynb 17 | ``` -------------------------------------------------------------------------------- /tracking_tagger.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "### 1. Imports\n", 8 | "##### 1.1 Import libraries and write settings." 9 | ] 10 | }, 11 | { 12 | "cell_type": "code", 13 | "execution_count": 3, 14 | "metadata": {}, 15 | "outputs": [], 16 | "source": [ 17 | "import pandas as pd\n", 18 | "import numpy as np\n", 19 | "\n", 20 | "# Options for pandas\n", 21 | "pd.options.display.max_columns = 50\n", 22 | "pd.options.display.max_rows = 50\n", 23 | "\n", 24 | "import warnings\n", 25 | "warnings.filterwarnings(\"ignore\")\n", 26 | "\n", 27 | "# Standard Plotting - Matplotlib\n", 28 | "import matplotlib.pyplot as plt\n", 29 | "import seaborn as sns\n", 30 | "import matplotlib as mpl\n", 31 | "from themepy import Theme\n", 32 | "\n", 33 | "# common stats imports\n", 34 | "from scipy import stats\n", 35 | "from math import pi\n", 36 | "\n", 37 | "# matplotlib helpers\n", 38 | "from myhelpers.matplotlib import *\n", 39 | "\n", 40 | "# increases resolution of plots\n", 41 | "%config InlineBackend.figure_format = \"retina\"\n", 42 | "%matplotlib inline\n", 43 | "\n", 44 | "# set a standard fontname to use\n", 45 | "fontname = \"Enigmatic Unicode\"" 46 | ] 47 | }, 48 | { 49 | "cell_type": "code", 50 | "execution_count": 4, 51 | "metadata": { 52 | "scrolled": false 53 | }, 54 | "outputs": [ 55 | { 56 | "data": { 57 | "application/javascript": [ 58 | "\n", 59 | "(function(root) {\n", 60 | " function now() {\n", 61 | " return new Date();\n", 62 | " }\n", 63 | "\n", 64 | " var force = true;\n", 65 | "\n", 66 | " if (typeof root._bokeh_onload_callbacks === \"undefined\" || force === true) {\n", 67 | " root._bokeh_onload_callbacks = [];\n", 68 | " root._bokeh_is_loading = undefined;\n", 69 | " }\n", 70 | "\n", 71 | " if (typeof (root._bokeh_timeout) === \"undefined\" || force === true) {\n", 72 | " root._bokeh_timeout = Date.now() + 5000;\n", 73 | " root._bokeh_failed_load = false;\n", 74 | " }\n", 75 | "\n", 76 | " function run_callbacks() {\n", 77 | " try {\n", 78 | " root._bokeh_onload_callbacks.forEach(function(callback) {\n", 79 | " if (callback != null)\n", 80 | " callback();\n", 81 | " });\n", 82 | " } finally {\n", 83 | " delete root._bokeh_onload_callbacks\n", 84 | " }\n", 85 | " console.debug(\"Bokeh: all callbacks have finished\");\n", 86 | " }\n", 87 | "\n", 88 | " function load_libs(css_urls, js_urls, js_modules, callback) {\n", 89 | " if (css_urls == null) css_urls = [];\n", 90 | " if (js_urls == null) js_urls = [];\n", 91 | " if (js_modules == null) js_modules = [];\n", 92 | "\n", 93 | " root._bokeh_onload_callbacks.push(callback);\n", 94 | " if (root._bokeh_is_loading > 0) {\n", 95 | " console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n", 96 | " return null;\n", 97 | " }\n", 98 | " if (js_urls.length === 0 && js_modules.length === 0) {\n", 99 | " run_callbacks();\n", 100 | " return null;\n", 101 | " }\n", 102 | " console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n", 103 | " root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length;\n", 104 | "\n", 105 | " function on_load() {\n", 106 | " root._bokeh_is_loading--;\n", 107 | " if (root._bokeh_is_loading === 0) {\n", 108 | " console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n", 109 | " run_callbacks()\n", 110 | " }\n", 111 | " }\n", 112 | "\n", 113 | " function on_error() {\n", 114 | " console.error(\"failed to load \" + url);\n", 115 | " }\n", 116 | "\n", 117 | " for (var i = 0; i < css_urls.length; i++) {\n", 118 | " var url = css_urls[i];\n", 119 | " const element = document.createElement(\"link\");\n", 120 | " element.onload = on_load;\n", 121 | " element.onerror = on_error;\n", 122 | " element.rel = \"stylesheet\";\n", 123 | " element.type = \"text/css\";\n", 124 | " element.href = url;\n", 125 | " console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n", 126 | " document.body.appendChild(element);\n", 127 | " }\n", 128 | "\n", 129 | " var skip = [];\n", 130 | " if (window.requirejs) {\n", 131 | " window.requirejs.config({'paths': {'tabulator': 'https://unpkg.com/tabulator-tables@4.9.3/dist/js/tabulator'}});\n", 132 | " require([], function() {\n", 133 | " })\n", 134 | " }\n", 135 | " if (((window['tabulator'] !== undefined) && (!(window['tabulator'] instanceof HTMLElement))) || window.requirejs) {\n", 136 | " var urls = ['https://unpkg.com/tabulator-tables@4.9.3/dist/js/tabulator.js', 'https://unpkg.com/moment@2.27.0/moment.js'];\n", 137 | " for (var i = 0; i < urls.length; i++) {\n", 138 | " skip.push(urls[i])\n", 139 | " }\n", 140 | " }\n", 141 | " for (var i = 0; i < js_urls.length; i++) {\n", 142 | " var url = js_urls[i];\n", 143 | " if (skip.indexOf(url) >= 0) { on_load(); continue; }\n", 144 | " var element = document.createElement('script');\n", 145 | " element.onload = on_load;\n", 146 | " element.onerror = on_error;\n", 147 | " element.async = false;\n", 148 | " element.src = url;\n", 149 | " console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n", 150 | " document.head.appendChild(element);\n", 151 | " }\n", 152 | " for (var i = 0; i < js_modules.length; i++) {\n", 153 | " var url = js_modules[i];\n", 154 | " if (skip.indexOf(url) >= 0) { on_load(); continue; }\n", 155 | " var element = document.createElement('script');\n", 156 | " element.onload = on_load;\n", 157 | " element.onerror = on_error;\n", 158 | " element.async = false;\n", 159 | " element.src = url;\n", 160 | " element.type = \"module\";\n", 161 | " console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n", 162 | " document.head.appendChild(element);\n", 163 | " }\n", 164 | " if (!js_urls.length && !js_modules.length) {\n", 165 | " on_load()\n", 166 | " }\n", 167 | " };\n", 168 | "\n", 169 | " function inject_raw_css(css) {\n", 170 | " const element = document.createElement(\"style\");\n", 171 | " element.appendChild(document.createTextNode(css));\n", 172 | " document.body.appendChild(element);\n", 173 | " }\n", 174 | "\n", 175 | " var js_urls = [\"https://unpkg.com/tabulator-tables@4.9.3/dist/js/tabulator.js\", \"https://unpkg.com/moment@2.27.0/moment.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-2.3.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.3.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.3.1.min.js\", \"https://unpkg.com/@holoviz/panel@^0.11.3/dist/panel.min.js\"];\n", 176 | " var js_modules = [];\n", 177 | " var css_urls = [\"https://unpkg.com/tabulator-tables@4.9.3/dist/css/tabulator_simple.min.css\", \"https://unpkg.com/@holoviz/panel@0.11.3/dist/css/alerts.css\", \"https://unpkg.com/@holoviz/panel@0.11.3/dist/css/card.css\", \"https://unpkg.com/@holoviz/panel@0.11.3/dist/css/widgets.css\", \"https://unpkg.com/@holoviz/panel@0.11.3/dist/css/markdown.css\", \"https://unpkg.com/@holoviz/panel@0.11.3/dist/css/json.css\", \"https://unpkg.com/@holoviz/panel@0.11.3/dist/css/loading.css\", \"https://unpkg.com/@holoviz/panel@0.11.3/dist/css/dataframe.css\"];\n", 178 | " var inline_js = [\n", 179 | " function(Bokeh) {\n", 180 | " inject_raw_css(\"\\n .bk.pn-loading.arcs:before {\\n background-image: url(\\\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBzdHlsZT0ibWFyZ2luOiBhdXRvOyBiYWNrZ3JvdW5kOiBub25lOyBkaXNwbGF5OiBibG9jazsgc2hhcGUtcmVuZGVyaW5nOiBhdXRvOyIgdmlld0JveD0iMCAwIDEwMCAxMDAiIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaWRZTWlkIj4gIDxjaXJjbGUgY3g9IjUwIiBjeT0iNTAiIHI9IjMyIiBzdHJva2Utd2lkdGg9IjgiIHN0cm9rZT0iI2MzYzNjMyIgc3Ryb2tlLWRhc2hhcnJheT0iNTAuMjY1NDgyNDU3NDM2NjkgNTAuMjY1NDgyNDU3NDM2NjkiIGZpbGw9Im5vbmUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+ICAgIDxhbmltYXRlVHJhbnNmb3JtIGF0dHJpYnV0ZU5hbWU9InRyYW5zZm9ybSIgdHlwZT0icm90YXRlIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIgZHVyPSIxcyIga2V5VGltZXM9IjA7MSIgdmFsdWVzPSIwIDUwIDUwOzM2MCA1MCA1MCI+PC9hbmltYXRlVHJhbnNmb3JtPiAgPC9jaXJjbGU+PC9zdmc+\\\")\\n }\\n \");\n", 181 | " },\n", 182 | " function(Bokeh) {\n", 183 | " Bokeh.set_log_level(\"info\");\n", 184 | " },\n", 185 | " function(Bokeh) {} // ensure no trailing comma for IE\n", 186 | " ];\n", 187 | "\n", 188 | " function run_inline_js() {\n", 189 | " if ((root.Bokeh !== undefined) || (force === true)) {\n", 190 | " for (var i = 0; i < inline_js.length; i++) {\n", 191 | " inline_js[i].call(root, root.Bokeh);\n", 192 | " }} else if (Date.now() < root._bokeh_timeout) {\n", 193 | " setTimeout(run_inline_js, 100);\n", 194 | " } else if (!root._bokeh_failed_load) {\n", 195 | " console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n", 196 | " root._bokeh_failed_load = true;\n", 197 | " }\n", 198 | " }\n", 199 | "\n", 200 | " if (root._bokeh_is_loading === 0) {\n", 201 | " console.debug(\"Bokeh: BokehJS loaded, going straight to plotting\");\n", 202 | " run_inline_js();\n", 203 | " } else {\n", 204 | " load_libs(css_urls, js_urls, js_modules, function() {\n", 205 | " console.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n", 206 | " run_inline_js();\n", 207 | " });\n", 208 | " }\n", 209 | "}(window));" 210 | ], 211 | "application/vnd.holoviews_load.v0+json": "\n(function(root) {\n function now() {\n return new Date();\n }\n\n var force = true;\n\n if (typeof root._bokeh_onload_callbacks === \"undefined\" || force === true) {\n root._bokeh_onload_callbacks = [];\n root._bokeh_is_loading = undefined;\n }\n\n if (typeof (root._bokeh_timeout) === \"undefined\" || force === true) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, js_modules, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n if (js_modules == null) js_modules = [];\n\n root._bokeh_onload_callbacks.push(callback);\n if (root._bokeh_is_loading > 0) {\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n }\n if (js_urls.length === 0 && js_modules.length === 0) {\n run_callbacks();\n return null;\n }\n console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length;\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n\n function on_error() {\n console.error(\"failed to load \" + url);\n }\n\n for (var i = 0; i < css_urls.length; i++) {\n var url = css_urls[i];\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error;\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n }\n\n var skip = [];\n if (window.requirejs) {\n window.requirejs.config({'paths': {'tabulator': 'https://unpkg.com/tabulator-tables@4.9.3/dist/js/tabulator'}});\n require([], function() {\n })\n }\n if (((window['tabulator'] !== undefined) && (!(window['tabulator'] instanceof HTMLElement))) || window.requirejs) {\n var urls = ['https://unpkg.com/tabulator-tables@4.9.3/dist/js/tabulator.js', 'https://unpkg.com/moment@2.27.0/moment.js'];\n for (var i = 0; i < urls.length; i++) {\n skip.push(urls[i])\n }\n }\n for (var i = 0; i < js_urls.length; i++) {\n var url = js_urls[i];\n if (skip.indexOf(url) >= 0) { on_load(); continue; }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (var i = 0; i < js_modules.length; i++) {\n var url = js_modules[i];\n if (skip.indexOf(url) >= 0) { on_load(); continue; }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n if (!js_urls.length && !js_modules.length) {\n on_load()\n }\n };\n\n function inject_raw_css(css) {\n const element = document.createElement(\"style\");\n element.appendChild(document.createTextNode(css));\n document.body.appendChild(element);\n }\n\n var js_urls = [\"https://unpkg.com/tabulator-tables@4.9.3/dist/js/tabulator.js\", \"https://unpkg.com/moment@2.27.0/moment.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-2.3.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.3.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.3.1.min.js\", \"https://unpkg.com/@holoviz/panel@^0.11.3/dist/panel.min.js\"];\n var js_modules = [];\n var css_urls = [\"https://unpkg.com/tabulator-tables@4.9.3/dist/css/tabulator_simple.min.css\", \"https://unpkg.com/@holoviz/panel@0.11.3/dist/css/alerts.css\", \"https://unpkg.com/@holoviz/panel@0.11.3/dist/css/card.css\", \"https://unpkg.com/@holoviz/panel@0.11.3/dist/css/widgets.css\", \"https://unpkg.com/@holoviz/panel@0.11.3/dist/css/markdown.css\", \"https://unpkg.com/@holoviz/panel@0.11.3/dist/css/json.css\", \"https://unpkg.com/@holoviz/panel@0.11.3/dist/css/loading.css\", \"https://unpkg.com/@holoviz/panel@0.11.3/dist/css/dataframe.css\"];\n var inline_js = [\n function(Bokeh) {\n inject_raw_css(\"\\n .bk.pn-loading.arcs:before {\\n background-image: url(\\\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBzdHlsZT0ibWFyZ2luOiBhdXRvOyBiYWNrZ3JvdW5kOiBub25lOyBkaXNwbGF5OiBibG9jazsgc2hhcGUtcmVuZGVyaW5nOiBhdXRvOyIgdmlld0JveD0iMCAwIDEwMCAxMDAiIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaWRZTWlkIj4gIDxjaXJjbGUgY3g9IjUwIiBjeT0iNTAiIHI9IjMyIiBzdHJva2Utd2lkdGg9IjgiIHN0cm9rZT0iI2MzYzNjMyIgc3Ryb2tlLWRhc2hhcnJheT0iNTAuMjY1NDgyNDU3NDM2NjkgNTAuMjY1NDgyNDU3NDM2NjkiIGZpbGw9Im5vbmUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+ICAgIDxhbmltYXRlVHJhbnNmb3JtIGF0dHJpYnV0ZU5hbWU9InRyYW5zZm9ybSIgdHlwZT0icm90YXRlIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIgZHVyPSIxcyIga2V5VGltZXM9IjA7MSIgdmFsdWVzPSIwIDUwIDUwOzM2MCA1MCA1MCI+PC9hbmltYXRlVHJhbnNmb3JtPiAgPC9jaXJjbGU+PC9zdmc+\\\")\\n }\\n \");\n },\n function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\n function(Bokeh) {} // ensure no trailing comma for IE\n ];\n\n function run_inline_js() {\n if ((root.Bokeh !== undefined) || (force === true)) {\n for (var i = 0; i < inline_js.length; i++) {\n inline_js[i].call(root, root.Bokeh);\n }} else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n }\n }\n\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: BokehJS loaded, going straight to plotting\");\n run_inline_js();\n } else {\n load_libs(css_urls, js_urls, js_modules, function() {\n console.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n run_inline_js();\n });\n }\n}(window));" 212 | }, 213 | "metadata": {}, 214 | "output_type": "display_data" 215 | }, 216 | { 217 | "data": { 218 | "application/javascript": [ 219 | "\n", 220 | "if ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n", 221 | " window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n", 222 | "}\n", 223 | "\n", 224 | "\n", 225 | " function JupyterCommManager() {\n", 226 | " }\n", 227 | "\n", 228 | " JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n", 229 | " if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n", 230 | " var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n", 231 | " comm_manager.register_target(comm_id, function(comm) {\n", 232 | " comm.on_msg(msg_handler);\n", 233 | " });\n", 234 | " } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n", 235 | " window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n", 236 | " comm.onMsg = msg_handler;\n", 237 | " });\n", 238 | " } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n", 239 | " google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n", 240 | " var messages = comm.messages[Symbol.asyncIterator]();\n", 241 | " function processIteratorResult(result) {\n", 242 | " var message = result.value;\n", 243 | " console.log(message)\n", 244 | " var content = {data: message.data, comm_id};\n", 245 | " var buffers = []\n", 246 | " for (var buffer of message.buffers || []) {\n", 247 | " buffers.push(new DataView(buffer))\n", 248 | " }\n", 249 | " var metadata = message.metadata || {};\n", 250 | " var msg = {content, buffers, metadata}\n", 251 | " msg_handler(msg);\n", 252 | " return messages.next().then(processIteratorResult);\n", 253 | " }\n", 254 | " return messages.next().then(processIteratorResult);\n", 255 | " })\n", 256 | " }\n", 257 | " }\n", 258 | "\n", 259 | " JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n", 260 | " if (comm_id in window.PyViz.comms) {\n", 261 | " return window.PyViz.comms[comm_id];\n", 262 | " } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n", 263 | " var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n", 264 | " var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n", 265 | " if (msg_handler) {\n", 266 | " comm.on_msg(msg_handler);\n", 267 | " }\n", 268 | " } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n", 269 | " var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n", 270 | " comm.open();\n", 271 | " if (msg_handler) {\n", 272 | " comm.onMsg = msg_handler;\n", 273 | " }\n", 274 | " } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n", 275 | " var comm_promise = google.colab.kernel.comms.open(comm_id)\n", 276 | " comm_promise.then((comm) => {\n", 277 | " window.PyViz.comms[comm_id] = comm;\n", 278 | " if (msg_handler) {\n", 279 | " var messages = comm.messages[Symbol.asyncIterator]();\n", 280 | " function processIteratorResult(result) {\n", 281 | " var message = result.value;\n", 282 | " var content = {data: message.data};\n", 283 | " var metadata = message.metadata || {comm_id};\n", 284 | " var msg = {content, metadata}\n", 285 | " msg_handler(msg);\n", 286 | " return messages.next().then(processIteratorResult);\n", 287 | " }\n", 288 | " return messages.next().then(processIteratorResult);\n", 289 | " }\n", 290 | " }) \n", 291 | " var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n", 292 | " return comm_promise.then((comm) => {\n", 293 | " comm.send(data, metadata, buffers, disposeOnDone);\n", 294 | " });\n", 295 | " };\n", 296 | " var comm = {\n", 297 | " send: sendClosure\n", 298 | " };\n", 299 | " }\n", 300 | " window.PyViz.comms[comm_id] = comm;\n", 301 | " return comm;\n", 302 | " }\n", 303 | " window.PyViz.comm_manager = new JupyterCommManager();\n", 304 | " \n", 305 | "\n", 306 | "\n", 307 | "var JS_MIME_TYPE = 'application/javascript';\n", 308 | "var HTML_MIME_TYPE = 'text/html';\n", 309 | "var EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\n", 310 | "var CLASS_NAME = 'output';\n", 311 | "\n", 312 | "/**\n", 313 | " * Render data to the DOM node\n", 314 | " */\n", 315 | "function render(props, node) {\n", 316 | " var div = document.createElement(\"div\");\n", 317 | " var script = document.createElement(\"script\");\n", 318 | " node.appendChild(div);\n", 319 | " node.appendChild(script);\n", 320 | "}\n", 321 | "\n", 322 | "/**\n", 323 | " * Handle when a new output is added\n", 324 | " */\n", 325 | "function handle_add_output(event, handle) {\n", 326 | " var output_area = handle.output_area;\n", 327 | " var output = handle.output;\n", 328 | " if ((output.data == undefined) || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n", 329 | " return\n", 330 | " }\n", 331 | " var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n", 332 | " var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n", 333 | " if (id !== undefined) {\n", 334 | " var nchildren = toinsert.length;\n", 335 | " var html_node = toinsert[nchildren-1].children[0];\n", 336 | " html_node.innerHTML = output.data[HTML_MIME_TYPE];\n", 337 | " var scripts = [];\n", 338 | " var nodelist = html_node.querySelectorAll(\"script\");\n", 339 | " for (var i in nodelist) {\n", 340 | " if (nodelist.hasOwnProperty(i)) {\n", 341 | " scripts.push(nodelist[i])\n", 342 | " }\n", 343 | " }\n", 344 | "\n", 345 | " scripts.forEach( function (oldScript) {\n", 346 | " var newScript = document.createElement(\"script\");\n", 347 | " var attrs = [];\n", 348 | " var nodemap = oldScript.attributes;\n", 349 | " for (var j in nodemap) {\n", 350 | " if (nodemap.hasOwnProperty(j)) {\n", 351 | " attrs.push(nodemap[j])\n", 352 | " }\n", 353 | " }\n", 354 | " attrs.forEach(function(attr) { newScript.setAttribute(attr.name, attr.value) });\n", 355 | " newScript.appendChild(document.createTextNode(oldScript.innerHTML));\n", 356 | " oldScript.parentNode.replaceChild(newScript, oldScript);\n", 357 | " });\n", 358 | " if (JS_MIME_TYPE in output.data) {\n", 359 | " toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n", 360 | " }\n", 361 | " output_area._hv_plot_id = id;\n", 362 | " if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n", 363 | " window.PyViz.plot_index[id] = Bokeh.index[id];\n", 364 | " } else {\n", 365 | " window.PyViz.plot_index[id] = null;\n", 366 | " }\n", 367 | " } else if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n", 368 | " var bk_div = document.createElement(\"div\");\n", 369 | " bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n", 370 | " var script_attrs = bk_div.children[0].attributes;\n", 371 | " for (var i = 0; i < script_attrs.length; i++) {\n", 372 | " toinsert[toinsert.length - 1].childNodes[1].setAttribute(script_attrs[i].name, script_attrs[i].value);\n", 373 | " }\n", 374 | " // store reference to server id on output_area\n", 375 | " output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n", 376 | " }\n", 377 | "}\n", 378 | "\n", 379 | "/**\n", 380 | " * Handle when an output is cleared or removed\n", 381 | " */\n", 382 | "function handle_clear_output(event, handle) {\n", 383 | " var id = handle.cell.output_area._hv_plot_id;\n", 384 | " var server_id = handle.cell.output_area._bokeh_server_id;\n", 385 | " if (((id === undefined) || !(id in PyViz.plot_index)) && (server_id !== undefined)) { return; }\n", 386 | " var comm = window.PyViz.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n", 387 | " if (server_id !== null) {\n", 388 | " comm.send({event_type: 'server_delete', 'id': server_id});\n", 389 | " return;\n", 390 | " } else if (comm !== null) {\n", 391 | " comm.send({event_type: 'delete', 'id': id});\n", 392 | " }\n", 393 | " delete PyViz.plot_index[id];\n", 394 | " if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n", 395 | " var doc = window.Bokeh.index[id].model.document\n", 396 | " doc.clear();\n", 397 | " const i = window.Bokeh.documents.indexOf(doc);\n", 398 | " if (i > -1) {\n", 399 | " window.Bokeh.documents.splice(i, 1);\n", 400 | " }\n", 401 | " }\n", 402 | "}\n", 403 | "\n", 404 | "/**\n", 405 | " * Handle kernel restart event\n", 406 | " */\n", 407 | "function handle_kernel_cleanup(event, handle) {\n", 408 | " delete PyViz.comms[\"hv-extension-comm\"];\n", 409 | " window.PyViz.plot_index = {}\n", 410 | "}\n", 411 | "\n", 412 | "/**\n", 413 | " * Handle update_display_data messages\n", 414 | " */\n", 415 | "function handle_update_output(event, handle) {\n", 416 | " handle_clear_output(event, {cell: {output_area: handle.output_area}})\n", 417 | " handle_add_output(event, handle)\n", 418 | "}\n", 419 | "\n", 420 | "function register_renderer(events, OutputArea) {\n", 421 | " function append_mime(data, metadata, element) {\n", 422 | " // create a DOM node to render to\n", 423 | " var toinsert = this.create_output_subarea(\n", 424 | " metadata,\n", 425 | " CLASS_NAME,\n", 426 | " EXEC_MIME_TYPE\n", 427 | " );\n", 428 | " this.keyboard_manager.register_events(toinsert);\n", 429 | " // Render to node\n", 430 | " var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n", 431 | " render(props, toinsert[0]);\n", 432 | " element.append(toinsert);\n", 433 | " return toinsert\n", 434 | " }\n", 435 | "\n", 436 | " events.on('output_added.OutputArea', handle_add_output);\n", 437 | " events.on('output_updated.OutputArea', handle_update_output);\n", 438 | " events.on('clear_output.CodeCell', handle_clear_output);\n", 439 | " events.on('delete.Cell', handle_clear_output);\n", 440 | " events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n", 441 | "\n", 442 | " OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n", 443 | " safe: true,\n", 444 | " index: 0\n", 445 | " });\n", 446 | "}\n", 447 | "\n", 448 | "if (window.Jupyter !== undefined) {\n", 449 | " try {\n", 450 | " var events = require('base/js/events');\n", 451 | " var OutputArea = require('notebook/js/outputarea').OutputArea;\n", 452 | " if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n", 453 | " register_renderer(events, OutputArea);\n", 454 | " }\n", 455 | " } catch(err) {\n", 456 | " }\n", 457 | "}\n" 458 | ], 459 | "application/vnd.holoviews_load.v0+json": "\nif ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n}\n\n\n function JupyterCommManager() {\n }\n\n JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n comm_manager.register_target(comm_id, function(comm) {\n comm.on_msg(msg_handler);\n });\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n comm.onMsg = msg_handler;\n });\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n console.log(message)\n var content = {data: message.data, comm_id};\n var buffers = []\n for (var buffer of message.buffers || []) {\n buffers.push(new DataView(buffer))\n }\n var metadata = message.metadata || {};\n var msg = {content, buffers, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n })\n }\n }\n\n JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n if (comm_id in window.PyViz.comms) {\n return window.PyViz.comms[comm_id];\n } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n if (msg_handler) {\n comm.on_msg(msg_handler);\n }\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n comm.open();\n if (msg_handler) {\n comm.onMsg = msg_handler;\n }\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n var comm_promise = google.colab.kernel.comms.open(comm_id)\n comm_promise.then((comm) => {\n window.PyViz.comms[comm_id] = comm;\n if (msg_handler) {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data};\n var metadata = message.metadata || {comm_id};\n var msg = {content, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n }\n }) \n var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n return comm_promise.then((comm) => {\n comm.send(data, metadata, buffers, disposeOnDone);\n });\n };\n var comm = {\n send: sendClosure\n };\n }\n window.PyViz.comms[comm_id] = comm;\n return comm;\n }\n window.PyViz.comm_manager = new JupyterCommManager();\n \n\n\nvar JS_MIME_TYPE = 'application/javascript';\nvar HTML_MIME_TYPE = 'text/html';\nvar EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\nvar CLASS_NAME = 'output';\n\n/**\n * Render data to the DOM node\n */\nfunction render(props, node) {\n var div = document.createElement(\"div\");\n var script = document.createElement(\"script\");\n node.appendChild(div);\n node.appendChild(script);\n}\n\n/**\n * Handle when a new output is added\n */\nfunction handle_add_output(event, handle) {\n var output_area = handle.output_area;\n var output = handle.output;\n if ((output.data == undefined) || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n return\n }\n var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n if (id !== undefined) {\n var nchildren = toinsert.length;\n var html_node = toinsert[nchildren-1].children[0];\n html_node.innerHTML = output.data[HTML_MIME_TYPE];\n var scripts = [];\n var nodelist = html_node.querySelectorAll(\"script\");\n for (var i in nodelist) {\n if (nodelist.hasOwnProperty(i)) {\n scripts.push(nodelist[i])\n }\n }\n\n scripts.forEach( function (oldScript) {\n var newScript = document.createElement(\"script\");\n var attrs = [];\n var nodemap = oldScript.attributes;\n for (var j in nodemap) {\n if (nodemap.hasOwnProperty(j)) {\n attrs.push(nodemap[j])\n }\n }\n attrs.forEach(function(attr) { newScript.setAttribute(attr.name, attr.value) });\n newScript.appendChild(document.createTextNode(oldScript.innerHTML));\n oldScript.parentNode.replaceChild(newScript, oldScript);\n });\n if (JS_MIME_TYPE in output.data) {\n toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n }\n output_area._hv_plot_id = id;\n if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n window.PyViz.plot_index[id] = Bokeh.index[id];\n } else {\n window.PyViz.plot_index[id] = null;\n }\n } else if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n var bk_div = document.createElement(\"div\");\n bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n var script_attrs = bk_div.children[0].attributes;\n for (var i = 0; i < script_attrs.length; i++) {\n toinsert[toinsert.length - 1].childNodes[1].setAttribute(script_attrs[i].name, script_attrs[i].value);\n }\n // store reference to server id on output_area\n output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n }\n}\n\n/**\n * Handle when an output is cleared or removed\n */\nfunction handle_clear_output(event, handle) {\n var id = handle.cell.output_area._hv_plot_id;\n var server_id = handle.cell.output_area._bokeh_server_id;\n if (((id === undefined) || !(id in PyViz.plot_index)) && (server_id !== undefined)) { return; }\n var comm = window.PyViz.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n if (server_id !== null) {\n comm.send({event_type: 'server_delete', 'id': server_id});\n return;\n } else if (comm !== null) {\n comm.send({event_type: 'delete', 'id': id});\n }\n delete PyViz.plot_index[id];\n if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n var doc = window.Bokeh.index[id].model.document\n doc.clear();\n const i = window.Bokeh.documents.indexOf(doc);\n if (i > -1) {\n window.Bokeh.documents.splice(i, 1);\n }\n }\n}\n\n/**\n * Handle kernel restart event\n */\nfunction handle_kernel_cleanup(event, handle) {\n delete PyViz.comms[\"hv-extension-comm\"];\n window.PyViz.plot_index = {}\n}\n\n/**\n * Handle update_display_data messages\n */\nfunction handle_update_output(event, handle) {\n handle_clear_output(event, {cell: {output_area: handle.output_area}})\n handle_add_output(event, handle)\n}\n\nfunction register_renderer(events, OutputArea) {\n function append_mime(data, metadata, element) {\n // create a DOM node to render to\n var toinsert = this.create_output_subarea(\n metadata,\n CLASS_NAME,\n EXEC_MIME_TYPE\n );\n this.keyboard_manager.register_events(toinsert);\n // Render to node\n var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n render(props, toinsert[0]);\n element.append(toinsert);\n return toinsert\n }\n\n events.on('output_added.OutputArea', handle_add_output);\n events.on('output_updated.OutputArea', handle_update_output);\n events.on('clear_output.CodeCell', handle_clear_output);\n events.on('delete.Cell', handle_clear_output);\n events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n\n OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n safe: true,\n index: 0\n });\n}\n\nif (window.Jupyter !== undefined) {\n try {\n var events = require('base/js/events');\n var OutputArea = require('notebook/js/outputarea').OutputArea;\n if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n register_renderer(events, OutputArea);\n }\n } catch(err) {\n }\n}\n" 460 | }, 461 | "metadata": {}, 462 | "output_type": "display_data" 463 | }, 464 | { 465 | "data": {}, 466 | "metadata": {}, 467 | "output_type": "display_data" 468 | }, 469 | { 470 | "data": { 471 | "application/vnd.holoviews_exec.v0+json": "", 472 | "text/html": [ 473 | "
\n", 474 | "\n", 475 | "\n", 476 | "\n", 477 | "\n", 478 | "\n", 479 | "
\n", 480 | "
\n", 481 | "" 505 | ], 506 | "text/plain": [ 507 | "Card(header=Markdown, header_background='#f0f0f0', width=980)\n", 508 | " [0] Row\n", 509 | " [0] Column\n", 510 | " [0] Bokeh(Figure)\n", 511 | " [1] Row\n", 512 | " [0] Card(header=Markdown, header_background='#f0f0f0', width=500)\n", 513 | " [0] Bokeh(DataTable, height=130, sizing_mode='fixed', width=485)\n", 514 | " [1] Card(header_background='#f0f0f0', height_policy='fit', width=220)\n", 515 | " [0] Column\n", 516 | " [0] Button(name='Add Frame', width=200)\n", 517 | " [1] Row\n", 518 | " [0] Button(name='Clear Pitch', width=75)\n", 519 | " [1] Button(name='Remove Last', width=105)\n", 520 | " [2] FileDownload(callback== 11:\n", 685 | " draw_toolA.add = False\n", 686 | " else:\n", 687 | " draw_toolA.add = True\n", 688 | " \n", 689 | "def lock_tool_H(attr, old, new):\n", 690 | " if len(sourceH.data['x']) >= 11:\n", 691 | " draw_toolH.add = False\n", 692 | " else:\n", 693 | " draw_toolH.add = True\n", 694 | " \n", 695 | "sourceA.on_change('data', lock_tool_A)\n", 696 | "sourceH.on_change('data', lock_tool_H)\n", 697 | "\n", 698 | "player_ids = ['H_0', 'H_1', 'H_2', 'H_3', 'H_4', 'H_5', 'H_6', 'H_7', 'H_8',\n", 699 | " 'H_9', 'H_10', 'H_11', 'H_12', 'H_13', 'H_14', 'H_15',\n", 700 | " 'A_0', 'A_1', 'A_2', 'A_3', 'A_4', 'A_5', 'A_6','A_7', 'A_8',\n", 701 | " 'A_9', 'A_10', 'A_11', 'A_12', 'A_13', 'A_14', 'A_15']\n", 702 | "\n", 703 | "table_out_columns = ['timestamp', 'type_name', 'player_name', 'result_name',\n", 704 | " 'Ball_x', 'Ball_y', 'H_0_x', 'H_0_y',\n", 705 | " 'H_1_x', 'H_1_y', 'H_2_x', 'H_2_y', 'H_3_x', 'H_3_y', 'H_4_x',\n", 706 | " 'H_4_y', 'H_5_x', 'H_5_y', 'H_6_x', 'H_6_y', 'H_7_x', 'H_7_y',\n", 707 | " 'H_8_x', 'H_8_y', 'H_9_x', 'H_9_y', 'H_10_x', 'H_10_y',\n", 708 | " 'A_0_x', 'A_0_y', 'A_1_x', 'A_1_y', 'A_2_x',\n", 709 | " 'A_2_y', 'A_3_x', 'A_3_y', 'A_4_x', 'A_4_y', 'A_5_x', 'A_5_y',\n", 710 | " 'A_6_x', 'A_6_y', 'A_7_x', 'A_7_y', 'A_8_x', 'A_8_y', 'A_9_x',\n", 711 | " 'A_9_y', 'A_10_x', 'A_10_y']\n", 712 | "\n", 713 | "sourceOUT = ColumnDataSource({col: [] for col in table_out_columns})\n", 714 | "\n", 715 | "# string columns\n", 716 | "table_out_tablecolumns = [TableColumn(field=col, title=col) for col in table_out_columns[:4]]\n", 717 | "# formatter for numerical columns\n", 718 | "table_out_tablecolumns.extend([TableColumn(field=col, title=col, formatter=formatter)\n", 719 | " for col in table_out_columns[4:]])\n", 720 | "\n", 721 | "tableOUT = DataTable(source=sourceOUT,\n", 722 | " columns=table_out_tablecolumns,\n", 723 | " editable=True, height=100, width=pitch_width-50)\n", 724 | "\n", 725 | "\n", 726 | "draw_toolH = PointDrawTool(renderers=[rendererH],\n", 727 | " empty_value='H', num_objects=11, custom_icon='static/icons/Pink_H_Icon.png')\n", 728 | "\n", 729 | "draw_toolA = PointDrawTool(renderers=[rendererA],\n", 730 | " empty_value='A', num_objects=11, custom_icon='static/icons/Blue_A_Icon.png')\n", 731 | "\n", 732 | "draw_toolB = PointDrawTool(renderers=[rendererB],\n", 733 | " empty_value='B', num_objects=1, custom_icon='static/icons/Black_B_Icon.png')\n", 734 | "\n", 735 | "p.add_tools(draw_toolH)\n", 736 | "p.add_tools(draw_toolA)\n", 737 | "p.add_tools(draw_toolB)\n", 738 | "\n", 739 | "p.toolbar.active_tap = draw_toolH\n", 740 | "\n", 741 | "global dfs\n", 742 | "dfs = []\n", 743 | "\n", 744 | "def remove_last_frame(event):\n", 745 | " global dfs\n", 746 | " if len(dfs) > 1:\n", 747 | " dfs = dfs[:-1]\n", 748 | " df = pd.concat(dfs).reset_index(drop=True)\n", 749 | " sourceOUT.data = dict(df)\n", 750 | " elif len(dfs) == 1:\n", 751 | " dfs = []\n", 752 | " sourceOUT.data = {col: [] for col in table_out_columns}\n", 753 | "\n", 754 | "def add_frame(event):\n", 755 | " df = pd.concat([pd.DataFrame(sourceH.data), pd.DataFrame(sourceA.data), pd.DataFrame(sourceB.data)])\n", 756 | " df['name'] = df.team + '_' + df.index.astype('str')\n", 757 | " df = df.drop('team', axis=1)\n", 758 | " df = pd.melt(df, id_vars=['name'])\n", 759 | " df['name'] = df['name'] + '_' + df['variable']\n", 760 | " df = df.drop('variable', axis=1).set_index('name').T\n", 761 | " df = df.rename({'B_0_x': 'Ball_x', 'B_0_y': 'Ball_y'}, axis=1)\n", 762 | " df = df.reset_index(drop=True)\n", 763 | " df = df.reindex(table_out_columns, axis=1)\n", 764 | "\n", 765 | " if timestamp_input.value != '':\n", 766 | " df['timestamp'] = timestamp_input.value\n", 767 | " else:\n", 768 | " df['timestamp'] = None\n", 769 | " \n", 770 | " if event_autocomplete.value != []:\n", 771 | " df['type_name'] = event_autocomplete.value[0]\n", 772 | " else:\n", 773 | " df['type_name'] = None\n", 774 | " \n", 775 | " if success_autocomplete.value != []:\n", 776 | " df['result_name'] = success_autocomplete.value[0]\n", 777 | " else:\n", 778 | " df['result_name'] = None\n", 779 | " \n", 780 | " if player_autocomplete.value != []:\n", 781 | " df['player_name'] = player_autocomplete.value[0]\n", 782 | " else:\n", 783 | " df['player_name'] = None\n", 784 | " \n", 785 | " dfs.append(df) \n", 786 | " df = pd.concat(dfs).reset_index(drop=True)\n", 787 | " \n", 788 | " sourceOUT.data = dict(df)\n", 789 | " \n", 790 | "def download_frame():\n", 791 | " if len(dfs) > 0:\n", 792 | " dl_df = pd.concat(dfs).reset_index(drop=True).round(2)\n", 793 | " sio = StringIO()\n", 794 | " dl_df.to_csv(sio)\n", 795 | " sio.seek(0)\n", 796 | " return sio\n", 797 | "\n", 798 | "add_frame_btn = pn.widgets.Button(name='Add Frame', width=200)\n", 799 | "add_frame_btn.on_click(add_frame)\n", 800 | "\n", 801 | "remove_frame_btn = pn.widgets.Button(name='Remove Last', width=105)\n", 802 | "remove_frame_btn.on_click(remove_last_frame)\n", 803 | "\n", 804 | "dl_button = pn.widgets.FileDownload(callback=download_frame, filename='frames.csv',\n", 805 | " button_type='default', width=200, margin=(10, 12, 10, 10))\n", 806 | "\n", 807 | "md_string=\"\"\"HOME AWAY and BALL\"\"\"\n", 808 | "\n", 809 | "timestamp_input = pn.widgets.TextInput(placeholder='Optional: timestamp (in m:ss)', width=200)\n", 810 | "\n", 811 | "event_autocomplete = pn.widgets.MultiChoice(\n", 812 | " options=EVENT_NAMES,\n", 813 | " max_items=1, width=200, margin=(0, 10, 0, 10),\n", 814 | " name='what event happened?')\n", 815 | "\n", 816 | "player_autocomplete = pn.widgets.MultiChoice(\n", 817 | " options=player_ids,\n", 818 | " max_items=1, width=200, margin=(0, 10, 0, 10),\n", 819 | " name='who made the event?')\n", 820 | "\n", 821 | "success_autocomplete = pn.widgets.MultiChoice(\n", 822 | " options=['success', 'failure'],\n", 823 | " max_items=1, width=200, margin=(0, 10, 10, 10),\n", 824 | " name='was the event successful?')\n", 825 | "\n", 826 | "def clear(event):\n", 827 | " sourceH.data = {'x': [], 'y': [], 'team': []}\n", 828 | " sourceA.data = {'x': [], 'y': [], 'team': []}\n", 829 | " sourceB.data = {'x': [], 'y': [], 'team': []}\n", 830 | " timestamp_input.value = ''\n", 831 | " event_autocomplete.value = []\n", 832 | " player_autocomplete.value = []\n", 833 | " success_autocomplete.value = []\n", 834 | "\n", 835 | "clear_button = pn.widgets.Button(name='Clear Pitch', width=75)\n", 836 | "clear_button.on_click(clear)\n", 837 | "\n", 838 | "\n", 839 | "layout = pn.Card(pn.Row(pn.Column(pn.pane.Bokeh(p),\n", 840 | " pn.Row(pn.Card(pn.pane.Bokeh(tableOUT, height=130, width=485),\n", 841 | " header_background='#f0f0f0',\n", 842 | " header=pn.pane.Markdown(\"\"\"Added Frames\"\"\"),\n", 843 | " width=500),\n", 844 | " pn.Card(pn.Column(add_frame_btn,\n", 845 | " pn.Row(clear_button, remove_frame_btn),\n", 846 | " dl_button),\n", 847 | " height_policy='fit', width=220,\n", 848 | " header_background='#f0f0f0'))\n", 849 | " ),\n", 850 | " \n", 851 | " pn.Card(pn.pane.Bokeh(tableH),\n", 852 | " pn.pane.Bokeh(tableA),\n", 853 | " pn.pane.Bokeh(tableB),\n", 854 | " pn.Column(timestamp_input, event_autocomplete, player_autocomplete, success_autocomplete, width=225, margin=(0, 0, 0, 0)),\n", 855 | " header_background='#f0f0f0',\n", 856 | " header=pn.pane.Markdown(md_string),\n", 857 | " width=225)),\n", 858 | " header_background='#f0f0f0',\n", 859 | " header=pn.pane.Markdown(\"\"\"

Positional Data and Event Tagger

\"\"\", width=250+pitch_width),\n", 860 | " width=245+pitch_width\n", 861 | " )\n", 862 | "\n", 863 | "layout.servable()" 864 | ] 865 | }, 866 | { 867 | "cell_type": "code", 868 | "execution_count": null, 869 | "metadata": {}, 870 | "outputs": [], 871 | "source": [] 872 | } 873 | ], 874 | "metadata": { 875 | "kernelspec": { 876 | "display_name": "Python 3", 877 | "language": "python", 878 | "name": "python3" 879 | }, 880 | "language_info": { 881 | "codemirror_mode": { 882 | "name": "ipython", 883 | "version": 3 884 | }, 885 | "file_extension": ".py", 886 | "mimetype": "text/x-python", 887 | "name": "python", 888 | "nbconvert_exporter": "python", 889 | "pygments_lexer": "ipython3", 890 | "version": "3.7.3" 891 | } 892 | }, 893 | "nbformat": 4, 894 | "nbformat_minor": 2 895 | } 896 | --------------------------------------------------------------------------------