├── 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 | 
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 |
--------------------------------------------------------------------------------