Skip to content

Commit b749ee6

Browse files
committed
Add MatplotlibChartWithToolbar and update test
Introduces MatplotlibChartWithToolbar for integrated chart and toolbar controls. Updates __init__.py to export the new class, refactors tests/mpl_v2_basic.py to use MatplotlibChartWithToolbar, and adds debug output and minor improvements to MatplotlibChart.
1 parent f7ffafd commit b749ee6

File tree

4 files changed

+151
-115
lines changed

4 files changed

+151
-115
lines changed

src/flet_charts/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
MatplotlibChartMessageEvent,
2525
MatplotlibChartToolbarButtonsUpdateEvent,
2626
)
27+
from flet_charts.matplotlib_chart_with_toolbar import MatplotlibChartWithToolbar
2728
from flet_charts.pie_chart import PieChart, PieChartEvent
2829
from flet_charts.pie_chart_section import PieChartSection
2930
from flet_charts.plotly_chart import PlotlyChart
@@ -84,4 +85,5 @@
8485
"ScatterChartTooltip",
8586
"MatplotlibChartMessageEvent",
8687
"MatplotlibChartToolbarButtonsUpdateEvent",
88+
"MatplotlibChartWithToolbar",
8789
]

src/flet_charts/matplotlib_chart.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,7 @@ def send_message(self, message):
355355
manager.handle_json(message)
356356

357357
def send_json(self, content):
358+
print(f"send_json: {content}")
358359
self._main_loop.call_soon_threadsafe(
359360
lambda: self._receive_queue.put_nowait((False, content))
360361
)
@@ -378,4 +379,6 @@ async def on_canvas_resize(self, e: fc.CanvasResizeEvent):
378379
self.send_message({"type": "refresh"})
379380
self._width = e.width
380381
self._height = e.height
381-
self.send_message({"type": "resize", "width": e.width, "height": e.height})
382+
self.send_message(
383+
{"type": "resize", "width": self._width, "height": self._height}
384+
)
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
from dataclasses import field
2+
3+
import flet as ft
4+
from matplotlib.figure import Figure
5+
6+
import flet_charts
7+
8+
_download_formats = [
9+
"eps",
10+
"jpeg",
11+
"pgf",
12+
"pdf",
13+
"png",
14+
"ps",
15+
"raw",
16+
"svg",
17+
"tif",
18+
"webp",
19+
]
20+
21+
22+
@ft.control(kw_only=True)
23+
class MatplotlibChartWithToolbar(ft.Column):
24+
figure: Figure = field(metadata={"skip": True})
25+
"""
26+
Matplotlib figure to draw - an instance of
27+
[`matplotlib.figure.Figure`](https://matplotlib.org/stable/api/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure).
28+
"""
29+
30+
def build(self):
31+
self.mpl = flet_charts.MatplotlibChart(
32+
figure=self.figure,
33+
expand=True,
34+
on_message=self.on_message,
35+
on_toolbar_buttons_update=self.on_toolbar_update,
36+
)
37+
self.home_btn = ft.IconButton(ft.Icons.HOME, on_click=lambda: self.mpl.home())
38+
self.back_btn = ft.IconButton(
39+
ft.Icons.ARROW_BACK_ROUNDED, on_click=lambda: self.mpl.back()
40+
)
41+
self.fwd_btn = ft.IconButton(
42+
ft.Icons.ARROW_FORWARD_ROUNDED, on_click=lambda: self.mpl.forward()
43+
)
44+
self.pan_btn = ft.IconButton(
45+
ft.Icons.OPEN_WITH,
46+
selected_icon=ft.Icons.OPEN_WITH,
47+
selected_icon_color=ft.Colors.AMBER_800,
48+
on_click=self.pan_click,
49+
)
50+
self.zoom_btn = ft.IconButton(
51+
ft.Icons.ZOOM_IN,
52+
selected_icon=ft.Icons.ZOOM_IN,
53+
selected_icon_color=ft.Colors.AMBER_800,
54+
on_click=self.zoom_click,
55+
)
56+
self.download_btn = ft.IconButton(
57+
ft.Icons.DOWNLOAD, on_click=self.download_click
58+
)
59+
self.download_fmt = ft.Dropdown(
60+
value="png",
61+
options=[ft.DropdownOption(fmt) for fmt in _download_formats],
62+
)
63+
self.msg = ft.Text()
64+
self.controls = [
65+
ft.Row(
66+
[
67+
self.home_btn,
68+
self.back_btn,
69+
self.fwd_btn,
70+
self.pan_btn,
71+
self.zoom_btn,
72+
self.download_btn,
73+
self.download_fmt,
74+
self.msg,
75+
]
76+
),
77+
self.mpl,
78+
]
79+
if not self.expand:
80+
if not self.height:
81+
self.height = self.figure.bbox.height
82+
if not self.width:
83+
self.width = self.figure.bbox.width
84+
85+
def on_message(self, e: flet_charts.MatplotlibChartMessageEvent):
86+
print(f"on_message: {e.message}")
87+
self.msg.value = e.message
88+
89+
def on_toolbar_update(
90+
self, e: flet_charts.MatplotlibChartToolbarButtonsUpdateEvent
91+
):
92+
self.back_btn.disabled = not e.back_enabled
93+
self.fwd_btn.disabled = not e.forward_enabled
94+
95+
def pan_click(self):
96+
self.mpl.pan()
97+
self.pan_btn.selected = not self.pan_btn.selected
98+
self.zoom_btn.selected = False
99+
100+
def zoom_click(self):
101+
self.mpl.zoom()
102+
self.pan_btn.selected = False
103+
self.zoom_btn.selected = not self.zoom_btn.selected
104+
105+
async def download_click(self):
106+
fmt = self.download_fmt.value
107+
buffer = self.mpl.download(fmt)
108+
title = self.figure.canvas.manager.get_window_title()
109+
fp = ft.FilePicker()
110+
await fp.save_file(file_name=f"{title}.{fmt}", src_bytes=buffer)

