├── .gitignore ├── .pre-commit-config.yaml ├── LICENSE ├── MANIFEST.in ├── README.md ├── friends_graph.png ├── setup.py └── streamlit_react_flow ├── __init__.py └── frontend ├── .prettierrc ├── package-lock.json ├── package.json ├── public ├── bootstrap.min.css └── index.html ├── src ├── DragandDropComponent.tsx ├── ReactFlowComponent.tsx ├── index.tsx ├── react-app-env.d.ts └── sidebar.js └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | dask-worker-space/ 2 | 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | pip-wheel-metadata/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | *.py,cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | db.sqlite3-journal 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # IPython 83 | profile_default/ 84 | ipython_config.py 85 | 86 | # pyenv 87 | .python-version 88 | 89 | # pipenv 90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 93 | # install all needed dependencies. 94 | #Pipfile.lock 95 | 96 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 97 | __pypackages__/ 98 | 99 | # Celery stuff 100 | celerybeat-schedule 101 | celerybeat.pid 102 | 103 | # SageMath parsed files 104 | *.sage.py 105 | 106 | # Environments 107 | .env 108 | .venv 109 | env/ 110 | venv/ 111 | ENV/ 112 | env.bak/ 113 | venv.bak/ 114 | 115 | # Spyder project settings 116 | .spyderproject 117 | .spyproject 118 | 119 | # Rope project settings 120 | .ropeproject 121 | 122 | # PyCharm project settings 123 | .idea 124 | 125 | # mkdocs documentation 126 | /site 127 | 128 | # mypy 129 | .mypy_cache/ 130 | .dmypy.json 131 | dmypy.json 132 | 133 | # Pyre type checker 134 | .pyre/ 135 | .DS_Store 136 | junit/ 137 | node_modules/* 138 | **/node_modules 139 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/psf/black 3 | rev: 19.10b0 4 | hooks: 5 | - id: black 6 | language_version: python3 7 | - repo: https://github.com/pycqa/isort 8 | rev: 5.7.0 9 | hooks: 10 | - id: isort 11 | args: 12 | - "--profile" 13 | - "black" 14 | - repo: https://github.com/pre-commit/pre-commit-hooks 15 | rev: v3.2.0 16 | hooks: 17 | - id: trailing-whitespace 18 | - id: end-of-file-fixer 19 | - id: check-yaml 20 | exclude: ^continuous_integration/recipe/ 21 | - id: check-added-large-files 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018-2021 Streamlit Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include streamlit_react_flow/frontend/build * 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # streamlit-react-flow 2 | 3 | A streamlit wrapper for easy-to-use flow graph library https://reactflow.dev/ 4 | 5 | ### installation 6 | 7 | ``` 8 | pip install streamlit-react-flow 9 | ``` 10 | 11 | ### example 12 | 13 | ```python 14 | from streamlit_react_flow import react_flow 15 | import streamlit as st 16 | 17 | st.title("React-Flow Test") 18 | 19 | st.subheader("Friends Graph") 20 | 21 | elements = [ 22 | { "id": '1', "data": { "label": 'Guru' }, "type":"input","style": { "background": '#ffcc50', "width": 100 }, 23 | "position": { "x": 100, "y": 100 } }, 24 | { "id": '2', "data": { "label": 'Achyuth' },"position": { "x": 300, "y": 100 }}, 25 | { "id": 'e1-2', "source": '1', "target": '2', "animated": True }, 26 | ] 27 | 28 | elements.extend([{"id":i+3,"data":{"label":name },"type":"output","position": { "x": 170*i, "y": 300+i }} for i,name in enumerate(["Aravind","Manoj","Velmurugan","sridhar"])]) 29 | elements.extend([{"id":f"e{i}-{j}","source":i,"target":j} for i,j in [(1,3),(1,4),(1,5),(1,6)]]) 30 | flowStyles = { "height": 500,"width":1100 } 31 | 32 | # Create an instance of our component with a constant `name` arg, and 33 | # print its output value. 34 | react_flow("friends",elements=elements,flow_styles=flowStyles) 35 | 36 | ``` 37 | 38 | ### The above example produces following graph 39 | 40 |  41 | -------------------------------------------------------------------------------- /friends_graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajagurunath/streamlit-react-flow/314d6e42cc50aef74d4aa4be991cca31323c4f48/friends_graph.png -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | setuptools.setup( 4 | name="streamlit-react-flow", 5 | version="0.0.3", 6 | author="Gurunath", 7 | author_email="gurunathrajagopal@gmail.com", 8 | description="A Wrapper around React-flow component", 9 | long_description="A flow graph component using react-flow and streamlit", 10 | long_description_content_type="text/plain", 11 | url="", 12 | packages=setuptools.find_packages(), 13 | include_package_data=True, 14 | classifiers=[], 15 | python_requires=">=3.6", 16 | install_requires=[ 17 | # By definition, a Custom Component depends on Streamlit. 18 | # If your component has other Python dependencies, list 19 | # them here. 20 | "streamlit >= 0.63", 21 | ], 22 | ) 23 | -------------------------------------------------------------------------------- /streamlit_react_flow/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import streamlit.components.v1 as components 4 | 5 | # Create a _RELEASE constant. We'll set this to False while we're developing 6 | # the component, and True when we're ready to package and distribute it. 7 | # (This is, of course, optional - there are innumerable ways to manage your 8 | # release process.) 9 | _RELEASE = True 10 | 11 | # Declare a Streamlit component. `declare_component` returns a function 12 | # that is used to create instances of the component. We're naming this 13 | # function "_react_flow_func", with an underscore prefix, because we don't want 14 | # to expose it directly to users. Instead, we will create a custom wrapper 15 | # function, below, that will serve as our component's public API. 16 | 17 | # It's worth noting that this call to `declare_component` is the 18 | # *only thing* you need to do to create the binding between Streamlit and 19 | # your component frontend. Everything else we do in this file is simply a 20 | # best practice. 21 | 22 | if not _RELEASE: 23 | _react_flow_func = components.declare_component( 24 | # We give the component a simple, descriptive name ("react_flow" 25 | # does not fit this bill, so please choose something better for your 26 | # own component :) 27 | "react_flow", 28 | # Pass `url` here to tell Streamlit that the component will be served 29 | # by the local dev server that you run via `npm run start`. 30 | # (This is useful while your component is in development.) 31 | url="http://localhost:3001", 32 | ) 33 | else: 34 | # When we're distributing a production version of the component, we'll 35 | # replace the `url` param with `path`, and point it to to the component's 36 | # build directory: 37 | parent_dir = os.path.dirname(os.path.abspath(__file__)) 38 | build_dir = os.path.join(parent_dir, "frontend/build") 39 | _react_flow_func = components.declare_component("react_flow", path=build_dir) 40 | 41 | 42 | # Create a wrapper function for the component. This is an optional 43 | # best practice - we could simply expose the component function returned by 44 | # `declare_component` and call it done. The wrapper allows us to customize 45 | # our component's API: we can pre-process its input args, post-process its 46 | # output value, and add a docstring for users. 47 | def simple_react_flow(name, elements={}, flow_styles={}, with_controls=False, key=None): 48 | """Create a new instance of "React flow graph" using the elements dictionary. 49 | 50 | Parameters 51 | ---------- 52 | name: str 53 | The name of the thing we're saying hello to. The component will display 54 | the text "Hello, {name}!" 55 | elements: dict 56 | nodes and edges 57 | flow_styles: dict 58 | flow properties 59 | key: str or None 60 | An optional key that uniquely identifies this component. If this is 61 | None, and the component's arguments are changed, the component will 62 | be re-mounted in the Streamlit frontend and lose its current state. 63 | 64 | Returns 65 | ------- 66 | None 67 | """ 68 | # Call through to our private component function. Arguments we pass here 69 | # will be sent to the frontend, where they'll be available in an "args" 70 | # dictionary. 71 | # 72 | # "default" is a special argument that specifies the initial return 73 | # value of the component before the user has interacted with it. 74 | component_value = _react_flow_func( 75 | name=name, 76 | elements=elements, 77 | flowStyles=flow_styles, 78 | key=key, 79 | default=0, 80 | withControls=with_controls, 81 | component_name="SimpleReactFlow", 82 | ) 83 | 84 | # We could modify the value returned from the component if we wanted. 85 | # There's no need to do this in our simple example - but it's an option. 86 | return component_value 87 | 88 | 89 | def drag_and_drop_flow(name, elements={}, flow_styles={}, key=None): 90 | """Create a new instance of "Drag and drop component". 91 | 92 | Parameters 93 | ---------- 94 | name: str 95 | The name of the thing we're saying hello to. The component will display 96 | the text "Hello, {name}!" 97 | elements: dict 98 | nodes and edges 99 | flow_styles: dict 100 | flow properties 101 | key: str or None 102 | An optional key that uniquely identifies this component. If this is 103 | None, and the component's arguments are changed, the component will 104 | be re-mounted in the Streamlit frontend and lose its current state. 105 | 106 | Returns 107 | ------- 108 | comming soon 109 | 110 | """ 111 | # Call through to our private component function. Arguments we pass here 112 | # will be sent to the frontend, where they'll be available in an "args" 113 | # dictionary. 114 | # 115 | # "default" is a special argument that specifies the initial return 116 | # value of the component before the user has interacted with it. 117 | component_value = _react_flow_func( 118 | name=name, 119 | elements=elements, 120 | flowStyles=flow_styles, 121 | key=key, 122 | default=0, 123 | component_name="DragandDropComponent", 124 | ) 125 | 126 | # We could modify the value returned from the component if we wanted. 127 | # There's no need to do this in our simple example - but it's an option. 128 | return component_value 129 | 130 | 131 | # Add some test code to play with the component while it's in development. 132 | # During development, we can run this just as we would any other Streamlit 133 | # app: `$ streamlit run react_flow/__init__.py` 134 | if not _RELEASE: 135 | # testing 136 | import streamlit as st 137 | 138 | st.title("React-Flow Test") 139 | 140 | st.subheader("Friends Graph") 141 | 142 | elements = [ 143 | { 144 | "id": "1", 145 | "data": {"label": "Guru"}, 146 | "type": "input", 147 | "style": {"background": "#ffcc50", "width": 100}, 148 | "position": {"x": 100, "y": 100}, 149 | }, 150 | {"id": "2", "data": {"label": "Achyuth"}, "position": {"x": 300, "y": 100}}, 151 | {"id": "e1-2", "source": "1", "target": "2", "animated": True}, 152 | ] 153 | 154 | elements.extend( 155 | [ 156 | { 157 | "id": i + 3, 158 | "data": {"label": name}, 159 | "type": "output", 160 | "position": {"x": 170 * i, "y": 300 + i}, 161 | } 162 | for i, name in enumerate(["Aravind", "Manoj", "Velmurugan", "sridhar"]) 163 | ] 164 | ) 165 | elements.extend( 166 | [ 167 | {"id": f"e{i}-{j}", "source": i, "target": j} 168 | for i, j in [(1, 3), (1, 4), (1, 5), (1, 6)] 169 | ] 170 | ) 171 | flowStyles = {"height": 500, "width": 1100} 172 | 173 | # Create an instance of our component with a constant `name` arg, and 174 | # print its output value. 175 | simple_react_flow( 176 | "friends", elements=elements, flow_styles=flowStyles, with_controls=True 177 | ) 178 | 179 | st.subheader("Dask-sql Plan") 180 | 181 | plan = "LogicalProject(monthh=[CAST(Reinterpret(-(CAST($0):TIMESTAMP(0), CAST($1):TIMESTAMP(0)))):INTEGER], yearr=[CAST(/INT(Reinterpret(-(CAST($0):TIMESTAMP(0), CAST($1):TIMESTAMP(0))), 12)):INTEGER])\n LogicalTableScan(table=[[root, test]])\n" 182 | plan = plan.strip( 183 | "\n" 184 | ) # 'LogicalProject(ms=[*(CAST(/INT(Reinterpret(-(CAST($0):TIMESTAMP(0), CAST($1):TIMESTAMP(0))), 1000)):INTEGER, 1000000)], sec=[CAST(/INT(Reinterpret(-(CAST($0):TIMESTAMP(0), CAST($1):TIMESTAMP(0))), 1000)):INTEGER], minn=[CAST(/INT(Reinterpret(-(CAST($0):TIMESTAMP(0), CAST($1):TIMESTAMP(0))), 60000)):INTEGER], hr=[CAST(/INT(Reinterpret(-(CAST($0):TIMESTAMP(0), CAST($1):TIMESTAMP(0))), 3600000)):INTEGER], dayy=[CAST(/INT(Reinterpret(-(CAST($0):TIMESTAMP(0), CAST($1):TIMESTAMP(0))), 86400000)):INTEGER])\n LogicalTableScan(table=[[root, test]])' 185 | subplans = plan.split("\n")[::-1] 186 | plan_elements = [ 187 | { 188 | "id": f"{i}", 189 | "data": {"label": text}, 190 | "style": {"background": "#62c1f0", "width": 300,}, 191 | "position": {"x": 100, "y": 100 + i * 100}, 192 | } 193 | for i, text in enumerate(subplans) 194 | ] 195 | plan_edges = [ 196 | {"id": f"conn{i}_{1+1}", "source": i, "target": i + 1} 197 | for i in range(len(subplans) - 1) 198 | ] 199 | plan_elements.extend(plan_edges) 200 | simple_react_flow("dask-sql", elements=plan_elements, flow_styles=flowStyles) 201 | 202 | drag_and_drop_flow("dask-sql", elements=plan_elements, flow_styles=flowStyles) 203 | -------------------------------------------------------------------------------- /streamlit_react_flow/frontend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "endOfLine": "lf", 3 | "semi": false, 4 | "trailingComma": "es5" 5 | } 6 | -------------------------------------------------------------------------------- /streamlit_react_flow/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "streamlit_react_flow", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^4.2.4", 7 | "@testing-library/react": "^9.3.2", 8 | "@testing-library/user-event": "^7.1.2", 9 | "@types/jest": "^24.0.0", 10 | "@types/node": "^12.0.0", 11 | "@types/react": "^16.9.0", 12 | "@types/react-dom": "^16.9.0", 13 | "react": "^16.13.1", 14 | "react-dom": "^16.13.1", 15 | "react-flow-renderer": "^9.6.11", 16 | "react-scripts": "3.4.1", 17 | "streamlit-component-lib": "^1.3.0", 18 | "typescript": "^3.7.7" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts start", 22 | "build": "react-scripts build", 23 | "test": "react-scripts test", 24 | "eject": "react-scripts eject" 25 | }, 26 | "eslintConfig": { 27 | "extends": "react-app" 28 | }, 29 | "browserslist": { 30 | "production": [ 31 | ">0.2%", 32 | "not dead", 33 | "not op_mini all" 34 | ], 35 | "development": [ 36 | "last 1 chrome version", 37 | "last 1 firefox version", 38 | "last 1 safari version" 39 | ] 40 | }, 41 | "homepage": "." 42 | } 43 | -------------------------------------------------------------------------------- /streamlit_react_flow/frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |