flet-charts 0.2.0.dev524__py3-none-any.whl → 0.70.0.dev6293__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of flet-charts might be problematic. Click here for more details.
- flet_charts/__init__.py +23 -1
- flet_charts/bar_chart.py +4 -5
- flet_charts/bar_chart_group.py +0 -1
- flet_charts/bar_chart_rod.py +0 -1
- flet_charts/candlestick_chart.py +266 -0
- flet_charts/candlestick_chart_spot.py +98 -0
- flet_charts/line_chart.py +4 -5
- flet_charts/line_chart_data.py +0 -1
- flet_charts/line_chart_data_point.py +0 -1
- flet_charts/matplotlib_backends/backend_flet_agg.py +16 -0
- flet_charts/matplotlib_chart.py +348 -31
- flet_charts/matplotlib_chart_with_toolbar.py +110 -0
- flet_charts/pie_chart.py +0 -1
- flet_charts/plotly_chart.py +1 -1
- flet_charts/scatter_chart.py +4 -5
- flet_charts/scatter_chart_spot.py +0 -1
- {flet_charts-0.2.0.dev524.dist-info → flet_charts-0.70.0.dev6293.dist-info}/METADATA +19 -11
- flet_charts-0.70.0.dev6293.dist-info/RECORD +42 -0
- flutter/flet_charts/CHANGELOG.md +1 -1
- flutter/flet_charts/LICENSE +1 -1
- flutter/flet_charts/analysis_options.yaml +1 -1
- flutter/flet_charts/lib/src/candlestick_chart.dart +136 -0
- flutter/flet_charts/lib/src/extension.dart +3 -0
- flutter/flet_charts/lib/src/utils/bar_chart.dart +136 -83
- flutter/flet_charts/lib/src/utils/candlestick_chart.dart +117 -0
- flutter/flet_charts/pubspec.yaml +10 -9
- flet_charts-0.2.0.dev524.dist-info/RECORD +0 -38
- flutter/flet_charts/README.md +0 -3
- flutter/flet_charts/pubspec.lock +0 -792
- {flet_charts-0.2.0.dev524.dist-info → flet_charts-0.70.0.dev6293.dist-info}/WHEEL +0 -0
- {flet_charts-0.2.0.dev524.dist-info → flet_charts-0.70.0.dev6293.dist-info}/licenses/LICENSE +0 -0
- {flet_charts-0.2.0.dev524.dist-info → flet_charts-0.70.0.dev6293.dist-info}/top_level.txt +0 -0
flet_charts/matplotlib_chart.py
CHANGED
|
@@ -1,22 +1,63 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
from
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from io import BytesIO
|
|
5
|
+
from typing import Optional
|
|
5
6
|
|
|
6
7
|
import flet as ft
|
|
8
|
+
import flet.canvas as fc
|
|
7
9
|
|
|
8
10
|
try:
|
|
11
|
+
import matplotlib
|
|
9
12
|
from matplotlib.figure import Figure
|
|
10
13
|
except ImportError as e:
|
|
11
14
|
raise Exception(
|
|
12
15
|
'Install "matplotlib" Python package to use MatplotlibChart control.'
|
|
13
16
|
) from e
|
|
14
17
|
|
|
15
|
-
__all__ = [
|
|
18
|
+
__all__ = [
|
|
19
|
+
"MatplotlibChart",
|
|
20
|
+
"MatplotlibChartMessageEvent",
|
|
21
|
+
"MatplotlibChartToolbarButtonsUpdateEvent",
|
|
22
|
+
]
|
|
16
23
|
|
|
24
|
+
logger = logging.getLogger("flet-charts.matplotlib")
|
|
17
25
|
|
|
18
|
-
|
|
19
|
-
|
|
26
|
+
matplotlib.use("module://flet_charts.matplotlib_backends.backend_flet_agg")
|
|
27
|
+
|
|
28
|
+
figure_cursors = {
|
|
29
|
+
"default": None,
|
|
30
|
+
"pointer": ft.MouseCursor.CLICK,
|
|
31
|
+
"crosshair": ft.MouseCursor.PRECISE,
|
|
32
|
+
"move": ft.MouseCursor.MOVE,
|
|
33
|
+
"wait": ft.MouseCursor.WAIT,
|
|
34
|
+
"ew-resize": ft.MouseCursor.RESIZE_LEFT_RIGHT,
|
|
35
|
+
"ns-resize": ft.MouseCursor.RESIZE_UP_DOWN,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class MatplotlibChartMessageEvent(ft.Event["MatplotlibChart"]):
|
|
41
|
+
message: str
|
|
42
|
+
"""
|
|
43
|
+
Message text.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class MatplotlibChartToolbarButtonsUpdateEvent(ft.Event["MatplotlibChart"]):
|
|
49
|
+
back_enabled: bool
|
|
50
|
+
"""
|
|
51
|
+
Whether Back button is enabled or not.
|
|
52
|
+
"""
|
|
53
|
+
forward_enabled: bool
|
|
54
|
+
"""
|
|
55
|
+
Whether Forward button is enabled or not.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@ft.control(kw_only=True, isolated=True)
|
|
60
|
+
class MatplotlibChart(ft.GestureDetector):
|
|
20
61
|
"""
|
|
21
62
|
Displays a [Matplotlib](https://matplotlib.org/) chart.
|
|
22
63
|
|
|
@@ -33,33 +74,309 @@ class MatplotlibChart(ft.Container):
|
|
|
33
74
|
[`matplotlib.figure.Figure`](https://matplotlib.org/stable/api/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure).
|
|
34
75
|
"""
|
|
35
76
|
|
|
36
|
-
|
|
77
|
+
on_message: Optional[ft.EventHandler[MatplotlibChartMessageEvent]] = None
|
|
37
78
|
"""
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
Set to `False` to display a chart that fits configured bounds.
|
|
79
|
+
The event is triggered on figure message update.
|
|
41
80
|
"""
|
|
42
81
|
|
|
43
|
-
|
|
82
|
+
on_toolbar_buttons_update: Optional[
|
|
83
|
+
ft.EventHandler[MatplotlibChartToolbarButtonsUpdateEvent]
|
|
84
|
+
] = None
|
|
44
85
|
"""
|
|
45
|
-
|
|
86
|
+
Triggers when toolbar buttons status is updated.
|
|
46
87
|
"""
|
|
47
88
|
|
|
48
|
-
def
|
|
49
|
-
self.
|
|
50
|
-
self.
|
|
51
|
-
self.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
self.
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
89
|
+
def build(self):
|
|
90
|
+
self.mouse_cursor = ft.MouseCursor.WAIT
|
|
91
|
+
self.__started = False
|
|
92
|
+
self.__dpr = self.page.media.device_pixel_ratio
|
|
93
|
+
logger.debug(f"DPR: {self.__dpr}")
|
|
94
|
+
self.__image_mode = "full"
|
|
95
|
+
|
|
96
|
+
self.canvas = fc.Canvas(
|
|
97
|
+
# resize_interval=10,
|
|
98
|
+
on_resize=self.on_canvas_resize,
|
|
99
|
+
expand=True,
|
|
100
|
+
)
|
|
101
|
+
self.keyboard_listener = ft.KeyboardListener(
|
|
102
|
+
self.canvas,
|
|
103
|
+
autofocus=True,
|
|
104
|
+
on_key_down=self._on_key_down,
|
|
105
|
+
on_key_up=self._on_key_up,
|
|
106
|
+
)
|
|
107
|
+
self.content = self.keyboard_listener
|
|
108
|
+
self.on_enter = self._on_enter
|
|
109
|
+
self.on_hover = self._on_hover
|
|
110
|
+
self.on_exit = self._on_exit
|
|
111
|
+
self.on_pan_start = self._pan_start
|
|
112
|
+
self.on_pan_update = self._pan_update
|
|
113
|
+
self.on_pan_end = self._pan_end
|
|
114
|
+
self.on_right_pan_start = self._right_pan_start
|
|
115
|
+
self.on_right_pan_update = self._right_pan_update
|
|
116
|
+
self.on_right_pan_end = self._right_pan_end
|
|
117
|
+
self.img_count = 1
|
|
118
|
+
self._receive_queue = asyncio.Queue()
|
|
119
|
+
self._main_loop = asyncio.get_event_loop()
|
|
120
|
+
self._width = 0
|
|
121
|
+
self._height = 0
|
|
122
|
+
self._waiting = False
|
|
123
|
+
|
|
124
|
+
def _on_key_down(self, e):
|
|
125
|
+
logger.debug(f"ON KEY DOWN: {e}")
|
|
126
|
+
|
|
127
|
+
def _on_key_up(self, e):
|
|
128
|
+
logger.debug(f"ON KEY UP: {e}")
|
|
129
|
+
|
|
130
|
+
def _on_enter(self, e: ft.HoverEvent):
|
|
131
|
+
logger.debug(f"_on_enter: {e.local_position.x}, {e.local_position.y}")
|
|
132
|
+
self.send_message(
|
|
133
|
+
{
|
|
134
|
+
"type": "figure_enter",
|
|
135
|
+
"x": e.local_position.x * self.__dpr,
|
|
136
|
+
"y": e.local_position.y * self.__dpr,
|
|
137
|
+
"button": 0,
|
|
138
|
+
"buttons": 0,
|
|
139
|
+
"modifiers": [],
|
|
140
|
+
}
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
def _on_hover(self, e: ft.HoverEvent):
|
|
144
|
+
logger.debug(f"_on_hover: {e.local_position.x}, {e.local_position.y}")
|
|
145
|
+
self.send_message(
|
|
146
|
+
{
|
|
147
|
+
"type": "motion_notify",
|
|
148
|
+
"x": e.local_position.x * self.__dpr,
|
|
149
|
+
"y": e.local_position.y * self.__dpr,
|
|
150
|
+
"button": 0,
|
|
151
|
+
"buttons": 0,
|
|
152
|
+
"modifiers": [],
|
|
153
|
+
}
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
def _on_exit(self, e: ft.HoverEvent):
|
|
157
|
+
logger.debug(f"_on_exit: {e.local_position.x}, {e.local_position.y}")
|
|
158
|
+
self.send_message(
|
|
159
|
+
{
|
|
160
|
+
"type": "figure_leave",
|
|
161
|
+
"x": e.local_position.x * self.__dpr,
|
|
162
|
+
"y": e.local_position.y * self.__dpr,
|
|
163
|
+
"button": 0,
|
|
164
|
+
"buttons": 0,
|
|
165
|
+
"modifiers": [],
|
|
166
|
+
}
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
def _pan_start(self, e: ft.DragStartEvent):
|
|
170
|
+
logger.debug(f"_pan_start: {e.local_position.x}, {e.local_position.y}")
|
|
171
|
+
asyncio.create_task(self.keyboard_listener.focus())
|
|
172
|
+
self.send_message(
|
|
173
|
+
{
|
|
174
|
+
"type": "button_press",
|
|
175
|
+
"x": e.local_position.x * self.__dpr,
|
|
176
|
+
"y": e.local_position.y * self.__dpr,
|
|
177
|
+
"button": 0,
|
|
178
|
+
"buttons": 1,
|
|
179
|
+
"modifiers": [],
|
|
180
|
+
}
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
def _pan_update(self, e: ft.DragUpdateEvent):
|
|
184
|
+
logger.debug(f"_pan_update: {e.local_position.x}, {e.local_position.y}")
|
|
185
|
+
self.send_message(
|
|
186
|
+
{
|
|
187
|
+
"type": "motion_notify",
|
|
188
|
+
"x": e.local_position.x * self.__dpr,
|
|
189
|
+
"y": e.local_position.y * self.__dpr,
|
|
190
|
+
"button": 0,
|
|
191
|
+
"buttons": 1,
|
|
192
|
+
"modifiers": [],
|
|
193
|
+
}
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
def _pan_end(self, e: ft.DragEndEvent):
|
|
197
|
+
logger.debug(f"_pan_end: {e.local_position.x}, {e.local_position.y}")
|
|
198
|
+
self.send_message(
|
|
199
|
+
{
|
|
200
|
+
"type": "button_release",
|
|
201
|
+
"x": e.local_position.x * self.__dpr,
|
|
202
|
+
"y": e.local_position.y * self.__dpr,
|
|
203
|
+
"button": 0,
|
|
204
|
+
"buttons": 0,
|
|
205
|
+
"modifiers": [],
|
|
206
|
+
}
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
def _right_pan_start(self, e: ft.PointerEvent):
|
|
210
|
+
logger.debug(f"_pan_start: {e.local_position.x}, {e.local_position.y}")
|
|
211
|
+
self.send_message(
|
|
212
|
+
{
|
|
213
|
+
"type": "button_press",
|
|
214
|
+
"x": e.local_position.x * self.__dpr,
|
|
215
|
+
"y": e.local_position.y * self.__dpr,
|
|
216
|
+
"button": 2,
|
|
217
|
+
"buttons": 2,
|
|
218
|
+
"modifiers": [],
|
|
219
|
+
}
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
def _right_pan_update(self, e: ft.PointerEvent):
|
|
223
|
+
logger.debug(f"_pan_update: {e.local_position.x}, {e.local_position.y}")
|
|
224
|
+
self.send_message(
|
|
225
|
+
{
|
|
226
|
+
"type": "motion_notify",
|
|
227
|
+
"x": e.local_position.x * self.__dpr,
|
|
228
|
+
"y": e.local_position.y * self.__dpr,
|
|
229
|
+
"button": 0,
|
|
230
|
+
"buttons": 2,
|
|
231
|
+
"modifiers": [],
|
|
232
|
+
}
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
def _right_pan_end(self, e: ft.PointerEvent):
|
|
236
|
+
logger.debug(f"_pan_end: {e.local_position.x}, {e.local_position.y}")
|
|
237
|
+
self.send_message(
|
|
238
|
+
{
|
|
239
|
+
"type": "button_release",
|
|
240
|
+
"x": e.local_position.x * self.__dpr,
|
|
241
|
+
"y": e.local_position.y * self.__dpr,
|
|
242
|
+
"button": 2,
|
|
243
|
+
"buttons": 0,
|
|
244
|
+
"modifiers": [],
|
|
245
|
+
}
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
def will_unmount(self):
|
|
249
|
+
self.figure.canvas.manager.remove_web_socket(self)
|
|
250
|
+
|
|
251
|
+
def home(self):
|
|
252
|
+
logger.debug("home)")
|
|
253
|
+
self.send_message({"type": "toolbar_button", "name": "home"})
|
|
254
|
+
|
|
255
|
+
def back(self):
|
|
256
|
+
logger.debug("back()")
|
|
257
|
+
self.send_message({"type": "toolbar_button", "name": "back"})
|
|
258
|
+
|
|
259
|
+
def forward(self):
|
|
260
|
+
logger.debug("forward)")
|
|
261
|
+
self.send_message({"type": "toolbar_button", "name": "forward"})
|
|
262
|
+
|
|
263
|
+
def pan(self):
|
|
264
|
+
logger.debug("pan()")
|
|
265
|
+
self.send_message({"type": "toolbar_button", "name": "pan"})
|
|
266
|
+
|
|
267
|
+
def zoom(self):
|
|
268
|
+
logger.debug("zoom()")
|
|
269
|
+
self.send_message({"type": "toolbar_button", "name": "zoom"})
|
|
270
|
+
|
|
271
|
+
def download(self, format):
|
|
272
|
+
logger.debug(f"Download in format: {format}")
|
|
273
|
+
buff = BytesIO()
|
|
274
|
+
self.figure.savefig(buff, format=format, dpi=self.figure.dpi * self.__dpr)
|
|
275
|
+
return buff.getvalue()
|
|
276
|
+
|
|
277
|
+
async def _receive_loop(self):
|
|
278
|
+
while True:
|
|
279
|
+
is_binary, content = await self._receive_queue.get()
|
|
280
|
+
if is_binary:
|
|
281
|
+
logger.debug(f"receive_binary({len(content)})")
|
|
282
|
+
if self.__image_mode == "full":
|
|
283
|
+
await self.canvas.clear_capture()
|
|
284
|
+
|
|
285
|
+
self.canvas.shapes = [
|
|
286
|
+
fc.Image(
|
|
287
|
+
src_bytes=content,
|
|
288
|
+
x=0,
|
|
289
|
+
y=0,
|
|
290
|
+
width=self.figure.bbox.size[0] / self.__dpr,
|
|
291
|
+
height=self.figure.bbox.size[1] / self.__dpr,
|
|
292
|
+
)
|
|
293
|
+
]
|
|
294
|
+
ft.context.disable_auto_update()
|
|
295
|
+
self.canvas.update()
|
|
296
|
+
await self.canvas.capture()
|
|
297
|
+
self.img_count += 1
|
|
298
|
+
self._waiting = False
|
|
299
|
+
else:
|
|
300
|
+
logger.debug(f"receive_json({content})")
|
|
301
|
+
if content["type"] == "image_mode":
|
|
302
|
+
self.__image_mode = content["mode"]
|
|
303
|
+
elif content["type"] == "cursor":
|
|
304
|
+
self.mouse_cursor = figure_cursors[content["cursor"]]
|
|
305
|
+
self.update()
|
|
306
|
+
elif content["type"] == "draw" and not self._waiting:
|
|
307
|
+
self._waiting = True
|
|
308
|
+
self.send_message({"type": "draw"})
|
|
309
|
+
elif content["type"] == "rubberband":
|
|
310
|
+
if len(self.canvas.shapes) == 2:
|
|
311
|
+
self.canvas.shapes.pop()
|
|
312
|
+
if (
|
|
313
|
+
content["x0"] != -1
|
|
314
|
+
and content["y0"] != -1
|
|
315
|
+
and content["x1"] != -1
|
|
316
|
+
and content["y1"] != -1
|
|
317
|
+
):
|
|
318
|
+
x0 = content["x0"] / self.__dpr
|
|
319
|
+
y0 = self._height - content["y0"] / self.__dpr
|
|
320
|
+
x1 = content["x1"] / self.__dpr
|
|
321
|
+
y1 = self._height - content["y1"] / self.__dpr
|
|
322
|
+
self.canvas.shapes.append(
|
|
323
|
+
fc.Rect(
|
|
324
|
+
x=x0,
|
|
325
|
+
y=y0,
|
|
326
|
+
width=x1 - x0,
|
|
327
|
+
height=y1 - y0,
|
|
328
|
+
paint=ft.Paint(
|
|
329
|
+
stroke_width=1, style=ft.PaintingStyle.STROKE
|
|
330
|
+
),
|
|
331
|
+
)
|
|
332
|
+
)
|
|
333
|
+
self.canvas.update()
|
|
334
|
+
elif content["type"] == "resize":
|
|
335
|
+
self.send_message({"type": "refresh"})
|
|
336
|
+
elif content["type"] == "message":
|
|
337
|
+
await self._trigger_event(
|
|
338
|
+
"message", {"message": content["message"]}
|
|
339
|
+
)
|
|
340
|
+
elif content["type"] == "history_buttons":
|
|
341
|
+
await self._trigger_event(
|
|
342
|
+
"toolbar_buttons_update",
|
|
343
|
+
{
|
|
344
|
+
"back_enabled": content["Back"],
|
|
345
|
+
"forward_enabled": content["Forward"],
|
|
346
|
+
},
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
def send_message(self, message):
|
|
350
|
+
logger.debug(f"send_message({message})")
|
|
351
|
+
manager = self.figure.canvas.manager
|
|
352
|
+
if manager is not None:
|
|
353
|
+
manager.handle_json(message)
|
|
354
|
+
|
|
355
|
+
def send_json(self, content):
|
|
356
|
+
logger.debug(f"send_json: {content}")
|
|
357
|
+
self._main_loop.call_soon_threadsafe(
|
|
358
|
+
lambda: self._receive_queue.put_nowait((False, content))
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
def send_binary(self, blob):
|
|
362
|
+
self._main_loop.call_soon_threadsafe(
|
|
363
|
+
lambda: self._receive_queue.put_nowait((True, blob))
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
async def on_canvas_resize(self, e: fc.CanvasResizeEvent):
|
|
367
|
+
logger.debug(f"on_canvas_resize: {e.width}, {e.height}")
|
|
368
|
+
|
|
369
|
+
if not self.__started:
|
|
370
|
+
self.__started = True
|
|
371
|
+
asyncio.create_task(self._receive_loop())
|
|
372
|
+
self.figure.canvas.manager.add_web_socket(self)
|
|
373
|
+
self.send_message({"type": "send_image_mode"})
|
|
374
|
+
self.send_message(
|
|
375
|
+
{"type": "set_device_pixel_ratio", "device_pixel_ratio": self.__dpr}
|
|
376
|
+
)
|
|
377
|
+
self.send_message({"type": "refresh"})
|
|
378
|
+
self._width = e.width
|
|
379
|
+
self._height = e.height
|
|
380
|
+
self.send_message(
|
|
381
|
+
{"type": "resize", "width": self._width, "height": self._height}
|
|
382
|
+
)
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from dataclasses import field
|
|
2
|
+
|
|
3
|
+
from matplotlib.figure import Figure
|
|
4
|
+
|
|
5
|
+
import flet as ft
|
|
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, isolated=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
|
+
self.msg.value = e.message
|
|
87
|
+
self.msg.update()
|
|
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
|
+
self.update()
|
|
95
|
+
|
|
96
|
+
def pan_click(self):
|
|
97
|
+
self.mpl.pan()
|
|
98
|
+
self.pan_btn.selected = not self.pan_btn.selected
|
|
99
|
+
self.zoom_btn.selected = False
|
|
100
|
+
|
|
101
|
+
def zoom_click(self):
|
|
102
|
+
self.mpl.zoom()
|
|
103
|
+
self.pan_btn.selected = False
|
|
104
|
+
self.zoom_btn.selected = not self.zoom_btn.selected
|
|
105
|
+
|
|
106
|
+
async def download_click(self):
|
|
107
|
+
fmt = self.download_fmt.value
|
|
108
|
+
buffer = self.mpl.download(fmt)
|
|
109
|
+
title = self.figure.canvas.manager.get_window_title()
|
|
110
|
+
await ft.FilePicker().save_file(file_name=f"{title}.{fmt}", src_bytes=buffer)
|
flet_charts/pie_chart.py
CHANGED
flet_charts/plotly_chart.py
CHANGED
|
@@ -29,7 +29,7 @@ class PlotlyChart(ft.Container):
|
|
|
29
29
|
figure: Figure = field(metadata={"skip": True})
|
|
30
30
|
"""
|
|
31
31
|
Plotly figure to draw.
|
|
32
|
-
|
|
32
|
+
|
|
33
33
|
The value is an instance of [`plotly.graph_objects.Figure`](https://plotly.com/python-api-reference/generated/plotly.graph_objects.Figure.html).
|
|
34
34
|
"""
|
|
35
35
|
|
flet_charts/scatter_chart.py
CHANGED
|
@@ -2,7 +2,6 @@ from dataclasses import dataclass, field
|
|
|
2
2
|
from typing import Any, Optional
|
|
3
3
|
|
|
4
4
|
import flet as ft
|
|
5
|
-
|
|
6
5
|
from flet_charts.chart_axis import ChartAxis
|
|
7
6
|
from flet_charts.scatter_chart_spot import ScatterChartSpot
|
|
8
7
|
from flet_charts.types import ChartEventType, ChartGridLines, HorizontalAlignment
|
|
@@ -173,22 +172,22 @@ class ScatterChart(ft.LayoutControl):
|
|
|
173
172
|
Controls drawing of chart's vertical lines.
|
|
174
173
|
"""
|
|
175
174
|
|
|
176
|
-
left_axis: ChartAxis =
|
|
175
|
+
left_axis: Optional[ChartAxis] = None
|
|
177
176
|
"""
|
|
178
177
|
Configures the appearance of the left axis, its title and labels.
|
|
179
178
|
"""
|
|
180
179
|
|
|
181
|
-
top_axis: ChartAxis =
|
|
180
|
+
top_axis: Optional[ChartAxis] = None
|
|
182
181
|
"""
|
|
183
182
|
Configures the appearance of the top axis, its title and labels.
|
|
184
183
|
"""
|
|
185
184
|
|
|
186
|
-
right_axis: ChartAxis =
|
|
185
|
+
right_axis: Optional[ChartAxis] = None
|
|
187
186
|
"""
|
|
188
187
|
Configures the appearance of the right axis, its title and labels.
|
|
189
188
|
"""
|
|
190
189
|
|
|
191
|
-
bottom_axis: ChartAxis =
|
|
190
|
+
bottom_axis: Optional[ChartAxis] = None
|
|
192
191
|
"""
|
|
193
192
|
Configures the appearance of the bottom axis, its title and labels.
|
|
194
193
|
"""
|
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flet-charts
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 0.70.0.dev6293
|
|
4
|
+
Summary: Interactive chart controls for Flet apps.
|
|
5
5
|
Author-email: Flet contributors <hello@flet.dev>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
7
7
|
Project-URL: Homepage, https://flet.dev
|
|
8
|
-
Project-URL: Documentation, https://flet
|
|
9
|
-
Project-URL: Repository, https://github.com/flet-dev/flet-charts
|
|
10
|
-
Project-URL: Issues, https://github.com/flet-dev/flet
|
|
8
|
+
Project-URL: Documentation, https://docs.flet.dev/charts
|
|
9
|
+
Project-URL: Repository, https://github.com/flet-dev/flet/tree/main/sdk/python/packages/flet-charts
|
|
10
|
+
Project-URL: Issues, https://github.com/flet-dev/flet/issues
|
|
11
11
|
Requires-Python: >=3.10
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
|
-
Requires-Dist: flet
|
|
14
|
+
Requires-Dist: flet==0.70.0.dev6293
|
|
15
15
|
Dynamic: license-file
|
|
16
16
|
|
|
17
17
|
# flet-charts
|
|
18
18
|
|
|
19
19
|
[](https://pypi.python.org/pypi/flet-charts)
|
|
20
20
|
[](https://pepy.tech/project/flet-charts)
|
|
21
|
-
[](https://github.com/flet-dev/flet/blob/main/sdk/python/packages/flet-charts/LICENSE)
|
|
22
22
|
|
|
23
23
|
A [Flet](https://flet.dev) extension for creating interactive charts and graphs.
|
|
24
24
|
|
|
@@ -26,12 +26,10 @@ It is based on the [fl_chart](https://pub.dev/packages/fl_chart) Flutter package
|
|
|
26
26
|
|
|
27
27
|
## Documentation
|
|
28
28
|
|
|
29
|
-
Detailed documentation to this package can be found [here](https://
|
|
29
|
+
Detailed documentation to this package can be found [here](https://docs.flet.dev/charts/).
|
|
30
30
|
|
|
31
31
|
## Platform Support
|
|
32
32
|
|
|
33
|
-
This package supports the following platforms:
|
|
34
|
-
|
|
35
33
|
| Platform | Windows | macOS | Linux | iOS | Android | Web |
|
|
36
34
|
|----------|---------|-------|-------|-----|---------|-----|
|
|
37
35
|
| Supported| ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
@@ -55,4 +53,14 @@ To install the `flet-charts` package and add it to your project dependencies:
|
|
|
55
53
|
|
|
56
54
|
### Examples
|
|
57
55
|
|
|
58
|
-
For examples, see [these](
|
|
56
|
+
For examples, see [these](https://github.com/flet-dev/flet/tree/main/sdk/python/examples/controls/charts).
|
|
57
|
+
|
|
58
|
+
### Available charts
|
|
59
|
+
|
|
60
|
+
- `BarChart`
|
|
61
|
+
- `CandlestickChart`
|
|
62
|
+
- `LineChart`
|
|
63
|
+
- `MatplotlibChart`
|
|
64
|
+
- `PieChart`
|
|
65
|
+
- `PlotlyChart`
|
|
66
|
+
- `ScatterChart`
|