tests/mpl_v2_basic.py

Lines changed: 35 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -13,123 +13,44 @@
1313

1414

1515
def main(page: ft.Page):
16-
# Sample data
17-
x = np.linspace(0, 10, 100)
18-
y = np.sin(x)
16+
# # Sample data
17+
# x = np.linspace(0, 10, 100)
18+
# y = np.sin(x)
19+
20+
# # Plot
21+
# fig = plt.figure()
22+
# print("Figure number:", fig.number)
23+
# plt.plot(x, y)
24+
# plt.title("Interactive Sine Wave")
25+
# plt.xlabel("X axis")
26+
# plt.ylabel("Y axis")
27+
# plt.grid(True)
28+
29+
# ----------------------------------------------------------
30+
31+
plt.style.use("_mpl-gallery")
32+
33+
# Make data for a double helix
34+
n = 50
35+
theta = np.linspace(0, 2 * np.pi, n)
36+
x1 = np.cos(theta)
37+
y1 = np.sin(theta)
38+
z1 = np.linspace(0, 1, n)
39+
x2 = np.cos(theta + np.pi)
40+
y2 = np.sin(theta + np.pi)
41+
z2 = z1
1942

2043
# Plot
21-
fig = plt.figure()
22-
print("Figure number:", fig.number)
23-
plt.plot(x, y)
24-
plt.title("Interactive Sine Wave")
25-
plt.xlabel("X axis")
26-
plt.ylabel("Y axis")
27-
plt.grid(True)
28-
29-
# plt.style.use('_mpl-gallery')
30-
31-
# # Make data for a double helix
32-
# n = 50
33-
# theta = np.linspace(0, 2*np.pi, n)
34-
# x1 = np.cos(theta)
35-
# y1 = np.sin(theta)
36-
# z1 = np.linspace(0, 1, n)
37-
# x2 = np.cos(theta + np.pi)
38-
# y2 = np.sin(theta + np.pi)
39-
# z2 = z1
44+
fig, ax = plt.subplots(subplot_kw={"projection": "3d"})
45+
ax.fill_between(x1, y1, z1, x2, y2, z2, alpha=0.5)
46+
ax.plot(x1, y1, z1, linewidth=2, color="C0")
47+
ax.plot(x2, y2, z2, linewidth=2, color="C0")
4048

