datalab-platform 1.0.4__py3-none-any.whl → 1.1.0__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.
- datalab/__init__.py +1 -1
- datalab/config.py +4 -0
- datalab/control/baseproxy.py +160 -0
- datalab/control/remote.py +175 -1
- datalab/data/doc/DataLab_en.pdf +0 -0
- datalab/data/doc/DataLab_fr.pdf +0 -0
- datalab/data/icons/control/copy_connection_info.svg +11 -0
- datalab/data/icons/control/start_webapi_server.svg +19 -0
- datalab/data/icons/control/stop_webapi_server.svg +7 -0
- datalab/gui/main.py +221 -2
- datalab/gui/settings.py +10 -0
- datalab/gui/tour.py +2 -3
- datalab/locale/fr/LC_MESSAGES/datalab.mo +0 -0
- datalab/locale/fr/LC_MESSAGES/datalab.po +87 -1
- datalab/tests/__init__.py +32 -1
- datalab/tests/backbone/config_unit_test.py +1 -1
- datalab/tests/backbone/main_app_test.py +4 -0
- datalab/tests/backbone/memory_leak.py +1 -1
- datalab/tests/features/common/createobject_unit_test.py +1 -1
- datalab/tests/features/common/misc_app_test.py +5 -0
- datalab/tests/features/control/call_method_unit_test.py +104 -0
- datalab/tests/features/control/embedded1_unit_test.py +8 -0
- datalab/tests/features/control/remoteclient_app_test.py +39 -35
- datalab/tests/features/control/simpleclient_unit_test.py +7 -3
- datalab/tests/features/hdf5/h5browser2_unit.py +1 -1
- datalab/tests/features/image/background_dialog_test.py +2 -2
- datalab/tests/features/image/imagetools_unit_test.py +1 -1
- datalab/tests/features/signal/baseline_dialog_test.py +1 -1
- datalab/tests/webapi_test.py +395 -0
- datalab/webapi/__init__.py +95 -0
- datalab/webapi/actions.py +318 -0
- datalab/webapi/adapter.py +642 -0
- datalab/webapi/controller.py +379 -0
- datalab/webapi/routes.py +576 -0
- datalab/webapi/schema.py +198 -0
- datalab/webapi/serialization.py +388 -0
- datalab/widgets/status.py +61 -0
- {datalab_platform-1.0.4.dist-info → datalab_platform-1.1.0.dist-info}/METADATA +6 -2
- {datalab_platform-1.0.4.dist-info → datalab_platform-1.1.0.dist-info}/RECORD +45 -33
- /datalab/data/icons/{libre-gui-link.svg → control/libre-gui-link.svg} +0 -0
- /datalab/data/icons/{libre-gui-unlink.svg → control/libre-gui-unlink.svg} +0 -0
- {datalab_platform-1.0.4.dist-info → datalab_platform-1.1.0.dist-info}/WHEEL +0 -0
- {datalab_platform-1.0.4.dist-info → datalab_platform-1.1.0.dist-info}/entry_points.txt +0 -0
- {datalab_platform-1.0.4.dist-info → datalab_platform-1.1.0.dist-info}/licenses/LICENSE +0 -0
- {datalab_platform-1.0.4.dist-info → datalab_platform-1.1.0.dist-info}/top_level.txt +0 -0
datalab/__init__.py
CHANGED
|
@@ -24,7 +24,7 @@ except RuntimeError:
|
|
|
24
24
|
# this module is imported more than once, e.g. when running tests)
|
|
25
25
|
pass
|
|
26
26
|
|
|
27
|
-
__version__ = "1.0
|
|
27
|
+
__version__ = "1.1.0"
|
|
28
28
|
__docurl__ = __homeurl__ = "https://datalab-platform.com/"
|
|
29
29
|
__supporturl__ = "https://github.com/DataLab-Platform/DataLab/issues/new/choose"
|
|
30
30
|
|
datalab/config.py
CHANGED
|
@@ -135,6 +135,7 @@ class MainSection(conf.Section, metaclass=conf.SectionMeta):
|
|
|
135
135
|
process_isolation_enabled = conf.Option()
|
|
136
136
|
rpc_server_enabled = conf.Option()
|
|
137
137
|
rpc_server_port = conf.Option()
|
|
138
|
+
webapi_localhost_no_token = conf.Option() # Allow localhost without token
|
|
138
139
|
traceback_log_path = conf.ConfigPathOption()
|
|
139
140
|
traceback_log_available = conf.Option()
|
|
140
141
|
faulthandler_enabled = conf.Option()
|
|
@@ -406,6 +407,9 @@ def initialize():
|
|
|
406
407
|
Conf.main.color_mode.get("auto")
|
|
407
408
|
Conf.main.process_isolation_enabled.get(True)
|
|
408
409
|
Conf.main.rpc_server_enabled.get(True)
|
|
410
|
+
Conf.main.webapi_localhost_no_token.get(
|
|
411
|
+
True
|
|
412
|
+
) # Enabled by default (Web API is off by default)
|
|
409
413
|
Conf.main.traceback_log_path.get(f".{APP_NAME}_traceback.log")
|
|
410
414
|
Conf.main.faulthandler_log_path.get(f".{APP_NAME}_faulthandler.log")
|
|
411
415
|
Conf.main.available_memory_threshold.get(500)
|
datalab/control/baseproxy.py
CHANGED
|
@@ -127,6 +127,14 @@ class AbstractDLControl(abc.ABC):
|
|
|
127
127
|
def reset_all(self) -> None:
|
|
128
128
|
"""Reset all application data"""
|
|
129
129
|
|
|
130
|
+
@abc.abstractmethod
|
|
131
|
+
def remove_object(self, force: bool = False) -> None:
|
|
132
|
+
"""Remove current object from current panel.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
force: if True, remove object without confirmation. Defaults to False.
|
|
136
|
+
"""
|
|
137
|
+
|
|
130
138
|
@abc.abstractmethod
|
|
131
139
|
def toggle_auto_refresh(self, state: bool) -> None:
|
|
132
140
|
"""Toggle auto refresh state.
|
|
@@ -546,6 +554,85 @@ class AbstractDLControl(abc.ABC):
|
|
|
546
554
|
ValueError: unknown function
|
|
547
555
|
"""
|
|
548
556
|
|
|
557
|
+
# =========================================================================
|
|
558
|
+
# Web API control methods
|
|
559
|
+
# =========================================================================
|
|
560
|
+
|
|
561
|
+
@abc.abstractmethod
|
|
562
|
+
def start_webapi_server(
|
|
563
|
+
self,
|
|
564
|
+
host: str | None = None,
|
|
565
|
+
port: int | None = None,
|
|
566
|
+
) -> dict:
|
|
567
|
+
"""Start the Web API server.
|
|
568
|
+
|
|
569
|
+
Args:
|
|
570
|
+
host: Host address to bind to. Defaults to "127.0.0.1".
|
|
571
|
+
port: Port number. Defaults to auto-detect available port.
|
|
572
|
+
|
|
573
|
+
Returns:
|
|
574
|
+
Dictionary with "url" and "token" keys.
|
|
575
|
+
|
|
576
|
+
Raises:
|
|
577
|
+
RuntimeError: If Web API deps not installed or server already running.
|
|
578
|
+
"""
|
|
579
|
+
|
|
580
|
+
@abc.abstractmethod
|
|
581
|
+
def stop_webapi_server(self) -> None:
|
|
582
|
+
"""Stop the Web API server."""
|
|
583
|
+
|
|
584
|
+
@abc.abstractmethod
|
|
585
|
+
def get_webapi_status(self) -> dict:
|
|
586
|
+
"""Get Web API server status.
|
|
587
|
+
|
|
588
|
+
Returns:
|
|
589
|
+
Dictionary with "running", "url", and "token" keys.
|
|
590
|
+
"""
|
|
591
|
+
|
|
592
|
+
@abc.abstractmethod
|
|
593
|
+
def call_method(
|
|
594
|
+
self,
|
|
595
|
+
method_name: str,
|
|
596
|
+
*args,
|
|
597
|
+
panel: str | None = None,
|
|
598
|
+
**kwargs,
|
|
599
|
+
):
|
|
600
|
+
"""Call a public method on a panel or main window.
|
|
601
|
+
|
|
602
|
+
This generic method allows calling any public method that is not explicitly
|
|
603
|
+
exposed in the proxy API. The method resolution follows this order:
|
|
604
|
+
|
|
605
|
+
1. If panel is specified: call method on that specific panel
|
|
606
|
+
2. If panel is None:
|
|
607
|
+
a. Try to call method on main window (DLMainWindow)
|
|
608
|
+
b. If not found, try to call method on current panel (BaseDataPanel)
|
|
609
|
+
|
|
610
|
+
This makes it convenient to call panel methods without specifying the panel
|
|
611
|
+
parameter when working on the current panel.
|
|
612
|
+
|
|
613
|
+
Args:
|
|
614
|
+
method_name: Name of the method to call
|
|
615
|
+
*args: Positional arguments to pass to the method
|
|
616
|
+
panel: Panel name ("signal", "image", or None for auto-detection).
|
|
617
|
+
Defaults to None.
|
|
618
|
+
**kwargs: Keyword arguments to pass to the method
|
|
619
|
+
|
|
620
|
+
Returns:
|
|
621
|
+
The return value of the called method
|
|
622
|
+
|
|
623
|
+
Raises:
|
|
624
|
+
AttributeError: If the method does not exist or is not public
|
|
625
|
+
ValueError: If the panel name is invalid
|
|
626
|
+
|
|
627
|
+
Examples:
|
|
628
|
+
>>> # Call remove_object on current panel (auto-detected)
|
|
629
|
+
>>> proxy.call_method("remove_object", force=True)
|
|
630
|
+
>>> # Call a signal panel method specifically
|
|
631
|
+
>>> proxy.call_method("delete_all_objects", panel="signal")
|
|
632
|
+
>>> # Call main window method
|
|
633
|
+
>>> proxy.call_method("raise_window")
|
|
634
|
+
"""
|
|
635
|
+
|
|
549
636
|
|
|
550
637
|
class BaseProxy(AbstractDLControl, metaclass=abc.ABCMeta):
|
|
551
638
|
"""Common base class for DataLab proxies
|
|
@@ -594,6 +681,14 @@ class BaseProxy(AbstractDLControl, metaclass=abc.ABCMeta):
|
|
|
594
681
|
"""Reset all application data"""
|
|
595
682
|
self._datalab.reset_all()
|
|
596
683
|
|
|
684
|
+
def remove_object(self, force: bool = False) -> None:
|
|
685
|
+
"""Remove current object from current panel.
|
|
686
|
+
|
|
687
|
+
Args:
|
|
688
|
+
force: if True, remove object without confirmation. Defaults to False.
|
|
689
|
+
"""
|
|
690
|
+
self._datalab.remove_object(force)
|
|
691
|
+
|
|
597
692
|
def toggle_auto_refresh(self, state: bool) -> None:
|
|
598
693
|
"""Toggle auto refresh state.
|
|
599
694
|
|
|
@@ -844,3 +939,68 @@ class BaseProxy(AbstractDLControl, metaclass=abc.ABCMeta):
|
|
|
844
939
|
filename: Filename.
|
|
845
940
|
"""
|
|
846
941
|
return self._datalab.import_macro_from_file(filename)
|
|
942
|
+
|
|
943
|
+
def call_method(
|
|
944
|
+
self,
|
|
945
|
+
method_name: str,
|
|
946
|
+
*args,
|
|
947
|
+
panel: str | None = None,
|
|
948
|
+
**kwargs,
|
|
949
|
+
):
|
|
950
|
+
"""Call a public method on a panel or main window.
|
|
951
|
+
|
|
952
|
+
Method resolution order when panel is None:
|
|
953
|
+
1. Try main window (DLMainWindow)
|
|
954
|
+
2. If not found, try current panel (BaseDataPanel)
|
|
955
|
+
|
|
956
|
+
Args:
|
|
957
|
+
method_name: Name of the method to call
|
|
958
|
+
*args: Positional arguments to pass to the method
|
|
959
|
+
panel: Panel name ("signal", "image", or None for auto-detection).
|
|
960
|
+
Defaults to None.
|
|
961
|
+
**kwargs: Keyword arguments to pass to the method
|
|
962
|
+
|
|
963
|
+
Returns:
|
|
964
|
+
The return value of the called method
|
|
965
|
+
|
|
966
|
+
Raises:
|
|
967
|
+
AttributeError: If the method does not exist or is not public
|
|
968
|
+
ValueError: If the panel name is invalid
|
|
969
|
+
"""
|
|
970
|
+
return self._datalab.call_method(method_name, *args, panel=panel, **kwargs)
|
|
971
|
+
|
|
972
|
+
# =========================================================================
|
|
973
|
+
# Web API control methods
|
|
974
|
+
# =========================================================================
|
|
975
|
+
|
|
976
|
+
def start_webapi_server(
|
|
977
|
+
self,
|
|
978
|
+
host: str | None = None,
|
|
979
|
+
port: int | None = None,
|
|
980
|
+
) -> dict:
|
|
981
|
+
"""Start the Web API server.
|
|
982
|
+
|
|
983
|
+
Args:
|
|
984
|
+
host: Host address to bind to. Defaults to "127.0.0.1".
|
|
985
|
+
port: Port number. Defaults to auto-detect available port.
|
|
986
|
+
|
|
987
|
+
Returns:
|
|
988
|
+
Dictionary with "url" and "token" keys.
|
|
989
|
+
|
|
990
|
+
Raises:
|
|
991
|
+
RuntimeError: If Web API dependencies not installed or server
|
|
992
|
+
already running.
|
|
993
|
+
"""
|
|
994
|
+
return self._datalab.start_webapi_server(host, port)
|
|
995
|
+
|
|
996
|
+
def stop_webapi_server(self) -> None:
|
|
997
|
+
"""Stop the Web API server."""
|
|
998
|
+
return self._datalab.stop_webapi_server()
|
|
999
|
+
|
|
1000
|
+
def get_webapi_status(self) -> dict:
|
|
1001
|
+
"""Get Web API server status.
|
|
1002
|
+
|
|
1003
|
+
Returns:
|
|
1004
|
+
Dictionary with "running", "url", and "token" keys.
|
|
1005
|
+
"""
|
|
1006
|
+
return self._datalab.get_webapi_status()
|
datalab/control/remote.py
CHANGED
|
@@ -48,12 +48,26 @@ def remote_call(func: Callable) -> object:
|
|
|
48
48
|
@functools.wraps(func)
|
|
49
49
|
def method_wrapper(*args, **kwargs):
|
|
50
50
|
"""Decorator wrapper function"""
|
|
51
|
-
self = args[0] # extracting 'self' from method arguments
|
|
51
|
+
self: RemoteServer = args[0] # extracting 'self' from method arguments
|
|
52
52
|
self.is_ready = False
|
|
53
53
|
output = func(*args, **kwargs)
|
|
54
54
|
while not self.is_ready:
|
|
55
55
|
QC.QCoreApplication.processEvents()
|
|
56
56
|
time.sleep(0.05)
|
|
57
|
+
# Check if an exception was raised and stored by the slot
|
|
58
|
+
if self.exception is not None:
|
|
59
|
+
exc = self.exception
|
|
60
|
+
self.exception = None # Clear the exception
|
|
61
|
+
raise exc
|
|
62
|
+
# For methods that use signal/slot pattern (like call_method),
|
|
63
|
+
# they return self.result which is set by the slot. The function
|
|
64
|
+
# itself returns this value, but it's set asynchronously, so we
|
|
65
|
+
# need to return the value AFTER waiting, not the initial return value.
|
|
66
|
+
# Since we wait above, self.result should now be set by the slot.
|
|
67
|
+
# If the function returned self.result, use the updated value.
|
|
68
|
+
if output is None:
|
|
69
|
+
# Return self.result which should be set by now
|
|
70
|
+
return self.result
|
|
57
71
|
return output
|
|
58
72
|
|
|
59
73
|
return method_wrapper
|
|
@@ -81,6 +95,7 @@ class RemoteServer(QC.QThread):
|
|
|
81
95
|
SIG_TOGGLE_AUTO_REFRESH = QC.Signal(bool)
|
|
82
96
|
SIG_TOGGLE_SHOW_TITLES = QC.Signal(bool)
|
|
83
97
|
SIG_RESET_ALL = QC.Signal()
|
|
98
|
+
SIG_REMOVE_OBJECT = QC.Signal(bool)
|
|
84
99
|
SIG_SAVE_TO_H5 = QC.Signal(str)
|
|
85
100
|
SIG_OPEN_H5 = QC.Signal(list, bool, bool)
|
|
86
101
|
SIG_IMPORT_H5 = QC.Signal(str, bool)
|
|
@@ -90,6 +105,7 @@ class RemoteServer(QC.QThread):
|
|
|
90
105
|
SIG_RUN_MACRO = QC.Signal(str)
|
|
91
106
|
SIG_STOP_MACRO = QC.Signal(str)
|
|
92
107
|
SIG_IMPORT_MACRO_FROM_FILE = QC.Signal(str)
|
|
108
|
+
SIG_CALL_METHOD = QC.Signal(str, list, object, dict)
|
|
93
109
|
|
|
94
110
|
def __init__(self, win: DLMainWindow) -> None:
|
|
95
111
|
QC.QThread.__init__(self)
|
|
@@ -97,6 +113,8 @@ class RemoteServer(QC.QThread):
|
|
|
97
113
|
self.is_ready = True
|
|
98
114
|
self.server: SimpleXMLRPCServer | None = None
|
|
99
115
|
self.win = win
|
|
116
|
+
self.result = None
|
|
117
|
+
self.exception = None
|
|
100
118
|
win.SIG_READY.connect(self.datalab_is_ready)
|
|
101
119
|
win.SIG_CLOSING.connect(self.shutdown_server)
|
|
102
120
|
self.SIG_CLOSE_APP.connect(win.close)
|
|
@@ -113,6 +131,7 @@ class RemoteServer(QC.QThread):
|
|
|
113
131
|
self.SIG_TOGGLE_AUTO_REFRESH.connect(win.toggle_auto_refresh)
|
|
114
132
|
self.SIG_TOGGLE_SHOW_TITLES.connect(win.toggle_show_titles)
|
|
115
133
|
self.SIG_RESET_ALL.connect(win.reset_all)
|
|
134
|
+
self.SIG_REMOVE_OBJECT.connect(win.remove_object)
|
|
116
135
|
self.SIG_SAVE_TO_H5.connect(win.save_to_h5_file)
|
|
117
136
|
self.SIG_OPEN_H5.connect(win.open_h5_files)
|
|
118
137
|
self.SIG_IMPORT_H5.connect(win.import_h5_file)
|
|
@@ -122,6 +141,7 @@ class RemoteServer(QC.QThread):
|
|
|
122
141
|
self.SIG_RUN_MACRO.connect(win.run_macro)
|
|
123
142
|
self.SIG_STOP_MACRO.connect(win.stop_macro)
|
|
124
143
|
self.SIG_IMPORT_MACRO_FROM_FILE.connect(win.import_macro_from_file)
|
|
144
|
+
self.SIG_CALL_METHOD.connect(win.call_method_slot)
|
|
125
145
|
|
|
126
146
|
def serve(self) -> None:
|
|
127
147
|
"""Start server and serve forever"""
|
|
@@ -234,6 +254,15 @@ class RemoteServer(QC.QThread):
|
|
|
234
254
|
"""Reset all application data"""
|
|
235
255
|
self.SIG_RESET_ALL.emit()
|
|
236
256
|
|
|
257
|
+
@remote_call
|
|
258
|
+
def remove_object(self, force: bool = False) -> None:
|
|
259
|
+
"""Remove current object from current panel.
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
force: if True, remove object without confirmation. Defaults to False.
|
|
263
|
+
"""
|
|
264
|
+
self.SIG_REMOVE_OBJECT.emit(force)
|
|
265
|
+
|
|
237
266
|
@remote_call
|
|
238
267
|
def save_to_h5_file(self, filename: str) -> None:
|
|
239
268
|
"""Save to a DataLab HDF5 file.
|
|
@@ -659,6 +688,83 @@ class RemoteServer(QC.QThread):
|
|
|
659
688
|
"""
|
|
660
689
|
self.SIG_IMPORT_MACRO_FROM_FILE.emit(filename)
|
|
661
690
|
|
|
691
|
+
@remote_call
|
|
692
|
+
def call_method(
|
|
693
|
+
self,
|
|
694
|
+
method_name: str,
|
|
695
|
+
call_params: dict,
|
|
696
|
+
):
|
|
697
|
+
"""Call a public method on a panel or main window.
|
|
698
|
+
|
|
699
|
+
Method resolution order when panel is None:
|
|
700
|
+
1. Try main window (DLMainWindow)
|
|
701
|
+
2. If not found, try current panel (BaseDataPanel)
|
|
702
|
+
|
|
703
|
+
Args:
|
|
704
|
+
method_name: Name of the method to call
|
|
705
|
+
call_params: Dictionary with keys 'args' (list), 'panel' (str|None),
|
|
706
|
+
'kwargs' (dict). Defaults to empty for missing keys.
|
|
707
|
+
|
|
708
|
+
Returns:
|
|
709
|
+
The return value of the called method
|
|
710
|
+
|
|
711
|
+
Raises:
|
|
712
|
+
AttributeError: If the method does not exist or is not public
|
|
713
|
+
ValueError: If the panel name is invalid
|
|
714
|
+
|
|
715
|
+
"""
|
|
716
|
+
args = call_params.get("args", [])
|
|
717
|
+
panel = call_params.get("panel")
|
|
718
|
+
kwargs = call_params.get("kwargs", {})
|
|
719
|
+
self.result = None # Initialize result
|
|
720
|
+
self.SIG_CALL_METHOD.emit(method_name, args, panel, kwargs)
|
|
721
|
+
# The decorator waits for is_ready, then this returns self.result
|
|
722
|
+
# which was set by call_method_slot
|
|
723
|
+
return self.result
|
|
724
|
+
|
|
725
|
+
# =========================================================================
|
|
726
|
+
# Web API control methods
|
|
727
|
+
# =========================================================================
|
|
728
|
+
|
|
729
|
+
@remote_call
|
|
730
|
+
def start_webapi_server(
|
|
731
|
+
self,
|
|
732
|
+
host: str | None = None,
|
|
733
|
+
port: int | None = None,
|
|
734
|
+
) -> dict:
|
|
735
|
+
"""Start the Web API server.
|
|
736
|
+
|
|
737
|
+
Args:
|
|
738
|
+
host: Host address to bind to. Defaults to "127.0.0.1".
|
|
739
|
+
port: Port number. Defaults to auto-detect available port.
|
|
740
|
+
|
|
741
|
+
Returns:
|
|
742
|
+
Dictionary with "url" and "token" keys.
|
|
743
|
+
|
|
744
|
+
Raises:
|
|
745
|
+
RuntimeError: If Web API dependencies not installed or server
|
|
746
|
+
already running.
|
|
747
|
+
"""
|
|
748
|
+
# Delegate to main window method which is @remote_controlled
|
|
749
|
+
# This ensures SIG_READY is emitted for the @remote_call decorator
|
|
750
|
+
return self.win.start_webapi_server(host, port)
|
|
751
|
+
|
|
752
|
+
@remote_call
|
|
753
|
+
def stop_webapi_server(self) -> None:
|
|
754
|
+
"""Stop the Web API server."""
|
|
755
|
+
# Delegate to main window method which is @remote_controlled
|
|
756
|
+
return self.win.stop_webapi_server()
|
|
757
|
+
|
|
758
|
+
@remote_call
|
|
759
|
+
def get_webapi_status(self) -> dict:
|
|
760
|
+
"""Get Web API server status.
|
|
761
|
+
|
|
762
|
+
Returns:
|
|
763
|
+
Dictionary with "running", "url", and "token" keys.
|
|
764
|
+
"""
|
|
765
|
+
# Delegate to main window method which is @remote_controlled
|
|
766
|
+
return self.win.get_webapi_status()
|
|
767
|
+
|
|
662
768
|
|
|
663
769
|
RemoteServer.check_remote_functions()
|
|
664
770
|
|
|
@@ -1038,3 +1144,71 @@ class RemoteClient(BaseProxy):
|
|
|
1038
1144
|
items_json = items_to_json(items)
|
|
1039
1145
|
if items_json is not None:
|
|
1040
1146
|
self._datalab.add_annotations_from_items(items_json, refresh_plot, panel)
|
|
1147
|
+
|
|
1148
|
+
def call_method(
|
|
1149
|
+
self,
|
|
1150
|
+
method_name: str,
|
|
1151
|
+
*args,
|
|
1152
|
+
panel: str | None = None,
|
|
1153
|
+
**kwargs,
|
|
1154
|
+
):
|
|
1155
|
+
"""Call a public method on a panel or main window.
|
|
1156
|
+
|
|
1157
|
+
Method resolution order when panel is None:
|
|
1158
|
+
1. Try main window (DLMainWindow)
|
|
1159
|
+
2. If not found, try current panel (BaseDataPanel)
|
|
1160
|
+
|
|
1161
|
+
Args:
|
|
1162
|
+
method_name: Name of the method to call
|
|
1163
|
+
*args: Positional arguments to pass to the method
|
|
1164
|
+
panel: Panel name ("signal", "image", or None for auto-detection).
|
|
1165
|
+
Defaults to None.
|
|
1166
|
+
**kwargs: Keyword arguments to pass to the method
|
|
1167
|
+
|
|
1168
|
+
Returns:
|
|
1169
|
+
The return value of the called method
|
|
1170
|
+
|
|
1171
|
+
Raises:
|
|
1172
|
+
AttributeError: If the method does not exist or is not public
|
|
1173
|
+
ValueError: If the panel name is invalid
|
|
1174
|
+
"""
|
|
1175
|
+
# Convert args/kwargs to single dict for XML-RPC serialization
|
|
1176
|
+
# This avoids XML-RPC signature mismatch issues with default parameters
|
|
1177
|
+
call_params = {
|
|
1178
|
+
"args": list(args) if args else [],
|
|
1179
|
+
"panel": panel,
|
|
1180
|
+
"kwargs": dict(kwargs) if kwargs else {},
|
|
1181
|
+
}
|
|
1182
|
+
return self._datalab.call_method(method_name, call_params)
|
|
1183
|
+
|
|
1184
|
+
# === WebAPI Server Control Methods ===
|
|
1185
|
+
|
|
1186
|
+
def start_webapi_server(
|
|
1187
|
+
self, host: str = "127.0.0.1", port: int = 8080
|
|
1188
|
+
) -> dict[str, str | int]:
|
|
1189
|
+
"""Start the WebAPI server.
|
|
1190
|
+
|
|
1191
|
+
Args:
|
|
1192
|
+
host: Host address to bind to. Defaults to "127.0.0.1".
|
|
1193
|
+
port: Port number. Defaults to 8080.
|
|
1194
|
+
|
|
1195
|
+
Returns:
|
|
1196
|
+
Dictionary with server info including 'url' and 'token'.
|
|
1197
|
+
"""
|
|
1198
|
+
return self._datalab.start_webapi_server(host, port)
|
|
1199
|
+
|
|
1200
|
+
def stop_webapi_server(self) -> bool:
|
|
1201
|
+
"""Stop the WebAPI server.
|
|
1202
|
+
|
|
1203
|
+
Returns:
|
|
1204
|
+
True if server was stopped, False if it wasn't running.
|
|
1205
|
+
"""
|
|
1206
|
+
return self._datalab.stop_webapi_server()
|
|
1207
|
+
|
|
1208
|
+
def get_webapi_status(self) -> dict[str, str | int | bool]:
|
|
1209
|
+
"""Get the current status of the WebAPI server.
|
|
1210
|
+
|
|
1211
|
+
Returns:
|
|
1212
|
+
Dictionary with status info including 'running', 'url', and 'port'.
|
|
1213
|
+
"""
|
|
1214
|
+
return self._datalab.get_webapi_status()
|
datalab/data/doc/DataLab_en.pdf
CHANGED
|
Binary file
|
datalab/data/doc/DataLab_fr.pdf
CHANGED
|
Binary file
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="256" height="256" viewBox="0 0 256 256" xml:space="preserve">
|
|
2
|
+
<g style="stroke: none; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: none; fill-rule: nonzero; opacity: 1;" transform="translate(1.4065934065934016 1.4065934065934016) scale(2.81 2.81)">
|
|
3
|
+
<path d="M 44.092 7.316 c 0.294 -1.329 0.228 -2.79 -0.865 -4.437 C 42.093 1.171 40.271 -0.001 38.221 0 c -3.32 0.002 -6.012 2.695 -6.012 6.016 c 0 0.448 0.057 0.881 0.15 1.3 H 26.79 c -1.013 0 -1.835 0.821 -1.835 1.835 v 4.243 c 0 1.997 1.619 3.617 3.617 3.617 h 19.306 c 1.997 0 3.617 -1.619 3.617 -3.617 V 9.151 c 0 -1.013 -0.821 -1.835 -1.835 -1.835 H 44.092 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(25,118,210); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round"/>
|
|
4
|
+
<path d="M 76.203 90 h -34.86 c -2.298 0 -4.16 -1.863 -4.16 -4.16 V 36.921 c 0 -2.298 1.863 -4.16 4.16 -4.16 h 34.86 c 2.298 0 4.16 1.863 4.16 4.16 V 85.84 C 80.363 88.137 78.501 90 76.203 90 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(216,230,239); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round"/>
|
|
5
|
+
<path d="M 37.183 36.921 c 0 -2.298 1.863 -4.16 4.16 -4.16 h 25.471 v -17.72 c 0 -2.233 -1.811 -4.044 -4.044 -4.044 H 51.495 v 2.397 c 0 1.997 -1.619 3.617 -3.617 3.617 H 28.572 c -1.997 0 -3.617 -1.619 -3.617 -3.617 v -2.397 H 13.681 c -2.233 0 -4.044 1.811 -4.044 4.044 v 63.662 c 0 2.233 1.811 4.044 4.044 4.044 h 23.502 V 36.921 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(33,150,243); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round"/>
|
|
6
|
+
<path d="M 71.325 50.294 H 46.221 c -0.552 0 -1 -0.447 -1 -1 s 0.448 -1 1 -1 h 25.104 c 0.553 0 1 0.447 1 1 S 71.878 50.294 71.325 50.294 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(33,150,243); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round"/>
|
|
7
|
+
<path d="M 71.325 62.381 H 46.221 c -0.552 0 -1 -0.447 -1 -1 s 0.448 -1 1 -1 h 25.104 c 0.553 0 1 0.447 1 1 S 71.878 62.381 71.325 62.381 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(33,150,243); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round"/>
|
|
8
|
+
<line x1="-6.509" y1="0" x2="6.509" y2="0" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(33,150,243); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) "/>
|
|
9
|
+
<path d="M 59.238 74.468 H 46.221 c -0.552 0 -1 -0.447 -1 -1 s 0.448 -1 1 -1 h 13.018 c 0.553 0 1 0.447 1 1 S 59.791 74.468 59.238 74.468 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(33,150,243); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round"/>
|
|
10
|
+
</g>
|
|
11
|
+
</svg>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="256" height="256" viewBox="0 0 256 256" xml:space="preserve">
|
|
2
|
+
<g style="stroke: none; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: none; fill-rule: nonzero; opacity: 1;" transform="translate(1.4065934065934016 1.4065934065934016) scale(2.81 2.81)">
|
|
3
|
+
<path d="M 2.653 89.174 c -0.256 0 -0.512 -0.098 -0.707 -0.293 c -0.391 -0.391 -0.391 -1.023 0 -1.414 l 40.243 -40.243 c 0.391 -0.391 1.023 -0.391 1.414 0 s 0.391 1.023 0 1.414 L 3.36 88.881 C 3.164 89.076 2.908 89.174 2.653 89.174 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round"/>
|
|
4
|
+
<path d="M 79.421 42.886 c -0.42 0 -0.83 0.04 -1.234 0.098 c -0.471 -2.513 -2.028 -4.641 -4.165 -5.88 c -2.078 2.778 -4.36 5.624 -6.854 8.539 l 0.246 9.709 h 10.621 h 9.12 c 0.573 -1.155 0.903 -2.452 0.903 -3.828 C 88.058 46.753 84.191 42.886 79.421 42.886 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(248,204,29); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round"/>
|
|
5
|
+
<path d="M 23.801 25.879 c 1.726 -1.726 4.122 -2.689 6.557 -2.619 l 15.765 0.4 c 2.773 -2.373 5.481 -4.546 8.131 -6.543 c -2.083 -3.049 -5.585 -5.052 -9.557 -5.052 c -0.563 0 -1.111 0.054 -1.653 0.132 C 42.041 6.83 37.34 2.767 31.683 2.767 c -5.657 0 -10.358 4.063 -11.362 9.428 c -0.541 -0.078 -1.09 -0.132 -1.653 -0.132 c -6.389 0 -11.568 5.179 -11.568 11.568 c 0 1.844 0.443 3.581 1.21 5.128 h 12.611 L 23.801 25.879 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(248,204,29); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round"/>
|
|
6
|
+
<path d="M 66.501 30.498 c -1.764 0 -3.423 -0.687 -4.67 -1.935 c -1.248 -1.248 -1.936 -2.906 -1.936 -4.67 c 0 -1.765 0.688 -3.423 1.936 -4.671 c 1.247 -1.248 2.906 -1.935 4.67 -1.935 c 1.765 0 3.424 0.687 4.671 1.935 c 1.248 1.248 1.935 2.906 1.935 4.671 c 0 1.764 -0.687 3.423 -1.935 4.67 l 0 0 C 69.925 29.811 68.265 30.498 66.501 30.498 z M 66.501 19.287 c -1.229 0 -2.386 0.479 -3.256 1.349 c -0.87 0.87 -1.35 2.026 -1.35 3.257 c 0 1.23 0.479 2.386 1.35 3.256 c 0.87 0.87 2.026 1.349 3.256 1.349 c 1.23 0 2.387 -0.479 3.257 -1.349 l 0 0 c 0.87 -0.87 1.349 -2.026 1.349 -3.256 c 0 -1.23 -0.479 -2.387 -1.349 -3.257 C 68.887 19.766 67.731 19.287 66.501 19.287 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round"/>
|
|
7
|
+
<path d="M 89.987 4.147 c 0.084 -0.903 -0.235 -1.789 -0.877 -2.431 c -0.642 -0.642 -1.534 -0.961 -2.431 -0.877 C 75.05 1.928 61.171 9.362 45.271 22.986 L 29.41 22.584 c -2.377 -0.071 -4.688 0.867 -6.362 2.541 l -7.246 7.246 c -0.804 0.804 -1.11 1.941 -0.817 3.04 c 0.293 1.099 1.123 1.933 2.221 2.231 l 11.27 3.056 l 7.056 7.055 c 0.391 0.391 1.023 0.391 1.414 0 s 0.391 -1.023 0 -1.414 l -6.547 -6.547 C 52.91 16.665 71.904 4.232 86.866 2.831 c 0.309 -0.024 0.611 0.081 0.83 0.3 s 0.328 0.522 0.299 0.83 c -1.4 14.961 -13.835 33.956 -36.961 56.467 l -6.548 -6.548 c -0.391 -0.391 -1.023 -0.391 -1.414 0 s -0.391 1.023 0 1.414 l 7.057 7.056 l 3.056 11.269 c 0.297 1.099 1.132 1.929 2.231 2.222 c 0.274 0.073 0.551 0.109 0.825 0.109 c 0.82 -0.001 1.611 -0.323 2.214 -0.927 l 7.245 -7.245 c 1.676 -1.676 2.603 -3.994 2.542 -6.362 L 67.84 45.555 C 81.463 29.655 88.897 15.776 89.987 4.147 z M 28.69 38.684 l -10.96 -2.972 c -0.579 -0.157 -0.76 -0.623 -0.812 -0.815 c -0.051 -0.193 -0.125 -0.688 0.299 -1.111 l 7.246 -7.246 c 1.29 -1.289 3.08 -2 4.897 -1.956 l 13.674 0.346 C 38.417 29.005 33.633 33.593 28.69 38.684 z M 66.242 61.467 c 0.047 1.822 -0.666 3.607 -1.956 4.897 l -7.245 7.245 c -0.424 0.424 -0.917 0.35 -1.11 0.299 c -0.193 -0.052 -0.659 -0.233 -0.816 -0.813 l -2.972 -10.959 c 5.09 -4.943 9.679 -9.726 13.754 -14.343 L 66.242 61.467 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round"/>
|
|
8
|
+
<path d="M 25.031 78.36 c -0.256 0 -0.512 -0.098 -0.707 -0.293 c -0.391 -0.391 -0.391 -1.023 0 -1.414 L 40.67 60.306 c 0.391 -0.391 1.023 -0.391 1.414 0 s 0.391 1.023 0 1.414 L 25.738 78.067 C 25.542 78.262 25.286 78.36 25.031 78.36 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(248,204,29); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round"/>
|
|
9
|
+
<path d="M 8.92 70.356 c -0.256 0 -0.512 -0.098 -0.707 -0.293 c -0.391 -0.391 -0.391 -1.023 0 -1.414 l 19.811 -19.811 c 0.391 -0.391 1.023 -0.391 1.414 0 s 0.391 1.023 0 1.414 L 9.627 70.063 C 9.431 70.259 9.176 70.356 8.92 70.356 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(248,204,29); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round"/>
|
|
10
|
+
<path d="M 35.868 79.835 c -0.256 0 -0.512 -0.098 -0.707 -0.293 c -0.391 -0.391 -0.391 -1.023 0 -1.414 l 8.834 -8.835 c 0.391 -0.391 1.023 -0.391 1.414 0 s 0.391 1.023 0 1.414 l -8.834 8.835 C 36.38 79.737 36.124 79.835 35.868 79.835 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(248,204,29); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round"/>
|
|
11
|
+
<path d="M 13.346 53.618 c -0.256 0 -0.512 -0.098 -0.707 -0.293 c -0.391 -0.391 -0.391 -1.023 0 -1.414 l 7.224 -7.223 c 0.391 -0.391 1.023 -0.391 1.414 0 s 0.391 1.023 0 1.415 l -7.224 7.223 C 13.858 53.52 13.602 53.618 13.346 53.618 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(248,204,29); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round"/>
|
|
12
|
+
<path d="M 19.829 85.689 c -0.552 0 -1 -0.447 -1 -1 v -4.256 c 0 -0.553 0.448 -1 1 -1 s 1 0.447 1 1 v 4.256 C 20.829 85.242 20.382 85.689 19.829 85.689 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(248,204,29); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round"/>
|
|
13
|
+
<path d="M 21.957 83.561 h -4.255 c -0.552 0 -1 -0.447 -1 -1 s 0.448 -1 1 -1 h 4.255 c 0.552 0 1 0.447 1 1 S 22.509 83.561 21.957 83.561 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(248,204,29); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round"/>
|
|
14
|
+
<path d="M 8.137 60.955 c -0.552 0 -1 -0.447 -1 -1 v -4.256 c 0 -0.553 0.448 -1 1 -1 s 1 0.447 1 1 v 4.256 C 9.137 60.508 8.69 60.955 8.137 60.955 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(248,204,29); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round"/>
|
|
15
|
+
<path d="M 10.265 58.827 H 6.01 c -0.552 0 -1 -0.447 -1 -1 s 0.448 -1 1 -1 h 4.255 c 0.552 0 1 0.447 1 1 S 10.817 58.827 10.265 58.827 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(248,204,29); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round"/>
|
|
16
|
+
<path d="M 3.194 78.277 c -0.853 0 -1.655 -0.333 -2.259 -0.937 C 0.332 76.738 0 75.935 0 75.083 s 0.332 -1.655 0.935 -2.259 c 1.246 -1.244 3.272 -1.244 4.517 0 c 0.604 0.604 0.936 1.406 0.936 2.259 s -0.332 1.654 -0.935 2.258 l 0 0 C 4.849 77.944 4.047 78.277 3.194 78.277 z M 3.194 73.889 c -0.306 0 -0.611 0.116 -0.844 0.349 C 2.124 74.464 2 74.763 2 75.083 c 0 0.318 0.124 0.618 0.35 0.844 c 0.451 0.451 1.238 0.451 1.688 0 h 0 c 0.226 -0.226 0.35 -0.525 0.35 -0.844 c 0 -0.319 -0.124 -0.619 -0.35 -0.845 C 3.805 74.006 3.499 73.889 3.194 73.889 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(248,204,29); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round"/>
|
|
17
|
+
<path d="M 30.658 87.236 c -0.818 0 -1.636 -0.312 -2.258 -0.934 c -0.604 -0.603 -0.936 -1.405 -0.936 -2.259 c 0 -0.853 0.332 -1.655 0.936 -2.258 c 1.245 -1.244 3.271 -1.244 4.517 0 c 0.604 0.603 0.936 1.405 0.936 2.258 c 0 0.854 -0.332 1.656 -0.936 2.259 C 32.294 86.925 31.476 87.236 30.658 87.236 z M 30.658 82.851 c -0.306 0 -0.611 0.116 -0.844 0.349 c -0.226 0.226 -0.35 0.525 -0.35 0.844 c 0 0.319 0.124 0.619 0.35 0.845 c 0.466 0.465 1.223 0.465 1.688 0 c 0.226 -0.226 0.35 -0.525 0.35 -0.845 c 0 -0.318 -0.124 -0.618 -0.35 -0.844 C 31.27 82.968 30.964 82.851 30.658 82.851 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(248,204,29); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round"/>
|
|
18
|
+
</g>
|
|
19
|
+
</svg>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="256" height="256" viewBox="0 0 256 256" xml:space="preserve">
|
|
2
|
+
<g style="stroke: none; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: none; fill-rule: nonzero; opacity: 1;" transform="translate(1.4065934065934016 1.4065934065934016) scale(2.81 2.81)">
|
|
3
|
+
<circle cx="45" cy="45" r="45" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(230,62,50); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) "/>
|
|
4
|
+
<path d="M 45 32 c -1.104 0 -2 -0.896 -2 -2 V 18.595 c 0 -1.104 0.896 -2 2 -2 s 2 0.896 2 2 V 30 C 47 31.104 46.104 32 45 32 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(255,255,255); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round"/>
|
|
5
|
+
<path d="M 45 71 c -14.336 0 -26 -11.663 -26 -26 c 0 -9.587 5.251 -18.366 13.704 -22.912 c 0.973 -0.525 2.186 -0.158 2.708 0.814 c 0.523 0.973 0.159 2.186 -0.814 2.708 C 27.444 29.458 23 36.888 23 45 c 0 12.131 9.869 22 22 22 s 22 -9.869 22 -22 c 0 -8.112 -4.444 -15.542 -11.599 -19.389 c -0.973 -0.523 -1.337 -1.736 -0.814 -2.708 c 0.522 -0.972 1.733 -1.339 2.709 -0.814 C 65.749 26.634 71 35.414 71 45 C 71 59.337 59.337 71 45 71 z" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(255,255,255); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round"/>
|
|
6
|
+
</g>
|
|
7
|
+
</svg>
|