├── .github ├── FUNDING.yml └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── LICENSE ├── README.md ├── example.PNG └── simple_data_flow.py /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: hoffstadt 2 | open_collective: DearPyGui 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | ** 3 | !.gitignore 4 | !example.PNG 5 | !LICENSE 6 | !README.md 7 | !simple_data_flow.py -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Jonathan Hoffstadt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SimpleDataFlow (not ready yet) 2 | A simple tool used to analyze data flow. 3 | 4 | | [Minimal Example](#minimal-example) - [Data Sets](#data-sets) - [Tools](#custom-tools) - [Modifiers](#custom-modifiers) - [Inspectors](#custom-inspectors) | 5 | |-| 6 | 7 | ![](https://github.com/hoffstadt/SimpleDataFlow/blob/main/example.PNG) 8 | 9 | ## Minimal Example 10 | Although not very useful, below is the minimal code required to start: 11 | ```python 12 | import simple_data_flow as sdf 13 | 14 | app = sdf.App() 15 | app.start() 16 | ``` 17 | 18 | ## Basics 19 | A _Simple Data Flow_ application consists of a few components, namely: 20 | 1. **Data Graph** - the center region that contains nodes 21 | 2. **Data sets** - data of any form that can be dropped onto the data graph 22 | 3. **Modifiers** - components that take in data and performs operation on it, outputting another data set. 23 | 4. **Inspectors** - components that take in data and takes measurements but does not affect the data set. 24 | 5. **Tools** - similar to **Inspectors** but these components do not have an output. 25 | 26 | ## Data Sets 27 | Data sets are the roots in node graph. The data itself can be any form. Below is a simple example of adding data sets: 28 | 29 | ```python 30 | import simple_data_flow as sdf 31 | 32 | app = sdf.App() 33 | 34 | app.add_data_set("New Data", [-5.0, -5.0, -3.0, -3.0, 0.0, 0.0, 3.0, 3.0, 5.0, 5.0]) 35 | 36 | app.start() 37 | ``` 38 | 39 | ## Custom Tools 40 | Creating a new tool requires the developer to create a new class derived from `Node`. It is the developers responsibility to create a factory static method and override the `execute` method. The constructor will add however many input attributes are required to perform its operation. Most tools will also need to add a [static attribute](#static-attributes). The typical steps involved within the `execute` method are as follows: 41 | 1. Retrieve data from input attributes. 42 | 2. Retrieve local data. 43 | 3. Perform operations. 44 | 4. Set some values in the static attributes. 45 | 5. Call `finish`. 46 | 47 | 48 | Below is a simple tool that displays the length of the incoming data: 49 | ```python 50 | import simple_data_flow as sdf 51 | from simple_data_flow import dpg 52 | 53 | 54 | class NewNode(sdf.Node): 55 | 56 | @staticmethod 57 | def factory(name, data): 58 | node = NewNode(name, data) 59 | return node 60 | 61 | def __init__(self, label: str, data): 62 | super().__init__(label, data) 63 | 64 | # add input attributes 65 | self.add_input_attribute(sdf.InputNodeAttribute("input")) 66 | 67 | self.custom_id = dpg.generate_uuid() 68 | 69 | def custom(self): 70 | 71 | dpg.add_input_int(label="Length", width=150, id=self.custom_id, readonly=True, step=0) 72 | 73 | def execute(self): 74 | 75 | # input data 76 | input_data_1 = self._input_attributes[0].get_data() 77 | 78 | # perform operations 79 | result = len(input_data_1) 80 | 81 | # set values 82 | dpg.set_value(self.custom_id, result) 83 | 84 | # call finish 85 | self.finish() 86 | 87 | 88 | app = sdf.App() 89 | 90 | app.add_tool("New Tool", NewNode.factory) 91 | 92 | app.start() 93 | 94 | ``` 95 | 96 | ## Custom Modifiers 97 | Creating a new modifier requires the developer to create a new class derived from `Node`. It is the developers responsibility to create a factory static method and override the `execute` method. The constructor will add however many input attributes are required to perform its operation. The constructor will also add the required number of output attributes. Most tools will also need to add a [static attribute](#static-attributes). The typical steps involved within the `execute` method are as follows: 98 | 1. Retrieve data from input attributes. 99 | 2. Retrieve local data. 100 | 3. Perform operations. 101 | 4. Call `execute` method of each output attribute, passing in output data. 102 | 5. Call `finish`. 103 | 104 | Below is a simple example of a modifier that subtracts some value from each value in the data set: 105 | ```python 106 | import simple_data_flow as sdf 107 | from simple_data_flow import dpg 108 | 109 | 110 | class NewNode(sdf.Node): 111 | 112 | @staticmethod 113 | def factory(name, data): 114 | node = NewNode(name, data) 115 | return node 116 | 117 | def __init__(self, label: str, data): 118 | super().__init__(label, data) 119 | 120 | # add input attributes 121 | self.add_input_attribute(sdf.InputNodeAttribute("input")) 122 | 123 | self.custom_id = dpg.generate_uuid() 124 | 125 | # add output attributes 126 | self.add_output_attribute(sdf.OutputNodeAttribute("output")) 127 | 128 | def custom(self): 129 | 130 | dpg.add_input_int(label="Subtract Value", width=150, id=self.custom_id, step=0) 131 | 132 | def execute(self): 133 | 134 | # input data 135 | input_data_1 = self._input_attributes[0].get_data() 136 | 137 | # static attribute value 138 | subtract_value = dpg.get_value(self.custom_id) 139 | 140 | # perform operations 141 | new_data = [] 142 | for value in input_data_1: 143 | new_data.append(value - subtract_value) 144 | 145 | # call execute on output attributes with data 146 | self._output_attributes[0].execute(new_data) 147 | 148 | # call finish 149 | self.finish() 150 | 151 | 152 | app = sdf.App() 153 | 154 | app.add_modifier("New Modifier", NewNode.factory) 155 | 156 | app.start() 157 | 158 | ``` 159 | 160 | ## Custom Inspectors 161 | Creating a new inspector requires the developer to create a new class derived from `Node`. It is the developers responsibility to create a factory static method and override the `execute` method. The constructor will add however many input attributes are required to perform its operation. The constructor will also add the required number of output attributes. Most tools will also need to add a [static attribute](#static-attributes). The typical steps involved within the `execute` method are as follows: 162 | 1. Retrieve data from input attributes. 163 | 2. Retrieve local data. 164 | 3. Perform operations. 165 | 4. Call `execute` method of each output attribute, passing in output data. 166 | 5. Call `finish`. 167 | 168 | Below is a simple example of an inspector that finds the maximum value of a data set: 169 | ```python 170 | import simple_data_flow as sdf 171 | from simple_data_flow import dpg 172 | 173 | class NewNode(sdf.Node): 174 | 175 | @staticmethod 176 | def factory(name, data): 177 | node = NewNode(name, data) 178 | return node 179 | 180 | def __init__(self, label: str, data): 181 | super().__init__(label, data) 182 | 183 | # add input attributes 184 | self.add_input_attribute(sdf.InputNodeAttribute("input")) 185 | 186 | # add output attributes 187 | self.add_output_attribute(sdf.OutputNodeAttribute("output")) 188 | 189 | def execute(self): 190 | 191 | # input data 192 | input_data_1 = self._input_attributes[0].get_data() 193 | 194 | # perform operations 195 | max_value = input_data_1[0] 196 | for value in input_data_1: 197 | if value > max_value: 198 | max_value = value 199 | 200 | # call execute on output attributes with data 201 | self._output_attributes[0].execute(max_value) 202 | 203 | # call finish 204 | self.finish() 205 | 206 | 207 | app = sdf.App() 208 | 209 | app.add_inspector("New Inspector", NewNode.factory) 210 | 211 | app.start() 212 | 213 | ``` 214 | -------------------------------------------------------------------------------- /example.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hoffstadt/SimpleDataFlow/baf8f54a82db863b3613bf30b8e4df547bed0ce1/example.PNG -------------------------------------------------------------------------------- /simple_data_flow.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | 3 | ######################################################################################################################## 4 | # Simple Data Flow 5 | ######################################################################################################################## 6 | # Table of Contents 7 | # 8 | # 0 Themes 9 | # 1 Node DPG Wrappings 10 | # 2 Drag & Drop 11 | # 3 Inspectors 12 | # 4 Modifiers 13 | # 5 Tools 14 | # 6 Application 15 | # 16 | # How do I use this tool? (check the bottom of this file) 17 | # 0. Create an App Instance. (app = App()) 18 | # 1. Add data sets. (add_data_set method) 19 | # 2. Add modifiers. (add_modifier method) 20 | # 3. Add inspectors. (add_inspector method) 21 | # 4. Add tools. (add_tool method) 22 | # 5. Start App. (start method) 23 | # 24 | # How do I create a Modifier/Inspector/Tool? (check the MinMaxNode below) 25 | # 0. Create a class derived from "Node". (Call it "NewNode" for this example) 26 | # 1. In the constructor, add attributes. 27 | # 2. Override the "execute" method that does the following: 28 | # a. First argument is "data". 29 | # b. Retrieve input attribute data. 30 | # c. Perform your operations. 31 | # d. Call "execute" methods of your output attributes (modifiers). 32 | # e. Call "finish". 33 | # 3. Create a static method called "factory" that creates a returns a "NewNode". 34 | # 4. Call either add_tool, add_modifier, or add_inspector method of app like so: 35 | # a. "app.add_modifier("NewNode", NewNode.factory, None)" 36 | # b. The 3rd argument of DragSource can be any data and will be passed into the factory's second argument 37 | ######################################################################################################################## 38 | 39 | ######################################################################################################################## 40 | # Setup 41 | ######################################################################################################################## 42 | dpg.create_context() 43 | dpg.create_viewport() 44 | dpg.setup_dearpygui() 45 | 46 | ######################################################################################################################## 47 | # Themes 48 | ######################################################################################################################## 49 | 50 | with dpg.theme() as _source_theme: 51 | with dpg.theme_component(dpg.mvButton): 52 | dpg.add_theme_color(dpg.mvThemeCol_Button, [25, 119, 0]) 53 | dpg.add_theme_color(dpg.mvThemeCol_ButtonHovered, [25, 255, 0]) 54 | dpg.add_theme_color(dpg.mvThemeCol_ButtonActive, [25, 119, 0]) 55 | 56 | with dpg.theme() as _completion_theme: 57 | with dpg.theme_component(dpg.mvAll): 58 | dpg.add_theme_color(dpg.mvNodeCol_TitleBar, [37, 28, 138], category=dpg.mvThemeCat_Nodes) 59 | dpg.add_theme_color(dpg.mvNodeCol_TitleBarHovered, [37, 28, 138], category=dpg.mvThemeCat_Nodes) 60 | dpg.add_theme_color(dpg.mvNodeCol_TitleBarSelected, [37, 28, 138], category=dpg.mvThemeCat_Nodes) 61 | 62 | 63 | ######################################################################################################################## 64 | # Node DPG Wrappings 65 | ######################################################################################################################## 66 | class OutputNodeAttribute: 67 | 68 | def __init__(self, label: str = "output"): 69 | 70 | self._label = label 71 | self.uuid = dpg.generate_uuid() 72 | self._children = [] # output attributes 73 | self._data = None 74 | 75 | def add_child(self, parent, child): 76 | 77 | dpg.add_node_link(self.uuid, child.uuid, parent=parent) 78 | child.set_parent(self) 79 | self._children.append(child) 80 | 81 | def execute(self, data): 82 | self._data = data 83 | for child in self._children: 84 | child._data = self._data 85 | 86 | def submit(self, parent): 87 | 88 | with dpg.node_attribute(parent=parent, attribute_type=dpg.mvNode_Attr_Output, 89 | user_data=self, id=self.uuid): 90 | dpg.add_text(self._label) 91 | 92 | 93 | class InputNodeAttribute: 94 | 95 | def __init__(self, label: str = "input"): 96 | 97 | self._label = label 98 | self.uuid = dpg.generate_uuid() 99 | self._parent = None # input attribute 100 | self._data = None 101 | 102 | def get_data(self): 103 | return self._data 104 | 105 | def set_parent(self, parent: OutputNodeAttribute): 106 | self._parent = parent 107 | 108 | def submit(self, parent): 109 | 110 | with dpg.node_attribute(parent=parent, user_data=self, id=self.uuid): 111 | dpg.add_text(self._label) 112 | 113 | 114 | class Node: 115 | 116 | def __init__(self, label: str, data): 117 | 118 | self.label = label 119 | self.uuid = dpg.generate_uuid() 120 | self.static_uuid = dpg.generate_uuid() 121 | self._input_attributes = [] 122 | self._output_attributes = [] 123 | self._data = data 124 | 125 | def finish(self): 126 | dpg.bind_item_theme(self.uuid, _completion_theme) 127 | 128 | def add_input_attribute(self, attribute: InputNodeAttribute): 129 | self._input_attributes.append(attribute) 130 | 131 | def add_output_attribute(self, attribute: OutputNodeAttribute): 132 | self._output_attributes.append(attribute) 133 | 134 | def execute(self): 135 | 136 | for attribute in self._output_attributes: 137 | attribute.execute(self._data) 138 | 139 | self.finish() 140 | 141 | def custom(self): 142 | pass 143 | 144 | def submit(self, parent): 145 | 146 | with dpg.node(parent=parent, label=self.label, tag=self.uuid): 147 | 148 | with dpg.node_attribute(attribute_type=dpg.mvNode_Attr_Static): 149 | dpg.add_button(label="Execute", user_data=self, callback=lambda s, a, u: u.execute()) 150 | 151 | for attribute in self._input_attributes: 152 | attribute.submit(self.uuid) 153 | 154 | with dpg.node_attribute(parent=self.uuid, attribute_type=dpg.mvNode_Attr_Static, 155 | user_data=self, tag=self.static_uuid): 156 | self.custom() 157 | 158 | for attribute in self._output_attributes: 159 | attribute.submit(self.uuid) 160 | 161 | 162 | class NodeEditor: 163 | 164 | @staticmethod 165 | def _link_callback(sender, app_data, user_data): 166 | output_attr_uuid, input_attr_uuid = app_data 167 | 168 | input_attr = dpg.get_item_user_data(input_attr_uuid) 169 | output_attr = dpg.get_item_user_data(output_attr_uuid) 170 | 171 | output_attr.add_child(sender, input_attr) 172 | 173 | def __init__(self): 174 | 175 | self._nodes = [] 176 | self.uuid = dpg.generate_uuid() 177 | 178 | def add_node(self, node: Node): 179 | self._nodes.append(node) 180 | 181 | def on_drop(self, sender, app_data, user_data): 182 | source, generator, data = app_data 183 | node = generator(source.label, data) 184 | node.submit(self.uuid) 185 | self.add_node(node) 186 | 187 | def submit(self, parent): 188 | 189 | with dpg.child_window(width=-160, parent=parent, user_data=self, drop_callback=lambda s, a, u: dpg.get_item_user_data(s).on_drop(s, a, u)): 190 | with dpg.node_editor(tag=self.uuid, callback=NodeEditor._link_callback, width=-1, height=-1): 191 | for node in self._nodes: 192 | node.submit(self.uuid) 193 | 194 | 195 | ######################################################################################################################## 196 | # Drag & Drop 197 | ######################################################################################################################## 198 | class DragSource: 199 | 200 | def __init__(self, label: str, node_generator, data): 201 | 202 | self.label = label 203 | self._generator = node_generator 204 | self._data = data 205 | 206 | def submit(self, parent): 207 | 208 | dpg.add_button(label=self.label, parent=parent, width=-1) 209 | dpg.bind_item_theme(dpg.last_item(), _source_theme) 210 | 211 | with dpg.drag_payload(parent=dpg.last_item(), drag_data=(self, self._generator, self._data)): 212 | dpg.add_text(f"Name: {self.label}") 213 | 214 | 215 | class DragSourceContainer: 216 | 217 | def __init__(self, label: str, width: int = 150, height: int = -1): 218 | 219 | self._label = label 220 | self._width = width 221 | self._height = height 222 | self._uuid = dpg.generate_uuid() 223 | self._children = [] # drag sources 224 | 225 | def add_drag_source(self, source: DragSource): 226 | 227 | self._children.append(source) 228 | 229 | def submit(self, parent): 230 | 231 | with dpg.child_window(parent=parent, width=self._width, height=self._height, tag=self._uuid, menubar=True) as child_parent: 232 | with dpg.menu_bar(): 233 | dpg.add_menu(label=self._label) 234 | 235 | for child in self._children: 236 | child.submit(child_parent) 237 | 238 | 239 | ######################################################################################################################## 240 | # Inspectors 241 | ######################################################################################################################## 242 | class MaxMinNode(Node): 243 | 244 | @staticmethod 245 | def factory(name, data): 246 | node = MaxMinNode(name, data) 247 | return node 248 | 249 | def __init__(self, label: str, data): 250 | super().__init__(label, data) 251 | 252 | self.add_input_attribute(InputNodeAttribute("values")) 253 | self.add_output_attribute(OutputNodeAttribute("min")) 254 | self.add_output_attribute(OutputNodeAttribute("max")) 255 | 256 | self.min_id = dpg.generate_uuid() 257 | self.max_id = dpg.generate_uuid() 258 | 259 | def custom(self): 260 | 261 | with dpg.group(width=150): 262 | dpg.add_text("Not Calculated", label="Min", show_label=True, tag=self.min_id) 263 | dpg.add_text("Not Calculated", label="Max", show_label=True, tag=self.max_id) 264 | 265 | def execute(self): 266 | 267 | # get input attribute data 268 | values = self._input_attributes[0].get_data() 269 | 270 | # perform actual operations 271 | min_value = values[0] 272 | max_value = values[0] 273 | 274 | for i in range(0, len(values)): 275 | 276 | if values[i] > max_value: 277 | max_value = values[i] 278 | 279 | if values[i] < min_value: 280 | min_value = values[i] 281 | 282 | dpg.set_value(self.min_id, str(min_value)) 283 | dpg.set_value(self.max_id, str(max_value)) 284 | 285 | # execute output attributes 286 | self._output_attributes[0].execute(min_value) 287 | self._output_attributes[1].execute(max_value) 288 | 289 | self.finish() 290 | 291 | 292 | ######################################################################################################################## 293 | # Modifiers 294 | ######################################################################################################################## 295 | class DataShifterNode(Node): 296 | 297 | @staticmethod 298 | def factory(name, data): 299 | node = DataShifterNode(name, data) 300 | return node 301 | 302 | def __init__(self, label: str, data): 303 | super().__init__(label, data) 304 | 305 | self.add_input_attribute(InputNodeAttribute("x")) 306 | self.add_input_attribute(InputNodeAttribute("y")) 307 | self.add_output_attribute(OutputNodeAttribute("x mod")) 308 | self.add_output_attribute(OutputNodeAttribute("y mod")) 309 | 310 | self.x_shift = dpg.generate_uuid() 311 | self.y_shift = dpg.generate_uuid() 312 | 313 | def custom(self): 314 | 315 | dpg.add_input_float(label="x", tag=self.x_shift, step=0, width=150) 316 | dpg.add_input_float(label="y", tag=self.y_shift, step=0, width=150) 317 | 318 | def execute(self): 319 | 320 | # get values from static attributes 321 | x_shift = dpg.get_value(self.x_shift) 322 | y_shift = dpg.get_value(self.y_shift) 323 | 324 | # get input attribute data 325 | x_orig_data = self._input_attributes[0].get_data() 326 | y_orig_data = self._input_attributes[1].get_data() 327 | 328 | # perform actual operations 329 | x_data = [] 330 | for i in range(0, len(x_orig_data)): 331 | x_data.append(x_orig_data[i] + x_shift) 332 | 333 | y_data = [] 334 | for i in range(0, len(y_orig_data)): 335 | y_data.append(y_orig_data[i] + y_shift) 336 | 337 | # execute output attributes 338 | self._output_attributes[0].execute(x_data) 339 | self._output_attributes[1].execute(y_data) 340 | 341 | self.finish() 342 | 343 | 344 | ######################################################################################################################## 345 | # Tools 346 | ######################################################################################################################## 347 | class ViewNode_1D(Node): 348 | 349 | @staticmethod 350 | def factory(name, data): 351 | node = ViewNode_1D(name, data) 352 | return node 353 | 354 | def __init__(self, label: str, data): 355 | super().__init__(label, data) 356 | 357 | self.add_input_attribute(InputNodeAttribute()) 358 | self.simple_plot = dpg.generate_uuid() 359 | 360 | def custom(self): 361 | 362 | dpg.add_simple_plot(label="Data View", width=200, height=80, tag=self.simple_plot) 363 | 364 | def execute(self): 365 | 366 | plot_id = self._static_attributes[0].simple_plot 367 | dpg.set_value(plot_id, self._input_attributes[0].get_data()) 368 | self.finish() 369 | 370 | 371 | class ViewNode_2D(Node): 372 | 373 | @staticmethod 374 | def factory(name, data): 375 | node = ViewNode_2D(name, data) 376 | return node 377 | 378 | def __init__(self, label: str, data): 379 | super().__init__(label, data) 380 | 381 | self.add_input_attribute(InputNodeAttribute("x")) 382 | self.add_input_attribute(InputNodeAttribute("y")) 383 | 384 | self.x_axis = dpg.generate_uuid() 385 | self.y_axis = dpg.generate_uuid() 386 | 387 | def custom(self): 388 | 389 | with dpg.plot(height=400, width=400, no_title=True): 390 | dpg.add_plot_axis(dpg.mvXAxis, label="", tag=self.x_axis) 391 | dpg.add_plot_axis(dpg.mvYAxis, label="", tag=self.y_axis) 392 | 393 | def execute(self): 394 | 395 | x_axis_id = self.x_axis 396 | y_axis_id = self.y_axis 397 | 398 | x_orig_data = self._input_attributes[0].get_data() 399 | y_orig_data = self._input_attributes[1].get_data() 400 | 401 | dpg.add_line_series(x_orig_data, y_orig_data, parent=y_axis_id) 402 | dpg.fit_axis_data(x_axis_id) 403 | dpg.fit_axis_data(y_axis_id) 404 | 405 | self.finish() 406 | 407 | 408 | class CheckerNode(Node): 409 | 410 | @staticmethod 411 | def factory(name, data): 412 | node = CheckerNode(name, data) 413 | return node 414 | 415 | def __init__(self, label: str, data): 416 | super().__init__(label, data) 417 | 418 | self.add_input_attribute(InputNodeAttribute("Value")) 419 | 420 | self.expected_id = dpg.generate_uuid() 421 | self.min_id = dpg.generate_uuid() 422 | self.max_id = dpg.generate_uuid() 423 | self.status_id = dpg.generate_uuid() 424 | 425 | with dpg.theme() as self.success: 426 | dpg.add_theme_color(dpg.mvThemeCol_CheckMark, [0, 255, 0], category=dpg.mvThemeCat_Core, tag=self.success) 427 | 428 | with dpg.theme() as self.fail: 429 | dpg.add_theme_color(dpg.mvThemeCol_CheckMark, [255, 0, 0], category=dpg.mvThemeCat_Core, tag=self.fail) 430 | 431 | with dpg.theme() as self.neutral: 432 | dpg.add_theme_color(dpg.mvThemeCol_CheckMark, [255, 255, 0], category=dpg.mvThemeCat_Core, tag=self.neutral) 433 | 434 | def custom(self): 435 | 436 | with dpg.group(width=150): 437 | dpg.add_input_float(label="Expected Value", step=0.0, tag=self.expected_id, default_value=10) 438 | dpg.add_input_float(label="Max Tolerance", step=0.0, tag=self.max_id, default_value=.05) 439 | dpg.add_input_float(label="Min Tolerance", step=0.0, tag=self.min_id, default_value=.05) 440 | dpg.add_radio_button(items=["Status"], tag=self.status_id) 441 | dpg.set_item_theme(self.status_id, self.neutral) 442 | 443 | def execute(self): 444 | 445 | # get input attribute data 446 | value = round(self._input_attributes[0].get_data(), 5) 447 | 448 | min_value = dpg.get_value(self.min_id) 449 | max_value = dpg.get_value(self.max_id) 450 | expect_value = dpg.get_value(self.expected_id) 451 | 452 | if round(expect_value - min_value,5) <= value <= round(expect_value + max_value, 5): 453 | dpg.set_item_theme(self._static_attributes[0].status_id, self.success) 454 | else: 455 | dpg.set_item_theme(self._static_attributes[0].status_id, self.fail) 456 | 457 | self.finish() 458 | 459 | 460 | class ValueNode(Node): 461 | 462 | @staticmethod 463 | def factory(name, data): 464 | node = ValueNode(name, data) 465 | return node 466 | 467 | def __init__(self, label: str, data): 468 | super().__init__(label, data) 469 | 470 | self.add_output_attribute(OutputNodeAttribute("Value")) 471 | 472 | self.value = dpg.generate_uuid() 473 | 474 | def custom(self): 475 | 476 | with dpg.group(width=150): 477 | dpg.add_input_float(label="Input Value", step=0, tag=self.value, default_value=10) 478 | 479 | def execute(self): 480 | 481 | # get input attribute data 482 | value = dpg.get_value(self.value) 483 | self._output_attributes[0].execute(value) 484 | 485 | self.finish() 486 | 487 | 488 | ######################################################################################################################## 489 | # Application 490 | ######################################################################################################################## 491 | class App: 492 | 493 | @staticmethod 494 | def data_node_factory(name, data): 495 | node = Node(name, data) 496 | node.add_output_attribute(OutputNodeAttribute("data")) 497 | return node 498 | 499 | def __init__(self): 500 | 501 | self.data_set_container = DragSourceContainer("Data Sets", 150, -500) 502 | self.tool_container = DragSourceContainer("Tools", 150, -1) 503 | self.inspector_container = DragSourceContainer("Inspectors", 150, -500) 504 | self.modifier_container = DragSourceContainer("Modifiers", 150, -1) 505 | self.plugin_menu_id = dpg.generate_uuid() 506 | self.left_panel = dpg.generate_uuid() 507 | self.right_panel = dpg.generate_uuid() 508 | 509 | self.plugins = [] 510 | 511 | self.add_data_set("Test Data", [-5.0, -5.0, -3.0, -3.0, 0.0, 0.0, 3.0, 3.0, 5.0, 5.0]) 512 | self.add_tool("1D Data View", ViewNode_1D.factory) 513 | self.add_tool("2D Data View", ViewNode_2D.factory) 514 | self.add_tool("Checker Tool", CheckerNode.factory) 515 | self.add_tool("Value Tool", ValueNode.factory) 516 | self.add_inspector("MinMax", MaxMinNode.factory) 517 | self.add_modifier("Data Shifter", DataShifterNode.factory) 518 | 519 | def update(self): 520 | 521 | with dpg.mutex(): 522 | dpg.delete_item(self.left_panel, children_only=True) 523 | self.data_set_container.submit(self.left_panel) 524 | self.modifier_container.submit(self.left_panel) 525 | 526 | dpg.delete_item(self.right_panel, children_only=True) 527 | self.inspector_container.submit(self.right_panel) 528 | self.tool_container.submit(self.right_panel) 529 | 530 | def add_data_set(self, label, data): 531 | self.data_set_container.add_drag_source(DragSource(label, App.data_node_factory, data)) 532 | 533 | def add_tool(self, label, factory, data=None): 534 | self.tool_container.add_drag_source(DragSource(label, factory, data)) 535 | 536 | def add_inspector(self, label, factory, data=None): 537 | self.inspector_container.add_drag_source(DragSource(label, factory, data)) 538 | 539 | def add_modifier(self, label, factory, data=None): 540 | self.modifier_container.add_drag_source(DragSource(label, factory, data)) 541 | 542 | def add_plugin(self, name, callback): 543 | self.plugins.append((name, callback)) 544 | 545 | def start(self): 546 | 547 | #dpg.setup_registries() 548 | dpg.set_viewport_title("Simple Data Flow") 549 | dpg.show_viewport() 550 | node_editor = NodeEditor() 551 | 552 | with dpg.window() as main_window: 553 | 554 | with dpg.menu_bar(): 555 | 556 | with dpg.menu(label="Operations"): 557 | 558 | dpg.add_menu_item(label="Reset", callback=lambda: dpg.delete_item(node_editor.uuid, children_only=True)) 559 | 560 | with dpg.menu(label="Plugins"): 561 | for plugin in self.plugins: 562 | dpg.add_menu_item(label=plugin[0], callback=plugin[1]) 563 | 564 | with dpg.group(horizontal=True) as group: 565 | 566 | # left panel 567 | with dpg.group(id=self.left_panel): 568 | self.data_set_container.submit(self.left_panel) 569 | self.modifier_container.submit(self.left_panel) 570 | 571 | # center panel 572 | node_editor.submit(group) 573 | 574 | # right panel 575 | with dpg.group(id=self.right_panel): 576 | self.inspector_container.submit(self.right_panel) 577 | self.tool_container.submit(self.right_panel) 578 | 579 | dpg.set_primary_window(main_window, True) 580 | dpg.start_dearpygui() 581 | 582 | 583 | if __name__ == "__main__": 584 | 585 | app = App() 586 | app.start() 587 | --------------------------------------------------------------------------------