From b0e7de2d2c0077f37c2341aa09a4741bce5ec3c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mose=20M=C3=BCller?= Date: Wed, 11 Oct 2023 13:35:59 +0200 Subject: [PATCH] docs: updating Readme --- README.md | 118 ++++++++++++++++++------- docs/images/ColouredEnum_component.png | Bin 0 -> 18439 bytes 2 files changed, 84 insertions(+), 34 deletions(-) create mode 100644 docs/images/ColouredEnum_component.png diff --git a/README.md b/README.md index d272df4..64c8888 100644 --- a/README.md +++ b/README.md @@ -224,6 +224,7 @@ if __name__ == "__main__": **Note** that defining classes within `DataService` classes is not supported (see [this issue](https://github.com/tiqi-group/pydase/issues/16)). ### Custom Components (`pydase.components`) + The custom components in `pydase` have two main parts: - A **Python Component Class** in the backend, implementing the logic needed to set, update, and manage the component's state and data. @@ -231,61 +232,110 @@ The custom components in `pydase` have two main parts: Below are the components available in the `pydase.components` module, accompanied by their Python usage: -- `Image`: This component allows users to display and update images within the application. +#### `Image` - ```python - import matplotlib.pyplot as plt - import numpy as np +This component provides a versatile interface for displaying images within the application. Users can update and manage images from various sources, including local paths, URLs, and even matplotlib figures. - import pydase - from pydase.components.image import Image +The component offers methods to load images seamlessly, ensuring that visual content is easily integrated and displayed within the data service. + +```python +import matplotlib.pyplot as plt +import numpy as np + +import pydase +from pydase.components.image import Image - class MyDataService(pydase.DataService): - my_image = Image() +class MyDataService(pydase.DataService): + my_image = Image() - if __name__ == "__main__": - service = MyDataService() - # loading from local path - service.my_image.load_from_path("/your/image/path/") +if __name__ == "__main__": + service = MyDataService() + # loading from local path + service.my_image.load_from_path("/your/image/path/") - # loading from a URL - service.my_image.load_from_url("https://cataas.com/cat") + # loading from a URL + service.my_image.load_from_url("https://cataas.com/cat") - # loading a matplotlib figure - fig = plt.figure() - x = np.linspace(0, 2 * np.pi) - plt.plot(x, np.sin(x)) - plt.grid() - service.my_image.load_from_matplotlib_figure(fig) + # loading a matplotlib figure + fig = plt.figure() + x = np.linspace(0, 2 * np.pi) + plt.plot(x, np.sin(x)) + plt.grid() + service.my_image.load_from_matplotlib_figure(fig) - pydase.Server(service).run() - ``` + pydase.Server(service).run() +``` - ![Image Component](docs/images/Image_component.png) +![Image Component](docs/images/Image_component.png) -- `NumberSlider`: An interactive slider component to adjust numerical values, including floats and integers, on the frontend while synchronizing the data with the backend in real-time. +#### `NumberSlider` - ```python - import pydase - from pydase.components import NumberSlider + This component provides an interactive slider interface for adjusting numerical values on the frontend. It supports both floats and integers. The values adjusted on the frontend are synchronized with the backend in real-time, ensuring consistent data representation. + +The slider can be customized with initial values, minimum and maximum limits, and step sizes to fit various use cases. + +```python +import pydase +from pydase.components import NumberSlider - class MyService(pydase.DataService): - slider = NumberSlider(value=3.5, min=0, max=10, step_size=0.1) +class MyService(pydase.DataService): + slider = NumberSlider(value=3.5, min=0, max=10, step_size=0.1, type="float") - if __name__ == "__main__": - service = MyService() - pydase.Server(service).run() - ``` +if __name__ == "__main__": + service = MyService() + pydase.Server(service).run() +``` - ![Slider Component](docs/images/Slider_component.png) +![Slider Component](docs/images/Slider_component.png) + +#### `ColouredEnum` + +This component provides a way to visually represent different states or categories in a data service using colour-coded options. It behaves similarly to a standard `StrEnum`, but the values encode colours in a format understood by CSS. The colours can be defined using various methods like Hexadecimal, RGB, HSL, and more. + +If the property associated with the `ColouredEnum` has a setter function, the keys of the enum will be rendered as a dropdown menu, allowing users to interact and select different options. Without a setter function, the selected key will simply be displayed as a coloured box with text inside, serving as a visual indicator. + +```python +import pydase +import pydase.components as pyc + + +class MyStatus(pyc.ColouredEnum): + PENDING = "#FFA500" # Hexadecimal colour (Orange) + RUNNING = "#0000FF80" # Hexadecimal colour with transparency (Blue) + PAUSED = "rgb(169, 169, 169)" # RGB colour (Dark Gray) + RETRYING = "rgba(255, 255, 0, 0.3)" # RGB colour with transparency (Yellow) + COMPLETED = "hsl(120, 100%, 50%)" # HSL colour (Green) + FAILED = "hsla(0, 100%, 50%, 0.7)" # HSL colour with transparency (Red) + CANCELLED = "SlateGray" # Cross-browser colour name (Slate Gray) + + +class StatusTest(pydase.DataService): + _status = MyStatus.RUNNING + + @property + def status(self) -> MyStatus: + return self._status + + @status.setter + def status(self, value: MyStatus) -> None: + # do something ... + self._status = value + +# Modifying or accessing the status value: +my_service = StatusExample() +my_service.status = MyStatus.FAILED +``` + +![ColouredEnum Component](docs/images/ColouredEnum_component.png) #### Extending with New Components Users can also extend the library by creating custom components. This involves defining the behavior on the Python backend and the visual representation on the frontend. For those looking to introduce new components, the [guide on adding components](https://pydase.readthedocs.io/en/latest/dev-guide/Adding_Components/) provides detailed steps on achieving this. + diff --git a/docs/images/ColouredEnum_component.png b/docs/images/ColouredEnum_component.png new file mode 100644 index 0000000000000000000000000000000000000000..e63c0f1d024ab35d39c84336391ec24b2946736b GIT binary patch literal 18439 zcmdtKcT`i`_b-Z`gB}sYqk;kgq99F*(z_B7LYLlA>7gkQda;6n^d?;d0-=Q#dQlOn zp(gYY=_DbchDg5~@tphn<2&B{z5Cv{9b+X7sK;^ za$YMH)(#ieo2dh5tGvH_jyl^yJ(QtZ+;vPyv4^(NXdzPJ_bqyCjoUs8zFf*n{r<7% zmW_5?+^U5&b#-lDxT~^pWxs9*o!&;1rFPXO)RbrHTIN_WYdZB^YcXHZx5aO>Hzm~e zI-qUyQoXccw3#fDLvX(t(Q=ceM|rPI2heZx-`*&M={LtbGo5d2GZh>$DbhLks;g~n zx*&`B>w?L*3*2vYbwOwB4F1luR15Pv?VGe~q5^7wPp{g2Bdu3BS% z2>U$G<^@UGsjlmv*_K}oet=7$J>~Y|N2*-Su#7h6b8(b_-yc`6p0uIuvZDI>=qyei zG!3QV7<1`7>Q`aBO2Dw3dZM9vpXTuB?_*PbJh11?Q#Dgh8XDmnN3RpUc}m{E&eL9C zEtS)YbZ6-##MJeqHGy3$UMePDAa_?+J2x*HkcZu4FFV_7K2Bba*Hpn;y2dZgU8bSA zMgzWo&%k$l8SCS9X@27SnzQ#%0AKbiFJ;Fw?738{^~j2@$t{&uWNO!ygS=9 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 k3?TrHeEM`9gTdfig#Q@wT`azeIr0gxiuV0d