├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.md ├── setup.py └── st_mapbox ├── __init__.py └── frontend ├── .prettierrc ├── package.json ├── public ├── bootstrap.min.css └── index.html ├── src ├── MapboxComponent.tsx ├── index.css ├── index.tsx └── react-app-env.d.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | st_mapbox/frontend/node_modules/ 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | pip-wheel-metadata/ 25 | share/python-wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | MANIFEST 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .nox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *.cover 51 | *.py,cover 52 | .hypothesis/ 53 | .pytest_cache/ 54 | 55 | # Translations 56 | *.mo 57 | *.pot 58 | 59 | # Django stuff: 60 | *.log 61 | local_settings.py 62 | db.sqlite3 63 | db.sqlite3-journal 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | .python-version 87 | 88 | # pipenv 89 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 90 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 91 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 92 | # install all needed dependencies. 93 | #Pipfile.lock 94 | 95 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 96 | __pypackages__/ 97 | 98 | # Celery stuff 99 | celerybeat-schedule 100 | celerybeat.pid 101 | 102 | # SageMath parsed files 103 | *.sage.py 104 | 105 | # Environments 106 | .env 107 | .venv 108 | env/ 109 | venv/ 110 | ENV/ 111 | env.bak/ 112 | venv.bak/ 113 | 114 | # Spyder project settings 115 | .spyderproject 116 | .spyproject 117 | 118 | # Rope project settings 119 | .ropeproject 120 | 121 | # mkdocs documentation 122 | /site 123 | 124 | # mypy 125 | .mypy_cache/ 126 | .dmypy.json 127 | dmypy.json 128 | 129 | # Pyre type checker 130 | .pyre/ 131 | st_mapbox/frontend/package-lock.json 132 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Qiusheng Wu 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 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include st_mapbox/frontend/build * 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # streamlit-mapbox 2 | 3 | [![image](https://img.shields.io/pypi/v/streamlit-mapbox.svg)](https://pypi.python.org/pypi/streamlit-mapbox) 4 | [![image](https://pepy.tech/badge/streamlit-mapbox)](https://pepy.tech/project/streamlit-mapbox) 5 | 6 | A Streamlit component for rendering Mapbox GL JS 7 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | with open("README.md") as readme_file: 4 | readme = readme_file.read() 5 | 6 | setuptools.setup( 7 | name="streamlit-mapbox", 8 | version="0.0.1", 9 | author="Qiusheng Wu", 10 | author_email="giswqs@gmail.com", 11 | description="Streamlit component for rendering Mapbox GL JS", 12 | long_description=readme, 13 | long_description_content_type="text/markdown", 14 | url="https://github.com/giswqs/streamlit-mapbox", 15 | packages=setuptools.find_packages(), 16 | include_package_data=True, 17 | classifiers=[ 18 | "Development Status :: 2 - Pre-Alpha", 19 | "Intended Audience :: Developers", 20 | "License :: OSI Approved :: MIT License", 21 | "Natural Language :: English", 22 | "Programming Language :: Python :: 3", 23 | "Programming Language :: Python :: 3.7", 24 | "Programming Language :: Python :: 3.8", 25 | "Programming Language :: Python :: 3.9", 26 | "Programming Language :: Python :: 3.10", 27 | ], 28 | python_requires=">=3.7", 29 | install_requires=[ 30 | "streamlit >= 0.63", 31 | ], 32 | ) 33 | -------------------------------------------------------------------------------- /st_mapbox/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import streamlit.components.v1 as components 3 | 4 | # Create a _RELEASE constant. We'll set this to False while we're developing 5 | # the component, and True when we're ready to package and distribute it. 6 | # (This is, of course, optional - there are innumerable ways to manage your 7 | # release process.) 8 | _RELEASE = False 9 | 10 | # Declare a Streamlit component. `declare_component` returns a function 11 | # that is used to create instances of the component. We're naming this 12 | # function "_component_func", with an underscore prefix, because we don't want 13 | # to expose it directly to users. Instead, we will create a custom wrapper 14 | # function, below, that will serve as our component's public API. 15 | 16 | # It's worth noting that this call to `declare_component` is the 17 | # *only thing* you need to do to create the binding between Streamlit and 18 | # your component frontend. Everything else we do in this file is simply a 19 | # best practice. 20 | 21 | if not _RELEASE: 22 | _component_func = components.declare_component( 23 | # We give the component a simple, descriptive name ("my_component" 24 | # does not fit this bill, so please choose something better for your 25 | # own component :) 26 | "my_component", 27 | # Pass `url` here to tell Streamlit that the component will be served 28 | # by the local dev server that you run via `npm run start`. 29 | # (This is useful while your component is in development.) 30 | url="http://localhost:3001", 31 | ) 32 | else: 33 | # When we're distributing a production version of the component, we'll 34 | # replace the `url` param with `path`, and point it to to the component's 35 | # build directory: 36 | parent_dir = os.path.dirname(os.path.abspath(__file__)) 37 | build_dir = os.path.join(parent_dir, "frontend/build") 38 | _component_func = components.declare_component( 39 | "my_component", 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 st_mapbox(name, key=None): 48 | """Create a new instance of "my_component". 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 | key: str or None 56 | An optional key that uniquely identifies this component. If this is 57 | None, and the component's arguments are changed, the component will 58 | be re-mounted in the Streamlit frontend and lose its current state. 59 | 60 | Returns 61 | ------- 62 | int 63 | The number of times the component's "Click Me" button has been clicked. 64 | (This is the value passed to `Streamlit.setComponentValue` on the 65 | frontend.) 66 | 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 = _component_func(name=name, key=key, default=0) 75 | 76 | # We could modify the value returned from the component if we wanted. 77 | # There's no need to do this in our simple example - but it's an option. 78 | return component_value 79 | 80 | 81 | # Add some test code to play with the component while it's in development. 82 | # During development, we can run this just as we would any other Streamlit 83 | # app: `$ streamlit run my_component/__init__.py` 84 | if not _RELEASE: 85 | import streamlit as st 86 | 87 | st.set_page_config(layout="wide") 88 | 89 | st.subheader("Streamlit Mapbox") 90 | st.write("Bidirectional communication between Streamlit and Mapbox GL JS") 91 | 92 | # Create an instance of our component with a constant `name` arg, and 93 | # print its output value. 94 | token = "your-access-token" 95 | location = st_mapbox(token) 96 | st.markdown("You've clicked: %s" % location) 97 | 98 | # st.markdown("---") 99 | # st.subheader("Component with variable args") 100 | 101 | # Create a second instance of our component whose `name` arg will vary 102 | # based on a text_input widget. 103 | # 104 | # We use the special "key" argument to assign a fixed identity to this 105 | # component instance. By default, when a component's arguments change, 106 | # it is considered a new instance and will be re-mounted on the frontend 107 | # and lose its current state. In this case, we want to vary the component's 108 | # "name" argument without having it get recreated. 109 | # name_input = st.text_input("Enter a name", value="Streamlit") 110 | # num_clicks = st_mapbox(name_input, key="foo") 111 | # st.markdown("You've clicked %s times!" % int(num_clicks)) 112 | -------------------------------------------------------------------------------- /st_mapbox/frontend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "endOfLine": "lf", 3 | "semi": false, 4 | "trailingComma": "es5" 5 | } 6 | -------------------------------------------------------------------------------- /st_mapbox/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "streamlit_mapbox", 3 | "version": "0.0.1", 4 | "private": true, 5 | "dependencies": { 6 | "@types/jest": "^24.0.0", 7 | "@types/node": "^12.0.0", 8 | "@types/react": "^16.9.0", 9 | "@types/react-dom": "^16.9.0", 10 | "@mapbox/mapbox-gl-draw": "1.1.2", 11 | "mapbox-gl": "1.1.1", 12 | "react": "^17.0.2", 13 | "react-dom": "^17.0.2", 14 | "react-scripts": "^4.0.3", 15 | "react-mapbox-gl": "4.5.1", 16 | "react-mapbox-gl-draw": "2.0.2", 17 | "streamlit-component-lib": "~1.3.0", 18 | "typescript": "~3.8.0" 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": [">0.2%", "not dead", "not op_mini all"], 31 | "development": [ 32 | "last 1 chrome version", 33 | "last 1 firefox version", 34 | "last 1 safari version" 35 | ] 36 | }, 37 | "homepage": "." 38 | } 39 | -------------------------------------------------------------------------------- /st_mapbox/frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Streamlit Mapbox 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /st_mapbox/frontend/src/MapboxComponent.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Streamlit, 3 | StreamlitComponentBase, 4 | withStreamlitConnection, 5 | } from "streamlit-component-lib" 6 | import React, { ReactNode } from "react" 7 | // import mapboxgl from '!mapbox-gl'; // eslint-disable-line import/no-webpack-loader-syntax 8 | import ReactMapboxGl from "react-mapbox-gl"; 9 | import DrawControl from "react-mapbox-gl-draw"; 10 | import "@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css"; 11 | 12 | 13 | interface State { 14 | numClicks: number 15 | isFocused: boolean 16 | } 17 | 18 | 19 | const Map = ReactMapboxGl({ 20 | accessToken: 21 | "your-access-token" 22 | }); 23 | 24 | 25 | /** 26 | * This is a React-based component template. The `render()` function is called 27 | * automatically when your component should be re-rendered. 28 | */ 29 | class MapboxComponent extends StreamlitComponentBase { 30 | onDrawCreate = ({ features }: any) => { 31 | Streamlit.setComponentValue(features); 32 | }; 33 | 34 | // onDrawCreate = ({ features }: any) => { 35 | // console.log(features); 36 | // }; 37 | 38 | onDrawUpdate = ({ features }: any) => { 39 | console.log({ features }); 40 | }; 41 | 42 | 43 | 44 | public state = { numClicks: 0, isFocused: false } 45 | 46 | public render = (): ReactNode => { 47 | // Arguments that are passed to the plugin in Python are accessible 48 | // via `this.props.args`. Here, we access the "name" arg. 49 | const name = this.props.args["name"] 50 | 51 | // Streamlit sends us a theme object via props that we can use to ensure 52 | // that our component has visuals that match the active theme in a 53 | // streamlit app. 54 | const { theme } = this.props 55 | const style: React.CSSProperties = {} 56 | 57 | // Maintain compatibility with older versions of Streamlit that don't send 58 | // a theme object. 59 | if (theme) { 60 | // Use the theme object to style our button border. Alternatively, the 61 | // theme style is defined in CSS vars. 62 | const borderStyling = `1px solid ${ 63 | this.state.isFocused ? theme.primaryColor : "gray" 64 | }` 65 | style.border = borderStyling 66 | style.outline = borderStyling 67 | } 68 | 69 | 70 | // Show a button and some text. 71 | // When the button is clicked, we'll increment our "numClicks" state 72 | // variable, and send its new value back to Streamlit, where it'll 73 | // be available to the Python program. 74 | return ( 75 |
76 | 83 | 84 | 85 |
86 | ); 87 | } 88 | 89 | // /** Click handler for our "Click Me!" button. */ 90 | // private onClicked = (): void => { 91 | // // Increment state.numClicks, and pass the new value back to 92 | // // Streamlit via `Streamlit.setComponentValue`. 93 | // this.setState( 94 | // (prevState) => ({ numClicks: prevState.numClicks + 1 }), 95 | // () => Streamlit.setComponentValue(this.state.numClicks) 96 | // ) 97 | // } 98 | 99 | // /** Focus handler for our "Click Me!" button. */ 100 | // private _onFocus = (): void => { 101 | // this.setState({ isFocused: true }) 102 | // } 103 | 104 | // /** Blur handler for our "Click Me!" button. */ 105 | // private _onBlur = (): void => { 106 | // this.setState({ isFocused: false }) 107 | // } 108 | } 109 | 110 | // "withStreamlitConnection" is a wrapper function. It bootstraps the 111 | // connection between your component and the Streamlit app, and handles 112 | // passing arguments from Python -> Component. 113 | // 114 | // You don't need to edit withStreamlitConnection (but you're welcome to!). 115 | export default withStreamlitConnection(MapboxComponent) 116 | -------------------------------------------------------------------------------- /st_mapbox/frontend/src/index.css: -------------------------------------------------------------------------------- 1 | .map-container { 2 | height: 400px; 3 | } 4 | 5 | .sidebar { 6 | background-color: rgba(35, 55, 75, 0.9); 7 | color: #fff; 8 | padding: 6px 12px; 9 | font-family: monospace; 10 | z-index: 1; 11 | position: absolute; 12 | top: 0; 13 | left: 0; 14 | margin: 12px; 15 | border-radius: 4px; 16 | } 17 | -------------------------------------------------------------------------------- /st_mapbox/frontend/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import ReactDOM from "react-dom" 3 | import 'mapbox-gl/dist/mapbox-gl.css'; 4 | import './index.css'; 5 | import MapboxComponent from "./MapboxComponent" 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById("root") 12 | ) 13 | -------------------------------------------------------------------------------- /st_mapbox/frontend/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /st_mapbox/frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "noEmit": true, 20 | "jsx": "react", 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | --------------------------------------------------------------------------------