41-
# # Plot
42-
# fig, ax = plt.subplots(subplot_kw={"projection": "3d"})
43-
# ax.fill_between(x1, y1, z1, x2, y2, z2, alpha=0.5)
44-
# ax.plot(x1, y1, z1, linewidth=2, color='C0')
45-
# ax.plot(x2, y2, z2, linewidth=2, color='C0')
46-
47-
# ax.set(xticklabels=[],
48-
# yticklabels=[],
49-
# zticklabels=[])
50-
51-
# plt.show()
52-
53-
download_formats = [
54-
"eps",
55-
"jpeg",
56-
"pgf",
57-
"pdf",
58-
"png",
59-
"ps",
60-
"raw",
61-
"svg",
62-
"tif",
63-
"webp",
64-
]
65-
66-
fp = ft.FilePicker()
67-
68-
msg = ft.Text()
69-
70-
def on_message(e: flet_charts.MatplotlibChartMessageEvent):
71-
msg.value = e.message
72-
73-
def on_toolbar_update(e: flet_charts.MatplotlibChartToolbarButtonsUpdateEvent):
74-
back_btn.disabled = not e.back_enabled
75-
fwd_btn.disabled = not e.forward_enabled
76-
77-
def pan_click():
78-
mpl.pan()
79-
pan_btn.selected = not pan_btn.selected
80-
zoom_btn.selected = False
81-
82-
def zoom_click():
83-
mpl.zoom()
84-
pan_btn.selected = False
85-
zoom_btn.selected = not zoom_btn.selected
86-
87-
async def download_click():
88-
fmt = dwnld_fmt.value
89-
buffer = mpl.download(fmt)
90-
title = fig.canvas.manager.get_window_title()
91-
await fp.save_file(file_name=f"{title}.{fmt}", src_bytes=buffer)
92-
93-
mpl = flet_charts.MatplotlibChart(
94-
figure=fig,
95-
expand=True,
96-
on_message=on_message,
97-
on_toolbar_buttons_update=on_toolbar_update,
98-
)
99-
100-
# fig1.canvas.start()
101-
page.add(
102-
ft.Row(
103-
[
104-
ft.IconButton(ft.Icons.HOME, on_click=lambda: mpl.home()),
105-
back_btn := ft.IconButton(
106-
ft.Icons.ARROW_BACK_ROUNDED, on_click=lambda: mpl.back()
107-
),
108-
fwd_btn := ft.IconButton(
109-
ft.Icons.ARROW_FORWARD_ROUNDED, on_click=lambda: mpl.forward()
110-
),
111-
pan_btn := ft.IconButton(
112-
ft.Icons.PAN_TOOL_OUTLINED,
113-
selected_icon=ft.Icons.PAN_TOOL_OUTLINED,
114-
selected_icon_color=ft.Colors.AMBER_800,
115-
on_click=pan_click,
116-
),
117-
zoom_btn := ft.IconButton(
118-
ft.Icons.ZOOM_IN,
119-
selected_icon=ft.Icons.ZOOM_IN,
120-
selected_icon_color=ft.Colors.AMBER_800,
121-
on_click=zoom_click,
122-
),
123-
ft.IconButton(ft.Icons.DOWNLOAD, on_click=download_click),
124-
dwnld_fmt := ft.Dropdown(
125-
value="png",
126-
options=[ft.DropdownOption(fmt) for fmt in download_formats],
127-
),
128-
msg,
129-
]
130-
),
131-
mpl,
132-
)
49+
ax.set(xticklabels=[], yticklabels=[], zticklabels=[])
50+
51+
plt.show()
52+
53+
page.add(flet_charts.MatplotlibChartWithToolbar(figure=fig, expand=True))
13354

13455

13556
ft.run(main)

0 commit comments

Comments
 (0)