├── .flake8 ├── .github └── workflows │ └── cartoframes-ci.yml ├── .gitignore ├── .hound.yml ├── .jshintignore ├── .jshintrc ├── .pre-commit-config.yaml ├── .pylintrc ├── .readthedocs.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── CONTRIBUTORS ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── SECURITY.md ├── binder └── requirements.txt ├── cartoframes ├── __init__.py ├── _version.py ├── analysis │ ├── __init__.py │ └── grid.py ├── assets │ ├── init.js.j2 │ ├── init_layout.js.j2 │ ├── src │ │ ├── bundle.js │ │ ├── constants.js │ │ ├── errors │ │ │ ├── display.js │ │ │ └── parse.js │ │ ├── index.js │ │ ├── layers.js │ │ ├── legends.js │ │ ├── map.js │ │ ├── map │ │ │ ├── SourceFactory.js │ │ │ ├── interactivity.js │ │ │ └── popups.js │ │ ├── utils.js │ │ └── widgets.js │ ├── style │ │ ├── common.html.j2 │ │ ├── error.html.j2 │ │ ├── layout.html.j2 │ │ ├── map.html.j2 │ │ ├── popup.html.j2 │ │ ├── themes │ │ │ ├── dark.html.j2 │ │ │ └── light.html.j2 │ │ └── widgets.html.j2 │ └── templates │ │ ├── error │ │ └── basic.html.j2 │ │ ├── iframe.html.j2 │ │ └── viz │ │ ├── basic.html.j2 │ │ ├── footer.html.j2 │ │ ├── layout.html.j2 │ │ ├── legends.html.j2 │ │ ├── legends_layout.html.j2 │ │ ├── main.html.j2 │ │ ├── main_layout.html.j2 │ │ ├── widgets.html.j2 │ │ └── widgets │ │ ├── animation.html.j2 │ │ ├── basic.html.j2 │ │ ├── category.html.j2 │ │ ├── formula.html.j2 │ │ ├── histogram.html.j2 │ │ └── time-series.html.j2 ├── auth │ ├── __init__.py │ ├── credentials.py │ └── defaults.py ├── data │ ├── __init__.py │ ├── clients │ │ ├── __init__.py │ │ ├── auth_api_client.py │ │ └── sql_client.py │ ├── observatory │ │ ├── __init__.py │ │ ├── catalog │ │ │ ├── __init__.py │ │ │ ├── catalog.py │ │ │ ├── category.py │ │ │ ├── country.py │ │ │ ├── dataset.py │ │ │ ├── entity.py │ │ │ ├── geography.py │ │ │ ├── provider.py │ │ │ ├── repository │ │ │ │ ├── __init__.py │ │ │ │ ├── category_repo.py │ │ │ │ ├── constants.py │ │ │ │ ├── country_repo.py │ │ │ │ ├── dataset_repo.py │ │ │ │ ├── entity_repo.py │ │ │ │ ├── geography_repo.py │ │ │ │ ├── provider_repo.py │ │ │ │ ├── repo_client.py │ │ │ │ ├── variable_group_repo.py │ │ │ │ └── variable_repo.py │ │ │ ├── subscription_info.py │ │ │ ├── subscriptions.py │ │ │ ├── summary.py │ │ │ ├── utils.py │ │ │ ├── variable.py │ │ │ └── variable_group.py │ │ └── enrichment │ │ │ ├── __init__.py │ │ │ ├── enrichment.py │ │ │ └── enrichment_service.py │ └── services │ │ ├── __init__.py │ │ ├── geocoding.py │ │ ├── isolines.py │ │ ├── service.py │ │ └── utils │ │ ├── __init__.py │ │ ├── geocoding_constants.py │ │ ├── geocoding_utils.py │ │ └── table_geocoding_lock.py ├── exceptions.py ├── io │ ├── __init__.py │ ├── carto.py │ ├── dataset_info.py │ └── managers │ │ ├── __init__.py │ │ ├── context_manager.py │ │ └── source_manager.py ├── utils │ ├── __init__.py │ ├── columns.py │ ├── geom_utils.py │ ├── logger.py │ ├── metrics.py │ └── utils.py └── viz │ ├── __init__.py │ ├── basemaps.py │ ├── constants.py │ ├── defaults.py │ ├── html │ ├── __init__.py │ ├── html_layout.py │ ├── html_map.py │ └── utils.py │ ├── kuviz.py │ ├── layer.py │ ├── layout.py │ ├── legend.py │ ├── legend_list.py │ ├── legends │ ├── __init__.py │ ├── basic_legend.py │ ├── color_bins_legend.py │ ├── color_category_legend.py │ ├── color_continuous_legend.py │ ├── default_legend.py │ ├── size_bins_legend.py │ ├── size_category_legend.py │ └── size_continuous_legend.py │ ├── map.py │ ├── palettes.py │ ├── popup.py │ ├── popup_list.py │ ├── popups │ ├── __init__.py │ ├── default_popup_element.py │ └── popup_element.py │ ├── source.py │ ├── style.py │ ├── styles │ ├── __init__.py │ ├── animation_style.py │ ├── basic_style.py │ ├── cluster_size_style.py │ ├── color_bins_style.py │ ├── color_category_style.py │ ├── color_continuous_style.py │ ├── isolines_style.py │ ├── size_bins_style.py │ ├── size_category_style.py │ ├── size_continuous_style.py │ └── utils.py │ ├── themes.py │ ├── widget.py │ ├── widget_list.py │ └── widgets │ ├── __init__.py │ ├── animation_widget.py │ ├── basic_widget.py │ ├── category_widget.py │ ├── default_widget.py │ ├── formula_widget.py │ ├── histogram_widget.py │ └── time_series_widget.py ├── docs ├── Makefile ├── RELEASING.md ├── build.sh ├── cartoframes.rst ├── conf.py ├── developers │ ├── dev_guide.rst │ └── migrations │ │ ├── 1.0.0.md │ │ └── rc1.md ├── examples │ ├── README.md │ ├── advanced_use_cases │ │ ├── building_a_dashboard.ipynb │ │ ├── combining_two_datasets.ipynb │ │ ├── revenue_prediction.ipynb │ │ ├── territory_management_1layer.ipynb │ │ └── territory_management_2layers.ipynb │ ├── data_management │ │ ├── change_carto_table_privacy.ipynb │ │ ├── read_carto_query.ipynb │ │ ├── read_carto_table.ipynb │ │ ├── read_csv.ipynb │ │ ├── read_geojson.ipynb │ │ ├── read_json.ipynb │ │ ├── read_shapefile.ipynb │ │ └── upload_to_carto.ipynb │ ├── data_observatory │ │ ├── do_access_premium_data.ipynb │ │ ├── do_access_public_data.ipynb │ │ ├── do_data_discovery.ipynb │ │ ├── do_data_enrichment.ipynb │ │ ├── do_dataset_notebook_template.ipynb │ │ ├── do_geography_notebook_template.ipynb │ │ ├── enrichment_subscription_workflow.ipynb │ │ ├── google_cloud_credentials.ipynb │ │ ├── points_enrichment_dataset.ipynb │ │ ├── polygons_enrichment_dataset.ipynb │ │ └── wof_to_filter_do_data.ipynb │ ├── data_services │ │ ├── geocoding.ipynb │ │ ├── geocoding_and_isolines.ipynb │ │ ├── isochrones.ipynb │ │ └── isodistances.ipynb │ ├── data_visualization │ │ ├── grid_layout │ │ │ ├── custom_layout.ipynb │ │ │ ├── default_layout.ipynb │ │ │ ├── layout_titles.ipynb │ │ │ ├── layout_viewport.ipynb │ │ │ └── static_layout.ipynb │ │ ├── layers │ │ │ ├── add_layer.ipynb │ │ │ └── add_multiple_layers.ipynb │ │ ├── legends │ │ │ ├── basic_legend.ipynb │ │ │ ├── color_bins_legend.ipynb │ │ │ ├── color_category_legend.ipynb │ │ │ ├── color_continuous_legend.ipynb │ │ │ ├── default_legend.ipynb │ │ │ ├── multiple_legends.ipynb │ │ │ ├── size_bins_legend.ipynb │ │ │ ├── size_category_legend.ipynb │ │ │ └── size_continuous_legend.ipynb │ │ ├── map_configuration │ │ │ ├── change_default_carto_basemap.ipynb │ │ │ ├── custom_viewport.ipynb │ │ │ ├── dark_theme.ipynb │ │ │ └── solid_color_background.ipynb │ │ ├── popups │ │ │ ├── default_popup.ipynb │ │ │ ├── multiple_popup_events.ipynb │ │ │ ├── popup_on_click.ipynb │ │ │ ├── popup_on_hover.ipynb │ │ │ └── popup_titles_and_format.ipynb │ │ ├── publish_and_share │ │ │ ├── publish_visualization_gdf.ipynb │ │ │ ├── publish_visualization_layout.ipynb │ │ │ ├── publish_visualization_private_table.ipynb │ │ │ └── publish_visualization_public_table.ipynb │ │ ├── styles │ │ │ ├── animation_style.ipynb │ │ │ ├── basic_style.ipynb │ │ │ ├── cluster_size_style.ipynb │ │ │ ├── color_bins_style.ipynb │ │ │ ├── color_category_style.ipynb │ │ │ ├── color_continuous_style.ipynb │ │ │ ├── combine_visualization_styles.ipynb │ │ │ ├── size_bins_style.ipynb │ │ │ ├── size_category_style.ipynb │ │ │ └── size_continuous_style.ipynb │ │ └── widgets │ │ │ ├── animation_widget.ipynb │ │ │ ├── basic_widget.ipynb │ │ │ ├── category_widget.ipynb │ │ │ ├── default_widget.ipynb │ │ │ ├── formula_widget.ipynb │ │ │ ├── histogram_widget.ipynb │ │ │ ├── multiple_widgets.ipynb │ │ │ └── time_series_widget.ipynb │ ├── examples.json │ ├── introduction.md │ └── use_cases │ │ ├── geocoding_london_stations.ipynb │ │ ├── paris_remarkable_trees.ipynb │ │ ├── paris_wifi_services.ipynb │ │ └── visualize_temperatures.ipynb ├── guides │ ├── 00-Introduction.md │ ├── 01-Installation.ipynb │ ├── 02-Authentication.ipynb │ ├── 03-Quickstart.ipynb │ ├── 04-Data-Management.ipynb │ ├── 05-Data-Visualization.ipynb │ ├── 06-Data-Services.ipynb │ ├── 07-Data-Observatory.ipynb │ ├── creds.sample.json │ ├── guides.json │ └── img │ │ ├── credentials │ │ ├── api-keys.png │ │ └── dashboard.png │ │ ├── enrichment │ │ └── enrichment_01.png │ │ └── subscriptions │ │ ├── sub_dat.png │ │ └── sub_geo.png ├── index.rst ├── reference │ ├── auth.rst │ ├── data_clients.rst │ ├── data_observatory.rst │ ├── data_services.rst │ ├── exceptions.rst │ ├── introduction.rst │ ├── io_functions.rst │ ├── utils.rst │ └── viz.rst ├── requirements.txt └── support │ ├── 01-Support-options.md │ └── 02-Contribute.md ├── package.json ├── requirements.txt ├── rollup.config.js ├── setup.cfg ├── setup.py ├── tests ├── README.md ├── __init__.py ├── conftest.py ├── e2e │ ├── __init__.py │ ├── data │ │ ├── __init__.py │ │ ├── client │ │ │ └── __init__.py │ │ ├── do-api │ │ │ └── test_do_dataset_integration.py │ │ ├── observatory │ │ │ ├── __init__.py │ │ │ ├── catalog │ │ │ │ ├── files │ │ │ │ │ ├── private-dataset.csv │ │ │ │ │ ├── private-geography.csv │ │ │ │ │ ├── public-dataset-join-geography.csv │ │ │ │ │ ├── public-dataset-ordered-and-limited.csv │ │ │ │ │ ├── public-dataset.csv │ │ │ │ │ └── public-geography.csv │ │ │ │ └── test_download.py │ │ │ └── enrichment │ │ │ │ ├── __init__.py │ │ │ │ ├── files │ │ │ │ ├── points-private.geojson │ │ │ │ ├── points-public-filter.geojson │ │ │ │ ├── points.geojson │ │ │ │ ├── polygon-private-avg.geojson │ │ │ │ ├── polygon-public-agg-custom-by-var.geojson │ │ │ │ ├── polygon-public-agg-custom-filter.geojson │ │ │ │ ├── polygon-public-none.geojson │ │ │ │ ├── polygon-public.geojson │ │ │ │ └── polygon.geojson │ │ │ │ └── test_enrichment.py │ │ └── services │ │ │ ├── __init__.py │ │ │ ├── fixtures │ │ │ ├── enrichment_points.csv │ │ │ └── enrichment_polygons.csv │ │ │ ├── test_geocoding.py │ │ │ └── test_isolines.py │ ├── helpers.py │ └── secret.json.sample ├── notebooks │ ├── creds.sample.json │ ├── requirements.txt │ └── test_notebooks.py └── unit │ ├── __init__.py │ ├── analysis │ ├── __init__.py │ ├── grid_quadkey_bbox.csv │ ├── grid_quadkey_pol.csv │ └── test_grid.py │ ├── auth │ ├── __init__.py │ ├── test_auth_api_client.py │ └── test_credentials.py │ ├── data │ ├── __init__.py │ ├── client │ │ ├── __init__.py │ │ └── test_sql_client.py │ └── observatory │ │ ├── __init__.py │ │ └── catalog │ │ ├── __init__.py │ │ ├── examples.py │ │ ├── repository │ │ ├── __init__.py │ │ ├── test_category_repo.py │ │ ├── test_country_repo.py │ │ ├── test_dataset_repo.py │ │ ├── test_geography_repo.py │ │ ├── test_provider_repo.py │ │ ├── test_repo_client.py │ │ ├── test_variable_group_repo.py │ │ └── test_variable_repo.py │ │ ├── test_catalog.py │ │ ├── test_category.py │ │ ├── test_country.py │ │ ├── test_dataset.py │ │ ├── test_geography.py │ │ ├── test_provider.py │ │ ├── test_variable.py │ │ └── test_variable_group.py │ ├── io │ ├── __init__.py │ ├── managers │ │ ├── __init__.py │ │ └── test_context_manager.py │ ├── test_carto.py │ └── test_crs.py │ ├── mocks │ ├── __init__.py │ ├── api_key_mock.py │ ├── kuviz_mock.py │ └── map_mock.py │ ├── utils │ ├── __init__.py │ ├── test_columns.py │ ├── test_geom_utils.py │ └── test_utils.py │ └── viz │ ├── __init__.py │ ├── popups │ ├── test_popup.py │ └── test_popup_list.py │ ├── test_basemaps.py │ ├── test_kuviz.py │ ├── test_layer.py │ ├── test_layout.py │ ├── test_legend.py │ ├── test_map.py │ ├── test_palettes.py │ ├── test_source.py │ ├── test_style.py │ ├── test_widget.py │ ├── test_widget_list.py │ ├── utils.py │ └── widgets │ ├── __init__.py │ ├── test_animation_widget.py │ ├── test_basic_widget.py │ ├── test_category_widget.py │ ├── test_formula_widget.py │ ├── test_histogram_widget.py │ └── test_time_series_widget.py └── tox.ini /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 120 3 | 4 | # Ignore rules from the "black" formatter. This is added 5 | # to avoid "Black would make changes." in houndci-bot. 6 | ignore = BLK, E121, E123, E126, E133, E226, E241, E242, E704, W503, W504, W505 7 | -------------------------------------------------------------------------------- /.github/workflows/cartoframes-ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | branches: 7 | - master 8 | - develop 9 | 10 | jobs: 11 | test: 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | include: 16 | - os: ubuntu-20.04 17 | python-version: 3.5 18 | - os: ubuntu-20.04 19 | python-version: 3.6 20 | - os: ubuntu-latest 21 | python-version: 3.7 22 | - os: ubuntu-latest 23 | python-version: 3.8 24 | 25 | name: Run tests on Python ${{ matrix.python-version }} 26 | 27 | runs-on: ${{ matrix.os }} 28 | 29 | steps: 30 | - uses: actions/checkout@v3 31 | 32 | - name: Set up Python ${{ matrix.python-version }} 33 | uses: actions/setup-python@v4 34 | with: 35 | python-version: ${{ matrix.python-version }} 36 | 37 | - name: Install dependencies 38 | run: | 39 | python -m pip install --upgrade pip 40 | pip install tox 41 | pip install tox-gh-actions 42 | 43 | - name: Test with tox 44 | run: | 45 | tox 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Python / Jupyter 2 | *.pyc 3 | .ipynb_checkpoints/ 4 | sandbox/ 5 | 6 | # Misc 7 | creds.json 8 | credentials.json 9 | docs/guides/*.csv 10 | 11 | __pycache__ 12 | .*.sw[nop] 13 | .vscode 14 | _debug 15 | 16 | # OS 17 | .DS_Store 18 | 19 | # Sphinx related 20 | _build 21 | .buildinfo 22 | .doctrees/ 23 | 24 | # Distribution / packaging 25 | .Python 26 | env/ 27 | .venv/ 28 | build/ 29 | develop-eggs/ 30 | dist/ 31 | downloads/ 32 | eggs/ 33 | .eggs/ 34 | /lib/ 35 | lib64/ 36 | parts/ 37 | sdist/ 38 | var/ 39 | wheels/ 40 | *.egg-info/ 41 | .installed.cfg 42 | *.egg 43 | Pipfile 44 | Pipfile.lock 45 | package-lock.json 46 | 47 | # Swap files 48 | .*.sw[nop] 49 | 50 | # project-specific 51 | CARTOCREDS.json 52 | SITEKEY.txt 53 | secret.json 54 | examples/scratch/* 55 | _debug 56 | 57 | # JavaScript 58 | node_modules 59 | 60 | # Tests 61 | .tox 62 | .coverage 63 | htmlcov 64 | test_*.json 65 | .pytest_cache 66 | tmp_file.csv 67 | my_dataset.csv 68 | fake_path 69 | 70 | # Sphinx documentation 71 | docs/build/ 72 | docs/_static/ 73 | docs/_templates/ 74 | -------------------------------------------------------------------------------- /.hound.yml: -------------------------------------------------------------------------------- 1 | flake8: 2 | enabled: true 3 | config_file: .flake8 4 | 5 | jshint: 6 | enabled: true 7 | config_file: .jshintrc 8 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | cartoframes/assets/src/bundle.js 2 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esversion": 6, 3 | "laxbreak" : true 4 | } 5 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v2.3.0 4 | hooks: 5 | - id: flake8 6 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MESSAGES CONTROL] 2 | disable= 3 | line-too-long, 4 | unused-import, 5 | import-error, 6 | unresolved-import, 7 | import-outside-toplevel, 8 | global-statement, 9 | too-many-branches, 10 | too-many-locals, 11 | too-many-arguments, 12 | too-many-ancestors, 13 | too-many-return-statements, 14 | too-many-public-methods, 15 | too-few-public-methods, 16 | too-many-instance-attributes, 17 | too-many-statements, 18 | missing-class-docstring, 19 | missing-module-docstring, 20 | missing-function-docstring, 21 | useless-object-inheritance, 22 | no-self-use, 23 | fixme, 24 | redefined-builtin, 25 | protected-access, 26 | attribute-defined-outside-init, 27 | no-else-return, 28 | dangerous-default-value, 29 | duplicate-code, 30 | deprecated-method, 31 | arguments-differ, 32 | signature-differs, 33 | unused-argument, 34 | invalid-name, 35 | super-init-not-called, 36 | pointless-string-statement, 37 | broad-except, 38 | abstract-method, 39 | useless-super-delegation, 40 | cyclic-import, 41 | missing-docstring, 42 | len-as-condition, 43 | old-style-class, 44 | superfluous-parens, 45 | wrong-import-order, 46 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Build documentation in the docs/ directory with Sphinx 9 | sphinx: 10 | configuration: docs/conf.py 11 | 12 | # Build documentation with MkDocs 13 | #mkdocs: 14 | # configuration: mkdocs.yml 15 | 16 | # Optionally build your docs in additional formats such as PDF and ePub 17 | formats: all 18 | 19 | # Optionally set the version of Python and requirements required to build your docs 20 | python: 21 | version: 3.7 22 | install: 23 | - requirements: docs/requirements.txt 24 | - requirements: requirements.txt 25 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | Andy Eschbacher 2 | Stuart Lynn 3 | David Medina 4 | Levi J. Wolf 5 | Michelle Ho 6 | Nacho Sánchez 7 | Cillas Subirá 8 | Jorge Sancha 9 | Simon Martín 10 | Elena Torró 11 | Javier Torres 12 | Alberto Romeu 13 | Román Jiménez 14 | Víctor Velarde 15 | Mario de Frutos Dieguez 16 | Jesús Arroyo 17 | Mamata Akella 18 | Josema Camacho 19 | Antonio Carlón 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, CartoDB 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of the copyright holder nor the names of its contributors 15 | may be used to endorse or promote products derived from this software without 16 | specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | # include the license, README, and asset files 2 | include LICENSE README.rst NEWS.rst 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | init: 2 | pip install -U pip 3 | pip install -e .[tests] 4 | 5 | lint: 6 | flake8 cartoframes tests 7 | 8 | test: 9 | pytest tests/unit/ 10 | 11 | clean: 12 | rm -fr build/* dist/* .egg cartoframes.egg-info 13 | 14 | dist: 15 | python setup.py sdist bdist_wheel --universal 16 | 17 | send: 18 | twine upload dist/* 19 | 20 | publish: 21 | clean dist send 22 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | 6 | | Version | Supported | 7 | | ------- | ------------------ | 8 | | 1.0 | :white_check_mark: | 9 | | < 1.0 | :x: | 10 | 11 | ## Reporting a Vulnerability 12 | 13 | Please read [security.txt](https://github.com/CartoDB/cartodb/blob/master/security.txt) to report any security vulnerabilities. We will acknowledge receipt of your vulnerability report and strive to send you regular updates about our progress. If you're curious about the status of your disclosure please feel free to email us again. 14 | 15 | Please refrain from requesting compensation for reporting vulnerabilities. If you want we will publicly acknowledge your responsible disclosure, once the issue is fixed. 16 | 17 | You are not allowed to search for vulnerabilities on carto.com itself. CARTO is open source software, you can install a copy yourself and test against that. 18 | 19 | When a vulnerability is suspected or discovered we create a confidential security issue to track it internally. Security patches are pushed to a private repository and they should not appear on CARTO.com until it's completely fixed. 20 | -------------------------------------------------------------------------------- /binder/requirements.txt: -------------------------------------------------------------------------------- 1 | cartoframes==1.2.0 2 | # Additional dependencies from examples 3 | matplotlib 4 | dask 5 | # needed for pandas.read_excel in data obs notebook 6 | xlrd 7 | -------------------------------------------------------------------------------- /cartoframes/__init__.py: -------------------------------------------------------------------------------- 1 | from ._version import __version__ 2 | from .utils.utils import check_package 3 | from .io.carto import read_carto, to_carto, list_tables, has_table, delete_table, rename_table, \ 4 | copy_table, create_table_from_query, describe_table, update_privacy_table 5 | 6 | 7 | # Check installed packages versions 8 | check_package('carto', '>=1.11.2') 9 | check_package('pandas', '>=0.25.0') 10 | check_package('geopandas', '>=0.6.0') 11 | 12 | 13 | __all__ = [ 14 | '__version__', 15 | 'read_carto', 16 | 'to_carto', 17 | 'list_tables', 18 | 'has_table', 19 | 'delete_table', 20 | 'rename_table', 21 | 'copy_table', 22 | 'create_table_from_query', 23 | 'describe_table', 24 | 'update_privacy_table' 25 | ] 26 | -------------------------------------------------------------------------------- /cartoframes/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = '1.2.4' 2 | -------------------------------------------------------------------------------- /cartoframes/analysis/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CartoDB/cartoframes/1f56b75f512da62b7ecd1c07e1021b8552158907/cartoframes/analysis/__init__.py -------------------------------------------------------------------------------- /cartoframes/analysis/grid.py: -------------------------------------------------------------------------------- 1 | from pandas import DataFrame, concat 2 | from geopandas import GeoDataFrame 3 | from shapely.geometry import box 4 | 5 | from ..utils.utils import check_package 6 | 7 | 8 | class QuadGrid: 9 | 10 | def polyfill(self, input_gdf, zoom_level): 11 | check_package('mercantile', is_optional=True) 12 | import mercantile 13 | 14 | if not hasattr(input_gdf, 'geometry'): 15 | raise ValueError('This dataframe has no valid geometry.') 16 | 17 | geometry_name = input_gdf.geometry.name 18 | 19 | dfs = [] 20 | for _, row in input_gdf.iterrows(): 21 | input_geometry = row[geometry_name] 22 | bounds = input_geometry.bounds 23 | tiles = mercantile.tiles(bounds[0], bounds[1], bounds[2], bounds[3], zoom_level) 24 | new_rows = [] 25 | for tile in tiles: 26 | new_row = row.copy() 27 | new_geometry = box(*mercantile.bounds(tile)) 28 | if new_geometry.intersects(input_geometry): 29 | new_row[geometry_name] = new_geometry 30 | new_row['quadkey'] = mercantile.quadkey(tile) 31 | new_rows.append(new_row) 32 | dfs.append(DataFrame(new_rows)) 33 | 34 | df = concat(dfs).reset_index(drop=True) 35 | 36 | return GeoDataFrame(df, geometry=geometry_name, crs='epsg:4326') 37 | -------------------------------------------------------------------------------- /cartoframes/assets/init.js.j2: -------------------------------------------------------------------------------- 1 | document 2 | .querySelector('as-responsive-content') 3 | .addEventListener('ready', () => { 4 | const basecolor = '{{basecolor}}'; 5 | const basemap = '{{basemap}}'; 6 | const bounds = {{ bounds }}; 7 | const camera = {{ camera|tojson }}; 8 | const has_legends = '{{has_legends}}' === 'True'; 9 | const is_static = '{{is_static}}' === 'True'; 10 | const layer_selector = '{{layer_selector}}' === 'True'; 11 | const layers = {{ layers|tojson }}; 12 | const mapboxtoken = '{{mapboxtoken}}'; 13 | const show_info = '{{show_info}}' === 'True'; 14 | 15 | init({ 16 | basecolor, 17 | basemap, 18 | bounds, 19 | camera, 20 | has_legends, 21 | is_static, 22 | layer_selector, 23 | layers, 24 | mapboxtoken, 25 | show_info 26 | }); 27 | }); -------------------------------------------------------------------------------- /cartoframes/assets/init_layout.js.j2: -------------------------------------------------------------------------------- 1 | const maps = {{ maps|tojson }}; 2 | const is_static = '{{is_static}}' === 'True'; 3 | 4 | init({ 5 | is_static, 6 | maps 7 | }); -------------------------------------------------------------------------------- /cartoframes/assets/src/constants.js: -------------------------------------------------------------------------------- 1 | export const BASEMAPS = { 2 | DarkMatter: carto.basemaps.darkmatter, 3 | Voyager: carto.basemaps.voyager, 4 | Positron: carto.basemaps.positron 5 | }; 6 | 7 | export const attributionControl = new mapboxgl.AttributionControl({ 8 | compact: false 9 | }); 10 | 11 | export const FIT_BOUNDS_SETTINGS = { animate: false, padding: 50, maxZoom: 16 }; -------------------------------------------------------------------------------- /cartoframes/assets/src/errors/display.js: -------------------------------------------------------------------------------- 1 | import { parse } from './parse'; 2 | 3 | export function displayError(e) { 4 | const error$ = document.getElementById('error-container'); 5 | const errors$ = error$.getElementsByClassName('errors'); 6 | const stacktrace$ = document.getElementById('error-stacktrace'); 7 | 8 | errors$[0].innerHTML = e.name; 9 | errors$[1].innerHTML = e.type; 10 | errors$[2].innerHTML = e.message.replace(e.type, ''); 11 | 12 | error$.style.visibility = 'visible'; 13 | 14 | const stack = parse(e.stack); 15 | const list = stack.map(item => { 16 | return `
  • 17 | at ${item.methodName}: 18 | (${item.file}:${item.lineNumber}:${item.column}) 19 |
  • `; 20 | }); 21 | 22 | stacktrace$.innerHTML = list.join('\n'); 23 | } -------------------------------------------------------------------------------- /cartoframes/assets/src/index.js: -------------------------------------------------------------------------------- 1 | import * as mapSettings from './map'; 2 | 3 | export default function init(settings) { 4 | mapSettings.setReady(settings); 5 | } -------------------------------------------------------------------------------- /cartoframes/assets/src/layers.js: -------------------------------------------------------------------------------- 1 | import { bridgeLayerWidgets, renderWidget } from './widgets'; 2 | import { createLegends } from './legends'; 3 | import SourceFactory from './map/SourceFactory'; 4 | import { displayError } from './errors/display'; 5 | 6 | const factory = new SourceFactory(); 7 | 8 | export function initMapLayer(layer, layerIndex, numLayers, hasLegends, map, mapIndex) { 9 | const mapSource = factory.createSource(layer); 10 | const mapViz = new carto.Viz(layer.viz); 11 | const mapLayer = new carto.Layer(`layer${layerIndex}`, mapSource, mapViz); 12 | const mapLayerIndex = numLayers - layerIndex - 1; 13 | 14 | try { 15 | mapLayer._updateLayer.catch(displayError); 16 | } catch (e) { 17 | throw e; 18 | } 19 | 20 | mapLayer.addTo(map); 21 | 22 | setLayerLegend(layer, mapLayerIndex, mapLayer, mapIndex, hasLegends); 23 | setLayerWidgets(map, layer, mapLayer, mapLayerIndex, mapSource); 24 | 25 | return mapLayer; 26 | } 27 | 28 | export function getInteractiveLayers(layers, mapLayers) { 29 | const interactiveLayers = []; 30 | const interactiveMapLayers = []; 31 | 32 | layers.forEach((layer, index) => { 33 | if (layer.interactivity) { 34 | interactiveLayers.push(layer); 35 | interactiveMapLayers.push(mapLayers[index]); 36 | } 37 | }); 38 | 39 | return { interactiveLayers, interactiveMapLayers }; 40 | } 41 | 42 | 43 | export function setLayerInteractivity(layer, mapLayer) { 44 | return layer.interactivity ? 45 | { interactiveLayer: layer, interactiveMapLayer: mapLayer } 46 | : { interactiveLayer: null, interactiveMapLayer: null }; 47 | } 48 | 49 | export function setLayerLegend(layer, mapLayerIndex, mapLayer, mapIndex, hasLegends) { 50 | if (hasLegends && layer.legends) { 51 | createLegends(mapLayer, layer.legends, mapLayerIndex, mapIndex); 52 | } 53 | } 54 | 55 | export function setLayerWidgets(map, layer, mapLayer, mapLayerIndex, mapSource) { 56 | if (layer.widgets.length) { 57 | initLayerWidgets(layer.widgets, mapLayerIndex); 58 | updateLayerWidgets(layer.widgets, mapLayer); 59 | bridgeLayerWidgets(map, mapLayer, mapSource, layer.widgets); 60 | } 61 | } 62 | 63 | export function initLayerWidgets(widgets, mapLayerIndex) { 64 | widgets.forEach((widget, widgetIndex) => { 65 | const id = `layer${mapLayerIndex}_widget${widgetIndex}`; 66 | widget.id = id; 67 | }); 68 | } 69 | 70 | export function updateLayerWidgets(widgets, mapLayer) { 71 | mapLayer.on('updated', () => renderLayerWidgets(widgets, mapLayer)); 72 | } 73 | 74 | export function renderLayerWidgets(widgets, mapLayer) { 75 | const variables = mapLayer.viz.variables; 76 | 77 | widgets 78 | .filter((widget) => !widget.has_bridge) 79 | .forEach((widget) => { 80 | const name = widget.variable_name; 81 | const value = getWidgetValue(name, variables); 82 | renderWidget(widget, value); 83 | }); 84 | } 85 | 86 | export function getWidgetValue(name, variables) { 87 | return name && variables[name] ? variables[name].value : null; 88 | } -------------------------------------------------------------------------------- /cartoframes/assets/src/legends.js: -------------------------------------------------------------------------------- 1 | import { formatter } from './utils'; 2 | 3 | export function createLegends(layer, legends, layerIndex, mapIndex=0) { 4 | if (legends.length) { 5 | legends.forEach((legend, legendIndex) => _createLegend(layer, legend, layerIndex, legendIndex, mapIndex)); 6 | } else { 7 | _createLegend(layer, legends, layerIndex, 0, mapIndex); 8 | } 9 | } 10 | 11 | function _createLegend(layer, legend, layerIndex, legendIndex, mapIndex=0) { 12 | const element = document.querySelector(`#layer${layerIndex}_map${mapIndex}_legend${legendIndex}`); 13 | 14 | if (legend.prop) { 15 | const othersLabel = 'Others'; // TODO: i18n 16 | const prop = legend.prop; 17 | const dynamic = legend.dynamic; 18 | const order = legend.ascending ? 'ASC' : 'DESC'; 19 | const variable = legend.variable; 20 | const config = { othersLabel, variable, order }; 21 | const formatFunc = (value) => formatter(value, legend.format); 22 | const options = { format: formatFunc, config, dynamic }; 23 | 24 | if (legend.type.startsWith('size-continuous')) { 25 | config.samples = 4; 26 | } 27 | 28 | AsBridge.VL.Legends.rampLegend(element, layer, prop, options); 29 | } else { 30 | // TODO: we don't have a bridge for this case, should this even be a case? 31 | } 32 | } -------------------------------------------------------------------------------- /cartoframes/assets/src/map/SourceFactory.js: -------------------------------------------------------------------------------- 1 | export default function SourceFactory() { 2 | const sourceTypes = { GeoJSON, Query, MVT }; 3 | 4 | this.createSource = (layer) => { 5 | return sourceTypes[layer.type](layer); 6 | }; 7 | } 8 | 9 | function GeoJSON(layer) { 10 | const options = JSON.parse(JSON.stringify(layer.options)); 11 | const data = _decodeJSONData(layer.data, layer.encode_data); 12 | 13 | return new carto.source.GeoJSON(data, options); 14 | } 15 | 16 | function Query(layer) { 17 | const auth = { 18 | username: layer.credentials.username, 19 | apiKey: layer.credentials.api_key || 'default_public' 20 | }; 21 | 22 | const config = { 23 | serverURL: layer.credentials.base_url || `https://${layer.credentials.username}.carto.com/` 24 | }; 25 | 26 | return new carto.source.SQL(layer.data, auth, config); 27 | } 28 | 29 | function MVT(layer) { 30 | return new carto.source.MVT(layer.data.file, JSON.parse(layer.data.metadata)); 31 | } 32 | 33 | function _decodeJSONData(data, encodeData) { 34 | try { 35 | if (encodeData) { 36 | const decodedJSON = pako.inflate(atob(data), { to: 'string' }); 37 | return JSON.parse(decodedJSON); 38 | } else { 39 | return JSON.parse(data); 40 | } 41 | } catch(error) { 42 | throw new Error(` 43 | Error: "${error}". CARTOframes is not able to parse your local data because it is too large. 44 | Please, disable the data compresion with encode_data=False in your Layer class. 45 | `); 46 | } 47 | } -------------------------------------------------------------------------------- /cartoframes/assets/src/map/interactivity.js: -------------------------------------------------------------------------------- 1 | import { 2 | resetPopupClick, 3 | resetPopupHover, 4 | setPopupsClick, 5 | setPopupsHover 6 | } from './popups'; 7 | 8 | export function setInteractivity(map, interactiveLayers, interactiveMapLayers) { 9 | const interactivity = new carto.Interactivity(interactiveMapLayers); 10 | 11 | const clickPopup = new mapboxgl.Popup({ 12 | closeButton: true, 13 | closeOnClick: false 14 | }); 15 | 16 | const hoverPopup = new mapboxgl.Popup({ 17 | closeButton: false, 18 | closeOnClick: false 19 | }); 20 | 21 | const { clickAttrs, hoverAttrs } = _setInteractivityAttrs(interactiveLayers); 22 | 23 | resetPopupClick(map, interactivity); 24 | resetPopupHover(map, interactivity); 25 | 26 | if (clickAttrs.length > 0) { 27 | setPopupsClick(map, clickPopup, hoverPopup, interactivity, clickAttrs); 28 | } 29 | 30 | if (hoverAttrs.length > 0) { 31 | setPopupsHover(map, hoverPopup, interactivity, hoverAttrs); 32 | } 33 | } 34 | 35 | function _setInteractivityAttrs(interactiveLayers) { 36 | let clickAttrs = []; 37 | let hoverAttrs = []; 38 | 39 | interactiveLayers.forEach((interactiveLayer) => { 40 | interactiveLayer.interactivity.forEach((interactivityDef) => { 41 | if (interactivityDef.event === 'click') { 42 | clickAttrs = clickAttrs.concat(interactivityDef.attrs); 43 | } else if (interactivityDef.event === 'hover') { 44 | hoverAttrs = hoverAttrs.concat(interactivityDef.attrs); 45 | } 46 | }); 47 | }); 48 | 49 | return { clickAttrs, hoverAttrs }; 50 | } -------------------------------------------------------------------------------- /cartoframes/assets/src/map/popups.js: -------------------------------------------------------------------------------- 1 | import { formatter } from '../utils'; 2 | 3 | export function resetPopupClick(interactivity) { 4 | interactivity.off('featureClick'); 5 | } 6 | 7 | export function resetPopupHover(interactivity) { 8 | interactivity.off('featureHover'); 9 | } 10 | 11 | export function setPopupsClick(map, clickPopup, hoverPopup, interactivity, attrs) { 12 | interactivity.on('featureClick', (event) => { 13 | updatePopup(map, clickPopup, event, attrs); 14 | hoverPopup.remove(); 15 | }); 16 | } 17 | 18 | export function setPopupsHover(map, hoverPopup, interactivity, attrs) { 19 | interactivity.on('featureHover', (event) => { 20 | updatePopup(map, hoverPopup, event, attrs); 21 | }); 22 | } 23 | 24 | export function updatePopup(map, popup, event, attrs) { 25 | if (event.features.length > 0) { 26 | let popupHTML = ''; 27 | const layerIDs = []; 28 | 29 | for (const feature of event.features) { 30 | if (layerIDs.includes(feature.layerId)) { 31 | continue; 32 | } 33 | // Track layers to add only one feature per layer 34 | layerIDs.push(feature.layerId); 35 | 36 | for (const item of attrs) { 37 | const variable = feature.variables[item.name]; 38 | if (variable) { 39 | let value = variable.value; 40 | value = formatter(value, item.format); 41 | 42 | popupHTML = ` 43 | ${item.title} 44 | ${value} 45 | ` + popupHTML; 46 | } 47 | } 48 | } 49 | 50 | if (popupHTML) { 51 | popup 52 | .setLngLat([event.coordinates.lng, event.coordinates.lat]) 53 | .setHTML(``); 54 | 55 | if (!popup.isOpen()) { 56 | popup.addTo(map); 57 | } 58 | } else { 59 | popup.remove(); 60 | } 61 | } else { 62 | popup.remove(); 63 | } 64 | } -------------------------------------------------------------------------------- /cartoframes/assets/src/utils.js: -------------------------------------------------------------------------------- 1 | import { format as d3Format } from 'd3-format'; 2 | 3 | export function formatter(value, specifier) { 4 | const formatFunc = specifier ? d3Format(specifier) : formatValue; 5 | 6 | if (Array.isArray(value)) { 7 | const [first, second] = value; 8 | if (first === -Infinity) { 9 | return `< ${formatFunc(second)}`; 10 | } 11 | if (second === Infinity) { 12 | return `> ${formatFunc(first)}`; 13 | } 14 | return `${formatFunc(first)} - ${formatFunc(second)}`; 15 | } 16 | return formatFunc(value); 17 | } 18 | 19 | export function formatValue(value) { 20 | if (typeof value === 'number') { 21 | return formatNumber(value); 22 | } 23 | return value; 24 | } 25 | 26 | export function formatNumber(value) { 27 | if (!Number.isInteger(value)) { 28 | return value.toLocaleString(undefined, { 29 | minimumFractionDigits: 2, 30 | maximumFractionDigits: 3 31 | }); 32 | } 33 | return value.toLocaleString(); 34 | } 35 | 36 | export function updateViewport(id, map) { 37 | function updateMapInfo() { 38 | const mapInfo$ = document.getElementById(id); 39 | const center = map.getCenter(); 40 | const lat = center.lat.toFixed(6); 41 | const lng = center.lng.toFixed(6); 42 | const zoom = map.getZoom().toFixed(2); 43 | 44 | mapInfo$.innerText = `viewport={'zoom': ${zoom}, 'lat': ${lat}, 'lng': ${lng}}`; 45 | } 46 | 47 | updateMapInfo(); 48 | 49 | map.on('zoom', updateMapInfo); 50 | map.on('move', updateMapInfo); 51 | } 52 | 53 | export function getBasecolorSettings(basecolor) { 54 | return { 55 | 'version': 8, 56 | 'sources': {}, 57 | 'layers': [{ 58 | 'id': 'background', 59 | 'type': 'background', 60 | 'paint': { 61 | 'background-color': basecolor 62 | } 63 | }] 64 | }; 65 | } 66 | 67 | export function getImageElement(mapIndex) { 68 | const id = mapIndex !== undefined ? `map-image-${mapIndex}` : 'map-image'; 69 | return document.getElementById(id); 70 | } 71 | 72 | export function getContainerElement(mapIndex) { 73 | const id = mapIndex !== undefined ? `main-container-${mapIndex}` : 'main-container'; 74 | return document.getElementById(id); 75 | } 76 | 77 | export function saveImage(mapIndex) { 78 | const img = getImageElement(mapIndex); 79 | const container = getContainerElement(mapIndex); 80 | 81 | html2canvas(container) 82 | .then((canvas) => setMapImage(canvas, img, container)); 83 | } 84 | 85 | export function setMapImage(canvas, img, container) { 86 | const src = canvas.toDataURL(); 87 | img.setAttribute('src', src); 88 | img.style.display = 'block'; 89 | container.style.display = 'none'; 90 | } -------------------------------------------------------------------------------- /cartoframes/assets/src/widgets.js: -------------------------------------------------------------------------------- 1 | import { formatter } from './utils'; 2 | 3 | export function renderWidget(widget, value) { 4 | widget.element = widget.element || document.querySelector(`#${widget.id}-value`); 5 | 6 | if (value && widget.element) { 7 | widget.element.innerText = typeof value === 'number' ? formatter(value, widget.options.format) : value; 8 | } 9 | } 10 | 11 | export function renderBridge(bridge, widget, mapLayer) { 12 | widget.element = widget.element || document.querySelector(`#${widget.id}`); 13 | 14 | switch (widget.type) { 15 | case 'histogram': 16 | const type = _getWidgetType(mapLayer, widget.value, widget.prop); 17 | const histogram = type === 'category' ? 'categoricalHistogram' : 'numericalHistogram'; 18 | bridge[histogram](widget.element, widget.value, widget.options); 19 | break; 20 | case 'category': 21 | bridge.category(widget.element, widget.value, widget.options); 22 | break; 23 | case 'animation': 24 | widget.options.propertyName = widget.prop; 25 | bridge.animationControls(widget.element, widget.value, widget.options); 26 | break; 27 | case 'time-series': 28 | widget.options.propertyName = widget.prop; 29 | bridge.timeSeries(widget.element, widget.value, widget.options); 30 | break; 31 | } 32 | } 33 | 34 | export function bridgeLayerWidgets(map, mapLayer, mapSource, widgets) { 35 | const bridge = new AsBridge.VL.Bridge({ 36 | carto: carto, 37 | layer: mapLayer, 38 | source: mapSource, 39 | map: map 40 | }); 41 | 42 | widgets 43 | .filter((widget) => widget.has_bridge) 44 | .forEach((widget) => renderBridge(bridge, widget, mapLayer)); 45 | 46 | bridge.build(); 47 | } 48 | 49 | function _getWidgetType(layer, property, value) { 50 | return layer.metadata && layer.metadata.properties[value] ? 51 | layer.metadata.properties[value].type 52 | : _getWidgetPropertyType(layer, property); 53 | } 54 | 55 | function _getWidgetPropertyType(layer, property) { 56 | return layer.metadata && layer.metadata.properties[property] ? 57 | layer.metadata.properties[property].type 58 | : null; 59 | } -------------------------------------------------------------------------------- /cartoframes/assets/style/common.html.j2: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cartoframes/assets/style/error.html.j2: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cartoframes/assets/style/layout.html.j2: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cartoframes/assets/style/map.html.j2: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cartoframes/assets/style/popup.html.j2: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cartoframes/assets/style/themes/dark.html.j2: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cartoframes/assets/style/themes/light.html.j2: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cartoframes/assets/style/widgets.html.j2: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cartoframes/assets/templates/error/basic.html.j2: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | : 4 |
    5 | 6 | 7 |
    8 |
    9 | 10 |
    11 | StackTrace 12 |
      13 |
      14 |
      15 | -------------------------------------------------------------------------------- /cartoframes/assets/templates/iframe.html.j2: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cartoframes/assets/templates/viz/basic.html.j2: -------------------------------------------------------------------------------- 1 | {% extends "templates/iframe.html.j2" %} 2 | {% block srcDoc %} 3 | {% include 'templates/viz/main.html.j2' %} 4 | {% endblock %} 5 | -------------------------------------------------------------------------------- /cartoframes/assets/templates/viz/footer.html.j2: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cartoframes/assets/templates/viz/layout.html.j2: -------------------------------------------------------------------------------- 1 | {% extends "templates/iframe.html.j2" %} 2 | {% block srcDoc %} 3 | {% include 'templates/viz/main_layout.html.j2' %} 4 | {% endblock %} 5 | -------------------------------------------------------------------------------- /cartoframes/assets/templates/viz/legends.html.j2: -------------------------------------------------------------------------------- 1 | {% macro createLegend(legend, id) -%} 2 | 3 | {%- endmacro %} 4 | 5 |
      6 | 7 | {% for layer in layers|reverse %} 8 | {% if layer.legends %} 9 | {% set layer_index = loop.index0 %} 10 | {% set slot = 'as-checkbox-layer-%d-slot' | format(layer_index) %} 11 |
      12 | {% if layer.has_legend_list %} 13 | {% for legend in layer.legends %} 14 | 17 | {{ createLegend(legend, 'layer%d_map%d_legend%d' | format(layer_index, 0, loop.index0)) }} 18 | {% if legend.footer %} 19 | {{legend.footer | safe }} 20 | {% endif %} 21 | 22 | {% endfor %} 23 | {% else %} 24 | 27 | {{ createLegend(layer.legends, 'layer%d_map%d_legend%d' | format(layer_index, 0, 0)) }} 28 | {% if layer.legends.footer %} 29 | {{layer.legends.footer | safe }} 30 | {% endif %} 31 | 32 | {% endif %} 33 |
      34 | {% endif %} 35 | {% endfor %} 36 |
      37 |
      -------------------------------------------------------------------------------- /cartoframes/assets/templates/viz/legends_layout.html.j2: -------------------------------------------------------------------------------- 1 | {% macro createLegend(legend, map_index, id) -%} 2 | 3 | {%- endmacro %} 4 | 5 |
      6 | 7 | {% for layer in layers|reverse %} 8 | {% if layer.legends %} 9 | {% set layer_index = loop.index0 %} 10 | {% set slot = 'as-checkbox-layer-%d-slot' | format(layer_index) %} 11 |
      12 | {% if layer.has_legend_list %} 13 | {% for legend in layer.legends %} 14 | 17 | {{ createLegend(legend, map_index, 'layer%d_map%d_legend%d' | format(layer_index, map_index, loop.index0)) }} 18 | {% if legend.footer %} 19 | {{legend.footer | safe }} 20 | {% endif %} 21 | 22 | {% endfor %} 23 | {% else %} 24 | 27 | {{ createLegend(layer.legends, map_index, 'layer%d_map%d_legend%d' | format(layer_index, map_index, 0)) }} 28 | {% if layer.legends.footer %} 29 | {{layer.legends.footer | safe }} 30 | {% endif %} 31 | 32 | {% endif %} 33 |
      34 | {% endif %} 35 | {% endfor %} 36 |
      37 |
      -------------------------------------------------------------------------------- /cartoframes/assets/templates/viz/main.html.j2: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{title}} 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {% if is_static %} 31 | 32 | {% endif %} 33 | 34 | {% if theme %} 35 | {% include 'style/themes/' + theme + '.html.j2' %} 36 | {% endif %} 37 | {% include 'style/common.html.j2' %} 38 | {% include 'style/map.html.j2' %} 39 | {% include 'style/error.html.j2' %} 40 | {% include 'style/popup.html.j2' %} 41 | {% include 'style/widgets.html.j2' %} 42 | 43 | 44 | 45 | Static map image 46 | 47 | {% if has_widgets %} 48 | {% include 'templates/viz/widgets.html.j2' %} 49 | {% endif %} 50 |
      51 |
      52 |
      53 | {% if show_info %} 54 |
      55 | {% endif %} 56 | {% if has_legends or layer_selector %} 57 |
      58 |
      59 | {% include 'templates/viz/legends.html.j2' %} 60 |
      61 |
      62 | {% endif %} 63 |
      64 |
      65 |
      66 | 67 | {% if is_embed %} 68 | {% include 'templates/viz/footer.html.j2' %} 69 | {% endif %} 70 | 71 | {% include 'templates/error/basic.html.j2' %} 72 | 73 | 74 | 77 | 80 | 81 | -------------------------------------------------------------------------------- /cartoframes/assets/templates/viz/widgets.html.j2: -------------------------------------------------------------------------------- 1 | {% macro createWidget(widget, id) -%} 2 |
      3 | {% with id = id %} 4 | {% include 'templates/viz/widgets/' + widget.type + '.html.j2' %} 5 | {% endwith %} 6 |
      7 | {%- endmacro %} 8 | 9 | 21 | -------------------------------------------------------------------------------- /cartoframes/assets/templates/viz/widgets/animation.html.j2: -------------------------------------------------------------------------------- 1 |
      2 | 8 | {% if widget.footer %} 9 |
      {{widget.footer | safe }}
      10 | {% endif %} 11 |
      12 | -------------------------------------------------------------------------------- /cartoframes/assets/templates/viz/widgets/basic.html.j2: -------------------------------------------------------------------------------- 1 |
      2 | 5 | 6 | {% if widget.footer %} 7 |
      {{widget.footer | safe }}
      8 | {% endif %} 9 |
      -------------------------------------------------------------------------------- /cartoframes/assets/templates/viz/widgets/category.html.j2: -------------------------------------------------------------------------------- 1 |
      2 | 6 | 7 | {% if widget.footer %} 8 |
      {{widget.footer | safe }}
      9 | {% endif %} 10 |
      11 | -------------------------------------------------------------------------------- /cartoframes/assets/templates/viz/widgets/formula.html.j2: -------------------------------------------------------------------------------- 1 |
      2 | 5 | 6 | 7 |

      8 | 9 | 10 | 11 | 12 | 13 |

      14 | 15 | {% if widget.footer %} 16 |
      {{widget.footer | safe }}
      17 | {% endif %} 18 |
      -------------------------------------------------------------------------------- /cartoframes/assets/templates/viz/widgets/histogram.html.j2: -------------------------------------------------------------------------------- 1 |
      2 | 6 | 7 | {% if widget.footer %} 8 |
      {{widget.footer | safe }}
      9 | {% endif %} 10 |
      -------------------------------------------------------------------------------- /cartoframes/assets/templates/viz/widgets/time-series.html.j2: -------------------------------------------------------------------------------- 1 |
      2 | 8 | 9 | {% if widget.footer %} 10 |
      {{widget.footer | safe }}
      11 | {% endif %} 12 |
      13 | -------------------------------------------------------------------------------- /cartoframes/auth/__init__.py: -------------------------------------------------------------------------------- 1 | """Auth namespace contains the class to manage authentication: 2 | :class:`cartoframes.auth.Credentials`. 3 | It also includes the utility functions 4 | :func:`cartoframes.auth.set_default_credentials` and 5 | :func:`cartoframes.auth.get_default_credentials`.""" 6 | 7 | from .credentials import Credentials 8 | from .defaults import get_default_credentials, set_default_credentials, unset_default_credentials 9 | 10 | __all__ = [ 11 | 'Credentials', 12 | 'set_default_credentials', 13 | 'get_default_credentials', 14 | 'unset_default_credentials' 15 | ] 16 | -------------------------------------------------------------------------------- /cartoframes/data/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CartoDB/cartoframes/1f56b75f512da62b7ecd1c07e1021b8552158907/cartoframes/data/__init__.py -------------------------------------------------------------------------------- /cartoframes/data/clients/__init__.py: -------------------------------------------------------------------------------- 1 | from .sql_client import SQLClient 2 | 3 | __all__ = [ 4 | 'SQLClient' 5 | ] 6 | -------------------------------------------------------------------------------- /cartoframes/data/clients/auth_api_client.py: -------------------------------------------------------------------------------- 1 | from carto.api_keys import APIKeyManager 2 | 3 | from ...auth import get_default_credentials 4 | from ...utils.utils import create_hash 5 | 6 | 7 | class AuthAPIClient: 8 | """AuthAPIClient class is a client of the CARTO Auth API. 9 | More info: https://carto.com/developers/auth-api/. 10 | 11 | Args: 12 | credentials (:py:class:`Credentials `, optional): 13 | credentials of user account. If not provided, a default credentials 14 | (if set with :py:meth:`set_default_credentials `) 15 | will attempted to be used. 16 | 17 | """ 18 | 19 | def __init__(self, credentials=None): 20 | credentials = credentials or get_default_credentials() 21 | self._api_key_manager = _get_api_key_manager(credentials) 22 | 23 | def create_api_key(self, sources, apis=['sql', 'maps'], permissions=['select'], name=None): 24 | tables = [] 25 | tables_names = [] 26 | 27 | for source in sources: 28 | table_names = source.get_table_names() 29 | for table_name in table_names: 30 | tables.append(_get_table_dict(source.schema(), table_name, permissions)) 31 | tables_names.append(table_name) 32 | 33 | tables_names.sort() 34 | gen_name = 'cartoframes_{}'.format(create_hash(tables_names)) 35 | 36 | if name is None: 37 | name = gen_name 38 | 39 | try: 40 | # Try to create the API key 41 | api_key = self._api_key_manager.create(name, apis, tables) 42 | except Exception as e: 43 | if str(e) == 'Validation failed: Name has already been taken': 44 | # If the API key already exists, use it 45 | api_key = self._api_key_manager.get(name) 46 | if name == gen_name: 47 | # For auto-generated API key, check its grants for the tables 48 | granted_tables = list(map(lambda x: x.name, api_key.grants.tables)) 49 | if not granted_tables or any(table not in granted_tables for table in tables_names): 50 | # If the API key does not grant all the tables (broken API key), 51 | # delete it and create a new one with the same name 52 | api_key.delete() 53 | api_key = self._api_key_manager.create(name, apis, tables) 54 | else: 55 | raise e 56 | 57 | return api_key.name, api_key.token, tables_names 58 | 59 | 60 | def _get_table_dict(schema, name, permissions): 61 | return { 62 | 'schema': schema, 63 | 'name': name, 64 | 'permissions': permissions 65 | } 66 | 67 | 68 | def _get_api_key_manager(credentials): 69 | auth_client = credentials.get_api_key_auth_client() 70 | return APIKeyManager(auth_client) 71 | -------------------------------------------------------------------------------- /cartoframes/data/observatory/__init__.py: -------------------------------------------------------------------------------- 1 | from .catalog.catalog import Catalog 2 | from .catalog.category import Category 3 | from .catalog.country import Country 4 | from .catalog.dataset import Dataset 5 | from .catalog.geography import Geography 6 | from .catalog.provider import Provider 7 | from .catalog.variable import Variable 8 | from .enrichment.enrichment import Enrichment 9 | from .catalog.entity import CatalogEntity, CatalogList 10 | from .catalog.subscriptions import Subscriptions 11 | from .catalog.subscription_info import SubscriptionInfo 12 | 13 | __all__ = [ 14 | 'Catalog', 15 | 'Category', 16 | 'Country', 17 | 'Dataset', 18 | 'Geography', 19 | 'Provider', 20 | 'Variable', 21 | 'Enrichment', 22 | 'Subscriptions', 23 | 'SubscriptionInfo', 24 | 'CatalogEntity', 25 | 'CatalogList', 26 | ] 27 | -------------------------------------------------------------------------------- /cartoframes/data/observatory/catalog/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CartoDB/cartoframes/1f56b75f512da62b7ecd1c07e1021b8552158907/cartoframes/data/observatory/catalog/__init__.py -------------------------------------------------------------------------------- /cartoframes/data/observatory/catalog/provider.py: -------------------------------------------------------------------------------- 1 | from .entity import CatalogEntity 2 | from .repository.provider_repo import get_provider_repo 3 | from .repository.dataset_repo import get_dataset_repo 4 | from .repository.constants import PROVIDER_FILTER 5 | 6 | 7 | class Provider(CatalogEntity): 8 | """This class represents a :py:class:`Provider ` 9 | of datasets and geographies in the :py:class:`Catalog `. 10 | 11 | Examples: 12 | List the available providers in the :py:class:`Catalog ` 13 | in combination with nested filters (categories, countries, etc.) 14 | 15 | >>> providers = Provider.get_all() 16 | 17 | Get a :py:class:`Provider ` from the 18 | :py:class:`Catalog ` given its ID 19 | 20 | >>> catalog = Catalog() 21 | >>> provider = catalog.provider('mrli') 22 | 23 | """ 24 | _entity_repo = get_provider_repo() 25 | 26 | @property 27 | def datasets(self): 28 | """Get the list of datasets related to this provider. 29 | 30 | Returns: 31 | :py:class:`CatalogList ` List of Dataset instances. 32 | 33 | Raises: 34 | CatalogError: if there's a problem when connecting to the catalog or no datasets are found. 35 | 36 | Examples: 37 | >>> provider = Provider.get('mrli') 38 | >>> datasets = provider.datasets 39 | 40 | Same example as above but using nested filters: 41 | 42 | >>> catalog = Catalog() 43 | >>> datasets = catalog.provider('mrli').datasets 44 | 45 | """ 46 | return get_dataset_repo().get_all({PROVIDER_FILTER: self.id}) 47 | 48 | @property 49 | def name(self): 50 | """Name of this provider.""" 51 | return self.data['name'] 52 | -------------------------------------------------------------------------------- /cartoframes/data/observatory/catalog/repository/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CartoDB/cartoframes/1f56b75f512da62b7ecd1c07e1021b8552158907/cartoframes/data/observatory/catalog/repository/__init__.py -------------------------------------------------------------------------------- /cartoframes/data/observatory/catalog/repository/category_repo.py: -------------------------------------------------------------------------------- 1 | from .constants import COUNTRY_FILTER, PROVIDER_FILTER, PUBLIC_FILTER 2 | from .entity_repo import EntityRepository 3 | 4 | 5 | _CATEGORY_ID_FIELD = 'id' 6 | _ALLOWED_FILTERS = [COUNTRY_FILTER, PROVIDER_FILTER, PUBLIC_FILTER] 7 | 8 | 9 | def get_category_repo(): 10 | return _REPO 11 | 12 | 13 | class CategoryRepository(EntityRepository): 14 | 15 | def __init__(self): 16 | super(CategoryRepository, self).__init__(_CATEGORY_ID_FIELD, _ALLOWED_FILTERS) 17 | 18 | @classmethod 19 | def _get_entity_class(cls): 20 | from cartoframes.data.observatory.catalog.category import Category 21 | return Category 22 | 23 | def _get_rows(self, filters=None): 24 | return self.client.get_categories(filters) 25 | 26 | def _map_row(self, row): 27 | return { 28 | 'id': self._normalize_field(row, self.id_field), 29 | 'name': self._normalize_field(row, 'name') 30 | } 31 | 32 | 33 | _REPO = CategoryRepository() 34 | -------------------------------------------------------------------------------- /cartoframes/data/observatory/catalog/repository/constants.py: -------------------------------------------------------------------------------- 1 | CATEGORY_FILTER = 'category' 2 | COUNTRY_FILTER = 'country' 3 | DATASET_FILTER = 'dataset' 4 | GEOGRAPHY_FILTER = 'geography' 5 | PROVIDER_FILTER = 'provider' 6 | PUBLIC_FILTER = 'public' 7 | VARIABLE_FILTER = 'variable' 8 | VARIABLE_GROUP_FILTER = 'variable_group' 9 | GLOBAL_COUNTRY_FILTER = 'glo' 10 | -------------------------------------------------------------------------------- /cartoframes/data/observatory/catalog/repository/country_repo.py: -------------------------------------------------------------------------------- 1 | from .constants import CATEGORY_FILTER, PROVIDER_FILTER, PUBLIC_FILTER 2 | from .entity_repo import EntityRepository 3 | 4 | 5 | _COUNTRY_ID_FIELD = 'id' 6 | _ALLOWED_FILTERS = [CATEGORY_FILTER, PROVIDER_FILTER, PUBLIC_FILTER] 7 | 8 | 9 | def get_country_repo(): 10 | return _REPO 11 | 12 | 13 | class CountryRepository(EntityRepository): 14 | 15 | def __init__(self): 16 | super(CountryRepository, self).__init__(_COUNTRY_ID_FIELD, _ALLOWED_FILTERS) 17 | 18 | @classmethod 19 | def _get_entity_class(cls): 20 | from cartoframes.data.observatory.catalog.country import Country 21 | return Country 22 | 23 | def _get_rows(self, filters=None): 24 | return self.client.get_countries(filters) 25 | 26 | def _map_row(self, row): 27 | return { 28 | 'id': self._normalize_field(row, 'id'), 29 | 'name': self._normalize_field(row, 'name') 30 | } 31 | 32 | 33 | _REPO = CountryRepository() 34 | -------------------------------------------------------------------------------- /cartoframes/data/observatory/catalog/repository/dataset_repo.py: -------------------------------------------------------------------------------- 1 | from .constants import CATEGORY_FILTER, COUNTRY_FILTER, GEOGRAPHY_FILTER, PROVIDER_FILTER, PUBLIC_FILTER 2 | from .entity_repo import EntityRepository 3 | 4 | DATASET_TYPE = 'dataset' 5 | 6 | _DATASET_ID_FIELD = 'id' 7 | _DATASET_SLUG_FIELD = 'slug' 8 | _ALLOWED_FILTERS = [CATEGORY_FILTER, COUNTRY_FILTER, GEOGRAPHY_FILTER, PROVIDER_FILTER, PUBLIC_FILTER] 9 | 10 | 11 | def get_dataset_repo(): 12 | return _REPO 13 | 14 | 15 | class DatasetRepository(EntityRepository): 16 | 17 | def __init__(self): 18 | super(DatasetRepository, self).__init__(_DATASET_ID_FIELD, _ALLOWED_FILTERS, _DATASET_SLUG_FIELD) 19 | 20 | def get_all(self, filters=None, credentials=None): 21 | if credentials is not None: 22 | filters = self._add_subscription_ids(filters, credentials, DATASET_TYPE) 23 | if filters is None: 24 | return [] 25 | 26 | # Using user credentials to fetch entities 27 | self.client.set_user_credentials(credentials) 28 | entities = self._get_filtered_entities(filters) 29 | self.client.reset_user_credentials() 30 | return entities 31 | 32 | @classmethod 33 | def _get_entity_class(cls): 34 | from cartoframes.data.observatory.catalog.dataset import Dataset 35 | return Dataset 36 | 37 | def _get_rows(self, filters=None): 38 | return self.client.get_datasets(filters) 39 | 40 | def _map_row(self, row): 41 | return { 42 | 'slug': self._normalize_field(row, 'slug'), 43 | 'name': self._normalize_field(row, 'name'), 44 | 'description': self._normalize_field(row, 'description'), 45 | 'category_id': self._normalize_field(row, 'category_id'), 46 | 'country_id': self._normalize_field(row, 'country_id'), 47 | 'data_source_id': self._normalize_field(row, 'data_source_id'), 48 | 'provider_id': self._normalize_field(row, 'provider_id'), 49 | 'geography_name': self._normalize_field(row, 'geography_name'), 50 | 'geography_description': self._normalize_field(row, 'geography_description'), 51 | 'temporal_aggregation': self._normalize_field(row, 'temporal_aggregation'), 52 | 'time_coverage': self._normalize_field(row, 'time_coverage'), 53 | 'update_frequency': self._normalize_field(row, 'update_frequency'), 54 | 'is_public_data': self._normalize_field(row, 'is_public_data'), 55 | 'lang': self._normalize_field(row, 'lang'), 56 | 'version': self._normalize_field(row, 'version'), 57 | 'category_name': self._normalize_field(row, 'category_name'), 58 | 'provider_name': self._normalize_field(row, 'provider_name'), 59 | 'summary_json': self._normalize_field(row, 'summary_json'), 60 | 'geography_id': self._normalize_field(row, 'geography_id'), 61 | 'id': self._normalize_field(row, self.id_field) 62 | } 63 | 64 | 65 | _REPO = DatasetRepository() 66 | -------------------------------------------------------------------------------- /cartoframes/data/observatory/catalog/repository/geography_repo.py: -------------------------------------------------------------------------------- 1 | from geopandas import GeoDataFrame 2 | 3 | from .....utils.geom_utils import set_geometry 4 | from .constants import COUNTRY_FILTER, CATEGORY_FILTER, PROVIDER_FILTER, PUBLIC_FILTER 5 | from .entity_repo import EntityRepository 6 | 7 | GEOGRAPHY_TYPE = 'geography' 8 | 9 | _GEOGRAPHY_ID_FIELD = 'id' 10 | _GEOGRAPHY_SLUG_FIELD = 'slug' 11 | _ALLOWED_FILTERS = [COUNTRY_FILTER, CATEGORY_FILTER, PROVIDER_FILTER, PUBLIC_FILTER] 12 | 13 | 14 | def get_geography_repo(): 15 | return _REPO 16 | 17 | 18 | class GeographyRepository(EntityRepository): 19 | 20 | def __init__(self): 21 | super(GeographyRepository, self).__init__(_GEOGRAPHY_ID_FIELD, _ALLOWED_FILTERS, _GEOGRAPHY_SLUG_FIELD) 22 | 23 | def get_all(self, filters=None, credentials=None): 24 | if credentials is not None: 25 | filters = self._add_subscription_ids(filters, credentials, GEOGRAPHY_TYPE) 26 | if filters is None: 27 | return [] 28 | 29 | # Using user credentials to fetch entities 30 | self.client.set_user_credentials(credentials) 31 | entities = self._get_filtered_entities(filters) 32 | self.client.reset_user_credentials() 33 | return entities 34 | 35 | @classmethod 36 | def _get_entity_class(cls): 37 | from cartoframes.data.observatory.catalog.geography import Geography 38 | return Geography 39 | 40 | def _get_rows(self, filters=None): 41 | return self.client.get_geographies(filters) 42 | 43 | def _map_row(self, row): 44 | return { 45 | 'slug': self._normalize_field(row, 'slug'), 46 | 'name': self._normalize_field(row, 'name'), 47 | 'description': self._normalize_field(row, 'description'), 48 | 'country_id': self._normalize_field(row, 'country_id'), 49 | 'provider_id': self._normalize_field(row, 'provider_id'), 50 | 'geom_type': self._normalize_field(row, 'geom_type'), 51 | 'geom_coverage': self._normalize_field(row, 'geom_coverage'), 52 | 'update_frequency': self._normalize_field(row, 'update_frequency'), 53 | 'is_public_data': self._normalize_field(row, 'is_public_data'), 54 | 'lang': self._normalize_field(row, 'lang'), 55 | 'version': self._normalize_field(row, 'version'), 56 | 'provider_name': self._normalize_field(row, 'provider_name'), 57 | 'summary_json': self._normalize_field(row, 'summary_json'), 58 | 'id': self._normalize_field(row, self.id_field) 59 | } 60 | 61 | def get_geographies_gdf(self): 62 | data = self.client.get_geographies({'get_geoms_coverage': True}) 63 | gdf = GeoDataFrame(data, crs='epsg:4326') 64 | set_geometry(gdf, 'geom_coverage', inplace=True) 65 | return gdf 66 | 67 | 68 | _REPO = GeographyRepository() 69 | -------------------------------------------------------------------------------- /cartoframes/data/observatory/catalog/repository/provider_repo.py: -------------------------------------------------------------------------------- 1 | from .constants import CATEGORY_FILTER, COUNTRY_FILTER, PUBLIC_FILTER 2 | from .entity_repo import EntityRepository 3 | 4 | 5 | _PROVIDER_ID_FIELD = 'id' 6 | _ALLOWED_FILTERS = [CATEGORY_FILTER, COUNTRY_FILTER, PUBLIC_FILTER] 7 | 8 | 9 | def get_provider_repo(): 10 | return _REPO 11 | 12 | 13 | class ProviderRepository(EntityRepository): 14 | 15 | def __init__(self): 16 | super(ProviderRepository, self).__init__(_PROVIDER_ID_FIELD, _ALLOWED_FILTERS) 17 | 18 | @classmethod 19 | def _get_entity_class(cls): 20 | from cartoframes.data.observatory.catalog.provider import Provider 21 | return Provider 22 | 23 | def _get_rows(self, filters=None): 24 | return self.client.get_providers(filters) 25 | 26 | def _map_row(self, row): 27 | return { 28 | 'id': self._normalize_field(row, self.id_field), 29 | 'name': self._normalize_field(row, 'name') 30 | } 31 | 32 | 33 | _REPO = ProviderRepository() 34 | -------------------------------------------------------------------------------- /cartoframes/data/observatory/catalog/repository/variable_group_repo.py: -------------------------------------------------------------------------------- 1 | from .constants import DATASET_FILTER 2 | from .entity_repo import EntityRepository 3 | 4 | 5 | _VARIABLE_GROUP_ID_FIELD = 'id' 6 | _VARIABLE_GROUP_SLUG_FIELD = 'slug' 7 | _ALLOWED_FILTERS = [DATASET_FILTER] 8 | 9 | 10 | def get_variable_group_repo(): 11 | return _REPO 12 | 13 | 14 | class VariableGroupRepository(EntityRepository): 15 | 16 | def __init__(self): 17 | super(VariableGroupRepository, self).__init__(_VARIABLE_GROUP_ID_FIELD, _ALLOWED_FILTERS, 18 | _VARIABLE_GROUP_SLUG_FIELD) 19 | 20 | @classmethod 21 | def _get_entity_class(cls): 22 | from cartoframes.data.observatory.catalog.variable_group import VariableGroup 23 | return VariableGroup 24 | 25 | def _get_rows(self, filters=None): 26 | return self.client.get_variables_groups(filters) 27 | 28 | def _map_row(self, row): 29 | return { 30 | 'id': self._normalize_field(row, self.id_field), 31 | 'slug': self._normalize_field(row, 'slug'), 32 | 'name': self._normalize_field(row, 'name'), 33 | 'dataset_id': self._normalize_field(row, 'dataset_id') 34 | } 35 | 36 | 37 | _REPO = VariableGroupRepository() 38 | -------------------------------------------------------------------------------- /cartoframes/data/observatory/catalog/repository/variable_repo.py: -------------------------------------------------------------------------------- 1 | from .constants import DATASET_FILTER, VARIABLE_GROUP_FILTER 2 | from .entity_repo import EntityRepository 3 | 4 | 5 | _VARIABLE_ID_FIELD = 'id' 6 | _VARIABLE_SLUG_FIELD = 'slug' 7 | _ALLOWED_DATASETS = [DATASET_FILTER, VARIABLE_GROUP_FILTER] 8 | 9 | 10 | def get_variable_repo(): 11 | return _REPO 12 | 13 | 14 | class VariableRepository(EntityRepository): 15 | 16 | def __init__(self): 17 | super(VariableRepository, self).__init__(_VARIABLE_ID_FIELD, _ALLOWED_DATASETS, _VARIABLE_SLUG_FIELD) 18 | 19 | @classmethod 20 | def _get_entity_class(cls): 21 | from cartoframes.data.observatory.catalog.variable import Variable 22 | return Variable 23 | 24 | def _get_rows(self, filters=None): 25 | return self.client.get_variables(filters) 26 | 27 | def _map_row(self, row): 28 | return { 29 | 'slug': self._normalize_field(row, 'slug'), 30 | 'name': self._normalize_field(row, 'name'), 31 | 'description': self._normalize_field(row, 'description'), 32 | 'db_type': self._normalize_field(row, 'db_type'), 33 | 'agg_method': self._normalize_field(row, 'agg_method'), 34 | 'summary_json': self._normalize_field(row, 'summary_json'), 35 | 'column_name': self._normalize_field(row, 'column_name'), 36 | 'variable_group_id': self._normalize_field(row, 'variable_group_id'), 37 | 'dataset_id': self._normalize_field(row, 'dataset_id'), 38 | 'id': self._normalize_field(row, self.id_field), 39 | } 40 | 41 | 42 | _REPO = VariableRepository() 43 | -------------------------------------------------------------------------------- /cartoframes/data/observatory/catalog/subscriptions.py: -------------------------------------------------------------------------------- 1 | 2 | from json.decoder import JSONDecodeError 3 | from carto.do_subscriptions import DOSubscriptionManager, DOSubscriptionCreationManager 4 | 5 | 6 | class Subscriptions: 7 | """This class is used to list the datasets and geographies you have acquired a subscription (or valid license) for. 8 | 9 | This class won't show any dataset or geography tagged in the catalog as `is_public_data` since those data do not 10 | require a subscription. 11 | 12 | """ 13 | def __init__(self, credentials): 14 | self._credentials = credentials 15 | self._filters = {'only_products': True} 16 | self._datasets = None 17 | self._geographies = None 18 | 19 | def __repr__(self): 20 | return 'Datasets: {0}\nGeographies: {1}'.format( 21 | self.datasets, 22 | self.geographies 23 | ) 24 | 25 | @property 26 | def datasets(self): 27 | """List of :obj:`Dataset` you have a subscription for. 28 | 29 | Raises: 30 | CatalogError: if there's a problem when connecting to the catalog. 31 | 32 | """ 33 | if self._datasets is None: 34 | from .dataset import Dataset 35 | self._datasets = Dataset.get_all(self._filters, self._credentials) 36 | return self._datasets 37 | 38 | @property 39 | def geographies(self): 40 | """List of :obj:`Geography` you have a subscription for. 41 | 42 | Raises: 43 | CatalogError: if there's a problem when connecting to the catalog. 44 | 45 | """ 46 | if self._geographies is None: 47 | from .geography import Geography 48 | self._geographies = Geography.get_all(self._filters, self._credentials) 49 | return self._geographies 50 | 51 | 52 | def get_subscription_ids(credentials, stype=None): 53 | subs = fetch_subscriptions(credentials) 54 | return [s.id for s in subs if (stype is None or stype == s.type) and s.status == 'active'] 55 | 56 | 57 | def fetch_subscriptions(credentials): 58 | if credentials: 59 | api_key_auth_client = credentials.get_api_key_auth_client() 60 | do_manager = DOSubscriptionManager(api_key_auth_client) 61 | if do_manager is not None: 62 | try: 63 | return do_manager.all() 64 | except JSONDecodeError: 65 | return [] 66 | return [] 67 | 68 | 69 | def trigger_subscription(id, type, credentials): 70 | api_key_auth_client = credentials.get_api_key_auth_client() 71 | do_manager = DOSubscriptionCreationManager(api_key_auth_client) 72 | return do_manager.create(id=id, type=type) 73 | -------------------------------------------------------------------------------- /cartoframes/data/observatory/catalog/variable_group.py: -------------------------------------------------------------------------------- 1 | from .entity import CatalogEntity 2 | from .repository.variable_group_repo import get_variable_group_repo 3 | from .repository.variable_repo import get_variable_repo 4 | from .repository.constants import VARIABLE_GROUP_FILTER 5 | 6 | 7 | class VariableGroup(CatalogEntity): 8 | 9 | _entity_repo = get_variable_group_repo() 10 | 11 | @property 12 | def variables(self): 13 | """Get the list of variables included in this variable group. 14 | 15 | Returns: 16 | :py:class:`CatalogList ` List of Variable instances. 17 | 18 | """ 19 | return get_variable_repo().get_all({VARIABLE_GROUP_FILTER: self.id}) 20 | 21 | @property 22 | def name(self): 23 | """Name of this variable group.""" 24 | return self.data['name'] 25 | 26 | @property 27 | def dataset(self): 28 | """ID of the dataset related to this variable group.""" 29 | return self.data['dataset_id'] 30 | -------------------------------------------------------------------------------- /cartoframes/data/observatory/enrichment/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CartoDB/cartoframes/1f56b75f512da62b7ecd1c07e1021b8552158907/cartoframes/data/observatory/enrichment/__init__.py -------------------------------------------------------------------------------- /cartoframes/data/services/__init__.py: -------------------------------------------------------------------------------- 1 | from .geocoding import Geocoding 2 | from .isolines import Isolines 3 | 4 | __all__ = [ 5 | 'Geocoding', 6 | 'Isolines' 7 | ] 8 | -------------------------------------------------------------------------------- /cartoframes/data/services/service.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | 3 | from ...io.managers.context_manager import ContextManager 4 | from ...utils.utils import create_tmp_name 5 | 6 | SERVICE_KEYS = ('hires_geocoder', 'isolines') 7 | QUOTA_INFO_KEYS = ('monthly_quota', 'used_quota', 'soft_limit', 'provider') 8 | 9 | 10 | Result = namedtuple('Result', ['data', 'metadata']) 11 | 12 | 13 | class Service: 14 | 15 | def __init__(self, credentials=None, quota_service=None): 16 | self._context_manager = ContextManager(credentials) 17 | self._credentials = self._context_manager.credentials 18 | self._quota_service = quota_service 19 | if self._quota_service not in SERVICE_KEYS: 20 | raise ValueError('Invalid service "{}" valid services are: {}'.format( 21 | self._quota_service, 22 | ', '.join(SERVICE_KEYS) 23 | )) 24 | 25 | def _quota_info(self, service): 26 | result = self._execute_query('SELECT * FROM cdb_service_quota_info()') 27 | for row in result.get('rows'): 28 | if row.get('service') == service: 29 | return {k: row.get(k) for k in QUOTA_INFO_KEYS} 30 | return None 31 | 32 | def provider(self): 33 | info = self._quota_info(self._quota_service) 34 | return info and info.get('provider') 35 | 36 | def used_quota(self): 37 | info = self._quota_info(self._quota_service) 38 | return info and info.get('used_quota') 39 | 40 | def available_quota(self): 41 | info = self._quota_info(self._quota_service) 42 | return info and (info.get('monthly_quota') - info.get('used_quota')) 43 | 44 | def result(self, data, metadata=None): 45 | return Result(data=data, metadata=metadata) 46 | 47 | def _schema(self): 48 | return self._context_manager.get_schema() 49 | 50 | def _new_temporary_table_name(self, base=None): 51 | return create_tmp_name(base=base or 'table') 52 | 53 | def _execute_query(self, query): 54 | return self._context_manager.execute_query(query) 55 | 56 | def _execute_long_running_query(self, query): 57 | return self._context_manager.execute_long_running_query(query) 58 | -------------------------------------------------------------------------------- /cartoframes/data/services/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from . import geocoding_constants 2 | from . import geocoding_utils 3 | from .table_geocoding_lock import TableGeocodingLock 4 | 5 | __all__ = [ 6 | 'geocoding_constants', 7 | 'geocoding_utils', 8 | 'TableGeocodingLock' 9 | ] 10 | -------------------------------------------------------------------------------- /cartoframes/data/services/utils/geocoding_constants.py: -------------------------------------------------------------------------------- 1 | __all__ = [ 2 | 'HASH_COLUMN', 3 | 'DEFAULT_STATUS', 4 | 'QUOTA_SERVICE', 5 | 'STATUS_FIELDS', 6 | 'STATUS_FIELDS_KEYS', 7 | 'GEOCODE_COLUMN_KEY', 8 | 'GEOCODE_VALUE_KEY', 9 | 'VALID_GEOCODE_KEYS' 10 | ] 11 | 12 | HASH_COLUMN = 'carto_geocode_hash' 13 | 14 | DEFAULT_STATUS = {'gc_status_rel': 'relevance'} 15 | 16 | QUOTA_SERVICE = 'hires_geocoder' 17 | 18 | STATUS_FIELDS = { 19 | 'relevance': ('numeric', "(_g.metadata->>'relevance')::numeric"), 20 | 'precision': ('text', "_g.metadata->>'precision'"), 21 | 'match_types': ('text', "cdb_dataservices_client.cdb_jsonb_array_casttext(_g.metadata->>'match_types')"), 22 | '*': ('jsonb', "_g.metadata") 23 | } 24 | 25 | STATUS_FIELDS_KEYS = sorted(STATUS_FIELDS.keys()) 26 | 27 | GEOCODE_COLUMN_KEY = 'column' 28 | 29 | GEOCODE_VALUE_KEY = 'value' 30 | 31 | VALID_GEOCODE_KEYS = [GEOCODE_COLUMN_KEY, GEOCODE_VALUE_KEY] 32 | -------------------------------------------------------------------------------- /cartoframes/data/services/utils/table_geocoding_lock.py: -------------------------------------------------------------------------------- 1 | from . import geocoding_utils 2 | 3 | 4 | class TableGeocodingLock: 5 | def __init__(self, execute_query, table_name): 6 | self._execute_query = execute_query 7 | text_id = 'carto-geocoder-{table_name}'.format(table_name=table_name) 8 | self.lock_id = geocoding_utils.hash_as_big_int(text_id) 9 | self.locked = False 10 | 11 | def __enter__(self): 12 | self.locked = geocoding_utils.lock(self._execute_query, self.lock_id) 13 | return self.locked 14 | 15 | def __exit__(self, type, value, traceback): 16 | if self.locked: 17 | geocoding_utils.unlock(self._execute_query, self.lock_id) 18 | -------------------------------------------------------------------------------- /cartoframes/exceptions.py: -------------------------------------------------------------------------------- 1 | 2 | class DOError(Exception): 3 | """This exception is raised when a problem is encountered while using DO functions. 4 | 5 | """ 6 | def __init__(self, message): 7 | super(DOError, self).__init__(message) 8 | 9 | 10 | class CatalogError(DOError): 11 | """This exception is raised when a problem is encountered while using catalog functions. 12 | 13 | """ 14 | def __init__(self, message): 15 | super(CatalogError, self).__init__(message) 16 | 17 | 18 | class EnrichmentError(DOError): 19 | """This exception is raised when a problem is encountered while using enrichment functions. 20 | 21 | """ 22 | def __init__(self, message): 23 | super(EnrichmentError, self).__init__(message) 24 | 25 | 26 | class PublishError(Exception): 27 | """This exception is raised when a problem is encountered while publishing visualizations. 28 | 29 | """ 30 | def __init__(self, message): 31 | super(PublishError, self).__init__(message) 32 | -------------------------------------------------------------------------------- /cartoframes/io/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CartoDB/cartoframes/1f56b75f512da62b7ecd1c07e1021b8552158907/cartoframes/io/__init__.py -------------------------------------------------------------------------------- /cartoframes/io/dataset_info.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from carto.datasets import DatasetManager 4 | 5 | from warnings import filterwarnings 6 | filterwarnings('ignore', category=FutureWarning, module='carto') 7 | 8 | 9 | class DatasetInfo: 10 | PRIVACY_PRIVATE = 'PRIVATE' 11 | """Dataset privacy for datasets that are private""" 12 | 13 | PRIVACY_PUBLIC = 'PUBLIC' 14 | """Dataset privacy for datasets that are public""" 15 | 16 | PRIVACY_LINK = 'LINK' 17 | """Dataset privacy for datasets that are accessible by link""" 18 | 19 | def __init__(self, auth_client, table_name): 20 | self._metadata = self._get_metadata(auth_client, table_name) 21 | self._privacy = self._metadata.privacy if self._metadata is not None else None 22 | 23 | @property 24 | def privacy(self): 25 | return self._privacy 26 | 27 | def update_privacy(self, privacy=None): 28 | if privacy and self._validate_privacy(privacy): 29 | self._privacy = privacy.upper() 30 | self._save_metadata() 31 | 32 | def _get_metadata(self, auth_client, table_name, retries=4, retry_wait_time=1): 33 | ds_manager = DatasetManager(auth_client) 34 | try: 35 | return ds_manager.get(table_name) 36 | except Exception as e: 37 | if type(e).__name__ == 'NotFoundException' and retries > 0: 38 | time.sleep(retry_wait_time) 39 | self._get_metadata(auth_client=auth_client, table_name=table_name, 40 | retries=retries-1, retry_wait_time=retry_wait_time*2) 41 | else: 42 | raise Exception('We could not get the table metadata. ' 43 | 'Please, try again in a few seconds or contact support for help') 44 | 45 | def _save_metadata(self): 46 | self._metadata.privacy = self._privacy 47 | self._metadata.save() 48 | 49 | def _validate_privacy(self, privacy): 50 | privacy = privacy.upper() 51 | if privacy not in [self.PRIVACY_PRIVATE, self.PRIVACY_PUBLIC, self.PRIVACY_LINK]: 52 | raise ValueError('Wrong privacy. The privacy: {p} is not valid. You can use: {o1}, {o2}, {o3}'.format( 53 | p=privacy, o1=self.PRIVACY_PRIVATE, o2=self.PRIVACY_PUBLIC, o3=self.PRIVACY_LINK)) 54 | 55 | if privacy != self._privacy: 56 | return True 57 | 58 | return False 59 | -------------------------------------------------------------------------------- /cartoframes/io/managers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CartoDB/cartoframes/1f56b75f512da62b7ecd1c07e1021b8552158907/cartoframes/io/managers/__init__.py -------------------------------------------------------------------------------- /cartoframes/io/managers/source_manager.py: -------------------------------------------------------------------------------- 1 | from pandas import DataFrame 2 | from geopandas import GeoDataFrame 3 | 4 | from .context_manager import ContextManager 5 | from ...utils.utils import is_sql_query 6 | from ...utils.geom_utils import has_geometry 7 | 8 | 9 | class SourceManager: 10 | 11 | def __init__(self, source, credentials): 12 | self._source = source 13 | if isinstance(source, str): 14 | # Table, SQL query 15 | self._remote_data = True 16 | self._context_manager = ContextManager(credentials) 17 | self._query = self._context_manager.compute_query(source) 18 | elif isinstance(source, DataFrame): 19 | # DataFrame, GeoDataFrame 20 | self._remote_data = False 21 | self._gdf = GeoDataFrame(source, copy=True) 22 | if has_geometry(source): 23 | self._gdf.set_geometry(source.geometry.name, inplace=True) 24 | else: 25 | raise ValueError('Wrong source input. Valid values are str and DataFrame.') 26 | 27 | @property 28 | def gdf(self): 29 | return self._gdf 30 | 31 | def is_remote(self): 32 | return self._remote_data 33 | 34 | def is_local(self): 35 | return not self._remote_data 36 | 37 | def is_table(self): 38 | return isinstance(self._source, str) and not self.is_query() 39 | 40 | def is_query(self): 41 | return is_sql_query(self._source) 42 | 43 | def is_dataframe(self): 44 | return isinstance(self._source, DataFrame) 45 | 46 | def get_query(self): 47 | if self.is_remote(): 48 | return self._query 49 | 50 | def get_num_rows(self): 51 | if self.is_remote(): 52 | return self._context_manager.get_num_rows(self._query) 53 | else: 54 | return len(self._gdf) 55 | 56 | def get_column_names(self): 57 | if self.is_remote(): 58 | return self._context_manager.get_column_names(self._query) 59 | else: 60 | return list(self._gdf.columns) 61 | -------------------------------------------------------------------------------- /cartoframes/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .logger import set_log_level 2 | from .geom_utils import decode_geometry 3 | from .metrics import setup_metrics 4 | 5 | __all__ = [ 6 | 'setup_metrics', 7 | 'set_log_level', 8 | 'decode_geometry' 9 | ] 10 | -------------------------------------------------------------------------------- /cartoframes/utils/logger.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import logging 3 | 4 | 5 | def init_logger(formatter): 6 | handler = logging.StreamHandler(sys.stdout) 7 | handler.setFormatter(logging.Formatter(formatter)) 8 | logging.basicConfig( 9 | level=logging.INFO, 10 | handlers=[handler]) 11 | return handler, logging.getLogger('CARTOframes') 12 | 13 | 14 | handler, log = init_logger('%(message)s') 15 | 16 | 17 | def set_log_level(level): 18 | """Set the level of the log in the library. 19 | 20 | Args: 21 | level (str): log level name. By default it's set to "info". Valid log levels are: 22 | "critical", "error", "warning", "info", "debug", "notset". 23 | 24 | """ 25 | levels = { 26 | 'critical': logging.CRITICAL, 27 | 'error': logging.ERROR, 28 | 'warning': logging.WARNING, 29 | 'info': logging.INFO, 30 | 'debug': logging.DEBUG, 31 | 'notset': logging.NOTSET 32 | } 33 | 34 | level = level.lower() 35 | if level not in levels: 36 | return ValueError('Wrong log level. Valid log levels are: critical, error, warning, info, debug, notset.') 37 | 38 | level = levels[level] 39 | 40 | if level == logging.DEBUG: 41 | handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')) 42 | else: 43 | handler.setFormatter(logging.Formatter('%(message)s')) 44 | 45 | log.setLevel(level) 46 | -------------------------------------------------------------------------------- /cartoframes/viz/__init__.py: -------------------------------------------------------------------------------- 1 | from .map import Map 2 | from .layer import Layer 3 | from .source import Source 4 | from .layout import Layout 5 | 6 | from .themes import Themes as themes 7 | from .basemaps import Basemaps as basemaps 8 | from .palettes import Palettes as palettes 9 | 10 | from .styles import animation_style 11 | from .styles import basic_style 12 | from .styles import color_bins_style 13 | from .styles import color_category_style 14 | from .styles import color_continuous_style 15 | from .styles import cluster_size_style 16 | from .styles import isolines_style 17 | from .styles import size_bins_style 18 | from .styles import size_category_style 19 | from .styles import size_continuous_style 20 | 21 | from .legends import basic_legend 22 | from .legends import color_bins_legend 23 | from .legends import color_category_legend 24 | from .legends import color_continuous_legend 25 | from .legends import size_bins_legend 26 | from .legends import size_category_legend 27 | from .legends import size_continuous_legend 28 | from .legends import default_legend 29 | 30 | from .widgets import basic_widget 31 | from .widgets import animation_widget 32 | from .widgets import category_widget 33 | from .widgets import formula_widget 34 | from .widgets import histogram_widget 35 | from .widgets import time_series_widget 36 | from .widgets import default_widget 37 | 38 | from .popups import popup_element 39 | from .popups import default_popup_element 40 | 41 | from .kuviz import all_publications 42 | from .kuviz import delete_publication 43 | 44 | __all__ = [ 45 | 'Map', 46 | 'Layer', 47 | 'Source', 48 | 'Layout', 49 | 'basemaps', 50 | 'themes', 51 | 'palettes', 52 | 53 | 'animation_style', 54 | 'basic_style', 55 | 'color_bins_style', 56 | 'color_category_style', 57 | 'color_continuous_style', 58 | 'cluster_size_style', 59 | 'isolines_style', 60 | 'size_bins_style', 61 | 'size_category_style', 62 | 'size_continuous_style', 63 | 64 | 'basic_legend', 65 | 'color_bins_legend', 66 | 'color_category_legend', 67 | 'color_continuous_legend', 68 | 'size_bins_legend', 69 | 'size_category_legend', 70 | 'size_continuous_legend', 71 | 'default_legend', 72 | 73 | 'animation_widget', 74 | 'basic_widget', 75 | 'category_widget', 76 | 'formula_widget', 77 | 'histogram_widget', 78 | 'time_series_widget', 79 | 'default_widget', 80 | 81 | 'popup_element', 82 | 'default_popup_element', 83 | 84 | 'all_publications', 85 | 'delete_publication' 86 | ] 87 | -------------------------------------------------------------------------------- /cartoframes/viz/basemaps.py: -------------------------------------------------------------------------------- 1 | class Basemaps: # pylint: disable=too-few-public-methods 2 | """Supported CARTO basemaps. Read more about the styles in the 3 | `CARTO Basemaps repository `__. 4 | 5 | Example: 6 | >>> Map(basemap=basemaps.positron) 7 | 8 | """ 9 | positron = 'Positron' 10 | darkmatter = 'DarkMatter' 11 | voyager = 'Voyager' 12 | -------------------------------------------------------------------------------- /cartoframes/viz/defaults.py: -------------------------------------------------------------------------------- 1 | STYLE = { 2 | 'point': { 3 | 'color': 'hex("#EE4D5A")', 4 | 'width': 'ramp(linear(zoom(),0,18),[2,10])', 5 | 'strokeWidth': 'ramp(linear(zoom(),0,18),[0,1])', 6 | 'strokeColor': 'opacity(#222,ramp(linear(zoom(),0,18),[0,0.6]))' 7 | }, 8 | 'line': { 9 | 'color': 'hex("#4CC8A3")', 10 | 'width': 'ramp(linear(zoom(),0,18),[0.5,4])' 11 | }, 12 | 'polygon': { 13 | 'color': 'hex("#826DBA")', 14 | 'strokeWidth': 'ramp(linear(zoom(),2,18),[0.5,1])', 15 | 'strokeColor': 'opacity(#2c2c2c,ramp(linear(zoom(),2,18),[0.2,0.6]))' 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /cartoframes/viz/html/__init__.py: -------------------------------------------------------------------------------- 1 | from .html_layout import HTMLLayout 2 | from .html_map import HTMLMap 3 | 4 | __all__ = [ 5 | 'HTMLMap', 6 | 'HTMLLayout' 7 | ] 8 | -------------------------------------------------------------------------------- /cartoframes/viz/html/utils.py: -------------------------------------------------------------------------------- 1 | """general utility functions for HTML Map templates""" 2 | 3 | 4 | def safe_quotes(text, escape_single_quotes=False): 5 | """htmlify string""" 6 | if isinstance(text, str): 7 | safe_text = text.replace('"', """) 8 | if escape_single_quotes: 9 | safe_text = safe_text.replace("'", "\'") 10 | return safe_text.replace('True', 'true') 11 | return text 12 | 13 | 14 | def quote_filter(value): 15 | return safe_quotes(value.unescape()) 16 | 17 | 18 | def iframe_size_filter(value): 19 | if isinstance(value, str): 20 | return value 21 | 22 | return '%spx;' % value 23 | 24 | 25 | def clear_none_filter(value): 26 | return dict(filter(lambda item: item[1] is not None, value.items())) 27 | -------------------------------------------------------------------------------- /cartoframes/viz/legend_list.py: -------------------------------------------------------------------------------- 1 | from .legend import Legend 2 | from .constants import SINGLE_LEGEND 3 | 4 | 5 | class LegendList: 6 | """LegendList 7 | Args: 8 | legends (list, Legend): List of legends for a layer. 9 | default_legend (Legend, optional): Default legend for a layer. 10 | geom_type (str, optional): The type of the geometry. 11 | 12 | """ 13 | 14 | def __init__(self, legends=None, default_legend=None, geom_type=None): 15 | self._legends = self._init_legends(legends, default_legend, geom_type) 16 | 17 | def _init_legends(self, legends, default_legend, layer_type): 18 | if isinstance(legends, list): 19 | legend_list = [] 20 | for legend in legends: 21 | if isinstance(legend, Legend): 22 | if legend._type == 'basic': 23 | legend._type = _get_simple_legend_geometry_type(layer_type) 24 | elif legend._type == 'default' and default_legend: 25 | legend._type = default_legend._type 26 | legend._prop = default_legend._prop 27 | legend_list.append(legend) 28 | else: 29 | raise ValueError('Legends list contains invalid elements') 30 | return legend_list 31 | else: 32 | return [] 33 | 34 | def get_info(self): 35 | legends_info = [] 36 | for legend in self._legends: 37 | if legend: 38 | legends_info.append(legend.get_info()) 39 | 40 | return legends_info 41 | 42 | 43 | def _get_simple_legend_geometry_type(layer_type): 44 | return SINGLE_LEGEND + '-' + layer_type 45 | -------------------------------------------------------------------------------- /cartoframes/viz/legends/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Legend helpers to generate legends faster. 3 | """ 4 | 5 | from .basic_legend import basic_legend 6 | from .color_bins_legend import color_bins_legend 7 | from .color_category_legend import color_category_legend 8 | from .color_continuous_legend import color_continuous_legend 9 | from .size_bins_legend import size_bins_legend 10 | from .size_category_legend import size_category_legend 11 | from .size_continuous_legend import size_continuous_legend 12 | from .default_legend import default_legend 13 | 14 | __all__ = [ 15 | 'basic_legend', 16 | 'color_bins_legend', 17 | 'color_category_legend', 18 | 'color_continuous_legend', 19 | 'size_bins_legend', 20 | 'size_category_legend', 21 | 'size_continuous_legend', 22 | 'default_legend' 23 | ] 24 | -------------------------------------------------------------------------------- /cartoframes/viz/legends/basic_legend.py: -------------------------------------------------------------------------------- 1 | from ..legend import Legend 2 | 3 | 4 | def basic_legend(title=None, description=None, footer=None): 5 | """Helper function for quickly creating a basic legend. 6 | 7 | Args: 8 | title (str, optional): 9 | Title of legend. 10 | description (str, optional): 11 | Description in legend. 12 | footer (str, optional): 13 | Footer of legend. This is often used to attribute data sources 14 | 15 | Returns: 16 | cartoframes.viz.legend.Legend 17 | 18 | Example: 19 | >>> basic_legend( 20 | ... title='Legend title', 21 | ... description='Legend description', 22 | ... footer='Legend footer') 23 | 24 | """ 25 | 26 | return Legend('basic', title, description, footer) 27 | -------------------------------------------------------------------------------- /cartoframes/viz/legends/color_bins_legend.py: -------------------------------------------------------------------------------- 1 | from ..legend import Legend 2 | 3 | 4 | def color_bins_legend(title=None, description=None, footer=None, prop='color', 5 | variable=None, dynamic=True, ascending=False, format=None): 6 | """Helper function for quickly creating a color bins legend. 7 | 8 | Args: 9 | title (str, optional): 10 | Title of legend. 11 | description (str, optional): 12 | Description in legend. 13 | footer (str, optional): 14 | Footer of legend. This is often used to attribute data sources. 15 | prop (str, optional): Allowed properties are 'color' and 'stroke_color'. 16 | It is 'color' by default. 17 | variable (str, optional): 18 | If the information in the legend depends on a different value than the 19 | information set to the style property, it is possible to set an independent 20 | variable. 21 | dynamic (boolean, optional): 22 | Update and render the legend depending on viewport changes. 23 | Defaults to ``True``. 24 | ascending (boolean, optional): 25 | If set to ``True`` the values are sorted in ascending order. 26 | Defaults to ``False``. 27 | format (str, optional): Format to apply to number values in the widget, based on d3-format 28 | specifier (https://github.com/d3/d3-format#locale_format). 29 | 30 | Returns: 31 | cartoframes.viz.legend.Legend 32 | 33 | Example: 34 | >>> color_bins_legend( 35 | ... title='Legend title', 36 | ... description='Legend description', 37 | ... footer='Legend footer', 38 | ... dynamic=False, 39 | ... format='.2~s') 40 | 41 | """ 42 | return Legend('color-bins', title, description, footer, prop, variable, dynamic, ascending, format) 43 | -------------------------------------------------------------------------------- /cartoframes/viz/legends/color_category_legend.py: -------------------------------------------------------------------------------- 1 | from ..legend import Legend 2 | 3 | 4 | def color_category_legend(title=None, description=None, footer=None, prop='color', 5 | variable=None, dynamic=True): 6 | """Helper function for quickly creating a color category legend. 7 | 8 | Args: 9 | title (str, optional): 10 | Title of legend. 11 | description (str, optional): 12 | Description in legend. 13 | footer (str, optional): 14 | Footer of legend. This is often used to attribute data sources. 15 | prop (str, optional): Allowed properties are 'color' and 'stroke_color'. 16 | It is 'color' by default. 17 | variable (str, optional): 18 | If the information in the legend depends on a different value than the 19 | information set to the style property, it is possible to set an independent 20 | variable. 21 | dynamic (boolean, optional): 22 | Update and render the legend depending on viewport changes. 23 | Defaults to ``True``. 24 | 25 | Returns: 26 | cartoframes.viz.legend.Legend 27 | 28 | Example: 29 | >>> color_category_legend( 30 | ... title='Legend title', 31 | ... description='Legend description', 32 | ... footer='Legend footer', 33 | ... dynamic=False) 34 | 35 | """ 36 | return Legend('color-category', title, description, footer, prop, variable, dynamic, ascending=True) 37 | -------------------------------------------------------------------------------- /cartoframes/viz/legends/color_continuous_legend.py: -------------------------------------------------------------------------------- 1 | from ..legend import Legend 2 | 3 | 4 | def color_continuous_legend(title=None, description=None, footer=None, prop='color', 5 | variable=None, dynamic=True, ascending=False, format=None): 6 | """Helper function for quickly creating a color continuous legend. 7 | 8 | Args: 9 | title (str, optional): 10 | Title of legend. 11 | description (str, optional): 12 | Description in legend. 13 | footer (str, optional): 14 | Footer of legend. This is often used to attribute data sources. 15 | prop (str, optional): Allowed properties are 'color' and 'stroke_color'. 16 | It is 'color' by default. 17 | variable (str, optional): 18 | If the information in the legend depends on a different value than the 19 | information set to the style property, it is possible to set an independent 20 | variable. 21 | dynamic (boolean, optional): 22 | Update and render the legend depending on viewport changes. 23 | Defaults to ``True``. 24 | ascending (boolean, optional): 25 | If set to ``True`` the values are sorted in ascending order. 26 | Defaults to ``False``. 27 | format (str, optional): Format to apply to number values in the widget, based on d3-format 28 | specifier (https://github.com/d3/d3-format#locale_format). 29 | 30 | Returns: 31 | cartoframes.viz.legend.Legend 32 | 33 | Example: 34 | >>> color_continuous_legend( 35 | ... title='Legend title', 36 | ... description='Legend description', 37 | ... footer='Legend footer', 38 | ... dynamic=False, 39 | ... format='.2~s') 40 | 41 | """ 42 | return Legend('color-continuous', title, description, footer, prop, variable, dynamic, ascending, format) 43 | -------------------------------------------------------------------------------- /cartoframes/viz/legends/default_legend.py: -------------------------------------------------------------------------------- 1 | from ..legend import Legend 2 | 3 | 4 | def default_legend(title=None, description=None, footer=None, format=None, **kwargs): 5 | """Helper function for quickly creating a default legend based on the style. 6 | A style helper is required. 7 | 8 | Args: 9 | title (str, optional): 10 | Title of legend. 11 | description (str, optional): 12 | Description in legend. 13 | footer (str, optional): 14 | Footer of legend. This is often used to attribute data sources. 15 | format (str, optional): Format to apply to number values in the widget, based on d3-format 16 | specifier (https://github.com/d3/d3-format#locale_format). 17 | 18 | Returns: 19 | cartoframes.viz.legend.Legend 20 | 21 | Example: 22 | >>> default_legend( 23 | ... title='Legend title', 24 | ... description='Legend description', 25 | ... footer='Legend footer', 26 | ... format='.2~s') 27 | 28 | """ 29 | return Legend('default', title=title, description=description, footer=footer, format=format, **kwargs) 30 | -------------------------------------------------------------------------------- /cartoframes/viz/legends/size_bins_legend.py: -------------------------------------------------------------------------------- 1 | from ..legend import Legend 2 | 3 | 4 | def size_bins_legend(title=None, description=None, footer=None, prop='size', 5 | variable=None, dynamic=True, ascending=False, format=None): 6 | """Helper function for quickly creating a size bins legend. 7 | 8 | Args: 9 | title (str, optional): 10 | Title of legend. 11 | description (str, optional): 12 | Description in legend. 13 | footer (str, optional): 14 | Footer of legend. This is often used to attribute data sources. 15 | prop (str, optional): Allowed properties are 'size' and 'stroke_width'. 16 | It is 'size' by default. 17 | variable (str, optional): 18 | If the information in the legend depends on a different value than the 19 | information set to the style property, it is possible to set an independent 20 | variable. 21 | dynamic (boolean, optional): 22 | Update and render the legend depending on viewport changes. 23 | Defaults to ``True``. 24 | ascending (boolean, optional): 25 | If set to ``True`` the values are sorted in ascending order. 26 | Defaults to ``False``. 27 | format (str, optional): Format to apply to number values in the widget, based on d3-format 28 | specifier (https://github.com/d3/d3-format#locale_format). 29 | 30 | Returns: 31 | cartoframes.viz.legend.Legend 32 | 33 | Example: 34 | >>> size_bins_style( 35 | ... title='Legend title', 36 | ... description='Legend description', 37 | ... footer='Legend footer', 38 | ... dynamic=False, 39 | ... format='.2~s') 40 | 41 | """ 42 | return Legend('size-bins', title, description, footer, prop, variable, dynamic, ascending, format) 43 | -------------------------------------------------------------------------------- /cartoframes/viz/legends/size_category_legend.py: -------------------------------------------------------------------------------- 1 | from ..legend import Legend 2 | 3 | 4 | def size_category_legend(title=None, description=None, footer=None, prop='size', 5 | variable=None, dynamic=True): 6 | """Helper function for quickly creating a size category legend. 7 | 8 | Args: 9 | title (str, optional): 10 | Title of legend. 11 | description (str, optional): 12 | Description in legend. 13 | footer (str, optional): 14 | Footer of legend. This is often used to attribute data sources. 15 | prop (str, optional): Allowed properties are 'size' and 'stroke_width'. 16 | It is 'size' by default. 17 | variable (str, optional): 18 | If the information in the legend depends on a different value than the 19 | information set to the style property, it is possible to set an independent 20 | variable. 21 | dynamic (boolean, optional): 22 | Update and render the legend depending on viewport changes. 23 | Defaults to ``True``. 24 | 25 | Returns: 26 | cartoframes.viz.legend.Legend 27 | 28 | Example: 29 | >>> size_category_legend( 30 | ... title='Legend title', 31 | ... description='Legend description', 32 | ... footer='Legend footer', 33 | ... dynamic=False) 34 | 35 | """ 36 | return Legend('size-category', title, description, footer, prop, variable, dynamic, ascending=True) 37 | -------------------------------------------------------------------------------- /cartoframes/viz/legends/size_continuous_legend.py: -------------------------------------------------------------------------------- 1 | from ..legend import Legend 2 | 3 | 4 | def size_continuous_legend(title=None, description=None, footer=None, prop='size', 5 | variable='size_value', dynamic=True, ascending=False, format=None): 6 | """Helper function for quickly creating a size continuous legend. 7 | 8 | Args: 9 | prop (str, optional): Allowed properties are 'size' and 'stroke_width'. 10 | dynamic (boolean, optional): 11 | Update and render the legend depending on viewport changes. 12 | Defaults to ``True``. 13 | title (str, optional): 14 | Title of legend. 15 | description (str, optional): 16 | Description in legend. 17 | footer (str, optional): 18 | Footer of legend. This is often used to attribute data sources. 19 | variable (str, optional): 20 | If the information in the legend depends on a different value than the 21 | information set to the style property, it is possible to set an independent 22 | variable. 23 | ascending (boolean, optional): 24 | If set to ``True`` the values are sorted in ascending order. 25 | Defaults to ``False``. 26 | format (str, optional): Format to apply to number values in the widget, based on d3-format 27 | specifier (https://github.com/d3/d3-format#locale_format). 28 | 29 | Returns: 30 | cartoframes.viz.legend.Legend 31 | 32 | Example: 33 | >>> size_continuous_legend( 34 | ... title='Legend title', 35 | ... description='Legend description', 36 | ... footer='Legend footer', 37 | ... dynamic=False, 38 | ... format='.2~s') 39 | 40 | """ 41 | return Legend('size-continuous', title, description, footer, prop, variable, dynamic, ascending, format) 42 | -------------------------------------------------------------------------------- /cartoframes/viz/palettes.py: -------------------------------------------------------------------------------- 1 | class Palettes: # pylint: disable=too-few-public-methods 2 | """Color palettes applied to Map visualizations in the style helper methods. 3 | By default, each color helper method has its own default palette. 4 | More information at https://carto.com/carto-colors/ 5 | 6 | Example: 7 | Create color bins style with the burg palette 8 | 9 | >>> color_bins_style('column name', palette=palettes.burg) 10 | 11 | """ 12 | pass 13 | 14 | 15 | PALETTES = [ 16 | 'BURG', 17 | 'BURGYL', 18 | 'REDOR', 19 | 'ORYEL', 20 | 'PEACH', 21 | 'PINKYL', 22 | 'MINT', 23 | 'BLUGRN', 24 | 'DARKMINT', 25 | 'EMRLD', 26 | 'AG_GRNYL', 27 | 'BLUYL', 28 | 'TEAL', 29 | 'TEALGRN', 30 | 'PURP', 31 | 'PURPOR', 32 | 'SUNSET', 33 | 'MAGENTA', 34 | 'SUNSETDARK', 35 | 'AG_SUNSET', 36 | 'BRWNYL', 37 | 'ARMYROSE', 38 | 'FALL', 39 | 'GEYSER', 40 | 'TEMPS', 41 | 'TEALROSE', 42 | 'TROPIC', 43 | 'EARTH', 44 | 'ANTIQUE', 45 | 'BOLD', 46 | 'PASTEL', 47 | 'PRISM', 48 | 'SAFE', 49 | 'VIVID' 50 | 'CB_YLGN', 51 | 'CB_YLGNBU', 52 | 'CB_GNBU', 53 | 'CB_BUGN', 54 | 'CB_PUBUGN', 55 | 'CB_PUBU', 56 | 'CB_BUPU', 57 | 'CB_RDPU', 58 | 'CB_PURD', 59 | 'CB_ORRD', 60 | 'CB_YLORRD', 61 | 'CB_YLORBR', 62 | 'CB_PURPLES', 63 | 'CB_BLUES', 64 | 'CB_GREENS', 65 | 'CB_ORANGES', 66 | 'CB_REDS', 67 | 'CB_GREYS', 68 | 'CB_PUOR', 69 | 'CB_BRBG', 70 | 'CB_PRGN', 71 | 'CB_PIYG', 72 | 'CB_RDBU', 73 | 'CB_RDGY', 74 | 'CB_RDYLBU', 75 | 'CB_SPECTRAL', 76 | 'CB_RDYLGN', 77 | 'CB_ACCENT', 78 | 'CB_DARK2', 79 | 'CB_PAIRED', 80 | 'CB_PASTEL1', 81 | 'CB_PASTEL2', 82 | 'CB_SET1', 83 | 'CB_SET2', 84 | 'CB_SET3' 85 | ] 86 | 87 | for palette in PALETTES: 88 | setattr(Palettes, palette.lower(), palette) 89 | -------------------------------------------------------------------------------- /cartoframes/viz/popup.py: -------------------------------------------------------------------------------- 1 | from ..utils.utils import gen_variable_name 2 | 3 | 4 | class Popup: 5 | """Popups can be added to each layer and displayed in the visualization when clicking or hovering 6 | features. 7 | 8 | They should be added by using the :py:meth:`popup_element ` in each 9 | Layer popup. 10 | 11 | """ 12 | def __init__(self, event=None, value=None, title=None, format=None): 13 | self._init_popup(event, value, title, format) 14 | 15 | def _init_popup(self, event=None, value=None, title=None, format=None): 16 | if not isinstance(event, str) and not isinstance(value, str): 17 | raise ValueError('Wrong popup input') 18 | 19 | self._event = event 20 | self._value = value 21 | self._title = title if title else value 22 | self._format = format 23 | 24 | self._interactivity = self._get_interactivity() 25 | self._variable = self._get_variable() 26 | 27 | @property 28 | def value(self): 29 | return self._value 30 | 31 | @property 32 | def title(self): 33 | return self._title 34 | 35 | @property 36 | def interactivity(self): 37 | return self._interactivity 38 | 39 | @property 40 | def variable(self): 41 | return self._variable 42 | 43 | def _get_interactivity(self): 44 | return { 45 | 'event': self._event, 46 | 'attrs': self._get_attrs() 47 | } 48 | 49 | def _get_attrs(self): 50 | return { 51 | 'name': gen_variable_name(self._value), 52 | 'title': self._title, 53 | 'format': self._format 54 | } 55 | 56 | def _get_variable(self): 57 | return { 58 | 'name': gen_variable_name(self._value), 59 | 'value': self._value 60 | } 61 | -------------------------------------------------------------------------------- /cartoframes/viz/popups/__init__.py: -------------------------------------------------------------------------------- 1 | from .popup_element import popup_element 2 | from .default_popup_element import default_popup_element 3 | 4 | __all__ = [ 5 | 'popup_element', 6 | 'default_popup_element' 7 | ] 8 | -------------------------------------------------------------------------------- /cartoframes/viz/popups/default_popup_element.py: -------------------------------------------------------------------------------- 1 | def default_popup_element(title=None, operation=None, format=None): 2 | """Helper function for quickly adding a default popup element based on the style. 3 | A style helper is required. 4 | 5 | Args: 6 | title (str, optional): Title for the given value. By default, it's the name of the value. 7 | operation (str, optional): Cluster operation, defaults to 'count'. Other options 8 | available are 'avg', 'min', 'max', and 'sum'. 9 | format (str, optional): Format to apply to number values in the widget, based on d3-format 10 | specifier (https://github.com/d3/d3-format#locale_format). 11 | 12 | Example: 13 | >>> default_popup_element(title='Popup title', format='.2~s') 14 | 15 | """ 16 | return { 17 | 'value': None, 18 | 'title': title, 19 | 'operation': operation, 20 | 'format': format 21 | } 22 | -------------------------------------------------------------------------------- /cartoframes/viz/popups/popup_element.py: -------------------------------------------------------------------------------- 1 | def popup_element(value, title=None, format=None): 2 | """Helper function for quickly adding a popup element to a layer. 3 | 4 | Args: 5 | value (str): Column name to display the value for each feature. 6 | title (str, optional): Title for the given value. By default, it's the name of the value. 7 | format (str, optional): Format to apply to number values in the widget, based on d3-format 8 | specifier (https://github.com/d3/d3-format#locale_format). 9 | 10 | Example: 11 | >>> popup_element('column_name', title='Popup title', format='.2~s') 12 | 13 | """ 14 | return { 15 | 'value': value, 16 | 'title': title, 17 | 'format': format 18 | } 19 | -------------------------------------------------------------------------------- /cartoframes/viz/styles/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Style helpers to generate styles faster. 3 | """ 4 | 5 | from .animation_style import animation_style 6 | from .basic_style import basic_style 7 | from .color_bins_style import color_bins_style 8 | from .color_category_style import color_category_style 9 | from .color_continuous_style import color_continuous_style 10 | from .cluster_size_style import cluster_size_style 11 | from .isolines_style import isolines_style 12 | from .size_bins_style import size_bins_style 13 | from .size_category_style import size_category_style 14 | from .size_continuous_style import size_continuous_style 15 | 16 | 17 | __all__ = [ 18 | 'animation_style', 19 | 'basic_style', 20 | 'color_bins_style', 21 | 'color_category_style', 22 | 'color_continuous_style', 23 | 'cluster_size_style', 24 | 'isolines_style', 25 | 'size_bins_style', 26 | 'size_category_style', 27 | 'size_continuous_style' 28 | ] 29 | -------------------------------------------------------------------------------- /cartoframes/viz/styles/animation_style.py: -------------------------------------------------------------------------------- 1 | from .utils import get_value, prop 2 | from ..style import Style 3 | from ..widgets import time_series_widget 4 | from ..popups import popup_element 5 | 6 | 7 | def animation_style(value, duration=20, fade_in=1, fade_out=1, color=None, 8 | size=None, opacity=None, stroke_color=None, stroke_width=None): 9 | """Helper function for quickly creating an animated style. 10 | 11 | Args: 12 | value (str): Column to symbolize by. 13 | duration (float, optional): Time of the animation in seconds. Default is 20s. 14 | fade_in (float, optional): Time of fade in transitions in seconds. Default is 1s. 15 | fade_out (float, optional): Time of fade out transitions in seconds. Default is 1s. 16 | color (str, optional): Hex, rgb or named color value. Default is '#EE5D5A' for points, 17 | '#4CC8A3' for lines and #826DBA for polygons. 18 | size (int, optional): Size of point or line features. 19 | opacity (float, optional): Opacity value. Default is 1 for points and lines and 20 | 0.9 for polygons. 21 | stroke_width (int, optional): Size of the stroke on point features. 22 | stroke_color (str, optional): Color of the stroke on point features. 23 | Default is '#222'. 24 | 25 | Returns: 26 | cartoframes.viz.style.Style 27 | 28 | """ 29 | fade = '({0}, {1})'.format(fade_in, fade_out) 30 | data = { 31 | 'point': { 32 | 'color': 'opacity({0}, {1})'.format( 33 | get_value(color, 'color', 'point'), 34 | get_value(opacity, 1)), 35 | 'width': get_value(size, 'width', 'point'), 36 | 'strokeColor': get_value(stroke_color, 'strokeColor', 'point'), 37 | 'strokeWidth': get_value(stroke_width, 'strokeWidth', 'point'), 38 | 'filter': _animation_filter(value, duration, fade) 39 | }, 40 | 'line': { 41 | 'color': 'opacity({0}, {1})'.format( 42 | get_value(color, 'color', 'line'), 43 | get_value(opacity, 1)), 44 | 'width': get_value(size, 'width', 'line'), 45 | 'filter': _animation_filter(value, duration, fade) 46 | }, 47 | 'polygon': { 48 | 'color': 'opacity({0}, {1})'.format( 49 | get_value(color, 'color', 'polygon'), 50 | get_value(opacity, 0.9)), 51 | 'strokeColor': get_value(stroke_color, 'strokeColor', 'polygon'), 52 | 'strokeWidth': get_value(stroke_width, 'strokeWidth', 'polygon'), 53 | 'filter': _animation_filter(value, duration, fade) 54 | } 55 | } 56 | 57 | return Style( 58 | data, 59 | value, 60 | default_widget=time_series_widget(value, title=value), 61 | default_popup_hover=popup_element(value, title=value), 62 | default_popup_click=popup_element(value, title=value) 63 | ) 64 | 65 | 66 | def _animation_filter(value, duration, fade): 67 | return 'animation(linear({0}), {1}, fade{2})'.format(prop(value), duration, fade) 68 | -------------------------------------------------------------------------------- /cartoframes/viz/styles/basic_style.py: -------------------------------------------------------------------------------- 1 | from .utils import get_value 2 | from ..style import Style 3 | from ..widgets import basic_widget 4 | 5 | 6 | def basic_style(color=None, size=None, opacity=None, stroke_color=None, stroke_width=None): 7 | """Helper function for quickly creating a basic style. 8 | 9 | Args: 10 | color (str, optional): hex, rgb or named color value. 11 | Defaults is '#FFB927' for point geometries and '#4CC8A3' for lines. 12 | size (int, optional): Size of point or line features. 13 | opacity (float, optional): Opacity value. Default is 1 for points and lines and 14 | 0.9 for polygons. 15 | stroke_color (str, optional): Color of the stroke on point features. 16 | Default is '#222'. 17 | stroke_width (int, optional): Size of the stroke on point features. 18 | 19 | Returns: 20 | cartoframes.viz.style.Style 21 | 22 | """ 23 | value = None 24 | data = { 25 | 'point': { 26 | 'color': 'opacity({0}, {1})'.format( 27 | get_value(color, 'color', 'point'), 28 | get_value(opacity, 1) 29 | ), 30 | 'width': get_value(size, 'width', 'point'), 31 | 'strokeColor': get_value(stroke_color, 'strokeColor', 'point'), 32 | 'strokeWidth': get_value(stroke_width, 'strokeWidth', 'point'), 33 | }, 34 | 'line': { 35 | 'color': 'opacity({0}, {1})'.format( 36 | get_value(color, 'color', 'line'), 37 | get_value(opacity, 1) 38 | ), 39 | 'width': get_value(size, 'width', 'line') 40 | }, 41 | 'polygon': { 42 | 'color': 'opacity({0}, {1})'.format( 43 | get_value(color, 'color', 'polygon'), 44 | get_value(opacity, 0.9) 45 | ), 46 | 'strokeColor': get_value(stroke_color, 'strokeColor', 'polygon'), 47 | 'strokeWidth': get_value(stroke_width, 'strokeWidth', 'polygon') 48 | } 49 | } 50 | 51 | return Style( 52 | data, 53 | value, 54 | default_widget=basic_widget() 55 | ) 56 | -------------------------------------------------------------------------------- /cartoframes/viz/styles/isolines_style.py: -------------------------------------------------------------------------------- 1 | from . import color_category_style 2 | from ...data.services.isolines import RANGE_LABEL_KEY 3 | 4 | 5 | def isolines_style(value=RANGE_LABEL_KEY, top=11, cat=None, palette='pinkyl', size=None, opacity=0.8, 6 | stroke_color='rgba(150,150,150,0.4)', stroke_width=None): 7 | """Helper function for quickly creating an isolines style. 8 | Based on the color category style. 9 | 10 | Args: 11 | value (str, optional): Column to symbolize by. Default is "range_label". 12 | top (int, optional): Number of categories. Default is 11. Values 13 | can range from 1 to 16. 14 | cat (list, optional): Category list. Must be a valid list of categories. 15 | palette (str, optional): Palette that can be a named cartocolor palette 16 | or other valid color palette. Use `help(cartoframes.viz.palettes)` to 17 | get more information. Default is "pinkyl". 18 | size (int, optional): Size of point or line features. 19 | opacity (float, optional): Opacity value for point color and line features. 20 | Default is 0.8. 21 | stroke_color (str, optional): Color of the stroke on point features. 22 | Default is 'rgba(150,150,150,0.4)'. 23 | stroke_width (int, optional): Size of the stroke on point features. 24 | 25 | Returns: 26 | cartoframes.viz.style.Style 27 | 28 | """ 29 | return color_category_style(value, top, cat, palette, size, opacity, stroke_color, stroke_width) 30 | -------------------------------------------------------------------------------- /cartoframes/viz/styles/size_category_style.py: -------------------------------------------------------------------------------- 1 | from .utils import get_value, prop 2 | from ..style import Style 3 | from ..legends import size_category_legend 4 | from ..widgets import category_widget 5 | from ..popups import popup_element 6 | 7 | 8 | def size_category_style(value, top=5, cat=None, size_range=None, color=None, opacity=None, 9 | stroke_color=None, stroke_width=None, animate=None): 10 | """Helper function for quickly creating a size category style. 11 | 12 | Args: 13 | value (str): Column to symbolize by. 14 | top (int, optional): Number of size categories. Default is 5. Values 15 | can range from 1 to 16. 16 | cat (list, optional): Category list as a string. 17 | size_range (list, optional): Min/max size array as a string. Default is 18 | [2, 20] for point geometries and [1, 10] for lines. 19 | color (str, optional): hex, rgb or named color value. 20 | Default is '#F46D43' for point geometries and '#4CC8A3' for lines. 21 | opacity (float, optional): Opacity value for point color and line features. 22 | Default is 0.8. 23 | stroke_color (str, optional): Color of the stroke on point features. 24 | Default is '#222'. 25 | stroke_width (int, optional): Size of the stroke on point features. 26 | animate (str, optional): Animate features by date/time or other numeric field. 27 | 28 | Returns: 29 | cartoframes.viz.style.Style 30 | 31 | """ 32 | func = 'buckets' if cat else 'top' 33 | animation_filter = 'animation(linear({}), 20, fade(1,1))'.format(prop(animate)) if animate else '1' 34 | opacity = opacity if opacity else '0.8' 35 | 36 | data = { 37 | 'point': { 38 | 'color': 'opacity({0}, {1})'.format( 39 | get_value(color, '#F46D43'), 40 | get_value(opacity, 1)), 41 | 'width': 'ramp({0}({1}, {2}), {3})'.format( 42 | func, prop(value), cat or top, size_range or [2, 20]), 43 | 'strokeColor': get_value(stroke_color, 'strokeColor', 'point'), 44 | 'strokeWidth': get_value(stroke_width, 'strokeWidth', 'point'), 45 | 'filter': animation_filter 46 | }, 47 | 'line': { 48 | 'color': 'opacity({0}, {1})'.format( 49 | get_value(color, 'color', 'line'), 50 | get_value(opacity, 1)), 51 | 'width': 'ramp({0}({1}, {2}), {3})'.format( 52 | func, prop(value), cat or top, size_range or [1, 10]), 53 | 'filter': animation_filter 54 | } 55 | } 56 | 57 | return Style( 58 | data, 59 | value, 60 | default_legend=size_category_legend(title=value), 61 | default_widget=category_widget(value, title=value), 62 | default_popup_hover=popup_element(value, title=value), 63 | default_popup_click=popup_element(value, title=value) 64 | ) 65 | -------------------------------------------------------------------------------- /cartoframes/viz/styles/utils.py: -------------------------------------------------------------------------------- 1 | from .. import defaults 2 | 3 | 4 | def serialize_palette(palette): 5 | if isinstance(palette, (list, tuple)): 6 | return '[{}]'.format(','.join(palette)) 7 | return palette 8 | 9 | 10 | def get_value(value, default, geom_type=None): 11 | if value is None: 12 | if geom_type in ['point', 'line', 'polygon']: 13 | return defaults.STYLE.get(geom_type, {}).get(default) 14 | return default 15 | return value 16 | 17 | 18 | def prop(value): 19 | if '\'' in value: 20 | return 'prop("{}")'.format(value) 21 | else: 22 | return "prop('{}')".format(value) 23 | -------------------------------------------------------------------------------- /cartoframes/viz/themes.py: -------------------------------------------------------------------------------- 1 | class Themes: # pylint: disable=too-few-public-methods 2 | """UI color theme applied to Widgets and Legends. 3 | When selecting the DarkMatter basemap, the Dark theme is used by default. 4 | 5 | Example: 6 | Create an embedded map using CARTO's Positron style with the dark theme 7 | 8 | >>> Map(theme=themes.dark) 9 | 10 | """ 11 | dark = 'dark' 12 | light = 'light' 13 | -------------------------------------------------------------------------------- /cartoframes/viz/widget_list.py: -------------------------------------------------------------------------------- 1 | from .widget import Widget 2 | from .styles.utils import prop 3 | 4 | 5 | class WidgetList: 6 | """WidgetList 7 | 8 | Args: 9 | widgets (list, Widget): The list of widgets for a layer. 10 | default_widget (Widget, optional): The widget to be used by default. 11 | 12 | """ 13 | def __init__(self, widgets=None, default_widget=None): 14 | self._widgets = self._init_widgets(widgets, default_widget) 15 | 16 | def _init_widgets(self, widgets, default_widget): 17 | if isinstance(widgets, list): 18 | widget_list = [] 19 | for widget in widgets: 20 | if isinstance(widget, dict): 21 | widget_list.append(Widget(widget)) 22 | elif isinstance(widget, Widget): 23 | if widget._type == 'default' and default_widget: 24 | widget._type = default_widget._type 25 | widget._prop = default_widget._prop 26 | widget._value = default_widget._value 27 | widget_list.append(widget) 28 | return widget_list 29 | if isinstance(widgets, dict): 30 | return [Widget(widgets)] 31 | else: 32 | return [] 33 | 34 | def get_widgets_info(self): 35 | widgets_info = [] 36 | for widget in self._widgets: 37 | if widget: 38 | widgets_info.append(widget.get_info()) 39 | 40 | return widgets_info 41 | 42 | def get_variables(self): 43 | output = {} 44 | for widget in self._widgets: 45 | if widget._variable_name: 46 | output[widget._variable_name] = prop(widget._value) if widget.has_bridge() else widget._value 47 | 48 | return output 49 | -------------------------------------------------------------------------------- /cartoframes/viz/widgets/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Widget helpers to generate widgets faster. 3 | """ 4 | 5 | from .basic_widget import basic_widget 6 | from .animation_widget import animation_widget 7 | from .category_widget import category_widget 8 | from .formula_widget import formula_widget 9 | from .histogram_widget import histogram_widget 10 | from .time_series_widget import time_series_widget 11 | from .default_widget import default_widget 12 | 13 | 14 | __all__ = [ 15 | 'basic_widget', 16 | 'animation_widget', 17 | 'category_widget', 18 | 'formula_widget', 19 | 'histogram_widget', 20 | 'time_series_widget', 21 | 'default_widget' 22 | ] 23 | -------------------------------------------------------------------------------- /cartoframes/viz/widgets/animation_widget.py: -------------------------------------------------------------------------------- 1 | from ..widget import Widget 2 | 3 | 4 | def animation_widget(title=None, description=None, footer=None, prop='filter'): 5 | """Helper function for quickly creating an animated widget. 6 | 7 | The animation widget includes an animation status bar as well as controls to play or pause animated data. 8 | The `filter` property of your map's style, applied to either a date or numeric field, drives both 9 | the animation and the widget. Only **one** animation can be controlled per layer. 10 | 11 | Args: 12 | title (str, optional): Title of widget. 13 | description (str, optional): Description text widget placed under widget title. 14 | footer (str, optional): Footer text placed on the widget bottom. 15 | prop (str, optional): Property of the style to get the animation. Default "filter". 16 | 17 | Returns: 18 | cartoframes.viz.widget.Widget 19 | 20 | Example: 21 | >>> animation_widget( 22 | ... title='Widget title', 23 | ... description='Widget description', 24 | ... footer='Widget footer') 25 | 26 | """ 27 | return Widget('animation', None, title, description, footer, 28 | prop=prop) 29 | -------------------------------------------------------------------------------- /cartoframes/viz/widgets/basic_widget.py: -------------------------------------------------------------------------------- 1 | from ..widget import Widget 2 | 3 | 4 | def basic_widget(title=None, description=None, footer=None): 5 | """Helper function for quickly creating a default widget. 6 | 7 | The default widget is a general purpose widget that can be used to provide additional information about your map. 8 | 9 | Args: 10 | title (str, optional): Title of widget. 11 | description (str, optional): Description text widget placed under widget title. 12 | footer (str, optional): Footer text placed on the widget bottom. 13 | 14 | Returns: 15 | cartoframes.viz.widget.Widget 16 | 17 | Example: 18 | >>> basic_widget( 19 | ... title='Widget title', 20 | ... description='Widget description', 21 | ... footer='Widget footer') 22 | 23 | """ 24 | return Widget('basic', None, title, description, footer) 25 | -------------------------------------------------------------------------------- /cartoframes/viz/widgets/category_widget.py: -------------------------------------------------------------------------------- 1 | from ..widget import Widget 2 | 3 | 4 | def category_widget(value, title=None, description=None, footer=None, read_only=False, weight=1): 5 | """Helper function for quickly creating a category widget. 6 | 7 | Args: 8 | value (str): Column name of the category value. 9 | title (str, optional): Title of widget. 10 | description (str, optional): Description text widget placed under widget title. 11 | footer (str, optional): Footer text placed on the widget bottom. 12 | read_only (boolean, optional): Interactively filter a category by selecting it in the widget. 13 | Set to "False" by default. 14 | weight (int, optional): Weight of the category widget. Default value is 1. 15 | 16 | Returns: 17 | cartoframes.viz.widget.Widget 18 | 19 | Example: 20 | >>> category_widget( 21 | ... 'column_name', 22 | ... title='Widget title', 23 | ... description='Widget description', 24 | ... footer='Widget footer') 25 | 26 | """ 27 | return Widget('category', value, title, description, footer, 28 | read_only=read_only, weight=weight) 29 | -------------------------------------------------------------------------------- /cartoframes/viz/widgets/default_widget.py: -------------------------------------------------------------------------------- 1 | from ..widget import Widget 2 | 3 | 4 | def default_widget(title=None, description=None, footer=None, **kwargs): 5 | """Helper function for quickly creating a default widget based on the style. 6 | A style helper is required. 7 | 8 | Args: 9 | title (str, optional): Title of widget. 10 | description (str, optional): Description text widget placed under widget title. 11 | footer (str, optional): Footer text placed on the widget bottom. 12 | 13 | Returns: 14 | cartoframes.viz.widget.Widget 15 | 16 | Example: 17 | >>> default_widget( 18 | ... title='Widget title', 19 | ... description='Widget description', 20 | ... footer='Widget footer') 21 | 22 | """ 23 | return Widget('default', None, title=title, description=description, footer=footer, **kwargs) 24 | -------------------------------------------------------------------------------- /cartoframes/viz/widgets/formula_widget.py: -------------------------------------------------------------------------------- 1 | from ..constants import FORMULA_OPERATIONS_GLOBAL, FORMULA_OPERATIONS_VIEWPORT 2 | from ..widget import Widget 3 | from ..styles.utils import prop 4 | 5 | 6 | def formula_widget(value, operation=None, title=None, description=None, footer=None, 7 | is_global=False, format=None): 8 | """Helper function for quickly creating a formula widget. 9 | 10 | Formula widgets calculate aggregated values ('avg', 'max', 'min', 'sum') from numeric columns 11 | or counts of features ('count') in a dataset. 12 | 13 | A formula widget's aggregations can be calculated on 'global' or 'viewport' based values. 14 | If you want the values in a formula widget to update on zoom and/or pan, use viewport based aggregations. 15 | 16 | Args: 17 | value (str): Column name of the numeric value. 18 | operation (str): attribute for widget's aggregated value ('count', 'avg', 'max', 'min', 'sum'). 19 | title (str, optional): Title of widget. 20 | description (str, optional): Description text widget placed under widget title. 21 | footer (str, optional): Footer text placed on the widget bottom. 22 | is_global (boolean, optional): Account for calculations based on the entire dataset ('global') vs. 23 | the default of 'viewport' features. 24 | format (str, optional): Format to apply to number values in the widget, based on d3-format 25 | specifier (https://github.com/d3/d3-format#locale_format). 26 | 27 | Returns: 28 | cartoframes.viz.widget.Widget 29 | 30 | Example: 31 | >>> formula_widget( 32 | ... 'column_name', 33 | ... title='Widget title', 34 | ... description='Widget description', 35 | ... footer='Widget footer') 36 | 37 | >>> formula_widget( 38 | ... 'column_name', 39 | ... operation='sum', 40 | ... title='Widget title', 41 | ... description='Widget description', 42 | ... footer='Widget footer', 43 | ... format='.2~s') 44 | 45 | """ 46 | if isinstance(operation, str): 47 | operation = operation.lower() 48 | value = _get_value_expression(operation, value, is_global) 49 | return Widget('formula', value, title, description, footer, format=format) 50 | 51 | 52 | def _get_value_expression(operation, value, is_global): 53 | if operation == 'count': 54 | formula_operation = _get_formula_operation('count', is_global) 55 | return formula_operation + '()' 56 | elif operation in ['avg', 'max', 'min', 'sum']: 57 | formula_operation = _get_formula_operation(operation, is_global) 58 | return formula_operation + '(' + prop(value) + ')' 59 | else: 60 | return prop(value) 61 | 62 | 63 | def _get_formula_operation(operation, is_global): 64 | if is_global: 65 | return FORMULA_OPERATIONS_GLOBAL.get(operation) 66 | else: 67 | return FORMULA_OPERATIONS_VIEWPORT.get(operation) 68 | -------------------------------------------------------------------------------- /cartoframes/viz/widgets/histogram_widget.py: -------------------------------------------------------------------------------- 1 | from ..widget import Widget 2 | 3 | 4 | def histogram_widget(value, title=None, description=None, footer=None, read_only=False, 5 | buckets=20, weight=1): 6 | """Helper function for quickly creating a histogram widget. 7 | 8 | Histogram widgets display the distribution of a numeric attribute, in buckets, to group 9 | ranges of values in your data. 10 | 11 | By default, you can hover over each bar to see each bucket's values and count, and also 12 | filter your map's data within a given range 13 | 14 | Args: 15 | value (str): Column name of the numeric or date value. 16 | title (str, optional): Title of widget. 17 | description (str, optional): Description text widget placed under widget title. 18 | footer (str, optional): Footer text placed on the widget bottom. 19 | read_only (boolean, optional): Interactively filter a range of numeric values by 20 | selecting them in the widget. Set to "False" by default. 21 | buckets (number, optional): Number of histogram buckets. Set to 20 by default. 22 | weight (int, optional): Weight of the category widget. Default value is 1. 23 | 24 | Returns: 25 | cartoframes.viz.widget.Widget 26 | 27 | Example: 28 | >>> histogram_widget( 29 | ... 'column_name', 30 | ... title='Widget title', 31 | ... description='Widget description', 32 | ... footer='Widget footer', 33 | ... buckets=9) 34 | 35 | """ 36 | return Widget('histogram', value, title, description, footer, 37 | read_only=read_only, buckets=buckets, weight=weight) 38 | -------------------------------------------------------------------------------- /cartoframes/viz/widgets/time_series_widget.py: -------------------------------------------------------------------------------- 1 | from ..widget import Widget 2 | 3 | 4 | def time_series_widget(value, title=None, description=None, footer=None, read_only=False, 5 | buckets=20, prop='filter', weight=1): 6 | """Helper function for quickly creating a time series widget. 7 | 8 | The time series widget enables you to display animated data (by aggregation) over a specified date or numeric field. 9 | Time series widgets provide a status bar of the animation, controls to play or pause, and the ability to filter on 10 | a range of values. 11 | 12 | Args: 13 | value (str): Column name of the numeric or date value. 14 | title (str, optional): Title of widget. 15 | description (str, optional): Description text widget placed under widget title. 16 | footer (str, optional): Footer text placed on the widget bottom 17 | read_only (boolean, optional): Interactively filter a range of numeric values by selecting them in the widget. 18 | Set to "False" by default. 19 | buckets (number, optional): Number of histogram buckets. Set to 20 by default. 20 | weight (int, optional): Weight of the category widget. Default value is 1. 21 | 22 | Returns: 23 | cartoframes.viz.widget.Widget 24 | 25 | Example: 26 | >>> time_series_widget( 27 | ... 'column_name', 28 | ... title='Widget title', 29 | ... description='Widget description', 30 | ... footer='Widget footer', 31 | ... buckets=10) 32 | 33 | """ 34 | return Widget('time-series', value, title, description, footer, 35 | read_only=read_only, buckets=buckets, prop=prop, weight=weight) 36 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SOURCEDIR = . 8 | BUILDDIR = build 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | .PHONY: help Makefile 15 | 16 | # Catch-all target: route all unknown targets to Sphinx using the new 17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 18 | %: Makefile 19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd .. 4 | pip uninstall cartoframes -y 5 | pip install -e . 6 | 7 | cd docs 8 | make clean && make html && make json 9 | -------------------------------------------------------------------------------- /docs/cartoframes.rst: -------------------------------------------------------------------------------- 1 | .. include:: reference/introduction.rst 2 | .. include:: reference/auth.rst 3 | .. include:: reference/io_functions.rst 4 | .. include:: reference/data_services.rst 5 | .. include:: reference/data_observatory.rst 6 | .. include:: reference/data_clients.rst 7 | .. include:: reference/viz.rst 8 | .. include:: reference/utils.rst 9 | .. include:: reference/exceptions.rst 10 | -------------------------------------------------------------------------------- /docs/examples/README.md: -------------------------------------------------------------------------------- 1 | ## List of available samples 2 | 3 | - https://libs.cartocdn.com/cartoframes/samples/starbucks_brooklyn.csv 4 | - https://libs.cartocdn.com/cartoframes/samples/starbucks_brooklyn_geocoded.csv 5 | - https://libs.cartocdn.com/cartoframes/samples/starbucks_brooklyn_geocoded.zip 6 | - https://libs.cartocdn.com/cartoframes/samples/starbucks_brooklyn_geocoded.geojson 7 | - https://libs.cartocdn.com/cartoframes/samples/chicago_cbg.csv 8 | - https://libs.cartocdn.com/cartoframes/samples/la_cbg.csv 9 | - https://libs.cartocdn.com/cartoframes/samples/starbucks_brooklyn_iso_enriched.geojson 10 | - https://libs.cartocdn.com/cartoframes/samples/starbucks_brooklyn_iso_enriched.csv 11 | - https://libs.cartocdn.com/cartoframes/samples/paris_wifi_services.json 12 | - https://libs.cartocdn.com/cartoframes/samples/arbres_remarquables_paris.json 13 | - https://libs.cartocdn.com/cartoframes/samples/sf_incidents.csv 14 | - https://libs.cartocdn.com/cartoframes/samples/london_stations.xls 15 | 16 | -------------------------------------------------------------------------------- /docs/examples/data_management/change_carto_table_privacy.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Change CARTO table privacy\n", 8 | "\n", 9 | "This example illustrates how to change the privacy of a CARTO table.\n", 10 | "\n", 11 | "_Note: You'll need [CARTO Account](https://carto.com/signup) credentials to reproduce this example._" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 1, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "from cartoframes.auth import set_default_credentials\n", 21 | "\n", 22 | "set_default_credentials('creds.json')" 23 | ] 24 | }, 25 | { 26 | "cell_type": "code", 27 | "execution_count": 2, 28 | "metadata": {}, 29 | "outputs": [ 30 | { 31 | "name": "stdout", 32 | "output_type": "stream", 33 | "text": [ 34 | "Success! Table updated correctly\n" 35 | ] 36 | }, 37 | { 38 | "data": { 39 | "text/plain": [ 40 | "'PRIVATE'" 41 | ] 42 | }, 43 | "execution_count": 2, 44 | "metadata": {}, 45 | "output_type": "execute_result" 46 | } 47 | ], 48 | "source": [ 49 | "from cartoframes import update_privacy_table, describe_table\n", 50 | "\n", 51 | "update_privacy_table('my_table', 'private')\n", 52 | "\n", 53 | "describe_table('my_table')['privacy']" 54 | ] 55 | }, 56 | { 57 | "cell_type": "code", 58 | "execution_count": 3, 59 | "metadata": {}, 60 | "outputs": [ 61 | { 62 | "name": "stdout", 63 | "output_type": "stream", 64 | "text": [ 65 | "Success! Table updated correctly\n" 66 | ] 67 | }, 68 | { 69 | "data": { 70 | "text/plain": [ 71 | "'PUBLIC'" 72 | ] 73 | }, 74 | "execution_count": 3, 75 | "metadata": {}, 76 | "output_type": "execute_result" 77 | } 78 | ], 79 | "source": [ 80 | "update_privacy_table('my_table', 'public')\n", 81 | "\n", 82 | "describe_table('my_table')['privacy']" 83 | ] 84 | } 85 | ], 86 | "metadata": { 87 | "kernelspec": { 88 | "display_name": "Python 3", 89 | "language": "python", 90 | "name": "python3" 91 | }, 92 | "language_info": { 93 | "codemirror_mode": { 94 | "name": "ipython", 95 | "version": 3 96 | }, 97 | "file_extension": ".py", 98 | "mimetype": "text/x-python", 99 | "name": "python", 100 | "nbconvert_exporter": "python", 101 | "pygments_lexer": "ipython3", 102 | "version": "3.6.7" 103 | } 104 | }, 105 | "nbformat": 4, 106 | "nbformat_minor": 2 107 | } 108 | -------------------------------------------------------------------------------- /docs/examples/data_visualization/publish_and_share/publish_visualization_layout.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Publish a visualization of a layout\n", 8 | "\n", 9 | "This example illustrates how to publish a layout of map visualizations.\n", 10 | "\n", 11 | ">_Note: You'll need your [CARTO Account](https://carto.com/signup) credentials to reproduce this example._" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 1, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "from cartoframes.auth import set_default_credentials\n", 21 | "from cartoframes.viz import Map, Layer, Layout, basic_style, color_category_style\n", 22 | "\n", 23 | "set_default_credentials('creds.json')" 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": 2, 29 | "metadata": { 30 | "scrolled": false 31 | }, 32 | "outputs": [], 33 | "source": [ 34 | "layout_viz = Layout([\n", 35 | " Map(Layer('public_table')),\n", 36 | " Map(Layer('private_table'))\n", 37 | "])" 38 | ] 39 | }, 40 | { 41 | "cell_type": "code", 42 | "execution_count": 3, 43 | "metadata": {}, 44 | "outputs": [ 45 | { 46 | "name": "stdout", 47 | "output_type": "stream", 48 | "text": [ 49 | "The map has been published. The \"cartoframes_997c05771fd4e0916de49826722e51cd\" Maps API key with value \"7rj9ftFsOKUjSotnygh2jg\" is being used for the datasets: \"private_table\". You can manage your API keys on your account.\n" 50 | ] 51 | }, 52 | { 53 | "data": { 54 | "text/plain": [ 55 | "{'id': '70057fe9-57ad-4e3c-999f-a086c2a1d494',\n", 56 | " 'url': 'https://cartoframes-org.carto.com/u/cartoframes/kuviz/70057fe9-57ad-4e3c-999f-a086c2a1d494',\n", 57 | " 'name': 'layout_public_private_table',\n", 58 | " 'privacy': 'password'}" 59 | ] 60 | }, 61 | "execution_count": 3, 62 | "metadata": {}, 63 | "output_type": "execute_result" 64 | } 65 | ], 66 | "source": [ 67 | "layout_viz.publish(\n", 68 | " 'layout_public_private_table',\n", 69 | " password='1234',\n", 70 | " if_exists='replace')" 71 | ] 72 | } 73 | ], 74 | "metadata": { 75 | "kernelspec": { 76 | "display_name": "Python 3", 77 | "language": "python", 78 | "name": "python3" 79 | }, 80 | "language_info": { 81 | "codemirror_mode": { 82 | "name": "ipython", 83 | "version": 3 84 | }, 85 | "file_extension": ".py", 86 | "mimetype": "text/x-python", 87 | "name": "python", 88 | "nbconvert_exporter": "python", 89 | "pygments_lexer": "ipython3", 90 | "version": "3.6.7" 91 | } 92 | }, 93 | "nbformat": 4, 94 | "nbformat_minor": 2 95 | } 96 | -------------------------------------------------------------------------------- /docs/examples/data_visualization/publish_and_share/publish_visualization_public_table.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Publish a visualization from a public table\n", 8 | "\n", 9 | "This example illustrates how to publish a visualization using a public table. You do not need a Maps API key to visualize a public table within a notebook. However you do need an API Key to publish any visualization, whether its data is public or private.\n", 10 | "\n", 11 | "Read more about API Keys here: https://carto.com/developers/auth-api/guides/types-of-API-Keys/\n", 12 | "\n", 13 | ">_Note: You'll need your [CARTO Account](https://carto.com/signup) credentials to reproduce this example._" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": 1, 19 | "metadata": {}, 20 | "outputs": [], 21 | "source": [ 22 | "from cartoframes.auth import set_default_credentials\n", 23 | "\n", 24 | "set_default_credentials('creds.json')" 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": 2, 30 | "metadata": {}, 31 | "outputs": [], 32 | "source": [ 33 | "from cartoframes.viz import Map, Layer\n", 34 | "\n", 35 | "map_viz = Map(Layer('public_table'))" 36 | ] 37 | }, 38 | { 39 | "cell_type": "code", 40 | "execution_count": 3, 41 | "metadata": {}, 42 | "outputs": [ 43 | { 44 | "data": { 45 | "text/plain": [ 46 | "{'id': '61ccd727-9716-4d5d-84a1-490cdd4f4aec',\n", 47 | " 'url': 'https://cartoframes-org.carto.com/u/cartoframes/kuviz/61ccd727-9716-4d5d-84a1-490cdd4f4aec',\n", 48 | " 'name': 'map_public_table',\n", 49 | " 'privacy': 'public'}" 50 | ] 51 | }, 52 | "execution_count": 3, 53 | "metadata": {}, 54 | "output_type": "execute_result" 55 | } 56 | ], 57 | "source": [ 58 | "map_viz.publish(\n", 59 | " name='map_public_table',\n", 60 | " password=None,\n", 61 | " if_exists='replace')" 62 | ] 63 | } 64 | ], 65 | "metadata": { 66 | "kernelspec": { 67 | "display_name": "Python 3", 68 | "language": "python", 69 | "name": "python3" 70 | }, 71 | "language_info": { 72 | "codemirror_mode": { 73 | "name": "ipython", 74 | "version": 3 75 | }, 76 | "file_extension": ".py", 77 | "mimetype": "text/x-python", 78 | "name": "python", 79 | "nbconvert_exporter": "python", 80 | "pygments_lexer": "ipython3", 81 | "version": "3.6.7" 82 | } 83 | }, 84 | "nbformat": 4, 85 | "nbformat_minor": 2 86 | } 87 | -------------------------------------------------------------------------------- /docs/examples/introduction.md: -------------------------------------------------------------------------------- 1 | Play with real examples and learn by doing -------------------------------------------------------------------------------- /docs/guides/00-Introduction.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | Use these guides to learn about integrating CARTOframes into your data science workflows. Working inside of a Jupyter Notebook, the guides walk you through installation, authentication, visualization, and all the data features provided in the library. 4 | 5 | If you are looking for more detailed use cases, [check our examples](/developers/cartoframes/examples). 6 | 7 |
      8 |
      9 | 10 | Installation 11 | 12 |

      Install the library and set up your environment

      13 |
      14 |
      15 | 16 | Authentication 17 | 18 |

      Login into CARTO to unlock the power of spatial analysis

      19 |
      20 |
      21 | 22 |
      23 |
      24 | 25 | Quickstart 26 | 27 |

      Complete a real project that walks through the library step by step

      28 |
      29 |
      30 | 31 | Data Management 32 | 33 |

      Work with data both locally and with a CARTO account

      34 |
      35 |
      36 | 37 | 38 |
      39 |
      40 | 41 | Data Visualization 42 | 43 |

      Explore and visualize your spatial data with interactive maps

      44 |
      45 |
      46 | 47 | Data Services 48 | 49 |

      Geocode and discover areas of influence with Isolines

      50 |
      51 |
      52 | 53 |
      54 |
      55 | 56 | Data Observatory 57 | 58 |

      Discover and enrich your data with the Data Observatory

      59 |
      60 |
      61 | -------------------------------------------------------------------------------- /docs/guides/creds.sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "username": "your_username", 3 | "api_key": "your_api_key" 4 | } -------------------------------------------------------------------------------- /docs/guides/guides.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": { 3 | "title": "Introduction", 4 | "file": "introduction.md" 5 | }, 6 | "categories": [ 7 | { 8 | "title": "Guides", 9 | "samples": [ 10 | { 11 | "title": "Installation", 12 | "desc": "Install the library and set up your environment", 13 | "file": "installation", 14 | "path": "" 15 | }, 16 | { 17 | "title": "Authentication", 18 | "desc": "Login into CARTO to unlock the power of spatial analysis", 19 | "file": "authentication", 20 | "path": "" 21 | }, 22 | { 23 | "title": "Quickstart", 24 | "desc": "Complete a real project that walks through the library step by step", 25 | "file": "quickstart", 26 | "path": "" 27 | }, 28 | { 29 | "title": "Data Management", 30 | "desc": "Work with data both locally and with a CARTO account", 31 | "file": "data_management", 32 | "path": "" 33 | }, 34 | { 35 | "title": "Data Visualization", 36 | "desc": "Explore and visualize your spatial data with interactive maps", 37 | "file": "data_visualization", 38 | "path": "" 39 | }, 40 | { 41 | "title": "Data Services", 42 | "desc": "Geocode and discover areas of influence with Isolines", 43 | "file": "data_services", 44 | "path": "" 45 | }, 46 | { 47 | "title": "Data Observatory", 48 | "desc": "Discover and enrich your data with the Data Observatory", 49 | "file": "data_observatory", 50 | "path": "" 51 | } 52 | ] 53 | } 54 | ] 55 | } 56 | -------------------------------------------------------------------------------- /docs/guides/img/credentials/api-keys.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CartoDB/cartoframes/1f56b75f512da62b7ecd1c07e1021b8552158907/docs/guides/img/credentials/api-keys.png -------------------------------------------------------------------------------- /docs/guides/img/credentials/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CartoDB/cartoframes/1f56b75f512da62b7ecd1c07e1021b8552158907/docs/guides/img/credentials/dashboard.png -------------------------------------------------------------------------------- /docs/guides/img/enrichment/enrichment_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CartoDB/cartoframes/1f56b75f512da62b7ecd1c07e1021b8552158907/docs/guides/img/enrichment/enrichment_01.png -------------------------------------------------------------------------------- /docs/guides/img/subscriptions/sub_dat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CartoDB/cartoframes/1f56b75f512da62b7ecd1c07e1021b8552158907/docs/guides/img/subscriptions/sub_dat.png -------------------------------------------------------------------------------- /docs/guides/img/subscriptions/sub_geo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CartoDB/cartoframes/1f56b75f512da62b7ecd1c07e1021b8552158907/docs/guides/img/subscriptions/sub_geo.png -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | =========== 2 | CARTOframes 3 | =========== 4 | 5 | .. toctree:: 6 | 7 | cartoframes -------------------------------------------------------------------------------- /docs/reference/auth.rst: -------------------------------------------------------------------------------- 1 | Auth 2 | ---- 3 | 4 | .. automodule:: cartoframes.auth 5 | :noindex: 6 | :members: 7 | :member-order: bysource 8 | :no-undoc-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/reference/data_clients.rst: -------------------------------------------------------------------------------- 1 | Data Clients 2 | ------------ 3 | 4 | .. automodule:: cartoframes.data.clients 5 | :noindex: 6 | :members: 7 | :member-order: bysource 8 | :undoc-members: 9 | :show-inheritance: -------------------------------------------------------------------------------- /docs/reference/data_observatory.rst: -------------------------------------------------------------------------------- 1 | Data Observatory 2 | ---------------- 3 | 4 | With CARTOframes it is possible to enrich your data by using our `Data Observatory <#data-observatory>`__ 5 | Catalog through the enrichment methods. 6 | 7 | .. automodule:: cartoframes.data.observatory 8 | :noindex: 9 | :members: 10 | :member-order: bysource 11 | :exclude-members: get_datasets_spatial_filtered 12 | :undoc-members: 13 | :show-inheritance: -------------------------------------------------------------------------------- /docs/reference/data_services.rst: -------------------------------------------------------------------------------- 1 | Data Services 2 | ------------- 3 | 4 | .. automodule:: cartoframes.data.services 5 | :noindex: 6 | :members: 7 | :member-order: bysource 8 | :undoc-members: 9 | :show-inheritance: -------------------------------------------------------------------------------- /docs/reference/exceptions.rst: -------------------------------------------------------------------------------- 1 | Exceptions 2 | ---------- 3 | 4 | .. automodule:: cartoframes.exceptions 5 | :noindex: 6 | :members: 7 | :member-order: bysource 8 | :undoc-members: 9 | :show-inheritance: -------------------------------------------------------------------------------- /docs/reference/introduction.rst: -------------------------------------------------------------------------------- 1 | Introduction 2 | ------------ 3 | 4 | The CARTOframes API is organized into three parts: `auth`, `data`, and `viz`. 5 | 6 | Authentication 7 | ^^^^^^^^^^^^^^ 8 | 9 | It is possible to use CARTOframes without having a CARTO account. However, to have access to data enrichment or to discover 10 | useful datasets, being a CARTO user offers many advantages. 11 | This module is responsible for connecting the user with its CARTO account through given user credentials. 12 | 13 | Manage Data 14 | ^^^^^^^^^^^ 15 | 16 | From discovering and enriching data to applying data analyisis and geocoding methods, the 17 | CARTOframes API is built with the purpose of managing data without leaving the context of your notebook. 18 | 19 | Visualize Data 20 | ^^^^^^^^^^^^^^ 21 | 22 | The viz API is designed to create useful, beautiful and straightforward visualizations. 23 | It is both predefined and flexible, giving advanced users the possibility of building specific visualizations, 24 | but also offering multiple built-in methods to work faster with a few lines of code. 25 | -------------------------------------------------------------------------------- /docs/reference/io_functions.rst: -------------------------------------------------------------------------------- 1 | I/O functions 2 | ------------- 3 | 4 | .. automodule:: cartoframes 5 | :noindex: 6 | :members: 7 | :member-order: bysource 8 | :undoc-members: 9 | :show-inheritance: -------------------------------------------------------------------------------- /docs/reference/utils.rst: -------------------------------------------------------------------------------- 1 | Utils 2 | ----- 3 | 4 | .. automodule:: cartoframes.utils 5 | :noindex: 6 | :members: 7 | :member-order: bysource 8 | :undoc-members: 9 | :show-inheritance: -------------------------------------------------------------------------------- /docs/reference/viz.rst: -------------------------------------------------------------------------------- 1 | Viz 2 | --- 3 | 4 | Viz namespace contains all the classes to create visualizations based on data 5 | 6 | Map 7 | ^^^ 8 | 9 | .. autoclass:: cartoframes.viz.Map 10 | :noindex: 11 | :members: 12 | :member-order: bysource 13 | :show-inheritance: 14 | 15 | Layer 16 | ^^^^^ 17 | 18 | .. autoclass:: cartoframes.viz.Layer 19 | :noindex: 20 | :members: 21 | :member-order: bysource 22 | :show-inheritance: 23 | 24 | Source 25 | ^^^^^^ 26 | 27 | .. autoclass:: cartoframes.viz.Source 28 | :noindex: 29 | :members: 30 | :member-order: bysource 31 | :show-inheritance: 32 | 33 | Layout 34 | ^^^^^^ 35 | 36 | .. autoclass:: cartoframes.viz.Layout 37 | :noindex: 38 | :members: 39 | :member-order: bysource 40 | :show-inheritance: 41 | 42 | Styles 43 | ^^^^^^ 44 | 45 | .. automodule:: cartoframes.viz 46 | :noindex: 47 | :members: basic_style, 48 | color_bins_style, 49 | color_category_style, 50 | color_continuous_style, 51 | cluster_size_style, 52 | isolines_style, 53 | size_bins_style, 54 | size_category_style, 55 | size_continuous_style, 56 | animation_style 57 | 58 | Legends 59 | ^^^^^^^ 60 | 61 | .. automodule:: cartoframes.viz 62 | :noindex: 63 | :members: basic_legend, 64 | color_bins_legend, 65 | color_category_legend, 66 | color_continuous_legend, 67 | size_bins_legend, 68 | size_category_legend, 69 | size_continuous_legend, 70 | default_legend 71 | 72 | Widgets 73 | ^^^^^^^ 74 | 75 | .. automodule:: cartoframes.viz 76 | :noindex: 77 | :members: basic_widget, 78 | category_widget, 79 | formula_widget, 80 | histogram_widget, 81 | time_series_widget, 82 | animation_widget, 83 | default_widget 84 | 85 | Popups 86 | ^^^^^^ 87 | 88 | .. automodule:: cartoframes.viz 89 | :noindex: 90 | :members: popup_element, 91 | default_popup_element 92 | 93 | Publications 94 | ^^^^^^^^^^^^ 95 | 96 | .. automodule:: cartoframes.viz 97 | :noindex: 98 | :members: all_publications, 99 | delete_publication -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | # documentation requirements 2 | 3 | sphinx>=1.6.5 4 | sphinxcontrib-napoleon>=0.7.0 5 | -------------------------------------------------------------------------------- /docs/support/01-Support-options.md: -------------------------------------------------------------------------------- 1 | ## Support Options 2 | 3 | Feeling stuck? There are many ways to find help. 4 | 5 | * Ask a question on [GIS StackExchange](https://gis.stackexchange.com/questions/tagged/carto) using the `CARTO` tag. 6 | * [Report an issue](https://github.com/CartoDB/cartoframes/issues/new) in Github. 7 | * Enterprise Plan customers have additional access to enterprise-level support through CARTO's support representatives. 8 | 9 | If you just want to describe an issue or share an idea, just . 10 | 11 | ### Issues on GitHub 12 | 13 | If you think you may have found a bug, or if you have a feature request that you would like to share with the CARTOframes team, please [open an issue](https://github.com/CartoDB/cartoframes/issues/new). 14 | 15 | 16 | ### Community support on GIS Stack Exchange 17 | 18 | GIS Stack Exchange is the most popular community in the geospatial industry. This is a collaboratively-edited question and answer site for geospatial programmers and technicians. It is a fantastic resource for asking technical questions about developing and maintaining your application. 19 | 20 | 21 | When posting a new question, please consider the following: 22 | 23 | * Read the GIS Stack Exchange [help](https://gis.stackexchange.com/help) and [how to ask](https://gis.stackexchange.com/help/how-to-ask) pages for guidelines and tips about posting questions. 24 | * Be very clear about your question in the subject. A clear explanation helps those trying to answer your question, as well as those who may be looking for information in the future. 25 | * Be informative in your post. Details, code snippets, logs, screenshots, etc. help others to understand your problem. 26 | * Use code that demonstrates the problem. It is very hard to debug errors without sample code to reproduce the problem. 27 | 28 | ### Enterprise Plan Customers 29 | 30 | Enterprise Plan customers have additional support options beyond general community support. As per your account Terms of Service, you have access to enterprise-level support through CARTO's support representatives available at [enterprise-support@carto.com](mailto:enterprise-support@carto.com) 31 | 32 | In order to speed up the resolution of your issue, provide as much information as possible (even if it is a link from community support). This allows our engineers to investigate your problem as soon as possible. 33 | 34 | If you are not yet a CARTO customer, browse our [plans & pricing](https://carto.com/pricing/) and find the right plan for you. 35 | -------------------------------------------------------------------------------- /docs/support/02-Contribute.md: -------------------------------------------------------------------------------- 1 | ## Contribute 2 | 3 | CARTOframes is an open-source library. We are more than happy to receive your contributions to the code and the documentation as well. 4 | 5 | ### Filling a ticket 6 | 7 | If you want to [open a new issue in our repository](https://github.com/cartodb/cartoframes/issues/new), please follow these instructions: 8 | 9 | - Add a descriptive title. 10 | - Write a good description, answering the following questions: 11 | - What is happening? 12 | - What should happen instead? 13 | - How to reproduce the issue. Remember to include Browser, OS and CARTOframes version. 14 | - Try to add an example showing the problem. 15 | 16 | ### Contributing code 17 | 18 | The best part of open source: collaborate directly with our [CARTOframes code](https://github.com/cartodb/cartoframes)! If you have fixed a bug or have a new functionality on your hands, we'd love to see it! These are the steps you should follow: 19 | 20 | - Sign our Contributor License Agreement (CLA) [Learn more here](https://carto.com/contributions/) 21 | - Fork the CARTOframes repository. 22 | - Create a new branch in your forked repository. 23 | - Commit your changes. Remeber to add new tests if possible. 24 | - Open a pull request. 25 | - Any of the CARTOframes mantainers will take a look. 26 | - If everything works, it will be merged and released \o/. 27 | - If you want more detailed information, [this GitHub guide](https://opensource.guide/how-to-contribute/) is a must. 28 | 29 | ### Completing documentation 30 | 31 | CARTOframes documentation is located in docs/. That folder is the content that appears in CARTO's [Developer Center](https://carto.com/developers/cartoframes). Follow the instructions described above and once we accept your pull request it will appear online :). 32 | 33 | ### Submitting contributions 34 | 35 | You will need to sign a Contributor License Agreement (CLA) before making a submission. [Learn more here](https://carto.com/contributions/). -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cartoframes", 3 | "version": "1.2.0", 4 | "description": "*********** CARTOframes ***********", 5 | "main": "./cartoframes/assets/src/index.js", 6 | "directories": { 7 | "doc": "docs", 8 | "example": "examples", 9 | "test": "test" 10 | }, 11 | "scripts": { 12 | "test": "echo \"Error: no test specified\" && exit 1", 13 | "dev": "rollup --config --watch", 14 | "build": "rollup --config" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/CartoDB/cartoframes.git" 19 | }, 20 | "author": "", 21 | "license": "ISC", 22 | "bugs": { 23 | "url": "https://github.com/CartoDB/cartoframes/issues" 24 | }, 25 | "homepage": "https://github.com/CartoDB/cartoframes#readme", 26 | "devDependencies": { 27 | "jshint": "^2.10.2", 28 | "rollup": "^1.17.0", 29 | "rollup-plugin-node-resolve": "^5.2.0" 30 | }, 31 | "dependencies": { 32 | "d3-format": "^1.4.4" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # check setup.py's REQUIRES variable for requirements 2 | . 3 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from 'rollup-plugin-node-resolve'; 2 | 3 | module.exports = { 4 | input: 'cartoframes/assets/src/index.js', 5 | output: { 6 | file: 'cartoframes/assets/src/bundle.js', 7 | format: 'iife', 8 | name: 'init' 9 | }, 10 | plugins: [ resolve() ], 11 | watch: { 12 | include: [ 13 | 'cartoframes/assets/src/**/*.js', 14 | 'cartoframes/assets/**/*.j2' 15 | ], 16 | exclude: [ 17 | 'node_modules/**', 18 | 'cartoframes/assets/src/bundle.js' 19 | ] 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal=1 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | from setuptools import setup, find_packages 6 | 7 | 8 | def walk_subpkg(name): 9 | data_files = [] 10 | package_dir = 'cartoframes' 11 | for parent, _, files in os.walk(os.path.join(package_dir, name)): 12 | # Remove package_dir from the path. 13 | sub_dir = os.sep.join(parent.split(os.sep)[1:]) 14 | for _file in files: 15 | data_files.append(os.path.join(sub_dir, _file)) 16 | return data_files 17 | 18 | 19 | def get_version(): 20 | _version = {} 21 | with open('cartoframes/_version.py') as fp: 22 | exec(fp.read(), _version) 23 | return _version['__version__'] 24 | 25 | 26 | REQUIRES = [ 27 | 'appdirs>=1.4.3,<2.0', 28 | 'carto>=1.11.3,<2.0', 29 | 'markupsafe<=2.0.1', 30 | 'jinja2>=2.10.1,<3.0', 31 | 'pandas>=0.25.0', 32 | 'geopandas>=0.6.0,<1.0', 33 | 'unidecode>=1.1.0,<2.0', 34 | 'semantic_version>=2.8.0,<3' 35 | ] 36 | 37 | 38 | EXTRAS_REQUIRES_TESTS = [ 39 | 'pytest', 40 | 'pytest-mock', 41 | 'pylint', 42 | 'flake8' 43 | ] 44 | 45 | 46 | PACKAGE_DATA = { 47 | '': [ 48 | 'LICENSE', 49 | 'CONTRIBUTORS', 50 | ], 51 | 'cartoframes': [ 52 | 'assets/*', 53 | 'assets/*.j2' 54 | ] + walk_subpkg('assets'), 55 | } 56 | 57 | 58 | DISTNAME = 'cartoframes' 59 | DESCRIPTION = 'CARTO Python package for data scientists' 60 | LICENSE = 'BSD' 61 | URL = 'https://github.com/CartoDB/cartoframes' 62 | AUTHOR = 'CARTO' 63 | EMAIL = 'contact@carto.com' 64 | 65 | 66 | setup( 67 | name=DISTNAME, 68 | version=get_version(), 69 | 70 | description=DESCRIPTION, 71 | long_description=open('README.rst').read(), 72 | long_description_content_type='text/x-rst', 73 | license=LICENSE, 74 | url=URL, 75 | 76 | author=AUTHOR, 77 | author_email=EMAIL, 78 | 79 | classifiers=[ 80 | 'Development Status :: 5 - Production/Stable', 81 | 'Intended Audience :: Developers', 82 | 'Intended Audience :: Science/Research', 83 | 'License :: OSI Approved :: BSD License', 84 | 'Programming Language :: Python', 85 | 'Programming Language :: Python :: 3', 86 | 'Programming Language :: Python :: 3.5', 87 | 'Programming Language :: Python :: 3.6', 88 | 'Programming Language :: Python :: 3.7', 89 | 'Programming Language :: Python :: 3.8' 90 | ], 91 | keywords=['carto', 'data', 'science', 'maps', 'spatial', 'pandas'], 92 | 93 | packages=find_packages(), 94 | package_data=PACKAGE_DATA, 95 | package_dir={'cartoframes': 'cartoframes'}, 96 | include_package_data=True, 97 | 98 | install_requires=REQUIRES, 99 | extras_require={ 100 | 'tests': EXTRAS_REQUIRES_TESTS 101 | }, 102 | python_requires='>=3.5' 103 | ) 104 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CartoDB/cartoframes/1f56b75f512da62b7ecd1c07e1021b8552158907/tests/__init__.py -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | from cartoframes.utils import setup_metrics 2 | 3 | 4 | def pytest_configure(config): 5 | """ 6 | Allows plugins and conftest files to perform initial configuration. 7 | This hook is called for every plugin and initial conftest 8 | file after command line options have been parsed. 9 | """ 10 | setup_metrics(False) 11 | 12 | 13 | def pytest_sessionstart(session): 14 | """ 15 | Called after the Session object has been created and 16 | before performing collection and entering the run test loop. 17 | """ 18 | 19 | 20 | def pytest_sessionfinish(session, exitstatus): 21 | """ 22 | Called after whole test run finished, right before 23 | returning the exit status to the system. 24 | """ 25 | 26 | 27 | def pytest_unconfigure(config): 28 | """ 29 | called before test process is exited. 30 | """ 31 | -------------------------------------------------------------------------------- /tests/e2e/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CartoDB/cartoframes/1f56b75f512da62b7ecd1c07e1021b8552158907/tests/e2e/__init__.py -------------------------------------------------------------------------------- /tests/e2e/data/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CartoDB/cartoframes/1f56b75f512da62b7ecd1c07e1021b8552158907/tests/e2e/data/__init__.py -------------------------------------------------------------------------------- /tests/e2e/data/client/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CartoDB/cartoframes/1f56b75f512da62b7ecd1c07e1021b8552158907/tests/e2e/data/client/__init__.py -------------------------------------------------------------------------------- /tests/e2e/data/observatory/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CartoDB/cartoframes/1f56b75f512da62b7ecd1c07e1021b8552158907/tests/e2e/data/observatory/__init__.py -------------------------------------------------------------------------------- /tests/e2e/data/observatory/catalog/files/private-dataset.csv: -------------------------------------------------------------------------------- 1 | BLOCKGROUP,HHDCY,MLTCY44111,MLTCY44112,MLTCY44121,MLTCY44122,MLTCY44131,MLTCY44132,MLTCY44211,MLTCY44221,MLTCY44229,MLTCY44311,MLTCY44411,MLTCY44412,MLTCY44413,MLTCY44419,MLTCY44421,MLTCY44422,MLTCY44511,MLTCY44512,MLTCY44521,MLTCY44522,MLTCY44523,MLTCY44529,MLTCY44531,MLTCY44611,MLTCY44612,MLTCY44613,MLTCY44619,MLTCY44719,MLTCY44811,MLTCY44812,MLTCY44813,MLTCY44814,MLTCY44815,MLTCY44819,MLTCY44821,MLTCY44831,MLTCY44832,MLTCY45111,MLTCY45112,MLTCY45113,MLTCY45114,MLTCY45121,MLTCY45211,MLTCY45291,MLTCY45299,MLTCY45311,MLTCY45321,MLTCY45322,MLTCY45331,MLTCY45391,MLTCY45392,MLTCY45393,MLTCY45399,MLTCY45411,MLTCY45421,MLTCY45431,MLTCY45439,MLTCY7211,MLTCY7212,MLTCY7213,MLTCY7221,MLTCY7222,MLTCY7223,MLTCY7224,RSGCY44111,RSGCY44112,RSGCY44121,RSGCY44122,RSGCY44131,RSGCY44132,RSGCY44211,RSGCY44221,RSGCY44229,RSGCY44311,RSGCY44411,RSGCY44412,RSGCY44413,RSGCY44419,RSGCY44421,RSGCY44422,RSGCY44511,RSGCY44512,RSGCY44521,RSGCY44522,RSGCY44523,RSGCY44529,RSGCY44531,RSGCY44611,RSGCY44612,RSGCY44613,RSGCY44619,RSGCY44719,RSGCY44811,RSGCY44812,RSGCY44813,RSGCY44814,RSGCY44815,RSGCY44819,RSGCY44821,RSGCY44831,RSGCY44832,RSGCY45111,RSGCY45112,RSGCY45113,RSGCY45114,RSGCY45121,RSGCY45211,RSGCY45291,RSGCY45299,RSGCY45311,RSGCY45321,RSGCY45322,RSGCY45331,RSGCY45391,RSGCY45392,RSGCY45393,RSGCY45399,RSGCY45411,RSGCY45421,RSGCY45431,RSGCY45439,RSGCY7211,RSGCY7212,RSGCY7213,RSGCY7221,RSGCY7222,RSGCY7223,RSGCY7224,geoid,do_date 2 | 10159819031,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10159819031,2018 3 | -------------------------------------------------------------------------------- /tests/e2e/data/observatory/catalog/files/private-geography.csv: -------------------------------------------------------------------------------- 1 | geoid,do_label,do_area,do_perimeter,do_num_vertices,geom 2 | 010010201001,Block Group 1,4277639.594,11282.424,90,"POLYGON((-86.505128 32.476833, -86.505188 32.47647, -86.50546 32.476215, -86.50553 32.475926, -86.505997 32.475628, -86.50614 32.475338, -86.506568 32.475232, -86.507076 32.474913, -86.507301 32.474898, -86.507518 32.474601, -86.507784 32.474598, -86.507942 32.474227, -86.508201 32.474123, -86.50853 32.473805, -86.509367 32.473332, -86.509679 32.472938, -86.510065 32.472609, -86.510377 32.472254, -86.510291 32.472032, -86.509823 32.472181, -86.509103 32.472011, -86.50877 32.471747, -86.508804 32.471468, -86.508616 32.470858, -86.508761 32.470764, -86.508776 32.470467, -86.508552 32.470262, -86.507391 32.469907, -86.503491 32.468676, -86.501889 32.468125, -86.500803 32.467441, -86.500202 32.466884, -86.498841 32.465508, -86.497977 32.464531, -86.497405 32.463743, -86.496642 32.462516, -86.494284 32.458602, -86.490355 32.452126, -86.489467 32.450789, -86.488683 32.449727, -86.488277 32.450933, -86.48785 32.451805, -86.487148 32.45277, -86.486025 32.454136, -86.484945 32.455258, -86.48437 32.455916, -86.483515 32.456843, -86.48218 32.45846, -86.481836 32.45879, -86.480431 32.459972, -86.480195 32.460369, -86.480139 32.460832, -86.480291 32.461393, -86.480758 32.461903, -86.481569 32.462443, -86.481861 32.462809, -86.481913 32.463099, -86.48188 32.463496, -86.479449 32.46334, -86.478711 32.462491, -86.478271 32.4621, -86.477419 32.461505, -86.47691 32.460929, -86.476915 32.460576, -86.476588 32.460347, -86.476369 32.460051, -86.475574 32.459408, -86.475368 32.459307, -86.475247 32.45988, -86.475609 32.460011, -86.475395 32.462413, -86.475368 32.463057, -86.475472 32.463476, -86.475244 32.465954, -86.475305 32.466347, -86.475816 32.467139, -86.476108 32.467647, -86.476973 32.469161, -86.477484 32.470132, -86.479245 32.473298, -86.479682 32.473962, -86.482069 32.472975, -86.485722 32.470653, -86.492392 32.472927, -86.49391 32.473464, -86.494775 32.473734, -86.499422 32.475293, -86.503307 32.476652, -86.504772 32.477141, -86.505128 32.476833))" 3 | -------------------------------------------------------------------------------- /tests/e2e/data/observatory/catalog/files/public-geography.csv: -------------------------------------------------------------------------------- 1 | geoid,do_label,do_area,do_perimeter,do_num_vertices,geom 2 | 01001020100,201,9842681.054,15985.502,79,"POLYGON((-86.478671 32.491083, -86.478962 32.492727, -86.480386 32.496003, -86.481193 32.497585, -86.483008 32.500284, -86.484273 32.50164, -86.488262 32.505159, -86.489328 32.503782, -86.490042 32.502661, -86.490767 32.50188, -86.49104 32.500707, -86.491853 32.498907, -86.49399 32.49581, -86.494654 32.494625, -86.496032 32.493485, -86.497085 32.492277, -86.498065 32.490935, -86.498485 32.489751, -86.499392 32.488629, -86.500353 32.48693, -86.500786 32.485642, -86.501564 32.484932, -86.502038 32.484098, -86.501965 32.483204, -86.502406 32.482228, -86.50321 32.481592, -86.503544 32.480896, -86.503156 32.480052, -86.503542 32.479388, -86.503565 32.478231, -86.504068 32.477193, -86.504534 32.477241, -86.505128 32.476833, -86.50553 32.475926, -86.50614 32.475338, -86.506568 32.475232, -86.50853 32.473805, -86.509367 32.473332, -86.510377 32.472254, -86.509103 32.472011, -86.50877 32.471747, -86.508552 32.470262, -86.503491 32.468676, -86.501889 32.468125, -86.500803 32.467441, -86.498841 32.465508, -86.497405 32.463743, -86.490355 32.452126, -86.488683 32.449727, -86.48785 32.451805, -86.486025 32.454136, -86.483515 32.456843, -86.48218 32.45846, -86.480431 32.459972, -86.480139 32.460832, -86.480291 32.461393, -86.481861 32.462809, -86.48188 32.463496, -86.479449 32.46334, -86.478711 32.462491, -86.477419 32.461505, -86.476915 32.460576, -86.475368 32.459307, -86.475609 32.460011, -86.475368 32.463057, -86.475472 32.463476, -86.475244 32.465954, -86.475816 32.467139, -86.479245 32.473298, -86.480782 32.475416, -86.481131 32.476078, -86.481268 32.476866, -86.481147 32.479933, -86.480795 32.482281, -86.480524 32.483201, -86.479809 32.484918, -86.479409 32.486174, -86.478867 32.489342, -86.478671 32.491083))" 3 | 01001020200,202,3347045.634,9787.267,44,"POLYGON((-86.479409 32.486174, -86.479809 32.484918, -86.480524 32.483201, -86.480795 32.482281, -86.481147 32.479933, -86.481268 32.476866, -86.481131 32.476078, -86.480782 32.475416, -86.479245 32.473298, -86.475816 32.467139, -86.475244 32.465954, -86.475472 32.463476, -86.475368 32.463057, -86.475609 32.460011, -86.475368 32.459307, -86.474581 32.458863, -86.473648 32.45767, -86.472537 32.457034, -86.471552 32.456249, -86.470334 32.456116, -86.470287 32.457217, -86.46764 32.456683, -86.467354 32.459308, -86.4671 32.461941, -86.467536 32.466462, -86.467502 32.467182, -86.466256 32.471085, -86.465772 32.472917, -86.465376 32.473723, -86.470619 32.473658, -86.470802 32.474821, -86.47082 32.476303, -86.470534 32.47713, -86.469693 32.478637, -86.46956 32.479394, -86.469585 32.484605, -86.469452 32.485557, -86.468237 32.487299, -86.46731 32.489203, -86.470319 32.489331, -86.475213 32.489161, -86.476716 32.489168, -86.478867 32.489342, -86.479409 32.486174))" 4 | -------------------------------------------------------------------------------- /tests/e2e/data/observatory/enrichment/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CartoDB/cartoframes/1f56b75f512da62b7ecd1c07e1021b8552158907/tests/e2e/data/observatory/enrichment/__init__.py -------------------------------------------------------------------------------- /tests/e2e/data/observatory/enrichment/files/points-private.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } }, 4 | "features": [ 5 | { "type": "Feature", "properties": { "RSGCY7224": 20020, "MLTCY7224": 20020, "BLOCKGROUP": 170312604001, "do_area": 327680.71699525212 }, "geometry": { "type": "Point", "coordinates": [ -87.736916406250003, 41.88582102814744 ] } }, 6 | { "type": "Feature", "properties": { "RSGCY7224": 20020, "MLTCY7224": 20020, "BLOCKGROUP": 170312604001, "do_area": 327680.71699525212 }, "geometry": { "type": "Point", "coordinates": [ -87.73681640625, 41.885921028147443 ] } }, 7 | { "type": "Feature", "properties": { "RSGCY7224": -99700, "MLTCY7224": 39680, "BLOCKGROUP": 551010021003, "do_area": 28969878.741730917 }, "geometry": { "type": "Point", "coordinates": [ -88.137817382809999, 42.670319772519058 ] } }, 8 | { "type": "Feature", "properties": { "RSGCY7224": 132348, "MLTCY7224": 132348, "BLOCKGROUP": 172010043003, "do_area": 166383226.50189239 }, "geometry": { "type": "Point", "coordinates": [ -89.357299804679997, 42.216313604344769 ] } }, 9 | { "type": "Feature", "properties": { "RSGCY7224": 47627, "MLTCY7224": 47627, "BLOCKGROUP": 170630006003, "do_area": 280786919.78601474 }, "geometry": { "type": "Point", "coordinates": [ -88.52783203125, 41.141433026536284 ] } } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /tests/e2e/data/observatory/enrichment/files/points-public-filter.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } }, 4 | "features": [ 5 | { "type": "Feature", "properties": { "poverty": null, "one_car": null, "geoid": null, "do_area": null }, "geometry": { "type": "Point", "coordinates": [ -87.736916406250003, 41.88582102814744 ] } }, 6 | { "type": "Feature", "properties": { "poverty": null, "one_car": null, "geoid": null, "do_area": null }, "geometry": { "type": "Point", "coordinates": [ -87.73681640625, 41.885921028147443 ] } }, 7 | { "type": "Feature", "properties": { "poverty": 167.0, "one_car": 301.0, "geoid": 55101002100, "do_area": 88948180.559350401 }, "geometry": { "type": "Point", "coordinates": [ -88.137817382809999, 42.670319772519058 ] } }, 8 | { "type": "Feature", "properties": { "poverty": 175.0, "one_car": 547.0, "geoid": 17201004300, "do_area": 184387686.63525927 }, "geometry": { "type": "Point", "coordinates": [ -89.357299804679997, 42.216313604344769 ] } }, 9 | { "type": "Feature", "properties": { "poverty": null, "one_car": null, "geoid": null, "do_area": null }, "geometry": { "type": "Point", "coordinates": [ -88.52783203125, 41.141433026536284 ] } } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /tests/e2e/data/observatory/enrichment/files/points.geojson: -------------------------------------------------------------------------------- 1 | {"type":"FeatureCollection","features":[ 2 | {"type":"Feature","properties":{},"geometry":{"type":"Point","coordinates":[-87.73691640625,41.88582102814744]}}, 3 | {"type":"Feature","properties":{},"geometry":{"type":"Point","coordinates":[-87.73681640625,41.88592102814744]}}, 4 | {"type":"Feature","properties":{},"geometry":{"type":"Point","coordinates":[-88.13781738281,42.67031977251906]}}, 5 | {"type":"Feature","properties":{},"geometry":{"type":"Point","coordinates":[-89.35729980468,42.21631360434477]}}, 6 | {"type":"Feature","properties":{},"geometry":{"type":"Point","coordinates":[-88.52783203125,41.14143302653628]}} 7 | ]} 8 | -------------------------------------------------------------------------------- /tests/e2e/data/observatory/enrichment/files/polygon-private-avg.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } }, 4 | "features": [ 5 | { "type": "Feature", "properties": { "RSGCY7224": -25998.225, "MLTCY7224": 40793.35 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -87.83843994140625, 41.832990869731084 ], [ -87.750205993652344, 41.807149141688363 ], [ -87.750205993652344, 41.834269892288312 ], [ -87.83843994140625, 41.832990869731084 ] ] ] } } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /tests/e2e/data/observatory/enrichment/files/polygon-public-agg-custom-by-var.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } }, 4 | "features": [ 5 | { "type": "Feature", "properties": { "poverty": 4497.199539839602, "one_car": 15, "geoid": "17031814200,17031815300,17031815701,17031820800,17031820700,17031815800,17031814400,17031815702,17031815400,17031560200,17031814500,17031560300,17031814300,17031815500,17031815200" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -87.83843994140625, 41.832990869731084 ], [ -87.750205993652344, 41.807149141688363 ], [ -87.750205993652344, 41.834269892288312 ], [ -87.83843994140625, 41.832990869731084 ] ] ] } } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /tests/e2e/data/observatory/enrichment/files/polygon-public-agg-custom-filter.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } }, 4 | "features": [ 5 | { "type": "Feature", "properties": { "poverty": 3694.5163418376524, "one_car": 3034.585708376479 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -87.83843994140625, 41.832990869731084 ], [ -87.750205993652344, 41.807149141688363 ], [ -87.750205993652344, 41.834269892288312 ], [ -87.83843994140625, 41.832990869731084 ] ] ] } } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /tests/e2e/data/observatory/enrichment/files/polygon-public.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } }, 4 | "features": [ 5 | { "type": "Feature", "properties": { "poverty": 627.06666666666672, "one_car": 4175.1847674103328 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -87.83843994140625, 41.832990869731084 ], [ -87.750205993652344, 41.807149141688363 ], [ -87.750205993652344, 41.834269892288312 ], [ -87.83843994140625, 41.832990869731084 ] ] ] } } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /tests/e2e/data/observatory/enrichment/files/polygon.geojson: -------------------------------------------------------------------------------- 1 | {"type":"FeatureCollection","features":[ 2 | {"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-87.83843994140625,41.832990869731084],[-87.75020599365234,41.80714914168836],[-87.75020599365234,41.83426989228831],[-87.83843994140625,41.832990869731084]]]}} 3 | ]} 4 | -------------------------------------------------------------------------------- /tests/e2e/data/services/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CartoDB/cartoframes/1f56b75f512da62b7ecd1c07e1021b8552158907/tests/e2e/data/services/__init__.py -------------------------------------------------------------------------------- /tests/e2e/data/services/fixtures/enrichment_points.csv: -------------------------------------------------------------------------------- 1 | __enrichment_id,__geom_column 2 | 1,POINT (-79.887 36.082835) 3 | 2,POINT (-80.4061889648438 25.5151787443985) 4 | 3,POINT (-98.5021405 40.868677) 5 | 4,POINT (-107.299463 31.820398) 6 | 5,POINT (-83.987743 44.507068) 7 | -------------------------------------------------------------------------------- /tests/e2e/data/services/fixtures/enrichment_polygons.csv: -------------------------------------------------------------------------------- 1 | __enrichment_id,__geom_column 2 | 1,POLYGON((-90.79369349999999 40.3670515, -90.79369349999999 40.5484845, -90.56156849999999 40.5484845, -90.56156849999999 40.3670515)) 3 | 2,POLYGON((-96.9603555 43.58713899999999, -96.9603555 43.762051, -96.6221105 43.762051, -96.6221105 43.58713899999999)) 4 | 3,POLYGON((-98.612071 40.7853815, -98.612071 40.9595765, -98.392263 40.9595765, -98.392263 40.7853815)) 5 | 4,POLYGON((-97.3415835 44.890463, -97.3415835 45.064705000000004, -97.0354245 45.064705000000004, -97.0354245 44.890463)) 6 | 5,POLYGON((-90.47619175 31.087278249999997, -90.47619175 31.26260875, -90.33176725000001 31.26260875, -90.33176725000001 31.087278249999997)) 7 | -------------------------------------------------------------------------------- /tests/e2e/helpers.py: -------------------------------------------------------------------------------- 1 | """Utility functions for cartoframes testing""" 2 | import json 3 | import logging 4 | import os 5 | import warnings 6 | 7 | import pytest 8 | 9 | 10 | class _UserUrlLoader: 11 | def user_url(self): 12 | user_url = None 13 | if os.environ.get('USERURL') is None: 14 | try: 15 | creds = json.loads(open('tests/e2e/secret.json').read()) 16 | user_url = creds['USERURL'] 17 | except Exception: 18 | warnings.warn('secret.json not found') 19 | 20 | if user_url in (None, ''): 21 | user_url = 'https://{username}.carto.com/' 22 | 23 | return user_url 24 | 25 | 26 | QUOTAS = {} 27 | 28 | 29 | def _update_quotas(service, quota): 30 | if service not in QUOTAS: 31 | QUOTAS[service] = { 32 | 'initial': None, 33 | 'final': None 34 | } 35 | QUOTAS[service]['final'] = quota 36 | if QUOTAS[service]['initial'] is None: 37 | QUOTAS[service]['initial'] = quota 38 | return quota 39 | 40 | 41 | def _report_quotas(): 42 | """Run pytest with options --log-level=info --log-cli-level=info 43 | to see this message about quota used during the tests 44 | """ 45 | for service in QUOTAS: 46 | used_quota = QUOTAS[service]['final'] - QUOTAS[service]['initial'] 47 | logging.info("TOTAL USED QUOTA for %s: %d", service, used_quota) 48 | 49 | 50 | class _ReportQuotas: 51 | @pytest.fixture(autouse=True, scope='class') 52 | def module_setup_teardown(self): 53 | yield 54 | _report_quotas() 55 | 56 | @classmethod 57 | def update_quotas(cls, service, quota): 58 | return _update_quotas(str(cls) + '_' + service, quota) 59 | -------------------------------------------------------------------------------- /tests/e2e/secret.json.sample: -------------------------------------------------------------------------------- 1 | { 2 | "APIKEY": "", 3 | "USERNAME": "", 4 | "USERURL": "https://{username}.carto.com/" 5 | } 6 | -------------------------------------------------------------------------------- /tests/notebooks/creds.sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "username": "your_username", 3 | "api_key": "your_api_key" 4 | } -------------------------------------------------------------------------------- /tests/notebooks/requirements.txt: -------------------------------------------------------------------------------- 1 | notebook==6.4.1 2 | pytest==6.1.1 3 | pytest-mock==3.3.1 4 | nbconvert==6.0.7 5 | ipykernel==5.3.4 6 | ipywidgets==7.5.1 7 | matplotlib==3.3.2 8 | xlrd==1.2.0 9 | seaborn==0.11.0 10 | google-auth-oauthlib==0.4.3 11 | google-cloud-storage==1.37.0 12 | google-cloud-bigquery==2.13.1 13 | -------------------------------------------------------------------------------- /tests/unit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CartoDB/cartoframes/1f56b75f512da62b7ecd1c07e1021b8552158907/tests/unit/__init__.py -------------------------------------------------------------------------------- /tests/unit/analysis/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CartoDB/cartoframes/1f56b75f512da62b7ecd1c07e1021b8552158907/tests/unit/analysis/__init__.py -------------------------------------------------------------------------------- /tests/unit/auth/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CartoDB/cartoframes/1f56b75f512da62b7ecd1c07e1021b8552158907/tests/unit/auth/__init__.py -------------------------------------------------------------------------------- /tests/unit/data/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CartoDB/cartoframes/1f56b75f512da62b7ecd1c07e1021b8552158907/tests/unit/data/__init__.py -------------------------------------------------------------------------------- /tests/unit/data/client/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CartoDB/cartoframes/1f56b75f512da62b7ecd1c07e1021b8552158907/tests/unit/data/client/__init__.py -------------------------------------------------------------------------------- /tests/unit/data/observatory/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CartoDB/cartoframes/1f56b75f512da62b7ecd1c07e1021b8552158907/tests/unit/data/observatory/__init__.py -------------------------------------------------------------------------------- /tests/unit/data/observatory/catalog/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CartoDB/cartoframes/1f56b75f512da62b7ecd1c07e1021b8552158907/tests/unit/data/observatory/catalog/__init__.py -------------------------------------------------------------------------------- /tests/unit/data/observatory/catalog/repository/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CartoDB/cartoframes/1f56b75f512da62b7ecd1c07e1021b8552158907/tests/unit/data/observatory/catalog/repository/__init__.py -------------------------------------------------------------------------------- /tests/unit/io/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CartoDB/cartoframes/1f56b75f512da62b7ecd1c07e1021b8552158907/tests/unit/io/__init__.py -------------------------------------------------------------------------------- /tests/unit/io/managers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CartoDB/cartoframes/1f56b75f512da62b7ecd1c07e1021b8552158907/tests/unit/io/managers/__init__.py -------------------------------------------------------------------------------- /tests/unit/io/test_crs.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from geopandas import GeoDataFrame 4 | from shapely.geometry import Point 5 | 6 | from cartoframes.auth import Credentials 7 | from cartoframes.io.carto import to_carto 8 | from cartoframes.io.managers.context_manager import ContextManager 9 | from cartoframes.viz import Layer 10 | 11 | CREDENTIALS = Credentials('fake_user', 'fake_api_key') 12 | 13 | 14 | @pytest.mark.parametrize('crs', ['epsg:4326', 'epsg:2263', 'epsg:3395']) 15 | def test_transform_crs_layer(crs): 16 | # Given 17 | gdf = GeoDataFrame({'geometry': [Point([0, 0])]}, crs=crs) 18 | 19 | # When 20 | Layer(gdf) # No error! 21 | 22 | 23 | @pytest.mark.parametrize('crs', ['epsg:4326', 'epsg:2263', 'epsg:3395']) 24 | def test_transform_crs_to_carto(mocker, crs): 25 | cm_mock = mocker.patch.object(ContextManager, 'copy_from') 26 | 27 | # Given 28 | gdf = GeoDataFrame({'geometry': [Point([0, 0])]}, crs=crs) 29 | 30 | # When 31 | to_carto(gdf, 'table_name', CREDENTIALS, skip_quota_warning=True) 32 | 33 | # Then 34 | cm_mock.assert_called_once_with(mocker.ANY, 'table_name', 'fail', True, 3) 35 | -------------------------------------------------------------------------------- /tests/unit/mocks/__init__.py: -------------------------------------------------------------------------------- 1 | from .kuviz_mock import CartoKuvizMock 2 | 3 | 4 | def mock_kuviz(name, html, credentials=None, password=None): 5 | return CartoKuvizMock(name=name, password=password) 6 | -------------------------------------------------------------------------------- /tests/unit/mocks/api_key_mock.py: -------------------------------------------------------------------------------- 1 | class TableMock(): 2 | def __init__(self, name): 3 | self.name = name 4 | 5 | 6 | class GrantsMock(): 7 | def __init__(self, tables): 8 | self.tables = [TableMock(table) for table in tables] 9 | 10 | 11 | class APIKeyMock(): 12 | def __init__(self, name, token, tables): 13 | self.name = name 14 | self.token = token 15 | self.type = None 16 | self.created_at = None 17 | self.updated_at = None 18 | self.grants = GrantsMock(tables) 19 | self.exists = True 20 | 21 | def delete(self): 22 | self.exists = False 23 | 24 | 25 | class APIKeyManagerMock(): 26 | def __init__(self, token=''): 27 | self.token = token 28 | self.api_keys = {} 29 | 30 | def create(self, name, apis, tables): 31 | if name in self.api_keys and self.api_keys[name].exists: 32 | raise Exception('Validation failed: Name has already been taken') 33 | self.api_keys[name] = APIKeyMock(name, self.token, tables) 34 | return self.api_keys[name] 35 | 36 | def get(self, name): 37 | return self.api_keys[name] 38 | -------------------------------------------------------------------------------- /tests/unit/mocks/kuviz_mock.py: -------------------------------------------------------------------------------- 1 | import copy 2 | 3 | from carto.kuvizs import Kuviz 4 | from cartoframes.viz.kuviz import KuvizPublisher, kuviz_to_dict 5 | 6 | PRIVACY_PUBLIC = 'public' 7 | PRIVACY_PASSWORD = 'password' 8 | 9 | 10 | class CartoKuvizMock(Kuviz): 11 | def __init__(self, name, id='a12345', url='https://carto.com', password=None): 12 | self.id = id 13 | self.url = url 14 | self.name = name 15 | if password: 16 | self.privacy = PRIVACY_PASSWORD 17 | else: 18 | self.privacy = PRIVACY_PUBLIC 19 | 20 | def save(self): 21 | return True 22 | 23 | def delete(self): 24 | return True 25 | 26 | 27 | class KuvizPublisherMock(KuvizPublisher): 28 | def __init__(self): 29 | self._layers = [] 30 | 31 | def get_layers(self): 32 | return self._layers 33 | 34 | def set_layers(self, layers, maps_api_key): 35 | if maps_api_key: 36 | layers_copy = [] 37 | for layer in layers: 38 | layer_copy = copy.deepcopy(layer) 39 | layer_copy.credentials['api_key'] = maps_api_key 40 | layers_copy.append(layer_copy) 41 | layers = layers_copy 42 | self._layers = layers 43 | 44 | def publish(self, html, name, password, if_exists='fail'): 45 | self.kuviz = CartoKuvizMock(name, password=password) 46 | return kuviz_to_dict(self.kuviz) 47 | -------------------------------------------------------------------------------- /tests/unit/mocks/map_mock.py: -------------------------------------------------------------------------------- 1 | from cartoframes.viz import Map 2 | 3 | from ..mocks.kuviz_mock import KuvizPublisherMock 4 | 5 | 6 | class MapMock(Map): 7 | def _get_publisher(self): 8 | return KuvizPublisherMock(self.layers) 9 | 10 | def _get_publication_html(self, name, maps_api_key): 11 | return "

      Hi Kuviz yeee

      " 12 | -------------------------------------------------------------------------------- /tests/unit/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CartoDB/cartoframes/1f56b75f512da62b7ecd1c07e1021b8552158907/tests/unit/utils/__init__.py -------------------------------------------------------------------------------- /tests/unit/viz/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CartoDB/cartoframes/1f56b75f512da62b7ecd1c07e1021b8552158907/tests/unit/viz/__init__.py -------------------------------------------------------------------------------- /tests/unit/viz/popups/test_popup.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from cartoframes.viz.popup import Popup 4 | 5 | 6 | class TestPopup(object): 7 | def test_is_popup_defined(self): 8 | """Popup""" 9 | assert Popup is not None 10 | 11 | def test_popup_init(self): 12 | """Popup should be properly initialized""" 13 | popup_click_1 = Popup('click', 'pop') 14 | popup_hover_1 = Popup('hover', 'pop', 'Pop') 15 | 16 | assert popup_click_1.value == 'pop' 17 | assert popup_click_1.title == 'pop' 18 | assert popup_hover_1.value == 'pop' 19 | assert popup_hover_1.title == 'Pop' 20 | 21 | def test_popup_interactivity(self): 22 | """Popup should return a proper interactivity object""" 23 | 24 | popup_click_1 = Popup('click', 'pop') 25 | popup_hover_1 = Popup('hover', 'pop', 'Pop') 26 | 27 | assert popup_click_1.interactivity == { 28 | 'event': 'click', 29 | 'attrs': { 30 | 'name': 'v4f197c', 31 | 'title': 'pop', 32 | 'format': None 33 | } 34 | } 35 | 36 | assert popup_hover_1.interactivity == { 37 | 'event': 'hover', 38 | 'attrs': { 39 | 'name': 'v4f197c', 40 | 'title': 'Pop', 41 | 'format': None 42 | } 43 | } 44 | 45 | def test_popup_variables(self): 46 | """Popup should return a proper variables object""" 47 | 48 | popup_click_1 = Popup('click', 'pop') 49 | popup_hover_1 = Popup('hover', 'pop', 'Pop') 50 | 51 | assert popup_click_1.variable == { 52 | 'name': 'v4f197c', 53 | 'value': 'pop' 54 | } 55 | 56 | assert popup_hover_1.variable == { 57 | 'name': 'v4f197c', 58 | 'value': 'pop' 59 | } 60 | 61 | def test_wrong_attribute(self): 62 | """Popup should raise an error if popup property is not valid""" 63 | with pytest.raises(ValueError): 64 | Popup(1234) 65 | -------------------------------------------------------------------------------- /tests/unit/viz/popups/test_popup_list.py: -------------------------------------------------------------------------------- 1 | from cartoframes.viz import popup_element 2 | from cartoframes.viz.popup import Popup 3 | from cartoframes.viz.popup_list import PopupList 4 | 5 | popup_list = PopupList({ 6 | 'click': [popup_element('value_1'), popup_element('value_2')], 7 | 'hover': [popup_element('value_1'), popup_element('value_3')] 8 | }) 9 | 10 | 11 | class TestPopupList(object): 12 | def test_should_have_access_to_popup_elements(self): 13 | for element in popup_list.elements: 14 | assert isinstance(element, Popup) 15 | 16 | def test_should_get_all_popup_interactivities(self): 17 | assert popup_list.get_interactivity() == [ 18 | { 19 | 'event': 'click', 20 | 'attrs': { 21 | 'name': 'v72224b', 22 | 'title': 'value_2', 23 | 'format': None 24 | } 25 | }, { 26 | 'event': 'click', 27 | 'attrs': { 28 | 'name': 'vbc6799', 29 | 'title': 'value_1', 30 | 'format': None 31 | } 32 | }, { 33 | 'event': 'hover', 34 | 'attrs': { 35 | 'name': 'vc266e3', 36 | 'title': 'value_3', 37 | 'format': None 38 | } 39 | }, { 40 | 'event': 'hover', 41 | 'attrs': { 42 | 'name': 'vbc6799', 43 | 'title': 'value_1', 44 | 'format': None 45 | } 46 | } 47 | ] 48 | 49 | def test_should_get_all_popup_variables(self): 50 | assert popup_list.get_variables() == { 51 | 'vbc6799': "prop('value_1')", 52 | 'v72224b': "prop('value_2')", 53 | 'vbc6799': "prop('value_1')", 54 | 'vc266e3': "prop('value_3')" 55 | } 56 | -------------------------------------------------------------------------------- /tests/unit/viz/test_basemaps.py: -------------------------------------------------------------------------------- 1 | from cartoframes.viz import basemaps 2 | 3 | 4 | class TestBasemaps(object): 5 | def test_is_defined(self): 6 | "basemaps" 7 | assert basemaps is not None 8 | 9 | def test_has_defined_basemaps(self): 10 | "basemaps content" 11 | assert basemaps.positron == 'Positron' 12 | assert basemaps.darkmatter == 'DarkMatter' 13 | assert basemaps.voyager == 'Voyager' 14 | -------------------------------------------------------------------------------- /tests/unit/viz/test_layer.py: -------------------------------------------------------------------------------- 1 | from cartoframes.auth import Credentials 2 | from cartoframes.viz.legend_list import LegendList 3 | from cartoframes.viz.widget_list import WidgetList 4 | from cartoframes.viz.popup_list import PopupList 5 | from cartoframes.viz.source import Source 6 | from cartoframes.viz.style import Style 7 | from cartoframes.viz import Layer 8 | from cartoframes.io.managers.context_manager import ContextManager 9 | 10 | 11 | def setup_mocks(mocker, table_name): 12 | query = 'SELECT * FROM "public"."{}"'.format(table_name) 13 | mocker.patch.object(ContextManager, 'compute_query', return_value=query) 14 | mocker.patch.object(ContextManager, 'get_geom_type', return_value='point') 15 | mocker.patch.object(ContextManager, 'get_bounds') 16 | 17 | 18 | class TestLayer(object): 19 | def test_is_layer_defined(self): 20 | """Layer""" 21 | assert Layer is not None 22 | 23 | def test_initialization_objects(self, mocker): 24 | """Layer should initialize layer attributes""" 25 | setup_mocks(mocker, 'layer_source') 26 | layer = Layer(Source('layer_source', credentials=Credentials('fakeuser'))) 27 | 28 | assert layer.is_basemap is False 29 | assert layer.source_data == 'SELECT * FROM "public"."layer_source"' 30 | assert isinstance(layer.source, Source) 31 | assert isinstance(layer.style, Style) 32 | assert isinstance(layer.popups, PopupList) 33 | assert isinstance(layer.legends, LegendList) 34 | assert isinstance(layer.widgets, WidgetList) 35 | assert layer.interactivity == [] 36 | 37 | def test_initialization_simple(self, mocker): 38 | """Layer should initialize layer attributes""" 39 | setup_mocks(mocker, 'layer_source') 40 | layer = Layer('layer_source', {}, credentials=Credentials('fakeuser')) 41 | 42 | assert layer.is_basemap is False 43 | assert layer.source_data == 'SELECT * FROM "public"."layer_source"' 44 | assert isinstance(layer.source, Source) 45 | assert isinstance(layer.style, Style) 46 | assert isinstance(layer.popups, PopupList) 47 | assert isinstance(layer.legends, LegendList) 48 | assert isinstance(layer.widgets, WidgetList) 49 | assert layer.interactivity == [] 50 | -------------------------------------------------------------------------------- /tests/unit/viz/test_legend.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from cartoframes.viz.legend import Legend 4 | 5 | 6 | class TestLegend(object): 7 | def test_is_legend_defined(self): 8 | """Legend""" 9 | assert Legend is not None 10 | 11 | def test_legend_init_properties(self): 12 | """Legend should be properly initialized when passing properties""" 13 | legend = Legend('color-category', 14 | prop='stroke_color', 15 | title='[title]', 16 | description='[description]', 17 | footer='[footer]', 18 | dynamic=False) 19 | 20 | assert legend._type == 'color-category' 21 | assert legend._prop == 'stroke_color' 22 | assert legend._title == '[title]' 23 | assert legend._description == '[description]' 24 | assert legend._footer == '[footer]' 25 | assert legend._dynamic is False 26 | 27 | def test_legends_info(self): 28 | """Legend should return a proper information object""" 29 | legend = Legend('color-category', 30 | title='[title]', 31 | description='[description]', 32 | footer='[footer]') 33 | 34 | assert legend.get_info() == { 35 | 'ascending': False, 36 | 'type': 'color-category', 37 | 'prop': 'color', 38 | 'title': '[title]', 39 | 'description': '[description]', 40 | 'footer': '[footer]', 41 | 'dynamic': True, 42 | 'variable': '', 43 | 'format': None 44 | } 45 | 46 | def test_wrong_type(self): 47 | """Legend should raise an error if legend type is not valid""" 48 | msg = 'Legend type "xxx" is not valid. Valid legend types are: basic, default, ' +\ 49 | 'color-bins, color-bins-line, color-bins-point, color-bins-polygon, ' + \ 50 | 'color-category, color-category-line, color-category-point, color-category-polygon, ' + \ 51 | 'color-continuous, color-continuous-line, color-continuous-point, color-continuous-polygon, ' + \ 52 | 'size-bins, size-bins-line, size-bins-point, ' + \ 53 | 'size-category, size-category-line, size-category-point, ' + \ 54 | 'size-continuous, size-continuous-line, size-continuous-point.' 55 | with pytest.raises(ValueError) as e: 56 | Legend('xxx').get_info() 57 | assert str(e.value) == msg 58 | 59 | def test_wrong_prop(self): 60 | """Legend should raise an error if legend prop is not valid""" 61 | msg = 'Legend property "xxx" is not valid. Valid legend properties are: ' + \ 62 | 'color, stroke_color, size, stroke_width.' 63 | with pytest.raises(ValueError) as e: 64 | Legend('color-category', prop='xxx').get_info() 65 | assert str(e.value) == msg 66 | -------------------------------------------------------------------------------- /tests/unit/viz/test_palettes.py: -------------------------------------------------------------------------------- 1 | from cartoframes.viz import palettes 2 | 3 | 4 | class TestPalettes(object): 5 | def test_is_defined(self): 6 | "palettes" 7 | assert palettes is not None 8 | 9 | def test_has_defined_palettes(self): 10 | "palettes content" 11 | assert palettes.burg == 'BURG' 12 | assert palettes.burgyl == 'BURGYL' 13 | assert palettes.cb_set3 == 'CB_SET3' 14 | -------------------------------------------------------------------------------- /tests/unit/viz/test_style.py: -------------------------------------------------------------------------------- 1 | from cartoframes.viz.style import Style 2 | 3 | 4 | class TestStyle(object): 5 | def test_is_style_defined(self): 6 | """Style""" 7 | assert Style is not None 8 | 9 | def test_style_default_point(self): 10 | """Style.compute_viz should return the default viz for point""" 11 | style = Style() 12 | viz = style.compute_viz('point') 13 | 14 | assert 'color: hex("#EE4D5A")' in viz 15 | assert 'width: ramp(linear(zoom(),0,18),[2,10])' in viz 16 | assert 'strokeWidth: ramp(linear(zoom(),0,18),[0,1])' in viz 17 | assert 'strokeColor: opacity(#222,ramp(linear(zoom(),0,18),[0,0.6]))' in viz 18 | 19 | def test_style_default_line(self): 20 | """Style.compute_viz should return the default viz for line""" 21 | style = Style() 22 | viz = style.compute_viz('line') 23 | 24 | assert 'color: hex("#4CC8A3")' in viz 25 | assert 'width: ramp(linear(zoom(),0,18),[0.5,4])' in viz 26 | 27 | def test_style_default_polygon(self): 28 | """Style.compute_viz should return the default viz for polygon""" 29 | style = Style() 30 | viz = style.compute_viz('polygon') 31 | 32 | assert 'color: hex("#826DBA")' in viz 33 | assert 'strokeWidth: ramp(linear(zoom(),2,18),[0.5,1])' in viz 34 | assert 'strokeColor: opacity(#2c2c2c,ramp(linear(zoom(),2,18),[0.2,0.6]))' in viz 35 | -------------------------------------------------------------------------------- /tests/unit/viz/utils.py: -------------------------------------------------------------------------------- 1 | from geopandas import GeoDataFrame, points_from_xy 2 | 3 | 4 | def build_geodataframe(lats, lngs, extra_columns=[]): 5 | columns = {'lat': lats, 'lng': lngs} 6 | for extra_column in extra_columns: 7 | columns[extra_column] = lats 8 | return GeoDataFrame(columns, geometry=points_from_xy(columns['lng'], columns['lat'])) 9 | -------------------------------------------------------------------------------- /tests/unit/viz/widgets/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CartoDB/cartoframes/1f56b75f512da62b7ecd1c07e1021b8552158907/tests/unit/viz/widgets/__init__.py -------------------------------------------------------------------------------- /tests/unit/viz/widgets/test_animation_widget.py: -------------------------------------------------------------------------------- 1 | from cartoframes.viz import widgets 2 | 3 | 4 | class TestAnimationWidget(object): 5 | def test_widgets(self): 6 | "should be defined" 7 | assert widgets.animation_widget is not None 8 | 9 | def test_factory(self): 10 | "should create an animation widget" 11 | widget = widgets.animation_widget(title='Animation Widget') 12 | widget_info = widget.get_info() 13 | assert widget_info.get('type') == 'animation' 14 | assert widget_info.get('title') == 'Animation Widget' 15 | -------------------------------------------------------------------------------- /tests/unit/viz/widgets/test_basic_widget.py: -------------------------------------------------------------------------------- 1 | from cartoframes.viz import widgets 2 | 3 | 4 | class TestBasicWidget(object): 5 | def test_widgets(self): 6 | "should be defined" 7 | assert widgets.basic_widget is not None 8 | 9 | def test_factory(self): 10 | "should create a basic widget" 11 | widget = widgets.basic_widget(title='Default Widget') 12 | widget_info = widget.get_info() 13 | assert widget_info.get('type') == 'basic' 14 | assert widget_info.get('title') == 'Default Widget' 15 | -------------------------------------------------------------------------------- /tests/unit/viz/widgets/test_category_widget.py: -------------------------------------------------------------------------------- 1 | from cartoframes.viz import widgets 2 | 3 | 4 | class TestCategoryWidget(object): 5 | def test_widgets(self): 6 | "should be defined" 7 | assert widgets.category_widget is not None 8 | 9 | def test_factory(self): 10 | "should create a category widget" 11 | widget = widgets.category_widget("prop('value')", title='Category Widget') 12 | widget_info = widget.get_info() 13 | assert widget_info.get('type') == 'category' 14 | assert widget_info.get('value') == "prop('value')" 15 | assert widget_info.get('title') == 'Category Widget' 16 | assert widget_info.get('options').get('readOnly') is False 17 | -------------------------------------------------------------------------------- /tests/unit/viz/widgets/test_formula_widget.py: -------------------------------------------------------------------------------- 1 | from cartoframes.viz import widgets 2 | 3 | 4 | class TestFormulaWidget(object): 5 | def test_widgets(self): 6 | "should be defined" 7 | assert widgets.formula_widget is not None 8 | 9 | def test_factory(self): 10 | "should create a default formula widget" 11 | widget = widgets.formula_widget('value', title='Formula Widget') 12 | widget_info = widget.get_info() 13 | assert widget_info.get('type') == 'formula' 14 | assert widget_info.get('value') == "prop('value')" 15 | assert widget_info.get('title') == 'Formula Widget' 16 | 17 | def test_count_formula_viewport(self): 18 | "should create a formula widget to count viewport features" 19 | widget = widgets.formula_widget('value', operation='count') 20 | widget_info = widget.get_info() 21 | assert widget_info.get('value') == 'viewportCount()' 22 | 23 | def test_count_formula_global(self): 24 | "should create a formula widget to count global features" 25 | widget = widgets.formula_widget('value', operation='count', is_global=True) 26 | widget_info = widget.get_info() 27 | assert widget_info.get('value') == 'globalCount()' 28 | 29 | def test_formula_viewport(self): 30 | "should create a formula widget to get a viewport operation" 31 | widget = widgets.formula_widget('value', operation='avg') 32 | widget_info = widget.get_info() 33 | assert widget_info.get('value') == "viewportAvg(prop('value'))" 34 | 35 | def test_formula_global(self): 36 | "should create a formula widget to get a global operation" 37 | widget = widgets.formula_widget('value', operation='avg', is_global=True) 38 | widget_info = widget.get_info() 39 | assert widget_info.get('value') == "globalAvg(prop('value'))" 40 | -------------------------------------------------------------------------------- /tests/unit/viz/widgets/test_histogram_widget.py: -------------------------------------------------------------------------------- 1 | from cartoframes.viz import widgets 2 | 3 | 4 | class TestHistogramWidget(object): 5 | def test_widgets(self): 6 | "should be defined" 7 | assert widgets.histogram_widget is not None 8 | 9 | def test_factory(self): 10 | "should create a histogram widget" 11 | widget = widgets.histogram_widget("prop('value')", title='Histogram Widget') 12 | widget_info = widget.get_info() 13 | assert widget_info.get('type') == 'histogram' 14 | assert widget_info.get('value') == "prop('value')" 15 | assert widget_info.get('title') == 'Histogram Widget' 16 | assert widget_info.get('options').get('readOnly') is False 17 | -------------------------------------------------------------------------------- /tests/unit/viz/widgets/test_time_series_widget.py: -------------------------------------------------------------------------------- 1 | from cartoframes.viz import widgets 2 | 3 | 4 | class TestTimeSeriesWidget(object): 5 | def test_widgets(self): 6 | "should be defined" 7 | assert widgets.time_series_widget is not None 8 | 9 | def test_factory(self): 10 | "should create a time series widget" 11 | widget = widgets.time_series_widget("prop('value')", title='Time Series Widget') 12 | widget_info = widget.get_info() 13 | assert widget_info.get('type') == 'time-series' 14 | assert widget_info.get('value') == "prop('value')" 15 | assert widget_info.get('title') == 'Time Series Widget' 16 | assert widget_info.get('options').get('readOnly') is False 17 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py35, py36, py37, py38 3 | 4 | [gh-actions] 5 | python = 6 | 3.5: py35 7 | 3.6: py36 8 | 3.7: py37 9 | 3.8: py38 10 | 11 | [testenv] 12 | deps = 13 | flake8 14 | pytest 15 | pytest-mock 16 | commands = 17 | {envpython} --version 18 | flake8 cartoframes tests 19 | py.test --basetemp="{envtmpdir}" tests/unit 20 | 21 | [testenv:unit] 22 | deps = 23 | flake8 24 | pytest 25 | pytest-mock 26 | commands = 27 | {envpython} --version 28 | flake8 tests/unit 29 | py.test --basetemp="{envtmpdir}" tests/unit 30 | 31 | [testenv:e2e] 32 | deps = 33 | flake8 34 | pytest 35 | commands = 36 | {envpython} --version 37 | flake8 tests/e2e 38 | py.test --basetemp="{envtmpdir}" tests/e2e 39 | 40 | [testenv:cov] 41 | deps = 42 | pytest 43 | pytest-mock 44 | pytest-cov 45 | commands = 46 | {envpython} --version 47 | py.test --basetemp="{envtmpdir}" --cov=cartoframes tests/unit 48 | 49 | [testenv:cov-html] 50 | deps = 51 | pytest 52 | pytest-mock 53 | pytest-cov 54 | commands = 55 | {envpython} --version 56 | py.test --basetemp="{envtmpdir}" --cov=cartoframes --cov-report html tests/unit 57 | 58 | [testenv:lint] 59 | deps = 60 | pylint 61 | commands = 62 | {envpython} --version 63 | pylint --rcfile=.pylintrc cartoframes tests 64 | --------------------------------------------------------------------------------