Skip to content

Commit f968f5d

Browse files
committed
Adding scatter example
1 parent 5454b95 commit f968f5d

File tree

1 file changed

+369
-0
lines changed

1 file changed

+369
-0
lines changed

examples/plotting.ipynb

Lines changed: 369 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,369 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"# Custom plotting library fully in Python!"
8+
]
9+
},
10+
{
11+
"cell_type": "code",
12+
"execution_count": null,
13+
"metadata": {},
14+
"outputs": [],
15+
"source": [
16+
"from math import pi\n",
17+
"\n",
18+
"import numpy as np\n",
19+
"\n",
20+
"import branca\n",
21+
"\n",
22+
"from ipywidgets import VBox, IntSlider\n",
23+
"\n",
24+
"from ipycanvas import Canvas, hold_canvas"
25+
]
26+
},
27+
{
28+
"cell_type": "code",
29+
"execution_count": null,
30+
"metadata": {},
31+
"outputs": [],
32+
"source": [
33+
"def init_2d_plot(x, y, color=None, scheme=branca.colormap.linear.RdBu_11, canvas=None, canvas_size=(800, 600), padding=0.1):\n",
34+
" if canvas is None:\n",
35+
" canvas = Canvas(size=canvas_size)\n",
36+
" else:\n",
37+
" canvas.size = canvas_size\n",
38+
"\n",
39+
" padding_x = padding * canvas_size[0]\n",
40+
" padding_y = padding * canvas_size[1]\n",
41+
"\n",
42+
" drawarea = (drawarea_min_x, drawarea_min_y, drawarea_max_x, drawarea_max_y) = (padding_x, padding_y, canvas_size[0] - 2 * padding_x, canvas_size[1] - 2 * padding_y)\n",
43+
"\n",
44+
" min_x, min_y, max_x, max_y = np.min(x), np.min(y), np.max(x), np.max(y)\n",
45+
"\n",
46+
" dx = max_x - min_x\n",
47+
" dy = max_y - min_y\n",
48+
"\n",
49+
" # Turns a data coordinate into pixel coordinate\n",
50+
" scale_x = lambda x: drawarea_max_x * (x - min_x) / dx + drawarea_min_x\n",
51+
" scale_y = lambda y: drawarea_max_y * (1 - (y - min_y) / dy) + drawarea_min_y\n",
52+
"\n",
53+
" # Turns a pixel coordinate into data coordinate\n",
54+
" unscale_x = lambda sx: (sx - drawarea_min_x) * dx / drawarea_max_x + min_x\n",
55+
" unscale_y = lambda sy: (1 - ((sy - drawarea_min_y) / drawarea_max_y)) * dy + min_y\n",
56+
"\n",
57+
" colormap = None\n",
58+
" if color is not None:\n",
59+
" colormap = scheme.scale(np.min(color), np.max(color)) \n",
60+
"\n",
61+
" return canvas, drawarea, scale_x, scale_y, unscale_x, unscale_y, colormap"
62+
]
63+
},
64+
{
65+
"cell_type": "code",
66+
"execution_count": null,
67+
"metadata": {},
68+
"outputs": [],
69+
"source": [
70+
"def draw_background(canvas, drawarea, unscale_x, unscale_y):\n",
71+
" drawarea_min_x, drawarea_min_y, drawarea_max_x, drawarea_max_y = drawarea\n",
72+
"\n",
73+
" # Draw background\n",
74+
" canvas.fill_style = '#f7f7f7'\n",
75+
" canvas.global_alpha = 0.3\n",
76+
" canvas.fill_rect(drawarea_min_x, drawarea_min_y, drawarea_max_x, drawarea_max_y)\n",
77+
" canvas.global_alpha = 1\n",
78+
"\n",
79+
" # Draw grid and ticks\n",
80+
" n_lines = 10\n",
81+
" canvas.fill_style = 'black'\n",
82+
" canvas.stroke_style = '#8c8c8c'\n",
83+
" canvas.line_width = 1\n",
84+
" canvas.begin_path()\n",
85+
"\n",
86+
" for i in range(n_lines):\n",
87+
" j = i / (n_lines - 1)\n",
88+
" line_x = drawarea_max_x * j + drawarea_min_x\n",
89+
" line_y = drawarea_max_y * j + drawarea_min_y\n",
90+
"\n",
91+
" # Line on the y axis\n",
92+
" canvas.move_to(line_x, drawarea_min_y)\n",
93+
" canvas.line_to(line_x, drawarea_max_y + drawarea_min_y)\n",
94+
"\n",
95+
" # Line on the x axis\n",
96+
" canvas.move_to(drawarea_min_x, line_y)\n",
97+
" canvas.line_to(drawarea_max_x + drawarea_min_x, line_y)\n",
98+
"\n",
99+
" # Draw y tick\n",
100+
" canvas.text_align = 'right'\n",
101+
" canvas.text_baseline = 'middle'\n",
102+
" canvas.fill_text('{0:.2e}'.format(unscale_y(line_y)), drawarea_min_x * 0.95, line_y)\n",
103+
"\n",
104+
" # Draw x tick\n",
105+
" canvas.text_align = 'center'\n",
106+
" canvas.text_baseline = 'top'\n",
107+
" canvas.fill_text('{0:.2e}'.format(unscale_x(line_x)), line_x, drawarea_max_y + drawarea_min_y + drawarea_min_y * 0.05)\n",
108+
"\n",
109+
" canvas.stroke()\n",
110+
" canvas.close_path()"
111+
]
112+
},
113+
{
114+
"cell_type": "code",
115+
"execution_count": null,
116+
"metadata": {},
117+
"outputs": [],
118+
"source": [
119+
"def scatter_plot(x, y, size, color, scheme=branca.colormap.linear.RdBu_11, stroke_color='black', canvas=None):\n",
120+
" canvas, drawarea, scale_x, scale_y, unscale_x, unscale_y, colormap = init_2d_plot(x, y, color, scheme, canvas=canvas)\n",
121+
"\n",
122+
" with hold_canvas(canvas):\n",
123+
" canvas.clear()\n",
124+
" canvas.save()\n",
125+
"\n",
126+
" draw_background(canvas, drawarea, unscale_x, unscale_y)\n",
127+
"\n",
128+
" # Draw scatter\n",
129+
" n_marks = min(x.shape[0], y.shape[0], size.shape[0], color.shape[0])\n",
130+
"\n",
131+
" for idx in range(n_marks):\n",
132+
" canvas.begin_path()\n",
133+
"\n",
134+
" canvas.fill_style = colormap(color[idx])\n",
135+
" canvas.stroke_style = stroke_color\n",
136+
"\n",
137+
" canvas.arc(\n",
138+
" scale_x(x[idx]), scale_y(y[idx]),\n",
139+
" size[idx],\n",
140+
" 0, 2 * pi\n",
141+
" )\n",
142+
"\n",
143+
" canvas.fill()\n",
144+
" canvas.stroke()\n",
145+
"\n",
146+
" canvas.close_path()\n",
147+
"\n",
148+
" canvas.restore()\n",
149+
"\n",
150+
" return canvas"
151+
]
152+
},
153+
{
154+
"cell_type": "code",
155+
"execution_count": null,
156+
"metadata": {},
157+
"outputs": [],
158+
"source": [
159+
"def line_plot(x, y, line_color='#749cb8', line_width=2, canvas=None):\n",
160+
" canvas, drawarea, scale_x, scale_y, unscale_x, unscale_y, _ = init_2d_plot(x, y, canvas=canvas)\n",
161+
"\n",
162+
" with hold_canvas(canvas):\n",
163+
" canvas.clear()\n",
164+
" canvas.save()\n",
165+
"\n",
166+
" draw_background(canvas, drawarea, unscale_x, unscale_y)\n",
167+
"\n",
168+
" # Draw lines\n",
169+
" n_points = min(x.shape[0], y.shape[0])\n",
170+
"\n",
171+
" canvas.begin_path()\n",
172+
" canvas.stroke_style = line_color\n",
173+
" canvas.line_width = line_width\n",
174+
" canvas.line_join = 'bevel'\n",
175+
" canvas.line_cap = 'round'\n",
176+
" canvas.move_to(scale_x(x[0]), scale_y(y[0]))\n",
177+
" for idx in range(1, n_points):\n",
178+
" canvas.line_to(\n",
179+
" scale_x(x[idx]), scale_y(y[idx])\n",
180+
" )\n",
181+
"\n",
182+
" canvas.stroke()\n",
183+
" canvas.close_path()\n",
184+
" \n",
185+
" canvas.restore()\n",
186+
"\n",
187+
" return canvas"
188+
]
189+
},
190+
{
191+
"cell_type": "code",
192+
"execution_count": null,
193+
"metadata": {},
194+
"outputs": [],
195+
"source": [
196+
"def heatmap_plot(x, y, color, scheme=branca.colormap.linear.RdBu_11, canvas=None):\n",
197+
" canvas, drawarea, scale_x, scale_y, unscale_x, unscale_y, colormap = init_2d_plot(x, y, color, scheme, canvas=canvas)\n",
198+
"\n",
199+
" outof_x_bound = lambda idx: True if idx >= x.shape[0] or idx < 0 else False\n",
200+
" outof_y_bound = lambda idx: True if idx >= y.shape[0] or idx < 0 else False\n",
201+
"\n",
202+
" with hold_canvas(canvas):\n",
203+
" canvas.clear()\n",
204+
" canvas.save()\n",
205+
"\n",
206+
" draw_background(canvas, drawarea, unscale_x, unscale_y)\n",
207+
"\n",
208+
" # Draw heatmap\n",
209+
" n_marks = min(x.shape[0], y.shape[0])\n",
210+
"\n",
211+
" for x_idx in range(1, color.shape[0] - 1):\n",
212+
" for y_idx in range(1, color.shape[1] - 1):\n",
213+
" canvas.fill_style = colormap(color[x_idx][y_idx])\n",
214+
"\n",
215+
" rect_center = (scale_x(x[x_idx]), scale_y(y[y_idx]))\n",
216+
" neighbours_x = (scale_x(x[x_idx - 1]), scale_x(x[x_idx + 1]))\n",
217+
" neighbours_y = (scale_y(y[y_idx - 1]), scale_y(y[y_idx + 1]))\n",
218+
"\n",
219+
" rect_top_left_corner = ((neighbours_x[0] + rect_center[0]) / 2, (neighbours_y[0] + rect_center[1]) / 2)\n",
220+
" rect_low_right_corner = ((neighbours_x[1] + rect_center[0]) / 2, (neighbours_y[1] + rect_center[1]) / 2)\n",
221+
"\n",
222+
" width = rect_low_right_corner[0] - rect_top_left_corner[0]\n",
223+
" height = rect_low_right_corner[1] - rect_top_left_corner[1]\n",
224+
"\n",
225+
" canvas.fill_rect(\n",
226+
" rect_top_left_corner[0], rect_top_left_corner[1],\n",
227+
" width, height\n",
228+
" )\n",
229+
"\n",
230+
" canvas.restore()\n",
231+
"\n",
232+
" return canvas"
233+
]
234+
},
235+
{
236+
"cell_type": "markdown",
237+
"metadata": {},
238+
"source": [
239+
"# Scatter plot"
240+
]
241+
},
242+
{
243+
"cell_type": "code",
244+
"execution_count": null,
245+
"metadata": {},
246+
"outputs": [],
247+
"source": [
248+
"n_points = 500"
249+
]
250+
},
251+
{
252+
"cell_type": "code",
253+
"execution_count": null,
254+
"metadata": {},
255+
"outputs": [],
256+
"source": [
257+
"x = np.random.rand(n_points)\n",
258+
"y = np.random.rand(n_points)\n",
259+
"sizes = np.random.randint(2, 8, n_points)\n",
260+
"colors = np.random.rand(n_points) * 10 - 2\n",
261+
"\n",
262+
"plot = scatter_plot(x, y, sizes, colors, branca.colormap.linear.viridis, stroke_color='white')\n",
263+
"plot"
264+
]
265+
},
266+
{
267+
"cell_type": "markdown",
268+
"metadata": {},
269+
"source": [
270+
"### Because it's a Canvas, you can draw on top of it! "
271+
]
272+
},
273+
{
274+
"cell_type": "code",
275+
"execution_count": null,
276+
"metadata": {},
277+
"outputs": [],
278+
"source": [
279+
"plot.stroke_style = 'red'\n",
280+
"plot.line_width = 2\n",
281+
"plot.stroke_rect(200, 300, 50, 100)"
282+
]
283+
},
284+
{
285+
"cell_type": "markdown",
286+
"metadata": {},
287+
"source": [
288+
"# Line plot"
289+
]
290+
},
291+
{
292+
"cell_type": "code",
293+
"execution_count": null,
294+
"metadata": {},
295+
"outputs": [],
296+
"source": [
297+
"x = np.linspace(0, 20, 500)\n",
298+
"y = np.sin(x)\n",
299+
"\n",
300+
"line_plot(x, y, line_width=3)"
301+
]
302+
},
303+
{
304+
"cell_type": "code",
305+
"execution_count": null,
306+
"metadata": {},
307+
"outputs": [],
308+
"source": [
309+
"slider = IntSlider(min=1, max=10, step=1)\n",
310+
"\n",
311+
"x = np.linspace(-20, 20, 500)\n",
312+
"y = np.power(x, slider.value)\n",
313+
"\n",
314+
"power_plot = line_plot(x, y, line_color='#32a852', line_width=3)\n",
315+
"\n",
316+
"def on_slider_change(change):\n",
317+
" y = np.power(x, slider.value)\n",
318+
"\n",
319+
" line_plot(x, y, line_color='#32a852', line_width=3, canvas=power_plot)\n",
320+
"\n",
321+
"slider.observe(on_slider_change, 'value')\n",
322+
"\n",
323+
"VBox((power_plot, slider))"
324+
]
325+
},
326+
{
327+
"cell_type": "markdown",
328+
"metadata": {},
329+
"source": [
330+
"# Heatmap"
331+
]
332+
},
333+
{
334+
"cell_type": "code",
335+
"execution_count": null,
336+
"metadata": {},
337+
"outputs": [],
338+
"source": [
339+
"x = np.linspace(-5, 5, 100)\n",
340+
"y = np.linspace(-5, 5, 100)\n",
341+
"x_grid, y_grid = np.meshgrid(x, y)\n",
342+
"color = np.sin(x_grid + y_grid**2) + np.cos(x_grid**2 + y_grid**2)\n",
343+
"\n",
344+
"heatmap_plot(x, y, color, scheme=branca.colormap.linear.RdYlBu_05)"
345+
]
346+
}
347+
],
348+
"metadata": {
349+
"kernelspec": {
350+
"display_name": "Python 3",
351+
"language": "python",
352+
"name": "python3"
353+
},
354+
"language_info": {
355+
"codemirror_mode": {
356+
"name": "ipython",
357+
"version": 3
358+
},
359+
"file_extension": ".py",
360+
"mimetype": "text/x-python",
361+
"name": "python",
362+
"nbconvert_exporter": "python",
363+
"pygments_lexer": "ipython3",
364+
"version": "3.7.3"
365+
}
366+
},
367+
"nbformat": 4,
368+
"nbformat_minor": 4
369+
}

0 commit comments

Comments
 (0)