Skip to content

Commit 9a7f3f0

Browse files
authored
Merge pull request #189 from jupyter-widgets/add_complex_app_section
Add complex applications authoring section
2 parents ec20388 + d3e2184 commit 9a7f3f0

File tree

5 files changed

+356
-0
lines changed

5 files changed

+356
-0
lines changed
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"id": "f8da2924-07bb-4e1e-9372-6ff2685a5312",
6+
"metadata": {},
7+
"source": [
8+
"# Building complex widget libraries"
9+
]
10+
},
11+
{
12+
"cell_type": "markdown",
13+
"id": "e31c1960-d4db-4681-8cbf-5d88b82c5e50",
14+
"metadata": {
15+
"tags": []
16+
},
17+
"source": [
18+
"## The problem\n",
19+
"Often when composing widgets into an application, it becomes cumbersome to find a good way of managing state and keeping track of variables as the application grows. This notebooks aims to provide gentle guidance for managing complex application libraries. Please bear in mind these are merely **suggestions** and are by no means the only, or even best, way of going about this."
20+
]
21+
},
22+
{
23+
"cell_type": "markdown",
24+
"id": "82d1eef5-52fe-4b37-8c80-cb812cbd4324",
25+
"metadata": {},
26+
"source": [
27+
"### The current approach"
28+
]
29+
},
30+
{
31+
"cell_type": "code",
32+
"execution_count": null,
33+
"id": "5a7f24bc-4df9-481a-a5c3-380485d5ff35",
34+
"metadata": {},
35+
"outputs": [],
36+
"source": [
37+
"# Generating data\n",
38+
"import numpy as np\n",
39+
"import pandas as pd\n",
40+
"\n",
41+
"np.random.seed(0)\n",
42+
"p_t, n = 100, 260\n",
43+
"stock_df = pd.DataFrame({f'Stock {i}': p_t + np.round(np.random.standard_normal(n).cumsum(), 2) for i in range(10)})"
44+
]
45+
},
46+
{
47+
"cell_type": "code",
48+
"execution_count": null,
49+
"id": "488c6461-0e3c-4234-ad4c-f03ef6115996",
50+
"metadata": {},
51+
"outputs": [],
52+
"source": [
53+
"# Imports\n",
54+
"from bqplot import LinearScale, Axis, Figure, Lines, CATEGORY10\n",
55+
"from ipywidgets import HBox, VBox, Layout, HTML\n",
56+
"from ipydatagrid import DataGrid\n",
57+
" \n",
58+
"# Setting up the data grid\n",
59+
"stock_grid = DataGrid(stock_df, selection_mode='column')\n",
60+
"\n",
61+
"# Creating the bqplot chart objects\n",
62+
"sc_x = LinearScale()\n",
63+
"sc_y = LinearScale()\n",
64+
"line = Lines(x=[], y=[], labels=['Fake stock price'], display_legend=True,\n",
65+
" scales={'x': sc_x, 'y': sc_y})\n",
66+
"ax_x = Axis(scale=sc_x, label='Index')\n",
67+
"ax_y = Axis(scale=sc_y, orientation='vertical', label='y-value')\n",
68+
"fig = Figure(marks=[line], axes=[ax_x, ax_y], title='Line Chart', layout=Layout(flex='1 1 auto', width='100%'))\n",
69+
"\n",
70+
"# Creating the application title\n",
71+
"app_title = HTML(value=\"<h1 style='color: salmon'>My complex application</h1><h2>Select a column to plot it</h2>\")"
72+
]
73+
},
74+
{
75+
"cell_type": "code",
76+
"execution_count": null,
77+
"id": "4d84c7b3-451c-4c88-b59e-4d0ae3d578e6",
78+
"metadata": {},
79+
"outputs": [],
80+
"source": [
81+
"# Define callbacks\n",
82+
"def plot_stock(*args):\n",
83+
" line.y = stock_grid.selected_cell_values\n",
84+
" line.x = range(len(line.y))\n",
85+
" column_index = stock_grid.selections[0]['c1']\n",
86+
" line.labels = [stock_df.columns[column_index]]\n",
87+
" line.colors = [CATEGORY10[np.random.randint(0, len(CATEGORY10)) % len(CATEGORY10)]]\n",
88+
" \n",
89+
"# Event listener for cell click\n",
90+
"stock_grid.observe(plot_stock, names='selections')"
91+
]
92+
},
93+
{
94+
"cell_type": "code",
95+
"execution_count": null,
96+
"id": "79e135e3-b37c-457b-a268-3695a3def263",
97+
"metadata": {},
98+
"outputs": [],
99+
"source": [
100+
"VBox([\n",
101+
" app_title,\n",
102+
" HBox(\n",
103+
" [stock_grid, fig]\n",
104+
" )\n",
105+
"])"
106+
]
107+
},
108+
{
109+
"cell_type": "markdown",
110+
"id": "5c635ec7-c85e-425b-ab5e-f3e4ad937a37",
111+
"metadata": {},
112+
"source": [
113+
"### A more structured approach"
114+
]
115+
},
116+
{
117+
"cell_type": "code",
118+
"execution_count": null,
119+
"id": "47f6bfa5-b044-4da2-bac8-d3066ac1532d",
120+
"metadata": {},
121+
"outputs": [],
122+
"source": [
123+
"class Chart:\n",
124+
" def __init__(self, figure_title='Line Chart'):\n",
125+
" self._sc_x = LinearScale()\n",
126+
" self._sc_y = LinearScale()\n",
127+
" self._line = Lines(x=[], y=[], labels=['Fake stock price'], display_legend=True,\n",
128+
" scales={'x': self._sc_x, 'y': self._sc_y})\n",
129+
" self._ax_x = Axis(scale=self._sc_x, label='Index')\n",
130+
" self._ax_y = Axis(scale=self._sc_y, orientation='vertical', label='y-value')\n",
131+
" self._fig = Figure(marks=[self._line], axes=[self._ax_x, self._ax_y], title=figure_title, layout=Layout(flex='1 1 auto', width='100%'))\n",
132+
" \n",
133+
" def get_figure(self):\n",
134+
" return self._fig\n",
135+
" \n",
136+
" def get_line(self):\n",
137+
" return self._line\n",
138+
" \n",
139+
" def set_line(self, x, y, labels, colors):\n",
140+
" self._line.x = x\n",
141+
" self._line.y = y\n",
142+
" self._line.labels = labels\n",
143+
" self._line.colors = colors"
144+
]
145+
},
146+
{
147+
"cell_type": "code",
148+
"execution_count": null,
149+
"id": "edaf116c-d873-4a24-ba8c-9536de90e4cc",
150+
"metadata": {},
151+
"outputs": [],
152+
"source": [
153+
"from IPython.display import display\n",
154+
"\n",
155+
"class MyApplication:\n",
156+
" def __init__(self, data, application_title='My complex application'):\n",
157+
" self.dataframe = data\n",
158+
" self.datagrid = self.process_data(data)\n",
159+
" self.chart = Chart()\n",
160+
" self.app_title = HTML(value=f\"<h1 style='color: salmon'>{application_title}</h1><h2>Select a column to plot it</h2>\")\n",
161+
" self.run_application()\n",
162+
" \n",
163+
" def process_data(self, dataframe):\n",
164+
" return DataGrid(dataframe, selection_mode='column')\n",
165+
" \n",
166+
" def generate_layout(self):\n",
167+
" return VBox([self.app_title, HBox([self.datagrid, self.chart.get_figure()])])\n",
168+
" \n",
169+
" def setup_event_handlers(self):\n",
170+
" self.datagrid.observe(self.plot_stock, names='selections')\n",
171+
" \n",
172+
" def run_application(self):\n",
173+
" self.setup_event_handlers()\n",
174+
" display(self.generate_layout())\n",
175+
" \n",
176+
" # Callbacks section\n",
177+
" def plot_stock(self, *args):\n",
178+
" column_index = self.datagrid.selections[0]['c1']\n",
179+
" line = self.chart.get_line()\n",
180+
" selected_values = self.datagrid.selected_cell_values\n",
181+
" self.chart.set_line(\n",
182+
" range(len(selected_values)), \n",
183+
" selected_values, \n",
184+
" [self.dataframe.columns[column_index]], \n",
185+
" [CATEGORY10[np.random.randint(0, len(CATEGORY10)) % len(CATEGORY10)]]\n",
186+
" )"
187+
]
188+
},
189+
{
190+
"cell_type": "code",
191+
"execution_count": null,
192+
"id": "05fdd2fa-ecac-4b1e-add4-362a8814f491",
193+
"metadata": {},
194+
"outputs": [],
195+
"source": [
196+
"app = MyApplication(data=stock_df, application_title=\"An alternative approach\")"
197+
]
198+
}
199+
],
200+
"metadata": {
201+
"kernelspec": {
202+
"display_name": "Python 3 (ipykernel)",
203+
"language": "python",
204+
"name": "python3"
205+
},
206+
"language_info": {
207+
"codemirror_mode": {
208+
"name": "ipython",
209+
"version": 3
210+
},
211+
"file_extension": ".py",
212+
"mimetype": "text/x-python",
213+
"name": "python",
214+
"nbconvert_exporter": "python",
215+
"pygments_lexer": "ipython3",
216+
"version": "3.9.13"
217+
}
218+
},
219+
"nbformat": 4,
220+
"nbformat_minor": 5
221+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"id": "413439cf-360f-435b-acfe-4b1cc9042773",
6+
"metadata": {},
7+
"source": [
8+
"# Refactoring the class-based implementation"
9+
]
10+
},
11+
{
12+
"cell_type": "code",
13+
"execution_count": null,
14+
"id": "36cd43c4-9df2-40cf-a4f0-66e660af7035",
15+
"metadata": {},
16+
"outputs": [],
17+
"source": [
18+
"# Generating data\n",
19+
"import numpy as np\n",
20+
"import pandas as pd\n",
21+
"\n",
22+
"np.random.seed(0)\n",
23+
"p_t, n = 100, 260\n",
24+
"stock_df = pd.DataFrame({f'Stock {i}': p_t + np.round(np.random.standard_normal(n).cumsum(), 2) for i in range(10)})"
25+
]
26+
},
27+
{
28+
"cell_type": "code",
29+
"execution_count": null,
30+
"id": "bde0b4dd-96bf-441c-a7b1-3e4ab37cd7c5",
31+
"metadata": {},
32+
"outputs": [],
33+
"source": [
34+
"from my_application import MyApplication"
35+
]
36+
},
37+
{
38+
"cell_type": "code",
39+
"execution_count": null,
40+
"id": "e04c0045-f815-4c7b-a0a4-dee9c004275a",
41+
"metadata": {},
42+
"outputs": [],
43+
"source": [
44+
"app = MyApplication(stock_df)"
45+
]
46+
}
47+
],
48+
"metadata": {
49+
"kernelspec": {
50+
"display_name": "Python 3 (ipykernel)",
51+
"language": "python",
52+
"name": "python3"
53+
},
54+
"language_info": {
55+
"codemirror_mode": {
56+
"name": "ipython",
57+
"version": 3
58+
},
59+
"file_extension": ".py",
60+
"mimetype": "text/x-python",
61+
"name": "python",
62+
"nbconvert_exporter": "python",
63+
"pygments_lexer": "ipython3",
64+
"version": "3.9.13"
65+
}
66+
},
67+
"nbformat": 4,
68+
"nbformat_minor": 5
69+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .application import MyApplication
2+
3+
__all__ = ["MyApplication"]
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from IPython.display import display
2+
from ipywidgets import HBox, VBox, HTML
3+
from ipydatagrid import DataGrid
4+
from bqplot import CATEGORY10
5+
from .chart import Chart
6+
import numpy as np
7+
8+
class MyApplication:
9+
def __init__(self, data, application_title='My complex application'):
10+
self.dataframe = data
11+
self.datagrid = self.process_data(data)
12+
self.chart = Chart()
13+
self.app_title = HTML(value=f"<h1 style='color: salmon'>{application_title}</h1><h2>Select a column to plot it</h2>")
14+
self.run_application()
15+
16+
def process_data(self, dataframe):
17+
return DataGrid(dataframe, selection_mode='column')
18+
19+
def generate_layout(self):
20+
return VBox([self.app_title, HBox([self.datagrid, self.chart.get_figure()])])
21+
22+
def setup_event_handlers(self):
23+
self.datagrid.observe(self.plot_stock, names='selections')
24+
25+
def run_application(self):
26+
self.setup_event_handlers()
27+
display(self.generate_layout())
28+
29+
# Callbacks section
30+
def plot_stock(self, *args):
31+
column_index = self.datagrid.selections[0]['c1']
32+
line = self.chart.get_line()
33+
selected_values = self.datagrid.selected_cell_values
34+
self.chart.set_line(
35+
range(len(selected_values)),
36+
selected_values,
37+
[self.dataframe.columns[column_index]],
38+
[CATEGORY10[np.random.randint(0, len(CATEGORY10)) % len(CATEGORY10)]]
39+
)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from bqplot import LinearScale, Axis, Figure, Lines, CATEGORY10
2+
from ipywidgets import Layout
3+
4+
class Chart:
5+
def __init__(self, figure_title='Line Chart'):
6+
self._sc_x = LinearScale()
7+
self._sc_y = LinearScale()
8+
self._line = Lines(x=[], y=[], labels=['Fake stock price'], display_legend=True,
9+
scales={'x': self._sc_x, 'y': self._sc_y})
10+
self._ax_x = Axis(scale=self._sc_x, label='Index')
11+
self._ax_y = Axis(scale=self._sc_y, orientation='vertical', label='y-value')
12+
self._fig = Figure(marks=[self._line], axes=[self._ax_x, self._ax_y], title=figure_title, layout=Layout(flex='1 1 auto', width='100%'))
13+
14+
def get_figure(self):
15+
return self._fig
16+
17+
def get_line(self):
18+
return self._line
19+
20+
def set_line(self, x, y, labels, colors):
21+
self._line.x = x
22+
self._line.y = y
23+
self._line.labels = labels
24+
self._line.colors = colors

0 commit comments

Comments
 (0)